//===========================================================================//
// File:	boxdisks.cc                                                         //
// Project: MUNGA           Brick: Spatializer Library                       //
// Contents: Implementation details of bounding-box collision subtypes       //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
// 01/11/95  JMA  Initial port back to C++                                   //
//---------------------------------------------------------------------------//
// Copyright (C) 1993-1995, Virtual World Entertainment, Inc.                //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//

#include <munga.hpp>
#pragma hdrstop

#if !defined(BOXSOLID_HPP)
#	include <boxsolid.hpp>
#endif

#if !defined(LINE_HPP)
#	include <line.hpp>
#endif

#if !defined(PLANE_HPP)
#	include <plane.hpp>
#endif

//#############################################################################
//##########################    BoxedXAxisCylinder    #########################
//#############################################################################

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

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoxedXAxisCylinder::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 center point in the YZ plane of the cylinder, and find the
	// radius of the cylinder by measuring in the Y axis.  The X value will
	// automatically be within the cylinder if Y & Z are
	//---------------------------------------------------------------------
	//
	Scalar y = (minY + maxY) * 0.5f;
	Scalar z = (minZ + maxZ) * 0.5f;
	Scalar radius = maxY - y;

	//
	//-----------------------------------------------------------------------
	// Convert the point to the coordinates of the cylinder, putting the
	// center point of the cylinder 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 y2 = y;
	Scalar z2 = z;
	Clamp(y2, extents.minY, extents.maxY);
	Clamp(z2, extents.minZ, extents.maxZ);
	y2 -= y;
	z2 -= z;
	return y2*y2 + z2*z2 <= radius*radius;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoxedXAxisCylinder::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 center point in the YZ plane of the cylinder, and find the
	// radius of the cylinder by measuring in the Y axis.  The X value will
	// automatically be within the cylinder if Y & Z are
	//---------------------------------------------------------------------
	//
	Scalar y = (minY + maxY) * 0.5f;
	Scalar z = (minZ + maxZ) * 0.5f;
	Scalar radius = maxY - y;

	//
	//-----------------------------------------------------------------------
	// Convert the point to the coordinates of the cylinder, putting the
	// center point of the cylinder 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
	//-----------------------------------------------------------------------
	//
	y -= point.y;
	z -= point.z;
	return y*y + z*z <= radius*radius;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Scalar
	BoxedXAxisCylinder::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 center point in the XZ plane of the cylinder, and find the
	// radius of the cylinder by measuring in the X axis.  The Y value will
	// automatically be within the cylinder if X & Z are
	//---------------------------------------------------------------------
	//
	Scalar z = (minZ + maxZ) * 0.5f;
	Scalar radius = maxZ - z;

	//
	//--------------------------------------------------------------------------
	// figure out the thickness of cylinder slice where a plane perpendicular to
	// X drops through the cylinder and the test point
	//--------------------------------------------------------------------------
	//
	z = point.z - z;
	Scalar y = radius - Sqrt(radius*radius - z*z);
	if (point.y > maxY - y)
	{
		return point.y - maxY + y;
	}
	else if (point.y < minY + y)
	{
		return -1.0f;
	}
	else
	{
		return 0.0f;
	}
}

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

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

	Scalar y = (minY + maxY) * 0.5f;
	Scalar z = (minZ + maxZ) * 0.5f;
	Scalar radius = maxY - y;

	Scalar a =
		line->direction.y*line->direction.y
		 + line->direction.z*line->direction.z;

	//
	//--------------------------------------------------------------------------
	// If the line is parallel to the cylinder, see if it hits the bottom/top of
	// the cylinder in the X-Z plane.  If it does, it hits the cylinder when it
	// hits the box, otherwise it misses altogether
	//--------------------------------------------------------------------------
	//
	if (Small_Enough(a))
	{
		y -= line->origin.y;
		z -= line->origin.z;
		if (y*y + z*z > radius*radius)
		{
			return False;
		}
		line->length = Max(enters, 0.0f);
		return True;
	}

	//
	//-----------------------------------------------------------------------
	// The line is not parallel to the cylinder, so solve the equation giving
	// the intersection of the line with the cylinder using the quadratic
	// equation
	//-----------------------------------------------------------------------
	//
	y = line->origin.y - y;
	z = line->origin.z - z;

	Scalar b = 2.0f * (line->direction.y*y + line->direction.z*z);
	Scalar c = y*y + z*z - radius*radius;
	Scalar i = b*b - 4.0f*a*c;

	if (i < SMALL)
	{
		return False;
	}

	//
	//-------------------------------------------------------------------------
	// If the line does hit the cylinder, update the enter/leaving distances to
	// take the cylinder surface into account
	//-------------------------------------------------------------------------
	//
	Verify(a > SMALL);
	Scalar ratio = Sqrt(a)/(a*a);
	i = Sqrt(i);
	b *= -ratio;
	i *= ratio;
	Scalar enter = (b - i) * 0.5f;
	if (enter > enters)
	{
		enters = enter;
	}

	Scalar leave = enter + i;
	if (leave < leaves)
	{
		leaves = leave;
	}

	//
	//------------------------------------------------------------------------
	// If we have pushed the entering distance after the leaving distance, the
	// cylinder was missed, otherwise it is hit at the new entering distance
	//------------------------------------------------------------------------
	//
	if (enters > leaves || enters > line->length || leaves < 0.0f)
	{
		return False;
	}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoxedXAxisCylinder::TestInstance() const
{
	return solidType == XAxisCylinderType;
}

