Intro to JavaScript 3D Physics using Ammo.js and Three.js

This article is a JavaScript adaptation of a similar article I wrote earlier for Bullet Physics and Urho3D game engine (which can be found here). It also serves as the first part to my Javascript 3D Physics tutorials using ammojs and threejs, the second part being “Moving Objects In JavaScript 3D Physics using Ammo.js and Three.js” and the third “Collision Detection In Javascript 3D Physics using Ammo.js and Three.js

With the advent of WebGL, the web has been opened up to yet another dimensions of possibilities: 3D graphics and Interactivity. This is huge with so much opportunities in the area of gaming and immersive visualization.

Equally, its good to know that some of the concepts that were available off the web in terms of interactivity are now right here with us, for example 3D physics.

Let’s say you want to make a bowling game. You have the ball and the pins arranged. But the problem is you’d have to code all the steps involved in rolling the ball and the ball knocking down the pins. So you set out to code the dynamics involved: how the ball will roll on the floor, its collision with the pins, the force of collision, how the pins will collide against each other and so on. Before you are through you are already regretting why you did not pay attention to Physics Mechanics in school.

Well, before you start regretting, some people have probably regretted it too and came up with a solution, Physics Engines. A physics Engine is a software, a group of codes or a collection of objects and algorithms that provides functions for the simulation of a physical world, be it in games or other virtual reality application. You don’t have to stress yourself coding the movement of a bowling ball or how it collides with pins when you can have them represented by physics objects and the actions simulated in a realistic way.

What do you want to make? Is it a catapult game like AngryBirds or a shooting game or you even want to make your own soccer game, Physics Engines have you well covered.

There are a handful of JavaScript Physics Engines available, you don’t have to believe me, google it yourself. We have Matter.js, Planck.js, Cannon.js, Box2Djs, Oimojs and Ammojs. These are some of the open source once we can get and there are still yet others we don’t even know about, some mainly because they are in-house tools. Some of the above mentioned libs are for two dimensional physics simulation while others like Cannonjs, Oimojs and Ammojs are for three dimensional simulation.

For this article/tutorial we will be working with Ammojs (written as ammo.js henceforth). Ammo.js has an interesting pedigree, it’s actually a direct JavaScript port of Bullet3D a C++ based physics engine. Having worked with Bullet3D for years I have been impressed by its performance and ease of use. Good enough some of these attributed are equally reflected in ammo.js.

We will be commandeering the magical powers of three.js for our 3D graphics. Look it up at threejs.org

DISCLAIMER: This is not a tutorial on three.js but rather a simple introduction to JavaScript 3D physics with ammoj.s as a case study. Also note that this is not exclusive to three.js, it can also go for other JavaScript 3D libraries, for example, BabylonJS

IT’S A PHYSICAL WORLD

To use a Physics Engine like ammo.js there are certain things you’d have to understand:

  • Physics World: There has to be a world that obeys the laws of physics except you are in a parallel Universe that has its own set of Physical Laws. In ammo.js this world is called a Collision World and has among its derivatives the Dynamic World. The physics world has options to set gravity and exposes functions and objects for the following to be possible.
  • Rigid Body Dynamics: The force, mass, inertia, velocity and constraints of the World. In a snooker game you take a shot, the cue ball rolls and knocks against ball which gradually rolls before coming to a stop. Or you shot a hanging sign post and it swings around.
  • Collision Filtering and Detection: Collision Filtering sets which objects should collide and which should not. Like a 1Up appearing and the enemies can pass through without absorbing it, but your character passes and picks it up. On the other hand Collision Detection is about detecting when two objects collide, for example, so that you can deduct the health of a monster when your sword slashes through it.
  • Constraints: Say Joints

We will be going through the above presented concepts with examples.

WORKSPACE SETUP

First, obtain the libraries for threejs and ammojs. Threejs can be gotten from https://threejs.org/build/three.js while for ammojs, download the repo from https://github.com/kripken/ammo.js and go to the build folder for 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 you obtained. Open the index.html file in your preferred IDE and paste the below code in it.

Nothing special in the code, just your usual html and JavaScript, except for the part where ammo.js is initialized through Ammo(), a method that returns a promise. This makes sure every necessary thing needed by ammo.js is initialized and ready to work for good. You’ll also notice a comment specifying variable declaration section, we’ll use that very soon.

On running the code in the browser you’ll probably see nothing. Open up your browser console, if no error is logged then you are good to go.

PHYSICS WORLD

