/*
**	Command & Conquer Renegade(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program is free software: you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation, either version 3 of the License, or
**	(at your option) any later version.
**
**	This program is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/***********************************************************************************************
 ***              C O N F I D E N T I A L  ---  W E S T W O O D  S T U D I O S               ***
 ***********************************************************************************************
 *                                                                                             *
 *                 Project Name : LevelEdit                                                    *
 *                                                                                             *
 *                     $Archive:: /Commando/Code/wwphys/PathfindSectorBuilder.cpp                                                                                                                                   $Revision:: 2                                                           $*
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#include "stdafx.h"
#include "pathfindsectorbuilder.h"
#include "phys3.h"
#include "filemgr.h"
#include "editorassetmgr.h"
#include "_assetmgr.h"
#include "humanphys.h"
#include "utils.h"
#include "editorphys.h"
#include "collisiongroups.h"
#include "sceneeditor.h"
#include "boxrobj.h"
#include "pathfind.h"
#include "systimer.h"

//////////////////////////////////////////////////////////////////////////
//	Static member initialization
//////////////////////////////////////////////////////////////////////////
BodyBoxCullObj *BodyBoxCullObj::m_First = NULL;

//////////////////////////////////////////////////////////////////////////
//	Local constants
//////////////////////////////////////////////////////////////////////////

class SimDirInfoClass
{
	public:

		SimDirInfoClass (void) { }
		SimDirInfoClass (float _heading, float _speed, const Vector3 &_move, const Vector3 &min, const Vector3 &max)
			: heading (_heading), speed (_speed), move (_move), min_move (min), max_move (max)	{ }

		float		heading;
		float		speed;
		Vector3	move;
		Vector3	min_move;
		Vector3	max_move;
};

/*const SIM_DIR_INFO DIR_INFO[DIR_MAX] =
{
	SIM_DIR_INFO ( 0.0F,						 	 Vector3 ( 0.9F, -0.1F, -1.5F), Vector3 ( 2.0F,  0.1F, 1.5F) ),
	SIM_DIR_INFO ( (float)DEG_TO_RAD (90),	 Vector3 (-0.1F,  0.9F, -1.5F), Vector3 ( 0.1F,  2.0F, 1.5F) ),
	SIM_DIR_INFO ( (float)DEG_TO_RAD (180), Vector3 (-2.0F, -0.1F, -1.5F), Vector3 (-0.9F,  0.1F, 1.5F) ),
	SIM_DIR_INFO ( (float)DEG_TO_RAD (270), Vector3 (-0.1F, -2.0F, -1.5F), Vector3 ( 0.1F, -0.9F, 1.5F) )
};*/


//
//	Note:  The vector members are in "percentage of bounding box" units.
//
static const SimDirInfoClass DIR_INFO[DIR_MAX] =
{
	SimDirInfoClass ( 0.0F,						 	 0, Vector3 ( 1.0F,  0,    0), Vector3 ( 0.95F, -0.05F, -2.0F), Vector3 (  1.2F,  0.05F, 2.0F) ),
	SimDirInfoClass ( (float)DEG_TO_RAD (90),	 0, Vector3 ( 0,     1.0F, 0), Vector3 (-0.05F,  0.95F, -2.0F), Vector3 ( 0.05F,   1.2F, 2.0F) ),
	SimDirInfoClass ( (float)DEG_TO_RAD (180), 0, Vector3 (-1.0F,  0,    0), Vector3 ( -1.2F, -0.05F, -2.0F), Vector3 (-0.95F,  0.05F, 2.0F) ),
	SimDirInfoClass ( (float)DEG_TO_RAD (270), 0, Vector3 ( 0,    -1.0F, 0), Vector3 (-0.05F,  -1.2F, -2.0F), Vector3 ( 0.05F, -0.95F, 2.0F) )
};