//#############################################################################
//##########################    BoxedYAxisCylinder    #########################
//#############################################################################

enum {
	Min_X_Bit = 0x01,
	Max_X_Bit = 0x02,
	X_Bits = Min_X_Bit|Max_X_Bit,
	Min_Y_Bit = 0x04,
	Max_Y_Bit = 0x08,
	Y_Bits = Min_Y_Bit|Max_Y_Bit,
	Min_Z_Bit = 0x10,
	Max_Z_Bit = 0x20,
	Z_Bits = Min_Z_Bit|Max_Z_Bit,
	X_Axis_Bit = 0x04,
	Y_Axis_Bit = 0x02,
	Z_Axis_Bit = 0x01
};

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

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoxedYAxisCylinder::VerifyCollision(BoxedSolidCollision &collision)
{
	Check(this);
	Check(&collision);

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

	Scalar
		x,
		z,
		their_x,
		their_z,
		radius,
		their_radius;

	BoxedSolid* solid = collision.GetTreeVolume();

	//
	//--------------------------------------------------------------------------
	// See which type of collision we are going to have to verify, and branch to
	// it
	//--------------------------------------------------------------------------
	//
	switch (solid->solidType)
	{

	//
	//-------------------------------------------------------------------------
	// When colliding with a ramp or horizontal cylinder, go ahead and have the
	// collision slice assume it is from a block.  The error thus generated is
	// actually very small and not really noticed by anyone
	//-------------------------------------------------------------------------
	//
	case BlockType:
	case SphereType:
	case ReducibleBlockType:
	case RampFacingNegativeZType:
	case RampFacingNegativeXType:
	case RampFacingPositiveZType:
	case RampFacingPositiveXType:
	case InvertedRampFacingNegativeZType:
	case InvertedRampFacingNegativeXType:
	case InvertedRampFacingPositiveZType:
	case InvertedRampFacingPositiveXType:
	case WedgeFacingNegativeZAndPositiveXType:
	case WedgeFacingNegativeZAndNegativeXType:
	case WedgeFacingPositiveZAndNegativeXType:
	case WedgeFacingPositiveZAndPositiveXType:
	case XAxisCylinderType:
	case ZAxisCylinderType:
	case RightHandedTileType:
	case LeftHandedTileType:
		return IntersectsBounded(collision.collisionSlice);

	//
	//--------------------------------------------------------------------------
	// When colliding with another upright cylinder, simply calculate the
	// distance between the two center points and see if it is less than the sum
	// of the two radii
	//--------------------------------------------------------------------------
	//
	case YAxisCylinderType:
		x = (maxX + minX) * 0.5f;
		z = (maxZ + minZ) * 0.5f;
		radius = maxX - x;

		their_x = (solid->maxX + solid->minX) * 0.5f;
		their_z = (solid->maxZ + solid->minZ) * 0.5f;
		their_radius = solid->maxX - their_x;

		radius += their_radius;
		x -= their_x;
		z -= their_z;

		return radius*radius >= x*x + z*z;

	//
	//------------------------------------------------------------------------
	// When colliding with a cone, we will base all the calculations on the
	// assumption that the collision can be detected identically by a cylinder
	// created by the the intersection of the bottom plane of this volume with
	// the cone
	//------------------------------------------------------------------------
	//
	case ConeType:
		x = (maxX + minX) * 0.5f;
		z = (maxZ + minZ) * 0.5f;
		radius = maxX - x;

		their_x = (solid->maxX + solid->minX) * 0.5f;
		their_z = (solid->maxZ + solid->minZ) * 0.5f;
		their_radius = solid->maxX - their_x;
		their_radius *= (solid->maxY - minY) / (solid->maxY - solid->minY);

		radius += their_radius;
		x -= their_x;
		z -= their_z;

		return radius*radius >= x*x + z*z;

	//
	//------------------------------
	// Fail on the unsupported types
	//------------------------------
	//
	default:
		#if defined(LAB_ONLY)
			DEBUG_STREAM << collision.GetTreeVolume()->solidType << endl;
			Fail("Unsupported collision primative\n");
		#endif
		return False;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoxedYAxisCylinder::ProcessCollision(
		BoxedSolidCollision &collision,
		const Vector3D &velocity,
		BoxedSolidCollisionList *last_collisions,
		Normal *normal,
		Scalar *penetration
	)
{
	Check(this);
	Check(&collision);
	Check(&velocity);
	Check_Pointer(normal);
	Check_Pointer(penetration);

	Scalar
		result,
		len;
	int
		i,
		ok=0,
		axes,
		axis,
		face,
		mask = 0,
		opp_face,
		temp;
	BoxedSolid *solid = collision.GetTreeVolume();
	Vector3D
		position;

	//
	//-----------------------------------------------------
	// First verify that the collision really should happen
	//-----------------------------------------------------
	//
	if (Small_Enough(velocity.LengthSquared()))
	{
		return False;
	}
	if (!VerifyCollision(collision))
	{
		return False;
	}

	//
	//------------------------------------------------------------------------
	// Look at each of the three coordinates of motion, and make sure that the
	// collision slice is valid for at least one of them
	//------------------------------------------------------------------------
	//
	for (axis=X_Axis; axis<=Z_Axis; ++axis)
	{
		//
		//-------------------------------------------------------------------
		// Make sure we have actual velocity along this axis, then figure out
		// which face to test
		//-------------------------------------------------------------------
		//
		(*normal)[axis] = 0.0f;
		mask <<= 1;
		if (Small_Enough(velocity[axis]))
		{
			continue;
		}
		face = (axis<<1) + (velocity[axis]>0.0f);
		opp_face = face ^ 1;

		//
		//----------------------------------------------------------------------
		// Find out which faces of the cylinder's bounding box should be
		// considered for purposes of the collision.  Sides are only considered
		// valid if the leading face of the collision slice matches the leading
		// face of the disk.
		//----------------------------------------------------------------------
		//
		if (
			collision.collisionSlice[face] == (*this)[face] &&
			(
				(face&1) && (*this)[opp_face] < collision.collisionSlice[opp_face]
				 ||
					!(face&1)
					 && (*this)[opp_face] > collision.collisionSlice[opp_face]
			)
		)
			mask |= 1;
	}

	//
	//-----------------------------------------------------------------
	// Handle the actual collision based upon the type of model and the
	// components involved in the collision
	//-----------------------------------------------------------------
	//
	switch (solid->solidType)
	{

	//
	//--------------------------
	// Handle the Z facing Ramps
	//--------------------------
	//
	case RampFacingNegativeZType:
		normal->y = solid->maxZ - solid->minZ;
		normal->z = solid->maxY - solid->minY;
		position.y = collision.collisionSlice.minY - solid->maxY;
		position.z = collision.collisionSlice.minZ - solid->minZ;

		//
		//--------------------------------------------------------------------
		// The normal y and z values are assumed at this point to point in the
		// right direction although their lengths are screwed up
		//--------------------------------------------------------------------
		//
Compute_X:
		len = Sqrt(normal->y*normal->y + normal->z*normal->z);
		Verify(!Small_Enough(len));
		normal->y /= len;
		normal->z /= len;
		Check_Fpu();

		result = velocity.y*normal->y + velocity.z*normal->z;
		if (result > -SMALL)
		{
			if (mask&X_Axis_Bit)
			{
				goto X_Only;
			}
			else
			{
				break;
			}
		}
		*penetration =
			fabs((position.y*normal->y + position.z*normal->z) / result);
		Check_Fpu();

		//
		//-----------------------------------------------------------------------
		// If the side of the ramp was involved in the collision, pick either the
		// ramp or side to reflect off of based upon which opposes motion more
		//-----------------------------------------------------------------------
		//
		if (mask&X_Axis_Bit)
		{
			//
			//----------------------------------------------------------------
			// Compute plane equation and find the distance from the most
			// penetrating slice point to the plane.  If the ray comes out the
			// ramp, we hit that, otherwise we hit the side
			//----------------------------------------------------------------
			//
			result = fabs((solid->maxX - solid->minX) / velocity.x);
			Check_Fpu();
			if (result < *penetration)
			{
X_Only:
				normal->x = (velocity.x > 0.0f) ? -1.0f : 1.0f;
				normal->y = 0.0f;
				normal->z = 0.0f;
				*penetration = result;
			}
		}
		goto Reflect;

	case RampFacingPositiveZType:
		normal->y = solid->maxZ - solid->minZ;
		normal->z = solid->minY - solid->maxY;
		position.y = collision.collisionSlice.minY - solid->maxY;
		position.z = collision.collisionSlice.maxZ - solid->maxZ;
		goto Compute_X;

	case InvertedRampFacingNegativeZType:
		normal->y = solid->minZ - solid->maxZ;
		normal->z = solid->maxY - solid->minY;
		position.y = collision.collisionSlice.maxY - solid->maxY;
		position.z = collision.collisionSlice.minZ - solid->maxZ;
		goto Compute_X;

	case InvertedRampFacingPositiveZType:
		normal->y = solid->minZ - solid->maxZ;
		normal->z = solid->minY - solid->maxY;
		position.y = collision.collisionSlice.maxY - solid->maxY;
		position.z = collision.collisionSlice.maxZ - solid->minZ;
		goto Compute_X;

	//
	//--------------------------
	// Handle the X facing ramps
	//--------------------------
	//
	case RampFacingNegativeXType:
		normal->x = solid->maxY - solid->minY;
		normal->y = solid->maxX - solid->minX;
		position.y = collision.collisionSlice.minY - solid->maxY;
		position.x = collision.collisionSlice.minX - solid->minX;

		//
		//--------------------------------------------------------------------
		// The normal x and y values are assumed at this point to point in the
		// right direction although their lengths are screwed up
		//--------------------------------------------------------------------
		//
Compute_Z:
		len = Sqrt(normal->x*normal->x + normal->y*normal->y);
		Verify(!Small_Enough(len));
		normal->x /= len;
		normal->y /= len;
		Check_Fpu();

		result = velocity.y*normal->y + velocity.x*normal->x;
		if (result > -SMALL)
		{
			if (mask&Z_Axis_Bit)
			{
				goto Z_Only;
			}
			else
			{
				break;
			}
		}
		*penetration =
			fabs((position.y*normal->y + position.x*normal->x) / result);
		Check_Fpu();

		//
		//-----------------------------------------------------------------------
		// If the side of the ramp was involved in the collision, pick either the
		// ramp or side to reflect off of based upon which opposes motion more
		//-----------------------------------------------------------------------
		//
		if (mask&Z_Axis_Bit)
		{
			//
			//----------------------------------------------------------------
			// Compute plane equation and find the distance from the most
			// penetrating slice point to the plane.  If the ray comes out the
			// ramp, we hit that, otherwise we hit the side
			//----------------------------------------------------------------
			//
			result = fabs((solid->maxZ - solid->minZ) / velocity.z);
			Check_Fpu();
			if (result < *penetration)
			{
Z_Only:
				normal->x = 0.0f;
				normal->y = 0.0f;
				normal->z = (velocity.z > 0.0f) ? -1.0f : 1.0f;
				*penetration = result;
			}
		}
		goto Reflect;

	case RampFacingPositiveXType:
		normal->x = solid->minY - solid->maxY;
		normal->y = solid->maxX - solid->minX;
		position.y = collision.collisionSlice.minY - solid->maxY;
		position.x = collision.collisionSlice.maxX - solid->maxX;
		goto Compute_Z;

	case InvertedRampFacingNegativeXType:
		normal->x = solid->maxY - solid->minY;
		normal->y = solid->minX - solid->maxX;
		position.y = collision.collisionSlice.maxY - solid->maxY;
		position.x = collision.collisionSlice.minX - solid->maxX;
		goto Compute_Z;

	case InvertedRampFacingPositiveXType:
		normal->x = solid->minY - solid->maxY;
		normal->y = solid->minX - solid->maxX;
		position.y = collision.collisionSlice.maxY - solid->maxY;
		position.x = collision.collisionSlice.maxX - solid->minX;
		goto Compute_Z;

	//
	//------------------
	// Handle the wedges
	//------------------
	//
	case WedgeFacingNegativeZAndPositiveXType:
		normal->x = solid->minZ - solid->maxZ;
		normal->z = solid->maxX - solid->minX;
		position.x = collision.collisionSlice.maxX - solid->minX;
		position.z = collision.collisionSlice.minZ - solid->minZ;

		//
		//--------------------------------------------------------------------
		// The normal x and z values are assumed at this point to point in the
		// right direction although their lengths are screwed up
		//--------------------------------------------------------------------
		//
Compute_Y:
		len = Sqrt(normal->x*normal->x + normal->z*normal->z);
		Verify(!Small_Enough(len));
		normal->x /= len;
		normal->z /= len;
		Check_Fpu();

		result = velocity.x*normal->x + velocity.z*normal->z;
		if (result > -SMALL)
		{
			if (mask&Y_Axis_Bit)
			{
				goto Y_Only;
			}
			else
			{
				break;
			}
		}
		*penetration =
			fabs((position.x*normal->x + position.z*normal->z) / result);
		Check_Fpu();

		//
		//-----------------------------------------------------------------------
		// If the top of the wedge was involved in the collision, pick either the
		// side or top to reflect off of based upon which opposes motion more
		//-----------------------------------------------------------------------
		//
		if (mask&Y_Axis_Bit)
		{
			//
			//----------------------------------------------------------------
			// Compute plane equation and find the distance from the most
			// penetrating slice point to the plane.  If the ray comes out the
			// ramp, we hit that, otherwise we hit the side
			//----------------------------------------------------------------
			//
			result = fabs((solid->maxY - solid->minY) / velocity.y);
			Check_Fpu();
			if (result < *penetration)
			{
Y_Only:
				normal->x = 0.0f;
				normal->y = (velocity.y > 0.0f) ? -1.0f : 1.0f;
				normal->z = 0.0f;
				*penetration = result;
			}
		}
		goto Reflect;

	case WedgeFacingNegativeZAndNegativeXType:
		normal->x = solid->maxZ - solid->minZ;
		normal->z = solid->maxX - solid->minX;
		position.x = collision.collisionSlice.minX - solid->maxX;
		position.z = collision.collisionSlice.minZ - solid->minZ;
		goto Compute_Y;

	case WedgeFacingPositiveZAndNegativeXType:
		normal->x = solid->maxZ - solid->minZ;
		normal->z = solid->minX - solid->maxX;
		position.x = collision.collisionSlice.minX - solid->minX;
		position.z = collision.collisionSlice.maxZ - solid->minZ;
		goto Compute_Y;

	case WedgeFacingPositiveZAndPositiveXType:
		normal->x = solid->minZ - solid->maxZ;
		normal->z = solid->minX - solid->maxX;
		position.x = collision.collisionSlice.maxX - solid->maxX;
		position.z = collision.collisionSlice.maxZ - solid->minZ;
		goto Compute_Y;

	//
	//----------------------------------------------------------------------
	//----------------------------------------------------------------------
	//
	case XAxisCylinderType:
		normal->y = minY + maxY - solid->minY - solid->maxY;
		normal->z = minZ + maxZ - solid->minZ - solid->maxZ;
		len = normal->y*normal->y + normal->z*normal->z;
		if (Small_Enough(len))
		{
			goto Reverse;
		}
		len = Sqrt(len);
		normal->y /= len;
		normal->z /= len;
		Check_Fpu();
		*penetration = 1.0f;
		goto Reflect;

	case ZAxisCylinderType:
		normal->x = minX + maxX - solid->minX - solid->maxX;
		normal->y = minY + maxY - solid->minY - solid->maxY;
		len = normal->x*normal->x + normal->y*normal->y;
		if (Small_Enough(len))
		{
			goto Reverse;
		}
		len = Sqrt(len);
		normal->x /= len;
		normal->y /= len;
		Check_Fpu();
		*penetration = 1.0f;
		goto Reflect;

	case YAxisCylinderType:
		normal->x = minX + maxX - solid->minX - solid->maxX;
		normal->z = minZ + maxZ - solid->minZ - solid->maxZ;
		len = normal->x*normal->x + normal->z*normal->z;
		if (Small_Enough(len))
		{
			goto Reverse;
		}
		len = Sqrt(len);
		normal->x /= len;
		normal->z /= len;
		Check_Fpu();
		*penetration = 1.0f;

		if (mask & Y_Axis_Bit)
		{
			len = (velocity.z > 0.0f) ? -1.0f : 1.0f;
			if (len*velocity.y < velocity.x*normal->x + velocity.z*normal->z)
			{
				normal->x = 0.0f;
				normal->y = len;
				normal->z = 0.0f;
			}
		}
		goto Reflect;

	//
	//-------------
	// Handle cones
	//-------------
	//
	case ConeType:
		normal->x = minX + maxX - solid->minX - solid->maxX;
		normal->z = minZ + maxZ - solid->minZ - solid->maxZ;
		len = normal->x*normal->x + normal->z*normal->z;
		*penetration = 1.0f;
		if (Small_Enough(len))
		{
			goto Reverse;
		}
		normal->y =
			0.5f * (solid->maxX - solid->minX) * Sqrt(len)
			 / (solid->maxY - solid->minY);
		Check_Fpu();
		normal->Normalize(*normal);
		goto Reflect;

	//
	//---------------
	// Handle spheres
	//---------------
	//
	case SphereType:
		normal->x = minX + maxX - solid->minX - solid->maxX;
		normal->y = minY + maxY - solid->minY - solid->maxY;
		normal->z = minZ + maxZ - solid->minZ - solid->maxZ;
		len = normal->x*normal->x + normal->y*normal->y + normal->z*normal->z;
		if (Small_Enough(len))
		{
			goto Reverse;
		}
		len = Sqrt(len);
		normal->x /= len;
		normal->y /= len;
		normal->z /= len;
		Check_Fpu();
		*penetration = 1.0f;
		goto Reflect;

	//
	//---------------------------------
	// Handle reflecting off of a block
	//---------------------------------
	//
	case BlockType:
	case ReducibleBlockType:

		//
		//---------------------------------------------------
		// Count the number of axes involved in the collision
		//---------------------------------------------------
		//
		temp = mask;
		for (axes=0; temp; temp>>=1)
		{
			if (temp&1)
			{
				++axes;
			}
		}

		//
		//------------------------------------------------------
		// Handle as appropriate to the number of sides involved
		//------------------------------------------------------
		//
		switch (axes)
		{

		//
		//---------------------------------------------------------------------
		// If no sides were found to be involved in the collision, then we have
		// a weird case where the disk has completely penetrated through the
		// block
		//---------------------------------------------------------------------
		//
		case 0:

			//
			//-------------------------------------------------------------------
			// This is where we will check the current volume against our list of
			// volumes hit last time.  If we have not already hit this solid,
			// reverse the motion as we have penetrated the object
			//-------------------------------------------------------------------
			//
			i = 0;
			if (last_collisions)
			{
				for (i=0; i<last_collisions->GetCollisionCount(); ++i)
				{
					if ((*last_collisions)[i].GetTreeVolume() == solid)
					{
						break;
					}
				}
			}
			if (!last_collisions || i == last_collisions->GetCollisionCount())
			{
Reverse:
				*penetration = 1.0f;
				result = -1.0f / velocity.Length();
				Check_Fpu();
				normal->x = velocity.x*result;
				normal->y = velocity.y*result;
				normal->z = velocity.z*result;
				ok = True;
				break;
			}

			//
			//--------------------------------------------------------------------
			// Since this is not a fresh hit, check to see if any part of the
			// colliding disk sticks out of the slice.  If so, then we are heading
			// out of the solid in that direction, so let the motion continue.
			// WHAT IS THE VELOCITY TRIGGER FOR????
			//--------------------------------------------------------------------
			//
			position.x = (maxX + minX) * 0.5f;
			position.y = (maxY + minY) * 0.5f;
			position.z = (maxZ + minZ) * 0.5f;
			for (face=0; face<6; ++face)
			{
				i = face >> 1;
				if (face&1)
				{
					if
					(
						(*this)[face] > collision.collisionSlice[face]
						 && position[i] > collision.collisionSlice[face]
						 && velocity[i] < 5.0f
					)
						break;
				}
				else if
				(
					(*this)[face] < collision.collisionSlice[face]
					 && position[i] < collision.collisionSlice[face]
					 && velocity[i] > -5.0f
				)
					break;
			}
			if (face != 6)
			{
				break;
			}

			//
			//-------------------------------------------------------------------
			// Otherwise, generate a normal opposing motion towards the center of
			// the solid.  For each component, if it does not oppose motion, zero
			// it out.  This will give a better approximation of the brick
			//-------------------------------------------------------------------
			//
			normal->x = position.x - (solid->minX + solid->maxX) * 0.5f;
			normal->y = position.y - (solid->minY + solid->maxY) * 0.5f;
			normal->z = position.z - (solid->minZ + solid->maxZ) * 0.5f;
			for (i=0; i<3; ++i)
			{
				if ((*normal)[i]*velocity[i] >= 0.0)
				{
					(*normal)[i] = 0.0;
				}
			}

			//
			//----------------------------------------------------------------
			// Normalize the vector, making sure we don't get killed by a zero
			// case
			//----------------------------------------------------------------
			//
			result = normal->Length();
			if (!Small_Enough(result))
			{
				normal->x /= result;
				normal->y /= result;
				normal->z /= result;
				Check_Fpu();
			}
			else
			{
				break;
			}
			*penetration = 1.0f;
			goto Reflect;

		//
		//-----------------------------------------------------------------
		// If only one side is involved in the collision, reflect off of it
		//-----------------------------------------------------------------
		//
		case 1:
Test_Corner:
			position.x = 0.5f * (minX + maxX);
			position.z = 0.5f * (minZ + maxZ);
			switch (mask)
			{
			case X_Axis_Bit:
				face = X_Axis << 1;

				//
				//----------------------------------
				// Figure out the z bounce direction
				//----------------------------------
				//
				if (collision.collisionSlice.minZ > position.z)
				{
					normal->z = position.z - collision.collisionSlice.minZ;
				}
				else if (collision.collisionSlice.maxZ < position.z)
				{
					normal->z = position.z - collision.collisionSlice.maxZ;
				}

				//
				//----------------------------------
				// Figure out the x bounce direction
				//----------------------------------
				//
				len = maxX - position.x;
				normal->x = len*len - normal->z*normal->z;
				Verify(normal->x >= 0.0f);
				normal->x = Sqrt(normal->x);
				normal->x /= len;
				normal->z /= len;
				Check_Fpu();
				if (velocity.x > 0.0f)
				{
					normal->x = -normal->x;
				}
				break;

			case Y_Axis_Bit:
				face = Y_Axis << 1;
				*penetration =
					fabs(
						(
							collision.collisionSlice[face]
							 - collision.collisionSlice[face^1]
						) / velocity[face>>1]
					);
				Check_Fpu();
				goto Pick_Side;

			case Z_Axis_Bit:
				face = Z_Axis << 1;

				//
				//----------------------------------
				// Figure out the x bounce direction
				//----------------------------------
				//
				if (collision.collisionSlice.minX > position.x)
				{
					normal->x = position.x - collision.collisionSlice.minX;
				}
				else if (collision.collisionSlice.maxX < position.x)
				{
					normal->x = position.x - collision.collisionSlice.maxX;
				}

				//
				//----------------------------------
				// Figure out the z bounce direction
				//----------------------------------
				//
				len = maxX - position.x;
				normal->z = len*len - normal->x*normal->x;
				Verify(normal->z >= 0.0f);
				normal->z = Sqrt(normal->z);
				normal->x /= len;
				normal->z /= len;
				Check_Fpu();
				if (velocity.z > 0.0f)
				{
					normal->z = -normal->z;
				}
				break;
			}
			*penetration =
				fabs(
					(
						collision.collisionSlice[face]
						 - collision.collisionSlice[face^1]
					) / velocity[face>>1]
				);
			Check_Fpu();

			//
			//-------------------------------------------------------------------
			// Check to make sure that the normal opposes velocity.  If it
			// doesn't look to see if this is the first hit on the object, and if
			// so, reverse the velocity
			//-------------------------------------------------------------------
			//
Reflect:
			Check(normal);
			ok = *normal * velocity < 0.0f;
			if (!ok)
			{
				i = 0;
				if (last_collisions)
				{
					for (i=0; i<last_collisions->GetCollisionCount(); ++i)
					{
						if ((*last_collisions)[i].GetTreeVolume() == solid)
						{
							break;
						}
					}
				}
				if (!last_collisions || i == last_collisions->GetCollisionCount())
				{
					goto Reverse;
				}
			}
			break;

		 //
		 //---------------------------------------------------------------------
		 // If two faces were involved, we hit an edge, so figure out which face
		 // we really hit and mask off the other face
		 //---------------------------------------------------------------------
		 //
		 case 2:
			switch (mask)
			{
			case X_Axis_Bit|Z_Axis_Bit:
				position.x =
					collision.collisionSlice.maxX - collision.collisionSlice.minX;
				position.z =
					collision.collisionSlice.maxZ - collision.collisionSlice.minZ;
				mask ^=
					(fabs(position.z*velocity.x) > fabs(position.x*velocity.z))
						? Z_Axis_Bit : X_Axis_Bit;
				break;

			case X_Axis_Bit|Y_Axis_Bit:
				position.x =
					collision.collisionSlice.maxX - collision.collisionSlice.minX;
				position.y =
					collision.collisionSlice.maxY - collision.collisionSlice.minY;
				mask ^=
					(fabs(position.y*velocity.x) > fabs(position.x*velocity.y))
						? Y_Axis_Bit : X_Axis_Bit;
				break;

			case Y_Axis_Bit|Z_Axis_Bit:
				position.y =
					collision.collisionSlice.maxY - collision.collisionSlice.minY;
				position.z =
					collision.collisionSlice.maxZ - collision.collisionSlice.minZ;
				mask ^=
					(fabs(position.y*velocity.z) > fabs(position.z*velocity.y))
						? Y_Axis_Bit : Z_Axis_Bit;
				break;
			}
			goto Test_Corner;


		//
		//-----------------------------------------------------------------------
		// Otherwise, project the intruding vertex back along the velocity vector
		// to try and find out which face to bounce off of
		//-----------------------------------------------------------------------
		//
		default:
			position.x =
				collision.collisionSlice.maxX - collision.collisionSlice.minX;
			position.y =
				collision.collisionSlice.maxY - collision.collisionSlice.minY;
			position.z =
				collision.collisionSlice.maxZ - collision.collisionSlice.minZ;

			position.x = fabs(position.x/velocity.x);
			position.y = fabs(position.y/velocity.y);
			position.z = fabs(position.z/velocity.z);

			if (position.x < position.y)
			{
				face = (position.x < position.z) ? (X_Axis<<1) : (Z_Axis<<1);
			}
			else
			{
				face = (position.y < position.z) ? (Y_Axis<<1) : (Z_Axis<<1);
			}
			*penetration = position[face>>1];

Pick_Side:
			if (velocity[face>>1] > 0.0f)
			{
				(*normal)[face>>1] = -1.0f;
			}
			else
			{
				(*normal)[face>>1] = 1.0f;
			}
			goto Reflect;
		}
		break;

	#if defined(LAB_ONLY)
		default:
			Fail("Unsupported collision type!...\n");
			break;
	#endif
	}

	if (ok)
	{
		Check(normal);
		if (*penetration < 0.0f)
		{
			return False;
		}
	}
	return ok;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoxedYAxisCylinder::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 center point in the XZ plane of the cylinder, and find the
	// radius of the cylinder by measuring in the X axis.  The Y value will
	// automatically be within the cylinder if X & Z are
	//---------------------------------------------------------------------
	//
	Scalar x = (minX + maxX) * 0.5f;
	Scalar z = (minZ + maxZ) * 0.5f;
	Scalar radius = maxX - x;

	//
	//-----------------------------------------------------------------------
	// Convert the point to the coordinates of the cylinder, putting the
	// center point of the cylinder 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 x2 = x;
	Scalar z2 = z;
	Clamp(x2, extents.minX, extents.maxX);
	Clamp(z2, extents.minZ, extents.maxZ);
	x2 -= x;
	z2 -= z;
	return x2*x2 + z2*z2 <= radius*radius;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoxedYAxisCylinder::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 center point in the XZ plane of the cylinder, and find the
	// radius of the cylinder by measuring in the X axis.  The Y value will
	// automatically be within the cylinder if X & Z are
	//---------------------------------------------------------------------
	//
	Scalar x = (minX + maxX) * 0.5f;
	Scalar z = (minZ + maxZ) * 0.5f;
	Scalar radius = maxX - x;

	//
	//-----------------------------------------------------------------------
	// Convert the point to the coordinates of the cylinder, putting the
	// center point of the cylinder 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
	//-----------------------------------------------------------------------
	//
	x -= point.x;
	z -= point.z;
	return x*x + z*z <= radius*radius;
}

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

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

	//
	//---------------------------------------------------------------------
	// Find the center point in the XZ plane of the cylinder, and find the
	// radius of the cylinder by measuring in the X axis.  The Y value will
	// automatically be within the cylinder if X & Z are
	//---------------------------------------------------------------------
	//
	Scalar x = (minX + maxX) * 0.5f;
	Scalar z = (minZ + maxZ) * 0.5f;
	Scalar radius = maxX - x;

	//
	//-----------------------------------------------------------------------
	// Convert the point to the coordinates of the cylinder, putting the
	// center point of the cylinder 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
	//-----------------------------------------------------------------------
	//
	x -= point.x;
	z -= point.z;
	if (x*x + z*z > radius*radius)
	{
		return -1.0f;
	}

	Scalar height = point.y - maxY;
	return Abs(height);
}

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

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

	Scalar x = (minX + maxX) * 0.5f;
	Scalar z = (minZ + maxZ) * 0.5f;
	Scalar radius = maxX - x;

	Scalar a =
		line->direction.x*line->direction.x
		 + line->direction.z*line->direction.z;

	//
	//--------------------------------------------------------------------------
	// If the line is parallel to the cylinder, see if it hits the bottom/top of
	// the cylinder in the X-Z plane.  If it does, it hits the cylinder when it
	// hits the box, otherwise it misses altogether
	//--------------------------------------------------------------------------
	//
	if (Small_Enough(a))
	{
		x -= line->origin.x;
		z -= line->origin.z;
		if (x*x + z*z > radius*radius)
		{
			return False;
		}
		line->length = Max(enters, 0.0f);
		return True;
	}

	//
	//-----------------------------------------------------------------------
	// The line is not parallel to the cylinder, so solve the equation giving
	// the intersection of the line with the cylinder using the quadratic
	// equation
	//-----------------------------------------------------------------------
	//
	x = line->origin.x - x;
	z = line->origin.z - z;

	Scalar b = 2.0f * (line->direction.x*x + line->direction.z*z);
	Scalar c = x*x + z*z - radius*radius;
	Scalar i = b*b - 4.0f*a*c;

	if (i < SMALL)
	{
		return False;
	}

	//
	//-------------------------------------------------------------------------
	// If the line does hit the cylinder, update the enter/leaving distances to
	// take the cylinder surface into account
	//-------------------------------------------------------------------------
	//
	Verify(a > SMALL);
	Scalar ratio = Sqrt(a)/(a*a);
	i = Sqrt(i);
	b *= -ratio;
	i *= ratio;
	Scalar enter = (b - i) * 0.5f;
	if (enter > enters)
	{
		enters = enter;
	}

	Scalar leave = enter + i;
	if (leave < leaves)
	{
		leaves = leave;
	}

	//
	//------------------------------------------------------------------------
	// If we have pushed the entering distance after the leaving distance, the
	// cylinder was missed, otherwise it is hit at the new entering distance
	//------------------------------------------------------------------------
	//
	if (enters > leaves || enters > line->length || leaves < 0.0f)
	{
		return False;
	}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoxedYAxisCylinder::TestInstance() const
{
	return solidType == YAxisCylinderType;
}

//#############################################################################
//##########################    BoxedZAxisCylinder    #########################
//#############################################################################

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

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoxedZAxisCylinder::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 center point in the XZ plane of the cylinder, and find the
	// radius of the cylinder by measuring in the X axis.  The Y value will
	// automatically be within the cylinder if X & Z are
	//---------------------------------------------------------------------
	//
	Scalar x = (minX + maxX) * 0.5f;
	Scalar y = (minY + maxY) * 0.5f;
	Scalar radius = maxX - x;

	//
	//-----------------------------------------------------------------------
	// Convert the point to the coordinates of the cylinder, putting the
	// center point of the cylinder 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 x2 = x;
	Scalar y2 = y;
	Clamp(x2, extents.minX, extents.maxX);
	Clamp(y2, extents.minY, extents.maxY);
	x2 -= x;
	y2 -= y;
	return x2*x2 + y2*y2 <= radius*radius;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoxedZAxisCylinder::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 center point in the XZ plane of the cylinder, and find the
	// radius of the cylinder by measuring in the X axis.  The Y value will
	// automatically be within the cylinder if X & Z are
	//---------------------------------------------------------------------
	//
	Scalar x = (minX + maxX) * 0.5f;
	Scalar y = (minY + maxY) * 0.5f;
	Scalar radius = maxX - x;

	//
	//-----------------------------------------------------------------------
	// Convert the point to the coordinates of the cylinder, putting the
	// center point of the cylinder 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
	//-----------------------------------------------------------------------
	//
	x -= point.x;
	y -= point.y;
	return x*x + y*y <= radius*radius;
}

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

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

	//
	//---------------------------------------------------------------------
	// Find the center point in the XZ plane of the cylinder, and find the
	// radius of the cylinder by measuring in the X axis.  The Y value will
	// automatically be within the cylinder if X & Z are
	//---------------------------------------------------------------------
	//
	Scalar x = (minX + maxX) * 0.5f;
	Scalar radius = maxX - x;

	//
	//--------------------------------------------------------------------------
	// figure out the thickness of cylinder slice where a plane perpendicular to
	// X drops through the cylinder and the test point
	//--------------------------------------------------------------------------
	//
	x = point.x - x;
	Scalar y = radius - Sqrt(radius*radius - x*x);
	if (point.y > maxY - y)
	{
		return point.y - maxY + y;
	}
	else if (point.y < minY + y)
	{
		return -1.0f;
	}
	else
	{
		return 0.0f;
	}
}

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

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

	Scalar x = (minX + maxX) * 0.5f;
	Scalar y = (minY + maxY) * 0.5f;
	Scalar radius = maxX - x;

	Scalar a =
		line->direction.y*line->direction.y
		 + line->direction.x*line->direction.x;

	//
	//--------------------------------------------------------------------------
	// If the line is parallel to the cylinder, see if it hits the bottom/top of
	// the cylinder in the X-Z plane.  If it does, it hits the cylinder when it
	// hits the box, otherwise it misses altogether
	//--------------------------------------------------------------------------
	//
	if (Small_Enough(a))
	{
		y -= line->origin.y;
		x -= line->origin.x;
		if (y*y + x*x > radius*radius)
		{
			return False;
		}
		line->length = Max(enters, 0.0f);
		return True;
	}

	//
	//-----------------------------------------------------------------------
	// The line is not parallel to the cylinder, so solve the equation giving
	// the intersection of the line with the cylinder using the quadratic
	// equation
	//-----------------------------------------------------------------------
	//
	y = line->origin.y - y;
	x = line->origin.x - x;

	Scalar b = 2.0f * (line->direction.y*y + line->direction.x*x);
	Scalar c = y*y + x*x - radius*radius;
	Scalar i = b*b - 4.0f*a*c;

	if (i < SMALL)
	{
		return False;
	}

	//
	//-------------------------------------------------------------------------
	// If the line does hit the cylinder, update the enter/leaving distances to
	// take the cylinder surface into account
	//-------------------------------------------------------------------------
	//
	Verify(a > SMALL);
	Scalar ratio = Sqrt(a)/(a*a);
	i = Sqrt(i);
	b *= -ratio;
	i *= ratio;
	Scalar enter = (b - i) * 0.5f;
	if (enter > enters)
	{
		enters = enter;
	}

	Scalar leave = enter + i;
	if (leave < leaves)
	{
		leaves = leave;
	}

	//
	//------------------------------------------------------------------------
	// If we have pushed the entering distance after the leaving distance, the
	// cylinder was missed, otherwise it is hit at the new entering distance
	//------------------------------------------------------------------------
	//
	if (enters > leaves || enters > line->length || leaves < 0.0f)
	{
		return False;
	}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoxedZAxisCylinder::TestInstance() const
{
	return solidType == ZAxisCylinderType;
}