So first lets create our physics (physical) world. As was earlier said, this is where our physics simulations will occur.

Under the variable declaration section add declaration for the physics world:

let physicsWorld;

Next add a function to setup the physics world

Let’s explain the above code (mainly extracted from the now defunct bullet physics wiki).

Ammo.btDbvtBroadphase: Broad phase algorithm to use. The broadphase algorithm usually uses the bounding boxes of objects in the world to quickly compute a conservative approximate list of colliding pairs. The list will include every pair of objects that are colliding, but it may also include pairs of objects whose bounding boxes intersect but are still not close enough to collide.

Ammo.btDefaultCollisionConfiguration :The collision configuration allows you to fine tune the algorithms used for the full (not broadphase) collision detection

Ammo.btCollisionDispatcher: You can use the collision dispatcher to register a callback that filters overlapping broadphase proxies so that the collisions are not processed by the rest of the system.

Ammo.btSequentialImpulseConstraintSolver: This is what causes the objects to interact properly, taking into account gravity, game logic supplied forces, collisions, and hinge constraints.

Ammo.btDiscreteDynamicsWorld: This is the dynamic world, our physics world. It does come in other variants like Ammo.btSoftRigidDynamicsWorld for soft body simulation.

By now you should have noticed the conventional “bt” prefix in the class names, this was directly gotten from Bullet physics, the parent project.

For basic intro to 3D physics in JavaScript using ammo.js, this should be enough info you need to set up a decent physics world for simulation. From the last line we can see where we set the gravity of our world by calling setGravity() method of physicsWorld and passing in an ammojs vector3 for the gravity. Other useful methods exists for the physicsWorld one of such methods is the stepSimulation() to which you pass the elapsed time since it was last called. The world then runs a simulation for the time passed, updates all of its objects (collisions are checked for, dynamics applied to rigid bodies and so on) and performs other necessary functions.

Now back to our code, in the empty start() add a call to setupPhysicsWorld();

Your current code should look like this and on refreshing the browser, everything should definitely be okay, even at the web console (though no visuals yet).

TIPS: Since this tutorial will have lots of iterative steps with a direct need to constantly refresh the browser, you can have a static file server, that has the ability to reload on file change, handle this for you. One such server is live-server https://github.com/tapio/live-server .

Now let’s hurriedly add a three.js environment so as to have some visuals. Under the variable declaration section of your code add declaration for scene, camera and renderer

let physicsWorld, scene, camera, renderer;

Just After the setupPhysicsWorld function declaration, add the below codes

Also invoke the newly added methods in the start() method

function start (){    setupPhysicsWorld();    setupGraphics();
renderFrame();
}

What these two functions do is to create a three.js scene, initialize the renderer and start the render loop (or game loop).

Your current code should look like this by now and checking the browser we should see a pale blue scene.

Just A Blue Scene

Still nothing much to see, but we just prepared our world for physics simulations.

RIGID BODY AND COLLISION SHAPE

For any form of physics interaction to occur there has to be a body. In ammo.js this body is called a collision object or a rigid body (rigid body derives from collision object). A rigid body is what moves, collides, has mass and can have impulse applied to it. But on its own it is formless, more like a ghost without a shell. It needs a shape so as to interact in collisions and to also help in calculating its inertia tensor (distribution of mass). This is achieved by the addition of a Collision Shape. Quoting from Bullet wiki

Each rigid body needs to reference a collision shape. The collision shape is for collisions only, and thus has no concept of mass, inertia, restitution, etc. If you have many bodies that use the same collision shape [eg every spaceship in your simulation is a 5-unit-radius sphere], it is good practice to have only one Bullet (ammo.js) collision shape, and share it among all those bodies…

Bullet (ammo.js) supports a large variety of different collision shapes, and it is possible to add your own. For best performance and quality it is important to choose the collision shape that suits your purpose

Some of the collision shapes supported are primitive shapes (e.g box, sphere, cylinder, capsule, cone and multisphere) , compound shapes, triangle mesh, convex hull, static plane and others.

Note that when a rigid body has a mass of zero it means the body has infinite mass hence it is static.

The Physics Illusion
It is important to note that in most cases the physics world and its objects are actually not part of your scene or game world. The physics world is in a world of its own on a different realm from your game. What the physics world really does is to model the physical objects of your scene and their possible interaction using its own objects. It is then your duty to update the transform (position, rotation etc) of your object, especially in the main (game) loop, based on the state of its corresponding physics object.

