衝突検出 (Collision Detection) の手順

衝突検出を行うには、ボディにあらかじめジオメトリによって形状の情報が設定されている必要があります。

またシミュレーションを適切に行うためには、ワールドの接触時の挙動について設定しておく必要もあります。

衝突検出の手順

  1. 衝突するボディの検出 (dSpaceCollide)
    衝突検出関数dSpaceCollide()を実行する。衝突が検出されると、そのコールバック関数が呼ばれる。
  2. 衝突した点における、接触情報の作成 (dColide)
    衝突したボディ同士の接触情報を取得する。
  3. 接触ジョイントの作成 (dJointCreateContact)
    得られた接触情報を基に、接触ジョイントを作成する。
  4. 衝突したボディを接触ジョイントにより接続 (dJointAttach)
    接触情報に含まれる衝突したボディ同士を、接触ジョイントにより接続する。

1.衝突するボディの検出 (dSpaceCollide)

シミュレーションステップごとに、ボディがお互いに接触するか検査します。

dSpaceCollide()で、指定のスペースに含まれるすべてのジオメトリ同士の衝突をテストできます。衝突する可能性のあるジオメトリが検出されたときには、そのジオメトリの組ごとにそれを引数として指定のコールバック関数が呼び出されます。

void dSpaceCollide (
    dSpaceID space,          // 検査対象とするスペース
    void *data,              // コールバック関数に渡す任意のデータ
    dNearCallback *callback  // 衝突時に呼び出されるコールバック関数
    );

衝突時に呼び出されるコールバック関数 (dNearCallback)

衝突検出時のコールバックとして指定するdNearCallbackは、

typedef void dNearCallback (
    void *data,              // dSpaceCollideから渡される任意のデータ
    dGeomID o1,              // 衝突する可能性のあるジオメトリ(スペース)1
    dGeomID o2               // 衝突する可能性のあるジオメトリ(スペース)2
    );

のように定義されています。

なおこの関数はジオメトリが衝突する可能性があるときに呼び出されるのであって、本当に衝突しているかどうかは、接触情報を調べて確認する必要があります。

また後述するdSpaceCollide2()からはジオメトリではなくスペースが渡される可能性もあるため、そのいずれかであるかを判定する必要もあります。それはdGeomIsSpace()により行えます。

int dGeomIsSpace ( dGeomID geom );

異なるスペースに含まれるジオメトリの衝突検出 (dSpaceCollide2)

dSpaceCollide()は同一スペースのジオメトリを対象としますが、dSpaceCollide2()ならばその対象を異なるスペースとできます。

void dSpaceCollide2 (
    dGeomID o1,              // 検査対象とするジオメトリ(スペース)1
    dGeomID o2,              // 検査対象とするジオメトリ(スペース)2
    void *data,              // コールバック関数に渡す任意のデータ
    dNearCallback *callback  // 衝突時に呼び出されるコールバック関数
    );

o1とo2が属するスペースによって、次の4パターンの処理があります。

  1. スペースに属するジオメトリと、属さないジオメトリ
  2. 両方のジオメトリが異なるスペースに属する
  3. 両方のジオメトリが同一のスペースに属する (dSpaceCollide()を呼び出すのと同一)
  4. 両方のジオメトリがスペースに属さない

2.衝突した点における、接触情報の作成 (dColide)

dColide()によって、衝突する可能性のある2つのジオメトリの接触情報を作成できます。その接触情報は、dContactGeom構造体の配列として取得できます。

戻り値は接触点の数であり、衝突していない場合には0が返されます。なおこの関数は、対象のジオメトリが属するスペースを考慮しません。

int dCollide (
    dGeomID o1,            // 検査対象のジオメトリ(スペース)1
    dGeomID o2,            // 検査対象のジオメトリ(スペース)2
    int flags,             // 接触情報の作成を指示するフラグ (接触点の最大数)
    dContactGeom *contact, // 接触情報を格納するdContactGeom構造体の配列
    int skip               // dContactGeom構造体の大きさ
    );
o1o2にスペースを指定した場合には、そのスペースに含まれるすべてのジオメトリが対象となります。flagでは下位16ビットのみのが使用され、それは接触点の最大数を表します。上位16ビットは常に0です。

dColideのskip引数

skipは、dContactGeom構造体に結果を格納するときにスキップするバイト数です。

dContactGeom構造体をそのまま渡すならば、sizeof (dContactGeom) でdContactGeom構造体の大きさを指定します。

dContactGeom contactGeom[ 256 ];

dCollide( o1, o2, flags, contactGeom, sizeof( dContactGeom ) );

一方、dContact構造体のgeomメンバ (dContactGeom構造体) を渡すならば、dContact構造体の大きさを指定します。

dContact contact[ 256 ];

dCollide( o1, o2, flags, &contact[ 0 ].geom, sizeof( dContact ) );

dContactGeom構造体

接触点の情報はdContactGeom構造体に、

struct dContactGeom {
    dVector3 pos;          // 接触した位置(絶対座標)
    dVector3 normal;       // 接触した面の方向
    dReal depth;           // ジオメトリ同士がめり込んだ大きさ (penetration depth)
    dGeomID g1;            // 接触したジオメトリ1
    dGeomID g2;            // 接触したジオメトリ2
    int side1;             //
    int side2;             //
};

のように格納されます。

3.ジョイント作成 (dJointCreateContact)

dContact構造体のオブジェクトを作成し、そのgeomメンバに先に得られた接触点の情報 (dContactGeom構造体) を格納します。

次にそれを引数に接触ジョイントを作成します。

4.衝突したボディを接触ジョイントにより接続 (dJointAttach)

衝突情報 (dContactGeom) に含まれる2つのジオメトリg1、g2のボディを取得し接触ジョイントで接続します。