How to Simulate Custom Gravity per Object in ammo.js
This article explains how to bypass the global gravity settings in the ammo.js physics engine to simulate highly customizable directional gravity for specific individual objects. You will learn how to override default physics world behaviors, apply static custom gravity vectors, and implement dynamic calculations for complex scenarios like planetary or localized gravity fields.
The Challenge with Global Gravity
By default, an ammo.js dynamics world
(btDiscreteDynamicsWorld) applies a single gravity vector
to all active rigid bodies within it. While this works well for standard
environments, games and simulations often require objects to experience
unique gravitational pulls—such as walking on a spherical planet,
entering zero-gravity zones, or experiencing inverted gravity.
To achieve individual gravity, you must bypass the global gravity setting and manually control the gravitational forces acting on specific rigid bodies.
Step 1: Overriding Global Gravity on a Rigid Body
Ammo.js allows you to set gravity on a per-body basis using the
setGravity() method on a btRigidBody. However,
there is a critical sequence quirk: you must set the body’s
gravity after adding it to the physics world.
When you call dynamicsWorld.addRigidBody(body), ammo.js
automatically overwrites that body’s gravity with the global world
gravity.
To set a static, custom gravity direction, use the following execution order:
// 1. Create your rigid body
let rigidBody = new Ammo.btRigidBody(constructionInfo);
// 2. Add the rigid body to the physics world
dynamicsWorld.addRigidBody(rigidBody);
// 3. Override the gravity with your custom vector
let customGravity = new Ammo.btVector3(0, 10, 0); // Example: Inverted gravity
rigidBody.setGravity(customGravity);
// Clean up WebAssembly memory
Ammo.destroy(customGravity);By calling setGravity after registration, the body will
ignore the world’s default gravity and continuously apply your custom
vector.
Step 2: Simulating Dynamic Directional Gravity
If your gravity direction changes dynamically—such as an object orbiting a moving planet—you must update the gravity vector on every physics tick. This requires calculating the vector between the object and the gravity source before stepping the simulation.
Implement the update logic inside your main rendering or physics loop:
function updateDynamicGravity(objectBody, planetBody, gravityStrength = 9.8) {
// Get current positions
let objectTransform = objectBody.getWorldTransform();
let planetTransform = planetBody.getWorldTransform();
let objPos = objectTransform.getOrigin();
let planetPos = planetTransform.getOrigin();
// Calculate direction vector from object to planet
let dx = planetPos.x() - objPos.x();
let dy = planetPos.y() - objPos.y();
let dz = planetPos.z() - objPos.z();
let distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (distance > 0.001) {
// Normalize direction and scale by gravity strength
let gravX = (dx / distance) * gravityStrength;
let gravY = (dy / distance) * gravityStrength;
let gravZ = (dz / distance) * gravityStrength;
// Apply the new gravity vector
let gravityVector = new Ammo.btVector3(gravX, gravY, gravZ);
objectBody.setGravity(gravityVector);
// Free memory allocated in WebAssembly heap
Ammo.destroy(gravityVector);
}
}Call this update function immediately before stepping your dynamics world:
function tick(deltaTime) {
// Update individual gravities first
updateDynamicGravity(playerBody, planetBody);
// Step the physics simulation
dynamicsWorld.stepSimulation(deltaTime, 10);
}Step 3: Alternative Method Using Manual Forces
If you need even more granular control, or if you want to apply gravity that falls off exponentially with distance, you can set the rigid body’s gravity to zero and apply a manual central force every frame.
- Set the body’s gravity to zero:
let zeroGravity = new Ammo.btVector3(0, 0, 0);
dynamicsWorld.addRigidBody(rigidBody);
rigidBody.setGravity(zeroGravity);
Ammo.destroy(zeroGravity);- Apply a custom force on every tick:
// Calculate your custom force vector based on physics formulas
let forceVector = new Ammo.btVector3(forceX, forceY, forceZ);
// Apply central force (this must be re-applied every physics step)
rigidBody.applyCentralForce(forceVector);
Ammo.destroy(forceVector);Using applyCentralForce requires you to manually factor
in the body’s mass if you want uniform acceleration across objects of
different weights, whereas setGravity automatically
accounts for mass.