//////////////////////////////////////////////////////////////////////////
//
//	PathfindSectorBuilderClass
//
//////////////////////////////////////////////////////////////////////////
PathfindSectorBuilderClass::PathfindSectorBuilderClass (void)
	:	m_PhysicsSim (NULL),
		m_RecurseLevel (0),
		m_RepartitionCount (0),
		m_CurrentSector (NULL),
		m_SimBoundingBox (Vector3 (0.5F, 0.5F, 1.8F)),
		m_DirInfo (NULL),
		m_bCancel (false)
{
	//
	//	Create an instance of the commando render object we can
	// use to simulate character movement
	//
	CString full_path;
	full_path = Get_File_Mgr ()->Make_Full_Path ("Characters\\Commando");
	_pThe3DAssetManager->Set_Current_Directory (full_path);

	RenderObjClass *commando_obj = WW3DAssetManager::Get_Instance()->Create_Render_Obj ("COMMANDO");
	if (commando_obj != NULL) {

		//
		//	Attempt to find the collision box for this physics object
		//
		RenderObjClass *collision_box = NULL;
		if (commando_obj->Class_ID () == RenderObjClass::CLASSID_DISTLOD) {
			RenderObjClass *lod0	= commando_obj->Get_Sub_Object(0);
			collision_box			= lod0->Get_Sub_Object_By_Name ("WORLDBOX");
			MEMBER_RELEASE (lod0);
		} else {
			collision_box			= commando_obj->Get_Sub_Object_By_Name ("WORLDBOX");
		}

		//
		//	Store the exents of the collision box for use in our simulation
		//
		if (collision_box != NULL) {
			const AABoxClass &box = collision_box->Get_Bounding_Box ();
			m_SimBoundingBox = box.Extent * 2;
			MEMBER_RELEASE (collision_box);
		}

		//
		//	Build an array of simulation information (specific to this
		// physics object).
		//
		m_DirInfo = new SimDirInfoClass[DIR_MAX];
		for (int index = 0; index < DIR_MAX; index ++) {
			m_DirInfo[index]					= DIR_INFO[index];
			m_DirInfo[index].move.X			*= m_SimBoundingBox.X;
			m_DirInfo[index].move.Y			*= m_SimBoundingBox.Y;
			m_DirInfo[index].move.Z			*= m_SimBoundingBox.Z;
			m_DirInfo[index].min_move.X	*= m_SimBoundingBox.X;
			m_DirInfo[index].min_move.Y	*= m_SimBoundingBox.Y;
			m_DirInfo[index].min_move.Z	*= m_SimBoundingBox.Z;
			m_DirInfo[index].max_move.X	*= m_SimBoundingBox.X;
			m_DirInfo[index].max_move.Y	*= m_SimBoundingBox.Y;
			m_DirInfo[index].max_move.Z	*= m_SimBoundingBox.Z;
			m_DirInfo[index].speed			= m_DirInfo[index].move.Length ();
		}

		//
		//	Create the physics object we will use for character simulation
		//
		m_PhysicsSim = new HumanPhysClass;
		m_PhysicsSim->Set_Model (commando_obj);
		m_PhysicsSim->Set_Controller (&m_Controller);
		m_PhysicsSim->Set_Collision_Group (0);
		MEMBER_RELEASE (commando_obj);

		//
		//	Setup the culling system for the boxes.
		//
		float max_extent	= max (m_SimBoundingBox.X, m_SimBoundingBox.Y);
		max_extent			= max (m_SimBoundingBox.Z, max_extent);
		Vector3 min;
		Vector3 max;
		PhysicsSceneClass::Get_Instance ()->Get_Level_Extents (min, max);
		m_BodyBoxCullingSystem.Re_Partition (min, max, max_extent * 5);
	}

	return ;
}


//////////////////////////////////////////////////////////////////////////
//
//	~PathfindSectorBuilderClass
//
//////////////////////////////////////////////////////////////////////////
PathfindSectorBuilderClass::~PathfindSectorBuilderClass (void)
{
	//MEMBER_RELEASE (m_PhysicsSim);
	Free_Sectors ();
	SAFE_DELETE_ARRAY (m_DirInfo);
	return ;
}


