Coming from three-mesh-bvh
This compares @pmndrs/bvh with three-mesh-bvh 0.9.18. Of the 52 features audited, 45 have an equivalent here, 2 have
part of one and 5 won't be done. The full audit, with the code behind each row, is
docs/internals/PARITY.md.
CPU: MeshBVH and friends
Most code switches by changing the import:
Without three, the same queries are on the core's CpuMeshBvh with plain points (CPU queries).
GPU: three-mesh-bvh/webgpu
Both libraries in one TypeScript program
Both packages add the same members to three's types, typed with their own classes: BufferGeometry.boundsTree,
computeBoundsTree and disposeBoundsTree, and BatchedMesh.boundsTrees, computeBoundsTree and
disposeBoundsTree. TypeScript can't merge two different types for one member, so it keeps the declaration it reads
first and the other doesn't apply. A program that imports both (while migrating, or to compare them) can then refuse
BatchedMesh.prototype.computeBoundsTree = computeBatchedBoundsTree, or type batchedMesh.boundsTrees as
three-mesh-bvh's trees.
Our declarations can't be made to match three-mesh-bvh's: its types name its own classes (GeometryBVH), whose
traverse and refit take other arguments than ours (our traverse is the shapecast box walk; our refit takes the
new positions). The way around it:
- Keep three-mesh-bvh's types out of the program that uses ours. Importing anything from
three-mesh-bvh, types included, brings its declarations in. If you need both, put the code that uses three-mesh-bvh in its owntsconfigproject, as this repository does for its comparison suite. - Or cast where the two meet: assign through
BatchedMesh.prototype as unknown as { computeBoundsTree: typeof computeBatchedBoundsTree }, and readbatchedMesh.boundsTrees as (BvhBoundsTree | null)[] | nullandgeometry.boundsTree as BvhBoundsTree | undefined. - Either way, set
skipLibCheck: the second declaration reports TS2717 in the other package's.d.ts.
At run time nothing clashes: whichever functions you assign to the prototypes are the ones that run.
Partly there
maxDepth: accepted, not honoured.setBoundingBox,verbose,onProgress:onProgressreports 0 and 1 only: tinybvh builds in one WebAssembly call and has no progress callback (its task hooks run only in threaded builds; ours is single-threaded).verboseis ignored.
There, with differences:
serialize: three-mesh-bvh's format is read and written. Reading keeps the tree's shape and recomputes exact boxes; a tree built over a draw range or groups that leave triangles out can't be read.getShapecastFn: the triangle test gets one world-space triangle at a time, not a leaf's index range, and there is no result pointer: the shape goes in by value, and results come back through awgslFn's private state.StaticGeometryGenerator: it also bakes a mesh again when only a morph weight changed (three-mesh-bvh keeps the old pose then).- Points and lines: CPU only (
SceneTreesand the GPU walk take triangles), as three-mesh-bvh's are. Where three-mesh-bvh departs from three's raycast (no bounding-sphere test, the draw range and groups fixed at build time, the index reordered outsideindirectmode), we follow three (Points and lines).
Missing
Nothing on the CPU. On the GPU nothing is missing either. SDF generation exists
(Signed distance fields, generateSdfTexture in /three), and so do closest point, sphere and box tests, shapecasts (with WGSL or TSL hooks) and the WGSL helpers
intersectRayTriangle and closestPointToTriangle (Spatial queries on the GPU); the queries
run over the whole scene in world space rather than over one mesh. ClusteredBVH's cut is clusterTraceScene
(Clustered top level), for scenes assembled without SceneTrees.
Won't be done
- GLSL and WebGL2 (
MeshBVHUniformStruct,BVHShaderGLSL, theVertexAttributeTextureclasses): WebGL2 isn't a target. ParallelMeshBVHWorker: it needsSharedArrayBufferand cross-origin isolation. A large mesh goes to the GPU H-PLOC builder instead.useSharedArrayBuffer: our trees are copied to the GPU, and the worker pool needs no shared memory.sampleTrianglePointandndcToCameraRay: the package ends at the hit. A hit gives the triangle and its barycentrics; interpolating attributes and making camera rays belong to the renderer above it.
What you get that three-mesh-bvh doesn't have
- GPU builds (H-PLOC and LBVH) and GPU refits (top level and per mesh), with nothing read back per frame.
- The same trees for three, for raw WebGPU passes, for workers and for other libraries on the device.
- The ray oracle on every builder and walk, and a watertight triangle test.
- Instance masks, a triangle filter and per-instance materials in the walk.
When to stay with three-mesh-bvh
- You need WebGL2 GPU traversal, or GLSL.
- Memory matters more than ray speed. Our GPU scenes use about 7 times three-mesh-bvh's bytes beyond the geometry on
Sponza (356 MiB against 51 MiB with defaults on both sides; 6.5 times on Bistro exterior). Indexed leaves, off by
default, bring Sponza to 226 MiB. Our CPU trees (
computeBoundsTree) keep 64-byte nodes: 8-9 times. See Memory. - You rely on years of other people's use: this package is new.