rayzee 9.3.2 → 9.3.3
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/{NeuralRetouch-CYgkQY8Z.js → NeuralRetouch-vkdXXyK2.js} +1 -1
- package/dist/{NeuralSuperRes-PkgDLQcD.js → NeuralSuperRes-FVqRJKQV.js} +1 -1
- package/dist/{ToneMapGPU-B6XIe3DU.js → ToneMapGPU-DKQM1pMd.js} +86 -78
- package/dist/rayzee.es.js +559 -459
- package/dist/rayzee.umd.js +67 -38
- package/package.json +1 -1
- package/src/Passes/AIUpscaler.js +58 -141
- package/src/Passes/OIDNDenoiser.js +92 -65
- package/src/PathTracerApp.js +2 -2
- package/src/Processor/BVHRefitter.js +64 -0
- package/src/Processor/SceneProcessor.js +27 -5
- package/src/Processor/ToneMapGPU.js +64 -15
- package/src/Processor/Workers/BVHRefitWorker.js +5 -2
- package/src/Stages/PathTracerStage.js +21 -4
- package/src/managers/AnimationManager.js +36 -5
|
@@ -2597,6 +2597,7 @@ export class SceneProcessor {
|
|
|
2597
2597
|
async refitBVH( newPositions, newNormals ) {
|
|
2598
2598
|
|
|
2599
2599
|
this._geometryVersion = ( this._geometryVersion ?? 0 ) + 1;
|
|
2600
|
+
this._tlasLeafBoxesOf = null;
|
|
2600
2601
|
|
|
2601
2602
|
if ( ! this.bvh || ! this.triangles || ! this.instanceTable ) {
|
|
2602
2603
|
|
|
@@ -2650,13 +2651,17 @@ export class SceneProcessor {
|
|
|
2650
2651
|
const table = this.instanceTable;
|
|
2651
2652
|
const FPT = TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE;
|
|
2652
2653
|
const triRanges = [];
|
|
2654
|
+
const blasRanges = [];
|
|
2655
|
+
let owners = 0;
|
|
2653
2656
|
for ( let i = 0; i < table.count; i ++ ) {
|
|
2654
2657
|
|
|
2655
2658
|
if ( ! table.isSet[ i ] || ! table.isOwner( i ) ) continue;
|
|
2659
|
+
owners ++;
|
|
2656
2660
|
|
|
2657
2661
|
const p = positionsFor( i );
|
|
2658
2662
|
if ( ! p ) continue;
|
|
2659
2663
|
this._updateMeshTrianglePositions( i, p );
|
|
2664
|
+
blasRanges.push( table.blasOffsetOf( i ), table.blasNodeCountOf( i ) );
|
|
2660
2665
|
|
|
2661
2666
|
// Smooth normals overwrite the face normals just computed, so they follow per mesh.
|
|
2662
2667
|
if ( normalsFor ) {
|
|
@@ -2673,6 +2678,11 @@ export class SceneProcessor {
|
|
|
2673
2678
|
|
|
2674
2679
|
}
|
|
2675
2680
|
|
|
2681
|
+
const tlasNodeCount = table.tlasNodeCount;
|
|
2682
|
+
const partial = tlasNodeCount > 0 && blasRanges.length / 2 < owners;
|
|
2683
|
+
const bvhRanges = partial ? [ this.computeTLASDirtyRange() ] : null;
|
|
2684
|
+
for ( let r = 0; partial && r < blasRanges.length; r += 2 ) bvhRanges.push( { offset: blasRanges[ r ] * 16, count: blasRanges[ r + 1 ] * 16 } );
|
|
2685
|
+
|
|
2676
2686
|
return new Promise( ( resolve, reject ) => {
|
|
2677
2687
|
|
|
2678
2688
|
this._refitWorker.onmessage = ( e ) => {
|
|
@@ -2680,7 +2690,7 @@ export class SceneProcessor {
|
|
|
2680
2690
|
const msg = e.data;
|
|
2681
2691
|
if ( msg.type === 'refitComplete' ) {
|
|
2682
2692
|
|
|
2683
|
-
resolve( { refitTimeMs: msg.refitTimeMs, triRanges } );
|
|
2693
|
+
resolve( { refitTimeMs: msg.refitTimeMs, triRanges, bvhRanges } );
|
|
2684
2694
|
|
|
2685
2695
|
} else if ( msg.type === 'error' ) {
|
|
2686
2696
|
|
|
@@ -2691,7 +2701,7 @@ export class SceneProcessor {
|
|
|
2691
2701
|
};
|
|
2692
2702
|
|
|
2693
2703
|
// Signal worker — no data transfer needed, everything is in shared memory
|
|
2694
|
-
this._refitWorker.postMessage( { type: 'refit' } );
|
|
2704
|
+
this._refitWorker.postMessage( { type: 'refit', blasRanges: partial ? blasRanges : null, tlasNodeCount } );
|
|
2695
2705
|
|
|
2696
2706
|
} );
|
|
2697
2707
|
|
|
@@ -2841,6 +2851,8 @@ export class SceneProcessor {
|
|
|
2841
2851
|
const start = performance.now();
|
|
2842
2852
|
const table = this.instanceTable;
|
|
2843
2853
|
const composed = this._transformScratch ??= new Float32Array( 16 );
|
|
2854
|
+
if ( this._movedPlacements?.length !== table.count ) this._movedPlacements = new Uint8Array( table.count );
|
|
2855
|
+
const movedMask = this._movedPlacements;
|
|
2844
2856
|
let moved = 0;
|
|
2845
2857
|
|
|
2846
2858
|
for ( const meshIndex of meshIndices ) {
|
|
@@ -2877,13 +2889,15 @@ export class SceneProcessor {
|
|
|
2877
2889
|
}
|
|
2878
2890
|
|
|
2879
2891
|
this._writeLeafMatrix( p );
|
|
2892
|
+
movedMask[ p ] = 1;
|
|
2880
2893
|
moved ++;
|
|
2881
2894
|
|
|
2882
2895
|
}
|
|
2883
2896
|
|
|
2884
2897
|
}
|
|
2885
2898
|
|
|
2886
|
-
this._refitTLAS();
|
|
2899
|
+
this._refitTLAS( movedMask );
|
|
2900
|
+
movedMask.fill( 0 );
|
|
2887
2901
|
|
|
2888
2902
|
return { refitTimeMs: performance.now() - start, placements: moved };
|
|
2889
2903
|
|
|
@@ -3364,6 +3378,8 @@ export class SceneProcessor {
|
|
|
3364
3378
|
/** Keep the chunked BVH and its u32 index view in step. @private */
|
|
3365
3379
|
_setBVHData( data ) {
|
|
3366
3380
|
|
|
3381
|
+
this._tlasLeafBoxesOf = null;
|
|
3382
|
+
|
|
3367
3383
|
const records = ! data ? null
|
|
3368
3384
|
: ( data instanceof ChunkedRecords
|
|
3369
3385
|
? data
|
|
@@ -3545,9 +3561,11 @@ export class SceneProcessor {
|
|
|
3545
3561
|
/**
|
|
3546
3562
|
* Refit TLAS AABBs in-place without rebuilding the tree structure.
|
|
3547
3563
|
* O(tlasNodeCount) bottom-up pass — much faster than full SAH rebuild.
|
|
3564
|
+
* @param {Uint8Array} [moved] - by placement: only these leaves' world boxes are recomputed, the
|
|
3565
|
+
* rest kept from the last pass. Every leaf when omitted, or when nothing valid was kept.
|
|
3548
3566
|
* @private
|
|
3549
3567
|
*/
|
|
3550
|
-
_refitTLAS() {
|
|
3568
|
+
_refitTLAS( moved = null ) {
|
|
3551
3569
|
|
|
3552
3570
|
const tlasNodeCount = this.instanceTable.tlasNodeCount;
|
|
3553
3571
|
|
|
@@ -3555,10 +3573,12 @@ export class SceneProcessor {
|
|
|
3555
3573
|
if ( ! this._tlasBounds || this._tlasBounds.length < tlasNodeCount * 6 ) {
|
|
3556
3574
|
|
|
3557
3575
|
this._tlasBounds = new Float32Array( tlasNodeCount * 6 );
|
|
3576
|
+
this._tlasLeafBoxesOf = null;
|
|
3558
3577
|
|
|
3559
3578
|
}
|
|
3560
3579
|
|
|
3561
3580
|
const table = this.instanceTable;
|
|
3581
|
+
const keep = moved && this._tlasLeafBoxesOf === table;
|
|
3562
3582
|
|
|
3563
3583
|
// Bottom-up pass: reverse iteration over TLAS nodes
|
|
3564
3584
|
for ( let i = tlasNodeCount - 1; i >= 0; i -- ) {
|
|
@@ -3574,7 +3594,7 @@ export class SceneProcessor {
|
|
|
3574
3594
|
// every placement of a shared geometry onto one box, so all but one copy sat
|
|
3575
3595
|
// outside its own bounds and rays walked straight past it.
|
|
3576
3596
|
const entryIndex = idxChunk[ o + 1 ] & TLAS_PLACEMENT_MASK;
|
|
3577
|
-
if ( entryIndex < table.count ) {
|
|
3597
|
+
if ( entryIndex < table.count && ! ( keep && ! moved[ entryIndex ] ) ) {
|
|
3578
3598
|
|
|
3579
3599
|
table.writeWorldAABB( entryIndex, this._tlasBounds, i * 6 );
|
|
3580
3600
|
|
|
@@ -3615,6 +3635,8 @@ export class SceneProcessor {
|
|
|
3615
3635
|
|
|
3616
3636
|
}
|
|
3617
3637
|
|
|
3638
|
+
this._tlasLeafBoxesOf = table;
|
|
3639
|
+
|
|
3618
3640
|
}
|
|
3619
3641
|
|
|
3620
3642
|
/**
|
|
@@ -56,7 +56,39 @@ fn rayzee_source( x: u32, y: u32 ) -> vec4<f32> {
|
|
|
56
56
|
}`,
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
// RGBA bytes for a picture, or four float planes (r, g, b, a) for a network that wants the curve
|
|
60
|
+
// unquantized. The planes take a 2.2 power, not the sRGB curve: what the AI upscaler has always fed
|
|
61
|
+
// its network, so its output does not move.
|
|
62
|
+
const OUTPUTS = {
|
|
63
|
+
rgba8: /* wgsl */ `
|
|
64
|
+
@group(0) @binding(1) var<storage, read_write> dst: array<u32>;
|
|
65
|
+
|
|
66
|
+
fn rayzee_store( i: u32, linear: vec4<f32>, mapped: vec3<f32> ) {
|
|
67
|
+
let b8 = rayzee_to_u8( rayzee_encode( mapped, params.mode ) );
|
|
68
|
+
var a8 = 255u;
|
|
69
|
+
if ( params.alpha == 1u ) { a8 = rayzee_to_u8( vec3<f32>( linear.a ) ).x; }
|
|
70
|
+
dst[ i ] = b8.x | ( b8.y << 8u ) | ( b8.z << 16u ) | ( a8 << 24u );
|
|
71
|
+
}`,
|
|
72
|
+
planar: /* wgsl */ `
|
|
73
|
+
@group(0) @binding(1) var<storage, read_write> dst: array<f32>;
|
|
74
|
+
|
|
75
|
+
fn rayzee_store( i: u32, linear: vec4<f32>, mapped: vec3<f32> ) {
|
|
76
|
+
var c = clamp( mapped, vec3<f32>( 0.0 ), vec3<f32>( 1.0 ) );
|
|
77
|
+
if ( ! rayzee_output_encoded( params.mode ) ) {
|
|
78
|
+
let m = max( mapped, vec3<f32>( 0.0 ) );
|
|
79
|
+
c = select( pow( m, vec3<f32>( 1.0 / 2.2 ) ), vec3<f32>( 0.0 ), m <= vec3<f32>( 0.0 ) );
|
|
80
|
+
}
|
|
81
|
+
let n = params.width * params.height;
|
|
82
|
+
dst[ i ] = c.r;
|
|
83
|
+
dst[ n + i ] = c.g;
|
|
84
|
+
dst[ 2u * n + i ] = c.b;
|
|
85
|
+
dst[ 3u * n + i ] = linear.a;
|
|
86
|
+
}`,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const BYTES_PER_PIXEL = { rgba8: 4, planar: 16 };
|
|
90
|
+
|
|
91
|
+
const toneMapPassWGSL = ( transformWGSL, input, output ) => /* wgsl */ `
|
|
60
92
|
struct Params {
|
|
61
93
|
width: u32,
|
|
62
94
|
height: u32,
|
|
@@ -67,11 +99,11 @@ struct Params {
|
|
|
67
99
|
alpha: u32,
|
|
68
100
|
};
|
|
69
101
|
|
|
70
|
-
@group(0) @binding(1) var<storage, read_write> dst: array<u32>;
|
|
71
102
|
@group(0) @binding(2) var<uniform> params: Params;
|
|
72
103
|
${SOURCES[ input ]}
|
|
73
104
|
|
|
74
105
|
${transformWGSL}
|
|
106
|
+
${OUTPUTS[ output ]}
|
|
75
107
|
|
|
76
108
|
@compute @workgroup_size(8, 8)
|
|
77
109
|
fn main( @builtin(global_invocation_id) gid: vec3<u32> ) {
|
|
@@ -81,11 +113,7 @@ fn main( @builtin(global_invocation_id) gid: vec3<u32> ) {
|
|
|
81
113
|
if ( params.flipY == 1u ) { srcY = params.height - 1u - gid.y; }
|
|
82
114
|
|
|
83
115
|
let linear = rayzee_source( gid.x, srcY );
|
|
84
|
-
|
|
85
|
-
let b8 = rayzee_to_u8( rayzee_encode( mapped, params.mode ) );
|
|
86
|
-
var a8 = 255u;
|
|
87
|
-
if ( params.alpha == 1u ) { a8 = rayzee_to_u8( vec3<f32>( linear.a ) ).x; }
|
|
88
|
-
dst[ gid.y * params.width + gid.x ] = b8.x | ( b8.y << 8u ) | ( b8.z << 16u ) | ( a8 << 24u );
|
|
116
|
+
rayzee_store( gid.y * params.width + gid.x, linear, rayzee_tone_map( linear.rgb, params.mode, params.exposure, params.saturation ) );
|
|
89
117
|
}
|
|
90
118
|
`;
|
|
91
119
|
|
|
@@ -94,17 +122,18 @@ const PARAMS_BYTES = 32;
|
|
|
94
122
|
|
|
95
123
|
/**
|
|
96
124
|
* Tone-maps a tightly packed rgba16float buffer (two `u32` per pixel), or with `input: 'texture'` a
|
|
97
|
-
* float texture read by pixel, into RGBA bytes.
|
|
125
|
+
* float texture read by pixel, into RGBA bytes — or with `output: 'planar'` into float planes.
|
|
98
126
|
*
|
|
99
127
|
* Bound to one device and one image size; `ensureSize()` reallocates when the size changes.
|
|
100
128
|
*/
|
|
101
129
|
export class PackedToneMapper {
|
|
102
130
|
|
|
103
|
-
constructor( device, label = 'rayzee:tonemap', { input = 'packed' } = {} ) {
|
|
131
|
+
constructor( device, label = 'rayzee:tonemap', { input = 'packed', output = 'rgba8' } = {} ) {
|
|
104
132
|
|
|
105
133
|
this.device = device;
|
|
106
134
|
this.label = label;
|
|
107
135
|
this.input = input;
|
|
136
|
+
this.output = output;
|
|
108
137
|
this.width = 0;
|
|
109
138
|
this.height = 0;
|
|
110
139
|
this._pipeline = null;
|
|
@@ -139,7 +168,7 @@ export class PackedToneMapper {
|
|
|
139
168
|
label: this.label,
|
|
140
169
|
layout: 'auto',
|
|
141
170
|
compute: {
|
|
142
|
-
module: this.device.createShaderModule( { label: this.label, code: toneMapPassWGSL( wgsl, this.input ) } ),
|
|
171
|
+
module: this.device.createShaderModule( { label: this.label, code: toneMapPassWGSL( wgsl, this.input, this.output ) } ),
|
|
143
172
|
entryPoint: 'main',
|
|
144
173
|
},
|
|
145
174
|
} );
|
|
@@ -202,7 +231,7 @@ export class PackedToneMapper {
|
|
|
202
231
|
|
|
203
232
|
this._releaseBuffers();
|
|
204
233
|
|
|
205
|
-
const bytes = width * height *
|
|
234
|
+
const bytes = width * height * BYTES_PER_PIXEL[ this.output ];
|
|
206
235
|
this._storage = this.device.createBuffer( {
|
|
207
236
|
label: `${this.label}-out`,
|
|
208
237
|
size: bytes,
|
|
@@ -235,7 +264,26 @@ export class PackedToneMapper {
|
|
|
235
264
|
* @param {boolean} [tone.preserveAlpha=false] - the source's alpha, rounded as the CPU rounds it; else 255
|
|
236
265
|
* @returns {Promise<Uint8ClampedArray>} RGBA bytes, `width * height * 4`
|
|
237
266
|
*/
|
|
238
|
-
async toRGBA8( src,
|
|
267
|
+
async toRGBA8( src, tone ) {
|
|
268
|
+
|
|
269
|
+
return new Uint8ClampedArray( await this._run( src, tone ) );
|
|
270
|
+
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* The same tone map into four float planes — r, g, b, then the source's alpha — each `width * height`.
|
|
275
|
+
* Needs `output: 'planar'`.
|
|
276
|
+
* @param {GPUBuffer|GPUTexture} src - as for toRGBA8
|
|
277
|
+
* @param {object} tone - as for toRGBA8; `preserveAlpha` does not apply
|
|
278
|
+
* @returns {Promise<Float32Array>}
|
|
279
|
+
*/
|
|
280
|
+
async toPlanar( src, tone ) {
|
|
281
|
+
|
|
282
|
+
return new Float32Array( await this._run( src, tone ) );
|
|
283
|
+
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async _run( src, { exposure = 1, toneMapping = 0, saturation = 1, flipY = false, preserveAlpha = false } = {} ) {
|
|
239
287
|
|
|
240
288
|
if ( this.disposed ) throw new Error( 'PackedToneMapper: disposed' );
|
|
241
289
|
if ( ! this._storage ) throw new Error( 'PackedToneMapper: call ensureSize() first' );
|
|
@@ -246,6 +294,7 @@ export class PackedToneMapper {
|
|
|
246
294
|
this._ensureTables();
|
|
247
295
|
|
|
248
296
|
const { width, height } = this;
|
|
297
|
+
const bytes = width * height * BYTES_PER_PIXEL[ this.output ];
|
|
249
298
|
|
|
250
299
|
this._paramU32[ 0 ] = width;
|
|
251
300
|
this._paramU32[ 1 ] = height;
|
|
@@ -275,13 +324,13 @@ export class PackedToneMapper {
|
|
|
275
324
|
pass.setBindGroup( 0, group );
|
|
276
325
|
pass.dispatchWorkgroups( Math.ceil( width / 8 ), Math.ceil( height / 8 ) );
|
|
277
326
|
pass.end();
|
|
278
|
-
encoder.copyBufferToBuffer( this._storage, 0, this._map, 0,
|
|
327
|
+
encoder.copyBufferToBuffer( this._storage, 0, this._map, 0, bytes );
|
|
279
328
|
this.device.queue.submit( [ encoder.finish() ] );
|
|
280
329
|
|
|
281
330
|
await this._map.mapAsync( GPUMapMode.READ );
|
|
282
|
-
const
|
|
331
|
+
const out = this._map.getMappedRange().slice( 0 );
|
|
283
332
|
this._map.unmap();
|
|
284
|
-
return
|
|
333
|
+
return out;
|
|
285
334
|
|
|
286
335
|
}
|
|
287
336
|
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Protocol:
|
|
5
5
|
* 'init' → receives the shared triangle and node buffers (once per scene)
|
|
6
|
-
* 'refit' → recomputes every node's AABB from the triangles already written there
|
|
6
|
+
* 'refit' → recomputes every node's AABB from the triangles already written there, or with
|
|
7
|
+
* `blasRanges` only those BLASes and the TLAS
|
|
7
8
|
*
|
|
8
9
|
* Positions never reach here: the main thread scatters each mesh into the shared triangle
|
|
9
10
|
* records as it reads it, so no buffer the size of the scene exists on either side.
|
|
@@ -45,7 +46,9 @@ self.onmessage = function ( e ) {
|
|
|
45
46
|
|
|
46
47
|
const startTime = performance.now();
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
const { blasRanges, tlasNodeCount } = e.data;
|
|
50
|
+
if ( blasRanges ) refitter.refitPartial( bvhData, triData, blasRanges, tlasNodeCount );
|
|
51
|
+
else refitter.refit( bvhData, triData, nodeCount );
|
|
49
52
|
|
|
50
53
|
self.postMessage( {
|
|
51
54
|
type: 'refitComplete',
|
|
@@ -1102,17 +1102,34 @@ export class PathTracerStage extends RenderStage {
|
|
|
1102
1102
|
|
|
1103
1103
|
}
|
|
1104
1104
|
|
|
1105
|
-
/**
|
|
1106
|
-
|
|
1105
|
+
/**
|
|
1106
|
+
* Update BVH node data in the existing GPU buffer.
|
|
1107
|
+
* @param {Object} [bvhData]
|
|
1108
|
+
* @param {Array<{offset:number,count:number}>} [ranges] - only these, in floats; every node when omitted
|
|
1109
|
+
*/
|
|
1110
|
+
updateBVHData( bvhData, ranges ) {
|
|
1107
1111
|
|
|
1108
1112
|
if ( this._bvhRecords ) {
|
|
1109
1113
|
|
|
1110
|
-
|
|
1114
|
+
if ( bvhData?.chunks?.length > 1 ) this._bvhRecords = bvhData;
|
|
1115
|
+
if ( ranges ) this.updateBufferRanges( [], ranges );
|
|
1116
|
+
else uploadStorageChunks( this.renderer, this.bvhStorageAttr, this._bvhRecords.chunks );
|
|
1117
|
+
return;
|
|
1118
|
+
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
const flat = bvhData?.chunks ? bvhData.chunks[ 0 ] : bvhData;
|
|
1122
|
+
if ( ! ranges ) {
|
|
1123
|
+
|
|
1124
|
+
this._updateStorageBuffer( this.bvhStorageAttr, flat );
|
|
1111
1125
|
return;
|
|
1112
1126
|
|
|
1113
1127
|
}
|
|
1114
1128
|
|
|
1115
|
-
|
|
1129
|
+
// The attribute keeps its own array once the refit has moved the nodes into shared memory.
|
|
1130
|
+
const own = this.bvhStorageAttr?.array;
|
|
1131
|
+
if ( flat && own && flat !== own ) for ( const r of ranges ) own.set( flat.subarray( r.offset, r.offset + r.count ), r.offset );
|
|
1132
|
+
this.updateBufferRanges( [], ranges );
|
|
1116
1133
|
|
|
1117
1134
|
}
|
|
1118
1135
|
|
|
@@ -10,6 +10,23 @@
|
|
|
10
10
|
import { AnimationMixer, EventDispatcher, Timer, Vector3, LoopRepeat, LoopOnce, PropertyBinding } from 'three';
|
|
11
11
|
import { EngineEvents } from '../EngineEvents.js';
|
|
12
12
|
|
|
13
|
+
/** The nodes of `nodes` with no ancestor in it, or null when updating each would cost more than one pass over `root`. */
|
|
14
|
+
function subtreeRoots( nodes, root ) {
|
|
15
|
+
|
|
16
|
+
const roots = [ ...nodes ].filter( node => {
|
|
17
|
+
|
|
18
|
+
for ( let o = node.parent; o; o = o.parent ) if ( nodes.has( o ) ) return false;
|
|
19
|
+
return true;
|
|
20
|
+
|
|
21
|
+
} );
|
|
22
|
+
|
|
23
|
+
let total = 0, covered = 0;
|
|
24
|
+
root.traverse?.( () => total ++ );
|
|
25
|
+
for ( const node of roots ) node.traverse?.( () => covered ++ );
|
|
26
|
+
return covered + roots.length / 2 < total ? roots : null;
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
|
|
13
30
|
export class AnimationManager extends EventDispatcher {
|
|
14
31
|
|
|
15
32
|
constructor() {
|
|
@@ -162,11 +179,19 @@ export class AnimationManager extends EventDispatcher {
|
|
|
162
179
|
|
|
163
180
|
const animated = new Set();
|
|
164
181
|
let visibility = false;
|
|
182
|
+
let unresolved = false;
|
|
165
183
|
for ( const track of clip.tracks ) {
|
|
166
184
|
|
|
167
185
|
const { nodeName, propertyName } = PropertyBinding.parseTrackName( track.name );
|
|
168
|
-
|
|
169
|
-
|
|
186
|
+
// As PropertyBinding.findNode: no node name binds the root itself.
|
|
187
|
+
const node = nodeName === '' || nodeName === '.' ? root : byName.get( nodeName );
|
|
188
|
+
if ( ! node ) {
|
|
189
|
+
|
|
190
|
+
unresolved = true;
|
|
191
|
+
continue;
|
|
192
|
+
|
|
193
|
+
}
|
|
194
|
+
|
|
170
195
|
animated.add( node );
|
|
171
196
|
if ( propertyName === 'visible' ) visibility = true;
|
|
172
197
|
|
|
@@ -191,7 +216,7 @@ export class AnimationManager extends EventDispatcher {
|
|
|
191
216
|
const cameras = new Set();
|
|
192
217
|
for ( const node of animated ) node.traverse( o => o.isCamera && cameras.add( o ) );
|
|
193
218
|
|
|
194
|
-
return { meshes: Int32Array.from( meshes ), visibility, cameras: [ ...cameras ] };
|
|
219
|
+
return { meshes: Int32Array.from( meshes ), visibility, cameras: [ ...cameras ], nodes: unresolved ? null : animated, roots: unresolved ? null : subtreeRoots( animated, root ) };
|
|
195
220
|
|
|
196
221
|
} );
|
|
197
222
|
|
|
@@ -218,15 +243,18 @@ export class AnimationManager extends EventDispatcher {
|
|
|
218
243
|
|
|
219
244
|
const meshes = new Set(), cameras = new Set();
|
|
220
245
|
let visibility = false;
|
|
246
|
+
let nodes = new Set();
|
|
221
247
|
for ( const plan of this._plans ) {
|
|
222
248
|
|
|
223
249
|
plan.meshes.forEach( m => meshes.add( m ) );
|
|
224
250
|
plan.cameras.forEach( c => cameras.add( c ) );
|
|
225
251
|
visibility ||= plan.visibility;
|
|
252
|
+
if ( nodes && plan.nodes ) plan.nodes.forEach( n => nodes.add( n ) );
|
|
253
|
+
else nodes = null;
|
|
226
254
|
|
|
227
255
|
}
|
|
228
256
|
|
|
229
|
-
this._allPlan = { meshes: Int32Array.from( meshes ), visibility, cameras: [ ...cameras ] };
|
|
257
|
+
this._allPlan = { meshes: Int32Array.from( meshes ), visibility, cameras: [ ...cameras ], nodes, roots: nodes ? subtreeRoots( nodes, this._mixerRoot ) : null };
|
|
230
258
|
|
|
231
259
|
}
|
|
232
260
|
|
|
@@ -491,7 +519,10 @@ export class AnimationManager extends EventDispatcher {
|
|
|
491
519
|
|
|
492
520
|
// Bones live outside mesh subtrees so per-mesh updateMatrixWorld() misses them. Doing it
|
|
493
521
|
// once here, on mixerRoot rather than the scene, keeps unrelated static objects out of it.
|
|
494
|
-
|
|
522
|
+
// A rigid clip moves only what its tracks name, so only those subtrees are updated.
|
|
523
|
+
const roots = this._rigid ? this._planFor( this._activeClip )?.roots : null;
|
|
524
|
+
if ( roots ) for ( const node of roots ) node.updateMatrixWorld( true );
|
|
525
|
+
else this._mixerRoot.updateMatrixWorld( true );
|
|
495
526
|
|
|
496
527
|
if ( this._rigid ) {
|
|
497
528
|
|