//////////////////////////////////////////////////////////////////////////
//
//	Generate_Sectors
//
//////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Generate_Sectors (const Vector3 &start_pos)
{
	bool sectors_displayed = PathfindClass::Get_Instance ()->Are_Sectors_Displayed ();
	bool portals_displayed = PathfindClass::Get_Instance ()->Are_Portals_Displayed ();
	PathfindClass::Get_Instance ()->Display_Sectors (false);
	PathfindClass::Get_Instance ()->Display_Portals (false);

	//
	// Start fresh
	//
	Free_Sectors ();
	PathfindClass::Get_Instance ()->Reset_Sectors ();
	Import_Raw_Data ();
	m_RecurseLevel			= 0;
	m_RepartitionCount	= 0;
	m_bCancel				= false;

	//
	//	Normalize the starting position.  We do this so that we can start as many
	// different 'generations' in the world and they will all match up perfectly.
	//
	Vector3 normalized_start_pos = start_pos;
	normalized_start_pos.X = (int(start_pos.X / m_SimBoundingBox.X)) * m_SimBoundingBox.X;
	normalized_start_pos.Y = (int(start_pos.Y / m_SimBoundingBox.Y)) * m_SimBoundingBox.Y;

	//
	//	Start flood filling from the given position
	//
	m_CurrentSector = Mark_Sector (normalized_start_pos);
	Floodfill (normalized_start_pos);

	DWORD start_ticks = TIMEGETTIME ();

	//
	//	Process all the sectors in our queue
	//
	while (m_FloodFillProcessList.Count () > 0) {
		m_CurrentSector = m_FloodFillProcessList[0];
		m_FloodFillProcessList.Delete (0);
		Floodfill (m_CurrentSector->Get_Transform ().Get_Translation ());
		m_bCancel = ((TIMEGETTIME () - start_ticks) >  20000000L);
	}

	//
	//	Compress the sectors into the largest possible rectangular regions
	//
	DWORD before_ticks = TIMEGETTIME ();
	Compress_Sectors ();
	DWORD after_ticks = TIMEGETTIME ();

	CString message;
	message.Format ("Time spent compressing: %d secs.\r\n", (after_ticks-before_ticks)/1000);
	::Ouput_Message (message);

	//
	//	Add the sectors to our global pathfind object
	//
	for (int index = 0; index < m_SectorList.Count (); index ++) {
		PathfindSectorClass *sector = m_SectorList[index];
		if (sector->Is_Valid ()) {
			PathfindClass::Get_Instance ()->Add_Sector (sector);
		}
	}

	//
	//	Turn the debug display back on if necessary
	//
	PathfindClass::Get_Instance ()->Display_Sectors (sectors_displayed);
	PathfindClass::Get_Instance ()->Display_Portals (portals_displayed);
	return ;
}


float Re_Orient_Vector (const Vector3 &pos, const Vector3 &look_at)
{
	float dx = (look_at[0] - pos[0]);
	float dy = (look_at[1] - pos[1]);
	float len = (float)sqrt(dx*dx + dy*dy);

	float angle = 0;

	if (fabs (dx) >= fabs(dy)) {
		double cosy = 0;
		if (len != 0.0f) {
			cosy = dx/len;
		} else {
			cosy = 1.0f;
		}

		angle = ::acos (cosy);
	} else {
		double siny = 0;
		if (len != 0.0f) {
			siny = dy/len;
		} else {
			siny = 0.0f;
		}

		angle = ::asin (siny);
	}

	return angle;
}


