#include "munga.h"
#pragma hdrstop

#include "boxsolid.h"
#include "origin.h"
#include "linmtrx.h"
#include "line.h"

//#############################################################################
//##############################    BoxedSphere    ############################
//#############################################################################

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BoxedSphere::BoxedSphere(
	const ExtentBox &extents,
	BoxedSolid::Material material,
	Simulation *owner,
	BoxedSolid *next_solid
):
	BoxedSolid(extents, SphereType, material, owner, next_solid)
{
	Check_Pointer(this);
}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoxedSphere::IntersectsBounded(const ExtentBox &extents)
{
	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);

	//
	//----------------------------------------------
	// Find the centerpoint and radius of the sphere
	//----------------------------------------------
	//	
	Point3D center;
	center.x = (minX + maxX) * 0.5f;
	center.y = (minY + maxY) * 0.5f;
	center.z = (minZ + maxZ) * 0.5f;
	Scalar radius = maxX - center.x;
	
	//
	//--------------------------------------------------------------------------
	// Constrain the centerpoint of the sphere to be within the bounded extents.
	// Then see if the constrained point is within the radius of the sphere.  If
	// so, it is an intersection
	//--------------------------------------------------------------------------
	//
	Point3D closest = center;
	extents.Constrain(&closest);
	closest -= center;
	return radius*radius >= closest.LengthSquared();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoxedSphere::ContainsBounded(const Point3D &point)
{
	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);

	//
	//----------------------------------------------
	// Find the centerpoint and radius of the sphere
	//----------------------------------------------
	//	
	Point3D center;
	center.x = (minX + maxX) * 0.5f;
	center.y = (minY + maxY) * 0.5f;
	center.z = (minZ + maxZ) * 0.5f;
	Scalar radius = maxX - center.x;
	
	//
	//-------------------------------------------------------------------------
	// translate the test point into the sphere's frame of reference and see if
	// it is close enough
	//-------------------------------------------------------------------------
	//
	center -= point;
	return radius*radius >= center.LengthSquared();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Scalar
	BoxedSphere::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);

	//
	//----------------------------------------------
	// Find the centerpoint and radius of the sphere
	//----------------------------------------------
	//	
	Point3D center;
	center.x = (minX + maxX) * 0.5f;
	center.y = (minY + maxY) * 0.5f;
	center.z = (minZ + maxZ) * 0.5f;
	Scalar radius = maxX - center.x;

	//
	//-----------------------------------------------------------------------
	// Convert the point to the coordinates of the sphere, putting the center
	// point of the sphere at the origin.  Note that we are subtracting the
	// point from the center point as opposed to the normal way.  This will
	// result in both X and Y being negated, but since we are squaring them,
	// this will not matter
	//-----------------------------------------------------------------------
	//
	Scalar x = center.x - point.x;
	Scalar z = center.z - point.z;
	Scalar h = radius*radius - x*x - z*z;
	if (h < SMALL)
	{
		return -1.0f;
	}

	//
	//-------------------------------------------------------------------
	// If the point is in the XZ shadow of the sphere, then the height is
	// determined by the height of the sphere at that point
	//-------------------------------------------------------------------
	//	
	Scalar height = point.y - (center.y + Sqrt(h));
	return Abs(height);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoxedSphere::HitByBounded(
		Line *line,
		Scalar enters,
		Scalar leaves
	)
{
	Check(this);
	Check(line);

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

	//
	//----------------------------------------------
	// Find the centerpoint and radius of the sphere
	//----------------------------------------------
	//	
	Point3D center;
	center.x = (minX + maxX) * 0.5f;
	center.y = (minY + maxY) * 0.5f;
	center.z = (minZ + maxZ) * 0.5f;
	Scalar radius = maxX - center.x;

	//
	//-------------------------------------------------------------------------
	// Find the point of closest approach to the center of the sphere, and make
	// sure that it is within the boundaries of the sphere
	//-------------------------------------------------------------------------
	//
	Scalar midlen = line->LengthToClosestPointTo(center);
	Point3D closest;
	line->Project(midlen, &closest);
	closest -= center;
	Scalar v = radius*radius - closest.LengthSquared();
	if (v < 0.0f)
	{
		return False;
	}

	//
	//---------------------------------------------------------------------
	// Find the closest possible length of ray traversal before hitting the
	// sphere
	//---------------------------------------------------------------------
	// 
	v = Sqrt(v);
	Scalar enter = midlen - v;
	if (enter > enters)
	{
		enters = enter;
	}
	Scalar leave = midlen + v;
	if (leave < leaves)
	{
		leaves = leave;
	}

	if (enters > leaves || enters > line->length || leaves < 0.0f)
	{
		return False;
	}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoxedSphere::TestInstance() const
{
	return solidType == SphereType;
}
