Physics of text using Matter.js

Matter.js is one of the libraries for physics

Final Image

Background

There were several libraries for arithmetic processing of squares, circles, and other shapes, but I couldn’t find a sample with text, so I’m going to research and post it as a reminder. As for the library selection, there were several libraries for physical operations, but I’m using Matter.js, which is lightweight and smartphone-compatible.

Custom Render

Matter.js uses a default render for displaying shapes. If you add a Body object to the **world**, this render will automatically display the body along with the physics operations. However, this default render does not support text display. Instead, you are instructed to use Custom Render, which seems to be a way to draw the body in the Canvas by itself using the calculated body information.

Normally the render is used as follows.

const Engine = Matter.Engine
const Render = Matter.Render
const engine = Engine.create()
const render = Render.create({
element: document.getElementById('app'),
engine: engine,
options: {
wireframes: false,
width: 300,
height: 400,
background: 'rgba(255, 0, 0, 0.5)'
}
})
Render.run(render)

Instead of this Render, we draw it ourselves. The bodies we get in `Matter.Composite.allBodies(this.engine.world)` contains the Body information (such as coordinate information) as a result of the physics calculation.

this.render()render () {
// NOTE: Retrieve all Body elements added to the World
const bodies = Matter.Composite.allBodies(this.engine.world)
const canvas = document.getElementById('canvas')
const context = canvas.getContext('2d')
window.requestAnimationFrame(this.render)
// NOTE: Draw a square by connecting the point coordinates
// (or 4 vertices in the case of a rectangle) in a body element.
for (let i = 0; i < bodies.length; i += 1) {
const part = bodies[i]
const vertices = bodies[i].vertices
context.moveTo(vertices[0].x, vertices[0].y)

for (var j = 1; j < vertices.length; j += 1) {
context.lineTo(vertices[j].x, vertices[j].y)
}
context.lineTo(vertices[0].x, vertices[0].y)
}
context.lineWidth = 1.5
context.strokeStyle = '#000000'
context.stroke()
}

Display Text

When creating the body, text information is registered as an option, and the text information in the body is used to display the text when rendering in Canvas using Custom Render.

Create a Body with text

const Bodies = Matter.Bodies;
const World = Matter.World;
const x = Math.random() * screen.width * 2;
const y = 0;
const wordBody = Bodies.rectangle(x, y, 200, 100, {
restitution: 0.95,
friction: 0,
render: {
fillStyle: "#FFFFFF",
text: {
fillStyle: "#000000",
content: content,
size: 50,
},
},
});
World.add(this.engine.world, wordBody);

Drawing text in CustomRender

render () {
var bodies = Matter.Composite.allBodies(this.engine.world)
var canvas = document.getElementById('canvas')
var context = canvas.getContext('2d')

window.requestAnimationFrame(this.render)
context.fillStyle = '#FFFFFF'
context.fillRect(0, 0, canvas.width, canvas.height)
context.globalAlpha = 1
context.beginPath()

for (var i = 0; i < bodies.length; i += 1) {
var part = bodies[i]
if (part.render.text) {
var fontsize = 30
var fontfamily = part.render.text.family || 'Arial'
var color = part.render.text.color || '#FF0000'
if (part.render.text.size) {
fontsize = part.render.text.size
} else if (part.circleRadius) {
fontsize = part.circleRadius / 2
}
var content = ''

if (typeof part.render.text === 'string') {
content = part.render.text
} else if (part.render.text.content) {
content = part.render.text.content
}
context.fillStyle = 'black'
context.save()
context.translate(part.position.x, part.position.y)
context.textBaseline = 'middle'
context.textAlign = 'center'
context.fillStyle = color
context.font = fontsize + 'px ' + fontfamily
context.fillText(content, 0, 0)
context.restore()
context.fillStyle = 'blue'
context.fillRect(part.position.x, part.position.y, 10, 10)
}
var vertices = bodies[i].vertices
context.moveTo(vertices[0].x, vertices[0].y)

for (var j = 1; j < vertices.length; j += 1) {
context.lineTo(vertices[j].x, vertices[j].y)
}

context.lineTo(vertices[0].x, vertices[0].y)
}
context.lineWidth = 1.5
context.strokeStyle = '#000000'
context.stroke()
}

Now we can display the text in a physics-operated text display, but in this state, only the text display container will be in a physics-operated display, and the text itself will not be rotated. If we know the coordinates of two points, we can use ata2 to calculate the angle of the body.

context.save();
context.translate(part.position.x, part.position.y);
// NOTE: Rotate text
const x = bodies[i].vertices[1].x - bodies[i].vertices[0].x;
const y = bodies[i].vertices[1].y - bodies[i].vertices[0].y;
const radian = Math.atan2(y, x);
context.rotate(radian);

Lock the rotation

You can select the target of physical operations and add rotation and other constraints to the operations. If you want to add rotation constraints, it seems to be a good idea to register the events that will be called before the physical operations are performed and set and control the rotation speed and constraint in the callback.

const Events = Matter.Events
Events.on(this.engine, 'beforeUpdate', this.matterBeforeUpdate)
matterBeforeUpdate (event) {
// NOTE: Set each body to not rotate before the coordinates are
updated
// http://brm.io/matter-js/docs/classes/Body.html#method_setAngularVelocity
const Body = Matter.Body for (var i = 0; i < this.wordBodyList.length; i++) {
const wordBody = this.wordBodyList[i]
Body.setAngularVelocity(wordBody, 0)
}
}

You can find a demo on GitHub.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store