Animation Cycle Data

 

 

Animation Cycle data is a deceptively tricky thing to get right within a robust real time animation system. Animation data tends to be generated by animation curves edited within an animation package such as Maya. Transferring this data directly to a real time animation system is however unlikely to work. For one thing, if IK, expressions, set driven keys etc are used within the character setup, then unless we fully support all of these within our own engine, we will never be able to re-create the animation.

A better approach is usually to sample specific animation values at a given time interval. This approach generates a huge amount of data, however it means our animation system can be much simpler without the need for IK chains and expression parsers/compilers.

 

 

 

The Sampled Input Data

The animation data will be assumed as being discrete samples of Scale, Translation and Rotation keyframes. Therefore, for a given animation cycle we may have 25 frame samples of each bones (local space) transformation.

We will need to convert this data to a new data storage, where the transforms have been split into scale, rotation and translation components. Each of these keys should also store the time at which these keys occur. This should allow us to remove any redundant key frames in the middle.

This portion of code translates very well into C++ templates since the process is the same for most data types. The TKeyFrame structure holds a value and a time value, the TKeyList template then manages an array of key frames for a core data type.

 

 

 

 

// a structure to hold a generic keyframe
template< typename T >
struct TKeyFrame
{

// the time on which the keyframe occurred

float time;

// the value of this keyframe

T value;

// a simple comparison operator, just compares the value

bool operator==(const TKeyFrame<T>& key) const {
return value == key.value;
}
// ctor

TKeyFrame() : time(0), value() {}

// copy ctor

TKeyFrame(const TKeyFrame<T>& key) : time(key.time), value(key.value) {}
};

// A list of key frames

template< typename T >
struct TKeyList :
public std::vector< TKeyFrame<T> > {

// ctor
TKeyList() : std::vector< TKeyFrame<T> >() {}
// sets the keyframe data from a C style array

void SetKeys(T* data,int num,float time_inc);

// sets the keyframe data from a C++ vector

void SetKeys(std::vector<T>& data,float time_inc);

// removes all redundant keyframes

void Strip() ;
};

 

 

To Scale or Not to Scale

There is often much debate around games programming forums about whether it is acceptable to use scale and translation values within animation cycles. One school of thought is that you should only use rotations within your animation engine for speed and storage reasons. The downside to this approach is that you loose the possibility of some cheap effects such as influence objects which can help to overcome certain deformation problems around trouble areas such as knees and elbows.

Since the main aim is to create as generic and flexible an animation system as possible, it seems that scale and translation data should also be included. Since this adds extra data requirements into our engine, it is worth investigating methods to reduce the physical amount of data we will need to store for an animation cycle.

This system will therefore evaluate the scale vector, translation vector and rotation quaternion individually. Since it is unusal for animations to require scale and translation control whilst animating, it seems sensible to split the key frame storage in order to minimise the data storage as much as possible. After all, if scale remains at 1,1,1 for the entire cycle, why store the data?

 

 

 

 

// a structure to hold the keyframes for a single bone in an animation cycle
struct BoneAnimCycle {

// returns the max time in this cycle
float MaxTime() const { return m_MaxTime; }

// sets the animation key data for the specified bone.

void SetKeys(std::vector<XVector>& scale_data,
std::vector<XVector>& translation_data,
std::vector<XQuaternion>& rotation_data,
float time_inc);

// sets the animation key data for the specified bone.
// this does exactly the same as the other version but with C style arrays

void SetKeys(unsigned int num_keys,
XVector scale_data[],
XVector translation_data[],
XQuaternion rotation_data[],
float time_inc);

// remove redundant keyframes
void Strip();


// evaluates the scale translation and rotation values for this
// time within the cycle.

void
EvaluateAt(const float t,XVector& scale,XVector& translate,XQuaternion& rot) const;

// ctor
BoneAnimCycle() : m_Translate(),m_Scale(),m_Rotate(),m_MaxTime(0) {}
private:
// all of the translation keyframes for this bone

TKeyList< XVector > m_Translate;

// all of the scale keyframes for this bone

TKeyList< XVector > m_Scale;

// all of the rotation key frames for this bone

TKeyList< XQuaternion > m_Rotate;

// the maximum time in the cycle

float m_MaxTime;
};