//////////////////////////////////////////////////////////////////////////
//
//	Do_Physics_Sim
//
//////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Do_Physics_Sim
(
	const Vector3 &	start_pos,
	PATHFIND_DIR		direction
)
{
	//
	//	Position the physics object at the test location
	//
	Matrix3D transform (start_pos);
	m_PhysicsSim->Set_Transform (transform);

	//
	//	Determine how far the simulation can move in each of the 3 directions
	// and still be considered 'valid'.
	//
	Vector3 expected_move			= m_DirInfo[direction].move;
	Vector3 min_exceptable_move	= m_DirInfo[direction].min_move;
	Vector3 max_exceptable_move	= m_DirInfo[direction].max_move;

	//
	//	Set-up the simulation to move one 'step' in the given direction
	//
	m_Controller.Reset ();
	m_Controller.Set_Move_Forward  (1);
	m_PhysicsSim->Set_Normalized_Speed (m_DirInfo[direction].speed);
	m_PhysicsSim->Set_Heading (m_DirInfo[direction].heading);

	Vector3 expected_pos = start_pos + expected_move;


	//
	//	Do the simulation
	//
	Vector3 new_pos;
	Vector3 move_vector;
	bool found					= false;
	bool on_ground				= false;

	#if 0
		bool should_continue		= true;
		for (int attempt = 0; (attempt < 50) && should_continue; attempt ++) {

			//
			//	Break the physics timestep down into small steps (however
			// make sure it processes for exactly 1 sec).
			//
			for (int index = 0; index < 2; index ++) {
				m_PhysicsSim->Timestep (0.05F);
			}

			new_pos		= m_PhysicsSim->Get_Position ();
			move_vector	= new_pos - start_pos;

			//
			//	Did we move into the expected range?
			//
			if ((move_vector.X >= min_exceptable_move.X) &&
				 (move_vector.Y >= min_exceptable_move.Y) &&
				 (move_vector.Z >= min_exceptable_move.Z) &&
				 (move_vector.X <= max_exceptable_move.X) &&
				 (move_vector.Y <= max_exceptable_move.Y) &&
				 (move_vector.Z <= max_exceptable_move.Z))
			{
				should_continue = false;
				found = true;
			} else {
				float angle = Re_Orient_Vector (new_pos, expected_pos);
				m_PhysicsSim->Set_Heading (angle);
			}
		}
		on_ground = m_PhysicsSim->Is_In_Contact ();
	#else

		float half_height = m_SimBoundingBox.Z * 0.5F;
		float movement = (4.9F + (m_SimBoundingBox.Z * 0.15F));

		//m_PhysicsSim->Set_Transform (Matrix3D (expected_pos));
		AABoxClass box;
		box.Center		= expected_pos;
		box.Center.Z	+= (m_SimBoundingBox.Z * 0.75F);
		box.Extent.X	= m_SimBoundingBox.X * 0.5F;
		box.Extent.Y	= m_SimBoundingBox.Y * 0.5F;
		box.Extent.Z	= half_height;

		CastResultStruct result;
		PhysAABoxCollisionTestClass test(	box,
														Vector3 (0, 0, -movement),
														&result,
														m_PhysicsSim->Get_Collision_Group (),
														COLLISION_TYPE_PHYSICAL);

		PhysicsSceneClass::Get_Instance ()->Cast_AABox (test);

		found = (result.StartBad == false) && (result.Normal.Z > 0.7F);
		if (found) {
			on_ground = (result.Fraction < 1.0F);
			new_pos.Z = box.Center.Z - half_height - (movement * result.Fraction) + 0.001F;

		} else {
			int test = 0;
		}

	#endif

	if (found && on_ground) {

		//
		//	Force the x and y values to snap to the expected position
		//
		new_pos.X = start_pos.X + expected_move.X;
		new_pos.Y = start_pos.Y + expected_move.Y;

		//
		//	Is this cell already marked?
		//
		BodyBoxCullObj *occupant = Get_Sector_Occupant (new_pos);
		if (occupant == NULL) {

			//
			//	Mark this cell and add it to the list of sectors
			// that need to be recursed.
			//
			occupant = Mark_Sector (new_pos);
			m_FloodFillProcessList.Add (occupant);
		}

		//
		//	Let the sector know who its new neighbor is
		//
		if (occupant != m_CurrentSector) {
			m_CurrentSector->Set_Neighbor (direction, occupant);
		}
	}

	return ;
}


