rayzee 8.8.2 → 9.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 +210 -20
- package/dist/{NeuralRetouch-CYOJ21Wn.js → NeuralRetouch-BRquqfmZ.js} +1 -1
- package/dist/{NeuralSuperRes-Dc6jRYke.js → NeuralSuperRes-CbNvQjCw.js} +5 -2
- package/dist/{ToneMapGPU-BVE852iN.js → ToneMapGPU-DpUZDMbQ.js} +2 -1
- package/dist/rayzee.es.js +10795 -5324
- package/dist/rayzee.umd.js +279 -268
- package/package.json +1 -1
- package/src/AssetConfig.js +4 -0
- package/src/EngineDefaults.js +60 -76
- package/src/EngineEvents.js +7 -0
- package/src/EngineIssues.js +6 -0
- package/src/Headless.js +3 -1
- package/src/Passes/AIUpscaler.js +2 -1
- package/src/Passes/OIDNDenoiser.js +3 -6
- package/src/Passes/OIDNTemporalHistory.js +25 -7
- package/src/PathTracerApp.js +313 -25
- package/src/Processor/ArchiveCache.js +120 -0
- package/src/Processor/ArchiveReader.js +179 -32
- package/src/Processor/AssetLoader.js +523 -76
- package/src/Processor/AtmosphereModel.js +465 -0
- package/src/Processor/BVHBuilder.js +4 -1
- package/src/Processor/BVHLeafFold.js +100 -0
- package/src/Processor/BVHRefitter.js +98 -214
- package/src/Processor/ChunkedRecords.js +189 -4
- package/src/Processor/EmissiveTriangleBuilder.js +8 -0
- package/src/Processor/GeometryExtractor.js +238 -40
- package/src/Processor/InstanceTable.js +55 -37
- package/src/Processor/PBRT/PBRTCurves.js +104 -27
- package/src/Processor/PBRT/PBRTParser.js +132 -22
- package/src/Processor/PBRT/PBRTSceneBuilder.js +210 -55
- package/src/Processor/PBRT/buffers.js +27 -0
- package/src/Processor/PBRT/index.js +55 -15
- package/src/Processor/PackedRayBuffer.js +10 -13
- package/src/Processor/ParallelBVHBuilder.js +36 -15
- package/src/Processor/PhysicalSky.js +308 -0
- package/src/Processor/QueueManager.js +1 -0
- package/src/Processor/SceneProcessor.js +1132 -181
- package/src/Processor/SplitBLAS.js +249 -0
- package/src/Processor/SunPosition.js +41 -0
- package/src/Processor/TextureCreator.js +21 -0
- package/src/Processor/Workers/AIUpscalerWorker.js +79 -52
- package/src/Processor/Workers/BVHWorker.js +17 -13
- package/src/Processor/ZipReader.js +207 -0
- package/src/RenderSettings.js +49 -8
- package/src/SceneState/SceneState.js +367 -0
- package/src/SceneState/portable.js +52 -0
- package/src/Stages/MotionVector.js +8 -42
- package/src/Stages/NRD.js +30 -21
- package/src/Stages/NormalDepth.js +12 -23
- package/src/Stages/PathTracer.js +114 -6
- package/src/Stages/PathTracerStage.js +251 -104
- package/src/Storage/BLASCache.js +190 -0
- package/src/Storage/CDFCache.js +77 -0
- package/src/Storage/DownloadCache.js +266 -0
- package/src/Storage/GeometrySpill.js +217 -0
- package/src/Storage/SceneGraphCodec.js +503 -0
- package/src/Storage/SpillStore.js +112 -0
- package/src/Storage/StorageManager.js +934 -0
- package/src/Storage/StorageOps.js +137 -0
- package/src/Storage/StorageWorker.js +37 -0
- package/src/Storage/events.js +29 -0
- package/src/Storage/identity.js +77 -0
- package/src/Storage/index.js +9 -0
- package/src/Storage/inlineTransport.js +24 -0
- package/src/Storage/locks.js +99 -0
- package/src/Storage/openStorage.js +87 -0
- package/src/Storage/sceneCachePolicy.js +12 -0
- package/src/Storage/shared.js +9 -0
- package/src/Storage/transport.js +62 -0
- package/src/TSL/Atmosphere.js +684 -0
- package/src/TSL/BVHTraversal.js +288 -239
- package/src/TSL/CameraRay.js +99 -14
- package/src/TSL/Common.js +11 -2
- package/src/TSL/DebugKernel.js +2 -2
- package/src/TSL/Displacement.js +6 -6
- package/src/TSL/EmissiveSampling.js +5 -5
- package/src/TSL/EnvironmentCDF.js +176 -0
- package/src/TSL/GenerateKernel.js +5 -5
- package/src/TSL/HitFacet.js +4 -4
- package/src/TSL/LightsDirect.js +8 -8
- package/src/TSL/LightsSampling.js +55 -37
- package/src/TSL/Random.js +1 -0
- package/src/TSL/ShadeKernel.js +75 -19
- package/src/TSL/Sun.js +40 -0
- package/src/TSL/TextureSampling.js +6 -7
- package/src/index.js +22 -0
- package/src/managers/CameraManager.js +412 -56
- package/src/managers/DenoisingManager.js +4 -3
- package/src/managers/EnvironmentManager.js +263 -33
- package/src/managers/GoboManager.js +19 -8
- package/src/managers/IESManager.js +3 -3
- package/src/managers/InteractionManager.js +30 -2
- package/src/managers/LightManager.js +98 -5
- package/src/managers/MaterialDataManager.js +22 -2
- package/src/managers/TransformManager.js +26 -0
- package/src/managers/UniformManager.js +11 -7
- package/src/managers/VideoRenderManager.js +38 -17
- package/src/managers/ViewCamera.js +77 -0
- package/src/managers/WalkControls.js +262 -0
- package/src/managers/helpers/OutlineHelper.js +22 -2
- package/src/managers/timeline/CameraTrack.js +235 -0
- package/src/managers/timeline/TimelineManager.js +146 -0
- package/src/version.js +3 -0
- package/src/Processor/ProceduralSky.js +0 -291
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* scene is 20 million triangles or 200 million.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { resized } from './buffers.js';
|
|
15
|
+
|
|
14
16
|
const DEFAULT_STEPS = 2;
|
|
15
17
|
|
|
16
18
|
/** Uniform cubic B-spline basis at t for the four control points of one span. */
|
|
@@ -58,48 +60,111 @@ function cross( a, b, out ) {
|
|
|
58
60
|
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
function spanCount( P, basis ) {
|
|
64
|
+
|
|
65
|
+
const cps = P.length / 3;
|
|
66
|
+
// bspline: every window of 4 control points is one span. bezier: 4 points per span,
|
|
67
|
+
// sharing an endpoint between consecutive spans.
|
|
68
|
+
return basis === 'bspline' ? cps - 3 : Math.max( 1, ( cps - 1 ) / 3 );
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The centreline at spline parameter u in [0, spans], written to out[o..o+2]. */
|
|
73
|
+
function pointAt( P, basis, spans, u, w, out, o ) {
|
|
74
|
+
|
|
75
|
+
let span = Math.floor( u );
|
|
76
|
+
if ( span >= spans ) span = spans - 1;
|
|
77
|
+
const t = u - span;
|
|
78
|
+
|
|
79
|
+
if ( basis === 'bspline' ) bsplineWeights( t, w );
|
|
80
|
+
else bezierWeights( t, w );
|
|
81
|
+
|
|
82
|
+
const base = basis === 'bspline' ? span : span * 3;
|
|
83
|
+
let x = 0, y = 0, z = 0;
|
|
84
|
+
for ( let k = 0; k < 4; k ++ ) {
|
|
85
|
+
|
|
86
|
+
const c = ( base + k ) * 3;
|
|
87
|
+
x += w[ k ] * P[ c ];
|
|
88
|
+
y += w[ k ] * P[ c + 1 ];
|
|
89
|
+
z += w[ k ] * P[ c + 2 ];
|
|
90
|
+
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
out[ o ] = x; out[ o + 1 ] = y; out[ o + 2 ] = z;
|
|
94
|
+
|
|
95
|
+
}
|
|
96
|
+
|
|
61
97
|
/**
|
|
62
98
|
* Sample a curve's centreline.
|
|
63
99
|
* @returns {{points: Float64Array, count: number}} count points, 3 floats each
|
|
64
100
|
*/
|
|
65
101
|
function sampleCenterline( P, basis, steps ) {
|
|
66
102
|
|
|
67
|
-
const
|
|
68
|
-
// bspline: every window of 4 control points is one span. bezier: 4 points per span,
|
|
69
|
-
// sharing an endpoint between consecutive spans.
|
|
70
|
-
const spans = basis === 'bspline' ? cps - 3 : Math.max( 1, ( cps - 1 ) / 3 );
|
|
103
|
+
const spans = spanCount( P, basis );
|
|
71
104
|
if ( spans < 1 ) return null;
|
|
72
105
|
|
|
73
106
|
const total = Math.max( 1, Math.round( steps * spans ) );
|
|
74
107
|
const points = new Float64Array( ( total + 1 ) * 3 );
|
|
75
108
|
const w = [ 0, 0, 0, 0 ];
|
|
109
|
+
for ( let i = 0; i <= total; i ++ ) pointAt( P, basis, spans, ( i / total ) * spans, w, points, i * 3 );
|
|
76
110
|
|
|
77
|
-
|
|
111
|
+
return { points, count: total + 1, spans };
|
|
78
112
|
|
|
79
|
-
|
|
80
|
-
let span = Math.floor( u );
|
|
81
|
-
if ( span >= spans ) span = spans - 1;
|
|
82
|
-
const t = u - span;
|
|
113
|
+
}
|
|
83
114
|
|
|
84
|
-
|
|
85
|
-
|
|
115
|
+
/** Distance from point a (in pa) to the segment from b to c (in pb). */
|
|
116
|
+
function segmentDistance( pa, a, pb, b, c ) {
|
|
86
117
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
118
|
+
const ax = pa[ a * 3 ], ay = pa[ a * 3 + 1 ], az = pa[ a * 3 + 2 ];
|
|
119
|
+
const bx = pb[ b * 3 ], by = pb[ b * 3 + 1 ], bz = pb[ b * 3 + 2 ];
|
|
120
|
+
const dx = pb[ c * 3 ] - bx, dy = pb[ c * 3 + 1 ] - by, dz = pb[ c * 3 + 2 ] - bz;
|
|
121
|
+
const len2 = dx * dx + dy * dy + dz * dz;
|
|
122
|
+
let t = len2 > 0 ? ( ( ax - bx ) * dx + ( ay - by ) * dy + ( az - bz ) * dz ) / len2 : 0;
|
|
123
|
+
t = t < 0 ? 0 : t > 1 ? 1 : t;
|
|
124
|
+
return Math.hypot( ax - bx - t * dx, ay - by - t * dy, az - bz - t * dz );
|
|
90
125
|
|
|
91
|
-
|
|
92
|
-
x += w[ k ] * P[ o ];
|
|
93
|
-
y += w[ k ] * P[ o + 1 ];
|
|
94
|
-
z += w[ k ] * P[ o + 2 ];
|
|
126
|
+
}
|
|
95
127
|
|
|
96
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Which uniform samples an adaptive strip keeps. A segment may stray by `tolerance` of the
|
|
130
|
+
* half-width, or by the uniform strip's own worst error on this curve if that is larger.
|
|
131
|
+
* @returns {Int32Array|null} kept sample indices, or null when every sample is kept
|
|
132
|
+
*/
|
|
133
|
+
function adaptiveSamples( P, basis, line, tolerance, width0, width1 ) {
|
|
134
|
+
|
|
135
|
+
const { points, count, spans } = line;
|
|
136
|
+
if ( count < 3 ) return null;
|
|
97
137
|
|
|
98
|
-
|
|
138
|
+
const mids = new Float64Array( ( count - 1 ) * 3 );
|
|
139
|
+
const w = [ 0, 0, 0, 0 ];
|
|
140
|
+
const total = count - 1;
|
|
141
|
+
for ( let k = 0; k < total; k ++ ) pointAt( P, basis, spans, ( ( k + 0.5 ) / total ) * spans, w, mids, k * 3 );
|
|
142
|
+
|
|
143
|
+
let worst = 0;
|
|
144
|
+
for ( let k = 0; k < total; k ++ ) worst = Math.max( worst, segmentDistance( mids, k, points, k, k + 1 ) );
|
|
145
|
+
|
|
146
|
+
const allowed = x => Math.max( worst, tolerance * 0.5 * ( width0 + ( width1 - width0 ) * ( x / total ) ) );
|
|
147
|
+
const fits = ( i, j ) => {
|
|
148
|
+
|
|
149
|
+
for ( let k = i + 1; k < j; k ++ ) if ( segmentDistance( points, k, points, i, j ) > allowed( k ) ) return false;
|
|
150
|
+
for ( let k = i; k < j; k ++ ) if ( segmentDistance( mids, k, points, i, j ) > allowed( k + 0.5 ) ) return false;
|
|
151
|
+
return true;
|
|
152
|
+
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const kept = new Int32Array( count );
|
|
156
|
+
let n = 0;
|
|
157
|
+
kept[ n ++ ] = 0;
|
|
158
|
+
for ( let i = 0; i < total; ) {
|
|
159
|
+
|
|
160
|
+
let j = i + 1;
|
|
161
|
+
while ( j < total && fits( i, j + 1 ) ) j ++;
|
|
162
|
+
kept[ n ++ ] = j;
|
|
163
|
+
i = j;
|
|
99
164
|
|
|
100
165
|
}
|
|
101
166
|
|
|
102
|
-
return
|
|
167
|
+
return n === count ? null : kept.subarray( 0, n );
|
|
103
168
|
|
|
104
169
|
}
|
|
105
170
|
|
|
@@ -112,6 +177,9 @@ function sampleCenterline( P, basis, steps ) {
|
|
|
112
177
|
* @param {ArrayLike<number>} [spec.N] - ribbon normals (start, end), 3 floats each
|
|
113
178
|
* @param {number} [spec.steps] - samples per spline span
|
|
114
179
|
* @param {number} [spec.sides=1] - 1 ribbon, 2 crossed ribbons, >=3 closed tube
|
|
180
|
+
* @param {number} [spec.tolerance=0] - how far a segment may stray from the curve, as a fraction
|
|
181
|
+
* of the strip's half-width; `steps` then caps the resolution rather than fixing it. 0 keeps
|
|
182
|
+
* every sample.
|
|
115
183
|
* @returns {{positions: Float32Array, indices: Uint32Array}|null}
|
|
116
184
|
*/
|
|
117
185
|
export function tessellateCurve( spec ) {
|
|
@@ -126,15 +194,20 @@ export function tessellateCurve( spec ) {
|
|
|
126
194
|
if ( ! line ) return null;
|
|
127
195
|
|
|
128
196
|
const { points, count } = line;
|
|
197
|
+
const kept = spec.tolerance > 0 ? adaptiveSamples( P, basis, line, spec.tolerance, width0, width1 ) : null;
|
|
198
|
+
const rows = kept ? kept.length : count;
|
|
129
199
|
const rings = sides >= 3 ? sides : sides * 2;
|
|
130
|
-
const positions = new Float32Array(
|
|
200
|
+
const positions = new Float32Array( rows * rings * 3 );
|
|
131
201
|
|
|
132
202
|
const tangent = [ 0, 0, 0 ];
|
|
133
203
|
const side = [ 0, 0, 0 ];
|
|
134
204
|
const up = [ 0, 0, 0 ];
|
|
135
205
|
const tmp = [ 0, 0, 0 ];
|
|
136
206
|
let haveSide = false;
|
|
207
|
+
let row = 0;
|
|
137
208
|
|
|
209
|
+
// The frame is carried through every sample, kept or not, so a kept ring faces exactly
|
|
210
|
+
// as it does in the uniform strip.
|
|
138
211
|
for ( let i = 0; i < count; i ++ ) {
|
|
139
212
|
|
|
140
213
|
const a = Math.max( 0, i - 1 ), b = Math.min( count - 1, i + 1 );
|
|
@@ -178,6 +251,8 @@ export function tessellateCurve( spec ) {
|
|
|
178
251
|
|
|
179
252
|
}
|
|
180
253
|
|
|
254
|
+
if ( kept && kept[ row ] !== i ) continue;
|
|
255
|
+
|
|
181
256
|
cross( tangent, side, up );
|
|
182
257
|
normalize( up );
|
|
183
258
|
|
|
@@ -205,20 +280,22 @@ export function tessellateCurve( spec ) {
|
|
|
205
280
|
|
|
206
281
|
}
|
|
207
282
|
|
|
208
|
-
const o = (
|
|
283
|
+
const o = ( row * rings + r ) * 3;
|
|
209
284
|
positions[ o ] = cx + dx * radius;
|
|
210
285
|
positions[ o + 1 ] = cy + dy * radius;
|
|
211
286
|
positions[ o + 2 ] = cz + dz * radius;
|
|
212
287
|
|
|
213
288
|
}
|
|
214
289
|
|
|
290
|
+
row ++;
|
|
291
|
+
|
|
215
292
|
}
|
|
216
293
|
|
|
217
294
|
const quads = sides >= 3 ? sides : sides;
|
|
218
|
-
const indices = new Uint32Array( (
|
|
295
|
+
const indices = new Uint32Array( ( rows - 1 ) * quads * 6 );
|
|
219
296
|
let w = 0;
|
|
220
297
|
|
|
221
|
-
for ( let i = 0; i <
|
|
298
|
+
for ( let i = 0; i < rows - 1; i ++ ) {
|
|
222
299
|
|
|
223
300
|
for ( let q = 0; q < quads; q ++ ) {
|
|
224
301
|
|
|
@@ -233,11 +310,11 @@ export function tessellateCurve( spec ) {
|
|
|
233
310
|
|
|
234
311
|
}
|
|
235
312
|
|
|
236
|
-
return { positions, indices: w === indices.length ? indices :
|
|
313
|
+
return { positions, indices: w === indices.length ? indices : resized( indices, w ) };
|
|
237
314
|
|
|
238
315
|
}
|
|
239
316
|
|
|
240
|
-
/** Triangles `tessellateCurve` would produce, without building anything. */
|
|
317
|
+
/** Triangles a uniform `tessellateCurve` would produce (an adaptive one's upper bound), without building anything. */
|
|
241
318
|
export function curveTriangleCount( controlPoints, basis, steps, sides ) {
|
|
242
319
|
|
|
243
320
|
const cps = controlPoints / 3;
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* recorded as `ctmEnd` / `cameraToWorldEnd` / `matricesEnd` only where it differs.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
+
import { resized } from './buffers.js';
|
|
31
32
|
import { TokenStream, TokenType } from './PBRTTokenizer.js';
|
|
32
33
|
import * as M from './PBRTMath.js';
|
|
33
34
|
|
|
@@ -39,6 +40,15 @@ const TYPED_ARRAY_THRESHOLD = 8;
|
|
|
39
40
|
|
|
40
41
|
const START = 1, END = 2, BOTH = 3;
|
|
41
42
|
|
|
43
|
+
// Directives whose effect outlives the file that holds them, or that tie its shapes to
|
|
44
|
+
// something other than the transform it was included under. A file containing none of them
|
|
45
|
+
// builds the same shapes wherever it is included, so a repeat is placed rather than re-read.
|
|
46
|
+
const INCLUDE_SIDE_EFFECTS = new Set( [
|
|
47
|
+
'Identity', 'Transform', 'CoordinateSystem', 'CoordSysTransform', 'ActiveTransform', 'TransformTimes',
|
|
48
|
+
'Camera', 'Film', 'WorldBegin', 'MakeNamedMaterial', 'Texture', 'MakeNamedMedium',
|
|
49
|
+
'LightSource', 'AreaLightSource', 'ObjectBegin', 'ObjectEnd', 'ObjectInstance'
|
|
50
|
+
] );
|
|
51
|
+
|
|
42
52
|
function sameMatrix( a, b ) {
|
|
43
53
|
|
|
44
54
|
if ( a === b ) return true;
|
|
@@ -58,20 +68,14 @@ const NUMERIC_STORAGE = {
|
|
|
58
68
|
rgb: Float32Array
|
|
59
69
|
};
|
|
60
70
|
|
|
61
|
-
function growFloat32( array, length ) {
|
|
62
|
-
|
|
63
|
-
const grown = new Float32Array( length );
|
|
64
|
-
grown.set( array );
|
|
65
|
-
return grown;
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
69
71
|
export class PBRTParser {
|
|
70
72
|
|
|
71
73
|
/**
|
|
72
74
|
* @param {object} [opts]
|
|
73
75
|
* @param {(path:string)=>string} [opts.resolveInclude] - returns the text of
|
|
74
76
|
* an Include/Import target, resolved relative to the current file.
|
|
77
|
+
* @param {boolean} [opts.instanceIncludes=true] - a file included again under the same
|
|
78
|
+
* material becomes placements of one template instead of a second copy.
|
|
75
79
|
*/
|
|
76
80
|
constructor( opts = {} ) {
|
|
77
81
|
|
|
@@ -85,7 +89,12 @@ export class PBRTParser {
|
|
|
85
89
|
// Placements past this are counted and dropped as they are read, not after: the peak
|
|
86
90
|
// is the parse itself, so a limit applied later saves nothing.
|
|
87
91
|
this.maxPlacements = opts.maxPlacements ?? Infinity;
|
|
88
|
-
|
|
92
|
+
this.instanceIncludes = opts.instanceIncludes !== false;
|
|
93
|
+
this._includes = new Map();
|
|
94
|
+
this._stateIds = new WeakMap();
|
|
95
|
+
this._nextStateId = 1;
|
|
96
|
+
this._effects = 0;
|
|
97
|
+
this._movedShapes = false;
|
|
89
98
|
|
|
90
99
|
// IR accumulators
|
|
91
100
|
this.ir = {
|
|
@@ -143,6 +152,15 @@ export class PBRTParser {
|
|
|
143
152
|
|
|
144
153
|
this.dirStack = [ baseDir ];
|
|
145
154
|
await this._run( new TokenStream( src ) );
|
|
155
|
+
if ( this._movedShapes ) this.ir.shapes = this.ir.shapes.filter( Boolean );
|
|
156
|
+
// Grown by doubling: up to half of every list is spare, 240 MB at 9M placements.
|
|
157
|
+
for ( const list of this.ir.instances.values() ) {
|
|
158
|
+
|
|
159
|
+
list.matrices = resized( list.matrices, list.count * 16 );
|
|
160
|
+
if ( list.matricesEnd ) list.matricesEnd = resized( list.matricesEnd, list.count * 16 );
|
|
161
|
+
|
|
162
|
+
}
|
|
163
|
+
|
|
146
164
|
return this.ir;
|
|
147
165
|
|
|
148
166
|
}
|
|
@@ -150,7 +168,7 @@ export class PBRTParser {
|
|
|
150
168
|
// ── token helpers ──────────────────────────────────────────────
|
|
151
169
|
|
|
152
170
|
/** Record one placement, copying the CTM straight into the template's matrix buffer. */
|
|
153
|
-
_addInstance( name ) {
|
|
171
|
+
_addInstance( name, m = this.ctm ) {
|
|
154
172
|
|
|
155
173
|
if ( this.ir.instanceCount >= this.maxPlacements ) {
|
|
156
174
|
|
|
@@ -166,13 +184,12 @@ export class PBRTParser {
|
|
|
166
184
|
if ( need > list.matrices.length ) {
|
|
167
185
|
|
|
168
186
|
const length = Math.max( need, list.matrices.length * 2 );
|
|
169
|
-
list.matrices =
|
|
170
|
-
if ( list.matricesEnd ) list.matricesEnd =
|
|
187
|
+
list.matrices = resized( list.matrices, length );
|
|
188
|
+
if ( list.matricesEnd ) list.matricesEnd = resized( list.matricesEnd, length );
|
|
171
189
|
|
|
172
190
|
}
|
|
173
191
|
|
|
174
192
|
const o = list.count * 16;
|
|
175
|
-
const m = this.ctm;
|
|
176
193
|
for ( let i = 0; i < 16; i ++ ) list.matrices[ o + i ] = m[ i ];
|
|
177
194
|
|
|
178
195
|
// Allocated once a placement of this template moves.
|
|
@@ -412,20 +429,14 @@ export class PBRTParser {
|
|
|
412
429
|
|
|
413
430
|
const t = this._next();
|
|
414
431
|
if ( t.type !== TokenType.NUMBER ) return this._finishMixedValue( buf, n, t );
|
|
415
|
-
if ( n === buf.length )
|
|
416
|
-
|
|
417
|
-
const grown = new Storage( buf.length * 2 );
|
|
418
|
-
grown.set( buf );
|
|
419
|
-
buf = grown;
|
|
420
|
-
|
|
421
|
-
}
|
|
432
|
+
if ( n === buf.length ) buf = resized( buf, buf.length * 2 );
|
|
422
433
|
|
|
423
434
|
buf[ n ++ ] = t.value;
|
|
424
435
|
|
|
425
436
|
}
|
|
426
437
|
|
|
427
438
|
this._next(); // ]
|
|
428
|
-
return n === buf.length ? buf :
|
|
439
|
+
return n === buf.length ? buf : resized( buf, n );
|
|
429
440
|
|
|
430
441
|
}
|
|
431
442
|
|
|
@@ -486,6 +497,8 @@ export class PBRTParser {
|
|
|
486
497
|
|
|
487
498
|
}
|
|
488
499
|
|
|
500
|
+
if ( INCLUDE_SIDE_EFFECTS.has( t.value ) ) this._effects ++;
|
|
501
|
+
|
|
489
502
|
// Only Include suspends; every other directive returns undefined and the loop
|
|
490
503
|
// stays synchronous, so an await per token is not paid.
|
|
491
504
|
const pending = this._directive( t.value );
|
|
@@ -826,6 +839,9 @@ export class PBRTParser {
|
|
|
826
839
|
async _include( path ) {
|
|
827
840
|
|
|
828
841
|
const dir = this.dirStack[ this.dirStack.length - 1 ];
|
|
842
|
+
const key = this._includeKey( path, dir );
|
|
843
|
+
if ( key !== null && this._placeRepeat( key, path ) ) return;
|
|
844
|
+
|
|
829
845
|
const source = await this.resolveInclude( path, dir );
|
|
830
846
|
if ( source == null ) {
|
|
831
847
|
|
|
@@ -834,6 +850,7 @@ export class PBRTParser {
|
|
|
834
850
|
|
|
835
851
|
}
|
|
836
852
|
|
|
853
|
+
const before = key === null ? null : this._includeState();
|
|
837
854
|
const childDir = path.includes( '/' ) ? path.slice( 0, path.lastIndexOf( '/' ) ) : '';
|
|
838
855
|
this.dirStack.push( childDir );
|
|
839
856
|
try {
|
|
@@ -846,10 +863,103 @@ export class PBRTParser {
|
|
|
846
863
|
// Depth-first, so only the open chain is live. Releasing here is what keeps a
|
|
847
864
|
// multi-gigabyte scene's text from all being resident at once; a file included
|
|
848
865
|
// again is simply resolved again.
|
|
849
|
-
this.releaseInclude?.( path, dir );
|
|
866
|
+
this.releaseInclude?.( path, dir, source );
|
|
867
|
+
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
if ( before && this._sameIncludeState( before ) && ! this._includes.has( key ) ) {
|
|
871
|
+
|
|
872
|
+
this._includes.set( key, { start: before.shapes, end: this.ir.shapes.length, ctm: before.ctm.slice(), template: null } );
|
|
873
|
+
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/** Null when this include cannot be reused: inside an object, moving, or emitting light. */
|
|
879
|
+
_includeKey( path, dir ) {
|
|
880
|
+
|
|
881
|
+
if ( ! this.instanceIncludes || this.currentObject !== null ) return null;
|
|
882
|
+
if ( this.ctmEnd !== null || this.activeTransform !== BOTH || this.state.areaLight ) return null;
|
|
883
|
+
return `${dir}\0${path}\0${this._stateId( this.state.material )}\0${this.state.reverseOrientation}`;
|
|
884
|
+
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
_stateId( object ) {
|
|
888
|
+
|
|
889
|
+
if ( ! object ) return 0;
|
|
890
|
+
let id = this._stateIds.get( object );
|
|
891
|
+
if ( id === undefined ) this._stateIds.set( object, id = this._nextStateId ++ );
|
|
892
|
+
return id;
|
|
893
|
+
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
_includeState() {
|
|
897
|
+
|
|
898
|
+
return {
|
|
899
|
+
shapes: this.ir.shapes.length,
|
|
900
|
+
effects: this._effects,
|
|
901
|
+
ctm: this.ctm,
|
|
902
|
+
attributes: this.attributeStack.length,
|
|
903
|
+
transforms: this.transformStack.length,
|
|
904
|
+
material: this.state.material,
|
|
905
|
+
reverseOrientation: this.state.reverseOrientation
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
_sameIncludeState( before ) {
|
|
911
|
+
|
|
912
|
+
return this._effects === before.effects && this.ctmEnd === null && this.activeTransform === BOTH &&
|
|
913
|
+
sameMatrix( this.ctm, before.ctm ) && this.attributeStack.length === before.attributes &&
|
|
914
|
+
this.transformStack.length === before.transforms && this.state.material === before.material &&
|
|
915
|
+
this.state.areaLight === null && this.state.reverseOrientation === before.reverseOrientation;
|
|
916
|
+
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Place a repeated include as another instance of the shapes its first reading made,
|
|
921
|
+
* turning those into a template the first time. False when they can no longer be moved.
|
|
922
|
+
*/
|
|
923
|
+
_placeRepeat( key, path ) {
|
|
924
|
+
|
|
925
|
+
const record = this._includes.get( key );
|
|
926
|
+
if ( ! record ) return false;
|
|
927
|
+
|
|
928
|
+
if ( record.template === null ) {
|
|
929
|
+
|
|
930
|
+
const { start, end } = record;
|
|
931
|
+
if ( end === start || this.ir.instanceCount + 2 > this.maxPlacements ) return false;
|
|
932
|
+
for ( let k = start; k < end; k ++ ) if ( ! this.ir.shapes[ k ] ) {
|
|
933
|
+
|
|
934
|
+
this._includes.delete( key );
|
|
935
|
+
return false;
|
|
936
|
+
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
const name = `${path}#${this.ir.objects.size}`;
|
|
940
|
+
const inverse = M.invert( record.ctm );
|
|
941
|
+
const template = [];
|
|
942
|
+
let ctm = null, relative = null;
|
|
943
|
+
for ( let k = start; k < end; k ++ ) {
|
|
944
|
+
|
|
945
|
+
const shape = this.ir.shapes[ k ];
|
|
946
|
+
if ( shape.ctm !== ctm ) relative = M.multiply( inverse, ctm = shape.ctm );
|
|
947
|
+
shape.relativeCTM = relative;
|
|
948
|
+
template.push( shape );
|
|
949
|
+
this.ir.shapes[ k ] = null;
|
|
950
|
+
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
this.ir.objects.set( name, template );
|
|
954
|
+
this._addInstance( name, record.ctm );
|
|
955
|
+
record.template = name;
|
|
956
|
+
this._movedShapes = true;
|
|
850
957
|
|
|
851
958
|
}
|
|
852
959
|
|
|
960
|
+
this._addInstance( record.template );
|
|
961
|
+
return true;
|
|
962
|
+
|
|
853
963
|
}
|
|
854
964
|
|
|
855
965
|
_skipTypeAndParams() {
|