Physics of text using Matter.js

by user

sample

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()
}

sample

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)

sample

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)
    }
}

sample

You can find a demo on GitHub.

You may also like

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.

Close Bitnami banner
Bitnami