summaryrefslogtreecommitdiff
path: root/game/client/replay/replay_ragdoll.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/client/replay/replay_ragdoll.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/client/replay/replay_ragdoll.cpp')
-rw-r--r--game/client/replay/replay_ragdoll.cpp750
1 files changed, 750 insertions, 0 deletions
diff --git a/game/client/replay/replay_ragdoll.cpp b/game/client/replay/replay_ragdoll.cpp
new file mode 100644
index 0000000..1327a8c
--- /dev/null
+++ b/game/client/replay/replay_ragdoll.cpp
@@ -0,0 +1,750 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// TODO:
+// - Use a mempool
+// - Need to be able to gracefully turn replay ragdolls on/off
+//
+//----------------------------------------------------------------------------------------
+
+#include "cbase.h"
+
+#if defined( REPLAY_ENABLED )
+
+#include "replay_ragdoll.h"
+#include "tier1/mempool.h"
+#include "debugoverlay_shared.h"
+#include "filesystem.h"
+
+//--------------------------------------------------------------------------------
+
+static matrix3x4_t gs_BoneCache[ MAXSTUDIOBONES ];
+static int gs_nBytesAllocated = 0;
+
+//--------------------------------------------------------------------------------
+
+void OnReplayCacheClientRagdollsCvarChange( IConVar *pVar, const char *pOldValue, float flOldValue )
+{
+ // TODO: need to be able to gracefully turn replay ragdolls on/off
+}
+
+//--------------------------------------------------------------------------------
+
+static ConVar replay_ragdoll_dbg( "replay_ragdoll_dbg", "0", FCVAR_CLIENTDLL, "Display replay ragdoll debugging information." );
+static ConVar replay_cache_client_ragdolls( "replay_cache_client_ragdolls", "0", FCVAR_CLIENTDLL, "Record ragdolls on the client during.", OnReplayCacheClientRagdollsCvarChange );
+
+//--------------------------------------------------------------------------------
+
+void DrawBones( matrix3x4_t const* pBones, int nNumBones, ragdoll_t const* pRagdoll,
+ int nRed, int nGreen, int nBlue, C_BaseAnimating* pBaseAnimating )
+{
+ Assert( pBones );
+ Assert( pRagdoll );
+ Assert( pBaseAnimating );
+
+ Vector from, to;
+ for ( int i = 0; i < nNumBones; ++i )
+ {
+// debugoverlay->AddCoordFrameOverlay( pBones[ i ], 3.0f );
+
+ int const iRagdollParentIndex = pRagdoll->list[ i ].parentIndex;
+ if ( iRagdollParentIndex < 0 )
+ continue;
+
+ int iBoneIndex = pRagdoll->boneIndex[ i ];
+ int iParentIndex = pRagdoll->boneIndex[ iRagdollParentIndex ];
+
+ MatrixPosition( pBones[ iParentIndex ], from );
+ MatrixPosition( pBones[ iBoneIndex ], to );
+
+ if ( debugoverlay )
+ {
+ debugoverlay->AddLineOverlay( from, to, nRed, nGreen, nBlue, true, 0.0f );
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------
+
+inline int GetServerTickCount()
+{
+ int nTick = TIME_TO_TICKS( engine->GetLastTimeStamp() );
+ return nTick;
+}
+
+//--------------------------------------------------------------------------------
+
+/*static*/ RagdollSimulationFrame_t* RagdollSimulationFrame_t::Alloc( int nNumBones )
+{
+ // TODO: use a mempool
+ RagdollSimulationFrame_t* pNew = new RagdollSimulationFrame_t();
+ pNew->pPositions = new Vector[ nNumBones ];
+ pNew->pAngles = new QAngle[ nNumBones ];
+ gs_nBytesAllocated += sizeof( pNew ) + nNumBones * ( sizeof( Vector ) + sizeof( QAngle ) );
+ return pNew;
+}
+
+//--------------------------------------------------------------------------------
+
+RagdollSimulationData_t::RagdollSimulationData_t( C_BaseAnimating* pEntity, int nStartTick, int nNumBones )
+: m_pEntity( pEntity ),
+ m_nEntityIndex( -1 ),
+ m_nStartTick( nStartTick ),
+ m_nNumBones( nNumBones ),
+ m_nDuration( -1 )
+{
+ if ( pEntity )
+ {
+ m_nEntityIndex = pEntity->entindex();
+ }
+
+ Assert( nNumBones >= 0 && nNumBones < MAXSTUDIOBONES );
+}
+
+bool _ComputeRagdollBones( const ragdoll_t *pRagdoll, matrix3x4_t &parentTransform, matrix3x4_t *pBones, Vector *pPositions, QAngle *pAngles )
+{
+ matrix3x4_t inverted, output;
+
+#ifdef _DEBUG
+ CBitVec<MAXSTUDIOBONES> vBonesComputed;
+ vBonesComputed.ClearAll();
+#endif
+
+ for ( int i = 0; i < pRagdoll->listCount; ++i )
+ {
+ const ragdollelement_t& element = pRagdoll->list[ i ];
+
+ // during restore if a model has changed since the file was saved, this could be NULL
+ if ( !element.pObject )
+ return false;
+
+ int const boneIndex = pRagdoll->boneIndex[ i ];
+ if ( boneIndex < 0 )
+ {
+ AssertMsg( 0, "Replay: No mapping for ragdoll bone\n" );
+ return false;
+ }
+
+ // Get global transform and put it into the bone cache
+ element.pObject->GetPositionMatrix( &pBones[ boneIndex ] );
+
+ // Ensure a fixed translation from the parent (no stretching)
+ if ( element.parentIndex >= 0 && !pRagdoll->allowStretch )
+ {
+ int parentIndex = pRagdoll->boneIndex[ element.parentIndex ];
+
+#ifdef _DEBUG
+ // Make sure we computed the parent already
+ Assert( vBonesComputed.IsBitSet(parentIndex) );
+#endif
+
+ // overwrite the position from physics to force rigid attachment
+ // NOTE: On the client we actually override this with the proper parent bone in each LOD
+ Vector out;
+ VectorTransform( element.originParentSpace, pBones[ parentIndex ], out );
+ MatrixSetColumn( out, 3, pBones[ boneIndex ] );
+
+ MatrixInvert( pBones[ parentIndex ], inverted );
+ }
+ else if ( element.parentIndex == - 1 )
+ {
+ // Decompose into parent space
+ MatrixInvert( parentTransform, inverted );
+ }
+
+#ifdef _DEBUG
+ vBonesComputed.Set( boneIndex, true );
+#endif
+
+ // Compute local transform and put into 'output'
+ ConcatTransforms( inverted, pBones[ boneIndex ], output );
+
+ // Cache as Euler/position
+ MatrixAngles( output, pAngles[ i ], pPositions[ i ] );
+ }
+ return true;
+}
+
+void RagdollSimulationData_t::Record()
+{
+ Assert( m_pEntity->m_pRagdoll );
+
+ // Allocate a frame
+ RagdollSimulationFrame_t* pNewFrame = RagdollSimulationFrame_t::Alloc( m_nNumBones );
+ if ( !pNewFrame )
+ return;
+
+ // Set the current tick
+ pNewFrame->nTick = GetServerTickCount();
+
+ // Add new frame to list of frames
+ m_lstFrames.AddToTail( pNewFrame );
+
+ // Compute parent transform
+ matrix3x4_t parentTransform;
+ Vector vRootPosition = m_pEntity->GetRenderOrigin();
+ QAngle angRootAngles = m_pEntity->GetRenderAngles();
+ AngleMatrix( angRootAngles, vRootPosition, parentTransform );
+
+// debugoverlay->AddCoordFrameOverlay( parentTransform, 100 );
+
+ // Cache off root position/orientation
+ pNewFrame->vRootPosition = vRootPosition;
+ pNewFrame->angRootAngles = angRootAngles;
+
+ // Compute actual ragdoll bones
+ matrix3x4_t* pBones = gs_BoneCache;
+ _ComputeRagdollBones( m_pEntity->m_pRagdoll->GetRagdoll(), parentTransform, pBones, pNewFrame->pPositions, pNewFrame->pAngles );
+
+ // Draw bones
+ if ( replay_ragdoll_dbg.GetBool() )
+ {
+ DrawBones( pBones, m_pEntity->m_pRagdoll->RagdollBoneCount(), m_pEntity->m_pRagdoll->GetRagdoll(), 255, 0, 0, m_pEntity );
+ }
+}
+
+//--------------------------------------------------------------------------------
+
+CReplayRagdollRecorder::CReplayRagdollRecorder()
+: m_bIsRecording(false)
+{
+}
+
+CReplayRagdollRecorder::~CReplayRagdollRecorder()
+{
+}
+
+/*static*/ CReplayRagdollRecorder& CReplayRagdollRecorder::Instance()
+{
+ static CReplayRagdollRecorder s_instance;
+ return s_instance;
+}
+
+void CReplayRagdollRecorder::Init()
+{
+ Assert( !m_bIsRecording );
+ m_bIsRecording = true;
+ gs_nBytesAllocated = 0;
+}
+
+void CReplayRagdollRecorder::Shutdown()
+{
+ if ( !m_bIsRecording )
+ return;
+
+ m_lstRagdolls.PurgeAndDeleteElements();
+ gs_nBytesAllocated = 0;
+
+ // RemoveAll() purges, and there is no UnlinkAll() - is there an easier way to do this?
+ Iterator_t i = m_lstRagdollsToRecord.Head();
+ while ( i != m_lstRagdollsToRecord.InvalidIndex() )
+ {
+ m_lstRagdollsToRecord.Unlink( i );
+ i = m_lstRagdollsToRecord.Head();
+ }
+
+ Assert( m_bIsRecording );
+ m_bIsRecording = false;
+}
+
+void CReplayRagdollRecorder::AddEntry( C_BaseAnimating* pEntity, int nStartTick, int nNumBones )
+{
+ DevMsg( "Replay: Processing Ragdoll at time %d\n", nStartTick );
+
+ Assert( pEntity );
+ RagdollSimulationData_t* pNewEntry = new RagdollSimulationData_t( pEntity, nStartTick, nNumBones );
+ gs_nBytesAllocated += sizeof( RagdollSimulationData_t );
+ m_lstRagdolls.AddToTail( pNewEntry );
+
+ // Also add to list of ragdolls to record
+ m_lstRagdollsToRecord.AddToTail( pNewEntry );
+}
+
+void CReplayRagdollRecorder::StopRecordingRagdoll( C_BaseAnimating* pEntity )
+{
+ Assert( pEntity );
+
+ // Find the entry in the recording list
+ Iterator_t nIndex;
+ if ( !FindEntryInRecordingList( pEntity, nIndex ) )
+ return;
+
+ StopRecordingRagdollAtIndex( nIndex );
+}
+
+void CReplayRagdollRecorder::StopRecordingRagdollAtIndex( Iterator_t nIndex )
+{
+ // No longer recording - compute duration
+ RagdollSimulationData_t* pData = m_lstRagdollsToRecord[ nIndex ];
+
+ // Does duration need to be set?
+ if ( pData->m_nDuration < 0 )
+ {
+ pData->m_nDuration = GetServerTickCount() - pData->m_nStartTick; Assert( pData->m_nDuration > 0 );
+ }
+
+ // Remove it from the recording list
+ m_lstRagdollsToRecord.Unlink( nIndex );
+}
+
+void CReplayRagdollRecorder::StopRecordingSleepingRagdolls()
+{
+ Iterator_t i = m_lstRagdollsToRecord.Head();
+ while ( i != m_lstRagdollsToRecord.InvalidIndex() )
+ {
+ if ( RagdollIsAsleep( *m_lstRagdollsToRecord[ i ]->m_pEntity->m_pRagdoll->GetRagdoll() ) )
+ {
+ DevMsg( "entity %d: Removing sleeping ragdoll\n", m_lstRagdollsToRecord[ i ]->m_nEntityIndex );
+
+ StopRecordingRagdollAtIndex( i );
+ i = m_lstRagdollsToRecord.Head();
+ }
+ else
+ {
+ i = m_lstRagdollsToRecord.Next( i );
+ }
+ }
+}
+
+bool CReplayRagdollRecorder::FindEntryInRecordingList( C_BaseAnimating* pEntity,
+ CReplayRagdollRecorder::Iterator_t& nOutIndex )
+{
+ // Find the entry
+ FOR_EACH_LL( m_lstRagdollsToRecord, i )
+ {
+ if ( m_lstRagdollsToRecord[ i ]->m_pEntity == pEntity )
+ {
+ nOutIndex = i;
+ return true;
+ }
+ }
+
+ nOutIndex = m_lstRagdollsToRecord.InvalidIndex();
+ return false;
+}
+
+void CReplayRagdollRecorder::Record()
+{
+ static ConVar* pReplayEnable = NULL;
+ static bool bLookedForConvar = false;
+ if ( bLookedForConvar )
+ {
+ pReplayEnable = (ConVar*)cvar->FindVar( "replay_enable" );
+ bLookedForConvar = true;
+ }
+ if ( !pReplayEnable || !pReplayEnable->GetInt() )
+ return;
+
+ if ( !replay_cache_client_ragdolls.GetInt() )
+ return;
+
+ FOR_EACH_LL( m_lstRagdollsToRecord, i )
+ {
+ Assert( m_lstRagdollsToRecord[ i ]->m_pEntity->IsRagdoll() );
+ m_lstRagdollsToRecord[ i ]->Record();
+ }
+}
+
+void CReplayRagdollRecorder::Think()
+{
+ if ( !IsRecording() )
+ return;
+
+ StopRecordingSleepingRagdolls();
+ Record();
+
+ PrintDebug();
+}
+
+void CReplayRagdollRecorder::PrintDebug()
+{
+ if ( !replay_ragdoll_dbg.GetInt() )
+ return;
+
+ int nLine = 0;
+
+ // Print memory usage
+ engine->Con_NPrintf( nLine++, "ragdolls: %.2f MB", gs_nBytesAllocated / 1048576.0f );
+
+ // Print server time
+ engine->Con_NPrintf( nLine++, "server time: %d", GetServerTickCount() );
+
+ ++nLine; // Blank line
+
+ // Print info about each ragdoll
+ FOR_EACH_LL( m_lstRagdolls, i )
+ {
+ engine->Con_NPrintf( nLine++, "entity %d: start time=%d duration=%d num bones=%d", m_lstRagdolls[i]->m_nEntityIndex, m_lstRagdolls[i]->m_nStartTick, m_lstRagdolls[i]->m_nDuration, m_lstRagdolls[i]->m_nNumBones );
+ }
+}
+
+void CReplayRagdollRecorder::CleanupStartupTicksAndDurations( int nStartTick )
+{
+ FOR_EACH_LL( m_lstRagdolls, i )
+ {
+ RagdollSimulationData_t* pRagdollData = m_lstRagdolls[ i ];
+
+ // Offset start tick with start tick, sent over from server
+ pRagdollData->m_nStartTick -= nStartTick; Assert( pRagdollData->m_nStartTick >= 0 );
+
+ // Setup duration
+ pRagdollData->m_nDuration = GetServerTickCount() - nStartTick; Assert( pRagdollData->m_nDuration > 0 );
+
+ // Go through all frames and subtract the start tick
+ FOR_EACH_LL( pRagdollData->m_lstFrames, j )
+ {
+ pRagdollData->m_lstFrames[ j ]->nTick -= nStartTick;
+ }
+ }
+}
+
+BEGIN_DMXELEMENT_UNPACK( RagdollSimulationData_t )
+ DMXELEMENT_UNPACK_FIELD( "nEntityIndex", "0", int, m_nEntityIndex )
+ DMXELEMENT_UNPACK_FIELD( "nStartTick", "0", int, m_nStartTick )
+ DMXELEMENT_UNPACK_FIELD( "nDuration", "0", int, m_nDuration )
+ DMXELEMENT_UNPACK_FIELD( "nNumBones", "0", int, m_nNumBones )
+END_DMXELEMENT_UNPACK( RagdollSimulationData_t, s_RagdollSimulationDataUnpack )
+
+bool CReplayRagdollRecorder::DumpRagdollsToDisk( char const* pFilename ) const
+{
+ MEM_ALLOC_CREDIT();
+ DECLARE_DMX_CONTEXT();
+
+ CDmxElement* pSimulations = CreateDmxElement( "Simulations" );
+ CDmxElementModifyScope modify( pSimulations );
+
+ int const nNumRagdolls = m_lstRagdolls.Count();
+
+ pSimulations->SetValue( "iNumRagdolls", nNumRagdolls );
+
+ CDmxAttribute* pRagdolls = pSimulations->AddAttribute( "ragdolls" );
+ CUtlVector< CDmxElement* >& ragdolls = pRagdolls->GetArrayForEdit< CDmxElement* >();
+
+ modify.Release();
+
+ char name[32];
+
+ FOR_EACH_LL( m_lstRagdolls, i )
+ {
+ RagdollSimulationData_t const* pData = m_lstRagdolls[ i ];
+
+ // Make sure we've setup all durations properly
+ Assert( pData->m_nDuration >= 0 );
+
+ CDmxElement* pRagdoll = CreateDmxElement( "ragdoll" );
+ ragdolls.AddToTail( pRagdoll );
+
+ V_snprintf( name, sizeof(name), "ragdoll %d", i );
+ pRagdoll->SetValue( "name", name );
+
+ CDmxElementModifyScope modifyClass( pRagdoll );
+
+ pRagdoll->AddAttributesFromStructure( pData, s_RagdollSimulationDataUnpack );
+
+ CDmxAttribute* pFrames = pRagdoll->AddAttribute( "frames" );
+ CUtlVector< CDmxElement* >& frames = pFrames->GetArrayForEdit< CDmxElement* >();
+
+ FOR_EACH_LL( pData->m_lstFrames, j )
+ {
+ CDmxElement* pFrame = CreateDmxElement( "frame" );
+ frames.AddToTail( pFrame );
+
+ V_snprintf( name, sizeof(name), "frame %d", j );
+ pFrame->SetValue( "name", name );
+
+ // Store tick
+ pFrame->SetValue( "tick", pData->m_lstFrames[ j ]->nTick );
+
+ // Store root pos/orientation
+ pFrame->SetValue( "root_pos" , pData->m_lstFrames[ j ]->vRootPosition );
+ pFrame->SetValue( "root_angles", pData->m_lstFrames[ j ]->angRootAngles );
+
+ for ( int k = 0; k < pData->m_nNumBones; ++k )
+ {
+ CDmxAttribute* pPositions = pFrame->AddAttribute( "positions" );
+ CUtlVector< Vector >& positions = pPositions->GetArrayForEdit< Vector >();
+
+ CDmxAttribute* pAngles = pFrame->AddAttribute( "angles" );
+ CUtlVector< QAngle >& angles = pAngles->GetArrayForEdit< QAngle >();
+
+ positions.AddToTail( pData->m_lstFrames[ j ]->pPositions[ k ] );
+ angles.AddToTail( pData->m_lstFrames[ j ]->pAngles[ k ] );
+ }
+ }
+ }
+
+ {
+ MEM_ALLOC_CREDIT();
+ CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
+ if ( !SerializeDMX( buf, pSimulations, pFilename ) )
+ {
+ Warning( "Replay: Failed to write ragdoll cache, %s.\n", pFilename );
+ return false;
+ }
+
+ // Write the file
+ filesystem->WriteFile( pFilename, "MOD", buf );
+ }
+
+ CleanupDMX( pSimulations );
+
+ Msg( "Replay: Cached ragdoll data.\n" );
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------
+
+CReplayRagdollCache::CReplayRagdollCache()
+: m_bInit( false )
+{
+}
+
+/*static*/ CReplayRagdollCache& CReplayRagdollCache::Instance()
+{
+ static CReplayRagdollCache s_instance;
+ return s_instance;
+}
+
+bool CReplayRagdollCache::Init( char const* pFilename )
+{
+ Assert( !m_bInit );
+
+ // Make sure valid filename
+ if ( !pFilename || pFilename[0] == 0 )
+ return false;
+
+ DECLARE_DMX_CONTEXT();
+
+ // Attempt to read from disk
+ CDmxElement* pRagdolls = NULL;
+ if ( !UnserializeDMX( pFilename, "MOD", true, &pRagdolls ) )
+// if ( !UnserializeDMX( pFilename, "GAME", false, &pRagdolls ) )
+ return false;
+
+ CUtlVector< CDmxElement* > const& ragdolls = pRagdolls->GetArray< CDmxElement* >( "ragdolls" );
+ for ( int i = 0; i < ragdolls.Count(); ++i )
+ {
+ CDmxElement* pCurRagdollInput = ragdolls[ i ];
+
+ // Create a new ragdoll entry and add to list
+ RagdollSimulationData_t* pNewSimData = new RagdollSimulationData_t();
+ m_lstRagdolls.AddToTail( pNewSimData );
+
+ // Read
+ pCurRagdollInput->UnpackIntoStructure( pNewSimData, sizeof( *pNewSimData ), s_RagdollSimulationDataUnpack );
+
+ // NOTE: Entity ptr doesn't get linked up here because it doesn't necessarily exist at this point
+
+ // Read frames
+ CUtlVector< CDmxElement* > const& frames = pCurRagdollInput->GetArray< CDmxElement* >( "frames" );
+ for ( int j = 0; j < frames.Count(); ++j )
+ {
+ CDmxElement* pCurFrameInput = frames[ j ];
+
+ // Create a new frame and add it to list of frames
+ RagdollSimulationFrame_t* pNewFrame = RagdollSimulationFrame_t::Alloc( pNewSimData->m_nNumBones );
+ pNewSimData->m_lstFrames.AddToTail( pNewFrame );
+
+ // Read tick
+ pNewFrame->nTick = pCurFrameInput->GetValue( "tick", -1 ); Assert( pNewFrame->nTick != -1 );
+
+ // Read root pos/orientation
+ pNewFrame->vRootPosition = pCurFrameInput->GetValue( "root_pos" , vec3_origin );
+ pNewFrame->angRootAngles = pCurFrameInput->GetValue( "root_angles", vec3_angle );
+
+ CUtlVector< Vector > const& positions = pCurFrameInput->GetArray< Vector >( "positions" );
+ CUtlVector< QAngle > const& angles = pCurFrameInput->GetArray< QAngle >( "angles" );
+
+ for ( int k = 0; k < pNewSimData->m_nNumBones; ++k )
+ {
+ pNewFrame->pPositions[ k ] = positions[ k ];
+ pNewFrame->pAngles[ k ] = angles[ k ];
+ }
+ }
+ }
+
+
+ // Cleanup
+ CleanupDMX( pRagdolls );
+
+ m_bInit = true;
+
+ return true;
+}
+
+void CReplayRagdollCache::Shutdown()
+{
+ if ( !m_bInit )
+ return;
+
+ m_lstRagdolls.PurgeAndDeleteElements();
+ m_bInit = false;
+}
+
+ConVar replay_ragdoll_blending( "replay_ragdoll_blending", "1", FCVAR_DEVELOPMENTONLY );
+ConVar replay_ragdoll_tickoffset( "replay_ragdoll_tickoffset", "0", FCVAR_DEVELOPMENTONLY );
+
+bool CReplayRagdollCache::GetFrame( C_BaseAnimating* pEntity, int nTick, bool* pBoneSimulated, CBoneAccessor* pBoneAccessor ) const
+{
+ nTick += replay_ragdoll_tickoffset.GetInt();
+
+ Assert( pEntity );
+ Assert( pBoneSimulated );
+ Assert( pEntity->m_pRagdoll );
+
+ // Find ragdoll for the given entity - will return NULL if nTick is out of the entry's time window
+ const RagdollSimulationData_t* pRagdollEntry = FindRagdollEntry( pEntity, nTick );
+ if ( !pRagdollEntry )
+ return false;
+
+ // Find frame for the given tick
+ RagdollSimulationFrame_t* pFrame;
+ RagdollSimulationFrame_t* pNextFrame;
+ if ( !FindFrame( pFrame, pNextFrame, pRagdollEntry, nTick ) )
+ return false;
+
+ // Compute root transform
+ matrix3x4_t rootTransform;
+ float flInterpAmount = gpGlobals->interpolation_amount;
+ if ( pNextFrame )
+ {
+ AngleMatrix(
+ (const QAngle &)Lerp( flInterpAmount, pFrame->angRootAngles, pNextFrame->angRootAngles ), // Actually does a slerp
+ Lerp( flInterpAmount, pFrame->vRootPosition, pNextFrame->vRootPosition ),
+ rootTransform
+ );
+ }
+ else
+ {
+ AngleMatrix( pFrame->angRootAngles, pFrame->vRootPosition, rootTransform );
+ }
+
+ // Compute each bone
+ ragdoll_t* pRagdoll = pEntity->m_pRagdoll->GetRagdoll(); Assert( pRagdoll );
+ for ( int k = 0; k < pRagdoll->listCount; ++k )
+ {
+ int objectIndex = k;
+ const ragdollelement_t& element = pRagdoll->list[ objectIndex ];
+
+ int const boneIndex = pRagdoll->boneIndex[ objectIndex ]; Assert( boneIndex >= 0 );
+
+ // Compute blended transform if possible
+ matrix3x4_t localTransform;
+ if ( pNextFrame && replay_ragdoll_blending.GetInt() )
+ {
+ // Get blended Eular angles - NOTE: The Lerp() here actually calls Lerp<QAngle>() which converts to quats and back
+ flInterpAmount = gpGlobals->interpolation_amount; Assert( flInterpAmount >= 0.0f && flInterpAmount <= 1.0f );
+ AngleMatrix(
+ (const QAngle &)Lerp( flInterpAmount, pFrame->pAngles [ objectIndex ], pNextFrame->pAngles [ objectIndex ] ),
+ Lerp( flInterpAmount, pFrame->pPositions[ objectIndex ], pNextFrame->pPositions[ objectIndex ] ),
+ localTransform
+ );
+ }
+ else
+ {
+ // Last frame
+ AngleMatrix( pFrame->pAngles[ objectIndex ], pFrame->pPositions[ objectIndex ], localTransform );
+ }
+
+ matrix3x4_t& boneMatrix = pBoneAccessor->GetBoneForWrite( boneIndex );
+
+ if ( element.parentIndex < 0 )
+ {
+ ConcatTransforms( rootTransform, localTransform, boneMatrix );
+ }
+ else
+ {
+ int parentBoneIndex = pRagdoll->boneIndex[ element.parentIndex ]; Assert( parentBoneIndex >= 0 );
+ Assert( pBoneSimulated[ parentBoneIndex ] );
+ matrix3x4_t const& parentMatrix = pBoneAccessor->GetBone( parentBoneIndex );
+ ConcatTransforms( parentMatrix, localTransform, boneMatrix );
+ }
+
+ // Simulated this bone
+ pBoneSimulated[ boneIndex ] = true;
+ }
+
+ if ( replay_ragdoll_dbg.GetBool() )
+ {
+ DrawBones( pBoneAccessor->GetBoneArrayForWrite(), pRagdollEntry->m_nNumBones, pRagdoll, 0, 0, 255, pEntity );
+ }
+
+ return true;
+}
+
+RagdollSimulationData_t* CReplayRagdollCache::FindRagdollEntry( C_BaseAnimating* pEntity, int nTick )
+{
+ Assert( pEntity );
+
+ int const nEntIndex = pEntity->entindex();
+
+ FOR_EACH_LL( m_lstRagdolls, i )
+ {
+ RagdollSimulationData_t* pRagdollData = m_lstRagdolls[ i ];
+
+ // If not the right entity or the tick is out range, continue.
+ if ( pRagdollData->m_nEntityIndex != nEntIndex )
+ continue;
+
+ // We've got the ragdoll, but only return it if nTick is in the window
+ if ( nTick < pRagdollData->m_nStartTick ||
+ nTick > pRagdollData->m_nStartTick + pRagdollData->m_nDuration )
+ return NULL;
+
+ return pRagdollData;
+ }
+
+ return NULL;
+}
+
+bool CReplayRagdollCache::FindFrame( RagdollSimulationFrame_t*& pFrameOut, RagdollSimulationFrame_t*& pNextFrameOut,
+ const RagdollSimulationData_t* pRagdollEntry, int nTick )
+{
+ // Look for the appropriate frame
+ FOR_EACH_LL( pRagdollEntry->m_lstFrames, j )
+ {
+ RagdollSimulationFrame_t* pFrame = pRagdollEntry->m_lstFrames[ j ];
+
+ // Get next frame if possible
+ int const nNext = pRagdollEntry->m_lstFrames.Next( j );
+ RagdollSimulationFrame_t* pNextFrame =
+ nNext == pRagdollEntry->m_lstFrames.InvalidIndex() ? NULL : pRagdollEntry->m_lstFrames[ nNext ];
+
+ // Use this frame?
+ if ( nTick >= pFrame->nTick &&
+ ( (pNextFrame && nTick <= pNextFrame->nTick) || !pNextFrame ) ) // Use the last frame if the tick is past the range of frames -
+ { // this is the "sleeping" ragdoll frame
+ pFrameOut = pFrame;
+ pNextFrameOut = pNextFrame;
+
+ return true;
+ }
+ }
+
+ pFrameOut = NULL;
+ pNextFrameOut = NULL;
+
+ return false;
+}
+
+void CReplayRagdollCache::Think()
+{
+ // TODO: Add IsPlayingReplayDemo() to engine interface
+ /*
+ engine->Con_NPrintf( 8, "time: %d", engine->GetDemoPlaybackTick() );
+ FOR_EACH_LL( m_lstRagdolls, i )
+ {
+ engine->Con_NPrintf( 10 + i, "entity %d: start time=%d duration=%d num bones=%d", m_lstRagdolls[i]->m_nEntityIndex, m_lstRagdolls[i]->m_nStartTick, m_lstRagdolls[i]->m_nDuration, m_lstRagdolls[i]->m_nNumBones );
+ }
+ */
+}
+
+//--------------------------------------------------------------------------------
+
+bool Replay_CacheRagdolls( const char* pFilename, int nStartTick )
+{
+ CReplayRagdollRecorder::Instance().CleanupStartupTicksAndDurations( nStartTick );
+ return CReplayRagdollRecorder::Instance().DumpRagdollsToDisk( pFilename );
+}
+
+#endif \ No newline at end of file