Calculate Impact Point Between Bodies in Matter.js
This article explains how to determine the precise point of impact between colliding rigid bodies using the Matter.js 2D physics engine. Matter.js detects collisions using the Separating Axis Theorem (SAT) and generates contact manifolds containing the vertices where bodies intersect. By capturing collision events, extracting support points, and calculating their geometric midpoint or using raycasting for continuous collision detection, you can accurately pinpoint where two bodies collide.
1. Listen for Collision Events
Matter.js provides collision lifecycle events through
Matter.Events. The collisionStart event fires
during the initial frame in which two bodies intersect, making it the
ideal place to detect an impact.
Matter.Events.on(engine, 'collisionStart', (event) => {
const pairs = event.pairs;
for (let i = 0; i < pairs.length; i++) {
const pair = pairs[i];
const bodyA = pair.bodyA;
const bodyB = pair.bodyB;
const impactPoint = getImpactPoint(pair);
console.log('Impact coordinates:', impactPoint);
}
});2. Extract Collision Support Points
Each collision pair contains a collision object holding
geometric information about the overlap. The
pair.collision.supports array contains the specific
vertices (support points) from the bodies that are actively involved in
the penetration.
Depending on the collision geometry, supports typically
contains one or two vertices:
- One point: Corner-to-edge or corner-to-corner impact.
- Two points: Edge-to-edge impact.
3. Compute the Single Impact Coordinate
When two support points exist, the exact point of impact is commonly resolved as the midpoint between them. If only one support point exists, that point represents the impact location directly.
function getImpactPoint(pair) {
const supports = pair.collision.supports;
if (!supports || supports.length === 0) {
// Fallback: Midpoint between body centroids if supports are unavailable
return {
x: (pair.bodyA.position.x + pair.bodyB.position.x) / 2,
y: (pair.bodyA.position.y + pair.bodyB.position.y) / 2
};
}
if (supports.length === 1) {
return {
x: supports[0].x,
y: supports[0].y
};
}
// Midpoint between the two support points
return {
x: (supports[0].x + supports[1].x) / 2,
y: (supports[0].y + supports[1].y) / 2
};
}4. Account for Penetration Depth (High Precision)
Matter.js uses discrete collision detection, meaning bodies overlap
slightly before the collision resolution separates them. If you need the
point on the surface of bodyA rather than inside the
overlapping region, offset the support point by half the collision
penetration depth along the collision normal:
function getExactSurfaceImpactPoint(pair) {
const collision = pair.collision;
const supports = collision.supports;
const normal = collision.normal; // Vector pointing from bodyA to bodyB
const depth = collision.depth;
let baseX, baseY;
if (supports.length > 1) {
baseX = (supports[0].x + supports[1].x) / 2;
baseY = (supports[0].y + supports[1].y) / 2;
} else {
baseX = supports[0].x;
baseY = supports[0].y;
}
// Adjust position along the normal to find the exact boundary
return {
x: baseX - normal.x * (depth * 0.5),
y: baseY - normal.y * (depth * 0.5)
};
}5. Alternative: Raycasting for Fast-Moving Bodies
For fast-moving objects subject to tunneling (passing through other
bodies between frames), discrete contact supports may reflect
significant overlap. You can determine the pre-impact surface contact
using Matter.Query.ray:
// Cast a ray from the previous position to the current position of bodyA
const rayHits = Matter.Query.ray([bodyB], bodyA.positionPrev, bodyA.position);
if (rayHits.length > 0) {
const firstHit = rayHits[0];
const exactImpact = {
x: firstHit.point.x,
y: firstHit.point.y
};
}Using pair.collision.supports handles standard collision
responses, while combining it with penetration depth correction or
raycasting ensures sub-frame accuracy for particle spawning, visual
effects, and gameplay mechanics.