rayzee 11.2.1 → 11.3.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.
Files changed (62) hide show
  1. package/README.md +58 -29
  2. package/dist/ArchiveImporter-D735xX-4.js +7860 -0
  3. package/dist/BidirectionalIntegrator-M89c0AlP.js +954 -0
  4. package/dist/{BufferLayout-D_IBo2ci.js → BufferLayout-xPRH9jYD.js} +41 -5
  5. package/dist/{NeuralRetouch-Cr-otSW8.js → NeuralRetouch-DiBH63kz.js} +1 -1
  6. package/dist/{NeuralSuperRes-B7E3_M0t.js → NeuralSuperRes-CZNQP13W.js} +1 -1
  7. package/dist/{PathTracer-D6nbknj5.js → PathTracer-BXCCVrPK.js} +4371 -4612
  8. package/dist/{PhysicalSky-Dq5VSiCQ.js → PhysicalSky-DqmaS0lb.js} +2 -2
  9. package/dist/{RayzeeRenderer-DbjqO6v-.js → RayzeeRenderer-DUSMFhmc.js} +426 -363
  10. package/dist/SolarLimb-bZE6y63S.js +762 -0
  11. package/dist/addons/archives.es.js +1 -1
  12. package/dist/addons/bidirectional.es.js +1 -1
  13. package/dist/addons/physical-sky.es.js +1 -1
  14. package/dist/rayzee-core.es.js +7 -7
  15. package/dist/rayzee.es.js +472 -468
  16. package/dist/rayzee.umd.js +106 -105
  17. package/dist/sceneCachePolicy-BAib5fCp.js +1602 -0
  18. package/package.json +1 -1
  19. package/src/PathTracerApp.js +4 -3
  20. package/src/Processor/ArchiveImporter.js +173 -94
  21. package/src/Processor/ArchiveReader.js +20 -23
  22. package/src/Processor/AssetLoader.js +48 -1
  23. package/src/Processor/BVHRefitter.js +109 -13
  24. package/src/Processor/BufferLayout.js +115 -0
  25. package/src/Processor/ChunkedRecords.js +3 -2
  26. package/src/Processor/EmissiveTriangleBuilder.js +6 -6
  27. package/src/Processor/GeometryExtractor.js +189 -79
  28. package/src/Processor/HostMemory.js +39 -9
  29. package/src/Processor/InstanceTable.js +714 -42
  30. package/src/Processor/PBRT/PBRTCurves.js +224 -6
  31. package/src/Processor/PBRT/PBRTMaterials.js +3 -0
  32. package/src/Processor/PBRT/PBRTSceneBuilder.js +82 -25
  33. package/src/Processor/PackedRayBuffer.js +5 -2
  34. package/src/Processor/SceneProcessor.js +747 -141
  35. package/src/Processor/TLASBuilder.js +32 -17
  36. package/src/Processor/USD/USDLayer.js +1149 -0
  37. package/src/Processor/USD/USDScene.js +1655 -0
  38. package/src/Processor/USD/USDStage.js +682 -0
  39. package/src/Processor/USD/USDText.js +674 -0
  40. package/src/Processor/USD/index.js +138 -0
  41. package/src/Processor/Workers/BVHRefitWorker.js +3 -3
  42. package/src/RayzeeRenderer.js +167 -26
  43. package/src/Stages/NormalDepth.js +7 -12
  44. package/src/Stages/PathTracer.js +12 -17
  45. package/src/Stages/PathTracerStage.js +273 -96
  46. package/src/Storage/GeometrySpill.js +46 -14
  47. package/src/Storage/SceneGraphCodec.js +17 -3
  48. package/src/Storage/SpillStore.js +9 -0
  49. package/src/TSL/BVHTraversal.js +183 -7
  50. package/src/TSL/Common.js +57 -13
  51. package/src/TSL/ShadeKernel.js +3 -2
  52. package/src/TSL/TextureSampling.js +11 -8
  53. package/src/TSL/patches.js +84 -0
  54. package/src/integrators/BidirectionalIntegrator.js +20 -2
  55. package/src/managers/DenoisingManager.js +4 -4
  56. package/src/managers/InteractionManager.js +19 -6
  57. package/src/managers/MaterialDataManager.js +22 -5
  58. package/src/managers/UniformManager.js +3 -0
  59. package/dist/ArchiveImporter-D_x863yu.js +0 -5198
  60. package/dist/BidirectionalIntegrator-DmkxxEOR.js +0 -946
  61. package/dist/SolarLimb-CPaeZVwz.js +0 -721
  62. package/dist/sceneCachePolicy-C4-4qwt6.js +0 -485
