///////////////////////////////////////////////////////////////////////////////
/// @file deFrustum.cpp
///
/// @brief Code for handling arbitrary frustums
///
/// @author Assassin
///
/// This file is the intellectual property of Novus Delta, LLC.. Usage of the
/// contents of this file is subject to the Destiny3D Member License which
/// can be found at http://www.destiny3d.com.  Any other usage is prohibited.
///
/// This file is distributed "AS IS" without warranty of any kind.  Novus
/// Delta, LLC. does not guarantee the fitness of the contents of this file
/// for any particular purpose.
///
/// Copyright (C) 2001-2003 Novus Delta, LLC. All Rights Reserved.
///
/// <hr>
///                                 Change History
/// <hr>
///
/// @date Jan 2003
/// @author Assassin
/// @remarks Creation
///
///////////////////////////////////////////////////////////////////////////////

#include "deCamera_priv.hpp"

DECAMERA_API IdePlaneFrustum * IdeFrustum_CreatePlaneFrustum()
{
	return new dePlaneFrustum;
}

dePlaneFrustum::dePlaneFrustum()
{
	m_Min = 0;
	m_Max = 1000;
	m_Ortho = deFALSE;
	m_InVec = deVecZ;
	m_PosVec = deZeroVec;
}

dePlaneFrustum::~dePlaneFrustum()
{
}

long dePlaneFrustum::Release()
{
	delete this;
	return 0;
}

deBoolean dePlaneFrustum::IsOrtho() const
{
	return m_Ortho;
}

void dePlaneFrustum::SetOrtho(deBoolean Ortho)
{
	m_Ortho = Ortho;
}

u32 dePlaneFrustum::GetNumPlanes() const
{
	return m_Planes.Length();
}

void dePlaneFrustum::SetNumPlanes(u32 NumPlanes)
{
	m_Planes.ResizeQuick(NumPlanes, deTRUE);
}

void dePlaneFrustum::AddPlane(const dePlane & plane)
{
	m_Planes.PushBack(plane);
}

void dePlaneFrustum::GetPlane(u32 index, dePlane & plane) const
{
	plane = m_Planes[index];
}

void dePlaneFrustum::SetPlane(u32 index, const dePlane & plane)
{
	m_Planes[index] = plane;
}

void dePlaneFrustum::SetInVec(const deVec3d & InVec)
{
	m_InVec = InVec;
	Vec3d_Normalize(m_InVec);
}

void dePlaneFrustum::SetPosVec(const deVec3d & PosVec)
{
	m_PosVec = PosVec;
}

void dePlaneFrustum::SetDists(const deDouble & Min, const deDouble & Max)
{
	m_Min = Min;
	m_Max = Max;
}

deDouble dePlaneFrustum::GetMinDist() const
{
	return m_Min;
}

deDouble dePlaneFrustum::GetMaxDist() const
{
	return m_Max;
}

deBoolean dePlaneFrustum::CopyFromFrustum(const IdePlaneFrustum* Ref)
{
	if (Ref == NULL)
		DEBUGRETURN(deFALSE, "CopyFromFrustum: Parameter 'Ref' was NULL");

	dePlaneFrustum* pFrustum;
	pFrustum = (dePlaneFrustum*)(Ref);

	m_Planes = pFrustum->m_Planes; // does an array copy
	m_Min = pFrustum->m_Min;
	m_Max = pFrustum->m_Max;
	m_Ortho = pFrustum->m_Ortho;

	return deTRUE;
}

deBoolean dePlaneFrustum::TestSphereInside(const deBoundSphere& Sphere, const deVec3d& WorldPos, deBoolean & AllInside) const
{
	s32 j;
	deVec3d SpherePos;
	deDouble SphereDist;
	deDouble PlaneDist;
	deVec3d PlaneNorm;

//#pragma todo (test me) // seems to work ok
	AllInside = deTRUE;
	SpherePos = Sphere.Center;
	SpherePos += WorldPos;
	for (j = -2; j < (s32)m_Planes.Length(); ++j)
	{
		if (j >= 0)
		{
			PlaneDist = m_Planes[j].Dist;
			PlaneNorm = m_Planes[j].Norm;
		}
		else
		{
			PlaneNorm = m_InVec;
			if (j == -2)
			{
				PlaneNorm *= m_Min;
				PlaneNorm += m_PosVec;
				PlaneDist = Vec3d_Dot(PlaneNorm, m_InVec);
				PlaneNorm = m_InVec;
			}
			else // j == -1
			{
				PlaneNorm *= m_Max;
				PlaneNorm += m_PosVec;
				PlaneDist = -Vec3d_Dot(PlaneNorm, m_InVec);
				PlaneNorm = -m_InVec;
			}
		}
		SphereDist = Vec3d_Dot(PlaneNorm, SpherePos) - PlaneDist;
		if (fabs(SphereDist) < Sphere.Radius)
		{
			AllInside = deFALSE;
		}
		else if (SphereDist < -Sphere.Radius)
			return deFALSE;
	}
	return deTRUE;
}

