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:

  1. Matter.Resolver.solvePosition(pairs, timeScale): Separates overlapping bodies to prevent interpenetration.
  2. 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.

  1. Set body.isSensor = true. The engine will continue to run broadphase and narrowphase collision detection for the body, but Matter.Resolver will skip it completely.
  2. Listen to the collisionActive or collisionStart events 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