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):
- Approach Track: Assign Category A.
- First Half of the Loop: Assign Category A.
- 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. - 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:
- Track and Marble Friction: Set
friction: 0.001andfrictionStatic: 0on both the track segments and the marble. High friction will convert critical kinetic energy into rotational or dissipated energy prematurely. - Air Resistance: Set
frictionAir: 0.001or lower on the marble to prevent artificial deceleration. - Gravity: Keep
engine.gravity.yat the default1(or scale it deliberately). Remember that doubling gravity requires an entry velocity scaled by \(\sqrt{2}\) to clear the same loop.
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:
- Sub-stepping: Increase solver fidelity on your
engine:
engine.positionIterations = 10; engine.velocityIterations = 10; - Segment Thickness: Ensure segment thickness is at least as thick as the marble's radius.
- 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.