Animation Blending

Started by
170 comments, last by haegarr 15 years, 5 months ago
Don't confuse the blending of key-frames and of animations. That are 2 different things.

See this: The tracks are used to compute a single skeleton attribute value for a given moment of time. It may do so by computing a function, e.g. m_amplitide * sin( tick / m_period ). It may do so by computing a parametrized curve, e.g. a Bezier spline. It may do so by interpolating key-frames. Even key-frame interpolation can be done in several ways; e.g linear interpolation (what blending actually is), cubic interpolation, spline interpolation, ... (You remember I said that key-framing isn't the only way, and that hiding the actual method in Track::contribute allows to use other ways as well?)

However, as you can see, one kind of tracks (your only one ATM) uses linear interpolation, implemented by your blend function. For this interpolation it computes how to weight the left and right key-frames.

Totally apart from that, animation blending should be done. Animation blending needs an own weight, namely one that weights how big the exposure of the current animation is w.r.t. the couple of all blended animations. Remember please that animations in this sense describe the behaviour of extensive parts of the skeleton if not even the entire skeleton, while a track only describes the behaviour of a single skeleton attribute.

Those m_weight is just that: A member variable of CAnimation that defines the exposure of that particular animation. An example: If you have a "walking" instance of CAnimation who's tracks define walking, and a "running" instance of CAnimation who's tracks define running, and you set them up like
walking->setWeight( 0.5 );
running->setWeight( 0.25 );
before rendering, then the animation blending produces a result animation with
0.5 / ( 0.25 + 0.5 ) = 2/3 walking and
0.25 / ( 0.25 + 0.5 ) = 1/3 running
i.e. a fast walking (not yet a slow running ;) ).
Advertisement
I understand everything here except one thing.

currentTime -= m_startTime;


Why you currentTime -= m_startTime;? If time = 0; and m_startTime = 1.0f, time will be negative number. And

   // handling looping if necessary ...   if( currentTime>=m_duration && m_loop ) {      do {         currentTime -= m_duration;         m_startTime += m_duration;      } while( currentTime>=m_duration );   }


why m_startTime -= m_duration

Thanks,
Kasya
Assume you have a local co-ordinate frame of a model. The frame relates the local co-ordinate system to the global co-ordinate system. You express the mesh w.r.t. the local system since this is simpler. Now, do the same with time. You are not interested in scaling the time, rotation makes no sense, translation is the thing of interest. Mathematically, the line
currentTime -= m_startTime;
does a translation along the time axis. Especially we do this with the purpose of co-ordinate frame change. We transform the global time (i.e. currentTime as is overhanded into the routine) so that it comes to rest at where the animation should start (i.e. the global m_startTime or the local 0, resp.).

