The amount of procedural games nowadays is very big. From Minecraft to Canabalt, the replayability that this form of random game gives is very appealing to a lot of players; And to developers too, that sometimes doesn't have enough time or skill to create levels good enough to keep players entertained for long time spans.

Subjects like procedural terrain and procedural events are well covered in a lot of sites in the web, some other subjects are covered in PCG wiki and some other forums. Even though, if you want to create some new type of procedural content, you have to venture and empirically test methods to create this content. We'll treat here my adventure on creating procedural racetracks.

Thinking of it

Before we start to shape any idea, we have to know well what is the content that we want to create. We can look some photos to help on this:

TopGear:

MarioKart:

Real life:

We can see on these photos that a racetrack (not all of them, but the kind we want to create):

Is a perfect loop. That is, the start and the end are the SAME point, this way the player can lap;

It can be very straight (though it should have at least 3 curves, to form a looping polygon);

But it can be very curvy too;

It can either be concave or convex;

It can have intersections, preferably, in a transverse way, so it will avoid the track going over itself;

One curve can be very open(that is, their angle is closer to 180), or very closed(angle closer to 0);

But not that closed, otherwise the track will get over itself.

7 simple traits that define for us what a racetrack is, and what characteristics define them.

For you that want a walk-through on how to create any procedural content, I didn't had an epiphany and came with all of these traits just by looking images. I only noticed 3 or 4 traits, the others were being noticed while I was testing. If while testing the generated content doesn't resembles what it actually is in real life, then try to come with a rule that make it fit in.

Implementation

Ok, mathematically, we can start with a simple statement: a racetrack is a polygon.

Yes, I know, polygons don't intersect themselves, but we'll get there. First we need to start from somewhere, and starting from an easy point that we all know since the first years at school helps a lot.

Recently, I hear a lot of people saying that Perlin/Simplex noise is an answer to any PCG, by the way, when I posted a #ScreenshotSaturday that I was working on these tracks, one guy asked me if they were generated with Perlin noise. Of course interpolated noise is useful in a lot of areas, but sometimes trying to fit them in every place can be very hard to you and your time. For example, taking a polygon from a map generated with Simplex noise can give us a very cool result, but this isn't a very simple method, and it isn't very easy to visualize for a lot of people. If you want to create your own kind of procedural content, you have to try to start with the easiest method to visualize the generation, which will be better for you during the development. This time, generating a 1D noise with your own language `rand()` function was chosen.

We'll use a method that will give us a polygon with no martyr. I show you the Convex Hull. In short words, a convex hull is the smallest polygon that can be obtained that contains a entire determined set of points. This seems very interesting for us!

We'll start defining the medium size that our track should have. I defined 250x250, which would give us (aproximately) one track of 600m (roughly measured by the rectangle perimeter) You can define what you want too, even use other units, like miles instead of meters.

From this defined retangle, generate a collection of random points inside it.

Well, this method works well to create random points, but these points are too random, they don't follow any rule of distribution and scattering as the described by Mick West in this GamaSutra post , but for our purposes, our points are fine. If you want more concise tracks, consider fixing this.

After generating a collections of points, we can generate the convex hull. I won't talk about details on how to calculate a convex hull, since there are tons of articles treating this on the web. The implemention that I used (and probably the one you'll want to use) returns a new collection of points with only the ones that are part of the polygon, and they're already sorted in a counter clockwise manner.

Vector2[] dataSet = ConvexHull.computePolygon(points);

Very simple. At this point we already have a convex polygon very beautiful, the little problem that may trick us ahead: In some cases, some vertices can get very "cumpled", and when we apply a spline to the track, this causes a little cusp in the interpolation, it looks like this:

Notice how the green curve create some "little loops" where the points are too close. We will try to avoid this creating a function that actuate on out dataSet and push the points that are too close apart.

If you want a simple and convex racetrack, you can jump to the end of the article, to the Interpolation and Mesh part, because we already have the basis for the track done.

As our definitions go beyond a convex map, we'll add some more points between the ones we already have, and these will be perturbed a little to increase the number of curves on the track and give it a non-convex form. We'll do this creating a new point between each 2 points, and for each one, choosing a length and direction and applying a displacement of these length and direction on it. Code speaks better:

Pay attention to the difficulty parameter, it makes the length of the vector be closer to 1 or maxDisp, this can help changing the difficulty of a track, putting more curves.

difficulty = 1f/20f

difficulty = 20f

Our map is ALMOST done, this last method we did add some cool curves, and also make it intersect itself in some cases. It just gives us one problem (that we already took note before), some curves are too closed. The track may go over itself when you try to create a mesh with thickness for it. I solved this by making that the max angle of two adjacent points never get greater than 100 degrees.