ホームゲームつくろー!衝突判定編<DirectXとの親和性を意識しよう

実装編
その1 DirectXとの親和性を意識しよう


 衝突判定の実装はそれなりのクラスを作成すればできますが、オリジナリティに溢れ過ぎるクラスは後々使うときに面倒を起こします。多少DirectXを意識してクラスを設計してみましょう。



@ 何を意識するのか?

 DirectXとの親和性を保つために何を意識するのか?それを理解するには、DirectXがポリゴン(プリミティブ)をどう扱っているかを知る必要があります。

 DirectXは「FVF(柔軟な頂点フォーマット)」という方法でポリゴンの頂点を管理しています。これは、1つの頂点の属性をまとめた構造体をユーザがある程度自由に組むことができるように工夫されています。しかし、扱える変数の型、定義順番は厳密に決められています。定義できる情報、その型を定義順に示します。

項目 詳細 データ型 サイズ(bit)
位置 X座標 float 16
Y座標 float 16
Z座標 float 16
RHW float 16
ブレンディングウェイト値 1〜5 float 各16
頂点法線 法線X float 16
法線Y float 16
法線Z float 16
ディフューズ色 RGBA long 32
スペキュラ色 RGBA long 32
テクスチャ座標セット1 1〜4から選択 float 16×選択数
...
テクスチャ座標セット8 1〜4から選択 float 16×選択数

 項目から必要なものだけを選択し、それをこの順に構造体に設定します。

 さて、衝突判定を行うためには頂点、ポリゴン、法線を扱うわけで、それ以外の情報はいりません。RHWというのは「座標変換済み頂点」に必要な値(前後関係を見るもの)でして、これを設定すると法線ベクトルを設定できなくなるため、これもいりません。ブレンディングウェイトは頂点ブレンディング(描画)時に必要になるものですが、衝突自体には必要ありません。当然、テクスチャもいりません。ただディフューズ色が無いと、例えばデバッグ時などに衝突判定を目で見れなくなりますので設定しておきます。結局、必要なものは、

 ・ 位置(X, Y, Z座標)
 ・ 頂点法線(法線X, Y, Z)
 ・ ディフューズ色

のみであることが分かります。よって、衝突判定用の頂点を表す構造体は次のように定義されます。

衝突判定用頂点構造体
struct COLLISIONVERTEX{
   // 頂点座標
   D3DXVECTOR3 m_v;

   // 法線
   D3DXVERTEX3 m_n;

   // ディフューズ色
   DWORD m_Color;
};

 D3DXVERTEX3はDirectX3Dで定義されている3次元ベクトルの構造体です。このようにDirectXと親和性を取っておくことによって「衝突判定用の頂点を外部から取り込める」「DirectXで定義されている有用なヘルパー関数を最大限に利用できる」という大きな利点を獲得できます。

 頂点を定義したら「この頂点を使いますよ」とフラグで示す必要があります。そのフラグはD3DFVFにまとめられています。上の構造体の場合、

#define  D3DFVF_COLLISION  D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE

とマクロで定義しておきます。これで頂点については準備万端です!



A ポリゴンの扱い

 頂点が並ぶと、そこにポリゴンができます。DirectXでは状況に合わせた柔軟なポリゴンを作成できます・・・いや、この表現は正しくありませんね。正しくは「柔軟に頂点の並びを解釈してポリゴンとみなします」。DirectXでは頂点の塊を「プリミティブ」と呼んでいます。プリミティブごとに頂点の解釈が変ります。DirectXで扱えるプリミティブは次の通りです。

 ・ ポイントリスト
 ・ ラインリスト
 ・ ラインストリップ
 ・ 三角形リスト
 ・ 三角形ストリップ
 ・ 三角形ファン

 これを見て分かるように、DirectXでは基本的に「3点以上の頂点を持つポリゴンを扱えません」。ポイントリストは頂点が1つのプリミティブ、つまり「点」です。ラインリストは2点で1つの線分を表現します。ラインストリップは線分が連続的に繋がった線ポリゴンです。ひも状の衝突に使えるかもしれません。三角形リストは3頂点で1つの三角形を定義します。三角形ストリップは蛇腹折りのように連続して繋がる三角形を定義する方法です。多角形ポリゴンを作成する時や長い板を作るときなどに重宝します。最後の三角形ファンは、1点を基準にして扇子のように三角形を並べる方法です。これにぴったりのオブジェクトが「ディスク」です。

 衝突判定用のオブジェクトはレンダリングしませんから、必ずしも上の方式に従う必要は無いのですが、扱っている頂点が何であるかを意識することは親和性を高める上でも重要でしょう。



