Repository: gkjohnson/three-mesh-bvh
Pull request #700 (2024-08-31 12:04:37)

Adjusted batched mesh bvh computation functions
I thought about this a bit more and decided to restructure the batched mesh bvh computation functions. cc @agargaro - let me know what you think!

- Update the compute and dispose bounds tree functions for batched mesh to be searate
- Update functions to take an "index" argument so only a single bvh can be generated or removed
- Update colors for the example so it's more distinct from the raycast one:

<img width="715" alt="image" src="https://github.com/user-attachments/assets/8b9ce2f6-9012-420e-9ec7-9c8cfd4804da">



Patch:
diff --git a/README.md b/README.md
index a1a0cb0..be3f7dd 100644
--- a/README.md
+++ b/README.md
@@ -77,19 +77,18 @@ Using pre-made functions
 ```js
 // Import via ES6 modules
 import * as THREE from 'three';
-import { computeBoundsTree, disposeBoundsTree, acceleratedRaycast } from 'three-mesh-bvh';
-
-// Or UMD
-const { computeBoundsTree, disposeBoundsTree, acceleratedRaycast } = window.MeshBVHLib;
-
+import {
+	computeBoundsTree, disposeBoundsTree,
+	computeBatchedBoundsTree, disposeBatchedBoundsTree, acceleratedRaycast,
+} from 'three-mesh-bvh';
 
 // Add the extension functions
 THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
 THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
 THREE.Mesh.prototype.raycast = acceleratedRaycast;
 
-THREE.BatchedMesh.prototype.computeBoundsTree = computeBoundsTree;
-THREE.BatchedMesh.prototype.disposeBoundsTree = disposeBoundsTree;
+THREE.BatchedMesh.prototype.computeBoundsTree = computeBatchedBoundsTree;
+THREE.BatchedMesh.prototype.disposeBoundsTree = disposeBatchedBoundsTree;
 THREE.BatchedMesh.prototype.raycast = acceleratedRaycast;
 
 // Generate geometry and associated BVH
@@ -110,10 +109,6 @@ Or manually building the BVH
 import * as THREE from 'three';
 import { MeshBVH, acceleratedRaycast } from 'three-mesh-bvh';
 
-// Or UMD
-const { MeshBVH, acceleratedRaycast } = window.MeshBVHLib;
-
-
 // Add the raycast function. Assumes the BVH is available on
 // the `boundsTree` variable
 THREE.Mesh.prototype.raycast = acceleratedRaycast;
@@ -845,14 +840,13 @@ If the `Raycaster` member `firstHitOnly` is set to true then the [.acceleratedRa
 ### .computeBoundsTree
 
 ```js
-computeBoundsTree( options : Object ) : void
+computeBoundsTree( options? : Object ) : void
 ```
 
-A pre-made BufferGeometry and BatchedMesh extension function that builds a new BVH, assigns it to `boundsTree` for BufferGeometry or `boundsTrees` for BatchedMesh, and applies the new index buffer to the geometry. Comparable to `computeBoundingBox` and `computeBoundingSphere`.
+A pre-made BufferGeometry extension function that builds a new BVH, assigns it to `boundsTree` for BufferGeometry, and applies the new index buffer to the geometry. Comparable to `computeBoundingBox` and `computeBoundingSphere`.
 
 ```js
 THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
-THREE.BatchedMesh.prototype.computeBoundsTree = computeBoundsTree;
 ```
 
 ### .disposeBoundsTree
@@ -861,11 +855,34 @@ THREE.BatchedMesh.prototype.computeBoundsTree = computeBoundsTree;
 disposeBoundsTree() : void
 ```
 
-A BufferGeometry and BatchedMesh extension function that disposes of the BVH.
+A BufferGeometry extension function that disposes of the BVH.
 
 ```js
 THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
-THREE.BatchedMesh.prototype.disposeBoundsTree = disposeBoundsTree;
+```
+
+### .computeBatchedBoundsTree
+
+```js
+computeBatchedBoundsTree( index = - 1 : Number, options? : Object ) : void
+```
+
+Equivalent of `computeBoundsTree` for BatchedMesh. Calling this generates a `BatchedMesh.boundsTrees` array if it doesn't exist and assigns the newly generated BVHs. If `index` is -1 then BVHs for all available geometry are generated. Otherwise only the BVH for the geometry at the given index is generated.
+
+```js
+THREE.BatchedMesh.prototype.computeBoundsTree = computeBatchedBoundsTree;
+```
+
+### .disposeBatchedBoundsTree
+
+```js
+disposeBatchedBoundsTree( index = - 1 : Number, options? : Object ) : void
+```
+
+Equivalent of `disposeBoundsTree` for BatchedMesh. Calling this sets entries in `BatchedMesh.boundsTrees` array to null. If `index` is -1 then BVHs are disposed. Otherwise only the BVH for the geometry at the given index is disposed.
+
+```js
+THREE.BatchedMesh.prototype.disposeBoundsTree = disposeBatchedBoundsTree;
 ```
 
 ### .acceleratedRaycast
