Building an IK Robotic Arm in Matter.js
This article provides a complete guide to constructing a 2D physics-driven robotic arm using Matter.js, directed by an Inverse Kinematics (IK) solver. You will learn how to model the physical arm segments using rigid bodies and constraints, calculate target joint angles using analytical IK, and apply motor-like forces to smoothly drive physical joints toward target coordinates while preserving physical collisions and interactions.
1. Setting Up the Physical Arm Hierarchy
A robotic arm in Matter.js consists of multiple rigid bodies
connected sequentially by pin constraints. Rather than directly moving
positions, the arm must rely on rigid bodies
(Matter.Bodies.rectangle) and pivot points
(Matter.Constraint.create) to ensure realistic mass
distribution and collision response.
const { Engine, Render, Runner, Bodies, Composite, Constraint } = Matter;
const engine = Engine.create();
const world = engine.world;
// Base anchor fixed in world space
const base = Bodies.circle(400, 500, 10, { isStatic: true });
// Arm segments (upper arm and forearm)
const segmentLength = 150;
const segmentWidth = 20;
const upperArm = Bodies.rectangle(400, 425, segmentWidth, segmentLength, {
collisionFilter: { group: -1 } // Prevent self-collision between links
});
const forearm = Bodies.rectangle(400, 275, segmentWidth, segmentLength, {
collisionFilter: { group: -1 }
});
// Shoulder joint (Base to Upper Arm)
const shoulderJoint = Constraint.create({
bodyA: base,
bodyB: upperArm,
pointA: { x: 0, y: 0 },
pointB: { x: 0, y: segmentLength / 2 },
stiffness: 1,
length: 0
});
// Elbow joint (Upper Arm to Forearm)
const elbowJoint = Constraint.create({
bodyA: upperArm,
bodyB: forearm,
pointA: { x: 0, y: -segmentLength / 2 },
pointB: { x: 0, y: segmentLength / 2 },
stiffness: 1,
length: 0
});
Composite.add(world, [base, upperArm, forearm, shoulderJoint, elbowJoint]);2. Implementing the Inverse Kinematics Solver
For a two-link planar arm, analytical trigonometry provides exact target joint angles without the overhead of iterative solvers like FABRIK or CCD. Given a target position \((tx, ty)\) relative to the base \((bx, by)\), calculate the required shoulder angle (\(\theta_1\)) and elbow angle (\(\theta_2\)) using the Law of Cosines.
function solveTwoBoneIK(baseX, baseY, targetX, targetY, l1, l2) {
const dx = targetX - baseX;
const dy = targetY - baseY;
let dist = Math.hypot(dx, dy);
// Clamp target within reach
const maxReach = (l1 + l2) * 0.999;
const minReach = Math.abs(l1 - l2) * 1.001;
dist = Math.max(minReach, Math.min(dist, maxReach));
// Law of Cosines
const cosElbow = (dist * dist - l1 * l1 - l2 * l2) / (2 * l1 * l2);
const elbowAngle = Math.acos(cosElbow); // Relative angle between segments
const alpha = Math.atan2(dy, dx);
const cosShoulder = (dist * dist + l1 * l1 - l2 * l2) / (2 * dist * l1);
const shoulderAngle = alpha - Math.acos(cosShoulder);
return {
shoulderTarget: shoulderAngle + Math.PI / 2,
elbowTarget: elbowAngle
};
}3. Coupling Kinematics to Matter.js Physics
Directly overriding a body's angle via
Matter.Body.setAngle() breaks the velocity and impulse
resolution of the physics engine, leading to tunneling and jitter.
Instead, use a Proportional-Derivative (PD) controller to apply torques
or angular velocities toward the target angles.
function applyJointTorque(body, targetAngle, kp = 0.05, kd = 0.01) {
// Normalize target difference to [-PI, PI]
let angleDiff = targetAngle - body.angle;
angleDiff = Math.atan2(Math.sin(angleDiff), Math.cos(angleDiff));
// PD Controller calculation
const torque = (angleDiff * kp) - (body.angularVelocity * kd);
// Apply torque directly to the body
Matter.Body.setAngularVelocity(body, body.angularVelocity + torque);
}4. Running the Unified Update Loop
Attach the IK calculation and torque application to Matter.js's before-update event hook. This ensures target angles are calculated, physics forces are injected, and collision solvers update sequentially.
const target = { x: 450, y: 350 };
Matter.Events.on(engine, 'beforeUpdate', () => {
// 1. Solve IK target angles
const angles = solveTwoBoneIK(
base.position.x,
base.position.y,
target.x,
target.y,
segmentLength,
segmentLength
);
// 2. Compute absolute targets
const targetShoulder = angles.shoulderTarget;
const targetElbow = upperArm.angle + angles.elbowTarget;
// 3. Drive bodies using physics-friendly angular velocity adjustments
applyJointTorque(upperArm, targetShoulder, 0.08, 0.02);
applyJointTorque(forearm, targetElbow, 0.08, 0.02);
});By relying on analytical IK to set reference orientations and driving the rigid bodies using angular torque, the arm dynamically follows target positions while naturally interacting with obstacles, pushing objects, and responding to external forces.