Shift Center of Mass in Ammo.js Rigid Body
Shifting the center of mass for an asymmetric rigid body in Ammo.js
requires a specific approach because the engine assumes the local origin
of a rigid body is always its center of mass. This article explains how
to use a compound shape (btCompoundShape) to offset the
collision geometry relative to the physics origin, effectively shifting
the center of mass, and how to align your visual representation
accordingly.
The Core Concept
In Bullet Physics (and its JavaScript port, Ammo.js), you cannot
directly modify the center of mass property of a
btRigidBody. The center of mass is hardcoded to the local
origin (0, 0, 0) of the body.
To shift the center of mass, you must offset the collision shape
itself away from this origin. The standard and most efficient way to
achieve this is by nesting your actual collision shape inside a
btCompoundShape with a translation transform.
Step-by-Step Implementation
1. Create a Compound Shape
A btCompoundShape is a container that can hold multiple
child shapes, each with its own local transform. Even if you only have
one asymmetric shape, you must use a compound shape to apply the
offset.
const compoundShape = new Ammo.btCompoundShape();2. Define the Local Transform Offset
Determine how much you want to shift the center of mass. If you want
the center of mass to move down by 1 unit, you must move
the collision shape up by 1 unit relative
to the local origin.
const localTransform = new Ammo.btTransform();
localTransform.setIdentity();
// Shift the geometry up to make the center of mass lower
const offset = new Ammo.btVector3(0, 1.0, 0);
localTransform.setOrigin(offset);3. Add the Child Shape to the Compound Shape
Pass your original, asymmetric collision shape (e.g., a
btConvexHullShape or btBoxShape) and the
transform to the compound shape.
const asymmetricShape = new Ammo.btBoxShape(new Ammo.btVector3(1, 2, 1)); // Example shape
compoundShape.addChildShape(localTransform, asymmetricShape);4. Construct the Rigid Body
Use the btCompoundShape as the collision shape when
constructing your btRigidBody. Calculate the local inertia
using the compound shape.
const mass = 10.0;
const localInertia = new Ammo.btVector3(0, 0, 0);
compoundShape.calculateLocalInertia(mass, localInertia);
const startTransform = new Ammo.btTransform();
startTransform.setIdentity();
startTransform.setOrigin(new Ammo.btVector3(0, 5, 0)); // World position
const motionState = new Ammo.btDefaultMotionState(startTransform);
const rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, compoundShape, localInertia);
const body = new Ammo.btRigidBody(rbInfo);
physicsWorld.addRigidBody(body);5. Align the Visual Mesh (e.g., Three.js)
Because the physics origin (0, 0, 0) is now offset from
the center of the geometry, your 3D rendering engine (such as Three.js)
will look misaligned if you simply copy the rigid body’s transform
directly to the mesh.
To fix this, you must apply the inverse offset to your visual mesh geometry or wrap the visual mesh in a parent group:
- Create a parent 3D Object (Group) that represents the physics body.
- Add the visual mesh as a child of this Group.
- Apply the inverse offset to the child mesh’s position.
// Three.js Example
const physicsGroup = new THREE.Group(); // This tracks the Ammo.js body transform
const visualMesh = new THREE.Mesh(geometry, material);
// Apply the inverse offset of the physics shape translation
visualMesh.position.set(0, -1.0, 0);
physicsGroup.add(visualMesh);
scene.add(physicsGroup);During your physics update loop, apply the transform of the Ammo.js
rigid body directly to physicsGroup. The visual mesh will
remain perfectly aligned with the offset physical boundaries.