In three-mesh-bvh, how to get normals of intersected tris?

In three-mesh-bvh game example, from what I understand, the following is responsible for checking intersections and calculating the offset needed to not intersect. This is great for simulating collision.

	collider.geometry.boundsTree.shapecast( {
		intersectsBounds: box => box.intersectsBox( tempBox ),
		intersectsTriangle: tri => {
			// check if the triangle is intersecting the capsule and adjust the
			// capsule position if it is.
			const triPoint = tempVector;
			const capsulePoint = tempVector2;
			const distance = tri.closestPointToSegment( tempSegment, triPoint, capsulePoint );
			if ( distance < capsuleInfo.radius ) {
				const depth = capsuleInfo.radius - distance;
				const direction = capsulePoint.sub( triPoint ).normalize();
				tempSegment.start.addScaledVector( direction, depth );
				tempSegment.end.addScaledVector( direction, depth );
			}
		}
	} );
	// get the adjusted position of the capsule collider in world space after checking
	// triangle collisions and moving it. capsuleInfo.segment.start is assumed to be
	// the origin of the player model.
	const newPosition = tempVector;
	newPosition.copy( tempSegment.start ).applyMatrix4( collider.matrixWorld );
	// check how much the collider was moved
	const deltaVector = tempVector2;
	deltaVector.subVectors( newPosition, player.position );

	// if the player was primarily adjusted vertically we assume it's on something we should consider ground
	playerIsOnGround = deltaVector.y > Math.abs( delta * playerVelocity.y * 0.25 );

For ground detection in the last line of the snipplet, the only thing being checked right now seems to essentially be whether the offset has a positive y value. This does not work with a velocity based character controller, and in my project falling next to a wall can count as being grounded, causing the player to stop falling or fall slowly while moving against a wall.


What I am hoping for, is to be able to check the intersected tris, to find if any of them qualify as the ground, by having a sufficiently high y value in the surface normal.

	collider.geometry.boundsTree.shapecast( {
		intersectsBounds: box => box.intersectsBox( tempBox ),
		intersectsTriangle: tri => {
			//...
			if ( distance < capsuleInfo.radius ) {
				//...
				console.log(tri.plane.normal);
			}
		}
	} );

When I tried checking what I believe is the tri’s normal however, all I got from the log was (1,0,0) nomatter the angle of what I was colliding with. Is there a way to do ground detection the way I am hoping, directly from the intersection checking? Or is the only way through raycasting?

From the meshbvh docs:

Note that all query functions expect arguments in local space of the BVH and return results in local space, as well. If world space results are needed they must be transformed into world space using object.matrixWorld .

…
So you’ll have to transform that plane normal through the objects matrixworld.

Something like:

var normalMatrix = new THREE.Matrix3(); // create once and reuse
var worldNormal = new THREE.Vector3(); // create once and reuse

...

normalMatrix.getNormalMatrix( object.matrixWorld );

worldNormal.copy( normal ).applyMatrix3( normalMatrix ).normalize();

(thanks @WestLangley!)

1 Like

How much cheaper or costlier will this be compared to maybe 1 or 2 raycasts to check for surface normal?

Also, I’m not sure how to do this right:

//after loading my environment mesh
mesh.updateMatrixWorld(true);
normalMatrix.getNormalMatrix(mesh.matrixWorld);

