rayzee 11.12.0 → 11.13.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/dist/{ArchiveImporter-vUowipjh.js → ArchiveImporter-DQU55mit.js} +1322 -1290
- package/dist/{BidirectionalIntegrator-DPh6PSYb.js → BidirectionalIntegrator-CLmFjSN3.js} +2 -2
- package/dist/{NeuralRetouch-wwAwssFM.js → NeuralRetouch-CEkBhI1M.js} +1 -1
- package/dist/{NeuralSuperRes-DqV4v_gQ.js → NeuralSuperRes-Drjws9nE.js} +1 -1
- package/dist/{PathTracer-DW-ElMON.js → PathTracer-7a7L3cGF.js} +3448 -3404
- package/dist/{PhysicalSky-Ispr77Lf.js → PhysicalSky-DUIbTE93.js} +1 -1
- package/dist/{RayzeeRenderer-CPbY683A.js → RayzeeRenderer-CjjGT2e0.js} +340 -340
- package/dist/{SceneMetadata-BNA22-m3.js → SceneMetadata-CoAHTAmo.js} +1 -1
- package/dist/addons/archives.es.js +1 -1
- package/dist/addons/bidirectional.es.js +1 -1
- package/dist/addons/physical-sky.es.js +1 -1
- package/dist/rayzee-core.es.js +4 -4
- package/dist/rayzee.es.js +13 -13
- package/dist/rayzee.umd.js +110 -110
- package/dist/{sceneCachePolicy-BLvdjkVh.js → sceneCachePolicy-DavHt6Ii.js} +217 -185
- package/package.json +1 -1
- package/src/Processor/ArchiveImporter.js +25 -3
- package/src/Processor/AssetLoader.js +6 -4
- package/src/Processor/InstanceTable.js +36 -11
- package/src/Processor/PBRT/PBRTCurves.js +6 -3
- package/src/Processor/PBRT/PBRTSceneBuilder.js +26 -3
- package/src/Processor/PBRT/index.js +1 -0
- package/src/Processor/SceneProcessor.js +43 -5
- package/src/Processor/TLASBuilder.js +5 -4
- package/src/Processor/USD/USDScene.js +6 -1
- package/src/Processor/USD/index.js +2 -2
- package/src/Processor/utils.js +80 -1
- package/src/Stages/PathTracerStage.js +17 -6
- package/src/TSL/BVHTraversal.js +1 -1
package/package.json
CHANGED
|
@@ -21,7 +21,7 @@ import { ENGINE_AREAS } from '../Storage/areas.js';
|
|
|
21
21
|
import { encodeSceneGraph, writeSceneGraph, decodeSceneGraph, SceneGraphUnsupported, SCENE_GRAPH_FORMAT, ARCHIVE_PATH, ARCHIVE_LOADER } from '../Storage/SceneGraphCodec.js';
|
|
22
22
|
import { worthStoring } from '../Storage/sceneCachePolicy.js';
|
|
23
23
|
import { VERSION } from '../version.js';
|
|
24
|
-
import { updateLoading } from './utils';
|
|
24
|
+
import { timeSlicer, updateLoading } from './utils';
|
|
25
25
|
import { loadPBRTScene, pickEntryPath, VirtualFS, PBRT_BUILD_REVISION } from './PBRT/index.js';
|
|
26
26
|
import { loadUSDScene, listUSDRootLayers, listUSDParts, USDFiles, USDStage, USD_LAYER } from './USD/index.js';
|
|
27
27
|
import { pfmTexture } from './PBRT/PFM.js';
|
|
@@ -687,7 +687,7 @@ export class ArchiveImporter {
|
|
|
687
687
|
: null;
|
|
688
688
|
|
|
689
689
|
const stored = graphKey ? await this._loadStoredGraph( graphKey, source, { imageFromBytes, envFromBytes } ) : null;
|
|
690
|
-
const built = stored ?? await loadPBRTScene( { vfs: zip, source, entryPath, plyParser, imageFromBytes, envFromBytes, ...shape } );
|
|
690
|
+
const built = stored ?? await loadPBRTScene( { vfs: zip, source, entryPath, plyParser, imageFromBytes, envFromBytes, ...shape, pause: this._buildPause( timeSlicer() ) } );
|
|
691
691
|
this.loader._throwDeferred();
|
|
692
692
|
|
|
693
693
|
const { group, environment, animations, report, warnings, meshCount, entryPath: loadedEntry, candidates,
|
|
@@ -819,7 +819,7 @@ export class ArchiveImporter {
|
|
|
819
819
|
const url = ext === 'pfm' ? null : URL.createObjectURL( blob );
|
|
820
820
|
try {
|
|
821
821
|
|
|
822
|
-
const texture = url ? await this.loader.loadEnvironmentByExtension( url, ext, { loader: ext === 'exr' ? this._exrLoader() : null } ) : pfmTexture( bytes );
|
|
822
|
+
const texture = url ? await this.loader.loadEnvironmentByExtension( url, ext, { loader: ext === 'exr' ? this._exrLoader() : null, quiet: true } ) : pfmTexture( bytes );
|
|
823
823
|
texture.mapping = EquirectangularReflectionMapping;
|
|
824
824
|
setEnvironmentSource( texture, `bytes:${await sampleHash( blob )}` );
|
|
825
825
|
texture.userData[ ARCHIVE_PATH ] = fname;
|
|
@@ -863,8 +863,17 @@ export class ArchiveImporter {
|
|
|
863
863
|
if ( chosen.length === 0 ) await this._requireUSDPartChoice( filename, files, entryPath, options.listing ?? paths.map( path => ( { path, size: entries[ path ]?.size ?? entries[ path ]?.byteLength ?? 0 } ) ), options.promptBytes );
|
|
864
864
|
|
|
865
865
|
const start = performance.now();
|
|
866
|
+
const slice = timeSlicer();
|
|
866
867
|
const built = await loadUSDScene( {
|
|
867
868
|
files, entryPath,
|
|
869
|
+
pauseRead: ( prims, counting ) => {
|
|
870
|
+
|
|
871
|
+
const turn = slice();
|
|
872
|
+
if ( turn ) updateLoading( { status: `${counting ? 'Counting' : 'Reading'} USD scene... ${prims.toLocaleString()} prims`, progress: counting ? 5 : 6 } );
|
|
873
|
+
return turn;
|
|
874
|
+
|
|
875
|
+
},
|
|
876
|
+
pauseBuild: this._buildPause( slice ),
|
|
868
877
|
resolveImage: async path => {
|
|
869
878
|
|
|
870
879
|
const bytes = await files.read( path );
|
|
@@ -918,6 +927,19 @@ export class ArchiveImporter {
|
|
|
918
927
|
|
|
919
928
|
}
|
|
920
929
|
|
|
930
|
+
/** The scene builder's pause: a turn for the page whenever `slice` gives one, with how far the build is. @private */
|
|
931
|
+
_buildPause( slice ) {
|
|
932
|
+
|
|
933
|
+
return ( done, total ) => {
|
|
934
|
+
|
|
935
|
+
const turn = slice();
|
|
936
|
+
if ( turn ) updateLoading( { status: `Building scene... ${done.toLocaleString()} of ${total.toLocaleString()} shapes`, progress: 8 } );
|
|
937
|
+
return turn;
|
|
938
|
+
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
}
|
|
942
|
+
|
|
921
943
|
/** Asks which parts of a large USD scene to load. @private */
|
|
922
944
|
async _requireUSDPartChoice( filename, files, entryPath, listing, promptBytes = USD_ELEMENT_PROMPT_BYTES ) {
|
|
923
945
|
|
|
@@ -9,7 +9,7 @@ import { clone as cloneWithSkeletons } from 'three/addons/utils/SkeletonUtils.js
|
|
|
9
9
|
import { DownloadCache, nameFromUrl, cachedObjectURL } from '../Storage/DownloadCache.js';
|
|
10
10
|
import { setEnvironmentSource } from '../Storage/CDFCache.js';
|
|
11
11
|
import { fileIdentity, identityKey } from '../Storage/identity.js';
|
|
12
|
-
import { disposeEngineOwnedResources, disposeObjectFromMemory, updateLoading } from './utils';
|
|
12
|
+
import { computeInstanceBoundingBoxes, disposeEngineOwnedResources, disposeObjectFromMemory, updateLoading } from './utils';
|
|
13
13
|
import { BuildTimer } from './BuildTimer.js';
|
|
14
14
|
import { getPlatform, hasImageDecoder, withHostWorker } from '../Platform.js';
|
|
15
15
|
import { loadPlatformImage, platformImagesPlugin, missingImageDecoderPlugin } from './PlatformImageLoader.js';
|
|
@@ -723,7 +723,8 @@ export class AssetLoader extends EventDispatcher {
|
|
|
723
723
|
* @param {string} extension
|
|
724
724
|
* @param {{loader?: Object}} [options] - a three.js loader to read the file with, for a format nothing registered
|
|
725
725
|
*/
|
|
726
|
-
|
|
726
|
+
/** `quiet` leaves the loading bar to a larger load this environment is part of. */
|
|
727
|
+
async loadEnvironmentByExtension( url, extension, { loader = null, quiet = false } = {} ) {
|
|
727
728
|
|
|
728
729
|
const format = this._formats.get( extension );
|
|
729
730
|
if ( ! loader && format?.type === 'environment' ) loader = this.loaderCache[ extension ] ??= await format.createLoader( this._loadingManager );
|
|
@@ -733,7 +734,7 @@ export class AssetLoader extends EventDispatcher {
|
|
|
733
734
|
const cancelable = AssetLoader._isNetworkUrl( url );
|
|
734
735
|
const status = "Downloading Environment...";
|
|
735
736
|
const source = await this._viaCache( url, status, cancelable );
|
|
736
|
-
const onProgress = source.cached ? undefined : this._downloadProgress( status, cancelable );
|
|
737
|
+
const onProgress = source.cached || quiet ? undefined : this._downloadProgress( status, cancelable );
|
|
737
738
|
|
|
738
739
|
let texture;
|
|
739
740
|
try {
|
|
@@ -763,7 +764,7 @@ export class AssetLoader extends EventDispatcher {
|
|
|
763
764
|
|
|
764
765
|
}
|
|
765
766
|
|
|
766
|
-
this._downloadComplete( "Processing Environment...", 62 );
|
|
767
|
+
if ( ! quiet ) this._downloadComplete( "Processing Environment...", 62 );
|
|
767
768
|
|
|
768
769
|
if ( this.maxEnvironmentWidth ) limitEnvironmentWidth( texture, this.maxEnvironmentWidth );
|
|
769
770
|
texture.mapping = EquirectangularReflectionMapping;
|
|
@@ -1042,6 +1043,7 @@ export class AssetLoader extends EventDispatcher {
|
|
|
1042
1043
|
|
|
1043
1044
|
// Center model and adjust camera
|
|
1044
1045
|
buildTimer.start( 'Camera setup' );
|
|
1046
|
+
computeInstanceBoundingBoxes( model );
|
|
1045
1047
|
const box = new Box3().setFromObject( model );
|
|
1046
1048
|
const center = box.getCenter( new Vector3() );
|
|
1047
1049
|
const size = box.getSize( new Vector3() );
|
|
@@ -550,10 +550,20 @@ export class InstanceTable {
|
|
|
550
550
|
*/
|
|
551
551
|
formClusters( world ) {
|
|
552
552
|
|
|
553
|
+
const steps = this.clusterSteps( world );
|
|
554
|
+
let step;
|
|
555
|
+
do step = steps.next(); while ( ! step.done );
|
|
556
|
+
return step.value;
|
|
557
|
+
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/** {@link formClusters} in steps: yields how many entries have their cluster, and returns what it returns. */
|
|
561
|
+
* clusterSteps( world ) {
|
|
562
|
+
|
|
553
563
|
const n = this.entryCount;
|
|
554
564
|
const sorted = new Int32Array( n );
|
|
555
565
|
for ( let e = 0; e < n; e ++ ) sorted[ e ] = e;
|
|
556
|
-
splitIntoRuns( sorted, 0, n, world );
|
|
566
|
+
yield * splitIntoRuns( sorted, 0, n, world );
|
|
557
567
|
|
|
558
568
|
const bounds = new Float64Array( this.clusterCount * 6 );
|
|
559
569
|
for ( let c = 0; c < this.clusterCount; c ++ ) {
|
|
@@ -936,31 +946,31 @@ export class InstanceTable {
|
|
|
936
946
|
}
|
|
937
947
|
|
|
938
948
|
/**
|
|
939
|
-
* Every TLAS entry's world-space bounds, 6 floats each — what the TLAS sorts on.
|
|
949
|
+
* Every TLAS entry's world-space bounds, 6 floats each — what the TLAS sorts on; entries `[from, to)` only, if given.
|
|
940
950
|
* @param {Float64Array|Float32Array} out
|
|
941
951
|
*/
|
|
942
|
-
writeEntryWorldAABBs( out ) {
|
|
952
|
+
writeEntryWorldAABBs( out, from = 0, to = this.entryCount ) {
|
|
943
953
|
|
|
944
954
|
if ( ! this.entryRep ) {
|
|
945
955
|
|
|
946
|
-
this.writeWorldAABBs( out );
|
|
956
|
+
this.writeWorldAABBs( out, from, to );
|
|
947
957
|
return;
|
|
948
958
|
|
|
949
959
|
}
|
|
950
960
|
|
|
951
|
-
for ( let e =
|
|
961
|
+
for ( let e = from; e < to; e ++ ) this.writeEntryWorldAABB( e, out, e * 6 );
|
|
952
962
|
|
|
953
963
|
}
|
|
954
964
|
|
|
955
965
|
/**
|
|
956
|
-
* Every placement's world-space bounds, 6 floats each.
|
|
966
|
+
* Every placement's world-space bounds, 6 floats each; placements `[from, to)` only, if given.
|
|
957
967
|
* @param {Float64Array|Float32Array} out
|
|
958
968
|
*/
|
|
959
|
-
writeWorldAABBs( out ) {
|
|
969
|
+
writeWorldAABBs( out, from = 0, to = this.count ) {
|
|
960
970
|
|
|
961
971
|
const aabb = this.tplObjectAABB, src = this.sourceMesh, set = this.isSet;
|
|
962
972
|
|
|
963
|
-
for ( let i =
|
|
973
|
+
for ( let i = from; i < to; i ++ ) {
|
|
964
974
|
|
|
965
975
|
const o = i * 6;
|
|
966
976
|
|
|
@@ -1204,15 +1214,30 @@ const _recordInverse = new Float64Array( 16 );
|
|
|
1204
1214
|
|
|
1205
1215
|
/**
|
|
1206
1216
|
* Orders `sorted[ lo, hi )` so each run of CLUSTER_SIZE holds neighbours: halved at the centre median of the widest
|
|
1207
|
-
* axis, the lower part a whole number of runs, which keeps the run count ⌈n / CLUSTER_SIZE⌉.
|
|
1217
|
+
* axis, the lower part a whole number of runs, which keeps the run count ⌈n / CLUSTER_SIZE⌉. Yields how many entries
|
|
1218
|
+
* are in their run, about every million entries of work.
|
|
1208
1219
|
*/
|
|
1209
|
-
function splitIntoRuns( sorted, lo, hi, world ) {
|
|
1220
|
+
function * splitIntoRuns( sorted, lo, hi, world ) {
|
|
1210
1221
|
|
|
1211
1222
|
const stack = [ lo, hi ];
|
|
1223
|
+
let placed = 0, work = 0;
|
|
1212
1224
|
while ( stack.length ) {
|
|
1213
1225
|
|
|
1214
1226
|
const end = stack.pop(), start = stack.pop();
|
|
1215
|
-
if ( end - start <= CLUSTER_SIZE )
|
|
1227
|
+
if ( end - start <= CLUSTER_SIZE ) {
|
|
1228
|
+
|
|
1229
|
+
placed += end - start;
|
|
1230
|
+
continue;
|
|
1231
|
+
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
work += end - start;
|
|
1235
|
+
if ( work >= 1 << 20 ) {
|
|
1236
|
+
|
|
1237
|
+
work = 0;
|
|
1238
|
+
yield placed;
|
|
1239
|
+
|
|
1240
|
+
}
|
|
1216
1241
|
|
|
1217
1242
|
let axis = 0, widest = - 1;
|
|
1218
1243
|
for ( let a = 0; a < 3; a ++ ) {
|
|
@@ -467,9 +467,9 @@ function withIndex( P, widths, index, basis ) {
|
|
|
467
467
|
|
|
468
468
|
/**
|
|
469
469
|
* A USD BasisCurves prim's curves (one point array, a count per curve) as one strip mesh. Modes and types are USD's;
|
|
470
|
-
* the first normal of a curve orients its ribbon.
|
|
470
|
+
* the first normal of a curve orients its ribbon. `pause()` is awaited every 1024 curves, and again as they are joined.
|
|
471
471
|
*/
|
|
472
|
-
export function tessellateCurves( spec ) {
|
|
472
|
+
export async function tessellateCurves( spec, pause = null ) {
|
|
473
473
|
|
|
474
474
|
const { P, counts, widths = null, widthMode = 'constant', N = null, normalMode = 'vertex', type = 'cubic', basis = 'bspline', wrap = 'nonperiodic' } = spec;
|
|
475
475
|
const pieces = [];
|
|
@@ -523,6 +523,7 @@ export function tessellateCurves( spec ) {
|
|
|
523
523
|
}
|
|
524
524
|
|
|
525
525
|
start += n;
|
|
526
|
+
if ( pause && ( c & 1023 ) === 1023 ) await pause();
|
|
526
527
|
|
|
527
528
|
}
|
|
528
529
|
|
|
@@ -530,12 +531,14 @@ export function tessellateCurves( spec ) {
|
|
|
530
531
|
const positions = new Float32Array( vertices * 3 );
|
|
531
532
|
const indices = new Uint32Array( indexCount );
|
|
532
533
|
let v = 0, i = 0;
|
|
533
|
-
for (
|
|
534
|
+
for ( let p = 0; p < pieces.length; p ++ ) {
|
|
534
535
|
|
|
536
|
+
const piece = pieces[ p ];
|
|
535
537
|
positions.set( piece.positions, v * 3 );
|
|
536
538
|
for ( let k = 0; k < piece.indices.length; k ++ ) indices[ i + k ] = piece.indices[ k ] + v;
|
|
537
539
|
v += piece.positions.length / 3;
|
|
538
540
|
i += piece.indices.length;
|
|
541
|
+
if ( pause && ( p & 1023 ) === 1023 ) await pause();
|
|
539
542
|
|
|
540
543
|
}
|
|
541
544
|
|
|
@@ -31,6 +31,7 @@ import { octahedralToEquirect } from './EqualAreaOctahedral.js';
|
|
|
31
31
|
import { tessellateCurve, tessellateCurves } from './PBRTCurves.js';
|
|
32
32
|
import * as M from './PBRTMath.js';
|
|
33
33
|
import { packUnitAttribute } from '../GeometryExtractor.js';
|
|
34
|
+
import { computeInstanceBoundingBoxes } from '../utils.js';
|
|
34
35
|
|
|
35
36
|
const LAMP_TYPES = new Set( [ 'distant', 'point', 'spot' ] );
|
|
36
37
|
|
|
@@ -590,6 +591,8 @@ export class PBRTSceneBuilder {
|
|
|
590
591
|
* @param {(filename:string)=>Promise<import('three').Texture>} [resolvers.resolveImage]
|
|
591
592
|
* @param {(filename:string)=>Promise<import('three').Texture>} [resolvers.resolveEnvironment]
|
|
592
593
|
* @param {boolean} [resolvers.convertHandedness=false]
|
|
594
|
+
* @param {(done:number, total:number)=>?Promise} [resolvers.pause] - called as shapes are built; a promise it
|
|
595
|
+
* returns is waited on, which lets the host redraw
|
|
593
596
|
*/
|
|
594
597
|
constructor( resolvers = {} ) {
|
|
595
598
|
|
|
@@ -604,6 +607,9 @@ export class PBRTSceneBuilder {
|
|
|
604
607
|
this.resolveEnvironment = resolvers.resolveEnvironment || resolvers.resolveImage || ( async () => null );
|
|
605
608
|
this.convertHandedness = resolvers.convertHandedness === true;
|
|
606
609
|
this._usage = resolvers.usage ?? null;
|
|
610
|
+
this.pause = resolvers.pause ?? null;
|
|
611
|
+
this._shapesDone = 0;
|
|
612
|
+
this._shapeTotal = 0;
|
|
607
613
|
|
|
608
614
|
this.warnings = [];
|
|
609
615
|
this.report = []; // per-mesh diagnostics for debugging imports
|
|
@@ -629,6 +635,13 @@ export class PBRTSceneBuilder {
|
|
|
629
635
|
|
|
630
636
|
}
|
|
631
637
|
|
|
638
|
+
/** @private */
|
|
639
|
+
_tick() {
|
|
640
|
+
|
|
641
|
+
return this.pause ? this.pause( this._shapesDone, this._shapeTotal ) : null;
|
|
642
|
+
|
|
643
|
+
}
|
|
644
|
+
|
|
632
645
|
/**
|
|
633
646
|
* @param {object} ir - output of PBRTParser
|
|
634
647
|
* @returns {Promise<{group:Group, camera:PerspectiveCamera|OrthographicCamera|null, environment:object|null, warnings:string[]}>}
|
|
@@ -671,6 +684,9 @@ export class PBRTSceneBuilder {
|
|
|
671
684
|
this.mergedShapes = 0;
|
|
672
685
|
this.droppedNoTemplate = 0;
|
|
673
686
|
this._animated = []; // { node, motion }
|
|
687
|
+
this._shapesDone = 0;
|
|
688
|
+
this._shapeTotal = ir.shapes.length;
|
|
689
|
+
for ( const name of ir.instances.keys() ) this._shapeTotal += ir.objects.get( name )?.length ?? 0;
|
|
674
690
|
|
|
675
691
|
// Shapes (direct + instanced)
|
|
676
692
|
for ( let i = 0; i < ir.shapes.length; i ++ ) {
|
|
@@ -682,6 +698,8 @@ export class PBRTSceneBuilder {
|
|
|
682
698
|
|
|
683
699
|
}
|
|
684
700
|
|
|
701
|
+
this._shapesDone ++;
|
|
702
|
+
await this._tick();
|
|
685
703
|
const shape = ir.shapes[ i ];
|
|
686
704
|
const [ geometry, surface ] = await Promise.all( [
|
|
687
705
|
this._batches && ! shape.shared ? this._createGeometry( shape ) : this._buildGeometry( shape ),
|
|
@@ -843,6 +861,9 @@ export class PBRTSceneBuilder {
|
|
|
843
861
|
|
|
844
862
|
}
|
|
845
863
|
|
|
864
|
+
// While the matrices are still in memory: the scene's framing reads every box.
|
|
865
|
+
computeInstanceBoundingBoxes( mesh );
|
|
866
|
+
|
|
846
867
|
group.add( mesh );
|
|
847
868
|
|
|
848
869
|
this.reportedMeshes += count;
|
|
@@ -888,6 +909,7 @@ export class PBRTSceneBuilder {
|
|
|
888
909
|
|
|
889
910
|
for ( const shape of template ) {
|
|
890
911
|
|
|
912
|
+
this._shapesDone ++;
|
|
891
913
|
if ( this._overBudget() ) {
|
|
892
914
|
|
|
893
915
|
this.skippedForBudget += list.count;
|
|
@@ -895,6 +917,7 @@ export class PBRTSceneBuilder {
|
|
|
895
917
|
|
|
896
918
|
}
|
|
897
919
|
|
|
920
|
+
await this._tick();
|
|
898
921
|
const [ built, sharedMaterial ] = await Promise.all( [
|
|
899
922
|
this._buildGeometry( shape ),
|
|
900
923
|
this._getMaterial( shape )
|
|
@@ -1576,9 +1599,9 @@ export class PBRTSceneBuilder {
|
|
|
1576
1599
|
}
|
|
1577
1600
|
|
|
1578
1601
|
/** Many curves on one point array, as USD writes them. */
|
|
1579
|
-
_curves( params ) {
|
|
1602
|
+
async _curves( params ) {
|
|
1580
1603
|
|
|
1581
|
-
const built = tessellateCurves( {
|
|
1604
|
+
const built = await tessellateCurves( {
|
|
1582
1605
|
P: param( params, 'P' ).value,
|
|
1583
1606
|
counts: param( params, 'counts' ).value,
|
|
1584
1607
|
widths: param( params, 'widths' )?.value ?? null,
|
|
@@ -1591,7 +1614,7 @@ export class PBRTSceneBuilder {
|
|
|
1591
1614
|
steps: this.curveSteps,
|
|
1592
1615
|
sides: this.curveSides ?? 1,
|
|
1593
1616
|
tolerance: this.curveTolerance,
|
|
1594
|
-
} );
|
|
1617
|
+
}, () => this._tick() );
|
|
1595
1618
|
|
|
1596
1619
|
if ( ! built ) {
|
|
1597
1620
|
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import { TextureCreator } from './TextureCreator.js';
|
|
13
13
|
import { GeometryExtractor, geometryBytesOf, usesTextureCoordinates } from './GeometryExtractor.js';
|
|
14
14
|
import { EmissiveTriangleBuilder } from './EmissiveTriangleBuilder.js';
|
|
15
|
-
import { updateLoading } from '../Processor/utils.js';
|
|
15
|
+
import { timeSlicer, updateLoading } from '../Processor/utils.js';
|
|
16
16
|
import { BuildTimer } from './BuildTimer.js';
|
|
17
17
|
import { createLogger, fmt, workerLogLevel } from '../utils/Logger.js';
|
|
18
18
|
import { Box3, Matrix4, SRGBColorSpace, Sphere, Vector3 } from 'three';
|
|
@@ -38,6 +38,8 @@ import { createWorker, hardwareThreads } from '../Platform.js';
|
|
|
38
38
|
|
|
39
39
|
// Under this the TLAS build is a few ms; the worker round trip would cost more than it saves.
|
|
40
40
|
const TLAS_WORKER_MIN_ENTRIES = 50_000;
|
|
41
|
+
// Per-copy passes run in slices this long, with a turn for the page between them when one is due.
|
|
42
|
+
const SLICE_RECORDS = 1 << 16;
|
|
41
43
|
|
|
42
44
|
// Meshes past this build across every core, one at a time.
|
|
43
45
|
const LARGE_MESH_TRIANGLES = 200000;
|
|
@@ -934,9 +936,11 @@ export class SceneProcessor {
|
|
|
934
936
|
*/
|
|
935
937
|
async _buildBVH() {
|
|
936
938
|
|
|
939
|
+
// A streamed build has reported its BLASes up to 35 % already.
|
|
940
|
+
const startProgress = this._streamed ? 35 : 25;
|
|
937
941
|
updateLoading( {
|
|
938
942
|
status: "Building BVH...",
|
|
939
|
-
progress:
|
|
943
|
+
progress: startProgress
|
|
940
944
|
} );
|
|
941
945
|
|
|
942
946
|
if ( this.triangleCount === 0 ) {
|
|
@@ -948,6 +952,7 @@ export class SceneProcessor {
|
|
|
948
952
|
this._log( 'Building two-level BVH (TLAS/BLAS)' );
|
|
949
953
|
this.memory.mark( 'blas' );
|
|
950
954
|
const startTime = performance.now();
|
|
955
|
+
this._slice = timeSlicer();
|
|
951
956
|
|
|
952
957
|
try {
|
|
953
958
|
|
|
@@ -996,6 +1001,7 @@ export class SceneProcessor {
|
|
|
996
1001
|
|
|
997
1002
|
for ( let m = 0; m < meshCount; m ++ ) {
|
|
998
1003
|
|
|
1004
|
+
if ( ( m & 0xffff ) === 0xffff ) await this._turn( 'Matching copies to their shapes', m, meshCount, startProgress );
|
|
999
1005
|
const range = ranges[ sourceOf( m ) ];
|
|
1000
1006
|
if ( ! range || range.count === 0 ) continue;
|
|
1001
1007
|
|
|
@@ -1115,6 +1121,7 @@ export class SceneProcessor {
|
|
|
1115
1121
|
|
|
1116
1122
|
for ( let m = 0; m < meshCount; m ++ ) {
|
|
1117
1123
|
|
|
1124
|
+
if ( ( m & 0xffff ) === 0xffff ) await this._turn( 'Placing copies', m, meshCount, 70 );
|
|
1118
1125
|
const owner = aliasOf[ m ];
|
|
1119
1126
|
if ( owner < 0 ) continue;
|
|
1120
1127
|
this.instanceTable.setAlias(
|
|
@@ -1149,8 +1156,17 @@ export class SceneProcessor {
|
|
|
1149
1156
|
|
|
1150
1157
|
// Each entry's world box picks its cluster and sizes its byte box; dropped once the leaves are written.
|
|
1151
1158
|
table.clusterWorld = new Float32Array( table.entryCount * 6 );
|
|
1152
|
-
|
|
1153
|
-
|
|
1159
|
+
for ( let e = 0; e < table.entryCount; e += SLICE_RECORDS ) {
|
|
1160
|
+
|
|
1161
|
+
table.writeEntryWorldAABBs( table.clusterWorld, e, Math.min( table.entryCount, e + SLICE_RECORDS ) );
|
|
1162
|
+
await this._turn( 'Measuring copies', e, table.entryCount, 72 );
|
|
1163
|
+
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
const steps = table.clusterSteps( table.clusterWorld );
|
|
1167
|
+
let step;
|
|
1168
|
+
while ( ! ( step = steps.next() ).done ) await this._turn( 'Grouping copies', step.value, table.entryCount, 72 );
|
|
1169
|
+
table.clusterBounds = step.value;
|
|
1154
1170
|
|
|
1155
1171
|
}
|
|
1156
1172
|
|
|
@@ -1218,6 +1234,7 @@ export class SceneProcessor {
|
|
|
1218
1234
|
|
|
1219
1235
|
for ( let t = 0; t < table.templateCount; t ++ ) {
|
|
1220
1236
|
|
|
1237
|
+
await this._turn( 'Assembling the scene', t, table.templateCount, 74 );
|
|
1221
1238
|
// Only owning templates hold one; spilled, they are read back one at a time.
|
|
1222
1239
|
const blas = spill ? await this._readSpilledBLAS( spill, t ) : table.blasData.get( t );
|
|
1223
1240
|
if ( ! blas ) continue;
|
|
@@ -1283,10 +1300,23 @@ export class SceneProcessor {
|
|
|
1283
1300
|
} );
|
|
1284
1301
|
throw error;
|
|
1285
1302
|
|
|
1303
|
+
} finally {
|
|
1304
|
+
|
|
1305
|
+
this._slice = null;
|
|
1306
|
+
|
|
1286
1307
|
}
|
|
1287
1308
|
|
|
1288
1309
|
}
|
|
1289
1310
|
|
|
1311
|
+
/** During a build, a turn for the page once a slice of it has passed, with how far `what` has got. @private */
|
|
1312
|
+
_turn( what, done, total, progress ) {
|
|
1313
|
+
|
|
1314
|
+
const turn = this._slice?.();
|
|
1315
|
+
if ( turn ) updateLoading( { status: `${what}: ${done.toLocaleString()} of ${total.toLocaleString()}`, progress } );
|
|
1316
|
+
return turn;
|
|
1317
|
+
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1290
1320
|
/** Worker config shared by all BLAS builds. @private */
|
|
1291
1321
|
_blasWorkerOpts() {
|
|
1292
1322
|
|
|
@@ -3889,6 +3919,7 @@ export class SceneProcessor {
|
|
|
3889
3919
|
const used = Math.ceil( count * RECORD_VEC4 / 4 );
|
|
3890
3920
|
this.bvh.setRecords( at, used === nodes ? stage : stage.subarray( 0, used * 16 ) );
|
|
3891
3921
|
await flushBVH?.( at + used );
|
|
3922
|
+
await this._turn( 'Writing copy records', r, n, 74 );
|
|
3892
3923
|
|
|
3893
3924
|
}
|
|
3894
3925
|
|
|
@@ -3907,8 +3938,15 @@ export class SceneProcessor {
|
|
|
3907
3938
|
// The worker needs a buffer it can own, and world bounds are derived anyway.
|
|
3908
3939
|
const aabbs = clustered ? table.clusterBounds : new Float64Array( n * 6 );
|
|
3909
3940
|
if ( ! clustered ) table.writeEntryWorldAABBs( aabbs );
|
|
3941
|
+
updateLoading( { status: 'Building the scene tree...', progress: 73 } );
|
|
3910
3942
|
const { tlasData, nodeCount } = await this._runTLASWorker( aabbs, n );
|
|
3911
|
-
|
|
3943
|
+
for ( let node = 0; node < nodeCount; node += SLICE_RECORDS ) {
|
|
3944
|
+
|
|
3945
|
+
TLASBuilder.fillLeaves( tlasData, nodeCount, table, node, Math.min( nodeCount, node + SLICE_RECORDS ) );
|
|
3946
|
+
await this._turn( 'Writing the scene tree', node, nodeCount, 73 );
|
|
3947
|
+
|
|
3948
|
+
}
|
|
3949
|
+
|
|
3912
3950
|
return tlasData;
|
|
3913
3951
|
|
|
3914
3952
|
} catch ( error ) {
|
|
@@ -227,21 +227,22 @@ export class TLASBuilder {
|
|
|
227
227
|
|
|
228
228
|
/**
|
|
229
229
|
* Fill in each leaf's BLAS pointer, visibility and world-to-object matrix, and record where
|
|
230
|
-
* the leaf landed. One O(n) pass, so it stays on the main thread while the SAH tree does not
|
|
230
|
+
* the leaf landed. One O(n) pass, so it stays on the main thread while the SAH tree does not;
|
|
231
|
+
* nodes `[from, to)` only, if given.
|
|
231
232
|
*
|
|
232
233
|
* A node is 16 floats and the BLAS pointer needs 4, so the affine inverse fits in the
|
|
233
234
|
* remaining 12: the ray moves into object space with no second binding, which matters
|
|
234
235
|
* because Shade is already at the 10 storage buffers Metal allows. The inverse is derived
|
|
235
236
|
* here rather than stored: a column of it costs 64 bytes a placement.
|
|
236
237
|
*/
|
|
237
|
-
static fillLeaves( data, nodeCount, table ) {
|
|
238
|
+
static fillLeaves( data, nodeCount, table, from = 0, to = nodeCount ) {
|
|
238
239
|
|
|
239
240
|
const idx = bvhIndexView( data );
|
|
240
241
|
|
|
241
242
|
if ( table.clusterCount > 0 ) {
|
|
242
243
|
|
|
243
244
|
// Built over clusters: each leaf holds one, its copies' transforms in records (table.writeRecord).
|
|
244
|
-
for ( let node =
|
|
245
|
+
for ( let node = from; node < to; node ++ ) {
|
|
245
246
|
|
|
246
247
|
const o = node * FLOATS_PER_NODE;
|
|
247
248
|
if ( idx[ o + 3 ] === BVH_LEAF_MARKERS.BLAS_POINTER_LEAF ) table.writeClusterLeaf( data, idx, o, idx[ o + 1 ], table.clusterWorld, node );
|
|
@@ -252,7 +253,7 @@ export class TLASBuilder {
|
|
|
252
253
|
|
|
253
254
|
}
|
|
254
255
|
|
|
255
|
-
for ( let node =
|
|
256
|
+
for ( let node = from; node < to; node ++ ) {
|
|
256
257
|
|
|
257
258
|
const o = node * FLOATS_PER_NODE;
|
|
258
259
|
if ( idx[ o + 3 ] !== BVH_LEAF_MARKERS.BLAS_POINTER_LEAF ) continue;
|
|
@@ -380,9 +380,12 @@ function thinCurves( curve, rate, salt ) {
|
|
|
380
380
|
|
|
381
381
|
export class USDSceneReader {
|
|
382
382
|
|
|
383
|
-
|
|
383
|
+
/** `pause( prims, counting )` is awaited every 256 prims, `counting` during the counting pass. */
|
|
384
|
+
constructor( stage, { maxTriangles = Infinity, maxPlacements = Infinity, curveSteps = 2, resolveImage = async () => null, warn = () => {}, pause = null } = {} ) {
|
|
384
385
|
|
|
385
386
|
this.stage = stage;
|
|
387
|
+
this.pause = pause;
|
|
388
|
+
this._visited = 0;
|
|
386
389
|
this.maxTriangles = maxTriangles;
|
|
387
390
|
this.maxPlacements = maxPlacements;
|
|
388
391
|
this.curveSteps = curveSteps;
|
|
@@ -430,6 +433,7 @@ export class USDSceneReader {
|
|
|
430
433
|
|
|
431
434
|
async walk() {
|
|
432
435
|
|
|
436
|
+
this._visited = 0;
|
|
433
437
|
const root = this.stage.pseudoRoot;
|
|
434
438
|
const top = this.stage.upAxis === 'Z' ? new Matrix4().makeRotationX( - Math.PI / 2 ) : new Matrix4();
|
|
435
439
|
for ( const name of root.childNames() ) {
|
|
@@ -527,6 +531,7 @@ export class USDSceneReader {
|
|
|
527
531
|
|
|
528
532
|
async visit( prim, parent, out, ctx ) {
|
|
529
533
|
|
|
534
|
+
if ( this.pause && ( ++ this._visited & 255 ) === 0 ) await this.pause( this._visited, this.dry );
|
|
530
535
|
if ( ! prim.isDefined || prim.meta( 'active' ) === false ) return;
|
|
531
536
|
if ( prim.value( 'visibility' ) === 'invisible' ) return;
|
|
532
537
|
const purpose = prim.value( 'purpose' );
|
|
@@ -96,7 +96,7 @@ export async function loadUSDScene( args ) {
|
|
|
96
96
|
const warn = message => warnings.push( message );
|
|
97
97
|
const builder = new PBRTSceneBuilder( {
|
|
98
98
|
maxTriangles: args.maxTriangles, maxPlacements: args.maxPlacements, mergeShapesAbove: args.mergeShapesAbove,
|
|
99
|
-
curveSteps: args.curveSteps, curveSides: args.curveSides, curveTolerance: args.curveTolerance,
|
|
99
|
+
curveSteps: args.curveSteps, curveSides: args.curveSides, curveTolerance: args.curveTolerance, pause: args.pauseBuild,
|
|
100
100
|
} );
|
|
101
101
|
|
|
102
102
|
const parseStart = performance.now();
|
|
@@ -104,7 +104,7 @@ export async function loadUSDScene( args ) {
|
|
|
104
104
|
await stage.open( args.entryPath );
|
|
105
105
|
const reader = new USDSceneReader( stage, {
|
|
106
106
|
maxTriangles: builder.maxTriangles, maxPlacements: builder.maxPlacements, curveSteps: builder.curveSteps,
|
|
107
|
-
resolveImage: args.resolveImage, warn,
|
|
107
|
+
resolveImage: args.resolveImage, warn, pause: args.pauseRead,
|
|
108
108
|
} );
|
|
109
109
|
const read = await reader.read();
|
|
110
110
|
if ( stage.omittedArcs > 0 ) warn( `${stage.omittedArcs} reference(s) into the parts left out of this load skipped` );
|
package/src/Processor/utils.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SphereGeometry, Mesh, MeshPhysicalMaterial, Group, Object3D, ShaderMaterial, Vector2, Matrix4, GLSL3 } from 'three';
|
|
1
|
+
import { SphereGeometry, Mesh, MeshPhysicalMaterial, Group, Object3D, ShaderMaterial, Vector2, Matrix4, GLSL3, Box3 } from 'three';
|
|
2
2
|
import { EngineEvents } from '../EngineEvents.js';
|
|
3
3
|
|
|
4
4
|
let _statusCallback = null;
|
|
@@ -27,6 +27,85 @@ export const updateStats = ( statsUpdate ) => {
|
|
|
27
27
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
// A message: unlike a timer it is not throttled in a background tab, and unlike scheduler.yield it queues behind the
|
|
31
|
+
// page's own timers and posted renders instead of running ahead of them.
|
|
32
|
+
function nextTask() {
|
|
33
|
+
|
|
34
|
+
if ( typeof MessageChannel === 'undefined' ) return new Promise( resolve => setTimeout( resolve, 0 ) );
|
|
35
|
+
return new Promise( resolve => {
|
|
36
|
+
|
|
37
|
+
const { port1, port2 } = new MessageChannel();
|
|
38
|
+
port1.onmessage = () => {
|
|
39
|
+
|
|
40
|
+
port1.close();
|
|
41
|
+
resolve();
|
|
42
|
+
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
port2.postMessage( null );
|
|
46
|
+
|
|
47
|
+
} );
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* For a long loop on the main thread: the returned function gives the page one task turn, to redraw, once `ms` have
|
|
53
|
+
* passed since the last, and returns null in between.
|
|
54
|
+
*/
|
|
55
|
+
export function timeSlicer( ms = 50 ) {
|
|
56
|
+
|
|
57
|
+
let since = performance.now();
|
|
58
|
+
const resume = () => {
|
|
59
|
+
|
|
60
|
+
since = performance.now();
|
|
61
|
+
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return () => ( performance.now() - since < ms ? null : nextTask().then( resume ) );
|
|
65
|
+
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** InstancedMesh.computeBoundingBox, bit for bit, for each instanced mesh under `object` without a box yet. */
|
|
69
|
+
export function computeInstanceBoundingBoxes( object ) {
|
|
70
|
+
|
|
71
|
+
object.traverse( mesh => {
|
|
72
|
+
|
|
73
|
+
if ( ! mesh.isInstancedMesh || mesh.boundingBox !== null ) return;
|
|
74
|
+
const geometry = mesh.geometry;
|
|
75
|
+
if ( geometry.boundingBox === null ) geometry.computeBoundingBox();
|
|
76
|
+
const box = mesh.boundingBox = new Box3();
|
|
77
|
+
const local = geometry.boundingBox;
|
|
78
|
+
if ( local.isEmpty() ) return;
|
|
79
|
+
|
|
80
|
+
const xs = [ local.min.x, local.max.x ], ys = [ local.min.y, local.max.y ], zs = [ local.min.z, local.max.z ];
|
|
81
|
+
const m = mesh.instanceMatrix.array;
|
|
82
|
+
let x0 = Infinity, y0 = Infinity, z0 = Infinity, x1 = - Infinity, y1 = - Infinity, z1 = - Infinity;
|
|
83
|
+
for ( let i = 0, o = 0; i < mesh.count; i ++, o += 16 ) {
|
|
84
|
+
|
|
85
|
+
const e0 = m[ o ], e1 = m[ o + 1 ], e2 = m[ o + 2 ], e3 = m[ o + 3 ], e4 = m[ o + 4 ], e5 = m[ o + 5 ], e6 = m[ o + 6 ], e7 = m[ o + 7 ];
|
|
86
|
+
const e8 = m[ o + 8 ], e9 = m[ o + 9 ], e10 = m[ o + 10 ], e11 = m[ o + 11 ], e12 = m[ o + 12 ], e13 = m[ o + 13 ], e14 = m[ o + 14 ], e15 = m[ o + 15 ];
|
|
87
|
+
for ( let c = 0; c < 8; c ++ ) {
|
|
88
|
+
|
|
89
|
+
const x = xs[ c >> 2 ], y = ys[ ( c >> 1 ) & 1 ], z = zs[ c & 1 ];
|
|
90
|
+
// Vector3.applyMatrix4's operations, in its order.
|
|
91
|
+
const w = 1 / ( e3 * x + e7 * y + e11 * z + e15 );
|
|
92
|
+
const px = ( e0 * x + e4 * y + e8 * z + e12 ) * w;
|
|
93
|
+
const py = ( e1 * x + e5 * y + e9 * z + e13 ) * w;
|
|
94
|
+
const pz = ( e2 * x + e6 * y + e10 * z + e14 ) * w;
|
|
95
|
+
x0 = Math.min( x0, px ); y0 = Math.min( y0, py ); z0 = Math.min( z0, pz );
|
|
96
|
+
x1 = Math.max( x1, px ); y1 = Math.max( y1, py ); z1 = Math.max( z1, pz );
|
|
97
|
+
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
box.min.set( x0, y0, z0 );
|
|
103
|
+
box.max.set( x1, y1, z1 );
|
|
104
|
+
|
|
105
|
+
} );
|
|
106
|
+
|
|
107
|
+
}
|
|
108
|
+
|
|
30
109
|
// Raw frameCount is the user-facing sample count (one full-frame pass per frame).
|
|
31
110
|
export function getDisplaySamples( pathTracerStage ) {
|
|
32
111
|
|