Kamay Xutax
9d17d2252c
Now we can debug properly lag compensation for animations and make it more perfect without using the CUserCmd struct For now it's used to sync better with client but in theory this can be removed soon. There's a lot of work to do in anim layers too.
1044 lines
29 KiB
C++
1044 lines
29 KiB
C++
//========= 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 )
|
|
vel = GetOuter()->GetAbsVelocity();
|
|
#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 );
|
|
}
|
|
|