6. 衝突検出

ゲーム開 発やロボットの研究者にも使われているオープンソースの物理計算エンジンODE (Open Dynamics Engine、オープン ダイナミクスエンジン)を学ぶODE初級講座の第6回目です。
今回は衝突検出機能について勉強します。前回勉強したようにODEでは動 力学計算と衝突検出計算が別々に実装されています。動力学計算をするためには、ワールドworldをdWorldCreate()で生成し、その中に剛体 bodyを生成し、dWorldStep()で動力学計算をしましたね。
ジオメトリ
ジオメトリとは物体の形状という意味で、下図の左からsphere(球)、box(直方体)、cylinder(円柱)、capsule(カプセル)などの種類があります。

一方、衝突検出計算をするためには、スペースspaceをdHashSpaceCreate()で生成し、その中に剛体bodyに対応するジオメトリ geometryを生成しなければなりません。以下のサンプルプログラムでは、86行目で球に対応するジオメトリをdCreateSphere()で生成 しています。87行目で、動力学計算の対象となるボディball.bodyと衝突検出計算の対象となるball.geomを結びつけています。これで ODEの物体が完成です。
次に、simLoop関数の中で、dSpaceCollide()を呼び出します。このAPIは衝突しそうな2つのジオメトリが発生したら、それらを nearCallback関数に渡します。nearCallback関数の引数o1, o2が衝突する可能性のある2つのジオメトリです。nearCallback関数では、接触点を算出したり、接触点の性質などを設定します。 なお、注意しなければいけないことは、引数o1、o2は接触する可能性があるだけで、実際に接触しているかどうかよくわからないことです。それを知るためにはdCollide()の戻り値、つまり、接触点数が1以上かどうかチェックすれば良いのです。
また、75行目にあるように接触点の集まりが格納される入れ物をdJointGroupCreate()で生成し、シミュレーションループで毎回そ れをdJointGroupEmpty()を使って空にしなければなりません。サンプルプログラムでは58行目でやっています。これを忘れると1ステップ 前の接触点達が悪さをしますのでお忘れなく!
- 衝突検出に関する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)
接触点が格納されているジョイントグループを空にする。
- dSpaceID dHashSpaceCreate(0)
以下にソースコードを示します。 前回のプログラムと違うところだけコメントを入れています。
さて、ソースコードをさっそく読みましょう。なお、前回のsample1 と全く同じstart関数やprepDrawStuff関数は省略しています。
[viewcode] src=sample6.cpp geshi=cpp lines scroll=yes scrollheight=500[/viewcode]
オブジェクトも動力学計算用のbody(ボディ)の他に衝突検出計算用にgeom(ジオメトリ)を設定する必要があるのでMyObject 構造体でそれらをメンバとして定義していますね。
main関数の中で玉オブジェクトのgeomをdCreateSphere()で作り、dGeomSetBodyでbodyとgeomを関連付けていますのでオブジェクトの位置と姿勢はbodyだけで設定すればOKです。これをしないと幽体離脱現象に陥ってしまいます。
衝突検出関数dSpaceCollideはシミュレーションの各ステップで実行されるsimLoop関数の中で呼び出されています。注意する点とし ては、必ずsimLoopの一番始めで呼び出してください。これを後のほうにもっていくと玉が地面を突き抜けてしまいますよ。dSpaceCollide ではコー ルバック関数nearCallbackを呼び出しています。31行目で接触する可能性のある2つの物体のうち、どちらかが地面groundなら isGroundをtrueにセットします。32行目のdCollide()の戻り値は接触点数です。
34行目のif (isGround)文は,衝突する可能性のある物体のうちどちらかが地面なら35行目から48行目までの処理をします.つまり,地面との衝突以外は考えていません.地面以外との衝突を考慮する場合は,このif文を削除してください.
35行目で地面でかつ接触点数が1以上なら、地面と接触したことを示すflagを1にセットしています。
では、ここからサンプルプログラム6をダウンロードして実行してください。
では、また次回!
>if (!(g1 ^ g2)) return;
衝突点の両方が地面あるいは障害物,あるいはどちらとも地面でも障害物でもないとき衝突処理を行わない、ということですね。
短いコードなのに理解するのに時間がかかってしまいましたが、例外処理もうまくいき、望んでいた挙動をさせることができました。
本当にありがとうございました。
はじめまして。いつもこのサイトと貴著にお世話になっています。
いきなりですが物体間の衝突処理について分からないことがあるので質問させていただきます。
今ODEのサンプルコードのdemo_buggyをもとにして自作のプログラムを作成中なのですが,
設定がうまくいかず,物体同士が衝突せずにすりぬけてしまいます。
ジオメトリの設定はしてあり,地面とは接触することが確認済みなのですが、
バギーおよび新たに追加したパーツ同士はすりぬけが発生してしまいます。
このような症状がおきてしまう原因,おそらくは自分の設定が足りていないからですが,
解決方法に心当たりがありましたらご教授いただけないでしょうか。
よろしくお願いします。
melさん、
はじめまして。demura.netと拙著をご愛読ありがとうございます。
さて、ご質問ですが、if (!(g1 ^ g2)) return;の行をコメントアウトすると問題は解決すると思います。
でむ
static void nearCallback (void *data, dGeomID o1, dGeomID o2)
{
int i,n;
// only collide things with the ground
int g1 = (o1 == ground || o1 == ground_box);
int g2 = (o2 == ground || o2 == ground_box);
if (!(g1 ^ g2)) return;
正確には衝突する可能性のあるジオメトリが2つとも地面あるいは地面ボックス,あるいは地面でも地面ボックスでないとき衝突検出をしないでリターンするということです.
排他的論理和^を使っているので少しわかりづらいですね.
今後ともご愛読やコメントよろしくお願いします.
でむ