rayzee 10.0.0 → 11.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.
Files changed (173) hide show
  1. package/README.md +252 -44
  2. package/dist/ActiveColor-YvuQq44O.js +13 -0
  3. package/dist/ArchiveImporter-BRQvLggb.js +4300 -0
  4. package/dist/AssetConfig-DSEntE65.js +30 -0
  5. package/dist/BidirectionalIntegrator-9lZQxFVH.js +934 -0
  6. package/dist/BufferLayout-D_IBo2ci.js +150 -0
  7. package/dist/ColorManagement-oeEk7oSu.js +1488 -0
  8. package/dist/EngineEvents-Ci8dVWCP.js +73 -0
  9. package/dist/EngineIssues-CIZnXHsF.js +72 -0
  10. package/dist/FileFormats-sPGJeCRc.js +102 -0
  11. package/dist/Logger-dDDGj9Av.js +97 -0
  12. package/dist/{NeuralRetouch-CYgkQY8Z.js → NeuralRetouch-BCXylThH.js} +8 -6
  13. package/dist/{NeuralSuperRes-PkgDLQcD.js → NeuralSuperRes-sZAeS142.js} +55 -120
  14. package/dist/PathTracer-CQPW06Mm.js +29971 -0
  15. package/dist/PhysicalSky-BvqCw_dy.js +1219 -0
  16. package/dist/RayzeeRenderer-COpwotpU.js +3551 -0
  17. package/dist/SolarLimb-CPaeZVwz.js +721 -0
  18. package/dist/ToneMapGPU-CjU3osDd.js +177 -0
  19. package/dist/ViewTransforms-CYN7bq2w.js +255 -0
  20. package/dist/WorkingMatrix-C6y233hT.js +27 -0
  21. package/dist/ZipReader-gDSWr0I5.js +98 -0
  22. package/dist/addons/archives.es.js +3 -0
  23. package/dist/addons/bidirectional.es.js +2 -0
  24. package/dist/addons/color.es.js +3 -0
  25. package/dist/addons/formats.es.js +2 -0
  26. package/dist/addons/physical-sky.es.js +2 -0
  27. package/dist/addons/storage.es.js +3 -0
  28. package/dist/archiveFormats-BZw7TiDp.js +75 -0
  29. package/dist/identity-DrQsGT2o.js +39 -0
  30. package/dist/openStorage-DwcBRUj7.js +820 -0
  31. package/dist/rayzee-core.es.js +12 -0
  32. package/dist/rayzee.es.js +2551 -44024
  33. package/dist/rayzee.umd.js +528 -487
  34. package/dist/sceneCachePolicy-BSSsKY98.js +443 -0
  35. package/dist/shared-BGBcSxBQ.js +17 -0
  36. package/package.json +23 -2
  37. package/src/Color/ActiveColor.js +34 -0
  38. package/src/Color/BasicColor.js +147 -0
  39. package/src/Color/ColorManagement.js +7 -31
  40. package/src/Color/ViewTransforms.js +5 -1
  41. package/src/EngineDefaults.js +62 -1029
  42. package/src/EngineEvents.js +2 -0
  43. package/src/EngineIssues.js +2 -0
  44. package/src/Headless.js +6 -5
  45. package/src/Passes/AIUpscaler.js +58 -141
  46. package/src/Passes/OIDNDenoiser.js +92 -65
  47. package/src/Passes/OIDNTemporalHistory.js +1 -1
  48. package/src/PathTracerApp.js +620 -4026
  49. package/src/Pipeline/PipelineContext.js +0 -4
  50. package/src/Platform.js +36 -0
  51. package/src/Processor/ArchiveCache.js +1 -1
  52. package/src/Processor/ArchiveImporter.js +1259 -0
  53. package/src/Processor/AssetLoader.js +327 -1763
  54. package/src/Processor/AtmosphereModel.js +1 -2
  55. package/src/Processor/BVHBuilder.js +14 -126
  56. package/src/Processor/BVHLeafFold.js +4 -6
  57. package/src/Processor/BVHRefitter.js +67 -46
  58. package/src/Processor/BufferLayout.js +291 -0
  59. package/src/Processor/EmissiveTriangleBuilder.js +1 -1
  60. package/src/Processor/EnvironmentExactTable.js +187 -0
  61. package/src/Processor/EquirectHDRInfo.js +30 -223
  62. package/src/Processor/FileFormats.js +116 -0
  63. package/src/Processor/GLTFDecoders.js +85 -0
  64. package/src/Processor/GLTFDiffuseTransmission.js +63 -0
  65. package/src/Processor/GeometryExtractor.js +15 -5
  66. package/src/Processor/HostMemory.js +7 -1
  67. package/src/Processor/IESParser.js +47 -35
  68. package/src/Processor/InstanceTable.js +1 -1
  69. package/src/Processor/KernelManager.js +35 -2
  70. package/src/Processor/MaterialPacking.js +10 -8
  71. package/src/Processor/PBRT/PBRTMaterials.js +129 -41
  72. package/src/Processor/PBRT/PBRTParser.js +30 -17
  73. package/src/Processor/PBRT/PBRTSceneBuilder.js +548 -37
  74. package/src/Processor/PBRT/PBRTTextureBake.js +154 -0
  75. package/src/Processor/PBRT/PFM.js +59 -0
  76. package/src/Processor/PackedRayBuffer.js +116 -9
  77. package/src/Processor/ParallelBVHBuilder.js +3 -6
  78. package/src/Processor/PhysicalSky.js +5 -4
  79. package/src/Processor/PlatformImageLoader.js +50 -6
  80. package/src/Processor/QueueManager.js +39 -5
  81. package/src/Processor/ReinsertionOptimizer.js +2 -2
  82. package/src/Processor/SceneProcessor.js +53 -62
  83. package/src/Processor/ShaderBuilder.js +6 -16
  84. package/src/Processor/SimpleSky.js +1 -62
  85. package/src/Processor/SolarLimb.js +3 -0
  86. package/src/Processor/SplitBLAS.js +1 -1
  87. package/src/Processor/StorageTexturePool.js +22 -1
  88. package/src/Processor/TLASBuilder.js +1 -3
  89. package/src/Processor/TextureBuckets.js +237 -0
  90. package/src/Processor/TextureCreator.js +181 -8
  91. package/src/Processor/ToneMapGPU.js +64 -15
  92. package/src/Processor/Workers/BVHRefitWorker.js +5 -2
  93. package/src/Processor/Workers/BVHSubtreeWorker.js +1 -34
  94. package/src/Processor/Workers/BVHWorker.js +5 -18
  95. package/src/Processor/Workers/CDFWorker.js +6 -183
  96. package/src/Processor/Workers/TexturesWorker.js +103 -18
  97. package/src/Processor/archiveFormats.js +7 -0
  98. package/src/Processor/utils.js +2 -1
  99. package/src/RayzeeRenderer.js +3899 -0
  100. package/src/RenderSettings.js +68 -62
  101. package/src/Stages/ASVGF.js +13 -14
  102. package/src/Stages/AutoExposure.js +17 -17
  103. package/src/Stages/BilateralFilter.js +2 -1
  104. package/src/Stages/Compositor.js +8 -23
  105. package/src/Stages/DenoiserSettings.js +182 -0
  106. package/src/Stages/EdgeFilter.js +2 -1
  107. package/src/Stages/MotionVector.js +1 -1
  108. package/src/Stages/NRD.js +11 -5
  109. package/src/Stages/NormalDepth.js +12 -8
  110. package/src/Stages/PathTracer.js +338 -43
  111. package/src/Stages/PathTracerStage.js +69 -66
  112. package/src/Stages/Variance.js +1 -1
  113. package/src/Storage/BLASCache.js +1 -1
  114. package/src/Storage/CDFCache.js +18 -17
  115. package/src/Storage/DownloadCache.js +1 -1
  116. package/src/Storage/SceneGraphCodec.js +46 -3
  117. package/src/Storage/SpillStore.js +1 -1
  118. package/src/Storage/StorageManager.js +45 -9
  119. package/src/Storage/areas.js +4 -0
  120. package/src/TSL/BVHTraversal.js +50 -25
  121. package/src/TSL/Bidirectional.js +204 -0
  122. package/src/TSL/BidirectionalLamps.js +132 -0
  123. package/src/TSL/Clearcoat.js +15 -6
  124. package/src/TSL/Common.js +20 -15
  125. package/src/TSL/ConnectKernel.js +147 -0
  126. package/src/TSL/Debugger.js +1 -1
  127. package/src/TSL/Displacement.js +4 -3
  128. package/src/TSL/EmissiveSampling.js +65 -18
  129. package/src/TSL/Environment.js +77 -106
  130. package/src/TSL/EnvironmentCDF.js +85 -39
  131. package/src/TSL/ExtendKernel.js +8 -3
  132. package/src/TSL/FinalWriteKernel.js +4 -4
  133. package/src/TSL/GenerateKernel.js +18 -4
  134. package/src/TSL/HitFacet.js +35 -5
  135. package/src/TSL/LightBVHSampling.js +40 -9
  136. package/src/TSL/LightGenerateKernel.js +293 -0
  137. package/src/TSL/LightGuide.js +198 -0
  138. package/src/TSL/LightSplatKernel.js +165 -0
  139. package/src/TSL/LightVisibility.js +111 -0
  140. package/src/TSL/LightsCore.js +13 -38
  141. package/src/TSL/LightsDirect.js +28 -29
  142. package/src/TSL/LightsIndirect.js +3 -1
  143. package/src/TSL/LightsSampling.js +239 -51
  144. package/src/TSL/MaterialEvaluation.js +113 -31
  145. package/src/TSL/MaterialProperties.js +44 -17
  146. package/src/TSL/MaterialTransmission.js +52 -47
  147. package/src/TSL/MergeKernel.js +218 -0
  148. package/src/TSL/PathTracerCore.js +65 -29
  149. package/src/TSL/Random.js +5 -1
  150. package/src/TSL/SceneResources.js +51 -0
  151. package/src/TSL/ShadeKernel.js +964 -101
  152. package/src/TSL/Struct.js +10 -0
  153. package/src/TSL/Sun.js +1 -1
  154. package/src/TSL/TextureSampling.js +66 -61
  155. package/src/addons/archives.js +14 -0
  156. package/src/addons/bidirectional.js +13 -0
  157. package/src/addons/color.js +32 -0
  158. package/src/addons/formats.js +16 -0
  159. package/src/addons/physicalSky.js +14 -0
  160. package/src/addons/storage.js +19 -0
  161. package/src/core.js +31 -0
  162. package/src/index.js +13 -23
  163. package/src/integrators/BidirectionalIntegrator.js +833 -0
  164. package/src/managers/AnimationManager.js +88 -6
  165. package/src/managers/CameraManager.js +103 -46
  166. package/src/managers/DenoisingManager.js +14 -10
  167. package/src/managers/EnvironmentManager.js +125 -103
  168. package/src/managers/GoboManager.js +9 -0
  169. package/src/managers/MaterialDataManager.js +48 -37
  170. package/src/managers/UniformManager.js +32 -10
  171. package/dist/ToneMapGPU-B6XIe3DU.js +0 -544
  172. package/dist/rolldown-runtime-CAFD8bLK.js +0 -11
  173. package/src/Processor/TreeletOptimizer.js +0 -574
