Repository: gkjohnson/three-mesh-bvh
Pull request #673 (2024-06-11 21:22:57)

Fix `InstancedMesh` raycast
I fixed the problem related to `getScaleWorld` and added tests. 

I need to clean up the code, but can you tell me if the `InstancedMesh` tests are okay? 

Shall I add tests on `BatchedMesh` too? (after https://github.com/mrdoob/three.js/pull/28462)


related #671 


Patch:
diff --git a/src/utils/ExtensionUtilities.js b/src/utils/ExtensionUtilities.js
index 90ae28c..86784b1 100644
--- a/src/utils/ExtensionUtilities.js
+++ b/src/utils/ExtensionUtilities.js
@@ -6,6 +6,7 @@ const ray = /* @__PURE__ */ new Ray();
 const direction = /* @__PURE__ */ new Vector3();
 const tmpInverseMatrix = /* @__PURE__ */ new Matrix4();
 const worldScale = /* @__PURE__ */ new Vector3();
+const tmpVec3 = /* @__PURE__ */ new Vector3();
 const origMeshRaycastFunc = Mesh.prototype.raycast;
 
 export function acceleratedRaycast( raycaster, intersects ) {
@@ -17,7 +18,7 @@ export function acceleratedRaycast( raycaster, intersects ) {
 		tmpInverseMatrix.copy( this.matrixWorld ).invert();
 		ray.copy( raycaster.ray ).applyMatrix4( tmpInverseMatrix );
 
-		this.getWorldScale( worldScale );
+		getWorldScale( this.matrixWorld, worldScale );
 		direction.copy( ray.direction ).multiply( worldScale );
 
 		const scaleFactor = direction.length();
@@ -70,3 +71,21 @@ export function disposeBoundsTree() {
 	this.boundsTree = null;
 
 }
+
+/** https://github.com/mrdoob/three.js/blob/dev/src/math/Matrix4.js#L732 */
+function getWorldScale( matrixWorld, target ) {
+
+	const te = matrixWorld.elements;
+
+	const sx = tmpVec3.set( te[ 0 ], te[ 1 ], te[ 2 ] ).length();
+	const sy = tmpVec3.set( te[ 4 ], te[ 5 ], te[ 6 ] ).length();
+	const sz = tmpVec3.set( te[ 8 ], te[ 9 ], te[ 10 ] ).length();
+
+	// // if determine is negative, we need to invert one scale
+	// const det = matrixWorld.determinant();
+	// if ( det < 0 ) sx = - sx;
+	// we don't need this.
+
+	target.set( sx, sy, sz );
+
+}


Test patch:
diff --git a/test/RandomRaycasts.test.js b/test/RandomRaycasts.test.js
index e200872..8edf264 100644
--- a/test/RandomRaycasts.test.js
+++ b/test/RandomRaycasts.test.js
@@ -7,6 +7,8 @@ import {
 	MeshBasicMaterial,
 	InterleavedBuffer,
 	InterleavedBufferAttribute,
+	InstancedMesh,
+	Object3D
 } from 'three';
 import {
 	acceleratedRaycast,
@@ -25,14 +27,17 @@ BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
 describe( 'Random CENTER intersections', () => runRandomTests( { strategy: CENTER } ) );
 describe( 'Random Interleaved CENTER intersections', () => runRandomTests( { strategy: CENTER, interleaved: true } ) );
 describe( 'Random Indirect Buffer CENTER intersections', () => runRandomTests( { strategy: CENTER, indirect: true } ) );
+describe( 'Random Instanced CENTER intersections', () => runRandomTests( { strategy: CENTER, instanced: true } ) );
 
 describe( 'Random AVERAGE intersections', () => runRandomTests( { strategy: AVERAGE } ) );
 describe( 'Random Interleaved AVERAGE intersections', () => runRandomTests( { strategy: AVERAGE, interleaved: true } ) );
 describe( 'Random Indirect Buffer AVERAGE intersections', () => runRandomTests( { strategy: AVERAGE, indirect: true } ) );
+describe( 'Random Instanced AVERAGE intersections', () => runRandomTests( { strategy: AVERAGE, instanced: true } ) );
 
 describe( 'Random SAH intersections', () => runRandomTests( { strategy: SAH } ) );
 describe( 'Random Interleaved SAH intersections', () => runRandomTests( { strategy: SAH, interleaved: true } ) );
 describe( 'Random Indirect Buffer SAH intersections', () => runRandomTests( { strategy: SAH, indirect: true } ) );
+describe( 'Random Instanced SAH intersections', () => runRandomTests( { strategy: SAH, instanced: true } ) );
 
 describe( 'Random CENTER intersections with near', () => runRandomTests( { strategy: CENTER, near: 6 } ) );
 describe( 'Random CENTER intersections with far', () => runRandomTests( { strategy: CENTER, far: 7 } ) );
@@ -96,21 +101,38 @@ function runRandomTests( options ) {
 			setSeed( transformSeed );
 			random(); // call random() to seed with a larger value
 
-			for ( var i = 0; i < 10; i ++ ) {
+			if ( options.instanced ) {
 
-				let geo = i % 2 ? groupedGeometry : ungroupedGeometry;
-				let mesh = new Mesh( geo, new MeshBasicMaterial() );
-				mesh.rotation.x = random() * 10;
-				mesh.rotation.y = random() * 10;
-				mesh.rotation.z = random() * 10;
+				const geo = groupedGeometry; // ungroupedGeometry not used...
+				const instancedMesh = new InstancedMesh( geo, new MeshBasicMaterial(), 10 );
 
-				mesh.position.x = random();
-				mesh.position.y = random();
-				mesh.position.z = random();
+				updateMatrix( instancedMesh );
 
-				scene.add( mesh );
-				mesh.updateMatrix( true );
-				mesh.updateMatrixWorld( true );
+				instancedMesh.updateMatrixWorld( true );
+
+				scene.add( instancedMesh );
+
+				const tempObj = new Object3D();
+
+				for ( var i = 0; i < 10; i ++ ) {
+
+					updateMatrix( tempObj );
+					instancedMesh.setMatrixAt( i, tempObj.matrix );
+
+				}
+
+			} else {
+
+				for ( var i = 0; i < 10; i ++ ) {
+
+					let geo = i % 2 ? groupedGeometry : ungroupedGeometry;
+					let mesh = new Mesh( geo, new MeshBasicMaterial() );
+
+					updateMatrix( mesh );
+					mesh.updateMatrixWorld( true );
+					scene.add( mesh );
+
+				}
 
 			}
 
@@ -169,3 +191,22 @@ function createInterleavedPositionBuffer( bufferAttribute ) {
 	return newBuffer;
 
 }
+
+
+function updateMatrix( target ) {
+
+	target.rotation.x = random() * 10;
+	target.rotation.y = random() * 10;
+	target.rotation.z = random() * 10;
+
+	target.position.x = random();
+	target.position.y = random();
+	target.position.z = random();
+
+	target.scale.x = random() * 2 - 1;
+	target.scale.y = random() * 2 - 1;
+	target.scale.z = random() * 2 - 1;
+
+	target.updateMatrix( true );
+
+}