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.