Working with the Four Key Types
Currently, there are four types of keys you can use in your animation sets,
each signified by a value ranging from 0 to 4 that is listed in the .X file
following the frame reference inside an AnimationKey template. These four keys
and their respective values are:
Rotational keys (type 0). These are quaternion rotational values, stored
using four components: w,x, y, and z.
Scaling keys (type 1). You can also use this type of key to animate scaling
values. A scale key uses three components that represent the x, y, and z scaling
values to use.
Translation keys (type 2). These keys specify a position in 3D space using three
components that represent the x, y, and z coordinates. You can easily store
these three values as a vector.
Transformation matrix keys (type 4). You can use this key to compound all
transformations into matrices. This key uses 16 floats that represent a
homogenous 4x4 transformation matrix that transforms a bone.
So getting back to the previous Animation data objects, you can see that the
very first AnimationKey object (which affects the Bip01 bone) defines a
transformation matrix key (represented by the value 4), as shown here:
{Bip01}
AnimationKey {
4;
3;
0; 16; 1.00000, 0.00000, 0.00000, 0.00000,
0.00000, 1.00000, 0.00000, 0.00000,
0.00000, 0.00000, 1.00000, 0.00000,
0.00000, 0.00000, 0.00000, 1.00000;;,
1000; 16; 1.00000, 0.00000, 0.00000, 0.00000,
0.00000, 1.00000, 0.00000, 0.00000,
0.00000, 0.00000, 1.00000, 0.00000,
0.00000, 0.00000, 0.00000, 1.00000;;,
2000; 16; 1.00000, 0.00000, 0.00000, 0.00000,
0.00000, 1.00000, 0.00000, 0.00000,
0.00000, 0.00000, 1.00000, 0.00000,
0.00000, 0.00000, 0.00000, 1.00000;;;
}
As for the second AnimationKey object (which affects the Bip01_LeftArm bone),
there are three keys in use: translation (value 2), scaling (value 1), and
rotation (value 0).
{Bip01_LeftArm}
AnimationKey {
0;
1;
0; 4; 1.00000, 0.000000, 0.00000, 0.000000;;;
}
AnimationKey {
1;
1;
0; 4; 1.000000, 1.00000, 1.000000;;;
}
AnimationKey {
2;
1;
0; 3; 0.000000, 0.00000, 0.000000;;;
}
As you may have surmised by now, you can have any number of AnimationKey
objects per Animation object, with each AnimationKey object using one specific
key type. Following the key's type value (0=rotational, 1=scaling, 2=position,
4=matrix) is the number of keys to use in the animation sequence for that
specific bone. In the first bone's set (Bip01) there are three matrix type keys
defined, whereas the remaining AnimationKey objects (that affect the
Bip01_LeftArm bone) use only one key for each of the remaining transformation
types (rotation, scaling, and position).
Next comes the key data. The first value for each key is the time value,
which is specified using an arbitrary value that you choose (such as seconds,
milliseconds, frames, or any other form of measurement you wish to use). In my
examples, I always specify time as milliseconds. A number that defines how many
key values are to follow comes after the time value. Take the following key
data, for example:
AnimationKey {
2; // Key type
1; // # of keys
0; // Key time
3; // # of values to follow for key's data
10.00000, 20.00000, 30.00000;;; // key's data
}
The first value, 2, means the key is used to contain translation animation
keys. The 1 means there is one key to follow. The first and only key is located
at time 0. The value 3 follows the time, which means that three more values (10,
20, and 30) are to follow. The three values represent the coordinates to use for
that time in the animation.
Going back to the earlier example, you can see that the first animation key
(the transformation matrix key) has three matrices that are used at times 0,
1000, and 2000. At those exact times during the animation, you will set the
transformation matrix for the Bip01 bone.
For the time values between keys, you need to interpolate the matrices to
come up with the correct transformations. In fact, you can interpolate all key
types to get the correct values to use between keys. The easiest way to
interpolate is to use the transformation matrix, scaling, translation, or
rotation values from the animation keys, divide by the time between two keys,
and multiply the result based on the time into the key. You saw me use linear
interpolation for a transformation matrix in the previous section. Before long,
I'll show you how to interpolate translation, scaling, and rotation values as
well.
That's basically it for the AnimationKey! You just need to read in each key
contained within your AnimationKey data objects and apply it to the proper bone
transformations using the interpolated matrices over time.
Okay, enough of the animation templates, data objects, keys, and
interpolation for now; we'll get back to that stuff in a bit. For now, let's get
your hands into some real code and see how to load the animation data into your
game. Then I'll get back to showing you how to work with the actual data.
Reading Animation Data from .X Files
You learned how to load meshes and frame hierarchies from an .X file, as well
as how to use frame hierarchies to deform (modify) the mesh while rendering.
This chapter's purpose is to teach you how to read in the animation data
contained in an .X file so you can play back key−framed animations.
The first step to reading in the animation data is to look at the templates
you'll be using and build a couple classes to contain the data from those
templates' data objects. First, here are the templates that you'll be working
with, along with their declarations:
template AnimationKey {
<10DD46A8−775B−11cf−8F52−0040333594A3>
DWORD keyType;
DWORD nKeys;
array TimedFloatKeys keys[nKeys];
}
template Animation {
<3D82AB4F−62DA−11cf−AB39−0020AF71E433>
[AnimationKey]
[AnimationOptions]
[...]
}
template AnimationSet {
<3D82AB50−62DA−11cf−AB39−0020AF71E433>
[Animation]
}
At the top of the list you have AnimationKey, which stores the type of
animation key data, the number of key values to follow, and the key data itself,
which is stored in an array of TimedFloatKey objects that store the time and an
array of floating−point values in the form Time:NumValues:Values.
Data objects of the Animation template class type can store an AnimationKey
object and an AnimationOptions object. Notice that the Animation template is
left open because it needs a frame data object reference to match the animation
key to a bone.
Last, there's the AnimationSet template, which only contains Animation
objects. You can store any number of animations within an animation set;
typically, you'll have one animation for each bone.
Note The AnimationOptions template, is highly
useful if you want your artists to specify playback options. Inside the
AnimationOptions
template, you'll find two variablesopenclosed and positionquality.If openclosed
is set to 0, then the animation in which the object is embedded doesn't loop; a
value of 1 means the animation loops. As for positionquality, setting it to a
value of 0 means to use spline positions, whereas a value of 1 means to use
linear positions. Typically, you'd set positionquality to 1.
You'll want to use some custom classes to store your animation data; those
classes will pretty much mirror the Animation templates' data exactly. First,
you want a class that contains the values of the various key types: scaling,
translation, rotation, and transformation. The first two types, scaling and
translation, both use a vector, so one class will suffice.
class cAnimationVectorKey
{
public:
float m_Time;
D3DXVECTOR3 m_vecKey;
};
Rotation keys use a quaternion (a four−dimensional vector).
class cAnimationQuaternionKey
{
public:
float m_Time;
D3DXQUATERNION m_quatKey;
};
Last, the transformation key uses a 4x4 homogenous matrix.
class cAnimationMatrixKey
{
public:
float m_Time;
D3DXMATRIX m_matKey;
};
So far, so good. Remember that each bone in your animation has its own list
of keys to use, which is the purpose of the Animation template. For each bone in
your hierarchy, there is a matching Animation data object. Your matching
animation class will therefore contain the name of the bone to which it is
connected, the number of keys for each type (translation, scaling, rotation, and
transformation), a linked list data pointer, and a pointer to the bone (or
frame) structure you're using in your hierarchy. Also, you need to include a
constructor and destructor that clear out the class's data.
class cAnimation
{
public:
char *m_Name; // Bone's name
D3DXFRAME *m_Bone; // Pointer to bone
cAnimation *m_Next; // Next animation object in list
// # each key type and array of each type's keys
DWORD m_NumTranslationKeys;
cAnimationVectorKey *m_TranslationKeys;
DWORD m_NumScaleKeys;
cAnimationVectorKey *m_ScaleKeys;
DWORD m_NumRotationKeys;
cAnimationQuaternionKey *m_RotationKeys;
DWORD m_NumMatrixKeys;
cAnimationMatrixKey *m_MatrixKeys;
public:
cAnimation();
~cAnimation();
};
Finally, the AnimationSet template contains the Animation objects for an
entire bone hierarchy. At this point, all your animation set class needs to do
is track an array of cAnimation classes (remember that each bone in the
hierarchy has a matching cAnimation class), as well as the length of the
complete animation.
class cAnimationSet
{
public:
char *m_Name; // Name of animation
DWORD m_Length; // Length of animation
cAnimationSet *m_Next; // Next set in linked list
DWORD m_NumAnimations;
cAnimation *m_Animations;
public:
cAnimationSet();
~cAnimationSet();
}