#include "munga.h"
#pragma hdrstop

#include "boxsolid.h"
#include "vector3d.h"

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

enum {
	X_Axis_Bit = 1,
	Y_Axis_Bit = 2,
	Z_Axis_Bit = 4
};

enum {
	Min_Side		= 0x001,
	Inside		= 0x002,
	Max_Side		= 0x004,
	Both_Sides	= Min_Side|Max_Side,
	Shift			= 3,
	X_Shift		= 2 * Shift,
	Min_X_Side	= Min_Side << X_Shift,
	Inside_X		= Inside << X_Shift,
	Max_X_Side	= Max_Side << X_Shift,
	X_Mask		= Min_X_Side|Inside_X|Max_X_Side,
	Y_Shift		= Shift,
	Min_Y_Side	= Min_Side << Y_Shift,
	Inside_Y		= Inside << Y_Shift,
	Max_Y_Side	= Max_Side << Y_Shift,
	Y_Mask		= Min_Y_Side|Inside_Y|Max_Y_Side,
	Z_Shift		= 0,
	Min_Z_Side	= Min_Side << Z_Shift,
	Inside_Z		= Inside << Z_Shift,
	Max_Z_Side	= Max_Side << Z_Shift,
	Z_Mask		= Min_Z_Side|Inside_Z|Max_Z_Side
};

