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.
579 lines
15 KiB
579 lines
15 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
#include "cbase.h" |
|
#include "view.h" |
|
#include "iviewrender.h" |
|
#include "c_sun.h" |
|
#include "particles_simple.h" |
|
#include "clienteffectprecachesystem.h" |
|
#include "c_pixel_visibility.h" |
|
#include "glow_overlay.h" |
|
#include "utllinkedlist.h" |
|
#include "view_shared.h" |
|
#include "tier0/vprof.h" |
|
#include "materialsystem/imaterialvar.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
CLIENTEFFECT_REGISTER_BEGIN( PrecacheEffectGlow ) |
|
CLIENTEFFECT_MATERIAL( "sun/overlay" ) |
|
CLIENTEFFECT_MATERIAL( "sprites/light_glow02_add_noz" ) |
|
CLIENTEFFECT_REGISTER_END() |
|
|
|
class CGlowOverlaySystem : public CAutoGameSystem |
|
{ |
|
public: |
|
CGlowOverlaySystem() : CAutoGameSystem( "CGlowOverlaySystem" ) |
|
{ |
|
} |
|
// Level init, shutdown |
|
virtual void LevelInitPreEntity() {} |
|
|
|
virtual void LevelShutdownPostEntity() |
|
{ |
|
m_GlowOverlays.PurgeAndDeleteElements(); |
|
} |
|
|
|
unsigned short AddToOverlayList( CGlowOverlay *pGlow ) |
|
{ |
|
return m_GlowOverlays.AddToTail( pGlow ); |
|
} |
|
void RemoveFromOverlayList( unsigned short handle ) |
|
{ |
|
if( handle != m_GlowOverlays.InvalidIndex() ) |
|
{ |
|
m_GlowOverlays.Remove( handle ); |
|
} |
|
} |
|
CUtlLinkedList<CGlowOverlay*, unsigned short> m_GlowOverlays; |
|
}; |
|
CGlowOverlaySystem g_GlowOverlaySystem; |
|
|
|
ConVar cl_ShowSunVectors( "cl_ShowSunVectors", "0", 0 ); |
|
|
|
ConVar cl_sun_decay_rate( "cl_sun_decay_rate", "0.05", FCVAR_CHEAT ); |
|
|
|
// Dot product space the overlays are drawn in. |
|
// Here it's setup to allow you to see it if you're looking within 40 degrees of the source. |
|
float g_flOverlayRange = cos( DEG2RAD( 40 ) ); |
|
|
|
// ----------------------------------------------------------------------------- // |
|
// ----------------------------------------------------------------------------- // |
|
|
|
void Do2DRotation( Vector vIn, Vector &vOut, float flDegrees, int i1, int i2, int i3 ) |
|
{ |
|
float c, s; |
|
SinCos( DEG2RAD( flDegrees ), &s, &c ); |
|
|
|
vOut[i1] = vIn[i1]*c - vIn[i2]*s; |
|
vOut[i2] = vIn[i1]*s + vIn[i2]*c; |
|
vOut[i3] = vIn[i3]; |
|
} |
|
|
|
|
|
// ----------------------------------------------------------------------------- // |
|
// ----------------------------------------------------------------------------- // |
|
|
|
CGlowOverlay::CGlowOverlay() |
|
{ |
|
m_ListIndex = 0xFFFF; |
|
m_nSprites = 0; |
|
m_flGlowObstructionScale = 0.0f; |
|
m_bDirectional = false; |
|
m_bInSky = false; |
|
m_skyObstructionScale = 1.0f; |
|
m_bActivated = false; |
|
|
|
m_flProxyRadius = 2.0f; |
|
|
|
m_queryHandle = 0; |
|
|
|
m_flHDRColorScale = 1.0f; |
|
|
|
//Init our sprites |
|
for ( int i = 0; i < MAX_SUN_LAYERS; i++ ) |
|
{ |
|
m_Sprites[i].m_vColor.Init(); |
|
m_Sprites[i].m_flHorzSize = 1.0f; |
|
m_Sprites[i].m_flVertSize = 1.0f; |
|
m_Sprites[i].m_pMaterial = NULL; |
|
} |
|
|
|
#ifdef PORTAL |
|
for( int i = 0; i != MAX_PORTAL_RECURSIVE_VIEWS; ++i ) |
|
{ |
|
m_skyObstructionScaleBackups[i] = 1.0f; |
|
} |
|
#endif |
|
} |
|
|
|
|
|
CGlowOverlay::~CGlowOverlay() |
|
{ |
|
g_GlowOverlaySystem.RemoveFromOverlayList( m_ListIndex ); |
|
} |
|
|
|
|
|
bool CGlowOverlay::Update() |
|
{ |
|
return true; |
|
} |
|
|
|
ConVar building_cubemaps( "building_cubemaps", "0" ); |
|
|
|
float CGlowOverlay::CalcGlowAspect() |
|
{ |
|
if ( m_nSprites ) |
|
{ |
|
if ( m_Sprites[0].m_flHorzSize != 0 && m_Sprites[0].m_flVertSize != 0 ) |
|
return m_Sprites[0].m_flHorzSize / m_Sprites[0].m_flVertSize; |
|
} |
|
return 1.0f; |
|
} |
|
|
|
void CGlowOverlay::UpdateSkyGlowObstruction( float zFar, bool bCacheFullSceneState ) |
|
{ |
|
Assert( m_bInSky ); |
|
|
|
// If we already cached the sky obstruction and are still using that, early-out |
|
if ( bCacheFullSceneState && m_bCacheSkyObstruction ) |
|
return; |
|
|
|
// Turning on sky obstruction caching mode |
|
if ( bCacheFullSceneState && !m_bCacheSkyObstruction ) |
|
{ |
|
m_bCacheSkyObstruction = true; |
|
} |
|
|
|
// Turning off sky obstruction caching mode |
|
if ( !bCacheFullSceneState && m_bCacheSkyObstruction ) |
|
{ |
|
m_bCacheSkyObstruction = false; |
|
} |
|
|
|
if ( PixelVisibility_IsAvailable() ) |
|
{ |
|
// Trace a ray at the object. |
|
Vector pos = CurrentViewOrigin() + m_vDirection * zFar * 0.99f; //9f; |
|
|
|
// UNDONE: Can probably do only the pixelvis query in this case if you can figure out where |
|
// to put it - or save the position of this trace |
|
pixelvis_queryparams_t params; |
|
params.Init( pos, m_flProxyRadius ); |
|
params.bSizeInScreenspace = true; |
|
m_skyObstructionScale = PixelVisibility_FractionVisible( params, &m_queryHandle ); |
|
return; |
|
} |
|
// Trace a ray at the object. |
|
trace_t trace; |
|
UTIL_TraceLine( CurrentViewOrigin(), CurrentViewOrigin() + (m_vDirection*MAX_TRACE_LENGTH), |
|
CONTENTS_SOLID, NULL, COLLISION_GROUP_NONE, &trace ); |
|
|
|
// back the trace with a pixel query to occlude with models |
|
if ( trace.surface.flags & SURF_SKY ) |
|
{ |
|
m_skyObstructionScale = 1.0f; |
|
} |
|
else |
|
{ |
|
m_skyObstructionScale = 0.0f; |
|
} |
|
} |
|
|
|
|
|
void CGlowOverlay::UpdateGlowObstruction( const Vector &vToGlow, bool bCacheFullSceneState ) |
|
{ |
|
// If we already cached the glow obstruction and are still using that, early-out |
|
if ( bCacheFullSceneState && m_bCacheGlowObstruction ) |
|
return; |
|
|
|
if ( bCacheFullSceneState && !m_bCacheGlowObstruction ) // If turning on sky obstruction caching mode |
|
{ |
|
m_bCacheGlowObstruction = true; |
|
} |
|
|
|
if ( !bCacheFullSceneState && m_bCacheGlowObstruction ) |
|
{ |
|
m_bCacheGlowObstruction = false; |
|
} |
|
|
|
if ( PixelVisibility_IsAvailable() ) |
|
{ |
|
if ( m_bInSky ) |
|
{ |
|
const CViewSetup *pViewSetup = view->GetViewSetup(); |
|
Vector pos = CurrentViewOrigin() + m_vDirection * (pViewSetup->zFar * 0.999f); |
|
pixelvis_queryparams_t params; |
|
params.Init( pos, m_flProxyRadius, CalcGlowAspect() ); |
|
params.bSizeInScreenspace = true; |
|
// use a pixel query to occlude with models |
|
m_flGlowObstructionScale = PixelVisibility_FractionVisible( params, &m_queryHandle ) * m_skyObstructionScale; |
|
} |
|
else |
|
{ |
|
// If it's not in the sky, then we need a valid position or else we don't |
|
// know what's in front of it. |
|
Assert( !m_bDirectional ); |
|
|
|
pixelvis_queryparams_t params; |
|
params.Init( m_vPos, m_flProxyRadius, CalcGlowAspect() ); |
|
|
|
m_flGlowObstructionScale = PixelVisibility_FractionVisible( params, &m_queryHandle ); |
|
} |
|
return; |
|
} |
|
|
|
bool bFade = false; |
|
if ( m_bInSky ) |
|
{ |
|
// Trace a ray at the object. |
|
trace_t trace; |
|
UTIL_TraceLine( CurrentViewOrigin(), CurrentViewOrigin() + (vToGlow*MAX_TRACE_LENGTH), |
|
CONTENTS_SOLID, NULL, COLLISION_GROUP_NONE, &trace ); |
|
|
|
bFade = (trace.fraction < 1 && !(trace.surface.flags & SURF_SKY)); |
|
} |
|
else |
|
{ |
|
// If it's not in the sky, then we need a valid position or else we don't |
|
// know what's in front of it. |
|
Assert( !m_bDirectional ); |
|
|
|
pixelvis_queryparams_t params; |
|
params.Init( m_vPos, m_flProxyRadius ); |
|
|
|
bFade = PixelVisibility_FractionVisible( params, &m_queryHandle ) < 1.0f ? true : false; |
|
|
|
} |
|
|
|
if ( bFade ) |
|
{ |
|
if ( building_cubemaps.GetBool() ) |
|
{ |
|
m_flGlowObstructionScale = 0.0f; |
|
} |
|
else |
|
{ |
|
m_flGlowObstructionScale -= gpGlobals->frametime / cl_sun_decay_rate.GetFloat(); |
|
m_flGlowObstructionScale = MAX( m_flGlowObstructionScale, 0.0f ); |
|
} |
|
} |
|
else |
|
{ |
|
if ( building_cubemaps.GetBool() ) |
|
{ |
|
m_flGlowObstructionScale = 1.0f; |
|
} |
|
else |
|
{ |
|
m_flGlowObstructionScale += gpGlobals->frametime / cl_sun_decay_rate.GetFloat(); |
|
m_flGlowObstructionScale = MIN( m_flGlowObstructionScale, 1.0f ); |
|
} |
|
} |
|
} |
|
|
|
void CGlowOverlay::CalcSpriteColorAndSize( |
|
float flDot, |
|
CGlowSprite *pSprite, |
|
float *flHorzSize, |
|
float *flVertSize, |
|
Vector *vColor ) |
|
{ |
|
// The overlay is largest and completely translucent at g_flOverlayRange. |
|
// When the dot product is 1, then it's smaller and more opaque. |
|
const float flSizeAtOverlayRangeMul = 150; |
|
const float flSizeAtOneMul = 70; |
|
|
|
const float flOpacityAtOverlayRange = 0; |
|
const float flOpacityAtOne = 1; |
|
|
|
// Figure out how big and how opaque it will be. |
|
*flHorzSize = RemapValClamped( |
|
flDot, |
|
g_flOverlayRange, |
|
1, |
|
flSizeAtOverlayRangeMul * pSprite->m_flHorzSize, |
|
flSizeAtOneMul * pSprite->m_flHorzSize ); |
|
|
|
*flVertSize = RemapValClamped( |
|
flDot, |
|
g_flOverlayRange, |
|
1, |
|
flSizeAtOverlayRangeMul * pSprite->m_flVertSize, |
|
flSizeAtOneMul * pSprite->m_flVertSize ); |
|
|
|
float flOpacity = RemapValClamped( |
|
flDot, |
|
g_flOverlayRange, |
|
1, |
|
flOpacityAtOverlayRange, |
|
flOpacityAtOne ); |
|
|
|
flOpacity = flOpacity * m_flGlowObstructionScale; |
|
*vColor = pSprite->m_vColor * flOpacity; |
|
} |
|
|
|
|
|
void CGlowOverlay::CalcBasis( |
|
const Vector &vToGlow, |
|
float flHorzSize, |
|
float flVertSize, |
|
Vector &vBasePt, |
|
Vector &vUp, |
|
Vector &vRight ) |
|
{ |
|
const float flOverlayDist = 100; |
|
vBasePt = CurrentViewOrigin() + vToGlow * flOverlayDist; |
|
|
|
vUp.Init( 0, 0, 1 ); |
|
|
|
vRight = vToGlow.Cross( vUp ); |
|
VectorNormalize( vRight ); |
|
|
|
vUp = vRight.Cross( vToGlow ); |
|
VectorNormalize( vUp ); |
|
|
|
vRight *= flHorzSize; |
|
vUp *= flVertSize; |
|
} |
|
|
|
|
|
void CGlowOverlay::Draw( bool bCacheFullSceneState ) |
|
{ |
|
extern ConVar r_drawsprites; |
|
if( !r_drawsprites.GetBool() ) |
|
return; |
|
|
|
// Get the vector to the sun. |
|
Vector vToGlow; |
|
|
|
if( m_bDirectional ) |
|
vToGlow = m_vDirection; |
|
else |
|
vToGlow = m_vPos - CurrentViewOrigin(); |
|
|
|
VectorNormalize( vToGlow ); |
|
|
|
float flDot = vToGlow.Dot( CurrentViewForward() ); |
|
|
|
UpdateGlowObstruction( vToGlow, bCacheFullSceneState ); |
|
if( m_flGlowObstructionScale == 0 ) |
|
return; |
|
|
|
bool bWireframe = ShouldDrawInWireFrameMode() || (r_drawsprites.GetInt() == 2); |
|
|
|
CMatRenderContextPtr pRenderContext( materials ); |
|
|
|
for( int iSprite=0; iSprite < m_nSprites; iSprite++ ) |
|
{ |
|
CGlowSprite *pSprite = &m_Sprites[iSprite]; |
|
|
|
// Figure out the color and size to draw it. |
|
float flHorzSize, flVertSize; |
|
Vector vColor; |
|
CalcSpriteColorAndSize( flDot, pSprite, &flHorzSize, &flVertSize, &vColor ); |
|
|
|
// If we're alpha'd out, then don't bother |
|
if ( vColor.LengthSqr() < 0.00001f ) |
|
continue; |
|
|
|
// Setup the basis to draw the sprite. |
|
Vector vBasePt, vUp, vRight; |
|
CalcBasis( vToGlow, flHorzSize, flVertSize, vBasePt, vUp, vRight ); |
|
|
|
//Get our diagonal radius |
|
float radius = (vRight+vUp).Length(); |
|
if ( R_CullSphere( view->GetFrustum(), 5, &vBasePt, radius ) ) |
|
continue; |
|
|
|
// Get our material (deferred default load) |
|
if ( m_Sprites[iSprite].m_pMaterial == NULL ) |
|
{ |
|
m_Sprites[iSprite].m_pMaterial = materials->FindMaterial( "sprites/light_glow02_add_noz", TEXTURE_GROUP_CLIENT_EFFECTS ); |
|
} |
|
|
|
Assert( m_Sprites[iSprite].m_pMaterial ); |
|
static unsigned int nHDRColorScaleCache = 0; |
|
IMaterialVar *pHDRColorScaleVar = m_Sprites[iSprite].m_pMaterial->FindVarFast( "$hdrcolorscale", &nHDRColorScaleCache ); |
|
if( pHDRColorScaleVar ) |
|
{ |
|
pHDRColorScaleVar->SetFloatValue( m_flHDRColorScale ); |
|
} |
|
|
|
// Draw the sprite. |
|
IMesh *pMesh = pRenderContext->GetDynamicMesh( false, 0, 0, m_Sprites[iSprite].m_pMaterial ); |
|
|
|
CMeshBuilder builder; |
|
builder.Begin( pMesh, MATERIAL_QUADS, 1 ); |
|
|
|
Vector vPt; |
|
|
|
vPt = vBasePt - vRight + vUp; |
|
builder.Position3fv( vPt.Base() ); |
|
builder.Color4f( VectorExpand(vColor), 1 ); |
|
builder.TexCoord2f( 0, 0, 1 ); |
|
builder.AdvanceVertex(); |
|
|
|
vPt = vBasePt + vRight + vUp; |
|
builder.Position3fv( vPt.Base() ); |
|
builder.Color4f( VectorExpand(vColor), 1 ); |
|
builder.TexCoord2f( 0, 1, 1 ); |
|
builder.AdvanceVertex(); |
|
|
|
vPt = vBasePt + vRight - vUp; |
|
builder.Position3fv( vPt.Base() ); |
|
builder.Color4f( VectorExpand(vColor), 1 ); |
|
builder.TexCoord2f( 0, 1, 0 ); |
|
builder.AdvanceVertex(); |
|
|
|
vPt = vBasePt - vRight - vUp; |
|
builder.Position3fv( vPt.Base() ); |
|
builder.Color4f( VectorExpand(vColor), 1 ); |
|
builder.TexCoord2f( 0, 0, 0 ); |
|
builder.AdvanceVertex(); |
|
|
|
builder.End( false, true ); |
|
|
|
if( bWireframe ) |
|
{ |
|
IMaterial *pWireframeMaterial = materials->FindMaterial( "debug/debugwireframevertexcolor", TEXTURE_GROUP_OTHER ); |
|
pRenderContext->Bind( pWireframeMaterial ); |
|
|
|
// Draw the sprite. |
|
IMesh *pMesh = pRenderContext->GetDynamicMesh( false, 0, 0, pWireframeMaterial ); |
|
|
|
CMeshBuilder builder; |
|
builder.Begin( pMesh, MATERIAL_QUADS, 1 ); |
|
|
|
Vector vPt; |
|
|
|
vPt = vBasePt - vRight + vUp; |
|
builder.Position3fv( vPt.Base() ); |
|
builder.Color3f( 1.0f, 0.0f, 0.0f ); |
|
builder.AdvanceVertex(); |
|
|
|
vPt = vBasePt + vRight + vUp; |
|
builder.Position3fv( vPt.Base() ); |
|
builder.Color3f( 1.0f, 0.0f, 0.0f ); |
|
builder.AdvanceVertex(); |
|
|
|
vPt = vBasePt + vRight - vUp; |
|
builder.Position3fv( vPt.Base() ); |
|
builder.Color3f( 1.0f, 0.0f, 0.0f ); |
|
builder.AdvanceVertex(); |
|
|
|
vPt = vBasePt - vRight - vUp; |
|
builder.Position3fv( vPt.Base() ); |
|
builder.Color3f( 1.0f, 0.0f, 0.0f ); |
|
builder.AdvanceVertex(); |
|
|
|
builder.End( false, true ); |
|
} |
|
} |
|
} |
|
|
|
|
|
void CGlowOverlay::Activate() |
|
{ |
|
m_bActivated = true; |
|
if( m_ListIndex == 0xFFFF ) |
|
{ |
|
m_ListIndex = g_GlowOverlaySystem.AddToOverlayList( this ); |
|
} |
|
} |
|
|
|
|
|
void CGlowOverlay::Deactivate() |
|
{ |
|
m_bActivated = false; |
|
} |
|
|
|
|
|
void CGlowOverlay::DrawOverlays( bool bCacheFullSceneState ) |
|
{ |
|
VPROF("CGlowOverlay::DrawOverlays()"); |
|
|
|
CMatRenderContextPtr pRenderContext( materials ); |
|
|
|
bool bClippingEnabled = pRenderContext->EnableClipping( true ); |
|
|
|
unsigned short iNext; |
|
for( unsigned short i=g_GlowOverlaySystem.m_GlowOverlays.Head(); i != g_GlowOverlaySystem.m_GlowOverlays.InvalidIndex(); i = iNext ) |
|
{ |
|
iNext = g_GlowOverlaySystem.m_GlowOverlays.Next( i ); |
|
CGlowOverlay *pOverlay = g_GlowOverlaySystem.m_GlowOverlays[i]; |
|
|
|
if( !pOverlay->m_bActivated ) |
|
continue; |
|
|
|
if( pOverlay->Update() ) |
|
{ |
|
pRenderContext->EnableClipping( ((pOverlay->m_bInSky) ? (false):(bClippingEnabled)) ); //disable clipping in skybox, restore clipping to pre-existing state when not in skybox (it may be off as well) |
|
pOverlay->Draw( bCacheFullSceneState ); |
|
} |
|
else |
|
{ |
|
delete pOverlay; |
|
} |
|
} |
|
|
|
pRenderContext->EnableClipping( bClippingEnabled ); //restore clipping to original state |
|
} |
|
|
|
void CGlowOverlay::UpdateSkyOverlays( float zFar, bool bCacheFullSceneState ) |
|
{ |
|
unsigned short iNext; |
|
for( unsigned short i=g_GlowOverlaySystem.m_GlowOverlays.Head(); i != g_GlowOverlaySystem.m_GlowOverlays.InvalidIndex(); i = iNext ) |
|
{ |
|
iNext = g_GlowOverlaySystem.m_GlowOverlays.Next( i ); |
|
CGlowOverlay *pOverlay = g_GlowOverlaySystem.m_GlowOverlays[i]; |
|
|
|
if( !pOverlay->m_bActivated || !pOverlay->m_bDirectional || !pOverlay->m_bInSky ) |
|
continue; |
|
|
|
pOverlay->UpdateSkyGlowObstruction( zFar, bCacheFullSceneState ); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
#ifdef PORTAL |
|
|
|
void CGlowOverlay::BackupSkyOverlayData( int iBackupToSlot ) |
|
{ |
|
unsigned short iNext; |
|
for( unsigned short i=g_GlowOverlaySystem.m_GlowOverlays.Head(); i != g_GlowOverlaySystem.m_GlowOverlays.InvalidIndex(); i = iNext ) |
|
{ |
|
iNext = g_GlowOverlaySystem.m_GlowOverlays.Next( i ); |
|
CGlowOverlay *pOverlay = g_GlowOverlaySystem.m_GlowOverlays[i]; |
|
|
|
if( !pOverlay->m_bActivated || !pOverlay->m_bDirectional || !pOverlay->m_bInSky ) |
|
continue; |
|
|
|
pOverlay->m_skyObstructionScaleBackups[iBackupToSlot] = pOverlay->m_skyObstructionScale; |
|
} |
|
} |
|
|
|
void CGlowOverlay::RestoreSkyOverlayData( int iRestoreFromSlot ) |
|
{ |
|
unsigned short iNext; |
|
for( unsigned short i=g_GlowOverlaySystem.m_GlowOverlays.Head(); i != g_GlowOverlaySystem.m_GlowOverlays.InvalidIndex(); i = iNext ) |
|
{ |
|
iNext = g_GlowOverlaySystem.m_GlowOverlays.Next( i ); |
|
CGlowOverlay *pOverlay = g_GlowOverlaySystem.m_GlowOverlays[i]; |
|
|
|
if( !pOverlay->m_bActivated || !pOverlay->m_bDirectional || !pOverlay->m_bInSky ) |
|
continue; |
|
|
|
pOverlay->m_skyObstructionScale = pOverlay->m_skyObstructionScaleBackups[iRestoreFromSlot]; |
|
} |
|
} |
|
|
|
#endif //#ifdef PORTAL |
|
|
|
|