three-mesh-bvh
Version:
A BVH implementation to speed up raycasting against three.js meshes.
147 lines (102 loc) • 4.19 kB
JavaScript
import { getBounds } from './computeBoundsUtils.js';
import { getOptimalSplit } from './splitUtils.js';
import { BVHNode } from '../BVHNode.js';
import { BYTES_PER_NODE } from '../Constants.js';
import { partition } from './sortUtils.js';
import { countNodes, populateBuffer } from './buildUtils.js';
export function buildTree( bvh, primitiveBounds, offset, count, options, loadRange ) {
// expand variables
const {
maxDepth,
verbose,
maxLeafSize,
strategy,
onProgress,
} = options;
const partitionBuffer = bvh.primitiveBuffer;
const partitionStride = bvh.primitiveBufferStride;
// generate intermediate variables
const cacheCentroidBoundingData = new Float32Array( 6 );
let reachedMaxDepth = false;
const root = new BVHNode();
getBounds( primitiveBounds, offset, count, root.boundingData, cacheCentroidBoundingData );
splitNode( root, offset, count, cacheCentroidBoundingData );
return root;
function triggerProgress( primitivesProcessed ) {
if ( onProgress ) {
onProgress( ( primitivesProcessed - loadRange.offset ) / loadRange.count );
}
}
// either recursively splits the given node, creating left and right subtrees for it, or makes it a leaf node,
// recording the offset and count of its primitives and writing them into the reordered geometry index.
function splitNode( node, offset, count, centroidBoundingData = null, depth = 0 ) {
if ( ! reachedMaxDepth && depth >= maxDepth ) {
reachedMaxDepth = true;
if ( verbose ) {
console.warn( `BVH: Max depth of ${ maxDepth } reached when generating BVH. Consider increasing maxDepth.` );
}
}
// early out if we've met our capacity
if ( count <= maxLeafSize || depth >= maxDepth ) {
triggerProgress( offset + count );
node.offset = offset;
node.count = count;
return node;
}
// Find where to split the volume
const split = getOptimalSplit( node.boundingData, centroidBoundingData, primitiveBounds, offset, count, strategy );
if ( split.axis === - 1 ) {
triggerProgress( offset + count );
node.offset = offset;
node.count = count;
return node;
}
const splitOffset = partition( partitionBuffer, partitionStride, primitiveBounds, offset, count, split );
// create the two new child nodes
if ( splitOffset === offset || splitOffset === offset + count ) {
triggerProgress( offset + count );
node.offset = offset;
node.count = count;
} else {
node.splitAxis = split.axis;
// create the left child and compute its bounding box
const left = new BVHNode();
const lstart = offset;
const lcount = splitOffset - offset;
node.left = left;
getBounds( primitiveBounds, lstart, lcount, left.boundingData, cacheCentroidBoundingData );
splitNode( left, lstart, lcount, cacheCentroidBoundingData, depth + 1 );
// repeat for right
const right = new BVHNode();
const rstart = splitOffset;
const rcount = count - lcount;
node.right = right;
getBounds( primitiveBounds, rstart, rcount, right.boundingData, cacheCentroidBoundingData );
splitNode( right, rstart, rcount, cacheCentroidBoundingData, depth + 1 );
}
return node;
}
}
export function buildPackedTree( bvh, options ) {
const BufferConstructor = options.useSharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer;
// get the range of buffer data to construct / arrange
const rootRanges = bvh.getRootRanges( options.range );
const firstRange = rootRanges[ 0 ];
const lastRange = rootRanges[ rootRanges.length - 1 ];
const fullRange = {
offset: firstRange.offset,
count: lastRange.offset + lastRange.count - firstRange.offset,
};
// construct the primitive bounds for sorting
const primitiveBounds = new Float32Array( 6 * fullRange.count );
primitiveBounds.offset = fullRange.offset;
bvh.computePrimitiveBounds( fullRange.offset, fullRange.count, primitiveBounds );
// Build BVH roots
bvh._roots = rootRanges.map( range => {
const root = buildTree( bvh, primitiveBounds, range.offset, range.count, options, fullRange );
const nodeCount = countNodes( root );
const buffer = new BufferConstructor( BYTES_PER_NODE * nodeCount );
populateBuffer( 0, root, buffer );
return buffer;
} );
}