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.
- Shape Creation: Use
btConvexHullShapeto represent the collision volume of each shard. Loop through the vertices of each fractured mesh and add them to the shape. - Rigid Body Setup: Create a
btRigidBodyfor each shard. Set their mass based on their volume to maintain physical accuracy. - Collision Margins: Set a very low collision margin
(e.g.,
0.01or0.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.
- Find Neighboring Shards: Determine which shards are touching. You can do this by checking the proximity of their bounding boxes or analyzing shared vertices.
- Apply Fixed Constraints: For every pair of touching
shards, create a
btFixedConstraint(or abtGeneric6DofConstraintwith all translational and rotational axes locked). - Anchor Points: Set the constraint’s transform helper frames at the contact point between the two shards.
- 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.