How to Simulate Earthquake Shaking in Matter.js

This guide explains how to model seismic ground motion and analyze its structural impact on multi-story building frames using the Matter.js 2D physics engine. By constructing flexible structural frames with rigid bodies and elastic constraints, anchoring them to a movable base, and driving that base with oscillating or accelerogram-based motion, you can effectively reproduce earthquake dynamics, resonance, and structural failure in a browser environment.

1. Setting Up the Environment

Begin by initializing the fundamental Matter.js modules: Engine, Render, Runner, Bodies, Composite, Constraint, and Events. Disable or tune default gravity to match your simulation's scale, typically leaving standard gravity along the Y-axis (engine.gravity.y = 1).

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

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

const render = Render.create({
    element: document.body,
    engine: engine,
    options: { width: 800, height: 600, wireframes: false }
});

Render.run(render);
Runner.run(Runner.create(), engine);

2. Building the Multi-Story Frame

A realistic multi-story frame requires vertical columns, horizontal floor beams, and connections that provide both stiffness and damping.

  1. Base/Foundation: Create a single rectangular body representing the ground slab. Set its collision filter so it does not snag on the world boundaries.
  2. Floor Slabs and Columns: Model floors and columns as distinct rectangular rigid bodies (Bodies.rectangle).
  3. Joints (Constraints): Connect columns to floor slabs using Constraint.create. Setting a stiffness value slightly below 1.0 (e.g., 0.8 to 0.95) allows the frame to flex, storing and releasing elastic potential energy under dynamic load. Add multiple constraints per joint to resist rotational moments.
const groundBase = Bodies.rectangle(400, 550, 300, 20, { isKinematic: true });
Composite.add(world, groundBase);

// Example for one story:
const leftColumn = Bodies.rectangle(320, 490, 15, 100);
const rightColumn = Bodies.rectangle(480, 490, 15, 100);
const floorBeam = Bodies.rectangle(400, 430, 200, 20);

Composite.add(world, [leftColumn, rightColumn, floorBeam]);

// Anchor columns to base
const jointLeft = Constraint.create({
    bodyA: groundBase,
    pointA: { x: -80, y: -10 },
    bodyB: leftColumn,
    pointB: { x: 0, y: 50 },
    stiffness: 0.9,
    damping: 0.05
});

Composite.add(world, jointLeft);

Stack these assemblies vertically to build additional stories, anchoring each floor's columns to the floor slab below.

3. Simulating Ground Motion

Earthquake excitation is simulated by manipulating the kinematic base body before each physics engine step using the beforeUpdate event.

Harmonic Ground Motion (Sinusoidal)

Harmonic motion allows you to test the building against resonant frequencies:

let time = 0;
const amplitude = 15; // Displacement in pixels
const frequency = 0.05; // Oscillation speed (rad/step)
const baseY = 550;
const baseX = 400;

Events.on(engine, 'beforeUpdate', () => {
    time += 1;
    
    // Calculate new position
    const targetX = baseX + amplitude * Math.sin(time * frequency);
    
    // Set position and velocity for accurate momentum transfer
    const vx = amplitude * frequency * Math.cos(time * frequency);
    Body.setVelocity(groundBase, { x: vx, y: 0 });
    Body.setPosition(groundBase, { x: targetX, y: baseY });
});

Accelerogram/Random Motion

For realistic earthquakes, import historical seismic data (time vs. acceleration) or generate filtered white noise. Calculate instantaneous velocity \(v(t) = v(t-1) + a(t) \cdot \Delta t\) and position \(x(t) = x(t-1) + v(t) \cdot \Delta t\), then update the base body accordingly.

4. Modeling Structural Failure

To simulate structural collapse:

  1. Stress Monitoring: On each engine tick, calculate the distance between the anchor points of each constraint.
  2. Breakable Joints: If the constraint length exceeds a defined strain threshold, remove the constraint from the world using Composite.remove(world, constraint).
Events.on(engine, 'afterUpdate', () => {
    const allConstraints = Composite.allConstraints(world);
    const maxStrain = 15; // Maximum allowable elongation in pixels

    allConstraints.forEach(c => {
        if (!c.bodyA || !c.bodyB) return;
        
        const posA = Vector.add(c.bodyA.position, c.pointA);
        const posB = Vector.add(c.bodyB.position, c.pointB);
        const currentLength = Vector.magnitude(Vector.sub(posA, posB));

        if (Math.abs(currentLength - c.length) > maxStrain) {
            Composite.remove(world, c);
        }
    });
});

5. Tuning Physics Parameters