//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Renders a cone for spotlight entities. Only renders when the parent
//			entity is selected.
//
//=============================================================================//

#include "stdafx.h"
#include "Box3D.h"
#include "fgdlib/HelperInfo.h"
#include "MapDefs.h"		// dvs: For COORD_NOTINIT
#include "MapEntity.h"
#include "MapLightCone.h"
#include "Render3D.h"
#include "Material.h"
#include "materialsystem/imaterialsystem.h"
#include "TextureSystem.h"
#include "hammer.h"
#include "Options.h"


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

#define NUM_LIGHTCONE_ZONES		5


IMPLEMENT_MAPCLASS(CMapLightCone)


//-----------------------------------------------------------------------------
// Purpose: Factory function. Used for creating a CMapLightCone helper from a
//			set of string parameters from the FGD file.
// Input  : *pInfo - Pointer to helper info class which gives us information
//				about how to create the helper.
// Output : Returns a pointer to the helper, NULL if an error occurs.
//-----------------------------------------------------------------------------
CMapClass *CMapLightCone::Create(CHelperInfo *pHelperInfo, CMapEntity *pParent)
{
	CMapLightCone *new1=new CMapLightCone;
	if( new1 != NULL )
	{
		//
		// The first parameter should be the inner fov key name. If it isn't
		// there we assume "_inner_cone".
		//
		const char *pszKeyName = pHelperInfo->GetParameter(0);
		if (pszKeyName != NULL)
		{
			strcpy(new1->m_szInnerConeKeyName, pszKeyName);
		}
		else
		{
			strcpy(new1->m_szInnerConeKeyName, "_inner_cone");
		}

		//
		// The second parameter should be the outer fov key name. If it isn't
		// there we assume "_cone".
		//
		pszKeyName = pHelperInfo->GetParameter(1);
		if (pszKeyName != NULL)
		{
			strcpy(new1->m_szOuterConeKeyName, pszKeyName);
		}
		else
		{
			strcpy(new1->m_szOuterConeKeyName, "_cone");
		}

		//
		// The third parameter should be the color of the light. If it isn't
		// there we assume "_light".
		//
		pszKeyName = pHelperInfo->GetParameter(2);
		if (pszKeyName != NULL)
		{
			strcpy(new1->m_szColorKeyName, pszKeyName);
		}
		else
		{
			strcpy(new1->m_szColorKeyName, "_light");
		}

		pszKeyName = pHelperInfo->GetParameter(3);
		if (pszKeyName != NULL)
		{
			new1->m_flPitchScale = Q_atof( pszKeyName );
		}
		else
		{
			new1->m_flPitchScale = 1.0f;
		}
	}
	return new1;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CMapLightCone::CMapLightCone(void)
{
	m_fQuadraticAttn = 1;
	m_fLinearAttn = 0;
	m_fConstantAttn = 0;
	m_bPitchSet = false;
	m_fPitch = 0;
	m_fFocus = 1;
	m_flPitchScale = 1;

	m_fBrightness = 100;
	m_fInnerConeAngle = 0;
	m_fOuterConeAngle = 45;

	m_fFiftyPercentDistance = -1;							// disabled - use attenuation
	m_Angles.Init();
	SignalUpdate( EVTYPE_LIGHTING_CHANGED );
}


//-----------------------------------------------------------------------------
// Purpose: Destructor. Deletes faces allocated by BuildCone.
//-----------------------------------------------------------------------------
CMapLightCone::~CMapLightCone(void)
{
	for (int i = 0; i < m_Faces.Count(); i++)
	{
		CMapFace *pFace = m_Faces.Element(i);
		delete pFace;
	}
	SignalUpdate( EVTYPE_LIGHTING_CHANGED );
}


//-----------------------------------------------------------------------------
// Purpose: Builds the light cone faces in local space. Does NOT call CalcBounds,
//			because that CalcBounds updates the parent, which causes problems
//			in the undo system.
//-----------------------------------------------------------------------------
void CMapLightCone::BuildCone(void)
{
	//
	// Delete the current face list.
	//
	for (int i = 0; i < m_Faces.Count(); i++)
	{
		CMapFace *pFace = m_Faces.Element(i);
		delete pFace;
	}
	m_Faces.RemoveAll();

	//
	// Make sure at least one of the lighting coefficients is nonzero.
	//
	if ((m_fQuadraticAttn == 0) && (m_fLinearAttn == 0) && (m_fConstantAttn == 0))
	{
		m_fConstantAttn = 1;
	}

	//
	// Solve for the lighting scale factor by which the brightness will be multiplied.
	//
	float fScaleFactor = m_fQuadraticAttn * 10000 + m_fLinearAttn * 100 + m_fConstantAttn;
	if (fScaleFactor == 0)
	{
		return;
	}

	//
	// Calculate the distances from the light origin to the various zones.
	//
	float fOffsetDist = 0;
	// Constant attenuation factor doesn't actually offset the cone yet. If it does, uncomment this:
	//SolveQuadratic(fOffsetDist, 0, m_fQuadraticAttn, m_fLinearAttn, -m_fConstantAttn);

	float fZoneDist[NUM_LIGHTCONE_ZONES];
	memset( fZoneDist, 0, sizeof( fZoneDist ) );
	fZoneDist[0] = 0;
	SolveQuadratic(fZoneDist[1], 0.25 * fScaleFactor, m_fQuadraticAttn, m_fLinearAttn, m_fConstantAttn);
	SolveQuadratic(fZoneDist[2], fScaleFactor, m_fQuadraticAttn, m_fLinearAttn, m_fConstantAttn);
	SolveQuadratic(fZoneDist[3], 4 * fScaleFactor, m_fQuadraticAttn, m_fLinearAttn, m_fConstantAttn);
	SolveQuadratic(fZoneDist[4], Options.view3d.fLightConeLength * fScaleFactor, m_fQuadraticAttn, m_fLinearAttn, m_fConstantAttn);

	//
	// there's no cone if it's greater then 90 degrees
	//
	if (m_fOuterConeAngle < 90)
	{

		//
		// Calculate the cone radius at each zone.
		//
		float fZoneRadius[NUM_LIGHTCONE_ZONES];
		for (int i = 0; i < NUM_LIGHTCONE_ZONES; i++)
		{
			fZoneRadius[i] = (fOffsetDist + fZoneDist[i]) * tan(DEG2RAD(m_fOuterConeAngle));
		}

		//
		// Build the new face list using the new parameters.
		//
		float fStepSize = 360.0 / 15.0;
		for (int nZone = 0; nZone < NUM_LIGHTCONE_ZONES - 1; nZone++)
		{
			float fSin0 = 0;
			float fCos0 = 1;

			float fTopDist = fZoneDist[nZone];
			float fBottomDist = fZoneDist[nZone + 1];

			float fTopRadius = fZoneRadius[nZone];
			float fBottomRadius = fZoneRadius[nZone + 1];

			for (int fAngle = fStepSize; fAngle <= 361; fAngle += fStepSize)
			{
				float fSin1 = sin(DEG2RAD(fAngle));
				float fCos1 = cos(DEG2RAD(fAngle));

				Vector Points[4];

				Points[0][2] = fBottomRadius * fCos1;
				Points[0][1] = fBottomRadius * fSin1;
				Points[0][0] = fBottomDist;

				Points[1][2] = fBottomRadius * fCos0;
				Points[1][1] = fBottomRadius * fSin0;
				Points[1][0] = fBottomDist;

				Points[2][2] = fTopRadius * fCos0;
				Points[2][1] = fTopRadius * fSin0;
				Points[2][0] = fTopDist;

				int nPoints = 3;
				if (fTopRadius != 0)
				{
					Points[3][2] = fTopRadius * fCos1;
					Points[3][1] = fTopRadius * fSin1;
					Points[3][0] = fTopDist;
					nPoints = 4;
				}

				CMapFace *pFace = new CMapFace;
				pFace->SetRenderColor(r * (1 - nZone / (float)NUM_LIGHTCONE_ZONES), g * (1 - nZone / (float)NUM_LIGHTCONE_ZONES), b * (1 - nZone / (float)NUM_LIGHTCONE_ZONES));
				pFace->SetRenderAlpha(180);
				pFace->CreateFace(Points, nPoints);
				pFace->RenderUnlit(true);
				m_Faces.AddToTail(pFace);

				fSin0 = fSin1;
				fCos0 = fCos1;
			}
		}
	}

	//
	// Lobe's aren't defined for > 90
	//
	if (m_fOuterConeAngle > 90)
		return;

	//
	// Build the a face list that shows light-angle falloff
	//
	float fStepSize = 360.0 / 15.0;
	float fPitchStepSize = 90.0 / 15.0;
	float fFocusRadius0 = 0;
	float fFocusDist0 = fZoneDist[1];
	float fInnerDot = cos(DEG2RAD(m_fInnerConeAngle));
	float fOuterDot = cos(DEG2RAD(m_fOuterConeAngle));

	for (float fPitch = fPitchStepSize; fPitch < m_fOuterConeAngle + fPitchStepSize; fPitch += fPitchStepSize)
	{
		float fSin0 = 0;
		float fCos0 = 1;

		// clamp to edge of cone
		if (fPitch > m_fOuterConeAngle)
			fPitch = m_fOuterConeAngle;

		float fIllumination = 0;
		if (fPitch <= m_fInnerConeAngle)
		{
			fIllumination = 1.0;
		}
		else
		{
			float fPitchDot = cos(DEG2RAD(fPitch));

			fIllumination = (fPitchDot - fOuterDot) / (fInnerDot - fOuterDot);

			if ((m_fFocus != 1) && (m_fFocus != 0))
			{
				fIllumination = pow( fIllumination, m_fFocus );
			}
		}

		// cosine falloff ^ exponent

		// draw as lobe
		float fFocusDist1 = cos(DEG2RAD(fPitch)) * fIllumination * fZoneDist[1];
		float fFocusRadius1 = sin(DEG2RAD(fPitch)) * fIllumination * fZoneDist[1];

		// draw as disk
		// float fFocusDist1 = fZoneDist[1];
		// float fFocusRadius1 = sin(DEG2RAD(fPitch)) * fZoneRadius[1] / sin(DEG_RAD * m_fConeAngle);

		for (int fAngle = fStepSize; fAngle <= 361; fAngle += fStepSize)
		{
			float fSin1 = sin(DEG2RAD(fAngle));
			float fCos1 = cos(DEG2RAD(fAngle));

			Vector Points[4];

			Points[0][2] = fFocusRadius1 * fCos0;
			Points[0][1] = fFocusRadius1 * fSin0;
			Points[0][0] = fFocusDist1;

			Points[1][2] = fFocusRadius1 * fCos1;

			Points[1][1] = fFocusRadius1 * fSin1;
			Points[1][0] = fFocusDist1;

			Points[2][2] = fFocusRadius0 * fCos1;
			Points[2][1] = fFocusRadius0 * fSin1;
			Points[2][0] = fFocusDist0;

			int nPoints = 3;
			if (fFocusRadius0 != 0)
			{
				Points[3][2] = fFocusRadius0 * fCos0;
				Points[3][1] = fFocusRadius0 * fSin0;
				Points[3][0] = fFocusDist0;
				nPoints = 4;
			}

			CMapFace *pFace = new CMapFace;
			pFace->SetRenderColor(r * fIllumination, g * fIllumination, b * fIllumination);
			pFace->SetRenderAlpha(180);
			pFace->CreateFace(Points, nPoints);
			pFace->RenderUnlit(true);
			m_Faces.AddToTail(pFace);

			fSin0 = fSin1;
			fCos0 = fCos1;
		}
		fFocusRadius0 = fFocusRadius1;
		fFocusDist0 = fFocusDist1;
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : bFullUpdate - 
//-----------------------------------------------------------------------------
void CMapLightCone::CalcBounds(BOOL bFullUpdate)
{
	CMapClass::CalcBounds(bFullUpdate);

	//
	// HACK: Update our origin to stick to our parent.
	//
	if (m_pParent != NULL)
	{
		GetParent()->GetOrigin(m_Origin);
	}

	//
	// Pretend to be very small for the 2D view. Won't be necessary when 2D
	// rendering is done in the map classes.
	//
	m_Render2DBox.ResetBounds();
	m_Render2DBox.UpdateBounds(m_Origin);

	SetCullBoxFromFaceList( &m_Faces );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : Angles - 
//-----------------------------------------------------------------------------
void CMapLightCone::GetAngles(QAngle &Angles)
{
	Angles = m_Angles;

	if (m_bPitchSet)
	{
		Angles[PITCH] = m_fPitch;
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Output : CMapClass
//-----------------------------------------------------------------------------
CMapClass *CMapLightCone::Copy(bool bUpdateDependencies)
{
	CMapLightCone *pCopy = new CMapLightCone;

	if (pCopy != NULL)
	{
		pCopy->CopyFrom(this, bUpdateDependencies);
	}

	return(pCopy);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pObject - 
// Output : CMapClass
//-----------------------------------------------------------------------------
CMapClass *CMapLightCone::CopyFrom(CMapClass *pObject, bool bUpdateDependencies)
{
	Assert(pObject->IsMapClass(MAPCLASS_TYPE(CMapLightCone)));
	CMapLightCone *pFrom = (CMapLightCone *)pObject;

	CMapClass::CopyFrom(pObject, bUpdateDependencies);

	m_fBrightness = pFrom->m_fBrightness;

	m_fQuadraticAttn = pFrom->m_fQuadraticAttn;
	m_fLinearAttn = pFrom->m_fLinearAttn;
	m_fConstantAttn = pFrom->m_fConstantAttn;
	m_flPitchScale = pFrom->m_flPitchScale;

	m_fInnerConeAngle = pFrom->m_fInnerConeAngle;
	m_fOuterConeAngle = pFrom->m_fOuterConeAngle;

	m_Angles = pFrom->m_Angles;

	m_bPitchSet = pFrom->m_bPitchSet;
	m_fPitch = pFrom->m_fPitch;

	m_fFocus = pFrom->m_fFocus;

	m_fFiftyPercentDistance = pFrom->m_fFiftyPercentDistance;
	m_fZeroPercentDistance = pFrom->m_fZeroPercentDistance;
	m_LightColor = pFrom->m_LightColor;
	
	Q_strncpy( m_szColorKeyName, pFrom->m_szColorKeyName, sizeof( m_szColorKeyName ) );
	Q_strncpy( m_szInnerConeKeyName, pFrom->m_szInnerConeKeyName, sizeof( m_szInnerConeKeyName ) );
	Q_strncpy( m_szOuterConeKeyName, pFrom->m_szOuterConeKeyName, sizeof( m_szOuterConeKeyName ) );

	BuildCone();

	SignalUpdate( EVTYPE_LIGHTING_CHANGED );
	return(this);
}


//-----------------------------------------------------------------------------
// Purpose: Notifies that this object's parent entity has had a key value change.
// Input  : szKey - The key that changed.
//			szValue - The new value of the key.
//-----------------------------------------------------------------------------
void CMapLightCone::OnParentKeyChanged(const char *szKey, const char *szValue)
{
	bool bRebuild = true;

	if (!stricmp(szKey, "angles"))
	{
		sscanf(szValue, "%f %f %f", &m_Angles[PITCH], &m_Angles[YAW], &m_Angles[ROLL]);
	}
	else if (!stricmp(szKey, m_szColorKeyName))
	{
		int nRed;
		int nGreen;
		int nBlue;
		int nBrightness;
		sscanf(szValue, "%d %d %d %d", &nRed, &nGreen, &nBlue, &nBrightness);

		r = m_LightColor.x = nRed;
		g = m_LightColor.y = nGreen;
		b = m_LightColor.z = nBlue;
		m_fBrightness = nBrightness;
	}
	else if (!stricmp(szKey, "pitch"))
	{
		// Pitch
		m_bPitchSet = true;
		m_fPitch = atof(szValue);
	}
	else if (!stricmp(szKey, "_constant_attn"))
	{
		// Constant attenuation
		m_fConstantAttn = atof(szValue);
	}
	else if (!stricmp(szKey, "_linear_attn"))
	{
		// Linear attenuation
		m_fLinearAttn = atof(szValue);
	}
	else if (!stricmp(szKey, "_quadratic_attn"))
	{
		// Quadratic attenuation
		m_fQuadraticAttn = atof(szValue);
	}
	else if (!stricmp(szKey, "_exponent"))
	{
		// Focus
		m_fFocus = atof(szValue);
	}
	else if (!stricmp(szKey, "_fifty_percent_distance"))
	{
		// Focus
		m_fFiftyPercentDistance = atof(szValue);
	}
	else if (!stricmp(szKey, "_zero_percent_distance"))
	{
		// Focus
		m_fZeroPercentDistance = atof(szValue);
	}
	else if (!stricmp(szKey, m_szInnerConeKeyName) || !stricmp(szKey, m_szOuterConeKeyName))
	{
		// check both of these together since they might be the same key.
		if( !stricmp(szKey, m_szInnerConeKeyName ))
		{
			// Inner Cone angle
			m_fInnerConeAngle = atof(szValue);
		}
		if( !stricmp(szKey, m_szOuterConeKeyName ))
		{
			// Outer Cone angle
			m_fOuterConeAngle = atof(szValue);
		}
	}
	else
	{
		bRebuild = false;
	}

	if (bRebuild)
	{
		SignalUpdate( EVTYPE_LIGHTING_CHANGED );
		BuildCone();
		PostUpdate(Notify_Changed);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Called after the entire map has been loaded. This allows the object
//			to perform any linking with other map objects or to do other operations
//			that require all world objects to be present.
// Input  : pWorld - The world that we are in.
//-----------------------------------------------------------------------------
void CMapLightCone::PostloadWorld(CMapWorld *pWorld)
{
	CMapClass::PostloadWorld(pWorld);

	BuildCone();
	SignalUpdate( EVTYPE_LIGHTING_CHANGED );
	CalcBounds();
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pRender - 
//-----------------------------------------------------------------------------
void CMapLightCone::Render3D(CRender3D *pRender)
{
	if (m_pParent->IsSelected())
	{
		CMatRenderContextPtr pRenderContext( MaterialSystemInterface() );
		pRenderContext->MatrixMode(MATERIAL_MODEL);
		pRenderContext->PushMatrix();

		pRenderContext->Translate(m_Origin[0],  m_Origin[1],  m_Origin[2]);

		QAngle Angles;
		GetAngles(Angles);

		pRenderContext->Rotate(Angles[YAW], 0, 0, 1);
		pRenderContext->Rotate(m_flPitchScale * Angles[PITCH], 0, -1, 0);
		pRenderContext->Rotate(Angles[ROLL], 1, 0, 0);

		if (
			(pRender->GetCurrentRenderMode() != RENDER_MODE_LIGHT_PREVIEW2) &&
			(pRender->GetCurrentRenderMode() != RENDER_MODE_LIGHT_PREVIEW_RAYTRACED) &&
			(GetSelectionState() != SELECT_MODIFY )
			)
		{
			// Render the cone faces flatshaded.
			pRender->PushRenderMode( RENDER_MODE_TRANSLUCENT_FLAT );
			
			for (int i = 0; i < m_Faces.Count(); i++)
			{
				CMapFace *pFace = m_Faces.Element(i);
				pFace->Render3D(pRender);
			}

			pRender->PopRenderMode();
		}

		//
		// Render the cone faces in yellow wireframe (on top)
		//
		pRender->PushRenderMode( RENDER_MODE_WIREFRAME );

		for (int i = 0; i < m_Faces.Count(); i++)
		{
			CMapFace *pFace = m_Faces.Element(i);
			pFace->Render3D(pRender);
		}

		//
		// Restore the default rendering mode.
		//
		pRender->PopRenderMode();

		pRenderContext->PopMatrix();
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : File - 
//			bRMF - 
// Output : int
//-----------------------------------------------------------------------------
int CMapLightCone::SerializeRMF(std::fstream &File, BOOL bRMF)
{
	return(0);
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : File - 
//			bRMF - 
// Output : int
//-----------------------------------------------------------------------------
int CMapLightCone::SerializeMAP(std::fstream &File, BOOL bRMF)
{
	return(0);
}


//-----------------------------------------------------------------------------
// Purpose: Solves a quadratic equation with the given coefficients.
// Input  : x - Receives solution.
//			y - Root to solve for.
//			A, B, C - Quadratic, linear, and constant coefficients.
// Output : Returns true if a real solution was found, false if not.
//-----------------------------------------------------------------------------
bool CMapLightCone::SolveQuadratic(float &x, float y, float A, float B, float C)
{
	C -= y;

	if (A == 0)
	{
		if (B != 0)
		{
			x = -C / B;
			return(true);
		}
	}
	else
	{
		float fDeterminant = B * B - 4 * A * C;
		if (fDeterminant > 0)
		{
			x = (-B + sqrt(fDeterminant)) / (2 * A);
			return(true);
		}
	}

	return(false);
}


//-----------------------------------------------------------------------------
// Purpose: Never select anything because of this helper.
//-----------------------------------------------------------------------------
CMapClass *CMapLightCone::PrepareSelection(SelectMode_t eSelectMode)
{
	return NULL;
}