Assuming you want more than one animation set loaded at once, you can even
create a class that contains an array (or rather, a linked list) of
cAnimationSet classes, which means that you can access a whole slew of
animations with one interface! This class, called cAnimationCollection, is also
derived from the cXParser class, so you can parse .X files directly from the
class in which you'll be storing the animations.
Here's the class declaration for cAnimationCollection:
class cAnimationCollection : public cXParser
{
public:
DWORD m_NumAnimationSets;
cAnimationSet *m_AnimationSets;
protected:
// Parse an .X file object
BOOL ParseObject(IDirectXFileData *pDataObj,
IDirectXFileData *pParentDataObj,
DWORD Depth,
void **Data, BOOL Reference);
public:
cAnimationCollection();
~cAnimationCollection();
BOOL Load(char *Filename);
void Free();
void Map(D3DXFRAME *RootFrame);
void Update(char *AnimationSetName, DWORD Time);
};
The details of each function in the cAnimationCollection class are not very
important at this point so I'll get back to them in a bit. At this point, all
you're interested in is reading in that animation data from an .X file. The
custom .X parser contained in the cAnimationCollection class does just that, it
loads the data from the Animation objects' data into the dizzying array of
objects you've just seen.
For every AnimationSet object you encounter in the .X file being parsed, you
need to allocate a cAnimationSet class and add it to the linked list of
animation sets already loaded. The most current cAnimationSet object is stored
at the start of the linked list, which makes it easy to determine which
animation−set data you are currently using.
From here, you can appropriately parse the Animation objects. If you were to
keep the most current cAnimationSet object at the start of your linked list,
every following Animation object that you parse would belong to that
animation−set object. The same goes for the AnimationKey objects, their data
would belong to the first cAnimation object in the linked list.
I will skip the constructors and destructors for all the different classes
because you only need them to clear and release each class's data. You're only
interested in a couple functions, the first being
cAnimationCollection::ParseObject, which deals with each animation object being
parsed from an .X file.
The ParseObject function starts by checking whether the currently enumerated
object is an AnimationSet. If it is, a new cAnimationSet object is allocated and
linked to the list of objects, while the animation−set object is simultaneously
named for further reference.
BOOL cAnimationCollection::ParseObject(
IDirectXFileData *pDataObj,
IDirectXFileData *pParentDataObj,
DWORD Depth,
void **Data, BOOL Reference)
{
const GUID *Type = GetObjectGUID(pDataObj);
DWORD i;
// Check if object is AnimationSet type
if(*Type == TID_D3DRMAnimationSet) {
// Create and link in a cAnimationSet object
cAnimationSet *AnimSet = new cAnimationSet();
AnimSet−>m_Next = m_AnimationSets;
m_AnimationSets = AnimSet;
// Increase # of animation sets
m_NumAnimationSets++;
// Set animation set name (set a default one if none)
if(!(AnimSet−>m_Name = GetObjectName(pDataObj)))
AnimSet−>m_Name = strdup("NoName");
}
As you can see, nothing special goes on with the animation set objects,
you're merely allocating an object that will eventually hold the upcoming
Animation data objects. Speaking of which, you want to parse the Animation
objects next.
// Check if object is Animation type
if(*Type == TID_D3DRMAnimation && m_AnimationSets) {
// Add a cAnimation class to top−level cAnimationSet
cAnimation *Anim = new cAnimation();
Anim−>m_Next = m_AnimationSets−>m_Animations;
m_AnimationSets−>m_Animations = Anim;
// Increase # of animations
m_AnimationSets−>m_NumAnimations++;
}
Again, nothing special going on there. In the preceding code, you're simply
ensuring that there's a cAnimationSet object allocated at the start of the
linked list. If there is, you can allocate and link a cAnimation object to the
list in the cAnimationSet object.
While we're on the topic of the cAnimation object, the next bit of code
retrieves the name of the frame instance located within the Animation object.
// Check if a frame reference inside animation object
if(*Type == TID_D3DRMFrame && Reference == TRUE && m_AnimationSets && m_AnimationSets−>m_Animations) {
// Make sure parent object is an Animation object
if(pParentDataObj && *GetObjectGUID(pParentDataObj) == TID_D3DRMAnimation) {
// Get name of frame and store it as animation
if(!(m_AnimationSets−>m_Animations−>m_Name = GetObjectName(pDataObj)))
m_AnimationSets−>m_Animations−>m_Name=strdup("NoName");
}
}
You can see in this code that only referenced frame objects are allowed in
the Animation object, a fact that you can verify by checking the parent object's
template GUID. Whew! So far this code is pretty easy, isn't it? Well, I don't
want to burst your bubble, but the hardest is yet to come! In fact, the most
difficult part of loading animation data from an .X file is loading the key
data. Don't let me scare you away, though; the key data is nothing more than a
time value and an array of values that represent the key data.
The remaining code in the ParseObject function checks to see which type of
key data an AnimationKey object holds. Depending on the type of data, the code
branches off and reads the data into the specific key objects (m_RotationKeys,
m_TranslationKeys, m_ScaleKeys, and m_MatrixKeys) inside the current cAnimation
object. Take a closer look to see how simple this code really is.
// Check if object is AnimationKey type
if(*Type == TID_D3DRMAnimationKey && m_AnimationSets && m_AnimationSets−>m_Animations) {
// Get a pointer to top−level animation object
cAnimation *Anim = m_AnimationSets−>m_Animations;
// Get a data pointer
DWORD *DataPtr = (DWORD*)GetObjectData(pDataObj, NULL);
// Get key type
DWORD Type = *DataPtr++;
// Get # of keys to follow
DWORD NumKeys = *DataPtr++;
In addition to checking to see whether there are valid cAnimationSet and
cAnimation objects at the start of the linked list of objects, the preceding
code gets a pointer to the key data and pulls out the key type value and the
number of keys to follow. Using the key type, the code then branches off to
allocate the key−frame objects and load in the key data.
// Branch based on key type
switch(Type) {
case 0: // Rotation
delete [] Anim−>m_RotationKeys;
Anim−>m_NumRotationKeys = NumKeys;
Anim−>m_RotationKeys = new cAnimationQuaternionKey[NumKeys];
for(i=0;i<NumKeys;i++) {
// Get time
Anim−>m_RotationKeys[i].m_Time = *DataPtr++;
if(Anim−>m_RotationKeys[i].m_Time > m_AnimationSets−>m_Length)
m_AnimationSets−>m_Length = Anim−>m_RotationKeys[i].m_Time;
// Skip # keys to follow (should be 4)
DataPtr++;
// Get rotational values
float *fPtr = (float*)DataPtr;
Anim−>m_RotationKeys[i].m_quatKey.w = *fPtr++;
Anim−>m_RotationKeys[i].m_quatKey.x = *fPtr++;
Anim−>m_RotationKeys[i].m_quatKey.y = *fPtr++;
Anim−>m_RotationKeys[i].m_quatKey.z = *fPtr++;
DataPtr+=4;
}
break;
You'll recall from earlier in this chapter that rotation keys use quaternion
values. These values are stored in w, x, y, z order; to make sure you use the
proper values, you must read them into the key's quaternion object
appropriately.
Next comes the code to load in the scaling and translation keys, which both
use vectors to store the x−, y−, and z−axis information.
case 1: // Scaling
delete [] Anim−>m_ScaleKeys;
Anim−>m_NumScaleKeys = NumKeys;
Anim−>m_ScaleKeys = new cAnimationVectorKey[NumKeys];
for(i=0;i<NumKeys;i++) {
// Get time
Anim−>m_ScaleKeys[i].m_Time = *DataPtr++;
if(Anim−>m_ScaleKeys[i].m_Time > m_AnimationSets−>m_Length)
m_AnimationSets−>m_Length = Anim−>m_ScaleKeys[i].m_Time;
// Skip # keys to follow (should be 3)
DataPtr++;
// Get scale values
D3DXVECTOR3 *vecPtr = (D3DXVECTOR3*)DataPtr;
Anim−>m_ScaleKeys[i].m_vecKey = *vecPtr;
DataPtr+=3;
}
break;
case 2: // Translation
delete [] Anim−>m_TranslationKeys;
Anim−>m_NumTranslationKeys = NumKeys;
Anim−>m_TranslationKeys = new cAnimationVectorKey[NumKeys];
for(i=0;i<NumKeys;i++) {
// Get time
Anim−>m_TranslationKeys[i].m_Time = *DataPtr++;
if(Anim−>m_TranslationKeys[i].m_Time > m_AnimationSets−>m_Length)
m_AnimationSets−>m_Length = Anim−>m_TranslationKeys[i].m_Time;
// Skip # keys to follow (should be 3)
DataPtr++;
// Get translation values
D3DXVECTOR3 *vecPtr = (D3DXVECTOR3*)DataPtr;
Anim−>m_TranslationKeys[i].m_vecKey = *vecPtr;
DataPtr+=3;
}
break;
Last is the code to read an array of transformation matrix keys.
case 4: // Transformation matrix
delete [] Anim−>m_MatrixKeys;
Anim−>m_NumMatrixKeys = NumKeys;
Anim−>m_MatrixKeys = new cAnimationMatrixKey[NumKeys];
for(i=0;i<NumKeys;i++) {
// Get time
Anim−>m_MatrixKeys[i].m_Time = *DataPtr++;
if(Anim−>m_MatrixKeys[i].m_Time > m_AnimationSets−>m_Length)
m_AnimationSets−>m_Length = Anim−>m_MatrixKeys[i].m_Time;
// Skip # keys to follow (should be 16)
DataPtr++;
// Get matrix values
D3DXMATRIX *mPtr = (D3DXMATRIX *)DataPtr;
Anim−>m_MatrixKeys[i].m_matKey = *mPtr;
DataPtr += 16;
}
break;
}
}
Okay now, take a quick breather and look back at what you've just
accomplished. So far, you've processed every AnimationSet, Animation, and
AnimationKey object (not to mention referenced Frame objects that contain the
bones' names), plus you've loaded the key objects full of the animation data.
You're almost ready to start animating!
Almost is right; there is one small step left: matching the animation objects
to their respective bone objects.