天行健 君子当自强而不息

Blending Skeletal Animations(2)

Enhancing Skeletal Animation Objects

Now that you've seen how simple it is to blend multiple skeletal animations, why not take this new knowledge and add on to the skeletal animation objects? Sounds like a great idea; by adding a single function to the cAnimationCollection class, you can be on your way to blending animations like the pros.

In fact, rather than messing with the code from cAnimationCollection, just derive a new class that handles blended animations. This new derived class, cBlendedAnimationCollection, is defined as follows:

class cBlendedAnimationCollection : public cAnimationCollection
{
public:
void Blend(char *AnimationSetName,
DWORD Time, BOOL Loop,
float Blend = 1.0f);
};

Wow, that's a small class! The one and only function declared in cBlendedAnimationCollection is Blend, which is meant to take over the cAnimationCollection::Update function. Why not just derive a new Update function, you ask? Well, with cBlendedAnimationCollection, you can use the regular animation sets you used, as well as your (soon to be) newly developed blended animation sets.

Take a close look at the Blend function to see what's going on, and then I'll show you how to put your new class to good use.

void cBlendedAnimationCollection::Blend(
    char *AnimationSetName,
    DWORD Time, BOOL Loop,
    float Blend)
{

The Blend function prototype takes four parameters, the first of which is AnimationSetName. When calling Blend, you need to set AnimationSetName to the name of the animation set you are going to blend into the animation. Each animation set in your source .X file has a unique animation name (as defined by the AnimationSet data object's instance name). You must set AnimationSetName to a matching name from the .X file.

I'll get back to the animation sets in a bit. For now, I want to get back to the Blend prototype. The second parameter of Blend is Time, which represents the time in the animation that you are using to blend the animation. If you have an animation that is 1000 milliseconds in length, then Time can be any value from 0 to 999. Specifying a value larger than the animation's length forces the Blend function to use the last key frame in the list to blend the animation.

What about looping the animation? Well, that's the purpose of the third parameter, Loop. If you set Loop to FALSE, then your animations will refuse to update if you try to update using a time value that is greater than the length of the animation. However, if you set Loop to TRUE, the Blend function bounds−checks the time value (Time) to always fall within the range of the animation's time.

The previous paragraph may not make perfect sense at first, so to help you understand, imagine the following function:

void UpdateAnimation(DWORD Elapsed)
{
static DWORD AnimationTime = 0; // Animation time
	// Call Blend, using AnimationTime as the time in the animation
AnimationBlend.Blend("Walk", AnimationTime, FALSE, 1.0f);
	// Update the time of the animation
AnimationTime += ELapsed;
}

In the UpdateAnimation function, you are tracking the animation time via a static variable. Every time UpdateAnimation is called, the Blend function is used to blend in an animation called Walk, using a time value specified as AnimationTime. Assuming the Walk animation is 1000 milliseconds in length and the elapsed time between calls to UpdateAnimation is 50 ms, you can see tell that the animation would reach its end after 20 calls to the function. This means after you call UpdateAnimation 20 times, the animation will stop (because you set Loop to FALSE).

Going back and changing the Loop value to TRUE forces Blend to bounds−check the timing value and make sure it always uses a valid timing value. When I say bounds−check, I mean to use a modulus calculation. I'll show you how to use the modulus calculation in a moment; for now I want to get back to the fourth and final parameter.

The last parameter is Blend, which is a floating−point value that represents a scalar value used to modify the blended transformation matrix before it is applied to the skeletal structure. For example, if you are blending a walking animation, but you only want 50 percent of the transformation to be applied, then you would set Blend to 0.5.

Okay, that's enough for the parameters; let's get into the function code! If you've perused the cAnimationCollection::Update function, you'll notice that the majority of code in the Blend function is the same. Starting off, you'll find a bit of code that scans the linked list of animation sets to find the one that matches the name you provided as AnimationSetName.

cAnimationSet *AnimSet = m_AnimationSets;
// Look for matching animation set name if used
if(AnimationSetName) {
	// Find matching animation set name
while(AnimSet != NULL) {
// Break when match found
if(!stricmp(AnimSet−>m_Name, AnimationSetName))
break;
		// Go to next animation set object
AnimSet = AnimSet−>m_Next;
}
}
// Return no set found
if(AnimSet == NULL)
return;

If you set AnimationSetName to NULL, then Blend will use the first animation set in the linked list of animation sets. If you specified a name in AnimationSetName and none was found in the linked list, then Blend will return without any further ado.

Now that you have a pointer to the appropriate animation set object, you can bounds−check the time according to the value set in Time and the looping flag Loop.

// Bounds time to animation length
if(Time > AnimSet−>m_Length)
    Time = (Loop==TRUE)?Time % (AnimSet−>m_Length+1):AnimSet−>m_Length;

Quite an ingenious little bit of code, the previous tidbit does one of two things, depending on the Loop flag. If Loop is set to FALSE, then Time is checked against the length of the animation (AnimSet−>m_Length). If Time is greater than the length of the animation, then Time is set to the length of the animation, thus locking it at the last millisecond (and later on, the last key frame) of the animation. If you set Loop to TRUE, then a modulus calculation forces Time to always lie within the range of the animation's length (from 0 to AnimSet−>m_Length).

After you have calculated the appropriate Time to use for the animation, it is time to scan the list of bones in your skeletal structure. For each bone, you are going to track the combined transformations from the appropriate key frames. For each key frame found in the animation, you need to add (not multiply) the transformation to the skeletal structure's transformations.

// Go through each animation
cAnimation *Anim = AnimSet−>m_Animations;
while(Anim) {
//Only process if it's attached to a bone
if(Anim−>m_Bone) {
// Reset transformation
D3DXMATRIX matAnimation;
D3DXMatrixIdentity(&matAnimation);
		// Apply various matrices to transformation

From here, scan each key frame (depending on the type of keys used) and calculate the transformation to apply to your skeletal structure. For the sake of space, I'm only going to list the code that scans matrix keys.

// Matrix
if(Anim−>m_NumMatrixKeys && Anim−>m_MatrixKeys) {
// Loop for matching matrix key
DWORD Key1 = 0, Key2 = 0;
	for(DWORD i=0;i<Anim−>m_NumMatrixKeys;i++) {
if(Time >= Anim−>m_MatrixKeys[i].m_Time)
Key1 = i;
}
	// Get 2nd key number
Key2 = (Key1>=(Anim−>m_NumMatrixKeys−1))?Key1:Key1+1;
	// Get difference in keys' times
DWORD TimeDiff = Anim−>m_MatrixKeys[Key2].m_Time − Anim−>m_MatrixKeys[Key1].m_Time;
	if(!TimeDiff)
TimeDiff = 1;
	// Calculate a scalar value to use
float Scalar = (float)(Time − Anim−>m_MatrixKeys[Key1].m_Time) / (float)TimeDiff;
	// Calculate interpolated matrix
D3DXMATRIX matDiff;
matDiff = Anim−>m_MatrixKeys[Key2].m_matKey − Anim−>m_MatrixKeys[Key1].m_matKey;
matDiff *= Scalar;
matDiff += Anim−>m_MatrixKeys[Key1].m_matKey;
	// Combine with transformation
matAnimation *= matDiff;
}

Basically, the code is searching the key frames and calculating an appropriate transformation to use. This transformation is stored in
matAnimation.

From this point on, things take a decidedly different course than the cAnimationCollection::Update function code. Instead of storing the transformation matrix (matAnimation) in the skeletal structure's frame object, you will calculate the difference in the transformation from matAnimation to the skeletal structure's initial transformation (stored in matOriginal when the skeletal structure was loaded). This difference in transformation values is scaled using the floating−point Blend value you provided, and the resulting transformation is then added (not multiplied, as you do with concoction) to the skeletal structure's frame transformation. This ensures that the transformations are properly blended at the appropriate blending values.

After that, the next bone's key frames are scanned, and the loop continues until all bones have been processed.

			// Get the difference in transformations
D3DXMATRIX matDiff = matAnimation − Anim−>m_Bone−>matOriginal;
			// Adjust by blending amount
matDiff *= Blend;
			// Add to transformation matrix
Anim−>m_Bone−>TransformationMatrix += matDiff;
}
		// Go to next animation
Anim = Anim−>m_Next;
}
}

Congratulations, you've just completed your Blend function'! Let's put this puppy to work! Suppose you have a mesh and frame hierarchy already loaded, and you want to load a series of animations from an .X file. Suppose this .X file (called Anims.x) has four animation sets: Stand,Walk, Wave, and Shoot. That's two animations for the legs and two for the arms and torso. Here's a bit of code to load the animation sets:

// pFrame = root frame in frame hierarchy
cBlendedAnimationSet BlendedAnims;
BlendedAnims.Load("Anims.x");

// Map animations frame hierarchy
BlendedAnims.Map(pFrame);

Now that you have an animation collection loaded, you can begin blending the animations before updating and rendering your skinned mesh. Suppose you want to blend the Walk and Shoot animations, both using 100 percent of the transformations. To start, you must reset your frame hierarchy's transformations to their original states. This means you need to copy the D3DXFRAME_EX::matOriginal transformation into the D3DXFRAME_EX::TransformationMatrix transformation. This is very important because it serves as a base to which your animation set transformations are added during the blending operation.

// Use D3DXFRAME_EX::Reset to reset transformations
pFrame−>Reset();

Once the transformations have been reset to their original states, you can blend your animation sets.

// AnimationTime = time of animation, which is the elapsed time since start of the animation

// Blend in the walk animation
BlendedAnims.Blend("Walk", AnimationTime, TRUE, 1.0f);

// Blend in the shoot animation
BlendedAnims.Blend("Shoot", AnimationTime, TRUE, 1.0f);

Once you've blended all the animation sets you're going to use, you need to update your frame hierarchy, which is then used to update your skinned mesh.

// Update the frame hierarchy
pFrame−>UpdateHierarchy();

After you have updated the hierarchy (the transformation matrices have been combined and the results are stored in D3DXFRAME_EX::matCombined), you can update your skinned mesh and render away!


posted on 2008-04-25 19:35 lovedday 阅读(345) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


公告

导航

统计

常用链接

随笔分类(178)

3D游戏编程相关链接

搜索

最新评论