# Alternate approach to generating rivers in a procedurally generated world creates very boring rivers

I promise, I have read through the 100 other posts concerning "adding rivers to procedurally generated terrain" but I have a different problem than they typically address.

I've been reading through some literature on the subject of generating watercourses such as rivers, particularly this:

(If you can't view the link, it isn't that important to the question, but it helps you see where I'm starting)

I've developed the following method of generating rivers:

1. Create a height map (standard x-y coordinates with a (double) z-height.
2. Generate a moisture map (this is just done through noise with a bit of tweaking).
3. Create "drip paths":
• A drip path of tile "t" is the path a drop of water will flow when it is placed on "t". This is a pretty typical way people generate procedural rivers.
• Water flows from a tile to its lowest neighbor.
• A drip path ends at water or a local minimum.
4. Create a (double) variable for each tile called "watershed".
• For each point in the landmass, iterate through its drip path, adding the moisture level of the beginning tile to each tile in the path. This ensures that rivers won't ever spawn in deserts, but can certainly run through them.
5. Rivers can be generated using these watershed values.

The last step is the step I'm struggling with. The only method I've been able to think of to use these values is to just define a river as anything with a watershed greater than X, where X can be a number I define. This works, but creates very straight, boring rivers. It also occasionally generates big blocky patches on things like cliffs, where the watershed is large because every point on the cliff is included in each watershed value.

Below, I will (try) to post two images I have created. The first is a generated world with rivers in black corresponding to tiles with sufficiently high watershed values. The second is a gradient map of (normalized) watershed values, where "black" is high values and "white" is low values. You will notice that it is mostly white, because a few tiles have extremely large watershed values (which I hope I can attribute to wider rivers).

My specific question is how, if it is possible, can I generate realistic rivers from these values? I'm pretty sure it can be done, but if there is some fundamental problem with the way I'm proceeding, let me know. Additionally, I have posted the code to my GenRiver class, which contains all the basics of the generation process described above. My actual world generation class is probably too large and complicated to post here, which I know is probably bad programming on my part. If you want any of that code I will gladly try to share it.

My final concern is that the way I'm actually generating terrain is what's accounting for these boring rivers (the terrain isn't bumpy enough). If this is the case, can someone recommend a way I add small detail to my terrain without changing the overall map shape? I'm using a basic 3D Perlin noise generator for the height map, with a frequency of 0.8, persistence of 0.5, lacunarity of 2.0, and I believe 6 or 8 octaves. Additionally, it is tiled along the x-axis.

Thank you for taking the time to read through my question. If you need more clarification, feel free to ask.

GenRiver.java

public class GenRiver extends Generator {

@Override
public String getName() {
return "River";
}

@Override
public void generate() {

MapGenerator.landGroups.stream().forEach((TileGroup tg) -> {
tg.tiles.stream().forEach((t) -> {
Watershed.drip(t);
});
});

MapGenerator.landGroups.stream().forEach((TileGroup tg) -> {
tg.tiles.stream().forEach((Tile t) -> {
if (t.watershed > 20) {
GenRiver.this.tiles.put(t, new BiomeRiver(0)); //Ignore the BiomeRiver part, it's just putting the tiles in a list for now.
}
});
});
}

public static class Watershed {

public static void drip(Tile t) {
boolean minimum = false;
double m = t.moisture;
do {
t.watershed += m;
int i = getRiverDirection(t);
switch(i) {
case 0:
minimum = true;
break;
case 1:
t = t.Right;
break;
case 2:
t = t.Top;
break;
case 3:
t = t.Left;
break;
case 4:
t = t.Bottom;
break;
default:
break;
}
} while(!t.isWater && !minimum);
}

private static int getRiverDirection(Tile t) {
double a = t.z-t.Right.z;
double b = t.z-t.Top.z;
double c = t.z-t.Left.z;
double d = t.z-t.Bottom.z;
//Higher positive numbers mean a sharper drop
double e = Math.max(Math.max(a, b), Math.max(c, d));
if(e < 0) { return 0; }
if(e == a) { return 1; }
if(e == b) { return 2; }
if(e == c) { return 3; }
if(e == d) { return 4; }
else { System.out.println("PROBLEM CHILD"); return 5; }
}

}}


Pictures:

Note: The thicker dark lines are just topographical separations

UPDATE:

I have multiplied my height map by a ridged multi-fractal (Frequency 0.8 with 8 octaves) and achieved the following image. As it turns out, what I technically want is shorter river blocks, since that means my terrain has more variation. I believe my question has been answered, but for anyone who sees this post and wants to know what my plan is, I will be generating rivers by finding the biggest watershed values and eroding paths that take me to a specified height level. The artificial erosion will be based on the moisture of the area (its watershed value). I will essentially be following a path until it reaches a minimum, determine if that minimum should end in a lake, and if it does/doesn't, if that river should continue past the lake, at which point I will erode a path that leads me to the highest watershed. I believe that by doing this I can generate lakes above sea level that will either hold (if they are in landlocked or mountainous terrain) or break into another river.

I will continue to play with the distortion values until I get pretty terrain, but for now this will do quite well.

• Off topic but, cool maps! If this is is for some kind of strategy game, I love the choke-points the generator seems to create – Niels Aug 4 '16 at 10:53
• @Niels Yeah I am planning on using it for a strategy game. If you want to make maps like it, I generated it with Perlin noise multiplied by a weak Ridged Multifractal using the Flowpowered Noise library. However, in the second version I'm working on now, I'm looking into other libraries to use, since Flow didn't give me all the options I wanted (difficult to make a heat gradient and other things). I'm planning on eventually posting a tutorial on how I went about the whole process, so stay tuned! – Nathaniel D. Hoffman Aug 16 '16 at 17:10