← All projects

Trilat-Tool

Live

A 3D trainer for turning distance readings into a position. Built for our Star Citizen org, playable in your browser right now.

Launch the trainer

Why it exists

Star Citizen parks six orbital markers, OM 1 through OM 6, above every planet and moon: two over the poles, four around the equator. Your HUD tells you how far you are from each marker. It does not tell you where that puts you. Turning those readings into a position (trilateration) is how org members found unmarked crash sites, caves, and each other. Explaining the technique over voice chat went about as well as you'd expect, so we built the thing people could see.

Trilat-Tool at rest: a gray planet with six labeled orbital markers and a control panel for the camera and marker toggles
The trainer at rest: one planet, six orbital markers on their game-accurate axes, and a control panel Ashley made presentable.

What it is

Click anywhere on the planet to drop a marker: that's the position you're trying to pin down. Toggle an orbital marker on and the trainer draws the straight-line distance to it, plus the ring of every point that shares that exact reading. That ring is the whole lesson. One reading means you could be anywhere on it.

One orbital marker enabled: a distance line labeled 89.12 units and a ring of all positions with that reading
One reading in: 89.12 units from OM 1, and the ring of every position that produces that number.

Add a second reading and its ring crosses the first at just two points. A third settles it: the rings meet at exactly one spot, the one you clicked. Once someone watches the rings collapse onto their own marker, the radio version of the lesson finally sticks.

Three orbital markers enabled: three distance lines and three rings crossing at the single clicked point
Three readings in: three rings, one possible position.

The math is one right triangle

The whole app is client-side Three.js in a single Webpack bundle: no backend, no accounts, nothing to install. Clicks are raycast against the planet mesh to find the surface point. The rings look like they'd need sphere-intersection solving; they don't. For a marker in unit direction u and a clicked point p, the projection legA = p·u says how far along the marker's axis the point sits, and legB = √(|p|² - legA²) is its distance from that axis. Every position with the same reading lives on that circle by symmetry, so each ring is just a thin cylinder of radius legB, placed legA along the axis and rotated to face its marker. One dot product, one square root, done.

Rendering without a render loop

Idle costs nothing. There's no animation loop. The scene re-renders only when something changes: a click, a toggle, a camera nudge. A tab left open all day does no work at all.

Labels that know their place. A distance label behind the planet should hide; one in front should never sink into the surface. Each frame renders in three passes (background, default, foreground) with the depth buffer cleared between them, and before every frame a raycast against the planet sorts each label and line into the right pass.

Dave wrote the JavaScript; Ashley styled the interface. Built in a few weeks in late 2024, and it still does its one job. Go drop a marker.