Why Fast Objects Pass Through Walls in Matter.js

Fast-moving bodies sometimes pass straight through obstacles in Matter.js due to a physics engine phenomenon known as "tunneling." This article explains the mechanics of discrete collision detection that cause high-speed bodies to miss collisions, along with practical, actionable methods to resolve the issue in your physics simulations.

The Root Cause: Discrete Collision Detection

Matter.js relies on discrete collision detection. Instead of continuously tracking an object's trajectory through space and time, the engine samples positions at discrete intervals (time steps). During each step, the engine updates an object's position based on its velocity and checks whether any bodies currently overlap.

When an object moves at a high velocity, the distance it travels in a single time step can exceed the thickness of the obstacle it is approaching. In one frame, the body is completely in front of the barrier; in the very next frame, it is already completely behind it. Because the body was never intersecting the barrier at the exact instant an engine step was calculated, Matter.js registers no collision, and the body passes cleanly through.

Increase Engine Sub-stepping

The most direct way to mitigate tunneling is to increase the frequency of physics updates. By reducing the duration of each time step, objects move smaller distances between checks:

Increase Obstacle Thickness

Because tunneling occurs when an object's displacement per step exceeds an obstacle's depth, increasing the thickness of static walls is an easy and performant solution:

Cap Maximum Velocity

If sub-stepping introduces too much CPU overhead, clamp the maximum speed of moving bodies. You can implement a pre-update listener to limit velocity:

Matter.Events.on(engine, 'beforeUpdate', () => {
    const maxSpeed = 20;
    if (body.speed > maxSpeed) {
        Matter.Body.setSpeed(body, maxSpeed);
    }
});

Ensure that maxSpeed * (deltaTime / 1000) is strictly less than the minimum thickness of any collider in the scene.

Implement Raycasting for Swept Detection

For critical projectiles like bullets, discrete checks are often insufficient. You can implement manual Continuous Collision Detection (CCD) using raycasting:

  1. Store the body's previous position before each step.
  2. In the beforeUpdate or custom update cycle, cast a ray from the previous position to the predicted next position using Matter.Query.ray(bodies, startPoint, endPoint).
  3. If an intersection is detected along that vector, manually trigger the collision response or position the body directly at the impact point before the regular solver runs.