
//
// Asura_Projector.h
//
// Created by Dave Reed - 05/01/2007 
// 
// Copyright (C) 2007 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

#ifdef ASURA_TARGET_SM3

#include "Core/Asura_Mempool.h"
#include "Core/Asura_Maths.h"
#include "Camera/Asura_Camera.h"
#include "Projectors/Asura_Projector.h"
#include "Projectors/Asura_Projector_System.h"
#include "Rendering/Asura_Render.h"
#include "Debugging/Asura_DebugText.h"


bool Asura_Projector::ClipToLimitingPlane(Asura_Vector_3 &xNearPoint, Asura_Vector_3 &xFarPoint, Asura_Vector_3 &xClippedPoint)
{		
	float fD = -(m_xLimitingPlanePos*m_xLimitingPlaneNormal);
	
	Asura_Vector_3 xRayVector = (xFarPoint-xNearPoint);
	float fLength = xRayVector.Normalise();
	float fRayDotNormal = (xRayVector * m_xLimitingPlaneNormal);

	if (fRayDotNormal!=0.0f)
	{
		float fT =  -((xNearPoint * m_xLimitingPlaneNormal) + fD) / fRayDotNormal;

		if (fT>0.0f && fT<fLength)
		{
			xClippedPoint = xNearPoint + (xRayVector * fT);
			return true;
		}
	}

	xClippedPoint = xFarPoint;
	return false;		
}

// ------------------------------------------------------------------------------------------
// Asura_Projector::UpdateVertices()
// Update the verts of the projection frustum
// ------------------------------------------------------------------------------------------

void Asura_Projector::UpdateVertices()
{
	// +ve Z is forward.	
	float fOfsMin, fOfsMax;

	// This is to avoid a problem case for the PointInFrustum function
	if (m_fMinRange<0.1f)
	{
		m_fMinRange = 0.1f;
	}

	if (m_eType == ASURA_PROJECTOR_TYPE_PERSPECTIVE)
	{
		fOfsMin = Asura_Maths::Tangent(m_fFoV*0.5f) * m_fMinRange;	
		fOfsMax = Asura_Maths::Tangent(m_fFoV*0.5f) * m_fMaxRange;	
	}
	else
	{
		fOfsMin = m_fScale;	
		fOfsMax = m_fScale;	
	}

	for(int i=0;i<4;i++)
	{	
		const float afScaleX[4] = {-1,1,1,-1};
		const float afScaleY[4] = {-1,-1,1,1};

		m_axVertices[i].x   = afScaleX[i]* fOfsMin * m_fUVAspect;
		m_axVertices[i].y   = afScaleY[i]* fOfsMin;
		m_axVertices[i].z   = m_fMinRange;
		m_axVertices[i+4].x = afScaleX[i]* fOfsMax * m_fUVAspect;
		m_axVertices[i+4].y = afScaleY[i]* fOfsMax;
		m_axVertices[i+4].z = m_fMaxRange;			
	}

	for(int i=0;i<8;i++)
	{
		m_axVertices[i] = m_xPos + (m_axVertices[i] * m_xOrient);		
	}


	if (m_bUseLimitingPlane)
	{
		int iNumClipped = 0;
		Asura_Vector_3 xClippedVerts[4];

		for(int i=0;i<4;i++)
		{							
			bool bResult = ClipToLimitingPlane(m_axVertices[i], m_axVertices[i+4], xClippedVerts[i]);

			if (bResult)
			{
				iNumClipped++;
			}
		}
		
		// If we've not clipped all 4 edges, we revert to the unclipped frustum
		if (iNumClipped==4)
		{
			for(int i=0;i<4;i++)
			{				
				m_axVertices[i+4] = xClippedVerts[i];
			}
		}
	}

	// Update Bounding Box
	m_xBB.Reset();
	for(int i=0;i<8;i++)
	{
		m_xBB.Set(m_axVertices[i]);
	}
}



// ------------------------------------------------------------------------------------------
// Asura_Projector::RenderFrustum()
// Render the projection frustum
// ------------------------------------------------------------------------------------------

