JavaScriptで使える3D物理エンジンを作ってみた

昔作ったActionScript 3.0向けの物理エンジンであるOimoPhysicsを全て書き直し、機能を大幅強化してJavaScriptから使えるようにしました。ようやく機能的にもAmmo.jsに対抗できるレベルになってきたかと思います。

デモは文字をクリックするかキー入力することで操作できます。Q, E キーで前後のデモと切り替えます。

>> Launch Demo (Click text on the left or press keys to control)
>> Open OimoPhysics GitHub page

 

物理エンジンとは

場面に合わせた物理シミュレーションをしてくれるライブラリです。最近のリアルなゲームには欠かせないものになりつつあります。オープンソースの有名どころでは Box2D, Bullet Physics, Open Dynamics Engine などがあります。ちなみにUnityでは標準でPhysXという物理エンジンを使うことができます。
JavaScriptで使える物理エンジンでは box2d.js※1Box2DをJavaScriptに変換したものです。, Matter.js, Ammo.js※2Bullet PhysicsをJavaScriptに変換したものです。, Oimo.js※3ActionScript 3.0版のOimoPhysicsをJavaScriptに移植したものです。, Cannon.js などがあります。Emscriptenの台頭により、C/C++で書かれたライブラリをasm.jsに変換したものが目立ちます。

OimoPhysicsについて

2011年頃から私が個人で開発を続けている軽量和製3D物理エンジンです。高速化に力を入れており、当時比較的遅いと言われてきたActionScript 3.0 (以下AS3) で実用的な3次元物理シミュレーションを可能にしました。

2016年から開発言語をAS3からHaxeに切り替え、1からコードを書き直しました。Haxeを採用した理由は、

 ・AS3, JavaScript, Java 等複数の言語に変換できる
 ・AS3やJavaScriptと同じECMAScript派生であり※4元々AS3がJavaScriptと同じECMAScript派生であり、HaxeがAS3から派生したという経緯があります。、文法が似ていて書きやすい
 ・静的型付けである
 ・強力なマクロが利用できる

などです。特に最後の2つが強力な理由です。Haxeのマクロについてはshohei909さんの「Haxe 実践マクロ」が最高に分かりやすいです。これがなければ開発が数年遅れていた可能性さえあります。ありがとうございました。

JavaScriptライブラリとしての利用

Haxeが書き出したJavaScriptファイルと、それをClosure Compilerで圧縮したものがGitHubのリポジトリに置いてあります。.jsファイルをダウンロードして普通のライブラリと同じように使うことができます。

 

機能と特長

他ライブラリとの独立性

3Dエンジンやその他ライブラリと完全に独立しています。好きなライブラリを組み合わせて使うことができますが、逆に言うと単体では描画機能を持ちません。

高速な衝突判定

以前より改善された広域衝突判定アルゴリズムにより、多数の剛体が動き回っていても快適に動作します。また、剛体の動きが小さい場合にはさらに判定が高速になります。

様々な図形のサポート

衝突形状として、
 ・球
 ・箱
 ・カプセル
 ・円柱
 ・円錐
 ・頂点群の凸包
が利用できます。これらを複数組み合わせて使うこともできます。

様々なジョイントのサポート

剛体同士をつなぐジョイントは
 ・球面ジョイント(Spherical Joint)
 ・回転ジョイント(Revolute Joint)
 ・直動ジョイント(Prismatic Joint)
 ・円筒ジョイント(Cylindrical Joint)
 ・ユニバーサルジョイント(Universal Joint)
 ・ラグドールジョイント(Ragdoll Joint)
をサポートしています。また、これらのうち多くは可動範囲の制限とモーター、可動範囲外に動いたときのバネ・ダンパを設定することができます。

ワールドに対するクエリ

物理演算ワールドに対して、AABBクエリ、レイキャスト、凸形状キャストを O(logN) で行うことができます。

その他諸々

 ・動かない剛体のスリープ
 ・衝突フィルタリング・コールバック
 ・回転方向の制限
 ・破壊可能なジョイント
 ・Projected Gauss-Seidelソルバによる安定したスタッキング

 

3Dライブラリとの連携

cx20さんがThree.jsなどの3DライブラリでOimoPhysicsを使うデモを作成されています。ありがとうございます!
このページ上で見るとテクスチャが読み込めなくて真っ黒になってしまうので、ぜひ元ページ上でご覧ください…… 追記:修正していただきました。このページ上でもちゃんと見られるようになりました。

 

Hello, OimoPhysics!

実際に使ってみたい方のために、HTML5のCanvasを使って簡単なシミュレーションを2Dで表示するサンプルを作ってみます。

準備

まずはCanvasを用意し、メインループが実行されるようにJavaScriptを書きます。ついでにこの記事を書いている段階での最新バージョン1.1.2のOimoPhysicsをインポートするスクリプトも書いておきます。
OimoPhysicsのクラスは全て window.OIMO 変数にexposeされているので、物理演算のためのワールドを new OIMO.World() で生成します。

