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.
- package/README.md +164 -0
- package/dist/rayzee.es.js +6728 -4735
- package/dist/rayzee.umd.js +121 -73
- package/package.json +2 -2
- package/src/EngineDefaults.js +168 -13
- package/src/EngineIssues.js +5 -0
- package/src/PathTracerApp.js +151 -25
- package/src/Processor/ArchiveReader.js +538 -0
- package/src/Processor/AssetLoader.js +265 -23
- package/src/Processor/BVHBuilder.js +48 -32
- package/src/Processor/BVHRefitter.js +244 -99
- package/src/Processor/ChunkedRecords.js +302 -0
- package/src/Processor/EmissiveTriangleBuilder.js +48 -21
- package/src/Processor/GeometryExtractor.js +580 -93
- package/src/Processor/HostMemory.js +222 -0
- package/src/Processor/InstanceTable.js +548 -97
- package/src/Processor/PBRT/EqualAreaOctahedral.js +32 -8
- package/src/Processor/PBRT/PBRTCurves.js +249 -0
- package/src/Processor/PBRT/PBRTMath.js +19 -0
- package/src/Processor/PBRT/PBRTParser.js +187 -41
- package/src/Processor/PBRT/PBRTSceneBuilder.js +586 -42
- package/src/Processor/PBRT/PBRTTokenizer.js +205 -65
- package/src/Processor/PBRT/index.js +257 -50
- package/src/Processor/PackedRayBuffer.js +29 -18
- package/src/Processor/ParallelBVHBuilder.js +9 -8
- package/src/Processor/QueueManager.js +5 -3
- package/src/Processor/SceneProcessor.js +1046 -365
- package/src/Processor/TLASBuilder.js +546 -201
- package/src/Processor/TextureCreator.js +11 -9
- package/src/Processor/VRAMTracker.js +12 -3
- package/src/Processor/Workers/BVHRefitWorker.js +16 -11
- package/src/Processor/Workers/BVHWorker.js +12 -12
- package/src/Processor/Workers/TLASWorker.js +37 -0
- package/src/Processor/utils.js +8 -20
- package/src/Stages/NormalDepth.js +6 -2
- package/src/Stages/PathTracer.js +5 -5
- package/src/Stages/PathTracerStage.js +163 -41
- package/src/TSL/BVHTraversal.js +208 -90
- package/src/TSL/Common.js +135 -2
- package/src/TSL/Displacement.js +21 -11
- package/src/TSL/EmissiveSampling.js +49 -25
- package/src/TSL/ExtendKernel.js +3 -2
- package/src/TSL/LightBVHSampling.js +7 -4
- package/src/TSL/LightsDirect.js +30 -29
- package/src/TSL/ShadeKernel.js +8 -6
- package/src/TSL/SortGlobalKernels.js +3 -4
- package/src/TSL/Struct.js +3 -0
- package/src/TSL/TextureSampling.js +24 -9
- package/src/TSL/patches.js +149 -84
- package/src/index.js +3 -0
- package/src/managers/AnimationManager.js +86 -76
- package/src/managers/MaterialDataManager.js +29 -23
- package/src/managers/TransformManager.js +9 -192
|
@@ -23,9 +23,26 @@
|
|
|
23
23
|
* }
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import {
|
|
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
|
-
|
|
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.
|
|
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
|
|
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(
|
|
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.
|
|
175
|
+
return this.stream.peek();
|
|
98
176
|
|
|
99
177
|
}
|
|
100
178
|
|
|
101
179
|
_next() {
|
|
102
180
|
|
|
103
|
-
return this.
|
|
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
|
|
271
|
+
const bracketed = this._peek() && this._peek().type === TokenType.LBRACKET;
|
|
272
|
+
if ( bracketed ) this._next();
|
|
194
273
|
|
|
195
|
-
|
|
274
|
+
const more = () => {
|
|
196
275
|
|
|
197
|
-
this.
|
|
198
|
-
|
|
276
|
+
const t = this._peek();
|
|
277
|
+
return bracketed ? t && t.type !== TokenType.RBRACKET : false;
|
|
278
|
+
|
|
279
|
+
};
|
|
199
280
|
|
|
200
|
-
|
|
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
|
-
}
|
|
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(
|
|
371
|
+
async _run( stream ) {
|
|
236
372
|
|
|
237
|
-
// Save/restore
|
|
238
|
-
const
|
|
239
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
576
|
-
|
|
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
|
|
584
|
-
this.dirStack.push(
|
|
585
|
-
|
|
586
|
-
|
|
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
|
|