Rigid-body simulation

Once the Rapier WASM module has been loaded asynchronously, we can create and populate a physics world. There are a few significant differences between the Rust API of Rapier and the JS API. The most notable difference is that the JS version of Rapier does not split the physics simulation into multiple components. Instead, it is only needed to instantiate one World class to obtain a fully functional physics pipeline. It is then possible to advance the simulation step by step inside of the game loop using its world.step() method:

import('@dimforge/rapier2d').then(RAPIER => {
let gravity = new RAPIER.Vector2(0.0, -9.81);
let world = new RAPIER.World(gravity);
// Game loop. Replace by your own game loop system.
let gameLoop = () => {
world.step();
setTimeout(gameLoop, 16);
}
gameLoop();
})
info

Note that the use of RAPIER.Vector2 and RAPIER.Vector3 is not mandatory. Any object with fields x, y (for 2D), and z (for 3D) will work as well.

This World class is responsible for creating/deleting rigid bodies, colliders, and joints, advancing the simulation, retrieving contact events, etc. Now that it is created, the next step is to create rigid-bodies and colliders.

Creating rigid-bodies

A rigid-body is the simplest type of body supported by Rapier. It can be seen as the aggregation of a position, orientation, and mass properties (rotational inertia tensor, mass, and center of mass). It does not hold any information regarding its shape which can optionally be specified by attaching one or multiple colliders to it. A rigid-body with no collider will be affected by all the forces the world is aware of, including joints attached to it, but not by contacts (because it does not have any shape that can be collided to).

To create a rigid-body, we must first initialize its RigidBodyDesc descriptor:

import('@dimforge/rapier2d').then(RAPIER => {
let gravity = new RAPIER.Vector2(0.0, -9.81);
let world = new RAPIER.World(gravity);
// Create a dynamic rigid-body.
// Use "static" for a static rigid-body instead.
let rigidBodyDesc = new RAPIER.RigidBodyDesc(RAPIER.BodyStatus.Dynamic)
.setTranslation(new RAPIER.Vector2(0.0, 1.0));
let rigidBody = world.createRigidBody(rigidBodyDesc);
// Game loop. Replace by your own game loop system.
let gameLoop = () => {
world.step();
// Get and print the rigid-body's position.
let position = rigidBody.translation();
console.log("Rigid-body position: ", position.x, position.y);
setTimeout(gameLoop, 16);
};
gameLoop();
})

The RigidBodyDesc allows us to define the initial state of the rigid-body, including its initial position, orientation, velocity, etc. This descriptor is then passed to the physics World to actually create the rigid-body and add it to the world. The world.createRigidBody(desc) returns a reference to the newly created rigid-body.

note

The RigidBodyDesc and the ColliderDesc will will see afterwards follow the builder pattern.

If you run this example, you will see that the rigid-body position is always equal to (0.0, 1.0, 0.0). It is not affected by the gravity because it does not have any mass yet. The mass of a rigid-body is automatically computed from the colliders attached to it.

Creating colliders

Colliders represent the geometric shapes that generate contacts and proximity events when they touch. They are also used to automatically compute the mass and angular inertia of the rigid-body they are attached to.

Similarly to the rigid-bodies, the creation of a collider required the initialization of a ColliderDesc class. This collider description contains the shape, position, density, etc., of the collider to be created. This description can then be passed to the world.createCollider(desc, parentHandle) function to create the actual collider and attach it to the given rigidBody:

import('@dimforge/rapier2d').then(RAPIER => {
let gravity = new RAPIER.Vector2(0.0, -9.81);
let world = new RAPIER.World(gravity);
// Create a dynamic rigid-body.
// Use "static" for a static rigid-body instead.
let rigidBodyDesc = new RAPIER.RigidBodyDesc(RAPIER.BodyStatus.Dynamic)
.setTranslation(new RAPIER.Vector2(0.0, 1.0));
let rigidBody = world.createRigidBody(rigidBodyDesc);
// Create a cuboid collider attached to rigidBody.
let colliderDesc = RAPIER.ColliderDesc.cuboid(0.5, 0.5)
.setDensity(2.0); // The default density is 1.0.
let collider = world.createCollider(colliderDesc, rigidBody.handle);
// Game loop. Replace by your own game loop system.
let gameLoop = () => {
world.step();
// Get and print the rigid-body's position.
let position = rigidBody.translation();
console.log("Rigid-body position: ", position.x, position.y);
setTimeout(gameLoop, 16);
};
gameLoop();
})

