p5-matter を使って p5.js での物理演算エンジン（Matter.js）の利用を簡単化する【活用編1】

以下の記事で扱ってきた、 p5.js で物理演算エンジン「Matter.js」を利用する話の続きです。

- [p5.js と物理演算エンジン「Matter.js」の組み合わせをお試し - Qiita](https://qiita.com/youtoy/items/0b16f6fb3a1c76d64b68)
- [p5-matter を使って p5.js での物理演算エンジン（Matter.js）の利用を簡単化する【概要編】 - Qiita](https://qiita.com/youtoy/items/a0e0da2da4c3acf66a7b)

1つ目の記事では、p5.js と Matter.js をそのまま組み合わせて、2つ目の記事ではそれら 2つの併用を簡単化してくれる [p5-matter](http://palmerpaul.com/p5-matter/) について軽く触れました。
今回の記事では p5-matter の情報をさらに見ていきつつ、1つ目の記事で掲載していたプログラムを p5-matter を使ったものに書きかえていきます。

ちなみに、できあがりはこのようなものになります。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/p5js?src=hash&amp;ref_src=twsrc%5Etfw">#p5js</a> と Matter.js を直接組み合わせて、物理演算エンジンに入門をしていた際のサンプル、<br>それを p5-matter を使って書き直してみた！<br><br>p5-matter を使うことでプログラムがかなり短くなりました。<br><br>あと、全く一緒なのもつまらない気がしたので、摩擦係数等のパラメータを一部いじってみたりした。 <a href="https://t.co/tspVL1Nugk">pic.twitter.com/tspVL1Nugk</a></p>&mdash; you (@youtoy) <a href="https://twitter.com/youtoy/status/1436518576412770308?ref_src=twsrc%5Etfw">September 11, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## 元になるプログラムと書きかえ後の内容
### 元のプログラム
[冒頭に掲載した 1つ目の記事](https://qiita.com/youtoy/items/0b16f6fb3a1c76d64b68)の中で、最終的に動かしたプログラムの JavaScript の部分は以下のとおりです。

規模感としては90行ほどです。

```javascript:sketch.js
let Engine = Matter.Engine,
  World = Matter.World,
  Bodies = Matter.Bodies;

let engine;
let world;
let circles = [];
let boundaries = [];

let ground;

function setup() {
  createCanvas(400, 400);
  engine = Engine.create();
  world = engine.world;

  boundaries.push(new Boundary(150, 100, width * 0.6, 20, 0.3));
  boundaries.push(new Boundary(250, 300, width * 0.6, 20, -0.3));
}

function mouseDragged() {
  circles.push(new Circle(mouseX, mouseY, random(5, 10)));
}

function draw() {
  background(180);
  Engine.update(engine);
  for (let i = 0; i < circles.length; i++) {
    circles[i].show();
  }
  for (let i = 0; i < boundaries.length; i++) {
    boundaries[i].show();
  }
}

function Boundary(x, y, w, h, a) {
  let options = {
    friction: 0,
    restitution: 0.95,
    angle: a,
    isStatic: true,
  };
  this.body = Bodies.rectangle(x, y, w, h, options);
  this.w = w;
  this.h = h;
  World.add(world, this.body);
  console.log(this.body);

  this.show = function () {
    let pos = this.body.position;
    let angle = this.body.angle;
    push();
    translate(pos.x, pos.y);
    rotate(angle);
    rectMode(CENTER);
    strokeWeight(1);
    noStroke();
    fill(0, 100, 200);
    rect(0, 0, this.w, this.h);
    pop();
  };
}

function Circle(x, y, r) {
  let options = {
    friction: 0,
    restitution: 0.95,
  };
  this.body = Bodies.circle(x, y, r, options);
  this.r = r;
  World.add(world, this.body);

  this.show = function () {
    let pos = this.body.position;
    let angle = this.body.angle;
    push();
    translate(pos.x, pos.y);
    rotate(angle);
    rectMode(CENTER);
    strokeWeight(1);
    stroke(255);
    fill(0, 0, 80);
    ellipse(0, 0, this.r * 2);
    pop();
  };
}
```

### 書きかえ後のプログラム
今回、書きかえを行った後のプログラムは以下のとおりです。
規模感的にはおおよそ 60行ほどで、先ほどのものが三分の一くらいになりました。

```javascript:sketch.js
let boundaries = [];
let circles = [];

function setup() {
  createCanvas(400, 400);

  matter.init();
  boundaries.push(
    matter.makeBarrier(150, 100, width * 0.6, 20, {
      angle: radians(20),
      friction: 0.02,
      restitution: 0.95,
    })
  );
  boundaries.push(
    matter.makeBarrier(250, 300, width * 0.6, 20, {
      angle: radians(-20),
      friction: 0.8,
      restitution: 0.95,
    })
  );
}

function draw() {
  background(180);

  push();
  strokeWeight(1);
  noStroke();
  fill(0, 100, 200);
  for (let i = 0; i < boundaries.length; i++) {
    boundaries[i].show();
  }
  pop();
  
  push();
  strokeWeight(1);
  stroke(255);
  fill(0, 0, 80);
  for (let i = circles.length - 1; i >= 0; i--) {
    let b = circles[i];
    b.show();
    if (b.isOffCanvas()) {
      matter.forget(b);
      circles.splice(i, 1);
    }
  }
  pop();
}

function mouseDragged() {
  circles.push(
    matter.makeBall(mouseX, mouseY, random(10, 20), {
      friction: 0.01,
      restitution: 0.95,
    })
  );
}
```

`boundaries.push` の部分をもう少しすっきりさせれば、もう少し短くなるかも。

### 書きかえの際に参考にしたプログラム
今回の書きかえをする際に参考にしたのは、以下の公式ページのサンプル「Tilted Platform」です。

> Tilted Platform - Roll balls down a series of tilted platforms with realistic collisions by clicking your mouse.
[See it live!](http://palmerpaul.com/p5-matter/examples/tilted-platform/) — [View source code.](https://github.com/pzp1997/p5-matter/blob/master/examples/tilted-platform/tilted-platform.js)

この後、プログラムの書きかえ前後についての補足などを書いていきます。

## プログラムの書きかえに関する補足
### 書きかえ前のおおまかな仕組み
書きかえ前のプログラムでは、Matter.js用の以下の変数を用意したり、 `engine = Engine.create();` といった処理を書く必要がありました。

```javascript
let Engine = Matter.Engine,
  World = Matter.World,
  Bodies = Matter.Bodies;
```

また、以下のように円や傾いた床のような部分は、それを生成するための関数を別に用意したほうが良さそうなくらいの記述量があるようでした。

```javascript
function Boundary(x, y, w, h, a) {
  let options = {
    friction: 0,
    restitution: 0.95,
    angle: a,
    isStatic: true,
  };
  this.body = Bodies.rectangle(x, y, w, h, options);
  ...
```

```javascript
function Circle(x, y, r) {
  let options = {
    friction: 0,
    restitution: 0.95,
  };
  this.body = Bodies.circle(x, y, r, options);
  ...
```

それを使う部分は、以下が該当します。

```javascript
function setup() {
  ...

  boundaries.push(new Boundary(150, 100, width * 0.6, 20, 0.3));
  boundaries.push(new Boundary(250, 300, width * 0.6, 20, -0.3));
}

function mouseDragged() {
  circles.push(new Circle(mouseX, mouseY, random(5, 10)));
}
```

そして、 `draw()` の中は、書きかえ前後で処理の流れ的には大幅な変更はない感じになりました。
（書きかえ後で、描画する際の色や枠の線の指定がこの中に入ってきたのはありますが）

### 上記の仕組みの中の各処理を変更する
#### 初期化処理的な部分
書きかえ後のほうでは、初期化処理等は基本的に以下のみです。
これは参考にした公式サンプルに書いてあったので追加してみました。

```javascript
matter.init();
```

こちらは、念のためドキュメントの以下の説明も見てみたのですが「書くのを忘れても大きな問題にならないもの」らしいです。
どうやら、説明を読むと「呼び出すのをオススメするけど、忘れてしまっていた場合は、何かのメソッドが呼ばれた際に呼び出しがされる」という感じのようです。

●p5-matter 1.0.0 | Documentation
　http://palmerpaul.com/p5-matter/docs/#matterinit
![matter.init()](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/6223d73c-7a83-6ffa-72fa-cca63d48a958.jpeg)

#### 円や傾いた床の呼び出し
書きかえ前の円や傾いた床の呼び出しは上で書いていたとおりでした。

これが書きかえ後では、以下の `matter.makeBall` や `matter.makeBarrier` を呼び出すだけです。
書きかえ前の `function Boundary(x, y, w, h, a)` ・ `function Circle(x, y, r)` で書いていた処理は、ライブラリ内でうまく処理をしてくれているようです。

```javascript
  circles.push(
    matter.makeBall(mouseX, mouseY, random(10, 20), {
      friction: 0.01,
      restitution: 0.95,
    })
  );
```

```javascript
  boundaries.push(
    matter.makeBarrier(250, 300, width * 0.6, 20, {
      angle: radians(-20),
      friction: 0.8,
      restitution: 0.95,
    })
  );
```

この時、各パラメータは　4番目の引数として連想配列で渡してやる必要があるようです（Matter.js と同じ形です）。
それと、パラメータとして指定できるものは以下の Matter.js の Body のプロパティに関する APIドキュメントを見れば良さそうでした。
　[Body - Matter.js Physics Engine API Docs - matter-js 0.17.0](https://brm.io/matter-js/docs/classes/Body.html#properties)

それと、これらを描画される際の処理で `show()`　を使っています。
書きかえ前はこの部分の中身を実装していた形でしたが、書きかえ後はライブラリ内で中身をうまく隠蔽してくれている形になるようです。

#### 円の削除
上で `draw()` の中の処理の流れは大幅な変更はない、と書いていたものの、書きかえ前になかった処理が加わっています。
それは、以下の `forget()` と `splice()` の部分です。

```javascript
  for (let i = circles.length - 1; i >= 0; i--) {
    let b = circles[i];
    b.show();
    if (b.isOffCanvas()) {
      matter.forget(b);
      circles.splice(i, 1);
    }
  }
```

これを入れておかないと、画面外に出て行った円が配列に残ったままになっていました。
これを書いていて思ったのですが、「実は書きかえ前のものもこれに該当する処理が必要では？」という感じがします。

ここで出てくる p5-matter絡みの処理について、公式リファレンスの記載を掲載しておきます。

![isOffCanvas](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/26b6f5c1-bd18-575f-d338-feefceb9bf3b.jpeg)

![forget](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/29661c7f-4501-c53f-f7b9-21d946de5d39.jpeg)

## まとめ
今回、 p5.js と Matter.js を直接組み合わせる形のプログラムを、p5-matter を使って書きかえてみました。
その結果、描画まわりの処理など、ある程度は自前で実装する必要があった部分が、ライブラリ内での処理になるのでソースコードの内容がすっきりしました。

この後も、引き続き以下の公式ドキュメントなどを見つつ、活用法を探っていければと思います。

●p5-matter 1.0.0 | Documentation
　http://palmerpaul.com/p5-matter/docs/

## 【追記】 MQTT との組み合わせ
以前、p5.js とリアルタイム通信の MQTT を組み合わせて、異なる 2つの異なるデバイスの画面が仮想的につながる、というものを試作してみていました。
その試作に、今回の p5-matter と Matter.js を使った物理演算を組み合わせてみました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">以前、 <a href="https://twitter.com/hashtag/p5js?src=hash&amp;ref_src=twsrc%5Etfw">#p5js</a> とリアルタイム通信（MQTT）を使って、異なる 2つのデバイスの画面を仮想的につなげてた件の続き、<br>最近手を出し始めた物理演算エンジン（p5-matter ＋ Matter.js）を組み込んでみました！<br><br>2台の iPad の画面間でボールが行き来するのですが、2つの画面で物理演算エンジンが働いてます。 <a href="https://t.co/vmdvhIBzwM">pic.twitter.com/vmdvhIBzwM</a></p>&mdash; you (@youtoy) <a href="https://twitter.com/youtoy/status/1436663697435291648?ref_src=twsrc%5Etfw">September 11, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>