rayzee 8.8.2 → 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 +210 -20
- package/dist/{NeuralRetouch-CYOJ21Wn.js → NeuralRetouch-BRquqfmZ.js} +1 -1
- package/dist/{NeuralSuperRes-Dc6jRYke.js → NeuralSuperRes-CbNvQjCw.js} +5 -2
- package/dist/{ToneMapGPU-BVE852iN.js → ToneMapGPU-DpUZDMbQ.js} +2 -1
- package/dist/rayzee.es.js +10795 -5324
- package/dist/rayzee.umd.js +279 -268
- package/package.json +1 -1
- package/src/AssetConfig.js +4 -0
- package/src/EngineDefaults.js +60 -76
- package/src/EngineEvents.js +7 -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/Passes/OIDNTemporalHistory.js +25 -7
- package/src/PathTracerApp.js +313 -25
- package/src/Processor/ArchiveCache.js +120 -0
- package/src/Processor/ArchiveReader.js +179 -32
- package/src/Processor/AssetLoader.js +523 -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 +210 -55
- 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 +49 -8
- package/src/SceneState/SceneState.js +367 -0
- package/src/SceneState/portable.js +52 -0
- package/src/Stages/MotionVector.js +8 -42
- package/src/Stages/NRD.js +30 -21
- package/src/Stages/NormalDepth.js +12 -23
- package/src/Stages/PathTracer.js +114 -6
- 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/CameraRay.js +99 -14
- package/src/TSL/Common.js +11 -2
- package/src/TSL/DebugKernel.js +2 -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 +5 -5
- 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 +22 -0
- package/src/managers/CameraManager.js +412 -56
- package/src/managers/DenoisingManager.js +4 -3
- package/src/managers/EnvironmentManager.js +263 -33
- package/src/managers/GoboManager.js +19 -8
- package/src/managers/IESManager.js +3 -3
- package/src/managers/InteractionManager.js +30 -2
- 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 +11 -7
- package/src/managers/VideoRenderManager.js +38 -17
- package/src/managers/ViewCamera.js +77 -0
- package/src/managers/WalkControls.js +262 -0
- package/src/managers/helpers/OutlineHelper.js +22 -2
- package/src/managers/timeline/CameraTrack.js +235 -0
- package/src/managers/timeline/TimelineManager.js +146 -0
- package/src/version.js +3 -0
- package/src/Processor/ProceduralSky.js +0 -291
package/src/PathTracerApp.js
CHANGED
|
@@ -17,7 +17,7 @@ import { AutoExposure } from './Stages/AutoExposure.js';
|
|
|
17
17
|
import { Compositor } from './Stages/Compositor.js';
|
|
18
18
|
import { RenderPipeline } from './Pipeline/RenderPipeline.js';
|
|
19
19
|
import { CompletionTracker } from './Pipeline/CompletionTracker.js';
|
|
20
|
-
import { ENGINE_DEFAULTS as DEFAULT_STATE, PRODUCTION_RENDER_CONFIG, INTERACTIVE_RENDER_CONFIG, modePresetSettings, MAX_STORAGE_TEXTURE_SIZE, MAX_RESERVABLE_RENDER_SIZE, setReservedRenderSize, getRenderProfile } from './EngineDefaults.js';
|
|
20
|
+
import { ENGINE_DEFAULTS as DEFAULT_STATE, TRIANGLE_DATA_LAYOUT, PRODUCTION_RENDER_CONFIG, INTERACTIVE_RENDER_CONFIG, modePresetSettings, MAX_STORAGE_TEXTURE_SIZE, MAX_RESERVABLE_RENDER_SIZE, setReservedRenderSize, getRenderProfile } from './EngineDefaults.js';
|
|
21
21
|
import { updateStats, updateLoading, resetLoading, setStatusCallback, getDisplaySamples, disposeObjectFromMemory, disposeRenderer } from './Processor/utils.js';
|
|
22
22
|
import { BuildTimer } from './Processor/BuildTimer.js';
|
|
23
23
|
import { TextureReadback } from './Processor/TextureReadback.js';
|
|
@@ -25,16 +25,23 @@ import { createLogger, fmt } from './utils/Logger.js';
|
|
|
25
25
|
import { InteractionManager } from './managers/InteractionManager.js';
|
|
26
26
|
import { EngineEvents } from './EngineEvents.js';
|
|
27
27
|
import { IssueLog, ISSUE_CODES } from './EngineIssues.js';
|
|
28
|
+
import { getAssetConfig } from './AssetConfig.js';
|
|
29
|
+
import { StorageManager } from './Storage/StorageManager.js';
|
|
30
|
+
import { acquireSharedStorage } from './Storage/openStorage.js';
|
|
31
|
+
import { nameFromUrl } from './Storage/DownloadCache.js';
|
|
32
|
+
import { fileIdentity, identityKey } from './Storage/identity.js';
|
|
28
33
|
import { SETTING_SOURCE } from './RenderSettings.js';
|
|
29
34
|
import { toneMapToRGBA8 } from './Processor/ToneMapCPU.js';
|
|
30
35
|
import { ColorManagement, setActiveColorManagement } from './Color/ColorManagement.js';
|
|
31
36
|
import { getViewTransform } from './Color/ViewTransforms.js';
|
|
32
37
|
import { AssetLoader } from './Processor/AssetLoader.js';
|
|
38
|
+
import { SPILL_TRIANGLE_BUDGET, SPILL_PLACEMENT_BUDGET } from './Processor/PBRT/index.js';
|
|
33
39
|
import { SceneProcessor } from './Processor/SceneProcessor.js';
|
|
34
40
|
|
|
35
41
|
// Managers
|
|
36
42
|
import { RenderSettings } from './RenderSettings.js';
|
|
37
43
|
import { CameraManager } from './managers/CameraManager.js';
|
|
44
|
+
import { TimelineManager } from './managers/timeline/TimelineManager.js';
|
|
38
45
|
import { LightManager } from './managers/LightManager.js';
|
|
39
46
|
import { GoboManager } from './managers/GoboManager.js';
|
|
40
47
|
import { IESManager } from './managers/IESManager.js';
|
|
@@ -43,6 +50,17 @@ import { OverlayManager } from './managers/OverlayManager.js';
|
|
|
43
50
|
import { AnimationManager } from './managers/AnimationManager.js';
|
|
44
51
|
import { TransformManager } from './managers/TransformManager.js';
|
|
45
52
|
import { TransformGizmoHelper } from './managers/helpers/TransformGizmoHelper.js';
|
|
53
|
+
import { captureSceneState, applySceneState } from './SceneState/SceneState.js';
|
|
54
|
+
import { TRIANGLE_PATCH_PROPERTIES } from './managers/MaterialDataManager.js';
|
|
55
|
+
import { VERSION } from './version.js';
|
|
56
|
+
|
|
57
|
+
export const RENDER_CHECKPOINT_VERSION = 1;
|
|
58
|
+
|
|
59
|
+
// The part of an archive a load chose, for sceneSource.
|
|
60
|
+
const partOf = ( { element, pbrtEntry } = {} ) => ( {
|
|
61
|
+
...( element !== undefined ? { element } : {} ),
|
|
62
|
+
...( pbrtEntry !== undefined ? { pbrtEntry } : {} ),
|
|
63
|
+
} );
|
|
46
64
|
|
|
47
65
|
// One app per canvas — auto-dispose a prior owner if the caller double-
|
|
48
66
|
// instantiates (StrictMode, HMR, etc.) so its rAF loop can't burn CPU.
|
|
@@ -128,6 +146,11 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
128
146
|
* @param {number} [options.maxSceneBytes] - refuse a scene whose estimated host memory is
|
|
129
147
|
* above this. The default refuses where the renderer process would be killed instead of
|
|
130
148
|
* throwing; raise it deliberately, on a fresh browser. See HostMemory.js.
|
|
149
|
+
* @param {false|'auto'|StorageManager} [options.storage] - on-disk storage; defaults to
|
|
150
|
+
* `configureAssets( { storage } )`. A host-supplied manager stays the host's to dispose.
|
|
151
|
+
* @param {boolean} [options.memorySpill=false] - experimental: once a large static scene is
|
|
152
|
+
* on the GPU, move its triangle records and BLAS nodes to disk (see
|
|
153
|
+
* {@link ensureSceneResident}). Needs storage; skipped for animated scenes.
|
|
131
154
|
*
|
|
132
155
|
* The engine dispatches `EngineEvents.FRAME` after each animate() iteration so hosts can
|
|
133
156
|
* tick external instrumentation (e.g. a stats panel) without coupling the engine to it.
|
|
@@ -152,6 +175,11 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
152
175
|
this._autoResize = options.autoResize !== false;
|
|
153
176
|
// A scene budget the host may raise; read where SceneProcessor is built, well after this.
|
|
154
177
|
this._maxSceneBytes = options.maxSceneBytes;
|
|
178
|
+
this._storageOption = options.storage;
|
|
179
|
+
this._memorySpill = options.memorySpill === true;
|
|
180
|
+
/** @type {?StorageManager} on-disk storage, null when off or unavailable */
|
|
181
|
+
this.storage = null;
|
|
182
|
+
this._storageRelease = null;
|
|
155
183
|
this._container = options.container || null;
|
|
156
184
|
// Apply the environment authored into a model file's metadata on load. See _beginSceneMetadataEnvironment().
|
|
157
185
|
this._applySceneMetadataEnabled = options.applySceneMetadata !== false;
|
|
@@ -181,6 +209,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
181
209
|
...DEFAULT_STATE,
|
|
182
210
|
environmentRotation: this._profile.environmentRotation,
|
|
183
211
|
saturation: this._profile.saturation,
|
|
212
|
+
dofMode: this._profile.dofMode,
|
|
184
213
|
},
|
|
185
214
|
{ issues: this._issues }
|
|
186
215
|
);
|
|
@@ -214,6 +243,8 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
214
243
|
// ── Managers (direct public access) ──
|
|
215
244
|
/** @type {CameraManager} */
|
|
216
245
|
this.cameraManager = null;
|
|
246
|
+
/** @type {TimelineManager} Authored animation: keyframed tracks. */
|
|
247
|
+
this.timeline = null;
|
|
217
248
|
/** @type {LightManager} */
|
|
218
249
|
this.lightManager = null;
|
|
219
250
|
/** @type {GoboManager} */
|
|
@@ -320,6 +351,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
320
351
|
*/
|
|
321
352
|
async init() {
|
|
322
353
|
|
|
354
|
+
const storageReady = this._initStorage();
|
|
323
355
|
await this._initRenderer();
|
|
324
356
|
this._applyPendingReservedRenderSize();
|
|
325
357
|
this._initCameraManager();
|
|
@@ -328,9 +360,11 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
328
360
|
this._initPipeline();
|
|
329
361
|
await this._initManagers();
|
|
330
362
|
this._wireEvents();
|
|
363
|
+
await storageReady;
|
|
364
|
+
this._attachStorage();
|
|
331
365
|
|
|
332
366
|
// Seed path tracer with minimal empty scene data
|
|
333
|
-
this.stages.pathTracer.setTriangleData( new
|
|
367
|
+
this.stages.pathTracer.setTriangleData( new Uint32Array( TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE ), 0 );
|
|
334
368
|
this.stages.pathTracer.setBVHData( new Float32Array( 16 ) );
|
|
335
369
|
this.stages.pathTracer.materialData.setMaterialData( new Float32Array( 16 ) );
|
|
336
370
|
this.stages.pathTracer.setupMaterial();
|
|
@@ -342,6 +376,48 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
342
376
|
|
|
343
377
|
}
|
|
344
378
|
|
|
379
|
+
async _initStorage() {
|
|
380
|
+
|
|
381
|
+
const option = this._storageOption ?? getAssetConfig().storage;
|
|
382
|
+
if ( option === false ) return;
|
|
383
|
+
|
|
384
|
+
if ( option instanceof StorageManager ) {
|
|
385
|
+
|
|
386
|
+
this.storage = option;
|
|
387
|
+
|
|
388
|
+
} else {
|
|
389
|
+
|
|
390
|
+
const { storage, reason, release } = await acquireSharedStorage( getAssetConfig().cacheNamespace );
|
|
391
|
+
if ( this._disposed ) {
|
|
392
|
+
|
|
393
|
+
release();
|
|
394
|
+
return;
|
|
395
|
+
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if ( ! storage ) {
|
|
399
|
+
|
|
400
|
+
this._issues.warn( ISSUE_CODES.STORAGE_UNAVAILABLE, `on-disk storage is off: ${reason}`, { reason } );
|
|
401
|
+
return;
|
|
402
|
+
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
this.storage = storage;
|
|
406
|
+
this._storageRelease = release;
|
|
407
|
+
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
this._addTrackedListener( this.storage, 'change', ( e ) => this.dispatchEvent( { type: EngineEvents.STORAGE_CHANGED, area: e.area } ) );
|
|
411
|
+
this._addTrackedListener( this.storage, 'issue', ( e ) => this._issues.warn( e.code, e.message, e.detail ) );
|
|
412
|
+
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
_attachStorage() {
|
|
416
|
+
|
|
417
|
+
if ( this.assetLoader ) this.assetLoader.storage = this.storage;
|
|
418
|
+
|
|
419
|
+
}
|
|
420
|
+
|
|
345
421
|
/**
|
|
346
422
|
* Starts the animation loop.
|
|
347
423
|
*/
|
|
@@ -359,7 +435,8 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
359
435
|
|
|
360
436
|
}
|
|
361
437
|
|
|
362
|
-
|
|
438
|
+
this.timeline.update();
|
|
439
|
+
this.cameraManager.updateControls();
|
|
363
440
|
|
|
364
441
|
this._applyPendingRenderScale();
|
|
365
442
|
|
|
@@ -619,6 +696,11 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
619
696
|
this._removeTrackedListeners();
|
|
620
697
|
setStatusCallback( null );
|
|
621
698
|
|
|
699
|
+
this._storageRelease?.();
|
|
700
|
+
this._storageRelease = null;
|
|
701
|
+
this.storage = null;
|
|
702
|
+
this._sceneSource = this._sceneSourceFile = null;
|
|
703
|
+
|
|
622
704
|
this._issues.detach(); // onIssue captures `this`; see IssueLog.detach()
|
|
623
705
|
|
|
624
706
|
// Holds the renderer and the issue log, both of which outlive it otherwise.
|
|
@@ -638,6 +720,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
638
720
|
this.iesManager?.dispose();
|
|
639
721
|
this.denoisingManager?.dispose();
|
|
640
722
|
this.interactionManager?.dispose();
|
|
723
|
+
this.timeline?.dispose();
|
|
641
724
|
this.cameraManager?.dispose();
|
|
642
725
|
|
|
643
726
|
this.pipeline?.dispose();
|
|
@@ -758,6 +841,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
758
841
|
this.transformManager?.detach?.();
|
|
759
842
|
|
|
760
843
|
this.assetLoader?.releaseTargetModel();
|
|
844
|
+
this._sceneSource = this._sceneSourceFile = null;
|
|
761
845
|
|
|
762
846
|
// Clear lights in the WebGPU light scene
|
|
763
847
|
this.lightManager?.clearLights?.();
|
|
@@ -765,7 +849,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
765
849
|
// Seed path tracer with empty data (matches the init-time seed)
|
|
766
850
|
if ( this.stages.pathTracer ) {
|
|
767
851
|
|
|
768
|
-
this.stages.pathTracer.setTriangleData( new
|
|
852
|
+
this.stages.pathTracer.setTriangleData( new Uint32Array( TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE ), 0 );
|
|
769
853
|
this.stages.pathTracer.setBVHData( new Float32Array( 16 ) );
|
|
770
854
|
this.stages.pathTracer.materialData.setMaterialData( new Float32Array( 16 ) );
|
|
771
855
|
this.stages.pathTracer.setEmissiveTriangleData?.( new Float32Array( 0 ), 0, 0 );
|
|
@@ -782,12 +866,14 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
782
866
|
/**
|
|
783
867
|
* Loads a model, builds BVH, and uploads scene data.
|
|
784
868
|
* @param {string} url - Model URL
|
|
869
|
+
* @param {{cacheKey?: string}} [options] - download-cache key for a URL that expires
|
|
785
870
|
*/
|
|
786
|
-
async loadModel( url ) {
|
|
871
|
+
async loadModel( url, options = {} ) {
|
|
787
872
|
|
|
788
873
|
await this._loadWithSceneRebuild(
|
|
789
|
-
() => this.assetLoader.loadModel( url ),
|
|
790
|
-
{ type: 'ModelLoaded', url }
|
|
874
|
+
() => this.assetLoader.loadModel( url, options ),
|
|
875
|
+
{ type: 'ModelLoaded', url },
|
|
876
|
+
{ kind: 'url', url, cacheKey: options.cacheKey ?? null }
|
|
791
877
|
);
|
|
792
878
|
|
|
793
879
|
}
|
|
@@ -811,7 +897,8 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
811
897
|
|
|
812
898
|
await this._loadWithSceneRebuild(
|
|
813
899
|
() => this.assetLoader.loadObject3D( object3d, name ),
|
|
814
|
-
{ type: 'Object3DLoaded', name }
|
|
900
|
+
{ type: 'Object3DLoaded', name },
|
|
901
|
+
{ kind: 'object3d', name }
|
|
815
902
|
);
|
|
816
903
|
|
|
817
904
|
}
|
|
@@ -885,22 +972,49 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
885
972
|
* tracer rendering buffers whose geometry has been freed. Here a concurrent call throws
|
|
886
973
|
* LOAD_IN_PROGRESS before anything is touched.
|
|
887
974
|
*
|
|
888
|
-
* @param {File} file
|
|
975
|
+
* @param {File|string} file - a File, or a URL to download (through the download cache)
|
|
889
976
|
* @param {object} [options] - forwarded to the archive loader: `element` to load one
|
|
890
|
-
* subtree of a multi-part scene, `pbrtEntry` to choose among several .pbrt scenes.
|
|
977
|
+
* subtree of a multi-part scene, `pbrtEntry` to choose among several .pbrt scenes. For a
|
|
978
|
+
* URL also `filename` and `cacheKey` (see AssetLoader.loadAssetFromUrl).
|
|
891
979
|
* @returns {Promise<void>}
|
|
892
980
|
*/
|
|
893
981
|
async loadFile( file, options = {} ) {
|
|
894
982
|
|
|
983
|
+
options = this._sceneBudgets( options );
|
|
984
|
+
|
|
985
|
+
if ( typeof file === 'string' ) {
|
|
986
|
+
|
|
987
|
+
const filename = options.filename ?? nameFromUrl( file );
|
|
988
|
+
const format = this.assetLoader?.getFileFormat( filename );
|
|
989
|
+
if ( ! format ) throw new Error( `Unsupported file format: ${filename}` );
|
|
990
|
+
if ( format.type === 'environment' || format.type === 'image' ) return this.loadEnvironment( file );
|
|
991
|
+
|
|
992
|
+
await this._loadWithSceneRebuild(
|
|
993
|
+
() => this.assetLoader.loadAssetFromUrl( file, { ...options, filename } ),
|
|
994
|
+
{ type: 'ModelLoaded', filename },
|
|
995
|
+
{ kind: 'url', url: file, filename, cacheKey: options.cacheKey ?? null, ...partOf( options ) }
|
|
996
|
+
);
|
|
997
|
+
return;
|
|
998
|
+
|
|
999
|
+
}
|
|
1000
|
+
|
|
895
1001
|
const format = this.assetLoader?.getFileFormat( file?.name || '' );
|
|
896
1002
|
if ( ! format ) throw new Error( `Unsupported file format: ${file?.name}` );
|
|
897
1003
|
|
|
898
1004
|
if ( format.type !== 'environment' && format.type !== 'image' ) {
|
|
899
1005
|
|
|
1006
|
+
const identity = fileIdentity( file ).catch( () => ( { name: file.name, size: file.size, lastModified: file.lastModified ?? 0, sample: null } ) );
|
|
900
1007
|
await this._loadWithSceneRebuild(
|
|
901
1008
|
() => this.assetLoader.loadAssetFromFile( file, options ),
|
|
902
|
-
{ type: 'ModelLoaded', filename: file.name }
|
|
1009
|
+
{ type: 'ModelLoaded', filename: file.name },
|
|
1010
|
+
async () => {
|
|
1011
|
+
|
|
1012
|
+
const id = await identity;
|
|
1013
|
+
return { kind: 'local-file', file: id, key: id.sample ? identityKey( id ) : null, ...partOf( options ) };
|
|
1014
|
+
|
|
1015
|
+
}
|
|
903
1016
|
);
|
|
1017
|
+
this._sceneSourceFile = file;
|
|
904
1018
|
return;
|
|
905
1019
|
|
|
906
1020
|
}
|
|
@@ -942,7 +1056,8 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
942
1056
|
|
|
943
1057
|
await this._loadWithSceneRebuild(
|
|
944
1058
|
() => this.assetLoader.loadExampleModels( index, modelFiles ),
|
|
945
|
-
{ type: 'ModelLoaded', index }
|
|
1059
|
+
{ type: 'ModelLoaded', index },
|
|
1060
|
+
{ kind: 'url', url: modelFiles[ index ]?.url ?? null, cacheKey: null }
|
|
946
1061
|
);
|
|
947
1062
|
|
|
948
1063
|
}
|
|
@@ -1003,8 +1118,28 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1003
1118
|
|
|
1004
1119
|
}
|
|
1005
1120
|
|
|
1121
|
+
/**
|
|
1122
|
+
* Where the model on screen came from, as plain data — what a saved session reopens:
|
|
1123
|
+
* `{ kind: 'url', url, cacheKey, filename?, element?, pbrtEntry? }`,
|
|
1124
|
+
* `{ kind: 'local-file', file: {name, size, lastModified, sample}, key, element?, pbrtEntry? }` or
|
|
1125
|
+
* `{ kind: 'object3d', name }`. Null before a load and after {@link unloadScene}.
|
|
1126
|
+
* @returns {?Object}
|
|
1127
|
+
*/
|
|
1128
|
+
get sceneSource() {
|
|
1129
|
+
|
|
1130
|
+
return this._sceneSource ?? null;
|
|
1131
|
+
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
/** The File a local load came from, so a host can save a project that carries it; null otherwise. */
|
|
1135
|
+
get sceneSourceFile() {
|
|
1136
|
+
|
|
1137
|
+
return this._sceneSourceFile ?? null;
|
|
1138
|
+
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1006
1141
|
/** Shared pipeline: load asset → sync controls → build BVH → reset → dispatch events */
|
|
1007
|
-
async _loadWithSceneRebuild( loadFn, eventPayload ) {
|
|
1142
|
+
async _loadWithSceneRebuild( loadFn, eventPayload, source = null ) {
|
|
1008
1143
|
|
|
1009
1144
|
if ( this._loadingInProgress ) throw this._busyError( 'PathTracerApp' );
|
|
1010
1145
|
|
|
@@ -1013,6 +1148,8 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1013
1148
|
try {
|
|
1014
1149
|
|
|
1015
1150
|
await loadFn();
|
|
1151
|
+
this._sceneSource = typeof source === 'function' ? await source() : source;
|
|
1152
|
+
this._sceneSourceFile = null;
|
|
1016
1153
|
// A fresh model re-establishes the emissive-sampling auto-default (incremental
|
|
1017
1154
|
// rebuilds — add/remove object, texture reprocess — preserve the user's choice).
|
|
1018
1155
|
this._emissiveSamplingUserSet = false;
|
|
@@ -1022,6 +1159,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1022
1159
|
this._clearAppendedModels();
|
|
1023
1160
|
this._syncControlsAfterLoad();
|
|
1024
1161
|
await this.loadSceneData( { pendingEnvironment: this._beginSceneMetadataEnvironment() } );
|
|
1162
|
+
this._maybeSpill();
|
|
1025
1163
|
this.pipeline?.eventBus.emit( 'autoexposure:resetHistory' );
|
|
1026
1164
|
// Not held: the first denoise of a new scene lands ~1.1 s after the load (upload and
|
|
1027
1165
|
// shader compilation come first), and a second of the previous model reads as a bug.
|
|
@@ -1054,6 +1192,8 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1054
1192
|
*/
|
|
1055
1193
|
_discardFailedLoad() {
|
|
1056
1194
|
|
|
1195
|
+
this._sceneSource = this._sceneSourceFile = null;
|
|
1196
|
+
|
|
1057
1197
|
try {
|
|
1058
1198
|
|
|
1059
1199
|
this._clearAppendedModels();
|
|
@@ -1166,7 +1306,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1166
1306
|
* CDF'd concurrently with the BVH build rather than before it.
|
|
1167
1307
|
* @returns {boolean}
|
|
1168
1308
|
*/
|
|
1169
|
-
async loadSceneData( { pendingEnvironment = null } = {} ) {
|
|
1309
|
+
async loadSceneData( { pendingEnvironment = null, keepUserLights = false } = {} ) {
|
|
1170
1310
|
|
|
1171
1311
|
// Clear selection before rebuilding — the old object leaves the scene graph.
|
|
1172
1312
|
// Skipped on the append path (addModel): the selected object persists, so its
|
|
@@ -1182,7 +1322,9 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1182
1322
|
this._tagPrimarySceneObject();
|
|
1183
1323
|
|
|
1184
1324
|
const timer = new BuildTimer( '', { namespace: 'scene', level: 'info' } );
|
|
1185
|
-
|
|
1325
|
+
// meshScene.environment is the HDRI slot: a sky, gradient or colour in use outlives the model.
|
|
1326
|
+
const hdri = () => this.stages.pathTracer.environment.envParams.mode === 'hdri' ? this.meshScene.environment : null;
|
|
1327
|
+
const environmentTexture = hdri();
|
|
1186
1328
|
|
|
1187
1329
|
// Environment CDF build in parallel with BVH
|
|
1188
1330
|
let cdfPromise = null;
|
|
@@ -1225,7 +1367,8 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1225
1367
|
// Build BVH
|
|
1226
1368
|
timer.start( 'BVH build (SceneProcessor)' );
|
|
1227
1369
|
this._sdf.setMaxTextureSize( this._maxTextureSize );
|
|
1228
|
-
await this._sdf.buildBVH( this.meshScene );
|
|
1370
|
+
await this._sdf.buildBVH( this.meshScene, { sceneKey: this.assetLoader?.sceneSourceKey ?? null, progressive: this._progressiveSpill() } );
|
|
1371
|
+
this.assetLoader?.flushPendingGraph( this._sdf.performanceMetrics.totalProcessingTime );
|
|
1229
1372
|
timer.end( 'BVH build (SceneProcessor)' );
|
|
1230
1373
|
|
|
1231
1374
|
// Transfer geometry, materials, and textures to GPU
|
|
@@ -1235,7 +1378,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1235
1378
|
|
|
1236
1379
|
// Re-read rather than reusing the snapshot above: a pendingEnvironment may have landed
|
|
1237
1380
|
// during the BVH build, and the snapshot is then a disposed texture.
|
|
1238
|
-
if ( ! this._sdf.uploadToPathTracer( this.stages.pathTracer, this.lightManager, this.meshScene,
|
|
1381
|
+
if ( ! this._sdf.uploadToPathTracer( this.stages.pathTracer, this.lightManager, this.meshScene, hdri(), { keepUserLights } ) ) return false;
|
|
1239
1382
|
|
|
1240
1383
|
// Patch per-mesh visibility into the TLAS leaves we just uploaded
|
|
1241
1384
|
this.stages.pathTracer._meshRefs = this.stages.pathTracer._collectMeshRefs( this.meshScene );
|
|
@@ -1363,7 +1506,8 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1363
1506
|
/** Reframe-free rebuild sequence. Assumes the _loadingInProgress guard is already held. */
|
|
1364
1507
|
async _finishRebuildNoReframe( eventPayload ) {
|
|
1365
1508
|
|
|
1366
|
-
await this.loadSceneData(); // emits 'SceneRebuild'
|
|
1509
|
+
await this.loadSceneData( { keepUserLights: true } ); // emits 'SceneRebuild'
|
|
1510
|
+
this._maybeSpill();
|
|
1367
1511
|
this._recalibrateControlLimits(); // scene bounds changed — retune zoom limits + near/far (no camera move)
|
|
1368
1512
|
this.pipeline?.eventBus.emit( 'autoexposure:resetHistory' );
|
|
1369
1513
|
this.reset();
|
|
@@ -1379,7 +1523,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1379
1523
|
* @param {string} [opts.name] - Display name for the scene-object list.
|
|
1380
1524
|
* @returns {Promise<string>} the new object's id (Object3D uuid).
|
|
1381
1525
|
*/
|
|
1382
|
-
async addModel( url, { name } = {} ) {
|
|
1526
|
+
async addModel( url, { name, cacheKey } = {} ) {
|
|
1383
1527
|
|
|
1384
1528
|
if ( this._loadingInProgress ) {
|
|
1385
1529
|
|
|
@@ -1391,8 +1535,10 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1391
1535
|
this._preserveSelectionOnRebuild = true;
|
|
1392
1536
|
try {
|
|
1393
1537
|
|
|
1394
|
-
const { root } = await this.assetLoader.appendModel( url );
|
|
1538
|
+
const { root } = await this.assetLoader.appendModel( url, { cacheKey } );
|
|
1395
1539
|
root.userData.__rayzeeSceneObject = true;
|
|
1540
|
+
root.userData.__rayzeeSourceUrl = url;
|
|
1541
|
+
if ( cacheKey ) root.userData.__rayzeeCacheKey = cacheKey;
|
|
1396
1542
|
if ( name ) root.userData.__rayzeeName = name;
|
|
1397
1543
|
await this._finishRebuildNoReframe( { type: 'ModelAdded', url, id: root.uuid } );
|
|
1398
1544
|
return root.uuid;
|
|
@@ -1545,6 +1691,75 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1545
1691
|
|
|
1546
1692
|
}
|
|
1547
1693
|
|
|
1694
|
+
/**
|
|
1695
|
+
* Everything changed since the scene loaded — render settings, environment, colour, lights,
|
|
1696
|
+
* cameras, timeline keys, material edits, hidden and moved objects — as JSON-safe data. The
|
|
1697
|
+
* model is not in it: {@link importSceneState} applies it once the host has loaded that again.
|
|
1698
|
+
* @returns {Object}
|
|
1699
|
+
*/
|
|
1700
|
+
exportSceneState() {
|
|
1701
|
+
|
|
1702
|
+
return captureSceneState( this );
|
|
1703
|
+
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
/**
|
|
1707
|
+
* Applies {@link exportSceneState}'s data to the scene now loaded. Objects, materials and
|
|
1708
|
+
* cameras are matched by position plus name; a scene that differs keeps its own objects and
|
|
1709
|
+
* materials and the rest still applies.
|
|
1710
|
+
* @param {Object} state
|
|
1711
|
+
* @param {Object} [options]
|
|
1712
|
+
* @param {function(Object): Promise<*>} [options.resolve] - supplies what the engine cannot
|
|
1713
|
+
* reach: `{ kind: 'environment', source }` → File | URL | null, `{ kind: 'colorConfig', config }`
|
|
1714
|
+
* → true once loaded. Without it, URL environments and built-in colour configs still restore.
|
|
1715
|
+
* @returns {Promise<{skipped: Array<{section: string, reason: string}>}>}
|
|
1716
|
+
*/
|
|
1717
|
+
async importSceneState( state, options ) {
|
|
1718
|
+
|
|
1719
|
+
return await applySceneState( this, state, options );
|
|
1720
|
+
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
/** Triangle and placement budgets for a scene archive: raised when the build can spill. @private */
|
|
1724
|
+
_sceneBudgets( options ) {
|
|
1725
|
+
|
|
1726
|
+
if ( ! this._memorySpill || ! this.storage ) return options;
|
|
1727
|
+
return { maxTriangles: SPILL_TRIANGLE_BUDGET, maxPlacements: SPILL_PLACEMENT_BUDGET, ...options };
|
|
1728
|
+
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
/**
|
|
1732
|
+
* Experimental memory spill, during the build: static scenes only, since a deforming clip
|
|
1733
|
+
* refits every frame. @private
|
|
1734
|
+
*/
|
|
1735
|
+
_progressiveSpill() {
|
|
1736
|
+
|
|
1737
|
+
if ( ! this._memorySpill || ! this.storage || ( this.assetLoader?.animations?.length ?? 0 ) > 0 ) return null;
|
|
1738
|
+
const stage = this.stages.pathTracer;
|
|
1739
|
+
return { storage: this.storage, uploader: ( records, kind ) => kind === 'triangles' ? stage.createTriangleChunkUploader( records ) : stage.createChunkUploader( records ) };
|
|
1740
|
+
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
/** Experimental memory spill for a static scene, in the background. @private */
|
|
1744
|
+
_maybeSpill() {
|
|
1745
|
+
|
|
1746
|
+
if ( ! this._memorySpill || ! this.storage || this.animationManager?.hasAnimations ) return;
|
|
1747
|
+
this._sdf?.spillToDisk( this.storage )
|
|
1748
|
+
.then( result => result && this.dispatchEvent( { type: 'SceneSpilled', ...result } ) )
|
|
1749
|
+
.catch( error => this._issues.warn( ISSUE_CODES.STORAGE_WRITE_FAILED, `memory spill failed: ${error.message}` ) );
|
|
1750
|
+
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
/**
|
|
1754
|
+
* Reads back what {@link options.memorySpill} moved to disk. The engine does this itself before
|
|
1755
|
+
* its own readers; a host calling {@link refitBLASes} on a spilled scene awaits it first.
|
|
1756
|
+
*/
|
|
1757
|
+
async ensureSceneResident() {
|
|
1758
|
+
|
|
1759
|
+
if ( this._sdf?.spilled ) await this._sdf.ensureResident();
|
|
1760
|
+
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1548
1763
|
/** Notify consumers that the camera list changed (names / count). */
|
|
1549
1764
|
_dispatchCamerasUpdated() {
|
|
1550
1765
|
|
|
@@ -1623,6 +1838,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1623
1838
|
*/
|
|
1624
1839
|
async refitBVH( newPositions, newNormals ) {
|
|
1625
1840
|
|
|
1841
|
+
await this.ensureSceneResident();
|
|
1626
1842
|
const result = await this._sdf.refitBVH( newPositions, newNormals );
|
|
1627
1843
|
|
|
1628
1844
|
this.stages.pathTracer.updateTriangleData( this._sdf.triangles );
|
|
@@ -1645,6 +1861,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1645
1861
|
*/
|
|
1646
1862
|
refitBLASes( affectedMeshIndices, newPositions, newNormals ) {
|
|
1647
1863
|
|
|
1864
|
+
if ( this._sdf?.spilled ) throw new Error( 'refitBLASes: the scene is spilled to disk — await app.ensureSceneResident() first' );
|
|
1648
1865
|
const result = this._sdf.refitBLASes( affectedMeshIndices, newPositions, newNormals );
|
|
1649
1866
|
|
|
1650
1867
|
const { triRanges, bvhRanges } = this._sdf.computeBLASDirtyRanges( affectedMeshIndices );
|
|
@@ -1725,6 +1942,13 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1725
1942
|
_refreshMovedEmitters() {
|
|
1726
1943
|
|
|
1727
1944
|
if ( ! this._emittersMoved ) return;
|
|
1945
|
+
if ( this._sdf?.spilled ) {
|
|
1946
|
+
|
|
1947
|
+
this.ensureSceneResident().then( () => this._refreshMovedEmitters() );
|
|
1948
|
+
return;
|
|
1949
|
+
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1728
1952
|
this._emittersMoved = false;
|
|
1729
1953
|
this._uploadEmissivePayload( this._sdf?.refreshEmissiveTransforms() ?? null );
|
|
1730
1954
|
|
|
@@ -2088,6 +2312,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
2088
2312
|
// refresh landing in the middle of that raced the renderer's own output pass.
|
|
2089
2313
|
this.denoisingManager?.setCadenceSuspended( isProduction );
|
|
2090
2314
|
|
|
2315
|
+
this.timeline.stop();
|
|
2091
2316
|
this.cameraManager.controls.enabled = ! isProduction;
|
|
2092
2317
|
|
|
2093
2318
|
// Anything with a SETTING_ROUTES entry must go through settings, not setUniform: set() early-returns on
|
|
@@ -2097,6 +2322,10 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
2097
2322
|
// renderMode has no SETTING_ROUTES entry
|
|
2098
2323
|
this.stages.pathTracer?.setUniform( 'renderMode', parseInt( config.renderMode ) );
|
|
2099
2324
|
|
|
2325
|
+
// A move just before this left the moving-camera drop in force or queued; a final render
|
|
2326
|
+
// never runs at it.
|
|
2327
|
+
if ( isProduction ) this._applyRenderScale( 1 );
|
|
2328
|
+
|
|
2100
2329
|
this.stages.pathTracer?.updateCompletionThreshold?.();
|
|
2101
2330
|
|
|
2102
2331
|
const denoiser = this.denoisingManager?.denoiser;
|
|
@@ -2440,6 +2669,41 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
2440
2669
|
|
|
2441
2670
|
}
|
|
2442
2671
|
|
|
2672
|
+
/**
|
|
2673
|
+
* A render in progress as data: the accumulated colour (and the aux buffers while they hold
|
|
2674
|
+
* samples), the per-pixel convergence buffers, and the counters that choose the next sample.
|
|
2675
|
+
* {@link restoreRenderCheckpoint} continues it — bit-for-bit in deterministic mode, since the
|
|
2676
|
+
* random sequence is a pure function of pixel, frame and seed tick. At 4K it is ~500 MB.
|
|
2677
|
+
* @returns {Promise<?Object>} null before the path tracer exists
|
|
2678
|
+
*/
|
|
2679
|
+
async captureRenderCheckpoint() {
|
|
2680
|
+
|
|
2681
|
+
const stage = this.stages.pathTracer;
|
|
2682
|
+
if ( ! stage ) return null;
|
|
2683
|
+
const accumulation = await stage.captureAccumulation();
|
|
2684
|
+
return { v: RENDER_CHECKPOINT_VERSION, engine: VERSION, savedAt: Date.now(), samples: accumulation.state.frameCount, ...accumulation };
|
|
2685
|
+
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
/**
|
|
2689
|
+
* Continues a {@link captureRenderCheckpoint}. The scene, settings and render size must be the
|
|
2690
|
+
* ones it was taken with (restore the scene state first), and the scene must have rendered a
|
|
2691
|
+
* frame at that size since it last changed. Anything that resets accumulation afterwards
|
|
2692
|
+
* discards it. Rendering is not restarted: call {@link wake} or {@link renderFrames}.
|
|
2693
|
+
* @param {Object} checkpoint
|
|
2694
|
+
*/
|
|
2695
|
+
restoreRenderCheckpoint( checkpoint ) {
|
|
2696
|
+
|
|
2697
|
+
if ( checkpoint?.v !== RENDER_CHECKPOINT_VERSION ) throw new Error( `render checkpoint version ${checkpoint?.v} is not ${RENDER_CHECKPOINT_VERSION}` );
|
|
2698
|
+
const stage = this.stages.pathTracer;
|
|
2699
|
+
if ( ! stage ) throw new Error( 'restoreRenderCheckpoint: app is not initialized' );
|
|
2700
|
+
|
|
2701
|
+
stage.restoreAccumulation( checkpoint );
|
|
2702
|
+
this.needsReset = false;
|
|
2703
|
+
this.dispatchEvent( { type: EngineEvents.RENDER_RESET, restored: checkpoint.samples } );
|
|
2704
|
+
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2443
2707
|
/**
|
|
2444
2708
|
* The path tracer's accumulation as pixels, read from its storage target rather than the
|
|
2445
2709
|
* canvas — so it works headless, works while the page is hidden, and cannot pick up a
|
|
@@ -2945,6 +3209,14 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
2945
3209
|
|
|
2946
3210
|
setMaterialProperty( materialIndex, property, value ) {
|
|
2947
3211
|
|
|
3212
|
+
// These rewrite triangle records, which a spilled scene has to read back first.
|
|
3213
|
+
if ( this._sdf?.spilled && TRIANGLE_PATCH_PROPERTIES.has( property ) ) {
|
|
3214
|
+
|
|
3215
|
+
this.ensureSceneResident().then( () => this.setMaterialProperty( materialIndex, property, value ) );
|
|
3216
|
+
return;
|
|
3217
|
+
|
|
3218
|
+
}
|
|
3219
|
+
|
|
2948
3220
|
this.stages.pathTracer?.materialData.updateMaterialProperty( materialIndex, property, value );
|
|
2949
3221
|
|
|
2950
3222
|
// Keep the emissive-NEE structure in sync unconditionally (not gated on the
|
|
@@ -3248,6 +3520,9 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
3248
3520
|
}
|
|
3249
3521
|
|
|
3250
3522
|
if ( envChanged ) await this.environmentManager?.buildEnvironmentCDF?.();
|
|
3523
|
+
// Its pixels never leave the GPU, so it is baked again in the new space rather than converted.
|
|
3524
|
+
if ( env?._isPhysicalSky ) await this.environmentManager.generateProceduralSkyTexture();
|
|
3525
|
+
this.environmentManager?.refreshSun?.();
|
|
3251
3526
|
|
|
3252
3527
|
this.reset();
|
|
3253
3528
|
|
|
@@ -3259,6 +3534,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
3259
3534
|
*/
|
|
3260
3535
|
async rebuildMaterials( scene ) {
|
|
3261
3536
|
|
|
3537
|
+
await this.ensureSceneResident();
|
|
3262
3538
|
await this.stages.pathTracer?.rebuildMaterials( scene || this.meshScene );
|
|
3263
3539
|
this.reset();
|
|
3264
3540
|
|
|
@@ -3390,6 +3666,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
3390
3666
|
_initCameraManager() {
|
|
3391
3667
|
|
|
3392
3668
|
this.cameraManager = new CameraManager( this.canvas );
|
|
3669
|
+
this.timeline = new TimelineManager( { cameraManager: this.cameraManager, onReset: () => this.reset() } );
|
|
3393
3670
|
|
|
3394
3671
|
}
|
|
3395
3672
|
|
|
@@ -3415,14 +3692,19 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
3415
3692
|
this.assetLoader.setRenderer( this.renderer );
|
|
3416
3693
|
this.assetLoader.createFloorPlane();
|
|
3417
3694
|
|
|
3418
|
-
|
|
3695
|
+
const onCameraMoved = () => {
|
|
3419
3696
|
|
|
3420
3697
|
this.needsReset = true;
|
|
3421
3698
|
// Here rather than in render(), so the first frame of the move is already at the lower resolution.
|
|
3422
3699
|
this.stages.pathTracer?.enterInteractionMode();
|
|
3423
3700
|
this.wake();
|
|
3424
3701
|
|
|
3425
|
-
}
|
|
3702
|
+
};
|
|
3703
|
+
|
|
3704
|
+
this._addTrackedListener( this.cameraManager.controls, 'change', onCameraMoved );
|
|
3705
|
+
this._addTrackedListener( this.cameraManager.walkControls, 'change', onCameraMoved );
|
|
3706
|
+
// A held key moves the camera from the frame loop, which may be asleep.
|
|
3707
|
+
this._addTrackedListener( this.cameraManager.walkControls, 'start', () => this.wake() );
|
|
3426
3708
|
|
|
3427
3709
|
}
|
|
3428
3710
|
|
|
@@ -3518,6 +3800,8 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
3518
3800
|
// Expose environment manager (lives on pathTracer stage)
|
|
3519
3801
|
this.environmentManager = this.stages.pathTracer.environment;
|
|
3520
3802
|
this.environmentManager.callbacks.onAutoExposureReset = () => this.pipeline.eventBus.emit( 'autoexposure:resetHistory' );
|
|
3803
|
+
// A whole-app reset, not the stage's: a sky bake lands after its input, often once the loop is idle.
|
|
3804
|
+
this.environmentManager.callbacks.onReset = () => this.reset();
|
|
3521
3805
|
|
|
3522
3806
|
}
|
|
3523
3807
|
|
|
@@ -3526,6 +3810,8 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
3526
3810
|
// Forward manager events → app events
|
|
3527
3811
|
this._addTrackedListener( this.cameraManager, 'CameraSwitched', ( e ) => this.dispatchEvent( e ) );
|
|
3528
3812
|
this._addTrackedListener( this.cameraManager, EngineEvents.AUTO_FOCUS_UPDATED, ( e ) => this.dispatchEvent( e ) );
|
|
3813
|
+
this._addTrackedListener( this.cameraManager, EngineEvents.ORTHO_HEIGHT_UPDATED, ( e ) => this.dispatchEvent( e ) );
|
|
3814
|
+
this._addTrackedListener( this.timeline, EngineEvents.TIMELINE_CHANGED, ( e ) => this.dispatchEvent( e ) );
|
|
3529
3815
|
|
|
3530
3816
|
this._forwardEvents( this.denoisingManager, [
|
|
3531
3817
|
EngineEvents.DENOISING_START, EngineEvents.DENOISING_END,
|
|
@@ -3566,9 +3852,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
3566
3852
|
|
|
3567
3853
|
// Auto-focus context — CameraManager stores it, reads it each frame
|
|
3568
3854
|
this.cameraManager.initAutoFocus( {
|
|
3569
|
-
meshScene: this.meshScene,
|
|
3570
3855
|
assetLoader: this.assetLoader,
|
|
3571
|
-
floorPlane: this.assetLoader.floorPlane,
|
|
3572
3856
|
pathTracer: this.stages.pathTracer,
|
|
3573
3857
|
settings: this.settings,
|
|
3574
3858
|
softReset: () => this.reset( true ),
|
|
@@ -3582,12 +3866,12 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
3582
3866
|
resetCallback: () => this.reset(),
|
|
3583
3867
|
reconcileCompletion: () => this._reconcileCompletion(),
|
|
3584
3868
|
denoisingManager: this.denoisingManager,
|
|
3585
|
-
cameraManager: this.cameraManager,
|
|
3586
3869
|
onInteractionRenderScale: () => {
|
|
3587
3870
|
|
|
3588
3871
|
if ( this.stages.pathTracer?.interactionMode ) this._requestRenderScale( this._interactionRenderScale() );
|
|
3589
3872
|
|
|
3590
3873
|
},
|
|
3874
|
+
onCameraProjection: ( value ) => this.cameraManager.applyProjection( value ),
|
|
3591
3875
|
} );
|
|
3592
3876
|
|
|
3593
3877
|
this.renderer.toneMappingExposure = this.settings.get( 'exposure' ) ?? 1.0;
|
|
@@ -3643,6 +3927,8 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
3643
3927
|
|
|
3644
3928
|
const cameras = [ this.cameraManager.camera, ...( event.cameras || [] ) ];
|
|
3645
3929
|
this.cameraManager.setCameras( cameras );
|
|
3930
|
+
// Keys are poses in the scene being replaced, like the cameras saved in it.
|
|
3931
|
+
this.timeline.clear();
|
|
3646
3932
|
|
|
3647
3933
|
if ( this.interactionManager ) {
|
|
3648
3934
|
|
|
@@ -3821,8 +4107,10 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
3821
4107
|
|
|
3822
4108
|
_syncControlsAfterLoad() {
|
|
3823
4109
|
|
|
4110
|
+
this.cameraManager.fitOrthographic();
|
|
3824
4111
|
this.cameraManager.controls.saveState();
|
|
3825
4112
|
this.cameraManager.controls.update();
|
|
4113
|
+
this.cameraManager.walkControls.fitSpeed( this.assetLoader.getSceneScale() );
|
|
3826
4114
|
|
|
3827
4115
|
}
|
|
3828
4116
|
|