//===========================================================================//
// File:	boxsolid.cpp                                                        //
// 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-1996, 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(FILEUTIL_HPP)
#	include <fileutil.hpp>
#endif

#if !defined(ORIGIN_HPP)
#	include <origin.hpp>
#endif

#if !defined(LINMTRX_HPP)
#	include <linmtrx.hpp>
#endif

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

#if !defined(NOTATION_HPP)
#	include <notation.hpp>
#endif

//#############################################################################
//##############################    BoxedSolid    #############################
//#############################################################################

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BoxedSolid::BoxedSolid(
	const ExtentBox &extents,
	BoxedSolid::Type type,
	BoxedSolid::Material material,
	Simulation *owner,
	BoxedSolid *next_solid
):
	TaggedBoundingBox(extents, owner)
{

	Check_Pointer(this);
	solidType = type;
	materialType = material;
	nextSolid = next_solid;
	if (nextSolid)
	{
		Check(nextSolid);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BoxedSolid::BoxedSolid(
	const ExtentBox &extents,
	Material material,
	Simulation *owner,
	BoxedSolid *next_solid
):
	TaggedBoundingBox(extents, owner)
{
	Check_Pointer(this);
	solidType = BlockType;
	materialType = material;
	nextSolid = next_solid;
	if (nextSolid)
	{
		Check(nextSolid);
	}
}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BoxedSolid *
	BoxedSolid::MakeBoxedSolid(
		BoxedSolidResource *boxed_solid_resource,
		Simulation *owner,
		BoxedSolid *next_solid
	)
{

	BoxedSolid *boxed_solid;

	switch(boxed_solid_resource->solidType)
	{
		case BoxedSolid::BlockType:
			boxed_solid =
				new BoxedSolid(
					boxed_solid_resource->solidExtents,
					boxed_solid_resource->materialType,
					owner,
					next_solid
				);
			break;
		case BoxedSolid::SphereType:
			boxed_solid =
				new BoxedSphere(
					boxed_solid_resource->solidExtents,
					boxed_solid_resource->materialType,
					owner,
					next_solid
				);
			break;
		case BoxedSolid::ConeType:
			boxed_solid =
				new BoxedCone(
					boxed_solid_resource->solidExtents,
					boxed_solid_resource->materialType,
					owner,
					next_solid
				);
			break;
		case BoxedSolid::ReducibleBlockType:
			boxed_solid =
				new BoxedReducibleBlock(
					boxed_solid_resource->solidExtents,
					boxed_solid_resource->materialType,
					owner,
					next_solid
				);
			break;
		case BoxedSolid::RampFacingNegativeZType:
			boxed_solid =
				new BoxedRampFacingNegativeZ(
					boxed_solid_resource->solidExtents,
					boxed_solid_resource->materialType,
					owner,
					next_solid
				);
			break;
		case BoxedSolid::RampFacingNegativeXType:
			boxed_solid =
				new BoxedRampFacingNegativeX(
					boxed_solid_resource->solidExtents,
					boxed_solid_resource->materialType,
					owner,
					next_solid
				);
			break;
		case BoxedSolid::RampFacingPositiveZType:
			boxed_solid =
				new BoxedRampFacingPositiveZ(
					boxed_solid_resource->solidExtents,
					boxed_solid_resource->materialType,
					owner,
					next_solid
				);
			break;
		case BoxedSolid::RampFacingPositiveXType:
			boxed_solid =
				new BoxedRampFacingPositiveX(
					boxed_solid_resource->solidExtents,
					boxed_solid_resource->materialType,
					owner,
					next_solid
				);
			break;
		case BoxedSolid::InvertedRampFacingNegativeZType:
			boxed_solid =
				new BoxedInvertedRampFacingNegativeZ(
					boxed_solid_resource->solidExtents,
					boxed_solid_resource->materialType,
					owner,
					next_solid
				);
			break;
		case BoxedSolid::InvertedRampFacingNegativeXType:
			boxed_solid =
				new BoxedInvertedRampFacingNegativeX(
					boxed_solid_resource->solidExtents,
					boxed_solid_resource->materialType,
					owner,
					next_solid
				);
			break;
		case BoxedSolid::InvertedRampFacingPositiveZType:
			boxed_solid =
				new BoxedInvertedRampFacingPositiveZ(
					boxed_solid_resource->solidExtents,
					boxed_solid_resource->materialType,
					owner,
					next_solid
				);
			break;
		case BoxedSolid::InvertedRampFacingPositiveXType:
			boxed_solid =
				new BoxedInvertedRampFacingPositiveX(
					boxed_solid_resource->solidExtents,
					boxed_solid_resource->materialType,
					owner,
					next_solid
				);
			break;
		case BoxedSolid::WedgeFacingNegativeZAndPositiveXType:
			boxed_solid =
				new BoxedWedgeFacingNegativeZAndPositiveX(
					boxed_solid_resource->solidExtents,
					boxed_solid_resource->materialType,
					owner,
					next_solid
				);
			break;
		case BoxedSolid::WedgeFacingNegativeZAndNegativeXType:
			boxed_solid =
				new BoxedWedgeFacingNegativeZAndNegativeX(
					boxed_solid_resource->solidExtents,
					boxed_solid_resource->materialType,
					owner,
					next_solid
				);
			break;
		case BoxedSolid::WedgeFacingPositiveZAndNegativeXType:
			boxed_solid =
				new BoxedWedgeFacingPositiveZAndNegativeX(
					boxed_solid_resource->solidExtents,
					boxed_solid_resource->materialType,
					owner,
					next_solid
				);
			break;
		case BoxedSolid::WedgeFacingPositiveZAndPositiveXType:
			boxed_solid =
				new BoxedWedgeFacingPositiveZAndPositiveX(
					boxed_solid_resource->solidExtents,
					boxed_solid_resource->materialType,
					owner,
					next_solid
				);
			break;
		case BoxedSolid::XAxisCylinderType:
			boxed_solid =
				new BoxedXAxisCylinder(
					boxed_solid_resource->solidExtents,
					boxed_solid_resource->materialType,
					owner,
					next_solid
				);
			break;
		case BoxedSolid::YAxisCylinderType:
			boxed_solid =
				new BoxedYAxisCylinder(
					boxed_solid_resource->solidExtents,
					boxed_solid_resource->materialType,
					owner,
					next_solid
				);
			break;
		case BoxedSolid::ZAxisCylinderType:
			boxed_solid =
				new BoxedZAxisCylinder(
					boxed_solid_resource->solidExtents,
					boxed_solid_resource->materialType,
					owner,
					next_solid
				);
			break;
#if 0
		case BoxedSolid::RightHandedTileType:
			{
				TileResource *terrain =
					(TileResource*)boxed_solid_resource;
				boxed_solid =
					new RightHandedTile(
						boxed_solid_resource->solidExtents,
						boxed_solid_resource->materialType,
						owner,
						next_solid,
						terrain->cornerHeight
					);
			}
			break;
		case BoxedSolid::LeftHandedTileType:
			{
				TileResource *terrain =
					(TileResource*)boxed_solid_resource;
				boxed_solid =
					new LeftHandedTile(
						boxed_solid_resource->solidExtents,
						boxed_solid_resource->materialType,
						owner,
						next_solid,
						terrain->cornerHeight
					);
			}
			break;
#endif
		#if defined(LAB_ONLY)
			default:
				Dump(boxed_solid_resource->solidType);
				Fail("Bad box type!");
		#endif
	}

	Check(boxed_solid);
	return boxed_solid;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
#if DEBUG_LEVEL>0
	BoxedSolid::VerifyCollision(BoxedSolidCollision &collision)
#else
	BoxedSolid::VerifyCollision(BoxedSolidCollision &)
#endif
{
	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);

	return True;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoxedSolid::ProcessCollision(
		BoxedSolidCollision &,
		const Vector3D &,
		BoxedSolidCollisionList *,
		Normal *,
		Scalar *
	)
{
	Fail("Unsupported mover collision type!\n");
	return False;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

Logical
	BoxedSolid::TestInstance() const
{
	return solidType == BlockType;
}

//#############################################################################
//##########################    BoxedSolidResource    #########################
//#############################################################################

static const BoxedSolid::Type
	Left_90[BoxedSolid::SolidTypeCount]=
{
	BoxedSolid::BlockType, BoxedSolid::SphereType,
	BoxedSolid::ConeType, BoxedSolid::ReducibleBlockType,

	BoxedSolid::RampFacingNegativeXType,
	BoxedSolid::RampFacingPositiveZType,
	BoxedSolid::RampFacingPositiveXType,
	BoxedSolid::RampFacingNegativeZType,

	BoxedSolid::InvertedRampFacingNegativeXType,
	BoxedSolid::InvertedRampFacingPositiveZType,
	BoxedSolid::InvertedRampFacingPositiveXType,
	BoxedSolid::InvertedRampFacingNegativeZType,

	BoxedSolid::WedgeFacingNegativeZAndNegativeXType,
	BoxedSolid::WedgeFacingPositiveZAndNegativeXType,
	BoxedSolid::WedgeFacingPositiveZAndPositiveXType,
	BoxedSolid::WedgeFacingNegativeZAndPositiveXType,

	BoxedSolid::ZAxisCylinderType,
	BoxedSolid::YAxisCylinderType,
	BoxedSolid::XAxisCylinderType,
	
	BoxedSolid::LeftHandedTileType,
	BoxedSolid::RightHandedTileType
};

static const BoxedSolid::Type
	Left_180[BoxedSolid::SolidTypeCount]=
{
	BoxedSolid::BlockType, BoxedSolid::SphereType,
	BoxedSolid::ConeType, BoxedSolid::ReducibleBlockType,

	BoxedSolid::RampFacingPositiveZType,
	BoxedSolid::RampFacingPositiveXType,
	BoxedSolid::RampFacingNegativeZType,
	BoxedSolid::RampFacingNegativeXType,

	BoxedSolid::InvertedRampFacingPositiveZType,
	BoxedSolid::InvertedRampFacingPositiveXType,
	BoxedSolid::InvertedRampFacingNegativeZType,
	BoxedSolid::InvertedRampFacingNegativeXType,

	BoxedSolid::WedgeFacingPositiveZAndNegativeXType,
	BoxedSolid::WedgeFacingPositiveZAndPositiveXType,
	BoxedSolid::WedgeFacingNegativeZAndPositiveXType,
	BoxedSolid::WedgeFacingNegativeZAndNegativeXType,

	BoxedSolid::XAxisCylinderType,
	BoxedSolid::YAxisCylinderType,
	BoxedSolid::ZAxisCylinderType,
	
	BoxedSolid::RightHandedTileType,
	BoxedSolid::LeftHandedTileType
};

static const BoxedSolid::Type
	Left_270[BoxedSolid::SolidTypeCount]=
{
	BoxedSolid::BlockType, BoxedSolid::SphereType,
	BoxedSolid::ConeType, BoxedSolid::ReducibleBlockType,

	BoxedSolid::RampFacingPositiveXType,
	BoxedSolid::RampFacingNegativeZType,
	BoxedSolid::RampFacingNegativeXType,
	BoxedSolid::RampFacingPositiveZType,

	BoxedSolid::InvertedRampFacingPositiveXType,
	BoxedSolid::InvertedRampFacingNegativeZType,
	BoxedSolid::InvertedRampFacingNegativeXType,
	BoxedSolid::InvertedRampFacingPositiveZType,

	BoxedSolid::WedgeFacingPositiveZAndPositiveXType,
	BoxedSolid::WedgeFacingNegativeZAndPositiveXType,
	BoxedSolid::WedgeFacingNegativeZAndNegativeXType,
	BoxedSolid::WedgeFacingPositiveZAndNegativeXType,

	BoxedSolid::ZAxisCylinderType,
	BoxedSolid::YAxisCylinderType,
	BoxedSolid::XAxisCylinderType,
	
	BoxedSolid::LeftHandedTileType,
	BoxedSolid::RightHandedTileType
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BoxedSolidResource::Instance(
		const BoxedSolidResource &source,
		const Origin& origin
	)
{
	materialType = source.materialType;

	//
	//-------------------
	// Handle no rotation
	//-------------------
	//
	if (Close_Enough(origin.angularPosition.w, 1.0f))
	{
		solidType = source.solidType;
		solidExtents = source.solidExtents;
		sliceExtents = source.sliceExtents;
	}

	//
	//-------------------------------
	// Test for a 180 degree rotation
	//-------------------------------
	//
	else if (Close_Enough(fabs(origin.angularPosition.y), 1.0f))
	{
		solidType = Left_180[source.solidType];
		solidExtents.minX = -source.solidExtents.maxX;
		solidExtents.maxX = -source.solidExtents.minX;
		solidExtents.minY = source.solidExtents.minY;
		solidExtents.maxY = source.solidExtents.maxY;
		solidExtents.minZ = -source.solidExtents.maxZ;
		solidExtents.maxZ = -source.solidExtents.minZ;
		sliceExtents.minX = -source.sliceExtents.maxX;
		sliceExtents.maxX = -source.sliceExtents.minX;
		sliceExtents.minY = source.sliceExtents.minY;
		sliceExtents.maxY = source.sliceExtents.maxY;
		sliceExtents.minZ = -source.sliceExtents.maxZ;
		sliceExtents.maxZ = -source.sliceExtents.minZ;
	}

	//
	//---------------------------------
	// Handle a 90 degree left rotation
	//---------------------------------
	//
	else if (Close_Enough(origin.angularPosition.w, origin.angularPosition.y))
	{
		solidType = Left_90[source.solidType];
		solidExtents.minX = source.solidExtents.minZ;
		solidExtents.maxX = source.solidExtents.maxZ;
		solidExtents.minY = source.solidExtents.minY;
		solidExtents.maxY = source.solidExtents.maxY;
		solidExtents.minZ = -source.solidExtents.maxX;
		solidExtents.maxZ = -source.solidExtents.minX;
		sliceExtents.minX = source.sliceExtents.minZ;
		sliceExtents.maxX = source.sliceExtents.maxZ;
		sliceExtents.minY = source.sliceExtents.minY;
		sliceExtents.maxY = source.sliceExtents.maxY;
		sliceExtents.minZ = -source.sliceExtents.maxX;
		sliceExtents.maxZ = -source.sliceExtents.minX;
	}

	//
	//----------------------------------
	// Handle a 90 degree right rotation
	//----------------------------------
	//
	else if (Close_Enough(origin.angularPosition.w, -origin.angularPosition.y))
	{
		solidType = Left_270[source.solidType];
		solidExtents.minX = -source.solidExtents.maxZ;
		solidExtents.maxX = -source.solidExtents.minZ;
		solidExtents.minY = source.solidExtents.minY;
		solidExtents.maxY = source.solidExtents.maxY;
		solidExtents.minZ = source.solidExtents.minX;
		solidExtents.maxZ = source.solidExtents.maxX;
		sliceExtents.minX = -source.sliceExtents.maxZ;
		sliceExtents.maxX = -source.sliceExtents.minZ;
		sliceExtents.minY = source.sliceExtents.minY;
		sliceExtents.maxY = source.sliceExtents.maxY;
		sliceExtents.minZ = source.sliceExtents.minX;
		sliceExtents.maxZ = source.sliceExtents.maxX;
	}

	//
	//-------------------------------------------------------------------------
	// Handle an arbitrary rotation.  Make sure that only vertically symetrical
	// solids get here
	//-------------------------------------------------------------------------
	//
	else if (
		source.solidType == BoxedSolid::YAxisCylinderType
		 || source.solidType == BoxedSolid::ConeType
		 || source.solidType == BoxedSolid::SphereType
	)
	{
		solidType = source.solidType;
		Verify(source.solidExtents.minX == source.sliceExtents.minX);
		Verify(source.solidExtents.maxX == source.sliceExtents.maxX);
		Verify(source.solidExtents.minY == source.sliceExtents.minY);
		Verify(source.solidExtents.maxY == source.sliceExtents.maxY);
		Verify(source.solidExtents.minZ == source.sliceExtents.minZ);
		Verify(source.solidExtents.maxZ == source.sliceExtents.maxZ);
		Vector3D center;
		center.x = (source.solidExtents.minX + source.solidExtents.maxX) * 0.5f;
		center.y = source.solidExtents.minY;
		center.z = (source.solidExtents.minZ + source.solidExtents.maxZ) * 0.5f;
		Scalar radius = center.x - source.solidExtents.minX;
		LinearMatrix m;
		m = origin.angularPosition;
		Vector3D translated;
		translated.Multiply(center, m);

		solidExtents.minX = translated.x - radius;
		solidExtents.maxX = translated.x + radius;
		solidExtents.minY = source.solidExtents.minY;
		solidExtents.maxY = source.solidExtents.maxY;
		solidExtents.minZ = translated.z - radius;
		solidExtents.maxZ = translated.z + radius;
		sliceExtents = solidExtents;
	}

	//
	//----------------
	// Otherwise, barf
	//----------------
	//
	#if defined(LAB_ONLY)
		else
		{
			Fail("Illegal solid for a non-90 degree rotation!\n");
		}
	#endif

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	TileResource::Instance(
		const TileResource &source,
		const Origin& origin
	)
{
	BoxedSolidResource::Instance(source, origin);

	//
	//-------------------------------
	// Test for a 180 degree rotation
	//-------------------------------
	//
	if (Close_Enough(fabs(origin.angularPosition.y), 1.0f))
	{
		cornerHeight[0] = source.cornerHeight[3];
		cornerHeight[1] = source.cornerHeight[2];
		cornerHeight[2] = source.cornerHeight[1];
		cornerHeight[3] = source.cornerHeight[0];
	}

	//
	//---------------------------------
	// Handle a 90 degree left rotation
	//---------------------------------
	//
	else if (Close_Enough(origin.angularPosition.w, origin.angularPosition.y))
	{
		cornerHeight[0] = source.cornerHeight[1];
		cornerHeight[1] = source.cornerHeight[3];
		cornerHeight[2] = source.cornerHeight[0];
		cornerHeight[3] = source.cornerHeight[2];
	}

	//
	//----------------------------------
	// Handle a 90 degree right rotation
	//----------------------------------
	//
	else if (Close_Enough(origin.angularPosition.w, -origin.angularPosition.y))
	{
		cornerHeight[0] = source.cornerHeight[2];
		cornerHeight[1] = source.cornerHeight[0];
		cornerHeight[2] = source.cornerHeight[3];
		cornerHeight[3] = source.cornerHeight[1];
	}
}

//#############################################################################

ResourceDescription::ResourceID
	BoxedSolidResource::CreateBoxedSolidStream(
		const char *entry_data,
		ResourceFile *file,
		const ResourceDirectories *resource_directories,
		Logical convert_boxes
	)
{
	char *filename =
		MakePathedFilename(resource_directories->collisionDirectory, entry_data);
	Register_Pointer(filename);

	NotationFile *collision_file = new NotationFile(filename);
	Unregister_Pointer(filename);
	delete filename;
	Register_Object(collision_file);


	ResourceDescription::ResourceID  collision_ID;

	if (collision_file->PageCount())
	{
//		LAB_ONLY |= collision_file->IsMarkedLabOnly();

		BoxedSolidList *box_solid_list = new BoxedSolidList;
		Register_Object(box_solid_list);

		int size = box_solid_list->AddBoxedSolids(collision_file);

		BoxedSolidResource *boxed_solid_resource =
			new BoxedSolidResource[size];
		Register_Pointer(boxed_solid_resource);

		int j = 0;


		BoundingBoxListNode *p;

		for (p = box_solid_list->GetRoot(); p; p = p->GetNextNode())
		{
			Verify(j < size);
			Check(p);
			BoxedSolid *box = (BoxedSolid *)p->GetBoundingBox();
			Check(box);

			boxed_solid_resource[j].materialType = box->materialType;

			if ((box->solidType == BoxedSolid::BlockType) && (convert_boxes))
			{
				boxed_solid_resource[j].solidType = BoxedSolid::ReducibleBlockType;
			}
			else
			{
				boxed_solid_resource[j].solidType = box->solidType;
			}

			boxed_solid_resource[j].solidExtents = *box;
			boxed_solid_resource[j].sliceExtents = p->solidSlice;
			boxed_solid_resource[j].recordLength = sizeof(boxed_solid_resource[j]);

			++j;
		}

		for (p = box_solid_list->GetRoot(); p; p = p->GetNextNode())
		{

			Check(p);
			BoxedSolid *box = (BoxedSolid *)p->GetBoundingBox();
			Check(box);

			Unregister_Object(box);
			delete(box);

		}

		ResourceDescription *res_description = file->FindResourceDescription(entry_data,
			ResourceDescription::BoxedSolidStreamResourceType);

		if(res_description == NULL)
		{
			collision_ID =
				file->AddResource(
					entry_data,
					ResourceDescription::BoxedSolidStreamResourceType,
					1,
					ResourceDescription::Preload,
					boxed_solid_resource,
					size * sizeof(BoxedSolidResource)
				)->resourceID;
		}
		else
		{
			collision_ID = res_description->resourceID;
		}

		Unregister_Pointer(boxed_solid_resource);
		delete boxed_solid_resource;

		Unregister_Object(box_solid_list);
		delete box_solid_list;

	}
	else
	{
		//----------------------------------------------
		// display warning that collision file is empty
		//----------------------------------------------
		DEBUG_STREAM << "** Collision file '" << entry_data <<
			"' empty or not found. **" << endl;
		//----------------------------------------------
		// continue to show all missing collision files
		//----------------------------------------------
	}

	Unregister_Object(collision_file);
	delete collision_file;

	return collision_ID;
}
//#############################################################################
//##########################    BoxedSolidCollision    ########################
//#############################################################################

static const int
	Cant_Occlude[BoxedSolid::SolidTypeCount]=
{
	0, 1<<(X_Axis|Y_Axis|Z_Axis), 1<<(X_Axis|Z_Axis), 0,
	1<<X_Axis, 1<<Z_Axis, 1<<X_Axis, 1<<Z_Axis,
	1<<X_Axis, 1<<Z_Axis, 1<<X_Axis, 1<<Z_Axis,
	1<<Y_Axis, 1<<Y_Axis, 1<<Y_Axis, 1<<Y_Axis,
	1<<X_Axis, 1<<Y_Axis, 1<<Z_Axis,
	0, 0
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoxedSolidCollision::Occludes(
		BoxedSolidCollision &collision,
		const Vector3D &velocity
	)
{
	Check(this);
	Check(&collision);
	Check(&velocity);

	//
	//--------------------------------------------------------------------------
	// Check against the three major axes of motion, making sure that the VTV is
	// actually moving in that component
	//--------------------------------------------------------------------------
	//
	for (int axis=X_Axis; axis<=Z_Axis; ++axis)
	{
		if (Small_Enough(velocity[axis]))
		{
			continue;
		}

		//
		//-----------------------------------------------------------------------
		// If we cannot guarantee occlusion, do some more testing to see if it is
		// possible to guarantee occlusion on a model-specific basis.  This is
		// simply to handle transversal motion across the ramp
		//
		// NOTE: This is a KLUDGE!!! This will not properly handle two ramps side
		//       by side of different sizes where the smaller ramp is in the
		//       empty portion of the larger ramp
		//-----------------------------------------------------------------------
		//
		BoxedSolid *first = GetTreeVolume();
		BoxedSolid *second = collision.GetTreeVolume();
		Check(first);
		Check(second);
		if (
			(Cant_Occlude[first->solidType] & (1<<axis))
			 && first->solidType != second->solidType
		)
		{
			continue;
		}

		//
		//----------------------------------------------------------------------
		// Figure out what face to check against, and make sure that the face of
		// the possibly occluding slice is nearer than the other slice
		//----------------------------------------------------------------------
		//
		int face = (axis<<1) + (velocity[axis] < 0.0f);
		int opposite_face = face^1;
		if (face&1)
		{
			if (first->solidType == BoxedSolid::BlockType)
			{
				if (collisionSlice[face] <= collision.collisionSlice[face])
				{
					continue;
				}
			}
			else if (
				collisionSlice[opposite_face] < collision.collisionSlice[face]
			)
			{
				continue;
			}
		}
		else
		{
			if (first->solidType == BoxedSolid::BlockType)
			{
				if (collisionSlice[face] >= collision.collisionSlice[face])
				{
					continue;
				}
			}
			else if (
				collisionSlice[opposite_face] > collision.collisionSlice[face]
			)
			{
				continue;
			}
		}

		//
		//------------------------------------------------------------------
		// Check the faces on the other axes to make sure that the occluding
		// object at least covers the extent of the occluded object
		//------------------------------------------------------------------
		//
		for (face=0; face<6; ++face)
		{
			if ((face>>1) == axis)
			{
				continue;
			}
			if (face&1)
			{
				if (collisionSlice[face] < collision.collisionSlice[face])
				{
					break;
				}
			}
			else if (collisionSlice[face] > collision.collisionSlice[face])
			{
				break;
			}
		}

		//
		//-----------------------------------------------------------------------
		// If everything checked out, then List[i] occludes List[j].  Stretch the
		// definition of List[i] to include the slice occluded if necessary
		//-----------------------------------------------------------------------
		//
		if (face == 6)
		{
			face = axis<<1;
			if (velocity[axis] > 0.0f)
			{
				++face;
				collisionSlice[face] =
					Max(collisionSlice[face], collision.collisionSlice[face]);
			}
			else
			{
				collisionSlice[face] =
					Min(collisionSlice[face], collision.collisionSlice[face]);
			}
			return True;
		}
	}
	return False;
}

//#############################################################################
//########################    BoxedSolidCollisionList    ######################
//#############################################################################

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BoxedSolidCollisionList::ReduceCollisionList(const Vector3D &velocity)
{
	Check(this);
	Check(&velocity);

	int
		i,j;

	//
	//--------------------------------------------------------------------------
	// Fuse the collision slices together into the largest possible chunks based
	// upon the collision model of each of the involved slices.  Repeat until no
	// fusings were made in the last pass or only one collision slice remains
	//--------------------------------------------------------------------------
	//
	Logical again = True;
	int collisions = GetCollisionCount();
	phantomCollisions = 0;
	while (again && collisions>1)
	{
		again = False;

		//
		//--------------------------------------------------------------------
		// Check each collision slice against the remaining slices in the list
		//--------------------------------------------------------------------
		//
		for (i=0; i<collisions; ++i)
		{
			for (j=i+1; j<collisions; ++j)
			{
				//
				//-------------------------------------------------------------
				// If the model types are different, these two slices cannot be
				// fused
				//-------------------------------------------------------------
				//
				BoxedSolidCollision *first = &(*this)[i];
				BoxedSolidCollision *second = &(*this)[j];
				if (
					first->GetTreeVolume()->solidType
					 != second->GetTreeVolume()->solidType
				)
				{
					continue;
				}

				//
				//----------------------------------------------------
				// Make sure that the faces on two sets of sides match
				//----------------------------------------------------
				//

				int matches = 0;
				int face = -1;
				for (int side=0; side<6; side += 2)
				{
					if (
						first->collisionSlice[side] == second->collisionSlice[side]
						 && first->collisionSlice[side+1]
							== second->collisionSlice[side+1]
					)
					{
						++matches;
					}
					else if (face<0)
					{
						face = side;
					}
				}
				if (matches != 2)
				{
					continue;
				}

				//
				//----------------------------------------------------------------
				// Check to make sure that the two slices have an opposing face in
				// common, which will allow the slices to be fused
				//----------------------------------------------------------------
				//
				if (
					first->collisionSlice[face] != second->collisionSlice[face+1]
					 && first->collisionSlice[face+1]
						!= second->collisionSlice[face]
				)
				{
					continue;
				}

				//
				//----------------------------------------------------------------
				// Find the face to fuse, then find out which solid description to
				// use based upon the velocity vector
				//----------------------------------------------------------------
				//
				if (first->collisionSlice[face+1] == second->collisionSlice[face])
				{
					++face;
				}
				BoxedSolid *original = NULL;
				if (
					first->GetTreeVolume() != second->GetTreeVolume()
					 && second->Occludes(*first, velocity)
				)
				{
					original = first->GetTreeVolume();
					first->treeVolume = second->GetTreeVolume();
				}

				//
				//-------------------------
				// Fuse the blocks together
				//-------------------------
				//
				first->collisionSlice[face] = second->collisionSlice[face];

				//
				//-----------------------------------------------
				// Erase the second slice from the collision list
				//-----------------------------------------------
				//
				for (int k=j+1; k<collisions; ++k)
					listStart[k-1] = listStart[k];
				++phantomCollisions;
				listStart[--collisions].treeVolume = original;
				--j;
				again = True;
			}
		}
	}

	//
	//-----------------------------------------------------------------------
	// Check to see if any of the collisions occlude one another if more than
	// one collision slice remains
	//-----------------------------------------------------------------------
	//
	if (collisions>1)
	{
		//
		//--------------------------
		// Erase the occlusion table
		//--------------------------
		//
		Logical* occluded = new Logical[collisions];
		Register_Pointer(occluded);
		BoxedSolid **volumes = new BoxedSolid* [collisions];
		Register_Pointer(volumes);
		for (i=0; i<collisions; ++i)
			occluded[i] = False;

		//
		//------------------------------------------------------------
		// Check each slice to see if it is occluded by something else
		//------------------------------------------------------------
		//
		for (i=0; i<collisions; ++i)
		{
			for (j=0; j<collisions; ++j)
			{
				if (i==j || occluded[j])
				{
					continue;
				}
				if ((*this)[i].Occludes((*this)[j], velocity))
				{
					occluded[j] = True;
				}
			}
		}

		//
		//---------------------------------------------------------
		// Check the occlusion table, and erase any occluded slices
		//---------------------------------------------------------
		//
		j = 0;
		int occludeds = 0;
		for (i=0; i<collisions; ++i)
		{
			if (!occluded[i])
			{
				if (i>j)
				{
					listStart[j] = listStart[i];
				}
				++j;
			}
			else
			{
				volumes[++occludeds] = (BoxedSolid*)listStart[i].treeVolume;
			}
		}
		i = 0;
		while (j<collisions)
		{
			listStart[j++].treeVolume = volumes[i++];
		}

		phantomCollisions += occludeds;
		Unregister_Pointer(volumes);
		delete[] volumes;
		Unregister_Pointer(occluded);
		delete[] occluded;
	}
}

//#############################################################################
//##########################    BoxedReducibleBlock    ########################
//#############################################################################

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

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoxedReducibleBlock::TestInstance() const
{
	return solidType == ReducibleBlockType;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoxedReducibleBlock::IntersectsBounded(const ExtentBox &extents)
{
	Check(this);
	Check(&extents);

	return ExtentBox::Intersects(extents);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
	BoxedReducibleBlock::ContainsBounded(const Point3D &point)
{
	Check(this);
	Check(&point);

	return ExtentBox::Contains(point);
}

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

	if (
		minX <= point.x && maxX >= point.x && minY <= point.y
		 && minZ <= point.z && maxZ >= point.z
	)
	{
		Scalar result = point.y - maxY;
		return Max(result, 0.0f);
	}
	else
	{
		return -1.0f;
	}
}

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

	Scalar
		perpendicular,
		drift,
		distance;

	//
	//--------------------
	// Set up for the test
	//--------------------
	//
	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 (distance > enters)
			{
				enters = distance;
			}
			if (enters > 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 (distance < leaves)
			{
				leaves = distance;
			}
			if (leaves < 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 (enters <= leaves)
	{
		line->length = Max(enters, 0.0f);
		return True;
	}
	else
	{
		return False;
	}
}

//#############################################################################
//###########################    BoxedSolidList    ############################
//#############################################################################

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

int
	BoxedSolidList::AddBoxedSolids(NotationFile *notation_file)
{
	char page_name[50];
	int solidnumber = 0;
	int type;

	BoxedSolidResource boxed_solid_resource;
	BoxedSolid	*boxed_solid;


	if (!notation_file->GetEntry("COLLISION", "count", &solidnumber))
	{
			DEBUG_STREAM << "No 'COLLISION' entry!!!!!" << endl;
			Exit(AbortExitCodeID);
	}

	for (int j=0; j < solidnumber; ++j)
	{
		sprintf(page_name, "volume %d", j);

		if (!notation_file->GetEntry(page_name, "TypeNumber", &type))
		{
			DEBUG_STREAM << "No solid at collision number : " << j << endl;
			Exit(AbortExitCodeID);
		}
		boxed_solid_resource.solidType = (BoxedSolid::Type)type;

		if (!notation_file->GetEntry(page_name, "MaterialNumber", &type))
		{
			DEBUG_STREAM << "No material at collision number : " << j << endl;
			Exit(AbortExitCodeID);
		}

		boxed_solid_resource.materialType = (BoxedSolid::Material)type;

		int errorvalue = 0;

		errorvalue |= !notation_file->GetEntry(page_name, "extminX", &(boxed_solid_resource.solidExtents.minX));
		errorvalue |= !notation_file->GetEntry(page_name, "extmaxX", &(boxed_solid_resource.solidExtents.maxX));
		errorvalue |= !notation_file->GetEntry(page_name, "extminY", &(boxed_solid_resource.solidExtents.minY));
		errorvalue |= !notation_file->GetEntry(page_name, "extmaxY", &(boxed_solid_resource.solidExtents.maxY));
		errorvalue |= !notation_file->GetEntry(page_name, "extminZ", &(boxed_solid_resource.solidExtents.minZ));
		errorvalue |= !notation_file->GetEntry(page_name, "extmaxZ", &(boxed_solid_resource.solidExtents.maxZ));

		if ( errorvalue )
		{
			DEBUG_STREAM << "Collision number : " << j
			 << " does not have valid extents\n";
			Exit(AbortExitCodeID);
		}

		errorvalue = 0;
		errorvalue |= !notation_file->GetEntry(page_name, "slcminX", &(boxed_solid_resource.sliceExtents.minX));
		errorvalue |= !notation_file->GetEntry(page_name, "slcmaxX", &(boxed_solid_resource.sliceExtents.maxX));
		errorvalue |= !notation_file->GetEntry(page_name, "slcminY", &(boxed_solid_resource.sliceExtents.minY));
		errorvalue |= !notation_file->GetEntry(page_name, "slcmaxY", &(boxed_solid_resource.sliceExtents.maxY));
		errorvalue |= !notation_file->GetEntry(page_name, "slcminZ", &(boxed_solid_resource.sliceExtents.minZ));
		errorvalue |= !notation_file->GetEntry(page_name, "slcmaxZ", &(boxed_solid_resource.sliceExtents.maxZ));

		if ( errorvalue )
		{
			DEBUG_STREAM << "Collision number : " << j
			 << " does not have valid slice extents\n";
			Exit(AbortExitCodeID);
		}


		boxed_solid =
			BoxedSolid::MakeBoxedSolid(&boxed_solid_resource, NULL, NULL);

		Register_Object(boxed_solid);

		Add(boxed_solid, boxed_solid_resource.sliceExtents);
	}

	return solidnumber;
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//


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

