Repository: gkjohnson/three-mesh-bvh
Pull request #632 (2024-01-29 09:04:54)

Buildtree updates
General changes from #627


Patch:
diff --git a/.gitignore b/.gitignore
index 84872a8..945f210 100644
--- a/.gitignore
+++ b/.gitignore
@@ -65,4 +65,4 @@ example/dev-bundle
 
 *.generated.js
 
-build/
+build/**
diff --git a/src/core/MeshBVHNode.js b/src/core/MeshBVHNode.js
index 625b281..bde17a2 100644
--- a/src/core/MeshBVHNode.js
+++ b/src/core/MeshBVHNode.js
@@ -5,6 +5,8 @@ export class MeshBVHNode {
 		// internal nodes have boundingData, left, right, and splitAxis
 		// leaf nodes have offset and count (referring to primitives in the mesh geometry)
 
+		this.boundingData = new Float32Array( 6 );
+
 	}
 
 }
diff --git a/src/core/build/buildTree.js b/src/core/build/buildTree.js
index 52338c2..702bf0a 100644
--- a/src/core/build/buildTree.js
+++ b/src/core/build/buildTree.js
@@ -2,12 +2,13 @@ import { ensureIndex, getFullGeometryRange, getRootIndexRanges, getTriCount, has
 import { getBounds, computeTriangleBounds } from './computeBoundsUtils.js';
 import { getOptimalSplit } from './splitUtils.js';
 import { MeshBVHNode } from '../MeshBVHNode.js';
-import { BYTES_PER_NODE, IS_LEAFNODE_FLAG } from '../Constants.js';
+import { BYTES_PER_NODE } from '../Constants.js';
 
 import { partition } from './sortUtils.generated.js';
 import { partition_indirect } from './sortUtils_indirect.generated.js';
+import { countNodes, populateBuffer } from './buildUtils.js';
 
-function generateIndirectBuffer( geometry, useSharedArrayBuffer ) {
+export function generateIndirectBuffer( geometry, useSharedArrayBuffer ) {
 
 	const triCount = ( geometry.index ? geometry.index.count : geometry.attributes.position.count ) / 3;
 	const useUint32 = triCount > 2 ** 16;
@@ -25,42 +26,31 @@ function generateIndirectBuffer( geometry, useSharedArrayBuffer ) {
 
 }
 
-function buildTree( bvh, options ) {
+export function buildTree( bvh, triangleBounds, offset, count, options ) {
 
-	// Compute the full bounds of the geometry at the same time as triangle bounds because
-	// we'll need it for the root bounds in the case with no groups and it should be fast here.
-	// We can't use the geometry bounding box if it's available because it may be out of date.
+	// epxand variables
 	const {
 		maxDepth,
 		verbose,
 		maxLeafTris,
 		strategy,
 		onProgress,
+		indirect,
 	} = options;
 	const indirectBuffer = bvh._indirectBuffer;
 	const geometry = bvh.geometry;
 	const indexArray = geometry.index ? geometry.index.array : null;
-	const totalTriangles = getTriCount( geometry );
-	let reachedMaxDepth = false;
+	const partionFunc = indirect ? partition_indirect : partition;
 
+	// generate intermediate variables
+	const totalTriangles = getTriCount( geometry );
 	const cacheCentroidBoundingData = new Float32Array( 6 );
-	const triangleBounds = computeTriangleBounds( geometry );
-	const partionFunc = options.indirect ? partition_indirect : partition;
-
-	const roots = [];
-	const ranges = options.indirect ? getFullGeometryRange( geometry ) : getRootIndexRanges( geometry );
-	for ( let range of ranges ) {
-
-		const root = new MeshBVHNode();
-		root.boundingData = new Float32Array( 6 );
-		getBounds( triangleBounds, range.offset, range.count, root.boundingData, cacheCentroidBoundingData );
-
-		splitNode( root, range.offset, range.count, cacheCentroidBoundingData );
-		roots.push( root );
-
-	}
+	let reachedMaxDepth = false;
 
-	return roots;
+	const root = new MeshBVHNode();
+	getBounds( triangleBounds, offset, count, root.boundingData, cacheCentroidBoundingData );
+	splitNode( root, offset, count, cacheCentroidBoundingData );
+	return root;
 
 	function triggerProgress( trianglesProcessed ) {
 
@@ -127,7 +117,6 @@ function buildTree( bvh, options ) {
 			const lstart = offset;
 			const lcount = splitOffset - offset;
 			node.left = left;
-			left.boundingData = new Float32Array( 6 );
 
 			getBounds( triangleBounds, lstart, lcount, left.boundingData, cacheCentroidBoundingData );
 			splitNode( left, lstart, lcount, cacheCentroidBoundingData, depth + 1 );
@@ -137,7 +126,6 @@ function buildTree( bvh, options ) {
 			const rstart = splitOffset;
 			const rcount = count - lcount;
 			node.right = right;
-			right.boundingData = new Float32Array( 6 );
 
 			getBounds( triangleBounds, rstart, rcount, right.boundingData, cacheCentroidBoundingData );
 			splitNode( right, rstart, rcount, cacheCentroidBoundingData, depth + 1 );
@@ -174,91 +162,18 @@ export function buildPackedTree( bvh, options ) {
 
 	}
 
-	// boundingData  				: 6 float32
-	// right / offset 				: 1 uint32
-	// splitAxis / isLeaf + count 	: 1 uint32 / 2 uint16
-	const roots = buildTree( bvh, options );
-
-	let float32Array;
-	let uint32Array;
-	let uint16Array;
-	const packedRoots = [];
 	const BufferConstructor = options.useSharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer;
-	for ( let i = 0; i < roots.length; i ++ ) {
 
-		const root = roots[ i ];
-		let nodeCount = countNodes( root );
+	const triangleBounds = computeTriangleBounds( geometry );
+	const geometryRanges = options.indirect ? getFullGeometryRange( geometry ) : getRootIndexRanges( geometry );
+	bvh._roots = geometryRanges.map( range => {
 
+		const root = buildTree( bvh, triangleBounds, range.offset, range.count, options );
+		const nodeCount = countNodes( root );
 		const buffer = new BufferConstructor( BYTES_PER_NODE * nodeCount );
-		float32Array = new Float32Array( buffer );
-		uint32Array = new Uint32Array( buffer );
-		uint16Array = new Uint16Array( buffer );
-		populateBuffer( 0, root );
-		packedRoots.push( buffer );
-
-	}
-
-	bvh._roots = packedRoots;
-	return;
-
-	function countNodes( node ) {
-
-		if ( node.count ) {
-
-			return 1;
-
-		} else {
-
-			return 1 + countNodes( node.left ) + countNodes( node.right );
-
-		}
+		populateBuffer( 0, root, buffer );
+		return buffer;
 
-	}
-
-	function populateBuffer( byteOffset, node ) {
-
-		const stride4Offset = byteOffset / 4;
-		const stride2Offset = byteOffset / 2;
-		const isLeaf = ! ! node.count;
-		const boundingData = node.boundingData;
-		for ( let i = 0; i < 6; i ++ ) {
-
-			float32Array[ stride4Offset + i ] = boundingData[ i ];
-
-		}
-
-		if ( isLeaf ) {
-
-			const offset = node.offset;
-			const count = node.count;
-			uint32Array[ stride4Offset + 6 ] = offset;
-			uint16Array[ stride2Offset + 14 ] = count;
-			uint16Array[ stride2Offset + 15 ] = IS_LEAFNODE_FLAG;
-			return byteOffset + BYTES_PER_NODE;
-
-		} else {
-
-			const left = node.left;
-			const right = node.right;
-			const splitAxis = node.splitAxis;
-
-			let nextUnusedPointer;
-			nextUnusedPointer = populateBuffer( byteOffset + BYTES_PER_NODE, left );
-
-			if ( ( nextUnusedPointer / 4 ) > Math.pow( 2, 32 ) ) {
-
-				throw new Error( 'MeshBVH: Cannot store child pointer greater than 32 bits.' );
-
-			}
-
-			uint32Array[ stride4Offset + 6 ] = nextUnusedPointer / 4;
-			nextUnusedPointer = populateBuffer( nextUnusedPointer, right );
-
-			uint32Array[ stride4Offset + 7 ] = splitAxis;
-			return nextUnusedPointer;
-
-		}
-
-	}
+	} );
 
 }
diff --git a/src/core/build/buildUtils.js b/src/core/build/buildUtils.js
new file mode 100644
index 0000000..ae27fdc
--- /dev/null
+++ b/src/core/build/buildUtils.js
@@ -0,0 +1,103 @@
+import { BYTES_PER_NODE, IS_LEAFNODE_FLAG } from '../Constants.js';
+import { IS_LEAF } from '../utils/nodeBufferUtils.js';
+
+let float32Array, uint32Array, uint16Array, uint8Array;
+const MAX_POINTER = Math.pow( 2, 32 );
+
+export function countNodes( node ) {
+
+	if ( node.count ) {
+
+		return 1;
+
+	} else {
+
+		return 1 + countNodes( node.left ) + countNodes( node.right );
+
+	}
+
+}
+
+export function populateBuffer( byteOffset, node, buffer ) {
+
+	float32Array = new Float32Array( buffer );
+	uint32Array = new Uint32Array( buffer );
+	uint16Array = new Uint16Array( buffer );
+	uint8Array = new Uint8Array( buffer );
+
+	return _populateBuffer( byteOffset, node );
+
+}
+
+// pack structure
+// boundingData  				: 6 float32
+// right / offset 				: 1 uint32
+// splitAxis / isLeaf + count 	: 1 uint32 / 2 uint16
+function _populateBuffer( byteOffset, node ) {
+
+	const stride4Offset = byteOffset / 4;
+	const stride2Offset = byteOffset / 2;
+	const isLeaf = ! ! node.count;
+	const boundingData = node.boundingData;
+	for ( let i = 0; i < 6; i ++ ) {
+
+		float32Array[ stride4Offset + i ] = boundingData[ i ];
+
+	}
+
+	if ( isLeaf ) {
+
+		if ( node.buffer ) {
+
+			const buffer = node.buffer;
+			uint8Array.set( new Uint8Array( buffer ), byteOffset );
+
+			for ( let offset = byteOffset, l = byteOffset + buffer.byteLength; offset < l; offset += BYTES_PER_NODE ) {
+
+				const offset2 = offset / 2;
+				if ( ! IS_LEAF( offset2, uint16Array ) ) {
+
+					uint32Array[ ( offset / 4 ) + 6 ] += stride4Offset;
+
+
+				}
+
+			}
+
+			return byteOffset + buffer.byteLength;
+
+		} else {
+
+			const offset = node.offset;
+			const count = node.count;
+			uint32Array[ stride4Offset + 6 ] = offset;
+			uint16Array[ stride2Offset + 14 ] = count;
+			uint16Array[ stride2Offset + 15 ] = IS_LEAFNODE_FLAG;
+			return byteOffset + BYTES_PER_NODE;
+
+		}
+
+	} else {
+
+		const left = node.left;
+		const right = node.right;
+		const splitAxis = node.splitAxis;
+
+		let nextUnusedPointer;
+		nextUnusedPointer = _populateBuffer( byteOffset + BYTES_PER_NODE, left );
+
+		if ( ( nextUnusedPointer / 4 ) > MAX_POINTER ) {
+
+			throw new Error( 'MeshBVH: Cannot store child pointer greater than 32 bits.' );
+
+		}
+
+		uint32Array[ stride4Offset + 6 ] = nextUnusedPointer / 4;
+		nextUnusedPointer = _populateBuffer( nextUnusedPointer, right );
+
+		uint32Array[ stride4Offset + 7 ] = splitAxis;
+		return nextUnusedPointer;
+
+	}
+
+}
diff --git a/src/core/build/computeBoundsUtils.js b/src/core/build/computeBoundsUtils.js
index 19b2617..840067c 100644
--- a/src/core/build/computeBoundsUtils.js
+++ b/src/core/build/computeBoundsUtils.js
@@ -73,12 +73,12 @@ export function getBounds( triangleBounds, offset, count, target, centroidTarget
 // result is an array of size tris.length * 6 where triangle i maps to a
 // [x_center, x_delta, y_center, y_delta, z_center, z_delta] tuple starting at index i * 6,
 // representing the center and half-extent in each dimension of triangle i
-export function computeTriangleBounds( geo ) {
+export function computeTriangleBounds( geo, target = null ) {
 
 	const posAttr = geo.attributes.position;
 	const index = geo.index ? geo.index.array : null;
 	const triCount = getTriCount( geo );
-	const triangleBounds = new Float32Array( triCount * 6 );
+	const triangleBounds = target || new Float32Array( triCount * 6 * 4 );
 	const normalized = posAttr.normalized;
 
 	// used for non-normalized positions
diff --git a/src/debug/Debug.js b/src/debug/Debug.js
index 7495e3b..2bf8238 100644
--- a/src/debug/Debug.js
+++ b/src/debug/Debug.js
@@ -192,20 +192,11 @@ function validateBounds( bvh ) {
 		if ( isLeaf ) {
 
 			// check triangles
-			for ( let i = offset, l = offset + count; i < l; i ++ ) {
+			for ( let i = offset * 3, l = ( offset + count ) * 3; i < l; i += 3 ) {
 
-				const triIndex = bvh.resolveTriangleIndex( i );
-				let i0 = 3 * triIndex;
-				let i1 = 3 * triIndex + 1;
-				let i2 = 3 * triIndex + 2;
-
-				if ( index ) {
-
-					i0 = index.getX( i0 );
-					i1 = index.getX( i1 );
-					i2 = index.getX( i2 );
-
-				}
+				const i0 = index.getX( i );
+				const i1 = index.getX( i + 1 );
+				const i2 = index.getX( i + 2 );
 
 				let isContained;
 


Test patch:
diff --git a/test/MeshBVH.options.test.js b/test/MeshBVH.options.test.js
index ac24b61..b6f0b59 100644
--- a/test/MeshBVH.options.test.js
+++ b/test/MeshBVH.options.test.js
@@ -8,7 +8,6 @@ import {
 	MeshBVH,
 	getBVHExtremes,
 	acceleratedRaycast,
-	validateBounds,
 } from '../src/index.js';
 import { getMaxDepth } from './utils.js';
 
@@ -161,13 +160,6 @@ describe( 'Options', () => {
 
 		} );
 
-		it( 'should produce valid bounds when true.', () => {
-
-			const bvh = new MeshBVH( geometry, { indirect: true } );
-			expect( validateBounds( bvh ) ).toBeTruthy();
-
-		} );
-
 		it( 'should unpack to the same rearranged index buffer.', () => {
 
 			const geometry = new TorusGeometry( 5, 5, 40, 10 );