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
- Self-Intersecting Paths: Ensure your vertex sequence does not cross over itself. Self-intersecting outlines cause decomposition to fail or produce degenerate physics bodies.
- Vertex Simplification: Complex SVG paths or geometry with hundreds of points can significantly lower frame rates. Use algorithms like Ramer-Douglas-Peucker to simplify paths before passing them to the physics engine.
- Hollow/Donut Shapes: Fully enclosed hollow bodies
(shapes with internal holes) cannot be generated directly from a single
vertex loop. To create a hollow enclosure or container, manually combine
multiple rectangular or polygonal convex bodies into a compound body
using
Matter.Body.create({ parts: [...] }).