aboutsummaryrefslogtreecommitdiff
path: root/sp/src/game/client/interpolatedvar.h
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /sp/src/game/client/interpolatedvar.h
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'sp/src/game/client/interpolatedvar.h')
-rw-r--r--sp/src/game/client/interpolatedvar.h1591
1 files changed, 1591 insertions, 0 deletions
diff --git a/sp/src/game/client/interpolatedvar.h b/sp/src/game/client/interpolatedvar.h
new file mode 100644
index 00000000..d3177121
--- /dev/null
+++ b/sp/src/game/client/interpolatedvar.h
@@ -0,0 +1,1591 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef INTERPOLATEDVAR_H
+#define INTERPOLATEDVAR_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "tier1/utllinkedlist.h"
+#include "rangecheckedvar.h"
+#include "lerp_functions.h"
+#include "animationlayer.h"
+#include "convar.h"
+
+
+#include "tier0/memdbgon.h"
+
+#define COMPARE_HISTORY(a,b) \
+ ( memcmp( m_VarHistory[a].GetValue(), m_VarHistory[b].GetValue(), sizeof(Type)*GetMaxCount() ) == 0 )
+
+// Define this to have it measure whether or not the interpolated entity list
+// is accurate.
+//#define INTERPOLATEDVAR_PARANOID_MEASUREMENT
+
+
+#define LATCH_ANIMATION_VAR (1<<0) // use AnimTime as sample basis
+#define LATCH_SIMULATION_VAR (1<<1) // use SimulationTime as sample basis
+
+#define EXCLUDE_AUTO_LATCH (1<<2)
+#define EXCLUDE_AUTO_INTERPOLATE (1<<3)
+
+#define INTERPOLATE_LINEAR_ONLY (1<<4) // don't do hermite interpolation
+#define INTERPOLATE_OMIT_UPDATE_LAST_NETWORKED (1<<5)
+
+
+
+#define EXTRA_INTERPOLATION_HISTORY_STORED 0.05f // It stores this much extra interpolation history,
+ // so you can always call Interpolate() this far
+ // in the past from your last call and be able to
+ // get an interpolated value.
+
+// this global keeps the last known server packet tick (to avoid calling engine->GetLastTimestamp() all the time)
+extern float g_flLastPacketTimestamp;
+
+inline void Interpolation_SetLastPacketTimeStamp( float timestamp)
+{
+ Assert( timestamp > 0 );
+ g_flLastPacketTimestamp = timestamp;
+}
+
+
+// Before calling Interpolate(), you can use this use this to setup the context if
+// you want to enable extrapolation.
+class CInterpolationContext
+{
+public:
+
+ CInterpolationContext()
+ {
+ m_bOldAllowExtrapolation = s_bAllowExtrapolation;
+ m_flOldLastTimeStamp = s_flLastTimeStamp;
+
+ // By default, disable extrapolation unless they call EnableExtrapolation.
+ s_bAllowExtrapolation = false;
+
+ // this is the context stack
+ m_pNext = s_pHead;
+ s_pHead = this;
+ }
+
+ ~CInterpolationContext()
+ {
+ // restore values from prev stack element
+ s_bAllowExtrapolation = m_bOldAllowExtrapolation;
+ s_flLastTimeStamp = m_flOldLastTimeStamp;
+
+ Assert( s_pHead == this );
+ s_pHead = m_pNext;
+ }
+
+ static void EnableExtrapolation(bool state)
+ {
+ s_bAllowExtrapolation = state;
+ }
+
+ static bool IsThereAContext()
+ {
+ return s_pHead != NULL;
+ }
+
+ static bool IsExtrapolationAllowed()
+ {
+ return s_bAllowExtrapolation;
+ }
+
+ static void SetLastTimeStamp(float timestamp)
+ {
+ s_flLastTimeStamp = timestamp;
+ }
+
+ static float GetLastTimeStamp()
+ {
+ return s_flLastTimeStamp;
+ }
+
+
+private:
+
+ CInterpolationContext *m_pNext;
+ bool m_bOldAllowExtrapolation;
+ float m_flOldLastTimeStamp;
+
+ static CInterpolationContext *s_pHead;
+ static bool s_bAllowExtrapolation;
+ static float s_flLastTimeStamp;
+};
+
+
+extern ConVar cl_extrapolate_amount;
+
+
+template< class T >
+inline T ExtrapolateInterpolatedVarType( const T &oldVal, const T &newVal, float divisor, float flExtrapolationAmount )
+{
+ return newVal;
+}
+
+inline Vector ExtrapolateInterpolatedVarType( const Vector &oldVal, const Vector &newVal, float divisor, float flExtrapolationAmount )
+{
+ return Lerp( 1.0f + flExtrapolationAmount * divisor, oldVal, newVal );
+}
+
+inline float ExtrapolateInterpolatedVarType( const float &oldVal, const float &newVal, float divisor, float flExtrapolationAmount )
+{
+ return Lerp( 1.0f + flExtrapolationAmount * divisor, oldVal, newVal );
+}
+
+inline QAngle ExtrapolateInterpolatedVarType( const QAngle &oldVal, const QAngle &newVal, float divisor, float flExtrapolationAmount )
+{
+ return Lerp<QAngle>( 1.0f + flExtrapolationAmount * divisor, oldVal, newVal );
+}
+
+
+// -------------------------------------------------------------------------------------------------------------- //
+// IInterpolatedVar interface.
+// -------------------------------------------------------------------------------------------------------------- //
+
+abstract_class IInterpolatedVar
+{
+public:
+ virtual ~IInterpolatedVar() {}
+
+ virtual void Setup( void *pValue, int type ) = 0;
+ virtual void SetInterpolationAmount( float seconds ) = 0;
+
+ // Returns true if the new value is different from the prior most recent value.
+ virtual void NoteLastNetworkedValue() = 0;
+ virtual bool NoteChanged( float changetime, bool bUpdateLastNetworkedValue ) = 0;
+ virtual void Reset() = 0;
+
+ // Returns 1 if the value will always be the same if currentTime is always increasing.
+ virtual int Interpolate( float currentTime ) = 0;
+
+ virtual int GetType() const = 0;
+ virtual void RestoreToLastNetworked() = 0;
+ virtual void Copy( IInterpolatedVar *pSrc ) = 0;
+
+ virtual const char *GetDebugName() = 0;
+ virtual void SetDebugName( const char* pName ) = 0;
+
+ virtual void SetDebug( bool bDebug ) = 0;
+};
+
+template< typename Type, bool IS_ARRAY >
+struct CInterpolatedVarEntryBase
+{
+ CInterpolatedVarEntryBase()
+ {
+ value = NULL;
+ count = 0;
+ changetime = 0;
+ }
+ ~CInterpolatedVarEntryBase()
+ {
+ delete[] value;
+ value = NULL;
+ }
+
+ // This will transfer the data from another varentry. This is used to avoid allocation
+ // pointers can be transferred (only one varentry has a copy), but not trivially copied
+ void FastTransferFrom( CInterpolatedVarEntryBase &src )
+ {
+ Assert(!value);
+ value = src.value;
+ count = src.count;
+ changetime = src.changetime;
+ src.value = 0;
+ src.count = 0;
+ }
+
+ CInterpolatedVarEntryBase& operator=( const CInterpolatedVarEntryBase& src )
+ {
+ delete[] value;
+ value = NULL;
+ count = 0;
+ if ( src.value )
+ {
+ count = src.count;
+ value = new Type[count];
+ for ( int i = 0; i < count; i++ )
+ {
+ value[i] = src.value[i];
+ }
+ }
+ return *this;
+ }
+
+ Type *GetValue() { return value; }
+ const Type *GetValue() const { return value; }
+
+ void Init(int maxCount)
+ {
+ if ( !maxCount )
+ {
+ DeleteEntry();
+ }
+ else
+ {
+ // resize
+ if ( maxCount != count )
+ {
+ DeleteEntry();
+ }
+
+ if ( !value )
+ {
+ count = maxCount;
+ value = new Type[maxCount];
+ }
+ }
+ Assert(count==maxCount);
+ }
+ Type *NewEntry( const Type *pValue, int maxCount, float time )
+ {
+ changetime = time;
+ Init(maxCount);
+ if ( value && maxCount)
+ {
+ memcpy( value, pValue, maxCount*sizeof(Type) );
+ }
+ return value;
+ }
+
+ void DeleteEntry()
+ {
+ delete[] value;
+ value = NULL;
+ count = 0;
+ }
+
+ float changetime;
+ int count;
+ Type * value;
+
+private:
+ CInterpolatedVarEntryBase( const CInterpolatedVarEntryBase &src );
+};
+
+template<typename Type>
+struct CInterpolatedVarEntryBase<Type, false>
+{
+ CInterpolatedVarEntryBase() {}
+ ~CInterpolatedVarEntryBase() {}
+
+ const Type *GetValue() const { return &value; }
+ Type *GetValue() { return &value; }
+
+ void Init(int maxCount)
+ {
+ Assert(maxCount==1);
+ }
+ Type *NewEntry( const Type *pValue, int maxCount, float time )
+ {
+ Assert(maxCount==1);
+ changetime = time;
+ memcpy( &value, pValue, maxCount*sizeof(Type) );
+ return &value;
+ }
+ void FastTransferFrom( CInterpolatedVarEntryBase &src )
+ {
+ *this = src;
+ }
+
+ void DeleteEntry() {}
+
+ float changetime;
+ Type value;
+};
+
+template<typename T>
+class CSimpleRingBuffer
+{
+public:
+ CSimpleRingBuffer( int startSize = 4 )
+ {
+ m_pElements = 0;
+ m_maxElement = 0;
+ m_firstElement = 0;
+ m_count = 0;
+ m_growSize = 16;
+ EnsureCapacity(startSize);
+ }
+ ~CSimpleRingBuffer()
+ {
+ delete[] m_pElements;
+ m_pElements = NULL;
+ }
+
+ inline int Count() const { return m_count; }
+
+ int Head() const { return (m_count>0) ? 0 : InvalidIndex(); }
+
+ bool IsIdxValid( int i ) const { return (i >= 0 && i < m_count) ? true : false; }
+ bool IsValidIndex(int i) const { return IsIdxValid(i); }
+ static int InvalidIndex() { return -1; }
+
+ T& operator[]( int i )
+ {
+ Assert( IsIdxValid(i) );
+ i += m_firstElement;
+ i = WrapRange(i);
+ return m_pElements[i];
+ }
+
+ const T& operator[]( int i ) const
+ {
+ Assert( IsIdxValid(i) );
+ i += m_firstElement;
+ i = WrapRange(i);
+ return m_pElements[i];
+ }
+
+ void EnsureCapacity( int capSize )
+ {
+ if ( capSize > m_maxElement )
+ {
+ int newMax = m_maxElement + ((capSize+m_growSize-1)/m_growSize) * m_growSize;
+ T *pNew = new T[newMax];
+ for ( int i = 0; i < m_maxElement; i++ )
+ {
+ // ------------
+ // If you wanted to make this a more generic container you'd probably want this code
+ // instead - since FastTransferFrom() is an optimization dependent on types stored
+ // here defining this operation.
+ //pNew[i] = m_pElements[WrapRange(i+m_firstElement)];
+ pNew[i].FastTransferFrom( m_pElements[WrapRange(i+m_firstElement)] );
+ // ------------
+ }
+ m_firstElement = 0;
+ m_maxElement = newMax;
+ delete[] m_pElements;
+ m_pElements = pNew;
+ }
+ }
+
+ int AddToHead()
+ {
+ EnsureCapacity( m_count + 1 );
+ int i = m_firstElement + m_maxElement - 1;
+ m_count++;
+ i = WrapRange(i);
+ m_firstElement = i;
+ return 0;
+ }
+
+ int AddToHead( const T &elem )
+ {
+ AddToHead();
+ m_pElements[m_firstElement] = elem;
+ return 0;
+ }
+
+ int AddToTail()
+ {
+ EnsureCapacity( m_count + 1 );
+ m_count++;
+ return WrapRange(m_firstElement+m_count-1);
+ }
+
+ void RemoveAll()
+ {
+ m_count = 0;
+ m_firstElement = 0;
+ }
+
+ void RemoveAtHead()
+ {
+ if ( m_count > 0 )
+ {
+ m_firstElement = WrapRange(m_firstElement+1);
+ m_count--;
+ }
+ }
+
+ void Truncate( int newLength )
+ {
+ if ( newLength < m_count )
+ {
+ Assert(newLength>=0);
+ m_count = newLength;
+ }
+ }
+
+private:
+ inline int WrapRange( int i ) const
+ {
+ return ( i >= m_maxElement ) ? (i - m_maxElement) : i;
+ }
+
+ T *m_pElements;
+ unsigned short m_maxElement;
+ unsigned short m_firstElement;
+ unsigned short m_count;
+ unsigned short m_growSize;
+};
+
+// -------------------------------------------------------------------------------------------------------------- //
+// CInterpolatedVarArrayBase - the main implementation of IInterpolatedVar.
+// -------------------------------------------------------------------------------------------------------------- //
+
+template< typename Type, bool IS_ARRAY>
+class CInterpolatedVarArrayBase : public IInterpolatedVar
+{
+public:
+ friend class CInterpolatedVarPrivate;
+
+ CInterpolatedVarArrayBase( const char *pDebugName="no debug name" );
+ virtual ~CInterpolatedVarArrayBase();
+
+
+ // IInterpolatedVar overrides.
+public:
+
+ virtual void Setup( void *pValue, int type );
+ virtual void SetInterpolationAmount( float seconds );
+ virtual void NoteLastNetworkedValue();
+ virtual bool NoteChanged( float changetime, bool bUpdateLastNetworkedValue );
+ virtual void Reset();
+ virtual int Interpolate( float currentTime );
+ virtual int GetType() const;
+ virtual void RestoreToLastNetworked();
+ virtual void Copy( IInterpolatedVar *pInSrc );
+ virtual const char *GetDebugName() { return m_pDebugName; }
+
+
+public:
+
+ // Just like the IInterpolatedVar functions, but you can specify an interpolation amount.
+ bool NoteChanged( float changetime, float interpolation_amount, bool bUpdateLastNetworkedValue );
+ int Interpolate( float currentTime, float interpolation_amount );
+
+ void DebugInterpolate( Type *pOut, float currentTime );
+
+ void GetDerivative( Type *pOut, float currentTime );
+ void GetDerivative_SmoothVelocity( Type *pOut, float currentTime ); // See notes on ::Derivative_HermiteLinearVelocity for info.
+
+ void ClearHistory();
+ void AddToHead( float changeTime, const Type* values, bool bFlushNewer );
+ const Type& GetPrev( int iArrayIndex=0 ) const;
+ const Type& GetCurrent( int iArrayIndex=0 ) const;
+
+ // Returns the time difference betweem the most recent sample and its previous sample.
+ float GetInterval() const;
+ bool IsValidIndex( int i );
+ Type *GetHistoryValue( int index, float& changetime, int iArrayIndex=0 );
+ int GetHead() { return 0; }
+ int GetNext( int i )
+ {
+ int next = i + 1;
+ if ( !m_VarHistory.IsValidIndex(next) )
+ return m_VarHistory.InvalidIndex();
+ return next;
+ }
+
+ void SetHistoryValuesForItem( int item, Type& value );
+ void SetLooping( bool looping, int iArrayIndex=0 );
+
+ void SetMaxCount( int newmax );
+ int GetMaxCount() const;
+
+ // Get the time of the oldest entry.
+ float GetOldestEntry();
+
+ // set a debug name (if not provided by constructor)
+ void SetDebugName(const char *pName ) { m_pDebugName = pName; }
+ virtual void SetDebug( bool bDebug ) { m_bDebug = bDebug; }
+ bool GetInterpolationInfo( float currentTime, int *pNewer, int *pOlder, int *pOldest );
+
+protected:
+
+ typedef CInterpolatedVarEntryBase<Type, IS_ARRAY> CInterpolatedVarEntry;
+ typedef CSimpleRingBuffer< CInterpolatedVarEntry > CVarHistory;
+ friend class CInterpolationInfo;
+
+ class CInterpolationInfo
+ {
+ public:
+ bool m_bHermite;
+ int oldest; // Only set if using hermite.
+ int older;
+ int newer;
+ float frac;
+ };
+
+
+protected:
+
+ void RemoveOldEntries( float oldesttime );
+ void RemoveEntriesPreviousTo( float flTime );
+
+ bool GetInterpolationInfo(
+ CInterpolationInfo *pInfo,
+ float currentTime,
+ float interpolation_amount,
+ int *pNoMoreChanges );
+
+ void TimeFixup_Hermite(
+ CInterpolatedVarEntry &fixup,
+ CInterpolatedVarEntry*& prev,
+ CInterpolatedVarEntry*& start,
+ CInterpolatedVarEntry*& end );
+
+ // Force the time between prev and start to be dt (and extend prev out farther if necessary).
+ void TimeFixup2_Hermite(
+ CInterpolatedVarEntry &fixup,
+ CInterpolatedVarEntry*& prev,
+ CInterpolatedVarEntry*& start,
+ float dt
+ );
+
+ void _Extrapolate(
+ Type *pOut,
+ CInterpolatedVarEntry *pOld,
+ CInterpolatedVarEntry *pNew,
+ float flDestinationTime,
+ float flMaxExtrapolationAmount
+ );
+
+ void _Interpolate( Type *out, float frac, CInterpolatedVarEntry *start, CInterpolatedVarEntry *end );
+ void _Interpolate_Hermite( Type *out, float frac, CInterpolatedVarEntry *pOriginalPrev, CInterpolatedVarEntry *start, CInterpolatedVarEntry *end, bool looping = false );
+
+ void _Derivative_Hermite( Type *out, float frac, CInterpolatedVarEntry *pOriginalPrev, CInterpolatedVarEntry *start, CInterpolatedVarEntry *end );
+ void _Derivative_Hermite_SmoothVelocity( Type *out, float frac, CInterpolatedVarEntry *b, CInterpolatedVarEntry *c, CInterpolatedVarEntry *d );
+ void _Derivative_Linear( Type *out, CInterpolatedVarEntry *start, CInterpolatedVarEntry *end );
+
+ bool ValidOrder();
+
+protected:
+ // The underlying data element
+ Type *m_pValue;
+ CVarHistory m_VarHistory;
+ // Store networked values so when we latch we can detect which values were changed via networking
+ Type * m_LastNetworkedValue;
+ float m_LastNetworkedTime;
+ byte m_fType;
+ byte m_nMaxCount;
+ byte * m_bLooping;
+ float m_InterpolationAmount;
+ const char * m_pDebugName;
+ bool m_bDebug : 1;
+};
+
+
+template< typename Type, bool IS_ARRAY >
+inline CInterpolatedVarArrayBase<Type, IS_ARRAY>::CInterpolatedVarArrayBase( const char *pDebugName )
+{
+ m_pDebugName = pDebugName;
+ m_pValue = NULL;
+ m_fType = LATCH_ANIMATION_VAR;
+ m_InterpolationAmount = 0.0f;
+ m_nMaxCount = 0;
+ m_LastNetworkedTime = 0;
+ m_LastNetworkedValue = NULL;
+ m_bLooping = NULL;
+ m_bDebug = false;
+}
+
+template< typename Type, bool IS_ARRAY >
+inline CInterpolatedVarArrayBase<Type, IS_ARRAY>::~CInterpolatedVarArrayBase()
+{
+ ClearHistory();
+ delete [] m_bLooping;
+ delete [] m_LastNetworkedValue;
+}
+
+template< typename Type, bool IS_ARRAY >
+inline void CInterpolatedVarArrayBase<Type, IS_ARRAY>::Setup( void *pValue, int type )
+{
+ m_pValue = ( Type * )pValue;
+ m_fType = type;
+}
+
+template< typename Type, bool IS_ARRAY >
+inline void CInterpolatedVarArrayBase<Type, IS_ARRAY>::SetInterpolationAmount( float seconds )
+{
+ m_InterpolationAmount = seconds;
+}
+
+template< typename Type, bool IS_ARRAY >
+inline int CInterpolatedVarArrayBase<Type, IS_ARRAY>::GetType() const
+{
+ return m_fType;
+}
+
+template< typename Type, bool IS_ARRAY >
+void CInterpolatedVarArrayBase<Type, IS_ARRAY>::NoteLastNetworkedValue()
+{
+ memcpy( m_LastNetworkedValue, m_pValue, m_nMaxCount * sizeof( Type ) );
+ m_LastNetworkedTime = g_flLastPacketTimestamp;
+}
+
+template< typename Type, bool IS_ARRAY >
+inline bool CInterpolatedVarArrayBase<Type, IS_ARRAY>::NoteChanged( float changetime, float interpolation_amount, bool bUpdateLastNetworkedValue )
+{
+ Assert( m_pValue );
+
+ // This is a big optimization where it can potentially avoid expensive interpolation
+ // involving this variable if it didn't get an actual new value in here.
+ bool bRet = true;
+ if ( m_VarHistory.Count() )
+ {
+ if ( memcmp( m_pValue, m_VarHistory[0].GetValue(), sizeof( Type ) * m_nMaxCount ) == 0 )
+ {
+ bRet = false;
+ }
+ }
+
+ if ( m_bDebug )
+ {
+ char const *pDiffString = bRet ? "differs" : "identical";
+
+ Msg( "%s LatchChanged at %f changetime %f: %s\n", GetDebugName(), gpGlobals->curtime, changetime, pDiffString );
+ }
+
+ AddToHead( changetime, m_pValue, true );
+
+ if ( bUpdateLastNetworkedValue )
+ {
+ NoteLastNetworkedValue();
+ }
+
+#if 0
+ // Since we don't clean out the old entries until Interpolate(), make sure that there
+ // aren't any super old entries hanging around.
+ RemoveOldEntries( gpGlobals->curtime - interpolation_amount - 2.0f );
+#else
+ // JAY: It doesn't seem like the above code is correct. This is keeping more than two seconds of history
+ // for variables that aren't being interpolated for some reason. For example, the player model isn't drawn
+ // in first person, so the history is only truncated here and will accumulate ~40 entries instead of 2 or 3
+ // changing over to the method in Interpolate() means that we always have a 3-sample neighborhood around
+ // any data we're going to need. Unless gpGlobals->curtime is different when samples are added vs. when
+ // they are interpolated I can't see this having any ill effects.
+ RemoveEntriesPreviousTo( gpGlobals->curtime - interpolation_amount - EXTRA_INTERPOLATION_HISTORY_STORED );
+#endif
+
+ return bRet;
+}
+
+
+template< typename Type, bool IS_ARRAY >
+inline bool CInterpolatedVarArrayBase<Type, IS_ARRAY>::NoteChanged( float changetime, bool bUpdateLastNetworkedValue )
+{
+ return NoteChanged( changetime, m_InterpolationAmount, bUpdateLastNetworkedValue );
+}
+
+
+template< typename Type, bool IS_ARRAY >
+inline void CInterpolatedVarArrayBase<Type, IS_ARRAY>::RestoreToLastNetworked()
+{
+ Assert( m_pValue );
+ memcpy( m_pValue, m_LastNetworkedValue, m_nMaxCount * sizeof( Type ) );
+}
+
+template< typename Type, bool IS_ARRAY >
+inline void CInterpolatedVarArrayBase<Type, IS_ARRAY>::ClearHistory()
+{
+ for ( int i = 0; i < m_VarHistory.Count(); i++ )
+ {
+ m_VarHistory[i].DeleteEntry();
+ }
+ m_VarHistory.RemoveAll();
+}
+
+template< typename Type, bool IS_ARRAY >
+inline void CInterpolatedVarArrayBase<Type, IS_ARRAY>::AddToHead( float changeTime, const Type* values, bool bFlushNewer )
+{
+ MEM_ALLOC_CREDIT_CLASS();
+ int newslot;
+
+ if ( bFlushNewer )
+ {
+ // Get rid of anything that has a timestamp after this sample. The server might have
+ // corrected our clock and moved us back, so our current changeTime is less than a
+ // changeTime we added samples during previously.
+ while ( m_VarHistory.Count() )
+ {
+ if ( (m_VarHistory[0].changetime+0.0001f) > changeTime )
+ {
+ m_VarHistory.RemoveAtHead();
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ newslot = m_VarHistory.AddToHead();
+ }
+ else
+ {
+ newslot = m_VarHistory.AddToHead();
+ for ( int i = 1; i < m_VarHistory.Count(); i++ )
+ {
+ if ( m_VarHistory[i].changetime <= changeTime )
+ break;
+ m_VarHistory[newslot].FastTransferFrom( m_VarHistory[i] );
+ newslot = i;
+ }
+ }
+
+ CInterpolatedVarEntry *e = &m_VarHistory[ newslot ];
+ e->NewEntry( values, m_nMaxCount, changeTime );
+}
+
+template< typename Type, bool IS_ARRAY >
+inline void CInterpolatedVarArrayBase<Type, IS_ARRAY>::Reset()
+{
+ ClearHistory();
+
+ if ( m_pValue )
+ {
+ AddToHead( gpGlobals->curtime, m_pValue, false );
+ AddToHead( gpGlobals->curtime, m_pValue, false );
+ AddToHead( gpGlobals->curtime, m_pValue, false );
+
+ memcpy( m_LastNetworkedValue, m_pValue, m_nMaxCount * sizeof( Type ) );
+ }
+}
+
+
+template< typename Type, bool IS_ARRAY >
+inline float CInterpolatedVarArrayBase<Type, IS_ARRAY>::GetOldestEntry()
+{
+ float lastVal = 0;
+ if ( m_VarHistory.Count() )
+ {
+ lastVal = m_VarHistory[m_VarHistory.Count()-1].changetime;
+ }
+ return lastVal;
+}
+
+
+template< typename Type, bool IS_ARRAY >
+inline void CInterpolatedVarArrayBase<Type, IS_ARRAY>::RemoveOldEntries( float oldesttime )
+{
+ int newCount = m_VarHistory.Count();
+ for ( int i = m_VarHistory.Count(); --i > 2; )
+ {
+ if ( m_VarHistory[i].changetime > oldesttime )
+ break;
+ newCount = i;
+ }
+ m_VarHistory.Truncate(newCount);
+}
+
+
+template< typename Type, bool IS_ARRAY >
+inline void CInterpolatedVarArrayBase<Type, IS_ARRAY>::RemoveEntriesPreviousTo( float flTime )
+{
+ for ( int i = 0; i < m_VarHistory.Count(); i++ )
+ {
+ if ( m_VarHistory[i].changetime < flTime )
+ {
+ // We need to preserve this sample (ie: the one right before this timestamp)
+ // and the sample right before it (for hermite blending), and we can get rid
+ // of everything else.
+ m_VarHistory.Truncate( i + 3 );
+ break;
+ }
+ }
+}
+
+
+template< typename Type, bool IS_ARRAY >
+inline bool CInterpolatedVarArrayBase<Type, IS_ARRAY>::GetInterpolationInfo(
+ typename CInterpolatedVarArrayBase<Type, IS_ARRAY>::CInterpolationInfo *pInfo,
+ float currentTime,
+ float interpolation_amount,
+ int *pNoMoreChanges
+ )
+{
+ Assert( m_pValue );
+
+ CVarHistory &varHistory = m_VarHistory;
+
+ float targettime = currentTime - interpolation_amount;
+
+ pInfo->m_bHermite = false;
+ pInfo->frac = 0;
+ pInfo->oldest = pInfo->older = pInfo->newer = varHistory.InvalidIndex();
+
+ for ( int i = 0; i < varHistory.Count(); i++ )
+ {
+ pInfo->older = i;
+
+ float older_change_time = m_VarHistory[ i ].changetime;
+ if ( older_change_time == 0.0f )
+ break;
+
+ if ( targettime < older_change_time )
+ {
+ pInfo->newer = pInfo->older;
+ continue;
+ }
+
+ if ( pInfo->newer == varHistory.InvalidIndex() )
+ {
+ // Have it linear interpolate between the newest 2 entries.
+ pInfo->newer = pInfo->older;
+
+ // Since the time given is PAST all of our entries, then as long
+ // as time continues to increase, we'll be returning the same value.
+ if ( pNoMoreChanges )
+ *pNoMoreChanges = 1;
+ return true;
+ }
+
+ float newer_change_time = varHistory[ pInfo->newer ].changetime;
+ float dt = newer_change_time - older_change_time;
+ if ( dt > 0.0001f )
+ {
+ pInfo->frac = ( targettime - older_change_time ) / ( newer_change_time - older_change_time );
+ pInfo->frac = MIN( pInfo->frac, 2.0f );
+
+ int oldestindex = i+1;
+
+ if ( !(m_fType & INTERPOLATE_LINEAR_ONLY) && varHistory.IsIdxValid(oldestindex) )
+ {
+ pInfo->oldest = oldestindex;
+ float oldest_change_time = varHistory[ oldestindex ].changetime;
+ float dt2 = older_change_time - oldest_change_time;
+ if ( dt2 > 0.0001f )
+ {
+ pInfo->m_bHermite = true;
+ }
+ }
+
+ // If pInfo->newer is the most recent entry we have, and all 2 or 3 other
+ // entries are identical, then we're always going to return the same value
+ // if currentTime increases.
+ if ( pNoMoreChanges && pInfo->newer == m_VarHistory.Head() )
+ {
+ if ( COMPARE_HISTORY( pInfo->newer, pInfo->older ) )
+ {
+ if ( !pInfo->m_bHermite || COMPARE_HISTORY( pInfo->newer, pInfo->oldest ) )
+ *pNoMoreChanges = 1;
+ }
+ }
+ }
+ return true;
+ }
+
+ // Didn't find any, return last entry???
+ if ( pInfo->newer != varHistory.InvalidIndex() )
+ {
+ pInfo->older = pInfo->newer;
+ return true;
+ }
+
+
+ // This is the single-element case
+ pInfo->newer = pInfo->older;
+ return (pInfo->older != varHistory.InvalidIndex());
+}
+
+
+template< typename Type, bool IS_ARRAY >
+inline bool CInterpolatedVarArrayBase<Type, IS_ARRAY>::GetInterpolationInfo( float currentTime, int *pNewer, int *pOlder, int *pOldest )
+{
+ CInterpolationInfo info;
+ bool result = GetInterpolationInfo( &info, currentTime, m_InterpolationAmount, NULL );
+
+ if (pNewer)
+ *pNewer = (int)info.newer;
+
+ if (pOlder)
+ *pOlder = (int)info.older;
+
+ if (pOldest)
+ *pOldest = (int)info.oldest;
+
+ return result;
+}
+
+
+template< typename Type, bool IS_ARRAY >
+inline void CInterpolatedVarArrayBase<Type, IS_ARRAY>::DebugInterpolate( Type *pOut, float currentTime )
+{
+ float interpolation_amount = m_InterpolationAmount;
+
+ int noMoreChanges = 0;
+
+ CInterpolationInfo info;
+ GetInterpolationInfo( &info, currentTime, interpolation_amount, &noMoreChanges );
+
+ CVarHistory &history = m_VarHistory;
+
+ if ( info.m_bHermite )
+ {
+ // base cast, we have 3 valid sample point
+ _Interpolate_Hermite( pOut, info.frac, &history[info.oldest], &history[info.older], &history[info.newer] );
+ }
+ else if ( info.newer == info.older )
+ {
+ // This means the server clock got way behind the client clock. Extrapolate the value here based on its
+ // previous velocity (out to a certain amount).
+ int realOlder = info.newer+1;
+ if ( CInterpolationContext::IsExtrapolationAllowed() &&
+ IsValidIndex( realOlder ) &&
+ history[realOlder].changetime != 0.0 &&
+ interpolation_amount > 0.000001f &&
+ CInterpolationContext::GetLastTimeStamp() <= m_LastNetworkedTime )
+ {
+ // At this point, we know we're out of data and we have the ability to get a velocity to extrapolate with.
+ //
+ // However, we only want to extraploate if the server is choking. We don't want to extrapolate if
+ // the object legimately stopped moving and the server stopped sending updates for it.
+ //
+ // The way we know that the server is choking is if we haven't heard ANYTHING from it for a while.
+ // The server's update interval should be at least as often as our interpolation amount (otherwise,
+ // we wouldn't have the ability to interpolate).
+ //
+ // So right here, if we see that we haven't gotten any server updates since the last interpolation
+ // history update to this entity (and since we're in here, we know that we're out of interpolation data),
+ // then we can assume that the server is choking and decide to extrapolate.
+ //
+ // The End
+
+ // Use the velocity here (extrapolate up to 1/4 of a second).
+ _Extrapolate( pOut, &history[realOlder], &history[info.newer], currentTime - interpolation_amount, cl_extrapolate_amount.GetFloat() );
+ }
+ else
+ {
+ _Interpolate( pOut, info.frac, &history[info.older], &history[info.newer] );
+ }
+ }
+ else
+ {
+ _Interpolate( pOut, info.frac, &history[info.older], &history[info.newer] );
+ }
+}
+
+template< typename Type, bool IS_ARRAY >
+inline int CInterpolatedVarArrayBase<Type, IS_ARRAY>::Interpolate( float currentTime, float interpolation_amount )
+{
+ int noMoreChanges = 0;
+
+ CInterpolationInfo info;
+ if (!GetInterpolationInfo( &info, currentTime, interpolation_amount, &noMoreChanges ))
+ return noMoreChanges;
+
+
+ CVarHistory &history = m_VarHistory;
+
+ if ( m_bDebug )
+ {
+ // "value will hold" means we are either extrapolating, or the samples in GetInterpolationInfo are all the same... In either case there are no more "changes" until we latch a new
+ // value and we can remove this var from the interpolated var list (bit perf optimization)
+ Msg( "%s Interpolate at %f%s\n", GetDebugName(), currentTime, noMoreChanges ? " [value will hold]" : "" );
+ }
+
+
+#ifdef INTERPOLATEDVAR_PARANOID_MEASUREMENT
+ Type *backupValues = (Type*)_alloca( m_nMaxCount * sizeof(Type) );
+ memcpy( backupValues, m_pValue, sizeof( Type ) * m_nMaxCount );
+#endif
+
+ if ( info.m_bHermite )
+ {
+ // base cast, we have 3 valid sample point
+ _Interpolate_Hermite( m_pValue, info.frac, &history[info.oldest], &history[info.older], &history[info.newer] );
+ }
+ else if ( info.newer == info.older )
+ {
+ // This means the server clock got way behind the client clock. Extrapolate the value here based on its
+ // previous velocity (out to a certain amount).
+ int realOlder = info.newer+1;
+ if ( CInterpolationContext::IsExtrapolationAllowed() &&
+ IsValidIndex( realOlder ) &&
+ history[realOlder].changetime != 0.0 &&
+ interpolation_amount > 0.000001f &&
+ CInterpolationContext::GetLastTimeStamp() <= m_LastNetworkedTime )
+ {
+ // At this point, we know we're out of data and we have the ability to get a velocity to extrapolate with.
+ //
+ // However, we only want to extraploate if the server is choking. We don't want to extrapolate if
+ // the object legimately stopped moving and the server stopped sending updates for it.
+ //
+ // The way we know that the server is choking is if we haven't heard ANYTHING from it for a while.
+ // The server's update interval should be at least as often as our interpolation amount (otherwise,
+ // we wouldn't have the ability to interpolate).
+ //
+ // So right here, if we see that we haven't gotten any server updates since the last interpolation
+ // history update to this entity (and since we're in here, we know that we're out of interpolation data),
+ // then we can assume that the server is choking and decide to extrapolate.
+ //
+ // The End
+
+ // Use the velocity here (extrapolate up to 1/4 of a second).
+ _Extrapolate( m_pValue, &history[realOlder], &history[info.newer], currentTime - interpolation_amount, cl_extrapolate_amount.GetFloat() );
+ }
+ else
+ {
+ _Interpolate( m_pValue, info.frac, &history[info.older], &history[info.newer] );
+ }
+ }
+ else
+ {
+ _Interpolate( m_pValue, info.frac, &history[info.older], &history[info.newer] );
+ }
+
+#ifdef INTERPOLATEDVAR_PARANOID_MEASUREMENT
+ if ( memcmp( backupValues, m_pValue, sizeof( Type ) * m_nMaxCount ) != 0 )
+ {
+ extern int g_nInterpolatedVarsChanged;
+ extern bool g_bRestoreInterpolatedVarValues;
+
+ ++g_nInterpolatedVarsChanged;
+
+ // This undoes the work that we do in here so if someone is in the debugger, they
+ // can find out which variable changed.
+ if ( g_bRestoreInterpolatedVarValues )
+ {
+ memcpy( m_pValue, backupValues, sizeof( Type ) * m_nMaxCount );
+ return noMoreChanges;
+ }
+ }
+#endif
+
+ // Clear out all entries before the oldest since we should never access them again.
+ // Usually, Interpolate() calls never go backwards in time, but C_BaseAnimating::BecomeRagdollOnClient for one
+ // goes slightly back in time
+ RemoveEntriesPreviousTo( currentTime - interpolation_amount - EXTRA_INTERPOLATION_HISTORY_STORED );
+ return noMoreChanges;
+}
+
+
+template< typename Type, bool IS_ARRAY >
+void CInterpolatedVarArrayBase<Type, IS_ARRAY>::GetDerivative( Type *pOut, float currentTime )
+{
+ CInterpolationInfo info;
+ if (!GetInterpolationInfo( &info, currentTime, m_InterpolationAmount, NULL ))
+ return;
+
+ if ( info.m_bHermite )
+ {
+ _Derivative_Hermite( pOut, info.frac, &m_VarHistory[info.oldest], &m_VarHistory[info.older], &m_VarHistory[info.newer] );
+ }
+ else
+ {
+ _Derivative_Linear( pOut, &m_VarHistory[info.older], &m_VarHistory[info.newer] );
+ }
+}
+
+
+template< typename Type, bool IS_ARRAY >
+void CInterpolatedVarArrayBase<Type, IS_ARRAY>::GetDerivative_SmoothVelocity( Type *pOut, float currentTime )
+{
+ CInterpolationInfo info;
+ if (!GetInterpolationInfo( &info, currentTime, m_InterpolationAmount, NULL ))
+ return;
+
+ CVarHistory &history = m_VarHistory;
+ bool bExtrapolate = false;
+ int realOlder = 0;
+
+ if ( info.m_bHermite )
+ {
+ _Derivative_Hermite_SmoothVelocity( pOut, info.frac, &history[info.oldest], &history[info.older], &history[info.newer] );
+ return;
+ }
+ else if ( info.newer == info.older && CInterpolationContext::IsExtrapolationAllowed() )
+ {
+ // This means the server clock got way behind the client clock. Extrapolate the value here based on its
+ // previous velocity (out to a certain amount).
+ realOlder = info.newer+1;
+ if ( IsValidIndex( realOlder ) && history[realOlder].changetime != 0.0 )
+ {
+ // At this point, we know we're out of data and we have the ability to get a velocity to extrapolate with.
+ //
+ // However, we only want to extraploate if the server is choking. We don't want to extrapolate if
+ // the object legimately stopped moving and the server stopped sending updates for it.
+ //
+ // The way we know that the server is choking is if we haven't heard ANYTHING from it for a while.
+ // The server's update interval should be at least as often as our interpolation amount (otherwise,
+ // we wouldn't have the ability to interpolate).
+ //
+ // So right here, if we see that we haven't gotten any server updates for a whole interpolation
+ // interval, then we know the server is choking.
+ //
+ // The End
+ if ( m_InterpolationAmount > 0.000001f &&
+ CInterpolationContext::GetLastTimeStamp() <= (currentTime - m_InterpolationAmount) )
+ {
+ bExtrapolate = true;
+ }
+ }
+ }
+
+ if ( bExtrapolate )
+ {
+ // Get the velocity from the last segment.
+ _Derivative_Linear( pOut, &history[realOlder], &history[info.newer] );
+
+ // Now ramp it to zero after cl_extrapolate_amount..
+ float flDestTime = currentTime - m_InterpolationAmount;
+ float diff = flDestTime - history[info.newer].changetime;
+ diff = clamp( diff, 0.f, cl_extrapolate_amount.GetFloat() * 2 );
+ if ( diff > cl_extrapolate_amount.GetFloat() )
+ {
+ float scale = 1 - (diff - cl_extrapolate_amount.GetFloat()) / cl_extrapolate_amount.GetFloat();
+ for ( int i=0; i < m_nMaxCount; i++ )
+ {
+ pOut[i] *= scale;
+ }
+ }
+ }
+ else
+ {
+ _Derivative_Linear( pOut, &history[info.older], &history[info.newer] );
+ }
+
+}
+
+
+template< typename Type, bool IS_ARRAY >
+inline int CInterpolatedVarArrayBase<Type, IS_ARRAY>::Interpolate( float currentTime )
+{
+ return Interpolate( currentTime, m_InterpolationAmount );
+}
+
+template< typename Type, bool IS_ARRAY >
+inline void CInterpolatedVarArrayBase<Type, IS_ARRAY>::Copy( IInterpolatedVar *pInSrc )
+{
+ CInterpolatedVarArrayBase<Type, IS_ARRAY> *pSrc = dynamic_cast< CInterpolatedVarArrayBase<Type, IS_ARRAY>* >( pInSrc );
+
+ if ( !pSrc || pSrc->m_nMaxCount != m_nMaxCount )
+ {
+ if ( pSrc )
+ {
+ AssertMsg3( false, "pSrc->m_nMaxCount (%i) != m_nMaxCount (%i) for %s.", pSrc->m_nMaxCount, m_nMaxCount, m_pDebugName);
+ }
+ else
+ {
+ AssertMsg( false, "pSrc was null in CInterpolatedVarArrayBase<Type, IS_ARRAY>::Copy.");
+ }
+
+ return;
+ }
+
+ Assert( (m_fType & ~EXCLUDE_AUTO_INTERPOLATE) == (pSrc->m_fType & ~EXCLUDE_AUTO_INTERPOLATE) );
+ Assert( m_pDebugName == pSrc->GetDebugName() );
+
+ for ( int i=0; i < m_nMaxCount; i++ )
+ {
+ m_LastNetworkedValue[i] = pSrc->m_LastNetworkedValue[i];
+ m_bLooping[i] = pSrc->m_bLooping[i];
+ }
+
+ m_LastNetworkedTime = pSrc->m_LastNetworkedTime;
+
+ // Copy the entries.
+ m_VarHistory.RemoveAll();
+
+ for ( int i = 0; i < pSrc->m_VarHistory.Count(); i++ )
+ {
+ int newslot = m_VarHistory.AddToTail();
+
+ CInterpolatedVarEntry *dest = &m_VarHistory[newslot];
+ CInterpolatedVarEntry *src = &pSrc->m_VarHistory[i];
+ dest->NewEntry( src->GetValue(), m_nMaxCount, src->changetime );
+ }
+}
+
+template< typename Type, bool IS_ARRAY >
+inline const Type& CInterpolatedVarArrayBase<Type, IS_ARRAY>::GetPrev( int iArrayIndex ) const
+{
+ Assert( m_pValue );
+ Assert( iArrayIndex >= 0 && iArrayIndex < m_nMaxCount );
+
+ if ( m_VarHistory.Count() > 1 )
+ {
+ return m_VarHistory[1].GetValue()[iArrayIndex];
+ }
+ return m_pValue[ iArrayIndex ];
+}
+
+template< typename Type, bool IS_ARRAY >
+inline const Type& CInterpolatedVarArrayBase<Type, IS_ARRAY>::GetCurrent( int iArrayIndex ) const
+{
+ Assert( m_pValue );
+ Assert( iArrayIndex >= 0 && iArrayIndex < m_nMaxCount );
+
+ if ( m_VarHistory.Count() > 0 )
+ {
+ return m_VarHistory[0].GetValue()[iArrayIndex];
+ }
+ return m_pValue[ iArrayIndex ];
+}
+
+template< typename Type, bool IS_ARRAY >
+inline float CInterpolatedVarArrayBase<Type, IS_ARRAY>::GetInterval() const
+{
+ if ( m_VarHistory.Count() > 1 )
+ {
+ return m_VarHistory[0].changetime - m_VarHistory[1].changetime;
+ }
+
+ return 0.0f;
+}
+
+template< typename Type, bool IS_ARRAY >
+inline bool CInterpolatedVarArrayBase<Type, IS_ARRAY>::IsValidIndex( int i )
+{
+ return m_VarHistory.IsValidIndex( i );
+}
+
+template< typename Type, bool IS_ARRAY >
+inline Type *CInterpolatedVarArrayBase<Type, IS_ARRAY>::GetHistoryValue( int index, float& changetime, int iArrayIndex )
+{
+ Assert( iArrayIndex >= 0 && iArrayIndex < m_nMaxCount );
+ if ( m_VarHistory.IsIdxValid(index) )
+ {
+ CInterpolatedVarEntry *entry = &m_VarHistory[ index ];
+ changetime = entry->changetime;
+ return &entry->GetValue()[ iArrayIndex ];
+ }
+ else
+ {
+ changetime = 0.0f;
+ return NULL;
+ }
+}
+
+template< typename Type, bool IS_ARRAY >
+inline void CInterpolatedVarArrayBase<Type, IS_ARRAY>::SetHistoryValuesForItem( int item, Type& value )
+{
+ Assert( item >= 0 && item < m_nMaxCount );
+
+ for ( int i = 0; i < m_VarHistory.Count(); i++ )
+ {
+ CInterpolatedVarEntry *entry = &m_VarHistory[ i ];
+ entry->GetValue()[ item ] = value;
+ }
+}
+
+template< typename Type, bool IS_ARRAY >
+inline void CInterpolatedVarArrayBase<Type, IS_ARRAY>::SetLooping( bool looping, int iArrayIndex )
+{
+ Assert( iArrayIndex >= 0 && iArrayIndex < m_nMaxCount );
+ m_bLooping[ iArrayIndex ] = looping;
+}
+
+template< typename Type, bool IS_ARRAY >
+inline void CInterpolatedVarArrayBase<Type, IS_ARRAY>::SetMaxCount( int newmax )
+{
+ bool changed = ( newmax != m_nMaxCount ) ? true : false;
+
+ // BUGBUG: Support 0 length properly?
+ newmax = MAX(1,newmax);
+
+ m_nMaxCount = newmax;
+ // Wipe everything any time this changes!!!
+ if ( changed )
+ {
+ delete [] m_bLooping;
+ delete [] m_LastNetworkedValue;
+ m_bLooping = new byte[m_nMaxCount];
+ m_LastNetworkedValue = new Type[m_nMaxCount];
+ memset( m_bLooping, 0, sizeof(byte) * m_nMaxCount);
+ memset( m_LastNetworkedValue, 0, sizeof(Type) * m_nMaxCount);
+
+ Reset();
+ }
+}
+
+
+template< typename Type, bool IS_ARRAY >
+inline int CInterpolatedVarArrayBase<Type, IS_ARRAY>::GetMaxCount() const
+{
+ return m_nMaxCount;
+}
+
+
+template< typename Type, bool IS_ARRAY >
+inline void CInterpolatedVarArrayBase<Type, IS_ARRAY>::_Interpolate( Type *out, float frac, CInterpolatedVarEntry *start, CInterpolatedVarEntry *end )
+{
+ Assert( start );
+ Assert( end );
+
+ if ( start == end )
+ {
+ // quick exit
+ for ( int i = 0; i < m_nMaxCount; i++ )
+ {
+ out[i] = end->GetValue()[i];
+ Lerp_Clamp( out[i] );
+ }
+ return;
+ }
+
+ Assert( frac >= 0.0f && frac <= 1.0f );
+
+ // Note that QAngle has a specialization that will do quaternion interpolation here...
+ for ( int i = 0; i < m_nMaxCount; i++ )
+ {
+ if ( m_bLooping[ i ] )
+ {
+ out[i] = LoopingLerp( frac, start->GetValue()[i], end->GetValue()[i] );
+ }
+ else
+ {
+ out[i] = Lerp( frac, start->GetValue()[i], end->GetValue()[i] );
+ }
+ Lerp_Clamp( out[i] );
+ }
+}
+
+
+template< typename Type, bool IS_ARRAY >
+inline void CInterpolatedVarArrayBase<Type, IS_ARRAY>::_Extrapolate(
+ Type *pOut,
+ CInterpolatedVarEntry *pOld,
+ CInterpolatedVarEntry *pNew,
+ float flDestinationTime,
+ float flMaxExtrapolationAmount
+ )
+{
+ if ( fabs( pOld->changetime - pNew->changetime ) < 0.001f || flDestinationTime <= pNew->changetime )
+ {
+ for ( int i=0; i < m_nMaxCount; i++ )
+ pOut[i] = pNew->GetValue()[i];
+ }
+ else
+ {
+ float flExtrapolationAmount = MIN( flDestinationTime - pNew->changetime, flMaxExtrapolationAmount );
+
+ float divisor = 1.0f / (pNew->changetime - pOld->changetime);
+ for ( int i=0; i < m_nMaxCount; i++ )
+ {
+ pOut[i] = ExtrapolateInterpolatedVarType( pOld->GetValue()[i], pNew->GetValue()[i], divisor, flExtrapolationAmount );
+ }
+ }
+}
+
+
+template< typename Type, bool IS_ARRAY >
+inline void CInterpolatedVarArrayBase<Type, IS_ARRAY>::TimeFixup2_Hermite(
+ typename CInterpolatedVarArrayBase<Type, IS_ARRAY>::CInterpolatedVarEntry &fixup,
+ typename CInterpolatedVarArrayBase<Type, IS_ARRAY>::CInterpolatedVarEntry*& prev,
+ typename CInterpolatedVarArrayBase<Type, IS_ARRAY>::CInterpolatedVarEntry*& start,
+ float dt1
+ )
+{
+ float dt2 = start->changetime - prev->changetime;
+
+ // If times are not of the same interval renormalize the earlier sample to allow for uniform hermite spline interpolation
+ if ( fabs( dt1 - dt2 ) > 0.0001f &&
+ dt2 > 0.0001f )
+ {
+ // Renormalize
+ float frac = dt1 / dt2;
+
+ // Fixed interval into past
+ fixup.changetime = start->changetime - dt1;
+
+ for ( int i = 0; i < m_nMaxCount; i++ )
+ {
+ if ( m_bLooping[i] )
+ {
+ fixup.GetValue()[i] = LoopingLerp( 1-frac, prev->GetValue()[i], start->GetValue()[i] );
+ }
+ else
+ {
+ fixup.GetValue()[i] = Lerp( 1-frac, prev->GetValue()[i], start->GetValue()[i] );
+ }
+ }
+
+ // Point previous sample at fixed version
+ prev = &fixup;
+ }
+}
+
+
+template< typename Type, bool IS_ARRAY >
+inline void CInterpolatedVarArrayBase<Type, IS_ARRAY>::TimeFixup_Hermite(
+ typename CInterpolatedVarArrayBase<Type, IS_ARRAY>::CInterpolatedVarEntry &fixup,
+ typename CInterpolatedVarArrayBase<Type, IS_ARRAY>::CInterpolatedVarEntry*& prev,
+ typename CInterpolatedVarArrayBase<Type, IS_ARRAY>::CInterpolatedVarEntry*& start,
+ typename CInterpolatedVarArrayBase<Type, IS_ARRAY>::CInterpolatedVarEntry*& end )
+{
+ TimeFixup2_Hermite( fixup, prev, start, end->changetime - start->changetime );
+}
+
+
+template< typename Type, bool IS_ARRAY >
+inline void CInterpolatedVarArrayBase<Type, IS_ARRAY>::_Interpolate_Hermite(
+ Type *out,
+ float frac,
+ CInterpolatedVarEntry *prev,
+ CInterpolatedVarEntry *start,
+ CInterpolatedVarEntry *end,
+ bool looping )
+{
+ Assert( start );
+ Assert( end );
+
+ // Disable range checks because we can produce weird values here and it's not an error.
+ // After interpolation, we will clamp the values.
+ CDisableRangeChecks disableRangeChecks;
+
+ CInterpolatedVarEntry fixup;
+ fixup.Init(m_nMaxCount);
+ TimeFixup_Hermite( fixup, prev, start, end );
+
+ for( int i = 0; i < m_nMaxCount; i++ )
+ {
+ // Note that QAngle has a specialization that will do quaternion interpolation here...
+ if ( m_bLooping[ i ] )
+ {
+ out[ i ] = LoopingLerp_Hermite( frac, prev->GetValue()[i], start->GetValue()[i], end->GetValue()[i] );
+ }
+ else
+ {
+ out[ i ] = Lerp_Hermite( frac, prev->GetValue()[i], start->GetValue()[i], end->GetValue()[i] );
+ }
+
+ // Clamp the output from interpolation. There are edge cases where something like m_flCycle
+ // can get set to a really high or low value when we set it to zero after a really small
+ // time interval (the hermite blender will think it's got a really high velocity and
+ // skyrocket it off into la-la land).
+ Lerp_Clamp( out[i] );
+ }
+}
+
+template< typename Type, bool IS_ARRAY >
+inline void CInterpolatedVarArrayBase<Type, IS_ARRAY>::_Derivative_Hermite(
+ Type *out,
+ float frac,
+ CInterpolatedVarEntry *prev,
+ CInterpolatedVarEntry *start,
+ CInterpolatedVarEntry *end )
+{
+ Assert( start );
+ Assert( end );
+
+ // Disable range checks because we can produce weird values here and it's not an error.
+ // After interpolation, we will clamp the values.
+ CDisableRangeChecks disableRangeChecks;
+
+ CInterpolatedVarEntry fixup;
+ fixup.value = (Type*)_alloca( sizeof(Type) * m_nMaxCount );
+ TimeFixup_Hermite( fixup, prev, start, end );
+
+ float divisor = 1.0f / (end->changetime - start->changetime);
+
+ for( int i = 0; i < m_nMaxCount; i++ )
+ {
+ Assert( !m_bLooping[ i ] );
+ out[i] = Derivative_Hermite( frac, prev->GetValue()[i], start->GetValue()[i], end->GetValue()[i] );
+ out[i] *= divisor;
+ }
+}
+
+
+template< typename Type, bool IS_ARRAY >
+inline void CInterpolatedVarArrayBase<Type, IS_ARRAY>::_Derivative_Hermite_SmoothVelocity(
+ Type *out,
+ float frac,
+ CInterpolatedVarEntry *b,
+ CInterpolatedVarEntry *c,
+ CInterpolatedVarEntry *d )
+{
+ CInterpolatedVarEntry fixup;
+ fixup.Init(m_nMaxCount);
+ TimeFixup_Hermite( fixup, b, c, d );
+ for ( int i=0; i < m_nMaxCount; i++ )
+ {
+ Type prevVel = (c->GetValue()[i] - b->GetValue()[i]) / (c->changetime - b->changetime);
+ Type curVel = (d->GetValue()[i] - c->GetValue()[i]) / (d->changetime - c->changetime);
+ out[i] = Lerp( frac, prevVel, curVel );
+ }
+}
+
+
+template< typename Type, bool IS_ARRAY >
+inline void CInterpolatedVarArrayBase<Type, IS_ARRAY>::_Derivative_Linear(
+ Type *out,
+ CInterpolatedVarEntry *start,
+ CInterpolatedVarEntry *end )
+{
+ if ( start == end || fabs( start->changetime - end->changetime ) < 0.0001f )
+ {
+ for( int i = 0; i < m_nMaxCount; i++ )
+ {
+ out[ i ] = start->GetValue()[i] * 0;
+ }
+ }
+ else
+ {
+ float divisor = 1.0f / (end->changetime - start->changetime);
+ for( int i = 0; i < m_nMaxCount; i++ )
+ {
+ out[ i ] = (end->GetValue()[i] - start->GetValue()[i]) * divisor;
+ }
+ }
+}
+
+
+template< typename Type, bool IS_ARRAY >
+inline bool CInterpolatedVarArrayBase<Type, IS_ARRAY>::ValidOrder()
+{
+ float newestchangetime = 0.0f;
+ bool first = true;
+ for ( int i = 0; i < m_VarHistory.Count(); i++ )
+ {
+ CInterpolatedVarEntry *entry = &m_VarHistory[ i ];
+ if ( first )
+ {
+ first = false;
+ newestchangetime = entry->changetime;
+ continue;
+ }
+
+ // They should get older as wel walk backwards
+ if ( entry->changetime > newestchangetime )
+ {
+ Assert( 0 );
+ return false;
+ }
+
+ newestchangetime = entry->changetime;
+ }
+
+ return true;
+}
+
+template< typename Type, int COUNT >
+class CInterpolatedVarArray : public CInterpolatedVarArrayBase<Type, true >
+{
+public:
+ CInterpolatedVarArray( const char *pDebugName = "no debug name" )
+ : CInterpolatedVarArrayBase<Type, true>( pDebugName )
+ {
+ this->SetMaxCount( COUNT );
+ }
+};
+
+
+// -------------------------------------------------------------------------------------------------------------- //
+// CInterpolatedVar.
+// -------------------------------------------------------------------------------------------------------------- //
+
+template< typename Type >
+class CInterpolatedVar : public CInterpolatedVarArrayBase< Type, false >
+{
+public:
+ CInterpolatedVar( const char *pDebugName = NULL )
+ : CInterpolatedVarArrayBase< Type, false >(pDebugName)
+ {
+ this->SetMaxCount( 1 );
+ }
+};
+
+#include "tier0/memdbgoff.h"
+
+#endif // INTERPOLATEDVAR_H