rayzee 7.31.1 → 8.1.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 (53) hide show
  1. package/README.md +164 -0
  2. package/dist/rayzee.es.js +6728 -4735
  3. package/dist/rayzee.umd.js +121 -73
  4. package/package.json +2 -2
  5. package/src/EngineDefaults.js +168 -13
  6. package/src/EngineIssues.js +5 -0
  7. package/src/PathTracerApp.js +151 -25
  8. package/src/Processor/ArchiveReader.js +538 -0
  9. package/src/Processor/AssetLoader.js +265 -23
  10. package/src/Processor/BVHBuilder.js +48 -32
  11. package/src/Processor/BVHRefitter.js +244 -99
  12. package/src/Processor/ChunkedRecords.js +302 -0
  13. package/src/Processor/EmissiveTriangleBuilder.js +48 -21
  14. package/src/Processor/GeometryExtractor.js +580 -93
  15. package/src/Processor/HostMemory.js +222 -0
  16. package/src/Processor/InstanceTable.js +548 -97
  17. package/src/Processor/PBRT/EqualAreaOctahedral.js +32 -8
  18. package/src/Processor/PBRT/PBRTCurves.js +249 -0
  19. package/src/Processor/PBRT/PBRTMath.js +19 -0
  20. package/src/Processor/PBRT/PBRTParser.js +187 -41
  21. package/src/Processor/PBRT/PBRTSceneBuilder.js +586 -42
  22. package/src/Processor/PBRT/PBRTTokenizer.js +205 -65
  23. package/src/Processor/PBRT/index.js +257 -50
  24. package/src/Processor/PackedRayBuffer.js +29 -18
  25. package/src/Processor/ParallelBVHBuilder.js +9 -8
  26. package/src/Processor/QueueManager.js +5 -3
  27. package/src/Processor/SceneProcessor.js +1046 -365
  28. package/src/Processor/TLASBuilder.js +546 -201
  29. package/src/Processor/TextureCreator.js +11 -9
  30. package/src/Processor/VRAMTracker.js +12 -3
  31. package/src/Processor/Workers/BVHRefitWorker.js +16 -11
  32. package/src/Processor/Workers/BVHWorker.js +12 -12
  33. package/src/Processor/Workers/TLASWorker.js +37 -0
  34. package/src/Processor/utils.js +8 -20
  35. package/src/Stages/NormalDepth.js +6 -2
  36. package/src/Stages/PathTracer.js +5 -5
  37. package/src/Stages/PathTracerStage.js +163 -41
  38. package/src/TSL/BVHTraversal.js +208 -90
  39. package/src/TSL/Common.js +135 -2
  40. package/src/TSL/Displacement.js +21 -11
  41. package/src/TSL/EmissiveSampling.js +49 -25
  42. package/src/TSL/ExtendKernel.js +3 -2
  43. package/src/TSL/LightBVHSampling.js +7 -4
  44. package/src/TSL/LightsDirect.js +30 -29
  45. package/src/TSL/ShadeKernel.js +8 -6
  46. package/src/TSL/SortGlobalKernels.js +3 -4
  47. package/src/TSL/Struct.js +3 -0
  48. package/src/TSL/TextureSampling.js +24 -9
  49. package/src/TSL/patches.js +149 -84
  50. package/src/index.js +3 -0
  51. package/src/managers/AnimationManager.js +86 -76
  52. package/src/managers/MaterialDataManager.js +29 -23
  53. package/src/managers/TransformManager.js +9 -192
@@ -23,9 +23,26 @@
23
23
  * }
24
24
  */
25
25
 
26
- import { tokenize, TokenType } from './PBRTTokenizer.js';
26
+ import { TokenStream, TokenType } from './PBRTTokenizer.js';
27
27
  import * as M from './PBRTMath.js';
28
28
 
