物理演算エンジン matter.js で簡単な機構を作る

**[matter.js](https://brm.io/matter-js/)** は JavaScript で動く物理演算エンジンです。ブラウザゲームとか作るのに便利です。今回はこれで回転運動を往復運動に変換する **クランク機構** を作ってみようと思います。こんなものができます。

![crank-2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40166/19734257-7a1c-756b-9bc4-5885bb6d58b7.gif)

無限に見てられますね。これを構成する部品は、

* 回転運動する円盤（赤）
* 往復運動するピストン（青）
* 両者を連結させる束縛（白）
* ピストンを内包しているシリンダー（黒）

です。自動車であればエンジンの往復運動→タイヤの回転運動ですが、今回は回転→往復でやります。用途はジャンプ漫画『Dr.STONE』に出てきた水車で動く送風機みたいなのを想像してください。

matter.js はダウンロードして、同じディレクトリの `index.html` で以下のように呼び出すだけで動きます。簡単。

```index.html
<body></body>
<script src='matter.js'></script>
<script src='script.js'></script>
```

script.js にはまず matter.js の雛形を作ります。Engine とか World とか Render がそれぞれ何をしているのかは僕もよく知らないので公式ドキュメントを見ましょう。

```script.js
// aliases
const Engine = Matter.Engine,
    Render = Matter.Render,
    World = Matter.World,
    Constraint = Matter.Constraint,
    Body = Matter.Body,
    Bodies = Matter.Bodies

const engine = Engine.create()
const render = Render.create({
    element: document.body,
    engine: engine,
})

engine.world.gravity.y = 0 // 今回は重力を使わない

/* ここにコードを追加していく */

Engine.run(engine)
Render.run(render)

```

真っ黒な画面が表示されます。次に円盤を作ります。

```js
const disk = Bodies.circle(200, 200, 50)
World.add(engine.world, [disk])
```

円盤を固定する束縛 Constraint を設定します。束縛は物体と空間、または物体同士を一定距離で固定することができます。今回は円盤の中心を空間に固定します。

```js
const anchor = Constraint.create({
    pointA: {x: 0, y: 0}, // 円盤の中心
    bodyA: disk,
    pointB: {x: 200, y: 200}, // 空間のこの位置に固定
    length: 0,
})
World.add(engine.world, anchor)
```

冒頭のGIFでは回転が見やすいようにホクロをつけていますが、コード上では省略しています。
次にピストンを作ります。これはただの長方形です。

```js
const piston = Bodies.rectangle(500, 200, 100, 50)
World.add(engine.world, piston)
```

円盤とピストンの間に、同様の束縛を設定します。

```js
const diskPistonJoint = Constraint.create( {
    pointA: {x: 30, y: 0}, // 円盤の中心から少しずらす
    bodyA: disk,
    pointB: {x: -40, y: 0},
    bodyB: piston,
    length: 230,
})
World.add(engine.world, diskPistonJoint)

```

最後にシリンダーを作ります。3個の長方形を組み合わせてひとつの物体にします。こちらは完全に動かないので `isStatic: true` フラグを立てて固定します。

```js
const cylinder = Body.create( {
    parts: [
        Bodies.rectangle(450, 170, 300, 10), // 上壁
        Bodies.rectangle(450, 230, 300, 10), // 下壁
        Bodies.rectangle(605, 200, 10, 70), // 右壁
    ],
    isStatic: true, // 動かない
})
World.add(engine.world, cylinder)
```

できました。あとは円盤を回すだけです。物体に速度を与える時は以下のようにします。

```js
Body.setAngularVelocity(disk, 0.1)
```

ただ matter.js は空気抵抗や摩擦があるので、放っておくとすぐに回転が止まってしまいます。今回は `setInterval` で0.1秒ごとに回転速度を再設定します。（もう少しスマートな方法がある気がするけど調べていない）

```js
setInterval( () => {
    Body.setAngularVelocity(disk, 0.1)
}, 100)
```

できあがり。機構としてはこれで完成です。

![_SS 2021-03-21 23.58.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40166/893e97bb-9f22-e76b-15ca-0eef3d2292fe.png)

最終的なコードはこのようになります。

```script.js
// aliases
const Engine = Matter.Engine,
    Render = Matter.Render,
    World = Matter.World,
    Constraint = Matter.Constraint,
    Body = Matter.Body,
    Bodies = Matter.Bodies

const engine = Engine.create()
const render = Render.create({
    element: document.body,
    engine: engine,
})

engine.world.gravity.y = 0 // 今回は重力を使わない

// 円盤を追加
const disk = Bodies.circle(200, 200, 50)
World.add(engine.world, disk)

// 円盤の中心を空間に固定
const anchor = Constraint.create({
    pointA: {x: 0, y: 0}, // 円盤の中心
    bodyA: disk,
    pointB: {x: 200, y: 200}, // 空間のこの位置に固定
    length: 0,
})
World.add(engine.world, anchor)

// ピストンを追加
const piston = Bodies.rectangle(500, 200, 100, 50)
World.add(engine.world, piston)

// ピストンと円盤の間に束縛を追加
const diskPistonJoint = Constraint.create( {
    pointA: {x: 30, y: 0}, // 中心から少しずらす
    bodyA: disk,
    pointB: {x: -40, y: 0},
    bodyB: piston,
    length: 230,
})
World.add(engine.world, diskPistonJoint)

// シリンダーを追加
const cylinder = Body.create( {
    parts: [
        Bodies.rectangle(450, 170, 300, 10),
        Bodies.rectangle(450, 230, 300, 10),
        Bodies.rectangle(605, 200, 10, 70),
    ],
    isStatic: true,
})
World.add(engine.world, cylinder)

// 円盤を回転
setInterval( () => {
    Body.setAngularVelocity(disk, 0.1)
}, 100)

Engine.run(engine)
Render.run(render)
```

色をつけたいときは、まず render の部分を以下のように変更します。

```js
const render = Render.create({
    element: document.body,
    engine: engine,
    options: {
        wireframes: false,
        background: '#99cdee',
    }
})
```

![_SS 2021-03-22 0.03.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40166/5a0bcd8e-bd6b-48a5-96cc-0b7e31382913.png)

あとは下例のように各物体に `render` オプションをつけて、好きな色を設定していきましょう。

```js
const disk = Bodies.circle(200, 200, 50, {
    render: {
        fillStyle: '#dd4444',
        strokeStyle: '#222222',
        lineWidth: 3,
    }
})
```

以上です。よき物理生活を。