Decomposing Complex SVG Paths in Matter.js

This guide explains how to convert complex, concave SVG paths into functional rigid bodies in Matter.js using automated convex decomposition. Because 2D physics engines struggle with concave collision detection, complex SVG paths must be converted into coordinate samples and broken down into sets of convex sub-polygons. By integrating the poly-decomp library with Matter.js's native vertex and SVG modules, you can dynamically transform raw SVG path strings into optimized, multi-part compound bodies directly within your simulation.

1. Enable the Convex Decomposition Engine

Matter.js relies on an external library called poly-decomp to execute the Bayazit or Greene decomposition algorithms. You must provide poly-decomp to the window object or inject it directly into Matter.js before creating bodies from non-convex vertices.

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

// Attach poly-decomp to Matter.js
Matter.Common.setDecomp(decomp);

If using browser <script> tags, ensure poly-decomp.js is loaded before matter.js, which allows Matter.js to detect it automatically on window.decomp.

2. Extract Vertices from SVG Path Data

To convert an SVG path string into coordinate data that Matter.js can interpret, dynamically create an SVG path element in memory and pass it to Matter.Svg.pathToVertices.

function parseSVGPathToVertices(pathData, sampleResolution = 15) {
  // Create an in-memory SVG path element
  const pathElement = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  pathElement.setAttribute('d', pathData);

  // Sample points along the SVG path
  // Lower sampleResolution means fewer points and better performance
  return Matter.Svg.pathToVertices(pathElement, sampleResolution);
}

sampleResolution determines the distance interval between sampled points along the curve. Lowering this value creates smoother boundaries at the cost of generating more vertices and increasing collision calculation overhead.

3. Generate the Convex Decomposed Rigid Body

Pass the generated vertex list to Matter.Bodies.fromVertices(). Matter.js uses the injected decomposition library to automatically divide concave vertex loops into a collection of convex collision hulls, binding them into a single compound body.

function createBodyFromSVG(x, y, pathData, options = {}) {
  const vertices = parseSVGPathToVertices(pathData);

  // Automatically decompose concave loops into a compound convex body
  const body = Matter.Bodies.fromVertices(x, y, vertices, options, true);

  return body;
}

Passing true as the fifth argument (flagInternal) marks the internal edges created by the decomposition as non-colliding, preventing neighboring convex parts of the same body from snagging on edges during collisions.

4. Correct for Center of Mass Offsets

When Matter.Bodies.fromVertices calculates a compound body, it computes the center of mass based on the geometry and automatically resets the origin to that point. This shifts the internal body position away from the initial (x, y) target coordinates.

To position the body accurately in relation to your original SVG canvas coordinates:

function createAlignedBodyFromSVG(x, y, pathData, options = {}) {
  const vertices = parseSVGPathToVertices(pathData);
  
  const body = Matter.Bodies.fromVertices(x, y, vertices, options, true);

  // Compute the offset between the geometric bounds and the calculated center of mass
  const bounds = Matter.Bounds.create(vertices);
  const width = bounds.max.x - bounds.min.x;
  const height = bounds.max.y - bounds.min.y;

  // Set the body precisely at the intended coordinates relative to its top-left bound
  Matter.Body.setPosition(body, {
    x: x + width / 2,
    y: y + height / 2
  });

  return body;
}

Complete Dynamic Implementation

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

Matter.Common.setDecomp(decomp);

const { Engine, Render, Runner, Composite, Bodies, Svg } = Matter;

const engine = Engine.create();
const world = engine.world;

const starSVGPath = "M 50,5 L 64,36 L 98,36 L 70,57 L 81,91 L 50,70 L 19,91 L 30,57 L 2,36 L 36,36 Z";

function createDecomposedSVG(pathString, x, y, options = {}) {
  const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  path.setAttribute('d', pathString);
  
  const vertices = Svg.pathToVertices(path, 10);
  
  return Bodies.fromVertices(x, y, vertices, {
    isStatic: false,
    restitution: 0.5,
    ...options
  }, true);
}

// Add decomposed SVG body to the simulation
const dynamicBody = createDecomposedSVG(starSVGPath, 400, 100);
Composite.add(world, dynamicBody);