Simulating a Quadcopter Drone in Matter.js
This article explains how to build a stable quadcopter drone simulation in Matter.js featuring multi-point motor thrust and active gyroscopic self-leveling. You will learn how to construct the drone's physical body, calculate local-to-world force vectors representing individual motor outputs, and implement a proportional-derivative (PD) controller that mimics an onboard flight controller to keep the craft balanced in a 2D physics environment.
1. Representing the Drone Body
In a 2D rigid-body engine like Matter.js, a quadcopter is modeled along its cross-section. The four physical rotors are represented as two thrust axes—left and right—where each side accounts for a pair of motors (front-left/rear-left and front-right/rear-right).
Create the drone chassis as a single rectangular body with an explicit mass and moment of inertia to ensure predictable rotational physics:
const { Engine, Render, Runner, Bodies, Composite, Body, Vector } = Matter;
const droneWidth = 120;
const droneHeight = 15;
const drone = Bodies.rectangle(400, 300, droneWidth, droneHeight, {
mass: 1.5,
inertia: 1500, // Explicit inertia provides resistance to erratic spinning
frictionAir: 0.02
});
Composite.add(engine.world, drone);2. Calculating Offset Thrust Points
To induce rotation, thrust must be applied at specific offsets along the drone's lateral axis rather than at the center of mass. Because the drone rotates, these offset points must be translated from local body coordinates to world coordinates on every frame.
The local offsets for the left and right motor mounts are positioned toward the ends of the chassis:
const motorOffset = droneWidth / 2 - 5; // Distance from center to motor mount
function getMotorWorldPositions(body) {
const angle = body.angle;
const cos = Math.cos(angle);
const sin = Math.sin(angle);
// Left motor position in world space
const leftPos = {
x: body.position.x - motorOffset * cos,
y: body.position.y - motorOffset * sin
};
// Right motor position in world space
const rightPos = {
x: body.position.x + motorOffset * cos,
y: body.position.y + motorOffset * sin
};
return { leftPos, rightPos };
}3. Directional Thrust Vectors
Rotors generate thrust perpendicular to the drone's frame. To ensure thrust pushes the craft relative to its current orientation, derive the upward normal vector using the drone's current rotation angle:
function getThrustVector(body, thrustMagnitude) {
// Normal vector pointing "up" relative to the drone's top surface
return {
x: thrustMagnitude * Math.sin(body.angle),
y: -thrustMagnitude * Math.cos(body.angle)
};
}4. Gyro Leveling with a PD Controller
A real drone uses an Inertial Measurement Unit (IMU) running a Proportional-Integral-Derivative (PID) algorithm to maintain level flight. In Matter.js, an effective gyro stabilizer can be achieved using a Proportional-Derivative (PD) controller:
- Proportional (\(K_p\)): Corrects the angular
position error relative to the target angle (horizontal, or
0radians). - Derivative (\(K_d\)): Counteracts angular velocity to prevent overshoot and dampen oscillations.
const Kp = 0.08; // Proportional gain: strength of tilt correction
const Kd = 0.45; // Derivative gain: rotational damping
function calculateStabilization(body, targetAngle = 0) {
const angleError = targetAngle - body.angle;
const angularVelocity = body.angularVelocity;
// Control output for differential thrust
return (Kp * angleError) - (Kd * angularVelocity);
}5. Running the Physics Update Loop
Integrate the stabilization logic directly into the engine's update
cycle via the beforeUpdate event. Calculate the base thrust
required to counteract gravity, modify each motor's output using the
stabilization control signal, and apply the resulting forces at the
motor positions.
const gravity = engine.gravity.y * engine.gravity.scale;
const hoverThrustPerMotor = (drone.mass * gravity) / 2;
Matter.Events.on(engine, 'beforeUpdate', () => {
const correction = calculateStabilization(drone, 0);
const { leftPos, rightPos } = getMotorWorldPositions(drone);
// Adjust individual thrust based on gyro output
// Tilting clockwise requires more left thrust; counter-clockwise requires more right thrust
const leftThrustMag = hoverThrustPerMotor - correction;
const rightThrustMag = hoverThrustPerMotor + correction;
const leftForce = getThrustVector(drone, Math.max(0, leftThrustMag));
const rightForce = getThrustVector(drone, Math.max(0, rightThrustMag));
// Apply forces to the physical body
Body.applyForce(drone, leftPos, leftForce);
Body.applyForce(drone, rightPos, rightForce);
});6. Tuning and Flight Control
To control lateral movement and altitude:
- Pitch Control: Modify the
targetAnglepassed tocalculateStabilization(). Setting it to0.1radians tilts the drone forward/right, directing part of the thrust vector horizontally. - Altitude Control: Scale both
leftThrustMagandrightThrustMagsimultaneously above or below the hover baseline. - Oscillation Tuning: If the drone wobbles rapidly,
decrease
Kpor increaseKd. If the response is sluggish or fails to level against disturbances, increaseKp.