Map Generator 2.0 - Please Critique!

Mmm, procedural coffee generator… :stuck_out_tongue:

Rivers seem to be behaving now. This image has 13 generated rivers with 2 of them merging:

Another image, this time with hills and mountains in the generation. Rivers getting lost under the terrain which I have circled. New hill and mountain meshes with room for the rivers to wind between them could make for some easy river canyon looks.

Going to have to look into randomization of the tangent and the scale between points of the river to get some more natural looking rivers. :slight_smile:

Here is a river with randomized scales. I tried making the range the river could scale larger, but at some point you start to see jagged edges pop out of the river and general ugliness. I will have to figure something out if I want a wider range, but this is good for now:

Each river is actually dozens of SplineMeshes being deformed along a spline. The river starts with a set start and end scale, but after each loop the end scale is randomized between .6 and 1.1 and the next start scale is set to match it.

Scaled the river start and end, made numerous other adjustments, and fixed a few bugs.

Thinking about a system of lakes that leads to more rivers. Water tiles that are completely surrounded by land will be easy to turn into lakes. Water that consists of several tiles could be a little more difficult without implementing some form of A*. So, I may implement 1 tile lakes and then come back to it when I have some algorithms that can do the job.

I will also consider methods of creating swamps and flood plains using rivers.

1 tile lakes are in, but properly stringing rivers from those lakes is going to require some more neighbor checking functions so I have taken the opportunity to look deeper into Axial and Cube coordinates.

Axial seems simple enough by itself, but I need to figure out how storing it in an array works and handling the poles with a coordinate system that sheers diagonally instead of horizontally.

So here is what I am trying to figure out:

(The prior spreadsheet image was incorrect so I replaced it with a correct version in the next post.)

The first spreadsheet is how my Offset Coordinate vector field is currently indexed. White cells are Even Offset hexes, and grey are Odd Offset hexes. I have to take that offset into account anytime I do anything regarding translating from one hex to another. Most of the generator is rather blind to the surrounding hexes, but when you start doing things like generating coasts and rivers, you need to do neighbor checks and lots of them. Obviously AI, pathfinding, and more complicated map seeding will require huge amounts of neighbor checking.

Which brings us to the second spreadsheet which is an Axial Coordinate vector field. While the first Array is indexed almost directly proportional to each cell’s placement on the map, the second Array is indexed as you would access it in a 2D Array with each row and column aligned by their X and Y coordinates. Most of the guides I have seen online are doing this with 2D arrays. Basically, an array of arrays. This isn’t strictly available in blueprint so I would have to come up with a way to emulate it if I want to use it, or find another way to get the job done.

So with the second spreadsheet, the white is the area that would be spawned on the map. The grey is all the wasted space in the array that I might have to account for if I use the standard 2D array method. I can either account for all that wasted space anytime I run an algorithm, or I can reorder it and store it in a single array and then again account for the fact that the rows don’t line up as they do in most of the algorithms and guides. Except the coordinates would still all be the same so perhaps it wouldn’t matter.

I am inclined just to get rid of all the grey space and mirror the first spreadsheet by giving each index in my vector field their matching coordinates. As far as the array is concerned, perhaps I don’t have to care about rows and columns, but just their X and Y for the algorithms.

So I could just make another array, store a 2d Vector struct with the X and Y, mirror it’s index as I do with my many other arrays, and call on it anytime I want to do some neighbor checking. I would just use my offset vector field index to check for the poles, and I could use either system to check for the world wrap.

I think talking it out on this forum helps me order my thoughts. :slight_smile:

Anyone see anything I am missing?

Ok, so I noticed a problem in my previously posted spreadsheet that made it completely invalid.

Here I have matched my offset indexes to their equivalent axial coordinates:

Here is a link to Amit’s excellent guide to hex grids that I used to figure things out: http://www.redblobgames.com/grids/hexagons/

The axial coordinate system uses 2 out of 3 cube map coordinates. You might notice that I am using X(Green) and Y(Red), with Z(Blue) not needed for coordinate storage though I can calculate it’s value easily anytime I need it. You just have to make the cell value total equal 0, so Z is the difference once you have added X and Y.

