p5.js と物理演算エンジン「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> と Matter.js を組み合わせて物理演算エンジンに入門してみた話の続き。<br><br>取り急ぎ Handtrack.js を使った実装を追加して、カメラ映像からの手の認識と組み合わせた機能を追加してみた！ <a href="https://t.co/ScPjDsLv6K">pic.twitter.com/ScPjDsLv6K</a></p>&mdash; you (@youtoy) <a href="https://twitter.com/youtoy/status/1435595470202490886?ref_src=twsrc%5Etfw">September 8, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

物理演算エンジン/物理エンジンとか、英語では Physics engine と呼ばれたりするものになります。

●物理演算エンジン - Wikipedia
　https://ja.wikipedia.org/wiki/%E7%89%A9%E7%90%86%E6%BC%94%E7%AE%97%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%B3

今回、JavaScript で使えるライブラリの話になるのですが、いくつかあるライブラリの中の「[Matter.js](https://brm.io/matter-js/)」を扱います。それを、[p5.js](https://p5js.org/) の描画処理と組み合わせて使います。

## 今回の内容
今回の記事で扱うのは、冒頭で掲載していた動画の中でも使われている仕組みで、具体的には以下のようなものです。

<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>物理演算エンジンに入門してみた！ <a href="https://t.co/7hWITEx4ad">pic.twitter.com/7hWITEx4ad</a></p>&mdash; you (@youtoy) <a href="https://twitter.com/youtoy/status/1435580538186649601?ref_src=twsrc%5Etfw">September 8, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

冒頭の動画は、「画像認識で手が開いている状態を認識できたら、その手の部分から円が出現する」というものでしたが、こちらは「マウスのドラッグ操作をすると、ドラッグした軌跡の上に円が出現する」というものになります。

## p5.js と Matter.js の組み合わせたプログラムの参照元
最初は、何か参考にできそうなものがあればとググってみるところから始めたのですが、その結果、以下の動画にたどり着きました。

●5.17: Introduction to Matter.js - The Nature of Code - YouTube
　https://www.youtube.com/watch?v=urR596FsU68

<iframe width="560" height="315" src="https://www.youtube.com/embed/urR596FsU68" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

スキップしつつ動画の内容をざっくり見ていくと、Matter.js を p5.js と組み合わせて使っているのが確認できました。

そして、動画の説明欄を見ていると「[Source Code for the all Video Lessons という記載とリンク](https://github.com/CodingTrain/website)」を見つけることができました。
![ソースコード.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/b716cdbe-7c51-ee3b-7637-6f305417bf81.jpeg)

そのリンク先はこちらです。
![CodingTrain_website.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/a84c3334-bdb8-dcab-3dc7-3912675a75ab.jpeg)

### ソースコードを探す
そのリポジトリの中にソースコードがありそうだったので、動画のタイトル「5.17: Introduction to Matter.js - The Nature of Code」の一部である「Introduction to Matter.js」をキーワードにして検索をかけてみました。

その結果が以下です。
![検索.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/456e53d3-692f-2aa5-2294-124b815e214b.jpeg)

その検索結果の一番上に README.md のファイルが表示されていますが、[そのファイルが置かれている階層](https://github.com/CodingTrain/website/tree/dbd1dfe415fd0a6e1f2bbeb8675e39c42d32ee71/Courses/natureofcode/5.18_matter_intro)へ移動してみました。

そして、ファイルの一覧を見ると sketch.js も置いてあります。
![スケッチ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/203d3efd-1869-6c1e-ea59-8d42b1961341.jpeg)
　
中はこのような内容になっていました。

```javascript:sketch.js
// Daniel Shiffman
// http://codingtra.in
// http://patreon.com/codingtrain
// Code for: https://youtu.be/uITcoKpbQq4

// module aliases
var Engine = Matter.Engine,
  // Render = Matter.Render,
  World = Matter.World,
  Bodies = Matter.Bodies;

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

var ground;

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

  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 keyPressed() {
//   if (key == ' ') {
//   }
// }

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

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

他に box.js、boundary.js といったファイルもありました。

```javascript:box.js
// Daniel Shiffman
// http://codingtra.in
// http://patreon.com/codingtrain
// Code for: https://youtu.be/uITcoKpbQq4

function Circle(x, y, r) {
  var 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() {
    var pos = this.body.position;
    var angle = this.body.angle;
    push();
    translate(pos.x, pos.y);
    rotate(angle);
    rectMode(CENTER);
    strokeWeight(1);
    stroke(255);
    fill(127);
    ellipse(0, 0, this.r * 2);
    pop();
  };
}
```

```javascript:boundary.js
// Daniel Shiffman
// http://codingtra.in
// http://patreon.com/codingtrain
// Code for: https://youtu.be/uITcoKpbQq4

function Boundary(x, y, w, h, a) {
  var 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() {
    var pos = this.body.position;
    var angle = this.body.angle;
    push();
    translate(pos.x, pos.y);
    rotate(angle);
    rectMode(CENTER);
    strokeWeight(1);
    noStroke();
    fill(0);
    rect(0, 0, this.w, this.h);
    pop();
  };
}
```

あとは index.html の中身を確認して、ライブラリの依存関係を確認しておきます。
上記の JavaScript のファイルを読み込む部分などもありましたが、依存しているライブラリは「p5.js と Matter.js のみ」となりそうです。

```html
  <script language="javascript" type="text/javascript" src="https://cdn.jsdelivr.net/npm/p5@1.3.1/lib/p5.min.js"></script>
  <script language="javascript" type="text/javascript" src="libraries/matter.js"></script>
```

## オンライン環境（p5.js Web Editor）で動かしてみる
必要そうなもの一式がそろったので、これをオンラインの開発・実行環境である「[p5.js Web Editor](https://editor.p5js.org/)」で動かしてみようと思います。

### index.html に関する内容
p5.js Web Editor のデフォルトの index.html に関しては、p5.sound.min.js を削除し、matter.min.js を追加しました。
matter.min.js は、現時点の最新版を CDN から読み込んでくるようにしました。

```html:index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.17.1/matter.min.js"></script><!-- 追加 -->
    <link rel="stylesheet" type="text/css" href="style.css">
    <meta charset="utf-8" />
  </head>
  <body>
    <script src="sketch.js"></script>
  </body>
</html>
```

### sketch.js に関する内容
sketch.js については、元々 3つに分かれていた処理を 1つにまとめてしまいました。
元の 3つに分割されたままで使っても良かったのですが、後で全体を眺めて読んでいきたかったので 1つにしてしまいました（全体の行数がそれほど多くないのもあり）。

そのまま動かしてみた後、色の設定の部分などは少しだけ変更を加えてみています。

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

### style.css に関する内容
style.css は特に変更は加えていません。

### 実行する
あとは、p5.js Web Editor の実行ボタンを押して動かせば OK です。

![実行ボタン.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/3ca34bfc-2bf6-1ddd-994f-edaf330942c4.jpeg)

実行後は、画面上でマウスのドラッグ操作をしてみましょう。
そうすると、ドラッグをした軌跡にあたる部分から、円がたくさん出現して画面内を落ちていきます。
![動作中の様子.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/64519ae2-fb0d-c021-731c-2c62a1c256ed.jpeg)

そして、円の落下・円同士の衝突など、画面内の物体の動きは、物理演算エンジンがうまく処理をしてくれているのも確認できました。

## まとめ
今回、サンプルとなるものをほぼそのまま利用して、p5.js と物理演算エンジン「Matter.js」を組み合わせた処理に入門してみました。

その後、[p5-matter](http://palmerpaul.com/p5-matter/)という、「p5.js と Matter.js を組み合わせるのを、より簡単にしてくれるライブラリ」があるのを知ったので、Matter.js をさらに使っていきつつ、こちらも試していければと思っています。

●p5-matter by pzp1997
　http://palmerpaul.com/p5-matter/
●pzp1997/p5-matter: Seamlessly integrate matter.js with p5.js
　https://github.com/pzp1997/p5-matter
![p5-matter_by_pzp1997.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/38d4932f-698e-2fc0-c14b-ab064c86075c.jpeg)

### 【追記】 p5-matter に関する記事の公開
p5-matter に関する記事を書きました。

●p5-matter を使って p5.js での物理演算エンジン（Matter.js）の利用を簡単化する【概要編】 - Qiita
　https://qiita.com/youtoy/items/a0e0da2da4c3acf66a7b
●p5-matter を使って p5.js での物理演算エンジン（Matter.js）の利用を簡単化する【活用編1】 - Qiita
　https://qiita.com/youtoy/items/7fa6f6e6df2cf60133e6