Monday, February 25, 2019

Grappling with lakes

This project has really taught me to hate lakes. There they sit, all smug and wet, lolling around in their basins with their multiple inflows and single outflows, all conforming to the laws of physics. They mock me with their impossible-to-model hydrological networks.

Large lakes really are the weakest element of Undiscovered Worlds at the moment. I've tried all kinds of different methods of implementing them, and none has really worked. A number of times I've seriously thought about just pretending they don't exist and hoping no-one would notice, but you can't really have all worlds be entirely lakeless. For now, they sort of work most of the time as long as you don't look too closely.

Why are they so wretchedly difficult? Lakes combine all the difficulties of coastlines with all the difficulties of rivers, plus some additional annoyances of their own. When we created them at the global level, we basically created artificial depressions at random points in the river network and added information in a special array giving the surface level of the lake at any given point. That allows us to have large lakes, covering many global-level cells (=regional-level tiles), at different elevations and with varying depths. We also tinkered with the rivers to try to ensure that each lake has only a single outflow, which is equal in size to all its inflows put together.

At the regional level, this all becomes something of a mess. First, my approach is to do the lakes first of all - before the rivers, even. The idea is that we really need to know where the lakes are before we calculate the paths of the rivers to influence those paths. For example, if we're calculating the path of a river that doesn't actually flow into/out of the lake, we want to make sure that it doesn't run into the lake.

So what UW does when creating the regional map is this: first, it goes through all the tiles whose source cells in the global map either are lake cells or next to lake cells. Then it creates terrain for that tile using the diamond-square method, exactly as I described before. (It also uses some of the same methods that we used for coasts, such as landfills, to try to make the lake coasts look more natural.) However, if the tile in question is next to a lake tile (rather than actually a lake tile), the edges of the tile are artificially raised to be higher than the surface of the lake. This ensures that the lake does not overflow these tiles. Then, all points that are lower than the surface of the lake are marked as lake. Then, UW ensures that all points adjacent to the lake are higher than the surface of the lake, and finally it deletes all terrain that isn't lake or immediately adjacent to the lake. So at the end of this pass, the terrain consists of just the lake beds. Then UW does the rivers, as described earlier, and only then does it do the real terrain, where it creates terrain around the lakes and rivers.

This is obviously wildly counterintuitive but I've found it to be the least bad way of doing this, as it allows for more control in the river calculations to try to make the rivers interact correctly with the lakes.

Still, it's horribly difficult to get everything to work properly. One perennial problem is rivers that emerge from lakes and then go back into them, which seem to crop up occasionally no matter how much I tinker with the global lake-making algorithms or the regional lake-drawing ones. I've tried creating new algorithms to remove them, but they are slow and unreliable. A better one I've recently hit upon is the idea of forcing all rivers that are sufficiently close to lakes to generate additional blobs of lake around themselves, which has the effect of expanding the lakes out where the rivers are close. This doesn't completely resolve the problem but it helps. I've also tried restricting lakes to only tiles that correspond to lake cells in the global map - that is, not allowing them to overspill into the neighbouring tiles at all. That tends to result in very artificial-looking lake coasts, though.

Anyway, the result is lakes like these:


These look fairly reasonable, but there are still issues. That dark blob near the river that flows into the northern coast of the southern lake marks a sudden drop in elevation, where the river plunges from over 700m above sea level to just 1m above sea level - an elevation it retains until it flows into the lake, the surface of which is over 800m above sea level. That doesn't seem quite right. I don't know why my rivers sometimes do this - there is some kind of bug in the carving algorithm that calculates the height of the terrain over which they flow, but I can't find it. As with so many of the problems with this program, it's just an occasional issue, which makes it very hard to isolate what causes it, especially as so many different systems are interacting - many just on the rivers alone (calculating the course, calculating the terrain height, working out where tributaries join main rivers, adding springs, etc.) - even before you take into account the lakes as well.

Rift lakes are easier than normal lakes, because they simply follow the course of the main river through that tile. All UW has to do is paste lake templates - roughly blob-shaped bits of lake - over the river as it passes through the tiles that have been marked on the global map as containing rift lake. Well, there's a bit more to it than that, but that's the basic idea. So these lakes are added after the rivers have been calculated. They look like this:


It's hard to get the coasts of these right (as usual). If the blobs drawn over the river are too smooth, you get something that just looks like very wide river, like a snake that's swallowed a slipper. I've tried to make the blobs more jagged and vary how and where they're drawn a little, resulting in the rougher-looking coasts you see here. I think they are rather rougher than real rift lakes, at the moment, but it's another work in progress to tinker with them.

(I do like the way you can see that the lake is causing more rainfall to the east - the winds are strong westerlies here - the result, according the climate model, is a small area of Mediterranean-style climate in the middle of that big desert.)

So that's the current status of large lakes. They have a nasty tendency to break the laws of physics and look rather artificial, but most of the time they roughly work, so I must just keep tinkering with them and hope they tend to improve rather than deteriorate further. Next up will be small lakes, which at the moment at least are more straightforward.

5 comments:

  1. Heh, heh. Everybody hates lakes!

    What I do is to decide on where the lake is, then sculpt the land under the lake appropriately. The basic notion is to find the lowest point in the terrain that surrounds the lake and then modify the height of all the ground in the lake so that it all flows to that low point. That's not quite as easy as it sounds, but starting at the exit and working your way back across the lake adding small increments to the height works out (at least for me). For me the big advantage of this is that I can just do river generation as normal, because everything that flows into the lake area will end up coming out at the lake exit.

    ReplyDelete
    Replies
    1. This does make a lot of sense. One problem I face is this: when I reroute the rivers at the global scale, if the lake is very irregularly shaped some of them may "cross" over peninsulas extending into the lake. Obviously that's not right. Your approach would avoid that. I shall have to think about this...

      Delete
  2. I added dynamic rivers and lakes to 3DWorld years ago. My situation was a bit different. I only had a 128x128 heightmap, but I wanted lakes to form naturally by filling up with rain water. There was also an evaporation term that provided feedback to limit lake sizes. I started by finding all local minima in the terrain (which was not modified). Each one became a lake, and the border pixels around each lake were tracked by finding the closest points from the center that were above the water level. As the water rose, lakes merged, and their water levels were constrained to be equal. When the water level fell due to evaporation the lakes would split apart again. I tried to make everything preserve water volume. It was incredibly complex and took forever to get right.
    It still has this problem where every so often a split or merge will make a lake go crazy and disappear. I think it may be some sort of floating-point problem, instability or divide-by-zero. Unfortunately, if I instrument the code with debugging printouts, I can never make it fail. It can take a very long time to find a failure. So I never quite figured it out and decided it was rare enough to ignore.

    ReplyDelete
    Replies
    1. I have immense respect for this, as I can't think of many things harder than trying to model lakes *naturally* like this. I wouldn't even know where to begin!

      Delete
  3. I too hate lakes :-) Thanks for writing this up!

    ReplyDelete