rayzee 7.13.0 → 7.15.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 +1 -1
- package/dist/rayzee.es.js +70 -31
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +4 -4
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/PathTracerApp.js +1 -0
- package/src/Processor/LightSerializer.js +4 -0
- package/src/managers/InteractionManager.js +56 -3
- package/src/managers/LightManager.js +1 -0
- package/src/managers/TransformManager.js +124 -2
package/package.json
CHANGED
package/src/PathTracerApp.js
CHANGED
|
@@ -1780,6 +1780,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1780
1780
|
transformManager: this.transformManager,
|
|
1781
1781
|
appDispatch: ( e ) => this.dispatchEvent( e ),
|
|
1782
1782
|
orbitControls: this.cameraManager.controls,
|
|
1783
|
+
helperScene: this._sceneHelpers.scene,
|
|
1783
1784
|
} );
|
|
1784
1785
|
|
|
1785
1786
|
this.denoisingManager.setOverlayManager( this.overlayManager );
|
|
@@ -90,6 +90,7 @@ export class LightSerializer {
|
|
|
90
90
|
|
|
91
91
|
addDirectionalLight( light ) {
|
|
92
92
|
|
|
93
|
+
if ( ! light.visible ) return; // Skip hidden lights
|
|
93
94
|
if ( light.intensity <= 0.0 ) return; // Skip zero intensity lights
|
|
94
95
|
|
|
95
96
|
light.updateMatrixWorld();
|
|
@@ -146,6 +147,7 @@ export class LightSerializer {
|
|
|
146
147
|
|
|
147
148
|
addRectAreaLight( light ) {
|
|
148
149
|
|
|
150
|
+
if ( ! light.visible ) return; // Skip hidden lights
|
|
149
151
|
if ( light.intensity <= 0.0 ) return; // Skip zero intensity lights
|
|
150
152
|
|
|
151
153
|
light.updateMatrixWorld();
|
|
@@ -190,6 +192,7 @@ export class LightSerializer {
|
|
|
190
192
|
|
|
191
193
|
addPointLight( light ) {
|
|
192
194
|
|
|
195
|
+
if ( ! light.visible ) return; // Skip hidden lights
|
|
193
196
|
if ( light.intensity <= 0.0 ) return; // Skip zero intensity lights
|
|
194
197
|
|
|
195
198
|
light.updateMatrixWorld();
|
|
@@ -218,6 +221,7 @@ export class LightSerializer {
|
|
|
218
221
|
|
|
219
222
|
addSpotLight( light ) {
|
|
220
223
|
|
|
224
|
+
if ( ! light.visible ) return; // Skip hidden lights
|
|
221
225
|
if ( light.intensity <= 0.0 ) return; // Skip zero intensity lights
|
|
222
226
|
|
|
223
227
|
light.updateMatrixWorld();
|
|
@@ -71,6 +71,7 @@ export class InteractionManager extends EventDispatcher {
|
|
|
71
71
|
this._transformManager = null;
|
|
72
72
|
this._appDispatch = null;
|
|
73
73
|
this._orbitControls = null;
|
|
74
|
+
this._helperScene = null; // SceneHelpers' scene — lets clicking a light's visual helper select it
|
|
74
75
|
|
|
75
76
|
}
|
|
76
77
|
|
|
@@ -85,13 +86,15 @@ export class InteractionManager extends EventDispatcher {
|
|
|
85
86
|
* @param {import('./TransformManager.js').TransformManager} deps.transformManager
|
|
86
87
|
* @param {Function} deps.appDispatch - (event) => dispatches on PathTracerApp
|
|
87
88
|
* @param {import('three/addons/controls/OrbitControls.js').OrbitControls} [deps.orbitControls]
|
|
89
|
+
* @param {import('three').Scene} [deps.helperScene] - SceneHelpers' scene, for light-helper picking
|
|
88
90
|
*/
|
|
89
|
-
setDependencies( { overlayManager, transformManager, appDispatch, orbitControls } ) {
|
|
91
|
+
setDependencies( { overlayManager, transformManager, appDispatch, orbitControls, helperScene } ) {
|
|
90
92
|
|
|
91
93
|
this._overlayManager = overlayManager || null;
|
|
92
94
|
this._transformManager = transformManager || null;
|
|
93
95
|
this._appDispatch = appDispatch || null;
|
|
94
96
|
this._orbitControls = orbitControls || null;
|
|
97
|
+
this._helperScene = helperScene || null;
|
|
95
98
|
|
|
96
99
|
}
|
|
97
100
|
|
|
@@ -469,10 +472,26 @@ export class InteractionManager extends EventDispatcher {
|
|
|
469
472
|
|
|
470
473
|
const intersects = this.raycaster.intersectObjects( this.scene.children, true );
|
|
471
474
|
const validIntersects = this.filterValidIntersects( intersects );
|
|
475
|
+
const lightHit = this._pickLightHelper();
|
|
472
476
|
|
|
473
|
-
|
|
477
|
+
let object = null;
|
|
478
|
+
|
|
479
|
+
if ( validIntersects.length > 0 && lightHit ) {
|
|
480
|
+
|
|
481
|
+
object = validIntersects[ 0 ].distance <= lightHit.distance ? validIntersects[ 0 ].object : lightHit.light;
|
|
482
|
+
|
|
483
|
+
} else if ( validIntersects.length > 0 ) {
|
|
484
|
+
|
|
485
|
+
object = validIntersects[ 0 ].object;
|
|
486
|
+
|
|
487
|
+
} else if ( lightHit ) {
|
|
488
|
+
|
|
489
|
+
object = lightHit.light;
|
|
490
|
+
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if ( object ) {
|
|
474
494
|
|
|
475
|
-
const object = validIntersects[ 0 ].object;
|
|
476
495
|
const currentlySelectedObject = this.selectedObject;
|
|
477
496
|
const isAlreadySelected = currentlySelectedObject && currentlySelectedObject.uuid === object.uuid;
|
|
478
497
|
|
|
@@ -674,6 +693,40 @@ export class InteractionManager extends EventDispatcher {
|
|
|
674
693
|
|
|
675
694
|
}
|
|
676
695
|
|
|
696
|
+
/**
|
|
697
|
+
* Raycasts against the light-helper overlay scene (SceneHelpers) and
|
|
698
|
+
* resolves the closest hit back to its source light. Helper geometry
|
|
699
|
+
* (PointLightHelper, SpotLightHelper, DirectionalLightHelper,
|
|
700
|
+
* RectAreaLightHelper) is often line/child geometry, so this walks up the
|
|
701
|
+
* parent chain looking for the `.light` reference each helper exposes.
|
|
702
|
+
* Returns null when there's no helper scene, no lights, or no hit — this
|
|
703
|
+
* naturally happens when "Light Helper" is toggled off, since SceneHelpers
|
|
704
|
+
* empties its scene in that case.
|
|
705
|
+
* @private
|
|
706
|
+
*/
|
|
707
|
+
_pickLightHelper() {
|
|
708
|
+
|
|
709
|
+
if ( ! this._helperScene || this._helperScene.children.length === 0 ) return null;
|
|
710
|
+
|
|
711
|
+
const intersects = this.raycaster.intersectObjects( this._helperScene.children, true );
|
|
712
|
+
|
|
713
|
+
for ( const hit of intersects ) {
|
|
714
|
+
|
|
715
|
+
let object = hit.object;
|
|
716
|
+
|
|
717
|
+
while ( object ) {
|
|
718
|
+
|
|
719
|
+
if ( object.light ) return { distance: hit.distance, light: object.light };
|
|
720
|
+
object = object.parent;
|
|
721
|
+
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
return null;
|
|
727
|
+
|
|
728
|
+
}
|
|
729
|
+
|
|
677
730
|
/**
|
|
678
731
|
* Update dependencies (useful when scene/camera changes)
|
|
679
732
|
*/
|
|
@@ -330,6 +330,7 @@ export class LightManager extends EventDispatcher {
|
|
|
330
330
|
uuid: light.uuid,
|
|
331
331
|
name: light.name,
|
|
332
332
|
type: light.type,
|
|
333
|
+
visible: light.visible,
|
|
333
334
|
intensity: light.intensity,
|
|
334
335
|
color: `#${light.color.getHexString()}`,
|
|
335
336
|
position: [ light.position.x, light.position.y, light.position.z ],
|
|
@@ -9,6 +9,11 @@ import { Matrix3, Scene, Vector3 } from 'three';
|
|
|
9
9
|
import { TransformControls } from 'three/addons/controls/TransformControls.js';
|
|
10
10
|
import { EngineEvents } from '../EngineEvents.js';
|
|
11
11
|
|
|
12
|
+
// Three.js "forward" convention (local -Z), used to derive a spot/directional
|
|
13
|
+
// light's aim direction from its quaternion. Read-only — setFromUnitVectors()
|
|
14
|
+
// does not mutate its arguments, so this can be a shared constant.
|
|
15
|
+
const FORWARD = new Vector3( 0, 0, - 1 );
|
|
16
|
+
|
|
12
17
|
export class TransformManager {
|
|
13
18
|
|
|
14
19
|
constructor( { camera, canvas, orbitControls, app } ) {
|
|
@@ -36,6 +41,12 @@ export class TransformManager {
|
|
|
36
41
|
this._refitInFlight = false;
|
|
37
42
|
this._baselineComputed = false;
|
|
38
43
|
|
|
44
|
+
// Light transform state — spot/directional lights aim via a separate
|
|
45
|
+
// `.target` Object3D rather than their own rotation (see attach()).
|
|
46
|
+
this._tempForward = new Vector3();
|
|
47
|
+
this._lightTargetDistance = null;
|
|
48
|
+
this._lastLightPosition = null;
|
|
49
|
+
|
|
39
50
|
// Bind handlers
|
|
40
51
|
this._onDraggingChanged = this._onDraggingChanged.bind( this );
|
|
41
52
|
this._onObjectChange = this._onObjectChange.bind( this );
|
|
@@ -91,6 +102,32 @@ export class TransformManager {
|
|
|
91
102
|
this._controls.attach( object );
|
|
92
103
|
this._attached = object;
|
|
93
104
|
|
|
105
|
+
this._lightTargetDistance = null;
|
|
106
|
+
this._lastLightPosition = null;
|
|
107
|
+
|
|
108
|
+
// Spot/directional lights aim via `.target.position`, not their own
|
|
109
|
+
// quaternion. Sync the quaternion to the current aim direction now so
|
|
110
|
+
// rotate mode starts from the true current direction instead of identity.
|
|
111
|
+
if ( object.isLight && object.target ) {
|
|
112
|
+
|
|
113
|
+
const forward = object.target.position.clone().sub( object.position );
|
|
114
|
+
const distance = forward.length();
|
|
115
|
+
|
|
116
|
+
if ( distance > 1e-4 ) {
|
|
117
|
+
|
|
118
|
+
object.quaternion.setFromUnitVectors( FORWARD, forward.normalize() );
|
|
119
|
+
this._lightTargetDistance = distance;
|
|
120
|
+
|
|
121
|
+
} else {
|
|
122
|
+
|
|
123
|
+
this._lightTargetDistance = 1;
|
|
124
|
+
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this._lastLightPosition = object.position.clone();
|
|
128
|
+
|
|
129
|
+
}
|
|
130
|
+
|
|
94
131
|
}
|
|
95
132
|
|
|
96
133
|
/**
|
|
@@ -102,6 +139,8 @@ export class TransformManager {
|
|
|
102
139
|
|
|
103
140
|
this._controls.detach();
|
|
104
141
|
this._attached = null;
|
|
142
|
+
this._lightTargetDistance = null;
|
|
143
|
+
this._lastLightPosition = null;
|
|
105
144
|
|
|
106
145
|
}
|
|
107
146
|
|
|
@@ -112,6 +151,9 @@ export class TransformManager {
|
|
|
112
151
|
|
|
113
152
|
this._controls.setMode( mode );
|
|
114
153
|
this._app?.dispatchEvent( { type: EngineEvents.TRANSFORM_MODE_CHANGED, mode } );
|
|
154
|
+
// The gizmo shape changes (arrows/rings/boxes) but nothing else invalidates
|
|
155
|
+
// the frame — nudge a redraw so it doesn't wait for the next camera move.
|
|
156
|
+
this._app?.refreshFrame();
|
|
115
157
|
|
|
116
158
|
}
|
|
117
159
|
|
|
@@ -121,6 +163,7 @@ export class TransformManager {
|
|
|
121
163
|
setSpace( space ) {
|
|
122
164
|
|
|
123
165
|
this._controls.setSpace( space );
|
|
166
|
+
this._app?.refreshFrame();
|
|
124
167
|
|
|
125
168
|
}
|
|
126
169
|
|
|
@@ -188,8 +231,17 @@ export class TransformManager {
|
|
|
188
231
|
|
|
189
232
|
} else {
|
|
190
233
|
|
|
191
|
-
// Drag ended — trigger final refit
|
|
192
|
-
this.
|
|
234
|
+
// Drag ended — trigger final refit (mesh) or finalize (light)
|
|
235
|
+
if ( this._attached?.isLight ) {
|
|
236
|
+
|
|
237
|
+
this._finalizeLightTransform();
|
|
238
|
+
|
|
239
|
+
} else {
|
|
240
|
+
|
|
241
|
+
this._recomputeAndRefit();
|
|
242
|
+
|
|
243
|
+
}
|
|
244
|
+
|
|
193
245
|
this._app.dispatchEvent( { type: EngineEvents.OBJECT_TRANSFORM_END } );
|
|
194
246
|
|
|
195
247
|
}
|
|
@@ -202,6 +254,73 @@ export class TransformManager {
|
|
|
202
254
|
this._app.needsReset = true;
|
|
203
255
|
this._app.wake();
|
|
204
256
|
|
|
257
|
+
if ( this._attached?.isLight ) {
|
|
258
|
+
|
|
259
|
+
this._syncLightDuringDrag();
|
|
260
|
+
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ── Light Transform Sync ──
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Called every gizmo move while a light is attached. Translate mode carries
|
|
269
|
+
* `.target` along by the same delta (so moving a light doesn't silently
|
|
270
|
+
* swing its aim); rotate mode recomputes `.target` from the light's
|
|
271
|
+
* quaternion at a fixed distance (so rotating actually steers the beam/sun).
|
|
272
|
+
* Also resyncs GPU light buffers + the visible SceneHelpers gizmo live.
|
|
273
|
+
*/
|
|
274
|
+
_syncLightDuringDrag() {
|
|
275
|
+
|
|
276
|
+
const light = this._attached;
|
|
277
|
+
|
|
278
|
+
if ( light.target ) {
|
|
279
|
+
|
|
280
|
+
const mode = this._controls.mode;
|
|
281
|
+
|
|
282
|
+
if ( mode === 'translate' && this._lastLightPosition ) {
|
|
283
|
+
|
|
284
|
+
const delta = this._tempForward.copy( light.position ).sub( this._lastLightPosition );
|
|
285
|
+
light.target.position.add( delta );
|
|
286
|
+
light.target.updateMatrixWorld( true );
|
|
287
|
+
|
|
288
|
+
} else if ( mode === 'rotate' && this._lightTargetDistance != null ) {
|
|
289
|
+
|
|
290
|
+
const forward = this._tempForward.set( 0, 0, - 1 ).applyQuaternion( light.quaternion );
|
|
291
|
+
light.target.position.copy( light.position ).addScaledVector( forward, this._lightTargetDistance );
|
|
292
|
+
light.target.updateMatrixWorld( true );
|
|
293
|
+
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
this._lastLightPosition.copy( light.position );
|
|
297
|
+
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
this._app.lightManager?.updateLights();
|
|
301
|
+
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Called once on drag end while a light is attached. Bakes RectAreaLight
|
|
306
|
+
* scale into width/height (the serializer also reads scale live, but the
|
|
307
|
+
* Lights panel sliders are the source of truth for size) and does a final
|
|
308
|
+
* GPU/helper resync.
|
|
309
|
+
*/
|
|
310
|
+
_finalizeLightTransform() {
|
|
311
|
+
|
|
312
|
+
const light = this._attached;
|
|
313
|
+
|
|
314
|
+
if ( light.isRectAreaLight && ( light.scale.x !== 1 || light.scale.y !== 1 ) ) {
|
|
315
|
+
|
|
316
|
+
light.width *= light.scale.x;
|
|
317
|
+
light.height *= light.scale.y;
|
|
318
|
+
light.scale.set( 1, 1, 1 );
|
|
319
|
+
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
this._app.lightManager?.updateLights();
|
|
323
|
+
|
|
205
324
|
}
|
|
206
325
|
|
|
207
326
|
// ── Position Extraction & BVH Refit ──
|
|
@@ -440,6 +559,9 @@ export class TransformManager {
|
|
|
440
559
|
this._skinnedCache = null;
|
|
441
560
|
this._normalCache = null;
|
|
442
561
|
this._baselineComputed = false;
|
|
562
|
+
this._tempForward = null;
|
|
563
|
+
this._lightTargetDistance = null;
|
|
564
|
+
this._lastLightPosition = null;
|
|
443
565
|
|
|
444
566
|
// Drop back-references to the owning app and shared resources so the
|
|
445
567
|
// PathTracerApp graph can be GC'd. Without this, _app pinned the entire
|