Followers 0

# Animation Blending

## 171 posts in this topic

Hello,
Thats my last Updated Code:

Animation Componenets:

class CKeyframe {	public:		float time;		float value;};enum CAnimationTrackType {	TRANSLATEX = 0,	TRANSLATEY,	TRANSLATEZ,	ROTATEX,	ROTATEY,	ROTATEZ};class CAnimationTrack {	public:		unsigned int trackID;		unsigned int nodeID;		CAnimationTrackType trackType;		std::vector<CKeyframe *> keys;};

Animation Class:
class CAnimation {	protected:		std::vector<CAnimationTrack *> m_Tracks;		float startTime, endTime;		char name[32];			public:		CAnimation();		~CAnimation();		CAnimationTrack * FindTrackByID(unsigned int trackID);		CKeyframe * FindKeyframeByTime(unsigned int trackID, float keyframe_time);		void AddTrack(unsigned int trackID, unsigned int nodeID, CAnimationTrackType trackType);		void AddKeyframe(unsigned int trackID, float value, float time);		void SetTime(float start_time, float end_time);		CAnimationTrack * operator[](unsigned int index);		CAnimationTrack *AnimationTrack(unsigned int index);		unsigned int NumAnimationTracks();	};

CBlend.cpp

void CBlend::Blend(CVector3 &vPos, CQuat &qRotate, CKeyframe *curKf, CKeyframe *prevKf, float blend_factor, CAnimationTrackType trackType) {	CVector3 vTmpPrev, vTmpCur;	CQuat qTmpPrev, qTmpCur;	if(trackType == TRANSLATEX) {		vTmpPrev.x = prevKf->value;		vTmpCur.x = curKf->value;			}	if(trackType == TRANSLATEY) {		vTmpPrev.y = prevKf->value;		vTmpCur.y = curKf->value;		}	if(trackType == TRANSLATEZ) {		vTmpPrev.z = prevKf->value;		vTmpCur.z = curKf->value;	}	if(trackType == ROTATEZ) {		qTmpPrev.SetAxis(prevKf->value, 1, 0, 0);		qTmpCur.SetAxis(curKf->value, 1, 0, 0);	}		if(trackType == ROTATEZ) {		qTmpPrev.SetAxis(prevKf->value, 0, 1, 0);		qTmpCur.SetAxis(curKf->value, 0, 1, 0);	}	if(trackType == ROTATEZ) {		qTmpPrev.SetAxis(prevKf->value, 0, 0, 1);		qTmpCur.SetAxis(curKf->value, 0, 0, 1);	}	vPos = vTmpCur * (1.0f - blend_factor) + vTmpPrev * blend_factor;	qRotate.Slerp(qTmpCur, qTmpPrev, blend_factor);}void CBlend::Blend(CAnimation *pFinalAnim, CAnimation *pCurAnimation, CAnimation *pNextAnimation, float blend_factor) {	CVector3 vTmp;	CQuat qTmp;	for(unsigned int i = 0; i < pCurAnimation->NumAnimationTracks(); i++) {		for(unsigned int n = 0; n < pNextAnimation->NumAnimationTracks(); n++) {						if(pCurAnimation->AnimationTrack(i)->nodeID == pNextAnimation->AnimationTrack(n)->nodeID && pCurAnimation->AnimationTrack(i)->trackType == pNextAnimation->AnimationTrack(n)->trackType) {				for(unsigned int j = 0; j < pCurAnimation->AnimationTrack(i)->keys.size(); j++) {					for(unsigned int k = 0; k < pNextAnimation->AnimationTrack(n)->keys.size(); k++) {						Blend(vTmp, qTmp, pCurAnimation->AnimationTrack(i)->keys[j], pNextAnimation->AnimationTrack(n)->keys[j], blend_factor, pNextAnimation->AnimationTrack(n)->trackType);						for(unsigned int v = 0; v < pFinalAnim->NumAnimationTracks(); v++) {							if(pFinalAnim->AnimationTrack(v)->nodeID == pCurAnimation->AnimationTrack(i)->nodeID && pFinalAnim->AnimationTrack(i)->trackType == pCurAnimation->AnimationTrack(n)->trackType) {									//What to write here??								}							}						}					}				}			}		}}

Animate Skeleton:

void CSkeletalAnimation::Animate(CSkeleton *pFinalSkel) {	float time = g_Timer.GetSeconds();        //Haven't written loop yet. :)	for(unsigned int i = 0; i < m_Tracks.size(); i++) {		unsigned int uiFrame = 0;		CBone * bone = pFinalSkel->FindBone(m_Tracks[i]->nodeID);		CAnimationTrackType trackType = m_Tracks[i]->trackType;		CVector3 vTmp;		CQuat qTmp;		while(uiFrame < m_Tracks[i]->keys.size() && m_Tracks[i]->keys[uiFrame]->time < time) uiFrame++;		if(uiFrame == 0) {			if(trackType == TRANSLATEX) {				vTmp.x = m_Tracks[i]->keys[0]->value;			}			if(trackType == TRANSLATEY) {				vTmp.y = m_Tracks[i]->keys[0]->value;			}			if(trackType == TRANSLATEZ) {				vTmp.z = m_Tracks[i]->keys[0]->value;			}			if(trackType == ROTATEX) {				qTmp.SetAxis(m_Tracks[i]->keys[0]->value, 1, 0, 0);							}			if(trackType == ROTATEY) {				qTmp.SetAxis(m_Tracks[i]->keys[0]->value, 0, 1, 0);			}			if(trackType == ROTATEZ) {				qTmp.SetAxis(m_Tracks[i]->keys[0]->value, 0, 0, 1);			}							}		if(uiFrame == m_Tracks[i]->keys.size()) {			if(trackType == TRANSLATEX) {				vTmp.x = m_Tracks[i]->keys[uiFrame-1]->value;			}			if(trackType == TRANSLATEY) {				vTmp.y = m_Tracks[i]->keys[uiFrame-1]->value;			}			if(trackType == TRANSLATEZ) {				vTmp.z = m_Tracks[i]->keys[uiFrame-1]->value;			}			if(trackType == ROTATEX) {				qTmp.SetAxis(m_Tracks[i]->keys[uiFrame-1]->value, 1, 0, 0);							}			if(trackType == ROTATEY) {				qTmp.SetAxis(m_Tracks[i]->keys[uiFrame-1]->value, 0, 1, 0);			}			if(trackType == ROTATEZ) {				qTmp.SetAxis(m_Tracks[i]->keys[uiFrame-1]->value, 0, 0, 1);			}			}		else {			CKeyframe *prevKf = m_Tracks[i]->keys[uiFrame-1];			CKeyframe *curKf = m_Tracks[i]->keys[uiFrame];			float delta = curKf->time - prevKf->time;			float blend = (time - prevKf->time) / delta;			CBlend::Blend(vTmp, qTmp, curKf, prevKf, blend, trackType);			}		bone->qRotate = bone->qRotate * qTmp;		bone->vPos = bone->vPos * vTmp;	}}

Is that code right?? And please answer my question inside Blend::Blend function.

Thanks,
Kasya

P.S. Timer is temporary i'll change it anyway. :)
0

##### Share on other sites
IMHO there are several problems within the code.

(1) You use 3 times ROTATIONZ but no ROTATIONX and ROTATIONY in the CBlend::Blend for 2 key-frames.

(2) The implementation of the same CBlend::Blend as above is very inefficient. My main grumbling is that the trackType is exactly 1 of the possible values, but you ever process all of them. Consider at least to choose a structure like
// either a rotation ...if( trackType>=ROTATEX ) {   CQuat qTmpPrev, qTmpCur;   if( trackType==ROTATIONX ) {      qTmpPrev.SetAxis(prevKf->value, 1, 0, 0);       qTmpCur.SetAxis(curKf->value, 1, 0, 0);   }   else if( trackType==ROTATIONY ) {      /// insert appropriate code here   }   else {      /// insert appropriate code here   }   qRotate.Slerp(qTmpCur, qTmpPrev, blend_factor);}// ... or else a translation ...else {   /// insert appropriate code here, similarly to the part above}

As you can see, this snippet tries to avoid senseless computations. You can use a switch statement for the inner decisions, of course. (Yes, you can say that this is a kind of pre-optimization, if you like. But choosing another implementation here has an impact on the invoking code, so changing it later can IMO introduce more problems than expected so far.)

(3) I would not choose the way of CBlend::Blend of 2 animations as you've done. Your way is appropriate for blending 2 animations, but not necessarily for blending more than 2 animations. Instead of blending animations pairwise, I would use the skeleton instance as an accumulator for the blending. I.e. preset the skeleton instance when entering the blending group, process each animation of the group without knowledge of the other animations, and post-process the skeleton instance when exiting the group.

I'm not sure what kind of animation system you try to implement. So I cannot really evaluate the code at the higher logical levels (this is strictly speaking already relevant for point (3) above). You may consider to tell us exactly what your goals are when we should discuss them.
0

##### Share on other sites
Hello,

it there a problem inside
CSkeletalAnimation::Animate(CSkeleton *pFinalSkel); except Time

And Thats my new Blending:

void CBlend::Blend(CVector3 &vPos, CQuat &qRotate, CKeyframe *curKf, CKeyframe *prevKf, float blend_factor, CAnimationTrackType trackType) {	if(trackType >= TRANSLATEX && trackType <= TRANSLATEZ) {			CVector3 vTmpPrev, vTmpCur;		if(trackType == TRANSLATEX) {			vTmpPrev.x = prevKf->value;			vTmpCur.x = curKf->value;					}		else if(trackType == TRANSLATEY) {			vTmpPrev.y = prevKf->value;			vTmpCur.y = curKf->value;				}		else if(trackType == TRANSLATEZ) {			vTmpPrev.z = prevKf->value;			vTmpCur.z = curKf->value;		}		vPos = vTmpCur * (1.0f - blend_factor) + vTmpPrev * blend_factor;	}	else if(trackType >= ROTATEX && trackType <= ROTATEZ) {		CQuat qTmpPrev, qTmpCur;		if(trackType == ROTATEX) {			qTmpPrev.SetAxis(prevKf->value, 1, 0, 0);			qTmpCur.SetAxis(curKf->value, 1, 0, 0);		}				else if(trackType == ROTATEY) {			qTmpPrev.SetAxis(prevKf->value, 0, 1, 0);			qTmpCur.SetAxis(curKf->value, 0, 1, 0);		}		else if(trackType == ROTATEZ) {			qTmpPrev.SetAxis(prevKf->value, 0, 0, 1);			qTmpCur.SetAxis(curKf->value, 0, 0, 1);		}		qRotate.Nlerp(qTmpCur, qTmpPrev, blend_factor);	}}void CBlend::Blend(CSkeleton *pFinalSkel, CSkeleton *pLastSkel, CSkeleton *pNextSkel, float blend_factor) {	for(unsigned int i = 0; i < pLastSkel->NumBones(); i++) {		pFinalSkel->bones[i]->vPos = pLastSkel->bones[i]->vPos * ( 1.0 - blend_factor) + pNextSkel->bones[i]->vPos * blend_factor;		pFinalSkel->bones[i]->qRotate.Nlerp(pLastSkel->bones[i]->qRotate, pNextSkel->bones[i]->qRotate, blend_factor);	}}

Thanks,
Kasya
0

##### Share on other sites
Okay; although the implementation of CBlend::Blend for 2 key-frames is still away from being optimal, it is good enough until the animation system works as expected. I here only hint at the potential to optimize it and urge you to come back to this topic at appropriate time.

In CSkeletalAnimation::Animate there is IMO an "else" being missed. The structure should probably be
if(uiFrame == 0) {   ...}else if(uiFrame == m_Tracks[i]->keys.size()) { // <-- notice the else at the beginning   ...else {   ...}

Next, vTmp is a QVector3, and I assume vPos to be one, too. You are multiplying the both in CSkeletalAnimation::Animate
bone->vPos = bone->vPos * vTmp;
but that isn't the correct operation. If you don't want to do animation blending, then addition would be the correct operation. If, on the other hand, you want to do animation blending, then blending would be the correct operation. In the latter case also bone->qRotate must be blended, of course.

I can't tell you about a working structure if you don't tell us what you want to do. So please:
(a) Do you want to integrate animation blending?
(b) Are the tracks of a particular animation unique w.r.t. the affected bone attribute, or else is blending already necessary at this level? (I would suggest the former.)
(c) What about layering? What kind of layer blending do you prefer, if any?
0

##### Share on other sites
Hello,

(a) I want ot use animation blending
(b) The Tracks are animating Bones
(c) I have no Layering no animation groups. I want to do that too. but after blending.

i changed lots of things here in CSkeleton::Animate(CSkeleton *pFinalSkel); functions.

void CSkeletalAnimation::Animate(CSkeleton *pFinalSkel) {	float time = g_Timer.GetSeconds() * 0.01f;	static float lastTime = startTime;	time += lastTime;	lastTime = time;	sprintf(t, "%f", time);	SetWindowText(GetHWND(), t);	if(time >= endTime) {		if(loop) {			lastTime = startTime;			time = startTime;		}	}	for(unsigned int i = 0; i < m_Tracks.size()-1; i++) {		unsigned int uiFrame = 0;		CBone * bone = pFinalSkel->FindBone(m_Tracks[i]->nodeID);		CAnimationTrackType trackType = m_Tracks[i]->trackType;		CVector3 vTmp;		CQuat qTmp;		while(uiFrame < m_Tracks[i]->keys.size() && m_Tracks[i]->keys[uiFrame]->time < time) uiFrame++;		if(uiFrame == 0) {			if(trackType >= TRANSLATEX && trackType <= TRANSLATEY) {							if(trackType == TRANSLATEX) {					vTmp.x = m_Tracks[i]->keys[0]->value;				}				else if(trackType == TRANSLATEY) {					vTmp.y = m_Tracks[i]->keys[0]->value;				}				else if(trackType == TRANSLATEZ) {					vTmp.z = m_Tracks[i]->keys[0]->value;				}			} 			else if(trackType >= ROTATEX && trackType <= ROTATEZ) {				if(trackType == ROTATEX) {					qTmp.SetAxis(m_Tracks[i]->keys[0]->value, 1, 0, 0);									}				else if(trackType == ROTATEY) {					qTmp.SetAxis(m_Tracks[i]->keys[0]->value, 0, 1, 0);				}				else if(trackType == ROTATEZ) {					qTmp.SetAxis(m_Tracks[i]->keys[0]->value, 0, 0, 1);				}								}		}		else if(uiFrame == m_Tracks[i]->keys.size()) {			if(trackType >= TRANSLATEX && trackType <= TRANSLATEY) {							if(trackType == TRANSLATEX) {					vTmp.x = m_Tracks[i]->keys[uiFrame-1]->value;				}				else if(trackType == TRANSLATEY) {					vTmp.y = m_Tracks[i]->keys[uiFrame-1]->value;				}				else if(trackType == TRANSLATEZ) {					vTmp.z = m_Tracks[i]->keys[uiFrame-1]->value;				}			} 			else if(trackType >= ROTATEX && trackType <= ROTATEZ) {				if(trackType == ROTATEX) {					qTmp.SetAxis(m_Tracks[i]->keys[uiFrame-1]->value, 1, 0, 0);									}				else if(trackType == ROTATEY) {					qTmp.SetAxis(m_Tracks[i]->keys[uiFrame-1]->value, 0, 1, 0);				}				else if(trackType == ROTATEZ) {					qTmp.SetAxis(m_Tracks[i]->keys[uiFrame-1]->value, 0, 0, 1);				}								}		}		else {			CKeyframe *prevKf = m_Tracks[i]->keys[uiFrame-1];			CKeyframe *curKf = m_Tracks[i]->keys[uiFrame];			float delta = curKf->time - prevKf->time;			float blend = (time - prevKf->time) / delta;			CBlend::Blend(vTmp, qTmp, curKf, prevKf, blend, trackType);			}		bone->qRotate = bone->qRotate * qTmp;		bone->vPos += vTmp;	}}

I only have time problem. But im gettings rid of it.

Thanks,
Kasya
0

##### Share on other sites
Quote:
 Original post by Kasya(a) I want ot use animation blending(b) The Tracks are animating Bones(c) I have no Layering no animation groups. I want to do that too. but after blending.

Okay. Answer (b) isn't complete w.r.t. my question, but I assume trackIDs being unique per animation until you constradict explicitely. I furthur assume that more than 2 animations should be able to be blended.

Coming to the timing. I suggest you the following:

How is g_Timer.GetSeconds() advanced? It is a timer provides by the OS? As mentioned earlier, the time used should be freezed for the current video frame. Due to this behaviour, I would expect the current time being overhanded like in
void CSkeletalAnimation::Animate(float currentTime,CSkeleton *pFinalSkel) ...
instead of being fetched inside that routine.

Next, what is lastTime being good for? Especially declaring it as static is probably a bad idea. I suggest something like this:
CSkeletalAnimation:: CSkeletalAnimation( float startTime, bool loop ):  m_startTime( startTime ),   m_loop( loop ) ...void CSkeletalAnimation::Animate( CSkeleton *pFinalSkel, float currentTime ) {   // relating current time to animation start   currentTime -= m_startTime;   // handling looping if necessary ...   if( currentTime>=m_duration && m_loop ) {      do {         currentTime -= m_duration;         m_startTime += m_duration;      } while( currentTime>=m_duration );   }   // pre-processing the skeleton   //    (nothing to do yet)   // iterating tracks   for( unsigned int i=0; i<m_Tracks.size()-1; ++i ) {      m_Tracks[i]->contribute( pFinalSkel, currentTime );   }   // post-processing the skeleton   //    (nothing to do yet)}

What do you think about that? Notice that the animation doesn't care here that tracks are build of key-frames.
0

##### Share on other sites
Hello,

What you mean

Quote:
 Notice that the animation doesn't care here that tracks are build of key-frames.

?

Does it Care on mine? Where?

Doesn't m_Tracks[i]->Contribute(pFinalSkel, currentTime);'s implementation like mine but inside function.

And for blending more than one animation, i need to do like that:

CSkeleton *pFinalSkel;CSkeleton tempSkel1, tempSkel2, tempSkel3;CSkeleton tempFinal;m_Animation[0].Animate(&tempSkel1, currentTime);m_Animation[1].Animate(&tempSkel2, currentTime);m_Animation[2].Animate(&tempSkel3, currentTime);CBlend::Blend(&tempFinal, &tempSkel1, &tempSkel2, blend_factor);CBlend::Blend(pFinalSkel, &tempFinal, &tempSkel3, blend_factor);

Thanks,
Kasya

0

##### Share on other sites
Quote:
Original post by Kasya
What you mean

Quote:
 Notice that the animation doesn't care here that tracks are build of key-frames.

?

Does it Care on mine? Where?

Your CSkeletalAnimation::Animate method executes the entire logic of tracks. Hence it also executes the look-up for the surrounding key-frames of the track. So yes, your solution requires the tracks being key-frame tracks. That is not an error; it is not even not a real problem if you stick with key-frame tracks only. But it is away from a clean OOP solution, IMHO. And you'll get into trouble if you decide to allow other kinds of tracks. At least, externalizing that functionality slims CSkeletalAnimation::Animate.

Quote:
 Original post by KasyaDoesn't m_Tracks[i]->Contribute(pFinalSkel, currentTime);'s implementation like mine but inside function.

Mostly.

Quote:
 Original post by KasyaAnd for blending more than one animation, i need to do like that:...

Looking at your code snippets shows me a hardcoded handling of an anknown amount of animations. Well, I assume you don't really meant that but gave that as an equivalent example, did you? However, the real problems are others.

First, look at the total weightings. What you suggest is something like
f1 := p2 * w12 + p1 * ( 1 - w12 )
f2 := p3 * w23 + f1 * ( 1 - w23 )
== p3 * w23 + ( p2 * w12 + p1 * ( 1 - w12 ) ) * ( 1 - w23 )
== p3 * w23 + p2 * w12 * ( 1 - w23 ) + p1 * ( 1 - w12 ) * ( 1 - w23 )
Do you notice the double weighting of p2 and p1? If you don't take countermeasures then animations incorporated at the beginning are more and more supressed due to their multiple weightings.

The 2nd problem is that, if you don't have complete animations w.r.t. the count of tracks (i.e. not all skeleton attributes are animated), then you have the need to lately set those attributes to the state of the bind pose. So you have the need to detect at the end of processing all animations, whether or not an attribute has been touched.

Both problems can be handled by using a sum of weights for each attribute. It has an additional advantage: It allows weights to be independent; it is sufficient if each weight is greater than 0 (an animation with weight 0 plays no role by definition, and negative weights are disallowed anyway).

An animation has a track that influences f. Since this is the first animation doing so, the value returned by the track is used as is, but the weight is remembered:
s = w1
f = p1
If no other animation has a track bound to the same attribute, then nothing more happens. If, on the other hand, another animation has a track bound to that attribute, then a blending happens but with an adapted weight:
s += w2
f = blend( f, p2, w2 / s )
So what happens? The adapted weight is
w2 / s == w2 / ( w1 + w2 )
so that the blending is actually computed as
p1 * ( 1 - w2 / ( w1 + w2 ) ) + p2 * w2 / ( w1 + w2 )
== p1 * w1 / ( w1 + w2 ) + p2 * w2 / ( w1 + w2 )
Assuming a 3rd animation track comes into play, then again
s += w3
f = blend( f, p3, w3 / s )
resulting in
p1 * w1 / ( w1 + w2 + w3 ) + p2 * w2 / ( w1 + w2 + w3 ) + p3 * w3 / ( w1 + w2 + w3 )

You see that the weights gets normalized automatically, and each track has an influence with just the weight defined by the animation rather than a mix of weights of various animations!

Moreover, when the animations are all processed, you can investigate the sum of weights and determine whether _any_ track ahd influence; if not, then set the value of the attribute to those of the bind pose.
0

##### Share on other sites

Notice please how the above scheme of blending animations fits perfectly with the track->contribute thingy. It is so because the said scheme blends animations one-by-one, so it needs only to know a single animation at a time. In other words, the blending can be done by the animation (or its track, in this case) itself. That is an advantage from the implementation's point of view.

To do so, you need to transport the animations weight to the track, of course, like so
   // iterating tracks   for( unsigned int i=0; i<m_Tracks.size()-1; ++i ) {      m_Tracks[i]->contribute( pFinalSkel, currentTime, m_weight );   }

And you can see why the comments "pre-processing" and "post-processing" in one of my previous posts are senseful...
0

##### Share on other sites
Hello,

You said

Quote:
 the blending can be done by the animation (or its track, in this case) itself

and used

m_Tracks[i]->contribute( pFinalSkel, currentTime, m_weight );

That means i need to calculate weight. But when its inside keyframe like

			CKeyframe *prevKf = m_Tracks[i]->keys[uiFrame-1];			CKeyframe *curKf = m_Tracks[i]->keys[uiFrame];			float delta = curKf->time - prevKf->time;			float blend = (time - prevKf->time) / delta;			CBlend::Blend(vTmp, qTmp, curKf, prevKf, blend, trackType);

i calculated weight in float blend;.

How can i calculate it before m_Tracks[i]->contribute loop

Thanks,
Kasya
0

##### Share on other sites
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 ;) ).
0

##### Share on other sites
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
0

##### Share on other sites
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.
0

##### Share on other sites
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[i], 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
0

##### Share on other sites
Quote:
 walking->setWeight( 0.5 );running->setWeight( 0.25 );before rendering, then the animation blending produces a result animation with0.5 / ( 0.25 + 0.5 ) = 2/3 walking and0.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]
0

##### Share on other sites
@ Kasya

@ 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 godmodderSecond, 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.
0

##### Share on other sites
Thank you very very much. :)
Kasya
0

##### Share on other sites
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 :(
0

##### Share on other sites
Quote:
 Original post by KasyaI 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.
0

##### Share on other sites
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[i]->qRotate.ToMatrix(matRotate);		CMath::CMatTranslate(matTrans, bones[i]->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[i]->length, 0, 0);		glEnd();		CMath::CMatTranslate(matTrans, bones[i]->vPos);		glMatMultiply(matTrans);				}	glPopMatrix();}

Thanks,
Kasya
0

##### Share on other sites
(1) transformation compositing

The usual way of compositing translation and rotation in OpenGL is
T * R
what presumbly differs from your matFinal = matRotate * matTrans; so try to revers that order. I assume that this step solves your current problem.

(2) bind pose

Perhaps not necessary ATM, but your code lacks at least some post-processing. I.e. you don't set untouched bone attributes to the bind pose. Not even that, but you have no definition of a bind pose yet at all.

(3) FK

You have an implicit bone chain model. You should resolve this into an explicit model one day, e.g. by overhanding an additional parameter to CSkeleton::AddBone that denotes the ID of the parental bone (using 0 for root bones). You have to adapt the draw method then, of course.

If you are interested in how to implement frames capable of FK and IK then look e.g. at this thread.

(4) Tracks

You should consider to move functionality of tracks from CAnimation to CTrack.
0

##### Share on other sites
Hello,

(1) This is my application:

SkeletalAnimation.zip

There are two bones Rotated and one is translated.

*NOTE* I changed matFinal = matTrans * matRotate

Code Used in Application:

	skel->AddBone(CVector3(1,1,1), CQuat(), 5, 1);	skel->AddBone(CVector3(), CQuat(), 5, 2);	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->AddTrack(2,2,TRANSLATEX);	anim->AddKeyframe(2,0.1f,0.0f);	anim->AddKeyframe(2,-0.1f,1.0f);	anim->AddKeyframe(2,0.1f,2.0f);	anim->SetTime(0.0f, 2.0f);

void CSkeleton::AddBone(CVector3 vPos, CQuat qRotate, float length, unsigned int id) {	if(FindBone(id) == NULL) {		CBone *bone = new CBone;		bone->id = id;	bone->vPos = vPos;	bone->qRotate = qRotate;	bone->length = length;	bones.push_back(bone);	} else {		MessageBox(NULL, "", "", MB_OK);		return;	}}

(3) You said:

Quote:
 You should consider to move functionality of tracks from CAnimation to CTrack.

Why?

Thanks,
Kasya

0

##### Share on other sites
Hello,

(1) This is my application:

SkeletalAnimation.zip

There are two bones Rotated and one is translated.

*NOTE* I changed matFinal = matTrans * matRotate

Code Used in Application:

	skel->AddBone(CVector3(1,1,1), CQuat(), 5, 1);	skel->AddBone(CVector3(), CQuat(), 5, 2);	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->AddTrack(2,2,TRANSLATEX);	anim->AddKeyframe(2,0.1f,0.0f);	anim->AddKeyframe(2,-0.1f,1.0f);	anim->AddKeyframe(2,0.1f,2.0f);	anim->SetTime(0.0f, 2.0f);

void CSkeleton::AddBone(CVector3 vPos, CQuat qRotate, float length, unsigned int id) {	if(FindBone(id) == NULL) {		CBone *bone = new CBone;		bone->id = id; //For finding Bone (Not Parent Bone)	bone->vPos = vPos;	bone->qRotate = qRotate;	bone->length = length;	bones.push_back(bone);	} else {		MessageBox(NULL, "", "", MB_OK);		return;	}}

(3) You said:

Quote:
 You should consider to move functionality of tracks from CAnimation to CTrack.

Why?

(4) And do i need Parent Bone why? Please show me a code for my bone system where i can use Parent?

Thanks,
Kasya

0

##### Share on other sites
@haegarr

Thanks for the explanation and proof of the blending technique. I think I fully understand it now.

@haegarr/robthebloke
I have a little side question now and it's about the AnimationTrack structure from some previous posts. I see there that every component (i.e. TRANSLATEX, TRANSLATEY,...) is treated separately. I find this strange for a couple of reasons:

- Most parameters that need to be animated are some sort of tuples (vectors, quaternions,...), so treating their components separately introduces alot of redundant/ugly code like nested switch statements, etc...

- Most rotations in character animation aren't around 1 axis. So if you seperate components to save memory, consider that you have to store an additional Time float variable in every keyframe. So you'll end up saving some memory when rotations are only around a fixed axis (reasonably rare), but there are also cases where you use more memory than with a time/tuple approach.

- Do you allow the separate components to update at different time intervals? If so, then you must execute the interpolation functions for every component. Doesn't this introduce more runtime overhead, because some values in these functions can be precomputed for a tuple?

What do you think of the following alternative?

template<typename T>class KeyFrame{   float Time;   T Value;};class AnimTrack{   // functions common for every sort of AnimTrack};class PosTrack : public AnimTrack{   private:      std::vector< Keyframe<Vector3> > m_Keys;};class Animation{   private:      std::vector<AnimTrack*> m_pTracks;   // this could be a boost::ptr_vec};

This way enables a much more concise approach IMO and even saves us the additional cost of storing an enum variable in every track. Now, there may be some runtime overhead in resolving the polymorf AnimTrack pointers, but I don't know if it's significant.

Tell me what you think of it,
Jeroen
0

##### Share on other sites
@ Kasya: Unfortunately I'm a Mac man. Running an exe is a problem for me.

Quote:
 Original post by Kasya(3) You said:You should consider to move functionality of tracks from CAnimation to CTrack.Why?

Its the way OOP goes: Group data and functionality into a class, making it an entirety, and encapsulate it for the rest of the world. Besides other advantages, it allows to have different implementations. E.g. godmodder has requested a kind of tracks for positions and another for orientations, where you use 6 tracks of the kind for float scalars. And I mentioned in an earlier post, that in principle tracks of floats can also be used to animation blend weights itself. Although the following quote relates to godmodder, I demonstrate the usefulness in the answer. So please keep on reading.

Quote:
 Original post by godmodderI have a little side question now and it's about the AnimationTrack structure from some previous posts. I see there that every component (i.e. TRANSLATEX, TRANSLATEY,...) is treated separately. I find this strange for a couple of reasons:...

I see your provisos. Let me say that professional DCC packages absolutely allow to animate each "channel" even of vector valued properties. RobTheBloke's posts are influenced by morpheme what is a professional tool. In the given case we speak of real-time in game animations of skeletons, and you are right in that animating Euler angles may (!) not be necessary.

However, here comes one of the advantages of OOP into play. With a Track (CTrack for Kasya) class we neither restrict the animation to floats, Vector2, Vector3, or whatever. As well as we don't restrict the Track to be defined by key-frames (as said, other examples are time based functions or other interpolation schemes).

I already outlined that in my animation system an animation instance is created from a prototype, and that a binding happens to connect tracks with skeleton attributes. I used the term "attribute" just because it may be a position, an orientation (quaternion in fact), or just a position.x co-ordinate. Whatever my artist requests, he get it.

Of course, such a generic system produces much additional implementation work. So I don't urge you to do the same (namely binding), but I really propose to encapsulate tracks in their own class. I think godmodder already came to the same conclusion.

Quote:
 Original post by godmodderDo you allow the separate components to update at different time intervals? If so, then you must execute the interpolation functions for every component. Doesn't this introduce more runtime overhead, because some values in these functions can be precomputed for a tuple?

Yes, I definitely allow this. The concept of not restricting the Track is the reason. From what I see ATM, the overhead is an addition and a division. Not so much. And due to allowing vector valued interpolation, too, the overhead occurs not so often.

For local optimization, the specialized Track classes can of course do anything that is senseful. E.g. in my KeyframeTrack classes, the last recently used key-frames are remembered, so that searching for the current interval always starts at a senseful place. Just as an example.
0

## Create an account

Register a new account