rayzee 11.2.1 → 11.3.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 +58 -29
- package/dist/ArchiveImporter-D735xX-4.js +7860 -0
- package/dist/BidirectionalIntegrator-M89c0AlP.js +954 -0
- package/dist/{BufferLayout-D_IBo2ci.js → BufferLayout-xPRH9jYD.js} +41 -5
- package/dist/{NeuralRetouch-Cr-otSW8.js → NeuralRetouch-DiBH63kz.js} +1 -1
- package/dist/{NeuralSuperRes-B7E3_M0t.js → NeuralSuperRes-CZNQP13W.js} +1 -1
- package/dist/{PathTracer-D6nbknj5.js → PathTracer-BXCCVrPK.js} +4371 -4612
- package/dist/{PhysicalSky-Dq5VSiCQ.js → PhysicalSky-DqmaS0lb.js} +2 -2
- package/dist/{RayzeeRenderer-DbjqO6v-.js → RayzeeRenderer-DUSMFhmc.js} +426 -363
- package/dist/SolarLimb-bZE6y63S.js +762 -0
- package/dist/addons/archives.es.js +1 -1
- package/dist/addons/bidirectional.es.js +1 -1
- package/dist/addons/physical-sky.es.js +1 -1
- package/dist/rayzee-core.es.js +7 -7
- package/dist/rayzee.es.js +472 -468
- package/dist/rayzee.umd.js +106 -105
- package/dist/sceneCachePolicy-BAib5fCp.js +1602 -0
- package/package.json +1 -1
- package/src/PathTracerApp.js +4 -3
- package/src/Processor/ArchiveImporter.js +173 -94
- package/src/Processor/ArchiveReader.js +20 -23
- package/src/Processor/AssetLoader.js +48 -1
- package/src/Processor/BVHRefitter.js +109 -13
- package/src/Processor/BufferLayout.js +115 -0
- package/src/Processor/ChunkedRecords.js +3 -2
- package/src/Processor/EmissiveTriangleBuilder.js +6 -6
- package/src/Processor/GeometryExtractor.js +189 -79
- package/src/Processor/HostMemory.js +39 -9
- package/src/Processor/InstanceTable.js +714 -42
- package/src/Processor/PBRT/PBRTCurves.js +224 -6
- package/src/Processor/PBRT/PBRTMaterials.js +3 -0
- package/src/Processor/PBRT/PBRTSceneBuilder.js +82 -25
- package/src/Processor/PackedRayBuffer.js +5 -2
- package/src/Processor/SceneProcessor.js +747 -141
- package/src/Processor/TLASBuilder.js +32 -17
- package/src/Processor/USD/USDLayer.js +1149 -0
- package/src/Processor/USD/USDScene.js +1655 -0
- package/src/Processor/USD/USDStage.js +682 -0
- package/src/Processor/USD/USDText.js +674 -0
- package/src/Processor/USD/index.js +138 -0
- package/src/Processor/Workers/BVHRefitWorker.js +3 -3
- package/src/RayzeeRenderer.js +167 -26
- package/src/Stages/NormalDepth.js +7 -12
- package/src/Stages/PathTracer.js +12 -17
- package/src/Stages/PathTracerStage.js +273 -96
- package/src/Storage/GeometrySpill.js +46 -14
- package/src/Storage/SceneGraphCodec.js +17 -3
- package/src/Storage/SpillStore.js +9 -0
- package/src/TSL/BVHTraversal.js +183 -7
- package/src/TSL/Common.js +57 -13
- package/src/TSL/ShadeKernel.js +3 -2
- package/src/TSL/TextureSampling.js +11 -8
- package/src/TSL/patches.js +84 -0
- package/src/integrators/BidirectionalIntegrator.js +20 -2
- package/src/managers/DenoisingManager.js +4 -4
- package/src/managers/InteractionManager.js +19 -6
- package/src/managers/MaterialDataManager.js +22 -5
- package/src/managers/UniformManager.js +3 -0
- package/dist/ArchiveImporter-D_x863yu.js +0 -5198
- package/dist/BidirectionalIntegrator-DmkxxEOR.js +0 -946
- package/dist/SolarLimb-CPaeZVwz.js +0 -721
- package/dist/sceneCachePolicy-C4-4qwt6.js +0 -485
|
@@ -9,10 +9,17 @@
|
|
|
9
9
|
* denormalised one. World bounds and the inverse transform are derived on demand.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
TRIANGLE_DATA_LAYOUT, BVH_LEAF_MARKERS, BVH_EMPTY_BOX, assertBVHIndexFits, bvhIndexView,
|
|
14
|
+
CLUSTER_SIZE, CLUSTER_FIRST_MASK, CLUSTER_COUNT_SHIFT, CLUSTER_COPY_HIDDEN, RECORD_VEC4, TLAS_LEAF_IDENTITY,
|
|
15
|
+
encodeClusterBoxes as encodeClusterBoxBytes,
|
|
16
|
+
} from './BufferLayout.js';
|
|
13
17
|
|
|
14
18
|
const IDENTITY = Float64Array.from( [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] );
|
|
15
19
|
|
|
20
|
+
/** Passed as a placement's matrix when its row already holds it (a pooled build). */
|
|
21
|
+
export const IN_PLACE = Object.freeze( [] );
|
|
22
|
+
|
|
16
23
|
/** Determinant of the upper-left 3x3 at `off`. Negative means the transform mirrors. */
|
|
17
24
|
export function determinant3At( m, off ) {
|
|
18
25
|
|
|
@@ -165,8 +172,8 @@ export class InstanceTable {
|
|
|
165
172
|
*
|
|
166
173
|
* @param {number} count - placements
|
|
167
174
|
* @param {number} [templateCount] - distinct source meshes; defaults to one per placement
|
|
168
|
-
* @param {Float32Array} [worldPool] - adopted
|
|
169
|
-
*
|
|
175
|
+
* @param {Float32Array|MatrixRuns} [worldPool] - the transforms, adopted rather than copied: one array of a row a
|
|
176
|
+
* placement, or runs of rows (GeometryExtractor) reading an InstancedMesh's own matrix list where it already is
|
|
170
177
|
* @param {Int32Array} [sourcePool] - likewise for the source-mesh column
|
|
171
178
|
*/
|
|
172
179
|
allocate( count, templateCount = count, worldPool = null, sourcePool = null ) {
|
|
@@ -186,17 +193,8 @@ export class InstanceTable {
|
|
|
186
193
|
// world space. A move composes the new world matrix against this.
|
|
187
194
|
this.tplBakeInverse = null;
|
|
188
195
|
|
|
189
|
-
if ( worldPool
|
|
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
|
-
}
|
|
196
|
+
if ( worldPool?.first ) this._adoptRuns( worldPool );
|
|
197
|
+
else this._adoptRuns( { first: [ 0, count ], arrays: [ worldPool && worldPool.length >= count * 16 ? worldPool : new Float32Array( count * 16 ) ], arrayOf: [ 0 ], base: [ 0 ], owned: [ worldPool ? 0 : 1 ] } );
|
|
200
198
|
|
|
201
199
|
this.tplTriOffset = new Int32Array( templateCount );
|
|
202
200
|
this.tplTriCount = new Int32Array( templateCount );
|
|
@@ -208,6 +206,26 @@ export class InstanceTable {
|
|
|
208
206
|
|
|
209
207
|
this._placementRuns = null;
|
|
210
208
|
|
|
209
|
+
// TLAS entries: one a placement, or one a copy of a group — instanced meshes placed by one shared matrix list,
|
|
210
|
+
// traced as one object through a small tree over their BLASes. Null maps are the identity.
|
|
211
|
+
this.groups = [];
|
|
212
|
+
this.groupOfTemplate = null;
|
|
213
|
+
this.memberSlot = null;
|
|
214
|
+
this.entryCount = count;
|
|
215
|
+
this.entryRep = null;
|
|
216
|
+
this.entryGroup = null;
|
|
217
|
+
this.placementEntry = null;
|
|
218
|
+
this.groupNodeStart = 0;
|
|
219
|
+
this.groupNodeCount = 0;
|
|
220
|
+
|
|
221
|
+
// Copy clusters (planClusters / formClusters): the TLAS is built over clusters of entries, and each entry's
|
|
222
|
+
// transform is a record behind the BLASes. Cluster c owns the CLUSTER_SIZE records from c · CLUSTER_SIZE.
|
|
223
|
+
this.clusterCount = 0;
|
|
224
|
+
this.recordEntry = null;
|
|
225
|
+
this.clusterLeaf = null;
|
|
226
|
+
this.recordNodeStart = 0;
|
|
227
|
+
this.recordNodeCount = 0;
|
|
228
|
+
|
|
211
229
|
// Keyed by template, and only templates that own a BLAS carry them.
|
|
212
230
|
this.originalToBvhMap = new Map();
|
|
213
231
|
this.bvhToOriginal = new Map();
|
|
@@ -221,24 +239,9 @@ export class InstanceTable {
|
|
|
221
239
|
this.isSet[ index ] = 1;
|
|
222
240
|
this.sourceMesh[ index ] = template;
|
|
223
241
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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;
|
|
242
|
+
if ( matrixWorld !== IN_PLACE ) this._writeRow( index, matrixWorld ?? IDENTITY, matrixWorld ? matrixOffset : 0 );
|
|
243
|
+
const o = this.matrixRow( index );
|
|
244
|
+
this.flipWinding[ index ] = determinant3At( this.rowArray, o ) < 0 ? 1 : 0;
|
|
242
245
|
|
|
243
246
|
}
|
|
244
247
|
|
|
@@ -300,6 +303,364 @@ export class InstanceTable {
|
|
|
300
303
|
|
|
301
304
|
}
|
|
302
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Groups instanced meshes placed by one shared matrix list: each copy becomes one TLAS entry, whose subtree is a
|
|
308
|
+
* small tree over the members' BLAS roots in the copies' shared object space. Every member keeps its placements
|
|
309
|
+
* (transforms, visibility, refit stay per mesh); only the TLAS sees one entry where it saw one per member.
|
|
310
|
+
*
|
|
311
|
+
* @param {Array<{members: ArrayLike<number>, starts: ArrayLike<number>, count: number}>} groups - members are
|
|
312
|
+
* templates, `starts` the first placement of each, all `count` long
|
|
313
|
+
*/
|
|
314
|
+
setGroups( groups ) {
|
|
315
|
+
|
|
316
|
+
this.groups = [];
|
|
317
|
+
this.groupOfTemplate = null;
|
|
318
|
+
this.memberSlot = null;
|
|
319
|
+
this.entryCount = this.count;
|
|
320
|
+
this.entryRep = null;
|
|
321
|
+
this.entryGroup = null;
|
|
322
|
+
this.placementEntry = null;
|
|
323
|
+
this.groupNodeCount = 0;
|
|
324
|
+
|
|
325
|
+
const valid = ( groups ?? [] ).filter( g => g.members.length >= 2 && Array.from( g.starts ).every( s => s >= 0 && s + g.count <= this.count ) );
|
|
326
|
+
if ( valid.length === 0 ) return;
|
|
327
|
+
|
|
328
|
+
const ofTemplate = new Int32Array( this.templateCount ).fill( - 1 );
|
|
329
|
+
const slot = new Int32Array( this.templateCount );
|
|
330
|
+
let nodes = 1; // the empty leaf a hidden member points at
|
|
331
|
+
|
|
332
|
+
for ( const g of valid ) {
|
|
333
|
+
|
|
334
|
+
const id = this.groups.length;
|
|
335
|
+
const members = Int32Array.from( g.members );
|
|
336
|
+
this.groups.push( { members, starts: Int32Array.from( g.starts ), count: g.count, offset: - 1, nodes: members.length - 1, aabb: new Float32Array( 6 ) } );
|
|
337
|
+
for ( let j = 0; j < members.length; j ++ ) {
|
|
338
|
+
|
|
339
|
+
ofTemplate[ members[ j ] ] = id;
|
|
340
|
+
slot[ members[ j ] ] = j;
|
|
341
|
+
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
nodes += members.length - 1;
|
|
345
|
+
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const n = this.count, src = this.sourceMesh;
|
|
349
|
+
const entryOf = new Int32Array( n );
|
|
350
|
+
let entries = 0;
|
|
351
|
+
for ( let p = 0; p < n; p ++ ) {
|
|
352
|
+
|
|
353
|
+
const t = src[ p ];
|
|
354
|
+
if ( ofTemplate[ t ] < 0 || slot[ t ] === 0 ) entryOf[ p ] = entries ++;
|
|
355
|
+
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const rep = new Int32Array( entries );
|
|
359
|
+
const group = new Int32Array( entries ).fill( - 1 );
|
|
360
|
+
for ( let p = 0; p < n; p ++ ) {
|
|
361
|
+
|
|
362
|
+
const t = src[ p ];
|
|
363
|
+
const g = ofTemplate[ t ];
|
|
364
|
+
if ( g >= 0 && slot[ t ] !== 0 ) continue;
|
|
365
|
+
rep[ entryOf[ p ] ] = p;
|
|
366
|
+
if ( g >= 0 ) group[ entryOf[ p ] ] = g;
|
|
367
|
+
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
for ( const g of this.groups ) {
|
|
371
|
+
|
|
372
|
+
const first = g.starts[ 0 ];
|
|
373
|
+
for ( let j = 1; j < g.members.length; j ++ ) entryOf.set( entryOf.subarray( first, first + g.count ), g.starts[ j ] );
|
|
374
|
+
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
this.groupOfTemplate = ofTemplate;
|
|
378
|
+
this.memberSlot = slot;
|
|
379
|
+
this.entryCount = entries;
|
|
380
|
+
this.entryRep = rep;
|
|
381
|
+
this.entryGroup = group;
|
|
382
|
+
this.placementEntry = entryOf;
|
|
383
|
+
this.groupNodeCount = nodes;
|
|
384
|
+
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** The placement whose transform an entry's TLAS leaf carries. */
|
|
388
|
+
repOf( entry ) {
|
|
389
|
+
|
|
390
|
+
return this.entryRep ? this.entryRep[ entry ] : entry;
|
|
391
|
+
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** The TLAS entry a placement is traced through. */
|
|
395
|
+
entryOf( placement ) {
|
|
396
|
+
|
|
397
|
+
return this.placementEntry ? this.placementEntry[ placement ] : placement;
|
|
398
|
+
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** Group of an entry, or -1. */
|
|
402
|
+
groupOfEntry( entry ) {
|
|
403
|
+
|
|
404
|
+
return this.entryGroup ? this.entryGroup[ entry ] : - 1;
|
|
405
|
+
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** The node an entry's leaf points at: its template's BLAS root, or its group's tree. */
|
|
409
|
+
entryRoot( entry ) {
|
|
410
|
+
|
|
411
|
+
const g = this.groupOfEntry( entry );
|
|
412
|
+
return g >= 0 ? this.groups[ g ].offset : this.tplBlasOffset[ this.sourceMesh[ this.repOf( entry ) ] ];
|
|
413
|
+
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** An entry is visible while any member placement of it is. */
|
|
417
|
+
entryVisible( entry ) {
|
|
418
|
+
|
|
419
|
+
const g = this.groupOfEntry( entry );
|
|
420
|
+
if ( g < 0 ) return this.visible[ this.repOf( entry ) ] === 1;
|
|
421
|
+
const { starts } = this.groups[ g ];
|
|
422
|
+
const copy = this.repOf( entry ) - starts[ 0 ];
|
|
423
|
+
for ( let j = 0; j < starts.length; j ++ ) if ( this.visible[ starts[ j ] + copy ] ) return true;
|
|
424
|
+
return false;
|
|
425
|
+
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/** Records `node` as the TLAS leaf of every placement traced through `entry`. */
|
|
429
|
+
setLeafOf( entry, node ) {
|
|
430
|
+
|
|
431
|
+
const g = this.groupOfEntry( entry );
|
|
432
|
+
const p = this.repOf( entry );
|
|
433
|
+
if ( g < 0 ) {
|
|
434
|
+
|
|
435
|
+
this.tlasLeafIndex[ p ] = node;
|
|
436
|
+
return;
|
|
437
|
+
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const { starts } = this.groups[ g ];
|
|
441
|
+
const copy = p - starts[ 0 ];
|
|
442
|
+
for ( let j = 0; j < starts.length; j ++ ) this.tlasLeafIndex[ starts[ j ] + copy ] = node;
|
|
443
|
+
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/** Each group's object-space bounds: every member's, hidden or not, so a toggle never moves the TLAS. */
|
|
447
|
+
computeGroupAABBs() {
|
|
448
|
+
|
|
449
|
+
for ( const g of this.groups ) {
|
|
450
|
+
|
|
451
|
+
const box = g.aabb;
|
|
452
|
+
box.fill( Infinity, 0, 3 );
|
|
453
|
+
box.fill( - Infinity, 3, 6 );
|
|
454
|
+
for ( const t of g.members ) {
|
|
455
|
+
|
|
456
|
+
if ( this.tplOwner[ t ] < 0 ) continue;
|
|
457
|
+
const o = t * 6;
|
|
458
|
+
for ( let a = 0; a < 3; a ++ ) {
|
|
459
|
+
|
|
460
|
+
box[ a ] = Math.min( box[ a ], this.tplObjectAABB[ o + a ] );
|
|
461
|
+
box[ 3 + a ] = Math.max( box[ 3 + a ], this.tplObjectAABB[ o + 3 + a ] );
|
|
462
|
+
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if ( box[ 0 ] > box[ 3 ] ) box.fill( 0 );
|
|
468
|
+
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/** The empty leaf a hidden group member points at: the group region's last node. */
|
|
474
|
+
get emptyGroupLeaf() {
|
|
475
|
+
|
|
476
|
+
return this.groupNodeStart + this.groupNodeCount - 1;
|
|
477
|
+
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* The group region's nodes, from the members' bounds, BLAS offsets and visibility: each group's tree, then the
|
|
482
|
+
* empty leaf a hidden member points at. Pre-order, so a parent precedes its children, as refit expects.
|
|
483
|
+
* @returns {Float32Array} groupNodeCount nodes of 16 floats, for node groupNodeStart on
|
|
484
|
+
*/
|
|
485
|
+
groupNodeBlock() {
|
|
486
|
+
|
|
487
|
+
const block = new Float32Array( this.groupNodeCount * 16 );
|
|
488
|
+
if ( ! this.groups.length ) return block;
|
|
489
|
+
|
|
490
|
+
const idx = new Uint32Array( block.buffer );
|
|
491
|
+
idx[ ( this.groupNodeCount - 1 ) * 16 + 3 ] = BVH_LEAF_MARKERS.TRIANGLE_LEAF;
|
|
492
|
+
for ( const g of this.groups ) this._writeGroupTree( g, block, idx );
|
|
493
|
+
return block;
|
|
494
|
+
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/** @private */
|
|
498
|
+
_writeGroupTree( g, block, idx ) {
|
|
499
|
+
|
|
500
|
+
const base = this.groupNodeStart;
|
|
501
|
+
const empty = this.emptyGroupLeaf;
|
|
502
|
+
const aabb = this.tplObjectAABB;
|
|
503
|
+
let next = g.offset;
|
|
504
|
+
|
|
505
|
+
const child = ( list ) => {
|
|
506
|
+
|
|
507
|
+
if ( list.length === 1 ) {
|
|
508
|
+
|
|
509
|
+
const j = list[ 0 ];
|
|
510
|
+
const t = g.members[ j ];
|
|
511
|
+
const shown = this.tplOwner[ t ] >= 0 && this.visible[ g.starts[ j ] ] === 1;
|
|
512
|
+
return shown ? { ref: this.tplBlasOffset[ t ], box: aabb.subarray( t * 6, t * 6 + 6 ) } : { ref: empty, box: null };
|
|
513
|
+
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const node = next ++;
|
|
517
|
+
const [ left, right ] = splitMembers( list, g.members, aabb );
|
|
518
|
+
const a = child( left );
|
|
519
|
+
const b = child( right );
|
|
520
|
+
const o = ( node - base ) * 16;
|
|
521
|
+
writeChildBox( block, o, a.box );
|
|
522
|
+
writeChildBox( block, o + 8, b.box );
|
|
523
|
+
idx[ o + 3 ] = a.ref;
|
|
524
|
+
idx[ o + 7 ] = b.ref;
|
|
525
|
+
return { ref: node, box: unionBoxes( a.box, b.box ) };
|
|
526
|
+
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
child( Array.from( g.members, ( _, j ) => j ) );
|
|
530
|
+
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Plans copy clusters: ⌈entries / CLUSTER_SIZE⌉ of them. Only how many, which is fixed before any bounds exist (the
|
|
535
|
+
* BVH layout and the BLAS cache need it then); formClusters picks the members.
|
|
536
|
+
*/
|
|
537
|
+
planClusters() {
|
|
538
|
+
|
|
539
|
+
this.clusterCount = Math.ceil( this.entryCount / CLUSTER_SIZE );
|
|
540
|
+
this.recordNodeCount = Math.ceil( this.entryCount * RECORD_VEC4 / 4 );
|
|
541
|
+
if ( this.entryCount >= CLUSTER_FIRST_MASK ) throw new RangeError( `${this.entryCount.toLocaleString()} copies is past what a copy cluster can address` );
|
|
542
|
+
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Picks each cluster's members: the entries halved at the centre median of their widest axis until runs of
|
|
547
|
+
* CLUSTER_SIZE remain, so a cluster holds near neighbours, whatever each draws. Records follow cluster order.
|
|
548
|
+
* @param {Float32Array} world - each entry's world bounds, entryCount × 6
|
|
549
|
+
* @returns {Float64Array} each cluster's world bounds, clusterCount × 6, what the TLAS sorts on
|
|
550
|
+
*/
|
|
551
|
+
formClusters( world ) {
|
|
552
|
+
|
|
553
|
+
const n = this.entryCount;
|
|
554
|
+
const sorted = new Int32Array( n );
|
|
555
|
+
for ( let e = 0; e < n; e ++ ) sorted[ e ] = e;
|
|
556
|
+
splitIntoRuns( sorted, 0, n, world );
|
|
557
|
+
|
|
558
|
+
const bounds = new Float64Array( this.clusterCount * 6 );
|
|
559
|
+
for ( let c = 0; c < this.clusterCount; c ++ ) {
|
|
560
|
+
|
|
561
|
+
const r0 = c * CLUSTER_SIZE, end = Math.min( r0 + CLUSTER_SIZE, n );
|
|
562
|
+
const b = c * 6;
|
|
563
|
+
bounds[ b ] = bounds[ b + 1 ] = bounds[ b + 2 ] = Infinity;
|
|
564
|
+
bounds[ b + 3 ] = bounds[ b + 4 ] = bounds[ b + 5 ] = - Infinity;
|
|
565
|
+
for ( let r = r0; r < end; r ++ ) {
|
|
566
|
+
|
|
567
|
+
const o = sorted[ r ] * 6;
|
|
568
|
+
for ( let a = 0; a < 3; a ++ ) {
|
|
569
|
+
|
|
570
|
+
if ( world[ o + a ] < bounds[ b + a ] ) bounds[ b + a ] = world[ o + a ];
|
|
571
|
+
if ( world[ o + 3 + a ] > bounds[ b + 3 + a ] ) bounds[ b + 3 + a ] = world[ o + 3 + a ];
|
|
572
|
+
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
this.recordEntry = sorted;
|
|
580
|
+
this.clusterLeaf = new Int32Array( this.clusterCount ).fill( - 1 );
|
|
581
|
+
return bounds;
|
|
582
|
+
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Writes cluster `c`'s TLAS leaf at float offset `o` of `data` (node `node`), its copies' boxes from `world`, and
|
|
587
|
+
* makes each copy's record its placements' instance id (`tlasLeafIndex`).
|
|
588
|
+
*/
|
|
589
|
+
writeClusterLeaf( data, idx, o, c, world, node ) {
|
|
590
|
+
|
|
591
|
+
const first = c * CLUSTER_SIZE, count = Math.min( CLUSTER_SIZE, this.entryCount - first );
|
|
592
|
+
idx[ o ] = ( first | ( count - 1 ) << CLUSTER_COUNT_SHIFT ) >>> 0;
|
|
593
|
+
idx[ o + 3 ] = BVH_LEAF_MARKERS.CLUSTER_LEAF;
|
|
594
|
+
for ( let k = 0; k < CLUSTER_SIZE; k ++ ) idx[ o + 12 + k ] = k < count ? this.copyWord( this.recordEntry[ first + k ] ) : 0;
|
|
595
|
+
this.encodeClusterBoxes( data, idx, o, c, world );
|
|
596
|
+
this.clusterLeaf[ c ] = node;
|
|
597
|
+
for ( let r = first; r < first + count; r ++ ) this.setLeafOf( this.recordEntry[ r ], r );
|
|
598
|
+
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/** A cluster leaf's word for `entry` (BufferLayout): its root, whether its transform is identity, and hidden. */
|
|
602
|
+
copyWord( entry ) {
|
|
603
|
+
|
|
604
|
+
const o = this.matrixRow( this.repOf( entry ) );
|
|
605
|
+
const identity = isIdentityAt( this.rowArray, o ) ? TLAS_LEAF_IDENTITY : 0;
|
|
606
|
+
return ( this.entryRoot( entry ) | identity | ( this.entryVisible( entry ) ? 0 : CLUSTER_COPY_HIDDEN ) ) >>> 0;
|
|
607
|
+
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Cluster `c`'s boxes into its leaf at `o` (BufferLayout `encodeClusterBoxes`): each copy's world box from `world`
|
|
612
|
+
* (entry-indexed, at build) or, without it, from its transform now. The union goes to `out` at `outOff`.
|
|
613
|
+
*/
|
|
614
|
+
encodeClusterBoxes( data, idx, o, c, world = null, out = null, outOff = 0 ) {
|
|
615
|
+
|
|
616
|
+
const first = c * CLUSTER_SIZE, count = Math.min( CLUSTER_SIZE, this.entryCount - first );
|
|
617
|
+
const boxes = _copyBoxes;
|
|
618
|
+
for ( let k = 0; k < count; k ++ ) {
|
|
619
|
+
|
|
620
|
+
const e = this.recordEntry[ first + k ];
|
|
621
|
+
if ( world ) for ( let i = 0; i < 6; i ++ ) boxes[ k * 6 + i ] = world[ e * 6 + i ];
|
|
622
|
+
else this.writeEntryWorldAABB( e, boxes, k * 6 );
|
|
623
|
+
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
encodeClusterBoxBytes( data, idx, o, boxes, count, out, outOff );
|
|
627
|
+
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/** A copy record: placement `p`'s world-to-object rows, laid out as a leaf's slots 4–15, at `off` of `out`. */
|
|
631
|
+
writeRecord( out, off, p ) {
|
|
632
|
+
|
|
633
|
+
const inv = _recordInverse;
|
|
634
|
+
const o = this.matrixRow( p );
|
|
635
|
+
invertAffineInto( this.rowArray, o, inv );
|
|
636
|
+
out[ off ] = inv[ 0 ]; out[ off + 1 ] = inv[ 4 ]; out[ off + 2 ] = inv[ 8 ]; out[ off + 3 ] = inv[ 12 ];
|
|
637
|
+
out[ off + 4 ] = inv[ 1 ]; out[ off + 5 ] = inv[ 5 ]; out[ off + 6 ] = inv[ 9 ]; out[ off + 7 ] = inv[ 13 ];
|
|
638
|
+
out[ off + 8 ] = inv[ 2 ]; out[ off + 9 ] = inv[ 6 ]; out[ off + 10 ] = inv[ 10 ]; out[ off + 11 ] = inv[ 14 ];
|
|
639
|
+
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/** The TLAS leaf holding placement `p`: its cluster's when clustered. */
|
|
643
|
+
leafOfPlacement( p ) {
|
|
644
|
+
|
|
645
|
+
const id = this.tlasLeafIndex[ p ];
|
|
646
|
+
return id < 0 || ! this.clusterCount ? id : this.clusterLeaf[ this.clusterOf( id ) ];
|
|
647
|
+
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/** The slot of record `r`'s word in its cluster leaf (`copyWord`). */
|
|
651
|
+
copyWordSlot( r ) {
|
|
652
|
+
|
|
653
|
+
return 12 + r % CLUSTER_SIZE;
|
|
654
|
+
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/** The cluster holding record `r`. */
|
|
658
|
+
clusterOf( r ) {
|
|
659
|
+
|
|
660
|
+
return ( r / CLUSTER_SIZE ) | 0;
|
|
661
|
+
|
|
662
|
+
}
|
|
663
|
+
|
|
303
664
|
/** Placements actually built. */
|
|
304
665
|
get setCount() {
|
|
305
666
|
|
|
@@ -309,6 +670,109 @@ export class InstanceTable {
|
|
|
309
670
|
|
|
310
671
|
}
|
|
311
672
|
|
|
673
|
+
/**
|
|
674
|
+
* Placement transforms are runs of rows: run r covers placements first[ r ] … first[ r + 1 ] − 1, whose 16 floats
|
|
675
|
+
* each follow from base[ r ] in arrays[ arrayOf[ r ] ] — an array of the table's own, or an InstancedMesh's matrix
|
|
676
|
+
* attribute, read through it so its array can go to disk and come back (`adoptedMatrices`). An adopted list is never
|
|
677
|
+
* written: the first write to its run copies the run into an array of the table's own.
|
|
678
|
+
* @private
|
|
679
|
+
*/
|
|
680
|
+
_adoptRuns( { first, arrays, arrayOf, base, owned } ) {
|
|
681
|
+
|
|
682
|
+
this._runFirst = Int32Array.from( first );
|
|
683
|
+
this._runArrays = arrays.slice();
|
|
684
|
+
this._runArray = Int32Array.from( arrayOf );
|
|
685
|
+
this._runBase = Float64Array.from( base );
|
|
686
|
+
this._runOwned = Uint8Array.from( owned );
|
|
687
|
+
this._lastRun = 0;
|
|
688
|
+
this.rowArray = this._runArrays[ 0 ]?.array ?? this._runArrays[ 0 ] ?? null;
|
|
689
|
+
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/** The run holding placement `p`. @private */
|
|
693
|
+
_runOf( p ) {
|
|
694
|
+
|
|
695
|
+
const first = this._runFirst;
|
|
696
|
+
let r = this._lastRun;
|
|
697
|
+
if ( p >= first[ r ] && p < first[ r + 1 ] ) return r;
|
|
698
|
+
let lo = 0, hi = first.length - 2;
|
|
699
|
+
while ( lo < hi ) {
|
|
700
|
+
|
|
701
|
+
const mid = ( lo + hi + 1 ) >> 1;
|
|
702
|
+
if ( first[ mid ] <= p ) lo = mid;
|
|
703
|
+
else hi = mid - 1;
|
|
704
|
+
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
r = this._lastRun = lo;
|
|
708
|
+
return r;
|
|
709
|
+
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/** Where placement `p`'s 16 object-to-world floats start in `this.rowArray`, which this sets. */
|
|
713
|
+
matrixRow( p ) {
|
|
714
|
+
|
|
715
|
+
const r = this._runOf( p );
|
|
716
|
+
const a = this._runArrays[ this._runArray[ r ] ];
|
|
717
|
+
this.rowArray = a.array ?? a;
|
|
718
|
+
return this._runBase[ r ] + ( p - this._runFirst[ r ] ) * 16;
|
|
719
|
+
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/** The transform array when a single run of the table's own holds every placement, else null. */
|
|
723
|
+
get world() {
|
|
724
|
+
|
|
725
|
+
const a = this._runFirst.length === 2 && this._runBase[ 0 ] === 0 ? this._runArrays[ this._runArray[ 0 ] ] : null;
|
|
726
|
+
return a?.array ?? a;
|
|
727
|
+
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/** Bytes of transforms the table holds itself, not counting the matrix lists it reads in place. */
|
|
731
|
+
get matrixBytes() {
|
|
732
|
+
|
|
733
|
+
const own = new Set();
|
|
734
|
+
for ( let r = 0; r < this._runArray.length; r ++ ) if ( this._runOwned[ r ] ) own.add( this._runArrays[ this._runArray[ r ] ] );
|
|
735
|
+
let bytes = 0;
|
|
736
|
+
for ( const a of own ) bytes += a.byteLength;
|
|
737
|
+
return bytes;
|
|
738
|
+
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/** The InstancedMesh matrix attributes the table reads in place. */
|
|
742
|
+
adoptedMatrices() {
|
|
743
|
+
|
|
744
|
+
const out = new Set();
|
|
745
|
+
for ( let r = 0; r < this._runArray.length; r ++ ) {
|
|
746
|
+
|
|
747
|
+
const a = this._runArrays[ this._runArray[ r ] ];
|
|
748
|
+
if ( ! this._runOwned[ r ] && a.isBufferAttribute ) out.add( a );
|
|
749
|
+
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
return [ ...out ];
|
|
753
|
+
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/** @private */
|
|
757
|
+
_writeRow( p, m, mo ) {
|
|
758
|
+
|
|
759
|
+
const r = this._runOf( p );
|
|
760
|
+
if ( ! this._runOwned[ r ] ) {
|
|
761
|
+
|
|
762
|
+
const start = this._runBase[ r ], end = start + ( this._runFirst[ r + 1 ] - this._runFirst[ r ] ) * 16;
|
|
763
|
+
const a = this._runArrays[ this._runArray[ r ] ];
|
|
764
|
+
this._runArrays.push( ( a.array ?? a ).slice( start, end ) );
|
|
765
|
+
this._runArray[ r ] = this._runArrays.length - 1;
|
|
766
|
+
this._runBase[ r ] = 0;
|
|
767
|
+
this._runOwned[ r ] = 1;
|
|
768
|
+
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const o = this.matrixRow( p ), w = this.rowArray;
|
|
772
|
+
for ( let k = 0; k < 16; k ++ ) w[ o + k ] = m[ mo + k ];
|
|
773
|
+
|
|
774
|
+
}
|
|
775
|
+
|
|
312
776
|
/**
|
|
313
777
|
* Replace one placement's object-to-world transform. Its geometry is untouched, which is the
|
|
314
778
|
* whole point: a rigid move is a matrix change, and rewriting shared triangles instead would
|
|
@@ -416,7 +880,8 @@ export class InstanceTable {
|
|
|
416
880
|
/** A view of the 16 object-to-world floats for `index`. Cold paths only. */
|
|
417
881
|
matrixWorldOf( index ) {
|
|
418
882
|
|
|
419
|
-
|
|
883
|
+
const o = this.matrixRow( index );
|
|
884
|
+
return this.rowArray.subarray( o, o + 16 );
|
|
420
885
|
|
|
421
886
|
}
|
|
422
887
|
|
|
@@ -426,7 +891,8 @@ export class InstanceTable {
|
|
|
426
891
|
*/
|
|
427
892
|
matrixInverseOf( index ) {
|
|
428
893
|
|
|
429
|
-
|
|
894
|
+
const o = this.matrixRow( index );
|
|
895
|
+
return invertAffineInto( this.rowArray, o, new Float64Array( 16 ) );
|
|
430
896
|
|
|
431
897
|
}
|
|
432
898
|
|
|
@@ -440,17 +906,59 @@ export class InstanceTable {
|
|
|
440
906
|
|
|
441
907
|
}
|
|
442
908
|
|
|
443
|
-
|
|
909
|
+
const o = this.matrixRow( index );
|
|
910
|
+
transformAABBInto( this.tplObjectAABB, this.sourceMesh[ index ] * 6, this.rowArray, o, out, off );
|
|
911
|
+
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/** Writes a TLAS entry's world-space bounds — its group's, for a group copy — into `out` at `off`. */
|
|
915
|
+
writeEntryWorldAABB( entry, out, off ) {
|
|
916
|
+
|
|
917
|
+
const g = this.groupOfEntry( entry );
|
|
918
|
+
if ( g < 0 ) {
|
|
919
|
+
|
|
920
|
+
this.writeWorldAABB( this.repOf( entry ), out, off );
|
|
921
|
+
return;
|
|
922
|
+
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
const p = this.repOf( entry );
|
|
926
|
+
if ( ! this.isSet[ p ] ) {
|
|
927
|
+
|
|
928
|
+
for ( let k = 0; k < 6; k ++ ) out[ off + k ] = 0;
|
|
929
|
+
return;
|
|
930
|
+
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
const o = this.matrixRow( p );
|
|
934
|
+
transformAABBInto( this.groups[ g ].aabb, 0, this.rowArray, o, out, off );
|
|
935
|
+
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Every TLAS entry's world-space bounds, 6 floats each — what the TLAS sorts on.
|
|
940
|
+
* @param {Float64Array|Float32Array} out
|
|
941
|
+
*/
|
|
942
|
+
writeEntryWorldAABBs( out ) {
|
|
943
|
+
|
|
944
|
+
if ( ! this.entryRep ) {
|
|
945
|
+
|
|
946
|
+
this.writeWorldAABBs( out );
|
|
947
|
+
return;
|
|
948
|
+
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
for ( let e = 0; e < this.entryCount; e ++ ) this.writeEntryWorldAABB( e, out, e * 6 );
|
|
444
952
|
|
|
445
953
|
}
|
|
446
954
|
|
|
447
955
|
/**
|
|
448
|
-
* Every placement's world-space bounds, 6 floats each
|
|
956
|
+
* Every placement's world-space bounds, 6 floats each.
|
|
449
957
|
* @param {Float64Array|Float32Array} out
|
|
450
958
|
*/
|
|
451
959
|
writeWorldAABBs( out ) {
|
|
452
960
|
|
|
453
|
-
const aabb = this.tplObjectAABB,
|
|
961
|
+
const aabb = this.tplObjectAABB, src = this.sourceMesh, set = this.isSet;
|
|
454
962
|
|
|
455
963
|
for ( let i = 0; i < this.count; i ++ ) {
|
|
456
964
|
|
|
@@ -464,7 +972,8 @@ export class InstanceTable {
|
|
|
464
972
|
|
|
465
973
|
}
|
|
466
974
|
|
|
467
|
-
|
|
975
|
+
const m = this.matrixRow( i );
|
|
976
|
+
transformAABBInto( aabb, src[ i ] * 6, this.rowArray, m, out, o );
|
|
468
977
|
|
|
469
978
|
}
|
|
470
979
|
|
|
@@ -621,7 +1130,18 @@ export class InstanceTable {
|
|
|
621
1130
|
assignOffsets( tlasNodeCount ) {
|
|
622
1131
|
|
|
623
1132
|
this.tlasNodeCount = tlasNodeCount;
|
|
1133
|
+
this.groupNodeStart = tlasNodeCount;
|
|
624
1134
|
let offset = tlasNodeCount;
|
|
1135
|
+
for ( const g of this.groups ) {
|
|
1136
|
+
|
|
1137
|
+
g.offset = offset;
|
|
1138
|
+
offset += g.nodes;
|
|
1139
|
+
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
if ( this.groups.length ) offset ++; // the empty leaf, last: a full refit computes it before any group node reads it
|
|
1143
|
+
const blasBase = offset;
|
|
1144
|
+
this.recordNodeStart = 0;
|
|
625
1145
|
|
|
626
1146
|
for ( let t = 0; t < this.templateCount; t ++ ) {
|
|
627
1147
|
|
|
@@ -641,15 +1161,30 @@ export class InstanceTable {
|
|
|
641
1161
|
|
|
642
1162
|
}
|
|
643
1163
|
|
|
644
|
-
this.totalBLASNodes = offset -
|
|
645
|
-
|
|
1164
|
+
this.totalBLASNodes = offset - blasBase;
|
|
1165
|
+
if ( this.clusterCount ) this.recordNodeStart = offset;
|
|
1166
|
+
assertBVHIndexFits( offset + ( this.clusterCount ? this.recordNodeCount : 0 ), 'combined TLAS + BLAS node count' );
|
|
1167
|
+
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
/** Where the BLASes start: after the TLAS and the group trees. */
|
|
1171
|
+
get blasBase() {
|
|
1172
|
+
|
|
1173
|
+
return this.tlasNodeCount + this.groupNodeCount;
|
|
1174
|
+
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
/** Tree nodes: TLAS, group trees, all BLASes. */
|
|
1178
|
+
get treeNodeCount() {
|
|
1179
|
+
|
|
1180
|
+
return this.blasBase + this.totalBLASNodes;
|
|
646
1181
|
|
|
647
1182
|
}
|
|
648
1183
|
|
|
649
|
-
/**
|
|
1184
|
+
/** The whole buffer in nodes: the tree, then the copy records when clustered. */
|
|
650
1185
|
get totalNodeCount() {
|
|
651
1186
|
|
|
652
|
-
return this.
|
|
1187
|
+
return this.treeNodeCount + ( this.clusterCount ? this.recordNodeCount : 0 );
|
|
653
1188
|
|
|
654
1189
|
}
|
|
655
1190
|
|
|
@@ -664,6 +1199,143 @@ export class InstanceTable {
|
|
|
664
1199
|
|
|
665
1200
|
}
|
|
666
1201
|
|
|
1202
|
+
const _copyBoxes = new Float64Array( CLUSTER_SIZE * 6 );
|
|
1203
|
+
const _recordInverse = new Float64Array( 16 );
|
|
1204
|
+
|
|
1205
|
+
/**
|
|
1206
|
+
* Orders `sorted[ lo, hi )` so each run of CLUSTER_SIZE holds neighbours: halved at the centre median of the widest
|
|
1207
|
+
* axis, the lower part a whole number of runs, which keeps the run count ⌈n / CLUSTER_SIZE⌉.
|
|
1208
|
+
*/
|
|
1209
|
+
function splitIntoRuns( sorted, lo, hi, world ) {
|
|
1210
|
+
|
|
1211
|
+
const stack = [ lo, hi ];
|
|
1212
|
+
while ( stack.length ) {
|
|
1213
|
+
|
|
1214
|
+
const end = stack.pop(), start = stack.pop();
|
|
1215
|
+
if ( end - start <= CLUSTER_SIZE ) continue;
|
|
1216
|
+
|
|
1217
|
+
let axis = 0, widest = - 1;
|
|
1218
|
+
for ( let a = 0; a < 3; a ++ ) {
|
|
1219
|
+
|
|
1220
|
+
let min = Infinity, max = - Infinity;
|
|
1221
|
+
for ( let i = start; i < end; i ++ ) {
|
|
1222
|
+
|
|
1223
|
+
const c = world[ sorted[ i ] * 6 + a ] + world[ sorted[ i ] * 6 + 3 + a ];
|
|
1224
|
+
if ( c < min ) min = c;
|
|
1225
|
+
if ( c > max ) max = c;
|
|
1226
|
+
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
if ( max - min > widest ) {
|
|
1230
|
+
|
|
1231
|
+
widest = max - min;
|
|
1232
|
+
axis = a;
|
|
1233
|
+
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
const mid = start + CLUSTER_SIZE * Math.ceil( ( end - start ) / ( 2 * CLUSTER_SIZE ) );
|
|
1239
|
+
selectNth( sorted, start, end, mid, world, axis );
|
|
1240
|
+
stack.push( start, mid, mid, end );
|
|
1241
|
+
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
/** Partially sorts `sorted[ lo, hi )` by centre along `axis` so index `k` holds what a full sort would put there. */
|
|
1247
|
+
function selectNth( sorted, lo, hi, k, world, axis ) {
|
|
1248
|
+
|
|
1249
|
+
const key = ( i ) => world[ sorted[ i ] * 6 + axis ] + world[ sorted[ i ] * 6 + 3 + axis ];
|
|
1250
|
+
let l = lo, r = hi - 1;
|
|
1251
|
+
while ( r > l ) {
|
|
1252
|
+
|
|
1253
|
+
const m = ( l + r ) >>> 1;
|
|
1254
|
+
const a = key( l ), b = key( m ), c = key( r );
|
|
1255
|
+
const pivot = a < b ? ( b < c ? b : a < c ? c : a ) : ( a < c ? a : b < c ? c : b );
|
|
1256
|
+
let i = l, j = r;
|
|
1257
|
+
while ( i <= j ) {
|
|
1258
|
+
|
|
1259
|
+
while ( key( i ) < pivot ) i ++;
|
|
1260
|
+
while ( key( j ) > pivot ) j --;
|
|
1261
|
+
if ( i <= j ) {
|
|
1262
|
+
|
|
1263
|
+
const t = sorted[ i ];
|
|
1264
|
+
sorted[ i ] = sorted[ j ];
|
|
1265
|
+
sorted[ j ] = t;
|
|
1266
|
+
i ++;
|
|
1267
|
+
j --;
|
|
1268
|
+
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
if ( k <= j ) r = j;
|
|
1274
|
+
else if ( k >= i ) l = i;
|
|
1275
|
+
else return;
|
|
1276
|
+
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
/** Splits group member slots in two at the centroid median of their widest axis. */
|
|
1282
|
+
function splitMembers( list, members, aabb ) {
|
|
1283
|
+
|
|
1284
|
+
const centre = ( j, a ) => aabb[ members[ j ] * 6 + a ] + aabb[ members[ j ] * 6 + 3 + a ];
|
|
1285
|
+
let axis = 0, widest = - 1;
|
|
1286
|
+
for ( let a = 0; a < 3; a ++ ) {
|
|
1287
|
+
|
|
1288
|
+
let lo = Infinity, hi = - Infinity;
|
|
1289
|
+
for ( const j of list ) {
|
|
1290
|
+
|
|
1291
|
+
const c = centre( j, a );
|
|
1292
|
+
if ( c < lo ) lo = c;
|
|
1293
|
+
if ( c > hi ) hi = c;
|
|
1294
|
+
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
if ( hi - lo > widest ) {
|
|
1298
|
+
|
|
1299
|
+
widest = hi - lo;
|
|
1300
|
+
axis = a;
|
|
1301
|
+
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
const sorted = list.slice().sort( ( x, y ) => centre( x, axis ) - centre( y, axis ) || x - y );
|
|
1307
|
+
const mid = sorted.length >> 1;
|
|
1308
|
+
return [ sorted.slice( 0, mid ), sorted.slice( mid ) ];
|
|
1309
|
+
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
/** A child's box into an inner node's slot at `off`; none is the far point box no ray enters. */
|
|
1313
|
+
function writeChildBox( block, off, box ) {
|
|
1314
|
+
|
|
1315
|
+
for ( let a = 0; a < 3; a ++ ) {
|
|
1316
|
+
|
|
1317
|
+
block[ off + a ] = box ? box[ a ] : BVH_EMPTY_BOX;
|
|
1318
|
+
block[ off + 4 + a ] = box ? box[ 3 + a ] : BVH_EMPTY_BOX;
|
|
1319
|
+
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
function unionBoxes( a, b ) {
|
|
1325
|
+
|
|
1326
|
+
if ( ! a || ! b ) return a ?? b;
|
|
1327
|
+
const out = new Float32Array( 6 );
|
|
1328
|
+
for ( let k = 0; k < 3; k ++ ) {
|
|
1329
|
+
|
|
1330
|
+
out[ k ] = Math.min( a[ k ], b[ k ] );
|
|
1331
|
+
out[ 3 + k ] = Math.max( a[ 3 + k ], b[ 3 + k ] );
|
|
1332
|
+
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
return out;
|
|
1336
|
+
|
|
1337
|
+
}
|
|
1338
|
+
|
|
667
1339
|
/** Bounds of triangles [triOffset, triOffset + triCount) of a flat or chunked store, into out[off..off+5]. */
|
|
668
1340
|
function aabbOfTriangles( triangleData, triOffset, triCount, out, off ) {
|
|
669
1341
|
|