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:
- Boom: The primary arm segment anchored to the vehicle chassis.
- Stick (Dipper): The intermediate arm segment connected to the boom.
- Bucket: The end-effector attached to the tip of the stick.
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
- Sub-stepping / Position Iterations: Hydraulic
linkages encounter high tension. Increase
engine.positionIterationsandengine.velocityIterationsto8or higher inMatter.Engineto eliminate visual jitter and joint separation. - Stiffness Balance: Keep hydraulic cylinder
stiffnessbetween0.7and1.0. Lower values will simulate hydraulic fluid compression, while a value of1.0delivers rigid mechanical response. - Mass Distribution: Ensure mass transitions smoothly down the arm. The boom should have the highest mass and the bucket the lowest to maintain stability during dynamic movement.