Realistic Billiards Pocketing in Matter.js
Simulating realistic ball-pocketing mechanics on a digital billiards table requires more than simply deleting a ball when it touches a pocket. Because Matter.js is a rigid-body 2D physics engine, it does not naturally account for the 3D drop, slate beveling, or pocket shelf dynamics of a real pool table. This guide demonstrates how to achieve realistic pocketing by combining sensor bodies, localized gravitational pull, visual scaling, and collision filtering to mimic depth and momentum loss.
1. Define Pockets as Sensors
In Matter.js, rigid cushions should deflect the ball, but the pocket
opening itself needs to detect balls without generating a physical
bounce. Create circular bodies for each pocket with
isSensor: true.
const pocketRadius = 25; // Adjusted to table scale
const pocket = Matter.Bodies.circle(x, y, pocketRadius, {
isStatic: true,
isSensor: true,
label: 'pocket'
});
Matter.Composite.add(engine.world, pocket);Place rounded cushion corner vertices flanking each pocket to simulate the pocket "jaws." A ball that strikes the corner should rebound, while a ball passing between them enters the sensor zone.
2. Track Ball-to-Pocket Interactions
Use the beforeUpdate or collisionActive
events to evaluate balls entering the pocket sensor. Simply removing the
ball immediately looks abrupt; instead, flag the ball as "sinking" and
store the target pocket's coordinates.
Matter.Events.on(engine, 'collisionStart', (event) => {
event.pairs.forEach((pair) => {
const { bodyA, bodyB } = pair;
if (bodyA.label === 'pocket' && bodyB.label === 'ball') {
startPocketing(bodyB, bodyA);
} else if (bodyB.label === 'pocket' && bodyA.label === 'ball') {
startPocketing(bodyA, bodyB);
}
});
});3. Apply a Radial Inward Force
To mimic the sloped bevel of the slate and the drop into the net, apply a continuous force directing the ball toward the pocket center during each engine update.
function applyPocketGravity(ball, pocket) {
const dx = pocket.position.x - ball.position.x;
const dy = pocket.position.y - ball.position.y;
const distance = Math.hypot(dx, dy);
if (distance > 0) {
const forceMagnitude = 0.0005 * ball.mass;
Matter.Body.applyForce(ball, ball.position, {
x: (dx / distance) * forceMagnitude,
y: (dy / distance) * forceMagnitude
});
}
}4. Dampen Velocity and Disable Table Collisions
Once a ball enters the pocket threshold, prevent it from bouncing back out onto the table due to unwanted collisions with other active balls:
- Modify Collision Filtering: Set the ball's
collisionFilter.group = -1or change itscollisionFilter.mask = 0so it no longer collides with rails or incoming balls. - Apply Friction/Damping: Artificially increase
linear damping (
ball.frictionAir = 0.1) to represent energy absorption by the pocket leather and netting.
5. Simulate 3D Depth via Visual Scaling
Because Matter.js operates in 2D, simulate vertical descent by scaling the ball down as it approaches the pocket center.
In your custom render loop or update step:
function updateSinkingBall(ball, pocket) {
const dx = pocket.position.x - ball.position.x;
const dy = pocket.position.y - ball.position.y;
const distance = Math.hypot(dx, dy);
// Shrink the ball as it gets closer to the center
const progress = Math.max(0, distance / pocketRadius);
const targetScale = 0.5 + 0.5 * progress;
// Render scale adjustment (custom render property)
ball.renderScale = targetScale;
// Remove completely when near the center or moving sufficiently slow
if (distance < 5 || ball.speed < 0.1) {
Matter.Composite.remove(engine.world, ball);
}
}6. Rim Roll and Pocket Rejection
To capture true physical realism, assess the ball's velocity upon pocket entry. If a ball hits the pocket jaws at an acute angle with high velocity, it should "rattle" or lip out:
- If velocity exceeds a specific threshold, reduce the gravitational attraction and allow cushion rebound bodies placed just inside the pocket mouth to deflect the ball back onto the table.
- Only trigger the full sinking routine when the ball's center of mass crosses deep enough past the pocket's outer threshold.