【Babylon.js】物理エンジンを使ってみた

`Kick soccer ball`ボタンを押すとサッカーボールが飛びます。

https://codesandbox.io/embed/divine-cherry-wiwgdz?fontsize=14&hidenavigation=1&theme=dark&view=preview

# はじめに

ARIの土井です。前回[【Babylon.js】Reactを使って3Dモデルを表示する](https://qiita.com/yuki_doi/private/3d15aa76567cdc9be1d5)という記事を書きました。今回は物理エンジンを使って実際にサッカーボールを蹴ってみたいと思います。

# 物理エンジンとは

物理エンジンを使用すると、Scene内に重力を発生させたり、メッシュに質量や反発係数、摩擦係数を設定したりすることができます。詳細は以下のドキュメントを参照してください。

https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine

# 手順

## 物理エンジンの有効化

物理エンジンの有効化はシンプルで、[cannon](https://www.npmjs.com/package/cannon)パッケージをimportして`scene.enablePhysics();`でScene内で物理エンジンを有効化するだけです。
```react
import * as cannon from "cannon";
```

```react
  // 物理エンジン有効化
  window.CANNON = cannon;
  scene.enablePhysics();
```

## groundの作成

`BABYLON.MeshBuilder.CreateGround`関数によって、単純な平面のメッシュを作成します。作成したメッシュに`physicsImpostor`というものを設定することで、質量や摩擦係数、反発係数を設定することができます。groundは重力の影響を受けないように質量を0にしておきます。
```react
  // ground作成
  const ground = BABYLON.MeshBuilder.CreateGround(
    "ground",
    { width: 6, height: 6 },
    scene
  );
  ground.physicsImpostor = new BABYLON.PhysicsImpostor(
    ground,
    BABYLON.PhysicsImpostor.BoxImpostor,
    { mass: 0, friction: 0.7, restitution: 0.7 },
    scene
  );
```

## Colliderを使って3Dモデルを剛体にする

読み込んだ3Dモデルを剛体にするためには、`Collider`という透明なメッシュを作成し、3Dモデルと`Collider`をグループ化したメッシュを作成するという方法をとります。サッカーボールは球体なので`sphereCollider`を作成し、`isVisible = false`で透明なメッシュにします。この`sphereCollider`と3Dモデルをグループ化した親メッシュ`soccerBall`を作成し、それに質量や摩擦係数を設定します。
```react
  // 親メッシュの作成
  const soccerBall = new BABYLON.Mesh("soccerBall", scene);

  // soccerBall.babylonからノードの読み込み
  const assetsManager = new BABYLON.AssetsManager(scene);
  const task = assetsManager.addMeshTask("task", "SoccerBall", SoccerBallAsset);
  task.onSuccess = function (task) {
    // 読み込んだ3Dモデル
    const mesh = task.loadedMeshes[0];

    // 球体のColliderを作成
    const radius = 0.25;
    mesh.position.set(0, -radius, 0);
    const sphereCollider = BABYLON.Mesh.CreateSphere(
      "sphere",
      16,
      2 * radius,
      scene
    );
    sphereCollider.isVisible = false;

    // 3DモデルとColliderをグループ化
    soccerBall.addChild(mesh);
    soccerBall.addChild(sphereCollider);

    sphereCollider.physicsImpostor = new BABYLON.PhysicsImpostor(
      sphereCollider,
      BABYLON.PhysicsImpostor.SphereImpostor,
      { mass: 0 },
      scene
    );

    // 親メッシュに質量や摩擦係数を設定
    soccerBall.physicsImpostor = new BABYLON.PhysicsImpostor(
      soccerBall,
      BABYLON.PhysicsImpostor.NoImpostor,
      { mass: 2, friction: 0.7, restitution: 0.7 },
      scene
    );
    soccerBall.position.y = radius;
  };
```

## ボタンの作成

ボタンがクリックされたらサッカーボールに力が与えらえるようにします。`Pulse`関数でサッカーボールに加わる力の向き、大きさ、作用点を設定し、`applyImpulse`関数で3Dモデルに力を与えます。作用点をサッカーボールの重心から少しずらしているので、横向きの回転がかかるようになります。ボタンをクリックしたらこの関数が実行されるように、`button.onPointerClickObservable.add(Pulse);`とします。

```react
  const Pulse = function () {
    const impulseDirection = new BABYLON.Vector3(0.5, 1, 1);
    const impulseMagnitude = 15;
    const contactLocalRefPoint = new BABYLON.Vector3(0.15, 0, 0);

    soccerBall.physicsImpostor.applyImpulse(
      impulseDirection.scale(impulseMagnitude),
      soccerBall.getAbsolutePosition().add(contactLocalRefPoint)
    );
  };
  button.onPointerClickObservable.add(Pulse);
  advancedTexture.addControl(button);
```

# まとめ

今回はBabylon.jsで物理エンジンを試してみました。パラメータをべた書きしてしまっているので、力の向きや大きさをGUIで設定できるようになったらPKのゲームとか作れそうですね。これからもBabylon.jsの記事を投稿していきたいと思います。