みかづきブログ・カスタム

基本的にはちょちょいのほいです。

床を傾けて3Dのボールを転がす(Cannon.js + Three.js + React.js) 🏐

Cannon.js + Three.js + React.jsで、

  • マウス座標に応じて床を傾ける
  • 四方を見えない壁で囲う

という空間を作って、そのな簡易3Dの球を配置してみました。


ソースコード(抜粋)

function handleMouseMove(evt) {
  const deg = 12;
  const x = (evt.clientX / windowWidth * 2) - 1;
  const y = (evt.clientY / windowHeight * 2) - 1;

  cannonObjectList[0].body.quaternion.setFromEuler(
    (-90 + deg * y) * Math.PI / 180,
    (deg * x) * Math.PI / 180,
    0
  );
}

床の傾きはこんな感じで実装しました。
画面の中心が基準で、

              -12°
              ⬆️
-12° ⬅️  ➡️ 12°
              ⬇️
              12°

という感じで傾くようにしています。

今回の気づきとしては、CANNON.Planeに向きがあるということです。

A plane, facing in the Z direction. The plane has its surface at z=0 and everything below z=0 is assumed to be solid plane. To make the plane face in some other direction than z, you must put it inside a RigidBody and rotate that body. See the demos.

https://schteppe.github.io/cannon.js/docs/classes/Plane.html より引用

と、ドキュメントに書いてあるのですが、翻訳すると、

A plane, facing in the Z direction. The plane has its surface at z=0 and everything below z=0 is assumed to be solid plane. To make the plane face in some other direction than z, you must put it inside a RigidBody and rotate that body. See the demos.

Z方向を向いている平面。平面の表面はz = 0にあり、z = 0より下のすべてはソリッド平面であると見なされます。平面をz以外の方向に向けるには、平面をRigidBody内に配置し、そのボディを回転させる必要があります。デモをご覧ください。

という感じで、まさにこのことな気がします。

CANNON.Planeを地面として使う際、z = 0より下にオブジェクトがめり込むと高く飛び跳ねます。
つまり、CANNON.Planeはペラペラなわけではなくて、そこを通過すると逆向きの力が発生するわけです。今回壁を作るとき、当初は回転方向を間違えていてボールが吹っ飛んで行った時に気づきました。

壁は、

ソースコード(抜粋)

[
  [0, 0, -size, 0, 0, 0],
  [0, 0, size, 0, Math.PI, 0],
  [-size, 0, 0, 0, Math.PI / 2, 0],
  [size, 0, 0, 0, -Math.PI / 2, 0]
].forEach((arr) => {
  const wallBody = new CANNON.Body({
    mass: 0,
    shape: new CANNON.Plane()
  });

  wallBody.position.set(...arr.splice(0, 3));
  wallBody.quaternion.setFromEuler(...arr);

  const wall = new THREE.Mesh(
    new THREE.PlaneGeometry(1, 1, 1, 1),
    new THREE.MeshNormalMaterial()
  );

  wall.material.visible = false;

  addCannonObject({
    body: wallBody,
    mesh: wall
  });
  scene.add(wall);
});

こんな感じのコードで生成しました。
本当は、座標とオイラー角の配列を分けた方が良いと思うのですが、横着してまとめています。