rayzee 7.17.0 → 7.19.0

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.
Files changed (47) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/BVHSubtreeWorker-D7KMbrQ-.js +2 -0
  3. package/dist/assets/BVHSubtreeWorker-D7KMbrQ-.js.map +1 -0
  4. package/dist/assets/BVHWorker-DOaGn6uN.js +2 -0
  5. package/dist/assets/BVHWorker-DOaGn6uN.js.map +1 -0
  6. package/dist/rayzee.es.js +2388 -2138
  7. package/dist/rayzee.es.js.map +1 -1
  8. package/dist/rayzee.umd.js +49 -49
  9. package/dist/rayzee.umd.js.map +1 -1
  10. package/package.json +1 -1
  11. package/src/EngineDefaults.js +7 -6
  12. package/src/Passes/OIDNDenoiser.js +17 -14
  13. package/src/PathTracerApp.js +63 -56
  14. package/src/Processor/BVHBuilder.js +11 -7
  15. package/src/Processor/BuildTimer.js +20 -6
  16. package/src/Processor/EmissiveTriangleBuilder.js +11 -7
  17. package/src/Processor/GeometryExtractor.js +11 -5
  18. package/src/Processor/KernelManager.js +48 -1
  19. package/src/Processor/PackedRayBuffer.js +20 -12
  20. package/src/Processor/ParallelBVHBuilder.js +9 -6
  21. package/src/Processor/QueueManager.js +6 -8
  22. package/src/Processor/SceneProcessor.js +47 -21
  23. package/src/Processor/ShaderBuilder.js +4 -1
  24. package/src/Processor/StorageTexturePool.js +4 -1
  25. package/src/Processor/Workers/BVHWorker.js +12 -6
  26. package/src/Processor/utils.js +30 -0
  27. package/src/RenderSettings.js +1 -1
  28. package/src/SceneHelpers.js +11 -1
  29. package/src/Stages/PathTracer.js +15 -4
  30. package/src/Stages/PathTracerStage.js +17 -14
  31. package/src/TSL/FinalWriteKernel.js +11 -6
  32. package/src/TSL/ShadeKernel.js +46 -24
  33. package/src/TSL/TextureSampling.js +12 -2
  34. package/src/index.js +3 -0
  35. package/src/managers/EnvironmentManager.js +12 -8
  36. package/src/managers/MaterialDataManager.js +12 -9
  37. package/src/managers/OverlayManager.js +105 -48
  38. package/src/managers/UniformManager.js +1 -1
  39. package/src/managers/helpers/OutlineHelper.js +42 -40
  40. package/src/managers/helpers/TransformGizmoHelper.js +61 -0
  41. package/src/managers/helpers/ViewOverlayRenderer.js +140 -0
  42. package/src/managers/helpers/overlaySurface.js +53 -0
  43. package/src/utils/Logger.js +291 -0
  44. package/dist/assets/BVHSubtreeWorker-sNzvxn66.js +0 -2
  45. package/dist/assets/BVHSubtreeWorker-sNzvxn66.js.map +0 -1
  46. package/dist/assets/BVHWorker-CiVdFrwe.js +0 -2
  47. package/dist/assets/BVHWorker-CiVdFrwe.js.map +0 -1
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Shared plumbing for the overlay canvases stacked over the main WebGPU canvas
3
+ * (the 3D scene layer and the 2D HUD). Both must agree on positioning and on
4
+ * how an on-screen box maps to device pixels, so both come from here.
5
+ */
6
+
7
+ /** Beyond 2× the extra pixels are invisible but the cost is not. */
8
+ export const MAX_VIEW_DPR = 2;
9
+
10
+ /**
11
+ * Creates a transparent, click-through canvas that covers its container.
12
+ * @param {boolean} [hidden=false] - Start parked (`display:none`)
13
+ * @returns {HTMLCanvasElement}
14
+ */
15
+ export function createOverlayCanvas( hidden = false ) {
16
+
17
+ const canvas = document.createElement( 'canvas' );
18
+ canvas.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;'
19
+ + ( hidden ? 'display:none;' : '' );
20
+ return canvas;
21
+
22
+ }
23
+
24
+ /**
25
+ * Device-pixel size of an element as it actually appears on screen. Uses the
26
+ * bounding rect (not clientWidth) so the viewport's CSS zoom is included.
27
+ *
28
+ * @param {HTMLElement} element
29
+ * @param {number} [maxDim=Infinity] - Per-axis ceiling; the box is scaled down uniformly to fit
30
+ * @returns {{ width: number, height: number }|null} null when the element has no layout box
31
+ */
32
+ export function viewPixelSize( element, maxDim = Infinity ) {
33
+
34
+ const rect = element?.getBoundingClientRect();
35
+ if ( ! rect || rect.width === 0 || rect.height === 0 ) return null;
36
+
37
+ const dpr = Math.min( window.devicePixelRatio || 1, MAX_VIEW_DPR );
38
+ let width = Math.round( rect.width * dpr );
39
+ let height = Math.round( rect.height * dpr );
40
+
41
+ // Uniform scale — a per-axis clamp would skew the aspect and slide the
42
+ // helpers out of alignment with the rendered image underneath.
43
+ const clamp = maxDim / Math.max( width, height );
44
+ if ( clamp < 1 ) {
45
+
46
+ width = Math.max( 1, Math.round( width * clamp ) );
47
+ height = Math.max( 1, Math.round( height * clamp ) );
48
+
49
+ }
50
+
51
+ return { width, height };
52
+
53
+ }
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Leveled, namespaced console logger shared by the engine, the app and the workers.
3
+ *
4
+ * The engine prints one summary line per startup phase at `info`; every per-detail
5
+ * line sits at `debug` so a normal load stays readable and nothing is lost.
6
+ *
7
+ * Active level, first match wins:
8
+ * 1. globalThis.__RAYZEE_LOG_LEVEL__ — set by Logger.setLevel(), forwarded to workers
9
+ * 2. localStorage.rayzeeLogLevel — survives reloads (main thread only)
10
+ * 3. 'info'
11
+ *
12
+ * From the devtools console:
13
+ * rayzee.log.setLevel( 'debug' ) // everything, including per-mesh/per-kernel detail
14
+ * rayzee.log.setLevel( 'warn' ) // problems only
15
+ * rayzee.log.only( 'bvh', 'gpu' ) // debug, limited to these namespaces
16
+ * rayzee.log.only() // clear the namespace filter
17
+ */
18
+
19
+ export const LOG_LEVELS = { silent: 0, error: 1, warn: 2, info: 3, debug: 4 };
20
+
21
+ const DEFAULT_LEVEL = 'info';
22
+ const STORAGE_KEY = 'rayzeeLogLevel';
23
+ const NS_STORAGE_KEY = 'rayzeeLogNamespaces';
24
+
25
+ const PREFIX_STYLE = 'color:#7c8494';
26
+ const RESET_STYLE = 'color:inherit';
27
+
28
+ // Cached so a hot log site is a numeric compare, not a storage read.
29
+ let activeLevel = null;
30
+ let namespaceFilter = null;
31
+
32
+ function readStorage( key ) {
33
+
34
+ try {
35
+
36
+ return globalThis.localStorage?.getItem( key ) ?? null;
37
+
38
+ } catch {
39
+
40
+ return null; // private mode, or a worker without storage access
41
+
42
+ }
43
+
44
+ }
45
+
46
+ function writeStorage( key, value ) {
47
+
48
+ try {
49
+
50
+ if ( value === null ) globalThis.localStorage?.removeItem( key );
51
+ else globalThis.localStorage?.setItem( key, value );
52
+
53
+ } catch {
54
+
55
+ // non-fatal: the level still applies for this session
56
+
57
+ }
58
+
59
+ }
60
+
61
+ function resolveLevel() {
62
+
63
+ if ( activeLevel !== null ) return activeLevel;
64
+
65
+ const fromGlobal = globalThis.__RAYZEE_LOG_LEVEL__;
66
+ if ( fromGlobal !== undefined && fromGlobal in LOG_LEVELS ) {
67
+
68
+ activeLevel = LOG_LEVELS[ fromGlobal ];
69
+ return activeLevel;
70
+
71
+ }
72
+
73
+ const fromStorage = readStorage( STORAGE_KEY );
74
+ activeLevel = LOG_LEVELS[ fromStorage ] ?? LOG_LEVELS[ DEFAULT_LEVEL ];
75
+ return activeLevel;
76
+
77
+ }
78
+
79
+ function resolveNamespaceFilter() {
80
+
81
+ if ( namespaceFilter !== null ) return namespaceFilter;
82
+
83
+ const raw = globalThis.__RAYZEE_LOG_NS__ ?? readStorage( NS_STORAGE_KEY );
84
+ namespaceFilter = raw ? String( raw ).split( ',' ).map( s => s.trim() ).filter( Boolean ) : [];
85
+ return namespaceFilter;
86
+
87
+ }
88
+
89
+ // The namespace filter only narrows `debug`; warnings and errors are never hidden by it.
90
+ function namespaceAllowed( ns, level ) {
91
+
92
+ if ( level < LOG_LEVELS.debug ) return true;
93
+ const filter = resolveNamespaceFilter();
94
+ return filter.length === 0 || filter.some( f => ns === f || ns.startsWith( `${f}:` ) );
95
+
96
+ }
97
+
98
+ function emit( level, ns, args ) {
99
+
100
+ if ( level > resolveLevel() || ! namespaceAllowed( ns, level ) ) return;
101
+
102
+ const sink = level === LOG_LEVELS.error ? console.error
103
+ : level === LOG_LEVELS.warn ? console.warn
104
+ : console.log;
105
+
106
+ const prefix = `[${ns}]`;
107
+
108
+ // Style only the prefix, and only when the first argument is a string we can splice into.
109
+ if ( typeof args[ 0 ] === 'string' ) {
110
+
111
+ sink( `%c${prefix}%c ${args[ 0 ]}`, PREFIX_STYLE, RESET_STYLE, ...args.slice( 1 ) );
112
+
113
+ } else {
114
+
115
+ sink( `%c${prefix}`, PREFIX_STYLE, ...args );
116
+
117
+ }
118
+
119
+ }
120
+
121
+ /**
122
+ * Formatters, so summary lines read consistently across the engine.
123
+ */
124
+ export const fmt = {
125
+
126
+ /** 18016 → "18,016" */
127
+ n: value => Number( value ).toLocaleString( 'en-US' ),
128
+
129
+ /** 481.27 → "481 ms"; sub-10ms keeps one decimal */
130
+ ms: value => ( value < 10 ? `${Number( value ).toFixed( 1 )} ms` : `${Math.round( value )} ms` ),
131
+
132
+ /** bytes → "592 MB"; sub-10MB keeps one decimal */
133
+ mb: bytes => {
134
+
135
+ const mb = bytes / ( 1024 * 1024 );
136
+ return mb < 10 ? `${mb.toFixed( 1 )} MB` : `${Math.round( mb )} MB`;
137
+
138
+ },
139
+
140
+ /** 512, 512 → "512×512" */
141
+ px: ( width, height ) => `${width}×${height}`,
142
+
143
+ /** 1, 'map' → "1 map"; 2, 'mesh', 'meshes' → "2 meshes" */
144
+ count: ( value, singular, plural = `${singular}s` ) =>
145
+ `${fmt.n( value )} ${Number( value ) === 1 ? singular : plural}`,
146
+
147
+ /** Joins the truthy parts of a summary line, so optional segments can be inlined as `cond && text`. */
148
+ list: parts => parts.filter( Boolean ).join( ' · ' ),
149
+
150
+ };
151
+
152
+ /**
153
+ * Global logger controls. Also reachable as `window.rayzee.log`.
154
+ */
155
+ export const Logger = {
156
+
157
+ levels: LOG_LEVELS,
158
+
159
+ /** @param {'silent'|'error'|'warn'|'info'|'debug'} name */
160
+ setLevel( name ) {
161
+
162
+ if ( ! ( name in LOG_LEVELS ) ) {
163
+
164
+ console.warn( `[log] unknown level '${name}' — expected one of ${Object.keys( LOG_LEVELS ).join( ', ' )}` );
165
+ return this.getLevel();
166
+
167
+ }
168
+
169
+ globalThis.__RAYZEE_LOG_LEVEL__ = name;
170
+ activeLevel = LOG_LEVELS[ name ];
171
+ writeStorage( STORAGE_KEY, name === DEFAULT_LEVEL ? null : name );
172
+ return name;
173
+
174
+ },
175
+
176
+ getLevel() {
177
+
178
+ const level = resolveLevel();
179
+ return Object.keys( LOG_LEVELS ).find( k => LOG_LEVELS[ k ] === level );
180
+
181
+ },
182
+
183
+ /** True when a level would actually print — use to skip expensive message construction. */
184
+ isEnabled( name ) {
185
+
186
+ return ( LOG_LEVELS[ name ] ?? LOG_LEVELS.debug ) <= resolveLevel();
187
+
188
+ },
189
+
190
+ /** Restrict `debug` to the given namespaces. No arguments clears the filter. */
191
+ only( ...namespaces ) {
192
+
193
+ const list = namespaces.flat().filter( Boolean );
194
+ namespaceFilter = list;
195
+ globalThis.__RAYZEE_LOG_NS__ = list.join( ',' );
196
+ writeStorage( NS_STORAGE_KEY, list.length ? list.join( ',' ) : null );
197
+ if ( list.length ) this.setLevel( 'debug' );
198
+ return list;
199
+
200
+ },
201
+
202
+ /** Drops the cached level so the next log re-reads globals/storage. */
203
+ refresh() {
204
+
205
+ activeLevel = null;
206
+ namespaceFilter = null;
207
+ return this.getLevel();
208
+
209
+ },
210
+
211
+ };
212
+
213
+ /**
214
+ * @param {string} namespace - short lowercase channel, e.g. 'bvh', 'gpu', 'scene'
215
+ */
216
+ export function createLogger( namespace ) {
217
+
218
+ return {
219
+
220
+ namespace,
221
+
222
+ error: ( ...args ) => emit( LOG_LEVELS.error, namespace, args ),
223
+ warn: ( ...args ) => emit( LOG_LEVELS.warn, namespace, args ),
224
+ info: ( ...args ) => emit( LOG_LEVELS.info, namespace, args ),
225
+ debug: ( ...args ) => emit( LOG_LEVELS.debug, namespace, args ),
226
+
227
+ isEnabled: name => Logger.isEnabled( name ),
228
+
229
+ /**
230
+ * One-line `info` summary with the detail lines folded into a collapsed group,
231
+ * so a normal load shows a single row per phase but the breakdown is one click away.
232
+ * @param {string} headline
233
+ * @param {string[]} [details]
234
+ */
235
+ summary( headline, details = [] ) {
236
+
237
+ const lines = details.filter( Boolean );
238
+
239
+ if ( lines.length === 0 || ! console.groupCollapsed ) {
240
+
241
+ emit( LOG_LEVELS.info, namespace, [ headline ] );
242
+ lines.forEach( line => emit( LOG_LEVELS.debug, namespace, [ line ] ) );
243
+ return;
244
+
245
+ }
246
+
247
+ if ( LOG_LEVELS.info > resolveLevel() ) return;
248
+
249
+ // Expanded at debug, folded otherwise.
250
+ const open = resolveLevel() >= LOG_LEVELS.debug && console.group;
251
+ ( open ? console.group : console.groupCollapsed )(
252
+ `%c[${namespace}]%c ${headline}`, PREFIX_STYLE, RESET_STYLE
253
+ );
254
+ lines.forEach( line => console.log( line ) );
255
+ console.groupEnd();
256
+
257
+ },
258
+
259
+ };
260
+
261
+ }
262
+
263
+ /** Warn once per key — for per-material/per-mesh conditions that would otherwise repeat. */
264
+ const seenOnce = new Set();
265
+
266
+ export function warnOnce( logger, key, ...args ) {
267
+
268
+ if ( seenOnce.has( key ) ) return;
269
+ seenOnce.add( key );
270
+ logger.warn( ...args );
271
+
272
+ }
273
+
274
+ /** Level name to hand to a worker so its `debug` lines follow the main thread. */
275
+ export function workerLogLevel() {
276
+
277
+ return Logger.getLevel();
278
+
279
+ }
280
+
281
+ /** Applies a level inside a worker, where globals and storage are separate. */
282
+ export function applyWorkerLogLevel( name ) {
283
+
284
+ if ( name && name in LOG_LEVELS ) {
285
+
286
+ globalThis.__RAYZEE_LOG_LEVEL__ = name;
287
+ activeLevel = LOG_LEVELS[ name ];
288
+
289
+ }
290
+
291
+ }
@@ -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}};async function r(e){let t=e instanceof URL?e.href:e,n=await fetch(t);if(!n.ok)throw Error(`Failed to fetch worker script: ${n.status}`);let r=new Blob([await n.text()],{type:`application/javascript`});return new Worker(URL.createObjectURL(r),{type:`module`})}var i=``+new URL(`BVHWorker-CiVdFrwe.js`,self.location.href).href;let a={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},o=a.FLOATS_PER_TRIANGLE;var s=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}},c=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=a.POSITION_A_OFFSET,r=a.POSITION_B_OFFSET,i=a.POSITION_C_OFFSET;for(let a=0;a<e;a++){let e=a*o,s=t[e+n],c=t[e+n+1],l=t[e+n+2],u=t[e+r],d=t[e+r+1],f=t[e+r+2],p=t[e+i],m=t[e+i+1],h=t[e+i+2],g=a*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[a]=a}}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/(o*4),this.processedTriangles=0,this.lastProgressUpdate=performance.now(),this.useWorker&&typeof Worker<`u`?new Promise((a,s)=>{let c=r=>{let i=this.totalTriangles,c=typeof SharedArrayBuffer<`u`;console.log(`[BVHBuilder] SharedArrayBuffer: ${c?`enabled`:`unavailable (using transfer fallback)`}`);let l=c?new SharedArrayBuffer(i*o*4):null;r.onmessage=e=>{let{bvhData:t,triangles:i,originalToBvh:o,error:c,progress:u,treeletStats:d}=e.data;if(c){r.terminate(),s(Error(c));return}if(u!==void 0&&n){n(u);return}d&&(this.splitStats=d),r.terminate(),a({bvhData:t,bvhRoot:!0,reorderedTriangles:l?new Float32Array(l):i,originalToBvh:o||null})},r.onerror=e=>{r.terminate(),s(e)};let u=e.buffer,d={triangleData:u,triangleByteOffset:e.byteOffset,triangleByteLength:e.byteLength,triangleCount:i,depth:t,reportProgress:!!n,sharedReorderBuffer:l,treeletOptimization:{enabled:this.enableTreeletOptimization,size:this.treeletSize,passes:this.treeletOptimizationPasses,minImprovement:this.treeletMinImprovement},reinsertionOptimization:{enabled:this.enableReinsertionOptimization,batchSizeRatio:this.reinsertionBatchSizeRatio,maxIterations:this.reinsertionMaxIterations}};r.postMessage(d,[u])};try{c(new Worker(i,{type:`module`}))}catch(o){o.name===`SecurityError`?r(i).then(c).catch(()=>{console.warn(`Worker fetch fallback failed, using synchronous build`),a(this._buildSyncAndFlatten(e,t,n))}):(console.warn(`Worker creation failed, falling back to synchronous build:`,o),a(this._buildSyncAndFlatten(e,t,n)))}}):new Promise(r=>{r(this._buildSyncAndFlatten(e,t,n))})}_buildSyncAndFlatten(e,t,n){let r=this.buildSync(e,t,n);return{bvhData:this.flattenBVH(r),bvhRoot:!0,reorderedTriangles:this.reorderedTriangleData||null,originalToBvh:this.originalToBvhMap||null}}buildSync(t,r=30,i=null,a=null){let s=performance.now();this.totalNodes=0,this.processedTriangles=0,this.triangles=t,this.totalTriangles=t.byteLength/(o*4),this.lastProgressUpdate=performance.now(),this.splitStats={sahSplits:0,objectMedianSplits:0,spatialMedianSplits:0,failedSplits:0,avgBinsUsed:0,totalSplitAttempts:0,mortonSortTime:0,totalBuildTime:0,treeletOptimizationTime:0,treeletsProcessed:0,treeletsImproved:0,averageSAHImprovement:0,reinsertionOptimizationTime:0,reinsertionsApplied:0,reinsertionIterations:0,saOrderTime:0,initTime:0,sahBuildTime:0,reorderTime:0};let c=this.totalTriangles,l=performance.now();this.centroids=new Float32Array(c*3),this.bMin=new Float32Array(c*3),this.bMax=new Float32Array(c*3),this.indices=new Uint32Array(c),this.mortonCodes=new Uint32Array(c),this.initializeTriangleArrays(),this.splitStats.initTime=performance.now()-l,this.sortTrianglesByMortonCode();let u=performance.now(),d=this.buildNodeRecursive(0,c,r,i);if(this.splitStats.sahBuildTime=performance.now()-u,this.enableTreeletOptimization&&this.totalTriangles>1e3){let t=this.totalTriangles>this.treeletComplexityThreshold,n=t?3:this.treeletSize,r=t?10:this.maxTreeletsPerScene,a=new e(this.traversalCost,this.intersectionCost);a.setTreeletSize(n),a.setMinImprovement(this.treeletMinImprovement),a.setMaxTreelets(r);let o=performance.now();for(let e=0;e<this.treeletOptimizationPasses;e++){let t=i?t=>{i(`Treelet optimization pass ${e+1}/${this.treeletOptimizationPasses}: ${t}`)}:null;try{a.optimizeBVH(d,t)}catch(t){console.error(`TreeletOptimizer: Error in pass ${e+1}:`,t);break}let n=a.getStatistics(),r=performance.now()-o;if(n.treeletsImproved===0&&e>0||r>15e3)break}let s=performance.now()-o;this.splitStats.treeletOptimizationTime=s;let c=a.getStatistics();this.splitStats.treeletsProcessed=c.treeletsProcessed,this.splitStats.treeletsImproved=c.treeletsImproved,this.splitStats.averageSAHImprovement=c.averageSAHImprovement}if(this.enableReinsertionOptimization&&this.totalTriangles>1e3){let e=new n(this.traversalCost,this.intersectionCost);e.setBatchSizeRatio(this.reinsertionBatchSizeRatio),e.setMaxIterations(this.reinsertionMaxIterations);let t=i?e=>{i(e)}:null;try{e.optimizeBVH(d,t)}catch(e){console.error(`ReinsertionOptimizer: Error:`,e)}let r=e.getStatistics();this.splitStats.reinsertionOptimizationTime=r.timeMs,this.splitStats.reinsertionsApplied=r.reinsertionsApplied,this.splitStats.reinsertionIterations=r.iterations}let f=performance.now();this.applySAOrdering(d),this.splitStats.saOrderTime=performance.now()-f;let p=performance.now(),m=this.triangles,h=a||new Float32Array(c*o);for(let e=0;e<c;e++){let t=this.indices[e]*o,n=e*o;h.set(m.subarray(t,t+o),n)}this.reorderedTriangleData=h;let g=new Uint32Array(c);for(let e=0;e<c;e++)g[this.indices[e]]=e;this.originalToBvhMap=g,this.splitStats.reorderTime=performance.now()-p,this.splitStats.totalBuildTime=performance.now()-s;let _=this.splitStats.totalBuildTime,v=this.splitStats;return console.log(`[BVH] ${c.toLocaleString()} tris → ${this.totalNodes} nodes in ${Math.round(_)}ms | SAH ${v.sahSplits} objMed ${v.objectMedianSplits} spatMed ${v.spatialMedianSplits} failed ${v.failedSplits}`+(v.treeletsProcessed?` | treelets ${v.treeletsImproved}/${v.treeletsProcessed} improved`:``)+(v.reinsertionsApplied?` | reinsertions ${v.reinsertionsApplied}`:``)),i&&i(100),this.centroids=null,this.bMin=null,this.bMax=null,this.mortonCodes=null,d}updateProgress(e,t){if(!t)return;this.processedTriangles+=e;let n=performance.now();n-this.lastProgressUpdate<this.progressUpdateInterval||(this.lastProgressUpdate=n,t(Math.min(Math.floor(this.processedTriangles/this.totalTriangles*100),99)))}buildNodeRecursiveToDepth(e,t,n,r,i,a,o,c,l,u,d){let f=new s;this.totalNodes++;let p=t-e;if(a===void 0?this.updateNodeBounds(f,e,t):(f.minX=a,f.minY=o,f.minZ=c,f.maxX=l,f.maxY=u,f.maxZ=d),p<=this.maxLeafSize||n<=0)return f.triangleOffset=e,f.triangleCount=p,this.updateProgress(p,i),f;if(r<=0&&p>this.maxLeafSize*16){let r=this.frontierTasks.length;return f.triangleOffset=e,f.triangleCount=p,f.isFrontier=!0,f.frontierTaskId=r,this.frontierTasks.push({taskId:r,start:e,end:t,depth:n,preMinX:f.minX,preMinY:f.minY,preMinZ:f.minZ,preMaxX:f.maxX,preMaxY:f.maxY,preMaxZ:f.maxZ}),f}let m=this.findBestSplitPositionSAH(e,t,f);if(!m.success){if(this.splitStats.failedSplits++,r>0||p<=this.maxLeafSize*16)return f.triangleOffset=e,f.triangleCount=p,this.updateProgress(p,i),f;let a=this.frontierTasks.length;return f.triangleOffset=e,f.triangleCount=p,f.isFrontier=!0,f.frontierTaskId=a,this.frontierTasks.push({taskId:a,start:e,end:t,depth:n,preMinX:f.minX,preMinY:f.minY,preMinZ:f.minZ,preMaxX:f.maxX,preMaxY:f.maxY,preMaxZ:f.maxZ}),f}m.method===`SAH`?this.splitStats.sahSplits++:m.method===`object_median`?this.splitStats.objectMedianSplits++:m.method===`spatial_median`&&this.splitStats.spatialMedianSplits++,this.partitionWithBounds(e,t,m.axis,m.pos);let h=this._partResult,g=h.mid,_=h.lMinX,v=h.lMinY,y=h.lMinZ,b=h.lMaxX,x=h.lMaxY,S=h.lMaxZ,C=h.rMinX,w=h.rMinY,T=h.rMinZ,E=h.rMaxX,D=h.rMaxY,O=h.rMaxZ;return g===e||g===t?(f.triangleOffset=e,f.triangleCount=p,this.updateProgress(p,i),f):(f.leftChild=this.buildNodeRecursiveToDepth(e,g,n-1,r-1,i,_,v,y,b,x,S),f.rightChild=this.buildNodeRecursiveToDepth(g,t,n-1,r-1,i,C,w,T,E,D,O),f)}buildNodeRecursive(e,t,n,r,i,a,o,c,l,u){let d=new s;this.totalNodes++;let f=t-e;if(i===void 0?this.updateNodeBounds(d,e,t):(d.minX=i,d.minY=a,d.minZ=o,d.maxX=c,d.maxY=l,d.maxZ=u),f<=this.maxLeafSize||n<=0)return d.triangleOffset=e,d.triangleCount=f,this.updateProgress(f,r),d;let p=this.findBestSplitPositionSAH(e,t,d);if(!p.success)return this.splitStats.failedSplits++,d.triangleOffset=e,d.triangleCount=f,this.updateProgress(f,r),d;p.method===`SAH`?this.splitStats.sahSplits++:p.method===`object_median`?this.splitStats.objectMedianSplits++:p.method===`spatial_median`&&this.splitStats.spatialMedianSplits++,this.partitionWithBounds(e,t,p.axis,p.pos);let m=this._partResult,h=m.mid,g=m.lMinX,_=m.lMinY,v=m.lMinZ,y=m.lMaxX,b=m.lMaxY,x=m.lMaxZ,S=m.rMinX,C=m.rMinY,w=m.rMinZ,T=m.rMaxX,E=m.rMaxY,D=m.rMaxZ;return h===e||h===t?(d.triangleOffset=e,d.triangleCount=f,this.updateProgress(f,r),d):(d.leftChild=this.buildNodeRecursive(e,h,n-1,r,g,_,v,y,b,x),d.rightChild=this.buildNodeRecursive(h,t,n-1,r,S,C,w,T,E,D),d)}partitionWithBounds(e,t,n,r){let i=this.indices,a=this.centroids,o=this.bMin,s=this.bMax,c=e,l=t-1,u=1/0,d=1/0,f=1/0,p=-1/0,m=-1/0,h=-1/0,g=1/0,_=1/0,v=1/0,y=-1/0,b=-1/0,x=-1/0;for(;c<=l;){let e=i[c],t=e*3;a[t+n]<=r?(o[t]<u&&(u=o[t]),o[t+1]<d&&(d=o[t+1]),o[t+2]<f&&(f=o[t+2]),s[t]>p&&(p=s[t]),s[t+1]>m&&(m=s[t+1]),s[t+2]>h&&(h=s[t+2]),c++):(o[t]<g&&(g=o[t]),o[t+1]<_&&(_=o[t+1]),o[t+2]<v&&(v=o[t+2]),s[t]>y&&(y=s[t]),s[t+1]>b&&(b=s[t+1]),s[t+2]>x&&(x=s[t+2]),i[c]=i[l],i[l]=e,l--)}let S=this._partResult;return S.mid=c,S.lMinX=u,S.lMinY=d,S.lMinZ=f,S.lMaxX=p,S.lMaxY=m,S.lMaxZ=h,S.rMinX=g,S.rMinY=_,S.rMinZ=v,S.rMaxX=y,S.rMaxY=b,S.rMaxZ=x,S}updateNodeBounds(e,t,n){let r=1/0,i=1/0,a=1/0,o=-1/0,s=-1/0,c=-1/0,l=this.indices,u=this.bMin,d=this.bMax;for(let e=t;e<n;e++){let t=l[e]*3;u[t]<r&&(r=u[t]),u[t+1]<i&&(i=u[t+1]),u[t+2]<a&&(a=u[t+2]),d[t]>o&&(o=d[t]),d[t+1]>s&&(s=d[t+1]),d[t+2]>c&&(c=d[t+2])}e.minX=r,e.minY=i,e.minZ=a,e.maxX=o,e.maxY=s,e.maxZ=c}findBestSplitPositionSAH(e,t,n){let r=1/0,i=-1,a=0,o=this.computeSurfaceAreaFlat(n.minX,n.minY,n.minZ,n.maxX,n.maxY,n.maxZ),s=t-e,c=this.intersectionCost*s,l=this.getOptimalBinCount(s);this.splitStats.totalSplitAttempts++,this.splitStats.avgBinsUsed=(this.splitStats.avgBinsUsed*(this.splitStats.totalSplitAttempts-1)+l)/this.splitStats.totalSplitAttempts;let u=this.indices,d=this.centroids,f=this.bMin,p=this.bMax,m=this.binBoundsMin,h=this.binBoundsMax,g=this.binCounts,_=this.leftPrefixMin,v=this.leftPrefixMax,y=this.leftPrefixCount,b=this.rightPrefixMin,x=this.rightPrefixMax,S=this.rightPrefixCount,C=1/0,w=-1/0,T=1/0,E=-1/0,D=1/0,O=-1/0;for(let n=e;n<t;n++){let e=u[n]*3,t=d[e],r=d[e+1],i=d[e+2];t<C&&(C=t),t>w&&(w=t),r<T&&(T=r),r>E&&(E=r),i<D&&(D=i),i>O&&(O=i)}let k=[C,T,D],A=[w,E,O];for(let n=0;n<3;n++){let s=k[n],C=A[n];if(C-s<1e-6)continue;for(let e=0;e<l;e++){g[e]=0;let t=e*3;m[t]=1/0,m[t+1]=1/0,m[t+2]=1/0,h[t]=-1/0,h[t+1]=-1/0,h[t+2]=-1/0}let w=l/(C-s);for(let r=e;r<t;r++){let e=u[r],t=d[e*3+n],i=Math.floor((t-s)*w);i>=l&&(i=l-1),g[i]++;let a=i*3,o=e*3;f[o]<m[a]&&(m[a]=f[o]),f[o+1]<m[a+1]&&(m[a+1]=f[o+1]),f[o+2]<m[a+2]&&(m[a+2]=f[o+2]),p[o]>h[a]&&(h[a]=p[o]),p[o+1]>h[a+1]&&(h[a+1]=p[o+1]),p[o+2]>h[a+2]&&(h[a+2]=p[o+2])}y[0]=g[0],_[0]=m[0],_[1]=m[1],_[2]=m[2],v[0]=h[0],v[1]=h[1],v[2]=h[2];for(let e=1;e<l;e++){let t=e*3,n=(e-1)*3;y[e]=y[e-1]+g[e];let r=_[n],i=m[t],a=_[n+1],o=m[t+1],s=_[n+2],c=m[t+2];_[t]=r<i?r:i,_[t+1]=a<o?a:o,_[t+2]=s<c?s:c;let l=v[n],u=h[t],d=v[n+1],f=h[t+1],p=v[n+2],b=h[t+2];v[t]=l>u?l:u,v[t+1]=d>f?d:f,v[t+2]=p>b?p:b}let T=l-1,E=T*3;S[T]=g[T],b[E]=m[E],b[E+1]=m[E+1],b[E+2]=m[E+2],x[E]=h[E],x[E+1]=h[E+1],x[E+2]=h[E+2];for(let e=T-1;e>=0;e--){let t=e*3,n=(e+1)*3;S[e]=S[e+1]+g[e];let r=b[n],i=m[t],a=b[n+1],o=m[t+1],s=b[n+2],c=m[t+2];b[t]=r<i?r:i,b[t+1]=a<o?a:o,b[t+2]=s<c?s:c;let l=x[n],u=h[t],d=x[n+1],f=h[t+1],p=x[n+2],_=h[t+2];x[t]=l>u?l:u,x[t+1]=d>f?d:f,x[t+2]=p>_?p:_}for(let e=1;e<l;e++){let t=(e-1)*3,u=e*3,d=y[e-1],f=S[e];if(d===0||f===0)continue;let p=v[t]-_[t],m=v[t+1]-_[t+1],h=v[t+2]-_[t+2],g=2*(p*m+m*h+h*p),w=x[u]-b[u],T=x[u+1]-b[u+1],E=x[u+2]-b[u+2],D=2*(w*T+T*E+E*w),O=this.traversalCost+g/o*d*this.intersectionCost+D/o*f*this.intersectionCost;O<r&&O<c&&(r=O,i=n,a=s+(C-s)*e/l)}}return i===-1?this.enableObjectMedianFallback?this.findObjectMedianSplit(e,t):this.enableSpatialMedianFallback?this.findSpatialMedianSplit(e,t):{success:!1,method:`fallbacks_disabled`}:{success:!0,axis:i,pos:a,method:`SAH`,binsUsed:l}}findObjectMedianSplit(e,t){let n=this.indices,r=this.centroids,i=-1,a=-1;for(let o=0;o<3;o++){let s=1/0,c=-1/0;for(let i=e;i<t;i++){let e=r[n[i]*3+o];e<s&&(s=e),e>c&&(c=e)}let l=c-s;l>a&&(a=l,i=o)}if(i===-1||a<1e-10)return this.enableSpatialMedianFallback?this.findSpatialMedianSplit(e,t):{success:!1,method:`object_median_failed`};let o=t-e,s=e+Math.floor(o/2);this.quickselect(e,t,s,i);let c=r[n[s]*3+i],l=!0;for(let e=s+1;e<t;e++)if(r[n[e]*3+i]>c){l=!1;break}if(l){let a=-1/0;for(let t=e;t<s;t++){let e=r[n[t]*3+i];e>a&&(a=e)}if(a<c)c=(a+c)*.5;else return this.enableSpatialMedianFallback?this.findSpatialMedianSplit(e,t):{success:!1,method:`object_median_degenerate`}}return{success:!0,axis:i,pos:c,method:`object_median`}}findSpatialMedianSplit(e,t){let n=this.indices,r=this.centroids,i=this.bMin,a=this.bMax,o=-1,s=-1,c=0,l=0;for(let r=0;r<3;r++){let u=1/0,d=-1/0;for(let o=e;o<t;o++){let e=n[o]*3+r;i[e]<u&&(u=i[e]),a[e]>d&&(d=a[e])}let f=d-u;f>s&&(s=f,o=r,c=u,l=d)}if(o===-1||s<1e-12)return{success:!1,method:`spatial_median_failed`};let u=(c+l)*.5,d=t-e,f=0;for(let i=e;i<t;i++)r[n[i]*3+o]<=u&&f++;if(f===0||f===d){let i=e+Math.floor(d/2);this.quickselect(e,t,i,o);let a=r[n[i]*3+o],s=!0;for(let i=e;i<t;i++)if(r[n[i]*3+o]!==a){s=!1;break}if(s)return{success:!1,method:`spatial_median_degenerate`};let c=-1/0;for(let t=e;t<i;t++){let e=r[n[t]*3+o];e>c&&(c=e)}if(c<a)u=(c+a)*.5;else{let e=1/0;for(let a=i+1;a<t;a++){let t=r[n[a]*3+o];t<e&&(e=t)}u=(a+e)*.5}}return{success:!0,axis:o,pos:u,method:`spatial_median`}}quickselect(e,t,n,r){let i=this.indices,a=this.centroids,o=e,s=t-1;for(;o<s;){let e=o+s>>>1,t=a[i[o]*3+r],c=a[i[e]*3+r],l=a[i[s]*3+r];if(t>c){let t=i[o];i[o]=i[e],i[e]=t}if(t>l){let e=i[o];i[o]=i[s],i[s]=e}if(c>l){let t=i[e];i[e]=i[s],i[s]=t}let u=a[i[e]*3+r],d=o,f=s;for(;d<=f;){for(;a[i[d]*3+r]<u;)d++;for(;a[i[f]*3+r]>u;)f--;if(d<=f){let e=i[d];i[d]=i[f],i[f]=e,d++,f--}}f<n&&(o=d),d>n&&(s=f)}}applySAOrdering(e){if(!e||!e.leftChild)return;let t=[e],n=[];for(;t.length>0;){let e=t.pop();!e.leftChild||!e.rightChild||(n.push(e),t.push(e.leftChild),t.push(e.rightChild))}for(let e=n.length-1;e>=0;e--){let t=n[e],r=t.leftChild,i=t.rightChild,a=r.maxX-r.minX,o=r.maxY-r.minY,s=r.maxZ-r.minZ,c=i.maxX-i.minX,l=i.maxY-i.minY,u=i.maxZ-i.minZ;c*l+l*u+u*c>a*o+o*s+s*a&&(t.leftChild=i,t.rightChild=r)}}flattenBVH(e){let t=[],n=[e];for(;n.length>0;){let e=n.pop();e._flatIndex=t.length,t.push(e),e.rightChild&&n.push(e.rightChild),e.leftChild&&n.push(e.leftChild)}let r=new Float32Array(t.length*16);for(let e=0;e<t.length;e++){let n=t[e],i=e*16;if(n.leftChild){let e=n.leftChild,t=n.rightChild;r[i]=e.minX,r[i+1]=e.minY,r[i+2]=e.minZ,r[i+3]=e._flatIndex,r[i+4]=e.maxX,r[i+5]=e.maxY,r[i+6]=e.maxZ,r[i+7]=t._flatIndex,r[i+8]=t.minX,r[i+9]=t.minY,r[i+10]=t.minZ,r[i+12]=t.maxX,r[i+13]=t.maxY,r[i+14]=t.maxZ}else r[i]=n.triangleOffset,r[i+1]=n.triangleCount,r[i+3]=-1}return r}flattenBVHWithFrontier(e){let t=[],n=[e];for(;n.length>0;){let e=n.pop();e._flatIndex=t.length,t.push(e),e.rightChild&&n.push(e.rightChild),e.leftChild&&n.push(e.leftChild)}let r=new Float32Array(t.length*16),i=[];for(let e=0;e<t.length;e++){let n=t[e],a=e*16;if(n.leftChild){let e=n.leftChild,t=n.rightChild;r[a]=e.minX,r[a+1]=e.minY,r[a+2]=e.minZ,r[a+3]=e._flatIndex,r[a+4]=e.maxX,r[a+5]=e.maxY,r[a+6]=e.maxZ,r[a+7]=t._flatIndex,r[a+8]=t.minX,r[a+9]=t.minY,r[a+10]=t.minZ,r[a+12]=t.maxX,r[a+13]=t.maxY,r[a+14]=t.maxZ}else if(n.isFrontier){let t=n.frontierTaskId;r[a]=n.triangleOffset,r[a+1]=n.triangleCount,r[a+2]=t,r[a+3]=-2,i.push({taskId:t,flatIndex:e})}else r[a]=n.triangleOffset,r[a+1]=n.triangleCount,r[a+3]=-1}return{flatData:r,frontierMap:i,nodeCount:t.length}}assembleParallelBVH(e,t,n,r){let i=[...r].sort((e,t)=>e.taskId-t.taskId),a=t;for(let e=0;e<i.length;e++)a+=i[e].nodeCount;let o=new Float32Array(a*16);o.set(e);let s=new Map;for(let e of n)s.set(e.taskId,e.flatIndex);let c=t;for(let e=0;e<i.length;e++){let t=i[e],n=t.flatData,r=t.nodeCount,a=c*16;o.set(n,a);for(let e=0;e<r;e++){let t=a+e*16;o[t+3]!==-1&&(o[t+3]+=c,o[t+7]+=c)}let l=s.get(t.taskId);if(l!==void 0){let e=l*16,t=a;for(let n=0;n<16;n++)o[e+n]=o[t+n]}c+=r}return o}computeSurfaceAreaFlat(e,t,n,r,i,a){let o=r-e,s=i-t,c=a-n;return 2*(o*s+s*c+c*o)}};self.onmessage=function(t){let{tasks:r,sharedTriangleData:i,sharedCentroids:a,sharedBMin:o,sharedBMax:s,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 c;t.maxLeafSize=d,t.numBins=f,t.maxBins=p,t.minBins=m,t.triangles=new Float32Array(i),t.centroids=new Float32Array(a),t.bMin=new Float32Array(o),t.bMax=new Float32Array(s),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-sNzvxn66.js.map