Simulate Bridge Load and Collapse in Matter.js

This article explains how to model an architectural arch bridge, simulate static and dynamic load bearing, and implement realistic structural collapse using the Matter.js 2D physics engine. You will learn how to generate arch geometry with rigid bodies, secure the structure with abutments, apply loads, and monitor constraint stress to trigger catastrophic structural failure when thresholds are exceeded.

1. Understanding Arch Bridge Mechanics in 2D Physics

A masonry or rigid arch carries loads primarily through compressive forces directed along the curve down into the abutments. In Matter.js, pure rigid-body contact can simulate basic compression through high surface friction, but architectural collapse simulation requires tracking tensile, compressive, and shear limits.

To simulate these material limits, you build the arch using segmented blocks (voussoirs) connected by breakable spring constraints (Matter.Constraint). When external loads induce stresses beyond a block's capacity, these constraints break, redistributing forces dynamically and causing the arch to crumble under its own weight.

2. Constructing the Arch Geometry and Abutments

The arch must rest between two immovable anchors (abutments) to counteract the outward horizontal thrust generated by load bearing.

const { Engine, Render, Runner, Bodies, Composite, Constraint, Events, Vector } = Matter;

const engine = Engine.create();
const world = engine.world;

// Create static abutments
const leftAbutment = Bodies.rectangle(150, 450, 100, 200, { isStatic: true });
const rightAbutment = Bodies.rectangle(650, 450, 100, 200, { isStatic: true });
Composite.add(world, [leftAbutment, rightAbutment]);

// Arch parameters
const segments = 15;
const archRadius = 250;
const centerX = 400;
const centerY = 450;
const startAngle = Math.PI * 0.85;
const endAngle = Math.PI * 0.15;
const angleStep = (startAngle - endAngle) / (segments - 1);

const voussoirs = [];
for (let i = 0; i < segments; i++) {
  const angle = startAngle - i * angleStep;
  const x = centerX + archRadius * Math.cos(angle);
  const y = centerY - archRadius * Math.sin(angle);

  const block = Bodies.rectangle(x, y, 35, 20, {
    angle: -angle + Math.PI / 2,
    density: 0.005,
    friction: 0.9,
    restitution: 0.05
  });

  voussoirs.push(block);
  Composite.add(world, block);
}

3. Binding Segments with Breakable Constraints

To model material cohesion, bind adjacent voussoirs using two parallel constraints per joint. Using dual constraints allows the joint to resist both rotational bending moments and linear tension.

const breakableConstraints = [];
const FAILURE_THRESHOLD = 0.08; // Maximum deformation/force limit

function createJoint(bodyA, bodyB, pointA, pointB) {
  const constraint = Constraint.create({
    bodyA,
    bodyB,
    pointA,
    pointB,
    stiffness: 0.9,
    damping: 0.1,
    render: { strokeStyle: '#aaaaaa' }
  });

  // Attach a custom property to store initial target length
  constraint.initialLength = Vector.magnitude(
    Vector.sub(
      Vector.add(bodyA.position, pointA),
      Vector.add(bodyB.position, pointB)
    )
  );

  breakableConstraints.push(constraint);
  Composite.add(world, constraint);
}

// Connect voussoirs sequentially
for (let i = 0; i < voussoirs.length - 1; i++) {
  createJoint(voussoirs[i], voussoirs[i + 1], { x: 0, y: -8 }, { x: 0, y: -8 });
  createJoint(voussoirs[i], voussoirs[i + 1], { x: 0, y: 8 }, { x: 0, y: 8 });
}

// Anchor ends to abutments
createJoint(leftAbutment, voussoirs[0], { x: 30, y: -60 }, { x: 0, y: 0 });
createJoint(rightAbutment, voussoirs[voussoirs.length - 1], { x: -30, y: -60 }, { x: 0, y: 0 });

4. Simulating Load Bearing

Apply static and dynamic loads by dropping heavy bodies onto the bridge deck or by generating a downward force directly at the crown (the highest center block).

// Adding a load over the center of the arch
const load = Bodies.rectangle(400, 150, 60, 60, {
  density: 0.05, // Heavy load to induce structural strain
  friction: 0.8
});
Composite.add(world, load);

5. Evaluating Stress and Triggering Collapse

In Matter.js, constraints do not have built-in breakage triggers. To simulate collapse, attach a listener to the beforeUpdate engine event. During each tick, compute the current distance between the two anchor points. When a constraint stretches beyond the tolerated displacement threshold, it snaps, and Matter.js removes it from the simulation.

Events.on(engine, 'beforeUpdate', () => {
  for (let i = breakableConstraints.length - 1; i >= 0; i--) {
    const c = breakableConstraints[i];

    // Compute actual world coordinates of attachment points
    const posA = c.bodyA ? Vector.add(c.bodyA.position, c.pointA) : c.pointA;
    const posB = c.bodyB ? Vector.add(c.bodyB.position, c.pointB) : c.pointB;
    
    const currentLength = Vector.magnitude(Vector.sub(posA, posB));
    const deformation = Math.abs(currentLength - c.length);

    // If stress exceeds structural limits, break the constraint
    if (deformation > FAILURE_THRESHOLD) {
      Composite.remove(world, c);
      breakableConstraints.splice(i, 1);
    }
  }
});

When enough constraints fail around the critical load point, the voussoirs slip past each other. The horizontal thrust forces turn into unconstrained rotational movement, causing the remaining arch structure to undergo realistic cascading structural collapse.