NIJIBOX

three.jsを触ってみた(その3) 〜 物理エンジンにHelloWordの巻 〜

更新日 2024.2.13
three.jsを触ってみた(その3) 〜 物理エンジンにHelloWordの巻 〜

3Dモデルを表示させてからだいぶ時間が経ってしまったんですが、今回は、three.jsで物理エンジンを使ってみたいと思います。
(three.jsはr95を使用しています)

やはり3Dモデルの次は物理エンジンですよね!3Dやってる感ハンパねえ

と言いつつ、物理エンジンへの知識は高校時に物理履修していたくらい。
皆無に等しいので、早速調べつつ始めます。

ちなみに物理エンジンとは…。

物理演算エンジン(ぶつりえんざんエンジン、Physics engine)とは、質量・速度・摩擦・風といった、古典力学的な法則をシミュレーションするコンピュータのソフトウェアである。多くの場合、ミドルウェアライブラリを指す。 略して物理演算、物理エンジン、Physicsとも言う。
wikipwdia 物理演算エンジン より

three.jsで使える物理エンジンを探す

一通り探してみました。

  • Oimo.js
    なんだか、かわいい名前の物理エンジン。
    ActionScript 3.0 用に書かれた OimoPhysics ってライブラリをJavascript用に移植したやつらしい。
    ドキュメントとか資料があまり見つからなかったので断念。
  • Ammo.js
    Bullet(メジャーなやつらしい)というC++のオープンソースの物理エンジンをJavascriptに移植したもの。
    ‘ammo’の由来は”Avoided Making My Own js physics engine by compiling bullet from C++”。
    機械的にjsに移植してるので、読めたもんじゃなさそう(読んでないけど)。
    いろいろ調べてたら「人がさわるようなものじゃない…」みたいなこと言ってる人もいた。(とはいえ、three.jsのexamplesでammo使用されているが…)
    あと重い。
  • Cannon.js
    Bullet(弾丸)よりも強いぜ!ってことでCannon(大砲)らしい。
    精度はそこまで高いわけではないけど、なかなか軽いみたいです。
    three.jsとammo.jsにインスパイアされて作られたそうです。

まだまだjs向けの物理エンジンはあるみたいたいですが、とりあえずこれらの中から選定。
個人的に調べた中では一番資料が充実していて使いやすそうだったcannon.jsを採用することにしました。

Hello World

ライブラリも決まったことなので、早速Helloworldしましょう。

物理世界を作る

まずはgithubのREADMEにあるサンプルを写経していきます。(consoleに物理演算の結果を出すサンプル)
あと、ついでにメモをコメントで追加していく。


// Setup our world
var world = new CANNON.World(); // world が物理世界になる
world.gravity.set(0, 0, -9.82); // m/s² // x y z それぞれにかかる力(重力)を設定

// Create a sphere
var radius = 1; // m
var sphereBody = new CANNON.Body({
mass: 5, // kg // massは重さ。
position: new CANNON.Vec3(0, 0, 10), // m
shape: new CANNON.Sphere(radius) // shapeはthree.jsのものと同じ
});
// ↑ ちなみに `sphereBody.mass = 5`みたいな設定もできる。

world.addBody(sphereBody); // worldにsphereBodyを追加

// Create a plane
var groundBody = new CANNON.Body({
mass: 0 // mass == 0 makes the body static
});
// ↑ mass = 0 にすると剛体になる。つまり動かない物体

var groundShape = new CANNON.Plane();
groundBody.addShape(groundShape);
world.addBody(groundBody);

var fixedTimeStep = 1.0 / 60.0; // seconds
var maxSubSteps = 3;

// Start the simulation loop
var lastTime;
(function simloop(time){
requestAnimationFrame(simloop);
if(lastTime !== undefined){
var dt = (time – lastTime) / 1000;
world.step(fixedTimeStep, dt, maxSubSteps);
/**
・↑ シミレーション時は requestAnimationFrame などで step を更新して world.step(1/60) などをする
・第1引数: 何秒ごとにシミュレーションを行うかを指定する。(requestAnimationFrameなら最大fps60なので 1/60 にする)
・第2引数: 前回の処理から、経過した時間を入れる
・第3引数: どのくらいコマ落ちして良いか指定する
・※ 第2引数以降は設定しなくても良い。 が、処理落ちしてfps30などになった場合にシミュレーションがゆっくり動くようになる
・第2,3引数を指定している場合は、処理落ちしている時に最大で第3引数指定したフレームだけコマ落ちさせて、表示速度を維持する。
*/
}
console.log(“Sphere z position: ” + sphereBody.position.z);
lastTime = time;
})();

