Last Wednesday I decided to spend some time on caves as part of my work improving the terrain generation. Starbound’s cave generation used some very simple algorithms that worked reasonably well but weren’t very exciting, and I wanted to try my hand at an improved cave generation algorithm. So, I wrote a new generation method and reworked the existing ones to get more interesting results. The short version is that caves are more frequent, more connected, and more varied. If you want the long version, keep reading for a technical explanation!

I’ve been calling the new method “karst” cave generation since it loosely mimics natural geology by generating caves in layers. The algorithm itself is fairly simple, relying on a combination of several Perlin noise sources with different configurations. All of the noise is applied vertically, so it’s possible to use only one-dimensional noise sources. For Starbound, however, I’m sampling in two-dimensional coordinates around a circle with circumference equal to the world width, which creates a noise pattern that smoothly wraps around the world seam.

The first step in the algorithm is to pick layers to place caves at. This could be a simple probability applied to each Y position in the world, but I’ve chosen to enforce a minimum layer resolution in order to avoid clumps:

Next I use a Perlin function with a high period to select where on these layers caves should be generated:

Then I apply a second Perlin function with a moderate period and amplitude, as well as some positive bias, to vary the caves’ ceiling height:

I use another, similar function to vary the floor depth, with a lower amplitude to make the caves easier to walk in:

Next, I taper the endpoints of the caves to avoid the harsh edges, based on the original noise function being below a cutoff point:

At this point it looks a bit like real caves, but they’re very horizontal and boring, so the next step is to apply another function with a very high amplitude and period, which gives the caves slopes and makes the layers cross over each other:

This is looking much better, but is still very horizontal, so we mix in some large, tall chambers using a different configuration of the same algorithm:

…and finally mix in some normal worm caves to further increase verticality and connectivity:

The final result is a good mix of caves that feel natural and provide plenty of opportunities for exploration as well as some hazards (sudden drops and chambers of enemies) to keep things fresh.

In this example there are a LOT of caves in the world, and I’ve been tuning the configuration to get slightly fewer caves but still retain the right amount of complexity. I’ve already been having a lot of fun testing it, so that’s a good sign!

Sorry for the lengthy post, but hopefully some of you will find the explanation interesting and maybe even useful in your future projects.