rayzee 8.8.0 → 8.8.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rayzee",
3
- "version": "8.8.0",
3
+ "version": "8.8.2",
4
4
  "type": "module",
5
5
  "description": "Real-time WebGPU path tracing engine built on Three.js",
6
6
  "main": "dist/rayzee.umd.js",
@@ -3473,6 +3473,12 @@ export class PathTracerApp extends EventDispatcher {
3473
3473
  this.cameraManager.setInteractionManager( this.interactionManager );
3474
3474
  this.lightManager = new LightManager( this.scene, this._sceneHelpers, this.stages.pathTracer, {
3475
3475
  onReset: () => this.reset(),
3476
+ // The gizmo stays attached to a removed light and warns every frame it draws.
3477
+ onLightRemoved: light => {
3478
+
3479
+ if ( this.interactionManager?.selectedObject === light ) this.interactionManager.deselect();
3480
+
3481
+ },
3476
3482
  } );
3477
3483
  this.goboManager = new GoboManager( this.stages.pathTracer, {
3478
3484
  onReset: () => this.reset(),
@@ -1013,6 +1013,8 @@ export class PathTracerStage extends RenderStage {
1013
1013
 
1014
1014
  if ( ! attr ) return;
1015
1015
  attr.array.set( data );
1016
+ // A pending update range would cut this full upload down to that range.
1017
+ attr.clearUpdateRanges();
1016
1018
  attr.needsUpdate = true;
1017
1019
 
1018
1020
  }
@@ -17,6 +17,7 @@ export class LightManager extends EventDispatcher {
17
17
  * @param {import('../Stages/PathTracer.js').PathTracer} pathTracer
18
18
  * @param {Object} [options]
19
19
  * @param {Function} [options.onReset] - Callback to reset accumulation after light changes
20
+ * @param {Function} [options.onLightRemoved] - Called with each light before it leaves the scene
20
21
  */
21
22
  constructor( scene, sceneHelpers, pathTracer, options = {} ) {
22
23
 
@@ -26,6 +27,7 @@ export class LightManager extends EventDispatcher {
26
27
  this.sceneHelpers = sceneHelpers;
27
28
  this.pathTracer = pathTracer;
28
29
  this._onReset = options.onReset || null;
30
+ this._onLightRemoved = options.onLightRemoved || null;
29
31
 
30
32
  }
31
33
 
@@ -108,6 +110,7 @@ export class LightManager extends EventDispatcher {
108
110
  const light = this.scene.getObjectByProperty( 'uuid', uuid );
109
111
  if ( ! light || ! light.isLight ) return false;
110
112
 
113
+ this._onLightRemoved?.( light );
111
114
  this.sceneHelpers.remove( light );
112
115
  if ( light.target ) light.target.removeFromParent();
113
116
  light.removeFromParent();
@@ -277,6 +280,7 @@ export class LightManager extends EventDispatcher {
277
280
 
278
281
  if ( this._disposed ) return;
279
282
  this._disposed = true;
283
+ this._onLightRemoved = null;
280
284
 
281
285
  this.sceneHelpers?.clear();
282
286
  this._removeAllLights();
@@ -296,6 +300,7 @@ export class LightManager extends EventDispatcher {
296
300
 
297
301
  this.scene.getObjectsByProperty( 'isLight', true ).forEach( light => {
298
302
 
303
+ this._onLightRemoved?.( light );
299
304
  if ( light.target ) this.scene.remove( light.target );
300
305
  this.scene.remove( light );
301
306
 
@@ -77,20 +77,7 @@ export class TransformManager {
77
77
  // rotate mode starts from the true current direction instead of identity.
78
78
  if ( object.isLight && object.target ) {
79
79
 
80
- const forward = object.target.position.clone().sub( object.position );
81
- const distance = forward.length();
82
-
83
- if ( distance > 1e-4 ) {
84
-
85
- object.quaternion.setFromUnitVectors( FORWARD, forward.normalize() );
86
- this._lightTargetDistance = distance;
87
-
88
- } else {
89
-
90
- this._lightTargetDistance = 1;
91
-
92
- }
93
-
80
+ this._syncLightAim( object );
94
81
  this._lastLightPosition = object.position.clone();
95
82
 
96
83
  }
@@ -233,9 +220,11 @@ export class TransformManager {
233
220
 
234
221
  /**
235
222
  * Called every gizmo move while a light is attached. Translate mode carries
236
- * `.target` along by the same delta (so moving a light doesn't silently
237
- * swing its aim); rotate mode recomputes `.target` from the light's
238
- * quaternion at a fixed distance (so rotating actually steers the beam/sun).
223
+ * a spot light's `.target` along by the same delta (so moving it doesn't
224
+ * silently swing its aim) but leaves a sun's in place: a sun is only a
225
+ * direction, so moving it must swing that, as the Lights panel's Position
226
+ * does. Rotate mode recomputes `.target` from the light's quaternion at a
227
+ * fixed distance (so rotating actually steers the beam/sun).
239
228
  * Also resyncs GPU light buffers + the visible SceneHelpers gizmo live.
240
229
  */
241
230
  _syncLightDuringDrag() {
@@ -246,7 +235,7 @@ export class TransformManager {
246
235
 
247
236
  const mode = this._controls.mode;
248
237
 
249
- if ( mode === 'translate' && this._lastLightPosition ) {
238
+ if ( mode === 'translate' && this._lastLightPosition && ! light.isDirectionalLight ) {
250
239
 
251
240
  const delta = this._tempForward.copy( light.position ).sub( this._lastLightPosition );
252
241
  light.target.position.add( delta );
@@ -271,13 +260,20 @@ export class TransformManager {
271
260
  /**
272
261
  * Called once on drag end while a light is attached. Bakes RectAreaLight
273
262
  * scale into width/height (the serializer also reads scale live, but the
274
- * Lights panel sliders are the source of truth for size) and does a final
275
- * GPU/helper resync.
263
+ * Lights panel sliders are the source of truth for size), re-aims a moved
264
+ * sun's quaternion so rotate mode starts from its new direction, and does a
265
+ * final GPU/helper resync.
276
266
  */
277
267
  _finalizeLightTransform() {
278
268
 
279
269
  const light = this._attached;
280
270
 
271
+ if ( light.isDirectionalLight && light.target && this._controls.mode === 'translate' ) {
272
+
273
+ this._syncLightAim( light );
274
+
275
+ }
276
+
281
277
  if ( light.isRectAreaLight && ( light.scale.x !== 1 || light.scale.y !== 1 ) ) {
282
278
 
283
279
  light.width *= light.scale.x;
@@ -290,6 +286,28 @@ export class TransformManager {
290
286
 
291
287
  }
292
288
 
289
+ /**
290
+ * Point the light's quaternion at its `.target` and record the distance, so
291
+ * rotate mode steers from the true current direction instead of identity.
292
+ */
293
+ _syncLightAim( light ) {
294
+
295
+ const forward = this._tempForward.copy( light.target.position ).sub( light.position );
296
+ const distance = forward.length();
297
+
298
+ if ( distance > 1e-4 ) {
299
+
300
+ light.quaternion.setFromUnitVectors( FORWARD, forward.normalize() );
301
+ this._lightTargetDistance = distance;
302
+
303
+ } else {
304
+
305
+ this._lightTargetDistance = 1;
306
+
307
+ }
308
+
309
+ }
310
+
293
311
  // ── Position Extraction & BVH Refit ──
294
312
 
295
313
  /**