Terrain Systems

XZ Columnar Terrain

An experiment in constructing a sealed, voxel-owned terrain surface from heightmap-derived planar patches and vertical side fragments.

This experiment began with a very simple idea. I wanted to bring the visual world of games like Terraria, Starbound, and RimWorld into full 3D.

An overhead view of a RimWorld colony with terrain, buildings, crops, and characters arranged on a tile-based world.
RimWorld presents its world as a collection of discrete, grid-aligned units while still allowing the environment to read as a continuous landscape. Screenshot source: GameStar.

One of the things that makes games like those unique is that they make their respective worlds readable as discrete units. You can look at the environment and immediately understand that it is made from units of “stuff” that can be mined, traversed, placed, or otherwise interacted with individually.

Minecraft is the obvious 3D version of that idea. Its blocks make the structure of the world completely explicit. But I wanted to see whether I could reach beyond a block-based world while preserving that same sense of discrete, editable terrain.

That led to an interesting question: how do you create a three-dimensional world that feels tiled and discrete without making everything look like a cube?

My preliminary answer was to keep voxels as the editable spatial structure, and sculpt the surface. I was comfortable keeping voxels underneath, but I did not want the player to see a world made from exposed voxel faces. Instead, I wanted the terrain itself to resolve into deliberate, tile-like pieces.

The Setup

To achieve that goal, I chose to start with a continuous terrain surface in 3D—a heightmap at first, though I eventually wanted to generalize the approach to density fields so it could support things like caves and overhangs.

In the experiment, the noise started at a scale of one sample per 20 world units and used five octaves. Each successive octave doubled the frequency while contributing half as much amplitude as the previous one. The combined result was normalized and scaled to produce the final terrain height.

The important property for the rest of the experiment was that this produced a continuous height function. For any XZ position, I could evaluate a corresponding terrain height. I could also sample nearby positions to estimate the local slope of the surface.

On top of that continuous surface, I would impose a readable tiled structure. Each visible piece would still feel like an individual unit of terrain, while the landscape as a whole would be allowed to resemble a natural environment.

That is where the experiment quickly became a geometry problem. Taken together, the setup was a continuous terrain surface flowing through a discrete voxel lattice, as shown in the visualization below. The visible surface therefore had to reconcile those two representations.

3D controls: Use the mouse to rotate, pan, and zoom the camera. Alternatively, click or Tab to the viewport and use the arrow keys to rotate, and + or - to zoom. Reset Camera restores the original view.

The highlighted voxels are those intersected by the continuous terrain surface. Some contain broad surface patches, while others contain only small fragments where the surface clips a corner or edge.

However, in this situation, what should count as one tile—how do you construct one? The obvious choice is to use the individual voxels that contain the surface as a basis. As I discovered, though, that choice introduces several issues.

The Tile Problem

There are several competing constraints in an approach like this (though some were admittedly self-imposed for what I was trying to achieve):

  • As stated previously, you need some geometric basis for constructing each tile—how do you use the voxel lattice and the surface to create them?
  • Whatever you do with the visible surface, it still has to map back to voxels in the end. That voxel ownership is what makes the terrain editable by the player.
  • The resulting tiles still have to fit together into a reasonably coherent terrain surface.
  • Because terrain would need to be loaded, unloaded, and rebuilt in real time, tile construction should be explicitly local. Each tile should be constructible using only its own voxel and the portion of the continuous surface passing through it, without polling neighboring geometry.

My first real approach was to approximate the terrain inside each surface voxel with a simple tangent plane. I found the points where the continuous surface intersected the voxel boundary and used their centroid as a representative point for the local surface. Combined with the local surface normal, that was enough to define a plane that locally approximated the terrain inside the voxel.

But defining the plane was only part of the problem. The visible tile itself could not extend infinitely—it had to be constrained by the voxel boundary. So the actual tile came from intersecting that local plane with the voxel cube.

3D controls: Use the mouse to rotate, pan, and zoom the camera. Alternatively, click or Tab to the viewport and use the arrow keys to rotate, and + or - to zoom. Reset Camera restores the original view.

A plane intersecting a cube can produce many different bounded polygons depending on its orientation and offset.

As you can see in the visualization above, this is where the approach started to break down. My goal was to create rectangular tiles, but a plane/cube intersection does not reliably give you that. Depending on how the plane passes through the voxel, the resulting tile might be a triangle, quadrilateral, pentagon, or hexagon. Worse still, it can also shrink down into a tiny fragment near an edge or corner.

So even though the geometry was valid, it did not give me the kind of consistent tile structure I was looking for. If each voxel was allowed to define its own tile this way, the terrain surface would be made from pieces with wildly different shapes and sizes.

The problem was not purely aesthetic either. Those irregular pieces also prevented this method of construction from producing a sealed terrain surface. Neighboring pieces did not meet in a way that completely covered the terrain, resulting in holes.

That led me to start thinking about the problem in terms of surface coverage. I knew that planar approximations were never going to reproduce the continuous surface perfectly, but I didn't necessarily need them to. What I needed was a construction that stayed close enough to the original surface while reliably covering the terrain's XZ footprint. Small discrepancies between neighboring pieces could potentially be sealed afterward.

