///////////////////////////////////////////////////////////////////////////////
/// @file dePortal.cpp
///
/// @brief Portal culling routines
///
/// @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 2002
/// @author Assassin
/// @remarks Creation
///
///////////////////////////////////////////////////////////////////////////////

#include "dePortal_priv.hpp"
#include "deScene.hpp"
#include "deDriver.hpp"
#include "deCamera.hpp"

#pragma todo(T-portal [de]activation)

static IdeVertexBuffer * s_PortalVBuffer = NULL;
static int s_NumPortals = 0;

IdeVertexBuffer * GetStaticVBuffer()
{
	if (!s_PortalVBuffer)
	{
		s_PortalVBuffer = IdeVertexBuffer_CreateVBuffer();
		s_PortalVBuffer->Lock();
		s_PortalVBuffer->CreateBuffer(IdeVertexBuffer::BUFFER_POSITION, deFALSE);
		s_PortalVBuffer->SetRenderType(IdeVertexBuffer::RENDER_POINTLIST);
		s_PortalVBuffer->Unlock();
#pragma todo(can probably add texture buffers for partly transparent textured portals)
	}
	return s_PortalVBuffer;
}

IdeVertexBuffer * MakePortalBuffer()
{
	IdeVertexBuffer * VBuffer;
	VBuffer = IdeVertexBuffer_CreateSubVBuffer(GetStaticVBuffer());
	if (!VBuffer)
		DEBUGRETURN(NULL, "MakePortalBuffer: could not create VBuffer");
	if (!VBuffer->Lock())
	{
		VBuffer->Release();
		return NULL;
	}
	VBuffer->SetRenderType(IdeVertexBuffer::RENDER_TRIANGLEFAN);
	VBuffer->Unlock();
	return VBuffer;
}

IdePortal * IdePortal_CreatePortal()
{
	return new dePortal;
}

IdePortal * IdePortal_CreatePortalWithVerts(deVec3d Vertices[], int Num)
{
	return new dePortal(Vertices, Num);
}

dePortal::dePortal()
: m_Vertices(0), m_Normal(deZeroVec)
{
	m_LinkWorldID = 0;
	m_LinkObjectID = 0;
	m_VBuffer = NULL;
	m_VertsChanged = deTRUE;
	m_DoesTransformation = deFALSE;
	m_IsPlanar = deFALSE;
	s_NumPortals++;

	m_VBuffer = MakePortalBuffer();
}

dePortal::dePortal(deVec3d Vertices[], int Num)
: m_Vertices(Num), m_Normal(deZeroVec)
{
	m_LinkWorldID = 0;
	m_LinkObjectID = 0;
	m_VBuffer = NULL;
	m_VertsChanged = deTRUE;
	m_DoesTransformation = deFALSE;
	s_NumPortals++;

	m_VBuffer = MakePortalBuffer();

	SetVertices(Vertices, Num);
}

dePortal::~dePortal()
{
	if (m_VBuffer)
		m_VBuffer->Release();

	s_NumPortals--;
	if (s_NumPortals <= 0)
	{
		s_PortalVBuffer->Release();
		s_PortalVBuffer = NULL;
	}
}

int dePortal::Release()
{
	delete this;
	return 0;
}

void dePortal::SetVertices(deVec3d Vertices[], long Num)
{
	int i;
#if 0
	deVec3d Center = ZeroVec;

	for (i = 0; i < Num; i++)
		Center += Vertices[i];
	Center /= (deDouble)Num;

	for (i = 0; i < Num; i++)
		m_Vertices[i] = Vertices[i] - Center;
	m_Position.Reset();
	m_Position.Translation = Center;
#else
	for (i = 0; i < Num; i++)
		m_Vertices[i] = Vertices[i];
#endif

	m_VertsChanged = deTRUE;
}

deVec3d* dePortal::GetVertices(long &Num) const
{
	Num = m_Vertices.Length();
	return m_Vertices.GetCArray();
}

deWorldID dePortal::GetLinkWorldID() const
{
	return m_LinkWorldID;
}

deObjectID dePortal::GetLinkObjectID() const
{
	return m_LinkObjectID;
}

void dePortal::SetLinkWorldID(deWorldID WorldID)
{
	m_LinkWorldID = WorldID;
}

void dePortal::SetLinkObjectID(deObjectID ObjectID)
{
	m_LinkObjectID = ObjectID;
}