// a structure to hold all of the animation keys for each bone
struct AnimCycle
{

// ctor

AnimCycle() : m_BoneAnims() {}

// returns the max time in this cycle
float MaxTime() const { return m_MaxTime; }


// sets the animation key data for the specified bone.

void SetKeys(unsigned int bone_idx,
std::vector<XVector>& scale_data,
std::vector<XVector>& translation_data,
std::vector<XQuaternion>& rotation_data,
float time_inc);

// sets the animation key data for the specified bone.
// this does exactly the same as the other version but with C style arrays

void SetKeys(unsigned int bone_idx,
unsigned int num_keys,
XVector scale_data[],
XVector translation_data[],
XQuaternion rotation_data[],
float time_inc);

// sets the number of bones within the bone anim list

void SetNumBones(
unsigned int size);

// this function strips all redundant key frames

void Strip();

// this evaluates the animation cycle at time value t, and outputs the result
// in the arrays passed in.

void
EvaluateAt(const float t,
std::vector<XVector>& output_scales,
std::vector<XVector>& output_translations,
std::vector<XQuaternion>& output_rotations) const;
private:

std::vector<BoneAnimCycle> m_BoneAnims;

float m_MaxTime;


};

 

 

Setting The Data

The input data for the structure here is assumed to be discrete keyframes for scale, translation and rotations. This data should ideally be stored as vectors and quaternions as apposed to matrices. Whilst storing matrices may seem sensible, we will later want to blend the animation cycles. When blending animation cycles we want to linearly interpolate the scale and translation, but spherically interpolate the rotation keys. For this reason, it is best to blend all of the animations as components and then convert to matrices at the last possible stage.

Since we also want to ideally store multiple character instances, we should also consider that the data these classes need to store is just that, data. They should not contain information on the current animation time etc, this would only cause the requirement that data would have to be duplicated. We will have to store this data elsewhere within the character instances.

 

 

 

 



template< typename T >
void TKeyList<T>::SetKeys(T* data,int num,float time_inc) {
resize(num);
iterator it = begin();
float t=0;

for( ; it != end(); ++it, ++data, t += time_inc ) {
it->value = *data;
it->time = t;
}
}

template< typename T >
void TKeyList<T>::SetKeys(std::vector<T>& data,float time_inc) {
resize(data.size());
iterator it = begin();
std::vector<T>::iterator itdata = data.begin();
float t=0;

for( ; it != end(); ++it, ++itdata, t += time_inc ) {
it->value = *itdata;
it->time = t;
}
}

// sets the animation key data for the specified bone.

void BoneAnimCycle::SetKeys(std::vector<XVector>& scale_data,
std::vector<XVector>& translation_data,
std::vector<XQuaternion>& rotation_data,
float time_inc){

m_Scale.SetKeys(scale_data,time_inc);
m_Translate.SetKeys(translation_data,time_inc);
m_Rotate.SetKeys(rotation_data,time_inc);
}

// sets the animation key data for the specified bone.
// this does exactly the same as the other version but with C style arrays

void
BoneAnimCycle::SetKeys(unsigned int num_keys,
XVector scale_data[],
XVector translation_data[],
XQuaternion rotation_data[],
float time_inc) {

m_Scale.SetKeys(scale_data,num_keys,time_inc);
m_Translate.SetKeys(translation_data,num_keys,time_inc);
m_Rotate.SetKeys(rotation_data,num_keys,time_inc);
}


// sets the animation key data for the specified bone.

void
AnimCycle::SetKeys(unsigned int bone_idx,
std::vector<XVector>& scale_data,
std::vector<XVector>& translation_data,
std::vector<XQuaternion>& rotation_data,
float time_inc) {
// make sure the bone_idx is valid
assert( bone_idx < m_BoneAnims.size() && "ERROR: Invalid Bone Index" );

// set the keyframe data for the specified bone

m_BoneAnims[bone_idx].SetKeys(scale_data,translation_data,rotation_data,time_inc);

m_MaxTime = (scale_data.size()-1) * time_inc;
}

// sets the animation key data for the specified bone.
// this does exactly the same as the other version but with C style arrays

