Points and lines
CpuPointsBvh and CpuSegmentsBvh put points and line segments in a tree, as three-mesh-bvh's PointsBVH and line
BVHs do. Use them to pick points in a scan, to snap a cursor to the nearest wire of a drawing, or to find the segments
near a shape. They need no GPU and no engine. In three, PointsBVH and the line trees wrap
them.
import { CpuPointsBvh, CpuSegmentsBvh } from '@pmndrs/bvh';
const cloud = CpuPointsBvh.build({ positions, indices }, { maxLeafPrimitives: 8 }); // synchronous, no WebAssembly
const ray = { origin: [0, 0, 5], direction: [0, 0, -1] }; // a unit direction, as three's rays are
const hits = cloud.raycast(ray, { threshold: 0.01, near: 0, far: 100 }); // three's Points.raycast test
const first = cloud.raycastFirst(ray, { threshold: 0.01 }); // or null
if (first) console.log(first.index, first.distance, first.distanceToRay, first.pointOnRay, first.pointOnPrimitive);
const nearest = cloud.closestPointToPoint([0, 1, 0]); // { point, distance, index } or null
// Lines: 'segments' pairs entries (LineSegments), 'strip' joins each to the next (Line), 'loop' closes it (LineLoop).
const lines = CpuSegmentsBvh.build({ positions, indices, mode: 'strip', start: 0, count: 1000 });
const below: number[] = [];
lines.shapecast({
intersectsBounds: (box) => box.min.y < 0.5, // false skips the box
intersectsLine: (line, index) => {
if (line.start.y < 0.5 && line.end.y < 0.5) below.push(index); // the segment's first entry
return false; // true stops the cast
},
});
The input
Points and lines come as three draws them: positions (x, y, z per vertex, Float32Array), an optional indices,
and a range of entries (start, count, like a draw range; default all). An entry is a position in indices, or
a vertex without them. A point is one entry. A segment starts at an entry and ends at the next:
A hit's index is the point's entry, or the segment's first entry (three's index for lines; for points three
reports the entry's vertex, indices[index]). The positions are copied into the tree as f32, so moving them later
needs refit().
Queries
raycast(ray, { threshold, near, far, accept })returns every primitive withinthresholdof the ray, nearest first (then by entry). It is three's test, computed with three'sRayoperations in three's order: a point counts when strictly closer thanthreshold, a segment when no farther.distanceruns from the ray's origin topointOnRay;distanceToRayis the gap between the ray and the primitive. The default threshold is 1, three's. A direction that isn't unit length is normalized first.accept(hit)can drop hits (a draw range, a distance in another space).raycastFirst(ray, options)returns the closest hit (the lower entry on a tie), or null. Boxes beyond the closest hit so far are skipped, as in three-mesh-bvh'sfirstHitOnly.closestPointToPoint(point, target, minThreshold, maxThreshold)returns{ point, distance, index }(a point on a segment, clamped to its ends), or null when nothing is withinmaxThreshold. It stops at the first one withinminThreshold.shapecast({ intersectsBounds, intersectsPoint | intersectsLine, intersectsRange, boundsTraverseOrder })hasCpuMeshBvh.shapecast's contract, with each point or segment handed over as a scratch object, with its entry.traverse,traverseNodes,getBoundingBox,resolvePrimitiveIndex,getPoint/getSegment,refit(input),serialize()andCpuPointsBvh.deserialize(bytes)/CpuSegmentsBvh.deserialize(bytes)(checked: a corrupt file throws).getBVHExtremes,validateBoundsandbvhHelperPositionstake these trees too.
The tree and its limits
- The builder is a centre split (three-mesh-bvh's default
CENTER), in TypeScript: the longest axis of the primitives' centres is cut at its middle, with a median cut when that leaves a side empty or past depth 40. It runs on the calling thread; a million points take about a second, as long as three-mesh-bvh'sPointsBVH. tinybvh isn't used: it builds over triangles, and points given to it as flat triangles come out with one node per point. - Nodes are the GPU layout's 64-byte two-child nodes, leaves of up to 8 primitives. A point record is 16 bytes, a segment 32: about 27 bytes per point and 43 per segment with the nodes.
- CPU only. The GPU walk and
SceneTreestake triangles; there are no point or segment leaves on the GPU. They are not built because no consumer has asked, and the reasons and a design are indocs/internals/design/points-and-lines.md. - An empty input builds an empty tree, which every query answers with nothing.