How to Slice and Sever Bodies in Matter.js
Slicing rigid bodies into severed polygons is a popular mechanic in
2D physics games, but Matter.js does not feature a native
geometry-cutting API. To sever a body, you must mathematically intersect
a cutting line with the body's polygon edges, split the original vertex
set across the cut line, insert the intersection points to close the new
shapes, and instantiate fresh bodies using
Matter.Bodies.fromVertices. This guide walks through the
step-by-step process of calculating these severed vertex arrays and
converting them back into active Matter.js physics objects.
Step 1: Define the Slicing Line
A cut is defined by two points in 2D space: a start point \(A(x_1, y_1)\) and an end point \(B(x_2, y_2)\). The line equation or the cross product can determine which side of the line any given vertex falls on:
\[\text{side}(P) = (B.x - A.x) \times (P.y - A.y) - (B.y - A.y) \times (P.x - A.x)\]
- If \(\text{side}(P) > 0\), the vertex lies on one side.
- If \(\text{side}(P) < 0\), it lies on the opposing side.
- If \(\text{side}(P) = 0\), the vertex lies precisely on the line.
Step 2: Detect Edge Intersections
Iterate through the target body's vertices in world coordinates
(body.vertices). Check each edge formed by vertex pair
\((V_i, V_{i+1})\) for an intersection
with line segment \(AB\):
function getLineIntersection(p1, p2, p3, p4) {
const denominator = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);
if (denominator === 0) return null; // Lines are parallel
const ua = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denominator;
const ub = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denominator;
// Check if the intersection falls within both segments
if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
return {
x: p1.x + ua * (p2.x - p1.x),
y: p1.y + ua * (p2.y - p1.y)
};
}
return null;
}A clean cut across a convex polygon produces exactly two intersection points.
Step 3: Reconstruct Two Vertex Lists
To rebuild the severed geometry into two distinct shapes, traverse the original polygon's perimeter. Sort the vertices into two lists, inserting the newly computed intersection points where the cut occurs:
function splitPolygon(vertices, cutStart, cutEnd) {
const polyA = [];
const polyB = [];
for (let i = 0; i < vertices.length; i++) {
const current = vertices[i];
const next = vertices[(i + 1) % vertices.length];
const sideA = (cutEnd.x - cutStart.x) * (current.y - cutStart.y) -
(cutEnd.y - cutStart.y) * (current.x - cutStart.x);
if (sideA >= 0) {
polyA.push(current);
} else {
polyB.push(current);
}
const intersection = getLineIntersection(current, next, cutStart, cutEnd);
if (intersection) {
polyA.push(intersection);
polyB.push(intersection);
}
}
return [polyA, polyB];
}Step 4: Ensure Correct Vertex Winding and Convexity
Matter.js expects vertices to follow a clockwise winding order. If the resulting vertex lists are wound counter-clockwise, reverse them before creating bodies.
Furthermore, Matter.js requires convex hulls for physics calculation.
If cutting a body produces non-convex (concave) pieces, ensure the
poly-decomp library is installed and exposed globally to
Matter.js:
// Provide decomp to Matter before creating bodies
window.decomp = require('poly-decomp');Step 5: Replace the Old Body with the Severed Pieces
Compute the center of mass for each new vertex list, generate the new
physics bodies using Bodies.fromVertices, transfer any
momentum or properties from the parent body, and update the engine
world:
function cutBody(world, body, cutStart, cutEnd) {
const [pointsA, pointsB] = splitPolygon(body.vertices, cutStart, cutEnd);
// Minimum 3 vertices required to form a polygon
if (pointsA.length < 3 || pointsB.length < 3) return;
// Calculate approximate center for placement
const centerA = Matter.Vertices.centre(pointsA);
const centerB = Matter.Vertices.centre(pointsB);
const bodyA = Matter.Bodies.fromVertices(centerA.x, centerA.y, [pointsA], {
render: { fillStyle: body.render.fillStyle }
});
const bodyB = Matter.Bodies.fromVertices(centerB.x, centerB.y, [pointsB], {
render: { fillStyle: body.render.fillStyle }
});
if (bodyA && bodyB) {
// Inherit parent linear and angular velocity
Matter.Body.setVelocity(bodyA, body.velocity);
Matter.Body.setVelocity(bodyB, body.velocity);
Matter.Body.setAngularVelocity(bodyA, body.angularVelocity);
Matter.Body.setAngularVelocity(bodyB, body.angularVelocity);
// Swap out old body for new severed bodies
Matter.Composite.remove(world, body);
Matter.Composite.add(world, [bodyA, bodyB]);
}
}This dynamic polygon replacement severs the geometry instantly while preserving kinetic continuity in the simulation.