Refit, scenes and helpers
CPU refits, a whole scene on the CPU, frustum culling, and three-mesh-bvh's debug functions.
Refit
input.positions[0] += 0.1; // the vertices moved, the triangles are the same
tree.refit(); // rereads the source positions, keeps the shape; or tree.refit(input)
refit(input?) rewrites the triangle records from the source positions and refits every box (refitOnCpu), keeping
the tree's shape. The index must not change. Every input is checked before anything is written, so a refit that throws
leaves the tree as it was.
Queries read the tree's own triangles, not the geometry's current positions: after editing positions, refit first.
A whole scene
CpuSceneBvh is three-mesh-bvh's ObjectBVH without three: a top level over instances and the mesh trees under it, one
walk for the whole scene.
import { buildMeshBvhWithTinyBvh, CpuSceneBvh, createTinyBvhBuilder, type MeshGeometry, type TraceSceneInstance } from '@pmndrs/bvh';
const tiny = await createTinyBvhBuilder();
const picker = CpuSceneBvh.build(meshes, instances, (mesh) => buildMeshBvhWithTinyBvh(mesh, tiny));
const hits = picker.raycast({ origin: [0, 1, 5], direction: [0, 0, -1], tMin: 0, tMax: 1e30 }, { firstHitOnly: true, mask: 0x1 });
// Frustum culling over the top level: planes as [a, b, c, d] with a*x + b*y + c*z + d >= 0 inside.
const visible = picker.intersectsFrustum([0, 0, -1, 10, 0, 0, 1, 10]);
picker.setInstances(instances); // moved instances: rebuilds the top level only
raycast(ray, { firstHitOnly, mask, accept })returns{ instance, triangle, t, u, v, frontFace, ... }per hit, sorted.accept(instance, triangle, frontFace)decides whether a hit counts (side culling, groups).intersectsFrustum(planes, { mask })andintersectsBox(box, { mask })walk the top level and return the instances whose world box meets the shape. A subtree outside a plane is skipped; one inside every plane is taken whole.setInstances(instances)takes new transforms, visibility, masks or materials, and rebuilds only the top level.
SceneTrees keeps its trees on the GPU only, so a CPU scene is built from the same meshes separately. In three,
BvhSceneRaycaster does it from the scene graph (CPU raycast in three).
Debug helpers
import { bvhHelperIndices, bvhHelperPositions, estimateMemoryInBytes, getBVHExtremes, validateBounds } from '@pmndrs/bvh';
console.log(getBVHExtremes(tree)); // [{ nodeCount, leafNodeCount, depth, primitives, splits, surfaceAreaScore }]
console.log(validateBounds(tree)); // every box holds its triangles (reads the source geometry)
console.log(estimateMemoryInBytes(tree)); // the tree's own bytes, not the geometry's
// The boxes down to depth 10 as line segments, for any engine to draw.
const positions = bvhHelperPositions(tree, 10, false);
const indices = bvhHelperIndices(positions, true);
getBVHExtremes(tree): three-mesh-bvh'sExtremeInfo, one entry for our one root. The split axis isn't stored in the layout, sosplitscounts the axis the children's centres lie furthest apart on.validateBounds(tree): three-mesh-bvh's checks andconsole.assertmessages, reading the source geometry, so edits withoutrefit()fail.estimateMemoryInBytes(tree): the tree's layout, its lookups and top level, not the geometry.bvhHelperPositions(tree, depth, displayParents)andbvhHelperIndices(positions, edges): the boxes as line segments (or faces), for any engine to draw. three'sMeshBVHHelperuses them.checkGpuLayoutandtreeSahCostcheck and score any tree (The builder contract).getJSONStructure(tree): three-mesh-bvh's plain-object tree, for looking at or logging a tree's shape. Each node hasboundsand eitheroffsetandcount(a leaf; tree positions, astraversereports them) orleftandright. The root's box is the tree's bounds. Boxes areBvhBox3s here and threeBox3s from@pmndrs/bvh/three; passmakeBoxfor another type. A tree written in three-mesh-bvh's format gives the same structure from itsgetJSONStructure, node for node.
import { getJSONStructure, type CpuMeshBvh } from '@pmndrs/bvh';
import { getJSONStructure as getJSONStructureThree } from '@pmndrs/bvh/three';
const root = getJSONStructure(tree); // { bounds: BvhBox3, left, right }, or { bounds, offset, count } at a leaf
console.log(JSON.stringify(root, null, 2));
// In three, the boxes are Box3s:
const threeRoot = getJSONStructureThree(geometry.boundsTree!);