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:

  1. Create a parent 3D Object (Group) that represents the physics body.
  2. Add the visual mesh as a child of this Group.
  3. 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.