Pool Table Cushion Rebound Damping in Matter.js

Simulating realistic cushion rail rebound damping on a pool table in Matter.js requires tuning specific physical properties and applying collision-based velocity adjustments. While standard rigid-body mechanics handle basic elastic collisions, real billiard cushions exhibit non-linear energy loss, angle deflection, and speed-dependent damping. This guide explains how to configure native Matter.js properties—such as restitution and friction—and implement post-collision velocity dampening to achieve authentic rail behavior.

1. Configure Native Body Properties

By default, Matter.js resolves restitution (bounciness) during a collision by taking the maximum restitution value between the two colliding bodies: Math.max(bodyA.restitution, bodyB.restitution).

To establish base damping, set the restitution of the cushion rails and the cue ball between 0.7 and 0.85. You should also define friction to handle tangential energy loss when balls strike the rails at an angle.

const railOptions = {
  isStatic: true,
  restitution: 0.8,      // Controls the bounce return
  friction: 0.2,         // Tangential deceleration along the rail
  frictionStatic: 0.1
};

const ballOptions = {
  restitution: 0.8,
  friction: 0.05,
  frictionAir: 0.01      // Rolling friction across cloth
};

2. Implement Non-Linear Velocity Damping

Real rubber rails absorb significantly more energy during high-speed impacts than during low-speed impacts. Because native restitution applies a constant coefficient regardless of impact force, you can use the collisionStart event to calculate and damp velocity dynamically based on incoming speed.

Matter.Events.on(engine, 'collisionStart', (event) => {
  event.pairs.forEach((pair) => {
    const { bodyA, bodyB } = pair;

    // Identify if the collision is between a ball and a rail
    const isBallRailCollision = 
      (bodyA.label === 'ball' && bodyB.label === 'rail') ||
      (bodyA.label === 'rail' && bodyB.label === 'ball');

    if (isBallRailCollision) {
      const ball = bodyA.label === 'ball' ? bodyA : bodyB;
      const speed = Matter.Vector.magnitude(ball.velocity);

      // Apply non-linear damping factor (harder hits lose more relative speed)
      const dampingFactor = Math.max(0.65, 0.9 - speed * 0.015);

      Matter.Body.setVelocity(ball, {
        x: ball.velocity.x * dampingFactor,
        y: ball.velocity.y * dampingFactor
      });
    }
  });
});

3. Handle Cushion Bevels and Angle Deflection

Pool cushions are angled inward (beveled) to prevent balls from jumping off the bed. In 2D space, you can model this rebound angle shift by decomposing the velocity into normal and tangential components relative to the rail face. Reducing the perpendicular velocity component more aggressively than the parallel component reproduces the "rail-grabbing" effect observed in real billiards.

Matter.Events.on(engine, 'collisionEnd', (event) => {
  event.pairs.forEach((pair) => {
    const ball = pair.bodyA.label === 'ball' ? pair.bodyA : (pair.bodyB.label === 'ball' ? pair.bodyB : null);
    const rail = pair.bodyA.label === 'rail' ? pair.bodyA : (pair.bodyB.label === 'rail' ? pair.bodyB : null);

    if (ball && rail) {
      // Additional micro-adjustment to normal vector to counter excessive rebound
      Matter.Body.setVelocity(ball, {
        x: ball.velocity.x * 0.95,
        y: ball.velocity.y * 0.95
      });
    }
  });
});

Combining static restitution settings for low-speed accuracy with dynamic collision events for high-speed dissipation creates a consistent, realistic pool rail rebound inside Matter.js.