Visualizing Contact Forces in Matter.js

This guide explains how to extract and visually render normal and tangential contact force vectors at active collision points in Matter.js. By tapping into collision events, reading contact manifold data and impulses, and utilizing the 2D canvas context after engine updates, you can draw real-time vector arrows representing normal reaction forces and tangential friction forces directly at collision interfaces.

Accessing Collision Data in Matter.js

Matter.js tracks collisions through the pairs managed by its internal collision detector. During an active collision, a pair object contains the collision normal and a list of active contact points (activeContacts). Each contact point records position coordinates as well as accumulated impulses: normalImpulse and tangentImpulse.

To read these values continuously without interfering with the physics solver, attach a listener to the afterRender event of the Render module. This grants access to the HTML5 Canvas 2D context right after bodies are drawn.

Calculating Force Vectors

The collision normal vector (\(\vec{n}\)) points outward from body A to body B. The tangential vector (\(\vec{t}\)) is orthogonal to the normal vector.

  1. Normal Force Vector: \[\vec{F}_{\text{normal}} = \vec{n} \times \text{normalImpulse} \times \text{scale}\]
  2. Tangential (Friction) Force Vector: \[\vec{t} = (-n_y, n_x)\] \[\vec{F}_{\text{tangent}} = \vec{t} \times \text{tangentImpulse} \times \text{scale}\]

Because impulse values in Matter.js represent momentum change over a single engine update step, multiply them by a visual scale factor to make them clearly visible on the canvas.

Implementation Example

Below is the complete implementation hooking into afterRender to draw normal forces in red and tangential forces in blue.

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

// 1. Create engine and renderer
const engine = Engine.create();
const render = Render.create({
    element: document.body,
    engine: engine,
    options: {
        width: 800,
        height: 600,
        wireframes: false
    }
});

Render.run(render);
Runner.run(Runner.create(), engine);

// 2. Helper function to draw a line vector
function drawVector(ctx, from, vector, color) {
    ctx.save();
    ctx.beginPath();
    ctx.moveTo(from.x, from.y);
    ctx.lineTo(from.x + vector.x, from.y + vector.y);
    ctx.strokeStyle = color;
    ctx.lineWidth = 2;
    ctx.stroke();
    
    // Draw contact point dot
    ctx.fillStyle = color;
    ctx.beginPath();
    ctx.arc(from.x, from.y, 3, 0, 2 * Math.PI);
    ctx.fill();
    ctx.restore();
}

// 3. Hook into render loop to draw forces
Events.on(render, 'afterRender', () => {
    const ctx = render.context;
    const pairs = engine.pairs.list;
    const forceScale = 15; // Scale multiplier for rendering visibility

    for (let i = 0; i < pairs.length; i++) {
        const pair = pairs[i];

        if (!pair.isActive) continue;

        const normal = pair.collision.normal;
        const tangent = Vector.perp(normal);

        // Iterate through all active contact points for the pair
        for (let j = 0; j < pair.activeContacts.length; j++) {
            const contact = pair.activeContacts[j];
            const vertex = contact.vertex;

            // Retrieve impulses (use fallback values if impulses resolve to 0)
            const normalMag = (contact.normalImpulse || 0) * forceScale;
            const tangentMag = (contact.tangentImpulse || 0) * forceScale;

            // Compute vector components
            const normalForce = Vector.mult(normal, normalMag);
            const tangentForce = Vector.mult(tangent, tangentMag);

            // Render Normal Force (Red)
            if (normalMag > 0.01) {
                drawVector(ctx, vertex, normalForce, '#ff3333');
            }

            // Render Tangential/Friction Force (Blue)
            if (Math.abs(tangentMag) > 0.01) {
                drawVector(ctx, vertex, tangentForce, '#3388ff');
            }
        }
    }
});

Interpreting the Visuals