You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
974 lines
28 KiB
974 lines
28 KiB
//===== 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<void *>( 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<C_BaseEntity *>(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<C_BaseAnimating> 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 : "<null>" );
|
|
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<<i) )
|
|
return (1<<i);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define ATTACH_INTERP_TIME 0.2
|
|
class C_ServerRagdollAttached : public C_ServerRagdoll
|
|
{
|
|
DECLARE_CLASS( C_ServerRagdollAttached, C_ServerRagdoll );
|
|
public:
|
|
C_ServerRagdollAttached( void )
|
|
{
|
|
m_bHasParent = false;
|
|
m_vecOffset.Init();
|
|
}
|
|
DECLARE_CLIENTCLASS();
|
|
bool SetupBones( matrix3x4a_t *pBoneToWorldOut, int nMaxBones, int boneMask, float currentTime )
|
|
{
|
|
if ( GetMoveParent() )
|
|
{
|
|
// HACKHACK: Force the attached bone to be set up
|
|
int index = m_boneIndex[m_ragdollAttachedObjectIndex];
|
|
int boneFlags = GetModelPtr()->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<ragdoll_remember_t> 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 );
|
|
}
|
|
|