//========= 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.999f;

		// 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