How to Build Loop-the-Loops in Matter.js

This guide explains how to construct a realistic marble run loop-the-loop in Matter.js that naturally requires sufficient kinetic energy to complete. By combining segmented geometry, continuous track alignment, 2D collision filtering, and tuned physics parameters, you can ensure that marbles with inadequate entry speed fall from the track's apex under gravity while fast-moving marbles clear the loop.

1. Understanding the Loop Physics

In classical mechanics, a body traversing an inside vertical loop of radius \(R\) under downward gravity \(g\) must maintain contact at the apex. The minimum velocity at the very top must satisfy \(v_{top} \ge \sqrt{gR}\) to ensure centripetal acceleration balances or exceeds gravity. Accounting for potential energy loss when climbing the loop, the minimum entry velocity at the base must be:

\[v_{entry} \ge \sqrt{5gR}\]

Because Matter.js simulates Newtonian rigid-body physics, you do not need to fake this behavior with custom velocity triggers. If the track is constructed as a solid physical surface and gravity is enabled, the marble will naturally drop mid-loop if its kinetic energy is insufficient to reach that threshold.

2. Overcoming the 2D Crossover Problem

Because Matter.js operates in a two-dimensional plane, a true loop-the-loop presents a spatial conflict: the entry track and the exit track cross over each other. If left unchecked, the entering marble will collide with the exit ramp.

To solve this, use Matter.js collision filters (collisionFilter.category and collisionFilter.mask):

  1. Approach Track: Assign Category A.
  2. First Half of the Loop: Assign Category A.
  3. Loop Apex: Place a sensor body (a body with isSensor: true) at the top of the loop. When the marble collides with this sensor, switch the marble’s collision mask from Category A to Category B.
  4. Second Half of the Loop and Exit Track: Assign Category B.

This allows the exit track to physically overlap the entry track without colliding with the incoming marble.

3. Generating the Loop Geometry

Matter.js does not have a native concave curve primitive. You must assemble the loop track out of a series of small, static rectangular bodies arranged in a circle.

const createLoop = (centerX, centerY, radius, segments = 36, thickness = 10) => {
  const angleStep = (Math.PI * 2) / segments;
  const loopParts = [];

  for (let i = 0; i < segments; i++) {
    const angle = i * angleStep;
    // Calculate position for each segment
    const x = centerX + Math.cos(angle) * radius;
    const y = centerY + Math.sin(angle) * radius;

    const segment = Matter.Bodies.rectangle(x, y, (2 * Math.PI * radius) / segments + 2, thickness, {
      isStatic: true,
      angle: angle + Math.PI / 2, // Tangent to circle
      friction: 0.001,
      restitution: 0.1,
      collisionFilter: {
        category: i < segments / 2 ? 0x0002 : 0x0004 // Split into entry/exit layers
      }
    });

    loopParts.push(segment);
  }

  return loopParts;
};

Ensure adjacent segments overlap slightly to prevent the marble from catching on microscopic seams between rectangles.

4. Tuning Physical Properties

For the energy requirement to feel consistent, track and marble friction must be controlled:

5. Preventing Tunnelling (High-Velocity Stability)

A marble entering the loop at high speed can experience tunneling, where it passes through track segments between physics simulation steps. To prevent this:

  1. Sub-stepping: Increase solver fidelity on your engine:
    engine.positionIterations = 10;
    engine.velocityIterations = 10;
  2. Segment Thickness: Ensure segment thickness is at least as thick as the marble's radius.
  3. Fixed Timesteps: Run the Matter.js engine with a fixed delta time (Matter.Engine.update(engine, 1000 / 60)) rather than variable delta time to keep trajectory integration stable.

6. Testing the Energy Threshold

To test the installation, release marbles from varying ramp heights above the loop entrance. The theoretical minimum drop height \(h\) from rest (neglecting rotational inertia and friction) is \(2.5R\) above the base of the loop. In Matter.js, due to rolling resistance and polygon segment collisions, the real required drop height will be roughly \(2.7R\) to \(3.0R\). Any release below this critical threshold will cause the marble to stall, detach from the upper wall, and fall downward.