//////////////////////////////////////////////////////////////////////////
//
//	Floodfill
//
//////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Floodfill (const Vector3 &start_pos)
{
	//
	//	Do a simulation in each of the four directions from the
	// current cell.
	//
	if (m_bCancel == false) {
		Do_Physics_Sim (start_pos, DIR_FORWARD);
		Do_Physics_Sim (start_pos, DIR_LEFT);
		Do_Physics_Sim (start_pos, DIR_BACKWARD);
		Do_Physics_Sim (start_pos, DIR_RIGHT);

		//
		//	Let the current sector 'know' its been completely processed
		//
		m_CurrentSector->Needs_Processing (false);
	}

	//
	//	Allow the user to cancel this operation
	//
	if (!m_bCancel && (::GetAsyncKeyState (VK_ESCAPE) < 0)) {
		m_bCancel = true;
		::MessageBox (NULL, "Operation cancelled.", "Pathfind", MB_OK | MB_SETFOREGROUND);
	}

	return ;
}


//////////////////////////////////////////////////////////////////////////
//
//	Get_Sector_Occupant
//
//////////////////////////////////////////////////////////////////////////
BodyBoxCullObj *
PathfindSectorBuilderClass::Get_Sector_Occupant (const Vector3 &pos)
{
	Vector3 box_center_pos	= pos;
	//box_center_pos.Z			+= (m_SimBoundingBox.Z * 0.1F);

	//
	//	Collect all the objects in our culling system that occupy the
	// given point
	//
	m_BodyBoxCullingSystem.Reset_Collection ();
	m_BodyBoxCullingSystem.Collect_Objects (box_center_pos);
	BodyBoxCullObj *body_box = m_BodyBoxCullingSystem.Get_First_Collected_Object ();

	if (body_box == NULL) {

		box_center_pos		= pos;
		box_center_pos.Z	+= (m_SimBoundingBox.Z * 0.25F);

		m_BodyBoxCullingSystem.Reset_Collection ();
		m_BodyBoxCullingSystem.Collect_Objects (box_center_pos);
		body_box = m_BodyBoxCullingSystem.Get_First_Collected_Object ();
	}

	if (body_box == NULL) {

		box_center_pos		= pos;
		box_center_pos.Z	+= (m_SimBoundingBox.Z * 0.5F);

		m_BodyBoxCullingSystem.Reset_Collection ();
		m_BodyBoxCullingSystem.Collect_Objects (box_center_pos);
		body_box = m_BodyBoxCullingSystem.Get_First_Collected_Object ();
	}

	//
	//	If we found any objects in the culling system, then the sector is occupied
	//
	return body_box;
}


//////////////////////////////////////////////////////////////////////////
//
//	Mark_Sector
//
//////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Mark_Sector (BodyBoxCullObj *body_box)
{
	//
	//	Add the culling object  to our AABTree culling system.  (This
	// effectively marks the cell as 'occupied').
	//
	body_box->Add_Ref ();
	m_BodyBoxCullingSystem.Add_Object (body_box);
	BodyBoxCullObj::Add (body_box);

	//
	//	Repartition the culling system every few hundred
	// entries.
	//
	/*m_RepartitionCount ++;
	if (m_RepartitionCount > 1000) {
		m_RepartitionCount = 0;
		m_BodyBoxCullingSystem.Re_Partition ();
	}*/

	return ;
}


//////////////////////////////////////////////////////////////////////////
//
//	Mark_Sector
//
//////////////////////////////////////////////////////////////////////////
BodyBoxCullObj *
PathfindSectorBuilderClass::Mark_Sector (const Vector3 &pos)
{
	//
	//	Create a culling system object to represent this 'sector' and
	// add it to our AABTree culling system.  (This effectively marks
	// the cell as 'occupied').
	//
	BodyBoxCullObj *body_box = new BodyBoxCullObj;
	AABoxClass box;
	box.Center		= pos;
	box.Extent.X	= m_SimBoundingBox.X * 0.5F;
	box.Extent.Y	= m_SimBoundingBox.Y * 0.5F;
	box.Extent.Z	= m_SimBoundingBox.Z * 0.5F;
	body_box->Set_Bounding_Box (box);
	body_box->Init_Transform (pos);

	Mark_Sector (body_box);
	body_box->Release_Ref ();
	return body_box;
}


