How to change AnimationStream for supporting Hermite Interpolation

Jan 23, 2014 at 7:25 AM
Edited Jan 23, 2014 at 7:32 AM
Hi Jacob Zink,
I am trying to implement the Hernimte Interpolation for StreamAnimation. This kind of interpolation, in addition to position variables, requires two more ones( Tangent In and Tangent out) for each AnimationState. Unfortunately, your StreamAnimation now only supports one kind of data is Vector3f, which serves as the data type for position and rotation, as I was seen at the end of the file AnimationStream.CPP
template class Glyph3::AnimationStream<Vector3f>; 
To implement the Hermite Interpolation, I need to change the method
AnimationStream::Update(float time)
And provide a new kind of data for each AnimationState
class HermiteData
{
     vector3f position;
     vector3f tangentIn;
     vector3f tangentOut;
}
Currently, in term of template C++ techniques, I think there are two ways do it now.
  • Extending the base class: AnimationStream, and overide the method AnimationStream::Update, which is as following:
    //HermiteAnimationStream.H
    template<class T>
    HermiteAnimatinoStream :: public AnimationStream<T>
    {
        virtual void Update(float time);
    }
    
    //HermiteAnimationStream.CPP
    template<class T>
    HermiteAnimationStream<T>::Update(float time)
    {
         //...Implement Hermite Interpolation here
    }
    
    template class HermiteAnimationStream<HermiteData>; //Instantiate template
    
But this methods doesn't work since "template class HermiteAnimationStream<HermiteData>" requires AnimationStream<HermiteData>, which results in linking error due to the linker cannot find the definition of AnimationStream<HermiteData>:: ....
  • The second way is using explicit template specialization, which I am doing now.
    template <>
    AnimationStream<HermiteData>::AnimationStream(){ ... }
    
    template <>
    AnimatinoStream<HermiteData>::Update() {...}
    
    template <>
    AnimationStream<HermiteData>::AddState(...) {...}
    
But this way requires me copy-paste all the source code from AnimationStream.CPP to the specialized file, which seems uneccessary since I only need chage the method "Update".


Could you suggest some pattern for resolving this problem.

Thanks so much,
Coordinator
Jan 28, 2014 at 7:04 PM
I took a look at this topic earlier today, and I think the main problem here is that the interpolation method is baked into the AnimationStream class. If I were to change the method to default to a linear interpolation routine, but with a method to set a new routine (probably via a std::function, which would allow a function pointer, a functor, or even a lambda), then you could easily define an interpolation method specific to your hermite data class.

Is this something you are interested in? If you can supply me with the hermite interpolation method, I can make this update and add it into the engine distribution if you would like. Just let me know and I'll act accordingly!
Jan 30, 2014 at 9:28 AM
Edited Jan 30, 2014 at 9:33 AM
It's so great to see how you resolve this problem.

I think that most of interpolation methods require a basic data type: Vector3f, so I use your AnimationState as a base class. Each time we need to implement a new interpolation method, we only need to extend this AnimationState for new additional data.

And this is HermiteAnimationState class, which contains additional data for Hermite Interpolation
class AnimationState1
{
public:
    AnimationState1()
    {
        m_fTimeStamp = 0.0f;
    };
    AnimationState1( float time, Vector3f& data)
    {
        m_spacialData = data;
        m_fTimeStamp = time;
    };

    Vector3f m_spacialData;         // spacial data to be interpolated
    float   m_fTimeStamp;           // Time stamp within the current animation.
};

class HermiteAnimationState : public AnimationState1
{
public:
    HermiteAnimationState();

    HermiteAnimationState(float time, Vector3f& pos, Vector3f& tanIn, Vector3f& tanOut);

    ~HermiteAnimationState();

    HermiteAnimationState& operator =(const HermiteAnimationState& other);

