rayzee 5.3.4 → 5.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rayzee",
3
- "version": "5.3.4",
3
+ "version": "5.3.6",
4
4
  "type": "module",
5
5
  "description": "Real-time WebGPU path tracing engine built on Three.js",
6
6
  "main": "dist/rayzee.umd.js",
@@ -1,5 +1,6 @@
1
1
  import { EventDispatcher, ACESFilmicToneMapping } from 'three';
2
2
  import { TONE_MAP_FNS, SRGB_GAMMA, applySaturation } from '../Processor/ToneMapCPU.js';
3
+ import { fetchAsWorker } from '../Processor/Workers/fetchAsWorker.js';
3
4
 
4
5
 
5
6
  // ─── Model Configuration ───────────────────────────────────────────────────────
@@ -137,10 +138,21 @@ export class AIUpscaler extends EventDispatcher {
137
138
  // Create worker on first use
138
139
  if ( ! this._worker ) {
139
140
 
140
- this._worker = new Worker(
141
- new URL( '../Processor/Workers/AIUpscalerWorker.js', import.meta.url ),
142
- { type: 'module' }
143
- );
141
+ try {
142
+
143
+ this._worker = new Worker(
144
+ new URL( '../Processor/Workers/AIUpscalerWorker.js', import.meta.url ),
145
+ { type: 'module' }
146
+ );
147
+
148
+ } catch ( e ) {
149
+
150
+ if ( e.name !== 'SecurityError' ) throw e;
151
+ this._worker = await fetchAsWorker(
152
+ new URL( '../Processor/Workers/AIUpscalerWorker.js', import.meta.url )
153
+ );
154
+
155
+ }
144
156
 
145
157
  }
146
158
 
@@ -645,8 +645,6 @@ export class PathTracerApp extends EventDispatcher {
645
645
  this.cameraManager.camera.aspect = width / height;
646
646
  this.cameraManager.camera.updateProjectionMatrix();
647
647
 
648
- this.denoisingManager?.syncCanvasStyle( width, height );
649
-
650
648
  // Overlay helpers always render at display resolution
651
649
  const dpr = window.devicePixelRatio || 1;
652
650
  this.overlayManager?.setSize(
@@ -679,8 +677,6 @@ export class PathTracerApp extends EventDispatcher {
679
677
 
680
678
  setCanvasSize( width, height ) {
681
679
 
682
- this.denoisingManager?.syncCanvasStyle( width, height );
683
-
684
680
  if ( width === 0 || height === 0 ) return;
685
681
 
686
682
  this.renderer.setPixelRatio( 1.0 );
@@ -1,5 +1,6 @@
1
1
  import { TreeletOptimizer } from './TreeletOptimizer.js';
2
2
  import { ReinsertionOptimizer } from './ReinsertionOptimizer.js';
3
+ import { fetchAsWorker } from './Workers/fetchAsWorker.js';
3
4
 
4
5
  // Inline copy of TRIANGLE_DATA_LAYOUT (mirrors Constants.js).
5
6
  // Cannot import Constants.js because BVHBuilder runs inside BVHWorker
@@ -397,12 +398,7 @@ export class BVHBuilder {
397
398
 
398
399
  return new Promise( ( resolve, reject ) => {
399
400
 
400
- try {
401
-
402
- const worker = new Worker(
403
- new URL( './Workers/BVHWorker.js', import.meta.url ),
404
- { type: 'module' }
405
- );
401
+ const setupWorker = ( worker ) => {
406
402
 
407
403
  const triangleCount = this.totalTriangles;
408
404
  const useShared = typeof SharedArrayBuffer !== 'undefined';
@@ -482,10 +478,34 @@ export class BVHBuilder {
482
478
 
483
479
  worker.postMessage( workerData, [ transferBuffer ] );
484
480
 
481
+ };
482
+
483
+ try {
484
+
485
+ setupWorker( new Worker(
486
+ new URL( './Workers/BVHWorker.js', import.meta.url ),
487
+ { type: 'module' }
488
+ ) );
489
+
485
490
  } catch ( error ) {
486
491
 
487
- console.warn( 'Worker creation failed, falling back to synchronous build:', error );
488
- resolve( this._buildSyncAndFlatten( triangles, depth, progressCallback ) );
492
+ if ( error.name === 'SecurityError' ) {
493
+
494
+ fetchAsWorker(
495
+ new URL( './Workers/BVHWorker.js', import.meta.url )
496
+ ).then( setupWorker ).catch( () => {
497
+
498
+ console.warn( 'Worker fetch fallback failed, using synchronous build' );
499
+ resolve( this._buildSyncAndFlatten( triangles, depth, progressCallback ) );
500
+
501
+ } );
502
+
503
+ } else {
504
+
505
+ console.warn( 'Worker creation failed, falling back to synchronous build:', error );
506
+ resolve( this._buildSyncAndFlatten( triangles, depth, progressCallback ) );
507
+
508
+ }
489
509
 
490
510
  }
491
511
 
@@ -1,4 +1,5 @@
1
1
  import { DataUtils, HalfFloatType, FloatType, SRGBColorSpace } from 'three';
2
+ import { fetchAsWorker } from './Workers/fetchAsWorker.js';
2
3
 
3
4
  /**
4
5
  * Binary search to find the closest index
@@ -221,10 +222,21 @@ export class EquirectHDRInfo {
221
222
  // Reuse worker across calls; create on first use
222
223
  if ( ! this._worker ) {
223
224
 
224
- this._worker = new Worker(
225
- new URL( './Workers/CDFWorker.js', import.meta.url ),
226
- { type: 'module' }
227
- );
225
+ try {
226
+
227
+ this._worker = new Worker(
228
+ new URL( './Workers/CDFWorker.js', import.meta.url ),
229
+ { type: 'module' }
230
+ );
231
+
232
+ } catch ( e ) {
233
+
234
+ if ( e.name !== 'SecurityError' ) throw e;
235
+ this._worker = await fetchAsWorker(
236
+ new URL( './Workers/CDFWorker.js', import.meta.url )
237
+ );
238
+
239
+ }
228
240
 
229
241
  }
230
242
 
@@ -7,6 +7,8 @@
7
7
  * a cycle that Vite cannot resolve.
8
8
  */
9
9
 
10
+ import { fetchAsWorker } from './Workers/fetchAsWorker.js';
11
+
10
12
  const FPT = 32; // FLOATS_PER_TRIANGLE
11
13
  const PARALLEL_THRESHOLD = 50000;
12
14
  const MAX_PARALLEL_WORKERS = 8;
@@ -34,7 +36,7 @@ export function buildBVHParallel( triangles, depth, progressCallback, config ) {
34
36
 
35
37
  return new Promise( ( resolve, reject ) => {
36
38
 
37
- try {
39
+ ( async () => {
38
40
 
39
41
  // Allocate SharedArrayBuffers
40
42
  const sharedTriangleData = new SharedArrayBuffer( triangles.byteLength );
@@ -48,10 +50,22 @@ export function buildBVHParallel( triangles, depth, progressCallback, config ) {
48
50
  const sharedReorderBuffer = new SharedArrayBuffer( triangleCount * FPT * 4 );
49
51
 
50
52
  // Phase 1: Coordinator worker
51
- const coordinatorWorker = new Worker(
52
- new URL( './Workers/BVHWorker.js', import.meta.url ),
53
- { type: 'module' }
54
- );
53
+ let coordinatorWorker;
54
+ try {
55
+
56
+ coordinatorWorker = new Worker(
57
+ new URL( './Workers/BVHWorker.js', import.meta.url ),
58
+ { type: 'module' }
59
+ );
60
+
61
+ } catch ( e ) {
62
+
63
+ if ( e.name !== 'SecurityError' ) throw e;
64
+ coordinatorWorker = await fetchAsWorker(
65
+ new URL( './Workers/BVHWorker.js', import.meta.url )
66
+ );
67
+
68
+ }
55
69
 
56
70
  let phase1Stats = null;
57
71
  const allWorkers = [ coordinatorWorker ];
@@ -133,7 +147,7 @@ export function buildBVHParallel( triangles, depth, progressCallback, config ) {
133
147
  triangleCount, progressCallback, coordinatorWorker,
134
148
  allWorkers, cleanup, fallbackToSingle, resolve, timerRef,
135
149
  config
136
- );
150
+ ).catch( err => fallbackToSingle( err.message ) );
137
151
  return;
138
152
 
139
153
  }
@@ -169,12 +183,12 @@ export function buildBVHParallel( triangles, depth, progressCallback, config ) {
169
183
  treeletOptimization: config.treeletOptimization
170
184
  } );
171
185
 
172
- } catch ( error ) {
186
+ } )().catch( ( error ) => {
173
187
 
174
188
  console.warn( '[ParallelBVH] Parallel build setup failed:', error );
175
189
  reject( error );
176
190
 
177
- }
191
+ } );
178
192
 
179
193
  } );
180
194
 
@@ -184,7 +198,7 @@ export function buildBVHParallel( triangles, depth, progressCallback, config ) {
184
198
  * Handle Phase 2: distribute subtree tasks to worker pool and collect results.
185
199
  * @private
186
200
  */
187
- function handlePhase2(
201
+ async function handlePhase2(
188
202
  phase1Result, numWorkers, sharedTriangleData, sharedCentroids,
189
203
  sharedBMin, sharedBMax, sharedIndices, sharedReorderBuffer,
190
204
  triangleCount, progressCallback, coordinatorWorker,
@@ -299,10 +313,22 @@ function handlePhase2(
299
313
  const bucket = workerTaskBuckets[ w ];
300
314
  if ( bucket.length === 0 ) continue;
301
315
 
302
- const subtreeWorker = new Worker(
303
- new URL( './Workers/BVHSubtreeWorker.js', import.meta.url ),
304
- { type: 'module' }
305
- );
316
+ let subtreeWorker;
317
+ try {
318
+
319
+ subtreeWorker = new Worker(
320
+ new URL( './Workers/BVHSubtreeWorker.js', import.meta.url ),
321
+ { type: 'module' }
322
+ );
323
+
324
+ } catch ( e ) {
325
+
326
+ if ( e.name !== 'SecurityError' ) throw e;
327
+ subtreeWorker = await fetchAsWorker(
328
+ new URL( './Workers/BVHSubtreeWorker.js', import.meta.url )
329
+ );
330
+
331
+ }
306
332
 
307
333
  allWorkers.push( subtreeWorker );
308
334
 
@@ -405,12 +431,24 @@ function buildSingleWorker( triangles, depth, progressCallback, config ) {
405
431
 
406
432
  return new Promise( ( resolve, reject ) => {
407
433
 
408
- try {
434
+ ( async () => {
409
435
 
410
- const worker = new Worker(
411
- new URL( './Workers/BVHWorker.js', import.meta.url ),
412
- { type: 'module' }
413
- );
436
+ let worker;
437
+ try {
438
+
439
+ worker = new Worker(
440
+ new URL( './Workers/BVHWorker.js', import.meta.url ),
441
+ { type: 'module' }
442
+ );
443
+
444
+ } catch ( e ) {
445
+
446
+ if ( e.name !== 'SecurityError' ) throw e;
447
+ worker = await fetchAsWorker(
448
+ new URL( './Workers/BVHWorker.js', import.meta.url )
449
+ );
450
+
451
+ }
414
452
 
415
453
  const triangleCount = triangles.byteLength / ( FPT * 4 );
416
454
  const useShared = typeof SharedArrayBuffer !== 'undefined';
@@ -466,12 +504,12 @@ function buildSingleWorker( triangles, depth, progressCallback, config ) {
466
504
  reinsertionOptimization: config.reinsertionOptimization
467
505
  }, [ transferBuffer ] );
468
506
 
469
- } catch ( error ) {
507
+ } )().catch( ( error ) => {
470
508
 
471
509
  console.warn( '[ParallelBVH] Single worker fallback failed:', error );
472
510
  reject( error );
473
511
 
474
- }
512
+ } );
475
513
 
476
514
  } );
477
515
 
@@ -11,6 +11,7 @@ import { EmissiveTriangleBuilder } from './EmissiveTriangleBuilder.js';
11
11
  import { updateLoading } from '../Processor/utils.js';
12
12
  import { BuildTimer } from './BuildTimer.js';
13
13
  import { TRIANGLE_DATA_LAYOUT } from '../EngineDefaults.js';
14
+ import { fetchAsWorker } from './Workers/fetchAsWorker.js';
14
15
 
15
16
  /**
16
17
  * SceneProcessor - Processes scene geometry into GPU-ready data:
@@ -663,24 +664,41 @@ export class SceneProcessor {
663
664
  };
664
665
 
665
666
  // Spin up the pool
666
- for ( let i = 0; i < poolSize; i ++ ) {
667
+ ( async () => {
667
668
 
668
- const worker = new Worker(
669
- new URL( './Workers/BVHWorker.js', import.meta.url ),
670
- { type: 'module' }
671
- );
672
- worker.onmessage = ( e ) => onWorkerMessage( worker, e );
673
- worker.onerror = ( err ) => {
669
+ for ( let i = 0; i < poolSize; i ++ ) {
674
670
 
675
- workers.forEach( w => w.terminate() );
676
- reject( err );
671
+ let worker;
672
+ try {
677
673
 
678
- };
674
+ worker = new Worker(
675
+ new URL( './Workers/BVHWorker.js', import.meta.url ),
676
+ { type: 'module' }
677
+ );
679
678
 
680
- workers.push( worker );
681
- dispatchNext( worker );
679
+ } catch ( e ) {
682
680
 
683
- }
681
+ if ( e.name !== 'SecurityError' ) { reject( e ); return; }
682
+ worker = await fetchAsWorker(
683
+ new URL( './Workers/BVHWorker.js', import.meta.url )
684
+ );
685
+
686
+ }
687
+
688
+ worker.onmessage = ( e ) => onWorkerMessage( worker, e );
689
+ worker.onerror = ( err ) => {
690
+
691
+ workers.forEach( w => w.terminate() );
692
+ reject( err );
693
+
694
+ };
695
+
696
+ workers.push( worker );
697
+ dispatchNext( worker );
698
+
699
+ }
700
+
701
+ } )().catch( reject );
684
702
 
685
703
  } );
686
704
 
@@ -1136,10 +1154,21 @@ export class SceneProcessor {
1136
1154
  // Lazy-create worker
1137
1155
  if ( ! this._refitWorker ) {
1138
1156
 
1139
- this._refitWorker = new Worker(
1140
- new URL( './Workers/BVHRefitWorker.js', import.meta.url ),
1141
- { type: 'module' }
1142
- );
1157
+ try {
1158
+
1159
+ this._refitWorker = new Worker(
1160
+ new URL( './Workers/BVHRefitWorker.js', import.meta.url ),
1161
+ { type: 'module' }
1162
+ );
1163
+
1164
+ } catch ( e ) {
1165
+
1166
+ if ( e.name !== 'SecurityError' ) throw e;
1167
+ this._refitWorker = await fetchAsWorker(
1168
+ new URL( './Workers/BVHRefitWorker.js', import.meta.url )
1169
+ );
1170
+
1171
+ }
1143
1172
 
1144
1173
  }
1145
1174
 
@@ -1655,26 +1684,13 @@ export class SceneProcessor {
1655
1684
  this._rebuildGeneration ++;
1656
1685
  const generation = this._rebuildGeneration;
1657
1686
 
1658
- for ( const meshIdx of meshIndices ) {
1659
-
1660
- const entry = this.instanceTable.entries[ meshIdx ];
1661
- if ( ! entry ) continue;
1662
-
1663
- // Cancel any in-flight rebuild for this mesh
1664
- const existing = this._pendingRebuilds.get( meshIdx );
1665
- if ( existing ) existing.terminate();
1687
+ const dispatchRebuild = ( meshIdx, entry, worker ) => {
1666
1688
 
1667
- // Copy current world-space triangle data for this mesh
1668
1689
  const meshTriData = this.triangleData.slice(
1669
1690
  entry.triOffset * FPT,
1670
1691
  ( entry.triOffset + entry.triCount ) * FPT
1671
1692
  );
1672
1693
 
1673
- const worker = new Worker(
1674
- new URL( './Workers/BVHWorker.js', import.meta.url ),
1675
- { type: 'module' }
1676
- );
1677
-
1678
1694
  this._pendingRebuilds.set( meshIdx, worker );
1679
1695
 
1680
1696
  worker.onmessage = ( e ) => {
@@ -1729,6 +1745,35 @@ export class SceneProcessor {
1729
1745
  },
1730
1746
  }, [ meshTriData.buffer ] );
1731
1747
 
1748
+ };
1749
+
1750
+ for ( const meshIdx of meshIndices ) {
1751
+
1752
+ const entry = this.instanceTable.entries[ meshIdx ];
1753
+ if ( ! entry ) continue;
1754
+
1755
+ // Cancel any in-flight rebuild for this mesh
1756
+ const existing = this._pendingRebuilds.get( meshIdx );
1757
+ if ( existing ) existing.terminate();
1758
+
1759
+ let worker;
1760
+ try {
1761
+
1762
+ worker = new Worker(
1763
+ new URL( './Workers/BVHWorker.js', import.meta.url ),
1764
+ { type: 'module' }
1765
+ );
1766
+ dispatchRebuild( meshIdx, entry, worker );
1767
+
1768
+ } catch ( e ) {
1769
+
1770
+ if ( e.name !== 'SecurityError' ) throw e;
1771
+ fetchAsWorker(
1772
+ new URL( './Workers/BVHWorker.js', import.meta.url )
1773
+ ).then( w => dispatchRebuild( meshIdx, entry, w ) );
1774
+
1775
+ }
1776
+
1732
1777
  }
1733
1778
 
1734
1779
  }
@@ -1,5 +1,6 @@
1
1
  import { DataArrayTexture, RGBAFormat, LinearFilter, UnsignedByteType, SRGBColorSpace } from "three";
2
2
  import { TEXTURE_CONSTANTS, MEMORY_CONSTANTS, DEFAULT_TEXTURE_MATRIX, MATERIAL_DATA_LAYOUT } from '../EngineDefaults.js';
3
+ import { fetchAsWorker } from './Workers/fetchAsWorker.js';
3
4
 
4
5
  // Canvas pooling for efficient reuse of canvas elements
5
6
  class CanvasPool {
@@ -602,10 +603,22 @@ export class TextureCreator {
602
603
 
603
604
  try {
604
605
 
605
- const worker = new Worker(
606
- new URL( './Workers/TexturesWorker.js', import.meta.url ),
607
- { type: 'module' }
608
- );
606
+ let worker;
607
+ try {
608
+
609
+ worker = new Worker(
610
+ new URL( './Workers/TexturesWorker.js', import.meta.url ),
611
+ { type: 'module' }
612
+ );
613
+
614
+ } catch ( e ) {
615
+
616
+ if ( e.name !== 'SecurityError' ) throw e;
617
+ worker = await fetchAsWorker(
618
+ new URL( './Workers/TexturesWorker.js', import.meta.url )
619
+ );
620
+
621
+ }
609
622
 
610
623
  // Prepare textures for worker with direct transfer
611
624
  const texturesData = await this.prepareTexturesForWorkerDirect( textures );
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Cross-origin Worker fallback.
3
+ *
4
+ * Browsers enforce same-origin policy on `new Worker(url)`. When the
5
+ * library's JS is served from a CDN (different origin to the page),
6
+ * the standard constructor throws `SecurityError`.
7
+ *
8
+ * This helper fetches the script over the network (CORS-allowed) and
9
+ * re-hosts it as a same-origin Blob URL.
10
+ *
11
+ * The Blob URL is intentionally **not** revoked — workers may reference
12
+ * `self.location.href` to spawn sub-workers (e.g. BVHWorker).
13
+ *
14
+ * @param {URL|string} url Worker script URL (may be cross-origin)
15
+ * @returns {Promise<Worker>}
16
+ */
17
+ export async function fetchAsWorker( url ) {
18
+
19
+ const href = url instanceof URL ? url.href : url;
20
+ const response = await fetch( href );
21
+ if ( ! response.ok ) {
22
+
23
+ throw new Error( `Failed to fetch worker script: ${response.status}` );
24
+
25
+ }
26
+
27
+ const blob = new Blob( [ await response.text() ], { type: 'application/javascript' } );
28
+ return new Worker( URL.createObjectURL( blob ) );
29
+
30
+ }
@@ -64,8 +64,10 @@ export class DenoisingManager extends EventDispatcher {
64
64
  const dc = document.createElement( 'canvas' );
65
65
  dc.width = mainCanvas.width;
66
66
  dc.height = mainCanvas.height;
67
- dc.style.width = `${mainCanvas.clientWidth}px`;
68
- dc.style.height = `${mainCanvas.clientHeight}px`;
67
+ dc.style.position = 'absolute';
68
+ dc.style.inset = '0';
69
+ dc.style.width = '100%';
70
+ dc.style.height = '100%';
69
71
 
70
72
  parent.insertBefore( dc, mainCanvas );
71
73
  return dc;
@@ -86,21 +88,6 @@ export class DenoisingManager extends EventDispatcher {
86
88
 
87
89
  }
88
90
 
89
- /**
90
- * Syncs the denoiser canvas CSS dimensions to match display size.
91
- * @param {number} width
92
- * @param {number} height
93
- */
94
- syncCanvasStyle( width, height ) {
95
-
96
- if ( this.denoiserCanvas ) {
97
-
98
- this.denoiserCanvas.style.width = `${width}px`;
99
- this.denoiserCanvas.style.height = `${height}px`;
100
-
101
- }
102
-
103
- }
104
91
 
105
92
  /**
106
93
  * Restores the denoiser canvas to base render resolution after upscaling.
@@ -1,2 +0,0 @@
1
- (function(){var e=class{constructor(e,t){this.traversalCost=e,this.intersectionCost=t,this.maxTreeletLeaves=7,this.minImprovement=.02,this.topologyCache=new Map;for(let e=3;e<=this.maxTreeletLeaves;e++)this.topologyCache.set(e,this.generateTopologies(e));this.stats={treeletsProcessed:0,treeletsImproved:0,totalSAHImprovement:0,averageSAHImprovement:0,optimizationTime:0}}generateTopologies(e){if(e===1)return[0];if(e===2)return[[0,1]];let t=[];for(let n=1;n<e;n++){let r=this.generateTopologies(n),i=this.generateTopologies(e-n);for(let e of r)for(let r of i)t.push([e,this.offsetTopology(r,n)])}return t}offsetTopology(e,t){return typeof e==`number`?e+t:[this.offsetTopology(e[0],t),this.offsetTopology(e[1],t)]}optimizeBVH(e){let t=performance.now();this.stats={treeletsProcessed:0,treeletsImproved:0,totalSAHImprovement:0,averageSAHImprovement:0,optimizationTime:0};let n=this.identifyTreeletRoots(e);for(let e=0;e<n.length;e++){if(performance.now()-t>3e4){console.warn(`TreeletOptimizer: timeout after ${e}/${n.length} treelets`);break}this.optimizeTreelet(n[e])}return this.stats.optimizationTime=performance.now()-t,this.stats.averageSAHImprovement=this.stats.treeletsProcessed>0?this.stats.totalSAHImprovement/this.stats.treeletsProcessed:0,e}identifyTreeletRoots(e){let t=[],n=new Set,r=[{node:e,visited:!1}];for(;r.length>0;){let e=r[r.length-1];if(e.visited){r.pop();let i=e.node;if(i.triangleCount>0||n.has(i))continue;let a=this.countLeaves(i);a>=3&&a<=this.maxTreeletLeaves&&(t.push(i),this.markSubtree(i,n))}else{e.visited=!0;let t=e.node;if(t.triangleCount>0)continue;t.rightChild&&r.push({node:t.rightChild,visited:!1}),t.leftChild&&r.push({node:t.leftChild,visited:!1})}}return t}countLeaves(e){return e?e.triangleCount>0?1:this.countLeaves(e.leftChild)+this.countLeaves(e.rightChild):0}markSubtree(e,t){e&&(t.add(e),!(e.triangleCount>0)&&(this.markSubtree(e.leftChild,t),this.markSubtree(e.rightChild,t)))}optimizeTreelet(e){let t=[];this.extractLeaves(e,t);let n=t.length;if(n<3||n>this.maxTreeletLeaves)return;this.stats.treeletsProcessed++;let r=this.evaluateSubtreeSAH(e),i=this.topologyCache.get(n);if(!i||i.length===0)return;let a=r,o=null,s=null;if(n<=5){let e=this.generatePermutations(n);for(let n of i)for(let r of e){let e=this.evaluateTopology(n,t,r);e<a&&(a=e,o=n,s=r)}}else{let e=Array.from({length:n},(e,t)=>t);for(let n of i){let r=this.evaluateTopology(n,t,e);r<a&&(a=r,o=n,s=e);let i=this.greedySwapOptimize(n,t,e,r);i.cost<a&&(a=i.cost,o=n,s=i.perm)}}let c=(r-a)/r;o&&c>this.minImprovement&&(this.reconstructTreelet(e,o,t,s),this.stats.treeletsImproved++,this.stats.totalSAHImprovement+=c)}extractLeaves(e,t){if(e){if(e.triangleCount>0){t.push({minX:e.minX,minY:e.minY,minZ:e.minZ,maxX:e.maxX,maxY:e.maxY,maxZ:e.maxZ,triangleOffset:e.triangleOffset,triangleCount:e.triangleCount});return}this.extractLeaves(e.leftChild,t),this.extractLeaves(e.rightChild,t)}}evaluateSubtreeSAH(e){if(!e)return 0;if(e.triangleCount>0)return this.surfaceAreaFlat(e.minX,e.minY,e.minZ,e.maxX,e.maxY,e.maxZ)*e.triangleCount*this.intersectionCost;let t=this.evaluateSubtreeSAH(e.leftChild),n=this.evaluateSubtreeSAH(e.rightChild);return this.surfaceAreaFlat(e.minX,e.minY,e.minZ,e.maxX,e.maxY,e.maxZ)*this.traversalCost+t+n}evaluateTopology(e,t,n){return this.evalTopoRecursive(e,t,n).cost}evalTopoRecursive(e,t,n){if(typeof e==`number`){let r=t[n[e]];return{cost:this.surfaceAreaFlat(r.minX,r.minY,r.minZ,r.maxX,r.maxY,r.maxZ)*r.triangleCount*this.intersectionCost,minX:r.minX,minY:r.minY,minZ:r.minZ,maxX:r.maxX,maxY:r.maxY,maxZ:r.maxZ}}let r=this.evalTopoRecursive(e[0],t,n),i=this.evalTopoRecursive(e[1],t,n),a=Math.min(r.minX,i.minX),o=Math.min(r.minY,i.minY),s=Math.min(r.minZ,i.minZ),c=Math.max(r.maxX,i.maxX),l=Math.max(r.maxY,i.maxY),u=Math.max(r.maxZ,i.maxZ);return{cost:this.surfaceAreaFlat(a,o,s,c,l,u)*this.traversalCost+r.cost+i.cost,minX:a,minY:o,minZ:s,maxX:c,maxY:l,maxZ:u}}surfaceAreaFlat(e,t,n,r,i,a){let o=r-e,s=i-t,c=a-n;return 2*(o*s+s*c+c*o)}generatePermutations(e){let t=[],n=Array.from({length:e},(e,t)=>t),r=i=>{if(i===e){t.push([...n]);return}for(let t=i;t<e;t++)[n[i],n[t]]=[n[t],n[i]],r(i+1),[n[i],n[t]]=[n[t],n[i]]};return r(0),t}greedySwapOptimize(e,t,n,r){let i=[...n],a=r,o=!0;for(;o;){o=!1;for(let n=0;n<i.length-1;n++)for(let r=n+1;r<i.length;r++){[i[n],i[r]]=[i[r],i[n]];let s=this.evaluateTopology(e,t,i);s<a?(a=s,o=!0):[i[n],i[r]]=[i[r],i[n]]}}return{perm:i,cost:a}}reconstructTreelet(e,t,n,r){let i=this.buildSubtree(t,n,r);e.minX=i.minX,e.minY=i.minY,e.minZ=i.minZ,e.maxX=i.maxX,e.maxY=i.maxY,e.maxZ=i.maxZ,e.leftChild=i.leftChild,e.rightChild=i.rightChild,e.triangleOffset=i.triangleOffset,e.triangleCount=i.triangleCount}buildSubtree(e,n,r){if(typeof e==`number`){let i=n[r[e]],a=new t;return a.minX=i.minX,a.minY=i.minY,a.minZ=i.minZ,a.maxX=i.maxX,a.maxY=i.maxY,a.maxZ=i.maxZ,a.triangleOffset=i.triangleOffset,a.triangleCount=i.triangleCount,a}let i=this.buildSubtree(e[0],n,r),a=this.buildSubtree(e[1],n,r),o=new t;return o.leftChild=i,o.rightChild=a,o.minX=Math.min(i.minX,a.minX),o.minY=Math.min(i.minY,a.minY),o.minZ=Math.min(i.minZ,a.minZ),o.maxX=Math.max(i.maxX,a.maxX),o.maxY=Math.max(i.maxY,a.maxY),o.maxZ=Math.max(i.maxZ,a.maxZ),o}setTreeletSize(e){this.maxTreeletLeaves=Math.max(3,Math.min(7,e));for(let e=3;e<=this.maxTreeletLeaves;e++)this.topologyCache.has(e)||this.topologyCache.set(e,this.generateTopologies(e))}setMinImprovement(e){this.minImprovement=Math.max(.001,e)}setMaxTreelets(){}getStatistics(){return{...this.stats}}},t=class{constructor(){this.minX=0,this.minY=0,this.minZ=0,this.maxX=0,this.maxY=0,this.maxZ=0,this.leftChild=null,this.rightChild=null,this.triangleOffset=0,this.triangleCount=0}},n=class{constructor(e,t){this.traversalCost=e,this.intersectionCost=t,this.batchSizeRatio=.02,this.maxIterations=2,this.timeBudgetMs=15e3,this.stats={reinsertionsApplied:0,iterations:0,timeMs:0}}setBatchSizeRatio(e){this.batchSizeRatio=Math.max(.005,Math.min(.1,e))}setMaxIterations(e){this.maxIterations=Math.max(1,Math.min(5,e))}getStatistics(){return{...this.stats}}surfaceArea(e){let t=e.maxX-e.minX,n=e.maxY-e.minY,r=e.maxZ-e.minZ;return t*n+n*r+r*t}buildParentMap(e){let t=new Map;t.set(e,{parent:null,isLeft:!1});let n=[e];for(;n.length>0;){let e=n.pop();e.triangleCount>0||(e.leftChild&&(t.set(e.leftChild,{parent:e,isLeft:!0}),n.push(e.leftChild)),e.rightChild&&(t.set(e.rightChild,{parent:e,isLeft:!1}),n.push(e.rightChild)))}return t}findCandidates(e,t,n){let r=[],i=[e];for(;i.length>0;){let a=i.pop();if(a!==e&&n.get(a).parent!==e){let e=this.surfaceArea(a);r.length<t?(r.push({node:a,cost:e}),r.length===t&&this._heapify(r)):e>r[0].cost&&(r[0]={node:a,cost:e},this._siftDown(r,0))}a.triangleCount===0&&(a.leftChild&&i.push(a.leftChild),a.rightChild&&i.push(a.rightChild))}return r}_heapify(e){for(let t=(e.length>>1)-1;t>=0;t--)this._siftDown(e,t)}_siftDown(e,t){let n=e.length;for(;;){let r=t,i=2*t+1,a=2*t+2;if(i<n&&e[i].cost<e[r].cost&&(r=i),a<n&&e[a].cost<e[r].cost&&(r=a),r===t)break;let o=e[t];e[t]=e[r],e[r]=o,t=r}}findReinsertion(e,t,n){let r=n.get(e),i=r.parent;if(!i)return null;let a=r.isLeft?i.rightChild:i.leftChild,o=this.surfaceArea(e),s=this.surfaceArea(i),c=null,l=0,u=s,d=a.minX,f=a.minY,p=a.minZ,m=a.maxX,h=a.maxY,g=a.maxZ,_=a,v=i,y=[];do{for(y.length=0,y.push(u,_);y.length>0;){let t=y.pop(),n=y.pop();if(n-o<=l)continue;let r=Math.min(t.minX,e.minX),i=Math.min(t.minY,e.minY),a=Math.min(t.minZ,e.minZ),s=Math.max(t.maxX,e.maxX),u=Math.max(t.maxY,e.maxY),d=Math.max(t.maxZ,e.maxZ),f=s-r,p=u-i,m=d-a,h=n-(f*p+p*m+m*f);if(h>l&&(c=t,l=h),t.triangleCount===0&&t.leftChild&&t.rightChild){let e=h+this.surfaceArea(t);y.push(e,t.leftChild),y.push(e,t.rightChild)}}let t=n.get(v);if(!t||t.parent===null)break;if(v!==i){d=Math.min(d,_.minX),f=Math.min(f,_.minY),p=Math.min(p,_.minZ),m=Math.max(m,_.maxX),h=Math.max(h,_.maxY),g=Math.max(g,_.maxZ);let e=m-d,t=h-f,n=g-p,r=e*t+t*n+n*e;u+=this.surfaceArea(v)-r}let r=t.parent;_=t.isLeft?r.rightChild:r.leftChild,v=r}while(n.get(v).parent!==null);return c===a||c===i?null:c?{from:e,to:c,areaDiff:l}:null}getConflicts(e,t,n){let r=n.get(e);return[t,e,r.isLeft?r.parent.rightChild:r.parent.leftChild,n.get(t).parent,r.parent]}reinsertNode(e,t,n){let r=n.get(e),i=r.parent,a=r.isLeft?i.rightChild:i.leftChild,o=n.get(i),s=o.parent,c=n.get(t),l=c.parent;o.isLeft?s.leftChild=a:s.rightChild=a,i.leftChild=e,i.rightChild=t,i.triangleOffset=0,i.triangleCount=0,i.minX=Math.min(e.minX,t.minX),i.minY=Math.min(e.minY,t.minY),i.minZ=Math.min(e.minZ,t.minZ),i.maxX=Math.max(e.maxX,t.maxX),i.maxY=Math.max(e.maxY,t.maxY),i.maxZ=Math.max(e.maxZ,t.maxZ),c.isLeft?l.leftChild=i:l.rightChild=i,n.set(a,{parent:s,isLeft:o.isLeft}),n.set(i,{parent:l,isLeft:c.isLeft}),n.set(e,{parent:i,isLeft:!0}),n.set(t,{parent:i,isLeft:!1}),this.refitFrom(s,n),this.refitFrom(l,n)}refitFrom(e,t){let n=e;for(;n;){if(n.triangleCount===0&&n.leftChild&&n.rightChild){let e=n.leftChild,t=n.rightChild;n.minX=Math.min(e.minX,t.minX),n.minY=Math.min(e.minY,t.minY),n.minZ=Math.min(e.minZ,t.minZ),n.maxX=Math.max(e.maxX,t.maxX),n.maxY=Math.max(e.maxY,t.maxY),n.maxZ=Math.max(e.maxZ,t.maxZ)}let e=t.get(n);n=e?e.parent:null}}optimizeBVH(e,t){let n=performance.now();this.stats={reinsertionsApplied:0,iterations:0,timeMs:0};for(let r=0;r<this.maxIterations&&!(performance.now()-n>this.timeBudgetMs);r++){let i=this.buildParentMap(e),a=i.size,o=Math.max(1,Math.floor(a*this.batchSizeRatio));t&&t(`Reinsertion iter ${r+1}/${this.maxIterations}: selecting ${o} candidates`);let s=this.findCandidates(e,o,i),c=[];for(let t=0;t<s.length&&!(performance.now()-n>this.timeBudgetMs);t++){let n=this.findReinsertion(s[t].node,e,i);n&&n.areaDiff>0&&c.push(n)}c.sort((e,t)=>t.areaDiff-e.areaDiff);let l=new Set,u=0;for(let e of c){let t=this.getConflicts(e.from,e.to,i);if(!t.some(e=>l.has(e))){for(let e of t)l.add(e);this.reinsertNode(e.from,e.to,i),u++}}if(this.stats.reinsertionsApplied+=u,this.stats.iterations=r+1,t&&t(`Reinsertion iter ${r+1}: applied ${u} reinsertions`),u===0)break}return this.stats.timeMs=performance.now()-n,this.stats}};let r={FLOATS_PER_TRIANGLE:32,POSITION_A_OFFSET:0,POSITION_B_OFFSET:4,POSITION_C_OFFSET:8,NORMAL_A_OFFSET:12,NORMAL_B_OFFSET:16,NORMAL_C_OFFSET:20,UV_AB_OFFSET:24,UV_C_MAT_OFFSET:28},i=r.FLOATS_PER_TRIANGLE;var a=class{constructor(){this.minX=0,this.minY=0,this.minZ=0,this.maxX=0,this.maxY=0,this.maxZ=0,this.leftChild=null,this.rightChild=null,this.triangleOffset=0,this.triangleCount=0}},o=class{constructor(){this.useWorker=!0,this.maxLeafSize=8,this.numBins=32,this.minBins=8,this.maxBins=64,this.totalNodes=0,this.processedTriangles=0,this.totalTriangles=0,this.lastProgressUpdate=0,this.progressUpdateInterval=100,this.traversalCost=1,this.intersectionCost=2.5,this.useMortonCodes=!0,this.mortonBits=10,this.mortonClusterThreshold=128,this.enableObjectMedianFallback=!0,this.enableSpatialMedianFallback=!0,this.splitStats={sahSplits:0,objectMedianSplits:0,spatialMedianSplits:0,failedSplits:0,avgBinsUsed:0,totalSplitAttempts:0,mortonSortTime:0,totalBuildTime:0,treeletOptimizationTime:0,treeletsProcessed:0,treeletsImproved:0,averageSAHImprovement:0,reinsertionOptimizationTime:0,reinsertionsApplied:0,reinsertionIterations:0},this.enableTreeletOptimization=!0,this.treeletSize=5,this.treeletOptimizationPasses=1,this.treeletMinImprovement=.02,this.maxTreeletDepth=3,this.maxTreeletsPerScene=20,this.treeletComplexityThreshold=5e4,this.enableReinsertionOptimization=!0,this.reinsertionBatchSizeRatio=.02,this.reinsertionMaxIterations=2,this.initializeBinArrays(),this._partResult={mid:0,lMinX:0,lMinY:0,lMinZ:0,lMaxX:0,lMaxY:0,lMaxZ:0,rMinX:0,rMinY:0,rMinZ:0,rMaxX:0,rMaxY:0,rMaxZ:0},this.centroids=null,this.bMin=null,this.bMax=null,this.indices=null,this.mortonCodes=null,this.triangles=null,this.reorderedTriangleData=null}initializeBinArrays(){let e=this.maxBins;this.binBoundsMin=new Float32Array(e*3),this.binBoundsMax=new Float32Array(e*3),this.binCounts=new Uint32Array(e),this.leftPrefixMin=new Float32Array(e*3),this.leftPrefixMax=new Float32Array(e*3),this.leftPrefixCount=new Uint32Array(e),this.rightPrefixMin=new Float32Array(e*3),this.rightPrefixMax=new Float32Array(e*3),this.rightPrefixCount=new Uint32Array(e)}getOptimalBinCount(e){return e<=16?this.minBins:e<=64?16:e<=256?32:e<=1024?48:this.maxBins}setAdaptiveBinConfig(e){e.minBins!==void 0&&(this.minBins=Math.max(4,e.minBins)),e.maxBins!==void 0&&(this.maxBins=Math.min(128,e.maxBins)),e.baseBins!==void 0&&(this.numBins=e.baseBins),e.maxBins!==void 0&&this.initializeBinArrays()}setMortonConfig(e){e.enabled!==void 0&&(this.useMortonCodes=e.enabled),e.bits!==void 0&&(this.mortonBits=Math.max(6,Math.min(10,e.bits))),e.threshold!==void 0&&(this.mortonClusterThreshold=Math.max(16,e.threshold))}setFallbackConfig(e){e.objectMedian!==void 0&&(this.enableObjectMedianFallback=e.objectMedian),e.spatialMedian!==void 0&&(this.enableSpatialMedianFallback=e.spatialMedian)}setTreeletConfig(e){e.enabled!==void 0&&(this.enableTreeletOptimization=e.enabled),e.size!==void 0&&(this.treeletSize=Math.max(3,Math.min(12,e.size))),e.passes!==void 0&&(this.treeletOptimizationPasses=Math.max(1,Math.min(3,e.passes))),e.minImprovement!==void 0&&(this.treeletMinImprovement=Math.max(.001,e.minImprovement))}disableTreeletOptimization(){this.enableTreeletOptimization=!1}setReinsertionConfig(e){e.enabled!==void 0&&(this.enableReinsertionOptimization=e.enabled),e.batchSizeRatio!==void 0&&(this.reinsertionBatchSizeRatio=Math.max(.005,Math.min(.1,e.batchSizeRatio))),e.maxIterations!==void 0&&(this.reinsertionMaxIterations=Math.max(1,Math.min(5,e.maxIterations)))}initializeTriangleArrays(){let e=this.totalTriangles,t=this.triangles,n=r.POSITION_A_OFFSET,a=r.POSITION_B_OFFSET,o=r.POSITION_C_OFFSET;for(let r=0;r<e;r++){let e=r*i,s=t[e+n],c=t[e+n+1],l=t[e+n+2],u=t[e+a],d=t[e+a+1],f=t[e+a+2],p=t[e+o],m=t[e+o+1],h=t[e+o+2],g=r*3;this.centroids[g]=(s+u+p)/3,this.centroids[g+1]=(c+d+m)/3,this.centroids[g+2]=(l+f+h)/3,this.bMin[g]=s<u?s<p?s:p:u<p?u:p,this.bMin[g+1]=c<d?c<m?c:m:d<m?d:m,this.bMin[g+2]=l<f?l<h?l:h:f<h?f:h,this.bMax[g]=s>u?s>p?s:p:u>p?u:p,this.bMax[g+1]=c>d?c>m?c:m:d>m?d:m,this.bMax[g+2]=l>f?l>h?l:h:f>h?f:h,this.indices[r]=r}}expandBits(e){return e=e*65537&4278190335,e=e*257&251719695,e=e*17&3272356035,e=e*5&1227133513,e}morton3D(e,t,n){return(this.expandBits(n)<<2)+(this.expandBits(t)<<1)+this.expandBits(e)}computeMortonCodeForIndex(e,t,n,r,i,a,o){let s=this.centroids,c=e*3,l=(1<<this.mortonBits)-1,u=i>0?(s[c]-t)/i:0,d=a>0?(s[c+1]-n)/a:0,f=o>0?(s[c+2]-r)/o:0,p=Math.max(0,Math.min(l,Math.floor(u*l))),m=Math.max(0,Math.min(l,Math.floor(d*l))),h=Math.max(0,Math.min(l,Math.floor(f*l)));return this.morton3D(p,m,h)}sortTrianglesByMortonCode(){let e=this.totalTriangles;if(!this.useMortonCodes||e<this.mortonClusterThreshold)return;let t=performance.now(),n=this.centroids,r=this.indices,i=1/0,a=1/0,o=1/0,s=-1/0,c=-1/0,l=-1/0;for(let t=0;t<e;t++){let e=r[t]*3,u=n[e],d=n[e+1],f=n[e+2];u<i&&(i=u),d<a&&(a=d),f<o&&(o=f),u>s&&(s=u),d>c&&(c=d),f>l&&(l=f)}let u=s-i,d=c-a,f=l-o,p=this.mortonCodes,m=(1<<this.mortonBits)-1,h=u>0?m/u:0,g=d>0?m/d:0,_=f>0?m/f:0;for(let t=0;t<e;t++){let e=r[t],s=e*3,c=(n[s]-i)*h,l=(n[s+1]-a)*g,u=(n[s+2]-o)*_;c=c<0?0:(c>m?m:c)|0,l=l<0?0:(l>m?m:l)|0,u=u<0?0:(u>m?m:u)|0,c=c*65537&4278190335,c=c*257&251719695,c=c*17&3272356035,c=c*5&1227133513,l=l*65537&4278190335,l=l*257&251719695,l=l*17&3272356035,l=l*5&1227133513,u=u*65537&4278190335,u=u*257&251719695,u=u*17&3272356035,u=u*5&1227133513,p[e]=(u<<2)+(l<<1)+c}let v=new Uint32Array(e),y=new Uint32Array(256);for(let t=0;t<32;t+=8){y.fill(0);for(let n=0;n<e;n++)y[p[r[n]]>>>t&255]++;let n=0;for(let e=0;e<256;e++){let t=y[e];y[e]=n,n+=t}for(let n=0;n<e;n++){let e=p[r[n]]>>>t&255;v[y[e]++]=r[n]}r.set(v)}this.splitStats.mortonSortTime+=performance.now()-t}build(e,t=30,n=null){return this.totalTriangles=e.byteLength/(i*4),this.processedTriangles=0,this.lastProgressUpdate=performance.now(),this.useWorker&&typeof Worker<`u`?new Promise((r,a)=>{try{let o=new Worker(new URL(``+new URL(`BVHWorker-BqQTDljT.js`,self.location.href).href,``+self.location.href),{type:`module`}),s=this.totalTriangles,c=typeof SharedArrayBuffer<`u`;console.log(`[BVHBuilder] SharedArrayBuffer: ${c?`enabled`:`unavailable (using transfer fallback)`}`);let l=c?new SharedArrayBuffer(s*i*4):null;o.onmessage=e=>{let{bvhData:t,triangles:i,originalToBvh:s,error:c,progress:u,treeletStats:d}=e.data;if(c){o.terminate(),a(Error(c));return}if(u!==void 0&&n){n(u);return}d&&(this.splitStats=d),o.terminate(),r({bvhData:t,bvhRoot:!0,reorderedTriangles:l?new Float32Array(l):i,originalToBvh:s||null})},o.onerror=e=>{o.terminate(),a(e)};let u=e.buffer,d={triangleData:u,triangleByteOffset:e.byteOffset,triangleByteLength:e.byteLength,triangleCount:s,depth:t,reportProgress:!!n,sharedReorderBuffer:l,treeletOptimization:{enabled:this.enableTreeletOptimization,size:this.treeletSize,passes:this.treeletOptimizationPasses,minImprovement:this.treeletMinImprovement},reinsertionOptimization:{enabled:this.enableReinsertionOptimization,batchSizeRatio:this.reinsertionBatchSizeRatio,maxIterations:this.reinsertionMaxIterations}};o.postMessage(d,[u])}catch(i){console.warn(`Worker creation failed, falling back to synchronous build:`,i),r(this._buildSyncAndFlatten(e,t,n))}}):new Promise(r=>{r(this._buildSyncAndFlatten(e,t,n))})}_buildSyncAndFlatten(e,t,n){let r=this.buildSync(e,t,n);return{bvhData:this.flattenBVH(r),bvhRoot:!0,reorderedTriangles:this.reorderedTriangleData||null,originalToBvh:this.originalToBvhMap||null}}buildSync(t,r=30,a=null,o=null){let s=performance.now();this.totalNodes=0,this.processedTriangles=0,this.triangles=t,this.totalTriangles=t.byteLength/(i*4),this.lastProgressUpdate=performance.now(),this.splitStats={sahSplits:0,objectMedianSplits:0,spatialMedianSplits:0,failedSplits:0,avgBinsUsed:0,totalSplitAttempts:0,mortonSortTime:0,totalBuildTime:0,treeletOptimizationTime:0,treeletsProcessed:0,treeletsImproved:0,averageSAHImprovement:0,reinsertionOptimizationTime:0,reinsertionsApplied:0,reinsertionIterations:0,saOrderTime:0,initTime:0,sahBuildTime:0,reorderTime:0};let c=this.totalTriangles,l=performance.now();this.centroids=new Float32Array(c*3),this.bMin=new Float32Array(c*3),this.bMax=new Float32Array(c*3),this.indices=new Uint32Array(c),this.mortonCodes=new Uint32Array(c),this.initializeTriangleArrays(),this.splitStats.initTime=performance.now()-l,this.sortTrianglesByMortonCode();let u=performance.now(),d=this.buildNodeRecursive(0,c,r,a);if(this.splitStats.sahBuildTime=performance.now()-u,this.enableTreeletOptimization&&this.totalTriangles>1e3){let t=this.totalTriangles>this.treeletComplexityThreshold,n=t?3:this.treeletSize,r=t?10:this.maxTreeletsPerScene,i=new e(this.traversalCost,this.intersectionCost);i.setTreeletSize(n),i.setMinImprovement(this.treeletMinImprovement),i.setMaxTreelets(r);let o=performance.now();for(let e=0;e<this.treeletOptimizationPasses;e++){let t=a?t=>{a(`Treelet optimization pass ${e+1}/${this.treeletOptimizationPasses}: ${t}`)}:null;try{i.optimizeBVH(d,t)}catch(t){console.error(`TreeletOptimizer: Error in pass ${e+1}:`,t);break}let n=i.getStatistics(),r=performance.now()-o;if(n.treeletsImproved===0&&e>0||r>15e3)break}let s=performance.now()-o;this.splitStats.treeletOptimizationTime=s;let c=i.getStatistics();this.splitStats.treeletsProcessed=c.treeletsProcessed,this.splitStats.treeletsImproved=c.treeletsImproved,this.splitStats.averageSAHImprovement=c.averageSAHImprovement}if(this.enableReinsertionOptimization&&this.totalTriangles>1e3){let e=new n(this.traversalCost,this.intersectionCost);e.setBatchSizeRatio(this.reinsertionBatchSizeRatio),e.setMaxIterations(this.reinsertionMaxIterations);let t=a?e=>{a(e)}:null;try{e.optimizeBVH(d,t)}catch(e){console.error(`ReinsertionOptimizer: Error:`,e)}let r=e.getStatistics();this.splitStats.reinsertionOptimizationTime=r.timeMs,this.splitStats.reinsertionsApplied=r.reinsertionsApplied,this.splitStats.reinsertionIterations=r.iterations}let f=performance.now();this.applySAOrdering(d),this.splitStats.saOrderTime=performance.now()-f;let p=performance.now(),m=this.triangles,h=o||new Float32Array(c*i);for(let e=0;e<c;e++){let t=this.indices[e]*i,n=e*i;h.set(m.subarray(t,t+i),n)}this.reorderedTriangleData=h;let g=new Uint32Array(c);for(let e=0;e<c;e++)g[this.indices[e]]=e;this.originalToBvhMap=g,this.splitStats.reorderTime=performance.now()-p,this.splitStats.totalBuildTime=performance.now()-s;let _=this.splitStats.totalBuildTime,v=this.splitStats;return console.log(`[BVH] ${c.toLocaleString()} tris → ${this.totalNodes} nodes in ${Math.round(_)}ms | SAH ${v.sahSplits} objMed ${v.objectMedianSplits} spatMed ${v.spatialMedianSplits} failed ${v.failedSplits}`+(v.treeletsProcessed?` | treelets ${v.treeletsImproved}/${v.treeletsProcessed} improved`:``)+(v.reinsertionsApplied?` | reinsertions ${v.reinsertionsApplied}`:``)),a&&a(100),this.centroids=null,this.bMin=null,this.bMax=null,this.mortonCodes=null,d}updateProgress(e,t){if(!t)return;this.processedTriangles+=e;let n=performance.now();n-this.lastProgressUpdate<this.progressUpdateInterval||(this.lastProgressUpdate=n,t(Math.min(Math.floor(this.processedTriangles/this.totalTriangles*100),99)))}buildNodeRecursiveToDepth(e,t,n,r,i,o,s,c,l,u,d){let f=new a;this.totalNodes++;let p=t-e;if(o===void 0?this.updateNodeBounds(f,e,t):(f.minX=o,f.minY=s,f.minZ=c,f.maxX=l,f.maxY=u,f.maxZ=d),p<=this.maxLeafSize||n<=0)return f.triangleOffset=e,f.triangleCount=p,this.updateProgress(p,i),f;if(r<=0&&p>this.maxLeafSize*16){let r=this.frontierTasks.length;return f.triangleOffset=e,f.triangleCount=p,f.isFrontier=!0,f.frontierTaskId=r,this.frontierTasks.push({taskId:r,start:e,end:t,depth:n,preMinX:f.minX,preMinY:f.minY,preMinZ:f.minZ,preMaxX:f.maxX,preMaxY:f.maxY,preMaxZ:f.maxZ}),f}let m=this.findBestSplitPositionSAH(e,t,f);if(!m.success){if(this.splitStats.failedSplits++,r>0||p<=this.maxLeafSize*16)return f.triangleOffset=e,f.triangleCount=p,this.updateProgress(p,i),f;let a=this.frontierTasks.length;return f.triangleOffset=e,f.triangleCount=p,f.isFrontier=!0,f.frontierTaskId=a,this.frontierTasks.push({taskId:a,start:e,end:t,depth:n,preMinX:f.minX,preMinY:f.minY,preMinZ:f.minZ,preMaxX:f.maxX,preMaxY:f.maxY,preMaxZ:f.maxZ}),f}m.method===`SAH`?this.splitStats.sahSplits++:m.method===`object_median`?this.splitStats.objectMedianSplits++:m.method===`spatial_median`&&this.splitStats.spatialMedianSplits++,this.partitionWithBounds(e,t,m.axis,m.pos);let h=this._partResult,g=h.mid,_=h.lMinX,v=h.lMinY,y=h.lMinZ,b=h.lMaxX,x=h.lMaxY,S=h.lMaxZ,C=h.rMinX,w=h.rMinY,T=h.rMinZ,E=h.rMaxX,D=h.rMaxY,O=h.rMaxZ;return g===e||g===t?(f.triangleOffset=e,f.triangleCount=p,this.updateProgress(p,i),f):(f.leftChild=this.buildNodeRecursiveToDepth(e,g,n-1,r-1,i,_,v,y,b,x,S),f.rightChild=this.buildNodeRecursiveToDepth(g,t,n-1,r-1,i,C,w,T,E,D,O),f)}buildNodeRecursive(e,t,n,r,i,o,s,c,l,u){let d=new a;this.totalNodes++;let f=t-e;if(i===void 0?this.updateNodeBounds(d,e,t):(d.minX=i,d.minY=o,d.minZ=s,d.maxX=c,d.maxY=l,d.maxZ=u),f<=this.maxLeafSize||n<=0)return d.triangleOffset=e,d.triangleCount=f,this.updateProgress(f,r),d;let p=this.findBestSplitPositionSAH(e,t,d);if(!p.success)return this.splitStats.failedSplits++,d.triangleOffset=e,d.triangleCount=f,this.updateProgress(f,r),d;p.method===`SAH`?this.splitStats.sahSplits++:p.method===`object_median`?this.splitStats.objectMedianSplits++:p.method===`spatial_median`&&this.splitStats.spatialMedianSplits++,this.partitionWithBounds(e,t,p.axis,p.pos);let m=this._partResult,h=m.mid,g=m.lMinX,_=m.lMinY,v=m.lMinZ,y=m.lMaxX,b=m.lMaxY,x=m.lMaxZ,S=m.rMinX,C=m.rMinY,w=m.rMinZ,T=m.rMaxX,E=m.rMaxY,D=m.rMaxZ;return h===e||h===t?(d.triangleOffset=e,d.triangleCount=f,this.updateProgress(f,r),d):(d.leftChild=this.buildNodeRecursive(e,h,n-1,r,g,_,v,y,b,x),d.rightChild=this.buildNodeRecursive(h,t,n-1,r,S,C,w,T,E,D),d)}partitionWithBounds(e,t,n,r){let i=this.indices,a=this.centroids,o=this.bMin,s=this.bMax,c=e,l=t-1,u=1/0,d=1/0,f=1/0,p=-1/0,m=-1/0,h=-1/0,g=1/0,_=1/0,v=1/0,y=-1/0,b=-1/0,x=-1/0;for(;c<=l;){let e=i[c],t=e*3;a[t+n]<=r?(o[t]<u&&(u=o[t]),o[t+1]<d&&(d=o[t+1]),o[t+2]<f&&(f=o[t+2]),s[t]>p&&(p=s[t]),s[t+1]>m&&(m=s[t+1]),s[t+2]>h&&(h=s[t+2]),c++):(o[t]<g&&(g=o[t]),o[t+1]<_&&(_=o[t+1]),o[t+2]<v&&(v=o[t+2]),s[t]>y&&(y=s[t]),s[t+1]>b&&(b=s[t+1]),s[t+2]>x&&(x=s[t+2]),i[c]=i[l],i[l]=e,l--)}let S=this._partResult;return S.mid=c,S.lMinX=u,S.lMinY=d,S.lMinZ=f,S.lMaxX=p,S.lMaxY=m,S.lMaxZ=h,S.rMinX=g,S.rMinY=_,S.rMinZ=v,S.rMaxX=y,S.rMaxY=b,S.rMaxZ=x,S}updateNodeBounds(e,t,n){let r=1/0,i=1/0,a=1/0,o=-1/0,s=-1/0,c=-1/0,l=this.indices,u=this.bMin,d=this.bMax;for(let e=t;e<n;e++){let t=l[e]*3;u[t]<r&&(r=u[t]),u[t+1]<i&&(i=u[t+1]),u[t+2]<a&&(a=u[t+2]),d[t]>o&&(o=d[t]),d[t+1]>s&&(s=d[t+1]),d[t+2]>c&&(c=d[t+2])}e.minX=r,e.minY=i,e.minZ=a,e.maxX=o,e.maxY=s,e.maxZ=c}findBestSplitPositionSAH(e,t,n){let r=1/0,i=-1,a=0,o=this.computeSurfaceAreaFlat(n.minX,n.minY,n.minZ,n.maxX,n.maxY,n.maxZ),s=t-e,c=this.intersectionCost*s,l=this.getOptimalBinCount(s);this.splitStats.totalSplitAttempts++,this.splitStats.avgBinsUsed=(this.splitStats.avgBinsUsed*(this.splitStats.totalSplitAttempts-1)+l)/this.splitStats.totalSplitAttempts;let u=this.indices,d=this.centroids,f=this.bMin,p=this.bMax,m=this.binBoundsMin,h=this.binBoundsMax,g=this.binCounts,_=this.leftPrefixMin,v=this.leftPrefixMax,y=this.leftPrefixCount,b=this.rightPrefixMin,x=this.rightPrefixMax,S=this.rightPrefixCount,C=1/0,w=-1/0,T=1/0,E=-1/0,D=1/0,O=-1/0;for(let n=e;n<t;n++){let e=u[n]*3,t=d[e],r=d[e+1],i=d[e+2];t<C&&(C=t),t>w&&(w=t),r<T&&(T=r),r>E&&(E=r),i<D&&(D=i),i>O&&(O=i)}let k=[C,T,D],A=[w,E,O];for(let n=0;n<3;n++){let s=k[n],C=A[n];if(C-s<1e-6)continue;for(let e=0;e<l;e++){g[e]=0;let t=e*3;m[t]=1/0,m[t+1]=1/0,m[t+2]=1/0,h[t]=-1/0,h[t+1]=-1/0,h[t+2]=-1/0}let w=l/(C-s);for(let r=e;r<t;r++){let e=u[r],t=d[e*3+n],i=Math.floor((t-s)*w);i>=l&&(i=l-1),g[i]++;let a=i*3,o=e*3;f[o]<m[a]&&(m[a]=f[o]),f[o+1]<m[a+1]&&(m[a+1]=f[o+1]),f[o+2]<m[a+2]&&(m[a+2]=f[o+2]),p[o]>h[a]&&(h[a]=p[o]),p[o+1]>h[a+1]&&(h[a+1]=p[o+1]),p[o+2]>h[a+2]&&(h[a+2]=p[o+2])}y[0]=g[0],_[0]=m[0],_[1]=m[1],_[2]=m[2],v[0]=h[0],v[1]=h[1],v[2]=h[2];for(let e=1;e<l;e++){let t=e*3,n=(e-1)*3;y[e]=y[e-1]+g[e];let r=_[n],i=m[t],a=_[n+1],o=m[t+1],s=_[n+2],c=m[t+2];_[t]=r<i?r:i,_[t+1]=a<o?a:o,_[t+2]=s<c?s:c;let l=v[n],u=h[t],d=v[n+1],f=h[t+1],p=v[n+2],b=h[t+2];v[t]=l>u?l:u,v[t+1]=d>f?d:f,v[t+2]=p>b?p:b}let T=l-1,E=T*3;S[T]=g[T],b[E]=m[E],b[E+1]=m[E+1],b[E+2]=m[E+2],x[E]=h[E],x[E+1]=h[E+1],x[E+2]=h[E+2];for(let e=T-1;e>=0;e--){let t=e*3,n=(e+1)*3;S[e]=S[e+1]+g[e];let r=b[n],i=m[t],a=b[n+1],o=m[t+1],s=b[n+2],c=m[t+2];b[t]=r<i?r:i,b[t+1]=a<o?a:o,b[t+2]=s<c?s:c;let l=x[n],u=h[t],d=x[n+1],f=h[t+1],p=x[n+2],_=h[t+2];x[t]=l>u?l:u,x[t+1]=d>f?d:f,x[t+2]=p>_?p:_}for(let e=1;e<l;e++){let t=(e-1)*3,u=e*3,d=y[e-1],f=S[e];if(d===0||f===0)continue;let p=v[t]-_[t],m=v[t+1]-_[t+1],h=v[t+2]-_[t+2],g=2*(p*m+m*h+h*p),w=x[u]-b[u],T=x[u+1]-b[u+1],E=x[u+2]-b[u+2],D=2*(w*T+T*E+E*w),O=this.traversalCost+g/o*d*this.intersectionCost+D/o*f*this.intersectionCost;O<r&&O<c&&(r=O,i=n,a=s+(C-s)*e/l)}}return i===-1?this.enableObjectMedianFallback?this.findObjectMedianSplit(e,t):this.enableSpatialMedianFallback?this.findSpatialMedianSplit(e,t):{success:!1,method:`fallbacks_disabled`}:{success:!0,axis:i,pos:a,method:`SAH`,binsUsed:l}}findObjectMedianSplit(e,t){let n=this.indices,r=this.centroids,i=-1,a=-1;for(let o=0;o<3;o++){let s=1/0,c=-1/0;for(let i=e;i<t;i++){let e=r[n[i]*3+o];e<s&&(s=e),e>c&&(c=e)}let l=c-s;l>a&&(a=l,i=o)}if(i===-1||a<1e-10)return this.enableSpatialMedianFallback?this.findSpatialMedianSplit(e,t):{success:!1,method:`object_median_failed`};let o=t-e,s=e+Math.floor(o/2);this.quickselect(e,t,s,i);let c=r[n[s]*3+i],l=!0;for(let e=s+1;e<t;e++)if(r[n[e]*3+i]>c){l=!1;break}if(l){let a=-1/0;for(let t=e;t<s;t++){let e=r[n[t]*3+i];e>a&&(a=e)}if(a<c)c=(a+c)*.5;else return this.enableSpatialMedianFallback?this.findSpatialMedianSplit(e,t):{success:!1,method:`object_median_degenerate`}}return{success:!0,axis:i,pos:c,method:`object_median`}}findSpatialMedianSplit(e,t){let n=this.indices,r=this.centroids,i=this.bMin,a=this.bMax,o=-1,s=-1,c=0,l=0;for(let r=0;r<3;r++){let u=1/0,d=-1/0;for(let o=e;o<t;o++){let e=n[o]*3+r;i[e]<u&&(u=i[e]),a[e]>d&&(d=a[e])}let f=d-u;f>s&&(s=f,o=r,c=u,l=d)}if(o===-1||s<1e-12)return{success:!1,method:`spatial_median_failed`};let u=(c+l)*.5,d=t-e,f=0;for(let i=e;i<t;i++)r[n[i]*3+o]<=u&&f++;if(f===0||f===d){let i=e+Math.floor(d/2);this.quickselect(e,t,i,o);let a=r[n[i]*3+o],s=!0;for(let i=e;i<t;i++)if(r[n[i]*3+o]!==a){s=!1;break}if(s)return{success:!1,method:`spatial_median_degenerate`};let c=-1/0;for(let t=e;t<i;t++){let e=r[n[t]*3+o];e>c&&(c=e)}if(c<a)u=(c+a)*.5;else{let e=1/0;for(let a=i+1;a<t;a++){let t=r[n[a]*3+o];t<e&&(e=t)}u=(a+e)*.5}}return{success:!0,axis:o,pos:u,method:`spatial_median`}}quickselect(e,t,n,r){let i=this.indices,a=this.centroids,o=e,s=t-1;for(;o<s;){let e=o+s>>>1,t=a[i[o]*3+r],c=a[i[e]*3+r],l=a[i[s]*3+r];if(t>c){let t=i[o];i[o]=i[e],i[e]=t}if(t>l){let e=i[o];i[o]=i[s],i[s]=e}if(c>l){let t=i[e];i[e]=i[s],i[s]=t}let u=a[i[e]*3+r],d=o,f=s;for(;d<=f;){for(;a[i[d]*3+r]<u;)d++;for(;a[i[f]*3+r]>u;)f--;if(d<=f){let e=i[d];i[d]=i[f],i[f]=e,d++,f--}}f<n&&(o=d),d>n&&(s=f)}}applySAOrdering(e){if(!e||!e.leftChild)return;let t=[e],n=[];for(;t.length>0;){let e=t.pop();!e.leftChild||!e.rightChild||(n.push(e),t.push(e.leftChild),t.push(e.rightChild))}for(let e=n.length-1;e>=0;e--){let t=n[e],r=t.leftChild,i=t.rightChild,a=r.maxX-r.minX,o=r.maxY-r.minY,s=r.maxZ-r.minZ,c=i.maxX-i.minX,l=i.maxY-i.minY,u=i.maxZ-i.minZ;c*l+l*u+u*c>a*o+o*s+s*a&&(t.leftChild=i,t.rightChild=r)}}flattenBVH(e){let t=[],n=[e];for(;n.length>0;){let e=n.pop();e._flatIndex=t.length,t.push(e),e.rightChild&&n.push(e.rightChild),e.leftChild&&n.push(e.leftChild)}let r=new Float32Array(t.length*16);for(let e=0;e<t.length;e++){let n=t[e],i=e*16;if(n.leftChild){let e=n.leftChild,t=n.rightChild;r[i]=e.minX,r[i+1]=e.minY,r[i+2]=e.minZ,r[i+3]=e._flatIndex,r[i+4]=e.maxX,r[i+5]=e.maxY,r[i+6]=e.maxZ,r[i+7]=t._flatIndex,r[i+8]=t.minX,r[i+9]=t.minY,r[i+10]=t.minZ,r[i+12]=t.maxX,r[i+13]=t.maxY,r[i+14]=t.maxZ}else r[i]=n.triangleOffset,r[i+1]=n.triangleCount,r[i+3]=-1}return r}flattenBVHWithFrontier(e){let t=[],n=[e];for(;n.length>0;){let e=n.pop();e._flatIndex=t.length,t.push(e),e.rightChild&&n.push(e.rightChild),e.leftChild&&n.push(e.leftChild)}let r=new Float32Array(t.length*16),i=[];for(let e=0;e<t.length;e++){let n=t[e],a=e*16;if(n.leftChild){let e=n.leftChild,t=n.rightChild;r[a]=e.minX,r[a+1]=e.minY,r[a+2]=e.minZ,r[a+3]=e._flatIndex,r[a+4]=e.maxX,r[a+5]=e.maxY,r[a+6]=e.maxZ,r[a+7]=t._flatIndex,r[a+8]=t.minX,r[a+9]=t.minY,r[a+10]=t.minZ,r[a+12]=t.maxX,r[a+13]=t.maxY,r[a+14]=t.maxZ}else if(n.isFrontier){let t=n.frontierTaskId;r[a]=n.triangleOffset,r[a+1]=n.triangleCount,r[a+2]=t,r[a+3]=-2,i.push({taskId:t,flatIndex:e})}else r[a]=n.triangleOffset,r[a+1]=n.triangleCount,r[a+3]=-1}return{flatData:r,frontierMap:i,nodeCount:t.length}}assembleParallelBVH(e,t,n,r){let i=[...r].sort((e,t)=>e.taskId-t.taskId),a=t;for(let e=0;e<i.length;e++)a+=i[e].nodeCount;let o=new Float32Array(a*16);o.set(e);let s=new Map;for(let e of n)s.set(e.taskId,e.flatIndex);let c=t;for(let e=0;e<i.length;e++){let t=i[e],n=t.flatData,r=t.nodeCount,a=c*16;o.set(n,a);for(let e=0;e<r;e++){let t=a+e*16;o[t+3]!==-1&&(o[t+3]+=c,o[t+7]+=c)}let l=s.get(t.taskId);if(l!==void 0){let e=l*16,t=a;for(let n=0;n<16;n++)o[e+n]=o[t+n]}c+=r}return o}computeSurfaceAreaFlat(e,t,n,r,i,a){let o=r-e,s=i-t,c=a-n;return 2*(o*s+s*c+c*o)}};self.onmessage=function(t){let{tasks:r,sharedTriangleData:i,sharedCentroids:a,sharedBMin:s,sharedBMax:c,sharedIndices:l,triangleCount:u,maxLeafSize:d,numBins:f,maxBins:p,minBins:m,treeletConfig:h,reinsertionConfig:g,reportProgress:_}=t.data;for(let t=0;t<r.length;t++){let v=r[t];try{let t=new o;t.maxLeafSize=d,t.numBins=f,t.maxBins=p,t.minBins=m,t.triangles=new Float32Array(i),t.centroids=new Float32Array(a),t.bMin=new Float32Array(s),t.bMax=new Float32Array(c),t.indices=new Uint32Array(l),t.totalTriangles=u,t.totalNodes=0,t.processedTriangles=0,t.lastProgressUpdate=performance.now(),t.splitStats={sahSplits:0,objectMedianSplits:0,spatialMedianSplits:0,failedSplits:0,avgBinsUsed:0,totalSplitAttempts:0,mortonSortTime:0,totalBuildTime:0,treeletOptimizationTime:0,treeletsProcessed:0,treeletsImproved:0,averageSAHImprovement:0,initTime:0,sahBuildTime:0,reorderTime:0};let r=_?e=>{self.postMessage({type:`progress`,taskId:v.taskId,progress:e})}:null,y=performance.now(),b=t.buildNodeRecursive(v.start,v.end,v.depth,r,v.preMinX,v.preMinY,v.preMinZ,v.preMaxX,v.preMaxY,v.preMaxZ);if(h&&h.enabled&&v.end-v.start>1e3){let n=v.end-v.start>5e4,r=n?3:h.size||5,i=n?10:20,a=new e(t.traversalCost,t.intersectionCost);a.setTreeletSize(r),a.setMinImprovement(h.minImprovement||.02),a.setMaxTreelets(i);let o=h.passes||1;for(let e=0;e<o;e++)try{a.optimizeBVH(b,null)}catch(t){console.error(`[BVHSubtreeWorker] Treelet pass ${e+1} error:`,t);break}}if(g&&g.enabled&&v.end-v.start>1e3){let e=new n(t.traversalCost,t.intersectionCost);g.batchSizeRatio&&e.setBatchSizeRatio(g.batchSizeRatio),g.maxIterations&&e.setMaxIterations(g.maxIterations);try{e.optimizeBVH(b,null)}catch(e){console.error(`[BVHSubtreeWorker] Reinsertion error:`,e)}}t.applySAOrdering(b);let x=t.flattenBVH(b),S=x.length/16,C=performance.now()-y;console.log(`[BVHSubtreeWorker] Task ${v.taskId}: ${(v.end-v.start).toLocaleString()} triangles, ${S} nodes, ${Math.round(C)}ms`),self.postMessage({type:`subtreeResult`,taskId:v.taskId,flatData:x,nodeCount:S},[x.buffer])}catch(e){console.error(`[BVHSubtreeWorker] Task ${v.taskId} error:`,e),self.postMessage({type:`error`,taskId:v.taskId,error:e.message})}}}})();
2
- //# sourceMappingURL=BVHSubtreeWorker-BoG4D6dP.js.map