`sphereBody`の高さが重力によりフレームごとに変化して行く様子。

おおお…。

数値だけの変化しか表示されていませんが、物体が落下するのが見えます。
自由落下が脳内に流れてくるぜええええええ!

感動。

consoleだけだとあれなので、ログをHTMLに書き込むするようにするなど加筆修正を加えたりしました。

See the Pen Hello Cannon.js by kohei sakamoto (@eeonk) on CodePen.

Threejsのmeshと繋げる

物理世界が再現されていたことが確認できましたので、次はThree.jsの世界にCannon.jsの物理世界を繋げていきます。

See the Pen connect Three & Cannon by kohei sakamoto (@eeonk) on CodePen.

three.jsリスペクトな感じで書かれているので、Three.js触ったことあればCannon.js書くのも割とすらすらかける印象です。
つなぎこみはこんな感じで描画のタイミングで、meshに対して、positionとQuaternionをコピーするだけ。簡単。

// つなぎこみ作業はフレーム毎に実行していた world.step() の後に
// meshに対して、positionとQuaternionをコピーするだけ。
mesh.position.copy(body.position)
mesh.quaternion.copy(body.quaternion)

個人的ハマりポイント

個人的にハマってしまったところがちょいちょいあったので、書き残しておきます。

① x、y、z軸の方向がおかしい

cannon.js、three.js間でのxyzのずれ

cannon.jsとthree.jsのx、y、zが若干違うくなっているみたいなので、何も考えずに作ると変な動きしたりすることがあります。これを知らずに2時間ほど溶かしました。
調整してあげましょう。

② なんだかスロー

落下が遅いサンプル

作ったものの、なんだかすごく動きがスローです。重力加速度もちゃんと9.8m/sにしているのに…!
調べてみると、物理エンジン使うときはMKS単位系を使用するのが一般的なお作法なんだそうです。

MKS…?

M(メートル)、K(キログラム)、S(秒) のこと。初耳でした。

つまりこれまでずっと、一辺50メートル、重さ100キログラムの箱を200メートル上空から落下させていた…。

落下が遅いサンプル

そう言われれば納得…。

単位はちゃんと守りましょう。MKS単位系です。

③ meshのsizeはbodyのsizeの2倍にしてあげる

これちょっとなんでか分からないんですが、`bodyWidth = meshWidht * 2` みたいな感じにしてあげないと、meshとbodyの大きさが揃ってくれないみたいです。


var body = new CANNON.Body({
mass: mass, // kg
position: new CANNON.Vec3(point.x, point.y, point.z), // m
// `bodyWidth = meshWidht * 2`になるように meshのsizeから 1/2 したものでサイズ指定
shape: new CANNON.Box(new CANNON.Vec3(weight / 2, weight / 2, weight / 2))
})

簡単なインタラクションのあるサンプルを作る

helloWorldが一瞬で終わった印象なので、物理エンジンを使用して簡単なコンテンツを作ってみました。

  • 物理演算やるとなったら、多分みんな最初に作るだろうヤツ
    (箱やら何やらがたくさん落ちてくる)
    See the Pen Three-Cannon sample1 by kohei sakamoto (@eeonk) on CodePen.
  • 物理演算やるとなったら、多分みんな2こ目に作るだろうヤツ
    (床が傾いて箱とかがサラサラする。あとposition_yを動かすと床が上下する)
    See the Pen Three-Cannon sample2 by kohei sakamoto (@eeonk) on CodePen.
  • 雑に箱を投げるやつ
    (spだと動く自信ない。すいません)
    (箱をマウスダウンで掴める。マウスアップで箱が離れるので、勢いをつけると箱が遠くに飛んでいく)
    See the Pen Three-Cannon sample3 by kohei sakamoto (@eeonk) on CodePen.

実装方法はコードをみてください…。すいません…。

まとめ

物理エンジン使うと、簡単な箱が落ちてくるだけのサンプルでもすごい技術感を感じられる。
ただ、これから面白コンテンツを作るとなると、なかなかアイデアが出ないものだなあという…、悩み。

重力のみのシミュレーションで空気抵抗とか風とかのない世界だったので、その他の力も加えると面白そう。ただ、今のところやり方がいまいちわからない。(空気抵抗はなんだか大変そうな気配がする)

(ここから愚痴)
あと、three.jsのバージョンが、毎月ペースでリリースされるので、どのバージョン使ったら良いか、いまいちわかんない…。
cannonでexampleを触る時に最近のバージョンでやると、すでに廃止されているmethodが使われているなどして警告やエラー出てつらい…。