UNPKG

three-mesh-bvh

Version:

A BVH implementation to speed up raycasting against three.js meshes.

318 lines (239 loc) 9.37 kB
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; }