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