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

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

3Dオブジェクトの位置を物理演算で算出する(Cannon.js + Three.js + React.js) 📈

Cannon.js + Three.jsでオブジェクトの位置を物理演算で算出してみました。

ざっくりとした流れとしては、

❶ Cannon.jsで計算用のworldをつくる
❷ 毎フレーム、Cannon.jsでオブジェクトの位置を計算する
❸ 毎フレーム、Three.jsのオブジェクトの位置と姿勢をCannon.jsの位置と姿勢に合わせる

という感じです。

それに伴い、Cannon.jsをいい感じに使えるようにするカスタムフックを作りました。


ソースコード

useCannon

import * as CANNON from 'cannon-es';
import { useEffect, useRef, useState } from 'react';

export default function useCannon() {
  const cannonObjectListRef = useRef<{
    body: CANNON.Body;
    mesh: THREE.Mesh;
  }[]>([]);
  const [ world ] = useState(new CANNON.World());
  const [ cannonObjectList, setCannonObjectList ] = useState<{
    body: CANNON.Body;
    mesh: THREE.Mesh;
  }[]>([]);

  useEffect(() => {
    world.gravity.set(0, -9.81, 0);
  }, [world]);

  function addCannonObject(body: CANNON.Body, mesh: THREE.Mesh) { // ❶
    cannonObjectListRef.current.push({
      body,
      mesh
    });

    setCannonObjectList(cannonObjectListRef.current);
    world.addBody(body);
  }

  function upDateCannonObject() { // ❷
    world.fixedStep();
    cannonObjectList.forEach(({ mesh, body }) => {
      mesh.position.copy(body.position as any);
      mesh.quaternion.copy(body.quaternion as any);
    });
  }

  return {
    CANNON,
    world,
    cannonObjectList,
    addCannonObject,
    upDateCannonObject
  };
}

❶ CANNON.BodyとTHREE.Meshをペアで登録する
❷ CANNON.Bodyの位置を更新し、THREE.Meshのposition、quaternionに代入する

という機能を実装しました。

  • 登録したオブジェクトを削除する
  • 動的にオブジェクトを増やした際の処理を最適化する

を実装していないので、そこは今後に期待です。


つかいかた

index.jsx(抜粋)

const {
  CANNON,
  cannonObjectList,
  addCannonObject,
  upDateCannonObject
} = useCannon();

useEffect(() => {
  if (!cannonObjectList.length) {
    const floorBody = new CANNON.Body({
      mass: 0,
      shape: new CANNON.Plane()
    });

    floorBody.position.set(0, 0, 0);
    floorBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0);

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

    floor.position.set(0, 0, 0);
    floor.rotation.x = -Math.PI / 2;

    addCannonObject(floorBody, floor); // ❶
    scene.add(floor);

    const cubeBody = new CANNON.Body({
      mass: 1,
      shape: new CANNON.Box(
        new CANNON.Vec3(.5, .5, .5)
      )
    });

    cubeBody.position.set(0, 4, 0);
    cubeBody.angularVelocity.set(1, 1, 1);

    const cube = new THREE.Mesh(
      new THREE.BoxGeometry(1, 1, 1),
      new THREE.MeshNormalMaterial()
    );

    addCannonObject(cubeBody, cube); // ❶
    scene.add(cube);

    return;
  }

  camera.position.set(8, 8, 8);
  camera.lookAt(new THREE.Vector3(0, 0, 0));

  renderer.setAnimationLoop(() => {
    upDateCannonObject(); // ❷
    renderer.render(scene, camera);
  });
}, [cannonObjectList]);

❶ CANNON.BodyとTHREE.Meshをペアで登録する
❷ CANNON.Bodyの位置を更新し、THREE.Meshのposition、quaternionに代入する

という感じで使います。


DEMO

影なし

f:id:kimizuka:20220302224019p:plain
kimizuka.org

影あり

f:id:kimizuka:20220302224021p:plain
kimizuka.org