B プリミティブと衝突用プリミティブの扱い

 衝突用プリミティブは基本的にはレンダリングしません。あくまでもキャラクタ同士の衝突判定に使用します。しかし、当然そのキャラクタ専用の衝突用プリミティブが定義されるのですから、レンダリングする頂点の情報と衝突用プリミティブは「同じオブジェクト内に保持する」べきでしょう。ただ、オブジェクトの中でこれらは明確に分離します。

 クラスの中でオブジェクトを分離するには2つの方法があります。1つは「多重継承」です。ただ、多重継承では複雑なオブジェクトを生成する度にクラスが山のようにできてしまいます。よって、衝突用プリミティブクラスを作成して、それをコンポジションとして保持する形になるでしょう。

 イメージとしてはこういう感じです。

class CCharacter
{
   sp<CCollisionObj> m_spCollObj;
   sp<CObj> m_spObj;
};

 sp<>というのはスマートポインタの事です。動的に生成したオブジェクトを保持する方法として一般的です。各コンポジションオブジェクトは頂点の情報を独自に持っていて、m_spObjの方はレンダリング用に、m_spCollObjの方は衝突判定用に使われます。当然ですが、双方の頂点が合っている必要は無く、衝突判定用オブジェクトは棒でも丸でも球でも何でも良いわけです。

 衝突判定用のオブジェクトは正直にポリゴンにしなくても良いでしょう。例えば球をポリゴンで表現するのはとても大変なのですが、衝突では球のポリゴン情報は1つも使いません。よって、CCollisionObjクラスには、衝突判定に必要な頂点(中心点や制御点など)の情報に合わせて付加する情報(半径、辺の長さなど)を保持すれば良いでしょう。



C 頂点変換の親和性

 レンダリングするポリゴン(プリミティブ)の頂点座標は「ローカル座標」に定義します。ローカル座標とは、そのプリミティブを世界の中心とする座標系の事です。このプリミティブを「ワールド座標」に変換するためには行列演算(ワールドトランスフォーム)を用います。この方法はDirectXの中で厳密に決まっていて、ワールド座標変換用の行列をD3DMATRIX構造体で定義し、IDirect3DDevice9::SetTransformメソッドを用いて設定します。

HRESULT SetTransform(      
    D3DTRANSFORMSTATETYPE State,     CONST D3DMATRIX *pMatrix );

 詳しい方法はここでは割愛しますが、ワールド座標に変換した時点で、オブジェクトは世界に置かれることになります。ということは、この段階で衝突判定を行わなくてはなりません。つまり、せっかくD3DMATRIX構造体で行列を定義するのですから、衝突判定用のオブジェクトもこの行列変換で擬似的に世界に置かれるように親和性を取るべきです。DirectXには行列演算用のヘルパー関数が山のように用意されています。これを使わない手はありません。
 ただ!1つ注意があります。これらのヘルパー関数は「Direct3Dエクステンション(D3DX)」という拡張機能で使用できます。D3DXでは行列は「D3DXMATRIX構造体」で定義し、ヘルパー関数はこの構造体を引数に取ります。よって、行列はすべてD3DXMATRIX構造体に統一するべきでしょう。ちなみに、D3DXMATRIX構造体はD3DMATRIX構造体から派生されているので、SetTransform関数などのD3D関数群全てで使用できます。

 次の章では、D3DXヘルパー関数の中から衝突判定に必要な関数の数々を紹介していきます。