ゲーム開発やロボットの研究者にも使われているオープンソースの物理計算エンジンODE(Open Dynamics Engine、オープン ダイナミクスエンジン)を学ぶODE初級講座の3回目です。
今回はODEを使ったシミュレーションの流れを動力学計算を中心に基本的なAPIと関連付けて説明します.サンプルプログラムとしては,物理シミュレーションで最も簡単な物体の落下を取り上げます.プログラミングの教科書では初めの例題はHello Worldを表示する例が定番です。ここではHello Worldの物理シミュレーション版を紹介します.
ODEを使ったシミュレーションの流れを代表的なAPIと関連付けて列挙します。
- シミュレーションの流れ
- 描画用関数ドロースタッフの設定
- カメラの設定 dsSetViewPoint()
- カメラの設定 dsSetViewPoint()
- ODEの初期化 dInitODE()
- 動力学計算の世界worldの生成 dWorldCreate()
- 重力加速度の設定 dWorldSetGravity()
- 剛体の生成
- 質量の設定 dBodySetMass()
- 位置の設定 dBodySetPosition()
- 姿勢の設定 dBodySetRotation()
- 質量の設定 dBodySetMass()
- シミュレーションループ(この部分は繰り返し実行される)
- 動力学計算の実施 dWorldStep()
- シミュレーションに必要な処理を書く
- 動力学worldの破壊 dWorldDestroy()
- ODEの終了 dCloseODE()
- 描画用関数ドロースタッフの設定
- 動力学計算
シミュレーションの流れでは色々なAPIを使っていますが、今回は物理エンジンの最も重要な動力学計算のAPIについて説明します。動力学計算をするAPIはdWorldStep()とdWorldQuickStep()です.違いはdWorldStep()の方が精度は高いですが速度は遅く,dWorldQuickStep()はその逆です.dWorldStep()とdWorldQuickStep()ではシミュレーションの結果が違うので,速度に問題なければ精度の高いdWorldStep()を使ってください.これらのAPIはシミュレーションで毎回呼び出さなければいけないのでサンプルプログラムのようにsimLoop関数の中で呼び出してください.
- void dWorldStep(dWorldID, dReal stepsize);
シミュレーションを引数stepsieze[s]だけ1ステップ進めます。stepsizeは数値積分の時間刻み幅、単位は秒。大きいと精度が悪くなりますが、スピードは遅くなります。
- void dWorldQuickStep(dWorldID, dReal stepsize);
シミュレーションを引数stepsize [s] だけ1ステップ進めます。上と機能は同じですが、dWorldStep()よりは高速ですが、精度は高くなります.
- void dWorldSetQuickStepNumIterations(dWorldID, int num);
dWorldQuickStep()の繰り返し計算の回数を設定します.numの値を大きくすると精度は高くなりますが速度は遅くなります.このAPIを呼び出さない場合,つまりnumのデフォルト値は20です.一般的にはdsSimulationLoop()を呼び出す前に指定します.
- ソースコード次に、詳しいコメントのついたソースコードを以下に示します。main関数から読んでください。なお,文字化けするために一部全角文字を使っています.カットアンドペーストではなく,ここからサンプルプログラム3をダウンロードしてお試しください.
// Hello World by でむ #include <ode/ode.h> // ODEのヘッダーファイル(動力学計算用) #include <drawstuff/drawtuff.h> // ドロースタッフヘッダーファイル(3Dグラフィクス用) #ifdef dDOUBLE #define dsDrawSphere dsDrawSphereD // 単精度と倍精度の描画関数に対応するおまじない #endif static dWorldID world; // 動力学計算用ワールド dBodyID ball; // 玉 const dReal radius = 0.2, mass = 1.0; // 玉の半径(m)、玉の重さ(kg) // シミュレーションループ 毎回呼び出され実行されます。 // 動力学計算はステップサイズをここで指定すれば自動的に計算される // ただし、描画はここで書かないと何も表示されません。 static void simLoop (int pause) { const dReal *pos,*R; // 位置、回転行列 dWorldStep(world,0.05); // シミュレーションの1ステップ進める。その時間を0.05秒に設定。 dsSetColor(1.0,0.0,0.0); // 色の設定。引数は光の3原色(赤、緑、青)。値は0から1. pos = dBodyGetPosition(ball); // 玉の位置を取得 R = dBodyGetRotation(ball); // 玉の姿勢を取得 dsDrawSphere(pos,R,radius); //赤玉を描画 } // ドロースタッフの前処理関数 void start() { // カメラの設定 static float xyz[3] = {0.0,-3.0,1.0}; // 視点の位置 (x, y, z [m]) static float hpr[3] = {90.0,0.0,0.0}; // 視線の方向(ヘッド、ピッチ、ロール[°]) dsSetViewpoint (xyz,hpr); // 視点の設定 } // メイン関数 ここから読んでください。 int main (int argc, char **argv) { dReal x0 = 0.0, y0 = 0.0, z0 = 1.0; // ボールの初期位置[m] dMass m1; // 質量パラメータ構造体(質量、慣性モーメントなど) // 描画API(ドロースタッフ)のおまじない(設定) dsFunctions fn; // ドロースタッフ構造体 fn.version = DS_VERSION; // ドロースタッフのバージョン fn.start = &start; // シミュレーションの前処理関数 fn.step = &simLoop; // シミュレーションの各ステップで呼ばれる関数 fn.command = NULL; // 関数がないのでNULLポインタを指定 fn.stop = NULL; // 関数がないのでNULLポインタを指定 fn.path_to_textures = "../../drawstuff/textures"; // テクスチャへのパス // 動力学計算用世界の創造 dInitODE(); // ODEの初期化 world = dWorldCreate(); // 動力学計算の対象となる剛体を入れるworldを作る。戻り値はそのID番号。 dWorldSetGravity(world,0,0,-0.001); // 重力加速度(x, y, z)の設定 -0.001 m/s^2 // 玉を作る ball = dBodyCreate(world); // 剛体を作る。戻り値はそのID番号 dMassSetZero(&m1); // 質量パラメータm1の初期化 dMassSetSphereTotal(&m1,mass,radius); // 質量パラメータm1に剛体の質量を設定 dBodySetMass(ball,&m1); // 剛体に質量パラメータm1を設定 dBodySetPosition(ball, x0, y0, z0); // 剛体ballの絶対座標での位置(x, y, z)を設定 // シミュレーション本体 // argc, argvはmain関数の引数、ウインドウサイズ352 x 288ピクセル, fnはドロースタッフ構造 dsSimulationLoop (argc,argv,352,288,&fn); // 世界の破壊 dWorldDestroy (world); // 動力学計算の対象であるworldを破壊する dCloseODE(); // ODEの終了 return 0; }
これは赤玉の自由落下のプログラムです。ODEのシミュレーションの流れでは、まず、dInitODE()でODEを初期化します。次に、物理計算をするworld(ワールド)をdWorldCreate()で作ります。物理計算を受ける物体はその中に作らなければなりません。ODEでは物体のことをbody(ボディ)と呼んでいます。物体はdBodyCreate(world)で作ります。物体を作ったら、次にその質量パラメータと位置や姿勢を設定します。このプログラムでは球の質量パラメータと位置だけを設定しています。
物体の生成と設定が終わったら、次はシミュレーションを進めます。これはdsSimulationLoop()で繰り替えしsimLoop関数が呼び出すことにより実行されています。simLoop関数のdWorldStep(world, 0.05)はシミュレーションを1ステップ進めています。進める時間は2番目の引数、この場合は0.05秒となります。dsDrawSphere()で落下する球を表示しています。
シミュレーションが終わると,後片付けを行います。dWorldDestroy(world)でワールドを破壊し,dCloseODE()でODEの終了処理をします。
なお、小文字のdで始まる関数はODEのAPI(application interface)で、dsで始まる関数はdrawstuff(ドロースタッフ)のAPIです。drawstuffはODE付属テストプログラム表示用のライブラリのことです。
説明はこのぐらいにしてサンプルプログラム3をダウンロード、ビルドして実行してみましょう!
- サンプルプログラム3 (ode-0.12用)
ode-0.12, Visual C++ 2008とCode::Blocks 8.02用のプロジェクトファイルが同封されています。Visual C++ 2010の場合は、変換ウィザードで変換すれば問題なくビルドできます。なお、ode-0.12より古いバージョンのODEではライブラリ名が変わったのでビルドできません。ode-0.12にアップグレードしてお試しください。
また、サンプルプログラムの設定ファイルは相対パスでdrawstuffのテクスチャパスを見ているので、c:\ode-0.12\myprogの下で解凍する必要があります(myprogというフォルダ名の必要はありませんが、半角英字にしてください)。解凍後はc:\ode-0.12\myprog\sample3-120904\sample3.vcxprojというファイルができていれば成功です。なお、これ以降のサンプルプログラムのビルドも全て上記と同じです。
ここでは重力加速度を赤玉がゆっくり落下していきますが、なんと地面を通り抜けて消えてしまいます。実は上のプログラムには衝突検出機能が組み込まれていなかったのです。
次回はODE付属の3Dグラフィクスライブラリdrawstuffについて学びましょう。