//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //===========================================================================// #include "cbase.h" #include "mathlib/vmatrix.h" #include "ragdoll_shared.h" #include "bone_setup.h" #include "materialsystem/imesh.h" #include "engine/ivmodelinfo.h" #include "iviewrender.h" #include "tier0/vprof.h" #include "view.h" #include "physics_saverestore.h" #include "vphysics/constraints.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #ifdef _DEBUG extern ConVar r_FadeProps; #endif CRagdoll::CRagdoll() { m_ragdoll.listCount = 0; m_vecLastOrigin.Init(); m_flLastOriginChangeTime = - 1.0f; m_lastUpdate = -FLT_MAX; } #define DEFINE_RAGDOLL_ELEMENT( i ) \ DEFINE_FIELD( m_ragdoll.list[i].originParentSpace, FIELD_VECTOR ), \ DEFINE_PHYSPTR( m_ragdoll.list[i].pObject ), \ DEFINE_PHYSPTR( m_ragdoll.list[i].pConstraint ), \ DEFINE_FIELD( m_ragdoll.list[i].parentIndex, FIELD_INTEGER ) BEGIN_SIMPLE_DATADESC( CRagdoll ) DEFINE_AUTO_ARRAY( m_ragdoll.boneIndex, FIELD_INTEGER ), DEFINE_FIELD( m_ragdoll.listCount, FIELD_INTEGER ), DEFINE_FIELD( m_ragdoll.allowStretch, FIELD_BOOLEAN ), DEFINE_PHYSPTR( m_ragdoll.pGroup ), DEFINE_RAGDOLL_ELEMENT( 0 ), DEFINE_RAGDOLL_ELEMENT( 1 ), DEFINE_RAGDOLL_ELEMENT( 2 ), DEFINE_RAGDOLL_ELEMENT( 3 ), DEFINE_RAGDOLL_ELEMENT( 4 ), DEFINE_RAGDOLL_ELEMENT( 5 ), DEFINE_RAGDOLL_ELEMENT( 6 ), DEFINE_RAGDOLL_ELEMENT( 7 ), DEFINE_RAGDOLL_ELEMENT( 8 ), DEFINE_RAGDOLL_ELEMENT( 9 ), DEFINE_RAGDOLL_ELEMENT( 10 ), DEFINE_RAGDOLL_ELEMENT( 11 ), DEFINE_RAGDOLL_ELEMENT( 12 ), DEFINE_RAGDOLL_ELEMENT( 13 ), DEFINE_RAGDOLL_ELEMENT( 14 ), DEFINE_RAGDOLL_ELEMENT( 15 ), DEFINE_RAGDOLL_ELEMENT( 16 ), DEFINE_RAGDOLL_ELEMENT( 17 ), DEFINE_RAGDOLL_ELEMENT( 18 ), DEFINE_RAGDOLL_ELEMENT( 19 ), DEFINE_RAGDOLL_ELEMENT( 20 ), DEFINE_RAGDOLL_ELEMENT( 21 ), DEFINE_RAGDOLL_ELEMENT( 22 ), DEFINE_RAGDOLL_ELEMENT( 23 ), END_DATADESC() IPhysicsObject *CRagdoll::GetElement( int elementNum ) { return m_ragdoll.list[elementNum].pObject; } void CRagdoll::BuildRagdollBounds( C_BaseEntity *ent ) { Vector mins, maxs, size; modelinfo->GetModelBounds( ent->GetModel(), mins, maxs ); size = (maxs - mins) * 0.5; m_radius = size.Length(); m_mins.Init(-m_radius,-m_radius,-m_radius); m_maxs.Init(m_radius,m_radius,m_radius); } void CRagdoll::Init( C_BaseEntity *ent, CStudioHdr *pstudiohdr, const Vector &forceVector, int forceBone, const matrix3x4_t *pDeltaBones0, const matrix3x4_t *pDeltaBones1, const matrix3x4_t *pCurrentBonePosition, float dt, bool bFixedConstraints ) { ragdollparams_t params; params.pGameData = static_cast( ent ); params.modelIndex = ent->GetModelIndex(); params.pCollide = modelinfo->GetVCollide( params.modelIndex ); params.pStudioHdr = pstudiohdr; params.forceVector = forceVector; params.forceBoneIndex = forceBone; params.forcePosition.Init(); params.pCurrentBones = pCurrentBonePosition; params.jointFrictionScale = 1.0; params.allowStretch = false; params.fixedConstraints = bFixedConstraints; RagdollCreate( m_ragdoll, params, physenv ); ent->VPhysicsSetObject( NULL ); ent->VPhysicsSetObject( m_ragdoll.list[0].pObject ); // Mark the ragdoll as debris. ent->SetCollisionGroup( COLLISION_GROUP_DEBRIS ); RagdollApplyAnimationAsVelocity( m_ragdoll, pDeltaBones0, pDeltaBones1, dt ); RagdollActivate( m_ragdoll, params.pCollide, ent->GetModelIndex() ); // It's moving now... m_flLastOriginChangeTime = gpGlobals->curtime; // So traces hit it. ent->AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID ); if ( !m_ragdoll.listCount ) return; BuildRagdollBounds( ent ); for ( int i = 0; i < m_ragdoll.listCount; i++ ) { g_pPhysSaveRestoreManager->AssociateModel( m_ragdoll.list[i].pObject, ent->GetModelIndex() ); } #if RAGDOLL_VISUALIZE memcpy( m_savedBone1, &pDeltaBones0[0], sizeof(matrix3x4_t) * pstudiohdr->numbones() ); memcpy( m_savedBone2, &pDeltaBones1[0], sizeof(matrix3x4_t) * pstudiohdr->numbones() ); memcpy( m_savedBone3, &pCurrentBonePosition[0], sizeof(matrix3x4_t) * pstudiohdr->numbones() ); #endif } CRagdoll::~CRagdoll( void ) { for ( int i = 0; i < m_ragdoll.listCount; i++ ) { IPhysicsObject *pObject = m_ragdoll.list[i].pObject; if ( pObject ) { g_pPhysSaveRestoreManager->ForgetModel( m_ragdoll.list[i].pObject ); // Disable collision on all ragdoll parts before calling RagdollDestroy // (which might cause touch callbacks on the ragdoll otherwise, which is // very bad for a half deleted ragdoll). pObject->EnableCollisions( false ); } } RagdollDestroy( m_ragdoll ); } void CRagdoll::RagdollBone( C_BaseEntity *ent, mstudiobone_t *pbones, int boneCount, bool *boneSimulated, CBoneAccessor &pBoneToWorld ) { for ( int i = 0; i < m_ragdoll.listCount; i++ ) { if ( RagdollGetBoneMatrix( m_ragdoll, pBoneToWorld, i ) ) { boneSimulated[m_ragdoll.boneIndex[i]] = true; } } } const Vector& CRagdoll::GetRagdollOrigin( ) { m_ragdoll.list[0].pObject->GetPosition( &m_origin, 0 ); return m_origin; } void CRagdoll::GetRagdollBounds( Vector &theMins, Vector &theMaxs ) { theMins = m_mins; theMaxs = m_maxs; } void CRagdoll::VPhysicsUpdate( IPhysicsObject *pPhysics ) { if ( m_lastUpdate == gpGlobals->curtime ) return; m_lastUpdate = gpGlobals->curtime; m_allAsleep = RagdollIsAsleep( m_ragdoll ); if ( m_allAsleep ) { // NOTE: This is the bbox of the ragdoll's physics // It's not always correct to use for culling, but it sure beats // using the radius box! Vector origin = GetRagdollOrigin(); RagdollComputeExactBbox( m_ragdoll, origin, m_mins, m_maxs ); m_mins -= origin; m_maxs -= origin; } else { m_mins.Init(-m_radius,-m_radius,-m_radius); m_maxs.Init(m_radius,m_radius,m_radius); if ( m_ragdoll.pGroup->IsInErrorState() ) { C_BaseEntity *pEntity = static_cast(m_ragdoll.list[0].pObject->GetGameData()); RagdollSolveSeparation( m_ragdoll, pEntity ); } } // See if we should go to sleep... CheckSettleStationaryRagdoll(); } //============================================================================= // HPE_BEGIN: // [menglish] Transforms a vector from the given bone's space to world space //============================================================================= bool CRagdoll::TransformVectorToWorld(int iBoneIndex, const Vector *vPosition, Vector *vOut) { int listIndex = -1; if( iBoneIndex >= 0 && iBoneIndex < m_ragdoll.listCount) { for ( int i = 0; i < m_ragdoll.listCount; ++i ) { if(m_ragdoll.boneIndex[i] == iBoneIndex) listIndex = i; } if(listIndex != -1) { m_ragdoll.list[listIndex].pObject->LocalToWorld(vOut, *vPosition); return true; } } return false; } //============================================================================= // HPE_END //============================================================================= //----------------------------------------------------------------------------- // Purpose: // Input : - //----------------------------------------------------------------------------- void CRagdoll::PhysForceRagdollToSleep() { for ( int i = 0; i < m_ragdoll.listCount; i++ ) { if ( m_ragdoll.list[i].pObject ) { PhysForceClearVelocity( m_ragdoll.list[i].pObject ); m_ragdoll.list[i].pObject->Sleep(); } } } #define RAGDOLL_SLEEP_TOLERANCE 1.0f static ConVar ragdoll_sleepaftertime( "ragdoll_sleepaftertime", "5.0f", 0, "After this many seconds of being basically stationary, the ragdoll will go to sleep." ); void CRagdoll::CheckSettleStationaryRagdoll() { Vector delta = GetRagdollOrigin() - m_vecLastOrigin; m_vecLastOrigin = GetRagdollOrigin(); for ( int i = 0; i < 3; ++i ) { // It's still moving... if ( fabs( delta[ i ] ) > RAGDOLL_SLEEP_TOLERANCE ) { m_flLastOriginChangeTime = gpGlobals->curtime; // Msg( "%d [%p] Still moving\n", gpGlobals->tickcount, this ); return; } } // It's totally asleep, don't worry about forcing it to settle if ( m_allAsleep ) return; // Msg( "%d [%p] Settling\n", gpGlobals->tickcount, this ); // It has stopped moving, see if it float dt = gpGlobals->curtime - m_flLastOriginChangeTime; if ( dt < ragdoll_sleepaftertime.GetFloat() ) return; // Msg( "%d [%p] FORCE SLEEP\n",gpGlobals->tickcount, this ); // Force it to go to sleep PhysForceRagdollToSleep(); } void CRagdoll::ResetRagdollSleepAfterTime( void ) { m_flLastOriginChangeTime = gpGlobals->curtime; } void CRagdoll::DrawWireframe() { IMaterial *pWireframe = materials->FindMaterial("shadertest/wireframevertexcolor", TEXTURE_GROUP_OTHER); int i; matrix3x4_t matrix; for ( i = 0; i < m_ragdoll.listCount; i++ ) { static color32 debugColor = {0,255,255,0}; // draw the actual physics positions, not the cleaned up animation position m_ragdoll.list[i].pObject->GetPositionMatrix( &matrix ); const CPhysCollide *pCollide = m_ragdoll.list[i].pObject->GetCollide(); engine->DebugDrawPhysCollide( pCollide, pWireframe, matrix, debugColor ); } #if RAGDOLL_VISUALIZE for ( i = 0; i < m_ragdoll.listCount; i++ ) { static color32 debugColor = {255,0,0,0}; const CPhysCollide *pCollide = m_ragdoll.list[i].pObject->GetCollide(); engine->DebugDrawPhysCollide( pCollide, pWireframe, m_savedBone1[m_ragdoll.boneIndex[i]], debugColor ); } for ( i = 0; i < m_ragdoll.listCount; i++ ) { static color32 debugColor = {0,255,0,0}; const CPhysCollide *pCollide = m_ragdoll.list[i].pObject->GetCollide(); engine->DebugDrawPhysCollide( pCollide, pWireframe, m_savedBone2[m_ragdoll.boneIndex[i]], debugColor ); } for ( i = 0; i < m_ragdoll.listCount; i++ ) { static color32 debugColor = {0,0,255,0}; const CPhysCollide *pCollide = m_ragdoll.list[i].pObject->GetCollide(); engine->DebugDrawPhysCollide( pCollide, pWireframe, m_savedBone3[m_ragdoll.boneIndex[i]], debugColor ); } #endif } CRagdoll *CreateRagdoll( C_BaseEntity *ent, CStudioHdr *pstudiohdr, const Vector &forceVector, int forceBone, const matrix3x4_t *pDeltaBones0, const matrix3x4_t *pDeltaBones1, const matrix3x4_t *pCurrentBonePosition, float dt, bool bFixedConstraints ) { CRagdoll *pRagdoll = new CRagdoll; pRagdoll->Init( ent, pstudiohdr, forceVector, forceBone, pDeltaBones0, pDeltaBones1, pCurrentBonePosition, dt, bFixedConstraints ); if ( !pRagdoll->IsValid() ) { Msg("Bad ragdoll for %s\n", pstudiohdr->pszName() ); delete pRagdoll; pRagdoll = NULL; } return pRagdoll; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- class C_ServerRagdoll : public C_BaseAnimating { public: DECLARE_CLASS( C_ServerRagdoll, C_BaseAnimating ); DECLARE_CLIENTCLASS(); DECLARE_INTERPOLATION(); C_ServerRagdoll( void ); virtual void PostDataUpdate( DataUpdateType_t updateType ); virtual int InternalDrawModel( int flags ); virtual CStudioHdr *OnNewModel( void ); virtual unsigned char GetClientSideFade(); virtual void SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights ); void GetRenderBounds( Vector& theMins, Vector& theMaxs ); virtual void AddEntity( void ); virtual void AccumulateLayers( IBoneSetup &boneSetup, Vector pos[], Quaternion q[], float currentTime ); virtual void BuildTransformations( CStudioHdr *pStudioHdr, Vector *pos, Quaternion q[], const matrix3x4_t &cameraTransform, int boneMask, CBoneBitList &boneComputed ); IPhysicsObject *GetElement( int elementNum ); virtual void UpdateOnRemove(); virtual float LastBoneChangedTime(); // Incoming from network Vector m_ragPos[RAGDOLL_MAX_ELEMENTS]; QAngle m_ragAngles[RAGDOLL_MAX_ELEMENTS]; CInterpolatedVarArray< Vector, RAGDOLL_MAX_ELEMENTS > m_iv_ragPos; CInterpolatedVarArray< QAngle, RAGDOLL_MAX_ELEMENTS > m_iv_ragAngles; int m_elementCount; int m_boneIndex[RAGDOLL_MAX_ELEMENTS]; private: C_ServerRagdoll( const C_ServerRagdoll &src ); typedef CHandle CBaseAnimatingHandle; CNetworkVar( CBaseAnimatingHandle, m_hUnragdoll ); CNetworkVar( float, m_flBlendWeight ); float m_flBlendWeightCurrent; CNetworkVar( int, m_nOverlaySequence ); float m_flLastBoneChangeTime; }; EXTERN_RECV_TABLE(DT_Ragdoll); IMPLEMENT_CLIENTCLASS_DT(C_ServerRagdoll, DT_Ragdoll, CRagdollProp) RecvPropArray(RecvPropQAngles(RECVINFO(m_ragAngles[0])), m_ragAngles), RecvPropArray(RecvPropVector(RECVINFO(m_ragPos[0])), m_ragPos), RecvPropEHandle(RECVINFO(m_hUnragdoll)), RecvPropFloat(RECVINFO(m_flBlendWeight)), RecvPropInt(RECVINFO(m_nOverlaySequence)), END_RECV_TABLE() C_ServerRagdoll::C_ServerRagdoll( void ) : m_iv_ragPos("C_ServerRagdoll::m_iv_ragPos"), m_iv_ragAngles("C_ServerRagdoll::m_iv_ragAngles") { m_elementCount = 0; m_flLastBoneChangeTime = -FLT_MAX; AddVar( m_ragPos, &m_iv_ragPos, LATCH_SIMULATION_VAR ); AddVar( m_ragAngles, &m_iv_ragAngles, LATCH_SIMULATION_VAR ); m_flBlendWeight = 0.0f; m_flBlendWeightCurrent = 0.0f; m_nOverlaySequence = -1; m_flFadeScale = 1; } void C_ServerRagdoll::PostDataUpdate( DataUpdateType_t updateType ) { BaseClass::PostDataUpdate( updateType ); m_iv_ragPos.NoteChanged( gpGlobals->curtime, true ); m_iv_ragAngles.NoteChanged( gpGlobals->curtime, true ); // this is the local client time at which this update becomes stale m_flLastBoneChangeTime = gpGlobals->curtime + GetInterpolationAmount(m_iv_ragPos.GetType()); } float C_ServerRagdoll::LastBoneChangedTime() { return m_flLastBoneChangeTime; } int C_ServerRagdoll::InternalDrawModel( int flags ) { int ret = BaseClass::InternalDrawModel( flags ); if ( vcollide_wireframe.GetBool() ) { vcollide_t *pCollide = modelinfo->GetVCollide( GetModelIndex() ); IMaterial *pWireframe = materials->FindMaterial("shadertest/wireframevertexcolor", TEXTURE_GROUP_OTHER); matrix3x4_t matrix; for ( int i = 0; i < m_elementCount; i++ ) { static color32 debugColor = {0,255,255,0}; AngleMatrix( m_ragAngles[i], m_ragPos[i], matrix ); engine->DebugDrawPhysCollide( pCollide->solids[i], pWireframe, matrix, debugColor ); } } return ret; } CStudioHdr *C_ServerRagdoll::OnNewModel( void ) { CStudioHdr *hdr = BaseClass::OnNewModel(); if ( !m_elementCount ) { vcollide_t *pCollide = modelinfo->GetVCollide( GetModelIndex() ); if ( !pCollide ) { const char *pszName = modelinfo->GetModelName( modelinfo->GetModel( GetModelIndex() ) ); Msg( "*** ERROR: C_ServerRagdoll::InitModel: %s missing vcollide data ***\n", (pszName) ? pszName : "" ); m_elementCount = 0; } else { m_elementCount = RagdollExtractBoneIndices( m_boneIndex, hdr, pCollide ); } m_iv_ragPos.SetMaxCount( m_elementCount ); m_iv_ragAngles.SetMaxCount( m_elementCount ); } return hdr; } //----------------------------------------------------------------------------- // Purpose: clear out any face/eye values stored in the material system //----------------------------------------------------------------------------- void C_ServerRagdoll::SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights ) { BaseClass::SetupWeights( pBoneToWorld, nFlexWeightCount, pFlexWeights, pFlexDelayedWeights ); CStudioHdr *hdr = GetModelPtr(); if ( !hdr ) return; int nFlexDescCount = hdr->numflexdesc(); if ( nFlexDescCount ) { Assert( !pFlexDelayedWeights ); memset( pFlexWeights, 0, nFlexWeightCount * sizeof(float) ); } if ( m_iEyeAttachment > 0 ) { matrix3x4_t attToWorld; if (GetAttachment( m_iEyeAttachment, attToWorld )) { Vector local, tmp; local.Init( 1000.0f, 0.0f, 0.0f ); VectorTransform( local, attToWorld, tmp ); modelrender->SetViewTarget( GetModelPtr(), GetBody(), tmp ); } } } void C_ServerRagdoll::GetRenderBounds( Vector& theMins, Vector& theMaxs ) { if( !CollisionProp()->IsBoundsDefinedInEntitySpace() ) { IRotateAABB( EntityToWorldTransform(), CollisionProp()->OBBMins(), CollisionProp()->OBBMaxs(), theMins, theMaxs ); } else { theMins = CollisionProp()->OBBMins(); theMaxs = CollisionProp()->OBBMaxs(); } } void C_ServerRagdoll::AddEntity( void ) { BaseClass::AddEntity(); // Move blend weight toward target over 0.2 seconds m_flBlendWeightCurrent = Approach( m_flBlendWeight, m_flBlendWeightCurrent, gpGlobals->frametime * 5.0f ); } void C_ServerRagdoll::AccumulateLayers( IBoneSetup &boneSetup, Vector pos[], Quaternion q[], float currentTime ) { BaseClass::AccumulateLayers( boneSetup, pos, q, currentTime ); if ( m_nOverlaySequence >= 0 && m_nOverlaySequence < boneSetup.GetStudioHdr()->GetNumSeq() ) { boneSetup.AccumulatePose( pos, q, m_nOverlaySequence, GetCycle(), m_flBlendWeightCurrent, currentTime, m_pIk ); } } void C_ServerRagdoll::BuildTransformations( CStudioHdr *hdr, Vector *pos, Quaternion q[], const matrix3x4_t &cameraTransform, int boneMask, CBoneBitList &boneComputed ) { if ( !hdr ) return; matrix3x4_t bonematrix; bool boneSimulated[MAXSTUDIOBONES]; // no bones have been simulated memset( boneSimulated, 0, sizeof(boneSimulated) ); mstudiobone_t *pbones = hdr->pBone( 0 ); mstudioseqdesc_t *pSeqDesc = NULL; if ( m_nOverlaySequence >= 0 && m_nOverlaySequence < hdr->GetNumSeq() ) { pSeqDesc = &hdr->pSeqdesc( m_nOverlaySequence ); } int i; for ( i = 0; i < m_elementCount; i++ ) { int iBone = m_boneIndex[i]; if ( iBone >= 0 ) { if ( hdr->boneFlags( iBone ) & boneMask ) { boneSimulated[iBone] = true; matrix3x4_t &matrix = GetBoneForWrite( iBone ); if ( m_flBlendWeightCurrent != 0.0f && pSeqDesc && // FIXME: this bone access is illegal pSeqDesc->weight( iBone ) != 0.0f ) { // Use the animated bone position instead boneSimulated[iBone] = false; } else { AngleMatrix( m_ragAngles[i], m_ragPos[i], matrix ); } } } } for ( i = 0; i < hdr->numbones(); i++ ) { if ( !( hdr->boneFlags( i ) & boneMask ) ) continue; // BUGBUG: Merge this code with the code in c_baseanimating somehow!!! // animate all non-simulated bones if ( boneSimulated[i] || CalcProceduralBone( hdr, i, m_BoneAccessor ) ) { continue; } else { QuaternionMatrix( q[i], pos[i], bonematrix ); if (pbones[i].parent == -1) { ConcatTransforms( cameraTransform, bonematrix, GetBoneForWrite( i ) ); } else { ConcatTransforms( GetBone( pbones[i].parent ), bonematrix, GetBoneForWrite( i ) ); } } if ( pbones[i].parent == -1 ) { // Apply client-side effects to the transformation matrix // ApplyBoneMatrixTransform( GetBoneForWrite( i ) ); } } } IPhysicsObject *C_ServerRagdoll::GetElement( int elementNum ) { return NULL; } //----------------------------------------------------------------------------- // Purpose: // Output : virtual void //----------------------------------------------------------------------------- void C_ServerRagdoll::UpdateOnRemove() { C_BaseAnimating *anim = m_hUnragdoll.Get(); if ( NULL != anim && anim->GetModel() && ( anim->GetModel() == GetModel() ) ) { // Need to tell C_BaseAnimating to blend out of the ragdoll data that we received last C_BaseAnimating::AutoAllowBoneAccess boneaccess( true, false ); anim->CreateUnragdollInfo( this ); } // Do last to mimic destrictor order BaseClass::UpdateOnRemove(); } //----------------------------------------------------------------------------- // Fade out //----------------------------------------------------------------------------- unsigned char C_ServerRagdoll::GetClientSideFade() { return UTIL_ComputeEntityFade( this, m_fadeMinDist, m_fadeMaxDist, m_flFadeScale ); } static int GetHighestBit( int flags ) { for ( int i = 31; i >= 0; --i ) { if ( flags & (1<boneFlags( iBone ); if ( !(boneFlags & boneMask) ) { // BUGBUG: The attached bone is required and this call is going to skip it, so force it // HACKHACK: Assume the highest bit numbered bone flag is the minimum bone set boneMask |= GetHighestBit( boneFlags ); } } return BaseClass::SetupBones( pBoneToWorldOut, nMaxBones, boneMask, currentTime ); } virtual void BuildTransformations( CStudioHdr *hdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed ) { VPROF_BUDGET( "C_ServerRagdollAttached::SetupBones", VPROF_BUDGETGROUP_CLIENT_ANIMATION ); if ( !hdr ) return; float frac = RemapVal( gpGlobals->curtime, m_parentTime, m_parentTime+ATTACH_INTERP_TIME, 0, 1 ); frac = clamp( frac, 0.f, 1.f ); // interpolate offset over some time Vector offset = m_vecOffset * (1-frac); C_BaseAnimating *parent = assert_cast< C_BaseAnimating* >( GetMoveParent() ); Vector worldOrigin; worldOrigin.Init(); if ( parent ) { Assert( parent != this ); parent->SetupBones( NULL, -1, BONE_USED_BY_ANYTHING, gpGlobals->curtime ); matrix3x4_t boneToWorld; parent->GetCachedBoneMatrix( m_boneIndexAttached, boneToWorld ); VectorTransform( m_attachmentPointBoneSpace, boneToWorld, worldOrigin ); } BaseClass::BuildTransformations( hdr, pos, q, cameraTransform, boneMask, boneComputed ); if ( parent ) { int iBone = m_boneIndex[m_ragdollAttachedObjectIndex]; const matrix3x4_t &matrix = GetBone( iBone ); Vector ragOrigin; VectorTransform( m_attachmentPointRagdollSpace, matrix, ragOrigin ); offset = worldOrigin - ragOrigin; // fixes culling SetAbsOrigin( worldOrigin ); m_vecOffset = offset; } for ( int i = 0; i < hdr->numbones(); i++ ) { if ( !( hdr->boneFlags( i ) & boneMask ) ) continue; Vector vPos; matrix3x4_t &matrix = GetBoneForWrite( i ); MatrixGetColumn( matrix, 3, vPos ); vPos += offset; MatrixSetColumn( vPos, 3, matrix ); } } void OnDataChanged( DataUpdateType_t updateType ); virtual float LastBoneChangedTime() { return FLT_MAX; } Vector m_attachmentPointBoneSpace; Vector m_vecOffset; Vector m_attachmentPointRagdollSpace; int m_ragdollAttachedObjectIndex; int m_boneIndexAttached; float m_parentTime; bool m_bHasParent; private: C_ServerRagdollAttached( const C_ServerRagdollAttached & ); }; EXTERN_RECV_TABLE(DT_Ragdoll_Attached); IMPLEMENT_CLIENTCLASS_DT(C_ServerRagdollAttached, DT_Ragdoll_Attached, CRagdollPropAttached) RecvPropInt( RECVINFO( m_boneIndexAttached ) ), RecvPropInt( RECVINFO( m_ragdollAttachedObjectIndex ) ), RecvPropVector(RECVINFO(m_attachmentPointBoneSpace) ), RecvPropVector(RECVINFO(m_attachmentPointRagdollSpace) ), END_RECV_TABLE() void C_ServerRagdollAttached::OnDataChanged( DataUpdateType_t updateType ) { BaseClass::OnDataChanged( updateType ); bool bParentNow = GetMoveParent() ? true : false; if ( m_bHasParent != bParentNow ) { if ( m_bHasParent ) { m_parentTime = gpGlobals->curtime; } m_bHasParent = bParentNow; } } struct ragdoll_remember_t { C_BaseEntity *ragdoll; int tickCount; }; struct ragdoll_memory_list_t { CUtlVector list; int tickCount; void Update() { if ( tickCount > gpGlobals->tickcount ) { list.RemoveAll(); return; } for ( int i = list.Count()-1; i >= 0; --i ) { if ( list[i].tickCount != gpGlobals->tickcount ) { list.FastRemove(i); } } } bool IsInList( C_BaseEntity *pRagdoll ) { for ( int i = list.Count()-1; i >= 0; --i ) { if ( list[i].ragdoll == pRagdoll ) return true; } return false; } void AddToList( C_BaseEntity *pRagdoll ) { Update(); int index = list.AddToTail(); list[index].ragdoll = pRagdoll; list[index].tickCount = gpGlobals->tickcount; } }; static ragdoll_memory_list_t gRagdolls; void NoteRagdollCreationTick( C_BaseEntity *pRagdoll ) { gRagdolls.AddToList( pRagdoll ); } // returns true if the ragdoll was created on this tick bool WasRagdollCreatedOnCurrentTick( C_BaseEntity *pRagdoll ) { gRagdolls.Update(); return gRagdolls.IsInList( pRagdoll ); }