I know this might be a tough one, thanks for opening this discussion my friendly brave soul.

I have made a "tree". I thought it was a MST but it might just be called a divide tree. It's not too complicated, it's just peaks and ridges. The peaks are points in an Astar algorithm and the ridges, their connections. I want the inverse or "dual" of this graph to use to create a river network. I noticed godot's AStar doesn't have a createDual() function! (Joking, of course it doesn't)

Anyway, anyone have any idea on how I could go about this? My googling has gotten me way too many pictures of real trees.

Look at this method! What just lay a random Voronoi diagram over it and prune? No wonder in this group's research they didn't show any terrains with rivers. Doesn't that seem a bit "hokey"?

4 days later

Hmm not really sure how to answer your question as it stands, but if the goal was to generate river paths on a known distance field/heightmap, I would probably do it the ugly way and brute force it with an agent simulation.

What I am imagining is spawn a few thousand 'rain drops' and simulate/map where they path during their lifetime. Then check for overlaps in the path and if the overlap exceeds a certain threshold, consider that the path of the river, something like this or this

@Bimbam said:

What I am imagining is spawn a few thousand 'rain drops' and simulate/map where they path during their lifetime.

This is something best done using compute shaders, so godot 4.x+. Something I'm curious to try out at some point myself, too.

This is something best done using compute shaders, so godot 4.x+. Something I'm curious to try out at some point myself, too.

Yeh Mr. Lague makes the same point in his videos, but I kinda wonder how hard it would be to dump it in/out of a texture using viewports and write a shader that generates it that way (albeit slower)? I don't know shaders well enough yet to know what limitations may be hit there.

I am indeed trying to get some rivers in there, but in a way that won't kill the fast compute time. Right now I'm just a bit slower than a few instances of simplex noise to render my graph of mountain peaks and ridges. I have basically made cellular noise but compute distance to lines instead of points. I feel there must be a way to take the "inverse" or something of graph I have available.

Hmm, couldn't you iterate the noise array and apply max(value)-value to effectively invert each value?

This assumes your storing/working with a texture heightmap and not a graph data structure (wasn't aware Godot really exposed one of these but I guess AStar outputs technically would be?)

@Bimbam said: Hmm, couldn't you iterate the noise array and apply max(value)-value to effectively invert each value?

Sorry, not sure what you mean by the noise array? Here's an example of the ridges and peaks:

Sorry I'll try to be a bit clearer, if you consider this texture as storing your height information:

To invert it, you would iterate each pixel in the texture (what I called a noise array as I wasn't sure how you were storing it. Texture... Array... same same in this context), and invert it by taking it's value away from the max available values.

For example, as heightmaps tend to be greyscale each pixel can be be considered a float between 0 and 1. if the float value for a given pixel was say 0.8, then it's new value becomes 1 - 0.8, or 0.2. Applying this to every point should invert the whole array and provide an inverse heightmap.

Which in this instance would look like:

I didn't think this was what you were after at first, hence suggestion about simulating rain, but it may be helpful?

@Bimbam I assume you mean then to take the inverse heightmap and ... subtract it from the terrain to form deeper, river or stream like depressions? Or do you mean to take advantage of that information to know what parts of the domain could/should be covered by water?

No, I'm not sure how the inverse of the heightmap would be used to identify rivers, was just responding to the need to invert the values but wasn't sure how you needed to implement.

I don't know what the best or mathematical solution is to identify rivers (delauney triangulation may be involved though?), there may be some papers on it somewhere?

I had this in mind for how I would do it jankily:

And to be clear 'agents' makes it sound fancier than it is. In janky pseudo-code I'm imagining:


cutoff = texture_width/2 # seems sane but less/more may be necessary
drop_paths = []
accuracy_mod = 0.2 #speed/accuracy trade off. Below a certain point your not gonna get any rivers
simulation_range = texture_width * texture_height * accuracymod 

For i in range(simulation_range ):
	iterations = 0
	pixel = random pixel #with a heightmap value above say 0.8 (start at tops of mountains)
	while iterations < cutoff:
		pixel = min(nearest pixel (eg.  indices like
				-1, 0, +1 
				-1, SKIP, +1
				-1, 0, +1)
		drop_paths [i].append[pixels index]
		iterations+=1

This should give you an array of rain drop paths, which you then would need to iterate and check for where paths overlap.

This is I think obviously computationally expensive and I doubt it's the 'best' way, but if it's for say procedural level generation and only needs to be run once or in background threads at a chunked scale, I think it would be practical even on a modest CPU. SebLague mentions he does 70k CPU iterations on a 255*255 heightmap in 0.75s (which would be accuracy_mod = ~1.1, way higher than what we would likely need to identify rivers)

Though as mentioned a Compute shader could simulate something similar in a fraction of the time.

This totally sounds like fun so I might try and hash something up myself just to see how well this method does/doesn't work, but won't be any time soon (work -.-)

TBH for that effect to make it good looking I was just planning on throwing an export from godot into an erosion simulation for a hot minute. On the flip side, I was under the impression that if in a procedural world like minecraft, if you performed erosion on the chunk you're loading up, the edges won't end up matching up with the next loaded chunk as it'll have to perform the simulation seperately.

if you performed erosion on the chunk you're loading up, the edges won't end up matching up with the next loaded chunk as it'll have to perform the simulation seperately

Good point, but for placement of rivers rather than eroding the entire terrain I imagine it would be a matter of snapping positions on the edge of each tile with some nearby interpolation only for the river's endpoints. Similarly, just for identifying river paths, I suspect the number of simulation iterations can be considerably lower than an erosion simulation.

Tiling definitely makes the problem more interesting :3

Anywayhopefully others can provide a better answer, as mine is purely how I would do it and is very unlikely to be the 'best' way to do it .

@Bimbam appreciate it mate

Does anyone know of a tool that can input a 3d model (preferably .obj) and perform erosion passes on it? I have been scrounging for the past two days and have found a couple of really cool tools- but none that allow me to start with a mesh I've already rendered.

This one I just have to share it's too fun. Hold mouse over a place and hit c to drop water. https://lanlou123.github.io/Webgl-Erosion/

This can be done in Blender, but in a roundabout way. You would need to import your model and render it to a texture as a heightmap, then use this texture as the noise input to the A.N.T.Landscape plugin which supports landscape erosion.

There are plenty more settings to play with in this plugin, highly recommend it.

@Bimbam Yes! I have seen that and I've been able to make ANT landscapes, but I can't for the life of me find this menu that has the tools you've screened. I'm almost ready to give up a kidney for knowing how to find that menu.

Maybe give it five for YouTube to generate the non-potato version

Another thing I forgot to mention is I think the tooling for A.N.T will disappear if you click away from the mesh and may not come back afterward. If so you just have to 'finalise' all your actions in one go (the Sapling TreeGen plugin is like this too).

ANOTHER thing I forgot (why I shouldn't make tutorials lol) is to turn off 'Falloff' under 'Landscape Displace':

Otherwise, this flattens the outer edge of the terrain by default, which may look pleasing, but will not match the heightmap. The terrain shown should have looked like this:

and not

FYI My pseudocode above isn't 'that' far off:

The above took about a minute to process on a single thread/GDScript, but suspect will need more passes to identify river paths with a good degree of accuracy, so will multithread it once bugs are ironed out. Might rewrite in C# too if I really want to give myself a headache.

Does seems to be an issue with paths ending once they reach a valley floor, but if anything that's an issue with this heightmap having a flat plane along the bottom, which kinda shouldn't happen.

Haven't yet implemented the bit that checks for overlaps in the paths to fully identify rivers, feel like this is going to have the worst performance so still mulling over how I'll do it.

I think what I'll do is write each path out to a texture, where each points adds 0.1 to the colour. That way as paths overlap it should increase, maybe applying a basic blur to the image and threshold out values below a certain number easily. Essentially creating a river heightmap.

Edit: Which also seems to in principle work:

The lack of rivers identified at the top is because I thresholded out everything below a certain height to speed it up, but once threaded can lower the height/increase the sample count. Then thresholding out rivers that don't overlap below a certain threshold:

Ramping up sample count/blurring is definitely needed to tidy up a lot of it, but otherwise should be good. Should be able to use this to generate another mesh with points slightly higher than the terrain layer...

Edit #2: Which looks iffy as. I need to rethink this last bit lol

omg m8 you have no idea how grateful I am. First of all, the menu. I have no idea how that menu eluded me so long, I've been making stuff in blender for almost half a year now. Second of all, thanks for going above and beyond by making a short video explaining the process. I am tinkering with a small break though I had on my own- which is to use the noise I generated as a scalar for simplex noise instead of trying to add my noise and other noises together.

I have no doubt rivers are computationally expensive. Minecraft after all these years still doesn't have rivers flowing downward.

I am using my noise as a scalar on the absolute value of simplex noise, which gives me one free layer of rivers which also cut through my mountain ridges. (I'm not 100% thrilled that they cut through, but it looks cool enough for me to be 98% thrilled)

There is a question of how you identify those rivers later to produce water meshes or what have you. Beyond the scope tho.

This image is my mountain noise as a scalar of simplex. IMO there's a lot of detail there for just two noises! Luckily now I have a way to add that water erosion to pass some more scrutiny.