Excavator Hydraulic Arm Simulation in Matter.js

This article explains how to construct a functional excavator arm featuring a multi-joint hydraulic piston simulation using the Matter.js 2D physics engine. You will learn how to set up the mechanical linkage chain—comprising the boom, stick, and bucket—and simulate realistic linear hydraulic actuators using dynamic distance constraints.

1. Architectural Overview of the Excavator Arm

An excavator arm consists of three main rigid links connected by pivot (revolute) joints:

In real-world machinery, each joint is articulated not by rotational motors at the pivot, but by linear hydraulic cylinders. In Matter.js, the structural pivots are simulated using zero-length constraints, while the hydraulic cylinders are simulated using variable-length distance constraints anchored offset from the main pivots.

2. Setting Up the Matter.js Environment

Initialize the basic Matter.js components: the engine, world, runner, and renderer.

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

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

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

Render.run(render);
Runner.run(Runner.create(), engine);

3. Creating the Arm Linkages

Model the boom, stick, and bucket as rigid rectangular bodies. Set collision filters if you want the segments to bypass mutual collision and rely purely on constraints.

// Base / Chassis
const base = Bodies.rectangle(200, 550, 150, 60, { isStatic: true });

// Boom, Stick, and Bucket
const boom = Bodies.rectangle(350, 480, 220, 25, { density: 0.005 });
const stick = Bodies.rectangle(520, 430, 180, 20, { density: 0.004 });
const bucket = Bodies.rectangle(650, 460, 80, 40, { density: 0.003 });

Composite.add(world, [base, boom, stick, bucket]);

4. Creating Pivot Joints

Anchor each body to the next using non-elastic constraints with a length of zero. These act as mechanical hinges.

// Hinge: Base to Boom
const baseToBoom = Constraint.create({
    bodyA: base,
    pointA: { x: 50, y: -20 },
    bodyB: boom,
    pointB: { x: -100, y: 0 },
    stiffness: 1,
    length: 0
});

// Hinge: Boom to Stick
const boomToStick = Constraint.create({
    bodyA: boom,
    pointA: { x: 100, y: 0 },
    bodyB: stick,
    pointB: { x: -80, y: 0 },
    stiffness: 1,
    length: 0
});

// Hinge: Stick to Bucket
const stickToBucket = Constraint.create({
    bodyA: stick,
    pointA: { x: 80, y: 0 },
    bodyB: bucket,
    pointB: { x: -30, y: -15 },
    stiffness: 1,
    length: 0
});

Composite.add(world, [baseToBoom, boomToStick, stickToBucket]);

5. Implementing Hydraulic Actuator Constraints

Hydraulic pistons are created by connecting two bodies with a dynamic constraint offset from their shared pivot. The offset provides the leverage arm required to generate mechanical torque when the constraint expands or contracts.

// Boom Hydraulic Cylinder (Base -> Boom)
const boomPiston = Constraint.create({
    bodyA: base,
    pointA: { x: 10, y: -25 },
    bodyB: boom,
    pointB: { x: -20, y: -15 },
    stiffness: 0.8,
    length: 120,
    render: { strokeStyle: '#ff9900', lineWidth: 6 }
});

// Stick Hydraulic Cylinder (Boom -> Stick)
const stickPiston = Constraint.create({
    bodyA: boom,
    pointA: { x: 10, y: 15 },
    bodyB: stick,
    pointB: { x: -30, y: 15 },
    stiffness: 0.8,
    length: 100,
    render: { strokeStyle: '#ff9900', lineWidth: 6 }
});

// Bucket Hydraulic Cylinder (Stick -> Bucket)
const bucketPiston = Constraint.create({
    bodyA: stick,
    pointA: { x: 30, y: 15 },
    bodyB: bucket,
    pointB: { x: 0, y: -20 },
    stiffness: 0.8,
    length: 80,
    render: { strokeStyle: '#ff9900', lineWidth: 4 }
});

Composite.add(world, [boomPiston, stickPiston, bucketPiston]);

6. Simulating Hydraulic Motion via Input

Hydraulics operate at a controlled linear rate. To extend or retract a piston, modify the length property of the respective constraint within the simulation tick. Define minimum and maximum stroke limits to prevent mechanical inversion.

const pistonControls = {
    boom: { constraint: boomPiston, min: 70, max: 180, speed: 1.5 },
    stick: { constraint: stickPiston, min: 60, max: 160, speed: 1.5 },
    bucket: { constraint: bucketPiston, min: 40, max: 120, speed: 1.5 }
};

function adjustPiston(actuator, direction) {
    const target = actuator.constraint.length + (direction * actuator.speed);
    if (target >= actuator.min && target <= actuator.max) {
        actuator.constraint.length = target;
    }
}

// Example input hook inside an update loop or event listener:
// Call adjustPiston(pistonControls.boom, 1) to extend.
// Call adjustPiston(pistonControls.boom, -1) to retract.

7. Stability and Tuning Considerations