aboutsummaryrefslogtreecommitdiff
path: root/sp/src/game/client/replay/replay_ragdoll.cpp
diff options
context:
space:
mode:
authorJørgen P. Tjernø <[email protected]>2013-12-02 19:31:46 -0800
committerJørgen P. Tjernø <[email protected]>2013-12-02 19:46:31 -0800
commitf56bb35301836e56582a575a75864392a0177875 (patch)
treede61ddd39de3e7df52759711950b4c288592f0dc /sp/src/game/client/replay/replay_ragdoll.cpp
parentMark some more files as text. (diff)
downloadsource-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz
source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip
Fix line endings. WHAMMY.
Diffstat (limited to 'sp/src/game/client/replay/replay_ragdoll.cpp')
-rw-r--r--sp/src/game/client/replay/replay_ragdoll.cpp1492
1 files changed, 746 insertions, 746 deletions
diff --git a/sp/src/game/client/replay/replay_ragdoll.cpp b/sp/src/game/client/replay/replay_ragdoll.cpp
index 73b3481e..a2633c0b 100644
--- a/sp/src/game/client/replay/replay_ragdoll.cpp
+++ b/sp/src/game/client/replay/replay_ragdoll.cpp
@@ -1,747 +1,747 @@
-//========= 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 );
-
- 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
- float 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 );
-}
-
+//========= 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 );
+
+ 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
+ float 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