Matter.js Scissor Lift with Cross-Pinned Constraints
This article explains how to build a functioning scissor lift physics simulation using cross-pinned constraints in Matter.js. By pairing crossed rigid bodies, pinning their centers with zero-length rotational constraints, chaining multiple tiers, and actuating the base horizontally, you can achieve realistic vertical mechanical extension.
Core Mechanics of a 2D Scissor Lift
A scissor lift works through pantograph geometry. Pairs of rigid beams intersect to form an "X" shape. When the bottom points of the "X" are pushed toward each other along a horizontal axis, the vertical distance between the bottom and top points increases rapidly.
In Matter.js, this requires three constraint types:
- Central Pivot Constraints: A revolute joint pinning the centers of two intersecting beams together.
- Tier-Linking Constraints: Revolute joints connecting the top ends of one tier to the bottom ends of the tier above it.
- Base Actuation: A static hinge on one side of the bottom tier and a horizontally sliding constraint or body on the opposite side to drive the motion.
Step 1: Defining the Beams and Center Pin
Each tier consists of two identical rectangular bodies arranged in an "X". To allow them to overlap without repelling each other, their collision categories must be adjusted or collisions disabled between them using collision groups.
const { Bodies, Body, Constraint, Composite } = Matter;
const beamWidth = 200;
const beamHeight = 15;
const collisionGroup = Body.nextGroup(true); // Negative group prevents mutual collision
function createScissorTier(x, y, angleOffset) {
const beamA = Bodies.rectangle(x, y, beamWidth, beamHeight, {
collisionFilter: { group: -1 },
angle: angleOffset
});
const beamB = Bodies.rectangle(x, y, beamWidth, beamHeight, {
collisionFilter: { group: -1 },
angle: -angleOffset
});
// Cross-pin constraint at the center
const centerPin = Constraint.create({
bodyA: beamA,
bodyB: beamB,
pointA: { x: 0, y: 0 },
pointB: { x: 0, y: 0 },
stiffness: 1,
length: 0
});
return { beamA, beamB, centerPin };
}Step 2: Chaining Multiple Tiers
To build height, multiple tiers are linked together at their outer tips. A Matter.js zero-length constraint joins the upper-left tip of the lower tier to the lower-left tip of the upper tier. The same connection is made on the right side.
function linkTiers(lowerTier, upperTier) {
const halfWidth = (beamWidth / 2) * 0.9; // Slight inset for joint stability
// Left side hinge
const leftHinge = Constraint.create({
bodyA: lowerTier.beamA,
bodyB: upperTier.beamB,
pointA: { x: -halfWidth, y: 0 },
pointB: { x: -halfWidth, y: 0 },
stiffness: 1,
length: 0
});
// Right side hinge
const rightHinge = Constraint.create({
bodyA: lowerTier.beamB,
bodyB: upperTier.beamA,
pointA: { x: halfWidth, y: 0 },
pointB: { x: halfWidth, y: 0 },
stiffness: 1,
length: 0
});
return [leftHinge, rightHinge];
}Step 3: Anchoring and Actuating the Base
A scissor lift needs one fixed base anchor and one movable base anchor to expand vertically:
- Fixed Pivot: Pin the bottom-left joint of the
lowest tier to a fixed point in the world using a constraint where
pointBis an absolute coordinate andbodyBisnull. - Sliding Joint: Connect the bottom-right joint to a rigid body constrained to move only along the horizontal axis, or directly push that corner with a constant linear force.
const halfWidth = (beamWidth / 2) * 0.9;
const baseTier = createScissorTier(400, 500, Math.PI / 6);
// Fixed left anchor
const fixedBase = Constraint.create({
bodyA: baseTier.beamA,
pointA: { x: -halfWidth, y: 0 },
pointB: { x: 310, y: 500 },
stiffness: 1,
length: 0
});
// Slider block for right anchor
const sliderBlock = Bodies.rectangle(490, 500, 30, 30, {
collisionFilter: { group: -1 }
});
const sliderHinge = Constraint.create({
bodyA: baseTier.beamB,
bodyB: sliderBlock,
pointA: { x: halfWidth, y: 0 },
pointB: { x: 0, y: 0 },
stiffness: 1,
length: 0
});
// Keep the slider restricted to horizontal motion
const horizontalTrack = Constraint.create({
bodyB: sliderBlock,
pointB: { x: 490, y: 500 },
stiffness: 0.1,
length: 0
});To drive the lift upward, apply a horizontal force pushing
sliderBlock toward the fixed anchor:
Matter.Events.on(engine, 'beforeUpdate', () => {
// Push the sliding side inward to raise the lift
Body.applyForce(sliderBlock, sliderBlock.position, { x: -0.05, y: 0 });
});Key Tuning Parameters for Stability
- Constraint Iterations: Default solver iterations in
Matter.js can cause scissor linkages to sag or drift under load.
Increase
engine.constraintIterationsto8or10to maintain rigid cross-pins. - Mass Ratios: Keep beam masses uniform to avoid rotational jitter at the pivot points.
- Angular Damping: If the lift oscillates excessively
when closing or opening, slightly increase the
frictionAirproperty on the individual beams.