diff --git a/example/batchedMesh.js b/example/batchedMesh.js
index 285c7f0..df46387 100644
--- a/example/batchedMesh.js
+++ b/example/batchedMesh.js
@@ -3,17 +3,21 @@ import * as dat from 'three/examples/jsm/libs/lil-gui.module.min.js';
 import * as THREE from 'three';
 import {
 	acceleratedRaycast, computeBoundsTree, disposeBoundsTree,
+	computeBatchedBoundsTree, disposeBatchedBoundsTree,
 	CENTER, SAH, AVERAGE,
 } from '..';
 
 THREE.Mesh.prototype.raycast = acceleratedRaycast;
 THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
 THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
+
 THREE.BatchedMesh.prototype.raycast = acceleratedRaycast;
-THREE.BatchedMesh.prototype.computeBoundsTree = computeBoundsTree;
-THREE.BatchedMesh.prototype.disposeBoundsTree = disposeBoundsTree;
+THREE.BatchedMesh.prototype.computeBoundsTree = computeBatchedBoundsTree;
+THREE.BatchedMesh.prototype.disposeBoundsTree = disposeBatchedBoundsTree;
 
-const bgColor = 0x263238 / 2;
+const bgColor = 0xcfd8dc;
+const meshColor = 0x263238;
+const lineColor = 0xd81b60;
 
 let renderer, scene, stats, camera;
 let material, containerObj, batchedMesh;
