//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: Responsible for drawing the scene // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "view.h" #include "iviewrender.h" #include "view_shared.h" #include "ivieweffects.h" #include "iinput.h" #include "model_types.h" #include "clientsideeffects.h" #include "particlemgr.h" #include "viewrender.h" #include "iclientmode.h" #include "voice_status.h" #include "radio_status.h" #include "glow_overlay.h" #include "materialsystem/imesh.h" #include "materialsystem/itexture.h" #include "materialsystem/imaterial.h" #include "materialsystem/imaterialvar.h" #include "materialsystem/imaterialsystemhardwareconfig.h" #include "detailobjectsystem.h" #include "tier0/vprof.h" #include "engine/IEngineTrace.h" #include "engine/ivmodelinfo.h" #include "view_scene.h" #include "particles_ez.h" #include "engine/IStaticPropMgr.h" #include "engine/ivdebugoverlay.h" #include "cs_view_scene.h" #include "c_cs_player.h" #include "cs_gamerules.h" #include "shake.h" #include "precache_register.h" #include "engine/IEngineSound.h" #include #include "hltvreplaysystem.h" // NOTE: This has to be the last file included! #include "tier0/memdbgon.h" PRECACHE_REGISTER_BEGIN( GLOBAL, PrecacheCSViewScene ) PRECACHE( MATERIAL, "effects/overlaysmoke" ) PRECACHE( MATERIAL, "effects/flashbang" ) PRECACHE( MATERIAL, "effects/flashbang_white" ) PRECACHE( MATERIAL, "effects/nightvision" ) PRECACHE_REGISTER_END() static CCSViewRender g_ViewRender; IViewRender *GetViewRenderInstance() { return &g_ViewRender; } CCSViewRender::CCSViewRender() { m_pFlashTexture = NULL; } void CCSViewRender::Init( void ) { CViewRender::Init(); } void CCSViewRender::PerformNightVisionEffect( const CViewSetup &view ) { C_CSPlayer *pPlayer = C_CSPlayer::GetLocalCSPlayer(); if ( !pPlayer ) return; if (pPlayer->GetObserverMode() == OBS_MODE_IN_EYE) { CBaseEntity *target = pPlayer->GetObserverTarget(); if (target && target->IsPlayer()) { pPlayer = (C_CSPlayer *)target; } } if ( pPlayer && pPlayer->m_flNightVisionAlpha > 0 ) { IMaterial *pMaterial = materials->FindMaterial( "effects/nightvision", TEXTURE_GROUP_CLIENT_EFFECTS, true ); if ( pMaterial ) { int iMaxValue = 255; byte overlaycolor[4] = { 0, 255, 0, 255 }; UpdateScreenEffectTexture( 0, view.x, view.y, view.width, view.height ); if ( pPlayer->m_bNightVisionOn ) { pPlayer->m_flNightVisionAlpha += 15; pPlayer->m_flNightVisionAlpha = MIN( pPlayer->m_flNightVisionAlpha, iMaxValue ); } else { pPlayer->m_flNightVisionAlpha -= 40; pPlayer->m_flNightVisionAlpha = MAX( pPlayer->m_flNightVisionAlpha, 0 ); } overlaycolor[3] = pPlayer->m_flNightVisionAlpha; render->ViewDrawFade( overlaycolor, pMaterial ); CMatRenderContextPtr pRenderContext( materials ); pRenderContext->DrawScreenSpaceQuad( pMaterial ); render->ViewDrawFade( overlaycolor, pMaterial ); pRenderContext->DrawScreenSpaceQuad( pMaterial ); } } } //Adrian - Super Nifty Flashbang Effect(tm) // this does the burn in for the flashbang effect. void CCSViewRender::PerformFlashbangEffect( const CViewSetup &view ) { C_CSPlayer *pLocalPlayer = C_CSPlayer::GetLocalCSPlayer(); if ( pLocalPlayer == NULL ) return; C_CSPlayer *pFlashBangPlayer = pLocalPlayer; //bool bReduceEffect = false; float flAlphaScale = 1.0f; if ( pLocalPlayer->GetObserverMode() != OBS_MODE_NONE ) { // If spectating, use values from target CBaseEntity *pTarget = pLocalPlayer->GetObserverTarget(); if ( pTarget ) { C_CSPlayer *pPlayerTmp = ToCSPlayer(pTarget); if ( pPlayerTmp ) { pFlashBangPlayer = pPlayerTmp; } } // reduce the effect if ( pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE && CanSeeSpectatorOnlyTools() ) { // Reduce alpha. // Note: the logic to reduce the duration of the effect is done when the effect is started (RecvProxy_FlashTime). flAlphaScale = 0.6f; } else if ( pLocalPlayer->GetObserverMode() == OBS_MODE_FIXED || pLocalPlayer->GetObserverMode() == OBS_MODE_ROAMING ) { // Reduce alpha. // Note: the logic to reduce the duration of the effect is done when the effect is started (RecvProxy_FlashTime). flAlphaScale = 0.2f; } else if ( pLocalPlayer->GetObserverMode() != OBS_MODE_IN_EYE ) { // Reduce alpha. // Note: the logic to reduce the duration of the effect is done when the effect is started (RecvProxy_FlashTime). flAlphaScale = 0.6f; } } if ( !pFlashBangPlayer->IsFlashBangActive() || CSGameRules()->IsIntermission() ) { if ( !pLocalPlayer->m_bFlashDspHasBeenCleared ) { CLocalPlayerFilter filter; enginesound->SetPlayerDSP( filter, 0, true ); pLocalPlayer->m_bFlashDspHasBeenCleared = true; } return; } // bandaid to insure that flashbang dsp effect doesn't continue on indefinitely if( pFlashBangPlayer->GetFlashTimeElapsed() > 1.6f && !pLocalPlayer->m_bFlashDspHasBeenCleared ) { CLocalPlayerFilter filter; enginesound->SetPlayerDSP( filter, 0, true ); pLocalPlayer->m_bFlashDspHasBeenCleared = true; } byte overlaycolor[4] = { 255, 255, 255, 255 }; // draw the screenshot overlay portion of the flashbang effect IMaterial *pMaterial = materials->FindMaterial( "effects/flashbang", TEXTURE_GROUP_CLIENT_EFFECTS, true ); if ( pMaterial ) { // This is for handling split screen where we could potentially enter this function more than once a frame. // Since this bit of code grabs both the left and right viewports of the buffer, it only needs to be done once per frame per flash. static float lastTimeGrabbed = 0.0f; if ( gpGlobals->curtime == lastTimeGrabbed ) { pLocalPlayer->m_bFlashScreenshotHasBeenGrabbed = true; pFlashBangPlayer->m_bFlashScreenshotHasBeenGrabbed = true; } if ( !pFlashBangPlayer->m_bFlashScreenshotHasBeenGrabbed ) { CMatRenderContextPtr pRenderContext( materials ); int nScreenWidth, nScreenHeight; pRenderContext->GetRenderTargetDimensions( nScreenWidth, nScreenHeight ); // update m_pFlashTexture lastTimeGrabbed = gpGlobals->curtime; bool foundVar; IMaterialVar* m_BaseTextureVar = pMaterial->FindVar( "$basetexture", &foundVar, false ); m_pFlashTexture = GetFullFrameFrameBufferTexture( 1 ); // When grabbing the texture for the super imposed frame, we grab the whole buffer, not just the viewport. // We were having issues with trying to grab only the right side of the buffer for the second player in split screen. Rect_t srcRect; srcRect.x = 0; srcRect.y = 0; srcRect.width = nScreenWidth; srcRect.height = nScreenHeight; m_BaseTextureVar->SetTextureValue( m_pFlashTexture ); pRenderContext->CopyRenderTargetToTextureEx( m_pFlashTexture, 0, &srcRect, NULL ); pRenderContext->SetFrameBufferCopyTexture( m_pFlashTexture ); pFlashBangPlayer->m_bFlashScreenshotHasBeenGrabbed = true; pLocalPlayer->m_bFlashScreenshotHasBeenGrabbed = true; } if ( !CanSeeSpectatorOnlyTools() ) { overlaycolor[0] = overlaycolor[1] = overlaycolor[2] = pFlashBangPlayer->m_flFlashScreenshotAlpha * flAlphaScale; if ( m_pFlashTexture != NULL ) { static int NUM_AFTER_IMAGE_PASSES = 4; for ( int pass = 0; pass < NUM_AFTER_IMAGE_PASSES; ++pass ) { render->ViewDrawFade( overlaycolor, pMaterial, false ); } } } } // draw pure white overlay part of the flashbang effect. pMaterial = materials->FindMaterial( "effects/flashbang_white", TEXTURE_GROUP_CLIENT_EFFECTS, true ); if ( pMaterial ) { overlaycolor[0] = overlaycolor[1] = overlaycolor[2] = pFlashBangPlayer->m_flFlashOverlayAlpha * flAlphaScale; render->ViewDrawFade( overlaycolor, pMaterial ); } } #ifdef _DEBUG //These are the outer ranges of the smoke overlay volume, at which there is 0% overlay. ConVar cl_smoke_origin_height_cv( "cl_smoke_origin_height", "68", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_HIDDEN, "The center of the visible smoke torus is this many units above the origin of the grenade that's on the ground." ); ConVar cl_smoke_torus_ring_radius_cv( "cl_smoke_torus_ring_radius", "61", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_HIDDEN, "The radius of the overall ring of the entire visible smoke volume, measured from the exact local center of the visible cloud." ); ConVar cl_smoke_torus_ring_subradius_cv( "cl_smoke_torus_ring_subradius", "88", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_HIDDEN, "The radius of the extruded circle that follows the main ring and forms the torus shape." ); ConVar cl_smoke_edge_feather_cv( "cl_smoke_edge_feather", "21", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_HIDDEN, "This many units towards the inner threshold and you will hit 100% overlay opacity." ); ConVar cl_smoke_lower_speed_cv( "cl_smoke_lower_speed", "4.5", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_HIDDEN, "How fast the smoke screen overlay clears." ); ConVar cl_show_smoke_overlay_thresholds( "cl_show_smoke_overlay_thresholds", "0", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_HIDDEN, "Show visualization of smoke overlay inner and outer thresholds." ); #define cl_smoke_origin_height cl_smoke_origin_height_cv.GetFloat() #define cl_smoke_torus_ring_radius cl_smoke_torus_ring_radius_cv.GetFloat() #define cl_smoke_torus_ring_subradius cl_smoke_torus_ring_subradius_cv.GetFloat() #define cl_smoke_edge_feather cl_smoke_edge_feather_cv.GetFloat() #define cl_smoke_lower_speed cl_smoke_lower_speed_cv.GetFloat() #else #define cl_smoke_origin_height 68.0f #define cl_smoke_torus_ring_radius 61.0f #define cl_smoke_torus_ring_subradius 88.0f #define cl_smoke_edge_feather 21.0f #define cl_smoke_lower_speed 4.5f #endif //If your eyes are less than this distance from the center of the visible smoke torus, we should check exact distance and may need to apply the screen overlay. #define cl_smoke_must_be_at_least_this_close_to_have_any_effect (cl_smoke_torus_ring_radius + cl_smoke_torus_ring_subradius) void DrawCappedTorus( Vector vecOrigin, float flRadiusA, float flRadiusB, Color colWireColor, float flDuration ) { // Draw a debug wireframe 'capped' torus. It's a torus with the middle filled in, like a cheese-boule shape that's meant to match a smoke volume Vector vecPointA, vecPointB, vecPointC; vecPointA.Init(); vecPointB.Init(); vecPointC.Init(); int i = 0; int j = 0; bool bTri = true; for ( int n=0; n<1643; n++ ) { bTri ? i++ : j++; bTri = !bTri; float phi = i * 6.28318f / 41.0f; float theta = j * 6.28318f / 20.0f; Vector vecTorusNode; vecTorusNode.Init( cos(phi) * ( flRadiusA + cos(theta) * flRadiusB ), sin(phi) * ( flRadiusA + cos(theta) * flRadiusB ), sin(theta) * flRadiusB ); vecTorusNode += vecOrigin; vecPointC = vecPointB; vecPointB = vecPointA; vecPointA = vecTorusNode; if ( cos(theta) < 0 ) { vecPointA.x = vecOrigin.x; vecPointA.y = vecOrigin.y; vecPointA.z = ( vecPointA.z > vecOrigin.z ) ? ( vecOrigin.z + flRadiusB ) : ( vecOrigin.z - flRadiusB ); if ( vecPointA.x == vecPointB.x && vecPointA.y == vecPointB.y ) continue; } if ( n > 1 ) debugoverlay->AddLineOverlay( vecPointB, vecPointA, colWireColor.r(), colWireColor.g(), colWireColor.b(), false, flDuration ); //triangulate //if ( n > 2 ) //{ // if ( !bTri ) // { // debugoverlay->AddTriangleOverlay( vecPointA, vecPointB, vecPointC, 255, 255, 255, 10, false, flDuration ); // } // else // { // debugoverlay->AddTriangleOverlay( vecPointC, vecPointB, vecPointA, 255, 255, 255, 10, false, flDuration ); // } //} } } //----------------------------------------------------------------------------- // Purpose: Renders pre-viewmodel smoke screen //----------------------------------------------------------------------------- #include "c_cs_player.h" // for clientSmokeGrenadeRecord_t extern CUtlVector g_SmokeGrenadeHandles; void CCSViewRender::RenderSmokeOverlay( bool bPreViewModel ) { if ( bPreViewModel ) { // update the overlay //Assume we have no smoke overlay float flOptimalSmokeOverlayAlpha = 0; if ( g_SmokeGrenadeHandles.Count() > 0 ) { Vector vecPlayerEyePos = MainViewOrigin(GET_ACTIVE_SPLITSCREEN_SLOT()); Vector vecClosestVecToSmoke; int iClosestSmokeIndex = -1; float flTempSmokeDistance = cl_smoke_must_be_at_least_this_close_to_have_any_effect; #ifdef _DEBUG if ( cl_show_smoke_overlay_thresholds.GetBool() ) flTempSmokeDistance = 2000.0f; #endif // we need to find the closest smoke to prevent a latter grenade in the list lifting the opacity of a potentially closer one for( int it=0; it < g_SmokeGrenadeHandles.Count(); it++ ) { CBaseCSGrenadeProjectile *pGrenade = static_cast< CBaseCSGrenadeProjectile* >( g_SmokeGrenadeHandles[it].Get() ); if ( !pGrenade ) continue; Vector toGrenade = ( pGrenade->GetAbsOrigin() + Vector( 0, 0, cl_smoke_origin_height) ) - vecPlayerEyePos; if ( toGrenade.Length() < flTempSmokeDistance ) { //save the new closest smoke flTempSmokeDistance = toGrenade.Length(); iClosestSmokeIndex = it; //remember its vector vecClosestVecToSmoke = toGrenade; } } //only continue if we actually found a close-enough smoke if ( iClosestSmokeIndex != -1 ) { Vector vecSmokePos = Vector( 0, 0, 0 ); CBaseCSGrenadeProjectile *pGrenade = static_cast< CBaseCSGrenadeProjectile* >( g_SmokeGrenadeHandles[iClosestSmokeIndex].Get() ); if ( pGrenade ) vecSmokePos = pGrenade->GetAbsOrigin(); //draw debug visualization #ifdef _DEBUG if ( cl_show_smoke_overlay_thresholds.GetBool() ) { Vector vecSmokeVisualOrigin = vecSmokePos + Vector( 0, 0, cl_smoke_origin_height ); //draw inner threshold DrawCappedTorus( vecSmokeVisualOrigin, cl_smoke_torus_ring_radius, cl_smoke_torus_ring_subradius - cl_smoke_edge_feather, Color( 200, 0, 0 ), gpGlobals->frametime ); //draw outer threshold DrawCappedTorus( vecSmokeVisualOrigin, cl_smoke_torus_ring_radius, cl_smoke_torus_ring_subradius, Color( 100, 0, 0 ), gpGlobals->frametime ); } #endif //linear interpolation between two capped torusoids //are we within the Z axis bounds? if ( abs( vecClosestVecToSmoke.z ) < cl_smoke_torus_ring_subradius ) { float flvecClosestVecToSmokeLength2D = vecClosestVecToSmoke.Length2D(); //are we within the cylindrical cap-space on the XY axis? if ( flvecClosestVecToSmokeLength2D < cl_smoke_torus_ring_radius ) { //if so we can just use z delta flOptimalSmokeOverlayAlpha = ( cl_smoke_torus_ring_subradius - abs( vecClosestVecToSmoke.z ) ) / cl_smoke_edge_feather; #ifdef _DEBUG if ( cl_show_smoke_overlay_thresholds.GetBool() ) { //draw the closest point on the inner torusoid threshold Vector vecSmokeVisualOrigin = vecSmokePos + Vector( 0, 0, cl_smoke_origin_height ); Vector vecClosestPoint = Vector( -vecClosestVecToSmoke.x, -vecClosestVecToSmoke.y, vecClosestVecToSmoke.z < 0 ? cl_smoke_torus_ring_subradius - cl_smoke_edge_feather : -cl_smoke_torus_ring_subradius + cl_smoke_edge_feather ); debugoverlay->AddBoxOverlay( vecClosestPoint + vecSmokeVisualOrigin, Vector( -1, -1, -1 ), Vector( 1, 1, 1 ), QAngle( 0, 0, 0 ), 0, 200, 0, 255, gpGlobals->frametime ); //line to eyes //debugoverlay->AddLineOverlay( vecPlayerEyePos, vecClosestPoint + vecSmokeVisualOrigin, 0,200,0, false, gpGlobals->frametime ); } #endif } else if ( flvecClosestVecToSmokeLength2D < cl_smoke_torus_ring_radius + cl_smoke_torus_ring_subradius ) { //are we within the outer possible horizontal range of the torusoid? if so we need the distance to the nearest point on the primary radius Vector vecRingPosOnSmokePlane = Vector( vecClosestVecToSmoke.x, vecClosestVecToSmoke.y, 0 ).Normalized() * cl_smoke_torus_ring_radius; #ifdef _DEBUG if ( cl_show_smoke_overlay_thresholds.GetBool() ) { //draw the closest point on the inner torusoid threshold Vector vecRingPosToInnerSurface = ( vecClosestVecToSmoke - vecRingPosOnSmokePlane ).Normalized() * ( cl_smoke_torus_ring_subradius - cl_smoke_edge_feather ); Vector vecSmokeVisualOrigin = vecSmokePos + Vector( 0, 0, cl_smoke_origin_height ); debugoverlay->AddBoxOverlay( vecSmokeVisualOrigin - vecRingPosOnSmokePlane - vecRingPosToInnerSurface, Vector( -1, -1, -1 ), Vector( 1, 1, 1 ), QAngle( 0, 0, 0 ), 0, 200, 0, 255, gpGlobals->frametime ); //line to eyes //debugoverlay->AddLineOverlay( vecPlayerEyePos, vecSmokeVisualOrigin - vecRingPosOnSmokePlane - vecRingPosToInnerSurface, 0,200,0, false, gpGlobals->frametime ); } #endif //and check the distance value float flDistanceToClosestRingPoint = ( vecClosestVecToSmoke - vecRingPosOnSmokePlane ).Length(); if ( flDistanceToClosestRingPoint < cl_smoke_torus_ring_subradius ) { flOptimalSmokeOverlayAlpha = ( cl_smoke_torus_ring_subradius - flDistanceToClosestRingPoint ) / cl_smoke_edge_feather; } } //clamp to 0-1 range flOptimalSmokeOverlayAlpha = clamp( flOptimalSmokeOverlayAlpha, 0.0f, 1.0f ); } #ifdef _DEBUG if ( cl_show_smoke_overlay_thresholds.GetBool() ) { debugoverlay->AddTextOverlay(vecPlayerEyePos, gpGlobals->frametime, "Overlay: %f", flOptimalSmokeOverlayAlpha ); } #endif } } //alpha can instantly increase, but decreases to the ideal at a constant rate. if ( flOptimalSmokeOverlayAlpha < m_flSmokeOverlayAmount ) { flOptimalSmokeOverlayAlpha = Approach( flOptimalSmokeOverlayAlpha, m_flSmokeOverlayAmount, gpGlobals->frametime * cl_smoke_lower_speed ); } m_flSmokeOverlayAmount = flOptimalSmokeOverlayAlpha; } if ( m_flSmokeOverlayAmount <= 0 ) { return; } else { IMaterial *pMaterial = materials->FindMaterial( "effects/overlaysmoke", TEXTURE_GROUP_CLIENT_EFFECTS, true ); if ( pMaterial ) { byte overlaycolor[4] = { 90, 90, 90, (byte)( m_flSmokeOverlayAmount * ( bPreViewModel ? 255 : 128 ) ) }; render->ViewDrawFade( overlaycolor, pMaterial ); } } } //----------------------------------------------------------------------------- // Purpose: Renders extra 2D effects in derived classes while the 2D view is on the stack //----------------------------------------------------------------------------- void CCSViewRender::Render2DEffectsPreHUD( const CViewSetup &view ) { PerformNightVisionEffect( view ); // this needs to come before the HUD is drawn, or it will wash the HUD out } //----------------------------------------------------------------------------- // Purpose: Renders extra 2D effects in derived classes while the 2D view is on the stack //----------------------------------------------------------------------------- void CCSViewRender::Render2DEffectsPostHUD( const CViewSetup &view ) { PerformFlashbangEffect( view ); float flFade = g_HltvReplaySystem.GetReplayVideoFadeAmount(); // replay camera post-process if ( g_HltvReplaySystem.WantsReplayEffect() ) { // demo playback uses full-screen quad to fade in/out. Hltv replay uses the pixel shader here. CMatRenderContextPtr pRenderContext( materials ); int x, y, w, h; pRenderContext->GetViewport( x, y, w, h ); if ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 95 ) // bail if ps30 is unsupported return; UpdateScreenEffectTexture( 0, view.x, view.y, view.width, view.height, false ); // overlay IMaterial *pMatReplay = materials->FindMaterial( "dev/replay", TEXTURE_GROUP_OTHER, true ); if ( pMatReplay ) { IMaterialVar* pVar = pMatReplay->FindVar( "$c0_x", NULL ); pVar->SetFloatValue( 1.0f / w ); pVar = pMatReplay->FindVar( "$c0_y", NULL ); pVar->SetFloatValue( 1.0f / h ); pVar = pMatReplay->FindVar( "$c0_z", NULL ); pVar->SetFloatValue( gpGlobals->realtime ); pVar = pMatReplay->FindVar( "$c0_w", NULL ); pVar->SetFloatValue( flFade ); //if( flFade > 0 && g_HltvReplaySystem.GetHltvReplayDelay() ) Msg( "%.2f dev/replay fade %.2f\n", Plat_FloatTime(), flFade ); // replayfade pRenderContext->DrawScreenSpaceRectangle( pMatReplay, x, y, w, h, 0, 0, w-1, h-1, w, h ); } } else if ( flFade > 0 ) { //Msg( "%.2f simple fade %.2f\n", Plat_FloatTime(), flFade ); // replayfade float flFadeAdj = flFade;// sinf( flFade * ( M_PI * .5f ) ); CMatRenderContextPtr pRenderContext( materials ); int x, y, w, h; pRenderContext->GetViewport( x, y, w, h ); //bars if ( IMaterial *pMatFade = materials->FindMaterial( "dev/replay_bars", TEXTURE_GROUP_OTHER, true ) ) { if ( IMaterialVar* pVar = pMatFade->FindVar( "$alpha", NULL ) ) { pVar->SetFloatValue( flFadeAdj ); pRenderContext->DrawScreenSpaceRectangle( pMatFade, 0, 0, w, h, 0, 0, 0, 0, 1, 1 ); } } } } //----------------------------------------------------------------------------- // Purpose: Renders voice feedback and other sprites attached to players // Input : none //----------------------------------------------------------------------------- void CCSViewRender::RenderPlayerSprites() { CViewRender::RenderPlayerSprites(); RadioManager()->DrawHeadLabels(); }