@@ -10,6 +10,23 @@
10
10
  import { AnimationMixer, EventDispatcher, Timer, Vector3, LoopRepeat, LoopOnce, PropertyBinding } from 'three';
11
11
  import { EngineEvents } from '../EngineEvents.js';
12
12
 
13
+ /** The nodes of `nodes` with no ancestor in it, or null when updating each would cost more than one pass over `root`. */
14
+ function subtreeRoots( nodes, root ) {
15
+
16
+ const roots = [ ...nodes ].filter( node => {
17
+
18
+ for ( let o = node.parent; o; o = o.parent ) if ( nodes.has( o ) ) return false;
19
+ return true;
20
+
21
+ } );
22
+
23
+ let total = 0, covered = 0;
24
+ root.traverse?.( () => total ++ );
25
+ for ( const node of roots ) node.traverse?.( () => covered ++ );
26
+ return covered + roots.length / 2 < total ? roots : null;
27
+
28
+ }
29
+
13
30
  export class AnimationManager extends EventDispatcher {
14
31
 
15
32
  constructor() {
@@ -39,6 +56,8 @@ export class AnimationManager extends EventDispatcher {
39
56
  this._slotOf = null; // meshIndex -> row in the two caches below, or -1
40
57
  this._lastWorld = null; // world matrix last handed to applyPoseCallback, 16 per row
41
58
  this._lastVisible = null; // 0 / 1, or 2 before the first pose
59
+ this._lastMeshWorld = null; // deforming clips: world matrix at each mesh's last read, 16 per mesh
60
+ this._lastMorphWeights = null;
42
61
 
43
62
  /** Injected by PathTracerApp — wakes the render loop after play/resume. */
44
63
  this.wakeCallback = null;
@@ -135,6 +154,8 @@ export class AnimationManager extends EventDispatcher {
135
154
  // 9 floats × every triangle — 1,030 MB at 30M, past what a renderer can allocate.
136
155
  this._skinnedCache = [];
137
156
  this._meshPositions = [];
157
+ this._lastMeshWorld = this._rigid ? null : new Float64Array( meshes.length * 16 ).fill( NaN );
158
+ this._lastMorphWeights = [];
138
159
 
139
160
  const skinnedCount = meshes.filter( m => m.isSkinnedMesh ).length;
140
161
  console.debug( `[AnimationManager] Init: ${animations.length} clips, ${meshes.length} meshes (${skinnedCount} skinned), ${offset} triangles, ${this._rigid ? 'rigid' : 'deforming'}` );
@@ -158,11 +179,19 @@ export class AnimationManager extends EventDispatcher {
158
179
 
159
180
  const animated = new Set();
160
181
  let visibility = false;
182
+ let unresolved = false;
161
183
  for ( const track of clip.tracks ) {
162
184
 
163
185
  const { nodeName, propertyName } = PropertyBinding.parseTrackName( track.name );
164
- const node = byName.get( nodeName );
165
- if ( ! node ) continue;
186
+ // As PropertyBinding.findNode: no node name binds the root itself.
187
+ const node = nodeName === '' || nodeName === '.' ? root : byName.get( nodeName );
188
+ if ( ! node ) {
189
+
190
+ unresolved = true;
191
+ continue;
192
+
193
+ }
194
+
166
195
  animated.add( node );
167
196
  if ( propertyName === 'visible' ) visibility = true;
168
197
 
@@ -187,7 +216,7 @@ export class AnimationManager extends EventDispatcher {
187
216
  const cameras = new Set();
188
217
  for ( const node of animated ) node.traverse( o => o.isCamera && cameras.add( o ) );
189
218
 
190
- return { meshes: Int32Array.from( meshes ), visibility, cameras: [ ...cameras ] };
219
+ return { meshes: Int32Array.from( meshes ), visibility, cameras: [ ...cameras ], nodes: unresolved ? null : animated, roots: unresolved ? null : subtreeRoots( animated, root ) };
191
220
 
192
221
  } );
193
222
 
@@ -214,15 +243,18 @@ export class AnimationManager extends EventDispatcher {
214
243
 
215
244
  const meshes = new Set(), cameras = new Set();
216
245
  let visibility = false;
246
+ let nodes = new Set();
217
247
  for ( const plan of this._plans ) {
218
248
 
219
249
  plan.meshes.forEach( m => meshes.add( m ) );
220
250
  plan.cameras.forEach( c => cameras.add( c ) );
221
251
  visibility ||= plan.visibility;
252
+ if ( nodes && plan.nodes ) plan.nodes.forEach( n => nodes.add( n ) );
253
+ else nodes = null;
222
254
 
223
255
  }
224
256
 
225
- this._allPlan = { meshes: Int32Array.from( meshes ), visibility, cameras: [ ...cameras ] };
257
+ this._allPlan = { meshes: Int32Array.from( meshes ), visibility, cameras: [ ...cameras ], nodes, roots: nodes ? subtreeRoots( nodes, this._mixerRoot ) : null };
226
258
 
227
259
  }
228
260
 
@@ -487,7 +519,10 @@ export class AnimationManager extends EventDispatcher {
487
519
 
488
520
  // Bones live outside mesh subtrees so per-mesh updateMatrixWorld() misses them. Doing it
489
521
  // once here, on mixerRoot rather than the scene, keeps unrelated static objects out of it.
490
- this._mixerRoot.updateMatrixWorld( true );
522
+ // A rigid clip moves only what its tracks name, so only those subtrees are updated.
523
+ const roots = this._rigid ? this._planFor( this._activeClip )?.roots : null;
524
+ if ( roots ) for ( const node of roots ) node.updateMatrixWorld( true );
525
+ else this._mixerRoot.updateMatrixWorld( true );
491
526
 
492
527
  if ( this._rigid ) {
493
528
 
@@ -499,7 +534,52 @@ export class AnimationManager extends EventDispatcher {
499
534
  const cameras = this._planFor( this._activeClip )?.cameras;
500
535
  if ( cameras?.length ) this.applyPoseCallback?.( { meshIndices: [], visibilityChanged: false, cameras } );
501
536
 
502
- return m => this._computeMeshPositions( m );
537
+ // Decided once per reader: an expanded instanced emitter asks for one mesh several times.
538
+ const moved = new Int8Array( this._meshes.length );
539
+ return m => {
540
+
541
+ if ( moved[ m ] === 0 ) moved[ m ] = this._meshes[ m ].isSkinnedMesh || this._poseChanged( m ) ? 1 : - 1;
542
+ return moved[ m ] > 0 ? this._computeMeshPositions( m ) : null;
543
+
544
+ };
545
+
546
+ }
547
+
548
+ /** Whether a mesh's world matrix or morph weights differ from when its positions were last read. @private */
549
+ _poseChanged( m ) {
550
+
551
+ const mesh = this._meshes[ m ];
552
+ const last = this._lastMeshWorld;
553
+ const e = mesh.matrixWorld.elements;
554
+ const o = m * 16;
555
+ let changed = false;
556
+
557
+ for ( let i = 0; i < 16; i ++ ) {
558
+
559
+ if ( last[ o + i ] !== e[ i ] ) {
560
+
561
+ for ( let j = 0; j < 16; j ++ ) last[ o + j ] = e[ j ];
562
+ changed = true;
563
+ break;
564
+
565
+ }
566
+
567
+ }
568
+
569
+ const weights = mesh.morphTargetInfluences;
570
+ if ( weights?.length ) {
571
+
572
+ const lastWeights = this._lastMorphWeights[ m ];
573
+ if ( ! lastWeights || lastWeights.length !== weights.length || lastWeights.some( ( w, i ) => w !== weights[ i ] ) ) {
574
+
575
+ this._lastMorphWeights[ m ] = Float64Array.from( weights );
576
+ changed = true;
577
+
578
+ }
579
+
580
+ }
581
+
582
+ return changed;
503
583
 
504
584
  }
505
585
 
@@ -640,6 +720,8 @@ export class AnimationManager extends EventDispatcher {
640
720
  this._slotOf = null;
641
721
  this._lastWorld = null;
642
722
  this._lastVisible = null;
723
+ this._lastMeshWorld = null;
724
+ this._lastMorphWeights = null;
643
725
 
644
726
  }
645
727
 
@@ -1,12 +1,22 @@
1
- import { EventDispatcher, MathUtils, OrthographicCamera, PerspectiveCamera, Quaternion, Vector3 } from 'three';
1
+ import { EventDispatcher, MathUtils, Matrix4, OrthographicCamera, PerspectiveCamera, Quaternion, Vector3 } from 'three';
2
2
  import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
3
3
  import { EngineEvents, } from '../EngineEvents.js';
4
- import { AF_DEFAULTS, CAMERA_PROJECTION_IDS } from '../EngineDefaults.js';
4
+ import { CAMERA_PROJECTION_IDS } from '../EngineDefaults.js';
5
5
  import { viewDepth } from './InteractionManager.js';
6
6
  import { ViewCamera } from './ViewCamera.js';
7
7
  import { WalkControls } from './WalkControls.js';
8
8
  import { toPortable, fromPortable } from '../SceneState/portable.js';
9
9
 
10
+ export const AUTO_FOCUS_DEFAULTS = {
11
+ autoFocusMode: 'auto', // 'manual' | 'auto'
12
+ afScreenPoint: { x: 0.5, y: 0.5 },
13
+ afSmoothingFactor: 0.15,
14
+ };
15
+
16
+ const AF_RESET_THRESHOLD = 0.05;
17
+ const AF_FALLBACK_DISTANCE = 10.0;
18
+ const AF_SNAP_THRESHOLD = 0.5;
19
+
10
20
  const DEFAULT_CAMERA_SCALE = Object.freeze( new Vector3( 1, 1, 1 ) );
11
21
 
12
22
  /** The height of an orthographic camera's view in world units, or null for any other camera. */
@@ -55,13 +65,14 @@ export class CameraManager extends EventDispatcher {
55
65
  this._userCameraCounter = 0;
56
66
 
57
67
  // Auto-focus state
58
- this.autoFocusMode = AF_DEFAULTS.SMOOTHING_FACTOR ? 'auto' : 'manual';
59
- this.afScreenPoint = { x: 0.5, y: 0.5 };
60
- this.afSmoothingFactor = AF_DEFAULTS.SMOOTHING_FACTOR;
68
+ this.autoFocusMode = AUTO_FOCUS_DEFAULTS.autoFocusMode;
69
+ this.afScreenPoint = { ...AUTO_FOCUS_DEFAULTS.afScreenPoint };
70
+ this.afSmoothingFactor = AUTO_FOCUS_DEFAULTS.afSmoothingFactor;
61
71
  this._lastValidFocusDistance = null;
62
72
  this._smoothedFocusDistance = null;
63
73
  this._afPointDirty = false;
64
74
  this._afSuspended = false;
75
+ this._afPick = null;
65
76
 
66
77
  // Saved state for default camera when switching to model cameras
67
78
  this._defaultCameraState = null;
@@ -311,40 +322,7 @@ export class CameraManager extends EventDispatcher {
311
322
 
312
323
  } else {
313
324
 
314
- const sourceCamera = this.cameras[ index ];
315
-
316
- this.camera.position.copy( sourceCamera.position );
317
- this.camera.quaternion.copy( sourceCamera.quaternion );
318
- // An imported camera can carry a mirror (a negative axis scale) that no
319
- // quaternion can express — a pbrt scene's `Scale -1 1 1`, for one. Copying
320
- // pose alone would silently un-mirror the view.
321
- this.camera.scale.copy( sourceCamera.scale );
322
- if ( sourceCamera.isPerspectiveCamera ) this.camera.fov = sourceCamera.fov;
323
- this.camera.near = sourceCamera.near;
324
- this.camera.far = sourceCamera.far;
325
- this.camera.updateProjectionMatrix();
326
- this.camera.updateMatrixWorld( true );
327
-
328
- // Restore the saved orbit target for user cameras; otherwise place it
329
- // along the forward direction at the focus distance.
330
- if ( this.controls ) {
331
-
332
- const savedTarget = sourceCamera.userData?.__rayzeeOrbitTarget;
333
- if ( savedTarget ) {
334
-
335
- this.controls.target.copy( savedTarget );
336
-
337
- } else {
338
-
339
- const forward = new Vector3( 0, 0, - 1 ).applyQuaternion( sourceCamera.quaternion );
340
- const focusDist = focusDistance || 5.0;
341
- this.controls.target.copy( this.camera.position ).addScaledVector( forward, focusDist );
342
-
343
- }
344
-
345
- this.controls.update();
346
-
347
- }
325
+ this._placeAt( this.cameras[ index ], focusDistance );
348
326
 
349
327
  }
350
328
 
@@ -369,6 +347,60 @@ export class CameraManager extends EventDispatcher {
369
347
 
370
348
  }
371
349
 
350
+ /**
351
+ * Puts the active camera back at the view it starts from: the default camera at the framing the
352
+ * model loaded with, any other at its own pose and lens. Its effects and projection stay.
353
+ */
354
+ resetView() {
355
+
356
+ const source = this.currentCameraIndex > 0 ? this.cameras[ this.currentCameraIndex ] : null;
357
+ if ( ! source ) {
358
+
359
+ this.controls.reset();
360
+ return;
361
+
362
+ }
363
+
364
+ this._placeAt( source, this._getSettings?.( 'focusDistance' ) );
365
+ const height = orthoHeightOf( source );
366
+ if ( this.camera.orthographic ) this._setView( true, height ? height / 2 : this.camera.orthoHalfHeight );
367
+ this._onReset?.();
368
+
369
+ }
370
+
371
+ // The view takes a camera's pose and lens, orbiting its saved target or a point ahead at the focus distance.
372
+ _placeAt( source, focusDistance ) {
373
+
374
+ this.camera.position.copy( source.position );
375
+ this.camera.quaternion.copy( source.quaternion );
376
+ // An imported camera can carry a mirror (a negative axis scale) that no
377
+ // quaternion can express — a pbrt scene's `Scale -1 1 1`, for one. Copying
378
+ // pose alone would silently un-mirror the view.
379
+ this.camera.scale.copy( source.scale );
380
+ if ( source.isPerspectiveCamera ) this.camera.fov = source.fov;
381
+ this.camera.near = source.near;
382
+ this.camera.far = source.far;
383
+ this.camera.updateProjectionMatrix();
384
+ this.camera.updateMatrixWorld( true );
385
+
386
+ if ( ! this.controls ) return;
387
+
388
+ const savedTarget = source.userData?.__rayzeeOrbitTarget;
389
+ if ( savedTarget ) {
390
+
391
+ this.controls.target.copy( savedTarget );
392
+
393
+ } else {
394
+
395
+ const forward = new Vector3( 0, 0, - 1 ).applyQuaternion( source.quaternion );
396
+ this.controls.target.copy( this.camera.position ).addScaledVector( forward, focusDistance || 5.0 );
397
+
398
+ }
399
+
400
+ this.controls.update();
401
+
402
+ }
403
+
372
404
  /**
373
405
  * Makes the camera orthographic at `height`, or perspective for null, then hands the `cameraProjection`
374
406
  * setting the outcome — after, so its handler finds the camera already there. A 360° panorama stays on
@@ -833,13 +865,12 @@ export class CameraManager extends EventDispatcher {
833
865
  && stage.frameCount > 0
834
866
  && ! stage.isComplete ) return;
835
867
 
836
- // AF screen point (0 to 1, y down) to NDC.
837
- const validHit = this.interactionManager.pickSurface( this.afScreenPoint.x * 2 - 1, 1 - this.afScreenPoint.y * 2 );
868
+ const hitPoint = this._pickFocusPoint( stage.resetCount );
838
869
 
839
870
  let rawDistance;
840
- if ( validHit ) {
871
+ if ( hitPoint ) {
841
872
 
842
- rawDistance = viewDepth( validHit.point, this.camera );
873
+ rawDistance = viewDepth( hitPoint, this.camera );
843
874
  this._lastValidFocusDistance = rawDistance;
844
875
 
845
876
  } else {
@@ -852,7 +883,7 @@ export class CameraManager extends EventDispatcher {
852
883
 
853
884
  // Nothing under the AF point on a fresh view: focus where the camera orbits.
854
885
  const depth = this.controls ? viewDepth( this.controls.target, this.camera ) : 0;
855
- rawDistance = depth > 0 ? depth : AF_DEFAULTS.FALLBACK_DISTANCE * ( assetLoader?.getSceneScale() || 1.0 );
886
+ rawDistance = depth > 0 ? depth : AF_FALLBACK_DISTANCE * ( assetLoader?.getSceneScale() || 1.0 );
856
887
  this._lastValidFocusDistance = rawDistance;
857
888
 
858
889
  }
@@ -872,7 +903,7 @@ export class CameraManager extends EventDispatcher {
872
903
  const changeFraction = Math.abs( rawDistance - this._smoothedFocusDistance )
873
904
  / this._smoothedFocusDistance;
874
905
 
875
- if ( changeFraction > AF_DEFAULTS.SNAP_THRESHOLD ) {
906
+ if ( changeFraction > AF_SNAP_THRESHOLD ) {
876
907
 
877
908
  this._smoothedFocusDistance = rawDistance;
878
909
 
@@ -900,7 +931,7 @@ export class CameraManager extends EventDispatcher {
900
931
 
901
932
  hardReset?.();
902
933
 
903
- } else if ( changeRatio > AF_DEFAULTS.RESET_THRESHOLD ) {
934
+ } else if ( changeRatio > AF_RESET_THRESHOLD ) {
904
935
 
905
936
  softReset?.();
906
937
 
@@ -910,6 +941,32 @@ export class CameraManager extends EventDispatcher {
910
941
 
911
942
  }
912
943
 
944
+ /** Picks again only on a new view or AF point, or a render reset, which every scene change makes. @private */
945
+ _pickFocusPoint( resetCount ) {
946
+
947
+ const camera = this.camera;
948
+ const { x, y } = this.afScreenPoint;
949
+ const last = this._afPick ??= { point: new Vector3(), hit: false, resetCount: NaN, x: NaN, y: NaN, view: new Matrix4(), projection: new Matrix4() };
950
+
951
+ if ( this._afPointDirty || resetCount !== last.resetCount || x !== last.x || y !== last.y
952
+ || ! last.view.equals( camera.matrixWorld ) || ! last.projection.equals( camera.projectionMatrix ) ) {
953
+
954
+ // AF screen point (0 to 1, y down) to NDC.
955
+ const hit = this.interactionManager.pickSurface( x * 2 - 1, 1 - y * 2 );
956
+ last.hit = !! hit;
957
+ if ( hit ) last.point.copy( hit.point );
958
+ last.resetCount = resetCount;
959
+ last.x = x;
960
+ last.y = y;
961
+ last.view.copy( camera.matrixWorld );
962
+ last.projection.copy( camera.projectionMatrix );
963
+
964
+ }
965
+
966
+ return last.hit ? last.point : null;
967
+
968
+ }
969
+
913
970
  /**
914
971
  * Deferred dependency injection — InteractionManager needs the camera
915
972
  * in its constructor, so it can't be passed during CameraManager creation.
@@ -9,7 +9,10 @@ import { createLogger } from '../utils/Logger.js';
9
9
  // The neural passes live in `../neural/` but report through the manager that drives them, so they
10
10
  // share one namespace: `rayzee.log.only( 'neural' )` shows the whole chain.
11
11
  const neuralLog = createLogger( 'neural' );
12
- import { ENGINE_DEFAULTS as DEFAULT_STATE, ASVGF_QUALITY_PRESETS, NRD_DEFAULTS, NRD_QUALITY_PRESETS, NRD_PRESET_KEYS, CAMERA_PROJECTION_IDS } from '../EngineDefaults.js';
12
+ import { CAMERA_PROJECTION_IDS } from '../EngineDefaults.js';
13
+ import {
14
+ DENOISER_DEFAULTS, ASVGF_QUALITY_PRESETS, NRD_DEFAULTS, NRD_QUALITY_PRESETS, NRD_PRESET_KEYS,
15
+ } from '../Stages/DenoiserSettings.js';
13
16
 
14
17
  // A refresh slower than this is a slideshow, not a live view, so the cadence swaps to a cheaper
15
18
  // model and puts the chosen one back for the finished image. Resolution-aware by construction:
@@ -135,13 +138,13 @@ export class DenoisingManager extends EventDispatcher {
135
138
 
136
139
  // The tier the finished image uses. The loaded tier is not always this one: while the
137
140
  // image is still accumulating we run a cheaper model (see previewQuality).
138
- this._finalQuality = DEFAULT_STATE.oidnQuality;
141
+ this._finalQuality = DENOISER_DEFAULTS.oidnQuality;
139
142
  // Two independent decisions. `finalDenoise` is the OIDN pass on the finished image;
140
143
  // `continuousDenoise` is OIDN as the live-view denoiser. `denoiser.enabled` means only
141
144
  // "OIDN is in use at all", which is what the aux G-buffer wiring needs.
142
- this.finalDenoise = DEFAULT_STATE.enableOIDN;
143
- this.continuousDenoise = DEFAULT_STATE.continuousDenoise;
144
- this.continuousDenoiseInterval = DEFAULT_STATE.continuousDenoiseInterval;
145
+ this.finalDenoise = DENOISER_DEFAULTS.enableOIDN;
146
+ this.continuousDenoise = DENOISER_DEFAULTS.continuousDenoise;
147
+ this.continuousDenoiseInterval = DENOISER_DEFAULTS.continuousDenoiseInterval;
145
148
  // -Infinity, not 0: 0 reads as "denoised at time zero", which blocks the first cadence
146
149
  // denoise while performance.now() is still below the interval.
147
150
  this._lastCadenceAt = - Infinity;
@@ -160,7 +163,7 @@ export class DenoisingManager extends EventDispatcher {
160
163
  this._onPostProcessRefresh = null;
161
164
  this._onDisplayRefresh = null;
162
165
 
163
- this.temporalHistory = DEFAULT_STATE.oidnTemporalHistory;
166
+ this.temporalHistory = DENOISER_DEFAULTS.oidnTemporalHistory;
164
167
  this._history = null;
165
168
  // Stale after a scene-changing reset, or one the app did not announce (material/env edits).
166
169
  this._historyDirty = true;
@@ -307,7 +310,8 @@ export class DenoisingManager extends EventDispatcher {
307
310
  // Compositor decides what the single canvas shows. The exposure, grade and tone curve
308
311
  // come from the renderer's own output pass, so it is not told about them either.
309
312
  this.denoiser = new OIDNDenoiser( this.renderer, {
310
- ...DEFAULT_STATE,
313
+ enableOIDN: DENOISER_DEFAULTS.enableOIDN,
314
+ oidnQuality: DENOISER_DEFAULTS.oidnQuality,
311
315
 
312
316
  backendParams: () => ( {
313
317
  device: this.renderer.backend.device,
@@ -378,8 +382,8 @@ export class DenoisingManager extends EventDispatcher {
378
382
  const pt = this._stages.pathTracer;
379
383
 
380
384
  this.upscaler = new AIUpscaler( this.upscalerCanvas, this.renderer, {
381
- scaleFactor: DEFAULT_STATE.upscalerScale || 2,
382
- quality: DEFAULT_STATE.upscalerQuality || 'fast',
385
+ scaleFactor: DENOISER_DEFAULTS.upscalerScale || 2,
386
+ quality: DENOISER_DEFAULTS.upscalerQuality || 'fast',
383
387
 
384
388
  // One source: the render canvas shows whatever the Compositor picked — the denoised
385
389
  // picture when OIDN is on, the raw render otherwise — so the upscaler always enlarges
@@ -407,7 +411,7 @@ export class DenoisingManager extends EventDispatcher {
407
411
  getSaturation: () => this._getSaturation(),
408
412
  } );
409
413
 
410
- this.upscaler.enabled = DEFAULT_STATE.enableUpscaler || false;
414
+ this.upscaler.enabled = DENOISER_DEFAULTS.enableUpscaler || false;
411
415
 
412
416
  // Forward lifecycle events (store refs for removal on re-setup / dispose)
413
417
  this._upscalerResChangedHandler = ( e ) =>