Create Soft Body Cloth with Matter.js Constraints

This article explains how to simulate a realistic soft body cloth in Matter.js by linking a grid of rigid point-mass bodies using distance constraints. You will learn the mechanics behind mass-spring cloth systems, how to assemble the particle grid, how to anchor fixed points, and how to adjust constraint parameters like stiffness and damping to control the fabric's behavior.

Core Concepts of Matter.js Cloth Simulation

Matter.js is inherently a rigid-body 2D physics engine, meaning it does not possess dedicated deformable body primitives out of the box. To achieve a soft body cloth effect, you must construct a point-mass lattice. In this system:

Implementing Cloth with Built-In Composites

Matter.js includes a built-in helper method, Matter.Composites.softBody, which generates a grid of circular bodies interconnected by constraints.

const { Engine, Render, Runner, Composites, Common, World, Bodies } = Matter;

// Create engine and world
const engine = Engine.create();
const world = engine.world;

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

// Cloth configuration
const startX = 200;
const startY = 100;
const columns = 20;
const rows = 12;
const columnGap = 20;
const rowGap = 20;
const particleRadius = 4;

// Generate cloth using Composites.softBody
const cloth = Composites.softBody(
  startX, startY, 
  columns, rows, 
  columnGap, rowGap, 
  false, 
  particleRadius, 
  {
    collisionFilter: { group: Matter.Body.nextGroup(true) }, // Prevent self-collision artifacts
    frictionAir: 0.05,
    render: { visible: true, fillStyle: '#4a90e2' }
  },
  {
    stiffness: 0.9,
    damping: 0.1,
    render: { strokeStyle: '#ffffff', lineWidth: 1 }
  }
);

// Pin the top row of particles
for (let i = 0; i < columns; i++) {
  cloth.bodies[i].isStatic = true;
}

World.add(world, cloth);

Creating a Custom Cloth Grid with Manual Constraints

For finer control over structural integrity, tearing, and diagonal bracing, you can build the cloth manually using Matter.Constraint.create and a two-dimensional array of bodies.

const { World, Bodies, Constraint } = Matter;

const cols = 15;
const rows = 10;
const spacing = 25;
const originX = 220;
const originY = 80;

const grid = [];
const group = Matter.Body.nextGroup(true);

// 1. Create Particles
for (let y = 0; y < rows; y++) {
  grid[y] = [];
  for (let x = 0; x < cols; x++) {
    const isPinned = y === 0 && (x === 0 || x === cols - 1 || x === Math.floor(cols / 2));
    const particle = Bodies.circle(
      originX + x * spacing,
      originY + y * spacing,
      5,
      {
        isStatic: isPinned,
        collisionFilter: { group: group },
        frictionAir: 0.02
      }
    );
    grid[y][x] = particle;
    World.add(world, particle);
  }
}

// 2. Link Particles with Constraints
for (let y = 0; y < rows; y++) {
  for (let x = 0; x < cols; x++) {
    // Horizontal link
    if (x < cols - 1) {
      World.add(world, Constraint.create({
        bodyA: grid[y][x],
        bodyB: grid[y][x + 1],
        stiffness: 0.8,
        render: { strokeStyle: '#888' }
      }));
    }

    // Vertical link
    if (y < rows - 1) {
      World.add(world, Constraint.create({
        bodyA: grid[y][x],
        bodyB: grid[y + 1][x],
        stiffness: 0.8,
        render: { strokeStyle: '#888' }
      }));
    }

    // Diagonal link (Shear resistance)
    if (x < cols - 1 && y < rows - 1) {
      World.add(world, Constraint.create({
        bodyA: grid[y][x],
        bodyB: grid[y + 1][x + 1],
        stiffness: 0.4,
        render: { visible: false } // Keep diagonal supports hidden
      }));
    }
  }
}

Tuning Cloth Parameters