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-js

Then 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:

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