nuxt.js × three.js × ammo.js でSpeedPizzaを作った時のメモ(成功)

# 概要
nuxt.js × three.js × ammo.js の開発に関するメモ。
外部OBJファイル等をloadしてsceneにaddしてから、それに対して、
物理演算(自由落下と当たり判定)を物理エンジンを使って加えられるかの実験メモ。

前回さくっと作ってみたら、object同士のcollision(当たり判定)が動作しない結果になった。
悔しいから色々試して、動作したバージョンができたので、メモとして残しておく。

前回の投稿の続きになるので、前提や環境は以下を参照。
[nuxt.js × three.js × ammo.js でSpeedPizzaを作った時のメモ(失敗)](https://qiita.com/yuichik-kusunoki/items/46922bfba8d388ff015f)

# 原因
結論からいうと前回の投稿の、「addしたobjectからshape→bodyを作成してPhysicsWorldに追加」の「そこで、makeShape関数によって、読み込んだobjectのgeometryからshapeを形成することにした。」の部分が原因。

makeShape関数の部分を細かく言うと、geometry(BufferGeometry)から頂点情報を取り出し、頂点情報からammo.jsのbtBvhTriangleMeshShapeを作成している。

色々調べたら、btBvhTriangleMeshShapeではmesh同士のcollisionが動作しないということがわかった、、、orz

そこで、btConvexHullShapeを使って、shapeを作り直すことにした。


<details><summary>誤っているソース</summary><div>

```ThreeCanvas.vue
<script type="module">

... 略

export default {

... 略

  methods: {

... 略

    makeShape(mesh) {
      const triangleMesh = new Ammo.btTriangleMesh(true, true);
      triangleMesh.setScaling(
        new Ammo.btVector3(mesh.scale.x, mesh.scale.y, mesh.scale.z)
      );
      const geometry = mesh.geometry;
      if (geometry instanceof THREE.BufferGeometry) {
        const vertexPositionArray = geometry.attributes.position.array;
        for (let i = 0; i * 3 < geometry.attributes.position.count; i++) {
          triangleMesh.addTriangle(
            new Ammo.btVector3(
              vertexPositionArray[i * 9],
              vertexPositionArray[i * 9 + 1],
              vertexPositionArray[i * 9 + 2]
            ),
            new Ammo.btVector3(
              vertexPositionArray[i * 9 + 3],
              vertexPositionArray[i * 9 + 4],
              vertexPositionArray[i * 9 + 5]
            ),
            new Ammo.btVector3(
              vertexPositionArray[i * 9 + 6],
              vertexPositionArray[i * 9 + 7],
              vertexPositionArray[i * 9 + 8]
            ),
            false
          );
        }
      } else if (geometry instanceof THREE.Geometry) {
        for (let i = 0; i < geometry.faces.length; i++) {
          const face = geometry.faces[i];
          if (face instanceof THREE.Face3) {
            const vec1 = new Ammo.btVector3(0, 0, 0);
            const vec2 = new Ammo.btVector3(0, 0, 0);
            const vec3 = new Ammo.btVector3(0, 0, 0);

            vec1.setX(face[0].x);
            vec1.setY(face[0].y);
            vec1.setZ(face[0].z);

            vec2.setX(face[1].x);
            vec2.setY(face[1].y);
            vec2.setZ(face[1].z);

            vec3.setX(face[2].x);
            vec3.setY(face[2].y);
            vec3.setZ(face[2].z);

            triangleMesh.addTriangle(vec1, vec2, vec3, true);
          }
        }
      }
      const shape = new Ammo.btBvhTriangleMeshShape(triangleMesh, true, true);
      return shape;
    },

... 略

  },
};
</script>
```
</div></details>

正解ソースはこちら

```ThreeCanvas.vue
<script type="module">

... 略

export default {

... 略

  methods: {

... 略

    makeShape(mesh) {
      const shape = new Ammo.btConvexHullShape();
      const geometry = mesh.geometry;
      if (geometry instanceof THREE.BufferGeometry) {
        const vertexPositionArray = geometry.attributes.position.array;
        for (let i = 0; i * 3 < geometry.attributes.position.count; i++) {
          shape.addPoint(
            new Ammo.btVector3(
              vertexPositionArray[i * 9],
              vertexPositionArray[i * 9 + 1],
              vertexPositionArray[i * 9 + 2]
            ),
            new Ammo.btVector3(
              vertexPositionArray[i * 9 + 3],
              vertexPositionArray[i * 9 + 4],
              vertexPositionArray[i * 9 + 5]
            ),
            new Ammo.btVector3(
              vertexPositionArray[i * 9 + 6],
              vertexPositionArray[i * 9 + 7],
              vertexPositionArray[i * 9 + 8]
            ),
            false
          );
        }
      } else if (geometry instanceof THREE.Geometry) {
        for (let i = 0; i < geometry.faces.length; i++) {
          const face = geometry.faces[i];
          if (face instanceof THREE.Face3) {
            shape.addPoint(
              new Ammo.btVector3(face[0].x, face[0].y, face[0].z),
              new Ammo.btVector3(face[1].x, face[1].y, face[1].z),
              new Ammo.btVector3(face[2].x, face[2].y, face[2].z),
              true
            );
          }
        }
      }
      shape.setLocalScaling(
        new Ammo.btVector3(mesh.scale.x, mesh.scale.y, mesh.scale.z)
      );
      return shape;
    },

... 略

  },
};
</script>
```

### おまけ
createRigidBody関数内で、bodyに色々な係数を渡すことができるので、それもメモっておこう。

```ThreeCanvas.vue
<script type="module">

... 略

export default {

... 略

  methods: {

... 略

    createRigidBody(threeObject, physicsShape, mass, pos, quat) {
      physicsShape.setMargin(0.05);
      const transform = new Ammo.btTransform();
      transform.setIdentity();
      transform.setOrigin(new Ammo.btVector3(pos.x, pos.y, pos.z));
      transform.setRotation(
        new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w)
      );
      const motionState = new Ammo.btDefaultMotionState(transform);
      const localInertia = new Ammo.btVector3(0, 0, 0);
      physicsShape.calculateLocalInertia(mass, localInertia);
      const rbInfo = new Ammo.btRigidBodyConstructionInfo(
        mass,
        motionState,
        physicsShape,
        localInertia
      );
      const body = new Ammo.btRigidBody(rbInfo);

      // この部分
      body.setRestitution(1);//反発
      body.setFriction(0.6);//摩擦
      body.setDamping(0, 0.04);//減衰
      body.setAngularFactor(new Ammo.btVector3(1, 1, 1));//回転の向き
      body.setLinearFactor(new Ammo.btVector3(1, 1, 1));//平行移動の向き

      threeObject.userData.physicsBody = body;
      this.rigidBodies.push(threeObject);
      body.setActivationState(4);
      this.physicsWorld.addRigidBody(body);
      return body;
    },

... 略

  },
};
</script>
```

最終的には、こんな感じになった
![success.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1046213/47a29afd-b820-14ef-96b9-52c610ec5a7f.gif)

跳ねすぎて、ほとんど落ちた

# まとめ
とりあえずはうまくいった。

意外と、外部のモデルファイルからloadしたobjectで、collisionを動作させる情報が少ないということがわかった。特にammo.jsは情報が少ない。
誰かの役に立ってくれたらと願います。

細かい部分はもっと勉強しなければ。