How to Simulate an Engine Piston in Matter.js

This article provides a practical guide to simulating a classic internal combustion engine's slider-crank mechanism using Matter.js. You will learn how to model the crankshaft, connecting rod, and reciprocating piston as rigid bodies, connect them using pivot constraints, restrict the piston's motion to linear travel, and drive the system with continuous rotational torque.

Understanding the Mechanical Components

A standard reciprocating engine assembly, known mechanically as a slider-crank linkage, consists of three primary moving parts:

  1. Crankshaft: A body pinned to a fixed center point that rotates continuously.
  2. Connecting Rod: A rigid link connected to the off-center crankpin on one end and the piston pin on the other via free-rotating pivots.
  3. Piston: A slider constrained to move along a single linear axis inside an engine cylinder.

Step-by-Step Implementation

1. Setup the Environment

Initialize the core Matter.js modules: Engine, Render, Runner, Bodies, Composite, and Constraint. Disable or reduce gravity depending on whether you want a purely mechanical demonstration or a realistic gravity-affected model.

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

const engine = Engine.create({
  gravity: { x: 0, y: 0 } // Neutral gravity for balanced rotation
});

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

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

2. Create the Crankshaft

The crankshaft is represented as a circular disc or a rotating arm anchored to a fixed coordinate in the world. An offset point serves as the crankpin.

const crankCenter = { x: 400, y: 400 };
const crankRadius = 60;

// Visual crank body
const crank = Bodies.circle(crankCenter.x, crankCenter.y, crankRadius, {
  collisionFilter: { group: -1 } // Prevent collision with connected parts
});

// Fixed pivot constraint anchoring the crank to the world
const crankAnchor = Constraint.create({
  pointA: crankCenter,
  bodyB: crank,
  pointB: { x: 0, y: 0 },
  stiffness: 1,
  length: 0
});

3. Create the Piston and Guide Rails

The piston is modeled as a rectangular body. To ensure it moves solely along a vertical axis without tilting or wandering, create static cylinder walls on either side with zero friction.

const pistonWidth = 80;
const pistonHeight = 60;
const cylinderX = crankCenter.x;
const pistonStartY = crankCenter.y - 250;

const piston = Bodies.rectangle(cylinderX, pistonStartY, pistonWidth, pistonHeight, {
  inertia: Infinity, // Prevents the piston from rotating
  friction: 0,
  frictionAir: 0.001
});

// Static walls forming the cylinder guide
const wallOptions = { isStatic: true, friction: 0 };
const leftWall = Bodies.rectangle(cylinderX - (pistonWidth / 2) - 5, pistonStartY, 10, 300, wallOptions);
const rightWall = Bodies.rectangle(cylinderX + (pistonWidth / 2) + 5, pistonStartY, 10, 300, wallOptions);

4. Connect the Assembly with the Connecting Rod

The connecting rod links the crank's perimeter (crankpin) to the center of the piston. You can use a stiff distance constraint to act as the rod.

const rodLength = 180;

const connectingRod = Constraint.create({
  bodyA: crank,
  pointA: { x: 0, y: -crankRadius }, // Crankpin position on the edge
  bodyB: piston,
  pointB: { x: 0, y: 0 },             // Wrist pin at the piston center
  length: rodLength,
  stiffness: 1,
  render: {
    lineWidth: 6,
    strokeStyle: '#e67e22'
  }
});

5. Assemble the Scene and Apply Driving Torque

Add all created bodies and constraints to the engine world. To simulate the combustion cycle or an electric starter motor driving the engine, continuously enforce angular velocity or apply torque on the crankshaft before every engine update.

Composite.add(engine.world, [
  crank,
  crankAnchor,
  piston,
  leftWall,
  rightWall,
  connectingRod
]);

// Apply continuous rotation to the crankshaft
Matter.Events.on(engine, 'beforeUpdate', () => {
  Body.setAngularVelocity(crank, 0.08);
});

Critical Tuning Parameters