Matter.js

Created: Introductory Matter.js notes

Updated: 03 September 2023

Some notes on using Matter.js based on The Coding Train YouTube Videos

Physics Engine Overview

Playlist

At a high level to move things around we make use of basic physics concepts using vector math, collisions, etc. In order to do this we can make use of pre-implemented things like a physics engine in order to make things move around and interact

In general when using a physics engine we have some of the following steps:

  1. Setup allows us to define the inital state of the world
  2. We then tell the physics engine to start
  3. The state we defined will iterate over a render loop updating the different properties of the objects in our world

Physics Engines usually have some common concepts:

  • The World which is a defined space with specific rules
  • A Body which is an entity in that world
  • A Connection which states how different bodies are related to one another

Matter.js

Matter.js is a 2D rigd body physics engine that runs in the browser. The general Getting Started Buide can be found on GitHub

Matter.js has a built-in render and runner for simulations that does very basic rendering for the purpose of debugging. For our purpose we’ll use p5.js for rendering

Setup the Application

To get started with the Library we should initilize a new package and add the matter.js library to it

1
mkdir matter-js
2
cd matter-ms
3
yarn init -y
4
yarn add matter-js p5

Thereafter we need to create a few files for our code:

  1. index.html which will just reference the relevant files
  2. index.js which is where we will implement our javascript

In our index.html file we’ll include the following to reference matter-js and our own index.js file:

index.html

1
<!DOCTYPE html>
2
<html lang="en">
3
<head>
4
<meta charset="UTF-8" />
5
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
<title>Matter.js</title>
7
</head>
8
9
<body>
10
<script src="./node_modules/matter-js/build/matter.js"></script>
11
<script src="./node_modules/p5/lib/p5.js"></script>
12
<script src="./index.js"></script>
13
</body>
14
</html>

Within our page context the Matter variable should allow us to access the functionality of the library

Basic Setup

As a basic setup to our page we can then create an Engine and Renderer instance using Matter in a setup function as well as define the different bodies and add them to the World

index.js

1
// Render = Matter.Render,
2
;(World = Matter.World), (Bodies = Matter.Bodies)
3
4
let engine
5
let render
6
let body
7
let ground
8
9
function setup() {
10
createCanvas(400, 400)
11
12
engine = Engine.create()
13
14
// shape is defined its centroid and dimensions
15
body = Bodies.rectangle(200, 200, 80, 80)
16
ground = Bodies.rectangle(200, 375, 400, 50, {
17
isStatic: true,
18
})
19
20
World.add(engine.world, [body, ground])
21
Engine.run(engine)
22
}

You can also see the call to createCanvas which is what processing will use to create the canvas for the site

Rendering

Now that the engine is running, we can start looking at how to render the specific objects as they move through our canvas

Each of the respective bodies we create have some basic properties that we can use to render the body. In the case of Matter.Bodies we have a vertices property. We can use p5 to render the vertices as follows:

1
const renderFromVertices = (vertices) => {
2
beginShape()
3
4
vertices.forEach((point) => {
5
vertex(point.x, point.y)
6
})
7
8
endShape(CLOSE)
9
}

We can then implement this in a draw function which p5 will call and pass it the relevant shapes we want to be drawn like so:

index.js

1
function draw() {
2
background(51)
3
4
const renderBody = (body) => {
5
if (body.render.visible) {
6
beginShape()
7
8
body.vertices.forEach((point) => {
9
vertex(point.x, point.y)
10
})
11
12
endShape(CLOSE)
13
}
14
}
15
16
renderBody(body)
17
renderBody(ground)
18
}

Taking into consideration the render properties of the body which we can assign from the Matter.Bodies instances, we can see that each body has render.fillStyle, render.strokeStyle, render.opacity, and a render.visible property

We can update our render function to make use of these properties like so:

1
const renderBody = ({ render, vertices }) => {
2
if (render.visible) {
3
beginShape()
4
5
opacity = render.opacity * 255
6
7
fillColour = color(render.fillStyle)
8
fillColour.setAlpha(opacity)
9
10
fill(fillColour)
11
12
strokeColour = color(render.strokeStyle)
13
strokeColour.setAlpha(opacity)
14
15
stroke(render.strokeStyle)
16
strokeWeight(render.lineWidth)
17
18
vertices.forEach((point) => {
19
vertex(point.x, point.y)
20
})
21
22
endShape(CLOSE)
23
}
24
}

Next, we can simply always render all the bodies in our world like so:

1
engine.world.bodies.forEach(renderBody)

So using all of the above, our draw function becomes:

1
function draw() {
2
background(51)
3
4
const renderBody = ({ render, vertices }) => {
5
if (render.visible) {
6
beginShape()
7
8
opacity = render.opacity * 255
9
10
fillColour = color(render.fillStyle)
11
fillColour.setAlpha(opacity)
12
13
fill(fillColour)
14
15
strokeColour = color(render.strokeStyle)
16
strokeColour.setAlpha(opacity)
17
18
stroke(render.strokeStyle)
19
strokeWeight(render.lineWidth)
20
21
vertices.forEach((point) => {
22
vertex(point.x, point.y)
23
})
24
25
endShape(CLOSE)
26
}
27
}
28
29
engine.world.bodies.forEach(renderBody)
30
}

While all the above technically works, I think it may just make sense to write a canvas renderer which should work more or less the same. So hopefully I’ll do that doo