rayzee 7.31.1 → 8.0.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 +6366 -4361
- package/dist/rayzee.umd.js +122 -73
- package/package.json +1 -1
- package/src/EngineDefaults.js +168 -13
- package/src/EngineIssues.js +5 -0
- package/src/PathTracerApp.js +120 -19
- 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 +577 -92
- 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/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/Struct.js +3 -0
- package/src/TSL/TextureSampling.js +24 -9
- package/src/TSL/patches.js +128 -3
- 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
|
@@ -1,101 +1,553 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* InstanceTable —
|
|
2
|
+
* InstanceTable — per-placement transforms plus per-template BLAS metadata for the two-level BVH.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* and
|
|
4
|
+
* Columns, not objects, and the per-template half is stored once rather than once per placement:
|
|
5
|
+
* millions of placements still share a few thousand geometries, so triangle ranges, node counts
|
|
6
|
+
* and object-space bounds live on the template. `sourceMesh` doubles as the template id.
|
|
7
|
+
*
|
|
8
|
+
* 75 bytes a placement, against 418 for the array-of-objects version and 211 for the flat but
|
|
9
|
+
* denormalised one. World bounds and the inverse transform are derived on demand.
|
|
7
10
|
*/
|
|
8
11
|
|
|
9
|
-
import { TRIANGLE_DATA_LAYOUT } from '../EngineDefaults.js';
|
|
12
|
+
import { BVH_LEAF_MARKERS, TRIANGLE_DATA_LAYOUT, assertBVHIndexFits, bvhIndexView } from '../EngineDefaults.js';
|
|
13
|
+
|
|
14
|
+
const IDENTITY = Float64Array.from( [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] );
|
|
15
|
+
|
|
16
|
+
/** Determinant of the upper-left 3x3 at `off`. Negative means the transform mirrors. */
|
|
17
|
+
export function determinant3At( m, off ) {
|
|
18
|
+
|
|
19
|
+
return m[ off ] * ( m[ off + 5 ] * m[ off + 10 ] - m[ off + 6 ] * m[ off + 9 ] )
|
|
20
|
+
- m[ off + 4 ] * ( m[ off + 1 ] * m[ off + 10 ] - m[ off + 2 ] * m[ off + 9 ] )
|
|
21
|
+
+ m[ off + 8 ] * ( m[ off + 1 ] * m[ off + 6 ] - m[ off + 2 ] * m[ off + 5 ] );
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Writes the inverse of the affine column-major 4x4 at `off` into `out`. */
|
|
26
|
+
export function invertAffineInto( m, off, out ) {
|
|
27
|
+
|
|
28
|
+
const det = determinant3At( m, off );
|
|
29
|
+
if ( ! Number.isFinite( det ) || Math.abs( det ) < 1e-20 ) {
|
|
30
|
+
|
|
31
|
+
out.set( IDENTITY );
|
|
32
|
+
return out;
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const s = 1 / det;
|
|
37
|
+
|
|
38
|
+
out[ 0 ] = ( m[ off + 5 ] * m[ off + 10 ] - m[ off + 6 ] * m[ off + 9 ] ) * s;
|
|
39
|
+
out[ 1 ] = ( m[ off + 2 ] * m[ off + 9 ] - m[ off + 1 ] * m[ off + 10 ] ) * s;
|
|
40
|
+
out[ 2 ] = ( m[ off + 1 ] * m[ off + 6 ] - m[ off + 2 ] * m[ off + 5 ] ) * s;
|
|
41
|
+
out[ 3 ] = 0;
|
|
42
|
+
out[ 4 ] = ( m[ off + 6 ] * m[ off + 8 ] - m[ off + 4 ] * m[ off + 10 ] ) * s;
|
|
43
|
+
out[ 5 ] = ( m[ off ] * m[ off + 10 ] - m[ off + 2 ] * m[ off + 8 ] ) * s;
|
|
44
|
+
out[ 6 ] = ( m[ off + 2 ] * m[ off + 4 ] - m[ off ] * m[ off + 6 ] ) * s;
|
|
45
|
+
out[ 7 ] = 0;
|
|
46
|
+
out[ 8 ] = ( m[ off + 4 ] * m[ off + 9 ] - m[ off + 5 ] * m[ off + 8 ] ) * s;
|
|
47
|
+
out[ 9 ] = ( m[ off + 1 ] * m[ off + 8 ] - m[ off ] * m[ off + 9 ] ) * s;
|
|
48
|
+
out[ 10 ] = ( m[ off ] * m[ off + 5 ] - m[ off + 1 ] * m[ off + 4 ] ) * s;
|
|
49
|
+
out[ 11 ] = 0;
|
|
50
|
+
|
|
51
|
+
out[ 12 ] = - ( out[ 0 ] * m[ off + 12 ] + out[ 4 ] * m[ off + 13 ] + out[ 8 ] * m[ off + 14 ] );
|
|
52
|
+
out[ 13 ] = - ( out[ 1 ] * m[ off + 12 ] + out[ 5 ] * m[ off + 13 ] + out[ 9 ] * m[ off + 14 ] );
|
|
53
|
+
out[ 14 ] = - ( out[ 2 ] * m[ off + 12 ] + out[ 6 ] * m[ off + 13 ] + out[ 10 ] * m[ off + 14 ] );
|
|
54
|
+
out[ 15 ] = 1;
|
|
55
|
+
return out;
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** `out = a * b`, column-major affine 4x4s; `b` is read at `bOff`. Matches how the extractor
|
|
60
|
+
* composes an InstancedMesh's host matrix with each instance matrix. */
|
|
61
|
+
export function multiplyAffine( a, b, bOff, out ) {
|
|
62
|
+
|
|
63
|
+
for ( let c = 0; c < 4; c ++ ) {
|
|
64
|
+
|
|
65
|
+
const o = bOff + c * 4;
|
|
66
|
+
const b0 = b[ o ], b1 = b[ o + 1 ], b2 = b[ o + 2 ], b3 = b[ o + 3 ];
|
|
67
|
+
|
|
68
|
+
for ( let r = 0; r < 4; r ++ ) {
|
|
69
|
+
|
|
70
|
+
out[ c * 4 + r ] = a[ r ] * b0 + a[ 4 + r ] * b1 + a[ 8 + r ] * b2 + a[ 12 + r ] * b3;
|
|
71
|
+
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return out;
|
|
77
|
+
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** True when the matrix leaves points untouched — lets refit skip the transform entirely. */
|
|
81
|
+
export function isIdentity( m ) {
|
|
82
|
+
|
|
83
|
+
for ( let i = 0; i < 16; i ++ ) if ( m[ i ] !== IDENTITY[ i ] ) return false;
|
|
84
|
+
return true;
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Axis-aligned bounds of an AABB carried through an affine transform (all 8 corners). */
|
|
89
|
+
export function transformAABB( aabb, m ) {
|
|
90
|
+
|
|
91
|
+
if ( ! aabb ) return aabb;
|
|
92
|
+
|
|
93
|
+
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
94
|
+
let maxX = - Infinity, maxY = - Infinity, maxZ = - Infinity;
|
|
95
|
+
|
|
96
|
+
for ( let c = 0; c < 8; c ++ ) {
|
|
97
|
+
|
|
98
|
+
const x = c & 1 ? aabb.maxX : aabb.minX;
|
|
99
|
+
const y = c & 2 ? aabb.maxY : aabb.minY;
|
|
100
|
+
const z = c & 4 ? aabb.maxZ : aabb.minZ;
|
|
101
|
+
|
|
102
|
+
const wx = m[ 0 ] * x + m[ 4 ] * y + m[ 8 ] * z + m[ 12 ];
|
|
103
|
+
const wy = m[ 1 ] * x + m[ 5 ] * y + m[ 9 ] * z + m[ 13 ];
|
|
104
|
+
const wz = m[ 2 ] * x + m[ 6 ] * y + m[ 10 ] * z + m[ 14 ];
|
|
105
|
+
|
|
106
|
+
if ( wx < minX ) minX = wx; if ( wx > maxX ) maxX = wx;
|
|
107
|
+
if ( wy < minY ) minY = wy; if ( wy > maxY ) maxY = wy;
|
|
108
|
+
if ( wz < minZ ) minZ = wz; if ( wz > maxZ ) maxZ = wz;
|
|
109
|
+
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return { minX, minY, minZ, maxX, maxY, maxZ };
|
|
113
|
+
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Writes an AABB carried through an affine transform into `dst` at `dstOff`. */
|
|
117
|
+
export function transformAABBInto( src, srcOff, m, mOff, dst, dstOff ) {
|
|
118
|
+
|
|
119
|
+
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
120
|
+
let maxX = - Infinity, maxY = - Infinity, maxZ = - Infinity;
|
|
121
|
+
|
|
122
|
+
for ( let c = 0; c < 8; c ++ ) {
|
|
123
|
+
|
|
124
|
+
const x = src[ srcOff + ( c & 1 ? 3 : 0 ) ];
|
|
125
|
+
const y = src[ srcOff + ( c & 2 ? 4 : 1 ) ];
|
|
126
|
+
const z = src[ srcOff + ( c & 4 ? 5 : 2 ) ];
|
|
127
|
+
|
|
128
|
+
const wx = m[ mOff ] * x + m[ mOff + 4 ] * y + m[ mOff + 8 ] * z + m[ mOff + 12 ];
|
|
129
|
+
const wy = m[ mOff + 1 ] * x + m[ mOff + 5 ] * y + m[ mOff + 9 ] * z + m[ mOff + 13 ];
|
|
130
|
+
const wz = m[ mOff + 2 ] * x + m[ mOff + 6 ] * y + m[ mOff + 10 ] * z + m[ mOff + 14 ];
|
|
131
|
+
|
|
132
|
+
if ( wx < minX ) minX = wx; if ( wx > maxX ) maxX = wx;
|
|
133
|
+
if ( wy < minY ) minY = wy; if ( wy > maxY ) maxY = wy;
|
|
134
|
+
if ( wz < minZ ) minZ = wz; if ( wz > maxZ ) maxZ = wz;
|
|
135
|
+
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
dst[ dstOff ] = minX; dst[ dstOff + 1 ] = minY; dst[ dstOff + 2 ] = minZ;
|
|
139
|
+
dst[ dstOff + 3 ] = maxX; dst[ dstOff + 4 ] = maxY; dst[ dstOff + 5 ] = maxZ;
|
|
140
|
+
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** True when the 16 floats at `off` leave points untouched — lets refit skip the transform. */
|
|
144
|
+
export function isIdentityAt( m, off ) {
|
|
145
|
+
|
|
146
|
+
for ( let i = 0; i < 16; i ++ ) if ( m[ off + i ] !== IDENTITY[ i ] ) return false;
|
|
147
|
+
return true;
|
|
148
|
+
|
|
149
|
+
}
|
|
10
150
|
|
|
11
151
|
export class InstanceTable {
|
|
12
152
|
|
|
13
153
|
constructor() {
|
|
14
154
|
|
|
15
|
-
|
|
16
|
-
this.
|
|
17
|
-
|
|
18
|
-
/** Total BVH node count across all BLASes (excludes TLAS nodes) */
|
|
155
|
+
this.count = 0;
|
|
156
|
+
this.templateCount = 0;
|
|
19
157
|
this.totalBLASNodes = 0;
|
|
20
|
-
|
|
21
|
-
/** Number of TLAS nodes */
|
|
22
158
|
this.tlasNodeCount = 0;
|
|
159
|
+
this.allocate( 0 );
|
|
23
160
|
|
|
24
161
|
}
|
|
25
162
|
|
|
26
163
|
/**
|
|
27
|
-
* Pre-allocate
|
|
28
|
-
*
|
|
29
|
-
* @param {number} count
|
|
164
|
+
* Pre-allocate the columns. Must be called before setEntry().
|
|
165
|
+
*
|
|
166
|
+
* @param {number} count - placements
|
|
167
|
+
* @param {number} [templateCount] - distinct source meshes; defaults to one per placement
|
|
168
|
+
* @param {Float32Array} [worldPool] - adopted as the transform column instead of allocating,
|
|
169
|
+
* so a caller that already built the matrices contiguously hands them over without a copy
|
|
170
|
+
* @param {Int32Array} [sourcePool] - likewise for the source-mesh column
|
|
30
171
|
*/
|
|
31
|
-
allocate( count ) {
|
|
172
|
+
allocate( count, templateCount = count, worldPool = null, sourcePool = null ) {
|
|
173
|
+
|
|
174
|
+
this.count = count;
|
|
175
|
+
this.templateCount = templateCount;
|
|
176
|
+
|
|
177
|
+
this.isSet = new Uint8Array( count );
|
|
178
|
+
// Object3D this placement came from, and the template index for everything below.
|
|
179
|
+
this.sourceMesh = sourcePool && sourcePool.length >= count ? sourcePool : new Int32Array( count );
|
|
180
|
+
this.tlasLeafIndex = new Int32Array( count ).fill( - 1 ); // set by TLASBuilder
|
|
181
|
+
this.visible = new Uint8Array( count ).fill( 1 ); // baked into TLAS leaf slot [2]
|
|
182
|
+
// A mirroring transform reverses triangle winding, so front and back swap in object
|
|
183
|
+
// space. Traversal needs telling, or single-sided faces cull inside out.
|
|
184
|
+
this.flipWinding = new Uint8Array( count );
|
|
185
|
+
// template -> inverse of the pose baked into its triangles, for templates stored in
|
|
186
|
+
// world space. A move composes the new world matrix against this.
|
|
187
|
+
this.tplBakeInverse = null;
|
|
32
188
|
|
|
33
|
-
|
|
189
|
+
if ( worldPool && worldPool.length >= count * 16 ) {
|
|
190
|
+
|
|
191
|
+
this.world = worldPool;
|
|
192
|
+
this.worldPooled = true;
|
|
193
|
+
|
|
194
|
+
} else {
|
|
195
|
+
|
|
196
|
+
this.world = new Float32Array( count * 16 );
|
|
197
|
+
this.worldPooled = false;
|
|
198
|
+
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
this.tplTriOffset = new Int32Array( templateCount );
|
|
202
|
+
this.tplTriCount = new Int32Array( templateCount );
|
|
203
|
+
this.tplExpandedStart = new Int32Array( templateCount );
|
|
204
|
+
this.tplNodeCount = new Int32Array( templateCount );
|
|
205
|
+
this.tplBlasOffset = new Int32Array( templateCount );
|
|
206
|
+
this.tplOwner = new Int32Array( templateCount ).fill( - 1 ); // placement that built the BLAS
|
|
207
|
+
this.tplObjectAABB = new Float32Array( templateCount * 6 );
|
|
208
|
+
|
|
209
|
+
this._placementRuns = null;
|
|
210
|
+
|
|
211
|
+
// Keyed by template, and only templates that own a BLAS carry them.
|
|
212
|
+
this.originalToBvhMap = new Map();
|
|
213
|
+
this.bvhToOriginal = new Map();
|
|
214
|
+
this.blasData = new Map();
|
|
215
|
+
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Writes the placement half of an entry. @private */
|
|
219
|
+
_place( index, template, matrixWorld, matrixOffset ) {
|
|
220
|
+
|
|
221
|
+
this.isSet[ index ] = 1;
|
|
222
|
+
this.sourceMesh[ index ] = template;
|
|
223
|
+
|
|
224
|
+
const o = index * 16;
|
|
225
|
+
|
|
226
|
+
if ( ! matrixWorld ) {
|
|
227
|
+
|
|
228
|
+
this.world.set( IDENTITY, o );
|
|
229
|
+
this.flipWinding[ index ] = 0;
|
|
230
|
+
return;
|
|
231
|
+
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Already in the pool — adopting it is the whole point of passing it in.
|
|
235
|
+
if ( matrixWorld !== this.world ) {
|
|
236
|
+
|
|
237
|
+
for ( let k = 0; k < 16; k ++ ) this.world[ o + k ] = matrixWorld[ matrixOffset + k ];
|
|
238
|
+
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
this.flipWinding[ index ] = determinant3At( this.world, o ) < 0 ? 1 : 0;
|
|
34
242
|
|
|
35
243
|
}
|
|
36
244
|
|
|
37
245
|
/**
|
|
38
|
-
* Set a
|
|
39
|
-
*
|
|
246
|
+
* Set a placement whose BLAS has just been built. Template metadata (triangle range, node
|
|
247
|
+
* count, reorder map) is recorded once against `sourceMesh`, not once per placement.
|
|
40
248
|
*
|
|
41
249
|
* @param {Object} params
|
|
42
|
-
* @param {number} params.meshIndex -
|
|
250
|
+
* @param {number} params.meshIndex - Placement slot
|
|
43
251
|
* @param {number} params.blasNodeCount - Number of BVH nodes in this BLAS
|
|
44
252
|
* @param {number} params.triOffset - Triangle index offset in global triangleData
|
|
45
253
|
* @param {number} params.triCount - Number of triangles for this mesh
|
|
46
254
|
* @param {Uint32Array} params.originalToBvhMap - Per-BLAS triangle reorder map
|
|
47
255
|
* @param {Float32Array} params.bvhData - Raw BLAS BVH data (local indices, before assembly)
|
|
256
|
+
* @param {ArrayLike<number>} [params.matrixWorld] - object-to-world, column-major 16
|
|
257
|
+
* @param {number} [params.matrixOffset] - element offset into `matrixWorld`
|
|
258
|
+
*/
|
|
259
|
+
setEntry( { meshIndex, blasNodeCount, triOffset, triCount, originalToBvhMap, bvhData, matrixWorld = null, matrixOffset = 0, expandedStart = null, sourceMesh = null } ) {
|
|
260
|
+
|
|
261
|
+
const t = sourceMesh ?? meshIndex;
|
|
262
|
+
|
|
263
|
+
this._place( meshIndex, t, matrixWorld, matrixOffset );
|
|
264
|
+
|
|
265
|
+
this.tplTriOffset[ t ] = triOffset;
|
|
266
|
+
this.tplTriCount[ t ] = triCount;
|
|
267
|
+
this.tplExpandedStart[ t ] = expandedStart ?? triOffset;
|
|
268
|
+
this.tplNodeCount[ t ] = blasNodeCount;
|
|
269
|
+
this.tplOwner[ t ] = meshIndex;
|
|
270
|
+
|
|
271
|
+
if ( originalToBvhMap ) this.originalToBvhMap.set( t, originalToBvhMap );
|
|
272
|
+
if ( bvhData ) this.blasData.set( t, bvhData );
|
|
273
|
+
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Register a placement that reuses `ownerIndex`'s triangles and BLAS. Only the transform
|
|
278
|
+
* differs, so its template borrows the owner's node range and object-space bounds.
|
|
279
|
+
*
|
|
280
|
+
* @param {number} meshIndex
|
|
281
|
+
* @param {number} ownerIndex
|
|
282
|
+
* @param {ArrayLike<number>} [matrixWorld]
|
|
283
|
+
*/
|
|
284
|
+
setAlias( meshIndex, ownerIndex, matrixWorld = null, expandedStart = null, sourceMesh = null, matrixOffset = 0 ) {
|
|
285
|
+
|
|
286
|
+
if ( ! this.isSet[ ownerIndex ] ) throw new Error( `InstanceTable.setAlias: owner ${ownerIndex} not built` );
|
|
287
|
+
|
|
288
|
+
const t = sourceMesh ?? meshIndex;
|
|
289
|
+
const ot = this.sourceMesh[ ownerIndex ];
|
|
290
|
+
|
|
291
|
+
this._place( meshIndex, t, matrixWorld, matrixOffset );
|
|
292
|
+
|
|
293
|
+
if ( t === ot ) return; // same template as the owner: nothing to mirror
|
|
294
|
+
|
|
295
|
+
this.tplTriOffset[ t ] = this.tplTriOffset[ ot ];
|
|
296
|
+
this.tplTriCount[ t ] = this.tplTriCount[ ot ];
|
|
297
|
+
this.tplNodeCount[ t ] = this.tplNodeCount[ ot ];
|
|
298
|
+
this.tplExpandedStart[ t ] = expandedStart ?? this.tplExpandedStart[ ot ];
|
|
299
|
+
this.tplOwner[ t ] = this.tplOwner[ ot ];
|
|
300
|
+
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** Placements actually built. */
|
|
304
|
+
get setCount() {
|
|
305
|
+
|
|
306
|
+
let n = 0;
|
|
307
|
+
for ( let i = 0; i < this.count; i ++ ) n += this.isSet[ i ];
|
|
308
|
+
return n;
|
|
309
|
+
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Replace one placement's object-to-world transform. Its geometry is untouched, which is the
|
|
314
|
+
* whole point: a rigid move is a matrix change, and rewriting shared triangles instead would
|
|
315
|
+
* drag every other placement of the same geometry along with it.
|
|
316
|
+
*/
|
|
317
|
+
setPlacementMatrix( index, matrixWorld, matrixOffset = 0 ) {
|
|
318
|
+
|
|
319
|
+
this._place( index, this.sourceMesh[ index ], matrixWorld, matrixOffset );
|
|
320
|
+
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* The contiguous run of placements recorded for `meshIndex`, or null. One object contributes
|
|
325
|
+
* one placement, or one per instance for an InstancedMesh, and the extractor emits them in
|
|
326
|
+
* mesh order — so the run is contiguous and worth indexing once rather than scanning per move.
|
|
327
|
+
*
|
|
328
|
+
* @returns {{start: number, count: number}|null}
|
|
329
|
+
*/
|
|
330
|
+
placementRunOf( meshIndex ) {
|
|
331
|
+
|
|
332
|
+
if ( ! this._placementRuns ) {
|
|
333
|
+
|
|
334
|
+
const runs = this._placementRuns = new Map();
|
|
335
|
+
for ( let i = 0; i < this.count; i ++ ) {
|
|
336
|
+
|
|
337
|
+
const m = this.sourceMesh[ i ];
|
|
338
|
+
const run = runs.get( m );
|
|
339
|
+
if ( run ) run.count ++;
|
|
340
|
+
else runs.set( m, { start: i, count: 1 } );
|
|
341
|
+
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return this._placementRuns.get( meshIndex ) ?? null;
|
|
347
|
+
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** True when this placement is the one whose BLAS the others alias. */
|
|
351
|
+
isOwner( index ) {
|
|
352
|
+
|
|
353
|
+
return this.tplOwner[ this.sourceMesh[ index ] ] === index;
|
|
354
|
+
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
triOffsetOf( index ) {
|
|
358
|
+
|
|
359
|
+
return this.tplTriOffset[ this.sourceMesh[ index ] ];
|
|
360
|
+
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
triCountOf( index ) {
|
|
364
|
+
|
|
365
|
+
return this.tplTriCount[ this.sourceMesh[ index ] ];
|
|
366
|
+
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
expandedStartOf( index ) {
|
|
370
|
+
|
|
371
|
+
return this.tplExpandedStart[ this.sourceMesh[ index ] ];
|
|
372
|
+
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
blasOffsetOf( index ) {
|
|
376
|
+
|
|
377
|
+
return this.tplBlasOffset[ this.sourceMesh[ index ] ];
|
|
378
|
+
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
blasNodeCountOf( index ) {
|
|
382
|
+
|
|
383
|
+
return this.tplNodeCount[ this.sourceMesh[ index ] ];
|
|
384
|
+
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** Record a rebuilt BLAS's node count. May shrink; never past what the build allocated. */
|
|
388
|
+
setBlasNodeCount( index, nodeCount ) {
|
|
389
|
+
|
|
390
|
+
this.tplNodeCount[ this.sourceMesh[ index ] ] = nodeCount;
|
|
391
|
+
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** Record the stored-order to caller-order map for a rebuilt BLAS. */
|
|
395
|
+
setBvhToOriginal( index, map ) {
|
|
396
|
+
|
|
397
|
+
this.bvhToOriginal.set( this.sourceMesh[ index ], map );
|
|
398
|
+
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* The per-BLAS triangle reorder map this placement traverses through. A placement that
|
|
403
|
+
* borrows another's BLAS borrows its map too — only the owner's template carries one.
|
|
404
|
+
*/
|
|
405
|
+
bvhToOriginalOf( index ) {
|
|
406
|
+
|
|
407
|
+
const t = this.sourceMesh[ index ];
|
|
408
|
+
const own = this.bvhToOriginal.get( t );
|
|
409
|
+
if ( own ) return own;
|
|
410
|
+
|
|
411
|
+
const owner = this.tplOwner[ t ];
|
|
412
|
+
return ( owner >= 0 ? this.bvhToOriginal.get( this.sourceMesh[ owner ] ) : null ) || null;
|
|
413
|
+
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** A view of the 16 object-to-world floats for `index`. Cold paths only. */
|
|
417
|
+
matrixWorldOf( index ) {
|
|
418
|
+
|
|
419
|
+
return this.world.subarray( index * 16, index * 16 + 16 );
|
|
420
|
+
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* The 16 world-to-object floats for `index`, inverted on demand. Allocates — per-mesh work
|
|
425
|
+
* only; the TLAS build inverts into its own scratch.
|
|
48
426
|
*/
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
this.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
427
|
+
matrixInverseOf( index ) {
|
|
428
|
+
|
|
429
|
+
return invertAffineInto( this.world, index * 16, new Float64Array( 16 ) );
|
|
430
|
+
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/** Writes this placement's world-space bounds into `out` at `off`. */
|
|
434
|
+
writeWorldAABB( index, out, off ) {
|
|
435
|
+
|
|
436
|
+
if ( ! this.isSet[ index ] ) {
|
|
437
|
+
|
|
438
|
+
for ( let k = 0; k < 6; k ++ ) out[ off + k ] = 0;
|
|
439
|
+
return;
|
|
440
|
+
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
transformAABBInto( this.tplObjectAABB, this.sourceMesh[ index ] * 6, this.world, index * 16, out, off );
|
|
444
|
+
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Every placement's world-space bounds, 6 floats each — what the TLAS sorts on.
|
|
449
|
+
* @param {Float64Array|Float32Array} out
|
|
450
|
+
*/
|
|
451
|
+
writeWorldAABBs( out ) {
|
|
452
|
+
|
|
453
|
+
const aabb = this.tplObjectAABB, world = this.world, src = this.sourceMesh, set = this.isSet;
|
|
454
|
+
|
|
455
|
+
for ( let i = 0; i < this.count; i ++ ) {
|
|
456
|
+
|
|
457
|
+
const o = i * 6;
|
|
458
|
+
|
|
459
|
+
if ( ! set[ i ] ) {
|
|
460
|
+
|
|
461
|
+
out[ o ] = 0; out[ o + 1 ] = 0; out[ o + 2 ] = 0;
|
|
462
|
+
out[ o + 3 ] = 0; out[ o + 4 ] = 0; out[ o + 5 ] = 0;
|
|
463
|
+
continue;
|
|
464
|
+
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
transformAABBInto( aabb, src[ i ] * 6, world, i * 16, out, o );
|
|
468
|
+
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* A plain snapshot of one placement, shaped like the old entry object.
|
|
475
|
+
*
|
|
476
|
+
* This allocates, so it is for per-mesh work (refit of a handful of meshes) only — never a
|
|
477
|
+
* loop over every placement, which is the cost this class exists to avoid.
|
|
478
|
+
*
|
|
479
|
+
* @param {number} i
|
|
480
|
+
* @returns {object|null}
|
|
481
|
+
*/
|
|
482
|
+
entryAt( i ) {
|
|
483
|
+
|
|
484
|
+
if ( ! this.isSet[ i ] ) return null;
|
|
485
|
+
|
|
486
|
+
const t = this.sourceMesh[ i ];
|
|
487
|
+
const owner = this.tplOwner[ t ];
|
|
488
|
+
|
|
489
|
+
return {
|
|
490
|
+
meshIndex: i,
|
|
491
|
+
blasOffset: this.tplBlasOffset[ t ],
|
|
492
|
+
blasNodeCount: this.tplNodeCount[ t ],
|
|
493
|
+
triOffset: this.tplTriOffset[ t ],
|
|
494
|
+
triCount: this.tplTriCount[ t ],
|
|
495
|
+
expandedStart: this.tplExpandedStart[ t ],
|
|
496
|
+
sourceMesh: t,
|
|
497
|
+
sharedFrom: owner === i ? null : owner,
|
|
498
|
+
matrixWorld: this.matrixWorldOf( i ),
|
|
499
|
+
matrixInverse: this.matrixInverseOf( i ),
|
|
500
|
+
flipWinding: !! this.flipWinding[ i ],
|
|
501
|
+
bvhToOriginal: this.bvhToOriginal.get( t ) || null,
|
|
502
|
+
visible: !! this.visible[ i ],
|
|
503
|
+
tlasLeafIndex: this.tlasLeafIndex[ i ],
|
|
62
504
|
};
|
|
63
505
|
|
|
64
506
|
}
|
|
65
507
|
|
|
66
508
|
/**
|
|
67
|
-
* Set per-mesh visibility flag. Does NOT update the GPU buffer —
|
|
68
|
-
*
|
|
509
|
+
* Set per-mesh visibility flag. Does NOT update the GPU buffer — caller must patch
|
|
510
|
+
* combinedBvhData[tlasLeafIndex*16 + 2] and mark the bvh attr dirty.
|
|
69
511
|
*
|
|
70
512
|
* @param {number} meshIndex
|
|
71
513
|
* @param {boolean} visible
|
|
72
514
|
*/
|
|
73
515
|
setVisibility( meshIndex, visible ) {
|
|
74
516
|
|
|
75
|
-
|
|
76
|
-
if ( entry ) entry.visible = visible;
|
|
517
|
+
if ( this.isSet[ meshIndex ] ) this.visible[ meshIndex ] = visible ? 1 : 0;
|
|
77
518
|
|
|
78
519
|
}
|
|
79
520
|
|
|
80
521
|
/**
|
|
81
|
-
* Compute
|
|
82
|
-
* O(1) per
|
|
522
|
+
* Compute object-space AABBs for every template from its BLAS root node.
|
|
523
|
+
* O(1) per template for inner roots; falls back to a triangle scan for leaf roots (rare).
|
|
83
524
|
*
|
|
84
525
|
* @param {Float32Array} triangleData - Global triangle data (needed for leaf-root fallback)
|
|
85
526
|
*/
|
|
86
527
|
computeAABBs( triangleData ) {
|
|
87
528
|
|
|
88
|
-
for (
|
|
529
|
+
for ( let t = 0; t < this.templateCount; t ++ ) {
|
|
530
|
+
|
|
531
|
+
if ( this.tplOwner[ t ] < 0 ) continue;
|
|
532
|
+
const blas = this.blasData.get( t );
|
|
533
|
+
if ( blas ) this._readRootAABB( blas, t, triangleData, this.tplObjectAABB, t * 6 );
|
|
534
|
+
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Templates aliasing another's BLAS share its object bounds; only the transform differs.
|
|
538
|
+
for ( let t = 0; t < this.templateCount; t ++ ) {
|
|
89
539
|
|
|
90
|
-
|
|
91
|
-
|
|
540
|
+
const owner = this.tplOwner[ t ];
|
|
541
|
+
if ( owner < 0 || this.blasData.has( t ) ) continue;
|
|
542
|
+
const ot = this.sourceMesh[ owner ];
|
|
543
|
+
if ( ot !== t ) this.tplObjectAABB.copyWithin( t * 6, ot * 6, ot * 6 + 6 );
|
|
92
544
|
|
|
93
545
|
}
|
|
94
546
|
|
|
95
547
|
}
|
|
96
548
|
|
|
97
549
|
/**
|
|
98
|
-
* Recompute
|
|
550
|
+
* Recompute a placement's template bounds after its BLAS was refit.
|
|
99
551
|
* Reads from the combined bvhData buffer at the BLAS root offset.
|
|
100
552
|
*
|
|
101
553
|
* @param {number} entryIndex
|
|
@@ -104,43 +556,39 @@ export class InstanceTable {
|
|
|
104
556
|
*/
|
|
105
557
|
recomputeAABB( entryIndex, combinedBvhData, triangleData ) {
|
|
106
558
|
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
|
|
559
|
+
const t = this.sourceMesh[ entryIndex ];
|
|
560
|
+
const n = this.tplBlasOffset[ t ];
|
|
561
|
+
// Flat buffer or ChunkedRecords: either way, take that one node's 16 floats.
|
|
562
|
+
const node = combinedBvhData.chunks
|
|
563
|
+
? combinedBvhData.chunkFor( n ).subarray( combinedBvhData.baseOf( n ), combinedBvhData.baseOf( n ) + 16 )
|
|
564
|
+
: combinedBvhData.subarray( n * 16, n * 16 + 16 );
|
|
565
|
+
this._readRootAABB( node, t, triangleData, this.tplObjectAABB, t * 6 );
|
|
110
566
|
|
|
111
567
|
}
|
|
112
568
|
|
|
113
569
|
/**
|
|
114
|
-
* Read the root node's AABB from a flat BVH data array
|
|
570
|
+
* Read the root node's AABB from a flat BVH data array into `out` at `off`.
|
|
115
571
|
* Inner root: union of left+right child AABBs (O(1)).
|
|
116
|
-
* Leaf root: scan triangles (rare — only meshes with
|
|
572
|
+
* Leaf root: scan triangles (rare — only meshes with <= maxLeafSize tris).
|
|
117
573
|
* @private
|
|
118
574
|
*/
|
|
119
|
-
_readRootAABB( bvhData,
|
|
575
|
+
_readRootAABB( bvhData, template, triangleData, out, off ) {
|
|
120
576
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if ( marker === - 1 ) {
|
|
577
|
+
if ( ! bvhData || bvhIndexView( bvhData )[ 3 ] === BVH_LEAF_MARKERS.TRIANGLE_LEAF ) {
|
|
124
578
|
|
|
125
579
|
// Root is a leaf — very small mesh. Scan its triangles.
|
|
126
|
-
|
|
580
|
+
this._computeAABBFromTriangles( template, triangleData, out, off );
|
|
581
|
+
return;
|
|
127
582
|
|
|
128
583
|
}
|
|
129
584
|
|
|
130
585
|
// Inner node: [leftMin.xyz, leftChild] [leftMax.xyz, rightChild] [rightMin.xyz, 0] [rightMax.xyz, 0]
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
minX: Math.min( lMinX, rMinX ),
|
|
138
|
-
minY: Math.min( lMinY, rMinY ),
|
|
139
|
-
minZ: Math.min( lMinZ, rMinZ ),
|
|
140
|
-
maxX: Math.max( lMaxX, rMaxX ),
|
|
141
|
-
maxY: Math.max( lMaxY, rMaxY ),
|
|
142
|
-
maxZ: Math.max( lMaxZ, rMaxZ ),
|
|
143
|
-
};
|
|
586
|
+
out[ off ] = Math.min( bvhData[ 0 ], bvhData[ 8 ] );
|
|
587
|
+
out[ off + 1 ] = Math.min( bvhData[ 1 ], bvhData[ 9 ] );
|
|
588
|
+
out[ off + 2 ] = Math.min( bvhData[ 2 ], bvhData[ 10 ] );
|
|
589
|
+
out[ off + 3 ] = Math.max( bvhData[ 4 ], bvhData[ 12 ] );
|
|
590
|
+
out[ off + 4 ] = Math.max( bvhData[ 5 ], bvhData[ 13 ] );
|
|
591
|
+
out[ off + 5 ] = Math.max( bvhData[ 6 ], bvhData[ 14 ] );
|
|
144
592
|
|
|
145
593
|
}
|
|
146
594
|
|
|
@@ -148,22 +596,28 @@ export class InstanceTable {
|
|
|
148
596
|
* Compute AABB by scanning triangle positions (fallback for leaf-root BLASes).
|
|
149
597
|
* @private
|
|
150
598
|
*/
|
|
151
|
-
_computeAABBFromTriangles(
|
|
599
|
+
_computeAABBFromTriangles( template, triangleData, out, off ) {
|
|
152
600
|
|
|
153
601
|
const FPT = TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE;
|
|
602
|
+
// Flat array or ChunkedRecords: resolve the chunk once per triangle either way.
|
|
603
|
+
const chunked = triangleData && triangleData.chunks ? triangleData.viewAs( Float32Array ) : null;
|
|
604
|
+
const flat = chunked ? null : ( triangleData instanceof Float32Array
|
|
605
|
+
? triangleData
|
|
606
|
+
: new Float32Array( triangleData.buffer, triangleData.byteOffset, triangleData.length ) );
|
|
154
607
|
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
155
608
|
let maxX = - Infinity, maxY = - Infinity, maxZ = - Infinity;
|
|
156
609
|
|
|
157
|
-
|
|
610
|
+
const triOffset = this.tplTriOffset[ template ], triCount = this.tplTriCount[ template ];
|
|
611
|
+
for ( let t = 0; t < triCount; t ++ ) {
|
|
158
612
|
|
|
159
|
-
const
|
|
613
|
+
const gi = triOffset + t;
|
|
614
|
+
const tri = chunked ? chunked.chunkFor( gi ) : flat;
|
|
615
|
+
const base = chunked ? chunked.baseOf( gi ) : gi * FPT;
|
|
160
616
|
|
|
161
|
-
//
|
|
162
|
-
for ( let
|
|
617
|
+
// Positions A (offset 0), B (offset 4), C (offset 8)
|
|
618
|
+
for ( let o = 0; o <= 8; o += 4 ) {
|
|
163
619
|
|
|
164
|
-
const x =
|
|
165
|
-
const y = triangleData[ base + off + 1 ];
|
|
166
|
-
const z = triangleData[ base + off + 2 ];
|
|
620
|
+
const x = tri[ base + o ], y = tri[ base + o + 1 ], z = tri[ base + o + 2 ];
|
|
167
621
|
if ( x < minX ) minX = x;
|
|
168
622
|
if ( y < minY ) minY = y;
|
|
169
623
|
if ( z < minZ ) minZ = z;
|
|
@@ -175,14 +629,13 @@ export class InstanceTable {
|
|
|
175
629
|
|
|
176
630
|
}
|
|
177
631
|
|
|
178
|
-
|
|
632
|
+
out[ off ] = minX; out[ off + 1 ] = minY; out[ off + 2 ] = minZ;
|
|
633
|
+
out[ off + 3 ] = maxX; out[ off + 4 ] = maxY; out[ off + 5 ] = maxZ;
|
|
179
634
|
|
|
180
635
|
}
|
|
181
636
|
|
|
182
637
|
/**
|
|
183
|
-
* Assign BLAS offsets in the combined BVH buffer.
|
|
184
|
-
* Called after TLAS node count is known.
|
|
185
|
-
*
|
|
638
|
+
* Assign BLAS offsets in the combined BVH buffer. Called once the TLAS node count is known.
|
|
186
639
|
* @param {number} tlasNodeCount - Number of nodes in the TLAS
|
|
187
640
|
*/
|
|
188
641
|
assignOffsets( tlasNodeCount ) {
|
|
@@ -190,42 +643,40 @@ export class InstanceTable {
|
|
|
190
643
|
this.tlasNodeCount = tlasNodeCount;
|
|
191
644
|
let offset = tlasNodeCount;
|
|
192
645
|
|
|
193
|
-
for (
|
|
646
|
+
for ( let t = 0; t < this.templateCount; t ++ ) {
|
|
194
647
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
648
|
+
const owner = this.tplOwner[ t ];
|
|
649
|
+
if ( owner < 0 || this.sourceMesh[ owner ] !== t ) continue;
|
|
650
|
+
this.tplBlasOffset[ t ] = offset;
|
|
651
|
+
offset += this.tplNodeCount[ t ];
|
|
198
652
|
|
|
199
653
|
}
|
|
200
654
|
|
|
201
|
-
this.
|
|
655
|
+
for ( let t = 0; t < this.templateCount; t ++ ) {
|
|
202
656
|
|
|
203
|
-
|
|
657
|
+
const owner = this.tplOwner[ t ];
|
|
658
|
+
if ( owner < 0 ) continue;
|
|
659
|
+
const ot = this.sourceMesh[ owner ];
|
|
660
|
+
if ( ot !== t ) this.tplBlasOffset[ t ] = this.tplBlasOffset[ ot ];
|
|
204
661
|
|
|
205
|
-
|
|
206
|
-
* Total node count (TLAS + all BLASes).
|
|
207
|
-
*/
|
|
208
|
-
get totalNodeCount() {
|
|
662
|
+
}
|
|
209
663
|
|
|
210
|
-
|
|
664
|
+
this.totalBLASNodes = offset - tlasNodeCount;
|
|
665
|
+
assertBVHIndexFits( offset, 'combined TLAS + BLAS node count' );
|
|
211
666
|
|
|
212
667
|
}
|
|
213
668
|
|
|
214
|
-
/**
|
|
215
|
-
|
|
216
|
-
*/
|
|
217
|
-
get count() {
|
|
669
|
+
/** Total node count (TLAS + all BLASes). */
|
|
670
|
+
get totalNodeCount() {
|
|
218
671
|
|
|
219
|
-
return this.
|
|
672
|
+
return this.tlasNodeCount + this.totalBLASNodes;
|
|
220
673
|
|
|
221
674
|
}
|
|
222
675
|
|
|
223
|
-
/**
|
|
224
|
-
* Reset all entries.
|
|
225
|
-
*/
|
|
676
|
+
/** Reset all columns. */
|
|
226
677
|
clear() {
|
|
227
678
|
|
|
228
|
-
this.
|
|
679
|
+
this.allocate( 0 );
|
|
229
680
|
this.totalBLASNodes = 0;
|
|
230
681
|
this.tlasNodeCount = 0;
|
|
231
682
|
|