Terrain Generation part 3: random continents

Where the last blog post left off was the idea of layering noise functions to create more natural looking terrain. That terrain looked like islands scattered aimlessly. That’s not nearly so much land mass as is wanted.

Next step is to slightly adjust the ratio of each noise function until the voronoi tiles more clearly show when something should be land.

Layering noise functions grey

Layering noise functions

This code here doesn’t display all function’s source but it should get the idea across of how the noise is composed.

let (width, height) = (512, 512);
let rng = rand::rngs::StdRng::seed_from_u64(20);

let basic_multi = ScalePoint::new(Billow::<Perlin>::new(20).set_lacunarity(3.0)).set_scale(3.0);
let normalized_noise = crate::utility::NormalizeNoise2::new(width as usize, height as usize, basic_multi);

let voronoi = crate::voronoi::Voronoi::new(width as usize, height as usize).set_variance(10);
let region_map = voronoi.generate(rng);

let mut regions_in_group = Vec::new();
for t in 0..voronoi.tiles_x {
    regions_in_group.push(t);
    regions_in_group.push(t + (voronoi.tiles_y - 1) * voronoi.tiles_x);
}
let region_map = region_map.with_region_group(regions_in_group);
let blur_noise = crate::utility::BlurNoise2::new(7, region_map);
let blur_noise = ScaleBias::new(blur_noise).set_scale(-1.0).set_bias(1.5);

let blend = Blend::new(normalized_noise, blur_noise, Constant::new(0.5));

That’s too much land mass now. But with the above method I can more evenly create land mass by setting entire voronoi tiles to scale towards land.

I’d like to have continents and irregular land mass. The simplest way to immediately see some results is to shuffle the order of voronoi tiles and take the first N. I can exclude the top and bottom layers by not even including them in the results to take from.

pub fn random_coverage(voronoi: Voronoi, coverage: f64, mut rng: impl RngCore) -> RegionMap {
    let mut remaining_tiles = Vec::new();
    for y in 1..voronoi.tiles_y - 1 {
        for x in 0..voronoi.tiles_x {
            remaining_tiles.push([x, y]);
        }
    }
    remaining_tiles.shuffle(&mut rng);
    let min_coverage = (coverage.clamp(0.0, 1.0) * (voronoi.tiles_x * voronoi.tiles_y) as f64).floor() as usize;

    voronoi.generate(rng)
        .with_default_level(0.0)
        .with_region_group(
            remaining_tiles.iter()
                .take(min_coverage)
                .map(|x| { x[0] + x[1] * voronoi.tiles_x }).collect())
}

Layering noise functions grey with random walk

Layering noise functions with random walk

With sampled noise, that doesn’t look so bad. That land mass is an interesting shape.

One problem is that the map isn’t really a cylinder. Wrapping the map around produces an artifact like this. Note that problem down for a later blog post.

Border artifacts

So far, every map has been 512 tiles by 512 tiles, and an image representation of the same size.

Scaling up to 1400 tiles by 700 tiles brings out some weirdness. It’s still essentially the same map but stretched oddly and I don’t like the look of it.

Layering noise functions grey with random walk

Layering noise functions with random walk

Accounting for width and height differences in noise functions will take care of that.

let seed: u32 = 20;
let (width, height) = (860 as u32, 640 as u32);
let rng = rand::rngs::StdRng::seed_from_u64(seed as u64);

let basic_multi = ScalePoint::new(Billow::<Perlin>::new(seed).set_lacunarity(width as f64 / 180.0)).set_scale(width as f64 / 180.0);
let normalized_noise = crate::utility::NormalizeNoise2::new(width as usize, height as usize, basic_multi);

let voronoi = Voronoi::new(width as usize, height as usize)
    .set_variance(10)
    .set_tiles(width as usize / 64, height as usize / 64);
let region_map = random_coverage(voronoi, 0.35, rng);

let blur_noise = crate::utility::BlurNoise2::new(4, region_map);

let blend = Blend::new(normalized_noise, blur_noise, Constant::new(0.38));

Layering noise functions grey with random walk and resizing map

Layering noise functions with random walk and resizing map

Commentary:

  • That edge of map bordering cylinder isn’t quite right.
    • Adjusting noise functions to wraparound would be the fix.
  • The noise patterns are clearly evident and get more clearly shown when zoomed in.
    • This map is an alien planet so I don’t much mind. In fact, making interesting water features is a good thing.

On the whole, that’s a good enough height map in order to start looking at other factors of terrain generation next time.

A few other generated samples from different seeded RNG:

Final generation seed rng 55

Final generation seed rng 1345345