Breakable Windows with Ammo.js Constraints

This article explains how to implement a realistic, breakable glass window using fractured physics shards and Ammo.js constraints. You will learn how to structure the fractured meshes, connect them using rigid body constraints, and dynamically break those connections upon impact to simulate shattering glass.

Step 1: Pre-Fracturing the Window Mesh

To create a breakable window, you cannot use a single, solid 3D block. Instead, you must pre-fracture the window geometry into individual shards.

This is typically done in a 3D modeling tool like Blender using Voronoi cell fracturing, or programmatically at runtime. The result should be a collection of adjacent, interlocking 3D meshes that perfectly fit together to form the complete window pane.

Step 2: Creating Ammo.js Rigid Bodies for Shards

Each individual shard must be represented as a dynamic rigid body in Ammo.js so they can fall and collide after the window breaks.

  1. Shape Creation: Use btConvexHullShape to represent the collision volume of each shard. Loop through the vertices of each fractured mesh and add them to the shape.
  2. Rigid Body Setup: Create a btRigidBody for each shard. Set their mass based on their volume to maintain physical accuracy.
  3. Collision Margins: Set a very low collision margin (e.g., 0.01 or 0.001) on the shapes to prevent the shards from pushing each other apart when the physics simulation starts.

Step 3: Binding Shards with Constraints

To keep the window intact before an impact, you must lock the adjacent shards together using rigid body constraints.

  1. Find Neighboring Shards: Determine which shards are touching. You can do this by checking the proximity of their bounding boxes or analyzing shared vertices.
  2. Apply Fixed Constraints: For every pair of touching shards, create a btFixedConstraint (or a btGeneric6DofConstraint with all translational and rotational axes locked).
  3. Anchor Points: Set the constraint’s transform helper frames at the contact point between the two shards.
  4. Add to World: Add these constraints to your Ammo.js physics world using physicsWorld.addConstraint(constraint, true). The second argument disables collisions between the linked shards to prevent jittering.

To keep the entire window in its frame, create static rigid bodies for the window frame and constrain the outer edge shards of the glass to those static bodies.

Step 4: Detecting Impacts and Breaking Constraints

To make the window break, you must monitor collisions and destroy the constraints when a threshold force is exceeded.

Collision Detection

Implement a collision callback or iterate through the physics world’s contact manifolds during your physics update loop:

let dispatcher = physicsWorld.getDispatcher();
let numManifolds = dispatcher.getNumManifolds();

for (let i = 0; i < numManifolds; i++) {
    let manifold = dispatcher.getManifoldByIndexInternal(i);
    let body0 = Ammo.castObject(manifold.getBody0(), Ammo.btRigidBody);
    let body1 = Ammo.castObject(manifold.getBody1(), Ammo.btRigidBody);

    let numContacts = manifold.getNumContacts();
    for (let j = 0; j < numContacts; j++) {
        let pt = manifold.getContactPoint(j);
        let impulse = pt.getAppliedImpulse();

        // Check if the collision force exceeds the breaking threshold
        if (impulse > BREAKING_THRESHOLD) {
            breakConstraintsBetween(body0, body1);
        }
    }
}

Breaking the Constraints

When the threshold is exceeded: 1. Identify the constraints connecting the two colliding bodies (or constraints connecting those bodies to their neighbors). 2. Remove the identified constraints from the physics world using physicsWorld.removeConstraint(constraint). 3. Delete the constraint from memory using Ammo.destroy(constraint).

Once the constraints are removed, the affected shards will immediately respond to gravity and external forces, falling away from the window pane while the unbroken parts of the window remain held together by their surviving constraints.