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:
- Track: A static horizontal surface for the cart to slide on.
- Cart: A dynamic rectangular body with low friction.
- 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
- Mass Ratio: The cart should generally be heavier than the pole (e.g., a 5:1 ratio) to ensure cart acceleration can counteract pendulum inertia.
- Gains: If the cart oscillates wildly, decrease
Kpor increaseKd. If the pole falls without the cart reacting quickly enough, increaseKp. - Centering: To prevent the cart from drifting off-screen, add a secondary term to the control force proportional to the cart's displacement from the screen center.