死んだ女房の口癖さ

いりあす(EARIUS,IRIUS)のMMO日記がゲームやる暇無くてバイク日記にそんなブログ

ODE-衝突検出

2005-11-27 07:35:57 | プログラムゥ



今回も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);

ODE

2005-11-22 23:33:15 | プログラムゥ



冬になってめっきりバイクねたもなくなったので、
プログラミングのメモでもつけていくことにする。


現在行っているのは自作3DエンジンにODE(open dynamics engine)というフリーの物理エンジンをマウントする作業。

ODEとはRussell Smithさんが中心となって開発を行っているオープンソース、フリーのC/C++用動力学シミュレーションライブラリです。

ライセンスは

LGPL

**************************************
Lesser General Public License:
社内や個人的に利用するにあたってのソースコード改変、再コンパイルには制限がない。
LGPLで配布されたプログラムを再配布する際にはソースコードを公開する必要がある。
LGPLライセンスで配布されたプログラムAについて、

AとリンクされたプログラムBは再配布する時、BのライセンスはLGPLでなくてもよい。
Aを改変して作成されたプログラムA'を再配布する場合、A'のライセンスはLGPLである必要がある。
(ウィキペディア)
**************************************

もしくはBSD

**************************************
Berkeley Software Distribution License:
「無保証」であることの明記と著作権表示だけを再頒布の条件とするライセンス規定。 この条件さえ満たせば、BSDライセンスのソースコードを複製・改変して作成したオブジェクトコードを、ソースコードを公開せずに頒布できる。

著作権表示さえしておけば、BSDライセンスのソースコードを他のプログラムに組み込み、しかも組み込み後のソースコードを非公開にできるため、GPLに比べ再配布時のライセンス条件を制限する事もなく、商用化及び標準規格の制定に利用しやすいライセンスである。
(修正BSDライセンスの場合)
(ウィキペディア)
**************************************

のどちらかの選択式で、BSDで使う場合はかなりの広範囲な用途(もちろん商用も)使用することができる。

ありがたく使わせていただいているものの、
さすがに海外のライブラリなわけでドキュメントも英語、なわけですが、
日本人ユーザーによるWikiも公開されており、こちらで主要な個所の訳は手に入ります。
また、最近、金沢工業大学でロボット研究をされている助教授の方がODE講座を始められているので、そちらも非常に参考になります。
しかしやはり的確な意味を把握したり訳されていないところは自分で訳すわけで、英語脳を持たない自分は結構苦労しているのでした。




今日のメモ:

※以下間違い

ODEのdBody構造体はobjects.hでdxBodyとして宣言されています。
dBaseとdObjectクラスを基底に持つものの、基本的には
struct dxBody : public dObject {
  dxJointNode *firstjoint;	// list of attached joints
  int flags;			// some dxBodyFlagXXX flags
  dGeomID geom;			// first collision geom associated with body
  dMass mass;			// mass parameters about POR
  dMatrix3 invI;		// inverse of mass.I
  dReal invMass;		// 1 / mass.mass
  dVector3 pos;			// position of POR (point of reference)
  dQuaternion q;		// orientation quaternion
  dMatrix3 R;			// rotation matrix, always corresponds to q
  dVector3 lvel,avel;		// linear and angular velocity of POR
  dVector3 facc,tacc;		// force and torque accumulators
  dVector3 finite_rot_axis;	// finite rotation axis, unit length or 0=none

  // auto-disable information
  dxAutoDisable adis;		// auto-disable parameters
  dReal adis_timeleft;		// time left to be idle
  int adis_stepsleft;		// steps left to be idle
};

とあらわされている構造体のメンバを使用します。

で、今日は上記のテーブルに太字で表示してあるdVector3 avelのメモです。
この数値は物体の角速度ベクトルを表していて、
void dBodySetAngularVel (dBodyID, dReal x, dReal y, dReal z);

でセット
const dReal * dBodyGetAngularVel (dBodyID);//戻り値はdReal[3]

で取得できます。

ところがこのaval、シミュレーション中は問題ないんですが、取得してみると軸が一般的な右手左手座標にならないのです。
角速度ベクトルは回転方向に直交した大きさが回転速度をRadian/sにしたベクトル。(間違ってないよね・・)
なのですが、
ためしに座標軸ごとのベクトルをいれてODEのStep関数で回転させてやると(左手座標)
(1,0,0)

(0,1,0)

(0,0,1)

よくわかんないことになりました。
まあ見たまんま座標軸入れ替わりながら部分的に正負反転しているようなので入れ替えてやればいいようです。
というわけで自分の場合左手座標系を使っているので、