deBoolean dePlaneFrustum::TestAABBInside(const deAABB& BBox, const deVec3d& WorldPos, deBoolean & AllInside) const
{
	s32 j;
	int PlaneTest;
	deDouble Dist;
	deVec3d Norm;
	deAABB TempAABB;

	TempAABB = BBox;
	TempAABB.Min += WorldPos;
	TempAABB.Max += WorldPos;
	AllInside = deTRUE;
	for (j = -2; j < (s32)m_Planes.Length(); ++j)
	{
		if (j >= 0)
		{
			Dist = m_Planes[j].Dist;
			Norm = m_Planes[j].Norm;
		}
		else
		{
			Norm = m_InVec;
			if (j == -2)
			{
				Norm *= m_Min;
				Norm += m_PosVec;
				Dist = Vec3d_Dot(Norm, m_InVec);
				Norm = m_InVec;
			}
			else
			{
				Norm *= m_Max;
				Norm += m_PosVec;
				Dist = -Vec3d_Dot(Norm, m_InVec);
				Norm = -m_InVec;
			}
		}
		PlaneTest = deGeometry::AABBPlaneClassify(TempAABB, Norm, Dist);
		if (PlaneTest < 0)
		{
			AllInside = deFALSE;
			return deFALSE;
		}
		if (PlaneTest == 0)
			AllInside = deFALSE;
	}
	return deTRUE;
}

static deBoolean PlaneIntersectionPoint(dePlane &p1, dePlane &p2, dePlane &p3, deVec3d &p)
{
	deVec3d &n1 = p1.Norm;
	deVec3d &n2 = p2.Norm;
	deVec3d &n3 = p3.Norm;
	deVec3d tempvec;
	Vec3d_Cross(n2, n3, tempvec);
	deDouble denom = Vec3d_Dot(n1, tempvec);
	
	if (Compare_Epsilon(denom, 0))
		return deFALSE;
	
	p = (Vec3d_Cross(n3, n2) * (-p1.Dist));
	p -= (Vec3d_Cross(n3, n1) * (-p2.Dist));
	p -= (Vec3d_Cross(n1, n2) * (-p3.Dist));
	p /= denom;
	
	return deTRUE;
}

deBoolean dePlaneFrustum::Optimize()
{
#pragma todo (test me)
	deTArray <deVec3d> IntersectPoints;
	dePlane CrossSection = {{0,0,1}, 1};
	deVec3d point;
	deDouble dbl;
	size_t i,k,n;
	deBoolean DelPlane, UsePoint;

	for (k = 0; k < m_Planes.Length(); ++k)
	{
		for (i = 0; i < m_Planes.Length(); ++i)
		{
			if (i == k)
				continue;
			if (PlaneIntersectionPoint(CrossSection, m_Planes[k], m_Planes[i], point))
			{
				UsePoint = deTRUE;
				for (n = 0; n < m_Planes.Length(); ++n)
				{
					if (n == k || n == i)
						continue;
					if (!deGeometry::PointOnPlaneFront(point, m_Planes[n].Norm, m_Planes[n].Dist))
					{
						UsePoint = deFALSE;
						break;
					}
				}
				if (UsePoint)
				{
					deTArray<deVec3d>::iterator it = IntersectPoints.IncrementSizeQuick();
					*it = point;
				}
			}
		}
	}

	for (k = 0; k < m_Planes.Length(); )
	{
		DelPlane = deTRUE;
		for (i = 0; i < IntersectPoints.Length(); ++i)
		{
			dbl = Vec3d_Dot(m_Planes[k].Norm, IntersectPoints[i]);
			if (Compare_Epsilon(dbl, 0))
			{
				DelPlane = deFALSE;
				break;
			}
		}
		if (DelPlane)
			m_Planes.RemoveElementAt(k);
		else
			++k;
	}

	return deTRUE;
}

