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 donAEt 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 classAE 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);
};
You may notice that the m_pFrameRoot variable is not of my derived frame type. This is because the D3DXLoadMeshHierarchyFromX function takes a variable of the LPD3DXFRAME type as a parameter. Do not worry though, as shown in the pseudo code for the allocation class we convert and setup the frame to the derived structure. What follows is pseudo code that shows what each function in the class will do. Again, if you want to see the completed code you can reference the tutorial example code.
//Load the model into the frame hierarchy and the animation cotroller
LoadXFile
{
// Create an object of the Allocation class
//Load the model
D3DXLoadMeshHierarchyFromX(// Path of File to load,
// Load Options (0),
// D3D Device,
// Hierarchy allocation class,
// Effects (NULL),
// Frame hierarchy root,
// Animation Controller (NULL)
);
//Setup the bones
SetupBoneMatrices((LPFRAME)m_pFrameRoot,
&m_pFrameRoot->TransformationMatrix);
}
//Set the matrices for the frames (bones)
SetupBoneMatrices
{
if(pParentMatrix)
{
// Set the pFrame->matCombined matrix to the parent times the
// frame transformation matrix
}
else
// Set the pFrame->matCombined to the frame transformation
// Create a variable to hold the mesh container that is in the frame
//if there is a mesh try to Setup the bones
SetupBoneMatricesOnMesh((LPMESHCONTAINER)pMesh);
//Check your Brother
SetupBoneMatrices((LPFRAME)pFrame->pFrameSibling, pParentMatrix);
//Check your Child
SetupBoneMatrices((LPFRAME)pFrame->pFrameFirstChild,
&pFrame->matCombined);
}
//Set the frame (bone) matrices to the mesh
SetupBoneMatricesOnMesh
{
//Make sure there is skin information
if(pMeshContainer->pSkinInfo)
{
//Get the number of bones
//Create the bone matrices
//Create the frame array
//Create a frame object
//Set the bone matrices
for (DWORD i = 0; i < cBones; i++)
{
// Find the frame
pFrame = (LPFRAME)D3DXFrameFind(m_pFrameRoot,
pMeshContainer->pSkinInfo>GetBoneName(i));
//set the bone to the combined matrix of the frame
//set the frame matrix
D3DXMatrixMultiply
(
&pMeshContainer->pFrameMatrices,
&pMeshContainer->pBoneOffsets,
&pMeshContainer->pBoneMatrices
);
}
}
}
//Draw the frame hierarchy
Draw
{
//if there is a frame hierarchy
DrawFrame((LPFRAME)m_pFrameRoot);
}
//Recursive loop through the frame hierarchy and draw the frame with meshes
DrawFrame
{
//Create a variable to hold the mesh container of the frame
LPD3DXMESHCONTAINER pMesh = pFrame->pMeshContainer;
//While there are mesh containers try to draw
{
DrawMesh((LPMESHCONTAINER)pMesh,
&pFrame->TransformationMatrix);
//Go to the next one
pMesh = pMesh->pNextMeshContainer;
}
//Check your brother
DrawFrame((LPFRAME)pFrame->pFrameSibling);
//Check your Child
DrawFrame((LPFRAME)pFrame->pFrameFirstChild);
}
//Draw the mesh with the correct material and texture
DrawMesh
{
//if there is original mesh data
{
// Using the original mesh
//Get the D3D device to draw with from the mesh
//Move to where the mesh should be
D3DDevice->SetTransform(D3DTS_WORLD, pMatrix);
//Draw each mesh subset with correct materials and texture
for (DWORD i = 0; i < pMeshContainer->NumMaterials; i++)
{
//set the material from the mesh container
//set the texture from the mesh container
//draw the subset of the mesh
}
//Release the device
}
else
{
// using the mesh container's mesh data's mesh
//Get the D3D device to draw with from the mesh
//Move to where the mesh should be
D3DDevice->SetTransform(D3DTS_WORLD, pMatrix);
//Draw each mesh subset with correct materials and texture
for (DWORD i = 0; i < pMeshContainer->NumMaterials; i++)
{
//set the material from the mesh container
//set the texture from the mesh container
//draw the subset of the mesh
}
//Release the device
}
}
After filling out all these functions in the CModel class you should be able to create an object of the class and display a textured model. I have included the Microsoft DirectX demo model tiny.x with the texture in the tutorial example code. If you have any questions or comments please feel free to email me at jason@jurecka.us.
Animation with DirectX .X Files
If you use the D3DXLoadMeshHierarchyFromX function the last parameter taken in is an animation controller. This controller stores all the animation data for the loaded model and allows you to animate the object.
To use the animation controller all you have to do is update the time inside the controller then go through each of the frame matrices and recomputed the combined matrix. To update the time in the controller do the following code in an update function.
m_pAnimationController->SetTime(m_pAnimationController->GetTime() +
fElapsedTime);
Where m_pAnimationController is the animation controller and the elapsed time is a calculated time difference. The animation will run all the way to the end and then repeat from the beginning.
This control of animation is good and is simple, but how do you play more than one animation you ask? Well, the DirectX interface loads all of the animation sets in a reverse order into different "tracks". To change animations all you have to do is change the "track" that is playing. This can be accomplished as follows:
LPD3DXANIMATIONSET AnimSet = NULL;
m_pAnimationController ->GetAnimationSet(m_AnimationTrack, &AnimSet);
m_pAnimationController ->SetTrackAnimationSet(0, AnimSet);
AnimSet->Release();
First, you need to create an animation set to store the one that you want to use. Next, use the GetAnimationSet() function from the animation controller interface. This function will return the animation set for the track number that you provide. Then, use the SetTrackAnimationSet() function to put the animation into the play track you want. The DirectX animation controller allows for animation blending by providing weights to the tracks of animation, but that is a topic for a later tutorial. I have set the animation track to "0" because that is the default play track and we are currently not doing any blending. Finally, you need to release the animation set to clean up your mess.
So, now if you run the test app we have been building you should see tiny taking a walk around the test world. I have also included a version of tiny that has different animation sets for you to use to test switching animations (brokentiny.x).
I hope that you have found this tutorial helpful. It is by no means the only way or the most optimized way to handle models in the .X format, but this should offer a good introduction to working with X files. If you have any questions or comments please feel free to email me at jason@jurecka.us.
Jason Jurecka
www.jurecka.us
www.tsbgames.com
jason@jurecka.us
Reference:
DirectX9 SDK sample code for Skinned Meshes provide by Microsoft Corp.
Adams, Jim, Advanced Animation with DirectX, Premier Press, 2003
Leimbach, Johannes, Character Animation with DirectX 8.0 or One Step Nearer to Quake
http://www.gamedev.net/reference/articles/article1653.asp
Microsoft DirectX Developer forum
http://discuss.microsoft.com/archives/DIRECTXDEV.html
Kh Abdulla, Sarmad, Implementing Skin Meshes with DX 8
http://www.gamedev.net/reference/programming/features/skinmesh/default.asp
Microsoft Online MSDN
http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/dx8_c/directx_cpp/graphics_xof_format_9c4m.asp