|
|
//========= 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
|