//////////////////////////////////////////////////////////////////////////
//
//	Compress_Sectors
//
//////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Compress_Sectors (void)
{
	BODY_BOX_LIST body_box_list;
	int sectors = 0;

	int largest_area						= 0;
	int largest_area_right				= 0;
	int largest_area_down				= 0;
	BodyBoxCullObj *upper_left_ptr	= NULL;

	//
	//	Keep looping until we've compressed all the body boxes into sectors
	//
	while (BodyBoxCullObj::Get_First () != NULL) {

		//
		//	Find the largest possible rectangular area that's 'free'
		// in the world.
		//
		largest_area = 0;
		for (	BodyBoxCullObj *cull_obj = BodyBoxCullObj::Get_First ();
				cull_obj != NULL;
				cull_obj = cull_obj->Get_Next ())
		{
			int max_down	= 0;
			int max_right	= 0;
			int max_area	= 0;

			int curr_down	= 65536;
			int curr_right = 1;

			//
			//	Find the largest rectangular area that this box is the upper-left
			// corner for.
			//
			BodyBoxCullObj *neighbor	= cull_obj;
			Vector3 pos						= cull_obj->Get_Position ();
			float min_z_pos				= pos.Z - m_SimBoundingBox.Z;
			float max_z_pos				= pos.Z + (m_SimBoundingBox.Z * 1.5F);
			while (neighbor != NULL)
			{
				curr_down = min (neighbor->Get_Count (DIR_BACKWARD, min_z_pos, max_z_pos) + 1, curr_down);

				//
				//	Determine if this region (right x down) contains the
				// largest area so far.
				//
				int area = (curr_down * curr_right);
				if (area > max_area) {
					max_area		= area;
					max_down		= curr_down;
					max_right	= curr_right;
				}

				//
				//	Move to the next cell to the right
				//
				curr_right ++;
				neighbor = neighbor->Next_Valid (DIR_RIGHT, min_z_pos, max_z_pos);
			}

			if (max_area > largest_area) {
				upper_left_ptr			= cull_obj;
				largest_area			= max_area;
				largest_area_right	= max_right;
				largest_area_down		= max_down;
			}
		}

		//
		//	Debug info
		//
		if (largest_area > 1) {

			CString message;
			message.Format ("Combining cells: width = %d, height = %d, area = %d\r\n", largest_area_right, largest_area_down, largest_area);
			::Ouput_Message (message);

			sectors ++;
		}

		Vector3 min_point (100000, 100000, 100000);
		Vector3 max_point (-100000, -100000, -100000);

		PathfindSectorClass *sector = new PathfindSectorClass;

		//
		//	Remove all the cells that compose this area from the list
		//
		BodyBoxCullObj *curr_cell = upper_left_ptr;
		for (int right = 0; (right < largest_area_right) && (curr_cell != NULL); right ++) {

			BodyBoxCullObj *down_obj = curr_cell;
			for (int down = 0; (down < largest_area_down) && (down_obj != NULL); down ++) {

				Matrix3D transform	= down_obj->Get_Transform ();
				Vector3 position		= transform.Get_Translation ();

				min_point.X = min (min_point.X, position.X - (m_SimBoundingBox.X / 2));
				min_point.Y = min (min_point.Y, position.Y - (m_SimBoundingBox.Y / 2));
				min_point.Z = min (min_point.Z, position.Z);

				max_point.X = max (max_point.X, position.X + (m_SimBoundingBox.X / 2));
				max_point.Y = max (max_point.Y, position.Y + (m_SimBoundingBox.Y / 2));
				max_point.Z = max (max_point.Z, position.Z + m_SimBoundingBox.Z);

				ASSERT (down_obj->Is_Taken () == false);
				down_obj->Set_Sector (sector);

				body_box_list.Add (down_obj);
				down_obj->Remove ();
				down_obj->Set_Taken ();
				down_obj = down_obj->Peek_Neighbor (DIR_BACKWARD);
			}

			curr_cell = curr_cell->Peek_Neighbor (DIR_RIGHT);
		}

		//
		//	Sumbit this sector
		//
		if (largest_area > 1) {

			AABoxClass bounding_box;
			bounding_box.Center = min_point + ((max_point - min_point) / 2.0F);

			bounding_box.Extent.X = (max_point.X - min_point.X) / 2;
			bounding_box.Extent.Y = (max_point.Y - min_point.Y) / 2;
			bounding_box.Extent.Z = (max_point.Z - min_point.Z) / 2;

			sector->Set_Bounding_Box (bounding_box);
			m_SectorList.Add (sector);
		} else {
			sector->Set_Valid (false);
			m_SectorList.Add (sector);
		}
	}

	//
	//	Build portals
	//
	for (int index = 0; index < body_box_list.Count (); index ++) {
		BodyBoxCullObj *cull_obj = body_box_list[index];

		PathfindSectorClass *sector = cull_obj->Peek_Sector ();
		if (sector->Is_Valid ()) {

			//
			//	Make portals out of this box for any of the four directions
			//
			for (int dir = 0; dir < DIR_MAX; dir ++) {

				if (cull_obj->Is_New_Portal ((PATHFIND_DIR)dir)) {
					PathfindPortalClass *portal = cull_obj->Make_Portal ((PATHFIND_DIR)dir, m_SimBoundingBox);
					sector->Add_Portal (portal);
				}
			}
		}
	}

	//
	//	Free the body box culling objects
	//
	for (index = 0; index < body_box_list.Count (); index ++) {

		//
		//	Remove the body box from the culling system
		//
		BodyBoxCullObj *cull_obj = body_box_list[index];
		m_BodyBoxCullingSystem.Remove_Object (cull_obj);

		//
		//	Save the body box in the global list so we can combine
		// generated sections later (if we want).  Also usefull
		// for debugging purposes.
		//
		cull_obj->Reset_Portal_Info ();
		cull_obj->Set_Taken (false);
		cull_obj->Set_Sector (NULL);
		PathfindClass::Get_Instance ()->Add_Body_Box (cull_obj);
		MEMBER_RELEASE (cull_obj);
	}
	body_box_list.Delete_All ();

	//
	//	Debug info
	//
	CString message;
	message.Format ("Total sectors: %d\r\n", sectors);
	::Ouput_Message (message);

	return ;
}


///////////////////////////////////////////////////////////////////////////
//
//	Free_Sectors
//
///////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Free_Sectors (void)
{
	//
	//	Free all the sector objects
	//
	for (int index = 0; index < m_SectorList.Count (); index ++) {
		PathfindSectorClass *sector = m_SectorList[index];
		MEMBER_RELEASE (sector);
	}

	m_SectorList.Delete_All ();
	return ;
}


///////////////////////////////////////////////////////////////////////////
//
//	Import_Raw_Data
//
///////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Import_Raw_Data (void)
{
	BODY_BOX_LIST &raw_list = PathfindClass::Get_Instance ()->Get_Raw_Data ();

	//
	//	Add all the body boxes from a previous build to the system.
	//
	for (int index = 0; index < raw_list.Count (); index ++) {
		BodyBoxCullObj *body_box = raw_list[index];
		Mark_Sector (body_box);

		//
		//	Add this box to our process queue if it wasn't processed
		// the first time around.
		//
		if (body_box->Needs_Processing ()) {
			m_FloodFillProcessList.Add (body_box);
		}
	}

	PathfindClass::Get_Instance ()->Reset_Generated_Data ();
	return ;
}