Collision Detection In Javascript 3D Physics using Ammo.js and Three.js


First, obtain the libraries for three.js and ammo.js. Three.js can be gotten from while for ammo.js, download the repo from, then go to the build folder for the ammo.js file.

Create your project folder and give it any name of your choice. In it create an index.html file and a “js” folder that is to contain the three.js and ammo.js files.

Heads-up: There is going to be a lot of copying and pasting if you are to follow along with this tutorial. And don’t be scared, this article is not as voluminous as it looks.

Contact Manifold Check

overlappingPairCache = new Ammo.btDbvtBroadphase()

“Dbvt” in the name stands for dynamic bounding volume tree

dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration)
if( distance > 0.0 ) continue;
ball.userData.physicsBody = ballPhysicsBody;
body.threeObject = wall;
body.threeObject = ball;
let contactManifold = dispatcher.getManifoldByIndexInternal( i );
let rb0 = Ammo.castObject( contactManifold.getBody0(), Ammo.btRigidBody );
let rb1 = Ammo.castObject( contactManifold.getBody1(), Ammo.btRigidBody );
let threeObject0 = rb0.threeObject;
let threeObject1 = rb1.threeObject;
if ( ! threeObject0 && ! threeObject1 ) continue;let userData0 = threeObject0 ? threeObject0.userData : null;
let userData1 = threeObject1 ? threeObject1.userData : null;
let tag0 = userData0 ? userData0.tag : "none";
let tag1 = userData1 ? userData1.tag : "none";
if( distance > 0.0 ) continue;
let velocity0 = rb0.getLinearVelocity();
let velocity1 = rb1.getLinearVelocity();
let worldPos0 = contactPoint.get_m_positionWorldOnA();
let worldPos1 = contactPoint.get_m_positionWorldOnB();
let localPos0 = contactPoint.get_m_localPointA();
let localPos1 = contactPoint.get_m_localPointB();
console.log({manifoldIndex: i, contactIndex: j, distance: distance});
manifoldIndex: i,
contactIndex: j,
distance: distance,
tag: tag0,
velocity: {x: velocity0.x(), y: velocity0.y(), z: velocity0.z()},
worldPos: {x: worldPos0.x(), y: worldPos0.y(), z: worldPos0.z()},
localPos: {x: localPos0.x(), y: localPos0.y(), z: localPos0.z()}
tag: tag1,
velocity: {x: velocity1.x(), y: velocity1.y(), z: velocity1.z()},
worldPos: {x: worldPos1.x(), y: worldPos1.y(), z: worldPos1.z()},
localPos: {x: localPos1.x(), y: localPos1.y(), z: localPos1.z()}


let cbContactResult;
float addSingleResult( 
[Ref] btManifoldPoint cp,
[Const] btCollisionObjectWrapper colObj0Wrap,
long partId0,
long index0,
[Const] btCollisionObjectWrapper colObj1Wrap,
long partId1,
long index1 )
physicsWorld.contactTest( ball.userData.physicsBody , cbContactResult );

Remember that your query object ( in our case the ball ) does not have to be part of the physics world for contactTest to work. However you'd have to update the transform yourself to get accurate results.


As with contactTest, the two physics objects do not need to be part of the physics world.

let redTile, cbContactPairResult;
if( == "red"){  mesh.userData.physicsBody = body;
redTile = mesh;
case 74://J




Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store