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.