今回もODEネタ
ODEについてはこちら
衝突検出に関しては現在WIKIでも訳があまり出てない状態なので基本部分をメモっとく。
重要な概念としては
・Geom
・Space
・Contact構造体
・ContactGeom構造体
・ContactJoint(dJointGroup)
を挙げておく。
Geom
マニュアル10.2に解説してある。
10.7にある各関数によって作り出すことができる、剛体のあたり判定につかわれる立方体や球、シリンダーなどの各形状を表すデータ。あたり判定を行いたいBodyとは別の形でもいい。dGeomSetBody()でBodyと関連付ける、それ以降の姿勢は関連付けられたBodyに従う。
Space
マニュアル10.3に解説してある。
10.7にある各関数によって作り出せる。Geomのかたまりの単位。
Spaceには別のSpaceもGeomと同じように格納することが出来て、それにより階層化できる。
Spaceには種類があって用途に応じて選べる。また、ちがう種類のスペース同士を階層化することもできる。
SimpleSpace、HashSpace、QuadTreeSpaceがある。
ContactGeom構造体
マニュアル10.1に解説してある。
dCollide()で記録される接触点データを格納する。
struct dContactGeom { dVector3 pos; //接触点。グローバル座標。 dVector3 normal; //接触法線。接触面に垂直 dReal depth; //接触深度。接触をどのくらい重なり合っているかであらわしたもの。 dGeomID g1,g2; //接触GeomのID。 };
Contact構造体
マニュアル7.3.7に解説してある。
コンタクトジョイントを作るためのデータが入っている。
struct dContact { dSurfaceParameters surface;//コンタクトフラグ dContactGeom geom;//上記のContactGeom構造体 dVector3 fdir1;//摩擦方向。surfaceにdContactFDir1が指定されている時のみ };
ContactJoint(dJointGroup)
通常、接触は接触点をジョイントとして扱うことで剛体間の関係をシミュレーションに反映する。
その接触ジョイントを保持しておくためのコンテナ。
ただのdJointGroupだが、接触処理専用で用意しておくことでdJointGroupEnpty()などを使ってシミュレート毎に高速に削除できる。
・GeomとSpace
Geomは直接接触判定に使われるが、Spaceはその集合にすぎずない。
Spaceは内部にSpaceを階層的に持つことができる。
これらの接触判定には
dCollide() dSpaceCollide() dSpaceCollide2()
が使われる。
図1:
上図をふまえて。
int dCollide ( dGeomID o1,dGeomID o2,//判定する双方のGeomのID。 int flags, //生成する接触点の上限数。 dContactGeom *contact,//接触点の情報を記録するための構造体のポインタ。 int skip //配列に記録するための、dContactGeom間のアドレス間隔。 ); //戻り値は生成した接触点の数
基本となるGeom同士の接触を検出する関数。
上図で一つ一つのGeomを取り出してdCollide()を呼び出す処理をユーザーが実装する方法もあるが、便利な関数が用意されている。
それがdSpaceCollide()とdSpaceCollide2()である。
void dSpaceCollide ( dSpaceID space, //判定するSpace void *data, //コールバック関数に渡すデータ用ポインタ dNearCallback *callback//コールバック関数。後述 );
この関数は第一引数のSpaceが内包するGeomおよびSpaceを2つづつ取り出しコールバック関数に渡す。
Space内部の衝突に使う。
図1でSpace1を指定した場合、
Geom1とSpace2、
Geom1とSpace3、
Space2とSpace3
を第一第二引数にして、それぞれコールバック関数を呼び出す。
Space2を指定した場合、
Geom2とGeom3
をコールバック関数に渡す。
void dSpaceCollide2 ( dGeomID o1, dGeomID o2,//判定する互いのGeomもしくはSpace void *data, //コールバック関数に渡すデータ用ポインタ dNearCallback *callback//コールバック関数。後述 );
この関数は第一引数のGeomもしくはSpaceが内包するGeomおよびSpaceを一つづつ取り出し、第二引数のGeomもしくはSpaceが内包するGeomおよびSpaceを一つづつ取り出し、コールバック関数に渡す。
Space同士(もしくはSpaceとGeom)の衝突に使う。
図1でSpace2とSpace3を指定した場合、
Geom2とGeom4
Geom2とGeom5
Geom3とGeom4
Geom3とGeom5
をそれぞれ引数にコールバック関数が呼び出される。
Geom1とSpace2の場合
Geom1とGeom2
Geom1とGeom3
が引数にコールバック関数が呼び出される。
※実際には上記のコールバック関数の呼び出しは逆になる。
どういうことかというと、
dSpaceCollide2()に渡したo1,o2はコールバック関数にo1はo2として、o2はo1として渡される。
・コールバック関数
説明が後になったが、コールバック関数はユーザーが定義してSpaceCollideに関数ポインタとして渡す。
void nearCallback (void *data, dGeomID o1, dGeomID o2);
※_cdecl規約呼び出し。そのためクラスのメンバとして実装する場合にはstatic宣言にしましょう。
引数のGeomIDは前述のように決まる。
一般にコールバック関数が(再帰的に)呼ばれて、引数が互いにGeomの時にdCollide()が呼ばれるようなアルゴリズムを定義する。
このようにdSpaceCollide()、dSpaceCollide2()とコールバック関数によってどのようにdCollide()が呼び出されるかを決めることができる。
ドキュメント記載の例:
void nearCallback (void *data, dGeomID o1, dGeomID o2) { if (dGeomIsSpace (o1) || dGeomIsSpace (o2)) { //SpaseとGeomもしくはSpaceの接触 dSpaceCollide2 (o1,o2,data,&nearCallback); //Space内部の接触判定 if (dGeomIsSpace (o1)) dSpaceCollide (o1,data,&nearCallback); if (dGeomIsSpace (o2)) dSpaceCollide (o2,data,&nearCallback); }else{ //互いがSpaceではないので // o1o2のコンタクトポイントを作る int num_contact = dCollide (o1,o2,max_contacts,contact_array,skip); // シミュレーション用のコンタクトポイントを追加する ... } } ...
この処理はSpace内部の最下層までSpaceCollide()を再帰的に呼び出して当たり判定を行っている。
図1の場合、Space3内部のGeom4とGeom5は常に重なっている。このように内包するSpaceの当たり判定はしない場合の処理は
アプリケーション側でdSpaceCollide()にSpace1を指定して呼び出すとして、
void nearCallback (void *data, dGeomID o1, dGeomID o2){ if (dGeomIsSpace (o1) || dGeomIsSpace (o2)) { dSpaceCollide2(o1,o2,data,&NearCallback); }else{ int num_contact = dCollide (o1,o2,max_contacts,contact_array,skip); } }
コールバック関数はこのような記述となる。
衝突ジョイントの作成
一般的な衝突処理のフローを大まかに説明すると
1- dCollide():衝突判定(dContactGeomにデータ格納)
2- dCreateContact():接触ジョイントを作成(dContactからdJointを作成)
3- dJointAttach():接触ジョイントを剛体に接続する
- 衝突判定、接触ジョイント生成終了
4- シミュレーションステップを実行する
ここでdCreateContact()で必要なのはdContactなのでdCollide()にわたすdContactGeomはdContact.geomにするのが適当。
dJointAttach()の引数に必要なdBodyIDはdGeomSetBody()でGeomと関連付けてあればdContact.geom.g1とdContact.geom.g2からdGeomGetBodyで取り出せる。
まとめ
・接触処理に必要な変数
dContact
コールバック関数内でローカルに宣言。
dJointGroup
接触ジョイントを保持するためdWorldStep()が呼ばれるまで保持でき、
かつコールバック関数に引数で渡せるスコープ、もしくはコールバック関数に直接スコープを与えるためグローバルに宣言。
コード例:(模式のため擬似的な記述)
//宣言 //dBody,dWorld宣言略 dSpace BigSpace; dSpace Space1,Space2; dGeom Geom1,Geom2,Geom3,Geom4; dJointGroupID ContactJGroup; //Space作成 BigSpace = dSimpleSpaceCreate(0); Space1 = dSimleSpaceCreate(BigSpace); Space2 = dSimleSpaceCreate(BigSpace); //Geom内包 Geom1 = dCreateSphere(Space1,ShereRadius); //略 //関連付け dGeomSetBody(Geom1,Body1); //略 //ジョイントグループ作成 ContactJGroup = dJointGroupCreate(0); //コールバック関数 void NearCallback(void *data, dGeomID o1, dGeomID o2){ if (dGeomIsSpace (o1) || dGeomIsSpace (o2)) { dSpaceCollide2(o1,o2,data,&NearCallback); }else{ dContact ContactArray[MAX_CONTACTS]; int NumContact = dCollide (o1,o2,MAX_CONTACTS,ContactArray,sizeof(dContact)); for(int i = 0;i < NumContact;++i) { //コンタクトタイプを指定 ContactArray[i].surface.mode = dContactBounce|dContactApprox1; //bounceを指定(コンタクトタイプの指定によって変える) ContactArray[i].surface.bounce = 0.8f; ContactArray[i].surface.mu = 0.2f; //コンタクトジョイント作成 dJointID Joint; Joint = dJointCreateContact(World,ContactJGroup,&ContactArray[i]); //コンタクトジョイントを剛体に接続 dJointAttach (Joint,dGeomGetBody(contact[i].geom.g1), dGeomGetBody(contact[i].geom.g2)); } } } //以下の動作で使用する dSpaceCollide(BigSpace,NULL,&NearCallback); dWorldStep(World,StepTime); |