How Matter.js Groups Collision Pairs in Events
This article explains how the Matter.js 2D physics engine identifies,
tracks, and groups colliding bodies into collision pairs during its
simulation lifecycle. You will learn the mechanics behind the broadphase
and narrowphase collision checks, how unique pair identifiers are
generated, how the internal Pairs module maintains state
across frames, and how these pairs are structured in the engine's
collision event payloads.
The Collision Lifecycle: Broadphase to Narrowphase
Before Matter.js can trigger collision events, it processes bodies
through two main detection phases during Engine.update:
- Broadphase Detection: The engine reduces
computational overhead by running a broadphase algorithm—typically using
a bounding box tree (
Matter.DetectororMatter.Grid)—to find bodies whose axis-aligned bounding boxes (AABB) overlap. This generates a list of potential collision pairs. - Narrowphase Resolution: For each candidate pair,
the engine uses the Separating Axis Theorem (SAT) via
Matter.SAT.collidesto verify whether the actual geometric vertices intersect. If an overlap is confirmed, a detailed collision record containing contact points, penetration depth, and collision normals is generated.
Unique Pair IDs and the Pairs Table
To ensure consistency and prevent duplicate computations, Matter.js
tracks contacts using an internal structure managed by the
Matter.Pairs module.
Every unique interaction between two bodies is assigned a deterministic string ID using their numerical identifiers:
pairId = bodyA.id < bodyB.id ? 'A' + bodyA.id + 'B' + bodyB.id : 'A' + bodyB.id + 'B' + bodyA.id;This ensures that regardless of which body is processed first, the
pair key remains identical. Matter.js stores all active pairs in a
dictionary lookup table (engine.pairs.table). If a
collision between two specific bodies persists across multiple frames,
the engine reuses the existing Pair instance rather than
instantiating a new object.
State Assignment and Event Categorization
During each physics tick, the engine updates the status of all pairs and segments them into three event lists based on their state transitions:
collisionStart: Contains pairs that were detected as overlapping in the current frame but did not exist in the activePairstable in the previous frame.collisionActive: Contains pairs that were colliding in the previous frame and continue to overlap in the current frame.collisionEnd: Contains pairs that were actively colliding in the previous frame but are no longer touching in the current frame.
The engine uses internal tracking properties such as
pair.isActive and collision timestamps to manage these
transitions. Pairs that have ended are removed from the active table
after the collisionEnd cycle completes.
Structure of the Event Object
When you subscribe to an engine event (such as
Events.on(engine, 'collisionStart', callback)), Matter.js
passes an event object containing an array of all relevant pairs for
that tick:
{
name: "collisionStart",
source: engine,
pairs: [
{
id: "A1B2",
bodyA: body1,
bodyB: body2,
collision: {
depth: 4.2,
normal: { x: 0, y: 1 },
supports: [...],
// Additional geometric data
},
contacts: [...],
isActive: true,
timeCreated: 1200,
timeUpdated: 1216
}
]
}Through this architecture, Matter.js groups all interacting bodies
into single-frame batches inside event.pairs, allowing
developers to iterate over collisions efficiently and retrieve complete
contact dynamics for every interacting pair.