With this collider created and attached to our rigidBody, we can see that the position printed in the console changes as the simulation progresses. Because our collider has a non-zero density, the rigid-body it is attached to will have a non-zero mass, which makes it affected by gravity.

What if we don't want our object to fall down indefinitely? We can easily add another rigid-body for it to collide with. This other rigid-body will be static and represent the ground. A static rigid-body isn't affected by any forces so it won't fall because of gravity:

import('@dimforge/rapier2d').then(RAPIER => {
let gravity = new RAPIER.Vector2(0.0, -9.81);
let world = new RAPIER.World(gravity);
// Create the ground
let groundRigidBodyDesc = new RAPIER.RigidBodyDesc(RAPIER.BodyStatus.Static);
let groundRigidBody = world.createRigidBody(groundRigidBodyDesc);
let groundColliderDesc = RAPIER.ColliderDesc.cuboid(10.0, 0.1);
world.createCollider(groundColliderDesc, groundRigidBody.handle);
// Create a dynamic rigid-body.
// Use "static" for a static rigid-body instead.
let rigidBodyDesc = new RAPIER.RigidBodyDesc(RAPIER.BodyStatus.Dynamic)
.setTranslation(new RAPIER.Vector2(0.0, 1.0));
let rigidBody = world.createRigidBody(rigidBodyDesc);
// Create a cuboid collider attached to rigidBody.
let colliderDesc = RAPIER.ColliderDesc.cuboid(0.5, 0.5)
.setDensity(2.0); // The default density is 1.0.
let collider = world.createCollider(colliderDesc, rigidBody.handle);
// Game loop. Replace by your own game loop system.
let gameLoop = () => {
world.step();
// Get and print the rigid-body's position.
let position = rigidBody.translation();
console.log("Rigid-body position: ", position.x, position.y);
setTimeout(gameLoop, 16);
};
gameLoop();
})

Now that the ground exists, the position of our dynamic rigid-body will stop decreasing once it hits the ground.

Applying forces and impulses

So far the only force affecting our rigid-bodies were the gravity and the contact forces created by Rapier when their colliders collide. It is possible to apply manual forces and impulses to a rigid-body in order to alter its trajectory:

import('@dimforge/rapier2d').then(RAPIER => {
// ... World initialization.
let force = new RAPIER.Vector2(1.0, 2.0);
let impulse = new RAPIER.Vector2(0.1, 0.2);
let torque = 3.0;
let torqueImpulse = 0.3;
rigidBody.applyForce(force, true);
rigidBody.applyImpulse(impulse, true);
rigidBody.applyTorque(torque, true);
rigidBody.applyTorqueImpulse(torqueImpulse, true);
})

A force or torque applied this way will affect the acceleration of the rigid-body during the next timestep according to the usual equation a=m1Fa = m^{-1}F where aa is the acceleration and FF the force.

info

All forces and torques applied to a rigid-body are cleared at the end of each timestep. To apply a continuous force or torque to rigid-body, it is necessary to call .applyForce and .applyTorque before each world.step.

An impulse or torque impulse on the other hand induce an instantaneous velocity change v+=m1ζv += m^{-1}\zeta where ζ\zeta is the impulse and VV the rigid-body velocity.

Removing a rigid-body

Once we are done with a rigid-body, it is possible to remove it from the physics world. doing so is as simple as calling world.removeRigidBody(rigidBody);. However, we need to be careful afterwards because:

  1. The rigidBody JS object will become invalid: most of its method will crash if called.
  2. All the joints and colliders attached to rigidBody will automatically be removed from the World. This implies that their corresponding JS objects will also become invalid and will lead to crashes if they are used any further.
  3. The handle of this removed rigid-body may be re-used for another newly created rigid-body. In the future, we may change this behavior to only return unique handles.