//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// // // Purpose: // // $NoKeywords: $ //===========================================================================// #include "cbase.h" #include "mathlib/vmatrix.h" #include "ragdoll_shared.h" #include "bone_setup.h" #if defined( _PS3 ) #include "bone_setup_PS3.h" #endif #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" #include "clientalphaproperty.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #ifdef _DEBUG extern ConVar r_FadeProps; #endif extern ConVar r_sequence_debug; CRagdoll::CRagdoll() { m_ragdoll.listCount = 0; m_vecLastOrigin.Init(); m_flLastOriginChangeTime = - 1.0f; m_flAwakeTime = -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 ), DEFINE_RAGDOLL_ELEMENT( 24 ), DEFINE_RAGDOLL_ELEMENT( 25 ), DEFINE_RAGDOLL_ELEMENT( 26 ), DEFINE_RAGDOLL_ELEMENT( 27 ), DEFINE_RAGDOLL_ELEMENT( 28 ), DEFINE_RAGDOLL_ELEMENT( 29 ), DEFINE_RAGDOLL_ELEMENT( 30 ), DEFINE_RAGDOLL_ELEMENT( 31 ), 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; float radius = size.Length(); m_mins.Init(-radius,-radius,-radius); m_maxs.Init(radius,radius,radius); } static ConVar cl_ragdoll_self_collision( "cl_ragdoll_self_collision", "1", FCVAR_DEVELOPMENTONLY ); extern ConVar cl_ragdoll_collide; 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, bool bBleedOutOnSleep ) { ragdollparams_t params; params.pGameData = static_cast( ent ); params.modelIndex = ent->GetModelIndex(); params.pCollide = modelinfo->GetVCollide( params.modelIndex ); if ( !params.pCollide ) { return; } 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 ); // forcibly disable ragdoll self collisions on the client in L4D / x360 to save perf for ( int i = 0; i < m_ragdoll.listCount; i++ ) { m_ragdoll.list[i].pObject->SetUseAlternateGravity(true); if ( !cl_ragdoll_self_collision.GetBool() && !cl_ragdoll_collide.GetBool() ) { PhysSetGameFlags(m_ragdoll.list[i].pObject, FVPHYSICS_NO_SELF_COLLISIONS); m_ragdoll.list[i].pObject->SetCollisionHints(COLLISION_HINT_DEBRIS); } } RagdollApplyAnimationAsVelocity( m_ragdoll, pDeltaBones0, pDeltaBones1, dt ); RagdollActivate( m_ragdoll, params.pCollide, ent->GetModelIndex() ); m_ragdoll.list[0].pObject->GetPosition( &m_origin, 0 ); // It's moving now... m_flLastOriginChangeTime = physenv->GetSimulationTime(); m_flAwakeTime = physenv->GetSimulationTime(); m_doBleedOut = bBleedOutOnSleep; // 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, const 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( ) { 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_ragdoll.list[0].pObject->GetPosition( &m_origin, 0 ); m_lastUpdate = gpGlobals->curtime; Vector origin = GetRagdollOrigin(); RagdollComputeApproximateBbox( m_ragdoll, origin, m_mins, m_maxs ); m_mins -= origin; m_maxs -= origin; m_allAsleep = RagdollIsAsleep( m_ragdoll ); if ( !m_allAsleep ) { 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(); } else if ( m_doBleedOut ) { // Stopped moving, now create the blood pool CreateBloodPool(); } } //----------------------------------------------------------------------------- // Purpose: 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; } //----------------------------------------------------------------------------- // Purpose: // Input : - //----------------------------------------------------------------------------- void CRagdoll::PhysForceRagdollToSleep() { IPhysicsObject *pList[32]; Vector vel; AngularImpulse angVel; vel.Init(); angVel.Init(); for ( int i = 0; i < m_ragdoll.listCount; i++ ) { if ( m_ragdoll.list[i].pObject ) { m_ragdoll.list[i].pObject->SetVelocity( &vel, &angVel ); pList[i] = m_ragdoll.list[i].pObject; } } physenv->ForceObjectsToSleep(pList, m_ragdoll.listCount); } #define RAGDOLL_SLEEP_TOLERANCE 1.0f static ConVar ragdoll_sleepaftertime( "ragdoll_sleepaftertime", "4", 0, "After this many seconds of being basically stationary, the ragdoll will go to sleep." ); void CRagdoll::CheckSettleStationaryRagdoll() { float dt = physenv->GetSimulationTime() - m_flAwakeTime; float tolerance = clamp( (RAGDOLL_SLEEP_TOLERANCE * dt), RAGDOLL_SLEEP_TOLERANCE, (RAGDOLL_SLEEP_TOLERANCE * 10) ); Vector delta = GetRagdollOrigin() - m_vecLastOrigin; for ( int i = 0; i < 3; ++i ) { // It's still moving... if ( fabs( delta[ i ] ) > tolerance ) { m_flLastOriginChangeTime = physenv->GetSimulationTime(); m_vecLastOrigin = GetRagdollOrigin(); //Msg( "%d [%p] Still moving (tolerance is %f, dt is %f)\n", gpGlobals->tickcount, this, tolerance, dt ); return; } } //Msg( "%d [%p] Settling - tolerance is %f, delta is %f %f %f\n", gpGlobals->tickcount, this, tolerance, delta.x, delta.y, delta.z ); // It has stopped moving, see if it dt = physenv->GetSimulationTime() - m_flLastOriginChangeTime; if ( dt < ragdoll_sleepaftertime.GetFloat() ) return; //Msg( "%d [%p] FORCE SLEEP\n",gpGlobals->tickcount, this ); // Force it to go to sleep PhysForceRagdollToSleep(); // Now that it stopped, create the blood pool CreateBloodPool(); } void CRagdoll::ResetRagdollSleepAfterTime( void ) { m_flLastOriginChangeTime = physenv->GetSimulationTime(); m_flAwakeTime = physenv->GetSimulationTime(); } #define ALLOW_BLOOD_POOL 0 void CRagdoll::CreateBloodPool( void ) { #if ALLOW_BLOOD_POOL if ( m_doBleedOut ) { DispatchParticleEffect( "blood_pool", GetRagdollOrigin(), QAngle( 0, 0, 0 ) ); m_doBleedOut = false; } #endif } void CRagdoll::DrawWireframe() { IMaterial *pWireframe = materials->FindMaterial("debug/debugwireframevertexcolor", 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, bool bBleedOutOnSleep ) { CRagdoll *pRagdoll = new CRagdoll; pRagdoll->Init( ent, pstudiohdr, forceVector, forceBone, pDeltaBones0, pDeltaBones1, pCurrentBonePosition, dt, bFixedConstraints, bBleedOutOnSleep ); 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 ); // Inherited from IClientUnknown public: virtual IClientModelRenderable* GetClientModelRenderable(); virtual void PostDataUpdate( DataUpdateType_t updateType ); virtual int InternalDrawModel( int flags, const RenderableInstance_t &instance ); virtual CStudioHdr *OnNewModel( void ); virtual void SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights ); void GetRenderBounds( Vector& theMins, Vector& theMaxs ); virtual bool Simulate( void ); virtual void AccumulateLayers( IBoneSetup &boneSetup, BoneVector pos[], BoneQuaternion q[], float currentTime ); virtual void BuildTransformations( CStudioHdr *pStudioHdr, BoneVector *pos, BoneQuaternion q[], const matrix3x4_t &cameraTransform, int boneMask, CBoneBitList &boneComputed ); IPhysicsObject *GetElement( int elementNum ); virtual void UpdateOnRemove(); virtual float LastBoneChangedTime(); #if defined( _PS3 ) virtual void AccumulateLayers_AddPoseCalls( IBoneSetup_PS3 &boneSetup, BoneVector pos[], BoneQuaternion q[], float currentTime ); #endif // 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; } void C_ServerRagdoll::PostDataUpdate( DataUpdateType_t updateType ) { BaseClass::PostDataUpdate( updateType ); m_iv_ragPos.NoteChanged( gpGlobals->curtime, gpGlobals->curtime, true ); m_iv_ragAngles.NoteChanged( gpGlobals->curtime, gpGlobals->curtime, true ); // this is the local client time at which this update becomes stale m_flLastBoneChangeTime = gpGlobals->curtime + GetInterpolationAmount(m_iv_ragPos.GetType()); if ( m_flBlendWeightCurrent != m_flBlendWeight ) { AddToEntityList( ENTITY_LIST_SIMULATE ); } } float C_ServerRagdoll::LastBoneChangedTime() { return m_flLastBoneChangeTime; } int C_ServerRagdoll::InternalDrawModel( int flags, const RenderableInstance_t &instance ) { int ret = BaseClass::InternalDrawModel( flags, instance ); if ( vcollide_wireframe.GetBool() ) { vcollide_t *pCollide = modelinfo->GetVCollide( GetModelIndex() ); IMaterial *pWireframe = materials->FindMaterial("debug/debugwireframevertexcolor", 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; 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( gpGlobals->curtime, m_elementCount ); m_iv_ragAngles.SetMaxCount( gpGlobals->curtime, m_elementCount ); } return hdr; } //----------------------------------------------------------------------------- // Should we participate in the model fast path? //----------------------------------------------------------------------------- IClientModelRenderable* C_ServerRagdoll::GetClientModelRenderable() { // FIXME: Once we get modelrender->SetViewTarget working in the model fast // path, we can eliminate the check for eye attachment if ( m_iEyeAttachment > 0 ) return NULL; return BaseClass::GetClientModelRenderable(); } //----------------------------------------------------------------------------- // 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 ) { memset( pFlexWeights, 0, nFlexWeightCount * sizeof(float) ); if ( pFlexDelayedWeights ) { memset( pFlexDelayedWeights, 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(); } } bool C_ServerRagdoll::Simulate( void ) { bool bRet = BaseClass::Simulate(); // Move blend weight toward target over 0.2 seconds m_flBlendWeightCurrent = Approach( m_flBlendWeight, m_flBlendWeightCurrent, gpGlobals->frametime * 5.0f ); if ( m_flBlendWeightCurrent != m_flBlendWeight ) bRet = true; return bRet; } void C_ServerRagdoll::AccumulateLayers( IBoneSetup &boneSetup, BoneVector pos[], BoneQuaternion q[], float currentTime ) { BaseClass::AccumulateLayers( boneSetup, pos, q, currentTime ); if ( m_nOverlaySequence >= 0 && m_nOverlaySequence < boneSetup.GetStudioHdr()->GetNumSeq() ) { if (r_sequence_debug.GetInt() == entindex()) { DevMsgRT( "%8.4f : %30s : %5.3f : %4.2f\n", currentTime, boneSetup.GetStudioHdr()->pSeqdesc( m_nOverlaySequence ).pszLabel(), GetCycle(), m_flBlendWeightCurrent ); } boneSetup.AccumulatePose( pos, q, m_nOverlaySequence, GetCycle(), m_flBlendWeightCurrent, currentTime, m_pIk ); } } #if defined( _PS3 ) void C_ServerRagdoll::AccumulateLayers_AddPoseCalls( IBoneSetup_PS3 &boneSetup, BoneVector pos[], BoneQuaternion q[], float currentTime ) { BaseClass::AccumulateLayers_AddPoseCalls( boneSetup, pos, q, currentTime ); if ( m_nOverlaySequence >= 0 && m_nOverlaySequence < boneSetup.GetStudioHdr()->GetNumSeq() ) { if (r_sequence_debug.GetInt() == entindex()) { DevMsgRT( "%8.4f : %30s : %5.3f : %4.2f\n", currentTime, boneSetup.GetStudioHdr()->pSeqdesc( m_nOverlaySequence ).pszLabel(), GetCycle(), m_flBlendWeightCurrent ); } // boneSetup.AccumulatePose( pos, q, m_nOverlaySequence, GetCycle(), m_flBlendWeightCurrent, currentTime, m_pIk ); boneSetup.AccumulatePose_AddToBoneJob( boneSetup.GetBoneJobSPU(), m_nOverlaySequence, GetCycle(), m_flBlendWeightCurrent, m_pIk, 0 ); } } #endif void C_ServerRagdoll::BuildTransformations( CStudioHdr *hdr, BoneVector *pos, BoneQuaternion q[], const matrix3x4_t &cameraTransform, int boneMask, CBoneBitList &boneComputed ) { if ( !hdr ) return; matrix3x4a_t bonematrix; bool boneSimulated[MAXSTUDIOBONES]; // no bones have been simulated memset( boneSimulated, 0, sizeof(boneSimulated) ); const 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 index = m_boneIndex[i]; if ( index >= 0 ) { if ( hdr->boneFlags(index) & boneMask ) { boneSimulated[index] = true; matrix3x4_t &matrix = GetBoneForWrite( index ); if ( m_flBlendWeightCurrent != 0.0f && pSeqDesc && // FIXME: this bone access is illegal pSeqDesc->weight( index ) != 0.0f ) { // Use the animated bone position instead boneSimulated[index] = 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_Aligned( 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(); } static int GetHighestBit( int flags ) { for ( int i = 31; i >= 0; --i ) { if ( flags & (1<boneFlags(index); 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, BoneVector *pos, BoneQuaternion 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, 1 ); // 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 index = m_boneIndex[m_ragdollAttachedObjectIndex]; const matrix3x4_t &matrix = GetBone( index ); 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 pos; matrix3x4_t &matrix = GetBoneForWrite( i ); MatrixGetColumn( matrix, 3, pos ); pos += offset; MatrixSetColumn( pos, 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 ); }