rayzee 8.6.0 → 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/README.md +1 -53
- package/dist/{DLSSNeural-BhBCBgKf.js → NeuralRetouch-CYOJ21Wn.js} +31 -31
- package/dist/{DLSSSuperRes-BCA7F_k5.js → NeuralSuperRes-Dc6jRYke.js} +23 -23
- package/dist/{ToneMapGPU-BzcLheoR.js → ToneMapGPU-BVE852iN.js} +2 -2
- package/dist/rayzee.es.js +3552 -2990
- package/dist/rayzee.umd.js +17 -17
- package/package.json +1 -1
- package/src/AssetConfig.js +6 -5
- package/src/Passes/OIDNDenoiser.js +13 -5
- 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/index.js +3 -3
- package/src/managers/AnimationManager.js +202 -8
- package/src/managers/DenoisingManager.js +60 -44
- package/src/{dlss/DLSSNeural.js → neural/NeuralRetouch.js} +42 -41
- package/src/{dlss/DLSSSuperRes.js → neural/NeuralSuperRes.js} +27 -26
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pbrt motion as per-object keyframes, for PBRTSceneBuilder to turn into one AnimationClip:
|
|
3
|
+
* - a frame sequence (`frame25.pbrt`, `frame35.pbrt`, …), each frame aligned with the previous
|
|
4
|
+
* so a moved object keeps one mesh, and one that comes or goes is shown only where it exists;
|
|
5
|
+
* - `ActiveTransform` / `TransformTimes` inside one file, as two keys.
|
|
6
|
+
*
|
|
7
|
+
* `motion = { times, matrices, visible }`: seconds, 16 floats per key, 0/1 per key or null.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** pbrt has no frame rate; 30 makes a 30 fps video export land on the scene's own frame numbers. */
|
|
11
|
+
export const SEQUENCE_FPS = 30;
|
|
12
|
+
|
|
13
|
+
// Frames past this are left out; each one is a full parse.
|
|
14
|
+
const MAX_SEQUENCE_FRAMES = 1000;
|
|
15
|
+
|
|
16
|
+
// Past this edit distance alignment matches by order instead; its memory grows with the square.
|
|
17
|
+
const MAX_ALIGN_EDITS = 2048;
|
|
18
|
+
|
|
19
|
+
// Placements animated past this stay where the first frame put them.
|
|
20
|
+
const MAX_ANIMATED_PLACEMENTS = 100_000;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The sequence the best-ranked scene belongs to — files in one directory whose names differ
|
|
24
|
+
* only by a number — or null.
|
|
25
|
+
* @param {string[]} candidates - scene files, best first (see listEntryPathsFrom)
|
|
26
|
+
* @returns {{ path: string, number: number }[] | null} frames in number order
|
|
27
|
+
*/
|
|
28
|
+
export function findFrameSequence( candidates ) {
|
|
29
|
+
|
|
30
|
+
const lead = candidates[ 0 ];
|
|
31
|
+
const parse = path => {
|
|
32
|
+
|
|
33
|
+
const slash = path.lastIndexOf( '/' );
|
|
34
|
+
const match = /^(.*?)(\d+)\.pbrt$/i.exec( path.slice( slash + 1 ) );
|
|
35
|
+
return match ? { group: path.slice( 0, slash + 1 ) + '\0' + match[ 1 ].toLowerCase(), number: parseInt( match[ 2 ], 10 ) } : null;
|
|
36
|
+
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const head = lead ? parse( lead ) : null;
|
|
40
|
+
if ( ! head ) return null;
|
|
41
|
+
|
|
42
|
+
const byNumber = new Map();
|
|
43
|
+
for ( const path of candidates ) {
|
|
44
|
+
|
|
45
|
+
const p = parse( path );
|
|
46
|
+
if ( p && p.group === head.group && ! byNumber.has( p.number ) ) byNumber.set( p.number, path );
|
|
47
|
+
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if ( byNumber.size < 2 ) return null;
|
|
51
|
+
|
|
52
|
+
return [ ...byNumber ]
|
|
53
|
+
.sort( ( a, b ) => a[ 0 ] - b[ 0 ] )
|
|
54
|
+
.slice( 0, MAX_SEQUENCE_FRAMES )
|
|
55
|
+
.map( ( [ number, path ] ) => ( { path, number } ) );
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── keys ────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
/** A 32-bit hash of a numeric array's bit patterns, with its length. */
|
|
62
|
+
function hashNumbers( values ) {
|
|
63
|
+
|
|
64
|
+
let bits = values instanceof Float32Array || values instanceof Int32Array || values instanceof Uint32Array
|
|
65
|
+
? new Uint32Array( values.buffer, values.byteOffset, values.length )
|
|
66
|
+
: null;
|
|
67
|
+
if ( ! bits ) bits = new Uint32Array( Float32Array.from( values ).buffer );
|
|
68
|
+
|
|
69
|
+
let h = 0x811c9dc5;
|
|
70
|
+
for ( let i = 0; i < bits.length; i ++ ) {
|
|
71
|
+
|
|
72
|
+
h = Math.imul( h ^ bits[ i ], 0x01000193 );
|
|
73
|
+
h ^= h >>> 15;
|
|
74
|
+
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return ( h >>> 0 ).toString( 36 ) + ':' + values.length;
|
|
78
|
+
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Equal exactly when the parameters are; long lists are hashed. */
|
|
82
|
+
function paramsKey( params ) {
|
|
83
|
+
|
|
84
|
+
let key = '';
|
|
85
|
+
for ( const name of Object.keys( params ).sort() ) {
|
|
86
|
+
|
|
87
|
+
const { type, value } = params[ name ];
|
|
88
|
+
const numeric = ArrayBuffer.isView( value ) || ( value.length > 16 && typeof value[ 0 ] === 'number' );
|
|
89
|
+
key += `${type} ${name}=${numeric ? '#' + hashNumbers( value ) : value.join( ',' )};`;
|
|
90
|
+
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return key;
|
|
94
|
+
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** A shape's identity across frames: everything but its transform. */
|
|
98
|
+
class ShapeKeys {
|
|
99
|
+
|
|
100
|
+
constructor( ir ) {
|
|
101
|
+
|
|
102
|
+
this.materialNames = new Map();
|
|
103
|
+
for ( const [ name, def ] of ir.namedMaterials ) this.materialNames.set( def, name );
|
|
104
|
+
this.cache = new Map();
|
|
105
|
+
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
of( shape ) {
|
|
109
|
+
|
|
110
|
+
return `${shape.type}|${shape.reverseOrientation ? 1 : 0}|${paramsKey( shape.params )}|` +
|
|
111
|
+
`${this._material( shape.material )}|${this._light( shape.areaLight )}`;
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
_material( material ) {
|
|
116
|
+
|
|
117
|
+
if ( ! material ) return '-';
|
|
118
|
+
let key = this.cache.get( material );
|
|
119
|
+
if ( key === undefined ) {
|
|
120
|
+
|
|
121
|
+
const name = this.materialNames.get( material ) ?? material._missingRef;
|
|
122
|
+
key = name !== undefined ? 'N:' + name : 'I:' + material.type + ':' + paramsKey( material.params );
|
|
123
|
+
this.cache.set( material, key );
|
|
124
|
+
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return key;
|
|
128
|
+
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
_light( light ) {
|
|
132
|
+
|
|
133
|
+
if ( ! light ) return '-';
|
|
134
|
+
let key = this.cache.get( light );
|
|
135
|
+
if ( key === undefined ) this.cache.set( light, key = 'L:' + light.type + ':' + paramsKey( light.params ) );
|
|
136
|
+
return key;
|
|
137
|
+
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── alignment ───────────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Longest common subsequence. Exporters emit a scene in the same order every frame, so this
|
|
146
|
+
* follows repeated parts through insertions, where pairing n-th copies shifts them onto neighbours.
|
|
147
|
+
* @param {Int32Array} a
|
|
148
|
+
* @param {Int32Array} b
|
|
149
|
+
* @returns {Int32Array} index into `a` for each entry of `b`, or -1
|
|
150
|
+
*/
|
|
151
|
+
export function alignSequences( a, b ) {
|
|
152
|
+
|
|
153
|
+
const matchOfB = new Int32Array( b.length ).fill( - 1 );
|
|
154
|
+
|
|
155
|
+
let lo = 0;
|
|
156
|
+
while ( lo < a.length && lo < b.length && a[ lo ] === b[ lo ] ) {
|
|
157
|
+
|
|
158
|
+
matchOfB[ lo ] = lo;
|
|
159
|
+
lo ++;
|
|
160
|
+
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let aHi = a.length, bHi = b.length;
|
|
164
|
+
while ( aHi > lo && bHi > lo && a[ aHi - 1 ] === b[ bHi - 1 ] ) {
|
|
165
|
+
|
|
166
|
+
matchOfB[ -- bHi ] = -- aHi;
|
|
167
|
+
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if ( lo < aHi && lo < bHi && ! myers( a, lo, aHi, b, lo, bHi, matchOfB ) ) {
|
|
171
|
+
|
|
172
|
+
matchByOrder( a, lo, aHi, b, lo, bHi, matchOfB );
|
|
173
|
+
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return matchOfB;
|
|
177
|
+
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Myers' O(ND) diff on a[aLo..aHi) × b[bLo..bHi). False when it needs more than MAX_ALIGN_EDITS. */
|
|
181
|
+
function myers( a, aLo, aHi, b, bLo, bHi, matchOfB ) {
|
|
182
|
+
|
|
183
|
+
const N = aHi - aLo, M = bHi - bLo;
|
|
184
|
+
const max = Math.min( N + M, MAX_ALIGN_EDITS );
|
|
185
|
+
const off = max + 1;
|
|
186
|
+
const V = new Int32Array( 2 * max + 3 );
|
|
187
|
+
const trace = [];
|
|
188
|
+
|
|
189
|
+
for ( let d = 0; d <= max; d ++ ) {
|
|
190
|
+
|
|
191
|
+
for ( let k = - d; k <= d; k += 2 ) {
|
|
192
|
+
|
|
193
|
+
let x = k === - d || ( k !== d && V[ off + k - 1 ] < V[ off + k + 1 ] ) ? V[ off + k + 1 ] : V[ off + k - 1 ] + 1;
|
|
194
|
+
let y = x - k;
|
|
195
|
+
while ( x < N && y < M && a[ aLo + x ] === b[ bLo + y ] ) {
|
|
196
|
+
|
|
197
|
+
x ++;
|
|
198
|
+
y ++;
|
|
199
|
+
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
V[ off + k ] = x;
|
|
203
|
+
|
|
204
|
+
if ( x >= N && y >= M ) {
|
|
205
|
+
|
|
206
|
+
trace.push( V.slice( off - d, off + d + 1 ) );
|
|
207
|
+
backtrack( trace, N, M, aLo, bLo, matchOfB );
|
|
208
|
+
return true;
|
|
209
|
+
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
trace.push( V.slice( off - d, off + d + 1 ) );
|
|
215
|
+
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return false;
|
|
219
|
+
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function backtrack( trace, N, M, aLo, bLo, matchOfB ) {
|
|
223
|
+
|
|
224
|
+
let x = N, y = M;
|
|
225
|
+
for ( let d = trace.length - 1; d > 0; d -- ) {
|
|
226
|
+
|
|
227
|
+
const prev = trace[ d - 1 ];
|
|
228
|
+
const at = kk => prev[ kk + d - 1 ];
|
|
229
|
+
const k = x - y;
|
|
230
|
+
const prevK = k === - d || ( k !== d && at( k - 1 ) < at( k + 1 ) ) ? k + 1 : k - 1;
|
|
231
|
+
const prevX = at( prevK ), prevY = prevX - prevK;
|
|
232
|
+
|
|
233
|
+
while ( x > prevX && y > prevY ) matchOfB[ bLo + ( -- y ) ] = aLo + ( -- x );
|
|
234
|
+
|
|
235
|
+
x = prevX;
|
|
236
|
+
y = prevY;
|
|
237
|
+
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
while ( x > 0 && y > 0 ) matchOfB[ bLo + ( -- y ) ] = aLo + ( -- x );
|
|
241
|
+
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Pair the n-th occurrence of each key in `b` with the n-th in `a`. */
|
|
245
|
+
function matchByOrder( a, aLo, aHi, b, bLo, bHi, matchOfB ) {
|
|
246
|
+
|
|
247
|
+
const queues = new Map();
|
|
248
|
+
for ( let i = aLo; i < aHi; i ++ ) {
|
|
249
|
+
|
|
250
|
+
const q = queues.get( a[ i ] );
|
|
251
|
+
if ( q ) q.push( i );
|
|
252
|
+
else queues.set( a[ i ], [ i ] );
|
|
253
|
+
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const heads = new Map();
|
|
257
|
+
for ( let j = bLo; j < bHi; j ++ ) {
|
|
258
|
+
|
|
259
|
+
const q = queues.get( b[ j ] );
|
|
260
|
+
if ( ! q ) continue;
|
|
261
|
+
const h = heads.get( b[ j ] ) ?? 0;
|
|
262
|
+
if ( h < q.length ) {
|
|
263
|
+
|
|
264
|
+
matchOfB[ j ] = q[ h ];
|
|
265
|
+
heads.set( b[ j ], h + 1 );
|
|
266
|
+
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ── motion ──────────────────────────────────────────────────────────
|
|
274
|
+
|
|
275
|
+
function sameMatrix( a, ao, b, bo ) {
|
|
276
|
+
|
|
277
|
+
for ( let i = 0; i < 16; i ++ ) if ( a[ ao + i ] !== b[ bo + i ] ) return false;
|
|
278
|
+
return true;
|
|
279
|
+
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** One key per frame; absent frames hold the nearest present pose. */
|
|
283
|
+
function motionFromFrames( times, present, matrixAt ) {
|
|
284
|
+
|
|
285
|
+
const F = times.length;
|
|
286
|
+
const matrices = new Float32Array( 16 * F );
|
|
287
|
+
let visible = null;
|
|
288
|
+
let last = - 1;
|
|
289
|
+
|
|
290
|
+
for ( let f = 0; f < F; f ++ ) {
|
|
291
|
+
|
|
292
|
+
if ( present( f ) ) {
|
|
293
|
+
|
|
294
|
+
matrices.set( matrixAt( f ), f * 16 );
|
|
295
|
+
if ( last < 0 ) for ( let g = 0; g < f; g ++ ) matrices.copyWithin( g * 16, f * 16, f * 16 + 16 );
|
|
296
|
+
last = f;
|
|
297
|
+
|
|
298
|
+
} else {
|
|
299
|
+
|
|
300
|
+
visible ??= new Uint8Array( F ).fill( 1 );
|
|
301
|
+
visible[ f ] = 0;
|
|
302
|
+
if ( last >= 0 ) matrices.copyWithin( f * 16, last * 16, last * 16 + 16 );
|
|
303
|
+
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return { times, matrices, visible };
|
|
309
|
+
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function motionIsStatic( motion ) {
|
|
313
|
+
|
|
314
|
+
if ( motion.visible ) return false;
|
|
315
|
+
const m = motion.matrices;
|
|
316
|
+
for ( let o = 16; o < m.length; o += 16 ) if ( ! sameMatrix( m, 0, m, o ) ) return false;
|
|
317
|
+
return true;
|
|
318
|
+
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Folds a sequence's frames, added in order, into the first frame's IR. */
|
|
322
|
+
export class FrameSequenceMerger {
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* @param {number[]} times - seconds, one per frame
|
|
326
|
+
* @param {string} name - clip name
|
|
327
|
+
*/
|
|
328
|
+
constructor( times, name ) {
|
|
329
|
+
|
|
330
|
+
this.times = Float32Array.from( times );
|
|
331
|
+
this.name = name;
|
|
332
|
+
this.base = null;
|
|
333
|
+
this.frame = - 1;
|
|
334
|
+
this.keyIds = new Map();
|
|
335
|
+
this.tracks = [];
|
|
336
|
+
this.prev = null; // { keys: Int32Array, tracks: [] }
|
|
337
|
+
// template -> { counts: per frame, diffs: placement -> frame -> matrix unlike frame 0's }
|
|
338
|
+
this.placements = new Map();
|
|
339
|
+
// template name + content -> name merged under
|
|
340
|
+
this.templates = new Map();
|
|
341
|
+
this.cameras = [];
|
|
342
|
+
this.warnings = new Set();
|
|
343
|
+
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** @param {object} ir - a parsed frame; the first one becomes the merged scene */
|
|
347
|
+
addFrame( ir ) {
|
|
348
|
+
|
|
349
|
+
const f = ++ this.frame;
|
|
350
|
+
if ( f === 0 ) this.base = ir;
|
|
351
|
+
else this._adoptDefinitions( ir );
|
|
352
|
+
|
|
353
|
+
for ( const w of ir.warnings ) this.warnings.add( w );
|
|
354
|
+
|
|
355
|
+
const keys = new ShapeKeys( ir );
|
|
356
|
+
const ids = new Int32Array( ir.shapes.length );
|
|
357
|
+
for ( let i = 0; i < ids.length; i ++ ) {
|
|
358
|
+
|
|
359
|
+
const key = keys.of( ir.shapes[ i ] );
|
|
360
|
+
let id = this.keyIds.get( key );
|
|
361
|
+
if ( id === undefined ) this.keyIds.set( key, id = this.keyIds.size );
|
|
362
|
+
ids[ i ] = id;
|
|
363
|
+
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const match = this.prev ? alignSequences( this.prev.keys, ids ) : null;
|
|
367
|
+
const tracks = new Array( ids.length );
|
|
368
|
+
for ( let j = 0; j < ids.length; j ++ ) {
|
|
369
|
+
|
|
370
|
+
const shape = ir.shapes[ j ];
|
|
371
|
+
let track = match && match[ j ] >= 0 ? this.prev.tracks[ match[ j ] ] : null;
|
|
372
|
+
if ( track ) {
|
|
373
|
+
|
|
374
|
+
track.last = f;
|
|
375
|
+
if ( track.ctms ) track.ctms.push( shape.ctm );
|
|
376
|
+
else if ( shape.ctm !== track.shape.ctm && ! sameMatrix( shape.ctm, 0, track.shape.ctm, 0 ) ) {
|
|
377
|
+
|
|
378
|
+
track.ctms = new Array( f - track.first ).fill( track.shape.ctm );
|
|
379
|
+
track.ctms.push( shape.ctm );
|
|
380
|
+
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
} else {
|
|
384
|
+
|
|
385
|
+
track = { shape, first: f, last: f, ctms: null };
|
|
386
|
+
if ( f > 0 ) this._adoptMaterial( shape, keys.materialNames );
|
|
387
|
+
this.tracks.push( track );
|
|
388
|
+
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
tracks[ j ] = track;
|
|
392
|
+
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
this.prev = { keys: ids, tracks };
|
|
396
|
+
this._addPlacements( ir, f, this._resolveTemplates( ir, f, keys ) );
|
|
397
|
+
this.cameras.push( ir.camera ? { cameraToWorld: ir.camera.cameraToWorld, fov: ir.camera.params?.fov?.value?.[ 0 ] } : null );
|
|
398
|
+
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** Named materials and textures a later frame defines that the first did not. */
|
|
402
|
+
_adoptDefinitions( ir ) {
|
|
403
|
+
|
|
404
|
+
for ( const [ name, def ] of ir.namedMaterials ) if ( ! this.base.namedMaterials.has( name ) ) this.base.namedMaterials.set( name, def );
|
|
405
|
+
for ( const [ name, def ] of ir.namedTextures ) if ( ! this.base.namedTextures.has( name ) ) this.base.namedTextures.set( name, def );
|
|
406
|
+
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* The name each template merges under; one a frame redefines becomes a variant `name @frame`.
|
|
411
|
+
* @returns {Map<string, string>}
|
|
412
|
+
*/
|
|
413
|
+
_resolveTemplates( ir, f, keys ) {
|
|
414
|
+
|
|
415
|
+
const resolved = new Map();
|
|
416
|
+
for ( const [ name, shapes ] of ir.objects ) {
|
|
417
|
+
|
|
418
|
+
let signature = '';
|
|
419
|
+
for ( const shape of shapes ) signature += keys.of( shape ) + '@' + Array.prototype.join.call( shape.relativeCTM || shape.ctm, ',' ) + ';';
|
|
420
|
+
|
|
421
|
+
const key = name + '\0' + signature;
|
|
422
|
+
let as = this.templates.get( key );
|
|
423
|
+
if ( as === undefined ) {
|
|
424
|
+
|
|
425
|
+
as = f === 0 || ! this.base.objects.has( name ) ? name : `${name} @${f}`;
|
|
426
|
+
this.templates.set( key, as );
|
|
427
|
+
if ( f > 0 ) {
|
|
428
|
+
|
|
429
|
+
for ( const shape of shapes ) this._adoptMaterial( shape, keys.materialNames );
|
|
430
|
+
this.base.objects.set( as, shapes );
|
|
431
|
+
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
resolved.set( name, as );
|
|
437
|
+
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return resolved;
|
|
441
|
+
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** Share the first frame's material of the same name. */
|
|
445
|
+
_adoptMaterial( shape, materialNames ) {
|
|
446
|
+
|
|
447
|
+
const name = shape.material && materialNames.get( shape.material );
|
|
448
|
+
if ( name !== undefined ) shape.material = this.base.namedMaterials.get( name ) ?? shape.material;
|
|
449
|
+
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** Placements are paired by position in their template's list. */
|
|
453
|
+
_addPlacements( ir, f, templateNames ) {
|
|
454
|
+
|
|
455
|
+
for ( const [ placed, list ] of ir.instances ) {
|
|
456
|
+
|
|
457
|
+
const name = templateNames.get( placed ) ?? placed;
|
|
458
|
+
let rec = this.placements.get( name );
|
|
459
|
+
if ( ! rec ) this.placements.set( name, rec = { counts: new Int32Array( this.times.length ), diffs: new Map() } );
|
|
460
|
+
rec.counts[ f ] = list.count;
|
|
461
|
+
if ( f === 0 ) continue;
|
|
462
|
+
|
|
463
|
+
const base = this.base.instances.get( name );
|
|
464
|
+
const baseCount = base?.count ?? 0;
|
|
465
|
+
for ( let p = 0; p < list.count; p ++ ) {
|
|
466
|
+
|
|
467
|
+
const o = p * 16;
|
|
468
|
+
if ( p < baseCount && sameMatrix( list.matrices, o, base.matrices, o ) ) continue;
|
|
469
|
+
let perFrame = rec.diffs.get( p );
|
|
470
|
+
if ( ! perFrame ) rec.diffs.set( p, perFrame = new Map() );
|
|
471
|
+
perFrame.set( f, list.matrices.slice( o, o + 16 ) );
|
|
472
|
+
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** @returns {object} the first frame's IR with every frame's motion folded in */
|
|
480
|
+
finish() {
|
|
481
|
+
|
|
482
|
+
const ir = this.base;
|
|
483
|
+
const times = this.times;
|
|
484
|
+
const F = this.frame + 1;
|
|
485
|
+
|
|
486
|
+
// First-frame shapes keep their order; later arrivals follow in order of appearance.
|
|
487
|
+
ir.shapes = this.tracks.map( track => {
|
|
488
|
+
|
|
489
|
+
const { shape, first, last, ctms } = track;
|
|
490
|
+
if ( first > 0 || last < F - 1 || ctms ) {
|
|
491
|
+
|
|
492
|
+
shape.motion = motionFromFrames( times, g => g >= first && g <= last, g => ( ctms ? ctms[ g - first ] : shape.ctm ) );
|
|
493
|
+
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return shape;
|
|
497
|
+
|
|
498
|
+
} );
|
|
499
|
+
|
|
500
|
+
ir.warnings = [ ...this.warnings ];
|
|
501
|
+
ir.animatedInstances = this._finishPlacements( ir, times, F );
|
|
502
|
+
|
|
503
|
+
const cams = this.cameras;
|
|
504
|
+
if ( cams[ 0 ] && cams.every( Boolean ) ) {
|
|
505
|
+
|
|
506
|
+
const motion = motionFromFrames( times, () => true, g => cams[ g ].cameraToWorld );
|
|
507
|
+
const fovs = cams.map( c => c.fov ?? cams[ 0 ].fov );
|
|
508
|
+
const fovVaries = fovs.some( v => v !== fovs[ 0 ] );
|
|
509
|
+
if ( ! motionIsStatic( motion ) || fovVaries ) {
|
|
510
|
+
|
|
511
|
+
ir.camera.motion = { ...motion, fov: fovVaries ? Float32Array.from( fovs ) : null };
|
|
512
|
+
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
ir.animation = { name: this.name, duration: times[ F - 1 ], frames: F };
|
|
518
|
+
return ir;
|
|
519
|
+
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
_finishPlacements( ir, times, F ) {
|
|
523
|
+
|
|
524
|
+
const animated = [];
|
|
525
|
+
let skipped = 0;
|
|
526
|
+
|
|
527
|
+
for ( const [ name, { counts, diffs } ] of this.placements ) {
|
|
528
|
+
|
|
529
|
+
const list = ir.instances.get( name );
|
|
530
|
+
const baseCount = list?.count ?? 0;
|
|
531
|
+
const most = Math.max( ...counts.subarray( 0, F ) );
|
|
532
|
+
const keep = [];
|
|
533
|
+
|
|
534
|
+
for ( let p = 0; p < most; p ++ ) {
|
|
535
|
+
|
|
536
|
+
const frames = diffs.get( p );
|
|
537
|
+
let moves = !! frames;
|
|
538
|
+
for ( let g = 0; g < F && ! moves; g ++ ) moves = p >= counts[ g ];
|
|
539
|
+
|
|
540
|
+
if ( moves && animated.length < MAX_ANIMATED_PLACEMENTS ) {
|
|
541
|
+
|
|
542
|
+
const motion = motionFromFrames( times, g => p < counts[ g ],
|
|
543
|
+
g => frames?.get( g ) ?? list.matrices.subarray( p * 16, p * 16 + 16 ) );
|
|
544
|
+
animated.push( { name, motion } );
|
|
545
|
+
|
|
546
|
+
} else {
|
|
547
|
+
|
|
548
|
+
if ( moves ) skipped ++;
|
|
549
|
+
if ( p < baseCount ) keep.push( p );
|
|
550
|
+
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if ( list && keep.length !== baseCount ) {
|
|
556
|
+
|
|
557
|
+
const matrices = new Float32Array( keep.length * 16 );
|
|
558
|
+
keep.forEach( ( p, i ) => matrices.set( list.matrices.subarray( p * 16, p * 16 + 16 ), i * 16 ) );
|
|
559
|
+
ir.instanceCount -= baseCount - keep.length;
|
|
560
|
+
list.matrices = matrices;
|
|
561
|
+
list.matricesEnd = null;
|
|
562
|
+
list.count = keep.length;
|
|
563
|
+
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if ( skipped > 0 ) ir.warnings.push( `${skipped.toLocaleString()} moving placement(s) past ${MAX_ANIMATED_PLACEMENTS.toLocaleString()} left static` );
|
|
569
|
+
return animated;
|
|
570
|
+
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* One file's animated transforms as two keys each; the start transform stays the static pose.
|
|
577
|
+
* @param {object} ir - parser output
|
|
578
|
+
* @returns {object} the same IR
|
|
579
|
+
*/
|
|
580
|
+
export function motionFromShutter( ir ) {
|
|
581
|
+
|
|
582
|
+
if ( ! ir.hasMotion ) return ir;
|
|
583
|
+
|
|
584
|
+
const { start, end } = ir.transformTimes;
|
|
585
|
+
const duration = end - start;
|
|
586
|
+
if ( ! ( duration > 0 ) ) return ir;
|
|
587
|
+
|
|
588
|
+
const times = Float32Array.of( 0, duration );
|
|
589
|
+
const pair = ( a, ao, b, bo ) => {
|
|
590
|
+
|
|
591
|
+
const matrices = new Float32Array( 32 );
|
|
592
|
+
for ( let i = 0; i < 16; i ++ ) {
|
|
593
|
+
|
|
594
|
+
matrices[ i ] = a[ ao + i ];
|
|
595
|
+
matrices[ 16 + i ] = b[ bo + i ];
|
|
596
|
+
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return { times, matrices, visible: null };
|
|
600
|
+
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
for ( const shape of ir.shapes ) {
|
|
604
|
+
|
|
605
|
+
if ( shape.ctmEnd ) shape.motion = pair( shape.ctm, 0, shape.ctmEnd, 0 );
|
|
606
|
+
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
ir.animatedInstances = [];
|
|
610
|
+
for ( const list of ir.instances.values() ) {
|
|
611
|
+
|
|
612
|
+
if ( ! list.matricesEnd ) continue;
|
|
613
|
+
|
|
614
|
+
const keep = [];
|
|
615
|
+
for ( let p = 0; p < list.count; p ++ ) {
|
|
616
|
+
|
|
617
|
+
const o = p * 16;
|
|
618
|
+
if ( sameMatrix( list.matrices, o, list.matricesEnd, o ) || ir.animatedInstances.length >= MAX_ANIMATED_PLACEMENTS ) keep.push( p );
|
|
619
|
+
else ir.animatedInstances.push( { name: list.name, motion: pair( list.matrices, o, list.matricesEnd, o ) } );
|
|
620
|
+
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
if ( keep.length !== list.count ) {
|
|
624
|
+
|
|
625
|
+
const matrices = new Float32Array( keep.length * 16 );
|
|
626
|
+
keep.forEach( ( p, i ) => matrices.set( list.matrices.subarray( p * 16, p * 16 + 16 ), i * 16 ) );
|
|
627
|
+
ir.instanceCount -= list.count - keep.length;
|
|
628
|
+
list.matrices = matrices;
|
|
629
|
+
list.count = keep.length;
|
|
630
|
+
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
list.matricesEnd = null;
|
|
634
|
+
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if ( ir.camera?.cameraToWorldEnd ) {
|
|
638
|
+
|
|
639
|
+
ir.camera.motion = { ...pair( ir.camera.cameraToWorld, 0, ir.camera.cameraToWorldEnd, 0 ), fov: null };
|
|
640
|
+
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
ir.animation = { name: 'Shutter motion', duration, frames: 2 };
|
|
644
|
+
return ir;
|
|
645
|
+
|
|
646
|
+
}
|