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.
1079 lines
30 KiB
1079 lines
30 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "base_playeranimstate.h" |
|
#include "tier0/vprof.h" |
|
#include "animation.h" |
|
#include "studio.h" |
|
#include "apparent_velocity_helper.h" |
|
#include "utldict.h" |
|
#include "filesystem.h" |
|
|
|
|
|
#ifdef CLIENT_DLL |
|
#include "c_baseplayer.h" |
|
#include "engine/ivdebugoverlay.h" |
|
|
|
ConVar cl_showanimstate( "cl_showanimstate", "-1", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Show the (client) animation state for the specified entity (-1 for none)." ); |
|
ConVar showanimstate_log( "cl_showanimstate_log", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "1 to output cl_showanimstate to Msg(). 2 to store in AnimStateClient.log. 3 for both." ); |
|
#else |
|
#include "player.h" |
|
ConVar sv_showanimstate( "sv_showanimstate", "-1", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Show the (server) animation state for the specified entity (-1 for none)." ); |
|
ConVar showanimstate_log( "sv_showanimstate_log", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "1 to output sv_showanimstate to Msg(). 2 to store in AnimStateServer.log. 3 for both." ); |
|
#endif |
|
|
|
|
|
// Below this many degrees, slow down turning rate linearly |
|
#define FADE_TURN_DEGREES 45.0f |
|
|
|
// After this, need to start turning feet |
|
#define MAX_TORSO_ANGLE 70.0f |
|
|
|
// Below this amount, don't play a turning animation/perform IK |
|
#define MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION 15.0f |
|
|
|
|
|
ConVar mp_feetyawrate( |
|
"mp_feetyawrate", |
|
"720", |
|
FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, |
|
"How many degrees per second that we can turn our feet or upper body." ); |
|
|
|
ConVar mp_facefronttime( |
|
"mp_facefronttime", |
|
"3", |
|
FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, |
|
"After this amount of time of standing in place but aiming to one side, go ahead and move feet to face upper body." ); |
|
|
|
ConVar mp_ik( "mp_ik", "1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Use IK on in-place turns." ); |
|
|
|
// Pose parameters stored for debugging. |
|
float g_flLastBodyPitch, g_flLastBodyYaw, m_flLastMoveYaw; |
|
|
|
|
|
// ------------------------------------------------------------------------------------------------ // |
|
// CBasePlayerAnimState implementation. |
|
// ------------------------------------------------------------------------------------------------ // |
|
|
|
CBasePlayerAnimState::CBasePlayerAnimState() |
|
{ |
|
m_flEyeYaw = 0.0f; |
|
m_flEyePitch = 0.0f; |
|
m_bCurrentFeetYawInitialized = false; |
|
m_flCurrentTorsoYaw = 0.0f; |
|
m_nTurningInPlace = TURN_NONE; |
|
m_flMaxGroundSpeed = 0.0f; |
|
m_flStoredCycle = 0.0f; |
|
|
|
m_flGaitYaw = 0.0f; |
|
m_flGoalFeetYaw = 0.0f; |
|
m_flCurrentFeetYaw = 0.0f; |
|
m_flLastYaw = 0.0f; |
|
m_flLastTurnTime = 0.0f; |
|
m_angRender.Init(); |
|
m_vLastMovePose.Init(); |
|
m_iCurrent8WayIdleSequence = -1; |
|
m_iCurrent8WayCrouchIdleSequence = -1; |
|
|
|
m_pOuter = NULL; |
|
m_eCurrentMainSequenceActivity = ACT_IDLE; |
|
m_flLastAnimationStateClearTime = 0.0f; |
|
} |
|
|
|
|
|
CBasePlayerAnimState::~CBasePlayerAnimState() |
|
{ |
|
} |
|
|
|
|
|
void CBasePlayerAnimState::Init( CBaseAnimatingOverlay *pPlayer, const CModAnimConfig &config ) |
|
{ |
|
m_pOuter = pPlayer; |
|
m_AnimConfig = config; |
|
ClearAnimationState(); |
|
} |
|
|
|
|
|
void CBasePlayerAnimState::Release() |
|
{ |
|
delete this; |
|
} |
|
|
|
|
|
void CBasePlayerAnimState::ClearAnimationState() |
|
{ |
|
ClearAnimationLayers(); |
|
m_bCurrentFeetYawInitialized = false; |
|
m_flLastAnimationStateClearTime = gpGlobals->curtime; |
|
} |
|
|
|
|
|
float CBasePlayerAnimState::TimeSinceLastAnimationStateClear() const |
|
{ |
|
return gpGlobals->curtime - m_flLastAnimationStateClearTime; |
|
} |
|
|
|
|
|
void CBasePlayerAnimState::Update( float eyeYaw, float eyePitch ) |
|
{ |
|
VPROF( "CBasePlayerAnimState::Update" ); |
|
|
|
// Clear animation overlays because we're about to completely reconstruct them. |
|
ClearAnimationLayers(); |
|
|
|
// Some mods don't want to update the player's animation state if they're dead and ragdolled. |
|
if ( !ShouldUpdateAnimState() ) |
|
{ |
|
ClearAnimationState(); |
|
return; |
|
} |
|
|
|
|
|
CStudioHdr *pStudioHdr = GetOuter()->GetModelPtr(); |
|
// Store these. All the calculations are based on them. |
|
m_flEyeYaw = AngleNormalize( eyeYaw ); |
|
m_flEyePitch = AngleNormalize( eyePitch ); |
|
|
|
// Compute sequences for all the layers. |
|
ComputeSequences( pStudioHdr ); |
|
|
|
|
|
// Compute all the pose params. |
|
ComputePoseParam_BodyPitch( pStudioHdr ); // Look up/down. |
|
ComputePoseParam_BodyYaw(); // Torso rotation. |
|
ComputePoseParam_MoveYaw( pStudioHdr ); // What direction his legs are running in. |
|
|
|
|
|
ComputePlaybackRate(); |
|
|
|
|
|
#ifdef CLIENT_DLL |
|
if ( cl_showanimstate.GetInt() == m_pOuter->entindex() ) |
|
{ |
|
DebugShowAnimStateFull( 5 ); |
|
} |
|
else if ( cl_showanimstate.GetInt() == -2 ) |
|
{ |
|
C_BasePlayer *targetPlayer = C_BasePlayer::GetLocalPlayer(); |
|
|
|
if( targetPlayer && ( targetPlayer->GetObserverMode() == OBS_MODE_IN_EYE || targetPlayer->GetObserverMode() == OBS_MODE_CHASE ) ) |
|
{ |
|
C_BaseEntity *target = targetPlayer->GetObserverTarget(); |
|
|
|
if( target && target->IsPlayer() ) |
|
{ |
|
targetPlayer = ToBasePlayer( target ); |
|
} |
|
} |
|
|
|
if ( m_pOuter == targetPlayer ) |
|
{ |
|
DebugShowAnimStateFull( 6 ); |
|
} |
|
} |
|
#else |
|
if ( sv_showanimstate.GetInt() == m_pOuter->entindex() ) |
|
{ |
|
DebugShowAnimState( 20 ); |
|
} |
|
#endif |
|
} |
|
|
|
|
|
bool CBasePlayerAnimState::ShouldUpdateAnimState() |
|
{ |
|
// By default, don't update their animation state when they're dead because they're |
|
// either a ragdoll or they're not drawn. |
|
return GetOuter()->IsAlive(); |
|
} |
|
|
|
|
|
bool CBasePlayerAnimState::ShouldChangeSequences( void ) const |
|
{ |
|
return true; |
|
} |
|
|
|
|
|
void CBasePlayerAnimState::SetOuterPoseParameter( int iParam, float flValue ) |
|
{ |
|
// Make sure to set all the history values too, otherwise the server can overwrite them. |
|
GetOuter()->SetPoseParameter( iParam, flValue ); |
|
} |
|
|
|
|
|
void CBasePlayerAnimState::ClearAnimationLayers() |
|
{ |
|
VPROF( "CBasePlayerAnimState::ClearAnimationLayers" ); |
|
if ( !m_pOuter ) |
|
return; |
|
|
|
m_pOuter->SetNumAnimOverlays( AIMSEQUENCE_LAYER+NUM_AIMSEQUENCE_LAYERS ); |
|
for ( int i=0; i < m_pOuter->GetNumAnimOverlays(); i++ ) |
|
{ |
|
m_pOuter->GetAnimOverlay( i )->SetOrder( CBaseAnimatingOverlay::MAX_OVERLAYS ); |
|
#ifndef CLIENT_DLL |
|
m_pOuter->GetAnimOverlay( i )->m_fFlags = 0; |
|
#endif |
|
} |
|
} |
|
|
|
|
|
void CBasePlayerAnimState::RestartMainSequence() |
|
{ |
|
CBaseAnimatingOverlay *pPlayer = GetOuter(); |
|
|
|
pPlayer->m_flAnimTime = gpGlobals->curtime; |
|
pPlayer->SetCycle( 0 ); |
|
} |
|
|
|
|
|
void CBasePlayerAnimState::ComputeSequences( CStudioHdr *pStudioHdr ) |
|
{ |
|
VPROF( "CBasePlayerAnimState::ComputeSequences" ); |
|
|
|
ComputeMainSequence(); // Lower body (walk/run/idle). |
|
UpdateInterpolators(); // The groundspeed interpolator uses the main sequence info. |
|
|
|
if ( m_AnimConfig.m_bUseAimSequences ) |
|
{ |
|
ComputeAimSequence(); // Upper body, based on weapon type. |
|
} |
|
} |
|
|
|
void CBasePlayerAnimState::ResetGroundSpeed( void ) |
|
{ |
|
m_flMaxGroundSpeed = GetCurrentMaxGroundSpeed(); |
|
} |
|
|
|
void CBasePlayerAnimState::ComputeMainSequence() |
|
{ |
|
VPROF( "CBasePlayerAnimState::ComputeMainSequence" ); |
|
|
|
CBaseAnimatingOverlay *pPlayer = GetOuter(); |
|
|
|
// Have our class or the mod-specific class determine what the current activity is. |
|
Activity idealActivity = CalcMainActivity(); |
|
|
|
#ifdef CLIENT_DLL |
|
Activity oldActivity = m_eCurrentMainSequenceActivity; |
|
#endif |
|
|
|
// Store our current activity so the aim and fire layers know what to do. |
|
m_eCurrentMainSequenceActivity = idealActivity; |
|
|
|
// Export to our outer class.. |
|
int animDesired = SelectWeightedSequence( TranslateActivity(idealActivity) ); |
|
|
|
#if !defined( HL1_CLIENT_DLL ) && !defined ( HL1_DLL ) |
|
if ( pPlayer->GetSequenceActivity( pPlayer->GetSequence() ) == pPlayer->GetSequenceActivity( animDesired ) ) |
|
return; |
|
#endif |
|
|
|
if ( animDesired < 0 ) |
|
animDesired = 0; |
|
|
|
pPlayer->ResetSequence( animDesired ); |
|
|
|
#ifdef CLIENT_DLL |
|
// If we went from idle to walk, reset the interpolation history. |
|
// Kind of hacky putting this here.. it might belong outside the base class. |
|
if ( (oldActivity == ACT_CROUCHIDLE || oldActivity == ACT_IDLE) && |
|
(idealActivity == ACT_WALK || idealActivity == ACT_RUN_CROUCH) ) |
|
{ |
|
ResetGroundSpeed(); |
|
} |
|
#endif |
|
} |
|
|
|
|
|
|
|
|
|
|
|
void CBasePlayerAnimState::UpdateAimSequenceLayers( |
|
float flCycle, |
|
int iFirstLayer, |
|
bool bForceIdle, |
|
CSequenceTransitioner *pTransitioner, |
|
float flWeightScale |
|
) |
|
{ |
|
float flAimSequenceWeight = 1; |
|
int iAimSequence = CalcAimLayerSequence( &flCycle, &flAimSequenceWeight, bForceIdle ); |
|
if ( iAimSequence == -1 ) |
|
iAimSequence = 0; |
|
|
|
// Feed the current state of the animation parameters to the sequence transitioner. |
|
// It will hand back either 1 or 2 animations in the queue to set, depending on whether |
|
// it's transitioning or not. We just dump those into the animation layers. |
|
pTransitioner->CheckForSequenceChange( |
|
m_pOuter->GetModelPtr(), |
|
iAimSequence, |
|
false, // don't force transitions on the same anim |
|
true // yes, interpolate when transitioning |
|
); |
|
|
|
pTransitioner->UpdateCurrent( |
|
m_pOuter->GetModelPtr(), |
|
iAimSequence, |
|
flCycle, |
|
GetOuter()->GetPlaybackRate(), |
|
gpGlobals->curtime |
|
); |
|
|
|
CAnimationLayer *pDest0 = m_pOuter->GetAnimOverlay( iFirstLayer ); |
|
CAnimationLayer *pDest1 = m_pOuter->GetAnimOverlay( iFirstLayer+1 ); |
|
|
|
if ( pTransitioner->m_animationQueue.Count() == 1 ) |
|
{ |
|
// If only 1 animation, then blend it in fully. |
|
CAnimationLayer *pSource0 = &pTransitioner->m_animationQueue[0]; |
|
*pDest0 = *pSource0; |
|
|
|
pDest0->m_flWeight = 1; |
|
pDest1->m_flWeight = 0; |
|
pDest0->m_nOrder = iFirstLayer; |
|
|
|
#ifndef CLIENT_DLL |
|
pDest0->m_fFlags |= ANIM_LAYER_ACTIVE; |
|
#endif |
|
} |
|
else if ( pTransitioner->m_animationQueue.Count() >= 2 ) |
|
{ |
|
// The first one should be fading out. Fade in the new one inversely. |
|
CAnimationLayer *pSource0 = &pTransitioner->m_animationQueue[0]; |
|
CAnimationLayer *pSource1 = &pTransitioner->m_animationQueue[1]; |
|
|
|
*pDest0 = *pSource0; |
|
*pDest1 = *pSource1; |
|
Assert( pDest0->m_flWeight >= 0.0f && pDest0->m_flWeight <= 1.0f ); |
|
pDest1->m_flWeight = 1 - pDest0->m_flWeight; // This layer just mirrors the other layer's weight (one fades in while the other fades out). |
|
|
|
pDest0->m_nOrder = iFirstLayer; |
|
pDest1->m_nOrder = iFirstLayer+1; |
|
|
|
#ifndef CLIENT_DLL |
|
pDest0->m_fFlags |= ANIM_LAYER_ACTIVE; |
|
pDest1->m_fFlags |= ANIM_LAYER_ACTIVE; |
|
#endif |
|
} |
|
|
|
pDest0->m_flWeight *= flWeightScale * flAimSequenceWeight; |
|
pDest0->m_flWeight = clamp( (float)pDest0->m_flWeight, 0.0f, 1.0f ); |
|
|
|
pDest1->m_flWeight *= flWeightScale * flAimSequenceWeight; |
|
pDest1->m_flWeight = clamp( (float)pDest1->m_flWeight, 0.0f, 1.0f ); |
|
|
|
pDest0->m_flCycle = pDest1->m_flCycle = flCycle; |
|
} |
|
|
|
|
|
void CBasePlayerAnimState::OptimizeLayerWeights( int iFirstLayer, int nLayers ) |
|
{ |
|
int i; |
|
|
|
// Find the total weight of the blended layers, not including the idle layer (iFirstLayer) |
|
float totalWeight = 0.0f; |
|
for ( i=1; i < nLayers; i++ ) |
|
{ |
|
CAnimationLayer *pLayer = m_pOuter->GetAnimOverlay( iFirstLayer+i ); |
|
if ( pLayer->IsActive() && pLayer->m_flWeight > 0.0f ) |
|
{ |
|
totalWeight += pLayer->m_flWeight; |
|
} |
|
} |
|
|
|
// Set the idle layer's weight to be 1 minus the sum of other layer weights |
|
CAnimationLayer *pLayer = m_pOuter->GetAnimOverlay( iFirstLayer ); |
|
if ( pLayer->IsActive() && pLayer->m_flWeight > 0.0f ) |
|
{ |
|
pLayer->m_flWeight = 1.0f - totalWeight; |
|
pLayer->m_flWeight = MAX( (float)pLayer->m_flWeight, 0.0f); |
|
} |
|
|
|
// This part is just an optimization. Since we have the walk/run animations weighted on top of |
|
// the idle animations, all this does is disable the idle animations if the walk/runs are at |
|
// full weighting, which is whenever a guy is at full speed. |
|
// |
|
// So it saves us blending a couple animation layers whenever a guy is walking or running full speed. |
|
int iLastOne = -1; |
|
for ( i=0; i < nLayers; i++ ) |
|
{ |
|
CAnimationLayer *pLayer = m_pOuter->GetAnimOverlay( iFirstLayer+i ); |
|
if ( pLayer->IsActive() && pLayer->m_flWeight > 0.99 ) |
|
iLastOne = i; |
|
} |
|
|
|
if ( iLastOne != -1 ) |
|
{ |
|
for ( int i=iLastOne-1; i >= 0; i-- ) |
|
{ |
|
CAnimationLayer *pLayer = m_pOuter->GetAnimOverlay( iFirstLayer+i ); |
|
#ifdef CLIENT_DLL |
|
pLayer->m_nOrder = CBaseAnimatingOverlay::MAX_OVERLAYS; |
|
#else |
|
pLayer->m_nOrder.Set( CBaseAnimatingOverlay::MAX_OVERLAYS ); |
|
pLayer->m_fFlags = 0; |
|
#endif |
|
} |
|
} |
|
} |
|
|
|
bool CBasePlayerAnimState::ShouldBlendAimSequenceToIdle() |
|
{ |
|
Activity act = GetCurrentMainSequenceActivity(); |
|
|
|
return (act == ACT_RUN || act == ACT_WALK || act == ACT_RUNTOIDLE || act == ACT_RUN_CROUCH); |
|
} |
|
|
|
void CBasePlayerAnimState::ComputeAimSequence() |
|
{ |
|
VPROF( "CBasePlayerAnimState::ComputeAimSequence" ); |
|
|
|
// Synchronize the lower and upper body cycles. |
|
float flCycle = m_pOuter->GetCycle(); |
|
|
|
// Figure out the new cycle time. |
|
UpdateAimSequenceLayers( flCycle, AIMSEQUENCE_LAYER, true, &m_IdleSequenceTransitioner, 1 ); |
|
|
|
if ( ShouldBlendAimSequenceToIdle() ) |
|
{ |
|
// What we do here is blend between the idle upper body animation (like where he's got the dual elites |
|
// held out in front of him but he's not moving) and his walk/run/crouchrun upper body animation, |
|
// weighting it based on how fast he's moving. That way, when he's moving slowly, his upper |
|
// body doesn't jiggle all around. |
|
bool bIsMoving; |
|
float flPlaybackRate = CalcMovementPlaybackRate( &bIsMoving ); |
|
if ( bIsMoving ) |
|
UpdateAimSequenceLayers( flCycle, AIMSEQUENCE_LAYER+2, false, &m_SequenceTransitioner, flPlaybackRate ); |
|
} |
|
|
|
OptimizeLayerWeights( AIMSEQUENCE_LAYER, NUM_AIMSEQUENCE_LAYERS ); |
|
} |
|
|
|
|
|
int CBasePlayerAnimState::CalcSequenceIndex( const char *pBaseName, ... ) |
|
{ |
|
char szFullName[512]; |
|
va_list marker; |
|
va_start( marker, pBaseName ); |
|
Q_vsnprintf( szFullName, sizeof( szFullName ), pBaseName, marker ); |
|
va_end( marker ); |
|
int iSequence = GetOuter()->LookupSequence( szFullName ); |
|
|
|
// Show warnings if we can't find anything here. |
|
if ( iSequence == -1 ) |
|
{ |
|
static CUtlDict<int,int> dict; |
|
if ( dict.Find( szFullName ) == -1 ) |
|
{ |
|
dict.Insert( szFullName, 0 ); |
|
Warning( "CalcSequenceIndex: can't find '%s'.\n", szFullName ); |
|
} |
|
|
|
iSequence = 0; |
|
} |
|
|
|
return iSequence; |
|
} |
|
|
|
|
|
|
|
void CBasePlayerAnimState::UpdateInterpolators() |
|
{ |
|
VPROF( "CBasePlayerAnimState::UpdateInterpolators" ); |
|
|
|
// First, figure out their current max speed based on their current activity. |
|
float flCurMaxSpeed = GetCurrentMaxGroundSpeed(); |
|
m_flMaxGroundSpeed = flCurMaxSpeed; |
|
} |
|
|
|
|
|
float CBasePlayerAnimState::GetInterpolatedGroundSpeed() |
|
{ |
|
return m_flMaxGroundSpeed; |
|
} |
|
|
|
|
|
float CBasePlayerAnimState::CalcMovementPlaybackRate( bool *bIsMoving ) |
|
{ |
|
// Determine ideal playback rate |
|
Vector vel; |
|
GetOuterAbsVelocity( vel ); |
|
|
|
float speed = vel.Length2D(); |
|
bool isMoving = ( speed > MOVING_MINIMUM_SPEED ); |
|
|
|
*bIsMoving = false; |
|
float flReturnValue = 1; |
|
|
|
if ( isMoving && CanThePlayerMove() ) |
|
{ |
|
float flGroundSpeed = GetInterpolatedGroundSpeed(); |
|
if ( flGroundSpeed < 0.001f ) |
|
{ |
|
flReturnValue = 0.01; |
|
} |
|
else |
|
{ |
|
// Note this gets set back to 1.0 if sequence changes due to ResetSequenceInfo below |
|
flReturnValue = speed / flGroundSpeed; |
|
flReturnValue = clamp( flReturnValue, 0.01f, 10.f ); // don't go nuts here. |
|
} |
|
*bIsMoving = true; |
|
} |
|
|
|
return flReturnValue; |
|
} |
|
|
|
|
|
bool CBasePlayerAnimState::CanThePlayerMove() |
|
{ |
|
return true; |
|
} |
|
|
|
|
|
void CBasePlayerAnimState::ComputePlaybackRate() |
|
{ |
|
VPROF( "CBasePlayerAnimState::ComputePlaybackRate" ); |
|
if ( m_AnimConfig.m_LegAnimType != LEGANIM_9WAY && m_AnimConfig.m_LegAnimType != LEGANIM_8WAY ) |
|
{ |
|
// When using a 9-way blend, playback rate is always 1 and we just scale the pose params |
|
// to speed up or slow down the animation. |
|
bool bIsMoving; |
|
float flRate = CalcMovementPlaybackRate( &bIsMoving ); |
|
if ( bIsMoving ) |
|
GetOuter()->SetPlaybackRate( flRate ); |
|
else |
|
GetOuter()->SetPlaybackRate( 1 ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : CBasePlayer |
|
//----------------------------------------------------------------------------- |
|
CBaseAnimatingOverlay *CBasePlayerAnimState::GetOuter() const |
|
{ |
|
return m_pOuter; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : dt - |
|
//----------------------------------------------------------------------------- |
|
void CBasePlayerAnimState::EstimateYaw() |
|
{ |
|
Vector est_velocity; |
|
GetOuterAbsVelocity( est_velocity ); |
|
|
|
float flLength = est_velocity.Length2D(); |
|
if ( flLength > MOVING_MINIMUM_SPEED ) |
|
{ |
|
m_flGaitYaw = atan2( est_velocity[1], est_velocity[0] ); |
|
m_flGaitYaw = RAD2DEG( m_flGaitYaw ); |
|
m_flGaitYaw = AngleNormalize( m_flGaitYaw ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Override for backpeddling |
|
// Input : dt - |
|
//----------------------------------------------------------------------------- |
|
void CBasePlayerAnimState::ComputePoseParam_MoveYaw( CStudioHdr *pStudioHdr ) |
|
{ |
|
VPROF( "CBasePlayerAnimState::ComputePoseParam_MoveYaw" ); |
|
|
|
//Matt: Goldsrc style animations need to not rotate the model |
|
if ( m_AnimConfig.m_LegAnimType == LEGANIM_GOLDSRC ) |
|
{ |
|
#ifndef CLIENT_DLL |
|
//Adrian: Make the model's angle match the legs so the hitboxes match on both sides. |
|
GetOuter()->SetLocalAngles( QAngle( 0, m_flCurrentFeetYaw, 0 ) ); |
|
#endif |
|
} |
|
|
|
// If using goldsrc-style animations where he's moving in the direction that his feet are facing, |
|
// we don't use move yaw. |
|
if ( m_AnimConfig.m_LegAnimType != LEGANIM_9WAY && m_AnimConfig.m_LegAnimType != LEGANIM_8WAY ) |
|
return; |
|
|
|
// view direction relative to movement |
|
float flYaw; |
|
|
|
EstimateYaw(); |
|
|
|
float ang = m_flEyeYaw; |
|
if ( ang > 180.0f ) |
|
{ |
|
ang -= 360.0f; |
|
} |
|
else if ( ang < -180.0f ) |
|
{ |
|
ang += 360.0f; |
|
} |
|
|
|
// calc side to side turning |
|
flYaw = ang - m_flGaitYaw; |
|
// Invert for mapping into 8way blend |
|
flYaw = -flYaw; |
|
flYaw = flYaw - (int)(flYaw / 360) * 360; |
|
|
|
if (flYaw < -180) |
|
{ |
|
flYaw = flYaw + 360; |
|
} |
|
else if (flYaw > 180) |
|
{ |
|
flYaw = flYaw - 360; |
|
} |
|
|
|
|
|
if ( m_AnimConfig.m_LegAnimType == LEGANIM_9WAY ) |
|
{ |
|
#ifndef CLIENT_DLL |
|
//Adrian: Make the model's angle match the legs so the hitboxes match on both sides. |
|
GetOuter()->SetLocalAngles( QAngle( 0, m_flCurrentFeetYaw, 0 ) ); |
|
#endif |
|
|
|
int iMoveX = GetOuter()->LookupPoseParameter( pStudioHdr, "move_x" ); |
|
int iMoveY = GetOuter()->LookupPoseParameter( pStudioHdr, "move_y" ); |
|
if ( iMoveX < 0 || iMoveY < 0 ) |
|
return; |
|
|
|
bool bIsMoving; |
|
float flPlaybackRate = CalcMovementPlaybackRate( &bIsMoving ); |
|
|
|
// Setup the 9-way blend parameters based on our speed and direction. |
|
Vector2D vCurMovePose( 0, 0 ); |
|
|
|
if ( bIsMoving ) |
|
{ |
|
vCurMovePose.x = cos( DEG2RAD( flYaw ) ) * flPlaybackRate; |
|
vCurMovePose.y = -sin( DEG2RAD( flYaw ) ) * flPlaybackRate; |
|
} |
|
|
|
GetOuter()->SetPoseParameter( pStudioHdr, iMoveX, vCurMovePose.x ); |
|
GetOuter()->SetPoseParameter( pStudioHdr, iMoveY, vCurMovePose.y ); |
|
|
|
m_vLastMovePose = vCurMovePose; |
|
} |
|
else |
|
{ |
|
int iMoveYaw = GetOuter()->LookupPoseParameter( pStudioHdr, "move_yaw" ); |
|
if ( iMoveYaw >= 0 ) |
|
{ |
|
GetOuter()->SetPoseParameter( pStudioHdr, iMoveYaw, flYaw ); |
|
m_flLastMoveYaw = flYaw; |
|
|
|
// Now blend in his idle animation. |
|
// This makes the 8-way blend act like a 9-way blend by blending to |
|
// an idle sequence as he slows down. |
|
#if defined(CLIENT_DLL) |
|
bool bIsMoving; |
|
CAnimationLayer *pLayer = m_pOuter->GetAnimOverlay( MAIN_IDLE_SEQUENCE_LAYER ); |
|
|
|
pLayer->m_flWeight = 1 - CalcMovementPlaybackRate( &bIsMoving ); |
|
if ( !bIsMoving ) |
|
{ |
|
pLayer->m_flWeight = 1; |
|
} |
|
|
|
if ( ShouldChangeSequences() ) |
|
{ |
|
// Whenever this layer stops blending, we can choose a new idle sequence to blend to, so he |
|
// doesn't always use the same idle. |
|
if ( pLayer->m_flWeight < 0.02f || m_iCurrent8WayIdleSequence == -1 ) |
|
{ |
|
m_iCurrent8WayIdleSequence = m_pOuter->SelectWeightedSequence( ACT_IDLE ); |
|
m_iCurrent8WayCrouchIdleSequence = m_pOuter->SelectWeightedSequence( ACT_CROUCHIDLE ); |
|
} |
|
|
|
if ( m_eCurrentMainSequenceActivity == ACT_CROUCHIDLE || m_eCurrentMainSequenceActivity == ACT_RUN_CROUCH ) |
|
pLayer->m_nSequence = m_iCurrent8WayCrouchIdleSequence; |
|
else |
|
pLayer->m_nSequence = m_iCurrent8WayIdleSequence; |
|
} |
|
|
|
pLayer->m_flPlaybackRate = 1; |
|
pLayer->m_flCycle += m_pOuter->GetSequenceCycleRate( pStudioHdr, pLayer->m_nSequence ) * gpGlobals->frametime; |
|
pLayer->m_flCycle = fmod( pLayer->m_flCycle, 1 ); |
|
pLayer->m_nOrder = MAIN_IDLE_SEQUENCE_LAYER; |
|
#endif |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBasePlayerAnimState::ComputePoseParam_BodyPitch( CStudioHdr *pStudioHdr ) |
|
{ |
|
VPROF( "CBasePlayerAnimState::ComputePoseParam_BodyPitch" ); |
|
|
|
// Get pitch from v_angle |
|
float flPitch = m_flEyePitch; |
|
if ( flPitch > 180.0f ) |
|
{ |
|
flPitch -= 360.0f; |
|
} |
|
flPitch = clamp( flPitch, -90.f, 90.f ); |
|
|
|
// See if we have a blender for pitch |
|
int pitch = GetOuter()->LookupPoseParameter( pStudioHdr, "body_pitch" ); |
|
if ( pitch < 0 ) |
|
return; |
|
|
|
GetOuter()->SetPoseParameter( pStudioHdr, pitch, flPitch ); |
|
g_flLastBodyPitch = flPitch; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : goal - |
|
// maxrate - |
|
// dt - |
|
// current - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int CBasePlayerAnimState::ConvergeAngles( float goal,float maxrate, float maxgap, float dt, float& current ) |
|
{ |
|
int direction = TURN_NONE; |
|
|
|
float anglediff = goal - current; |
|
anglediff = AngleNormalize( anglediff ); |
|
|
|
float anglediffabs = fabs( anglediff ); |
|
|
|
float scale = 1.0f; |
|
if ( anglediffabs <= FADE_TURN_DEGREES ) |
|
{ |
|
scale = anglediffabs / FADE_TURN_DEGREES; |
|
// Always do at least a bit of the turn ( 1% ) |
|
scale = clamp( scale, 0.01f, 1.0f ); |
|
} |
|
|
|
float maxmove = maxrate * dt * scale; |
|
|
|
if ( anglediffabs > maxgap ) |
|
{ |
|
// gap is too big, jump |
|
maxmove = (anglediffabs - maxgap); |
|
} |
|
|
|
if ( anglediffabs < maxmove ) |
|
{ |
|
// we are close enought, just set the final value |
|
current = goal; |
|
} |
|
else |
|
{ |
|
// adjust value up or down |
|
if ( anglediff > 0 ) |
|
{ |
|
current += maxmove; |
|
direction = TURN_LEFT; |
|
} |
|
else |
|
{ |
|
current -= maxmove; |
|
direction = TURN_RIGHT; |
|
} |
|
} |
|
|
|
current = AngleNormalize( current ); |
|
|
|
return direction; |
|
} |
|
|
|
void CBasePlayerAnimState::ComputePoseParam_BodyYaw() |
|
{ |
|
VPROF( "CBasePlayerAnimState::ComputePoseParam_BodyYaw" ); |
|
|
|
// Find out which way he's running (m_flEyeYaw is the way he's looking). |
|
Vector vel; |
|
GetOuterAbsVelocity( vel ); |
|
bool bIsMoving = vel.Length2D() > MOVING_MINIMUM_SPEED; |
|
|
|
// If we just initialized this guy (maybe he just came into the PVS), then immediately |
|
// set his feet in the right direction, otherwise they'll spin around from 0 to the |
|
// right direction every time someone switches spectator targets. |
|
if ( !m_bCurrentFeetYawInitialized ) |
|
{ |
|
m_bCurrentFeetYawInitialized = true; |
|
m_flGoalFeetYaw = m_flCurrentFeetYaw = m_flEyeYaw; |
|
m_flLastTurnTime = 0.0f; |
|
} |
|
else if ( bIsMoving ) |
|
{ |
|
// player is moving, feet yaw = aiming yaw |
|
if ( m_AnimConfig.m_LegAnimType == LEGANIM_9WAY || m_AnimConfig.m_LegAnimType == LEGANIM_8WAY ) |
|
{ |
|
// His feet point in the direction his eyes are, but they can run in any direction. |
|
m_flGoalFeetYaw = m_flEyeYaw; |
|
} |
|
else |
|
{ |
|
m_flGoalFeetYaw = RAD2DEG( atan2( vel.y, vel.x ) ); |
|
|
|
// If he's running backwards, flip his feet backwards. |
|
Vector vEyeYaw( cos( DEG2RAD( m_flEyeYaw ) ), sin( DEG2RAD( m_flEyeYaw ) ), 0 ); |
|
Vector vFeetYaw( cos( DEG2RAD( m_flGoalFeetYaw ) ), sin( DEG2RAD( m_flGoalFeetYaw ) ), 0 ); |
|
if ( vEyeYaw.Dot( vFeetYaw ) < -0.01 ) |
|
{ |
|
m_flGoalFeetYaw += 180; |
|
} |
|
} |
|
|
|
} |
|
else if ( (gpGlobals->curtime - m_flLastTurnTime) > mp_facefronttime.GetFloat() ) |
|
{ |
|
// player didn't move & turn for quite some time |
|
m_flGoalFeetYaw = m_flEyeYaw; |
|
} |
|
else |
|
{ |
|
// If he's rotated his view further than the model can turn, make him face forward. |
|
float flDiff = AngleNormalize( m_flGoalFeetYaw - m_flEyeYaw ); |
|
|
|
if ( fabs(flDiff) > m_AnimConfig.m_flMaxBodyYawDegrees ) |
|
{ |
|
if ( flDiff > 0 ) |
|
m_flGoalFeetYaw -= m_AnimConfig.m_flMaxBodyYawDegrees; |
|
else |
|
m_flGoalFeetYaw += m_AnimConfig.m_flMaxBodyYawDegrees; |
|
} |
|
} |
|
|
|
m_flGoalFeetYaw = AngleNormalize( m_flGoalFeetYaw ); |
|
|
|
if ( m_flCurrentFeetYaw != m_flGoalFeetYaw ) |
|
{ |
|
ConvergeAngles( m_flGoalFeetYaw, mp_feetyawrate.GetFloat(), m_AnimConfig.m_flMaxBodyYawDegrees, |
|
gpGlobals->frametime, m_flCurrentFeetYaw ); |
|
|
|
m_flLastTurnTime = gpGlobals->curtime; |
|
} |
|
|
|
float flCurrentTorsoYaw = AngleNormalize( m_flEyeYaw - m_flCurrentFeetYaw ); |
|
|
|
// Rotate entire body into position |
|
m_angRender[YAW] = m_flCurrentFeetYaw; |
|
m_angRender[PITCH] = m_angRender[ROLL] = 0; |
|
|
|
SetOuterBodyYaw( flCurrentTorsoYaw ); |
|
g_flLastBodyYaw = flCurrentTorsoYaw; |
|
} |
|
|
|
|
|
|
|
float CBasePlayerAnimState::SetOuterBodyYaw( float flValue ) |
|
{ |
|
int body_yaw = GetOuter()->LookupPoseParameter( "body_yaw" ); |
|
if ( body_yaw < 0 ) |
|
{ |
|
return 0; |
|
} |
|
|
|
SetOuterPoseParameter( body_yaw, flValue ); |
|
return flValue; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : activity - |
|
// Output : Activity |
|
//----------------------------------------------------------------------------- |
|
Activity CBasePlayerAnimState::BodyYawTranslateActivity( Activity activity ) |
|
{ |
|
// Not even standing still, sigh |
|
if ( activity != ACT_IDLE ) |
|
return activity; |
|
|
|
// Not turning |
|
switch ( m_nTurningInPlace ) |
|
{ |
|
default: |
|
case TURN_NONE: |
|
return activity; |
|
case TURN_RIGHT: |
|
case TURN_LEFT: |
|
return mp_ik.GetBool() ? ACT_TURN : activity; |
|
} |
|
|
|
Assert( 0 ); |
|
return activity; |
|
} |
|
|
|
const QAngle& CBasePlayerAnimState::GetRenderAngles() |
|
{ |
|
return m_angRender; |
|
} |
|
|
|
|
|
void CBasePlayerAnimState::GetOuterAbsVelocity( Vector& vel ) const |
|
{ |
|
#if defined( CLIENT_DLL ) |
|
GetOuter()->EstimateAbsVelocity( vel ); |
|
#else |
|
vel = GetOuter()->GetAbsVelocity(); |
|
#endif |
|
} |
|
|
|
|
|
float CBasePlayerAnimState::GetOuterXYSpeed() const |
|
{ |
|
Vector vel; |
|
GetOuterAbsVelocity( vel ); |
|
return vel.Length2D(); |
|
} |
|
|
|
// ----------------------------------------------------------------------------- |
|
void CBasePlayerAnimState::AnimStateLog( const char *pMsg, ... ) |
|
{ |
|
// Format the string. |
|
char str[4096]; |
|
va_list marker; |
|
va_start( marker, pMsg ); |
|
Q_vsnprintf( str, sizeof( str ), pMsg, marker ); |
|
va_end( marker ); |
|
|
|
// Log it? |
|
if ( showanimstate_log.GetInt() == 1 || showanimstate_log.GetInt() == 3 ) |
|
{ |
|
Msg( "%s", str ); |
|
} |
|
|
|
if ( showanimstate_log.GetInt() > 1 ) |
|
{ |
|
#ifdef CLIENT_DLL |
|
const char *fname = "AnimStateClient.log"; |
|
#else |
|
const char *fname = "AnimStateServer.log"; |
|
#endif |
|
static FileHandle_t hFile = filesystem->Open( fname, "wt" ); |
|
filesystem->FPrintf( hFile, "%s", str ); |
|
filesystem->Flush( hFile ); |
|
} |
|
} |
|
|
|
|
|
// ----------------------------------------------------------------------------- |
|
void CBasePlayerAnimState::AnimStatePrintf( int iLine, const char *pMsg, ... ) |
|
{ |
|
// Format the string. |
|
char str[4096]; |
|
va_list marker; |
|
va_start( marker, pMsg ); |
|
Q_vsnprintf( str, sizeof( str ), pMsg, marker ); |
|
va_end( marker ); |
|
|
|
// Show it with Con_NPrintf. |
|
engine->Con_NPrintf( iLine, "%s", str ); |
|
|
|
// Log it. |
|
AnimStateLog( "%s\n", str ); |
|
} |
|
|
|
|
|
// ----------------------------------------------------------------------------- |
|
void CBasePlayerAnimState::DebugShowAnimState( int iStartLine ) |
|
{ |
|
Vector vOuterVel; |
|
GetOuterAbsVelocity( vOuterVel ); |
|
|
|
int iLine = iStartLine; |
|
AnimStatePrintf( iLine++, "main: %s(%d), cycle: %.2f cyclerate: %.2f playbackrate: %.2f\n", |
|
GetSequenceName( m_pOuter->GetModelPtr(), m_pOuter->GetSequence() ), |
|
m_pOuter->GetSequence(), |
|
m_pOuter->GetCycle(), |
|
m_pOuter->GetSequenceCycleRate(m_pOuter->GetModelPtr(), m_pOuter->GetSequence()), |
|
m_pOuter->GetPlaybackRate() |
|
); |
|
|
|
if ( m_AnimConfig.m_LegAnimType == LEGANIM_8WAY ) |
|
{ |
|
CAnimationLayer *pLayer = m_pOuter->GetAnimOverlay( MAIN_IDLE_SEQUENCE_LAYER ); |
|
|
|
AnimStatePrintf( iLine++, "idle: %s, weight: %.2f\n", |
|
GetSequenceName( m_pOuter->GetModelPtr(), pLayer->m_nSequence ), |
|
(float)pLayer->m_flWeight ); |
|
} |
|
|
|
for ( int i=0; i < m_pOuter->GetNumAnimOverlays()-1; i++ ) |
|
{ |
|
CAnimationLayer *pLayer = m_pOuter->GetAnimOverlay( AIMSEQUENCE_LAYER + i ); |
|
#ifdef CLIENT_DLL |
|
AnimStatePrintf( iLine++, "%s(%d), weight: %.2f, cycle: %.2f, order (%d), aim (%d)", |
|
!pLayer->IsActive() ? "-- ": (pLayer->m_nSequence == 0 ? "-- " : GetSequenceName( m_pOuter->GetModelPtr(), pLayer->m_nSequence ) ), |
|
!pLayer->IsActive() ? 0 : (int)pLayer->m_nSequence, |
|
!pLayer->IsActive() ? 0 : (float)pLayer->m_flWeight, |
|
!pLayer->IsActive() ? 0 : (float)pLayer->m_flCycle, |
|
!pLayer->IsActive() ? 0 : (int)pLayer->m_nOrder, |
|
i |
|
); |
|
#else |
|
AnimStatePrintf( iLine++, "%s(%d), flags (%d), weight: %.2f, cycle: %.2f, order (%d), aim (%d)", |
|
!pLayer->IsActive() ? "-- " : ( pLayer->m_nSequence == 0 ? "-- " : GetSequenceName( m_pOuter->GetModelPtr(), pLayer->m_nSequence ) ), |
|
!pLayer->IsActive() ? 0 : (int)pLayer->m_nSequence, |
|
!pLayer->IsActive() ? 0 : (int)pLayer->m_fFlags,// Doesn't exist on client |
|
!pLayer->IsActive() ? 0 : (float)pLayer->m_flWeight, |
|
!pLayer->IsActive() ? 0 : (float)pLayer->m_flCycle, |
|
!pLayer->IsActive() ? 0 : (int)pLayer->m_nOrder, |
|
i |
|
); |
|
#endif |
|
} |
|
|
|
AnimStatePrintf( iLine++, "vel: %.2f, time: %.2f, max: %.2f, animspeed: %.2f", |
|
vOuterVel.Length2D(), gpGlobals->curtime, GetInterpolatedGroundSpeed(), m_pOuter->GetSequenceGroundSpeed(m_pOuter->GetSequence()) ); |
|
|
|
if ( m_AnimConfig.m_LegAnimType == LEGANIM_8WAY ) |
|
{ |
|
AnimStatePrintf( iLine++, "ent yaw: %.2f, body_yaw: %.2f, move_yaw: %.2f, gait_yaw: %.2f, body_pitch: %.2f", |
|
m_angRender[YAW], g_flLastBodyYaw, m_flLastMoveYaw, m_flGaitYaw, g_flLastBodyPitch ); |
|
} |
|
else |
|
{ |
|
AnimStatePrintf( iLine++, "ent yaw: %.2f, body_yaw: %.2f, body_pitch: %.2f, move_x: %.2f, move_y: %.2f", |
|
m_angRender[YAW], g_flLastBodyYaw, g_flLastBodyPitch, m_vLastMovePose.x, m_vLastMovePose.y ); |
|
} |
|
|
|
// Draw a red triangle on the ground for the eye yaw. |
|
float flBaseSize = 10; |
|
float flHeight = 80; |
|
Vector vBasePos = GetOuter()->GetAbsOrigin() + Vector( 0, 0, 3 ); |
|
QAngle angles( 0, 0, 0 ); |
|
angles[YAW] = m_flEyeYaw; |
|
Vector vForward, vRight, vUp; |
|
AngleVectors( angles, &vForward, &vRight, &vUp ); |
|
debugoverlay->AddTriangleOverlay( vBasePos+vRight*flBaseSize/2, vBasePos-vRight*flBaseSize/2, vBasePos+vForward*flHeight, 255, 0, 0, 255, false, 0.01 ); |
|
|
|
// Draw a blue triangle on the ground for the body yaw. |
|
angles[YAW] = m_angRender[YAW]; |
|
AngleVectors( angles, &vForward, &vRight, &vUp ); |
|
debugoverlay->AddTriangleOverlay( vBasePos+vRight*flBaseSize/2, vBasePos-vRight*flBaseSize/2, vBasePos+vForward*flHeight, 0, 0, 255, 255, false, 0.01 ); |
|
|
|
} |
|
|
|
// ----------------------------------------------------------------------------- |
|
void CBasePlayerAnimState::DebugShowAnimStateFull( int iStartLine ) |
|
{ |
|
AnimStateLog( "----------------- frame %d -----------------\n", gpGlobals->framecount ); |
|
|
|
DebugShowAnimState( iStartLine ); |
|
|
|
AnimStateLog( "--------------------------------------------\n\n" ); |
|
} |
|
|
|
// ----------------------------------------------------------------------------- |
|
int CBasePlayerAnimState::SelectWeightedSequence( Activity activity ) |
|
{ |
|
return GetOuter()->SelectWeightedSequence( activity ); |
|
} |
|
|
|
|