//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:		Dynamic light
//
// $Workfile:     $
// $Date:         $
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "dlight.h"
#include "iefx.h"
#include "iviewrender.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"


#if HL2_EPISODIC
// In Episodic we unify the NO_WORLD_ILLUMINATION lights to use 
// the more efficient elight structure instead. This should theoretically
// be extended to other projects but may have unintended consequences
// and bears more thorough testing.
//
// For an earlier iteration on this technique see changelist 214433,
// which had a specific flag for use of elights.
#define DLIGHT_NO_WORLD_USES_ELIGHT 1
#endif


//-----------------------------------------------------------------------------
// A dynamic light, with the goofy hack needed for spotlights
//-----------------------------------------------------------------------------
class C_DynamicLight : public C_BaseEntity
{
public:
	DECLARE_CLASS( C_DynamicLight, C_BaseEntity );
	DECLARE_CLIENTCLASS();

	C_DynamicLight();

public:
	void	OnDataChanged(DataUpdateType_t updateType);
	bool	ShouldDraw();
	void	ClientThink( void );
	void	Release( void );

	unsigned char	m_Flags;
	unsigned char	m_LightStyle;

	float	m_Radius;
	int		m_Exponent;
	float	m_InnerAngle;
	float	m_OuterAngle;
	float	m_SpotRadius;

private:
	dlight_t*	m_pDynamicLight;
	dlight_t*	m_pSpotlightEnd;


	inline bool ShouldBeElight() { return (m_Flags & DLIGHT_NO_WORLD_ILLUMINATION); }
};

IMPLEMENT_CLIENTCLASS_DT(C_DynamicLight, DT_DynamicLight, CDynamicLight)
	RecvPropInt		(RECVINFO(m_Flags)),
	RecvPropInt		(RECVINFO(m_LightStyle)),
	RecvPropFloat	(RECVINFO(m_Radius)),
	RecvPropInt		(RECVINFO(m_Exponent)),
	RecvPropFloat	(RECVINFO(m_InnerAngle)),
	RecvPropFloat	(RECVINFO(m_OuterAngle)),
	RecvPropFloat	(RECVINFO(m_SpotRadius)),
END_RECV_TABLE()


//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
C_DynamicLight::C_DynamicLight(void) : m_pSpotlightEnd(0), m_pDynamicLight(0)
{
}


//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
void C_DynamicLight::OnDataChanged(DataUpdateType_t updateType)
{
	if ( updateType == DATA_UPDATE_CREATED )
	{
		SetNextClientThink(gpGlobals->curtime + 0.05);
	}

	BaseClass::OnDataChanged( updateType );
}


//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
bool C_DynamicLight::ShouldDraw()
{
	return false;
}

//------------------------------------------------------------------------------
// Purpose : Disable drawing of this light when entity perishes
//------------------------------------------------------------------------------
void C_DynamicLight::Release()
{
	if (m_pDynamicLight)
	{
		m_pDynamicLight->die = gpGlobals->curtime;
		m_pDynamicLight = 0;
	}
	
	if (m_pSpotlightEnd)
	{
		m_pSpotlightEnd->die = gpGlobals->curtime;
		m_pSpotlightEnd = 0;
	}

	BaseClass::Release();
}


