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
@@ -11,19 +11,155 @@
11
11
  // Inline copy of layout constants (source of truth: EngineDefaults.js).
12
12
  // Cannot import because this runs inside Web Workers where window is not defined.
13
13
  const TRIANGLE_DATA_LAYOUT = {
14
- FLOATS_PER_TRIANGLE: 32,
14
+ FLOATS_PER_TRIANGLE: 20,
15
15
  POSITION_A_OFFSET: 0,
16
16
  POSITION_B_OFFSET: 4,
17
17
  POSITION_C_OFFSET: 8,
18
- NORMAL_A_OFFSET: 12,
19
- NORMAL_B_OFFSET: 16,
20
- NORMAL_C_OFFSET: 20,
18
+ NORMAL_A_PACKED_OFFSET: 3,
19
+ NORMAL_B_PACKED_OFFSET: 7,
20
+ NORMAL_C_PACKED_OFFSET: 11,
21
21
  };
22
22
 
23
23
  const FPT = TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE;
24
+
25
+ // The record buffer is uint; positions are f32 in it. Views share the memory, no copy.
26
+ const floatView = ( data ) => data instanceof Float32Array
27
+ ? data : new Float32Array( data.buffer, data.byteOffset, data.length );
28
+ const uintView = ( data ) => data instanceof Uint32Array
29
+ ? data : new Uint32Array( data.buffer, data.byteOffset, data.length );
30
+
31
+ // Triangles arrive either as one flat record array or, past the ~2 GB array cap, as a
32
+ // ChunkedRecords. Both are handled by resolving the chunk once per triangle; the flat case
33
+ // keeps a constant chunk and plain `index * FPT` arithmetic.
34
+ // BVH nodes get the same treatment: one flat Float32Array, or a ChunkedRecords past the cap.
35
+ const nodeAccess = ( bvhData ) => {
36
+
37
+ const chunked = bvhData && bvhData.chunks ? bvhData : null;
38
+ return {
39
+ chunked,
40
+ f: chunked ? bvhData : bvhData,
41
+ idx: chunked ? bvhData.viewAs( Uint32Array ) : uintView( bvhData ),
42
+ };
43
+
44
+ };
45
+
46
+ // Resolve one node: returns nothing, callers use nodeF/nodeIdx/nodeBase below.
47
+ const nodeF = ( acc, n ) => ( acc.chunked ? acc.f.chunkFor( n ) : acc.f );
48
+ const nodeIdx = ( acc, n ) => ( acc.chunked ? acc.idx.chunkFor( n ) : acc.idx );
49
+ const nodeBase = ( acc, n ) => ( acc.chunked ? acc.chunked.baseOf( n ) : n * FLOATS_PER_NODE );
50
+
51
+ const triAccess = ( triangleData ) => {
52
+
53
+ const chunked = triangleData && triangleData.chunks ? triangleData : null;
54
+ return {
55
+ chunked,
56
+ f: chunked ? chunked.viewAs( Float32Array ) : floatView( triangleData ),
57
+ u: chunked ? triangleData : uintView( triangleData ),
58
+ };
59
+
60
+ };
61
+
62
+ // Octahedral snorm16 pair, matching packNormalOct in EngineDefaults (not importable here).
63
+ function packNormalOct( x, y, z ) {
64
+
65
+ const len = Math.sqrt( x * x + y * y + z * z );
66
+ if ( len > 0 ) {
67
+
68
+ x /= len; y /= len; z /= len;
69
+
70
+ } else {
71
+
72
+ x = 0; y = 0; z = 1;
73
+
74
+ }
75
+
76
+ const sum = Math.abs( x ) + Math.abs( y ) + Math.abs( z );
77
+ let u = x / sum, v = y / sum;
78
+ if ( z < 0 ) {
79
+
80
+ const au = u, av = v;
81
+ u = ( 1 - Math.abs( av ) ) * ( au >= 0 ? 1 : - 1 );
82
+ v = ( 1 - Math.abs( au ) ) * ( av >= 0 ? 1 : - 1 );
83
+
84
+ }
85
+
86
+ const qu = Math.round( Math.min( 1, Math.max( - 1, u ) ) * 32767 ) & 0xffff;
87
+ const qv = Math.round( Math.min( 1, Math.max( - 1, v ) ) * 32767 ) & 0xffff;
88
+ return ( ( qv << 16 ) | qu ) >>> 0;
89
+
90
+ }
91
+
24
92
  const FLOATS_PER_NODE = 16; // 4 vec4s per BVH node
