How to Convert Open SVG Paths to Matter.js Bodies
Converting non-closed SVG paths into Matter.js rigid bodies requires transforming zero-area open lines into collidable geometry, as physics engines require defined mass and volume to calculate collisions. This article explains why open paths fail in standard conversion pipelines and outlines three practical solutions: expanding strokes into polygonal outlines, auto-closing the path geometry, and breaking open paths into chained compound bodies.
Why Non-Closed SVG Paths Fail in Matter.js
Matter.js relies on closed, non-intersecting polygons to calculate
mass, center of mass, inertia, and collision bounds. When using
utilities like Matter.Svg.pathToVertices(), the engine
samples points along the vector path to generate a vertex array, which
is then passed to Matter.Bodies.fromVertices().
If an SVG path is open (such as a simple line, an arc, or an unclosed
bezier curve), the resulting vertex array does not form a complete
boundary. Passing an open set of points to the vertex decomposition
algorithm (such as poly-decomp.js, which Matter.js uses
under the hood) causes the triangulation to fail, producing inverted
polygons, missing collision bounds, or completely collapsed bodies with
zero mass.
Method 1: Stroke Expansion (Recommended for Thick Lines)
The most reliable way to convert an open path with visible thickness into a solid body is to convert its stroke into a closed boundary polygon before passing it to Matter.js.
- Calculate the Stroke Offset: Instead of using the
raw path centerline, offset the path outward by half the stroke width on
both sides (
+strokeWidth / 2and-strokeWidth / 2). - Cap the Ends: Connect the parallel edges at the endpoints using either a flat cap (butt) or a series of rounded points (round cap).
- Generate a Closed Polygon: Join the offset edges into a continuous, clockwise-oriented loop.
You can automate this in JavaScript using vector libraries such as
Paper.js (path.strokeBounds or path expansion tools) or
ClipperLib. Once the path has an enclosed area,
Matter.Bodies.fromVertices() will accurately generate the
physical shape.
Method 2: Segment Chaining via Compound Bodies
If you are dealing with thin walls, terrain, or open tracks where stroke expansion creates unnecessary vertices, you can decompose the open path into a chain of overlapping convex rectangles.
- Sample Points Along the Path: Use the SVG DOM
method
SVGPathElement.getPointAtLength()to sample points at regular intervals along the open path. - Generate Segment Bodies: Iterate through the
sampled points. For every pair of adjacent points \((P_1, P_2)\):
- Calculate the distance \(d\) between them: \(\sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2}\).
- Calculate the angle: \(\text{atan2}(y_2 - y_1, x_2 - x_1)\).
- Create a thin rectangle using
Matter.Bodies.rectangle(midX, midY, d, thickness, { angle }).
- Group into a Compound Body: Assemble all segment
rectangles into a single rigid structure using
Matter.Body.create({ parts: [rect1, rect2, ...] }).
This approach ensures smooth collisions without requiring third-party vector-offsetting algorithms, making it ideal for procedural tracks and irregular static boundaries.
Method 3: Direct Path Closure (For Quasi-Closed Shapes)
If the SVG path was intended to be a solid shape but simply omitted
the terminal Z or z segment in its
d attribute, you can manually force closure:
- Append the Close Command: Modify the SVG string
directly by appending a
Zto the end of thedattribute before parsing it (dAttribute + " Z"). - Verify Non-Self-Intersection: Ensure that drawing a
straight line from the end vertex back to the start vertex does not
cross over existing path segments. Self-intersecting polygons will cause
vertex decomposition errors in
poly-decomp.js.
Handling Concavity and Polygon Decomposition
Regardless of the method used, the resulting closed vertex list will
almost certainly be concave. Matter.js cannot handle concave shapes
natively and requires the poly-decomp library to break
shapes down into convex hulls.
Ensure the decomposition library is registered to your environment before creating bodies:
// Provide poly-decomp to Matter.js
Matter.Common.setDecomp(require('poly-decomp'));
// Generate the rigid body from the closed vertices
const body = Matter.Bodies.fromVertices(x, y, vertexSets, {
isStatic: true
});By ensuring every open path is either expanded into a boundary with area, chained into discrete segments, or cleanly closed with a terminal segment, you avoid degenerate physics states and achieve consistent collision detection.