3. こんにちは物理世界

ODE

ゲーム開発やロボットの研究者にも使われているオープンソースの物理計算エンジンODE(Open Dynamics Engine、オープン ダイナミクスエンジン)を学ぶODE初級講座の3回目です。

今回はODEを使ったシミュレーションの流れを動力学計算を中心に基本的なAPIと関連付けて説明します.サンプルプログラムとしては,物理シミュレーションで最も簡単な物体の落下を取り上げます.プログラミングの教科書では初めの例題はHello Worldを表示する例が定番です。ここではHello Worldの物理シミュレーション版を紹介します.

ODEを使ったシミュレーションの流れを代表的なAPIと関連付けて列挙します。

    • シミュレーションの流れ
      • 描画用関数ドロースタッフの設定
        • カメラの設定 dsSetViewPoint()
      • ODEの初期化 dInitODE()
      • 動力学計算の世界worldの生成 dWorldCreate()
      • 重力加速度の設定 dWorldSetGravity()
      • 剛体の生成
        • 質量の設定 dBodySetMass()
        • 位置の設定 dBodySetPosition()
        • 姿勢の設定 dBodySetRotation()
      • シミュレーションループ(この部分は繰り返し実行される)
        • 動力学計算の実施 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をダウンロード、ビルドして実行してみましょう!

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について学びましょう。

こんにちは。この夏からODEを始めた者です。
かなり中途半端な質問になってしまいますが、ボールの落下のシミュレーションで画面内のボールを増やすことってできるんですが?

  • starさん、

    はじめまして、

    できますよ。

    上のソースコードですと、ボールの生成部分である56~60行目をボールの数だけ追加し、表示部分の21~23行目をボールの数だけ追加するとOKです。同じ形状の場合は、dBodyID ball[100];などと配列で宣言するとソースコードを短くできます。

    なお、ボールではありませんが、ドミノ倒しのサンプルプログラムが以下にあります。ただし、ODEをより簡単にするために、自前の関数を多用しています。良ろしければ、ODEで学ぶC言語2の記事もご覧ください。
    http://demura.net/9ode/7524.html

    でむ

    • starさん,

      この手のご質問が多かったのでサンプルプログラムを作りました.以下の練習にあるsample6multi-100817.zipをダウンロードしてソースコードをご参考にしてください.
      ODE初級講座 6.衝突検出 http://demura.net/tutorials/ode6

      でむ

    • 返信ありがとうございます。
      試しにソースコードを変更してみたのですがうまくいきません。これって全く同じ座標にボールが重なってる状態だと思うのですが、座標をずらす方法が良く分かりません。
      自分としては離れた位置にボールを二つ表示したいのですが初期位置の座標データを二つにしてもうまくいかないしどういうことでしょうか?

  • でむさん

    お世話になりました.うまくhello.cbpが動きました.
    これからはでむさんページ+自力で勉強します.

    どうもありがとうございました.

  • とても分かりやすいサイトをありがとうございます.
    ODEを初めて使ってみようと勉強させていただいております.
    さて,サンプルプログラム(sample3.cbp)などを開こうとすると

    mingw32-g++.exe – ディスクがありません
    ドライブにディスクがありません.ディスクをドライブ D:に挿入してください.

    というエラーが出て,操作不能になるのですが,そのような事例ご存知でしょうか.
    アドバイスいただけたら幸いです.
    よろしくお願いいたします.

  • コメントを残す

    メールアドレスが公開されることはありません。