int
	Legal_To_Fuse[BoxedSolid::SolidTypeCount]=
{
	X_Axis_Bit|Y_Axis_Bit|Z_Axis_Bit, 0, 0, 0,
	X_Axis_Bit, Z_Axis_Bit, X_Axis_Bit, Z_Axis_Bit,
	X_Axis_Bit, Z_Axis_Bit, X_Axis_Bit, Z_Axis_Bit,
	Y_Axis_Bit, Y_Axis_Bit, Y_Axis_Bit, Y_Axis_Bit,
	X_Axis_Bit, Y_Axis_Bit, Z_Axis_Bit
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BoundingBoxList::Reduce()
{
	Check(this);

	BoundingBoxListNode
		*i, *j, *previous;

	//
	//--------------------------------------------------------------------------
	// 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;
	while (again)
	{
		again = False;

		//
		//--------------------------------------------------------------------
		// Check each collision slice against the remaining slices in the list
		//--------------------------------------------------------------------
		//
		for (i=root; i; i = i->previousNode)
		{
			Check(i);
			previous = i;
			j = i->previousNode;
			while (j)
			{
				Check(j);
				//
				//-------------------------------------------------------------
				// If the model types are different, these two slices cannot be
				// fused
				//-------------------------------------------------------------
				//
				BoundingBox *first = i->boundingBox;
				BoundingBox *second = j->boundingBox;

				//
				//----------------------------------------------------
				// 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)[side] == (*second)[side]
						 && (*first)[side+1] == (*second)[side+1]
					)
					{
						++matches;
					}
					else if (face<0)
					{
						face = side;
					}
				}
				if (matches != 2)
				{
Next_Solid:
					previous = j;
					j = j->previousNode;
					continue;
				}

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

				//
				//----------------------------------------------------
				// Find the face to fuse, and fuse the blocks together
				//----------------------------------------------------
				//
				if ((*first)[face+1] == (*second)[face])
				{
					++face;
				}
				(*first)[face] = (*second)[face];

				//
				//-----------------------------------------------
				// Erase the second solid from the collision list
				//-----------------------------------------------
				//
            --nodeCount;
				if (previous)
				{
					previous->previousNode = j->previousNode;
					Unregister_Object(j);
					delete(j);
					j = previous->previousNode;
				}
				else
				{
					root = j->previousNode;
					Unregister_Object(j);
					delete(j);
					j = root;
				}
				again = True;
			}
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BoundingBoxList::SortForTree()
{
	Check(this);

	int
		i, j;
	BoundingBoxListNode
		*p, *q;

	//
	//-----------------------------------------------------------------------
	// Find out how many collision solids are in the list, and figure out the
	// total extent box so that we can set the traversal order correctly
	//-----------------------------------------------------------------------
	//
	ExtentBox map_extents;
	map_extents.minX = 65535.9999f;
	map_extents.minY = 65535.9999f;
	map_extents.minZ = 65535.9999f;
	map_extents.maxX = -65535.9999f;
	map_extents.maxY = -65535.9999f;
	map_extents.maxZ = -65535.9999f;
	for (p=root; p; p = p->previousNode)
	{
		Check(p);
		if (p->boundingBox->minX < map_extents.minX)
		{
			map_extents.minX = p->boundingBox->minX;
		}
		if (p->boundingBox->maxX > map_extents.maxX)
		{
			map_extents.maxX = p->boundingBox->maxX;
		}

		if (p->boundingBox->minY < map_extents.minY)
		{
			map_extents.minY = p->boundingBox->minY;
		}
		if (p->boundingBox->maxY > map_extents.maxY)
		{
			map_extents.maxY = p->boundingBox->maxY;
		}

		if (p->boundingBox->minZ < map_extents.minZ)
		{
			map_extents.minZ = p->boundingBox->minZ;
		}
		if (p->boundingBox->maxZ > map_extents.maxZ)
		{
			map_extents.maxZ = p->boundingBox->maxZ;
		}
	}
	if (
		map_extents.maxZ - map_extents.minZ
		 > map_extents.maxX - map_extents.minX
	)
	{
		isXMajorAxis = False;
	}

	//
	//----------------------------------------------------
	// Allocate a scoreboard and fill it with sorting info
	//----------------------------------------------------
	//
	Verify(!scoreBoard);
	scoreBoard = new int[nodeCount * nodeCount];
	Register_Pointer(scoreBoard);
	int *score = scoreBoard;

	boundingBoxIndex = new BoundingBox* [nodeCount];
	Register_Pointer(boundingBoxIndex);

	for (p=root,i=0; p; p = p->previousNode,++i)
	{
		Check(p);
		BoundingBox *first = p->boundingBox;
		boundingBoxIndex[i] = first;
		for (q=root,j=0; q; q = q->previousNode,++j,++score)
		{

			//
			//------------------------------
			// Ignore scoring against itself
			//------------------------------
			//
			Check(q);
			*score = 0;
			if (p == q)
			{
				continue;
			}
			BoundingBox *second = q->boundingBox;

			//
			//--------------------------------------------------------------------
			// Step through the three axes and set the flags showing how the
			// second solid is split up by the first solid.  Make sure that if the
			// second solid completely covers the first that this inside bit is
			// set correctly
			//--------------------------------------------------------------------
			//
			for (int axis = X_Axis; axis <= Z_Axis; --axis)
			{
				*score <<= Shift;
				int face = axis << 1;
				if ((*second)[face] < (*first)[face])
				{
					*score |= Min_Side;
				}
				else if ((*second)[face] < (*first)[face+1])
				{
					*score |= Inside;
				}
				if ((*second)[face+1] > (*first)[face+1])
				{
					*score |= Max_Side;
				}
				else if ((*second)[face+1] > (*first)[face])
				{
					*score |= Inside;
				}

				if ((*score & Both_Sides) == Both_Sides)
				{
					*score |= Inside;
				}
			}
		}
	}

	//
	//--------------------------------------------------------------------------
	// Create a new BoxedSolid list for results to go into, and a third in which
	// to pass the active list
	//--------------------------------------------------------------------------
	//
	BoundingBoxList active;
	active.root = root;
	active.nodeCount = nodeCount;
	root = NULL;
	BoundingBoxTree tree_so_far;

	Sort(
		active,
		map_extents,
		tree_so_far
	);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BoundingBoxList::Sort(
		BoundingBoxList &,//active,
		ExtentBox &,//map_extents,
		BoundingBoxTree &//tree_so_far
	)
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BoxedSolidList::Reduce()
{
	Check(this);

	BoundingBoxListNode
		*i, *j, *previous;

	//
	//--------------------------------------------------------------------------
	// 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;
	while (again)
	{
		again = False;

		//
		//--------------------------------------------------------------------
		// Check each collision slice against the remaining slices in the list
		//--------------------------------------------------------------------
		//
		for (i=root; i; i = i->previousNode)
		{
			Check(i);
			previous = i;
			j = i->previousNode;
			while (j)
			{
				Check(j);
				//
				//-------------------------------------------------------------
				// If the model types are different, these two slices cannot be
				// fused
				//-------------------------------------------------------------
				//
				BoxedSolid *first = Cast_Object(BoxedSolid*, i->boundingBox);
				BoxedSolid *second = Cast_Object(BoxedSolid*, j->boundingBox);
				if (first->solidType != second->solidType)
				{
Next_Solid:
					previous = j;
					j = j->previousNode;
					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)[side] == (*second)[side]
						 && (*first)[side+1] == (*second)[side+1]
					)
					{
						++matches;
					}
					else if (face<0)
					{
						face = side;
					}
				}
				if (matches != 2)
				{
					goto Next_Solid;
				}

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

				//
				//---------------------------------------------------------------
				// Make sure that this type of solid is legal to be fused in this
				// direction
				//---------------------------------------------------------------
				//
				if (!(Legal_To_Fuse[first->solidType] & (face>>1)))
				{
					goto Next_Solid;
				}

				//
				//----------------------------------------------------
				// Find the face to fuse, and fuse the blocks together
				//----------------------------------------------------
				//
				if ((*first)[face+1] == (*second)[face])
				{
					++face;
				}
				(*first)[face] = (*second)[face];

				//
				//-----------------------------------------------
				// Erase the second solid from the collision list
				//-----------------------------------------------
				//
				if (previous)
				{
					previous->previousNode = j->previousNode;
					Unregister_Object(j);
					delete(j);
					j = previous->previousNode;
				}
				else
				{
					root = j->previousNode;
					Unregister_Object(j);
					delete(j);
					j = root;
				}
				again = True;
			}
		}
	}
}