This was an important shift in the way I approached the problem. Instead of treating the terrain as a completely general 3D surface, I was now deliberately giving the vertical axis a special role and prioritizing horizontal coverage.

From Voxels to Columns

The key change was to stop asking an individual surface voxel to define the footprint of a tile. Instead, I began treating each XZ voxel position as a vertical column through the terrain.

That gave me something the previous approach could not: a fixed, rectangular tile domain. Rather than letting whatever fragment of the surface happened to fall inside a voxel determine the tile boundary, I could define the footprint first and then consider the continuous terrain across that entire region.

That also gave me more surface information to work with when choosing the tile's representative plane. The next question was how to reduce that richer information back down to a single planar tile.

Constructing the Surface

With the tile footprint fixed, I also changed how I chose the representative plane. Rather than deriving its tangent point from the intersection between the terrain and an individual voxel, I could evaluate the heightmap directly at the center of the XZ footprint and construct a tangent plane from the height and local slope there.

That gave me a simple and consistent way to construct each tile, but it still had an obvious limitation: the entire plane was being decided by what the terrain happened to be doing at a single point. Everything else happening across the footprint was effectively ignored.

I also briefly experimented with using the terrain heights at the four corners of the footprint directly. That gave a better picture of how the surface behaved across the tile, but introduced another problem: four points sampled from a curved surface are not generally coplanar. I could fit a plane to them, but once again I would be throwing away some of the information I had just gained.

What I really needed was some kind of intermediate representation that could use all of that local information before I reduced it back down to a plane. I also had the sense that the way the surface was sloped across the footprint needed to factor into the result—not just height information. My initial line of reasoning was to somehow combine or average the gradients around the edges of the XZ footprint.

That line of thinking eventually led me to a bicubic Hermite patch, which is something I had admittedly never seen before this experiment. It gave me a way to preserve both height and slope information across the footprint instead of reducing that information too early.

For each corner of the footprint, I sampled the terrain height and estimated its slope in X and Z. Those values defined the Hermite patch across the cell. I then evaluated the patch at its center and used the resulting height and gradient to define the final tangent plane.

ySlope
Direct 0.00 (0.00, 0.00)
Hermite 0.00 (0.00, 0.00)

3D controls: Use the mouse to rotate, pan, and zoom the camera. Alternatively, click or Tab to the viewport and use the arrow keys to rotate, and + or - to zoom. Reset Camera restores the original view.

The direct tangent uses the height and slope of the original heightmap at the center of the cell. The Hermite tangent uses the height and slope evaluated from a bicubic patch built from information across the entire footprint.

The patch itself was never meant to become the visible terrain. It was simply a way to gather more of the local surface behavior before reducing everything back down to a planar tile.

In practice, that also helped the resulting planes follow the underlying continuous surface more closely. Because the Hermite patch incorporated information from across the footprint, its center height and slope tended to produce a plane that better reflected the broader shape of the terrain than one based only on a direct sample at the center. And, depending on the terrain, the two planes could differ noticeably.

Voxel Ownership and Sealing

At this point I had a consistent way to construct one planar surface across an XZ footprint, but the plane did not necessarily belong to a single voxel in Y.

If the underlying surface was steep enough, the planar tile could extend up through the voxel column over many voxels. And because the terrain was still meant to be editable at the voxel level, I needed to preserve that ownership rather than treating the entire plane as one indivisible piece.

Chunk boundaries made that ownership question more important. A single planar tile could cross not only several voxels in Y, but also a Y-chunk boundary. Its pieces therefore had to be assigned to the voxel and chunk that actually contained them.

The solution was to slice the plane wherever it crossed a horizontal voxel boundary. Each resulting surface fragment could then be assigned to the voxel whose vertical range contained it. The same rule naturally carried across chunk boundaries, keeping the ownership model local and predictable.

With ownership handled, the remaining issue was continuity between neighboring columns. The columnar construction gave me reliable horizontal coverage across XZ, but adjacent tiles were still built independently. Their shared edges would often end at different heights, leaving vertical gaps between them.

This brought the surface-coverage problem back in a different form. I handled those gaps by generating side fragments along the boundaries between neighboring columns.

Those fragments acted like vertical skirts, connecting the edge of one planar tile to the edge of the next. Together with the sliced top pieces, they turned the independently constructed column surfaces into a sealed terrain surface.

XZ columnar terrain rendered with normal-based coloring, making the vertical sealing fragments easy to distinguish from the sloped top surfaces.The same XZ columnar terrain view rendered with lighting and terrain textures.
The same terrain shown with normal-based coloring and with lit terrain textures. In the normal view, the vertically oriented side fragments stand out clearly between the sloped top pieces, making the sealing structure easier to see.

Sealing did weaken one of my original locality goals. A column could no longer be processed in complete isolation, because determining what needed to be filled along an edge required comparing it against the neighboring column.

I still wanted to avoid making chunk construction depend directly on neighboring chunks, so each chunk-column grid included a one-cell halo around its owned XZ region. Those halo cells were constructed from the same underlying heightmap and provided the neighboring surface information needed for edge comparisons, including along chunk boundaries.

