/* -----------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */


		#include <float.h>
		#include "geometry/bounding_box.h"
		#include "metafile/nml.h"
		#include "math/matrix4.h"

		using namespace GS;
		using namespace GS::NML;


//------------------------------------------------------------------------------
void				OBB::Transform(const Matrix4 &mtx)
{
	Matrix3 rmtx = Matrix3::FromMatrix4(mtx);
	bb_rotation = rmtx * bb_rotation;
	bb_position = bb_position * rmtx + mtx.GetRow(3);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				OBB::ComputeMinMax(MinMax &minmax)
{
	Vector4 xtd(bb_scale * 0.5f);
	Vector4 smt[4];

	smt[0].Set(xtd.x, xtd.y, xtd.z);
	smt[1].Set(-xtd.x, xtd.y, xtd.z);
	smt[2].Set(xtd.x, -xtd.y, xtd.z);
	smt[3].Set(xtd.x, xtd.y, -xtd.z);

	int n;
	for	(n = 0; n < 4; n++)
		smt[n] = (smt[n] * bb_rotation).Abs();

	minmax.mx = smt[0];
	for	(n = 1; n < 4; n++)
	{
		if	(smt[n].x > minmax.mx.x) minmax.mx.x = smt[n].x;
		if	(smt[n].y > minmax.mx.y) minmax.mx.y = smt[n].y;
		if	(smt[n].z > minmax.mx.z) minmax.mx.z = smt[n].z;
	}
	minmax.mn.x = -minmax.mx.x;
	minmax.mn.y = -minmax.mx.y;
	minmax.mn.z = -minmax.mx.z;

	minmax.mn += bb_position;
	minmax.mx += bb_position;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				OBB::FromMetaTag(Tag &tag)
{
	Tag *t;
	List <Tag *> ::Iterator i(tag.GetTags().GetRoot());

	t = i.ObjectPtr();
	if	(!t)	return false;
	bb_position.FromMetaTag(*t);
	++i;

	t = i.ObjectPtr();
	if	(!t)	return false;
	bb_scale.FromMetaTag(*t);
	++i;

	t = i.ObjectPtr();
	if	(!t)	return false;
	bb_rotation.FromMetaTag(*t);
	return true;
}
Tag					*OBB::AsMetaTag()
{
	Tag *root = new Tag("OBB");
	if	(root)
	{
		root->AddChild(bb_position.AsMetaTag("Position"));
		root->AddChild(bb_scale.AsMetaTag("Scale"));
		root->AddChild(bb_rotation.AsMetaTag("Matrix"));
	}
	return root;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				MinMax::IntersectRay(const Vector4 &o, const Vector4 &d, float &tmin, float &tmax)
{
	tmin = 0;
	tmax = FLT_MAX;

	for	(uint n = 0; n < 3; ++n)
		if	(Math::EqualZero(d[n]))
		{
			if	((o[n] < mn[n]) || (o[n] > mx[n]))
				return false;
		}
		else
		{
			float ood = 1.f / d[n];
			float t0 = (mn[n] - o[n]) * ood;
			float t1 = (mx[n] - o[n]) * ood;

			if	(t0 > t1)
			{	float swp = t1; t1 = t0; t0 = swp;	}

			tmin = tmin < t0 ? t0 : tmin;
			tmax = tmax < t1 ? tmax : t1;

			if	(tmin > tmax)
				return false;
		}
	return true;
}
bool				MinMax::ClassifyLine(const Vector4 &p1, const Vector4 &direction, Vector4 &itr, Vector4 *n) const
{
	uint oc1, oc2;

	oc1 = cc_oc(mn, mx, p1);
	if	(oc1 == ClipNone)
	{
		// Point inside bounding box.
		if	(n)
			n->Set(0, 0, 0);
		itr = p1;
		return true;
	}

	oc2 = ss_oc(direction);

	// Same side.
	if	((oc1 & oc2) > ClipNone)
		return false;

	// Check intersections.
	if	(oc1 & (ClipRight | ClipLeft))
	{
		if	(oc1 & ClipRight)
		{
			if	(n)
				n->Set(1, 0, 0);
			itr.x = mx.x;
		}
		else
		{
			if	(n)
				n->Set(-1, 0, 0);
			itr.x = mn.x;
		}
		float	x1 = direction.x;
		float	x2 = itr.x - p1.x;
		itr.y = p1.y + x2 * direction.y / x1;
		itr.z = p1.z + x2 * direction.z / x1;

		if	((itr.y <= mx.y) && (itr.y >= mn.y) && (itr.z <= mx.z) && (itr.z >= mn.z))
			return true;
	}
	if	(oc1 & (ClipTop | ClipBottom))
	{
		if	(oc1 & ClipTop)
		{
			if	(n)
				n->Set(0, 1, 0);
			itr.y = mx.y;
		}
		else
		{
			if	(n)
				n->Set(0, -1, 0);
			itr.y = mn.y;
		}
		float y1 = direction.y;
		float y2 = itr.y - p1.y;
		itr.x = p1.x + y2 * direction.x / y1;
		itr.z = p1.z + y2 * direction.z / y1;

		if	((itr.x <= mx.x) && (itr.x >= mn.x) && (itr.z <= mx.z) && (itr.z >= mn.z))
			return true;
	}
	if	(oc1 & (ClipFront | ClipBack))
	{
		if	(oc1 & ClipBack)
		{
			if	(n)
				n->Set(0, 0, 1);
			itr.z = mx.z;
		}
		else
		{
			if	(n)
				n->Set(0, 0, -1);
			itr.z = mn.z;
		}
		float z1 = direction.z;
		float z2 = itr.z - p1.z;
		itr.x = p1.x + z2 * direction.x / z1;
		itr.y = p1.y + z2 * direction.y / z1;

		if	((itr.x <= mx.x) && (itr.x >= mn.x) && (itr.y <= mx.y) && (itr.y >= mn.y))
			return true;
	}
	return false;
}
bool				MinMax::ClassifySegment(const Vector4 &p1, const Vector4 &p2, Vector4 &itr, Vector4 *n) const
{
	uint oc1, oc2;

	oc1 = cc_oc(mn, mx, p1);
	if	(oc1 == ClipNone)
	{
		// Point inside bounding box.
		if	(n)
			n->Set(0, 0, 0);
		itr = p1;
		return true;
	}

	oc2 = cc_oc(mn, mx, p2);
	if	(oc2 == ClipNone)
	{
		// point inside bounding box
		itr = p2;
		return true;
	}

	// Same side.
	if	((oc1 & oc2) > ClipNone)
		return false;

	// Check intersections.
	if	(oc1 & (ClipRight | ClipLeft))
	{
		if	(oc1 & ClipRight)
		{
			if	(n)
				n->Set(1, 0, 0);
			itr.x = mx.x;
		}
		else
		{
			if	(n)
				n->Set(-1, 0, 0);
			itr.x = mn.x;
		}

		float	x1 = p2.x - p1.x;
		float	x2 = itr.x - p1.x;
		itr.y = p1.y + x2 * (p2.y - p1.y) / x1;
		itr.z = p1.z + x2 * (p2.z - p1.z) / x1;

		if	(	(itr.y <= mx.y)	&&
				(itr.y >= mn.y)	&&
				(itr.z <= mx.z)	&&
				(itr.z >= mn.z)	)
			return true;
	}
	if	(oc1 & (ClipTop | ClipBottom))
	{
		if	(oc1 & ClipTop)
		{
			if	(n)
				n->Set(0, 1, 0);
			itr.y = mx.y;
		}
		else
		{
			if	(n)
				n->Set(0, -1, 0);
			itr.y = mn.y;
		}
		float y1 = p2.y - p1.y;
		float y2 = itr.y - p1.y;
		itr.x = p1.x + y2 * (p2.x - p1.x) / y1;
		itr.z = p1.z + y2 * (p2.z - p1.z) / y1;

		if	(	(itr.x <= mx.x)	&&
				(itr.x >= mn.x)	&&
				(itr.z <= mx.z)	&&
				(itr.z >= mn.z)	)
			return true;
	}
	if	(oc1 & (ClipFront | ClipBack))
	{
		if	(oc1 & ClipBack)
		{
			if	(n)
				n->Set(0, 0, 1);
			itr.z = mx.z;
		}
		else
		{
			if	(n)
				n->Set(0, 0, -1);
			itr.z = mn.z;
		}
		float z1 = p2.z - p1.z;
		float z2 = itr.z - p1.z;
		itr.x = p1.x + z2 * (p2.x - p1.x) / z1;
		itr.y = p1.y + z2 * (p2.y - p1.y) / z1;

		if	(	(itr.x <= mx.x)	&&
				(itr.x >= mn.x)	&&
				(itr.y <= mx.y)	&&
				(itr.y >= mn.y)	)
			return true;
	}
	return false;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				MinMax::FromMetaTag(Tag &tag)
{
	Tag *t;
	List <Tag *> ::Iterator i(tag.GetTags().GetRoot());

	t = i.ObjectPtr();
	if	(!t) return false;
	mn.FromMetaTag(*t);
	++i;

	t = i.ObjectPtr();
	if	(!t) return false;
	mx.FromMetaTag(*t);

	return true;
}
Tag					*MinMax::AsMetaTag()
{
	Tag *root = new Tag("MinMax");
	if	(root)
	{
		root->AddChild(mn.AsMetaTag("Min"));
		root->AddChild(mx.AsMetaTag("Max"));
	}
	return root;
}
//------------------------------------------------------------------------------
