npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@driftengine/terrain

v4.14.0

Published

Heightfields: a query that answers the surface that is drawn, and patches that meet without cracks

Readme

@driftengine/terrain

A heightfield, the geometry it draws, and the same answer to both.

Cost: 1.3 KB gzipped on top of core. Measured by scripts/size-gate.test.mjs, which fails if it drifts more than 3% — the number is derived from the same floors that gate asserts, so a README quoting a stale one is a red suite rather than a thing somebody notices.

What it is

import { Terrain, heightfieldPatch } from '@driftengine/terrain';

const terrain = new Terrain({
  width: 257,
  depth: 257,
  spacingM: 2,
  heights, // 257 * 257 metres, row-major, x fastest
  origin: [-256, 0, -256],
});

const near = renderer.createMesh(heightfieldPatch(terrain, { x: 0, z: 0, cells: 32 }));
const far = renderer.createMesh(
  heightfieldPatch(terrain, { x: 32, z: 0, cells: 32, step: 4, neighbours: { minusX: 1 } }),
);

It imports no renderer. What comes back is MeshData, which goes to createMesh like any other geometry, so terrain is drawn by the pass that already exists. That is why this is a package rather than a part of core: a world with no terrain pays nothing, and a world with terrain pays for arithmetic.

The query answers the surface that is drawn

terrain.heightAt(x, z) reads the triangle the point falls in, not a bilinear patch through the four samples around it. Those are different surfaces — they agree only at the corners and along the diagonal they share — and the bilinear one is the obvious thing to write.

Writing it puts a character above the ground over half of every cell and below it over the other half, by up to a quarter of that cell's height range, everywhere, for ever. There is no screenshot that shows it and no test that fails. So the query reads the triangulation, heightfieldPatch emits that same triangulation, and a test asserts every vertex of a patch against the query.

terrain.normalAt(x, z, out) is the other way round on purpose: a central difference over the samples, so it is continuous across every cell boundary. A face normal jumps at every edge, which makes terrain faceted and — worse — makes two patches shade differently along the edge they share, which reads as a crack in geometry that has none. A caller who wants the plane a wheel rests on takes three heightAt samples; a caller orienting a character takes this.

The seam between two levels of detail is matched, not hidden

A heightfield drawn at one resolution is either too coarse underfoot or too fine at the horizon, so it is drawn in patches and the distant ones skip samples with step. Where a patch that skips none meets one that skips every other, the fine edge has vertices the coarse edge does not — and those sit on the field while the coarse edge cuts the chord beneath them. The gap is a crack, and a crack in terrain is a hole through to the sky.

The usual remedy is a skirt: a vertical curtain dropped from every patch edge, which fills the hole with something the wrong colour, lit the wrong way, and costs geometry along every boundary in the world for ever.

neighbours does it exactly instead. Name the step of any neighbour that is coarser than this patch, and the fine edge's odd vertices move onto the coarse edge's own straight segment, so the two edges are one polyline and there is nothing to fill:

heightfieldPatch(terrain, { x: 32, z: 0, cells: 32, step: 1, neighbours: { plusX: 4 } });

Only the coarser side needs naming — the finer patch is the one that moves — so a caller passes what it knows and leaves the rest out.

What it costs, stated because it is real: along a matched edge the drawn surface is the coarse chord rather than the field, so heightAt and the picture differ there by up to the sag of one coarse cell. That is the same error the coarse patch carries over its whole area, arriving one cell early.

Several materials across one field

const materials = new TerrainMaterials({
  materials: [{ color: [0.3, 0.5, 0.2] }, { color: [0.5, 0.5, 0.5], specular: 0.3 }],
  width: 64,
  depth: 64,
  weights, // 64 * 64 * 2, row-major, material fastest
});

heightfieldPatch(terrain, { x: 0, z: 0, cells: 32, materials });

A weight map baked into vertex colour, and that is the idiomatic answer here rather than a compromise. This engine's colour is vertex data — it is what makes the world one flat-shaded draw call. A splat map read by a shader would mean a texture bound on every terrain draw and a branch in flatFrag, which Track D measured at 19.8 KB gzipped for a capability generated into sixteen fragment permutations. This costs nothing at runtime: the blend happens once, where the patch is built.

What that gives up: the blend is only as sharp as the mesh. A material boundary inside a cell is drawn as a gradient across it, and a patch at a quarter detail blends four times as coarsely. A consumer who wants a sharp line — a road edge, a cliff base — draws that patch at full detail, which is the same dial the geometry already uses.

The weights are read bilinearly, which is the opposite of how a height is read, and both decisions are written down because the contrast is the point. A height is read as the triangle because the surface is drawn as triangles and the query has to agree with the picture. A weight is not drawn at all: it decides a vertex colour, and the vertex is wherever the mesh put it. Reading a weight map as triangles would put a visible crease along every cell diagonal for nothing.

Weights are normalised and a sample that weighs nothing falls back to the first material, because an unpainted corner of a map should not be a black patch.

It collides, through the mesh the picture is made of

import { meshShape, BODY_STATIC } from '@driftengine/physics';

const patch = heightfieldPatch(terrain, { x: 0, z: 0, cells: 32 });
world.addBody({ type: BODY_STATIC, shape: meshShape(patch.positions, patch.indices) });

There is no terrain-shaped collider and none is needed for terrain to collide: meshShape takes exactly the two arrays a patch hands back, so the geometry you draw is the geometry you collide with, with nothing authored twice. terrainCollision.test.ts asserts the three surfaces are one: a ray cast against that body lands where heightAt says the ground is, at forty points off the lattice.

That test is also what found a real defect in the first version of this package — the mesh was split along one diagonal while the query read the other, which every per-vertex assertion passed because a vertex lies on both.

A patch drawn coarse collides coarse, which is the right answer: colliding against the field while drawing a chord would let a character walk through ground they can see.

From a script

drift/terrain is bound: heightAt, normalX/normalY/normalZ, slopeAt, covers, extentX and extentZ, all deterministic reads, so a @deterministic system may ask where the ground is. The field reaches a script through uses, the way a NavGraph does.

Or as a heightfield, carrying no triangles at all

import { heightfieldShape, BODY_STATIC } from '@driftengine/physics';

world.addBody({ type: BODY_STATIC, shape: heightfieldShape(terrain) });

A Terrain is a Heightfield — same names, same meanings — so this type-checks with no import in either direction. That is the boundary doing its work rather than being worked around: @driftengine/physics imports no other engine package, and a shape is not a dependency.

The collider indexes the cell under a body straight from its x and z and holds the heights it was handed: no vertex buffer, no index buffer, no tree. At a 129-square field a mesh collider's positions and indices alone are more than five times the bytes of the heights, before the tree over them is counted.

It is not a second narrow phase. meshContact.ts still owns every contact rule — many manifolds rather than one, one-sided triangles, the interior-edge filter that stops a box stumbling on a flat seam — and a field only changes where triangles come from. The proof is differential: both colliders over one field, a box walked across it, and identical normals, counts and separations at every pose.

Which to use. The mesh path when the collider must be the patch you drew, chords and all — a coarse patch collides coarse, and a character then walks on what they can see. The heightfield path when the field is the truth and the patches are only how it is drawn, which is the usual case for a large world, and the only one of the two whose cost does not grow with the triangles.