deBoolean dePortal::LinkTo(IdeSceneRoom* SceneRoom)
{
	if (!SceneRoom)
		DEBUGRETURN(deFALSE, "LinkTo: SceneRoom is NULL");

	deWorldID	WorldID;
	deObjectID	ObjectID;

	WorldID = SceneRoom->GetWorldID();
	ObjectID = SceneRoom->GetObjectID();

	if (WorldID == 0 || ObjectID == 0)
		DEBUGRETURN(deFALSE, "LinkTo: SceneRoom has invalid ID");

	m_LinkWorldID = WorldID;
	m_LinkObjectID = ObjectID;
	return deTRUE;
}

void dePortal::SetNormal(const deVec3d & v)
{
	m_Normal = v;
}

void dePortal::GetNormal(deVec3d & v) const
{
	v = m_Normal;
}

void dePortal::SetPosition(const deTransformInfo & M)
{
	m_Position = M;
}

void dePortal::GetPosition(deTransformInfo & M) const
{
	M = m_Position;
}

void dePortal::SetTransform(const deTransformInfo & M)
{
	m_DoesTransformation = deTRUE;
	m_Transform = M;
}

void dePortal::GetTransform(deTransformInfo & M) const
{
	M = m_Transform;
}

void dePortal::ConcatTransform(deTransformInfo & M) 
{
	M = m_Transform.Multiply(M);
}

deBoolean dePortal::IsTPortal() const
{
	return m_DoesTransformation;
}

void dePortal::SetTPortal(deBoolean TPortal)
{
	m_DoesTransformation = TPortal;
}

deBoolean dePortal::IsPlanar()
{
#pragma todo (test me)
	if (m_Vertices.Length() < 3)
		return deFALSE;

	if (!m_VertsChanged)
		return m_IsPlanar;
		
	deVec3d Normal;
	deVec3d Vec0, Vec1, Vec2;
	size_t i;

	Vec0 = m_Vertices[0];
	Vec1 = m_Vertices[1];
	Vec2 = m_Vertices[2];
	Vec1 -= Vec0;
	Vec2 -= Vec0;
	Vec3d_Cross(Vec1, Vec2, Normal);
	Vec3d_Normalize(Normal);

	m_IsPlanar = deTRUE;

	for (i = 3; i < m_Vertices.Length(); ++i)
	{
		Vec1 = m_Vertices[i];
		Vec1 -= Vec0;
		if (!Compare_Epsilon(Vec3d_Dot(Vec1, Normal), 0))
		{
			m_IsPlanar = deFALSE;
		}
	}

	return m_IsPlanar;
}

