Table of contents
Introduction
To blend is to fade between two states. In the animation process it allows to create a transition between two animations. With this sample code, you can blend animations in 3 ways:
- BlendSwitch: just switch to the second/destination animation (stop the first/source and start the second).
- BlendWhileAnimating: cross-fade, blend source animation out while blending destination animation in.
- BlendThenAnimate: pick source's current frame and blend it with destination's first frame.
Here is a Mogre port: MOGRE AnimationBlender
Source Code
AnimationBlender.h
class AnimationBlender { public: enum BlendingTransition { BlendSwitch, // stop source and start dest BlendWhileAnimating, // cross fade, blend source animation out while blending destination animation in BlendThenAnimate // blend source to first frame of dest, when done, start dest anim }; private: Entity *mEntity; AnimationState *mSource; AnimationState *mTarget; BlendingTransition mTransition; bool loop; ~AnimationBlender() {} public: Real mTimeleft, mDuration; bool complete; void blend( const String &animation, BlendingTransition transition, Real duration, bool l=true ); void addTime( Real ); Real getProgress() { return mTimeleft/ mDuration; } AnimationState *getSource() { return mSource; } AnimationState *getTarget() { return mTarget; } AnimationBlender( Entity *); void init( const String &animation, bool l=true ); };
AnimationBlender.cpp
void AnimationBlender::init(const String &animation, bool l) { AnimationStateSet *set = mEntity->getAllAnimationStates(); AnimationStateSet::iterator it = set->begin(); while(it != set->end()) { AnimationState &anim = it->second; anim.setEnabled(false); anim.setWeight(0); anim.setTimePosition(0); ++it; } mSource = mEntity->getAnimationState( animation ); mSource->setEnabled(true); mSource->setWeight(1); mTimeleft = 0; mDuration = 1; mTarget = 0; complete = false; loop = l; } void AnimationBlender::blend( const String &animation, BlendingTransition transition, Real duration, bool l ) { loop = l; if( transition == AnimationBlender::BlendSwitch ) { if( mSource != 0 ) mSource->setEnabled(false); mSource = mEntity->getAnimationState( animation ); mSource->setEnabled(true); mSource->setWeight(1); mSource->setTimePosition(0); mTimeleft = 0; } else { AnimationState *newTarget = mEntity->getAnimationState( animation ); if( mTimeleft > 0 ) { // oops, weren't finished yet if( newTarget == mTarget ) { // nothing to do! (ignoring duration here) } else if( newTarget == mSource ) { // going back to the source state, so let's switch mSource = mTarget; mTarget = newTarget; mTimeleft = mDuration - mTimeleft; // i'm ignoring the new duration here } else { // ok, newTarget is really new, so either we simply replace the target with this one, or // we make the target the new source if( mTimeleft < mDuration * 0.5 ) { // simply replace the target with this one mTarget->setEnabled(false); mTarget->setWeight(0); } else { // old target becomes new source mSource->setEnabled(false); mSource->setWeight(0); mSource = mTarget; } mTarget = newTarget; mTarget->setEnabled(true); mTarget->setWeight( 1.0 - mTimeleft / mDuration ); mTarget->setTimePosition(0); } } else { // assert( target == 0, "target should be 0 when not blending" ) // mSource->setEnabled(true); // mSource->setWeight(1); mTransition = transition; mTimeleft = mDuration = duration; mTarget = newTarget; mTarget->setEnabled(true); mTarget->setWeight(0); mTarget->setTimePosition(0); } } } void AnimationBlender::addTime( Real time ) { if( mSource != 0 ) { if( mTimeleft > 0 ) { mTimeleft -= time; if( mTimeleft <= 0 ) { // finish blending mSource->setEnabled(false); mSource->setWeight(0); mSource = mTarget; mSource->setEnabled(true); mSource->setWeight(1); mTarget = 0; } else { // still blending, advance weights mSource->setWeight(mTimeleft / mDuration); mTarget->setWeight(1.0 - mTimeleft / mDuration); if(mTransition == AnimationBlender::BlendWhileAnimating) mTarget->addTime(time); } } if (mSource->getTimePosition() >= mSource->getLength()) { complete=true; } else { complete=false; } mSource->addTime(time); mSource->setLoop(loop); } } AnimationBlender::AnimationBlender( Entity *entity ) : mEntity(entity) { }
AnimationBlender.cpp for Ogre 1.2 (minor changes)
void AnimationBlender::init(const String &animation, bool l) { AnimationStateSet *set = mEntity->getAllAnimationStates(); AnimationStateIterator it = set->getAnimationStateIterator(); while(it.hasMoreElements()) { AnimationState *anim = it.getNext(); anim->setEnabled(false); anim->setWeight(0); anim->setTimePosition(0); } mSource = mEntity->getAnimationState( animation ); mSource->setEnabled(true); mSource->setWeight(1); mTimeleft = 0; mDuration = 1; mTarget = 0; complete = false; loop = l; } void AnimationBlender::blend( const String &animation, BlendingTransition transition, Real duration, bool l ) { loop = l; if( transition == AnimationBlender::BlendSwitch ) { if( mSource != 0 ) mSource->setEnabled(false); mSource = mEntity->getAnimationState( animation ); mSource->setEnabled(true); mSource->setWeight(1); mSource->setTimePosition(0); mTimeleft = 0; } else { AnimationState *newTarget = mEntity->getAnimationState( animation ); if( mTimeleft > 0 ) { // oops, weren't finished yet if( newTarget == mTarget ) { // nothing to do! (ignoring duration here) } else if( newTarget == mSource ) { // going back to the source state, so let's switch mSource = mTarget; mTarget = newTarget; mTimeleft = mDuration - mTimeleft; // i'm ignoring the new duration here } else { // ok, newTarget is really new, so either we simply replace the target with this one, or // we make the target the new source if( mTimeleft < mDuration * 0.5 ) { // simply replace the target with this one mTarget->setEnabled(false); mTarget->setWeight(0); } else { // old target becomes new source mSource->setEnabled(false); mSource->setWeight(0); mSource = mTarget; } mTarget = newTarget; mTarget->setEnabled(true); mTarget->setWeight( 1.0 - mTimeleft / mDuration ); mTarget->setTimePosition(0); } } else { // assert( target == 0, "target should be 0 when not blending" ) // mSource->setEnabled(true); // mSource->setWeight(1); mTransition = transition; mTimeleft = mDuration = duration; mTarget = newTarget; mTarget->setEnabled(true); mTarget->setWeight(0); mTarget->setTimePosition(0); } } } void AnimationBlender::addTime( Real time ) { if( mSource != 0 ) { if( mTimeleft > 0 ) { mTimeleft -= time; if( mTimeleft <= 0 ) { // finish blending mSource->setEnabled(false); mSource->setWeight(0); mSource = mTarget; mSource->setEnabled(true); mSource->setWeight(1); mTarget = 0; } else { // still blending, advance weights mSource->setWeight(mTimeleft / mDuration); mTarget->setWeight(1.0 - mTimeleft / mDuration); if(mTransition == AnimationBlender::BlendWhileAnimating) mTarget->addTime(time); } } if (mSource->getTimePosition() >= mSource->getLength()) { complete = true; } else { complete = false; } mSource->addTime(time); mSource->setLoop(loop); } } AnimationBlender::AnimationBlender( Entity *entity ) : mEntity(entity) { }
Utilisation
To use the functions, you can do something like this:
If you want your character to walk for instance you could do this:
if (!walking) { thePlayer->mAnimationBlender->blend( "Walk",AnimationBlender::BlendWhileAnimating, 0.2, true ); walking=true; } if (mInputDevice->isKeyDown( KC_SPACE ) && !jumping) { jumping=true; thePlayer->mAnimationBlender->blend("Jump",AnimationBlender::BlendWhileAnimating, 0.2, false ); } if (jumping) { if (thePlayer->mAnimationBlender->complete) { thePlayer->mAnimationBlender->blend( "Idle1",AnimationBlender::BlendWhileAnimating, 0.02, true ); jumping=false; } }
Transition Bug
Bug in Animation Blender
I have noticed that the AnimationBlender dies/freezes (the animation not the application) when the blend function is called between the transition time of the last blend.
I am specifically talking about "BlendWhileAnimating" state here.
Work around posted here:
http://www.ogre3d.org/forums/viewtopic.php?f=2&t=53911&p=366582#p366582
Alias: Talk:AnimationBlender