Terrain Carving for Matter.js Ground Bodies

This guide explains how to implement dynamic terrain carving and wormhole creation in Matter.js using 2D polygon clipping and vertex decomposition. Because Matter.js does not support native Constructive Solid Geometry (CSG) or concave dynamic collisions without polygon decomposition, carving through static bodies requires subtracting shapes mathematically and regenerating the physics bodies in the simulation world.

1. Required Dependencies

Matter.js cannot decompose complex concave shapes into convex collision hulls on its own. You must provide a polygon decomposition library and a 2D polygon clipping library:

import Matter from 'matter-js';
import decomp from 'poly-decomp';
import polygonClipping from 'polygon-clipping';

Matter.Common.setDecomp(decomp);

2. Representing Ground as Polygons

Instead of using basic primitive rectangles (Matter.Bodies.rectangle), represent your ground as an array of 2D coordinates defining a closed polygon.

// A simple ground block defined clockwise
let groundCoords = [
  [[0, 200], [800, 200], [800, 600], [0, 600]]
];

3. Generating the Carve Shape

To carve a wormhole, generate a circular polygon or tunnel path at the target coordinates. The polygon must be represented in the same coordinate space as the ground terrain.

function createCirclePolygon(centerX, centerY, radius, segments = 16) {
  const points = [];
  for (let i = 0; i < segments; i++) {
    const angle = (i / segments) * Math.PI * 2;
    points.push([
      centerX + Math.cos(angle) * radius,
      centerY + Math.sin(angle) * radius
    ]);
  }
  return [points]; // Format matching polygon-clipping input
}

4. Performing Boolean Subtraction

Execute a boolean difference between the current terrain polygons and the carve shape. This removes the overlapping area, creating the hole or tunnel.

function carveHole(terrainRings, holeRings) {
  // polygonClipping.difference returns a MultiPolygon structure
  return polygonClipping.difference(terrainRings, holeRings);
}

5. Rebuilding the Physics Body

Matter.js bodies cannot have their geometry modified dynamically in place. You must remove the existing terrain body, generate new bodies using Matter.Bodies.fromVertices(), and re-add them to the Matter world.

let groundBody = null;

function updateGroundPhysics(world, multiPolygon) {
  // 1. Remove the old ground body from the world
  if (groundBody) {
    Matter.Composite.remove(world, groundBody);
  }

  const bodies = [];

  // 2. Iterate through resulting polygons
  for (const polygon of multiPolygon) {
    for (const ring of polygon) {
      // Map coordinates to Matter.Vector objects
      const vertices = ring.map(pt => ({ x: pt[0], y: pt[1] }));

      // 3. Create a static compound body using decomposed vertices
      const body = Matter.Bodies.fromVertices(
        0, 0,
        vertices,
        {
          isStatic: true,
          render: { fillStyle: '#2e2e2e' }
        },
        true // Flag to automatically fix/normalize vertices
      );

      if (body) {
        // Correct position offset caused by fromVertices center-of-mass calculation
        Matter.Body.setPosition(body, {
          x: body.position.x + (body.bounds.min.x < 0 ? Math.abs(body.bounds.min.x) : 0),
          y: body.position.y + (body.bounds.min.y < 0 ? Math.abs(body.bounds.min.y) : 0)
        });
        bodies.push(body);
      }
    }
  }

  // 4. Group all parts into a single composite or compound body
  groundBody = Matter.Body.create({
    parts: bodies.flatMap(b => b.parts.slice(1)),
    isStatic: true
  });

  Matter.Composite.add(world, groundBody);
}

6. Performance Optimizations