diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/client/interpolatedvar.h | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/client/interpolatedvar.h')
| -rw-r--r-- | game/client/interpolatedvar.h | 1591 |
1 files changed, 1591 insertions, 0 deletions
diff --git a/game/client/interpolatedvar.h b/game/client/interpolatedvar.h new file mode 100644 index 0000000..f0edb58 --- /dev/null +++ b/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 |