I created 3 diagonal lines and color coded them to their formula for easy visual reference. Each of those lines is exactly straight as displayed in the hex map. The reason it looks a little odd on the spreadsheet is because I have mirrored it to the Offset index which I will still use for actual tile placement and a few other things such as setting boundaries at the poles and whatnot.

Here you can see my Axial Coordinate generator, which is roughly mirrors the Offset Vector Field shown in the first post of this thread:

Using a 2D Vector struct to hold the coordinates. Since the indexes are mirrored I should be able to implement all pathfinding using these coordinates and convert back to any of the other arrays as needed.

If I have time this weekend(probably not) I will run a few tests to make sure I didn’t miss anything and then I will start the wholesale conversion of all my neighbor checking functions to make use of the axial coordinates. Once that is all done I will be back to working on rivers and perhaps a few other things that should be easier to do with the new neighbor checks such as wider coasts, bigger lakes, etc.

Neighbor checking seems to have a problem whose solution continues to elude me. I have the same problems with Axial coordinates that I had with Offset, except Axial doesn’t care about the Offset until it is on an edge. That doesn’t end up saving me much blueprint clutter since I have to make the check every time no matter what.

Here is what I have to take into consideration every time I even think about doing a neighbor check:

North: Top Row?
South: Bottom Row?

If Even:
NorthWest: (Can’t be Top) (Can’t be West Edge)
NorthEast: (Can’t be Top) East Edge?
SouthWest: Bottom Edge? (Can’t be West Edge)
SouthEast: Bottom Edge? East Edge?

If Odd:
NorthWest: Top Edge? West Edge?
NorthEast: Top Edge? (Can’t be East Edge)
SouthWest: (Can’t be Bottom Edge) West Edge?
SouthEast: (Can’t be Bottom Edge) (Can’t be East Edge)

Maybe I am just trying to fix a problem that can’t be solved. I think I can mitigate the effect of the problem by making my neighbor check functions more modular, but I am wishing that I could get rid of it altogether. If anyone has any ideas I would love it hear them! :slight_smile:

As I’ve mentioned, you can fix this by populating the entire edge of the map with invisible “walls” when you generate the map array. That way you can later just have a single check to see if the tile is a wall or not, and won’t have to make all the separate checks. I don’t think this will really slow anything down much, since you don’t render the walls. For the world wrapping in east and west, you might make separate types of walls for north/south and east/west, and if one of the east/west walls are selected you direct the blueprint to the appropriate tile on the other side of the map instead.

Yeah but in the end, I replace “Is Top?” with “Is Map Edge?”.

The bigger issue that I just realized would be that my arrays have a special symmetry that gets destroyed by adding extra tiles around the outside. Performance wise it wouldn’t matter, but I would literally have to redesign every system I have while losing the functionality that my array structure currently gives. All my map scaling, temperature bands, dozens of functions, etc would be made more complicated by increasing the array size which currently scales in exact units of 10.

Let me see if I can bust out some new neighbor check functions tonight. I think I can improve on them enough to make this a moot point.

Here is a pic of my neighbor checks before I collapse them into functions(pure and otherwise):

At the end of each I am setting Current Index, Current Direction Index(such as Current North Index), Current Axial, and Current Direction Axial(such as Current SouthEast Axial). I made a variable for each type and direction because I may need to simultaneously check the state of each nearby tile, such as with a coast check, so I need to store them separately.

Here is a closeup view of the NorthEast check for reference:

If you look at NorthEast Even, you can see the top half is wrapped, and the bottom half is for any other tile.

Each block will collapse down into ~1-3 pure functions and 1 normal function. I will be able to use one function for any given direction I want to check. My previous setup was extremely clunky, but this should hopefully let me forget about offsets and wrapping in general since it is taken care of in these functions.

EDIT: Forgot to multiply the Map Size by 2 before the X on the Axial check on the NorthEast Wrap.

