rayzee 8.6.1 → 8.8.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.
@@ -15,12 +15,17 @@
15
15
  * camera: { type, params, cameraToWorld:number[16] } | null,
16
16
  * namedMaterials: Map<name, { type, params }>,
17
17
  * namedTextures: Map<name, { dataType, class, params }>,
18
- * shapes: [ { type, params, ctm, material, areaLight, reverseOrientation } ],
18
+ * shapes: [ { type, params, ctm, ctmEnd?, material, areaLight, reverseOrientation } ],
19
19
  * lights: [ { type, params, ctm } ],
20
- * instances: [ { name, ctm } ],
20
+ * instances: Map<name, { name, count, matrices, matricesEnd? }>,
21
21
  * objects: Map<name, shapes[]>,
22
+ * transformTimes: { start, end },
23
+ * hasMotion: boolean,
22
24
  * warnings: string[]
23
25
  * }
26
+ *
27
+ * `ctm` is pbrt's start transform. `ActiveTransform` can edit a second, end-of-shutter one,
28
+ * recorded as `ctmEnd` / `cameraToWorldEnd` / `matricesEnd` only where it differs.
24
29
  */
25
30
 
26
31
  import { TokenStream, TokenType } from './PBRTTokenizer.js';
@@ -32,6 +37,16 @@ import * as M from './PBRTMath.js';
32
37
  // ~100 bytes of overhead, and most parameters are one number or three.
33
38
  const TYPED_ARRAY_THRESHOLD = 8;
34
39
 
