rayzee 9.3.1 → 9.3.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": "9.3.1",
3
+ "version": "9.3.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",
@@ -1925,9 +1925,9 @@ export class PathTracerApp extends EventDispatcher {
1925
1925
  async refitBVH( newPositions, newNormals ) {
1926
1926
 
1927
1927
  await this.ensureSceneResident();
1928
- const result = await this._sdf.refitBVH( newPositions, newNormals );
1928
+ const { triRanges, ...result } = await this._sdf.refitBVH( newPositions, newNormals );
1929
1929
 
1930
- this.stages.pathTracer.updateTriangleData( this._sdf.triangles );
1930
+ this.stages.pathTracer.updateTriangleData( this._sdf.triangles, triRanges );
1931
1931
  this.stages.pathTracer.updateBVHData( this._sdf.bvh );
1932
1932
  this.reset( false, { motion: true } );
1933
1933
 
@@ -2648,6 +2648,8 @@ export class SceneProcessor {
2648
2648
  // can be released as soon as it is read. Callers hand over world space; triangles are
2649
2649
  // stored per instance, so each mesh comes back through its own inverse.
2650
2650
  const table = this.instanceTable;
2651
+ const FPT = TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE;
2652
+ const triRanges = [];
2651
2653
  for ( let i = 0; i < table.count; i ++ ) {
2652
2654
 
2653
2655
  if ( ! table.isSet[ i ] || ! table.isOwner( i ) ) continue;
@@ -2664,6 +2666,11 @@ export class SceneProcessor {
2664
2666
 
2665
2667
  }
2666
2668
 
2669
+ const offset = table.triOffsetOf( i ) * FPT, count = table.triCountOf( i ) * FPT;
2670
+ const last = triRanges.at( - 1 );
2671
+ if ( last && last.offset + last.count === offset ) last.count += count;
2672
+ else triRanges.push( { offset, count } );
2673
+
2667
2674
  }
2668
2675
 
2669
2676
  return new Promise( ( resolve, reject ) => {
@@ -2673,7 +2680,7 @@ export class SceneProcessor {
2673
2680
  const msg = e.data;
2674
2681
  if ( msg.type === 'refitComplete' ) {
2675
2682
 
2676
- resolve( { refitTimeMs: msg.refitTimeMs } );
2683
+ resolve( { refitTimeMs: msg.refitTimeMs, triRanges } );
2677
2684
 
2678
2685
  } else if ( msg.type === 'error' ) {
2679
2686
 
@@ -1088,12 +1088,17 @@ export class PathTracerStage extends RenderStage {
1088
1088
 
1089
1089
  }
1090
1090
 
1091
- /** Re-uploads every triangle from the CPU records, adopting `triangleData` as them when given. */
1092
- updateTriangleData( triangleData ) {
1091
+ /**
1092
+ * Re-uploads triangles from the CPU records, adopting `triangleData` as them when given.
1093
+ * @param {Object} [triangleData]
1094
+ * @param {Array<{offset:number,count:number}>} [ranges] - only these, in record lanes; every triangle when omitted
1095
+ */
1096
+ updateTriangleData( triangleData, ranges ) {
1093
1097
 
1094
1098
  if ( triangleData?.chunks?.length > 1 ) this._triangleRecords = triangleData;
1095
1099
  else if ( triangleData ) this._triangleFlat = triangleData.chunks ? triangleData.chunks[ 0 ] : triangleData;
1096
- this._uploadTriangles( 0, this._triangleRecordCount );
1100
+ if ( ranges ) this.updateBufferRanges( ranges, [] );
1101
+ else this._uploadTriangles( 0, this._triangleRecordCount );
1097
1102
 
1098
1103
  }
1099
1104
 
@@ -39,6 +39,8 @@ export class AnimationManager extends EventDispatcher {
39
39
  this._slotOf = null; // meshIndex -> row in the two caches below, or -1
40
40
  this._lastWorld = null; // world matrix last handed to applyPoseCallback, 16 per row
41
41
  this._lastVisible = null; // 0 / 1, or 2 before the first pose
42
+ this._lastMeshWorld = null; // deforming clips: world matrix at each mesh's last read, 16 per mesh
43
+ this._lastMorphWeights = null;
42
44
 
43
45
  /** Injected by PathTracerApp — wakes the render loop after play/resume. */
44
46
  this.wakeCallback = null;
@@ -135,6 +137,8 @@ export class AnimationManager extends EventDispatcher {
135
137
  // 9 floats × every triangle — 1,030 MB at 30M, past what a renderer can allocate.
136
138
  this._skinnedCache = [];
137
139
  this._meshPositions = [];
140
+ this._lastMeshWorld = this._rigid ? null : new Float64Array( meshes.length * 16 ).fill( NaN );
141
+ this._lastMorphWeights = [];
138
142
 
139
143
  const skinnedCount = meshes.filter( m => m.isSkinnedMesh ).length;
140
144
  console.debug( `[AnimationManager] Init: ${animations.length} clips, ${meshes.length} meshes (${skinnedCount} skinned), ${offset} triangles, ${this._rigid ? 'rigid' : 'deforming'}` );
@@ -499,7 +503,52 @@ export class AnimationManager extends EventDispatcher {
499
503
  const cameras = this._planFor( this._activeClip )?.cameras;
500
504
  if ( cameras?.length ) this.applyPoseCallback?.( { meshIndices: [], visibilityChanged: false, cameras } );
501
505
 
502
- return m => this._computeMeshPositions( m );
506
+ // Decided once per reader: an expanded instanced emitter asks for one mesh several times.
507
+ const moved = new Int8Array( this._meshes.length );
508
+ return m => {
509
+
510
+ if ( moved[ m ] === 0 ) moved[ m ] = this._meshes[ m ].isSkinnedMesh || this._poseChanged( m ) ? 1 : - 1;
511
+ return moved[ m ] > 0 ? this._computeMeshPositions( m ) : null;
512
+
513
+ };
514
+
515
+ }
516
+
517
+ /** Whether a mesh's world matrix or morph weights differ from when its positions were last read. @private */
518
+ _poseChanged( m ) {
519
+
520
+ const mesh = this._meshes[ m ];
521
+ const last = this._lastMeshWorld;
522
+ const e = mesh.matrixWorld.elements;
523
+ const o = m * 16;
524
+ let changed = false;
525
+
526
+ for ( let i = 0; i < 16; i ++ ) {
527
+
528
+ if ( last[ o + i ] !== e[ i ] ) {
529
+
530
+ for ( let j = 0; j < 16; j ++ ) last[ o + j ] = e[ j ];
531
+ changed = true;
532
+ break;
533
+
534
+ }
535
+
536
+ }
537
+
538
+ const weights = mesh.morphTargetInfluences;
539
+ if ( weights?.length ) {
540
+
541
+ const lastWeights = this._lastMorphWeights[ m ];
542
+ if ( ! lastWeights || lastWeights.length !== weights.length || lastWeights.some( ( w, i ) => w !== weights[ i ] ) ) {
543
+
544
+ this._lastMorphWeights[ m ] = Float64Array.from( weights );
545
+ changed = true;
546
+
547
+ }
548
+
549
+ }
550
+
551
+ return changed;
503
552
 
504
553
  }
505
554
 
@@ -640,6 +689,8 @@ export class AnimationManager extends EventDispatcher {
640
689
  this._slotOf = null;
641
690
  this._lastWorld = null;
642
691
  this._lastVisible = null;
692
+ this._lastMeshWorld = null;
693
+ this._lastMorphWeights = null;
643
694
 
644
695
  }
645
696
 
@@ -1,4 +1,4 @@
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
4
  import { AF_DEFAULTS, CAMERA_PROJECTION_IDS } from '../EngineDefaults.js';
@@ -62,6 +62,7 @@ export class CameraManager extends EventDispatcher {
62
62
  this._smoothedFocusDistance = null;
63
63
  this._afPointDirty = false;
64
64
  this._afSuspended = false;
65
+ this._afPick = null;
65
66
 
66
67
  // Saved state for default camera when switching to model cameras
67
68
  this._defaultCameraState = null;
@@ -833,13 +834,12 @@ export class CameraManager extends EventDispatcher {
833
834
  && stage.frameCount > 0
834
835
  && ! stage.isComplete ) return;
835
836
 
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 );
837
+ const hitPoint = this._pickFocusPoint( stage.resetCount );
838
838
 
839
839
  let rawDistance;
840
- if ( validHit ) {
840
+ if ( hitPoint ) {
841
841
 
842
- rawDistance = viewDepth( validHit.point, this.camera );
842
+ rawDistance = viewDepth( hitPoint, this.camera );
843
843
  this._lastValidFocusDistance = rawDistance;
844
844
 
845
845
  } else {
@@ -910,6 +910,32 @@ export class CameraManager extends EventDispatcher {
910
910
 
911
911
  }
912
912
 
913
+ /** Picks again only on a new view or AF point, or a render reset, which every scene change makes. @private */
914
+ _pickFocusPoint( resetCount ) {
915
+
916
+ const camera = this.camera;
917
+ const { x, y } = this.afScreenPoint;
918
+ const last = this._afPick ??= { point: new Vector3(), hit: false, resetCount: NaN, x: NaN, y: NaN, view: new Matrix4(), projection: new Matrix4() };
919
+
920
+ if ( this._afPointDirty || resetCount !== last.resetCount || x !== last.x || y !== last.y
921
+ || ! last.view.equals( camera.matrixWorld ) || ! last.projection.equals( camera.projectionMatrix ) ) {
922
+
923
+ // AF screen point (0 to 1, y down) to NDC.
924
+ const hit = this.interactionManager.pickSurface( x * 2 - 1, 1 - y * 2 );
925
+ last.hit = !! hit;
926
+ if ( hit ) last.point.copy( hit.point );
927
+ last.resetCount = resetCount;
928
+ last.x = x;
929
+ last.y = y;
930
+ last.view.copy( camera.matrixWorld );
931
+ last.projection.copy( camera.projectionMatrix );
932
+
933
+ }
934
+
935
+ return last.hit ? last.point : null;
936
+
937
+ }
938
+
913
939
  /**
914
940
  * Deferred dependency injection — InteractionManager needs the camera
915
941
  * in its constructor, so it can't be passed during CameraManager creation.