rayzee 9.0.0 → 9.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -8
- package/dist/{NeuralRetouch-CYOJ21Wn.js → NeuralRetouch-BRquqfmZ.js} +1 -1
- package/dist/{NeuralSuperRes-CQLCLpcC.js → NeuralSuperRes-CbNvQjCw.js} +3 -2
- package/dist/{ToneMapGPU-BVE852iN.js → ToneMapGPU-DpUZDMbQ.js} +2 -1
- package/dist/rayzee.es.js +10353 -5337
- package/dist/rayzee.umd.js +265 -265
- package/package.json +1 -1
- package/src/AssetConfig.js +4 -0
- package/src/EngineDefaults.js +38 -56
- package/src/EngineEvents.js +3 -0
- package/src/EngineIssues.js +6 -0
- package/src/Headless.js +3 -1
- package/src/Passes/AIUpscaler.js +2 -1
- package/src/Passes/OIDNDenoiser.js +3 -6
- package/src/PathTracerApp.js +290 -19
- package/src/Processor/ArchiveCache.js +120 -0
- package/src/Processor/ArchiveReader.js +179 -32
- package/src/Processor/AssetLoader.js +514 -76
- package/src/Processor/AtmosphereModel.js +465 -0
- package/src/Processor/BVHBuilder.js +4 -1
- package/src/Processor/BVHLeafFold.js +100 -0
- package/src/Processor/BVHRefitter.js +98 -214
- package/src/Processor/ChunkedRecords.js +189 -4
- package/src/Processor/EmissiveTriangleBuilder.js +8 -0
- package/src/Processor/GeometryExtractor.js +238 -40
- package/src/Processor/InstanceTable.js +55 -37
- package/src/Processor/KernelManager.js +4 -1
- package/src/Processor/PBRT/PBRTCurves.js +104 -27
- package/src/Processor/PBRT/PBRTParser.js +132 -22
- package/src/Processor/PBRT/PBRTSceneBuilder.js +192 -49
- package/src/Processor/PBRT/buffers.js +27 -0
- package/src/Processor/PBRT/index.js +55 -15
- package/src/Processor/PackedRayBuffer.js +10 -13
- package/src/Processor/ParallelBVHBuilder.js +36 -15
- package/src/Processor/PhysicalSky.js +308 -0
- package/src/Processor/QueueManager.js +1 -0
- package/src/Processor/SceneProcessor.js +1132 -181
- package/src/Processor/SplitBLAS.js +249 -0
- package/src/Processor/SunPosition.js +41 -0
- package/src/Processor/TextureCreator.js +21 -0
- package/src/Processor/Workers/AIUpscalerWorker.js +79 -52
- package/src/Processor/Workers/BVHWorker.js +17 -13
- package/src/Processor/ZipReader.js +207 -0
- package/src/RenderSettings.js +38 -0
- package/src/SceneState/SceneState.js +367 -0
- package/src/SceneState/portable.js +52 -0
- package/src/Stages/NormalDepth.js +7 -6
- package/src/Stages/PathTracer.js +108 -4
- package/src/Stages/PathTracerStage.js +251 -104
- package/src/Storage/BLASCache.js +190 -0
- package/src/Storage/CDFCache.js +77 -0
- package/src/Storage/DownloadCache.js +266 -0
- package/src/Storage/GeometrySpill.js +217 -0
- package/src/Storage/SceneGraphCodec.js +503 -0
- package/src/Storage/SpillStore.js +112 -0
- package/src/Storage/StorageManager.js +934 -0
- package/src/Storage/StorageOps.js +137 -0
- package/src/Storage/StorageWorker.js +37 -0
- package/src/Storage/events.js +29 -0
- package/src/Storage/identity.js +77 -0
- package/src/Storage/index.js +9 -0
- package/src/Storage/inlineTransport.js +24 -0
- package/src/Storage/locks.js +99 -0
- package/src/Storage/openStorage.js +87 -0
- package/src/Storage/sceneCachePolicy.js +12 -0
- package/src/Storage/shared.js +9 -0
- package/src/Storage/transport.js +62 -0
- package/src/TSL/Atmosphere.js +684 -0
- package/src/TSL/BVHTraversal.js +288 -239
- package/src/TSL/Common.js +11 -2
- package/src/TSL/Displacement.js +6 -6
- package/src/TSL/EmissiveSampling.js +5 -5
- package/src/TSL/EnvironmentCDF.js +176 -0
- package/src/TSL/GenerateKernel.js +3 -3
- package/src/TSL/HitFacet.js +4 -4
- package/src/TSL/LightsDirect.js +8 -8
- package/src/TSL/LightsSampling.js +55 -37
- package/src/TSL/Random.js +1 -0
- package/src/TSL/ShadeKernel.js +75 -19
- package/src/TSL/Sun.js +40 -0
- package/src/TSL/TextureSampling.js +6 -7
- package/src/index.js +18 -0
- package/src/managers/CameraManager.js +169 -1
- package/src/managers/EnvironmentManager.js +263 -33
- package/src/managers/GoboManager.js +19 -8
- package/src/managers/IESManager.js +3 -3
- package/src/managers/LightManager.js +98 -5
- package/src/managers/MaterialDataManager.js +22 -2
- package/src/managers/TransformManager.js +26 -0
- package/src/managers/UniformManager.js +5 -3
- package/src/managers/VideoRenderManager.js +3 -1
- package/src/managers/timeline/CameraTrack.js +22 -0
- package/src/managers/timeline/TimelineManager.js +14 -0
- package/src/version.js +3 -0
- package/src/Processor/ProceduralSky.js +0 -291
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
|
|
1
2
|
/**
|
|
2
3
|
* BVHRefitter — Fast O(N) bottom-up BVH AABB refit for animated geometry.
|
|
3
4
|
*
|
|
@@ -8,6 +9,8 @@
|
|
|
8
9
|
* Designed to run in both main thread and Web Worker contexts.
|
|
9
10
|
*/
|
|
10
11
|
|
|
12
|
+
import { isFoldedRef, foldedFirst, foldedCount } from './BVHLeafFold.js';
|
|
13
|
+
|
|
11
14
|
// Inline copy of layout constants (source of truth: EngineDefaults.js).
|
|
12
15
|
// Cannot import because this runs inside Web Workers where window is not defined.
|
|
13
16
|
const TRIANGLE_DATA_LAYOUT = {
|
|
@@ -161,6 +164,98 @@ function transformBoundsToWorld( bvhData, nodeOff, src, srcOff, dst, dstOff ) {
|
|
|
161
164
|
}
|
|
162
165
|
|
|
163
166
|
|
|
167
|
+
/** Bounds of triangles [first, first + count) into out[ off .. off + 6 ). */
|
|
168
|
+
function triangleBounds( acc, first, count, out, off ) {
|
|
169
|
+
|
|
170
|
+
const A = TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET;
|
|
171
|
+
const B = TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET;
|
|
172
|
+
const C = TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET;
|
|
173
|
+
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
174
|
+
let maxX = - Infinity, maxY = - Infinity, maxZ = - Infinity;
|
|
175
|
+
|
|
176
|
+
for ( let t = 0; t < count; t ++ ) {
|
|
177
|
+
|
|
178
|
+
const gi = first + t;
|
|
179
|
+
const f = acc.chunked ? acc.f.chunkFor( gi ) : acc.f;
|
|
180
|
+
const o = acc.chunked ? acc.chunked.baseOf( gi ) : gi * FPT;
|
|
181
|
+
minX = Math.min( minX, f[ o + A ], f[ o + B ], f[ o + C ] );
|
|
182
|
+
minY = Math.min( minY, f[ o + A + 1 ], f[ o + B + 1 ], f[ o + C + 1 ] );
|
|
183
|
+
minZ = Math.min( minZ, f[ o + A + 2 ], f[ o + B + 2 ], f[ o + C + 2 ] );
|
|
184
|
+
maxX = Math.max( maxX, f[ o + A ], f[ o + B ], f[ o + C ] );
|
|
185
|
+
maxY = Math.max( maxY, f[ o + A + 1 ], f[ o + B + 1 ], f[ o + C + 1 ] );
|
|
186
|
+
maxZ = Math.max( maxZ, f[ o + A + 2 ], f[ o + B + 2 ], f[ o + C + 2 ] );
|
|
187
|
+
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
out[ off ] = minX; out[ off + 1 ] = minY; out[ off + 2 ] = minZ;
|
|
191
|
+
out[ off + 3 ] = maxX; out[ off + 4 ] = maxY; out[ off + 5 ] = maxZ;
|
|
192
|
+
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const folded = new Float32Array( 12 );
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* One node of a bottom-up refit: its own bounds into `bounds[ ( i - base ) * 6 ]`, and an inner
|
|
199
|
+
* node's two child boxes rewritten from theirs. A leaf folded into its parent takes its box from
|
|
200
|
+
* its triangles.
|
|
201
|
+
*/
|
|
202
|
+
function refitNode( nodes, i, acc, bounds, base ) {
|
|
203
|
+
|
|
204
|
+
const bvhF = nodeF( nodes, i );
|
|
205
|
+
const idx = nodeIdx( nodes, i );
|
|
206
|
+
const o = nodeBase( nodes, i );
|
|
207
|
+
const b = ( i - base ) * 6;
|
|
208
|
+
const marker = idx[ o + 3 ];
|
|
209
|
+
|
|
210
|
+
if ( marker === LEAF_MARKER ) {
|
|
211
|
+
|
|
212
|
+
triangleBounds( acc, idx[ o ], idx[ o + 1 ], bounds, b );
|
|
213
|
+
return;
|
|
214
|
+
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if ( marker === BLAS_POINTER_MARKER ) {
|
|
218
|
+
|
|
219
|
+
// BLAS-pointer leaf (TLAS): the BLAS root's bounds are in the instance's own
|
|
220
|
+
// space, and the TLAS sorts on world bounds — so carry the box out through the
|
|
221
|
+
// leaf's transform. Slots 4..15 are the rows of world-to-object; the eight
|
|
222
|
+
// corners go back the other way through its inverse.
|
|
223
|
+
transformBoundsToWorld( bvhF, o, bounds, ( idx[ o ] - base ) * 6, bounds, b );
|
|
224
|
+
return;
|
|
225
|
+
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const right = idx[ o + 7 ];
|
|
229
|
+
let ls = bounds, lb = ( marker - base ) * 6;
|
|
230
|
+
if ( isFoldedRef( marker ) ) {
|
|
231
|
+
|
|
232
|
+
triangleBounds( acc, foldedFirst( marker ), foldedCount( marker ), folded, 0 );
|
|
233
|
+
ls = folded; lb = 0;
|
|
234
|
+
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let rs = bounds, rb = ( right - base ) * 6;
|
|
238
|
+
if ( isFoldedRef( right ) ) {
|
|
239
|
+
|
|
240
|
+
triangleBounds( acc, foldedFirst( right ), foldedCount( right ), folded, 6 );
|
|
241
|
+
rs = folded; rb = 6;
|
|
242
|
+
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
bvhF[ o ] = ls[ lb ]; bvhF[ o + 1 ] = ls[ lb + 1 ]; bvhF[ o + 2 ] = ls[ lb + 2 ];
|
|
246
|
+
bvhF[ o + 4 ] = ls[ lb + 3 ]; bvhF[ o + 5 ] = ls[ lb + 4 ]; bvhF[ o + 6 ] = ls[ lb + 5 ];
|
|
247
|
+
bvhF[ o + 8 ] = rs[ rb ]; bvhF[ o + 9 ] = rs[ rb + 1 ]; bvhF[ o + 10 ] = rs[ rb + 2 ];
|
|
248
|
+
bvhF[ o + 12 ] = rs[ rb + 3 ]; bvhF[ o + 13 ] = rs[ rb + 4 ]; bvhF[ o + 14 ] = rs[ rb + 5 ];
|
|
249
|
+
|
|
250
|
+
for ( let a = 0; a < 3; a ++ ) {
|
|
251
|
+
|
|
252
|
+
bounds[ b + a ] = Math.min( ls[ lb + a ], rs[ rb + a ] );
|
|
253
|
+
bounds[ b + 3 + a ] = Math.max( ls[ lb + 3 + a ], rs[ rb + 3 + a ] );
|
|
254
|
+
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
}
|
|
258
|
+
|
|
164
259
|
export class BVHRefitter {
|
|
165
260
|
|
|
166
261
|
constructor() {
|
|
@@ -254,102 +349,9 @@ export class BVHRefitter {
|
|
|
254
349
|
|
|
255
350
|
}
|
|
256
351
|
|
|
257
|
-
const bounds = this._bounds;
|
|
258
|
-
const endNode = startNode + nodeCount;
|
|
259
352
|
const nodes = nodeAccess( bvhData );
|
|
260
|
-
|
|
261
|
-
for ( let i =
|
|
262
|
-
|
|
263
|
-
const bvhF = nodeF( nodes, i );
|
|
264
|
-
const idx = nodeIdx( nodes, i );
|
|
265
|
-
const o = nodeBase( nodes, i );
|
|
266
|
-
const b = ( i - startNode ) * 6; // bounds indexed relative to BLAS start
|
|
267
|
-
|
|
268
|
-
if ( idx[ o + 3 ] === LEAF_MARKER ) {
|
|
269
|
-
|
|
270
|
-
const triOffset = idx[ o ];
|
|
271
|
-
const triCount = idx[ o + 1 ];
|
|
272
|
-
|
|
273
|
-
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
274
|
-
let maxX = - Infinity, maxY = - Infinity, maxZ = - Infinity;
|
|
275
|
-
|
|
276
|
-
for ( let t = 0; t < triCount; t ++ ) {
|
|
277
|
-
|
|
278
|
-
const gi = triOffset + t;
|
|
279
|
-
const triFloats = acc.chunked ? acc.f.chunkFor( gi ) : acc.f;
|
|
280
|
-
const tOff = acc.chunked ? acc.chunked.baseOf( gi ) : gi * FPT;
|
|
281
|
-
const ax = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET ];
|
|
282
|
-
const ay = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 1 ];
|
|
283
|
-
const az = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 2 ];
|
|
284
|
-
const bx = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET ];
|
|
285
|
-
const by = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 1 ];
|
|
286
|
-
const bz = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 2 ];
|
|
287
|
-
const cx = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET ];
|
|
288
|
-
const cy = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 1 ];
|
|
289
|
-
const cz = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 2 ];
|
|
290
|
-
|
|
291
|
-
minX = Math.min( minX, ax, bx, cx );
|
|
292
|
-
minY = Math.min( minY, ay, by, cy );
|
|
293
|
-
minZ = Math.min( minZ, az, bz, cz );
|
|
294
|
-
maxX = Math.max( maxX, ax, bx, cx );
|
|
295
|
-
maxY = Math.max( maxY, ay, by, cy );
|
|
296
|
-
maxZ = Math.max( maxZ, az, bz, cz );
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
bounds[ b ] = minX;
|
|
301
|
-
bounds[ b + 1 ] = minY;
|
|
302
|
-
bounds[ b + 2 ] = minZ;
|
|
303
|
-
bounds[ b + 3 ] = maxX;
|
|
304
|
-
bounds[ b + 4 ] = maxY;
|
|
305
|
-
bounds[ b + 5 ] = maxZ;
|
|
306
|
-
|
|
307
|
-
} else {
|
|
308
|
-
|
|
309
|
-
// Inner node — child indices are absolute, but bounds index relative to startNode
|
|
310
|
-
const leftIdx = idx[ o + 3 ];
|
|
311
|
-
const rightIdx = idx[ o + 7 ];
|
|
312
|
-
const lb = ( leftIdx - startNode ) * 6;
|
|
313
|
-
const rb = ( rightIdx - startNode ) * 6;
|
|
314
|
-
|
|
315
|
-
const lMinX = bounds[ lb ];
|
|
316
|
-
const lMinY = bounds[ lb + 1 ];
|
|
317
|
-
const lMinZ = bounds[ lb + 2 ];
|
|
318
|
-
const lMaxX = bounds[ lb + 3 ];
|
|
319
|
-
const lMaxY = bounds[ lb + 4 ];
|
|
320
|
-
const lMaxZ = bounds[ lb + 5 ];
|
|
321
|
-
|
|
322
|
-
const rMinX = bounds[ rb ];
|
|
323
|
-
const rMinY = bounds[ rb + 1 ];
|
|
324
|
-
const rMinZ = bounds[ rb + 2 ];
|
|
325
|
-
const rMaxX = bounds[ rb + 3 ];
|
|
326
|
-
const rMaxY = bounds[ rb + 4 ];
|
|
327
|
-
const rMaxZ = bounds[ rb + 5 ];
|
|
328
|
-
|
|
329
|
-
bvhF[ o ] = lMinX;
|
|
330
|
-
bvhF[ o + 1 ] = lMinY;
|
|
331
|
-
bvhF[ o + 2 ] = lMinZ;
|
|
332
|
-
bvhF[ o + 4 ] = lMaxX;
|
|
333
|
-
bvhF[ o + 5 ] = lMaxY;
|
|
334
|
-
bvhF[ o + 6 ] = lMaxZ;
|
|
335
|
-
|
|
336
|
-
bvhF[ o + 8 ] = rMinX;
|
|
337
|
-
bvhF[ o + 9 ] = rMinY;
|
|
338
|
-
bvhF[ o + 10 ] = rMinZ;
|
|
339
|
-
bvhF[ o + 12 ] = rMaxX;
|
|
340
|
-
bvhF[ o + 13 ] = rMaxY;
|
|
341
|
-
bvhF[ o + 14 ] = rMaxZ;
|
|
342
|
-
|
|
343
|
-
bounds[ b ] = Math.min( lMinX, rMinX );
|
|
344
|
-
bounds[ b + 1 ] = Math.min( lMinY, rMinY );
|
|
345
|
-
bounds[ b + 2 ] = Math.min( lMinZ, rMinZ );
|
|
346
|
-
bounds[ b + 3 ] = Math.max( lMaxX, rMaxX );
|
|
347
|
-
bounds[ b + 4 ] = Math.max( lMaxY, rMaxY );
|
|
348
|
-
bounds[ b + 5 ] = Math.max( lMaxZ, rMaxZ );
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
}
|
|
353
|
+
// Bounds indexed relative to the BLAS start; child indices are absolute.
|
|
354
|
+
for ( let i = startNode + nodeCount - 1; i >= startNode; i -- ) refitNode( nodes, i, acc, this._bounds, startNode );
|
|
353
355
|
|
|
354
356
|
}
|
|
355
357
|
|
|
@@ -374,126 +376,8 @@ export class BVHRefitter {
|
|
|
374
376
|
|
|
375
377
|
}
|
|
376
378
|
|
|
377
|
-
const bounds = this._bounds;
|
|
378
379
|
const nodes = nodeAccess( bvhData );
|
|
379
|
-
|
|
380
|
-
// Reverse iteration: bottom-up in pre-order layout
|
|
381
|
-
for ( let i = nodeCount - 1; i >= 0; i -- ) {
|
|
382
|
-
|
|
383
|
-
const bvhF = nodeF( nodes, i );
|
|
384
|
-
const idx = nodeIdx( nodes, i );
|
|
385
|
-
const o = nodeBase( nodes, i );
|
|
386
|
-
const b = i * 6;
|
|
387
|
-
|
|
388
|
-
const marker = idx[ o + 3 ];
|
|
389
|
-
|
|
390
|
-
if ( marker === LEAF_MARKER ) {
|
|
391
|
-
|
|
392
|
-
// Triangle leaf: compute AABB from triangles
|
|
393
|
-
const triOffset = idx[ o ];
|
|
394
|
-
const triCount = idx[ o + 1 ];
|
|
395
|
-
|
|
396
|
-
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
397
|
-
let maxX = - Infinity, maxY = - Infinity, maxZ = - Infinity;
|
|
398
|
-
|
|
399
|
-
for ( let t = 0; t < triCount; t ++ ) {
|
|
400
|
-
|
|
401
|
-
const gi = triOffset + t;
|
|
402
|
-
const triFloats = acc.chunked ? acc.f.chunkFor( gi ) : acc.f;
|
|
403
|
-
const tOff = acc.chunked ? acc.chunked.baseOf( gi ) : gi * FPT;
|
|
404
|
-
|
|
405
|
-
// Position A
|
|
406
|
-
const ax = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET ];
|
|
407
|
-
const ay = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 1 ];
|
|
408
|
-
const az = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 2 ];
|
|
409
|
-
// Position B
|
|
410
|
-
const bx = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET ];
|
|
411
|
-
const by = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 1 ];
|
|
412
|
-
const bz = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 2 ];
|
|
413
|
-
// Position C
|
|
414
|
-
const cx = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET ];
|
|
415
|
-
const cy = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 1 ];
|
|
416
|
-
const cz = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 2 ];
|
|
417
|
-
|
|
418
|
-
minX = Math.min( minX, ax, bx, cx );
|
|
419
|
-
minY = Math.min( minY, ay, by, cy );
|
|
420
|
-
minZ = Math.min( minZ, az, bz, cz );
|
|
421
|
-
maxX = Math.max( maxX, ax, bx, cx );
|
|
422
|
-
maxY = Math.max( maxY, ay, by, cy );
|
|
423
|
-
maxZ = Math.max( maxZ, az, bz, cz );
|
|
424
|
-
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
bounds[ b ] = minX;
|
|
428
|
-
bounds[ b + 1 ] = minY;
|
|
429
|
-
bounds[ b + 2 ] = minZ;
|
|
430
|
-
bounds[ b + 3 ] = maxX;
|
|
431
|
-
bounds[ b + 4 ] = maxY;
|
|
432
|
-
bounds[ b + 5 ] = maxZ;
|
|
433
|
-
|
|
434
|
-
} else if ( marker === BLAS_POINTER_MARKER ) {
|
|
435
|
-
|
|
436
|
-
// BLAS-pointer leaf (TLAS): the BLAS root's bounds are in the instance's own
|
|
437
|
-
// space, and the TLAS sorts on world bounds — so carry the box out through the
|
|
438
|
-
// leaf's transform. Slots 4..15 are the rows of world-to-object; the eight
|
|
439
|
-
// corners go back the other way through its inverse.
|
|
440
|
-
const blasRoot = idx[ o ];
|
|
441
|
-
const br = blasRoot * 6;
|
|
442
|
-
transformBoundsToWorld( bvhF, o, bounds, br, bounds, b );
|
|
443
|
-
|
|
444
|
-
} else {
|
|
445
|
-
|
|
446
|
-
// Inner node: union children bounds (already computed since we iterate in reverse)
|
|
447
|
-
const leftIdx = idx[ o + 3 ];
|
|
448
|
-
const rightIdx = idx[ o + 7 ];
|
|
449
|
-
const lb = leftIdx * 6;
|
|
450
|
-
const rb = rightIdx * 6;
|
|
451
|
-
|
|
452
|
-
const lMinX = bounds[ lb ];
|
|
453
|
-
const lMinY = bounds[ lb + 1 ];
|
|
454
|
-
const lMinZ = bounds[ lb + 2 ];
|
|
455
|
-
const lMaxX = bounds[ lb + 3 ];
|
|
456
|
-
const lMaxY = bounds[ lb + 4 ];
|
|
457
|
-
const lMaxZ = bounds[ lb + 5 ];
|
|
458
|
-
|
|
459
|
-
const rMinX = bounds[ rb ];
|
|
460
|
-
const rMinY = bounds[ rb + 1 ];
|
|
461
|
-
const rMinZ = bounds[ rb + 2 ];
|
|
462
|
-
const rMaxX = bounds[ rb + 3 ];
|
|
463
|
-
const rMaxY = bounds[ rb + 4 ];
|
|
464
|
-
const rMaxZ = bounds[ rb + 5 ];
|
|
465
|
-
|
|
466
|
-
// Write left child AABB into bvhData
|
|
467
|
-
bvhF[ o ] = lMinX;
|
|
468
|
-
bvhF[ o + 1 ] = lMinY;
|
|
469
|
-
bvhF[ o + 2 ] = lMinZ;
|
|
470
|
-
// o+3 = leftChildIdx (preserved)
|
|
471
|
-
bvhF[ o + 4 ] = lMaxX;
|
|
472
|
-
bvhF[ o + 5 ] = lMaxY;
|
|
473
|
-
bvhF[ o + 6 ] = lMaxZ;
|
|
474
|
-
// o+7 = rightChildIdx (preserved)
|
|
475
|
-
|
|
476
|
-
// Write right child AABB into bvhData
|
|
477
|
-
bvhF[ o + 8 ] = rMinX;
|
|
478
|
-
bvhF[ o + 9 ] = rMinY;
|
|
479
|
-
bvhF[ o + 10 ] = rMinZ;
|
|
480
|
-
// o+11 = 0 padding
|
|
481
|
-
bvhF[ o + 12 ] = rMaxX;
|
|
482
|
-
bvhF[ o + 13 ] = rMaxY;
|
|
483
|
-
bvhF[ o + 14 ] = rMaxZ;
|
|
484
|
-
// o+15 = 0 padding
|
|
485
|
-
|
|
486
|
-
// Store this node's bounds as union of children
|
|
487
|
-
bounds[ b ] = Math.min( lMinX, rMinX );
|
|
488
|
-
bounds[ b + 1 ] = Math.min( lMinY, rMinY );
|
|
489
|
-
bounds[ b + 2 ] = Math.min( lMinZ, rMinZ );
|
|
490
|
-
bounds[ b + 3 ] = Math.max( lMaxX, rMaxX );
|
|
491
|
-
bounds[ b + 4 ] = Math.max( lMaxY, rMaxY );
|
|
492
|
-
bounds[ b + 5 ] = Math.max( lMaxZ, rMaxZ );
|
|
493
|
-
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
}
|
|
380
|
+
for ( let i = nodeCount - 1; i >= 0; i -- ) refitNode( nodes, i, acc, this._bounds, 0 );
|
|
497
381
|
|
|
498
382
|
}
|
|
499
383
|
|
|
@@ -134,7 +134,7 @@ export class ChunkedRecords {
|
|
|
134
134
|
const lanes = Math.min( this.recordsPerChunk, this.recordCount - from ) * this.lanesPerRecord;
|
|
135
135
|
const chunk = this.chunks[ k ] = allocChunk( this.LaneType, lanes, this.shared );
|
|
136
136
|
|
|
137
|
-
for ( const v of this.
|
|
137
|
+
for ( const v of this._liveViews() ) {
|
|
138
138
|
|
|
139
139
|
v.chunks[ k ] = new v.LaneType( chunk.buffer, chunk.byteOffset, chunk.byteLength / v.LaneType.BYTES_PER_ELEMENT );
|
|
140
140
|
|
|
@@ -147,7 +147,7 @@ export class ChunkedRecords {
|
|
|
147
147
|
/** Allocate whatever a lazy fill never touched, so the result behaves like an eager one. */
|
|
148
148
|
materializeAll() {
|
|
149
149
|
|
|
150
|
-
for ( let k = 0; k < this.chunks.length; k ++ ) if ( ! this.chunks[ k ] ) this._materialize( k );
|
|
150
|
+
for ( let k = 0; k < this.chunks.length; k ++ ) if ( ! this.chunks[ k ] && ! this._spilled?.has( k ) ) this._materialize( k );
|
|
151
151
|
return this;
|
|
152
152
|
|
|
153
153
|
}
|
|
@@ -177,7 +177,104 @@ export class ChunkedRecords {
|
|
|
177
177
|
chunkFor( record ) {
|
|
178
178
|
|
|
179
179
|
const k = ( record / this.recordsPerChunk ) | 0;
|
|
180
|
-
|
|
180
|
+
const chunk = this.chunks[ k ];
|
|
181
|
+
if ( chunk ) return chunk;
|
|
182
|
+
if ( this._spilled?.has( k ) ) throw new Error( `ChunkedRecords: chunk ${k} is on disk — await ensureResident() before touching record ${record}` );
|
|
183
|
+
return this._materialize( k );
|
|
184
|
+
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Writes every resident chunk to `store` and lets go of it; {@link ensureResident} reads it
|
|
189
|
+
* back. A spilled chunk's accessors throw rather than read zeros. Every other holder of a chunk
|
|
190
|
+
* (a worker, a view) keeps it alive, so spill only what nothing else holds.
|
|
191
|
+
* @param {{write: function(number, ArrayBufferView): Promise<void>, read: function(number, number): Promise<ArrayBuffer>}} store
|
|
192
|
+
* @param {{keep?: function(number): boolean}} [options] - chunks that stay in memory
|
|
193
|
+
* @returns {Promise<number>} bytes let go
|
|
194
|
+
*/
|
|
195
|
+
async spill( store, { keep = () => false } = {} ) {
|
|
196
|
+
|
|
197
|
+
if ( this._owner ) throw new Error( 'ChunkedRecords: spill the storage, not a view of it' );
|
|
198
|
+
this._store = store;
|
|
199
|
+
this._spilled ??= new Set();
|
|
200
|
+
let bytes = 0;
|
|
201
|
+
|
|
202
|
+
for ( let k = 0; k < this.chunks.length; k ++ ) {
|
|
203
|
+
|
|
204
|
+
const chunk = this.chunks[ k ];
|
|
205
|
+
if ( ! chunk || keep( k ) ) continue;
|
|
206
|
+
await store.write( k, chunk );
|
|
207
|
+
this.chunks[ k ] = null;
|
|
208
|
+
for ( const v of this._liveViews() ) v.chunks[ k ] = null;
|
|
209
|
+
this._spilled.add( k );
|
|
210
|
+
bytes += chunk.byteLength;
|
|
211
|
+
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return bytes;
|
|
215
|
+
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Writes one chunk to `store` and lets go of it — a build hands chunks over as it finishes them. */
|
|
219
|
+
async spillChunk( k, store ) {
|
|
220
|
+
|
|
221
|
+
const chunk = this.chunks[ k ];
|
|
222
|
+
if ( ! chunk ) return 0;
|
|
223
|
+
this._store = store;
|
|
224
|
+
this._spilled ??= new Set();
|
|
225
|
+
await store.write( k, chunk );
|
|
226
|
+
this.chunks[ k ] = null;
|
|
227
|
+
for ( const v of this._liveViews() ) v.chunks[ k ] = null;
|
|
228
|
+
this._spilled.add( k );
|
|
229
|
+
return chunk.byteLength;
|
|
230
|
+
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Reads the spilled chunks under records [start, start+count) back into memory. */
|
|
234
|
+
async ensureResident( start = 0, count = this.recordCount ) {
|
|
235
|
+
|
|
236
|
+
if ( ! this._spilled?.size || count <= 0 ) return;
|
|
237
|
+
const first = ( start / this.recordsPerChunk ) | 0;
|
|
238
|
+
const last = ( ( start + count - 1 ) / this.recordsPerChunk ) | 0;
|
|
239
|
+
|
|
240
|
+
for ( let k = first; k <= last; k ++ ) {
|
|
241
|
+
|
|
242
|
+
if ( ! this._spilled.has( k ) ) continue;
|
|
243
|
+
const lanes = Math.min( this.recordsPerChunk, this.recordCount - k * this.recordsPerChunk ) * this.lanesPerRecord;
|
|
244
|
+
const bytes = await this._store.read( k, lanes * this.LaneType.BYTES_PER_ELEMENT );
|
|
245
|
+
const chunk = allocChunk( this.LaneType, lanes, this.shared );
|
|
246
|
+
new Uint8Array( chunk.buffer, chunk.byteOffset, chunk.byteLength ).set( new Uint8Array( bytes ) );
|
|
247
|
+
this.chunks[ k ] = chunk;
|
|
248
|
+
for ( const v of this._liveViews() ) v.chunks[ k ] = new v.LaneType( chunk.buffer, chunk.byteOffset, chunk.byteLength / v.LaneType.BYTES_PER_ELEMENT );
|
|
249
|
+
this._spilled.delete( k );
|
|
250
|
+
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** @private */
|
|
256
|
+
_liveViews() {
|
|
257
|
+
|
|
258
|
+
if ( this._weakViews ) this._weakViews = this._weakViews.filter( ref => ref.deref() );
|
|
259
|
+
return [ ...( this._views ?? [] ), ...( this._weakViews ?? [] ).map( ref => ref.deref() ) ];
|
|
260
|
+
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Whether records [start, start+count) are all in memory. */
|
|
264
|
+
isResident( start = 0, count = this.recordCount ) {
|
|
265
|
+
|
|
266
|
+
if ( ! this._spilled?.size || count <= 0 ) return true;
|
|
267
|
+
const first = ( start / this.recordsPerChunk ) | 0;
|
|
268
|
+
const last = ( ( start + count - 1 ) / this.recordsPerChunk ) | 0;
|
|
269
|
+
for ( let k = first; k <= last; k ++ ) if ( this._spilled.has( k ) ) return false;
|
|
270
|
+
return true;
|
|
271
|
+
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Chunks currently on disk. */
|
|
275
|
+
get spilledChunks() {
|
|
276
|
+
|
|
277
|
+
return this._spilled?.size ?? 0;
|
|
181
278
|
|
|
182
279
|
}
|
|
183
280
|
|
|
@@ -278,6 +375,87 @@ export class ChunkedRecords {
|
|
|
278
375
|
|
|
279
376
|
}
|
|
280
377
|
|
|
378
|
+
/**
|
|
379
|
+
* A lazy store taking more records without moving the ones it has: new chunks are added and
|
|
380
|
+
* only a short last chunk is reallocated. A build streaming chunks to disk cannot copy the
|
|
381
|
+
* store, since earlier chunks may already be gone.
|
|
382
|
+
*/
|
|
383
|
+
grow( recordCount ) {
|
|
384
|
+
|
|
385
|
+
if ( recordCount <= this.recordCount ) return;
|
|
386
|
+
const last = this.chunks.length - 1;
|
|
387
|
+
const lanesFor = ( k, count ) => Math.min( this.recordsPerChunk, count - k * this.recordsPerChunk ) * this.lanesPerRecord;
|
|
388
|
+
const old = this.chunks[ last ];
|
|
389
|
+
|
|
390
|
+
this.recordCount = recordCount;
|
|
391
|
+
for ( const v of this._liveViews() ) v.recordCount = recordCount;
|
|
392
|
+
if ( this.chunks.length * this.recordsPerChunk < recordCount ) {
|
|
393
|
+
|
|
394
|
+
this.chunks.length = Math.ceil( recordCount / this.recordsPerChunk );
|
|
395
|
+
for ( const v of this._liveViews() ) v.chunks.length = this.chunks.length;
|
|
396
|
+
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if ( old && old.length < lanesFor( last, recordCount ) ) {
|
|
400
|
+
|
|
401
|
+
const chunk = allocChunk( this.LaneType, lanesFor( last, recordCount ), this.shared );
|
|
402
|
+
chunk.set( old );
|
|
403
|
+
this.chunks[ last ] = chunk;
|
|
404
|
+
for ( const v of this._liveViews() ) v.chunks[ last ] = new v.LaneType( chunk.buffer, chunk.byteOffset, chunk.byteLength / v.LaneType.BYTES_PER_ELEMENT );
|
|
405
|
+
|
|
406
|
+
} else if ( ! old && this._spilled?.has( last ) ) {
|
|
407
|
+
|
|
408
|
+
throw new Error( `ChunkedRecords: cannot grow past chunk ${last}, which is on disk` );
|
|
409
|
+
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Lets go of the chunks lying wholly inside records [start, start+count) without writing them
|
|
416
|
+
* anywhere: their contents are about to be rewritten, and the next access allocates them again.
|
|
417
|
+
* @returns {number} bytes let go
|
|
418
|
+
*/
|
|
419
|
+
release( start, count ) {
|
|
420
|
+
|
|
421
|
+
const rpc = this.recordsPerChunk;
|
|
422
|
+
const first = Math.ceil( start / rpc );
|
|
423
|
+
const end = Math.floor( ( start + count ) / rpc );
|
|
424
|
+
let bytes = 0;
|
|
425
|
+
for ( let k = first; k < end; k ++ ) {
|
|
426
|
+
|
|
427
|
+
const chunk = this.chunks[ k ];
|
|
428
|
+
if ( ! chunk ) continue;
|
|
429
|
+
bytes += chunk.byteLength;
|
|
430
|
+
this.chunks[ k ] = undefined;
|
|
431
|
+
for ( const v of this._liveViews() ) v.chunks[ k ] = undefined;
|
|
432
|
+
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return bytes;
|
|
436
|
+
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** {@link trimTo} on this store itself, keeping what it has spilled. */
|
|
440
|
+
trimInPlace( recordCount ) {
|
|
441
|
+
|
|
442
|
+
const needed = Math.max( 1, Math.ceil( recordCount / this.recordsPerChunk ) );
|
|
443
|
+
const lanes = Math.max( 0, ( recordCount - ( needed - 1 ) * this.recordsPerChunk ) * this.lanesPerRecord );
|
|
444
|
+
const stores = [ this, ...this._liveViews() ];
|
|
445
|
+
for ( const s of stores ) {
|
|
446
|
+
|
|
447
|
+
s.chunks.length = needed;
|
|
448
|
+
const last = s.chunks[ needed - 1 ];
|
|
449
|
+
if ( last && last.length > lanes ) s.chunks[ needed - 1 ] = last.subarray( 0, lanes );
|
|
450
|
+
s.recordCount = recordCount;
|
|
451
|
+
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if ( this._spilled ) for ( const k of this._spilled ) if ( k >= needed ) this._spilled.delete( k );
|
|
455
|
+
return this;
|
|
456
|
+
|
|
457
|
+
}
|
|
458
|
+
|
|
281
459
|
/** A parallel set of views of another lane type over the same memory (u32 records read as f32). */
|
|
282
460
|
viewAs( LaneType ) {
|
|
283
461
|
|
|
@@ -287,14 +465,21 @@ export class ChunkedRecords {
|
|
|
287
465
|
this.recordCount, this.lanesPerRecord, this.recordsPerChunk
|
|
288
466
|
);
|
|
289
467
|
v.LaneType = LaneType;
|
|
468
|
+
// A view never allocates for itself: a chunk it finds missing is the storage's to create.
|
|
469
|
+
v._owner = this;
|
|
290
470
|
|
|
291
471
|
if ( this._views ) {
|
|
292
472
|
|
|
293
|
-
v._owner = this;
|
|
294
473
|
this._views.push( v );
|
|
295
474
|
|
|
475
|
+
} else {
|
|
476
|
+
|
|
477
|
+
// Weakly: callers take short-lived views, and a spill must still reach the lasting ones.
|
|
478
|
+
( this._weakViews ??= [] ).push( new WeakRef( v ) );
|
|
479
|
+
|
|
296
480
|
}
|
|
297
481
|
|
|
482
|
+
v._spilled = this._spilled ??= new Set();
|
|
298
483
|
return v;
|
|
299
484
|
|
|
300
485
|
}
|
|
@@ -90,6 +90,14 @@ export class EmissiveTriangleBuilder {
|
|
|
90
90
|
|
|
91
91
|
for ( let i = 0; i < triangleCount; i ++ ) {
|
|
92
92
|
|
|
93
|
+
// A chunk a build spilled holds no emitters: that is why it could be spilled.
|
|
94
|
+
if ( chunked?.spilledChunks && ! chunked.isResident( i, 1 ) ) {
|
|
95
|
+
|
|
96
|
+
i = ( ( ( i / chunked.recordsPerChunk ) | 0 ) + 1 ) * chunked.recordsPerChunk - 1;
|
|
97
|
+
continue;
|
|
98
|
+
|
|
99
|
+
}
|
|
100
|
+
|
|
93
101
|
const triU = chunked ? chunked.chunkFor( i ) : flatU;
|
|
94
102
|
const triFloats = chunked ? chunkedF.chunkFor( i ) : flatF;
|
|
95
103
|
const baseOffset = chunked ? chunked.baseOf( i ) : i * FLOATS_PER_TRIANGLE;
|