forked from teamnwah/openmw-tes3coop
		
	Revert "reworked Nif::KeyListT into Nif::CurveT"
This reverts commit e7665582ad.
			
			
This commit is contained in:
		
							parent
							
								
									de3c76a54b
								
							
						
					
					
						commit
						24f968623f
					
				
					 5 changed files with 258 additions and 483 deletions
				
			
		| 
						 | 
				
			
			@ -1,424 +0,0 @@
 | 
			
		|||
#ifndef _NIF_KEYLIST_H_
 | 
			
		||||
#define _NIF_KEYLIST_H_
 | 
			
		||||
 | 
			
		||||
#include <cfloat>
 | 
			
		||||
 | 
			
		||||
namespace Nif
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
template <typename iterator , typename predicate>
 | 
			
		||||
void bubble_sort (iterator begin, iterator end, predicate const & in_order)
 | 
			
		||||
{
 | 
			
		||||
    if (end > begin)
 | 
			
		||||
    {
 | 
			
		||||
        for (iterator i = begin; i != end - 1; ++i)
 | 
			
		||||
        {
 | 
			
		||||
            if (in_order (*(i+0), *(i+1)))
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            for (iterator j = i; j >= begin; --j)
 | 
			
		||||
            {
 | 
			
		||||
                std::swap (*(j+0), *(j+1));
 | 
			
		||||
 | 
			
		||||
                if (in_order (*(j+0), *(j+1)))
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename value_type>
 | 
			
		||||
value_type linear_interpolate (float amount, value_type prev, value_type next)
 | 
			
		||||
{
 | 
			
		||||
    return prev + (next - prev) * amount;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline
 | 
			
		||||
Ogre::Quaternion linear_interpolate (float amount, Ogre::Quaternion prev, Ogre::Quaternion next)
 | 
			
		||||
{
 | 
			
		||||
    return Ogre::Quaternion::nlerp (amount, prev, next);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template<typename value_type>
 | 
			
		||||
struct KeyT {
 | 
			
		||||
 | 
			
		||||
    static const size_t EncodedLength =
 | 
			
		||||
        NIFStream::handler <float>::EncodedLength +
 | 
			
		||||
        NIFStream::handler <value_type>::EncodedLength
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
    float mTime;
 | 
			
		||||
    value_type mValue;
 | 
			
		||||
 | 
			
		||||
    void extract (NIFStream &nif)
 | 
			
		||||
    {
 | 
			
		||||
        nif.uncheckedRead (mTime);
 | 
			
		||||
        nif.uncheckedRead (mValue);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static bool in_order (KeyT <value_type> const & l, KeyT <value_type> const & r)
 | 
			
		||||
    {
 | 
			
		||||
        return l.mTime < r.mTime;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    template <typename derived_type>
 | 
			
		||||
    struct NIFStream_handler
 | 
			
		||||
    {
 | 
			
		||||
        static const bool FixedLength = true;
 | 
			
		||||
        static const size_t EncodedLength = derived_type::EncodedLength;
 | 
			
		||||
        static const bool FileCompatibleLayout = true;
 | 
			
		||||
 | 
			
		||||
        static void extract (NIFStream& Stream, KeyT <value_type> & Value)
 | 
			
		||||
        {
 | 
			
		||||
            static_cast <derived_type &> (Value).extract (Stream);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
struct LinearKeyT : KeyT <T>
 | 
			
		||||
{
 | 
			
		||||
    static T interpolate (LinearKeyT <T> * prev, LinearKeyT <T> * next, float amount)
 | 
			
		||||
    {
 | 
			
		||||
        return linear_interpolate (amount, prev->mValue, next->mValue);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
struct QuadraticKeyT : KeyT <T>
 | 
			
		||||
{
 | 
			
		||||
    static const size_t EncodedLength =
 | 
			
		||||
        KeyT <T>::EncodedLength +
 | 
			
		||||
        NIFStream::handler <T>::EncodedLength * 2
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
    T mForwardValue;
 | 
			
		||||
    T mBackwardValue;
 | 
			
		||||
 | 
			
		||||
    static T interpolate (QuadraticKeyT <T> * prev, QuadraticKeyT <T> * next, float amount)
 | 
			
		||||
    {
 | 
			
		||||
        return linear_interpolate (amount, prev->mValue, next->mValue);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void extract (NIFStream &nif)
 | 
			
		||||
    {
 | 
			
		||||
        KeyT<T>::extract (nif);
 | 
			
		||||
 | 
			
		||||
        nif.uncheckedRead (mForwardValue);
 | 
			
		||||
        nif.uncheckedRead (mBackwardValue);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
struct TbcKeyT : KeyT <T>
 | 
			
		||||
{
 | 
			
		||||
    static const size_t EncodedLength =
 | 
			
		||||
        KeyT <T>::EncodedLength +
 | 
			
		||||
        NIFStream::handler <float>::EncodedLength * 3
 | 
			
		||||
        ;
 | 
			
		||||
 | 
			
		||||
    float mTension;
 | 
			
		||||
    float mBias;
 | 
			
		||||
    float mContinuity;
 | 
			
		||||
 | 
			
		||||
    static T interpolate (TbcKeyT <T> * prev, TbcKeyT <T> * next, float amount)
 | 
			
		||||
    {
 | 
			
		||||
        return linear_interpolate (amount, prev->mValue, next->mValue);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void extract (NIFStream &nif)
 | 
			
		||||
    {
 | 
			
		||||
        KeyT<T>::extract (nif);
 | 
			
		||||
 | 
			
		||||
        nif.uncheckedRead (mTension);
 | 
			
		||||
        nif.uncheckedRead (mBias);
 | 
			
		||||
        nif.uncheckedRead (mContinuity);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// register NIFStream extraction handlers for KeyT derivatives
 | 
			
		||||
template <typename T> struct NIFStream::handler < LinearKeyT    <T> > : KeyT <T>::template NIFStream_handler < LinearKeyT    <T> > {};
 | 
			
		||||
template <typename T> struct NIFStream::handler < QuadraticKeyT <T> > : KeyT <T>::template NIFStream_handler < QuadraticKeyT <T> > {};
 | 
			
		||||
template <typename T> struct NIFStream::handler < TbcKeyT       <T> > : KeyT <T>::template NIFStream_handler < TbcKeyT       <T> > {};
 | 
			
		||||
 | 
			
		||||
struct Curve
 | 
			
		||||
{
 | 
			
		||||
    static const int sLinearInterpolation    = 1;
 | 
			
		||||
    static const int sQuadraticInterpolation = 2;
 | 
			
		||||
    static const int sTBCInterpolation       = 3;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename value_type>
 | 
			
		||||
struct CurveT : Curve {
 | 
			
		||||
 | 
			
		||||
    typedef KeyT <value_type>          BaseKey;
 | 
			
		||||
    typedef TbcKeyT <value_type>       TcbKey;
 | 
			
		||||
    typedef LinearKeyT <value_type>    LinearKey;
 | 
			
		||||
    typedef QuadraticKeyT <value_type> QuadraticKey;
 | 
			
		||||
 | 
			
		||||
    union keys {
 | 
			
		||||
        LinearKey*    Linear;
 | 
			
		||||
        QuadraticKey* Quadratic;
 | 
			
		||||
        TcbKey*       Tcb;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    class interpolator;
 | 
			
		||||
 | 
			
		||||
    int mInterpolationType;
 | 
			
		||||
    size_t  mSize;
 | 
			
		||||
    keys    mKeys;
 | 
			
		||||
 | 
			
		||||
    value_type sample (float time) const;
 | 
			
		||||
 | 
			
		||||
    KeyT <value_type> const * const & keyAtIndex (size_t Index) const
 | 
			
		||||
    {
 | 
			
		||||
        switch (mInterpolationType)
 | 
			
		||||
        {
 | 
			
		||||
        case sLinearInterpolation:    return mKeys.Linear    + Index;
 | 
			
		||||
        case sQuadraticInterpolation: return mKeys.Quadratic + Index;
 | 
			
		||||
        case sTBCInterpolation:       return mKeys.Tcb       + Index;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void read(NIFStream *nif, bool force=false)
 | 
			
		||||
    {
 | 
			
		||||
        size_t count = nif->getInt();
 | 
			
		||||
 | 
			
		||||
        mSize = 0;
 | 
			
		||||
 | 
			
		||||
        if(count > 0 || force)
 | 
			
		||||
        {
 | 
			
		||||
            mInterpolationType = nif->getInt();
 | 
			
		||||
 | 
			
		||||
            assert (mInterpolationType >= sLinearInterpolation && mInterpolationType <= sTBCInterpolation);
 | 
			
		||||
 | 
			
		||||
            if (count > 0)
 | 
			
		||||
            {
 | 
			
		||||
                if(mInterpolationType == sLinearInterpolation)
 | 
			
		||||
                    read_keys (nif, mKeys.Linear, count);
 | 
			
		||||
                else if(mInterpolationType == sQuadraticInterpolation)
 | 
			
		||||
                    read_keys (nif, mKeys.Quadratic, count);
 | 
			
		||||
                else if(mInterpolationType == sTBCInterpolation)
 | 
			
		||||
                    read_keys (nif, mKeys.Tcb, count);
 | 
			
		||||
                else
 | 
			
		||||
                    nif->file->warn("Unhandled interpolation type: "+Ogre::StringConverter::toString(mInterpolationType));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
            mInterpolationType = sLinearInterpolation;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    CurveT () { init (); }
 | 
			
		||||
    CurveT (CurveT <value_type> const & k) { init (k); }
 | 
			
		||||
    //CurveT (CurveT <value_type> && k) { init (); swap (std::move (k)); }
 | 
			
		||||
    ~CurveT () { dest (); }
 | 
			
		||||
 | 
			
		||||
    operator bool () const { return mSize > 0; }
 | 
			
		||||
 | 
			
		||||
    //void operator = (CurveT<value_type> && k) { swap(k); }
 | 
			
		||||
    void operator = (CurveT<value_type> const & k) { dest (); init (k); }
 | 
			
		||||
 | 
			
		||||
    void swap (CurveT<value_type> & k)
 | 
			
		||||
    {
 | 
			
		||||
        std::swap (mSize, k.mSize);
 | 
			
		||||
        std::swap (mInterpolationType, k.mInterpolationType);
 | 
			
		||||
        std::swap (mKeys, k.mKeys);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
 | 
			
		||||
    void init ()
 | 
			
		||||
    {
 | 
			
		||||
        mSize = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void init (CurveT<value_type> const & k)
 | 
			
		||||
    {
 | 
			
		||||
        mInterpolationType = k.mInterpolationType;
 | 
			
		||||
        switch (mInterpolationType)
 | 
			
		||||
        {
 | 
			
		||||
        default:
 | 
			
		||||
        case sLinearInterpolation:
 | 
			
		||||
            mKeys.Linear = new LinearKey [k.mSize];
 | 
			
		||||
            memcpy (mKeys.Linear, k.mKeys.Linear, sizeof (LinearKey) * k.mSize);
 | 
			
		||||
            mSize = k.mSize;
 | 
			
		||||
            break;
 | 
			
		||||
        case sQuadraticInterpolation:
 | 
			
		||||
            mKeys.Quadratic = new QuadraticKey [k.mSize];
 | 
			
		||||
            memcpy (mKeys.Quadratic, k.mKeys.Quadratic, sizeof (QuadraticKey) * k.mSize);
 | 
			
		||||
            mSize = k.mSize;
 | 
			
		||||
            break;
 | 
			
		||||
        case sTBCInterpolation:
 | 
			
		||||
            mKeys.Tcb = new TcbKey [k.mSize];
 | 
			
		||||
            memcpy (mKeys.Tcb, k.mKeys.Tcb, sizeof (TcbKey) * k.mSize);
 | 
			
		||||
            mSize = k.mSize;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void dest ()
 | 
			
		||||
    {
 | 
			
		||||
        if (mSize > 0)
 | 
			
		||||
        {
 | 
			
		||||
            switch (mInterpolationType)
 | 
			
		||||
            {
 | 
			
		||||
            case sLinearInterpolation:      delete mKeys.Linear;    break;
 | 
			
		||||
            case sQuadraticInterpolation:   delete mKeys.Quadratic; break;
 | 
			
		||||
            case sTBCInterpolation:         delete mKeys.Tcb;       break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    template <typename T>
 | 
			
		||||
    void read_keys (NIFStream *nif, T * & store, size_t count)
 | 
			
		||||
    {
 | 
			
		||||
        store = new T [count];
 | 
			
		||||
 | 
			
		||||
        mSize = count;
 | 
			
		||||
 | 
			
		||||
        nif->getArray (store, count);
 | 
			
		||||
 | 
			
		||||
        //NOTE: Is this really necessary? It seems reasonable to assume that
 | 
			
		||||
        //      animation data is already sorted by time...
 | 
			
		||||
        //      verified no out of order frames in GOTY edition
 | 
			
		||||
        bubble_sort (store, store+count, T::in_order);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename value_type>
 | 
			
		||||
class CurveT<value_type>::interpolator
 | 
			
		||||
{
 | 
			
		||||
    template <typename key_type>
 | 
			
		||||
    struct impl
 | 
			
		||||
    {
 | 
			
		||||
        key_type *Cur, *End;
 | 
			
		||||
 | 
			
		||||
        void init (key_type * Beg, size_t Len)
 | 
			
		||||
        {
 | 
			
		||||
            if (Len > 0)
 | 
			
		||||
            {
 | 
			
		||||
                Cur = Beg;
 | 
			
		||||
                End = Beg + Len - 1;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                Cur = End = NULL;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool hasData () const
 | 
			
		||||
        {
 | 
			
		||||
            return Cur && Cur <= End;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        value_type valueAt (float time)
 | 
			
		||||
        {
 | 
			
		||||
            while ((Cur < End) && (time >= Cur [1].mTime))
 | 
			
		||||
                ++Cur;
 | 
			
		||||
 | 
			
		||||
            if (Cur < End)
 | 
			
		||||
            {
 | 
			
		||||
                if (time > Cur->mTime)
 | 
			
		||||
                {
 | 
			
		||||
                    key_type * Nxt = Cur + 1;
 | 
			
		||||
 | 
			
		||||
                    float offset = time - Cur->mTime;
 | 
			
		||||
                    float length = Nxt->mTime - Cur->mTime;
 | 
			
		||||
 | 
			
		||||
                    return key_type::interpolate (Cur, Nxt, offset / length);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                    return Cur->mValue;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
                return End->mValue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        float curTime () const
 | 
			
		||||
        {
 | 
			
		||||
            return (Cur != NULL) ? Cur->Time : FLT_MIN;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        float nextTime () const
 | 
			
		||||
        {
 | 
			
		||||
            return Cur < End ? (Cur + 1)->mTime : FLT_MAX;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
 | 
			
		||||
    int mInterpolationType;
 | 
			
		||||
    union {
 | 
			
		||||
        impl <LinearKey>    Linear;
 | 
			
		||||
        impl <QuadraticKey> Quadratic;
 | 
			
		||||
        impl <TcbKey>       Tcb;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    interpolator (CurveT <value_type> const & Curve)
 | 
			
		||||
    {
 | 
			
		||||
        mInterpolationType = Curve.mInterpolationType;
 | 
			
		||||
 | 
			
		||||
        switch (mInterpolationType)
 | 
			
		||||
        {
 | 
			
		||||
        default:
 | 
			
		||||
        case Curve::sLinearInterpolation:    Linear   .init (Curve.mKeys.Linear,    Curve.mSize); break;
 | 
			
		||||
        case Curve::sQuadraticInterpolation: Quadratic.init (Curve.mKeys.Quadratic, Curve.mSize); break;
 | 
			
		||||
        case Curve::sTBCInterpolation:       Tcb      .init (Curve.mKeys.Tcb,       Curve.mSize); break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // return true if there is any value(s) in this curve
 | 
			
		||||
    float hasData () const
 | 
			
		||||
    {
 | 
			
		||||
        switch (mInterpolationType)
 | 
			
		||||
        {
 | 
			
		||||
        default:
 | 
			
		||||
        case Curve::sLinearInterpolation:    return Linear   .hasData ();
 | 
			
		||||
        case Curve::sQuadraticInterpolation: return Quadratic.hasData ();
 | 
			
		||||
        case Curve::sTBCInterpolation:       return Tcb      .hasData ();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // return the timestamp of the next key-frame, or FLT_MAX if
 | 
			
		||||
    // there are no more key-frames, valid if hasData returns false
 | 
			
		||||
    float nextTime () const
 | 
			
		||||
    {
 | 
			
		||||
        switch (mInterpolationType)
 | 
			
		||||
        {
 | 
			
		||||
        default:
 | 
			
		||||
        case Curve::sLinearInterpolation:    return Linear   .nextTime ();
 | 
			
		||||
        case Curve::sQuadraticInterpolation: return Quadratic.nextTime ();
 | 
			
		||||
        case Curve::sTBCInterpolation:       return Tcb      .nextTime ();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // return the value of the curve at the specified time
 | 
			
		||||
    // the passed in time should never exceed the result of
 | 
			
		||||
    // nextTime, not valid if hasData returns false
 | 
			
		||||
    value_type valueAt (float time)
 | 
			
		||||
    {
 | 
			
		||||
        switch (mInterpolationType)
 | 
			
		||||
        {
 | 
			
		||||
        default:
 | 
			
		||||
        case Curve::sLinearInterpolation:    return Linear   .valueAt (time);
 | 
			
		||||
        case Curve::sQuadraticInterpolation: return Quadratic.valueAt (time);
 | 
			
		||||
        case Curve::sTBCInterpolation:       return Tcb      .valueAt (time);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template<typename value_type>
 | 
			
		||||
value_type CurveT<value_type>::sample (float time) const
 | 
			
		||||
{
 | 
			
		||||
    interpolator i (*this);
 | 
			
		||||
    return i.valueAt (time);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
typedef CurveT<float> FloatCurve;
 | 
			
		||||
typedef CurveT<Ogre::Vector3> Vector3Curve;
 | 
			
		||||
typedef CurveT<Ogre::Vector4> Vector4Curve;
 | 
			
		||||
typedef CurveT<Ogre::Quaternion> QuaternionCurve;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			@ -25,7 +25,6 @@
 | 
			
		|||
#define OPENMW_COMPONENTS_NIF_DATA_HPP
 | 
			
		||||
 | 
			
		||||
#include "controlled.hpp"
 | 
			
		||||
#include "curve.hpp"
 | 
			
		||||
 | 
			
		||||
#include <OgreQuaternion.h>
 | 
			
		||||
#include <OgreVector3.h>
 | 
			
		||||
| 
						 | 
				
			
			@ -212,7 +211,7 @@ public:
 | 
			
		|||
class NiPosData : public Record
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    Vector3Curve mKeyList;
 | 
			
		||||
    Vector3KeyList mKeyList;
 | 
			
		||||
 | 
			
		||||
    void read(NIFStream *nif)
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -223,7 +222,7 @@ public:
 | 
			
		|||
class NiUVData : public Record
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    FloatCurve mKeyList[4];
 | 
			
		||||
    FloatKeyList mKeyList[4];
 | 
			
		||||
 | 
			
		||||
    void read(NIFStream *nif)
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -235,7 +234,7 @@ public:
 | 
			
		|||
class NiFloatData : public Record
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    FloatCurve mKeyList;
 | 
			
		||||
    FloatKeyList mKeyList;
 | 
			
		||||
 | 
			
		||||
    void read(NIFStream *nif)
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -285,7 +284,7 @@ public:
 | 
			
		|||
class NiColorData : public Record
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    Vector4Curve mKeyList;
 | 
			
		||||
    Vector4KeyList mKeyList;
 | 
			
		||||
 | 
			
		||||
    void read(NIFStream *nif)
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -390,7 +389,7 @@ public:
 | 
			
		|||
struct NiMorphData : public Record
 | 
			
		||||
{
 | 
			
		||||
    struct MorphData {
 | 
			
		||||
        FloatCurve mData;
 | 
			
		||||
        FloatKeyList mData;
 | 
			
		||||
        std::vector<Ogre::Vector3> mVertices;
 | 
			
		||||
    };
 | 
			
		||||
    std::vector<MorphData> mMorphs;
 | 
			
		||||
| 
						 | 
				
			
			@ -413,9 +412,9 @@ struct NiMorphData : public Record
 | 
			
		|||
 | 
			
		||||
struct NiKeyframeData : public Record
 | 
			
		||||
{
 | 
			
		||||
    QuaternionCurve mRotations;
 | 
			
		||||
    Vector3Curve mTranslations;
 | 
			
		||||
    FloatCurve mScales;
 | 
			
		||||
    QuaternionKeyList mRotations;
 | 
			
		||||
    Vector3KeyList mTranslations;
 | 
			
		||||
    FloatKeyList mScales;
 | 
			
		||||
 | 
			
		||||
    void read(NIFStream *nif)
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -132,5 +132,81 @@ public:
 | 
			
		|||
    size_t numRoots() { return roots.size(); }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
template<typename T>
 | 
			
		||||
struct KeyT {
 | 
			
		||||
    float mTime;
 | 
			
		||||
    T mValue;
 | 
			
		||||
    T mForwardValue;  // Only for Quadratic interpolation
 | 
			
		||||
    T mBackwardValue; // Only for Quadratic interpolation
 | 
			
		||||
    float mTension;    // Only for TBC interpolation
 | 
			
		||||
    float mBias;       // Only for TBC interpolation
 | 
			
		||||
    float mContinuity; // Only for TBC interpolation
 | 
			
		||||
};
 | 
			
		||||
typedef KeyT<float> FloatKey;
 | 
			
		||||
typedef KeyT<Ogre::Vector3> Vector3Key;
 | 
			
		||||
typedef KeyT<Ogre::Vector4> Vector4Key;
 | 
			
		||||
typedef KeyT<Ogre::Quaternion> QuaternionKey;
 | 
			
		||||
 | 
			
		||||
template<typename T, T (NIFStream::*getValue)()>
 | 
			
		||||
struct KeyListT {
 | 
			
		||||
    typedef std::vector< KeyT<T> > VecType;
 | 
			
		||||
 | 
			
		||||
    static const int sLinearInterpolation = 1;
 | 
			
		||||
    static const int sQuadraticInterpolation = 2;
 | 
			
		||||
    static const int sTBCInterpolation = 3;
 | 
			
		||||
 | 
			
		||||
    int mInterpolationType;
 | 
			
		||||
    VecType mKeys;
 | 
			
		||||
 | 
			
		||||
    void read(NIFStream *nif, bool force=false)
 | 
			
		||||
    {
 | 
			
		||||
        size_t count = nif->getInt();
 | 
			
		||||
        if(count == 0 && !force)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        mInterpolationType = nif->getInt();
 | 
			
		||||
        mKeys.resize(count);
 | 
			
		||||
        if(mInterpolationType == sLinearInterpolation)
 | 
			
		||||
        {
 | 
			
		||||
            for(size_t i = 0;i < count;i++)
 | 
			
		||||
            {
 | 
			
		||||
                KeyT<T> &key = mKeys[i];
 | 
			
		||||
                key.mTime = nif->getFloat();
 | 
			
		||||
                key.mValue = (nif->*getValue)();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else if(mInterpolationType == sQuadraticInterpolation)
 | 
			
		||||
        {
 | 
			
		||||
            for(size_t i = 0;i < count;i++)
 | 
			
		||||
            {
 | 
			
		||||
                KeyT<T> &key = mKeys[i];
 | 
			
		||||
                key.mTime = nif->getFloat();
 | 
			
		||||
                key.mValue = (nif->*getValue)();
 | 
			
		||||
                key.mForwardValue = (nif->*getValue)();
 | 
			
		||||
                key.mBackwardValue = (nif->*getValue)();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else if(mInterpolationType == sTBCInterpolation)
 | 
			
		||||
        {
 | 
			
		||||
            for(size_t i = 0;i < count;i++)
 | 
			
		||||
            {
 | 
			
		||||
                KeyT<T> &key = mKeys[i];
 | 
			
		||||
                key.mTime = nif->getFloat();
 | 
			
		||||
                key.mValue = (nif->*getValue)();
 | 
			
		||||
                key.mTension = nif->getFloat();
 | 
			
		||||
                key.mBias = nif->getFloat();
 | 
			
		||||
                key.mContinuity = nif->getFloat();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
            nif->file->warn("Unhandled interpolation type: "+Ogre::StringConverter::toString(mInterpolationType));
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
typedef KeyListT<float,&NIFStream::getFloat> FloatKeyList;
 | 
			
		||||
typedef KeyListT<Ogre::Vector3,&NIFStream::getVector3> Vector3KeyList;
 | 
			
		||||
typedef KeyListT<Ogre::Vector4,&NIFStream::getVector4> Vector4KeyList;
 | 
			
		||||
typedef KeyListT<Ogre::Quaternion,&NIFStream::getQuaternion> QuaternionKeyList;
 | 
			
		||||
 | 
			
		||||
} // Namespace
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -166,9 +166,9 @@ public:
 | 
			
		|||
    class Value : public NodeTargetValue<Ogre::Real>
 | 
			
		||||
    {
 | 
			
		||||
    private:
 | 
			
		||||
        Nif::QuaternionCurve mRotations;
 | 
			
		||||
        Nif::Vector3Curve mTranslations;
 | 
			
		||||
        Nif::FloatCurve mScales;
 | 
			
		||||
        Nif::QuaternionKeyList mRotations;
 | 
			
		||||
        Nif::Vector3KeyList mTranslations;
 | 
			
		||||
        Nif::FloatKeyList mScales;
 | 
			
		||||
 | 
			
		||||
    public:
 | 
			
		||||
        Value(Ogre::Node *target, const Nif::NiKeyframeData *data)
 | 
			
		||||
| 
						 | 
				
			
			@ -186,16 +186,68 @@ public:
 | 
			
		|||
 | 
			
		||||
        virtual void setValue(Ogre::Real time)
 | 
			
		||||
        {
 | 
			
		||||
            if(mRotations)
 | 
			
		||||
                mNode->setOrientation(mRotations.sample (time));
 | 
			
		||||
 | 
			
		||||
            if(mTranslations)
 | 
			
		||||
                mNode->setPosition(mTranslations.sample (time));
 | 
			
		||||
 | 
			
		||||
            if(mScales)
 | 
			
		||||
            if(mRotations.mKeys.size() > 0)
 | 
			
		||||
            {
 | 
			
		||||
                float s = mScales.sample (time);
 | 
			
		||||
                mNode->setScale(s, s, s);
 | 
			
		||||
                if(time <= mRotations.mKeys.front().mTime)
 | 
			
		||||
                    mNode->setOrientation(mRotations.mKeys.front().mValue);
 | 
			
		||||
                else if(time >= mRotations.mKeys.back().mTime)
 | 
			
		||||
                    mNode->setOrientation(mRotations.mKeys.back().mValue);
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    Nif::QuaternionKeyList::VecType::const_iterator iter(mRotations.mKeys.begin()+1);
 | 
			
		||||
                    for(;iter != mRotations.mKeys.end();iter++)
 | 
			
		||||
                    {
 | 
			
		||||
                        if(iter->mTime < time)
 | 
			
		||||
                            continue;
 | 
			
		||||
 | 
			
		||||
                        Nif::QuaternionKeyList::VecType::const_iterator last(iter-1);
 | 
			
		||||
                        float a = (time-last->mTime) / (iter->mTime-last->mTime);
 | 
			
		||||
                        mNode->setOrientation(Ogre::Quaternion::nlerp(a, last->mValue, iter->mValue));
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if(mTranslations.mKeys.size() > 0)
 | 
			
		||||
            {
 | 
			
		||||
                if(time <= mTranslations.mKeys.front().mTime)
 | 
			
		||||
                    mNode->setPosition(mTranslations.mKeys.front().mValue);
 | 
			
		||||
                else if(time >= mTranslations.mKeys.back().mTime)
 | 
			
		||||
                    mNode->setPosition(mTranslations.mKeys.back().mValue);
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    Nif::Vector3KeyList::VecType::const_iterator iter(mTranslations.mKeys.begin()+1);
 | 
			
		||||
                    for(;iter != mTranslations.mKeys.end();iter++)
 | 
			
		||||
                    {
 | 
			
		||||
                        if(iter->mTime < time)
 | 
			
		||||
                            continue;
 | 
			
		||||
 | 
			
		||||
                        Nif::Vector3KeyList::VecType::const_iterator last(iter-1);
 | 
			
		||||
                        float a = (time-last->mTime) / (iter->mTime-last->mTime);
 | 
			
		||||
                        mNode->setPosition(last->mValue + ((iter->mValue - last->mValue)*a));
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if(mScales.mKeys.size() > 0)
 | 
			
		||||
            {
 | 
			
		||||
                if(time <= mScales.mKeys.front().mTime)
 | 
			
		||||
                    mNode->setScale(Ogre::Vector3(mScales.mKeys.front().mValue));
 | 
			
		||||
                else if(time >= mScales.mKeys.back().mTime)
 | 
			
		||||
                    mNode->setScale(Ogre::Vector3(mScales.mKeys.back().mValue));
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    Nif::FloatKeyList::VecType::const_iterator iter(mScales.mKeys.begin()+1);
 | 
			
		||||
                    for(;iter != mScales.mKeys.end();iter++)
 | 
			
		||||
                    {
 | 
			
		||||
                        if(iter->mTime < time)
 | 
			
		||||
                            continue;
 | 
			
		||||
 | 
			
		||||
                        Nif::FloatKeyList::VecType::const_iterator last(iter-1);
 | 
			
		||||
                        float a = (time-last->mTime) / (iter->mTime-last->mTime);
 | 
			
		||||
                        mNode->setScale(Ogre::Vector3(last->mValue + ((iter->mValue - last->mValue)*a)));
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
| 
						 | 
				
			
			@ -210,14 +262,30 @@ public:
 | 
			
		|||
    {
 | 
			
		||||
    private:
 | 
			
		||||
        Ogre::MaterialPtr mMaterial;
 | 
			
		||||
        Nif::FloatCurve mUTrans;
 | 
			
		||||
        Nif::FloatCurve mVTrans;
 | 
			
		||||
        Nif::FloatCurve mUScale;
 | 
			
		||||
        Nif::FloatCurve mVScale;
 | 
			
		||||
        Nif::FloatKeyList mUTrans;
 | 
			
		||||
        Nif::FloatKeyList mVTrans;
 | 
			
		||||
        Nif::FloatKeyList mUScale;
 | 
			
		||||
        Nif::FloatKeyList mVScale;
 | 
			
		||||
 | 
			
		||||
        static float lookupValue(const Nif::FloatCurve &keys, float time, float def)
 | 
			
		||||
        static float lookupValue(const Nif::FloatKeyList &keys, float time, float def)
 | 
			
		||||
        {
 | 
			
		||||
            return keys ? keys.sample (time) : def;
 | 
			
		||||
            if(keys.mKeys.size() == 0)
 | 
			
		||||
                return def;
 | 
			
		||||
 | 
			
		||||
            if(time <= keys.mKeys.front().mTime)
 | 
			
		||||
                return keys.mKeys.front().mValue;
 | 
			
		||||
 | 
			
		||||
            Nif::FloatKeyList::VecType::const_iterator iter(keys.mKeys.begin()+1);
 | 
			
		||||
            for(;iter != keys.mKeys.end();iter++)
 | 
			
		||||
            {
 | 
			
		||||
                if(iter->mTime < time)
 | 
			
		||||
                    continue;
 | 
			
		||||
 | 
			
		||||
                Nif::FloatKeyList::VecType::const_iterator last(iter-1);
 | 
			
		||||
                float a = (time-last->mTime) / (iter->mTime-last->mTime);
 | 
			
		||||
                return last->mValue + ((iter->mValue - last->mValue)*a);
 | 
			
		||||
            }
 | 
			
		||||
            return keys.mKeys.back().mValue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    public:
 | 
			
		||||
| 
						 | 
				
			
			@ -324,19 +392,18 @@ class NIFObjectLoader
 | 
			
		|||
                const Nif::NiColorData *clrdata = cl->data.getPtr();
 | 
			
		||||
 | 
			
		||||
                Ogre::ParticleAffector *affector = partsys->addAffector("ColourInterpolator");
 | 
			
		||||
                size_t num_colors = std::min<size_t>(6, clrdata->mKeyList.mSize);
 | 
			
		||||
                size_t num_colors = std::min<size_t>(6, clrdata->mKeyList.mKeys.size());
 | 
			
		||||
                for(size_t i = 0;i < num_colors;i++)
 | 
			
		||||
                {
 | 
			
		||||
                    Nif::Vector4Curve::BaseKey const * Key = clrdata->mKeyList.keyAtIndex (i);
 | 
			
		||||
                    Ogre::ColourValue color;
 | 
			
		||||
                    color.r = Key->mValue[0];
 | 
			
		||||
                    color.g = Key->mValue[1];
 | 
			
		||||
                    color.b = Key->mValue[2];
 | 
			
		||||
                    color.a = Key->mValue[3];
 | 
			
		||||
                    color.r = clrdata->mKeyList.mKeys[i].mValue[0];
 | 
			
		||||
                    color.g = clrdata->mKeyList.mKeys[i].mValue[1];
 | 
			
		||||
                    color.b = clrdata->mKeyList.mKeys[i].mValue[2];
 | 
			
		||||
                    color.a = clrdata->mKeyList.mKeys[i].mValue[3];
 | 
			
		||||
                    affector->setParameter("colour"+Ogre::StringConverter::toString(i),
 | 
			
		||||
                                           Ogre::StringConverter::toString(color));
 | 
			
		||||
                    affector->setParameter("time"+Ogre::StringConverter::toString(i),
 | 
			
		||||
                                           Ogre::StringConverter::toString(Key->mTime));
 | 
			
		||||
                                           Ogre::StringConverter::toString(clrdata->mKeyList.mKeys[i].mTime));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else if(e->recType == Nif::RC_NiParticleRotation)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,11 +10,6 @@
 | 
			
		|||
 | 
			
		||||
namespace NifOgre
 | 
			
		||||
{
 | 
			
		||||
template <typename value_type>
 | 
			
		||||
static value_type min (value_type V0, value_type V1, value_type V2, value_type V3)
 | 
			
		||||
{
 | 
			
		||||
    return std::min (std::min (V0, V1), std::min (V2, V3));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NIFSkeletonLoader::buildAnimation(Ogre::Skeleton *skel, const std::string &name, const std::vector<const Nif::NiKeyframeController*> &ctrls, const std::vector<std::string> &targets, float startTime, float stopTime)
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -27,6 +22,15 @@ void NIFSkeletonLoader::buildAnimation(Ogre::Skeleton *skel, const std::string &
 | 
			
		|||
            continue;
 | 
			
		||||
        const Nif::NiKeyframeData *kf = kfc->data.getPtr();
 | 
			
		||||
 | 
			
		||||
        /* Get the keyframes and make sure they're sorted first to last */
 | 
			
		||||
        const Nif::QuaternionKeyList &quatkeys = kf->mRotations;
 | 
			
		||||
        const Nif::Vector3KeyList &trankeys = kf->mTranslations;
 | 
			
		||||
        const Nif::FloatKeyList &scalekeys = kf->mScales;
 | 
			
		||||
 | 
			
		||||
        Nif::QuaternionKeyList::VecType::const_iterator quatiter = quatkeys.mKeys.begin();
 | 
			
		||||
        Nif::Vector3KeyList::VecType::const_iterator traniter = trankeys.mKeys.begin();
 | 
			
		||||
        Nif::FloatKeyList::VecType::const_iterator scaleiter = scalekeys.mKeys.begin();
 | 
			
		||||
 | 
			
		||||
        Ogre::Bone *bone = skel->getBone(targets[i]);
 | 
			
		||||
        // NOTE: For some reason, Ogre doesn't like the node track ID being different from
 | 
			
		||||
        // the bone ID
 | 
			
		||||
| 
						 | 
				
			
			@ -34,30 +38,83 @@ void NIFSkeletonLoader::buildAnimation(Ogre::Skeleton *skel, const std::string &
 | 
			
		|||
                                              anim->getNodeTrack(bone->getHandle()) :
 | 
			
		||||
                                              anim->createNodeTrack(bone->getHandle(), bone);
 | 
			
		||||
 | 
			
		||||
        Nif::QuaternionCurve::interpolator rci (kf->mRotations);
 | 
			
		||||
        Nif::Vector3Curve::interpolator tci (kf->mTranslations);
 | 
			
		||||
        Nif::FloatCurve::interpolator sci (kf->mScales);
 | 
			
		||||
        Ogre::Quaternion lastquat, curquat;
 | 
			
		||||
        Ogre::Vector3 lasttrans(0.0f), curtrans(0.0f);
 | 
			
		||||
        Ogre::Vector3 lastscale(1.0f), curscale(1.0f);
 | 
			
		||||
        if(quatiter != quatkeys.mKeys.end())
 | 
			
		||||
            lastquat = curquat = quatiter->mValue;
 | 
			
		||||
        if(traniter != trankeys.mKeys.end())
 | 
			
		||||
            lasttrans = curtrans = traniter->mValue;
 | 
			
		||||
        if(scaleiter != scalekeys.mKeys.end())
 | 
			
		||||
            lastscale = curscale = Ogre::Vector3(scaleiter->mValue);
 | 
			
		||||
 | 
			
		||||
        float next_timestamp = startTime;
 | 
			
		||||
 | 
			
		||||
        for (;;)
 | 
			
		||||
        bool didlast = false;
 | 
			
		||||
        while(!didlast)
 | 
			
		||||
        {
 | 
			
		||||
            static const Ogre::Vector3 one (1,1,1);
 | 
			
		||||
            float curtime = std::numeric_limits<float>::max();
 | 
			
		||||
 | 
			
		||||
            //Get latest time
 | 
			
		||||
            if(quatiter != quatkeys.mKeys.end())
 | 
			
		||||
                curtime = std::min(curtime, quatiter->mTime);
 | 
			
		||||
            if(traniter != trankeys.mKeys.end())
 | 
			
		||||
                curtime = std::min(curtime, traniter->mTime);
 | 
			
		||||
            if(scaleiter != scalekeys.mKeys.end())
 | 
			
		||||
                curtime = std::min(curtime, scaleiter->mTime);
 | 
			
		||||
 | 
			
		||||
            curtime = std::max(curtime, startTime);
 | 
			
		||||
            if(curtime >= stopTime)
 | 
			
		||||
            {
 | 
			
		||||
                didlast = true;
 | 
			
		||||
                curtime = stopTime;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Get the latest quaternions, translations, and scales for the
 | 
			
		||||
            // current time
 | 
			
		||||
            while(quatiter != quatkeys.mKeys.end() && curtime >= quatiter->mTime)
 | 
			
		||||
            {
 | 
			
		||||
                lastquat = curquat;
 | 
			
		||||
                if(++quatiter != quatkeys.mKeys.end())
 | 
			
		||||
                    curquat = quatiter->mValue;
 | 
			
		||||
            }
 | 
			
		||||
            while(traniter != trankeys.mKeys.end() && curtime >= traniter->mTime)
 | 
			
		||||
            {
 | 
			
		||||
                lasttrans = curtrans;
 | 
			
		||||
                if(++traniter != trankeys.mKeys.end())
 | 
			
		||||
                    curtrans = traniter->mValue;
 | 
			
		||||
            }
 | 
			
		||||
            while(scaleiter != scalekeys.mKeys.end() && curtime >= scaleiter->mTime)
 | 
			
		||||
            {
 | 
			
		||||
                lastscale = curscale;
 | 
			
		||||
                if(++scaleiter != scalekeys.mKeys.end())
 | 
			
		||||
                    curscale = Ogre::Vector3(scaleiter->mValue);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Ogre::TransformKeyFrame *kframe;
 | 
			
		||||
            kframe = nodetrack->createNodeKeyFrame (next_timestamp);
 | 
			
		||||
 | 
			
		||||
            if (rci.hasData ()) kframe->setRotation  (rci.valueAt (next_timestamp));
 | 
			
		||||
            if (tci.hasData ()) kframe->setTranslate (tci.valueAt (next_timestamp));
 | 
			
		||||
            if (sci.hasData ()) kframe->setScale     (sci.valueAt (next_timestamp)*one);
 | 
			
		||||
 | 
			
		||||
            if (next_timestamp >= stopTime)
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            next_timestamp = min (stopTime,
 | 
			
		||||
                                  rci.nextTime (),
 | 
			
		||||
                                  tci.nextTime (),
 | 
			
		||||
                                  sci.nextTime ());
 | 
			
		||||
            kframe = nodetrack->createNodeKeyFrame(curtime);
 | 
			
		||||
            if(quatiter == quatkeys.mKeys.end() || quatiter == quatkeys.mKeys.begin())
 | 
			
		||||
                kframe->setRotation(curquat);
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                Nif::QuaternionKeyList::VecType::const_iterator last = quatiter-1;
 | 
			
		||||
                float diff = (curtime-last->mTime) / (quatiter->mTime-last->mTime);
 | 
			
		||||
                kframe->setRotation(Ogre::Quaternion::nlerp(diff, lastquat, curquat));
 | 
			
		||||
            }
 | 
			
		||||
            if(traniter == trankeys.mKeys.end() || traniter == trankeys.mKeys.begin())
 | 
			
		||||
                kframe->setTranslate(curtrans);
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                Nif::Vector3KeyList::VecType::const_iterator last = traniter-1;
 | 
			
		||||
                float diff = (curtime-last->mTime) / (traniter->mTime-last->mTime);
 | 
			
		||||
                kframe->setTranslate(lasttrans + ((curtrans-lasttrans)*diff));
 | 
			
		||||
            }
 | 
			
		||||
            if(scaleiter == scalekeys.mKeys.end() || scaleiter == scalekeys.mKeys.begin())
 | 
			
		||||
                kframe->setScale(curscale);
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                Nif::FloatKeyList::VecType::const_iterator last = scaleiter-1;
 | 
			
		||||
                float diff = (curtime-last->mTime) / (scaleiter->mTime-last->mTime);
 | 
			
		||||
                kframe->setScale(lastscale + ((curscale-lastscale)*diff));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    anim->optimise();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue