Create a Deformable Fishing Net in Matter.js

This article explains how to build a realistic, deformable fishing net in Matter.js using a mass-spring system. By arranging lightweight circular bodies into a two-dimensional grid and interconnecting them with distance constraints, you can simulate flexible, cloth-like physical behaviors that react dynamically to gravity, external forces, and collisions.

Core Concepts

A deformable net consists of two primary elements in Matter.js:

  1. Point Masses: Small, lightweight circular rigid bodies (Matter.Bodies.circle) representing the intersections or knots of the net.
  2. Springs: Distance constraints (Matter.Constraint.create) connecting adjacent point masses to act as the threads of the net. Adjusting constraint stiffness controls how much the net stretches under load.

Step-by-Step Implementation

1. Setup the Matter.js Environment

Initialize the basic Matter.js modules: the engine, runner, renderer, and world composite.

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

const engine = Engine.create();
const world = engine.world;

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. Define Net Configuration

Set the dimensions and properties of the grid. Using a small radius for point masses prevents unwanted self-collisions while maintaining mass.

const cols = 15;
const rows = 10;
const spacing = 25;
const startX = 220;
const startY = 100;
const particleRadius = 3;
const stiffness = 0.8; // Elasticity of threads (0 = fully elastic, 1 = rigid)

3. Generate Point Masses

Loop through rows and columns to generate the grid of bodies. Anchor specific points (such as the top corners) by setting isStatic: true to suspend the net in space.

const grid = [];

for (let y = 0; y < rows; y++) {
  grid[y] = [];
  for (let x = 0; x < cols; x++) {
    // Pin top-left and top-right points to hang the net
    const isPinned = (y === 0 && (x === 0 || x === cols - 1));

    const particle = Bodies.circle(
      startX + x * spacing,
      startY + y * spacing,
      particleRadius,
      {
        isStatic: isPinned,
        frictionAir: 0.02,
        collisionFilter: { group: -1 }, // Negative group prevents self-collision among knots
        render: { fillStyle: '#ffffff' }
      }
    );

    grid[y][x] = particle;
    Composite.add(world, particle);
  }
}

4. Connect Masses with Constraints

Iterate through the generated grid and attach structural constraints horizontally and vertically. For increased structural integrity and resistance to shearing, diagonal cross-constraints can also be added.

for (let y = 0; y < rows; y++) {
  for (let x = 0; x < cols; x++) {
    // Horizontal connections
    if (x < cols - 1) {
      Composite.add(world, Constraint.create({
        bodyA: grid[y][x],
        bodyB: grid[y][x + 1],
        stiffness: stiffness,
        damping: 0.1,
        render: { strokeStyle: '#555555', lineWidth: 1 }
      }));
    }

    // Vertical connections
    if (y < rows - 1) {
      Composite.add(world, Constraint.create({
        bodyA: grid[y][x],
        bodyB: grid[y + 1][x],
        stiffness: stiffness,
        damping: 0.1,
        render: { strokeStyle: '#555555', lineWidth: 1 }
      }));
    }
  }
}

5. Add Interactivity and Interaction Bodies

Add a mouse constraint to allow dragging parts of the net, as well as an external dynamic body to interact with it.

// Add a heavy object to fall into the net
const catchObject = Bodies.circle(400, 50, 20, {
  density: 0.05,
  render: { fillStyle: '#ff4757' }
});
Composite.add(world, catchObject);

// Enable mouse interaction
const mouse = Mouse.create(render.canvas);
const mouseConstraint = MouseConstraint.create(engine, {
  mouse: mouse,
  constraint: {
    stiffness: 0.2,
    render: { visible: false }
  }
});
Composite.add(world, mouseConstraint);
render.mouse = mouse;

Tuning and Performance Considerations