An example will make this clear. Let us assume you have a box plane model in your scene, and you want a sphere to fall from a height on to it, You’d have to create a model of your scene in the physics world using objects provided by ammo.js.

The first step would be to create the physics world and set it’s gravity, just as we’ve done. Next, a block plane will be created in three.js for which a corresponding ammo.js rigid body is created with a box collision shape bearing the same dimensions as the block plane. Since we want the block plane to be static we will be setting the mass of its partner physics body to zero. For the sphere the same process applies: a sphere will be created in three.js with a corresponding rigid body in ammo.js having a sphere collision shape with dimensions same as the three.js sphere object. Since this sphere is a dynamic object we will give it a mass greater than zero, say 1.

After we’ve setup the physics world and its object, we would then call it’s stepSimulation function in our application loop, that is renderFrame method. For each of the call we obtain the new transform of the sphere physics object (rigid body) and update the transform of the sphere three.js object.

That is how it happens: while the Physics World is running, you are busy extracting out information from it to update your scene. This can also go vice versa .

Practicals. We are going to recreate the above scenario in code.

To the variable declaration section at the top of your code add

rigidBodies = [], tmpTrans;

the rigidBodies array will serve as a collection for all three.js mesh that has an associated physics object and that should be updated at each render loop. tmpTrans is for temporary ammo.js transform object that we will be reusing.

Next step add the below line at the top of start() method

tmpTrans = new Ammo.btTransform();

Then copy the below method definitions and paste in your code right after the renderFrame method definition:

in the above code we have createBlock, which is to create a block plane, createBall for creating the ball that will fall on the block and then updatePhysics to be called in the game loop to run the physics simulation and update the necessary three.js objects.

Under the three.js Section for both createBlock and createBall we have the creation of block plane and sphere respectively. For the ammo.js section there are quite a bit of things going on there so I’ll explain. Note that the explanation goes for the both creation methods unless otherwise indicated.

First we created and set the transform of our rigid body through the Ammo.btTransform object from which we create the motion state. Quoting from the Bullet physics wiki:

MotionStates are a way for Bullet to do all the hard work for you getting the objects being simulated into the rendering part of your program

What this is saying in simple terms is that motion state stores the state/status of the motion of your rigid bodies. With this it helps you obtains the transform of a physics body and to equally set it.

In the next section of the code a collision shape is created by passing in the respective dimensions of the three.js objects. Box shape for the block while sphere for the ball.
All these are passed as parameters to the rigid body construction info object. The essence of this object is to be able to create multiple rigid bodies that have the same properties with just one construction info.

After that we have the actual creation of the respective rigid bodies and then add them to the physics world.

For the createBall method, after the rigid body is added to the physics world, it is also added to userData object property of the three.js ball we created. This three.js ball is in turn added to the rigidBodies array so it can be retrieved when we want to update objects after a physics simulation as will be explained in the updatePhysics method below.

Now in the update physics method, elapsed time is passed as a parameter to be sent to the physics world’s stepSimulation method. This method runs a simulation for the elapsed time updating the various transforms of the respective physics bodies. As was noted earlier, it is now our duty to translate these updates from the physics bodies to their respective visual components.

Taking from the code: we loop through the rigidBodies array for each threejs object in it we get its associated ammo.js rigid body, obtain the world transform, apply the obtained transform to the transform of the threejs object
, end (yep that’s it).

The last step we need to take is to call the createBlock and createBall methods in the start method right after the setupGraphics method call, and to call updatePhysics in the renderFrame method just before the render.render(…) statement by passing in the deltaTime variable to it.

let deltaTime = clock.getDelta();//new line of code
updatePhysics( deltaTime );
renderer.render( scene, camera );requestAnimationFrame( renderFrame );

Your current code should look like this by now and if that’s so you should have a block showing on the screen with a ball falling towards it.

Here comes the ball

COLLISION FILTERING

An important task in using a Physics Engine is the ability to select which sets of objects collides and which sets don’t. For example in a shooting game you want your bullet to hit enemy troops but not friendly troops or you are in a fantasy /mystical game and you are able to cast a slightly transparent wall to block a monster but yet you are able to shoot fire balls through the wall.
The ammo.js has three easy ways to ensure that only certain objects collide with each other: masks, broadphase filter callbacks and nearcallbacks. We will be discussing masks in this article.

