840 lines
24 KiB
C++
840 lines
24 KiB
C++
|
/*
|
||
|
** 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 ;
|
||
|
}
|