Extending D3DXMESHCONTAINER
Whereas you might be used to using the ID3DXMesh object to contain your mesh
data, you may have found
it a pain to store the mesh's material and effects data separately. Not only
that, but what about using the other
D3DX mesh objects, such as ID3DXPMesh and ID3DXSkinMesh? Why not just create a
single mesh
object that represents all mesh types and contains all material data along with
it?
In fact, there is such an object−it's called D3DXMESHCONTAINER! The
D3DXMESHCONTAINER object
stores a pointer to your mesh data (regardless of the mesh type used) and all
material and effects data. It also
contains pointers to your mesh's adjacency buffer and skinned mesh data object.
And as if that wasn't enough,
the D3DXMESHCONTAINER contains pointers to form a linked list of mesh objects.
typedef struct D3DXMESHCONTAINER {
LPSTR Name;
D3DXMESHDATA MeshData;
LPD3DXMATERIAL pMaterials;
LPD3DXEFFECTINSTANCE pEffects;
DWORD NumMaterials;
DWORD * pAdjacency;
LPD3DXSKININFO pSkinInfo;
D3DXMESHCONTAINER * pNextMeshContainer;
} D3DXMESHCONTAINER, *LPD3DXMESHCONTAINER;
What could I possibly do to extend the usefulness of the already nifty
D3DXMESHCONTAINER, you ask?
Well, for one thing, D3DXMESHCONTAINER has no default constructor or destructor.
Also, textures data is
missing−there's only a buffer that contains the names of the textures to use for
the mesh. Last, there's no
support for storing skinned mesh animation data.
No problem, because extending the D3DXMESHCONTAINER is simple! The new
version, which I call
D3DXMESHCONTAINER_EX, adds a total of four data objects and three functions. The
data objects include
an array of texture objects, a skinned mesh object (to store an animated skinned
mesh), and two arrays of
matrix objects.
Here's how I defined the D3DXMESHCONTAINER_EX object, as well as declaring
the four variables I
mentioned:
//-------------------------------------------------------------------------------------------
// Declare an extended version of D3DXMESHCONTAINER that contains a constructor
and destructor
// as well as an array of textures, a mesh object that contains the generated
skin mesh, and
// matrices that map to the frame hierarchy's and for updating bones.
//-------------------------------------------------------------------------------------------
struct D3DXMESHCONTAINER_EX : public D3DXMESHCONTAINER
{
IDirect3DTexture9** textures;
ID3DXMesh* skin_mesh;
D3DXMATRIX** frame_matrices;
D3DXMATRIX* bone_matrices;
The textures array of pointers contains the
texture objects used to render the mesh. I build the
textures array up by first loading a mesh and then querying the texture buffer
(D3DXMESHCONTAINER::pMaterials) for the file names of the textures to use.
As for skin_mesh, you only use it when you are
using a skinned mesh You see, when loading a skinned mesh, the actual mesh
data is stored in D3DXMESHCONTAINER::MeshData::pMesh. The only problem is, you
need another mesh container to store the skinned mesh as it is animated. That is
the purpose of skin_mesh.
Last, you'll find frame_matrices and
bone_matrices. Not to drag it out, but these are
also used for
skinned meshes. Just so it makes sense at this point, a skinned mesh animates by
attaching the vertices of the mesh to an underlying hierarchy of bones. As the
bones move, so do the vertices. ppFrameMatrices and pBoneMatrices are used to
map the vertices to
the bones.
Aside from the variables in D3DXMESHCONTAINER_EX, there are also a few
functions. The first two are the
constructor and destructor:
D3DXMESHCONTAINER_EX()
{
ZeroMemory(this, sizeof(*this));
}
~D3DXMESHCONTAINER_EX()
{
if(textures && NumMaterials)
{
for(DWORD i = 0; i < NumMaterials; i++)
release_com(textures[i]);
}
delete[] textures; textures = NULL;
NumMaterials = 0;
delete[] Name; Name = NULL;
delete[] pMaterials; pMaterials = NULL;
delete pEffects; pEffects = NULL;
delete[] pAdjacency; pAdjacency = NULL;
delete[] frame_matrices; frame_matrices = NULL;
delete[] bone_matrices; bone_matrices = NULL;
release_com(MeshData.pMesh);
release_com(pSkinInfo);
release_com(skin_mesh);
delete pNextMeshContainer; pNextMeshContainer = NULL;
}
The constructor and destructor have the task of initializing the data to a
known state and releasing the data
used by the object, respectively. Basically,
release_com is a macro that safely releases a COM interface and sets the
interface pointer to NULL.
The third function in D3DXMESHCONTAINER_EX is find,
which lets you scan the linked list of meshes for
a specifically named mesh, much like D3DXFRAME_EX::find.
A quick string compare is used to check the
names, and a recursive call to find is used to scan
the entire linked list.
D3DXMESHCONTAINER_EX* find(const char* mesh_name)
{
// return this mesh instance if name matched
if(Name && mesh_name && !strcmp(mesh_name, Name))
return this;
// scan next in list
if(pNextMeshContainer)
return ((D3DXMESHCONTAINER_EX*) pNextMeshContainer)->find(mesh_name);
return NULL; // no found
}
And that does it for the helper objects! The D3DXFRAME_EX and
D3DXMESHCONTAINER_EX objects are
extremely helpful when it comes to dealing with Direct3D; as such, you should
spend as much time as you
can getting used to these two objects. I think you'll find them very useful in
your own projects.
Aside from the helper objects, there are a number of helper functions that
I'd like to introduce to you, which
should help you alleviate the mundane tasks common to most Direct3D−related
projects.