25
- const LEAF_MARKER = - 1;
26
- const BLAS_POINTER_MARKER = - 2;
93
+ // Inline copies of EngineDefaults.BVH_LEAF_MARKERS — this module also runs inside a worker.
94
+ // Index fields are u32 BIT PATTERNS (exact past 2^24), read through `indexView`.
95
+ const LEAF_MARKER = 0x40000000;
96
+ const BLAS_POINTER_MARKER = 0x40000001;
97
+
98
+ // Relative to the matrix's own magnitude, never an absolute floor — see transformBoundsToWorld.
99
+ const SINGULAR_REL_EPS = 1e-12;
100
+
101
+ /**
102
+ * Object-space bounds at `srcOff` through the instance transform stored on TLAS leaf `nodeOff`,
103
+ * written to `dstOff`. Inlined rather than imported: this file also runs inside a worker.
104
+ */
105
+ function transformBoundsToWorld( bvhData, nodeOff, src, srcOff, dst, dstOff ) {
106
+
107
+ const a0 = bvhData[ nodeOff + 4 ], a1 = bvhData[ nodeOff + 5 ], a2 = bvhData[ nodeOff + 6 ], tx = bvhData[ nodeOff + 7 ];
108
+ const a3 = bvhData[ nodeOff + 8 ], a4 = bvhData[ nodeOff + 9 ], a5 = bvhData[ nodeOff + 10 ], ty = bvhData[ nodeOff + 11 ];
109
+ const a6 = bvhData[ nodeOff + 12 ], a7 = bvhData[ nodeOff + 13 ], a8 = bvhData[ nodeOff + 14 ], tz = bvhData[ nodeOff + 15 ];
110
+
111
+ // Inverse of the 3x3 whose rows are (a0..a2), (a3..a5), (a6..a8).
112
+ const c0x = a4 * a8 - a5 * a7, c0y = a5 * a6 - a3 * a8, c0z = a3 * a7 - a4 * a6;
113
+ const det = a0 * c0x + a1 * c0y + a2 * c0z;
114
+
115
+ const identity = a0 === 1 && a4 === 1 && a8 === 1
116
+ && a1 === 0 && a2 === 0 && a3 === 0 && a5 === 0 && a6 === 0 && a7 === 0
117
+ && tx === 0 && ty === 0 && tz === 0;
118
+
119
+ // Moana's ocean is a unit quad scaled to ±1,089,735, so its world-to-object determinant is
120
+ // 7.7e-19 — an absolute floor called that singular and copied the unit quad's bounds straight
121
+ // through as world bounds, collapsing the root AABB on every refit.
122
+ const magnitude = ( Math.abs( a0 ) + Math.abs( a1 ) + Math.abs( a2 ) )
123
+ * ( Math.abs( a3 ) + Math.abs( a4 ) + Math.abs( a5 ) )
124
+ * ( Math.abs( a6 ) + Math.abs( a7 ) + Math.abs( a8 ) );
125
+
126
+ if ( identity || ! Number.isFinite( det ) || Math.abs( det ) <= SINGULAR_REL_EPS * magnitude ) {
127
+
128
+ for ( let i = 0; i < 6; i ++ ) dst[ dstOff + i ] = src[ srcOff + i ];
129
+ return;
130
+
131
+ }
132
+
133
+ const inv = 1 / det;
134
+ // Columns of the inverse: cross(r1,r2), cross(r2,r0), cross(r0,r1).
135
+ const k0x = c0x * inv, k0y = c0y * inv, k0z = c0z * inv;
136
+ const k1x = ( a2 * a7 - a1 * a8 ) * inv, k1y = ( a0 * a8 - a2 * a6 ) * inv, k1z = ( a1 * a6 - a0 * a7 ) * inv;
137
+ const k2x = ( a1 * a5 - a2 * a4 ) * inv, k2y = ( a2 * a3 - a0 * a5 ) * inv, k2z = ( a0 * a4 - a1 * a3 ) * inv;
138
+
139
+ let minX = Infinity, minY = Infinity, minZ = Infinity;
140
+ let maxX = - Infinity, maxY = - Infinity, maxZ = - Infinity;
141
+
142
+ for ( let c = 0; c < 8; c ++ ) {
143
+
144
+ const px = ( c & 1 ? src[ srcOff + 3 ] : src[ srcOff ] ) - tx;
145
+ const py = ( c & 2 ? src[ srcOff + 4 ] : src[ srcOff + 1 ] ) - ty;
146
+ const pz = ( c & 4 ? src[ srcOff + 5 ] : src[ srcOff + 2 ] ) - tz;
147
+
148
+ const wx = k0x * px + k1x * py + k2x * pz;
149
+ const wy = k0y * px + k1y * py + k2y * pz;
150
+ const wz = k0z * px + k1z * py + k2z * pz;
151
+
152
+ if ( wx < minX ) minX = wx; if ( wx > maxX ) maxX = wx;
153
+ if ( wy < minY ) minY = wy; if ( wy > maxY ) maxY = wy;
154
+ if ( wz < minZ ) minZ = wz; if ( wz > maxZ ) maxZ = wz;
155
+
156
+ }
157
+
158
+ dst[ dstOff ] = minX; dst[ dstOff + 1 ] = minY; dst[ dstOff + 2 ] = minZ;
159
+ dst[ dstOff + 3 ] = maxX; dst[ dstOff + 4 ] = maxY; dst[ dstOff + 5 ] = maxZ;
160
+
161
+ }
162
+
27
163
 