void
AnimCycle::SetKeys(unsigned int bone_idx,
unsigned int num_keys,
XVector scale_data[],
XVector translation_data[],
XQuaternion rotation_data[],
float time_inc){
// make sure the bone_idx is valid
assert( bone_idx < m_BoneAnims.size() && "ERROR: Invalid Bone Index" );

// set the keyframe data for the specified bone

m_BoneAnims[bone_idx].SetKeys(num_keys,scale_data,translation_data,rotation_data,time_inc);

m_MaxTime = (num_keys-1) * time_inc;
}

// sets the number of bones within the bone anim list

void
AnimCycle::SetNumBones(unsigned int size) {
// set the keyframe data for the specified bone

m_BoneAnims.clear();
m_BoneAnims.resize(size);

}

 

 

Key Stripping

Animation Data is going to account for the greatest amount of memory usage within any real time animation system. It is therefore of great importance to find a way to reduce this data requirement as much as possible without losing quality. Lets assume that our data exporter has simply output the various transformations for each bone on each key frame.

For each transform then, we will have a list of the various positions of that transform on each frame. In order to reduce this amount, we will need to determine the extent to which a key frame will affect the animation cycle. If for example a transform does not move for the entirity of a cycle, we should be able to reduce it's keyframes down to one. (zero would be a bad idea since this transform may not be the same as the bind pose transform. Removing this info would cause all manner of problems when trying to blend animation cycles).

If our animation data is currently stored as a list of keys, we normally only store a key for each frame. To start reducing this data, we will have to split each transform into seperate lists of scale, translation and rotation keyframes. We should also store each key frame with a time value

 

 

 

 


template< typename T >
void TKeyList<T>::Strip() {
if
( size() == 2 ) goto sort_2;
if
( size() == 1 ) return;
assert
( size() != 0 );

// walk through the keys in triplets of keys

iterator
it0 = begin();
iterator
it1 = it0 + 1;
iterator
it2 = it1 + 1;

for
( int idx=0; it2 != end(); ) {
// if the middle key frame has no effect

if
( *it0 == *it1 &&
*it1 == *it2 )
{
// erase the middle keyframe

erase
(it1);
// remap iterators if we have more to do

if
( (size()-idx)>=3 ) {

it0 = begin();
it1 = it0 + 1;
it2 = it1 + 1;
}
else
// finished

break
;
}
else
// check the next keys

{
++idx;
++it0;
++it1;
++it2;
}
}

// if only 2 keys left, check to see if they are the same

if
( size() == 2 ) {
sort_2:
if
( *(begin()) == *(begin()+1) ) {
pop_back
();
}
}
}

void BoneAnimCycle::Strip() {
// strip all redundant keys

m_Translate.Strip();
m_Scale.Strip();
m_Rotate.Strip();

}

// this function strips all redundant key frames
void
AnimCycle::Strip() {
std::vector<BoneAnimCycle>::iterator it = m_BoneAnims.begin();

// strip all keys from individual bone anim cycles

for( ; it != m_BoneAnims.end(); ++it ) {
it->Strip();
}
}

 

 

 

Evaluating The Animation Cycles

Animation Data is going to account for the greatest amount of memory usage within any real time animation system. It is therefore of great importance to find a way to reduce this data requirement as much as possible without losing quality. Lets assume that our data exporter has simply output the various transformations for each bone on each key frame.

For each transform then, we will have a list of the various positions of that transform on each frame. In order to reduce this amount, we will need to determine the extent to which a key frame will affect the animation cycle. If for example a transform does not move for the entirity of a cycle, we should be able to reduce it's keyframes down to one. (zero would be a bad idea since this transform may not be the same as the bind pose transform. Removing this info would cause all manner of problems when trying to blend animation cycles).

If our animation data is currently stored as a list of keys, we normally only store a key for each frame. To start reducing this data, we will have to split each transform into seperate lists of scale, translation and rotation keyframes. We should also store each key frame with a time value

 

 

 

 

void LERP(XVector& out,float t,const XVector& a,const XVector& b) {
float it = 1.0f - t;
out = it * a + t * b;
}

