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:
- Ice Volume: Modeled as an aggregate of small, discrete rigid bodies (e.g., small circles or rectangles) arranged in a grid.
- Cohesion: Adjacent bodies are joined using
Matter.Constraintobjects with high stiffness. - Tensile Failure: During every physics update, calculate the distance between connected bodies. If the elongation exceeds a specified critical strain threshold, remove the constraint from the world.
- Stress Application: Apply differential boundary conditions, such as pinning the upstream ice while applying an outward force or gravity gradient downstream to create tensile strain.
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
- Yield Strength: Lowering
FRACTURE_THRESHOLDsimulates brittle, cold surface ice prone to immediate cracking. A higher threshold simulates warmer, ductile ice. - Crevasse Depth (Zero Stress Depth): Real crevasses only propagate to depths where tensile stress exceeds lithostatic (overburden) pressure. Adding gravity and confining the base with static ground bodies prevents fracture propagation through the bottom layers.
- Topographic Extension: Instead of applying artificial lateral forces, place the particle assembly on a slope with a change in bed slope (convex bedrock), allowing gravity and differential friction to generate realistic extensional flow.