Simulating Momentum Conservation in Matter.js

This article explains how to accurately model the conservation of linear momentum in both elastic and inelastic collisions using the Matter.js 2D physics engine. By default, Matter.js enforces momentum conservation via its constraint- and impulse-based solver. However, environmental factors like gravity, surface friction, and air resistance bleed energy and momentum from the system. Below, you will learn how to isolate your simulation environment, configure physical properties like restitution and mass, and verify momentum preservation mathematically using Matter.js events.

Fundamentals of Collision Dynamics

Linear momentum (\(p\)) is the product of an object's mass (\(m\)) and its velocity (\(v\)):

\[p = m \cdot v\]

In an isolated system with no external net forces, total linear momentum is always conserved (\(p_{\text{initial}} = p_{\text{final}}\)):

\[m_1 v_{1i} + m_2 v_{2i} = m_1 v_{1f} + m_2 v_{2f}\]


1. Isolating the Matter.js Environment

To observe strict conservation of momentum, you must eliminate external forces such as air resistance, surface friction, and gravity.

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

const engine = Engine.create();

// Disable global gravity
engine.gravity.x = 0;
engine.gravity.y = 0;
engine.gravity.scale = 0;

When instantiating bodies, eliminate damping:


2. Simulating Perfectly Elastic Collisions

In Matter.js, elasticity is governed by the restitution property, which represents the coefficient of restitution (\(e\)). For a perfectly elastic collision, set restitution: 1 on all colliding bodies.

// Body A moving to the right
const bodyA = Bodies.circle(100, 300, 30, {
    mass: 2,
    restitution: 1, // Perfectly elastic
    friction: 0,
    frictionAir: 0,
    inertia: Infinity // Disables rotational energy transfer for pure linear tests
});

// Body B at rest
const bodyB = Bodies.circle(400, 300, 30, {
    mass: 2,
    restitution: 1, // Perfectly elastic
    friction: 0,
    frictionAir: 0,
    inertia: Infinity
});

Composite.add(engine.world, [bodyA, bodyB]);

// Impart an initial velocity to Body A
Body.setVelocity(bodyA, { x: 5, y: 0 });

Setting inertia: Infinity stops the bodies from rotating upon impact. This ensures that 100% of the energy remains in linear momentum, rather than transferring into angular momentum.


3. Simulating Inelastic Collisions

Partially Inelastic

To simulate real-world impacts where some kinetic energy is lost, set restitution to a value between 0 and 1.

bodyA.restitution = 0.5;
bodyB.restitution = 0.5;

In Matter.js, the effective restitution between two colliding bodies defaults to \(\max(\text{restitution}_A, \text{restitution}_B)\). To ensure a lower restitution takes effect, both bodies must have their restitution set accordingly.

Perfectly Inelastic (Sticking Together)

To simulate a perfectly inelastic collision where bodies do not rebound, set restitution: 0. If you want the bodies to physically latch together upon contact:

bodyA.restitution = 0;
bodyB.restitution = 0;

// Connect bodies with a constraint upon collision
Events.on(engine, 'collisionStart', (event) => {
    const pairs = event.pairs;
    for (let i = 0; i < pairs.length; i++) {
        const { bodyA: a, bodyB: b } = pairs[i];
        
        if ((a === bodyA && b === bodyB) || (a === bodyB && b === bodyA)) {
            const constraint = Matter.Constraint.create({
                bodyA: a,
                bodyB: b,
                stiffness: 1,
                length: a.circleRadius + b.circleRadius
            });
            Composite.add(engine.world, constraint);
        }
    }
});

4. Measuring and Verifying Momentum

To verify that momentum is conserved, compute the total vector momentum before and after the collision:

function calculateTotalMomentum(bodies) {
    return bodies.reduce((total, body) => {
        return {
            x: total.x + (body.mass * body.velocity.x),
            y: total.y + (body.mass * body.velocity.y)
        };
    }, { x: 0, y: 0 });
}

// Log total system momentum every engine tick
Events.on(engine, 'afterUpdate', () => {
    const totalP = calculateTotalMomentum([bodyA, bodyB]);
    console.log(`Total Px: ${totalP.x.toFixed(4)}, Total Py: ${totalP.y.toFixed(4)}`);
});

Regardless of whether restitution is set to 1 or 0, the total momentum output (\(P_x, P_y\)) will remain constant across updates, provided external friction and gravity remain at zero.