Inverted Pendulum Simulation in Matter.js

This article explains how to build and stabilize an inverted pendulum mounted on a moving cart using the Matter.js 2D physics engine. You will learn how to set up the rigid bodies for the track, cart, and pole, join them using a revolute constraint, and implement a Proportional-Derivative (PD) control loop to apply balancing forces to the cart in real time.

1. Setting Up the Matter.js Environment

To begin, initialize the fundamental Matter.js modules: the engine, world, renderer, and runner.

const { Engine, Render, Runner, Bodies, Composite, Constraint, Body, Events } = Matter;

const engine = Engine.create();
const world = engine.world;

const render = Render.create({
    element: document.body,
    engine: engine,
    options: {
        width: 800,
        height: 600,
        wireframes: false
    }
});

Render.run(render);
const runner = Runner.create();
Runner.run(runner, engine);

2. Creating the Cart, Pole, and Track

The system consists of three main bodies:

  1. Track: A static horizontal surface for the cart to slide on.
  2. Cart: A dynamic rectangular body with low friction.
  3. Pole: A tall, thin dynamic rectangle that balances atop the cart.
// Track
const ground = Bodies.rectangle(400, 500, 800, 20, { 
    isStatic: true, 
    friction: 0.001 
});

// Cart
const cart = Bodies.rectangle(400, 470, 80, 40, {
    mass: 5,
    friction: 0.001,
    frictionAir: 0.01
});

// Pole
const poleLength = 150;
const pole = Bodies.rectangle(400, 470 - poleLength / 2, 10, poleLength, {
    mass: 1,
    frictionAir: 0.001,
    collisionFilter: { group: -1 } // Prevent collision between cart and pole
});
cart.collisionFilter = { group: -1 };

Composite.add(world, [ground, cart, pole]);

3. Joining the Cart and Pole

Use a Constraint with zero length to act as a revolute hinge connecting the center of the cart to the bottom end of the pole.

const pivot = Constraint.create({
    bodyA: cart,
    pointA: { x: 0, y: -10 },
    bodyB: pole,
    pointB: { x: 0, y: poleLength / 2 },
    stiffness: 1,
    length: 0
});

Composite.add(world, pivot);

4. Implementing the Balancing Controller

To keep the pendulum upright, apply horizontal forces to the cart based on the angle and angular velocity of the pole. A PD controller calculates the appropriate correction force before each physics update.

// Controller Gains
const Kp = 0.15;  // Proportional gain (angle error)
const Kd = 0.05;  // Derivative gain (angular velocity error)

Events.on(engine, 'beforeUpdate', () => {
    // Normalize pole angle to be relative to the upright position (0 radians)
    let angle = pole.angle % (2 * Math.PI);
    if (angle > Math.PI) angle -= 2 * Math.PI;
    if (angle < -Math.PI) angle += 2 * Math.PI;

    const angularVelocity = pole.angularVelocity;

    // PD Control Law
    const controlForce = (Kp * angle) + (Kd * angularVelocity);

    // Apply horizontal force at the center of the cart
    Body.applyForce(cart, cart.position, {
        x: controlForce,
        y: 0
    });
});

5. Fine-Tuning the System