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.
509 lines
15 KiB
509 lines
15 KiB
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Client side view model implementation. Responsible for drawing |
|
// the view model. |
|
// |
|
// $NoKeywords: $ |
|
//===========================================================================// |
|
#include "cbase.h" |
|
#include "c_baseviewmodel.h" |
|
#include "model_types.h" |
|
#include "hud.h" |
|
#include "view_shared.h" |
|
#include "iviewrender.h" |
|
#include "view.h" |
|
#include "mathlib/vmatrix.h" |
|
#include "cl_animevent.h" |
|
#include "eventlist.h" |
|
#include "tools/bonelist.h" |
|
#include <KeyValues.h> |
|
#include "hltvcamera.h" |
|
#include "r_efx.h" |
|
#include "dlight.h" |
|
#include "clientalphaproperty.h" |
|
#include "iinput.h" |
|
#if defined( REPLAY_ENABLED ) |
|
#include "replaycamera.h" |
|
#endif |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
ConVar vm_debug( "vm_debug", "0", FCVAR_CHEAT ); |
|
ConVar vm_draw_always( "vm_draw_always", "0" ); |
|
|
|
void PostToolMessage( HTOOLHANDLE hEntity, KeyValues *msg ); |
|
extern float g_flMuzzleFlashScale; |
|
|
|
void FormatViewModelAttachment( C_BasePlayer *pPlayer, Vector &vOrigin, bool bInverse ) |
|
{ |
|
int nSlot = 0; |
|
if ( pPlayer ) |
|
{ |
|
int nPlayerSlot = C_BasePlayer::GetSplitScreenSlotForPlayer( pPlayer ); |
|
if ( nPlayerSlot == -1 ) |
|
{ |
|
nSlot = GET_ACTIVE_SPLITSCREEN_SLOT(); |
|
} |
|
else |
|
{ |
|
nSlot = nPlayerSlot; |
|
} |
|
} |
|
|
|
Assert( nSlot != -1 ); |
|
|
|
// Presumably, SetUpView has been called so we know our FOV and render origin. |
|
const CViewSetup *pViewSetup = view->GetPlayerViewSetup( nSlot ); |
|
|
|
float worldx = tan( pViewSetup->fov * M_PI/360.0 ); |
|
float viewx = tan( pViewSetup->fovViewmodel * M_PI/360.0 ); |
|
|
|
// aspect ratio cancels out, so only need one factor |
|
// the difference between the screen coordinates of the 2 systems is the ratio |
|
// of the coefficients of the projection matrices (tan (fov/2) is that coefficient) |
|
float factorX = worldx / viewx; |
|
|
|
float factorY = factorX; |
|
|
|
// Get the coordinates in the viewer's space. |
|
Vector tmp = vOrigin - pViewSetup->origin; |
|
Vector vTransformed( MainViewRight(nSlot).Dot( tmp ), MainViewUp(nSlot).Dot( tmp ), MainViewForward(nSlot).Dot( tmp ) ); |
|
|
|
// Now squash X and Y. |
|
if ( bInverse ) |
|
{ |
|
if ( factorX != 0 && factorY != 0 ) |
|
{ |
|
vTransformed.x /= factorX; |
|
vTransformed.y /= factorY; |
|
} |
|
else |
|
{ |
|
vTransformed.x = 0.0f; |
|
vTransformed.y = 0.0f; |
|
} |
|
} |
|
else |
|
{ |
|
vTransformed.x *= factorX; |
|
vTransformed.y *= factorY; |
|
} |
|
|
|
|
|
|
|
// Transform back to world space. |
|
Vector vOut = (MainViewRight(nSlot) * vTransformed.x) + (MainViewUp(nSlot) * vTransformed.y) + (MainViewForward(nSlot) * vTransformed.z); |
|
vOrigin = pViewSetup->origin + vOut; |
|
} |
|
|
|
|
|
void C_BaseViewModel::FormatViewModelAttachment( int nAttachment, matrix3x4_t &attachmentToWorld ) |
|
{ |
|
C_BasePlayer *pPlayer = ToBasePlayer( GetOwner() ); |
|
Vector vecOrigin; |
|
MatrixPosition( attachmentToWorld, vecOrigin ); |
|
::FormatViewModelAttachment( pPlayer, vecOrigin, false ); |
|
PositionMatrix( vecOrigin, attachmentToWorld ); |
|
} |
|
|
|
void C_BaseViewModel::UncorrectViewModelAttachment( Vector &vOrigin ) |
|
{ |
|
C_BasePlayer *pPlayer = ToBasePlayer( GetOwner() ); |
|
// Unformat the attachment. |
|
::FormatViewModelAttachment( pPlayer, vOrigin, true ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose |
|
//----------------------------------------------------------------------------- |
|
void C_BaseViewModel::FireEvent( const Vector& origin, const QAngle& angles, int eventNum, const char *options ) |
|
{ |
|
// We override sound requests so that we can play them locally on the owning player |
|
if ( ( eventNum == AE_CL_PLAYSOUND ) || ( eventNum == CL_EVENT_SOUND ) ) |
|
{ |
|
// Only do this if we're owned by someone |
|
if ( GetOwner() != NULL ) |
|
{ |
|
CLocalPlayerFilter filter; |
|
EmitSound( filter, GetOwner()->GetSoundSourceIndex(), options, &GetAbsOrigin() ); |
|
return; |
|
} |
|
} |
|
|
|
C_BasePlayer *pOwner = ToBasePlayer( GetOwner() ); |
|
if ( !pOwner ) |
|
return; |
|
|
|
ACTIVE_SPLITSCREEN_PLAYER_GUARD_ENT( pOwner ); |
|
|
|
// Otherwise pass the event to our associated weapon |
|
C_BaseCombatWeapon *pWeapon = pOwner->GetActiveWeapon(); |
|
if ( pWeapon ) |
|
{ |
|
bool bResult = pWeapon->OnFireEvent( this, origin, angles, eventNum, options ); |
|
if ( !bResult ) |
|
{ |
|
if ( eventNum == AE_CLIENT_EFFECT_ATTACH && ::input->CAM_IsThirdPerson() ) |
|
return; |
|
|
|
BaseClass::FireEvent( origin, angles, eventNum, options ); |
|
} |
|
} |
|
} |
|
|
|
bool C_BaseViewModel::Interpolate( float currentTime ) |
|
{ |
|
CStudioHdr *pStudioHdr = GetModelPtr(); |
|
// Make sure we reset our animation information if we've switch sequences |
|
UpdateAnimationParity(); |
|
|
|
bool bret = BaseClass::Interpolate( currentTime ); |
|
|
|
// Hack to extrapolate cycle counter for view model |
|
float elapsed_time = currentTime - m_flAnimTime; |
|
C_BasePlayer *pPlayer = ToBasePlayer( GetOwner() ); |
|
|
|
// Predicted viewmodels have fixed up interval |
|
if ( GetPredictable() || IsClientCreated() ) |
|
{ |
|
Assert( pPlayer ); |
|
float curtime = pPlayer ? pPlayer->GetFinalPredictedTime() : gpGlobals->curtime; |
|
elapsed_time = curtime - m_flAnimTime; |
|
// Adjust for interpolated partial frame |
|
elapsed_time += ( gpGlobals->interpolation_amount * TICK_INTERVAL ); |
|
} |
|
|
|
// Prediction errors? |
|
if ( elapsed_time < 0 ) |
|
{ |
|
elapsed_time = 0; |
|
} |
|
|
|
float dt = elapsed_time * GetSequenceCycleRate( pStudioHdr, GetSequence() ); |
|
if ( dt >= 1.0f ) |
|
{ |
|
if ( !IsSequenceLooping( GetSequence() ) ) |
|
{ |
|
dt = 0.999f; |
|
} |
|
else |
|
{ |
|
dt = fmod( dt, 1.0f ); |
|
} |
|
} |
|
|
|
SetCycle( dt ); |
|
return bret; |
|
} |
|
|
|
|
|
inline bool C_BaseViewModel::ShouldFlipViewModel() |
|
{ |
|
return false; |
|
} |
|
|
|
|
|
void C_BaseViewModel::ApplyBoneMatrixTransform( matrix3x4_t& transform ) |
|
{ |
|
if ( ShouldFlipViewModel() ) |
|
{ |
|
ACTIVE_SPLITSCREEN_PLAYER_GUARD_ENT( GetOwner() ); |
|
|
|
matrix3x4_t viewMatrix, viewMatrixInverse; |
|
|
|
// We could get MATERIAL_VIEW here, but this is called sometimes before the renderer |
|
// has set that matrix. Luckily, this is called AFTER the CViewSetup has been initialized. |
|
const CViewSetup *pSetup = view->GetPlayerViewSetup(); |
|
AngleMatrix( pSetup->angles, pSetup->origin, viewMatrixInverse ); |
|
MatrixInvert( viewMatrixInverse, viewMatrix ); |
|
|
|
// Transform into view space. |
|
matrix3x4_t temp, temp2; |
|
ConcatTransforms( viewMatrix, transform, temp ); |
|
|
|
// Flip it along X. |
|
|
|
// (This is the slower way to do it, and it equates to negating the top row). |
|
//matrix3x4_t mScale; |
|
//SetIdentityMatrix( mScale ); |
|
//mScale[0][0] = 1; |
|
//mScale[1][1] = -1; |
|
//mScale[2][2] = 1; |
|
//ConcatTransforms( mScale, temp, temp2 ); |
|
temp[1][0] = -temp[1][0]; |
|
temp[1][1] = -temp[1][1]; |
|
temp[1][2] = -temp[1][2]; |
|
temp[1][3] = -temp[1][3]; |
|
|
|
// Transform back out of view space. |
|
ConcatTransforms( viewMatrixInverse, temp, transform ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: check if weapon viewmodel should be drawn |
|
//----------------------------------------------------------------------------- |
|
bool C_BaseViewModel::ShouldDraw() |
|
{ |
|
if ( g_bEngineIsHLTV ) |
|
{ |
|
return ( HLTVCamera()->GetMode() == OBS_MODE_IN_EYE && |
|
HLTVCamera()->GetPrimaryTarget() == GetOwner() ); |
|
} |
|
#if defined( REPLAY_ENABLED ) |
|
else if ( engine->IsReplay() ) |
|
{ |
|
return ( ReplayCamera()->GetMode() == OBS_MODE_IN_EYE && |
|
ReplayCamera()->GetPrimaryTarget() == GetOwner() ); |
|
} |
|
#endif |
|
else |
|
{ |
|
Assert( !IsEffectActive( EF_NODRAW ) ); |
|
Assert( GetRenderMode() != kRenderNone ); |
|
|
|
if ( vm_draw_always.GetBool() ) |
|
return true; |
|
if ( GetOwner() != C_BasePlayer::GetLocalPlayer() ) |
|
return false; |
|
|
|
return BaseClass::ShouldDraw(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Render the weapon. Draw the Viewmodel if the weapon's being carried |
|
// by this player, otherwise draw the worldmodel. |
|
//----------------------------------------------------------------------------- |
|
int C_BaseViewModel::DrawModel( int flags, const RenderableInstance_t &instance ) |
|
{ |
|
if ( !m_bReadyToDraw ) |
|
return 0; |
|
|
|
if ( flags & STUDIO_RENDER ) |
|
{ |
|
// Determine blending amount and tell engine |
|
float blend = (float)( instance.m_nAlpha / 255.0f ); |
|
|
|
// Totally gone |
|
if ( blend <= 0.0f ) |
|
return 0; |
|
|
|
// Tell engine |
|
render->SetBlend( blend ); |
|
|
|
float color[3]; |
|
GetColorModulation( color ); |
|
render->SetColorModulation( color ); |
|
} |
|
|
|
CMatRenderContextPtr pRenderContext( materials ); |
|
|
|
if ( ShouldFlipViewModel() ) |
|
pRenderContext->CullMode( MATERIAL_CULLMODE_CW ); |
|
|
|
int ret = 0; |
|
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); |
|
C_BaseCombatWeapon *pWeapon = GetOwningWeapon(); |
|
|
|
// If the local player's overriding the viewmodel rendering, let him do it |
|
if ( pPlayer && pPlayer->IsOverridingViewmodel() ) |
|
{ |
|
ret = pPlayer->DrawOverriddenViewmodel( this, flags, instance ); |
|
} |
|
else if ( pWeapon && pWeapon->IsOverridingViewmodel() ) |
|
{ |
|
ret = pWeapon->DrawOverriddenViewmodel( this, flags, instance ); |
|
} |
|
else |
|
{ |
|
ret = BaseClass::DrawModel( flags, instance ); |
|
} |
|
|
|
pRenderContext->CullMode( MATERIAL_CULLMODE_CCW ); |
|
|
|
// Now that we've rendered, reset the animation restart flag |
|
if ( flags & STUDIO_RENDER ) |
|
{ |
|
if ( m_nOldAnimationParity != m_nAnimationParity ) |
|
{ |
|
m_nOldAnimationParity = m_nAnimationParity; |
|
} |
|
|
|
// Tell the weapon itself that we've rendered, in case it wants to do something |
|
if ( pWeapon ) |
|
{ |
|
pWeapon->ViewModelDrawn( this ); |
|
} |
|
|
|
if ( vm_debug.GetBool() ) |
|
{ |
|
MDLCACHE_CRITICAL_SECTION(); |
|
|
|
int line = 16; |
|
CStudioHdr *hdr = GetModelPtr(); |
|
engine->Con_NPrintf( line++, "%s: %s(%d), cycle: %.2f cyclerate: %.2f playbackrate: %.2f\n", |
|
(hdr)?hdr->pszName():"(null)", |
|
GetSequenceName( GetSequence() ), |
|
GetSequence(), |
|
GetCycle(), |
|
GetSequenceCycleRate( hdr, GetSequence() ), |
|
GetPlaybackRate() |
|
); |
|
if ( hdr ) |
|
{ |
|
for( int i=0; i < hdr->GetNumPoseParameters(); ++i ) |
|
{ |
|
const mstudioposeparamdesc_t &Pose = hdr->pPoseParameter( i ); |
|
engine->Con_NPrintf( line++, "pose_param %s: %f", |
|
Pose.pszName(), GetPoseParameter( i ) ); |
|
} |
|
} |
|
|
|
// Determine blending amount and tell engine |
|
float blend = (float)( instance.m_nAlpha / 255.0f ); |
|
float color[3]; |
|
GetColorModulation( color ); |
|
engine->Con_NPrintf( line++, "blend=%f, color=%f,%f,%f", blend, color[0], color[1], color[2] ); |
|
engine->Con_NPrintf( line++, "GetRenderMode()=%d", GetRenderMode() ); |
|
engine->Con_NPrintf( line++, "m_nRenderFX=0x%8.8X", GetRenderFX() ); |
|
|
|
color24 c = GetRenderColor(); |
|
unsigned char a = GetRenderAlpha(); |
|
engine->Con_NPrintf( line++, "rendercolor=%d,%d,%d,%d", c.r, c.g, c.b, a ); |
|
|
|
engine->Con_NPrintf( line++, "origin=%f, %f, %f", GetRenderOrigin().x, GetRenderOrigin().y, GetRenderOrigin().z ); |
|
engine->Con_NPrintf( line++, "angles=%f, %f, %f", GetRenderAngles()[0], GetRenderAngles()[1], GetRenderAngles()[2] ); |
|
|
|
if ( IsEffectActive( EF_NODRAW ) ) |
|
{ |
|
engine->Con_NPrintf( line++, "EF_NODRAW" ); |
|
} |
|
} |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called by the player when the player's overriding the viewmodel drawing. Avoids infinite recursion. |
|
//----------------------------------------------------------------------------- |
|
int C_BaseViewModel::DrawOverriddenViewmodel( int flags, const RenderableInstance_t &instance ) |
|
{ |
|
return BaseClass::DrawModel( flags, instance ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
uint8 C_BaseViewModel::OverrideAlphaModulation( uint8 nAlpha ) |
|
{ |
|
ACTIVE_SPLITSCREEN_PLAYER_GUARD_ENT( GetOwner() ); |
|
|
|
// See if the local player wants to override the viewmodel's rendering |
|
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); |
|
if ( pPlayer && pPlayer->IsOverridingViewmodel() ) |
|
return pPlayer->AlphaProp()->ComputeRenderAlpha(); |
|
|
|
C_BaseCombatWeapon *pWeapon = GetOwningWeapon(); |
|
if ( pWeapon && pWeapon->IsOverridingViewmodel() ) |
|
return pWeapon->AlphaProp()->ComputeRenderAlpha(); |
|
|
|
return nAlpha; |
|
|
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
RenderableTranslucencyType_t C_BaseViewModel::ComputeTranslucencyType( void ) |
|
{ |
|
ACTIVE_SPLITSCREEN_PLAYER_GUARD_ENT( GetOwner() ); |
|
|
|
// See if the local player wants to override the viewmodel's rendering |
|
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); |
|
if ( pPlayer && pPlayer->IsOverridingViewmodel() ) |
|
return pPlayer->ComputeTranslucencyType(); |
|
|
|
C_BaseCombatWeapon *pWeapon = GetOwningWeapon(); |
|
if ( pWeapon && pWeapon->IsOverridingViewmodel() ) |
|
return pWeapon->ComputeTranslucencyType(); |
|
|
|
return BaseClass::ComputeTranslucencyType(); |
|
|
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: If the animation parity of the weapon has changed, we reset cycle to avoid popping |
|
//----------------------------------------------------------------------------- |
|
void C_BaseViewModel::UpdateAnimationParity( void ) |
|
{ |
|
C_BasePlayer *pPlayer = ToBasePlayer( GetOwner() ); |
|
|
|
// If we're predicting, then we don't use animation parity because we change the animations on the clientside |
|
// while predicting. When not predicting, only the server changes the animations, so a parity mismatch |
|
// tells us if we need to reset the animation. |
|
if ( m_nOldAnimationParity != m_nAnimationParity && !GetPredictable() ) |
|
{ |
|
float curtime = (pPlayer && IsIntermediateDataAllocated()) ? pPlayer->GetFinalPredictedTime() : gpGlobals->curtime; |
|
// FIXME: this is bad |
|
// Simulate a networked m_flAnimTime and m_flCycle |
|
// FIXME: Do we need the magic 0.1? |
|
SetCycle( 0.0f ); // GetSequenceCycleRate( GetSequence() ) * 0.1; |
|
m_flAnimTime = curtime; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Update global map state based on data received |
|
// Input : bnewentity - |
|
//----------------------------------------------------------------------------- |
|
void C_BaseViewModel::OnDataChanged( DataUpdateType_t updateType ) |
|
{ |
|
if ( updateType == DATA_UPDATE_CREATED ) |
|
{ |
|
AlphaProp()->EnableAlphaModulationOverride( true ); |
|
} |
|
|
|
SetPredictionEligible( true ); |
|
BaseClass::OnDataChanged(updateType); |
|
} |
|
void C_BaseViewModel::PostDataUpdate( DataUpdateType_t updateType ) |
|
{ |
|
BaseClass::PostDataUpdate(updateType); |
|
OnLatchInterpolatedVariables( LATCH_ANIMATION_VAR ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Return the player who will predict this entity |
|
//----------------------------------------------------------------------------- |
|
CBasePlayer *C_BaseViewModel::GetPredictionOwner() |
|
{ |
|
return ToBasePlayer( GetOwner() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void C_BaseViewModel::GetBoneControllers(float controllers[MAXSTUDIOBONECTRLS]) |
|
{ |
|
BaseClass::GetBoneControllers( controllers ); |
|
|
|
// Tell the weapon itself that we've rendered, in case it wants to do something |
|
C_BasePlayer *pPlayer = ToBasePlayer( GetOwner() ); |
|
if ( !pPlayer ) |
|
return; |
|
|
|
C_BaseCombatWeapon *pWeapon = pPlayer->GetActiveWeapon(); |
|
if ( pWeapon ) |
|
{ |
|
pWeapon->GetViewmodelBoneControllers( this, controllers ); |
|
} |
|
} |
|
|
|
|