Every rigid body in ammo.js has a bitwise masks collision group and collision mask. The collision group represents the collision identity group of the rigid body while the collision mask represents other collision identity groups that should be collided with. Let us assume we have two bodies A and B, collision between them can only occur if a bitwise AND operation between the collision mask of A and the collision group of B is anything but zero and vice versa.

Using the data provided in the table below

ANDing the mask of A, 1 (which is 0001 in binary form), with the group of B, 3, will give us 0001 AND 0011 = 0001, which is non-zero. Doing it the other way round using B’s mask against A’s group would be 0010 AND 0010 = 0010, which again is non-zero. Since the non-zero condition was met in both ways bodies A and B will collide. Using this method you’ll also find out that A and C will equally collide but not B and C.

Practicals. We are going to add a green ball to our scene to demonstrate collision filtering using collision mask.

To the top of your code, just after the variable declaration line, add

let colGroupPlane = 1, colGroupRedBall = 2, colGroupGreenBall = 4

this defines the collision groups we’ll be using.

Next copy the below method to your code preferably right after the createBall method definition.

On close observation you’ll notice that the above code is similar to createBall method definition except that the position of the three.js object has been translated 30 units along the positive y axis and 1 unit along the positive x axis. Also there are two additional parameters to the physicsWorld.addRigidBody(…) method call. The first of these two parameters is for the collision group of the rigid body while the second is for the collision mask, that is, other collision groups it should collide with. To further explain this, the line of code
physicsWorld.addRigidBody( body, colGroupGreenBall, colGroupRedBall);
is actually saying that while adding the rigid body to the physics world, let it belong to the colGroupGreenBall collision group and it should collide with colGroupRedBall collision group.

Next up modify the createBlock method, replace the line that has

physicsWorld.addRigidBody( body );

with

physicsWorld.addRigidBody( body, colGroupPlane, colGroupRedBall );

Equally modify the createBall method and replace the line that has

physicsWorld.addRigidBody( body );

with

physicsWorld.addRigidBody( body, colGroupRedBall, colGroupPlane | colGroupGreenBall );

To wrap it up, call the newly added createMaskBall in the start method just under createBall method call.

By now your code should look something like this and on running it you should see two balls, red and green, falling down to the block plane, with the green ball further up. However, the green ball collides with the red ball but doesn’t collide with the block and falls through instead. That’s because we set the plane block and the green block to only collide with the red ball group and not with each other, but the red ball to collide with both of them through
colGroupPlane | colGroupGreenBall

The green ball collide with the red but falls through the block

CONSTRAINTS

Joints, that is what it is in its simplest explanation, just joints. A constraint component connects two rigid bodies together or connects a rigid body to a static point in the world. Below is a list of some of the constraints supported by ammo.js (images where obtained from Bullet Physics manual) :

  • Point to Point (p2p): Point to point constraint limits the translation so that the local pivot points of two rigid bodies match in worldspace. A chain of rigid bodies can be connected using this constraint.
Point to Point
  • Hinge Constraint: Hinge constraint, or revolute joint restricts two additional angular degrees of freedom, so the body can only rotate around one axis, the hinge axis. This can be useful to represent doors or wheels rotating around one axis. The user can specify limits and motor for the hinge.
Hinge
  • Slider Constraint: The slider constraint allows the body to rotate around one axis and translate along this axis.
Slider
  • Cone Twist Constraint: This is a special point to point constraint that adds cone and twist axis limits. x-axis serves as twist axis. To create ragdolls, the cone twist constraint is very useful for limbs like the upper arm.

We will be demonstrating a point to point constraint. This will be achieved by creating a sphere and a block and then joining them together through a p2p constraint with the block underneath.

Copy the below code and paste it right after createMaskBall method definition.

Sure most part should be familiar without much explanation. Now at the “Create Joints” section, basically what we did was to create pivot point for the respective objects. This is where the joining would be established and should be relative to the origin of the object in question. A point2point constraint was created next by passing to its constructor the two objects to be joined and their respective pivot points. Lastly it gets added to the physics world.

As usual, invoke the newly added method by calling it in the start method right after createMaskBall method call.

Your final code should be looking like this and it should run just fine showing a sphere with a block swirling underneath it.

Hang on tight

Phew!!. That’s it.

Feel free to explore and hack the examples included in the ammo.js repository. Equally you can consult the ammo.idl file also in the repo to know additional Bullet classes and interfaces exposed to JavaScript in ammo.js.

When you are good to go then move over to the second part of this tutorial Moving Objects In JavaScript 3D Physics using Ammo.js and Three.js

Happy coding.