void SLERP(XQuaternion& out,float t,const XQuaternion& a,const XQuaternion& b) {
// we need a temporary for the second quaternion.
XQuaternion Second;

// Every rotation from angle a to angle b has two possible outcomes. Imagine
// turning on the spot. You could either turn left, or turn right. You automatically
// decide which direction to turn in. Our animation engine does not have the
// benefit of judgement, so we have to figure it out and tell it
//
// In some ways, think of a quaternion as a rotation that also has an idea of
// the direction in which to rotate; i.e. does it turn left or right.
// If the quaternion represents a rotation to the 'left', the it's inverse
// represents the same rotation but by having got there by turning in the
// opposite direction.
//
// If two quaternions are known to be similar, we can assume that they will
// either both be 'positive', or both 'negative'. There are occasions where the
// quaternions will actually be the inverse of what you would expect. This
// leads to a glitch in the animation system where the bones rotate the long
// way round to get to the next keyframe. The long way round, is usually the
// wrong way.... :(
//
// If we perform a dot product between 2 quaternions, they return a negative
// number if the two rotate in different directions... so lets do a dot product
// to find out.....
float product = ( a.x * b.x ) +
( a.y * b.y ) +
( a.z * b.z ) +
( a.w * b.w );

// if the angle is greater than 90degress, negate 2nd quaternion and product
if ( product < 0.001f )
{
Second =
XQuaternion(-b.x,
-b.y,
-b.z,
-b.w);
product = -product;
}
else
{
Second = b;
}

// an ugly hack to make sure that acos does not get an
// invalid value due to floating point rounding errors.
// The outcome of not doing this is usually an assert on linux when the value is 1 or -1

if( product > 1 )
product=0.999999f;
if( product < -1 )
product=-0.999999f;

// spherical interpolation takes place around a sphere. Therefore we use the
// product (remeber dot product to finds angle between 2 vectors) to get the
// angle we have to rotate to go from key1 to key2.
//

float theta = static_cast<float>( acos(product) );

// precalculate the sin value
//

float sinTheta = static_cast<float>( sin(theta) );
if(sinTheta != 0.0f)
{
interpolating_invt=
static_cast<float>(sin(interpolating_invt*theta)/sinTheta);
interpolating_t =
static_cast<float>(sin(interpolating_t *theta)/sinTheta);
}


// perform the interpolation

out.x = (inv_interpt * a.x) + (interp_t * Second.x);
out.y = (inv_interpt * a.y) + (interp_t * Second.y);
out.z = (inv_interpt * a.z) + (interp_t * Second.z);
out.w = (inv_interpt * a.w) + (interp_t * Second.w);

// normalise the result

out.
Normalise();

}

void BoneAnimCycle::EvaluateAt(const float t,XVector& scale,XVector& translate,XQuaternion& rot) const {
assert(m_Scale.size()!=0 && "[ERROR] Need at least one scale key for a bone!");
assert(m_Translate.size()!=0 && "[ERROR] Need at least one translation key for a bone!");
assert(m_Rotate.size()!=0 && "[ERROR] Need at least one rotation key for a bone!");

// if anim time before cycle start, just use the first key

if(t<0) {
scale = m_Scale[0].value;
translate = m_Translate[0].value;
rot = m_Rotate[0].value;
}
else {
// if anim time after cycle end, just use the last key

if
( t > m_MaxTime ) {
scale = m_Scale[m_Scale.
size()-1].value;
translate = m_Translate[m_Translate.
size()-1].value;
rot = m_Rotate[m_Rotate.
size()-1].value;
}
else {
// if only one keyframe, then always use it

if( m_Scale.size() == 1 ) {
scale = m_Scale[0].value;
}
else
{

// iterate through the key frames to find keys to interpolate with

TKeyList< XVector >::const_iterator it = m_Scale.begin();
TKeyList< XVector >::const_iterator itnext = it+1;

// loop through all scale keys

for( ; itnext != end(); ++itnext,++it ) {

// if the time value falls between these two keys

if( it->time < t && itnext->time >= t ) {

// calculate the time difference between the two key frames

float diff_t = itnext->time - it->time;

// since we will linearly interpolate between the two key values,
// we need to scale the time value so that it goes from 0 to 1
// between the two key frames

float interp_t = (t-it->time)/diff_t;

// interpolate between the two keys to get the scale value

LERP(scale, interp_t, it->value, itnext->value);
}
}
}

// if only one translation keyframe, then always use it

if( m_Translate.size() == 1 ) {
translate = m_Translate[0].value;
}
else
{

// iterate through the key frames to find keys to interpolate with

TKeyList< XVector >::const_iterator it = m_Translate.begin();
TKeyList< XVector >::const_iterator itnext = it+1;

// loop through all translation keys

for( ; itnext != end(); ++itnext,++it ) {

// if the time value falls between these two keys

if( it->time < t && itnext->time >= t ) {

// calculate the time difference between the two key frames

float diff_t = itnext->time - it->time;

// since we will linearly interpolate between the two key values,
// we need to scale the time value so that it goes from 0 to 1
// between the two key frames

float interp_t = (t-it->time)/diff_t;

// interpolate between the two keys to get the scale value

LERP(translate, interp_t, it->value, itnext->value);
}
}
}

// if only one keyframe, then always use it

if( m_Rotate.size() == 1 ) {
rot = m_Rotate[0].value;
}
else
{

// iterate through the key frames to find keys to interpolate with

TKeyList< XQuaternion >::const_iterator it = m_Rotate.begin();
TKeyList< XQuaternion >::const_iterator itnext = it+1;

// loop through all rotation keys

for( ; itnext != end(); ++itnext,++it ) {

// if the time value falls between these two keys

if( it->time < t && itnext->time >= t ) {

// calculate the time difference between the two key frames

float diff_t = itnext->time - it->time;

// since we will linearly interpolate between the two key values,
// we need to scale the time value so that it goes from 0 to 1
// between the two key frames

float interp_t = (t-it->time)/diff_t;

// since we will spherically linearly interpolate between the two
// key values, we need to scale the time value so that it goes
// from 0 to 1 between the two key frames
// interpolate between the two keys to get the scale value

SLERP(rot, interp_t, it->value, itnext->value);
}
}
}
}
}
}