Where It Broke Down

By this point, the experiment was doing most of what I had set out to do. The terrain was built from discrete, voxel-owned pieces, neighboring columns could be sealed together, and the result still followed the underlying continuous surface reasonably well.

But getting there had also changed the nature of the system. What started as a more general voxel-surface idea had become something much more specifically columnar, and that introduced several tradeoffs.

The first was axis asymmetry. X and Z defined the tile footprint, while Y became the direction in which each tile was sliced, assigned, and sealed. That worked naturally for heightmap terrain, but it also meant the three axes were no longer being treated equally. The approach had become much less suitable for arbitrary 3D surfaces. That also worked against my original hope of eventually extending the approach from heightmaps to more general density-field terrain.

The fixed XZ footprint also became increasingly awkward on steep terrain. Every tile covered the same horizontal area regardless of slope, so a tile spanning a steep section of terrain had to stretch farther through world space than one on flatter ground. The result could be long surface pieces that no longer read as especially tile-like.

A steep mountainside in the XZ columnar terrain experiment, where the planar terrain tiles appear stretched into long pieces as they climb the slope.
On steep terrain, the fixed XZ footprint causes the planar top pieces to stretch through world space. The tiles remain consistent in horizontal coverage, but they become elongated and less tile-like as the slope increases.

The most visible problem showed up where the underlying terrain changed slope quickly. Because each tile was still a flat planar approximation, neighboring tiles could end up with noticeably different orientations. Instead of following the continuous surface smoothly through those regions, the mesh developed sharp ridges where one planar approximation gave way to the next.

That effect was often exaggerated because adjacent top pieces did not necessarily meet cleanly at their shared boundary. One tile could project farther than the other, creating a staggered edge on top of the slope change itself. The surface was sealed, but it could still look jagged in exactly the places where the original terrain was changing shape most quickly.

A close-up of the XZ columnar terrain experiment showing sharp, jagged transitions between neighboring planar tiles on a snowy mountainside.
Where the underlying terrain changes slope quickly, neighboring planar tiles can diverge noticeably. Instead of forming a smooth transition, the surface develops sharp ridges and staggered edges between adjacent pieces.

That also made the mesh a poor candidate for traversal or collision. In the same places where the visible surface became jagged, the geometry could create abrupt ridges and awkward transitions for a character to move across. A smoother collision surface could be generated separately, but then the rendered terrain and the surface the player actually moved over would no longer agree very well. Characters would appear to clip through or otherwise ignore parts of the visible geometry.

There was also a locality tradeoff. Sealing required neighboring surface information, so the construction was no longer completely local at the individual-column level, even though the halo kept chunk construction independent.

Taken together, those tradeoffs gave me a much clearer sense of where this approach worked well and where it started to strain. More importantly, they gave me some ideas I can carry forward.

What I Took From It

In the end, this was more of an exploration. I started off with a somewhat vague idea, followed it to one of its logical conclusions, and ended up with what I have described above.

I don't think I will be using this exact terrain construction for anything I currently want to build, but I can still imagine cases where it could make sense. Something closer to a digital board game, for example, could probably make better use of the strongly tiled surface—especially if character movement was designed around the geometry rather than expected to behave like ordinary free movement across terrain.

One thing the experiment clarified for me was the idea of ownership. I already knew that I wanted the visible surface to be separate from the voxel structure underneath it, but working through this system made the relationship more concrete. Surface geometry still has to belong somewhere. If the underlying spatial structure is what makes the world editable, then the visible pieces need clear rules for which voxel or chunk owns them.

The bigger idea I want to carry forward is the shift toward defining the domain of a surface piece before constructing it. My first approach let the intersection between the continuous terrain and the voxel lattice decide what geometry was available. The columnar approach reversed that: I defined the XZ footprint first, then asked how the terrain should be represented inside it. That feels like a much more general design idea—either work with whatever geometry naturally falls out of the underlying representations, or define the region you want to cover first and fit the surface into it.

The Hermite construction was another useful result. I originally arrived there because I wanted some way to make use of the height and slope information spread across a tile rather than relying on a single sample. It gave me a way to turn that richer local information back into the simple planar geometry I was looking for, and it also introduced me to a kind of interpolation I hadn't worked with before. That feels like a technique with uses beyond this terrain experiment.

The biggest unresolved question is still the tile shape itself. Forcing square tiles onto a continuous terrain surface in 3D was always going to be awkward, and this experiment made some of the limitations involved very obvious. I still think there may be a better way to do this without producing the stretching and jagged transitions I ended up with here. I just don't know what that construction looks like yet.

So although I would approach the final surface construction differently, several ideas from this experiment are worth keeping and exploring further: explicit ownership of visible terrain, defining surface domains up front, reasoning in terms of coverage, and using richer local surface information before reducing it into simpler geometry.

Related Project

Plane Voxel Lab

An experimental C++ / OpenGL lab for investigating voxel terrain representation, surface construction, rendering, spatial systems, and approaches to editable terrain.