Perlin Worms

In the world of procedural generation, noise functions are ubiquitous and almost omnipresent. Perlin worms are a solution for those looking for river and cave generation in many of these procedural worlds. Perlin worms are actually an incredibly simple method to add realistic and winding caves and rivers. Yet for some reason, there’s a lack of explanation on the internet. And even less code to show it off

One of the best sources I found for this was actually a GDC talk which was archived called Math for Game Programmers: Digging with Perlin Worms. Squirrel Eiserloh takes about 30 minutes to go ahead and dive deep into the uses of noise and worms. He starts talking about Wandering Algorithms and Perlin Worms around the 10 minute mark.

Onto the coding process: How should we do this? The first step is determining where you want your worms to spawn. There’s way too many different places to find suitable locations – from min-max testing to throwing some numbers into rand() – I’ll let you choose. What’s important here is the creation and management of these worms

auto perlin_worm(float x, float y) -> void {
      auto worm_x = x;
      auto worm_y = y;

      float worm_dir = get_noise(worm_x, worm_y) * 360.0f;

      // MORE CODE LATER
}

The function fragment above is simply setting up some variables. The main line of interest is the worm_dir. worm_dir is a value in degrees (you can use radians as well if you want) that determines the direction the worm is facing. Here, we assume get_noise()’s result is normalized between 0.0 and 1.0. The next step will be to iterate forward and modify the direction.

// Iterate

const int MAX_STEPS = 64; //How many steps to iterate
const int STEP_SIZE = 5.0f; //How far to move the head
const int ANGLE_CHANGE = 120.0f; //Total change of +/- 60.0f

for(uint8_t step = 0; step < MAX_STEPS; step++) {
     //DO YOUR EFFECT

     worm_x += cosf(worm_dir) * STEP_SIZE;
     worm_y += sinf(worm_dir)  * STEP_SIZE;

     worm_dir += (get_noise(worm_x, worm_y) - 0.5f) * ANGLE_CHANGE;
}

This code iterates over the loop for MAX_STEPS number of times. The worm_x and worm_y are incremented by the direction. This can be multiplied by a scalar value STEP_SIZE to change how far the worm goes in a given direction. Finally, the worm’s direction is modified by plus or minus half of the ANGLE_CHANGE value. The effect is left up to the implementor. This could be clearing an area surrounding the worm, or maybe even a sphere?

In general, this is actually a very simple algorithm. You can customize the frequency of get_noise in order to vary how often a change is made and the angle in order to change how drastically these modifications affect it. You could even add multiple octaves of noise to get some very curvy rivers or caves.

In 3 dimensions, your step would be to take the vector direction, then rotate it on the relative cross vector and rotate it on the relative up vector in order to get a 3D transformation.