6. 衝突検出

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

今回は衝突検出機能について勉強します。前回勉強したようにODEでは動 力学計算と衝突検出計算が別々に実装されています。動力学計算をするためには、ワールドworldをdWorldCreate()で生成し、その中に剛体 bodyを生成し、dWorldStep()で動力学計算をしましたね。

ジオメトリ

ジオメトリとは物体の形状という意味で、下図の左からsphere(球)、box(直方体)、cylinder(円柱)、capsule(カプセル)などの種類があります。

  • 衝突検出に関するAPI
    • dSpaceID dHashSpaceCreate(0) 衝突計算用スペースを生成し、そのID(識別子)を返す。 
    • dGeomID dCreatePlane(dSpaceID space ,dReal a, dReal b, dReal c, dReal d) spaceにax+by+cz=dの平面ジオメトリを生成する。 
    • dGeomID dCreateSphere(dSpaceID space, dReal r) spaceに半径rの球ジオメトリを生成する。 
    • void dGeomSetBody(dGeomID geom, dBodyID body) 物体の2つの属性であるジオメトリgeomと剛体bodyを関連づける。 
    • dJointGroupID dJointGroupCreate(0) 接触点のグループを格納するジョイントグループを生成し、そのIDを返す。 
    • void dJointGroupEmpty(dJointGroupID) 接触点が格納されているジョイントグループを空にする。 

以下にソースコードを示します。 前回のプログラムと違うところだけコメントを入れています。

さて、ソースコードをさっそく読みましょう。なお、前回のsample1 と全く同じstart関数やprepDrawStuff関数は省略しています。

#include <ode/ode.h>
#include <drawstuff/drawstuff.h>

static dWorldID world;
static dSpaceID space;
static dGeomID  ground;
static dJointGroupID contactgroup;
static int flag = 0;
dsFunctions fn;

const dReal   radius = 0.2;
const dReal   mass   = 1.0;

typedef struct {
  dBodyID body;
  dGeomID geom;
} MyObject;
MyObject ball;

static void nearCallback(void *data, dGeomID o1, dGeomID o2)
{
  const int N = 10;
  dContact contact[N];

  int isGround = ((ground == o1) || (ground == o2));

  int n =  dCollide(o1,o2,N,&contact[0].geom,sizeof(dContact));

  if (isGround)  {
    if (n >= 1) flag = 1;
    else        flag = 0;
    for (int i = 0; i < n; i++) {
      contact[i].surface.mode = dContactBounce;
      contact[i].surface.mu   = dInfinity;
      contact[i].surface.bounce     = 0.0; // (0.0~1.0) restitution parameter
      contact[i].surface.bounce_vel = 0.0; // minimum incoming velocity for bounce
      dJointID c = dJointCreateContact(world,contactgroup,&contact[i]);
      dJointAttach (c,dGeomGetBody(contact[i].geom.g1),dGeomGetBody(contact[i].geom.g2));
    }
  }
}

static void simLoop (int pause)
{
  const dReal *pos,*R;

  flag = 0;
  dSpaceCollide(space,0,&nearCallback);

  dWorldStep(world,0.01);

  dJointGroupEmpty(contactgroup);

  if (flag == 0) dsSetColor(1.0, 0.0, 0.0);
  else           dsSetColor(0.0, 0.0, 1.0);
  pos = dBodyGetPosition(ball.body);
  R   = dBodyGetRotation(ball.body);
  dsDrawSphere(pos,R,radius);
}

int main (int argc, char *argv[])
{
  dReal x0 = 0.0, y0 = 0.0, z0 = 2.0;
  dMass m1;

  prepDrawStuff();

  dInitODE();
  world = dWorldCreate();
  space = dHashSpaceCreate(0);
  contactgroup = dJointGroupCreate(0);

  dWorldSetGravity(world,0,0,-0.5);

  // Create a ground
  ground = dCreatePlane(space,0,0,1,0);

  // Create a ball
  ball.body = dBodyCreate(world);
  dMassSetZero(&m1);
  dMassSetSphereTotal(&m1,mass,radius);
  dBodySetMass(ball.body,&m1);
  dBodySetPosition(ball.body, x0, y0, z0);

  ball.geom = dCreateSphere(space,radius);
  dGeomSetBody(ball.geom,ball.body);

  dsSimulationLoop (argc,argv,352,288,&fn);

  dWorldDestroy (world);
  dCloseODE();

  return 0;
}

オブジェクトも動力学計算用のbody(ボディ)の他に衝突検出計算用にgeom(ジオメトリ)を設定する必要があるのでMyObject 構造体でそれらをメンバとして定義していますね。

main関数の中で玉オブジェクトのgeomをdCreateSphere()で作り、dGeomSetBodyでbodyとgeomを関連付けていますのでオブジェクトの位置と姿勢はbodyだけで設定すればOKです。これをしないと幽体離脱現象に陥ってしまいます。

衝突検出関数dSpaceCollideはシミュレーションの各ステップで実行されるsimLoop関数の中で呼び出されています。注意する点とし ては、必ずsimLoopの一番始めで呼び出してください。これを後のほうにもっていくと玉が地面を突き抜けてしまいますよ。dSpaceCollide ではコー ルバック関数nearCallbackを呼び出しています。31行目で接触する可能性のある2つの物体のうち、どちらかが地面groundなら isGroundをtrueにセットします。32行目のdCollide()の戻り値は接触点数です。