void AnimCycle::EvaluateAt(const float t,
std::vector<XVector>& output_scales,
std::vector<XVector>& output_translations,
std::vector<XQuaternion>& output_rotations) const {

// make sure that the output array sizes match the number of bones
// we think we have

assert( output_scales.size() == m_BoneAnims.size() &&
"ERROR: Scale array does not match bone animation array size\n");
assert( output_translations.size() == m_BoneAnims.size() &&
"ERROR: Translation array does not match bone animation array size\n");
assert( output_rotations.size() == m_BoneAnims.size() &&
"ERROR: Rotation array does not match bone animation array size\n");

std::vector<BoneAnimCycle>::const_iterator itBoneAnim = m_BoneAnims.begin();
std::vector<
XVector>::iterator itScale = output_scales.begin();
std::vector<
XVector>::iterator itTranslate = output_translations.begin();
std::vector<
XQuaternion>::iterator itRotation = output_rotations.begin();

for( ; itBoneAnim != m_BoneAnims.end(); ++itBoneAnim ) {
itBoneAnim->
EvaluateAt(t,*itScale,*itTranslate,*itRotation);

++itScale;
++itTranslate;
++itRotation;
}
}

 

 

Storing Multiple Animation Cycle Instances

Since ideally we want to blend the multiple cycles together, we will need to run multiple animation cycles at the same time for each character instance. For this, we will need to create an animation instance structure.

 

 

 

 

