rayzee 8.8.1 → 9.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 +71 -12
- package/dist/{NeuralSuperRes-Dc6jRYke.js → NeuralSuperRes-CQLCLpcC.js} +2 -0
- package/dist/rayzee.es.js +3572 -3111
- package/dist/rayzee.umd.js +110 -99
- package/package.json +1 -1
- package/src/EngineDefaults.js +21 -19
- package/src/EngineEvents.js +4 -0
- package/src/Passes/OIDNTemporalHistory.js +25 -7
- package/src/PathTracerApp.js +23 -6
- package/src/Processor/AssetLoader.js +9 -0
- package/src/Processor/PBRT/PBRTSceneBuilder.js +18 -6
- package/src/RenderSettings.js +11 -8
- package/src/Stages/MotionVector.js +8 -42
- package/src/Stages/NRD.js +30 -21
- package/src/Stages/NormalDepth.js +5 -17
- package/src/Stages/PathTracer.js +6 -2
- package/src/Stages/PathTracerStage.js +2 -0
- package/src/TSL/CameraRay.js +99 -14
- package/src/TSL/DebugKernel.js +2 -2
- package/src/TSL/GenerateKernel.js +2 -2
- package/src/index.js +4 -0
- package/src/managers/CameraManager.js +244 -56
- package/src/managers/DenoisingManager.js +4 -3
- package/src/managers/InteractionManager.js +30 -2
- package/src/managers/UniformManager.js +6 -4
- package/src/managers/VideoRenderManager.js +35 -16
- package/src/managers/ViewCamera.js +77 -0
- package/src/managers/WalkControls.js +262 -0
- package/src/managers/helpers/OutlineHelper.js +22 -2
- package/src/managers/timeline/CameraTrack.js +213 -0
- package/src/managers/timeline/TimelineManager.js +132 -0
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
import { EventDispatcher, PerspectiveCamera, Vector3 } from 'three';
|
|
1
|
+
import { EventDispatcher, MathUtils, OrthographicCamera, PerspectiveCamera, Vector3 } from 'three';
|
|
2
2
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
3
3
|
import { EngineEvents, } from '../EngineEvents.js';
|
|
4
|
-
import { AF_DEFAULTS } from '../EngineDefaults.js';
|
|
4
|
+
import { AF_DEFAULTS, CAMERA_PROJECTION_IDS } from '../EngineDefaults.js';
|
|
5
|
+
import { viewDepth } from './InteractionManager.js';
|
|
6
|
+
import { ViewCamera } from './ViewCamera.js';
|
|
7
|
+
import { WalkControls } from './WalkControls.js';
|
|
5
8
|
|
|
6
9
|
const DEFAULT_CAMERA_SCALE = Object.freeze( new Vector3( 1, 1, 1 ) );
|
|
7
10
|
|
|
11
|
+
/** The height of an orthographic camera's view in world units, or null for any other camera. */
|
|
12
|
+
const orthoHeightOf = camera => camera?.isOrthographicCamera ? ( camera.top - camera.bottom ) / camera.zoom : null;
|
|
13
|
+
|
|
8
14
|
/**
|
|
9
15
|
* Manages camera creation, switching, auto-focus, and AF point placement.
|
|
10
16
|
*
|
|
11
|
-
* Owns the
|
|
17
|
+
* Owns the {@link ViewCamera} and OrbitControls instances.
|
|
12
18
|
* Dispatches events that PathTracerApp relays to external consumers.
|
|
13
19
|
*/
|
|
14
20
|
export class CameraManager extends EventDispatcher {
|
|
@@ -23,7 +29,7 @@ export class CameraManager extends EventDispatcher {
|
|
|
23
29
|
const width = canvas.clientWidth;
|
|
24
30
|
const height = canvas.clientHeight;
|
|
25
31
|
|
|
26
|
-
this.camera = new
|
|
32
|
+
this.camera = new ViewCamera( 60, width / height || 1, 0.01, 1000 );
|
|
27
33
|
this.camera.position.set( 0, 0, 5 );
|
|
28
34
|
|
|
29
35
|
this.controls = new OrbitControls( this.camera, canvas );
|
|
@@ -31,9 +37,16 @@ export class CameraManager extends EventDispatcher {
|
|
|
31
37
|
this.controls.zoomToCursor = true;
|
|
32
38
|
this.controls.saveState();
|
|
33
39
|
|
|
40
|
+
// The wheel zooms an orthographic view: report its height.
|
|
41
|
+
this._reportedOrthoHeight = null;
|
|
42
|
+
this._reportOrthoHeight = this._reportOrthoHeight.bind( this );
|
|
43
|
+
this.controls.addEventListener( 'change', this._reportOrthoHeight );
|
|
44
|
+
|
|
45
|
+
this.walkControls = new WalkControls( this.camera, canvas, this.controls );
|
|
46
|
+
|
|
34
47
|
this.interactionManager = null;
|
|
35
48
|
|
|
36
|
-
/** @type {import('three').
|
|
49
|
+
/** @type {import('three').Camera[]} */
|
|
37
50
|
this.cameras = [ this.camera ];
|
|
38
51
|
this.currentCameraIndex = 0;
|
|
39
52
|
|
|
@@ -62,19 +75,20 @@ export class CameraManager extends EventDispatcher {
|
|
|
62
75
|
|
|
63
76
|
/**
|
|
64
77
|
* Sets the list of available cameras (default + extracted from model).
|
|
65
|
-
* @param {import('three').
|
|
78
|
+
* @param {import('three').Camera[]} cameras - perspective or orthographic
|
|
66
79
|
*/
|
|
67
80
|
setCameras( cameras ) {
|
|
68
81
|
|
|
69
82
|
this.cameras = cameras;
|
|
70
83
|
this._userCameraCounter = 0;
|
|
84
|
+
this.resetAutoFocus();
|
|
71
85
|
|
|
72
86
|
}
|
|
73
87
|
|
|
74
88
|
/**
|
|
75
89
|
* Adds a new user camera that snapshots the current render camera's pose,
|
|
76
|
-
* FOV, clip planes, and orbit target. The snapshot is a standalone
|
|
77
|
-
* appended to the list; switching to it restores this exact framing.
|
|
90
|
+
* FOV or orthographic size, clip planes, and orbit target. The snapshot is a standalone
|
|
91
|
+
* template appended to the list; switching to it restores this exact framing.
|
|
78
92
|
*
|
|
79
93
|
* @param {string} [name] - Optional display name (auto-generated otherwise).
|
|
80
94
|
* @returns {number} The index of the newly added camera.
|
|
@@ -82,7 +96,10 @@ export class CameraManager extends EventDispatcher {
|
|
|
82
96
|
addCameraFromView( name ) {
|
|
83
97
|
|
|
84
98
|
const src = this.camera;
|
|
85
|
-
const cam =
|
|
99
|
+
const cam = src.orthographic
|
|
100
|
+
? new OrthographicCamera( src.left, src.right, src.top, src.bottom, src.near, src.far )
|
|
101
|
+
: new PerspectiveCamera( src.fov, src.aspect, src.near, src.far );
|
|
102
|
+
cam.zoom = src.zoom;
|
|
86
103
|
cam.position.copy( src.position );
|
|
87
104
|
cam.quaternion.copy( src.quaternion );
|
|
88
105
|
cam.scale.copy( src.scale );
|
|
@@ -193,8 +210,10 @@ export class CameraManager extends EventDispatcher {
|
|
|
193
210
|
focalLength: get( 'focalLength' ),
|
|
194
211
|
apertureScale: get( 'apertureScale' ),
|
|
195
212
|
anamorphicRatio: get( 'anamorphicRatio' ),
|
|
213
|
+
dofBlur: get( 'dofBlur' ),
|
|
196
214
|
autoFocusMode: this.autoFocusMode,
|
|
197
215
|
afScreenPoint: { ...this.afScreenPoint },
|
|
216
|
+
orthoHeight: this.orthoHeight,
|
|
198
217
|
};
|
|
199
218
|
|
|
200
219
|
}
|
|
@@ -214,6 +233,7 @@ export class CameraManager extends EventDispatcher {
|
|
|
214
233
|
focalLength: eff.focalLength,
|
|
215
234
|
apertureScale: eff.apertureScale,
|
|
216
235
|
anamorphicRatio: eff.anamorphicRatio,
|
|
236
|
+
dofBlur: eff.dofBlur,
|
|
217
237
|
} );
|
|
218
238
|
|
|
219
239
|
if ( eff.autoFocusMode !== undefined ) this.setAutoFocusMode( eff.autoFocusMode );
|
|
@@ -221,13 +241,6 @@ export class CameraManager extends EventDispatcher {
|
|
|
221
241
|
|
|
222
242
|
}
|
|
223
243
|
|
|
224
|
-
/** Scene scale used to convert scaled focusDistance to UI-facing units. */
|
|
225
|
-
_getSceneScale() {
|
|
226
|
-
|
|
227
|
-
return this._afContext?.assetLoader?.getSceneScale?.() || 1;
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
|
|
231
244
|
/**
|
|
232
245
|
* Switches the active camera by index.
|
|
233
246
|
* Uses stored callbacks from initCallbacks() for resize/reset.
|
|
@@ -305,7 +318,7 @@ export class CameraManager extends EventDispatcher {
|
|
|
305
318
|
// quaternion can express — a pbrt scene's `Scale -1 1 1`, for one. Copying
|
|
306
319
|
// pose alone would silently un-mirror the view.
|
|
307
320
|
this.camera.scale.copy( sourceCamera.scale );
|
|
308
|
-
this.camera.fov = sourceCamera.fov;
|
|
321
|
+
if ( sourceCamera.isPerspectiveCamera ) this.camera.fov = sourceCamera.fov;
|
|
309
322
|
this.camera.near = sourceCamera.near;
|
|
310
323
|
this.camera.far = sourceCamera.far;
|
|
311
324
|
this.camera.updateProjectionMatrix();
|
|
@@ -334,18 +347,121 @@ export class CameraManager extends EventDispatcher {
|
|
|
334
347
|
|
|
335
348
|
}
|
|
336
349
|
|
|
350
|
+
this.resetAutoFocus();
|
|
351
|
+
|
|
352
|
+
// A camera left orthographic comes back orthographic; one never visited is what it was made as.
|
|
353
|
+
const incoming = this.cameras[ index ];
|
|
354
|
+
const saved = incoming?.userData?.__rayzeeEffects;
|
|
355
|
+
this._showOrthographic( saved ? saved.orthoHeight : orthoHeightOf( incoming ) );
|
|
356
|
+
|
|
337
357
|
// Restore the incoming camera's own DOF/focus effects (if it has any saved;
|
|
338
358
|
// otherwise it inherits the current global config).
|
|
339
|
-
this._applyEffects(
|
|
359
|
+
this._applyEffects( saved );
|
|
340
360
|
|
|
341
361
|
onResize?.();
|
|
342
362
|
onReset?.();
|
|
343
363
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
364
|
+
this.dispatchEvent( {
|
|
365
|
+
type: 'CameraSwitched', cameraIndex: index, effects: this._captureEffects(), fov: this.camera.fov,
|
|
366
|
+
cameraProjection: this._getSettings?.( 'cameraProjection' ),
|
|
367
|
+
} );
|
|
368
|
+
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Makes the camera orthographic at `height`, or perspective for null, then hands the `cameraProjection`
|
|
373
|
+
* setting the outcome — after, so its handler finds the camera already there. A 360° panorama stays on
|
|
374
|
+
* across perspective cameras.
|
|
375
|
+
* @param {?number} height - in world units
|
|
376
|
+
*/
|
|
377
|
+
_showOrthographic( height ) {
|
|
378
|
+
|
|
379
|
+
if ( height > 0 ) this._setView( true, height / 2 );
|
|
380
|
+
else this._setView( false );
|
|
381
|
+
|
|
382
|
+
const current = this._getSettings?.( 'cameraProjection' );
|
|
383
|
+
const next = height > 0 ? 'orthographic' : current === 'orthographic' ? 'perspective' : current;
|
|
384
|
+
if ( next !== current ) this._applySettings?.( { cameraProjection: next } );
|
|
385
|
+
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* The camera's side of the `cameraProjection` setting: set that, not this. Turning orthographic
|
|
390
|
+
* keeps what the view shows at the orbit target, and turning back moves the camera to keep it.
|
|
391
|
+
* @param {'perspective' | 'orthographic' | 'equirectangular'} projection
|
|
392
|
+
*/
|
|
393
|
+
applyProjection( projection ) {
|
|
394
|
+
|
|
395
|
+
const camera = this.camera;
|
|
396
|
+
const orthographic = projection === 'orthographic';
|
|
397
|
+
if ( camera.orthographic === orthographic ) return;
|
|
398
|
+
|
|
399
|
+
if ( orthographic ) {
|
|
400
|
+
|
|
401
|
+
this._setView( true, this._fittedHalfHeight() );
|
|
402
|
+
|
|
403
|
+
} else {
|
|
404
|
+
|
|
405
|
+
const target = this.controls.target;
|
|
406
|
+
const distance = this.orthoHeight / 2 / Math.tan( MathUtils.degToRad( camera.fov ) / 2 );
|
|
407
|
+
camera.position.sub( target ).setLength( distance ).add( target );
|
|
408
|
+
this._setView( false );
|
|
409
|
+
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
this.controls.update();
|
|
413
|
+
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** The height of the view in world units while orthographic, the wheel's zoom included; null otherwise. */
|
|
417
|
+
get orthoHeight() {
|
|
418
|
+
|
|
419
|
+
return orthoHeightOf( this.camera );
|
|
420
|
+
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/** @param {number} height - the orthographic view's height, in world units */
|
|
424
|
+
setOrthoHeight( height ) {
|
|
425
|
+
|
|
426
|
+
if ( ! ( height > 0 ) ) return;
|
|
427
|
+
this._setView( this.camera.orthographic, height / 2 );
|
|
428
|
+
this._onReset?.();
|
|
429
|
+
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** Sizes an orthographic view to show what a perspective one would at the orbit target. */
|
|
433
|
+
fitOrthographic() {
|
|
434
|
+
|
|
435
|
+
if ( this.camera.orthographic ) this.setOrthoHeight( 2 * this._fittedHalfHeight() );
|
|
436
|
+
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// The one writer of the camera's projection. The wheel's zoom starts over.
|
|
440
|
+
_setView( orthographic, halfHeight = this.camera.orthoHalfHeight ) {
|
|
441
|
+
|
|
442
|
+
const camera = this.camera;
|
|
443
|
+
camera.orthographic = orthographic;
|
|
444
|
+
camera.orthoHalfHeight = halfHeight;
|
|
445
|
+
camera.zoom = 1;
|
|
446
|
+
camera.updateProjectionMatrix();
|
|
447
|
+
this._reportOrthoHeight();
|
|
448
|
+
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Half of what a perspective view shows at the orbit target.
|
|
452
|
+
_fittedHalfHeight() {
|
|
453
|
+
|
|
454
|
+
return this.camera.position.distanceTo( this.controls.target ) * Math.tan( MathUtils.degToRad( this.camera.fov ) / 2 );
|
|
455
|
+
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
_reportOrthoHeight() {
|
|
459
|
+
|
|
460
|
+
if ( ! this.camera.orthographic ) return;
|
|
461
|
+
const height = this.orthoHeight;
|
|
462
|
+
if ( height === this._reportedOrthoHeight ) return;
|
|
463
|
+
this._reportedOrthoHeight = height;
|
|
464
|
+
this.dispatchEvent( { type: EngineEvents.ORTHO_HEIGHT_UPDATED, height } );
|
|
349
465
|
|
|
350
466
|
}
|
|
351
467
|
|
|
@@ -362,9 +478,88 @@ export class CameraManager extends EventDispatcher {
|
|
|
362
478
|
|
|
363
479
|
}
|
|
364
480
|
|
|
481
|
+
/**
|
|
482
|
+
* Orbit around a target, or walk through the scene first-person (see {@link WalkControls}).
|
|
483
|
+
* Leaving walk mode orbits around the surface at the centre of the view.
|
|
484
|
+
* @param {'orbit' | 'walk'} mode
|
|
485
|
+
*/
|
|
486
|
+
setNavigationMode( mode ) {
|
|
487
|
+
|
|
488
|
+
const walk = mode === 'walk';
|
|
489
|
+
if ( this.walkControls.enabled === walk ) return;
|
|
490
|
+
|
|
491
|
+
const controls = this.controls;
|
|
492
|
+
controls.enableRotate = controls.enablePan = controls.enableZoom = ! walk;
|
|
493
|
+
this.walkControls.enabled = walk;
|
|
494
|
+
if ( walk ) return;
|
|
495
|
+
|
|
496
|
+
this.walkControls.release();
|
|
497
|
+
const hit = this.interactionManager?.pickSurface( 0, 0 );
|
|
498
|
+
if ( ! hit ) return;
|
|
499
|
+
|
|
500
|
+
const distance = MathUtils.clamp( hit.distance, controls.minDistance, controls.maxDistance );
|
|
501
|
+
controls.target.copy( this.camera.position ).addScaledVector( this.camera.getWorldDirection( new Vector3() ), distance );
|
|
502
|
+
controls.update();
|
|
503
|
+
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/** @returns {'orbit' | 'walk'} */
|
|
507
|
+
get navigationMode() {
|
|
508
|
+
|
|
509
|
+
return this.walkControls.enabled ? 'walk' : 'orbit';
|
|
510
|
+
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/** Per frame, before rendering. */
|
|
514
|
+
updateControls() {
|
|
515
|
+
|
|
516
|
+
this.walkControls.update();
|
|
517
|
+
this.controls.update();
|
|
518
|
+
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* The current view as a pose: where the camera is, what it looks at, and how much it shows — the
|
|
523
|
+
* height an orthographic view covers, or what a perspective one covers at its orbit target.
|
|
524
|
+
* @returns {import('./timeline/CameraTrack.js').CameraPose}
|
|
525
|
+
*/
|
|
526
|
+
captureView() {
|
|
527
|
+
|
|
528
|
+
const camera = this.camera;
|
|
529
|
+
const orthoHeight = this.orthoHeight ?? 2 * this._fittedHalfHeight();
|
|
530
|
+
return { position: camera.position.clone(), target: this.controls.target.clone(), fov: camera.fov, orthoHeight };
|
|
531
|
+
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Puts the camera at a pose, looking at its target, sized for the projection in use.
|
|
536
|
+
* @param {import('./timeline/CameraTrack.js').CameraPose} pose
|
|
537
|
+
*/
|
|
538
|
+
applyPose( pose ) {
|
|
539
|
+
|
|
540
|
+
const camera = this.camera;
|
|
541
|
+
camera.position.copy( pose.position );
|
|
542
|
+
camera.lookAt( pose.target );
|
|
543
|
+
this.controls.target.copy( pose.target );
|
|
544
|
+
|
|
545
|
+
if ( camera.orthographic ) {
|
|
546
|
+
|
|
547
|
+
this._setView( true, pose.orthoHeight / 2 );
|
|
548
|
+
|
|
549
|
+
} else {
|
|
550
|
+
|
|
551
|
+
camera.fov = pose.fov;
|
|
552
|
+
camera.updateProjectionMatrix();
|
|
553
|
+
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
camera.updateMatrixWorld();
|
|
557
|
+
|
|
558
|
+
}
|
|
559
|
+
|
|
365
560
|
// ── Aliases (match Sub-API surface) ───────────────────────────
|
|
366
561
|
|
|
367
|
-
/** The active
|
|
562
|
+
/** The active camera, a {@link ViewCamera}. */
|
|
368
563
|
get active() {
|
|
369
564
|
|
|
370
565
|
return this.camera;
|
|
@@ -393,6 +588,15 @@ export class CameraManager extends EventDispatcher {
|
|
|
393
588
|
|
|
394
589
|
}
|
|
395
590
|
|
|
591
|
+
/** Forget what auto-focus last measured: it belonged to another model or viewpoint. */
|
|
592
|
+
resetAutoFocus() {
|
|
593
|
+
|
|
594
|
+
this._lastValidFocusDistance = null;
|
|
595
|
+
this._smoothedFocusDistance = null;
|
|
596
|
+
this._afPointDirty = true;
|
|
597
|
+
|
|
598
|
+
}
|
|
599
|
+
|
|
396
600
|
setAFScreenPoint( x, y ) {
|
|
397
601
|
|
|
398
602
|
this.afScreenPoint = { x, y };
|
|
@@ -420,9 +624,7 @@ export class CameraManager extends EventDispatcher {
|
|
|
420
624
|
* Per-frame auto-focus update. Called in animate() before pipeline.render().
|
|
421
625
|
*
|
|
422
626
|
* @param {Object} params
|
|
423
|
-
* @param {import('three').Scene} params.meshScene
|
|
424
627
|
* @param {Object} params.assetLoader
|
|
425
|
-
* @param {import('three').Mesh} params.floorPlane
|
|
426
628
|
* @param {number} params.currentFocusDistance
|
|
427
629
|
* @param {import('../Stages/PathTracer.js').PathTracer} params.pathTracer
|
|
428
630
|
* @param {Function} params.setFocusDistance - Callback to update uniform + settings
|
|
@@ -431,16 +633,18 @@ export class CameraManager extends EventDispatcher {
|
|
|
431
633
|
*/
|
|
432
634
|
updateAutoFocus( ctx ) {
|
|
433
635
|
|
|
434
|
-
const
|
|
435
|
-
if ( !
|
|
636
|
+
const context = ctx || this._afContext;
|
|
637
|
+
if ( ! context || ! this.interactionManager ) return;
|
|
638
|
+
const { assetLoader, currentFocusDistance, pathTracer, setFocusDistance, softReset, hardReset } = context;
|
|
436
639
|
|
|
437
640
|
if ( this.autoFocusMode === 'manual' ) return;
|
|
438
641
|
|
|
439
642
|
// Depth-of-field is the only consumer of the auto-focus distance. With DOF
|
|
440
643
|
// off (the default) the per-frame scene raycast is pure waste, so skip it.
|
|
441
|
-
//
|
|
644
|
+
// A panorama pauses it too: Raycaster.setFromCamera only knows a frustum.
|
|
645
|
+
// Re-snap on the frame it resumes so focus is correct immediately
|
|
442
646
|
// rather than racking from a stale smoothed value.
|
|
443
|
-
if ( ! pathTracer?.enableDOF?.value ) {
|
|
647
|
+
if ( ! pathTracer?.enableDOF?.value || pathTracer.cameraProjection?.value === CAMERA_PROJECTION_IDS.equirectangular ) {
|
|
444
648
|
|
|
445
649
|
this._afSuspended = true;
|
|
446
650
|
return;
|
|
@@ -461,27 +665,13 @@ export class CameraManager extends EventDispatcher {
|
|
|
461
665
|
&& stage.frameCount > 0
|
|
462
666
|
&& ! stage.isComplete ) return;
|
|
463
667
|
|
|
464
|
-
//
|
|
465
|
-
const
|
|
466
|
-
const ndcY = - ( this.afScreenPoint.y * 2 - 1 );
|
|
467
|
-
|
|
468
|
-
const raycaster = this.interactionManager?.raycaster;
|
|
469
|
-
if ( ! raycaster ) return;
|
|
470
|
-
|
|
471
|
-
raycaster.setFromCamera( { x: ndcX, y: ndcY }, this.camera );
|
|
472
|
-
const intersects = raycaster.intersectObjects( meshScene.children, true );
|
|
473
|
-
|
|
474
|
-
const validHit = intersects.find( hit =>
|
|
475
|
-
hit.object !== this.interactionManager?.focusPointIndicator &&
|
|
476
|
-
hit.object !== floorPlane &&
|
|
477
|
-
! hit.object.name.includes( 'Helper' ) &&
|
|
478
|
-
hit.object.type === 'Mesh'
|
|
479
|
-
);
|
|
668
|
+
// AF screen point (0 to 1, y down) to NDC.
|
|
669
|
+
const validHit = this.interactionManager.pickSurface( this.afScreenPoint.x * 2 - 1, 1 - this.afScreenPoint.y * 2 );
|
|
480
670
|
|
|
481
671
|
let rawDistance;
|
|
482
672
|
if ( validHit ) {
|
|
483
673
|
|
|
484
|
-
rawDistance = validHit.
|
|
674
|
+
rawDistance = viewDepth( validHit.point, this.camera );
|
|
485
675
|
this._lastValidFocusDistance = rawDistance;
|
|
486
676
|
|
|
487
677
|
} else {
|
|
@@ -492,8 +682,9 @@ export class CameraManager extends EventDispatcher {
|
|
|
492
682
|
|
|
493
683
|
} else {
|
|
494
684
|
|
|
495
|
-
|
|
496
|
-
|
|
685
|
+
// Nothing under the AF point on a fresh view: focus where the camera orbits.
|
|
686
|
+
const depth = this.controls ? viewDepth( this.controls.target, this.camera ) : 0;
|
|
687
|
+
rawDistance = depth > 0 ? depth : AF_DEFAULTS.FALLBACK_DISTANCE * ( assetLoader?.getSceneScale() || 1.0 );
|
|
497
688
|
this._lastValidFocusDistance = rawDistance;
|
|
498
689
|
|
|
499
690
|
}
|
|
@@ -533,9 +724,8 @@ export class CameraManager extends EventDispatcher {
|
|
|
533
724
|
|
|
534
725
|
setFocusDistance( newFocus );
|
|
535
726
|
|
|
536
|
-
// Update store for UI display (unscaled value)
|
|
537
727
|
const scale = assetLoader?.getSceneScale() || 1.0;
|
|
538
|
-
this.dispatchEvent( { type: EngineEvents.AUTO_FOCUS_UPDATED, distance: newFocus / scale } );
|
|
728
|
+
this.dispatchEvent( { type: EngineEvents.AUTO_FOCUS_UPDATED, distance: newFocus / scale, worldDistance: newFocus } );
|
|
539
729
|
|
|
540
730
|
const changeRatio = Math.abs( newFocus - prevFocus ) / Math.max( prevFocus, 0.001 );
|
|
541
731
|
if ( forceReset ) {
|
|
@@ -569,20 +759,16 @@ export class CameraManager extends EventDispatcher {
|
|
|
569
759
|
* and `updateAutoFocus()` reads from it each frame — no per-frame allocation.
|
|
570
760
|
*
|
|
571
761
|
* @param {Object} deps
|
|
572
|
-
* @param {import('three').Scene} deps.meshScene
|
|
573
762
|
* @param {import('../Processor/AssetLoader.js').AssetLoader} deps.assetLoader
|
|
574
|
-
* @param {import('three').Mesh} deps.floorPlane
|
|
575
763
|
* @param {import('../Stages/PathTracer.js').PathTracer} deps.pathTracer
|
|
576
764
|
* @param {import('../RenderSettings.js').RenderSettings} deps.settings
|
|
577
765
|
* @param {Function} deps.softReset
|
|
578
766
|
* @param {Function} deps.hardReset
|
|
579
767
|
*/
|
|
580
|
-
initAutoFocus( {
|
|
768
|
+
initAutoFocus( { assetLoader, pathTracer, settings, softReset, hardReset } ) {
|
|
581
769
|
|
|
582
770
|
this._afContext = {
|
|
583
|
-
meshScene,
|
|
584
771
|
assetLoader,
|
|
585
|
-
floorPlane,
|
|
586
772
|
pathTracer,
|
|
587
773
|
setFocusDistance: ( d ) => settings.set( 'focusDistance', d, { silent: true } ),
|
|
588
774
|
softReset,
|
|
@@ -598,6 +784,8 @@ export class CameraManager extends EventDispatcher {
|
|
|
598
784
|
|
|
599
785
|
dispose() {
|
|
600
786
|
|
|
787
|
+
this.walkControls?.dispose();
|
|
788
|
+
this.controls?.removeEventListener( 'change', this._reportOrthoHeight );
|
|
601
789
|
this.controls?.dispose();
|
|
602
790
|
|
|
603
791
|
}
|
|
@@ -8,7 +8,7 @@ import { createLogger } from '../utils/Logger.js';
|
|
|
8
8
|
// The neural passes live in `../neural/` but report through the manager that drives them, so they
|
|
9
9
|
// share one namespace: `rayzee.log.only( 'neural' )` shows the whole chain.
|
|
10
10
|
const neuralLog = createLogger( 'neural' );
|
|
11
|
-
import { ENGINE_DEFAULTS as DEFAULT_STATE, ASVGF_QUALITY_PRESETS, NRD_DEFAULTS, NRD_QUALITY_PRESETS, NRD_PRESET_KEYS } from '../EngineDefaults.js';
|
|
11
|
+
import { ENGINE_DEFAULTS as DEFAULT_STATE, ASVGF_QUALITY_PRESETS, NRD_DEFAULTS, NRD_QUALITY_PRESETS, NRD_PRESET_KEYS, CAMERA_PROJECTION_IDS } from '../EngineDefaults.js';
|
|
12
12
|
|
|
13
13
|
// A refresh slower than this is a slideshow, not a live view, so the cadence swaps to a cheaper
|
|
14
14
|
// model and puts the chosen one back for the finished image. Resolution-aware by construction:
|
|
@@ -164,7 +164,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
164
164
|
this._knownResetCount = 0;
|
|
165
165
|
this._seenTracedFrames = 0;
|
|
166
166
|
this._viewProj = new Matrix4();
|
|
167
|
-
this._historyCamera = { world: null, projInv: null, viewProj: null };
|
|
167
|
+
this._historyCamera = { world: null, projInv: null, viewProj: null, orthographic: false };
|
|
168
168
|
// TLAS leaf → { world, offset, prev }: placements moved since the last traced frame.
|
|
169
169
|
this._movedPlacements = new Map();
|
|
170
170
|
this._movedUpload = { count: 0, leaves: new Uint32Array( 0 ), toPrev: new Float32Array( 0 ) };
|
|
@@ -870,7 +870,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
870
870
|
|
|
871
871
|
const pt = this._stages.pathTracer;
|
|
872
872
|
return this.historyWanted && ! this._cadenceSuspended && !! this._stages.normalDepth?.enabled
|
|
873
|
-
&&
|
|
873
|
+
&& pt?.uniforms?.get( 'cameraProjection' )?.value !== CAMERA_PROJECTION_IDS.equirectangular
|
|
874
874
|
&& ! ( pt?.visMode?.value > 0 );
|
|
875
875
|
|
|
876
876
|
}
|
|
@@ -1043,6 +1043,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1043
1043
|
camera.world = pt.cameraWorldMatrix.value.elements;
|
|
1044
1044
|
camera.projInv = pt.cameraProjectionMatrixInverse.value.elements;
|
|
1045
1045
|
camera.viewProj = this._viewProj.elements;
|
|
1046
|
+
camera.orthographic = pt.uniforms?.get( 'cameraProjection' )?.value === CAMERA_PROJECTION_IDS.orthographic;
|
|
1046
1047
|
return camera;
|
|
1047
1048
|
|
|
1048
1049
|
}
|
|
@@ -3,9 +3,28 @@ import {
|
|
|
3
3
|
Raycaster,
|
|
4
4
|
SphereGeometry,
|
|
5
5
|
Mesh,
|
|
6
|
-
MeshBasicMaterial
|
|
6
|
+
MeshBasicMaterial,
|
|
7
|
+
Vector3
|
|
7
8
|
} from 'three';
|
|
8
9
|
import { EngineEvents } from '../EngineEvents.js';
|
|
10
|
+
import { CAMERA_PROJECTION_IDS } from '../EngineDefaults.js';
|
|
11
|
+
|
|
12
|
+
const _viewAxis = new Vector3();
|
|
13
|
+
const _toPoint = new Vector3();
|
|
14
|
+
|
|
15
|
+
/** Depth of a point along the camera's view axis — the distance the flat focal plane is measured in. */
|
|
16
|
+
export function viewDepth( point, camera ) {
|
|
17
|
+
|
|
18
|
+
return _toPoint.subVectors( point, camera.position ).dot( camera.getWorldDirection( _viewAxis ) );
|
|
19
|
+
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The focus distance that puts a raycast hit in focus: a panorama focuses along each ray, not on a plane. */
|
|
23
|
+
export function focusDistanceOfHit( hit, camera, panorama = false ) {
|
|
24
|
+
|
|
25
|
+
return panorama ? hit.distance : viewDepth( hit.point, camera );
|
|
26
|
+
|
|
27
|
+
}
|
|
9
28
|
|
|
10
29
|
/**
|
|
11
30
|
* InteractionManager
|
|
@@ -207,7 +226,8 @@ export class InteractionManager extends EventDispatcher {
|
|
|
207
226
|
if ( intersects.length > 0 ) {
|
|
208
227
|
|
|
209
228
|
const intersection = intersects[ 0 ];
|
|
210
|
-
const
|
|
229
|
+
const panorama = this.pathTracer?.cameraProjection?.value === CAMERA_PROJECTION_IDS.equirectangular;
|
|
230
|
+
const distance = focusDistanceOfHit( intersection, this.camera, panorama );
|
|
211
231
|
|
|
212
232
|
// Show visual indicator at focus point
|
|
213
233
|
this.showFocusPoint( intersection.point );
|
|
@@ -675,6 +695,14 @@ export class InteractionManager extends EventDispatcher {
|
|
|
675
695
|
|
|
676
696
|
}
|
|
677
697
|
|
|
698
|
+
/** The nearest scene surface under an NDC point, skipping the floor and helpers. */
|
|
699
|
+
pickSurface( ndcX, ndcY ) {
|
|
700
|
+
|
|
701
|
+
this.raycaster.setFromCamera( { x: ndcX, y: ndcY }, this.camera );
|
|
702
|
+
return this.filterValidIntersects( this.raycaster.intersectObjects( this.scene.children, true ) )[ 0 ] ?? null;
|
|
703
|
+
|
|
704
|
+
}
|
|
705
|
+
|
|
678
706
|
/**
|
|
679
707
|
* Filter intersections to exclude helper objects and floor plane
|
|
680
708
|
* @private
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { uniform, uniformArray } from 'three/tsl';
|
|
9
9
|
import { Vector2, Matrix4, Vector3, Color, MathUtils } from 'three';
|
|
10
10
|
import { samplingTechniqueUniform } from '../TSL/Random.js';
|
|
11
|
-
import { ENGINE_DEFAULTS as DEFAULT_STATE } from '../EngineDefaults.js';
|
|
11
|
+
import { ENGINE_DEFAULTS as DEFAULT_STATE, CAMERA_PROJECTION_IDS } from '../EngineDefaults.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Map of uniform names to their WGSL shader names (where different).
|
|
@@ -278,9 +278,9 @@ export class UniformManager {
|
|
|
278
278
|
u( 'cameraViewMatrix', new Matrix4(), 'mat4' );
|
|
279
279
|
u( 'cameraProjectionMatrix', new Matrix4(), 'mat4' );
|
|
280
280
|
|
|
281
|
-
// Projection:
|
|
281
|
+
// Projection: CAMERA_PROJECTION_IDS. Ranges are radians (UI carries degrees).
|
|
282
282
|
const radRange = ( [ min, max ] ) => new Vector2( min, max ).multiplyScalar( MathUtils.DEG2RAD );
|
|
283
|
-
u( 'cameraProjection', DEFAULT_STATE.cameraProjection
|
|
283
|
+
u( 'cameraProjection', CAMERA_PROJECTION_IDS[ DEFAULT_STATE.cameraProjection ], 'int' );
|
|
284
284
|
u( 'panoLonRange', radRange( DEFAULT_STATE.panoramaLonRange ), 'vec2' );
|
|
285
285
|
u( 'panoLatRange', radRange( DEFAULT_STATE.panoramaLatRange ), 'vec2' );
|
|
286
286
|
ub( 'panoLevelHorizon', DEFAULT_STATE.panoramaLevelHorizon );
|
|
@@ -292,7 +292,9 @@ export class UniformManager {
|
|
|
292
292
|
u( 'aperture', DEFAULT_STATE.aperture, 'float' );
|
|
293
293
|
u( 'apertureScale', 1.0, 'float' );
|
|
294
294
|
u( 'anamorphicRatio', DEFAULT_STATE.anamorphicRatio ?? 1.0, 'float' );
|
|
295
|
-
u( '
|
|
295
|
+
u( 'unitsPerMetre', DEFAULT_STATE.unitsPerMetre, 'float' );
|
|
296
|
+
u( 'dofMode', DEFAULT_STATE.dofMode === 'look' ? 1 : 0, 'int' );
|
|
297
|
+
u( 'dofBlur', DEFAULT_STATE.dofBlur, 'float' );
|
|
296
298
|
|
|
297
299
|
// Sampling — use the module-level uniform from Random.js so TSL sees the same node
|
|
298
300
|
this._uniforms.set( 'samplingTechnique', samplingTechniqueUniform );
|