Moving Objects In JavaScript 3D Physics using Ammo.js and Three.js
This article is a continuation to Intro to JavaScript 3D Physics using Ammo.js and Three.js. I strongly suggest you go through the first part before continuing.
So, from the earlier article, we’ve seen how to setup the physics world, add objects to it and even filter collisions using masks. We equally touched on constraints (say joints).
What if you wanted move the objects around, how would you go about it? For example in a game you want your character to move while the walk animation is playing or you need a floating block that would ferry a player across a dangerous sea of lava unto a safe section of the game scene.
To be able to achieve this we'd have to revisit some fundamentals.
Dynamic, Static and Kinematic Rigid Bodies
Quoting from bullet user manual:
There are 3 different types of objects in Bullet
Dynamic (moving) rigidbodies: positive mass, every simulation frame the dynamics will update its world transform.
Static rigidbodies: zero mass, cannot move but just collide.
Kinematic rigidbodies: zero mass, can be animated by the user, but there will be only one way interaction: dynamic objects will be pushed away but there is no influence from dynamic objects.
Heads-up: There is going to be a lot of copying and pasting if you are to follow along with this tutorial.
Dynamic Rigid Bodies
Dynamic rigid bodies have mass greater than zero and will move around being affected by the forces and laws governing the physics world. This means they will be affected by gravity, impulse, and would respond appropriately to collisions from other bodies. To move a dynamic rigid body you either use applyForce
, applyImpulse
or setLinearVelocity
. These are all rigid body methods.
applyForce
and applyImpulse
“would result in a body acceleration, so your characters will have momentum”, but for this tutorial we will use setLinearVelocity
.
We could be creating an example to demonstrate this. Our example will consist of a ball that will be moved around using the WASD keys.
We’ll still maintain the workspace setup used for the earlier tutorial. So once again if you haven’t gone through that tutorial please do
Go ahead and create a html file in the workspace and give it any reasonable name. Copy the below code into it as a form of bootstrapping.
If you view this in your browser you’ll see a plane with a red ball on it.
Under the variable declaration section of the code add:
let ballObject = null,
moveDirection = { left: 0, right: 0, forward: 0, back: 0 }const STATE = { DISABLE_DEACTIVATION : 4 }
ballObject
will be our handle to the ball and moveDirection
will be used to hold the respective directional key (WASD) that is pressed.
Constant STATE
is a compiler-define in the bullet source file ( btCollissionObject.h). As at the time of this writing it is yet to be included in ammo.idl, however its value gives the expected result in engine.
When a rigid body is no longer participating in dynamic interaction in the physics world, ammojs deactivates it. In this deactivated state we won’t be able to apply force to it. To stop this from happening set the activation state for the rigid body to STATE.DISABLE_DEACTIVATION
.
Back to the code. Go to definition of createBall
method and change the line that has:
let ball = new THREE.Mesh(new THREE.SphereBufferGeometry(radius), new THREE.MeshPhongMaterial({color: 0xff0505}));
to
let ball = ballObject = new THREE.Mesh(new THREE.SphereBufferGeometry(radius), new THREE.MeshPhongMaterial({color: 0xff0505}));
This sets the ballObject
as a handle to the ball that we created.
We are not done yet. After the line that says body.setRollingFriction(10);
add
body.setActivationState( STATE.DISABLE_DEACTIVATION );
Next is to handle keydown
and keyup
events for the WASD keyboard keys.
Copy and paste the below snippet after renderFrame()
method definition:
The above wires the keyboard’s keydown
and keyup
events to appropriate event handlers that are also defined in the snippet.
How the event handlers work is to set a key’s moveDirection
component to 1 when the key is pressed (handleKeyDown()
) and to 0 when released (handleKeyUp()
). Pretty straight forward I guess.
Lest we forget, call this newly added setupEventHandlers()
method inside start()
method at the top, right before the invocation of renderFrame()
. Your start()
method should look like :
function start (){ tmpTrans = new Ammo.btTransform(); setupPhysicsWorld(); setupGraphics();
createBlock();
createBall(); setupEventHandlers();
renderFrame();}
Now to actually move the ball we have to
- first resolve the directions set in
moveDirection
variable - set a
vector
with the resolved directions - multiply the
vector
by a scaling factor - then apply the
vector
as the linear velocity of the balls rigid body
All these are contained in the the moveBall()
method below. Paste it into your code after createBall()
method definition
Note that the y component is zero because we didn’t handle up and down movement.
To renderFrame()
method body add a call to moveBall()
just after the line that has
let deltaTime = clock.getDelta();
renderFrame()
should now look like:
function renderFrame(){ let deltaTime = clock.getDelta(); moveBall(); updatePhysics( deltaTime ); renderer.render( scene, camera ); requestAnimationFrame( renderFrame );}
If you followed every instruction above then your final code should look like this and on pressing the WASD keys you’ll notice that the red ball moves. Here is a working demo.
Pause for a bit of reflection.
In essence what is happening is that for each render loop we check if any directional key is pressed. If so we apply linear velocity, based on the resolution of the directional keys, to the physics rigid body. In this way our object moves.
Static Rigid Body
Static rigid bodies, which have a mass of zero, are just what their name says . They should never be moved by the user
Kinematic Rigid Body
Quoting from bullet user manual:
If you plan to animate or move static objects, you should flag them as kinematic. Also disable the sleeping/deactivation for them during the animation. This means Bullet (ammojs) dynamics world will get the
new worldtransform from the btMotionState every simulation frame.
kinematic objects are static objects that can be moved, but by the user. They are not affected by any force from the physics world not even gravity, they are just there. A good example of where a kinematic object would be useful, as stated earlier above, is a floating block that would ferry a player across a dangerous sea of lava unto a safe section of the game.
Since kinematic bodies are not affected by forces in the physics world it means we cannot use applyForce
to move them, neither can we use applyImpulse
nor setLinearVelocity
. To move a kinematic rigid body you have to set it’s physics world transform (position and rotation ) from your 3d world. So while in the case of dynamic rigid bodies we get the current position and rotation from the physics world and update our 3d objects, in kinematic rigid bodies reverse is the case: get the current position and rotation from our 3d objects and update the physics objects.
To demonstrate this we will add a kinematic box to our scene to be moved around using the arrow keys (←↑↓→).
Back to our code. Under the variable declaration section add
let kObject = null,
kMoveDirection = { left: 0, right: 0, forward: 0, back: 0 },
tmpPos = new THREE.Vector3(), tmpQuat = new THREE.Quaternion();
kObject
will be our handle to the kinematic object while kMoveDirection
just like moveDirection
will hold the keypress direction for moving the kinematic object. tmpPos
and tmpQuat
are temporary vector and quaternion respectively.
Still in this section add const FLAGS = { CF_KINEMATIC_OBJECT: 2 }
Just like the case for the constant STATE
, FLAGS
is an enum type in the bullet source but was not included in ammo.idl though its value still gives the expected result.
When a rigid body is flagged as kinematic using FLAGS.CF_KINEMATIC_OBJECT
ammojs will treat is as such.
Next up is to create our kinematic box
Add the above to the code after createBall()
method definition. There is no much different between this and others we’ve been using to create physics objects except for the line highlighted below that flags it as a kinematic object:
body.setActivationState( STATE.DISABLE_DEACTIVATION );
body.setCollisionFlags( FLAGS.CF_KINEMATIC_OBJECT );
Now call createKinematicBox()
from start()
method preferably after createBall()
.
Your start()
method should currently look like
function start (){ tmpTrans = new Ammo.btTransform(); setupPhysicsWorld(); setupGraphics();
createBlock();
createBall();
createKinematicBox(); setupEventHandlers();
renderFrame();}
If you check your browser at this stage you should see our box right there in the scene. Try moving the ball against it and notice that it is not affected by the collision.
Now to the important part. Moving the box.
Let’s handle the keydown
and keyup
of the keyboard’s arrow keys. Add the below code to the switch…case
statement in handleKeyDown()
case 38: //↑: FORWARD
kMoveDirection.forward = 1
break;
case 40: //↓: BACK
kMoveDirection.back = 1
break;
case 37: //←: LEFT
kMoveDirection.left = 1
break;
case 39: //→: RIGHT
kMoveDirection.right = 1
break;
Equally add the following to that of handleKeyUp()
case 38: //↑: FORWARD
kMoveDirection.forward = 0
break;
case 40: //↓: BACK
kMoveDirection.back = 0
break;
case 37: //←: LEFT
kMoveDirection.left = 0
break;
case 39: //→: RIGHT
kMoveDirection.right = 0
break;
Before adding the method to move our kinematic box, let’s put in place some helpful variables.
To the variable declaration section add
let ammoTmpPos = null, ammoTmpQuat = null;
Proceed to start()
method, after the line that has
tmpTrans = new Ammo.btTransform();
add
ammoTmpPos = new Ammo.btVector3();
ammoTmpQuat = new Ammo.btQuaternion();
Good.
It’s time to finally move our kinematic box.
Paste the following method definition after that of moveBall
Let’s explain what’s happening above:
- First, directions in
kMoveDirection
are resolved and used in translating the box in 3d world. - Next, the 3d box’s world transform (position and quaternion) is obtained.
- Lastly we get the physics rigid body attached to the 3d box, retrieve it’s motion state and set its world transform with that obtained from the 3d box.
That it is.
Oh, one more, add a call to moveKinematic()
in renderFrame()
just after moveBall()
. renderFrame()
should now look more like
function renderFrame(){ let deltaTime = clock.getDelta();
moveBall();
moveKinematic(); updatePhysics( deltaTime ); renderer.render( scene, camera ); requestAnimationFrame( renderFrame );}
Having done everything that has been outlined in this tutorial, you should have your final code looking like this . Press the arrow keys to move the box around. Notice how the box can push the ball around but is itself not affected.
Here is an online demo of it.
As a bonus I’ve added this link containing the above demo but modified to shoot out balls on mouse click (source code)
Phew!!. That’s it once again.
Feel free to modify the codes, explore and experiment as much as you like. If there is any mistake do let me know, I will be delighted to make corrections.
Enjoys.
(Next article: Collision Detection In Javascript 3D Physics using Ammo.js and Three.js)