three-mesh-bvh
Version:
A BVH implementation to speed up raycasting against three.js meshes.
331 lines (215 loc) • 6.12 kB
JavaScript
import { LineBasicMaterial, BufferAttribute, Box3, Group, MeshBasicMaterial, Object3D, BufferGeometry } from 'three';
import { arrayToBox } from '../utils/ArrayBoxUtilities.js';
const boundingBox = /* @__PURE__ */ new Box3();
class MeshBVHRootVisualizer extends Object3D {
get isMesh() {
return ! this.displayEdges;
}
get isLineSegments() {
return this.displayEdges;
}
get isLine() {
return this.displayEdges;
}
constructor( mesh, material, depth = 10, group = 0 ) {
super();
this.material = material;
this.geometry = new BufferGeometry();
this.name = 'MeshBVHRootVisualizer';
this.depth = depth;
this.displayParents = false;
this.mesh = mesh;
this.displayEdges = true;
this._group = group;
}
raycast() {}
update() {
const geometry = this.geometry;
const boundsTree = this.mesh.geometry.boundsTree;
const group = this._group;
geometry.dispose();
this.visible = false;
if ( boundsTree ) {
// count the number of bounds required
const targetDepth = this.depth - 1;
const displayParents = this.displayParents;
let boundsCount = 0;
boundsTree.traverse( ( depth, isLeaf ) => {
if ( depth === targetDepth || isLeaf ) {
boundsCount ++;
return true;
} else if ( displayParents ) {
boundsCount ++;
}
}, group );
// fill in the position buffer with the bounds corners
let posIndex = 0;
const positionArray = new Float32Array( 8 * 3 * boundsCount );
boundsTree.traverse( ( depth, isLeaf, boundingData ) => {
const terminate = depth === targetDepth || isLeaf;
if ( terminate || displayParents ) {
arrayToBox( 0, boundingData, boundingBox );
const { min, max } = boundingBox;
for ( let x = - 1; x <= 1; x += 2 ) {
const xVal = x < 0 ? min.x : max.x;
for ( let y = - 1; y <= 1; y += 2 ) {
const yVal = y < 0 ? min.y : max.y;
for ( let z = - 1; z <= 1; z += 2 ) {
const zVal = z < 0 ? min.z : max.z;
positionArray[ posIndex + 0 ] = xVal;
positionArray[ posIndex + 1 ] = yVal;
positionArray[ posIndex + 2 ] = zVal;
posIndex += 3;
}
}
}
return terminate;
}
}, group );
let indexArray;
let indices;
if ( this.displayEdges ) {
// fill in the index buffer to point to the corner points
indices = new Uint8Array( [
// x axis
0, 4,
1, 5,
2, 6,
3, 7,
// y axis
0, 2,
1, 3,
4, 6,
5, 7,
// z axis
0, 1,
2, 3,
4, 5,
6, 7,
] );
} else {
indices = new Uint8Array( [
// X-, X+
0, 1, 2,
2, 1, 3,
4, 6, 5,
6, 7, 5,
// Y-, Y+
1, 4, 5,
0, 4, 1,
2, 3, 6,
3, 7, 6,
// Z-, Z+
0, 2, 4,
2, 6, 4,
1, 5, 3,
3, 5, 7,
] );
}
if ( positionArray.length > 65535 ) {
indexArray = new Uint32Array( indices.length * boundsCount );
} else {
indexArray = new Uint16Array( indices.length * boundsCount );
}
const indexLength = indices.length;
for ( let i = 0; i < boundsCount; i ++ ) {
const posOffset = i * 8;
const indexOffset = i * indexLength;
for ( let j = 0; j < indexLength; j ++ ) {
indexArray[ indexOffset + j ] = posOffset + indices[ j ];
}
}
// update the geometry
geometry.setIndex(
new BufferAttribute( indexArray, 1, false ),
);
geometry.setAttribute(
'position',
new BufferAttribute( positionArray, 3, false ),
);
this.visible = true;
}
}
}
class MeshBVHVisualizer extends Group {
get color() {
return this.edgeMaterial.color;
}
get opacity() {
return this.edgeMaterial.opacity;
}
set opacity( v ) {
this.edgeMaterial.opacity = v;
this.meshMaterial.opacity = v;
}
constructor( mesh, depth = 10 ) {
super();
this.name = 'MeshBVHVisualizer';
this.depth = depth;
this.mesh = mesh;
this.displayParents = false;
this.displayEdges = true;
this._roots = [];
const edgeMaterial = new LineBasicMaterial( {
color: 0x00FF88,
transparent: true,
opacity: 0.3,
depthWrite: false,
} );
const meshMaterial = new MeshBasicMaterial( {
color: 0x00FF88,
transparent: true,
opacity: 0.3,
depthWrite: false,
} );
meshMaterial.color = edgeMaterial.color;
this.edgeMaterial = edgeMaterial;
this.meshMaterial = meshMaterial;
this.update();
}
update() {
const bvh = this.mesh.geometry.boundsTree;
const totalRoots = bvh ? bvh._roots.length : 0;
while ( this._roots.length > totalRoots ) {
const root = this._roots.pop();
root.geometry.dispose();
this.remove( root );
}
for ( let i = 0; i < totalRoots; i ++ ) {
if ( i >= this._roots.length ) {
const root = new MeshBVHRootVisualizer( this.mesh, this.edgeMaterial, this.depth, i );
this.add( root );
this._roots.push( root );
}
const root = this._roots[ i ];
root.depth = this.depth;
root.mesh = this.mesh;
root.displayParents = this.displayParents;
root.displayEdges = this.displayEdges;
root.material = this.displayEdges ? this.edgeMaterial : this.meshMaterial;
root.update();
}
}
updateMatrixWorld( ...args ) {
this.position.copy( this.mesh.position );
this.rotation.copy( this.mesh.rotation );
this.scale.copy( this.mesh.scale );
super.updateMatrixWorld( ...args );
}
copy( source ) {
this.depth = source.depth;
this.mesh = source.mesh;
}
clone() {
return new MeshBVHVisualizer( this.mesh, this.depth );
}
dispose() {
this.edgeMaterial.dispose();
this.meshMaterial.dispose();
const children = this.children;
for ( let i = 0, l = children.length; i < l; i ++ ) {
children[ i ].geometry.dispose();
}
}
}
export { MeshBVHVisualizer };