This repository has been archived on 2025-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
CnC_Renegade/Code/Tools/LevelEdit/PathfindSectorBuilder.cpp

3004 lines
80 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/Tools/LevelEdit/PathfindSectorBuilder.cpp $Modtime:: 5/27/00 4:10p $*
* *
* $Revision:: 56 $*
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "stdafx.h"
#include "pathfindsectorbuilder.h"
#include "phys3.h"
#include "editorassetmgr.h"
#include "humanphys.h"
#include "phys3.h"
#include "boxrobj.h"
#include "pathfind.h"
#include "pscene.h"
#include "physcoltest.h"
#include "filemgr.h"
#include "_assetmgr.h"
#include "utils.h"
#include "nodemgr.h"
#include "filelocations.h"
#include "transitionnode.h"
#include "collisiongroups.h"
#include "pathfindportal.h"
#include "GeneratingPathfindDialog.h"
#include "terrainnode.h"
#include "sceneeditor.h"
#include "staticanimphys.h"
#include "elevator.h"
#include "doors.h"
#include "soldier.h"
#include "humanphys.h"
#include "combatchunkid.h"
//////////////////////////////////////////////////////////////////////////
// Local Prototypes
//////////////////////////////////////////////////////////////////////////
UINT fnPathfindDialogThread (DWORD dwparam1, DWORD dwparam2, DWORD, HRESULT *, HWND *);
///////////////////////////////////////////////////////////////////////////
// Local inlines
///////////////////////////////////////////////////////////////////////////
static inline bool
Clip_Point (Vector3 *point, const AABoxClass &box)
{
Vector3 temp_point = *point;
//
// Clip the temporary point
//
temp_point.X = max (temp_point.X, box.Center.X - box.Extent.X);
temp_point.Y = max (temp_point.Y, box.Center.Y - box.Extent.Y);
temp_point.Z = max (temp_point.Z, box.Center.Z - box.Extent.Z);
temp_point.X = min (temp_point.X, box.Center.X + box.Extent.X);
temp_point.Y = min (temp_point.Y, box.Center.Y + box.Extent.Y);
temp_point.Z = min (temp_point.Z, box.Center.Z + box.Extent.Z);
//
// Did the clip change the point?
//
bool retval = (point->X != temp_point.X);
retval |= (point->Y != temp_point.Y);
retval |= (point->Z != temp_point.Z);
//
// Pass the new point back to the caller
//
(*point) = temp_point;
return retval;
}
//////////////////////////////////////////////////////////////////////////
// Local constants
//////////////////////////////////////////////////////////////////////////
const float ONE_SEC_FALL_DIST = 4.9F;
class SimDirInfoClass
{
public:
SimDirInfoClass (void) { }
SimDirInfoClass (float _heading, float _speed, const Vector3 &_move, const Vector3 &_quadmove)
: heading (_heading), speed (_speed), move (_move), quad_move (_quadmove) { }
float heading;
float speed;
Vector3 move;
Vector3 quad_move;
};
//
// 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 ( 1.5F, -0.5F, 0) ),
SimDirInfoClass ( (float)DEG_TO_RAD (90), 0, Vector3 ( 0, 1.0F, 0), Vector3 ( 0.5F, 1.5F, 0) ),
SimDirInfoClass ( (float)DEG_TO_RAD (180), 0, Vector3 (-1.0F, 0, 0), Vector3 (-1.5F, 0.5F, 0) ),
SimDirInfoClass ( (float)DEG_TO_RAD (270), 0, Vector3 ( 0, -1.0F, 0), Vector3 (-0.5F, -1.5F, 0) )
};
static HumanPhysClass *_PhysSimObj = NULL;
static SoldierGameObj *_GameSimObj = NULL;
//////////////////////////////////////////////////////////////////////////
//
// PathfindSectorBuilderClass
//
//////////////////////////////////////////////////////////////////////////
PathfindSectorBuilderClass::PathfindSectorBuilderClass (void)
: m_CurrentSector (NULL),
m_SimBoundingBox (Vector3 (0.5F, 0.5F, 1.8F)),
m_SimBoxExtents (Vector3 (0.25F, 0.25F, 0.9F)),
m_DirInfo (NULL),
m_StepHeight (0),
m_TotalBoxCount (0),
m_TotalBoxGuess (0),
m_BeforeUpdateCount (0),
m_AllowWaterFloodfill (false),
m_MaxSectorDim (28000.0F)
{
RenderObjClass *commando_obj = NULL;
//
// Create an instance of the commando render object we can
// use to simulate character movement
//
SoldierGameObjDef *definition = (SoldierGameObjDef *)DefinitionMgrClass::Find_Typed_Definition ("Commando", CLASSID_GAME_OBJECT_DEF_SOLDIER, false);
if (definition != NULL) {
SoldierGameObj *game_obj = new SoldierGameObj;
game_obj->Init (*definition);
REF_PTR_SET (commando_obj, game_obj->Peek_Model ());
game_obj->Set_Delete_Pending ();
}
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");
REF_PTR_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;
m_SimBoundingBox.Z = (box.Extent.Z * 2);
m_SimBoxExtents = m_SimBoundingBox * 0.5F;
m_StepHeight = 0.25F;
REF_PTR_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].quad_move.X *= m_SimBoundingBox.X;
m_DirInfo[index].quad_move.Y *= m_SimBoundingBox.Y;
m_DirInfo[index].quad_move.Z *= m_SimBoundingBox.Z;
m_DirInfo[index].speed = m_DirInfo[index].move.Length ();
}
REF_PTR_RELEASE (commando_obj);
//
// Configure the body box culling system
//
Vector3 lev_min;
Vector3 lev_max;
PhysicsSceneClass::Get_Instance ()->Get_Level_Extents (lev_min, lev_max);
m_BodyBoxCullingSystem.Initialize (m_SimBoxExtents, lev_min, lev_max);
//
// Setup the simulation constants
//
m_SimMovement = Vector3 (0, 0, -(ONE_SEC_FALL_DIST + (m_SimBoundingBox.Z * 0.75F)));
m_SimSweepBox.Extent = m_SimBoxExtents;
}
Detect_Level_Features ();
/*AABoxClass collision_box;
collision_box.Center.Set (0, 0, 0);
collision_box.Extent = m_SimBoxExtents;
_PhysSimObj->Set_Collision_Box (collision_box);*/
return ;
}
//////////////////////////////////////////////////////////////////////////
//
// ~PathfindSectorBuilderClass
//
//////////////////////////////////////////////////////////////////////////
PathfindSectorBuilderClass::~PathfindSectorBuilderClass (void)
{
/*if (_GameSimObj != NULL) {
_GameSimObj->Destroy ();
_GameSimObj = NULL;
}*/
if (m_DirInfo != NULL) {
delete [] m_DirInfo;
m_DirInfo = NULL;
}
return ;
}
//////////////////////////////////////////////////////////////////////////
//
// Prepare_Level
//
//////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Prepare_Level (void)
{
::Get_Scene_Editor ()->Display_Static_Anim_Phys (true);
//
// Get the list of all static objects from the physics scene
//
RefPhysListIterator iterator = ::Get_Scene_Editor ()->Get_Static_Object_Iterator ();
for (iterator.First (); !iterator.Is_Done (); iterator.Next ()) {
//
// Is this object a static anim phys?
//
PhysClass *phys_obj = iterator.Peek_Obj ();
StaticAnimPhysClass *static_phys_obj = phys_obj->As_StaticAnimPhysClass ();
if (static_phys_obj != NULL) {
//
// Should we disable collisions with this object during pathfind?
//
StaticAnimPhysDefClass *definition = static_phys_obj->Get_StaticAnimPhysDef ();
if (definition->Does_Collide_In_Pathfind () == false) {
phys_obj->Inc_Ignore_Counter ();
} else {
while (phys_obj->Is_Ignore_Me ()) {
phys_obj->Dec_Ignore_Counter ();
}
}
}
}
//
// Make sure that tile's use collision group 15...
//
for ( NodeClass *node = NodeMgrClass::Get_First (NODE_TYPE_TILE);
node != NULL;
node = NodeMgrClass::Get_Next (node, NODE_TYPE_TILE))
{
PhysClass *phys_obj = node->Peek_Physics_Obj ();
if (phys_obj != NULL) {
phys_obj->Set_Collision_Group (15);
}
}
return ;
}
//////////////////////////////////////////////////////////////////////////
//
// Restore_Level
//
//////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Restore_Level (void)
{
//
// Restore each tile's collision group...
//
for ( NodeClass *node = NodeMgrClass::Get_First (NODE_TYPE_TILE);
node != NULL;
node = NodeMgrClass::Get_Next (node, NODE_TYPE_TILE))
{
PhysClass *phys_obj = node->Peek_Physics_Obj ();
if (phys_obj != NULL) {
phys_obj->Set_Collision_Group (EDITOR_COLLISION_GROUP);
}
}
//
// Get the list of all static objects from the physics scene
//
RefPhysListIterator iterator = ::Get_Scene_Editor ()->Get_Static_Object_Iterator ();
for (iterator.First (); !iterator.Is_Done (); iterator.Next ()) {
//
// Is this object a static anim phys?
//
PhysClass *phys_obj = iterator.Peek_Obj ();
StaticAnimPhysClass *static_phys_obj = phys_obj->As_StaticAnimPhysClass ();
if (static_phys_obj != NULL) {
//
// Do we need to re-enable collisions with this object?
//
StaticAnimPhysDefClass *definition = static_phys_obj->Get_StaticAnimPhysDef ();
if (definition->Does_Collide_In_Pathfind () == false) {
phys_obj->Dec_Ignore_Counter ();
}
}
}
return ;
}
//////////////////////////////////////////////////////////////////////////
//
// Initialize
//
//////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Initialize (void)
{
Prepare_Level ();
//
// Turn off sector display
//
PathfindClass::Get_Instance ()->Display_Sectors (false);
PathfindClass::Get_Instance ()->Display_Portals (false);
//
// Initialize the dynamic vector array's so they will resize
// in appropriate size steps (to decrease the total number of resizes)
//
m_FloodFillProcessList.Set_Growth_Step (20000);
m_PossiblePortalList.Set_Growth_Step (20000);
m_BodyBoxReleaseList.Set_Growth_Step (100000);
//
// Make a guess at the total number of boxes in the world based on
// its size
//
Vector3 lev_min;
Vector3 lev_max;
PhysicsSceneClass::Get_Instance ()->Get_Level_Extents (lev_min, lev_max);
int width = (lev_max.X - lev_min.X) / m_SimBoundingBox.X;
int height = (lev_max.Y - lev_min.Y) / m_SimBoundingBox.Y;
m_TotalBoxGuess = width * height;
//
// Show some UI
//
Show_Dialog ();
return ;
}
//////////////////////////////////////////////////////////////////////////
//
// Shutdown
//
//////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Shutdown (void)
{
//
// Do some cleanup
//
Free_Floodfill_Boxes ();
Cleanup_Level_Features ();
//
// Make sure we re-partition the sector culling system so
// it does us some good... :)
//
PathfindClass::Get_Instance ()->Re_Partition_Sector_Tree ();
//
// Shutdown the UI and restore any level objects we modified
//
Close_Dialog ();
Restore_Level ();
return ;
}
//////////////////////////////////////////////////////////////////////////
//
// Compress_Sectors_For_Dyna_Culling
//
//////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Compress_Sectors_For_Dyna_Culling (void)
{
const float MAX_DYNAMIC_VIS_CELL_DIMENSION = 20.0f;
//
// Change the dialog's checkmark so the user knows we are comprssing sectors
//
m_pDialog->Set_State(GeneratingPathfindDialogClass::STATE_COMPRESS);
//
// Floodfill the level and compress the floodfill into sectors
//
DynamicVectorClass<AABoxClass> sectors;
Set_Max_Sector_Size(MAX_DYNAMIC_VIS_CELL_DIMENSION);
Compress_Sectors(&sectors);
//
// Hand the sectors into the scene (NOTE: this will have to reset the
// VIS system)
//
PhysicsSceneClass::Get_Instance()->Re_Partition_Dynamic_Culling_System(sectors);
return;
}
//////////////////////////////////////////////////////////////////////////
//
// Compress_Sectors_For_Pathfind
//
//////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Compress_Sectors_For_Pathfind (void)
{
//
// Discard any previously generated data
//
PathfindClass::Get_Instance ()->Reset_Sectors ();
PathfindClass::Get_Instance ()->Reset_Portals ();
//
// Change the dialog's checkmark so the user knows we are comprssing sectors
//
m_pDialog->Set_State (GeneratingPathfindDialogClass::STATE_COMPRESS);
//
// Compress the sectors into the largest possible rectangular regions
//
DWORD before_ticks = ::GetTickCount ();
Post_Process_Floodfill_For_Level_Features ();
Compress_Sectors ();
Generate_Portals ();
DWORD after_ticks = ::GetTickCount ();
WWDEBUG_SAY(("Time spent compressing: %d secs.\r\n", (after_ticks-before_ticks)/1000));
//
// Loop through the level and create any portals around
// ladders, doors, etc that may be needed.
//
Apply_Level_Features ();
return ;
}
//////////////////////////////////////////////////////////////////////////
//
// Generate_Sectors
//
//////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Generate_Sectors (void)
{
//
// Start floodfilling from each of the start points
//
for (int index = 0; index < m_StartPointList.Count (); index ++) {
Vector3 start_pos = m_StartPointList[index];
//
// Normalize the starting position. We do this so that all the different
// generation points will match up perfectly.
//
start_pos.X = (int(start_pos.X / m_SimBoundingBox.X)) * m_SimBoundingBox.X;
start_pos.Y = (int(start_pos.Y / m_SimBoundingBox.Y)) * m_SimBoundingBox.Y;
start_pos.Z += (m_SimBoundingBox.Z * 0.5F);
//
// Start floodfilling from this position
//
AABoxClass new_box;
if (Try_Standing_Here (start_pos, &new_box)) {
m_CurrentSector = Mark_Sector (new_box);
Floodfill (start_pos);
}
}
//
// Process all the floodfill boxes that have been queued up
//
int compress_count = 0;
while (m_FloodFillProcessList.Count () > 0) {
//
// Pop a floodfill box off the list and process its neighbors
//
m_CurrentSector = m_FloodFillProcessList[0];
m_FloodFillProcessList.Delete (0);
AABoxClass box = Get_Body_Box_Bounds (m_CurrentSector);
Vector3 pos = box.Center;
pos.Z = box.Center.Z - box.Extent.Z + (m_SimBoundingBox.Z * 0.5F);
Floodfill (pos);
}
return ;
}
//////////////////////////////////////////////////////////////////////////
//
// Find_Ground
//
//////////////////////////////////////////////////////////////////////////
bool
PathfindSectorBuilderClass::Find_Ground (const Vector3 &pos, float *ground_pos)
{
bool retval = false;
AABoxClass sweep_box;
sweep_box.Center = pos;
sweep_box.Center.Z += m_StepHeight;
sweep_box.Extent = m_SimBoxExtents;
sweep_box.Extent.Z = 0.1F;
//
// Sweep a very short box downwards to see where the ground is
//
CastResultStruct result;
Vector3 sweep_vector (0, 0, -6.5F);
PhysAABoxCollisionTestClass test( sweep_box,
sweep_vector,
&result,
STATIC_OBJ_COLLISION_GROUP,
COLLISION_TYPE_PHYSICAL);
PhysicsSceneClass::Get_Instance ()->Cast_AABox (test);
//
// Check to see if we found a floor, and its valid to stand on.
//
if ( (result.StartBad == false) &&
(result.Fraction < 1.0F) &&
(result.Normal.Z > 0.71F) &&
(m_AllowWaterFloodfill || (result.SurfaceType != SURFACE_TYPE_WATER)))
{
//
// Calculate what z-position the ground is
//
(*ground_pos) = (sweep_box.Center.Z + (sweep_vector.Z * result.Fraction)) - sweep_box.Extent.Z;
retval = true;
}
return retval;
}
//////////////////////////////////////////////////////////////////////////
//
// Find_Ceiling
//
//////////////////////////////////////////////////////////////////////////
bool
PathfindSectorBuilderClass::Find_Ceiling (const Vector3 &pos, float *ceiling_pos)
{
bool retval = false;
AABoxClass sweep_box;
sweep_box.Center = pos;
sweep_box.Center.Z += m_StepHeight;
sweep_box.Extent = m_SimBoxExtents;
sweep_box.Extent.Z = 0.1F;
//
// Sweep a very short box downwards to see where the ground is
//
CastResultStruct result;
Vector3 sweep_vector (0, 0, 6.5F);
PhysAABoxCollisionTestClass test( sweep_box,
sweep_vector,
&result,
STATIC_OBJ_COLLISION_GROUP,
COLLISION_TYPE_PHYSICAL);
PhysicsSceneClass::Get_Instance ()->Cast_AABox (test);
//
// Check to ensure we didn't start the cast intersecting a poly
//
if (result.StartBad == false) {
//
// Calculate what the z-position of the ceiling is
//
(*ceiling_pos) = (sweep_box.Center.Z + (sweep_vector.Z * result.Fraction)) + sweep_box.Extent.Z;
retval = true;
}
return retval;
}
//////////////////////////////////////////////////////////////////////////
//
// Try_Moving_Here
//
//////////////////////////////////////////////////////////////////////////
bool
PathfindSectorBuilderClass::Try_Moving_Here
(
const Vector3 & start_pos,
const Vector3 & expected_pos,
AABoxClass * real_pos
)
{
bool is_valid = false;
float start_ground_pos = start_pos.Z - m_SimBoxExtents.Z;
float curr_ground_pos = 0;
//
// Find the ground level where we were standing and the ground level where
// we want to be standing
//
if (Find_Ground (expected_pos, &curr_ground_pos)) {
//
// Check to see if the character can step from the old position to the new one
//
float step_height = curr_ground_pos - start_ground_pos;
if (step_height < m_StepHeight) {
//
// Find the ceiling
//
float curr_ceiling_pos = curr_ground_pos;
Find_Ceiling (expected_pos, &curr_ceiling_pos);
//
// Can the character fit between the floor and ceiling without crouching?
//
if ((curr_ceiling_pos - curr_ground_pos) > m_SimBoundingBox.Z) {
//
// Success! Build a bounding box for the new position
//
real_pos->Extent.X = m_SimBoxExtents.X;
real_pos->Extent.Y = m_SimBoxExtents.Y;
real_pos->Extent.Z = m_SimBoxExtents.Z;
real_pos->Center.X = expected_pos.X;
real_pos->Center.Y = expected_pos.Y;
real_pos->Center.Z = curr_ground_pos + m_SimBoxExtents.Z + WWMATH_EPSILON;
is_valid = true;
}
}
}
return is_valid;
}
//////////////////////////////////////////////////////////////////////////
//
// Try_Standing_Here
//
//////////////////////////////////////////////////////////////////////////
bool
PathfindSectorBuilderClass::Try_Standing_Here
(
const Vector3 & expected_pos,
AABoxClass * real_pos
)
{
bool is_valid = false;
AABoxClass sweep_box;
sweep_box.Center = expected_pos;
sweep_box.Center.Z += m_StepHeight;
sweep_box.Extent = m_SimSweepBox.Extent;
sweep_box.Extent.Z -= m_StepHeight;
//
// Do a box cast to see if we can 'stand' here.
//
CastResultStruct result;
PhysAABoxCollisionTestClass test( sweep_box,
m_SimMovement,
&result,
STATIC_OBJ_COLLISION_GROUP,
COLLISION_TYPE_PHYSICAL);
test.CheckStaticObjs = true;
test.CheckDynamicObjs = false;
PhysicsSceneClass::Get_Instance ()->Cast_AABox (test);
//
// Is the start of the cast valid and is the polygon we
// landed on flat enough to stand on?
//
if ((result.StartBad == false) && (result.Normal.Z > 0.71F)) {
//
// Are we standing on the ground? Is the ground a valid type?
//
bool on_ground = (result.Fraction < 1.0F);
if (on_ground && (m_AllowWaterFloodfill || (result.SurfaceType != SURFACE_TYPE_WATER))) {
//
// Snap the x and y positions and calculate the z position
// of the new box.
//
real_pos->Extent.X = sweep_box.Extent.X;
real_pos->Extent.Y = sweep_box.Extent.Y;
real_pos->Extent.Z = ((-m_SimMovement.Z * result.Fraction) * 0.5F) + (m_SimBoundingBox.Z * 0.5F);
real_pos->Center.X = expected_pos.X;
real_pos->Center.Y = expected_pos.Y;
real_pos->Center.Z = (sweep_box.Center.Z + (m_SimBoundingBox.Z * 0.5F) - real_pos->Extent.Z) + 0.001F;
is_valid = true;
}
}
return is_valid;
}
//////////////////////////////////////////////////////////////////////////
//
// Do_Physics_Sim
//
//////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Do_Physics_Sim
(
const Vector3 & start_pos,
PATHFIND_DIR direction
)
{
//
// Determine where the next cell should be
//
Vector3 expected_pos = start_pos + m_DirInfo[direction].move;
//
// Do a quick-and-dirty test to see if we already 'know' about a
// body-box in the requested direction.
//
bool found = false;
FloodfillBoxClass *neighbor = m_CurrentSector->Peek_Neighbor (direction, false);
if (neighbor != NULL) {
m_SimSweepBox.Center = expected_pos;
//
// If the neighbor's bounding box completely CONTAINS the bounding
// box we would have cast, then we can skip the cast and simply
// mark the direction as 'traversible'
//
AABoxClass bounding_box = Get_Body_Box_Bounds (neighbor);
found = bounding_box.Contains (m_SimSweepBox);
float min1 = bounding_box.Center.Z - bounding_box.Extent.Z;
float max1 = bounding_box.Center.Z + bounding_box.Extent.Z;
float min2 = m_SimSweepBox.Center.Z - m_SimSweepBox.Extent.Z;
float max2 = m_SimSweepBox.Center.Z + m_SimSweepBox.Extent.Z;
found = (min1 <= min2) && (max1 >= max2);
if (found) {
m_CurrentSector->Set_Neighbor (direction, neighbor, true);
}
}
//
// Did the quick-and-dirty check fail? If so, then do the full box-cast
//
if (found == false) {
Vector3 move_vector = (m_DirInfo[direction].move);
Vector3 new_pos = start_pos + move_vector;
AABoxClass new_box;
//if (Try_Standing_Here (new_pos, &new_box)) {
if (Try_Moving_Here (start_pos, new_pos, &new_box)) {
Submit_Box (m_CurrentSector, new_box, direction);
}
}
return ;
}
//////////////////////////////////////////////////////////////////////////
//
// Do_Real_Physics_Sim
//
//////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Do_Real_Physics_Sim
(
const Vector3 & start_pos,
PATHFIND_DIR direction
)
{
//
// Determine where the next cell should be
//
Vector3 expected_pos = start_pos + m_DirInfo[direction].move;
//
// Do a quick-and-dirty test to see if we already 'know' about a
// body-box in the requested direction.
//
bool found = false;
FloodfillBoxClass *neighbor = m_CurrentSector->Peek_Neighbor (direction, false);
if (neighbor != NULL) {
m_SimSweepBox.Center = expected_pos;
//
// If the neighbor's bounding box completely CONTAINS the bounding
// box we would have cast, then we can skip the cast and simply
// mark the direction as 'traversible'
//
AABoxClass bounding_box = Get_Body_Box_Bounds (neighbor);
found = bounding_box.Contains (m_SimSweepBox);
float min1 = bounding_box.Center.Z - bounding_box.Extent.Z;
float max1 = bounding_box.Center.Z + bounding_box.Extent.Z;
float min2 = m_SimSweepBox.Center.Z - m_SimSweepBox.Extent.Z;
float max2 = m_SimSweepBox.Center.Z + m_SimSweepBox.Extent.Z;
found = (min1 <= min2) && (max1 >= max2);
if (found) {
m_CurrentSector->Set_Neighbor (direction, neighbor, true);
}
}
//
// Did the quick-and-dirty check fail? If so, then do the full box-cast
//
if (found == false) {
Vector3 move_vector = (m_DirInfo[direction].move);
Vector3 new_pos = start_pos + move_vector;
AABoxClass test_box (start_pos, m_SimBoxExtents);
test_box.Add_Box (AABoxClass (new_pos, m_SimBoxExtents));
test_box.Center.Z += 0.1F;
NonRefPhysListClass obj_list;
PhysicsSceneClass::Get_Instance ()->Collect_Collideable_Objects (test_box, COLLISION_TYPE_PHYSICAL, true, false, &obj_list);
if (obj_list.Count () == 0) {
AABoxClass new_box;
if (Try_Standing_Here (new_pos, &new_box)) {
Submit_Box (m_CurrentSector, new_box, direction);
}
return ;
} else {
bool did_intersect = false;
NonRefPhysListIterator it (&obj_list);
for (it.First(); !it.Is_Done(); it.Next()) {
PhysClass *phys_obj = it.Peek_Obj ();
if (phys_obj != NULL) {
RenderObjClass *model = phys_obj->Peek_Model ();
AABoxIntersectionTestClass int_test (test_box, COLLISION_TYPE_PHYSICAL);
if (model->Intersect_AABox (int_test)) {
did_intersect = true;
break;
}
}
}
if (did_intersect == false) {
AABoxClass new_box;
if (Try_Standing_Here (new_pos, &new_box)) {
Submit_Box (m_CurrentSector, new_box, direction);
}
return ;
}
}
_PhysSimObj->Set_Position (start_pos);
_PhysSimObj->Set_Velocity (Vector3 (0, 0, 0));
for (int attempt = 0; attempt < 20; attempt ++) {
if (_PhysSimObj->Is_In_Contact ()) {
break;
}
_PhysSimObj->Timestep (0.1F);
}
bool keep_going = true;
for (attempt = 0; keep_going && (attempt < 20); attempt ++) {
Vector3 curr_pos = _PhysSimObj->Get_Position ();
Vector3 delta = (new_pos - curr_pos);
delta.Z = 0;
float delta_len = delta.Length ();
if (delta_len < 0.05F && _PhysSimObj->Is_In_Contact ()) {
AABoxClass new_box;
new_box.Center = new_pos;
new_box.Center.Z = curr_pos.Z;
new_box.Extent = m_SimBoxExtents;
Submit_Box (m_CurrentSector, new_box, direction);
keep_going = false;
} else {
Vector3 x_axis = (new_pos - curr_pos);
Vector3 z_axis = Vector3 (0, 0, 1);
x_axis.Normalize ();
Vector3 y_axis = Vector3::Cross_Product (x_axis, z_axis);
Matrix3D new_tm (x_axis, y_axis, z_axis, curr_pos);
_PhysSimObj->Set_Transform (new_tm);
_PhysSimObj->Get_Controller ()->Reset ();
_PhysSimObj->Get_Controller ()->Set_Move_Forward (0.09F);
_PhysSimObj->Timestep (0.075F);
}
}
/*AABoxClass new_box;
if (Try_Standing_Here (new_pos, &new_box)) {
Submit_Box (m_CurrentSector, new_box, direction);
}*/
}
return ;
}
//////////////////////////////////////////////////////////////////////////
//
// Submit_Box
//
//////////////////////////////////////////////////////////////////////////
FloodfillBoxClass *
PathfindSectorBuilderClass::Submit_Box
(
FloodfillBoxClass * from_obj,
const AABoxClass & new_box,
PATHFIND_DIR direction
)
{
//
// Is this cell already marked?
//
Vector3 position = new_box.Center;
position.Z = new_box.Center.Z - new_box.Extent.Z + (m_SimBoundingBox.Z * 0.5F);
FloodfillBoxClass *occupant = Get_Sector_Occupant (position);
if (occupant == NULL) {
//
// Mark this cell and add it to the list of sectors
// that need to be recursed.
//
occupant = Mark_Sector (new_box);
m_FloodFillProcessList.Add (occupant);
//
// Let the new occupant know where it came from (even if it can't get there itself).
//
occupant->Set_Neighbor (::Inverse_Pathfind_Dir (direction), from_obj, false);
m_TotalBoxCount ++;
}
//
// Let the sector know who its new neighbor is
//
if (occupant != from_obj) {
from_obj->Set_Neighbor (direction, occupant, true);
}
return occupant;
}
//////////////////////////////////////////////////////////////////////////
//
// Floodfill
//
//////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Floodfill (const Vector3 &start_pos)
{
//
// Do a simulation in each of the four directions from the
// current cell.
//
if (m_pDialog->Was_Cancelled () == false) {
//
// Loop through and check any neighbors we don't already know about
//
for (int index = 0; index < DIR_MAX; index ++) {
if (m_CurrentSector->Peek_Neighbor (PATHFIND_DIR(index)) == NULL) {
Do_Physics_Sim (start_pos, PATHFIND_DIR(index));
m_BeforeUpdateCount ++;
}
}
//
// Check to see if we need to climb up any ladders, open doors, etc...
//
Check_For_Level_Feature (m_CurrentSector);
//
// Let the current sector 'know' its been completely processed
//
m_CurrentSector->Needs_Processing (false);
//
// Update the UI
//
if (m_BeforeUpdateCount > 512) {
CString message;
message.Format ("Floodfilled %d boxes. Approx total boxes: %d", m_TotalBoxCount, m_TotalBoxGuess);
m_pDialog->Set_Status (message, float (m_TotalBoxCount) / float (m_TotalBoxGuess));
m_BeforeUpdateCount = 0;
}
}
//
// Allow windows messages to be processed
//
static int message_pump = 0;
message_pump++;
if (message_pump > 5000) {
General_Pump_Messages ();
message_pump = 0;
}
return ;
}
//////////////////////////////////////////////////////////////////////////
//
// Get_Sector_Occupant
//
//////////////////////////////////////////////////////////////////////////
FloodfillBoxClass *
PathfindSectorBuilderClass::Get_Sector_Occupant (const Vector3 &pos)
{
//
// If we found any objects in the culling system, then the sector is occupied
//
return m_BodyBoxCullingSystem.Find_Box (pos);
}
//////////////////////////////////////////////////////////////////////////
//
// Mark_Sector
//
//////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Mark_Sector (FloodfillBoxClass *body_box)
{
//
// Add the culling object to our culling system. (This
// effectively marks the cell as 'occupied').
//
body_box->Add_Ref ();
m_BodyBoxCullingSystem.Add_Box (body_box);
FloodfillBoxClass::Add (body_box);
return ;
}
//////////////////////////////////////////////////////////////////////////
//
// Mark_Sector
//
//////////////////////////////////////////////////////////////////////////
FloodfillBoxClass *
PathfindSectorBuilderClass::Mark_Sector (const AABoxClass &bounding_box)
{
//
// Create a culling system object to represent this 'sector' and
// add it to our AABTree culling system. (This effectively marks
// the cell as 'occupied').
//
FloodfillBoxClass *body_box = new FloodfillBoxClass;
body_box->Set_Position (bounding_box.Center);
Mark_Sector (body_box);
body_box->Release_Ref ();
return body_box;
}
///////////////////////////////////////////////////////////////////////////
//
// Determine_Height
//
///////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Determine_Height
(
FloodfillBoxClass * start_box,
float * min_z_pos,
float * max_z_pos
)
{
AABoxClass box = Get_Body_Box_Bounds (start_box);
float z_extent = box.Extent.Z;
box.Extent.X -= WWMATH_EPSILON;
box.Extent.Y -= WWMATH_EPSILON;
box.Extent.Z = 500.0F;
//
// Loop over all the body-boxes that exist above or below the starting box
//
m_BodyBoxCullingSystem.Collect_Boxes (box);
DynamicVectorClass<FloodfillBoxClass *> &list = m_BodyBoxCullingSystem.Get_Collection_List ();
for (int index = 0; index < list.Count (); index ++) {
FloodfillBoxClass *body_box = list[index];
//
// Only check 'other' boxes
//
if (body_box != start_box) {
AABoxClass curr_box = Get_Body_Box_Bounds (body_box);
//
// Determine the min and max z-positions of the current box
//
float curr_min_z = curr_box.Center.Z - (curr_box.Extent.Z + WWMATH_EPSILON);
float curr_max_z = curr_box.Center.Z + (curr_box.Extent.Z + WWMATH_EPSILON);
//
// Return new minimum and maximum
//
if (curr_max_z < box.Center.Z && curr_max_z > (*min_z_pos)) {
(*min_z_pos) = curr_max_z;
}
if (curr_min_z > box.Center.Z && curr_min_z < (*max_z_pos)) {
(*max_z_pos) = curr_min_z;
}
}
}
/*float delta_up = (*max_z_pos) - box.Center.Z;
float delta_down = box.Center.Z - (*min_z_pos);
delta_up = max (delta_up / 2.0F, (z_extent + WWMATH_EPSILON));
delta_down = max (delta_down / 2.0F, (z_extent + WWMATH_EPSILON));
(*max_z_pos) = box.Center.Z + delta_up;
(*min_z_pos) = box.Center.Z - delta_down;*/
//
// Make sure a box's min position is at least the
// bottom of its box.
//
float box_bottom = (box.Center.Z - z_extent);
if ((*min_z_pos) > (box_bottom - 0.5F)) {
(*min_z_pos) = (box_bottom - 0.5F);
}
return ;
}
//////////////////////////////////////////////////////////////////////////
//
// Build_Height_Values
//
//////////////////////////////////////////////////////////////////////////
int
PathfindSectorBuilderClass::Build_Height_Values (void)
{
int before_ticks = ::GetTickCount ();
int total_box_count = 0;
int box_count = 0;
//
// Backup the body-box list to ensure we delete them all...
//
for ( FloodfillBoxClass *body_box = FloodfillBoxClass::Get_First ();
body_box != NULL;
body_box = body_box->Get_Next ())
{
m_BodyBoxReleaseList.Add (body_box);
Vector3 pos = body_box->Get_Position ();
float min_z = pos.Z - (m_SimBoundingBox.Z * 500.0F);
float max_z = pos.Z + (m_SimBoundingBox.Z * 500.0F);
Determine_Height (body_box, &min_z, &max_z);
body_box->Set_Min_Z_Pos (min_z);
body_box->Set_Max_Z_Pos (max_z);
total_box_count ++;
}
int after_ticks = ::GetTickCount ();
WWDEBUG_SAY(("Time spent generating z-values: %d secs.\r\n", (after_ticks-before_ticks)/1000));
return total_box_count;
}
//////////////////////////////////////////////////////////////////////////
//
// Can_Traverse
//
//////////////////////////////////////////////////////////////////////////
inline bool
Can_Traverse (FloodfillBoxClass *cell, PATHFIND_DIR dir)
{
//
// Make sure the cell passes the basic 'traverse-requirements'
// to move in the given direction
//
return ( cell != NULL &&
cell->Needs_Processing () == false &&
cell->Is_Taken () == false);
}
//////////////////////////////////////////////////////////////////////////
//
// Check_Directions
//
//////////////////////////////////////////////////////////////////////////
inline bool
Check_Directions (FloodfillBoxClass *cell, int dir_mask)
{
bool is_valid = true;
//
// Check to make sure we can walk from the cell to its
// adjacent boxes (Only checking the requested directions).
//
for (int mask = 1; mask < MASK_MAX; mask <<= 1) {
if (dir_mask & mask) {
PATHFIND_DIR check_dir = ::Mask_to_Dir (DIRECTION_MASK(mask));
is_valid &= cell->Is_Two_Way_Traversible (check_dir);
}
}
return is_valid;
}
//////////////////////////////////////////////////////////////////////////
//
// Move_Dir
//
//////////////////////////////////////////////////////////////////////////
inline FloodfillBoxClass *
PathfindSectorBuilderClass::Move_Dir (FloodfillBoxClass *start_box, PATHFIND_DIR dir, int dir_mask)
{
//
// Peek a the cell in the given direction
//
FloodfillBoxClass *retval = NULL;
FloodfillBoxClass *new_cell = start_box->Peek_Neighbor (dir);
//
// Does this cell pass our traversal requirements?
//
if ( start_box->Is_Two_Way_Traversible (dir) &&
Can_Traverse (new_cell, dir) &&
Check_Directions (new_cell, dir_mask))
{
//
// Add this cell's min and max heights to the height-watcher.
// This is done so we can break the sector before it intersects
// a sector above or below it
//
m_HeightWatcher.Add_Lower_Bound (new_cell->Get_Min_Z_Pos ());
m_HeightWatcher.Add_Upper_Bound (new_cell->Get_Max_Z_Pos ());
//
// Is this cell within the min and max height values?
//
AABoxClass box = Get_Body_Box_Bounds (new_cell);
if ( m_HeightWatcher.Is_Current_Pos_Valid (box.Center.Z - box.Extent.Z) &&
m_HeightWatcher.Is_Current_Pos_Valid (box.Center.Z + box.Extent.Z))
{
retval = new_cell;
}
}
return retval;
}
//////////////////////////////////////////////////////////////////////////
//
// Check_Edge
//
//////////////////////////////////////////////////////////////////////////
inline bool
PathfindSectorBuilderClass::Check_Edge
(
FloodfillBoxClass * start_box,
int count_left,
int count_right,
int count_up,
int count_down,
PATHFIND_DIR move_dir
)
{
bool retval = false;
//
// Check the first cell, can we move in the requested direction?
//
int dir_mask = 0;
dir_mask |= (count_left > 0) ? MASK_LEFT : 0;
dir_mask |= (count_right > 0) ? MASK_RIGHT : 0;
dir_mask |= (count_up > 0) ? MASK_UP : 0;
dir_mask |= (count_down > 0) ? MASK_DOWN : 0;
if (Move_Dir (start_box, move_dir, dir_mask) != NULL) {
retval = true;
}
//
// Check the given number of cells in the left direction
//
FloodfillBoxClass *curr_cell = start_box;
for (int x = 0; retval && x < count_left; x++) {
//
// Can we move one step in this direction?
//
curr_cell = Move_Dir (curr_cell, DIR_LEFT, 0);
if (curr_cell != NULL) {
//
// Can this new cell move in the movement direction AND
// can its new cell move right and, if necessary, left?
//
dir_mask = MASK_RIGHT;
dir_mask |= ((x + 1) < count_left) ? MASK_LEFT : 0;
retval = (Move_Dir (curr_cell, move_dir, dir_mask) != NULL);
} else {
retval = false;
}
}
//
// Check the given number of cells in the left direction
//
curr_cell = start_box;
for (x = 0; retval && x < count_right; x++) {
//
// Can we move one step in this direction?
//
curr_cell = Move_Dir (curr_cell, DIR_RIGHT, 0);
if (curr_cell != NULL) {
//
// Can this new cell move in the movement direction AND
// can its new cell move left and, if necessary, right?
//
dir_mask = MASK_LEFT;
dir_mask |= ((x + 1) < count_right) ? MASK_RIGHT : 0;
retval = (Move_Dir (curr_cell, move_dir, dir_mask) != NULL);
} else {
retval = false;
}
}
//
// Check the given number of cells in the up direction
//
curr_cell = start_box;
for (int y = 0; retval && y < count_up; y++) {
//
// Can we move one step in this direction?
//
curr_cell = Move_Dir (curr_cell, DIR_UP, 0);
if (curr_cell != NULL) {
//
// Can this new cell move in the movement direction AND
// can its new cell move down and, if necessary, up?
//
dir_mask = MASK_DOWN;
dir_mask |= ((y + 1) < count_up) ? MASK_UP : 0;
retval = (Move_Dir (curr_cell, move_dir, dir_mask) != NULL);
} else {
retval = false;
}
}
//
// Check the given number of cells in the down direction
//
curr_cell = start_box;
for (y = 0; retval && y < count_down; y++) {
//
// Can we move one step in this direction?
//
curr_cell = Move_Dir (curr_cell, DIR_DOWN, 0);
if (curr_cell != NULL) {
//
// Can this new cell move in the movement direction AND
// can its new cell move up and, if necessary, down?
//
dir_mask = MASK_UP;
dir_mask |= ((y + 1) < count_down) ? MASK_DOWN : 0;
retval = (Move_Dir (curr_cell, move_dir, dir_mask) != NULL);
} else {
retval = false;
}
}
return retval;
}
//////////////////////////////////////////////////////////////////////////
//
// Find_Perimeter
//
//////////////////////////////////////////////////////////////////////////
FloodfillBoxClass *
PathfindSectorBuilderClass::Find_Perimeter (FloodfillBoxClass *start_box, BOX_PERIMETER *perimeter)
{
FloodfillBoxClass *up_cell = start_box;
FloodfillBoxClass *down_cell = start_box;
FloodfillBoxClass *left_cell = start_box;
FloodfillBoxClass *right_cell = start_box;
//
// Determine what the maximum number of cells in a given direction can be
//
int max_dim = m_MaxSectorDim / max (m_SimBoundingBox.X, m_SimBoundingBox.Y);
//
// Create an object that will allow us to break the sector if
// it would intersect with a sector above or below us.
//
Vector3 pos = start_box->Get_Position ();
m_HeightWatcher.Initialize (pos.Z - (m_SimBoundingBox.Z * 500.0F), pos.Z + (m_SimBoundingBox.Z * 500.0F), pos.Z);
do
{
//
// Expand the box 'up' one row (if possible)
//
if (perimeter->stop_up == false) {
perimeter->stop_up = true;
FloodfillBoxClass *new_cell = Move_Dir (up_cell, DIR_UP, 0);
if (new_cell != NULL) {
if (Check_Edge (up_cell, perimeter->count_left, perimeter->count_right, 0, 0, DIR_UP)) {
up_cell = new_cell;
perimeter->count_up ++;
perimeter->stop_up = false;
}
}
}
//
// Expand the box 'down' one row (if possible)
//
if (perimeter->stop_down == false) {
perimeter->stop_down = true;
FloodfillBoxClass *new_cell = Move_Dir (down_cell, DIR_DOWN, 0);
if (new_cell != NULL) {
if (Check_Edge (down_cell, perimeter->count_left, perimeter->count_right, 0, 0, DIR_DOWN)) {
down_cell = new_cell;
perimeter->count_down ++;
perimeter->stop_down = false;
}
}
}
//
// Expand the box 'left' one column (if possible)
//
if (perimeter->stop_left == false) {
perimeter->stop_left = true;
FloodfillBoxClass *new_cell = Move_Dir (left_cell, DIR_LEFT, 0);
if (new_cell != NULL) {
if (Check_Edge (left_cell, 0, 0, perimeter->count_up, perimeter->count_down, DIR_LEFT)) {
left_cell = new_cell;
perimeter->count_left ++;
perimeter->stop_left = false;
}
}
}
//
// Expand the box 'right' one column (if possible)
//
if (perimeter->stop_right == false) {
perimeter->stop_right = true;
FloodfillBoxClass *new_cell = Move_Dir (right_cell, DIR_RIGHT, 0);
if (new_cell != NULL) {
if (Check_Edge (right_cell, 0, 0, perimeter->count_up, perimeter->count_down, DIR_RIGHT)) {
right_cell = new_cell;
perimeter->count_right ++;
perimeter->stop_right = false;
}
}
}
//
// Check to see if we are going to exceed the maximum sector size in either
// direction.
//
if (perimeter->count_left + perimeter->count_right >= max_dim) {
perimeter->stop_left = true;
perimeter->stop_right = true;
}
if (perimeter->count_up + perimeter->count_down >= max_dim) {
perimeter->stop_up = true;
perimeter->stop_down = true;
}
}
while ( perimeter->stop_up == false || perimeter->stop_down == false ||
perimeter->stop_left == false || perimeter->stop_right == false);
//
// Some debug checking to make sure we built the region
// correctly.
//
FloodfillBoxClass *upper_left1 = up_cell;
FloodfillBoxClass *upper_left2 = left_cell;
for (int index = 0; index < perimeter->count_left; index ++) {
upper_left1 = upper_left1->Peek_Neighbor (DIR_LEFT);
}
for (index = 0; index < perimeter->count_up; index ++) {
upper_left2 = upper_left2->Peek_Neighbor (DIR_UP);
}
WWASSERT (upper_left1 == upper_left2);
if (upper_left1 != upper_left2) {
CString message;
message.Format ("upper_left1 = %d\nupper_left2 = %d\nup_cell = %d\nleft_cell = %d", upper_left1, upper_left2, up_cell, left_cell);
::MessageBox (NULL, message, "Info", MB_OK);
}
return upper_left1;
}
//////////////////////////////////////////////////////////////////////////
//
// Compress_Sectors
//
//////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Compress_Sectors (DynamicVectorClass<AABoxClass> *box_list)
{
//
// Generate a set of maximum and minimum height values
// for each box
//
int total_box_count = Build_Height_Values ();
int box_count = 0;
bool keep_going = true;
FloodfillBoxClass *start_box = NULL;
FloodfillBoxClass *old_start_box = NULL;
BOX_PERIMETER old_perimeter = { 0 };
//
// Loop over all the boxes and compress them into rectangular regions
//
while (keep_going && ((start_box = FloodfillBoxClass::Get_First ()) != NULL)) {
//
// Don't compress any boxes that still need processing
//
if (start_box->Needs_Processing ()) {
start_box->Remove ();
continue;
}
//
// Perform a simple check to make sure we don't keep getting the same
// box off the top of the list (this should never, ever, ever, happen).
//
if ( (start_box == old_start_box) &&
(FloodfillBoxClass::Get_First () != FloodfillBoxClass::Get_Last ()))
{
::MessageBox (::AfxGetMainWnd ()->m_hWnd, "Found the same box twice...", "Compression Error", MB_OK | MB_ICONERROR);
keep_going = false;
}
old_start_box = start_box;
//
// Find the largest rectangular region that this box can be part of
//
BOX_PERIMETER perimeter = { 0 };
FloodfillBoxClass *upper_left_cell = Find_Perimeter (start_box, &perimeter);
old_perimeter = perimeter;
//
// How big is the new rectangular region?
//
int width = (perimeter.count_left + perimeter.count_right) + 1;
int height = (perimeter.count_up + perimeter.count_down) + 1;
//
// If the area is of an exceptable size, then compress it into a sector
//
if (width > 2 || height > 2) {
int skipped_count = start_box->Get_Compress_Skipped_Count ();
int min_dim = min (width, height);
//
// Should we skip this box in hopes that we find a better sector later?
//
if (skipped_count <= (12 - min_dim)) {
start_box->Inc_Compress_Skipped_Count ();
start_box->Remove ();
FloodfillBoxClass::Add (start_box);
} else {
//
// Compress the region into a new pathfinding sector
//
PathfindSectorClass *sector = Build_Sector (upper_left_cell, width, height);
if (sector != NULL) {
//
// Sumbit this sector
//
if (box_list != NULL) {
AABoxClass bbox = sector->Get_Bounding_Box ();
box_list->Add (bbox);
MEMBER_RELEASE (sector);
} else {
PathfindClass::Get_Instance ()->Add_Sector (sector);
}
}
//
// Update the UI
//
box_count += (width * height);
CString message;
message.Format ("Compressed %d of %d boxes.", box_count, total_box_count);
m_pDialog->Set_Status (message, ((float)box_count) / ((float)total_box_count));
}
} else {
//
// Sector is too small, so remove the box
// from the system.
//
start_box->Set_Taken ();
start_box->Remove ();
}
}
return ;
}
//////////////////////////////////////////////////////////////////////////
//
// Filter_Useless_Sectors
//
//////////////////////////////////////////////////////////////////////////
/*void
PathfindSectorBuilderClass::Filter_Useless_Sectors (DynamicVectorClass<PathfindSectorClass *> &sector_list)
{
DynamicVectorClass<PathfindSectorClass *> final_sector_list;
for (int index = 0; index < sector_list.Count (); index ++) {
PathfindSectorClass *sector = sector_list[index];
bool found_usefull_portal = false;
int portal_count = sector->Get_Portal_Count ();
for (int portal_index = 0; portal_index < portal_count; portal_index ++) {
}
if (found_usefull_portal) {
final_sector_list.Add (sector);
}
}
//
// Add all the sectors to the pathfind system
//
for (index = 0; index < final_sector_list.Count (); index ++) {
}
return ;
}*/
//////////////////////////////////////////////////////////////////////////
//
// Scan_Edge
//
//////////////////////////////////////////////////////////////////////////
void
Scan_Edge
(
FloodfillBoxClass * start_obj,
int cell_count,
PATHFIND_DIR check_dir,
PATHFIND_DIR move_dir,
int * best_count
)
{
FloodfillBoxClass * best_first_cell = 0;
FloodfillBoxClass * first_cell = NULL;
int count = 0;
//
// Go along the edge
//
FloodfillBoxClass *box_obj = start_obj;
for (int box_index = 0; box_index < cell_count; box_index ++) {
//
// Can we traverse to the left and back?
//
if (box_obj->Is_Two_Way_Traversible (check_dir)) {
//
// Start recording this run
//
if (first_cell == NULL) {
first_cell = box_obj;
count = 1;
} else {
count ++;
}
} else {
//
// Is this the best run we've found?
//
if (count > (*best_count)) {
best_first_cell = first_cell;
(*best_count) = count;
}
//
// Reset
//
first_cell = NULL;
count = 0;
}
//
// Move to the next edge cell
//
box_obj = box_obj->Peek_Neighbor (move_dir);
}
//
// Is this the best run we've found?
//
if (count > (*best_count)) {
best_first_cell = first_cell;
(*best_count) = count;
}
return ;
}
//////////////////////////////////////////////////////////////////////////
//
// Is_Valid_Sector
//
//////////////////////////////////////////////////////////////////////////
bool
PathfindSectorBuilderClass::Is_Valid_Sector
(
FloodfillBoxClass ** upper_left_ptr,
int & cells_right,
int & cells_down
)
{
bool retval = true;
//
// Is this sector to small?
//
if (cells_right < 3 || cells_down < 3) {
retval = false;
if (cells_right < 3) {
FloodfillBoxClass *ul_ptr = (*upper_left_ptr);
FloodfillBoxClass *ur_ptr = (*upper_left_ptr)->Find_Relative (cells_right - 1, 0);
//
// Determine if either edge can enter an adjacent sector. This
// would make the sector walkable
//
int best_count = 0;
Scan_Edge (ul_ptr, cells_down, DIR_LEFT, DIR_DOWN, &best_count);
Scan_Edge (ur_ptr, cells_down, DIR_RIGHT, DIR_DOWN, &best_count);
//
// Was this run good enough?
//
retval = (best_count >= 3);
} else {
FloodfillBoxClass *ul_ptr = (*upper_left_ptr);
FloodfillBoxClass *ll_ptr = (*upper_left_ptr)->Find_Relative (0, cells_down - 1);
//
// Determine if either edge can enter an adjacent sector. This
// would make the sector walkable
//
int best_count = 0;
Scan_Edge (ul_ptr, cells_right, DIR_UP, DIR_RIGHT, &best_count);
Scan_Edge (ll_ptr, cells_right, DIR_DOWN, DIR_RIGHT, &best_count);
//
// Was this run good enough?
//
retval = (best_count >= 3);
}
}
return retval;
}
//////////////////////////////////////////////////////////////////////////
//
// Build_Sector
//
//////////////////////////////////////////////////////////////////////////
PathfindSectorClass *
PathfindSectorBuilderClass::Build_Sector
(
FloodfillBoxClass * upper_left_ptr,
int cells_right,
int cells_down
)
{
Vector3 min_point (100000.0F, 100000.0F, 100000.0F);
Vector3 max_point (-100000.0F, -100000.0F, -100000.0F);
//
// Check to see if this sector meets are minimum requirements
//
bool is_valid = true;//Is_Valid_Sector (&upper_left_ptr, cells_right, cells_down);
//
// Allocate a new sector
//
PathfindSectorClass *sector = NULL;
if (is_valid) {
sector = new PathfindSectorClass;
}
//
// Remove all the cells that compose this area from the list
//
FloodfillBoxClass *curr_cell = upper_left_ptr;
for (int right = 0; (right < cells_right); right ++) {
FloodfillBoxClass *down_obj = curr_cell;
for (int down = 0; (down < cells_down); down ++) {
AABoxClass bounding_box = Get_Body_Box_Bounds (down_obj);
min_point.X = min (min_point.X, bounding_box.Center.X - bounding_box.Extent.X);
min_point.Y = min (min_point.Y, bounding_box.Center.Y - bounding_box.Extent.Y);
min_point.Z = min (min_point.Z, bounding_box.Center.Z - bounding_box.Extent.Z);
max_point.X = max (max_point.X, bounding_box.Center.X + bounding_box.Extent.X);
max_point.Y = max (max_point.Y, bounding_box.Center.Y + bounding_box.Extent.Y);
max_point.Z = max (max_point.Z, bounding_box.Center.Z + bounding_box.Extent.Z);
WWASSERT (down_obj->Is_Taken () == false);
if (is_valid) {
down_obj->Set_Sector (sector);
}
down_obj->Remove ();
down_obj->Set_Taken ();
//
// Is this an edge case? (If so its a possible portal)
//
if ( (down == 0) ||
(right == 0) ||
(down == (cells_down - 1)) ||
(right == (cells_right - 1)))
{
if (is_valid) {
m_PossiblePortalList.Add (down_obj);
}
}
//
// Advance to the next box down
//
down_obj = down_obj->Peek_Neighbor (DIR_DOWN);
}
//
// Advance to the next box right
//
curr_cell = curr_cell->Peek_Neighbor (DIR_RIGHT);
}
//
// Calculate this sector's bounding box
//
if (is_valid) {
AABoxClass bounding_box;
bounding_box.Center = min_point + ((max_point - min_point) * 0.5F);
bounding_box.Extent.X = (max_point.X - min_point.X) * 0.5F;
bounding_box.Extent.Y = (max_point.Y - min_point.Y) * 0.5F;
bounding_box.Extent.Z = (max_point.Z - min_point.Z) * 0.5F;
sector->Set_Bounding_Box (bounding_box);
}
return sector;
}
//////////////////////////////////////////////////////////////////////////
//
// Generate_Portals
//
//////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Generate_Portals (void)
{
//
// Loop through all the body-boxes that are possible portals
//
for (int index = 0; index < m_PossiblePortalList.Count (); index ++) {
FloodfillBoxClass *cull_obj = m_PossiblePortalList[index];
PathfindSectorClass *sector = cull_obj->Peek_Sector ();
if ((sector != NULL) && sector->Is_Valid ()) {
const AABoxClass &box = sector->Get_Bounding_Box ();
Vector3 portal_size = m_SimBoundingBox;
portal_size.Z = box.Extent.Z * 2;
float z_pos = box.Center.Z - box.Extent.Z;
//
// 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)) {
//
// Create the portal
//
cull_obj->Make_Portal ((PATHFIND_DIR)dir, portal_size, z_pos, 0.75F);
}
}
}
}
m_PossiblePortalList.Delete_All ();
return ;
}
///////////////////////////////////////////////////////////////////////////
//
// Free_Floodfill_Boxes
//
///////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Free_Floodfill_Boxes (void)
{
//
// Loop over all the floodfill boxes
//
for (int index = 0; index < m_BodyBoxReleaseList.Count (); index ++) {
//
// Remove the floodfill box from the culling system and
// free our hold on it
//
FloodfillBoxClass *body_box = m_BodyBoxReleaseList[index];
body_box->Reset_Portal_Info ();
body_box->Set_Taken (false);
body_box->Set_Sector (NULL);
body_box->Remove ();
REF_PTR_RELEASE (body_box);
}
//
// Reset our systems
//
m_BodyBoxCullingSystem.Reset ();
m_BodyBoxReleaseList.Delete_All ();
return ;
}
///////////////////////////////////////////////////////////////////////////
//
// Cleanup_Level_Features
//
///////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Cleanup_Level_Features (void)
{
//
// Loop over all the transition objects and remove them from the
// culling system
//
int count = m_LevelFeatureList.Count ();
for (int index = 0; index < count; index ++) {
LevelFeatureClass *cull_obj = m_LevelFeatureList[index];
m_LevelFeatureCullingSystem.Remove_Object (cull_obj);
MEMBER_RELEASE (cull_obj);
}
m_LevelFeatureList.Delete_All ();
return ;
}
///////////////////////////////////////////////////////////////////////////
//
// Detect_Level_Features
//
///////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Detect_Level_Features (void)
{
//
// Detect the different types of level features
//
Detect_Doors ();
Detect_Elevators ();
Detect_Level_Transitions ();
//
// Optimize the level feature culling system
//
m_LevelFeatureCullingSystem.Re_Partition ();
return ;
}
///////////////////////////////////////////////////////////////////////////
//
// Detect_Elevators
//
///////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Detect_Elevators (void)
{
//
// Get the list of all static objects from the physics scene
//
RefPhysListIterator iterator = ::Get_Scene_Editor ()->Get_Static_Object_Iterator ();
for (iterator.First (); !iterator.Is_Done (); iterator.Next ()) {
//
// Is this object an elevator?
//
PhysClass *phys_obj = iterator.Peek_Obj ();
ElevatorPhysClass *elevator = phys_obj->As_ElevatorPhysClass ();
if (elevator != NULL) {
const ElevatorPhysDefClass *definition = elevator->Get_ElevatorPhysDef ();
//
// Get information about the elevator
//
OBBoxClass lower_call;
OBBoxClass upper_call;
OBBoxClass lower_inside;
OBBoxClass upper_inside;
const Matrix3D &tm = elevator->Get_Transform ();
OBBoxClass::Transform (tm, definition->Get_Zone (ZONE_LOWER_CALL), &lower_call);
OBBoxClass::Transform (tm, definition->Get_Zone (ZONE_UPPER_CALL), &upper_call);
OBBoxClass::Transform (tm, definition->Get_Zone (ZONE_LOWER_INSIDE), &lower_inside);
OBBoxClass::Transform (tm, definition->Get_Zone (ZONE_UPPER_INSIDE), &upper_inside);
//
// Create a box that bounds the area the unit will actually be 'moving' in
//
AABoxClass bottom_box;
AABoxClass top_box;
bottom_box.Center = lower_inside.Center;
top_box.Center = upper_inside.Center;
top_box.Center.Z += m_SimBoxExtents.Z;
lower_inside.Compute_Axis_Aligned_Extent (&bottom_box.Extent);
upper_inside.Compute_Axis_Aligned_Extent (&top_box.Extent);
AABoxClass teleport_zone = bottom_box;
teleport_zone.Add_Box (top_box);
//
// Create a level feature for going UP the elevator
//
LevelFeatureClass *up_feature = new LevelFeatureClass;
up_feature->Set_Type (LevelFeatureClass::TYPE_ELEVATOR);
up_feature->Set_Start (lower_call);
up_feature->Set_End (upper_call);
up_feature->Set_End_TM (Matrix3D (upper_call.Center));
up_feature->Set_Teleport_Zone (teleport_zone);
up_feature->Set_Mechanism_ID (elevator->Get_ID ());
//
// Create a level feature for going DOWN the elevator
//
LevelFeatureClass *down_feature = new LevelFeatureClass;
down_feature->Set_Type (LevelFeatureClass::TYPE_ELEVATOR);
down_feature->Set_Start (upper_call);
down_feature->Set_End (lower_call);
down_feature->Set_End_TM (Matrix3D (lower_call.Center));
down_feature->Set_Teleport_Zone (teleport_zone);
down_feature->Set_Mechanism_ID (elevator->Get_ID ());
//
// Add the level features to the culling system
//
m_LevelFeatureCullingSystem.Add_Object (up_feature);
m_LevelFeatureCullingSystem.Add_Object (down_feature);
m_LevelFeatureList.Add (up_feature);
m_LevelFeatureList.Add (down_feature);
}
}
return ;
}
///////////////////////////////////////////////////////////////////////////
//
// Detect_Doors
//
///////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Detect_Doors (void)
{
//
// Get the list of all static objects from the physics scene
//
RefPhysListIterator iterator = ::Get_Scene_Editor ()->Get_Static_Object_Iterator ();
for (iterator.First (); !iterator.Is_Done (); iterator.Next ()) {
//
// Is this object an elevator?
//
PhysClass *phys_obj = iterator.Peek_Obj ();
DoorPhysClass *door = phys_obj->As_DoorPhysClass ();
if ( door != NULL &&
door->Get_DoorPhysDef () != NULL &&
door->Get_DoorPhysDef ()->Is_Vehicle_Door () == false)
{
const DoorPhysDefClass *definition = door->Get_DoorPhysDef ();
//
// Get the door's trigger zones
//
const Matrix3D &tm = door->Get_Transform ();
OBBoxClass trigger_zone1;
OBBoxClass trigger_zone2;
OBBoxClass::Transform (tm, definition->Get_Trigger_Zone1 (), &trigger_zone1);
OBBoxClass::Transform (tm, definition->Get_Trigger_Zone2 (), &trigger_zone2);
//
// Create a box that bounds the area the unit will actually be 'moving' in
//
AABoxClass box1;
AABoxClass box2;
box1.Center = trigger_zone1.Center;
box2.Center = trigger_zone2.Center;
trigger_zone1.Compute_Axis_Aligned_Extent (&box1.Extent);
trigger_zone2.Compute_Axis_Aligned_Extent (&box2.Extent);
AABoxClass teleport_zone = box1;
teleport_zone.Add_Box (box2);
//
// Create a level feature for going one direction through the door
//
LevelFeatureClass *feature1 = new LevelFeatureClass;
feature1->Set_Type (LevelFeatureClass::TYPE_DOOR);
feature1->Set_Start (trigger_zone1);
feature1->Set_End (trigger_zone2);
feature1->Set_End_TM (Matrix3D (trigger_zone2.Center));
feature1->Set_Teleport_Zone (teleport_zone);
feature1->Set_Mechanism_ID (door->Get_ID ());
//
// Create a level feature for going the other direction through the door
//
LevelFeatureClass *feature2 = new LevelFeatureClass;
feature2->Set_Type (LevelFeatureClass::TYPE_DOOR);
feature2->Set_Start (trigger_zone2);
feature2->Set_End (trigger_zone1);
feature2->Set_End_TM (Matrix3D (trigger_zone1.Center));
feature2->Set_Teleport_Zone (teleport_zone);
feature2->Set_Mechanism_ID (door->Get_ID ());
//
// Add the level features to the culling system
//
m_LevelFeatureCullingSystem.Add_Object (feature1);
m_LevelFeatureCullingSystem.Add_Object (feature2);
m_LevelFeatureList.Add (feature1);
m_LevelFeatureList.Add (feature2);
}
}
return ;
}
///////////////////////////////////////////////////////////////////////////
//
// Detect_Level_Transitions
//
///////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Detect_Level_Transitions (void)
{
int ladder_index = 0;
//
// Loop through all the transitions in the level
//
TransitionNodeClass *node = NULL;
for (node = (TransitionNodeClass *)NodeMgrClass::Get_First (NODE_TYPE_TRANSITION);
node != NULL;
node = (TransitionNodeClass *)NodeMgrClass::Get_Next (node, NODE_TYPE_TRANSITION))
{
//
// Loop through all the different transitions in this node
//
bool need_new_ladder_index = true;
int count = node->Get_Transition_Count ();
for (int index = 0; index < count; index ++) {
TransitionInstanceClass *transition = node->Get_Transition (index);
if (transition != NULL) {
//
// Build an AABox from the zone this transition uses
//
OBBoxClass zone = transition->Get_Zone ();
//
// We handle each type of transition differently
//
switch (transition->Get_Type ())
{
//
// For ladders, we need to find the 'exit' point
//
case TransitionDataClass::LADDER_ENTER_TOP:
case TransitionDataClass::LADDER_ENTER_BOTTOM:
{
Vector3 pos = zone.Center;
//
// Insert this transition into our culling system
//
LevelFeatureClass *level_feature = new LevelFeatureClass;
level_feature->Set_Start (zone);
//
// Search for the exit transition
//
TransitionInstanceClass *exit_transition = NULL;
TransitionNodeClass *exit_node = NULL;
if (transition->Get_Type () == TransitionDataClass::LADDER_ENTER_BOTTOM) {
exit_transition = Find_Transition (TransitionDataClass::LADDER_EXIT_TOP, pos, 100.0F, &exit_node);
} else {
exit_transition = Find_Transition (TransitionDataClass::LADDER_EXIT_BOTTOM, pos, -100.0F, &exit_node);
}
//
// Pass the exit zone onto the level feature object
//
if (exit_transition != NULL) {
level_feature->Set_End (exit_transition->Get_Zone ());
level_feature->Set_End_TM (exit_transition->Get_Ending_TM ());
AABoxClass box;
box.Center = pos;
box.Extent = m_SimBoxExtents;
box.Extent.X *= 4.0F;
box.Extent.Y *= 4.0F;
//
// Create a box that encompasses the start position and end position
//
AABoxClass teleport_box (box);
box.Center = exit_transition->Get_Ending_TM ().Get_Translation ();
box.Center.Z += m_SimBoxExtents.Z;
teleport_box.Add_Box (box);
//
// Pass the teleport area onto the level feature
//
level_feature->Set_Teleport_Zone (teleport_box);
}
//
// Set the level feature's type
//
if (transition->Get_Type () == TransitionDataClass::LADDER_ENTER_BOTTOM) {
level_feature->Set_Type (LevelFeatureClass::TYPE_LADDER_BOTTOM);
} else {
level_feature->Set_Type (LevelFeatureClass::TYPE_LADDER_TOP);
}
int new_ladder_index = -1;
//
// Can we reuse the same ladder index that the exit portal uses?
//
if (exit_node != NULL && exit_node->Get_Transition_Game_Obj () != NULL) {
new_ladder_index = exit_node->Get_Transition_Game_Obj ()->Get_Ladder_Index ();
if (new_ladder_index != -1) {
need_new_ladder_index = false;
}
}
//
// Increment the ladder counter as necessary
//
if (need_new_ladder_index) {
need_new_ladder_index = false;
ladder_index ++;
new_ladder_index = ladder_index;
}
//
// Give the level feature a unique identifier
//
level_feature->Set_Mechanism_ID (ladder_index);
if (node->Get_Transition_Game_Obj () != NULL) {
node->Get_Transition_Game_Obj ()->Set_Ladder_Index (ladder_index);
}
//
// Attempt to tie the bottom and top transitions to the same value
//
if (exit_node != NULL && exit_node->Get_Transition_Game_Obj () != NULL) {
exit_node->Get_Transition_Game_Obj ()->Set_Ladder_Index (ladder_index);
}
//
// Add the level feature to the culling systems
//
m_LevelFeatureCullingSystem.Add_Object (level_feature);
m_LevelFeatureList.Add (level_feature);
}
break;
default:
break;
}
}
}
}
return ;
}
///////////////////////////////////////////////////////////////////////////
//
// Post_Process_Floodfill_For_Level_Features
//
///////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Post_Process_Floodfill_For_Level_Features (void)
{
//
// Loop over all the level features
//
for (int index = 0; index < m_LevelFeatureList.Count (); index ++) {
LevelFeatureClass *level_feature = m_LevelFeatureList[index];
if (level_feature != NULL) {
//
// Get the area where the player will be moving
//
AABoxClass box = level_feature->Get_Teleport_Zone ();
//
// Loop over all the body-boxes that exist in this area
//
m_BodyBoxCullingSystem.Collect_Boxes (box);
DynamicVectorClass<FloodfillBoxClass *> &list = m_BodyBoxCullingSystem.Get_Collection_List ();
for (int index = 0; index < list.Count (); index ++) {
FloodfillBoxClass *body_box = list[index];
//
// Force the test to pass in the z-component
//
box.Extent.Z = 1000.0F;
//
// Remove this box from the system if its completely contained
// inside the teleport zone
//
AABoxClass test_box = Get_Body_Box_Bounds (body_box);
if (CollisionMath::Overlap_Test (box, test_box) == CollisionMath::INSIDE) {
//
// Remove the box from the possible portal list (if necessary)
//
int portal_list_index = m_PossiblePortalList.ID (body_box);
if (portal_list_index != -1) {
m_PossiblePortalList.Delete (portal_list_index);
}
//
// Remove this box from the system
//
m_BodyBoxReleaseList.Add (body_box);
body_box->Unlink ();
m_BodyBoxCullingSystem.Remove_Box (body_box);
}
}
}
}
return ;
}
///////////////////////////////////////////////////////////////////////////
//
// Apply_Level_Features
//
///////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Apply_Level_Features (void)
{
//
// Loop over all the level features and create sectors and portals
// for them
//
for (int index = 0; index < m_LevelFeatureList.Count (); index ++) {
LevelFeatureClass *level_feature = m_LevelFeatureList[index];
if (level_feature != NULL) {
//
// Integrate this feature into the pathfind data
//
Build_Sector_For_Level_Feature (level_feature);
}
}
return ;
}
///////////////////////////////////////////////////////////////////////////
//
// Find_Transition
//
///////////////////////////////////////////////////////////////////////////
TransitionInstanceClass *
PathfindSectorBuilderClass::Find_Transition
(
TransitionDataClass::StyleType transition_type,
const Vector3 & start_pos,
float z_delta,
TransitionNodeClass ** transition_node
)
{
TransitionInstanceClass *retval = NULL;
(*transition_node) = NULL;
float start_z = start_pos.Z;
float closest_z = 1000000;
//
// Loop over all the transitions in the level and find the one
// that most closely matches what we are looking for
//
TransitionNodeClass *node = NULL;
for (node = (TransitionNodeClass *)NodeMgrClass::Get_First (NODE_TYPE_TRANSITION);
node != NULL;
node = (TransitionNodeClass *)NodeMgrClass::Get_Next (node, NODE_TYPE_TRANSITION))
{
//
// See if we can find a transition game object inside the node
// that is the type we are looking for.
//
int index = node->Find_Transition (transition_type);
if (index >= 0) {
//
// Get the transition game object
//
TransitionInstanceClass *transition = node->Get_Transition (index);
if (transition != NULL) {
const OBBoxClass &zone = transition->Get_Zone ();
//
// Determine how 'far' we are from this transition
//
float curr_z_min = zone.Center.Z - zone.Extent.Z;
float curr_z_max = zone.Center.Z + zone.Extent.Z;
float curr_delta = min (WWMath::Fabs (start_z - curr_z_min), WWMath::Fabs (start_z - curr_z_max));
//
// Is this transition zone the one we are looking for?
//
if ( (curr_delta < closest_z) &&
(WWMath::Fabs (start_pos.X - zone.Center.X) < 1.0F) &&
(WWMath::Fabs (start_pos.Y - zone.Center.Y) < 1.0F))
{
(*transition_node) = node;
retval = transition;
closest_z = curr_delta;
}
}
}
}
return retval;
}
///////////////////////////////////////////////////////////////////////////
//
// Find_Sector
//
///////////////////////////////////////////////////////////////////////////
PathfindSectorClass *
PathfindSectorBuilderClass::Find_Sector
(
const Vector3 & point,
const AABoxClass & box
)
{
PathfindSectorClass *sector = PathfindClass::Get_Instance ()->Find_Sector (point, 1.0F);
if (sector == NULL) {
sector = PathfindClass::Get_Instance ()->Find_Sector (box.Center, 1.0F);
}
if (sector == NULL) {
Vector3 box_point = box.Center;
box_point.X += box.Extent.X;
box_point.Y += box.Extent.Y;
sector = PathfindClass::Get_Instance ()->Find_Sector (box_point, 1.0F);
}
if (sector == NULL) {
Vector3 box_point = box.Center;
box_point.X -= box.Extent.X;
box_point.Y -= box.Extent.Y;
sector = PathfindClass::Get_Instance ()->Find_Sector (box_point, 1.0F);
}
if (sector == NULL) {
Vector3 box_point = box.Center;
box_point.X += box.Extent.X;
box_point.Y -= box.Extent.Y;
sector = PathfindClass::Get_Instance ()->Find_Sector (box_point, 1.0F);
}
if (sector == NULL) {
Vector3 box_point = box.Center;
box_point.X -= box.Extent.X;
box_point.Y += box.Extent.Y;
sector = PathfindClass::Get_Instance ()->Find_Sector (box_point, 1.0F);
}
return sector;
}
///////////////////////////////////////////////////////////////////////////
//
// Build_Sector_For_Level_Feature
//
///////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Build_Sector_For_Level_Feature (LevelFeatureClass *level_feature)
{
if (level_feature == NULL) {
return ;
}
//
// Get the starting and ending positions
//
Matrix3D end_tm = level_feature->Get_End_TM ();
Vector3 end_pos = end_tm.Get_Translation ();
Vector3 start_pos = level_feature->Get_Start ().Center;
//
// Get the starting and ending AABox's for this feature
//
AABoxClass start_box;
AABoxClass end_box;
level_feature->Get_Start_AABox (start_box);
level_feature->Get_End_AABox (end_box);
Vector3 sector_end_pos = end_pos;
//
// Assume the player will want to exit "away" from where (s)he entered
//
if (level_feature->Get_Type () == LevelFeatureClass::TYPE_DOOR) {
Vector3 offset = end_box.Center - start_box.Center;
offset.Normalize ();
sector_end_pos = end_pos + offset;
Clip_Point (&sector_end_pos, end_box);
}
//
// Are there any pathfinding sectors intersecting the
// start and ending regions?
//
PathfindSectorClass *start_sector = Find_Sector (start_pos, start_box);
PathfindSectorClass *end_sector = Find_Sector (sector_end_pos, end_box);
if ( level_feature->Get_Type () == LevelFeatureClass::TYPE_LADDER_BOTTOM ||
level_feature->Get_Type () == LevelFeatureClass::TYPE_LADDER_TOP)
{
if (end_sector != NULL) {
end_box = end_sector->Get_Bounding_Box ();
}
}
//
// Expand the end box by a little bit
//
/*end_box.Extent.X += 0.25F;
end_box.Extent.Y += 0.25F;
end_box.Extent.Z += 0.25F;
//
// Build a list of sectors that the exit should connect to
//
DynamicVectorClass<PathfindSectorClass *> end_sector_list;
PathfindClass::Get_Instance ()->Collect_Sectors (end_sector_list, end_box, start_sector);*/
//
// Were we successful?
//
if ((start_sector != NULL) && (end_sector != NULL)) {
//
// Create a sector from scratch
//
PathfindSectorClass *new_sector = new PathfindSectorClass;
new_sector->Set_Bounding_Box (AABoxClass (Vector3 (0, 0, 0), Vector3 (0, 0, 0)));
PathfindClass::Get_Instance ()->Add_Sector (new_sector);
new_sector->Release_Ref ();
//
// Create the starting portal
//
PathfindActionPortalClass *enter_portal = new PathfindActionPortalClass;
enter_portal->Set_Bounding_Box (start_box);
enter_portal->Add_Dest_Sector (new_sector);
enter_portal->Set_Destination (end_pos);
//
// Create the ending portal
//
PathfindActionPortalClass *exit_portal = new PathfindActionPortalClass;
exit_portal->Set_Bounding_Box (end_box);
exit_portal->Add_Dest_Sector (end_sector);
//
// Determine which type of action should be performed at this portal
//
switch (level_feature->Get_Type ())
{
case LevelFeatureClass::TYPE_LADDER_BOTTOM:
case LevelFeatureClass::TYPE_LADDER_TOP:
{
AABoxClass end_trigger_zone;
level_feature->Get_End_AABox (end_trigger_zone);
enter_portal->Set_Destination (end_trigger_zone.Center);
enter_portal->Set_Action_Type (PathClass::ACTION_LADDER);
enter_portal->Set_Mechanism_ID (level_feature->Get_Mechanism_ID ());
exit_portal->Set_Action_Type (PathClass::ACTION_NONE);
break;
}
case LevelFeatureClass::TYPE_DOOR:
case LevelFeatureClass::TYPE_ELEVATOR:
enter_portal->Set_Action_Type (PathClass::ACTION_MECHANISM);
enter_portal->Set_Mechanism_ID (level_feature->Get_Mechanism_ID ());
exit_portal->Set_Action_Type (PathClass::ACTION_NONE);
break;
}
//
// Let the enter portal "know" where its going to end up...
//
enter_portal->Set_Exit_Portal (exit_portal);
exit_portal->Set_Enter_Portal (enter_portal);
//
// Register the new portals with the pathfinding system,
// and let the sectors know they can access these portals.
//
int enter_portal_index = PathfindClass::Get_Instance ()->Add_Portal (enter_portal);
int exit_portal_index = PathfindClass::Get_Instance ()->Add_Portal (exit_portal);
start_sector->Add_Portal (enter_portal_index);
new_sector->Add_Portal (exit_portal_index);
REF_PTR_RELEASE (enter_portal);
REF_PTR_RELEASE (exit_portal);
}
return ;
}
///////////////////////////////////////////////////////////////////////////
//
// Check_For_Level_Feature
//
///////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Check_For_Level_Feature (FloodfillBoxClass *body_box)
{
if (m_LevelFeatureCullingSystem.Object_Count () > 0) {
AABoxClass bounding_box = Get_Body_Box_Bounds (body_box);
//
// Find all level features that intersect our current position
//
m_LevelFeatureCullingSystem.Reset_Collection ();
m_LevelFeatureCullingSystem.Collect_Objects (bounding_box);
//
// Find all the features this body box intersects
//
DynamicVectorClass<LevelFeatureClass *> feature_list;
LevelFeatureClass *level_feature = NULL;
for ( level_feature = m_LevelFeatureCullingSystem.Get_First_Collected_Object ();
level_feature != NULL;
level_feature = m_LevelFeatureCullingSystem.Get_Next_Collected_Object (level_feature))
{
feature_list.Add (level_feature);
}
//
// Loop through all the features in our list and process any 'portals' (ladders, doors, etc)
//
for (int index = 0; index < feature_list.Count (); index ++) {
level_feature = feature_list[index];
//
// Get the bounding box for the pathfind object and the level feature's 'start-zone'
//
AABoxClass pathfind_box = Get_Body_Box_Bounds (body_box);
AABoxClass feature_box;
level_feature->Get_Start_AABox (feature_box);
//
// Do these boxe's intersect?
//
if ( (pathfind_box.Center.X + pathfind_box.Extent.X < feature_box.Center.X + feature_box.Extent.X) &&
(pathfind_box.Center.X - pathfind_box.Extent.X > feature_box.Center.X - feature_box.Extent.X) &&
(pathfind_box.Center.Y + pathfind_box.Extent.Y < feature_box.Center.Y + feature_box.Extent.Y) &&
(pathfind_box.Center.Y - pathfind_box.Extent.Y > feature_box.Center.Y - feature_box.Extent.Y))
{
//
// Continue our pathfind across this feature (i.e. up the ladder, down the elevator, etc)
//
Path_Across_Feature (level_feature);
}
}
}
return ;
}
///////////////////////////////////////////////////////////////////////////
//
// Path_Across_Feature
//
///////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Path_Across_Feature (LevelFeatureClass *level_feature)
{
//
// Get the point where we think this feature will 'teleport' us to
//
Vector3 expected_pos = level_feature->Get_End_TM ().Get_Translation ();
expected_pos.Z += m_SimBoxExtents.Z;
//
// Snap this position to our pathfind grid
//
Vector3 normalized_pos = expected_pos;
normalized_pos.X = (int(expected_pos.X / m_SimBoundingBox.X)) * m_SimBoundingBox.X;
normalized_pos.Y = (int(expected_pos.Y / m_SimBoundingBox.Y)) * m_SimBoundingBox.Y;
//
// Can we stand at this new position?
//
AABoxClass new_box;
if (Try_Standing_Here (normalized_pos, &new_box)) {
//
// Is this cell already marked?
//
Vector3 position = new_box.Center;
position.Z = new_box.Center.Z;
FloodfillBoxClass *occupant = Get_Sector_Occupant (position);
if (occupant == NULL) {
//
// Mark this cell and add it to the list of sectors
// that need to be recursed.
//
occupant = Mark_Sector (new_box);
m_FloodFillProcessList.Add (occupant);
}
}
return ;
}
/////////////////////////////////////////////////////////////////////////////
//
// fnPathfindDialogThread
//
////////////////////////////////////////////////////////////////////////////
UINT
fnPathfindDialogThread
(
DWORD dwparam1,
DWORD dwparam2,
DWORD /*dwparam3*/,
HRESULT* /*presult*/,
HWND* /*phmain_wnd*/
)
{
GeneratingPathfindDialogClass *dialog = new GeneratingPathfindDialogClass (::AfxGetMainWnd ());
dialog->ShowWindow (SW_SHOW);
//
// Return the dialog object to the caller
//
GeneratingPathfindDialogClass **return_val = (GeneratingPathfindDialogClass **)dwparam2;
if (return_val != NULL) {
(*return_val) = dialog;
}
return 1;
}
////////////////////////////////////////////////////////////////////////////
//
// Show_Dialog
//
////////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Show_Dialog (void)
{
::Create_UI_Thread (fnPathfindDialogThread, 0, (DWORD)&m_pDialog, 0, NULL, NULL);
return ;
}
////////////////////////////////////////////////////////////////////////////
//
// Close_Dialog
//
////////////////////////////////////////////////////////////////////////////
void
PathfindSectorBuilderClass::Close_Dialog (void)
{
if (m_pDialog != NULL) {
::PostMessage (m_pDialog->m_hWnd, WM_USER+101, 0, 0L);
}
return ;
}