Physics · interface

HeightSurfaceOptions

Any function that answers "how high is the ground at this column?", as a GroundSurface.

Explained in Your first game, Rigid bodies.

interface HeightSurfaceOptions
import type { HeightSurfaceOptions } from '@driftengine/physics';

In depth

The hundred lines every consumer with terrain writes, written once. A world that is not flat has to answer that question somewhere, and the two things that already implement GroundSurface answer different ones: RibbonSurface is a route with a width and a bank, and BoxSurface is a set of flat pads. Neither is a hillside, a ditch, a heightmap or a dune. A consumer with one of those has a heightAt(x, z) of their own within an afternoon, and then spends the rest of the week turning it into a surface: a normal by differences, a band query, the route fields nothing asked for, and finally the discovery that the character controller and everything else that stands on ground each need their own copy of the wiring.

That was reported from outside as "there is no terrain height a controller can ask about", beside a note that the answer they wrote is a hundred lines and that every consumer with a road will write it again.

This is a seam and not terrain. It renders nothing, stores nothing, and has no level of detail: heightAt is the consumer's, and what this adds is the surface contract around it, so one object can be handed to a character controller, to a vehicle, and to whatever places props — and the floor is one thing rather than three copies of it. Terrain proper is its own track.

A heightAt that returns a non-finite number means there is no ground in that column, and that is the contract a layered world is built out of: a bridge deck answers its height over the span and NaN everywhere else, so it is a floor exactly where it is drawn and nothing anywhere else. Until 2026-08-30 this was said only in a private comment inside this file, which is no use to somebody deciding how to model a flyover.

Properties

NameTypeDescription
stepMreadonlyoptionalnumberHow far apart the two samples that measure the slope are, metres.
More

This is the scale the ground is measured at, and it is a real choice. The normal comes from a central difference, so a feature narrower than twice this is smoothed away — a kerb sampled at half a metre is a gentle ramp — while a step far below the resolution of heightAt measures its rounding rather than its slope. Five centimetres suits ground a person walks on.

A caller who can differentiate their own field should pass normalAt instead and pay nothing.

boundsreadonlyoptional{ readonly minX: number; readonly maxX: number; readonly minZ: number; readonly maxZ: number; }Where the surface exists at all. Outside it, there is no ground and a query says so.
More

Absent means everywhere, which is right for a field defined by arithmetic and wrong for one backed by a finite array — a sample off the end of a heightmap is not ground at height zero.

Methods

normalAtoptional

normalAt?(x: number, z: number, out: { x: number; y: number; z: number; }): void

The exact unit normal, when the caller has one.

ParameterTypeDescription
xnumber
znumber
out{ x: number; y: number; z: number; }
More

An analytic profile — a trapezoid ditch, a cone, a sine dune — knows its own derivative, and four extra calls to heightAt a tick to rediscover it numerically is both slower and less accurate. Must be unit length: nothing normalises it afterwards, and a controller compares it against a slope cosine.