This is not going to be a full explanation of the .X file format and the capabilities of the format. This tutorial is to help setup a working model and play animations for that model while using the .X format. This tutorial assumes that you already have an exported model with animation in the .X format. If you do not have a model or an exporter you can use the tiny.x file that is provided with this tutorial or by the DirectX SDK. I also assume that you have a working Direct3D window. In the tutorial example code you will find a file called main.cpp, which can be used for setting up a basic view window.
The .X file format stores everything in a hierarchical method. Each level of the hierarchy can have any number of "objects" to hold data, but usually it is broken down enough to where there are only a few "objects" on the same level. The main "object" of the model geometry hierarchy is a frame. For animation, the main "object" of the hierarchy is an animation set. The frame structure is defined bellow. We will be doing some altering to this structure later. typedef struct _D3DXFRAME {
LPTSTR Name; //The name of the frame
D3DXMATRIX TransformationMatrix; //The frame transform
LPD3DXMESHCONTAINER pMeshContainer; //The mesh container
struct _D3DXFRAME *pFrameSibling; //A frame on the same level
struct _D3DXFRAME *pFrameFirstChild; //The frame one level down
} D3DXFRAME, *LPD3DXFRAME;
An animation set does not have a structure that defines what it contains, but if you open and look at the .X file you will see that an animation set contains at least one animation which is a structure that contains all the animation data. In the DirectX .X format animation can be stored as individual scaling, transformation, or rotation data or as a matrix that does all three. The exporter that I have used in the past uses the method of saving out each individual scaling, transformation, or rotation data. The function that we are doing to work with, to load the model, handles both styles of animation storage. There will be more of an explanation of this when we start to animate the loaded model.
Setup:
To load a model you can either write a parser that will search an .X file for all the frames and put them into a hierarchy for you or use a function that is located in the d3dx9anim.h file. I will be using the function D3DXLoadMeshHierarchyFromX, because I don抰 want to turn this tutorial into a book. The D3DXLoadMeshHierarchyFromX function takes 7 parameters as defined by the header: HRESULT D3DXLoadMeshHierarchyFromX(
LPCTSTR Filename,
DWORD MeshOptions,
LPDIRECT3DDEVICE9 pDevice,
LPD3DXALLOCATEHIERARCHY pAlloc,
LPD3DXLOADUSERDATA pUserDataLoader,
LPD3DXFRAME* ppFrameHeirarchy,
LPD3DXANIMATIONCONTROLLER* ppAnimController
);
The filename is the path to the .X file you want to load, the Mesh Options are the option flags you want to pass to the function, the device is a created and initialized direct3d 9 device, the alloc parameter is an allocation class that handles creation and deletion of frames and mesh containers, the userdataloader is a pointer to a class that allows you to load your own registered tem plated data from the .X file format, the frame hierarchy is the passed in root of the frame hierarchy, and the animcontroller is an animation controlling device that is defined in DirectX.
First thing we need to look at are the two structures that we will be using throughout this tutorial, D3DXFRAME and D3DXMESHCONTAINER. These structures have almost everything we need, but I am going add to these structures. To the D3DXFRAME structure I will add a matrix object called matCombined. This matrix will be used to make sure that transformation, scaling, or rotation is passed down the entire hierarchy of the "object". Also, I created some names for this structure so it would be easier for us to see where we are using our derived structures. It looks like this: typedef struct _D3DXFRAME_DERIVED: public D3DXFRAME
D3DXMATRIX matCombined; //Combined Transformation Matrix
}FRAME, *LPFRAME;
Next we look at the D3DXMESHCONTAINER. In this structure we will add more things to fill out the structure to something we can more easily use. typedef struct _D3DXMESHCONTAINER_DERIVED: public D3DXMESHCONTAINER
{
//Mesh variables
LPDIRECT3DTEXTURE9* ppTextures; // Textures of the mesh
D3DMATERIAL9* pMaterials9; // Use the DirectX 9 Material type
//Skinned mesh variables
LPD3DXMESH pOrigMesh; // The original mesh
D3DXMATRIX* pBoneMatrices; // The bones for the mesh
D3DXMATRIX* pBoneOffsets; // The bone matrix Offsets
D3DXMATRIX* pFrameMatrices; // The Frame Matrix
}MESHCONTAINER, *LPMESHCONTAINER;
Now that we have the more fleshed out structures lets look at the pAlloc parameter of the D3DXLoadMeshHierarchyFromX function. As I said earlier this is a pointer to an allocation class that handles all the creation and deletion of frames and mesh containers. The only problem here is that LPD3DXALLOCATEHIERARCHY points to an abstract class that is not defined only prototyped. The good thing is that we do not need to call any of the allocation class?functions in our code. The functions that use the allocation class will call them all for us. So, let us derive off the ID3DXALLOCATEHIERARCHY object and create the class for ourselves. The allocation class should look like this. class CAllocateHierarchy: public ID3DXAllocateHierarchy
{
public:
// Create a frame
//1. The name of the frame
//2. The output new frame
STDMETHOD(CreateFrame)(THIS_ LPCTSTR Name,
LPD3DXFRAME *ppNewFrame);
// Create a Mesh Container
//1. Name of the Mesh
//2. The mesh Data
//3. that materials of the mesh
//4. the effects on the mesh
//5. the number of materials in the mesh
//6. the adjacency array for the mesh
//7. the skin information for the mesh
//8. the output mesh container
STDMETHOD(CreateMeshContainer)(THIS_ LPCTSTR Name,
LPD3DXMESHDATA pMeshData,
LPD3DXMATERIAL pMaterials,
LPD3DXEFFECTINSTANCE pEffectInstances,
DWORD NumMaterials,
DWORD *pAdjacency,
LPD3DXSKININFO pSkinInfo,
LPD3DXMESHCONTAINER *ppNewMeshContainer);
// Destroy a frame
//1. The frame to delete
STDMETHOD(DestroyFrame)(THIS_ LPD3DXFRAME pFrameToFree);
// Destroy a mesh container
//1. The container to destroy
STDMETHOD(DestroyMeshContainer)(THIS_
LPD3DXMESHCONTAINER pMeshContainerBase);
};
You can reference the tutorial example code if you want to see all the allocation function code, but here is pseudo code for each of the functions. //Callback function used by the load hierarchy function to create each frame
CreateFrame
{
// Create a frame using the derived structure
// Initialize the passed in frame to NULL
// Put the name in the frame
// Initialize the rest of the frame to NULL
// Set the output frame to the one that we created
}
//Callback function used by the D3DXDestroyFrame function
DestroyFrame
{
//Convert the frame to the derived structure
// Delete the name
// Delete the frame
}
//Callback function used by the load hierarchy function to create each mesh
CreateMeshContainer
{
// Create a Temp mesh container using my derived structure
// Initialize passed in Container to NULL
// Put the name in the mesh container
// Get the number of Faces for adjacency
//Get the number of materials
//Create the arrays for the materials and the textures
// create the array for the adjacency data multiply by 3 because there are
// three adjacent triangles
// Copy all the Adjacency data
// Zero out the textures
// Copy the materials
for(DWORD i=0; iNumMaterials; i++)
{
// Copy the material
// Set the ambient color for the material
}
// Put the mesh into the mesh container
//Get the D3D device from the mesh
//For all the materials
for(i=0; iNumMaterials; i++)
{
//Use D3DXCreateTextureFromFile to load the texture and put it
// in to the mesh container
}
//Release the D3D device
if(pSkinInfo)
{
// first put the SkinInfo into the mesh container
// create a copy of the mesh and set it to the pOrigMesh
// Get the number of bones from SkinInfo
// create the array of bone offsets
// for each bone get each of the bone offset matrices so that we
// don't need to get them later
for (UINT i = 0; i < uBones; i++)
{
// Set the bone offsets
}
}
else
{
//initialize the rest to NULL
}
// Set the output mesh container to the temp one
}
//Callback function used by the D3DXDestroyFrame function
DestroyMeshContainer
{
//Convert to my derived structure type
// Delete name
// Delete materials
//Release the textures
// Delete textures
// Delete bones in the mesh
// Delete adjacency data
// Delete bone offsets
// Delete frame matrices
// Delete mesh
// Delete skin information
// Delete copy of the mesh
//Delete the mesh container
}
Now that we have that all set up we can now get to more fun things. Next we will load a model and display it to the screen.
Loading a Model:
I am of the mind that the use of classes is a good thing, so I am going to create a wrapper class called CModel that will handle all the model loading, drawing, and later animation. The definition of my class looks something like this: class CModel
{
private:
LPD3DXFRAME m_pFrameRoot;
void DrawMesh(LPMESHCONTAINER pMeshContainer,
LPD3DXMATRIX pMatrix);
void DrawFrame(LPFRAME pFrame);
void SetupBoneMatrices(LPFRAME pFrame,
LPD3DXMATRIX pParentMatrix);
void SetupBoneMatricesOnMesh(LPMESHCONTAINER pMeshContainer);
public:
CModel();
virtual ~CModel();
void Draw(void);
void LoadXFile(char* strFileName,
LPDIRECT3DDEVICE9 pd3dDevice);
};
|