Heightfields, after all
For a long time the one hard rule of the planet work was no heightmaps. On 28 September the Meadowbound Engine replaced its volumetric planet terrain with measured and generated heightfields. Why, what a one-metre patch of Yosemite is doing in it, and what the change costs.
For a long time the planet work in the Meadowbound Engine had one rule above all others: no heightmaps. On 28 September the engine replaced its whole volumetric planet terrain with heightfields, measured and generated. This entry is about why the rule existed, why it fell, and what the planet is made of now.
One radius per direction
A heightmap on a flat map is simple: for every point on the ground, one number, the height. On a planet the same idea becomes a radial heightfield. Stand at the planet’s centre and look in any direction: the heightfield stores exactly one radius for that direction, the distance to the surface. Everything below it is rock, everything above it is air.
That is the whole representation, and it is also its limit. One radius per direction means the surface can be crossed exactly once on the way out from the centre. It cannot make a cave, because a cave is air under rock. It cannot make an overhang, a natural arch, a tunnel, the underside of a cliff, a sea stack leaning over the water. Every one of those needs a ray from the centre to leave the rock, enter it again, and leave it again.
Drag the ray
So: volumes
If the ground is going to have caves and overhangs everywhere on a planet, not just in one hero spot, it has to be stored as a volume: for every point in space, is it rock or air? The engine went that way with signed distance fields. A signed distance field stores, for every point, the distance to the nearest surface, negative inside and positive outside; the surface itself is where the distance is zero. Two fields can be combined with a minimum or a maximum, which is how you carve a cave out of a mountain or glue an arch onto a cliff, and the result is still a valid field.
The tools were PicoGK, the geometry kernel LEAP 71 released for computational engineering, and OpenVDB, the sparse volume format from film visual effects. The plan: generate the planet’s fields offline, bake them into volumes, turn the volumes into meshes, stream the meshes at runtime. The offline part is allowed to take a long time; the runtime part is not.
What it looked like
The adaptive refinement worked, and it was a joy to watch: as I flew down to the planet, the geometry grew finer everywhere at once, the whole globe the same kind of surface. But the ground itself did not hold up when I stood on it.
Coarse, blocky surfaces where a voxel was still too big. Straight lines where rock should never be straight. Many meshes running into each other, and the edges where one region met the next visible as tiles. Cracks between regions. Textures stretched across surfaces the mapping had not expected. A planet can be correct in principle and still wrong on screen, and on screen is the only place anyone will ever see it.
The volume approach was not wrong. It was expensive in every direction at once: in memory, in bake time, in the number of seams, in the amount of machinery between a number in a field and a pixel. And most of what it bought, caves and arches, is a small part of a planet’s surface.
What it is now
The planet now takes the heightfield path, and only that. Its offline forge writes six equal-angle cube faces of height and climate data. Plate-driven continental uplift and ridged relief are evaluated directly in height space: there is no voxel stage and no mesh extraction any more. An enrichment stage adds the landscape and drainage data on top.
At runtime the terrain is drawn with CDLOD: a quadtree over each face, and for every frame one cut through it, fine cells close to the camera and coarse ones far away, each level twice the size of the one inside it. The same height source answers the game session’s ground queries, so what you see and what you stand on cannot disagree.
Drag the altitude
A metre of Yosemite
A heightfield can do something a procedural volume could not easily do: hold a measurement. BinaryStars, the game, supplies a crop of the USGS 3DEP one-metre elevation model of Yosemite. 4096 × 4096 samples, 16,777,216 of them, each a 32-bit float, from 1162.026 to 2307.570 metres, about 4.1 kilometres on a side. The data is free to use; the game keeps its source address, its checksum, the crop and the licence record beside the asset.
The importer does not resample and does not touch the contrast. It checks the extent, the grid spacing, that every elevation is finite, and the original file’s SHA-256, and then writes the samples as they are. Every check is a refusal, not a repair:
nodata=im.tag_v2.get(42113)
data=np.asarray(im.crop((x,y,x+n,y+n)),dtype='<f4')
if not np.isfinite(data).all() or data.min() < -20000 or data.max()>20000 or (nodata and np.any(data==float(nodata))):
raise ValueError('crop has nonfinite, nodata or out-of-envelope elevations')
# TIFF rows go north->south; engine rows go south->north.
payload=np.ascontiguousarray(data[::-1],dtype='<f4')
if digest(a.source)!=a.source_sha256:raise ValueError('source changed during import')The digest is taken twice, before the file is read and again after, so a file that changes while it is being imported is caught too. The only thing done to the data is the order of its rows: a GeoTIFF runs from north to south, the engine from south to north.
On the planet the patch is placed on a tangent chart of the fictional world; it is not a claim to be Yosemite, or to be on Earth. The chart is the plane touching the top of the planet, and a direction from the centre lands on it at the planet’s radius times its sideways components over its upward one. That keeps every number near the patch in metres. The alternative, subtracting two planet-sized positions, does not survive 32-bit floating point, which at the size of the Earth is only good to about half a metre:
// The +Y chart avoids subtracting planet-sized floats at metre-scale samples.
float3 dem_height(texture2d_array<float> tex,float3 d,constant FrameUniforms& U) {
if(U.heightmap.x<16 || d.y<=.5f)return 0;
const float side=U.heightmap.x,spacing=U.heightmap.y,halfSamples=(side-1)*.5f;
const float2 metres=U.planet.x*d.xz/d.y;
const float2 edge=halfSamples*spacing-abs(metres);
const float fade=halfSamples*spacing*.25f;
const float w=smoothstep(0.0f,fade,edge.x)*smoothstep(0.0f,fade,edge.y);
if(w<=0)return 0;
const float2 uv=clamp(metres/spacing+halfSamples,0.0f,side-1);
const uint2 p=min(uint2(uv),uint2(uint(side)-2));
const float2 f=uv-float2(p);
const uint n=U.misc.y,tiles=(uint(side)+n-1)/n;
float v[4];
for(uint k=0;k<4;++k){const uint2 q=p+uint2(k&1,k>>1);
v[k]=tex.read(q%n+U.misc.z,6+(q.y/n)*tiles+q.x/n).r;}
return float3(mix(mix(v[0],v[1],f.x),mix(v[2],v[3],f.x),f.y),w,0);
}The outer quarter of the patch, about 512 metres, blends into the procedural terrain around it: w rises from nothing at the edge to full weight a quarter of the way in, along both axes, and inside that the measured heights stand alone. The patch lives in spare slices of the height atlas the rest of the planet already uses, read in tiles, four samples at a time. CPU and Metal read it with the same bilinear sample; across 121 probe points at metre and centimetre offsets they agree to within 3 cm.
It is regional measured terrain, not a metre everywhere. But where it is, it is real rock.
What the change removed
The volumetric planet did not become an option that is switched off. It was removed: the planetary solid and cave sources, the refinement streams, the volume seams, the solid render, shadow and contact paths, the tools that cooked and inspected them. Twenty-two test registrations went with the features they tested, listed by name in the engine’s heightfield contract. Git keeps the history; there is no dormant second production path.
PicoGK stays, for what it is best at: individual objects. The rocks and props of the forge are still made with it, because a boulder does not need to be part of the planet’s surface representation to sit on it. The volumetric clouds stay, and so do the physical models for gas and water. The rule has not so much been broken as moved to where it earns its cost.
Still open
The honest list is in the engine’s own README, in the same table as everything else. The vertex cache is still 80 bytes a vertex, not a streamed hierarchy of residual and material tiles. There is no page streaming of the regional data yet, no centimetre accuracy bound, no drainage recalibrated to the measured patch, and no connection to canonical gameplay state. And the frame time is still far too long.
The next step is to look at it from the ground, on the grass, and see whether it holds.