28
164
  export class BVHRefitter {
29
165
 
@@ -40,18 +176,22 @@ export class BVHRefitter {
40
176
  * Update triangle positions in the BVH-reordered triangle array.
41
177
  * Iterates in BVH order (sequential writes, random reads) for cache efficiency.
42
178
  *
43
- * @param {Float32Array} triangleData - BVH-reordered triangle array (mutated in place)
179
+ * @param {Uint32Array} triangleData - BVH-reordered triangle records (mutated in place)
44
180
  * @param {Float32Array} newPositions - 9 floats per triangle in ORIGINAL mesh order
45
181
  * @param {Uint32Array} bvhToOriginal - Map from BVH-order index to original tri index
46
182
  */
47
183
  updateTrianglePositions( triangleData, newPositions, bvhToOriginal ) {
48
184
 
49
185
  const triCount = bvhToOriginal.length;
186
+ const acc = triAccess( triangleData );
50
187
 
51
188
  for ( let bvhIdx = 0; bvhIdx < triCount; bvhIdx ++ ) {
52
189
 
53
190
  const orig = bvhToOriginal[ bvhIdx ];
54
- const dstOff = bvhIdx * FPT; // sequential writes
191
+ // sequential writes
192
+ const f = acc.chunked ? acc.f.chunkFor( bvhIdx ) : acc.f;
193
+ const u = acc.chunked ? acc.u.chunkFor( bvhIdx ) : acc.u;
194
+ const dstOff = acc.chunked ? acc.chunked.baseOf( bvhIdx ) : bvhIdx * FPT;
55
195
  const srcOff = orig * 9;
56
196
 
57
197
  const ax = newPositions[ srcOff ];
@@ -64,37 +204,30 @@ export class BVHRefitter {
64
204
  const cy = newPositions[ srcOff + 7 ];
65
205
  const cz = newPositions[ srcOff + 8 ];
66
206
 
67
- triangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET ] = ax;
68
- triangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 1 ] = ay;
69
- triangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 2 ] = az;
207
+ f[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET ] = ax;
208
+ f[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 1 ] = ay;
209
+ f[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 2 ] = az;
70
210
 
71
- triangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET ] = bx;
72
- triangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 1 ] = by;
73
- triangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 2 ] = bz;
211
+ f[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET ] = bx;
212
+ f[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 1 ] = by;
213
+ f[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 2 ] = bz;
74
214
 
