rayzee 7.19.1 → 7.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -4
- package/dist/assets/{AIUpscalerWorker-AXN-lKWN.js.map → AIUpscalerWorker-ByQj53uK.js.map} +1 -1
- package/dist/assets/{BVHRefitWorker-GkmNJYvb.js.map → BVHRefitWorker-WezTgpI5.js.map} +1 -1
- package/dist/assets/BVHSubtreeWorker-B98NTgvc.js.map +1 -0
- package/dist/assets/BVHWorker-H923xA44.js.map +1 -0
- package/dist/assets/{CDFWorker-BFQUr3By.js.map → CDFWorker-CSxzmFhe.js.map} +1 -1
- package/dist/assets/{TexturesWorker-6mlBy4Dm.js.map → TexturesWorker-Dm6RnwKh.js.map} +1 -1
- package/dist/rayzee.es.js +1507 -1375
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +49 -48
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +2 -2
- package/src/Passes/AIUpscaler.js +2 -12
- package/src/PathTracerApp.js +270 -1
- package/src/Pipeline/RenderPipeline.js +6 -0
- package/src/Processor/BVHBuilder.js +4 -18
- package/src/Processor/EquirectHDRInfo.js +2 -12
- package/src/Processor/ParallelBVHBuilder.js +5 -36
- package/src/Processor/SceneProcessor.js +51 -32
- package/src/Processor/TextureCreator.js +2 -13
- package/src/Stages/PathTracerStage.js +23 -2
- package/dist/assets/AIUpscalerWorker-AXN-lKWN.js +0 -2
- package/dist/assets/BVHRefitWorker-GkmNJYvb.js +0 -2
- package/dist/assets/BVHSubtreeWorker-D7KMbrQ-.js +0 -2
- package/dist/assets/BVHSubtreeWorker-D7KMbrQ-.js.map +0 -1
- package/dist/assets/BVHWorker-DOaGn6uN.js +0 -2
- package/dist/assets/BVHWorker-DOaGn6uN.js.map +0 -1
- package/dist/assets/CDFWorker-BFQUr3By.js +0 -2
- package/dist/assets/TexturesWorker-6mlBy4Dm.js +0 -2
- package/src/Processor/Workers/fetchAsWorker.js +0 -35
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Rayzee Engine
|
|
2
2
|
|
|
3
|
-
[![npm]
|
|
4
|
-
[![
|
|
5
|
-
[![
|
|
6
|
-
[![jsDelivr]
|
|
3
|
+
[![NPM Package][npm]][npm-url]
|
|
4
|
+
[![Build Size][build-size]][build-size-url]
|
|
5
|
+
[![NPM Downloads][npm-downloads]][npmtrends-url]
|
|
6
|
+
[![jsDelivr Downloads][jsdelivr-downloads]][jsdelivr-url]
|
|
7
7
|
|
|
8
8
|
A real-time WebGPU path tracing engine built on Three.js. Framework-agnostic — use it with React, Vue, vanilla JS, or any other setup.
|
|
9
9
|
|
|
@@ -802,6 +802,24 @@ Your browser may not support WebGPU. Use Chrome 113+, Edge 113+, Safari 18+, or
|
|
|
802
802
|
**Models not loading**
|
|
803
803
|
If serving locally, place files in your `public/` folder and reference them with absolute paths (e.g., `/scene.glb`). For remote files, ensure the server allows CORS.
|
|
804
804
|
|
|
805
|
+
**Workers blocked by Content-Security-Policy**
|
|
806
|
+
Rayzee's Web Workers are embedded in the bundle and spawned from a `blob:` URL, so a strict `worker-src` policy will block them — the symptom is BVH building, texture processing, or HDRI CDF generation silently failing. Allow `blob:`:
|
|
807
|
+
|
|
808
|
+
```
|
|
809
|
+
Content-Security-Policy: worker-src 'self' blob:
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
Only needed if you set an explicit `worker-src` (or fall back to a restrictive `default-src`). Pages without a CSP are unaffected.
|
|
813
|
+
|
|
805
814
|
## License
|
|
806
815
|
|
|
807
816
|
MIT
|
|
817
|
+
|
|
818
|
+
[npm]: https://img.shields.io/npm/v/rayzee
|
|
819
|
+
[npm-url]: https://www.npmjs.com/package/rayzee
|
|
820
|
+
[build-size]: https://badgen.net/bundlephobia/minzip/rayzee
|
|
821
|
+
[build-size-url]: https://bundlephobia.com/result?p=rayzee
|
|
822
|
+
[npm-downloads]: https://img.shields.io/npm/dw/rayzee
|
|
823
|
+
[npmtrends-url]: https://www.npmtrends.com/rayzee
|
|
824
|
+
[jsdelivr-downloads]: https://img.shields.io/jsdelivr/npm/hm/rayzee
|
|
825
|
+
[jsdelivr-url]: https://www.jsdelivr.com/package/npm/rayzee
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AIUpscalerWorker-
|
|
1
|
+
{"version":3,"file":"AIUpscalerWorker-ByQj53uK.js","names":[],"sources":["../../src/Processor/Workers/AIUpscalerWorker.js"],"sourcesContent":["/**\n * Web Worker for AI Upscaler inference.\n * Handles ONNX model loading and tile-based inference off the main thread.\n *\n * Messages:\n * Main → Worker:\n * { type: 'load', url, sessionOptions } — load/switch model\n * { type: 'infer', tileData, width, height, id } — run inference on a tile\n * { type: 'dispose' } — release session\n *\n * Worker → Main:\n * { type: 'loaded', backend }\n * { type: 'inferred', outputData, id }\n * { type: 'error', message, id? }\n */\n\n// Asset config supplied via 'load' message from the main thread.\n// Defaults match the upstream Rayzee deployment; override via configureAssets().\nlet _assetConfig = {\n\tortRuntimeUrl: 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/ort.webgpu.bundle.min.mjs',\n\tortWasmPaths: 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/',\n\tcacheNamespace: 'rayzee',\n};\n\nlet ort = null;\n\nasync function getOrt() {\n\n\tif ( ort ) return ort;\n\n\tort = await import( /* @vite-ignore */ _assetConfig.ortRuntimeUrl );\n\n\t// WASM paths for CDN delivery — WebGPU EP still uses WASM for lightweight shape ops\n\tort.env.wasm.wasmPaths = _assetConfig.ortWasmPaths;\n\tort.env.logLevel = 'error';\n\n\treturn ort;\n\n}\n\n// IndexedDB names are namespaced so multiple consumers on the same origin don't collide.\nfunction getIdbName() {\n\n\treturn `${_assetConfig.cacheNamespace}:ai-upscaler-models`;\n\n}\n\nconst IDB_STORE = 'models';\n\nlet session = null;\nlet currentModelUrl = null;\n\n// ─── IndexedDB Model Cache ───────────────────────────────────────────────────\n\nfunction openDB() {\n\n\treturn new Promise( ( resolve, reject ) => {\n\n\t\tconst req = indexedDB.open( getIdbName(), 1 );\n\t\treq.onupgradeneeded = () => req.result.createObjectStore( IDB_STORE );\n\t\treq.onsuccess = () => resolve( req.result );\n\t\treq.onerror = () => reject( req.error );\n\n\t} );\n\n}\n\nasync function getCachedModel( url ) {\n\n\ttry {\n\n\t\tconst db = await openDB();\n\t\treturn await new Promise( ( resolve, reject ) => {\n\n\t\t\tconst tx = db.transaction( IDB_STORE, 'readonly' );\n\t\t\tconst req = tx.objectStore( IDB_STORE ).get( url );\n\t\t\treq.onsuccess = () => resolve( req.result || null );\n\t\t\treq.onerror = () => reject( req.error );\n\n\t\t} );\n\n\t} catch {\n\n\t\treturn null;\n\n\t}\n\n}\n\nasync function cacheModel( url, buffer ) {\n\n\ttry {\n\n\t\tconst db = await openDB();\n\t\tawait new Promise( ( resolve, reject ) => {\n\n\t\t\tconst tx = db.transaction( IDB_STORE, 'readwrite' );\n\t\t\ttx.objectStore( IDB_STORE ).put( buffer, url );\n\t\t\ttx.oncomplete = () => resolve();\n\t\t\ttx.onerror = () => reject( tx.error );\n\n\t\t} );\n\n\t} catch {\n\n\t\t// Cache write failure is non-fatal\n\t}\n\n}\n\n// ─── Model Loading ───────────────────────────────────────────────────────────\n\nasync function fetchModel( url ) {\n\n\t// Try IndexedDB cache first\n\tconst cached = await getCachedModel( url );\n\tif ( cached ) {\n\n\t\tconsole.log( `AI Upscaler Worker: model loaded from cache (${( cached.byteLength / 1024 / 1024 ).toFixed( 1 )}MB)` );\n\t\treturn cached;\n\n\t}\n\n\t// Network fetch + cache\n\tconst response = await fetch( url );\n\tif ( ! response.ok ) throw new Error( `Failed to fetch model: ${response.status}` );\n\tconst buffer = await response.arrayBuffer();\n\n\t// Cache in background (don't block session creation)\n\tcacheModel( url, buffer.slice( 0 ) );\n\n\treturn buffer;\n\n}\n\nasync function loadModel( url, sessionOptions ) {\n\n\tif ( session && currentModelUrl === url ) {\n\n\t\tconst backend = 'webgpu';\n\t\tself.postMessage( { type: 'loaded', backend } );\n\t\treturn;\n\n\t}\n\n\t// Dispose previous session\n\tif ( session ) {\n\n\t\tawait session.release();\n\t\tsession = null;\n\n\t}\n\n\tconst [ modelBuffer, ortLib ] = await Promise.all( [ fetchModel( url ), getOrt() ] );\n\n\tsession = await ortLib.InferenceSession.create( modelBuffer, sessionOptions );\n\tcurrentModelUrl = url;\n\n\t// Detect GPU and recommend tile size based on device type\n\tlet tileSize = 512; // default\n\ttry {\n\n\t\tconst adapter = await navigator.gpu?.requestAdapter();\n\t\tconst info = await adapter?.requestAdapterInfo?.() || adapter?.info;\n\t\tconst isMobile = /apple|swiftshader|llvmpipe/i.test( info?.vendor || '' )\n\t\t\t|| /apple|swiftshader/i.test( info?.architecture || '' );\n\t\tconst isIntegrated = info?.device?.toLowerCase?.()?.includes( 'integrated' )\n\t\t\t|| /intel.*iris|intel.*uhd|intel.*hd|amd.*vega|radeon.*graphics/i.test( info?.description || '' );\n\n\t\tif ( isMobile ) {\n\n\t\t\ttileSize = 128;\n\n\t\t} else if ( isIntegrated ) {\n\n\t\t\ttileSize = 256;\n\n\t\t} else {\n\n\t\t\ttileSize = 512;\n\n\t\t}\n\n\t\tconsole.log( `AI Upscaler Worker: GPU=\"${info?.description || info?.device || 'unknown'}\", tileSize=${tileSize}` );\n\n\t} catch { /* fallback to default */ }\n\n\tconst sizeMB = ( modelBuffer.byteLength / 1024 / 1024 ).toFixed( 1 );\n\tconsole.log( `AI Upscaler Worker: model loaded (${sizeMB}MB), backend: webgpu` );\n\n\tself.postMessage( { type: 'loaded', backend: 'webgpu', tileSize } );\n\n}\n\nasync function inferTile( tileData, width, height, id ) {\n\n\tconst ortLib = await getOrt();\n\tconst inputName = session.inputNames[ 0 ];\n\tconst outputName = session.outputNames[ 0 ];\n\tconst inputTensor = new ortLib.Tensor( 'float32', tileData, [ 1, 3, height, width ] );\n\n\tconst results = await session.run( { [ inputName ]: inputTensor } );\n\tconst outputData = results[ outputName ].data;\n\n\t// Transfer the output buffer (zero-copy)\n\tself.postMessage( { type: 'inferred', outputData, id }, [ outputData.buffer ] );\n\n}\n\nself.onmessage = async ( e ) => {\n\n\tconst { type } = e.data;\n\n\ttry {\n\n\t\tif ( type === 'load' ) {\n\n\t\t\t// Apply asset overrides from main thread before any network or cache access\n\t\t\tif ( e.data.ortRuntimeUrl ) _assetConfig.ortRuntimeUrl = e.data.ortRuntimeUrl;\n\t\t\tif ( e.data.ortWasmPaths ) _assetConfig.ortWasmPaths = e.data.ortWasmPaths;\n\t\t\tif ( e.data.cacheNamespace ) _assetConfig.cacheNamespace = e.data.cacheNamespace;\n\t\t\tawait loadModel( e.data.url, e.data.sessionOptions );\n\n\t\t} else if ( type === 'infer' ) {\n\n\t\t\tawait inferTile( e.data.tileData, e.data.width, e.data.height, e.data.id );\n\n\t\t} else if ( type === 'dispose' ) {\n\n\t\t\tif ( session ) {\n\n\t\t\t\tawait session.release();\n\t\t\t\tsession = null;\n\t\t\t\tcurrentModelUrl = null;\n\n\t\t\t}\n\n\t\t}\n\n\t} catch ( error ) {\n\n\t\tself.postMessage( { type: 'error', message: error.message, id: e.data?.id } );\n\n\t}\n\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BVHRefitWorker-GkmNJYvb.js","names":["FLOATS_PER_NODE"],"sources":["../../src/Processor/BVHRefitter.js","../../src/Processor/Workers/BVHRefitWorker.js"],"sourcesContent":["/**\n * BVHRefitter — Fast O(N) bottom-up BVH AABB refit for animated geometry.\n *\n * When mesh topology stays the same but vertex positions change (skeletal animation,\n * morph targets), this avoids the full O(N log N) SAH rebuild by recomputing only\n * the bounding boxes in the existing tree structure.\n *\n * Designed to run in both main thread and Web Worker contexts.\n */\n\n// Inline copy of layout constants (source of truth: EngineDefaults.js).\n// Cannot import because this runs inside Web Workers where window 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};\n\nconst FPT = TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE;\nconst FLOATS_PER_NODE = 16; // 4 vec4s per BVH node\nconst LEAF_MARKER = - 1;\nconst BLAS_POINTER_MARKER = - 2;\n\nexport class BVHRefitter {\n\n\tconstructor() {\n\n\t\t// Reusable bounds buffer — cached across refit calls to avoid allocation per frame.\n\t\t// Resized only when nodeCount changes (i.e., new scene loaded).\n\t\tthis._bounds = null;\n\t\tthis._boundsNodeCount = 0;\n\n\t}\n\n\t/**\n\t * Update triangle positions in the BVH-reordered triangle array.\n\t * Iterates in BVH order (sequential writes, random reads) for cache efficiency.\n\t *\n\t * @param {Float32Array} triangleData - BVH-reordered triangle array (mutated in place)\n\t * @param {Float32Array} newPositions - 9 floats per triangle in ORIGINAL mesh order\n\t * @param {Uint32Array} bvhToOriginal - Map from BVH-order index to original tri index\n\t */\n\tupdateTrianglePositions( triangleData, newPositions, bvhToOriginal ) {\n\n\t\tconst triCount = bvhToOriginal.length;\n\n\t\tfor ( let bvhIdx = 0; bvhIdx < triCount; bvhIdx ++ ) {\n\n\t\t\tconst orig = bvhToOriginal[ bvhIdx ];\n\t\t\tconst dstOff = bvhIdx * FPT; // sequential writes\n\t\t\tconst srcOff = orig * 9;\n\n\t\t\tconst ax = newPositions[ srcOff ];\n\t\t\tconst ay = newPositions[ srcOff + 1 ];\n\t\t\tconst az = newPositions[ srcOff + 2 ];\n\t\t\tconst bx = newPositions[ srcOff + 3 ];\n\t\t\tconst by = newPositions[ srcOff + 4 ];\n\t\t\tconst bz = newPositions[ srcOff + 5 ];\n\t\t\tconst cx = newPositions[ srcOff + 6 ];\n\t\t\tconst cy = newPositions[ srcOff + 7 ];\n\t\t\tconst cz = newPositions[ srcOff + 8 ];\n\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET ] = ax;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 1 ] = ay;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 2 ] = az;\n\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET ] = bx;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 1 ] = by;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 2 ] = bz;\n\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET ] = cx;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 1 ] = cy;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 2 ] = cz;\n\n\t\t\t// Compute unnormalized face normal from cross product.\n\t\t\t// Skip sqrt normalization — the path tracer shader normalizes during shading.\n\t\t\tconst abx = bx - ax, aby = by - ay, abz = bz - az;\n\t\t\tconst acx = cx - ax, acy = cy - ay, acz = cz - az;\n\t\t\tconst nx = aby * acz - abz * acy;\n\t\t\tconst ny = abz * acx - abx * acz;\n\t\t\tconst nz = abx * acy - aby * acx;\n\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_A_OFFSET ] = nx;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_A_OFFSET + 1 ] = ny;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_A_OFFSET + 2 ] = nz;\n\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_B_OFFSET ] = nx;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_B_OFFSET + 1 ] = ny;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_B_OFFSET + 2 ] = nz;\n\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_C_OFFSET ] = nx;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_C_OFFSET + 1 ] = ny;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_C_OFFSET + 2 ] = nz;\n\n\t\t}\n\n\t}\n\n\t/**\n\t * Refit a BLAS sub-range within the combined BVH buffer.\n\t * Same algorithm as refit() but scoped to nodes [startNode, startNode + count).\n\t *\n\t * @param {Float32Array} bvhData - Combined BVH array (TLAS + all BLASes)\n\t * @param {Float32Array} triangleData - Global triangle data\n\t * @param {number} startNode - First node index of this BLAS in bvhData\n\t * @param {number} nodeCount - Number of nodes in this BLAS\n\t */\n\trefitRange( bvhData, triangleData, startNode, nodeCount ) {\n\n\t\t// Grow-only bounds buffer to avoid reallocation on mixed-size BLASes\n\t\tif ( nodeCount > this._boundsNodeCount ) {\n\n\t\t\tthis._bounds = new Float32Array( nodeCount * 6 );\n\t\t\tthis._boundsNodeCount = nodeCount;\n\n\t\t}\n\n\t\tconst bounds = this._bounds;\n\t\tconst endNode = startNode + nodeCount;\n\n\t\tfor ( let i = endNode - 1; i >= startNode; i -- ) {\n\n\t\t\tconst o = i * FLOATS_PER_NODE;\n\t\t\tconst b = ( i - startNode ) * 6; // bounds indexed relative to BLAS start\n\n\t\t\tif ( bvhData[ o + 3 ] === LEAF_MARKER ) {\n\n\t\t\t\tconst triOffset = bvhData[ o ];\n\t\t\t\tconst triCount = bvhData[ o + 1 ];\n\n\t\t\t\tlet minX = Infinity, minY = Infinity, minZ = Infinity;\n\t\t\t\tlet maxX = - Infinity, maxY = - Infinity, maxZ = - Infinity;\n\n\t\t\t\tfor ( let t = 0; t < triCount; t ++ ) {\n\n\t\t\t\t\tconst tOff = ( triOffset + t ) * FPT;\n\t\t\t\t\tconst ax = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET ];\n\t\t\t\t\tconst ay = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 1 ];\n\t\t\t\t\tconst az = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 2 ];\n\t\t\t\t\tconst bx = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET ];\n\t\t\t\t\tconst by = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 1 ];\n\t\t\t\t\tconst bz = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 2 ];\n\t\t\t\t\tconst cx = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET ];\n\t\t\t\t\tconst cy = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 1 ];\n\t\t\t\t\tconst cz = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 2 ];\n\n\t\t\t\t\tminX = Math.min( minX, ax, bx, cx );\n\t\t\t\t\tminY = Math.min( minY, ay, by, cy );\n\t\t\t\t\tminZ = Math.min( minZ, az, bz, cz );\n\t\t\t\t\tmaxX = Math.max( maxX, ax, bx, cx );\n\t\t\t\t\tmaxY = Math.max( maxY, ay, by, cy );\n\t\t\t\t\tmaxZ = Math.max( maxZ, az, bz, cz );\n\n\t\t\t\t}\n\n\t\t\t\tbounds[ b ] = minX;\n\t\t\t\tbounds[ b + 1 ] = minY;\n\t\t\t\tbounds[ b + 2 ] = minZ;\n\t\t\t\tbounds[ b + 3 ] = maxX;\n\t\t\t\tbounds[ b + 4 ] = maxY;\n\t\t\t\tbounds[ b + 5 ] = maxZ;\n\n\t\t\t} else {\n\n\t\t\t\t// Inner node — child indices are absolute, but bounds index relative to startNode\n\t\t\t\tconst leftIdx = bvhData[ o + 3 ];\n\t\t\t\tconst rightIdx = bvhData[ o + 7 ];\n\t\t\t\tconst lb = ( leftIdx - startNode ) * 6;\n\t\t\t\tconst rb = ( rightIdx - startNode ) * 6;\n\n\t\t\t\tconst lMinX = bounds[ lb ];\n\t\t\t\tconst lMinY = bounds[ lb + 1 ];\n\t\t\t\tconst lMinZ = bounds[ lb + 2 ];\n\t\t\t\tconst lMaxX = bounds[ lb + 3 ];\n\t\t\t\tconst lMaxY = bounds[ lb + 4 ];\n\t\t\t\tconst lMaxZ = bounds[ lb + 5 ];\n\n\t\t\t\tconst rMinX = bounds[ rb ];\n\t\t\t\tconst rMinY = bounds[ rb + 1 ];\n\t\t\t\tconst rMinZ = bounds[ rb + 2 ];\n\t\t\t\tconst rMaxX = bounds[ rb + 3 ];\n\t\t\t\tconst rMaxY = bounds[ rb + 4 ];\n\t\t\t\tconst rMaxZ = bounds[ rb + 5 ];\n\n\t\t\t\tbvhData[ o ] = lMinX;\n\t\t\t\tbvhData[ o + 1 ] = lMinY;\n\t\t\t\tbvhData[ o + 2 ] = lMinZ;\n\t\t\t\tbvhData[ o + 4 ] = lMaxX;\n\t\t\t\tbvhData[ o + 5 ] = lMaxY;\n\t\t\t\tbvhData[ o + 6 ] = lMaxZ;\n\n\t\t\t\tbvhData[ o + 8 ] = rMinX;\n\t\t\t\tbvhData[ o + 9 ] = rMinY;\n\t\t\t\tbvhData[ o + 10 ] = rMinZ;\n\t\t\t\tbvhData[ o + 12 ] = rMaxX;\n\t\t\t\tbvhData[ o + 13 ] = rMaxY;\n\t\t\t\tbvhData[ o + 14 ] = rMaxZ;\n\n\t\t\t\tbounds[ b ] = Math.min( lMinX, rMinX );\n\t\t\t\tbounds[ b + 1 ] = Math.min( lMinY, rMinY );\n\t\t\t\tbounds[ b + 2 ] = Math.min( lMinZ, rMinZ );\n\t\t\t\tbounds[ b + 3 ] = Math.max( lMaxX, rMaxX );\n\t\t\t\tbounds[ b + 4 ] = Math.max( lMaxY, rMaxY );\n\t\t\t\tbounds[ b + 5 ] = Math.max( lMaxZ, rMaxZ );\n\n\t\t\t}\n\n\t\t}\n\n\t}\n\n\t/**\n\t * Bottom-up refit of all BVH node AABBs.\n\t * Reverse pre-order iteration gives valid bottom-up order (children have higher\n\t * indices than parents in pre-order, so reversing processes children first).\n\t *\n\t * @param {Float32Array} bvhData - Flat BVH array (mutated in place)\n\t * @param {Float32Array} triangleData - Updated triangle data\n\t * @param {number} nodeCount - Total number of BVH nodes\n\t */\n\trefit( bvhData, triangleData, nodeCount ) {\n\n\t\t// Reuse bounds buffer across frames (reallocate only on scene change)\n\t\tif ( nodeCount !== this._boundsNodeCount ) {\n\n\t\t\tthis._bounds = new Float32Array( nodeCount * 6 );\n\t\t\tthis._boundsNodeCount = nodeCount;\n\n\t\t}\n\n\t\tconst bounds = this._bounds;\n\n\t\t// Reverse iteration: bottom-up in pre-order layout\n\t\tfor ( let i = nodeCount - 1; i >= 0; i -- ) {\n\n\t\t\tconst o = i * FLOATS_PER_NODE;\n\t\t\tconst b = i * 6;\n\n\t\t\tconst marker = bvhData[ o + 3 ];\n\n\t\t\tif ( marker === LEAF_MARKER ) {\n\n\t\t\t\t// Triangle leaf: compute AABB from triangles\n\t\t\t\tconst triOffset = bvhData[ o ];\n\t\t\t\tconst triCount = bvhData[ o + 1 ];\n\n\t\t\t\tlet minX = Infinity, minY = Infinity, minZ = Infinity;\n\t\t\t\tlet maxX = - Infinity, maxY = - Infinity, maxZ = - Infinity;\n\n\t\t\t\tfor ( let t = 0; t < triCount; t ++ ) {\n\n\t\t\t\t\tconst tOff = ( triOffset + t ) * FPT;\n\n\t\t\t\t\t// Position A\n\t\t\t\t\tconst ax = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET ];\n\t\t\t\t\tconst ay = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 1 ];\n\t\t\t\t\tconst az = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 2 ];\n\t\t\t\t\t// Position B\n\t\t\t\t\tconst bx = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET ];\n\t\t\t\t\tconst by = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 1 ];\n\t\t\t\t\tconst bz = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 2 ];\n\t\t\t\t\t// Position C\n\t\t\t\t\tconst cx = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET ];\n\t\t\t\t\tconst cy = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 1 ];\n\t\t\t\t\tconst cz = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 2 ];\n\n\t\t\t\t\tminX = Math.min( minX, ax, bx, cx );\n\t\t\t\t\tminY = Math.min( minY, ay, by, cy );\n\t\t\t\t\tminZ = Math.min( minZ, az, bz, cz );\n\t\t\t\t\tmaxX = Math.max( maxX, ax, bx, cx );\n\t\t\t\t\tmaxY = Math.max( maxY, ay, by, cy );\n\t\t\t\t\tmaxZ = Math.max( maxZ, az, bz, cz );\n\n\t\t\t\t}\n\n\t\t\t\tbounds[ b ] = minX;\n\t\t\t\tbounds[ b + 1 ] = minY;\n\t\t\t\tbounds[ b + 2 ] = minZ;\n\t\t\t\tbounds[ b + 3 ] = maxX;\n\t\t\t\tbounds[ b + 4 ] = maxY;\n\t\t\t\tbounds[ b + 5 ] = maxZ;\n\n\t\t\t} else if ( marker === BLAS_POINTER_MARKER ) {\n\n\t\t\t\t// BLAS-pointer leaf (TLAS): read BLAS root node's bounds (already computed)\n\t\t\t\tconst blasRoot = bvhData[ o ];\n\t\t\t\tconst br = blasRoot * 6;\n\t\t\t\tbounds[ b ] = bounds[ br ];\n\t\t\t\tbounds[ b + 1 ] = bounds[ br + 1 ];\n\t\t\t\tbounds[ b + 2 ] = bounds[ br + 2 ];\n\t\t\t\tbounds[ b + 3 ] = bounds[ br + 3 ];\n\t\t\t\tbounds[ b + 4 ] = bounds[ br + 4 ];\n\t\t\t\tbounds[ b + 5 ] = bounds[ br + 5 ];\n\n\t\t\t} else {\n\n\t\t\t\t// Inner node: union children bounds (already computed since we iterate in reverse)\n\t\t\t\tconst leftIdx = bvhData[ o + 3 ];\n\t\t\t\tconst rightIdx = bvhData[ o + 7 ];\n\t\t\t\tconst lb = leftIdx * 6;\n\t\t\t\tconst rb = rightIdx * 6;\n\n\t\t\t\tconst lMinX = bounds[ lb ];\n\t\t\t\tconst lMinY = bounds[ lb + 1 ];\n\t\t\t\tconst lMinZ = bounds[ lb + 2 ];\n\t\t\t\tconst lMaxX = bounds[ lb + 3 ];\n\t\t\t\tconst lMaxY = bounds[ lb + 4 ];\n\t\t\t\tconst lMaxZ = bounds[ lb + 5 ];\n\n\t\t\t\tconst rMinX = bounds[ rb ];\n\t\t\t\tconst rMinY = bounds[ rb + 1 ];\n\t\t\t\tconst rMinZ = bounds[ rb + 2 ];\n\t\t\t\tconst rMaxX = bounds[ rb + 3 ];\n\t\t\t\tconst rMaxY = bounds[ rb + 4 ];\n\t\t\t\tconst rMaxZ = bounds[ rb + 5 ];\n\n\t\t\t\t// Write left child AABB into bvhData\n\t\t\t\tbvhData[ o ] = lMinX;\n\t\t\t\tbvhData[ o + 1 ] = lMinY;\n\t\t\t\tbvhData[ o + 2 ] = lMinZ;\n\t\t\t\t// o+3 = leftChildIdx (preserved)\n\t\t\t\tbvhData[ o + 4 ] = lMaxX;\n\t\t\t\tbvhData[ o + 5 ] = lMaxY;\n\t\t\t\tbvhData[ o + 6 ] = lMaxZ;\n\t\t\t\t// o+7 = rightChildIdx (preserved)\n\n\t\t\t\t// Write right child AABB into bvhData\n\t\t\t\tbvhData[ o + 8 ] = rMinX;\n\t\t\t\tbvhData[ o + 9 ] = rMinY;\n\t\t\t\tbvhData[ o + 10 ] = rMinZ;\n\t\t\t\t// o+11 = 0 padding\n\t\t\t\tbvhData[ o + 12 ] = rMaxX;\n\t\t\t\tbvhData[ o + 13 ] = rMaxY;\n\t\t\t\tbvhData[ o + 14 ] = rMaxZ;\n\t\t\t\t// o+15 = 0 padding\n\n\t\t\t\t// Store this node's bounds as union of children\n\t\t\t\tbounds[ b ] = Math.min( lMinX, rMinX );\n\t\t\t\tbounds[ b + 1 ] = Math.min( lMinY, rMinY );\n\t\t\t\tbounds[ b + 2 ] = Math.min( lMinZ, rMinZ );\n\t\t\t\tbounds[ b + 3 ] = Math.max( lMaxX, rMaxX );\n\t\t\t\tbounds[ b + 4 ] = Math.max( lMaxY, rMaxY );\n\t\t\t\tbounds[ b + 5 ] = Math.max( lMaxZ, rMaxZ );\n\n\t\t\t}\n\n\t\t}\n\n\t}\n\n}\n","/**\n * BVHRefitWorker — Off-main-thread BVH refit using SharedArrayBuffer.\n *\n * Protocol:\n * 'init' → receives SharedArrayBuffers + index map (once per scene)\n * 'refit' → reads shared positions, writes shared bvh/tri data (per frame)\n */\n\nimport { BVHRefitter } from '../BVHRefitter.js';\n\nconst FLOATS_PER_NODE = 16;\nconst refitter = new BVHRefitter();\n\n// Cached shared memory views (set once on 'init', reused every frame)\nlet bvhData = null;\nlet triData = null;\nlet posData = null;\nlet bvhToOriginal = null;\nlet nodeCount = 0;\n\nself.onmessage = function ( e ) {\n\n\tconst { type } = e.data;\n\n\tif ( type === 'init' ) {\n\n\t\tbvhData = new Float32Array( e.data.sharedBvhBuf );\n\t\ttriData = new Float32Array( e.data.sharedTriBuf );\n\t\tposData = new Float32Array( e.data.sharedPosBuf );\n\t\tbvhToOriginal = e.data.bvhToOriginal; // transferred Uint32Array\n\t\tnodeCount = bvhData.length / FLOATS_PER_NODE;\n\t\treturn;\n\n\t}\n\n\tif ( type === 'refit' ) {\n\n\t\ttry {\n\n\t\t\tconst startTime = performance.now();\n\n\t\t\trefitter.updateTrianglePositions( triData, posData, bvhToOriginal );\n\t\t\trefitter.refit( bvhData, triData, nodeCount );\n\n\t\t\tself.postMessage( {\n\t\t\t\ttype: 'refitComplete',\n\t\t\t\trefitTimeMs: performance.now() - startTime\n\t\t\t} );\n\n\t\t} catch ( error ) {\n\n\t\t\tconsole.error( '[BVHRefitWorker] Refit error:', error );\n\t\t\tself.postMessage( { type: 'error', error: error.message } );\n\n\t\t}\n\n\t}\n\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BVHRefitWorker-WezTgpI5.js","names":["FLOATS_PER_NODE"],"sources":["../../src/Processor/BVHRefitter.js","../../src/Processor/Workers/BVHRefitWorker.js"],"sourcesContent":["/**\n * BVHRefitter — Fast O(N) bottom-up BVH AABB refit for animated geometry.\n *\n * When mesh topology stays the same but vertex positions change (skeletal animation,\n * morph targets), this avoids the full O(N log N) SAH rebuild by recomputing only\n * the bounding boxes in the existing tree structure.\n *\n * Designed to run in both main thread and Web Worker contexts.\n */\n\n// Inline copy of layout constants (source of truth: EngineDefaults.js).\n// Cannot import because this runs inside Web Workers where window 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};\n\nconst FPT = TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE;\nconst FLOATS_PER_NODE = 16; // 4 vec4s per BVH node\nconst LEAF_MARKER = - 1;\nconst BLAS_POINTER_MARKER = - 2;\n\nexport class BVHRefitter {\n\n\tconstructor() {\n\n\t\t// Reusable bounds buffer — cached across refit calls to avoid allocation per frame.\n\t\t// Resized only when nodeCount changes (i.e., new scene loaded).\n\t\tthis._bounds = null;\n\t\tthis._boundsNodeCount = 0;\n\n\t}\n\n\t/**\n\t * Update triangle positions in the BVH-reordered triangle array.\n\t * Iterates in BVH order (sequential writes, random reads) for cache efficiency.\n\t *\n\t * @param {Float32Array} triangleData - BVH-reordered triangle array (mutated in place)\n\t * @param {Float32Array} newPositions - 9 floats per triangle in ORIGINAL mesh order\n\t * @param {Uint32Array} bvhToOriginal - Map from BVH-order index to original tri index\n\t */\n\tupdateTrianglePositions( triangleData, newPositions, bvhToOriginal ) {\n\n\t\tconst triCount = bvhToOriginal.length;\n\n\t\tfor ( let bvhIdx = 0; bvhIdx < triCount; bvhIdx ++ ) {\n\n\t\t\tconst orig = bvhToOriginal[ bvhIdx ];\n\t\t\tconst dstOff = bvhIdx * FPT; // sequential writes\n\t\t\tconst srcOff = orig * 9;\n\n\t\t\tconst ax = newPositions[ srcOff ];\n\t\t\tconst ay = newPositions[ srcOff + 1 ];\n\t\t\tconst az = newPositions[ srcOff + 2 ];\n\t\t\tconst bx = newPositions[ srcOff + 3 ];\n\t\t\tconst by = newPositions[ srcOff + 4 ];\n\t\t\tconst bz = newPositions[ srcOff + 5 ];\n\t\t\tconst cx = newPositions[ srcOff + 6 ];\n\t\t\tconst cy = newPositions[ srcOff + 7 ];\n\t\t\tconst cz = newPositions[ srcOff + 8 ];\n\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET ] = ax;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 1 ] = ay;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 2 ] = az;\n\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET ] = bx;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 1 ] = by;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 2 ] = bz;\n\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET ] = cx;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 1 ] = cy;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 2 ] = cz;\n\n\t\t\t// Compute unnormalized face normal from cross product.\n\t\t\t// Skip sqrt normalization — the path tracer shader normalizes during shading.\n\t\t\tconst abx = bx - ax, aby = by - ay, abz = bz - az;\n\t\t\tconst acx = cx - ax, acy = cy - ay, acz = cz - az;\n\t\t\tconst nx = aby * acz - abz * acy;\n\t\t\tconst ny = abz * acx - abx * acz;\n\t\t\tconst nz = abx * acy - aby * acx;\n\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_A_OFFSET ] = nx;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_A_OFFSET + 1 ] = ny;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_A_OFFSET + 2 ] = nz;\n\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_B_OFFSET ] = nx;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_B_OFFSET + 1 ] = ny;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_B_OFFSET + 2 ] = nz;\n\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_C_OFFSET ] = nx;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_C_OFFSET + 1 ] = ny;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_C_OFFSET + 2 ] = nz;\n\n\t\t}\n\n\t}\n\n\t/**\n\t * Refit a BLAS sub-range within the combined BVH buffer.\n\t * Same algorithm as refit() but scoped to nodes [startNode, startNode + count).\n\t *\n\t * @param {Float32Array} bvhData - Combined BVH array (TLAS + all BLASes)\n\t * @param {Float32Array} triangleData - Global triangle data\n\t * @param {number} startNode - First node index of this BLAS in bvhData\n\t * @param {number} nodeCount - Number of nodes in this BLAS\n\t */\n\trefitRange( bvhData, triangleData, startNode, nodeCount ) {\n\n\t\t// Grow-only bounds buffer to avoid reallocation on mixed-size BLASes\n\t\tif ( nodeCount > this._boundsNodeCount ) {\n\n\t\t\tthis._bounds = new Float32Array( nodeCount * 6 );\n\t\t\tthis._boundsNodeCount = nodeCount;\n\n\t\t}\n\n\t\tconst bounds = this._bounds;\n\t\tconst endNode = startNode + nodeCount;\n\n\t\tfor ( let i = endNode - 1; i >= startNode; i -- ) {\n\n\t\t\tconst o = i * FLOATS_PER_NODE;\n\t\t\tconst b = ( i - startNode ) * 6; // bounds indexed relative to BLAS start\n\n\t\t\tif ( bvhData[ o + 3 ] === LEAF_MARKER ) {\n\n\t\t\t\tconst triOffset = bvhData[ o ];\n\t\t\t\tconst triCount = bvhData[ o + 1 ];\n\n\t\t\t\tlet minX = Infinity, minY = Infinity, minZ = Infinity;\n\t\t\t\tlet maxX = - Infinity, maxY = - Infinity, maxZ = - Infinity;\n\n\t\t\t\tfor ( let t = 0; t < triCount; t ++ ) {\n\n\t\t\t\t\tconst tOff = ( triOffset + t ) * FPT;\n\t\t\t\t\tconst ax = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET ];\n\t\t\t\t\tconst ay = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 1 ];\n\t\t\t\t\tconst az = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 2 ];\n\t\t\t\t\tconst bx = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET ];\n\t\t\t\t\tconst by = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 1 ];\n\t\t\t\t\tconst bz = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 2 ];\n\t\t\t\t\tconst cx = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET ];\n\t\t\t\t\tconst cy = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 1 ];\n\t\t\t\t\tconst cz = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 2 ];\n\n\t\t\t\t\tminX = Math.min( minX, ax, bx, cx );\n\t\t\t\t\tminY = Math.min( minY, ay, by, cy );\n\t\t\t\t\tminZ = Math.min( minZ, az, bz, cz );\n\t\t\t\t\tmaxX = Math.max( maxX, ax, bx, cx );\n\t\t\t\t\tmaxY = Math.max( maxY, ay, by, cy );\n\t\t\t\t\tmaxZ = Math.max( maxZ, az, bz, cz );\n\n\t\t\t\t}\n\n\t\t\t\tbounds[ b ] = minX;\n\t\t\t\tbounds[ b + 1 ] = minY;\n\t\t\t\tbounds[ b + 2 ] = minZ;\n\t\t\t\tbounds[ b + 3 ] = maxX;\n\t\t\t\tbounds[ b + 4 ] = maxY;\n\t\t\t\tbounds[ b + 5 ] = maxZ;\n\n\t\t\t} else {\n\n\t\t\t\t// Inner node — child indices are absolute, but bounds index relative to startNode\n\t\t\t\tconst leftIdx = bvhData[ o + 3 ];\n\t\t\t\tconst rightIdx = bvhData[ o + 7 ];\n\t\t\t\tconst lb = ( leftIdx - startNode ) * 6;\n\t\t\t\tconst rb = ( rightIdx - startNode ) * 6;\n\n\t\t\t\tconst lMinX = bounds[ lb ];\n\t\t\t\tconst lMinY = bounds[ lb + 1 ];\n\t\t\t\tconst lMinZ = bounds[ lb + 2 ];\n\t\t\t\tconst lMaxX = bounds[ lb + 3 ];\n\t\t\t\tconst lMaxY = bounds[ lb + 4 ];\n\t\t\t\tconst lMaxZ = bounds[ lb + 5 ];\n\n\t\t\t\tconst rMinX = bounds[ rb ];\n\t\t\t\tconst rMinY = bounds[ rb + 1 ];\n\t\t\t\tconst rMinZ = bounds[ rb + 2 ];\n\t\t\t\tconst rMaxX = bounds[ rb + 3 ];\n\t\t\t\tconst rMaxY = bounds[ rb + 4 ];\n\t\t\t\tconst rMaxZ = bounds[ rb + 5 ];\n\n\t\t\t\tbvhData[ o ] = lMinX;\n\t\t\t\tbvhData[ o + 1 ] = lMinY;\n\t\t\t\tbvhData[ o + 2 ] = lMinZ;\n\t\t\t\tbvhData[ o + 4 ] = lMaxX;\n\t\t\t\tbvhData[ o + 5 ] = lMaxY;\n\t\t\t\tbvhData[ o + 6 ] = lMaxZ;\n\n\t\t\t\tbvhData[ o + 8 ] = rMinX;\n\t\t\t\tbvhData[ o + 9 ] = rMinY;\n\t\t\t\tbvhData[ o + 10 ] = rMinZ;\n\t\t\t\tbvhData[ o + 12 ] = rMaxX;\n\t\t\t\tbvhData[ o + 13 ] = rMaxY;\n\t\t\t\tbvhData[ o + 14 ] = rMaxZ;\n\n\t\t\t\tbounds[ b ] = Math.min( lMinX, rMinX );\n\t\t\t\tbounds[ b + 1 ] = Math.min( lMinY, rMinY );\n\t\t\t\tbounds[ b + 2 ] = Math.min( lMinZ, rMinZ );\n\t\t\t\tbounds[ b + 3 ] = Math.max( lMaxX, rMaxX );\n\t\t\t\tbounds[ b + 4 ] = Math.max( lMaxY, rMaxY );\n\t\t\t\tbounds[ b + 5 ] = Math.max( lMaxZ, rMaxZ );\n\n\t\t\t}\n\n\t\t}\n\n\t}\n\n\t/**\n\t * Bottom-up refit of all BVH node AABBs.\n\t * Reverse pre-order iteration gives valid bottom-up order (children have higher\n\t * indices than parents in pre-order, so reversing processes children first).\n\t *\n\t * @param {Float32Array} bvhData - Flat BVH array (mutated in place)\n\t * @param {Float32Array} triangleData - Updated triangle data\n\t * @param {number} nodeCount - Total number of BVH nodes\n\t */\n\trefit( bvhData, triangleData, nodeCount ) {\n\n\t\t// Reuse bounds buffer across frames (reallocate only on scene change)\n\t\tif ( nodeCount !== this._boundsNodeCount ) {\n\n\t\t\tthis._bounds = new Float32Array( nodeCount * 6 );\n\t\t\tthis._boundsNodeCount = nodeCount;\n\n\t\t}\n\n\t\tconst bounds = this._bounds;\n\n\t\t// Reverse iteration: bottom-up in pre-order layout\n\t\tfor ( let i = nodeCount - 1; i >= 0; i -- ) {\n\n\t\t\tconst o = i * FLOATS_PER_NODE;\n\t\t\tconst b = i * 6;\n\n\t\t\tconst marker = bvhData[ o + 3 ];\n\n\t\t\tif ( marker === LEAF_MARKER ) {\n\n\t\t\t\t// Triangle leaf: compute AABB from triangles\n\t\t\t\tconst triOffset = bvhData[ o ];\n\t\t\t\tconst triCount = bvhData[ o + 1 ];\n\n\t\t\t\tlet minX = Infinity, minY = Infinity, minZ = Infinity;\n\t\t\t\tlet maxX = - Infinity, maxY = - Infinity, maxZ = - Infinity;\n\n\t\t\t\tfor ( let t = 0; t < triCount; t ++ ) {\n\n\t\t\t\t\tconst tOff = ( triOffset + t ) * FPT;\n\n\t\t\t\t\t// Position A\n\t\t\t\t\tconst ax = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET ];\n\t\t\t\t\tconst ay = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 1 ];\n\t\t\t\t\tconst az = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 2 ];\n\t\t\t\t\t// Position B\n\t\t\t\t\tconst bx = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET ];\n\t\t\t\t\tconst by = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 1 ];\n\t\t\t\t\tconst bz = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 2 ];\n\t\t\t\t\t// Position C\n\t\t\t\t\tconst cx = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET ];\n\t\t\t\t\tconst cy = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 1 ];\n\t\t\t\t\tconst cz = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 2 ];\n\n\t\t\t\t\tminX = Math.min( minX, ax, bx, cx );\n\t\t\t\t\tminY = Math.min( minY, ay, by, cy );\n\t\t\t\t\tminZ = Math.min( minZ, az, bz, cz );\n\t\t\t\t\tmaxX = Math.max( maxX, ax, bx, cx );\n\t\t\t\t\tmaxY = Math.max( maxY, ay, by, cy );\n\t\t\t\t\tmaxZ = Math.max( maxZ, az, bz, cz );\n\n\t\t\t\t}\n\n\t\t\t\tbounds[ b ] = minX;\n\t\t\t\tbounds[ b + 1 ] = minY;\n\t\t\t\tbounds[ b + 2 ] = minZ;\n\t\t\t\tbounds[ b + 3 ] = maxX;\n\t\t\t\tbounds[ b + 4 ] = maxY;\n\t\t\t\tbounds[ b + 5 ] = maxZ;\n\n\t\t\t} else if ( marker === BLAS_POINTER_MARKER ) {\n\n\t\t\t\t// BLAS-pointer leaf (TLAS): read BLAS root node's bounds (already computed)\n\t\t\t\tconst blasRoot = bvhData[ o ];\n\t\t\t\tconst br = blasRoot * 6;\n\t\t\t\tbounds[ b ] = bounds[ br ];\n\t\t\t\tbounds[ b + 1 ] = bounds[ br + 1 ];\n\t\t\t\tbounds[ b + 2 ] = bounds[ br + 2 ];\n\t\t\t\tbounds[ b + 3 ] = bounds[ br + 3 ];\n\t\t\t\tbounds[ b + 4 ] = bounds[ br + 4 ];\n\t\t\t\tbounds[ b + 5 ] = bounds[ br + 5 ];\n\n\t\t\t} else {\n\n\t\t\t\t// Inner node: union children bounds (already computed since we iterate in reverse)\n\t\t\t\tconst leftIdx = bvhData[ o + 3 ];\n\t\t\t\tconst rightIdx = bvhData[ o + 7 ];\n\t\t\t\tconst lb = leftIdx * 6;\n\t\t\t\tconst rb = rightIdx * 6;\n\n\t\t\t\tconst lMinX = bounds[ lb ];\n\t\t\t\tconst lMinY = bounds[ lb + 1 ];\n\t\t\t\tconst lMinZ = bounds[ lb + 2 ];\n\t\t\t\tconst lMaxX = bounds[ lb + 3 ];\n\t\t\t\tconst lMaxY = bounds[ lb + 4 ];\n\t\t\t\tconst lMaxZ = bounds[ lb + 5 ];\n\n\t\t\t\tconst rMinX = bounds[ rb ];\n\t\t\t\tconst rMinY = bounds[ rb + 1 ];\n\t\t\t\tconst rMinZ = bounds[ rb + 2 ];\n\t\t\t\tconst rMaxX = bounds[ rb + 3 ];\n\t\t\t\tconst rMaxY = bounds[ rb + 4 ];\n\t\t\t\tconst rMaxZ = bounds[ rb + 5 ];\n\n\t\t\t\t// Write left child AABB into bvhData\n\t\t\t\tbvhData[ o ] = lMinX;\n\t\t\t\tbvhData[ o + 1 ] = lMinY;\n\t\t\t\tbvhData[ o + 2 ] = lMinZ;\n\t\t\t\t// o+3 = leftChildIdx (preserved)\n\t\t\t\tbvhData[ o + 4 ] = lMaxX;\n\t\t\t\tbvhData[ o + 5 ] = lMaxY;\n\t\t\t\tbvhData[ o + 6 ] = lMaxZ;\n\t\t\t\t// o+7 = rightChildIdx (preserved)\n\n\t\t\t\t// Write right child AABB into bvhData\n\t\t\t\tbvhData[ o + 8 ] = rMinX;\n\t\t\t\tbvhData[ o + 9 ] = rMinY;\n\t\t\t\tbvhData[ o + 10 ] = rMinZ;\n\t\t\t\t// o+11 = 0 padding\n\t\t\t\tbvhData[ o + 12 ] = rMaxX;\n\t\t\t\tbvhData[ o + 13 ] = rMaxY;\n\t\t\t\tbvhData[ o + 14 ] = rMaxZ;\n\t\t\t\t// o+15 = 0 padding\n\n\t\t\t\t// Store this node's bounds as union of children\n\t\t\t\tbounds[ b ] = Math.min( lMinX, rMinX );\n\t\t\t\tbounds[ b + 1 ] = Math.min( lMinY, rMinY );\n\t\t\t\tbounds[ b + 2 ] = Math.min( lMinZ, rMinZ );\n\t\t\t\tbounds[ b + 3 ] = Math.max( lMaxX, rMaxX );\n\t\t\t\tbounds[ b + 4 ] = Math.max( lMaxY, rMaxY );\n\t\t\t\tbounds[ b + 5 ] = Math.max( lMaxZ, rMaxZ );\n\n\t\t\t}\n\n\t\t}\n\n\t}\n\n}\n","/**\n * BVHRefitWorker — Off-main-thread BVH refit using SharedArrayBuffer.\n *\n * Protocol:\n * 'init' → receives SharedArrayBuffers + index map (once per scene)\n * 'refit' → reads shared positions, writes shared bvh/tri data (per frame)\n */\n\nimport { BVHRefitter } from '../BVHRefitter.js';\n\nconst FLOATS_PER_NODE = 16;\nconst refitter = new BVHRefitter();\n\n// Cached shared memory views (set once on 'init', reused every frame)\nlet bvhData = null;\nlet triData = null;\nlet posData = null;\nlet bvhToOriginal = null;\nlet nodeCount = 0;\n\nself.onmessage = function ( e ) {\n\n\tconst { type } = e.data;\n\n\tif ( type === 'init' ) {\n\n\t\tbvhData = new Float32Array( e.data.sharedBvhBuf );\n\t\ttriData = new Float32Array( e.data.sharedTriBuf );\n\t\tposData = new Float32Array( e.data.sharedPosBuf );\n\t\tbvhToOriginal = e.data.bvhToOriginal; // transferred Uint32Array\n\t\tnodeCount = bvhData.length / FLOATS_PER_NODE;\n\t\treturn;\n\n\t}\n\n\tif ( type === 'refit' ) {\n\n\t\ttry {\n\n\t\t\tconst startTime = performance.now();\n\n\t\t\trefitter.updateTrianglePositions( triData, posData, bvhToOriginal );\n\t\t\trefitter.refit( bvhData, triData, nodeCount );\n\n\t\t\tself.postMessage( {\n\t\t\t\ttype: 'refitComplete',\n\t\t\t\trefitTimeMs: performance.now() - startTime\n\t\t\t} );\n\n\t\t} catch ( error ) {\n\n\t\t\tconsole.error( '[BVHRefitWorker] Refit error:', error );\n\t\t\tself.postMessage( { type: 'error', error: error.message } );\n\n\t\t}\n\n\t}\n\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BVHSubtreeWorker-B98NTgvc.js","names":["BVHWorker"],"sources":["../../src/Processor/TreeletOptimizer.js","../../src/Processor/ReinsertionOptimizer.js","../../src/utils/Logger.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 * Leveled, namespaced console logger shared by the engine, the app and the workers.\n *\n * The engine prints one summary line per startup phase at `info`; every per-detail\n * line sits at `debug` so a normal load stays readable and nothing is lost.\n *\n * Active level, first match wins:\n * 1. globalThis.__RAYZEE_LOG_LEVEL__ — set by Logger.setLevel(), forwarded to workers\n * 2. localStorage.rayzeeLogLevel — survives reloads (main thread only)\n * 3. 'info'\n *\n * From the devtools console:\n * rayzee.log.setLevel( 'debug' ) // everything, including per-mesh/per-kernel detail\n * rayzee.log.setLevel( 'warn' ) // problems only\n * rayzee.log.only( 'bvh', 'gpu' ) // debug, limited to these namespaces\n * rayzee.log.only() // clear the namespace filter\n */\n\nexport const LOG_LEVELS = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };\n\nconst DEFAULT_LEVEL = 'info';\nconst STORAGE_KEY = 'rayzeeLogLevel';\nconst NS_STORAGE_KEY = 'rayzeeLogNamespaces';\n\nconst PREFIX_STYLE = 'color:#7c8494';\nconst RESET_STYLE = 'color:inherit';\n\n// Cached so a hot log site is a numeric compare, not a storage read.\nlet activeLevel = null;\nlet namespaceFilter = null;\n\nfunction readStorage( key ) {\n\n\ttry {\n\n\t\treturn globalThis.localStorage?.getItem( key ) ?? null;\n\n\t} catch {\n\n\t\treturn null; // private mode, or a worker without storage access\n\n\t}\n\n}\n\nfunction writeStorage( key, value ) {\n\n\ttry {\n\n\t\tif ( value === null ) globalThis.localStorage?.removeItem( key );\n\t\telse globalThis.localStorage?.setItem( key, value );\n\n\t} catch {\n\n\t\t// non-fatal: the level still applies for this session\n\n\t}\n\n}\n\nfunction resolveLevel() {\n\n\tif ( activeLevel !== null ) return activeLevel;\n\n\tconst fromGlobal = globalThis.__RAYZEE_LOG_LEVEL__;\n\tif ( fromGlobal !== undefined && fromGlobal in LOG_LEVELS ) {\n\n\t\tactiveLevel = LOG_LEVELS[ fromGlobal ];\n\t\treturn activeLevel;\n\n\t}\n\n\tconst fromStorage = readStorage( STORAGE_KEY );\n\tactiveLevel = LOG_LEVELS[ fromStorage ] ?? LOG_LEVELS[ DEFAULT_LEVEL ];\n\treturn activeLevel;\n\n}\n\nfunction resolveNamespaceFilter() {\n\n\tif ( namespaceFilter !== null ) return namespaceFilter;\n\n\tconst raw = globalThis.__RAYZEE_LOG_NS__ ?? readStorage( NS_STORAGE_KEY );\n\tnamespaceFilter = raw ? String( raw ).split( ',' ).map( s => s.trim() ).filter( Boolean ) : [];\n\treturn namespaceFilter;\n\n}\n\n// The namespace filter only narrows `debug`; warnings and errors are never hidden by it.\nfunction namespaceAllowed( ns, level ) {\n\n\tif ( level < LOG_LEVELS.debug ) return true;\n\tconst filter = resolveNamespaceFilter();\n\treturn filter.length === 0 || filter.some( f => ns === f || ns.startsWith( `${f}:` ) );\n\n}\n\nfunction emit( level, ns, args ) {\n\n\tif ( level > resolveLevel() || ! namespaceAllowed( ns, level ) ) return;\n\n\tconst sink = level === LOG_LEVELS.error ? console.error\n\t\t: level === LOG_LEVELS.warn ? console.warn\n\t\t\t: console.log;\n\n\tconst prefix = `[${ns}]`;\n\n\t// Style only the prefix, and only when the first argument is a string we can splice into.\n\tif ( typeof args[ 0 ] === 'string' ) {\n\n\t\tsink( `%c${prefix}%c ${args[ 0 ]}`, PREFIX_STYLE, RESET_STYLE, ...args.slice( 1 ) );\n\n\t} else {\n\n\t\tsink( `%c${prefix}`, PREFIX_STYLE, ...args );\n\n\t}\n\n}\n\n/**\n * Formatters, so summary lines read consistently across the engine.\n */\nexport const fmt = {\n\n\t/** 18016 → \"18,016\" */\n\tn: value => Number( value ).toLocaleString( 'en-US' ),\n\n\t/** 481.27 → \"481 ms\"; sub-10ms keeps one decimal */\n\tms: value => ( value < 10 ? `${Number( value ).toFixed( 1 )} ms` : `${Math.round( value )} ms` ),\n\n\t/** bytes → \"592 MB\"; sub-10MB keeps one decimal */\n\tmb: bytes => {\n\n\t\tconst mb = bytes / ( 1024 * 1024 );\n\t\treturn mb < 10 ? `${mb.toFixed( 1 )} MB` : `${Math.round( mb )} MB`;\n\n\t},\n\n\t/** 512, 512 → \"512×512\" */\n\tpx: ( width, height ) => `${width}×${height}`,\n\n\t/** 1, 'map' → \"1 map\"; 2, 'mesh', 'meshes' → \"2 meshes\" */\n\tcount: ( value, singular, plural = `${singular}s` ) =>\n\t\t`${fmt.n( value )} ${Number( value ) === 1 ? singular : plural}`,\n\n\t/** Joins the truthy parts of a summary line, so optional segments can be inlined as `cond && text`. */\n\tlist: parts => parts.filter( Boolean ).join( ' · ' ),\n\n};\n\n/**\n * Global logger controls. Also reachable as `window.rayzee.log`.\n */\nexport const Logger = {\n\n\tlevels: LOG_LEVELS,\n\n\t/** @param {'silent'|'error'|'warn'|'info'|'debug'} name */\n\tsetLevel( name ) {\n\n\t\tif ( ! ( name in LOG_LEVELS ) ) {\n\n\t\t\tconsole.warn( `[log] unknown level '${name}' — expected one of ${Object.keys( LOG_LEVELS ).join( ', ' )}` );\n\t\t\treturn this.getLevel();\n\n\t\t}\n\n\t\tglobalThis.__RAYZEE_LOG_LEVEL__ = name;\n\t\tactiveLevel = LOG_LEVELS[ name ];\n\t\twriteStorage( STORAGE_KEY, name === DEFAULT_LEVEL ? null : name );\n\t\treturn name;\n\n\t},\n\n\tgetLevel() {\n\n\t\tconst level = resolveLevel();\n\t\treturn Object.keys( LOG_LEVELS ).find( k => LOG_LEVELS[ k ] === level );\n\n\t},\n\n\t/** True when a level would actually print — use to skip expensive message construction. */\n\tisEnabled( name ) {\n\n\t\treturn ( LOG_LEVELS[ name ] ?? LOG_LEVELS.debug ) <= resolveLevel();\n\n\t},\n\n\t/** Restrict `debug` to the given namespaces. No arguments clears the filter. */\n\tonly( ...namespaces ) {\n\n\t\tconst list = namespaces.flat().filter( Boolean );\n\t\tnamespaceFilter = list;\n\t\tglobalThis.__RAYZEE_LOG_NS__ = list.join( ',' );\n\t\twriteStorage( NS_STORAGE_KEY, list.length ? list.join( ',' ) : null );\n\t\tif ( list.length ) this.setLevel( 'debug' );\n\t\treturn list;\n\n\t},\n\n\t/** Drops the cached level so the next log re-reads globals/storage. */\n\trefresh() {\n\n\t\tactiveLevel = null;\n\t\tnamespaceFilter = null;\n\t\treturn this.getLevel();\n\n\t},\n\n};\n\n/**\n * @param {string} namespace - short lowercase channel, e.g. 'bvh', 'gpu', 'scene'\n */\nexport function createLogger( namespace ) {\n\n\treturn {\n\n\t\tnamespace,\n\n\t\terror: ( ...args ) => emit( LOG_LEVELS.error, namespace, args ),\n\t\twarn: ( ...args ) => emit( LOG_LEVELS.warn, namespace, args ),\n\t\tinfo: ( ...args ) => emit( LOG_LEVELS.info, namespace, args ),\n\t\tdebug: ( ...args ) => emit( LOG_LEVELS.debug, namespace, args ),\n\n\t\tisEnabled: name => Logger.isEnabled( name ),\n\n\t\t/**\n\t\t * One-line `info` summary with the detail lines folded into a collapsed group,\n\t\t * so a normal load shows a single row per phase but the breakdown is one click away.\n\t\t * @param {string} headline\n\t\t * @param {string[]} [details]\n\t\t */\n\t\tsummary( headline, details = [] ) {\n\n\t\t\tconst lines = details.filter( Boolean );\n\n\t\t\tif ( lines.length === 0 || ! console.groupCollapsed ) {\n\n\t\t\t\temit( LOG_LEVELS.info, namespace, [ headline ] );\n\t\t\t\tlines.forEach( line => emit( LOG_LEVELS.debug, namespace, [ line ] ) );\n\t\t\t\treturn;\n\n\t\t\t}\n\n\t\t\tif ( LOG_LEVELS.info > resolveLevel() ) return;\n\n\t\t\t// Expanded at debug, folded otherwise.\n\t\t\tconst open = resolveLevel() >= LOG_LEVELS.debug && console.group;\n\t\t\t( open ? console.group : console.groupCollapsed )(\n\t\t\t\t`%c[${namespace}]%c ${headline}`, PREFIX_STYLE, RESET_STYLE\n\t\t\t);\n\t\t\tlines.forEach( line => console.log( line ) );\n\t\t\tconsole.groupEnd();\n\n\t\t},\n\n\t};\n\n}\n\n/** Warn once per key — for per-material/per-mesh conditions that would otherwise repeat. */\nconst seenOnce = new Set();\n\nexport function warnOnce( logger, key, ...args ) {\n\n\tif ( seenOnce.has( key ) ) return;\n\tseenOnce.add( key );\n\tlogger.warn( ...args );\n\n}\n\n/** Level name to hand to a worker so its `debug` lines follow the main thread. */\nexport function workerLogLevel() {\n\n\treturn Logger.getLevel();\n\n}\n\n/** Applies a level inside a worker, where globals and storage are separate. */\nexport function applyWorkerLogLevel( name ) {\n\n\tif ( name && name in LOG_LEVELS ) {\n\n\t\tglobalThis.__RAYZEE_LOG_LEVEL__ = name;\n\t\tactiveLevel = LOG_LEVELS[ name ];\n\n\t}\n\n}\n","import { TreeletOptimizer } from './TreeletOptimizer.js';\nimport { ReinsertionOptimizer } from './ReinsertionOptimizer.js';\nimport BVHWorker from './Workers/BVHWorker.js?worker&inline';\n// Logger is worker-safe (globalThis only, storage access guarded), unlike Constants.js below.\nimport { createLogger, fmt } from '../utils/Logger.js';\n\nconst log = createLogger( 'bvh' );\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\tlog.debug( `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 BVHWorker() );\n\n\t\t\t\t} catch ( error ) {\n\n\t\t\t\t\tlog.warn( 'worker creation failed, falling back to synchronous build:', error );\n\t\t\t\t\tresolve( this._buildSyncAndFlatten( triangles, depth, progressCallback ) );\n\n\t\t\t\t}\n\n\t\t\t} );\n\n\t\t} else {\n\n\t\t\treturn new Promise( ( resolve ) => {\n\n\t\t\t\tresolve( this._buildSyncAndFlatten( triangles, depth, progressCallback ) );\n\n\t\t\t} );\n\n\t\t}\n\n\t}\n\n\t/**\n\t * Synchronous build + flatten helper for non-worker path.\n\t * @private\n\t */\n\t_buildSyncAndFlatten( triangles, depth, progressCallback ) {\n\n\t\tconst root = this.buildSync( triangles, depth, progressCallback );\n\t\tconst bvhData = this.flattenBVH( root );\n\t\t// Return reordered triangles if available (avoids 362MB copy)\n\t\tconst reorderedTriangles = this.reorderedTriangleData || null;\n\t\tconst originalToBvh = this.originalToBvhMap || null;\n\t\treturn { bvhData, bvhRoot: true, reorderedTriangles, originalToBvh };\n\n\t}\n\n\tbuildSync( triangles, depth = 30, progressCallback = null, reorderTarget = null ) {\n\n\t\tconst buildStartTime = performance.now();\n\n\t\t// Reset state\n\t\tthis.totalNodes = 0;\n\t\tthis.processedTriangles = 0;\n\t\tthis.triangles = triangles;\n\t\tthis.totalTriangles = triangles.byteLength / ( FPT * 4 );\n\t\tthis.lastProgressUpdate = performance.now();\n\n\t\tthis.splitStats = {\n\t\t\tsahSplits: 0,\n\t\t\tobjectMedianSplits: 0,\n\t\t\tspatialMedianSplits: 0,\n\t\t\tfailedSplits: 0,\n\t\t\tavgBinsUsed: 0,\n\t\t\ttotalSplitAttempts: 0,\n\t\t\tmortonSortTime: 0,\n\t\t\ttotalBuildTime: 0,\n\t\t\ttreeletOptimizationTime: 0,\n\t\t\ttreeletsProcessed: 0,\n\t\t\ttreeletsImproved: 0,\n\t\t\taverageSAHImprovement: 0,\n\t\t\treinsertionOptimizationTime: 0,\n\t\t\treinsertionsApplied: 0,\n\t\t\treinsertionIterations: 0,\n\t\t\tsaOrderTime: 0,\n\t\t\t// Granular phase timings\n\t\t\tinitTime: 0,\n\t\t\tsahBuildTime: 0,\n\t\t\treorderTime: 0\n\t\t};\n\n\t\tconst n = this.totalTriangles;\n\n\t\t// Phase 1: Allocate and initialize per-triangle arrays\n\t\tconst initStart = performance.now();\n\n\t\tthis.centroids = new Float32Array( n * 3 );\n\t\tthis.bMin = new Float32Array( n * 3 );\n\t\tthis.bMax = new Float32Array( n * 3 );\n\t\tthis.indices = new Uint32Array( n );\n\t\tthis.mortonCodes = new Uint32Array( n );\n\n\t\tthis.initializeTriangleArrays();\n\n\t\tthis.splitStats.initTime = performance.now() - initStart;\n\n\t\t// Phase 2: Morton code spatial clustering\n\t\tthis.sortTrianglesByMortonCode();\n\n\t\t// Phase 3: Recursive SAH build\n\t\tconst sahStart = performance.now();\n\t\tconst root = this.buildNodeRecursive( 0, n, depth, progressCallback );\n\t\tthis.splitStats.sahBuildTime = performance.now() - sahStart;\n\n\t\t// Phase 4: Treelet optimization\n\t\tif ( this.enableTreeletOptimization && this.totalTriangles > 1000 ) {\n\n\t\t\tconst isLargeScene = this.totalTriangles > this.treeletComplexityThreshold;\n\t\t\tconst adaptiveTreeletSize = isLargeScene ? 3 : this.treeletSize;\n\t\t\tconst adaptiveMaxTreelets = isLargeScene ? 10 : this.maxTreeletsPerScene;\n\n\t\t\tconst optimizer = new TreeletOptimizer( this.traversalCost, this.intersectionCost );\n\t\t\toptimizer.setTreeletSize( adaptiveTreeletSize );\n\t\t\toptimizer.setMinImprovement( this.treeletMinImprovement );\n\t\t\toptimizer.setMaxTreelets( adaptiveMaxTreelets );\n\n\t\t\tconst optimizationStartTime = performance.now();\n\n\t\t\tfor ( let pass = 0; pass < this.treeletOptimizationPasses; pass ++ ) {\n\n\t\t\t\tconst passCallback = progressCallback ? ( status ) => {\n\n\t\t\t\t\tprogressCallback( `Treelet optimization pass ${pass + 1}/${this.treeletOptimizationPasses}: ${status}` );\n\n\t\t\t\t} : null;\n\n\t\t\t\ttry {\n\n\t\t\t\t\toptimizer.optimizeBVH( root, passCallback );\n\n\t\t\t\t} catch ( error ) {\n\n\t\t\t\t\tlog.error( `treelet optimizer failed 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\tlog.error( 'reinsertion optimizer failed:', 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\tlog.debug(\n\t\t\t`${fmt.n( n )} tris → ${fmt.n( this.totalNodes )} nodes in ${fmt.ms( total )}` +\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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"}
|