rayzee 4.8.4 → 4.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -82
- package/dist/assets/AIUpscalerWorker-D58dcMrY.js +2 -0
- package/dist/assets/AIUpscalerWorker-D58dcMrY.js.map +1 -0
- package/dist/assets/BVHRefitWorker-GkmNJYvb.js +2 -0
- package/dist/assets/BVHRefitWorker-GkmNJYvb.js.map +1 -0
- package/dist/assets/BVHSubtreeWorker-C02ZWVeG.js +2 -0
- package/dist/assets/BVHSubtreeWorker-C02ZWVeG.js.map +1 -0
- package/dist/assets/BVHWorker-DobVXMda.js +2 -0
- package/dist/assets/BVHWorker-DobVXMda.js.map +1 -0
- package/dist/assets/CDFWorker-2MoynL4F.js +2 -0
- package/dist/assets/CDFWorker-2MoynL4F.js.map +1 -0
- package/dist/assets/TexturesWorker-DBqGmVdR.js +2 -0
- package/dist/assets/TexturesWorker-DBqGmVdR.js.map +1 -0
- package/dist/rayzee.es.js +922 -871
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +43 -43
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/Passes/AIUpscaler.js +1 -2
- package/src/PathTracerApp.js +36 -3
- package/src/Processor/AssetLoader.js +2 -2
- package/src/Processor/BVHBuilder.js +1 -2
- package/src/Processor/EquirectHDRInfo.js +1 -2
- package/src/Processor/LightSerializer.js +26 -7
- package/src/Processor/ParallelBVHBuilder.js +8 -9
- package/src/Processor/SceneProcessor.js +3 -4
- package/src/Processor/TextureCreator.js +1 -2
- package/src/Processor/Workers/AIUpscalerWorker.js +21 -7
- package/src/README.md +1 -2
- package/src/Stages/PathTracer.js +7 -6
- package/src/TSL/LightsCore.js +12 -2
- package/src/TSL/LightsSampling.js +8 -6
- package/src/managers/LightManager.js +1 -1
- package/src/managers/UniformManager.js +2 -2
- package/src/Processor/createWorker.js +0 -38
package/README.md
CHANGED
|
@@ -5,22 +5,10 @@ A real-time WebGPU path tracing engine built on Three.js. Framework-agnostic —
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install rayzee
|
|
8
|
+
npm install rayzee three
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
```json
|
|
14
|
-
{
|
|
15
|
-
"dependencies": {
|
|
16
|
-
"rayzee": "github:atul-mourya/RayTracing",
|
|
17
|
-
"three": "^0.183.0",
|
|
18
|
-
"stats-gl": "^4.0.2"
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
npm publishing is coming soon. `three` and `stats-gl` are required peer dependencies.
|
|
11
|
+
`three` (>=0.170.0) is a required peer dependency. `stats-gl` is installed automatically as a transitive dependency.
|
|
24
12
|
|
|
25
13
|
## Getting Started
|
|
26
14
|
|
|
@@ -31,26 +19,10 @@ npm publishing is coming soon. `three` and `stats-gl` are required peer dependen
|
|
|
31
19
|
```bash
|
|
32
20
|
npm create vite@latest my-raytracer -- --template vanilla
|
|
33
21
|
cd my-raytracer
|
|
34
|
-
npm install rayzee
|
|
22
|
+
npm install rayzee three
|
|
35
23
|
```
|
|
36
24
|
|
|
37
|
-
2. **
|
|
38
|
-
|
|
39
|
-
```js
|
|
40
|
-
import { defineConfig } from 'vite';
|
|
41
|
-
|
|
42
|
-
export default defineConfig({
|
|
43
|
-
server: {
|
|
44
|
-
headers: {
|
|
45
|
-
'Cross-Origin-Opener-Policy': 'same-origin',
|
|
46
|
-
'Cross-Origin-Embedder-Policy': 'credentialless',
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
assetsInclude: ['**/*.hdr'],
|
|
50
|
-
});
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
3. **Set up the HTML**
|
|
25
|
+
2. **Set up the HTML**
|
|
54
26
|
|
|
55
27
|
```html
|
|
56
28
|
<!-- index.html -->
|
|
@@ -60,7 +32,7 @@ npm publishing is coming soon. `three` and `stats-gl` are required peer dependen
|
|
|
60
32
|
</body>
|
|
61
33
|
```
|
|
62
34
|
|
|
63
|
-
|
|
35
|
+
3. **Write the code**
|
|
64
36
|
|
|
65
37
|
```js
|
|
66
38
|
// main.js
|
|
@@ -88,11 +60,11 @@ npm publishing is coming soon. `three` and `stats-gl` are required peer dependen
|
|
|
88
60
|
});
|
|
89
61
|
|
|
90
62
|
// Tweak settings
|
|
91
|
-
engine.set('
|
|
63
|
+
engine.set('bounces', 8);
|
|
92
64
|
engine.set('exposure', 1.2);
|
|
93
65
|
```
|
|
94
66
|
|
|
95
|
-
|
|
67
|
+
4. **Run**
|
|
96
68
|
|
|
97
69
|
```bash
|
|
98
70
|
npm run dev
|
|
@@ -132,7 +104,8 @@ A single HTML file — no Node.js, no build step. Uses [ES module import maps](h
|
|
|
132
104
|
|
|
133
105
|
const engine = new PathTracerApp(canvas);
|
|
134
106
|
await engine.init();
|
|
135
|
-
|
|
107
|
+
// Replace with your own model URL
|
|
108
|
+
await engine.loadModel('https://your-cdn.com/scene.glb');
|
|
136
109
|
engine.animate();
|
|
137
110
|
|
|
138
111
|
window.addEventListener('resize', () => {
|
|
@@ -164,25 +137,27 @@ export default function Viewport({ modelUrl }) {
|
|
|
164
137
|
const engineRef = useRef(null);
|
|
165
138
|
|
|
166
139
|
useEffect(() => {
|
|
167
|
-
|
|
140
|
+
const canvas = canvasRef.current;
|
|
141
|
+
canvas.width = canvas.clientWidth;
|
|
142
|
+
canvas.height = canvas.clientHeight;
|
|
168
143
|
|
|
169
|
-
|
|
170
|
-
|
|
144
|
+
const engine = new PathTracerApp(canvas);
|
|
145
|
+
engineRef.current = engine;
|
|
146
|
+
|
|
147
|
+
(async () => {
|
|
171
148
|
await engine.init();
|
|
172
149
|
if (modelUrl) await engine.loadModel(modelUrl);
|
|
173
150
|
engine.animate();
|
|
174
|
-
|
|
175
|
-
}
|
|
151
|
+
})();
|
|
176
152
|
|
|
177
|
-
|
|
178
|
-
return () => engine?.dispose();
|
|
153
|
+
return () => engine.dispose();
|
|
179
154
|
}, [modelUrl]);
|
|
180
155
|
|
|
181
156
|
return <canvas ref={canvasRef} style={{ width: '100%', height: '100vh' }} />;
|
|
182
157
|
}
|
|
183
158
|
```
|
|
184
159
|
|
|
185
|
-
|
|
160
|
+
No special build config is needed — models and HDRs are loaded via URL at runtime.
|
|
186
161
|
|
|
187
162
|
## API Reference
|
|
188
163
|
|
|
@@ -191,14 +166,14 @@ The same Vite config (headers + assetsInclude) applies for React projects.
|
|
|
191
166
|
The main engine class. Extends Three.js `EventDispatcher`.
|
|
192
167
|
|
|
193
168
|
```js
|
|
194
|
-
const engine = new PathTracerApp(canvas,
|
|
169
|
+
const engine = new PathTracerApp(canvas, options?)
|
|
195
170
|
```
|
|
196
171
|
|
|
197
172
|
| Parameter | Type | Description |
|
|
198
173
|
|---|---|---|
|
|
199
174
|
| `canvas` | `HTMLCanvasElement` | Rendering target |
|
|
200
|
-
| `denoiserCanvas` | `HTMLCanvasElement` | Optional canvas for OIDN denoiser output |
|
|
201
175
|
| `options.autoResize` | `boolean` | Auto-resize on window resize (default: `true`) |
|
|
176
|
+
| `options.statsContainer` | `HTMLElement` | DOM element to append the stats panel to (defaults to `document.body`) |
|
|
202
177
|
|
|
203
178
|
#### Lifecycle
|
|
204
179
|
|
|
@@ -209,25 +184,29 @@ engine.pause() // Pause rendering
|
|
|
209
184
|
engine.resume() // Resume rendering
|
|
210
185
|
engine.reset() // Reset accumulation (restart from sample 0)
|
|
211
186
|
engine.dispose() // Clean up all resources
|
|
187
|
+
engine.wake() // Resume render loop if idle (called automatically on interaction)
|
|
188
|
+
engine.isComplete() // Check if rendering has converged
|
|
189
|
+
engine.getFrameCount() // Get the current accumulated frame count
|
|
212
190
|
```
|
|
213
191
|
|
|
214
192
|
#### Loading Assets
|
|
215
193
|
|
|
216
194
|
```js
|
|
217
195
|
await engine.loadModel(url) // Load GLB/GLTF/FBX/OBJ/STL/PLY/DAE/3MF/USDZ
|
|
196
|
+
await engine.loadObject3D(object3d) // Load a Three.js Object3D directly
|
|
218
197
|
await engine.loadEnvironment(url) // Load HDR/EXR environment map
|
|
219
198
|
```
|
|
220
199
|
|
|
221
200
|
#### Settings
|
|
222
201
|
|
|
223
202
|
```js
|
|
224
|
-
engine.set('
|
|
203
|
+
engine.set('bounces', 8) // Set a single parameter
|
|
225
204
|
engine.setMany({ // Set multiple parameters at once
|
|
226
|
-
|
|
205
|
+
bounces: 8,
|
|
227
206
|
samplesPerPixel: 1,
|
|
228
207
|
exposure: 1.0
|
|
229
208
|
})
|
|
230
|
-
engine.get('
|
|
209
|
+
engine.get('bounces') // Read a parameter
|
|
231
210
|
engine.getAll() // Get all current settings
|
|
232
211
|
```
|
|
233
212
|
|
|
@@ -235,25 +214,33 @@ Key settings:
|
|
|
235
214
|
|
|
236
215
|
| Setting | Type | Default | Description |
|
|
237
216
|
|---|---|---|---|
|
|
238
|
-
| `
|
|
217
|
+
| `bounces` | `number` | 3 | Max ray bounce depth |
|
|
239
218
|
| `samplesPerPixel` | `number` | 1 | Samples per pixel per frame |
|
|
219
|
+
| `maxSamples` | `number` | 60 | Max accumulated samples before stopping |
|
|
240
220
|
| `exposure` | `number` | 1.0 | Exposure value |
|
|
221
|
+
| `saturation` | `number` | 1.2 | Color saturation |
|
|
241
222
|
| `enableEnvironment` | `boolean` | true | Use environment lighting |
|
|
242
223
|
| `environmentIntensity` | `number` | 1.0 | Environment light strength |
|
|
243
|
-
| `environmentRotation` | `number` |
|
|
244
|
-
| `fireflyThreshold` | `number` |
|
|
224
|
+
| `environmentRotation` | `number` | 270 | Environment Y-rotation (degrees) |
|
|
225
|
+
| `fireflyThreshold` | `number` | 3.0 | Firefly clamping threshold |
|
|
245
226
|
| `transmissiveBounces` | `number` | 5 | Max bounces for transmissive materials |
|
|
246
|
-
| `
|
|
227
|
+
| `enableDOF` | `boolean` | false | Enable depth of field |
|
|
228
|
+
| `focusDistance` | `number` | 0.8 | DOF focus distance |
|
|
229
|
+
| `aperture` | `number` | 5.6 | DOF aperture (f-stop) |
|
|
230
|
+
| `focalLength` | `number` | 50 | DOF focal length (mm) |
|
|
231
|
+
| `adaptiveSampling` | `boolean` | false | Variance-guided sample distribution |
|
|
232
|
+
| `transparentBackground` | `boolean` | false | Transparent canvas background |
|
|
233
|
+
| `debugMode` | `number` | 0 | Debug visualization mode (0 = off) |
|
|
234
|
+
| `environmentMode` | `string` | 'hdri' | Sky mode: `'hdri'` \| `'procedural'` \| `'gradient'` \| `'color'` |
|
|
247
235
|
|
|
248
236
|
See `ENGINE_DEFAULTS` for the full list with default values.
|
|
249
237
|
|
|
250
238
|
#### Rendering Modes
|
|
251
239
|
|
|
252
240
|
```js
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
engine.configureForMode('
|
|
256
|
-
engine.configureForMode('interactive') // Real-time navigation (3 bounces)
|
|
241
|
+
engine.configureForMode('final-render') // High quality (tiled, 20 bounces, OIDN)
|
|
242
|
+
engine.configureForMode('preview') // Real-time navigation (3 bounces)
|
|
243
|
+
engine.configureForMode('results') // Paused rendering for image viewing
|
|
257
244
|
```
|
|
258
245
|
|
|
259
246
|
#### Camera
|
|
@@ -262,6 +249,9 @@ engine.configureForMode('interactive') // Real-time navigation (3 bounces)
|
|
|
262
249
|
engine.switchCamera(index) // Switch between scene cameras
|
|
263
250
|
engine.getCameraNames() // List available cameras
|
|
264
251
|
engine.toggleFocusMode() // Enable click-to-focus DOF
|
|
252
|
+
engine.focusOnPoint(center) // Focus orbit camera on a world-space point
|
|
253
|
+
engine.camera // Access the active PerspectiveCamera
|
|
254
|
+
engine.controls // Access OrbitControls
|
|
265
255
|
```
|
|
266
256
|
|
|
267
257
|
#### Lights
|
|
@@ -269,17 +259,58 @@ engine.toggleFocusMode() // Enable click-to-focus DOF
|
|
|
269
259
|
```js
|
|
270
260
|
engine.addLight('point') // Add a light (point, spot, directional, area)
|
|
271
261
|
engine.removeLight(uuid) // Remove by UUID
|
|
262
|
+
engine.clearLights() // Remove all lights
|
|
272
263
|
engine.getLights() // Get all lights
|
|
264
|
+
engine.updateLights() // Re-upload light data to GPU
|
|
273
265
|
engine.setShowLightHelper(true) // Toggle visual helpers
|
|
274
266
|
```
|
|
275
267
|
|
|
268
|
+
#### Object Selection & Transform
|
|
269
|
+
|
|
270
|
+
```js
|
|
271
|
+
engine.toggleSelectMode() // Toggle object selection mode
|
|
272
|
+
engine.selectObject(object) // Programmatically select an object
|
|
273
|
+
engine.setTransformMode('translate') // Set gizmo mode: 'translate' | 'rotate' | 'scale'
|
|
274
|
+
engine.setTransformSpace('world') // Set gizmo space: 'world' | 'local'
|
|
275
|
+
engine.transformManager // Access the underlying TransformManager
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
#### Animation
|
|
279
|
+
|
|
280
|
+
```js
|
|
281
|
+
engine.playAnimation(clipIndex) // Play a GLTF animation clip
|
|
282
|
+
engine.pauseAnimation() // Pause playback
|
|
283
|
+
engine.resumeAnimation() // Resume playback
|
|
284
|
+
engine.stopAnimationPlayback() // Stop and reset
|
|
285
|
+
engine.setAnimationSpeed(speed) // Set playback speed multiplier
|
|
286
|
+
engine.setAnimationLoop(loop) // Enable/disable looping
|
|
287
|
+
engine.animationClips // Get available animation clips
|
|
288
|
+
```
|
|
289
|
+
|
|
276
290
|
#### Denoising
|
|
277
291
|
|
|
278
292
|
```js
|
|
279
|
-
engine.setDenoiserStrategy('asvgf')
|
|
280
|
-
engine.setDenoiserStrategy('oidn')
|
|
281
|
-
engine.setDenoiserStrategy('
|
|
282
|
-
engine.
|
|
293
|
+
engine.setDenoiserStrategy('asvgf') // Real-time temporal denoiser
|
|
294
|
+
engine.setDenoiserStrategy('oidn') // Intel OIDN (higher quality, final renders)
|
|
295
|
+
engine.setDenoiserStrategy('edgeaware') // Edge-preserving temporal filter (default)
|
|
296
|
+
engine.setDenoiserStrategy('none') // No denoising
|
|
297
|
+
engine.setASVGFEnabled(true, 'medium') // ASVGF with quality preset (low/medium/high)
|
|
298
|
+
engine.applyASVGFPreset('high') // Apply an ASVGF quality preset
|
|
299
|
+
engine.setAutoExposureEnabled(true) // Toggle auto-exposure
|
|
300
|
+
engine.setAdaptiveSamplingEnabled(true) // Toggle adaptive sampling
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
#### Environment
|
|
304
|
+
|
|
305
|
+
```js
|
|
306
|
+
engine.getEnvParams() // Get current environment parameters
|
|
307
|
+
engine.getEnvironmentTexture() // Get the loaded environment texture
|
|
308
|
+
engine.setEnvironmentMap(texture) // Set a custom environment texture
|
|
309
|
+
engine.setEnvironmentMode('procedural') // Switch sky mode
|
|
310
|
+
engine.generateProceduralSkyTexture() // Generate Preetham-model sky
|
|
311
|
+
engine.generateGradientTexture() // Generate gradient sky
|
|
312
|
+
engine.generateSolidColorTexture() // Generate solid color sky
|
|
313
|
+
engine.markEnvironmentNeedsUpdate() // Flag environment for GPU re-upload
|
|
283
314
|
```
|
|
284
315
|
|
|
285
316
|
#### Canvas & Resolution
|
|
@@ -287,6 +318,25 @@ engine.setASVGFEnabled(true, 'balanced') // ASVGF with quality preset
|
|
|
287
318
|
```js
|
|
288
319
|
engine.setCanvasSize(1920, 1080) // Set explicit canvas dimensions
|
|
289
320
|
engine.onResize() // Trigger manual resize recalculation
|
|
321
|
+
engine.getOutputCanvas() // Get the canvas with the final rendered image
|
|
322
|
+
engine.takeScreenshot() // Download a PNG screenshot
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
#### Materials
|
|
326
|
+
|
|
327
|
+
```js
|
|
328
|
+
engine.updateMaterialProperty(index, property, value) // Update a material property
|
|
329
|
+
engine.updateTextureTransform(index, textureName, transform)
|
|
330
|
+
engine.refreshMaterial() // Re-upload all material data to GPU
|
|
331
|
+
engine.updateMaterial(index, material) // Replace a material entirely
|
|
332
|
+
await engine.rebuildMaterials(scene) // Full material rebuild (texture changes)
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
#### Scene Info
|
|
336
|
+
|
|
337
|
+
```js
|
|
338
|
+
engine.getSceneStatistics() // Triangle count, mesh count, etc.
|
|
339
|
+
engine.stages // Named access to all pipeline stages
|
|
290
340
|
```
|
|
291
341
|
|
|
292
342
|
### Events
|
|
@@ -297,40 +347,47 @@ Subscribe to engine lifecycle events via `addEventListener`:
|
|
|
297
347
|
import { EngineEvents } from 'rayzee';
|
|
298
348
|
|
|
299
349
|
engine.addEventListener(EngineEvents.RENDER_COMPLETE, (e) => {
|
|
300
|
-
console.log('Render complete'
|
|
350
|
+
console.log('Render complete');
|
|
301
351
|
});
|
|
302
352
|
|
|
303
353
|
engine.addEventListener(EngineEvents.STATS_UPDATE, (e) => {
|
|
304
|
-
console.log('
|
|
354
|
+
console.log('Stats:', e);
|
|
305
355
|
});
|
|
306
356
|
```
|
|
307
357
|
|
|
308
358
|
| Event | Fired when |
|
|
309
359
|
|---|---|
|
|
310
|
-
| `RENDER_COMPLETE` |
|
|
360
|
+
| `RENDER_COMPLETE` | Rendering has converged |
|
|
311
361
|
| `RENDER_RESET` | Accumulation buffer is reset |
|
|
312
362
|
| `DENOISING_START` / `DENOISING_END` | Denoiser runs |
|
|
313
363
|
| `UPSCALING_START` / `UPSCALING_PROGRESS` / `UPSCALING_END` | AI upscaler runs |
|
|
314
364
|
| `LOADING_UPDATE` / `LOADING_RESET` | Asset loading progress |
|
|
315
365
|
| `STATS_UPDATE` | Performance stats updated |
|
|
316
366
|
| `OBJECT_SELECTED` / `OBJECT_DESELECTED` | Object selection changes |
|
|
367
|
+
| `OBJECT_DOUBLE_CLICKED` | Object double-clicked |
|
|
368
|
+
| `OBJECT_TRANSFORM_START` / `OBJECT_TRANSFORM_END` | Transform gizmo drag |
|
|
369
|
+
| `TRANSFORM_MODE_CHANGED` | Gizmo mode changed |
|
|
370
|
+
| `SELECT_MODE_CHANGED` | Selection mode toggled |
|
|
317
371
|
| `SETTING_CHANGED` | A render setting is modified |
|
|
318
372
|
| `AUTO_FOCUS_UPDATED` | Auto-focus recalculated |
|
|
319
373
|
| `AUTO_EXPOSURE_UPDATED` | Auto-exposure recalculated |
|
|
374
|
+
| `AF_POINT_PLACED` | Focus point placed on screen |
|
|
375
|
+
| `ANIMATION_STARTED` / `ANIMATION_PAUSED` / `ANIMATION_STOPPED` / `ANIMATION_FINISHED` | Animation lifecycle |
|
|
376
|
+
| `VIDEO_RENDER_PROGRESS` / `VIDEO_RENDER_COMPLETE` | Video export progress |
|
|
320
377
|
|
|
321
378
|
### Advanced: Custom Pipeline Stages
|
|
322
379
|
|
|
323
380
|
Build custom rendering stages by extending `RenderStage`:
|
|
324
381
|
|
|
325
382
|
```js
|
|
326
|
-
import { RenderStage
|
|
383
|
+
import { RenderStage } from 'rayzee';
|
|
327
384
|
|
|
328
385
|
class MyCustomStage extends RenderStage {
|
|
329
386
|
constructor() {
|
|
330
387
|
super('my-stage');
|
|
331
388
|
}
|
|
332
389
|
|
|
333
|
-
render(
|
|
390
|
+
render(context, writeBuffer) {
|
|
334
391
|
const input = context.getTexture('pathtracer:color');
|
|
335
392
|
// ... process input, write output
|
|
336
393
|
context.setTexture('my-stage:output', this.outputTexture);
|
|
@@ -351,6 +408,13 @@ import {
|
|
|
351
408
|
CAMERA_PRESETS,
|
|
352
409
|
CAMERA_RANGES,
|
|
353
410
|
SKY_PRESETS,
|
|
411
|
+
AUTO_FOCUS_MODES,
|
|
412
|
+
AF_DEFAULTS,
|
|
413
|
+
TRIANGLE_DATA_LAYOUT,
|
|
414
|
+
BVH_LEAF_MARKERS,
|
|
415
|
+
TEXTURE_CONSTANTS,
|
|
416
|
+
DEFAULT_TEXTURE_MATRIX,
|
|
417
|
+
MEMORY_CONSTANTS,
|
|
354
418
|
FINAL_RENDER_CONFIG,
|
|
355
419
|
PREVIEW_RENDER_CONFIG,
|
|
356
420
|
} from 'rayzee';
|
|
@@ -361,6 +425,10 @@ import {
|
|
|
361
425
|
CameraManager,
|
|
362
426
|
LightManager,
|
|
363
427
|
DenoisingManager,
|
|
428
|
+
OverlayManager,
|
|
429
|
+
AnimationManager,
|
|
430
|
+
TransformManager,
|
|
431
|
+
VideoRenderManager,
|
|
364
432
|
} from 'rayzee';
|
|
365
433
|
|
|
366
434
|
// Advanced: pipeline infrastructure
|
|
@@ -374,32 +442,23 @@ import {
|
|
|
374
442
|
|
|
375
443
|
## Browser Requirements
|
|
376
444
|
|
|
377
|
-
- WebGPU support (Chrome 113+, Edge 113+, Firefox
|
|
445
|
+
- WebGPU support (Chrome 113+, Edge 113+, Safari 18+, Firefox 141+)
|
|
378
446
|
- Secure context (HTTPS or localhost)
|
|
379
447
|
|
|
380
448
|
## Optional Dependencies
|
|
381
449
|
|
|
382
|
-
| Package | Purpose |
|
|
383
|
-
|
|
384
|
-
| `oidn-web` | Intel Open Image Denoise for high-quality final renders |
|
|
385
|
-
| `onnxruntime-web` | AI-powered upscaling |
|
|
386
|
-
|
|
387
|
-
Install them alongside rayzee if needed:
|
|
388
|
-
|
|
389
|
-
```bash
|
|
390
|
-
npm install oidn-web onnxruntime-web
|
|
391
|
-
```
|
|
450
|
+
| Package | Purpose | Install needed? |
|
|
451
|
+
|---|---|---|
|
|
452
|
+
| `oidn-web` | Intel Open Image Denoise for high-quality final renders | Yes — `npm install oidn-web` |
|
|
453
|
+
| `onnxruntime-web` | AI-powered upscaling | No — loaded from CDN at runtime |
|
|
392
454
|
|
|
393
455
|
## Troubleshooting
|
|
394
456
|
|
|
395
457
|
**Black screen / "WebGPU not supported"**
|
|
396
|
-
Your browser may not support WebGPU. Use Chrome 113
|
|
397
|
-
|
|
398
|
-
**CORS errors loading models/HDRs**
|
|
399
|
-
Add cross-origin isolation headers to your dev server (see the Vite config in Getting Started).
|
|
458
|
+
Your browser may not support WebGPU. Use Chrome 113+, Edge 113+, Safari 18+, or Firefox 141+. Ensure you're on HTTPS or localhost.
|
|
400
459
|
|
|
401
460
|
**Models not loading**
|
|
402
|
-
|
|
461
|
+
If serving locally, place files in your `public/` folder and reference them with absolute paths (e.g., `/scene.glb`). For remote files, ensure the server allows CORS.
|
|
403
462
|
|
|
404
463
|
## License
|
|
405
464
|
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(function(){let e=null;async function t(){return e||(e=await import(`https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/ort.webgpu.bundle.min.mjs`),e.env.wasm.wasmPaths=`https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/`,e.env.logLevel=`error`,e)}let n=`models`,r=null,i=null;function a(){return new Promise((e,t)=>{let r=indexedDB.open(`ai-upscaler-models`,1);r.onupgradeneeded=()=>r.result.createObjectStore(n),r.onsuccess=()=>e(r.result),r.onerror=()=>t(r.error)})}async function o(e){try{let t=await a();return await new Promise((r,i)=>{let a=t.transaction(n,`readonly`).objectStore(n).get(e);a.onsuccess=()=>r(a.result||null),a.onerror=()=>i(a.error)})}catch{return null}}async function s(e,t){try{let r=await a();await new Promise((i,a)=>{let o=r.transaction(n,`readwrite`);o.objectStore(n).put(t,e),o.oncomplete=()=>i(),o.onerror=()=>a(o.error)})}catch{}}async function c(e){let t=await o(e);if(t)return console.log(`AI Upscaler Worker: model loaded from cache (${(t.byteLength/1024/1024).toFixed(1)}MB)`),t;let n=await fetch(e);if(!n.ok)throw Error(`Failed to fetch model: ${n.status}`);let r=await n.arrayBuffer();return s(e,r.slice(0)),r}async function l(e,n){if(r&&i===e){self.postMessage({type:`loaded`,backend:`webgpu`});return}r&&=(await r.release(),null);let[a,o]=await Promise.all([c(e),t()]);r=await o.InferenceSession.create(a,n),i=e;let s=512;try{let e=await navigator.gpu?.requestAdapter(),t=await e?.requestAdapterInfo?.()||e?.info,n=/apple|swiftshader|llvmpipe/i.test(t?.vendor||``)||/apple|swiftshader/i.test(t?.architecture||``),r=t?.device?.toLowerCase?.()?.includes(`integrated`)||/intel.*iris|intel.*uhd|intel.*hd|amd.*vega|radeon.*graphics/i.test(t?.description||``);s=n?128:r?256:512,console.log(`AI Upscaler Worker: GPU="${t?.description||t?.device||`unknown`}", tileSize=${s}`)}catch{}let l=(a.byteLength/1024/1024).toFixed(1);console.log(`AI Upscaler Worker: model loaded (${l}MB), backend: webgpu`),self.postMessage({type:`loaded`,backend:`webgpu`,tileSize:s})}async function u(e,n,i,a){let o=await t(),s=r.inputNames[0],c=r.outputNames[0],l=new o.Tensor(`float32`,e,[1,3,i,n]),u=(await r.run({[s]:l}))[c].data;self.postMessage({type:`inferred`,outputData:u,id:a},[u.buffer])}self.onmessage=async e=>{let{type:t}=e.data;try{t===`load`?await l(e.data.url,e.data.sessionOptions):t===`infer`?await u(e.data.tileData,e.data.width,e.data.height,e.data.id):t===`dispose`&&r&&(await r.release(),r=null,i=null)}catch(t){self.postMessage({type:`error`,message:t.message,id:e.data?.id})}}})();
|
|
2
|
+
//# sourceMappingURL=AIUpscalerWorker-D58dcMrY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AIUpscalerWorker-D58dcMrY.js","names":[],"sources":["../../src/Processor/Workers/AIUpscalerWorker.js"],"sourcesContent":["/**\n * Web Worker for AI Upscaler inference.\n * Handles ONNX model loading and tile-based inference off the main thread.\n *\n * Messages:\n * Main → Worker:\n * { type: 'load', url, sessionOptions } — load/switch model\n * { type: 'infer', tileData, width, height, id } — run inference on a tile\n * { type: 'dispose' } — release session\n *\n * Worker → Main:\n * { type: 'loaded', backend }\n * { type: 'inferred', outputData, id }\n * { type: 'error', message, id? }\n */\n\n// Loaded lazily via CDN to avoid bundling the 69 MB onnxruntime-web package\nconst ORT_CDN_URL = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/ort.webgpu.bundle.min.mjs';\n\nlet ort = null;\n\nasync function getOrt() {\n\n\tif ( ort ) return ort;\n\n\tort = await import( /* @vite-ignore */ ORT_CDN_URL );\n\n\t// WASM paths for CDN delivery — WebGPU EP still uses WASM for lightweight shape ops\n\tort.env.wasm.wasmPaths = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.3/dist/';\n\tort.env.logLevel = 'error';\n\n\treturn ort;\n\n}\n\nconst IDB_NAME = 'ai-upscaler-models';\nconst IDB_STORE = 'models';\n\nlet session = null;\nlet currentModelUrl = null;\n\n// ─── IndexedDB Model Cache ───────────────────────────────────────────────────\n\nfunction openDB() {\n\n\treturn new Promise( ( resolve, reject ) => {\n\n\t\tconst req = indexedDB.open( IDB_NAME, 1 );\n\t\treq.onupgradeneeded = () => req.result.createObjectStore( IDB_STORE );\n\t\treq.onsuccess = () => resolve( req.result );\n\t\treq.onerror = () => reject( req.error );\n\n\t} );\n\n}\n\nasync function getCachedModel( url ) {\n\n\ttry {\n\n\t\tconst db = await openDB();\n\t\treturn await new Promise( ( resolve, reject ) => {\n\n\t\t\tconst tx = db.transaction( IDB_STORE, 'readonly' );\n\t\t\tconst req = tx.objectStore( IDB_STORE ).get( url );\n\t\t\treq.onsuccess = () => resolve( req.result || null );\n\t\t\treq.onerror = () => reject( req.error );\n\n\t\t} );\n\n\t} catch {\n\n\t\treturn null;\n\n\t}\n\n}\n\nasync function cacheModel( url, buffer ) {\n\n\ttry {\n\n\t\tconst db = await openDB();\n\t\tawait new Promise( ( resolve, reject ) => {\n\n\t\t\tconst tx = db.transaction( IDB_STORE, 'readwrite' );\n\t\t\ttx.objectStore( IDB_STORE ).put( buffer, url );\n\t\t\ttx.oncomplete = () => resolve();\n\t\t\ttx.onerror = () => reject( tx.error );\n\n\t\t} );\n\n\t} catch {\n\n\t\t// Cache write failure is non-fatal\n\t}\n\n}\n\n// ─── Model Loading ───────────────────────────────────────────────────────────\n\nasync function fetchModel( url ) {\n\n\t// Try IndexedDB cache first\n\tconst cached = await getCachedModel( url );\n\tif ( cached ) {\n\n\t\tconsole.log( `AI Upscaler Worker: model loaded from cache (${( cached.byteLength / 1024 / 1024 ).toFixed( 1 )}MB)` );\n\t\treturn cached;\n\n\t}\n\n\t// Network fetch + cache\n\tconst response = await fetch( url );\n\tif ( ! response.ok ) throw new Error( `Failed to fetch model: ${response.status}` );\n\tconst buffer = await response.arrayBuffer();\n\n\t// Cache in background (don't block session creation)\n\tcacheModel( url, buffer.slice( 0 ) );\n\n\treturn buffer;\n\n}\n\nasync function loadModel( url, sessionOptions ) {\n\n\tif ( session && currentModelUrl === url ) {\n\n\t\tconst backend = 'webgpu';\n\t\tself.postMessage( { type: 'loaded', backend } );\n\t\treturn;\n\n\t}\n\n\t// Dispose previous session\n\tif ( session ) {\n\n\t\tawait session.release();\n\t\tsession = null;\n\n\t}\n\n\tconst [ modelBuffer, ortLib ] = await Promise.all( [ fetchModel( url ), getOrt() ] );\n\n\tsession = await ortLib.InferenceSession.create( modelBuffer, sessionOptions );\n\tcurrentModelUrl = url;\n\n\t// Detect GPU and recommend tile size based on device type\n\tlet tileSize = 512; // default\n\ttry {\n\n\t\tconst adapter = await navigator.gpu?.requestAdapter();\n\t\tconst info = await adapter?.requestAdapterInfo?.() || adapter?.info;\n\t\tconst isMobile = /apple|swiftshader|llvmpipe/i.test( info?.vendor || '' )\n\t\t\t|| /apple|swiftshader/i.test( info?.architecture || '' );\n\t\tconst isIntegrated = info?.device?.toLowerCase?.()?.includes( 'integrated' )\n\t\t\t|| /intel.*iris|intel.*uhd|intel.*hd|amd.*vega|radeon.*graphics/i.test( info?.description || '' );\n\n\t\tif ( isMobile ) {\n\n\t\t\ttileSize = 128;\n\n\t\t} else if ( isIntegrated ) {\n\n\t\t\ttileSize = 256;\n\n\t\t} else {\n\n\t\t\ttileSize = 512;\n\n\t\t}\n\n\t\tconsole.log( `AI Upscaler Worker: GPU=\"${info?.description || info?.device || 'unknown'}\", tileSize=${tileSize}` );\n\n\t} catch { /* fallback to default */ }\n\n\tconst sizeMB = ( modelBuffer.byteLength / 1024 / 1024 ).toFixed( 1 );\n\tconsole.log( `AI Upscaler Worker: model loaded (${sizeMB}MB), backend: webgpu` );\n\n\tself.postMessage( { type: 'loaded', backend: 'webgpu', tileSize } );\n\n}\n\nasync function inferTile( tileData, width, height, id ) {\n\n\tconst ortLib = await getOrt();\n\tconst inputName = session.inputNames[ 0 ];\n\tconst outputName = session.outputNames[ 0 ];\n\tconst inputTensor = new ortLib.Tensor( 'float32', tileData, [ 1, 3, height, width ] );\n\n\tconst results = await session.run( { [ inputName ]: inputTensor } );\n\tconst outputData = results[ outputName ].data;\n\n\t// Transfer the output buffer (zero-copy)\n\tself.postMessage( { type: 'inferred', outputData, id }, [ outputData.buffer ] );\n\n}\n\nself.onmessage = async ( e ) => {\n\n\tconst { type } = e.data;\n\n\ttry {\n\n\t\tif ( type === 'load' ) {\n\n\t\t\tawait loadModel( e.data.url, e.data.sessionOptions );\n\n\t\t} else if ( type === 'infer' ) {\n\n\t\t\tawait inferTile( e.data.tileData, e.data.width, e.data.height, e.data.id );\n\n\t\t} else if ( type === 'dispose' ) {\n\n\t\t\tif ( session ) {\n\n\t\t\t\tawait session.release();\n\t\t\t\tsession = null;\n\t\t\t\tcurrentModelUrl = null;\n\n\t\t\t}\n\n\t\t}\n\n\t} catch ( error ) {\n\n\t\tself.postMessage( { type: 'error', message: error.message, id: e.data?.id } );\n\n\t}\n\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(function(){let e={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},t=e.FLOATS_PER_TRIANGLE,n=new class{constructor(){this._bounds=null,this._boundsNodeCount=0}updateTrianglePositions(n,r,i){let a=i.length;for(let o=0;o<a;o++){let a=i[o],s=o*t,c=a*9,l=r[c],u=r[c+1],d=r[c+2],f=r[c+3],p=r[c+4],m=r[c+5],h=r[c+6],g=r[c+7],_=r[c+8];n[s+e.POSITION_A_OFFSET]=l,n[s+e.POSITION_A_OFFSET+1]=u,n[s+e.POSITION_A_OFFSET+2]=d,n[s+e.POSITION_B_OFFSET]=f,n[s+e.POSITION_B_OFFSET+1]=p,n[s+e.POSITION_B_OFFSET+2]=m,n[s+e.POSITION_C_OFFSET]=h,n[s+e.POSITION_C_OFFSET+1]=g,n[s+e.POSITION_C_OFFSET+2]=_;let v=f-l,y=p-u,b=m-d,x=h-l,S=g-u,C=_-d,w=y*C-b*S,T=b*x-v*C,E=v*S-y*x;n[s+e.NORMAL_A_OFFSET]=w,n[s+e.NORMAL_A_OFFSET+1]=T,n[s+e.NORMAL_A_OFFSET+2]=E,n[s+e.NORMAL_B_OFFSET]=w,n[s+e.NORMAL_B_OFFSET+1]=T,n[s+e.NORMAL_B_OFFSET+2]=E,n[s+e.NORMAL_C_OFFSET]=w,n[s+e.NORMAL_C_OFFSET+1]=T,n[s+e.NORMAL_C_OFFSET+2]=E}}refitRange(n,r,i,a){a>this._boundsNodeCount&&(this._bounds=new Float32Array(a*6),this._boundsNodeCount=a);let o=this._bounds,s=i+a;for(let a=s-1;a>=i;a--){let s=a*16,c=(a-i)*6;if(n[s+3]===-1){let i=n[s],a=n[s+1],l=1/0,u=1/0,d=1/0,f=-1/0,p=-1/0,m=-1/0;for(let n=0;n<a;n++){let a=(i+n)*t,o=r[a+e.POSITION_A_OFFSET],s=r[a+e.POSITION_A_OFFSET+1],c=r[a+e.POSITION_A_OFFSET+2],h=r[a+e.POSITION_B_OFFSET],g=r[a+e.POSITION_B_OFFSET+1],_=r[a+e.POSITION_B_OFFSET+2],v=r[a+e.POSITION_C_OFFSET],y=r[a+e.POSITION_C_OFFSET+1],b=r[a+e.POSITION_C_OFFSET+2];l=Math.min(l,o,h,v),u=Math.min(u,s,g,y),d=Math.min(d,c,_,b),f=Math.max(f,o,h,v),p=Math.max(p,s,g,y),m=Math.max(m,c,_,b)}o[c]=l,o[c+1]=u,o[c+2]=d,o[c+3]=f,o[c+4]=p,o[c+5]=m}else{let e=n[s+3],t=n[s+7],r=(e-i)*6,a=(t-i)*6,l=o[r],u=o[r+1],d=o[r+2],f=o[r+3],p=o[r+4],m=o[r+5],h=o[a],g=o[a+1],_=o[a+2],v=o[a+3],y=o[a+4],b=o[a+5];n[s]=l,n[s+1]=u,n[s+2]=d,n[s+4]=f,n[s+5]=p,n[s+6]=m,n[s+8]=h,n[s+9]=g,n[s+10]=_,n[s+12]=v,n[s+13]=y,n[s+14]=b,o[c]=Math.min(l,h),o[c+1]=Math.min(u,g),o[c+2]=Math.min(d,_),o[c+3]=Math.max(f,v),o[c+4]=Math.max(p,y),o[c+5]=Math.max(m,b)}}}refit(n,r,i){i!==this._boundsNodeCount&&(this._bounds=new Float32Array(i*6),this._boundsNodeCount=i);let a=this._bounds;for(let o=i-1;o>=0;o--){let i=o*16,s=o*6,c=n[i+3];if(c===-1){let o=n[i],c=n[i+1],l=1/0,u=1/0,d=1/0,f=-1/0,p=-1/0,m=-1/0;for(let n=0;n<c;n++){let i=(o+n)*t,a=r[i+e.POSITION_A_OFFSET],s=r[i+e.POSITION_A_OFFSET+1],c=r[i+e.POSITION_A_OFFSET+2],h=r[i+e.POSITION_B_OFFSET],g=r[i+e.POSITION_B_OFFSET+1],_=r[i+e.POSITION_B_OFFSET+2],v=r[i+e.POSITION_C_OFFSET],y=r[i+e.POSITION_C_OFFSET+1],b=r[i+e.POSITION_C_OFFSET+2];l=Math.min(l,a,h,v),u=Math.min(u,s,g,y),d=Math.min(d,c,_,b),f=Math.max(f,a,h,v),p=Math.max(p,s,g,y),m=Math.max(m,c,_,b)}a[s]=l,a[s+1]=u,a[s+2]=d,a[s+3]=f,a[s+4]=p,a[s+5]=m}else if(c===-2){let e=n[i]*6;a[s]=a[e],a[s+1]=a[e+1],a[s+2]=a[e+2],a[s+3]=a[e+3],a[s+4]=a[e+4],a[s+5]=a[e+5]}else{let e=n[i+3],t=n[i+7],r=e*6,o=t*6,c=a[r],l=a[r+1],u=a[r+2],d=a[r+3],f=a[r+4],p=a[r+5],m=a[o],h=a[o+1],g=a[o+2],_=a[o+3],v=a[o+4],y=a[o+5];n[i]=c,n[i+1]=l,n[i+2]=u,n[i+4]=d,n[i+5]=f,n[i+6]=p,n[i+8]=m,n[i+9]=h,n[i+10]=g,n[i+12]=_,n[i+13]=v,n[i+14]=y,a[s]=Math.min(c,m),a[s+1]=Math.min(l,h),a[s+2]=Math.min(u,g),a[s+3]=Math.max(d,_),a[s+4]=Math.max(f,v),a[s+5]=Math.max(p,y)}}}},r=null,i=null,a=null,o=null,s=0;self.onmessage=function(e){let{type:t}=e.data;if(t===`init`){r=new Float32Array(e.data.sharedBvhBuf),i=new Float32Array(e.data.sharedTriBuf),a=new Float32Array(e.data.sharedPosBuf),o=e.data.bvhToOriginal,s=r.length/16;return}if(t===`refit`)try{let e=performance.now();n.updateTrianglePositions(i,a,o),n.refit(r,i,s),self.postMessage({type:`refitComplete`,refitTimeMs:performance.now()-e})}catch(e){console.error(`[BVHRefitWorker] Refit error:`,e),self.postMessage({type:`error`,error:e.message})}}})();
|
|
2
|
+
//# sourceMappingURL=BVHRefitWorker-GkmNJYvb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BVHRefitWorker-GkmNJYvb.js","names":["FLOATS_PER_NODE"],"sources":["../../src/Processor/BVHRefitter.js","../../src/Processor/Workers/BVHRefitWorker.js"],"sourcesContent":["/**\n * BVHRefitter — Fast O(N) bottom-up BVH AABB refit for animated geometry.\n *\n * When mesh topology stays the same but vertex positions change (skeletal animation,\n * morph targets), this avoids the full O(N log N) SAH rebuild by recomputing only\n * the bounding boxes in the existing tree structure.\n *\n * Designed to run in both main thread and Web Worker contexts.\n */\n\n// Inline copy of layout constants (source of truth: EngineDefaults.js).\n// Cannot import because this runs inside Web Workers where window is not defined.\nconst TRIANGLE_DATA_LAYOUT = {\n\tFLOATS_PER_TRIANGLE: 32,\n\tPOSITION_A_OFFSET: 0,\n\tPOSITION_B_OFFSET: 4,\n\tPOSITION_C_OFFSET: 8,\n\tNORMAL_A_OFFSET: 12,\n\tNORMAL_B_OFFSET: 16,\n\tNORMAL_C_OFFSET: 20,\n};\n\nconst FPT = TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE;\nconst FLOATS_PER_NODE = 16; // 4 vec4s per BVH node\nconst LEAF_MARKER = - 1;\nconst BLAS_POINTER_MARKER = - 2;\n\nexport class BVHRefitter {\n\n\tconstructor() {\n\n\t\t// Reusable bounds buffer — cached across refit calls to avoid allocation per frame.\n\t\t// Resized only when nodeCount changes (i.e., new scene loaded).\n\t\tthis._bounds = null;\n\t\tthis._boundsNodeCount = 0;\n\n\t}\n\n\t/**\n\t * Update triangle positions in the BVH-reordered triangle array.\n\t * Iterates in BVH order (sequential writes, random reads) for cache efficiency.\n\t *\n\t * @param {Float32Array} triangleData - BVH-reordered triangle array (mutated in place)\n\t * @param {Float32Array} newPositions - 9 floats per triangle in ORIGINAL mesh order\n\t * @param {Uint32Array} bvhToOriginal - Map from BVH-order index to original tri index\n\t */\n\tupdateTrianglePositions( triangleData, newPositions, bvhToOriginal ) {\n\n\t\tconst triCount = bvhToOriginal.length;\n\n\t\tfor ( let bvhIdx = 0; bvhIdx < triCount; bvhIdx ++ ) {\n\n\t\t\tconst orig = bvhToOriginal[ bvhIdx ];\n\t\t\tconst dstOff = bvhIdx * FPT; // sequential writes\n\t\t\tconst srcOff = orig * 9;\n\n\t\t\tconst ax = newPositions[ srcOff ];\n\t\t\tconst ay = newPositions[ srcOff + 1 ];\n\t\t\tconst az = newPositions[ srcOff + 2 ];\n\t\t\tconst bx = newPositions[ srcOff + 3 ];\n\t\t\tconst by = newPositions[ srcOff + 4 ];\n\t\t\tconst bz = newPositions[ srcOff + 5 ];\n\t\t\tconst cx = newPositions[ srcOff + 6 ];\n\t\t\tconst cy = newPositions[ srcOff + 7 ];\n\t\t\tconst cz = newPositions[ srcOff + 8 ];\n\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET ] = ax;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 1 ] = ay;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 2 ] = az;\n\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET ] = bx;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 1 ] = by;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 2 ] = bz;\n\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET ] = cx;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 1 ] = cy;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 2 ] = cz;\n\n\t\t\t// Compute unnormalized face normal from cross product.\n\t\t\t// Skip sqrt normalization — the path tracer shader normalizes during shading.\n\t\t\tconst abx = bx - ax, aby = by - ay, abz = bz - az;\n\t\t\tconst acx = cx - ax, acy = cy - ay, acz = cz - az;\n\t\t\tconst nx = aby * acz - abz * acy;\n\t\t\tconst ny = abz * acx - abx * acz;\n\t\t\tconst nz = abx * acy - aby * acx;\n\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_A_OFFSET ] = nx;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_A_OFFSET + 1 ] = ny;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_A_OFFSET + 2 ] = nz;\n\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_B_OFFSET ] = nx;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_B_OFFSET + 1 ] = ny;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_B_OFFSET + 2 ] = nz;\n\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_C_OFFSET ] = nx;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_C_OFFSET + 1 ] = ny;\n\t\t\ttriangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_C_OFFSET + 2 ] = nz;\n\n\t\t}\n\n\t}\n\n\t/**\n\t * Refit a BLAS sub-range within the combined BVH buffer.\n\t * Same algorithm as refit() but scoped to nodes [startNode, startNode + count).\n\t *\n\t * @param {Float32Array} bvhData - Combined BVH array (TLAS + all BLASes)\n\t * @param {Float32Array} triangleData - Global triangle data\n\t * @param {number} startNode - First node index of this BLAS in bvhData\n\t * @param {number} nodeCount - Number of nodes in this BLAS\n\t */\n\trefitRange( bvhData, triangleData, startNode, nodeCount ) {\n\n\t\t// Grow-only bounds buffer to avoid reallocation on mixed-size BLASes\n\t\tif ( nodeCount > this._boundsNodeCount ) {\n\n\t\t\tthis._bounds = new Float32Array( nodeCount * 6 );\n\t\t\tthis._boundsNodeCount = nodeCount;\n\n\t\t}\n\n\t\tconst bounds = this._bounds;\n\t\tconst endNode = startNode + nodeCount;\n\n\t\tfor ( let i = endNode - 1; i >= startNode; i -- ) {\n\n\t\t\tconst o = i * FLOATS_PER_NODE;\n\t\t\tconst b = ( i - startNode ) * 6; // bounds indexed relative to BLAS start\n\n\t\t\tif ( bvhData[ o + 3 ] === LEAF_MARKER ) {\n\n\t\t\t\tconst triOffset = bvhData[ o ];\n\t\t\t\tconst triCount = bvhData[ o + 1 ];\n\n\t\t\t\tlet minX = Infinity, minY = Infinity, minZ = Infinity;\n\t\t\t\tlet maxX = - Infinity, maxY = - Infinity, maxZ = - Infinity;\n\n\t\t\t\tfor ( let t = 0; t < triCount; t ++ ) {\n\n\t\t\t\t\tconst tOff = ( triOffset + t ) * FPT;\n\t\t\t\t\tconst ax = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET ];\n\t\t\t\t\tconst ay = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 1 ];\n\t\t\t\t\tconst az = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 2 ];\n\t\t\t\t\tconst bx = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET ];\n\t\t\t\t\tconst by = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 1 ];\n\t\t\t\t\tconst bz = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 2 ];\n\t\t\t\t\tconst cx = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET ];\n\t\t\t\t\tconst cy = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 1 ];\n\t\t\t\t\tconst cz = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 2 ];\n\n\t\t\t\t\tminX = Math.min( minX, ax, bx, cx );\n\t\t\t\t\tminY = Math.min( minY, ay, by, cy );\n\t\t\t\t\tminZ = Math.min( minZ, az, bz, cz );\n\t\t\t\t\tmaxX = Math.max( maxX, ax, bx, cx );\n\t\t\t\t\tmaxY = Math.max( maxY, ay, by, cy );\n\t\t\t\t\tmaxZ = Math.max( maxZ, az, bz, cz );\n\n\t\t\t\t}\n\n\t\t\t\tbounds[ b ] = minX;\n\t\t\t\tbounds[ b + 1 ] = minY;\n\t\t\t\tbounds[ b + 2 ] = minZ;\n\t\t\t\tbounds[ b + 3 ] = maxX;\n\t\t\t\tbounds[ b + 4 ] = maxY;\n\t\t\t\tbounds[ b + 5 ] = maxZ;\n\n\t\t\t} else {\n\n\t\t\t\t// Inner node — child indices are absolute, but bounds index relative to startNode\n\t\t\t\tconst leftIdx = bvhData[ o + 3 ];\n\t\t\t\tconst rightIdx = bvhData[ o + 7 ];\n\t\t\t\tconst lb = ( leftIdx - startNode ) * 6;\n\t\t\t\tconst rb = ( rightIdx - startNode ) * 6;\n\n\t\t\t\tconst lMinX = bounds[ lb ];\n\t\t\t\tconst lMinY = bounds[ lb + 1 ];\n\t\t\t\tconst lMinZ = bounds[ lb + 2 ];\n\t\t\t\tconst lMaxX = bounds[ lb + 3 ];\n\t\t\t\tconst lMaxY = bounds[ lb + 4 ];\n\t\t\t\tconst lMaxZ = bounds[ lb + 5 ];\n\n\t\t\t\tconst rMinX = bounds[ rb ];\n\t\t\t\tconst rMinY = bounds[ rb + 1 ];\n\t\t\t\tconst rMinZ = bounds[ rb + 2 ];\n\t\t\t\tconst rMaxX = bounds[ rb + 3 ];\n\t\t\t\tconst rMaxY = bounds[ rb + 4 ];\n\t\t\t\tconst rMaxZ = bounds[ rb + 5 ];\n\n\t\t\t\tbvhData[ o ] = lMinX;\n\t\t\t\tbvhData[ o + 1 ] = lMinY;\n\t\t\t\tbvhData[ o + 2 ] = lMinZ;\n\t\t\t\tbvhData[ o + 4 ] = lMaxX;\n\t\t\t\tbvhData[ o + 5 ] = lMaxY;\n\t\t\t\tbvhData[ o + 6 ] = lMaxZ;\n\n\t\t\t\tbvhData[ o + 8 ] = rMinX;\n\t\t\t\tbvhData[ o + 9 ] = rMinY;\n\t\t\t\tbvhData[ o + 10 ] = rMinZ;\n\t\t\t\tbvhData[ o + 12 ] = rMaxX;\n\t\t\t\tbvhData[ o + 13 ] = rMaxY;\n\t\t\t\tbvhData[ o + 14 ] = rMaxZ;\n\n\t\t\t\tbounds[ b ] = Math.min( lMinX, rMinX );\n\t\t\t\tbounds[ b + 1 ] = Math.min( lMinY, rMinY );\n\t\t\t\tbounds[ b + 2 ] = Math.min( lMinZ, rMinZ );\n\t\t\t\tbounds[ b + 3 ] = Math.max( lMaxX, rMaxX );\n\t\t\t\tbounds[ b + 4 ] = Math.max( lMaxY, rMaxY );\n\t\t\t\tbounds[ b + 5 ] = Math.max( lMaxZ, rMaxZ );\n\n\t\t\t}\n\n\t\t}\n\n\t}\n\n\t/**\n\t * Bottom-up refit of all BVH node AABBs.\n\t * Reverse pre-order iteration gives valid bottom-up order (children have higher\n\t * indices than parents in pre-order, so reversing processes children first).\n\t *\n\t * @param {Float32Array} bvhData - Flat BVH array (mutated in place)\n\t * @param {Float32Array} triangleData - Updated triangle data\n\t * @param {number} nodeCount - Total number of BVH nodes\n\t */\n\trefit( bvhData, triangleData, nodeCount ) {\n\n\t\t// Reuse bounds buffer across frames (reallocate only on scene change)\n\t\tif ( nodeCount !== this._boundsNodeCount ) {\n\n\t\t\tthis._bounds = new Float32Array( nodeCount * 6 );\n\t\t\tthis._boundsNodeCount = nodeCount;\n\n\t\t}\n\n\t\tconst bounds = this._bounds;\n\n\t\t// Reverse iteration: bottom-up in pre-order layout\n\t\tfor ( let i = nodeCount - 1; i >= 0; i -- ) {\n\n\t\t\tconst o = i * FLOATS_PER_NODE;\n\t\t\tconst b = i * 6;\n\n\t\t\tconst marker = bvhData[ o + 3 ];\n\n\t\t\tif ( marker === LEAF_MARKER ) {\n\n\t\t\t\t// Triangle leaf: compute AABB from triangles\n\t\t\t\tconst triOffset = bvhData[ o ];\n\t\t\t\tconst triCount = bvhData[ o + 1 ];\n\n\t\t\t\tlet minX = Infinity, minY = Infinity, minZ = Infinity;\n\t\t\t\tlet maxX = - Infinity, maxY = - Infinity, maxZ = - Infinity;\n\n\t\t\t\tfor ( let t = 0; t < triCount; t ++ ) {\n\n\t\t\t\t\tconst tOff = ( triOffset + t ) * FPT;\n\n\t\t\t\t\t// Position A\n\t\t\t\t\tconst ax = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET ];\n\t\t\t\t\tconst ay = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 1 ];\n\t\t\t\t\tconst az = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 2 ];\n\t\t\t\t\t// Position B\n\t\t\t\t\tconst bx = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET ];\n\t\t\t\t\tconst by = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 1 ];\n\t\t\t\t\tconst bz = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 2 ];\n\t\t\t\t\t// Position C\n\t\t\t\t\tconst cx = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET ];\n\t\t\t\t\tconst cy = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 1 ];\n\t\t\t\t\tconst cz = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 2 ];\n\n\t\t\t\t\tminX = Math.min( minX, ax, bx, cx );\n\t\t\t\t\tminY = Math.min( minY, ay, by, cy );\n\t\t\t\t\tminZ = Math.min( minZ, az, bz, cz );\n\t\t\t\t\tmaxX = Math.max( maxX, ax, bx, cx );\n\t\t\t\t\tmaxY = Math.max( maxY, ay, by, cy );\n\t\t\t\t\tmaxZ = Math.max( maxZ, az, bz, cz );\n\n\t\t\t\t}\n\n\t\t\t\tbounds[ b ] = minX;\n\t\t\t\tbounds[ b + 1 ] = minY;\n\t\t\t\tbounds[ b + 2 ] = minZ;\n\t\t\t\tbounds[ b + 3 ] = maxX;\n\t\t\t\tbounds[ b + 4 ] = maxY;\n\t\t\t\tbounds[ b + 5 ] = maxZ;\n\n\t\t\t} else if ( marker === BLAS_POINTER_MARKER ) {\n\n\t\t\t\t// BLAS-pointer leaf (TLAS): read BLAS root node's bounds (already computed)\n\t\t\t\tconst blasRoot = bvhData[ o ];\n\t\t\t\tconst br = blasRoot * 6;\n\t\t\t\tbounds[ b ] = bounds[ br ];\n\t\t\t\tbounds[ b + 1 ] = bounds[ br + 1 ];\n\t\t\t\tbounds[ b + 2 ] = bounds[ br + 2 ];\n\t\t\t\tbounds[ b + 3 ] = bounds[ br + 3 ];\n\t\t\t\tbounds[ b + 4 ] = bounds[ br + 4 ];\n\t\t\t\tbounds[ b + 5 ] = bounds[ br + 5 ];\n\n\t\t\t} else {\n\n\t\t\t\t// Inner node: union children bounds (already computed since we iterate in reverse)\n\t\t\t\tconst leftIdx = bvhData[ o + 3 ];\n\t\t\t\tconst rightIdx = bvhData[ o + 7 ];\n\t\t\t\tconst lb = leftIdx * 6;\n\t\t\t\tconst rb = rightIdx * 6;\n\n\t\t\t\tconst lMinX = bounds[ lb ];\n\t\t\t\tconst lMinY = bounds[ lb + 1 ];\n\t\t\t\tconst lMinZ = bounds[ lb + 2 ];\n\t\t\t\tconst lMaxX = bounds[ lb + 3 ];\n\t\t\t\tconst lMaxY = bounds[ lb + 4 ];\n\t\t\t\tconst lMaxZ = bounds[ lb + 5 ];\n\n\t\t\t\tconst rMinX = bounds[ rb ];\n\t\t\t\tconst rMinY = bounds[ rb + 1 ];\n\t\t\t\tconst rMinZ = bounds[ rb + 2 ];\n\t\t\t\tconst rMaxX = bounds[ rb + 3 ];\n\t\t\t\tconst rMaxY = bounds[ rb + 4 ];\n\t\t\t\tconst rMaxZ = bounds[ rb + 5 ];\n\n\t\t\t\t// Write left child AABB into bvhData\n\t\t\t\tbvhData[ o ] = lMinX;\n\t\t\t\tbvhData[ o + 1 ] = lMinY;\n\t\t\t\tbvhData[ o + 2 ] = lMinZ;\n\t\t\t\t// o+3 = leftChildIdx (preserved)\n\t\t\t\tbvhData[ o + 4 ] = lMaxX;\n\t\t\t\tbvhData[ o + 5 ] = lMaxY;\n\t\t\t\tbvhData[ o + 6 ] = lMaxZ;\n\t\t\t\t// o+7 = rightChildIdx (preserved)\n\n\t\t\t\t// Write right child AABB into bvhData\n\t\t\t\tbvhData[ o + 8 ] = rMinX;\n\t\t\t\tbvhData[ o + 9 ] = rMinY;\n\t\t\t\tbvhData[ o + 10 ] = rMinZ;\n\t\t\t\t// o+11 = 0 padding\n\t\t\t\tbvhData[ o + 12 ] = rMaxX;\n\t\t\t\tbvhData[ o + 13 ] = rMaxY;\n\t\t\t\tbvhData[ o + 14 ] = rMaxZ;\n\t\t\t\t// o+15 = 0 padding\n\n\t\t\t\t// Store this node's bounds as union of children\n\t\t\t\tbounds[ b ] = Math.min( lMinX, rMinX );\n\t\t\t\tbounds[ b + 1 ] = Math.min( lMinY, rMinY );\n\t\t\t\tbounds[ b + 2 ] = Math.min( lMinZ, rMinZ );\n\t\t\t\tbounds[ b + 3 ] = Math.max( lMaxX, rMaxX );\n\t\t\t\tbounds[ b + 4 ] = Math.max( lMaxY, rMaxY );\n\t\t\t\tbounds[ b + 5 ] = Math.max( lMaxZ, rMaxZ );\n\n\t\t\t}\n\n\t\t}\n\n\t}\n\n}\n","/**\n * BVHRefitWorker — Off-main-thread BVH refit using SharedArrayBuffer.\n *\n * Protocol:\n * 'init' → receives SharedArrayBuffers + index map (once per scene)\n * 'refit' → reads shared positions, writes shared bvh/tri data (per frame)\n */\n\nimport { BVHRefitter } from '../BVHRefitter.js';\n\nconst FLOATS_PER_NODE = 16;\nconst refitter = new BVHRefitter();\n\n// Cached shared memory views (set once on 'init', reused every frame)\nlet bvhData = null;\nlet triData = null;\nlet posData = null;\nlet bvhToOriginal = null;\nlet nodeCount = 0;\n\nself.onmessage = function ( e ) {\n\n\tconst { type } = e.data;\n\n\tif ( type === 'init' ) {\n\n\t\tbvhData = new Float32Array( e.data.sharedBvhBuf );\n\t\ttriData = new Float32Array( e.data.sharedTriBuf );\n\t\tposData = new Float32Array( e.data.sharedPosBuf );\n\t\tbvhToOriginal = e.data.bvhToOriginal; // transferred Uint32Array\n\t\tnodeCount = bvhData.length / FLOATS_PER_NODE;\n\t\treturn;\n\n\t}\n\n\tif ( type === 'refit' ) {\n\n\t\ttry {\n\n\t\t\tconst startTime = performance.now();\n\n\t\t\trefitter.updateTrianglePositions( triData, posData, bvhToOriginal );\n\t\t\trefitter.refit( bvhData, triData, nodeCount );\n\n\t\t\tself.postMessage( {\n\t\t\t\ttype: 'refitComplete',\n\t\t\t\trefitTimeMs: performance.now() - startTime\n\t\t\t} );\n\n\t\t} catch ( error ) {\n\n\t\t\tconsole.error( '[BVHRefitWorker] Refit error:', error );\n\t\t\tself.postMessage( { type: 'error', error: error.message } );\n\n\t\t}\n\n\t}\n\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(function(){var e=class{constructor(e,t){this.traversalCost=e,this.intersectionCost=t,this.maxTreeletLeaves=7,this.minImprovement=.02,this.topologyCache=new Map;for(let e=3;e<=this.maxTreeletLeaves;e++)this.topologyCache.set(e,this.generateTopologies(e));this.stats={treeletsProcessed:0,treeletsImproved:0,totalSAHImprovement:0,averageSAHImprovement:0,optimizationTime:0}}generateTopologies(e){if(e===1)return[0];if(e===2)return[[0,1]];let t=[];for(let n=1;n<e;n++){let r=this.generateTopologies(n),i=this.generateTopologies(e-n);for(let e of r)for(let r of i)t.push([e,this.offsetTopology(r,n)])}return t}offsetTopology(e,t){return typeof e==`number`?e+t:[this.offsetTopology(e[0],t),this.offsetTopology(e[1],t)]}optimizeBVH(e){let t=performance.now();this.stats={treeletsProcessed:0,treeletsImproved:0,totalSAHImprovement:0,averageSAHImprovement:0,optimizationTime:0};let n=this.identifyTreeletRoots(e);for(let e=0;e<n.length;e++){if(performance.now()-t>3e4){console.warn(`TreeletOptimizer: timeout after ${e}/${n.length} treelets`);break}this.optimizeTreelet(n[e])}return this.stats.optimizationTime=performance.now()-t,this.stats.averageSAHImprovement=this.stats.treeletsProcessed>0?this.stats.totalSAHImprovement/this.stats.treeletsProcessed:0,e}identifyTreeletRoots(e){let t=[],n=new Set,r=[{node:e,visited:!1}];for(;r.length>0;){let e=r[r.length-1];if(e.visited){r.pop();let i=e.node;if(i.triangleCount>0||n.has(i))continue;let a=this.countLeaves(i);a>=3&&a<=this.maxTreeletLeaves&&(t.push(i),this.markSubtree(i,n))}else{e.visited=!0;let t=e.node;if(t.triangleCount>0)continue;t.rightChild&&r.push({node:t.rightChild,visited:!1}),t.leftChild&&r.push({node:t.leftChild,visited:!1})}}return t}countLeaves(e){return e?e.triangleCount>0?1:this.countLeaves(e.leftChild)+this.countLeaves(e.rightChild):0}markSubtree(e,t){e&&(t.add(e),!(e.triangleCount>0)&&(this.markSubtree(e.leftChild,t),this.markSubtree(e.rightChild,t)))}optimizeTreelet(e){let t=[];this.extractLeaves(e,t);let n=t.length;if(n<3||n>this.maxTreeletLeaves)return;this.stats.treeletsProcessed++;let r=this.evaluateSubtreeSAH(e),i=this.topologyCache.get(n);if(!i||i.length===0)return;let a=r,o=null,s=null;if(n<=5){let e=this.generatePermutations(n);for(let n of i)for(let r of e){let e=this.evaluateTopology(n,t,r);e<a&&(a=e,o=n,s=r)}}else{let e=Array.from({length:n},(e,t)=>t);for(let n of i){let r=this.evaluateTopology(n,t,e);r<a&&(a=r,o=n,s=e);let i=this.greedySwapOptimize(n,t,e,r);i.cost<a&&(a=i.cost,o=n,s=i.perm)}}let c=(r-a)/r;o&&c>this.minImprovement&&(this.reconstructTreelet(e,o,t,s),this.stats.treeletsImproved++,this.stats.totalSAHImprovement+=c)}extractLeaves(e,t){if(e){if(e.triangleCount>0){t.push({minX:e.minX,minY:e.minY,minZ:e.minZ,maxX:e.maxX,maxY:e.maxY,maxZ:e.maxZ,triangleOffset:e.triangleOffset,triangleCount:e.triangleCount});return}this.extractLeaves(e.leftChild,t),this.extractLeaves(e.rightChild,t)}}evaluateSubtreeSAH(e){if(!e)return 0;if(e.triangleCount>0)return this.surfaceAreaFlat(e.minX,e.minY,e.minZ,e.maxX,e.maxY,e.maxZ)*e.triangleCount*this.intersectionCost;let t=this.evaluateSubtreeSAH(e.leftChild),n=this.evaluateSubtreeSAH(e.rightChild);return this.surfaceAreaFlat(e.minX,e.minY,e.minZ,e.maxX,e.maxY,e.maxZ)*this.traversalCost+t+n}evaluateTopology(e,t,n){return this.evalTopoRecursive(e,t,n).cost}evalTopoRecursive(e,t,n){if(typeof e==`number`){let r=t[n[e]];return{cost:this.surfaceAreaFlat(r.minX,r.minY,r.minZ,r.maxX,r.maxY,r.maxZ)*r.triangleCount*this.intersectionCost,minX:r.minX,minY:r.minY,minZ:r.minZ,maxX:r.maxX,maxY:r.maxY,maxZ:r.maxZ}}let r=this.evalTopoRecursive(e[0],t,n),i=this.evalTopoRecursive(e[1],t,n),a=Math.min(r.minX,i.minX),o=Math.min(r.minY,i.minY),s=Math.min(r.minZ,i.minZ),c=Math.max(r.maxX,i.maxX),l=Math.max(r.maxY,i.maxY),u=Math.max(r.maxZ,i.maxZ);return{cost:this.surfaceAreaFlat(a,o,s,c,l,u)*this.traversalCost+r.cost+i.cost,minX:a,minY:o,minZ:s,maxX:c,maxY:l,maxZ:u}}surfaceAreaFlat(e,t,n,r,i,a){let o=r-e,s=i-t,c=a-n;return 2*(o*s+s*c+c*o)}generatePermutations(e){let t=[],n=Array.from({length:e},(e,t)=>t),r=i=>{if(i===e){t.push([...n]);return}for(let t=i;t<e;t++)[n[i],n[t]]=[n[t],n[i]],r(i+1),[n[i],n[t]]=[n[t],n[i]]};return r(0),t}greedySwapOptimize(e,t,n,r){let i=[...n],a=r,o=!0;for(;o;){o=!1;for(let n=0;n<i.length-1;n++)for(let r=n+1;r<i.length;r++){[i[n],i[r]]=[i[r],i[n]];let s=this.evaluateTopology(e,t,i);s<a?(a=s,o=!0):[i[n],i[r]]=[i[r],i[n]]}}return{perm:i,cost:a}}reconstructTreelet(e,t,n,r){let i=this.buildSubtree(t,n,r);e.minX=i.minX,e.minY=i.minY,e.minZ=i.minZ,e.maxX=i.maxX,e.maxY=i.maxY,e.maxZ=i.maxZ,e.leftChild=i.leftChild,e.rightChild=i.rightChild,e.triangleOffset=i.triangleOffset,e.triangleCount=i.triangleCount}buildSubtree(e,n,r){if(typeof e==`number`){let i=n[r[e]],a=new t;return a.minX=i.minX,a.minY=i.minY,a.minZ=i.minZ,a.maxX=i.maxX,a.maxY=i.maxY,a.maxZ=i.maxZ,a.triangleOffset=i.triangleOffset,a.triangleCount=i.triangleCount,a}let i=this.buildSubtree(e[0],n,r),a=this.buildSubtree(e[1],n,r),o=new t;return o.leftChild=i,o.rightChild=a,o.minX=Math.min(i.minX,a.minX),o.minY=Math.min(i.minY,a.minY),o.minZ=Math.min(i.minZ,a.minZ),o.maxX=Math.max(i.maxX,a.maxX),o.maxY=Math.max(i.maxY,a.maxY),o.maxZ=Math.max(i.maxZ,a.maxZ),o}setTreeletSize(e){this.maxTreeletLeaves=Math.max(3,Math.min(7,e));for(let e=3;e<=this.maxTreeletLeaves;e++)this.topologyCache.has(e)||this.topologyCache.set(e,this.generateTopologies(e))}setMinImprovement(e){this.minImprovement=Math.max(.001,e)}setMaxTreelets(){}getStatistics(){return{...this.stats}}},t=class{constructor(){this.minX=0,this.minY=0,this.minZ=0,this.maxX=0,this.maxY=0,this.maxZ=0,this.leftChild=null,this.rightChild=null,this.triangleOffset=0,this.triangleCount=0}},n=class{constructor(e,t){this.traversalCost=e,this.intersectionCost=t,this.batchSizeRatio=.02,this.maxIterations=2,this.timeBudgetMs=15e3,this.stats={reinsertionsApplied:0,iterations:0,timeMs:0}}setBatchSizeRatio(e){this.batchSizeRatio=Math.max(.005,Math.min(.1,e))}setMaxIterations(e){this.maxIterations=Math.max(1,Math.min(5,e))}getStatistics(){return{...this.stats}}surfaceArea(e){let t=e.maxX-e.minX,n=e.maxY-e.minY,r=e.maxZ-e.minZ;return t*n+n*r+r*t}buildParentMap(e){let t=new Map;t.set(e,{parent:null,isLeft:!1});let n=[e];for(;n.length>0;){let e=n.pop();e.triangleCount>0||(e.leftChild&&(t.set(e.leftChild,{parent:e,isLeft:!0}),n.push(e.leftChild)),e.rightChild&&(t.set(e.rightChild,{parent:e,isLeft:!1}),n.push(e.rightChild)))}return t}findCandidates(e,t,n){let r=[],i=[e];for(;i.length>0;){let a=i.pop();if(a!==e&&n.get(a).parent!==e){let e=this.surfaceArea(a);r.length<t?(r.push({node:a,cost:e}),r.length===t&&this._heapify(r)):e>r[0].cost&&(r[0]={node:a,cost:e},this._siftDown(r,0))}a.triangleCount===0&&(a.leftChild&&i.push(a.leftChild),a.rightChild&&i.push(a.rightChild))}return r}_heapify(e){for(let t=(e.length>>1)-1;t>=0;t--)this._siftDown(e,t)}_siftDown(e,t){let n=e.length;for(;;){let r=t,i=2*t+1,a=2*t+2;if(i<n&&e[i].cost<e[r].cost&&(r=i),a<n&&e[a].cost<e[r].cost&&(r=a),r===t)break;let o=e[t];e[t]=e[r],e[r]=o,t=r}}findReinsertion(e,t,n){let r=n.get(e),i=r.parent;if(!i)return null;let a=r.isLeft?i.rightChild:i.leftChild,o=this.surfaceArea(e),s=this.surfaceArea(i),c=null,l=0,u=s,d=a.minX,f=a.minY,p=a.minZ,m=a.maxX,h=a.maxY,g=a.maxZ,_=a,v=i,y=[];do{for(y.length=0,y.push(u,_);y.length>0;){let t=y.pop(),n=y.pop();if(n-o<=l)continue;let r=Math.min(t.minX,e.minX),i=Math.min(t.minY,e.minY),a=Math.min(t.minZ,e.minZ),s=Math.max(t.maxX,e.maxX),u=Math.max(t.maxY,e.maxY),d=Math.max(t.maxZ,e.maxZ),f=s-r,p=u-i,m=d-a,h=n-(f*p+p*m+m*f);if(h>l&&(c=t,l=h),t.triangleCount===0&&t.leftChild&&t.rightChild){let e=h+this.surfaceArea(t);y.push(e,t.leftChild),y.push(e,t.rightChild)}}let t=n.get(v);if(!t||t.parent===null)break;if(v!==i){d=Math.min(d,_.minX),f=Math.min(f,_.minY),p=Math.min(p,_.minZ),m=Math.max(m,_.maxX),h=Math.max(h,_.maxY),g=Math.max(g,_.maxZ);let e=m-d,t=h-f,n=g-p,r=e*t+t*n+n*e;u+=this.surfaceArea(v)-r}let r=t.parent;_=t.isLeft?r.rightChild:r.leftChild,v=r}while(n.get(v).parent!==null);return c===a||c===i?null:c?{from:e,to:c,areaDiff:l}:null}getConflicts(e,t,n){let r=n.get(e);return[t,e,r.isLeft?r.parent.rightChild:r.parent.leftChild,n.get(t).parent,r.parent]}reinsertNode(e,t,n){let r=n.get(e),i=r.parent,a=r.isLeft?i.rightChild:i.leftChild,o=n.get(i),s=o.parent,c=n.get(t),l=c.parent;o.isLeft?s.leftChild=a:s.rightChild=a,i.leftChild=e,i.rightChild=t,i.triangleOffset=0,i.triangleCount=0,i.minX=Math.min(e.minX,t.minX),i.minY=Math.min(e.minY,t.minY),i.minZ=Math.min(e.minZ,t.minZ),i.maxX=Math.max(e.maxX,t.maxX),i.maxY=Math.max(e.maxY,t.maxY),i.maxZ=Math.max(e.maxZ,t.maxZ),c.isLeft?l.leftChild=i:l.rightChild=i,n.set(a,{parent:s,isLeft:o.isLeft}),n.set(i,{parent:l,isLeft:c.isLeft}),n.set(e,{parent:i,isLeft:!0}),n.set(t,{parent:i,isLeft:!1}),this.refitFrom(s,n),this.refitFrom(l,n)}refitFrom(e,t){let n=e;for(;n;){if(n.triangleCount===0&&n.leftChild&&n.rightChild){let e=n.leftChild,t=n.rightChild;n.minX=Math.min(e.minX,t.minX),n.minY=Math.min(e.minY,t.minY),n.minZ=Math.min(e.minZ,t.minZ),n.maxX=Math.max(e.maxX,t.maxX),n.maxY=Math.max(e.maxY,t.maxY),n.maxZ=Math.max(e.maxZ,t.maxZ)}let e=t.get(n);n=e?e.parent:null}}optimizeBVH(e,t){let n=performance.now();this.stats={reinsertionsApplied:0,iterations:0,timeMs:0};for(let r=0;r<this.maxIterations&&!(performance.now()-n>this.timeBudgetMs);r++){let i=this.buildParentMap(e),a=i.size,o=Math.max(1,Math.floor(a*this.batchSizeRatio));t&&t(`Reinsertion iter ${r+1}/${this.maxIterations}: selecting ${o} candidates`);let s=this.findCandidates(e,o,i),c=[];for(let t=0;t<s.length&&!(performance.now()-n>this.timeBudgetMs);t++){let n=this.findReinsertion(s[t].node,e,i);n&&n.areaDiff>0&&c.push(n)}c.sort((e,t)=>t.areaDiff-e.areaDiff);let l=new Set,u=0;for(let e of c){let t=this.getConflicts(e.from,e.to,i);if(!t.some(e=>l.has(e))){for(let e of t)l.add(e);this.reinsertNode(e.from,e.to,i),u++}}if(this.stats.reinsertionsApplied+=u,this.stats.iterations=r+1,t&&t(`Reinsertion iter ${r+1}: applied ${u} reinsertions`),u===0)break}return this.stats.timeMs=performance.now()-n,this.stats}};let r={FLOATS_PER_TRIANGLE:32,POSITION_A_OFFSET:0,POSITION_B_OFFSET:4,POSITION_C_OFFSET:8,NORMAL_A_OFFSET:12,NORMAL_B_OFFSET:16,NORMAL_C_OFFSET:20,UV_AB_OFFSET:24,UV_C_MAT_OFFSET:28},i=r.FLOATS_PER_TRIANGLE;var a=class{constructor(){this.minX=0,this.minY=0,this.minZ=0,this.maxX=0,this.maxY=0,this.maxZ=0,this.leftChild=null,this.rightChild=null,this.triangleOffset=0,this.triangleCount=0}},o=class{constructor(){this.useWorker=!0,this.maxLeafSize=8,this.numBins=32,this.minBins=8,this.maxBins=64,this.totalNodes=0,this.processedTriangles=0,this.totalTriangles=0,this.lastProgressUpdate=0,this.progressUpdateInterval=100,this.traversalCost=1,this.intersectionCost=2.5,this.useMortonCodes=!0,this.mortonBits=10,this.mortonClusterThreshold=128,this.enableObjectMedianFallback=!0,this.enableSpatialMedianFallback=!0,this.splitStats={sahSplits:0,objectMedianSplits:0,spatialMedianSplits:0,failedSplits:0,avgBinsUsed:0,totalSplitAttempts:0,mortonSortTime:0,totalBuildTime:0,treeletOptimizationTime:0,treeletsProcessed:0,treeletsImproved:0,averageSAHImprovement:0,reinsertionOptimizationTime:0,reinsertionsApplied:0,reinsertionIterations:0},this.enableTreeletOptimization=!0,this.treeletSize=5,this.treeletOptimizationPasses=1,this.treeletMinImprovement=.02,this.maxTreeletDepth=3,this.maxTreeletsPerScene=20,this.treeletComplexityThreshold=5e4,this.enableReinsertionOptimization=!0,this.reinsertionBatchSizeRatio=.02,this.reinsertionMaxIterations=2,this.initializeBinArrays(),this._partResult={mid:0,lMinX:0,lMinY:0,lMinZ:0,lMaxX:0,lMaxY:0,lMaxZ:0,rMinX:0,rMinY:0,rMinZ:0,rMaxX:0,rMaxY:0,rMaxZ:0},this.centroids=null,this.bMin=null,this.bMax=null,this.indices=null,this.mortonCodes=null,this.triangles=null,this.reorderedTriangleData=null}initializeBinArrays(){let e=this.maxBins;this.binBoundsMin=new Float32Array(e*3),this.binBoundsMax=new Float32Array(e*3),this.binCounts=new Uint32Array(e),this.leftPrefixMin=new Float32Array(e*3),this.leftPrefixMax=new Float32Array(e*3),this.leftPrefixCount=new Uint32Array(e),this.rightPrefixMin=new Float32Array(e*3),this.rightPrefixMax=new Float32Array(e*3),this.rightPrefixCount=new Uint32Array(e)}getOptimalBinCount(e){return e<=16?this.minBins:e<=64?16:e<=256?32:e<=1024?48:this.maxBins}setAdaptiveBinConfig(e){e.minBins!==void 0&&(this.minBins=Math.max(4,e.minBins)),e.maxBins!==void 0&&(this.maxBins=Math.min(128,e.maxBins)),e.baseBins!==void 0&&(this.numBins=e.baseBins),e.maxBins!==void 0&&this.initializeBinArrays()}setMortonConfig(e){e.enabled!==void 0&&(this.useMortonCodes=e.enabled),e.bits!==void 0&&(this.mortonBits=Math.max(6,Math.min(10,e.bits))),e.threshold!==void 0&&(this.mortonClusterThreshold=Math.max(16,e.threshold))}setFallbackConfig(e){e.objectMedian!==void 0&&(this.enableObjectMedianFallback=e.objectMedian),e.spatialMedian!==void 0&&(this.enableSpatialMedianFallback=e.spatialMedian)}setTreeletConfig(e){e.enabled!==void 0&&(this.enableTreeletOptimization=e.enabled),e.size!==void 0&&(this.treeletSize=Math.max(3,Math.min(12,e.size))),e.passes!==void 0&&(this.treeletOptimizationPasses=Math.max(1,Math.min(3,e.passes))),e.minImprovement!==void 0&&(this.treeletMinImprovement=Math.max(.001,e.minImprovement))}disableTreeletOptimization(){this.enableTreeletOptimization=!1}setReinsertionConfig(e){e.enabled!==void 0&&(this.enableReinsertionOptimization=e.enabled),e.batchSizeRatio!==void 0&&(this.reinsertionBatchSizeRatio=Math.max(.005,Math.min(.1,e.batchSizeRatio))),e.maxIterations!==void 0&&(this.reinsertionMaxIterations=Math.max(1,Math.min(5,e.maxIterations)))}initializeTriangleArrays(){let e=this.totalTriangles,t=this.triangles,n=r.POSITION_A_OFFSET,a=r.POSITION_B_OFFSET,o=r.POSITION_C_OFFSET;for(let r=0;r<e;r++){let e=r*i,s=t[e+n],c=t[e+n+1],l=t[e+n+2],u=t[e+a],d=t[e+a+1],f=t[e+a+2],p=t[e+o],m=t[e+o+1],h=t[e+o+2],g=r*3;this.centroids[g]=(s+u+p)/3,this.centroids[g+1]=(c+d+m)/3,this.centroids[g+2]=(l+f+h)/3,this.bMin[g]=s<u?s<p?s:p:u<p?u:p,this.bMin[g+1]=c<d?c<m?c:m:d<m?d:m,this.bMin[g+2]=l<f?l<h?l:h:f<h?f:h,this.bMax[g]=s>u?s>p?s:p:u>p?u:p,this.bMax[g+1]=c>d?c>m?c:m:d>m?d:m,this.bMax[g+2]=l>f?l>h?l:h:f>h?f:h,this.indices[r]=r}}expandBits(e){return e=e*65537&4278190335,e=e*257&251719695,e=e*17&3272356035,e=e*5&1227133513,e}morton3D(e,t,n){return(this.expandBits(n)<<2)+(this.expandBits(t)<<1)+this.expandBits(e)}computeMortonCodeForIndex(e,t,n,r,i,a,o){let s=this.centroids,c=e*3,l=(1<<this.mortonBits)-1,u=i>0?(s[c]-t)/i:0,d=a>0?(s[c+1]-n)/a:0,f=o>0?(s[c+2]-r)/o:0,p=Math.max(0,Math.min(l,Math.floor(u*l))),m=Math.max(0,Math.min(l,Math.floor(d*l))),h=Math.max(0,Math.min(l,Math.floor(f*l)));return this.morton3D(p,m,h)}sortTrianglesByMortonCode(){let e=this.totalTriangles;if(!this.useMortonCodes||e<this.mortonClusterThreshold)return;let t=performance.now(),n=this.centroids,r=this.indices,i=1/0,a=1/0,o=1/0,s=-1/0,c=-1/0,l=-1/0;for(let t=0;t<e;t++){let e=r[t]*3,u=n[e],d=n[e+1],f=n[e+2];u<i&&(i=u),d<a&&(a=d),f<o&&(o=f),u>s&&(s=u),d>c&&(c=d),f>l&&(l=f)}let u=s-i,d=c-a,f=l-o,p=this.mortonCodes,m=(1<<this.mortonBits)-1,h=u>0?m/u:0,g=d>0?m/d:0,_=f>0?m/f:0;for(let t=0;t<e;t++){let e=r[t],s=e*3,c=(n[s]-i)*h,l=(n[s+1]-a)*g,u=(n[s+2]-o)*_;c=c<0?0:(c>m?m:c)|0,l=l<0?0:(l>m?m:l)|0,u=u<0?0:(u>m?m:u)|0,c=c*65537&4278190335,c=c*257&251719695,c=c*17&3272356035,c=c*5&1227133513,l=l*65537&4278190335,l=l*257&251719695,l=l*17&3272356035,l=l*5&1227133513,u=u*65537&4278190335,u=u*257&251719695,u=u*17&3272356035,u=u*5&1227133513,p[e]=(u<<2)+(l<<1)+c}let v=new Uint32Array(e),y=new Uint32Array(256);for(let t=0;t<32;t+=8){y.fill(0);for(let n=0;n<e;n++)y[p[r[n]]>>>t&255]++;let n=0;for(let e=0;e<256;e++){let t=y[e];y[e]=n,n+=t}for(let n=0;n<e;n++){let e=p[r[n]]>>>t&255;v[y[e]++]=r[n]}r.set(v)}this.splitStats.mortonSortTime+=performance.now()-t}build(e,t=30,n=null){return this.totalTriangles=e.byteLength/(i*4),this.processedTriangles=0,this.lastProgressUpdate=performance.now(),this.useWorker&&typeof Worker<`u`?new Promise((r,a)=>{try{let o=new Worker(new URL(``+new URL(`BVHWorker-DobVXMda.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=e=>_>0?(e/_*100).toFixed(1)+`%`:`0%`,y=[{Phase:`Init + bounds`,"Time (ms)":Math.round(this.splitStats.initTime),"%":v(this.splitStats.initTime)},{Phase:`Morton sort`,"Time (ms)":Math.round(this.splitStats.mortonSortTime),"%":v(this.splitStats.mortonSortTime)},{Phase:`SAH recursive build`,"Time (ms)":Math.round(this.splitStats.sahBuildTime),"%":v(this.splitStats.sahBuildTime)},{Phase:`Treelet optimization`,"Time (ms)":Math.round(this.splitStats.treeletOptimizationTime),"%":v(this.splitStats.treeletOptimizationTime)},{Phase:`Reinsertion optimization`,"Time (ms)":Math.round(this.splitStats.reinsertionOptimizationTime),"%":v(this.splitStats.reinsertionOptimizationTime)},{Phase:`SA ordering`,"Time (ms)":Math.round(this.splitStats.saOrderTime),"%":v(this.splitStats.saOrderTime)},{Phase:`Triangle reorder`,"Time (ms)":Math.round(this.splitStats.reorderTime),"%":v(this.splitStats.reorderTime)},{Phase:`TOTAL`,"Time (ms)":Math.round(_),"%":`100%`}];return console.groupCollapsed(`⏱ BVH Build (${c.toLocaleString()} triangles, ${Math.round(_)}ms)`),console.table(y),console.table({"Total Nodes":this.totalNodes,"Max Leaf Size":this.maxLeafSize,"SAH Splits":this.splitStats.sahSplits,"Object Median Splits":this.splitStats.objectMedianSplits,"Spatial Median Splits":this.splitStats.spatialMedianSplits,"Failed Splits":this.splitStats.failedSplits,"Treelets Processed":this.splitStats.treeletsProcessed,"Treelets Improved":this.splitStats.treeletsImproved,"Avg SAH Improvement":(this.splitStats.averageSAHImprovement*100).toFixed(2)+`%`,"Reinsertions Applied":this.splitStats.reinsertionsApplied,"Reinsertion Iterations":this.splitStats.reinsertionIterations}),console.groupEnd(),a&&a(100),this.centroids=null,this.bMin=null,this.bMax=null,this.mortonCodes=null,d}updateProgress(e,t){if(!t)return;this.processedTriangles+=e;let n=performance.now();n-this.lastProgressUpdate<this.progressUpdateInterval||(this.lastProgressUpdate=n,t(Math.min(Math.floor(this.processedTriangles/this.totalTriangles*100),99)))}buildNodeRecursiveToDepth(e,t,n,r,i,o,s,c,l,u,d){let f=new a;this.totalNodes++;let p=t-e;if(o===void 0?this.updateNodeBounds(f,e,t):(f.minX=o,f.minY=s,f.minZ=c,f.maxX=l,f.maxY=u,f.maxZ=d),p<=this.maxLeafSize||n<=0)return f.triangleOffset=e,f.triangleCount=p,this.updateProgress(p,i),f;if(r<=0&&p>this.maxLeafSize*16){let r=this.frontierTasks.length;return f.triangleOffset=e,f.triangleCount=p,f.isFrontier=!0,f.frontierTaskId=r,this.frontierTasks.push({taskId:r,start:e,end:t,depth:n,preMinX:f.minX,preMinY:f.minY,preMinZ:f.minZ,preMaxX:f.maxX,preMaxY:f.maxY,preMaxZ:f.maxZ}),f}let m=this.findBestSplitPositionSAH(e,t,f);if(!m.success){if(this.splitStats.failedSplits++,r>0||p<=this.maxLeafSize*16)return f.triangleOffset=e,f.triangleCount=p,this.updateProgress(p,i),f;let a=this.frontierTasks.length;return f.triangleOffset=e,f.triangleCount=p,f.isFrontier=!0,f.frontierTaskId=a,this.frontierTasks.push({taskId:a,start:e,end:t,depth:n,preMinX:f.minX,preMinY:f.minY,preMinZ:f.minZ,preMaxX:f.maxX,preMaxY:f.maxY,preMaxZ:f.maxZ}),f}m.method===`SAH`?this.splitStats.sahSplits++:m.method===`object_median`?this.splitStats.objectMedianSplits++:m.method===`spatial_median`&&this.splitStats.spatialMedianSplits++,this.partitionWithBounds(e,t,m.axis,m.pos);let h=this._partResult,g=h.mid,_=h.lMinX,v=h.lMinY,y=h.lMinZ,b=h.lMaxX,x=h.lMaxY,S=h.lMaxZ,C=h.rMinX,w=h.rMinY,T=h.rMinZ,E=h.rMaxX,D=h.rMaxY,O=h.rMaxZ;return g===e||g===t?(f.triangleOffset=e,f.triangleCount=p,this.updateProgress(p,i),f):(f.leftChild=this.buildNodeRecursiveToDepth(e,g,n-1,r-1,i,_,v,y,b,x,S),f.rightChild=this.buildNodeRecursiveToDepth(g,t,n-1,r-1,i,C,w,T,E,D,O),f)}buildNodeRecursive(e,t,n,r,i,o,s,c,l,u){let d=new a;this.totalNodes++;let f=t-e;if(i===void 0?this.updateNodeBounds(d,e,t):(d.minX=i,d.minY=o,d.minZ=s,d.maxX=c,d.maxY=l,d.maxZ=u),f<=this.maxLeafSize||n<=0)return d.triangleOffset=e,d.triangleCount=f,this.updateProgress(f,r),d;let p=this.findBestSplitPositionSAH(e,t,d);if(!p.success)return this.splitStats.failedSplits++,d.triangleOffset=e,d.triangleCount=f,this.updateProgress(f,r),d;p.method===`SAH`?this.splitStats.sahSplits++:p.method===`object_median`?this.splitStats.objectMedianSplits++:p.method===`spatial_median`&&this.splitStats.spatialMedianSplits++,this.partitionWithBounds(e,t,p.axis,p.pos);let m=this._partResult,h=m.mid,g=m.lMinX,_=m.lMinY,v=m.lMinZ,y=m.lMaxX,b=m.lMaxY,x=m.lMaxZ,S=m.rMinX,C=m.rMinY,w=m.rMinZ,T=m.rMaxX,E=m.rMaxY,D=m.rMaxZ;return h===e||h===t?(d.triangleOffset=e,d.triangleCount=f,this.updateProgress(f,r),d):(d.leftChild=this.buildNodeRecursive(e,h,n-1,r,g,_,v,y,b,x),d.rightChild=this.buildNodeRecursive(h,t,n-1,r,S,C,w,T,E,D),d)}partitionWithBounds(e,t,n,r){let i=this.indices,a=this.centroids,o=this.bMin,s=this.bMax,c=e,l=t-1,u=1/0,d=1/0,f=1/0,p=-1/0,m=-1/0,h=-1/0,g=1/0,_=1/0,v=1/0,y=-1/0,b=-1/0,x=-1/0;for(;c<=l;){let e=i[c],t=e*3;a[t+n]<=r?(o[t]<u&&(u=o[t]),o[t+1]<d&&(d=o[t+1]),o[t+2]<f&&(f=o[t+2]),s[t]>p&&(p=s[t]),s[t+1]>m&&(m=s[t+1]),s[t+2]>h&&(h=s[t+2]),c++):(o[t]<g&&(g=o[t]),o[t+1]<_&&(_=o[t+1]),o[t+2]<v&&(v=o[t+2]),s[t]>y&&(y=s[t]),s[t+1]>b&&(b=s[t+1]),s[t+2]>x&&(x=s[t+2]),i[c]=i[l],i[l]=e,l--)}let S=this._partResult;return S.mid=c,S.lMinX=u,S.lMinY=d,S.lMinZ=f,S.lMaxX=p,S.lMaxY=m,S.lMaxZ=h,S.rMinX=g,S.rMinY=_,S.rMinZ=v,S.rMaxX=y,S.rMaxY=b,S.rMaxZ=x,S}updateNodeBounds(e,t,n){let r=1/0,i=1/0,a=1/0,o=-1/0,s=-1/0,c=-1/0,l=this.indices,u=this.bMin,d=this.bMax;for(let e=t;e<n;e++){let t=l[e]*3;u[t]<r&&(r=u[t]),u[t+1]<i&&(i=u[t+1]),u[t+2]<a&&(a=u[t+2]),d[t]>o&&(o=d[t]),d[t+1]>s&&(s=d[t+1]),d[t+2]>c&&(c=d[t+2])}e.minX=r,e.minY=i,e.minZ=a,e.maxX=o,e.maxY=s,e.maxZ=c}findBestSplitPositionSAH(e,t,n){let r=1/0,i=-1,a=0,o=this.computeSurfaceAreaFlat(n.minX,n.minY,n.minZ,n.maxX,n.maxY,n.maxZ),s=t-e,c=this.intersectionCost*s,l=this.getOptimalBinCount(s);this.splitStats.totalSplitAttempts++,this.splitStats.avgBinsUsed=(this.splitStats.avgBinsUsed*(this.splitStats.totalSplitAttempts-1)+l)/this.splitStats.totalSplitAttempts;let u=this.indices,d=this.centroids,f=this.bMin,p=this.bMax,m=this.binBoundsMin,h=this.binBoundsMax,g=this.binCounts,_=this.leftPrefixMin,v=this.leftPrefixMax,y=this.leftPrefixCount,b=this.rightPrefixMin,x=this.rightPrefixMax,S=this.rightPrefixCount,C=1/0,w=-1/0,T=1/0,E=-1/0,D=1/0,O=-1/0;for(let n=e;n<t;n++){let e=u[n]*3,t=d[e],r=d[e+1],i=d[e+2];t<C&&(C=t),t>w&&(w=t),r<T&&(T=r),r>E&&(E=r),i<D&&(D=i),i>O&&(O=i)}let k=[C,T,D],A=[w,E,O];for(let n=0;n<3;n++){let s=k[n],C=A[n];if(C-s<1e-6)continue;for(let e=0;e<l;e++){g[e]=0;let t=e*3;m[t]=1/0,m[t+1]=1/0,m[t+2]=1/0,h[t]=-1/0,h[t+1]=-1/0,h[t+2]=-1/0}let w=l/(C-s);for(let r=e;r<t;r++){let e=u[r],t=d[e*3+n],i=Math.floor((t-s)*w);i>=l&&(i=l-1),g[i]++;let a=i*3,o=e*3;f[o]<m[a]&&(m[a]=f[o]),f[o+1]<m[a+1]&&(m[a+1]=f[o+1]),f[o+2]<m[a+2]&&(m[a+2]=f[o+2]),p[o]>h[a]&&(h[a]=p[o]),p[o+1]>h[a+1]&&(h[a+1]=p[o+1]),p[o+2]>h[a+2]&&(h[a+2]=p[o+2])}y[0]=g[0],_[0]=m[0],_[1]=m[1],_[2]=m[2],v[0]=h[0],v[1]=h[1],v[2]=h[2];for(let e=1;e<l;e++){let t=e*3,n=(e-1)*3;y[e]=y[e-1]+g[e];let r=_[n],i=m[t],a=_[n+1],o=m[t+1],s=_[n+2],c=m[t+2];_[t]=r<i?r:i,_[t+1]=a<o?a:o,_[t+2]=s<c?s:c;let l=v[n],u=h[t],d=v[n+1],f=h[t+1],p=v[n+2],b=h[t+2];v[t]=l>u?l:u,v[t+1]=d>f?d:f,v[t+2]=p>b?p:b}let T=l-1,E=T*3;S[T]=g[T],b[E]=m[E],b[E+1]=m[E+1],b[E+2]=m[E+2],x[E]=h[E],x[E+1]=h[E+1],x[E+2]=h[E+2];for(let e=T-1;e>=0;e--){let t=e*3,n=(e+1)*3;S[e]=S[e+1]+g[e];let r=b[n],i=m[t],a=b[n+1],o=m[t+1],s=b[n+2],c=m[t+2];b[t]=r<i?r:i,b[t+1]=a<o?a:o,b[t+2]=s<c?s:c;let l=x[n],u=h[t],d=x[n+1],f=h[t+1],p=x[n+2],_=h[t+2];x[t]=l>u?l:u,x[t+1]=d>f?d:f,x[t+2]=p>_?p:_}for(let e=1;e<l;e++){let t=(e-1)*3,u=e*3,d=y[e-1],f=S[e];if(d===0||f===0)continue;let p=v[t]-_[t],m=v[t+1]-_[t+1],h=v[t+2]-_[t+2],g=2*(p*m+m*h+h*p),w=x[u]-b[u],T=x[u+1]-b[u+1],E=x[u+2]-b[u+2],D=2*(w*T+T*E+E*w),O=this.traversalCost+g/o*d*this.intersectionCost+D/o*f*this.intersectionCost;O<r&&O<c&&(r=O,i=n,a=s+(C-s)*e/l)}}return i===-1?this.enableObjectMedianFallback?this.findObjectMedianSplit(e,t):this.enableSpatialMedianFallback?this.findSpatialMedianSplit(e,t):{success:!1,method:`fallbacks_disabled`}:{success:!0,axis:i,pos:a,method:`SAH`,binsUsed:l}}findObjectMedianSplit(e,t){let n=this.indices,r=this.centroids,i=-1,a=-1;for(let o=0;o<3;o++){let s=1/0,c=-1/0;for(let i=e;i<t;i++){let e=r[n[i]*3+o];e<s&&(s=e),e>c&&(c=e)}let l=c-s;l>a&&(a=l,i=o)}if(i===-1||a<1e-10)return this.enableSpatialMedianFallback?this.findSpatialMedianSplit(e,t):{success:!1,method:`object_median_failed`};let o=t-e,s=e+Math.floor(o/2);this.quickselect(e,t,s,i);let c=r[n[s]*3+i],l=!0;for(let e=s+1;e<t;e++)if(r[n[e]*3+i]>c){l=!1;break}if(l){let a=-1/0;for(let t=e;t<s;t++){let e=r[n[t]*3+i];e>a&&(a=e)}if(a<c)c=(a+c)*.5;else return this.enableSpatialMedianFallback?this.findSpatialMedianSplit(e,t):{success:!1,method:`object_median_degenerate`}}return{success:!0,axis:i,pos:c,method:`object_median`}}findSpatialMedianSplit(e,t){let n=this.indices,r=this.centroids,i=this.bMin,a=this.bMax,o=-1,s=-1,c=0,l=0;for(let r=0;r<3;r++){let u=1/0,d=-1/0;for(let o=e;o<t;o++){let e=n[o]*3+r;i[e]<u&&(u=i[e]),a[e]>d&&(d=a[e])}let f=d-u;f>s&&(s=f,o=r,c=u,l=d)}if(o===-1||s<1e-12)return{success:!1,method:`spatial_median_failed`};let u=(c+l)*.5,d=t-e,f=0;for(let i=e;i<t;i++)r[n[i]*3+o]<=u&&f++;if(f===0||f===d){let i=e+Math.floor(d/2);this.quickselect(e,t,i,o);let a=r[n[i]*3+o],s=!0;for(let i=e;i<t;i++)if(r[n[i]*3+o]!==a){s=!1;break}if(s)return{success:!1,method:`spatial_median_degenerate`};let c=-1/0;for(let t=e;t<i;t++){let e=r[n[t]*3+o];e>c&&(c=e)}if(c<a)u=(c+a)*.5;else{let e=1/0;for(let a=i+1;a<t;a++){let t=r[n[a]*3+o];t<e&&(e=t)}u=(a+e)*.5}}return{success:!0,axis:o,pos:u,method:`spatial_median`}}quickselect(e,t,n,r){let i=this.indices,a=this.centroids,o=e,s=t-1;for(;o<s;){let e=o+s>>>1,t=a[i[o]*3+r],c=a[i[e]*3+r],l=a[i[s]*3+r];if(t>c){let t=i[o];i[o]=i[e],i[e]=t}if(t>l){let e=i[o];i[o]=i[s],i[s]=e}if(c>l){let t=i[e];i[e]=i[s],i[s]=t}let u=a[i[e]*3+r],d=o,f=s;for(;d<=f;){for(;a[i[d]*3+r]<u;)d++;for(;a[i[f]*3+r]>u;)f--;if(d<=f){let e=i[d];i[d]=i[f],i[f]=e,d++,f--}}f<n&&(o=d),d>n&&(s=f)}}applySAOrdering(e){if(!e||!e.leftChild)return;let t=[e],n=[];for(;t.length>0;){let e=t.pop();!e.leftChild||!e.rightChild||(n.push(e),t.push(e.leftChild),t.push(e.rightChild))}for(let e=n.length-1;e>=0;e--){let t=n[e],r=t.leftChild,i=t.rightChild,a=r.maxX-r.minX,o=r.maxY-r.minY,s=r.maxZ-r.minZ,c=i.maxX-i.minX,l=i.maxY-i.minY,u=i.maxZ-i.minZ;c*l+l*u+u*c>a*o+o*s+s*a&&(t.leftChild=i,t.rightChild=r)}}flattenBVH(e){let t=[],n=[e];for(;n.length>0;){let e=n.pop();e._flatIndex=t.length,t.push(e),e.rightChild&&n.push(e.rightChild),e.leftChild&&n.push(e.leftChild)}let r=new Float32Array(t.length*16);for(let e=0;e<t.length;e++){let n=t[e],i=e*16;if(n.leftChild){let e=n.leftChild,t=n.rightChild;r[i]=e.minX,r[i+1]=e.minY,r[i+2]=e.minZ,r[i+3]=e._flatIndex,r[i+4]=e.maxX,r[i+5]=e.maxY,r[i+6]=e.maxZ,r[i+7]=t._flatIndex,r[i+8]=t.minX,r[i+9]=t.minY,r[i+10]=t.minZ,r[i+12]=t.maxX,r[i+13]=t.maxY,r[i+14]=t.maxZ}else r[i]=n.triangleOffset,r[i+1]=n.triangleCount,r[i+3]=-1}return r}flattenBVHWithFrontier(e){let t=[],n=[e];for(;n.length>0;){let e=n.pop();e._flatIndex=t.length,t.push(e),e.rightChild&&n.push(e.rightChild),e.leftChild&&n.push(e.leftChild)}let r=new Float32Array(t.length*16),i=[];for(let e=0;e<t.length;e++){let n=t[e],a=e*16;if(n.leftChild){let e=n.leftChild,t=n.rightChild;r[a]=e.minX,r[a+1]=e.minY,r[a+2]=e.minZ,r[a+3]=e._flatIndex,r[a+4]=e.maxX,r[a+5]=e.maxY,r[a+6]=e.maxZ,r[a+7]=t._flatIndex,r[a+8]=t.minX,r[a+9]=t.minY,r[a+10]=t.minZ,r[a+12]=t.maxX,r[a+13]=t.maxY,r[a+14]=t.maxZ}else if(n.isFrontier){let t=n.frontierTaskId;r[a]=n.triangleOffset,r[a+1]=n.triangleCount,r[a+2]=t,r[a+3]=-2,i.push({taskId:t,flatIndex:e})}else r[a]=n.triangleOffset,r[a+1]=n.triangleCount,r[a+3]=-1}return{flatData:r,frontierMap:i,nodeCount:t.length}}assembleParallelBVH(e,t,n,r){let i=[...r].sort((e,t)=>e.taskId-t.taskId),a=t;for(let e=0;e<i.length;e++)a+=i[e].nodeCount;let o=new Float32Array(a*16);o.set(e);let s=new Map;for(let e of n)s.set(e.taskId,e.flatIndex);let c=t;for(let e=0;e<i.length;e++){let t=i[e],n=t.flatData,r=t.nodeCount,a=c*16;o.set(n,a);for(let e=0;e<r;e++){let t=a+e*16;o[t+3]!==-1&&(o[t+3]+=c,o[t+7]+=c)}let l=s.get(t.taskId);if(l!==void 0){let e=l*16,t=a;for(let n=0;n<16;n++)o[e+n]=o[t+n]}c+=r}return o}computeSurfaceAreaFlat(e,t,n,r,i,a){let o=r-e,s=i-t,c=a-n;return 2*(o*s+s*c+c*o)}};self.onmessage=function(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-C02ZWVeG.js.map
|