IdeVertexBuffer * dePortal::GetVBuffer()
{
	if (m_VertsChanged)
	{
		IsPlanar();
		int i, length;
		deVertex * PositionBuffer;
		length = m_Vertices.Length();
		m_VBuffer->Lock();
		m_VBuffer->SetBufferSize(length, deFALSE);
		PositionBuffer = (deVertex *)(m_VBuffer->GetBuffer(IdeVertexBuffer::BUFFER_POSITION));
		for (i = 0; i < length; i++)
			PositionBuffer[i] = deMake_Vertex(m_Vertices[i]);
		m_VBuffer->DirtyBuffer(IdeVertexBuffer::BUFFER_POSITION);
		m_VBuffer->Unlock();

		m_VertsChanged = deFALSE;
	}

	return m_VBuffer;
}
/*
void dePortal::ClipFrustum(deTransformInfo * CamPos, const deFrustum &in, deFrustum &out)
{
	int i,numverts = m_Vertices.Length();
	deTransform M(*CamPos);
	deVec3d * verts = new deVec3d[numverts];
	deVec3d *pVerts = verts;
	deDouble MinTop;
	deDouble MaxBottom;
	deDouble MinLeft;
	deDouble MaxRight;
	deDouble dbl;

	M = M.GetTranspose();
	M.TransformArray(m_Vertices.GetCArray(), verts, numverts);

	MinTop = 10000;
	MaxBottom = -10000;
	MaxRight = -10000;
	MinLeft = 10000;

	for (i = 0; i < numverts; i++, pVerts++)
	{
		if (pVerts->z == 0)
			continue;
		if (pVerts->z < 0)
			pVerts->z *= -0.0001; // if the thing is behind us, put it in front of us but WAY off screen

		if (!in.Ortho)
			dbl = pVerts->x/pVerts->z;
		else
			dbl = pVerts->x;
		if (dbl > MaxRight)
			MaxRight = dbl;
		if (dbl < MinLeft)
			MinLeft = dbl;

		if (!in.Ortho)
			dbl = pVerts->y/pVerts->z;
		else
			dbl = pVerts->y;
		if (dbl < MinTop)
			MinTop = dbl;
		if (dbl > MaxBottom)
			MaxBottom = dbl;
	}
	delete[] verts;
	pVerts = verts = NULL;

	out.Ortho = in.Ortho;
	out.TopTan = max(MinTop, in.TopTan);
	out.BottomTan = min(MaxBottom, in.BottomTan);
	out.LeftTan = max(MinLeft, in.LeftTan);
	out.RightTan = min(MaxRight, in.RightTan);
	out.MaxDist = in.MaxDist;
}

deBoolean dePortal::IntersectsFrustum(deTransformInfo * CamPos, const deFrustum & f)
{
	int i, numverts = m_Vertices.Length();
	if (numverts <= 0)
		return deFALSE;
	
	deTransform M;
	deVec3d * verts = new deVec3d[numverts];
	deVec3d *pVerts;
	deDouble dbl;
	bool BehindPlane;
	bool FoundInFront = false;
	deDouble LeftTan, RightTan, TopTan, BottomTan;
	deBoolean FoundInX, FoundInY;
	deBoolean FoundLessX, FoundLessY, FoundMoreX, FoundMoreY;

	if (m_DoesTransformation)
	{
		verts[0] = m_Vertices[0];
		verts[0] -= CamPos->Translation;
		if (Vec3d_Dot(verts[0], m_Normal) > 0)
			goto ret_false;
	}

	LeftTan = TopTan = -100000.0;
	RightTan = BottomTan = 100000.0;
	FoundLessX = FoundLessY = FoundMoreX = FoundMoreY = DE_FALSE;
	FoundInX = FoundInY = DE_FALSE;

	((deTransform*)CamPos)->GetTranspose(M);
	M.TransformArray(m_Vertices.GetCArray(), verts, numverts);
	pVerts = verts;
	for (i = 0; i < numverts; i++, pVerts++)
	{
		if (f.MaxDist > 0 && pVerts->z > f.MaxDist)
			continue;

		if (pVerts->z <= 0)
		{
			BehindPlane =  true;
			continue;
		}
		else
		{
			BehindPlane =  false;
			FoundInFront = true;
		}

		if (!FoundInX)
		{
			if (!f.Ortho)
				dbl = pVerts->x/pVerts->z;
			else
				dbl = pVerts->x;
			if (dbl > f.LeftTan)
			{
				if (dbl < f.RightTan)
				{
					FoundInX = DE_TRUE;
				}
				else
				{
					FoundMoreX = DE_TRUE;
					RightTan = dbl;
				}
			}
			else
			{
				FoundLessX = DE_TRUE;
				LeftTan = dbl;
			}
		}

		if (!FoundInY)
		{
			if (!f.Ortho)
				dbl = pVerts->y/pVerts->z;
			else
				dbl = pVerts->y;
			if (dbl > f.TopTan)
			{
				if (dbl < f.BottomTan)
				{
					FoundInY = DE_TRUE;
				}
				else
				{
					FoundMoreY = DE_TRUE;
					BottomTan = dbl;
				}
			}
			else
			{
				FoundLessY = DE_TRUE;
				TopTan = dbl;
			}
		}

		if ((FoundInX) || (FoundLessX && FoundMoreX))
		{
			if ((FoundInY) || (FoundLessY && FoundMoreY))
				goto ret_true;
		}
	}

// this doesn't always work perfectly for orthographic frustums

	if (FoundInFront)
	{
		pVerts = verts;
		for (i = 0; i < numverts; i++, pVerts++)
		{
			if (f.MaxDist > 0 && pVerts->z > f.MaxDist)
				continue;

			if (pVerts->z <= 0)
			{
				BehindPlane =  true;
				pVerts->z *= -1;
			}
			else
			{
				continue;
			}

			if (!FoundInX)
			{
				if (!f.Ortho)
					dbl = pVerts->x/pVerts->z;
				else
					dbl = pVerts->x;
				if (dbl > f.LeftTan)
				{
					if (dbl < f.RightTan)
					{
						//FoundInX = DE_TRUE;
					}
					else
					{
						if (dbl > -LeftTan)
							FoundMoreX = DE_TRUE;
					}
				}
				else
				{
					if (dbl < -RightTan)
						FoundLessX = DE_TRUE;
				}
			}

			if (!FoundInY)
			{
				if (!f.Ortho)
					dbl = pVerts->y/pVerts->z;
				else
					dbl = pVerts->y;
				if (dbl > f.TopTan)
				{
					if (dbl < f.BottomTan)
					{
						//FoundInY = DE_TRUE;
					}
					else
					{
						if (dbl > -TopTan)
							FoundMoreY = DE_TRUE;
					}
				}
				else
				{
					if (dbl < -BottomTan)
						FoundLessY = DE_TRUE;
				}
			}

			if ((FoundInX) || (FoundLessX && FoundMoreX))
			{
				if ((FoundInY) || (FoundLessY && FoundMoreY))
					goto ret_true;
			}
		}
	}

ret_false:
	delete[] verts;
	return DE_FALSE;

ret_true:
	delete[] verts;
	return DE_TRUE;
}
*/

deBoolean dePortal::ClipFrustum(const deTransformInfo & CamPos, const IdePlaneFrustum* in, IdePlaneFrustum* out)
{
	if (!in)
		DEBUGRETURN(deFALSE, "BuildRootFrustum: Parameter 'in' was NULL");
	if (!out)
		DEBUGRETURN(deFALSE, "BuildRootFrustum: Parameter 'out' was NULL");

#pragma todo(unfinished function: ClipFrustum)
	out->CopyFromFrustum(in);

	if (!in->IsOrtho())
	{
		if (IsPlanar())
		{
			static const deVec3d ZVec = {0,0,1};
			deTransform* cam = (deTransform*)(&CamPos);
			dePlane plane;
			deVec3d vec1, vec2;
			size_t i, k;

			for (i = 0; i < m_Vertices.Length(); ++i)
			{
#pragma todo(convert this back into world space computations)
				k = (i+1) % m_Vertices.Length();
				m_Position.Transform(m_Vertices[i], vec1);
				cam->TransposeTransform(vec1, vec1);
				m_Position.Transform(m_Vertices[k], vec2);
				cam->TransposeTransform(vec2, vec2);
				vec1 -= vec2;
				Vec3d_Cross(vec1, vec2, plane.Norm);
				Vec3d_Normalize(plane.Norm);
				Vec3d_Squeeze(plane.Norm);
				plane.Dist = 0;
				out->AddPlane(plane);
			}
		}
	}

	// trace edges of portal from 'CamPos'

	// for each edge
	//   create a plane relative to 'CamPos', bounding the edge
	//   put that plane into 'out'

	// optimize 'out'
	out->Optimize();

	return deTRUE;
}

deBoolean dePortal::IntersectsFrustum(const deTransformInfo & CamPos, const IdePlaneFrustum* f) const
{
#pragma todo(test me)
	deTArray <dePlane>	Planes;
	deVec3d Point;
	size_t i,k, numplanes;
	deBoolean FoundInside, FoundInFront;
	deBoolean FoundLessX, FoundLessY, FoundMoreX, FoundMoreY;

	DE_ASSERT(((deTransform&)CamPos).IsOrthoNormal());

	FoundInFront = deFALSE;
	FoundInside = deFALSE;
	FoundLessX = deFALSE;
	FoundLessY = deFALSE;
	FoundMoreX = deFALSE;
	FoundMoreY = deFALSE;
	FoundInside = deFALSE;

	// copy planes
	numplanes = f->GetNumPlanes();
	Planes.Resize(numplanes);
	for (i = 0; i < numplanes; ++i)
		f->GetPlane(i, Planes[i]);
	// for each portal point
	for (k = 0; !FoundInside && k < m_Vertices.Length(); ++k)
	{
		m_Position.Transform(m_Vertices[k], Point);
		((deTransform&)CamPos).TransposeTransform(Point, Point);
		if (Point.z > 0)
			FoundInFront = deTRUE;
		FoundInside = deTRUE;
		// for each frustum plane
		for (i = 0; i < numplanes; ++i)
		{
			// if point found outside the frustum, immediatly fail
			if (deGeometry::PointOnPlaneBack(Point, Planes[i].Norm, Planes[i].Dist))
			{
				if (Point.x < 0)
					FoundLessX = deTRUE;
				else
					FoundMoreX = deTRUE;

				if (Point.y < 0)
					FoundLessY = deTRUE;
				else
					FoundMoreY = deTRUE;

				FoundInside = deFALSE;

				break;
			}
			// if point is behind
		}
	}

	if (!FoundInFront)
		return deFALSE;
	if (FoundInside)
		return deTRUE;
	if (FoundLessX && FoundMoreX && FoundLessY && FoundMoreY)
		return deTRUE;

	return deFALSE;
}