40
+ const START = 1, END = 2, BOTH = 3;
41
+
42
+ function sameMatrix( a, b ) {
43
+
44
+ if ( a === b ) return true;
45
+ for ( let i = 0; i < 16; i ++ ) if ( a[ i ] !== b[ i ] ) return false;
46
+ return true;
47
+
48
+ }
49
+
35
50
  const NUMERIC_STORAGE = {
36
51
  integer: Int32Array,
37
52
  point3: Float32Array,
@@ -43,6 +58,14 @@ const NUMERIC_STORAGE = {
43
58
  rgb: Float32Array
44
59
  };
45
60
 
61
+ function growFloat32( array, length ) {
62
+
63
+ const grown = new Float32Array( length );
64
+ grown.set( array );
65
+ return grown;
66
+
67
+ }
68
+
46
69
  export class PBRTParser {
47
70
 
48
71
  /**
@@ -79,11 +102,15 @@ export class PBRTParser {
79
102
  instanceCount: 0,
80
103
  skippedInstances: 0,
81
104
  objects: new Map(),
105
+ transformTimes: { start: 0, end: 1 },
106
+ hasMotion: false,
82
107
  warnings: []
83
108
  };
84
109
 
85
- // Graphics state
110
+ // Graphics state. `ctmEnd` is null while it equals `ctm`.
86
111
  this.ctm = M.identity();
112
+ this.ctmEnd = null;
113
+ this.activeTransform = BOTH;
87
114
  this._ctmSource = null;
88
115
  this._ctmValue = null;
89
116
  this.state = { material: null, areaLight: null, reverseOrientation: false };
@@ -133,20 +160,31 @@ export class PBRTParser {
133
160
  }
134
161
 
135
162
  let list = this.ir.instances.get( name );
136
- if ( ! list ) this.ir.instances.set( name, list = { name, count: 0, matrices: new Float32Array( 16 * 32 ) } );
163
+ if ( ! list ) this.ir.instances.set( name, list = { name, count: 0, matrices: new Float32Array( 16 * 32 ), matricesEnd: null } );
137
164
 
138
165
  const need = ( list.count + 1 ) * 16;
139
166
  if ( need > list.matrices.length ) {
140
167
 
141
- const grown = new Float32Array( Math.max( need, list.matrices.length * 2 ) );
142
- grown.set( list.matrices );
143
- list.matrices = grown;
168
+ const length = Math.max( need, list.matrices.length * 2 );
169
+ list.matrices = growFloat32( list.matrices, length );
170
+ if ( list.matricesEnd ) list.matricesEnd = growFloat32( list.matricesEnd, length );
144
171
 
145
172
  }
146
173
 
147
174
  const o = list.count * 16;
148
175
  const m = this.ctm;
149
176
  for ( let i = 0; i < 16; i ++ ) list.matrices[ o + i ] = m[ i ];
177
+
178
+ // Allocated once a placement of this template moves.
179
+ const end = this._ctmEndSnapshot();
180
+ if ( end && ! list.matricesEnd ) list.matricesEnd = list.matrices.slice();
181
+ if ( list.matricesEnd ) {
182
+
183
+ const e = end || m;
184
+ for ( let i = 0; i < 16; i ++ ) list.matricesEnd[ o + i ] = e[ i ];
185
+
186
+ }
187
+
150
188
  list.count ++;
151
189
  this.ir.instanceCount ++;
152
190
 
@@ -170,6 +208,72 @@ export class PBRTParser {
170
208
 
171
209
  }
172
210
 
211
+ /** The end-of-shutter CTM when it differs from the start one, else null. */
212
+ _ctmEndSnapshot() {
213
+
214
+ if ( this.ctmEnd === null || sameMatrix( this.ctmEnd, this.ctm ) ) return null;
215
+ this.ir.hasMotion = true;
216
+ return this.ctmEnd.slice();
217
+
218
+ }
219
+
220
+ /** Right-multiply `m` into whichever CTMs ActiveTransform selects. */
221
+ _concat( m ) {
222
+
223
+ if ( this.activeTransform === BOTH && this.ctmEnd === null ) {
224
+
225
+ this.ctm = M.multiply( this.ctm, m );
226
+ return;
227
+
228
+ }
229
+
230
+ const end = this.ctmEnd ?? this.ctm;
231
+ this.ctmEnd = this.activeTransform & END ? M.multiply( end, m ) : end;
232
+ if ( this.activeTransform & START ) this.ctm = M.multiply( this.ctm, m );
233
+
234
+ }
235
+
236
+ /** Replace whichever CTMs ActiveTransform selects. */
237
+ _setCTM( m ) {
238
+
239
+ if ( this.activeTransform === BOTH ) {
240
+
241
+ this.ctm = m;
242
+ this.ctmEnd = null;
243
+ return;
244
+
245
+ }
246
+
247
+ const end = this.ctmEnd ?? this.ctm;
248
+ this.ctmEnd = this.activeTransform & END ? m : end;
249
+ if ( this.activeTransform & START ) this.ctm = m;
250
+
251
+ }
252
+
253
+ _pushGraphicsState() {
254
+
255
+ this.attributeStack.push( {
256
+ ctm: this.ctm,
257
+ ctmEnd: this.ctmEnd,
258
+ activeTransform: this.activeTransform,
259
+ material: this.state.material,
260
+ areaLight: this.state.areaLight,
261
+ reverseOrientation: this.state.reverseOrientation
262
+ } );
263
+
264
+ }
265
+
266
+ _popGraphicsState() {
267
+
268
+ const s = this.attributeStack.pop();
269
+ if ( ! s ) return;
270
+ this.ctm = s.ctm;
271
+ this.ctmEnd = s.ctmEnd;
272
+ this.activeTransform = s.activeTransform;
273
+ this.state = { material: s.material, areaLight: s.areaLight, reverseOrientation: s.reverseOrientation };
274
+
275
+ }
276
+
173
277
  _peek() {
174
278
 
175
279
  return this.stream.peek();
@@ -398,11 +502,11 @@ export class PBRTParser {
398
502
  switch ( name ) {
399
503
 
400
504
  // ── transforms ──
401
- case 'Identity': this.ctm = M.identity(); break;
505
+ case 'Identity': this._setCTM( M.identity() ); break;
402
506
  case 'Translate': {
403
507
 
404
508
  const [ x, y, z ] = this._readNumbers( 3 );
405
- this.ctm = M.multiply( this.ctm, M.translate( x, y, z ) );
509
+ this._concat( M.translate( x, y, z ) );
406
510
  break;
407
511
 
408
512
  }
@@ -410,7 +514,7 @@ export class PBRTParser {
410
514
  case 'Scale': {
411
515
 
412
516
  const [ x, y, z ] = this._readNumbers( 3 );
413
- this.ctm = M.multiply( this.ctm, M.scale( x, y, z ) );
517
+ this._concat( M.scale( x, y, z ) );
414
518
  break;
415
519
 
416
520
  }
@@ -418,7 +522,7 @@ export class PBRTParser {
418
522
  case 'Rotate': {
419
523
 
420
524
  const [ angle, x, y, z ] = this._readNumbers( 4 );
421
- this.ctm = M.multiply( this.ctm, M.rotate( angle, x, y, z ) );
525
+ this._concat( M.rotate( angle, x, y, z ) );
422
526
  break;
423
527
 
424
528
  }
@@ -430,31 +534,37 @@ export class PBRTParser {
430
534
  [ v[ 0 ], v[ 1 ], v[ 2 ] ], [ v[ 3 ], v[ 4 ], v[ 5 ] ], [ v[ 6 ], v[ 7 ], v[ 8 ] ]
431
535
  );
432
536
  // pbrt sets CTM to world-to-camera = inverse(cameraToWorld).
433
- this.ctm = M.multiply( this.ctm, M.invert( camToWorld ) );
537
+ this._concat( M.invert( camToWorld ) );
434
538
  break;
435
539
 
436
540
  }
437
541
 
438
542
  case 'Transform': {
439
543
 
440
- this.ctm = this._readBracketedOrBareNumbers( 16 );
544
+ this._setCTM( this._readBracketedOrBareNumbers( 16 ) );
441
545
  break;
442
546
 
443
547
  }
444
548
 
445
549
  case 'ConcatTransform': {
446
550
 
447
- const m = this._readBracketedOrBareNumbers( 16 );
448
- this.ctm = M.multiply( this.ctm, m );
551
+ this._concat( this._readBracketedOrBareNumbers( 16 ) );
449
552
  break;
450
553
 
451
554
  }
452
555
 
453
- case 'CoordinateSystem': this.coordSystems.set( this._expectString( 'CoordinateSystem' ), this.ctm.slice() ); break;
556
+ // Both ends, whatever ActiveTransform says — as pbrt does.
557
+ case 'CoordinateSystem': this.coordSystems.set( this._expectString( 'CoordinateSystem' ), { ctm: this.ctm, ctmEnd: this.ctmEnd } ); break;
454
558
  case 'CoordSysTransform': {
455
559
 
456
560
  const cs = this.coordSystems.get( this._expectString( 'CoordSysTransform' ) );
457
- if ( cs ) this.ctm = cs.slice();
561
+ if ( cs ) {
562
+
563
+ this.ctm = cs.ctm;
564
+ this.ctmEnd = cs.ctmEnd;
565
+
566
+ }
567
+
458
568
  break;
459
569
 
460
570
  }
@@ -466,6 +576,8 @@ export class PBRTParser {
466
576
  const params = this._parseParams();
467
577
  // Camera-to-world is the inverse of the CTM at the Camera directive.
468
578
  this.ir.camera = { type, params, cameraToWorld: M.invert( this.ctm ) };
579
+ const end = this._ctmEndSnapshot();
580
+ if ( end ) this.ir.camera.cameraToWorldEnd = M.invert( end );
469
581
  break;
470
582
 
471
583
  }
@@ -497,25 +609,24 @@ export class PBRTParser {
497
609
  // ── world block ──
498
610
  case 'WorldBegin':
499
611
  this.ctm = M.identity();
612
+ this.ctmEnd = null;
613
+ this.activeTransform = BOTH;
500
614
  this.state = { material: null, areaLight: null, reverseOrientation: false };
501
615
  break;
502
616
  case 'WorldEnd': break; // legacy v3
503
617
 
504
- case 'AttributeBegin':
505
- this.attributeStack.push( {
506
- ctm: this.ctm.slice(),
507
- material: this.state.material,
508
- areaLight: this.state.areaLight,
509
- reverseOrientation: this.state.reverseOrientation
510
- } );
511
- break;
512
- case 'AttributeEnd': {
618
+ case 'AttributeBegin': this._pushGraphicsState(); break;
619
+ case 'AttributeEnd': this._popGraphicsState(); break;
513
620
 
514
- const s = this.attributeStack.pop();
515
- if ( s ) {
621
+ case 'TransformBegin': this.transformStack.push( { ctm: this.ctm, ctmEnd: this.ctmEnd, activeTransform: this.activeTransform } ); break;
622
+ case 'TransformEnd': {
623
+
624
+ const t = this.transformStack.pop();
625
+ if ( t ) {
516
626
 
517
- this.ctm = s.ctm;
518
- this.state = { material: s.material, areaLight: s.areaLight, reverseOrientation: s.reverseOrientation };
627
+ this.ctm = t.ctm;
628
+ this.ctmEnd = t.ctmEnd;
629
+ this.activeTransform = t.activeTransform;
519
630
 
520
631
  }
521
632
 
@@ -523,19 +634,30 @@ export class PBRTParser {
523
634
 
524
635
  }
525
636
 
526
- case 'TransformBegin': this.transformStack.push( this.ctm.slice() ); break;
527
- case 'TransformEnd': {
637
+ case 'ReverseOrientation': this.state.reverseOrientation = ! this.state.reverseOrientation; break;
638
+
639
+ // `Attribute "target" params` — v4 default-setting; ignored for MVP.
640
+ case 'Attribute': this._expectString( 'Attribute target' ); this._parseParams(); break;
641
+ case 'ActiveTransform': {
528
642
 
529
- const m = this.transformStack.pop(); if ( m ) this.ctm = m; break;
643
+ const t = this._next();
644
+ const which = t?.value;
645
+ if ( which === 'StartTime' ) this.activeTransform = START;
646
+ else if ( which === 'EndTime' ) this.activeTransform = END;
647
+ else if ( which === 'All' ) this.activeTransform = BOTH;
648
+ else this._warn( `ActiveTransform "${which}" not recognised` );
649
+ break;
530
650
 
531
651
  }
532
652
 
533
- case 'ReverseOrientation': this.state.reverseOrientation = ! this.state.reverseOrientation; break;
653
+ case 'TransformTimes': {
654
+
655
+ const [ start, end ] = this._readNumbers( 2 );
656
+ this.ir.transformTimes = { start, end };
657
+ break;
658
+
659
+ }
534
660
 
535
- // `Attribute "target" params` — v4 default-setting; ignored for MVP.
536
- case 'Attribute': this._expectString( 'Attribute target' ); this._parseParams(); break;
537
- case 'ActiveTransform': this._next(); break; // StartTime|EndTime|All
538
- case 'TransformTimes': this._readNumbers( 2 ); break;
539
661
  case 'MediumInterface': {
540
662
 
541
663
  // up to two strings (inside/outside)
@@ -602,6 +724,7 @@ export class PBRTParser {
602
724
 
603
725
  const type = this._expectString( 'LightSource type' );
604
726
  const params = this._parseParams();
727
+ if ( this.ctmEnd !== null && ! sameMatrix( this.ctmEnd, this.ctm ) ) this._warnOnce( 'animated-light', 'animated LightSource transforms are not supported — using the start transform' );
605
728
  this.ir.lights.push( { type, params, ctm: this._ctmSnapshot() } );
606
729
  break;
607
730
 
@@ -620,6 +743,14 @@ export class PBRTParser {
620
743
  areaLight: this.state.areaLight,
621
744
  reverseOrientation: this.state.reverseOrientation
622
745
  };
746
+ const end = this.currentObject === null ? this._ctmEndSnapshot() : null;
747
+ if ( end ) shape.ctmEnd = end;
748
+ else if ( this.currentObject !== null && this.ctmEnd !== null && ! sameMatrix( this.ctmEnd, this.ctm ) ) {
749
+
750
+ this._warnOnce( 'animated-template', 'animated transforms inside ObjectBegin are not supported — using the start transform' );
751
+
752
+ }
753
+
623
754
  this._emitShape( shape );
624
755
  break;
625
756
 
@@ -630,12 +761,7 @@ export class PBRTParser {
630
761
 
631
762
  const objName = this._expectString( 'ObjectBegin name' );
632
763
  // pbrt implicitly pushes graphics state.
633
- this.attributeStack.push( {
634
- ctm: this.ctm.slice(),
635
- material: this.state.material,
636
- areaLight: this.state.areaLight,
637
- reverseOrientation: this.state.reverseOrientation
638
- } );
764
+ this._pushGraphicsState();
639
765
  this.currentObject = objName;
640
766
  this.objectBeginCTM = this.ctm.slice();
641
767
  if ( ! this.ir.objects.has( objName ) ) this.ir.objects.set( objName, [] );
@@ -647,14 +773,7 @@ export class PBRTParser {
647
773
 
648
774
  this.currentObject = null;
649
775
  this.objectBeginCTM = null;
650
- const s = this.attributeStack.pop();
651
- if ( s ) {
652
-
653
- this.ctm = s.ctm;
654
- this.state = { material: s.material, areaLight: s.areaLight, reverseOrientation: s.reverseOrientation };
655
-
656
- }
657
-
776
+ this._popGraphicsState();
658
777
  break;
659
778
 
660
779
  }
@@ -770,6 +889,14 @@ export class PBRTParser {
770
889
 
771
890
  }
772
891
 
892
+ _warnOnce( key, msg ) {
893
+
894
+ if ( this._warnedUnknown.has( key ) ) return;
895
+ this._warnedUnknown.add( key );
896
+ this._warn( msg );
897
+
898
+ }
899
+
773
900
  _warnUnknown( name ) {
774
901
 
775
902
  if ( this._warnedUnknown.has( name ) ) return;