So far our particles have been relatively simple shapes - circles, squares, and polygons. But what if we want our particle to be something more complex? What if we wanted it to be in the shape of a letter?
Luckily MatterJS provides a module that helps us create a body from an svg path:
Matter.Svg.pathToVertices(path)
In order to use this module we'll need to import two other libraries: poly-decomp.js and SVGPathSeg polyfill.
1
2
<script src="https://cdn.jsdelivr.net/npm/poly-decomp-es@0.4.2/dist/poly-decomp-es.cjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pathseg@1.2.1/pathseg.min.js"></script>
These libraries allow MatterJS to convert complex shapes (like our letter above) into rigid MatterJS bodies. We'll be using a starter codepen where both of these libraries have already been imported.
Let's get started.
We'll use Figma to create our path. First we'll create a 500x500 frame:
Then we'll use the text tool and add a letter to our frame. It can be any size, but let's make it pretty large - I've made mine 200px.
In order to turn our letter into a path we'll need to flatten it. Right click on the letter to bring up the menu, then select Flatten from the options.
Let's export the SVG so that we can use it in the codepen.
Open the svg in your text editor and just copy the path. That's all we'll need, everything else is in the starter codepen.
In the HTML portion of our starter codepen let's paste the path into our SVG, and give it an id of "svgPath" so we can reference it later.
1
2
3
4
5
6
7
<div class="visHolder">
<svg id="svg" width="" height="" viewBox="">
<rect id="bg" x="" y="" width="" height="" />
<path class="svgPath" d="M54.0039 89.1602H16.9922V69.7266H54.0039C60.4492 69.7266 65.6576 68.6849 69.6289 66.6016C73.6003 64.5182 76.4974 61.6536 78.3203 58.0078C80.2083 54.2969 81.1523 50.0651 81.1523 45.3125C81.1523 40.8203 80.2083 36.6211 78.3203 32.7148C76.4974 28.7435 73.6003 25.5534 69.6289 23.1445C65.6576 20.7357 60.4492 19.5312 54.0039 19.5312H24.5117V142.188H0V0H54.0039C65.0065 0 74.349 1.95312 82.0312 5.85938C89.7786 9.70052 95.6706 15.0391 99.707 21.875C103.743 28.6458 105.762 36.3932 105.762 45.1172C105.762 54.2969 103.743 62.1745 99.707 68.75C95.6706 75.3255 89.7786 80.3711 82.0312 83.8867C74.349 87.4023 65.0065 89.1602 54.0039 89.1602Z" fill="black"/>
</svg>
<div id="rendererHolder"></div>
</div>
I won't go over the usual methods we've used to initialize our SVG and set up the renderer, since we've discussed them in prior lessons. Let's start with building our initParticle method.It's only slightly more complicated that the initParticle methods we've used before.
The first thing we need to do is get the vertices from our path:
1
2
3
4
function initParticle() {
const svgPath = document.querySelector(".svgPath");
const verticies = Matter.Svg.pathToVertices(svgPath);
}
Now we can create our MatterJS body
1
2
3
4
5
6
7
8
function initParticle() {
const svgPath = document.querySelector(".svgPath");
const verticies = Matter.Svg.pathToVertices(svgPath);
svgBody = Matter.Bodies.fromVertices(0, 0, [verticies], {
restitution: 0.6,
isStatic: false});
}
The next thing we need to do is to put our path inside a container so that we can animate it. We'll need to make sure that the path is positioned so that it's center of gravity is at the 0,0 point of the container. For circles and squares, that's easy to do - the center of gravity is just half the width and half the height. For example, with a 100x100 square the center is at 50,50, so we'd need to shift the square by -50, -50.
For irregular, asymetrically shaped objects, it's a bit more difficult - the center of gravity wont be the same as the object's geometric center. For example"
Fortunately our MatterJS body has a bounds.min property which we can use to properly "center" our svg path.
svgBody.bounds.min.x
svgBody.bounds.min.y
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function initParticle() {
const svgPath = document.querySelector(".svgPath");
const verticies = Matter.Svg.pathToVertices(svgPath);
svgBody = Matter.Bodies.fromVertices(0, 0, [verticies], {
restitution: 0.6,
isStatic: false});
}
const minX = svgBody.bounds.min.x;
const minY = svgBody.bounds.min.y;
console.log("minX: ", minX);
console.log("minY: ", minY);
// "minX: " -41.3878444381203
// "minY: " -60.1207653072272
Now that we have our min point (-41, -60) we can use it to position our path inside a group element so that the center of gravity lines up with the 0,0 point of it's parent group.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function initParticle() {
const svgPath = document.querySelector(".svgPath");
const verticies = Matter.Svg.pathToVertices(svgPath);
svgBody = Matter.Bodies.fromVertices(0, 0, [verticies], {
restitution: 0.6,
isStatic: false});
}
const minX = svgBody.bounds.min.x;
const minY = svgBody.bounds.min.y;
console.log("minX: ", minX);
console.log("minY: ", minY);
// "minX: " -41.3878444381203
// "minY: " -60.1207653072272
svgGraphic = document.createElementNS(namespace, "g");
svgGraphic.appendChild(svgPath);
svgPath.setAttribute("transform", `translate(${minX} ${minY})`);
svg.appendChild(svgGraphic);
The rest of the code is basically identical to what we've seen with our other MatterJS animations. We'll add our wall and floor, initialize our world by adding all the bodies to it, and create and call our update method to change the position and rotation of the group containing our path.