75
- triangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET ] = cx;
76
- triangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 1 ] = cy;
77
- triangleData[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 2 ] = cz;
215
+ f[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET ] = cx;
216
+ f[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 1 ] = cy;
217
+ f[ dstOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 2 ] = cz;
78
218
 
79
- // Compute unnormalized face normal from cross product.
80
- // Skip sqrt normalization — the path tracer shader normalizes during shading.
219
+ // Face normal from the cross product; packNormalOct normalizes it.
81
220
  const abx = bx - ax, aby = by - ay, abz = bz - az;
82
221
  const acx = cx - ax, acy = cy - ay, acz = cz - az;
83
- const nx = aby * acz - abz * acy;
84
- const ny = abz * acx - abx * acz;
85
- const nz = abx * acy - aby * acx;
86
-
87
- triangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_A_OFFSET ] = nx;
88
- triangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_A_OFFSET + 1 ] = ny;
89
- triangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_A_OFFSET + 2 ] = nz;
222
+ const packed = packNormalOct(
223
+ aby * acz - abz * acy,
224
+ abz * acx - abx * acz,
225
+ abx * acy - aby * acx
226
+ );
90
227
 
91
- triangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_B_OFFSET ] = nx;
92
- triangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_B_OFFSET + 1 ] = ny;
93
- triangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_B_OFFSET + 2 ] = nz;
94
-
95
- triangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_C_OFFSET ] = nx;
96
- triangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_C_OFFSET + 1 ] = ny;
97
- triangleData[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_C_OFFSET + 2 ] = nz;
228
+ u[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_A_PACKED_OFFSET ] = packed;
229
+ u[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_B_PACKED_OFFSET ] = packed;
230
+ u[ dstOff + TRIANGLE_DATA_LAYOUT.NORMAL_C_PACKED_OFFSET ] = packed;
98
231
 
99
232
  }
100
233
 
