How to Change Ammo.js Collision Shapes at Runtime

In ammo.js, a standard rigid body cannot have its collision shape swapped instantly while it is actively participating in the physics simulation. However, you can dynamically change a rigid body’s collision shape during runtime by temporarily removing the body from the physics world, applying the new shape, recalculating its inertia, and then re-adding it to the simulation. This article provides a direct, step-by-step guide and code implementation on how to achieve this safely without breaking your physics simulation.

Why Direct Swapping Fails

Ammo.js is a direct port of Bullet Physics. The physics engine caches collision detection data, contact points, and broadphase proxies for active rigid bodies. If you change the underlying collision shape of an active rigid body directly via setCollisionShape() without updating the physics world, it will cause collision glitches, ghost collisions, or immediate crashes because the cached data no longer matches the new geometry.

The Standard Re-addition Method

To safely change a collision shape at runtime, you must follow this specific sequence:

  1. Remove the rigid body from the dynamics world to clear cached collision pairs.
  2. Apply the new collision shape to the rigid body.
  3. Recalculate the local inertia based on the new shape’s geometry and the body’s mass.
  4. Update the mass properties of the rigid body.
  5. Re-add the rigid body back to the dynamics world.

Code Implementation

Here is how to implement this process in ammo.js:

function changeRigidBodyShape(dynamicsWorld, rigidBody, newShape, mass) {
    // 1. Remove the body from the physics world
    dynamicsWorld.removeRigidBody(rigidBody);

    // 2. Set the new collision shape
    rigidBody.setCollisionShape(newShape);

    // 3. Recalculate local inertia for the new shape
    const localInertia = new Ammo.btVector3(0, 0, 0);
    if (mass > 0) {
        newShape.calculateLocalInertia(mass, localInertia);
    }

    // 4. Update the rigid body's mass and inertia
    rigidBody.setMassProps(mass, localInertia);
    rigidBody.updateInertiaTensor();

    // 5. Re-add the body to the physics world
    dynamicsWorld.addRigidBody(rigidBody);

    // Clean up temporary vector
    Ammo.destroy(localInertia);
}

Alternative: Using Compound Shapes

If you need to change the collision boundaries frequently—such as toggling parts of a destructible object or extending a character’s reach—using btCompoundShape is often a highly efficient alternative.

With a compound shape, you can add, remove, or rescale child shapes dynamically using addChildShape() and removeChildShape() methods. After modifying child shapes within a compound shape, you only need to call dynamicsWorld.updateSingleAabb(rigidBody) to update the collision bounds, bypassing the need to remove and re-add the rigid body entirely.