Simulate Glacier Crevasse Opening in Matter.js

This article explains how to model glacier crevasse formation under tensile stress using the 2D physics engine Matter.js. Because Matter.js is a rigid-body physics engine rather than a continuum mechanics simulator, crevasse opening is modeled using a discrete element approach: a lattice of interconnected rigid bodies bound by breakable elastic constraints. By measuring the elongation of these constraints under applied extensional forces, you can sever the bonds when tensile thresholds are breached, dynamically opening realistic fractures.

1. Conceptual Framework

Glacial ice fractures when longitudinal stretching (tensile stress) exceeds the tensile strength of the ice. To simulate this in Matter.js:

2. Implementation Steps

Setting Up the Matter.js Environment

Initialize the engine, renderer, and runner:

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

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

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

Render.run(render);
const runner = Runner.create();
Runner.run(runner, engine);

Generating the Ice Lattice

Create a grid of small particles and connect them horizontally and vertically with constraints:

const rows = 6;
const cols = 20;
const spacing = 20;
const particleRadius = 6;
const particles = [];
const breakableConstraints = [];

// Create particles
for (let r = 0; r < rows; r++) {
    particles[r] = [];
    for (let c = 0; c < cols; c++) {
        const isPinned = (c === 0); // Pin the upstream end
        const body = Bodies.circle(100 + c * spacing, 150 + r * spacing, particleRadius, {
            isStatic: isPinned,
            friction: 0.1,
            restitution: 0.0,
            density: 0.001
        });
        particles[r][c] = body;
        Composite.add(world, body);
    }
}

// Connect particles with constraints
function createBond(bodyA, bodyB) {
    const distance = Vector.magnitude(Vector.sub(bodyA.position, bodyB.position));
    const constraint = Constraint.create({
        bodyA: bodyA,
        bodyB: bodyB,
        stiffness: 0.8,
        damping: 0.05,
        length: distance,
        render: { strokeStyle: '#70a1ff', lineWidth: 2 }
    });
    
    // Store original rest length on the constraint for strain calculation
    constraint.restLength = distance;
    breakableConstraints.push(constraint);
    Composite.add(world, constraint);
}

for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
        if (c < cols - 1) createBond(particles[r][c], particles[r][c + 1]); // Horizontal
        if (r < rows - 1) createBond(particles[r][c], particles[r + 1][c]); // Vertical
    }
}

Simulating Tensile Stress and Fracture

Hookean tension is proportional to elongation (\(\Delta L = L - L_0\)). Attach an event listener to beforeUpdate to continuously evaluate the strain on each bond. Apply an extensional pull force on the rightmost edge to generate tensile stress throughout the glacier:

// Tensile failure threshold (maximum allowed elongation beyond rest length)
const FRACTURE_THRESHOLD = 4.0;

Events.on(engine, 'beforeUpdate', () => {
    // 1. Apply extensional tensile force to downstream edge
    for (let r = 0; r < rows; r++) {
        const downstreamBody = particles[r][cols - 1];
        Matter.Body.applyForce(downstreamBody, downstreamBody.position, { x: 0.003, y: 0.0 });
    }

    // 2. Evaluate tensile stress and fracture
    for (let i = breakableConstraints.length - 1; i >= 0; i--) {
        const constraint = breakableConstraints[i];
        
        const posA = constraint.bodyA.position;
        const posB = constraint.bodyB.position;
        const currentDistance = Vector.magnitude(Vector.sub(posA, posB));
        const extension = currentDistance - constraint.restLength;

        // Break constraint if tensile limit is exceeded
        if (extension > FRACTURE_THRESHOLD) {
            Composite.remove(world, constraint);
            breakableConstraints.splice(i, 1);
        }
    }
});

3. Tuning the Simulation