Sunday, October 9, 2016

Making Islands

Many of the procedural map generators you'll find on the Internet generate maps of islands, like this example from Red Blob Games:


Islands are nice because they're self-contained, and having all the edges of the map as water avoids problems with land running off the map.  Island maps are also handy for something like a role-playing campaign because they set a natural boundary to the story.  But Martin O'Leary's fantasy map generator creates maps that look like a portion of the coastline of a bigger country, like this early example from my map generator:

These maps look pretty authentic.  But although I like the look of these maps, I also wanted to be able to generate island maps.  But Martin's generator doesn't do islands. So how do you generate an island, anyway?

To start with, you can simply plop a blob down in the middle of the map.


That's certainly an island, but it's a pretty unsatisfying sort of island.  A more interesting island should have a more irregular (but natural-looking) shape, possibly with some outlying pieces.  I don't really care about perfectly simulating the natural processes that lead to islands.  I just want an island shape that is interesting and plausible.  So how can we generate an interesting island?

Let's back up a step.  Before we worry about generating an interesting island, let's consider how we can generate an interesting landscape -- without worrying about whether it is an island.  One common approach is to use noise.  A common technique in procedural generation is to create a heightmap based on a noise function, usually overlayed at several frequencies.  The result is not always entirely plausible, but our brains interpret it as "natural looking":


(That view, by the way, uses the 3-D viewer from this page.   You can play around on that page generating noise-based landscapes.)

Unfortunately, the landscapes generated that way aren't often islands:


A simple way to turn these landscapes into islands is to apply a circular mask to the landscape.  Anything outside the mask is pushed down into the sea. Voila!  An island:



That's a little better -- it's definitely an island, and has some interest within the boundaries of the island -- but the circular outline is too apparent.  A circle just isn't a very interesting island mask, largely because it's so obviously unnatural.  We know how to create an interesting landscape using noise, so our problem is really how to create an interesting island mask.

An initial idea is to make the mask more interesting by pushing the circle inward in some places, and outward in other places, in some sort of natural-looking way:

That's an improvement -- still a blob, but at least more interesting than a perfect circle.  So how can we do this pushing in and out effect?

The approach I took was to create a circular island and then perturb it.  The idea is to randomly move all the pieces of the island around -- to perturb their locations.  In pseudo-code it might look something like this:
for (x = 0; x < mapWidth; x++) {
       for (y = 0; y < mapHeight; y++) {
           if (circularMask(x,y,radius) == true) {
                 // Circular mask says to put some land at
                 // (x,y).  Instead, let's perturb that
                 // location and put land in the perturbed
                 // location instead.
                 x = x + randomOffset();
                 y = y + randomOffset();
                 world[x, y] = land;
           }
       }
    }

Let's try it:


Well, that doesn't look quite right.  The problem is that we're just randomly throwing every bit of the island around the map.  Some of it ends up in the center of the map, but the rest goes every which way.  We want neighboring parts of the island to move together -- so that if one piece of the shore gets pushed out, so do its neighbors.  Instead of moving each piece of the island independently, we want to move pieces that are near each other approximately the same way.

Luckily for us, that's exactly what noise does.  The value of the noise function depends upon the parameters you give it and the value varies smoothly as those parameters change.  If we use our x,y coordinates as the parameters, then nearby points will be similarly perturbed.  So let's revise our code to use noise to perturb the island locations:
    for (x = 0; x < mapWidth; x++) {
       for (y = 0; y < mapHeight; y++) {
           if (circularMask(x,y,radius) == true) {
                 x = x + noise(x,y);
                 y = y + noise(x,y);
                 world[x, y] = land;
           }
       }
    }

Let's try it:
Although this looks much better, there's a (possibly non-obvious) problem.  In the algorithm above, I'm adding the same noise to both the x and the y coordinates.  So points will only be perturbed along a diagonal -- the same distance and direction in both x and y.  To address this, we need to modify the algorithm so that we have separate noise sources for the two dimensions:

    noiseX = new NoiseGenerator();
    noiseY = new NoiseGenerator();
    for (x = 0; x < mapWidth; x++) {
       for (y = 0; y < mapHeight; y++) {
           if (circularMask(x,y,radius) == true) {
                 x = x + noiseX(x,y);
                 y = y + noiseY(x,y);
                 world[x, y] = land;
           }
       }
    }


The cool thing about generating islands this way is that we can create quite a bit of interesting diversity just by tweaking the noise we use to perturb the island.  For example, if we use a low-frequency noise, the perturbations are smooth:
 If we use a high-frequency noise, the perturbations are jagged:
We can also vary the magnitude of the perturbations.  A low frequency perturbation with a stronger magnitude gives us a stretched out blob:
A high frequency perturbation with a stronger magnitude gives us a wildly chaotic coastline:
These two adjustments alone can produce a nice variety of plausible island shapes, but you can imagine other variants.  Adding octaves of noise can create some interesting shapes:
Or you could try adding more noise in one direction than the other:
In these examples so far, the land I've been perturbing has been a flat circle.  More complexity and interest is added when the land being perturbed is more interesting -- when it has been filled in with noise, mountains, etc.

3 comments:

  1. Hi! I pondered this for a while creating my own map generator (link). As I need it to be fast, I decided to create Island shape via the easiest scenario I can imagine:
    1. select a random polygon not close to borders
    2. assign a random value from 0 to 1 as the polygon high
    3. find neighbor polygons and set their high as initial high * 0.99 with a small random modifier
    4. repeat step 4 for new neighbors while high greater than 0.01
    You will get a cute blob (hill). Repeat the steps up to 15 times to get a whole map.
    To get more continuous landmass I create a big Island blob as a first step and than add 10 small hills.

    How do you find the resulted shapes? Are they plausible enough or I need to add a kind off erosion?

    ReplyDelete
    Replies
    1. Thanks for the comment! I really enjoyed looking at your generator, and to me the islands look pretty realistic and interesting. I suspect in a real 3D view, the terrain would look too conical, but that's probably not a problem for this use. I also posted a link to your work on Reddit.

      Delete
    2. Hi Scott! Thanks, I'am so glad you like my generator! I don't have any experience in 3D (it's my first attempt even with 2D), so I cannot check the 3D view. I assume it really will be conical as I have one central mountain. It should be more interesting in the case you will use about 20 small blobs ("hills") instead of one big and 10 small ones.

      My problem is that isles seem flat. Highmap, which is considered to be a default view, does not show the topology in an obvious way. I've tried Martin O'Leary's idea with hills shading, but it looks not so good as I use only 8000 polygons (about 2000 land polygons). Maybe there is a way to use svg gradient, but I have no ideas on implementation.

      Delete