Closest point

The nearest point on a mesh to a point, or to another mesh, and what lies at a hit.
const closest = tree.closestPointToPoint({ x: 0, y: 2, z: 0 }); // { point, distance, faceIndex } or null
// minThreshold: stop below this distance; maxThreshold: look no farther.
const near = tree.closestPointToPoint({ x: 0, y: 2, z: 0 }, {}, 0, 1);

const onTree = {};
const onOther = {};
const between = tree.closestPointToGeometry(other, otherToTree, onTree, onOther); // onTree is between too
  • closestPointToPoint(point, target, minThreshold, maxThreshold) is three-mesh-bvh's shapecast step for step: the nearer box first, boxes culled by the best distance so far and by maxThreshold, an early stop below minThreshold. It fills target with point, distance and faceIndex (the original triangle), and returns it, or null with target untouched. As in three-mesh-bvh, maxThreshold culls boxes, not triangles. One departure: on a sliver (the squared sine of its angle at a at most 1e-7) the nearer of three's point and the nearest point of its three edges is kept, as on the GPU, because three's Triangle.closestPointToPoint cancels there.
  • closestPointToGeometry(other, geometryToBvh, target1, target2, minThreshold, maxThreshold) runs the same casts between two meshes. target1 is on this tree, target2 (null skips it) on the other, in its own space. Both faceIndexes are original triangles. A scaling geometryToBvh skews the culling, as in three-mesh-bvh.

Against three-mesh-bvh 0.9.18 (bench/closest-point.ts --rounds 10, Node 22 on the Apple M5, the 269,764-triangle room, 2,000 points per row, 200 placements; 2026-11-13, after the CPU query pass, under load; indicative):

QueryAgreementOursthree-mesh-bvh
Random points in the room's box2,000 of 2,000 the same distance (241 on another, equally near triangle)1.6 µs4.9 µs
Points just off random surfaces2,000 of 2,000 (245 tied)2.4 µs6.6 µs
closestPointToGeometry, a 180-triangle sphere with its own tree200 of 200 the same distance28.4 ms27.7 ms

What lies at a hit

getTriangleHitPointInfo(point, triangle, { indices, attributes }) gives the barycentric weights of the three corners at a point, the corners, the face normal, and any per-vertex attributes interpolated there:

const hit = tree.raycastFirst({ origin: [0, 0, 5], direction: [0, 0, -1] });
if (hit) {
    const info = tree.getTriangleHitPointInfo(hit.point, hit.triangle, {
        attributes: { normal: { array: normals, itemSize: 3 }, uv: { array: uvs, itemSize: 2 } },
    });
    console.log(info.barycoord, info.faceNormal, info.attributes.uv);
}

The tree's triangles hold positions only. Normals, UVs and colours come from your own arrays.

See also