以降はscriptタグの内部のみ見ていきます。

剛体を追加する

剛体のワールドへの追加は次のような手順で行います※5Box2Dを使ったことがある方は、Box2Dにおける Shape, Fixture, Body, *Def がそれぞれOimoPhysicsにおける Geometry, Shape, RigidBody, *Config であるという理解をしておけば問題ありません。

  1. ShapeConfig を用意する
  2. ShapeConfig に衝突ジオメトリを設定する
  3. ShapeConfig を使って Shape を生成する
  4. RigidBodyConfig のインスタンスを用意する
  5. RigidBodyConfig に位置や速度、剛体のタイプなどを設定する
  6. RigidBodyConfig を使って RigidBody を生成する
  7. RigidBodyShape を追加する
  8. RigidBody をワールドに追加する

恐らく「剛体一つ追加するだけでどんだけ長いねん!」という気持ちになったことでしょう。しかし1つ1つのステップは簡単ですので、覚えてしまえばそこまで大変ではないはずです。

まずは Shape の生成です。Shape には設定できる項目が多いので、ShapeConfig という設定をまとめたオブジェクトをコンストラクタに渡してやります。ShapeConfig で設定できるものは
 ・衝突図形(Geometry
 ・密度
 ・摩擦係数
 ・反発係数
 ・衝突フィルタリング
などです。最低限必要なものは図形だけなので、図形を設定した ShapeConfig を使って Shape を生成します。

ちなみに GeometryShape の違いですが、Geometry が「幾何的な情報」のみ持っているのに対し、Shape の方は回転や平行移動を含めた「物理的な性質の情報」も持っています。また、Geometry は複数の Shape の間で使い回すことができます。

続いて剛体の生成です。剛体の生成にも RigidBodyConfig という設定オブジェクトを使います。生成した剛体に先程の Shape を追加し、ワールドに剛体を追加します。

剛体には DYNAMIC, STATIC, KINEMATIC の3種類のタイプがあります。通常は動く剛体に DYNAMIC、壁などの動かない剛体に STATIC を指定しておけば大丈夫です。

以上をまとめて追加したコードが以下になります。これでワールドに剛体を追加することができました。

実行すると何も表示されませんが、内部では確かに箱が高さ5mの地点から落下しています

箱を表示する

何も表示されないのは寂しいので、落下する箱を表示されましょう。OimoPhysicsにはデバッグ用の表示補助機能があるので、今回はそれを使います。DebugDraw オブジェクトを用意し、line メソッドを上書きします※6本当は point メソッドと triangle メソッドも上書きする必要がありますが、とりあえず線だけ描画することにします。。このとき、座標の単位がメートルであることと、Y軸が鉛直上向きであることに注意します。最後にワールドに DebugDraw オブジェクトを設定すれば完了です。

ワールドを描画する際は、World.debugDraw メソッドを呼び出します。

以上のコードを initframe に追加すると……

落下する箱が表示されました!

箱を増やす

このままでは箱は奈落の底へ落ちてしまいますので、床を作りましょう。ついでに箱を増やして、球体も追加してみましょう。まず、 Geometry と座標、剛体のタイプを受け取って剛体を生成する関数を作ります。

適当な場所に床を配置し、箱と球体を生成するコードを書きます。

これを init 内に書いて実行します。

崩れる箱のシミュレーションが完成しました!
DebugDraw で上書きしたのは線分を書くメソッドだけですが、球体もちゃんと表示されています。これは内部で図形の描画を線分の描画に帰着させるコードが動いているためです。pointtriangle も実装すればポリゴンも描画されますが、陰面消去が使えないと前後関係がおかしくなってしまいます。

 

🍠 おわり 🍠

いかがでしょうか?
物理エンジンを使うと意外と簡単にシミュレーションを作成することができます。ライブラリの詳しい仕様は OimoPhysics API Documentation から見ることができます。他にも色々な機能があるので試してみてください。
また、今回のサンプルはこちらに置いてあります。

OimoPhysicsに関してバグ報告や質問等があれば、GitHubにissueを投げるTwitterでリプライを送ると対応できると思います(多分Twitterの方が反応が早いです)。使ってみた報告もお待ちしています……!

Contact me

Send me a reply or create an issue on GitHub if you have any question or bug report. Consider using Twitter if you want a quicker response. I welcome English messages :)

注釈   [ + ]

1. Box2DをJavaScriptに変換したものです。
2. Bullet PhysicsをJavaScriptに変換したものです。
3. ActionScript 3.0版のOimoPhysicsをJavaScriptに移植したものです。
4. 元々AS3がJavaScriptと同じECMAScript派生であり、HaxeがAS3から派生したという経緯があります。
5. Box2Dを使ったことがある方は、Box2Dにおける Shape, Fixture, Body, *Def がそれぞれOimoPhysicsにおける Geometry, Shape, RigidBody, *Config であるという理解をしておけば問題ありません。
6. 本当は point メソッドと triangle メソッドも上書きする必要がありますが、とりあえず線だけ描画することにします。

Leave a Reply