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