// a structure to hold the keyframes for a single bone in an animation cycle
struct AnimCycleInstance {

// returns the max time in this cycle
float MaxTime() const { return m_MaxTime; }

// sets the current animation cycle to use
void SetCycle(AnimCycle* cycle) {
m_pCycle = cycle;
m_fCurrentTime = 0;
}

// sets the current animation cycle to use
bool Update(float dt) {
// set a flag when we pass the end of the cycle
bool flag=false;
// update the current time in the cycle

m_fCurrentTime
+= dt;
// evaluate the cycle at the new time
m_pCycle->EvaluateAt(m_fCurrentTime,m_Translate,m_Scale,m_Rotate);

// if exceeded the end of the cycle
if(m_fCurrentTime>MaxTime()) {
m_fCurrentTime -= MaxTime();
flag = true;
}
return flag;
}

// sets the current position in the animation cycle using a 0 to 1 range
bool SetParametricT(float param_t) {
// set a flag when we pass the end of the cycle
bool flag=false;
m_fCurrentTime = param_t*MaxTime();

// evaluate the cycle at the new time
m_pCycle->EvaluateAt(m_fCurrentTime,m_Translate,m_Scale,m_Rotate);

// if exceeded the end of the cycle
if(m_fCurrentTime>MaxTime()) {
m_fCurrentTime -= MaxTime();
flag = true;
}
return flag;
}

// returns an array of all the current local space translation values for the bones in this model

const std::vector< XVector >& Translation() const { return m_Translate; }

// returns an array of all the current local space scale values for the bones in this model

const std::vector< XVector >& Scale() const { return m_Scale; }

// returns an array of all the current local space rotation values for the bones in this model

const std::vector< XQuaternion >& Rotation() const { return m_Rotate; }

// sets the number of bones within the bone anim list

void
SetNumBones(unsigned int size) {
m_Translate.resize(size);

m_Scale.resize(size);

m_Rotate.resize(size);

}


// ctor
AnimCycleInstance() : m_Translate(),m_Scale(),m_Rotate(),m_MaxTime(0) {}
private:
// all of the current local space translation values for the bones in this model

std::vector< XVector > m_Translate;

// all of the current local space scale values for the bones in this model

std::vector< XVector > m_Scale;

// all of the current local space rotation values for the bones in this model

std::vector< XQuaternion > m_Rotate;

// the maximum time in the cycle

AnimCycle* m_pCycle;
// the current time value within the cycle

float m_fCurrentTime;
};

 

 

Blending The Cycles

What we want to create is essentially a small mixer class capable of blending our component scale, rotation and translation values from both cycles together. It contains two animation cycle instances, both of which reference an Animation data chunk.

This Mixer simply allows you to switch from one animation to another one via a smooth blend of a specified number of seconds. The process of blending itself is straight forward, we simply have to lineraly interpolate the scale and translation values of both cycles, and spherically interpolate their rotation quaternions.

If however the mixer is not switching cycles, it automatically just runs using the current cycle instance.

This animation mixer is a fairly simple case of how to actually blend together two animation cycles. For a fully fledged game engine you may want to run numerous cycles on various parts of the body blended together, this should just be a simple extension of the techniques presented here.

 

 

 

 

