#include "ProxyHeaders.hpp"

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
SortInterestZonesProcess::SortInterestZonesProcess(
	Stuff::NotationFile *config_file,
	const char* set_path,
	const Stuff::DynamicArrayOf<Stuff::ExtentBox> &zone_array,
	BinSortProcess *bin_process,
	FindErrorsProcess *find_errors
):
	Process(config_file),
	zoneArray(zone_array)
{
	Check_Object(config_file);
	Check_Pointer(set_path);
	Check_Object(&zone_array);

	configFile = config_file;
	setDirectory = set_path;
	zoneCounter = 0;
	setContents = NULL;
	trimmingTolerance = 1e-2f;
	Page *page = config_file->FindPage("InterestBSP");
	if (page)
		page->GetEntry("TrimmingTolerance", &trimmingTolerance);
	sortProcess = bin_process;
	errorProcess = find_errors;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	SortInterestZonesProcess::SplitCallback(GenericProxy *proxy)
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	SortInterestZonesProcess::CreateCallback(GenericProxy *proxy)
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
FlattenHierarchyProcess*
	SortInterestZonesProcess::MakeFlattenHierarchyProcess(
		Stuff::NotationFile *config_file,
		GroupProxy *parent_group
	)
{
	Check_Object(this);
	return new FlattenHierarchyProcess(config_file, parent_group);
}

//
//############################################################################
//###########################    InterestBSP    ##############################
//############################################################################
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
InterestBSP::~InterestBSP()
{
	if (insideNode)
	{
		Check_Object(insideNode);
		delete insideNode;
	}
	if (outsideNode)
	{
		Check_Object(outsideNode);
		delete outsideNode;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
InterestBSP*
	InterestBSP::CreateBSPTree(SortInterestZonesProcess *process)
{
	Check_Object(process);

	//
	//-----------------------------------
	// Create the candidate set of planes
	//-----------------------------------
	//
	DynamicArrayOf<Plane> planes;
	Find_Planes_Of_Boxes(&planes, process->zoneArray);

	//
	//------------------------------------------------------------
	// Create the scoreboard, and test each box against each plane
	//------------------------------------------------------------
	//
	unsigned plane_count = planes.GetLength();
	unsigned zone_count = process->zoneArray.GetLength();
	DynamicArrayOf<int> scoreboard(plane_count * zone_count);
	int *entry = &scoreboard[0];
	for (unsigned plane=0; plane<plane_count; ++plane)
	{
		for (unsigned zone=0; zone<zone_count; ++zone, ++entry)
		{
			Verify(
				static_cast<unsigned>(entry - &scoreboard[0])
				 < plane_count * zone_count
			);
			if (
				planes[plane].ContainsAllOf(
					process->zoneArray[zone],
					process->planeThicknessTolerance
				)
			)
				*entry = ContainsAll;
			else if (
				planes[plane].ContainsSomeOf(
					process->zoneArray[zone],
					process->planeThicknessTolerance
				)
			)
				*entry = Splits;
			else
				*entry = ContainsNone;
		}
	}

	//
	//-----------------------------------------
	// Create an index into the zones structure
	//-----------------------------------------
	//
	DynamicArrayOf<unsigned> index(zone_count);
	for (unsigned zone=0; zone<zone_count; ++zone)
		index[zone] = zone;

	//
	//------------------------------------------------
	// Start the BSP process, returning the first node
	//------------------------------------------------
	//
	InterestBSP *tree =
		MakeBSPNode(process, scoreboard, planes, index, zone_count);
	Check_Object(tree);
	return tree;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
InterestBSP*
	InterestBSP::MakeBSPNode(
		SortInterestZonesProcess *process,
		const Stuff::DynamicArrayOf<int> &scoreboard,
		const Stuff::DynamicArrayOf<Stuff::Plane> &planes,
		Stuff::DynamicArrayOf<unsigned> &index,
		unsigned zone_count
	)
{
	Check_Object(&scoreboard);
	Check_Object(&planes);
	unsigned plane_count = planes.GetLength();
	Verify(plane_count > 0);
	Verify(zone_count > 0);
	Verify(plane_count * zone_count == scoreboard.GetLength());
	Check_Object(&index);
	unsigned active_count = index.GetLength();
	Verify(active_count <= zone_count);
	Verify(active_count > 0);

	//
	//---------------------------------------------------------------------
	// If the active zone list only contains one zone then we create a leaf
	// BSP node that holds it
	//---------------------------------------------------------------------
	//
	InterestBSP *node = new InterestBSP;
	if (active_count == 1)
	{
		node->zoneIndex = index[0];
		return node;
	}
	node->zoneIndex = zone_count;

	//
	//-------------------------------------------------------------------------
	// Our first step is to build a summary of the scoreboard information on
	// all the active zones across all the separation planes.  The summary will
	// be based upon the balance achieved by that particular plane on the
	// current set of zones
	//-------------------------------------------------------------------------
	//
	DynamicArrayOf<int> summary(plane_count);
	unsigned plane;
	for (plane=0; plane<plane_count; ++plane)
	{
		const int *row = &scoreboard[plane * zone_count];

		//
		//----------------------------------------------------------------------
		// Spin through each box, looking for the balance information.  Boxes on
		// the inside of the plane will be positive, boxes on the outside will
		// be negative, and splits will abort the summary and set the plane as
		// totally unbalanced
		//----------------------------------------------------------------------
		//
		summary[plane] = 0;
		for (unsigned i=0; i<active_count; ++i)
		{
			unsigned zone = index[i];
			Verify(zone < zone_count);
			if (row[zone] == ContainsAll)
				++summary[plane];
			else if (row[zone] == ContainsNone)
				--summary[plane];
			else
			{
				summary[plane] = active_count;
				break;
			}
		}
	}

	//
	//-----------------------------------------
	// Now we must find the best balanced plane
	//-----------------------------------------
	//
	unsigned best_plane = plane_count;
	int best_balance = active_count;
	for (plane=0; plane<plane_count; ++plane)
	{
		if (Abs(summary[plane]) < Abs(best_balance))
		{
			best_balance = summary[plane];
			best_plane = plane;
		}
	}
	if (best_plane == plane_count)
		STOP(("Couldn't find splitting plane!"));

	//
	//------------------------------------------------------------
	// Figure out the sizes of the inside and outside index arrays
	//------------------------------------------------------------
	//
	node->dividingPlane = planes[best_plane];
	Verify(!((active_count + best_balance)&1));
	unsigned in_size = (active_count + best_balance) >> 1;
	unsigned out_size = active_count - in_size;
	DynamicArrayOf<unsigned> in(in_size);
	DynamicArrayOf<unsigned> out(out_size);

	//
	//----------------------------------------------------------------------
	// Run through the list, moving the index values to the appropriate list
	//----------------------------------------------------------------------
	//
	unsigned in_count = 0;
	unsigned out_count = 0;
	const int *row = &scoreboard[best_plane * zone_count];
	for (unsigned i=0; i<active_count; ++i)
	{
		unsigned zone = index[i];
		Verify(zone < zone_count);
		if (row[zone] == ContainsAll)
			in[in_count++] = zone;
		else
		{
			Verify(row[zone] == ContainsNone);
			out[out_count++] = zone;
		}
	}
	Verify(in_count - out_count == best_balance);

	//
	//-------------------------------------
	// Run the BSP process on each sub-list
	//-------------------------------------
	//
	node->insideNode =
		MakeBSPNode(process, scoreboard, planes, in, zone_count);
	Check_Object(node->insideNode);
	node->outsideNode =
		MakeBSPNode(process, scoreboard, planes, out, zone_count);
	Check_Object(node->outsideNode);
	return node;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	InterestBSP::SplitScene(
		SortInterestZonesProcess *process,
		SceneProxy *scene
	)
{
	Check_Object(this);
	Check_Object(process);
	Check_Object(scene);

	//
	//---------------------------------------
	// Make sure that the process says its OK
	//---------------------------------------
	//
	process->SplitCallback(scene);
	if (!process->continueProcess)
		return;

	//
	//----------------------------------------------------------------------
	// We are not a leaf node, so find out how many children we need to test
	// against the BSP plane.  If there are none, just return
	//----------------------------------------------------------------------
	//
	unsigned child_count = scene->GetChildCount();
	if (!child_count)
		return;

	//
	//--------------------------------------------------------------------
	// If there are no children of this BSP node, we need to write out the
	// interest zone info
	//--------------------------------------------------------------------
	//
	if (!insideNode && !outsideNode)
	{
		CreateInterestZone(process, scene);
		return;
	}

	//
	//------------------------------------------------------------------------
	// Create the two new group nodes that the meshes will be sorted into,
	// then run through the original children and sort each one into the
	// appropriate set
	//------------------------------------------------------------------------
	//
	GroupProxy *inside_set = scene->AppendNewGroupProxy();
	Check_Object(inside_set);
	GroupProxy *outside_set = scene->AppendNewGroupProxy();
	Check_Object(outside_set);
	ChildProxy *child = scene->UseFirstChildProxy();
	for (unsigned i=0; i<child_count; ++i)
	{
		Check_Object(child);
		ChildProxy *next = child->UseNextSiblingProxy();
		if (
			SplitMesh(
				process,
				Cast_Object(PolygonMeshProxy*, child),
				inside_set,
				outside_set
			)
		)
			child->DetachReference();
		child = next;
		if (!process->continueProcess)
			break;
	}
	if (child)
		child->DetachReference();
	if (!process->continueProcess)
	{
		inside_set->DetachReference();
		outside_set->DetachReference();
		return;
	}

	//
	//-----------------------------------------------------------
	// Now, split each group against the next lower BSP nodes
	//-----------------------------------------------------------
	//
	Check_Object(insideNode);
	if (insideNode->SplitGroup(process, inside_set))
		inside_set->DetachReference();
	if (!process->continueProcess)
		outside_set->DetachReference();
	else
	{
		Check_Object(outsideNode);
		if (outsideNode->SplitGroup(process, outside_set))
			outside_set->DetachReference();
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
	InterestBSP::SplitGroup(
		SortInterestZonesProcess *process,
		GroupProxy *group
	)
{
	Check_Object(this);
	Check_Object(process);
	Check_Object(group);

	//
	//---------------------------------------
	// Make sure that the process says its OK
	//---------------------------------------
	//
	process->SplitCallback(group);
	if (!process->continueProcess)
		return true;

	//
	//----------------------------------------------------------------------
	// We are not a leaf node, so find out how many children we need to test
	// against the BSP plane.  If there are none, just return
	//----------------------------------------------------------------------
	//
	unsigned child_count = group->GetChildCount();
	if (!child_count)
	{
		group->Destroy();
		return false;
	}

	//
	//--------------------------------------------------------------------
	// If there are no children of this BSP node, we need to write out the
	// interest zone info
	//--------------------------------------------------------------------
	//
	if (!insideNode && !outsideNode)
		return CreateInterestZone(process, group);

	//
	//------------------------------------------------------------------------
	// Create the two new group nodes that the meshes will be sorted into,
	// then run through the original children and sort each one into the
	// appropriate set
	//------------------------------------------------------------------------
	//
	GroupProxy *inside_set = group->AppendNewGroupProxy();
	Check_Object(inside_set);
	GroupProxy *outside_set = group->AppendNewGroupProxy();
	Check_Object(outside_set);
	ChildProxy *child = group->UseFirstChildProxy();
	for (unsigned i=0; i<child_count; ++i)
	{
		Check_Object(child);
		ChildProxy *next = child->UseNextSiblingProxy();
		if (
			SplitMesh(
				process,
				Cast_Object(PolygonMeshProxy*, child),
				inside_set,
				outside_set
			)
		)
			child->DetachReference();
		child = next;
		if (!process->continueProcess)
			break;
	}
	if (child)
		child->DetachReference();
	if (!process->continueProcess)
	{
		inside_set->DetachReference();
		outside_set->DetachReference();
		return true;
	}

	//
	//-----------------------------------------------------------
	// Now, split each group against the next lower BSP nodes
	//-----------------------------------------------------------
	//
	Check_Object(insideNode);
	if (insideNode->SplitGroup(process, inside_set))
		inside_set->DetachReference();
	if (!process->continueProcess)
		outside_set->DetachReference();
	else
	{
		Check_Object(outsideNode);
		if (outsideNode->SplitGroup(process, outside_set))
			outside_set->DetachReference();
	}
	return true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
static inline int
	Find_Side(
		Scalar distance,
		Scalar thickness
	)
{
	if (distance > thickness)
		return 1;
	else if (distance < -thickness)
		return -1;
	return 0;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
	InterestBSP::SplitMesh(
		SortInterestZonesProcess *process,
		PolygonMeshProxy *mesh,
		GroupProxy *inside_set,
		GroupProxy *outside_set
	)
{
	Check_Object(this);
	Check_Object(mesh);
	Check_Object(inside_set);
	Check_Object(outside_set);

	//
	//---------------------------------------
	// Make sure that the process says its OK
	//---------------------------------------
	//
	process->SplitCallback(mesh);
	if (!process->continueProcess)
		return true;

	//
	//-------------------------------------------------------------
	// Rotate the separating plane into the local space of the mesh
	//-------------------------------------------------------------
	//
	LinearMatrix4D local_to_world;
	mesh->GetLocalToParent(&local_to_world);
	LinearMatrix4D world_to_local;
	world_to_local.Invert(local_to_world);
	Plane local_plane;
	local_plane.Multiply(dividingPlane, world_to_local);

	//
	//------------------------------------------------------------------------
	// The first task is to find out which side of the plane the mesh is to
	// be on.  We do this by looking at all the vertices and categorizing each
	// one
	//------------------------------------------------------------------------
	//
	int type = 0;
	Scalar thickness = process->trimmingTolerance;
	DynamicArrayOf<VertexProxy*> vertices;
	unsigned vertex_count = mesh->UseVertexArray(&vertices);
	unsigned i;
	VertexProxy *vertex;
	for (i=0; i<vertex_count; ++i)
	{
		vertex = vertices[i];
		Check_Object(vertex);
		Point3D position;
		vertex->GetPosition(&position);
		Scalar distance = local_plane.GetDistanceTo(position);
		if (distance < -thickness)
			type |= 1;
		else if (distance > thickness)
			type |= 2;
		if (type == 3)
			break;
	}
	mesh->DetachArrayReferences(&vertices);
	Verify(mesh->GetReferenceCount() == 1);

	//
	//-------------------------------------------------------------------------
	// Put the mesh in the correct bin based upon the index status.  Make sure
	// to put empty or coplanar meshes on the inside mesh
	//-------------------------------------------------------------------------
	//
	if (type<2)
	{
		mesh->TransferAndAppendToParentGroup(inside_set);
		return true;
	}

	//
	//-----------------------------------------------------------------------
	// Case 2 means that the mesh fits totally outside the plane's half-space
	//-----------------------------------------------------------------------
	//
	else if (type==2)
	{
		mesh->TransferAndAppendToParentGroup(outside_set);
		return true;
	}

	//
	//-------------------------------------------------------------------------
	// If we get here, then we have to split the polygon mesh apart, so get the
	// list of polygons
	//-------------------------------------------------------------------------
	//
	Verify(type == 3);
	DynamicArrayOf<PolygonProxy*> old_polygons;
	unsigned old_polygon_count = mesh->UsePolygonArray(&old_polygons);

	//
	//---------------------------
	// Create the polygon buffers
	//---------------------------
	//
	DynamicArrayOf<PolygonProxy*> inside_polygons(old_polygon_count);
	unsigned inside_polygon_count = 0;
	DynamicArrayOf<PolygonProxy*> outside_polygons(old_polygon_count);
	unsigned outside_polygon_count = 0;
	DynamicArrayOf<VertexProxy*> new_vertices(2*old_polygon_count);
	unsigned new_vertex_count = 0;

	//
	//-----------------------------------------------------------------------
	// Spin through the polygons, looking at each polygon to see if it can be
	// transferred as is to one side or the other
	//-----------------------------------------------------------------------
	//
	for (i=0; i<old_polygon_count; ++i)
	{
		PolygonProxy *polygon = old_polygons[i];
		Check_Object(polygon);
		type = 0;
		DynamicArrayOf<IndexProxy*> old_indices;
		unsigned old_index_count = polygon->UseIndexArray(&old_indices);
		Verify(old_index_count >= 3);
		DynamicArrayOf<Scalar> distances(old_index_count);
		unsigned j;
		for (j=0; j<old_index_count; ++j)
		{
			Check_Object(old_indices[j]);
			vertex = old_indices[j]->GetVertexProxy();
			Check_Object(vertex);
			Point3D position;
			vertex->GetPosition(&position);
			distances[j] = local_plane.GetDistanceTo(position);
			if (distances[j] < -thickness)
				type |= 1;
			else if (distances[j] > thickness)
				type |= 2;
		}

		//
		//-----------------------------------------------------------------
		// If the polygon is co-planar or inside, put it on the  inside set
		//-----------------------------------------------------------------
		//
		if (type<2)
		{
			polygon->AttachReference();
			inside_polygons[inside_polygon_count++] = polygon;
			polygon->DetachArrayReferences(&old_indices);
			continue;
		}

		//
		//-----------------------------------------------------
		// If the polygon is outside, put it on the outside set
		//-----------------------------------------------------
		//
		if (type==2)
		{
			polygon->AttachReference();
			outside_polygons[outside_polygon_count++] = polygon;
			polygon->DetachArrayReferences(&old_indices);
			continue;
		}

		//
		//----------------------------------------------------------------------
		// We have to split this polygon, which will basically mean that we will
		// build two new polygons, one per side
		//----------------------------------------------------------------------
		//
		Verify(type==3);
		DynamicArrayOf<VertexProxy*> inside_indices(old_index_count+1);
		unsigned inside_index_count = 0;
		DynamicArrayOf<VertexProxy*> outside_indices(old_index_count+1);
		unsigned outside_index_count = 0;

		//
		//---------------------------
		// Set up the trailing vertex
		//---------------------------
		//
		Scalar distance_a = distances[old_index_count-1];
		int side_a = Find_Side(distance_a, thickness);

		//
		//--------------------------
		// Set up the testing vertex
		//--------------------------
		//
		Scalar distance_b = distances[0];
		int side_b = Find_Side(distance_b, thickness);
		Check_Object(old_indices[0]);
		VertexProxy *vertex_b = old_indices[0]->GetVertexProxy();
		Check_Object(vertex_b);
		Point3D position_b;
		vertex_b->GetPosition(&position_b);

		//
		//--------------------------
		// Set up the leading vertex
		//--------------------------
		//
		unsigned lead_index = 1;
		Scalar distance_c = distances[lead_index];
		int side_c = Find_Side(distance_c, thickness);
		Check_Object(old_indices[lead_index]);
		VertexProxy *vertex_c = old_indices[lead_index]->GetVertexProxy();
		Check_Object(vertex_c);
		Point3D position_c;
		vertex_c->GetPosition(&position_c);

		//
		//-------------------------
		// Loop through the indices
		//-------------------------
		//
		Scalar welding = process->duplicateVertexTolerance;
		int crossings = 0;
		for (j=0; j<old_index_count; ++j)
		{
			if (j)
			{
				side_b = side_c;
				vertex_b = vertex_c;
				position_b = position_c;
				distance_b = distance_c;
				if (++lead_index == old_index_count)
					lead_index = 0;
				Check_Object(old_indices[lead_index]);
				vertex_c = old_indices[lead_index]->GetVertexProxy();
				Check_Object(vertex_c);
				vertex_c->GetPosition(&position_c);
				distance_c = distances[lead_index];
				side_c = Find_Side(distance_c, thickness);
			}

			//
			//-------------------------------------------------------------------
			// Assign this vertex to either or both the inside or outside polygon
			// (it can go to both if it is on the plane
			//-------------------------------------------------------------------
			//
			if (side_b < 1)
				inside_indices[inside_index_count++] = vertex_b;
			if (side_b > -1)
				outside_indices[outside_index_count++] = vertex_b;

			//
			//------------------------------------------------
			// Check to see if this index is a splitting index
			//------------------------------------------------
			//
Analyze_Vertex:
			if (!side_b && side_a && side_a == -side_c)
			{
				++crossings;
				Verify(crossings < 3);
				side_a = side_b;
			}

			//
			//----------------------------------------------------------------
			// Check to see if the edge from b to c needs to be split.  If so,
			// calculate where the crossing would happen
			//----------------------------------------------------------------
			//
			else if (side_b && side_b == -side_c)
			{
				Scalar lerp = distance_b / (distance_b - distance_c);
				Point3D position;
				position.Lerp(position_b, position_c, lerp);

				//
				//---------------------------------------------------------------
				// Before we actually do the split, we need to make sure that new
				// vertex doesn't effectively duplicate either of the existing
				// vertices
				//---------------------------------------------------------------
				//
				if (
					Small_Enough(lerp)
					 || Close_Enough(position, position_b, welding)
				)
				{
					if (side_b > 0)
						inside_indices[inside_index_count++] = vertex_b;
					else
						outside_indices[outside_index_count++] = vertex_b;
					side_b = 0;
					distance_b = 0.0f;
					goto Analyze_Vertex;
				}
				else if (
					Close_Enough(lerp, 1.0f)
					 || Close_Enough(position, position_c, welding)
				)
				{
					side_c = 0;
					distance_c = 0.0f;
					goto Analyze_Vertex;
				}

				//
				//----------------------
				// Create the new vertex
				//----------------------
				//
				vertex = VertexProxy::MakeProxy();
				Check_Object(vertex);
				new_vertices[new_vertex_count++] = vertex;
				vertex->SetPosition(position);

				//
				//---------------
				// Lerp the color
				//---------------
				//
				RGBAColor color_b;
				if (vertex_b->GetColor(&color_b))
				{
					RGBAColor color_c;
					#if defined(_ARMOR)
						bool result =
					#endif
					vertex_c->GetColor(&color_c);
					Verify(result);
					RGBAColor color;
					color.Lerp(color_b, color_c, lerp);
					vertex->SetColor(color);
				}

				//
				//---------------
				// Lerp the color
				//---------------
				//
				DynamicArrayOf<Vector2DOf<Scalar> > uv_b;
				if (vertex_b->GetUVs(&uv_b))
				{
					DynamicArrayOf<Vector2DOf<Scalar> > uv_c;
					#if defined(_ARMOR)
						bool result =
					#endif
					vertex_c->GetUVs(&uv_c);
					Verify(result);
					Verify(uv_b.GetLength() == uv_c.GetLength());
					
					DynamicArrayOf<Vector2DOf<Scalar> > uv;
					uv.SetLength(uv_b.GetLength());

					for(unsigned uv_loop=0;uv_loop<uv_b.GetLength();uv_loop++)
					{
						uv[uv_loop].Lerp(uv_b[uv_loop], uv_c[uv_loop], lerp);
					}
					vertex->SetUVs(uv);
				}

				//
				//---------------
				// Lerp the color
				//---------------
				//
				Normal3D normal_b;
				if (vertex_b->GetNormal(&normal_b))
				{
					Normal3D normal_c;
					#if defined(_ARMOR)
						bool result =
					#endif
					vertex_c->GetNormal(&normal_c);
					Verify(result);
					Normal3D normal;
					normal.Lerp(normal_b, normal_c, lerp);
					vertex->SetNormal(normal);
				}

				//
				//---------------------------------------
				// Now, insert the vertex into both sides
				//---------------------------------------
				//
				++crossings;
				Verify(crossings < 3);
				inside_indices[inside_index_count++] = vertex;
				outside_indices[outside_index_count++] = vertex;
				side_a = 0;
			}
		}

		//
		//----------------------------------------------------------
		// See if we actually ended up with a polygon on the outside
		//----------------------------------------------------------
		//
		if (inside_index_count < 3)
		{
			Verify(outside_index_count >= 3);
Move_It_Outside:
			polygon->AttachReference();
			outside_polygons[outside_polygon_count++] = polygon;
		}

		//
		//---------------------------------------------------------
		// See if we actually ended up with a polygon on the inside
		//---------------------------------------------------------
		//
		else if (outside_index_count < 3)
		{
			Verify(inside_index_count >= 3);
Move_It_Inside:
			polygon->AttachReference();
			inside_polygons[inside_polygon_count++] = polygon;
		}

		//
		//------------------------------------------
		// Construct the inside and outside polygons
		//------------------------------------------
		//
		else
		{
			//
			//---------------------------------------------
			// Make sure that the outside polygon isn't bad
			//---------------------------------------------
			//
			Verify(outside_index_count >= 3);
			outside_indices.SetLength(outside_index_count);
			PolygonProxy *outside_polygon = PolygonProxy::MakeProxy();
			Check_Object(outside_polygon);
			outside_polygon->SetPolygonIndices(outside_indices);
			if (outside_polygon->FindErrors(process->errorProcess) > 0)
			{
				outside_polygon->DetachReference();
				goto Move_It_Inside;
			}

			//
			//--------------------------------------------
			// Make sure that the inside polygon isn't bad
			//--------------------------------------------
			//
			Verify(inside_index_count >= 3);
			inside_indices.SetLength(inside_index_count);
			PolygonProxy *inside_polygon = PolygonProxy::MakeProxy();
			Check_Object(inside_polygon);
			inside_polygon->SetPolygonIndices(inside_indices);
			if (inside_polygon->FindErrors(process->errorProcess) > 0)
			{
				outside_polygon->DetachReference();
				inside_polygon->DetachReference();
				goto Move_It_Outside;
			}

			//
			//---------------------------------------------
			// This is good, so actually setup both proxies
			//---------------------------------------------
			//
			MultiState multi_state;
			polygon->UseMultiState(&multi_state);
			Check_Object(&multi_state);

			outside_polygon->SetStatesToMatch(multi_state);
			inside_polygon->SetStatesToMatch(multi_state);
	
			multi_state.DetachReferences();
			
			outside_polygons[outside_polygon_count++] = outside_polygon;
			inside_polygons[inside_polygon_count++] = inside_polygon;

		}

		//
		//---------------------
		// Clean up the polygon
		//---------------------
		//
		polygon->DetachArrayReferences(&old_indices);
	}

	//
	//--------------------------------------------------
	// See if we should just transfer the mesh after all
	//--------------------------------------------------
	//
	bool destroy_mesh;
	if (!inside_polygon_count)
	{
		Verify(outside_polygon_count == old_polygon_count);
		mesh->TransferAndAppendToParentGroup(outside_set);
		destroy_mesh = false;
	}
	else if (!outside_polygon_count)
	{
		Verify(inside_polygon_count == old_polygon_count);
		mesh->TransferAndAppendToParentGroup(inside_set);
		destroy_mesh = false;
	}

	//
	//------------------------------------------------------
	// Otherwise, create the new meshes and add the polygons
	//------------------------------------------------------
	//
	else
	{
		PolygonMeshProxy *inside_mesh = inside_set->AppendNewPolygonMeshProxy();
		Check_Object(inside_mesh);
		inside_polygons.SetLength(inside_polygon_count);
		inside_mesh->AddPolygons(process, inside_polygons);
		inside_mesh->DetachReference();

		PolygonMeshProxy *outside_mesh =
			outside_set->AppendNewPolygonMeshProxy();
		Check_Object(outside_mesh);
		outside_polygons.SetLength(outside_polygon_count);
		outside_mesh->AddPolygons(process, outside_polygons);
		outside_mesh->DetachReference();
		destroy_mesh = true;
	}

	//
	//------------------------------------------------------------------------
	// Clean up the buffer arrays.  Passing NULL into the polygon detach array
	// function cleans up the abstract indices
	//------------------------------------------------------------------------
	//
	for (i=0; i<inside_polygon_count; ++i)
		inside_polygons[i]->DetachReference();
	for (i=0; i<outside_polygon_count; ++i)
		outside_polygons[i]->DetachReference();
	for (i=0; i<new_vertex_count; ++i)
		new_vertices[i]->DetachReference();

	//
	//------------------------------------
	// This mesh may be no longer required
	//------------------------------------
	//
	mesh->DetachArrayReferences(&old_polygons);
	Verify(mesh->GetReferenceCount() == 1);
	if (destroy_mesh)
		mesh->Destroy();
	return !destroy_mesh;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
	InterestBSP::CreateInterestZone(
		SortInterestZonesProcess *process,
		GenericProxy *proxy
	)
{
	Check_Object(this);
	Check_Object(process);
	Check_Object(proxy);

	//
	//---------------------------------------
	// Make sure that the process says its OK
	//---------------------------------------
	//
	process->CreateCallback(proxy);
	if (!process->continueProcess)
		return true;

	//
	//----------------------------------------------------------------------
	// Create the node name for this interest zone, and assign it as the new
	// proxy name
	//----------------------------------------------------------------------
	//
	MString zone_name = "iz";
	char buffer[200];
	_itoa(process->zoneCounter++, buffer, 10);
	zone_name += buffer;

	//
	//-------------------------------------------------------------------------
	// Get the centerpoint of the box, and then convert that to a linear
	// transform we can apply to the child meshes in order to get them centered
	// around the world origin
	//-------------------------------------------------------------------------
	//
	Check_Object(&process->zoneArray);
	const ExtentBox *box = &process->zoneArray[zoneIndex];
	Check_Object(box);
	Point3D box_center;
	box->GetCenterpoint(&box_center);
	LinearMatrix4D box_to_world(box_center);
	LinearMatrix4D world_to_box;
	world_to_box.Invert(box_to_world);

	//
	//----------------------------------------------------------------------
	// We need to see if we are working on a scene or group, and get the
	// proper child info from either
	//----------------------------------------------------------------------
	//
	unsigned child_count;
	GroupProxy *basket;
	ChildProxy *child;
	GroupProxy *iz;
	if (proxy->IsDerivedFrom(GroupProxy::DefaultData))
	{
		GroupProxy *group = Cast_Object(GroupProxy*, proxy);
		iz = group;
		child_count = group->GetChildCount();
		basket = group->AppendNewGroupProxy();
		child = group->UseFirstChildProxy();
		iz->AttachReference();
	}
	else
	{
		Verify(proxy->IsDerivedFrom(SceneProxy::DefaultData));
		SceneProxy *scene = Cast_Object(SceneProxy*, proxy);
		child_count = scene->GetChildCount();
		iz = scene->AppendNewGroupProxy();
		basket = iz->AppendNewGroupProxy();
		child = scene->UseFirstChildProxy();
	}

	//
	//--------------------------------------------
	// Name the basket and move each child into it
	//--------------------------------------------
	//
	MString set_name = "floor";
	set_name += buffer;
	iz->SetName(zone_name);
	basket->SetName(set_name);
	for (unsigned i=0; i<child_count; ++i)
	{
		ChildProxy *next = child->UseNextSiblingProxy();
		child->TransformLocalToParent(world_to_box);
		child->TransferAndAppendToParentGroup(basket);
		child->DetachReference();
		child = next;
	}
	if (child)
		child->DetachReference();

	//
	//------------------------------------------------------------------------
	// Optimize the basket, and move it in the hierarchy to where it should be
	// viewed at
	//------------------------------------------------------------------------
	//
	OptimizeBasket(process, basket);
	basket->SetLocalToParent(box_to_world);
	basket->DetachReference();

	//
	//------------------------------------------------------------------------
	// Now we need to generate the contents file for this interest zone.  It
	// will specify a single entity which will try and hook up to the izset###
	// locator.
	//------------------------------------------------------------------------
	//
	Check_Pointer(process->setDirectory);
	MString contents_name = process->setDirectory;
	contents_name += zone_name;
	contents_name += ".contents";
	NotationFile contents_file;
	const char* page_name = "Floor";
	Page *page = contents_file.SetPage(page_name);
	Check_Object(page);
	page->SetEntry("Model", "Floor.data");
	char center_text[200];
	sprintf(center_text, "%f %f %f", box_center.x, box_center.y, box_center.z);
	page->SetEntry("Translation", center_text);
	page->SetEntry("PreCollisionState", "NeverExecuteState");
	page->SetEntry("PostCollisionState", "NeverExecuteState");
	page->SetEntry("CollisionMask", "-1");
	page->SetEntry("Collidee", "yes");
	page->SetEntry("ParentedToInterestGraphFlag", "yes");
	page->SetEntry("CanInterestFlag", "yes");
	page->SetEntry("CulledInterestFlag", "yes");
	MString locator_name = "\"";
	locator_name += set_name;
	locator_name += '"';
	page->SetEntry("Locator", locator_name);
	contents_file.SaveAs(contents_name);

	//
	//-----------------------------------------------------------------------
	// Create the interest zone entry within the set contents file.  The page
	// name will be InterestZone###
	//-----------------------------------------------------------------------
	//
	Check_Object(process->setContents);
	NotationFile *set_contents_file = process->setContents;
	page_name = zone_name;
	Page *set_page = set_contents_file->SetPage(page_name);
	Check_Object(set_page);
	set_page->SetEntry("Model", "InterestZone.data");
	set_page->SetEntry("PreCollisionState", "NeverExecuteState");
	set_page->SetEntry("PostCollisionState", "NeverExecuteState");
	set_page->SetEntry("ParentedToInterestGraphFlag", "no");
	set_page->SetEntry("CanInterestFlag", "yes");
	set_page->SetEntry("CulledInterestFlag", "no");

	//
	//------------------------------------------------------------------------
	// Set up the locator the interest zone is to search for, put the box size
	// in ASCII, and finish the page
	//------------------------------------------------------------------------
	//
	locator_name = "\"";
	locator_name += zone_name;
	locator_name += '"';
	set_page->SetEntry("Locator", locator_name);
	set_page->SetEntry("ArmoryZoneFlag", "no");
	sprintf(
		buffer,
		"%f,%f,%f,%f,%f,%f",
		box->minX,
		box->minY,
		box->minZ,
		box->maxX,
		box->maxY,
		box->maxZ
	);
	set_page->SetEntry("Box", buffer);
	set_page->SetEntry("Contents", zone_name + ".contents");

	//
	//----------------------------------------------------
	// Hierarchy proxies must be discarded prior to return
	//----------------------------------------------------
	//
	iz->DetachReference();
	return true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	InterestBSP::OptimizeBasket(
		SortInterestZonesProcess *process,
		GroupProxy *basket
	)
{
	Check_Object(this);
	Check_Object(process);
	Check_Object(basket);

	//
	//---------------------------
	// Flatten out the transforms
	//---------------------------
	//
	FlattenHierarchyProcess *flatten_hierarchy =
		process->MakeFlattenHierarchyProcess(process->configFile, basket);
	Check_Object(flatten_hierarchy);
	basket->FlattenHierarchy(flatten_hierarchy);
	Check_Object(flatten_hierarchy);
	delete flatten_hierarchy;

	//
	//--------------------------------------------------------------------------
	// Now that all the children are split up by state, the next step is to fuse
	// polygon mesh children that have the same state
	//--------------------------------------------------------------------------
	//
	unsigned child_count = basket->GetChildCount();
	if (!child_count)
		return;

	//
	//---------------------------
	// Set up the analysis arrays
	//---------------------------
	//
	DynamicArrayOf<PolygonMeshProxy*> meshes(child_count);
	DynamicArrayOf<MultiState*> states(child_count);

	DynamicArrayOf<DynamicArrayOf<PolygonProxy*> >
		polygon_arrays(child_count);
	unsigned unique_states = 0;

	//
	//-----------------------------------------------------------------
	// Loop through each mesh looking for the proper state bin for each
	//-----------------------------------------------------------------
	//
	ChildProxy *child = basket->UseFirstChildProxy();
	unsigned
		m,
		s;
	for (m=0; m<child_count; ++m)
	{
		Check_Object(child);
		ChildProxy *next = child->UseNextSiblingProxy();
		meshes[m] = Cast_Object(PolygonMeshProxy*, child);
		#if defined(_ARMOR)
			LinearMatrix4D test_matrix;
			Verify(!meshes[m]->GetLocalToParent(&test_matrix));
		#endif

		//
		//-------------------
		// Find a state proxy
		//-------------------
		//
		DynamicArrayOf<PolygonProxy *> polygons;
		unsigned poly_count = meshes[m]->UsePolygonArray(&polygons);

		MultiState poly_states;

		polygons[0]->UseMultiState(&poly_states);

		//
		//-------------------------------------------------------
		// See if this state matches any of our previous children
		//-------------------------------------------------------
		//
		for (s=0; s<unique_states;++s)
		{
			if(poly_states.IsEqualTo(*states[s]))
			{
				break;
			}
		}

		//
		//----------------------------
		// Keep the state if it is new
		//----------------------------
		//
		if (s<unique_states)
		{
			poly_states.DetachReferences();
		}
		else
		{
			states[s] = new MultiState(poly_states);

			unique_states++;
		}

		//
		//--------------------------------------------------
		// Copy the polygon proxies into the appropriate bin
		//--------------------------------------------------
		//
		unsigned i = polygon_arrays[s].GetLength();
		polygon_arrays[s].SetLength(i + poly_count);
		for (unsigned j=0; j<poly_count; ++j)
			polygon_arrays[s][i+j] = polygons[j];

		//
		//-----------------------
		// Move to the next child
		//-----------------------
		//
		child = next;
	}
	Verify(!child);
	Verify(unique_states>0);

	//
	//------------------------------------------------------------------------
	// Bucket sort each state separately.  The state proxy was only needed for
	// the sorting, so we don't need it anymore
	//------------------------------------------------------------------------
	//
	for (s=0; s<unique_states;++s)
	{
		states[s]->DetachReferences();

		basket->SortAndAddPolygons(
			process->sortProcess,
			polygon_arrays[s]
		);
		meshes[s]->DetachArrayReferences(&polygon_arrays[s]);
	}

	//
	//----------------------
	// Remove the old meshes
	//----------------------
	//
	for (m=0; m<child_count; ++m)
	{
		Check_Object(meshes[m]);
		Verify(meshes[m]->GetReferenceCount() == 1);
		meshes[m]->Destroy();
	}

	//
	//------------------------------------------------------------------------
	// Now binsort everything.  Since the meshes are already within the bucket
	// size, this will just create new groups to arrange them within
	//------------------------------------------------------------------------
	//
	basket->BinSort(process->sortProcess);
}
