LoginSignup
2
5

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-05-11

概要

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

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

前回の投稿の続きになるので、前提や環境は以下を参照。
nuxt.js × three.js × ammo.js でSpeedPizzaを作った時のメモ(失敗)

原因

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

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

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

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

誤っているソース
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>

正解ソースはこちら

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

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

まとめ

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

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

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

2
5
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5