startrcewww-data endrce
AnimationBlender         Utility class for transitions between animations

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