Follow me on Twitter Follow me on GitHub
Chandler Prall Thoughts & Experiments for the Web

Physics with Ammo.js + Three.js

Let’s explore the basics of using Ammo.js for physics in a Three.js environment.

For my entry to the Pokki 1UP contest I rushed out a simple physics-based WebGL game. From start to finish I had 9 days left until the game needed to be submitted. I broke that down into one day for the initial setup, a couple days for physics, one day for sound, one day for interface, and the last day for minute touch ups. Turned out to be the perfect timeline. The setup consisted of creating a Three.js environment within the Pokki framework. I also spent that first day hashing out the basic ideas, inspired by a “reverse Tetris” plan.

The second day was my first real attempt at using any physics library. Right now the most popular Javascript physics library is probably Box2D, as long as you don’t need to simulate a full 3D world. My game didn’t need an entire 3D simulation – the player can only move blocks along the X axis and gravity only applies to the Y axis; no simulation was needed across the Z axis. However, the results I got from Box2D were poor. It seemed to detect collisions a bit late and I wasn’t able to coerce any of the boxes to interact in a believable way. I attribute that to my unfamiliarity with the library, but I was on a very tight time table and I had spent a full night failing to achieve decent results.

Next night I found my winner – Ammo.js – which is a direct port from the C++ library Bullet physics. An added benefit is Ammo.js does simulate a full 3D environment, not just 2 dimensions like Box2D; this gave my game more realism as the blocks aren’t constrained to just XY coordinates. I was able to get all of the necessary physics setup in just a few hours – from a static ground plane to placeable blocks and a tipping tower. In this post I am leaving out any of the scene management of creating and adding the 3D objects themselves; if you are not familiar with Three.js I highly suggest doing yourself a favor and learn how to setup a simple scene, and then play with some of the included examples.

Setting up the scene

In order to use Ammo.js in your 3D scene you must first create the simulation world. These six lines create a default environment and add it as a property to the THREE.Scene object, making it simple to interactive with later. The only customization you need to do here is adjust the gravity vector on the last line.

var collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
var dispatcher = new Ammo.btCollisionDispatcher( collisionConfiguration );
var overlappingPairCache = new Ammo.btDbvtBroadphase();
var solver = new Ammo.btSequentialImpulseConstraintSolver();
scene.world = new Ammo.btDiscreteDynamicsWorld( dispatcher, overlappingPairCache, solver, collisionConfiguration );
scene.world.setGravity(new Ammo.btVector3(0, -12, 0));

Creating the ground

For the second part of creating a scene we need to initialize the ground object. This takes a few more lines of code than creating the world, but not by much. Again, most of the code is simply creating the default configuration. One important thing to remember here is that the shape measurements used on the first line is half of the full ground’s box. This is the case in both Ammo.js and Box2D when creating either a box or a sphere – the size you pass in is either the sphere’s radius or half the box’s measurements. Second, remember to set the ground’s mass to 0 so that gravity cannot affect it. Basic principle of physics: an object without mass cannot move (or exist, usually… but hey, this is just a simulation!)

var groundShape = new Ammo.btBoxShape(new Ammo.btVector3( 25, 1, 25 )); // Create block 50x2x50
var groundTransform = new Ammo.btTransform();
groundTransform.setIdentity();
groundTransform.setOrigin(new Ammo.btVector3( 0, -1, 0 )); // Set initial position

var groundMass = 0; // Mass of 0 means ground won't move from gravity or collisions
var localInertia = new Ammo.btVector3(0, 0, 0);
var motionState = new Ammo.btDefaultMotionState( groundTransform );
var rbInfo = new Ammo.btRigidBodyConstructionInfo( groundMass, motionState, groundShape, localInertia );
var groundAmmo = new Ammo.btRigidBody( rbInfo );
scene.world.addRigidBody( groundAmmo );

Creating boxes

So far we’ve created a 3D scene and a 3D world. They know nothing about each other, and there’s nothing going on physics-wise anyway. Now it’s time to create a box and add it to the world. There is one major point to pay attention to here: Ammo.js uses quaternions to represent an object’s rotation. Three.js models use Euler rotation by default, but you can change that by setting mesh.useQuaternion = true; You’ll see in the next section how to update a Three.js scene from the Ammo.js world.

var mass = 3 * 3 * 3; // Matches box dimensions for simplicity
var startTransform = new Ammo.btTransform();
startTransform.setIdentity();
startTransform.setOrigin(new Ammo.btVector3( position_x, 20, position_z )); // Set initial position

var localInertia = new Ammo.btVector3(0, 0, 0);

var boxShape = new Ammo.btBoxShape(new Ammo.btVector3( 1.5, 1.5, 1.5 )); // Box is 3x3x3
boxShape.calculateLocalInertia( mass, localInertia );

var motionState = new Ammo.btDefaultMotionState( startTransform );
var rbInfo = new Ammo.btRigidBodyConstructionInfo( mass, motionState, boxShape, localInertia );
var boxAmmo = new Ammo.btRigidBody( rbInfo );
scene.world.addRigidBody( boxAmmo );

boxAmmo.mesh = box; // Assign the Three.js mesh in `box`, this is used to update the model position later
boxes.push( boxAmmo ); // Keep track of this box

Lights, camera, action!

“Wait just a minute,” I hear you exclaim, “that does nothing, the box just floats in the air not moving at all!” You are correct, and it is now time to run the physics and update the 3D scene. To do that I have a function which is called from the render loop.

