Jump to content

Yet another random map generator


Pyrophorus
 Share

Recommended Posts

I read through the mapfile now. Two organizational things:

  • At this point we might want to start using our code review platform instead, then I can post inline comments right in the code, might make things easier. http://code.wildfiregames.com/
  • A copy of the WIP library code in the map is not going to be committed by me. The library file counts 430 lines, the mapfile 1136 lines. The library must be removed from the map. A random map script should consist of a buch of texture/entity/height constants + 20-50 function calls. Lops should almost always be library functions. I suspect your demo map will be that short too if the code is sorted.

I didn't test nor comprehend the basic idea of the algorithm yet. But here things I discovered when reading the file for the first time:

  • About your HeightArray, we pass u16 arrays to the engine, so that should be the same limit here rather than u32. That can be more easily created using new Array(g_Map.getSize() + 1).fill(0).map(zero => new Uint8Array(g_Map.getSize() + 1).fill(false)); (for example). Changing this.mSize before an operation and reverting this change afterwards is bad practice, just create a helper variable or repeat this sum.
  • Magic numbers like 10000 should be avoided. At least a global constant, but better letting the random map scripts determine the best numbers themselves. Is that histogram thing only debug code and can be removed anyhow?
  • The duplication in HeightArray.prototype.createAltitudes should be removed using a loop, possibly a function.
  • Whenever there are x and z, those should be in a vector. Most often one create the vectors in advance. Sometimes it improves performance, othertimes it doesn't. I haven't an inacceptable occurence yet and I don't want anybody to read ugly code to reduce the loading screen time from 15 to 12 seconds in the best case (some maps need 1-2 min).
  • "tObjMap.gCells[j][Symbol.iterator] = function* () {" never seen such code, probably avoidable. I have some reading to do. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*
  • "var snowLimit = 32000;" that's a height right? In a23 I have moved most height constants to the top of the file and uniformly called them height*, i.e. `const heightFooBar = 3;`. They are grouped, so one can quickly compare their values and see which heights are used by the map immediately.
  • for (let i = 0; i < mapSize; i++) for (let j = 0; j < mapSize; j++) Whenever we see that, we should think about transforming that into a createArea call on a MapBoundsPlacer. The effect on that area should be moved to a new painter in case it doesn't match an existing painter. This way it can be reused on arbitrary areas. In places where we only need to collect the points, we can save the result of a createArea call.

  • if(test < idx) test = idx; Each statement on a separate line.

  • if (g_MapSettings.CircularMap) { newline before the {

  • while( space after while

  • warn, log -> those are g_Map.log calls which also result in that thing being timed.

  • let cent1 = (patches[dist.b1].barx - centPos) * (patches[dist.b1].barx - centPos) + (patches[dist.b1].bary - centPos) * (patches[dist.b1].bary - centPos);

  • let cent2 = (patches[dist.b2].barx - centPos) * (patches[dist.b2].barx - centPos) + (patches[dist.b2].bary - centPos) * (patches[dist.b2].bary - centPos); That's exactly where the vector operations halve the width and height of these 2 lines and reveal geometric intent.  (Isn't that a cross, product and perpendicular vector somwehere? I would have to interpret first)

  • log("Creating base for player " + id + "..."); That entire loop is gone using rP20815

  • "crocodiles and palms on the shore" don't. That loop implements a custom coordinate randomization, but it should leave that up to createObjectGroups or createObjectGroupsByArea. One could implement a constraint for this g_TOMap.gCells[zx][zy].done check, though I'm not sure what that does and if it isn't redundant with tileclasses.

I will test now.

  • Thanks 1
Link to comment
Share on other sites

1 hour ago, elexis said:
On 2/2/2018 at 8:50 PM, Pyrophorus said:

FractalPainter

Gimme gimme gimme!

You have it in the YAMGLibrary I posted yesterday...

1 hour ago, elexis said:
18 hours ago, Pyrophorus said:

The script is self sufficient. There is no need to install a library as I inserted all the needed code into.

If it contains functions that ought to be in the library, then we'll put it in the library. Otherwise we're just adding work for later. But that's not too much work for now. I can transfer it.

Well... Catch what you want in the script.

1 hour ago, elexis said:
18 hours ago, Pyrophorus said:

Suggestions about decorations and such are welcomed of course, but not so deadly wanted

The saver of all boring flat maps: createBumps. Also createPatches / createLayeredPatches to randomize the textures a bit. The mountains should be painted according to Slope with the SlopeConstraint, I can look into it when adapting to a23. Another huge improvement are paths (consider the one on danubius for instance). Lorraine Plaine also shows how paths (here: rivers) can have a treeline parallel to them. It's just hard to try to find a path that doesn't break the terrain, so might need lots of retry loops which in turn can consume too much time. But there are remedies for that too (first computing placeable areas, then finding coordinates within that area, rather than picking random coordinates and testing against constraint that are highly likely to fail).

I put his in my TODO list.

1 hour ago, elexis said:
18 hours ago, Pyrophorus said:

encounter some cases where some parts of the map are unreachable and even players probably cannot join, because mountains/water pools are on the way. I aware of this and will provide a solution.

That's more problematic. Eventually we will need some reachability test. I also ran into this problem bigtime when reducing the radius of the playerbase flattening on Ambush and had to add a ramp so that the players can reach a nearby bluff (so that they can't be surrounded by an impassable bluff) #4993. A nice solution would be to somehow detect the unreachable case and use createPassage (a23 function) or a PathPlacer + elevation setter to force a way to somewhere else. But tricky to achieve and likely messy (typically paths should be placed first too and terrain only placed around it).

Ahem... Do you think something like that ?

preview-3.jpg.5ddd173a1ebec85b250de57f1de6dff7.jpg

 

This is far from perfect, but one can catch the idea: the blue road digs its way through the mountains. This may solve the accessibility problem in cases like this one where the map is split in two by a probably impassable wall :

preview-4.jpg.8953dff90dae51290b510f2ca849f1b3.jpg

I worked faster than I thought and the linking of every caer by winding roads is OK as you can see (well.. enlarge the picture... :)). Ford crossing is already working: you have one near the middle of the map. I need to better manage mountain crossing, but it's not a difficult problem.

