Fast and Robust Mesh Collision Detection in JavaScript
Test whether two triangle meshes collide in JavaScript and TypeScript, measure their clearance, find the closest pair, and move meshes without rebuilding anything. WebAssembly, runs in Node.js and the browser.
Collision detection asks three questions about two meshes. Do they touch? If not, how far apart are they? Where is the contact? trueform answers all three on triangle meshes, with each mesh in its own pose. One npm install. It runs as WebAssembly in Node.js and the browser, with TypeScript types. This tutorial covers the test, the clearance, the closest pair, the contact curves, and moving a mesh without rebuilding anything.

Install
npm install @polydera/trueform
The WebAssembly binary loads on first use. In the browser, multithreading needs SharedArrayBuffer headers. See the installation guide.
Loading the meshes
import * as tf from "@polydera/trueform";
import { readFileSync } from "node:fs";
const fixture = tf.readStl(readFileSync("fixture.stl"));
const part = tf.readObj(readFileSync("part.obj"));
In the browser, pass the bytes from fetch instead. To follow along without files, use a subdivided box as the fixed mesh and a small sphere as the moving one:
const fixed = tf.boxMesh(2.0, 2.0, 2.0, 10, 10, 10);
const moving = tf.sphereMesh(0.5, 30, 30);
The box spans −1 to 1 on each axis. The sphere has radius 0.5.
Moving a mesh
Each Mesh can carry a 4×4 transformation. Every query applies it on the fly. The points are not rewritten and the spatial tree stays valid.
moving.transformation = tf.makeTranslation(3.0, 0, 0);
moving.transformation = tf.makeRotation(45, "z");

Matrices are row-major. A Three.js Matrix4 is column-major, so transpose it:
moving.transformation = tf.ndarray(object.matrixWorld.elements, [4, 4]).T;
Do the meshes collide?
tf.intersects returns a boolean:
moving.transformation = tf.makeTranslation(3.0, 0, 0);
tf.intersects(fixed, moving); // false
moving.transformation = tf.makeTranslation(1.3, 0, 0);
tf.intersects(fixed, moving); // true

Touching counts. With the sphere at x = 1.5, its surface just meets the box face at x = 1, and the answer is true.
The test is on surfaces. A sphere entirely inside the box does not cross any box face, so tf.intersects returns false, and tf.distance returns the gap between the two surfaces, 0.5 here.
How far apart are they?
For clearance, use tf.distance. It returns 0 when the meshes touch.
moving.transformation = tf.makeTranslation(1.55, 0, 0);
tf.distance(fixed, moving); // 0.05
tf.distance2(fixed, moving); // 0.0025, squared

To get the closest pair itself, call tf.neighborSearch with two meshes:
const { elementId0, elementId1, point0, point1, distance2 } =
tf.neighborSearch(fixed, moving);
// elementId0, elementId1: closest face on each mesh
// point0, point1: closest points, in world coordinates
// distance2: squared distance between them
The same calls take primitives such as points and segments. A common use is a capsule, a segment with a radius, as a stand-in for a tool or a robot link:
const axis = tf.segment(tf.point(1.3, -0.5, 0), tf.point(1.3, 0.5, 0));
const radius = 0.2;
const clearance = tf.distance(fixed, axis) - radius; // 0.1
const { elementId, point } = tf.neighborSearch(fixed, axis); // closest box face and point
Where is the contact?
When the meshes overlap, the contact is the curve where their surfaces cross. Compute it with intersection curves:
moving.transformation = tf.makeTranslation(1.3, 0, 0);
const contact = tf.intersectionCurves(fixed, moving);
contact.length; // 1, a closed loop on the box face
The curve is exact. Use it to draw the contact, to measure it, or to decide whether a boolean is needed.
Working with results
In a motion loop, build the trees once and only change the transformation:
fixed.buildTree();
moving.buildTree();
for (let x = 3.0; x >= 0; x -= 0.5) {
moving.transformation = tf.makeTranslation(x, 0, 0);
const hit = tf.intersects(fixed, moving);
const gap = tf.distance(fixed, moving);
}
To test many copies of one part in different poses, use shallowCopy(). Each copy shares the points and faces, and carries its own transformation:
const copies = [1.2, 2.2, 3.2].map((x) => {
const view = moving.shallowCopy();
view.transformation = tf.makeTranslation(x, 0, 0);
return view;
});
copies.map((view) => tf.intersects(fixed, view)); // [true, false, false]
In the browser, the async variants run off the main thread and keep the frame loop free:
const hit = await tf.async.intersects(fixed, moving);
const gap = await tf.async.distance(fixed, moving);
Related tutorials
- Fast and Robust Closest Point on a Mesh in JavaScript — nearest surface point for a batch of queries.
- Fast and Exact Mesh Intersection Curves in JavaScript — exact curves where two or more meshes cross.
- Fast Mesh Booleans in JavaScript — exact union, intersection and difference of two meshes.
- Fast STL and OBJ Reading in JavaScript — load and write meshes, the first step of every pipeline.
Performance
We measured mesh–mesh queries between the dragon and a transformed copy of it, at five sizes, against three-mesh-bvh's closestPointToGeometry and intersectsGeometry. The closest pair is taken across a gap; the intersection test on a pose where the surfaces cross.
Apple M4 Max 16 threads Node.js 25 · WebAssembly
Both trees are built before the timer, except in the last row. Both libraries return the same answer on every input. Median of 9 runs after 2 warm-ups, each library in its own Node.js process. three-mesh-bvh runs on one JavaScript thread.
Measured 2026-10-31 with trueform 0.10.8; harness and raw results: benchmarks/tutorials/javascript-collision-detection.
Every collision query runs on a spatial tree. On a 1,025,766-polygon mesh, trueform builds it in 15.8 ms in Node.js, against 259.9 ms for three-mesh-bvh. The tree is built once. Moving a mesh by its transformation does not rebuild it.

Full benchmarks · Documentation
@article{polydera:fast-mesh-collision-detection-in-javascript,
title={Fast and Robust Mesh Collision Detection in JavaScript},
author={Sajovic, {\v{Z}}iga, Polydera},
year={2026},
url={https://polydera.com/tutorials/fast-mesh-collision-detection-in-javascript},
organization={Polydera}
}