UNPKG

three-mesh-bvh

Version:

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

1 lines 564 kB
{"version":3,"file":"index.umd.cjs","sources":["../src/core/Constants.js","../src/utils/ArrayBoxUtilities.js","../src/core/utils/nodeBufferUtils.js","../src/core/build/computeBoundsUtils.js","../src/core/build/splitUtils.js","../src/core/BVHNode.js","../src/core/build/sortUtils.js","../src/core/build/buildUtils.js","../src/core/build/buildTree.js","../src/utils/PrimitivePool.js","../src/core/utils/BufferStack.js","../src/core/cast/shapecast.js","../src/core/cast/bvhcast.js","../src/core/BVH.js","../src/utils/BufferUtils.js","../src/core/build/geometryUtils.js","../src/core/GeometryBVH.js","../src/math/SeparatingAxisBounds.js","../src/math/MathUtilities.js","../src/math/ExtendedTriangle.js","../src/math/OrientedBox.js","../src/utils/ExtendedTrianglePool.js","../src/core/cast/closestPointToPoint.js","../src/utils/ThreeRayIntersectUtilities.js","../src/utils/TriangleUtilities.js","../src/core/utils/iterationUtils.generated.js","../src/core/cast/refit.generated.js","../src/core/utils/intersectUtils.js","../src/core/utils/iterationUtils_indirect.generated.js","../src/core/cast/raycast.generated.js","../src/core/cast/raycastFirst.generated.js","../src/core/cast/intersectsGeometry.generated.js","../src/core/cast/closestPointToGeometry.generated.js","../src/core/cast/refit_indirect.generated.js","../src/core/cast/raycast_indirect.generated.js","../src/core/cast/raycastFirst_indirect.generated.js","../src/core/cast/intersectsGeometry_indirect.generated.js","../src/core/cast/closestPointToGeometry_indirect.generated.js","../src/utils/GeometryRayIntersectUtilities.js","../src/core/MeshBVH.js","../src/core/LineBVH.js","../src/core/PointsBVH.js","../src/core/ObjectBVH.js","../src/core/SkinnedMeshBVH.js","../src/objects/BVHHelper.js","../src/debug/Debug.js","../src/utils/ExtensionUtilities.js","../src/webgl/VertexAttributeTexture.js","../src/webgl/MeshBVHUniformStruct.js","../src/utils/StaticGeometryGenerator.js","../src/webgl/glsl/common_functions.glsl.js","../src/webgl/glsl/bvh_distance_functions.glsl.js","../src/webgl/glsl/bvh_ray_functions.glsl.js","../src/webgl/glsl/bvh_struct_definitions.glsl.js","../src/index.js"],"sourcesContent":["// Split strategy constants\nexport const CENTER = 0;\nexport const AVERAGE = 1;\nexport const SAH = 2;\n\n// Traversal constants\nexport const NOT_INTERSECTED = 0;\nexport const INTERSECTED = 1;\nexport const CONTAINED = 2;\n\n// SAH cost constants\n// TODO: hone these costs more. The relative difference between them should be the\n// difference in measured time to perform a primitive intersection vs traversing\n// bounds.\n// TODO: could be tuned per primitive type (triangles vs lines vs points)\nexport const PRIMITIVE_INTERSECT_COST = 1.25;\nexport const TRAVERSAL_COST = 1;\n\n\n// Build constants\nexport const BYTES_PER_NODE = 6 * 4 + 4 + 4;\nexport const UINT32_PER_NODE = BYTES_PER_NODE / 4;\nexport const IS_LEAFNODE_FLAG = 0xFFFF;\n\n// Bit masks for 32 bit node data\nexport const LEAFNODE_MASK_32 = IS_LEAFNODE_FLAG << 16;\n\n// EPSILON for computing floating point error during build\n// https://en.wikipedia.org/wiki/Machine_epsilon#Values_for_standard_hardware_floating_point_arithmetics\nexport const FLOAT32_EPSILON = Math.pow( 2, - 24 );\n\nexport const SKIP_GENERATION = Symbol( 'SKIP_GENERATION' );\n\nexport const DEFAULT_OPTIONS = {\n\tstrategy: CENTER,\n\tmaxDepth: 40,\n\tmaxLeafSize: 10,\n\tuseSharedArrayBuffer: false,\n\tsetBoundingBox: true,\n\tonProgress: null,\n\tindirect: false,\n\tverbose: true,\n\trange: null,\n\t[ SKIP_GENERATION ]: false,\n};\n\n","export function arrayToBox( nodeIndex32, array, target ) {\n\n\ttarget.min.x = array[ nodeIndex32 ];\n\ttarget.min.y = array[ nodeIndex32 + 1 ];\n\ttarget.min.z = array[ nodeIndex32 + 2 ];\n\n\ttarget.max.x = array[ nodeIndex32 + 3 ];\n\ttarget.max.y = array[ nodeIndex32 + 4 ];\n\ttarget.max.z = array[ nodeIndex32 + 5 ];\n\n\treturn target;\n\n}\n\nexport function makeEmptyBounds( target ) {\n\n\ttarget[ 0 ] = target[ 1 ] = target[ 2 ] = Infinity;\n\ttarget[ 3 ] = target[ 4 ] = target[ 5 ] = - Infinity;\n\n}\n\nexport function getLongestEdgeIndex( bounds ) {\n\n\tlet splitDimIdx = - 1;\n\tlet splitDist = - Infinity;\n\n\tfor ( let i = 0; i < 3; i ++ ) {\n\n\t\tconst dist = bounds[ i + 3 ] - bounds[ i ];\n\t\tif ( dist > splitDist ) {\n\n\t\t\tsplitDist = dist;\n\t\t\tsplitDimIdx = i;\n\n\t\t}\n\n\t}\n\n\treturn splitDimIdx;\n\n}\n\n// copies bounds a into bounds b\nexport function copyBounds( source, target ) {\n\n\ttarget.set( source );\n\n}\n\n// sets bounds target to the union of bounds a and b\nexport function unionBounds( a, b, target ) {\n\n\tlet aVal, bVal;\n\tfor ( let d = 0; d < 3; d ++ ) {\n\n\t\tconst d3 = d + 3;\n\n\t\t// set the minimum values\n\t\taVal = a[ d ];\n\t\tbVal = b[ d ];\n\t\ttarget[ d ] = aVal < bVal ? aVal : bVal;\n\n\t\t// set the max values\n\t\taVal = a[ d3 ];\n\t\tbVal = b[ d3 ];\n\t\ttarget[ d3 ] = aVal > bVal ? aVal : bVal;\n\n\t}\n\n}\n\n// expands the given bounds by the provided primitive bounds\nexport function expandByPrimitiveBounds( startIndex, primitiveBounds, bounds ) {\n\n\tfor ( let d = 0; d < 3; d ++ ) {\n\n\t\tconst tCenter = primitiveBounds[ startIndex + 2 * d ];\n\t\tconst tHalf = primitiveBounds[ startIndex + 2 * d + 1 ];\n\n\t\tconst tMin = tCenter - tHalf;\n\t\tconst tMax = tCenter + tHalf;\n\n\t\tif ( tMin < bounds[ d ] ) {\n\n\t\t\tbounds[ d ] = tMin;\n\n\t\t}\n\n\t\tif ( tMax > bounds[ d + 3 ] ) {\n\n\t\t\tbounds[ d + 3 ] = tMax;\n\n\t\t}\n\n\t}\n\n}\n\n// compute bounds surface area\nexport function computeSurfaceArea( bounds ) {\n\n\tconst d0 = bounds[ 3 ] - bounds[ 0 ];\n\tconst d1 = bounds[ 4 ] - bounds[ 1 ];\n\tconst d2 = bounds[ 5 ] - bounds[ 2 ];\n\n\treturn 2 * ( d0 * d1 + d1 * d2 + d2 * d0 );\n\n}\n","import { IS_LEAFNODE_FLAG, UINT32_PER_NODE } from '../Constants.js';\n\nexport function IS_LEAF( n16, uint16Array ) {\n\n\treturn uint16Array[ n16 + 15 ] === IS_LEAFNODE_FLAG;\n\n}\n\nexport function OFFSET( n32, uint32Array ) {\n\n\treturn uint32Array[ n32 + 6 ];\n\n}\n\nexport function COUNT( n16, uint16Array ) {\n\n\treturn uint16Array[ n16 + 14 ];\n\n}\n\n// Returns the uint32-aligned offset of the left child node for performance\nexport function LEFT_NODE( n32 ) {\n\n\treturn n32 + UINT32_PER_NODE;\n\n}\n\n// Returns the uint32-aligned offset of the right child node for performance\nexport function RIGHT_NODE( n32, uint32Array ) {\n\n\t// stored value is relative offset from parent, convert to absolute uint32 index\n\tconst relativeOffset = uint32Array[ n32 + 6 ];\n\treturn n32 + relativeOffset * UINT32_PER_NODE;\n\n}\n\nexport function SPLIT_AXIS( n32, uint32Array ) {\n\n\treturn uint32Array[ n32 + 7 ];\n\n}\n\nexport function BOUNDING_DATA_INDEX( n32 ) {\n\n\treturn n32;\n\n}\n","// computes the union of the bounds of all of the given primitives and puts the resulting box in \"target\".\n// A bounding box is computed for the centroids of the primitives, as well, and placed in \"centroidTarget\".\n// These are computed together to avoid redundant accesses to bounds array.\nexport function getBounds( primitiveBounds, offset, count, target, centroidTarget ) {\n\n\tlet minx = Infinity;\n\tlet miny = Infinity;\n\tlet minz = Infinity;\n\tlet maxx = - Infinity;\n\tlet maxy = - Infinity;\n\tlet maxz = - Infinity;\n\n\tlet cminx = Infinity;\n\tlet cminy = Infinity;\n\tlet cminz = Infinity;\n\tlet cmaxx = - Infinity;\n\tlet cmaxy = - Infinity;\n\tlet cmaxz = - Infinity;\n\n\tconst boundsOffset = primitiveBounds.offset || 0;\n\tfor ( let i = ( offset - boundsOffset ) * 6, end = ( offset + count - boundsOffset ) * 6; i < end; i += 6 ) {\n\n\t\tconst cx = primitiveBounds[ i + 0 ];\n\t\tconst hx = primitiveBounds[ i + 1 ];\n\t\tconst lx = cx - hx;\n\t\tconst rx = cx + hx;\n\t\tif ( lx < minx ) minx = lx;\n\t\tif ( rx > maxx ) maxx = rx;\n\t\tif ( cx < cminx ) cminx = cx;\n\t\tif ( cx > cmaxx ) cmaxx = cx;\n\n\t\tconst cy = primitiveBounds[ i + 2 ];\n\t\tconst hy = primitiveBounds[ i + 3 ];\n\t\tconst ly = cy - hy;\n\t\tconst ry = cy + hy;\n\t\tif ( ly < miny ) miny = ly;\n\t\tif ( ry > maxy ) maxy = ry;\n\t\tif ( cy < cminy ) cminy = cy;\n\t\tif ( cy > cmaxy ) cmaxy = cy;\n\n\t\tconst cz = primitiveBounds[ i + 4 ];\n\t\tconst hz = primitiveBounds[ i + 5 ];\n\t\tconst lz = cz - hz;\n\t\tconst rz = cz + hz;\n\t\tif ( lz < minz ) minz = lz;\n\t\tif ( rz > maxz ) maxz = rz;\n\t\tif ( cz < cminz ) cminz = cz;\n\t\tif ( cz > cmaxz ) cmaxz = cz;\n\n\t}\n\n\ttarget[ 0 ] = minx;\n\ttarget[ 1 ] = miny;\n\ttarget[ 2 ] = minz;\n\n\ttarget[ 3 ] = maxx;\n\ttarget[ 4 ] = maxy;\n\ttarget[ 5 ] = maxz;\n\n\tcentroidTarget[ 0 ] = cminx;\n\tcentroidTarget[ 1 ] = cminy;\n\tcentroidTarget[ 2 ] = cminz;\n\n\tcentroidTarget[ 3 ] = cmaxx;\n\tcentroidTarget[ 4 ] = cmaxy;\n\tcentroidTarget[ 5 ] = cmaxz;\n\n}\n","import { getLongestEdgeIndex, computeSurfaceArea, copyBounds, unionBounds, expandByPrimitiveBounds } from '../../utils/ArrayBoxUtilities.js';\nimport { CENTER, AVERAGE, SAH, PRIMITIVE_INTERSECT_COST, TRAVERSAL_COST } from '../Constants.js';\n\nconst BIN_COUNT = 32;\nconst binsSort = ( a, b ) => a.candidate - b.candidate;\nconst sahBins = /* @__PURE__ */ new Array( BIN_COUNT ).fill().map( () => {\n\n\treturn {\n\n\t\tcount: 0,\n\t\tbounds: new Float32Array( 6 ),\n\t\trightCacheBounds: new Float32Array( 6 ),\n\t\tleftCacheBounds: new Float32Array( 6 ),\n\t\tcandidate: 0,\n\n\t};\n\n} );\nconst leftBounds = /* @__PURE__ */ new Float32Array( 6 );\n\nexport function getOptimalSplit( nodeBoundingData, centroidBoundingData, primitiveBounds, offset, count, strategy ) {\n\n\tlet axis = - 1;\n\tlet pos = 0;\n\n\t// Center\n\tif ( strategy === CENTER ) {\n\n\t\taxis = getLongestEdgeIndex( centroidBoundingData );\n\t\tif ( axis !== - 1 ) {\n\n\t\t\tpos = ( centroidBoundingData[ axis ] + centroidBoundingData[ axis + 3 ] ) / 2;\n\n\t\t}\n\n\t} else if ( strategy === AVERAGE ) {\n\n\t\taxis = getLongestEdgeIndex( nodeBoundingData );\n\t\tif ( axis !== - 1 ) {\n\n\t\t\tpos = getAverage( primitiveBounds, offset, count, axis );\n\n\t\t}\n\n\t} else if ( strategy === SAH ) {\n\n\t\tconst rootSurfaceArea = computeSurfaceArea( nodeBoundingData );\n\t\tlet bestCost = PRIMITIVE_INTERSECT_COST * count;\n\n\t\t// iterate over all axes\n\t\tconst boundsOffset = primitiveBounds.offset || 0;\n\t\tconst cStart = ( offset - boundsOffset ) * 6;\n\t\tconst cEnd = ( offset + count - boundsOffset ) * 6;\n\t\tfor ( let a = 0; a < 3; a ++ ) {\n\n\t\t\tconst axisLeft = centroidBoundingData[ a ];\n\t\t\tconst axisRight = centroidBoundingData[ a + 3 ];\n\t\t\tconst axisLength = axisRight - axisLeft;\n\t\t\tconst binWidth = axisLength / BIN_COUNT;\n\n\t\t\t// If we have fewer primitives than we're planning to split then just check all\n\t\t\t// the primitive positions because it will be faster.\n\t\t\tif ( count < BIN_COUNT / 4 ) {\n\n\t\t\t\t// initialize the bin candidates\n\t\t\t\tconst truncatedBins = [ ...sahBins ];\n\t\t\t\ttruncatedBins.length = count;\n\n\t\t\t\t// set the candidates\n\t\t\t\tlet b = 0;\n\t\t\t\tfor ( let c = cStart; c < cEnd; c += 6, b ++ ) {\n\n\t\t\t\t\tconst bin = truncatedBins[ b ];\n\t\t\t\t\tbin.candidate = primitiveBounds[ c + 2 * a ];\n\t\t\t\t\tbin.count = 0;\n\n\t\t\t\t\tconst {\n\t\t\t\t\t\tbounds,\n\t\t\t\t\t\tleftCacheBounds,\n\t\t\t\t\t\trightCacheBounds,\n\t\t\t\t\t} = bin;\n\t\t\t\t\tfor ( let d = 0; d < 3; d ++ ) {\n\n\t\t\t\t\t\trightCacheBounds[ d ] = Infinity;\n\t\t\t\t\t\trightCacheBounds[ d + 3 ] = - Infinity;\n\n\t\t\t\t\t\tleftCacheBounds[ d ] = Infinity;\n\t\t\t\t\t\tleftCacheBounds[ d + 3 ] = - Infinity;\n\n\t\t\t\t\t\tbounds[ d ] = Infinity;\n\t\t\t\t\t\tbounds[ d + 3 ] = - Infinity;\n\n\t\t\t\t\t}\n\n\t\t\t\t\texpandByPrimitiveBounds( c, primitiveBounds, bounds );\n\n\t\t\t\t}\n\n\t\t\t\ttruncatedBins.sort( binsSort );\n\n\t\t\t\t// remove redundant splits\n\t\t\t\tlet splitCount = count;\n\t\t\t\tfor ( let bi = 0; bi < splitCount; bi ++ ) {\n\n\t\t\t\t\tconst bin = truncatedBins[ bi ];\n\t\t\t\t\twhile ( bi + 1 < splitCount && truncatedBins[ bi + 1 ].candidate === bin.candidate ) {\n\n\t\t\t\t\t\ttruncatedBins.splice( bi + 1, 1 );\n\t\t\t\t\t\tsplitCount --;\n\n\t\t\t\t\t}\n\n\t\t\t\t}\n\n\t\t\t\t// find the appropriate bin for each primitive and expand the bounds.\n\t\t\t\tfor ( let c = cStart; c < cEnd; c += 6 ) {\n\n\t\t\t\t\tconst center = primitiveBounds[ c + 2 * a ];\n\t\t\t\t\tfor ( let bi = 0; bi < splitCount; bi ++ ) {\n\n\t\t\t\t\t\tconst bin = truncatedBins[ bi ];\n\t\t\t\t\t\tif ( center >= bin.candidate ) {\n\n\t\t\t\t\t\t\texpandByPrimitiveBounds( c, primitiveBounds, bin.rightCacheBounds );\n\n\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\texpandByPrimitiveBounds( c, primitiveBounds, bin.leftCacheBounds );\n\t\t\t\t\t\t\tbin.count ++;\n\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\n\t\t\t\t}\n\n\t\t\t\t// expand all the bounds\n\t\t\t\tfor ( let bi = 0; bi < splitCount; bi ++ ) {\n\n\t\t\t\t\tconst bin = truncatedBins[ bi ];\n\t\t\t\t\tconst leftCount = bin.count;\n\t\t\t\t\tconst rightCount = count - bin.count;\n\n\t\t\t\t\t// check the cost of this split\n\t\t\t\t\tconst leftBounds = bin.leftCacheBounds;\n\t\t\t\t\tconst rightBounds = bin.rightCacheBounds;\n\n\t\t\t\t\tlet leftProb = 0;\n\t\t\t\t\tif ( leftCount !== 0 ) {\n\n\t\t\t\t\t\tleftProb = computeSurfaceArea( leftBounds ) / rootSurfaceArea;\n\n\t\t\t\t\t}\n\n\t\t\t\t\tlet rightProb = 0;\n\t\t\t\t\tif ( rightCount !== 0 ) {\n\n\t\t\t\t\t\trightProb = computeSurfaceArea( rightBounds ) / rootSurfaceArea;\n\n\t\t\t\t\t}\n\n\t\t\t\t\tconst cost = TRAVERSAL_COST + PRIMITIVE_INTERSECT_COST * (\n\t\t\t\t\t\tleftProb * leftCount + rightProb * rightCount\n\t\t\t\t\t);\n\n\t\t\t\t\tif ( cost < bestCost ) {\n\n\t\t\t\t\t\taxis = a;\n\t\t\t\t\t\tbestCost = cost;\n\t\t\t\t\t\tpos = bin.candidate;\n\n\t\t\t\t\t}\n\n\t\t\t\t}\n\n\t\t\t} else {\n\n\t\t\t\t// reset the bins\n\t\t\t\tfor ( let i = 0; i < BIN_COUNT; i ++ ) {\n\n\t\t\t\t\tconst bin = sahBins[ i ];\n\t\t\t\t\tbin.count = 0;\n\t\t\t\t\tbin.candidate = axisLeft + binWidth + i * binWidth;\n\n\t\t\t\t\tconst bounds = bin.bounds;\n\t\t\t\t\tfor ( let d = 0; d < 3; d ++ ) {\n\n\t\t\t\t\t\tbounds[ d ] = Infinity;\n\t\t\t\t\t\tbounds[ d + 3 ] = - Infinity;\n\n\t\t\t\t\t}\n\n\t\t\t\t}\n\n\t\t\t\t// iterate over all center positions\n\t\t\t\tfor ( let c = cStart; c < cEnd; c += 6 ) {\n\n\t\t\t\t\tconst triCenter = primitiveBounds[ c + 2 * a ];\n\t\t\t\t\tconst relativeCenter = triCenter - axisLeft;\n\n\t\t\t\t\t// in the partition function if the centroid lies on the split plane then it is\n\t\t\t\t\t// considered to be on the right side of the split\n\t\t\t\t\tlet binIndex = ~ ~ ( relativeCenter / binWidth );\n\t\t\t\t\tif ( binIndex >= BIN_COUNT ) binIndex = BIN_COUNT - 1;\n\n\t\t\t\t\tconst bin = sahBins[ binIndex ];\n\t\t\t\t\tbin.count ++;\n\n\t\t\t\t\texpandByPrimitiveBounds( c, primitiveBounds, bin.bounds );\n\n\t\t\t\t}\n\n\t\t\t\t// cache the unioned bounds from right to left so we don't have to regenerate them each time\n\t\t\t\tconst lastBin = sahBins[ BIN_COUNT - 1 ];\n\t\t\t\tcopyBounds( lastBin.bounds, lastBin.rightCacheBounds );\n\t\t\t\tfor ( let i = BIN_COUNT - 2; i >= 0; i -- ) {\n\n\t\t\t\t\tconst bin = sahBins[ i ];\n\t\t\t\t\tconst nextBin = sahBins[ i + 1 ];\n\t\t\t\t\tunionBounds( bin.bounds, nextBin.rightCacheBounds, bin.rightCacheBounds );\n\n\t\t\t\t}\n\n\t\t\t\tlet leftCount = 0;\n\t\t\t\tfor ( let i = 0; i < BIN_COUNT - 1; i ++ ) {\n\n\t\t\t\t\tconst bin = sahBins[ i ];\n\t\t\t\t\tconst binCount = bin.count;\n\t\t\t\t\tconst bounds = bin.bounds;\n\n\t\t\t\t\tconst nextBin = sahBins[ i + 1 ];\n\t\t\t\t\tconst rightBounds = nextBin.rightCacheBounds;\n\n\t\t\t\t\t// don't do anything with the bounds if the new bounds have no primitives\n\t\t\t\t\tif ( binCount !== 0 ) {\n\n\t\t\t\t\t\tif ( leftCount === 0 ) {\n\n\t\t\t\t\t\t\tcopyBounds( bounds, leftBounds );\n\n\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\tunionBounds( bounds, leftBounds, leftBounds );\n\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\n\t\t\t\t\tleftCount += binCount;\n\n\t\t\t\t\t// check the cost of this split\n\t\t\t\t\tlet leftProb = 0;\n\t\t\t\t\tlet rightProb = 0;\n\n\t\t\t\t\tif ( leftCount !== 0 ) {\n\n\t\t\t\t\t\tleftProb = computeSurfaceArea( leftBounds ) / rootSurfaceArea;\n\n\t\t\t\t\t}\n\n\t\t\t\t\tconst rightCount = count - leftCount;\n\t\t\t\t\tif ( rightCount !== 0 ) {\n\n\t\t\t\t\t\trightProb = computeSurfaceArea( rightBounds ) / rootSurfaceArea;\n\n\t\t\t\t\t}\n\n\t\t\t\t\tconst cost = TRAVERSAL_COST + PRIMITIVE_INTERSECT_COST * (\n\t\t\t\t\t\tleftProb * leftCount + rightProb * rightCount\n\t\t\t\t\t);\n\n\t\t\t\t\tif ( cost < bestCost ) {\n\n\t\t\t\t\t\taxis = a;\n\t\t\t\t\t\tbestCost = cost;\n\t\t\t\t\t\tpos = bin.candidate;\n\n\t\t\t\t\t}\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t}\n\n\t} else {\n\n\t\tconsole.warn( `BVH: Invalid build strategy value ${ strategy } used.` );\n\n\t}\n\n\treturn { axis, pos };\n\n}\n\n// returns the average coordinate on the specified axis of all the provided primitives\nfunction getAverage( primitiveBounds, offset, count, axis ) {\n\n\tlet avg = 0;\n\tconst boundsOffset = primitiveBounds.offset;\n\tfor ( let i = offset, end = offset + count; i < end; i ++ ) {\n\n\t\tavg += primitiveBounds[ ( i - boundsOffset ) * 6 + axis * 2 ];\n\n\t}\n\n\treturn avg / count;\n\n}\n","export class BVHNode {\n\n\tconstructor() {\n\n\t\t// internal nodes have boundingData, left, right, and splitAxis\n\t\t// leaf nodes have offset and count (referring to primitives in the mesh geometry)\n\n\t\tthis.boundingData = new Float32Array( 6 );\n\n\t}\n\n}\n","// reorders the partition buffer such that for `count` elements after `offset`, elements on the left side of the split\n// will be on the left and elements on the right side of the split will be on the right. returns the index\n// of the first element on the right side, or offset + count if there are no elements on the right side.\nexport function partition( buffer, stride, primitiveBounds, offset, count, split ) {\n\n\tlet left = offset;\n\tlet right = offset + count - 1;\n\tconst pos = split.pos;\n\tconst axisOffset = split.axis * 2;\n\tconst boundsOffset = primitiveBounds.offset || 0;\n\n\t// hoare partitioning, see e.g. https://en.wikipedia.org/wiki/Quicksort#Hoare_partition_scheme\n\twhile ( true ) {\n\n\t\twhile ( left <= right && primitiveBounds[ ( left - boundsOffset ) * 6 + axisOffset ] < pos ) {\n\n\t\t\tleft ++;\n\n\t\t}\n\n\t\t// if a primitive center lies on the partition plane it is considered to be on the right side\n\t\twhile ( left <= right && primitiveBounds[ ( right - boundsOffset ) * 6 + axisOffset ] >= pos ) {\n\n\t\t\tright --;\n\n\t\t}\n\n\t\tif ( left < right ) {\n\n\t\t\t// we need to swap all of the information associated with the primitives at index\n\t\t\t// left and right; that's the elements in the partition buffer and the bounds\n\t\t\tfor ( let i = 0; i < stride; i ++ ) {\n\n\t\t\t\tlet t0 = buffer[ left * stride + i ];\n\t\t\t\tbuffer[ left * stride + i ] = buffer[ right * stride + i ];\n\t\t\t\tbuffer[ right * stride + i ] = t0;\n\n\t\t\t}\n\n\t\t\t// swap bounds\n\t\t\tfor ( let i = 0; i < 6; i ++ ) {\n\n\t\t\t\tconst l = left - boundsOffset;\n\t\t\t\tconst r = right - boundsOffset;\n\t\t\t\tconst tb = primitiveBounds[ l * 6 + i ];\n\t\t\t\tprimitiveBounds[ l * 6 + i ] = primitiveBounds[ r * 6 + i ];\n\t\t\t\tprimitiveBounds[ r * 6 + i ] = tb;\n\n\t\t\t}\n\n\t\t\tleft ++;\n\t\t\tright --;\n\n\t\t} else {\n\n\t\t\treturn left;\n\n\t\t}\n\n\t}\n\n}\n","import { BYTES_PER_NODE, IS_LEAFNODE_FLAG } from '../Constants.js';\n\nlet float32Array, uint32Array, uint16Array, uint8Array;\nconst MAX_POINTER = Math.pow( 2, 32 );\n\nexport function countNodes( node ) {\n\n\tif ( 'count' in node ) {\n\n\t\treturn 1;\n\n\t} else {\n\n\t\treturn 1 + countNodes( node.left ) + countNodes( node.right );\n\n\t}\n\n}\n\nexport function populateBuffer( byteOffset, node, buffer ) {\n\n\tfloat32Array = new Float32Array( buffer );\n\tuint32Array = new Uint32Array( buffer );\n\tuint16Array = new Uint16Array( buffer );\n\tuint8Array = new Uint8Array( buffer );\n\n\treturn _populateBuffer( byteOffset, node );\n\n}\n\n// pack structure\n// boundingData \t\t\t\t: 6 float32\n// right / offset \t\t\t\t: 1 uint32\n// splitAxis / isLeaf + count \t: 1 uint32 / 2 uint16\nfunction _populateBuffer( byteOffset, node ) {\n\n\tconst node32Index = byteOffset / 4;\n\tconst node16Index = byteOffset / 2;\n\tconst isLeaf = 'count' in node;\n\tconst boundingData = node.boundingData;\n\tfor ( let i = 0; i < 6; i ++ ) {\n\n\t\tfloat32Array[ node32Index + i ] = boundingData[ i ];\n\n\t}\n\n\tif ( isLeaf ) {\n\n\t\tif ( node.buffer ) {\n\n\t\t\tuint8Array.set( new Uint8Array( node.buffer ), byteOffset );\n\t\t\treturn byteOffset + node.buffer.byteLength;\n\n\t\t} else {\n\n\t\t\tuint32Array[ node32Index + 6 ] = node.offset;\n\t\t\tuint16Array[ node16Index + 14 ] = node.count;\n\t\t\tuint16Array[ node16Index + 15 ] = IS_LEAFNODE_FLAG;\n\t\t\treturn byteOffset + BYTES_PER_NODE;\n\n\t\t}\n\n\t} else {\n\n\t\tconst { left, right, splitAxis } = node;\n\n\t\t// fill in the left node contents\n\t\tconst leftByteOffset = byteOffset + BYTES_PER_NODE;\n\t\tlet rightByteOffset = _populateBuffer( leftByteOffset, left );\n\n\t\t// calculate relative offset from parent to right child\n\t\tconst currentNodeIndex = byteOffset / BYTES_PER_NODE;\n\t\tconst rightNodeIndex = rightByteOffset / BYTES_PER_NODE;\n\t\tconst relativeRightIndex = rightNodeIndex - currentNodeIndex;\n\n\t\t// check if the relative offset is too high\n\t\tif ( relativeRightIndex > MAX_POINTER ) {\n\n\t\t\tthrow new Error( 'MeshBVH: Cannot store relative child node offset greater than 32 bits.' );\n\n\t\t}\n\n\t\t// fill in the right node contents (store as relative offset)\n\t\tuint32Array[ node32Index + 6 ] = relativeRightIndex;\n\t\tuint32Array[ node32Index + 7 ] = splitAxis;\n\n\t\t// return the next available buffer pointer\n\t\treturn _populateBuffer( rightByteOffset, right );\n\n\t}\n\n}\n","import { getBounds } from './computeBoundsUtils.js';\nimport { getOptimalSplit } from './splitUtils.js';\nimport { BVHNode } from '../BVHNode.js';\nimport { BYTES_PER_NODE } from '../Constants.js';\n\nimport { partition } from './sortUtils.js';\nimport { countNodes, populateBuffer } from './buildUtils.js';\n\nexport function buildTree( bvh, primitiveBounds, offset, count, options, loadRange ) {\n\n\t// expand variables\n\tconst {\n\t\tmaxDepth,\n\t\tverbose,\n\t\tmaxLeafSize,\n\t\tstrategy,\n\t\tonProgress,\n\t} = options;\n\n\tconst partitionBuffer = bvh.primitiveBuffer;\n\tconst partitionStride = bvh.primitiveBufferStride;\n\n\t// generate intermediate variables\n\tconst cacheCentroidBoundingData = new Float32Array( 6 );\n\tlet reachedMaxDepth = false;\n\n\tconst root = new BVHNode();\n\tgetBounds( primitiveBounds, offset, count, root.boundingData, cacheCentroidBoundingData );\n\tsplitNode( root, offset, count, cacheCentroidBoundingData );\n\treturn root;\n\n\tfunction triggerProgress( primitivesProcessed ) {\n\n\t\tif ( onProgress ) {\n\n\t\t\tonProgress( ( primitivesProcessed - loadRange.offset ) / loadRange.count );\n\n\t\t}\n\n\t}\n\n\t// either recursively splits the given node, creating left and right subtrees for it, or makes it a leaf node,\n\t// recording the offset and count of its primitives and writing them into the reordered geometry index.\n\tfunction splitNode( node, offset, count, centroidBoundingData = null, depth = 0 ) {\n\n\t\tif ( ! reachedMaxDepth && depth >= maxDepth ) {\n\n\t\t\treachedMaxDepth = true;\n\t\t\tif ( verbose ) {\n\n\t\t\t\tconsole.warn( `BVH: Max depth of ${ maxDepth } reached when generating BVH. Consider increasing maxDepth.` );\n\n\t\t\t}\n\n\t\t}\n\n\t\t// early out if we've met our capacity\n\t\tif ( count <= maxLeafSize || depth >= maxDepth ) {\n\n\t\t\ttriggerProgress( offset + count );\n\t\t\tnode.offset = offset;\n\t\t\tnode.count = count;\n\t\t\treturn node;\n\n\t\t}\n\n\t\t// Find where to split the volume\n\t\tconst split = getOptimalSplit( node.boundingData, centroidBoundingData, primitiveBounds, offset, count, strategy );\n\t\tif ( split.axis === - 1 ) {\n\n\t\t\ttriggerProgress( offset + count );\n\t\t\tnode.offset = offset;\n\t\t\tnode.count = count;\n\t\t\treturn node;\n\n\t\t}\n\n\t\tconst splitOffset = partition( partitionBuffer, partitionStride, primitiveBounds, offset, count, split );\n\n\t\t// create the two new child nodes\n\t\tif ( splitOffset === offset || splitOffset === offset + count ) {\n\n\t\t\ttriggerProgress( offset + count );\n\t\t\tnode.offset = offset;\n\t\t\tnode.count = count;\n\n\t\t} else {\n\n\t\t\tnode.splitAxis = split.axis;\n\n\t\t\t// create the left child and compute its bounding box\n\t\t\tconst left = new BVHNode();\n\t\t\tconst lstart = offset;\n\t\t\tconst lcount = splitOffset - offset;\n\t\t\tnode.left = left;\n\n\t\t\tgetBounds( primitiveBounds, lstart, lcount, left.boundingData, cacheCentroidBoundingData );\n\t\t\tsplitNode( left, lstart, lcount, cacheCentroidBoundingData, depth + 1 );\n\n\t\t\t// repeat for right\n\t\t\tconst right = new BVHNode();\n\t\t\tconst rstart = splitOffset;\n\t\t\tconst rcount = count - lcount;\n\t\t\tnode.right = right;\n\n\t\t\tgetBounds( primitiveBounds, rstart, rcount, right.boundingData, cacheCentroidBoundingData );\n\t\t\tsplitNode( right, rstart, rcount, cacheCentroidBoundingData, depth + 1 );\n\n\t\t}\n\n\t\treturn node;\n\n\t}\n\n}\n\nexport function buildPackedTree( bvh, options ) {\n\n\tconst BufferConstructor = options.useSharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer;\n\n\t// get the range of buffer data to construct / arrange\n\tconst rootRanges = bvh.getRootRanges( options.range );\n\tconst firstRange = rootRanges[ 0 ];\n\tconst lastRange = rootRanges[ rootRanges.length - 1 ];\n\tconst fullRange = {\n\t\toffset: firstRange.offset,\n\t\tcount: lastRange.offset + lastRange.count - firstRange.offset,\n\t};\n\n\t// construct the primitive bounds for sorting\n\tconst primitiveBounds = new Float32Array( 6 * fullRange.count );\n\tprimitiveBounds.offset = fullRange.offset;\n\tbvh.computePrimitiveBounds( fullRange.offset, fullRange.count, primitiveBounds );\n\n\t// Build BVH roots\n\tbvh._roots = rootRanges.map( range => {\n\n\t\tconst root = buildTree( bvh, primitiveBounds, range.offset, range.count, options, fullRange );\n\t\tconst nodeCount = countNodes( root );\n\t\tconst buffer = new BufferConstructor( BYTES_PER_NODE * nodeCount );\n\t\tpopulateBuffer( 0, root, buffer );\n\t\treturn buffer;\n\n\t} );\n\n}\n","export class PrimitivePool {\n\n\tconstructor( getNewPrimitive ) {\n\n\t\tthis._getNewPrimitive = getNewPrimitive;\n\t\tthis._primitives = [];\n\n\t}\n\n\tgetPrimitive() {\n\n\t\tconst primitives = this._primitives;\n\t\tif ( primitives.length === 0 ) {\n\n\t\t\treturn this._getNewPrimitive();\n\n\t\t} else {\n\n\t\t\treturn primitives.pop();\n\n\t\t}\n\n\t}\n\n\treleasePrimitive( primitive ) {\n\n\t\tthis._primitives.push( primitive );\n\n\t}\n\n}\n","class _BufferStack {\n\n\tconstructor() {\n\n\t\tthis.float32Array = null;\n\t\tthis.uint16Array = null;\n\t\tthis.uint32Array = null;\n\n\t\tconst stack = [];\n\t\tlet prevBuffer = null;\n\t\tthis.setBuffer = buffer => {\n\n\t\t\tif ( prevBuffer ) {\n\n\t\t\t\tstack.push( prevBuffer );\n\n\t\t\t}\n\n\t\t\tprevBuffer = buffer;\n\t\t\tthis.float32Array = new Float32Array( buffer );\n\t\t\tthis.uint16Array = new Uint16Array( buffer );\n\t\t\tthis.uint32Array = new Uint32Array( buffer );\n\n\t\t};\n\n\t\tthis.clearBuffer = () => {\n\n\t\t\tprevBuffer = null;\n\t\t\tthis.float32Array = null;\n\t\t\tthis.uint16Array = null;\n\t\t\tthis.uint32Array = null;\n\n\t\t\tif ( stack.length !== 0 ) {\n\n\t\t\t\tthis.setBuffer( stack.pop() );\n\n\t\t\t}\n\n\t\t};\n\n\t}\n\n}\n\nexport const BufferStack = /* @__PURE__ */ new _BufferStack();\n","import { Box3 } from 'three';\nimport { CONTAINED, UINT32_PER_NODE } from '../Constants.js';\nimport { arrayToBox } from '../../utils/ArrayBoxUtilities.js';\nimport { PrimitivePool } from '../../utils/PrimitivePool.js';\nimport { COUNT, OFFSET, LEFT_NODE, RIGHT_NODE, IS_LEAF, BOUNDING_DATA_INDEX } from '../utils/nodeBufferUtils.js';\nimport { BufferStack } from '../utils/BufferStack.js';\n\nlet _box1, _box2;\nconst boxStack = [];\nconst boxPool = /* @__PURE__ */ new PrimitivePool( () => new Box3() );\n\nexport function shapecast( bvh, root, intersectsBounds, intersectsRange, boundsTraverseOrder, nodeOffset ) {\n\n\t// setup\n\t_box1 = boxPool.getPrimitive();\n\t_box2 = boxPool.getPrimitive();\n\tboxStack.push( _box1, _box2 );\n\tBufferStack.setBuffer( bvh._roots[ root ] );\n\n\tconst result = shapecastTraverse( 0, bvh.geometry, intersectsBounds, intersectsRange, boundsTraverseOrder, nodeOffset );\n\n\t// cleanup\n\tBufferStack.clearBuffer();\n\tboxPool.releasePrimitive( _box1 );\n\tboxPool.releasePrimitive( _box2 );\n\tboxStack.pop();\n\tboxStack.pop();\n\n\tconst length = boxStack.length;\n\tif ( length > 0 ) {\n\n\t\t_box2 = boxStack[ length - 1 ];\n\t\t_box1 = boxStack[ length - 2 ];\n\n\t}\n\n\treturn result;\n\n}\n\nfunction shapecastTraverse(\n\tnodeIndex32,\n\tgeometry,\n\tintersectsBoundsFunc,\n\tintersectsRangeFunc,\n\tnodeScoreFunc = null,\n\tnodeIndexOffset = 0, // offset for unique node identifier\n\tdepth = 0\n) {\n\n\tconst { float32Array, uint16Array, uint32Array } = BufferStack;\n\tlet nodeIndex16 = nodeIndex32 * 2;\n\n\tconst isLeaf = IS_LEAF( nodeIndex16, uint16Array );\n\tif ( isLeaf ) {\n\n\t\tconst offset = OFFSET( nodeIndex32, uint32Array );\n\t\tconst count = COUNT( nodeIndex16, uint16Array );\n\t\tarrayToBox( BOUNDING_DATA_INDEX( nodeIndex32 ), float32Array, _box1 );\n\t\treturn intersectsRangeFunc( offset, count, false, depth, nodeIndexOffset + nodeIndex32 / UINT32_PER_NODE, _box1 );\n\n\t} else {\n\n\t\tconst left = LEFT_NODE( nodeIndex32 );\n\t\tconst right = RIGHT_NODE( nodeIndex32, uint32Array );\n\t\tlet c1 = left;\n\t\tlet c2 = right;\n\n\t\tlet score1, score2;\n\t\tlet box1, box2;\n\t\tif ( nodeScoreFunc ) {\n\n\t\t\tbox1 = _box1;\n\t\t\tbox2 = _box2;\n\n\t\t\t// bounding data is not offset\n\t\t\tarrayToBox( BOUNDING_DATA_INDEX( c1 ), float32Array, box1 );\n\t\t\tarrayToBox( BOUNDING_DATA_INDEX( c2 ), float32Array, box2 );\n\n\t\t\tscore1 = nodeScoreFunc( box1 );\n\t\t\tscore2 = nodeScoreFunc( box2 );\n\n\t\t\tif ( score2 < score1 ) {\n\n\t\t\t\tc1 = right;\n\t\t\t\tc2 = left;\n\n\t\t\t\tconst temp = score1;\n\t\t\t\tscore1 = score2;\n\t\t\t\tscore2 = temp;\n\n\t\t\t\tbox1 = box2;\n\t\t\t\t// box2 is always set before use below\n\n\t\t\t}\n\n\t\t}\n\n\t\t// Check box 1 intersection\n\t\tif ( ! box1 ) {\n\n\t\t\tbox1 = _box1;\n\t\t\tarrayToBox( BOUNDING_DATA_INDEX( c1 ), float32Array, box1 );\n\n\t\t}\n\n\t\tconst isC1Leaf = IS_LEAF( c1 * 2, uint16Array );\n\t\tconst c1Intersection = intersectsBoundsFunc( box1, isC1Leaf, score1, depth + 1, nodeIndexOffset + c1 / UINT32_PER_NODE );\n\n\t\tlet c1StopTraversal;\n\t\tif ( c1Intersection === CONTAINED ) {\n\n\t\t\tconst offset = getLeftOffset( c1 );\n\t\t\tconst end = getRightEndOffset( c1 );\n\t\t\tconst count = end - offset;\n\n\t\t\tc1StopTraversal = intersectsRangeFunc( offset, count, true, depth + 1, nodeIndexOffset + c1 / UINT32_PER_NODE, box1 );\n\n\t\t} else {\n\n\t\t\tc1StopTraversal =\n\t\t\t\tc1Intersection &&\n\t\t\t\tshapecastTraverse(\n\t\t\t\t\tc1,\n\t\t\t\t\tgeometry,\n\t\t\t\t\tintersectsBoundsFunc,\n\t\t\t\t\tintersectsRangeFunc,\n\t\t\t\t\tnodeScoreFunc,\n\t\t\t\t\tnodeIndexOffset,\n\t\t\t\t\tdepth + 1\n\t\t\t\t);\n\n\t\t}\n\n\t\tif ( c1StopTraversal ) return true;\n\n\t\t// Check box 2 intersection\n\t\t// cached box2 will have been overwritten by previous traversal\n\t\tbox2 = _box2;\n\t\tarrayToBox( BOUNDING_DATA_INDEX( c2 ), float32Array, box2 );\n\n\t\tconst isC2Leaf = IS_LEAF( c2 * 2, uint16Array );\n\t\tconst c2Intersection = intersectsBoundsFunc( box2, isC2Leaf, score2, depth + 1, nodeIndexOffset + c2 / UINT32_PER_NODE );\n\n\t\tlet c2StopTraversal;\n\t\tif ( c2Intersection === CONTAINED ) {\n\n\t\t\tconst offset = getLeftOffset( c2 );\n\t\t\tconst end = getRightEndOffset( c2 );\n\t\t\tconst count = end - offset;\n\n\t\t\tc2StopTraversal = intersectsRangeFunc( offset, count, true, depth + 1, nodeIndexOffset + c2 / UINT32_PER_NODE, box2 );\n\n\t\t} else {\n\n\t\t\tc2StopTraversal =\n\t\t\t\tc2Intersection &&\n\t\t\t\tshapecastTraverse(\n\t\t\t\t\tc2,\n\t\t\t\t\tgeometry,\n\t\t\t\t\tintersectsBoundsFunc,\n\t\t\t\t\tintersectsRangeFunc,\n\t\t\t\t\tnodeScoreFunc,\n\t\t\t\t\tnodeIndexOffset,\n\t\t\t\t\tdepth + 1\n\t\t\t\t);\n\n\t\t}\n\n\t\tif ( c2StopTraversal ) return true;\n\n\t\treturn false;\n\n\t\t// Define these inside the function so it has access to the local variables needed\n\t\t// when converting to the buffer equivalents\n\t\tfunction getLeftOffset( nodeIndex32 ) {\n\n\t\t\tconst { uint16Array, uint32Array } = BufferStack;\n\t\t\tlet nodeIndex16 = nodeIndex32 * 2;\n\n\t\t\t// traverse until we find a leaf\n\t\t\twhile ( ! IS_LEAF( nodeIndex16, uint16Array ) ) {\n\n\t\t\t\tnodeIndex32 = LEFT_NODE( nodeIndex32 );\n\t\t\t\tnodeIndex16 = nodeIndex32 * 2;\n\n\t\t\t}\n\n\t\t\treturn OFFSET( nodeIndex32, uint32Array );\n\n\t\t}\n\n\t\tfunction getRightEndOffset( nodeIndex32 ) {\n\n\t\t\tconst { uint16Array, uint32Array } = BufferStack;\n\t\t\tlet nodeIndex16 = nodeIndex32 * 2;\n\n\t\t\t// traverse until we find a leaf\n\t\t\twhile ( ! IS_LEAF( nodeIndex16, uint16Array ) ) {\n\n\t\t\t\t// adjust offset to point to the right node\n\t\t\t\tnodeIndex32 = RIGHT_NODE( nodeIndex32, uint32Array );\n\t\t\t\tnodeIndex16 = nodeIndex32 * 2;\n\n\t\t\t}\n\n\t\t\t// return the end offset of the triangle range\n\t\t\treturn OFFSET( nodeIndex32, uint32Array ) + COUNT( nodeIndex16, uint16Array );\n\n\t\t}\n\n\t}\n\n}\n","import { Box3, Matrix4 } from 'three';\nimport { BufferStack } from '../utils/BufferStack.js';\nimport { BOUNDING_DATA_INDEX, COUNT, IS_LEAF, LEFT_NODE, OFFSET, RIGHT_NODE } from '../utils/nodeBufferUtils.js';\nimport { arrayToBox } from '../../utils/ArrayBoxUtilities.js';\nimport { PrimitivePool } from '../../utils/PrimitivePool.js';\nimport { BYTES_PER_NODE, UINT32_PER_NODE } from '../Constants.js';\n\nconst _bufferStack1 = /* @__PURE__ */ new BufferStack.constructor();\nconst _bufferStack2 = /* @__PURE__ */ new BufferStack.constructor();\nconst _boxPool = /* @__PURE__ */ new PrimitivePool( () => new Box3() );\nconst _leftBox1 = /* @__PURE__ */ new Box3();\nconst _rightBox1 = /* @__PURE__ */ new Box3();\n\nconst _leftBox2 = /* @__PURE__ */ new Box3();\nconst _rightBox2 = /* @__PURE__ */ new Box3();\n\nlet _active = false;\n\nexport function bvhcast( bvh, otherBvh, matrixToLocal, intersectsRanges ) {\n\n\tif ( _active ) {\n\n\t\tthrow new Error( 'MeshBVH: Recursive calls to bvhcast not supported.' );\n\n\t}\n\n\t_active = true;\n\n\tconst roots = bvh._roots;\n\tconst otherRoots = otherBvh._roots;\n\tlet result;\n\tlet nodeOffset1 = 0;\n\tlet nodeOffset2 = 0;\n\tconst invMat = new Matrix4().copy( matrixToLocal ).invert();\n\n\t// iterate over the first set of roots\n\tfor ( let i = 0, il = roots.length; i < il; i ++ ) {\n\n\t\t_bufferStack1.setBuffer( roots[ i ] );\n\t\tnodeOffset2 = 0;\n\n\t\t// prep the initial root box\n\t\tconst localBox = _boxPool.getPrimitive();\n\t\tarrayToBox( BOUNDING_DATA_INDEX( 0 ), _bufferStack1.float32Array, localBox );\n\t\tlocalBox.applyMatrix4( invMat );\n\n\t\t// iterate over the second set of roots\n\t\tfor ( let j = 0, jl = otherRoots.length; j < jl; j ++ ) {\n\n\t\t\t_bufferStack2.setBuffer( otherRoots[ j ] );\n\n\t\t\tresult = _traverse(\n\t\t\t\t0, 0, matrixToLocal, invMat, intersectsRanges,\n\t\t\t\tnodeOffset1, nodeOffset2, 0, 0,\n\t\t\t\tlocalBox,\n\t\t\t);\n\n\t\t\t_bufferStack2.clearBuffer();\n\t\t\tnodeOffset2 += otherRoots[ j ].byteLength / BYTES_PER_NODE;\n\n\t\t\tif ( result ) {\n\n\t\t\t\tbreak;\n\n\t\t\t}\n\n\t\t}\n\n\t\t// release stack info\n\t\t_boxPool.releasePrimitive( localBox );\n\t\t_bufferStack1.clearBuffer();\n\t\tnodeOffset1 += roots[ i ].byteLength / BYTES_PER_NODE;\n\n\t\tif ( result ) {\n\n\t\t\tbreak;\n\n\t\t}\n\n\t}\n\n\t_active = false;\n\treturn result;\n\n}\n\nfunction _traverse(\n\tnode1Index32,\n\tnode2Index32,\n\tmatrix2to1,\n\tmatrix1to2,\n\tintersectsRangesFunc,\n\n\t// offsets for ids\n\tnode1IndexOffset = 0,\n\tnode2IndexOffset = 0,\n\n\t// tree depth\n\tdepth1 = 0,\n\tdepth2 = 0,\n\n\tcurrBox = null,\n\treversed = false,\n\n) {\n\n\t// get the buffer stacks associated with the current indices\n\tlet bufferStack1, bufferStack2;\n\tif ( reversed ) {\n\n\t\tbufferStack1 = _bufferStack2;\n\t\tbufferStack2 = _bufferStack1;\n\n\t} else {\n\n\t\tbufferStack1 = _bufferStack1;\n\t\tbufferStack2 = _bufferStack2;\n\n\t}\n\n\t// get the local instances of the typed buffers\n\tconst\n\t\tfloat32Array1 = bufferStack1.float32Array,\n\t\tuint32Array1 = bufferStack1.uint32Array,\n\t\tuint16Array1 = bufferStack1.uint16Array,\n\t\tfloat32Array2 = bufferStack2.float32Array,\n\t\tuint32Array2 = bufferStack2.uint32Array,\n\t\tuint16Array2 = bufferStack2.uint16Array;\n\n\tconst node1Index16 = node1Index32 * 2;\n\tconst node2Index16 = node2Index32 * 2;\n\tconst isLeaf1 = IS_LEAF( node1Index16, uint16Array1 );\n\tconst isLeaf2 = IS_LEAF( node2Index16, uint16Array2 );\n\tlet result = false;\n\tif ( isLeaf2 && isLeaf1 ) {\n\n\t\t// if both bounds are leaf nodes then fire the callback if the boxes intersect\n\t\t// Note the \"nodeIndex\" values are just intended to be used as unique identifiers in the tree and\n\t\t// not used for accessing data\n\t\tif ( reversed ) {\n\n\t\t\tresult = intersectsRangesFunc(\n\t\t\t\tOFFSET( node2Index32, uint32Array2 ), COUNT( node2Index32 * 2, uint16Array2 ),\n\t\t\t\tOFFSET( node1Index32, uint32Array1 ), COUNT( node1Index32 * 2, uint16Array1 ),\n\t\t\t\tdepth2, node2IndexOffset + node2Index32 / UINT32_PER_NODE,\n\t\t\t\tdepth1, node1IndexOffset + node1Index32 / UINT32_PER_NODE,\n\t\t\t);\n\n\t\t} else {\n\n\t\t\tresult = intersectsRangesFunc(\n\t\t\t\tOFFSET( node1Index32, uint32Array1 ), COUNT( node1Index32 * 2, uint16Array1 ),\n\t\t\t\tOFFSET( node2Index32, uint32Array2 ), COUNT( node2Index32 * 2, uint16Array2 ),\n\t\t\t\tdepth1, node1IndexOffset + node1Index32 / UINT32_PER_NODE,\n\t\t\t\tdepth2, node2IndexOffset + node2Index32 / UINT32_PER_NODE,\n\t\t\t);\n\n\t\t}\n\n\t} else if ( isLeaf2 ) {\n\n\t\t// SWAP\n\t\t// If we've traversed to the leaf node on the other bvh then we need to swap over\n\t\t// to traverse down the first one\n\n\t\t// get the new box to use\n\t\tconst newBox = _boxPool.getPrimitive();\n\t\tarrayToBox( BOUNDING_DATA_INDEX( node2Index32 ), float32Array2, newBox );\n\t\tnewBox.applyMatrix4( matrix2to1 );\n\n\t\t// get the child bounds to check before traversal\n\t\tconst cl1 = LEFT_NODE( node1Index32 );\n\t\tconst cr1 = RIGHT_NODE( node1Index32, uint32Array1 );\n\t\tarrayToBox( BOUNDING_DATA_INDEX( cl1 ), float32Array1, _leftBox1 );\n\t\tarrayToBox( BOUNDING_DATA_INDEX( cr1 ), float32Array1, _rightBox1 );\n\n\t\t// precompute the intersections otherwise the global boxes will be modified during traversal\n\t\tconst intersectCl1 = newBox.intersectsBox( _leftBox1 );\n\t\tconst intersectCr1 = newBox.intersectsBox( _rightBox1 );\n\t\tresult = (\n\t\t\tintersectCl1 && _traverse(\n\t\t\t\tnode2Index32, cl1, matrix1to2, matrix2to1, intersectsRangesFunc,\n\t\t\t\tnode2IndexOffset, node1IndexOffset, depth2, depth1 + 1,\n\t\t\t\tnewBox, ! reversed,\n\t\t\t)\n\t\t) || (\n\t\t\tintersectCr1 && _traverse(\n\t\t\t\tnode2Index32, cr1, matrix1to2, matrix2to1, intersectsRangesFunc,\n\t\t\t\tnode2IndexOffset, node1IndexOffset, depth2, depth1 + 1,\n\t\t\t\tnewBox, ! reversed,\n\t\t\t)\n\t\t);\n\n\t\t_boxPool.releasePrimitive( newBox );\n\n\t} else {\n\n\t\t// if neither are leaves then we should swap if one of the children does not\n\t\t// intersect with the current bounds\n\n\t\t// get the child bounds to check\n\t\tconst cl2 = LEFT_NODE( node2Index32 );\n\t\tconst cr2 = RIGHT_NODE( node2Index32, uint32Array2 );\n\t\tarrayToBox( BOUNDING_DATA_INDEX( cl2 ), float32Array2, _leftBox2 );\n\t\tarrayToBox( BOUNDING_DATA_INDEX( cr2 ), float32Array2, _rightBox2 );\n\n\t\tconst leftIntersects = currBox.intersectsBox( _leftBox2 );\n\t\tconst rightIntersects = currBox.intersectsBox( _rightBox2 );\n\t\tif ( leftIntersects && rightIntersects ) {\n\n\t\t\t// continue to traverse both children if they both intersect\n\t\t\tresult = _traverse(\n\t\t\t\tnode1Index32, cl2, matrix2to1, matrix1to2, intersectsRangesFunc,\n\t\t\t\tnode1IndexOffset, node2IndexOffset, depth1, depth2 + 1,\n\t\t\t\tcurrBox, reversed,\n\t\t\t) || _traverse(\n\t\t\t\tnode1Index32, cr2, matrix2to1, matrix1to2, intersectsRangesFunc,\n\t\t\t\tnode1IndexOffset, node2IndexOffset, depth1, depth2 + 1,\n\t\t\t\tcurrBox, reversed,\n\t\t\t);\n\n\t\t} else if ( leftIntersects ) {\n\n\t\t\tif ( isLeaf1 ) {\n\n\t\t\t\t// if the current box is a leaf then just continue\n\t\t\t\tresult = _traverse(\n\t\t\t\t\tnode1Index32, cl2, matrix2to1, matrix1to2, intersectsRangesFunc,\n\t\t\t\t\tnode1IndexOffset, node2IndexOffset, depth1, depth2 + 1,\n\t\t\t\t\tcurrBox, reversed,\n\t\t\t\t);\n\n\t\t\t} else {\n\n\t\t\t\t// SWAP\n\t\t\t\t// if only one box intersects then we have to swap to the other bvh to continue\n\t\t\t\tconst newBox = _boxPool.getPrimitive();\n\t\t\t\tnewBox.copy( _leftBox2 ).applyMatrix4( matrix2to1 );\n\n\t\t\t\tconst cl1 = LEFT_NODE( node1Index32 );\n\t\t\t\tconst cr1 = RIGHT_NODE( node1Index32, uint32Array1 );\n\t\t\t\tarrayToBox( BOUNDING_DATA_INDEX( cl1 ), float32Array1, _leftBox1 );\n\t\t\t\tarrayToBox( BOUNDING_DATA_INDEX( cr1 ), float32Array1, _rightBox1 );\n\n\t\t\t\t// precompute the intersections otherwise the global boxes will be modified during traversal\n\t\t\t\tconst intersectCl1 = newBox.intersectsBox( _leftBox1 );\n\t\t\t\tconst intersectCr1 = newBox.intersectsBox( _rightBox1 );\n\t\t\t\tresult = (\n\t\t\t\t\tintersectCl1 && _traverse(\n\t\t\t\t\t\tcl2, cl1, matrix1to2, matrix2to1, intersectsRangesFunc,\n\t\t\t\t\t\tnode2IndexOffset, node1IndexOffset, depth2, depth1 + 1,\n\t\t\t\t\t\tnewBox, ! reversed,\n\t\t\t\t\t)\n\t\t\t\t) || (\n\t\t\t\t\tintersectCr1 && _traverse(\n\t\t\t\t\t\tcl2, cr1, matrix1to2, matrix2to1, intersectsRangesFunc,\n\t\t\t\t\t\tnode2IndexOffset, node1IndexOffset, depth2, depth1 + 1,\n\t\t\t\t\t\tnewBox, ! reversed,\n\t\t\t\t\t)\n\t\t\t\t);\n\n\t\t\t\t_boxPool.releasePrimitive( newBox );\n\n\t\t\t}\n\n\t\t} else if ( rightIntersects ) {\n\n\t\t\tif ( isLeaf1 ) {\n\n\t\t\t\t// if the current box is a leaf then just continue\n\t\t\t\tresult = _traverse(\n\t\t\t\t\tnode1Index32, cr2, matrix2to1, matrix1to2, intersectsRangesFunc,\n\t\t\t\t\tnode1IndexOffset, node2IndexOffset, depth1, depth2 + 1,\n\t\t\t\t\tcurrBox, reversed,\n\t\t\t\t);\n\n\t\t\t} else {\n\n\t\t\t\t// SWAP\n\t\t\t\t// if only one box intersects then we have to swap to the other bvh to continue\n\t\t\t\tconst newBox = _boxPool.getPrimitive();\n\t\t\t\tnewBox.copy( _rightBox2 ).applyMatrix4( matrix2to1 );\n\n\t\t\t\tconst cl1 = LEFT_NODE( node1Index32 );\n\t\t\t\tconst cr1 = RIGHT_NODE( node1Index32, uint32Array1 );\n\t\t\t\tarrayToBox( BOUNDING_DATA_INDEX( cl1 ), float32Array1, _leftBox1 );\n\t\t\t\tarrayToBox( BOUNDING_DATA_INDEX( cr1 ), float32Array1, _rightBox1 );\n\n\t\t\t\t// precompute the intersections otherwise the global boxes will be modified during traversal\n\t\t\t\tconst intersectCl1 = newBox.intersectsBox( _leftBox1 );\n\t\t\t\tconst intersectCr1 = newBox.intersectsBox( _rightBox1 );\n\t\t\t\tresult = (\n\t\t\t\t\tintersectCl1 && _traverse(\n\t\t\t\t\t\tcr2, cl1, matrix1to2, matrix2to1, intersectsRangesFunc,\n\t\t\t\t\t\tnode2IndexOffset, node1IndexOffset, depth2, depth1 + 1,\n\t\t\t\t\t\tnewBox, ! reversed,\n\t\t\t\t\t)\n\t\t\t\t) || (\n\t\t\t\t\tintersectCr1 && _traverse(\n\t\t\t\t\t\tcr2, cr1, matrix1to2, matrix2to1, intersectsRangesFunc,\n\t\t\t\t\t\tnode2IndexOffset, node1IndexOffset, depth2, depth1 + 1,\n\t\t\t\t\t\tnewBox, ! reversed,\n\t\t\t\t\t)\n\t\t\t\t);\n\n\t\t\t\t_boxPool.releasePrimitive( newBox );\n\n\t\t\t}\n\n\t\t}\n\n\t}\n\n\treturn result;\n\n}\n\n","import { Box3 } from 'three';\nimport { BYTES_PER_NODE, UINT32_PER_NODE, DEFAULT_OPTIONS, FLOAT32_EPSILON } from './Constants.js';\nimport { arrayToBox } from '../utils/ArrayBoxUtilities.js';\nimport { IS_LEAF, LEFT_NODE, RIGHT_NODE, SPLIT_AXIS, COUNT, OFFSET } from './utils/nodeBufferUtils.js';\nimport { buildPackedTree } from './build/buildTree.js';\nimport { shapecast as shapecastFunc } from './cast/shapecast.js';\nimport { bvhcast } from './cast/bvhcast.js';\n\nconst _tempBox = /* @__PURE__ */ new Box3();\nconst _tempBuffer = /* @__PURE__ */ new Float32Array( 6 );\n\nexport class BVH {\n\n\tconstructor() {\n\n\t\tthis._roots = null;\n\t\tthis.primitiveBuffer = null;\n\t\tthis.primitiveBufferStride = null;\n\n\t}\n\n\tinit( options ) {\n\n\t\toptions = {\n\t\t\t...DEFAULT_OPTIONS,\n\t\t\t...options,\n\t\t};\n\n\t\tbuildPackedTree( this, options );\n\n\t}\n\n\tgetRootRanges( /* range */ ) {\n\n\t\t// TODO: can we avoid passing range in here?\n\t\tthrow new Error( 'BVH: getRootRanges() not implemented' );\n\n\t}\n\n\t// write the i-th primitive bounds in a 6-value min / max format to the buffer\n\t// starting at the given \"writeOffset\"\n\twritePrimitiveBounds( /* i, buffer, writeOffset */ ) {\n\n\t\tthrow new Error( 'BVH: writePrimitiveBounds() not implemented' );\n\n\t}\n\n\t// writes the union bounds of all primitives in the given range in a min / max format\n\t// to the buffer\n\twritePrimitiveRangeBounds( offset, count, targetBuffer, baseIndex ) {\n\n\t\t// Initialize bounds\n\t\tlet minX = Infinity;\n\t\tlet minY = Infinity;\n\t\tlet minZ = Infinity;\n\t\tlet maxX = - Infinity;\n\t\tlet maxY = - Infinity;\n\t\tlet maxZ = - Infinity;\n\n\t\t// compute union of all bounds\n\t\tfor ( let i = offset, end = offset + count; i < end; i ++ ) {\n\n\t\t\tthis.writePrimitiveBounds( i, _tempBuffer, 0 );\n\n\t\t\t// compute union\n\t\t\tconst [ lx, ly, lz, rx, ry, rz ] = _tempBuffer;\n\t\t\tif ( lx < minX ) minX = lx;\n\t\t\tif ( rx > maxX ) maxX = rx;\n\t\t\tif ( ly < minY ) minY = ly;\n\t\t\tif ( ry > maxY ) maxY = ry;\n\t\t\tif ( lz < minZ ) minZ = lz;\n\t\t\tif ( rz > maxZ ) maxZ = rz;\n\n\t\t}\n\n\t\t// write bounds\n\t\ttargetBuffer[ baseIndex + 0 ] = minX;\n\t\ttargetBuffer[ baseIndex + 1 ] = minY;\n\t\ttargetBuffer[ baseIndex + 2 ] = minZ;\n\t\ttargetBuffer[ baseIndex + 3 ] = maxX;\n\t\ttargetBuffer[ baseIndex + 4 ] = maxY;\n\t\ttargetBuffer[ baseIndex + 5 ] = maxZ;\n\n\t\treturn targetBuffer;\n\n\t}\n\n\tcomputePrimitiveBounds( offset, count, targetBuffer ) {\n\n\t\tconst boundsOffset = targetBuffer.offset || 0;\n\t\tfor ( let i = offset, end = offset + count; i < end; i ++ ) {\n\n\t\t\tthis.writePrimitiveBounds( i, _tempBuffer, 0 );\n\n\t\t\t// construction primitive bounds requires a center + half extents format\n\t\t\tconst [ lx, ly, lz, rx, ry, rz ] = _tempBuffer;\n\n\t\t\tconst cx = ( lx + rx ) / 2;\n\t\t\tconst cy = ( ly + ry ) / 2;\n\t\t\tconst cz = ( lz + rz ) / 2;\n\n\t\t\tconst hx = ( rx - lx ) / 2;\n\t\t\tconst hy = ( ry - ly ) / 2;\n\t\t\tconst hz = ( rz - lz ) / 2;\n\n\t\t\tconst baseIndex = ( i - boundsOffset ) * 6;\n\t\t\ttargetBuffer[ baseIndex + 0 ] = cx;\n\t\t\ttargetBuffer[ baseIndex + 1 ] = hx + ( Math.abs( cx ) + hx ) * FLOAT32_EPSILON;\n\t\t\ttargetBuffer[ baseIndex + 2 ] = cy;\n\t\t\ttargetBuffer[ baseIndex + 3 ] = hy + ( Math.abs( cy ) + hy ) * FLOAT32_EPSILON;\n\t\t\ttargetBuffer[ baseIndex + 4 ] = cz;\n\t\t\ttargetBuffer[ baseIndex + 5 ] = hz + ( Math.abs( cz ) + hz ) * FLOAT32_EPSILON;\n\n\t\t}\n\n\t\treturn targetBuffer;\n\n\t}\n\n\tshiftPrimitiveOffsets( offset ) {\n\n\t\tconst indirectBuffer = this._indirectBuffer;\n\t\tif ( indirectBuffer ) {\n\n\t\t\t// the offsets are embedded in the indirect buffer\n\t\t\tfor ( let i = 0, l = indirectBuffer.length; i < l; i ++ ) {\n\n\t\t\t\tindirectBuffer[ i ] += offset;\n\n\t\t\t}\n\n\t\t} else {\n\n\t\t\t// offsets are embedded in the leaf nodes\n\t\t\tconst roots = this._roots;\n\t\t\tfor ( let rootIndex = 0; rootIndex < roots.length; rootIndex ++ ) {\n\n\t\t\t\tconst root = roots[ rootIndex ];\n\t\t\t\tconst uint32Array = new Uint32Array( root );\n\t\t\t\tconst uint16Array = new Uint16Array( root );\n\t\t\t\tconst totalNodes = root.byteLength / BYTES_PER_NODE;\n\t\t\t\tfor ( let node = 0; node < totalNodes; node ++ ) {\n\n\t\t\t\t\tconst node32Index = UINT32_PER_NODE * node;\n\t\t\t\t\tconst node16Index = 2 * node32Index;\n\t\t\t\t\tif ( IS_LEAF( node16Index, uint16Array ) ) {\n\n\t\t\t\t\t\t// offset value\n\t\t\t\t\t\tuint32Array[ node32Index + 6 ] += offset;\n\n\t\t\t\t\t}\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t}\n\n\t}\n\n\ttraverse( callback, rootIndex = 0 ) {\n\n\t\tconst buffer = this._roots[ rootIndex ];\n\t\tconst uint32Array = new Uint32Array( buffer );\n\t\tconst uint16Array = new Uint16Array( buffer );\n\t\t_traverse( 0 );\n\n\t\tfunction _traverse( node32Index, depth = 0 ) {\n\n\t\t\tconst node16Index = node32Index * 2;\n\t\t\tconst isLeaf = IS_LEAF( node16Index, uint16Array );\n\t\t\tif ( isLeaf ) {\n\n\t\t\t\tconst offset = uint32Array[ node32Index + 6 ];\n\t\t\t\tconst count = uint16Array[ node16Index + 14 ];\n\t\t\t\tcallback( depth, isLeaf, new Float32Array( buffer, node32Index * 4, 6 ), offset, count );\n\n\t\t\t} else {\n\n\t\t\t\tconst left = LEFT_NODE( node32Index );\n\t\t\t\tconst right = RIGHT_NODE( node32Index, uint32Array );\n\t\t\t\tconst splitAxis = SPLIT_AXIS( node32Index, uint32Array );\n\t\t\t\tconst stopTraversal = callback( depth, isLeaf, new Float32Array( buffer, node32Index * 4, 6 ), splitAxis );\n\n\t\t\t\tif ( ! stopTraversal ) {\n\n\t\t\t\t\t_traverse( left, depth + 1 );\n\t\t\t\t\t_traverse( right, depth + 1 );\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t}\n\n\t}\n\n\trefit( /* nodeIndices = null */ ) {\n\n\t\t// TODO: add support for \"nodeIndices\"\n\t\t// if ( nodeIndices && Array.isArray( nodeIndices ) ) {\n\n\t\t// \tnodeIndices = new Set( nodeIndices );\n\n\t\t// }\n\n\t\tconst roots = this._roots;\n\t\tfor ( let rootIndex = 0, rootCount = roots.length; rootIndex < rootCount; rootIndex ++ ) {\n\n\t\t\tconst bu