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.
@@ -5,6 +5,7 @@ import {
5
5
  Mesh,
6
6
  MeshBasicMaterial
7
7
  } from 'three';
8
+ import { EngineEvents } from '../EngineEvents.js';
8
9
 
9
10
  /**
10
11
  * InteractionManager
@@ -65,6 +66,91 @@ export class InteractionManager extends EventDispatcher {
65
66
  this.canvas.addEventListener( 'pointerup', this.handleContextPointerUp );
66
67
  this.canvas.addEventListener( 'contextmenu', this.handleContextMenu );
67
68
 
69
+ // Cross-manager dependencies (injected after init via setDependencies)
70
+ this._overlayManager = null;
71
+ this._transformManager = null;
72
+ this._appDispatch = null;
73
+ this._orbitControls = null;
74
+
75
+ }
76
+
77
+ // ==================== CROSS-MANAGER COORDINATION ====================
78
+
79
+ /**
80
+ * Inject dependencies needed for select/deselect coordination.
81
+ * Call once after all managers are created.
82
+ *
83
+ * @param {Object} deps
84
+ * @param {import('./OverlayManager.js').OverlayManager} deps.overlayManager
85
+ * @param {import('./TransformManager.js').TransformManager} deps.transformManager
86
+ * @param {Function} deps.appDispatch - (event) => dispatches on PathTracerApp
87
+ * @param {import('three/addons/controls/OrbitControls.js').OrbitControls} [deps.orbitControls]
88
+ */
89
+ setDependencies( { overlayManager, transformManager, appDispatch, orbitControls } ) {
90
+
91
+ this._overlayManager = overlayManager || null;
92
+ this._transformManager = transformManager || null;
93
+ this._appDispatch = appDispatch || null;
94
+ this._orbitControls = orbitControls || null;
95
+
96
+ }
97
+
98
+ /**
99
+ * Programmatically selects an object (or deselects if null).
100
+ * Coordinates: outline helper, internal state, transform gizmo, and event dispatch.
101
+ * @param {import('three').Object3D|null} object
102
+ */
103
+ select( object ) {
104
+
105
+ const outline = this._overlayManager?.getHelper( 'outline' );
106
+ if ( outline ) outline.setSelectedObjects( object ? [ object ] : [] );
107
+
108
+ this.selectedObject = object || null;
109
+
110
+ if ( object ) {
111
+
112
+ this._transformManager?.attach( object );
113
+
114
+ } else {
115
+
116
+ this._transformManager?.detach();
117
+
118
+ }
119
+
120
+ this._appDispatch?.( { type: EngineEvents.OBJECT_SELECTED, object: object || null } );
121
+
122
+ }
123
+
124
+ /**
125
+ * Deselects the current object.
126
+ */
127
+ deselect() {
128
+
129
+ this.select( null );
130
+
131
+ }
132
+
133
+ /**
134
+ * Disables selection mode and detaches the transform gizmo.
135
+ */
136
+ disableMode() {
137
+
138
+ this.disableSelectMode();
139
+ this._transformManager?.detach();
140
+
141
+ }
142
+
143
+ /**
144
+ * Subscribes to an interaction event.
145
+ * @param {string} type - Event type
146
+ * @param {Function} handler - Event handler
147
+ * @returns {Function} Unsubscribe function
148
+ */
149
+ on( type, handler ) {
150
+
151
+ this.addEventListener( type, handler );
152
+ return () => this.removeEventListener( type, handler );
153
+
68
154
  }
69
155
 
70
156
  // ==================== FOCUS MODE ====================
@@ -91,6 +177,9 @@ export class InteractionManager extends EventDispatcher {
91
177
 
92
178
  }
93
179
 
180
+ // Disable orbit controls when focus mode is active
181
+ if ( this._orbitControls ) this._orbitControls.enabled = ! this.focusMode;
182
+
94
183
  // Emit event for external listeners