29行目のif (isGround)文は,衝突する可能性のある物体のうちどちらかが地面なら30行目から43行目までの処理をします.つまり,地面との衝突以外は考えていません.地面以外との衝突を考慮する場合は,このif文を削除してください.

30行目で地面でかつ接触点数が1以上なら、地面と接触したことを示すflagを1にセットしています。

一方、衝突検出計算をするためには、スペースspaceをdHashSpaceCreate()で生成し、その中に剛体bodyに対応するジオメトリ geometryを生成しなければなりません。以下のサンプルプログラムでは、85行目で球に対応するジオメトリをdCreateSphere()で生成 しています。86行目で、動力学計算の対象となるボディball.bodyと衝突検出計算の対象となるball.geomを結びつけています。これで ODEの物体が完成です。

次に、simLoop関数の中で、dSpaceCollide()を呼び出します。このAPIは衝突しそうな2つのジオメトリが発生したら、それらを nearCallback関数に渡します。nearCallback関数の引数o1, o2が衝突する可能性のある2つのジオメトリです。nearCallback関数では、接触点を算出したり、接触点の性質などを設定します。 なお、注意しなければいけないことは、引数o1、o2は接触する可能性があるだけで、実際に接触しているかどうかよくわからないことです。それを知るためにはdCollide()の戻り値、つまり、接触点数が1以上かどうかチェックすれば良いのです。

また、71行目にあるように接触点の集まりが格納される入れ物をdJointGroupCreate()で生成し、シミュレーションループで毎回そ れをdJointGroupEmpty()を使って空にしなければなりません。サンプルプログラムでは52行目でやっています。これを忘れると1ステップ 前の接触点達が悪さをしますのでお忘れなく!

では、次のサンプルプログラム6をダウンロードして実行しよう。

練 習

上の例ではボール1個を落下させましたが,多くのボールを落下させるプログラムを書いてみましょう.回答例は次からダウンロードできます。

では、また次回!

はじめまして。
私は現在ODEを使ってロボットのシミュレーションを行いたいと考え、勉強しはじめた初心者です。

一つ質問が有ります。
本記事のサンプルをビルドするとode/ode.hに対してNo such file or directoryとなり、
以下50個のエラーが吐き出されます。
恐らく、インクルードエラーだと思うのですが、具体的な対処が分かりません。

私の様なエラーについて質問なさっている方が他にも散見されましたが、
読んでも解決する事ができませんでした。

解決法についてのご回答をよろしくおねがいします。

  • はじめまして、このサイトと貴著でODEを勉強しているものです。
    今、落下物と床とは別の剛体との衝突のシミュレーションを作成しようとしているのですが、落下物が剛体をすり抜けてしまいます。
    この場合はnearCallbackをもう一つ作らなければならないのでしょうか?それともnearCallback関数内に衝突計算を複数作らなければならないんでしょうか?

    • syanさん、

      はじめまして。

      nearCallbackの関数内で、すべてのジオメトリのペア毎に衝突検出に関する属性、例えば、摩擦係数や反発係数など、を変える場合は、if文で条件分けをする必要があります。nearCallbackは1個でOKです。

      御回答になったでしょうか。

      でむ

      • 返信ありがとうございます。
        nearCallbackが1個でいいのは分かったんですが剛体同士の衝突についてはどうしても分かりません。
        この場合はサンプルであった((ground == o1) || (ground == o2)); とは別に((ground == o1) || (ground == o3));といった感じにif文を増やせばよろしいのでしょうか?

        • いいえ。

          nearCallbackには接触する可能性の2つのジオメトリ(つまり、o1, o2)が入力されます。o3といった感じに増やすのではなく、例えば、球とボックスジオメトリの衝突に関する属性を変えたい場合は、if (((o1==sphere)&&(o2==box))||((o2==sphere)&&(o1==box))) としてください。

          なお、物体間の衝突に関する属性が全て同じ場合は条件を分ける必要がありません。

          でむ

  • >if (!(g1 ^ g2)) return;
    衝突点の両方が地面あるいは障害物,あるいはどちらとも地面でも障害物でもないとき衝突処理を行わない、ということですね。
    短いコードなのに理解するのに時間がかかってしまいましたが、例外処理もうまくいき、望んでいた挙動をさせることができました。

    本当にありがとうございました。

  • はじめまして。いつもこのサイトと貴著にお世話になっています。
    いきなりですが物体間の衝突処理について分からないことがあるので質問させていただきます。

    今ODEのサンプルコードのdemo_buggyをもとにして自作のプログラムを作成中なのですが,
    設定がうまくいかず,物体同士が衝突せずにすりぬけてしまいます。
    ジオメトリの設定はしてあり,地面とは接触することが確認済みなのですが、
    バギーおよび新たに追加したパーツ同士はすりぬけが発生してしまいます。

    このような症状がおきてしまう原因,おそらくは自分の設定が足りていないからですが,
    解決方法に心当たりがありましたらご教授いただけないでしょうか。

    よろしくお願いします。

  • コメントを残す

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