UNPKG

three-mesh-bvh

Version:

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

180 lines (122 loc) 5.35 kB
import { ensureIndex, getFullGeometryRange, getRootIndexRanges, getTriCount, hasGroupGaps, } from './geometryUtils.js'; import { getBounds, computeTriangleBounds } from './computeBoundsUtils.js'; import { getOptimalSplit } from './splitUtils.js'; import { MeshBVHNode } from '../MeshBVHNode.js'; import { BYTES_PER_NODE } from '../Constants.js'; import { partition } from './sortUtils.generated.js'; import { partition_indirect } from './sortUtils_indirect.generated.js'; import { countNodes, populateBuffer } from './buildUtils.js'; export function generateIndirectBuffer( geometry, useSharedArrayBuffer ) { const triCount = ( geometry.index ? geometry.index.count : geometry.attributes.position.count ) / 3; const useUint32 = triCount > 2 ** 16; const byteCount = useUint32 ? 4 : 2; const buffer = useSharedArrayBuffer ? new SharedArrayBuffer( triCount * byteCount ) : new ArrayBuffer( triCount * byteCount ); const indirectBuffer = useUint32 ? new Uint32Array( buffer ) : new Uint16Array( buffer ); for ( let i = 0, l = indirectBuffer.length; i < l; i ++ ) { indirectBuffer[ i ] = i; } return indirectBuffer; } export function buildTree( bvh, triangleBounds, offset, count, options ) { // epxand variables const { maxDepth, verbose, maxLeafTris, strategy, onProgress, indirect, } = options; const indirectBuffer = bvh._indirectBuffer; const geometry = bvh.geometry; const indexArray = geometry.index ? geometry.index.array : null; const partionFunc = indirect ? partition_indirect : partition; // generate intermediate variables const totalTriangles = getTriCount( geometry ); const cacheCentroidBoundingData = new Float32Array( 6 ); let reachedMaxDepth = false; const root = new MeshBVHNode(); getBounds( triangleBounds, offset, count, root.boundingData, cacheCentroidBoundingData ); splitNode( root, offset, count, cacheCentroidBoundingData ); return root; function triggerProgress( trianglesProcessed ) { if ( onProgress ) { onProgress( trianglesProcessed / totalTriangles ); } } // 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 triangles 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( `MeshBVH: Max depth of ${ maxDepth } reached when generating BVH. Consider increasing maxDepth.` ); console.warn( geometry ); } } // early out if we've met our capacity if ( count <= maxLeafTris || 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, triangleBounds, offset, count, strategy ); if ( split.axis === - 1 ) { triggerProgress( offset + count ); node.offset = offset; node.count = count; return node; } const splitOffset = partionFunc( indirectBuffer, indexArray, triangleBounds, 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 MeshBVHNode(); const lstart = offset; const lcount = splitOffset - offset; node.left = left; getBounds( triangleBounds, lstart, lcount, left.boundingData, cacheCentroidBoundingData ); splitNode( left, lstart, lcount, cacheCentroidBoundingData, depth + 1 ); // repeat for right const right = new MeshBVHNode(); const rstart = splitOffset; const rcount = count - lcount; node.right = right; getBounds( triangleBounds, rstart, rcount, right.boundingData, cacheCentroidBoundingData ); splitNode( right, rstart, rcount, cacheCentroidBoundingData, depth + 1 ); } return node; } } export function buildPackedTree( bvh, options ) { const geometry = bvh.geometry; if ( options.indirect ) { bvh._indirectBuffer = generateIndirectBuffer( geometry, options.useSharedArrayBuffer ); if ( hasGroupGaps( geometry, options.range ) && ! options.verbose ) { console.warn( 'MeshBVH: Provided geometry contains groups or a range that do not fully span the vertex contents while using the "indirect" option. ' + 'BVH may incorrectly report intersections on unrendered portions of the geometry.' ); } } if ( ! bvh._indirectBuffer ) { ensureIndex( geometry, options ); } const BufferConstructor = options.useSharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer; const triangleBounds = computeTriangleBounds( geometry ); const geometryRanges = options.indirect ? getFullGeometryRange( geometry, options.range ) : getRootIndexRanges( geometry, options.range ); bvh._roots = geometryRanges.map( range => { const root = buildTree( bvh, triangleBounds, range.offset, range.count, options ); const nodeCount = countNodes( root ); const buffer = new BufferConstructor( BYTES_PER_NODE * nodeCount ); populateBuffer( 0, root, buffer ); return buffer; } ); }