RollerCoaster Tycoon (and the other similar Tycoon games) had a distinct terrain style, something like this:
Basically, it’s a like a heightmap, except tiles can be extruded if the difference between neighbor heights is not the same. In the case of this terrain, a tile is a collection of four distinct points on the terrain.
In my implementation, I have a basic Tile object which has a few properties:
- topLeft, topRight, bottomLeft, bottomRight: Absolute heights of the tile. These are clamped so all values are -/+ one of the other.
- flat, edge: An index into a tile set representing the type of tile rendered. The tile set stores properties like texture and color.
And a map (or Stage in my case) is a collection of these tiles. A 4 × 4 map is thus equivalent to an 8 × 8 heightmap. The biggest different is when the heights of neighbor points diverge, the tile appears extruded and a wall/edge is generated (see image above).
The triangulation process is simple enough. The tiles can be triangulated easily enough: in my case, I form two triangles: (topLeft, topRight, bottomRight) and (topLeft, bottomRight, bottomLeft). The edges, however, are a bit trickier.
Here’s a sample that generates the vertices for a left edge:
local function getLeftVertices(tile, neighbor)
local tileRef1 = tile.topLeft
local tileRef2 = tile.bottomLeft
local neighborRef1 = neighbor.topRight
local neighborRef2 = neighbor.bottomRight
local difference1 = tileRef1 – neighborRef1
local difference2 = tileRef2 – neighborRef2
Essentially, the logic is: if both reference points (e.g., tile’s topLeft and neighbor’s topRight) are separated by more than 1 unit, it’s considered separate and a triangle needs to be added. There’s three cases: both corners are separate, the first corner is separate, and the second corner is separate.
Of course, we could just brute-force it and add 0-area triangles, but what’s the fun in that? :)
The logic for the other edges are the same, just rotated. I couldn’t think of a nicer way of handling it without creating some weird metafunctions, so I just hardcoded the four permutations (left, right, top, bottom).
(Note: Units are rounded to the nearest integer, so fractional elevations aren’t allowed).
In the end, it creates something nice: