#include "munga.h"
#pragma hdrstop

#include "bndgbox.h"
#include "origin.h"
#include "line.h"

//#############################################################################
//##############################    BoundingBox    ############################
//#############################################################################
	
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BoundingBox::BoundingBox(const ExtentBox &extents):
	ExtentBox(extents)
{
	Check_Pointer(this);
	Check(&extents);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BoundingBox::BoundingBox()
{
	Check_Pointer(this);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void
	BoundingBox::PlaceBoundingBox(const Origin& origin)
{
	Check(this);
	Check(&origin);

	Dump(origin);

	minX += origin.linearPosition.x;
	maxX += origin.linearPosition.x;
	minY += origin.linearPosition.y;
	maxY += origin.linearPosition.y;
	minZ += origin.linearPosition.z;
	maxZ += origin.linearPosition.z;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BoundingBox::~BoundingBox()
{
	Check_Pointer(this);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoundingBox::Intersects(const ExtentBox &extents)
{
	Check(this);
	Check(&extents);

	if (ExtentBox::Intersects(extents))
	{
		ExtentBox slice;
		slice.Intersect(*this, extents);
		return IntersectsBounded(slice);
	}
	else
	{
		return False;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoundingBox::Intersects(
		const ExtentBox &extents,
		ExtentBox *slice
	)
{
	Check(this);
	Check(&extents);
	Check_Pointer(slice);

	if (ExtentBox::Intersects(extents))
	{
		slice->Intersect(*this, extents);
		return IntersectsBounded(*slice);
	}
	else
	{
		return False;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoundingBox::Contains(const Point3D &point)
{
	Check(this);
	Check(&point);

	if (ExtentBox::Contains(point))
	{
		return ContainsBounded(point);
	}
	else
	{
		return False;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Scalar
	BoundingBox::FindDistanceBelow(const Point3D &point)
{
	Check(this);
	Check(&point);

	if (
		minX <= point.x && maxX >= point.x && minY <= point.y
		 && minZ <= point.z && maxZ >= point.z
	)
	{
		return FindDistanceBelowBounded(point);
	}
	else
	{
		return -1.0f;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoundingBox::HitBy(Line *line)
{
	Check(this);
	Check(line);

	Scalar
		enter,
		leave,
		perpendicular,
		drift,
		distance;

	//
	//--------------------
	// Set up for the test
	//--------------------
	//
	Logical entered = False;
	Logical left = False;
	for (int i=0; i<6; ++i)
	{
		//
		//--------------------------------------------------------------------
		// Figure out what axis we are dealing with, then based upon the
		// direction of the face, find out the distance from the origin to the
		// place against the normal, and find out how fast the perpendicular
		// distance changes with a unit movement along the line
		//--------------------------------------------------------------------
		//
		int face = i;
		int axis = face >> 1;
		if (face&1)
		{
			perpendicular = line->origin[axis] - (*this)[face];
			drift = line->direction[axis];
		}
		else
		{
			perpendicular = (*this)[face] - line->origin[axis];
			drift = -line->direction[axis];
		}

		//
		//-------------------------------------------------------------------
		// If the line is parallel to the face, figure out whether or not the
		// line origin lies within the face's half-space
		//-------------------------------------------------------------------
		//
		if (Small_Enough(drift))
		{
			if (perpendicular > 0.0f)
			{
				return False;
			}
			else
			{
				continue;
			}
		}

		//
		//--------------------------------------------------------------------
		// If the drift is towards the plane's halfspace, this plane is one of
		// the one through which the line could enter the node
		//--------------------------------------------------------------------
		//
		distance = -perpendicular / drift;
		if (drift < 0.0f)
		{
			if (!entered)
			{
				entered = True;
				enter = distance;
			}
			else if (distance > enter)
			{
				enter = distance;
			}
			if (enter > line->length)
			{
				return False;
			}
		}

		//
		//--------------------------------------------------------------------
		// If the drift is towards the plane's halfspace, this plane is one of
		// the one through which the line could enter the node
		//--------------------------------------------------------------------
		//
		else
		{
			if (!left)
			{
				left = True;
				leave = distance;
			}
			else if (distance < leave)
			{
				leave = distance;
			}
			if (leave < 0.0f)
			{
				return False;
			}
		}
	}

	//
	//-----------------------------------------------------------------------
	// If we exit the loop, then make sure that we actually hit the interior,
	// and let the box figure out if the what happens inside it
	//-----------------------------------------------------------------------
	//
	if (enter <= leave)
	{
		return HitByBounded(line, enter, leave);
	}
	return False;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
#if DEBUG_LEVEL>0
	BoundingBox::IntersectsBounded(const ExtentBox &extents)
#else
	BoundingBox::IntersectsBounded(const ExtentBox &)
#endif
{
	Check(this);
	Check(&extents);

	Verify(minX <= extents.minX);
	Verify(maxX >= extents.maxX);
	Verify(minY <= extents.minY);
	Verify(maxY >= extents.maxY);
	Verify(minZ <= extents.minZ);
	Verify(maxZ >= extents.maxZ);

	return True;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
#if DEBUG_LEVEL>0
	BoundingBox::ContainsBounded(const Point3D &point)
#else
	BoundingBox::ContainsBounded(const Point3D &)
#endif
{
	Check(this);
	Check(&point);

	Verify(minX <= point.x);
	Verify(maxX >= point.x);
	Verify(minY <= point.y);
	Verify(maxY >= point.y);
	Verify(minZ <= point.z);
	Verify(maxZ >= point.z);

	return True;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Scalar
	BoundingBox::FindDistanceBelowBounded(const Point3D &point)
{
	Check(this);
	Check(&point);

	Verify(minX <= point.x);
	Verify(maxX >= point.x);
	Verify(minY <= point.y);
	Verify(minZ <= point.z);
	Verify(maxZ >= point.z);

	Scalar result = point.y - maxY;
	return Max(result, 0.0f);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoundingBox::HitByBounded(
		Line *line,
		Scalar enters,
#if DEBUG_LEVEL>0
		Scalar leaves
#else
		Scalar
#endif
	)
{
	Check(this);
	Check(line);

	Verify(enters <= leaves);
	Verify(leaves >= 0.0f);

	line->length = Max(enters, 0.0f);
	return True;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoundingBox::TestInstance() const
{
	return True;
}

//#############################################################################
//#########################    BoundingBoxCollision    ########################
//#############################################################################

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoundingBoxCollision::TestInstance() const
{
	return True;
}

//#############################################################################
//#######################    BoundingBoxCollisionList    ######################
//#############################################################################

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BoundingBoxCollisionList::BoundingBoxCollisionList(int max_length)
{
	listStart		= new BoundingBoxCollision[max_length];
	Register_Pointer(listStart);
	maxCollisions	= max_length;
   Reset();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BoundingBoxCollisionList::AddCollisionToList(
		BoundingBox *tree_volume,
		const ExtentBox &slice
	)
{
	Check(tree_volume);
	Check(&slice);
	Check(this);
	Check_Pointer(nextCollision);
	Verify(collisionsLeft);

	//
	// BT port fix: Verify() compiles out at DEBUG_LEVEL=0, leaving NO live bounds
	// check -- and several callers are add-then-check (MOVER.cpp:1589,
	// BOXLIST.cpp:230), so a list entered with exactly 0 slots free went NEGATIVE,
	// every later "!collisionsLeft" full-test read nonzero-negative as "room
	// left", and the static-tree pass then appended EVERY overlapping terrain/
	// building box past the 10-slot heap block (0x1C bytes per entry).  Trigger:
	// >10 simultaneous contacts from >1 source -- e.g. a mech standing INSIDE a
	// building solid while gravity presses it into terrain boxes.  Clamp at
	// capacity: overflow contacts are dropped, never written.
	//
	if (collisionsLeft <= 0)
	{
		return;
	}

	nextCollision->treeVolume		= tree_volume;
	nextCollision->collisionSlice = slice;
	++nextCollision;
	--collisionsLeft;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoundingBoxCollisionList::TestInstance() const
{
	return True;
}

//#############################################################################
//###########################    TaggedBoundingBox    #########################
//#############################################################################

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
TaggedBoundingBox::TaggedBoundingBox(void *tag)
{
	tagPointer = tag;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
TaggedBoundingBox::TaggedBoundingBox(
	const ExtentBox &extents,
	void *tag
):
	BoundingBox(extents)
{
	tagPointer = tag;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
TaggedBoundingBox::~TaggedBoundingBox()
{
}

#if defined(TEST_CLASS)
	#include "bndgbox.tcp"
#endif