95
184
  this.dispatchEvent( {
96
185
  type: 'focusModeChanged',
@@ -598,6 +687,59 @@ export class InteractionManager extends EventDispatcher {
598
687
 
599
688
  // ==================== LIFECYCLE ====================
600
689
 
690
+ /**
691
+ * Wires interaction events to app-level dispatches and side-effects.
692
+ * Call once during init after selection sub-API is available on the app.
693
+ *
694
+ * @param {import('../PathTracerApp.js').PathTracerApp} app
695
+ */
696
+ wireAppEvents( app ) {
697
+
698
+ this.addEventListener( 'objectSelected', ( event ) => {
699
+
700
+ this.select( event.object );
701
+ app.refreshFrame();
702
+ app.dispatchEvent( { type: 'objectSelected', object: event.object, uuid: event.uuid } );
703
+
704
+ } );
705
+
706
+ this.addEventListener( 'objectDeselected', ( event ) => {
707
+
708
+ this.select( null );
709
+ app.refreshFrame();
710
+ app.dispatchEvent( { type: 'objectDeselected', object: event.object, uuid: event.uuid } );
711
+
712
+ } );
713
+
714
+ this.addEventListener( 'selectModeChanged', ( event ) => {
715
+
716
+ app.dispatchEvent( { type: EngineEvents.SELECT_MODE_CHANGED, enabled: event.enabled } );
717
+
718
+ } );
719
+
720
+ this.addEventListener( 'objectDoubleClicked', ( event ) => {
721
+
722
+ this.select( event.object );
723
+ app.refreshFrame();
724
+ app.dispatchEvent( { type: EngineEvents.OBJECT_DOUBLE_CLICKED, object: event.object, uuid: event.uuid } );
725
+
726
+ } );
727
+
728
+ this.addEventListener( 'focusChanged', ( event ) => {
729
+
730
+ app.settings.set( 'focusDistance', event.worldDistance );
731
+ app.dispatchEvent( { type: 'focusChanged', distance: event.distance } );
732
+
733
+ } );
734
+
735
+ this.addEventListener( 'afPointPlaced', ( event ) => {
736
+
737
+ app.dispatchEvent( { type: EngineEvents.AF_POINT_PLACED, point: event.point } );
738
+
739
+ } );
740
+
741
+ }
742
+
601
743
  /**
602
744
  * Clean up all event listeners and state
603
745
  */
@@ -15,14 +15,17 @@ export class LightManager extends EventDispatcher {
15
15
  * @param {import('three').Scene} scene - WebGPU light scene
16
16
  * @param {import('../SceneHelpers.js').SceneHelpers} sceneHelpers
17
17
  * @param {import('../Stages/PathTracer.js').PathTracer} pathTracer
18
+ * @param {Object} [options]
19
+ * @param {Function} [options.onReset] - Callback to reset accumulation after light changes
18
20
  */
19
- constructor( scene, sceneHelpers, pathTracer ) {
21
+ constructor( scene, sceneHelpers, pathTracer, options = {} ) {
20
22
 
21
23
  super();
22
24
 
23
25
  this.scene = scene;
24
26
  this.sceneHelpers = sceneHelpers;
25
27
  this.pathTracer = pathTracer;
28
+ this._onReset = options.onReset || null;
26
29
 
27
30
  }
28
31
 
@@ -78,6 +81,7 @@ export class LightManager extends EventDispatcher {
78
81
  this.scene.add( light );
79
82
  this.updateLights();
80
83
  this._syncHelpers();
84
+ this._onReset?.();
81
85
 
82
86
  return this._buildDescriptor( light );
83
87
 
@@ -97,6 +101,7 @@ export class LightManager extends EventDispatcher {
97
101
  if ( light.target ) light.target.removeFromParent();
98
102
  light.removeFromParent();
99
103
  this.updateLights();
104
+ this._onReset?.();
100
105
  return true;
101
106
 
102
107
  }
@@ -109,6 +114,7 @@ export class LightManager extends EventDispatcher {
109
114
  this.sceneHelpers.clear();
110
115
  this._removeAllLights();
111
116
  this.updateLights();
117
+ this._onReset?.();
112
118
 
113
119
  }
114
120
 
@@ -205,6 +211,50 @@ export class LightManager extends EventDispatcher {
205
211
 
206
212
  }
207
213
 
214
+ // ── Aliases (match Sub-API surface for zero-churn migration) ──
215
+
216
+ /** @see addLight */
217
+ add( type ) {
218
+
219
+ return this.addLight( type );
220
+
221
+ }
222
+
223
+ /** @see removeLight */
224
+ remove( uuid ) {
225
+
226
+ return this.removeLight( uuid );
227
+
228
+ }
229
+
230
+ /** @see clearLights */
231
+ clear() {
232
+
233
+ this.clearLights();
234
+
235
+ }
236
+
237
+ /** @see getLights */
238
+ getAll() {
239
+
240
+ return this.getLights();
241
+
242
+ }
243
+
244
+ /** @see updateLights */
245
+ sync() {
246
+
247
+ this.updateLights();
248
+
249
+ }
250
+
251
+ /** @see setShowLightHelper */
252
+ showHelpers( show ) {
253
+
254
+ this.setShowLightHelper( show );
255
+
256
+ }
257
+
208
258
  // ── Private ───────────────────────────────────────────────────
209
259
 
210
260
  /** Syncs helpers in sceneHelpers with current scene lights. */
@@ -1,3 +1,7 @@
1
+ import { TileHelper } from './helpers/TileHelper.js';
2
+ import { OutlineHelper } from './helpers/OutlineHelper.js';
3
+ import { EngineEvents } from '../EngineEvents.js';
4
+
1
5
  /**
2
6
  * OverlayManager — Unified overlay system for visual helpers.
3
7
  *
@@ -63,6 +67,99 @@ export class OverlayManager {
63
67
 
64
68
  }
65
69
 
70
+ // ═══════════════════════════════════════════════════════════════
71
+ // Default helpers setup
72
+ // ═══════════════════════════════════════════════════════════════
73
+
74
+ /**
75
+ * Creates and wires the default overlay helpers (tile progress, outline).
76
+ * Call once during app init after pipeline and managers are ready.
77
+ *
78
+ * @param {Object} config
79
+ * @param {import('../SceneHelpers.js').SceneHelpers} config.helperScene
80
+ * @param {import('three').Scene} config.meshScene
81
+ * @param {import('../Pipeline/RenderPipeline.js').RenderPipeline} config.pipeline
82
+ * @param {import('./DenoisingManager.js').DenoisingManager} config.denoisingManager
83
+ * @param {import('three').EventDispatcher} config.app - App instance for resize/render-complete events
84
+ * @param {number} config.renderWidth
85
+ * @param {number} config.renderHeight
86
+ */
87
+ setupDefaultHelpers( { helperScene, meshScene, pipeline, denoisingManager, app, renderWidth, renderHeight } ) {
88
+
89
+ this.setHelperScene( helperScene );
90
+
91
+ // ── Tile helper (shared across path tracer, OIDN, upscaler) ──
92
+ const tileHelper = new TileHelper();
93
+ this.register( 'tiles', tileHelper );
94
+
95
+ tileHelper.setRenderSize( renderWidth || 1, renderHeight || 1 );
96
+
97
+ app.addEventListener( 'resolution_changed', ( e ) => {
98
+
99
+ tileHelper.setRenderSize( e.width, e.height );
100
+
101
+ } );
102
+
103
+ // Path tracer tile events
104
+ pipeline.eventBus.on( 'tile:changed', ( e ) => {
105
+
106
+ if ( e.renderMode === 1 && e.tileBounds ) {
107
+
108
+ tileHelper.setActiveTile( e.tileBounds );
109
+ tileHelper.show();
110
+
111
+ }
112
+
113
+ } );
114
+
115
+ pipeline.eventBus.on( 'pipeline:reset', () => tileHelper.hide() );
116
+ app.addEventListener( EngineEvents.RENDER_COMPLETE, () => tileHelper.hide() );
117
+
118
+ // OIDN/upscaler tile events
119
+ this._wireDenoiserTileEvents( tileHelper, denoisingManager );
120
+
121
+ // ── Outline helper ──
122
+ const outlineHelper = new OutlineHelper( this.renderer, meshScene, this.camera );
123
+ this.register( 'outline', outlineHelper );
124
+
125
+ }
126
+
127
+ /**
128
+ * Wires denoiser/upscaler tile progress events to the tile helper.
129
+ * These fire while the animation loop is stopped, so we trigger manual HUD redraws.
130
+ */
131
+ _wireDenoiserTileEvents( tileHelper, denoisingManager ) {
132
+
133
+ const sources = [ denoisingManager?.denoiser, denoisingManager?.upscaler ];
134
+
135
+ for ( const source of sources ) {
136
+
137
+ if ( ! source ) continue;
138
+
139
+ source.addEventListener( 'tileProgress', ( e ) => {
140
+
141
+ if ( e.tile ) {
142
+
143
+ tileHelper.setRenderSize( e.imageWidth, e.imageHeight );
144
+ tileHelper.setActiveTile( e.tile );
145
+ tileHelper.show();
146
+ this.refreshHUD();
147
+
148
+ }
149
+
150
+ } );
151
+
152
+ source.addEventListener( 'end', () => {
153
+
154
+ tileHelper.hide();
155
+ this.refreshHUD();
156
+
157
+ } );
158
+
159
+ }
160
+
161
+ }
162
+
66
163
  // ═══════════════════════════════════════════════════════════════
67
164
  // Helper registration
68
165
  // ═══════════════════════════════════════════════════════════════
@@ -107,6 +107,7 @@ export class TransformManager {
107
107
  setMode( mode ) {
108
108
 
109
109
  this._controls.setMode( mode );
110
+ this._app?.dispatchEvent( { type: EngineEvents.TRANSFORM_MODE_CHANGED, mode } );
110
111
 
111
112
  }
112
113
 
@@ -87,7 +87,7 @@ export class VideoRenderManager {
87
87
  app.stages.pathTracer?.updateCompletionThreshold?.();
88
88
 
89
89
  // Disable camera controls during render
90
- if ( app._controls ) app._controls.enabled = false;
90
+ if ( app.cameraManager.controls ) app.cameraManager.controls.enabled = false;
91
91
 
92
92
  try {
93
93
 
@@ -132,7 +132,7 @@ export class VideoRenderManager {
132
132
  if ( this._cancelled ) break;
133
133
 
134
134
  // 5. Capture frame from output canvas
135
- const canvas = app.getOutputCanvas();
135
+ const canvas = app.getCanvas();
136
136
  if ( canvas && onFrame ) {
137
137
 
138
138
  const bitmap = await createImageBitmap( canvas );
@@ -205,7 +205,7 @@ export class VideoRenderManager {
205
205
 
206
206
  while ( ! pathTracer.isComplete && ! this._cancelled ) {
207
207
 
208
- app._camera.updateMatrixWorld();
208
+ app.cameraManager.camera.updateMatrixWorld();
209
209
  app.pipeline.render();
210
210
 
211
211
  // Yield every 4 passes to keep UI responsive and update stats
@@ -280,7 +280,7 @@ export class VideoRenderManager {
280
280
  samplesPerPixel: app.settings.get( 'samplesPerPixel' ),
281
281
  transmissiveBounces: app.settings.get( 'transmissiveBounces' ),
282
282
  renderMode: app.stages.pathTracer?.renderMode?.value,
283
- controlsEnabled: app._controls?.enabled,
283
+ controlsEnabled: app.cameraManager.controls?.enabled,
284
284
  oidnEnabled: app.denoisingManager?.denoiser?.enabled,
285
285
  oidnQuality: app.denoisingManager?.denoiser?.quality,
286
286
  wasPlaying: app.animationManager?.isPlaying,
@@ -306,13 +306,13 @@ export class VideoRenderManager {
306
306
 
307
307
  if ( app.stages.pathTracer && state.renderMode !== undefined ) {
308
308
 
309
- app.setRenderMode( state.renderMode );
309
+ app.stages.pathTracer?.setUniform( 'renderMode', parseInt( state.renderMode ) );
310
310
 
311
311
  }
312
312
 
313
313
  app.stages.pathTracer?.updateCompletionThreshold?.();
314
314
 
315
- if ( app._controls ) app._controls.enabled = state.controlsEnabled ?? true;
315
+ if ( app.cameraManager.controls ) app.cameraManager.controls.enabled = state.controlsEnabled ?? true;
316
316
 
317
317
  if ( app.denoisingManager?.denoiser ) {
318
318
 
@@ -0,0 +1,45 @@
1
+ import Stats from 'stats-gl';
2
+
3
+ /**
4
+ * Creates and configures a stats-gl performance panel.
5
+ *
6
+ * @param {import('three/webgpu').WebGPURenderer} renderer
7
+ * @param {HTMLElement} [container=document.body] - DOM element to mount the stats panel
8
+ * @returns {Stats}
9
+ */
10
+ export function createStats( renderer, container ) {
11
+
12
+ const stats = new Stats( { horizontal: true, trackGPU: true } );
13
+ stats.dom.style.position = 'absolute';
14
+ stats.dom.style.top = 'unset';
15
+ stats.dom.style.bottom = '48px';
16
+
17
+ stats.init( renderer );
18
+ ( container || document.body ).appendChild( stats.dom );
19
+
20
+ const foregroundColor = '#ffffff';
21
+ const backgroundColor = '#1e293b';
22
+
23
+ const gradient = stats.fpsPanel.context.createLinearGradient(
24
+ 0, stats.fpsPanel.GRAPH_Y,
25
+ 0, stats.fpsPanel.GRAPH_Y + stats.fpsPanel.GRAPH_HEIGHT
26
+ );
27
+ gradient.addColorStop( 0, foregroundColor );
28
+
29
+ stats.fpsPanel.fg = stats.msPanel.fg = foregroundColor;
30
+ stats.fpsPanel.bg = stats.msPanel.bg = backgroundColor;
31
+ stats.fpsPanel.gradient = stats.msPanel.gradient = gradient;
32
+
33
+ if ( stats.gpuPanel ) {
34
+
35
+ stats.gpuPanel.fg = foregroundColor;
36
+ stats.gpuPanel.bg = backgroundColor;
37
+ stats.gpuPanel.gradient = gradient;
38
+
39
+ }
40
+
41
+ stats.dom.style.display = '';
42
+
43
+ return stats;
44
+
45
+ }
@@ -1,87 +0,0 @@
1
- /**
2
- * Animation sub-API — playback controls for GLTF animation clips.
3
- *
4
- * Access via `engine.animation`.
5
- *
6
- * @example
7
- * engine.animation.play(0);
8
- * engine.animation.setSpeed(2);
9
- * console.log(engine.animation.clips);
10
- */
11
- export class AnimationAPI {
12
-
13
- /** @param {import('../PathTracerApp.js').PathTracerApp} app */
14
- constructor( app ) {
15
-
16
- this._app = app;
17
-
18
- }
19
-
20
- /**
21
- * Plays an animation clip by index.
22
- * @param {number} [clipIndex=0]
23
- */
24
- play( clipIndex = 0 ) {
25
-
26
- this._app.playAnimation( clipIndex );
27
-
28
- }
29
-
30
- /**
31
- * Pauses animation, preserving current time position.
32
- */
33
- pause() {
34
-
35
- this._app.pauseAnimation();
36
-
37
- }
38
-
39
- /**
40
- * Resumes animation from paused state.
41
- */
42
- resume() {
43
-
44
- this._app.resumeAnimation();
45
-
46
- }
47
-
48
- /**
49
- * Stops animation and resets to beginning.
50
- */
51
- stop() {
52
-
53
- this._app.stopAnimationPlayback();
54
-
55
- }
56
-
57
- /**
58
- * Sets playback speed multiplier.
59
- * @param {number} speed - 1.0 = normal speed
60
- */
61
- setSpeed( speed ) {
62
-
63
- this._app.setAnimationSpeed( speed );
64
-
65
- }
66
-
67
- /**
68
- * Sets loop mode for animation playback.
69
- * @param {boolean} loop - true for repeat, false for play-once
70
- */
71
- setLoop( loop ) {
72
-
73
- this._app.setAnimationLoop( loop );
74
-
75
- }
76
-
77
- /**
78
- * Available animation clips.
79
- * @returns {{ index: number, name: string, duration: number }[]}
80
- */
81
- get clips() {
82
-
83
- return this._app.animationClips;
84
-
85
- }
86
-
87
- }
@@ -1,109 +0,0 @@
1
- /**
2
- * Camera sub-API — camera switching, auto-focus, DOF, and raw Three.js access.
3
- *
4
- * Access via `engine.camera`.
5
- *
6
- * @example
7
- * engine.camera.switch(1);
8
- * engine.camera.active.fov = 60;
9
- * engine.camera.controls.target.set(0, 1, 0);
10
- */
11
- export class CameraAPI {
12
-
13
- /** @param {import('../PathTracerApp.js').PathTracerApp} app */
14
- constructor( app ) {
15
-
16
- this._app = app;
17
-
18
- }
19
-
20
- /**
21
- * The active Three.js PerspectiveCamera.
22
- * @returns {import('three').PerspectiveCamera}
23
- */
24
- get active() {
25
-
26
- return this._app.cameraManager?.camera ?? this._app._camera;
27
-
28
- }
29
-
30
- /**
31
- * The OrbitControls instance.
32
- * @returns {import('three/addons/controls/OrbitControls.js').OrbitControls}
33
- */
34
- get controls() {
35
-
36
- return this._app.cameraManager?.controls ?? this._app._controls;
37
-
38
- }
39
-
40
- /**
41
- * Switches the active camera by index.
42
- * @param {number} index
43
- */
44
- switch( index ) {
45
-
46
- this._app.switchCamera( index );
47
-
48
- }
49
-
50
- /**
51
- * Returns display names for all available cameras.
52
- * @returns {string[]}
53
- */
54
- getNames() {
55
-
56
- return this._app.getCameraNames();
57
-
58
- }
59
-
60
- /**
61
- * Focuses the orbit camera on a world-space point.
62
- * @param {import('three').Vector3} center
63
- */
64
- focusOn( center ) {
65
-
66
- this._app.focusOnPoint( center );
67
-
68
- }
69
-
70
- /**
71
- * Sets the auto-focus mode.
72
- * @param {'auto'|'manual'} mode
73
- */
74
- setAutoFocusMode( mode ) {
75
-
76
- this._app.cameraManager?.setAutoFocusMode( mode );
77
-
78
- }
79
-
80
- /**
81
- * Sets the normalized AF screen point (0-1 range).
82
- * @param {number} x
83
- * @param {number} y
84
- */
85
- setAFScreenPoint( x, y ) {
86
-
87
- this._app.cameraManager?.setAFScreenPoint( x, y );
88
-
89
- }
90
-
91
- /**
92
- * Enters AF point placement interaction mode.
93
- */
94
- enterAFPointPlacementMode() {
95
-
96
- this._app.cameraManager?.enterAFPointPlacementMode();
97
-
98
- }
99
-
100
- /**
101
- * Exits AF point placement interaction mode.
102
- */
103
- exitAFPointPlacementMode() {
104
-
105
- this._app.cameraManager?.exitAFPointPlacementMode();
106
-
107
- }
108
-
109
- }