Tuesday, February 5, 2019

Lakes!

Using the Planchon-Darboux algorithm to fill depressions, as we did in the previous post, makes it much easier to work out the river flows, at least to begin with. (It wasn't easy getting those flows to look good!) But of course, having no depressions means we have no lakes, and while that's liveable with (Uncharted Atlas doesn't have lakes and it looks fine), it would be nice if we could do something about it.

The answer is straightforward. In the real world, lakes form in depressions. In our simulated worlds, we'll just stick lakes any old where and then dig depressions under them. The end result is the same.

It's not tremendously complicated. Undiscovered Worlds stores the surface level of lakes in another array; any cell that has a non-zero value in that array is part of a lake, and the value is the height of the surface of the water. The program chooses random points on the river map to be the centre of lakes. If they are within the right size range (world-scale lakes won't appear on rivers that are too small or too large) and if they're in appropriate locations (they won't appear too near the coast either, or in arid climates), it makes a lake there.

This is done using templates similar to those used to build the continents in the first place. They're not just pasted haphazardly over the river, though. Rather, UW first determines the surface level of the new lake by taking the height of the terrain at the starting cell and reducing it by a set amount. (This is because the surface level of the lake should be the same as the level of the river that's flowing into it, and when we come to the regional map rivers will be drawn lower than the average height of their cells.) It then moves across the lake template and creates lake wherever the existing height of the cell at that point is no lower than the surface level but no higher than the surface level plus a randomly determined "maximum cliff height" value. The result is a lake whose size and shape is determined more by the existing terrain than by the template being used.

It also tends to result not in a single lake but in several disconnected bodies of water. This might seem OK at first glance, but it's not - what if the river is entering one of these bodies and flowing out of another? It'll look like the flow is being magically teleported from one lake to another. The answer is to adapt the floodfill function that I wrote for ensuring that there is only one body of ocean on the planet. Here, it's used to identify any portions of lake that aren't connected to the central point of the lake, and remove them.

Finally, it's time to scoop out the ground underneath the lake to give it some depth. This is easily done: UW goes over the lake area, and if any cells are higher than the lake surface, it inverts them so that they are deeper than the surface by the same amount they were higher than it. That gives a nicely varied lake bed, which UW also multiplies by some randomly determined depth modifier. If it's a deep lake, some parts of its bed may lie below sea level. This is OK - UW understands that terrain below sea level doesn't count as sea if it's part of a lake.

Running these functions, we get areas of the global map that look like this:


It's hard to see the details because of the scale of the map, but the blobby things on those rivers are lakes - very large lakes too, because remember that each pixel is 32 km across. These don't look bad (and that one in the middle even has a small island in the middle). However, closer inspection reveals a number of problems. One, which you can't see in the image, is that the cells surrounding each lake are not necessarily higher than the surface level, which doesn't make much sense. That's easily sorted by adding a routine to check these cells and raise them if need be. Sometimes that produces a sort of rim around the lakes, but we can live with that.

There are more serious issues with the rivers themselves. Even though the terrain covered by the lake has been altered, the river map has not. That means that the rivers that previously flowed through the area covered by the lake still do. They don't care that they're probably flowing uphill under the surface of a lake now. That's OK - we just won't draw rivers where there are lakes. But multiple rivers might be running through the area where the lake is. In fact, if this isn't a desert - which it isn't, because we don't put lakes there - probably every cell in and around the lake will have a river of some size going through it, because of the way we did the river map. It's not a problem to have lots of rivers going into the lake, of course, but it is a problem to have them coming out again, because lakes almost always have only a single outflow. You can see this in particular with the most southernly lake in that map, which seems to be centred on a tributary of that big river that passes it just to the north. It looks like that big river actually goes in and out of part of the lake, and undoubtedly others of its tributaries do too. That is definitely not OK.

Dealing with this is surprisingly tedious and tricky (remember the second rule of procedural landscape generation). It goes something like this. First, UW goes over the lake and surrounding area, identifying the rivers flowing out of the lake, and chooses one of them at random to be the outflow. It then creates a new array the same size as the whole world and marks this river, from the lake all the way downstream to the sea, on that array. This is to ensure that this river does not get messed up in what follows.

The program then goes back to where this river leaves the lake, and moves downstream a few cells to a point where the river is definitely clear of the lake.

Now it scans over the lake and the surrounding area. All river flow directions in that area - other than those on the marked river - are altered to point to the centre of the lake. Each time a flow direction is changed, the existing river must be removed from that cell all the way down its old stream and its flow added to its new downstream.

Then we scan over the lake area again and identify the cells that are flowing into the lake. All of their flow (January and July, of course) is added up to work out what the total outflow should be. The outflow is then checked and if it's not correct - and it normally isn't, because this whole process never seems to work quite as you'd expect - it is corrected, together with all of its downstream cells.

The end result is a lake with lots of rivers flowing into it, and only one - larger one - flowing out. That river may itself then flow into another lake, of course. So this is what we get now:


Making those changes has resulted in the lakes appearing quite different, as it has caused perturbations in the random numbers being generated. But you can see here that every lake has only a single outflow, which is what we want.

There are more finickity tweaks to add, such as ensuring that the outflow doesn't have to go uphill to get out of the lake, and lots of messing around with the exact heights of the edges of the lake to make them appear OK in the regional map, when we get to it. There's one last detail: temperature and precipitation. Wind picks up moisture from lakes just as it does from the sea, and if you have large lakes like these then you can get extra precipitation downwind of them. So we rerun our climate modelling routines for these areas, this time looking at lake cells rather than sea cells. And of course we then add any new precipitation to the river flow map, so there may be new or larger rivers as a result. (This never seems to make much actual difference, but it gives me an enormous sense of wellbeing to know it's being calculated.)

In the case of this map, here is the precipitation before and after applying this effect:




You can see additional heavy rain in some areas near the lakes. In this case it's not enough to change the climate (the climate map remains unchanged here) but sometimes it is, and you find e.g. rainforest appearing where it would otherwise be savannah. I think this adds a nice extra level of believability to the world.

1 comment:

  1. I had some deja vu reading this, as I just implemented a bunch of the same fixes for lakes -- and I definitely agree with your first two rules of procedural terrain generation :-). Rather than invert the land and then fix the flows, I dug out the lake area in a way that forces the flows to work out correctly, but I think it ends up being the same. One thing I don't do is have lakes impact on precipitation, which is a good idea. I'll have to add that to my TODO list.

    ReplyDelete