// a structure to hold the keyframes for a single bone in an animation cycle
struct AnimCycleMixer {

// returns the max time in this cycle
float MaxTimeCurrent() const {
return m_pCycles[!m_NextCycle].MaxTime();
}
float MaxTimeNext() const {
return m_pCycles[m_NextCycle].MaxTime();
}

// sets the current animation cycle to use
bool SetNextCycle(AnimCycle* cycle,float transition_time) {
// if already blending two cycles, return false
if(m_fTimeForTransition>0) return false;
m_fTotalTimeForTransition = m_fTimeForTransition = transition_time;
m_pCycles[m_NextCycle] = cycle;
m_NextCycle = !m_NextCycle;
m_NextCycle = 1;
m_fTimeForTransition = -1;
}

// sets the current animation cycle to use
void SetCycle(AnimCycle* cycle) {
m_NextCycle = 1;
m_fTimeForTransition = -1;
m_pCycles[0] = cycle;
m_pCycles[1] = 0;
}

// sets the current animation cycle to use
bool Update(float dt) {
if(m_fTimeForTransition<0) {
return m_pCycles[!m_NextCycle].Update(dt);
}

m_fTimeForTransition -= dt;
if(m_fTimeForTransition<0) {
m_NextCycle = !m_NextCycle;

return m_pCycles[!m_NextCycle].Update(dt)
}
// set a flag when we pass the end of the cycle
bool flag = m_pCycles[!m_NextCycle].Update(dt);
// set a flag when we pass the end of the cycle
flag = flag || m_pCycles[m_NextCycle].Update(dt);

float t = 1.0f-m_fTimeForTransition/m_fTotalTimeForTransition;
// interpolate the translation values
{
std::vector< XVector >::iterator it = m_Translate.begin();
std::vector< XVector >::const_iterator ita = m_pCycles[!m_NextCycle].Translation().begin();
std::vector< XVector >::const_iterator itb = m_pCycles[m_NextCycle].Translation().begin();
// iterate through the translation values and linearly interpolate between them
for( ; it != m_Translate.end(); ++it,++ita,++itb ) {
LERP( *it, t, *ita, *itb );
}
}

// interpolate the scale values
{
std::vector< XVector >::iterator it = m_Scale.begin();
std::vector< XVector >::const_iterator ita = m_pCycles[!m_NextCycle].Scale().begin();
std::vector< XVector >::const_iterator itb = m_pCycles[m_NextCycle].Scale().begin();
// iterate through the translation values and linearly interpolate between them
for( ; it != m_Scale.end(); ++it,++ita,++itb ) {
LERP( *it, t, *ita, *itb );
}
}

// spherically interpolate the rotation values
{
std::vector< XQuaternion >::iterator it = m_Rotate.begin();
std::vector< XQuaternion >::const_iterator ita = m_pCycles[!m_NextCycle].Rotation().begin();
std::vector< XQuaternion >::const_iterator itb = m_pCycles[m_NextCycle].Rotation().begin();
// iterate through the translation values and spherically interpolate between them
for( ; it != m_Rotate.end(); ++it,++ita,++itb ) {
SLERP( *it, t, *ita, *itb );
}
}
return flag;
}


// returns an array of all the current local space translation values for the bones in this model

const std::vector< XVector >& Translation() const {
if(m_fTimeForTransition<0)
m_pCycles[!m_NextCycle].Translation();
return m_Translate;
}

// returns an array of all the current local space scale values for the bones in this model

const std::vector< XVector >& Scale() const {
if(m_fTimeForTransition<0)
m_pCycles[!m_NextCycle].
Scale();
return m_Scale;
}

// returns an array of all the current local space rotation values for the bones in this model

const std::vector< XQuaternion>& Rotation() const {
if(m_fTimeForTransition<0)
m_pCycles[!m_NextCycle].
Rotation();
return m_Rotate;
}


// sets the number of bones within the bone anim list

void
SetNumBones(unsigned int size) {
m_Translate.resize(size);

m_Scale.resize(size);

m_Rotate.resize(size);

m_pCycles[0].SetNumBones(size);
m_pCycles[1].SetNumBones(size);
}


// ctor
AnimCycleMixer() : m_Translate(),m_Scale(),m_Rotate(),m_NextCycle(0),
m_fTotalTimeForTransition(0),m_fTimeForTransition(-1) {
}
private:
// the blended current local space translation values for the bones in this model

std::vector< XVector > m_Translate;

// the blended current local space scale values for the bones in this model

std::vector< XVector > m_Scale;

// the blended current local space scale values for the bones in this model

std::vector< XQuaternion > m_Rotate;

// the blended current local space rotation values for the bones in this model

unsigned int m_NextCycle;

// the maximum time in the cycle

AnimCycleInstance m_pCycles[2];

// the total time specified for the transition between the two cycles to occur
float m_fTotalTimeForTransition;

// the time remaining for the transition between the two cycles to occur
float m_fTimeForTransition;
};

 

 

Generating The Local Space Bone Matrices

Having blended together the local space transforms, we will need to convert these to local, world and inverse matrices for each bone.

 

 

 

 


struct BoneInfo {

// the index to the parent bone

unsigned int
parent;
// the orientation of the joints local axis

XQuaternion
joint_orient;


void
CalculateLocal(XMatrix& local,
const XVector& scale,
const XVector& translate,
const XQuaternion& rotate) {


XMatrix OrientationMatrix;
XMatrix RotationMatrix;

// get the joint orientation quaternion to a matrix
joint_orient.
AsMatrix(OrientationMatrix.data);

// get the rotation matrix from the rotation quaternion of this bones transform
rotate.AsMatrix(RotationMatrix.data);

// add the translation into the matrix to simply things
OrientationMatrix.data[12] = translate.x;
OrientationMatrix.data[13] = translate.y;
OrientationMatrix.data[14] = translate.z;

// local space xform = OrientationMatrix * RotationMatrix
local = OrientationMatrix;
local *= RotationMatrix;

// apply scale to local space matrix
local.Scale(scale);

}


void
CalculateWorld(const std::vector<XMatrix>& world_matrices,
const XMatrix& local,
XMatrix& world,
XMatrix& inverse) {

// world space = ParentMatrix * LocalSpaceMatrix
world = world_matrices[parent];
world *= local;

// store the inverse cos it may be useful for deformations
world.Inverse(inverse);

}

};
struct HierarchyData {
unsigned short
byte_size;
unsigned
short
num_bones;
BoneInfo bones[1];

void
CalculateLocal(std::vector<XMatrix>& local,
const std::vector<XVector>& scale,
const std::vector<XVector>& translate,
const std::vector<XQuaternion>& rotate) {

BoneInfo *bi =
bones;
BoneInfo *end =
bi + num_bones;
std::vector<XMatrix>::iterator itL = local.begin();
std::vector<XVector>::const_iterator itS = scale.begin();
std::vector<XVector>::const_iterator itT = translate.begin();
std::vector<XQuaternion>::const_iterator itR = rotate.begin();

for( ; bi != end; ++itL,++itS,++itT,++itR) {

bi->
CalculateLocal(*itL,*itS,*itT,*itR);
}

}
void
CalculateWorld(const std::vector<XMatrix>& local,
std::vector<XMatrix>& world,
std::vector<XMatrix>& inverse) {

BoneInfo *bi =
bones;
BoneInfo *end =
bi + num_bones;
std::vector<XMatrix>::const_iterator itL = local.begin();
std::vector<XMatrix>::iterator itW = world.begin();
std::vector<XMatrix>::iterator itI = inverse.begin();

for( ; bi != end; ++itL,++itW,++itI) {
bi->
CalculateWorld(world,*itL,*itW,*itI);
}

}
};

 

 