    Vector3f m_tangentIn;
    Vector3f m_tangentOut;
};
I also apply the same pattern for AnimationStream, we means we will extend the method "Update" from AnimationStream1 whenever we need a new implemention for interpolation.
class HermiteAnimationStream1 : public AnimationStream1
{
public:
    HermiteAnimationStream1();
    virtual ~HermiteAnimationStream1();
    virtual void Update(float fTime);
};
This is the Update method from HermiteAnimationStream, which extends from AnimationStream1.
void HermiteAnimationStream1::Update(float fTime)
{
cout<<"HermiteAnimationStream1::Update"<<endl;

if ( m_bRunning )
{
    // Update the time of the animation
    m_fAnimationTime += fTime;

    // Check for the next key-frame transition.  This is only valid if there
    // is another state to transition too!  If the current animation time has
    // exceeded the next state's time stamp, then increment to the next state.

    if ( m_uiCurrFrame + 1 < m_vStates.size() ) 
    {
        while ( ( m_uiCurrFrame < m_uiEndFrame ) && ( m_vStates[m_uiCurrFrame+1].m_fTimeStamp < m_fAnimationTime ) )
        {
            m_uiCurrFrame++;
        }
    }

    // Check if we have reached the last frame.  If so, we stop the animation
    // mode and set the output value to the final state.

    if ( m_uiCurrFrame >= m_uiEndFrame )
    {
        if ( m_uiCurrFrame > m_uiEndFrame ) m_uiCurrFrame = m_uiEndFrame;
        m_bRunning = false;
        m_kCurrState = m_vStates[m_uiCurrFrame];
    }
    else
    {
        // Perform interpolation between the current frame and the next frame
        // to find the current state.
        HermiteAnimationState *pHerState0, *pHerState1;
        pHerState0 = reinterpret_cast<HermiteAnimationState*>(&m_vStates[m_uiCurrFrame]);
        pHerState1 = reinterpret_cast<HermiteAnimationState*>(&m_vStates[m_uiCurrFrame+1]);

        Vector3f tangentOut0 = pHerState0->m_tangentOut;
        Vector3f tangentIn1 = pHerState1->m_tangentIn;
        Vector3f position0 = pHerState0->m_spacialData;
        Vector3f position1 = pHerState1->m_spacialData;

        float t = (m_fAnimationTime - pHerState0->m_fTimeStamp)/(pHerState1->m_fTimeStamp - pHerState0->m_fTimeStamp);
        float t2 = t*t;
        float t3 = t2*t;

        // calculate Hermite basis
        float h1 =  2.0f * t3 - 3.0f * t2 + 1.0f;
        float h2 = -2.0f * t3 + 3.0f * t2;
        float h3 =         t3 - 2.0f * t2 + t;
        float h4 =         t3 -        t2;

        m_kCurrState.m_spacialData.x = h1 * position0.x + h3 * tangentOut0.x + h2 * position1.x + h4 * tangentIn1.x;
        m_kCurrState.m_spacialData.y = h1 * position0.y + h3 * tangentOut0.y + h2 * position1.y + h4 * tangentIn1.y;
        m_kCurrState.m_spacialData.z = h1 * position0.z + h3 * tangentOut0.z + h2 * position1.z + h4 * tangentIn1.z;

    }
}
}
Feb 5, 2014 at 6:22 AM
Edited Feb 5, 2014 at 6:22 AM
Hello,
I have finished the MS3D skinning mesh using the above pattern. But I have heard somewhere that using data inheritance in critical loop as I do (HermiteAnimationState derives from AnimationState) could overhead the CPU.

You said that I can change the design to pass the Functor to AnimationStream, but it covers only the implemetation of Update method. What's about additional data that the Update method need (in my case is two vectors Tangent In and Tangent out for eac AnimationState )
Coordinator
Feb 15, 2014 at 8:10 PM
We could probably make the update method functor take an argument that is dependent on the 'data' element type. If you want, I can put together a prototype and push it to the repository for you to take a look at. What do you think?
Marked as answer by khanhhh89 on 2/16/2014 at 4:24 PM
Feb 16, 2014 at 11:25 PM
Yeah,
Thanks for your support!
I hope to see your solution soon.
Coordinator
Feb 25, 2014 at 1:52 AM
I just pushed some changes, including the update to the animation stream class. It is now a class template, and you can set the interpolation method as well (the default is set in the default constructor initialization list). Please try this out in your custom SkinnedActor with your updated data type structure, and let me know how it works out for you. I also added your Hermite interpolation method to the Tween.h/.inl file.

If further changes are needed, just let me know!
Marked as answer by khanhhh89 on 3/31/2014 at 2:18 AM
Mar 31, 2014 at 9:17 AM
Edited Mar 31, 2014 at 9:19 AM
Hello,
I thought you foget about this animation class stuff :). I have changed my code to apply your solution. It's so simple and effective. Thanks for your support.
Coordinator
Mar 31, 2014 at 2:02 PM
Cool - I'm glad it worked out for you :) If there is any other areas of improvement, please feel free to bring them up in a new discussion tab!