You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1591 lines
44 KiB
1591 lines
44 KiB
//========= 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() = default; |
|
~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
|
|
|