Matter.js Self-Intersecting Polygons: Effects and Fixes
Defining a self-intersecting polygon in Matter.js causes severe simulation glitches, including broken collision detection, erroneous mass properties, and failure during convex decomposition. Because Matter.js relies on algorithms that assume simple, non-self-intersecting geometric boundaries, feeding it intersecting vertices violates its mathematical preconditions. This article details the specific physics engine breakdowns that occur with self-intersecting paths and explains how to properly handle complex geometry in your simulations.
Broken Mass and Inertia Calculations
Matter.js calculates a body's physical properties—such as area, center of mass, and moment of inertia—using Green's theorem, which integrates over the boundary formed by the vertices. This calculation assumes that the vertex loop defines a clear interior and exterior with consistent winding (clockwise or counter-clockwise).
When edges intersect, parts of the polygon have opposing winding directions relative to each other. As a result:
- Subtracted Area: The signed areas of opposing loops cancel each other out, leading to an artificially tiny, zero, or negative calculated area and mass.
- Displaced Center of Mass: The calculated center of gravity shifts unpredictably, often landing outside the visible bounds of the shape.
- Erratic Rotation: Because the moment of inertia depends on proper mass distribution, the body may spin with unnatural acceleration or resist rotation completely.
Collision Failures with the Separating Axis Theorem
Matter.js relies on the Separating Axis Theorem (SAT) to detect
collisions between rigid bodies. SAT strictly requires convex shapes.
When an arbitrary polygon is provided directly via
Matter.Body.create, the engine treats the vertices as a
single hull.
With a self-intersecting polygon:
- Invalid Collision Normals: SAT generates surface normals along each defined edge. An edge cutting through the interior creates normals pointing inward, confusing the collision solver.
- Physics Explosions: When another body touches an inward-pointing edge, the overlap depth calculation becomes inverted. The solver attempts to resolve penetration by applying massive separation impulses, launching bodies across the screen at extreme velocities.
- Tunneling and Ghost Collisions: Objects may pass straight through visible areas or collide with empty space near intersection points.
Failure in
Bodies.fromVertices and Poly-Decomp
To handle non-convex shapes, Matter.js provides
Matter.Bodies.fromVertices(), which integrates with the
external library poly-decomp.js to automatically slice
concave polygons into a set of convex parts.
poly-decomp.js requires simple polygons that do not
cross over themselves. When passed a self-intersecting set of
vertices:
- Silent Failures or Dropped Parts: The decomposition algorithm often fails to resolve valid diagonals, leading to missing chunks of the object or skipped decomposition entirely.
- Inverted Child Bodies: Decomposed parts may generate with backwards vertex orders, which Matter.js either rejects or processes as inside-out colliders.
- Infinite Loops or Crashes: In some complex configurations, the triangulation or decomposition routines can enter infinite recursion or throw runtime errors, freezing the simulation thread.
Visual Artifacts in the Default Renderer
The built-in Matter.Render module renders vertices using
the HTML5 Canvas 2D API's path drawing methods (moveTo and
lineTo). By default, canvas rendering uses the non-zero
winding rule to fill paths.
A self-intersecting polygon creates overlapping sub-regions where the fill rule produces visual inconsistencies:
- Overlapping sections may appear filled or hollow depending on their winding direction.
- The visible shape on screen will not match the collision hull computed by the physics engine, leading to a visual disconnect where external bodies bounce off invisible boundaries or sink deep into visible geometry.
How to Properly Handle Self-Intersecting Polygons
To use shapes with intersecting edges in Matter.js, you must preprocess the geometry before instantiating the physics body:
- Polygon Boolean Operations: Run the vertex set
through a polygon-clipping library (such as
polyclip-tsormartinez-polygon-clipping) to perform a self-union operation. This resolves self-intersections and extracts a clean, non-overlapping boundary. - Manual Decomposition: Manually break down the
complex shape into multiple simple, non-intersecting polygons, create a
separate body for each, and combine them using
Matter.Body.create({ parts: [...] }). - Ensure Consistent Winding: Always ensure the final
vertex arrays are wound in a consistent direction (clockwise or
counter-clockwise) without crossing lines before passing them to
Bodies.fromVertices.