void Asura_Projector::RenderFrustum()
{
	bool bPrev = Asura_Render::GetEnableBatchedLineRendering();
	Asura_Render::SetEnableBatchedLineRendering(false);

	for (int iPass=0; iPass<2; iPass++)
	{
		// Pass 0 - Translucent bits - behind scenery
		// Pass 1 - Opaque visible bits
		if (iPass == 0)
		{
			Asura_Render::CurrentStates.RequestZBufferWriteEnabled(false);
			Asura_Render::CurrentStates.RequestZBufferCompareMode(ASURA_ZBUFFERCMP_GREATER);
		}
		else
		{
			Asura_Render::CurrentStates.RequestZBufferWriteEnabled(true);
			Asura_Render::CurrentStates.RequestZBufferCompareMode(ASURA_ZBUFFERCMP_LESSEQUAL);
		}

		for(int i=0;i<4;i++)
		{
			Asura_Vector_3 xVec = *Asura_Camera::GetPosition() - m_xPos;
			if (xVec.MagnitudeSquared() > ((m_fDistFadeStart+m_fDistFadeRange)
										  *(m_fDistFadeStart+m_fDistFadeRange)))
			{
				uint32 uAlpha = (iPass==0)?0x18000000:0x80000000;				

				// Out of range, inactive
				Asura_Render::RenderLine(m_xPos			,   m_axVertices[i],			uAlpha|0x00666666);
				Asura_Render::RenderLine(m_axVertices[i],   m_axVertices[4+i],			uAlpha|0x00888888);
				Asura_Render::RenderLine(m_axVertices[i],   m_axVertices[(1+i)&3],		uAlpha|0x00888888);
				Asura_Render::RenderLine(m_axVertices[i+4], m_axVertices[((1+i)&3)+4],	uAlpha|0x00888888);
			}
			else
			{
				uint32 uAlpha = (iPass==0)?0x20000000:0xff000000;				

				// Currently active
				Asura_Render::RenderLine(m_xPos			,   m_axVertices[i],			uAlpha|0x00666666);
				Asura_Render::RenderLine(m_axVertices[i],   m_axVertices[4+i],			uAlpha|0x00ffff00);
				Asura_Render::RenderLine(m_axVertices[i],   m_axVertices[(1+i)&3],		uAlpha|0x00ffff00);
				Asura_Render::RenderLine(m_axVertices[i+4], m_axVertices[((1+i)&3)+4],	uAlpha|0x00ffff00);
			}		
		}
	}

	Asura_Render::SetEnableBatchedLineRendering(bPrev);
}



void Asura_Projector::SetLookAt(Asura_Vector_3 xFrom, Asura_Vector_3 xAt, Asura_Vector_3 xUp)
{

	// Update the frustum vertices
	UpdateVertices();
}





bool Asura_Projector::PointInFrustum(Asura_Vector_3 xPoint, float fThresholdDist) const
{
	int iPointsIn = 0;

	// 3 verts on each face in the frustum
	const uint16 au16Indices[] = {  0,1,3,
									0,4,5, 
									1,5,6, 
									3,6,7, 
									0,7,4,
									4,7,5};


	for(int i=0; i<6; i++)
	{	
		// Todo - Optimization: Only update the normals/plane equations when the frustum moves
		Asura_Vector_3 xVert = m_axVertices[au16Indices[i*3]];
		Asura_Vector_3 xA = m_axVertices[au16Indices[i*3]] - m_axVertices[au16Indices[i*3 +1]];
		Asura_Vector_3 xB = m_axVertices[au16Indices[i*3 + 2]] - m_axVertices[au16Indices[i*3 +1]];
		xA.Normalise();
		xB.Normalise();
		Asura_Vector_3 xNormal = xA^xB;
		xNormal.Normalise();

		// d = -(ax+by+cz)

		float fD = - ((xNormal.x * xVert.x)+(xNormal.y * xVert.y)+(xNormal.z * xVert.z));

		// dist from plane = ax+by+cz+d
		
		float fDist = ((xNormal.x*xPoint.x)+(xNormal.y*xPoint.y)+(xNormal.z*xPoint.z)) + fD;

		if (fDist<fThresholdDist)
		{
			iPointsIn++;
		}
	}

	return (iPointsIn==6);
}


// get a nice fade-out to the edge of the frustum, approximating the intensity of the light at this point
// returns 1 at brightest point, 0 on edge, and negative values outside
float Asura_Projector::FrustumFade(Asura_Vector_3 xPoint) const
{
	Asura_Vector_3 xRelPos = xPoint-m_xPos;
	
	xRelPos = m_xOrient.ApplyTranspose(xRelPos);

	float fDist = xRelPos.Magnitude();
	float fTanU = Asura_Maths::Abs(xRelPos.x) / xRelPos.z;
	float fTanV = Asura_Maths::Abs(xRelPos.y) / xRelPos.z;
	
	// use limits of range
	// alternatively: use falloffs... probably would look better...
	float fTanFov = Asura_Maths::Tangent(m_fFoV*0.5f);
	float fFarness = Asura_Maths::Max( fTanU/m_fUVAspect, fTanV )/ fTanFov; // 0 at centre line of frustum, 1 at edges
	fFarness = Asura_Maths::Max( fFarness, (fDist/m_fMaxRange)*2.0f-1.0f ); // 0.5 in centre of range, 1 at limit of range, don't fade towards near plane.
	
//	Asura_DebugTextPrint(( 15,  8, 0xffff4000, 0, "xRelPos: %.1f %.1f %.1f", xRelPos.GetX(), xRelPos.GetY(), xRelPos.GetZ() ));
//	Asura_DebugTextPrint(( 15,  9, 0xffff4000, 0, "fFarness: %.2f", fFarness ));
	
	return 1.0f-fFarness;
}



void Asura_Projector::SetSpotlightAngularFalloff( float fInnerAngle, float fOuterAngle)
{
	if (fInnerAngle <= 0.0f)
	{
		fInnerAngle = 0.01f;
	}

	if (fOuterAngle <= 0.0f)
	{
		fOuterAngle = 0.01f;
	}

	float fInner = Asura_Maths::Cosine(fInnerAngle*0.5f);
	float fOuter = Asura_Maths::Cosine(fOuterAngle*0.5f);

	float fRange = fInner-fOuter;

	if (fRange <= 0.0f)
	{
		fRange = 0.01f;
	}

	m_fAngularFalloffScale = 1.0f / fRange;
	m_fAngularFalloffOfs = -fOuter;
}


#endif //#ifdef ASURA_TARGET_SM3
