css_enhanced_waf/game/client/view.cpp
unknown d03c92b6f0 We have now better angle update
Since it was previously based on old frametime,
you would see your angle changes from previous frametime,
not the current one...
It's now fixed.
2024-09-12 22:33:24 +02:00

1372 lines
39 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//===========================================================================//
#include "cbase.h"
#include "view.h"
#include "cdll_client_int.h"
#include "gamerules.h"
#include "iviewrender.h"
#include "iviewrender_beams.h"
#include "view_shared.h"
#include "ivieweffects.h"
#include "iinput.h"
#include "iclientmode.h"
#include "prediction.h"
#include "viewrender.h"
#include "c_te_legacytempents.h"
#include "cl_mat_stub.h"
#include "tier0/vprof.h"
#include "iclientvehicle.h"
#include "engine/IEngineTrace.h"
#include "mathlib/vmatrix.h"
#include "rendertexture.h"
#include "c_world.h"
#include <KeyValues.h>
#include "igameevents.h"
#include "smoke_fog_overlay.h"
#include "bitmap/tgawriter.h"
#include "hltvcamera.h"
#if defined( REPLAY_ENABLED )
#include "replay/replaycamera.h"
#include "replay/replay_screenshot.h"
#endif
#include "input.h"
#include "filesystem.h"
#include "materialsystem/itexture.h"
#include "materialsystem/imaterialsystem.h"
#include "materialsystem/materialsystem_config.h"
#include "VGuiMatSurface/IMatSystemSurface.h"
#include "toolframework_client.h"
#include "tier0/icommandline.h"
#include "ienginevgui.h"
#include <vgui_controls/Controls.h>
#include <vgui/ISurface.h>
#include "ScreenSpaceEffects.h"
#include "sourcevr/isourcevirtualreality.h"
#include "client_virtualreality.h"
#if defined( REPLAY_ENABLED )
#include "replay/ireplaysystem.h"
#include "replay/ienginereplay.h"
#endif
#if defined( HL2_CLIENT_DLL ) || defined( CSTRIKE_DLL )
#define USE_MONITORS
#endif
#ifdef PORTAL
#include "c_prop_portal.h" //portal surface rendering functions
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
void ToolFramework_AdjustEngineViewport( int& x, int& y, int& width, int& height );
bool ToolFramework_SetupEngineView( Vector &origin, QAngle &angles, float &fov );
bool ToolFramework_SetupEngineMicrophone( Vector &origin, QAngle &angles );
extern ConVar default_fov;
extern bool g_bRenderingScreenshot;
#if !defined( _X360 )
#define SAVEGAME_SCREENSHOT_WIDTH 180
#define SAVEGAME_SCREENSHOT_HEIGHT 100
#else
#define SAVEGAME_SCREENSHOT_WIDTH 128
#define SAVEGAME_SCREENSHOT_HEIGHT 128
#endif
#ifndef _XBOX
extern ConVar sensitivity;
#endif
ConVar zoom_sensitivity_ratio( "zoom_sensitivity_ratio", "1.0", 0, "Additional mouse sensitivity scale factor applied when FOV is zoomed in." );
CViewRender g_DefaultViewRender;
IViewRender *view = NULL; // set in cldll_client_init.cpp if no mod creates their own
#if _DEBUG
bool g_bRenderingCameraView = false;
#endif
// These are the vectors for the "main" view - the one the player is looking down.
// For stereo views, they are the vectors for the middle eye.
static Vector g_vecRenderOrigin(0,0,0);
static QAngle g_vecRenderAngles(0,0,0);
static Vector g_vecPrevRenderOrigin(0,0,0); // Last frame's render origin
static QAngle g_vecPrevRenderAngles(0,0,0); // Last frame's render angles
static Vector g_vecVForward(0,0,0), g_vecVRight(0,0,0), g_vecVUp(0,0,0);
static VMatrix g_matCamInverse;
extern ConVar cl_forwardspeed;
static ConVar v_centermove( "v_centermove", "0.15");
static ConVar v_centerspeed( "v_centerspeed","500" );
#ifdef TF_CLIENT_DLL
// 54 degrees approximates a 35mm camera - we determined that this makes the viewmodels
// and motions look the most natural.
ConVar v_viewmodel_fov( "viewmodel_fov", "54", FCVAR_ARCHIVE );
#else
ConVar v_viewmodel_fov( "viewmodel_fov", "54", FCVAR_CHEAT );
#endif
ConVar mat_viewportscale( "mat_viewportscale", "1.0", FCVAR_ARCHIVE, "Scale down the main viewport (to reduce GPU impact on CPU profiling)", true, (1.0f / 640.0f), true, 1.0f );
ConVar mat_viewportupscale( "mat_viewportupscale", "1", FCVAR_ARCHIVE, "Scale the viewport back up" );
ConVar cl_leveloverview( "cl_leveloverview", "0", FCVAR_CHEAT );
#ifdef ANDROID
#define MAPEXTENTS_DEFAULT "12288" // small optimization
#else
#define MAPEXTENTS_DEFAULT "16384"
#endif
static ConVar r_mapextents( "r_mapextents", MAPEXTENTS_DEFAULT, FCVAR_CHEAT,
"Set the max dimension for the map. This determines the far clipping plane" );
// UNDONE: Delete this or move to the material system?
ConVar gl_clear( "gl_clear", "0");
ConVar gl_clear_randomcolor( "gl_clear_randomcolor", "0", FCVAR_CHEAT, "Clear the back buffer to random colors every frame. Helps spot open seams in geometry." );
static ConVar r_farz( "r_farz", "-1", FCVAR_CHEAT, "Override the far clipping plane. -1 means to use the value in env_fog_controller." );
static ConVar cl_demoviewoverride( "cl_demoviewoverride", "0", 0, "Override view during demo playback" );
void SoftwareCursorChangedCB( IConVar *pVar, const char *pOldValue, float fOldValue )
{
ConVar *pConVar = (ConVar *)pVar;
vgui::surface()->SetSoftwareCursor( pConVar->GetBool() || UseVR() );
}
static ConVar cl_software_cursor ( "cl_software_cursor", "0", FCVAR_ARCHIVE, "Switches the game to use a larger software cursor instead of the normal OS cursor", SoftwareCursorChangedCB );
static Vector s_DemoView;
static QAngle s_DemoAngle;
static void CalcDemoViewOverride( Vector &origin, QAngle &angles )
{
engine->SetViewAngles( s_DemoAngle );
input->ExtraMovementSample( gpGlobals->tickcount, 0, gpGlobals->absoluteframetime, true );
static int iOldTickCount = 0;
if (iOldTickCount != gpGlobals->tickcount)
{
iOldTickCount = gpGlobals->tickcount;
}
engine->GetViewAngles( s_DemoAngle );
Vector forward, right, up;
AngleVectors( s_DemoAngle, &forward, &right, &up );
float speed = gpGlobals->absoluteframetime * cl_demoviewoverride.GetFloat() * 320;
s_DemoView += speed * input->KeyState (&in_forward) * forward ;
s_DemoView -= speed * input->KeyState (&in_back) * forward ;
s_DemoView += speed * input->KeyState (&in_moveright) * right ;
s_DemoView -= speed * input->KeyState (&in_moveleft) * right ;
origin = s_DemoView;
angles = s_DemoAngle;
}
// Selects the relevant member variable to update. You could do it manually, but...
// We always set up the MONO eye, even when doing stereo, and it's set up to be mid-way between the left and right,
// so if you don't really care about L/R (e.g. culling, sound, etc), just use MONO.
CViewSetup &CViewRender::GetView(StereoEye_t eEye)
{
if ( eEye == STEREO_EYE_MONO )
{
return m_View;
}
else if ( eEye == STEREO_EYE_RIGHT )
{
return m_ViewRight;
}
else
{
Assert ( eEye == STEREO_EYE_LEFT );
return m_ViewLeft;
}
}
const CViewSetup &CViewRender::GetView(StereoEye_t eEye) const
{
return (const_cast<CViewRender*>(this))->GetView ( eEye );
}
//-----------------------------------------------------------------------------
// Accessors to return the main view (where the player's looking)
//-----------------------------------------------------------------------------
const Vector &MainViewOrigin()
{
return g_vecRenderOrigin;
}
const QAngle &MainViewAngles()
{
return g_vecRenderAngles;
}
const Vector &MainViewForward()
{
return g_vecVForward;
}
const Vector &MainViewRight()
{
return g_vecVRight;
}
const Vector &MainViewUp()
{
return g_vecVUp;
}
const VMatrix &MainWorldToViewMatrix()
{
return g_matCamInverse;
}
const Vector &PrevMainViewOrigin()
{
return g_vecPrevRenderOrigin;
}
const QAngle &PrevMainViewAngles()
{
return g_vecPrevRenderAngles;
}
//-----------------------------------------------------------------------------
// Compute the world->camera transform
//-----------------------------------------------------------------------------
void ComputeCameraVariables( const Vector &vecOrigin, const QAngle &vecAngles,
Vector *pVecForward, Vector *pVecRight, Vector *pVecUp, VMatrix *pMatCamInverse )
{
// Compute view bases
AngleVectors( vecAngles, pVecForward, pVecRight, pVecUp );
for (int i = 0; i < 3; ++i)
{
(*pMatCamInverse)[0][i] = (*pVecRight)[i];
(*pMatCamInverse)[1][i] = (*pVecUp)[i];
(*pMatCamInverse)[2][i] = -(*pVecForward)[i];
(*pMatCamInverse)[3][i] = 0.0F;
}
(*pMatCamInverse)[0][3] = -DotProduct( *pVecRight, vecOrigin );
(*pMatCamInverse)[1][3] = -DotProduct( *pVecUp, vecOrigin );
(*pMatCamInverse)[2][3] = DotProduct( *pVecForward, vecOrigin );
(*pMatCamInverse)[3][3] = 1.0F;
}
bool R_CullSphere(
VPlane const *pPlanes,
int nPlanes,
Vector const *pCenter,
float radius)
{
for(int i=0; i < nPlanes; i++)
if(pPlanes[i].DistTo(*pCenter) < -radius)
return true;
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
static void StartPitchDrift( void )
{
view->StartPitchDrift();
}
static ConCommand centerview( "centerview", StartPitchDrift );
extern ConVar default_fov;
//-----------------------------------------------------------------------------
// Purpose: Initializes all view systems
//-----------------------------------------------------------------------------
void CViewRender::Init( void )
{
memset( &m_PitchDrift, 0, sizeof( m_PitchDrift ) );
m_bDrawOverlay = false;
m_pDrawEntities = cvar->FindVar( "r_drawentities" );
m_pDrawBrushModels = cvar->FindVar( "r_drawbrushmodels" );
beams->InitBeams();
tempents->Init();
m_TranslucentSingleColor.Init( "debug/debugtranslucentsinglecolor", TEXTURE_GROUP_OTHER );
m_ModulateSingleColor.Init( "engine/modulatesinglecolor", TEXTURE_GROUP_OTHER );
extern CMaterialReference g_material_WriteZ;
g_material_WriteZ.Init( "engine/writez", TEXTURE_GROUP_OTHER );
// FIXME:
QAngle angles;
engine->GetViewAngles( angles );
AngleVectors( angles, &m_vecLastFacing );
#if defined( REPLAY_ENABLED )
m_pReplayScreenshotTaker = NULL;
#endif
#if defined( CSTRIKE_DLL )
m_flLastFOV = default_fov.GetFloat();
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Called once per level change
//-----------------------------------------------------------------------------
void CViewRender::LevelInit( void )
{
beams->ClearBeams();
tempents->Clear();
m_BuildWorldListsNumber = 0;
m_BuildRenderableListsNumber = 0;
for( int i=0; i < STEREO_EYE_MAX; i++ )
{
m_rbTakeFreezeFrame[ i ] = false;
}
m_flFreezeFrameUntil = 0;
// Clear our overlay materials
m_ScreenOverlayMaterial.Init( NULL );
// Init all IScreenSpaceEffects
g_pScreenSpaceEffects->InitScreenSpaceEffects( );
}
//-----------------------------------------------------------------------------
// Purpose: Called once per level change
//-----------------------------------------------------------------------------
void CViewRender::LevelShutdown( void )
{
g_pScreenSpaceEffects->ShutdownScreenSpaceEffects( );
}
//-----------------------------------------------------------------------------
// Purpose: Called at shutdown
//-----------------------------------------------------------------------------
void CViewRender::Shutdown( void )
{
m_TranslucentSingleColor.Shutdown( );
m_ModulateSingleColor.Shutdown( );
m_ScreenOverlayMaterial.Shutdown();
m_UnderWaterOverlayMaterial.Shutdown();
beams->ShutdownBeams();
tempents->Shutdown();
}
//-----------------------------------------------------------------------------
// Returns the worldlists build number
//-----------------------------------------------------------------------------
int CViewRender::BuildWorldListsNumber( void ) const
{
return m_BuildWorldListsNumber;
}
//-----------------------------------------------------------------------------
// Purpose: Start moving pitch toward ideal
//-----------------------------------------------------------------------------
void CViewRender::StartPitchDrift (void)
{
if ( m_PitchDrift.laststop == gpGlobals->curtime )
{
// Something else is blocking the drift.
return;
}
if ( m_PitchDrift.nodrift || !m_PitchDrift.pitchvel )
{
m_PitchDrift.pitchvel = v_centerspeed.GetFloat();
m_PitchDrift.nodrift = false;
m_PitchDrift.driftmove = 0;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CViewRender::StopPitchDrift (void)
{
m_PitchDrift.laststop = gpGlobals->curtime;
m_PitchDrift.nodrift = true;
m_PitchDrift.pitchvel = 0;
}
//-----------------------------------------------------------------------------
// Purpose: Moves the client pitch angle towards cl.idealpitch sent by the server.
// If the user is adjusting pitch manually, either with lookup/lookdown,
// mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped.
//-----------------------------------------------------------------------------
void CViewRender::DriftPitch (void)
{
float delta, move;
C_BasePlayer *player = C_BasePlayer::GetLocalPlayer();
if ( !player )
return;
#if defined( REPLAY_ENABLED )
if ( engine->IsHLTV() || g_pEngineClientReplay->IsPlayingReplayDemo() || ( player->GetGroundEntity() == NULL ) || engine->IsPlayingDemo() )
#else
if ( engine->IsHLTV() || ( player->GetGroundEntity() == NULL ) || engine->IsPlayingDemo() )
#endif
{
m_PitchDrift.driftmove = 0;
m_PitchDrift.pitchvel = 0;
return;
}
// Don't count small mouse motion
if ( m_PitchDrift.nodrift )
{
if ( fabs( input->GetLastForwardMove() ) < cl_forwardspeed.GetFloat() )
{
m_PitchDrift.driftmove = 0;
}
else
{
m_PitchDrift.driftmove += gpGlobals->frametime;
}
if ( m_PitchDrift.driftmove > v_centermove.GetFloat() )
{
StartPitchDrift ();
}
return;
}
// How far off are we
delta = prediction->GetIdealPitch() - player->GetAbsAngles()[ PITCH ];
if ( !delta )
{
m_PitchDrift.pitchvel = 0;
return;
}
// Determine movement amount
move = gpGlobals->frametime * m_PitchDrift.pitchvel;
// Accelerate
m_PitchDrift.pitchvel += gpGlobals->frametime * v_centerspeed.GetFloat();
// Move predicted pitch appropriately
if (delta > 0)
{
if ( move > delta )
{
m_PitchDrift.pitchvel = 0;
move = delta;
}
player->SetLocalAngles( player->GetLocalAngles() + QAngle( move, 0, 0 ) );
}
else if ( delta < 0 )
{
if ( move > -delta )
{
m_PitchDrift.pitchvel = 0;
move = -delta;
}
player->SetLocalAngles( player->GetLocalAngles() - QAngle( move, 0, 0 ) );
}
}
StereoEye_t CViewRender::GetFirstEye() const
{
if( UseVR() )
return STEREO_EYE_LEFT;
else
return STEREO_EYE_MONO;
}
StereoEye_t CViewRender::GetLastEye() const
{
if( UseVR() )
return STEREO_EYE_RIGHT;
else
return STEREO_EYE_MONO;
}
// This is called by cdll_client_int to setup view model origins. This has to be done before
// simulation so entities can access attachment points on view models during simulation.
void CViewRender::OnRenderStart()
{
VPROF_("CViewRender::OnRenderStart", 2, VPROF_BUDGETGROUP_OTHER_UNACCOUNTED, false, 0);
SetUpViews();
// Adjust mouse sensitivity based upon the current FOV
C_BasePlayer *player = C_BasePlayer::GetLocalPlayer();
if ( player )
{
default_fov.SetValue( player->m_iDefaultFOV );
//Update our FOV, including any zooms going on
int iDefaultFOV = default_fov.GetInt();
int localFOV = player->GetFOV();
int min_fov = player->GetMinFOV();
// Don't let it go too low
localFOV = MAX( min_fov, localFOV );
gHUD.m_flFOVSensitivityAdjust = 1.0f;
#ifndef _XBOX
if ( gHUD.m_flMouseSensitivityFactor )
{
gHUD.m_flMouseSensitivity = sensitivity.GetFloat() * gHUD.m_flMouseSensitivityFactor;
}
else
#endif
{
// No override, don't use huge sensitivity
if ( localFOV == iDefaultFOV )
{
#ifndef _XBOX
// reset to saved sensitivity
gHUD.m_flMouseSensitivity = 0;
#endif
}
else
{
// Set a new sensitivity that is proportional to the change from the FOV default and scaled
// by a separate compensating factor
if ( iDefaultFOV == 0 )
{
Assert(0); // would divide by zero, something is broken with iDefatulFOV
iDefaultFOV = 1;
}
gHUD.m_flFOVSensitivityAdjust =
((float)localFOV / (float)iDefaultFOV) * // linear fov downscale
zoom_sensitivity_ratio.GetFloat(); // sensitivity scale factor
#ifndef _XBOX
gHUD.m_flMouseSensitivity = gHUD.m_flFOVSensitivityAdjust * sensitivity.GetFloat(); // regular sensitivity
#endif
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : const CViewSetup
//-----------------------------------------------------------------------------
const CViewSetup *CViewRender::GetViewSetup( void ) const
{
return &m_CurrentView;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : const CViewSetup
//-----------------------------------------------------------------------------
const CViewSetup *CViewRender::GetPlayerViewSetup( void ) const
{
const CViewSetup &view = GetView ( STEREO_EYE_MONO );
return &view;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CViewRender::DisableVis( void )
{
m_bForceNoVis = true;
}
#ifdef DBGFLAG_ASSERT
static Vector s_DbgSetupOrigin;
static QAngle s_DbgSetupAngles;
#endif
//-----------------------------------------------------------------------------
// Gets znear + zfar
//-----------------------------------------------------------------------------
float CViewRender::GetZNear()
{
return VIEW_NEARZ;
}
float CViewRender::GetZFar()
{
// Initialize view structure with default values
float farZ;
if ( r_farz.GetFloat() < 1 )
{
// Use the far Z from the map's parameters.
farZ = r_mapextents.GetFloat() * 1.73205080757f;
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
if( pPlayer && pPlayer->GetFogParams() )
{
if ( pPlayer->GetFogParams()->farz > 0 )
{
farZ = pPlayer->GetFogParams()->farz;
}
}
}
else
{
farZ = r_farz.GetFloat();
}
return farZ;
}
//-----------------------------------------------------------------------------
// Sets up the view parameters
//-----------------------------------------------------------------------------
void CViewRender::SetUpViews()
{
VPROF("CViewRender::SetUpViews");
// Initialize view structure with default values
float farZ = GetZFar();
// Set up the mono/middle view.
CViewSetup &view = m_View;
view.zFar = farZ;
view.zFarViewmodel = farZ;
// UNDONE: Make this farther out?
// closest point of approach seems to be view center to top of crouched box
view.zNear = GetZNear();
view.zNearViewmodel = 1;
view.fov = default_fov.GetFloat();
view.m_bOrtho = false;
view.m_bViewToProjectionOverride = false;
view.m_eStereoEye = STEREO_EYE_MONO;
// Enable spatial partition access to edicts
partition->SuppressLists( PARTITION_ALL_CLIENT_EDICTS, false );
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
// You in-view weapon aim.
bool bCalcViewModelView = false;
Vector ViewModelOrigin;
QAngle ViewModelAngles;
if ( engine->IsHLTV() )
{
HLTVCamera()->CalcView( view.origin, view.angles, view.fov );
}
#if defined( REPLAY_ENABLED )
else if ( g_pEngineClientReplay->IsPlayingReplayDemo() )
{
ReplayCamera()->CalcView( view.origin, view.angles, view.fov );
}
#endif
else
{
// FIXME: Are there multiple views? If so, then what?
// FIXME: What happens when there's no player?
if (pPlayer)
{
pPlayer->CalcView( view.origin, view.angles, view.zNear, view.zFar, view.fov );
// If we are looking through another entities eyes, then override the angles/origin for view
int viewentity = render->GetViewEntity();
if ( !g_nKillCamMode && (pPlayer->entindex() != viewentity) )
{
C_BaseEntity *ve = cl_entitylist->GetEnt( viewentity );
if ( ve )
{
VectorCopy( ve->GetAbsOrigin(), view.origin );
VectorCopy( ve->GetAbsAngles(), view.angles );
}
}
// There is a viewmodel.
bCalcViewModelView = true;
ViewModelOrigin = view.origin;
ViewModelAngles = view.angles;
}
else
{
view.origin.Init();
view.angles.Init();
}
// Even if the engine is paused need to override the view
// for keeping the camera control during pause.
g_pClientMode->OverrideView( &view );
}
// give the toolsystem a chance to override the view
ToolFramework_SetupEngineView( view.origin, view.angles, view.fov );
if ( engine->IsPlayingDemo() )
{
if ( cl_demoviewoverride.GetFloat() > 0.0f )
{
// Retreive view angles from engine ( could have been set in IN_AdjustAngles above )
CalcDemoViewOverride( view.origin, view.angles );
}
else
{
s_DemoView = view.origin;
s_DemoAngle = view.angles;
}
}
//Find the offset our current FOV is from the default value
float fDefaultFov = default_fov.GetFloat();
float flFOVOffset = fDefaultFov - view.fov;
//Adjust the viewmodel's FOV to move with any FOV offsets on the viewer's end
view.fovViewmodel = fabs( g_pClientMode->GetViewModelFOV() - flFOVOffset );
if ( UseVR() )
{
// Let the headtracking read the status of the HMD, etc.
// This call can go almost anywhere, but it needs to know the player FOV for sniper weapon zoom, etc
if ( flFOVOffset == 0.0f )
{
g_ClientVirtualReality.ProcessCurrentTrackingState ( 0.0f );
}
else
{
g_ClientVirtualReality.ProcessCurrentTrackingState ( view.fov );
}
HeadtrackMovementMode_t hmmOverrideMode = g_pClientMode->ShouldOverrideHeadtrackControl();
g_ClientVirtualReality.OverrideView( &m_View, &ViewModelOrigin, &ViewModelAngles, hmmOverrideMode );
// left and right stereo views should default to being the same as the mono/middle view
m_ViewLeft = m_View;
m_ViewRight = m_View;
m_ViewLeft.m_eStereoEye = STEREO_EYE_LEFT;
m_ViewRight.m_eStereoEye = STEREO_EYE_RIGHT;
g_ClientVirtualReality.OverrideStereoView( &m_View, &m_ViewLeft, &m_ViewRight );
}
else
{
// left and right stereo views should default to being the same as the mono/middle view
m_ViewLeft = m_View;
m_ViewRight = m_View;
m_ViewLeft.m_eStereoEye = STEREO_EYE_LEFT;
m_ViewRight.m_eStereoEye = STEREO_EYE_RIGHT;
}
if ( bCalcViewModelView )
{
Assert ( pPlayer != NULL );
pPlayer->CalcViewModelView ( ViewModelOrigin, ViewModelAngles );
}
// Disable spatial partition access
partition->SuppressLists( PARTITION_ALL_CLIENT_EDICTS, true );
// Enable access to all model bones
C_BaseAnimating::PopBoneAccess( "OnRenderStart->CViewRender::SetUpView" ); // pops the (true, false) bone access set in OnRenderStart
C_BaseAnimating::PushAllowBoneAccess( true, true, "CViewRender::SetUpView->OnRenderEnd" ); // pop is in OnRenderEnd()
// Compute the world->main camera transform
// This is only done for the main "middle-eye" view, not for the various other views.
ComputeCameraVariables( view.origin, view.angles,
&g_vecVForward, &g_vecVRight, &g_vecVUp, &g_matCamInverse );
// set up the hearing origin...
AudioState_t audioState;
audioState.m_Origin = view.origin;
audioState.m_Angles = view.angles;
audioState.m_bIsUnderwater = pPlayer && pPlayer->AudioStateIsUnderwater( view.origin );
ToolFramework_SetupAudioState( audioState );
// TomF: I wonder when the audio tools modify this, if ever...
Assert ( view.origin == audioState.m_Origin );
Assert ( view.angles == audioState.m_Angles );
view.origin = audioState.m_Origin;
view.angles = audioState.m_Angles;
engine->SetAudioState( audioState );
g_vecPrevRenderOrigin = g_vecRenderOrigin;
g_vecPrevRenderAngles = g_vecRenderAngles;
g_vecRenderOrigin = view.origin;
g_vecRenderAngles = view.angles;
#ifdef DBGFLAG_ASSERT
s_DbgSetupOrigin = view.origin;
s_DbgSetupAngles = view.angles;
#endif
}
void CViewRender::WriteSaveGameScreenshotOfSize( const char *pFilename, int width, int height, bool bCreatePowerOf2Padded/*=false*/,
bool bWriteVTF/*=false*/ )
{
#ifndef _X360
CMatRenderContextPtr pRenderContext( materials );
pRenderContext->MatrixMode( MATERIAL_PROJECTION );
pRenderContext->PushMatrix();
pRenderContext->MatrixMode( MATERIAL_VIEW );
pRenderContext->PushMatrix();
g_bRenderingScreenshot = true;
// Push back buffer on the stack with small viewport
pRenderContext->PushRenderTargetAndViewport( NULL, 0, 0, width, height );
// render out to the backbuffer
CViewSetup viewSetup = GetView ( STEREO_EYE_MONO );
viewSetup.x = 0;
viewSetup.y = 0;
viewSetup.width = width;
viewSetup.height = height;
viewSetup.fov = ScaleFOVByWidthRatio( viewSetup.fov, ( (float)width / (float)height ) / ( 4.0f / 3.0f ) );
viewSetup.m_bRenderToSubrectOfLargerScreen = true;
// draw out the scene
// Don't draw the HUD or the viewmodel
RenderView( viewSetup, VIEW_CLEAR_DEPTH | VIEW_CLEAR_COLOR, 0 );
// get the data from the backbuffer and save to disk
// bitmap bits
unsigned char *pImage = ( unsigned char * )malloc( width * height * 3 );
// Get Bits from the material system
pRenderContext->ReadPixels( 0, 0, width, height, pImage, IMAGE_FORMAT_RGB888 );
// Some stuff to be setup dependent on padded vs. not padded
int nSrcWidth, nSrcHeight;
unsigned char *pSrcImage;
// Create a padded version if necessary
unsigned char *pPaddedImage = NULL;
if ( bCreatePowerOf2Padded )
{
// Setup dimensions as needed
int nPaddedWidth = SmallestPowerOfTwoGreaterOrEqual( width );
int nPaddedHeight = SmallestPowerOfTwoGreaterOrEqual( height );
// Allocate
int nPaddedImageSize = nPaddedWidth * nPaddedHeight * 3;
pPaddedImage = ( unsigned char * )malloc( nPaddedImageSize );
// Zero out the entire thing
V_memset( pPaddedImage, 255, nPaddedImageSize );
// Copy over each row individually
for ( int nRow = 0; nRow < height; ++nRow )
{
unsigned char *pDst = pPaddedImage + 3 * ( nRow * nPaddedWidth );
const unsigned char *pSrc = pImage + 3 * ( nRow * width );
V_memcpy( pDst, pSrc, 3 * width );
}
// Setup source data
nSrcWidth = nPaddedWidth;
nSrcHeight = nPaddedHeight;
pSrcImage = pPaddedImage;
}
else
{
// Use non-padded info
nSrcWidth = width;
nSrcHeight = height;
pSrcImage = pImage;
}
// allocate a buffer to write the tga into
CUtlBuffer buffer;
bool bWriteResult;
if ( bWriteVTF )
{
// Create and initialize a VTF texture
IVTFTexture *pVTFTexture = CreateVTFTexture();
const int nFlags = TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD | TEXTUREFLAGS_SRGB;
if ( pVTFTexture->Init( nSrcWidth, nSrcHeight, 1, IMAGE_FORMAT_RGB888, nFlags, 1, 1 ) )
{
// Copy the image data over to the VTF
unsigned char *pDestBits = pVTFTexture->ImageData();
int nDstSize = nSrcWidth * nSrcHeight * 3;
V_memcpy( pDestBits, pSrcImage, nDstSize );
// Allocate output buffer
int iMaxVTFSize = 1024 + ( nSrcWidth * nSrcHeight * 3 );
void *pVTF = malloc( iMaxVTFSize );
buffer.SetExternalBuffer( pVTF, iMaxVTFSize, 0 );
// Serialize to the buffer
bWriteResult = pVTFTexture->Serialize( buffer );
// Free the VTF texture
DestroyVTFTexture( pVTFTexture );
}
else
{
bWriteResult = false;
}
}
else
{
// Write TGA format to buffer
int iMaxTGASize = 1024 + ( nSrcWidth * nSrcHeight * 4 );
void *pTGA = new char[ iMaxTGASize ];
buffer.SetExternalBuffer( pTGA, iMaxTGASize, 0 );
bWriteResult = TGAWriter::WriteToBuffer( pSrcImage, buffer, nSrcWidth, nSrcHeight, IMAGE_FORMAT_RGB888, IMAGE_FORMAT_RGB888 );
}
if ( !bWriteResult )
{
Error( "Couldn't write bitmap data snapshot.\n" );
}
free( pImage );
free( pPaddedImage );
// async write to disk (this will take ownership of the memory)
char szPathedFileName[_MAX_PATH];
Q_snprintf( szPathedFileName, sizeof(szPathedFileName), "//MOD/%s", pFilename );
filesystem->AsyncWrite( szPathedFileName, buffer.Base(), buffer.TellPut(), true );
// restore our previous state
pRenderContext->PopRenderTargetAndViewport();
pRenderContext->MatrixMode( MATERIAL_PROJECTION );
pRenderContext->PopMatrix();
pRenderContext->MatrixMode( MATERIAL_VIEW );
pRenderContext->PopMatrix();
g_bRenderingScreenshot = false;
#endif
}
//-----------------------------------------------------------------------------
// Purpose: takes a screenshot for the replay system
//-----------------------------------------------------------------------------
void CViewRender::WriteReplayScreenshot( WriteReplayScreenshotParams_t &params )
{
#if defined( REPLAY_ENABLED )
if ( !m_pReplayScreenshotTaker )
return;
m_pReplayScreenshotTaker->TakeScreenshot( params );
#endif
}
void CViewRender::UpdateReplayScreenshotCache()
{
#if defined( REPLAY_ENABLED )
// Delete the old one
delete m_pReplayScreenshotTaker;
// Create a new one
m_pReplayScreenshotTaker = new CReplayScreenshotTaker( this, GetView ( STEREO_EYE_MONO ) );
#endif
}
//-----------------------------------------------------------------------------
// Purpose: takes a screenshot of the save game
//-----------------------------------------------------------------------------
void CViewRender::WriteSaveGameScreenshot( const char *pFilename )
{
WriteSaveGameScreenshotOfSize( pFilename, SAVEGAME_SCREENSHOT_WIDTH, SAVEGAME_SCREENSHOT_HEIGHT );
}
float ScaleFOVByWidthRatio( float fovDegrees, float ratio )
{
float halfAngleRadians = fovDegrees * ( 0.5f * M_PI / 180.0f );
float t = tan( halfAngleRadians );
t *= ratio;
float retDegrees = ( 180.0f / M_PI ) * atan( t );
return retDegrees * 2.0f;
}
//-----------------------------------------------------------------------------
// Purpose: Sets view parameters for level overview mode
// Input : *rect -
//-----------------------------------------------------------------------------
void CViewRender::SetUpOverView()
{
static int oldCRC = 0;
CViewSetup &view = GetView ( STEREO_EYE_MONO );
view.m_bOrtho = true;
float aspect = (float)view.width/(float)view.height;
int size_y = 1024.0f * cl_leveloverview.GetFloat(); // scale factor, 1024 = OVERVIEW_MAP_SIZE
int size_x = size_y * aspect; // standard screen aspect
view.origin.x -= size_x / 2;
view.origin.y += size_y / 2;
view.m_OrthoLeft = 0;
view.m_OrthoTop = -size_y;
view.m_OrthoRight = size_x;
view.m_OrthoBottom = 0;
view.angles = QAngle( 90, 90, 0 );
// simple movement detector, show position if moved
int newCRC = view.origin.x + view.origin.y + view.origin.z;
if ( newCRC != oldCRC )
{
Msg( "Overview: scale %.2f, pos_x %.0f, pos_y %.0f\n", cl_leveloverview.GetFloat(),
view.origin.x, view.origin.y );
oldCRC = newCRC;
}
CMatRenderContextPtr pRenderContext( materials );
pRenderContext->ClearColor4ub( 0, 255, 0, 255 );
// render->DrawTopView( true );
}
//-----------------------------------------------------------------------------
// Purpose: Render current view into specified rectangle
// Input : *rect - is computed by CVideoMode_Common::GetClientViewRect()
//-----------------------------------------------------------------------------
void CViewRender::Render( vrect_t *rect )
{
Assert(s_DbgSetupOrigin == m_View.origin);
Assert(s_DbgSetupAngles == m_View.angles);
VPROF_BUDGET( "CViewRender::Render", "CViewRender::Render" );
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
vrect_t vr = *rect;
// Stub out the material system if necessary.
CMatStubHandler matStub;
engine->EngineStats_BeginFrame();
// Assume normal vis
m_bForceNoVis = false;
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
// Set for console commands, etc.
render->SetMainView ( m_View.origin, m_View.angles );
for( StereoEye_t eEye = GetFirstEye(); eEye <= GetLastEye(); eEye = (StereoEye_t)(eEye+1) )
{
CViewSetup &view = GetView( eEye );
#if 0 && defined( CSTRIKE_DLL )
const bool bPlayingBackReplay = g_pEngineClientReplay && g_pEngineClientReplay->IsPlayingReplayDemo();
if ( pPlayer && !bPlayingBackReplay )
{
C_BasePlayer *pViewTarget = pPlayer;
if ( pPlayer->IsObserver() && pPlayer->GetObserverMode() == OBS_MODE_IN_EYE )
{
pViewTarget = dynamic_cast<C_BasePlayer*>( pPlayer->GetObserverTarget() );
}
if ( pViewTarget )
{
float targetFOV = (float)pViewTarget->m_iFOV;
if ( targetFOV == 0 )
{
// FOV of 0 means use the default FOV
targetFOV = g_pGameRules->DefaultFOV();
}
float deltaFOV = view.fov - m_flLastFOV;
float FOVDirection = targetFOV - pViewTarget->m_iFOVStart;
// Clamp FOV changes to stop FOV oscillation
if ( ( deltaFOV < 0.0f && FOVDirection > 0.0f ) ||
( deltaFOV > 0.0f && FOVDirection < 0.0f ) )
{
view.fov = m_flLastFOV;
}
// Catch case where FOV overshoots its target FOV
if ( ( view.fov < targetFOV && FOVDirection <= 0.0f ) ||
( view.fov > targetFOV && FOVDirection >= 0.0f ) )
{
view.fov = targetFOV;
}
m_flLastFOV = view.fov;
}
}
#endif
static ConVarRef sv_restrict_aspect_ratio_fov( "sv_restrict_aspect_ratio_fov" );
float aspectRatio = engine->GetScreenAspectRatio() * 0.75f; // / (4/3)
float limitedAspectRatio = aspectRatio;
if ( ( sv_restrict_aspect_ratio_fov.GetInt() > 0 && engine->IsWindowedMode() && gpGlobals->maxClients > 1 ) ||
sv_restrict_aspect_ratio_fov.GetInt() == 2 )
{
limitedAspectRatio = MIN( aspectRatio, 1.85f * 0.75f ); // cap out the FOV advantage at a 1.85:1 ratio (about the widest any legit user should be)
}
view.fov = ScaleFOVByWidthRatio( view.fov, limitedAspectRatio );
view.fovViewmodel = ScaleFOVByWidthRatio( view.fovViewmodel, aspectRatio );
// Let the client mode hook stuff.
g_pClientMode->PreRender(&view);
g_pClientMode->AdjustEngineViewport( vr.x, vr.y, vr.width, vr.height );
ToolFramework_AdjustEngineViewport( vr.x, vr.y, vr.width, vr.height );
float flViewportScale = mat_viewportscale.GetFloat();
view.m_nUnscaledX = vr.x;
view.m_nUnscaledY = vr.y;
view.m_nUnscaledWidth = vr.width;
view.m_nUnscaledHeight = vr.height;
switch( eEye )
{
case STEREO_EYE_MONO:
{
#if 0
// Good test mode for debugging viewports that are not full-size.
view.width = vr.width * flViewportScale * 0.75f;
view.height = vr.height * flViewportScale * 0.75f;
view.x = vr.x + view.width * 0.10f;
view.y = vr.y + view.height * 0.20f;
#else
view.x = vr.x * flViewportScale;
view.y = vr.y * flViewportScale;
view.width = vr.width * flViewportScale;
view.height = vr.height * flViewportScale;
#endif
float engineAspectRatio = engine->GetScreenAspectRatio();
view.m_flAspectRatio = ( engineAspectRatio > 0.0f ) ? engineAspectRatio : ( (float)view.width / (float)view.height );
}
break;
case STEREO_EYE_RIGHT:
case STEREO_EYE_LEFT:
{
g_pSourceVR->GetViewportBounds( (ISourceVirtualReality::VREye)(eEye - 1 ), &view.x, &view.y, &view.width, &view.height );
view.m_nUnscaledWidth = view.width;
view.m_nUnscaledHeight = view.height;
view.m_nUnscaledX = view.x;
view.m_nUnscaledY = view.y;
}
break;
default:
Assert ( false );
break;
}
// if we still don't have an aspect ratio, compute it from the view size
if( view.m_flAspectRatio <= 0.f )
view.m_flAspectRatio = (float)view.width / (float)view.height;
int nClearFlags = VIEW_CLEAR_DEPTH | VIEW_CLEAR_STENCIL;
if( gl_clear_randomcolor.GetBool() )
{
CMatRenderContextPtr pRenderContext( materials );
pRenderContext->ClearColor3ub( rand()%256, rand()%256, rand()%256 );
pRenderContext->ClearBuffers( true, false, false );
pRenderContext->Release();
}
else if ( gl_clear.GetBool() )
{
nClearFlags |= VIEW_CLEAR_COLOR;
}
else if ( IsPosix() )
{
MaterialAdapterInfo_t adapterInfo;
materials->GetDisplayAdapterInfo( materials->GetCurrentAdapter(), adapterInfo );
// On Posix, on ATI, we always clear color if we're antialiasing
if ( adapterInfo.m_VendorID == 0x1002 )
{
if ( g_pMaterialSystem->GetCurrentConfigForVideoCard().m_nAASamples > 0 )
{
nClearFlags |= VIEW_CLEAR_COLOR;
}
}
}
// Determine if we should draw view model ( client mode override )
bool drawViewModel = g_pClientMode->ShouldDrawViewModel();
if ( cl_leveloverview.GetFloat() > 0 )
{
SetUpOverView();
nClearFlags |= VIEW_CLEAR_COLOR;
drawViewModel = false;
}
// Apply any player specific overrides
if ( pPlayer )
{
// Override view model if necessary
if ( !pPlayer->m_Local.m_bDrawViewmodel )
{
drawViewModel = false;
}
}
int flags = 0;
if( eEye == STEREO_EYE_MONO || eEye == STEREO_EYE_LEFT || ( g_ClientVirtualReality.ShouldRenderHUDInWorld() ) )
{
flags = RENDERVIEW_DRAWHUD;
}
if ( drawViewModel )
{
flags |= RENDERVIEW_DRAWVIEWMODEL;
}
if( eEye == STEREO_EYE_RIGHT )
{
// we should use the monitor view from the left eye for both eyes
flags |= RENDERVIEW_SUPPRESSMONITORRENDERING;
}
RenderView( view, nClearFlags, flags );
if ( UseVR() )
{
bool bDoUndistort = ! engine->IsTakingScreenshot();
if ( bDoUndistort )
{
g_ClientVirtualReality.PostProcessFrame( eEye );
}
// logic here all cloned from code in viewrender.cpp around RenderHUDQuad:
// figure out if we really want to draw the HUD based on freeze cam
bool bInFreezeCam = ( pPlayer && pPlayer->GetObserverMode() == OBS_MODE_FREEZECAM );
// draw the HUD after the view model so its "I'm closer" depth queues work right.
if( !bInFreezeCam && g_ClientVirtualReality.ShouldRenderHUDInWorld() )
{
// TODO - a bit of a shonky test - basically trying to catch the main menu, the briefing screen, the loadout screen, etc.
bool bTranslucent = !g_pMatSystemSurface->IsCursorVisible();
g_ClientVirtualReality.OverlayHUDQuadWithUndistort( view, bDoUndistort, g_pClientMode->ShouldBlackoutAroundHUD(), bTranslucent );
}
}
}
// TODO: should these be inside or outside the stereo eye stuff?
g_pClientMode->PostRender();
engine->EngineStats_EndFrame();
#if !defined( _X360 )
// Stop stubbing the material system so we can see the budget panel
matStub.End();
#endif
// Draw all of the UI stuff "fullscreen"
// (this is not health, ammo, etc. Nor is it pre-game briefing interface stuff - this is the stuff that appears when you hit Esc in-game)
// In stereo mode this is rendered inside of RenderView so it goes into the render target
if( !g_ClientVirtualReality.ShouldRenderHUDInWorld() )
{
CViewSetup view2d;
view2d.x = rect->x;
view2d.y = rect->y;
view2d.width = rect->width;
view2d.height = rect->height;
render->Push2DView( view2d, 0, NULL, GetFrustum() );
render->VGui_Paint( PAINT_UIPANELS | PAINT_CURSOR );
render->PopView( GetFrustum() );
}
}
static void GetPos( const CCommand &args, Vector &vecOrigin, QAngle &angles )
{
vecOrigin = MainViewOrigin();
angles = MainViewAngles();
if ( args.ArgC() == 2 && atoi( args[1] ) == 2 )
{
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
if ( pPlayer )
{
vecOrigin = pPlayer->GetAbsOrigin();
angles = pPlayer->GetAbsAngles();
}
}
}
CON_COMMAND( spec_pos, "dump position and angles to the console" )
{
Vector vecOrigin;
QAngle angles;
GetPos( args, vecOrigin, angles );
Warning( "spec_goto %.1f %.1f %.1f %.1f %.1f\n", vecOrigin.x, vecOrigin.y,
vecOrigin.z, angles.x, angles.y );
}
CON_COMMAND( getpos, "dump position and angles to the console" )
{
Vector vecOrigin;
QAngle angles;
GetPos( args, vecOrigin, angles );
const char *pCommand1 = "setpos";
const char *pCommand2 = "setang";
if ( args.ArgC() == 2 && atoi( args[1] ) == 2 )
{
pCommand1 = "setpos_exact";
pCommand2 = "setang_exact";
}
Warning( "%s %f %f %f;", pCommand1, vecOrigin.x, vecOrigin.y, vecOrigin.z );
Warning( "%s %f %f %f\n", pCommand2, angles.x, angles.y, angles.z );
}