Why should we do so? As indicated above, it allows us to deal with the tracks being build with local time moments. This makes the life of our artists easier, and it allows furthur a more efficient instanciation of animations (you don't instanciate (yet), but perhaps you see its advantages and hence will do so in the future).

Of course, the resulting local time may be negative. You can define this as "the animation has not yet been started" as I do,
currentTime -= m_startTime;if( currentTime >= 0 ) {   ...}
and hence skip the entire animation for now. You may have another enabling mechanism, so that a negative local time should interpreted another way; then I suggest you to stick with the initial pose, i.e. clip the time
currentTime -= m_startTime;if( currentTime < 0 ) {   currentTime = 0;}...

A similar situation occurs if currentTime exceeds the duration, of course.


Coming to the second point, notice please that I have not suggested m_startTime -= m_duration but m_startTime += m_duration! The entire thing
   // handling looping if necessary ...   if( currentTime>=m_duration && m_loop ) {      do {         currentTime -= m_duration;         m_startTime += m_duration;      } while( currentTime>=m_duration );   }

guarantees that (the local) currentTime is between 0 and m_duration _if_ looping is enabled. You can, of course, drop that line and the animation will still play as expected. Notice what would happen if you play a 5 second looping animation at 52 seconds; you do 10 iterations for every animation computation to correct the time. But, with the little extra line in co-operation with the famous currentTime -= m_startTime, you have to correct the time only every now and then, ragardless how long the animation is lasting. Hence it is a kind of integrated optimization.
Hello,
I wrote my Animation System:

CSkeletalAnimation::CSkeletalAnimation() : m_loop(true) {}void CSkeletalAnimation::AnimateTrack(CAnimationTrack *track, CSkeleton *pFinalSkel, float time) {		unsigned int uiFrame = 0;		CBone * bone = pFinalSkel->FindBone(track->nodeID);		CAnimationTrackType trackType = track->trackType;		CVector3 vTmp;		CQuat qTmp;		while(uiFrame < track->keys.size() && track->keys[uiFrame]->time < time) uiFrame++;		if(uiFrame == 0) {			if(trackType >= TRANSLATEX && trackType <= TRANSLATEZ) {							if(trackType == TRANSLATEX) {					vTmp.x = track->keys[0]->value;				}				else if(trackType == TRANSLATEY) {					vTmp.y = track->keys[0]->value;				}				else if(trackType == TRANSLATEZ) {					vTmp.z = track->keys[0]->value;				}			}			else if(trackType >= ROTATEX && trackType <= ROTATEZ) {				if(trackType == ROTATEX) {					qTmp.SetAxis(track->keys[0]->value, 1, 0, 0);									}				else if(trackType == ROTATEY) {					qTmp.SetAxis(track->keys[0]->value, 0, 1, 0);				}				else if(trackType == ROTATEZ) {					qTmp.SetAxis(track->keys[0]->value, 0, 0, 1);				}								}		}		else if(uiFrame == track->keys.size()) {			if(trackType >= TRANSLATEX && trackType <= TRANSLATEZ) {							if(trackType == TRANSLATEX) {					vTmp.x = track->keys[uiFrame-1]->value;				}				else if(trackType == TRANSLATEY) {					vTmp.y = track->keys[uiFrame-1]->value;				}				else if(trackType == TRANSLATEZ) {					vTmp.z = track->keys[uiFrame-1]->value;				}			} 			else if(trackType >= ROTATEX && trackType <= ROTATEZ) {				if(trackType == ROTATEX) {					qTmp.SetAxis(track->keys[uiFrame-1]->value, 1, 0, 0);									}				else if(trackType == ROTATEY) {					qTmp.SetAxis(track->keys[uiFrame-1]->value, 0, 1, 0);				}				else if(trackType == ROTATEZ) {					qTmp.SetAxis(track->keys[uiFrame-1]->value, 0, 0, 1);				}								}		}		else {			CKeyframe *prevKf = track->keys[uiFrame-1];			CKeyframe *curKf = track->keys[uiFrame];			float delta = curKf->time - prevKf->time;			float blend = (time - prevKf->time) / delta;			CBlend::Blend(vTmp, qTmp, curKf, prevKf, blend, trackType);			}	}	char t[10];void CSkeletalAnimation::Animate(CSkeleton *pFinalSkel, float time) {	float currentTime = time;	currentTime -= m_startTime;	if(currentTime >= m_duration && m_loop) {		do {			currentTime -= m_duration;			m_startTime += m_duration;		} while(currentTime>=m_duration);	}	for(unsigned int i = 0; i < m_Tracks.size(); i++) {			AnimateTrack(m_Tracks, pFinalSkel, currentTime);	}		}


I encountered a problem here. When i create one track there is no Problem. But when there are more than one track.

	skel->AddBone(CVector3(1,1,1), CQuat(), 5, 1 /* Bone ID */);	skel->AddBone(CVector3(), CQuat(), 5, 2);	anim->AddTrack(2/* Track ID */,2/* NodeID */,TRANSLATEX);	anim->AddKeyframe(2,0.1f,0.0f);	anim->AddKeyframe(2,-0.1f,1.0f);	anim->AddKeyframe(2,0.1f,2.0f);	anim->AddTrack(1,1,ROTATEZ);	anim->AddKeyframe(1,0.01f,0.0f);	anim->AddKeyframe(1,-0.01f,1.0f);	anim->AddKeyframe(1,0.01f,2.0f);	anim->SetTime(0.0f, 2.0f);


*MAIN QUESTION*
(1) Translates only one but rotates both of Them. What can i do?


*OPTIONAL QUESTION*
(2) And i have a question?? Do bones need translation Keyframe? If yes, when? When character jumps? If yes, Can't i just use Physics for Jumping? Rotate Legs Using Keyframe and use Physics for translation. Or when person recharges gun which has a bullets inside (throws what is inside). Doesn't they use Physics here. Or they use Keyframe PLUS Physics

Thanks,
Kasya
Quote:walking->setWeight( 0.5 );
running->setWeight( 0.25 );
before rendering, then the animation blending produces a result animation with
0.5 / ( 0.25 + 0.5 ) = 2/3 walking and
0.25 / ( 0.25 + 0.5 ) = 1/3 running


I don't fully understand how this weighting method works. To what are the animation weights related? How would you know you needed 0.5 of walking and 0.25 of running? And why don't all the weights add up to 1.0?
I understand the process of blending 2 animations perfectly, when you use weight and 1-weight to transition from one anim to another. I also understand that this technique get's me into trouble when I want to blend more than two animations together, because then the first animations would influence the end result less and less. But could you please explain in detail how your method works, because I'm a little confused right now.

Second, I have trouble finding a use case for blending more than 2 animations together. Would you use it for example when a character is transitioning from a walking animation to a running animation while at the same time pulling out his gun? It isn't entirely clear to me what blending is for and at what part layering is useful.

Thanks,
Jeroen

[Edited by - godmodder on August 26, 2008 9:32:34 AM]
@ Kasya
Give me some time to work through your code, please.

@ godmodder
Quote:Original post by godmodder
...
I don't fully understand how this weighting method works. To what are the animation weights related? How would you know you needed 0.5 of walking and 0.25 of running? And why don't all the weights add up to 1.0?
I understand the process of blending 2 animations perfectly, when you use weight and 1-weight to transition from one anim to another. I also understand that this technique get's me into trouble when I want to blend more than two animations together, because then the first animations would influence the end result less and less. But could you please explain in detail how your method works, because I'm a little confused right now.

Well, the weights itself do not need to add up to 1, since they get normalized lately, i.e. when being applied. Assume you have 2 animations, the 1st weighted by w1:=0.5 and the 2nd by w2:=0.25, like the example in my above post. Normalizing the weights is done simply by dividing them by their sum
w1' := w1 / ( w1+w2 )
w2' := w2 / ( w1+w2 )
The proof is simple:
w1' + w2' = w1 / ( w1+w2 ) + w2 / ( w1+w2 ) = ( w1+w2 ) / ( w1+w2 ) = 1
Moreover, you can use the 1st and last term of this equation sequence, substitute w2' by w, and get
w2' =: w
w1' = 1 - w2' =: 1 - w
and yield in the well known linear hull weighting. However, the scheme is more efficient w.r.t. the count of weights, since it allows to deal with many weights, too.
wi' := wi / sum1<=j<=i( wj )

So far you need the a-priori knowledge of all weights to work. The method I proposed does not need so. Instead, the method does a kind of de-normalize + re-normalize step. Assume that i animations are already blended. The result is accumulated in p. The current sum of weights is
si := sum1<=j<=i( wj )
Now, the next animation ai+1 is to be blended with all those already accumulated so far. That means:
pi+1 := ( 1-w ) * pi + w * ai+1
As described earlier, the blending weight w is a scaled version of the current weight:
w == wi+1' = wi+1 / sum1<=j<=i+1( wj )
Setting this into 1-w yield in
1 - w = 1 - wi+1 / sum1<=j<=i+1( wj )
== ( sum1<=j<=i+1( wj ) - wi+1 ) / sum1<=j<=i+1( wj )
== sum1<=j<=i( wj ) / sum1<=j<=i+1( wj )
Now, each term of the accumulated pi is normalized with sum1<=j<=i( wj )
and that is the same as the nominator of the transformed 1-w term above! Let us inspect
( 1-w ) * pi
now as a 2 step process, namely the multiplication with the nominator and a subsequent division by the denominator. Then
pi * nominator( 1-w )
does a de-normalization by the old (1<=j<=i) sum (please inset the terms yourself, I'm too lazy), and the following
pi / denominator( 1-w )
does a re-normalization with the new (1<=j<=i+1) sum.

The other half of the blending
w * ai+1
is weighted with its own weight wi+1 but normnalized by the new (1<=j<=i+1) sum. So all summands of p are now weighted by their own weight but normalized by the new sum.
q.e.d. :)

Quote:Original post by godmodder
Second, I have trouble finding a use case for blending more than 2 animations together. Would you use it for example when a character is transitioning from a walking animation to a running animation while at the same time pulling out his gun? It isn't entirely clear to me what blending is for and at what part layering is useful.

Never underestimate the imaginativeness of artists. What is if you you want to go uphill/downhill? What is if you want to turn left/right? What is if you want to walk a bit, run a bit, turn left meanwhile, and that upwards a hill? All this has effects on the walk cycle.
Thank you very very much. :)
Kasya
Ups, I saw that I haven't answered the introductionary questions in my previous post. So here we go.
Quote:Original post by godmodder
... To what are the animation weights related? How would you know you needed 0.5 of walking and 0.25 of running?...

