Create Concave Polygons in Matter.js with decomp.js
This guide demonstrates how to integrate decomp.js
(poly-decomp) with Matter.js to generate and simulate concave rigid
bodies. While the Matter.js physics engine natively supports only convex
polygons, loading the decomp.js library allows the engine
to automatically break down concave shapes into a compound set of convex
parts, enabling realistic physics simulations for complex geometric
paths.
Why decomp.js is Required
Matter.js relies on the Separating Axis Theorem (SAT) for collision detection, which functions exclusively on convex hulls. When you pass concave vertices into Matter.js without an external decomposition library, the engine automatically generates a single convex hull that bridges across your intended indentations and hollows.
To retain concave shapes (like stars, letters, or hollow containers),
Matter.js uses poly-decomp.js to split the complex polygon
into multiple adjacent convex shapes joined together into a single
compound body.
1. Install and Provide decomp.js to Matter.js
Matter.js needs a reference to decomp.js before it can
process concave vertices. You can load it via a CDN or through a module
bundler.
Via CDN (Browser Environment)
Include poly-decomp.js before your Matter.js script, or
ensure it is assigned to the global window object:
<script src="https://cdn.jsdelivr.net/npm/poly-decomp@0.3.0/build/decomp.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>When loaded globally via a script tag, Matter.js detects
window.decomp automatically.
Via NPM / Bundlers (ES6 & Node.js)
If using Webpack, Vite, or Node.js, install the package:
npm install poly-decomp matter-jsThen explicitly register the library using
Matter.Common.setDecomp:
import Matter from 'matter-js';
import decomp from 'poly-decomp';
Matter.Common.setDecomp(decomp);2. Define Concave Vertices
Vertices must be defined as an array of vector objects containing
x and y coordinates, or parsed from an SVG
path using Matter.Svg.pathToVertices().
Ensure that:
- The path traces around the perimeter in a continuous loop.
- The polygon is not self-intersecting (lines should not cross each other).
Example vertices for an arrow or chevron shape:
const vertices = [
{ x: 0, y: 0 },
{ x: 100, y: 50 },
{ x: 0, y: 100 },
{ x: 30, y: 50 } // Indented point making the shape concave
];3. Create the Body Using Bodies.fromVertices
Use Matter.Bodies.fromVertices() to instantiate the
body. Matter.js calls decomp.quickDecomp() behind the
scenes to split the path into convex components.
const x = 400;
const y = 300;
const concaveBody = Matter.Bodies.fromVertices(x, y, vertices, {
isStatic: false,
render: {
fillStyle: '#2ecc71',
strokeStyle: '#27ae60',
lineWidth: 1
}
});
Matter.Composite.add(engine.world, concaveBody);Complete Implementation Example
// Setup engine and renderer
const { Engine, Render, Runner, Bodies, Composite, Common } = Matter;
// Provide decomp to Matter if using a bundler
// Common.setDecomp(decomp);
const engine = Engine.create();
const render = Render.create({
element: document.body,
engine: engine,
options: {
width: 800,
height: 600,
wireframes: false
}
});
Render.run(render);
Runner.run(Runner.create(), engine);
// Concave star vertices
const starVertices = [
{ x: 50, y: 0 },
{ x: 63, y: 38 },
{ x: 100, y: 38 },
{ x: 69, y: 59 },
{ x: 82, y: 100 },
{ x: 50, y: 75 },
{ x: 18, y: 100 },
{ x: 31, y: 59 },
{ x: 0, y: 38 },
{ x: 37, y: 38 }
];
// Generate compound concave body
const starBody = Bodies.fromVertices(400, 100, starVertices, {
restitution: 0.5,
render: { fillStyle: '#e74c3c' }
});
// Ground to catch the falling shape
const ground = Bodies.rectangle(400, 580, 810, 40, { isStatic: true });
Composite.add(engine.world, [starBody, ground]);Best Practices and Troubleshooting
- Self-Intersecting Geometry:
decomp.jsfails if polygons have overlapping lines or complex self-intersections. Ensure external perimeter lines never cross. - Vertex Simplification: Highly detailed paths from
SVGs can create dozens of convex sub-bodies, degrading collision
performance. Simplify SVG paths or reduce points with
Matter.Vertices.hull()or external simplification algorithms prior to body creation. - Winding Order: Matter.js automatically attempts to normalize winding orders (clockwise vs. counter-clockwise), but consistently supplying clockwise perimeter points ensures reliable decomposition.