rayzee 4.8.15 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -97
- package/dist/rayzee.es.js +1910 -2175
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +56 -56
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/PathTracerApp.js +402 -1567
- package/src/Pipeline/CompletionTracker.js +89 -0
- package/src/Processor/AssetLoader.js +25 -1
- package/src/Processor/SceneProcessor.js +146 -0
- package/src/Processor/TLASBuilder.js +61 -30
- package/src/RenderSettings.js +82 -4
- package/src/index.js +2 -12
- package/src/managers/AnimationManager.js +18 -6
- package/src/managers/CameraManager.js +133 -15
- package/src/managers/DenoisingManager.js +289 -3
- package/src/managers/EnvironmentManager.js +94 -1
- package/src/managers/InteractionManager.js +142 -0
- package/src/managers/LightManager.js +51 -1
- package/src/managers/OverlayManager.js +97 -0
- package/src/managers/TransformManager.js +1 -0
- package/src/managers/VideoRenderManager.js +6 -6
- package/src/managers/helpers/StatsHelper.js +45 -0
- package/src/api/AnimationAPI.js +0 -87
- package/src/api/CameraAPI.js +0 -109
- package/src/api/DenoisingAPI.js +0 -243
- package/src/api/EnvironmentAPI.js +0 -106
- package/src/api/LightsAPI.js +0 -80
- package/src/api/MaterialsAPI.js +0 -73
- package/src/api/OutputAPI.js +0 -90
- package/src/api/SelectionAPI.js +0 -89
- package/src/api/TransformAPI.js +0 -49
- package/src/api/index.js +0 -16
package/src/PathTracerApp.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { WebGPURenderer, RectAreaLightNode } from 'three/webgpu';
|
|
2
2
|
import {
|
|
3
|
-
ACESFilmicToneMapping,
|
|
4
|
-
Mesh, CircleGeometry, MeshPhysicalMaterial, TimestampQuery
|
|
3
|
+
ACESFilmicToneMapping, Scene, EventDispatcher, TimestampQuery
|
|
5
4
|
} from 'three';
|
|
6
5
|
import { RectAreaLightTexturesLib } from 'three/addons/lights/RectAreaLightTexturesLib.js';
|
|
7
|
-
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
8
6
|
import { SceneHelpers } from './SceneHelpers.js';
|
|
9
|
-
import
|
|
7
|
+
import { createStats } from './managers/helpers/StatsHelper.js';
|
|
10
8
|
import { PathTracer } from './Stages/PathTracer.js';
|
|
11
9
|
import { NormalDepth } from './Stages/NormalDepth.js';
|
|
12
10
|
import { MotionVector } from './Stages/MotionVector.js';
|
|
@@ -19,6 +17,7 @@ import { AutoExposure } from './Stages/AutoExposure.js';
|
|
|
19
17
|
import { SSRC } from './Stages/SSRC.js';
|
|
20
18
|
import { Display } from './Stages/Display.js';
|
|
21
19
|
import { RenderPipeline } from './Pipeline/RenderPipeline.js';
|
|
20
|
+
import { CompletionTracker } from './Pipeline/CompletionTracker.js';
|
|
22
21
|
import { ENGINE_DEFAULTS as DEFAULT_STATE, FINAL_RENDER_CONFIG, PREVIEW_RENDER_CONFIG } from './EngineDefaults.js';
|
|
23
22
|
import { updateStats, updateLoading, resetLoading, setStatusCallback, getDisplaySamples } from './Processor/utils.js';
|
|
24
23
|
import { BuildTimer } from './Processor/BuildTimer.js';
|
|
@@ -27,17 +26,6 @@ import { EngineEvents } from './EngineEvents.js';
|
|
|
27
26
|
import { AssetLoader } from './Processor/AssetLoader.js';
|
|
28
27
|
import { SceneProcessor } from './Processor/SceneProcessor.js';
|
|
29
28
|
|
|
30
|
-
// Sub-API facades
|
|
31
|
-
import { OutputAPI } from './api/OutputAPI.js';
|
|
32
|
-
import { LightsAPI } from './api/LightsAPI.js';
|
|
33
|
-
import { AnimationAPI } from './api/AnimationAPI.js';
|
|
34
|
-
import { SelectionAPI } from './api/SelectionAPI.js';
|
|
35
|
-
import { TransformAPI } from './api/TransformAPI.js';
|
|
36
|
-
import { CameraAPI } from './api/CameraAPI.js';
|
|
37
|
-
import { EnvironmentAPI } from './api/EnvironmentAPI.js';
|
|
38
|
-
import { MaterialsAPI } from './api/MaterialsAPI.js';
|
|
39
|
-
import { DenoisingAPI } from './api/DenoisingAPI.js';
|
|
40
|
-
|
|
41
29
|
// Managers
|
|
42
30
|
import { RenderSettings } from './RenderSettings.js';
|
|
43
31
|
import { CameraManager } from './managers/CameraManager.js';
|
|
@@ -46,18 +34,22 @@ import { DenoisingManager } from './managers/DenoisingManager.js';
|
|
|
46
34
|
import { OverlayManager } from './managers/OverlayManager.js';
|
|
47
35
|
import { AnimationManager } from './managers/AnimationManager.js';
|
|
48
36
|
import { TransformManager } from './managers/TransformManager.js';
|
|
49
|
-
import { TileHelper } from './managers/helpers/TileHelper.js';
|
|
50
|
-
import { OutlineHelper } from './managers/helpers/OutlineHelper.js';
|
|
51
37
|
|
|
52
38
|
|
|
53
39
|
/**
|
|
54
40
|
* WebGPU Path Tracer Application.
|
|
55
41
|
*
|
|
56
|
-
*
|
|
57
|
-
* - {@link
|
|
58
|
-
* - {@link
|
|
59
|
-
* - {@link
|
|
60
|
-
* - {@link
|
|
42
|
+
* Managers are exposed as direct public properties (Three.js style):
|
|
43
|
+
* - `app.cameraManager` — {@link CameraManager} (camera, controls, auto-focus, DOF)
|
|
44
|
+
* - `app.lightManager` — {@link LightManager} (CRUD, helpers, GPU transfer)
|
|
45
|
+
* - `app.denoisingManager` — {@link DenoisingManager} (strategy, OIDN, AI upscaler)
|
|
46
|
+
* - `app.animationManager` — {@link AnimationManager} (playback, clips, speed)
|
|
47
|
+
* - `app.transformManager` — {@link TransformManager} (gizmo, drag, BVH refit)
|
|
48
|
+
* - `app.interactionManager` — {@link InteractionManager} (selection, focus, context menu)
|
|
49
|
+
* - `app.overlayManager` — {@link OverlayManager} (HUD, helpers)
|
|
50
|
+
* - `app.environmentManager` — EnvironmentManager (HDRI, procedural sky, mode switching)
|
|
51
|
+
* - `app.settings` — {@link RenderSettings} (all render parameters)
|
|
52
|
+
* - `app.stages` — Named pipeline stages for advanced control
|
|
61
53
|
*
|
|
62
54
|
* Extends EventDispatcher for event-driven communication with stores/UI.
|
|
63
55
|
*/
|
|
@@ -75,7 +67,6 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
75
67
|
super();
|
|
76
68
|
|
|
77
69
|
this.canvas = canvas;
|
|
78
|
-
this.denoiserCanvas = null;
|
|
79
70
|
this._autoResize = options.autoResize !== false;
|
|
80
71
|
this._showStats = options.showStats !== false;
|
|
81
72
|
this._statsContainer = options.statsContainer || null;
|
|
@@ -85,16 +76,13 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
85
76
|
|
|
86
77
|
// ── Core objects (populated in init) ──
|
|
87
78
|
this.renderer = null;
|
|
88
|
-
this._camera = null;
|
|
89
79
|
this.scene = null;
|
|
90
80
|
this.meshScene = null;
|
|
91
81
|
this._sceneHelpers = null;
|
|
92
|
-
this._controls = null;
|
|
93
82
|
|
|
94
83
|
// ── Asset pipeline ──
|
|
95
84
|
this.assetLoader = null;
|
|
96
85
|
this._sdf = null;
|
|
97
|
-
this.animationManager = new AnimationManager();
|
|
98
86
|
this._animRefitInFlight = false;
|
|
99
87
|
|
|
100
88
|
// ── Pipeline & stages ──
|
|
@@ -107,169 +95,40 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
107
95
|
*/
|
|
108
96
|
this.stages = {};
|
|
109
97
|
|
|
110
|
-
// ── Managers (
|
|
98
|
+
// ── Managers (direct public access) ──
|
|
99
|
+
/** @type {CameraManager} */
|
|
111
100
|
this.cameraManager = null;
|
|
101
|
+
/** @type {LightManager} */
|
|
112
102
|
this.lightManager = null;
|
|
103
|
+
/** @type {DenoisingManager} */
|
|
113
104
|
this.denoisingManager = null;
|
|
105
|
+
/** @type {OverlayManager} */
|
|
114
106
|
this.overlayManager = null;
|
|
107
|
+
/** @type {InteractionManager} */
|
|
108
|
+
this.interactionManager = null;
|
|
109
|
+
/** @type {TransformManager} */
|
|
110
|
+
this.transformManager = null;
|
|
111
|
+
/** @type {AnimationManager} */
|
|
112
|
+
this.animationManager = new AnimationManager();
|
|
113
|
+
/** @type {import('./managers/EnvironmentManager.js').EnvironmentManager} */
|
|
114
|
+
this.environmentManager = null;
|
|
115
115
|
|
|
116
116
|
// ── State ──
|
|
117
117
|
this.isInitialized = false;
|
|
118
118
|
this.pauseRendering = false;
|
|
119
119
|
this.pathTracerEnabled = true;
|
|
120
|
-
this.
|
|
120
|
+
this.animationManagerId = null;
|
|
121
121
|
this.needsReset = false;
|
|
122
|
-
this._renderCompleteDispatched = false;
|
|
123
122
|
this._loadingInProgress = false;
|
|
124
123
|
this._needsDisplayRefresh = false;
|
|
125
124
|
this._paused = false;
|
|
126
125
|
|
|
127
|
-
//
|
|
128
|
-
this.
|
|
129
|
-
this.timeElapsed = 0;
|
|
126
|
+
// Render completion tracking
|
|
127
|
+
this.completion = new CompletionTracker();
|
|
130
128
|
|
|
131
129
|
// Resolution state
|
|
132
|
-
this._lastRenderWidth = 0;
|
|
133
|
-
this._lastRenderHeight = 0;
|
|
134
130
|
this._resizeDebounceTimer = null;
|
|
135
131
|
|
|
136
|
-
// ── Sub-API facade instances (lazily created via getters) ──
|
|
137
|
-
this._outputAPI = null;
|
|
138
|
-
this._lightsAPI = null;
|
|
139
|
-
this._animationAPI = null;
|
|
140
|
-
this._selectionAPI = null;
|
|
141
|
-
this._transformAPI = null;
|
|
142
|
-
this._cameraAPI = null;
|
|
143
|
-
this._environmentAPI = null;
|
|
144
|
-
this._materialsAPI = null;
|
|
145
|
-
this._denoisingAPI = null;
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// ═══════════════════════════════════════════════════════════════
|
|
150
|
-
// Settings API — unified parameter access
|
|
151
|
-
// ═══════════════════════════════════════════════════════════════
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Sets a render parameter. Replaces all individual setXxx() methods.
|
|
155
|
-
* @param {string} key - Setting key (e.g. 'maxBounces', 'exposure')
|
|
156
|
-
* @param {*} value - New value
|
|
157
|
-
* @param {Object} [options]
|
|
158
|
-
* @param {boolean} [options.reset] - Override default reset behavior
|
|
159
|
-
* @param {boolean} [options.silent] - Suppress settingChanged event
|
|
160
|
-
*/
|
|
161
|
-
set( key, value, options ) {
|
|
162
|
-
|
|
163
|
-
this.settings.set( key, value, options );
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Batch-update multiple settings. Only resets once.
|
|
169
|
-
* @param {Object} updates - Key/value pairs
|
|
170
|
-
* @param {Object} [options]
|
|
171
|
-
*/
|
|
172
|
-
setMany( updates, options ) {
|
|
173
|
-
|
|
174
|
-
this.settings.setMany( updates, options );
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Reads the current value of a setting.
|
|
180
|
-
* @param {string} key
|
|
181
|
-
* @returns {*}
|
|
182
|
-
*/
|
|
183
|
-
get( key ) {
|
|
184
|
-
|
|
185
|
-
return this.settings.get( key );
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Returns a snapshot of all current settings.
|
|
191
|
-
* @returns {Object}
|
|
192
|
-
*/
|
|
193
|
-
getAll() {
|
|
194
|
-
|
|
195
|
-
return this.settings.getAll();
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// ═══════════════════════════════════════════════════════════════
|
|
200
|
-
// Sub-API Facades — namespaced access to grouped functionality
|
|
201
|
-
// ═══════════════════════════════════════════════════════════════
|
|
202
|
-
|
|
203
|
-
/** Canvas output, screenshots, resize, and scene statistics. */
|
|
204
|
-
get output() {
|
|
205
|
-
|
|
206
|
-
if ( ! this._outputAPI ) this._outputAPI = new OutputAPI( this );
|
|
207
|
-
return this._outputAPI;
|
|
208
|
-
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/** Light CRUD, helpers, and GPU sync. */
|
|
212
|
-
get lights() {
|
|
213
|
-
|
|
214
|
-
if ( ! this._lightsAPI ) this._lightsAPI = new LightsAPI( this );
|
|
215
|
-
return this._lightsAPI;
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/** Animation playback controls. */
|
|
220
|
-
get animation() {
|
|
221
|
-
|
|
222
|
-
if ( ! this._animationAPI ) this._animationAPI = new AnimationAPI( this );
|
|
223
|
-
return this._animationAPI;
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/** Object selection and interaction modes. */
|
|
228
|
-
get selection() {
|
|
229
|
-
|
|
230
|
-
if ( ! this._selectionAPI ) this._selectionAPI = new SelectionAPI( this );
|
|
231
|
-
return this._selectionAPI;
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/** Transform gizmo mode and space. */
|
|
236
|
-
get transform() {
|
|
237
|
-
|
|
238
|
-
if ( ! this._transformAPI ) this._transformAPI = new TransformAPI( this );
|
|
239
|
-
return this._transformAPI;
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/** Camera switching, auto-focus, and DOF — also exposes raw Three.js objects via .active and .controls. */
|
|
244
|
-
get camera() {
|
|
245
|
-
|
|
246
|
-
if ( ! this._cameraAPI ) this._cameraAPI = new CameraAPI( this );
|
|
247
|
-
return this._cameraAPI;
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/** Environment maps, sky modes, and procedural generation. */
|
|
252
|
-
get environment() {
|
|
253
|
-
|
|
254
|
-
if ( ! this._environmentAPI ) this._environmentAPI = new EnvironmentAPI( this );
|
|
255
|
-
return this._environmentAPI;
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/** Material property updates and texture transforms. */
|
|
260
|
-
get materials() {
|
|
261
|
-
|
|
262
|
-
if ( ! this._materialsAPI ) this._materialsAPI = new MaterialsAPI( this );
|
|
263
|
-
return this._materialsAPI;
|
|
264
|
-
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/** Denoiser strategy, ASVGF, OIDN, upscaler, adaptive sampling, and auto-exposure. */
|
|
268
|
-
get denoising() {
|
|
269
|
-
|
|
270
|
-
if ( ! this._denoisingAPI ) this._denoisingAPI = new DenoisingAPI( this );
|
|
271
|
-
return this._denoisingAPI;
|
|
272
|
-
|
|
273
132
|
}
|
|
274
133
|
|
|
275
134
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -281,226 +140,13 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
281
140
|
*/
|
|
282
141
|
async init() {
|
|
283
142
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const adapter = await navigator.gpu.requestAdapter( { powerPreference: 'high-performance' } );
|
|
295
|
-
if ( ! adapter ) {
|
|
296
|
-
|
|
297
|
-
throw new Error( 'Failed to get WebGPU adapter' );
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const adapterLimits = adapter.limits;
|
|
302
|
-
|
|
303
|
-
// Create and initialize WebGPU renderer
|
|
304
|
-
this.renderer = new WebGPURenderer( {
|
|
305
|
-
canvas: this.canvas,
|
|
306
|
-
alpha: true,
|
|
307
|
-
powerPreference: 'high-performance',
|
|
308
|
-
requiredLimits: {
|
|
309
|
-
maxBufferSize: adapterLimits.maxBufferSize,
|
|
310
|
-
maxStorageBufferBindingSize: adapterLimits.maxStorageBufferBindingSize,
|
|
311
|
-
maxColorAttachmentBytesPerSample: 128,
|
|
312
|
-
}
|
|
313
|
-
} );
|
|
314
|
-
|
|
315
|
-
window.renderer = this.renderer; // For debugging
|
|
316
|
-
|
|
317
|
-
await this.renderer.init();
|
|
318
|
-
|
|
319
|
-
// Initialize LTC textures required by RectAreaLight in WebGPU renderer
|
|
320
|
-
RectAreaLightNode.setLTC( RectAreaLightTexturesLib.init() );
|
|
321
|
-
|
|
322
|
-
this.renderer.toneMapping = ACESFilmicToneMapping;
|
|
323
|
-
this.renderer.toneMappingExposure = 1.0;
|
|
324
|
-
|
|
325
|
-
const width = this.canvas.clientWidth;
|
|
326
|
-
const height = this.canvas.clientHeight;
|
|
327
|
-
this.renderer.setPixelRatio( 1.0 );
|
|
328
|
-
|
|
329
|
-
// Setup camera
|
|
330
|
-
this._camera = new PerspectiveCamera( 60, width / height || 1, 0.01, 1000 );
|
|
331
|
-
this._camera.position.set( 0, 0, 5 );
|
|
332
|
-
|
|
333
|
-
// Create scenes
|
|
334
|
-
this.scene = new Scene();
|
|
335
|
-
this.meshScene = new Scene();
|
|
336
|
-
this._sceneHelpers = new SceneHelpers();
|
|
337
|
-
|
|
338
|
-
// Setup orbit controls
|
|
339
|
-
this._controls = new OrbitControls( this._camera, this.canvas );
|
|
340
|
-
this._controls.screenSpacePanning = true;
|
|
341
|
-
this._controls.zoomToCursor = true;
|
|
342
|
-
this._controls.saveState();
|
|
343
|
-
|
|
344
|
-
// Asset pipeline
|
|
345
|
-
this._sdf = new SceneProcessor();
|
|
346
|
-
this.assetLoader = new AssetLoader( this.meshScene, this._camera, this._controls );
|
|
347
|
-
this._setupFloorPlane();
|
|
348
|
-
this.assetLoader.setFloorPlane( this._floorPlane );
|
|
349
|
-
|
|
350
|
-
// Track camera movement for reset
|
|
351
|
-
this._controls.addEventListener( 'change', () => {
|
|
352
|
-
|
|
353
|
-
this.needsReset = true;
|
|
354
|
-
this.wake();
|
|
355
|
-
|
|
356
|
-
} );
|
|
357
|
-
|
|
358
|
-
// ── Create pipeline stages ──
|
|
359
|
-
this._createStages();
|
|
360
|
-
|
|
361
|
-
// ── Pipeline orchestration ──
|
|
362
|
-
const { clientWidth: w, clientHeight: h } = this.canvas;
|
|
363
|
-
this.pipeline = new RenderPipeline( this.renderer, w || 1, h || 1 );
|
|
364
|
-
this.pipeline.addStage( this.stages.pathTracer );
|
|
365
|
-
this.pipeline.addStage( this.stages.normalDepth );
|
|
366
|
-
this.pipeline.addStage( this.stages.motionVector );
|
|
367
|
-
this.pipeline.addStage( this.stages.ssrc );
|
|
368
|
-
this.pipeline.addStage( this.stages.asvgf );
|
|
369
|
-
this.pipeline.addStage( this.stages.variance );
|
|
370
|
-
this.pipeline.addStage( this.stages.bilateralFilter );
|
|
371
|
-
this.pipeline.addStage( this.stages.adaptiveSampling );
|
|
372
|
-
this.pipeline.addStage( this.stages.edgeFilter );
|
|
373
|
-
this.pipeline.addStage( this.stages.autoExposure );
|
|
374
|
-
this.pipeline.addStage( this.stages.display );
|
|
375
|
-
|
|
376
|
-
// Set initial render dimensions
|
|
377
|
-
const initRenderW = width || 1;
|
|
378
|
-
const initRenderH = height || 1;
|
|
379
|
-
this.pipeline.setSize( initRenderW, initRenderH );
|
|
380
|
-
this._lastRenderWidth = initRenderW;
|
|
381
|
-
this._lastRenderHeight = initRenderH;
|
|
382
|
-
|
|
383
|
-
// ── Interaction manager ──
|
|
384
|
-
this._interactionManager = new InteractionManager( {
|
|
385
|
-
scene: this.meshScene,
|
|
386
|
-
camera: this._camera,
|
|
387
|
-
canvas: this.canvas,
|
|
388
|
-
assetLoader: this.assetLoader,
|
|
389
|
-
pathTracer: null,
|
|
390
|
-
floorPlane: this._floorPlane
|
|
391
|
-
} );
|
|
392
|
-
this._setupInteractionListeners();
|
|
393
|
-
|
|
394
|
-
// ── Managers ──
|
|
395
|
-
this.cameraManager = new CameraManager( this._camera, this._controls, this._interactionManager );
|
|
396
|
-
this.lightManager = new LightManager( this.scene, this._sceneHelpers, this.stages.pathTracer );
|
|
397
|
-
this._createDenoiserCanvas();
|
|
398
|
-
this._setupDenoisingManager();
|
|
399
|
-
this._setupOverlayManager();
|
|
400
|
-
|
|
401
|
-
// ── Transform controls ──
|
|
402
|
-
this._transformManager = new TransformManager( {
|
|
403
|
-
camera: this._camera,
|
|
404
|
-
canvas: this.canvas,
|
|
405
|
-
orbitControls: this._controls,
|
|
406
|
-
app: this,
|
|
407
|
-
} );
|
|
408
|
-
|
|
409
|
-
// Wire CameraManager events → app events
|
|
410
|
-
this.cameraManager.addEventListener( 'CameraSwitched', ( e ) => this.dispatchEvent( e ) );
|
|
411
|
-
this.cameraManager.addEventListener( EngineEvents.AUTO_FOCUS_UPDATED, ( e ) => this.dispatchEvent( e ) );
|
|
412
|
-
|
|
413
|
-
// Wire DenoisingManager events → app events
|
|
414
|
-
this._forwardEvents( this.denoisingManager, [
|
|
415
|
-
EngineEvents.DENOISING_START, EngineEvents.DENOISING_END,
|
|
416
|
-
EngineEvents.UPSCALING_START, EngineEvents.UPSCALING_PROGRESS, EngineEvents.UPSCALING_END,
|
|
417
|
-
'resolution_changed',
|
|
418
|
-
] );
|
|
419
|
-
|
|
420
|
-
// Set up auto-exposure event listener
|
|
421
|
-
this._setupAutoExposureListener();
|
|
422
|
-
|
|
423
|
-
// ── Stable auto-focus context (avoid per-frame allocation) ──
|
|
424
|
-
this._autoFocusContext = {
|
|
425
|
-
meshScene: this.meshScene,
|
|
426
|
-
assetLoader: this.assetLoader,
|
|
427
|
-
floorPlane: this._floorPlane,
|
|
428
|
-
get currentFocusDistance() {
|
|
429
|
-
|
|
430
|
-
return null;
|
|
431
|
-
|
|
432
|
-
}, // replaced below
|
|
433
|
-
pathTracer: this.stages.pathTracer,
|
|
434
|
-
setFocusDistance: ( d ) => this.settings.set( 'focusDistance', d, { silent: true } ),
|
|
435
|
-
softReset: () => this.reset( true ),
|
|
436
|
-
hardReset: () => this.reset(),
|
|
437
|
-
};
|
|
438
|
-
|
|
439
|
-
// Use a getter so currentFocusDistance reads live value without allocation
|
|
440
|
-
const settingsRef = this.settings;
|
|
441
|
-
Object.defineProperty( this._autoFocusContext, 'currentFocusDistance', {
|
|
442
|
-
get: () => settingsRef.get( 'focusDistance' ),
|
|
443
|
-
} );
|
|
444
|
-
|
|
445
|
-
// ── Bind RenderSettings ──
|
|
446
|
-
this.settings.bind( {
|
|
447
|
-
pathTracer: this.stages.pathTracer,
|
|
448
|
-
resetCallback: () => this.reset(),
|
|
449
|
-
handlers: this._buildSettingsHandlers(),
|
|
450
|
-
delegates: {},
|
|
451
|
-
} );
|
|
452
|
-
|
|
453
|
-
// ── Resize handling ──
|
|
454
|
-
this.onResize();
|
|
455
|
-
this.resizeHandler = () => this.onResize();
|
|
456
|
-
if ( this._autoResize ) {
|
|
457
|
-
|
|
458
|
-
window.addEventListener( 'resize', this.resizeHandler );
|
|
459
|
-
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// ── Asset load events ──
|
|
463
|
-
this._onAssetLoaded = async ( event ) => {
|
|
464
|
-
|
|
465
|
-
if ( this._loadingInProgress ) return;
|
|
466
|
-
|
|
467
|
-
if ( event.model ) {
|
|
468
|
-
|
|
469
|
-
await this.loadSceneData();
|
|
470
|
-
|
|
471
|
-
} else if ( event.texture ) {
|
|
472
|
-
|
|
473
|
-
const envTexture = this.meshScene.environment;
|
|
474
|
-
if ( envTexture && this.stages.pathTracer ) {
|
|
475
|
-
|
|
476
|
-
await this.stages.pathTracer.environment.setEnvironmentMap( envTexture );
|
|
477
|
-
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
resetLoading();
|
|
481
|
-
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
this.pauseRendering = false;
|
|
485
|
-
this.reset();
|
|
486
|
-
|
|
487
|
-
};
|
|
488
|
-
|
|
489
|
-
this.assetLoader.addEventListener( 'load', this._onAssetLoaded );
|
|
490
|
-
|
|
491
|
-
this.assetLoader.addEventListener( 'modelProcessed', ( event ) => {
|
|
492
|
-
|
|
493
|
-
const cameras = [ this._camera, ...( event.cameras || [] ) ];
|
|
494
|
-
this.cameraManager.setCameras( cameras );
|
|
495
|
-
|
|
496
|
-
this._floorPlane = this.assetLoader.floorPlane;
|
|
497
|
-
if ( this._interactionManager ) {
|
|
498
|
-
|
|
499
|
-
this._interactionManager.floorPlane = this._floorPlane;
|
|
500
|
-
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
} );
|
|
143
|
+
await this._initRenderer();
|
|
144
|
+
this._initCameraManager();
|
|
145
|
+
this._initScenes();
|
|
146
|
+
this._initAssetPipeline();
|
|
147
|
+
this._initPipeline();
|
|
148
|
+
this._initManagers();
|
|
149
|
+
this._wireEvents();
|
|
504
150
|
|
|
505
151
|
// Seed path tracer with minimal empty scene data
|
|
506
152
|
this.stages.pathTracer.setTriangleData( new Float32Array( 32 ), 0 );
|
|
@@ -508,7 +154,6 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
508
154
|
this.stages.pathTracer.materialData.setMaterialData( new Float32Array( 16 ) );
|
|
509
155
|
this.stages.pathTracer.setupMaterial();
|
|
510
156
|
|
|
511
|
-
// Setup stats panel
|
|
512
157
|
if ( this._showStats ) this._initStats();
|
|
513
158
|
|
|
514
159
|
this.isInitialized = true;
|
|
@@ -523,7 +168,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
523
168
|
*/
|
|
524
169
|
animate() {
|
|
525
170
|
|
|
526
|
-
this.
|
|
171
|
+
this.animationManagerId = requestAnimationFrame( () => this.animate() );
|
|
527
172
|
|
|
528
173
|
if ( this._loadingInProgress || this._sdf?.isProcessing ) {
|
|
529
174
|
|
|
@@ -532,7 +177,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
532
177
|
|
|
533
178
|
}
|
|
534
179
|
|
|
535
|
-
if ( this.
|
|
180
|
+
if ( this.cameraManager.controls ) this.cameraManager.controls.update();
|
|
536
181
|
|
|
537
182
|
// Animation playback: compute skinned positions and refit BVH.
|
|
538
183
|
// Guard prevents overlapping async refits (fire-and-forget with 1-frame latency).
|
|
@@ -561,12 +206,12 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
561
206
|
|
|
562
207
|
}
|
|
563
208
|
|
|
564
|
-
this.
|
|
209
|
+
this.cameraManager.camera.updateMatrixWorld();
|
|
565
210
|
|
|
566
211
|
// Raster fallback when path tracer is disabled
|
|
567
212
|
if ( ! this.pathTracerEnabled ) {
|
|
568
213
|
|
|
569
|
-
this.renderer.render( this.meshScene, this.
|
|
214
|
+
this.renderer.render( this.meshScene, this.cameraManager.camera );
|
|
570
215
|
this._renderHelperOverlay();
|
|
571
216
|
return;
|
|
572
217
|
|
|
@@ -575,12 +220,12 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
575
220
|
if ( this.pauseRendering ) return;
|
|
576
221
|
|
|
577
222
|
// Auto-focus: compute focus distance before rendering
|
|
578
|
-
this.cameraManager.updateAutoFocus(
|
|
223
|
+
this.cameraManager.updateAutoFocus();
|
|
579
224
|
|
|
580
225
|
// Render path tracing
|
|
581
226
|
if ( this.stages.pathTracer?.isReady ) {
|
|
582
227
|
|
|
583
|
-
if ( this.stages.pathTracer.isComplete && this.
|
|
228
|
+
if ( this.stages.pathTracer.isComplete && this.completion.renderCompleteDispatched ) {
|
|
584
229
|
|
|
585
230
|
if ( this._needsDisplayRefresh ) {
|
|
586
231
|
|
|
@@ -600,28 +245,24 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
600
245
|
|
|
601
246
|
if ( ! this.stages.pathTracer.isComplete ) {
|
|
602
247
|
|
|
603
|
-
this.
|
|
248
|
+
this.completion.updateTime();
|
|
604
249
|
|
|
605
250
|
}
|
|
606
251
|
|
|
607
|
-
updateStats( { timeElapsed: this.timeElapsed, samples: getDisplaySamples( this.stages.pathTracer ) } );
|
|
252
|
+
updateStats( { timeElapsed: this.completion.timeElapsed, samples: getDisplaySamples( this.stages.pathTracer ) } );
|
|
608
253
|
|
|
609
254
|
// Check time limit
|
|
610
|
-
|
|
611
|
-
const renderTimeLimit = this.settings.get( 'renderTimeLimit' );
|
|
612
|
-
if ( renderLimitMode === 'time' && renderTimeLimit > 0 && this.timeElapsed >= renderTimeLimit ) {
|
|
255
|
+
if ( this.completion.isTimeLimitReached( this.settings.get( 'renderLimitMode' ), this.settings.get( 'renderTimeLimit' ) ) ) {
|
|
613
256
|
|
|
614
257
|
this.stages.pathTracer.isComplete = true;
|
|
615
258
|
|
|
616
259
|
}
|
|
617
260
|
|
|
618
261
|
// Render completion → denoise/upscale chain
|
|
619
|
-
if ( this.stages.pathTracer.isComplete &&
|
|
620
|
-
|
|
621
|
-
this._renderCompleteDispatched = true;
|
|
262
|
+
if ( this.stages.pathTracer.isComplete && this.completion.markComplete() ) {
|
|
622
263
|
|
|
623
264
|
this.denoisingManager.onRenderComplete( {
|
|
624
|
-
isStillComplete: () => this.
|
|
265
|
+
isStillComplete: () => this.completion.renderCompleteDispatched,
|
|
625
266
|
context: this.pipeline?.context,
|
|
626
267
|
} );
|
|
627
268
|
|
|
@@ -645,10 +286,10 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
645
286
|
*/
|
|
646
287
|
stopAnimation() {
|
|
647
288
|
|
|
648
|
-
if ( this.
|
|
289
|
+
if ( this.animationManagerId ) {
|
|
649
290
|
|
|
650
|
-
cancelAnimationFrame( this.
|
|
651
|
-
this.
|
|
291
|
+
cancelAnimationFrame( this.animationManagerId );
|
|
292
|
+
this.animationManagerId = null;
|
|
652
293
|
|
|
653
294
|
}
|
|
654
295
|
|
|
@@ -657,7 +298,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
657
298
|
/** Wakes the animation loop if it was stopped due to idle. */
|
|
658
299
|
wake() {
|
|
659
300
|
|
|
660
|
-
if ( ! this.
|
|
301
|
+
if ( ! this.animationManagerId && this.isInitialized && ! this._paused ) this.animate();
|
|
661
302
|
|
|
662
303
|
}
|
|
663
304
|
|
|
@@ -674,7 +315,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
674
315
|
resume() {
|
|
675
316
|
|
|
676
317
|
this._paused = false;
|
|
677
|
-
if ( ! this.
|
|
318
|
+
if ( ! this.animationManagerId ) this.animate();
|
|
678
319
|
if ( this._stats ) this._stats.dom.style.display = '';
|
|
679
320
|
|
|
680
321
|
}
|
|
@@ -692,29 +333,18 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
692
333
|
|
|
693
334
|
}
|
|
694
335
|
|
|
695
|
-
// Abort post-processing
|
|
336
|
+
// Abort post-processing and restore denoiser canvas resolution
|
|
696
337
|
this.denoisingManager?.abort( this.canvas );
|
|
697
338
|
|
|
698
|
-
|
|
699
|
-
if ( this.denoiserCanvas && this._lastRenderWidth && this._lastRenderHeight ) {
|
|
700
|
-
|
|
701
|
-
const wasResized = this.denoiserCanvas.width !== this._lastRenderWidth
|
|
702
|
-
|| this.denoiserCanvas.height !== this._lastRenderHeight;
|
|
703
|
-
|
|
704
|
-
this.denoiserCanvas.width = this._lastRenderWidth;
|
|
705
|
-
this.denoiserCanvas.height = this._lastRenderHeight;
|
|
339
|
+
if ( this.denoisingManager?.restoreBaseResolution() ) {
|
|
706
340
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
}
|
|
341
|
+
const w = this.denoisingManager._lastRenderWidth;
|
|
342
|
+
const h = this.denoisingManager._lastRenderHeight;
|
|
343
|
+
this.dispatchEvent( { type: 'resolution_changed', width: w, height: h } );
|
|
712
344
|
|
|
713
345
|
}
|
|
714
346
|
|
|
715
|
-
this.
|
|
716
|
-
this.lastResetTime = performance.now();
|
|
717
|
-
this._renderCompleteDispatched = false;
|
|
347
|
+
this.completion.reset();
|
|
718
348
|
this.wake();
|
|
719
349
|
this.dispatchEvent( { type: 'RenderReset' } );
|
|
720
350
|
this.dispatchEvent( { type: EngineEvents.RENDER_RESET } );
|
|
@@ -736,21 +366,13 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
736
366
|
|
|
737
367
|
}
|
|
738
368
|
|
|
739
|
-
this.
|
|
369
|
+
this.transformManager?.dispose();
|
|
740
370
|
this.overlayManager?.dispose();
|
|
741
371
|
this._sceneHelpers?.clear();
|
|
742
372
|
this.denoisingManager?.dispose();
|
|
743
|
-
|
|
744
|
-
if ( this.denoiserCanvas?.parentNode ) {
|
|
745
|
-
|
|
746
|
-
this.denoiserCanvas.parentNode.removeChild( this.denoiserCanvas );
|
|
747
|
-
this.denoiserCanvas = null;
|
|
748
|
-
|
|
749
|
-
}
|
|
750
|
-
|
|
751
373
|
this.pipeline?.dispose();
|
|
752
|
-
this.
|
|
753
|
-
this.
|
|
374
|
+
this.interactionManager?.dispose();
|
|
375
|
+
this.cameraManager?.dispose();
|
|
754
376
|
this.renderer?.dispose();
|
|
755
377
|
|
|
756
378
|
if ( this._stats ) {
|
|
@@ -878,6 +500,9 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
878
500
|
*/
|
|
879
501
|
async loadSceneData() {
|
|
880
502
|
|
|
503
|
+
// Clear selection before rebuilding — the old object leaves the scene graph
|
|
504
|
+
this.interactionManager?.deselect();
|
|
505
|
+
|
|
881
506
|
// Stop any running animation before rebuilding scene data
|
|
882
507
|
this.animationManager.dispose();
|
|
883
508
|
this._animRefitInFlight = false;
|
|
@@ -901,90 +526,24 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
901
526
|
await this._sdf.buildBVH( this.meshScene );
|
|
902
527
|
timer.end( 'BVH build (SceneProcessor)' );
|
|
903
528
|
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
if ( ! triangleData ) {
|
|
907
|
-
|
|
908
|
-
console.error( 'PathTracerApp: Failed to get triangle data' );
|
|
909
|
-
return false;
|
|
910
|
-
|
|
911
|
-
}
|
|
912
|
-
|
|
529
|
+
// Transfer geometry, materials, and textures to GPU
|
|
913
530
|
updateLoading( { status: "Transferring data to GPU...", progress: 86 } );
|
|
914
531
|
await new Promise( r => setTimeout( r, 0 ) );
|
|
915
532
|
timer.start( 'GPU data transfer' );
|
|
916
|
-
this.stages.pathTracer.setTriangleData( triangleData, triangleCount );
|
|
917
533
|
|
|
918
|
-
if ( !
|
|
534
|
+
if ( ! this._sdf.uploadToPathTracer( this.stages.pathTracer, this.lightManager, this.meshScene, environmentTexture ) ) return false;
|
|
919
535
|
|
|
920
|
-
|
|
921
|
-
return false;
|
|
536
|
+
timer.end( 'GPU data transfer' );
|
|
922
537
|
|
|
923
|
-
|
|
538
|
+
// Compile shaders
|
|
539
|
+
updateLoading( { status: "Compiling shaders...", progress: 90 } );
|
|
540
|
+
await new Promise( r => setTimeout( r, 0 ) );
|
|
541
|
+
timer.start( 'Material setup (TSL compile)' );
|
|
542
|
+
this.stages.pathTracer.setupMaterial();
|
|
543
|
+
timer.end( 'Material setup (TSL compile)' );
|
|
924
544
|
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
if ( materialData ) {
|
|
928
|
-
|
|
929
|
-
this.stages.pathTracer.materialData.setMaterialData( materialData );
|
|
930
|
-
|
|
931
|
-
} else {
|
|
932
|
-
|
|
933
|
-
console.warn( 'PathTracerApp: No material data, using defaults' );
|
|
934
|
-
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
if ( environmentTexture ) {
|
|
938
|
-
|
|
939
|
-
this.stages.pathTracer.environment.setEnvironmentTexture( environmentTexture );
|
|
940
|
-
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
// Transfer material texture arrays
|
|
944
|
-
this.stages.pathTracer.materialData.setMaterialTextures( {
|
|
945
|
-
albedoMaps: this._sdf.albedoTextures,
|
|
946
|
-
normalMaps: this._sdf.normalTextures,
|
|
947
|
-
bumpMaps: this._sdf.bumpTextures,
|
|
948
|
-
roughnessMaps: this._sdf.roughnessTextures,
|
|
949
|
-
metalnessMaps: this._sdf.metalnessTextures,
|
|
950
|
-
emissiveMaps: this._sdf.emissiveTextures,
|
|
951
|
-
displacementMaps: this._sdf.displacementTextures,
|
|
952
|
-
} );
|
|
953
|
-
|
|
954
|
-
// Emissive triangle data
|
|
955
|
-
if ( this._sdf.emissiveTriangleData ) {
|
|
956
|
-
|
|
957
|
-
this.stages.pathTracer.setEmissiveTriangleData(
|
|
958
|
-
this._sdf.emissiveTriangleData,
|
|
959
|
-
this._sdf.emissiveTriangleCount,
|
|
960
|
-
this._sdf.emissiveTotalPower,
|
|
961
|
-
);
|
|
962
|
-
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
// Light BVH data
|
|
966
|
-
if ( this._sdf.lightBVHNodeData ) {
|
|
967
|
-
|
|
968
|
-
this.stages.pathTracer.setLightBVHData(
|
|
969
|
-
this._sdf.lightBVHNodeData,
|
|
970
|
-
this._sdf.lightBVHNodeCount,
|
|
971
|
-
);
|
|
972
|
-
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
// Transfer lights
|
|
976
|
-
this.lightManager.transferSceneLights( this.meshScene );
|
|
977
|
-
timer.end( 'GPU data transfer' );
|
|
978
|
-
|
|
979
|
-
// Compile shaders
|
|
980
|
-
updateLoading( { status: "Compiling shaders...", progress: 90 } );
|
|
981
|
-
await new Promise( r => setTimeout( r, 0 ) );
|
|
982
|
-
timer.start( 'Material setup (TSL compile)' );
|
|
983
|
-
this.stages.pathTracer.setupMaterial();
|
|
984
|
-
timer.end( 'Material setup (TSL compile)' );
|
|
985
|
-
|
|
986
|
-
// Wait for CDF
|
|
987
|
-
if ( cdfPromise ) {
|
|
545
|
+
// Wait for CDF
|
|
546
|
+
if ( cdfPromise ) {
|
|
988
547
|
|
|
989
548
|
updateLoading( { status: "Finalizing environment map...", progress: 95 } );
|
|
990
549
|
await cdfPromise;
|
|
@@ -1001,25 +560,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1001
560
|
timer.print();
|
|
1002
561
|
resetLoading();
|
|
1003
562
|
|
|
1004
|
-
|
|
1005
|
-
// scene = meshScene (for full matrixWorld updates including parent chain)
|
|
1006
|
-
// mixerRoot = targetModel (GLTF model root, for animation track name resolution)
|
|
1007
|
-
const animations = this.assetLoader?.animations || [];
|
|
1008
|
-
if ( animations.length > 0 ) {
|
|
1009
|
-
|
|
1010
|
-
const mixerRoot = this.assetLoader?.targetModel || this.meshScene;
|
|
1011
|
-
this.animationManager.init( this.meshScene, mixerRoot, this._sdf.meshes, animations, this._sdf.triangleCount );
|
|
1012
|
-
this.animationManager.onFinished = () => {
|
|
1013
|
-
|
|
1014
|
-
this._animRefitInFlight = false;
|
|
1015
|
-
this.dispatchEvent( { type: EngineEvents.ANIMATION_FINISHED } );
|
|
1016
|
-
|
|
1017
|
-
};
|
|
1018
|
-
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
// Initialize transform manager mesh data for BVH refit on object transforms
|
|
1022
|
-
this._transformManager?.setMeshData( this._sdf.meshes, this._sdf.triangleCount );
|
|
563
|
+
this._initAnimationAndTransforms();
|
|
1023
564
|
|
|
1024
565
|
this.dispatchEvent( { type: 'SceneRebuild' } );
|
|
1025
566
|
return true;
|
|
@@ -1066,36 +607,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1066
607
|
|
|
1067
608
|
const result = this._sdf.refitBLASes( affectedMeshIndices, newPositions, newNormals );
|
|
1068
609
|
|
|
1069
|
-
|
|
1070
|
-
const instanceTable = this._sdf.instanceTable;
|
|
1071
|
-
const triRanges = [];
|
|
1072
|
-
const bvhRanges = [];
|
|
1073
|
-
const FPT = 32; // FLOATS_PER_TRIANGLE
|
|
1074
|
-
const FPN = 16; // FLOATS_PER_NODE
|
|
1075
|
-
|
|
1076
|
-
for ( const meshIdx of affectedMeshIndices ) {
|
|
1077
|
-
|
|
1078
|
-
const entry = instanceTable.entries[ meshIdx ];
|
|
1079
|
-
if ( ! entry ) continue;
|
|
1080
|
-
|
|
1081
|
-
triRanges.push( {
|
|
1082
|
-
offset: entry.triOffset * FPT,
|
|
1083
|
-
count: entry.triCount * FPT
|
|
1084
|
-
} );
|
|
1085
|
-
|
|
1086
|
-
bvhRanges.push( {
|
|
1087
|
-
offset: entry.blasOffset * FPN,
|
|
1088
|
-
count: entry.blasNodeCount * FPN
|
|
1089
|
-
} );
|
|
1090
|
-
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
// Always include TLAS range (rebuilt on every refit)
|
|
1094
|
-
bvhRanges.push( {
|
|
1095
|
-
offset: 0,
|
|
1096
|
-
count: instanceTable.tlasNodeCount * FPN
|
|
1097
|
-
} );
|
|
1098
|
-
|
|
610
|
+
const { triRanges, bvhRanges } = this._sdf.computeBLASDirtyRanges( affectedMeshIndices );
|
|
1099
611
|
this.stages.pathTracer.updateBufferRanges( triRanges, bvhRanges );
|
|
1100
612
|
this.reset();
|
|
1101
613
|
|
|
@@ -1113,95 +625,10 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1113
625
|
|
|
1114
626
|
}
|
|
1115
627
|
|
|
1116
|
-
/**
|
|
1117
|
-
* Start playing a GLTF animation clip.
|
|
1118
|
-
* @param {number} [clipIndex=0] - Clip index, or -1 to play all
|
|
1119
|
-
*/
|
|
1120
|
-
/** @internal Use engine.animation.play() */
|
|
1121
|
-
playAnimation( clipIndex = 0 ) {
|
|
1122
|
-
|
|
1123
|
-
if ( ! this.animationManager?.hasAnimations ) {
|
|
1124
|
-
|
|
1125
|
-
console.warn( 'playAnimation: No animation clips available' );
|
|
1126
|
-
return;
|
|
1127
|
-
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
this.animationManager.play( clipIndex );
|
|
1131
|
-
this.wake();
|
|
1132
|
-
this.dispatchEvent( { type: EngineEvents.ANIMATION_STARTED, clipIndex } );
|
|
1133
|
-
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
/**
|
|
1137
|
-
* @internal Use engine.animation.pause()
|
|
1138
|
-
* Pause animation — preserves current time position.
|
|
1139
|
-
*/
|
|
1140
|
-
pauseAnimation() {
|
|
1141
|
-
|
|
1142
|
-
this.animationManager?.pause();
|
|
1143
|
-
this._animRefitInFlight = false;
|
|
1144
|
-
this.dispatchEvent( { type: EngineEvents.ANIMATION_PAUSED } );
|
|
1145
|
-
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
/**
|
|
1149
|
-
* @internal Use engine.animation.resume()
|
|
1150
|
-
*/
|
|
1151
|
-
resumeAnimation() {
|
|
1152
|
-
|
|
1153
|
-
this.animationManager?.resume();
|
|
1154
|
-
this.wake();
|
|
1155
|
-
this.dispatchEvent( { type: EngineEvents.ANIMATION_STARTED } );
|
|
1156
|
-
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
/**
|
|
1160
|
-
* @internal Use engine.animation.stop()
|
|
1161
|
-
*/
|
|
1162
|
-
stopAnimationPlayback() {
|
|
1163
|
-
|
|
1164
|
-
this.animationManager?.stop();
|
|
1165
|
-
this._animRefitInFlight = false;
|
|
1166
|
-
this.dispatchEvent( { type: EngineEvents.ANIMATION_STOPPED } );
|
|
1167
|
-
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
/**
|
|
1171
|
-
* @internal Use engine.animation.setSpeed()
|
|
1172
|
-
* @param {number} speed
|
|
1173
|
-
*/
|
|
1174
|
-
setAnimationSpeed( speed ) {
|
|
1175
|
-
|
|
1176
|
-
this.animationManager?.setSpeed( speed );
|
|
1177
|
-
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
/**
|
|
1181
|
-
* @internal Use engine.animation.setLoop()
|
|
1182
|
-
* @param {boolean} loop
|
|
1183
|
-
*/
|
|
1184
|
-
setAnimationLoop( loop ) {
|
|
1185
|
-
|
|
1186
|
-
this.animationManager?.setLoop( loop );
|
|
1187
|
-
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
/**
|
|
1191
|
-
* @internal Use engine.animation.clips
|
|
1192
|
-
* @returns {{ index: number, name: string, duration: number }[]}
|
|
1193
|
-
*/
|
|
1194
|
-
get animationClips() {
|
|
1195
|
-
|
|
1196
|
-
return this.animationManager?.clips || [];
|
|
1197
|
-
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
628
|
// ═══════════════════════════════════════════════════════════════
|
|
1201
629
|
// Resize
|
|
1202
630
|
// ═══════════════════════════════════════════════════════════════
|
|
1203
631
|
|
|
1204
|
-
/** @internal Use engine.output.resize() */
|
|
1205
632
|
onResize() {
|
|
1206
633
|
|
|
1207
634
|
const width = this.canvas.clientWidth;
|
|
@@ -1210,15 +637,10 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1210
637
|
|
|
1211
638
|
this.renderer.setPixelRatio( 1.0 );
|
|
1212
639
|
this.renderer.setSize( width, height, false );
|
|
1213
|
-
this.
|
|
1214
|
-
this.
|
|
640
|
+
this.cameraManager.camera.aspect = width / height;
|
|
641
|
+
this.cameraManager.camera.updateProjectionMatrix();
|
|
1215
642
|
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
this.denoiserCanvas.style.width = `${width}px`;
|
|
1219
|
-
this.denoiserCanvas.style.height = `${height}px`;
|
|
1220
|
-
|
|
1221
|
-
}
|
|
643
|
+
this.denoisingManager?.syncCanvasStyle( width, height );
|
|
1222
644
|
|
|
1223
645
|
// Overlay helpers always render at display resolution
|
|
1224
646
|
const dpr = window.devicePixelRatio || 1;
|
|
@@ -1227,7 +649,9 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1227
649
|
Math.round( height * dpr )
|
|
1228
650
|
);
|
|
1229
651
|
|
|
1230
|
-
|
|
652
|
+
const lastW = this.denoisingManager?._lastRenderWidth ?? 0;
|
|
653
|
+
const lastH = this.denoisingManager?._lastRenderHeight ?? 0;
|
|
654
|
+
if ( width === lastW && height === lastH ) return;
|
|
1231
655
|
|
|
1232
656
|
clearTimeout( this._resizeDebounceTimer );
|
|
1233
657
|
this._resizeDebounceTimer = setTimeout( () => {
|
|
@@ -1240,37 +664,26 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1240
664
|
|
|
1241
665
|
_applyRenderResize( renderWidth, renderHeight ) {
|
|
1242
666
|
|
|
1243
|
-
this._lastRenderWidth = renderWidth;
|
|
1244
|
-
this._lastRenderHeight = renderHeight;
|
|
1245
|
-
|
|
1246
667
|
this.pipeline?.setSize( renderWidth, renderHeight );
|
|
1247
|
-
this.denoisingManager?.
|
|
1248
|
-
this.denoisingManager?.upscaler?.setBaseSize( renderWidth, renderHeight );
|
|
668
|
+
this.denoisingManager?.setRenderSize( renderWidth, renderHeight );
|
|
1249
669
|
this.needsReset = true;
|
|
1250
670
|
|
|
1251
671
|
this.dispatchEvent( { type: 'resolution_changed', width: renderWidth, height: renderHeight } );
|
|
1252
672
|
|
|
1253
673
|
}
|
|
1254
674
|
|
|
1255
|
-
/** @internal Use engine.output.setSize() */
|
|
1256
675
|
setCanvasSize( width, height ) {
|
|
1257
676
|
|
|
1258
677
|
this.canvas.style.width = `${width}px`;
|
|
1259
678
|
this.canvas.style.height = `${height}px`;
|
|
1260
|
-
|
|
1261
|
-
if ( this.denoiserCanvas ) {
|
|
1262
|
-
|
|
1263
|
-
this.denoiserCanvas.style.width = `${width}px`;
|
|
1264
|
-
this.denoiserCanvas.style.height = `${height}px`;
|
|
1265
|
-
|
|
1266
|
-
}
|
|
679
|
+
this.denoisingManager?.syncCanvasStyle( width, height );
|
|
1267
680
|
|
|
1268
681
|
if ( width === 0 || height === 0 ) return;
|
|
1269
682
|
|
|
1270
683
|
this.renderer.setPixelRatio( 1.0 );
|
|
1271
684
|
this.renderer.setSize( width, height, false );
|
|
1272
|
-
this.
|
|
1273
|
-
this.
|
|
685
|
+
this.cameraManager.camera.aspect = width / height;
|
|
686
|
+
this.cameraManager.camera.updateProjectionMatrix();
|
|
1274
687
|
|
|
1275
688
|
clearTimeout( this._resizeDebounceTimer );
|
|
1276
689
|
this._applyRenderResize( width, height );
|
|
@@ -1291,7 +704,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1291
704
|
if ( mode === 'results' ) {
|
|
1292
705
|
|
|
1293
706
|
this.pauseRendering = true;
|
|
1294
|
-
this.
|
|
707
|
+
this.cameraManager.controls.enabled = false;
|
|
1295
708
|
this.renderer?.domElement && ( this.renderer.domElement.style.display = 'none' );
|
|
1296
709
|
this.denoisingManager?.denoiser?.output && ( this.denoisingManager.denoiser.output.style.display = 'none' );
|
|
1297
710
|
return;
|
|
@@ -1301,7 +714,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1301
714
|
const isFinal = mode === 'final-render';
|
|
1302
715
|
const config = isFinal ? FINAL_RENDER_CONFIG : PREVIEW_RENDER_CONFIG;
|
|
1303
716
|
|
|
1304
|
-
this.
|
|
717
|
+
this.cameraManager.controls.enabled = ! isFinal;
|
|
1305
718
|
|
|
1306
719
|
// Batch uniform updates via settings
|
|
1307
720
|
this.settings.setMany( {
|
|
@@ -1311,9 +724,17 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1311
724
|
transmissiveBounces: config.transmissiveBounces,
|
|
1312
725
|
}, { silent: true } );
|
|
1313
726
|
|
|
1314
|
-
this.
|
|
1315
|
-
this.setTileCount( config.tiles );
|
|
1316
|
-
|
|
727
|
+
this.stages.pathTracer?.setUniform( 'renderMode', parseInt( config.renderMode ) );
|
|
728
|
+
this.stages.pathTracer?.tileManager?.setTileCount( config.tiles );
|
|
729
|
+
|
|
730
|
+
const tileHelper = this.overlayManager?.getHelper( 'tiles' );
|
|
731
|
+
if ( tileHelper ) {
|
|
732
|
+
|
|
733
|
+
tileHelper.enabled = config.tilesHelper;
|
|
734
|
+
if ( ! config.tilesHelper ) tileHelper.hide();
|
|
735
|
+
|
|
736
|
+
}
|
|
737
|
+
|
|
1317
738
|
this.stages.pathTracer?.updateCompletionThreshold?.();
|
|
1318
739
|
|
|
1319
740
|
const denoiser = this.denoisingManager?.denoiser;
|
|
@@ -1342,751 +763,438 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1342
763
|
|
|
1343
764
|
}
|
|
1344
765
|
|
|
766
|
+
refreshFrame() {
|
|
767
|
+
|
|
768
|
+
this._needsDisplayRefresh = true;
|
|
769
|
+
this.wake();
|
|
770
|
+
|
|
771
|
+
}
|
|
772
|
+
|
|
1345
773
|
// ═══════════════════════════════════════════════════════════════
|
|
1346
|
-
//
|
|
774
|
+
// Output (absorbed from OutputAPI)
|
|
1347
775
|
// ═══════════════════════════════════════════════════════════════
|
|
1348
776
|
|
|
1349
|
-
/**
|
|
1350
|
-
|
|
777
|
+
/**
|
|
778
|
+
* Returns the canvas element with the final rendered image.
|
|
779
|
+
* Chooses the post-processing canvas when denoiser/upscaler are active.
|
|
780
|
+
* @returns {HTMLCanvasElement|null}
|
|
781
|
+
*/
|
|
782
|
+
getCanvas() {
|
|
1351
783
|
|
|
1352
|
-
this.
|
|
1353
|
-
index,
|
|
1354
|
-
this.settings.get( 'focusDistance' ),
|
|
1355
|
-
() => this.onResize(),
|
|
1356
|
-
() => this.reset()
|
|
1357
|
-
);
|
|
784
|
+
if ( ! this.renderer?.domElement ) return null;
|
|
1358
785
|
|
|
1359
|
-
|
|
786
|
+
const dm = this.denoisingManager;
|
|
787
|
+
const usePostProcess = ( dm?.denoiser?.enabled || dm?.upscaler?.enabled )
|
|
788
|
+
&& dm?.denoiserCanvas
|
|
789
|
+
&& this.stages.pathTracer?.isComplete;
|
|
790
|
+
|
|
791
|
+
if ( usePostProcess ) return dm.denoiserCanvas;
|
|
792
|
+
|
|
793
|
+
// Re-render display stage so the WebGPU canvas has valid content
|
|
794
|
+
if ( this.stages.display && this.pipeline?.context ) {
|
|
795
|
+
|
|
796
|
+
this.stages.display.render( this.pipeline.context );
|
|
1360
797
|
|
|
1361
|
-
|
|
1362
|
-
getCameraNames() {
|
|
798
|
+
}
|
|
1363
799
|
|
|
1364
|
-
return this.
|
|
800
|
+
return this.renderer.domElement;
|
|
1365
801
|
|
|
1366
802
|
}
|
|
1367
803
|
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
804
|
+
/**
|
|
805
|
+
* Downloads a PNG screenshot of the current render.
|
|
806
|
+
*/
|
|
807
|
+
screenshot() {
|
|
1371
808
|
|
|
1372
|
-
|
|
1373
|
-
|
|
809
|
+
const canvas = this.getCanvas();
|
|
810
|
+
if ( ! canvas ) return;
|
|
1374
811
|
|
|
1375
|
-
|
|
1376
|
-
this.reset();
|
|
1377
|
-
return descriptor;
|
|
812
|
+
try {
|
|
1378
813
|
|
|
1379
|
-
|
|
814
|
+
const data = canvas.toDataURL( 'image/png' );
|
|
815
|
+
const link = document.createElement( 'a' );
|
|
816
|
+
link.href = data;
|
|
817
|
+
link.download = 'screenshot.png';
|
|
818
|
+
link.click();
|
|
1380
819
|
|
|
1381
|
-
|
|
1382
|
-
|
|
820
|
+
} catch ( error ) {
|
|
821
|
+
|
|
822
|
+
console.error( 'Screenshot failed:', error );
|
|
1383
823
|
|
|
1384
|
-
|
|
1385
|
-
if ( removed ) this.reset();
|
|
1386
|
-
return removed;
|
|
824
|
+
}
|
|
1387
825
|
|
|
1388
826
|
}
|
|
1389
827
|
|
|
1390
|
-
/**
|
|
1391
|
-
|
|
828
|
+
/**
|
|
829
|
+
* Returns scene statistics (triangle count, mesh count, etc.).
|
|
830
|
+
* @returns {Object|null}
|
|
831
|
+
*/
|
|
832
|
+
getStatistics() {
|
|
833
|
+
|
|
834
|
+
try {
|
|
1392
835
|
|
|
1393
|
-
|
|
1394
|
-
this.reset();
|
|
836
|
+
return this._sdf?.getStatistics?.() ?? null;
|
|
1395
837
|
|
|
1396
|
-
|
|
838
|
+
} catch {
|
|
1397
839
|
|
|
1398
|
-
|
|
1399
|
-
getLights() {
|
|
840
|
+
return null;
|
|
1400
841
|
|
|
1401
|
-
|
|
842
|
+
}
|
|
1402
843
|
|
|
1403
844
|
}
|
|
1404
845
|
|
|
1405
|
-
/**
|
|
1406
|
-
|
|
846
|
+
/**
|
|
847
|
+
* Whether the path tracer has finished converging.
|
|
848
|
+
* @returns {boolean}
|
|
849
|
+
*/
|
|
850
|
+
isComplete() {
|
|
1407
851
|
|
|
1408
|
-
this.
|
|
852
|
+
return this.stages.pathTracer?.isComplete ?? false;
|
|
1409
853
|
|
|
1410
854
|
}
|
|
1411
855
|
|
|
1412
|
-
/**
|
|
1413
|
-
|
|
856
|
+
/**
|
|
857
|
+
* Returns the current accumulated frame/sample count.
|
|
858
|
+
* @returns {number}
|
|
859
|
+
*/
|
|
860
|
+
getFrameCount() {
|
|
1414
861
|
|
|
1415
|
-
this.
|
|
862
|
+
return this.stages.pathTracer?.frameCount || 0;
|
|
1416
863
|
|
|
1417
864
|
}
|
|
1418
865
|
|
|
1419
866
|
// ═══════════════════════════════════════════════════════════════
|
|
1420
|
-
//
|
|
867
|
+
// Materials (absorbed from MaterialsAPI)
|
|
1421
868
|
// ═══════════════════════════════════════════════════════════════
|
|
1422
869
|
|
|
1423
|
-
/**
|
|
1424
|
-
|
|
870
|
+
/**
|
|
871
|
+
* Updates a single material property and triggers emissive rebuild if needed.
|
|
872
|
+
* @param {number} materialIndex
|
|
873
|
+
* @param {string} property
|
|
874
|
+
* @param {*} value
|
|
875
|
+
*/
|
|
876
|
+
setMaterialProperty( materialIndex, property, value ) {
|
|
1425
877
|
|
|
1426
|
-
this.
|
|
1427
|
-
this.reset();
|
|
878
|
+
this.stages.pathTracer?.materialData.updateMaterialProperty( materialIndex, property, value );
|
|
1428
879
|
|
|
1429
|
-
|
|
880
|
+
const emissiveAffectingProps = [ 'emissive', 'emissiveIntensity', 'visible' ];
|
|
881
|
+
if ( emissiveAffectingProps.includes( property )
|
|
882
|
+
&& this.stages.pathTracer?.enableEmissiveTriangleSampling?.value ) {
|
|
1430
883
|
|
|
1431
|
-
|
|
1432
|
-
|
|
884
|
+
const result = this._sdf.updateMaterialEmissive( materialIndex, property, value );
|
|
885
|
+
if ( result ) {
|
|
1433
886
|
|
|
1434
|
-
|
|
1435
|
-
|
|
887
|
+
this.stages.pathTracer.setEmissiveTriangleData(
|
|
888
|
+
result.rawData, result.emissiveCount, result.totalPower,
|
|
889
|
+
);
|
|
1436
890
|
|
|
1437
|
-
|
|
891
|
+
}
|
|
1438
892
|
|
|
1439
|
-
|
|
1440
|
-
applyASVGFPreset( presetName ) {
|
|
893
|
+
}
|
|
1441
894
|
|
|
1442
|
-
this.denoisingManager.applyASVGFPreset( presetName );
|
|
1443
895
|
this.reset();
|
|
1444
896
|
|
|
1445
897
|
}
|
|
1446
898
|
|
|
1447
|
-
/**
|
|
1448
|
-
|
|
899
|
+
/**
|
|
900
|
+
* Updates a material's texture transform (offset, repeat, rotation).
|
|
901
|
+
* @param {number} materialIndex
|
|
902
|
+
* @param {string} textureName
|
|
903
|
+
* @param {Object} transform
|
|
904
|
+
*/
|
|
905
|
+
setTextureTransform( materialIndex, textureName, transform ) {
|
|
1449
906
|
|
|
1450
|
-
this.
|
|
907
|
+
this.stages.pathTracer?.materialData.updateTextureTransform( materialIndex, textureName, transform );
|
|
1451
908
|
this.reset();
|
|
1452
909
|
|
|
1453
910
|
}
|
|
1454
911
|
|
|
1455
|
-
/**
|
|
1456
|
-
|
|
912
|
+
/**
|
|
913
|
+
* Full material rebuild (required after texture changes).
|
|
914
|
+
* @param {import('three').Scene} [scene]
|
|
915
|
+
*/
|
|
916
|
+
async rebuildMaterials( scene ) {
|
|
1457
917
|
|
|
1458
|
-
this.
|
|
1459
|
-
this.
|
|
918
|
+
await this.stages.pathTracer?.rebuildMaterials( scene || this.meshScene );
|
|
919
|
+
this.reset();
|
|
1460
920
|
|
|
1461
921
|
}
|
|
1462
922
|
|
|
1463
923
|
// ═══════════════════════════════════════════════════════════════
|
|
1464
|
-
//
|
|
924
|
+
// Private — Initialization
|
|
1465
925
|
// ═══════════════════════════════════════════════════════════════
|
|
1466
926
|
|
|
1467
|
-
|
|
1468
|
-
|
|
927
|
+
async _initRenderer() {
|
|
928
|
+
|
|
929
|
+
setStatusCallback( ( event ) => this.dispatchEvent( event ) );
|
|
1469
930
|
|
|
1470
|
-
|
|
1471
|
-
if ( outlineHelper ) {
|
|
931
|
+
if ( ! navigator.gpu ) {
|
|
1472
932
|
|
|
1473
|
-
|
|
933
|
+
throw new Error( 'WebGPU is not supported in this browser' );
|
|
1474
934
|
|
|
1475
935
|
}
|
|
1476
936
|
|
|
1477
|
-
|
|
937
|
+
const adapter = await navigator.gpu.requestAdapter( { powerPreference: 'high-performance' } );
|
|
938
|
+
if ( ! adapter ) {
|
|
1478
939
|
|
|
1479
|
-
|
|
940
|
+
throw new Error( 'Failed to get WebGPU adapter' );
|
|
1480
941
|
|
|
1481
942
|
}
|
|
1482
943
|
|
|
1483
|
-
|
|
1484
|
-
if ( this._transformManager ) {
|
|
1485
|
-
|
|
1486
|
-
if ( object ) {
|
|
1487
|
-
|
|
1488
|
-
this._transformManager.attach( object );
|
|
944
|
+
const adapterLimits = adapter.limits;
|
|
1489
945
|
|
|
1490
|
-
|
|
946
|
+
this.renderer = new WebGPURenderer( {
|
|
947
|
+
canvas: this.canvas,
|
|
948
|
+
alpha: true,
|
|
949
|
+
powerPreference: 'high-performance',
|
|
950
|
+
requiredLimits: {
|
|
951
|
+
maxBufferSize: adapterLimits.maxBufferSize,
|
|
952
|
+
maxStorageBufferBindingSize: adapterLimits.maxStorageBufferBindingSize,
|
|
953
|
+
maxColorAttachmentBytesPerSample: 128,
|
|
954
|
+
}
|
|
955
|
+
} );
|
|
1491
956
|
|
|
1492
|
-
|
|
957
|
+
window.renderer = this.renderer; // For debugging
|
|
1493
958
|
|
|
1494
|
-
|
|
959
|
+
await this.renderer.init();
|
|
1495
960
|
|
|
1496
|
-
|
|
961
|
+
RectAreaLightNode.setLTC( RectAreaLightTexturesLib.init() );
|
|
1497
962
|
|
|
1498
|
-
this.
|
|
963
|
+
this.renderer.toneMapping = ACESFilmicToneMapping;
|
|
964
|
+
this.renderer.toneMappingExposure = 1.0;
|
|
965
|
+
this.renderer.setPixelRatio( 1.0 );
|
|
1499
966
|
|
|
1500
967
|
}
|
|
1501
968
|
|
|
1502
|
-
|
|
1503
|
-
toggleFocusMode() {
|
|
969
|
+
_initCameraManager() {
|
|
1504
970
|
|
|
1505
|
-
|
|
1506
|
-
const enabled = this._interactionManager.toggleFocusMode();
|
|
1507
|
-
if ( this._controls ) this._controls.enabled = ! enabled;
|
|
1508
|
-
return enabled;
|
|
971
|
+
this.cameraManager = new CameraManager( this.canvas );
|
|
1509
972
|
|
|
1510
973
|
}
|
|
1511
974
|
|
|
1512
|
-
|
|
1513
|
-
toggleSelectMode() {
|
|
975
|
+
_initScenes() {
|
|
1514
976
|
|
|
1515
|
-
|
|
1516
|
-
|
|
977
|
+
this.scene = new Scene();
|
|
978
|
+
this.meshScene = new Scene();
|
|
979
|
+
this._sceneHelpers = new SceneHelpers();
|
|
1517
980
|
|
|
1518
981
|
}
|
|
1519
982
|
|
|
1520
|
-
|
|
1521
|
-
disableSelectMode() {
|
|
983
|
+
_initAssetPipeline() {
|
|
1522
984
|
|
|
1523
|
-
this.
|
|
1524
|
-
this.
|
|
1525
|
-
|
|
1526
|
-
}
|
|
985
|
+
this._sdf = new SceneProcessor();
|
|
986
|
+
this.assetLoader = new AssetLoader( this.meshScene, this.cameraManager.camera, this.cameraManager.controls );
|
|
987
|
+
this.assetLoader.createFloorPlane();
|
|
1527
988
|
|
|
1528
|
-
|
|
1529
|
-
// Delegated APIs — Transform
|
|
1530
|
-
// ═══════════════════════════════════════════════════════════════
|
|
989
|
+
this.cameraManager.controls.addEventListener( 'change', () => {
|
|
1531
990
|
|
|
1532
|
-
|
|
1533
|
-
|
|
991
|
+
this.needsReset = true;
|
|
992
|
+
this.wake();
|
|
1534
993
|
|
|
1535
|
-
|
|
1536
|
-
this.dispatchEvent( { type: EngineEvents.TRANSFORM_MODE_CHANGED, mode } );
|
|
994
|
+
} );
|
|
1537
995
|
|
|
1538
996
|
}
|
|
1539
997
|
|
|
1540
|
-
|
|
1541
|
-
setTransformSpace( space ) {
|
|
998
|
+
_initPipeline() {
|
|
1542
999
|
|
|
1543
|
-
this.
|
|
1000
|
+
this._createStages();
|
|
1544
1001
|
|
|
1545
|
-
|
|
1002
|
+
const { clientWidth: w, clientHeight: h } = this.canvas;
|
|
1003
|
+
this.pipeline = new RenderPipeline( this.renderer, w || 1, h || 1 );
|
|
1546
1004
|
|
|
1547
|
-
|
|
1548
|
-
|
|
1005
|
+
this.pipeline.addStage( this.stages.pathTracer );
|
|
1006
|
+
this.pipeline.addStage( this.stages.normalDepth );
|
|
1007
|
+
this.pipeline.addStage( this.stages.motionVector );
|
|
1008
|
+
this.pipeline.addStage( this.stages.ssrc );
|
|
1009
|
+
this.pipeline.addStage( this.stages.asvgf );
|
|
1010
|
+
this.pipeline.addStage( this.stages.variance );
|
|
1011
|
+
this.pipeline.addStage( this.stages.bilateralFilter );
|
|
1012
|
+
this.pipeline.addStage( this.stages.adaptiveSampling );
|
|
1013
|
+
this.pipeline.addStage( this.stages.edgeFilter );
|
|
1014
|
+
this.pipeline.addStage( this.stages.autoExposure );
|
|
1015
|
+
this.pipeline.addStage( this.stages.display );
|
|
1549
1016
|
|
|
1550
|
-
|
|
1017
|
+
const initRenderW = this.canvas.clientWidth || 1;
|
|
1018
|
+
const initRenderH = this.canvas.clientHeight || 1;
|
|
1019
|
+
this.pipeline.setSize( initRenderW, initRenderH );
|
|
1551
1020
|
|
|
1552
1021
|
}
|
|
1553
1022
|
|
|
1554
|
-
|
|
1023
|
+
_initManagers() {
|
|
1555
1024
|
|
|
1556
|
-
this.
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
/** @internal Use engine.environment.params */
|
|
1566
|
-
getEnvParams() {
|
|
1567
|
-
|
|
1568
|
-
return this.stages.pathTracer?.environment?.envParams ?? null;
|
|
1569
|
-
|
|
1570
|
-
}
|
|
1571
|
-
|
|
1572
|
-
/** @internal Use engine.environment.texture */
|
|
1573
|
-
getEnvironmentTexture() {
|
|
1574
|
-
|
|
1575
|
-
return this.stages.pathTracer?.environment?.environmentTexture ?? null;
|
|
1576
|
-
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
|
-
/** @internal */
|
|
1580
|
-
getEnvironmentCDF() {
|
|
1581
|
-
|
|
1582
|
-
return null;
|
|
1025
|
+
this.interactionManager = new InteractionManager( {
|
|
1026
|
+
scene: this.meshScene,
|
|
1027
|
+
camera: this.cameraManager.camera,
|
|
1028
|
+
canvas: this.canvas,
|
|
1029
|
+
assetLoader: this.assetLoader,
|
|
1030
|
+
pathTracer: null,
|
|
1031
|
+
floorPlane: this.assetLoader.floorPlane
|
|
1032
|
+
} );
|
|
1583
1033
|
|
|
1584
|
-
|
|
1034
|
+
this.interactionManager.wireAppEvents( this );
|
|
1585
1035
|
|
|
1586
|
-
|
|
1587
|
-
|
|
1036
|
+
this.cameraManager.setInteractionManager( this.interactionManager );
|
|
1037
|
+
this.lightManager = new LightManager( this.scene, this._sceneHelpers, this.stages.pathTracer, {
|
|
1038
|
+
onReset: () => this.reset(),
|
|
1039
|
+
} );
|
|
1040
|
+
this._setupDenoisingManager();
|
|
1041
|
+
this._setupOverlayManager();
|
|
1588
1042
|
|
|
1589
|
-
|
|
1043
|
+
this.transformManager = new TransformManager( {
|
|
1044
|
+
camera: this.cameraManager.camera,
|
|
1045
|
+
canvas: this.canvas,
|
|
1046
|
+
orbitControls: this.cameraManager.controls,
|
|
1047
|
+
app: this,
|
|
1048
|
+
} );
|
|
1590
1049
|
|
|
1591
|
-
|
|
1050
|
+
// Wire cross-manager dependencies
|
|
1051
|
+
this.interactionManager.setDependencies( {
|
|
1052
|
+
overlayManager: this.overlayManager,
|
|
1053
|
+
transformManager: this.transformManager,
|
|
1054
|
+
appDispatch: ( e ) => this.dispatchEvent( e ),
|
|
1055
|
+
orbitControls: this.cameraManager.controls,
|
|
1056
|
+
} );
|
|
1592
1057
|
|
|
1593
|
-
|
|
1594
|
-
|
|
1058
|
+
this.denoisingManager.setOverlayManager( this.overlayManager );
|
|
1059
|
+
this.denoisingManager.setResetCallback( () => this.reset() );
|
|
1060
|
+
this.denoisingManager.setSettings( this.settings );
|
|
1595
1061
|
|
|
1596
|
-
|
|
1062
|
+
// Expose environment manager (lives on pathTracer stage)
|
|
1063
|
+
this.environmentManager = this.stages.pathTracer.environment;
|
|
1064
|
+
this.environmentManager.callbacks.onAutoExposureReset = () => this.pipeline.eventBus.emit( 'autoexposure:resetHistory' );
|
|
1597
1065
|
|
|
1598
1066
|
}
|
|
1599
1067
|
|
|
1600
|
-
|
|
1601
|
-
async generateSolidColorTexture() {
|
|
1068
|
+
_wireEvents() {
|
|
1602
1069
|
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
/** @internal Use engine.environment.setTexture() */
|
|
1608
|
-
async setEnvironmentMap( texture ) {
|
|
1070
|
+
// Forward manager events → app events
|
|
1071
|
+
this.cameraManager.addEventListener( 'CameraSwitched', ( e ) => this.dispatchEvent( e ) );
|
|
1072
|
+
this.cameraManager.addEventListener( EngineEvents.AUTO_FOCUS_UPDATED, ( e ) => this.dispatchEvent( e ) );
|
|
1609
1073
|
|
|
1610
|
-
|
|
1074
|
+
this._forwardEvents( this.denoisingManager, [
|
|
1075
|
+
EngineEvents.DENOISING_START, EngineEvents.DENOISING_END,
|
|
1076
|
+
EngineEvents.UPSCALING_START, EngineEvents.UPSCALING_PROGRESS, EngineEvents.UPSCALING_END,
|
|
1077
|
+
'resolution_changed',
|
|
1078
|
+
] );
|
|
1611
1079
|
|
|
1612
|
-
|
|
1613
|
-
return;
|
|
1080
|
+
this._setupAutoExposureListener();
|
|
1614
1081
|
|
|
1615
|
-
|
|
1082
|
+
// Animation lifecycle → wake + refit flag
|
|
1083
|
+
this.animationManager.wakeCallback = () => this.wake();
|
|
1084
|
+
this._forwardEvents( this.animationManager, [
|
|
1085
|
+
EngineEvents.ANIMATION_STARTED,
|
|
1086
|
+
EngineEvents.ANIMATION_PAUSED,
|
|
1087
|
+
EngineEvents.ANIMATION_STOPPED,
|
|
1088
|
+
] );
|
|
1089
|
+
this.animationManager.addEventListener( EngineEvents.ANIMATION_PAUSED, () => {
|
|
1616
1090
|
|
|
1617
|
-
|
|
1618
|
-
this.reset();
|
|
1091
|
+
this._animRefitInFlight = false;
|
|
1619
1092
|
|
|
1620
|
-
|
|
1093
|
+
} );
|
|
1094
|
+
this.animationManager.addEventListener( EngineEvents.ANIMATION_STOPPED, () => {
|
|
1621
1095
|
|
|
1622
|
-
|
|
1623
|
-
markEnvironmentNeedsUpdate() {
|
|
1096
|
+
this._animRefitInFlight = false;
|
|
1624
1097
|
|
|
1625
|
-
|
|
1626
|
-
if ( tex ) tex.needsUpdate = true;
|
|
1098
|
+
} );
|
|
1627
1099
|
|
|
1628
|
-
|
|
1100
|
+
// Camera callbacks for switchCamera / focusOn
|
|
1101
|
+
this.cameraManager.initCallbacks( {
|
|
1102
|
+
onResize: () => this.onResize(),
|
|
1103
|
+
onReset: () => this.reset(),
|
|
1104
|
+
getSettings: ( k ) => this.settings.get( k ),
|
|
1105
|
+
} );
|
|
1629
1106
|
|
|
1630
|
-
|
|
1631
|
-
|
|
1107
|
+
// Auto-focus context — CameraManager stores it, reads it each frame
|
|
1108
|
+
this.cameraManager.initAutoFocus( {
|
|
1109
|
+
meshScene: this.meshScene,
|
|
1110
|
+
assetLoader: this.assetLoader,
|
|
1111
|
+
floorPlane: this.assetLoader.floorPlane,
|
|
1112
|
+
pathTracer: this.stages.pathTracer,
|
|
1113
|
+
settings: this.settings,
|
|
1114
|
+
softReset: () => this.reset( true ),
|
|
1115
|
+
hardReset: () => this.reset(),
|
|
1116
|
+
} );
|
|
1632
1117
|
|
|
1633
|
-
|
|
1634
|
-
this.
|
|
1118
|
+
// Bind settings to pipeline stages
|
|
1119
|
+
this.settings.bind( {
|
|
1120
|
+
stages: this.stages,
|
|
1121
|
+
resetCallback: () => this.reset(),
|
|
1122
|
+
reconcileCompletion: () => this._reconcileCompletion(),
|
|
1123
|
+
} );
|
|
1635
1124
|
|
|
1636
|
-
|
|
1125
|
+
// Resize handling
|
|
1126
|
+
this.onResize();
|
|
1127
|
+
this.resizeHandler = () => this.onResize();
|
|
1128
|
+
if ( this._autoResize ) {
|
|
1637
1129
|
|
|
1638
|
-
|
|
1639
|
-
this._previousCDF = this.getEnvironmentCDF();
|
|
1130
|
+
window.addEventListener( 'resize', this.resizeHandler );
|
|
1640
1131
|
|
|
1641
1132
|
}
|
|
1642
1133
|
|
|
1643
|
-
|
|
1134
|
+
// Asset load events
|
|
1135
|
+
this._onAssetLoaded = async ( event ) => {
|
|
1644
1136
|
|
|
1645
|
-
|
|
1137
|
+
if ( this._loadingInProgress ) return;
|
|
1646
1138
|
|
|
1647
|
-
|
|
1139
|
+
if ( event.model ) {
|
|
1648
1140
|
|
|
1649
|
-
|
|
1141
|
+
await this.loadSceneData();
|
|
1650
1142
|
|
|
1651
|
-
|
|
1143
|
+
} else if ( event.texture ) {
|
|
1652
1144
|
|
|
1653
|
-
|
|
1145
|
+
const envTexture = this.meshScene.environment;
|
|
1146
|
+
if ( envTexture && this.stages.pathTracer ) {
|
|
1654
1147
|
|
|
1655
|
-
|
|
1148
|
+
await this.stages.pathTracer.environment.setEnvironmentMap( envTexture );
|
|
1656
1149
|
|
|
1657
|
-
|
|
1150
|
+
}
|
|
1658
1151
|
|
|
1659
|
-
|
|
1660
|
-
this._previousHDRI = null;
|
|
1661
|
-
this._previousCDF = null;
|
|
1152
|
+
resetLoading();
|
|
1662
1153
|
|
|
1663
1154
|
}
|
|
1664
1155
|
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
const envParams = this.getEnvParams();
|
|
1668
|
-
if ( envParams ) envParams.mode = mode;
|
|
1669
|
-
|
|
1670
|
-
this.markEnvironmentNeedsUpdate();
|
|
1671
|
-
this.pipeline?.eventBus.emit( 'autoexposure:resetHistory' );
|
|
1672
|
-
this.reset();
|
|
1673
|
-
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
// ═══════════════════════════════════════════════════════════════
|
|
1677
|
-
// Read-Only Accessors
|
|
1678
|
-
// ═══════════════════════════════════════════════════════════════
|
|
1679
|
-
|
|
1680
|
-
/** @internal Use engine.output.isComplete() */
|
|
1681
|
-
isComplete() {
|
|
1682
|
-
|
|
1683
|
-
return this.stages.pathTracer?.isComplete ?? false;
|
|
1684
|
-
|
|
1685
|
-
}
|
|
1686
|
-
|
|
1687
|
-
/** @internal Use engine.output.getFrameCount() */
|
|
1688
|
-
getFrameCount() {
|
|
1689
|
-
|
|
1690
|
-
return this.stages.pathTracer?.frameCount || 0;
|
|
1691
|
-
|
|
1692
|
-
}
|
|
1693
|
-
|
|
1694
|
-
/**
|
|
1695
|
-
* Convenience alias for the raw Three.js PerspectiveCamera.
|
|
1696
|
-
* Prefer engine.camera.active for consistency with the sub-API pattern.
|
|
1697
|
-
*/
|
|
1698
|
-
get activeCamera() {
|
|
1699
|
-
|
|
1700
|
-
return this.cameraManager?.camera ?? this._camera;
|
|
1701
|
-
|
|
1702
|
-
}
|
|
1703
|
-
|
|
1704
|
-
/** @internal Use engine.output.getStatistics() */
|
|
1705
|
-
getSceneStatistics() {
|
|
1706
|
-
|
|
1707
|
-
try {
|
|
1708
|
-
|
|
1709
|
-
return this._sdf?.getStatistics?.() ?? null;
|
|
1710
|
-
|
|
1711
|
-
} catch {
|
|
1712
|
-
|
|
1713
|
-
return null;
|
|
1714
|
-
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
}
|
|
1718
|
-
|
|
1719
|
-
/**
|
|
1720
|
-
* @internal Use engine.output.getCanvas()
|
|
1721
|
-
* Returns the canvas element suitable for reading pixels from.
|
|
1722
|
-
* Ensures the WebGPU canvas has fresh content if it's the source.
|
|
1723
|
-
* Use this instead of directly accessing renderer.domElement / denoiserCanvas.
|
|
1724
|
-
* @returns {HTMLCanvasElement|null}
|
|
1725
|
-
*/
|
|
1726
|
-
getOutputCanvas() {
|
|
1727
|
-
|
|
1728
|
-
if ( ! this.renderer?.domElement ) return null;
|
|
1729
|
-
|
|
1730
|
-
const denoiser = this.denoisingManager?.denoiser;
|
|
1731
|
-
const upscaler = this.denoisingManager?.upscaler;
|
|
1732
|
-
const usePostProcess = ( denoiser?.enabled || upscaler?.enabled )
|
|
1733
|
-
&& this.denoiserCanvas
|
|
1734
|
-
&& this.stages.pathTracer?.isComplete;
|
|
1735
|
-
|
|
1736
|
-
if ( usePostProcess ) return this.denoiserCanvas;
|
|
1737
|
-
|
|
1738
|
-
// Re-render display stage so the WebGPU canvas has valid content
|
|
1739
|
-
if ( this.stages.display && this.pipeline?.context ) {
|
|
1740
|
-
|
|
1741
|
-
this.stages.display.render( this.pipeline.context );
|
|
1742
|
-
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
return this.renderer.domElement;
|
|
1746
|
-
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
|
-
/**
|
|
1750
|
-
* @internal Use engine.cameraAPI.focusOn()
|
|
1751
|
-
* @param {import('three').Vector3} center
|
|
1752
|
-
*/
|
|
1753
|
-
focusOnPoint( center ) {
|
|
1754
|
-
|
|
1755
|
-
if ( ! center || ! this._controls ) return;
|
|
1756
|
-
this._controls.target.copy( center );
|
|
1757
|
-
this._controls.update();
|
|
1758
|
-
this.reset();
|
|
1759
|
-
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
/**
|
|
1763
|
-
* @internal Use engine.selection.dispatchEvent()
|
|
1764
|
-
* @param {Object} event
|
|
1765
|
-
*/
|
|
1766
|
-
dispatchInteractionEvent( event ) {
|
|
1767
|
-
|
|
1768
|
-
this._interactionManager?.dispatchEvent( event );
|
|
1769
|
-
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
|
-
/**
|
|
1773
|
-
* @internal Use engine.selection.on()
|
|
1774
|
-
* @param {string} type
|
|
1775
|
-
* @param {Function} handler
|
|
1776
|
-
* @returns {Function} unsubscribe function
|
|
1777
|
-
*/
|
|
1778
|
-
onInteractionEvent( type, handler ) {
|
|
1779
|
-
|
|
1780
|
-
this._interactionManager?.addEventListener( type, handler );
|
|
1781
|
-
return () => this._interactionManager?.removeEventListener( type, handler );
|
|
1782
|
-
|
|
1783
|
-
}
|
|
1784
|
-
|
|
1785
|
-
/** @internal Use engine.output.screenshot() */
|
|
1786
|
-
takeScreenshot() {
|
|
1787
|
-
|
|
1788
|
-
const canvas = this.getOutputCanvas();
|
|
1789
|
-
if ( ! canvas ) return;
|
|
1790
|
-
|
|
1791
|
-
try {
|
|
1792
|
-
|
|
1793
|
-
const screenshot = canvas.toDataURL( 'image/png' );
|
|
1794
|
-
const link = document.createElement( 'a' );
|
|
1795
|
-
link.href = screenshot;
|
|
1796
|
-
link.download = 'screenshot.png';
|
|
1797
|
-
link.click();
|
|
1798
|
-
|
|
1799
|
-
} catch ( error ) {
|
|
1800
|
-
|
|
1801
|
-
console.error( 'PathTracerApp: Screenshot failed:', error );
|
|
1802
|
-
|
|
1803
|
-
}
|
|
1804
|
-
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
|
-
setPathTracerEnabled( val ) {
|
|
1808
|
-
|
|
1809
|
-
this.pathTracerEnabled = val;
|
|
1810
|
-
|
|
1811
|
-
}
|
|
1812
|
-
setAccumulationEnabled( val ) {
|
|
1813
|
-
|
|
1814
|
-
this.stages.pathTracer?.setAccumulationEnabled( val );
|
|
1815
|
-
|
|
1816
|
-
}
|
|
1817
|
-
setRenderMode( mode ) {
|
|
1818
|
-
|
|
1819
|
-
this.stages.pathTracer?.setUniform( 'renderMode', parseInt( mode ) );
|
|
1820
|
-
|
|
1821
|
-
}
|
|
1822
|
-
setTileCount( val ) {
|
|
1823
|
-
|
|
1824
|
-
this.stages.pathTracer?.tileManager?.setTileCount( val );
|
|
1825
|
-
|
|
1826
|
-
}
|
|
1827
|
-
setInteractionModeEnabled( val ) {
|
|
1828
|
-
|
|
1829
|
-
this.stages.pathTracer?.setInteractionModeEnabled( val );
|
|
1830
|
-
|
|
1831
|
-
}
|
|
1832
|
-
|
|
1833
|
-
/** @internal Use engine.denoising.setAdaptiveSamplingParams() */
|
|
1834
|
-
setAdaptiveSamplingParameters( params ) {
|
|
1835
|
-
|
|
1836
|
-
if ( params.min !== undefined ) this.stages.pathTracer?.setAdaptiveSamplingMin( params.min );
|
|
1837
|
-
if ( params.adaptiveSamplingMax !== undefined ) this.settings.set( 'adaptiveSamplingMax', params.adaptiveSamplingMax );
|
|
1838
|
-
this.stages.adaptiveSampling?.setAdaptiveSamplingParameters( params );
|
|
1839
|
-
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
|
-
/** @internal Use engine.materials.setProperty() */
|
|
1843
|
-
updateMaterialProperty( materialIndex, property, value ) {
|
|
1844
|
-
|
|
1845
|
-
this.stages.pathTracer?.materialData.updateMaterialProperty( materialIndex, property, value );
|
|
1846
|
-
|
|
1847
|
-
const emissiveAffectingProps = [ 'emissive', 'emissiveIntensity', 'visible' ];
|
|
1848
|
-
if ( emissiveAffectingProps.includes( property )
|
|
1849
|
-
&& this._sdf?.emissiveTriangleBuilder
|
|
1850
|
-
&& this.stages.pathTracer?.enableEmissiveTriangleSampling?.value ) {
|
|
1156
|
+
this.pauseRendering = false;
|
|
1157
|
+
this.reset();
|
|
1851
1158
|
|
|
1852
|
-
|
|
1853
|
-
if ( mat ) {
|
|
1159
|
+
};
|
|
1854
1160
|
|
|
1855
|
-
|
|
1856
|
-
else if ( property === 'emissiveIntensity' ) mat.emissiveIntensity = value;
|
|
1857
|
-
else if ( property === 'visible' ) mat.visible = value;
|
|
1161
|
+
this.assetLoader.addEventListener( 'load', this._onAssetLoaded );
|
|
1858
1162
|
|
|
1859
|
-
|
|
1860
|
-
materialIndex, mat,
|
|
1861
|
-
this._sdf.triangleData, this._sdf.materials, this._sdf.triangleCount,
|
|
1862
|
-
);
|
|
1163
|
+
this.assetLoader.addEventListener( 'modelProcessed', ( event ) => {
|
|
1863
1164
|
|
|
1864
|
-
|
|
1165
|
+
const cameras = [ this.cameraManager.camera, ...( event.cameras || [] ) ];
|
|
1166
|
+
this.cameraManager.setCameras( cameras );
|
|
1865
1167
|
|
|
1866
|
-
|
|
1867
|
-
this.stages.pathTracer.setEmissiveTriangleData(
|
|
1868
|
-
emissiveRawData,
|
|
1869
|
-
this._sdf.emissiveTriangleBuilder.emissiveCount,
|
|
1870
|
-
this._sdf.emissiveTriangleBuilder.totalEmissivePower,
|
|
1871
|
-
);
|
|
1168
|
+
if ( this.interactionManager ) {
|
|
1872
1169
|
|
|
1873
|
-
|
|
1170
|
+
this.interactionManager.floorPlane = this.assetLoader.floorPlane;
|
|
1874
1171
|
|
|
1875
1172
|
}
|
|
1876
1173
|
|
|
1877
|
-
}
|
|
1878
|
-
|
|
1879
|
-
this.reset();
|
|
1880
|
-
|
|
1881
|
-
}
|
|
1882
|
-
|
|
1883
|
-
/** @internal Use engine.materials.setTextureTransform() */
|
|
1884
|
-
updateTextureTransform( materialIndex, textureName, transform ) {
|
|
1885
|
-
|
|
1886
|
-
this.stages.pathTracer?.materialData.updateTextureTransform( materialIndex, textureName, transform );
|
|
1887
|
-
this.reset();
|
|
1888
|
-
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
/** @internal Use engine.materials.refresh() */
|
|
1892
|
-
refreshMaterial() {
|
|
1893
|
-
|
|
1894
|
-
this.reset();
|
|
1895
|
-
|
|
1896
|
-
}
|
|
1897
|
-
|
|
1898
|
-
/** @internal Use engine.materials.replace() */
|
|
1899
|
-
updateMaterial( materialIndex, material ) {
|
|
1900
|
-
|
|
1901
|
-
this.stages.pathTracer?.materialData.updateMaterial( materialIndex, material );
|
|
1902
|
-
|
|
1903
|
-
}
|
|
1904
|
-
|
|
1905
|
-
/** @internal Use engine.materials.rebuild() */
|
|
1906
|
-
async rebuildMaterials( scene ) {
|
|
1907
|
-
|
|
1908
|
-
await this.stages.pathTracer?.rebuildMaterials( scene || this.meshScene );
|
|
1909
|
-
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
|
-
// ═══════════════════════════════════════════════════════════════
|
|
1913
|
-
// Stage Parameter Facade — hides direct stage access from store
|
|
1914
|
-
// ═══════════════════════════════════════════════════════════════
|
|
1915
|
-
|
|
1916
|
-
// ── ASVGF ──
|
|
1917
|
-
|
|
1918
|
-
/** @internal Use engine.denoising.setASVGFParams() */
|
|
1919
|
-
updateASVGFParameters( params ) {
|
|
1920
|
-
|
|
1921
|
-
this.stages.asvgf?.updateParameters( params );
|
|
1922
|
-
|
|
1923
|
-
}
|
|
1924
|
-
|
|
1925
|
-
/** @internal Use engine.denoising.toggleASVGFHeatmap() */
|
|
1926
|
-
toggleASVGFHeatmap( enabled ) {
|
|
1927
|
-
|
|
1928
|
-
this.stages.asvgf?.toggleHeatmap?.( enabled );
|
|
1174
|
+
} );
|
|
1929
1175
|
|
|
1930
1176
|
}
|
|
1931
1177
|
|
|
1932
1178
|
/**
|
|
1933
|
-
*
|
|
1934
|
-
* @param {Object} config
|
|
1179
|
+
* Initializes animation manager and transform manager after scene rebuild.
|
|
1935
1180
|
*/
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
if ( ! this.stages.asvgf ) return;
|
|
1939
|
-
|
|
1940
|
-
this.stages.asvgf.enabled = config.enabled;
|
|
1941
|
-
if ( this.stages.variance ) this.stages.variance.enabled = config.enabled;
|
|
1942
|
-
if ( this.stages.bilateralFilter ) this.stages.bilateralFilter.enabled = config.enabled;
|
|
1943
|
-
|
|
1944
|
-
if ( config.enabled ) {
|
|
1945
|
-
|
|
1946
|
-
this.stages.asvgf.updateParameters( config );
|
|
1947
|
-
|
|
1948
|
-
}
|
|
1949
|
-
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
// ── SSRC ──
|
|
1953
|
-
|
|
1954
|
-
/** @internal Use engine.denoising.setSSRCParams() */
|
|
1955
|
-
updateSSRCParameters( params ) {
|
|
1956
|
-
|
|
1957
|
-
this.stages.ssrc?.updateParameters( params );
|
|
1958
|
-
|
|
1959
|
-
}
|
|
1960
|
-
|
|
1961
|
-
// ── EdgeAware Filtering ──
|
|
1962
|
-
|
|
1963
|
-
/** @internal Use engine.denoising.setEdgeAwareParams() */
|
|
1964
|
-
updateEdgeAwareUniforms( params ) {
|
|
1965
|
-
|
|
1966
|
-
this.stages.edgeFilter?.updateUniforms( params );
|
|
1967
|
-
|
|
1968
|
-
}
|
|
1969
|
-
|
|
1970
|
-
// ── Auto Exposure ──
|
|
1971
|
-
|
|
1972
|
-
/** @internal Use engine.denoising.setAutoExposureParams() */
|
|
1973
|
-
updateAutoExposureParameters( params ) {
|
|
1974
|
-
|
|
1975
|
-
this.stages.autoExposure?.updateParameters( params );
|
|
1976
|
-
|
|
1977
|
-
}
|
|
1978
|
-
|
|
1979
|
-
// ── Adaptive Sampling ──
|
|
1980
|
-
|
|
1981
|
-
/** @internal Use engine.denoising.setAdaptiveSamplingParams() */
|
|
1982
|
-
updateAdaptiveSamplingParameters( params ) {
|
|
1983
|
-
|
|
1984
|
-
this.stages.adaptiveSampling?.setAdaptiveSamplingParameters( params );
|
|
1985
|
-
|
|
1986
|
-
}
|
|
1987
|
-
|
|
1988
|
-
/** @internal Use engine.denoising.setAdaptiveSamplingParams({ varianceThreshold }) */
|
|
1989
|
-
setAdaptiveSamplingVarianceThreshold( v ) {
|
|
1990
|
-
|
|
1991
|
-
this.stages.adaptiveSampling?.setVarianceThreshold( v );
|
|
1992
|
-
|
|
1993
|
-
}
|
|
1994
|
-
|
|
1995
|
-
/** @internal Use engine.denoising.setAdaptiveSamplingParams({ materialBias }) */
|
|
1996
|
-
setAdaptiveSamplingMaterialBias( v ) {
|
|
1997
|
-
|
|
1998
|
-
this.stages.adaptiveSampling?.setMaterialBias( v );
|
|
1999
|
-
|
|
2000
|
-
}
|
|
2001
|
-
|
|
2002
|
-
/** @internal Use engine.denoising.setAdaptiveSamplingParams({ edgeBias }) */
|
|
2003
|
-
setAdaptiveSamplingEdgeBias( v ) {
|
|
2004
|
-
|
|
2005
|
-
this.stages.adaptiveSampling?.setEdgeBias( v );
|
|
2006
|
-
|
|
2007
|
-
}
|
|
2008
|
-
|
|
2009
|
-
/** @internal Use engine.denoising.setAdaptiveSamplingParams({ convergenceSpeed }) */
|
|
2010
|
-
setAdaptiveSamplingConvergenceSpeed( v ) {
|
|
2011
|
-
|
|
2012
|
-
this.stages.adaptiveSampling?.setConvergenceSpeed( v );
|
|
2013
|
-
|
|
2014
|
-
}
|
|
2015
|
-
|
|
2016
|
-
/** @internal Use engine.denoising.toggleAdaptiveSamplingHelper() */
|
|
2017
|
-
toggleAdaptiveSamplingHelper( enabled ) {
|
|
2018
|
-
|
|
2019
|
-
this.stages.adaptiveSampling?.toggleHelper( enabled );
|
|
2020
|
-
|
|
2021
|
-
}
|
|
2022
|
-
|
|
2023
|
-
// ── Tile Highlight ──
|
|
2024
|
-
|
|
2025
|
-
/** @internal Use engine.denoising.setTileHighlightEnabled() */
|
|
2026
|
-
setTileHighlightEnabled( enabled ) {
|
|
2027
|
-
|
|
2028
|
-
this.setTileHelperEnabled( enabled );
|
|
2029
|
-
|
|
2030
|
-
}
|
|
2031
|
-
|
|
2032
|
-
// ── OIDN Denoiser ──
|
|
2033
|
-
|
|
2034
|
-
/** @internal Use engine.denoising.setOIDNEnabled() */
|
|
2035
|
-
setOIDNEnabled( enabled ) {
|
|
2036
|
-
|
|
2037
|
-
const d = this.denoisingManager?.denoiser;
|
|
2038
|
-
if ( d ) d.enabled = enabled;
|
|
2039
|
-
|
|
2040
|
-
}
|
|
1181
|
+
_initAnimationAndTransforms() {
|
|
2041
1182
|
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
this.denoisingManager?.denoiser?.updateQuality( quality );
|
|
2046
|
-
|
|
2047
|
-
}
|
|
2048
|
-
|
|
2049
|
-
/** @internal Use engine.denoising.setOIDNTileHelper() */
|
|
2050
|
-
setOIDNTileHelper( enabled ) {
|
|
2051
|
-
|
|
2052
|
-
this.setTileHelperEnabled( enabled );
|
|
2053
|
-
|
|
2054
|
-
}
|
|
1183
|
+
const animations = this.assetLoader?.animations || [];
|
|
1184
|
+
if ( animations.length > 0 ) {
|
|
2055
1185
|
|
|
2056
|
-
|
|
2057
|
-
|
|
1186
|
+
const mixerRoot = this.assetLoader?.targetModel || this.meshScene;
|
|
1187
|
+
this.animationManager.init( this.meshScene, mixerRoot, this._sdf.meshes, animations, this._sdf.triangleCount );
|
|
1188
|
+
this.animationManager.onFinished = () => {
|
|
2058
1189
|
|
|
2059
|
-
|
|
2060
|
-
|
|
1190
|
+
this._animRefitInFlight = false;
|
|
1191
|
+
this.dispatchEvent( { type: EngineEvents.ANIMATION_FINISHED } );
|
|
2061
1192
|
|
|
2062
|
-
|
|
2063
|
-
if ( ! enabled ) tileHelper.hide();
|
|
1193
|
+
};
|
|
2064
1194
|
|
|
2065
1195
|
}
|
|
2066
1196
|
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
// ── AI Upscaler ──
|
|
2070
|
-
|
|
2071
|
-
/** @internal Use engine.denoising.setUpscalerEnabled() */
|
|
2072
|
-
setUpscalerEnabled( enabled ) {
|
|
2073
|
-
|
|
2074
|
-
const u = this.denoisingManager?.upscaler;
|
|
2075
|
-
if ( u ) u.enabled = enabled;
|
|
2076
|
-
|
|
2077
|
-
}
|
|
2078
|
-
|
|
2079
|
-
/** @internal Use engine.denoising.setUpscalerScaleFactor() */
|
|
2080
|
-
setUpscalerScaleFactor( factor ) {
|
|
2081
|
-
|
|
2082
|
-
this.denoisingManager?.upscaler?.setScaleFactor( factor );
|
|
2083
|
-
|
|
2084
|
-
}
|
|
2085
|
-
|
|
2086
|
-
/** @internal Use engine.denoising.setUpscalerQuality() */
|
|
2087
|
-
setUpscalerQuality( quality ) {
|
|
2088
|
-
|
|
2089
|
-
this.denoisingManager?.upscaler?.setQuality( quality );
|
|
1197
|
+
this.transformManager?.setMeshData( this._sdf.meshes, this._sdf.triangleCount );
|
|
2090
1198
|
|
|
2091
1199
|
}
|
|
2092
1200
|
|
|
@@ -2099,11 +1207,11 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
2099
1207
|
const adaptiveSamplingMax = this.settings.get( 'adaptiveSamplingMax' );
|
|
2100
1208
|
const useAdaptiveSampling = this.settings.get( 'useAdaptiveSampling' );
|
|
2101
1209
|
|
|
2102
|
-
this.stages.pathTracer = new PathTracer( this.renderer, this.scene, this.
|
|
1210
|
+
this.stages.pathTracer = new PathTracer( this.renderer, this.scene, this.cameraManager.camera );
|
|
2103
1211
|
this.stages.normalDepth = new NormalDepth( this.renderer, {
|
|
2104
1212
|
pathTracer: this.stages.pathTracer
|
|
2105
1213
|
} );
|
|
2106
|
-
this.stages.motionVector = new MotionVector( this.renderer, this.
|
|
1214
|
+
this.stages.motionVector = new MotionVector( this.renderer, this.cameraManager.camera, {
|
|
2107
1215
|
pathTracer: this.stages.pathTracer
|
|
2108
1216
|
} );
|
|
2109
1217
|
this.stages.ssrc = new SSRC( this.renderer, { enabled: false } );
|
|
@@ -2124,31 +1232,13 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
2124
1232
|
|
|
2125
1233
|
}
|
|
2126
1234
|
|
|
2127
|
-
_createDenoiserCanvas() {
|
|
2128
|
-
|
|
2129
|
-
if ( this.denoiserCanvas ) return; // guard against double init
|
|
2130
|
-
|
|
2131
|
-
const parent = this.canvas.parentNode;
|
|
2132
|
-
if ( ! parent ) return; // headless / detached canvas — skip
|
|
2133
|
-
|
|
2134
|
-
const dc = document.createElement( 'canvas' );
|
|
2135
|
-
dc.width = this.canvas.width;
|
|
2136
|
-
dc.height = this.canvas.height;
|
|
2137
|
-
dc.style.width = `${this.canvas.clientWidth}px`;
|
|
2138
|
-
dc.style.height = `${this.canvas.clientHeight}px`;
|
|
2139
|
-
|
|
2140
|
-
parent.insertBefore( dc, this.canvas );
|
|
2141
|
-
this.denoiserCanvas = dc;
|
|
2142
|
-
|
|
2143
|
-
}
|
|
2144
|
-
|
|
2145
1235
|
_setupDenoisingManager() {
|
|
2146
1236
|
|
|
2147
1237
|
this.denoisingManager = new DenoisingManager( {
|
|
2148
1238
|
renderer: this.renderer,
|
|
2149
|
-
|
|
1239
|
+
mainCanvas: this.canvas,
|
|
2150
1240
|
scene: this.scene,
|
|
2151
|
-
camera: this.
|
|
1241
|
+
camera: this.cameraManager.camera,
|
|
2152
1242
|
stages: {
|
|
2153
1243
|
pathTracer: this.stages.pathTracer,
|
|
2154
1244
|
asvgf: this.stages.asvgf,
|
|
@@ -2169,82 +1259,10 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
2169
1259
|
this.denoisingManager.setupDenoiser();
|
|
2170
1260
|
this.denoisingManager.setupUpscaler();
|
|
2171
1261
|
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
* be routed with a simple uniform forward.
|
|
2177
|
-
*/
|
|
2178
|
-
_buildSettingsHandlers() {
|
|
2179
|
-
|
|
2180
|
-
return {
|
|
2181
|
-
|
|
2182
|
-
handleTransparentBackground: ( value ) => {
|
|
2183
|
-
|
|
2184
|
-
this.stages.pathTracer?.setUniform( 'transparentBackground', value );
|
|
2185
|
-
this.stages.display?.setTransparentBackground( value );
|
|
2186
|
-
|
|
2187
|
-
},
|
|
2188
|
-
|
|
2189
|
-
handleExposure: ( value ) => {
|
|
2190
|
-
|
|
2191
|
-
if ( ! this.stages.autoExposure?.enabled ) {
|
|
2192
|
-
|
|
2193
|
-
this.stages.display?.setExposure( value );
|
|
2194
|
-
|
|
2195
|
-
}
|
|
2196
|
-
|
|
2197
|
-
},
|
|
2198
|
-
|
|
2199
|
-
handleSaturation: ( value ) => {
|
|
2200
|
-
|
|
2201
|
-
this.stages.display?.setSaturation( value );
|
|
2202
|
-
|
|
2203
|
-
},
|
|
2204
|
-
|
|
2205
|
-
handleRenderLimitMode: ( value ) => {
|
|
2206
|
-
|
|
2207
|
-
if ( this.stages.pathTracer?.setRenderLimitMode ) {
|
|
2208
|
-
|
|
2209
|
-
this.stages.pathTracer.setRenderLimitMode( value );
|
|
2210
|
-
|
|
2211
|
-
}
|
|
2212
|
-
|
|
2213
|
-
},
|
|
2214
|
-
|
|
2215
|
-
handleMaxSamples: ( value ) => {
|
|
2216
|
-
|
|
2217
|
-
this.stages.pathTracer?.setUniform( 'maxSamples', value );
|
|
2218
|
-
this.stages.pathTracer?.updateCompletionThreshold();
|
|
2219
|
-
this._reconcileCompletion();
|
|
2220
|
-
|
|
2221
|
-
},
|
|
2222
|
-
|
|
2223
|
-
handleRenderTimeLimit: () => {
|
|
2224
|
-
|
|
2225
|
-
this._reconcileCompletion();
|
|
2226
|
-
|
|
2227
|
-
},
|
|
2228
|
-
|
|
2229
|
-
handleRenderMode: ( value ) => {
|
|
2230
|
-
|
|
2231
|
-
this.stages.pathTracer?.setUniform( 'renderMode', parseInt( value ) );
|
|
2232
|
-
|
|
2233
|
-
},
|
|
2234
|
-
|
|
2235
|
-
handleEnvironmentRotation: ( value ) => {
|
|
2236
|
-
|
|
2237
|
-
this.stages.pathTracer?.environment.setEnvironmentRotation( value );
|
|
2238
|
-
|
|
2239
|
-
},
|
|
2240
|
-
|
|
2241
|
-
handleInteractionModeEnabled: ( value ) => {
|
|
2242
|
-
|
|
2243
|
-
this.setInteractionModeEnabled( value );
|
|
2244
|
-
|
|
2245
|
-
},
|
|
2246
|
-
|
|
2247
|
-
};
|
|
1262
|
+
// Set initial render resolution
|
|
1263
|
+
const initW = this.canvas.clientWidth || 1;
|
|
1264
|
+
const initH = this.canvas.clientHeight || 1;
|
|
1265
|
+
this.denoisingManager.setRenderSize( initW, initH );
|
|
2248
1266
|
|
|
2249
1267
|
}
|
|
2250
1268
|
|
|
@@ -2253,7 +1271,9 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
2253
1271
|
const stage = this.stages.pathTracer;
|
|
2254
1272
|
if ( ! stage ) return;
|
|
2255
1273
|
|
|
2256
|
-
const shouldBeComplete = this.
|
|
1274
|
+
const shouldBeComplete = this.completion.isLimitReached(
|
|
1275
|
+
stage, this.settings.get( 'renderLimitMode' ), this.settings.get( 'renderTimeLimit' )
|
|
1276
|
+
);
|
|
2257
1277
|
|
|
2258
1278
|
if ( shouldBeComplete && ! stage.isComplete ) {
|
|
2259
1279
|
|
|
@@ -2262,147 +1282,23 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
2262
1282
|
} else if ( ! shouldBeComplete && stage.isComplete ) {
|
|
2263
1283
|
|
|
2264
1284
|
stage.isComplete = false;
|
|
2265
|
-
this.
|
|
2266
|
-
|
|
2267
|
-
// Adjust lastResetTime so timeElapsed continues from where it paused
|
|
2268
|
-
// rather than including idle time spent while completed
|
|
2269
|
-
this.lastResetTime = performance.now() - this.timeElapsed * 1000;
|
|
1285
|
+
this.completion.resumeFromPause();
|
|
2270
1286
|
|
|
2271
1287
|
this.canvas.style.opacity = '1';
|
|
2272
1288
|
const denoiserOutput = this.denoisingManager?.denoiser?.output;
|
|
2273
1289
|
if ( denoiserOutput ) denoiserOutput.style.display = 'none';
|
|
2274
1290
|
|
|
2275
1291
|
this.dispatchEvent( { type: EngineEvents.RENDER_RESET } );
|
|
2276
|
-
|
|
2277
|
-
// Restart the animation loop (it was stopped when render completed)
|
|
2278
1292
|
this.wake();
|
|
2279
1293
|
|
|
2280
1294
|
}
|
|
2281
1295
|
|
|
2282
1296
|
}
|
|
2283
1297
|
|
|
2284
|
-
_isRenderLimitReached() {
|
|
2285
|
-
|
|
2286
|
-
const stage = this.stages.pathTracer;
|
|
2287
|
-
if ( ! stage ) return false;
|
|
2288
|
-
|
|
2289
|
-
if ( this.settings.get( 'renderLimitMode' ) === 'time' ) {
|
|
2290
|
-
|
|
2291
|
-
const limit = this.settings.get( 'renderTimeLimit' );
|
|
2292
|
-
return limit > 0 && this.timeElapsed >= limit;
|
|
2293
|
-
|
|
2294
|
-
}
|
|
2295
|
-
|
|
2296
|
-
return stage.frameCount >= stage.completionThreshold;
|
|
2297
|
-
|
|
2298
|
-
}
|
|
2299
|
-
|
|
2300
|
-
_setupFloorPlane() {
|
|
2301
|
-
|
|
2302
|
-
this._floorPlane = new Mesh(
|
|
2303
|
-
new CircleGeometry(),
|
|
2304
|
-
new MeshPhysicalMaterial( {
|
|
2305
|
-
transparent: false,
|
|
2306
|
-
color: 0x303030,
|
|
2307
|
-
roughness: 1,
|
|
2308
|
-
metalness: 0,
|
|
2309
|
-
opacity: 0,
|
|
2310
|
-
transmission: 0,
|
|
2311
|
-
} )
|
|
2312
|
-
);
|
|
2313
|
-
this._floorPlane.name = "Ground";
|
|
2314
|
-
this._floorPlane.visible = false;
|
|
2315
|
-
this.meshScene.add( this._floorPlane );
|
|
2316
|
-
|
|
2317
|
-
}
|
|
2318
|
-
|
|
2319
1298
|
_initStats() {
|
|
2320
1299
|
|
|
2321
|
-
this._stats = new Stats( { horizontal: true, trackGPU: true } );
|
|
2322
|
-
this._stats.dom.style.position = 'absolute';
|
|
2323
|
-
this._stats.dom.style.top = 'unset';
|
|
2324
|
-
this._stats.dom.style.bottom = '48px';
|
|
2325
|
-
|
|
2326
|
-
this._stats.init( this.renderer );
|
|
2327
1300
|
const container = this._statsContainer || this.canvas.parentElement || document.body;
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
const foregroundColor = '#ffffff';
|
|
2331
|
-
const backgroundColor = '#1e293b';
|
|
2332
|
-
|
|
2333
|
-
const gradient = this._stats.fpsPanel.context.createLinearGradient( 0, this._stats.fpsPanel.GRAPH_Y, 0, this._stats.fpsPanel.GRAPH_Y + this._stats.fpsPanel.GRAPH_HEIGHT );
|
|
2334
|
-
gradient.addColorStop( 0, foregroundColor );
|
|
2335
|
-
|
|
2336
|
-
this._stats.fpsPanel.fg = this._stats.msPanel.fg = foregroundColor;
|
|
2337
|
-
this._stats.fpsPanel.bg = this._stats.msPanel.bg = backgroundColor;
|
|
2338
|
-
this._stats.fpsPanel.gradient = this._stats.msPanel.gradient = gradient;
|
|
2339
|
-
|
|
2340
|
-
if ( this._stats.gpuPanel ) {
|
|
2341
|
-
|
|
2342
|
-
this._stats.gpuPanel.fg = foregroundColor;
|
|
2343
|
-
this._stats.gpuPanel.bg = backgroundColor;
|
|
2344
|
-
this._stats.gpuPanel.gradient = gradient;
|
|
2345
|
-
|
|
2346
|
-
}
|
|
2347
|
-
|
|
2348
|
-
this._stats.dom.style.display = '';
|
|
2349
|
-
|
|
2350
|
-
}
|
|
2351
|
-
|
|
2352
|
-
_setupInteractionListeners() {
|
|
2353
|
-
|
|
2354
|
-
if ( ! this._interactionManager ) return;
|
|
2355
|
-
|
|
2356
|
-
this._interactionManager.addEventListener( 'objectSelected', ( event ) => {
|
|
2357
|
-
|
|
2358
|
-
this.selectObject( event.object );
|
|
2359
|
-
this.refreshFrame();
|
|
2360
|
-
this.dispatchEvent( { type: 'objectSelected', object: event.object, uuid: event.uuid } );
|
|
2361
|
-
|
|
2362
|
-
} );
|
|
2363
|
-
|
|
2364
|
-
this._interactionManager.addEventListener( 'objectDeselected', ( event ) => {
|
|
2365
|
-
|
|
2366
|
-
this.selectObject( null );
|
|
2367
|
-
this.refreshFrame();
|
|
2368
|
-
this.dispatchEvent( { type: 'objectDeselected', object: event.object, uuid: event.uuid } );
|
|
2369
|
-
|
|
2370
|
-
} );
|
|
2371
|
-
|
|
2372
|
-
this._interactionManager.addEventListener( 'selectModeChanged', ( event ) => {
|
|
2373
|
-
|
|
2374
|
-
this.dispatchEvent( { type: EngineEvents.SELECT_MODE_CHANGED, enabled: event.enabled } );
|
|
2375
|
-
|
|
2376
|
-
} );
|
|
2377
|
-
|
|
2378
|
-
this._interactionManager.addEventListener( 'objectDoubleClicked', ( event ) => {
|
|
2379
|
-
|
|
2380
|
-
this.selectObject( event.object );
|
|
2381
|
-
this.refreshFrame();
|
|
2382
|
-
this.dispatchEvent( { type: EngineEvents.OBJECT_DOUBLE_CLICKED, object: event.object, uuid: event.uuid } );
|
|
2383
|
-
|
|
2384
|
-
} );
|
|
2385
|
-
|
|
2386
|
-
this._interactionManager.addEventListener( 'focusChanged', ( event ) => {
|
|
2387
|
-
|
|
2388
|
-
this.settings.set( 'focusDistance', event.worldDistance );
|
|
2389
|
-
this.dispatchEvent( { type: 'focusChanged', distance: event.distance } );
|
|
2390
|
-
|
|
2391
|
-
} );
|
|
2392
|
-
|
|
2393
|
-
this._interactionManager.addEventListener( 'focusModeChanged', ( event ) => {
|
|
2394
|
-
|
|
2395
|
-
if ( ! event.enabled && this._controls ) this._controls.enabled = true;
|
|
2396
|
-
|
|
2397
|
-
} );
|
|
2398
|
-
|
|
2399
|
-
this._interactionManager.addEventListener( 'afPointPlaced', ( event ) => {
|
|
2400
|
-
|
|
2401
|
-
this.cameraManager.setAFScreenPoint( event.point.x, event.point.y );
|
|
2402
|
-
if ( this._controls ) this._controls.enabled = true;
|
|
2403
|
-
this.dispatchEvent( { type: EngineEvents.AF_POINT_PLACED, point: event.point } );
|
|
2404
|
-
|
|
2405
|
-
} );
|
|
1301
|
+
this._stats = createStats( this.renderer, container );
|
|
2406
1302
|
|
|
2407
1303
|
}
|
|
2408
1304
|
|
|
@@ -2426,91 +1322,30 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
2426
1322
|
|
|
2427
1323
|
this.scene.updateMatrixWorld();
|
|
2428
1324
|
this.overlayManager?.render();
|
|
2429
|
-
this.
|
|
1325
|
+
this.transformManager?.render( this.renderer );
|
|
2430
1326
|
|
|
2431
1327
|
}
|
|
2432
1328
|
|
|
2433
1329
|
_setupOverlayManager() {
|
|
2434
1330
|
|
|
2435
|
-
this.overlayManager = new OverlayManager( this.renderer, this.
|
|
2436
|
-
this.overlayManager.
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
this.addEventListener( 'resolution_changed', ( e ) => {
|
|
2445
|
-
|
|
2446
|
-
tileHelper.setRenderSize( e.width, e.height );
|
|
2447
|
-
|
|
2448
|
-
} );
|
|
2449
|
-
|
|
2450
|
-
// ── Path tracer tile events ──
|
|
2451
|
-
this.pipeline.eventBus.on( 'tile:changed', ( e ) => {
|
|
2452
|
-
|
|
2453
|
-
if ( e.renderMode === 1 && e.tileBounds ) {
|
|
2454
|
-
|
|
2455
|
-
tileHelper.setActiveTile( e.tileBounds );
|
|
2456
|
-
tileHelper.show();
|
|
2457
|
-
|
|
2458
|
-
}
|
|
2459
|
-
|
|
1331
|
+
this.overlayManager = new OverlayManager( this.renderer, this.cameraManager.camera );
|
|
1332
|
+
this.overlayManager.setupDefaultHelpers( {
|
|
1333
|
+
helperScene: this._sceneHelpers,
|
|
1334
|
+
meshScene: this.meshScene,
|
|
1335
|
+
pipeline: this.pipeline,
|
|
1336
|
+
denoisingManager: this.denoisingManager,
|
|
1337
|
+
app: this,
|
|
1338
|
+
renderWidth: this.denoisingManager?._lastRenderWidth || this.canvas.clientWidth || 1,
|
|
1339
|
+
renderHeight: this.denoisingManager?._lastRenderHeight || this.canvas.clientHeight || 1,
|
|
2460
1340
|
} );
|
|
2461
1341
|
|
|
2462
|
-
this.pipeline.eventBus.on( 'pipeline:reset', () => tileHelper.hide() );
|
|
2463
|
-
this.addEventListener( EngineEvents.RENDER_COMPLETE, () => tileHelper.hide() );
|
|
2464
|
-
|
|
2465
|
-
// ── OIDN denoiser tile events ──
|
|
2466
|
-
this._setupDenoiserTileHelper( tileHelper );
|
|
2467
|
-
|
|
2468
|
-
// ── Outline helper (renders at display resolution, not render resolution) ──
|
|
2469
|
-
const outlineHelper = new OutlineHelper( this.renderer, this.meshScene, this._camera );
|
|
2470
|
-
this.overlayManager.register( 'outline', outlineHelper );
|
|
2471
|
-
|
|
2472
|
-
}
|
|
2473
|
-
|
|
2474
|
-
_setupDenoiserTileHelper( tileHelper ) {
|
|
2475
|
-
|
|
2476
|
-
// OIDN/upscaler tile events fire while the animation loop is stopped
|
|
2477
|
-
// (render completed → stopAnimation → async denoise). We must manually
|
|
2478
|
-
// trigger HUD redraws since overlayManager.render() isn't being called.
|
|
2479
|
-
const sources = [ this.denoisingManager?.denoiser, this.denoisingManager?.upscaler ];
|
|
2480
|
-
|
|
2481
|
-
for ( const source of sources ) {
|
|
2482
|
-
|
|
2483
|
-
if ( ! source ) continue;
|
|
2484
|
-
|
|
2485
|
-
source.addEventListener( 'tileProgress', ( e ) => {
|
|
2486
|
-
|
|
2487
|
-
if ( e.tile ) {
|
|
2488
|
-
|
|
2489
|
-
tileHelper.setRenderSize( e.imageWidth, e.imageHeight );
|
|
2490
|
-
tileHelper.setActiveTile( e.tile );
|
|
2491
|
-
tileHelper.show();
|
|
2492
|
-
this.overlayManager?.refreshHUD();
|
|
2493
|
-
|
|
2494
|
-
}
|
|
2495
|
-
|
|
2496
|
-
} );
|
|
2497
|
-
|
|
2498
|
-
source.addEventListener( 'end', () => {
|
|
2499
|
-
|
|
2500
|
-
tileHelper.hide();
|
|
2501
|
-
this.overlayManager?.refreshHUD();
|
|
2502
|
-
|
|
2503
|
-
} );
|
|
2504
|
-
|
|
2505
|
-
}
|
|
2506
|
-
|
|
2507
1342
|
}
|
|
2508
1343
|
|
|
2509
1344
|
|
|
2510
1345
|
_syncControlsAfterLoad() {
|
|
2511
1346
|
|
|
2512
|
-
this.
|
|
2513
|
-
this.
|
|
1347
|
+
this.cameraManager.controls.saveState();
|
|
1348
|
+
this.cameraManager.controls.update();
|
|
2514
1349
|
|
|
2515
1350
|
}
|
|
2516
1351
|
|