rayzee 4.8.4 → 4.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -82
- package/dist/assets/AIUpscalerWorker-D58dcMrY.js +2 -0
- package/dist/assets/AIUpscalerWorker-D58dcMrY.js.map +1 -0
- package/dist/assets/BVHRefitWorker-GkmNJYvb.js +2 -0
- package/dist/assets/BVHRefitWorker-GkmNJYvb.js.map +1 -0
- package/dist/assets/BVHSubtreeWorker-C02ZWVeG.js +2 -0
- package/dist/assets/BVHSubtreeWorker-C02ZWVeG.js.map +1 -0
- package/dist/assets/BVHWorker-DobVXMda.js +2 -0
- package/dist/assets/BVHWorker-DobVXMda.js.map +1 -0
- package/dist/assets/CDFWorker-2MoynL4F.js +2 -0
- package/dist/assets/CDFWorker-2MoynL4F.js.map +1 -0
- package/dist/assets/TexturesWorker-DBqGmVdR.js +2 -0
- package/dist/assets/TexturesWorker-DBqGmVdR.js.map +1 -0
- package/dist/rayzee.es.js +922 -871
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +43 -43
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/Passes/AIUpscaler.js +1 -2
- package/src/PathTracerApp.js +36 -3
- package/src/Processor/AssetLoader.js +2 -2
- package/src/Processor/BVHBuilder.js +1 -2
- package/src/Processor/EquirectHDRInfo.js +1 -2
- package/src/Processor/LightSerializer.js +26 -7
- package/src/Processor/ParallelBVHBuilder.js +8 -9
- package/src/Processor/SceneProcessor.js +3 -4
- package/src/Processor/TextureCreator.js +1 -2
- package/src/Processor/Workers/AIUpscalerWorker.js +21 -7
- package/src/README.md +1 -2
- package/src/Stages/PathTracer.js +7 -6
- package/src/TSL/LightsCore.js +12 -2
- package/src/TSL/LightsSampling.js +8 -6
- package/src/managers/LightManager.js +1 -1
- package/src/managers/UniformManager.js +2 -2
- package/src/Processor/createWorker.js +0 -38
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BVHSubtreeWorker-C02ZWVeG.js","names":[],"sources":["../../src/Processor/TreeletOptimizer.js","../../src/Processor/ReinsertionOptimizer.js","../../src/Processor/BVHBuilder.js","../../src/Processor/Workers/BVHSubtreeWorker.js"],"sourcesContent":["// No external dependencies — uses inline float fields for bounds (no Vector3)\n\n/**\n * Treelet optimization for BVH trees (Bittner et al. 2013).\n *\n * For each small subtree (\"treelet\") of N leaves, enumerates all\n * Catalan(N-1) distinct binary tree topologies, tries all leaf\n * permutations (or greedy assignment for N > 5), and replaces the\n * subtree with the arrangement that minimises SAH cost.\n */\nexport class TreeletOptimizer {\n\n\tconstructor( traversalCost, intersectionCost ) {\n\n\t\tthis.traversalCost = traversalCost;\n\t\tthis.intersectionCost = intersectionCost;\n\t\tthis.maxTreeletLeaves = 7;\n\t\tthis.minImprovement = 0.02; // relative improvement threshold\n\n\t\t// Precompute topologies for leaf counts 3..maxTreeletLeaves\n\t\tthis.topologyCache = new Map();\n\t\tfor ( let n = 3; n <= this.maxTreeletLeaves; n ++ ) {\n\n\t\t\tthis.topologyCache.set( n, this.generateTopologies( n ) );\n\n\t\t}\n\n\t\t// Stats\n\t\tthis.stats = {\n\t\t\ttreeletsProcessed: 0,\n\t\t\ttreeletsImproved: 0,\n\t\t\ttotalSAHImprovement: 0,\n\t\t\taverageSAHImprovement: 0,\n\t\t\toptimizationTime: 0\n\t\t};\n\n\t}\n\n\t// ------------------------------------------------------------------\n\t// Topology generation — nested binary trees via Catalan decomposition\n\t// ------------------------------------------------------------------\n\n\t/**\n\t * Generate all distinct binary tree topologies for `n` leaves.\n\t * Leaves are represented as integers 0..n-1 (slot indices).\n\t * Internal nodes are 2-element arrays [left, right].\n\t *\n\t * Examples for n=3 (Catalan(2)=2):\n\t * [[0,1],2] and [0,[1,2]]\n\t */\n\tgenerateTopologies( n ) {\n\n\t\tif ( n === 1 ) return [ 0 ];\n\t\tif ( n === 2 ) return [[ 0, 1 ]];\n\n\t\tconst results = [];\n\n\t\t// Split n leaves into left (k) and right (n-k) groups.\n\t\t// Base cases return arrays of topologies:\n\t\t// n=1 → [0] (one topology: leaf 0)\n\t\t// n=2 → [[0,1]] (one topology: pair [0,1])\n\t\t// So `for (const t of topos)` yields individual topologies in all cases.\n\t\tfor ( let k = 1; k < n; k ++ ) {\n\n\t\t\tconst leftTopos = this.generateTopologies( k );\n\t\t\tconst rightTopos = this.generateTopologies( n - k );\n\n\t\t\tfor ( const lt of leftTopos ) {\n\n\t\t\t\tfor ( const rt of rightTopos ) {\n\n\t\t\t\t\tresults.push( [ lt, this.offsetTopology( rt, k ) ] );\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t}\n\n\t\treturn results;\n\n\t}\n\n\t/**\n\t * Offset all leaf indices in a topology by `offset`.\n\t */\n\toffsetTopology( topo, offset ) {\n\n\t\tif ( typeof topo === 'number' ) return topo + offset;\n\t\treturn [ this.offsetTopology( topo[ 0 ], offset ), this.offsetTopology( topo[ 1 ], offset ) ];\n\n\t}\n\n\t// ------------------------------------------------------------------\n\t// Main entry point\n\t// ------------------------------------------------------------------\n\n\toptimizeBVH( bvhRoot ) {\n\n\t\tconst startTime = performance.now();\n\t\tconst maxTime = 30000; // 30s safety timeout\n\n\t\tthis.stats = {\n\t\t\ttreeletsProcessed: 0,\n\t\t\ttreeletsImproved: 0,\n\t\t\ttotalSAHImprovement: 0,\n\t\t\taverageSAHImprovement: 0,\n\t\t\toptimizationTime: 0\n\t\t};\n\n\t\t// Identify all treelet roots (bottom-up, non-overlapping)\n\t\tconst treeletRoots = this.identifyTreeletRoots( bvhRoot );\n\n\t\tfor ( let i = 0; i < treeletRoots.length; i ++ ) {\n\n\t\t\tif ( performance.now() - startTime > maxTime ) {\n\n\t\t\t\tconsole.warn( `TreeletOptimizer: timeout after ${i}/${treeletRoots.length} treelets` );\n\t\t\t\tbreak;\n\n\t\t\t}\n\n\t\t\tthis.optimizeTreelet( treeletRoots[ i ] );\n\n\t\t}\n\n\t\tthis.stats.optimizationTime = performance.now() - startTime;\n\t\tthis.stats.averageSAHImprovement = this.stats.treeletsProcessed > 0\n\t\t\t? this.stats.totalSAHImprovement / this.stats.treeletsProcessed\n\t\t\t: 0;\n\n\t\treturn bvhRoot;\n\n\t}\n\n\t// ------------------------------------------------------------------\n\t// Treelet identification — bottom-up, non-overlapping\n\t// ------------------------------------------------------------------\n\n\tidentifyTreeletRoots( bvhRoot ) {\n\n\t\tconst roots = [];\n\t\tconst processed = new Set();\n\n\t\t// Post-order traversal: children before parents so we process\n\t\t// bottom-up and skip subtrees already covered by a deeper treelet.\n\t\tconst stack = [ { node: bvhRoot, visited: false } ];\n\n\t\twhile ( stack.length > 0 ) {\n\n\t\t\tconst top = stack[ stack.length - 1 ];\n\n\t\t\tif ( top.visited ) {\n\n\t\t\t\tstack.pop();\n\t\t\t\tconst node = top.node;\n\n\t\t\t\t// Skip leaves\n\t\t\t\tif ( node.triangleCount > 0 ) continue;\n\t\t\t\t// Skip if already inside a processed treelet\n\t\t\t\tif ( processed.has( node ) ) continue;\n\n\t\t\t\tconst leafCount = this.countLeaves( node );\n\t\t\t\tif ( leafCount >= 3 && leafCount <= this.maxTreeletLeaves ) {\n\n\t\t\t\t\troots.push( node );\n\t\t\t\t\tthis.markSubtree( node, processed );\n\n\t\t\t\t}\n\n\t\t\t} else {\n\n\t\t\t\ttop.visited = true;\n\t\t\t\tconst node = top.node;\n\t\t\t\tif ( node.triangleCount > 0 ) continue;\n\t\t\t\tif ( node.rightChild ) stack.push( { node: node.rightChild, visited: false } );\n\t\t\t\tif ( node.leftChild ) stack.push( { node: node.leftChild, visited: false } );\n\n\t\t\t}\n\n\t\t}\n\n\t\treturn roots;\n\n\t}\n\n\tcountLeaves( node ) {\n\n\t\tif ( ! node ) return 0;\n\t\tif ( node.triangleCount > 0 ) return 1;\n\t\treturn this.countLeaves( node.leftChild ) + this.countLeaves( node.rightChild );\n\n\t}\n\n\tmarkSubtree( node, set ) {\n\n\t\tif ( ! node ) return;\n\t\tset.add( node );\n\t\tif ( node.triangleCount > 0 ) return;\n\t\tthis.markSubtree( node.leftChild, set );\n\t\tthis.markSubtree( node.rightChild, set );\n\n\t}\n\n\t// ------------------------------------------------------------------\n\t// Single treelet optimization\n\t// ------------------------------------------------------------------\n\n\toptimizeTreelet( treeletRoot ) {\n\n\t\t// Extract leaves\n\t\tconst leaves = [];\n\t\tthis.extractLeaves( treeletRoot, leaves );\n\t\tconst n = leaves.length;\n\n\t\tif ( n < 3 || n > this.maxTreeletLeaves ) return;\n\n\t\tthis.stats.treeletsProcessed ++;\n\n\t\tconst originalCost = this.evaluateSubtreeSAH( treeletRoot );\n\t\tconst topologies = this.topologyCache.get( n );\n\t\tif ( ! topologies || topologies.length === 0 ) return;\n\n\t\tlet bestCost = originalCost;\n\t\tlet bestTopo = null;\n\t\tlet bestPerm = null;\n\n\t\tif ( n <= 5 ) {\n\n\t\t\t// Full permutation search — feasible for n<=5 (max 120 perms × 14 topos)\n\t\t\tconst perms = this.generatePermutations( n );\n\n\t\t\tfor ( const topo of topologies ) {\n\n\t\t\t\tfor ( const perm of perms ) {\n\n\t\t\t\t\tconst cost = this.evaluateTopology( topo, leaves, perm );\n\t\t\t\t\tif ( cost < bestCost ) {\n\n\t\t\t\t\t\tbestCost = cost;\n\t\t\t\t\t\tbestTopo = topo;\n\t\t\t\t\t\tbestPerm = perm;\n\n\t\t\t\t\t}\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t} else {\n\n\t\t\t// Greedy assignment for n>5: for each topology, use identity perm\n\t\t\t// plus a few SAH-guided swaps\n\t\t\tconst identityPerm = Array.from( { length: n }, ( _, i ) => i );\n\n\t\t\tfor ( const topo of topologies ) {\n\n\t\t\t\t// Try identity permutation\n\t\t\t\tconst cost = this.evaluateTopology( topo, leaves, identityPerm );\n\t\t\t\tif ( cost < bestCost ) {\n\n\t\t\t\t\tbestCost = cost;\n\t\t\t\t\tbestTopo = topo;\n\t\t\t\t\tbestPerm = identityPerm;\n\n\t\t\t\t}\n\n\t\t\t\t// Try greedy swap improvements\n\t\t\t\tconst result = this.greedySwapOptimize( topo, leaves, identityPerm, cost );\n\t\t\t\tif ( result.cost < bestCost ) {\n\n\t\t\t\t\tbestCost = result.cost;\n\t\t\t\t\tbestTopo = topo;\n\t\t\t\t\tbestPerm = result.perm;\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t}\n\n\t\t// Apply if relative improvement exceeds threshold\n\t\tconst relativeImprovement = ( originalCost - bestCost ) / originalCost;\n\t\tif ( bestTopo && relativeImprovement > this.minImprovement ) {\n\n\t\t\tthis.reconstructTreelet( treeletRoot, bestTopo, leaves, bestPerm );\n\t\t\tthis.stats.treeletsImproved ++;\n\t\t\tthis.stats.totalSAHImprovement += relativeImprovement;\n\n\t\t}\n\n\t}\n\n\textractLeaves( node, leaves ) {\n\n\t\tif ( ! node ) return;\n\n\t\tif ( node.triangleCount > 0 ) {\n\n\t\t\t// BVHNode uses inline floats (minX/maxX etc.)\n\t\t\tleaves.push( {\n\t\t\t\tminX: node.minX, minY: node.minY, minZ: node.minZ,\n\t\t\t\tmaxX: node.maxX, maxY: node.maxY, maxZ: node.maxZ,\n\t\t\t\ttriangleOffset: node.triangleOffset,\n\t\t\t\ttriangleCount: node.triangleCount\n\t\t\t} );\n\t\t\treturn;\n\n\t\t}\n\n\t\tthis.extractLeaves( node.leftChild, leaves );\n\t\tthis.extractLeaves( node.rightChild, leaves );\n\n\t}\n\n\t// ------------------------------------------------------------------\n\t// SAH evaluation\n\t// ------------------------------------------------------------------\n\n\tevaluateSubtreeSAH( node ) {\n\n\t\tif ( ! node ) return 0;\n\n\t\tif ( node.triangleCount > 0 ) {\n\n\t\t\treturn this.surfaceAreaFlat( node.minX, node.minY, node.minZ, node.maxX, node.maxY, node.maxZ ) * node.triangleCount * this.intersectionCost;\n\n\t\t}\n\n\t\tconst leftCost = this.evaluateSubtreeSAH( node.leftChild );\n\t\tconst rightCost = this.evaluateSubtreeSAH( node.rightChild );\n\t\tconst sa = this.surfaceAreaFlat( node.minX, node.minY, node.minZ, node.maxX, node.maxY, node.maxZ );\n\t\treturn sa * this.traversalCost + leftCost + rightCost;\n\n\t}\n\n\t/**\n\t * Evaluate SAH cost for a topology with a given leaf permutation.\n\t * Returns { cost, boundsMin, boundsMax } for internal use, or just cost number.\n\t */\n\tevaluateTopology( topo, leaves, perm ) {\n\n\t\tconst result = this.evalTopoRecursive( topo, leaves, perm );\n\t\treturn result.cost;\n\n\t}\n\n\tevalTopoRecursive( topo, leaves, perm ) {\n\n\t\tif ( typeof topo === 'number' ) {\n\n\t\t\t// Leaf slot — look up actual leaf via permutation\n\t\t\tconst leaf = leaves[ perm[ topo ] ];\n\t\t\treturn {\n\t\t\t\tcost: this.surfaceAreaFlat( leaf.minX, leaf.minY, leaf.minZ, leaf.maxX, leaf.maxY, leaf.maxZ ) * leaf.triangleCount * this.intersectionCost,\n\t\t\t\tminX: leaf.minX, minY: leaf.minY, minZ: leaf.minZ,\n\t\t\t\tmaxX: leaf.maxX, maxY: leaf.maxY, maxZ: leaf.maxZ\n\t\t\t};\n\n\t\t}\n\n\t\tconst left = this.evalTopoRecursive( topo[ 0 ], leaves, perm );\n\t\tconst right = this.evalTopoRecursive( topo[ 1 ], leaves, perm );\n\n\t\tconst mnX = Math.min( left.minX, right.minX );\n\t\tconst mnY = Math.min( left.minY, right.minY );\n\t\tconst mnZ = Math.min( left.minZ, right.minZ );\n\t\tconst mxX = Math.max( left.maxX, right.maxX );\n\t\tconst mxY = Math.max( left.maxY, right.maxY );\n\t\tconst mxZ = Math.max( left.maxZ, right.maxZ );\n\n\t\tconst sa = this.surfaceAreaFlat( mnX, mnY, mnZ, mxX, mxY, mxZ );\n\t\treturn {\n\t\t\tcost: sa * this.traversalCost + left.cost + right.cost,\n\t\t\tminX: mnX, minY: mnY, minZ: mnZ,\n\t\t\tmaxX: mxX, maxY: mxY, maxZ: mxZ\n\t\t};\n\n\t}\n\n\tsurfaceAreaFlat( minX, minY, minZ, maxX, maxY, maxZ ) {\n\n\t\tconst dx = maxX - minX;\n\t\tconst dy = maxY - minY;\n\t\tconst dz = maxZ - minZ;\n\t\treturn 2 * ( dx * dy + dy * dz + dz * dx );\n\n\t}\n\n\t// ------------------------------------------------------------------\n\t// Permutation helpers\n\t// ------------------------------------------------------------------\n\n\tgeneratePermutations( n ) {\n\n\t\tconst result = [];\n\t\tconst arr = Array.from( { length: n }, ( _, i ) => i );\n\n\t\tconst permute = ( start ) => {\n\n\t\t\tif ( start === n ) {\n\n\t\t\t\tresult.push( [ ...arr ] );\n\t\t\t\treturn;\n\n\t\t\t}\n\n\t\t\tfor ( let i = start; i < n; i ++ ) {\n\n\t\t\t\t[ arr[ start ], arr[ i ] ] = [ arr[ i ], arr[ start ] ];\n\t\t\t\tpermute( start + 1 );\n\t\t\t\t[ arr[ start ], arr[ i ] ] = [ arr[ i ], arr[ start ] ];\n\n\t\t\t}\n\n\t\t};\n\n\t\tpermute( 0 );\n\t\treturn result;\n\n\t}\n\n\t/**\n\t * Greedy pairwise swap optimization for large treelet sizes.\n\t * Starting from an initial permutation, try all pairwise swaps\n\t * and accept any that improve cost. Repeat until no improvement.\n\t */\n\tgreedySwapOptimize( topo, leaves, initialPerm, initialCost ) {\n\n\t\tconst perm = [ ...initialPerm ];\n\t\tlet cost = initialCost;\n\t\tlet improved = true;\n\n\t\twhile ( improved ) {\n\n\t\t\timproved = false;\n\n\t\t\tfor ( let i = 0; i < perm.length - 1; i ++ ) {\n\n\t\t\t\tfor ( let j = i + 1; j < perm.length; j ++ ) {\n\n\t\t\t\t\t// Swap\n\t\t\t\t\t[ perm[ i ], perm[ j ] ] = [ perm[ j ], perm[ i ] ];\n\t\t\t\t\tconst newCost = this.evaluateTopology( topo, leaves, perm );\n\n\t\t\t\t\tif ( newCost < cost ) {\n\n\t\t\t\t\t\tcost = newCost;\n\t\t\t\t\t\timproved = true;\n\n\t\t\t\t\t} else {\n\n\t\t\t\t\t\t// Swap back\n\t\t\t\t\t\t[ perm[ i ], perm[ j ] ] = [ perm[ j ], perm[ i ] ];\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\treturn { perm, cost };\n\n\t}\n\n\t// ------------------------------------------------------------------\n\t// Reconstruction — builds proper BVHNode instances\n\t// ------------------------------------------------------------------\n\n\treconstructTreelet( treeletRoot, topo, leaves, perm ) {\n\n\t\tconst built = this.buildSubtree( topo, leaves, perm );\n\n\t\t// Copy built structure into the existing treelet root node (inline floats)\n\t\ttreeletRoot.minX = built.minX; treeletRoot.minY = built.minY; treeletRoot.minZ = built.minZ;\n\t\ttreeletRoot.maxX = built.maxX; treeletRoot.maxY = built.maxY; treeletRoot.maxZ = built.maxZ;\n\t\ttreeletRoot.leftChild = built.leftChild;\n\t\ttreeletRoot.rightChild = built.rightChild;\n\t\ttreeletRoot.triangleOffset = built.triangleOffset;\n\t\ttreeletRoot.triangleCount = built.triangleCount;\n\n\t}\n\n\t/**\n\t * Recursively build a BVHNode subtree from a topology + leaf permutation.\n\t */\n\tbuildSubtree( topo, leaves, perm ) {\n\n\t\tif ( typeof topo === 'number' ) {\n\n\t\t\t// Leaf — create a lightweight node with inline bounds\n\t\t\tconst leaf = leaves[ perm[ topo ] ];\n\t\t\tconst node = new TreeletBVHNode();\n\t\t\tnode.minX = leaf.minX; node.minY = leaf.minY; node.minZ = leaf.minZ;\n\t\t\tnode.maxX = leaf.maxX; node.maxY = leaf.maxY; node.maxZ = leaf.maxZ;\n\t\t\tnode.triangleOffset = leaf.triangleOffset;\n\t\t\tnode.triangleCount = leaf.triangleCount;\n\t\t\treturn node;\n\n\t\t}\n\n\t\tconst left = this.buildSubtree( topo[ 0 ], leaves, perm );\n\t\tconst right = this.buildSubtree( topo[ 1 ], leaves, perm );\n\n\t\tconst node = new TreeletBVHNode();\n\t\tnode.leftChild = left;\n\t\tnode.rightChild = right;\n\t\tnode.minX = Math.min( left.minX, right.minX );\n\t\tnode.minY = Math.min( left.minY, right.minY );\n\t\tnode.minZ = Math.min( left.minZ, right.minZ );\n\t\tnode.maxX = Math.max( left.maxX, right.maxX );\n\t\tnode.maxY = Math.max( left.maxY, right.maxY );\n\t\tnode.maxZ = Math.max( left.maxZ, right.maxZ );\n\n\t\treturn node;\n\n\t}\n\n\t// ------------------------------------------------------------------\n\t// Configuration\n\t// ------------------------------------------------------------------\n\n\tsetTreeletSize( size ) {\n\n\t\tthis.maxTreeletLeaves = Math.max( 3, Math.min( 7, size ) );\n\n\t\t// Regenerate topology cache if needed\n\t\tfor ( let n = 3; n <= this.maxTreeletLeaves; n ++ ) {\n\n\t\t\tif ( ! this.topologyCache.has( n ) ) {\n\n\t\t\t\tthis.topologyCache.set( n, this.generateTopologies( n ) );\n\n\t\t\t}\n\n\t\t}\n\n\t}\n\n\tsetMinImprovement( threshold ) {\n\n\t\tthis.minImprovement = Math.max( 0.001, threshold );\n\n\t}\n\n\tsetMaxTreelets() {\n\n\t\t// No-op — we process all valid treelets now.\n\t\t// Kept for API compatibility with BVHBuilder.\n\n\t}\n\n\tgetStatistics() {\n\n\t\treturn { ...this.stats };\n\n\t}\n\n}\n\n/**\n * Lightweight BVH node used during reconstruction.\n * Duck-type compatible with the main BVHNode class —\n * uses inline float fields (minX/maxX etc.).\n */\nclass TreeletBVHNode {\n\n\tconstructor() {\n\n\t\tthis.minX = 0; this.minY = 0; this.minZ = 0;\n\t\tthis.maxX = 0; this.maxY = 0; this.maxZ = 0;\n\t\tthis.leftChild = null;\n\t\tthis.rightChild = null;\n\t\tthis.triangleOffset = 0;\n\t\tthis.triangleCount = 0;\n\n\t}\n\n}\n","/**\n * ReinsertionOptimizer — BVH quality improvement via node reinsertion.\n *\n * Based on \"Parallel Reinsertion for Bounding Volume Hierarchy Optimization\"\n * by Meister & Bittner, adapted from madmann91/bvh (MIT).\n *\n * Algorithm:\n * 1. Find the top N nodes by surface area (highest traversal cost).\n * 2. For each candidate, search the tree for the optimal reinsertion target\n * using branch-and-bound pruning.\n * 3. Sort reinsertions by area improvement, apply non-conflicting ones greedily.\n * 4. Repeat for a configurable number of iterations.\n *\n * Typically yields 10-20% SAH cost reduction on top of treelet optimization.\n */\nexport class ReinsertionOptimizer {\n\n\tconstructor( traversalCost, intersectionCost ) {\n\n\t\tthis.traversalCost = traversalCost;\n\t\tthis.intersectionCost = intersectionCost;\n\n\t\t// Fraction of total nodes to consider per iteration\n\t\tthis.batchSizeRatio = 0.02;\n\n\t\t// Maximum optimization iterations\n\t\tthis.maxIterations = 2;\n\n\t\t// Time budget (ms) — abort if exceeded\n\t\tthis.timeBudgetMs = 15000;\n\n\t\t// Statistics\n\t\tthis.stats = { reinsertionsApplied: 0, iterations: 0, timeMs: 0 };\n\n\t}\n\n\tsetBatchSizeRatio( ratio ) {\n\n\t\tthis.batchSizeRatio = Math.max( 0.005, Math.min( 0.1, ratio ) );\n\n\t}\n\n\tsetMaxIterations( n ) {\n\n\t\tthis.maxIterations = Math.max( 1, Math.min( 5, n ) );\n\n\t}\n\n\tgetStatistics() {\n\n\t\treturn { ...this.stats };\n\n\t}\n\n\t// --- Surface area (half SA, consistent with TreeletOptimizer) ---\n\n\tsurfaceArea( node ) {\n\n\t\tconst dx = node.maxX - node.minX;\n\t\tconst dy = node.maxY - node.minY;\n\t\tconst dz = node.maxZ - node.minZ;\n\t\treturn dx * dy + dy * dz + dz * dx;\n\n\t}\n\n\t// --- Parent map: node → { parent, isLeft } ---\n\n\tbuildParentMap( root ) {\n\n\t\tconst map = new Map();\n\t\tmap.set( root, { parent: null, isLeft: false } );\n\n\t\tconst stack = [ root ];\n\t\twhile ( stack.length > 0 ) {\n\n\t\t\tconst node = stack.pop();\n\t\t\tif ( node.triangleCount > 0 ) continue;\n\n\t\t\tif ( node.leftChild ) {\n\n\t\t\t\tmap.set( node.leftChild, { parent: node, isLeft: true } );\n\t\t\t\tstack.push( node.leftChild );\n\n\t\t\t}\n\n\t\t\tif ( node.rightChild ) {\n\n\t\t\t\tmap.set( node.rightChild, { parent: node, isLeft: false } );\n\t\t\t\tstack.push( node.rightChild );\n\n\t\t\t}\n\n\t\t}\n\n\t\treturn map;\n\n\t}\n\n\t// --- Candidate selection: top N nodes by surface area (min-heap) ---\n\n\tfindCandidates( root, targetCount, parentMap ) {\n\n\t\t// Collect all non-root nodes (skip root itself and its direct children\n\t\t// whose reinsertion would require mutating root identity)\n\t\tconst heap = []; // min-heap by cost (surface area)\n\n\t\tconst stack = [ root ];\n\t\twhile ( stack.length > 0 ) {\n\n\t\t\tconst node = stack.pop();\n\n\t\t\tif ( node !== root ) {\n\n\t\t\t\tconst info = parentMap.get( node );\n\t\t\t\t// Skip direct children of root to avoid root mutation complexity\n\t\t\t\tif ( info.parent !== root ) {\n\n\t\t\t\t\tconst cost = this.surfaceArea( node );\n\n\t\t\t\t\tif ( heap.length < targetCount ) {\n\n\t\t\t\t\t\theap.push( { node, cost } );\n\t\t\t\t\t\tif ( heap.length === targetCount ) this._heapify( heap );\n\n\t\t\t\t\t} else if ( cost > heap[ 0 ].cost ) {\n\n\t\t\t\t\t\theap[ 0 ] = { node, cost };\n\t\t\t\t\t\tthis._siftDown( heap, 0 );\n\n\t\t\t\t\t}\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\tif ( node.triangleCount === 0 ) {\n\n\t\t\t\tif ( node.leftChild ) stack.push( node.leftChild );\n\t\t\t\tif ( node.rightChild ) stack.push( node.rightChild );\n\n\t\t\t}\n\n\t\t}\n\n\t\treturn heap;\n\n\t}\n\n\t// Min-heap helpers (smallest cost at index 0)\n\t_heapify( heap ) {\n\n\t\tfor ( let i = ( heap.length >> 1 ) - 1; i >= 0; i -- ) this._siftDown( heap, i );\n\n\t}\n\n\t_siftDown( heap, i ) {\n\n\t\tconst n = heap.length;\n\t\twhile ( true ) {\n\n\t\t\tlet smallest = i;\n\t\t\tconst l = 2 * i + 1;\n\t\t\tconst r = 2 * i + 2;\n\t\t\tif ( l < n && heap[ l ].cost < heap[ smallest ].cost ) smallest = l;\n\t\t\tif ( r < n && heap[ r ].cost < heap[ smallest ].cost ) smallest = r;\n\t\t\tif ( smallest === i ) break;\n\t\t\tconst tmp = heap[ i ];\n\t\t\theap[ i ] = heap[ smallest ];\n\t\t\theap[ smallest ] = tmp;\n\t\t\ti = smallest;\n\n\t\t}\n\n\t}\n\n\t// --- Find best reinsertion target for a candidate node ---\n\n\tfindReinsertion( nodeA, root, parentMap ) {\n\n\t\tconst aInfo = parentMap.get( nodeA );\n\t\tconst parentA = aInfo.parent;\n\t\tif ( ! parentA ) return null;\n\n\t\tconst siblingA = aInfo.isLeft ? parentA.rightChild : parentA.leftChild;\n\n\t\tconst nodeArea = this.surfaceArea( nodeA );\n\t\tconst parentArea = this.surfaceArea( parentA );\n\n\t\tlet bestTo = null;\n\t\tlet bestAreaDiff = 0;\n\n\t\t// areaDiff accumulates the net area savings from removing nodeA\n\t\t// along the path from its parent to the current pivot level\n\t\tlet areaDiff = parentArea;\n\n\t\t// pivotBbox tracks the combined bounds of everything except nodeA\n\t\t// along the path from siblingA upward\n\t\tlet pbMinX = siblingA.minX, pbMinY = siblingA.minY, pbMinZ = siblingA.minZ;\n\t\tlet pbMaxX = siblingA.maxX, pbMaxY = siblingA.maxY, pbMaxZ = siblingA.maxZ;\n\n\t\tlet siblingNode = siblingA;\n\t\tlet pivotNode = parentA;\n\n\t\tconst searchStack = [];\n\n\t\tdo {\n\n\t\t\t// Search sibling subtree at current level\n\t\t\tsearchStack.length = 0;\n\t\t\tsearchStack.push( areaDiff, siblingNode );\n\n\t\t\twhile ( searchStack.length > 0 ) {\n\n\t\t\t\t// Pop node and areaDiff (stored as pairs: [areaDiff, node, areaDiff, node, ...])\n\t\t\t\tconst topNode = searchStack.pop();\n\t\t\t\tconst topAreaDiff = searchStack.pop();\n\n\t\t\t\t// Prune: upper bound on improvement can't beat current best\n\t\t\t\tif ( topAreaDiff - nodeArea <= bestAreaDiff ) continue;\n\n\t\t\t\t// Compute merged area if we insert nodeA next to this target\n\t\t\t\tconst mMinX = Math.min( topNode.minX, nodeA.minX );\n\t\t\t\tconst mMinY = Math.min( topNode.minY, nodeA.minY );\n\t\t\t\tconst mMinZ = Math.min( topNode.minZ, nodeA.minZ );\n\t\t\t\tconst mMaxX = Math.max( topNode.maxX, nodeA.maxX );\n\t\t\t\tconst mMaxY = Math.max( topNode.maxY, nodeA.maxY );\n\t\t\t\tconst mMaxZ = Math.max( topNode.maxZ, nodeA.maxZ );\n\t\t\t\tconst mdx = mMaxX - mMinX, mdy = mMaxY - mMinY, mdz = mMaxZ - mMinZ;\n\t\t\t\tconst mergedArea = mdx * mdy + mdy * mdz + mdz * mdx;\n\n\t\t\t\tconst reinsertArea = topAreaDiff - mergedArea;\n\n\t\t\t\tif ( reinsertArea > bestAreaDiff ) {\n\n\t\t\t\t\tbestTo = topNode;\n\t\t\t\t\tbestAreaDiff = reinsertArea;\n\n\t\t\t\t}\n\n\t\t\t\t// Descend into children if inner node\n\t\t\t\tif ( topNode.triangleCount === 0 && topNode.leftChild && topNode.rightChild ) {\n\n\t\t\t\t\tconst childArea = reinsertArea + this.surfaceArea( topNode );\n\t\t\t\t\tsearchStack.push( childArea, topNode.leftChild );\n\t\t\t\t\tsearchStack.push( childArea, topNode.rightChild );\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\t// Move up one level\n\t\t\tconst pivotInfo = parentMap.get( pivotNode );\n\t\t\tif ( ! pivotInfo || pivotInfo.parent === null ) break;\n\n\t\t\t// Update pivot bbox: accumulate sibling bounds at each level above parentA\n\t\t\tif ( pivotNode !== parentA ) {\n\n\t\t\t\tpbMinX = Math.min( pbMinX, siblingNode.minX );\n\t\t\t\tpbMinY = Math.min( pbMinY, siblingNode.minY );\n\t\t\t\tpbMinZ = Math.min( pbMinZ, siblingNode.minZ );\n\t\t\t\tpbMaxX = Math.max( pbMaxX, siblingNode.maxX );\n\t\t\t\tpbMaxY = Math.max( pbMaxY, siblingNode.maxY );\n\t\t\t\tpbMaxZ = Math.max( pbMaxZ, siblingNode.maxZ );\n\n\t\t\t\tconst pdx = pbMaxX - pbMinX, pdy = pbMaxY - pbMinY, pdz = pbMaxZ - pbMinZ;\n\t\t\t\tconst pivotBboxArea = pdx * pdy + pdy * pdz + pdz * pdx;\n\t\t\t\tareaDiff += this.surfaceArea( pivotNode ) - pivotBboxArea;\n\n\t\t\t}\n\n\t\t\t// Get sibling of pivot at next ancestor level\n\t\t\tconst pivotParent = pivotInfo.parent;\n\t\t\tsiblingNode = pivotInfo.isLeft ? pivotParent.rightChild : pivotParent.leftChild;\n\t\t\tpivotNode = pivotParent;\n\n\t\t} while ( parentMap.get( pivotNode ).parent !== null );\n\n\t\t// Reject trivial reinsertions (same position)\n\t\tif ( bestTo === siblingA || bestTo === parentA ) return null;\n\n\t\treturn bestTo ? { from: nodeA, to: bestTo, areaDiff: bestAreaDiff } : null;\n\n\t}\n\n\t// --- Conflict detection: nodes involved in a reinsertion ---\n\n\tgetConflicts( from, to, parentMap ) {\n\n\t\tconst aInfo = parentMap.get( from );\n\t\tconst siblingA = aInfo.isLeft ? aInfo.parent.rightChild : aInfo.parent.leftChild;\n\n\t\treturn [\n\t\t\tto,\n\t\t\tfrom,\n\t\t\tsiblingA,\n\t\t\tparentMap.get( to ).parent,\n\t\t\taInfo.parent\n\t\t];\n\n\t}\n\n\t// --- Perform the actual tree surgery ---\n\n\treinsertNode( nodeA, targetC, parentMap ) {\n\n\t\tconst aInfo = parentMap.get( nodeA );\n\t\tconst parentA = aInfo.parent;\n\t\tconst siblingA = aInfo.isLeft ? parentA.rightChild : parentA.leftChild;\n\n\t\tconst parentAInfo = parentMap.get( parentA );\n\t\tconst grandparent = parentAInfo.parent;\n\n\t\tconst cInfo = parentMap.get( targetC );\n\t\tconst targetParent = cInfo.parent;\n\n\t\t// Step 1: Remove parentA from grandparent, replace with siblingA\n\t\tif ( parentAInfo.isLeft ) {\n\n\t\t\tgrandparent.leftChild = siblingA;\n\n\t\t} else {\n\n\t\t\tgrandparent.rightChild = siblingA;\n\n\t\t}\n\n\t\t// Step 2: Reuse parentA as new parent of (nodeA, targetC)\n\t\tparentA.leftChild = nodeA;\n\t\tparentA.rightChild = targetC;\n\t\tparentA.triangleOffset = 0;\n\t\tparentA.triangleCount = 0;\n\t\tparentA.minX = Math.min( nodeA.minX, targetC.minX );\n\t\tparentA.minY = Math.min( nodeA.minY, targetC.minY );\n\t\tparentA.minZ = Math.min( nodeA.minZ, targetC.minZ );\n\t\tparentA.maxX = Math.max( nodeA.maxX, targetC.maxX );\n\t\tparentA.maxY = Math.max( nodeA.maxY, targetC.maxY );\n\t\tparentA.maxZ = Math.max( nodeA.maxZ, targetC.maxZ );\n\n\t\t// Step 3: Place parentA where targetC was\n\t\tif ( cInfo.isLeft ) {\n\n\t\t\ttargetParent.leftChild = parentA;\n\n\t\t} else {\n\n\t\t\ttargetParent.rightChild = parentA;\n\n\t\t}\n\n\t\t// Step 4: Update parent map\n\t\tparentMap.set( siblingA, { parent: grandparent, isLeft: parentAInfo.isLeft } );\n\t\tparentMap.set( parentA, { parent: targetParent, isLeft: cInfo.isLeft } );\n\t\tparentMap.set( nodeA, { parent: parentA, isLeft: true } );\n\t\tparentMap.set( targetC, { parent: parentA, isLeft: false } );\n\n\t\t// Step 5: Refit bounds from removal point (grandparent) up to root\n\t\tthis.refitFrom( grandparent, parentMap );\n\n\t\t// Step 6: Refit bounds from insertion point (targetParent) up to root\n\t\tthis.refitFrom( targetParent, parentMap );\n\n\t}\n\n\t// --- Refit bounding boxes from a node up to root ---\n\n\trefitFrom( node, parentMap ) {\n\n\t\tlet current = node;\n\t\twhile ( current ) {\n\n\t\t\tif ( current.triangleCount === 0 && current.leftChild && current.rightChild ) {\n\n\t\t\t\tconst L = current.leftChild;\n\t\t\t\tconst R = current.rightChild;\n\t\t\t\tcurrent.minX = Math.min( L.minX, R.minX );\n\t\t\t\tcurrent.minY = Math.min( L.minY, R.minY );\n\t\t\t\tcurrent.minZ = Math.min( L.minZ, R.minZ );\n\t\t\t\tcurrent.maxX = Math.max( L.maxX, R.maxX );\n\t\t\t\tcurrent.maxY = Math.max( L.maxY, R.maxY );\n\t\t\t\tcurrent.maxZ = Math.max( L.maxZ, R.maxZ );\n\n\t\t\t}\n\n\t\t\tconst info = parentMap.get( current );\n\t\t\tcurrent = info ? info.parent : null;\n\n\t\t}\n\n\t}\n\n\t// --- Main entry point ---\n\n\toptimizeBVH( root, progressCallback ) {\n\n\t\tconst startTime = performance.now();\n\t\tthis.stats = { reinsertionsApplied: 0, iterations: 0, timeMs: 0 };\n\n\t\tfor ( let iter = 0; iter < this.maxIterations; iter ++ ) {\n\n\t\t\tif ( performance.now() - startTime > this.timeBudgetMs ) break;\n\n\t\t\t// Rebuild parent map each iteration (tree structure changes)\n\t\t\tconst parentMap = this.buildParentMap( root );\n\t\t\tconst nodeCount = parentMap.size;\n\t\t\tconst targetCount = Math.max( 1, Math.floor( nodeCount * this.batchSizeRatio ) );\n\n\t\t\tif ( progressCallback ) {\n\n\t\t\t\tprogressCallback( `Reinsertion iter ${iter + 1}/${this.maxIterations}: selecting ${targetCount} candidates` );\n\n\t\t\t}\n\n\t\t\t// Find highest-cost candidates\n\t\t\tconst candidates = this.findCandidates( root, targetCount, parentMap );\n\n\t\t\t// Find best reinsertion for each candidate\n\t\t\tconst reinsertions = [];\n\t\t\tfor ( let i = 0; i < candidates.length; i ++ ) {\n\n\t\t\t\tif ( performance.now() - startTime > this.timeBudgetMs ) break;\n\n\t\t\t\tconst r = this.findReinsertion( candidates[ i ].node, root, parentMap );\n\t\t\t\tif ( r && r.areaDiff > 0 ) {\n\n\t\t\t\t\treinsertions.push( r );\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\t// Sort by improvement (largest first) and apply greedily\n\t\t\treinsertions.sort( ( a, b ) => b.areaDiff - a.areaDiff );\n\n\t\t\tconst touched = new Set();\n\t\t\tlet applied = 0;\n\n\t\t\tfor ( const r of reinsertions ) {\n\n\t\t\t\tconst conflicts = this.getConflicts( r.from, r.to, parentMap );\n\t\t\t\tif ( conflicts.some( n => touched.has( n ) ) ) continue;\n\n\t\t\t\tfor ( const n of conflicts ) touched.add( n );\n\n\t\t\t\tthis.reinsertNode( r.from, r.to, parentMap );\n\t\t\t\tapplied ++;\n\n\t\t\t}\n\n\t\t\tthis.stats.reinsertionsApplied += applied;\n\t\t\tthis.stats.iterations = iter + 1;\n\n\t\t\tif ( progressCallback ) {\n\n\t\t\t\tprogressCallback( `Reinsertion iter ${iter + 1}: applied ${applied} reinsertions` );\n\n\t\t\t}\n\n\t\t\t// No improvements found — tree is already optimal\n\t\t\tif ( applied === 0 ) break;\n\n\t\t}\n\n\t\tthis.stats.timeMs = performance.now() - startTime;\n\t\treturn this.stats;\n\n\t}\n\n}\n","import { TreeletOptimizer } from './TreeletOptimizer.js';\nimport { ReinsertionOptimizer } from './ReinsertionOptimizer.js';\n\n// Inline copy of TRIANGLE_DATA_LAYOUT (mirrors Constants.js).\n// Cannot import Constants.js because BVHBuilder runs inside BVHWorker\n// where `window` (used elsewhere in Constants.js) is not defined.\nconst TRIANGLE_DATA_LAYOUT = {\n\tFLOATS_PER_TRIANGLE: 32,\n\tPOSITION_A_OFFSET: 0,\n\tPOSITION_B_OFFSET: 4,\n\tPOSITION_C_OFFSET: 8,\n\tNORMAL_A_OFFSET: 12,\n\tNORMAL_B_OFFSET: 16,\n\tNORMAL_C_OFFSET: 20,\n\tUV_AB_OFFSET: 24,\n\tUV_C_MAT_OFFSET: 28 // vec4: uvC.x, uvC.y, materialIndex, meshIndex\n};\n\nconst FPT = TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE;\n\nclass BVHNode {\n\n\tconstructor() {\n\n\t\t// Inline floats instead of Vector3 to avoid 2M+ object allocations\n\t\tthis.minX = 0; this.minY = 0; this.minZ = 0;\n\t\tthis.maxX = 0; this.maxY = 0; this.maxZ = 0;\n\t\tthis.leftChild = null;\n\t\tthis.rightChild = null;\n\t\tthis.triangleOffset = 0;\n\t\tthis.triangleCount = 0;\n\n\t}\n\n}\n\nexport class BVHBuilder {\n\n\tconstructor() {\n\n\t\tthis.useWorker = true;\n\t\tthis.maxLeafSize = 8;\n\t\tthis.numBins = 32;\n\t\tthis.minBins = 8;\n\t\tthis.maxBins = 64;\n\t\tthis.totalNodes = 0;\n\t\tthis.processedTriangles = 0;\n\t\tthis.totalTriangles = 0;\n\t\tthis.lastProgressUpdate = 0;\n\t\tthis.progressUpdateInterval = 100;\n\n\t\t// SAH constants — GPU intersection is ~2.5x more expensive than traversal\n\t\t// (8 storage buffer fetches + Möller-Trumbore vs 4 vec4 reads + slab test)\n\t\tthis.traversalCost = 1.0;\n\t\tthis.intersectionCost = 2.5;\n\n\t\t// Morton code clustering settings\n\t\tthis.useMortonCodes = true;\n\t\tthis.mortonBits = 10;\n\t\tthis.mortonClusterThreshold = 128;\n\n\t\t// Fallback method configuration\n\t\tthis.enableObjectMedianFallback = true;\n\t\tthis.enableSpatialMedianFallback = true;\n\n\t\t// Split statistics\n\t\tthis.splitStats = {\n\t\t\tsahSplits: 0,\n\t\t\tobjectMedianSplits: 0,\n\t\t\tspatialMedianSplits: 0,\n\t\t\tfailedSplits: 0,\n\t\t\tavgBinsUsed: 0,\n\t\t\ttotalSplitAttempts: 0,\n\t\t\tmortonSortTime: 0,\n\t\t\ttotalBuildTime: 0,\n\t\t\ttreeletOptimizationTime: 0,\n\t\t\ttreeletsProcessed: 0,\n\t\t\ttreeletsImproved: 0,\n\t\t\taverageSAHImprovement: 0,\n\t\t\treinsertionOptimizationTime: 0,\n\t\t\treinsertionsApplied: 0,\n\t\t\treinsertionIterations: 0\n\t\t};\n\n\t\t// Treelet optimization configuration\n\t\tthis.enableTreeletOptimization = true;\n\t\tthis.treeletSize = 5;\n\t\tthis.treeletOptimizationPasses = 1;\n\t\tthis.treeletMinImprovement = 0.02;\n\t\tthis.maxTreeletDepth = 3;\n\t\tthis.maxTreeletsPerScene = 20;\n\t\tthis.treeletComplexityThreshold = 50000;\n\n\t\t// Reinsertion optimization configuration\n\t\tthis.enableReinsertionOptimization = true;\n\t\tthis.reinsertionBatchSizeRatio = 0.02;\n\t\tthis.reinsertionMaxIterations = 2;\n\n\t\t// Pre-allocate bin arrays\n\t\tthis.initializeBinArrays();\n\n\t\t// Reusable partition result (avoids per-node object allocation)\n\t\tthis._partResult = {\n\t\t\tmid: 0,\n\t\t\tlMinX: 0, lMinY: 0, lMinZ: 0, lMaxX: 0, lMaxY: 0, lMaxZ: 0,\n\t\t\trMinX: 0, rMinY: 0, rMinZ: 0, rMaxX: 0, rMaxY: 0, rMaxZ: 0\n\t\t};\n\n\t\t// Flat per-triangle arrays (allocated in buildSync)\n\t\tthis.centroids = null;\n\t\tthis.bMin = null;\n\t\tthis.bMax = null;\n\t\tthis.indices = null;\n\t\tthis.mortonCodes = null;\n\t\tthis.triangles = null;\n\n\t\t// Reordered output (produced by buildSync)\n\t\tthis.reorderedTriangleData = null;\n\n\t}\n\n\tinitializeBinArrays() {\n\n\t\tconst mb = this.maxBins;\n\t\t// Flat bin bounds: 3 floats per bin (x, y, z)\n\t\tthis.binBoundsMin = new Float32Array( mb * 3 );\n\t\tthis.binBoundsMax = new Float32Array( mb * 3 );\n\t\tthis.binCounts = new Uint32Array( mb );\n\n\t\t// Prefix-sum arrays for SAH evaluation\n\t\tthis.leftPrefixMin = new Float32Array( mb * 3 );\n\t\tthis.leftPrefixMax = new Float32Array( mb * 3 );\n\t\tthis.leftPrefixCount = new Uint32Array( mb );\n\t\tthis.rightPrefixMin = new Float32Array( mb * 3 );\n\t\tthis.rightPrefixMax = new Float32Array( mb * 3 );\n\t\tthis.rightPrefixCount = new Uint32Array( mb );\n\n\t}\n\n\tgetOptimalBinCount( triangleCount ) {\n\n\t\tif ( triangleCount <= 16 ) return this.minBins;\n\t\tif ( triangleCount <= 64 ) return 16;\n\t\tif ( triangleCount <= 256 ) return 32;\n\t\tif ( triangleCount <= 1024 ) return 48;\n\t\treturn this.maxBins;\n\n\t}\n\n\tsetAdaptiveBinConfig( config ) {\n\n\t\tif ( config.minBins !== undefined ) this.minBins = Math.max( 4, config.minBins );\n\t\tif ( config.maxBins !== undefined ) this.maxBins = Math.min( 128, config.maxBins );\n\t\tif ( config.baseBins !== undefined ) this.numBins = config.baseBins;\n\t\tif ( config.maxBins !== undefined ) this.initializeBinArrays();\n\n\t}\n\n\tsetMortonConfig( config ) {\n\n\t\tif ( config.enabled !== undefined ) this.useMortonCodes = config.enabled;\n\t\tif ( config.bits !== undefined ) this.mortonBits = Math.max( 6, Math.min( 10, config.bits ) );\n\t\tif ( config.threshold !== undefined ) this.mortonClusterThreshold = Math.max( 16, config.threshold );\n\n\t}\n\n\tsetFallbackConfig( config ) {\n\n\t\tif ( config.objectMedian !== undefined ) this.enableObjectMedianFallback = config.objectMedian;\n\t\tif ( config.spatialMedian !== undefined ) this.enableSpatialMedianFallback = config.spatialMedian;\n\n\t}\n\n\tsetTreeletConfig( config ) {\n\n\t\tif ( config.enabled !== undefined ) this.enableTreeletOptimization = config.enabled;\n\t\tif ( config.size !== undefined ) this.treeletSize = Math.max( 3, Math.min( 12, config.size ) );\n\t\tif ( config.passes !== undefined ) this.treeletOptimizationPasses = Math.max( 1, Math.min( 3, config.passes ) );\n\t\tif ( config.minImprovement !== undefined ) this.treeletMinImprovement = Math.max( 0.001, config.minImprovement );\n\n\t}\n\n\tdisableTreeletOptimization() {\n\n\t\tthis.enableTreeletOptimization = false;\n\n\t}\n\n\tsetReinsertionConfig( config ) {\n\n\t\tif ( config.enabled !== undefined ) this.enableReinsertionOptimization = config.enabled;\n\t\tif ( config.batchSizeRatio !== undefined ) this.reinsertionBatchSizeRatio = Math.max( 0.005, Math.min( 0.1, config.batchSizeRatio ) );\n\t\tif ( config.maxIterations !== undefined ) this.reinsertionMaxIterations = Math.max( 1, Math.min( 5, config.maxIterations ) );\n\n\t}\n\n\t/**\n\t * Fill per-triangle arrays (centroids, bMin, bMax, indices) from triangle data.\n\t * Arrays must already be allocated on `this` before calling.\n\t */\n\tinitializeTriangleArrays() {\n\n\t\tconst n = this.totalTriangles;\n\t\tconst src = this.triangles;\n\t\tconst PA = TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET;\n\t\tconst PB = TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET;\n\t\tconst PC = TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET;\n\n\t\tfor ( let i = 0; i < n; i ++ ) {\n\n\t\t\tconst base = i * FPT;\n\t\t\tconst ax = src[ base + PA ], ay = src[ base + PA + 1 ], az = src[ base + PA + 2 ];\n\t\t\tconst bx = src[ base + PB ], by = src[ base + PB + 1 ], bz = src[ base + PB + 2 ];\n\t\t\tconst cx = src[ base + PC ], cy = src[ base + PC + 1 ], cz = src[ base + PC + 2 ];\n\n\t\t\tconst o3 = i * 3;\n\t\t\tthis.centroids[ o3 ] = ( ax + bx + cx ) / 3;\n\t\t\tthis.centroids[ o3 + 1 ] = ( ay + by + cy ) / 3;\n\t\t\tthis.centroids[ o3 + 2 ] = ( az + bz + cz ) / 3;\n\n\t\t\tthis.bMin[ o3 ] = ax < bx ? ( ax < cx ? ax : cx ) : ( bx < cx ? bx : cx );\n\t\t\tthis.bMin[ o3 + 1 ] = ay < by ? ( ay < cy ? ay : cy ) : ( by < cy ? by : cy );\n\t\t\tthis.bMin[ o3 + 2 ] = az < bz ? ( az < cz ? az : cz ) : ( bz < cz ? bz : cz );\n\n\t\t\tthis.bMax[ o3 ] = ax > bx ? ( ax > cx ? ax : cx ) : ( bx > cx ? bx : cx );\n\t\t\tthis.bMax[ o3 + 1 ] = ay > by ? ( ay > cy ? ay : cy ) : ( by > cy ? by : cy );\n\t\t\tthis.bMax[ o3 + 2 ] = az > bz ? ( az > cz ? az : cz ) : ( bz > cz ? bz : cz );\n\n\t\t\tthis.indices[ i ] = i;\n\n\t\t}\n\n\t}\n\n\t// --- Morton code helpers ---\n\n\texpandBits( value ) {\n\n\t\tvalue = ( value * 0x00010001 ) & 0xFF0000FF;\n\t\tvalue = ( value * 0x00000101 ) & 0x0F00F00F;\n\t\tvalue = ( value * 0x00000011 ) & 0xC30C30C3;\n\t\tvalue = ( value * 0x00000005 ) & 0x49249249;\n\t\treturn value;\n\n\t}\n\n\tmorton3D( x, y, z ) {\n\n\t\treturn ( this.expandBits( z ) << 2 ) + ( this.expandBits( y ) << 1 ) + this.expandBits( x );\n\n\t}\n\n\tcomputeMortonCodeForIndex( idx, sceneMinX, sceneMinY, sceneMinZ, rangeX, rangeY, rangeZ ) {\n\n\t\tconst c = this.centroids;\n\t\tconst o = idx * 3;\n\t\tconst mortonScale = ( 1 << this.mortonBits ) - 1;\n\n\t\tlet nx = rangeX > 0 ? ( c[ o ] - sceneMinX ) / rangeX : 0;\n\t\tlet ny = rangeY > 0 ? ( c[ o + 1 ] - sceneMinY ) / rangeY : 0;\n\t\tlet nz = rangeZ > 0 ? ( c[ o + 2 ] - sceneMinZ ) / rangeZ : 0;\n\n\t\tconst x = Math.max( 0, Math.min( mortonScale, Math.floor( nx * mortonScale ) ) );\n\t\tconst y = Math.max( 0, Math.min( mortonScale, Math.floor( ny * mortonScale ) ) );\n\t\tconst z = Math.max( 0, Math.min( mortonScale, Math.floor( nz * mortonScale ) ) );\n\n\t\treturn this.morton3D( x, y, z );\n\n\t}\n\n\tsortTrianglesByMortonCode() {\n\n\t\tconst n = this.totalTriangles;\n\t\tif ( ! this.useMortonCodes || n < this.mortonClusterThreshold ) return;\n\n\t\tconst startTime = performance.now();\n\t\tconst c = this.centroids;\n\t\tconst indices = this.indices;\n\n\t\t// Compute scene bounds from centroids\n\t\tlet sMinX = Infinity, sMinY = Infinity, sMinZ = Infinity;\n\t\tlet sMaxX = - Infinity, sMaxY = - Infinity, sMaxZ = - Infinity;\n\t\tfor ( let i = 0; i < n; i ++ ) {\n\n\t\t\tconst idx = indices[ i ];\n\t\t\tconst o = idx * 3;\n\t\t\tconst cx = c[ o ], cy = c[ o + 1 ], cz = c[ o + 2 ];\n\t\t\tif ( cx < sMinX ) sMinX = cx;\n\t\t\tif ( cy < sMinY ) sMinY = cy;\n\t\t\tif ( cz < sMinZ ) sMinZ = cz;\n\t\t\tif ( cx > sMaxX ) sMaxX = cx;\n\t\t\tif ( cy > sMaxY ) sMaxY = cy;\n\t\t\tif ( cz > sMaxZ ) sMaxZ = cz;\n\n\t\t}\n\n\t\tconst rX = sMaxX - sMinX, rY = sMaxY - sMinY, rZ = sMaxZ - sMinZ;\n\n\t\t// Compute morton codes (inlined to avoid per-triangle method dispatch)\n\t\tconst mc = this.mortonCodes;\n\t\tconst mortonScale = ( 1 << this.mortonBits ) - 1;\n\t\tconst invRX = rX > 0 ? mortonScale / rX : 0;\n\t\tconst invRY = rY > 0 ? mortonScale / rY : 0;\n\t\tconst invRZ = rZ > 0 ? mortonScale / rZ : 0;\n\n\t\tfor ( let i = 0; i < n; i ++ ) {\n\n\t\t\tconst triIdx = indices[ i ];\n\t\t\tconst o = triIdx * 3;\n\n\t\t\tlet mx = ( c[ o ] - sMinX ) * invRX;\n\t\t\tlet my = ( c[ o + 1 ] - sMinY ) * invRY;\n\t\t\tlet mz = ( c[ o + 2 ] - sMinZ ) * invRZ;\n\n\t\t\t// Clamp and truncate to integer\n\t\t\tmx = mx < 0 ? 0 : ( mx > mortonScale ? mortonScale : mx ) | 0;\n\t\t\tmy = my < 0 ? 0 : ( my > mortonScale ? mortonScale : my ) | 0;\n\t\t\tmz = mz < 0 ? 0 : ( mz > mortonScale ? mortonScale : mz ) | 0;\n\n\t\t\t// Inline expandBits + morton3D\n\t\t\tmx = ( mx * 0x00010001 ) & 0xFF0000FF;\n\t\t\tmx = ( mx * 0x00000101 ) & 0x0F00F00F;\n\t\t\tmx = ( mx * 0x00000011 ) & 0xC30C30C3;\n\t\t\tmx = ( mx * 0x00000005 ) & 0x49249249;\n\n\t\t\tmy = ( my * 0x00010001 ) & 0xFF0000FF;\n\t\t\tmy = ( my * 0x00000101 ) & 0x0F00F00F;\n\t\t\tmy = ( my * 0x00000011 ) & 0xC30C30C3;\n\t\t\tmy = ( my * 0x00000005 ) & 0x49249249;\n\n\t\t\tmz = ( mz * 0x00010001 ) & 0xFF0000FF;\n\t\t\tmz = ( mz * 0x00000101 ) & 0x0F00F00F;\n\t\t\tmz = ( mz * 0x00000011 ) & 0xC30C30C3;\n\t\t\tmz = ( mz * 0x00000005 ) & 0x49249249;\n\n\t\t\tmc[ triIdx ] = ( mz << 2 ) + ( my << 1 ) + mx;\n\n\t\t}\n\n\t\t// Radix sort indices by morton code (O(N), 4 passes of 8-bit digits)\n\t\tconst temp = new Uint32Array( n );\n\t\tconst counts = new Uint32Array( 256 );\n\n\t\tfor ( let shift = 0; shift < 32; shift += 8 ) {\n\n\t\t\tcounts.fill( 0 );\n\n\t\t\t// Count digit occurrences\n\t\t\tfor ( let i = 0; i < n; i ++ ) {\n\n\t\t\t\tcounts[ ( mc[ indices[ i ] ] >>> shift ) & 0xFF ] ++;\n\n\t\t\t}\n\n\t\t\t// Prefix sum\n\t\t\tlet total = 0;\n\t\t\tfor ( let i = 0; i < 256; i ++ ) {\n\n\t\t\t\tconst c = counts[ i ];\n\t\t\t\tcounts[ i ] = total;\n\t\t\t\ttotal += c;\n\n\t\t\t}\n\n\t\t\t// Scatter to temp\n\t\t\tfor ( let i = 0; i < n; i ++ ) {\n\n\t\t\t\tconst digit = ( mc[ indices[ i ] ] >>> shift ) & 0xFF;\n\t\t\t\ttemp[ counts[ digit ] ++ ] = indices[ i ];\n\n\t\t\t}\n\n\t\t\tindices.set( temp );\n\n\t\t}\n\n\t\tthis.splitStats.mortonSortTime += performance.now() - startTime;\n\n\t}\n\n\t// --- Build entry points ---\n\n\t/**\n\t * Build BVH from triangle data.\n\t * Returns { bvhData: Float32Array, bvhRoot: true, reorderedTriangles: Float32Array }\n\t * where bvhData is the GPU-ready flat array (12 floats/node) and reorderedTriangles\n\t * is the BVH-ordered triangle data. Caller must use reorderedTriangles instead of\n\t * the original input (which is neutered after transfer to the worker).\n\t */\n\tbuild( triangles, depth = 30, progressCallback = null ) {\n\n\t\tthis.totalTriangles = triangles.byteLength / ( FPT * 4 );\n\t\tthis.processedTriangles = 0;\n\t\tthis.lastProgressUpdate = performance.now();\n\n\t\tif ( this.useWorker && typeof Worker !== 'undefined' ) {\n\n\t\t\treturn new Promise( ( resolve, reject ) => {\n\n\t\t\t\ttry {\n\n\t\t\t\t\tconst worker = new Worker(\n\t\t\t\t\t\tnew URL( './Workers/BVHWorker.js', import.meta.url ),\n\t\t\t\t\t\t{ type: 'module' }\n\t\t\t\t\t);\n\n\t\t\t\t\tconst triangleCount = this.totalTriangles;\n\t\t\t\t\tconst useShared = typeof SharedArrayBuffer !== 'undefined';\n\t\t\t\t\tconsole.log( `[BVHBuilder] SharedArrayBuffer: ${useShared ? 'enabled' : 'unavailable (using transfer fallback)'}` );\n\n\t\t\t\t\t// Pre-allocate SharedArrayBuffer for reordered output so worker\n\t\t\t\t\t// writes directly to shared memory (no transfer needed on return).\n\t\t\t\t\tconst sharedReorderBuffer = useShared\n\t\t\t\t\t\t? new SharedArrayBuffer( triangleCount * FPT * 4 )\n\t\t\t\t\t\t: null;\n\n\t\t\t\t\tworker.onmessage = ( e ) => {\n\n\t\t\t\t\t\tconst { bvhData, triangles: transferredTriangles, originalToBvh, error, progress, treeletStats } = e.data;\n\n\t\t\t\t\t\tif ( error ) {\n\n\t\t\t\t\t\t\tworker.terminate();\n\t\t\t\t\t\t\treject( new Error( error ) );\n\t\t\t\t\t\t\treturn;\n\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif ( progress !== undefined && progressCallback ) {\n\n\t\t\t\t\t\t\tprogressCallback( progress );\n\t\t\t\t\t\t\treturn;\n\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif ( treeletStats ) {\n\n\t\t\t\t\t\t\tthis.splitStats = treeletStats;\n\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tworker.terminate();\n\n\t\t\t\t\t\t// Reordered triangles: from shared memory or fallback transfer\n\t\t\t\t\t\tconst reorderedTriangles = sharedReorderBuffer\n\t\t\t\t\t\t\t? new Float32Array( sharedReorderBuffer )\n\t\t\t\t\t\t\t: transferredTriangles;\n\n\t\t\t\t\t\tresolve( { bvhData, bvhRoot: true, reorderedTriangles, originalToBvh: originalToBvh || null } );\n\n\t\t\t\t\t};\n\n\t\t\t\t\tworker.onerror = ( error ) => {\n\n\t\t\t\t\t\tworker.terminate();\n\t\t\t\t\t\treject( error );\n\n\t\t\t\t\t};\n\n\t\t\t\t\t// Transfer the original buffer directly — avoids 362MB copy.\n\t\t\t\t\tconst transferBuffer = triangles.buffer;\n\t\t\t\t\tconst workerData = {\n\t\t\t\t\t\ttriangleData: transferBuffer,\n\t\t\t\t\t\ttriangleByteOffset: triangles.byteOffset,\n\t\t\t\t\t\ttriangleByteLength: triangles.byteLength,\n\t\t\t\t\t\ttriangleCount,\n\t\t\t\t\t\tdepth,\n\t\t\t\t\t\treportProgress: !! progressCallback,\n\t\t\t\t\t\tsharedReorderBuffer,\n\t\t\t\t\t\ttreeletOptimization: {\n\t\t\t\t\t\t\tenabled: this.enableTreeletOptimization,\n\t\t\t\t\t\t\tsize: this.treeletSize,\n\t\t\t\t\t\t\tpasses: this.treeletOptimizationPasses,\n\t\t\t\t\t\t\tminImprovement: this.treeletMinImprovement\n\t\t\t\t\t\t},\n\t\t\t\t\t\treinsertionOptimization: {\n\t\t\t\t\t\t\tenabled: this.enableReinsertionOptimization,\n\t\t\t\t\t\t\tbatchSizeRatio: this.reinsertionBatchSizeRatio,\n\t\t\t\t\t\t\tmaxIterations: this.reinsertionMaxIterations\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tworker.postMessage( workerData, [ transferBuffer ] );\n\n\t\t\t\t} catch ( error ) {\n\n\t\t\t\t\tconsole.warn( 'Worker creation failed, falling back to synchronous build:', error );\n\t\t\t\t\tresolve( this._buildSyncAndFlatten( triangles, depth, progressCallback ) );\n\n\t\t\t\t}\n\n\t\t\t} );\n\n\t\t} else {\n\n\t\t\treturn new Promise( ( resolve ) => {\n\n\t\t\t\tresolve( this._buildSyncAndFlatten( triangles, depth, progressCallback ) );\n\n\t\t\t} );\n\n\t\t}\n\n\t}\n\n\t/**\n\t * Synchronous build + flatten helper for non-worker path.\n\t * @private\n\t */\n\t_buildSyncAndFlatten( triangles, depth, progressCallback ) {\n\n\t\tconst root = this.buildSync( triangles, depth, progressCallback );\n\t\tconst bvhData = this.flattenBVH( root );\n\t\t// Return reordered triangles if available (avoids 362MB copy)\n\t\tconst reorderedTriangles = this.reorderedTriangleData || null;\n\t\tconst originalToBvh = this.originalToBvhMap || null;\n\t\treturn { bvhData, bvhRoot: true, reorderedTriangles, originalToBvh };\n\n\t}\n\n\tbuildSync( triangles, depth = 30, progressCallback = null, reorderTarget = null ) {\n\n\t\tconst buildStartTime = performance.now();\n\n\t\t// Reset state\n\t\tthis.totalNodes = 0;\n\t\tthis.processedTriangles = 0;\n\t\tthis.triangles = triangles;\n\t\tthis.totalTriangles = triangles.byteLength / ( FPT * 4 );\n\t\tthis.lastProgressUpdate = performance.now();\n\n\t\tthis.splitStats = {\n\t\t\tsahSplits: 0,\n\t\t\tobjectMedianSplits: 0,\n\t\t\tspatialMedianSplits: 0,\n\t\t\tfailedSplits: 0,\n\t\t\tavgBinsUsed: 0,\n\t\t\ttotalSplitAttempts: 0,\n\t\t\tmortonSortTime: 0,\n\t\t\ttotalBuildTime: 0,\n\t\t\ttreeletOptimizationTime: 0,\n\t\t\ttreeletsProcessed: 0,\n\t\t\ttreeletsImproved: 0,\n\t\t\taverageSAHImprovement: 0,\n\t\t\treinsertionOptimizationTime: 0,\n\t\t\treinsertionsApplied: 0,\n\t\t\treinsertionIterations: 0,\n\t\t\tsaOrderTime: 0,\n\t\t\t// Granular phase timings\n\t\t\tinitTime: 0,\n\t\t\tsahBuildTime: 0,\n\t\t\treorderTime: 0\n\t\t};\n\n\t\tconst n = this.totalTriangles;\n\n\t\t// Phase 1: Allocate and initialize per-triangle arrays\n\t\tconst initStart = performance.now();\n\n\t\tthis.centroids = new Float32Array( n * 3 );\n\t\tthis.bMin = new Float32Array( n * 3 );\n\t\tthis.bMax = new Float32Array( n * 3 );\n\t\tthis.indices = new Uint32Array( n );\n\t\tthis.mortonCodes = new Uint32Array( n );\n\n\t\tthis.initializeTriangleArrays();\n\n\t\tthis.splitStats.initTime = performance.now() - initStart;\n\n\t\t// Phase 2: Morton code spatial clustering\n\t\tthis.sortTrianglesByMortonCode();\n\n\t\t// Phase 3: Recursive SAH build\n\t\tconst sahStart = performance.now();\n\t\tconst root = this.buildNodeRecursive( 0, n, depth, progressCallback );\n\t\tthis.splitStats.sahBuildTime = performance.now() - sahStart;\n\n\t\t// Phase 4: Treelet optimization\n\t\tif ( this.enableTreeletOptimization && this.totalTriangles > 1000 ) {\n\n\t\t\tconst isLargeScene = this.totalTriangles > this.treeletComplexityThreshold;\n\t\t\tconst adaptiveTreeletSize = isLargeScene ? 3 : this.treeletSize;\n\t\t\tconst adaptiveMaxTreelets = isLargeScene ? 10 : this.maxTreeletsPerScene;\n\n\t\t\tconst optimizer = new TreeletOptimizer( this.traversalCost, this.intersectionCost );\n\t\t\toptimizer.setTreeletSize( adaptiveTreeletSize );\n\t\t\toptimizer.setMinImprovement( this.treeletMinImprovement );\n\t\t\toptimizer.setMaxTreelets( adaptiveMaxTreelets );\n\n\t\t\tconst optimizationStartTime = performance.now();\n\n\t\t\tfor ( let pass = 0; pass < this.treeletOptimizationPasses; pass ++ ) {\n\n\t\t\t\tconst passCallback = progressCallback ? ( status ) => {\n\n\t\t\t\t\tprogressCallback( `Treelet optimization pass ${pass + 1}/${this.treeletOptimizationPasses}: ${status}` );\n\n\t\t\t\t} : null;\n\n\t\t\t\ttry {\n\n\t\t\t\t\toptimizer.optimizeBVH( root, passCallback );\n\n\t\t\t\t} catch ( error ) {\n\n\t\t\t\t\tconsole.error( `TreeletOptimizer: Error in pass ${pass + 1}:`, error );\n\t\t\t\t\tbreak;\n\n\t\t\t\t}\n\n\t\t\t\t// optimizeBVH resets stats internally, so afterStats reflects this pass only\n\t\t\t\tconst afterStats = optimizer.getStatistics();\n\t\t\t\tconst passTime = performance.now() - optimizationStartTime;\n\t\t\t\tif ( ( afterStats.treeletsImproved === 0 && pass > 0 ) || passTime > 15000 ) {\n\n\t\t\t\t\tbreak;\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\tconst treeletTime = performance.now() - optimizationStartTime;\n\t\t\tthis.splitStats.treeletOptimizationTime = treeletTime;\n\t\t\tconst treeletStats = optimizer.getStatistics();\n\t\t\tthis.splitStats.treeletsProcessed = treeletStats.treeletsProcessed;\n\t\t\tthis.splitStats.treeletsImproved = treeletStats.treeletsImproved;\n\t\t\tthis.splitStats.averageSAHImprovement = treeletStats.averageSAHImprovement;\n\n\t\t}\n\n\t\t// Phase 4b: Reinsertion optimization (Meister & Bittner)\n\t\tif ( this.enableReinsertionOptimization && this.totalTriangles > 1000 ) {\n\n\t\t\tconst reinsertionOptimizer = new ReinsertionOptimizer( this.traversalCost, this.intersectionCost );\n\t\t\treinsertionOptimizer.setBatchSizeRatio( this.reinsertionBatchSizeRatio );\n\t\t\treinsertionOptimizer.setMaxIterations( this.reinsertionMaxIterations );\n\n\t\t\tconst reinsertCallback = progressCallback ? ( status ) => {\n\n\t\t\t\tprogressCallback( status );\n\n\t\t\t} : null;\n\n\t\t\ttry {\n\n\t\t\t\treinsertionOptimizer.optimizeBVH( root, reinsertCallback );\n\n\t\t\t} catch ( error ) {\n\n\t\t\t\tconsole.error( 'ReinsertionOptimizer: Error:', error );\n\n\t\t\t}\n\n\t\t\tconst reinsertStats = reinsertionOptimizer.getStatistics();\n\t\t\tthis.splitStats.reinsertionOptimizationTime = reinsertStats.timeMs;\n\t\t\tthis.splitStats.reinsertionsApplied = reinsertStats.reinsertionsApplied;\n\t\t\tthis.splitStats.reinsertionIterations = reinsertStats.iterations;\n\n\t\t}\n\n\t\t// Phase 5: Surface-area child ordering (DFS cache locality)\n\t\tconst saOrderStart = performance.now();\n\t\tthis.applySAOrdering( root );\n\t\tthis.splitStats.saOrderTime = performance.now() - saOrderStart;\n\n\t\t// Phase 6: Create reordered triangle data from final index order\n\t\tconst reorderStart = performance.now();\n\t\tconst triSrc = this.triangles;\n\t\tconst reordered = reorderTarget || new Float32Array( n * FPT );\n\t\tfor ( let i = 0; i < n; i ++ ) {\n\n\t\t\tconst srcOff = this.indices[ i ] * FPT;\n\t\t\tconst dstOff = i * FPT;\n\t\t\treordered.set( triSrc.subarray( srcOff, srcOff + FPT ), dstOff );\n\n\t\t}\n\n\t\tthis.reorderedTriangleData = reordered;\n\n\t\t// Phase 6b: Build inverse index map for BVH refit\n\t\t// originalToBvh[originalTriIdx] = bvhOrderIdx\n\t\tconst originalToBvh = new Uint32Array( n );\n\t\tfor ( let i = 0; i < n; i ++ ) {\n\n\t\t\toriginalToBvh[ this.indices[ i ] ] = i;\n\n\t\t}\n\n\t\tthis.originalToBvhMap = originalToBvh;\n\n\t\tthis.splitStats.reorderTime = performance.now() - reorderStart;\n\n\t\tthis.splitStats.totalBuildTime = performance.now() - buildStartTime;\n\n\t\t// Print phase-level timing breakdown\n\t\tconst total = this.splitStats.totalBuildTime;\n\t\tconst phasePct = ( ms ) => total > 0 ? ( ms / total * 100 ).toFixed( 1 ) + '%' : '0%';\n\n\t\tconst timingRows = [\n\t\t\t{ Phase: 'Init + bounds', 'Time (ms)': Math.round( this.splitStats.initTime ), '%': phasePct( this.splitStats.initTime ) },\n\t\t\t{ Phase: 'Morton sort', 'Time (ms)': Math.round( this.splitStats.mortonSortTime ), '%': phasePct( this.splitStats.mortonSortTime ) },\n\t\t\t{ Phase: 'SAH recursive build', 'Time (ms)': Math.round( this.splitStats.sahBuildTime ), '%': phasePct( this.splitStats.sahBuildTime ) },\n\t\t\t{ Phase: 'Treelet optimization', 'Time (ms)': Math.round( this.splitStats.treeletOptimizationTime ), '%': phasePct( this.splitStats.treeletOptimizationTime ) },\n\t\t\t{ Phase: 'Reinsertion optimization', 'Time (ms)': Math.round( this.splitStats.reinsertionOptimizationTime ), '%': phasePct( this.splitStats.reinsertionOptimizationTime ) },\n\t\t\t{ Phase: 'SA ordering', 'Time (ms)': Math.round( this.splitStats.saOrderTime ), '%': phasePct( this.splitStats.saOrderTime ) },\n\t\t\t{ Phase: 'Triangle reorder', 'Time (ms)': Math.round( this.splitStats.reorderTime ), '%': phasePct( this.splitStats.reorderTime ) },\n\t\t\t{ Phase: 'TOTAL', 'Time (ms)': Math.round( total ), '%': '100%' }\n\t\t];\n\n\t\tconsole.groupCollapsed( `⏱ BVH Build (${n.toLocaleString()} triangles, ${Math.round( total )}ms)` );\n\t\tconsole.table( timingRows );\n\t\tconsole.table( {\n\t\t\t'Total Nodes': this.totalNodes,\n\t\t\t'Max Leaf Size': this.maxLeafSize,\n\t\t\t'SAH Splits': this.splitStats.sahSplits,\n\t\t\t'Object Median Splits': this.splitStats.objectMedianSplits,\n\t\t\t'Spatial Median Splits': this.splitStats.spatialMedianSplits,\n\t\t\t'Failed Splits': this.splitStats.failedSplits,\n\t\t\t'Treelets Processed': this.splitStats.treeletsProcessed,\n\t\t\t'Treelets Improved': this.splitStats.treeletsImproved,\n\t\t\t'Avg SAH Improvement': ( this.splitStats.averageSAHImprovement * 100 ).toFixed( 2 ) + '%',\n\t\t\t'Reinsertions Applied': this.splitStats.reinsertionsApplied,\n\t\t\t'Reinsertion Iterations': this.splitStats.reinsertionIterations,\n\t\t} );\n\t\tconsole.groupEnd();\n\n\t\tprogressCallback && progressCallback( 100 );\n\n\t\t// Free flat arrays (no longer needed)\n\t\tthis.centroids = null;\n\t\tthis.bMin = null;\n\t\tthis.bMax = null;\n\t\tthis.mortonCodes = null;\n\n\t\treturn root;\n\n\t}\n\n\tupdateProgress( trianglesProcessed, progressCallback ) {\n\n\t\tif ( ! progressCallback ) return;\n\n\t\tthis.processedTriangles += trianglesProcessed;\n\t\tconst now = performance.now();\n\t\tif ( now - this.lastProgressUpdate < this.progressUpdateInterval ) return;\n\n\t\tthis.lastProgressUpdate = now;\n\t\tconst progress = Math.min( Math.floor( ( this.processedTriangles / this.totalTriangles ) * 100 ), 99 );\n\t\tprogressCallback( progress );\n\n\t}\n\n\t// --- Top-level BVH build for parallel construction ---\n\n\t/**\n\t * Build top levels of BVH, creating frontier leaves for parallel subtree construction.\n\t * Frontier leaves are marked with `isFrontier = true` and recorded in `this.frontierTasks`.\n\t * @param {number} start - Start index in indices array\n\t * @param {number} end - End index in indices array\n\t * @param {number} depth - Remaining depth budget for full tree\n\t * @param {number} frontierDepthRemaining - Levels still to build before creating frontier leaves\n\t * @param {Function} progressCallback - Optional progress callback\n\t * @param {number} preMinX - Precomputed bounds (optional)\n\t */\n\tbuildNodeRecursiveToDepth( start, end, depth, frontierDepthRemaining, progressCallback, preMinX, preMinY, preMinZ, preMaxX, preMaxY, preMaxZ ) {\n\n\t\tconst node = new BVHNode();\n\t\tthis.totalNodes ++;\n\n\t\tconst count = end - start;\n\n\t\t// Use precomputed bounds from parent's partition, or compute for root\n\t\tif ( preMinX !== undefined ) {\n\n\t\t\tnode.minX = preMinX; node.minY = preMinY; node.minZ = preMinZ;\n\t\t\tnode.maxX = preMaxX; node.maxY = preMaxY; node.maxZ = preMaxZ;\n\n\t\t} else {\n\n\t\t\tthis.updateNodeBounds( node, start, end );\n\n\t\t}\n\n\t\t// Normal leaf condition (small enough to not need a subtree)\n\t\tif ( count <= this.maxLeafSize || depth <= 0 ) {\n\n\t\t\tnode.triangleOffset = start;\n\t\t\tnode.triangleCount = count;\n\t\t\tthis.updateProgress( count, progressCallback );\n\t\t\treturn node;\n\n\t\t}\n\n\t\t// Frontier condition: stop recursion and record as parallel task\n\t\tif ( frontierDepthRemaining <= 0 && count > this.maxLeafSize * 16 ) {\n\n\t\t\tconst taskId = this.frontierTasks.length;\n\t\t\tnode.triangleOffset = start;\n\t\t\tnode.triangleCount = count;\n\t\t\tnode.isFrontier = true;\n\t\t\tnode.frontierTaskId = taskId;\n\t\t\tthis.frontierTasks.push( {\n\t\t\t\ttaskId,\n\t\t\t\tstart,\n\t\t\t\tend,\n\t\t\t\tdepth,\n\t\t\t\tpreMinX: node.minX, preMinY: node.minY, preMinZ: node.minZ,\n\t\t\t\tpreMaxX: node.maxX, preMaxY: node.maxY, preMaxZ: node.maxZ\n\t\t\t} );\n\t\t\treturn node;\n\n\t\t}\n\n\t\t// Find best split using SAH\n\t\tconst splitInfo = this.findBestSplitPositionSAH( start, end, node );\n\n\t\tif ( ! splitInfo.success ) {\n\n\t\t\tthis.splitStats.failedSplits ++;\n\n\t\t\t// If we haven't reached frontier depth yet, make it a frontier task anyway\n\t\t\tif ( frontierDepthRemaining > 0 || count <= this.maxLeafSize * 16 ) {\n\n\t\t\t\tnode.triangleOffset = start;\n\t\t\t\tnode.triangleCount = count;\n\t\t\t\tthis.updateProgress( count, progressCallback );\n\t\t\t\treturn node;\n\n\t\t\t}\n\n\t\t\tconst taskId = this.frontierTasks.length;\n\t\t\tnode.triangleOffset = start;\n\t\t\tnode.triangleCount = count;\n\t\t\tnode.isFrontier = true;\n\t\t\tnode.frontierTaskId = taskId;\n\t\t\tthis.frontierTasks.push( {\n\t\t\t\ttaskId,\n\t\t\t\tstart,\n\t\t\t\tend,\n\t\t\t\tdepth,\n\t\t\t\tpreMinX: node.minX, preMinY: node.minY, preMinZ: node.minZ,\n\t\t\t\tpreMaxX: node.maxX, preMaxY: node.maxY, preMaxZ: node.maxZ\n\t\t\t} );\n\t\t\treturn node;\n\n\t\t}\n\n\t\t// Track split method\n\t\tif ( splitInfo.method === 'SAH' ) this.splitStats.sahSplits ++;\n\t\telse if ( splitInfo.method === 'object_median' ) this.splitStats.objectMedianSplits ++;\n\t\telse if ( splitInfo.method === 'spatial_median' ) this.splitStats.spatialMedianSplits ++;\n\n\t\t// Partition and compute child bounds in one pass\n\t\tthis.partitionWithBounds( start, end, splitInfo.axis, splitInfo.pos );\n\n\t\tconst p = this._partResult;\n\t\tconst mid = p.mid;\n\t\tconst lMnX = p.lMinX, lMnY = p.lMinY, lMnZ = p.lMinZ;\n\t\tconst lMxX = p.lMaxX, lMxY = p.lMaxY, lMxZ = p.lMaxZ;\n\t\tconst rMnX = p.rMinX, rMnY = p.rMinY, rMnZ = p.rMinZ;\n\t\tconst rMxX = p.rMaxX, rMxY = p.rMaxY, rMxZ = p.rMaxZ;\n\n\t\t// Degenerate partition fallback\n\t\tif ( mid === start || mid === end ) {\n\n\t\t\tnode.triangleOffset = start;\n\t\t\tnode.triangleCount = count;\n\t\t\tthis.updateProgress( count, progressCallback );\n\t\t\treturn node;\n\n\t\t}\n\n\t\tnode.leftChild = this.buildNodeRecursiveToDepth(\n\t\t\tstart, mid, depth - 1, frontierDepthRemaining - 1, progressCallback,\n\t\t\tlMnX, lMnY, lMnZ, lMxX, lMxY, lMxZ\n\t\t);\n\t\tnode.rightChild = this.buildNodeRecursiveToDepth(\n\t\t\tmid, end, depth - 1, frontierDepthRemaining - 1, progressCallback,\n\t\t\trMnX, rMnY, rMnZ, rMxX, rMxY, rMxZ\n\t\t);\n\n\t\treturn node;\n\n\t}\n\n\t// --- Recursive BVH build (operates on index range) ---\n\n\tbuildNodeRecursive( start, end, depth, progressCallback, preMinX, preMinY, preMinZ, preMaxX, preMaxY, preMaxZ ) {\n\n\t\tconst node = new BVHNode();\n\t\tthis.totalNodes ++;\n\n\t\tconst count = end - start;\n\n\t\t// Use precomputed bounds from parent's partition, or compute for root\n\t\tif ( preMinX !== undefined ) {\n\n\t\t\tnode.minX = preMinX; node.minY = preMinY; node.minZ = preMinZ;\n\t\t\tnode.maxX = preMaxX; node.maxY = preMaxY; node.maxZ = preMaxZ;\n\n\t\t} else {\n\n\t\t\tthis.updateNodeBounds( node, start, end );\n\n\t\t}\n\n\t\t// Leaf condition\n\t\tif ( count <= this.maxLeafSize || depth <= 0 ) {\n\n\t\t\tnode.triangleOffset = start;\n\t\t\tnode.triangleCount = count;\n\t\t\tthis.updateProgress( count, progressCallback );\n\t\t\treturn node;\n\n\t\t}\n\n\t\t// Find best split using SAH\n\t\tconst splitInfo = this.findBestSplitPositionSAH( start, end, node );\n\n\t\tif ( ! splitInfo.success ) {\n\n\t\t\tthis.splitStats.failedSplits ++;\n\t\t\tnode.triangleOffset = start;\n\t\t\tnode.triangleCount = count;\n\t\t\tthis.updateProgress( count, progressCallback );\n\t\t\treturn node;\n\n\t\t}\n\n\t\t// Track split method\n\t\tif ( splitInfo.method === 'SAH' ) this.splitStats.sahSplits ++;\n\t\telse if ( splitInfo.method === 'object_median' ) this.splitStats.objectMedianSplits ++;\n\t\telse if ( splitInfo.method === 'spatial_median' ) this.splitStats.spatialMedianSplits ++;\n\n\t\t// Partition and compute child bounds in one pass\n\t\tthis.partitionWithBounds( start, end, splitInfo.axis, splitInfo.pos );\n\n\t\t// Snapshot into locals — _partResult is reused and will be overwritten by child recursion\n\t\tconst p = this._partResult;\n\t\tconst mid = p.mid;\n\t\tconst lMnX = p.lMinX, lMnY = p.lMinY, lMnZ = p.lMinZ;\n\t\tconst lMxX = p.lMaxX, lMxY = p.lMaxY, lMxZ = p.lMaxZ;\n\t\tconst rMnX = p.rMinX, rMnY = p.rMinY, rMnZ = p.rMinZ;\n\t\tconst rMxX = p.rMaxX, rMxY = p.rMaxY, rMxZ = p.rMaxZ;\n\n\t\t// Degenerate partition fallback\n\t\tif ( mid === start || mid === end ) {\n\n\t\t\tnode.triangleOffset = start;\n\t\t\tnode.triangleCount = count;\n\t\t\tthis.updateProgress( count, progressCallback );\n\t\t\treturn node;\n\n\t\t}\n\n\t\tnode.leftChild = this.buildNodeRecursive(\n\t\t\tstart, mid, depth - 1, progressCallback,\n\t\t\tlMnX, lMnY, lMnZ, lMxX, lMxY, lMxZ\n\t\t);\n\t\tnode.rightChild = this.buildNodeRecursive(\n\t\t\tmid, end, depth - 1, progressCallback,\n\t\t\trMnX, rMnY, rMnZ, rMxX, rMxY, rMxZ\n\t\t);\n\n\t\treturn node;\n\n\t}\n\n\t// Partition indices and accumulate child bounds in a single pass\n\tpartitionWithBounds( start, end, axis, splitPos ) {\n\n\t\tconst idx = this.indices;\n\t\tconst c = this.centroids;\n\t\tconst bMn = this.bMin;\n\t\tconst bMx = this.bMax;\n\n\t\tlet lo = start;\n\t\tlet hi = end - 1;\n\n\t\tlet lMinX = Infinity, lMinY = Infinity, lMinZ = Infinity;\n\t\tlet lMaxX = - Infinity, lMaxY = - Infinity, lMaxZ = - Infinity;\n\t\tlet rMinX = Infinity, rMinY = Infinity, rMinZ = Infinity;\n\t\tlet rMaxX = - Infinity, rMaxY = - Infinity, rMaxZ = - Infinity;\n\n\t\twhile ( lo <= hi ) {\n\n\t\t\tconst triIdx = idx[ lo ];\n\t\t\tconst o = triIdx * 3;\n\n\t\t\tif ( c[ o + axis ] <= splitPos ) {\n\n\t\t\t\t// Left partition — accumulate bounds\n\t\t\t\tif ( bMn[ o ] < lMinX ) lMinX = bMn[ o ];\n\t\t\t\tif ( bMn[ o + 1 ] < lMinY ) lMinY = bMn[ o + 1 ];\n\t\t\t\tif ( bMn[ o + 2 ] < lMinZ ) lMinZ = bMn[ o + 2 ];\n\t\t\t\tif ( bMx[ o ] > lMaxX ) lMaxX = bMx[ o ];\n\t\t\t\tif ( bMx[ o + 1 ] > lMaxY ) lMaxY = bMx[ o + 1 ];\n\t\t\t\tif ( bMx[ o + 2 ] > lMaxZ ) lMaxZ = bMx[ o + 2 ];\n\t\t\t\tlo ++;\n\n\t\t\t} else {\n\n\t\t\t\t// Right partition — accumulate bounds\n\t\t\t\tif ( bMn[ o ] < rMinX ) rMinX = bMn[ o ];\n\t\t\t\tif ( bMn[ o + 1 ] < rMinY ) rMinY = bMn[ o + 1 ];\n\t\t\t\tif ( bMn[ o + 2 ] < rMinZ ) rMinZ = bMn[ o + 2 ];\n\t\t\t\tif ( bMx[ o ] > rMaxX ) rMaxX = bMx[ o ];\n\t\t\t\tif ( bMx[ o + 1 ] > rMaxY ) rMaxY = bMx[ o + 1 ];\n\t\t\t\tif ( bMx[ o + 2 ] > rMaxZ ) rMaxZ = bMx[ o + 2 ];\n\n\t\t\t\t// Swap indices[lo] and indices[hi]\n\t\t\t\tidx[ lo ] = idx[ hi ];\n\t\t\t\tidx[ hi ] = triIdx;\n\t\t\t\thi --;\n\n\t\t\t}\n\n\t\t}\n\n\t\tconst r = this._partResult;\n\t\tr.mid = lo;\n\t\tr.lMinX = lMinX; r.lMinY = lMinY; r.lMinZ = lMinZ;\n\t\tr.lMaxX = lMaxX; r.lMaxY = lMaxY; r.lMaxZ = lMaxZ;\n\t\tr.rMinX = rMinX; r.rMinY = rMinY; r.rMinZ = rMinZ;\n\t\tr.rMaxX = rMaxX; r.rMaxY = rMaxY; r.rMaxZ = rMaxZ;\n\t\treturn r;\n\n\t}\n\n\tupdateNodeBounds( node, start, end ) {\n\n\t\tlet minX = Infinity, minY = Infinity, minZ = Infinity;\n\t\tlet maxX = - Infinity, maxY = - Infinity, maxZ = - Infinity;\n\n\t\tconst idx = this.indices;\n\t\tconst bMin = this.bMin;\n\t\tconst bMax = this.bMax;\n\n\t\tfor ( let i = start; i < end; i ++ ) {\n\n\t\t\tconst o = idx[ i ] * 3;\n\t\t\tif ( bMin[ o ] < minX ) minX = bMin[ o ];\n\t\t\tif ( bMin[ o + 1 ] < minY ) minY = bMin[ o + 1 ];\n\t\t\tif ( bMin[ o + 2 ] < minZ ) minZ = bMin[ o + 2 ];\n\t\t\tif ( bMax[ o ] > maxX ) maxX = bMax[ o ];\n\t\t\tif ( bMax[ o + 1 ] > maxY ) maxY = bMax[ o + 1 ];\n\t\t\tif ( bMax[ o + 2 ] > maxZ ) maxZ = bMax[ o + 2 ];\n\n\t\t}\n\n\t\tnode.minX = minX; node.minY = minY; node.minZ = minZ;\n\t\tnode.maxX = maxX; node.maxY = maxY; node.maxZ = maxZ;\n\n\t}\n\n\t// --- SAH with prefix-sum (O(bins) per axis instead of O(bins²)) ---\n\n\tfindBestSplitPositionSAH( start, end, parentNode ) {\n\n\t\tlet bestCost = Infinity;\n\t\tlet bestAxis = - 1;\n\t\tlet bestPos = 0;\n\n\t\tconst parentSA = this.computeSurfaceAreaFlat( parentNode.minX, parentNode.minY, parentNode.minZ, parentNode.maxX, parentNode.maxY, parentNode.maxZ );\n\t\tconst count = end - start;\n\t\tconst leafCost = this.intersectionCost * count;\n\t\tconst currentBinCount = this.getOptimalBinCount( count );\n\n\t\tthis.splitStats.totalSplitAttempts ++;\n\t\tthis.splitStats.avgBinsUsed = ( ( this.splitStats.avgBinsUsed * ( this.splitStats.totalSplitAttempts - 1 ) ) + currentBinCount ) / this.splitStats.totalSplitAttempts;\n\n\t\tconst idx = this.indices;\n\t\tconst c = this.centroids;\n\t\tconst bMn = this.bMin;\n\t\tconst bMx = this.bMax;\n\t\tconst bbMin = this.binBoundsMin;\n\t\tconst bbMax = this.binBoundsMax;\n\t\tconst bc = this.binCounts;\n\t\tconst lpMin = this.leftPrefixMin;\n\t\tconst lpMax = this.leftPrefixMax;\n\t\tconst lpc = this.leftPrefixCount;\n\t\tconst rpMin = this.rightPrefixMin;\n\t\tconst rpMax = this.rightPrefixMax;\n\t\tconst rpc = this.rightPrefixCount;\n\n\t\t// Single pass: find centroid bounds for all 3 axes\n\t\tlet cMin0 = Infinity, cMax0 = - Infinity;\n\t\tlet cMin1 = Infinity, cMax1 = - Infinity;\n\t\tlet cMin2 = Infinity, cMax2 = - Infinity;\n\n\t\tfor ( let i = start; i < end; i ++ ) {\n\n\t\t\tconst o = idx[ i ] * 3;\n\t\t\tconst c0 = c[ o ], c1 = c[ o + 1 ], c2 = c[ o + 2 ];\n\t\t\tif ( c0 < cMin0 ) cMin0 = c0; if ( c0 > cMax0 ) cMax0 = c0;\n\t\t\tif ( c1 < cMin1 ) cMin1 = c1; if ( c1 > cMax1 ) cMax1 = c1;\n\t\t\tif ( c2 < cMin2 ) cMin2 = c2; if ( c2 > cMax2 ) cMax2 = c2;\n\n\t\t}\n\n\t\tconst centroidMin = [ cMin0, cMin1, cMin2 ];\n\t\tconst centroidMax = [ cMax0, cMax1, cMax2 ];\n\n\t\tfor ( let axis = 0; axis < 3; axis ++ ) {\n\n\t\t\tconst minCentroid = centroidMin[ axis ];\n\t\t\tconst maxCentroid = centroidMax[ axis ];\n\n\t\t\tif ( maxCentroid - minCentroid < 1e-6 ) continue;\n\n\t\t\t// Reset bins\n\t\t\tfor ( let b = 0; b < currentBinCount; b ++ ) {\n\n\t\t\t\tbc[ b ] = 0;\n\t\t\t\tconst b3 = b * 3;\n\t\t\t\tbbMin[ b3 ] = Infinity; bbMin[ b3 + 1 ] = Infinity; bbMin[ b3 + 2 ] = Infinity;\n\t\t\t\tbbMax[ b3 ] = - Infinity; bbMax[ b3 + 1 ] = - Infinity; bbMax[ b3 + 2 ] = - Infinity;\n\n\t\t\t}\n\n\t\t\t// Place triangles into bins\n\t\t\tconst binScale = currentBinCount / ( maxCentroid - minCentroid );\n\t\t\tfor ( let i = start; i < end; i ++ ) {\n\n\t\t\t\tconst triIdx = idx[ i ];\n\t\t\t\tconst cv = c[ triIdx * 3 + axis ];\n\t\t\t\tlet bi = Math.floor( ( cv - minCentroid ) * binScale );\n\t\t\t\tif ( bi >= currentBinCount ) bi = currentBinCount - 1;\n\n\t\t\t\tbc[ bi ] ++;\n\t\t\t\tconst b3 = bi * 3;\n\t\t\t\tconst t3 = triIdx * 3;\n\n\t\t\t\tif ( bMn[ t3 ] < bbMin[ b3 ] ) bbMin[ b3 ] = bMn[ t3 ];\n\t\t\t\tif ( bMn[ t3 + 1 ] < bbMin[ b3 + 1 ] ) bbMin[ b3 + 1 ] = bMn[ t3 + 1 ];\n\t\t\t\tif ( bMn[ t3 + 2 ] < bbMin[ b3 + 2 ] ) bbMin[ b3 + 2 ] = bMn[ t3 + 2 ];\n\t\t\t\tif ( bMx[ t3 ] > bbMax[ b3 ] ) bbMax[ b3 ] = bMx[ t3 ];\n\t\t\t\tif ( bMx[ t3 + 1 ] > bbMax[ b3 + 1 ] ) bbMax[ b3 + 1 ] = bMx[ t3 + 1 ];\n\t\t\t\tif ( bMx[ t3 + 2 ] > bbMax[ b3 + 2 ] ) bbMax[ b3 + 2 ] = bMx[ t3 + 2 ];\n\n\t\t\t}\n\n\t\t\t// Build left prefix sums\n\t\t\tlpc[ 0 ] = bc[ 0 ];\n\t\t\tlpMin[ 0 ] = bbMin[ 0 ]; lpMin[ 1 ] = bbMin[ 1 ]; lpMin[ 2 ] = bbMin[ 2 ];\n\t\t\tlpMax[ 0 ] = bbMax[ 0 ]; lpMax[ 1 ] = bbMax[ 1 ]; lpMax[ 2 ] = bbMax[ 2 ];\n\n\t\t\tfor ( let b = 1; b < currentBinCount; b ++ ) {\n\n\t\t\t\tconst b3 = b * 3;\n\t\t\t\tconst p3 = ( b - 1 ) * 3;\n\t\t\t\tlpc[ b ] = lpc[ b - 1 ] + bc[ b ];\n\t\t\t\tconst lp0 = lpMin[ p3 ], lb0 = bbMin[ b3 ];\n\t\t\t\tconst lp1 = lpMin[ p3 + 1 ], lb1 = bbMin[ b3 + 1 ];\n\t\t\t\tconst lp2 = lpMin[ p3 + 2 ], lb2 = bbMin[ b3 + 2 ];\n\t\t\t\tlpMin[ b3 ] = lp0 < lb0 ? lp0 : lb0;\n\t\t\t\tlpMin[ b3 + 1 ] = lp1 < lb1 ? lp1 : lb1;\n\t\t\t\tlpMin[ b3 + 2 ] = lp2 < lb2 ? lp2 : lb2;\n\t\t\t\tconst lxp0 = lpMax[ p3 ], lxb0 = bbMax[ b3 ];\n\t\t\t\tconst lxp1 = lpMax[ p3 + 1 ], lxb1 = bbMax[ b3 + 1 ];\n\t\t\t\tconst lxp2 = lpMax[ p3 + 2 ], lxb2 = bbMax[ b3 + 2 ];\n\t\t\t\tlpMax[ b3 ] = lxp0 > lxb0 ? lxp0 : lxb0;\n\t\t\t\tlpMax[ b3 + 1 ] = lxp1 > lxb1 ? lxp1 : lxb1;\n\t\t\t\tlpMax[ b3 + 2 ] = lxp2 > lxb2 ? lxp2 : lxb2;\n\n\t\t\t}\n\n\t\t\t// Build right prefix sums\n\t\t\tconst last = currentBinCount - 1;\n\t\t\tconst l3 = last * 3;\n\t\t\trpc[ last ] = bc[ last ];\n\t\t\trpMin[ l3 ] = bbMin[ l3 ]; rpMin[ l3 + 1 ] = bbMin[ l3 + 1 ]; rpMin[ l3 + 2 ] = bbMin[ l3 + 2 ];\n\t\t\trpMax[ l3 ] = bbMax[ l3 ]; rpMax[ l3 + 1 ] = bbMax[ l3 + 1 ]; rpMax[ l3 + 2 ] = bbMax[ l3 + 2 ];\n\n\t\t\tfor ( let b = last - 1; b >= 0; b -- ) {\n\n\t\t\t\tconst b3 = b * 3;\n\t\t\t\tconst n3 = ( b + 1 ) * 3;\n\t\t\t\trpc[ b ] = rpc[ b + 1 ] + bc[ b ];\n\t\t\t\tconst rn0 = rpMin[ n3 ], rb0 = bbMin[ b3 ];\n\t\t\t\tconst rn1 = rpMin[ n3 + 1 ], rb1 = bbMin[ b3 + 1 ];\n\t\t\t\tconst rn2 = rpMin[ n3 + 2 ], rb2 = bbMin[ b3 + 2 ];\n\t\t\t\trpMin[ b3 ] = rn0 < rb0 ? rn0 : rb0;\n\t\t\t\trpMin[ b3 + 1 ] = rn1 < rb1 ? rn1 : rb1;\n\t\t\t\trpMin[ b3 + 2 ] = rn2 < rb2 ? rn2 : rb2;\n\t\t\t\tconst rxn0 = rpMax[ n3 ], rxb0 = bbMax[ b3 ];\n\t\t\t\tconst rxn1 = rpMax[ n3 + 1 ], rxb1 = bbMax[ b3 + 1 ];\n\t\t\t\tconst rxn2 = rpMax[ n3 + 2 ], rxb2 = bbMax[ b3 + 2 ];\n\t\t\t\trpMax[ b3 ] = rxn0 > rxb0 ? rxn0 : rxb0;\n\t\t\t\trpMax[ b3 + 1 ] = rxn1 > rxb1 ? rxn1 : rxb1;\n\t\t\t\trpMax[ b3 + 2 ] = rxn2 > rxb2 ? rxn2 : rxb2;\n\n\t\t\t}\n\n\t\t\t// Evaluate splits using prefix sums (O(bins) instead of O(bins²))\n\t\t\tfor ( let i = 1; i < currentBinCount; i ++ ) {\n\n\t\t\t\tconst leftIdx = ( i - 1 ) * 3;\n\t\t\t\tconst rightIdx = i * 3;\n\t\t\t\tconst leftCount = lpc[ i - 1 ];\n\t\t\t\tconst rightCount = rpc[ i ];\n\n\t\t\t\tif ( leftCount === 0 || rightCount === 0 ) continue;\n\n\t\t\t\t// Inlined surface area: 2*(dx*dy + dy*dz + dz*dx)\n\t\t\t\tconst ldx = lpMax[ leftIdx ] - lpMin[ leftIdx ];\n\t\t\t\tconst ldy = lpMax[ leftIdx + 1 ] - lpMin[ leftIdx + 1 ];\n\t\t\t\tconst ldz = lpMax[ leftIdx + 2 ] - lpMin[ leftIdx + 2 ];\n\t\t\t\tconst leftSA = 2 * ( ldx * ldy + ldy * ldz + ldz * ldx );\n\n\t\t\t\tconst rdx = rpMax[ rightIdx ] - rpMin[ rightIdx ];\n\t\t\t\tconst rdy = rpMax[ rightIdx + 1 ] - rpMin[ rightIdx + 1 ];\n\t\t\t\tconst rdz = rpMax[ rightIdx + 2 ] - rpMin[ rightIdx + 2 ];\n\t\t\t\tconst rightSA = 2 * ( rdx * rdy + rdy * rdz + rdz * rdx );\n\n\t\t\t\tconst cost = this.traversalCost +\n\t\t\t\t\t( leftSA / parentSA ) * leftCount * this.intersectionCost +\n\t\t\t\t\t( rightSA / parentSA ) * rightCount * this.intersectionCost;\n\n\t\t\t\tif ( cost < bestCost && cost < leafCost ) {\n\n\t\t\t\t\tbestCost = cost;\n\t\t\t\t\tbestAxis = axis;\n\t\t\t\t\tbestPos = minCentroid + ( maxCentroid - minCentroid ) * i / currentBinCount;\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t}\n\n\t\t// Fallbacks\n\t\tif ( bestAxis === - 1 ) {\n\n\t\t\tif ( this.enableObjectMedianFallback ) return this.findObjectMedianSplit( start, end );\n\t\t\tif ( this.enableSpatialMedianFallback ) return this.findSpatialMedianSplit( start, end );\n\t\t\treturn { success: false, method: 'fallbacks_disabled' };\n\n\t\t}\n\n\t\treturn { success: true, axis: bestAxis, pos: bestPos, method: 'SAH', binsUsed: currentBinCount };\n\n\t}\n\n\tfindObjectMedianSplit( start, end ) {\n\n\t\tconst idx = this.indices;\n\t\tconst c = this.centroids;\n\t\tlet bestAxis = - 1;\n\t\tlet bestSpread = - 1;\n\n\t\tfor ( let axis = 0; axis < 3; axis ++ ) {\n\n\t\t\tlet minC = Infinity, maxC = - Infinity;\n\t\t\tfor ( let i = start; i < end; i ++ ) {\n\n\t\t\t\tconst v = c[ idx[ i ] * 3 + axis ];\n\t\t\t\tif ( v < minC ) minC = v;\n\t\t\t\tif ( v > maxC ) maxC = v;\n\n\t\t\t}\n\n\t\t\tconst spread = maxC - minC;\n\t\t\tif ( spread > bestSpread ) {\n\n\t\t\t\tbestSpread = spread;\n\t\t\t\tbestAxis = axis;\n\n\t\t\t}\n\n\t\t}\n\n\t\tif ( bestAxis === - 1 || bestSpread < 1e-10 ) {\n\n\t\t\tif ( this.enableSpatialMedianFallback ) return this.findSpatialMedianSplit( start, end );\n\t\t\treturn { success: false, method: 'object_median_failed' };\n\n\t\t}\n\n\t\t// Quickselect to find median centroid value in O(N) average\n\t\tconst count = end - start;\n\t\tconst k = start + Math.floor( count / 2 );\n\t\tthis.quickselect( start, end, k, bestAxis );\n\n\t\tlet splitPos = c[ idx[ k ] * 3 + bestAxis ];\n\n\t\t// Quickselect guarantees [start,k) <= splitPos, so leftCount >= k-start > 0.\n\t\t// Check if degenerate: all elements at [k+1,end) also <= splitPos (all same value).\n\t\tlet degenerate = true;\n\t\tfor ( let i = k + 1; i < end; i ++ ) {\n\n\t\t\tif ( c[ idx[ i ] * 3 + bestAxis ] > splitPos ) {\n\n\t\t\t\tdegenerate = false;\n\t\t\t\tbreak;\n\n\t\t\t}\n\n\t\t}\n\n\t\tif ( degenerate ) {\n\n\t\t\t// Nudge split between median and its left neighbor\n\t\t\tlet leftMax = - Infinity;\n\t\t\tfor ( let i = start; i < k; i ++ ) {\n\n\t\t\t\tconst v = c[ idx[ i ] * 3 + bestAxis ];\n\t\t\t\tif ( v > leftMax ) leftMax = v;\n\n\t\t\t}\n\n\t\t\tif ( leftMax < splitPos ) {\n\n\t\t\t\tsplitPos = ( leftMax + splitPos ) * 0.5;\n\n\t\t\t} else {\n\n\t\t\t\tif ( this.enableSpatialMedianFallback ) return this.findSpatialMedianSplit( start, end );\n\t\t\t\treturn { success: false, method: 'object_median_degenerate' };\n\n\t\t\t}\n\n\t\t}\n\n\t\treturn { success: true, axis: bestAxis, pos: splitPos, method: 'object_median' };\n\n\t}\n\n\tfindSpatialMedianSplit( start, end ) {\n\n\t\tconst idx = this.indices;\n\t\tconst c = this.centroids;\n\t\tconst bMn = this.bMin;\n\t\tconst bMx = this.bMax;\n\t\tlet bestAxis = - 1;\n\t\tlet bestSpread = - 1;\n\t\tlet bestMin = 0, bestMax = 0;\n\n\t\tfor ( let axis = 0; axis < 3; axis ++ ) {\n\n\t\t\tlet minB = Infinity, maxB = - Infinity;\n\t\t\tfor ( let i = start; i < end; i ++ ) {\n\n\t\t\t\tconst o = idx[ i ] * 3 + axis;\n\t\t\t\tif ( bMn[ o ] < minB ) minB = bMn[ o ];\n\t\t\t\tif ( bMx[ o ] > maxB ) maxB = bMx[ o ];\n\n\t\t\t}\n\n\t\t\tconst spread = maxB - minB;\n\t\t\tif ( spread > bestSpread ) {\n\n\t\t\t\tbestSpread = spread;\n\t\t\t\tbestAxis = axis;\n\t\t\t\tbestMin = minB;\n\t\t\t\tbestMax = maxB;\n\n\t\t\t}\n\n\t\t}\n\n\t\tif ( bestAxis === - 1 || bestSpread < 1e-12 ) {\n\n\t\t\treturn { success: false, method: 'spatial_median_failed' };\n\n\t\t}\n\n\t\tlet splitPos = ( bestMin + bestMax ) * 0.5;\n\n\t\t// Verify split quality\n\t\tconst count = end - start;\n\t\tlet leftCount = 0;\n\t\tfor ( let i = start; i < end; i ++ ) {\n\n\t\t\tif ( c[ idx[ i ] * 3 + bestAxis ] <= splitPos ) leftCount ++;\n\n\t\t}\n\n\t\tif ( leftCount === 0 || leftCount === count ) {\n\n\t\t\t// Force balanced via quickselect median (O(N) instead of O(N log N) sort)\n\t\t\tconst k = start + Math.floor( count / 2 );\n\t\t\tthis.quickselect( start, end, k, bestAxis );\n\n\t\t\tconst medianVal = c[ idx[ k ] * 3 + bestAxis ];\n\n\t\t\t// Check if all centroids are identical on this axis\n\t\t\tlet allSame = true;\n\t\t\tfor ( let i = start; i < end; i ++ ) {\n\n\t\t\t\tif ( c[ idx[ i ] * 3 + bestAxis ] !== medianVal ) {\n\n\t\t\t\t\tallSame = false;\n\t\t\t\t\tbreak;\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\tif ( allSame ) {\n\n\t\t\t\treturn { success: false, method: 'spatial_median_degenerate' };\n\n\t\t\t}\n\n\t\t\t// Nudge split between median and its neighbor to guarantee a non-empty partition\n\t\t\tlet leftMax = - Infinity;\n\t\t\tfor ( let i = start; i < k; i ++ ) {\n\n\t\t\t\tconst v = c[ idx[ i ] * 3 + bestAxis ];\n\t\t\t\tif ( v > leftMax ) leftMax = v;\n\n\t\t\t}\n\n\t\t\tif ( leftMax < medianVal ) {\n\n\t\t\t\tsplitPos = ( leftMax + medianVal ) * 0.5;\n\n\t\t\t} else {\n\n\t\t\t\t// leftMax == medianVal; find first element > medianVal in right half\n\t\t\t\tlet rightMin = Infinity;\n\t\t\t\tfor ( let i = k + 1; i < end; i ++ ) {\n\n\t\t\t\t\tconst v = c[ idx[ i ] * 3 + bestAxis ];\n\t\t\t\t\tif ( v < rightMin ) rightMin = v;\n\n\t\t\t\t}\n\n\t\t\t\tsplitPos = ( medianVal + rightMin ) * 0.5;\n\n\t\t\t}\n\n\t\t}\n\n\t\treturn { success: true, axis: bestAxis, pos: splitPos, method: 'spatial_median' };\n\n\t}\n\n\t// --- Quickselect (Hoare's selection algorithm) ---\n\n\tquickselect( start, end, k, axis ) {\n\n\t\tconst idx = this.indices;\n\t\tconst c = this.centroids;\n\n\t\tlet lo = start;\n\t\tlet hi = end - 1;\n\n\t\twhile ( lo < hi ) {\n\n\t\t\t// Median-of-three pivot selection\n\t\t\tconst mid = ( lo + hi ) >>> 1;\n\t\t\tconst vLo = c[ idx[ lo ] * 3 + axis ];\n\t\t\tconst vMid = c[ idx[ mid ] * 3 + axis ];\n\t\t\tconst vHi = c[ idx[ hi ] * 3 + axis ];\n\n\t\t\t// Sort lo, mid, hi and use mid as pivot\n\t\t\tif ( vLo > vMid ) {\n\n\t\t\t\tconst t = idx[ lo ];\n\t\t\t\tidx[ lo ] = idx[ mid ];\n\t\t\t\tidx[ mid ] = t;\n\n\t\t\t}\n\n\t\t\tif ( vLo > vHi ) {\n\n\t\t\t\tconst t = idx[ lo ];\n\t\t\t\tidx[ lo ] = idx[ hi ];\n\t\t\t\tidx[ hi ] = t;\n\n\t\t\t}\n\n\t\t\tif ( vMid > vHi ) {\n\n\t\t\t\tconst t = idx[ mid ];\n\t\t\t\tidx[ mid ] = idx[ hi ];\n\t\t\t\tidx[ hi ] = t;\n\n\t\t\t}\n\n\t\t\tconst pivot = c[ idx[ mid ] * 3 + axis ];\n\n\t\t\t// Partition around pivot\n\t\t\tlet i = lo;\n\t\t\tlet j = hi;\n\n\t\t\twhile ( i <= j ) {\n\n\t\t\t\twhile ( c[ idx[ i ] * 3 + axis ] < pivot ) i ++;\n\t\t\t\twhile ( c[ idx[ j ] * 3 + axis ] > pivot ) j --;\n\n\t\t\t\tif ( i <= j ) {\n\n\t\t\t\t\tconst t = idx[ i ]; idx[ i ] = idx[ j ]; idx[ j ] = t;\n\t\t\t\t\ti ++;\n\t\t\t\t\tj --;\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\tif ( j < k ) lo = i;\n\t\t\tif ( i > k ) hi = j;\n\n\t\t}\n\n\t}\n\n\t// --- Surface-area child ordering (DFS cache locality) ---\n\n\t/**\n\t * Ensure left child always has >= surface area of right child.\n\t * This places the larger subtree first in the DFS flat layout,\n\t * improving cache locality during traversal.\n\t * Iterative post-order to avoid stack overflow on deep trees.\n\t */\n\tapplySAOrdering( root ) {\n\n\t\tif ( ! root || ! root.leftChild ) return;\n\n\t\t// Iterative post-order traversal — swap after both children are processed\n\t\tconst stack = [ root ];\n\t\tconst order = [];\n\n\t\twhile ( stack.length > 0 ) {\n\n\t\t\tconst node = stack.pop();\n\t\t\tif ( ! node.leftChild || ! node.rightChild ) continue;\n\n\t\t\torder.push( node );\n\t\t\tstack.push( node.leftChild );\n\t\t\tstack.push( node.rightChild );\n\n\t\t}\n\n\t\t// Process in reverse (bottom-up)\n\t\tfor ( let i = order.length - 1; i >= 0; i -- ) {\n\n\t\t\tconst node = order[ i ];\n\t\t\tconst L = node.leftChild;\n\t\t\tconst R = node.rightChild;\n\n\t\t\tconst ldx = L.maxX - L.minX, ldy = L.maxY - L.minY, ldz = L.maxZ - L.minZ;\n\t\t\tconst rdx = R.maxX - R.minX, rdy = R.maxY - R.minY, rdz = R.maxZ - R.minZ;\n\n\t\t\tif ( rdx * rdy + rdy * rdz + rdz * rdx > ldx * ldy + ldy * ldz + ldz * ldx ) {\n\n\t\t\t\tnode.leftChild = R;\n\t\t\t\tnode.rightChild = L;\n\n\t\t\t}\n\n\t\t}\n\n\t}\n\n\t// --- BVH flattening (GPU-ready format) ---\n\n\t/**\n\t * Flatten BVH tree into a Float32Array (16 floats per node).\n\t * Layout per node (4 × vec4):\n\t * Inner: vec4( leftMin.xyz, leftChildIdx ) vec4( leftMax.xyz, rightChildIdx )\n\t * vec4( rightMin.xyz, 0 ) vec4( rightMax.xyz, 0 )\n\t * Leaf: vec4( triOffset, triCount, 0, -1 ) [zeros × 12]\n\t *\n\t * This is the same format as TextureCreator.createBVHRawData,\n\t * but runs inside the worker to avoid structured-clone overhead\n\t * of transferring 1M+ BVHNode objects.\n\t */\n\tflattenBVH( root ) {\n\n\t\t// First pass: assign indices via pre-order traversal\n\t\tconst nodes = [];\n\t\tconst stack = [ root ];\n\t\twhile ( stack.length > 0 ) {\n\n\t\t\tconst node = stack.pop();\n\t\t\tnode._flatIndex = nodes.length;\n\t\t\tnodes.push( node );\n\t\t\t// Push right first so left is processed first (pre-order)\n\t\t\tif ( node.rightChild ) stack.push( node.rightChild );\n\t\t\tif ( node.leftChild ) stack.push( node.leftChild );\n\n\t\t}\n\n\t\t// Second pass: write flat data\n\t\t// Layout: 4 vec4 per node (16 floats)\n\t\t// Inner: [leftMin.xyz, leftChild] [leftMax.xyz, rightChild] [rightMin.xyz, 0] [rightMax.xyz, 0]\n\t\t// Leaf: [triOffset, triCount, 0, -1] [0,0,0,0] [0,0,0,0] [0,0,0,0]\n\t\tconst FLOATS_PER_NODE = 16;\n\t\tconst data = new Float32Array( nodes.length * FLOATS_PER_NODE );\n\n\t\tfor ( let i = 0; i < nodes.length; i ++ ) {\n\n\t\t\tconst node = nodes[ i ];\n\t\t\tconst o = i * FLOATS_PER_NODE;\n\n\t\t\tif ( node.leftChild ) {\n\n\t\t\t\t// Inner node: store children's AABBs\n\t\t\t\tconst left = node.leftChild;\n\t\t\t\tconst right = node.rightChild;\n\n\t\t\t\tdata[ o ] = left.minX;\n\t\t\t\tdata[ o + 1 ] = left.minY;\n\t\t\t\tdata[ o + 2 ] = left.minZ;\n\t\t\t\tdata[ o + 3 ] = left._flatIndex;\n\n\t\t\t\tdata[ o + 4 ] = left.maxX;\n\t\t\t\tdata[ o + 5 ] = left.maxY;\n\t\t\t\tdata[ o + 6 ] = left.maxZ;\n\t\t\t\tdata[ o + 7 ] = right._flatIndex;\n\n\t\t\t\tdata[ o + 8 ] = right.minX;\n\t\t\t\tdata[ o + 9 ] = right.minY;\n\t\t\t\tdata[ o + 10 ] = right.minZ;\n\t\t\t\t// data[o+11] = 0 (padding)\n\n\t\t\t\tdata[ o + 12 ] = right.maxX;\n\t\t\t\tdata[ o + 13 ] = right.maxY;\n\t\t\t\tdata[ o + 14 ] = right.maxZ;\n\t\t\t\t// data[o+15] = 0 (padding)\n\n\t\t\t} else {\n\n\t\t\t\t// Leaf node: triOffset, triCount in vec4(0), marked by leftChild = -1\n\t\t\t\tdata[ o ] = node.triangleOffset;\n\t\t\t\tdata[ o + 1 ] = node.triangleCount;\n\t\t\t\t// data[o+2] = 0 (padding)\n\t\t\t\tdata[ o + 3 ] = - 1; // Leaf marker\n\n\t\t\t}\n\n\t\t}\n\n\t\treturn data;\n\n\t}\n\n\t/**\n\t * Flatten BVH tree marking frontier leaves with -2 sentinel.\n\t * Returns the flat data and a frontier map for assembly.\n\t * @param {BVHNode} root\n\t * @returns {{ flatData: Float32Array, frontierMap: Array<{taskId: number, flatIndex: number}> }}\n\t */\n\tflattenBVHWithFrontier( root ) {\n\n\t\tconst FLOATS_PER_NODE = 16;\n\n\t\t// First pass: assign indices via pre-order traversal\n\t\tconst nodes = [];\n\t\tconst stack = [ root ];\n\t\twhile ( stack.length > 0 ) {\n\n\t\t\tconst node = stack.pop();\n\t\t\tnode._flatIndex = nodes.length;\n\t\t\tnodes.push( node );\n\t\t\tif ( node.rightChild ) stack.push( node.rightChild );\n\t\t\tif ( node.leftChild ) stack.push( node.leftChild );\n\n\t\t}\n\n\t\t// Second pass: write flat data\n\t\tconst data = new Float32Array( nodes.length * FLOATS_PER_NODE );\n\t\tconst frontierMap = [];\n\n\t\tfor ( let i = 0; i < nodes.length; i ++ ) {\n\n\t\t\tconst node = nodes[ i ];\n\t\t\tconst o = i * FLOATS_PER_NODE;\n\n\t\t\tif ( node.leftChild ) {\n\n\t\t\t\t// Inner node\n\t\t\t\tconst left = node.leftChild;\n\t\t\t\tconst right = node.rightChild;\n\n\t\t\t\tdata[ o ] = left.minX;\n\t\t\t\tdata[ o + 1 ] = left.minY;\n\t\t\t\tdata[ o + 2 ] = left.minZ;\n\t\t\t\tdata[ o + 3 ] = left._flatIndex;\n\n\t\t\t\tdata[ o + 4 ] = left.maxX;\n\t\t\t\tdata[ o + 5 ] = left.maxY;\n\t\t\t\tdata[ o + 6 ] = left.maxZ;\n\t\t\t\tdata[ o + 7 ] = right._flatIndex;\n\n\t\t\t\tdata[ o + 8 ] = right.minX;\n\t\t\t\tdata[ o + 9 ] = right.minY;\n\t\t\t\tdata[ o + 10 ] = right.minZ;\n\n\t\t\t\tdata[ o + 12 ] = right.maxX;\n\t\t\t\tdata[ o + 13 ] = right.maxY;\n\t\t\t\tdata[ o + 14 ] = right.maxZ;\n\n\t\t\t} else if ( node.isFrontier ) {\n\n\t\t\t\t// Frontier leaf: mark with -2 sentinel, use the taskId stored on the node\n\t\t\t\tconst taskId = node.frontierTaskId;\n\t\t\t\tdata[ o ] = node.triangleOffset;\n\t\t\t\tdata[ o + 1 ] = node.triangleCount;\n\t\t\t\tdata[ o + 2 ] = taskId;\n\t\t\t\tdata[ o + 3 ] = - 2; // Frontier sentinel\n\n\t\t\t\tfrontierMap.push( { taskId, flatIndex: i } );\n\n\t\t\t} else {\n\n\t\t\t\t// Regular leaf\n\t\t\t\tdata[ o ] = node.triangleOffset;\n\t\t\t\tdata[ o + 1 ] = node.triangleCount;\n\t\t\t\tdata[ o + 3 ] = - 1; // Leaf marker\n\n\t\t\t}\n\n\t\t}\n\n\t\treturn { flatData: data, frontierMap, nodeCount: nodes.length };\n\n\t}\n\n\t/**\n\t * Assemble the final BVH from top-level flat data and parallel-built subtrees.\n\t * @param {Float32Array} topFlatData - Flattened top-level tree with frontier sentinels\n\t * @param {number} topNodeCount - Number of nodes in top-level tree\n\t * @param {Array} frontierMap - Array of {taskId, flatIndex} from flattenBVHWithFrontier\n\t * @param {Array<{taskId: number, flatData: Float32Array, nodeCount: number}>} subtreeResults\n\t * @returns {Float32Array} Final GPU-ready BVH flat data\n\t */\n\tassembleParallelBVH( topFlatData, topNodeCount, frontierMap, subtreeResults ) {\n\n\t\tconst FLOATS_PER_NODE = 16;\n\n\t\t// Sort subtreeResults by taskId for consistent ordering\n\t\tconst sortedResults = [ ...subtreeResults ].sort( ( a, b ) => a.taskId - b.taskId );\n\n\t\t// Calculate total node count\n\t\tlet totalNodes = topNodeCount;\n\t\tfor ( let i = 0; i < sortedResults.length; i ++ ) {\n\n\t\t\ttotalNodes += sortedResults[ i ].nodeCount;\n\n\t\t}\n\n\t\t// Allocate final array\n\t\tconst finalData = new Float32Array( totalNodes * FLOATS_PER_NODE );\n\n\t\t// Copy top-level data\n\t\tfinalData.set( topFlatData );\n\n\t\t// Build taskId → frontierMap lookup\n\t\tconst frontierByTaskId = new Map();\n\t\tfor ( const entry of frontierMap ) {\n\n\t\t\tfrontierByTaskId.set( entry.taskId, entry.flatIndex );\n\n\t\t}\n\n\t\t// Append each subtree and patch references\n\t\tlet globalOffset = topNodeCount;\n\n\t\tfor ( let i = 0; i < sortedResults.length; i ++ ) {\n\n\t\t\tconst result = sortedResults[ i ];\n\t\t\tconst subtreeData = result.flatData;\n\t\t\tconst subtreeNodeCount = result.nodeCount;\n\t\t\tconst destOffset = globalOffset * FLOATS_PER_NODE;\n\n\t\t\t// Copy subtree data into final array\n\t\t\tfinalData.set( subtreeData, destOffset );\n\n\t\t\t// Adjust child indices within the subtree by adding globalOffset\n\t\t\tfor ( let j = 0; j < subtreeNodeCount; j ++ ) {\n\n\t\t\t\tconst o = destOffset + j * FLOATS_PER_NODE;\n\n\t\t\t\t// Check if inner node (not a leaf: leaf has -1 at o+3)\n\t\t\t\tif ( finalData[ o + 3 ] !== - 1 ) {\n\n\t\t\t\t\t// Adjust leftChildIndex and rightChildIndex\n\t\t\t\t\tfinalData[ o + 3 ] += globalOffset;\n\t\t\t\t\tfinalData[ o + 7 ] += globalOffset;\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\t// Overwrite frontier leaf with subtree root data\n\t\t\tconst frontierFlatIndex = frontierByTaskId.get( result.taskId );\n\t\t\tif ( frontierFlatIndex !== undefined ) {\n\n\t\t\t\tconst frontierOffset = frontierFlatIndex * FLOATS_PER_NODE;\n\t\t\t\tconst subtreeRootOffset = destOffset;\n\n\t\t\t\t// Copy subtree root's 16 floats over the frontier leaf\n\t\t\t\tfor ( let k = 0; k < FLOATS_PER_NODE; k ++ ) {\n\n\t\t\t\t\tfinalData[ frontierOffset + k ] = finalData[ subtreeRootOffset + k ];\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\tglobalOffset += subtreeNodeCount;\n\n\t\t}\n\n\t\treturn finalData;\n\n\t}\n\n\t// --- Surface area helpers ---\n\n\tcomputeSurfaceAreaFlat( minX, minY, minZ, maxX, maxY, maxZ ) {\n\n\t\tconst dx = maxX - minX;\n\t\tconst dy = maxY - minY;\n\t\tconst dz = maxZ - minZ;\n\t\treturn 2 * ( dx * dy + dy * dz + dz * dx );\n\n\t}\n\n}\n","import { BVHBuilder } from '../BVHBuilder.js';\nimport { TreeletOptimizer } from '../TreeletOptimizer.js';\nimport { ReinsertionOptimizer } from '../ReinsertionOptimizer.js';\n\nself.onmessage = function ( e ) {\n\n\tconst {\n\t\ttasks,\n\t\tsharedTriangleData, sharedCentroids, sharedBMin, sharedBMax, sharedIndices,\n\t\ttriangleCount,\n\t\tmaxLeafSize, numBins, maxBins, minBins,\n\t\ttreeletConfig,\n\t\treinsertionConfig,\n\t\treportProgress\n\t} = e.data;\n\n\t// Process each task sequentially\n\tfor ( let t = 0; t < tasks.length; t ++ ) {\n\n\t\tconst task = tasks[ t ];\n\n\t\ttry {\n\n\t\t\tconst builder = new BVHBuilder();\n\n\t\t\t// Apply configuration\n\t\t\tbuilder.maxLeafSize = maxLeafSize;\n\t\t\tbuilder.numBins = numBins;\n\t\t\tbuilder.maxBins = maxBins;\n\t\t\tbuilder.minBins = minBins;\n\n\t\t\t// Attach shared buffer views (read-only for triangles/centroids/bounds,\n\t\t\t// write to disjoint [start,end) range for indices)\n\t\t\tbuilder.triangles = new Float32Array( sharedTriangleData );\n\t\t\tbuilder.centroids = new Float32Array( sharedCentroids );\n\t\t\tbuilder.bMin = new Float32Array( sharedBMin );\n\t\t\tbuilder.bMax = new Float32Array( sharedBMax );\n\t\t\tbuilder.indices = new Uint32Array( sharedIndices );\n\t\t\tbuilder.totalTriangles = triangleCount;\n\n\t\t\t// Reset build state\n\t\t\tbuilder.totalNodes = 0;\n\t\t\tbuilder.processedTriangles = 0;\n\t\t\tbuilder.lastProgressUpdate = performance.now();\n\n\t\t\tbuilder.splitStats = {\n\t\t\t\tsahSplits: 0, objectMedianSplits: 0, spatialMedianSplits: 0,\n\t\t\t\tfailedSplits: 0, avgBinsUsed: 0, totalSplitAttempts: 0,\n\t\t\t\tmortonSortTime: 0, totalBuildTime: 0, treeletOptimizationTime: 0,\n\t\t\t\ttreeletsProcessed: 0, treeletsImproved: 0, averageSAHImprovement: 0,\n\t\t\t\tinitTime: 0, sahBuildTime: 0, reorderTime: 0\n\t\t\t};\n\n\t\t\tconst progressCallback = reportProgress ? ( progress ) => {\n\n\t\t\t\tself.postMessage( {\n\t\t\t\t\ttype: 'progress',\n\t\t\t\t\ttaskId: task.taskId,\n\t\t\t\t\tprogress\n\t\t\t\t} );\n\n\t\t\t} : null;\n\n\t\t\tconst startTime = performance.now();\n\n\t\t\t// Build subtree using precomputed shared data\n\t\t\tconst root = builder.buildNodeRecursive(\n\t\t\t\ttask.start, task.end, task.depth, progressCallback,\n\t\t\t\ttask.preMinX, task.preMinY, task.preMinZ,\n\t\t\t\ttask.preMaxX, task.preMaxY, task.preMaxZ\n\t\t\t);\n\n\t\t\t// Treelet optimization on subtree\n\t\t\tif ( treeletConfig && treeletConfig.enabled && ( task.end - task.start ) > 1000 ) {\n\n\t\t\t\tconst isLargeSubtree = ( task.end - task.start ) > 50000;\n\t\t\t\tconst adaptiveSize = isLargeSubtree ? 3 : ( treeletConfig.size || 5 );\n\t\t\t\tconst adaptiveMax = isLargeSubtree ? 10 : 20;\n\n\t\t\t\tconst optimizer = new TreeletOptimizer( builder.traversalCost, builder.intersectionCost );\n\t\t\t\toptimizer.setTreeletSize( adaptiveSize );\n\t\t\t\toptimizer.setMinImprovement( treeletConfig.minImprovement || 0.02 );\n\t\t\t\toptimizer.setMaxTreelets( adaptiveMax );\n\n\t\t\t\tconst passes = treeletConfig.passes || 1;\n\t\t\t\tfor ( let pass = 0; pass < passes; pass ++ ) {\n\n\t\t\t\t\ttry {\n\n\t\t\t\t\t\toptimizer.optimizeBVH( root, null );\n\n\t\t\t\t\t} catch ( err ) {\n\n\t\t\t\t\t\tconsole.error( `[BVHSubtreeWorker] Treelet pass ${pass + 1} error:`, err );\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t}\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\t// Reinsertion optimization on subtree\n\t\t\tif ( reinsertionConfig && reinsertionConfig.enabled && ( task.end - task.start ) > 1000 ) {\n\n\t\t\t\tconst reinsertionOptimizer = new ReinsertionOptimizer( builder.traversalCost, builder.intersectionCost );\n\t\t\t\tif ( reinsertionConfig.batchSizeRatio ) reinsertionOptimizer.setBatchSizeRatio( reinsertionConfig.batchSizeRatio );\n\t\t\t\tif ( reinsertionConfig.maxIterations ) reinsertionOptimizer.setMaxIterations( reinsertionConfig.maxIterations );\n\n\t\t\t\ttry {\n\n\t\t\t\t\treinsertionOptimizer.optimizeBVH( root, null );\n\n\t\t\t\t} catch ( err ) {\n\n\t\t\t\t\tconsole.error( `[BVHSubtreeWorker] Reinsertion error:`, err );\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\t// Surface-area child ordering (DFS cache locality)\n\t\t\tbuilder.applySAOrdering( root );\n\n\t\t\t// Flatten subtree (local indices starting from 0)\n\t\t\tconst flatData = builder.flattenBVH( root );\n\t\t\tconst nodeCount = flatData.length / 16;\n\n\t\t\tconst buildTime = performance.now() - startTime;\n\t\t\tconsole.log( `[BVHSubtreeWorker] Task ${task.taskId}: ${( task.end - task.start ).toLocaleString()} triangles, ${nodeCount} nodes, ${Math.round( buildTime )}ms` );\n\n\t\t\tself.postMessage( {\n\t\t\t\ttype: 'subtreeResult',\n\t\t\t\ttaskId: task.taskId,\n\t\t\t\tflatData,\n\t\t\t\tnodeCount\n\t\t\t}, [ flatData.buffer ] );\n\n\t\t} catch ( error ) {\n\n\t\t\tconsole.error( `[BVHSubtreeWorker] Task ${task.taskId} error:`, error );\n\t\t\tself.postMessage( {\n\t\t\t\ttype: 'error',\n\t\t\t\ttaskId: task.taskId,\n\t\t\t\terror: error.message\n\t\t\t} );\n\n\t\t}\n\n\t}\n\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(function(){var e=class{constructor(e,t){this.traversalCost=e,this.intersectionCost=t,this.maxTreeletLeaves=7,this.minImprovement=.02,this.topologyCache=new Map;for(let e=3;e<=this.maxTreeletLeaves;e++)this.topologyCache.set(e,this.generateTopologies(e));this.stats={treeletsProcessed:0,treeletsImproved:0,totalSAHImprovement:0,averageSAHImprovement:0,optimizationTime:0}}generateTopologies(e){if(e===1)return[0];if(e===2)return[[0,1]];let t=[];for(let n=1;n<e;n++){let r=this.generateTopologies(n),i=this.generateTopologies(e-n);for(let e of r)for(let r of i)t.push([e,this.offsetTopology(r,n)])}return t}offsetTopology(e,t){return typeof e==`number`?e+t:[this.offsetTopology(e[0],t),this.offsetTopology(e[1],t)]}optimizeBVH(e){let t=performance.now();this.stats={treeletsProcessed:0,treeletsImproved:0,totalSAHImprovement:0,averageSAHImprovement:0,optimizationTime:0};let n=this.identifyTreeletRoots(e);for(let e=0;e<n.length;e++){if(performance.now()-t>3e4){console.warn(`TreeletOptimizer: timeout after ${e}/${n.length} treelets`);break}this.optimizeTreelet(n[e])}return this.stats.optimizationTime=performance.now()-t,this.stats.averageSAHImprovement=this.stats.treeletsProcessed>0?this.stats.totalSAHImprovement/this.stats.treeletsProcessed:0,e}identifyTreeletRoots(e){let t=[],n=new Set,r=[{node:e,visited:!1}];for(;r.length>0;){let e=r[r.length-1];if(e.visited){r.pop();let i=e.node;if(i.triangleCount>0||n.has(i))continue;let a=this.countLeaves(i);a>=3&&a<=this.maxTreeletLeaves&&(t.push(i),this.markSubtree(i,n))}else{e.visited=!0;let t=e.node;if(t.triangleCount>0)continue;t.rightChild&&r.push({node:t.rightChild,visited:!1}),t.leftChild&&r.push({node:t.leftChild,visited:!1})}}return t}countLeaves(e){return e?e.triangleCount>0?1:this.countLeaves(e.leftChild)+this.countLeaves(e.rightChild):0}markSubtree(e,t){e&&(t.add(e),!(e.triangleCount>0)&&(this.markSubtree(e.leftChild,t),this.markSubtree(e.rightChild,t)))}optimizeTreelet(e){let t=[];this.extractLeaves(e,t);let n=t.length;if(n<3||n>this.maxTreeletLeaves)return;this.stats.treeletsProcessed++;let r=this.evaluateSubtreeSAH(e),i=this.topologyCache.get(n);if(!i||i.length===0)return;let a=r,o=null,s=null;if(n<=5){let e=this.generatePermutations(n);for(let n of i)for(let r of e){let e=this.evaluateTopology(n,t,r);e<a&&(a=e,o=n,s=r)}}else{let e=Array.from({length:n},(e,t)=>t);for(let n of i){let r=this.evaluateTopology(n,t,e);r<a&&(a=r,o=n,s=e);let i=this.greedySwapOptimize(n,t,e,r);i.cost<a&&(a=i.cost,o=n,s=i.perm)}}let c=(r-a)/r;o&&c>this.minImprovement&&(this.reconstructTreelet(e,o,t,s),this.stats.treeletsImproved++,this.stats.totalSAHImprovement+=c)}extractLeaves(e,t){if(e){if(e.triangleCount>0){t.push({minX:e.minX,minY:e.minY,minZ:e.minZ,maxX:e.maxX,maxY:e.maxY,maxZ:e.maxZ,triangleOffset:e.triangleOffset,triangleCount:e.triangleCount});return}this.extractLeaves(e.leftChild,t),this.extractLeaves(e.rightChild,t)}}evaluateSubtreeSAH(e){if(!e)return 0;if(e.triangleCount>0)return this.surfaceAreaFlat(e.minX,e.minY,e.minZ,e.maxX,e.maxY,e.maxZ)*e.triangleCount*this.intersectionCost;let t=this.evaluateSubtreeSAH(e.leftChild),n=this.evaluateSubtreeSAH(e.rightChild);return this.surfaceAreaFlat(e.minX,e.minY,e.minZ,e.maxX,e.maxY,e.maxZ)*this.traversalCost+t+n}evaluateTopology(e,t,n){return this.evalTopoRecursive(e,t,n).cost}evalTopoRecursive(e,t,n){if(typeof e==`number`){let r=t[n[e]];return{cost:this.surfaceAreaFlat(r.minX,r.minY,r.minZ,r.maxX,r.maxY,r.maxZ)*r.triangleCount*this.intersectionCost,minX:r.minX,minY:r.minY,minZ:r.minZ,maxX:r.maxX,maxY:r.maxY,maxZ:r.maxZ}}let r=this.evalTopoRecursive(e[0],t,n),i=this.evalTopoRecursive(e[1],t,n),a=Math.min(r.minX,i.minX),o=Math.min(r.minY,i.minY),s=Math.min(r.minZ,i.minZ),c=Math.max(r.maxX,i.maxX),l=Math.max(r.maxY,i.maxY),u=Math.max(r.maxZ,i.maxZ);return{cost:this.surfaceAreaFlat(a,o,s,c,l,u)*this.traversalCost+r.cost+i.cost,minX:a,minY:o,minZ:s,maxX:c,maxY:l,maxZ:u}}surfaceAreaFlat(e,t,n,r,i,a){let o=r-e,s=i-t,c=a-n;return 2*(o*s+s*c+c*o)}generatePermutations(e){let t=[],n=Array.from({length:e},(e,t)=>t),r=i=>{if(i===e){t.push([...n]);return}for(let t=i;t<e;t++)[n[i],n[t]]=[n[t],n[i]],r(i+1),[n[i],n[t]]=[n[t],n[i]]};return r(0),t}greedySwapOptimize(e,t,n,r){let i=[...n],a=r,o=!0;for(;o;){o=!1;for(let n=0;n<i.length-1;n++)for(let r=n+1;r<i.length;r++){[i[n],i[r]]=[i[r],i[n]];let s=this.evaluateTopology(e,t,i);s<a?(a=s,o=!0):[i[n],i[r]]=[i[r],i[n]]}}return{perm:i,cost:a}}reconstructTreelet(e,t,n,r){let i=this.buildSubtree(t,n,r);e.minX=i.minX,e.minY=i.minY,e.minZ=i.minZ,e.maxX=i.maxX,e.maxY=i.maxY,e.maxZ=i.maxZ,e.leftChild=i.leftChild,e.rightChild=i.rightChild,e.triangleOffset=i.triangleOffset,e.triangleCount=i.triangleCount}buildSubtree(e,n,r){if(typeof e==`number`){let i=n[r[e]],a=new t;return a.minX=i.minX,a.minY=i.minY,a.minZ=i.minZ,a.maxX=i.maxX,a.maxY=i.maxY,a.maxZ=i.maxZ,a.triangleOffset=i.triangleOffset,a.triangleCount=i.triangleCount,a}let i=this.buildSubtree(e[0],n,r),a=this.buildSubtree(e[1],n,r),o=new t;return o.leftChild=i,o.rightChild=a,o.minX=Math.min(i.minX,a.minX),o.minY=Math.min(i.minY,a.minY),o.minZ=Math.min(i.minZ,a.minZ),o.maxX=Math.max(i.maxX,a.maxX),o.maxY=Math.max(i.maxY,a.maxY),o.maxZ=Math.max(i.maxZ,a.maxZ),o}setTreeletSize(e){this.maxTreeletLeaves=Math.max(3,Math.min(7,e));for(let e=3;e<=this.maxTreeletLeaves;e++)this.topologyCache.has(e)||this.topologyCache.set(e,this.generateTopologies(e))}setMinImprovement(e){this.minImprovement=Math.max(.001,e)}setMaxTreelets(){}getStatistics(){return{...this.stats}}},t=class{constructor(){this.minX=0,this.minY=0,this.minZ=0,this.maxX=0,this.maxY=0,this.maxZ=0,this.leftChild=null,this.rightChild=null,this.triangleOffset=0,this.triangleCount=0}},n=class{constructor(e,t){this.traversalCost=e,this.intersectionCost=t,this.batchSizeRatio=.02,this.maxIterations=2,this.timeBudgetMs=15e3,this.stats={reinsertionsApplied:0,iterations:0,timeMs:0}}setBatchSizeRatio(e){this.batchSizeRatio=Math.max(.005,Math.min(.1,e))}setMaxIterations(e){this.maxIterations=Math.max(1,Math.min(5,e))}getStatistics(){return{...this.stats}}surfaceArea(e){let t=e.maxX-e.minX,n=e.maxY-e.minY,r=e.maxZ-e.minZ;return t*n+n*r+r*t}buildParentMap(e){let t=new Map;t.set(e,{parent:null,isLeft:!1});let n=[e];for(;n.length>0;){let e=n.pop();e.triangleCount>0||(e.leftChild&&(t.set(e.leftChild,{parent:e,isLeft:!0}),n.push(e.leftChild)),e.rightChild&&(t.set(e.rightChild,{parent:e,isLeft:!1}),n.push(e.rightChild)))}return t}findCandidates(e,t,n){let r=[],i=[e];for(;i.length>0;){let a=i.pop();if(a!==e&&n.get(a).parent!==e){let e=this.surfaceArea(a);r.length<t?(r.push({node:a,cost:e}),r.length===t&&this._heapify(r)):e>r[0].cost&&(r[0]={node:a,cost:e},this._siftDown(r,0))}a.triangleCount===0&&(a.leftChild&&i.push(a.leftChild),a.rightChild&&i.push(a.rightChild))}return r}_heapify(e){for(let t=(e.length>>1)-1;t>=0;t--)this._siftDown(e,t)}_siftDown(e,t){let n=e.length;for(;;){let r=t,i=2*t+1,a=2*t+2;if(i<n&&e[i].cost<e[r].cost&&(r=i),a<n&&e[a].cost<e[r].cost&&(r=a),r===t)break;let o=e[t];e[t]=e[r],e[r]=o,t=r}}findReinsertion(e,t,n){let r=n.get(e),i=r.parent;if(!i)return null;let a=r.isLeft?i.rightChild:i.leftChild,o=this.surfaceArea(e),s=this.surfaceArea(i),c=null,l=0,u=s,d=a.minX,f=a.minY,p=a.minZ,m=a.maxX,h=a.maxY,g=a.maxZ,_=a,v=i,y=[];do{for(y.length=0,y.push(u,_);y.length>0;){let t=y.pop(),n=y.pop();if(n-o<=l)continue;let r=Math.min(t.minX,e.minX),i=Math.min(t.minY,e.minY),a=Math.min(t.minZ,e.minZ),s=Math.max(t.maxX,e.maxX),u=Math.max(t.maxY,e.maxY),d=Math.max(t.maxZ,e.maxZ),f=s-r,p=u-i,m=d-a,h=n-(f*p+p*m+m*f);if(h>l&&(c=t,l=h),t.triangleCount===0&&t.leftChild&&t.rightChild){let e=h+this.surfaceArea(t);y.push(e,t.leftChild),y.push(e,t.rightChild)}}let t=n.get(v);if(!t||t.parent===null)break;if(v!==i){d=Math.min(d,_.minX),f=Math.min(f,_.minY),p=Math.min(p,_.minZ),m=Math.max(m,_.maxX),h=Math.max(h,_.maxY),g=Math.max(g,_.maxZ);let e=m-d,t=h-f,n=g-p,r=e*t+t*n+n*e;u+=this.surfaceArea(v)-r}let r=t.parent;_=t.isLeft?r.rightChild:r.leftChild,v=r}while(n.get(v).parent!==null);return c===a||c===i?null:c?{from:e,to:c,areaDiff:l}:null}getConflicts(e,t,n){let r=n.get(e);return[t,e,r.isLeft?r.parent.rightChild:r.parent.leftChild,n.get(t).parent,r.parent]}reinsertNode(e,t,n){let r=n.get(e),i=r.parent,a=r.isLeft?i.rightChild:i.leftChild,o=n.get(i),s=o.parent,c=n.get(t),l=c.parent;o.isLeft?s.leftChild=a:s.rightChild=a,i.leftChild=e,i.rightChild=t,i.triangleOffset=0,i.triangleCount=0,i.minX=Math.min(e.minX,t.minX),i.minY=Math.min(e.minY,t.minY),i.minZ=Math.min(e.minZ,t.minZ),i.maxX=Math.max(e.maxX,t.maxX),i.maxY=Math.max(e.maxY,t.maxY),i.maxZ=Math.max(e.maxZ,t.maxZ),c.isLeft?l.leftChild=i:l.rightChild=i,n.set(a,{parent:s,isLeft:o.isLeft}),n.set(i,{parent:l,isLeft:c.isLeft}),n.set(e,{parent:i,isLeft:!0}),n.set(t,{parent:i,isLeft:!1}),this.refitFrom(s,n),this.refitFrom(l,n)}refitFrom(e,t){let n=e;for(;n;){if(n.triangleCount===0&&n.leftChild&&n.rightChild){let e=n.leftChild,t=n.rightChild;n.minX=Math.min(e.minX,t.minX),n.minY=Math.min(e.minY,t.minY),n.minZ=Math.min(e.minZ,t.minZ),n.maxX=Math.max(e.maxX,t.maxX),n.maxY=Math.max(e.maxY,t.maxY),n.maxZ=Math.max(e.maxZ,t.maxZ)}let e=t.get(n);n=e?e.parent:null}}optimizeBVH(e,t){let n=performance.now();this.stats={reinsertionsApplied:0,iterations:0,timeMs:0};for(let r=0;r<this.maxIterations&&!(performance.now()-n>this.timeBudgetMs);r++){let i=this.buildParentMap(e),a=i.size,o=Math.max(1,Math.floor(a*this.batchSizeRatio));t&&t(`Reinsertion iter ${r+1}/${this.maxIterations}: selecting ${o} candidates`);let s=this.findCandidates(e,o,i),c=[];for(let t=0;t<s.length&&!(performance.now()-n>this.timeBudgetMs);t++){let n=this.findReinsertion(s[t].node,e,i);n&&n.areaDiff>0&&c.push(n)}c.sort((e,t)=>t.areaDiff-e.areaDiff);let l=new Set,u=0;for(let e of c){let t=this.getConflicts(e.from,e.to,i);if(!t.some(e=>l.has(e))){for(let e of t)l.add(e);this.reinsertNode(e.from,e.to,i),u++}}if(this.stats.reinsertionsApplied+=u,this.stats.iterations=r+1,t&&t(`Reinsertion iter ${r+1}: applied ${u} reinsertions`),u===0)break}return this.stats.timeMs=performance.now()-n,this.stats}};let r={FLOATS_PER_TRIANGLE:32,POSITION_A_OFFSET:0,POSITION_B_OFFSET:4,POSITION_C_OFFSET:8,NORMAL_A_OFFSET:12,NORMAL_B_OFFSET:16,NORMAL_C_OFFSET:20,UV_AB_OFFSET:24,UV_C_MAT_OFFSET:28},i=r.FLOATS_PER_TRIANGLE;var a=class{constructor(){this.minX=0,this.minY=0,this.minZ=0,this.maxX=0,this.maxY=0,this.maxZ=0,this.leftChild=null,this.rightChild=null,this.triangleOffset=0,this.triangleCount=0}},o=class{constructor(){this.useWorker=!0,this.maxLeafSize=8,this.numBins=32,this.minBins=8,this.maxBins=64,this.totalNodes=0,this.processedTriangles=0,this.totalTriangles=0,this.lastProgressUpdate=0,this.progressUpdateInterval=100,this.traversalCost=1,this.intersectionCost=2.5,this.useMortonCodes=!0,this.mortonBits=10,this.mortonClusterThreshold=128,this.enableObjectMedianFallback=!0,this.enableSpatialMedianFallback=!0,this.splitStats={sahSplits:0,objectMedianSplits:0,spatialMedianSplits:0,failedSplits:0,avgBinsUsed:0,totalSplitAttempts:0,mortonSortTime:0,totalBuildTime:0,treeletOptimizationTime:0,treeletsProcessed:0,treeletsImproved:0,averageSAHImprovement:0,reinsertionOptimizationTime:0,reinsertionsApplied:0,reinsertionIterations:0},this.enableTreeletOptimization=!0,this.treeletSize=5,this.treeletOptimizationPasses=1,this.treeletMinImprovement=.02,this.maxTreeletDepth=3,this.maxTreeletsPerScene=20,this.treeletComplexityThreshold=5e4,this.enableReinsertionOptimization=!0,this.reinsertionBatchSizeRatio=.02,this.reinsertionMaxIterations=2,this.initializeBinArrays(),this._partResult={mid:0,lMinX:0,lMinY:0,lMinZ:0,lMaxX:0,lMaxY:0,lMaxZ:0,rMinX:0,rMinY:0,rMinZ:0,rMaxX:0,rMaxY:0,rMaxZ:0},this.centroids=null,this.bMin=null,this.bMax=null,this.indices=null,this.mortonCodes=null,this.triangles=null,this.reorderedTriangleData=null}initializeBinArrays(){let e=this.maxBins;this.binBoundsMin=new Float32Array(e*3),this.binBoundsMax=new Float32Array(e*3),this.binCounts=new Uint32Array(e),this.leftPrefixMin=new Float32Array(e*3),this.leftPrefixMax=new Float32Array(e*3),this.leftPrefixCount=new Uint32Array(e),this.rightPrefixMin=new Float32Array(e*3),this.rightPrefixMax=new Float32Array(e*3),this.rightPrefixCount=new Uint32Array(e)}getOptimalBinCount(e){return e<=16?this.minBins:e<=64?16:e<=256?32:e<=1024?48:this.maxBins}setAdaptiveBinConfig(e){e.minBins!==void 0&&(this.minBins=Math.max(4,e.minBins)),e.maxBins!==void 0&&(this.maxBins=Math.min(128,e.maxBins)),e.baseBins!==void 0&&(this.numBins=e.baseBins),e.maxBins!==void 0&&this.initializeBinArrays()}setMortonConfig(e){e.enabled!==void 0&&(this.useMortonCodes=e.enabled),e.bits!==void 0&&(this.mortonBits=Math.max(6,Math.min(10,e.bits))),e.threshold!==void 0&&(this.mortonClusterThreshold=Math.max(16,e.threshold))}setFallbackConfig(e){e.objectMedian!==void 0&&(this.enableObjectMedianFallback=e.objectMedian),e.spatialMedian!==void 0&&(this.enableSpatialMedianFallback=e.spatialMedian)}setTreeletConfig(e){e.enabled!==void 0&&(this.enableTreeletOptimization=e.enabled),e.size!==void 0&&(this.treeletSize=Math.max(3,Math.min(12,e.size))),e.passes!==void 0&&(this.treeletOptimizationPasses=Math.max(1,Math.min(3,e.passes))),e.minImprovement!==void 0&&(this.treeletMinImprovement=Math.max(.001,e.minImprovement))}disableTreeletOptimization(){this.enableTreeletOptimization=!1}setReinsertionConfig(e){e.enabled!==void 0&&(this.enableReinsertionOptimization=e.enabled),e.batchSizeRatio!==void 0&&(this.reinsertionBatchSizeRatio=Math.max(.005,Math.min(.1,e.batchSizeRatio))),e.maxIterations!==void 0&&(this.reinsertionMaxIterations=Math.max(1,Math.min(5,e.maxIterations)))}initializeTriangleArrays(){let e=this.totalTriangles,t=this.triangles,n=r.POSITION_A_OFFSET,a=r.POSITION_B_OFFSET,o=r.POSITION_C_OFFSET;for(let r=0;r<e;r++){let e=r*i,s=t[e+n],c=t[e+n+1],l=t[e+n+2],u=t[e+a],d=t[e+a+1],f=t[e+a+2],p=t[e+o],m=t[e+o+1],h=t[e+o+2],g=r*3;this.centroids[g]=(s+u+p)/3,this.centroids[g+1]=(c+d+m)/3,this.centroids[g+2]=(l+f+h)/3,this.bMin[g]=s<u?s<p?s:p:u<p?u:p,this.bMin[g+1]=c<d?c<m?c:m:d<m?d:m,this.bMin[g+2]=l<f?l<h?l:h:f<h?f:h,this.bMax[g]=s>u?s>p?s:p:u>p?u:p,this.bMax[g+1]=c>d?c>m?c:m:d>m?d:m,this.bMax[g+2]=l>f?l>h?l:h:f>h?f:h,this.indices[r]=r}}expandBits(e){return e=e*65537&4278190335,e=e*257&251719695,e=e*17&3272356035,e=e*5&1227133513,e}morton3D(e,t,n){return(this.expandBits(n)<<2)+(this.expandBits(t)<<1)+this.expandBits(e)}computeMortonCodeForIndex(e,t,n,r,i,a,o){let s=this.centroids,c=e*3,l=(1<<this.mortonBits)-1,u=i>0?(s[c]-t)/i:0,d=a>0?(s[c+1]-n)/a:0,f=o>0?(s[c+2]-r)/o:0,p=Math.max(0,Math.min(l,Math.floor(u*l))),m=Math.max(0,Math.min(l,Math.floor(d*l))),h=Math.max(0,Math.min(l,Math.floor(f*l)));return this.morton3D(p,m,h)}sortTrianglesByMortonCode(){let e=this.totalTriangles;if(!this.useMortonCodes||e<this.mortonClusterThreshold)return;let t=performance.now(),n=this.centroids,r=this.indices,i=1/0,a=1/0,o=1/0,s=-1/0,c=-1/0,l=-1/0;for(let t=0;t<e;t++){let e=r[t]*3,u=n[e],d=n[e+1],f=n[e+2];u<i&&(i=u),d<a&&(a=d),f<o&&(o=f),u>s&&(s=u),d>c&&(c=d),f>l&&(l=f)}let u=s-i,d=c-a,f=l-o,p=this.mortonCodes,m=(1<<this.mortonBits)-1,h=u>0?m/u:0,g=d>0?m/d:0,_=f>0?m/f:0;for(let t=0;t<e;t++){let e=r[t],s=e*3,c=(n[s]-i)*h,l=(n[s+1]-a)*g,u=(n[s+2]-o)*_;c=c<0?0:(c>m?m:c)|0,l=l<0?0:(l>m?m:l)|0,u=u<0?0:(u>m?m:u)|0,c=c*65537&4278190335,c=c*257&251719695,c=c*17&3272356035,c=c*5&1227133513,l=l*65537&4278190335,l=l*257&251719695,l=l*17&3272356035,l=l*5&1227133513,u=u*65537&4278190335,u=u*257&251719695,u=u*17&3272356035,u=u*5&1227133513,p[e]=(u<<2)+(l<<1)+c}let v=new Uint32Array(e),y=new Uint32Array(256);for(let t=0;t<32;t+=8){y.fill(0);for(let n=0;n<e;n++)y[p[r[n]]>>>t&255]++;let n=0;for(let e=0;e<256;e++){let t=y[e];y[e]=n,n+=t}for(let n=0;n<e;n++){let e=p[r[n]]>>>t&255;v[y[e]++]=r[n]}r.set(v)}this.splitStats.mortonSortTime+=performance.now()-t}build(e,t=30,n=null){return this.totalTriangles=e.byteLength/(i*4),this.processedTriangles=0,this.lastProgressUpdate=performance.now(),this.useWorker&&typeof Worker<`u`?new Promise((r,a)=>{try{let o=new Worker(self.location.href,{type:`module`}),s=this.totalTriangles,c=typeof SharedArrayBuffer<`u`;console.log(`[BVHBuilder] SharedArrayBuffer: ${c?`enabled`:`unavailable (using transfer fallback)`}`);let l=c?new SharedArrayBuffer(s*i*4):null;o.onmessage=e=>{let{bvhData:t,triangles:i,originalToBvh:s,error:c,progress:u,treeletStats:d}=e.data;if(c){o.terminate(),a(Error(c));return}if(u!==void 0&&n){n(u);return}d&&(this.splitStats=d),o.terminate(),r({bvhData:t,bvhRoot:!0,reorderedTriangles:l?new Float32Array(l):i,originalToBvh:s||null})},o.onerror=e=>{o.terminate(),a(e)};let u=e.buffer,d={triangleData:u,triangleByteOffset:e.byteOffset,triangleByteLength:e.byteLength,triangleCount:s,depth:t,reportProgress:!!n,sharedReorderBuffer:l,treeletOptimization:{enabled:this.enableTreeletOptimization,size:this.treeletSize,passes:this.treeletOptimizationPasses,minImprovement:this.treeletMinImprovement},reinsertionOptimization:{enabled:this.enableReinsertionOptimization,batchSizeRatio:this.reinsertionBatchSizeRatio,maxIterations:this.reinsertionMaxIterations}};o.postMessage(d,[u])}catch(i){console.warn(`Worker creation failed, falling back to synchronous build:`,i),r(this._buildSyncAndFlatten(e,t,n))}}):new Promise(r=>{r(this._buildSyncAndFlatten(e,t,n))})}_buildSyncAndFlatten(e,t,n){let r=this.buildSync(e,t,n);return{bvhData:this.flattenBVH(r),bvhRoot:!0,reorderedTriangles:this.reorderedTriangleData||null,originalToBvh:this.originalToBvhMap||null}}buildSync(t,r=30,a=null,o=null){let s=performance.now();this.totalNodes=0,this.processedTriangles=0,this.triangles=t,this.totalTriangles=t.byteLength/(i*4),this.lastProgressUpdate=performance.now(),this.splitStats={sahSplits:0,objectMedianSplits:0,spatialMedianSplits:0,failedSplits:0,avgBinsUsed:0,totalSplitAttempts:0,mortonSortTime:0,totalBuildTime:0,treeletOptimizationTime:0,treeletsProcessed:0,treeletsImproved:0,averageSAHImprovement:0,reinsertionOptimizationTime:0,reinsertionsApplied:0,reinsertionIterations:0,saOrderTime:0,initTime:0,sahBuildTime:0,reorderTime:0};let c=this.totalTriangles,l=performance.now();this.centroids=new Float32Array(c*3),this.bMin=new Float32Array(c*3),this.bMax=new Float32Array(c*3),this.indices=new Uint32Array(c),this.mortonCodes=new Uint32Array(c),this.initializeTriangleArrays(),this.splitStats.initTime=performance.now()-l,this.sortTrianglesByMortonCode();let u=performance.now(),d=this.buildNodeRecursive(0,c,r,a);if(this.splitStats.sahBuildTime=performance.now()-u,this.enableTreeletOptimization&&this.totalTriangles>1e3){let t=this.totalTriangles>this.treeletComplexityThreshold,n=t?3:this.treeletSize,r=t?10:this.maxTreeletsPerScene,i=new e(this.traversalCost,this.intersectionCost);i.setTreeletSize(n),i.setMinImprovement(this.treeletMinImprovement),i.setMaxTreelets(r);let o=performance.now();for(let e=0;e<this.treeletOptimizationPasses;e++){let t=a?t=>{a(`Treelet optimization pass ${e+1}/${this.treeletOptimizationPasses}: ${t}`)}:null;try{i.optimizeBVH(d,t)}catch(t){console.error(`TreeletOptimizer: Error in pass ${e+1}:`,t);break}let n=i.getStatistics(),r=performance.now()-o;if(n.treeletsImproved===0&&e>0||r>15e3)break}let s=performance.now()-o;this.splitStats.treeletOptimizationTime=s;let c=i.getStatistics();this.splitStats.treeletsProcessed=c.treeletsProcessed,this.splitStats.treeletsImproved=c.treeletsImproved,this.splitStats.averageSAHImprovement=c.averageSAHImprovement}if(this.enableReinsertionOptimization&&this.totalTriangles>1e3){let e=new n(this.traversalCost,this.intersectionCost);e.setBatchSizeRatio(this.reinsertionBatchSizeRatio),e.setMaxIterations(this.reinsertionMaxIterations);let t=a?e=>{a(e)}:null;try{e.optimizeBVH(d,t)}catch(e){console.error(`ReinsertionOptimizer: Error:`,e)}let r=e.getStatistics();this.splitStats.reinsertionOptimizationTime=r.timeMs,this.splitStats.reinsertionsApplied=r.reinsertionsApplied,this.splitStats.reinsertionIterations=r.iterations}let f=performance.now();this.applySAOrdering(d),this.splitStats.saOrderTime=performance.now()-f;let p=performance.now(),m=this.triangles,h=o||new Float32Array(c*i);for(let e=0;e<c;e++){let t=this.indices[e]*i,n=e*i;h.set(m.subarray(t,t+i),n)}this.reorderedTriangleData=h;let g=new Uint32Array(c);for(let e=0;e<c;e++)g[this.indices[e]]=e;this.originalToBvhMap=g,this.splitStats.reorderTime=performance.now()-p,this.splitStats.totalBuildTime=performance.now()-s;let _=this.splitStats.totalBuildTime,v=e=>_>0?(e/_*100).toFixed(1)+`%`:`0%`,y=[{Phase:`Init + bounds`,"Time (ms)":Math.round(this.splitStats.initTime),"%":v(this.splitStats.initTime)},{Phase:`Morton sort`,"Time (ms)":Math.round(this.splitStats.mortonSortTime),"%":v(this.splitStats.mortonSortTime)},{Phase:`SAH recursive build`,"Time (ms)":Math.round(this.splitStats.sahBuildTime),"%":v(this.splitStats.sahBuildTime)},{Phase:`Treelet optimization`,"Time (ms)":Math.round(this.splitStats.treeletOptimizationTime),"%":v(this.splitStats.treeletOptimizationTime)},{Phase:`Reinsertion optimization`,"Time (ms)":Math.round(this.splitStats.reinsertionOptimizationTime),"%":v(this.splitStats.reinsertionOptimizationTime)},{Phase:`SA ordering`,"Time (ms)":Math.round(this.splitStats.saOrderTime),"%":v(this.splitStats.saOrderTime)},{Phase:`Triangle reorder`,"Time (ms)":Math.round(this.splitStats.reorderTime),"%":v(this.splitStats.reorderTime)},{Phase:`TOTAL`,"Time (ms)":Math.round(_),"%":`100%`}];return console.groupCollapsed(`⏱ BVH Build (${c.toLocaleString()} triangles, ${Math.round(_)}ms)`),console.table(y),console.table({"Total Nodes":this.totalNodes,"Max Leaf Size":this.maxLeafSize,"SAH Splits":this.splitStats.sahSplits,"Object Median Splits":this.splitStats.objectMedianSplits,"Spatial Median Splits":this.splitStats.spatialMedianSplits,"Failed Splits":this.splitStats.failedSplits,"Treelets Processed":this.splitStats.treeletsProcessed,"Treelets Improved":this.splitStats.treeletsImproved,"Avg SAH Improvement":(this.splitStats.averageSAHImprovement*100).toFixed(2)+`%`,"Reinsertions Applied":this.splitStats.reinsertionsApplied,"Reinsertion Iterations":this.splitStats.reinsertionIterations}),console.groupEnd(),a&&a(100),this.centroids=null,this.bMin=null,this.bMax=null,this.mortonCodes=null,d}updateProgress(e,t){if(!t)return;this.processedTriangles+=e;let n=performance.now();n-this.lastProgressUpdate<this.progressUpdateInterval||(this.lastProgressUpdate=n,t(Math.min(Math.floor(this.processedTriangles/this.totalTriangles*100),99)))}buildNodeRecursiveToDepth(e,t,n,r,i,o,s,c,l,u,d){let f=new a;this.totalNodes++;let p=t-e;if(o===void 0?this.updateNodeBounds(f,e,t):(f.minX=o,f.minY=s,f.minZ=c,f.maxX=l,f.maxY=u,f.maxZ=d),p<=this.maxLeafSize||n<=0)return f.triangleOffset=e,f.triangleCount=p,this.updateProgress(p,i),f;if(r<=0&&p>this.maxLeafSize*16){let r=this.frontierTasks.length;return f.triangleOffset=e,f.triangleCount=p,f.isFrontier=!0,f.frontierTaskId=r,this.frontierTasks.push({taskId:r,start:e,end:t,depth:n,preMinX:f.minX,preMinY:f.minY,preMinZ:f.minZ,preMaxX:f.maxX,preMaxY:f.maxY,preMaxZ:f.maxZ}),f}let m=this.findBestSplitPositionSAH(e,t,f);if(!m.success){if(this.splitStats.failedSplits++,r>0||p<=this.maxLeafSize*16)return f.triangleOffset=e,f.triangleCount=p,this.updateProgress(p,i),f;let a=this.frontierTasks.length;return f.triangleOffset=e,f.triangleCount=p,f.isFrontier=!0,f.frontierTaskId=a,this.frontierTasks.push({taskId:a,start:e,end:t,depth:n,preMinX:f.minX,preMinY:f.minY,preMinZ:f.minZ,preMaxX:f.maxX,preMaxY:f.maxY,preMaxZ:f.maxZ}),f}m.method===`SAH`?this.splitStats.sahSplits++:m.method===`object_median`?this.splitStats.objectMedianSplits++:m.method===`spatial_median`&&this.splitStats.spatialMedianSplits++,this.partitionWithBounds(e,t,m.axis,m.pos);let h=this._partResult,g=h.mid,_=h.lMinX,v=h.lMinY,y=h.lMinZ,b=h.lMaxX,x=h.lMaxY,S=h.lMaxZ,C=h.rMinX,w=h.rMinY,T=h.rMinZ,E=h.rMaxX,D=h.rMaxY,O=h.rMaxZ;return g===e||g===t?(f.triangleOffset=e,f.triangleCount=p,this.updateProgress(p,i),f):(f.leftChild=this.buildNodeRecursiveToDepth(e,g,n-1,r-1,i,_,v,y,b,x,S),f.rightChild=this.buildNodeRecursiveToDepth(g,t,n-1,r-1,i,C,w,T,E,D,O),f)}buildNodeRecursive(e,t,n,r,i,o,s,c,l,u){let d=new a;this.totalNodes++;let f=t-e;if(i===void 0?this.updateNodeBounds(d,e,t):(d.minX=i,d.minY=o,d.minZ=s,d.maxX=c,d.maxY=l,d.maxZ=u),f<=this.maxLeafSize||n<=0)return d.triangleOffset=e,d.triangleCount=f,this.updateProgress(f,r),d;let p=this.findBestSplitPositionSAH(e,t,d);if(!p.success)return this.splitStats.failedSplits++,d.triangleOffset=e,d.triangleCount=f,this.updateProgress(f,r),d;p.method===`SAH`?this.splitStats.sahSplits++:p.method===`object_median`?this.splitStats.objectMedianSplits++:p.method===`spatial_median`&&this.splitStats.spatialMedianSplits++,this.partitionWithBounds(e,t,p.axis,p.pos);let m=this._partResult,h=m.mid,g=m.lMinX,_=m.lMinY,v=m.lMinZ,y=m.lMaxX,b=m.lMaxY,x=m.lMaxZ,S=m.rMinX,C=m.rMinY,w=m.rMinZ,T=m.rMaxX,E=m.rMaxY,D=m.rMaxZ;return h===e||h===t?(d.triangleOffset=e,d.triangleCount=f,this.updateProgress(f,r),d):(d.leftChild=this.buildNodeRecursive(e,h,n-1,r,g,_,v,y,b,x),d.rightChild=this.buildNodeRecursive(h,t,n-1,r,S,C,w,T,E,D),d)}partitionWithBounds(e,t,n,r){let i=this.indices,a=this.centroids,o=this.bMin,s=this.bMax,c=e,l=t-1,u=1/0,d=1/0,f=1/0,p=-1/0,m=-1/0,h=-1/0,g=1/0,_=1/0,v=1/0,y=-1/0,b=-1/0,x=-1/0;for(;c<=l;){let e=i[c],t=e*3;a[t+n]<=r?(o[t]<u&&(u=o[t]),o[t+1]<d&&(d=o[t+1]),o[t+2]<f&&(f=o[t+2]),s[t]>p&&(p=s[t]),s[t+1]>m&&(m=s[t+1]),s[t+2]>h&&(h=s[t+2]),c++):(o[t]<g&&(g=o[t]),o[t+1]<_&&(_=o[t+1]),o[t+2]<v&&(v=o[t+2]),s[t]>y&&(y=s[t]),s[t+1]>b&&(b=s[t+1]),s[t+2]>x&&(x=s[t+2]),i[c]=i[l],i[l]=e,l--)}let S=this._partResult;return S.mid=c,S.lMinX=u,S.lMinY=d,S.lMinZ=f,S.lMaxX=p,S.lMaxY=m,S.lMaxZ=h,S.rMinX=g,S.rMinY=_,S.rMinZ=v,S.rMaxX=y,S.rMaxY=b,S.rMaxZ=x,S}updateNodeBounds(e,t,n){let r=1/0,i=1/0,a=1/0,o=-1/0,s=-1/0,c=-1/0,l=this.indices,u=this.bMin,d=this.bMax;for(let e=t;e<n;e++){let t=l[e]*3;u[t]<r&&(r=u[t]),u[t+1]<i&&(i=u[t+1]),u[t+2]<a&&(a=u[t+2]),d[t]>o&&(o=d[t]),d[t+1]>s&&(s=d[t+1]),d[t+2]>c&&(c=d[t+2])}e.minX=r,e.minY=i,e.minZ=a,e.maxX=o,e.maxY=s,e.maxZ=c}findBestSplitPositionSAH(e,t,n){let r=1/0,i=-1,a=0,o=this.computeSurfaceAreaFlat(n.minX,n.minY,n.minZ,n.maxX,n.maxY,n.maxZ),s=t-e,c=this.intersectionCost*s,l=this.getOptimalBinCount(s);this.splitStats.totalSplitAttempts++,this.splitStats.avgBinsUsed=(this.splitStats.avgBinsUsed*(this.splitStats.totalSplitAttempts-1)+l)/this.splitStats.totalSplitAttempts;let u=this.indices,d=this.centroids,f=this.bMin,p=this.bMax,m=this.binBoundsMin,h=this.binBoundsMax,g=this.binCounts,_=this.leftPrefixMin,v=this.leftPrefixMax,y=this.leftPrefixCount,b=this.rightPrefixMin,x=this.rightPrefixMax,S=this.rightPrefixCount,C=1/0,w=-1/0,T=1/0,E=-1/0,D=1/0,O=-1/0;for(let n=e;n<t;n++){let e=u[n]*3,t=d[e],r=d[e+1],i=d[e+2];t<C&&(C=t),t>w&&(w=t),r<T&&(T=r),r>E&&(E=r),i<D&&(D=i),i>O&&(O=i)}let k=[C,T,D],A=[w,E,O];for(let n=0;n<3;n++){let s=k[n],C=A[n];if(C-s<1e-6)continue;for(let e=0;e<l;e++){g[e]=0;let t=e*3;m[t]=1/0,m[t+1]=1/0,m[t+2]=1/0,h[t]=-1/0,h[t+1]=-1/0,h[t+2]=-1/0}let w=l/(C-s);for(let r=e;r<t;r++){let e=u[r],t=d[e*3+n],i=Math.floor((t-s)*w);i>=l&&(i=l-1),g[i]++;let a=i*3,o=e*3;f[o]<m[a]&&(m[a]=f[o]),f[o+1]<m[a+1]&&(m[a+1]=f[o+1]),f[o+2]<m[a+2]&&(m[a+2]=f[o+2]),p[o]>h[a]&&(h[a]=p[o]),p[o+1]>h[a+1]&&(h[a+1]=p[o+1]),p[o+2]>h[a+2]&&(h[a+2]=p[o+2])}y[0]=g[0],_[0]=m[0],_[1]=m[1],_[2]=m[2],v[0]=h[0],v[1]=h[1],v[2]=h[2];for(let e=1;e<l;e++){let t=e*3,n=(e-1)*3;y[e]=y[e-1]+g[e];let r=_[n],i=m[t],a=_[n+1],o=m[t+1],s=_[n+2],c=m[t+2];_[t]=r<i?r:i,_[t+1]=a<o?a:o,_[t+2]=s<c?s:c;let l=v[n],u=h[t],d=v[n+1],f=h[t+1],p=v[n+2],b=h[t+2];v[t]=l>u?l:u,v[t+1]=d>f?d:f,v[t+2]=p>b?p:b}let T=l-1,E=T*3;S[T]=g[T],b[E]=m[E],b[E+1]=m[E+1],b[E+2]=m[E+2],x[E]=h[E],x[E+1]=h[E+1],x[E+2]=h[E+2];for(let e=T-1;e>=0;e--){let t=e*3,n=(e+1)*3;S[e]=S[e+1]+g[e];let r=b[n],i=m[t],a=b[n+1],o=m[t+1],s=b[n+2],c=m[t+2];b[t]=r<i?r:i,b[t+1]=a<o?a:o,b[t+2]=s<c?s:c;let l=x[n],u=h[t],d=x[n+1],f=h[t+1],p=x[n+2],_=h[t+2];x[t]=l>u?l:u,x[t+1]=d>f?d:f,x[t+2]=p>_?p:_}for(let e=1;e<l;e++){let t=(e-1)*3,u=e*3,d=y[e-1],f=S[e];if(d===0||f===0)continue;let p=v[t]-_[t],m=v[t+1]-_[t+1],h=v[t+2]-_[t+2],g=2*(p*m+m*h+h*p),w=x[u]-b[u],T=x[u+1]-b[u+1],E=x[u+2]-b[u+2],D=2*(w*T+T*E+E*w),O=this.traversalCost+g/o*d*this.intersectionCost+D/o*f*this.intersectionCost;O<r&&O<c&&(r=O,i=n,a=s+(C-s)*e/l)}}return i===-1?this.enableObjectMedianFallback?this.findObjectMedianSplit(e,t):this.enableSpatialMedianFallback?this.findSpatialMedianSplit(e,t):{success:!1,method:`fallbacks_disabled`}:{success:!0,axis:i,pos:a,method:`SAH`,binsUsed:l}}findObjectMedianSplit(e,t){let n=this.indices,r=this.centroids,i=-1,a=-1;for(let o=0;o<3;o++){let s=1/0,c=-1/0;for(let i=e;i<t;i++){let e=r[n[i]*3+o];e<s&&(s=e),e>c&&(c=e)}let l=c-s;l>a&&(a=l,i=o)}if(i===-1||a<1e-10)return this.enableSpatialMedianFallback?this.findSpatialMedianSplit(e,t):{success:!1,method:`object_median_failed`};let o=t-e,s=e+Math.floor(o/2);this.quickselect(e,t,s,i);let c=r[n[s]*3+i],l=!0;for(let e=s+1;e<t;e++)if(r[n[e]*3+i]>c){l=!1;break}if(l){let a=-1/0;for(let t=e;t<s;t++){let e=r[n[t]*3+i];e>a&&(a=e)}if(a<c)c=(a+c)*.5;else return this.enableSpatialMedianFallback?this.findSpatialMedianSplit(e,t):{success:!1,method:`object_median_degenerate`}}return{success:!0,axis:i,pos:c,method:`object_median`}}findSpatialMedianSplit(e,t){let n=this.indices,r=this.centroids,i=this.bMin,a=this.bMax,o=-1,s=-1,c=0,l=0;for(let r=0;r<3;r++){let u=1/0,d=-1/0;for(let o=e;o<t;o++){let e=n[o]*3+r;i[e]<u&&(u=i[e]),a[e]>d&&(d=a[e])}let f=d-u;f>s&&(s=f,o=r,c=u,l=d)}if(o===-1||s<1e-12)return{success:!1,method:`spatial_median_failed`};let u=(c+l)*.5,d=t-e,f=0;for(let i=e;i<t;i++)r[n[i]*3+o]<=u&&f++;if(f===0||f===d){let i=e+Math.floor(d/2);this.quickselect(e,t,i,o);let a=r[n[i]*3+o],s=!0;for(let i=e;i<t;i++)if(r[n[i]*3+o]!==a){s=!1;break}if(s)return{success:!1,method:`spatial_median_degenerate`};let c=-1/0;for(let t=e;t<i;t++){let e=r[n[t]*3+o];e>c&&(c=e)}if(c<a)u=(c+a)*.5;else{let e=1/0;for(let a=i+1;a<t;a++){let t=r[n[a]*3+o];t<e&&(e=t)}u=(a+e)*.5}}return{success:!0,axis:o,pos:u,method:`spatial_median`}}quickselect(e,t,n,r){let i=this.indices,a=this.centroids,o=e,s=t-1;for(;o<s;){let e=o+s>>>1,t=a[i[o]*3+r],c=a[i[e]*3+r],l=a[i[s]*3+r];if(t>c){let t=i[o];i[o]=i[e],i[e]=t}if(t>l){let e=i[o];i[o]=i[s],i[s]=e}if(c>l){let t=i[e];i[e]=i[s],i[s]=t}let u=a[i[e]*3+r],d=o,f=s;for(;d<=f;){for(;a[i[d]*3+r]<u;)d++;for(;a[i[f]*3+r]>u;)f--;if(d<=f){let e=i[d];i[d]=i[f],i[f]=e,d++,f--}}f<n&&(o=d),d>n&&(s=f)}}applySAOrdering(e){if(!e||!e.leftChild)return;let t=[e],n=[];for(;t.length>0;){let e=t.pop();!e.leftChild||!e.rightChild||(n.push(e),t.push(e.leftChild),t.push(e.rightChild))}for(let e=n.length-1;e>=0;e--){let t=n[e],r=t.leftChild,i=t.rightChild,a=r.maxX-r.minX,o=r.maxY-r.minY,s=r.maxZ-r.minZ,c=i.maxX-i.minX,l=i.maxY-i.minY,u=i.maxZ-i.minZ;c*l+l*u+u*c>a*o+o*s+s*a&&(t.leftChild=i,t.rightChild=r)}}flattenBVH(e){let t=[],n=[e];for(;n.length>0;){let e=n.pop();e._flatIndex=t.length,t.push(e),e.rightChild&&n.push(e.rightChild),e.leftChild&&n.push(e.leftChild)}let r=new Float32Array(t.length*16);for(let e=0;e<t.length;e++){let n=t[e],i=e*16;if(n.leftChild){let e=n.leftChild,t=n.rightChild;r[i]=e.minX,r[i+1]=e.minY,r[i+2]=e.minZ,r[i+3]=e._flatIndex,r[i+4]=e.maxX,r[i+5]=e.maxY,r[i+6]=e.maxZ,r[i+7]=t._flatIndex,r[i+8]=t.minX,r[i+9]=t.minY,r[i+10]=t.minZ,r[i+12]=t.maxX,r[i+13]=t.maxY,r[i+14]=t.maxZ}else r[i]=n.triangleOffset,r[i+1]=n.triangleCount,r[i+3]=-1}return r}flattenBVHWithFrontier(e){let t=[],n=[e];for(;n.length>0;){let e=n.pop();e._flatIndex=t.length,t.push(e),e.rightChild&&n.push(e.rightChild),e.leftChild&&n.push(e.leftChild)}let r=new Float32Array(t.length*16),i=[];for(let e=0;e<t.length;e++){let n=t[e],a=e*16;if(n.leftChild){let e=n.leftChild,t=n.rightChild;r[a]=e.minX,r[a+1]=e.minY,r[a+2]=e.minZ,r[a+3]=e._flatIndex,r[a+4]=e.maxX,r[a+5]=e.maxY,r[a+6]=e.maxZ,r[a+7]=t._flatIndex,r[a+8]=t.minX,r[a+9]=t.minY,r[a+10]=t.minZ,r[a+12]=t.maxX,r[a+13]=t.maxY,r[a+14]=t.maxZ}else if(n.isFrontier){let t=n.frontierTaskId;r[a]=n.triangleOffset,r[a+1]=n.triangleCount,r[a+2]=t,r[a+3]=-2,i.push({taskId:t,flatIndex:e})}else r[a]=n.triangleOffset,r[a+1]=n.triangleCount,r[a+3]=-1}return{flatData:r,frontierMap:i,nodeCount:t.length}}assembleParallelBVH(e,t,n,r){let i=[...r].sort((e,t)=>e.taskId-t.taskId),a=t;for(let e=0;e<i.length;e++)a+=i[e].nodeCount;let o=new Float32Array(a*16);o.set(e);let s=new Map;for(let e of n)s.set(e.taskId,e.flatIndex);let c=t;for(let e=0;e<i.length;e++){let t=i[e],n=t.flatData,r=t.nodeCount,a=c*16;o.set(n,a);for(let e=0;e<r;e++){let t=a+e*16;o[t+3]!==-1&&(o[t+3]+=c,o[t+7]+=c)}let l=s.get(t.taskId);if(l!==void 0){let e=l*16,t=a;for(let n=0;n<16;n++)o[e+n]=o[t+n]}c+=r}return o}computeSurfaceAreaFlat(e,t,n,r,i,a){let o=r-e,s=i-t,c=a-n;return 2*(o*s+s*c+c*o)}};self.onmessage=function(e){let t=e.data,n=t.type;n===`buildPhase1`?s(t):n===`assemble`?c(t):l(t)};function s(e){let{sharedTriangleData:t,sharedCentroids:n,sharedBMin:r,sharedBMax:i,sharedIndices:a,sharedMortonCodes:s,triangleCount:c,depth:l,parallelDepth:u,reportProgress:d,treeletOptimization:f}=e;try{let e=new o;f&&e.setTreeletConfig(f);let p=d?e=>{self.postMessage({type:`progress`,progress:e})}:null;e.triangles=new Float32Array(t),e.centroids=new Float32Array(n),e.bMin=new Float32Array(r),e.bMax=new Float32Array(i),e.indices=new Uint32Array(a),e.mortonCodes=new Uint32Array(s),e.totalTriangles=c,e.totalNodes=0,e.processedTriangles=0,e.lastProgressUpdate=performance.now(),e.splitStats={sahSplits:0,objectMedianSplits:0,spatialMedianSplits:0,failedSplits:0,avgBinsUsed:0,totalSplitAttempts:0,mortonSortTime:0,totalBuildTime:0,treeletOptimizationTime:0,treeletsProcessed:0,treeletsImproved:0,averageSAHImprovement:0,initTime:0,sahBuildTime:0,reorderTime:0};let m=performance.now(),h=performance.now();e.initializeTriangleArrays(),e.splitStats.initTime=performance.now()-h,e.sortTrianglesByMortonCode(),e.frontierTasks=[];let g=performance.now(),_=e.buildNodeRecursiveToDepth(0,c,l,u,p);e.splitStats.sahBuildTime=performance.now()-g,e.applySAOrdering(_);let v=performance.now(),{flatData:y,frontierMap:b,nodeCount:x}=e.flattenBVHWithFrontier(_),S=performance.now()-v,C=performance.now()-m;console.log(`[BVHWorker] Phase 1: ${Math.round(C)}ms (init: ${Math.round(e.splitStats.initTime)}ms, morton: ${Math.round(e.splitStats.mortonSortTime)}ms, SAH: ${Math.round(e.splitStats.sahBuildTime)}ms, flatten: ${Math.round(S)}ms), ${e.frontierTasks.length} frontier tasks`),self.postMessage({type:`phase1Result`,topFlatData:y,topNodeCount:x,frontierTasks:e.frontierTasks,frontierMap:b,splitStats:e.splitStats},[y.buffer])}catch(e){console.error(`[BVHWorker] Phase 1 error:`,e),self.postMessage({type:`error`,error:e.message})}}function c(e){let{topFlatData:t,topNodeCount:n,frontierMap:r,subtreeResults:i,sharedTriangleData:a,sharedIndices:s,sharedReorderBuffer:c,triangleCount:l}=e;try{let e=performance.now(),u=new o().assembleParallelBVH(t,n,r,i),d=new Uint32Array(s),f=new Float32Array(a),p=new Float32Array(c);for(let e=0;e<l;e++){let t=d[e]*32,n=e*32;p.set(f.subarray(t,t+32),n)}let m=new Uint32Array(l);for(let e=0;e<l;e++)m[d[e]]=e;let h=performance.now()-e;console.log(`[BVHWorker] Phase 3 (assemble + reorder): ${Math.round(h)}ms (${(u.byteLength/1024/1024).toFixed(1)}MB BVH)`),self.postMessage({type:`assembleResult`,bvhData:u,originalToBvh:m,triangleCount:l},[u.buffer,m.buffer])}catch(e){console.error(`[BVHWorker] Assembly error:`,e),self.postMessage({type:`error`,error:e.message})}}function l(e){let{triangleData:t,triangleByteOffset:n,triangleByteLength:r,depth:i,reportProgress:a,treeletOptimization:s,reinsertionOptimization:c,sharedReorderBuffer:l}=e,u=new o;try{s&&u.setTreeletConfig(s),c&&u.setReinsertionConfig(c);let e=a?e=>{self.postMessage({progress:e})}:null,o=n===void 0?new Float32Array(t):new Float32Array(t,n,r/4),d=l?new Float32Array(l):null,f=u.buildSync(o,i,e,d),p=performance.now(),m=u.flattenBVH(f),h=performance.now()-p;console.log(`[BVHWorker] Flatten BVH: ${Math.round(h)}ms (${(m.byteLength/1024/1024).toFixed(1)}MB)`);let g=u.originalToBvhMap||null;if(l){let e=[m.buffer];g&&e.push(g.buffer),self.postMessage({bvhData:m,originalToBvh:g,triangleCount:o.length/32,treeletStats:u.splitStats},e)}else{let e=u.reorderedTriangleData,t=e.byteLength/128,n=[m.buffer,e.buffer];g&&n.push(g.buffer),self.postMessage({bvhData:m,triangles:e,originalToBvh:g,triangleCount:t,treeletStats:u.splitStats},n)}}catch(e){console.error(`[BVHWorker] Error:`,e),self.postMessage({error:e.message})}}})();
|
|
2
|
+
//# sourceMappingURL=BVHWorker-DobVXMda.js.map
|