2 hours ago, elexis said:
18 hours ago, Pyrophorus said:

swiss-knife placer

Whut? Let me see. (I'm really missing a new centered placer, we only have the two ones, ClumpPlacer and ChainPlacer, which were iirc present when rmgen code was in C++ 12 years ago).

Yeah, I think rmgen would really benefit to have algorithms of this kind along with the existing ones. This because rmgen mainly works with geometric shapes, circles, rectangles, more or less randomized. Instead, I make a heavy use of filling algorithms and more accurately, stack based filling algorithms. Its like pouring slowly some liquid on a surface. The liquid spot will grow until it eventually finds a boundary, and if the surface is not perfectly regular, will quickly turn to a twisted shape. The surface slope and the viscosity of the liquid rules the expansion. In the picture above, one can see examples. The green surfaces have been drawn with viscosity, using the tiles slope, within a height fork: this results in rather regular shapes, near rectangular, where the terrain is flat and unlimited. The players bases are drawn with even more viscosity and some noise on the borders, and, on the contrary, woods are set using little viscosity and much more slope influence, so the shape is much less compact. Even roads are set with a variant of this algorithm.

Friendly,

 

  • Like 1
Link to comment
Share on other sites

Hi !

13 hours ago, elexis said:
  • At this point we might want to start using our code review platform instead, then I can post inline comments right in the code, might make things easier. http://code.wildfiregames.com/
  •  

I'm sorry but I have no idea what I'm supposed to do on this site. Create an account ?

 

14 hours ago, elexis said:

A copy of the WIP library code in the map is not going to be committed by me. The library file counts 430 lines, the mapfile 1136 lines. The library must be removed from the map. A random map script should consist of a buch of texture/entity/height constants + 20-50 function calls. Lops should almost always be library functions. I suspect your demo map will be that short too if the code is sorted.

As you say, this is work in progress, so the code is not clean yet and not ready to be committed. That's why it still contains "magic numbers", useless calls to warn() and cosmetic defaults. Now, my job is to create the best scripts I can. Stating which part should go to a library or not is yours because it's your project, not mine. I have for now no clue of what you finally intend to do: merge some parts into the existing libraries ? Create a new one ? I have understood you only want the fractal map creation and painter, is it still true ?  Now, the code needs not to be sorted: the library part (or more accurately, the part I would push into a library if it was only my project) is at the beginning of the file and ends at the InitMap() line.

Now, about this:

14 hours ago, elexis said:
  • The duplication in HeightArray.prototype.createAltitudes should be removed using a loop, possibly a function.
  • Whenever there are x and z, those should be in a vector. Most often one create the vectors in advance. Sometimes it improves performance, othertimes it doesn't. I haven't an inacceptable occurence yet and I don't want anybody to read ugly code to reduce the loading screen time from 15 to 12 seconds in the best case (some maps need 1-2 min).
  • "tObjMap.gCells[j][Symbol.iterator] = function* () {" never seen such code, probably avoidable. I have some reading to do. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*
  •  

First of all, I'm an old man about 70, and I began programming those game maps in assembler on 8086 PCs running at 15Mhz with 64Ko RAM. Not to declare you're an ignorant youngster, but to say that at this time, saving memory and execution time was not an option, and I have kept the habit to have always an eye on it. Now I think programs are mainly made for computers, and not for humans. Of course, it's good practice to make as easy as possible programs understanding for maintenance and communication purposes, but not at any cost. Then yes, this line for instance:

 

14 hours ago, elexis said:

let cent1 = (patches[dist.b1].barx - centPos) * (patches[dist.b1].barx - centPos) + (patches[dist.b1].bary - centPos) * (patches[dist.b1].bary - centPos);

is not clear at first glance. What is it ? It's the square of the distance between a  point and the center of the map. Of course, I could write this using a nice vector function providing directly the length of a vector. It would add a call to a lot of functions including a square root, which is not a cheap function, and is here perfectly useless since I compare distances. Comparing their square works as well. The overhead looks not so important, but if you execute this code mapSize*mapSize times, it becomes significant. That's why I use here (and in every double loops of this kind) only arithmetic operations and no time consuming functions. This may lead to code replication and things looking awkward that's right, but I don't think it would improve anything: embed the createAltitudes code in a loop will only obfuscate the code, because the loop itself will not be trivial, and the reader will wonder at it, trying to understand what the hell it could do. Try it and you'll see.
BTW, the iterator on tiles object provides the connected tiles to this tile object, if exist: so there's no need to check maps boundaries each time I need to fetch the neighbors of this tile and I do this very often.

 

15 hours ago, elexis said:

"crocodiles and palms on the shore" don't. That loop implements a custom coordinate randomization, but it should leave that up to createObjectGroups or createObjectGroupsByArea. One could implement a constraint for this g_TOMap.gCells[zx][zy].done check, though I'm not sure what that does and if it isn't redundant with tileclasses.

Of course, it's possible to do that with rmgen functions, but I can't see the real benefit. The done flag in the cells map works along with the PatchPlacer and replaces a lot of tileclasses and constraints. If you look at the code part where I build the player environment with legacy functions, it's not more compact: you need to compute a lot of parameters instead.

So what ? To me there's something obvious: except the cosmetic and good practices remarks, all your critics strike on the same topic, which IMO is a real design problem. My code rely on another data organization, not the rmgen one. That's why I use so little features of rmgen, not because I think they are bad or mine are better. Their design clash, at least for now. And suggesting to implement a constraint in rmgen fashion to check the done flag only hides the incoherence. I need  a cell object, where to store and retrieve easily informations about a tile. Some of them may exist here and there in some rmgen data structures like tile classes, but certainly not the key value, the done flag and the road pointer which are specific to my algorithms. You denied the interest to have some other informations stored as well, like the slope, saying you have already a slope constraint. But I don't use slope only as a constraint and I need its value, not only checking boundaries, same for height and other things. Asking to work only with what I can find in rmgen is like cutting my wings and asking me to fly.

I agree, of course, with the need to provide a consistent interface in the whole set of map libraries, but if you expect me to do that within a week while the whole thing is still in development, the reply is clearly no. I say that without any angriness or frustration, it's just impossible. Remember: "Cheap, right and fast, choose two of them". Now, you may pick any part you want in my code and even rework it as you will to fit your guidelines.Do what you think best for OAD, it will be OK to me..

Friendly,

  • Like 1
Link to comment
Share on other sites

Hi...

A new version of the script for testing purpose only. It includes roads. This feature is not only decorative: it allow to detect unplayable maps where no path exists between some players. Since the algorithm tries to find the easiest pathes (avoiding steep slopes, water and mountains if possible), it's not very likely you'll see a road crossing mountains, but in some cases, they make their way through the mountains like here, digging a trench where the slope is too steep:

Roads.jpg.50514042c106d1b820aeb96cbb68de4a.jpg

As usual, if you detect some bugs or ugly things, please report the size, the seed and the players number of the offending map.

Thanks in advance for you interest and testing...

 

EgyptOasis.zip

Edited by Pyrophorus
Fix a bug in the script
  • Like 5
Link to comment
Share on other sites

Hi !

As @stanislas69 suggested, I finally refactored the whole thing to make it a mod and push it into GitHub.

There's no feature change from the previous version, but the script has been split in three files, two of them holding most of the code:

  • fractal.js: all the height map creation is here, including a fractal painter.
  • placers.js: various utilities, constraints and placers using the global tile map object.

I removed from the script and the library all uses of points objects (PointXZ and Vector2D as well), because I don't need them. There is still one place where it can't be avoided: the place() method of the placers objects which must return an array of one of these types. To make things easier, I return those arrays through a convenience method (zoneToPoints) located at the beginning of placers.js. It creates a point array from a cell array and one can change this to the particular object type (s)he expects.

Just for fun, an impregnable fortress :P:

fortress.jpg.c4f5cd553d2e86e8112daf0a847b5c69.jpg

I have to fix this...

Friendly,

 

  • Like 1
  • Haha 1
Link to comment
Share on other sites

The github branch is a very good idea for maintenance.

PointXZ was nuked because it was a duplicate of the Vector2D. The entity placement method was changed, like many other methods. A list of changes in alpha 23 is found here: https://trac.wildfiregames.com/query?status=closed&component=Maps&milestone=Alpha+23&group=resolution&col=id&col=summary&col=owner&col=type&col=priority&col=component&col=time&order=priority.

Using what we currently have in rmgen is a necessity. I like maps inventing their own thing, but it should be so universal that every other map can be combined with this new feature. That the Placer, Painter and Constraint interfaces were implemented is a very good improvement.

About the player territory border being harsh, you could use a ChainPlacer, that covers up bordes pretty well. Yes, we need those paths and I have converted FeXoRs path placement method from Caledonian Meadows to be a bit more reusable thinking about this and other maps. That mountain looks like it could use a second texture for higher slopes by using the SlopeConstraint once this is migrated to alpha 23. The latter has to be done sooner or later by one of us (https://trac.wildfiregames.com/wiki/BuildInstructions).

The code of the oasis map looks much better now that the library code was removed.

In alpha 23 we have a playerbase function, so you can replace those 100 lines with one function call and some values.

The textures and template names should be constants at the top of the file.

I'm not particularly fond of the TileObjectMap prototype as it reinvents what is covered by the library for a great part, as it has a very confined use case and is a global that needs to be initialized, kept in sync and used throughout the map generation. But Placers, Painters and Constraints should not rely on globals other than g_Map, receive all other data in the constructor. This way the the function becomes deterministic given the arguments. The heightmap is saved already in g_Map.height. The slope is currently only computed on demand since recomputing it upon each elevation change costs too much performance. If it must be cached, then it would better fit in the g_Map object so it can be reused. The paintMap method should not be part of the library as it's the meaning of the map if there can be such a thing as cliffs or something that only one map uses. findPlayersBases is playerPlacementRandom in the alpha 23 library. putRandEntities sounds a lot like createObjectGroups. putLargeEntity: the map should define what is large, small or very large, in numbers. putForestChunks should be Terrain painting or createObjectGroups. AvoidSlopeConstraint - there is a SlopeConstraint in alpha 23, AvoidAltConstraint - HeightConstraint.

paintPointsArray -> createArea + MapBoundsPlacer + TerrainPainter.

buildRoads -> this looks like the RandomPathPlacer

We should aim  at getting those 1700 library lines down to 400-600. But one step after another. I believe migrating to alpha 23 is the most urgent thing, because every line that is added now to the map has to be rewritten. So one can save time writing the lines only once. Since many functions were renamed, deleted, replaced and received different arguments, you can ask me on IRC what has changed for specific functions (https://webchat.quakenet.org/?channels=0ad-dev), or here if you want to. I assume this process won't be finished before april, but it would yield a high quality addition if done properly and will be reused by many maps. The new library should be part of the main library. rmgen2/ and heightmap/ are only temporary workarounds, put aside because they are some experiments. It shouldn't become three but one consistent library to rule them all :-) The randombiome library is the only one so far that is legitimately split IMO.

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

Hi !

@elexis Thanks for your reply.

3 hours ago, elexis said:

Using what we currently have in rmgen is a necessity. I like maps inventing their own thing, but it should be so universal that every other map can be combined with this new feature. That the Placer, Painter and Constraint interfaces were implemented is a very good improvement.

OK, I perfectly understand that, but including my work in the rmgen library is YOUR idea, not mine. I never asked for this, not because I don't want to collaborate, but because I'm conscious it's  really difficult to achieve. I did implement the Placer, Painter and Constraint in my development even if I don't use them and I don't need them, but if this not enough... My goal was not and is not to refactor rmgen or replace it but to experiment other ways to do the same things. At the highest abstract level, we all do exactly the same: creating a heightmap, painting it and placing entities, and at this level, many things look duplicates. Now, looking more deeply on them may reveal they don't give the same result, and according to circumstances and personal tastes, a method can be preferred. Why don't you say my fractal height map creation duplicates DiamondSquare which already exists ? They do exactly the same job using very similar algorithms.:unsure:

Now it's your responsibility to pick in my work what you think possible/interesting to include in your development. But please, take a deeper look at my code/results and stop saying it duplicates other things. I read rather thoroughly the whole rmgen library code and I haven't the habit to reinvent hot water.

3 hours ago, elexis said:

buildRoads -> this looks like the RandomPathPlacer

Can it create up to 100 or more optimal roads between arbitrary points, crossing the moutains in a realistic way, just in one call ? Mine does and not just looping road after road: the work is done in one pass on the map for all the roads at the same time. As a side effect, it returns how many roads nets it created (some points may not be connected because impassable mountains or lakes), a reliable test to know if some parts of the map are unreachable. I'm not trying to sell you my work as better than yours. Just let me say it's not a duplicate.:angel: And if you want me encapsulating this in a placer, it's five minutes work.

6 hours ago, elexis said:

paintPointsArray -> createArea + MapBoundsPlacer + TerrainPainter.

I don't need that area, have already checked map boundaries, and TerrainPainter does nothing more than PlaceTerrain(). Where is the problem ? I don't expect you adding this to rmgen of course, but do you really want to deny me the right to create some convenience short functions ?

4 hours ago, elexis said:

putForestChunks should be Terrain painting or createObjectGroups

The problem is creating the region covered with forests. The YPatchPlacer I use creates not the kind of regions rmgen placers does. Since they use geometric shapes, they define rather regular regions. You told me you had the problem in Ambush. Do you see anything of the like in my maps ? Now, I have not yet experimented fully all the capacities of this placer, but as a side effect, it provides the border of the region created . See the hedges in the pictures above, can you tell me how I could do that with rmgen with three lines of code ?:unsure:

Now the placer should be able to expand a previously created region to create an irregular ring around, and even expand and find the border of any other region. For instance, it should be able to be fed with a ChainPlacer result and add some blur and noise at the border or only find the border. Do you really think it's nothing more than a HeightPlacer ? You may think these features are uninteresting and not worth to be integrated, but don't say it's a duplicate.:)

4 hours ago, elexis said:

I'm not particularly fond of the TileObjectMap prototype as it reinvents what is covered by the library for a great part, as it has a very confined use case and is a global that needs to be initialized, kept in sync and used throughout the map generation.

Confined use case ? Are you conscious I'm creating this whole script map without any call to rmgen except to set up players things ? (Which don't work very well BTW, I have problems with this, and I'm tempted to substitute my own code) . Of course, you can say so because no random map script uses these features for now...:P

That said, I know you don't like TileObjectMap for good reasons, but I already told you it was a fundamental design clash and not only something which could be changed or avoided easily. Rmgen has no cell object and splits  informations here and there in many places. And the reasons you give apply to rmgen as well: g_Map needs to be initialized, and you can put the slope in a cell object or in a separate map like heightmap library does, they will run out of of sync exactly at the same time and for the same reason. And what if the programmer inadvertently creates two or more inconsistent slope maps as (s)he can perfectly do for now ? Another problem is you can't use the === operator to know if two Vector2D or PointXZ objects are the same, because the library can freely create more than one object holding the same coordinates. So you must access the members and compare them, and you couldn't use a Set container with reliable results because it would eventually store duplicates. This is why I use a cell object, because in the reality, there is only one tile at x,y coordinates, not many. And this is why you can't add any member to this coordinates pair without running into awful synchronization problems. So why make them an object ? Of course, the cell object map should be in g_Map and should have mutators dealing with the sync problem, but we already discuss the matter and it was rejected for some reasons. Then I created my own map to avoid the risk to fool ExportMap() with unexpected additions.

Now, I went somewhat further than I use to go. I'm not your boss and give only advices when I am asked. I'm not speaking the TRUTH here, but only my humble opinion: to me, it's poor design so I have no reason to follow it since I don't belong to your programming team. The proof is in the result: I don't need all the rmgen stuff and that's why object oriented programming exists. It simplify greatly the work and makes the design clearer and easier to enhance. I can add easily all compatibility code you want like the place() and paint() methods I don't use, constraints which are here as a proof of concept and never used as well, but don't ask me to rewrite the main code without a Cell unique object and map. I hope you'll understand this and will not see it as a mark of contempt about your skills or your work.

Now, you are at the crossroad: if you drop the Cell object (or whatever variation it could be),  you drop all the content of placers.js and the random map script as well. I'm conscious it is a difficult choice because you are aware of your library consistency. But I think you should make a clear decision now and not expect I will solve magically the problem. For myself, I don't mind. My job can perfectly live in a mod without clashing with your development process and maybe it's the best solution.Again, do what you think best for OAD. :)

Friendly,

  • Like 1
Link to comment
Share on other sites

@stanislas69 Thanks for these suggestions. I don't think I need really more terrains for now or vegetation for now. My problem is more to select a set  giving good looking results. I'm rather happy with the green parts of the map and mountains, but much less with the others. Desert plants look too green, and in the kind of steppe lying between the players patches, the map exhibits some patterns. I believed at first there was something wrong with the RNG, but at a closer look it is not. Some terrains textures share the same layout, and the color only change. I wonder if is possible to rotate terrains when painting, as we can rotate actors. It would certainly break those patterns.

Friendly,

Link to comment
Share on other sites

@stanislas69

28 minutes ago, stanislas69 said:

Well we did have alpha maps to ensure smooth transitions but I believe they are broken. So if c++ is one of your skills you might want to look into that I can fetch the tickets if you need me too.

:P Hey !  Wait ! I'll finish this map script business first ! Then maybe I'll give a go since it' badly needed. I think I have seen somewhere some description of the feature and it's a pity if it is broken.
 

34 minutes ago, stanislas69 said:

About rotating textures it's not possible as far as I know

OK, it's not a problem. I'll give a try rotating them by hand and test the result. For now, I don't want to put a lot of stuff into my mod, making it impossible to include in OAD distribution if this is the final decision.But if the idea is abandoned, then everything becomes possible ! Sparkling animated eye candies, strange looking terrains and fantasmagoric landscapes ! Hurray ! :blush:

Friendly,

  • Like 1
Link to comment
Share on other sites

Hi !

Here come some previews of the fractal painter. As shown before in this  thread, it creates a little height map to modify a region of the g_Map. This region is defined by a pair of coordinates array, i.e. something like {x:some_value,y:some_value}. So it can be anything including Vector2D, cell objects and even PointXZ (converted on the fly). In the former version, the painter replaced indistinctly all the heights of the regions, which gives this:

fractal-2.jpg.aabc6dc8080dc54a01cc78190aad64ce.jpg

The result is not great, because it's possible to have chiasms at the border of the region. This mode is still available but the default is now to modify only the parts where the computed height is higher than original terrain:

fractal-1.jpg.b453ad2815e7127129cfa6ae3c05a279.jpg

The merge is now much better. What if we want not a mountains patch but a depression ? There is a new painter for that:

fractal-4.jpg.379858347276dd33ea321381a54ed72b.jpg

It creates a bumped hole in the map, but the default here modifies only the parts lower than the original map and it merges better:

fractal-3.jpg.2b498f69bb9772a49f28dd18165ee0d8.jpg

Now another painter. Mesas or cliffs:

fractal-5.jpg.008b3d3f7ec3f7feb834a8cbe7494c58.jpg

The top of the mesa (deep green) is perfectly flat, but this can be changed easily: at the end of the painting, the painter stores into a member array all the top points. The top of  the mesa is painted using this region. A new fractal painter (or anything else) can be applied to modify this part if something less plain is desired. The counterpart of this painter obviously creates holes whose bottom is flat:

fractal-6.jpg.37221f6dbe6eaf909a8ab45de76d8de1.jpg

In the same way, the flat part is available after painting, for morphing, painting or populating. Just put an Orthanc tower here, some Fangorn forests around and you've got Isenguard ring :P

Please note the painter can be applied on any kind of regions, not only the rather round one I use here.

More of it, everyone can define easily a new fractal painter because they all inherit from an abstract object holding all the code complexity. The child painters hold only the part where the two maps are mixed. Here is an example:

function MyNewFractalPainter(area,centerHeight,bump,rough,progress, your_parameters_if_needed) {
	AbstractFractalPainter.call(this,area,centerHeight,bump,rough,progress);
	this.param1 = your_parameters_if_needed;
}
MyNewFractalPainter.prototype = Object.create(AbstractFractalPainter.prototype);

MyNewFractalPainter.prototype.paint = function() {
	let res = this.maps(); // this computes the temporary fractal map
	let depx = res[0]; // these are the offsets of the fractal map in the main map. No need to change them.
	let depy = res[1];
	
		for (let pt of this.region)
		{
		// melting the g_Map and the temporary map. This is the only part to modify.
			let xg = pt.x - depx;
			let yg = pt.y - depy;
			if(g_Map.height[pt.x][pt.y] < this.tMap[xg][yg])
				continue; // we skip this value
			g_Map.height[pt.x][pt.y] = this.tMap[xg][yg]; // we keep this one
		}	
}

Not in Github for now. The reason is I don't know yet in which format the flat regions created by the two last painters should be created.

Friendly,

 

 

 

Edited by Pyrophorus
  • Like 3
Link to comment
Share on other sites

Hi !

This is a (long) technical post. Don't read if you're not interested in programming. This is a proposal to address integration problems.

Spoiler

This integration suggestion relies on the fact any object holding a member pair x,y (and even z, duplicating y) can replace the PointXZ and the Vector2D in the rmgen library, since those routines use only these two values. What would be impossible in a strong typed language is perfectly possible in Javascript. One can call any rmgen function requiring an array of points using an anonymous object {x:10,y:7,z:7}, or a more elaborate one.
So my proposal is to create a mapSize*mapSize array of more elaborated objects having the properties x and y (and even z if this prove to be useful) instead of PointXZ and Vector2D.
The main advantage would be to avoid multiple copies of those objects representing the same tile. In present state, it's impossible to enhance them with other members (like the slope) without running into intricated synchronisation problems. It's a well known problem in databases.
The changes needed are very few: there are only 11 lines in rmgnen where those points objects are created, for example:
pointQ.push(new PointXZ(nx, nz));
which can be replaced with:
pointQ.push(g_TOMap.gCells[nx][nz]);
Here we created not a new object, but a new reference to the unique object representing a tile, and no other changes are needed since the program only requires a reference to an object holding a coordinate pair. We can substitute anything to these points objects.
The code below is only a proposal to show a possible implementation and give an idea.


/**
 * First of all, a cell or tile object
 * @param x
 * @param y
 * @param slope
 */

function Cell(x,y,slope) {
	// if Vector2D exists and is not only a coordinates pair, set up inheritance.
	// There are better syntaxes to do that, using 'class' for instance, but in this Javascript engine, they work not.
	Vector2D.call(x,y);
	// else:
	this.x = x;
	this.y = y;
	this.z = y; // ??? why not, if compatibility is still needed ?
	
	this.slope = slope;
	// ... add more members at will, inclination, height, whatever...
	// but since they can be added dynamically, we don't need to worry about.
}

// if Vector2D exists and is not only a coordinates pair, set up inheritance:
Cell.prototype = Object.create(Vector2D.prototype);


Cell.prototype.update = function() {
		// update members when g_Map.height is changed
		let s1 = Math.abs(g_Map.height[this.x+1][this.y+1] - g_Map.height[this.x][this.y]);
		let s2 = Math.abs(g_Map.height[this.x+1][this.x] - g_Map.height[this.x][this.y+1]);
		this.slope = (s1 > s2) ? s1 : s2;
		// ... more update if needed 

}

/**
 * 	mutator replacing direct setting of g_Map.height
 * Not mandatory but recommended if few modifications are made. Avoid using global updates, see below
 */
Cell.prototype.setHeight = function(h) {
		g_Map.height[this.x][this.y] = h;
		this.update();
		if((this.x > 0) && (this.y > 0))
			gCells[this.x-1][this.y-1].update();
		if((this.x > 0))
			gCells[this.x-1][this.y].update();
		if((this.y > 0))
			gCells[this.x][this.y-1].update();
}

/**
 * Another access to the slope, more expansive, but always up to date
 * Can be provided along with the other solution.
 * 
 * @returns : slope
 */
Cell.prototype.getSlope = function() {

	let s1 = Math.abs(g_Map.height[this.x+1][this.y+1] - g_Map.height[this.x][this.y]);
	let s2 = Math.abs(g_Map.height[this.x+1][this.x] - g_Map.height[this.x][this.y+1]);
	return (s1 > s2) ? s1 : s2;
	
}

// ================================ The cells map ============================

/**
 * The cell map: 2D Array of cells
 * I made it an object, but since it's most probably a singleton, it can be directly created as an empty Array or a member of g_Map.
*/
function CellMap() {
	this.gCells = []; 
	for (let i = 0; i < mapSize; i++)
	{
		this.gCells[i] = [];
		for (let j = 0; j < mapSize; j++)
		{
			this.gCells[i][j] = new Cell(i,j,0); //At start, all cells have slope = 0, so we can build the array like this.
		}
	}
}

/**
 * creates an iterator returning all neighbor cells of this one (checking map boundaries)
 * 
 * To get all the existing neighbors of a cell c in loop:
 * 	for(let neighbor of c)
 * 
 * @param tObjMap a CellMap object
 * @returns
 */
function setTilesNeighbors(tObjMap) {
	for (let i = 0; i < mapSize; i++)
	{
		for (let j = 0; j < mapSize; j++)
		{
			tObjMap.gCells[i][j][Symbol.iterator] = function* () {
				if(this.x > 0) {
					if(this.y > 0)
						yield tObjMap.gCells[this.x-1][this.y-1];
					yield tObjMap.gCells[this.x-1][this.y];
					if(this.y < (mapSize -1))
						yield tObjMap.gCells[this.x-1][this.y+1];
				}
				if(this.y > 0)
					yield tObjMap.gCells[this.x][this.y-1];
				if(this.y < (mapSize -1))
					yield tObjMap.gCells[this.x][this.y+1];
				if(this.x < (mapSize - 1)) {
					if(this.y > 0)
						yield tObjMap.gCells[this.x+1][this.y-1];
					yield tObjMap.gCells[this.x+1][this.y];
					if(this.y < (mapSize -1))
						yield tObjMap.gCells[this.x+1][this.y+1];
				}
			};
		}
	}
}

/**
 * To start up the thing
 */
var g_TOMap = new CellMap();
setTilesNeighbors(g_TOMap);

/**
 * When most map creation is done (or at any time if accurate slope is needed)
 */
CellMap.prototype.updateCellMap = function () {
	for (let i = 0; i < mapSize; i++)
	{
		for (let j = 0; j < mapSize; j++)
		{
			this.gCells[i][j].update();
		}
	}
}

/**
 * Update only a region (in the future, could be added to painters modifying the map)
 * Region must be a Cell array of course.
 */
function updateCellRegion(region) {
	for(let c of region)
		c.update();
}

 

The point is open to discussion, but my development will be stalled until I have a reply.

Friendly,

 

  • Like 1
Link to comment
Share on other sites

Hi all !

Today, I'm making some kind of request for comments. I have finalized two methods of a placer and would like to have [random] map makers opinion and suggestions. Have they the use of such tools ? As they are or working another way ? I'm open to suggestions and requests for features now and later.

The picture below shows the principle:

concentric.jpg.8fe0dd02ded1d358cc9220e1487baec1.jpgThe center of the spot (deep green) is created with a good ol' clump placer. From this region, we deduce approximatively concentric regions with the placer. The first step is using a method initializing the placer with the central region. As all methods of this placer, it computes the border of the region (one tile wide), and can be used only in this purpose: get the border of any connexe region.

Next another method expands the region, adding the chosen number of tiles. Those tiles can be added to the initial region or returned as a separate one (only the ring). This step can be repeated. Here, we call the function twice, and finally paint the border of the last created region.

As one can see, the expansion is not isotropic nor regular, because there are other ways to create geometric shapes. The expansion is ruled by three factors: the distance to the center of the region, the tile height and slope, the weight of each one being a parameter of the placer. Some noise can be added too, which essentially change the border.

Maybe other factors could be desirable too or instead. Suggestions are welcomed.

I used this very spot to create the pictures below. Applying a fractal painter to the inner region, and raising the two rings to a fixed height:

concentric-2.jpg.f9f476c9bfdf76e76a0fdd4dc3756e1b.jpg

Spoiler

- Mom ! Mom ! Look ! It's Gondor ! - Shut up stupid boy ! I don't want copyright problems...

This one is created with exactly the same code, but, a different map seed gives another result:

concentric-3.jpg.8a0a7d2fd6c11843bc37e1ca5159947a.jpg

I hope the discussion will bring new interesting ideas.

Friendly,

  • Like 2
Link to comment
Share on other sites

Hi !

Just a word to say I have updated Github with the new features described in earlier posts.

Since I have no new from the programming team, I suppose they will catch only the fractal part of the library. It can work as is, in alpha22 or alpha 23. I made provisions to ensure it works with both Vector2D and PointXZ. The various fractal painters have no dependency with the fractal generation of global map. This one has two rather large methods allowing to draw composite maps. They can be included or not, at will.
I have nothing more in my TODO list, at least  for the library.

Friendly,

Link to comment
Share on other sites

Oh, nice to see so much going on here (y)

Try to catch up :P

On 2/1/2018 at 3:46 PM, elexis said:

giant maps are totally out of reach currently

This is not really true for the random map part if you avoid using placers/painters/constraints.
And some part of code has to be first being fast enough that other parts are recognized to be slow and made faster x)
It's likely just a few parts causing most of the cost - as usual - so identifying those would help.

On 2/8/2018 at 4:24 PM, Pyrophorus said:

I'm sorry but I have no idea what I'm supposed to do on this site. Create an account ?

This is where we propose code contributions to get reviewed and eventually committed. See https://trac.wildfiregames.com/wiki/ReviewingPatches

I agree that trying to add a map with this features (though gorgeous) for the coming Alpha would be a bit hasty. But I would love to see one entering the review queue at some time!

On 2/17/2018 at 7:21 AM, Pyrophorus said:

This is a proposal to address integration problems.

ATM I can't see a huge advantage of this besides partial upgrading (which also might need border tiles) ... but it's absolutely possible I'm blind (I'll read it again after I hit the pillow) ;p
Also this would AFAICS restrict this object only to properties in from the beginning (e.g. normals, waterHeight, waterVelocity are not used in most maps but might be wanted in others ... which then can't add them). If I am wrong here please teach me differently! Is inheriting from an instance possible (assuming the instance is generated in the libs which don't know the needs of a map)?
Also what about the performance of derived properties like grad/rot/div of the original properties? Any benefit here (besides partial updates)?
So, sorry I can't give you an opinion right away :pardon:

On 2/14/2018 at 3:29 PM, Pyrophorus said:

I'm not trying to sell you my work as better than yours.

Good! But I can tell you I consider many parts of it better than mine! :D

On 2/19/2018 at 5:58 PM, Pyrophorus said:

two methods of a placer

I consider both of you painters valuable!

 

The map looks promising already. Yes, texture variation could be higher for my taste. Breaking patterns of the textures is possible by applying more than one chosen randomly in the same area. But those have to really match well to not look to patchy/edgy/separated (don't know better words).

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

Hi !

@FeXoR Thanks for your feed back and appreciation.

4 hours ago, FeXoR said:

I agree that trying to add a map with this features (though gorgeous) for the coming Alpha would be a bit hasty. But I would love to see one entering the review queue at some time!

At least, I think it should be possible to add the fractal painters. It's only adding new painters. There is no compatibility problems. The remainder of the fractal.js can be inserted too, but is more a convenience to create  whole maps in one pass.

5 hours ago, FeXoR said:

ATM I can't see a huge advantage of this besides partial upgrading (which also might need border tiles) ... but it's absolutely possible I'm blind (I'll read it again after I hit the pillow) ;p
Also this would AFAICS restrict this object only to properties in from the beginning (e.g. normals, waterHeight, waterVelocity are not used in most maps but might be wanted in others ... which then can't add them). If I am wrong here please teach me differently! Is inheriting from an instance possible (assuming the instance is generated in the libs which don't know the needs of a map)?

Javascript has not strict inheritance but allows to add properties to an object at any time. So it's easy to add new values to the Cell object or its prototype when they're needed. And we need not to define a full featured Cell object as in Java or C++. In the code, it would look quite the same. For now, if you want to use slope, you have to create a separate slope map first which is stored in a array. Slope (or anything) would be stored in the Cell objects instead. And if you want to know if the work has already be done (rather useful in a library where you have no control on operation order), you can test if the property is undefined on any Cell.

For myself, I use four members which will probably never been used in other functions. But it's not a problem. I can easily enhance the Cell object if exists.

5 hours ago, FeXoR said:

Also what about the performance of derived properties like grad/rot/div of the original properties? Any benefit here (besides partial updates)?
So, sorry I can't give you an opinion right away :pardon:

IMO, there should be no penalty because, obviously, Javascript uses references when manipulating arrays or such containers. It matters not if the object is large or not. But creating a new object has a cost. Now the operator === is certainly faster than (x1 == x2) && (y1 == y2), but I'm not sure it adds a real benefit. If you look at this line:

if((neigh.slope < this.slopemax) && (neigh.slope >= this.slopemin) && (neigh.alt < this.altmax) && (neigh.alt >= this.altmin) && !(this.mask & neigh.lock))

where neigh is a Cell object. Using separate maps would result in:

if((slope[x][y] < this.slopemax) && (slope[x][y] >= this.slopemin) && (alt[x][y] < this.altmax) && (alt[x][y] >= this.altmin) && !(this.mask & lock[x][y]))

which is certainly not faster. But I don't suggest this to improve performances. The real way to avoid updating many times cell objects (or maps) slope or anything is to compute this value only when height map is quite finished. If you modify the height map anywhere in the main script... :wacko:

I'm a object oriented programmer, so I'm not easy with your way to split in many arrays what is to me properties of a cell. In my experience, the main benefit of this approach is not in performances but it saves development and maintenance time. If  you library had a cell object and used mainly references to them, the change from PointXZ to Vector2D would have been much easier, because most often, you don't use the actual coordinates. And if there was accessors to the coordinates, it would have been enough to change a few lines of code in the cell object only.

Friendly,

  • Like 2
Link to comment
Share on other sites

My main problem is that I don't have the time currently to review all of it and before I understood every line of code, I can't judge if we want that Cell object or not.

We currently have at least less than one week and at most a bit more than one week until Alpha 23 feature freeze and we have to finish the scheduled features on the milestone until then https://trac.wildfiregames.com/milestone/Alpha 23. After that we have to fix the defects and release asap.

Given that schedule, you might want to consider dropping alpha 22 support, the PointXY prototype was removed for instance.

I totally agree that the cost of constructing Vector2D objects is an addition. But it has to be compared to the rest of the performance cost. It's currently more the avoidClasses / stayClasses constraints with large numbers that take really long. But there's a plan for that (some ConstantConstraint wrapper that is evaluated once per createArea / createObjectGroups call rather than reevaluated for each random coordinate in that loop).

Your screenshots are very promising!

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

1 hour ago, elexis said:

Given that schedule, you might want to consider dropping alpha 22 support, the PointXY prototype was removed for instance.

There's no need to do that. The fractal library can work with both Vector2D and PointXZ (I think you mean this one).

1 hour ago, elexis said:

I totally agree that the cost of constructing Vector2D objects is an addition. But it has to be compared to the rest of the performance cost. It's currently more the avoidClasses / stayClasses constraints with large numbers that take really long. But there's a plan for that (some ConstantConstraint wrapper that is evaluated once per createArea / createObjectGroups call rather than reevaluated for each random coordinate in that loop).

The cell object offers another (much faster) way to deal with the problem, but as I said some times ago, I think all this is not mature enough to enter alpha23. So I think a decision should be made and it would concern only the fractal part of the library (fractal.js) which rely NOT on the cell object. The fractal painters are no more than other painters in rmgen. You can copy/paste them somewhere as they are and if you want to review the code, it's only 340 lines, a great many of them being comments.  The remaining part of the fractal library is more a convenience and a code example to create globally height maps. It can be avoided if you want. I can remove PointXZ support for you if you want too, but I don't want to do that in my mod until the release is out, and have no reason to do so if finally you decide to include nothing in alpha23.

Friendly,

Link to comment
Share on other sites

  • 2 weeks later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...