Move Ammo.js Rigid Body Without Breaking Physics

Moving an active rigid body in ammo.js (the JavaScript port of the Bullet physics engine) requires careful handling to prevent glitches, clipping, or breaking the physics solver. This article explains the three safest methods to translate or teleport an active rigid body: resetting its world transform with state activation, applying physical forces, and utilizing kinematic states for manual control.

Method 1: Safe Teleportation (Direct Position Update)

Directly modifying a rigid body’s position during an active simulation can cause severe physics glitches, such as objects passing through walls or experiencing extreme velocity spikes. To safely teleport a rigid body, you must update its world transform, update its motion state, reset its velocities, and force the body to wake up.

function teleportBody(body, position, rotation) {
    // 1. Create a new transform
    const transform = new Ammo.btTransform();
    transform.setIdentity();
    
    // 2. Set the new origin (position)
    const ammoPosition = new Ammo.btVector3(position.x, position.y, position.z);
    transform.setOrigin(ammoPosition);
    
    // 3. Set the rotation (quaternion)
    const ammoRotation = new Ammo.btQuaternion(rotation.x, rotation.y, rotation.z, rotation.w);
    transform.setRotation(ammoRotation);
    
    // 4. Apply to both the world transform and the motion state
    body.setWorldTransform(transform);
    if (body.getMotionState()) {
        body.getMotionState().setWorldTransform(transform);
    }
    
    // 5. Reset velocities to prevent "slingshot" physics bugs
    const zeroVector = new Ammo.btVector3(0, 0, 0);
    body.setLinearVelocity(zeroVector);
    body.setAngularVelocity(zeroVector);
    
    // 6. Force the physics engine to re-evaluate the body
    body.activate(true);
    
    // Clean up memory
    Ammo.destroy(transform);
    Ammo.destroy(ammoPosition);
    Ammo.destroy(ammoRotation);
    Ammo.destroy(zeroVector);
}

Method 2: Physics-Based Movement (Forces and Impulses)

If you want the body to move naturally while interacting with the environment, do not set the position directly. Instead, apply forces or impulses. This allows the solver to calculate collisions and friction realistically.

// Apply a continuous push (run this in your physics update loop)
const force = new Ammo.btVector3(x, y, z);
body.applyCentralForce(force);
body.activate(true); // Ensure the body is awake
Ammo.destroy(force);

Method 3: Direct Velocity Manipulation

For precise control (such as a player character or a moving platform) where you want instantaneous movement without ignoring physics collisions, set the linear velocity directly.

// Set a specific velocity vector
const velocity = new Ammo.btVector3(velocityX, velocityY, velocityZ);
body.setLinearVelocity(velocity);
body.activate(true);
Ammo.destroy(velocity);

Method 4: Switch to a Kinematic Body

If you need to manually control an object’s position frame-by-frame while still allowing it to push other dynamic objects out of the way, convert the body to a Kinematic Object. Kinematic bodies are not affected by gravity or forces; they are moved solely by your code.

To set up a kinematic body during initialization:

// Get existing collision flags and add the kinematic flag
const kinematicFlag = 2; // btCollisionObject::CF_KINEMATIC_OBJECT
body.setCollisionFlags(body.getCollisionFlags() | kinematicFlag);

// Disable gravity influence
body.setActivationState(4); // DISABLE_DEACTIVATION

To move a kinematic body, update its motion state transform inside your render/update loop:

function moveKinematicBody(body, position) {
    const transform = new Ammo.btTransform();
    transform.setIdentity();
    
    const ammoPosition = new Ammo.btVector3(position.x, position.y, position.z);
    transform.setOrigin(ammoPosition);
    
    body.getMotionState().setWorldTransform(transform);
    
    Ammo.destroy(transform);
    Ammo.destroy(ammoPosition);
}