Override Default Collision Resolver in Matter.js
Overriding the default collision resolver in Matter.js allows
developers to implement custom physics responses, such as non-standard
restitution, customized impulse distribution, or specialized kinematic
behaviors. This article explains how to directly intercept and replace
the internal methods of Matter.Resolver—specifically
solvePosition and solveVelocity—as well as how
to use collision events with sensor bodies for a non-destructive
alternative to custom resolution.
Understanding the Matter.js Resolver
During each simulation step (Engine.update), Matter.js
detects collisions via Matter.Detector and generates pairs.
It then executes the collision resolution phase through
Matter.Resolver, which contains two core static
functions:
Matter.Resolver.solvePosition(pairs, timeScale): Separates overlapping bodies to prevent interpenetration.Matter.Resolver.solveVelocity(pairs, timeScale): Adjusts body velocities and angular velocities based on mass, restitution, and friction.
To globally change how Matter.js resolves physical impacts, you must patch these functions.
Method 1:
Monkey-Patching Matter.Resolver
The most direct way to override the collision resolver is to
overwrite Matter.Resolver.solvePosition and
Matter.Resolver.solveVelocity before initializing or
running your engine.
import Matter from 'matter-js';
// Cache the original implementations if fallback is needed
const originalSolvePosition = Matter.Resolver.solvePosition;
const originalSolveVelocity = Matter.Resolver.solveVelocity;
// 1. Override Position Resolution (Penetration Correction)
Matter.Resolver.solvePosition = function(pairs, timeScale) {
for (let i = 0; i < pairs.length; i++) {
const pair = pairs[i];
// Skip inactive or sensor collisions
if (!pair.isActive || pair.isSensor) continue;
const collision = pair.collision;
const bodyA = collision.bodyA;
const bodyB = collision.bodyB;
// Custom position separation logic:
// Move bodies apart along collision.normal by collision.depth
const separation = collision.depth * 0.5;
if (!bodyA.isStatic) {
bodyA.position.x -= collision.normal.x * separation;
bodyA.position.y -= collision.normal.y * separation;
}
if (!bodyB.isStatic) {
bodyB.position.x += collision.normal.x * separation;
bodyB.position.y += collision.normal.y * separation;
}
}
};
// 2. Override Velocity Resolution (Impulse and Momentum)
Matter.Resolver.solveVelocity = function(pairs, timeScale) {
for (let i = 0; i < pairs.length; i++) {
const pair = pairs[i];
if (!pair.isActive || pair.isSensor) continue;
const bodyA = pair.bodyA;
const bodyB = pair.bodyB;
const normal = pair.collision.normal;
// Calculate relative velocity
const rvx = bodyB.velocity.x - bodyA.velocity.x;
const rvy = bodyB.velocity.y - bodyA.velocity.y;
const velAlongNormal = rvx * normal.x + rvy * normal.y;
// Do not resolve if velocities are separating
if (velAlongNormal > 0) continue;
// Custom impulse calculation (e.g., perfectly elastic bounce)
const restitution = 1.0;
const impulseMagnitude = -(1 + restitution) * velAlongNormal / (bodyA.inverseMass + bodyB.inverseMass);
const impulseX = impulseMagnitude * normal.x;
const impulseY = impulseMagnitude * normal.y;
if (!bodyA.isStatic) {
Matter.Body.setVelocity(bodyA, {
x: bodyA.velocity.x - bodyA.inverseMass * impulseX,
y: bodyA.velocity.y - bodyA.inverseMass * impulseY
});
}
if (!bodyB.isStatic) {
Matter.Body.setVelocity(bodyB, {
x: bodyB.velocity.x + bodyB.inverseMass * impulseX,
y: bodyB.velocity.y + bodyB.inverseMass * impulseY
});
}
}
};Method 2: Disabling Resolution per Body via Sensors
If the goal is to override resolution only for specific interactions rather than globally altering the entire physics simulation, convert the bodies into sensors and compute the forces manually.
- Set
body.isSensor = true. The engine will continue to run broadphase and narrowphase collision detection for the body, butMatter.Resolverwill skip it completely. - Listen to the
collisionActiveorcollisionStartevents on the engine to execute custom resolution math.
// Configure a body as a sensor to bypass Matter.Resolver
const customBody = Matter.Bodies.circle(100, 100, 20, {
isSensor: true
});
// Apply custom resolution manually inside the collision event
Matter.Events.on(engine, 'collisionActive', (event) => {
event.pairs.forEach((pair) => {
if (pair.bodyA === customBody || pair.bodyB === customBody) {
const normal = pair.collision.normal;
const depth = pair.collision.depth;
// Apply manual positioning or forces
Matter.Body.applyForce(customBody, customBody.position, {
x: -normal.x * depth * 0.05,
y: -normal.y * depth * 0.05
});
}
});
});Summary of Differences
- Monkey-Patching
Matter.Resolver: Best when you need global control over the underlying physics equations, contact sleeping behavior, or velocity constraint solving across all bodies. - Sensor Collision Handling: Best when you want standard Matter.js dynamics for the majority of the world while applying customized collision behavior to specific game objects.