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:

  1. Broadphase Detection: The engine reduces computational overhead by running a broadphase algorithm—typically using a bounding box tree (Matter.Detector or Matter.Grid)—to find bodies whose axis-aligned bounding boxes (AABB) overlap. This generates a list of potential collision pairs.
  2. Narrowphase Resolution: For each candidate pair, the engine uses the Separating Axis Theorem (SAT) via Matter.SAT.collides to 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:

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.