@@ -61,15 +65,15 @@ function init() {
 
 	// scene setup
 	scene = new THREE.Scene();
-	scene.fog = new THREE.Fog( 0x263238 / 2, 40, 80 );
+	scene.fog = new THREE.Fog( bgColor, 40, 100 );
 
-	const light = new THREE.DirectionalLight( 0xffffff, 0.5 );
+	const light = new THREE.DirectionalLight( 0xffffff, 1.5 );
 	light.position.set( 1, 1, 1 );
 	scene.add( light );
-	scene.add( new THREE.AmbientLight( 0xffffff, 0.4 ) );
+	scene.add( new THREE.AmbientLight( 0xffffff, 1.2 ) );
 
 	containerObj = new THREE.Object3D();
-	material = new THREE.MeshPhongMaterial( { color: 0xE91E63 } );
+	material = new THREE.MeshPhongMaterial( { color: meshColor } );
 	containerObj.scale.multiplyScalar( 10 );
 	containerObj.rotation.x = 10.989999999999943;
 	containerObj.rotation.y = 10.989999999999943;
@@ -189,13 +193,13 @@ function addRaycaster() {
 
 	// Objects
 	const obj = new THREE.Object3D();
-	const material = new THREE.MeshBasicMaterial( { color: 0xffffff } );
+	const material = new THREE.MeshBasicMaterial( { color: lineColor } );
 	const origMesh = new THREE.Mesh( sphere, material );
 	const hitMesh = new THREE.Mesh( sphere, material );
 	hitMesh.scale.multiplyScalar( 0.25 );
 	origMesh.scale.multiplyScalar( 0.5 );
 
-	const cylinderMesh = new THREE.Mesh( cylinder, new THREE.MeshBasicMaterial( { color: 0xffffff, transparent: true, opacity: 0.25 } ) );
+	const cylinderMesh = new THREE.Mesh( cylinder, new THREE.MeshBasicMaterial( { color: lineColor, transparent: true, opacity: 0.5 } ) );
 
 	// Init the rotation root
 	obj.add( cylinderMesh );
@@ -288,7 +292,7 @@ function updateFromOptions() {
 	if ( params.mesh.useBoundsTree && ! batchedMesh.boundsTrees ) {
 
 		console.time( 'computing bounds tree' );
-		batchedMesh.computeBoundsTree( {
+		batchedMesh.computeBoundsTree( - 1, {
 			maxLeafTris: 5,
 			strategy: parseFloat( params.mesh.splitStrategy ),
 		} );
diff --git a/src/index.d.ts b/src/index.d.ts
index 82a2f10..3902d41 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -199,10 +199,14 @@ export class MeshBVHHelper extends Group {
 
 // THREE.js Extensions
 
-export function computeBoundsTree( options?: MeshBVHOptions ): MeshBVH | MeshBVH[];
+export function computeBoundsTree( options?: MeshBVHOptions ): MeshBVH;
 
 export function disposeBoundsTree(): void;
 
+export function computeBatchedBoundsTree( index?: Number, options?: MeshBVHOptions ): MeshBVH | MeshBVH[];
+
+export function disposeBatchedBoundsTree( index?: Number ): void;
+
 export function acceleratedRaycast(
   raycaster: Raycaster,
   intersects: Array<Intersection>
@@ -216,9 +220,9 @@ declare module 'three' {
   }
 
   export interface BatchedMesh {
-    boundsTrees?: MeshBVH[];
-    computeBoundsTree: typeof computeBoundsTree;
-    disposeBoundsTree: typeof disposeBoundsTree;
+    boundsTrees?: Array<MeshBVH | null>;
+    computeBoundsTree: typeof computeBatchedBoundsTree;
+    disposeBoundsTree: typeof disposeBatchedBoundsTree;
   }
 
   export interface Raycaster {
diff --git a/src/index.js b/src/index.js
index 0c32fd7..d6663c5 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2,7 +2,7 @@ export { MeshBVH } from './core/MeshBVH.js';
 export { MeshBVHHelper } from './objects/MeshBVHHelper.js';
 export { CENTER, AVERAGE, SAH, NOT_INTERSECTED, INTERSECTED, CONTAINED } from './core/Constants.js';
 export { getBVHExtremes, estimateMemoryInBytes, getJSONStructure, validateBounds } from './debug/Debug.js';
-export { acceleratedRaycast, computeBoundsTree, disposeBoundsTree } from './utils/ExtensionUtilities.js';
+export * from './utils/ExtensionUtilities.js';
 export { getTriangleHitPointInfo } from './utils/TriangleUtilities.js';
 export * from './math/ExtendedTriangle.js';
 export * from './math/OrientedBox.js';
diff --git a/src/utils/ExtensionUtilities.js b/src/utils/ExtensionUtilities.js
index c69c2cf..61b787d 100644
--- a/src/utils/ExtensionUtilities.js
+++ b/src/utils/ExtensionUtilities.js
@@ -151,30 +151,53 @@ function acceleratedMeshRaycast( raycaster, intersects ) {
 
 export function computeBoundsTree( options = {} ) {
 
-	if ( this.isBatchedMesh ) {
+	this.boundsTree = new MeshBVH( this, options );
+	return this.boundsTree;
 
-		if ( ! IS_REVISION_166 ) {
+}
 
-			console.error( 'Three r166+ is required.' );
-			return;
+export function disposeBoundsTree() {
 
-		}
+	this.boundsTree = null;
 
-		if ( options.indirect ) {
+}
 
-			console.warn( '"Indirect" is set to false because it is not supported for BatchedMesh.' );
+export function computeBatchedBoundsTree( index = - 1, options = {} ) {
 
-		}
+	if ( ! IS_REVISION_166 ) {
 
-		options = {
-			...options,
-			indirect: false,
-			range: null
-		};
+		throw new Error( 'BatchedMesh: Three r166+ is required to compute bounds trees.' );
 
-		const drawRanges = this._drawRanges;
-		const geometryCount = this._geometryCount;
-		const boundsTrees = [];
+	}
+
+	if ( options.indirect ) {
+
+		console.warn( '"Indirect" is set to false because it is not supported for BatchedMesh.' );
+
+	}
+
+	options = {
+		...options,
+		indirect: false,
+		range: null
+	};
+
+	const drawRanges = this._drawRanges;
+	const geometryCount = this._geometryCount;
+	if ( ! this.boundsTrees ) {
+
+		this.boundsTrees = new Array( geometryCount ).fill( null );
+
+	}
+
+	const boundsTrees = this.boundsTrees;
+	while ( boundsTrees.length < geometryCount ) {
+
+		boundsTrees.push( null );
+
+	}
+
+	if ( index < 0 ) {
 
 		for ( let i = 0; i < geometryCount; i ++ ) {
 
@@ -183,25 +206,36 @@ export function computeBoundsTree( options = {} ) {
 
 		}
 
-		this.boundsTrees = boundsTrees;
-		return this.boundsTrees;
+		return boundsTrees;
 
-	}
+	} else {
 
-	this.boundsTree = new MeshBVH( this, options );
-	return this.boundsTree;
+		if ( index < drawRanges.length ) {
+
+			options.range = drawRanges[ index ];
+			boundsTrees[ index ] = new MeshBVH( this.geometry, options );
+
+		}
+
+		return boundsTrees[ index ] || null;
+
+	}
 
 }
 
-export function disposeBoundsTree() {
+export function disposeBatchedBoundsTree( index = - 1 ) {
 
-	if ( this.isBatchedMesh ) {
+	if ( index < 0 ) {
 
-		this.boundsTrees = null;
+		this.boundsTrees.fill( null );
 
 	} else {
 
-		this.boundsTree = null;
+		if ( index < this.boundsTree.length ) {
+
+			this.boundsTrees[ index ] = null;
+
+		}
 
 	}
 


Test patch:
diff --git a/test/RandomRaycasts.test.js b/test/RandomRaycasts.test.js
index a2d4293..981db24 100644
--- a/test/RandomRaycasts.test.js
+++ b/test/RandomRaycasts.test.js
@@ -16,6 +16,8 @@ import {
 	acceleratedRaycast,
 	computeBoundsTree,
 	disposeBoundsTree,
+	computeBatchedBoundsTree,
+	disposeBatchedBoundsTree,
 	CENTER,
 	SAH,
 	AVERAGE,
@@ -26,8 +28,8 @@ Mesh.prototype.raycast = acceleratedRaycast;
 BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
 BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
 BatchedMesh.prototype.raycast = acceleratedRaycast;
-BatchedMesh.prototype.computeBoundsTree = computeBoundsTree;
-BatchedMesh.prototype.disposeBoundsTree = disposeBoundsTree;
+BatchedMesh.prototype.computeBoundsTree = computeBatchedBoundsTree;
+BatchedMesh.prototype.disposeBoundsTree = disposeBatchedBoundsTree;
 
 describe( 'Random CENTER intersections', () => runRandomTests( { strategy: CENTER } ) );
 describe( 'Random Interleaved CENTER intersections', () => runRandomTests( { strategy: CENTER, interleaved: true } ) );
@@ -144,14 +146,14 @@ function runRandomTests( options ) {
 				const geoId = batchedMesh.addGeometry( geo );
 				if ( options.onlyOneGeo ) {
 
-					batchedMesh.computeBoundsTree( options );
+					batchedMesh.computeBoundsTree( - 1, options );
 
 				}
 
 				const geo2Id = batchedMesh.addGeometry( geo2 );
 				if ( ! options.onlyOneGeo ) {
 
-					batchedMesh.computeBoundsTree( options );
+					batchedMesh.computeBoundsTree( - 1, options );
 
 				}
 
diff --git a/test/TypescriptImportTest.ts b/test/TypescriptImportTest.ts
index 7ae6d16..9c3d3a6 100644
--- a/test/TypescriptImportTest.ts
+++ b/test/TypescriptImportTest.ts
@@ -1,11 +1,13 @@
 import { BufferGeometry, Mesh, Raycaster, BatchedMesh } from 'three';
-import { acceleratedRaycast, computeBoundsTree, disposeBoundsTree } from '../src/index';
+import { acceleratedRaycast, computeBoundsTree, disposeBoundsTree, computeBatchedBoundsTree, disposeBatchedBoundsTree } from '../src/index';
 
 Mesh.prototype.raycast = acceleratedRaycast;
 BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
 BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
-BatchedMesh.prototype.computeBoundsTree = computeBoundsTree;
-BatchedMesh.prototype.disposeBoundsTree = disposeBoundsTree;
+
+BatchedMesh.prototype.raycast = acceleratedRaycast;
+BatchedMesh.prototype.computeBoundsTree = computeBatchedBoundsTree;
+BatchedMesh.prototype.disposeBoundsTree = disposeBatchedBoundsTree;
 
 const mesh = new Mesh();
 mesh.geometry.computeBoundsTree();