rayzee 9.0.0 → 9.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +10344 -5334
- 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/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
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
import { ISSUE_CODES } from '../EngineIssues.js';
|
|
6
6
|
import { ChunkedRecords, SHARED_MEMORY_AVAILABLE } from './ChunkedRecords.js';
|
|
7
7
|
import { createLogger, fmt, warnOnce } from '../utils/Logger.js';
|
|
8
|
+
import { GeometrySpill } from '../Storage/GeometrySpill.js';
|
|
8
9
|
|
|
9
10
|
const log = createLogger( 'geometry' );
|
|
10
11
|
|
|
@@ -257,6 +258,167 @@ export class GeometryExtractor {
|
|
|
257
258
|
|
|
258
259
|
}
|
|
259
260
|
|
|
261
|
+
/**
|
|
262
|
+
* `extract()`, handing each stored range to `onRange( meshIndex, range )` as soon as it is
|
|
263
|
+
* written and awaiting `pause( triangles )` every `yieldEvery` triangles, so a build can work
|
|
264
|
+
* on the first meshes while the rest are read. The store is lazy and sized exactly
|
|
265
|
+
* ({@link _countStoredTriangles}): a chunk takes memory when first written, and the receiver
|
|
266
|
+
* may send it to disk once it is done with it. `releaseGeometry( geometry )` is handed each
|
|
267
|
+
* geometry after its last read, already compressed, unless something else still holds its arrays.
|
|
268
|
+
*/
|
|
269
|
+
async extractStreaming( object, { onStore, onRange, pause, yieldEvery = 1 << 20, releaseGeometry = null } ) {
|
|
270
|
+
|
|
271
|
+
this.resetArrays();
|
|
272
|
+
this._triangleCapacity = Math.max( 1, this._countStoredTriangles( object ) );
|
|
273
|
+
this._allocateTriangles( this._triangleCapacity, true );
|
|
274
|
+
onStore?.( this.triangles );
|
|
275
|
+
this.currentTriangleIndex = 0;
|
|
276
|
+
this._allocatePlacements( this._countPlacements( object ) );
|
|
277
|
+
this._geometryUses = this._countGeometryUses( object );
|
|
278
|
+
this.bakeInverse = new Map();
|
|
279
|
+
this._onRange = onRange;
|
|
280
|
+
this._releaseAfterRead = releaseGeometry ? this._geometryReleaser( object, releaseGeometry ) : null;
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
|
|
284
|
+
// Depth-first pre-order, as traverseObject: mesh indices follow the same walk.
|
|
285
|
+
const stack = [ object ];
|
|
286
|
+
let yielded = 0;
|
|
287
|
+
while ( stack.length ) {
|
|
288
|
+
|
|
289
|
+
const node = stack.pop();
|
|
290
|
+
this._visit( node );
|
|
291
|
+
if ( node.children ) for ( let i = node.children.length - 1; i >= 0; i -- ) stack.push( node.children[ i ] );
|
|
292
|
+
if ( this.currentTriangleIndex - yielded >= yieldEvery ) {
|
|
293
|
+
|
|
294
|
+
yielded = this.currentTriangleIndex;
|
|
295
|
+
await pause( this.currentTriangleIndex );
|
|
296
|
+
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
} finally {
|
|
302
|
+
|
|
303
|
+
this._onRange = null;
|
|
304
|
+
this._releaseAfterRead = null;
|
|
305
|
+
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
this._shareInstanceMatrices();
|
|
309
|
+
this._compressAttributes( object );
|
|
310
|
+
this.triangles.trimInPlace( this.currentTriangleIndex );
|
|
311
|
+
this._streamedStore = true;
|
|
312
|
+
|
|
313
|
+
this.logStats();
|
|
314
|
+
return this.getExtractedData();
|
|
315
|
+
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Triangles `extract()` will store: a geometry shared under one material once, an emissive
|
|
320
|
+
* instanced mesh once per instance (it is expanded), everything else once per mesh.
|
|
321
|
+
* @private
|
|
322
|
+
*/
|
|
323
|
+
_countStoredTriangles( object ) {
|
|
324
|
+
|
|
325
|
+
const seen = new Set();
|
|
326
|
+
let count = 0;
|
|
327
|
+
const walk = node => {
|
|
328
|
+
|
|
329
|
+
if ( node.isMesh && node.geometry && node.material ) {
|
|
330
|
+
|
|
331
|
+
const g = node.geometry;
|
|
332
|
+
const tris = Math.ceil( ( g.index ? g.index.count : ( g.attributes.position?.count ?? 0 ) ) / 3 );
|
|
333
|
+
const material = Array.isArray( node.material ) ? node.material[ 0 ] : node.material;
|
|
334
|
+
const e = material?.emissive;
|
|
335
|
+
const emissive = material?.emissiveIntensity > 0 && !! e && ( e.r > 0 || e.g > 0 || e.b > 0 );
|
|
336
|
+
const instances = node.isInstancedMesh ? ( node.count ?? 0 ) : 1;
|
|
337
|
+
|
|
338
|
+
if ( emissive && node.isInstancedMesh && ! isDeformable( node ) && tris * instances <= MAX_EXPANDED_EMISSIVE_TRIANGLES ) count += tris * instances;
|
|
339
|
+
else if ( emissive || isDeformable( node ) ) count += tris;
|
|
340
|
+
else {
|
|
341
|
+
|
|
342
|
+
const key = `${g.uuid}|${material?.uuid}`;
|
|
343
|
+
if ( ! seen.has( key ) ) {
|
|
344
|
+
|
|
345
|
+
seen.add( key );
|
|
346
|
+
count += tris;
|
|
347
|
+
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if ( node.children ) for ( const child of node.children ) walk( child );
|
|
355
|
+
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
walk( object );
|
|
359
|
+
return count;
|
|
360
|
+
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Counts each geometry's reads, and returns what to call after each mesh: on a geometry's
|
|
365
|
+
* last read it is compressed and handed to `release`. Never a host's geometry, a deforming
|
|
366
|
+
* one, one GeometrySpill cannot swap, or one sharing an array with another geometry.
|
|
367
|
+
* @private
|
|
368
|
+
*/
|
|
369
|
+
_geometryReleaser( object, release ) {
|
|
370
|
+
|
|
371
|
+
const reads = new Map();
|
|
372
|
+
const holders = new Map();
|
|
373
|
+
const kept = new Set();
|
|
374
|
+
|
|
375
|
+
const walk = ( node, external ) => {
|
|
376
|
+
|
|
377
|
+
external ||= !! node.userData?.__rayzeeExternal;
|
|
378
|
+
const g = node.isMesh && node.material ? node.geometry : null;
|
|
379
|
+
if ( g ) {
|
|
380
|
+
|
|
381
|
+
if ( ! reads.has( g ) ) {
|
|
382
|
+
|
|
383
|
+
if ( ! GeometrySpill.canTake( g ) ) kept.add( g );
|
|
384
|
+
for ( const attribute of [ ...Object.values( g.attributes ), g.index ] ) {
|
|
385
|
+
|
|
386
|
+
const array = attribute?.array;
|
|
387
|
+
if ( ! array ) continue;
|
|
388
|
+
const holder = holders.get( array );
|
|
389
|
+
if ( holder && holder !== g ) kept.add( g ).add( holder );
|
|
390
|
+
else holders.set( array, g );
|
|
391
|
+
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
reads.set( g, ( reads.get( g ) ?? 0 ) + 1 );
|
|
397
|
+
if ( external || isDeformable( node ) ) kept.add( g );
|
|
398
|
+
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if ( node.children ) for ( const child of node.children ) walk( child, external );
|
|
402
|
+
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
walk( object, false );
|
|
406
|
+
|
|
407
|
+
return mesh => {
|
|
408
|
+
|
|
409
|
+
const g = mesh.material ? mesh.geometry : null;
|
|
410
|
+
if ( ! g || ! reads.has( g ) ) return;
|
|
411
|
+
const left = reads.get( g ) - 1;
|
|
412
|
+
if ( left > 0 ) return void reads.set( g, left );
|
|
413
|
+
reads.delete( g );
|
|
414
|
+
if ( kept.has( g ) ) return;
|
|
415
|
+
this._compressGeometry( g );
|
|
416
|
+
release( g );
|
|
417
|
+
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
}
|
|
421
|
+
|
|
260
422
|
/**
|
|
261
423
|
* The sizes a memory preflight needs, without doing any of the work: what `extract()` would
|
|
262
424
|
* store, plus the three.js geometry already resident behind it.
|
|
@@ -354,8 +516,6 @@ export class GeometryExtractor {
|
|
|
354
516
|
*/
|
|
355
517
|
_compressAttributes( object ) {
|
|
356
518
|
|
|
357
|
-
const done = this._compressed;
|
|
358
|
-
|
|
359
519
|
// A host's own Object3D is rendered as a copy, but the copy shares its geometry by
|
|
360
520
|
// reference, so rewriting an attribute there would rewrite the host's data. Its whole
|
|
361
521
|
// subtree is skipped rather than the root alone.
|
|
@@ -369,44 +529,50 @@ export class GeometryExtractor {
|
|
|
369
529
|
|
|
370
530
|
walk( object, o => {
|
|
371
531
|
|
|
372
|
-
|
|
373
|
-
if ( ! o.isMesh || ! g || done.has( g.uuid ) ) return;
|
|
374
|
-
done.add( g.uuid );
|
|
532
|
+
if ( o.isMesh && o.geometry ) this._compressGeometry( o.geometry );
|
|
375
533
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const attr = g.getAttribute( name );
|
|
379
|
-
if ( ! attr || attr.normalized || ! ( attr.array instanceof Float32Array ) ) continue;
|
|
380
|
-
if ( g.morphAttributes?.[ name ]?.length ) continue;
|
|
534
|
+
} );
|
|
381
535
|
|
|
382
|
-
|
|
383
|
-
let ok = true;
|
|
384
|
-
for ( let i = 0; i < src.length; i ++ ) {
|
|
536
|
+
}
|
|
385
537
|
|
|
386
|
-
|
|
387
|
-
|
|
538
|
+
/** One geometry's share of {@link _compressAttributes}; each geometry is done once. @private */
|
|
539
|
+
_compressGeometry( g ) {
|
|
388
540
|
|
|
389
|
-
|
|
541
|
+
if ( this._compressed.has( g.uuid ) ) return;
|
|
542
|
+
this._compressed.add( g.uuid );
|
|
390
543
|
|
|
391
|
-
|
|
544
|
+
for ( const name of [ 'normal', 'tangent', 'color' ] ) {
|
|
392
545
|
|
|
393
|
-
|
|
546
|
+
const attr = g.getAttribute( name );
|
|
547
|
+
if ( ! attr || attr.normalized || ! ( attr.array instanceof Float32Array ) ) continue;
|
|
548
|
+
if ( g.morphAttributes?.[ name ]?.length ) continue;
|
|
394
549
|
|
|
395
|
-
|
|
396
|
-
|
|
550
|
+
const src = attr.array;
|
|
551
|
+
let ok = true;
|
|
552
|
+
for ( let i = 0; i < src.length; i ++ ) {
|
|
397
553
|
|
|
398
|
-
const
|
|
399
|
-
|
|
554
|
+
const v = src[ i ];
|
|
555
|
+
if ( ! ( v >= - 1.0001 && v <= 1.0001 ) ) {
|
|
400
556
|
|
|
401
|
-
|
|
557
|
+
ok = false; break;
|
|
402
558
|
|
|
403
559
|
}
|
|
404
560
|
|
|
405
|
-
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Out of snorm range (or NaN) — leave it as floats rather than silently clamp.
|
|
564
|
+
if ( ! ok ) continue;
|
|
565
|
+
|
|
566
|
+
const packed = new Int16Array( src.length );
|
|
567
|
+
for ( let i = 0; i < src.length; i ++ ) {
|
|
568
|
+
|
|
569
|
+
packed[ i ] = Math.round( Math.max( - 1, Math.min( 1, src[ i ] ) ) * 32767 );
|
|
406
570
|
|
|
407
571
|
}
|
|
408
572
|
|
|
409
|
-
|
|
573
|
+
g.setAttribute( name, new BufferAttribute( packed, attr.itemSize, true ) );
|
|
574
|
+
|
|
575
|
+
}
|
|
410
576
|
|
|
411
577
|
}
|
|
412
578
|
|
|
@@ -462,6 +628,15 @@ export class GeometryExtractor {
|
|
|
462
628
|
|
|
463
629
|
if ( needed <= this._triangleCapacity ) return;
|
|
464
630
|
|
|
631
|
+
// A streamed store may already have chunks on disk: it grows where it stands.
|
|
632
|
+
if ( this._onRange ) {
|
|
633
|
+
|
|
634
|
+
this.triangles.grow( needed );
|
|
635
|
+
this._triangleCapacity = needed;
|
|
636
|
+
return;
|
|
637
|
+
|
|
638
|
+
}
|
|
639
|
+
|
|
465
640
|
const previous = this.triangles;
|
|
466
641
|
const used = this.currentTriangleIndex;
|
|
467
642
|
this._allocateTriangles( needed );
|
|
@@ -486,11 +661,11 @@ export class GeometryExtractor {
|
|
|
486
661
|
* V8 ArrayBuffer cap, well below what the GPU buffer can take.
|
|
487
662
|
* @private
|
|
488
663
|
*/
|
|
489
|
-
_allocateTriangles( capacity ) {
|
|
664
|
+
_allocateTriangles( capacity, lazy = false ) {
|
|
490
665
|
|
|
491
666
|
// Shared-backed so a refit reads and writes them in place; copying them into shared memory
|
|
492
667
|
// on first refit would be a second copy of the largest thing in the scene.
|
|
493
|
-
this.triangles = new ChunkedRecords(
|
|
668
|
+
this.triangles = ( lazy ? ChunkedRecords.lazy : ( ...a ) => new ChunkedRecords( ...a ) )(
|
|
494
669
|
capacity, TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE, Uint32Array,
|
|
495
670
|
undefined, SHARED_MEMORY_AVAILABLE
|
|
496
671
|
);
|
|
@@ -502,10 +677,28 @@ export class GeometryExtractor {
|
|
|
502
677
|
|
|
503
678
|
traverseObject( object ) {
|
|
504
679
|
|
|
505
|
-
|
|
680
|
+
this._visit( object );
|
|
681
|
+
|
|
682
|
+
// Process children recursively
|
|
683
|
+
if ( object.children ) {
|
|
684
|
+
|
|
685
|
+
for ( const child of object.children ) {
|
|
686
|
+
|
|
687
|
+
this.traverseObject( child );
|
|
688
|
+
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/** @private */
|
|
696
|
+
_visit( object ) {
|
|
697
|
+
|
|
506
698
|
if ( object.isMesh ) {
|
|
507
699
|
|
|
508
700
|
this.processMesh( object );
|
|
701
|
+
this._releaseAfterRead?.( object );
|
|
509
702
|
|
|
510
703
|
} else if ( object.isDirectionalLight ) {
|
|
511
704
|
|
|
@@ -517,17 +710,6 @@ export class GeometryExtractor {
|
|
|
517
710
|
|
|
518
711
|
}
|
|
519
712
|
|
|
520
|
-
// Process children recursively
|
|
521
|
-
if ( object.children ) {
|
|
522
|
-
|
|
523
|
-
for ( const child of object.children ) {
|
|
524
|
-
|
|
525
|
-
this.traverseObject( child );
|
|
526
|
-
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
}
|
|
530
|
-
|
|
531
713
|
}
|
|
532
714
|
|
|
533
715
|
processMesh( mesh ) {
|
|
@@ -602,6 +784,7 @@ export class GeometryExtractor {
|
|
|
602
784
|
this.meshTriangleRanges.push( range );
|
|
603
785
|
if ( range.count > 0 && ! bake && shareable ) this._geometryRanges.set( key, { ...range, meshIndex } );
|
|
604
786
|
this._recordPlacements( mesh, meshIndex, bake, expand );
|
|
787
|
+
if ( range.count > 0 ) this._onRange?.( meshIndex, range );
|
|
605
788
|
|
|
606
789
|
}
|
|
607
790
|
|
|
@@ -1209,10 +1392,24 @@ export class GeometryExtractor {
|
|
|
1209
1392
|
}
|
|
1210
1393
|
|
|
1211
1394
|
/** The filled triangle records, as one chunk when they fit and several when they do not. */
|
|
1395
|
+
/**
|
|
1396
|
+
* Lets go of the triangle store once its owner has taken it. The extractor's own reference
|
|
1397
|
+
* (and its float view) kept every chunk alive, so a spilled chunk never left memory.
|
|
1398
|
+
*/
|
|
1399
|
+
releaseTriangles() {
|
|
1400
|
+
|
|
1401
|
+
this.triangles = null;
|
|
1402
|
+
this.triangleData = null;
|
|
1403
|
+
this.triangleFloatChunks = null;
|
|
1404
|
+
this.triangleFloats = null;
|
|
1405
|
+
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1212
1408
|
getTriangleData() {
|
|
1213
1409
|
|
|
1214
1410
|
if ( ! this.triangles ) return null;
|
|
1215
|
-
|
|
1411
|
+
// A streamed store is trimmed where it stands: its receiver holds chunks of it on disk.
|
|
1412
|
+
return this._streamedStore ? this.triangles : this.triangles.trimTo( this.currentTriangleIndex );
|
|
1216
1413
|
|
|
1217
1414
|
}
|
|
1218
1415
|
|
|
@@ -1308,6 +1505,7 @@ export class GeometryExtractor {
|
|
|
1308
1505
|
|
|
1309
1506
|
// Reset triangle data
|
|
1310
1507
|
this.triangles = null;
|
|
1508
|
+
this._streamedStore = false;
|
|
1311
1509
|
this.triangleData = null;
|
|
1312
1510
|
this.triangleFloatChunks = null;
|
|
1313
1511
|
this.triangleFloats = null;
|
|
@@ -524,13 +524,18 @@ export class InstanceTable {
|
|
|
524
524
|
*
|
|
525
525
|
* @param {Float32Array} triangleData - Global triangle data (needed for leaf-root fallback)
|
|
526
526
|
*/
|
|
527
|
-
|
|
527
|
+
/**
|
|
528
|
+
* @param {Object} triangleData
|
|
529
|
+
* @param {{owners?: boolean}} [options] - `owners: false` when each owner's bounds were taken
|
|
530
|
+
* as its BLAS landed (a build that spills its BLASes keeps none of them here)
|
|
531
|
+
*/
|
|
532
|
+
computeAABBs( triangleData, { owners = true } = {} ) {
|
|
528
533
|
|
|
529
|
-
for ( let t = 0; t < this.templateCount; t ++ ) {
|
|
534
|
+
for ( let t = 0; owners && t < this.templateCount; t ++ ) {
|
|
530
535
|
|
|
531
536
|
if ( this.tplOwner[ t ] < 0 ) continue;
|
|
532
537
|
const blas = this.blasData.get( t );
|
|
533
|
-
if ( blas ) this._readRootAABB( blas, t, triangleData, this.tplObjectAABB, t * 6 );
|
|
538
|
+
if ( blas ) this._readRootAABB( Array.isArray( blas ) ? blas[ 0 ] : blas, t, triangleData, this.tplObjectAABB, t * 6 );
|
|
534
539
|
|
|
535
540
|
}
|
|
536
541
|
|
|
@@ -574,10 +579,17 @@ export class InstanceTable {
|
|
|
574
579
|
*/
|
|
575
580
|
_readRootAABB( bvhData, template, triangleData, out, off ) {
|
|
576
581
|
|
|
582
|
+
InstanceTable.rootAABB( bvhData, triangleData, this.tplTriOffset[ template ], this.tplTriCount[ template ], out, off );
|
|
583
|
+
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/** A BLAS's object-space bounds, from its root node or, for a leaf root, its triangles. */
|
|
587
|
+
static rootAABB( bvhData, triangleData, triOffset, triCount, out, off ) {
|
|
588
|
+
|
|
577
589
|
if ( ! bvhData || bvhIndexView( bvhData )[ 3 ] === BVH_LEAF_MARKERS.TRIANGLE_LEAF ) {
|
|
578
590
|
|
|
579
591
|
// Root is a leaf — very small mesh. Scan its triangles.
|
|
580
|
-
|
|
592
|
+
aabbOfTriangles( triangleData, triOffset, triCount, out, off );
|
|
581
593
|
return;
|
|
582
594
|
|
|
583
595
|
}
|
|
@@ -598,39 +610,7 @@ export class InstanceTable {
|
|
|
598
610
|
*/
|
|
599
611
|
_computeAABBFromTriangles( template, triangleData, out, off ) {
|
|
600
612
|
|
|
601
|
-
|
|
602
|
-
// Flat array or ChunkedRecords: resolve the chunk once per triangle either way.
|
|
603
|
-
const chunked = triangleData && triangleData.chunks ? triangleData.viewAs( Float32Array ) : null;
|
|
604
|
-
const flat = chunked ? null : ( triangleData instanceof Float32Array
|
|
605
|
-
? triangleData
|
|
606
|
-
: new Float32Array( triangleData.buffer, triangleData.byteOffset, triangleData.length ) );
|
|
607
|
-
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
608
|
-
let maxX = - Infinity, maxY = - Infinity, maxZ = - Infinity;
|
|
609
|
-
|
|
610
|
-
const triOffset = this.tplTriOffset[ template ], triCount = this.tplTriCount[ template ];
|
|
611
|
-
for ( let t = 0; t < triCount; t ++ ) {
|
|
612
|
-
|
|
613
|
-
const gi = triOffset + t;
|
|
614
|
-
const tri = chunked ? chunked.chunkFor( gi ) : flat;
|
|
615
|
-
const base = chunked ? chunked.baseOf( gi ) : gi * FPT;
|
|
616
|
-
|
|
617
|
-
// Positions A (offset 0), B (offset 4), C (offset 8)
|
|
618
|
-
for ( let o = 0; o <= 8; o += 4 ) {
|
|
619
|
-
|
|
620
|
-
const x = tri[ base + o ], y = tri[ base + o + 1 ], z = tri[ base + o + 2 ];
|
|
621
|
-
if ( x < minX ) minX = x;
|
|
622
|
-
if ( y < minY ) minY = y;
|
|
623
|
-
if ( z < minZ ) minZ = z;
|
|
624
|
-
if ( x > maxX ) maxX = x;
|
|
625
|
-
if ( y > maxY ) maxY = y;
|
|
626
|
-
if ( z > maxZ ) maxZ = z;
|
|
627
|
-
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
out[ off ] = minX; out[ off + 1 ] = minY; out[ off + 2 ] = minZ;
|
|
633
|
-
out[ off + 3 ] = maxX; out[ off + 4 ] = maxY; out[ off + 5 ] = maxZ;
|
|
613
|
+
aabbOfTriangles( triangleData, this.tplTriOffset[ template ], this.tplTriCount[ template ], out, off );
|
|
634
614
|
|
|
635
615
|
}
|
|
636
616
|
|
|
@@ -683,3 +663,41 @@ export class InstanceTable {
|
|
|
683
663
|
}
|
|
684
664
|
|
|
685
665
|
}
|
|
666
|
+
|
|
667
|
+
/** Bounds of triangles [triOffset, triOffset + triCount) of a flat or chunked store, into out[off..off+5]. */
|
|
668
|
+
function aabbOfTriangles( triangleData, triOffset, triCount, out, off ) {
|
|
669
|
+
|
|
670
|
+
const FPT = TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE;
|
|
671
|
+
// Flat array or ChunkedRecords: resolve the chunk once per triangle either way.
|
|
672
|
+
const chunked = triangleData && triangleData.chunks ? triangleData.viewAs( Float32Array ) : null;
|
|
673
|
+
const flat = chunked ? null : ( triangleData instanceof Float32Array
|
|
674
|
+
? triangleData
|
|
675
|
+
: new Float32Array( triangleData.buffer, triangleData.byteOffset, triangleData.length ) );
|
|
676
|
+
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
677
|
+
let maxX = - Infinity, maxY = - Infinity, maxZ = - Infinity;
|
|
678
|
+
|
|
679
|
+
for ( let t = 0; t < triCount; t ++ ) {
|
|
680
|
+
|
|
681
|
+
const gi = triOffset + t;
|
|
682
|
+
const tri = chunked ? chunked.chunkFor( gi ) : flat;
|
|
683
|
+
const base = chunked ? chunked.baseOf( gi ) : gi * FPT;
|
|
684
|
+
|
|
685
|
+
// Positions A (offset 0), B (offset 4), C (offset 8)
|
|
686
|
+
for ( let o = 0; o <= 8; o += 4 ) {
|
|
687
|
+
|
|
688
|
+
const x = tri[ base + o ], y = tri[ base + o + 1 ], z = tri[ base + o + 2 ];
|
|
689
|
+
if ( x < minX ) minX = x;
|
|
690
|
+
if ( y < minY ) minY = y;
|
|
691
|
+
if ( z < minZ ) minZ = z;
|
|
692
|
+
if ( x > maxX ) maxX = x;
|
|
693
|
+
if ( y > maxY ) maxY = y;
|
|
694
|
+
if ( z > maxZ ) maxZ = z;
|
|
695
|
+
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
out[ off ] = minX; out[ off + 1 ] = minY; out[ off + 2 ] = minZ;
|
|
701
|
+
out[ off + 3 ] = maxX; out[ off + 4 ] = maxY; out[ off + 5 ] = maxZ;
|
|
702
|
+
|
|
703
|
+
}
|