rayzee 7.31.1 → 8.0.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 +164 -0
- package/dist/rayzee.es.js +6366 -4361
- package/dist/rayzee.umd.js +122 -73
- package/package.json +1 -1
- package/src/EngineDefaults.js +168 -13
- package/src/EngineIssues.js +5 -0
- package/src/PathTracerApp.js +120 -19
- package/src/Processor/ArchiveReader.js +538 -0
- package/src/Processor/AssetLoader.js +265 -23
- package/src/Processor/BVHBuilder.js +48 -32
- package/src/Processor/BVHRefitter.js +244 -99
- package/src/Processor/ChunkedRecords.js +302 -0
- package/src/Processor/EmissiveTriangleBuilder.js +48 -21
- package/src/Processor/GeometryExtractor.js +577 -92
- package/src/Processor/HostMemory.js +222 -0
- package/src/Processor/InstanceTable.js +548 -97
- package/src/Processor/PBRT/EqualAreaOctahedral.js +32 -8
- package/src/Processor/PBRT/PBRTCurves.js +249 -0
- package/src/Processor/PBRT/PBRTMath.js +19 -0
- package/src/Processor/PBRT/PBRTParser.js +187 -41
- package/src/Processor/PBRT/PBRTSceneBuilder.js +586 -42
- package/src/Processor/PBRT/PBRTTokenizer.js +205 -65
- package/src/Processor/PBRT/index.js +257 -50
- package/src/Processor/PackedRayBuffer.js +29 -18
- package/src/Processor/ParallelBVHBuilder.js +9 -8
- package/src/Processor/QueueManager.js +5 -3
- package/src/Processor/SceneProcessor.js +1046 -365
- package/src/Processor/TLASBuilder.js +546 -201
- package/src/Processor/TextureCreator.js +11 -9
- package/src/Processor/VRAMTracker.js +12 -3
- package/src/Processor/Workers/BVHRefitWorker.js +16 -11
- package/src/Processor/Workers/BVHWorker.js +12 -12
- package/src/Processor/Workers/TLASWorker.js +37 -0
- package/src/Stages/NormalDepth.js +6 -2
- package/src/Stages/PathTracer.js +5 -5
- package/src/Stages/PathTracerStage.js +163 -41
- package/src/TSL/BVHTraversal.js +208 -90
- package/src/TSL/Common.js +135 -2
- package/src/TSL/Displacement.js +21 -11
- package/src/TSL/EmissiveSampling.js +49 -25
- package/src/TSL/ExtendKernel.js +3 -2
- package/src/TSL/LightBVHSampling.js +7 -4
- package/src/TSL/LightsDirect.js +30 -29
- package/src/TSL/ShadeKernel.js +8 -6
- package/src/TSL/Struct.js +3 -0
- package/src/TSL/TextureSampling.js +24 -9
- package/src/TSL/patches.js +128 -3
- package/src/index.js +3 -0
- package/src/managers/AnimationManager.js +86 -76
- package/src/managers/MaterialDataManager.js +29 -23
- package/src/managers/TransformManager.js +9 -192
package/package.json
CHANGED
package/src/EngineDefaults.js
CHANGED
|
@@ -533,25 +533,122 @@ export const AF_DEFAULTS = {
|
|
|
533
533
|
SNAP_THRESHOLD: 0.5,
|
|
534
534
|
};
|
|
535
535
|
|
|
536
|
-
|
|
536
|
+
/**
|
|
537
|
+
* Triangle record: 5 uvec4 lanes (80 bytes). The buffer is declared `uvec4` so packed lanes
|
|
538
|
+
* keep their exact bit pattern — a packSnorm2x16 result can land in the f32 NaN range, and an
|
|
539
|
+
* f32 lane may canonicalise it (the same reason the G-buffer is uvec4). Positions and UVs are
|
|
540
|
+
* written as f32 through a Float32Array view of the same memory and read back with
|
|
541
|
+
* `uintBitsToFloat`, so they carry full precision; only normals are compressed.
|
|
542
|
+
*
|
|
543
|
+
* Was 32 floats (128 B) with 4 dead padding lanes. At 128 B the 2 GB V8 ArrayBuffer cap put a
|
|
544
|
+
* hard ceiling of 16.7M triangles on the scene — 80 B lifts that to 26.8M and cuts geometry
|
|
545
|
+
* VRAM by the same 37.5%. Each normal now rides in its position's spare .w lane, so the three
|
|
546
|
+
* vec4 loads the intersection test already does carry the normals with them.
|
|
547
|
+
*/
|
|
537
548
|
export const TRIANGLE_DATA_LAYOUT = {
|
|
538
|
-
FLOATS_PER_TRIANGLE:
|
|
549
|
+
FLOATS_PER_TRIANGLE: 20,
|
|
539
550
|
|
|
540
|
-
//
|
|
541
|
-
POSITION_A_OFFSET: 0,
|
|
551
|
+
POSITION_A_OFFSET: 0, // f32 xyz + packed normal A
|
|
542
552
|
POSITION_B_OFFSET: 4,
|
|
543
553
|
POSITION_C_OFFSET: 8,
|
|
544
554
|
|
|
545
|
-
//
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
NORMAL_C_OFFSET: 20,
|
|
555
|
+
NORMAL_A_PACKED_OFFSET: 3, // oct16 in each position's .w lane
|
|
556
|
+
NORMAL_B_PACKED_OFFSET: 7,
|
|
557
|
+
NORMAL_C_PACKED_OFFSET: 11,
|
|
549
558
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
559
|
+
UV_AB_OFFSET: 12, // f32 uvA.xy, uvB.xy
|
|
560
|
+
UV_C_OFFSET: 16, // f32 uvC.xy
|
|
561
|
+
MATERIAL_FLAGS_OFFSET: 18, // materialIndex | side << 24 | shadowBlockerBits << 26 (two bits)
|
|
562
|
+
MESH_INDEX_OFFSET: 19
|
|
553
563
|
};
|
|
554
564
|
|
|
565
|
+
export const TRI_MATERIAL_MASK = 0xffffff;
|
|
566
|
+
export const TRI_SIDE_SHIFT = 24; // 0 front, 1 back, 2 double
|
|
567
|
+
export const TRI_BLOCKER_SHIFT = 26; // 1 = blocks shadow rays whatever the settings
|
|
568
|
+
export const TRI_BLOCKER_ALPHA_SHIFT = 27; // 1 = blocks them unless alpha-cutout shadows are on
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* How a shadow ray settles on this material without fetching it, mirroring traceShadowRay:
|
|
572
|
+
* bit 0 set = always a blocker; bit 1 set = a blocker while alpha-cutout shadows are off
|
|
573
|
+
* (MASK/BLEND with nothing else letting light through); 0 = light may pass, so the shadow
|
|
574
|
+
* traversal has to find the nearest such surface.
|
|
575
|
+
*/
|
|
576
|
+
export function shadowBlockerBits( material ) {
|
|
577
|
+
|
|
578
|
+
if ( ! material ) return 0;
|
|
579
|
+
const solid = ( material.transmission || 0 ) === 0
|
|
580
|
+
&& ( ( material.transparent | 0 ) === 0 || ( material.opacity ?? 1 ) >= 1 );
|
|
581
|
+
if ( ! solid ) return 0;
|
|
582
|
+
return ( material.alphaMode | 0 ) === 0 ? 1 : 2;
|
|
583
|
+
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Material index plus the per-triangle flags the shader reads without touching the
|
|
588
|
+
* material buffer: `side` for inline culling and the two shadow-blocker bits.
|
|
589
|
+
*/
|
|
590
|
+
export function packTriangleFlags( materialIndex, material ) {
|
|
591
|
+
|
|
592
|
+
return ( ( materialIndex & TRI_MATERIAL_MASK )
|
|
593
|
+
| ( ( material?.side ?? 0 ) << TRI_SIDE_SHIFT )
|
|
594
|
+
| ( shadowBlockerBits( material ) << TRI_BLOCKER_SHIFT ) ) >>> 0;
|
|
595
|
+
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Octahedral-encode a unit normal into one u32 (two snorm16). Worst-case error is ~0.03°,
|
|
600
|
+
* well under what normal maps and barycentric interpolation already contribute.
|
|
601
|
+
* A degenerate (zero-length) normal encodes as +Z rather than NaN.
|
|
602
|
+
*/
|
|
603
|
+
export function packNormalOct( x, y, z ) {
|
|
604
|
+
|
|
605
|
+
const len = Math.sqrt( x * x + y * y + z * z );
|
|
606
|
+
if ( len > 0 ) {
|
|
607
|
+
|
|
608
|
+
x /= len; y /= len; z /= len;
|
|
609
|
+
|
|
610
|
+
} else {
|
|
611
|
+
|
|
612
|
+
x = 0; y = 0; z = 1;
|
|
613
|
+
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const sum = Math.abs( x ) + Math.abs( y ) + Math.abs( z );
|
|
617
|
+
let u = x / sum, v = y / sum;
|
|
618
|
+
if ( z < 0 ) {
|
|
619
|
+
|
|
620
|
+
const au = u, av = v;
|
|
621
|
+
u = ( 1 - Math.abs( av ) ) * ( au >= 0 ? 1 : - 1 );
|
|
622
|
+
v = ( 1 - Math.abs( au ) ) * ( av >= 0 ? 1 : - 1 );
|
|
623
|
+
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const qu = Math.round( Math.min( 1, Math.max( - 1, u ) ) * 32767 ) & 0xffff;
|
|
627
|
+
const qv = Math.round( Math.min( 1, Math.max( - 1, v ) ) * 32767 ) & 0xffff;
|
|
628
|
+
return ( ( qv << 16 ) | qu ) >>> 0;
|
|
629
|
+
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/** Inverse of packNormalOct; writes into `out` (length >= 3) and returns it. */
|
|
633
|
+
export function unpackNormalOct( packed, out ) {
|
|
634
|
+
|
|
635
|
+
const u = ( ( packed << 16 ) >> 16 ) / 32767;
|
|
636
|
+
const v = ( packed >> 16 ) / 32767;
|
|
637
|
+
let x = u, y = v, z = 1 - Math.abs( u ) - Math.abs( v );
|
|
638
|
+
if ( z < 0 ) {
|
|
639
|
+
|
|
640
|
+
const ax = x, ay = y;
|
|
641
|
+
x = ( 1 - Math.abs( ay ) ) * ( ax >= 0 ? 1 : - 1 );
|
|
642
|
+
y = ( 1 - Math.abs( ax ) ) * ( ay >= 0 ? 1 : - 1 );
|
|
643
|
+
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const len = Math.sqrt( x * x + y * y + z * z ) || 1;
|
|
647
|
+
out[ 0 ] = x / len; out[ 1 ] = y / len; out[ 2 ] = z / len;
|
|
648
|
+
return out;
|
|
649
|
+
|
|
650
|
+
}
|
|
651
|
+
|
|
555
652
|
// Material data layout constants — single source of truth for material buffer offsets.
|
|
556
653
|
// Shared between CPU writers (TextureCreator, MaterialDataManager) and GPU readers (Common.js getMaterial).
|
|
557
654
|
export const MATERIAL_DATA_LAYOUT = {
|
|
@@ -652,11 +749,69 @@ export const MATERIAL_DATA_LAYOUT = {
|
|
|
652
749
|
export const normalizeAttenuationDistance = d => ( Number.isFinite( d ) && d > 0 ? d : 0 );
|
|
653
750
|
|
|
654
751
|
// BVH node leaf markers
|
|
752
|
+
/**
|
|
753
|
+
* Node tags, written into slot [3] of a BVH node as a raw u32 bit pattern.
|
|
754
|
+
*
|
|
755
|
+
* Node indices, triangle offsets and counts are integers living inside a Float32Array. Written as
|
|
756
|
+
* float *values* they round silently past 2^24 (16,777,216): in a 24M-node scene half of every
|
|
757
|
+
* BLAS pointer landed on a neighbouring node and that geometry vanished from the render with no
|
|
758
|
+
* error at all. Every one of those fields is written as a u32 bit pattern instead, exact to 2^30.
|
|
759
|
+
*
|
|
760
|
+
* Tags sit above {@link BVH_MAX_INDEX} so a single unsigned compare separates a leaf from an inner
|
|
761
|
+
* node's left-child index. All three are ordinary finite floats — nothing lands in the NaN range,
|
|
762
|
+
* which an f32 storage buffer is free to canonicalise.
|
|
763
|
+
*/
|
|
764
|
+
export const BVH_MAX_INDEX = 0x40000000; // 2^30
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Slot [1] of a BLAS-pointer leaf holds its placement index, which {@link BVH_MAX_INDEX} keeps
|
|
768
|
+
* below 2^30. Bit 30 is therefore free to say the leaf's matrix is identity: geometry no other
|
|
769
|
+
* placement shares is baked to world space at extraction, and a ray reaching it needs no
|
|
770
|
+
* transform at all. Mask the bit off before using the slot as an index.
|
|
771
|
+
*/
|
|
772
|
+
export const TLAS_LEAF_IDENTITY = 0x40000000;
|
|
773
|
+
export const TLAS_PLACEMENT_MASK = 0x3fffffff;
|
|
774
|
+
|
|
655
775
|
export const BVH_LEAF_MARKERS = {
|
|
656
|
-
TRIANGLE_LEAF:
|
|
657
|
-
BLAS_POINTER_LEAF:
|
|
776
|
+
TRIANGLE_LEAF: 0x40000000, // leaf containing triangle references
|
|
777
|
+
BLAS_POINTER_LEAF: 0x40000001, // TLAS leaf pointing to a BLAS root node
|
|
778
|
+
FRONTIER: 0x40000002, // parallel-build placeholder, overwritten during assembly
|
|
658
779
|
};
|
|
659
780
|
|
|
781
|
+
/** A u32 view over a float buffer, for writing index fields as exact bit patterns. */
|
|
782
|
+
export function bvhIndexView( f32 ) {
|
|
783
|
+
|
|
784
|
+
return new Uint32Array( f32.buffer, f32.byteOffset, f32.length );
|
|
785
|
+
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Refuse to build a BVH whose indices would collide with the leaf tags.
|
|
790
|
+
*
|
|
791
|
+
* Throws rather than degrades: the failure this replaces was a scene that rendered with half its
|
|
792
|
+
* geometry silently missing, which is far worse than a scene that refuses to load. Guarding the
|
|
793
|
+
* totals covers every individual write, since no index can exceed the count it indexes into.
|
|
794
|
+
*
|
|
795
|
+
* @param {number} count - node or triangle total about to be indexed
|
|
796
|
+
* @param {string} what - what the count is, for the message
|
|
797
|
+
* @throws {RangeError}
|
|
798
|
+
*/
|
|
799
|
+
export function assertBVHIndexFits( count, what ) {
|
|
800
|
+
|
|
801
|
+
if ( count >= BVH_MAX_INDEX ) {
|
|
802
|
+
|
|
803
|
+
throw new RangeError(
|
|
804
|
+
`${what} is ${count.toLocaleString()}, at or past the BVH index limit of ` +
|
|
805
|
+
`${BVH_MAX_INDEX.toLocaleString()}. Node indices are stored as u32 bit patterns and the ` +
|
|
806
|
+
'leaf tags occupy everything above that.'
|
|
807
|
+
);
|
|
808
|
+
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
return count;
|
|
812
|
+
|
|
813
|
+
}
|
|
814
|
+
|
|
660
815
|
// Texture processing constants
|
|
661
816
|
export const TEXTURE_CONSTANTS = {
|
|
662
817
|
PIXELS_PER_MATERIAL: 30,
|
package/src/EngineIssues.js
CHANGED
|
@@ -8,6 +8,8 @@ export const ISSUE_CODES = Object.freeze( {
|
|
|
8
8
|
ADAPTER_SOFTWARE: 'adapter.software',
|
|
9
9
|
ASSET_UNREACHABLE: 'asset.unreachable',
|
|
10
10
|
ASSET_AMBIGUOUS_ENTRY: 'asset.ambiguous_entry',
|
|
11
|
+
ASSET_ARCHIVE_TOO_LARGE: 'asset.archive_too_large',
|
|
12
|
+
ASSET_ENTRY_TOO_LARGE: 'asset.entry_too_large',
|
|
11
13
|
TEXTURE_BUILD_FAILED: 'texture.build_failed',
|
|
12
14
|
TEXTURE_PROCESSING_FALLBACK: 'texture.processing_fallback',
|
|
13
15
|
TEXTURE_LIMIT_EXCEEDED: 'texture.limit_exceeded',
|
|
@@ -16,6 +18,9 @@ export const ISSUE_CODES = Object.freeze( {
|
|
|
16
18
|
RENDER_SIZE_DECLINED: 'render.size_declined',
|
|
17
19
|
STAGE_RENDER_FAILED: 'stage.render_failed',
|
|
18
20
|
RENDER_RESERVE_CAPPED: 'render.reserve_capped',
|
|
21
|
+
SCENE_MEMORY_BUDGET: 'scene.memory_budget',
|
|
22
|
+
EMISSIVE_INSTANCES_COLLAPSED: 'emissive.instances_collapsed',
|
|
23
|
+
REFIT_SHARED_GEOMETRY: 'refit.shared_geometry',
|
|
19
24
|
} );
|
|
20
25
|
|
|
21
26
|
/** Strict throws on ERROR only. */
|
package/src/PathTracerApp.js
CHANGED
|
@@ -122,6 +122,9 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
122
122
|
* rendering a plausible wrong image. See EngineIssues.js; read `app.issues` when off.
|
|
123
123
|
* @param {string} [options.profile='viewer'] - Which tuning to apply where the viewer's
|
|
124
124
|
* product decisions differ from the physical answer. See RENDER_PROFILES.
|
|
125
|
+
* @param {number} [options.maxSceneBytes] - refuse a scene whose estimated host memory is
|
|
126
|
+
* above this. The default refuses where the renderer process would be killed instead of
|
|
127
|
+
* throwing; raise it deliberately, on a fresh browser. See HostMemory.js.
|
|
125
128
|
*
|
|
126
129
|
* The engine dispatches `EngineEvents.FRAME` after each animate() iteration so hosts can
|
|
127
130
|
* tick external instrumentation (e.g. a stats panel) without coupling the engine to it.
|
|
@@ -144,6 +147,8 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
144
147
|
|
|
145
148
|
this.canvas = canvas;
|
|
146
149
|
this._autoResize = options.autoResize !== false;
|
|
150
|
+
// A scene budget the host may raise; read where SceneProcessor is built, well after this.
|
|
151
|
+
this._maxSceneBytes = options.maxSceneBytes;
|
|
147
152
|
this._container = options.container || null;
|
|
148
153
|
// Apply the environment authored into a model file's metadata on load. See _beginSceneMetadataEnvironment().
|
|
149
154
|
this._applySceneMetadataEnabled = options.applySceneMetadata !== false;
|
|
@@ -796,6 +801,18 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
796
801
|
|
|
797
802
|
}
|
|
798
803
|
|
|
804
|
+
/**
|
|
805
|
+
* Lists the independently loadable parts of an archive without unpacking it, so a host
|
|
806
|
+
* can offer a choice for a scene too large to load whole.
|
|
807
|
+
* @param {File} file
|
|
808
|
+
* @returns {Promise<{kind:string, root:string|null, elements:Array, entryCount:number, totalBytes:number}>}
|
|
809
|
+
*/
|
|
810
|
+
async inspectArchive( file ) {
|
|
811
|
+
|
|
812
|
+
return await this.assetLoader.inspectArchive( file );
|
|
813
|
+
|
|
814
|
+
}
|
|
815
|
+
|
|
799
816
|
/**
|
|
800
817
|
* Loads a user-supplied File (drag-drop, file picker) — model, archive, or environment
|
|
801
818
|
* map, dispatched by extension.
|
|
@@ -808,9 +825,11 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
808
825
|
* LOAD_IN_PROGRESS before anything is touched.
|
|
809
826
|
*
|
|
810
827
|
* @param {File} file
|
|
828
|
+
* @param {object} [options] - forwarded to the archive loader: `element` to load one
|
|
829
|
+
* subtree of a multi-part scene, `pbrtEntry` to choose among several .pbrt scenes.
|
|
811
830
|
* @returns {Promise<void>}
|
|
812
831
|
*/
|
|
813
|
-
async loadFile( file ) {
|
|
832
|
+
async loadFile( file, options = {} ) {
|
|
814
833
|
|
|
815
834
|
const format = this.assetLoader?.getFileFormat( file?.name || '' );
|
|
816
835
|
if ( ! format ) throw new Error( `Unsupported file format: ${file?.name}` );
|
|
@@ -818,7 +837,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
818
837
|
if ( format.type !== 'environment' && format.type !== 'image' ) {
|
|
819
838
|
|
|
820
839
|
await this._loadWithSceneRebuild(
|
|
821
|
-
() => this.assetLoader.loadAssetFromFile( file ),
|
|
840
|
+
() => this.assetLoader.loadAssetFromFile( file, options ),
|
|
822
841
|
{ type: 'ModelLoaded', filename: file.name }
|
|
823
842
|
);
|
|
824
843
|
return;
|
|
@@ -904,7 +923,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
904
923
|
}
|
|
905
924
|
|
|
906
925
|
// Reprocess the loaded scene so the new cap takes effect immediately.
|
|
907
|
-
if ( reprocess && this._maxTextureSize !== prev && this._sdf?.
|
|
926
|
+
if ( reprocess && this._maxTextureSize !== prev && this._sdf?.triangles && ! this._loadingInProgress ) {
|
|
908
927
|
|
|
909
928
|
this._loadingInProgress = true;
|
|
910
929
|
try {
|
|
@@ -948,6 +967,14 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
948
967
|
this.dispatchEvent( eventPayload );
|
|
949
968
|
this._dispatchCamerasUpdated();
|
|
950
969
|
|
|
970
|
+
} catch ( error ) {
|
|
971
|
+
|
|
972
|
+
// loadFn released the previous model before this one was known to be loadable, so
|
|
973
|
+
// there is nothing to fall back to. Leaving it half-built would keep the last
|
|
974
|
+
// frame's buffers on screen under a scene that no longer exists.
|
|
975
|
+
if ( ! error || error.code !== 'LOAD_IN_PROGRESS' ) this._discardFailedLoad();
|
|
976
|
+
throw error;
|
|
977
|
+
|
|
951
978
|
} finally {
|
|
952
979
|
|
|
953
980
|
this._loadingInProgress = false;
|
|
@@ -956,6 +983,27 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
956
983
|
|
|
957
984
|
}
|
|
958
985
|
|
|
986
|
+
/**
|
|
987
|
+
* Put the engine back to an empty scene after a load failed part-way. The failure itself is
|
|
988
|
+
* rethrown for the host to report; this only makes sure what is on screen matches it.
|
|
989
|
+
* @private
|
|
990
|
+
*/
|
|
991
|
+
_discardFailedLoad() {
|
|
992
|
+
|
|
993
|
+
try {
|
|
994
|
+
|
|
995
|
+
this._clearAppendedModels();
|
|
996
|
+
this.assetLoader?.releaseTargetModel();
|
|
997
|
+
this.reset();
|
|
998
|
+
|
|
999
|
+
} catch ( cleanupError ) {
|
|
1000
|
+
|
|
1001
|
+
console.warn( 'PathTracerApp: could not clear the scene after a failed load', cleanupError );
|
|
1002
|
+
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
}
|
|
1006
|
+
|
|
959
1007
|
/**
|
|
960
1008
|
* Scene-level authoring metadata carried by the current model file (glTF `extras`),
|
|
961
1009
|
* or null when the file has none. See {@link module:Processor/SceneMetadata}.
|
|
@@ -1198,7 +1246,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1198
1246
|
_sceneSummaryParts() {
|
|
1199
1247
|
|
|
1200
1248
|
const pt = this.stages.pathTracer;
|
|
1201
|
-
const meshes = this._sdf?.instanceTable?.
|
|
1249
|
+
const meshes = this._sdf?.instanceTable?.setCount ?? 0;
|
|
1202
1250
|
const maps = this._sdf?.geometryExtractor?.maps?.length ?? 0;
|
|
1203
1251
|
|
|
1204
1252
|
return [
|
|
@@ -1497,16 +1545,22 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1497
1545
|
* Topology must stay the same (same triangle count and connectivity).
|
|
1498
1546
|
* Call this per-frame for skeletal/morph-target animation.
|
|
1499
1547
|
*
|
|
1500
|
-
*
|
|
1501
|
-
*
|
|
1548
|
+
* Positions come in one of two shapes. A **callback** `(meshIndex, triCount) => Float32Array`
|
|
1549
|
+
* is asked for one mesh at a time and may return the same scratch buffer each call — prefer it,
|
|
1550
|
+
* since it never holds more than one mesh. A **scene-wide Float32Array** of 9 floats per
|
|
1551
|
+
* triangle for every triangle (meshes in {@link sceneMeshes} order, triangles in index order)
|
|
1552
|
+
* still works, but is 1,030 MB at 30M triangles and will not allocate at that size.
|
|
1553
|
+
*
|
|
1554
|
+
* @param {Float32Array|function(number, number): Float32Array} newPositions - (ax,ay,az, bx,by,bz, cx,cy,cz) per triangle, world space
|
|
1555
|
+
* @param {Float32Array|function(number, number): Float32Array} [newNormals] - Optional smooth normals, same two shapes. If omitted, face normals are computed from positions.
|
|
1502
1556
|
* @returns {Promise<{ refitTimeMs: number }>}
|
|
1503
1557
|
*/
|
|
1504
1558
|
async refitBVH( newPositions, newNormals ) {
|
|
1505
1559
|
|
|
1506
1560
|
const result = await this._sdf.refitBVH( newPositions, newNormals );
|
|
1507
1561
|
|
|
1508
|
-
this.stages.pathTracer.updateTriangleData( this._sdf.
|
|
1509
|
-
this.stages.pathTracer.updateBVHData( this._sdf.
|
|
1562
|
+
this.stages.pathTracer.updateTriangleData( this._sdf.triangles );
|
|
1563
|
+
this.stages.pathTracer.updateBVHData( this._sdf.bvh );
|
|
1510
1564
|
this.reset();
|
|
1511
1565
|
|
|
1512
1566
|
return result;
|
|
@@ -1518,8 +1572,9 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1518
1572
|
* Faster than refitBVH for single-object transforms in multi-mesh scenes.
|
|
1519
1573
|
*
|
|
1520
1574
|
* @param {number[]} affectedMeshIndices - Mesh indices to refit
|
|
1521
|
-
* @param {Float32Array} newPositions -
|
|
1522
|
-
*
|
|
1575
|
+
* @param {Float32Array|function(number, number): Float32Array} newPositions - the same two shapes
|
|
1576
|
+
* {@link refitBVH} takes; only the affected meshes are asked for
|
|
1577
|
+
* @param {Float32Array|function(number, number): Float32Array} [newNormals] - Optional smooth normals
|
|
1523
1578
|
* @returns {{ refitTimeMs: number }}
|
|
1524
1579
|
*/
|
|
1525
1580
|
refitBLASes( affectedMeshIndices, newPositions, newNormals ) {
|
|
@@ -1531,11 +1586,12 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1531
1586
|
this.reset();
|
|
1532
1587
|
|
|
1533
1588
|
// Kick off background rebuild for optimal SAH quality
|
|
1534
|
-
this._sdf.scheduleBackgroundRebuild( affectedMeshIndices, () => {
|
|
1589
|
+
this._sdf.scheduleBackgroundRebuild( affectedMeshIndices, ( meshIndex ) => {
|
|
1535
1590
|
|
|
1536
|
-
// Swap complete — upload
|
|
1537
|
-
|
|
1538
|
-
|
|
1591
|
+
// Swap complete — upload just that mesh's triangles and nodes, plus the TLAS. A whole
|
|
1592
|
+
// re-upload here is gigabytes on a large scene, for one mesh's worth of change.
|
|
1593
|
+
const dirty = this._sdf.computeBLASDirtyRanges( [ meshIndex ] );
|
|
1594
|
+
this.stages.pathTracer.updateBufferRanges( dirty.triRanges, dirty.bvhRanges );
|
|
1539
1595
|
this.reset();
|
|
1540
1596
|
|
|
1541
1597
|
} );
|
|
@@ -1544,6 +1600,28 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1544
1600
|
|
|
1545
1601
|
}
|
|
1546
1602
|
|
|
1603
|
+
/**
|
|
1604
|
+
* Apply new transforms for objects that moved, without touching their geometry.
|
|
1605
|
+
*
|
|
1606
|
+
* This is the right call for a gizmo drag or any other rigid move: triangles are stored in
|
|
1607
|
+
* each object's own space, so only the placement matrix changes. {@link refitBLASes} is for
|
|
1608
|
+
* geometry that actually deformed — on a rigid move it rewrites vertices needlessly, and
|
|
1609
|
+
* drags along any other object sharing the same geometry.
|
|
1610
|
+
*
|
|
1611
|
+
* @param {number[]} meshIndices - indices into {@link sceneMeshes}
|
|
1612
|
+
* @returns {{ refitTimeMs: number, placements: number }}
|
|
1613
|
+
*/
|
|
1614
|
+
updateMeshTransforms( meshIndices ) {
|
|
1615
|
+
|
|
1616
|
+
const result = this._sdf.updateMeshTransforms( meshIndices );
|
|
1617
|
+
|
|
1618
|
+
this.stages.pathTracer.updateBufferRanges( [], [ this._sdf.computeTLASDirtyRange() ] );
|
|
1619
|
+
this.reset();
|
|
1620
|
+
|
|
1621
|
+
return result;
|
|
1622
|
+
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1547
1625
|
// ═══════════════════════════════════════════════════════════════
|
|
1548
1626
|
// Resize
|
|
1549
1627
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -2503,6 +2581,25 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
2503
2581
|
|
|
2504
2582
|
}
|
|
2505
2583
|
|
|
2584
|
+
/**
|
|
2585
|
+
* CPU-side memory for the last scene build: what the preflight predicted, what each phase
|
|
2586
|
+
* allocated, and what was live at each phase boundary.
|
|
2587
|
+
*
|
|
2588
|
+
* Note that `performance.memory.usedJSHeapSize` does NOT count SharedArrayBuffer, and the
|
|
2589
|
+
* triangle and BVH stores are SAB-backed — so the browser's own heap reading under-reports
|
|
2590
|
+
* a large scene by several gigabytes and this is the figure to trust.
|
|
2591
|
+
*
|
|
2592
|
+
* @returns {?{preflight: ?Object, allocatedBytes: number, peakLiveBytes: number,
|
|
2593
|
+
* byPhase: Object, samples: Object[]}} null before a scene is built
|
|
2594
|
+
*/
|
|
2595
|
+
getHostMemoryInfo() {
|
|
2596
|
+
|
|
2597
|
+
const sp = this._sdf;
|
|
2598
|
+
if ( ! sp?.memory ) return null;
|
|
2599
|
+
return { preflight: sp.memoryPreflight, ...sp.memory.report };
|
|
2600
|
+
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2506
2603
|
// Idempotent: registers the cross-stage texture provider and re-measures on
|
|
2507
2604
|
// allocation events (scene/env load, resize) so peak is caught even while idle.
|
|
2508
2605
|
_ensureVRAMWiring() {
|
|
@@ -2631,13 +2728,13 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
2631
2728
|
*/
|
|
2632
2729
|
_refreshEmissiveForVisibility() {
|
|
2633
2730
|
|
|
2634
|
-
const
|
|
2635
|
-
if ( !
|
|
2731
|
+
const table = this._sdf?.instanceTable;
|
|
2732
|
+
if ( ! table ) return;
|
|
2636
2733
|
|
|
2637
2734
|
const hidden = new Set();
|
|
2638
|
-
for (
|
|
2735
|
+
for ( let i = 0; i < table.count; i ++ ) {
|
|
2639
2736
|
|
|
2640
|
-
if (
|
|
2737
|
+
if ( table.isSet[ i ] && ! table.visible[ i ] ) hidden.add( i );
|
|
2641
2738
|
|
|
2642
2739
|
}
|
|
2643
2740
|
|
|
@@ -2856,7 +2953,11 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
2856
2953
|
|
|
2857
2954
|
_initAssetPipeline() {
|
|
2858
2955
|
|
|
2859
|
-
this._sdf = new SceneProcessor( {
|
|
2956
|
+
this._sdf = new SceneProcessor( {
|
|
2957
|
+
issues: this._issues,
|
|
2958
|
+
// Spread into defaults, so only pass it when the host actually set one.
|
|
2959
|
+
...( this._maxSceneBytes === undefined ? {} : { maxSceneBytes: this._maxSceneBytes } ),
|
|
2960
|
+
} );
|
|
2860
2961
|
this.assetLoader = new AssetLoader(
|
|
2861
2962
|
this.meshScene, this.cameraManager.camera, this.cameraManager.controls,
|
|
2862
2963
|
{ issues: this._issues, profile: this._profile }
|