diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /sp/src/game/client/replay/replay_ragdoll.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-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.cpp | 1492 |
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 |