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}\]
- Elastic Collisions: Both linear momentum and total kinetic energy are conserved. Bodies bounce off one another without permanent deformation or heat generation.
- Inelastic Collisions: Linear momentum is conserved, but kinetic energy is partially or completely converted into internal energy, heat, or deformation. In a perfectly inelastic collision, the colliding bodies stick together and move with a shared final velocity.
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:
- Set
frictionAir: 0so the body does not lose velocity over time. - Set
friction: 0andfrictionStatic: 0to prevent kinetic energy loss during contact.
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.