@@ -0,0 +1,674 @@
1
+ /**
2
+ * USD's text format (.usda) read into a Layer (USDLayer.js): prims with their specifier, metadata, composition arcs,
3
+ * variant sets and properties. Values stay as authored; numeric arrays become typed arrays.
4
+ */
5
+
6
+ import { Layer, Ref } from './USDLayer.js';
7
+
8
+ const LIST_OPS = new Set( [ 'prepend', 'append', 'add', 'delete', 'reorder' ] );
9
+ const SPECIFIERS = new Set( [ 'def', 'over', 'class' ] );
10
+ const QUALIFIERS = new Set( [ 'custom', 'uniform', 'varying', 'config' ] );
11
+
12
+ const INT_TYPES = new Set( [ 'int', 'int2', 'int3', 'int4', 'uint', 'int64', 'uint64', 'uchar' ] );
13
+ const DOUBLE_TYPES = new Set( [ 'double', 'double2', 'double3', 'double4', 'matrix2d', 'matrix3d', 'matrix4d', 'quatd', 'timecode',
14
+ 'point3d', 'vector3d', 'normal3d', 'color3d', 'color4d', 'texCoord2d', 'texCoord3d', 'frame4d' ] );
15
+ const NON_NUMERIC = new Set( [ 'token', 'string', 'asset', 'bool', 'dictionary', 'opaque', 'pathExpression' ] );
16
+
17
+ class Asset {
18
+
19
+ constructor( path ) {
20
+
21
+ this.asset = path;
22
+
23
+ }
24
+
25
+ }
26
+
27
+ class Path {
28
+
29
+ constructor( path ) {
30
+
31
+ this.path = path;
32
+
33
+ }
34
+
35
+ }
36
+
37
+ const isIdentStart = c => ( c >= 65 && c <= 90 ) || ( c >= 97 && c <= 122 ) || c === 95;
38
+ const isIdentChar = c => isIdentStart( c ) || ( c >= 48 && c <= 57 ) || c === 58 || c === 46;
39
+
40
+ class Reader {
41
+
42
+ constructor( text ) {
43
+
44
+ this.s = text;
45
+ this.i = 0;
46
+
47
+ }
48
+
49
+ fail( message ) {
50
+
51
+ let line = 1;
52
+ for ( let k = 0; k < this.i && k < this.s.length; k ++ ) if ( this.s.charCodeAt( k ) === 10 ) line ++;
53
+ throw new Error( `usda line ${line}: ${message}` );
54
+
55
+ }
56
+
57
+ skip() {
58
+
59
+ const s = this.s;
60
+ for ( ;; ) {
61
+
62
+ const c = s.charCodeAt( this.i );
63
+ if ( c === 32 || c === 9 || c === 10 || c === 13 || c === 59 ) this.i ++;
64
+ else if ( c === 35 ) {
65
+
66
+ while ( this.i < s.length && s.charCodeAt( this.i ) !== 10 ) this.i ++;
67
+
68
+ } else return c;
69
+
70
+ }
71
+
72
+ }
73
+
74
+ peek() {
75
+
76
+ return this.skip();
77
+
78
+ }
79
+
80
+ startsNumber( c ) {
81
+
82
+ if ( ( c >= 48 && c <= 57 ) || c === 46 ) return true;
83
+ if ( c !== 45 && c !== 43 ) return false;
84
+ const n = this.s.charCodeAt( this.i + 1 );
85
+ return ( n >= 48 && n <= 57 ) || n === 46 || n === 105;
86
+
87
+ }
88
+
89
+ /** A layer offset after a reference or sublayer, `( offset = 10; scale = 2 )`, which nothing here applies. */
90
+ skipLayerOffset() {
91
+
92
+ const save = this.i;
93
+ if ( this.peek() !== 40 ) return;
94
+ this.i ++;
95
+ const word = this.peekIdent();
96
+ this.i = save;
97
+ if ( word !== 'offset' && word !== 'scale' ) return;
98
+ this.skip();
99
+ const end = this.s.indexOf( ')', this.i );
100
+ if ( end < 0 ) this.fail( 'unterminated layer offset' );
101
+ this.i = end + 1;
102
+
103
+ }
104
+
105
+ eat( ch ) {
106
+
107
+ if ( this.skip() !== ch.charCodeAt( 0 ) ) this.fail( `expected "${ch}", found "${this.s[ this.i ] ?? 'end of file'}"` );
108
+ this.i ++;
109
+
110
+ }
111
+
112
+ maybe( ch ) {
113
+
114
+ if ( this.skip() !== ch.charCodeAt( 0 ) ) return false;
115
+ this.i ++;
116
+ return true;
117
+
118
+ }
119
+
120
+ ident() {
121
+
122
+ const c = this.skip();
123
+ if ( ! isIdentStart( c ) ) this.fail( `expected a name, found "${this.s[ this.i ] ?? 'end of file'}"` );
124
+ const start = this.i ++;
125
+ while ( isIdentChar( this.s.charCodeAt( this.i ) ) ) this.i ++;
126
+ return this.s.slice( start, this.i );
127
+
128
+ }
129
+
130
+ peekIdent() {
131
+
132
+ const save = this.i;
133
+ const c = this.skip();
134
+ if ( ! isIdentStart( c ) ) {
135
+
136
+ this.i = save;
137
+ return null;
138
+
139
+ }
140
+
141
+ const word = this.ident();
142
+ this.i = save;
143
+ return word;
144
+
145
+ }
146
+
147
+ string() {
148
+
149
+ const c = this.skip();
150
+ if ( c !== 34 && c !== 39 ) this.fail( `expected a string, found "${this.s[ this.i ] ?? 'end of file'}"` );
151
+ const s = this.s;
152
+ const q = s[ this.i ];
153
+ if ( s.startsWith( q + q + q, this.i ) ) {
154
+
155
+ const end = s.indexOf( q + q + q, this.i + 3 );
156
+ if ( end < 0 ) this.fail( 'unterminated string' );
157
+ const value = s.slice( this.i + 3, end );
158
+ this.i = end + 3;
159
+ return value;
160
+
161
+ }
162
+
163
+ let out = '';
164
+ let start = ++ this.i;
165
+ for ( ;; ) {
166
+
167
+ const c = s.charCodeAt( this.i );
168
+ if ( Number.isNaN( c ) ) this.fail( 'unterminated string' );
169
+ if ( c === 92 ) {
170
+
171
+ out += s.slice( start, this.i );
172
+ const e = s[ this.i + 1 ];
173
+ out += e === 'n' ? '\n' : e === 't' ? '\t' : e;
174
+ this.i += 2;
175
+ start = this.i;
176
+ continue;
177
+
178
+ }
179
+
180
+ if ( s[ this.i ] === q ) break;
181
+ this.i ++;
182
+
183
+ }
184
+
185
+ out += s.slice( start, this.i );
186
+ this.i ++;
187
+ return out;
188
+
189
+ }
190
+
191
+ asset() {
192
+
193
+ const s = this.s;
194
+ if ( s.startsWith( '@@@', this.i ) ) {
195
+
196
+ const end = s.indexOf( '@@@', this.i + 3 );
197
+ if ( end < 0 ) this.fail( 'unterminated asset path' );
198
+ const value = s.slice( this.i + 3, end );
199
+ this.i = end + 3;
200
+ return value;
201
+
202
+ }
203
+
204
+ const end = s.indexOf( '@', this.i + 1 );
205
+ if ( end < 0 ) this.fail( 'unterminated asset path' );
206
+ const value = s.slice( this.i + 1, end );
207
+ this.i = end + 1;
208
+ return value;
209
+
210
+ }
211
+
212
+ path() {
213
+
214
+ const end = this.s.indexOf( '>', this.i + 1 );
215
+ if ( end < 0 ) this.fail( 'unterminated path' );
216
+ const value = this.s.slice( this.i + 1, end );
217
+ this.i = end + 1;
218
+ return value;
219
+
220
+ }
221
+
222
+ number() {
223
+
224
+ const s = this.s;
225
+ const start = this.i;
226
+ if ( s[ this.i ] === '-' || s[ this.i ] === '+' ) this.i ++;
227
+ if ( s.startsWith( 'inf', this.i ) ) {
228
+
229
+ this.i += 3;
230
+ return s[ start ] === '-' ? - Infinity : Infinity;
231
+
232
+ }
233
+
234
+ while ( this.i < s.length ) {
235
+
236
+ const c = s.charCodeAt( this.i );
237
+ if ( ( c >= 48 && c <= 57 ) || c === 46 ) this.i ++;
238
+ else if ( c === 101 || c === 69 ) {
239
+
240
+ this.i ++;
241
+ if ( s[ this.i ] === '-' || s[ this.i ] === '+' ) this.i ++;
242
+
243
+ } else break;
244
+
245
+ }
246
+
247
+ const value = Number( s.slice( start, this.i ) );
248
+ if ( Number.isNaN( value ) ) this.fail( `bad number "${s.slice( start, this.i )}"` );
249
+ return value;
250
+
251
+ }
252
+
253
+ value() {
254
+
255
+ const c = this.skip();
256
+ if ( c === 40 ) return this.sequence( ')' );
257
+ if ( c === 91 ) return this.sequence( ']' );
258
+ if ( c === 123 ) return this.dictionary();
259
+ if ( c === 34 || c === 39 ) return this.string();
260
+ if ( c === 64 ) {
261
+
262
+ const asset = this.asset();
263
+ const value = this.peek() === 60 ? new Ref( asset, this.path() ) : new Asset( asset );
264
+ this.skipLayerOffset();
265
+ return value;
266
+
267
+ }
268
+
269
+ if ( c === 60 ) {
270
+
271
+ const value = new Path( this.path() );
272
+ this.skipLayerOffset();
273
+ return value;
274
+
275
+ }
276
+
277
+ if ( this.startsNumber( c ) ) return this.number();
278
+ const word = this.ident();
279
+ if ( word === 'true' ) return true;
280
+ if ( word === 'false' ) return false;
281
+ if ( word === 'None' ) return null;
282
+ if ( word === 'inf' ) return Infinity;
283
+ if ( word === 'nan' ) return NaN;
284
+ return word;
285
+
286
+ }
287
+
288
+ sequence( close ) {
289
+
290
+ this.i ++;
291
+ const out = [];
292
+ while ( this.peek() !== close.charCodeAt( 0 ) ) {
293
+
294
+ out.push( this.value() );
295
+ if ( ! this.maybe( ',' ) ) break;
296
+
297
+ }
298
+
299
+ this.eat( close );
300
+ return out;
301
+
302
+ }
303
+
304
+ /** `{ type key = value ... }`, as metadata dictionaries and `variants` are written. */
305
+ dictionary() {
306
+
307
+ this.i ++;
308
+ const out = {};
309
+ while ( this.peek() !== 125 ) {
310
+
311
+ if ( this.startsNumber( this.peek() ) ) {
312
+
313
+ // timeSamples: { 1: value, 2: value }
314
+ const time = this.number();
315
+ this.eat( ':' );
316
+ out[ time ] = this.value();
317
+ this.maybe( ',' );
318
+ continue;
319
+
320
+ }
321
+
322
+ let type = this.ident();
323
+ if ( this.peek() === 91 && this.s[ this.i + 1 ] === ']' ) {
324
+
325
+ this.i += 2;
326
+ type += '[]';
327
+
328
+ }
329
+
330
+ const key = this.peek() === 34 || this.peek() === 39 ? this.string() : this.ident();
331
+ this.eat( '=' );
332
+ out[ key ] = type === 'dictionary' ? this.dictionary() : this.value();
333
+ this.maybe( ',' );
334
+
335
+ }
336
+
337
+ this.eat( '}' );
338
+ return out;
339
+
340
+ }
341
+
342
+ }
343
+
344
+ function refsOf( value ) {
345
+
346
+ if ( value === null ) return [];
347
+ const items = Array.isArray( value ) ? value : [ value ];
348
+ const out = [];
349
+ for ( const item of items ) {
350
+
351
+ if ( item instanceof Ref ) out.push( item );
352
+ else if ( item instanceof Asset ) out.push( new Ref( item.asset, '' ) );
353
+ else if ( item instanceof Path ) out.push( new Ref( '', item.path ) );
354
+
355
+ }
356
+
357
+ return out;
358
+
359
+ }
360
+
361
+ function pathsOf( value ) {
362
+
363
+ if ( value === null ) return [];
364
+ return ( Array.isArray( value ) ? value : [ value ] ).filter( v => v instanceof Path ).map( v => v.path );
365
+
366
+ }
367
+
368
+ function stringsOf( value ) {
369
+
370
+ if ( value === null ) return [];
371
+ return ( Array.isArray( value ) ? value : [ value ] ).map( String );
372
+
373
+ }
374
+
375
+ function plain( value ) {
376
+
377
+ if ( value instanceof Asset ) return value.asset;
378
+ if ( value instanceof Path ) return value.path;
379
+ if ( Array.isArray( value ) ) return value.map( plain );
380
+ return value;
381
+
382
+ }
383
+
384
+ function flatten( value, out ) {
385
+
386
+ for ( const v of value ) {
387
+
388
+ if ( Array.isArray( v ) ) flatten( v, out );
389
+ else out.push( v );
390
+
391
+ }
392
+
393
+ return out;
394
+
395
+ }
396
+
397
+ function typed( type, value ) {
398
+
399
+ if ( value === null || value === undefined ) return value;
400
+ const array = type.endsWith( '[]' );
401
+ const base = array ? type.slice( 0, - 2 ) : type;
402
+ if ( NON_NUMERIC.has( base ) || ! Array.isArray( value ) ) return plain( value );
403
+ const numbers = flatten( value, [] );
404
+ // Text writes a quaternion real part first; crate files and three.js keep it last.
405
+ if ( base.startsWith( 'quat' ) ) {
406
+
407
+ for ( let i = 0; i + 3 < numbers.length; i += 4 ) numbers.splice( i, 4, numbers[ i + 1 ], numbers[ i + 2 ], numbers[ i + 3 ], numbers[ i ] );
408
+
409
+ }
410
+
411
+ if ( ! array ) return numbers;
412
+ if ( INT_TYPES.has( base ) ) return Int32Array.from( numbers );
413
+ if ( DOUBLE_TYPES.has( base ) ) return Float64Array.from( numbers );
414
+ return Float32Array.from( numbers );
415
+
416
+ }
417
+
418
+ function setPrimMeta( spec, key, op, value ) {
419
+
420
+ switch ( key ) {
421
+
422
+ case 'references':
423
+ case 'payload':
424
+ spec.setArc( key, op, refsOf( value ) );
425
+ return;
426
+ case 'inherits':
427
+ case 'specializes':
428
+ spec.setArc( key, op, pathsOf( value ) );
429
+ return;
430
+ case 'variantSets':
431
+ spec.setArc( 'variantSetNames', op, stringsOf( value ) );
432
+ return;
433
+ case 'apiSchemas':
434
+ spec.setArc( 'apiSchemas', op, stringsOf( value ) );
435
+ return;
436
+ case 'variants':
437
+ spec.variantSelection = { ...( spec.variantSelection ?? {} ), ...value };
438
+ return;
439
+ default:
440
+ spec.meta[ key ] = plain( value );
441
+
442
+ }
443
+
444
+ }
445
+
446
+ class TextParser extends Reader {
447
+
448
+ parse( path ) {
449
+
450
+ const layer = new Layer( path );
451
+ if ( this.s.charCodeAt( 0 ) === 0xFEFF ) this.i = 1;
452
+ if ( ! this.s.startsWith( '#usda', this.i ) ) throw new Error( 'not a usda layer' );
453
+ while ( this.i < this.s.length && this.s.charCodeAt( this.i ) !== 10 ) this.i ++;
454
+
455
+ if ( this.peek() === 40 ) this.metadata( ( key, op, value ) => {
456
+
457
+ if ( key === 'subLayers' ) layer.meta.subLayers = refsOf( value ).map( r => r.asset );
458
+ else layer.meta[ key ] = plain( value );
459
+
460
+ }, doc => {
461
+
462
+ layer.meta.doc = doc;
463
+
464
+ } );
465
+
466
+ while ( ! Number.isNaN( this.peek() ) ) this.prim( layer.root );
467
+
468
+ return layer;
469
+
470
+ }
471
+
472
+ metadata( onEntry, onDoc = null ) {
473
+
474
+ this.eat( '(' );
475
+ while ( this.peek() !== 41 ) {
476
+
477
+ const c = this.peek();
478
+ if ( c === 34 || c === 39 ) {
479
+
480
+ const doc = this.string();
481
+ if ( onDoc ) onDoc( doc );
482
+ continue;
483
+
484
+ }
485
+
486
+ let key = this.ident();
487
+ let op = 'explicit';
488
+ if ( LIST_OPS.has( key ) ) {
489
+
490
+ op = key;
491
+ key = this.ident();
492
+
493
+ }
494
+
495
+ this.eat( '=' );
496
+ onEntry( key, op, this.value() );
497
+
498
+ }
499
+
500
+ this.eat( ')' );
501
+
502
+ }
503
+
504
+ prim( parent ) {
505
+
506
+ const specifier = this.ident();
507
+ if ( ! SPECIFIERS.has( specifier ) ) this.fail( `expected def, over or class, found "${specifier}"` );
508
+ const typeName = this.peek() === 34 || this.peek() === 39 ? '' : this.ident();
509
+ const name = this.string();
510
+ const spec = parent.child( name, true );
511
+ spec.specifier = specifier;
512
+ spec.typeName = typeName;
513
+ if ( this.peek() === 40 ) this.metadata( ( key, op, value ) => setPrimMeta( spec, key, op, value ), doc => {
514
+
515
+ spec.meta.doc = doc;
516
+
517
+ } );
518
+
519
+ this.body( spec );
520
+
521
+ }
522
+
523
+ body( spec ) {
524
+
525
+ this.eat( '{' );
526
+ while ( this.peek() !== 125 ) {
527
+
528
+ const word = this.peekIdent();
529
+ if ( word === null ) this.fail( `unexpected "${this.s[ this.i ] ?? 'end of file'}" in a prim` );
530
+ if ( SPECIFIERS.has( word ) ) this.prim( spec );
531
+ else if ( word === 'variantSet' ) this.variantSet( spec );
532
+ else if ( word === 'reorder' ) {
533
+
534
+ this.ident();
535
+ const what = this.ident();
536
+ this.eat( '=' );
537
+ const order = stringsOf( this.value() );
538
+ if ( what === 'nameChildren' ) spec.reorder = order;
539
+
540
+ } else this.property( spec );
541
+
542
+ }
543
+
544
+ this.eat( '}' );
545
+
546
+ }
547
+
548
+ variantSet( spec ) {
549
+
550
+ this.ident();
551
+ const setName = this.string();
552
+ this.eat( '=' );
553
+ this.eat( '{' );
554
+ while ( this.peek() !== 125 ) {
555
+
556
+ const variant = spec.variant( setName, this.string(), true );
557
+ if ( this.peek() === 40 ) this.metadata( ( key, op, value ) => setPrimMeta( variant, key, op, value ) );
558
+ this.body( variant );
559
+
560
+ }
561
+
562
+ this.eat( '}' );
563
+
564
+ }
565
+
566
+ property( spec ) {
567
+
568
+ let word = this.ident();
569
+ let op = 'explicit';
570
+ let uniform = false;
571
+ let custom = false;
572
+ for ( ;; ) {
573
+
574
+ if ( LIST_OPS.has( word ) ) op = word;
575
+ else if ( QUALIFIERS.has( word ) ) {
576
+
577
+ if ( word === 'uniform' ) uniform = true;
578
+ if ( word === 'custom' ) custom = true;
579
+
580
+ } else break;
581
+ word = this.ident();
582
+
583
+ }
584
+
585
+ if ( word === 'rel' ) {
586
+
587
+ const name = this.ident();
588
+ const prop = spec.property( name, true );
589
+ prop.rel = true;
590
+ prop.custom = custom;
591
+ if ( this.maybe( '=' ) ) {
592
+
593
+ const targets = this.value();
594
+ prop.setTargets( op, pathsOf( targets ) );
595
+
596
+ }
597
+
598
+ if ( this.peek() === 40 ) this.metadata( ( key, _, value ) => {
599
+
600
+ prop.meta[ key ] = plain( value );
601
+
602
+ }, () => {} );
603
+ return;
604
+
605
+ }
606
+
607
+ let type = word;
608
+ if ( this.peek() === 91 && this.s[ this.i + 1 ] === ']' ) {
609
+
610
+ this.i += 2;
611
+ type += '[]';
612
+
613
+ }
614
+
615
+ let name = this.ident();
616
+ let mode = 'default';
617
+ for ( const suffix of [ '.connect', '.timeSamples', '.spline' ] ) {
618
+
619
+ if ( name.endsWith( suffix ) ) {
620
+
621
+ mode = suffix.slice( 1 );
622
+ name = name.slice( 0, - suffix.length );
623
+
624
+ }
625
+
626
+ }
627
+
628
+ const prop = spec.property( name, true );
629
+ prop.type = type;
630
+ prop.uniform = uniform || prop.uniform;
631
+ prop.custom = custom;
632
+
633
+ if ( this.maybe( '=' ) ) {
634
+
635
+ if ( mode === 'connect' ) prop.setConnections( op, pathsOf( this.value() ) );
636
+ else if ( mode === 'timeSamples' ) {
637
+
638
+ const samples = this.value();
639
+ prop.timeSamples = new Map( Object.entries( samples ).map( ( [ t, v ] ) => [ Number( t ), typed( type, v ) ] ).sort( ( a, b ) => a[ 0 ] - b[ 0 ] ) );
640
+
641
+ } else if ( mode === 'spline' ) {
642
+
643
+ this.value();
644
+
645
+ } else {
646
+
647
+ const value = this.value();
648
+ prop.value = value === null ? null : typed( type, value );
649
+ prop.hasValue = true;
650
+
651
+ }
652
+
653
+ }
654
+
655
+ if ( this.peek() === 40 ) this.metadata( ( key, _, value ) => {
656
+
657
+ prop.meta[ key ] = plain( value );
658
+
659
+ }, doc => {
660
+
661
+ prop.meta.doc = doc;
662
+
663
+ } );
664
+
665
+ }
666
+
667
+ }
668
+
669
+ export function parseUSDA( text, path = '' ) {
670
+
671
+ return new TextParser( text ).parse( path );
672
+
673
+ }
674
+