17 Jul 2017

Note: the /x/ series of posts on my site are mostly unpolished mini posts. I’m trying out a few different ideas for procedural map generation; this is one of them. In my previous blog post I experimented with elevation based on river drainage basins. It was cool, but it didn’t match what we needed for our game, so I stepped back a bit and decided to work on something else. We want the game/level designer to have some control over mountains, coastlines, and rivers. The designer should be able to draw a rough sketch, and then the procedural generator will fill in the rest. We’re intentionally making unrealistic terrain (likely something low-poly and cartoony), but that’s something for a future blog post. This week’s experiment is about designer control of mountains and coastlines. Draw ⯀mountain ridges or ⯀coastlines: erase all Mountain ridges Erase Coastlines Noise: Things to try: Draw the mountain ridges, and the system figures out the coastline. Adjust the coastline by drawing your own.

Draw the coastline, and the system figures out the mountain ridges once you completely enclose an area. Draw your own mountain ranges to replace the default.

Draw a partial coastline and also some mountain ridges to see how the water/land “spills” in/out of the partially enclosed area. The implementation is surprisingly simple.

1 Distance fields # I had tried several approaches, including medial axis, before I found something I like quite a lot. The idea is to use distance fields. For each point on the mesh, calculate the (graph) distance from a set of seed points. Here are the three distance fields for the map you drew above: A = Mountains B = Boundary C = Coastline The land and water were spilling over the coastlines. To fix that, I made the distance fields for the boundary and mountains not cross the coastline. Distance fields are easy to calculate on a graph, using breadth first search (around 20 lines of code).

2 Combining distance fields # Given A = distance-from-mountain and B = distance-from-boundary, how should I combine them? My first thought was to interpolate the nearest constraint points. Between +1 and 0 I would interpolate linearly. But I draw an enclosed “O” shape for mountains, the interpolation would be between 1 and 1, and it’d end up as a plateau. I’d prefer it to be a crater. So I searched for some useful interpolation functions. I discovered that B/(A+B) works really well! Near the mountain ridges, A is small, so B/(A+B) is close to B/B = 1. Near the boundary, B is small, so B/(A+B) is close to 0/A = 0. In between, it interpolates. It’s scale-free: if I replace distances by ten times the distance, I get the same answer. This is an important property. Without it, I have much lower confidence in the results. 2.1 Pairwise combination The mountains and boundary aren’t enough. I wanted to be able to draw a coastline C as well. I extended the two-point interpolation: If C is furthest away, use the previous interpolation with only A and B but modify it for -1 (boundary) to +1 (mountains): 2*B/(A+B)-1

is furthest away, use the previous interpolation with only and but modify it for -1 (boundary) to +1 (mountains): If B is furthest away, interpolate between coastline (0) and mountains (+1): C/(A+C)

is furthest away, interpolate between coastline (0) and mountains (+1): If A is furthest away, interpolate between coastline (0) and boundary (-1): B/(B+C)-1 There are sometimes weird effects with partially drawn coastlines. The boundary–mountain interpolation doesn’t always match up with the boundary–coastline + coastline–mountain interpolations. The problem is that any time I introduce if-then into the equation, there’s a potential for discontinuity. Are there other functions that don’t lead to discontinuities? 2.2 Harmonic mean I looked at the weighted geometric mean[1] and the weighted harmonic mean[2]. The weighted geometric mean would return positive elevations only, so it’s not useful here. The weighted harmonic mean though looks nice. For two values with elevation 0.0 and 1.0, it becomes B/(A+B) , which is exactly what I had started with. For three values: (+1 * 1/A + 0 * 1/C + -1 * 1/B) / (1/A + 1/C + 1/B) If there’s no coastline, C is infinity, so 1/C is 0, and this becomes (1/A-1/B)/(1/A+1/B) which turns out to be equivalent to the previous rule of 2*B/(A+B)-1 .

is infinity, so is 0, and this becomes which turns out to be equivalent to the previous rule of . If the land is surrounded by coastline, in the interior B is infinity, so 1/B is 0, and this becomes (1/A)/(1/A+1/C) which turns out to be equivalent to the previous rule of C/(A+C) .

is infinity, so is 0, and this becomes which turns out to be equivalent to the previous rule of . If the land is surrounded by coastline, in the exterior A is infinity, so 1/A is 0, and this becomes (-1/B)/(1/C+1/B) which turns out to be equivalent to the previous rule of B/(B+C)-1 . Hey, that’s pretty cool — when there’s no coastline or a complete coastline, it is the same as my previous set of rules! Where it differs is when there’s a partial coastline. I had discontinuities with partial coastlines, but the harmonic mean eliminates all the discontinuities. It’s less code too. Nice! 2.3 Weighting functions With harmonic means, there’s also the potential to use different weighting other than linear: (+1 * 1/Aⁿ + 0 * 1/Cⁿ + -1 * 1/Bⁿ) / (1/Aⁿ + 1/Cⁿ + 1/Bⁿ) . Linear: n=1 Square: n=2 Square root: n=sqrt(2) For the demo on this page I chose harmonic means with sqrt weighting. On land, it should produce more valleys and sharper mountain peaks. In the water, it should produce more shallow water and occasional deep water trenches. However it doesn’t actually produce that effect for the three-valued harmonic mean. :-( The sqrt weighting puts a lot more of the values in the middle, which is 0 for the -1:+1 range. That’s what I want — a lot of values near 0. But for the three-valued harmonic mean, it’s separately putting a lot of values in the middle of the -1:0 range (lots of -0.5) and the 0:+1 range (lots of +0.5). So it’s a cute trick but it has issues.