ホーム < ゲームつくろー! < DirectX技術編 < アニメーションの根っこ:オブジェクトを読み込む
その25 アニメーションの根っこ:オブジェクトを読み込む
その24でアニメーション情報を格納してあるxファイルの中身を見て、アニメーションに必要な情報をまとめました。この章では実際にxファイルからアニメーション情報を取り出す方法部分を見てみます。この抽出については、ちゃんと関数が用意されています。D3DXの関数であるD3DXLoadMeshHierarchyFromX関数です。しかし、この関数は飛びっきり面倒な「癖」を持っています。話の中心は、その「癖」であるID3DXAllocateHierarchyインターフェイスの説明になります。気合入れて行きましょう!
@ D3DXLoadMeshHierarchyFromX関数
D3DXLoadMeshHierarchyFromX関数は、xファイル内に定義されているポリゴンメッシュのフレーム階層を読み込んで格納する関数です。まずは、その23でも取り上げたこの関数の定義から調べてみましょう。
D3DXLoadMeshHierarchyFromX関数 HRESULT D3DXLoadMeshHierarchyFromX(
LPCTSTR Filename,
DWORD MeshOptions,
LPDIRECT3DDEVICE9 pDevice,
LPD3DXALLOCATEHIERARCHY pAlloc,
LPD3DXLOADUSERDATA pUserDataLoader,
LPD3DXFRAME* ppFrameHeirarchy,
LPD3DXANIMATIONCONTROLLER* ppAnimController
);
Filenameにはxファイルを指定します。
MeshOptionというのはメッシュ作成のオプションです。D3DXMESH_MANAGEDを指定するのが一般的のようで、Direct3Dが管理するメモリ下に作成してくれます。
pDeviceはデバイスへのポインタです。これは問題ないですね。
pAllocはID3DXAllocateHierarchyインターフェイスへのポインタを渡します。これが今回の「肝」です。
pUserDataLoaderはxファイル内にユーザ定義部分があるとき、その情報をここに格納します。必要としないならNULLが指定できます。
ppFrameHeirarchyはD3DXFRAME構造体へのダブルポインタで、ここにフレーム階層の親分(ルートフレーム)へのポインタのポインタが返ります。D3DXFRAME構造体には兄弟フレームや子フレームへのポインタも含まれます。ここに、フレーム階層の全てが格納されるわけです。
ppAnimContorllerはID3DXAnimationControllerインターフェイスへのポインタで、xファイル内で定義されていたAnimationテンプレートの情報が格納されます。その23でも見たように、アニメーション部分はフレーム部分と独立に定義されるので、このアニメーション専用のインターフェイスが存在するわけです。
この関数を扱う上でのキーポイントはpAllocただ一つです。
A ID3DXAllocateHierarchyはユーザの実装が必要
D3DXLoadMeshHierarchyFromX関数の第4引数pAllocはID3DXAllocateHierarchyインターフェイスへのポインタを渡す仕様になっています。これは、ID3DXAllocateHierarchyの実体を作成してそのアドレスを渡すとOK・・・というわけにはいきません。というのは、インターフェイスは実体を持たない抽象クラスだからです。抽象クラスを、
ID3DXAllocateHierarchy AlllocHrcy;
と実体を宣言すると、コンパイラから「抽象オブジェクトは実体化できないよ」と怒られます。
ID3DXAllocateHierarchy *pAlllocHrcy;
とポインタ宣言することは可能です(ポインタは実体ではないので)。しかし、これをD3DXLoadMeshHierarchyFromX関数に渡す暴挙に出ると、関数内部でポインタが参照された瞬間メモリ保護違反でアプリケーションが止まります。つまり、ID3DXAllocateHierarchyからクラスを派生して、定義されている仮想関数を実際に実装する必要があります。これが、極上にめんどくさい部分なのです!!(T_T)
B CD3DXMyAllocateHierarchyクラスのヘッダー部
ID3DXAllocateHierarchyインターフェイスから派生されるクラスであるCMyAllocateHierarchyを作成しましょう。メンバ関数はID3DXAllocateHierarchyインターフェイスで定義されている純粋仮想関数です。それは、4つ定義されています:
・ CreateFrame関数
・ CreateMeshContainer関数
・ DestroyFrame関数
・ DestroyMeshContainer関数
CreateFrame関数はフレームオブジェクト(D3DXFRAME構造体)を作成します。
CreateMeshContainer関数はメッシュコンテナ(D3DXMESHCONTAINER構造体)を作成します。
DestroyFrame関数及びDestroyMeshContainer関数は、指定されたフレームやメッシュコンテナを削除します。
つまり、このインターフェイスはオブジェクトの作成と削除を担当する専門家です。
では、まずはヘッダファイルを見てみましょう。
MyAllocateHierarchy.h class CMyAllocateHierarchy : public ID3DXAllocateHierarchy
{
public:
CMyAllocateHierarchy();
virtual ~CMyAllocateHierarchy();
STDMETHOD(CreateFrame)(THIS_
LPCSTR Name,
LPD3DXFRAME *ppNewFrame
);
STDMETHOD(CreateMeshContainer)(THIS_
LPCSTR Name,
CONST D3DXMESHDATA *pMeshData,
CONST D3DXMATERIAL *pMaterials,
CONST D3DXEFFECTINSTANCE *pEffectInstances,
DWORD NumMaterials,
CONST DWORD *pAdjacency,
LPD3DXSKININFO pSkinInfo,
LPD3DXMESHCONTAINER *ppNewMeshContainer
);
STDMETHOD(DestroyFrame)(THIS_
LPD3DXFRAME pFrameToFree
);
STDMETHOD(DestroyMeshContainer)(THIS_
LPD3DXMESHCONTAINER pMeshContainerToFree
);
};
わかりやすくするためなるべく短く表記したいのですが、どうしてもこのくらいにはなってしまいます(上でぎりぎりです)。コンストラクタ・デストラクタ以外に、4つすべての仮想関数を宣言します。STDMETHODというのはCOMの実装経験がある方にはおなじみのマクロです。これは、
STDMETHOD(FuncName) --> virtual HRESULT __stdcall FuncName
と展開されます。仮想関数でHRESULTを返す__stdcallされる関数ということです。COM仕様のインターフェイスはすべてこの形式に従っていますので、マクロ化してちょっと楽してるわけです。もう1つ「THIS_」というマクロもありますが、これは
THIS_ -->
という感じで空っぽに展開されるようです。何もしないってことですね。
C CMyAllocateHierarchy::CreateFrame関数(実装部)
さて次に実装です。まだそれぞれの関数について良くわからないので、とりあえずスケルトンだけ作っておきましょう。次のような感じです。
MyAllocateHierarchy.cpp HRESULT CMyAllocateHierarchy::CreateFrame(THIS_
LPCSTR Name,
LPD3DXFRAME *ppNewFrame)
{
return D3D_OK;}
HRESULT CMyAllocateHierarchy::CreateMeshContainer(THIS_
LPCSTR Name,
CONST D3DXMESHDATA *pMeshData,
CONST D3DXMATERIAL *pMaterials,
CONST D3DXEFFECTINSTANCE *pEffectInstances,
DWORD NumMaterials,
CONST DWORD *pAdjacency,
LPD3DXSKININFO pSkinInfo,
LPD3DXMESHCONTAINER *ppNewMeshContainer)
{
return D3D_OK;}
HRESULT CMyAllocateHierarchy::DestroyFrame(THIS_
LPD3DXFRAME pFrameToFree)
{
return D3D_OK;}
HRESULT CMyAllocateHierarchy::DestroyMeshContainer(THIS_
LPD3DXMESHCONTAINER pMeshContainerToFree)
{
return D3D_OK;
}
すべての関数の戻り値はD3D_OKにします(これはマニュアルで指定されてます)。この状態でD3DXLoadMeshHierarchyFromX関数を動かしてみましょう。テストですから次のような簡便なプログラムを作成します。
メッシュ階層の読み込みテスト CMyAllocateHierarchy AH;
D3DXFRAME *pFR;
ID3DXAnimationController *pAC;
hRes = D3DXLoadMeshHierarchyFromX(
"tiny.x",
D3DXMESH_MANAGED,
m_cp3DDevice.GetPtr(),
&AH,
NULL,
&pFR,
&pAC
);
パスの通ったフォルダにtiny.x(サンプルのxファイル)を置きます。CMyAllocateHierarchyオブジェクトを1つ作って、それをD3DXLoadMeshHierarchyFromX関数に渡します。CMyAllocateHierarchyのメンバ関数のどれがどのタイミングで呼ばれるかをテストするため、クラスの実装部すべてにブレークポイントを置きます。それではデバッグ開始!
D3DXLoadMeshHierarchyFromX関数をデバッグすると最初に止まるのが「CreateFrame関数」です。
CreateFrame関数(定義) HRESULT CMyAllocateHierarchy::CreateFrame(THIS_
LPCSTR Name,
LPD3DXFRAME *ppNewFrame)
これはD3DXLoadMeshHierarchyFromX関数内部でCMyAllocateHierarchyオブジェクトが呼ばれたことを示しています。コールバック関数のようですね。引数Nameには"Scene_Root"が渡されていました。Scene_Rootはtiny.x内のルートフレームです。CreateFrame関数の役目はD3DXFRAME構造体を動的に生成することですから、唯一の情報であるScene_Rootという名前をD3DXFRAME構造体に格納し、第2引数にそのポインタ渡してみましょう。
CreateFrame関数 HRESULT CMyAllocateHierarchy::CreateFrame(THIS_
LPCSTR Name,
LPD3DXFRAME *ppNewFrame)
{
// フレームを新しく生成する
D3DXFRAME *p = new D3DXFRAME;
ZeroMemory( p, sizeof(D3DXFRAME) );
p->Name = new char[strlen(Name)+1];
strcpy(p->Name, Name);
*ppNewFrame = p;
return D3D_OK;
}
D3DXFRAME::NameはLPSTRですから文字列のポインタを持ちます。よって、文字の長さ文+1のメモリを確保して、strcpyでフレームの名前"Scene_Root"をコピーします。+1しないと、ナル文字が入るスペースが無くなるので注意です。
こう実装してデバッガを再度実行すると、Nameに「Scene_Root → body → ""(空っぽ)」と連続して入って来ます。とりあえずはこれでうまく動いているようです。その後、CreateMeshContainer関数でブレークしました。新しい局面です。
D CMyAllocateHierarchy::CreateMeshContainer関数(実装部)
ブレークポイントで止まったCreateMeshContainer関数の引数には、次のような沢山の情報が渡されていました。
引数 | 内容 | ||
Name | "" | ||
pMeshData | Type | D3DXMESHTYPE_MESH | |
pMesh | 0x01cc22b0 (インターフェイス) | ||
pMaterials | MatD3D | マテリアル情報が色々と | |
pTextureFilename | Tini_skin.dds | ||
pEffectInstances | pEffectFilename | NULL | |
NumDefaults | 6 | ||
pDefaults | pParamName | Diffuse | |
Type | D3DXDT_FLOATS | ||
NumBytes | 16 | ||
pValue | 0x01cc48d4 | ||
NumMaterials | 1 | ||
pAdjacency | 0x01a4f518 | ||
pSkinInfo | 0x01cc4610 (インターフェイス) |
いやぁ、なんとも目がくらみますね(*-*)。でも、少しずつ噛み砕けば大丈夫です。
どうやらこれは、Tinyのポリゴンメッシュに関係する情報がずらっと揃っているようです。CreateMeshContainer関数の役目は関数内部で生成したD3DXMESHCONTAINER構造体にこれら情報をコピーし格納することです。やることが沢山あるので、少しずつプログラムを作っていきましょう。
まず、何は無くともD3DXMESHCONTAINER構造体を動的に生成します。
D3DXMESHCONTAINERオブジェクトの動的生成 // メッシュコンテナオブジェクトの生成
D3DXMESHCONTAINER *p = new D3DXMESHCONTAINER;
この構造体に引数の情報を全て格納します。
○ 名前
Name | "" |
引数Nameの内容はD3DXMESHCONTAINER::Nameに格納します。これはCreateFrameと同じですね。
Nameの格納
p->MeshData = *pMeshData;
p->Name = new char[ strlen(Name)+1 ];
strcpy( p->Name, Name );
○ メッシュデータ
pMeshData | メンバ | 引数の内容 | |
Type | D3DXMESHTYPE_MESH | ||
pMesh | 0x01cc22b0 (インターフェイス) | ||
次のpMeshDataはD3DXMESHDATA構造体へのポインタです。
D3DXMESHDATA構造体 typedef struct D3DXMESHDATA {
D3DXMESHDATATYPE Type;
union {
LPD3DXMESH pMesh;
LPD3DXPMESH pPMesh;
LPD3DXPATCHMESH pPatchMesh;
};
} D3DXMESHDATA, *LPD3DXMESHDATA;
この構造体にはポリゴンメッシュのタイプを表すType、そして共用体として3つのメッシュインターフェイスへのポインタが定義されています。メッシュには通常メッシュ、プログレッシブメッシュそしてパッチメッシュという3タイプがありまして、そのどれかがここの共用体に収容されているわけです(共用体についてはここでは冗長になりますのでMSDNで検索してみてください)。pMeshDataに渡されたこれらの情報はD3DXMESHCONTAINER::MeshDataに格納します。
渡されたメッシュが何であるかはTypeに示されますので、これで条件分岐します。
pMeshData p->MeshData.Type = pMeshData->Type;
// 通常メッシュ
if(pMeshData->Type == D3DXMESHTYPE_MESH){
p->MeshData.pMesh = pMeshData->pMesh;
p->MeshData.pMesh->AddRef();
}
// プログレッシブメッシュ
else if(pMeshData->Type == D3DXMESHTYPE_PMESH){
p->MeshData.pPMesh = pMeshData->pPMesh;
p->MeshData.pPMesh->AddRef();
}
// パッチメッシュ
else{
p->MeshData.pPatchMesh = pMeshData->pPatchMesh;
p->MeshData.pPatchMesh->AddRef();
}
D3DXLoadHierarchyFromX関数内部で生成されたメッシュインターフェイスは、すでに参照カウンタが1つ増えていますが、D3DXLoadHierarchyFromX関数を抜けるときに参照カウンタは減らされているようです。よって、ここで必ず参照カウンタを1つ増やす必要があります。
○ マテリアルデータ
pMaterials | メンバ | 引数の内容 | |
MatD3D | マテリアル情報が色々と | ||
pTextureFilename | Tini_skin.dds |
これはメッシュに適用されているマテリアルの情報です。マニュアルを見るとここは「Array of materials used in the mesh.」すなわち「メッシュ内で使用されているマテリアルの配列」とあります。pMaterialsはD3DXMATERIAL構造体の配列の先頭ポインタというわけです。では、配列の数はいくつなのか?それは引数のNumMaterialsにあります。便利に出来ていますね(^-^)
マテリアル情報はD3DXMESHCONTAINER::pMaterialsに格納します。配列のコピー(DeepCopy)なのでメモリコピーをしたくなりますが、例えばpTextureFilenameなどは文字列へのポインタなので、そのままメモリコピーをするとポインタ共有の罠にはまります。ここは、落ち着いてしくしくとコピーしていきましょう。
pMaterials // 配列の確保
p->pMaterials = new D3DXMATERIAL[ NumMaterials ];
for(int i=0; i<NumMaterials; i++){
p->pMaterials[i].MatD3D = pMaterials[i].MatD3D;
p->pMaterials[i].pTextureFilename = new char[ strlen(pMaterials[i].pTextureFilename)+1];
strcpy( p->pMaterials[i].pTextureFilename, pMaterials[i].pTextureFilename);
}
○ エフェクトデータ
pEffectInstances | メンバ | 引数の値 | |
pEffectFilename | NULL | ||
NumDefaults | 6 | ||
pDefaults | pParamName | Diffuse | |
Type | D3DXDT_FLOATS | ||
NumBytes | 16 | ||
pValue | 0x01cc48d4 |
次はエフェクトの情報です。エフェクトとはレンダリングをカスタマイズする専用のプログラムのようなもので、外部ファイルに記録されます。pEffectFilenameはエフェクトファイルの名前、NumDefaultsは次のpDefaults(D3DXEFFECTDEFAULT構造体)の配列の要素数を表しています。pDefaultsは配列の先頭ポインタです。D3DXEFFECTDEFAULT構造体の中身で、pParamNameはパラメータに付けられた名前、Typeはそのパラメタの型情報、NumBytesはその大きさ、そしてpValueがその値を指すポインタとなります。何だかややこしい感じがしますが、落ち着いてコピーしていけば問題ありません。コピー先はD3DXMESHCONTAINER::pEffectsです。
pEffectInstnaces const D3DXEFFECTINSTANCE *pEI = pEffectInstances; // 長いので
p->pEffects = new D3DXEFFECTINSTANCE;
p->pEffects->pEffectFilename = CopyStr(pEI->pEffectFilename);
p->pEffects->NumDefaults = pEI->NumDefaults;
p->pEffects->pDefaults = new D3DXEFFECTDEFAULT[pEI->NumDefaults];
D3DXEFFECTDEFAULT *pDIST = pEI->pDefaults; // コピー元
D3DXEFFECTDEFAULT *pCOPY = p->pEffects->pDefaults; // コピー先
for(unsigned int i=0; i<pEI->NumDefaults; i++)
{
pCOPY[i].pParamName = CopyStr(pDIST[i].pParamName);
DWORD NumBytes = pCOPY[i].NumBytes = pDIST[i].NumBytes;
pCOPY[i].Type = pDIST[i].Type;
if(pDIST[i].Type <= D3DXEDT_DWORD){
pCOPY[i].pValue = new DWORD[ NumBytes ];
memcpy( pCOPY[i].pValue, pDIST[i].pValue, NumBytes);
}
}
ちょっと見た目ごちゃごちゃですが、やっていることは単純なディープコピー(ハードコピー)です。名前のコピーが面倒になってきたのでCopyStr関数を作りました。やっていることは、メモリを確保して名前をハードコピーし、そのポインタを返すだけです。
○ マテリアルの数
NumMaterials | 1 |
これは問題ありませんね。格納先はD3DXMESHCONTAINER::NumMaterialsです。
NumMaterials p->NumMaterials = NumMaterials;
○ 隣接ポリゴンインデックス
pAdjacency | 0x01a4f518 |
次はある1つの三角ポリゴンに隣接する3つのポリゴンインデックス番号を格納した「隣接ポリゴンインデックス」です。これはDWORDの3つの塊を1つとしてポリゴン数だけ存在するでっかい配列になっています。メッシュを構成する三角ポリゴンの数は最初の方で格納したpMeshData->pMesh->GetNumFaces関数で取得できます。この情報の格納先はD3DXMESHCONTAINER::pAdjacencyです。
pMeshData // ポリゴン数
DWORD NumPolygon = pMeshData->pMesh->GetNumFaces();
// 配列の確保
p->pAdjacency = new DWORD[ NumPolygon * 3];
// コピー
memcpy( p->pAdjacency, pAdjacency, NumPolygon * 3 * sizeof(DWROD));
「*3」がポイントですね。
○ スキン情報
pSkinInfo | 0x01cc4610 (インターフェイス) |
最後はスキン情報です。ここがNULLなら、スキンの情報はありませんが、tiny.xはスキン情報を持っているので、ID3DXSkinInfoインターフェイスへのポインタが存在します。インターフェイスなのでそのままコピーして、参照カウンタを1つ増やしましょう。
pSkinInfo p->pSkinInfo = pSkinInfo;
p->pSkinInfo->AddRef();
後は忘れずに作成したメッシュコンテナ構造へのポインタを引数のppNewMeshContainerに渡しましょう。
*ppNewMeshContainer = p;
return S_OK;
さてこれで生成に関する情報はすべてメッシュコンテナ構造体に格納し終わりました。デバッグを続けるとD3DXLoadMeshHierarchyFromX関数を抜けることができます。関数はS_OKを返し、そして出力としてppFrameHeirarchyにD3DXFRAMEへのポインタ、ppAnimContorllerにID3DXAnimationControllerインターフェイスへのポインタを渡してくれます。ppFrameHeirarchyの内部をデバッガで見てみると、な〜んとフレームの階層構造を全部きっちりと繋げてくれています!これはすばらしい(^-^)b!
E CD3DXMyAllocateHierarchy::DestroyFrame関数(実装部)
ところで、このまま実行するとプログラムは終了してしまいます。ここで「あれ?」っと思いました。確かDestroyFrame関数とDestroyMeshContainer関数にブレークを張っていたのに、ひっかかってこなかったのです。そりゃ当然、だって誰も呼んでいないのですから(笑)。自らが確保したメモリや関数内でもらった数々のインターフェイスを開放しなければ、メモリリークの大フィーバーになってしまいます。
削除するタイミングについては後で考えることにして、とりあえず今は指定されたオブジェクトをすっきりと消してくれるDestroyFrame関数およびDestroyMeshContainer関数を作成しましょう。
まずはDestroyFrame関数です。
DestroyFrame関数 HRESULT CD3DXMyAllocateHierarchy::DestroyFrame(
LPD3DXFRAME pFrameToFree
);
引数のD3DXFRAME構造体は次のように定義されています。
D3DXFRAME構造体 typedef struct D3DXFRAME {
LPSTR Name;
D3DXMATRIX TransformationMatrix;
LPD3DXMESHCONTAINER pMeshContainer;
D3DXFRAME * pFrameSibling;
D3DXFRAME * pFrameFirstChild;
} D3DXFRAME, *LPD3DXFRAME;
Nameは確かchar型の配列として動的にメモリを確保しているのでdeleteする必要があります。
TransformationMatrixは特に何もしなくてOK。
pMeshContainerは消すものが山ほどありますから、DestroyMeshContainer関数に削除作業を委託します。
pFrameSiblingとpFrameFirstChildはともにD3DXFRAME構造体なので、自分自身であるDestroyFrame関数で削除します。再帰関数として使うわけです。
実装は次のようになります。
DestroyFrame関数 HRESULT CMyAllocateHierarchy::DestroyFrame(THIS_ LPD3DXFRAME pFrameToFree)
{
if(pFrameToFree->Name)
delete[] pFrameToFree->Name;
if(pFrameToFree->pMeshContainer)
DestroyMeshContainer(pFrameToFree->pMeshContainer);
if(pFrameToFree->pFrameSibling)
DestroyFrame(pFrameToFree->pFrameSibling);
if(pFrameToFree->pFrameFirstChild)
DestroyFrame(pFrameToFree->pFrameFirstChild);
delete pFrameToFree;
return D3D_OK;
}
淡々と消してます。
F CD3DXMyAllocateHierarchy::DestroyMeshContainer関数(実装部)
消去の山場はこっちです。実に沢山のメモリをちまちまと確保しているので、漏らすことなく消さないといけません。まずは、動的確保したものを整理してみましょう。
D3DXMESHCONTAINER::
Name
MeshData.pMesh (インターフェイス)
pMaterials配列 (要素数NumMaterials)
pMaterials.pTextureFilename
pEffects
pEffects.pEffectFilename
pEffects.pDefaults配列 (要素数pEffects.NumDefaults)
pEffects.pDefaults.pParamName
pEffects.pDefaults.pValue (サイズpEffects.pDefaults.NumBytes)
pAdjacency配列 (要素数MeshData.pMesh->GetFacesNum() * 3)
pSkinInfo (インターフェイス)
こんなにあります。これらを正しく開放していきます。インターフェイスは参照カウンタを1つ下げるだけです。pMaterials配列はまずそのメンバ変数であるpMaterials.pTextureFilenameを削除してから配列自体を削除します。pEffectsもpEffectFilenameを開放してからです。メンバ内のポインタ変数が動的に確保したメモリを指しているときは、忘れずにそのメモリを開放していかないと気付き難いメモリリークが発生します。
DestroyMeshContainer関数 HRESULT CMyAllocateHierarchy::DestroyMeshContainer(THIS_ LPD3DXMESHCONTAINER pMeshContainerToFree)
{
D3DXMESHCONTAINER *p = pMeshContainerToFree; // 長いので
delete[] p->Name;
SAFE_RELEASE(p->MeshData.pMesh)
for(unsigned int i=0; i<p->NumMaterials; i++){
delete[] p->pMaterials[i].pTextureFilename;
}
delete[] p->pMaterials;
// エフェクト
int i;
for(i=0; i<p->pEffects->NumDefaults; i++){
delete[] p->pEffects->pDefaults[i].pParamName;
delete[] p->pEffects->pDefaults[i].pValue;
}
delete[] p->pEffects->pEffectFilename;
delete[] p->pEffects->pDefaults;
delete p->pEffects;
delete[] p->pAdjacency;
SAFE_RELEASE(p->pSkinInfo);
return D3D_OK;
}
これで削除したいときはCD3DXMyAllocateHierarchy::DestroyFrame関数を呼び出すだけで良くなります。
G もらったフレーム階層情報はいつ消すのか?
@〜Fまでで、xファイル内のフレーム情報をアプリケーションに完全に読み込むことができるようになりました。ところで、この情報は誰がいつまで使うのでしょうか?いわずもがな、それはポリゴンオブジェクトが消滅するまで使用します。つまり、フレーム階層情報の寿命はポリゴンオブジェクトの寿命と等しいと言えます。この情報を消すタイミングがそこにあるわけです。具体的には、ポリゴンオブジェクトのデストラクタ辺りで、
CD3DXMyAllocateHierarchy AH;
AH.DeleteFrame( pRootFrame );
と記述することになります。となると、自ずとこれらの情報はポリゴンオブジェクトのクラス内にメンバ変数として保持されることになります。アニメーションまで考えると、ポリゴンオブジェクトのクラスの作り方も随分と変ってきますね。
H そもそもどうしてインターフェイスの実装が必要なの?
ここまでやってきた面倒なID3DXAllocateHierarchyインターフェイスの実装をプログラマにわざわざ要求するのはなぜなのでしょうか?これは、D3DXFRAMEやD3DXMESHCONTAINER構造体が拡張される事があるためです。例えば、D3DXFRAME::TransformationMatrixに格納されるのは親→子の座標変換行列ですが、インバースキネマティクスをする時には、子フレームから親フレームへの逆方向の座標変換が必要になったりします。その変換行列を格納するのはD3DXFRAMEの中が最適なわけでして、継承したくなってくるわけです。
struct D3DXFRAME_IK : public D3DXFRAME
{
D3DXMATRIX InvTransformationMatrix; // 逆座標変換
}
こうなると、作成したCD3DXMyAllocateHierarchy::CreateFrame関数内部で動的に生成するのはD3DXFRAMEではなくてD3DXFRAME_IKということになります。こういったユーザの自由度を持たせるため、わざわざ実装を要求しているわけです。
アニメーションするというのは、なかなかにして面倒なものですねぇ・・・。特に外部にある情報が膨大なだけに、それを読み込んだり削除したりするのも非常に乱雑になってしまいます。ただ、D3DXLoadMeshHierarchyFromX関数は、そのファイルアクセス・読み込み部分を内部で処理してくれて、大分ユーザを楽させてくれているのも確かです。考えてみると、あの膨大な情報を格納するXファイルから情報を切り分ける作業は・・・嫌ですね(^-^;
さて次は、苦労の果てに頂いた貴重な
LPD3DXFRAME* ppFrameHeirarchy,
LPD3DXANIMATIONCONTROLLER* ppAnimController
について見ていくことにしましょう。
I 謝辞
本章内文章において、皆様から幾つかの箇所に対して誤記の報告を頂き、修正する事ができました。この場を借りて厚くお礼申し上げます。