D3D Animation Basis(1)
The D3DXFRAME object helps form a hierarchy of reference frames. These reference frames are used to
connect a series of meshes together, with each frame having its own transformation to apply to the mesh
connected to it. In this way of using frames to point to meshes, you can minimize the number of meshes used
because you can reference meshes instead of having to reload them.
For example, imagine you have a car that consists of a body and four wheels. The body and wheel form two
meshes. These two meshes are used in conjunction with five frames (one for the body and four for the tires).
When rendering, each frame's transformation is used to position and render the mesh that the frame uses. That
means one frame transforms and renders the body once, while the other frames transform and render the tire
mesh four times.
here is D3DXFRAME's define:
Encapsulates a transform frame in a transformation frame hierarchy.
typedef struct D3DXFRAME {
LPSTR Name;
D3DXMATRIX TransformationMatrix;
LPD3DXMESHCONTAINER pMeshContainer;
D3DXFRAME * pFrameSibling;
D3DXFRAME * pFrameFirstChild;
} D3DXFRAME, *LPD3DXFRAME;
Members
- Name
- Name of the frame.
- TransformationMatrix
- Transformation matrix.
- pMeshContainer
- Pointer to the mesh container.
- pFrameSibling
- Pointer to a sibling frame.
- pFrameFirstChild
- Pointer to a child frame.
Remarks
An application can derive from this structure to add other data.
As for the D3DXMESHCONTAINER object, it is used to contain a mesh as well as to link to a series of other
meshes (using a linked list). Why not just use the ID3DXBaseMesh object instead, you ask? Well, there's
more to D3DXMESHCONTAINER than you might expect. First, you can store any type of mesh, whether it's
regular, skinned, or progressive. Second, the D3DXMESHCONTAINER object holds material and effect data.
here is D3DXMESHCONTAINER define:
Encapsulates a mesh object in a transformation frame hierarchy.
typedef struct D3DXMESHCONTAINER {
LPSTR Name;
D3DXMESHDATA MeshData;
LPD3DXMATERIAL pMaterials;
LPD3DXEFFECTINSTANCE pEffects;
DWORD NumMaterials;
DWORD * pAdjacency;
LPD3DXSKININFO pSkinInfo;
D3DXMESHCONTAINER * pNextMeshContainer;
} D3DXMESHCONTAINER, *LPD3DXMESHCONTAINER;
Members
- Name
- Mesh name.
- MeshData
- Type of data in the mesh.
- pMaterials
- Array of mesh materials.
- pEffects
- Pointer to a set of default effect parameters.
- NumMaterials
- Number of materials in the mesh.
- pAdjacency
- Pointer to an array of three DWORDs per triangle of the mesh that contains adjacency information.
- pSkinInfo
- Pointer to the skin information interface.
- pNextMeshContainer
- Pointer to the next mesh container.
Remarks
An application can derive from this structure to add other data.
D3DXMESHDATA
Mesh data structure.
typedef struct D3DXMESHDATA {
D3DXMESHDATATYPE Type;
union {
LPD3DXMESH pMesh;
LPD3DXPMESH pPMesh;
LPD3DXPATCHMESH pPatchMesh;
};
} D3DXMESHDATA, *LPD3DXMESHDATA;
Members
- Type
- Defines the mesh data type.
- pMesh
- Pointer to a mesh.
- pPMesh
- Pointer to a progressive mesh.
- pPatchMesh
- Pointer to a patch mesh.
D3DXMESHDATATYPE
Defines the type of mesh data present in D3DXMESHDATA.
typedef enum D3DXMESHDATATYPE
{
D3DXMESHTYPE_MESH = 0x001,
D3DXMESHTYPE_PMESH = 0x002,
D3DXMESHTYPE_PATCHMESH = 0x003,
D3DXEDT_FORCE_DWORD = 0x7fffffff,
} D3DXMESHDATATYPE, *LPD3DXMESHDATATYPE;
Constants
- D3DXMESHTYPE_MESH
- The data type is a mesh.
- D3DXMESHTYPE_PMESH
- The data type is a progressive mesh.
- D3DXMESHTYPE_PATCHMESH
- The data type is a patch mesh.
- D3DXEDT_FORCE_DWORD
- Forces this enumeration to compile to 32 bits in size. Without this value, some compilers would allow this enumeration to compile to a size other than 32 bits. This value is not used.
Extending D3DXFRAME
By itself, the D3DXFRAME object is very useful, but unfortunately it lacks a few very essential tidbits of
information, namely data for containing transformations when animating meshes, functions to handle the
animation data, and a default constructor and destructor.
To correct these omissions, I have created an extended version of D3DXFRAME, which I call
D3DXFRAME_EX. This new object adds a total of two D3DXMATRIX objects and six functions to the mix.
The two matrix objects contain the original transformation of the frame (before any animation transformations
are applied) and the combined transformation from all parent frames to which the frame is connected (in the
hierarchy).
Here's how I defined the D3DXFRAME_EX structure along with the two matrix objects:
//-------------------------------------------------------------------------------------------
// Declare an extended version of D3DXFRAME that contains a constructor and destructor
// as well as a combined transformation matrix.
//-------------------------------------------------------------------------------------------
struct D3DXFRAME_EX : D3DXFRAME
{
D3DXMATRIX mat_combined; // combined matrix
D3DXMATRIX mat_original; // original transformation from .X
For now, let's
just move on to the functions, starting with the constructor. The constructor has the job of clearing out the
structure's data (including the original data from the base D3DXFRAME object).
D3DXFRAME_EX()
{
Name = NULL;
pMeshContainer = NULL;
pFrameSibling = pFrameFirstChild = NULL;
D3DXMatrixIdentity(&matCombined);
D3DXMatrixIdentity(&matOriginal);
D3DXMatrixIdentity(&TransformationMatrix);
}
On the flip side, the destructor has the job of freeing the data used by the D3DXFRAME_EX object.
~D3DXFRAME_EX()
{
delete[] Name; Name = NULL;
delete pFrameSibling; pFrameSibling = NULL;
delete pFrameFirstChild; pFrameFirstChild = NULL;
}
As you can see, the constructor and destructor are pretty typical in the way those things normally go−initialize
the object's data and free the resources when done. What comes next are a handful of functions that help you
search for a specific frame in the hierarchy, reset the animation matrices to their original states, update the
hierarchy after modifying a transformation, and count the number of frames in the hierarchy.
The first function, find, is used to find a specific frame in the hierarchy and return a pointer to it. If you're
not aware of this, each D3DXFRAME object (and the derived D3DXFRAME_EX object) has a Name data
buffer, which you're free to fill in with whatever text you find appropriate. Typically, frames are named after
bones that define the hierarchy.
To find a specific frame (and retrieve a pointer to the frame's object), just call the find function, specifying
the name of the frame you wish to find as the one and only parameter.
// Function to scan hierarchy for matching frame name
D3DXFRAME_EX* find(const char* frame_name)
{
// return this frame instance if name matched
if(Name && frame_name && !strcmp(frame_name, Name))
return this;
if(pFrameSibling) // scan siblings
return ((D3DXFRAME_EX*) pFrameSibling)->find(frame_name);
if(pFrameFirstChild) // scan children
return ((D3DXFRAME_EX*) pFrameSibling)->find(frame_name);
return NULL; // no found
}
The find function compares the name you passed to the current frame's name; if they match, the pointer to
the frame is returned. If no match is found, then the linked list is scanned for matches using a recursive call to
find.
Next in the line of added functions is reset, which scans through the entire frame hierarchy (which, by the
way, is a linked list of child and sibling objects). For each frame found, it copies the original transformation to
the current transformation. Here's the code:
// reset transformation matrices to originals
void reset()
{
TransformationMatrix = mat_original;
if(pFrameSibling)
((D3DXFRAME_EX*) pFrameSibling)->reset();
if(pFrameFirstChild)
((D3DXFRAME_EX*) pFrameFirstChild)->reset();
}
Typically, you call reset to restore the frame hierarchy's transformation back to what it was when you
created or loaded the frames. the next function in the list is update_hierarchy, which has the job of rebuilding
the entire frame hierarchy's list of transformations after any one of those transformations has been altered.
Rebuilding the hierarchy is essential to making sure the mesh is rebuilt or rendered correctly after you have
updated an animation. let's just check out the code, which takes an optional transformation matrix to apply
to the root frame of the hierarchy.
// function to combine matrices in frame hierarchy
void update_hierarchy(D3DXMATRIX* mat_trans)
{
// use an identity matrix if none passed
if(mat_trans == NULL)
{
D3DXMATRIX mat_identity;
D3DXMatrixIdentity(&mat_identity);
mat_trans = &mat_identity;
}
// combine matrices with supplied transformation matrix
mat_combined = TransformationMatrix * (*mat_trans);
// combine with sibling frames
if(pFrameSibling)
((D3DXFRAME_EX*) pFrameSibling)->update_hierarchy(mat_trans);
// combine with child frames
if(pFrameFirstChild)
((D3DXFRAME_EX*) pFrameFirstChild)->update_hierarchy(mat_combined);
}
the update_hierarchy function transforms the frames by their own transformation matrix
(stored in matTransformation) by a matrix that is passed as the optional parameter of the function. This
way, a frame inherits the transformation of its parent frame in the hierarchy, meaning that each transformation
applied winds its way down the entire hierarchy.
Last, with the D3DXFRAME_EX object you have the count function, which helps you by counting the
number of frames contained within the hierarchy. This is accomplished using a recursive call of the count
function for each frame contained in the linked list. For each frame found in the list, a counter variable (that
you provide as the parameter) is incremented. Check out the Count code to see what I mean.
void count(DWORD* num)
{
if(num == NULL) // error checking
return;
(*num) += 1; // increase count of frames
// process sibling frames
if(pFrameSibling)
((D3DXFRAME_EX*) pFrameSibling)->count(num);
// process child frames
if(pFrameFirstChild)
((D3DXFRAME_EX*) pFrameFirstChild)->count(num);
}
And that pretty much wraps up the D3DXFRAME_EX object. If you're used to using the D3DXFRAME object
(and you should be if you're a DX9 user), then everything I've just shown you should be pretty easy to
understand.