updateBoxes = function() {
	scene.world.stepSimulation( 1 / 60, 5 ); // Tells Ammo.js to apply physics for 1/60th of a second with a maximum of 5 steps
	var i, transform = new Ammo.btTransform(), origin, rotation;

	for ( i = 0; i < boxes.length; i++ ) {
		boxes[i].getMotionState().getWorldTransform( transform ); // Retrieve box position & rotation from Ammo

		// Update position
		origin = transform.getOrigin();
		boxes[i].mesh.position.x = origin.x();
		boxes[i].mesh.position.y = origin.y();
		boxes[i].mesh.position.z = origin.z();

		// Update rotation
		rotation = transform.getRotation();
		boxes[i].mesh.quaternion.x = rotation.x();
		boxes[i].mesh.quaternion.y = rotation.y();
		boxes[i].mesh.quaternion.z = rotation.z();
		boxes[i].mesh.quaternion.w = rotation.w();
	};
};

Finishing touches

Finally, add in some user interaction and you have a nice little demo. After the basics are in place you can begin expanding to your heart’s content. Maybe it’s as simple as dropping boxes from the air, or maybe you’d be more interested in creating a bowling ally. Perhaps a game of ping pong or air hockey? Whatever it is, using Ammo.js with Three.js is simple and there’s no reason not to try it.

If you’re anything like me, looking at an example’s source code is just as much help as a blog post introducing some concepts. An example using the code from this post can be viewed over on this page.

Ammo.js + Three.js Example

15 Responses to Physics with Ammo.js + Three.js

  1. Chris Usick says:

    Hey, this looks really good, do you think could email me the actual code you used to make this. I can’t seem to get it to work:(

  2. Chris Usick says:

    Yeah thanks. I realized that after! Its really good!

  3. Chris Usick says:

    I have never used ammo.js or Bullet physics so do you know how to use the applyForce() method?
    I know that the first argument is the force and the second is the relative position or where the force is apply from. Also that the arguments are both vectors (Ammo.btVector3()).

    Thanks.

    • chandler says:

      I like it when people look up things first – thank you :)

      First, what you’re looking for probably isn’t a Force but an Impulse. A force is something which would be applied every iteration, like gravity. An impulse is like when you shove something – a one time application of momentum.

      Second, in a lot of cases you could use applyCentralForce/applyCentralImpulse instead of applyForce/applyImpulse. The only difference is the first two also apply torque on the body.

      How to use them:
      body.applyCentralForce|applyCentralImpulse( direction_you_want_to_move_body ) // new Ammo.btVector3( 0, 10, 0 ) goes straight up.

      Like you said, applyForce and applyImpulse take a second argument which is a 3D coordinate relative to the body’s center of mass. You can use this offset to control where on the body the force or impulse is applied. If your body is a 3x3x3 box, you could use an offset of btVector3( 1.5, 0, 1.5 ) to apply the force at the vertical center of a box’s edge.

  4. Chris Usick says:

    Thank you very much. With this tutorial I am starting a little game. Thanks.

  5. Chris Usick says:

    Hey, I’ve looked around and can’t find any examples (in Bullet or ammo.js) of creating an event on a collision. I know it will have to do with the methods contactResultCallback() and contactTest() but I don’t know how to get it to work. If you could direct me to someone that could help or help me yourself that would be greatly appreciated. Thanks.

  6. Abe Petrillo says:

    Great post on combining phsyics with three.js, thanks for sharing :)

  7. jack says:

    nice post,

    i have a curve plane like this: http://jsfiddle.net/AQnWn/ (i converted this 2d shape to a 3d one like this sample: http://mrdoob.github.com/three.js/examples/webgl_geometry_shapes.html), i need to enable physics for this curve, can i create rigidBody for this shape(the 3d shape) ?, how?

    thanks

    • chandler says:

      If you only need items to bounce off of the top of the curve then you can use the Ammo.btConvexHullShape and pass in the vertex locations – here is an example. If objects will need to interact with the other side of the curve, such as collecting them in a bowl, you will need to deconstruct the curved plane into smaller segments that are much less concave.

      • jack says:

        chandler,
        i want the second one (“…such as collecting them in a bowl…”), the THREE.Shape class have extrude, getPointsGeometry, getSpacedPointsGeometry & … methods, all of these three methods returns an object (the type is not matter) that has a property of “vertices”, can i use any of these vertices for creating a “Ammo.btConvexHullShape(vertices)” ?, or i need to decompose the concave shape myself ?

        thanks for the reply

        • chandler says:

          Those are the vertices you will want to use, but unfortunately you will need to first decompose the shape yourself.

          Workflow ->
          1) Create bowl shape
          2) Deconstruct into convex shapes
          3) Create a btConvexHullShape for each deconstructed shape
          4) Combine hulls into a btCompoundShape

          • jack says:

            chandler,
            finally i do that in this way(this can be used for any other shape):

            THREE.Vector3.prototype.toBt = function () {
            return new Ammo.btVector3(this.x, this.y, this.z);
            };

            var compoundShape = new Ammo.btCompoundShape(true);
            var faces = shape3d.faces;
            var vertices = shape3d.vertices;
            faces.forEach(function (f, i, arr) {
            var v1 = vertices[f.a].toBt(),
            v2 = vertices[f.b].toBt(),
            v3 = vertices[f.c].toBt();
            var convex = new Ammo.btConvexHullShape(),
            transform = new Ammo.btTransform();
            transform.setIdentity();

            convex.addPoint(v1);
            convex.addPoint(v2);
            convex.addPoint(v3);
            if (f.d) {
            var v4 = vertices[f.d].toBt();
            convex.addPoint(v4);
            }
            compoundShape.addChildShape(transform, convex);
            });

            thanks for the help

  8. sharavsambuu says:

    Wow it’s working very well with many rigid bodies. Nice post!

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>