Scenes and the sync
SceneTreesObject3DSync reads a three scene into a SceneTrees and keeps it in step: only what changed is pushed.
import { SceneTrees } from '@pmndrs/bvh';
import { SceneTreesObject3DSync } from '@pmndrs/bvh/three';
import type { Mesh, Object3D } from 'three';
const trees = await SceneTrees.create(device); // static instances merge by default (mergeStaticInstances)
scene.updateMatrixWorld(); // the world matrices are read as they are
const sync = new SceneTreesObject3DSync(trees, scene, {
static: (object) => !object.userData.moves, // static instances can merge, and are never pushed again
material: (mesh) => materialIndexOf(mesh), // becomes hit.material (0-65534)
layers: true, // layers 0-7 become the instance mask (default)
});
// After adding or removing objects, or changing geometry, groups or materials:
sync.sync();
// Every frame: moved, shown, hidden or re-layered objects only.
sync.update();
sync()after objects are added or removed, or a geometry, draw range, group or material changes: only the meshes and instances that changed are added or removed. It walks and plans before it changes anything, so a throwingmaterialcallback leaves everything as it was.update()each frame: it skips an object whosematrixWorld, effective visibility, layers andinstanceMatrix.versionare unchanged, without allocating. SetinstanceMatrix.needsUpdate = trueaftersetMatrixAt, as three needs anyway.- Then
trees.update(encoder), submit, andbvh.update()if you use BvhTraceNodes.
What is read
Mesh,InstancedMesh(instances below itscount),BatchedMesh(active, visible instances) andSkinnedMesh(posed on the GPU with arenderer; Skinned and morphed meshes).- The triangles three's
Mesh.raycasttests: the draw range and, with an array of materials, the groups within it. Ranges are read as three steps through them, even off a multiple of 3 or overlapping. - Multi-material meshes (an array of materials, with groups) are folded: one mesh holding every group, in group
order, and one instance per object (and per instance of an
InstancedMesh). Each triangle carries its group's material (triangleMaterials, frommaterial(mesh, info), asked once per group withinfo.groupandinfo.materialIndex). The sync,bvhSceneFromObject3DandBvhSceneRaycasterfold the same way, so instance N is the same object in all three. A folded source'sgroupandmaterialIndexare -1 (the hit's triangle names its group: see "Hits back to objects" below). An object whose instances give a group different materials (anInstancedMeshwith a material per instance) is not folded: it keeps one mesh per group, shared by its instances, with each instance's material on the instance.multiMaterial: 'groups'keeps one mesh and instance per group for every object, as the sync and the scene raycaster did before. - Indexed, non-indexed and interleaved positions. Interleaved, normalized or non-float positions are compacted once per geometry.
- One mesh per geometry and range, shared by every object and instance that uses it.
- Visibility: an object counts as visible when it and every ancestor are. Hidden objects stay in as hidden instances.
- Layers: layers 0-7 become instance mask bits 0-7; layers 8-31 are dropped. An object on none of layers 0-7 gets
mask 0, which no ray hits. A default object (layer 0) gets
0x01.layers: falsegives every instance0xff. - Materials: the
material(mesh, info)callback is read atsync()only. After changing what it returns, callsync().
Call scene.updateMatrixWorld() first: world matrices are read as they are.
Hits back to objects
import { threeGroupOf, threeMaterialOf } from '@pmndrs/bvh/three';
const source = sync.sourceOf(hitInstance); // { object, instanceId, group, materialIndex, mesh, static }
const meshSource = sync.meshSourceOf(hitInstance);
if (source && meshSource) {
console.log(source.object.name, source.instanceId);
// A folded multi-material mesh has group -1: the hit's triangle names its group and three material.
const group = threeGroupOf(meshSource, hitTriangle); // { group, materialIndex, material, ... } or null
const material = threeMaterialOf(source, meshSource, hitTriangle); // object.material[group.materialIndex], or object.material
console.log(group?.materialIndex, material?.name);
}
hit.triangle is the triangle within its mesh (a folded mesh's groups one after another, a group, a draw range or a
batch range). threeFaceIndex(meshSource, triangle) turns it into three's faceIndex; sync.meshSourceOf(instance)
gives the mesh source (BvhSceneRaycaster.meshSourceOf, or scene.meshSources[source.mesh], elsewhere).
hit.material is what your material callback returned for the hit triangle's group (or the instance's answer, for
a mesh of one material). To get from a hit to three's object.material[i], either return info.materialIndex (or a
value you map back from it) in the callback, or call threeMaterialOf(source, meshSource, hit.triangle), which works
without a callback. threeGroupOf(meshSource, hit.triangle) gives the group itself.
sync.meshHandles lists the SceneTrees mesh handles the sync holds (one per mesh it added: a shared geometry range,
or a posed object's own), for trees.meshStatus(handle) and stats. It is a new array each time; sync() can add and
remove handles. Merged static groups are SceneTrees' own (trees.groupStatus).
// The SceneTrees mesh handles the sync holds now: one per shared geometry (or posed object).
let nodes = 0;
for (const handle of sync.meshHandles) nodes += trees.meshStatus(handle).placement?.nodeCount ?? 0;
Static objects
static: true, or (object, instanceId) => boolean, marks instances for mergeStaticInstances
(Merged static instances). The sync never pushes changes to static instances.
After SceneTrees merged an object's static instances, sync() skips removing that object or changing its geometry,
draw range, groups or materials, and warns once per object (skipped in the counts). Mark objects that may go away
static: false.
Without SceneTrees
bvhSceneFromObject3D(root, options) reads the scene once and returns meshes, instances and their three
sources. Build it with buildTraceScene(scene.meshes, scene.instances, { build }), upload it with
uploadTraceScene, and BvhTraceNodes takes the result as { buffers }.