Got the new neighbor checks implemented. Makes things much cleaner though I am not using the axial portion just yet. Just cleaning up and compartmentalizing some of the code was a huge improvement. I also cleaned up various other pieces of the river generator to make it easier to read, which still has a long way to go for readability:

I am contemplating further methods of cleaning this generation such as turning each column or set of columns into a function which has a switch on int/enum inside and setting variable at the beginning of each row to state which of the 6 points it is currently generating from. The switch would break it apart inside the function and route to the row it is on. Will be much cleaner.

I was considering creating diagonal neighbor checks for the rivers, but I think first I may try and create some limited A* method of walking across a body of water, and then depending on it’s overall size, it and all it’s tiles could be designated and stored as a lake or ocean. I am thinking that any self contained body of water could be searched and referenced for all kinds of things. 2 ports on the same body of water would be able to use their lake/ocean designator to tell them both that they could link up to form a trade route(and then do a path distance check if on the same body of water). If a river generates into one side of the lake, a point elsewhere in the lake could be chosen for another river to pop out and continue on until hitting an ocean.

I also plan on storing each river as a single entity for future referencing and meddling. Trade routes, dams, river rerouting, etc.

Anyway, I may make some limited progress tomorrow, but after that I will be out for 2 weeks so I will start fresh on some of these things then.

Cleaned up the river generator. Made a few improvements and bug fixes too. This is how it looks before I collapse everything into functions:

See you guys in a couple weeks. :slight_smile:

Got a few questions -
Do you intend to make this available via the marketplace?

Is network replication supported with this awesome terrain generation tool?

10/10 would buy :smiley:

Hehe no, I don’t intend to put the generator on the marketplace, at least not for a long long time.

I haven’t built any network replication into the generator as of yet, but it is something that I will do in the future.

So right now I am currently pondering a couple things that seem to require 2D arrays(array of arrays), which we don’t currently have in UE4, but can be approximated using normal arrays. For instance, my vector field is indexed in a way that makes it similar to a 2D array.

What I want to do is take a river and all of it’s points, and store it as a single entity for reference as such. And then store any number of other rivers right along side it. The same goes for every body of water and every landmass, as well as a number of other systems.

So, one idea is to create an array similar to my vector field. Instead of each column corresponding to a longitude on a map, it would hold all of whatever entity I wanted to store, such as a river. So if I had 19 rivers, the array would have 19 “columns”, and each column could be of any give size. For instance, a river covering 15 tiles would take 15 spots in the array, or 0-14. So I would need a way to know what column is what. In my vector field, columns are clearly defined by map size, so if the map is size 20, each column is 20 indexes tall. When you hit the 21st index, you know you are on column 2. Everything is cleanly divisible by the map size. The remainder is your row. Rivers wouldn’t be so clean, so I would have to add some value for each column so I know what column it is on.

So as I index each river, I could just add (column * 1000). So the first river would be 1000, 1001, 1002, etc. Second river would be 2000, 2001, 2002, etc. I divide the value by 1000 which gives me both the column as the whole, and the remainder would be my row position.

I am using 1000 as an example because a river could theoretically be more than 100 tiles long, but a 1000 tile long river would be practically impossible without direct user creation. The number might need to be 100K to account for other systems such as landmass and water bodies.

What do you guys think? Is there a cleaner way to store and reference this information? I am thinking data tables, but I am not sure those features are ready for blueprint yet. Also, I am not entirely sure about performance differences between arrays and data tables and if it matters for what I am trying to do.

Mind = blown.
now plz explain?

What is the question? :slight_smile:

I can’t come up with a better solution for river arrays at the top of my head, but I’ll think about it. Something else I’ve though about is that you might want to alter some of your algorithms a bit to make the terrain more realistic. For instance, rivers tend to originate from mountains in the real world, mountain ranges tend to somewhat follow coastlines etc. For the most part it’s probably unneccessary to worry too much about this, but it might be something to keep in mind. You are already doing some of this this by having cool poles and a hotter equator, for instance. For me personally, the rivers appearing out of nothing is a bit jarring, but I might be in a small minority. I think rivers in CIV V usually begin Next to hills or mountains.

A summary of what’s going on and what it is for plz?