Matter.jsを使ってテキストを物理演算させる

![03.gif](https://qiita-image-store.s3.amazonaws.com/0/117549/ff98f4d5-220f-5a80-2bd7-78d869675da8.gif)

四角や丸などの図形を演算処理するサンプルはいくつかライブラリがありましたが、テキストを使ったサンプルが見つからなかったため、リサーチして備忘録として投稿します。ライブラリの選定ですが、物理演算のライブラリはいくつかありましたが、軽量かつスマートフォンにも対応している**Matter.js**を使っています。

## Custom Render
Matter.jsは図形表示用にデフォルトのレンダーを使っています
。このレンダーは`World`に物理演算対象のBodyオブジェクトを追加すると、あとは自動的にBodyを演算処理に沿って表示してくれます。ただし、このデフォルトのレンダーはテキスト表示には対応していません。代わりにCustom Renderを使うように指示されています。Custom Renderとは、演算処理されたBody情報を使ってCanvas内に自前でBodyを描画する方法のようです。

通常のRenderは以下の感じに使います。

```js:通常のレンダー
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)
```

このRenderの代わりに自前で描画する。`Matter.Composite.allBodies(this.engine.world)`で取得する`bodies`には物理計算された結果 Body情報(座標情報など)が入っています。

```js:CustomRender
this.render()

render () {
		// NOTE: Worldに追加した全てのBody要素を取得
		const bodies = Matter.Composite.allBodies(this.engine.world)
		const canvas = document.getElementById('canvas')
		const context = canvas.getContext('2d')

		window.requestAnimationFrame(this.render)

		// NOTE: 一つのBody要素に入っている点座標(rectangleであれば4頂点情報)をつなげて
		//			 四角を描画する
		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()
	}    
}
```

## テキスト表示
Bodyを作る際に、text情報をoptionとして登録しておき、Custom Renderを使ってCanvas上にレンダリングする際にBody内のtext情報を使ってテキスト表示を行います。

```js:テキスト付きのBodyを作成
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)
```
```js: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()
}

```

![01.gif](https://qiita-image-store.s3.amazonaws.com/0/117549/3cc53519-db5a-a23b-c224-e244b5c48386.gif)

これで物理演算されたテキスト表示が可能だが、この状態だとテキスト表示のコンテナだけが物理演算された表示に
なってしまい、テキスト自体は回転しません。そこで、Bodyを描画するときの頂点情報を元に、Bodyの傾きを算出して、テキストを回転させる。2点の座標がわかれば`ata2`を使って角度を算出できる

```js:CustomRenderに追加
context.save()
context.translate(part.position.x, part.position.y)

// NOTE: テキストを回転させる
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)

```

![02.gif](https://qiita-image-store.s3.amazonaws.com/0/117549/ba5ec872-fcca-6cc6-5430-c46a02855951.gif)


## 回転をロック
物理演算をさせる対象を選択し、演算に回転などの制約を加えることができます。回転の制約を加える場合は、物理演算される前に呼ばれるイベントを登録し、コールバック内で回転速度やConstraintなど設定・制御を行うと良いようです。

```js:回転させない制約
const Events = Matter.Events
Events.on(this.engine, 'beforeUpdate', this.matterBeforeUpdate)

matterBeforeUpdate (event) {
	// NOTE: 座標が更新される前に各ボディを回転させないように設定させる
	// 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)
	}
}
```

![03.gif](https://qiita-image-store.s3.amazonaws.com/0/117549/658e01f7-b761-774d-9428-fff8a5ca43a1.gif)


デモは[GitHub](https://github.com/mitsuyacider/matter-js-test)に置いてあります。