rayzee 9.0.0 → 9.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -8
- package/dist/{NeuralRetouch-CYOJ21Wn.js → NeuralRetouch-BRquqfmZ.js} +1 -1
- package/dist/{NeuralSuperRes-CQLCLpcC.js → NeuralSuperRes-CbNvQjCw.js} +3 -2
- package/dist/{ToneMapGPU-BVE852iN.js → ToneMapGPU-DpUZDMbQ.js} +2 -1
- package/dist/rayzee.es.js +10353 -5337
- package/dist/rayzee.umd.js +265 -265
- package/package.json +1 -1
- package/src/AssetConfig.js +4 -0
- package/src/EngineDefaults.js +38 -56
- package/src/EngineEvents.js +3 -0
- package/src/EngineIssues.js +6 -0
- package/src/Headless.js +3 -1
- package/src/Passes/AIUpscaler.js +2 -1
- package/src/Passes/OIDNDenoiser.js +3 -6
- package/src/PathTracerApp.js +290 -19
- package/src/Processor/ArchiveCache.js +120 -0
- package/src/Processor/ArchiveReader.js +179 -32
- package/src/Processor/AssetLoader.js +514 -76
- package/src/Processor/AtmosphereModel.js +465 -0
- package/src/Processor/BVHBuilder.js +4 -1
- package/src/Processor/BVHLeafFold.js +100 -0
- package/src/Processor/BVHRefitter.js +98 -214
- package/src/Processor/ChunkedRecords.js +189 -4
- package/src/Processor/EmissiveTriangleBuilder.js +8 -0
- package/src/Processor/GeometryExtractor.js +238 -40
- package/src/Processor/InstanceTable.js +55 -37
- package/src/Processor/KernelManager.js +4 -1
- package/src/Processor/PBRT/PBRTCurves.js +104 -27
- package/src/Processor/PBRT/PBRTParser.js +132 -22
- package/src/Processor/PBRT/PBRTSceneBuilder.js +192 -49
- package/src/Processor/PBRT/buffers.js +27 -0
- package/src/Processor/PBRT/index.js +55 -15
- package/src/Processor/PackedRayBuffer.js +10 -13
- package/src/Processor/ParallelBVHBuilder.js +36 -15
- package/src/Processor/PhysicalSky.js +308 -0
- package/src/Processor/QueueManager.js +1 -0
- package/src/Processor/SceneProcessor.js +1132 -181
- package/src/Processor/SplitBLAS.js +249 -0
- package/src/Processor/SunPosition.js +41 -0
- package/src/Processor/TextureCreator.js +21 -0
- package/src/Processor/Workers/AIUpscalerWorker.js +79 -52
- package/src/Processor/Workers/BVHWorker.js +17 -13
- package/src/Processor/ZipReader.js +207 -0
- package/src/RenderSettings.js +38 -0
- package/src/SceneState/SceneState.js +367 -0
- package/src/SceneState/portable.js +52 -0
- package/src/Stages/NormalDepth.js +7 -6
- package/src/Stages/PathTracer.js +108 -4
- package/src/Stages/PathTracerStage.js +251 -104
- package/src/Storage/BLASCache.js +190 -0
- package/src/Storage/CDFCache.js +77 -0
- package/src/Storage/DownloadCache.js +266 -0
- package/src/Storage/GeometrySpill.js +217 -0
- package/src/Storage/SceneGraphCodec.js +503 -0
- package/src/Storage/SpillStore.js +112 -0
- package/src/Storage/StorageManager.js +934 -0
- package/src/Storage/StorageOps.js +137 -0
- package/src/Storage/StorageWorker.js +37 -0
- package/src/Storage/events.js +29 -0
- package/src/Storage/identity.js +77 -0
- package/src/Storage/index.js +9 -0
- package/src/Storage/inlineTransport.js +24 -0
- package/src/Storage/locks.js +99 -0
- package/src/Storage/openStorage.js +87 -0
- package/src/Storage/sceneCachePolicy.js +12 -0
- package/src/Storage/shared.js +9 -0
- package/src/Storage/transport.js +62 -0
- package/src/TSL/Atmosphere.js +684 -0
- package/src/TSL/BVHTraversal.js +288 -239
- package/src/TSL/Common.js +11 -2
- package/src/TSL/Displacement.js +6 -6
- package/src/TSL/EmissiveSampling.js +5 -5
- package/src/TSL/EnvironmentCDF.js +176 -0
- package/src/TSL/GenerateKernel.js +3 -3
- package/src/TSL/HitFacet.js +4 -4
- package/src/TSL/LightsDirect.js +8 -8
- package/src/TSL/LightsSampling.js +55 -37
- package/src/TSL/Random.js +1 -0
- package/src/TSL/ShadeKernel.js +75 -19
- package/src/TSL/Sun.js +40 -0
- package/src/TSL/TextureSampling.js +6 -7
- package/src/index.js +18 -0
- package/src/managers/CameraManager.js +169 -1
- package/src/managers/EnvironmentManager.js +263 -33
- package/src/managers/GoboManager.js +19 -8
- package/src/managers/IESManager.js +3 -3
- package/src/managers/LightManager.js +98 -5
- package/src/managers/MaterialDataManager.js +22 -2
- package/src/managers/TransformManager.js +26 -0
- package/src/managers/UniformManager.js +5 -3
- package/src/managers/VideoRenderManager.js +3 -1
- package/src/managers/timeline/CameraTrack.js +22 -0
- package/src/managers/timeline/TimelineManager.js +14 -0
- package/src/version.js +3 -0
- package/src/Processor/ProceduralSky.js +0 -291
package/src/TSL/BVHTraversal.js
CHANGED
|
@@ -24,10 +24,10 @@ import {
|
|
|
24
24
|
bool as tslBool,
|
|
25
25
|
} from 'three/tsl';
|
|
26
26
|
|
|
27
|
-
import { BVH_LEAF_MARKERS, BVH_MAX_INDEX, TRI_MATERIAL_MASK, TRI_SIDE_SHIFT, TLAS_LEAF_IDENTITY } from '../EngineDefaults.js';
|
|
27
|
+
import { BVH_LEAF_MARKERS, BVH_MAX_INDEX, BVH_FOLDED_LEAF_MAX, TRI_MATERIAL_MASK, TRI_SIDE_SHIFT, TLAS_LEAF_IDENTITY } from '../EngineDefaults.js';
|
|
28
28
|
import { HitInfo } from './Struct.js';
|
|
29
29
|
import {
|
|
30
|
-
getDatafromStorageBuffer, instanceRows, instanceNormalToWorld, unpackTriangleNormal,
|
|
30
|
+
getDatafromStorageBuffer, instanceRows, instanceNormalToWorld, unpackTriangleNormal, triangleRow, shadowFlagsSettle
|
|
31
31
|
} from './Common.js';
|
|
32
32
|
|
|
33
33
|
const MAX_STACK_DEPTH = 32;
|
|
@@ -46,6 +46,16 @@ const HUGE_VAL = 1e8;
|
|
|
46
46
|
|
|
47
47
|
const createStack = () => array( 'int', MAX_STACK_DEPTH ).toVar();
|
|
48
48
|
|
|
49
|
+
// A negative stack entry is a leaf folded into its parent, `~( first << 4 | count )` as the parent
|
|
50
|
+
// stores it, so child slots are pushed as they are and a leaf needs no fetch.
|
|
51
|
+
const foldedFirst = ( entry ) => int( uint( entry.bitNot() ).shiftRight( uint( 4 ) ) );
|
|
52
|
+
const foldedCount = ( entry ) => int( uint( entry.bitNot() ).bitAnd( uint( BVH_FOLDED_LEAF_MAX ) ) );
|
|
53
|
+
|
|
54
|
+
// Leaf node tags sit from 2^30 up; in a folded BVH only to 2^31, where folded leaves begin.
|
|
55
|
+
const leafTagTest = ( folded ) => ( folded
|
|
56
|
+
? ( tag ) => tag.shiftRight( uint( 30 ) ).equal( uint( 1 ) )
|
|
57
|
+
: ( tag ) => tag.greaterThanEqual( uint( BVH_MAX_INDEX ) ) );
|
|
58
|
+
|
|
49
59
|
// Reciprocal ray direction with the axis-aligned cases pinned to a large signed value.
|
|
50
60
|
const buildInvDir = ( d ) => {
|
|
51
61
|
|
|
@@ -219,6 +229,10 @@ const makeTraverseBVH = ( trackStats ) => Fn( ( [
|
|
|
219
229
|
// either side to find the exit; exterior rays honor the authored side as before.
|
|
220
230
|
const inMedium = insideMedium ?? tslBool( false );
|
|
221
231
|
|
|
232
|
+
// Folded leaves cost up to 4 % of traversal, so only a BVH built with them gets that code.
|
|
233
|
+
const folded = bvhBuffer.value?.foldedLeaves === true;
|
|
234
|
+
const isLeafTag = leafTagTest( folded );
|
|
235
|
+
|
|
222
236
|
const closestHit = HitInfo( {
|
|
223
237
|
didHit: false,
|
|
224
238
|
dst: float( 1e20 ),
|
|
@@ -260,183 +274,200 @@ const makeTraverseBVH = ( trackStats ) => Fn( ( [
|
|
|
260
274
|
const instExit = int( 0 ).toVar();
|
|
261
275
|
const hitInstLeaf = int( - 1 ).toVar();
|
|
262
276
|
|
|
277
|
+
// Emitted once per leaf kind, in its own branch: hoisted below both, the range stayed live
|
|
278
|
+
// through the inner-node path and cost 9–13 % of traversal time.
|
|
279
|
+
const testLeaf = ( first, count ) => {
|
|
280
|
+
|
|
281
|
+
const triStart = int( first ).toVar();
|
|
282
|
+
const triCount = int( count ).toVar();
|
|
283
|
+
|
|
284
|
+
Loop( { start: int( 0 ), end: triCount }, ( { i } ) => {
|
|
285
|
+
|
|
286
|
+
if ( trackStats ) closestHit.triTests.addAssign( 1 );
|
|
287
|
+
const triIndex = triStart.add( i ).toVar();
|
|
288
|
+
|
|
289
|
+
// Three fetches carry positions AND the packed normals in their .w lanes.
|
|
290
|
+
const recA = triangleRow( triangleBuffer, triIndex, 0 ).toVar();
|
|
291
|
+
const recB = triangleRow( triangleBuffer, triIndex, 1 ).toVar();
|
|
292
|
+
const recC = triangleRow( triangleBuffer, triIndex, 2 ).toVar();
|
|
293
|
+
const pA = uintBitsToFloat( recA.xyz );
|
|
294
|
+
const pB = uintBitsToFloat( recB.xyz );
|
|
295
|
+
const pC = uintBitsToFloat( recC.xyz );
|
|
296
|
+
|
|
297
|
+
const triResult = RayTriangleGeometry( { rayOrigin, rayDir: rayDirection, pA, pB, pC, closestHitDst: closestHit.dst, woopParams } );
|
|
298
|
+
|
|
299
|
+
// RayTriangleGeometry already guarantees t < closestHit.dst when w > 0.5
|
|
300
|
+
If( triResult.w.greaterThan( 0.5 ), () => {
|
|
301
|
+
|
|
302
|
+
const t = triResult.x;
|
|
303
|
+
const u = triResult.y;
|
|
304
|
+
const v = triResult.z;
|
|
305
|
+
|
|
306
|
+
// Normals came with the positions; only the side flag needs a fetch, and
|
|
307
|
+
// slot 4 also carries matIdx/meshIndex, deferred to post-traversal.
|
|
308
|
+
const nA = unpackTriangleNormal( recA.w );
|
|
309
|
+
const nB = unpackTriangleNormal( recB.w );
|
|
310
|
+
const nC = unpackTriangleNormal( recC.w );
|
|
311
|
+
const flags = triangleRow( triangleBuffer, triIndex, 4 ).z;
|
|
312
|
+
const side = int( flags.shiftRight( uint( TRI_SIDE_SHIFT ) ).bitAnd( uint( 3 ) ) ).toVar();
|
|
313
|
+
|
|
314
|
+
// Interpolate normal for the side-culling dot product (kept local,
|
|
315
|
+
// not stored on closestHit — re-derived post-loop from closestTriIdx).
|
|
316
|
+
const w = float( 1.0 ).sub( u ).sub( v );
|
|
317
|
+
const rayDotNormal = rayDirection.dot(
|
|
318
|
+
normalize( nA.mul( w ).add( nB.mul( u ) ).add( nC.mul( v ) ) )
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
// Side culling (inline; per-mesh visibility is at the BLAS-pointer level).
|
|
322
|
+
// 0=front (reject back-facing), 1=back (reject front-facing), 2=double (pass).
|
|
323
|
+
const sidePass = inMedium.or( side.equal( int( 2 ) ) )
|
|
324
|
+
.or( side.equal( int( 0 ) ).and( rayDotNormal.lessThan( - 0.0001 ) ) )
|
|
325
|
+
.or( side.equal( int( 1 ) ).and( rayDotNormal.greaterThan( 0.0001 ) ) );
|
|
326
|
+
If( sidePass, () => {
|
|
327
|
+
|
|
328
|
+
closestHit.didHit.assign( true );
|
|
329
|
+
closestHit.dst.assign( t );
|
|
330
|
+
|
|
331
|
+
// Defer normal/materialIndex/meshIndex/hitPoint/UV to post-traversal
|
|
332
|
+
// (all re-derived from closestTriIdx after the loop exits).
|
|
333
|
+
closestTriIdx.assign( triIndex );
|
|
334
|
+
closestU.assign( u );
|
|
335
|
+
closestV.assign( v );
|
|
336
|
+
hitInstLeaf.assign( instLeaf );
|
|
337
|
+
|
|
338
|
+
} );
|
|
339
|
+
|
|
340
|
+
} );
|
|
341
|
+
|
|
342
|
+
} );
|
|
343
|
+
|
|
344
|
+
// If we found a very close hit, we can terminate early
|
|
345
|
+
If( closestHit.didHit.and( closestHit.dst.lessThan( 0.001 ) ), () => {
|
|
346
|
+
|
|
347
|
+
Break();
|
|
348
|
+
|
|
349
|
+
} );
|
|
350
|
+
|
|
351
|
+
};
|
|
352
|
+
|
|
263
353
|
const iterCount = int( 0 ).toVar();
|
|
264
354
|
|
|
265
355
|
Loop( stackPtr.greaterThan( int( 0 ) ).and( iterCount.lessThan( int( MAX_BVH_ITERATIONS ) ) ), () => {
|
|
266
356
|
|
|
267
357
|
iterCount.addAssign( 1 );
|
|
268
358
|
stackPtr.subAssign( 1 );
|
|
269
|
-
const
|
|
359
|
+
const entry = stack.element( stackPtr ).toVar();
|
|
270
360
|
instLeaf.assign( select( stackPtr.lessThan( instExit ), int( - 1 ), instLeaf ) );
|
|
271
361
|
|
|
272
|
-
|
|
273
|
-
// Leaf: vec4(0) = [triOffset, triCount, 0, -1]
|
|
274
|
-
// Inner: vec4(0) = [leftMin.xyz, leftChild], vec4(1) = [leftMax.xyz, rightChild],
|
|
275
|
-
// vec4(2) = [rightMin.xyz, 0], vec4(3) = [rightMax.xyz, 0]
|
|
276
|
-
const nodeData0 = getDatafromStorageBuffer( bvhBuffer, nodeIndex, int( 0 ), int( BVH_STRIDE ) );
|
|
277
|
-
if ( trackStats ) closestHit.boxTests.addAssign( 1 );
|
|
278
|
-
|
|
279
|
-
// Slot [3] carries a u32 bit pattern: an inner node's left-child index, or a leaf tag
|
|
280
|
-
// above every valid index. Stored as float VALUES these rounded past 2^24 and sent the
|
|
281
|
-
// ray to a neighbouring node, which silently erased geometry from large scenes.
|
|
282
|
-
const nodeTag = floatBitsToUint( nodeData0.w ).toVar();
|
|
283
|
-
|
|
284
|
-
If( nodeTag.greaterThanEqual( uint( BVH_MAX_INDEX ) ), () => {
|
|
285
|
-
|
|
286
|
-
If( nodeTag.equal( uint( BVH_LEAF_MARKERS.TRIANGLE_LEAF ) ), () => {
|
|
287
|
-
|
|
288
|
-
// Triangle leaf — triOffset and triCount packed in vec4(0).xy
|
|
289
|
-
const triStart = int( floatBitsToUint( nodeData0.x ) ).toVar();
|
|
290
|
-
const triCount = int( floatBitsToUint( nodeData0.y ) ).toVar();
|
|
291
|
-
|
|
292
|
-
// Process triangles in leaf
|
|
293
|
-
Loop( { start: int( 0 ), end: triCount }, ( { i } ) => {
|
|
294
|
-
|
|
295
|
-
if ( trackStats ) closestHit.triTests.addAssign( 1 );
|
|
296
|
-
const triIndex = triStart.add( i ).toVar();
|
|
297
|
-
|
|
298
|
-
// Three fetches carry positions AND the packed normals in their .w lanes.
|
|
299
|
-
const recA = getDatafromStorageBuffer( triangleBuffer, triIndex, int( 0 ), int( TRI_STRIDE ) ).toVar();
|
|
300
|
-
const recB = getDatafromStorageBuffer( triangleBuffer, triIndex, int( 1 ), int( TRI_STRIDE ) ).toVar();
|
|
301
|
-
const recC = getDatafromStorageBuffer( triangleBuffer, triIndex, int( 2 ), int( TRI_STRIDE ) ).toVar();
|
|
302
|
-
const pA = uintBitsToFloat( recA.xyz );
|
|
303
|
-
const pB = uintBitsToFloat( recB.xyz );
|
|
304
|
-
const pC = uintBitsToFloat( recC.xyz );
|
|
305
|
-
|
|
306
|
-
const triResult = RayTriangleGeometry( { rayOrigin, rayDir: rayDirection, pA, pB, pC, closestHitDst: closestHit.dst, woopParams } );
|
|
307
|
-
|
|
308
|
-
// RayTriangleGeometry already guarantees t < closestHit.dst when w > 0.5
|
|
309
|
-
If( triResult.w.greaterThan( 0.5 ), () => {
|
|
310
|
-
|
|
311
|
-
const t = triResult.x;
|
|
312
|
-
const u = triResult.y;
|
|
313
|
-
const v = triResult.z;
|
|
314
|
-
|
|
315
|
-
// Normals came with the positions; only the side flag needs a fetch, and
|
|
316
|
-
// slot 4 also carries matIdx/meshIndex, deferred to post-traversal.
|
|
317
|
-
const nA = unpackTriangleNormal( recA.w );
|
|
318
|
-
const nB = unpackTriangleNormal( recB.w );
|
|
319
|
-
const nC = unpackTriangleNormal( recC.w );
|
|
320
|
-
const flags = getDatafromStorageBuffer( triangleBuffer, triIndex, int( 4 ), int( TRI_STRIDE ) ).z;
|
|
321
|
-
const side = int( flags.shiftRight( uint( TRI_SIDE_SHIFT ) ).bitAnd( uint( 3 ) ) ).toVar();
|
|
322
|
-
|
|
323
|
-
// Interpolate normal for the side-culling dot product (kept local,
|
|
324
|
-
// not stored on closestHit — re-derived post-loop from closestTriIdx).
|
|
325
|
-
const w = float( 1.0 ).sub( u ).sub( v );
|
|
326
|
-
const rayDotNormal = rayDirection.dot(
|
|
327
|
-
normalize( nA.mul( w ).add( nB.mul( u ) ).add( nC.mul( v ) ) )
|
|
328
|
-
);
|
|
329
|
-
|
|
330
|
-
// Side culling (inline; per-mesh visibility is at the BLAS-pointer level).
|
|
331
|
-
// 0=front (reject back-facing), 1=back (reject front-facing), 2=double (pass).
|
|
332
|
-
const sidePass = inMedium.or( side.equal( int( 2 ) ) )
|
|
333
|
-
.or( side.equal( int( 0 ) ).and( rayDotNormal.lessThan( - 0.0001 ) ) )
|
|
334
|
-
.or( side.equal( int( 1 ) ).and( rayDotNormal.greaterThan( 0.0001 ) ) );
|
|
335
|
-
If( sidePass, () => {
|
|
336
|
-
|
|
337
|
-
closestHit.didHit.assign( true );
|
|
338
|
-
closestHit.dst.assign( t );
|
|
339
|
-
|
|
340
|
-
// Defer normal/materialIndex/meshIndex/hitPoint/UV to post-traversal
|
|
341
|
-
// (all re-derived from closestTriIdx after the loop exits).
|
|
342
|
-
closestTriIdx.assign( triIndex );
|
|
343
|
-
closestU.assign( u );
|
|
344
|
-
closestV.assign( v );
|
|
345
|
-
hitInstLeaf.assign( instLeaf );
|
|
362
|
+
const visitNode = () => {
|
|
346
363
|
|
|
347
|
-
|
|
364
|
+
const nodeIndex = entry;
|
|
348
365
|
|
|
349
|
-
|
|
366
|
+
// 4 vec4 per node
|
|
367
|
+
// Leaf: vec4(0) = [triOffset, triCount, 0, TRIANGLE_LEAF]
|
|
368
|
+
// Inner: vec4(0) = [leftMin.xyz, leftChild], vec4(1) = [leftMax.xyz, rightChild],
|
|
369
|
+
// vec4(2) = [rightMin.xyz, 0], vec4(3) = [rightMax.xyz, 0]
|
|
370
|
+
const nodeData0 = getDatafromStorageBuffer( bvhBuffer, nodeIndex, int( 0 ), int( BVH_STRIDE ) );
|
|
371
|
+
if ( trackStats ) closestHit.boxTests.addAssign( 1 );
|
|
350
372
|
|
|
351
|
-
|
|
373
|
+
// Slot [3] carries a u32 bit pattern: an inner node's left child, or a leaf tag. Stored as
|
|
374
|
+
// float VALUES these rounded past 2^24 and sent the ray to a neighbouring node, which
|
|
375
|
+
// silently erased geometry from large scenes.
|
|
376
|
+
const nodeTag = floatBitsToUint( nodeData0.w ).toVar();
|
|
352
377
|
|
|
353
|
-
|
|
354
|
-
If( closestHit.didHit.and( closestHit.dst.lessThan( 0.001 ) ), () => {
|
|
378
|
+
If( isLeafTag( nodeTag ), () => {
|
|
355
379
|
|
|
356
|
-
|
|
380
|
+
If( nodeTag.equal( uint( BVH_LEAF_MARKERS.TRIANGLE_LEAF ) ), () => {
|
|
357
381
|
|
|
358
|
-
|
|
382
|
+
testLeaf( floatBitsToUint( nodeData0.x ), floatBitsToUint( nodeData0.y ) );
|
|
359
383
|
|
|
360
|
-
|
|
384
|
+
} ).Else( () => {
|
|
361
385
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
386
|
+
// BLAS-pointer leaf — enter the instance if the mesh is visible.
|
|
387
|
+
// nodeData0: [blasRootNodeIndex, meshIndex, visibility, -2]; slots 4..15 hold
|
|
388
|
+
// the world-to-object matrix. Visibility is free-fetched with the leaf.
|
|
389
|
+
If( nodeData0.z.greaterThan( 0.5 ).and( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ) ), () => {
|
|
366
390
|
|
|
367
|
-
|
|
368
|
-
|
|
391
|
+
// A baked placement already sits in world space; only a transformed one moves the ray.
|
|
392
|
+
If( floatBitsToUint( nodeData0.y ).bitAnd( uint( TLAS_LEAF_IDENTITY ) ).equal( uint( 0 ) ), () => {
|
|
369
393
|
|
|
370
|
-
|
|
371
|
-
|
|
394
|
+
const rows = instanceRows( bvhBuffer, nodeIndex );
|
|
395
|
+
const localDir = toObjectDir( rows, worldDirection ).toVar();
|
|
372
396
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
397
|
+
rayOrigin.assign( toObjectPoint( rows, worldOrigin ) );
|
|
398
|
+
rayDirection.assign( localDir );
|
|
399
|
+
const localInv = buildInvDir( localDir ).toVar();
|
|
400
|
+
invDir.assign( localInv );
|
|
401
|
+
woopParams.assign( computeWoopFromInvDir( { rayDir: localDir, invDir: localInv } ) );
|
|
402
|
+
instLeaf.assign( nodeIndex );
|
|
403
|
+
instExit.assign( stackPtr );
|
|
380
404
|
|
|
381
|
-
|
|
405
|
+
} ).Else( () => {
|
|
382
406
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
407
|
+
rayOrigin.assign( worldOrigin );
|
|
408
|
+
rayDirection.assign( worldDirection );
|
|
409
|
+
invDir.assign( worldInvDir );
|
|
410
|
+
woopParams.assign( worldWoop );
|
|
411
|
+
instLeaf.assign( int( - 1 ) );
|
|
388
412
|
|
|
389
|
-
|
|
413
|
+
} );
|
|
414
|
+
|
|
415
|
+
stack.element( stackPtr ).assign( int( floatBitsToUint( nodeData0.x ) ) );
|
|
416
|
+
stackPtr.addAssign( 1 );
|
|
390
417
|
|
|
391
|
-
|
|
392
|
-
stackPtr.addAssign( 1 );
|
|
418
|
+
} );
|
|
393
419
|
|
|
394
420
|
} );
|
|
395
421
|
|
|
396
|
-
} )
|
|
422
|
+
} ).Else( () => {
|
|
397
423
|
|
|
398
|
-
|
|
424
|
+
// Inner node — child AABBs stored in this node (4 reads total, no child fetches)
|
|
425
|
+
const nodeData1 = getDatafromStorageBuffer( bvhBuffer, nodeIndex, int( 1 ), int( BVH_STRIDE ) );
|
|
426
|
+
const nodeData2 = getDatafromStorageBuffer( bvhBuffer, nodeIndex, int( 2 ), int( BVH_STRIDE ) );
|
|
427
|
+
const nodeData3 = getDatafromStorageBuffer( bvhBuffer, nodeIndex, int( 3 ), int( BVH_STRIDE ) );
|
|
399
428
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const nodeData2 = getDatafromStorageBuffer( bvhBuffer, nodeIndex, int( 2 ), int( BVH_STRIDE ) );
|
|
403
|
-
const nodeData3 = getDatafromStorageBuffer( bvhBuffer, nodeIndex, int( 3 ), int( BVH_STRIDE ) );
|
|
429
|
+
const leftChild = int( nodeTag ).toVar();
|
|
430
|
+
const rightChild = int( floatBitsToUint( nodeData1.w ) ).toVar();
|
|
404
431
|
|
|
405
|
-
|
|
406
|
-
|
|
432
|
+
const inInst = instLeaf.greaterThanEqual( int( 0 ) );
|
|
433
|
+
const boxOrigin = select( inInst, rayOrigin, worldOrigin ).toVar();
|
|
434
|
+
const boxInv = select( inInst, invDir, worldInvDir ).toVar();
|
|
435
|
+
const dstA = fastRayAABBDst( { rayOrigin: boxOrigin, invDir: boxInv, boxMin: nodeData0.xyz, boxMax: nodeData1.xyz } ).toVar();
|
|
436
|
+
const dstB = fastRayAABBDst( { rayOrigin: boxOrigin, invDir: boxInv, boxMin: nodeData2.xyz, boxMax: nodeData3.xyz } ).toVar();
|
|
407
437
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
const dstA = fastRayAABBDst( { rayOrigin: boxOrigin, invDir: boxInv, boxMin: nodeData0.xyz, boxMax: nodeData1.xyz } ).toVar();
|
|
412
|
-
const dstB = fastRayAABBDst( { rayOrigin: boxOrigin, invDir: boxInv, boxMin: nodeData2.xyz, boxMax: nodeData3.xyz } ).toVar();
|
|
438
|
+
// Optimized early rejection
|
|
439
|
+
const minDst = min( dstA, dstB );
|
|
440
|
+
If( minDst.lessThan( closestHit.dst ), () => {
|
|
413
441
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
If( minDst.lessThan( closestHit.dst ), () => {
|
|
442
|
+
// Improved node ordering with fewer conditionals
|
|
443
|
+
const aCloser = dstA.lessThan( dstB );
|
|
417
444
|
|
|
418
|
-
|
|
419
|
-
|
|
445
|
+
// Push far child first (processed last)
|
|
446
|
+
If( select( aCloser, dstB, dstA ).lessThan( closestHit.dst ).and( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ) ), () => {
|
|
420
447
|
|
|
421
|
-
|
|
422
|
-
|
|
448
|
+
stack.element( stackPtr ).assign( select( aCloser, rightChild, leftChild ) );
|
|
449
|
+
stackPtr.addAssign( 1 );
|
|
423
450
|
|
|
424
|
-
|
|
425
|
-
stackPtr.addAssign( 1 );
|
|
451
|
+
} );
|
|
426
452
|
|
|
427
|
-
|
|
453
|
+
// Push near child second (processed first)
|
|
454
|
+
If( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ), () => {
|
|
428
455
|
|
|
429
|
-
|
|
430
|
-
|
|
456
|
+
stack.element( stackPtr ).assign( select( aCloser, leftChild, rightChild ) );
|
|
457
|
+
stackPtr.addAssign( 1 );
|
|
431
458
|
|
|
432
|
-
|
|
433
|
-
stackPtr.addAssign( 1 );
|
|
459
|
+
} );
|
|
434
460
|
|
|
435
461
|
} );
|
|
436
462
|
|
|
437
463
|
} );
|
|
438
464
|
|
|
439
|
-
|
|
465
|
+
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
// Only a folded BVH has negative entries: a leaf folded into its parent, tested with no fetch.
|
|
469
|
+
if ( folded ) If( entry.lessThan( int( 0 ) ), () => testLeaf( foldedFirst( entry ), foldedCount( entry ) ) ).Else( visitNode );
|
|
470
|
+
else visitNode();
|
|
440
471
|
|
|
441
472
|
} );
|
|
442
473
|
|
|
@@ -449,9 +480,9 @@ const makeTraverseBVH = ( trackStats ) => Fn( ( [
|
|
|
449
480
|
|
|
450
481
|
// Re-fetch the winning triangle's normals — trading 3 storage reads (once)
|
|
451
482
|
// for ~3 regs freed across every BVH iteration.
|
|
452
|
-
const nA = unpackTriangleNormal(
|
|
453
|
-
const nB = unpackTriangleNormal(
|
|
454
|
-
const nC = unpackTriangleNormal(
|
|
483
|
+
const nA = unpackTriangleNormal( triangleRow( triangleBuffer, closestTriIdx, 0 ).w );
|
|
484
|
+
const nB = unpackTriangleNormal( triangleRow( triangleBuffer, closestTriIdx, 1 ).w );
|
|
485
|
+
const nC = unpackTriangleNormal( triangleRow( triangleBuffer, closestTriIdx, 2 ).w );
|
|
455
486
|
const objectNormal = normalize( nA.mul( w ).add( nB.mul( closestU ) ).add( nC.mul( closestV ) ) ).toVar();
|
|
456
487
|
If( hitInstLeaf.greaterThanEqual( int( 0 ) ), () => {
|
|
457
488
|
|
|
@@ -461,8 +492,8 @@ const makeTraverseBVH = ( trackStats ) => Fn( ( [
|
|
|
461
492
|
|
|
462
493
|
closestHit.normal.assign( objectNormal );
|
|
463
494
|
|
|
464
|
-
const uvData1 = uintBitsToFloat(
|
|
465
|
-
const uvData2 =
|
|
495
|
+
const uvData1 = uintBitsToFloat( triangleRow( triangleBuffer, closestTriIdx, 3 ) );
|
|
496
|
+
const uvData2 = triangleRow( triangleBuffer, closestTriIdx, 4 ).toVar();
|
|
466
497
|
closestHit.uv.assign(
|
|
467
498
|
uvData1.xy.mul( w ).add( uvData1.zw.mul( closestU ) ).add( uintBitsToFloat( uvData2.xy ).mul( closestV ) )
|
|
468
499
|
);
|
|
@@ -492,6 +523,9 @@ export const traverseBVHShadow = Fn( ( [
|
|
|
492
523
|
maxShadowDist,
|
|
493
524
|
] ) => {
|
|
494
525
|
|
|
526
|
+
const folded = bvhBuffer.value?.foldedLeaves === true;
|
|
527
|
+
const isLeafTag = leafTagTest( folded );
|
|
528
|
+
|
|
495
529
|
const closestHit = HitInfo( {
|
|
496
530
|
didHit: false,
|
|
497
531
|
dst: maxShadowDist,
|
|
@@ -524,152 +558,167 @@ export const traverseBVHShadow = Fn( ( [
|
|
|
524
558
|
const instExit = int( 0 ).toVar();
|
|
525
559
|
const blocked = tslBool( false ).toVar();
|
|
526
560
|
|
|
527
|
-
const
|
|
561
|
+
const testLeaf = ( first, count ) => {
|
|
528
562
|
|
|
529
|
-
|
|
563
|
+
const triStart = int( first ).toVar();
|
|
564
|
+
const triCount = int( count ).toVar();
|
|
530
565
|
|
|
531
|
-
|
|
532
|
-
stackPtr.subAssign( 1 );
|
|
533
|
-
const nodeIndex = stack.element( stackPtr ).toVar();
|
|
534
|
-
instLeaf.assign( select( stackPtr.lessThan( instExit ), int( - 1 ), instLeaf ) );
|
|
566
|
+
Loop( { start: int( 0 ), end: triCount }, ( { i } ) => {
|
|
535
567
|
|
|
536
|
-
|
|
568
|
+
const triIndex = triStart.add( i ).toVar();
|
|
537
569
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
const nodeTag = floatBitsToUint( nodeData0.w ).toVar();
|
|
570
|
+
const pA = uintBitsToFloat( triangleRow( triangleBuffer, triIndex, 0 ).xyz );
|
|
571
|
+
const pB = uintBitsToFloat( triangleRow( triangleBuffer, triIndex, 1 ).xyz );
|
|
572
|
+
const pC = uintBitsToFloat( triangleRow( triangleBuffer, triIndex, 2 ).xyz );
|
|
542
573
|
|
|
543
|
-
|
|
574
|
+
const triResult = RayTriangleGeometry( { rayOrigin, rayDir: rayDirection, pA, pB, pC, closestHitDst: closestHit.dst, woopParams } );
|
|
544
575
|
|
|
545
|
-
If(
|
|
576
|
+
If( triResult.w.greaterThan( 0.5 ), () => {
|
|
546
577
|
|
|
547
|
-
//
|
|
548
|
-
const
|
|
549
|
-
const triCount = int( floatBitsToUint( nodeData0.y ) ).toVar();
|
|
578
|
+
// Per-mesh visibility is handled at the BLAS-pointer level.
|
|
579
|
+
const uvData2 = triangleRow( triangleBuffer, triIndex, 4 ).toVar();
|
|
550
580
|
|
|
551
|
-
|
|
581
|
+
closestHit.didHit.assign( true );
|
|
582
|
+
closestHit.dst.assign( triResult.x );
|
|
583
|
+
closestHit.materialIndex.assign( int( uvData2.z.bitAnd( uint( TRI_MATERIAL_MASK ) ) ) );
|
|
584
|
+
closestHit.meshIndex.assign( int( uvData2.w ) );
|
|
585
|
+
closestHit.instanceLeaf.assign( instLeaf );
|
|
552
586
|
|
|
553
|
-
|
|
587
|
+
// Hit point is cheap (origin + dir*t). Geometric normal is deferred
|
|
588
|
+
// to traceShadowRay — only the transmission branch needs it, so we
|
|
589
|
+
// skip the cross+normalize for the (much more common) opaque-blocker
|
|
590
|
+
// and alpha-cutout paths. Normal stays vec3(0) from struct init.
|
|
591
|
+
closestHit.hitPoint.assign( worldOrigin.add( worldDirection.mul( triResult.x ) ) );
|
|
554
592
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
const pC = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, triIndex, int( 2 ), int( TRI_STRIDE ) ).xyz );
|
|
593
|
+
closestHit.uv.assign( vec2( triResult.y, triResult.z ) );
|
|
594
|
+
closestHit.triangleIndex.assign( triIndex );
|
|
558
595
|
|
|
559
|
-
|
|
596
|
+
// An opaque blocker settles the ray. A surface light passes through has to be
|
|
597
|
+
// the nearest one, or the layers behind the first find are never counted.
|
|
598
|
+
If( shadowFlagsSettle( uvData2.z ), () => {
|
|
560
599
|
|
|
561
|
-
|
|
600
|
+
blocked.assign( true );
|
|
601
|
+
Break();
|
|
562
602
|
|
|
563
|
-
|
|
564
|
-
const uvData2 = getDatafromStorageBuffer( triangleBuffer, triIndex, int( 4 ), int( TRI_STRIDE ) ).toVar();
|
|
603
|
+
} );
|
|
565
604
|
|
|
566
|
-
|
|
567
|
-
closestHit.dst.assign( triResult.x );
|
|
568
|
-
closestHit.materialIndex.assign( int( uvData2.z.bitAnd( uint( TRI_MATERIAL_MASK ) ) ) );
|
|
569
|
-
closestHit.meshIndex.assign( int( uvData2.w ) );
|
|
570
|
-
closestHit.instanceLeaf.assign( instLeaf );
|
|
605
|
+
} );
|
|
571
606
|
|
|
572
|
-
|
|
573
|
-
// to traceShadowRay — only the transmission branch needs it, so we
|
|
574
|
-
// skip the cross+normalize for the (much more common) opaque-blocker
|
|
575
|
-
// and alpha-cutout paths. Normal stays vec3(0) from struct init.
|
|
576
|
-
closestHit.hitPoint.assign( worldOrigin.add( worldDirection.mul( triResult.x ) ) );
|
|
607
|
+
} );
|
|
577
608
|
|
|
578
|
-
|
|
579
|
-
closestHit.triangleIndex.assign( triIndex );
|
|
609
|
+
};
|
|
580
610
|
|
|
581
|
-
|
|
582
|
-
// the nearest one, or the layers behind the first find are never counted.
|
|
583
|
-
If( shadowFlagsSettle( uvData2.z ), () => {
|
|
611
|
+
const sIterCount = int( 0 ).toVar();
|
|
584
612
|
|
|
585
|
-
|
|
586
|
-
Break();
|
|
613
|
+
Loop( stackPtr.greaterThan( int( 0 ) ).and( blocked.not() ).and( sIterCount.lessThan( int( MAX_BVH_ITERATIONS ) ) ), () => {
|
|
587
614
|
|
|
588
|
-
|
|
615
|
+
sIterCount.addAssign( 1 );
|
|
616
|
+
stackPtr.subAssign( 1 );
|
|
617
|
+
const entry = stack.element( stackPtr ).toVar();
|
|
618
|
+
instLeaf.assign( select( stackPtr.lessThan( instExit ), int( - 1 ), instLeaf ) );
|
|
589
619
|
|
|
590
|
-
|
|
620
|
+
const visitNode = () => {
|
|
591
621
|
|
|
592
|
-
|
|
622
|
+
const nodeIndex = entry;
|
|
623
|
+
const nodeData0 = getDatafromStorageBuffer( bvhBuffer, nodeIndex, int( 0 ), int( BVH_STRIDE ) );
|
|
593
624
|
|
|
594
|
-
|
|
625
|
+
// Slot [3] carries a u32 bit pattern: an inner node's left child, or a leaf tag. Stored as
|
|
626
|
+
// float VALUES these rounded past 2^24 and sent the ray to a neighbouring node, which
|
|
627
|
+
// silently erased geometry from large scenes.
|
|
628
|
+
const nodeTag = floatBitsToUint( nodeData0.w ).toVar();
|
|
595
629
|
|
|
596
|
-
|
|
597
|
-
If( nodeData0.z.greaterThan( 0.5 ).and( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ) ), () => {
|
|
630
|
+
If( isLeafTag( nodeTag ), () => {
|
|
598
631
|
|
|
599
|
-
|
|
600
|
-
If( floatBitsToUint( nodeData0.y ).bitAnd( uint( TLAS_LEAF_IDENTITY ) ).equal( uint( 0 ) ), () => {
|
|
632
|
+
If( nodeTag.equal( uint( BVH_LEAF_MARKERS.TRIANGLE_LEAF ) ), () => {
|
|
601
633
|
|
|
602
|
-
|
|
603
|
-
const localDir = toObjectDir( rows, worldDirection ).toVar();
|
|
634
|
+
testLeaf( floatBitsToUint( nodeData0.x ), floatBitsToUint( nodeData0.y ) );
|
|
604
635
|
|
|
605
|
-
|
|
606
|
-
rayDirection.assign( localDir );
|
|
607
|
-
const localInv = buildInvDir( localDir ).toVar();
|
|
608
|
-
invDir.assign( localInv );
|
|
609
|
-
woopParams.assign( computeWoopFromInvDir( { rayDir: localDir, invDir: localInv } ) );
|
|
610
|
-
instLeaf.assign( nodeIndex );
|
|
611
|
-
instExit.assign( stackPtr );
|
|
636
|
+
} ).Else( () => {
|
|
612
637
|
|
|
613
|
-
|
|
638
|
+
// BLAS-pointer leaf — enter the instance if the mesh is visible.
|
|
639
|
+
If( nodeData0.z.greaterThan( 0.5 ).and( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ) ), () => {
|
|
614
640
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
invDir.assign( worldInvDir );
|
|
618
|
-
woopParams.assign( worldWoop );
|
|
619
|
-
instLeaf.assign( int( - 1 ) );
|
|
641
|
+
// A baked placement already sits in world space; only a transformed one moves the ray.
|
|
642
|
+
If( floatBitsToUint( nodeData0.y ).bitAnd( uint( TLAS_LEAF_IDENTITY ) ).equal( uint( 0 ) ), () => {
|
|
620
643
|
|
|
621
|
-
|
|
644
|
+
const rows = instanceRows( bvhBuffer, nodeIndex );
|
|
645
|
+
const localDir = toObjectDir( rows, worldDirection ).toVar();
|
|
646
|
+
|
|
647
|
+
rayOrigin.assign( toObjectPoint( rows, worldOrigin ) );
|
|
648
|
+
rayDirection.assign( localDir );
|
|
649
|
+
const localInv = buildInvDir( localDir ).toVar();
|
|
650
|
+
invDir.assign( localInv );
|
|
651
|
+
woopParams.assign( computeWoopFromInvDir( { rayDir: localDir, invDir: localInv } ) );
|
|
652
|
+
instLeaf.assign( nodeIndex );
|
|
653
|
+
instExit.assign( stackPtr );
|
|
654
|
+
|
|
655
|
+
} ).Else( () => {
|
|
656
|
+
|
|
657
|
+
rayOrigin.assign( worldOrigin );
|
|
658
|
+
rayDirection.assign( worldDirection );
|
|
659
|
+
invDir.assign( worldInvDir );
|
|
660
|
+
woopParams.assign( worldWoop );
|
|
661
|
+
instLeaf.assign( int( - 1 ) );
|
|
622
662
|
|
|
623
|
-
|
|
624
|
-
|
|
663
|
+
} );
|
|
664
|
+
|
|
665
|
+
stack.element( stackPtr ).assign( int( floatBitsToUint( nodeData0.x ) ) );
|
|
666
|
+
stackPtr.addAssign( 1 );
|
|
667
|
+
|
|
668
|
+
} );
|
|
625
669
|
|
|
626
670
|
} );
|
|
627
671
|
|
|
628
|
-
} )
|
|
672
|
+
} ).Else( () => {
|
|
629
673
|
|
|
630
|
-
|
|
674
|
+
// Inner node — child AABBs stored in this node
|
|
675
|
+
const nodeData1 = getDatafromStorageBuffer( bvhBuffer, nodeIndex, int( 1 ), int( BVH_STRIDE ) );
|
|
676
|
+
const nodeData2 = getDatafromStorageBuffer( bvhBuffer, nodeIndex, int( 2 ), int( BVH_STRIDE ) );
|
|
677
|
+
const nodeData3 = getDatafromStorageBuffer( bvhBuffer, nodeIndex, int( 3 ), int( BVH_STRIDE ) );
|
|
631
678
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
const nodeData2 = getDatafromStorageBuffer( bvhBuffer, nodeIndex, int( 2 ), int( BVH_STRIDE ) );
|
|
635
|
-
const nodeData3 = getDatafromStorageBuffer( bvhBuffer, nodeIndex, int( 3 ), int( BVH_STRIDE ) );
|
|
679
|
+
const leftChild = int( nodeTag ).toVar();
|
|
680
|
+
const rightChild = int( floatBitsToUint( nodeData1.w ) ).toVar();
|
|
636
681
|
|
|
637
|
-
|
|
638
|
-
|
|
682
|
+
const inInst = instLeaf.greaterThanEqual( int( 0 ) );
|
|
683
|
+
const boxOrigin = select( inInst, rayOrigin, worldOrigin ).toVar();
|
|
684
|
+
const boxInv = select( inInst, invDir, worldInvDir ).toVar();
|
|
685
|
+
const dstA = fastRayAABBDst( { rayOrigin: boxOrigin, invDir: boxInv, boxMin: nodeData0.xyz, boxMax: nodeData1.xyz } ).toVar();
|
|
686
|
+
const dstB = fastRayAABBDst( { rayOrigin: boxOrigin, invDir: boxInv, boxMin: nodeData2.xyz, boxMax: nodeData3.xyz } ).toVar();
|
|
639
687
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
688
|
+
// Distance-ordered traversal — nearer child first for faster any-hit
|
|
689
|
+
// termination. SA build ordering improves cache locality (larger-SA
|
|
690
|
+
// child = left = first in DFS flat layout).
|
|
691
|
+
const minDst = min( dstA, dstB );
|
|
692
|
+
If( minDst.lessThan( closestHit.dst ), () => {
|
|
645
693
|
|
|
646
|
-
|
|
647
|
-
// termination. SA build ordering improves cache locality (larger-SA
|
|
648
|
-
// child = left = first in DFS flat layout).
|
|
649
|
-
const minDst = min( dstA, dstB );
|
|
650
|
-
If( minDst.lessThan( closestHit.dst ), () => {
|
|
694
|
+
const aCloser = dstA.lessThan( dstB );
|
|
651
695
|
|
|
652
|
-
|
|
696
|
+
// Push far child first (processed last)
|
|
697
|
+
If( select( aCloser, dstB, dstA ).lessThan( closestHit.dst ).and( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ) ), () => {
|
|
653
698
|
|
|
654
|
-
|
|
655
|
-
|
|
699
|
+
stack.element( stackPtr ).assign( select( aCloser, rightChild, leftChild ) );
|
|
700
|
+
stackPtr.addAssign( 1 );
|
|
656
701
|
|
|
657
|
-
|
|
658
|
-
stackPtr.addAssign( 1 );
|
|
702
|
+
} );
|
|
659
703
|
|
|
660
|
-
|
|
704
|
+
// Push near child second (processed first)
|
|
705
|
+
If( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ), () => {
|
|
661
706
|
|
|
662
|
-
|
|
663
|
-
|
|
707
|
+
stack.element( stackPtr ).assign( select( aCloser, leftChild, rightChild ) );
|
|
708
|
+
stackPtr.addAssign( 1 );
|
|
664
709
|
|
|
665
|
-
|
|
666
|
-
stackPtr.addAssign( 1 );
|
|
710
|
+
} );
|
|
667
711
|
|
|
668
712
|
} );
|
|
669
713
|
|
|
670
714
|
} );
|
|
671
715
|
|
|
672
|
-
|
|
716
|
+
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
// Only a folded BVH has negative entries: a leaf folded into its parent, tested with no fetch.
|
|
720
|
+
if ( folded ) If( entry.lessThan( int( 0 ) ), () => testLeaf( foldedFirst( entry ), foldedCount( entry ) ) ).Else( visitNode );
|
|
721
|
+
else visitNode();
|
|
673
722
|
|
|
674
723
|
} );
|
|
675
724
|
|