@@ -105,12 +238,14 @@ export class BVHRefitter {
105
238
  * Same algorithm as refit() but scoped to nodes [startNode, startNode + count).
106
239
  *
107
240
  * @param {Float32Array} bvhData - Combined BVH array (TLAS + all BLASes)
108
- * @param {Float32Array} triangleData - Global triangle data
241
+ * @param {Uint32Array} triangleData - Global triangle records
109
242
  * @param {number} startNode - First node index of this BLAS in bvhData
110
243
  * @param {number} nodeCount - Number of nodes in this BLAS
111
244
  */
112
245
  refitRange( bvhData, triangleData, startNode, nodeCount ) {
113
246
 
247
+ const acc = triAccess( triangleData );
248
+
114
249
  // Grow-only bounds buffer to avoid reallocation on mixed-size BLASes
115
250
  if ( nodeCount > this._boundsNodeCount ) {
116
251
 
@@ -121,32 +256,37 @@ export class BVHRefitter {
121
256
 
122
257
  const bounds = this._bounds;
123
258
  const endNode = startNode + nodeCount;
259
+ const nodes = nodeAccess( bvhData );
124
260
 
125
261
  for ( let i = endNode - 1; i >= startNode; i -- ) {
126
262
 
127
- const o = i * FLOATS_PER_NODE;
263
+ const bvhF = nodeF( nodes, i );
264
+ const idx = nodeIdx( nodes, i );
265
+ const o = nodeBase( nodes, i );
128
266
  const b = ( i - startNode ) * 6; // bounds indexed relative to BLAS start
129
267
 
130
- if ( bvhData[ o + 3 ] === LEAF_MARKER ) {
268
+ if ( idx[ o + 3 ] === LEAF_MARKER ) {
131
269
 
132
- const triOffset = bvhData[ o ];
133
- const triCount = bvhData[ o + 1 ];
270
+ const triOffset = idx[ o ];
271
+ const triCount = idx[ o + 1 ];
134
272
 
135
273
  let minX = Infinity, minY = Infinity, minZ = Infinity;
136
274
  let maxX = - Infinity, maxY = - Infinity, maxZ = - Infinity;
137
275
 
138
276
  for ( let t = 0; t < triCount; t ++ ) {
139
277
 
140
- const tOff = ( triOffset + t ) * FPT;
141
- const ax = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET ];
142
- const ay = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 1 ];
143
- const az = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 2 ];
144
- const bx = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET ];
145
- const by = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 1 ];
146
- const bz = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 2 ];
147
- const cx = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET ];
148
- const cy = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 1 ];
149
- const cz = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 2 ];
278
+ const gi = triOffset + t;
279
+ const triFloats = acc.chunked ? acc.f.chunkFor( gi ) : acc.f;
280
+ const tOff = acc.chunked ? acc.chunked.baseOf( gi ) : gi * FPT;
281
+ const ax = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET ];
282
+ const ay = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 1 ];
283
+ const az = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 2 ];
284
+ const bx = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET ];
285
+ const by = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 1 ];
286
+ const bz = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 2 ];
287
+ const cx = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET ];
288
+ const cy = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 1 ];
289
+ const cz = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 2 ];
150
290
 
151
291
  minX = Math.min( minX, ax, bx, cx );
152
292
  minY = Math.min( minY, ay, by, cy );