//inside intersect checking
collider.geometry.boundsTree.shapecast( {
	intersectsBounds: box => box.intersectsBox( tempBox ),
	intersectsTriangle: tri => {
	//...
	if ( distance < capsuleInfo.radius ) {
		//...
		worldNormal.copy(tri.plane.normal).applyMatrix3(normalMatrix).normalize();
		console.log(worldNormal);
	}
}

I’m still getting only (1,0,0) from the log.

Ahh bummer… well we might need to make some kind of reproduction of the problem in glitch or codepen to debug the problem further.

Here’s an empty glitch app shell you can put your code in… “remix” this glitch, then edit the code in TEST.js, and “share” the code link here.

Am I using the method how you intended at least?

Also if this method is gonna be costlier than a raycast or 2, that would also be reason enough for me to stop pursuing this method of getting the surface normal.

Oh you’re totally on the right track… mesh-bvh raycast is indeed faster than the default threejs raycast, on static/complex geometry.

The default threejs raycaster just does this normal transformation for you internally before returning the result iirc, as a convenience.

I think mesh-bvh doesn’t do that because it uses those shapecasting methods internally and doesn’t always need the normal in world space, so it leaves that up to the user.

Can you console.log( the mesh.worldMatrix, mesh.position, mesh.scale, mesh.rotation? or inspect them in the debugger?
If they are non 0/identity, then something is wrong in how were doing the normal calculation.

I usually solve these kinds of problems by stepping through the code in the debugger, and hovering the mouse over each thing along the way to verify that it contains what i expect…

edit: I asked chatGPT, and it thinks roughly the same:

After a good night’s sleep and a little bit of thinking and looking, I found the Triangle’s “getNormal” function too.

So currently in my project, the intersection check reveals the individual tris of the world environemnt which intersect with my player capsule, and I can use that method to get the normal of said tris.

collider.geometry.boundsTree.shapecast( {
	intersectsBounds: box => box.intersectsBox( tempBox ),
	intersectsTriangle: tri => {
	//...
	if ( distance < capsuleInfo.radius ) {
		//...
		let tempNormal = new THREE.Vector3();
		tri.getNormal(tempNormal);
		console.log(tempNormal);
	}
}

I just wish I didn’t have to use a new Vector3 every time. Are there any risks for creating many Vector3? And again on the question of efficiency or speed, would this be costlier compared to a few raycasts, or compared to how you seemed to be pre-mapping everything?

1 Like

Hey @laperen !

Did you manage to progress with this one? I have a similar issue regarding ground check that I just can’t figure out.
When going down on a slope, the playerIsOnGround variable is mostly false. I’ve tried to use the triangle normal, but that doesn’t seem to be helpful either as it sometimes returns 0 vector, I guess because of the forward vector for some frames the capsule doesn’t collide with the ground until gravity moves it downwards.

Do you have any idea for a more accurate ground detection?

I have since put my tinkering with ThreeJS on hiatus due to real life. Before I stopped, I also encountered the same problem when walking on a down slope. The 2 ideas I had no time to implement both involve shooting a raycast.

Idea 1 is simple, but might be naive. Shoot a ray directly downward, and let that ray handle ground detection. Very direct and definitely usable for simpler stuff.

Idea 2 I have no idea if it will work properly, but feels more flexible at least in my head. Ground detection will be handled as has been discussed in the thread so far. The movement velocity however will be made to conform to the surface as much as possible. Shoot a ray diagonally ahead in the direction of movement, and compare the point result to the current position. Transform the movement velocity based on the delta, this should allow the movement to account for height differences. I did something similar in Unity before, just yet to try this in ThreeJS.

1 Like

Gonna experiment with your ideas, thank you. If I come up with a viable solution, I’ll let you know!

Yeah creating lots of Vector3s in the hot part of your rendering loop isn’t great. Generally you want to allocate some temporaries outside of the function, and re-use them.
For geometric problems like this, I often make a batch of temporaries in the file like:

let v0 = new THREE.Vector3();
let v1 = new THREE.Vector3();
let v2 = new THREE.Vector3();
let v3 = new THREE.Vector3();

4 of them is usually enough to cover most geometric problems…

If you haven’t seen this character control sample in meshbvh yet, it works well and was easy to get working in my tests:

https://gkjohnson.github.io/three-mesh-bvh/example/bundle/characterMovement.html

Well, figured the best solution would be to cast a ray down and take into account the slope angle when setting the movement direction vector.
At least this is what the internet says mostly, but I just don’t know how should I do it in code actually. I have the ray and the surface normal, but have no idea how to apply it the movement vector.