ondrejnepozitek / edgar-dotnet Goto Github PK
View Code? Open in Web Editor NEWConfigurable procedural layout generator
Home Page: https://ondrejnepozitek.github.io/Edgar-DotNet/docs/introduction
License: MIT License
Configurable procedural layout generator
Home Page: https://ondrejnepozitek.github.io/Edgar-DotNet/docs/introduction
License: MIT License
It is needed to describe a format that will be used to load rooms and input graphs when using GUI. The format should probably support all the options that can be used through the api.
One way is to distinguish two types of files: one for map description (graph) and another one for room description.
Another open question is the language that should be used for the files. Candidates are for example YAML or JSON.
name: rectangles
default:
doors-mode:
type: specific-positions
door-positions:
- [[0,0], [0,3]]
rooms:
-
name: 4-square
shape: [[0,0], [0,4], [4,4], [4,0]]
doors-mode:
type: overlap
door-length: 1
minimum-overlap: 1
-
name: 2-4-rectangle
shape: [[0,0], [0,4], [2,4], [2,0]]
default-room-shapes:
-
set-name: rectangles
rotate: true
probability: 4
scale: [2,2]
exclude: [4-square]
-
set-name: rectangles
room-name: 4-square
probability: 8
scale: [2,2]
rooms:
- name: 1
- name: 2
- name: 3
-
name: boss-room
room-shapes:
-
set-name: boss-rooms-set
passages:
- [1,2]
- [2,3]
- [3,boss-room]
None.
None.
None.
None.
In the first phase, user should be able to configure basic parameters of the algorithm. He should be able to set the number of layouts that should be generated, set the random generator seed. It should be also possible to either choose from already created map description config files or upload a new one.
New button will open after clicking the "Generate" button. It should show the progress of the generation and results.
None.
None.
None.
None.
Simulated annealing's goal is to generate several layouts that will be used to add more chains and make a final layout. This reports describes what layouts are accepted in an individual run of a simulated annealing.
The goal is to not accept every valid layout that is encountered. The motivation behind that is that if one layout fails as a starting point for further extending, we do not want to try it again with a layout that is very similar to the previous one because it would most likely fail, too.
The difference check is currently mainly based on a position difference of positions of corresponding configurations in a layout. The difference is computed as a difference of centers of bounding rectangles (we want the computation to be as fast as possible to not slow down the algorithm). The total difference is then normalized to reflect number of rooms and their sizes and the result is compared to all already generated layouts.
Multiple values of a minimal distance were benchmarked.
It seems that values between 0.2 and 1.6 behave in a similar way. More than 80 tries would be probably needed to determine the best option. Too small and too big values did not perform very vell.
None.
Difference computing is currently not invariant to scaling of rooms. It is divided by the average size of rooms but the scale already deformed the computation as the distance is squared before the normalization. See #22
The program should be at least as fast as the original paper. The problem is that the paper works with real coordinates so it is (in my opinion) easier to generate a valid layout. But it should be enough to show that scaling the rooms up yields similar results as the paper.
Scaling the rooms up sometimes hardly converges (e.g. the graph with 41 vertices).
See description.
Generating a layout for a grid is more complicated than using real coordinates. Grid layout is also more general as it can be used even when working with real coordinates but not the other way around.
It sometimes happens that the way the earlier chains are laid out causes problems in the later stages of the algorithm.
It should not happen that later chains are connected e.g. to the first chain. But it sometimes happens.
Currently, the branching factor is set to 5. This may be too much if we have to backtrack really far. From some initial results, it seems like some inputs do not benefit from decreasing the branching factor at all. But if the result is improved decreasing the branching factor, it seems like it benefits the most from branching factor of 2.
Commit 8c9c5be added 4 more transformation to existing rotation. However, format of configs was not update to reflect this change. For now, it is only possible to use either all transformations or none. Moreover, the config key is called "rotate", which is obviously wrong.
There could be a switch that would ensure that really different layouts would be outputted. There should also be a matric that would take the total number of rooms into account when deciding whether two layouts are different enough.
What does it mean that layouts are different enough? We are doing a depth-first search with backtracking so it is very likely that all the layouts will be different but also have a few common chains. Is it ok? Or should we start a completely new run of the algorithm to ensure that no two chains are the same? Or is it enough to sometimes perturb a node that is not currently in the last chain? Or should we try to choose a chain at random and perturb it? Will it converge?
None.
Layouts have a lot in common.
None.
None.
Silly question - is it possible to mark a room as having a doorway that is not connected to another area.
Use case -
Over-world - for external areas, you use a door - to take you into an interior - at which point you get a new map.
Inside the interior - maps created by your library are used for the interior - but we still need a doorway to mark the place that we entered the map.
This would be a door in a room that does not connect to another room.
When trying to use the DLLs included on the latest release on a new project, it throws a DllNotFoundException (GeneralAlgorithms.Algorithms.Common.BoostWrapper.IsPlanar(Int32[] edges, Int32 edgesCount, Int32 verticesCount)
if Boostwrapper isn't found (although the installation tutorial indicates it isn't necessary), and if the DLL is added to the bin folder (since Virtual Studio refuses to add it as a dependency), it throws a NullReferenceException(GeneralAlgorithms.Algorithms.Common.CollectionExtensions.GetWeightedRandom[T](IList
1 elements, Func2 weightSelector, Random random)
, in both cases when trying to call layoutGenerator.GetLayout
Just in case it helps, this is my code:
protected override void Initialize()
{
base.Initialize();
Utils.CenterWindow(this, graphics);
IsMouseVisible = true;
var mapDescription = new MapDescription<int>();
mapDescription.AddRoom(0);
mapDescription.AddRoom(1);
mapDescription.AddRoom(2);
mapDescription.AddRoom(3);
mapDescription.AddPassage(0, 1);
mapDescription.AddPassage(0, 3);
mapDescription.AddPassage(1, 2);
mapDescription.AddPassage(2, 3);
var doorMode = new OverlapMode(1, 1);
var squareRoom = new RoomDescription(GridPolygon.GetSquare(8), doorMode);
var rectangleRoom = new RoomDescription(GridPolygon.GetRectangle(6, 10), doorMode);
var corridor = new RoomDescription(GridPolygon.GetSquare(1), new OverlapMode(1, 1));
mapDescription.AddRoomShapes(squareRoom);
mapDescription.AddRoomShapes(rectangleRoom);
mapDescription.AddCorridorShapes(corridor);
mapDescription.SetWithCorridors(true, new List<int>() { 1 });
var layoutGenerator = LayoutGeneratorFactory.GetChainBasedGeneratorWithCorridors<int>(new List<int>() { 1 });
var generatedLayouts = layoutGenerator.GetLayouts(mapDescription, 1);
}
It may sound stupid, but there are situations where it's useful to allow doors with zero length. Probably the main usecase for this is if we have to switch from Unity tilemaps to our representation of room shapes. The problem is that when we convert doors from Unity to this library, the length is in fact reduced by 1. And that means, that doors with size 1 in Unity are represented as zero length doors in the library, which is currently not possible.
The main problem when allowing zero length doors is with corner points. We can't deduce in which direction should other rooms be connected because there are two valid possibilities. One possible solution is to either allow connections in both directions or explicitly specify the direction if only one is allowed.
The algorithm should not depend on the scale of the rooms. If both polygons and respective doors are equally scaled, the algorithm should produce similar results with similar speed.
None.
The algorithm converges in a similar way with regard to the number of iterations. However, the number of iterations per second decreases as rooms are scaled up. It should be further investigated what part of the algorithms slows it down.
Number of iterations and iterations per seconds should not be worse when scaling rooms up.
The reason why scaling up the rooms yields worse results is still unknown.
Picking a random intersection now operates in O(1) rather than in O(n) with "n" being the length of a chosen line.
Method AddNodeGreedily
was changed in a way that it does not search the whole space but rather it tries every i-th point. Where i is chosen in a way that the number of points is left the same. It increases the speed of iterations and has little effect on convergence rate.
ConfigurationSpacesGenerator
is currently very slow when scaling up the rooms and must be reworked.
Map description should allow user to normalize polygon probabilities. It would mean that shapes with a lot of rotations do not discrimate for example squares that have only one rotation. Their probabilities are therefore normalized in a way that for example all non-square rectangles have probabilities 1/2 (because there are two rotations). This option therefore makes sense only when polygon rotating is enabled.
None.
Rooms with small number of rotations are discriminated and it then looks like they are in the minority.
None.
None.
To build CppCliWrapper, one must have the whole Boost library installed. It should be sufficient to distribute the program with only a few header files. There should be a utility that lets you extract only the header files that you really need.
None.
The Wrapper is configured in a way that it finds a boost installation on the disk.
None.
None.
I am currently not able to build it in 64 bits.
Simulated annealing can easily generate multiple partial layouts. Straightforward implementation would be to return a list of layouts. However, this could result in generating layouts that are never used. With C#'s yield construct it is quite easy to implement a lazy evaluation that generates a layout only when it is really needed.
With lazy evaluation, we can decrease the number of iterations in most of runs. At worst, the number of iterations stays the same because if we needed to generate a "full tree" of layers, both lazy and non-lazy solutions would behave in a same way.
Simulated annealing was changed in a way that it uses yield return
to fill an IEnumerable
. It is then very easy to implement a generator planner that evaluated the problem lazily. In fact, it is now easier to make it lazy because the infrastructure is already prepared. BasicGeneratorPlanner
was implemented to showcase the difference between lazy and "classic" evaluation.
When implemented correctly, lazy evaluation is a technique that increases the converge without any side effects.
Some input's convergence is heavily influenced by the lazy evaluation.
For boss rooms, etc..
Use case:
Online roguelike / mud style game - On my overworld (1000x1000 grid) - every 5x5 pixel is a cell- I use this to navigate and draw players position on world map.
I was hoping to use this library for drawing maps for interior areas - such as castles etc.
Instead of drawing a 5x5 pixel of where they are - Now I would simply highlight the room that they are in.
In order to do that- I need to be able to translate Room 1 - into an array of points that correspond with the corners of that room.
Any ideas how to do this with the outputs currently provided?
It is currently possible to pick a valid position for two rooms that have different lengths of doors.
None.
Configuration spaces should check that the door lengths match. It must also be implemented in the method that post-process layouts to add doors.
None.
Prepare a project documentation with for example https://docusaurus.io/.
None.
None.
None.
None.
Through generator planner debug logs, it was possible to get a valuable information about where most of the time is spent when the algorithm is stuck. With that information, few changes were proposed.
None.
Generator planner logs showed that a lot of time was spent on unsuccessful attempts. The simulated annealing ended often after more than 2000 iterations were done. That is quite much. Therefore, the number of iterations per each cycle was decreased in order to use random restarts sooner.
Currently, each simulated annealing run can generate up to 15 layouts. But it was discovered that it is too much. It results in a very wide tree if a layout is bad and most of the generated layouts fail to generate anything. Using this information, the maximum number of generated layout was decreased to 5 layouts.
Proposed changes heavily influenced the convergence rate of the algorithm. Both median and average number of iterations decreased.
None.
None.
None.
None.
None.
None.
<< Breadth first, Lazy, Sigma avg 100, Difference from avg, Perturb inside >> (80 repeats)
---------------------------------------------------------------------------------------------------------
Name | # layouts | Time first | Time ten | Iterations | Iterations/sec
---------------------------------------------------------------------------------------------------------
Fig 1 (17 vertices) .98/1 .20s/.05s .21s/.05s 8.42k/1.60k 39k/30k
Fig 7 top (9 vertices) 1/1 .00s/.00s .00s/.00s .09k/.02k 29k/10k
Fig 7 bottom (17 vertices) .68/1 .57s/.36s .70s/.59s 27.99k/27.66k 40k/47k
Fig 8 (41 vertices) .95/1 3.10s/1.02s 3.62s/1.02s 89.13k/29.82k 24k/29k
Fig 9 (15 vertices) 1/1 .69s/.09s .69s/.09s 22.10k/3.39k 32k/37k
=========================================================================================================
The goal of this feature is to improve the performance of the algorithm by observing how it behaves on a given input and then propose improvements of the generator's configuration based on these observations.
Currently, there is a single configuration that i used for all inputs. The goal of the bachelor thesis was to make the algorithm usable during runtime so the configuration is already much better than it was in the original paper. However, it is still far from perfect. The problem is that it is quite hard to improve the configuration without seeing how the generator behaves on the input.
So instead of trying to guess how to change the configuration by looking only at the input, I propose to run the algorithm with a given input (e.g. 1000 times), gather statistics and then look at these statistics and propose improvements. The good thing is that we can repeat this process and see whether the performance improved or not.
The resulting optimization technique will be a simple evolutionary algorithm that will start with a single individual (the initial configuration) and then apply mutations (changes to that configuration) with the goal of finding an individual with the best fitness (the configuration with the best performance).
The optimization process will take some time as we need to repeatedly run the algorithm for an extended time to collect enough data. However, it is sufficient to run this optimization once for every input so it can be done for example before releasing a game.
Running the algorithm on multiple threads could hugely improve overall performance. It can not, however, be implemented before e.g. #17 is resolved as it is not currently clear what part of the algorithm should be done with multiple threads.
One way to do it would be to run the whole algorithm multiple times which would often mean that the running time is equal to time to generate first * number of layouts to generate / number of cores. The problem with this approach is that it could generate very similar layouts without a way to control the process.
Another approach could be to use one or more threads to do the depth-first search and also use one or more threads to plan what layouts should be expanded.
None.
None.
None.
None.
Algorithm to decompose graph into chains is needed.
None.
3 different algorithms are currently implemented. The best one yet is the one that uses a breadth first search and tries to minimize the number of chains that have only 1 vertex. The problem is that the reference graph with 17 vertices has a worse convergence rate to using a handmade decomposition.
None.
Quality of the chain decomposition plays a crucial role in the convergence speed of the algorithm.
Convergence is now better than with the handmade decomposition. However, only 3 inputs were tested. It should be tested at least on all the inputs from the reference paper.
All reference inputs are now implemented. Some of them should be further investigated to see if the chain decomposition could be improved.
All reference inputs seem to behave well using the best chain decomposition.
The algorithm works best with polygons that are quite simple - having fewer rectangles in the decomposition means cheaper checks if two polygons overlap or not. The goal would be, therefore, to preprocess room shapes to make them simpler while retaining their original door positions.
The goal of this feature is to generate each room chain in two distinct stages. In the first stage, only a subset of rooms is laid out, ignoring all the others rooms. In the second stage, all the other rooms are laid out and after this step succeeds, the next chain can be generated.
The main motivation of this feature is the generalization of corridors. Currently, there are special implementations of some parts of the generator that allow corridors to be generated (LayoutOperationsWithCorridors, CorridorsData, ConfigurationSpacesGenerator). With the two-stage generation, corridors could be treated as another room type that is alwys handled in the second stage of the generation process. It would also mean that we would be able to exactly which rooms should be connected by corridors.
AddCorridors
from SimulatedAnnealingEvolver
, replace with calling the second stage logicIt would probably not be very practical because we would have to implement configuration spaces that can handle case where two or more stage-two rooms are missing between two stage-one rooms.
Solution: It will not be supported for now
Being implemented in the dev-corridor-configuration-spaces branch
Option 1
Save complete mapping of which position of two non-corridor rooms corresponds to which positions and shapes of corridors.
Pros: Should be fast during runtime if memory requirements are not too high.
Cons: Does not scale very well because we have to compute individual points and not just work with lines. That will results in higher computational cost when computing these configuration spaces. Could be solved by precomputing configuration spaces.
Option 2
Do not save any mapping.
Pros: Fast computation of configuration spaces.
Cons: Probably not that much slower during runtime. When trying to choose a corridor shape, we do not know which shape to pick. So we pick a random one and check whether the intersection of configuration space of the two non-corridor rooms is empty or not. If it is empty we try another shape. That means that we can eliminate a given shape after computing one configuration spaces intersection.
Option 3
Mixed approach. Save only some information.
How should the input look like? Dot language?
It should be somewhere check that to MapDescription desribes a connected graph. The algorithm is not made to handle not connected graphs and it probably doesn't even make sense.
None.
Not connected graphs may cause exceptions for example when handling chain decomposition.
None.
This report describes how random restarting of the Simulated annealing affects the algorithm. Number of failed iterations is counted and with more failed attempts there is a bigger chance to stop the function and return what has been already generated.
Simulated annealing can get stuck so the motivation is to try to evolve the layout from a different starting point when a lot of failed attempts are made.
It is important to determine what action results in a successful iteration. Three such places were tried: on valid layout encounter; on valid layout and different enough and at last on an accepted layout (by the simulated annealing). It was also tried to reset the counter when encountering an accepting condition. And at last the probabilities of restarting were scaled.
OnValidAndDifferent dominated all the other place choices so it seems like this is the only option to be further investigated. The performance regarding the scale and resets was pretty similar among the options tried. It also seems like that bigger probabilities of restarting are better.
The main benchmark was done on 50 repeats which may not be enough.
It looks like it is (for now) safe to keep the default setting to be OnValidAndDifferent, without resets, scale one.
Benchmark showed that having a kind of random restarting definitely helps with the converge of the generator.
This is a specific idea that would be useful to my game project - but figured it might be re-usable.
In my game I am working on - each room type will have a specific amount of 'slots' where furniture/devices/interact-able objects can go.
At the moment I don't have anything in place to visualise the slots - but I imagine it would be a case of drawing a series of smaller squares inside a room - ideally adjacent to the walls.
The image would then be visualised with a series of grey outlined boxes inside the room shape.
In my game - players could then choose to put objects / devices / technology into those slots.
Eg: A player who does not have detect trap - gets the original map - minus the trap outlines, but when they 'detect' the trap - the dungeon master reveals the map that has the traps visible.
The goal of this feature is to simplify the ChainBasedGenerator class. The problem is that the current implementation can generate layouts for different map descriptions without having to create a new instance of the generator. To support that, the generator provides multiple SomethingCreator() classes that are used to set factories for individual components of the algorithm. This makes the algorithm needlessly complex e.g. because it must have additional generic parameters.
The goal is to make the generator simpler. It will possibly mean that users will have to do more of the work themselves (decomposing graphs, creating configuration spaces, etc.). However, even now, we need factory methods to create an instance of the generator so we may implement something similar with the new algorithm.
The new implementation will be probably specialized on a single map description - we will probably have to create a new generator instance for each map description. But that is something that is already being done for example when benchmarking the library, so it is probably not an issue.
Learning parameters for simulated annealing, like when to restart the current run.
Check how hard it would be to convert the libraries into .NET Standard.
Rectangles are currently disadvantaged because they have at most 2 rotations whereas other more complex polygons can have up to 4 rotations.
There are quite a few exceptions that don't have any message.
After implementing corridors, a new situation in graph decomposition was revealed. There are graphs where faces are not directly connected to other faces but there is a path or tree that connects them. Previous implementation of chain decomposition was not able to handle this case correctly as it was done in a way that all faces were processed first (and had to be connected to at least one other face) and only then chains were processed.
None.
This situation is very usual after adding corridors. Chain decomposition with corridors should be as similar to the decomposition without corridors as possible.
None.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.