@@ -167,8 +307,8 @@ export class BVHRefitter {
167
307
  } else {
168
308
 
169
309
  // Inner node — child indices are absolute, but bounds index relative to startNode
170
- const leftIdx = bvhData[ o + 3 ];
171
- const rightIdx = bvhData[ o + 7 ];
310
+ const leftIdx = idx[ o + 3 ];
311
+ const rightIdx = idx[ o + 7 ];
172
312
  const lb = ( leftIdx - startNode ) * 6;
173
313
  const rb = ( rightIdx - startNode ) * 6;
174
314
 
@@ -186,19 +326,19 @@ export class BVHRefitter {
186
326
  const rMaxY = bounds[ rb + 4 ];
187
327
  const rMaxZ = bounds[ rb + 5 ];
188
328
 
189
- bvhData[ o ] = lMinX;
190
- bvhData[ o + 1 ] = lMinY;
191
- bvhData[ o + 2 ] = lMinZ;
192
- bvhData[ o + 4 ] = lMaxX;
193
- bvhData[ o + 5 ] = lMaxY;
194
- bvhData[ o + 6 ] = lMaxZ;
329
+ bvhF[ o ] = lMinX;
330
+ bvhF[ o + 1 ] = lMinY;
331
+ bvhF[ o + 2 ] = lMinZ;
332
+ bvhF[ o + 4 ] = lMaxX;
333
+ bvhF[ o + 5 ] = lMaxY;
334
+ bvhF[ o + 6 ] = lMaxZ;
195
335
 
196
- bvhData[ o + 8 ] = rMinX;
197
- bvhData[ o + 9 ] = rMinY;
198
- bvhData[ o + 10 ] = rMinZ;
199
- bvhData[ o + 12 ] = rMaxX;
200
- bvhData[ o + 13 ] = rMaxY;
201
- bvhData[ o + 14 ] = rMaxZ;
336
+ bvhF[ o + 8 ] = rMinX;
337
+ bvhF[ o + 9 ] = rMinY;
338
+ bvhF[ o + 10 ] = rMinZ;
339
+ bvhF[ o + 12 ] = rMaxX;
340
+ bvhF[ o + 13 ] = rMaxY;
341
+ bvhF[ o + 14 ] = rMaxZ;
202
342
 
203
343
  bounds[ b ] = Math.min( lMinX, rMinX );
204
344
  bounds[ b + 1 ] = Math.min( lMinY, rMinY );
@@ -219,11 +359,13 @@ export class BVHRefitter {
219
359
  * indices than parents in pre-order, so reversing processes children first).
220
360
  *
221
361
  * @param {Float32Array} bvhData - Flat BVH array (mutated in place)
222
- * @param {Float32Array} triangleData - Updated triangle data
362
+ * @param {Uint32Array} triangleData - Updated triangle records
223
363
  * @param {number} nodeCount - Total number of BVH nodes
224
364
  */
225
365
  refit( bvhData, triangleData, nodeCount ) {
226
366
 
367
+ const acc = triAccess( triangleData );
368
+
227
369
  // Reuse bounds buffer across frames (reallocate only on scene change)
228
370
  if ( nodeCount !== this._boundsNodeCount ) {
229
371
 
@@ -233,40 +375,45 @@ export class BVHRefitter {
233
375
  }
234
376
 
235
377
  const bounds = this._bounds;
378
+ const nodes = nodeAccess( bvhData );
236
379
 
237
380
  // Reverse iteration: bottom-up in pre-order layout
238
381
  for ( let i = nodeCount - 1; i >= 0; i -- ) {
239
382
 
240
- const o = i * FLOATS_PER_NODE;
383
+ const bvhF = nodeF( nodes, i );
384
+ const idx = nodeIdx( nodes, i );
385
+ const o = nodeBase( nodes, i );
241
386
  const b = i * 6;
242
387
 
243
- const marker = bvhData[ o + 3 ];
388
+ const marker = idx[ o + 3 ];
244
389
 
245
390
  if ( marker === LEAF_MARKER ) {
246
391
 
247
392
  // Triangle leaf: compute AABB from triangles
248
- const triOffset = bvhData[ o ];
249
- const triCount = bvhData[ o + 1 ];
393
+ const triOffset = idx[ o ];
394
+ const triCount = idx[ o + 1 ];
250
395
 
251
396
  let minX = Infinity, minY = Infinity, minZ = Infinity;
252
397
  let maxX = - Infinity, maxY = - Infinity, maxZ = - Infinity;
253
398
 
254
399
  for ( let t = 0; t < triCount; t ++ ) {
255
400
 
256
- const tOff = ( triOffset + t ) * FPT;
401
+ const gi = triOffset + t;
402
+ const triFloats = acc.chunked ? acc.f.chunkFor( gi ) : acc.f;
403
+ const tOff = acc.chunked ? acc.chunked.baseOf( gi ) : gi * FPT;
257
404
 
258
405
  // Position A
259
- const ax = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET ];
260
- const ay = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 1 ];
261
- const az = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 2 ];
406
+ const ax = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET ];
407
+ const ay = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 1 ];
408
+ const az = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_A_OFFSET + 2 ];
262
409
  // Position B
263
- const bx = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET ];
264
- const by = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 1 ];
265
- const bz = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 2 ];
410
+ const bx = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET ];
411
+ const by = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 1 ];
412
+ const bz = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_B_OFFSET + 2 ];
266
413
  // Position C
267
- const cx = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET ];
268
- const cy = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 1 ];
269
- const cz = triangleData[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 2 ];
414
+ const cx = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET ];
415
+ const cy = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 1 ];
416
+ const cz = triFloats[ tOff + TRIANGLE_DATA_LAYOUT.POSITION_C_OFFSET + 2 ];
270
417
 
