rayzee 8.6.1 → 8.7.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/dist/rayzee.es.js +3507 -2955
- package/dist/rayzee.umd.js +16 -16
- package/package.json +1 -1
- package/src/PathTracerApp.js +97 -2
- package/src/Processor/AssetLoader.js +14 -8
- package/src/Processor/EmissiveTriangleBuilder.js +70 -0
- package/src/Processor/PBRT/PBRTAnimation.js +646 -0
- package/src/Processor/PBRT/PBRTParser.js +179 -52
- package/src/Processor/PBRT/PBRTSceneBuilder.js +181 -25
- package/src/Processor/PBRT/index.js +73 -14
- package/src/Processor/SceneProcessor.js +22 -0
- package/src/Stages/PathTracerStage.js +16 -1
- package/src/managers/AnimationManager.js +202 -8
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AnimationManager — Drives
|
|
2
|
+
* AnimationManager — Drives animation clips for the path tracer.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* a
|
|
4
|
+
* Deforming clips (a SkinnedMesh or morph track) return a per-mesh position reader for
|
|
5
|
+
* PathTracerApp.refitBVH(). Rigid clips return nothing and hand the moved meshes to
|
|
6
|
+
* `applyPoseCallback`, which updates placements only — a refit would bake world positions into
|
|
7
|
+
* triangles that other placements share.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
|
-
import { AnimationMixer, EventDispatcher, Timer, Vector3, LoopRepeat, LoopOnce } from 'three';
|
|
10
|
+
import { AnimationMixer, EventDispatcher, Timer, Vector3, LoopRepeat, LoopOnce, PropertyBinding } from 'three';
|
|
10
11
|
import { EngineEvents } from '../EngineEvents.js';
|
|
11
12
|
|
|
12
13
|
export class AnimationManager extends EventDispatcher {
|
|
@@ -31,9 +32,20 @@ export class AnimationManager extends EventDispatcher {
|
|
|
31
32
|
this._savedTimeScale = 1;
|
|
32
33
|
this.onFinished = null; // callback when a non-looping clip ends
|
|
33
34
|
|
|
35
|
+
this._rigid = false;
|
|
36
|
+
this._plans = null; // per clip: { meshes: Int32Array, visibility: boolean, cameras: Camera[] }
|
|
37
|
+
this._allPlan = null;
|
|
38
|
+
this._activeClip = 0;
|
|
39
|
+
this._slotOf = null; // meshIndex -> row in the two caches below, or -1
|
|
40
|
+
this._lastWorld = null; // world matrix last handed to applyPoseCallback, 16 per row
|
|
41
|
+
this._lastVisible = null; // 0 / 1, or 2 before the first pose
|
|
42
|
+
|
|
34
43
|
/** Injected by PathTracerApp — wakes the render loop after play/resume. */
|
|
35
44
|
this.wakeCallback = null;
|
|
36
45
|
|
|
46
|
+
/** Injected by PathTracerApp — receives `{ meshIndices, visibilityChanged, cameras }`. */
|
|
47
|
+
this.applyPoseCallback = null;
|
|
48
|
+
|
|
37
49
|
}
|
|
38
50
|
|
|
39
51
|
/**
|
|
@@ -88,6 +100,10 @@ export class AnimationManager extends EventDispatcher {
|
|
|
88
100
|
|
|
89
101
|
this.actions = actions;
|
|
90
102
|
|
|
103
|
+
this._rigid = ! meshes.some( m => m?.isSkinnedMesh ) &&
|
|
104
|
+
! animations.some( clip => clip.tracks.some( t => t.name.includes( 'morphTargetInfluences' ) ) );
|
|
105
|
+
this._planClips( animations );
|
|
106
|
+
|
|
91
107
|
// Listen for non-looping clip completion
|
|
92
108
|
this.mixer.addEventListener( 'finished', () => {
|
|
93
109
|
|
|
@@ -121,7 +137,154 @@ export class AnimationManager extends EventDispatcher {
|
|
|
121
137
|
this._meshPositions = [];
|
|
122
138
|
|
|
123
139
|
const skinnedCount = meshes.filter( m => m.isSkinnedMesh ).length;
|
|
124
|
-
console.debug( `[AnimationManager] Init: ${animations.length} clips, ${meshes.length} meshes (${skinnedCount} skinned), ${offset} triangles` );
|
|
140
|
+
console.debug( `[AnimationManager] Init: ${animations.length} clips, ${meshes.length} meshes (${skinnedCount} skinned), ${offset} triangles, ${this._rigid ? 'rigid' : 'deforming'}` );
|
|
141
|
+
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Which meshes and cameras each clip moves, directly or through an ancestor. @private */
|
|
145
|
+
_planClips( animations ) {
|
|
146
|
+
|
|
147
|
+
const root = this._mixerRoot;
|
|
148
|
+
const byName = new Map();
|
|
149
|
+
// First name in pre-order wins, as in PropertyBinding.findNode.
|
|
150
|
+
root.traverse?.( node => {
|
|
151
|
+
|
|
152
|
+
if ( ! byName.has( node.name ) ) byName.set( node.name, node );
|
|
153
|
+
byName.set( node.uuid, node );
|
|
154
|
+
|
|
155
|
+
} );
|
|
156
|
+
|
|
157
|
+
this._plans = animations.map( clip => {
|
|
158
|
+
|
|
159
|
+
const animated = new Set();
|
|
160
|
+
let visibility = false;
|
|
161
|
+
for ( const track of clip.tracks ) {
|
|
162
|
+
|
|
163
|
+
const { nodeName, propertyName } = PropertyBinding.parseTrackName( track.name );
|
|
164
|
+
const node = byName.get( nodeName );
|
|
165
|
+
if ( ! node ) continue;
|
|
166
|
+
animated.add( node );
|
|
167
|
+
if ( propertyName === 'visible' ) visibility = true;
|
|
168
|
+
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const meshes = [];
|
|
172
|
+
for ( let m = 0; m < this._meshes.length; m ++ ) {
|
|
173
|
+
|
|
174
|
+
for ( let o = this._meshes[ m ]; o; o = o.parent ) {
|
|
175
|
+
|
|
176
|
+
if ( animated.has( o ) ) {
|
|
177
|
+
|
|
178
|
+
meshes.push( m );
|
|
179
|
+
break;
|
|
180
|
+
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const cameras = new Set();
|
|
188
|
+
for ( const node of animated ) node.traverse( o => o.isCamera && cameras.add( o ) );
|
|
189
|
+
|
|
190
|
+
return { meshes: Int32Array.from( meshes ), visibility, cameras: [ ...cameras ] };
|
|
191
|
+
|
|
192
|
+
} );
|
|
193
|
+
|
|
194
|
+
this._allPlan = null;
|
|
195
|
+
if ( ! this._rigid ) return;
|
|
196
|
+
|
|
197
|
+
this._slotOf = new Int32Array( this._meshes.length ).fill( - 1 );
|
|
198
|
+
let rows = 0;
|
|
199
|
+
for ( const plan of this._plans ) for ( const m of plan.meshes ) if ( this._slotOf[ m ] < 0 ) this._slotOf[ m ] = rows ++;
|
|
200
|
+
|
|
201
|
+
// Unknown until the first pose, which therefore sends every animated mesh.
|
|
202
|
+
this._lastWorld = new Float64Array( rows * 16 ).fill( NaN );
|
|
203
|
+
this._lastVisible = new Uint8Array( rows ).fill( 2 );
|
|
204
|
+
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** The plan for a clip index, or the union of every clip for -1. @private */
|
|
208
|
+
_planFor( clipIndex ) {
|
|
209
|
+
|
|
210
|
+
if ( ! this._plans ) return null;
|
|
211
|
+
if ( clipIndex >= 0 ) return this._plans[ clipIndex ] ?? null;
|
|
212
|
+
|
|
213
|
+
if ( ! this._allPlan ) {
|
|
214
|
+
|
|
215
|
+
const meshes = new Set(), cameras = new Set();
|
|
216
|
+
let visibility = false;
|
|
217
|
+
for ( const plan of this._plans ) {
|
|
218
|
+
|
|
219
|
+
plan.meshes.forEach( m => meshes.add( m ) );
|
|
220
|
+
plan.cameras.forEach( c => cameras.add( c ) );
|
|
221
|
+
visibility ||= plan.visibility;
|
|
222
|
+
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
this._allPlan = { meshes: Int32Array.from( meshes ), visibility, cameras: [ ...cameras ] };
|
|
226
|
+
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return this._allPlan;
|
|
230
|
+
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Send meshes whose world matrix or visibility changed since the last pose. @private */
|
|
234
|
+
_applyRigidPose( clipIndex ) {
|
|
235
|
+
|
|
236
|
+
const plan = this._planFor( clipIndex );
|
|
237
|
+
if ( ! plan ) return;
|
|
238
|
+
|
|
239
|
+
const moved = [];
|
|
240
|
+
let visibilityChanged = false;
|
|
241
|
+
const last = this._lastWorld, lastVisible = this._lastVisible;
|
|
242
|
+
|
|
243
|
+
for ( const m of plan.meshes ) {
|
|
244
|
+
|
|
245
|
+
const mesh = this._meshes[ m ];
|
|
246
|
+
const row = this._slotOf[ m ];
|
|
247
|
+
const e = mesh.matrixWorld.elements;
|
|
248
|
+
const o = row * 16;
|
|
249
|
+
|
|
250
|
+
for ( let i = 0; i < 16; i ++ ) {
|
|
251
|
+
|
|
252
|
+
if ( last[ o + i ] !== e[ i ] ) {
|
|
253
|
+
|
|
254
|
+
for ( let j = 0; j < 16; j ++ ) last[ o + j ] = e[ j ];
|
|
255
|
+
moved.push( m );
|
|
256
|
+
break;
|
|
257
|
+
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if ( plan.visibility || lastVisible[ row ] === 2 ) {
|
|
263
|
+
|
|
264
|
+
let visible = 1;
|
|
265
|
+
for ( let n = mesh; n; n = n.parent ) if ( ! n.visible ) {
|
|
266
|
+
|
|
267
|
+
visible = 0;
|
|
268
|
+
break;
|
|
269
|
+
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if ( lastVisible[ row ] !== visible ) {
|
|
273
|
+
|
|
274
|
+
lastVisible[ row ] = visible;
|
|
275
|
+
visibilityChanged = true;
|
|
276
|
+
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if ( moved.length || visibilityChanged || plan.cameras.length ) {
|
|
284
|
+
|
|
285
|
+
this.applyPoseCallback?.( { meshIndices: moved, visibilityChanged, cameras: plan.cameras } );
|
|
286
|
+
|
|
287
|
+
}
|
|
125
288
|
|
|
126
289
|
}
|
|
127
290
|
|
|
@@ -145,6 +308,7 @@ export class AnimationManager extends EventDispatcher {
|
|
|
145
308
|
|
|
146
309
|
}
|
|
147
310
|
|
|
311
|
+
this._activeClip = clipIndex;
|
|
148
312
|
this.timer.reset();
|
|
149
313
|
this.isPlaying = true;
|
|
150
314
|
this.wakeCallback?.();
|
|
@@ -188,10 +352,19 @@ export class AnimationManager extends EventDispatcher {
|
|
|
188
352
|
|
|
189
353
|
if ( ! this.mixer ) return;
|
|
190
354
|
|
|
355
|
+
// Restores every animated property to its loaded value.
|
|
191
356
|
this.mixer.stopAllAction();
|
|
192
357
|
this.mixer.timeScale = this._savedTimeScale || 1;
|
|
193
358
|
this.timer.reset();
|
|
194
359
|
this.isPlaying = false;
|
|
360
|
+
|
|
361
|
+
if ( this._rigid ) {
|
|
362
|
+
|
|
363
|
+
this._mixerRoot.updateMatrixWorld( true );
|
|
364
|
+
this._applyRigidPose( - 1 );
|
|
365
|
+
|
|
366
|
+
}
|
|
367
|
+
|
|
195
368
|
this.dispatchEvent( { type: EngineEvents.ANIMATION_STOPPED } );
|
|
196
369
|
|
|
197
370
|
}
|
|
@@ -229,12 +402,15 @@ export class AnimationManager extends EventDispatcher {
|
|
|
229
402
|
*
|
|
230
403
|
* @param {number} time - Absolute time in seconds
|
|
231
404
|
* @param {number} [clipIndex=0] - Clip to evaluate, or -1 for all active
|
|
232
|
-
* @returns {function(number): Float32Array|null} Per-mesh reader for refitBVH, or null if no
|
|
405
|
+
* @returns {function(number): Float32Array|null} Per-mesh reader for refitBVH, or null if no
|
|
406
|
+
* mixer or the clips are rigid
|
|
233
407
|
*/
|
|
234
408
|
seekTo( time, clipIndex = 0 ) {
|
|
235
409
|
|
|
236
410
|
if ( ! this.mixer || this.actions.length === 0 ) return null;
|
|
237
411
|
|
|
412
|
+
this._activeClip = clipIndex;
|
|
413
|
+
|
|
238
414
|
// Ensure the target action(s) are active so setTime evaluates them.
|
|
239
415
|
// Actions must NOT be paused — setTime() calls update() internally,
|
|
240
416
|
// which skips paused actions entirely.
|
|
@@ -279,7 +455,8 @@ export class AnimationManager extends EventDispatcher {
|
|
|
279
455
|
* Advance animation and prepare deformed positions.
|
|
280
456
|
* Call once per frame from the animate loop.
|
|
281
457
|
*
|
|
282
|
-
* @returns {function(number): Float32Array|null} Per-mesh reader for refitBVH, or null if not
|
|
458
|
+
* @returns {function(number): Float32Array|null} Per-mesh reader for refitBVH, or null if not
|
|
459
|
+
* playing or the clips are rigid
|
|
283
460
|
*/
|
|
284
461
|
update() {
|
|
285
462
|
|
|
@@ -312,6 +489,16 @@ export class AnimationManager extends EventDispatcher {
|
|
|
312
489
|
// once here, on mixerRoot rather than the scene, keeps unrelated static objects out of it.
|
|
313
490
|
this._mixerRoot.updateMatrixWorld( true );
|
|
314
491
|
|
|
492
|
+
if ( this._rigid ) {
|
|
493
|
+
|
|
494
|
+
this._applyRigidPose( this._activeClip );
|
|
495
|
+
return null;
|
|
496
|
+
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const cameras = this._planFor( this._activeClip )?.cameras;
|
|
500
|
+
if ( cameras?.length ) this.applyPoseCallback?.( { meshIndices: [], visibilityChanged: false, cameras } );
|
|
501
|
+
|
|
315
502
|
return m => this._computeMeshPositions( m );
|
|
316
503
|
|
|
317
504
|
}
|
|
@@ -446,6 +633,13 @@ export class AnimationManager extends EventDispatcher {
|
|
|
446
633
|
this._meshPositions = null;
|
|
447
634
|
this._skinnedCache = null;
|
|
448
635
|
this._clipsCache = null;
|
|
636
|
+
this._rigid = false;
|
|
637
|
+
this._plans = null;
|
|
638
|
+
this._allPlan = null;
|
|
639
|
+
this._activeClip = 0;
|
|
640
|
+
this._slotOf = null;
|
|
641
|
+
this._lastWorld = null;
|
|
642
|
+
this._lastVisible = null;
|
|
449
643
|
|
|
450
644
|
}
|
|
451
645
|
|