rayzee 7.25.0 → 7.26.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 +7 -4
- package/dist/rayzee.es.js +1929 -1779
- package/dist/rayzee.umd.js +58 -58
- package/package.json +1 -1
- package/src/PathTracerApp.js +236 -29
- package/src/Processor/AssetLoader.js +113 -37
- package/src/Processor/SceneMetadata.js +123 -0
- package/src/Processor/utils.js +11 -0
- package/src/index.js +3 -0
- package/src/managers/EnvironmentManager.js +34 -2
package/package.json
CHANGED
package/src/PathTracerApp.js
CHANGED
|
@@ -57,6 +57,8 @@ const _appsByCanvas = new WeakMap();
|
|
|
57
57
|
* - `app.settings` — {@link RenderSettings} (all render parameters)
|
|
58
58
|
* - `app.stages` — Named pipeline stages for advanced control
|
|
59
59
|
* - `app.sceneMeshes` — meshes backing the BVH, in buffer order (see {@link refitBVH})
|
|
60
|
+
* - `app.sceneModel` — root of the rendered model (a copy, for {@link loadObject3D})
|
|
61
|
+
* - `app.getSceneObject(id)` — the rendered root for an appended object's id
|
|
60
62
|
*
|
|
61
63
|
* Extends EventDispatcher for event-driven communication with stores/UI.
|
|
62
64
|
*/
|
|
@@ -109,6 +111,9 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
109
111
|
this.canvas = canvas;
|
|
110
112
|
this._autoResize = options.autoResize !== false;
|
|
111
113
|
this._container = options.container || null;
|
|
114
|
+
// Apply the environment authored into a model file's metadata on load. See _beginSceneMetadataEnvironment().
|
|
115
|
+
this._applySceneMetadataEnabled = options.applySceneMetadata !== false;
|
|
116
|
+
this._applyingSceneMetadata = false;
|
|
112
117
|
|
|
113
118
|
// ── Settings (single source of truth for all render parameters) ──
|
|
114
119
|
this.settings = new RenderSettings( DEFAULT_STATE );
|
|
@@ -623,8 +628,6 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
623
628
|
this.interactionManager?.deselect();
|
|
624
629
|
this.transformManager?.detach?.();
|
|
625
630
|
|
|
626
|
-
// Release the loaded model. If loaded via loadObject3D(), the caller owns it —
|
|
627
|
-
// we only detach it from the scene. Otherwise dispose geometries/materials/textures.
|
|
628
631
|
this.assetLoader?.releaseTargetModel();
|
|
629
632
|
|
|
630
633
|
// Clear lights in the WebGPU light scene
|
|
@@ -662,7 +665,13 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
662
665
|
/**
|
|
663
666
|
* Loads a Three.js Object3D directly into the path tracer scene.
|
|
664
667
|
* Builds BVH from the object's meshes and uploads scene data.
|
|
665
|
-
*
|
|
668
|
+
*
|
|
669
|
+
* Renders a copy: `object3d` is never reparented, rewritten or disposed, so passing a
|
|
670
|
+
* subtree of a scene the host still renders is safe. Geometry/material/texture are shared
|
|
671
|
+
* by reference, and any ancestor transform is baked in. Later edits to `object3d` do not
|
|
672
|
+
* reach the render — mutate {@link sceneModel}, then {@link refitBVH}/{@link refitBLASes}.
|
|
673
|
+
*
|
|
674
|
+
* @param {import('three').Object3D} object3d - The Object3D to render; left untouched.
|
|
666
675
|
* @param {string} [name='object3d'] - Display name for the object
|
|
667
676
|
*/
|
|
668
677
|
async loadObject3D( object3d, name = 'object3d' ) {
|
|
@@ -674,17 +683,26 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
674
683
|
|
|
675
684
|
}
|
|
676
685
|
|
|
686
|
+
/**
|
|
687
|
+
* A load is already running. Typed so hosts can tell "you clicked too fast" apart from
|
|
688
|
+
* a genuine load failure and say so, instead of dropping the request on the floor.
|
|
689
|
+
* @private
|
|
690
|
+
*/
|
|
691
|
+
_busyError( where ) {
|
|
692
|
+
|
|
693
|
+
const error = new Error( `${where}: another load is already in progress` );
|
|
694
|
+
error.code = 'LOAD_IN_PROGRESS';
|
|
695
|
+
return error;
|
|
696
|
+
|
|
697
|
+
}
|
|
698
|
+
|
|
677
699
|
/**
|
|
678
700
|
* Loads an environment map and rebuilds CDF.
|
|
679
701
|
* @param {string} url - Environment URL
|
|
680
702
|
*/
|
|
681
703
|
async loadEnvironment( url ) {
|
|
682
704
|
|
|
683
|
-
if ( this._loadingInProgress )
|
|
684
|
-
|
|
685
|
-
throw new Error( 'PathTracerApp.loadEnvironment: another load is already in progress' );
|
|
686
|
-
|
|
687
|
-
}
|
|
705
|
+
if ( this._loadingInProgress ) throw this._busyError( 'PathTracerApp.loadEnvironment' );
|
|
688
706
|
|
|
689
707
|
this._loadingInProgress = true;
|
|
690
708
|
|
|
@@ -711,6 +729,63 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
711
729
|
|
|
712
730
|
}
|
|
713
731
|
|
|
732
|
+
/**
|
|
733
|
+
* Loads a user-supplied File (drag-drop, file picker) — model, archive, or environment
|
|
734
|
+
* map, dispatched by extension.
|
|
735
|
+
*
|
|
736
|
+
* Prefer this over driving `assetLoader.loadAssetFromFile()` directly. That bypasses the
|
|
737
|
+
* in-progress guard, and AssetLoader disposes the outgoing model before it knows whether
|
|
738
|
+
* the engine will rebuild: a file dropped mid-load would tear down the live scene, add the
|
|
739
|
+
* new one to the graph, and then be discarded by the `load` handler — leaving the path
|
|
740
|
+
* tracer rendering buffers whose geometry has been freed. Here a concurrent call throws
|
|
741
|
+
* LOAD_IN_PROGRESS before anything is touched.
|
|
742
|
+
*
|
|
743
|
+
* @param {File} file
|
|
744
|
+
* @returns {Promise<void>}
|
|
745
|
+
*/
|
|
746
|
+
async loadFile( file ) {
|
|
747
|
+
|
|
748
|
+
const format = this.assetLoader?.getFileFormat( file?.name || '' );
|
|
749
|
+
if ( ! format ) throw new Error( `Unsupported file format: ${file?.name}` );
|
|
750
|
+
|
|
751
|
+
if ( format.type !== 'environment' && format.type !== 'image' ) {
|
|
752
|
+
|
|
753
|
+
await this._loadWithSceneRebuild(
|
|
754
|
+
() => this.assetLoader.loadAssetFromFile( file ),
|
|
755
|
+
{ type: 'ModelLoaded', filename: file.name }
|
|
756
|
+
);
|
|
757
|
+
return;
|
|
758
|
+
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Environment drop: install it, no scene rebuild.
|
|
762
|
+
if ( this._loadingInProgress ) throw this._busyError( 'PathTracerApp.loadFile' );
|
|
763
|
+
|
|
764
|
+
this._loadingInProgress = true;
|
|
765
|
+
|
|
766
|
+
try {
|
|
767
|
+
|
|
768
|
+
await this.assetLoader.loadAssetFromFile( file );
|
|
769
|
+
|
|
770
|
+
const texture = this.meshScene.environment;
|
|
771
|
+
if ( texture && this.stages.pathTracer ) {
|
|
772
|
+
|
|
773
|
+
await this.stages.pathTracer.environment.applyHDRI( texture );
|
|
774
|
+
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
this.pipeline?.eventBus.emit( 'autoexposure:resetHistory' );
|
|
778
|
+
this.reset();
|
|
779
|
+
this.dispatchEvent( { type: 'EnvironmentLoaded', filename: file.name } );
|
|
780
|
+
|
|
781
|
+
} finally {
|
|
782
|
+
|
|
783
|
+
this._loadingInProgress = false;
|
|
784
|
+
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
}
|
|
788
|
+
|
|
714
789
|
/**
|
|
715
790
|
* Loads example models by index.
|
|
716
791
|
* @param {number} index
|
|
@@ -784,11 +859,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
784
859
|
/** Shared pipeline: load asset → sync controls → build BVH → reset → dispatch events */
|
|
785
860
|
async _loadWithSceneRebuild( loadFn, eventPayload ) {
|
|
786
861
|
|
|
787
|
-
if ( this._loadingInProgress )
|
|
788
|
-
|
|
789
|
-
throw new Error( 'PathTracerApp: another load is already in progress' );
|
|
790
|
-
|
|
791
|
-
}
|
|
862
|
+
if ( this._loadingInProgress ) throw this._busyError( 'PathTracerApp' );
|
|
792
863
|
|
|
793
864
|
this._loadingInProgress = true;
|
|
794
865
|
|
|
@@ -803,7 +874,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
803
874
|
// (The old primary was already released by releaseTargetModel() in loadFn.)
|
|
804
875
|
this._clearAppendedModels();
|
|
805
876
|
this._syncControlsAfterLoad();
|
|
806
|
-
await this.loadSceneData();
|
|
877
|
+
await this.loadSceneData( { pendingEnvironment: this._beginSceneMetadataEnvironment() } );
|
|
807
878
|
this.pipeline?.eventBus.emit( 'autoexposure:resetHistory' );
|
|
808
879
|
this.reset();
|
|
809
880
|
this.cameraManager.currentCameraIndex = 0;
|
|
@@ -818,11 +889,99 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
818
889
|
|
|
819
890
|
}
|
|
820
891
|
|
|
892
|
+
/**
|
|
893
|
+
* Scene-level authoring metadata carried by the current model file (glTF `extras`),
|
|
894
|
+
* or null when the file has none. See {@link module:Processor/SceneMetadata}.
|
|
895
|
+
* @type {{ environment?: { sourceFile: string, rotation?: number, intensity?: number } }|null}
|
|
896
|
+
*/
|
|
897
|
+
get sceneMetadata() {
|
|
898
|
+
|
|
899
|
+
return this.assetLoader?.sceneMetadata ?? null;
|
|
900
|
+
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* Starts fetching the environment authored into the just-loaded model's metadata and
|
|
905
|
+
* returns a promise for the texture — deliberately NOT awaited here. The fetch is the
|
|
906
|
+
* longest single step of such a load (measured 1448 ms for a 1k HDRI against 325 ms to
|
|
907
|
+
* build and upload a 112k-triangle scene), so the promise is handed to loadSceneData(),
|
|
908
|
+
* which installs it and builds its CDF while the BVH builds.
|
|
909
|
+
*
|
|
910
|
+
* Called from both replace-load seams: the URL/catalog path (_loadWithSceneRebuild) and
|
|
911
|
+
* the drag-drop / File-open path, which drives the AssetLoader directly and rebuilds via
|
|
912
|
+
* the 'load' event (_onAssetLoaded). Appending a model never applies it — an appended
|
|
913
|
+
* model must not hijack the scene's environment.
|
|
914
|
+
*
|
|
915
|
+
* Resolves to null and never rejects: a failed fetch leaves the model on the current
|
|
916
|
+
* environment. Returns null outright when there is nothing to apply.
|
|
917
|
+
* Opt out per app with `new PathTracerApp( canvas, { applySceneMetadata: false } )`.
|
|
918
|
+
*
|
|
919
|
+
* @returns {Promise<import('three').Texture|null>|null}
|
|
920
|
+
*/
|
|
921
|
+
_beginSceneMetadataEnvironment() {
|
|
922
|
+
|
|
923
|
+
const env = this.sceneMetadata?.environment;
|
|
924
|
+
if ( ! this._applySceneMetadataEnabled || ! env?.sourceFile || ! this.stages.pathTracer ) return null;
|
|
925
|
+
|
|
926
|
+
// Claim 'hdri' mode up front: loadEnvironment() fires beforeEnvironmentLoad, and a
|
|
927
|
+
// host that answers it with setMode( 'hdri' ) would otherwise restore the HDRI
|
|
928
|
+
// stashed by the current sky mode on top of the download now in flight.
|
|
929
|
+
this.stages.pathTracer.environment.beginHDRI();
|
|
930
|
+
this._applyingSceneMetadata = true;
|
|
931
|
+
|
|
932
|
+
return this.assetLoader.loadEnvironment( env.sourceFile )
|
|
933
|
+
.catch( error => {
|
|
934
|
+
|
|
935
|
+
log.warn( `scene metadata: environment "${env.sourceFile}" failed to load`, error );
|
|
936
|
+
return null;
|
|
937
|
+
|
|
938
|
+
} )
|
|
939
|
+
.finally( () => {
|
|
940
|
+
|
|
941
|
+
this._applyingSceneMetadata = false;
|
|
942
|
+
|
|
943
|
+
} );
|
|
944
|
+
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Mirrors the authored environment onto the settings once its texture is installed.
|
|
949
|
+
* The authored env is both the light and the backdrop, so the backdrop axis
|
|
950
|
+
* (showBackground / transparentBackground) is forced to the image.
|
|
951
|
+
* @private
|
|
952
|
+
*/
|
|
953
|
+
_applySceneMetadataSettings( texture ) {
|
|
954
|
+
|
|
955
|
+
const metadata = this.sceneMetadata;
|
|
956
|
+
const env = metadata?.environment;
|
|
957
|
+
if ( ! env ) return;
|
|
958
|
+
|
|
959
|
+
const updates = { enableEnvironment: true, showBackground: true, transparentBackground: false };
|
|
960
|
+
if ( env.intensity !== undefined ) {
|
|
961
|
+
|
|
962
|
+
updates.environmentIntensity = env.intensity;
|
|
963
|
+
updates.backgroundIntensity = env.intensity;
|
|
964
|
+
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
if ( env.rotation !== undefined ) updates.environmentRotation = env.rotation;
|
|
968
|
+
|
|
969
|
+
this.settings.setMany( updates, { reset: false } );
|
|
970
|
+
if ( this.scene ) this.scene.background = texture;
|
|
971
|
+
|
|
972
|
+
this.dispatchEvent( { type: 'SceneMetadataApplied', metadata, environment: { ...env } } );
|
|
973
|
+
|
|
974
|
+
}
|
|
975
|
+
|
|
821
976
|
/**
|
|
822
977
|
* Builds BVH from meshScene and uploads all scene data to the path tracer.
|
|
978
|
+
* @param {Object} [options]
|
|
979
|
+
* @param {Promise<import('three').Texture|null>} [options.pendingEnvironment] - An
|
|
980
|
+
* environment still being fetched (see _beginSceneMetadataEnvironment). Installed and
|
|
981
|
+
* CDF'd concurrently with the BVH build rather than before it.
|
|
823
982
|
* @returns {boolean}
|
|
824
983
|
*/
|
|
825
|
-
async loadSceneData() {
|
|
984
|
+
async loadSceneData( { pendingEnvironment = null } = {} ) {
|
|
826
985
|
|
|
827
986
|
// Clear selection before rebuilding — the old object leaves the scene graph.
|
|
828
987
|
// Skipped on the append path (addModel): the selected object persists, so its
|
|
@@ -841,7 +1000,23 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
841
1000
|
|
|
842
1001
|
// Environment CDF build in parallel with BVH
|
|
843
1002
|
let cdfPromise = null;
|
|
844
|
-
if (
|
|
1003
|
+
if ( pendingEnvironment ) {
|
|
1004
|
+
|
|
1005
|
+
// The authored environment is still downloading. Install + CDF it off to the side so
|
|
1006
|
+
// the fetch overlaps the BVH build below instead of gating it.
|
|
1007
|
+
timer.start( 'Environment fetch + CDF (concurrent)' );
|
|
1008
|
+
cdfPromise = pendingEnvironment
|
|
1009
|
+
.then( async texture => {
|
|
1010
|
+
|
|
1011
|
+
if ( ! texture ) return;
|
|
1012
|
+
await this.stages.pathTracer.environment.applyHDRI( texture );
|
|
1013
|
+
this._applySceneMetadataSettings( texture );
|
|
1014
|
+
|
|
1015
|
+
} )
|
|
1016
|
+
.catch( error => log.warn( 'scene metadata: environment install failed', error ) )
|
|
1017
|
+
.finally( () => timer.end( 'Environment fetch + CDF (concurrent)' ) );
|
|
1018
|
+
|
|
1019
|
+
} else if ( environmentTexture?.image?.data ) {
|
|
845
1020
|
|
|
846
1021
|
timer.start( 'Environment CDF build (worker)' );
|
|
847
1022
|
this.stages.pathTracer.scene.environment = environmentTexture;
|
|
@@ -861,7 +1036,9 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
861
1036
|
await new Promise( r => setTimeout( r, 0 ) );
|
|
862
1037
|
timer.start( 'GPU data transfer' );
|
|
863
1038
|
|
|
864
|
-
|
|
1039
|
+
// Re-read rather than reusing the snapshot above: a pendingEnvironment may have landed
|
|
1040
|
+
// during the BVH build, and the snapshot is then a disposed texture.
|
|
1041
|
+
if ( ! this._sdf.uploadToPathTracer( this.stages.pathTracer, this.lightManager, this.meshScene, this.meshScene.environment ) ) return false;
|
|
865
1042
|
|
|
866
1043
|
// Patch per-mesh visibility into the TLAS leaves we just uploaded
|
|
867
1044
|
this.stages.pathTracer._meshRefs = this.stages.pathTracer._collectMeshRefs( this.meshScene );
|
|
@@ -963,10 +1140,9 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
963
1140
|
/** Tag the primary (replace-loaded) model as a removable scene object (read by the Outliner + removeSceneObject). Idempotent. */
|
|
964
1141
|
_tagPrimarySceneObject() {
|
|
965
1142
|
|
|
966
|
-
const m = this.
|
|
1143
|
+
const m = this.sceneModel;
|
|
967
1144
|
if ( ! m ) return;
|
|
968
1145
|
m.userData.__rayzeeSceneObject = true;
|
|
969
|
-
m.userData.__rayzeeExternal = ( m === this.assetLoader._externalModel );
|
|
970
1146
|
|
|
971
1147
|
}
|
|
972
1148
|
|
|
@@ -976,12 +1152,12 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
976
1152
|
const scene = this.meshScene;
|
|
977
1153
|
if ( ! scene ) return;
|
|
978
1154
|
const floor = this.assetLoader?.floorPlane;
|
|
979
|
-
const primary = this.
|
|
1155
|
+
const primary = this.sceneModel;
|
|
980
1156
|
for ( const child of [ ...scene.children ] ) {
|
|
981
1157
|
|
|
982
1158
|
if ( child === floor || child === primary ) continue;
|
|
983
1159
|
if ( ! child.userData?.__rayzeeSceneObject ) continue;
|
|
984
|
-
this.assetLoader.removeModelRoot( child
|
|
1160
|
+
this.assetLoader.removeModelRoot( child );
|
|
985
1161
|
|
|
986
1162
|
}
|
|
987
1163
|
|
|
@@ -1020,7 +1196,6 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1020
1196
|
|
|
1021
1197
|
const { root } = await this.assetLoader.appendModel( url );
|
|
1022
1198
|
root.userData.__rayzeeSceneObject = true;
|
|
1023
|
-
root.userData.__rayzeeExternal = false;
|
|
1024
1199
|
if ( name ) root.userData.__rayzeeName = name;
|
|
1025
1200
|
await this._finishRebuildNoReframe( { type: 'ModelAdded', url, id: root.uuid } );
|
|
1026
1201
|
return root.uuid;
|
|
@@ -1036,7 +1211,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1036
1211
|
|
|
1037
1212
|
/**
|
|
1038
1213
|
* Append a caller-owned Object3D to the current scene, then rebuild (no reframe).
|
|
1039
|
-
*
|
|
1214
|
+
* Appends a copy; the object passed in is never mutated. Same rules as {@link loadObject3D}.
|
|
1040
1215
|
* @param {import('three').Object3D} object3d
|
|
1041
1216
|
* @param {Object} [opts]
|
|
1042
1217
|
* @param {string} [opts.name]
|
|
@@ -1056,7 +1231,6 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1056
1231
|
|
|
1057
1232
|
const { root } = this.assetLoader.appendObject3D( object3d, name || 'object3d' );
|
|
1058
1233
|
root.userData.__rayzeeSceneObject = true;
|
|
1059
|
-
root.userData.__rayzeeExternal = true;
|
|
1060
1234
|
if ( name ) root.userData.__rayzeeName = name;
|
|
1061
1235
|
await this._finishRebuildNoReframe( { type: 'ModelAdded', id: root.uuid } );
|
|
1062
1236
|
return root.uuid;
|
|
@@ -1083,7 +1257,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1083
1257
|
const floor = this.assetLoader?.floorPlane;
|
|
1084
1258
|
if ( floor && floor.uuid === id ) return false; // Ground is not deletable
|
|
1085
1259
|
|
|
1086
|
-
const root =
|
|
1260
|
+
const root = this.getSceneObject( id );
|
|
1087
1261
|
if ( ! root ) return false;
|
|
1088
1262
|
|
|
1089
1263
|
if ( this._loadingInProgress ) {
|
|
@@ -1098,13 +1272,13 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1098
1272
|
this.interactionManager?.deselect();
|
|
1099
1273
|
this.transformManager?.detach?.();
|
|
1100
1274
|
|
|
1101
|
-
if ( root === this.
|
|
1275
|
+
if ( root === this.sceneModel ) {
|
|
1102
1276
|
|
|
1103
1277
|
this.assetLoader.releaseTargetModel();
|
|
1104
1278
|
|
|
1105
1279
|
} else {
|
|
1106
1280
|
|
|
1107
|
-
this.assetLoader.removeModelRoot( root
|
|
1281
|
+
this.assetLoader.removeModelRoot( root );
|
|
1108
1282
|
|
|
1109
1283
|
}
|
|
1110
1284
|
|
|
@@ -1207,6 +1381,32 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1207
1381
|
|
|
1208
1382
|
}
|
|
1209
1383
|
|
|
1384
|
+
/**
|
|
1385
|
+
* Root of the model actually being rendered. For {@link loadObject3D} this is the engine's
|
|
1386
|
+
* copy, not the object you passed — mutate this one, then {@link refitBVH}/{@link refitBLASes}.
|
|
1387
|
+
*
|
|
1388
|
+
* @returns {import('three').Object3D|null} Live reference, or null when nothing is loaded.
|
|
1389
|
+
*/
|
|
1390
|
+
get sceneModel() {
|
|
1391
|
+
|
|
1392
|
+
return this.assetLoader?.targetModel ?? null;
|
|
1393
|
+
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
/**
|
|
1397
|
+
* Resolve an id from {@link addModel}/{@link addModelFromObject3D} to the root being
|
|
1398
|
+
* rendered for it — for an appended Object3D, the engine's copy.
|
|
1399
|
+
*
|
|
1400
|
+
* @param {string} id
|
|
1401
|
+
* @returns {import('three').Object3D|null} Live reference, or null if no such object.
|
|
1402
|
+
*/
|
|
1403
|
+
getSceneObject( id ) {
|
|
1404
|
+
|
|
1405
|
+
const root = this.meshScene?.children.find( c => c.uuid === id );
|
|
1406
|
+
return root?.userData?.__rayzeeSceneObject ? root : null;
|
|
1407
|
+
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1210
1410
|
/**
|
|
1211
1411
|
* Update vertex positions for animation without full BVH rebuild.
|
|
1212
1412
|
* O(N) bottom-up AABB refit instead of O(N log N) SAH rebuild.
|
|
@@ -2612,11 +2812,18 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
2612
2812
|
|
|
2613
2813
|
if ( this._loadingInProgress ) return;
|
|
2614
2814
|
|
|
2815
|
+
// The authored-environment fetch runs through this same AssetLoader, so its 'load'
|
|
2816
|
+
// event lands right back here. We install that texture ourselves — without the
|
|
2817
|
+
// guard the texture branch below would rebuild the CDF a second time and
|
|
2818
|
+
// resetLoading() would tear down the overlay mid-upload. Model events still pass:
|
|
2819
|
+
// a user can drop another file while the environment is in flight.
|
|
2820
|
+
if ( this._applyingSceneMetadata && event.texture ) return;
|
|
2821
|
+
|
|
2615
2822
|
if ( event.model ) {
|
|
2616
2823
|
|
|
2617
2824
|
// Drag-drop / file load is a replace: clear any appended models first.
|
|
2618
2825
|
this._clearAppendedModels();
|
|
2619
|
-
await this.loadSceneData();
|
|
2826
|
+
await this.loadSceneData( { pendingEnvironment: this._beginSceneMetadataEnvironment() } );
|
|
2620
2827
|
|
|
2621
2828
|
} else if ( event.texture ) {
|
|
2622
2829
|
|
|
@@ -2661,7 +2868,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
2661
2868
|
const animations = this.assetLoader?.animations || [];
|
|
2662
2869
|
if ( animations.length > 0 ) {
|
|
2663
2870
|
|
|
2664
|
-
const mixerRoot = this.
|
|
2871
|
+
const mixerRoot = this.sceneModel || this.meshScene;
|
|
2665
2872
|
this.animationManager.init( this.meshScene, mixerRoot, this._sdf.meshes, animations );
|
|
2666
2873
|
this.animationManager.onFinished = () => {
|
|
2667
2874
|
|