Skeletal Data and Skeleton instances

It's slightly important to order the data into two seperate structures. First is all of the skeletal data that does not vary, and then is all of the variable data (which belongs to each instance).

 

 

 

 



struct SkeletonData {
// the bone hierarchy info

HierarchyData* pHierarchy;
// an array of the animation cycles

std::vector<AnimCycle*> m_aAnimCycles;
// the inverse bind pose transforms

std
::vector<XMatrix> m_BindPoseInverse;

// return the pointer to the animation cycle

AnimCycle* GetCycle(unsigned int ref) {
assert( ref < m_aAnimCycles.size() );
return m_aAnimCycles[ref];
}

};

struct SkeletonInstance {
private:

// the bone hierarchy data

SkeletonData
* m_HierarchyData;

// the animation mixer for the class

AnimCycleMixer
m_Mixer;

// the animation mixer for the class

std
::vector<XMatrix> m_Local;

// the animation mixer for the class

std::vector<XMatrix> m_World;

// the inverse world space transforms for the bone

std::vector<XMatrix> m_Inverse;
public:


SkeletonInstance( SkeletonData* data ) {
m_HierarchyData = data;
m_Mixer.SetCycle(0);
m_Local.resize(InverseBindPose().size());
m_World.resize(InverseBindPose().size());
m_Inverse.resize(InverseBindPose().size());
}

// returns the local space matrices array

const std::vector<XMatrix>& Local() const{
return m_Local;
}

// returns the world space matrices array

const std::vector<XMatrix>& World() const{
return m_World;
}

// returns the inverse world space matrices array

const std::vector<XMatrix>& Inverse() const{
return m_Inverse;
}

// returns the inverse bind pose matrices array

const std::vector<XMatrix>& InverseBindPose() const {
return m_HierarchyData->m_BindPoseInverse;
}

// returns the max time in this cycle
float MaxTimeCurrent() const {
return m_Mixer.MaxTimeCurrent();
}

// sets the current animation cycle to use
float MaxTimeNext() const {
return m_Mixer.MaxTimeNext();
}

// sets the current animation cycle to use
bool SetNextCycle(unsigned int cycle,float transition_time) {
return m_Mixer.SetNextCycle(m_HierarchyData->GetCycle(cycle),transition_time);
}

// sets the current animation cycle to use
void SetCycle(unsigned int cycle) {
m_Mixer.SetCycle(m_HierarchyData->GetCycle(cycle));
}

// sets the current animation cycle to use
bool Update(float dt) {
// update the animation mixer

bool b = m_Mixer.Update(dt);

// calculate the local space matrices

m_HierarchyData->pHierarchy->CalculateLocal( m_Local,
m_Mixer.Scale(),
m_Mixer.Translation(),
m_Mixer.Rotations() );

// calculate the world space matrices

m_HierarchyData->pHierarchy->CalculateWorld( m_Local ,
m_World ,
m_Inverse );
return b;
}
};

 

 

 

Relevant Maya API documents

Extracting Transform Data (MFnTransform)

Extracting Sampled Animation Data

Extracting Animation Curves