ODE(x,y,z)から自分の座標系で使える角速度ベクトル(x',y',z')に変換する時は
(x',y',z') = (z,-y,-x)
自分の座標系(x',y',z')からODEの角速度ベクトル(x,y,z)に変換する時は
(x,y,z) = (z',-y',-x')
としました。


ちなみに右手座標系の場合
ODE(x,y,z)から自分の座標系で使える角速度ベクトル(x',y',z')に変換する時は
(x',y',z') = (z,-y,x)
自分の座標系(x',y',z')からODEの角速度ベクトル(x,y,z)に変換する時は
(x,y,z) = (z',-y',x')

対応を図解しておく

右手系これであってるかな・・多分あってる。

ソース解析してavelをどのように使っているか見たけど把握できなかったのでこの方法でいいと思いました。

※以上間違い

(2005-11-25追記)
えーどうやら、根本的な間違いとして、ODEのQuaternion構造を誤解していたようです。
Direct3DではQuaternionを(x,y,z,w)としているのに対し、
ODEでは(w,x,y,z)としているようです。


互いの変換で配列を入れ替えてやればそれで解決する問題でした。

この仕様に関してはODEドキュメント9.1項の最初に普通の書体で

• The inverse of a quaternion is [q[0] − q[1] − q[2] − q[3]].

とだけ記されていて見落としていました。
要するにQuaternionの逆(逆とあるがこれは共役でしょうか)は
[q[0],−q[1],−q[2],−q[3]]
と書いてあったので(w,v)のv成分がq[1]からq[3]と発覚しました。

この日記含めてすごい時間を無駄にしたようです。

メモ終了っと。

11月14日 奥多摩

2005-11-14 04:39:26 | バイク


12時に目がさめたら天気予報あたってやんの。




というわけで行ってまいりました奥多摩。

着いたときにはもう3時半になってた。

おそらく今日で今年の走り収めというのに情けない・・

気温は8度。昼はよく路面もあったまってたんだろうけど、ついたときにはもうずいぶん日陰が侵食していた。気温のピークも越えた後っぽかった。

一度タイヤ冷えたら面倒になるのでそのまま走り出す。

下見しつつ昼に到着していた仲間も走っているのを確認して徐々にペース上げることにした。
完全にタイヤがあったまるまでは後輪のズリズリ感があり、バンク角制限されるものの、ワイドオープンしなければこけるほどではないっぽかった。
あったまった後も路面は冷えていて工事の浮き砂があるせいか路面の食いつきが悪いっぽくて、クリップから立ち上がりでIN側ステップから押し出すような過重入れると「トトト」と、ちょっとずつ滑っていく予兆が後輪から伝わってきた。

登ってくる途中で一緒だったアプリリアRSVの奴が早い。
数往復目の上りでその後ろにつくことになったので集中してついていくことに。
が、ストレートに立ち上がるコーナーのクリップでスタンド擦って後輪スリップ。
フルバンクから激しく振られたけどなんとかリカバー。とはいえ前走車に離されたしビビるしで一服入れた。

振られたときの挙動は「ズリッ」ではなく「ズザッ」と一度大きく滑った後車体が立ち、もう一度小さく滑ってグリップした感じだった。
寒いせいなのか、こういうコンパウンドのせいなのかわからないけど、思いっきりハイサイド食らわないですんだ。
ちなみにGPR70sp。

その後は行楽シーズンということもあり、車で渋滞しながらたまにクリアラップが取れる感じだった。



グースやNSRの後ろについて走りつつライディングフォームを研究してた。
NSR追っかけてる時に上りストレートへのコーナーの進入でRブレーキをガッツリかけてまたもや、ひやっとする滑りかたしたけどビビリミッターのおかげでこけずに終了した。

5時にはもう完全に日は沈み真っ暗闇だった。
都民まで降りる道には電灯がなく、ガードの反射板とヘッドライトだけをたよりに走っていると不思議な感覚に襲われる。まずコーナーでの自分の位置がつかめなくなって、次の症状で未来予測が出来なくなる。
自分も一瞬パニクった時に事故るかと思った。


キャブセッティング
メインジェット
ジェットニードル
エアスクリュ
260
190
5FN118 3/5
5FN117 3/5
1.5戻し
2.0戻し
サスセッティング
スプリング
15mm(突出)
27mm(圧縮)

低速でのかぶり症状はクラッチ切ることで対処、エンブレやパーシャルが長時間続かないようにするか、周りを完全無視して回す。で今回は乗り切った。

あのバンク角でスタンドをするというのはリア車高が足りないためだろうか。確かにバンク角はもうちょい欲しいということもあるので、今後上げてみようと思う。
スタンドは内側に引っ込むマージンがないタイプなので擦ったら即挙動が乱れてたいへん。

タイヤは、この時期でもずいぶんグリップすることがわかった。コンパウンドは軟らかいんだけどすべりかたは硬かった。半生状態だとそれが如実。