There is no pre-defined pescription for the 0.5 and 0.25 at all. I've chosen this pair more or less arbitrarily but with the objective that it doesn't sums up to 1. The absolute values are controlled by e.g. the AI system, or the input system in the case of the avatar.

Some animations are known to work "mutual exclusive". E.g. sneaking vs. walking vs. running. The system should pick only 2 of them, preferably 2 successive ones, and blend them with a defined sum. Say, when "running" is at 100% of the sum, then "walking" is at 0%.

Some animations work another way. E.g. moving upwards a hill may be useable for all 3 basic locomations, sneaking, walking, and running. So it can be mixed into the forward locomotion, but should never supress the locomation totally. A similar case is turning.

I don't know if you remember, but my animation system isn't totally implemented by me yet. Maybe there are use cases that cannot be solved with the stuff so far. RobTheBloke has spoken of 20 or so blending modes in morpheme, where we have only 3 ATM. Although one can assume that 20 is influenced a bit by the marketing division, there are nevertheless presumbly good reasons for most of them. Unfortunately we have no insight how the modes are defined :(
Quote:Original post by Kasya
I encountered a problem here. When i create one track there is no Problem. But when there are more than one track.
*MAIN QUESTION*
(1) Translates only one but rotates both of Them. What can i do?

Its difficult to answer without seeing what happens. So far we've spoken of aimations and tracks and blending, but we never have spoken of the hierarchy of bones. Have you implemented your skeleton to represent a FK hierarchy? Do you set the animated skeleton attributes as locally or as globally related parameters? Are you aware that rotations of bones totally apart from the origin can be misinterpreted as a combination of rotation/translation?

From the posted set-up of the skeleton, it is absolutely not clear whether or not bone 1 and bone 2 are related as parent and child. Please clarify that. If they are not related, then bone 1 should rotate and bone 2 should translate. If they are related (probably with bone 2 being a child of bone 1), then the rotation track of bone 1 should rotate bone 1 and also bone 2 due to FK. And bone 2 should additionally be translated due to its own track. Is this what you have expected, and/or is this what you have observed?

Quote:Original post by Kasya
*OPTIONAL QUESTION*
(2) And i have a question?? Do bones need translation Keyframe? If yes, when? When character jumps? If yes, Can't i just use Physics for Jumping? Rotate Legs Using Keyframe and use Physics for translation. Or when person recharges gun which has a bullets inside (throws what is inside). Doesn't they use Physics here. Or they use Keyframe PLUS Physics

Well, this question is one of those that cannot be answered with a simple yes or no. If the weight of 50kg burdens vertically on a knee, one can assume that a bit of compression can be observed. But that doesn't mean that one has to model this effect, too (not to say I would dissuate).

Another thing may be to use small translations for a more suitable shaping, e.g. to emphasize some muscles under the skin. However, my artist (who is not a professional) says that this is unlikely since other ways are by far more typical. On the other hand, use cases of translation appear when simulating mechanical systems or modeling the well known exaggerations in toon animations.
Hello,

Thats my Bone System:

class CBone {	public:		CVector3 vPos;		CQuat qRotate;		float length;		unsigned int id;};class CSkeleton {	public:		std::vector<CBone *> bones;		CBone * root;		CSkeleton();		~CSkeleton();			unsigned int NumBones();				CBone *FindBone(unsigned int id);		void AddBone(CVector3 vPos, CQuat qRotate, float length, unsigned int id);		void Draw();				};


Draw Skeleton:

void CSkeleton::Draw() {	CMatrix matRotate, matTrans, matFinal;	glPushMatrix();	for(unsigned int i = 0; i < bones.size(); i++) {		matRotate.LoadIdentity();		matTrans.LoadIdentity();		matFinal.LoadIdentity();		bones->qRotate.ToMatrix(matRotate);		CMath::CMatTranslate(matTrans, bones->vPos);		matFinal = matRotate * matTrans;				glMatMultiply(matFinal); //#define glMatMultiply(a) glMultMatrixf(a.row)		glBegin(GL_LINES);		glColor3f(1,1,1);		glVertex3f(0, 0, 0);		glColor3f(1,1,1);		glVertex3f(bones->length, 0, 0);		glEnd();		CMath::CMatTranslate(matTrans, bones->vPos);		glMatMultiply(matTrans);				}	glPopMatrix();}


Thanks,
Kasya

This topic is closed to new replies.

Advertisement