three-mesh-bvh
Version:
A BVH implementation to speed up raycasting against three.js meshes.
318 lines (239 loc) • 9.37 kB
JavaScript
import { Box3, Matrix4 } from 'three';
import { BufferStack } from '../utils/BufferStack.js';
import { BOUNDING_DATA_INDEX, COUNT, IS_LEAF, LEFT_NODE, OFFSET, RIGHT_NODE } from '../utils/nodeBufferUtils.js';
import { arrayToBox } from '../../utils/ArrayBoxUtilities.js';
import { PrimitivePool } from '../../utils/PrimitivePool.js';
import { BYTES_PER_NODE, UINT32_PER_NODE } from '../Constants.js';
const _bufferStack1 = /* @__PURE__ */ new BufferStack.constructor();
const _bufferStack2 = /* @__PURE__ */ new BufferStack.constructor();
const _boxPool = /* @__PURE__ */ new PrimitivePool( () => new Box3() );
const _leftBox1 = /* @__PURE__ */ new Box3();
const _rightBox1 = /* @__PURE__ */ new Box3();
const _leftBox2 = /* @__PURE__ */ new Box3();
const _rightBox2 = /* @__PURE__ */ new Box3();
let _active = false;
export function bvhcast( bvh, otherBvh, matrixToLocal, intersectsRanges ) {
if ( _active ) {
throw new Error( 'MeshBVH: Recursive calls to bvhcast not supported.' );
}
_active = true;
const roots = bvh._roots;
const otherRoots = otherBvh._roots;
let result;
let nodeOffset1 = 0;
let nodeOffset2 = 0;
const invMat = new Matrix4().copy( matrixToLocal ).invert();
// iterate over the first set of roots
for ( let i = 0, il = roots.length; i < il; i ++ ) {
_bufferStack1.setBuffer( roots[ i ] );
nodeOffset2 = 0;
// prep the initial root box
const localBox = _boxPool.getPrimitive();
arrayToBox( BOUNDING_DATA_INDEX( 0 ), _bufferStack1.float32Array, localBox );
localBox.applyMatrix4( invMat );
// iterate over the second set of roots
for ( let j = 0, jl = otherRoots.length; j < jl; j ++ ) {
_bufferStack2.setBuffer( otherRoots[ j ] );
result = _traverse(
0, 0, matrixToLocal, invMat, intersectsRanges,
nodeOffset1, nodeOffset2, 0, 0,
localBox,
);
_bufferStack2.clearBuffer();
nodeOffset2 += otherRoots[ j ].byteLength / BYTES_PER_NODE;
if ( result ) {
break;
}
}
// release stack info
_boxPool.releasePrimitive( localBox );
_bufferStack1.clearBuffer();
nodeOffset1 += roots[ i ].byteLength / BYTES_PER_NODE;
if ( result ) {
break;
}
}
_active = false;
return result;
}
function _traverse(
node1Index32,
node2Index32,
matrix2to1,
matrix1to2,
intersectsRangesFunc,
// offsets for ids
node1IndexOffset = 0,
node2IndexOffset = 0,
// tree depth
depth1 = 0,
depth2 = 0,
currBox = null,
reversed = false,
) {
// get the buffer stacks associated with the current indices
let bufferStack1, bufferStack2;
if ( reversed ) {
bufferStack1 = _bufferStack2;
bufferStack2 = _bufferStack1;
} else {
bufferStack1 = _bufferStack1;
bufferStack2 = _bufferStack2;
}
// get the local instances of the typed buffers
const
float32Array1 = bufferStack1.float32Array,
uint32Array1 = bufferStack1.uint32Array,
uint16Array1 = bufferStack1.uint16Array,
float32Array2 = bufferStack2.float32Array,
uint32Array2 = bufferStack2.uint32Array,
uint16Array2 = bufferStack2.uint16Array;
const node1Index16 = node1Index32 * 2;
const node2Index16 = node2Index32 * 2;
const isLeaf1 = IS_LEAF( node1Index16, uint16Array1 );
const isLeaf2 = IS_LEAF( node2Index16, uint16Array2 );
let result = false;
if ( isLeaf2 && isLeaf1 ) {
// if both bounds are leaf nodes then fire the callback if the boxes intersect
// Note the "nodeIndex" values are just intended to be used as unique identifiers in the tree and
// not used for accessing data
if ( reversed ) {
result = intersectsRangesFunc(
OFFSET( node2Index32, uint32Array2 ), COUNT( node2Index32 * 2, uint16Array2 ),
OFFSET( node1Index32, uint32Array1 ), COUNT( node1Index32 * 2, uint16Array1 ),
depth2, node2IndexOffset + node2Index32 / UINT32_PER_NODE,
depth1, node1IndexOffset + node1Index32 / UINT32_PER_NODE,
);
} else {
result = intersectsRangesFunc(
OFFSET( node1Index32, uint32Array1 ), COUNT( node1Index32 * 2, uint16Array1 ),
OFFSET( node2Index32, uint32Array2 ), COUNT( node2Index32 * 2, uint16Array2 ),
depth1, node1IndexOffset + node1Index32 / UINT32_PER_NODE,
depth2, node2IndexOffset + node2Index32 / UINT32_PER_NODE,
);
}
} else if ( isLeaf2 ) {
// SWAP
// If we've traversed to the leaf node on the other bvh then we need to swap over
// to traverse down the first one
// get the new box to use
const newBox = _boxPool.getPrimitive();
arrayToBox( BOUNDING_DATA_INDEX( node2Index32 ), float32Array2, newBox );
newBox.applyMatrix4( matrix2to1 );
// get the child bounds to check before traversal
const cl1 = LEFT_NODE( node1Index32 );
const cr1 = RIGHT_NODE( node1Index32, uint32Array1 );
arrayToBox( BOUNDING_DATA_INDEX( cl1 ), float32Array1, _leftBox1 );
arrayToBox( BOUNDING_DATA_INDEX( cr1 ), float32Array1, _rightBox1 );
// precompute the intersections otherwise the global boxes will be modified during traversal
const intersectCl1 = newBox.intersectsBox( _leftBox1 );
const intersectCr1 = newBox.intersectsBox( _rightBox1 );
result = (
intersectCl1 && _traverse(
node2Index32, cl1, matrix1to2, matrix2to1, intersectsRangesFunc,
node2IndexOffset, node1IndexOffset, depth2, depth1 + 1,
newBox, ! reversed,
)
) || (
intersectCr1 && _traverse(
node2Index32, cr1, matrix1to2, matrix2to1, intersectsRangesFunc,
node2IndexOffset, node1IndexOffset, depth2, depth1 + 1,
newBox, ! reversed,
)
);
_boxPool.releasePrimitive( newBox );
} else {
// if neither are leaves then we should swap if one of the children does not
// intersect with the current bounds
// get the child bounds to check
const cl2 = LEFT_NODE( node2Index32 );
const cr2 = RIGHT_NODE( node2Index32, uint32Array2 );
arrayToBox( BOUNDING_DATA_INDEX( cl2 ), float32Array2, _leftBox2 );
arrayToBox( BOUNDING_DATA_INDEX( cr2 ), float32Array2, _rightBox2 );
const leftIntersects = currBox.intersectsBox( _leftBox2 );
const rightIntersects = currBox.intersectsBox( _rightBox2 );
if ( leftIntersects && rightIntersects ) {
// continue to traverse both children if they both intersect
result = _traverse(
node1Index32, cl2, matrix2to1, matrix1to2, intersectsRangesFunc,
node1IndexOffset, node2IndexOffset, depth1, depth2 + 1,
currBox, reversed,
) || _traverse(
node1Index32, cr2, matrix2to1, matrix1to2, intersectsRangesFunc,
node1IndexOffset, node2IndexOffset, depth1, depth2 + 1,
currBox, reversed,
);
} else if ( leftIntersects ) {
if ( isLeaf1 ) {
// if the current box is a leaf then just continue
result = _traverse(
node1Index32, cl2, matrix2to1, matrix1to2, intersectsRangesFunc,
node1IndexOffset, node2IndexOffset, depth1, depth2 + 1,
currBox, reversed,
);
} else {
// SWAP
// if only one box intersects then we have to swap to the other bvh to continue
const newBox = _boxPool.getPrimitive();
newBox.copy( _leftBox2 ).applyMatrix4( matrix2to1 );
const cl1 = LEFT_NODE( node1Index32 );
const cr1 = RIGHT_NODE( node1Index32, uint32Array1 );
arrayToBox( BOUNDING_DATA_INDEX( cl1 ), float32Array1, _leftBox1 );
arrayToBox( BOUNDING_DATA_INDEX( cr1 ), float32Array1, _rightBox1 );
// precompute the intersections otherwise the global boxes will be modified during traversal
const intersectCl1 = newBox.intersectsBox( _leftBox1 );
const intersectCr1 = newBox.intersectsBox( _rightBox1 );
result = (
intersectCl1 && _traverse(
cl2, cl1, matrix1to2, matrix2to1, intersectsRangesFunc,
node2IndexOffset, node1IndexOffset, depth2, depth1 + 1,
newBox, ! reversed,
)
) || (
intersectCr1 && _traverse(
cl2, cr1, matrix1to2, matrix2to1, intersectsRangesFunc,
node2IndexOffset, node1IndexOffset, depth2, depth1 + 1,
newBox, ! reversed,
)
);
_boxPool.releasePrimitive( newBox );
}
} else if ( rightIntersects ) {
if ( isLeaf1 ) {
// if the current box is a leaf then just continue
result = _traverse(
node1Index32, cr2, matrix2to1, matrix1to2, intersectsRangesFunc,
node1IndexOffset, node2IndexOffset, depth1, depth2 + 1,
currBox, reversed,
);
} else {
// SWAP
// if only one box intersects then we have to swap to the other bvh to continue
const newBox = _boxPool.getPrimitive();
newBox.copy( _rightBox2 ).applyMatrix4( matrix2to1 );
const cl1 = LEFT_NODE( node1Index32 );
const cr1 = RIGHT_NODE( node1Index32, uint32Array1 );
arrayToBox( BOUNDING_DATA_INDEX( cl1 ), float32Array1, _leftBox1 );
arrayToBox( BOUNDING_DATA_INDEX( cr1 ), float32Array1, _rightBox1 );
// precompute the intersections otherwise the global boxes will be modified during traversal
const intersectCl1 = newBox.intersectsBox( _leftBox1 );
const intersectCr1 = newBox.intersectsBox( _rightBox1 );
result = (
intersectCl1 && _traverse(
cr2, cl1, matrix1to2, matrix2to1, intersectsRangesFunc,
node2IndexOffset, node1IndexOffset, depth2, depth1 + 1,
newBox, ! reversed,
)
) || (
intersectCr1 && _traverse(
cr2, cr1, matrix1to2, matrix2to1, intersectsRangesFunc,
node2IndexOffset, node1IndexOffset, depth2, depth1 + 1,
newBox, ! reversed,
)
);
_boxPool.releasePrimitive( newBox );
}
}
}
return result;
}