//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
void C_DynamicLight::ClientThink(void)
{
	Vector forward;
	AngleVectors( GetAbsAngles(), &forward );

	if ( (m_Flags & DLIGHT_NO_MODEL_ILLUMINATION) == 0 )
	{
		// Deal with the model light
 		if ( !m_pDynamicLight || (m_pDynamicLight->key != index) )
		{
#if DLIGHT_NO_WORLD_USES_ELIGHT
			m_pDynamicLight = ShouldBeElight() != 0
				? effects->CL_AllocElight( index )
				: effects->CL_AllocDlight( index );
#else
			m_pDynamicLight = effects->CL_AllocDlight( index );
#endif
			Assert (m_pDynamicLight);
			m_pDynamicLight->minlight = 0;
		}

		m_pDynamicLight->style = m_LightStyle;
		m_pDynamicLight->radius = m_Radius;
		m_pDynamicLight->flags = m_Flags;
		if ( m_OuterAngle > 0 )
			m_pDynamicLight->flags |= DLIGHT_NO_WORLD_ILLUMINATION;
		m_pDynamicLight->color.r = m_clrRender->r;
		m_pDynamicLight->color.g = m_clrRender->g;
		m_pDynamicLight->color.b = m_clrRender->b;
		m_pDynamicLight->color.exponent	= m_Exponent;	// this makes it match the world
		m_pDynamicLight->origin		= GetAbsOrigin();
		m_pDynamicLight->m_InnerAngle = m_InnerAngle;
		m_pDynamicLight->m_OuterAngle = m_OuterAngle;
		m_pDynamicLight->die = gpGlobals->curtime + 1e6;
		m_pDynamicLight->m_Direction = forward;
	}
	else
	{
		// In this case, the m_Flags could have changed; which is how we turn the light off
		if (m_pDynamicLight)
		{
			m_pDynamicLight->die = gpGlobals->curtime;
			m_pDynamicLight = 0;
		}
	}
	
#if DLIGHT_NO_WORLD_USES_ELIGHT
	if (( m_OuterAngle > 0 ) && !ShouldBeElight())
#else
	if (( m_OuterAngle > 0 ) && ((m_Flags & DLIGHT_NO_WORLD_ILLUMINATION) == 0))
#endif
	{
		// Raycast to where the endpoint goes
		// Deal with the environment light
		if ( !m_pSpotlightEnd || (m_pSpotlightEnd->key != -index) )
		{
			m_pSpotlightEnd = effects->CL_AllocDlight( -index );
			Assert (m_pSpotlightEnd);
		}
				  
		// Trace a line outward, don't use hitboxes (too slow)
		Vector end;
		VectorMA( GetAbsOrigin(), m_Radius, forward, end );

		trace_t		pm;
		C_BaseEntity::PushEnableAbsRecomputations( false );	 // HACK don't recompute positions while doing RayTrace
		UTIL_TraceLine( GetAbsOrigin(), end, MASK_NPCWORLDSTATIC, NULL, COLLISION_GROUP_NONE, &pm );
		C_BaseEntity::PopEnableAbsRecomputations();
		VectorCopy( pm.endpos, m_pSpotlightEnd->origin );
		
		if (pm.fraction == 1.0f)
		{
			m_pSpotlightEnd->die = gpGlobals->curtime;
			m_pSpotlightEnd = 0;
		}
		else
		{
			float falloff = 1.0 - pm.fraction;
			falloff *= falloff;

			m_pSpotlightEnd->style = m_LightStyle;
			m_pSpotlightEnd->flags = DLIGHT_NO_MODEL_ILLUMINATION | (m_Flags & DLIGHT_DISPLACEMENT_MASK);
			m_pSpotlightEnd->radius		= m_SpotRadius; // * falloff;
			m_pSpotlightEnd->die		= gpGlobals->curtime + 1e6;
			m_pSpotlightEnd->color.r	= m_clrRender->r * falloff;
			m_pSpotlightEnd->color.g	= m_clrRender->g * falloff;
			m_pSpotlightEnd->color.b	= m_clrRender->b * falloff;
			m_pSpotlightEnd->color.exponent	= m_Exponent;

			// For bumped lighting
			m_pSpotlightEnd->m_Direction = forward;

			// Update list of surfaces we influence
			render->TouchLight( m_pSpotlightEnd );
		}
	}
	else
	{
		// In this case, the m_Flags could have changed; which is how we turn the light off
		if (m_pSpotlightEnd)
		{
			m_pSpotlightEnd->die = gpGlobals->curtime;
			m_pSpotlightEnd = 0;
		}
	}

	SetNextClientThink(gpGlobals->curtime + 0.001);
}