Cue Ball Deflection Angles in Matter.js

This article explains how to calculate the deflection angle of a cue ball after it collides with another ball using the Matter.js 2D physics engine. You will learn the underlying vector mathematics of 2D elastic collisions, how to implement predictive trajectory vectors using the Matter.Vector module for aiming lines, and how to read real-time post-collision velocities directly from Matter.js collision events.

Physics Principles of 2D Ball Collisions

In a standard frictionless billiard collision with balls of identical mass and restitution (bounciness set to 1), two vectors dictate post-collision motion:

  1. The Normal Vector (\(\vec{n}\)): The line passing through the centers of both balls at the point of contact. The target ball (object ball) always moves along this normal line.
  2. The Tangent Vector (\(\vec{t}\)): Perpendicular to the normal line. The cue ball deflects along this tangent line.

Assuming an elastic collision without spin (draw, follow, or sidespin), the cue ball's post-collision velocity is simply its pre-collision velocity projected onto the tangent vector, resulting in a 90-degree separation angle between the two balls.

Predictive Calculation (Aiming Line Math)

To predict where the cue ball will deflect before a shot is taken, calculate the normal and tangential projections using Matter.Vector:

const { Vector } = Matter;

function calculateDeflection(cueBallPos, cueBallVelocity, targetBallPos) {
    // 1. Calculate the collision normal vector (from cue ball to target ball)
    const normal = Vector.normalise(Vector.sub(targetBallPos, cueBallPos));

    // 2. Calculate the unit tangent vector (perpendicular to normal)
    // A 90-degree clockwise perpendicular to (x, y) is (-y, x)
    const tangent = Vector.create(-normal.y, normal.x);

    // 3. Project the cue ball's initial velocity onto the normal and tangent vectors
    const velocityNormal = Vector.dot(cueBallVelocity, normal);
    const velocityTangent = Vector.dot(cueBallVelocity, tangent);

    // 4. In an elastic collision of equal masses:
    // - Target ball inherits the normal velocity
    // - Cue ball retains the tangential velocity
    const targetBallDeflectedVelocity = Vector.mult(normal, velocityNormal);
    const cueBallDeflectedVelocity = Vector.mult(tangent, velocityTangent);

    // 5. Calculate the deflection angle in radians
    const cueBallAngle = Math.atan2(cueBallDeflectedVelocity.y, cueBallDeflectedVelocity.x);
    const targetBallAngle = Math.atan2(targetBallDeflectedVelocity.y, targetBallDeflectedVelocity.x);

    return {
        cueBallAngle,
        cueBallVelocity: cueBallDeflectedVelocity,
        targetBallAngle,
        targetBallVelocity: targetBallDeflectedVelocity
    };
}

Extracting Angles from Real-Time Matter.js Events

If the collision has already occurred in the simulation and you need to determine the resulting angle, read the velocities via the collisionEnd event:

Matter.Events.on(engine, 'collisionEnd', (event) => {
    const pairs = event.pairs;

    for (let i = 0; i < pairs.length; i++) {
        const { bodyA, bodyB } = pairs[i];

        // Identify which body is the cue ball
        const cueBall = bodyA.label === 'cueBall' ? bodyA : (bodyB.label === 'cueBall' ? bodyB : null);

        if (cueBall) {
            // Angle of cue ball deflection in radians
            const deflectionAngle = Math.atan2(cueBall.velocity.y, cueBall.velocity.x);

            // Convert to degrees
            const degrees = deflectionAngle * (180 / Math.PI);
            console.log(`Cue ball deflection: ${degrees}°`);
        }
    }
});

Key Engine Configurations for Accurate Pool Physics

To ensure Matter.js calculates deflection angles cleanly without unnatural sticking or loss of velocity: