#include "Frustum.h"
#include "BoundingBox.h"
#include "Line.h"

namespace cbk
{
	Frustum::Frustum(const Plane& Left, const Plane& Right, const Plane& Top, const Plane& Bottom, const Plane& Front, const Plane& Back)
	{
		left = Left;
		right = Right;
		top = Top;
		bottom = Bottom;
		front = Front;
		back = Back;
	}

	Frustum::Frustum(const D3DXMATRIX& view, const D3DXMATRIX& proj, const D3DVIEWPORT9& viewPort)
	{
		const D3DXMATRIX viewProj = view * proj;
		//http://www2.ravensoft.com/users/ggribb/plane%20extraction.pdf
		left.CreatePlaneFromABCD(viewProj._14 + viewProj._11, viewProj._24 + viewProj._21, viewProj._34 + viewProj._31, viewProj._44 + viewProj._41);
		right.CreatePlaneFromABCD(viewProj._14 - viewProj._11, viewProj._24 - viewProj._21, viewProj._34 - viewProj._31, viewProj._44 - viewProj._41);

		bottom.CreatePlaneFromABCD(viewProj._14 + viewProj._12, viewProj._24 + viewProj._22, viewProj._34 + viewProj._32, viewProj._44 + viewProj._42);
		top.CreatePlaneFromABCD(viewProj._14 - viewProj._12, viewProj._24 - viewProj._22, viewProj._34 - viewProj._32, viewProj._44 - viewProj._42);

		front.CreatePlaneFromABCD(viewProj._13, viewProj._23, viewProj._33, viewProj._43);
		back.CreatePlaneFromABCD(viewProj._14 - viewProj._13, viewProj._24 - viewProj._23, viewProj._34 - viewProj._33, viewProj._44 - viewProj._43);

		D3DXMATRIX world;
		D3DXMatrixIdentity(&world);

		D3DXVECTOR3 po(float(viewPort.X), float(viewPort.Y), float(viewPort.MinZ));
		D3DXVec3Unproject(&po, &po, &viewPort, &proj, &view, &world);
		p[0] = po;

		po = D3DXVECTOR3(float(viewPort.Width), float(viewPort.Y), float(viewPort.MinZ));
		D3DXVec3Unproject(&po, &po, &viewPort, &proj, &view, &world);
		p[1] = po;

		po = D3DXVECTOR3(float(viewPort.X), float(viewPort.Y), float(viewPort.MinZ));
		D3DXVec3Unproject(&po, &po, &viewPort, &proj, &view, &world);
		p[2] = po;

		po = D3DXVECTOR3(float(viewPort.Width), float(viewPort.Y), float(viewPort.MinZ));
		D3DXVec3Unproject(&po, &po, &viewPort, &proj, &view, &world);
		p[3] = po;

		po = D3DXVECTOR3(float(viewPort.X), float(viewPort.Height), float(viewPort.MaxZ));
		D3DXVec3Unproject(&po, &po, &viewPort, &proj, &view, &world);
		p[4] = po;

		po = D3DXVECTOR3(float(viewPort.Width), float(viewPort.Height), float(viewPort.MaxZ));
		D3DXVec3Unproject(&po, &po, &viewPort, &proj, &view, &world);
		p[5] = po;

		po = D3DXVECTOR3(float(viewPort.X), float(viewPort.Height), float(viewPort.MaxZ));
		D3DXVec3Unproject(&po, &po, &viewPort, &proj, &view, &world);
		p[6] = po;

		po = D3DXVECTOR3(float(viewPort.Width), float(viewPort.Height), float(viewPort.MaxZ));
		D3DXVec3Unproject(&po, &po, &viewPort, &proj, &view, &world);
		p[7] = po;
	}

	const bool Frustum::isInside(const BoundingBox& b) const
	{
		return (isInside(b.getLesser()) || isInside(b.getGreater()));
	}

	const bool Frustum::isInside(const BoundingBox& b, const Vector& location, const float& scale) const
	{
		const BoundingBox& bb = (b * scale) + location;
		const Vector& bMax = bb.getGreater();
		const Vector& bMin = bb.getLesser();

		Vector t;
		register int x;

		for (x = 0; x < 8; x++)
		{
			if (x & 1)
				t.X = bMin.X;
			else
				t.X = bMax.X;

			if (x & 2)
				t.Y = bMin.Y;
			else
				t.Y = bMax.Y;

			if (x & 4)
				t.Z = bMin.Z;
			else
				t.Z = bMax.Z;

			if (isInside(t))
				return true;
		}

		return false;
	}

	const bool Frustum::isInside(const Vector& p) const
	{
		const bool le = (left.pointInFront(p) >= Plane::DIR_Equal);
		const bool ri = (right.pointInFront(p) >= Plane::DIR_Equal);
		const bool to = (top.pointInFront(p) >= Plane::DIR_Equal);
		const bool bo = (bottom.pointInFront(p) >= Plane::DIR_Equal);
		const bool fr = (front.pointInFront(p) >= Plane::DIR_Equal);
		const bool ba = (back.pointInFront(p) >= Plane::DIR_Equal);
		return (le && ri && to && bo && fr && ba);
	}

	const Frustum::Direction Frustum::isInside(const Plane& pl) const
	{
		register unsigned int x;
		Vector Min(p[0]), Max(p[0]);

		for (x = 1; x < 8; x++)
		{
			Min = Vector(min(p[x].X, Min.X), min(p[x].Y, Min.Y), min(p[x].Z, Min.Z));
			Max = Vector(max(p[x].X, Max.X), max(p[x].Y, Max.Y), max(p[x].Z, Max.Z));
		}

		const BoundingBox t(Min, Max);
		
		const Plane::Direction i = pl.boundingBoxInFront(t);
		if (i == Plane::DIR_Equal)
			return DIR_Inside;
		else if (i == Plane::DIR_Behind)
			return DIR_Behind;
		else
			return DIR_Front;
	}
/*
	const Frustum::Direction Frustum::isInside(const Plane& pl) const
	{
		register unsigned int x, y;

		for (x = 0; x < 8; x++)
			for (y = x; y < 8; y++)
			{
				const Line x(p[x], p[y]);

				const Plane::Direction i = pl.Intersection(x);
				if (i == Plane::DIR_Equal)
					return DIR_Inside;
			}
		
		Plane::Direction t = pl.pointInFront(p[0]);
		if (t == Plane::DIR_Behind)
			return DIR_Behind;
		else if (t == Plane::DIR_Front)
			return DIR_Front;
		else
			return DIR_Inside;
	}
*/
/*
	const Frustum::Direction Frustum::isInside(const Plane& p) const
	{
		const Vector pLe = left.getPoint();
		const Vector pRi = right.getPoint();
		const Vector pTo = top.getPoint();
		const Vector pBo = bottom.getPoint();

		const Line x(pLe, pRi);
		const Line y(pTo, pBo);

		const Plane::Direction ix = p.Intersection(x);
		const Plane::Direction iy = p.Intersection(y);

		bool two = true;

		if ((ix == Plane::DIR_Equal) ^ (iy == Plane::DIR_Equal))
		{
			//If one of the first two worked, check the third
			const Vector pFr = front.getPoint();
			const Vector pBa = back.getPoint();
			const Line z(pFr, pBa);

			const Plane::Direction iz = p.Intersection(z);

			if (iz != Plane::DIR_Equal)
				two = false;	//If only one of the three worked, fail
		}
		else if ((ix != Plane::DIR_Equal) && (iy != Plane::DIR_Equal))
			two = false; //If neither of the first two worked, fail

		if (two) //Two is true if at least two of the three tests passed
			return DIR_Inside;
		else
		{
			Plane::Direction t = p.pointInFront(pLe);
			if (t == Plane::DIR_Behind)
				return DIR_Behind;
			else if (t == Plane::DIR_Front)
				return DIR_Front;
			else
				return DIR_Inside;
		}
	}
*/
}
