Terrain · interface

HeightfieldOptions

A heightfield, and the two questions everything else asks it.

Explained in Terrain.

interface HeightfieldOptions
import type { HeightfieldOptions } from '@driftengine/terrain';

In depth

The whole design of this file is one rule: the answer a query gives is the surface that is drawn. A heightfield is stored as a lattice of samples and drawn as triangles, and those are not the same surface — a bilinear patch through four corners and the two triangles that span them agree only along their shared diagonal and at the corners. Interpolating bilinearly because it is the obvious thing gives a character that floats over half of every cell and sinks into the other half, by up to a quarter of the cell's height range, everywhere, for ever. It is a defect with no symptom a screenshot can show and no test that fails, which is exactly the kind this repository writes down.

So heightAt reads the triangle, and heightfieldPatch builds those same triangles, and a test asserts each vertex against the query.

And there is now a fourth surface, so the rule extends rather than changes. Wave 4B stores heights as a DTEX layer, which quantises them — so a renderer reading the layer while a query reads the samples this class was built from differ by the format's tolerance, everywhere, permanently. terrainTexture.ts answers it the only way that holds: terrainFromHeightLayer builds a Terrain from the decoded samples, and everything reads that one object. The rule is unchanged — there is one surface — and what changed is where the numbers come from.

The normal is the other way round on purpose. A face normal is constant over a triangle and jumps at every edge, so a field built from them is faceted, and — worse — two patches meeting at a boundary shade differently along it, which reads as a crack that is not there. normalAt is a central difference over the samples, continuous by construction, and it is what the mesh carries as its vertex normals. A caller who wants the plane a wheel actually rests on takes three heightAt samples; a caller who wants to orient a character takes this.

Properties

NameTypeDescription
widthreadonlynumberSamples across x. At least two, since one sample is not a cell.
depthreadonlynumberSamples across z.
spacingMreadonlynumberMetres between samples, the same both ways.
heightsreadonlyArrayLike<number>width * depth heights in metres, row-major with x running fastest.
originreadonlyoptionalReadonlyVec3World position of sample (0, 0). The origin's own y is added to every height.