rayzee 5.3.4 → 5.3.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/dist/assets/BVHSubtreeWorker-CTHfS54a.js +2 -0
- package/dist/assets/BVHSubtreeWorker-CTHfS54a.js.map +1 -0
- package/dist/assets/BVHWorker-BarjE67Z.js +2 -0
- package/dist/assets/BVHWorker-BarjE67Z.js.map +1 -0
- package/dist/rayzee.es.js +983 -910
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +41 -41
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/Passes/AIUpscaler.js +16 -4
- package/src/PathTracerApp.js +0 -4
- package/src/Processor/BVHBuilder.js +28 -8
- package/src/Processor/EquirectHDRInfo.js +16 -4
- package/src/Processor/ParallelBVHBuilder.js +58 -20
- package/src/Processor/SceneProcessor.js +76 -31
- package/src/Processor/TextureCreator.js +17 -4
- package/src/Processor/Workers/fetchAsWorker.js +30 -0
- package/src/managers/DenoisingManager.js +4 -17
- package/dist/assets/BVHSubtreeWorker-BoG4D6dP.js +0 -2
- package/dist/assets/BVHSubtreeWorker-BoG4D6dP.js.map +0 -1
- package/dist/assets/BVHWorker-BqQTDljT.js +0 -2
- package/dist/assets/BVHWorker-BqQTDljT.js.map +0 -1
|
@@ -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}};async function r(e){let t=e instanceof URL?e.href:e,n=await fetch(t);if(!n.ok)throw Error(`Failed to fetch worker script: ${n.status}`);let r=new Blob([await n.text()],{type:`application/javascript`});return new Worker(URL.createObjectURL(r))}let i={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},a=i.FLOATS_PER_TRIANGLE;var o=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}},s=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=i.POSITION_A_OFFSET,r=i.POSITION_B_OFFSET,o=i.POSITION_C_OFFSET;for(let i=0;i<e;i++){let e=i*a,s=t[e+n],c=t[e+n+1],l=t[e+n+2],u=t[e+r],d=t[e+r+1],f=t[e+r+2],p=t[e+o],m=t[e+o+1],h=t[e+o+2],g=i*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[i]=i}}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/(a*4),this.processedTriangles=0,this.lastProgressUpdate=performance.now(),this.useWorker&&typeof Worker<`u`?new Promise((i,o)=>{let s=r=>{let s=this.totalTriangles,c=typeof SharedArrayBuffer<`u`;console.log(`[BVHBuilder] SharedArrayBuffer: ${c?`enabled`:`unavailable (using transfer fallback)`}`);let l=c?new SharedArrayBuffer(s*a*4):null;r.onmessage=e=>{let{bvhData:t,triangles:a,originalToBvh:s,error:c,progress:u,treeletStats:d}=e.data;if(c){r.terminate(),o(Error(c));return}if(u!==void 0&&n){n(u);return}d&&(this.splitStats=d),r.terminate(),i({bvhData:t,bvhRoot:!0,reorderedTriangles:l?new Float32Array(l):a,originalToBvh:s||null})},r.onerror=e=>{r.terminate(),o(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}};r.postMessage(d,[u])};try{s(new Worker(new URL(``+new URL(`BVHWorker-BarjE67Z.js`,self.location.href).href,``+self.location.href),{type:`module`}))}catch(a){a.name===`SecurityError`?r(new URL(`data:text/javascript;base64,aW1wb3J0IHsgQlZIQnVpbGRlciB9IGZyb20gJy4uL0JWSEJ1aWxkZXIuanMnOwoKY29uc3QgRlBUID0gMzI7IC8vIEZMT0FUU19QRVJfVFJJQU5HTEUKCi8vIC0tLSBNZXNzYWdlIGRpc3BhdGNoZXIgLS0tCgpzZWxmLm9ubWVzc2FnZSA9IGZ1bmN0aW9uICggZSApIHsKCgljb25zdCBkYXRhID0gZS5kYXRhOwoJY29uc3QgdHlwZSA9IGRhdGEudHlwZTsKCglpZiAoIHR5cGUgPT09ICdidWlsZFBoYXNlMScgKSB7CgoJCWhhbmRsZVBoYXNlMSggZGF0YSApOwoKCX0gZWxzZSBpZiAoIHR5cGUgPT09ICdhc3NlbWJsZScgKSB7CgoJCWhhbmRsZUFzc2VtYmxlKCBkYXRhICk7CgoJfSBlbHNlIHsKCgkJLy8gTGVnYWN5OiBmdWxsIHNpbmdsZS13b3JrZXIgYnVpbGQgKGJhY2t3YXJkIGNvbXBhdGlibGUpCgkJaGFuZGxlRnVsbEJ1aWxkKCBkYXRhICk7CgoJfQoKfTsKCi8vIC0tLSBQaGFzZSAxOiBJbml0ICsgTW9ydG9uIHNvcnQgKyB0b3AtbGV2ZWwgU0FIIGJ1aWxkIC0tLQoKZnVuY3Rpb24gaGFuZGxlUGhhc2UxKCBkYXRhICkgewoKCWNvbnN0IHsKCQlzaGFyZWRUcmlhbmdsZURhdGEsIHNoYXJlZENlbnRyb2lkcywgc2hhcmVkQk1pbiwgc2hhcmVkQk1heCwKCQlzaGFyZWRJbmRpY2VzLCBzaGFyZWRNb3J0b25Db2RlcywKCQl0cmlhbmdsZUNvdW50LCBkZXB0aCwgcGFyYWxsZWxEZXB0aCwKCQlyZXBvcnRQcm9ncmVzcywgdHJlZWxldE9wdGltaXphdGlvbgoJfSA9IGRhdGE7CgoJdHJ5IHsKCgkJY29uc3QgYnVpbGRlciA9IG5ldyBCVkhCdWlsZGVyKCk7CgoJCWlmICggdHJlZWxldE9wdGltaXphdGlvbiApIHsKCgkJCWJ1aWxkZXIuc2V0VHJlZWxldENvbmZpZyggdHJlZWxldE9wdGltaXphdGlvbiApOwoKCQl9CgoJCWNvbnN0IHByb2dyZXNzQ2FsbGJhY2sgPSByZXBvcnRQcm9ncmVzcyA/ICggcHJvZ3Jlc3MgKSA9PiB7CgoJCQlzZWxmLnBvc3RNZXNzYWdlKCB7IHR5cGU6ICdwcm9ncmVzcycsIHByb2dyZXNzIH0gKTsKCgkJfSA6IG51bGw7CgoJCS8vIEF0dGFjaCBzaGFyZWQgYnVmZmVyIHZpZXdzCgkJYnVpbGRlci50cmlhbmdsZXMgPSBuZXcgRmxvYXQzMkFycmF5KCBzaGFyZWRUcmlhbmdsZURhdGEgKTsKCQlidWlsZGVyLmNlbnRyb2lkcyA9IG5ldyBGbG9hdDMyQXJyYXkoIHNoYXJlZENlbnRyb2lkcyApOwoJCWJ1aWxkZXIuYk1pbiA9IG5ldyBGbG9hdDMyQXJyYXkoIHNoYXJlZEJNaW4gKTsKCQlidWlsZGVyLmJNYXggPSBuZXcgRmxvYXQzMkFycmF5KCBzaGFyZWRCTWF4ICk7CgkJYnVpbGRlci5pbmRpY2VzID0gbmV3IFVpbnQzMkFycmF5KCBzaGFyZWRJbmRpY2VzICk7CgkJYnVpbGRlci5tb3J0b25Db2RlcyA9IG5ldyBVaW50MzJBcnJheSggc2hhcmVkTW9ydG9uQ29kZXMgKTsKCQlidWlsZGVyLnRvdGFsVHJpYW5nbGVzID0gdHJpYW5nbGVDb3VudDsKCgkJLy8gUmVzZXQgc3RhdGUKCQlidWlsZGVyLnRvdGFsTm9kZXMgPSAwOwoJCWJ1aWxkZXIucHJvY2Vzc2VkVHJpYW5nbGVzID0gMDsKCQlidWlsZGVyLmxhc3RQcm9ncmVzc1VwZGF0ZSA9IHBlcmZvcm1hbmNlLm5vdygpOwoKCQlidWlsZGVyLnNwbGl0U3RhdHMgPSB7CgkJCXNhaFNwbGl0czogMCwgb2JqZWN0TWVkaWFuU3BsaXRzOiAwLCBzcGF0aWFsTWVkaWFuU3BsaXRzOiAwLAoJCQlmYWlsZWRTcGxpdHM6IDAsIGF2Z0JpbnNVc2VkOiAwLCB0b3RhbFNwbGl0QXR0ZW1wdHM6IDAsCgkJCW1vcnRvblNvcnRUaW1lOiAwLCB0b3RhbEJ1aWxkVGltZTogMCwgdHJlZWxldE9wdGltaXphdGlvblRpbWU6IDAsCgkJCXRyZWVsZXRzUHJvY2Vzc2VkOiAwLCB0cmVlbGV0c0ltcHJvdmVkOiAwLCBhdmVyYWdlU0FISW1wcm92ZW1lbnQ6IDAsCgkJCWluaXRUaW1lOiAwLCBzYWhCdWlsZFRpbWU6IDAsIHJlb3JkZXJUaW1lOiAwCgkJfTsKCgkJY29uc3Qgc3RhcnRUaW1lID0gcGVyZm9ybWFuY2Uubm93KCk7CgoJCS8vIFBoYXNlIDFhOiBJbml0aWFsaXplIHBlci10cmlhbmdsZSBhcnJheXMgKHdyaXRlcyBpbnRvIHNoYXJlZCBidWZmZXJzKQoJCWNvbnN0IGluaXRTdGFydCA9IHBlcmZvcm1hbmNlLm5vdygpOwoJCWJ1aWxkZXIuaW5pdGlhbGl6ZVRyaWFuZ2xlQXJyYXlzKCk7CgkJYnVpbGRlci5zcGxpdFN0YXRzLmluaXRUaW1lID0gcGVyZm9ybWFuY2Uubm93KCkgLSBpbml0U3RhcnQ7CgoJCS8vIFBoYXNlIDFiOiBNb3J0b24gY29kZSBzcGF0aWFsIGNsdXN0ZXJpbmcKCQlidWlsZGVyLnNvcnRUcmlhbmdsZXNCeU1vcnRvbkNvZGUoKTsKCgkJLy8gUGhhc2UgMWM6IEJ1aWxkIHRvcC1sZXZlbCB0cmVlIHRvIHBhcmFsbGVsRGVwdGgKCQlidWlsZGVyLmZyb250aWVyVGFza3MgPSBbXTsKCQljb25zdCBzYWhTdGFydCA9IHBlcmZvcm1hbmNlLm5vdygpOwoJCWNvbnN0IHJvb3QgPSBidWlsZGVyLmJ1aWxkTm9kZVJlY3Vyc2l2ZVRvRGVwdGgoIDAsIHRyaWFuZ2xlQ291bnQsIGRlcHRoLCBwYXJhbGxlbERlcHRoLCBwcm9ncmVzc0NhbGxiYWNrICk7CgkJYnVpbGRlci5zcGxpdFN0YXRzLnNhaEJ1aWxkVGltZSA9IHBlcmZvcm1hbmNlLm5vdygpIC0gc2FoU3RhcnQ7CgoJCS8vIFBoYXNlIDFkOiBTdXJmYWNlLWFyZWEgY2hpbGQgb3JkZXJpbmcgKERGUyBjYWNoZSBsb2NhbGl0eSkKCQlidWlsZGVyLmFwcGx5U0FPcmRlcmluZyggcm9vdCApOwoKCQkvLyBQaGFzZSAxZTogRmxhdHRlbiB0b3AtbGV2ZWwgdHJlZSB3aXRoIGZyb250aWVyIHNlbnRpbmVscwoJCWNvbnN0IGZsYXR0ZW5TdGFydCA9IHBlcmZvcm1hbmNlLm5vdygpOwoJCWNvbnN0IHsgZmxhdERhdGEsIGZyb250aWVyTWFwLCBub2RlQ291bnQgfSA9IGJ1aWxkZXIuZmxhdHRlbkJWSFdpdGhGcm9udGllciggcm9vdCApOwoJCWNvbnN0IGZsYXR0ZW5UaW1lID0gcGVyZm9ybWFuY2Uubm93KCkgLSBmbGF0dGVuU3RhcnQ7CgoJCWNvbnN0IHRvdGFsVGltZSA9IHBlcmZvcm1hbmNlLm5vdygpIC0gc3RhcnRUaW1lOwoJCWNvbnNvbGUubG9nKCBgW0JWSFdvcmtlcl0gUGhhc2UgMTogJHtNYXRoLnJvdW5kKCB0b3RhbFRpbWUgKX1tcyAoaW5pdDogJHtNYXRoLnJvdW5kKCBidWlsZGVyLnNwbGl0U3RhdHMuaW5pdFRpbWUgKX1tcywgbW9ydG9uOiAke01hdGgucm91bmQoIGJ1aWxkZXIuc3BsaXRTdGF0cy5tb3J0b25Tb3J0VGltZSApfW1zLCBTQUg6ICR7TWF0aC5yb3VuZCggYnVpbGRlci5zcGxpdFN0YXRzLnNhaEJ1aWxkVGltZSApfW1zLCBmbGF0dGVuOiAke01hdGgucm91bmQoIGZsYXR0ZW5UaW1lICl9bXMpLCAke2J1aWxkZXIuZnJvbnRpZXJUYXNrcy5sZW5ndGh9IGZyb250aWVyIHRhc2tzYCApOwoKCQlzZWxmLnBvc3RNZXNzYWdlKCB7CgkJCXR5cGU6ICdwaGFzZTFSZXN1bHQnLAoJCQl0b3BGbGF0RGF0YTogZmxhdERhdGEsCgkJCXRvcE5vZGVDb3VudDogbm9kZUNvdW50LAoJCQlmcm9udGllclRhc2tzOiBidWlsZGVyLmZyb250aWVyVGFza3MsCgkJCWZyb250aWVyTWFwLAoJCQlzcGxpdFN0YXRzOiBidWlsZGVyLnNwbGl0U3RhdHMKCQl9LCBbIGZsYXREYXRhLmJ1ZmZlciBdICk7CgoJfSBjYXRjaCAoIGVycm9yICkgewoKCQljb25zb2xlLmVycm9yKCAnW0JWSFdvcmtlcl0gUGhhc2UgMSBlcnJvcjonLCBlcnJvciApOwoJCXNlbGYucG9zdE1lc3NhZ2UoIHsgdHlwZTogJ2Vycm9yJywgZXJyb3I6IGVycm9yLm1lc3NhZ2UgfSApOwoKCX0KCn0KCi8vIC0tLSBQaGFzZSAzOiBBc3NlbWJsZSBmaW5hbCBCVkggKyByZW9yZGVyIHRyaWFuZ2xlcyAtLS0KCmZ1bmN0aW9uIGhhbmRsZUFzc2VtYmxlKCBkYXRhICkgewoKCWNvbnN0IHsKCQl0b3BGbGF0RGF0YSwgdG9wTm9kZUNvdW50LCBmcm9udGllck1hcCwgc3VidHJlZVJlc3VsdHMsCgkJc2hhcmVkVHJpYW5nbGVEYXRhLCBzaGFyZWRJbmRpY2VzLCBzaGFyZWRSZW9yZGVyQnVmZmVyLAoJCXRyaWFuZ2xlQ291bnQKCX0gPSBkYXRhOwoKCXRyeSB7CgoJCWNvbnN0IHN0YXJ0VGltZSA9IHBlcmZvcm1hbmNlLm5vdygpOwoJCWNvbnN0IGJ1aWxkZXIgPSBuZXcgQlZIQnVpbGRlcigpOwoKCQkvLyBBc3NlbWJsZSB0aGUgZmluYWwgQlZICgkJY29uc3QgYnZoRGF0YSA9IGJ1aWxkZXIuYXNzZW1ibGVQYXJhbGxlbEJWSCgKCQkJdG9wRmxhdERhdGEsIHRvcE5vZGVDb3VudCwgZnJvbnRpZXJNYXAsIHN1YnRyZWVSZXN1bHRzCgkJKTsKCgkJLy8gUmVvcmRlciB0cmlhbmdsZXMgdXNpbmcgZmluYWwgaW5kaWNlcyBmcm9tIFNoYXJlZEFycmF5QnVmZmVyCgkJY29uc3QgaW5kaWNlcyA9IG5ldyBVaW50MzJBcnJheSggc2hhcmVkSW5kaWNlcyApOwoJCWNvbnN0IHNyYyA9IG5ldyBGbG9hdDMyQXJyYXkoIHNoYXJlZFRyaWFuZ2xlRGF0YSApOwoJCWNvbnN0IGRzdCA9IG5ldyBGbG9hdDMyQXJyYXkoIHNoYXJlZFJlb3JkZXJCdWZmZXIgKTsKCgkJZm9yICggbGV0IGkgPSAwOyBpIDwgdHJpYW5nbGVDb3VudDsgaSArKyApIHsKCgkJCWNvbnN0IHNyY09mZiA9IGluZGljZXNbIGkgXSAqIEZQVDsKCQkJY29uc3QgZHN0T2ZmID0gaSAqIEZQVDsKCQkJZHN0LnNldCggc3JjLnN1YmFycmF5KCBzcmNPZmYsIHNyY09mZiArIEZQVCApLCBkc3RPZmYgKTsKCgkJfQoKCQkvLyBCdWlsZCBpbnZlcnNlIGluZGV4IG1hcCBmb3IgQlZIIHJlZml0CgkJY29uc3Qgb3JpZ2luYWxUb0J2aCA9IG5ldyBVaW50MzJBcnJheSggdHJpYW5nbGVDb3VudCApOwoJCWZvciAoIGxldCBpID0gMDsgaSA8IHRyaWFuZ2xlQ291bnQ7IGkgKysgKSB7CgoJCQlvcmlnaW5hbFRvQnZoWyBpbmRpY2VzWyBpIF0gXSA9IGk7CgoJCX0KCgkJY29uc3QgdG90YWxUaW1lID0gcGVyZm9ybWFuY2Uubm93KCkgLSBzdGFydFRpbWU7CgkJY29uc29sZS5sb2coIGBbQlZIV29ya2VyXSBQaGFzZSAzIChhc3NlbWJsZSArIHJlb3JkZXIpOiAke01hdGgucm91bmQoIHRvdGFsVGltZSApfW1zICgkeyggYnZoRGF0YS5ieXRlTGVuZ3RoIC8gMTAyNCAvIDEwMjQgKS50b0ZpeGVkKCAxICl9TUIgQlZIKWAgKTsKCgkJc2VsZi5wb3N0TWVzc2FnZSggewoJCQl0eXBlOiAnYXNzZW1ibGVSZXN1bHQnLAoJCQlidmhEYXRhLAoJCQlvcmlnaW5hbFRvQnZoLAoJCQl0cmlhbmdsZUNvdW50CgkJfSwgWyBidmhEYXRhLmJ1ZmZlciwgb3JpZ2luYWxUb0J2aC5idWZmZXIgXSApOwoKCX0gY2F0Y2ggKCBlcnJvciApIHsKCgkJY29uc29sZS5lcnJvciggJ1tCVkhXb3JrZXJdIEFzc2VtYmx5IGVycm9yOicsIGVycm9yICk7CgkJc2VsZi5wb3N0TWVzc2FnZSggeyB0eXBlOiAnZXJyb3InLCBlcnJvcjogZXJyb3IubWVzc2FnZSB9ICk7CgoJfQoKfQoKLy8gLS0tIExlZ2FjeTogZnVsbCBzaW5nbGUtd29ya2VyIGJ1aWxkIC0tLQoKZnVuY3Rpb24gaGFuZGxlRnVsbEJ1aWxkKCBkYXRhICkgewoKCWNvbnN0IHsgdHJpYW5nbGVEYXRhLCB0cmlhbmdsZUJ5dGVPZmZzZXQsIHRyaWFuZ2xlQnl0ZUxlbmd0aCwgZGVwdGgsIHJlcG9ydFByb2dyZXNzLCB0cmVlbGV0T3B0aW1pemF0aW9uLCByZWluc2VydGlvbk9wdGltaXphdGlvbiwgc2hhcmVkUmVvcmRlckJ1ZmZlciB9ID0gZGF0YTsKCWNvbnN0IGJ1aWxkZXIgPSBuZXcgQlZIQnVpbGRlcigpOwoKCXRyeSB7CgoJCWlmICggdHJlZWxldE9wdGltaXphdGlvbiApIHsKCgkJCWJ1aWxkZXIuc2V0VHJlZWxldENvbmZpZyggdHJlZWxldE9wdGltaXphdGlvbiApOwoKCQl9CgoJCWlmICggcmVpbnNlcnRpb25PcHRpbWl6YXRpb24gKSB7CgoJCQlidWlsZGVyLnNldFJlaW5zZXJ0aW9uQ29uZmlnKCByZWluc2VydGlvbk9wdGltaXphdGlvbiApOwoKCQl9CgoJCWNvbnN0IHByb2dyZXNzQ2FsbGJhY2sgPSByZXBvcnRQcm9ncmVzcyA/ICggcHJvZ3Jlc3MgKSA9PiB7CgoJCQlzZWxmLnBvc3RNZXNzYWdlKCB7IHByb2dyZXNzIH0gKTsKCgkJfSA6IG51bGw7CgoJCWNvbnN0IGlucHV0VHJpYW5nbGVzID0gdHJpYW5nbGVCeXRlT2Zmc2V0ICE9PSB1bmRlZmluZWQKCQkJPyBuZXcgRmxvYXQzMkFycmF5KCB0cmlhbmdsZURhdGEsIHRyaWFuZ2xlQnl0ZU9mZnNldCwgdHJpYW5nbGVCeXRlTGVuZ3RoIC8gNCApCgkJCTogbmV3IEZsb2F0MzJBcnJheSggdHJpYW5nbGVEYXRhICk7CgoJCWNvbnN0IHJlb3JkZXJUYXJnZXQgPSBzaGFyZWRSZW9yZGVyQnVmZmVyCgkJCT8gbmV3IEZsb2F0MzJBcnJheSggc2hhcmVkUmVvcmRlckJ1ZmZlciApCgkJCTogbnVsbDsKCgkJY29uc3QgYnZoUm9vdCA9IGJ1aWxkZXIuYnVpbGRTeW5jKCBpbnB1dFRyaWFuZ2xlcywgZGVwdGgsIHByb2dyZXNzQ2FsbGJhY2ssIHJlb3JkZXJUYXJnZXQgKTsKCgkJY29uc3QgZmxhdHRlblN0YXJ0ID0gcGVyZm9ybWFuY2Uubm93KCk7CgkJY29uc3QgYnZoRGF0YSA9IGJ1aWxkZXIuZmxhdHRlbkJWSCggYnZoUm9vdCApOwoJCWNvbnN0IGZsYXR0ZW5UaW1lID0gcGVyZm9ybWFuY2Uubm93KCkgLSBmbGF0dGVuU3RhcnQ7CgkJY29uc29sZS5sb2coIGBbQlZIV29ya2VyXSBGbGF0dGVuIEJWSDogJHtNYXRoLnJvdW5kKCBmbGF0dGVuVGltZSApfW1zICgkeyggYnZoRGF0YS5ieXRlTGVuZ3RoIC8gMTAyNCAvIDEwMjQgKS50b0ZpeGVkKCAxICl9TUIpYCApOwoKCQljb25zdCBvcmlnaW5hbFRvQnZoID0gYnVpbGRlci5vcmlnaW5hbFRvQnZoTWFwIHx8IG51bGw7CgoJCWlmICggc2hhcmVkUmVvcmRlckJ1ZmZlciApIHsKCgkJCWNvbnN0IHRyYW5zZmVyYWJsZXMgPSBbIGJ2aERhdGEuYnVmZmVyIF07CgkJCWlmICggb3JpZ2luYWxUb0J2aCApIHRyYW5zZmVyYWJsZXMucHVzaCggb3JpZ2luYWxUb0J2aC5idWZmZXIgKTsKCgkJCXNlbGYucG9zdE1lc3NhZ2UoIHsKCQkJCWJ2aERhdGEsCgkJCQlvcmlnaW5hbFRvQnZoLAoJCQkJdHJpYW5nbGVDb3VudDogaW5wdXRUcmlhbmdsZXMubGVuZ3RoIC8gMzIsCgkJCQl0cmVlbGV0U3RhdHM6IGJ1aWxkZXIuc3BsaXRTdGF0cwoJCQl9LCB0cmFuc2ZlcmFibGVzICk7CgoJCX0gZWxzZSB7CgoJCQljb25zdCByZW9yZGVyZWRGbG9hdDMyQXJyYXkgPSBidWlsZGVyLnJlb3JkZXJlZFRyaWFuZ2xlRGF0YTsKCQkJY29uc3QgdHJpYW5nbGVDb3VudCA9IHJlb3JkZXJlZEZsb2F0MzJBcnJheS5ieXRlTGVuZ3RoIC8gKCAzMiAqIDQgKTsKCgkJCWNvbnN0IHRyYW5zZmVyYWJsZXMgPSBbIGJ2aERhdGEuYnVmZmVyLCByZW9yZGVyZWRGbG9hdDMyQXJyYXkuYnVmZmVyIF07CgkJCWlmICggb3JpZ2luYWxUb0J2aCApIHRyYW5zZmVyYWJsZXMucHVzaCggb3JpZ2luYWxUb0J2aC5idWZmZXIgKTsKCgkJCXNlbGYucG9zdE1lc3NhZ2UoIHsKCQkJCWJ2aERhdGEsCgkJCQl0cmlhbmdsZXM6IHJlb3JkZXJlZEZsb2F0MzJBcnJheSwKCQkJCW9yaWdpbmFsVG9CdmgsCgkJCQl0cmlhbmdsZUNvdW50LAoJCQkJdHJlZWxldFN0YXRzOiBidWlsZGVyLnNwbGl0U3RhdHMKCQkJfSwgdHJhbnNmZXJhYmxlcyApOwoKCQl9CgoJfSBjYXRjaCAoIGVycm9yICkgewoKCQljb25zb2xlLmVycm9yKCAnW0JWSFdvcmtlcl0gRXJyb3I6JywgZXJyb3IgKTsKCQlzZWxmLnBvc3RNZXNzYWdlKCB7IGVycm9yOiBlcnJvci5tZXNzYWdlIH0gKTsKCgl9Cgp9Cg==`,``+self.location.href)).then(s).catch(()=>{console.warn(`Worker fetch fallback failed, using synchronous build`),i(this._buildSyncAndFlatten(e,t,n))}):(console.warn(`Worker creation failed, falling back to synchronous build:`,a),i(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,i=null,o=null){let s=performance.now();this.totalNodes=0,this.processedTriangles=0,this.triangles=t,this.totalTriangles=t.byteLength/(a*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,i);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,a=new e(this.traversalCost,this.intersectionCost);a.setTreeletSize(n),a.setMinImprovement(this.treeletMinImprovement),a.setMaxTreelets(r);let o=performance.now();for(let e=0;e<this.treeletOptimizationPasses;e++){let t=i?t=>{i(`Treelet optimization pass ${e+1}/${this.treeletOptimizationPasses}: ${t}`)}:null;try{a.optimizeBVH(d,t)}catch(t){console.error(`TreeletOptimizer: Error in pass ${e+1}:`,t);break}let n=a.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=a.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=i?e=>{i(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*a);for(let e=0;e<c;e++){let t=this.indices[e]*a,n=e*a;h.set(m.subarray(t,t+a),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=this.splitStats;return console.log(`[BVH] ${c.toLocaleString()} tris → ${this.totalNodes} nodes in ${Math.round(_)}ms | SAH ${v.sahSplits} objMed ${v.objectMedianSplits} spatMed ${v.spatialMedianSplits} failed ${v.failedSplits}`+(v.treeletsProcessed?` | treelets ${v.treeletsImproved}/${v.treeletsProcessed} improved`:``)+(v.reinsertionsApplied?` | reinsertions ${v.reinsertionsApplied}`:``)),i&&i(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,a,s,c,l,u,d){let f=new o;this.totalNodes++;let p=t-e;if(a===void 0?this.updateNodeBounds(f,e,t):(f.minX=a,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,a,s,c,l,u){let d=new o;this.totalNodes++;let f=t-e;if(i===void 0?this.updateNodeBounds(d,e,t):(d.minX=i,d.minY=a,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(t){let{tasks:r,sharedTriangleData:i,sharedCentroids:a,sharedBMin:o,sharedBMax:c,sharedIndices:l,triangleCount:u,maxLeafSize:d,numBins:f,maxBins:p,minBins:m,treeletConfig:h,reinsertionConfig:g,reportProgress:_}=t.data;for(let t=0;t<r.length;t++){let v=r[t];try{let t=new s;t.maxLeafSize=d,t.numBins=f,t.maxBins=p,t.minBins=m,t.triangles=new Float32Array(i),t.centroids=new Float32Array(a),t.bMin=new Float32Array(o),t.bMax=new Float32Array(c),t.indices=new Uint32Array(l),t.totalTriangles=u,t.totalNodes=0,t.processedTriangles=0,t.lastProgressUpdate=performance.now(),t.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 r=_?e=>{self.postMessage({type:`progress`,taskId:v.taskId,progress:e})}:null,y=performance.now(),b=t.buildNodeRecursive(v.start,v.end,v.depth,r,v.preMinX,v.preMinY,v.preMinZ,v.preMaxX,v.preMaxY,v.preMaxZ);if(h&&h.enabled&&v.end-v.start>1e3){let n=v.end-v.start>5e4,r=n?3:h.size||5,i=n?10:20,a=new e(t.traversalCost,t.intersectionCost);a.setTreeletSize(r),a.setMinImprovement(h.minImprovement||.02),a.setMaxTreelets(i);let o=h.passes||1;for(let e=0;e<o;e++)try{a.optimizeBVH(b,null)}catch(t){console.error(`[BVHSubtreeWorker] Treelet pass ${e+1} error:`,t);break}}if(g&&g.enabled&&v.end-v.start>1e3){let e=new n(t.traversalCost,t.intersectionCost);g.batchSizeRatio&&e.setBatchSizeRatio(g.batchSizeRatio),g.maxIterations&&e.setMaxIterations(g.maxIterations);try{e.optimizeBVH(b,null)}catch(e){console.error(`[BVHSubtreeWorker] Reinsertion error:`,e)}}t.applySAOrdering(b);let x=t.flattenBVH(b),S=x.length/16,C=performance.now()-y;console.log(`[BVHSubtreeWorker] Task ${v.taskId}: ${(v.end-v.start).toLocaleString()} triangles, ${S} nodes, ${Math.round(C)}ms`),self.postMessage({type:`subtreeResult`,taskId:v.taskId,flatData:x,nodeCount:S},[x.buffer])}catch(e){console.error(`[BVHSubtreeWorker] Task ${v.taskId} error:`,e),self.postMessage({type:`error`,taskId:v.taskId,error:e.message})}}}})();
|
|
2
|
+
//# sourceMappingURL=BVHSubtreeWorker-CTHfS54a.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BVHSubtreeWorker-CTHfS54a.js","names":[],"sources":["../../src/Processor/TreeletOptimizer.js","../../src/Processor/ReinsertionOptimizer.js","../../src/Processor/Workers/fetchAsWorker.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","/**\n * Cross-origin Worker fallback.\n *\n * Browsers enforce same-origin policy on `new Worker(url)`. When the\n * library's JS is served from a CDN (different origin to the page),\n * the standard constructor throws `SecurityError`.\n *\n * This helper fetches the script over the network (CORS-allowed) and\n * re-hosts it as a same-origin Blob URL.\n *\n * The Blob URL is intentionally **not** revoked — workers may reference\n * `self.location.href` to spawn sub-workers (e.g. BVHWorker).\n *\n * @param {URL|string} url Worker script URL (may be cross-origin)\n * @returns {Promise<Worker>}\n */\nexport async function fetchAsWorker( url ) {\n\n\tconst href = url instanceof URL ? url.href : url;\n\tconst response = await fetch( href );\n\tif ( ! response.ok ) {\n\n\t\tthrow new Error( `Failed to fetch worker script: ${response.status}` );\n\n\t}\n\n\tconst blob = new Blob( [ await response.text() ], { type: 'application/javascript' } );\n\treturn new Worker( URL.createObjectURL( blob ) );\n\n}\n","import { TreeletOptimizer } from './TreeletOptimizer.js';\nimport { ReinsertionOptimizer } from './ReinsertionOptimizer.js';\nimport { fetchAsWorker } from './Workers/fetchAsWorker.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\tconst setupWorker = ( worker ) => {\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};\n\n\t\t\t\ttry {\n\n\t\t\t\t\tsetupWorker( 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} catch ( error ) {\n\n\t\t\t\t\tif ( error.name === 'SecurityError' ) {\n\n\t\t\t\t\t\tfetchAsWorker(\n\t\t\t\t\t\t\tnew URL( './Workers/BVHWorker.js', import.meta.url )\n\t\t\t\t\t\t).then( setupWorker ).catch( () => {\n\n\t\t\t\t\t\t\tconsole.warn( 'Worker fetch fallback failed, using synchronous build' );\n\t\t\t\t\t\t\tresolve( this._buildSyncAndFlatten( triangles, depth, progressCallback ) );\n\n\t\t\t\t\t\t} );\n\n\t\t\t\t\t} else {\n\n\t\t\t\t\t\tconsole.warn( 'Worker creation failed, falling back to synchronous build:', error );\n\t\t\t\t\t\tresolve( this._buildSyncAndFlatten( triangles, depth, progressCallback ) );\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\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\tconst total = this.splitStats.totalBuildTime;\n\t\tconst s = this.splitStats;\n\t\tconsole.log(\n\t\t\t`[BVH] ${n.toLocaleString()} tris → ${this.totalNodes} nodes in ${Math.round( total )}ms` +\n\t\t\t` | SAH ${s.sahSplits} objMed ${s.objectMedianSplits} spatMed ${s.spatialMedianSplits} failed ${s.failedSplits}` +\n\t\t\t( s.treeletsProcessed ? ` | treelets ${s.treeletsImproved}/${s.treeletsProcessed} improved` : '' ) +\n\t\t\t( s.reinsertionsApplied ? ` | reinsertions ${s.reinsertionsApplied}` : '' )\n\t\t);\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}};async function r(e){let t=e instanceof URL?e.href:e,n=await fetch(t);if(!n.ok)throw Error(`Failed to fetch worker script: ${n.status}`);let r=new Blob([await n.text()],{type:`application/javascript`});return new Worker(URL.createObjectURL(r))}let i={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},a=i.FLOATS_PER_TRIANGLE;var o=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}},s=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=i.POSITION_A_OFFSET,r=i.POSITION_B_OFFSET,o=i.POSITION_C_OFFSET;for(let i=0;i<e;i++){let e=i*a,s=t[e+n],c=t[e+n+1],l=t[e+n+2],u=t[e+r],d=t[e+r+1],f=t[e+r+2],p=t[e+o],m=t[e+o+1],h=t[e+o+2],g=i*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[i]=i}}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/(a*4),this.processedTriangles=0,this.lastProgressUpdate=performance.now(),this.useWorker&&typeof Worker<`u`?new Promise((i,o)=>{let s=r=>{let s=this.totalTriangles,c=typeof SharedArrayBuffer<`u`;console.log(`[BVHBuilder] SharedArrayBuffer: ${c?`enabled`:`unavailable (using transfer fallback)`}`);let l=c?new SharedArrayBuffer(s*a*4):null;r.onmessage=e=>{let{bvhData:t,triangles:a,originalToBvh:s,error:c,progress:u,treeletStats:d}=e.data;if(c){r.terminate(),o(Error(c));return}if(u!==void 0&&n){n(u);return}d&&(this.splitStats=d),r.terminate(),i({bvhData:t,bvhRoot:!0,reorderedTriangles:l?new Float32Array(l):a,originalToBvh:s||null})},r.onerror=e=>{r.terminate(),o(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}};r.postMessage(d,[u])};try{s(new Worker(self.location.href,{type:`module`}))}catch(a){a.name===`SecurityError`?r(new URL(`data:text/javascript;base64,aW1wb3J0IHsgQlZIQnVpbGRlciB9IGZyb20gJy4uL0JWSEJ1aWxkZXIuanMnOwoKY29uc3QgRlBUID0gMzI7IC8vIEZMT0FUU19QRVJfVFJJQU5HTEUKCi8vIC0tLSBNZXNzYWdlIGRpc3BhdGNoZXIgLS0tCgpzZWxmLm9ubWVzc2FnZSA9IGZ1bmN0aW9uICggZSApIHsKCgljb25zdCBkYXRhID0gZS5kYXRhOwoJY29uc3QgdHlwZSA9IGRhdGEudHlwZTsKCglpZiAoIHR5cGUgPT09ICdidWlsZFBoYXNlMScgKSB7CgoJCWhhbmRsZVBoYXNlMSggZGF0YSApOwoKCX0gZWxzZSBpZiAoIHR5cGUgPT09ICdhc3NlbWJsZScgKSB7CgoJCWhhbmRsZUFzc2VtYmxlKCBkYXRhICk7CgoJfSBlbHNlIHsKCgkJLy8gTGVnYWN5OiBmdWxsIHNpbmdsZS13b3JrZXIgYnVpbGQgKGJhY2t3YXJkIGNvbXBhdGlibGUpCgkJaGFuZGxlRnVsbEJ1aWxkKCBkYXRhICk7CgoJfQoKfTsKCi8vIC0tLSBQaGFzZSAxOiBJbml0ICsgTW9ydG9uIHNvcnQgKyB0b3AtbGV2ZWwgU0FIIGJ1aWxkIC0tLQoKZnVuY3Rpb24gaGFuZGxlUGhhc2UxKCBkYXRhICkgewoKCWNvbnN0IHsKCQlzaGFyZWRUcmlhbmdsZURhdGEsIHNoYXJlZENlbnRyb2lkcywgc2hhcmVkQk1pbiwgc2hhcmVkQk1heCwKCQlzaGFyZWRJbmRpY2VzLCBzaGFyZWRNb3J0b25Db2RlcywKCQl0cmlhbmdsZUNvdW50LCBkZXB0aCwgcGFyYWxsZWxEZXB0aCwKCQlyZXBvcnRQcm9ncmVzcywgdHJlZWxldE9wdGltaXphdGlvbgoJfSA9IGRhdGE7CgoJdHJ5IHsKCgkJY29uc3QgYnVpbGRlciA9IG5ldyBCVkhCdWlsZGVyKCk7CgoJCWlmICggdHJlZWxldE9wdGltaXphdGlvbiApIHsKCgkJCWJ1aWxkZXIuc2V0VHJlZWxldENvbmZpZyggdHJlZWxldE9wdGltaXphdGlvbiApOwoKCQl9CgoJCWNvbnN0IHByb2dyZXNzQ2FsbGJhY2sgPSByZXBvcnRQcm9ncmVzcyA/ICggcHJvZ3Jlc3MgKSA9PiB7CgoJCQlzZWxmLnBvc3RNZXNzYWdlKCB7IHR5cGU6ICdwcm9ncmVzcycsIHByb2dyZXNzIH0gKTsKCgkJfSA6IG51bGw7CgoJCS8vIEF0dGFjaCBzaGFyZWQgYnVmZmVyIHZpZXdzCgkJYnVpbGRlci50cmlhbmdsZXMgPSBuZXcgRmxvYXQzMkFycmF5KCBzaGFyZWRUcmlhbmdsZURhdGEgKTsKCQlidWlsZGVyLmNlbnRyb2lkcyA9IG5ldyBGbG9hdDMyQXJyYXkoIHNoYXJlZENlbnRyb2lkcyApOwoJCWJ1aWxkZXIuYk1pbiA9IG5ldyBGbG9hdDMyQXJyYXkoIHNoYXJlZEJNaW4gKTsKCQlidWlsZGVyLmJNYXggPSBuZXcgRmxvYXQzMkFycmF5KCBzaGFyZWRCTWF4ICk7CgkJYnVpbGRlci5pbmRpY2VzID0gbmV3IFVpbnQzMkFycmF5KCBzaGFyZWRJbmRpY2VzICk7CgkJYnVpbGRlci5tb3J0b25Db2RlcyA9IG5ldyBVaW50MzJBcnJheSggc2hhcmVkTW9ydG9uQ29kZXMgKTsKCQlidWlsZGVyLnRvdGFsVHJpYW5nbGVzID0gdHJpYW5nbGVDb3VudDsKCgkJLy8gUmVzZXQgc3RhdGUKCQlidWlsZGVyLnRvdGFsTm9kZXMgPSAwOwoJCWJ1aWxkZXIucHJvY2Vzc2VkVHJpYW5nbGVzID0gMDsKCQlidWlsZGVyLmxhc3RQcm9ncmVzc1VwZGF0ZSA9IHBlcmZvcm1hbmNlLm5vdygpOwoKCQlidWlsZGVyLnNwbGl0U3RhdHMgPSB7CgkJCXNhaFNwbGl0czogMCwgb2JqZWN0TWVkaWFuU3BsaXRzOiAwLCBzcGF0aWFsTWVkaWFuU3BsaXRzOiAwLAoJCQlmYWlsZWRTcGxpdHM6IDAsIGF2Z0JpbnNVc2VkOiAwLCB0b3RhbFNwbGl0QXR0ZW1wdHM6IDAsCgkJCW1vcnRvblNvcnRUaW1lOiAwLCB0b3RhbEJ1aWxkVGltZTogMCwgdHJlZWxldE9wdGltaXphdGlvblRpbWU6IDAsCgkJCXRyZWVsZXRzUHJvY2Vzc2VkOiAwLCB0cmVlbGV0c0ltcHJvdmVkOiAwLCBhdmVyYWdlU0FISW1wcm92ZW1lbnQ6IDAsCgkJCWluaXRUaW1lOiAwLCBzYWhCdWlsZFRpbWU6IDAsIHJlb3JkZXJUaW1lOiAwCgkJfTsKCgkJY29uc3Qgc3RhcnRUaW1lID0gcGVyZm9ybWFuY2Uubm93KCk7CgoJCS8vIFBoYXNlIDFhOiBJbml0aWFsaXplIHBlci10cmlhbmdsZSBhcnJheXMgKHdyaXRlcyBpbnRvIHNoYXJlZCBidWZmZXJzKQoJCWNvbnN0IGluaXRTdGFydCA9IHBlcmZvcm1hbmNlLm5vdygpOwoJCWJ1aWxkZXIuaW5pdGlhbGl6ZVRyaWFuZ2xlQXJyYXlzKCk7CgkJYnVpbGRlci5zcGxpdFN0YXRzLmluaXRUaW1lID0gcGVyZm9ybWFuY2Uubm93KCkgLSBpbml0U3RhcnQ7CgoJCS8vIFBoYXNlIDFiOiBNb3J0b24gY29kZSBzcGF0aWFsIGNsdXN0ZXJpbmcKCQlidWlsZGVyLnNvcnRUcmlhbmdsZXNCeU1vcnRvbkNvZGUoKTsKCgkJLy8gUGhhc2UgMWM6IEJ1aWxkIHRvcC1sZXZlbCB0cmVlIHRvIHBhcmFsbGVsRGVwdGgKCQlidWlsZGVyLmZyb250aWVyVGFza3MgPSBbXTsKCQljb25zdCBzYWhTdGFydCA9IHBlcmZvcm1hbmNlLm5vdygpOwoJCWNvbnN0IHJvb3QgPSBidWlsZGVyLmJ1aWxkTm9kZVJlY3Vyc2l2ZVRvRGVwdGgoIDAsIHRyaWFuZ2xlQ291bnQsIGRlcHRoLCBwYXJhbGxlbERlcHRoLCBwcm9ncmVzc0NhbGxiYWNrICk7CgkJYnVpbGRlci5zcGxpdFN0YXRzLnNhaEJ1aWxkVGltZSA9IHBlcmZvcm1hbmNlLm5vdygpIC0gc2FoU3RhcnQ7CgoJCS8vIFBoYXNlIDFkOiBTdXJmYWNlLWFyZWEgY2hpbGQgb3JkZXJpbmcgKERGUyBjYWNoZSBsb2NhbGl0eSkKCQlidWlsZGVyLmFwcGx5U0FPcmRlcmluZyggcm9vdCApOwoKCQkvLyBQaGFzZSAxZTogRmxhdHRlbiB0b3AtbGV2ZWwgdHJlZSB3aXRoIGZyb250aWVyIHNlbnRpbmVscwoJCWNvbnN0IGZsYXR0ZW5TdGFydCA9IHBlcmZvcm1hbmNlLm5vdygpOwoJCWNvbnN0IHsgZmxhdERhdGEsIGZyb250aWVyTWFwLCBub2RlQ291bnQgfSA9IGJ1aWxkZXIuZmxhdHRlbkJWSFdpdGhGcm9udGllciggcm9vdCApOwoJCWNvbnN0IGZsYXR0ZW5UaW1lID0gcGVyZm9ybWFuY2Uubm93KCkgLSBmbGF0dGVuU3RhcnQ7CgoJCWNvbnN0IHRvdGFsVGltZSA9IHBlcmZvcm1hbmNlLm5vdygpIC0gc3RhcnRUaW1lOwoJCWNvbnNvbGUubG9nKCBgW0JWSFdvcmtlcl0gUGhhc2UgMTogJHtNYXRoLnJvdW5kKCB0b3RhbFRpbWUgKX1tcyAoaW5pdDogJHtNYXRoLnJvdW5kKCBidWlsZGVyLnNwbGl0U3RhdHMuaW5pdFRpbWUgKX1tcywgbW9ydG9uOiAke01hdGgucm91bmQoIGJ1aWxkZXIuc3BsaXRTdGF0cy5tb3J0b25Tb3J0VGltZSApfW1zLCBTQUg6ICR7TWF0aC5yb3VuZCggYnVpbGRlci5zcGxpdFN0YXRzLnNhaEJ1aWxkVGltZSApfW1zLCBmbGF0dGVuOiAke01hdGgucm91bmQoIGZsYXR0ZW5UaW1lICl9bXMpLCAke2J1aWxkZXIuZnJvbnRpZXJUYXNrcy5sZW5ndGh9IGZyb250aWVyIHRhc2tzYCApOwoKCQlzZWxmLnBvc3RNZXNzYWdlKCB7CgkJCXR5cGU6ICdwaGFzZTFSZXN1bHQnLAoJCQl0b3BGbGF0RGF0YTogZmxhdERhdGEsCgkJCXRvcE5vZGVDb3VudDogbm9kZUNvdW50LAoJCQlmcm9udGllclRhc2tzOiBidWlsZGVyLmZyb250aWVyVGFza3MsCgkJCWZyb250aWVyTWFwLAoJCQlzcGxpdFN0YXRzOiBidWlsZGVyLnNwbGl0U3RhdHMKCQl9LCBbIGZsYXREYXRhLmJ1ZmZlciBdICk7CgoJfSBjYXRjaCAoIGVycm9yICkgewoKCQljb25zb2xlLmVycm9yKCAnW0JWSFdvcmtlcl0gUGhhc2UgMSBlcnJvcjonLCBlcnJvciApOwoJCXNlbGYucG9zdE1lc3NhZ2UoIHsgdHlwZTogJ2Vycm9yJywgZXJyb3I6IGVycm9yLm1lc3NhZ2UgfSApOwoKCX0KCn0KCi8vIC0tLSBQaGFzZSAzOiBBc3NlbWJsZSBmaW5hbCBCVkggKyByZW9yZGVyIHRyaWFuZ2xlcyAtLS0KCmZ1bmN0aW9uIGhhbmRsZUFzc2VtYmxlKCBkYXRhICkgewoKCWNvbnN0IHsKCQl0b3BGbGF0RGF0YSwgdG9wTm9kZUNvdW50LCBmcm9udGllck1hcCwgc3VidHJlZVJlc3VsdHMsCgkJc2hhcmVkVHJpYW5nbGVEYXRhLCBzaGFyZWRJbmRpY2VzLCBzaGFyZWRSZW9yZGVyQnVmZmVyLAoJCXRyaWFuZ2xlQ291bnQKCX0gPSBkYXRhOwoKCXRyeSB7CgoJCWNvbnN0IHN0YXJ0VGltZSA9IHBlcmZvcm1hbmNlLm5vdygpOwoJCWNvbnN0IGJ1aWxkZXIgPSBuZXcgQlZIQnVpbGRlcigpOwoKCQkvLyBBc3NlbWJsZSB0aGUgZmluYWwgQlZICgkJY29uc3QgYnZoRGF0YSA9IGJ1aWxkZXIuYXNzZW1ibGVQYXJhbGxlbEJWSCgKCQkJdG9wRmxhdERhdGEsIHRvcE5vZGVDb3VudCwgZnJvbnRpZXJNYXAsIHN1YnRyZWVSZXN1bHRzCgkJKTsKCgkJLy8gUmVvcmRlciB0cmlhbmdsZXMgdXNpbmcgZmluYWwgaW5kaWNlcyBmcm9tIFNoYXJlZEFycmF5QnVmZmVyCgkJY29uc3QgaW5kaWNlcyA9IG5ldyBVaW50MzJBcnJheSggc2hhcmVkSW5kaWNlcyApOwoJCWNvbnN0IHNyYyA9IG5ldyBGbG9hdDMyQXJyYXkoIHNoYXJlZFRyaWFuZ2xlRGF0YSApOwoJCWNvbnN0IGRzdCA9IG5ldyBGbG9hdDMyQXJyYXkoIHNoYXJlZFJlb3JkZXJCdWZmZXIgKTsKCgkJZm9yICggbGV0IGkgPSAwOyBpIDwgdHJpYW5nbGVDb3VudDsgaSArKyApIHsKCgkJCWNvbnN0IHNyY09mZiA9IGluZGljZXNbIGkgXSAqIEZQVDsKCQkJY29uc3QgZHN0T2ZmID0gaSAqIEZQVDsKCQkJZHN0LnNldCggc3JjLnN1YmFycmF5KCBzcmNPZmYsIHNyY09mZiArIEZQVCApLCBkc3RPZmYgKTsKCgkJfQoKCQkvLyBCdWlsZCBpbnZlcnNlIGluZGV4IG1hcCBmb3IgQlZIIHJlZml0CgkJY29uc3Qgb3JpZ2luYWxUb0J2aCA9IG5ldyBVaW50MzJBcnJheSggdHJpYW5nbGVDb3VudCApOwoJCWZvciAoIGxldCBpID0gMDsgaSA8IHRyaWFuZ2xlQ291bnQ7IGkgKysgKSB7CgoJCQlvcmlnaW5hbFRvQnZoWyBpbmRpY2VzWyBpIF0gXSA9IGk7CgoJCX0KCgkJY29uc3QgdG90YWxUaW1lID0gcGVyZm9ybWFuY2Uubm93KCkgLSBzdGFydFRpbWU7CgkJY29uc29sZS5sb2coIGBbQlZIV29ya2VyXSBQaGFzZSAzIChhc3NlbWJsZSArIHJlb3JkZXIpOiAke01hdGgucm91bmQoIHRvdGFsVGltZSApfW1zICgkeyggYnZoRGF0YS5ieXRlTGVuZ3RoIC8gMTAyNCAvIDEwMjQgKS50b0ZpeGVkKCAxICl9TUIgQlZIKWAgKTsKCgkJc2VsZi5wb3N0TWVzc2FnZSggewoJCQl0eXBlOiAnYXNzZW1ibGVSZXN1bHQnLAoJCQlidmhEYXRhLAoJCQlvcmlnaW5hbFRvQnZoLAoJCQl0cmlhbmdsZUNvdW50CgkJfSwgWyBidmhEYXRhLmJ1ZmZlciwgb3JpZ2luYWxUb0J2aC5idWZmZXIgXSApOwoKCX0gY2F0Y2ggKCBlcnJvciApIHsKCgkJY29uc29sZS5lcnJvciggJ1tCVkhXb3JrZXJdIEFzc2VtYmx5IGVycm9yOicsIGVycm9yICk7CgkJc2VsZi5wb3N0TWVzc2FnZSggeyB0eXBlOiAnZXJyb3InLCBlcnJvcjogZXJyb3IubWVzc2FnZSB9ICk7CgoJfQoKfQoKLy8gLS0tIExlZ2FjeTogZnVsbCBzaW5nbGUtd29ya2VyIGJ1aWxkIC0tLQoKZnVuY3Rpb24gaGFuZGxlRnVsbEJ1aWxkKCBkYXRhICkgewoKCWNvbnN0IHsgdHJpYW5nbGVEYXRhLCB0cmlhbmdsZUJ5dGVPZmZzZXQsIHRyaWFuZ2xlQnl0ZUxlbmd0aCwgZGVwdGgsIHJlcG9ydFByb2dyZXNzLCB0cmVlbGV0T3B0aW1pemF0aW9uLCByZWluc2VydGlvbk9wdGltaXphdGlvbiwgc2hhcmVkUmVvcmRlckJ1ZmZlciB9ID0gZGF0YTsKCWNvbnN0IGJ1aWxkZXIgPSBuZXcgQlZIQnVpbGRlcigpOwoKCXRyeSB7CgoJCWlmICggdHJlZWxldE9wdGltaXphdGlvbiApIHsKCgkJCWJ1aWxkZXIuc2V0VHJlZWxldENvbmZpZyggdHJlZWxldE9wdGltaXphdGlvbiApOwoKCQl9CgoJCWlmICggcmVpbnNlcnRpb25PcHRpbWl6YXRpb24gKSB7CgoJCQlidWlsZGVyLnNldFJlaW5zZXJ0aW9uQ29uZmlnKCByZWluc2VydGlvbk9wdGltaXphdGlvbiApOwoKCQl9CgoJCWNvbnN0IHByb2dyZXNzQ2FsbGJhY2sgPSByZXBvcnRQcm9ncmVzcyA/ICggcHJvZ3Jlc3MgKSA9PiB7CgoJCQlzZWxmLnBvc3RNZXNzYWdlKCB7IHByb2dyZXNzIH0gKTsKCgkJfSA6IG51bGw7CgoJCWNvbnN0IGlucHV0VHJpYW5nbGVzID0gdHJpYW5nbGVCeXRlT2Zmc2V0ICE9PSB1bmRlZmluZWQKCQkJPyBuZXcgRmxvYXQzMkFycmF5KCB0cmlhbmdsZURhdGEsIHRyaWFuZ2xlQnl0ZU9mZnNldCwgdHJpYW5nbGVCeXRlTGVuZ3RoIC8gNCApCgkJCTogbmV3IEZsb2F0MzJBcnJheSggdHJpYW5nbGVEYXRhICk7CgoJCWNvbnN0IHJlb3JkZXJUYXJnZXQgPSBzaGFyZWRSZW9yZGVyQnVmZmVyCgkJCT8gbmV3IEZsb2F0MzJBcnJheSggc2hhcmVkUmVvcmRlckJ1ZmZlciApCgkJCTogbnVsbDsKCgkJY29uc3QgYnZoUm9vdCA9IGJ1aWxkZXIuYnVpbGRTeW5jKCBpbnB1dFRyaWFuZ2xlcywgZGVwdGgsIHByb2dyZXNzQ2FsbGJhY2ssIHJlb3JkZXJUYXJnZXQgKTsKCgkJY29uc3QgZmxhdHRlblN0YXJ0ID0gcGVyZm9ybWFuY2Uubm93KCk7CgkJY29uc3QgYnZoRGF0YSA9IGJ1aWxkZXIuZmxhdHRlbkJWSCggYnZoUm9vdCApOwoJCWNvbnN0IGZsYXR0ZW5UaW1lID0gcGVyZm9ybWFuY2Uubm93KCkgLSBmbGF0dGVuU3RhcnQ7CgkJY29uc29sZS5sb2coIGBbQlZIV29ya2VyXSBGbGF0dGVuIEJWSDogJHtNYXRoLnJvdW5kKCBmbGF0dGVuVGltZSApfW1zICgkeyggYnZoRGF0YS5ieXRlTGVuZ3RoIC8gMTAyNCAvIDEwMjQgKS50b0ZpeGVkKCAxICl9TUIpYCApOwoKCQljb25zdCBvcmlnaW5hbFRvQnZoID0gYnVpbGRlci5vcmlnaW5hbFRvQnZoTWFwIHx8IG51bGw7CgoJCWlmICggc2hhcmVkUmVvcmRlckJ1ZmZlciApIHsKCgkJCWNvbnN0IHRyYW5zZmVyYWJsZXMgPSBbIGJ2aERhdGEuYnVmZmVyIF07CgkJCWlmICggb3JpZ2luYWxUb0J2aCApIHRyYW5zZmVyYWJsZXMucHVzaCggb3JpZ2luYWxUb0J2aC5idWZmZXIgKTsKCgkJCXNlbGYucG9zdE1lc3NhZ2UoIHsKCQkJCWJ2aERhdGEsCgkJCQlvcmlnaW5hbFRvQnZoLAoJCQkJdHJpYW5nbGVDb3VudDogaW5wdXRUcmlhbmdsZXMubGVuZ3RoIC8gMzIsCgkJCQl0cmVlbGV0U3RhdHM6IGJ1aWxkZXIuc3BsaXRTdGF0cwoJCQl9LCB0cmFuc2ZlcmFibGVzICk7CgoJCX0gZWxzZSB7CgoJCQljb25zdCByZW9yZGVyZWRGbG9hdDMyQXJyYXkgPSBidWlsZGVyLnJlb3JkZXJlZFRyaWFuZ2xlRGF0YTsKCQkJY29uc3QgdHJpYW5nbGVDb3VudCA9IHJlb3JkZXJlZEZsb2F0MzJBcnJheS5ieXRlTGVuZ3RoIC8gKCAzMiAqIDQgKTsKCgkJCWNvbnN0IHRyYW5zZmVyYWJsZXMgPSBbIGJ2aERhdGEuYnVmZmVyLCByZW9yZGVyZWRGbG9hdDMyQXJyYXkuYnVmZmVyIF07CgkJCWlmICggb3JpZ2luYWxUb0J2aCApIHRyYW5zZmVyYWJsZXMucHVzaCggb3JpZ2luYWxUb0J2aC5idWZmZXIgKTsKCgkJCXNlbGYucG9zdE1lc3NhZ2UoIHsKCQkJCWJ2aERhdGEsCgkJCQl0cmlhbmdsZXM6IHJlb3JkZXJlZEZsb2F0MzJBcnJheSwKCQkJCW9yaWdpbmFsVG9CdmgsCgkJCQl0cmlhbmdsZUNvdW50LAoJCQkJdHJlZWxldFN0YXRzOiBidWlsZGVyLnNwbGl0U3RhdHMKCQkJfSwgdHJhbnNmZXJhYmxlcyApOwoKCQl9CgoJfSBjYXRjaCAoIGVycm9yICkgewoKCQljb25zb2xlLmVycm9yKCAnW0JWSFdvcmtlcl0gRXJyb3I6JywgZXJyb3IgKTsKCQlzZWxmLnBvc3RNZXNzYWdlKCB7IGVycm9yOiBlcnJvci5tZXNzYWdlIH0gKTsKCgl9Cgp9Cg==`,``+self.location.href)).then(s).catch(()=>{console.warn(`Worker fetch fallback failed, using synchronous build`),i(this._buildSyncAndFlatten(e,t,n))}):(console.warn(`Worker creation failed, falling back to synchronous build:`,a),i(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,i=null,o=null){let s=performance.now();this.totalNodes=0,this.processedTriangles=0,this.triangles=t,this.totalTriangles=t.byteLength/(a*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,i);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,a=new e(this.traversalCost,this.intersectionCost);a.setTreeletSize(n),a.setMinImprovement(this.treeletMinImprovement),a.setMaxTreelets(r);let o=performance.now();for(let e=0;e<this.treeletOptimizationPasses;e++){let t=i?t=>{i(`Treelet optimization pass ${e+1}/${this.treeletOptimizationPasses}: ${t}`)}:null;try{a.optimizeBVH(d,t)}catch(t){console.error(`TreeletOptimizer: Error in pass ${e+1}:`,t);break}let n=a.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=a.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=i?e=>{i(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*a);for(let e=0;e<c;e++){let t=this.indices[e]*a,n=e*a;h.set(m.subarray(t,t+a),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=this.splitStats;return console.log(`[BVH] ${c.toLocaleString()} tris → ${this.totalNodes} nodes in ${Math.round(_)}ms | SAH ${v.sahSplits} objMed ${v.objectMedianSplits} spatMed ${v.spatialMedianSplits} failed ${v.failedSplits}`+(v.treeletsProcessed?` | treelets ${v.treeletsImproved}/${v.treeletsProcessed} improved`:``)+(v.reinsertionsApplied?` | reinsertions ${v.reinsertionsApplied}`:``)),i&&i(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,a,s,c,l,u,d){let f=new o;this.totalNodes++;let p=t-e;if(a===void 0?this.updateNodeBounds(f,e,t):(f.minX=a,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,a,s,c,l,u){let d=new o;this.totalNodes++;let f=t-e;if(i===void 0?this.updateNodeBounds(d,e,t):(d.minX=i,d.minY=a,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`?c(t):n===`assemble`?l(t):u(t)};function c(e){let{sharedTriangleData:t,sharedCentroids:n,sharedBMin:r,sharedBMax:i,sharedIndices:a,sharedMortonCodes:o,triangleCount:c,depth:l,parallelDepth:u,reportProgress:d,treeletOptimization:f}=e;try{let e=new s;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(o),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 l(e){let{topFlatData:t,topNodeCount:n,frontierMap:r,subtreeResults:i,sharedTriangleData:a,sharedIndices:o,sharedReorderBuffer:c,triangleCount:l}=e;try{let e=performance.now(),u=new s().assembleParallelBVH(t,n,r,i),d=new Uint32Array(o),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 u(e){let{triangleData:t,triangleByteOffset:n,triangleByteLength:r,depth:i,reportProgress:a,treeletOptimization:o,reinsertionOptimization:c,sharedReorderBuffer:l}=e,u=new s;try{o&&u.setTreeletConfig(o),c&&u.setReinsertionConfig(c);let e=a?e=>{self.postMessage({progress:e})}:null,s=n===void 0?new Float32Array(t):new Float32Array(t,n,r/4),d=l?new Float32Array(l):null,f=u.buildSync(s,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:s.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-BarjE67Z.js.map
|