rayzee 9.0.0 → 9.1.1

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 (95) hide show
  1. package/README.md +139 -8
  2. package/dist/{NeuralRetouch-CYOJ21Wn.js → NeuralRetouch-BRquqfmZ.js} +1 -1
  3. package/dist/{NeuralSuperRes-CQLCLpcC.js → NeuralSuperRes-CbNvQjCw.js} +3 -2
  4. package/dist/{ToneMapGPU-BVE852iN.js → ToneMapGPU-DpUZDMbQ.js} +2 -1
  5. package/dist/rayzee.es.js +10353 -5337
  6. package/dist/rayzee.umd.js +265 -265
  7. package/package.json +1 -1
  8. package/src/AssetConfig.js +4 -0
  9. package/src/EngineDefaults.js +38 -56
  10. package/src/EngineEvents.js +3 -0
  11. package/src/EngineIssues.js +6 -0
  12. package/src/Headless.js +3 -1
  13. package/src/Passes/AIUpscaler.js +2 -1
  14. package/src/Passes/OIDNDenoiser.js +3 -6
  15. package/src/PathTracerApp.js +290 -19
  16. package/src/Processor/ArchiveCache.js +120 -0
  17. package/src/Processor/ArchiveReader.js +179 -32
  18. package/src/Processor/AssetLoader.js +514 -76
  19. package/src/Processor/AtmosphereModel.js +465 -0
  20. package/src/Processor/BVHBuilder.js +4 -1
  21. package/src/Processor/BVHLeafFold.js +100 -0
  22. package/src/Processor/BVHRefitter.js +98 -214
  23. package/src/Processor/ChunkedRecords.js +189 -4
  24. package/src/Processor/EmissiveTriangleBuilder.js +8 -0
  25. package/src/Processor/GeometryExtractor.js +238 -40
  26. package/src/Processor/InstanceTable.js +55 -37
  27. package/src/Processor/KernelManager.js +4 -1
  28. package/src/Processor/PBRT/PBRTCurves.js +104 -27
  29. package/src/Processor/PBRT/PBRTParser.js +132 -22
  30. package/src/Processor/PBRT/PBRTSceneBuilder.js +192 -49
  31. package/src/Processor/PBRT/buffers.js +27 -0
  32. package/src/Processor/PBRT/index.js +55 -15
  33. package/src/Processor/PackedRayBuffer.js +10 -13
  34. package/src/Processor/ParallelBVHBuilder.js +36 -15
  35. package/src/Processor/PhysicalSky.js +308 -0
  36. package/src/Processor/QueueManager.js +1 -0
  37. package/src/Processor/SceneProcessor.js +1132 -181
  38. package/src/Processor/SplitBLAS.js +249 -0
  39. package/src/Processor/SunPosition.js +41 -0
  40. package/src/Processor/TextureCreator.js +21 -0
  41. package/src/Processor/Workers/AIUpscalerWorker.js +79 -52
  42. package/src/Processor/Workers/BVHWorker.js +17 -13
  43. package/src/Processor/ZipReader.js +207 -0
  44. package/src/RenderSettings.js +38 -0
  45. package/src/SceneState/SceneState.js +367 -0
  46. package/src/SceneState/portable.js +52 -0
  47. package/src/Stages/NormalDepth.js +7 -6
  48. package/src/Stages/PathTracer.js +108 -4
  49. package/src/Stages/PathTracerStage.js +251 -104
  50. package/src/Storage/BLASCache.js +190 -0
  51. package/src/Storage/CDFCache.js +77 -0
  52. package/src/Storage/DownloadCache.js +266 -0
  53. package/src/Storage/GeometrySpill.js +217 -0
  54. package/src/Storage/SceneGraphCodec.js +503 -0
  55. package/src/Storage/SpillStore.js +112 -0
  56. package/src/Storage/StorageManager.js +934 -0
  57. package/src/Storage/StorageOps.js +137 -0
  58. package/src/Storage/StorageWorker.js +37 -0
  59. package/src/Storage/events.js +29 -0
  60. package/src/Storage/identity.js +77 -0
  61. package/src/Storage/index.js +9 -0
  62. package/src/Storage/inlineTransport.js +24 -0
  63. package/src/Storage/locks.js +99 -0
  64. package/src/Storage/openStorage.js +87 -0
  65. package/src/Storage/sceneCachePolicy.js +12 -0
  66. package/src/Storage/shared.js +9 -0
  67. package/src/Storage/transport.js +62 -0
  68. package/src/TSL/Atmosphere.js +684 -0
  69. package/src/TSL/BVHTraversal.js +288 -239
  70. package/src/TSL/Common.js +11 -2
  71. package/src/TSL/Displacement.js +6 -6
  72. package/src/TSL/EmissiveSampling.js +5 -5
  73. package/src/TSL/EnvironmentCDF.js +176 -0
  74. package/src/TSL/GenerateKernel.js +3 -3
  75. package/src/TSL/HitFacet.js +4 -4
  76. package/src/TSL/LightsDirect.js +8 -8
  77. package/src/TSL/LightsSampling.js +55 -37
  78. package/src/TSL/Random.js +1 -0
  79. package/src/TSL/ShadeKernel.js +75 -19
  80. package/src/TSL/Sun.js +40 -0
  81. package/src/TSL/TextureSampling.js +6 -7
  82. package/src/index.js +18 -0
  83. package/src/managers/CameraManager.js +169 -1
  84. package/src/managers/EnvironmentManager.js +263 -33
  85. package/src/managers/GoboManager.js +19 -8
  86. package/src/managers/IESManager.js +3 -3
  87. package/src/managers/LightManager.js +98 -5
  88. package/src/managers/MaterialDataManager.js +22 -2
  89. package/src/managers/TransformManager.js +26 -0
  90. package/src/managers/UniformManager.js +5 -3
  91. package/src/managers/VideoRenderManager.js +3 -1
  92. package/src/managers/timeline/CameraTrack.js +22 -0
  93. package/src/managers/timeline/TimelineManager.js +14 -0
  94. package/src/version.js +3 -0
  95. 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 cps = P.length / 3;
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
- for ( let i = 0; i <= total; i ++ ) {
111
+ return { points, count: total + 1, spans };
78
112
 
79
- const u = ( i / total ) * spans;
80
- let span = Math.floor( u );
81
- if ( span >= spans ) span = spans - 1;
82
- const t = u - span;
113
+ }
83
114
 
84
- if ( basis === 'bspline' ) bsplineWeights( t, w );
85
- else bezierWeights( t, w );
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
- const base = basis === 'bspline' ? span : span * 3;
88
- let x = 0, y = 0, z = 0;
89
- for ( let k = 0; k < 4; k ++ ) {
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
- const o = ( base + k ) * 3;
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
- points[ i * 3 ] = x; points[ i * 3 + 1 ] = y; points[ i * 3 + 2 ] = z;
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 { points, count: total + 1 };
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( count * rings * 3 );
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 = ( i * rings + r ) * 3;
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( ( count - 1 ) * quads * 6 );
295
+ const indices = new Uint32Array( ( rows - 1 ) * quads * 6 );
219
296
  let w = 0;
220
297
 
221
- for ( let i = 0; i < count - 1; 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 : indices.slice( 0, w ) };
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 = growFloat32( list.matrices, length );
170
- if ( list.matricesEnd ) list.matricesEnd = growFloat32( list.matricesEnd, length );
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 : buf.slice( 0, n );
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() {