271
418
  minX = Math.min( minX, ax, bx, cx );
272
419
  minY = Math.min( minY, ay, by, cy );
@@ -286,21 +433,19 @@ export class BVHRefitter {
286
433
 
287
434
  } else if ( marker === BLAS_POINTER_MARKER ) {
288
435
 
289
- // BLAS-pointer leaf (TLAS): read BLAS root node's bounds (already computed)
290
- const blasRoot = bvhData[ o ];
436
+ // BLAS-pointer leaf (TLAS): the BLAS root's bounds are in the instance's own
437
+ // space, and the TLAS sorts on world bounds — so carry the box out through the
438
+ // leaf's transform. Slots 4..15 are the rows of world-to-object; the eight
439
+ // corners go back the other way through its inverse.
440
+ const blasRoot = idx[ o ];
291
441
  const br = blasRoot * 6;
292
- bounds[ b ] = bounds[ br ];
293
- bounds[ b + 1 ] = bounds[ br + 1 ];
294
- bounds[ b + 2 ] = bounds[ br + 2 ];
295
- bounds[ b + 3 ] = bounds[ br + 3 ];
296
- bounds[ b + 4 ] = bounds[ br + 4 ];
297
- bounds[ b + 5 ] = bounds[ br + 5 ];
442
+ transformBoundsToWorld( bvhF, o, bounds, br, bounds, b );
298
443
 
299
444
  } else {
300
445
 
301
446
  // Inner node: union children bounds (already computed since we iterate in reverse)
302
- const leftIdx = bvhData[ o + 3 ];
303
- const rightIdx = bvhData[ o + 7 ];
447
+ const leftIdx = idx[ o + 3 ];
448
+ const rightIdx = idx[ o + 7 ];
304
449
  const lb = leftIdx * 6;
305
450
  const rb = rightIdx * 6;
306
451
 
@@ -319,23 +464,23 @@ export class BVHRefitter {
319
464
  const rMaxZ = bounds[ rb + 5 ];
320
465
 
321
466
  // Write left child AABB into bvhData
322
- bvhData[ o ] = lMinX;
323
- bvhData[ o + 1 ] = lMinY;
324
- bvhData[ o + 2 ] = lMinZ;
467
+ bvhF[ o ] = lMinX;
468
+ bvhF[ o + 1 ] = lMinY;
469
+ bvhF[ o + 2 ] = lMinZ;
325
470
  // o+3 = leftChildIdx (preserved)
326
- bvhData[ o + 4 ] = lMaxX;
327
- bvhData[ o + 5 ] = lMaxY;
328
- bvhData[ o + 6 ] = lMaxZ;
471
+ bvhF[ o + 4 ] = lMaxX;
472
+ bvhF[ o + 5 ] = lMaxY;
473
+ bvhF[ o + 6 ] = lMaxZ;
329
474
  // o+7 = rightChildIdx (preserved)
330
475
 
331
476
  // Write right child AABB into bvhData
332
- bvhData[ o + 8 ] = rMinX;
333
- bvhData[ o + 9 ] = rMinY;
334
- bvhData[ o + 10 ] = rMinZ;
477
+ bvhF[ o + 8 ] = rMinX;
478
+ bvhF[ o + 9 ] = rMinY;
479
+ bvhF[ o + 10 ] = rMinZ;
335
480
  // o+11 = 0 padding
336
- bvhData[ o + 12 ] = rMaxX;
337
- bvhData[ o + 13 ] = rMaxY;
338
- bvhData[ o + 14 ] = rMaxZ;
481
+ bvhF[ o + 12 ] = rMaxX;
482
+ bvhF[ o + 13 ] = rMaxY;
483
+ bvhF[ o + 14 ] = rMaxZ;
339
484
  // o+15 = 0 padding
340
485
 
341
486
  // Store this node's bounds as union of children