# Time-based animation

This topic is 2555 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

## Recommended Posts

Hello, I'm writing a simple 3d engine that uses keyframes to animate a mesh. I'm following the book "Advanced Animation with DirectX" by Jim Adams. It's a little confusing but I've managed to make some progress .

I have a KeyFrame class that stores the time and transformation matrix of each keyframe. My Animation class stores a vector of KeyFrames and an Update() function that is called (before?) every frame update.

Here's a snippet from the book:

void FrameUpdate()
{
static DWORD StartTime = timeGetTime();
DWORD Elapsed = timeGetTime() ? StartTime;

With the elapsed time now in hand, you can scan the key frames to look for the two between which the time value lies. For example, if the current time is 60 milliseconds, the animation is somewhere between key frame #0 (at 0 milliseconds) and key frame #1 (at 400 milliseconds). A quick scan through the key frames determines which to use based on the elapsed time.

DWORD Keyframe = 0; // Start at 1st keyframe
for(DWORD i=0;i<4;i++) {
// If time is greater or equal to a key?frame's time then update the keyframe to use
if(Time >= Keyframes.Time)
Keyframe = i;
}

At the end of the loop, the Keyframe variable will hold the first of the two key frames between which the animation time lies. If Keyframe isn't the last key frame in the array (in which there are four key frames), then you can add 1 to Keyframe to obtain the second key frame. If Keyframe is the last key frame in the array, you can use the same key?frame value in your calculations. Using a second variable to store the next key frame in line is perfect. Remember that if Keyframe is the last key frame in the array, you need to set this new key frame to the same value.

DWORD Keyframe2 = (Keyframe==3) ? Keyframe:Keyframe + 1;

Now you need to grab the time values and calculate a scalar based on the time difference of the keys and the position of the key frame between the keys.

DWORD TimeDiff = Keyframes[Keyframe2].Time ? Keyframes[Keyframe].Time;
// Make sure there's a time difference to avoid divide?by?zero errors later on.
if(!TimeDiff) TimeDiff=1;

float Scalar = (Time ? Keyframes[Keyframe].Time)/TimeDiff;

You now have the scalar value (which ranges from 0 to 1) used to interpolate the transformation matrices of the keys.
[/quote]

I've modified the code a bit, here is my Update function:

D3DXMATRIX Animation::Update(void)
{
DWORD keyFrame, keyFrame2;

if (p_StartTime == 0) p_StartTime = timeGetTime();
p_ElapsedTime = timeGetTime() - p_StartTime;

for (DWORD i = 0; i < Get_KeyFrameCount(); i++)
{
if (p_ElapsedTime >= p_KeyFrames->Get_Time()) keyFrame = i;
}

keyFrame2 = (keyFrame == Get_KeyFrameCount() - 1) ? keyFrame:keyFrame + 1;

DWORD timeDiff = p_KeyFrames[keyFrame2]->Get_Time() - p_KeyFrames[keyFrame]->Get_Time();
if (!timeDiff) timeDiff = 1;

float scalar = (p_ElapsedTime - p_KeyFrames[keyFrame]->Get_Time()) / timeDiff;

//...
};
[/quote]

Everything seems to work but the scalar returns weird numbers like 3209.0, 1203.0, instead of a value between 0-1. Am I doing something wrong? Thanks for the help in advance!

##### Share on other sites
It looks like float scalar may be the result of an integer divide (for you to check). In addition, you have a parentheses out of place. Should be something like:
 float scalar = (p_Elapsed - pKeyFrames.Time)/timeDiff; // .... NOT AS BELOW float scalar = (p_Elapsed - pKeyFrames.Time/timeDiff); 

##### Share on other sites
grr I thought I edited the post and fixed the parentheses, my mistake. Either way it still returns the same numbers. I don't know whats wrong

##### Share on other sites
Well, looking back at the book it also uses DWORD for Time, so I don't understand how deviding 2 integers returns a float value between 0 and 1? Every time it returns 0 or some odd 4 digit number. Should I cast them to float before deviding? Like this?

float scalar = (float)(p_ElapsedTime - p_KeyFrames[p_KeyFrame]->Get_Time()) / (float)timeDiff;
[/quote]

##### Share on other sites

Well, looking back at the book it also uses DWORD for Time, so I don't understand how deviding 2 integers returns a float value between 0 and 1? Every time it returns 0 or some odd 4 digit number. Should I cast them to float before deviding? Like this?

float scalar = (float)(p_ElapsedTime - p_KeyFrames[p_KeyFrame]->Get_Time()) / (float)timeDiff;

[/quote]
Casting either the numerator or denominator should be sufficient but, yes, a cast to float is required.

##### Share on other sites
Well I figured it out, there was code missing from the book but by some miracle I got it working . Now I am faced with another problem...

The scalar variable returns the correct values, now I need to "linearly interpolate between the 2 transformation matrices at each keyframe" to get the final transformation matrix. In the book he uses D3DMATRIX in his Keyframe struct and casts it to D3DXMATRIX in the Update function:

// Calculate the difference in transformations
D3DXMATRIX matInt = D3DXMATRIX(Keyframes[Keyframe2].matTransformation) - D3DXMATRIX(Keyframes[KeyFrame].matTransformation);

// Scale the difference
matInt *= Scalar;

// Add scaled transformation matrix back to 1st keyframe matrix
matInt += D3DXMATRIX(Keyframes[KeyFrame].matTransformation);
[/quote]

My KeyFrame class already stores the transform matrix as a D3DXMATRIX so I don't need to cast anything. Will the math still work the same? This is supposed to return the "interpolated" matrix between 2 keyframes but I don't think its working. Nothing happens when I use this matrix to render the mesh, or when I multiply it by the world matrix.

D3DXMATRIX Animation::Update(void)
{
if (Get_KeyFrameCount() == 1) return p_KeyFrames[0]->Get_TransformMatrix();
else
{
if (p_StartTime == 0) p_StartTime = timeGetTime();
p_ElapsedTime = timeGetTime() - p_StartTime;

// Missing from original code
DWORD duration = p_ElapsedTime % p_KeyFrames[Get_KeyFrameCount() - 1]->Get_Time();

for (DWORD i = 0; i < Get_KeyFrameCount(); i++)
{
if (duration >= p_KeyFrames->Get_Time()) p_KeyFrame = i;
}

p_KeyFrame2 = (p_KeyFrame == Get_KeyFrameCount() - 1) ? p_KeyFrame : p_KeyFrame + 1;

DWORD timeDiff = p_KeyFrames[p_KeyFrame2]->Get_Time() - p_KeyFrames[p_KeyFrame]->Get_Time();
if (timeDiff == 0) timeDiff = 1;

float scalar = (float)(duration - p_KeyFrames[p_KeyFrame]->Get_Time()) / (float)timeDiff;

D3DXMATRIX matInt = p_KeyFrames[p_KeyFrame2]->Get_TransformMatrix() - p_KeyFrames[p_KeyFrame]->Get_TransformMatrix();
matInt *= scalar;
matInt += p_KeyFrames[p_KeyFrame]->Get_TransformMatrix();
return matInt;

}
};
[/quote]

##### Share on other sites
First, the formatting of your code snippets isn't nice to read. Please use the "Insert code snippet" button just to the right of "Insert quotation" button for code snippets!

The "addition and subtraction stuff" the author of the book is using is named linear interpolation. The internet gives you a plenty of explanations, and I hope the book explains it, too. Furthermore I hope that the book tells you that the shown code snippet is a no-go as soon as any non-linear transformation is part of the keyframes. E.g. the shown method will work fine for translation and scaling, but it will fail for rotation. Even for small rotations I'd have expected some kind of re-orthonormalization for the rotational part.

Besides that, the shown code snippet seems me correct. May be that the resolution of timeGetTime() is a bit to low (I've read "above 1 millisecond"). And be aware that in the 1st run p_ElapsedTime will be 0, so that p_KeyFrames[0] will be returned. So, have you tested this in a run where p_ElapsedTime lies nicely in the middle of the both first keyframes?

##### Share on other sites
As mentioned by haegarr, rotations can't be interpolated with the method you've posted.

It's more common to use a vector for the translation (and scale, if necessary) and a quaternion for the rotation. Then the interpolation between keyframes is something like (pseudo-code):
 D3DXVECTOR3 transVectorInterp = transVectorKeyFrame1 + (1-scalar)*transVectorKeyFrame2; // D3DXVec3Lerp can be used also D3DXQUATERNION quaternionInterp = D3DXQuaternionSlerp( quatKeyFrame1, quatKeyFrame2, scalar ); D3DXMatrixTranslation( matTrans, transVectorInterp.x, ...); D3DXMatrixRotationQuaternion( matRot, quaternionInterp ); finalMatrix = matRot * matTrans; 

If keyframe data is stored as a matrix, rather than as a translation vector and quaternion for rotation*, it get's a bit inefficient to use this method as you'll have to decompose the keyframe matrix into scale and translation vectors, and a quaternion. You might want to consider storing keyframe data itself with vectors and a quaternion.

*I believe this storage scheme for keyframe data is more common.

EDIT: Be careful about operation precedence. For instance, in the line:
p_KeyFrame2 = (p_KeyFrame == Get_KeyFrameCount() - 1) ? p_KeyFrame : p_KeyFrame + 1;

if I remember correctly, the == operator has a higher precedence than arithmetic operators. That line should probably be:
p_KeyFrame2 = p_KeyFrame == (Get_KeyFrameCount() - 1) ? p_KeyFrame : p_KeyFrame + 1;

##### Share on other sites

First, the formatting of your code snippets isn't nice to read. Please use the "Insert code snippet" button just to the right of "Insert quotation" button for code snippets!

The "addition and subtraction stuff" the author of the book is using is named linear interpolation. The internet gives you a plenty of explanations, and I hope the book explains it, too. Furthermore I hope that the book tells you that the shown code snippet is a no-go as soon as any non-linear transformation is part of the keyframes. E.g. the shown method will work fine for translation and scaling, but it will fail for rotation. Even for small rotations I'd have expected some kind of re-orthonormalization for the rotational part.

Besides that, the shown code snippet seems me correct. May be that the resolution of timeGetTime() is a bit to low (I've read "above 1 millisecond"). And be aware that in the 1st run p_ElapsedTime will be 0, so that p_KeyFrames[0] will be returned. So, have you tested this in a run where p_ElapsedTime lies nicely in the middle of the both first keyframes?

Sorry, I tried using the code snippet tag but it didn't work when I tried I'll use it from now on.

That's the thing, this book doesn't explain anything very well. It jumps around alot and provides lots of code without much information, but it's the only book I could find on keyframe animation I guess I'll search the web for some examples/tutorials. I just prefer ebooks.

Thanks for letting me know about the rotation, I probably would've spent hours messing with code before realizing it myself. Thats definately something he should have brought up in the book.

##### Share on other sites

As mentioned by haegarr, rotations can't be interpolated with the method you've posted.

It's more common to use a vector for the translation (and scale, if necessary) and a quaternion for the rotation. Then the interpolation between keyframes is something like (pseudo-code):
 D3DXVECTOR3 transVectorInterp = transVectorKeyFrame1 + (1-scalar)*transVectorKeyFrame2; // D3DXVec3Lerp can be used also D3DXQUATERNION quaternionInterp = D3DXQuaternionSlerp( quatKeyFrame1, quatKeyFrame2, scalar ); D3DXMatrixTranslation( matTrans, transVectorInterp.x, ...); D3DXMatrixRotationQuaternion( matRot, quaternionInterp ); finalMatrix = matRot * matTrans; 

If keyframe data is stored as a matrix, rather than as a translation vector and quaternion for rotation*, it get's a bit inefficient to use this method as you'll have to decompose the keyframe matrix into scale and translation vectors, and a quaternion. You might want to consider storing keyframe data itself with vectors and a quaternion.

Thanks for the advice! Good to know because rotation is the main reason I'm using keyframes.

Technically, my keyframe data isn't stored as a matrix. My Animation class loads the keyframes from a mock INI file that looks like this:

// Key=<int|Time> <float|posX posY posZ> <float|rotX rotY rotZ> <float|scaleX scaleY scaleZ>

[Animation]
Subset=1
Loop=-1
Key=0 0 0 0 0 0 0 1 1 1
Key=1000 0 0 0 0 0.5 0 1 1 1
Key=2000 0 0 0 0 0 0 1 1 1
[/quote]

Then I have a Transform class that stores the position, rotation, and scale in 3 D3DXVECTOR3's, and a function Get_Matrix() that converts them into a transformation matrix:

 D3DXMATRIX Transform::Get_Matrix(void) { D3DXMATRIXA16 matScale, matRot, matTrans; D3DXMatrixScaling(&matScale, p_Scale.x, p_Scale.y, p_Scale.z); D3DXMatrixRotationYawPitchRoll(&matRot, p_Rotation.x, p_Rotation.y, p_Rotation.z); D3DXMatrixTranslation(&matTrans, p_Position.x, p_Position.y, p_Position.z); return matScale * matRot * matTrans; }; 

Your method above would work great with translation and scale, but I would need a quaternion for rotation.

Is there another alternative to spherical interpolation (slerp) that works with vectors? Wikipedia talks about geometric slerp and quaternion slerp. The reason I ask is because I want to keep my animation files human-readable and easy to edit. I'd like to stay away from quaternions unless there is no other option.

Edit: Is there a way to convert a rotation vector into a quaternion? That would solve my problem.

##### Share on other sites
Is there a way to convert a rotation vector into a quaternion? [/quote]
Not sure what you mean by "rotation vector," but look at the D3DXQuaternion... functions to create a quaternion in one of several ways.

##### Share on other sites
What I meant was, is there a function that takes yaw/pitch/roll values and returns a quaternion, but you pointed me in the right direction.

It works now! But something is still wrong. Translation and rotation seem to be working fine but the interpolated scale is off. My keyframes are all set to 1.0, 1.0, 1.0 scale, but when I render the animation the mesh shrinks... No idea why Am I supposed to normalize something?

 Transform* Animation::Update(void) { if (Get_KeyFrameCount() == 1) return p_KeyFrames[0]->Get_Transform(); D3DXVECTOR3 transInterp, scaleInterp; D3DXQUATERNION rotInterp; if (p_StartTime == 0) p_StartTime = timeGetTime(); p_ElapsedTime = timeGetTime() - p_StartTime; p_Duration = p_ElapsedTime % p_KeyFrames[Get_KeyFrameCount() - 1]->Get_Time(); for (DWORD i = 0; i < Get_KeyFrameCount(); i++) { if (p_Duration >= p_KeyFrames->Get_Time()) p_KeyFrame = i; } p_KeyFrame2 = p_KeyFrame == (Get_KeyFrameCount() - 1) ? p_KeyFrame : p_KeyFrame + 1; DWORD timeDiff = p_KeyFrames[p_KeyFrame2]->Get_Time() - p_KeyFrames[p_KeyFrame]->Get_Time(); if (timeDiff == 0) timeDiff = 1; float scalar = (float)(p_Duration - p_KeyFrames[p_KeyFrame]->Get_Time()) / (float)timeDiff; transInterp = p_KeyFrames[p_KeyFrame]->Get_Position() + (1 - scalar) * p_KeyFrames[p_KeyFrame2]->Get_Position(); scaleInterp = p_KeyFrames[p_KeyFrame]->Get_Scale() + (1 - scalar) * p_KeyFrames[p_KeyFrame2]->Get_Scale(); D3DXQuaternionSlerp(&rotInterp, &p_KeyFrames[p_KeyFrame]->Get_Rotation(), &p_KeyFrames[p_KeyFrame2]->Get_Rotation(), scalar); return new Transform(transInterp, rotInterp, scaleInterp); }; 

 D3DXMATRIX Transform::Get_Matrix(void) { D3DXMATRIXA16 matScale, matRot, matTrans; D3DXMatrixScaling(&matScale, p_Scale.x, p_Scale.y, p_Scale.z); D3DXMatrixRotationQuaternion(&matRot, &p_Rotation); D3DXMatrixTranslation(&matTrans, p_Position.x, p_Position.y, p_Position.z); return matScale * matRot * matTrans; }; 

##### Share on other sites
What have you checked with regard to the values you calculate in your Update function? You can't always debug code just by observing what it does.

##### Share on other sites
Well I've tried using different animation files with a different amount of keyframes and they all seem to perform the same. I've also tried animating different subsets of the mesh. Translation and rotation work exacly as expected, but the mesh (or subset) shrinks from the first keyframe to the last.

// Move mesh 5 points along z axis and back again
[Animation]
Subset=-1
Loop=-1
Key=0 0 0 0 0 0 0 1 1 1
Key=1000 0 0 5 0 0 0 1 1 1
Key=2000 0 0 0 0 0 0 1 1 1

// Rotate subset[1] 1.5 radians along x axis
[Animation]
Subset=1
Loop=-1
Key=0 0 0 0 0 0 0 1 1 1
Key=1000 0 0 0 1.5 0 0 1 1 1

// Move and rotate subset[2]
[Animation]
Subset=2
Loop=-1
Key=0 0 0 0 0 0 0 1 1 1
Key=2000 -5 0 0 1.5 0 0 1 1 1
Key=4000 0 0 0 0 0 0 1 1 1
Key=6000 5 0 0 -1.5 0 0 1 1 1
[/quote]

##### Share on other sites
Okay, it appears the keys all have scales of ( 1,1,1 ) - if the last 3 digits are scale - as you said. As mentioned above, what have you done to determine that your Update function works as expected? Have you stepped through the code, looking at values (such as scaleInterp) when they're calculated?

##### Share on other sites
After looking at the values more closely, scale isn't the only thing acting strange. Consider the following animation:

// Move 3 points along x axis
[Animation]
Subset=1
Loop=-1
Key=0 0 0 0 0 0 0 1 1 1
Key=100 3 0 0 0 0 0 1 1 1
[/quote]

I used OutputDebugString to look at the interpolated values, transInterp should start at 0, 0, 0 and end at 3, 0, 0 but it looks like it's doing the opposite. scaleInterp starts at 2, 2, 2 and ends at 1, 1, 1 (no idea why). rotInterp works as expected, so I'm guessing it has something to do with the equasion (keyframe1Scale + (1 - scalar) * keyframe2Scale)

Time: 0 ms
transInterp: 3.0000 0.0000 0.0000
rotInterp: 0.0000 0.0000 0.0000 1.0000
scaleInterp: 2.0000 2.0000 2.0000

Time: 40 ms
transInterp: 1.8000 0.0000 0.0000
rotInterp: 0.0000 0.0000 0.0000 1.0000
scaleInterp: 1.6000 1.6000 1.6000

Time: 50 ms
transInterp: 1.5000 0.0000 0.0000
rotInterp: 0.0000 0.0000 0.0000 1.0000
scaleInterp: 1.5000 1.5000 1.5000

Time: 70 ms
transInterp: 0.9000 0.0000 0.0000
rotInterp: 0.0000 0.0000 0.0000 1.0000
scaleInterp: 1.3000 1.3000 1.3000

Time: 90 ms
transInterp: 0.3000 0.0000 0.0000
rotInterp: 0.0000 0.0000 0.0000 1.0000
scaleInterp: 1.1000 1.1000 1.1000

Time: 100 ms
transInterp: 3.0000 0.0000 0.0000
rotInterp: 0.0000 0.0000 0.0000 1.0000
scaleInterp: 2.0000 2.0000 2.0000
[/quote]

##### Share on other sites
Linear [size=2]interpolation is done as
[size=2]g( w ) := f[sub]1[/sub] [size=2]* ( 1 - w ) + f[sub]2[/sub][size=2] * w == f[sub]1[/sub][size=2] + ( f[sub]2[/sub][size=2] - f[sub]1[/sub][size=2] ) * w
[size=2]with w being the weight, f[sub]1[/sub][size=2] being the "start" value and f[sub]2[/sub][size=2] the "end" value, so that
[size=2]g( w=0 ) == f[sub][size=2]1[/sub][size=2]
g( w=1 ) == f[sub][size=2]2[/sub][size=2]
[sub][/sub]
[sub]Use that for translation as well as for scaling.[/sub]
[sub] [/sub]
[sub]For comparison: Your (wrong) equation seems to be [/sub]
[sub]

### [size=2]f[sub]1[/sub] [size=2]+ ( 1 - w ) * f[sub]2[/sub][/sub]

##### Share on other sites
I figured it out by using D3DXVec3Lerp as Buckeye mentioned above. Works perfect now thanks to both of you for the help and patience!