How to Create Concave Shapes in Matter.js

This article explains how to create concave shapes using the Matter.js 2D physics engine. Although Matter.js natively supports only convex polygons due to the mathematical requirements of its collision detection algorithm, you can create functional concave bodies by decomposing them into sets of convex sub-bodies. Below, you will discover why this restriction exists, how Matter.js integrates with vertex decomposition libraries, and the exact code necessary to build complex, hollow, or irregular concave geometries.

Why Matter.js Requires Convex Polygons

Matter.js relies on the Separating Axis Theorem (SAT) for rigid-body collision detection. SAT works by projecting shapes onto various axes to check for overlapping regions. For convex polygons, if a single separating axis exists where the projections do not overlap, the shapes are definitely not colliding.

Concave polygons break this theorem. Because a concave shape has inward-denting vertices, an axis can show overlapping projections even when the bodies are not physically touching, resulting in false collisions or bodies passing through each other. To prevent these computational errors, Matter.js restricts basic bodies to convex geometry.

The Solution: Vertex Decomposition

To simulate a concave object, you must break the shape into multiple smaller convex polygons. In Matter.js, these convex sub-polygons are joined together into a single rigid compound body. The compound body moves, rotates, and responds to physical forces as one cohesive unit while properly registering collisions along its concave crevices.

Matter.js automates this decomposition using a third-party library called poly-decomp.

Step-by-Step Implementation

1. Include the poly-decomp Library

Matter.js does not bundle poly-decomp by default to keep the core engine lightweight. You must install or import it separately and expose it to the engine.

In a browser environment:

<script src="path/to/decomp.js"></script>
<script src="path/to/matter.js"></script>

In a module-based JavaScript project:

import Matter from 'matter-js';
import decomp from 'poly-decomp';

// Register the decomp library with Matter.js
Matter.Common.setDecomp(decomp);

2. Define the Concave Vertices

Provide the outline of your shape as an array of vector coordinates. The vertices should trace the perimeter of the concave shape.

const concaveVertices = [
  { x: 0, y: 0 },
  { x: 100, y: 0 },
  { x: 100, y: 100 },
  { x: 50, y: 50 },  // Inward notch creating the concave shape
  { x: 0, y: 100 }
];

3. Generate the Body Using Bodies.fromVertices

Call the Matter.Bodies.fromVertices method. Matter.js detects whether the vertices form a concave path. If so, it passes the vertices to poly-decomp, creates convex parts, and groups them under a single parent compound body:

const concaveBody = Matter.Bodies.fromVertices(200, 200, concaveVertices, {
  isStatic: false,
  restitution: 0.5,
  friction: 0.1
});

// Add the body to your Matter.js world
Matter.Composite.add(engine.world, concaveBody);

Best Practices and Considerations