deBoolean dePlaneFrustum::CreateFromPolygon(const deVec3d * PointArray, long NumPoints, const deVec3d & Position)
{
#pragma todo (test me)

	if (NumPoints < 3)
		return deFALSE;
	
	deVec3d Delta1, Delta2, Center, Delta3;
	int i, k;

	// test for the position being in the same plane
	// if it is, then we can't project a frustum since it would have no volume
	Delta1 = PointArray[0];
	Delta2 = Delta1;
	Delta1 -= PointArray[1];
	Delta1 -= PointArray[1];
	Vec3d_Cross(Delta1, Delta2, Delta3);
	Delta1 = Position;
	Delta1 -= PointArray[0];
	if (Compare_Epsilon(Vec3d_Dot(Delta1, Delta3), 0))
		return deFALSE;

	m_Planes.ResizeQuick(0, deFALSE);
	m_Planes.ResizeQuick(NumPoints, deFALSE);
	
	Center = PointArray[0];
	// could loop to only 3 instead of NumPoints...
	for (i = 1; i < NumPoints; ++i)
	{
		Center += PointArray[i];
	}
	Center *= 1.0/NumPoints;

	for (i = 0; i < NumPoints; ++i)
	{
		k = (i+1)%NumPoints;

		Delta1 = PointArray[k];
		Delta1 -= Position;
		
		Delta2 = PointArray[k];
		Delta2 -=PointArray[i];

		// delta from a point on plane to average of points
		Delta3 = PointArray[k];
		Delta3 -= Center;
		
		Vec3d_Cross(Delta1, Delta2, m_Planes[i].Norm);
		Vec3d_Normalize(m_Planes[i].Norm);
		// if this normal faces out, the dot of it and the "center delta" will be negative
		// we want inward-facing normals, so reverse any out-facing normals
		if (Vec3d_Dot(m_Planes[i].Norm, Delta3) < 0)
			m_Planes[i].Norm *= -1;
		m_Planes[i].Dist = Vec3d_Dot(m_Planes[i].Norm, Position);
	}

	return deTRUE;
}




deBoolean deCamera::FrustumRect(const IdePlaneFrustum * pFrustum, deRect & SubRect)
{
//#pragma todo(unfinished function: FrustumRect) // finished now
	int Width, Height;
	deDouble Aspect, MidX, MidY;
	deFloatRect FSubRect;
	
	deDouble LeftTan, RightTan, TopTan, BottomTan;
	
	TopTan = 10000;
	BottomTan = -10000;
	RightTan = -10000;
	LeftTan = 10000;
	
	{
		deTArray<dePlane> Planes;
		dePlane CrossSection = {{0,0,1}, 1};
		deVec3d point;
		size_t i,k;

		k = pFrustum->GetNumPlanes();
		Planes.ResizeQuick(k);
		for (i = 0; i < k; ++i)
		{
			pFrustum->GetPlane(i, Planes[i]);
		}
		for (k = 0; k < Planes.Length(); ++k)
		{
			i = (k+1) % Planes.Length();
			if (PlaneIntersectionPoint(CrossSection, Planes[k], Planes[i], point))
			{
				if (point.x > RightTan)
					RightTan = point.x;
				if (point.x < LeftTan)
					LeftTan = point.x;
				
				if (point.y < TopTan)
					TopTan = point.y;
				if (point.y > BottomTan)
					BottomTan = point.y;
			}
		}
	}
	
	MidX = (m_Viewport.Right + m_Viewport.Left) / 2.0f;
	MidY = (m_Viewport.Bottom + m_Viewport.Top) / 2.0f;
	Width = m_Viewport.Right - m_Viewport.Left;
	Height = m_Viewport.Bottom - m_Viewport.Top;
	Aspect = ((deDouble)Width)/Height;
	
	if (pFrustum->IsOrtho())
	{
		return deFALSE;
	}
	else
	{
		deDouble Multiplier = 45.0 / m_FOV;
		
		FSubRect.Left = (deFloat)((LeftTan)*Multiplier);
		FSubRect.Right = (deFloat)((RightTan)*Multiplier);
		FSubRect.Bottom = (deFloat)((BottomTan*Aspect)*Multiplier);
		FSubRect.Top = (deFloat)((TopTan*Aspect)*Multiplier);
	}
	SubRect.Top = (int)(-Height*(FSubRect.Bottom - 1) - m_Viewport.Top - MidY);
	SubRect.Left = (int)(Width*(FSubRect.Left) - m_Viewport.Left + MidX);
	SubRect.Right = (int)(Width*(FSubRect.Right - 1) + m_Viewport.Right + MidX + 1);
	SubRect.Bottom = (int)(-Height*(FSubRect.Top) + m_Viewport.Bottom - MidY + 1);
	
	if (SubRect.Top < 0)
		SubRect.Top = 0;
	if (SubRect.Left < 0)
		SubRect.Left = 0;
	if (SubRect.Right > m_Viewport.Right)
		SubRect.Right = m_Viewport.Right;
	if (SubRect.Bottom > m_Viewport.Bottom)
		SubRect.Bottom = m_Viewport.Bottom;
	
	return deTRUE;
}