29
+ // Vertex streams reach tens of millions of entries and every consumer narrows them to
30
+ // float32 / uint32 anyway, so hold them at that width instead of 8 bytes a number.
31
+ // Below this a typed array costs more than it saves: an ArrayBuffer plus its view runs to
32
+ // ~100 bytes of overhead, and most parameters are one number or three.
33
+ const TYPED_ARRAY_THRESHOLD = 8;
34
+
35
+ const NUMERIC_STORAGE = {
36
+ integer: Int32Array,
37
+ point3: Float32Array,
38
+ point2: Float32Array,
39
+ vector3: Float32Array,
40
+ vector2: Float32Array,
41
+ vector: Float32Array,
42
+ normal: Float32Array,
43
+ rgb: Float32Array
44
+ };
45
+
29
46
  export class PBRTParser {
30
47
 
31
48
  /**
@@ -35,12 +52,18 @@ export class PBRTParser {
35
52
  */
36
53
  constructor( opts = {} ) {
37
54
 
55
+ this.releaseInclude = opts.releaseInclude || null;
38
56
  this.resolveInclude = opts.resolveInclude || ( () => {
39
57
 
40
58
  throw new Error( 'PBRTParser: Include used but no resolveInclude provided' );
41
59
 
42
60
  } );
43
61
 
62
+ // Placements past this are counted and dropped as they are read, not after: the peak
63
+ // is the parse itself, so a limit applied later saves nothing.
64
+ this.maxPlacements = opts.maxPlacements ?? Infinity;
65
+
66
+
44
67
  // IR accumulators
45
68
  this.ir = {
46
69
  film: null,
@@ -49,13 +72,20 @@ export class PBRTParser {
49
72
  namedTextures: new Map(),
50
73
  shapes: [],
51
74
  lights: [],
52
- instances: [],
75
+ // name -> { name, count, matrices } with the transforms packed end to end. One object
76
+ // and one 4x4 per placement measured 3.7 GB on isCoastline's 2.5M; this is 64 bytes
77
+ // each and allocates nothing per placement.
78
+ instances: new Map(),
79
+ instanceCount: 0,
80
+ skippedInstances: 0,
53
81
  objects: new Map(),
54
82
  warnings: []
55
83
  };
56
84
 
57
85
  // Graphics state
58
86
  this.ctm = M.identity();
87
+ this._ctmSource = null;
88
+ this._ctmValue = null;
59
89
  this.state = { material: null, areaLight: null, reverseOrientation: false };
60
90
  this.attributeStack = [];
61
91
  this.transformStack = [];
@@ -69,38 +99,86 @@ export class PBRTParser {
69
99
  this.dirStack = [ '' ];
70
100
 
71
101
  // Token cursor (swapped during Include recursion)
72
- this.tokens = [];
73
- this.pos = 0;
102
+ this.stream = null;
74
103
 
75
104
  this._warnedUnknown = new Set();
76
105
 
77
106
  }
78
107
 
79
108
  /**
80
- * Parse a top-level pbrt source string.
81
- * @param {string} src
109
+ * Parse a top-level pbrt source.
110
+ * @param {string|Uint8Array} src - bytes are preferred; a scene file can be larger
111
+ * than the longest string JavaScript will build.
82
112
  * @param {string} [baseDir] - directory of the source file, for Include paths
83
113
  * @returns {object} IR
84
114
  */
85
- parse( src, baseDir = '' ) {
115
+ async parse( src, baseDir = '' ) {
86
116
 
87
117
  this.dirStack = [ baseDir ];
88
- this._run( tokenize( src ) );
118
+ await this._run( new TokenStream( src ) );
89
119
  return this.ir;
90
120
 
91
121
  }
92
122
 
93
123
  // ── token helpers ──────────────────────────────────────────────
94
124
 
125
+ /** Record one placement, copying the CTM straight into the template's matrix buffer. */
126
+ _addInstance( name ) {
127
+
128
+ if ( this.ir.instanceCount >= this.maxPlacements ) {
129
+
130
+ this.ir.skippedInstances ++;
131
+ return;
132
+
133
+ }
134
+
135
+ let list = this.ir.instances.get( name );
136
+ if ( ! list ) this.ir.instances.set( name, list = { name, count: 0, matrices: new Float32Array( 16 * 32 ) } );
137
+
138
+ const need = ( list.count + 1 ) * 16;
139
+ if ( need > list.matrices.length ) {
140
+
141
+ const grown = new Float32Array( Math.max( need, list.matrices.length * 2 ) );
142
+ grown.set( list.matrices );
143
+ list.matrices = grown;
144
+
145
+ }
146
+
147
+ const o = list.count * 16;
148
+ const m = this.ctm;
149
+ for ( let i = 0; i < 16; i ++ ) list.matrices[ o + i ] = m[ i ];
150
+ list.count ++;
151
+ this.ir.instanceCount ++;
152
+
153
+ }
154
+
155
+ /**
156
+ * The CTM as the IR should hold it. Consumers treat it as read-only, so consecutive
157
+ * shapes under one transform share a single copy — Moana's ground cover emits five
158
+ * million shapes without touching the CTM between them.
159
+ */
160
+ _ctmSnapshot() {
161
+
162
+ if ( this._ctmSource !== this.ctm ) {
163
+
164
+ this._ctmSource = this.ctm;
165
+ this._ctmValue = this.ctm.slice();
166
+
167
+ }
168
+
169
+ return this._ctmValue;
170
+
171
+ }
172
+
95
173
  _peek() {
96
174
 
97
- return this.tokens[ this.pos ];
175
+ return this.stream.peek();
98
176
 
99
177
  }
100
178
 
101
179
  _next() {
102
180
 
103
- return this.tokens[ this.pos ++ ];
181
+ return this.stream.next();
104
182
 
105
183
  }
106
184
 
@@ -178,7 +256,7 @@ export class PBRTParser {
178
256
  const type = decl[ 0 ];
179
257
  const name = decl[ 1 ] !== undefined ? decl[ 1 ] : decl[ 0 ];
180
258
 
181
- const value = this._parseParamValue();
259
+ const value = this._parseParamValue( type );
182
260
  params[ name ] = { type, value };
183
261
 
184
262
  }
@@ -188,27 +266,85 @@ export class PBRTParser {
188
266
  }
189
267
 
190
268
  /** Read a single parameter value: a bracketed array or one bare token. */
191
- _parseParamValue() {
269
+ _parseParamValue( declaredType ) {
192
270
 
193
- const out = [];
271
+ const bracketed = this._peek() && this._peek().type === TokenType.LBRACKET;
272
+ if ( bracketed ) this._next();
194
273
 
195
- if ( this._peek() && this._peek().type === TokenType.LBRACKET ) {
274
+ const more = () => {
196
275
 
197
- this._next(); // [
198
- while ( this._peek() && this._peek().type !== TokenType.RBRACKET ) {
276
+ const t = this._peek();
277
+ return bracketed ? t && t.type !== TokenType.RBRACKET : false;
278
+
279
+ };
199
280
 
200
- out.push( this._coerceValueToken( this._next() ) );
281
+ if ( ! bracketed ) return [ this._coerceValueToken( this._next() ) ];
282
+
283
+ // A long all-number list goes typed; a short one does not. See TYPED_ARRAY_THRESHOLD.
284
+ if ( this._peek() && this._peek().type === TokenType.NUMBER ) {
285
+
286
+ const small = [];
287
+ while ( more() && small.length < TYPED_ARRAY_THRESHOLD ) {
288
+
289
+ const t = this._next();
290
+ if ( t.type !== TokenType.NUMBER ) return this._finishMixedValue( small, small.length, t );
291
+ small.push( t.value );
292
+
293
+ }
294
+
295
+ if ( ! more() ) {
296
+
297
+ this._next(); // ]
298
+ return small;
299
+
300
+ }
301
+
302
+ const Storage = NUMERIC_STORAGE[ declaredType ] || Float64Array;
303
+ let buf = new Storage( TYPED_ARRAY_THRESHOLD * 4 );
304
+ buf.set( small );
305
+ let n = small.length;
306
+
307
+ while ( more() ) {
308
+
309
+ const t = this._next();
310
+ if ( t.type !== TokenType.NUMBER ) return this._finishMixedValue( buf, n, t );
311
+ if ( n === buf.length ) {
312
+
313
+ const grown = new Storage( buf.length * 2 );
314
+ grown.set( buf );
315
+ buf = grown;
316
+
317
+ }
318
+
319
+ buf[ n ++ ] = t.value;
201
320
 
202
321
  }
203
322
 
204
323
  this._next(); // ]
324
+ return n === buf.length ? buf : buf.slice( 0, n );
205
325
 
206
- } else {
326
+ }
327
+
328
+ const out = [];
329
+ while ( more() ) out.push( this._coerceValueToken( this._next() ) );
330
+ this._next(); // ]
331
+ return out;
332
+
333
+ }
334
+
335
+ /** A list that started numeric but turned out to be mixed — finish it as a plain array. */
336
+ _finishMixedValue( buf, n, pending ) {
337
+
338
+ const out = [];
339
+ for ( let i = 0; i < n; i ++ ) out.push( buf[ i ] );
340
+ out.push( this._coerceValueToken( pending ) );
341
+ while ( this._peek() && this._peek().type !== TokenType.RBRACKET ) {
207
342
 
208
343
  out.push( this._coerceValueToken( this._next() ) );
209
344
 
210
345
  }
211
346
 
347
+ this._next(); // ]
212
348
  return out;
213
349
 
214
350
  }
@@ -232,29 +368,28 @@ export class PBRTParser {
232
368
 
233
369
  // ── main directive loop ────────────────────────────────────────
234
370
 
235
- _run( tokens ) {
371
+ async _run( stream ) {
236
372
 
237
- // Save/restore cursor so Include can recurse on a fresh token array.
238
- const savedTokens = this.tokens;
239
- const savedPos = this.pos;
240
- this.tokens = tokens;
241
- this.pos = 0;
373
+ // Save/restore so Include can recurse on a fresh stream.
374
+ const saved = this.stream;
375
+ this.stream = stream;
242
376
 
243
- while ( this.pos < this.tokens.length ) {
377
+ for ( let t = this._next(); t !== null; t = this._next() ) {
244
378
 
245
- const t = this._next();
246
379
  if ( t.type !== TokenType.WORD ) {
247
380
 
248
381
  throw new Error( `PBRT parser: expected directive, got ${t.type} ${t.value ?? ''}` );
249
382
 
250
383
  }
251
384
 
252
- this._directive( t.value );
385
+ // Only Include suspends; every other directive returns undefined and the loop
386
+ // stays synchronous, so an await per token is not paid.
387
+ const pending = this._directive( t.value );
388
+ if ( pending !== undefined ) await pending;
253
389
 
254
390
  }
255
391
 
256
- this.tokens = savedTokens;
257
- this.pos = savedPos;
392
+ this.stream = saved;
258
393
 
259
394
  }
260
395
 
@@ -467,7 +602,7 @@ export class PBRTParser {
467
602
 
468
603
  const type = this._expectString( 'LightSource type' );
469
604
  const params = this._parseParams();
470
- this.ir.lights.push( { type, params, ctm: this.ctm.slice() } );
605
+ this.ir.lights.push( { type, params, ctm: this._ctmSnapshot() } );
471
606
  break;
472
607
 
473
608
  }
@@ -480,7 +615,7 @@ export class PBRTParser {
480
615
  const shape = {
481
616
  type,
482
617
  params,
483
- ctm: this.ctm.slice(),
618
+ ctm: this._ctmSnapshot(),
484
619
  material: this.state.material,
485
620
  areaLight: this.state.areaLight,
486
621
  reverseOrientation: this.state.reverseOrientation
@@ -527,7 +662,7 @@ export class PBRTParser {
527
662
  case 'ObjectInstance': {
528
663
 
529
664
  const objName = this._expectString( 'ObjectInstance name' );
530
- this.ir.instances.push( { name: objName, ctm: this.ctm.slice() } );
665
+ this._addInstance( objName );
531
666
  break;
532
667
 
533
668
  }
@@ -537,8 +672,7 @@ export class PBRTParser {
537
672
  case 'Import': {
538
673
 
539
674
  const path = this._expectString( name );
540
- this._include( path );
541
- break;
675
+ return this._include( path );
542
676
 
543
677
  }
544
678
 
@@ -570,20 +704,32 @@ export class PBRTParser {
570
704
 
571
705
  }
572
706
 
573
- _include( path ) {
707
+ async _include( path ) {
574
708
 
575
- const text = this.resolveInclude( path, this.dirStack[ this.dirStack.length - 1 ] );
576
- if ( text == null ) {
709
+ const dir = this.dirStack[ this.dirStack.length - 1 ];
710
+ const source = await this.resolveInclude( path, dir );
711
+ if ( source == null ) {
577
712
 
578
713
  this._warn( `Include target not found: ${path}` );
579
714
  return;
580
715
 
581
716
  }
582
717
 
583
- const dir = path.includes( '/' ) ? path.slice( 0, path.lastIndexOf( '/' ) ) : '';
584
- this.dirStack.push( dir );
585
- this._run( tokenize( text ) );
586
- this.dirStack.pop();
718
+ const childDir = path.includes( '/' ) ? path.slice( 0, path.lastIndexOf( '/' ) ) : '';
719
+ this.dirStack.push( childDir );
720
+ try {
721
+
722
+ await this._run( new TokenStream( source ) );
723
+
724
+ } finally {
725
+
726
+ this.dirStack.pop();
727
+ // Depth-first, so only the open chain is live. Releasing here is what keeps a
728
+ // multi-gigabyte scene's text from all being resident at once; a file included
729
+ // again is simply resolved again.
730
+ this.releaseInclude?.( path, dir );
731
+
732
+ }
587
733
 
588
734
  }
589
735