3004 lines
80 KiB
C++
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(§ors);
|
|
|
|
//
|
|
// 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 *> §or_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 (§or_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 ;
|
|
}
|