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/wwphys/Pathfind.cpp

1994 lines
49 KiB
C++

/*
** Command & Conquer Renegade(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/***********************************************************************************************
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
***********************************************************************************************
* *
* Project Name : LevelEdit *
* *
* $Archive:: /Commando/Code/wwphys/Pathfind.cpp $*
* *
* Author:: Patrick Smith *
* *
* $Modtime:: 10/15/01 7:44p $*
* *
* $Revision:: 35 $*
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "pathfind.h"
#include "pathfindportal.h"
#include "path.h"
#include "pscene.h"
#include "boxrobj.h"
#include "decophys.h"
#include "mesh.h"
#include "assetmgr.h"
#include "chunkio.h"
#include "wwphysids.h"
#include "pathdebugplotter.h"
#include "persistfactory.h"
#include "waypath.h"
#include "waypoint.h"
#include "wwmemlog.h"
#include "heightdb.h"
#include "colmathaabox.h"
///////////////////////////////////////////////////////////////////////////
// Local prototypes
///////////////////////////////////////////////////////////////////////////
int __cdecl fnCompareWaypathPortalsCallback (const void *elem1, const void *elem2);
bool Find_Intersection_Point (const AABoxClass &box, const Vector3 &p0, const Vector3 &p1, float *percent, Vector3 *intersection_point);
void Add_New_Portal_To_List (DynamicVectorClass<PathfindWaypathPortalClass *> &portal_list, PathfindSectorClass *dest_sector1, PathfindSectorClass *dest_sector2, int waypath_id, int waypoint_index, const Vector3 &portal_pos, float percent);
///////////////////////////////////////////////////////////////////////////
// Constants
///////////////////////////////////////////////////////////////////////////
const int MAX_TEMP_PORTALS = 20;
///////////////////////////////////////////////////////////////////////////
// Static data initialization
///////////////////////////////////////////////////////////////////////////
static int _NextTempPortalID = PathfindClass::TEMP_PORTAL_ID_START;
///////////////////////////////////////////////////////////////////////////
// Save/Load stuff
///////////////////////////////////////////////////////////////////////////
enum
{
CHUNKID_DATABASE = 0x01060635,
XXX_CHUNKID_SECTOR,
CHUNKID_PORTAL,
CHUNKID_SECTOR_CULLING_SYSTEM,
CHUNKID_SECTOR,
CHUNKID_SECTOR_CULL_TREE,
CHUNKID_SECTOR_LINKAGE,
CHUNKID_SECTOR_OBJECT,
CHUNKID_HEIGHTDB,
CHUNKID_ACTION_PORTAL,
CHUNKID_WAYPATH_PORTAL,
CHUNKID_PATHFIND_SECTOR_OBJECT
};
///////////////////////////////////////////////////////////////////////////
// Static member initialization
///////////////////////////////////////////////////////////////////////////
PathfindClass *PathfindClass::_Pathfinder = NULL;
int PathfindClass::_MemoryFootprint = 0;
///////////////////////////////////////////////////////////////////////////
//
// PathfindClass
//
///////////////////////////////////////////////////////////////////////////
PathfindClass::PathfindClass (void)
: m_SectorsDisplayed (false),
m_PortalsDisplayed (false),
m_Plotter (NULL),
m_SectorList(1000), // 1000's of these
m_PortalList(5000), // 10.000's of these
m_SectorDisplayList(1000), // 1000's of these (debug only)
m_WaypathList(30) // 5-30 of these?
{
WWASSERT (_Pathfinder == NULL);
_Pathfinder = this;
// Set good estimates of growth steps
m_SectorList.Set_Growth_Step(500);
m_PortalList.Set_Growth_Step(2500);
m_SectorDisplayList.Set_Growth_Step(500);
m_WaypathList.Set_Growth_Step(10);
m_Plotter = new PathDebugPlotterClass;
//
// Initialize the height database
//
HeightDBClass::Initialize ();
return ;
}
///////////////////////////////////////////////////////////////////////////
//
// ~PathfindClass
//
///////////////////////////////////////////////////////////////////////////
PathfindClass::~PathfindClass (void)
{
Reset_Sectors ();
Reset_Portals ();
Reset_Waypaths ();
_Pathfinder = NULL;
REF_PTR_RELEASE (m_Plotter);
//
// Free the height database
//
HeightDBClass::Shutdown ();
return ;
}
///////////////////////////////////////////////////////////////////////////
//
// Add_Sector
//
///////////////////////////////////////////////////////////////////////////
void
PathfindClass::Add_Sector (PathfindSectorClass *sector, bool add_to_tree)
{
WWASSERT (sector != NULL);
if (sector != NULL) {
sector->Add_Ref ();
//
// Add this sector to the culling system (if needs be)
//
if (add_to_tree) {
m_SectorTree.Add_Object (sector);
}
//
// Add this sector to our linear list (for housekeeping)
//
m_SectorList.Add (sector);
//
// For debugging purposes, add this object to our
// memory footprint counter.
//
_MemoryFootprint += sizeof (PathfindSectorClass);
}
return ;
}
///////////////////////////////////////////////////////////////////////////
//
// Add_Portal
//
///////////////////////////////////////////////////////////////////////////
int
PathfindClass::Add_Portal (PathfindPortalClass *portal)
{
//
// Add this portal to the housekeeping list
//
portal->Add_Ref ();
m_PortalList.Add (portal);
//
// Assign the portal a unique ID
//
int portal_id = m_PortalList.Count () - 1;
portal->Set_ID (portal_id);
//
// Add the size of this portal to the pool.
//
_MemoryFootprint += sizeof (PathfindPortalClass);
return portal_id;
}
///////////////////////////////////////////////////////////////////////////
//
// Add_Waypath_Portal
//
///////////////////////////////////////////////////////////////////////////
int
PathfindClass::Add_Waypath_Portal (PathfindWaypathPortalClass *portal)
{
//
// Add this portal to the housekeeping list
//
portal->Add_Ref ();
m_WaypathPortalList.Add (portal);
//
// Assign the portal a unique ID
//
int portal_id = (m_WaypathPortalList.Count () - 1) + WAYPATH_PORTAL_ID_START;
portal->Set_ID (portal_id);
//
// Add the size of this portal to the pool.
//
_MemoryFootprint += sizeof (PathfindWaypathPortalClass);
return portal_id;
}
///////////////////////////////////////////////////////////////////////////
//
// Add_Temporary_Portal
//
///////////////////////////////////////////////////////////////////////////
int
PathfindClass::Add_Temporary_Portal
(
PathfindSectorClass *sector_from,
PathfindSectorClass *sector_to,
const Vector3 & start_pos,
const Vector3 & dest_pos
)
{
int retval = 0;
if (sector_from != NULL && sector_to != NULL) {
//
// Check to see if there is already a portal between the two sectors
//
bool found = false;
for (int index = 0; index < sector_from->Get_Portal_Count (); index ++) {
PathfindPortalClass *portal = sector_from->Peek_Portal (index);
PathfindSectorClass *sector = portal->Peek_Dest_Sector (sector_from);
//
// Is this portal's destination sector the one we are looking for?
//
if (sector == sector_to) {
found = true;
break;
}
}
//
// If there isn't already a portal between the sectors, then add one...
//
if (found == false) {
AABoxClass portal_box (start_pos, Vector3 (0.25F, 0.25F, 1.0F));
PathfindActionPortalClass *new_portal = new PathfindActionPortalClass;
new_portal->Set_Bounding_Box (portal_box);
new_portal->Add_Dest_Sector (sector_to);
new_portal->Set_Entrance_Sector (sector_from);
new_portal->Set_Action_Type (PathClass::ACTION_LEAP);
new_portal->Set_Destination (dest_pos);
new_portal->Set_ID (_NextTempPortalID ++);
//
// Add this portal to the housekeeping list
//
m_TemporaryPortalList.Add (new_portal);
sector_from->Add_Portal (new_portal->Get_ID ());
//
// Add the size of this portal to the pool.
//
_MemoryFootprint += sizeof (PathfindActionPortalClass);
retval = new_portal->Get_ID ();
//
// Have we reached the maximum number of temporary portals?
//
if (m_TemporaryPortalList.Count () > MAX_TEMP_PORTALS) {
//
// Remove the first temp portal in the list
//
PathfindActionPortalClass *old_portal = (PathfindActionPortalClass *)m_TemporaryPortalList[0];
if (old_portal != NULL) {
//
// Remove the portal from which ever sectors reference it
//
PathfindSectorClass *sector = old_portal->Get_Entrance_Sector ();
if (sector != NULL) {
sector->Remove_Portal (old_portal->Get_ID ());
}
//
// Remove the portal from our list and free our hold on it
//
m_TemporaryPortalList.Delete (0);
old_portal->Release_Ref ();
}
}
}
}
return retval;
}
///////////////////////////////////////////////////////////////////////////
//
// Save
//
///////////////////////////////////////////////////////////////////////////
bool
PathfindClass::Save (ChunkSaveClass &csave)
{
WWMEMLOG(MEM_PATHFIND);
csave.Begin_Chunk (CHUNKID_DATABASE);
//
// Save the sectors and portals
//
bool retval = Save_Culling_System (csave);
retval &= Save_Portals (csave);
retval &= Save_Waypaths (csave);
//
// Save the height database for flying vehicles
//
csave.Begin_Chunk (CHUNKID_HEIGHTDB);
HeightDBClass::Save (csave);
csave.End_Chunk ();
csave.End_Chunk ();
return retval;
}
///////////////////////////////////////////////////////////////////////////
//
// Save_Portals
//
///////////////////////////////////////////////////////////////////////////
bool
PathfindClass::Save_Portals (ChunkSaveClass &csave)
{
bool retval = true;
int count = m_PortalList.Count ();
for (int index = 0; index < count && retval; index ++) {
PathfindPortalClass *portal = m_PortalList[index];
//
// Determine what type of chunk to save this portal in
//
if (portal->As_PathfindActionPortalClass () != NULL) {
csave.Begin_Chunk (CHUNKID_ACTION_PORTAL);
} else {
csave.Begin_Chunk (CHUNKID_PORTAL);
}
//
// Write this portal out ot its own chunk
//
retval &= portal->Save (csave);
csave.End_Chunk ();
}
//
// Now save the waypath portals
//
count = m_WaypathPortalList.Count ();
for (index = 0; index < count && retval; index ++) {
PathfindPortalClass *portal = m_WaypathPortalList[index];
//
// Write this portal out ot its own chunk
//
csave.Begin_Chunk (CHUNKID_WAYPATH_PORTAL);
retval &= portal->Save (csave);
csave.End_Chunk ();
}
return retval;
}
///////////////////////////////////////////////////////////////////////////
//
// Load
//
///////////////////////////////////////////////////////////////////////////
bool
PathfindClass::Load (ChunkLoadClass &cload)
{
WWMEMLOG(MEM_PATHFIND);
Reset_Sectors ();
Reset_Portals ();
Reset_Waypaths ();
bool retval = true;
//
// Make sure we are reading data from the right file...
//
if ( cload.Open_Chunk () != true ||
cload.Cur_Chunk_ID () != CHUNKID_DATABASE)
{
retval = false;
}
//
// Read all the chunks...
//
while (retval && cload.Open_Chunk ()) {
switch (cload.Cur_Chunk_ID ()) {
case CHUNKID_HEIGHTDB:
retval &= HeightDBClass::Load (cload);
break;
case CHUNKID_SECTOR_CULLING_SYSTEM:
retval &= Load_Culling_System (cload);
break;
case CHUNKID_PORTAL:
{
PathfindPortalClass *portal = new PathfindPortalClass;
retval &= Load_Portal (cload, portal);
}
break;
case CHUNKID_ACTION_PORTAL:
{
PathfindActionPortalClass *portal = new PathfindActionPortalClass;
retval &= Load_Portal (cload, portal);
}
break;
case CHUNKID_WAYPATH_PORTAL:
{
PathfindWaypathPortalClass *portal = new PathfindWaypathPortalClass;
retval &= Load_Portal (cload, portal);
}
break;
default:
{
//
// Load the object from the chunk (if possible)
//
PersistFactoryClass *factory = SaveLoadSystemClass::Find_Persist_Factory (cload.Cur_Chunk_ID ());
if (factory != NULL) {
PersistClass *object = factory->Load (cload);
//
// Were we successful?
//
if (object != NULL) {
//
// If this is a waypath, then register it with the system
//
if (cload.Cur_Chunk_ID () == PHYSICS_CHUNKID_WAYPATH) {
Add_Waypath ((WaypathClass *)object);
((WaypathClass *)object)->Release_Ref ();
}
} else {
WWDEBUG_SAY (("Unknown chunk ID 0x%X\r\n", cload.Cur_Chunk_ID ()));
retval = false;
}
}
}
break;
}
cload.Close_Chunk ();
}
cload.Close_Chunk ();
return retval;
}
///////////////////////////////////////////////////////////////////////////
//
// Save_Sector
//
///////////////////////////////////////////////////////////////////////////
bool
PathfindClass::Save_Sector
(
ChunkSaveClass & csave,
PathfindSectorClass * sector
)
{
bool retval = true;
csave.Begin_Chunk (CHUNKID_SECTOR);
//
// Write this sector out to its own chunk
//
bool save_linkage = true;
if (sector->As_PathfindWaypathSectorClass () != NULL) {
csave.Begin_Chunk (CHUNKID_PATHFIND_SECTOR_OBJECT);
save_linkage = false;
} else {
csave.Begin_Chunk (CHUNKID_SECTOR_OBJECT);
}
retval &= sector->Save (csave);
csave.End_Chunk ();
//
// Now write the sector's AABTreeCullSystem linkage to a chunk
//
if (save_linkage) {
csave.Begin_Chunk (CHUNKID_SECTOR_LINKAGE);
m_SectorTree.Save_Object_Linkage (csave, sector);
csave.End_Chunk ();
}
csave.End_Chunk ();
return retval;
}
///////////////////////////////////////////////////////////////////////////
//
// Load_Sector
//
///////////////////////////////////////////////////////////////////////////
bool
PathfindClass::Load_Sector (ChunkLoadClass &cload)
{
bool retval = true;
PathfindSectorClass *sector = NULL;
//
// Read all the chunks...
//
while (cload.Open_Chunk ()) {
switch (cload.Cur_Chunk_ID ()) {
case CHUNKID_SECTOR_OBJECT:
sector = new PathfindSectorClass;
retval &= sector->Load (cload);
break;
case CHUNKID_PATHFIND_SECTOR_OBJECT:
sector = new PathfindWaypathSectorClass;
retval &= sector->Load (cload);
//
// Add this sector to our housekeeping list
//
Add_Sector (sector, false);
break;
case CHUNKID_SECTOR_LINKAGE:
WWASSERT (sector != NULL);
if (sector != NULL) {
//
// Read the linkage information from the chunk
//
m_SectorTree.Load_Object_Linkage (cload, sector);
//
// Add this sector to our housekeeping list
//
Add_Sector (sector, false);
}
break;
default:
WWDEBUG_SAY (("Unknown chunk ID 0x%X\r\n", cload.Cur_Chunk_ID ()));
retval = false;
break;
}
cload.Close_Chunk ();
}
REF_PTR_RELEASE (sector);
return retval;
}
///////////////////////////////////////////////////////////////////////////
//
// Load_Portal
//
///////////////////////////////////////////////////////////////////////////
bool
PathfindClass::Load_Portal (ChunkLoadClass &cload, PathfindPortalClass *portal)
{
//
// Load the portal's data from its chunk
// and add it to our global portal list.
//
bool retval = portal->Load (cload);
if (retval) {
//
// Now add this portal to its list
//
if (portal->As_PathfindWaypathPortalClass () == NULL) {
m_PortalList.Add (portal);
} else {
m_WaypathPortalList.Add (portal->As_PathfindWaypathPortalClass ());
}
//
// Add the size of this portal to the pool.
//
_MemoryFootprint += sizeof (PathfindPortalClass);
}
return retval;
}
///////////////////////////////////////////////////////////////////////////
//
// Get_Sector_Index
//
///////////////////////////////////////////////////////////////////////////
int
PathfindClass::Get_Sector_Index (PathfindSectorClass *sector)
{
int index = m_SectorList.Count ();
bool keep_going = true;
while (keep_going && index --) {
keep_going = (m_SectorList[index] != sector);
}
return index;
}
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;
}
///////////////////////////////////////////////////////////////////////////
//
// Collect_Sectors
//
///////////////////////////////////////////////////////////////////////////
void
PathfindClass::Collect_Sectors
(
DynamicVectorClass<PathfindSectorClass *> & list,
const AABoxClass & box,
PathfindSectorClass * exclude_sector
)
{
//
// Collect all the sectors this box intersects
//
m_SectorTree.Reset_Collection ();
m_SectorTree.Collect_Objects (box);
//
// Return the list of sectors to the caller
//
PathfindSectorClass *sector = NULL;
for ( sector = m_SectorTree.Get_First_Collected_Object ();
sector != NULL;
sector = m_SectorTree.Get_Next_Collected_Object (sector))
{
if (sector != exclude_sector) {
//
// Ignore sectors that are 0 size
//
const AABoxClass &box = sector->Get_Bounding_Box ();
if (box.Extent.X != 0 && box.Extent.Y != 0 && box.Extent.Z != 0) {
list.Add (sector);
}
}
}
return ;
}
///////////////////////////////////////////////////////////////////////////
//
// Find_Sector
//
///////////////////////////////////////////////////////////////////////////
PathfindSectorClass *
PathfindClass::Find_Sector
(
const Vector3 & position,
float sector_fudge,
PathfindSectorClass *exclude_sector
)
{
//
// Collect all the sectors this position intersects (hopefully
// will be only one).
//
SphereClass sphere (position, sector_fudge);
m_SectorTree.Reset_Collection ();
m_SectorTree.Collect_Objects (sphere);
float closest = 99999.9F;
//
// Find an acceptable sector to return
//
PathfindSectorClass *closest_sector = NULL;
PathfindSectorClass *sector = NULL;
for ( sector = m_SectorTree.Get_First_Collected_Object ();
sector != NULL;
sector = m_SectorTree.Get_Next_Collected_Object (sector))
{
if (sector != exclude_sector) {
//
// Ignore sectors that are small
//
const AABoxClass &box = sector->Get_Bounding_Box ();
if (box.Extent.X > 0.325F && box.Extent.Y > 0.325F && box.Extent.Z > WWMATH_EPSILON) {
//
// Clip the player's position to this sector
//
Vector3 clipped_pos = position;
::Clip_Point (&clipped_pos, box);
//
// Check to see if this clipped position is the closest yet
//
float dist = (clipped_pos - position).Length ();
if (dist < closest) {
closest = dist;
closest_sector = sector;
}
}
}
}
//
// Return the first (and hopefully only) sector
//
return closest_sector;
}
///////////////////////////////////////////////////////////////////////////
//
// Reset_Sectors
//
///////////////////////////////////////////////////////////////////////////
void
PathfindClass::Reset_Sectors (void)
{
Display_Sectors (false);
Display_Portals (false);
//
// Release our hold on each of the sectors
//
for (int index = 0; index < m_SectorList.Count (); index ++) {
PathfindSectorClass *sector = m_SectorList[index];
//
// Remove this sector from the culling system (if necessary)
//
if (sector->As_PathfindWaypathSectorClass () == NULL) {
m_SectorTree.Remove_Object (sector);
}
REF_PTR_RELEASE (sector);
}
m_SectorList.Delete_All ();
_MemoryFootprint = 0;
return ;
}
///////////////////////////////////////////////////////////////////////////
//
// Reset_Portals
//
///////////////////////////////////////////////////////////////////////////
void
PathfindClass::Reset_Portals (void)
{
Display_Portals (false);
//
// Release our hold on each of the portals
//
for (int index = 0; index < m_PortalList.Count (); index ++) {
PathfindPortalClass *portal = m_PortalList[index];
REF_PTR_RELEASE (portal);
}
//
// Release our hold on each of the temporary portals
//
for (index = 0; index < m_TemporaryPortalList.Count (); index ++) {
PathfindPortalClass *portal = m_TemporaryPortalList[index];
REF_PTR_RELEASE (portal);
}
//
// Release our hold on each of the waypath portals
//
for (index = 0; index < m_WaypathPortalList.Count (); index ++) {
PathfindPortalClass *portal = m_WaypathPortalList[index];
REF_PTR_RELEASE (portal);
}
//
// Reset the lists
//
m_PortalList.Delete_All ();
m_TemporaryPortalList.Delete_All ();
m_WaypathPortalList.Delete_All ();
//
// Reset the starting IDs
//
_NextTempPortalID = TEMP_PORTAL_ID_START;
return ;
}
///////////////////////////////////////////////////////////////////////////
//
// Display_Sectors
//
///////////////////////////////////////////////////////////////////////////
void
PathfindClass::Display_Sectors (bool onoff)
{
//
// Start fresh
//
m_SectorDisplayWidgets.Reset_Debug_Widget_List ();
if (onoff) {
//
// Add boxes to our debug widget system to represent the sectors
//
for (int index = 0; index < m_SectorList.Count (); index ++) {
PathfindSectorClass *sector = m_SectorList[index];
const AABoxClass &bounding_box = sector->Get_Bounding_Box ();
//
// Add a randomly colored box to the widget system
//
float red = 0;
float green = 0.5F + float(rand () % 128) / 256.0F;
float blue = 0.5F + float(rand () % 128) / 256.0F;
m_SectorDisplayWidgets.Add_Debug_AABox (bounding_box, Vector3 (red, green, blue));
}
}
m_SectorsDisplayed = onoff;
return ;
}
///////////////////////////////////////////////////////////////////////////
//
// Display_Portals
//
///////////////////////////////////////////////////////////////////////////
void
PathfindClass::Display_Portals (bool onoff)
{
//
// Start fresh
//
m_PortalDisplayWidgets.Reset_Debug_Widget_List ();
//
// Add boxes to our debug widget system to represent the sectors
//
if (onoff) {
for (int index = 0; index < m_PortalList.Count (); index ++) {
PathfindPortalClass *portal = m_PortalList[index];
AABoxClass bounding_box;
portal->Get_Bounding_Box (bounding_box);
//
// Choose a color for this box
//
Vector3 color (1, 1, 1);
if (portal->Is_Two_Way_Portal () == false) {
color.Set (1, 0, 0);
}
//
// Add a box to the system
//
m_PortalDisplayWidgets.Add_Debug_AABox (bounding_box, color);
}
}
m_PortalsDisplayed = onoff;
return ;
}
///////////////////////////////////////////////////////////////////////////
//
// Add_Waypath
//
///////////////////////////////////////////////////////////////////////////
void
PathfindClass::Add_Waypath (WaypathClass *waypath)
{
if (waypath != NULL) {
waypath->Add_Ref ();
m_WaypathList.Add (waypath);
}
return ;
}
///////////////////////////////////////////////////////////////////////////
//
// Remove_Waypath
//
///////////////////////////////////////////////////////////////////////////
bool
PathfindClass::Remove_Waypath (WaypathClass *waypath)
{
bool retval = false;
if (waypath != NULL) {
int index = m_WaypathList.Count ();
while (index -- && (retval == false)) {
WaypathClass *curr_waypath = m_WaypathList[index];
//
// If this is the waypath we are looking for
// then remove it from the list and release our
// hold on it.
//
if (curr_waypath == waypath) {
REF_PTR_RELEASE (waypath);
m_WaypathList.Delete (index);
retval = true;
}
}
}
return retval;
}
///////////////////////////////////////////////////////////////////////////
//
// Find_Waypath
//
///////////////////////////////////////////////////////////////////////////
WaypathClass *
PathfindClass::Find_Waypath (int id) const
{
WaypathClass *waypath = NULL;
//
// Loop over all the paths in our list until we've
// found the one are looking for.
//
int index = m_WaypathList.Count ();
while (index -- && (waypath == NULL)) {
WaypathClass *curr_waypath = m_WaypathList[index];
//
// Is this the waypath we are looking for?
//
if (curr_waypath->Get_ID () == id) {
waypath = curr_waypath;
}
}
return waypath;
}
///////////////////////////////////////////////////////////////////////////
//
// Count_Waypaths_Starting_In_Box
//
///////////////////////////////////////////////////////////////////////////
int PathfindClass::Count_Waypaths_Starting_In_Box (const AABoxClass & box)
{
//
// Loop over all the paths in our list counting the ones whose
// start points are contained inside the given box.
//
int count = 0;
for (int index=0; index<m_WaypathList.Count(); index++) {
WaypathClass *curr_waypath = m_WaypathList[index];
if (CollisionMath::Overlap_Test(box,curr_waypath->Get_Point(0)->Get_Position()) == CollisionMath::INSIDE) {
count++;
}
}
return count;
}
///////////////////////////////////////////////////////////////////////////
//
// Get_Waypath_Starting_In_Box
//
///////////////////////////////////////////////////////////////////////////
WaypathClass * PathfindClass::Get_Waypath_Starting_In_Box (const AABoxClass & box,int i)
{
//
// Loop over all the paths in our list counting down until
// we get to the i'th one that starts in the given box.
//
WaypathClass * path = NULL;
int count = i;
for (int index=0; index<m_WaypathList.Count(); index++) {
WaypathClass *curr_waypath = m_WaypathList[index];
if (CollisionMath::Overlap_Test(box,curr_waypath->Get_Point(0)->Get_Position()) == CollisionMath::INSIDE) {
if (count == 0) {
path = curr_waypath;
break;
} else {
count--;
}
}
}
if (path != NULL) {
path->Add_Ref();
}
return path;
}
///////////////////////////////////////////////////////////////////////////
//
// Reset_Waypaths
//
///////////////////////////////////////////////////////////////////////////
void
PathfindClass::Reset_Waypaths (void)
{
//
// Release our hold on each of the waypaths
//
int index = m_WaypathList.Count ();
while (index --) {
WaypathClass *waypath = m_WaypathList[index];
REF_PTR_RELEASE (waypath);
}
//
// Remove all the waypaths from our list
//
m_WaypathList.Delete_All ();
return ;
}
//////////////////////////////////////////////////////////////////////////////////
//
// Save_Waypaths
//
//////////////////////////////////////////////////////////////////////////////////
bool
PathfindClass::Save_Waypaths (ChunkSaveClass &csave)
{
//
// Loop over all the waypaths
//
int index = m_WaypathList.Count ();
while (index --) {
WaypathClass *waypath = m_WaypathList[index];
//
// Save this waypath object
//
csave.Begin_Chunk (waypath->Get_Factory ().Chunk_ID ());
waypath->Get_Factory ().Save (csave, waypath);
csave.End_Chunk ();
//
// Now save all the waypoints that this waypath contains
//
int waypoint_index = waypath->Get_Point_Count ();
while (waypoint_index --) {
WaypointClass *waypoint = waypath->Get_Point (waypoint_index);
//
// Save this waypoint object
//
csave.Begin_Chunk (waypoint->Get_Factory ().Chunk_ID ());
waypoint->Get_Factory ().Save (csave, waypoint);
csave.End_Chunk ();
}
}
return true;
}
//////////////////////////////////////////////////////////////////////////////////
//
// Save_Culling_System
//
//////////////////////////////////////////////////////////////////////////////////
bool
PathfindClass::Save_Culling_System (ChunkSaveClass &csave)
{
bool retval = true;
csave.Begin_Chunk (CHUNKID_SECTOR_CULLING_SYSTEM);
//
// Write the AABTreeCullSystem we use for sector lookups
// to the chunk
//
csave.Begin_Chunk (CHUNKID_SECTOR_CULL_TREE);
m_SectorTree.Save (csave);
csave.End_Chunk ();
//
// Now write the contents of the tree (the sector list)
// to the chunk
//
int count = m_SectorList.Count ();
for (int index = 0; index < count && retval; index ++) {
Save_Sector (csave, m_SectorList[index]);
}
csave.End_Chunk ();
return retval;
}
//////////////////////////////////////////////////////////////////////////////////
//
// Load_Culling_System
//
//////////////////////////////////////////////////////////////////////////////////
bool
PathfindClass::Load_Culling_System (ChunkLoadClass &cload)
{
bool retval = true;
//
// Read all the chunks...
//
while (cload.Open_Chunk ()) {
switch (cload.Cur_Chunk_ID ()) {
case CHUNKID_SECTOR_CULL_TREE:
m_SectorTree.Load (cload);
break;
case CHUNKID_SECTOR:
retval &= Load_Sector (cload);
break;
default:
WWDEBUG_SAY (("Unknown chunk ID 0x%X\r\n", cload.Cur_Chunk_ID ()));
retval = false;
break;
}
cload.Close_Chunk ();
}
return retval;
}
//////////////////////////////////////////////////////////////////////////////////
//
// Re_Partition_Sector_Tree
//
//////////////////////////////////////////////////////////////////////////////////
void
PathfindClass::Re_Partition_Sector_Tree (void)
{
m_SectorTree.Re_Partition ();
return ;
}
/////////////////////////////////////////////////////////////////////////
//
// Peek_Portal
//
/////////////////////////////////////////////////////////////////////////
PathfindPortalClass *
PathfindClass::Peek_Portal (int portal_index)
{
PathfindPortalClass *portal = NULL;
if (portal_index >= TEMP_PORTAL_ID_START) {
//
// Try to find the portal in our temporary portal list
//
for (int index = 0; index < m_TemporaryPortalList.Count (); index ++) {
//
// Is this the portal we are looking for?
//
if (portal_index == (int)m_TemporaryPortalList[index]->Get_ID ()) {
portal = m_TemporaryPortalList[index];
break;
}
}
} else if (portal_index >= WAYPATH_PORTAL_ID_START) {
//
// Look for the portal in our waypath portal list
//
portal = m_WaypathPortalList[portal_index - WAYPATH_PORTAL_ID_START];
} else if (portal_index >= 0) {
portal = m_PortalList[portal_index];
}
return portal;
}
//////////////////////////////////////////////////////////////////////////////////
//
// Get_Height_Value
//
//////////////////////////////////////////////////////////////////////////////////
float
PathfindClass::Get_Height_Value (const Vector3 &pos)
{
return HeightDBClass::Get_Height (pos);
}
//////////////////////////////////////////////////////////////////////////////////
//
// Find_Random_Spot
//
//////////////////////////////////////////////////////////////////////////////////
static DynamicVectorClass<AABoxClass> box_list;
bool
PathfindClass::Find_Random_Spot
(
const Vector3 &center,
float max_dist,
Vector3 * dest
)
{
bool retval = false;
//
// Lookup the starting sector we'll search from
//
PathfindSectorClass *start_sector = Find_Sector (center, 0.1F, NULL);
if (start_sector != NULL) {
box_list.Reset_Active();
//
// Start a list of valid 'regions' with the bounding box of the
// current sector
//
box_list.Add (start_sector->Get_Bounding_Box ());
//
// Square the distances for quick compares
//
float max_dist2 = max_dist * max_dist;
//
// Loop over all connecting sectors and add any of them that meet
// the distance requirements to our list
//
for (int index = 0; index < start_sector->Get_Portal_Count (); index ++) {
PathfindPortalClass *portal = start_sector->Peek_Portal (index);
PathfindSectorClass *dest_sector = portal->Peek_Dest_Sector (start_sector);
if (dest_sector != NULL) {
const AABoxClass &bounding_box = dest_sector->Get_Bounding_Box ();
//
// Find a "close" point to the box from the starting position
//
Vector3 closest_pt = center;
Clip_Point (&closest_pt, bounding_box);
//
// Is this box within the maximum acceptable distance?
//
float dist_to_box2 = (center - closest_pt).Length2 ();
if (dist_to_box2 < max_dist2) {
box_list.Add (bounding_box);
}
}
}
//
// Pick a random box from the list
//
int count = box_list.Count ();
int box_index = (rand () % count);
const AABoxClass &box = box_list[box_index];
//
// Find a "close" point to the box from the starting position
//
Vector3 closest_pt = center;
Clip_Point (&closest_pt, box);
//
// Now add a random vector to this point that will stay
// inside the requirements
//
float curr_dist = (closest_pt - center).Length ();
float max_allowable_dist = (max_dist - curr_dist);
(*dest) = closest_pt;
dest->X += WWMath::Random_Float (-max_allowable_dist, max_allowable_dist);
dest->Y += WWMath::Random_Float (-max_allowable_dist, max_allowable_dist);
//
// Now, simply make sure the point stays inside the pathfind sector
//
Clip_Point (dest, box);
retval = true;
} else if (m_SectorList.Count () == 0) {
//
// If we don't have any pathfind data, then simply choose a random point
//
(*dest) = center;
dest->X += WWMath::Random_Float (-max_dist, max_dist);
dest->Y += WWMath::Random_Float (-max_dist, max_dist);
retval = true;
}
return retval;
}
//////////////////////////////////////////////////////////////////////////////////
//
// Free_Waypath_Sectors_And_Portals
//
//////////////////////////////////////////////////////////////////////////////////
void
PathfindClass::Free_Waypath_Sectors_And_Portals (void)
{
//
// Release our hold on each of the waypath portals
//
for (int index = 0; index < m_WaypathPortalList.Count (); index ++) {
PathfindPortalClass *portal = m_WaypathPortalList[index];
//
// Remove this portal from the sectors that reference it
//
uint16 dest_sector1_id = portal->Get_Dest_Sector1 ();
uint16 dest_sector2_id = portal->Get_Dest_Sector2 ();
PathfindSectorClass *dest_sector1 = Peek_Sector (dest_sector1_id);
PathfindSectorClass *dest_sector2 = Peek_Sector (dest_sector2_id);
if (dest_sector1 != NULL) {
dest_sector1->Remove_Portal (portal->Get_ID ());
}
if (dest_sector2 != NULL) {
dest_sector2->Remove_Portal (portal->Get_ID ());
}
REF_PTR_RELEASE (portal);
}
//
// Loop over all the sectors in our list
//
for (index = 0; index < m_SectorList.Count (); index ++) {
PathfindSectorClass *sector = m_SectorList[index];
//
// Is this sector a waypath sector?
//
if (sector->As_PathfindWaypathSectorClass () != NULL) {
//
// Free the sector and remove it from the list
//
REF_PTR_RELEASE (sector);
m_SectorList.Delete (index);
index --;
}
}
//
// Reset the portal list
//
m_WaypathPortalList.Delete_All ();
return ;
}
//////////////////////////////////////////////////////////////////////////////////
//
// Generate_Waypath_Sectors_And_Portals
//
//////////////////////////////////////////////////////////////////////////////////
void
PathfindClass::Generate_Waypath_Sectors_And_Portals (void)
{
//
// Start fresh
//
Free_Waypath_Sectors_And_Portals ();
//
// Loop over all the waypaths in the system
//
for (int index = 0; index < m_WaypathList.Count (); index ++) {
WaypathClass *waypath = m_WaypathList[index];
//
// Only process waypaths that can be used during a path solve
//
if (waypath->Get_Flag (WaypathClass::FLAG_INNATE_PATHFIND)) {
Generate_Waypath_Sector_And_Portals (waypath);
}
}
return ;
}
//////////////////////////////////////////////////////////////////////////////////
//
// Generate_Waypath_Sector_And_Portals
//
//////////////////////////////////////////////////////////////////////////////////
void
PathfindClass::Generate_Waypath_Sector_And_Portals (WaypathClass *waypath)
{
DynamicVectorClass<PathfindWaypathPortalClass *> portal_list;
//
// Get information about this waypath
//
int waypoint_count = waypath->Get_Point_Count ();
int waypath_id = waypath->Get_ID ();
//
// Create a new "waypath sector" that will hold all the enter and
// exit portals for us
//
PathfindWaypathSectorClass *new_sector = new PathfindWaypathSectorClass;
new_sector->Set_Waypath_ID (waypath_id);
new_sector->Set_Bounding_Box (AABoxClass (Vector3 (0, 0, 0), Vector3 (0, 0, 0)));
//
// Register this new sector with the pathfind system
//
Add_Sector (new_sector, false);
//
// Add a portal around each waypoint in the path
//
for (int index = 0; index < waypoint_count; index ++) {
WaypointClass *waypoint = waypath->Get_Point (index);
const Vector3 &waypoint_pos = waypoint->Get_Position ();
//
// Check to see if there is a pathfind sector at the waypoint position...
// if there is, then we should create a new portal at this position.
//
PathfindSectorClass *sector_at_pos = Find_Sector (waypoint_pos, 0.125F);
if (sector_at_pos != NULL) {
//
// Add a new portal to the list at this position
//
::Add_New_Portal_To_List (portal_list, new_sector, sector_at_pos, waypath_id,
index, waypoint_pos, 0);
}
}
//
// Now add a portal everywhere the waypath intersects a pathfind sector
//
Add_Intersection_Portals_To_List (portal_list, waypath, new_sector);
if (portal_list.Count () > 0) {
//
// Now sort the portals based on occurance within the path
//
::qsort (&portal_list[0], portal_list.Count (),
sizeof (PathfindWaypathPortalClass *), fnCompareWaypathPortalsCallback);
//
// Register the portals with the system and add them to their sectors
//
for (int portal_index = 0; portal_index < portal_list.Count (); portal_index ++) {
PathfindWaypathPortalClass *curr_portal = portal_list[portal_index];
//
// Register the portal
//
int portal_id = Add_Waypath_Portal (curr_portal);
//
// Lookup the sectors this portal will connect
//
uint16 dest_sector1_id = curr_portal->Get_Dest_Sector1 ();
uint16 dest_sector2_id = curr_portal->Get_Dest_Sector2 ();
PathfindSectorClass *dest_sector1 = Peek_Sector (dest_sector1_id);
PathfindSectorClass *dest_sector2 = Peek_Sector (dest_sector2_id);
//
// Add this portal to the sectors
//
if (dest_sector1 != NULL) {
dest_sector1->Add_Portal (portal_id);
}
if (dest_sector2 != NULL) {
dest_sector2->Add_Portal (portal_id);
}
//
// Release our hold on this portal
//
REF_PTR_RELEASE (curr_portal);
}
}
//
// Now release our hold on the sector we just created
//
REF_PTR_RELEASE (new_sector);
return ;
}
//////////////////////////////////////////////////////////////////////////////////
//
// Add_Intersection_Portals_To_List
//
//////////////////////////////////////////////////////////////////////////////////
void
PathfindClass::Add_Intersection_Portals_To_List
(
DynamicVectorClass<PathfindWaypathPortalClass *> & portal_list,
WaypathClass * waypath,
PathfindWaypathSectorClass * dest_sector
)
{
int waypoint_count = waypath->Get_Point_Count ();
int waypath_id = waypath->Get_ID ();
//
// For this waypath, find all the pathfind sectors it intersects
//
for (int waypoint_index = 0; waypoint_index < waypoint_count - 1; waypoint_index ++) {
WaypointClass *waypoint1 = waypath->Get_Point (waypoint_index);
WaypointClass *waypoint2 = waypath->Get_Point (waypoint_index + 1);
const Vector3 &pos1 = waypoint1->Get_Position ();
const Vector3 &pos2 = waypoint2->Get_Position ();
//
// Build a bounding box around the two points
//
Vector3 extent = (pos2 - pos1) * 0.5F;
Vector3 center = pos1 + extent;
extent.X = max (WWMath::Fabs (extent.X), 1.0F);
extent.Y = max (WWMath::Fabs (extent.Y), 1.0F);
extent.Z = max (WWMath::Fabs (extent.Z), 1.0F);
AABoxClass bounding_box (center, extent);
//
// Find all the pathfind sectors in this bounding box
//
m_SectorTree.Reset_Collection ();
m_SectorTree.Collect_Objects (bounding_box);
//
// Loop over all the sectors that this line segment could possibly intersect
//
PathfindSectorClass *sector = NULL;
for ( sector = m_SectorTree.Peek_First_Collected_Object ();
sector != NULL;
sector = m_SectorTree.Peek_Next_Collected_Object (sector))
{
const AABoxClass &sector_box = sector->Get_Bounding_Box ();
//
// Find where this line segment intersects the sector (if at all)
//
float percent = 0;
Vector3 portal_pos;
if (::Find_Intersection_Point (sector_box, pos1, pos2, &percent, &portal_pos)) {
//
// Add a new portal to the list at this position
//
::Add_New_Portal_To_List (portal_list, dest_sector, sector, waypath_id,
waypoint_index, portal_pos, percent);
}
}
}
return ;
}
//////////////////////////////////////////////////////////////////////////////////
//
// Add_New_Portal_To_List
//
//////////////////////////////////////////////////////////////////////////////////
void
Add_New_Portal_To_List
(
DynamicVectorClass<PathfindWaypathPortalClass *> & portal_list,
PathfindSectorClass * dest_sector1,
PathfindSectorClass * dest_sector2,
int waypath_id,
int waypoint_index,
const Vector3 & portal_pos,
float percent
)
{
const Vector3 portal_extent (0.5F, 0.5F, 1.0F);
//
// Create a position along this waypath for the waypoint
//
WaypathPositionClass waypath_pos;
waypath_pos.Set_Waypath_ID (waypath_id);
waypath_pos.Set_Waypoint_Index (waypoint_index);
waypath_pos.Set_Percent (percent);
//
// Allcoate and configure a new portal
//
PathfindWaypathPortalClass *new_portal = new PathfindWaypathPortalClass;
new_portal->Set_Waypath_Pos (waypath_pos);
new_portal->Set_Bounding_Box (AABoxClass (portal_pos, portal_extent));
if (dest_sector1 != NULL) {
new_portal->Add_Dest_Sector (dest_sector1);
}
if (dest_sector2 != NULL) {
new_portal->Add_Dest_Sector (dest_sector2);
}
//
// Add this portal to the list
//
portal_list.Add (new_portal);
return ;
}
//////////////////////////////////////////////////////////////////////////////////
//
// Find_Intersection_Point
//
//////////////////////////////////////////////////////////////////////////////////
bool
Find_Intersection_Point
(
const AABoxClass & box,
const Vector3 & p0,
const Vector3 & p1,
float * percent,
Vector3 * intersection_point
)
{
bool retval = false;
//
// Find the locations of each of the 6 "planes"
// we will be testing against
//
float x1 = box.Center.X - box.Extent.X;
float x2 = box.Center.X + box.Extent.X;
float y1 = box.Center.Y - box.Extent.Y;
float y2 = box.Center.Y + box.Extent.Y;
float z1 = box.Center.Z - box.Extent.Z;
float z2 = box.Center.Z + box.Extent.Z;
float t_values[6] = { -1, -1, -1, -1, -1, -1 };
Vector3 delta = p1 - p0;
//
// Find the "t" values where the line intersects the
// 2 "Y-Z" planes
//
if (delta.X != 0) {
t_values[0] = (x1 - p0.X) / delta.X;
t_values[1] = (x2 - p0.X) / delta.X;
}
//
// Find the "t" values where the line intersects the
// 2 "X-Z" planes
//
if (delta.Y != 0) {
t_values[2] = (y1 - p0.Y) / delta.Y;
t_values[3] = (y2 - p0.Y) / delta.Y;
}
//
// Find the "t" values where the line intersects the
// 2 "X-Y" planes
//
if (delta.Z != 0) {
t_values[4] = (z1 - p0.Z) / delta.Z;
t_values[5] = (z2 - p0.Z) / delta.Z;
}
//
// Loop over all the "t" values we've calculated
//
(*percent) = 2.0F;
for (int index = 0; index < 6; index ++) {
//
// Is this "t" value the smallest in-range value we've
// found yet?
//
if ( t_values[index] >= 0 &&
t_values[index] <= 1.0F &&
t_values[index] < (*percent))
{
//
// Find the point which exists at this "t" value along the line segment
//
Vector3 point = p0 + delta * t_values[index];
//
// If this point isn't outside the box, then we'll consider it good enough
//
if ( (WWMath::Fabs (point.X - box.Center.X) <= (box.Extent.X + WWMATH_EPSILON)) &&
(WWMath::Fabs (point.Y - box.Center.Y) <= (box.Extent.Y + WWMATH_EPSILON)) &&
(WWMath::Fabs (point.Z - box.Center.Z) <= (box.Extent.Z + WWMATH_EPSILON)))
{
(*percent) = t_values[index];
(*intersection_point) = point;
retval = true;
}
}
}
return retval;
}
////////////////////////////////////////////////////////////////
//
// fnCompareWaypathPortalsCallback
//
////////////////////////////////////////////////////////////////
int __cdecl
fnCompareWaypathPortalsCallback
(
const void *elem1,
const void *elem2
)
{
WWASSERT (elem1 != NULL);
WWASSERT (elem2 != NULL);
PathfindWaypathPortalClass *portal1 = *((PathfindWaypathPortalClass **)elem1);
PathfindWaypathPortalClass *portal2 = *((PathfindWaypathPortalClass **)elem2);
const WaypathPositionClass &pos1 = portal1->Get_Waypath_Pos ();
const WaypathPositionClass &pos2 = portal2->Get_Waypath_Pos ();
//
// Sort based on the waypath segment the portals lie one
//
int result = 0;
if (pos1.Get_Waypoint_Index () < pos2.Get_Waypoint_Index ()) {
result = -1;
} else if (pos1.Get_Waypoint_Index () > pos2.Get_Waypoint_Index ()) {
result = 1;
} else {
//
// If the portals lie on the same segment, then sort by their
// position along the segment
//
if (pos1.Get_Percent () < pos2.Get_Percent ()) {
result = -1;
} else if (pos1.Get_Percent () > pos2.Get_Percent ()) {
result = 1;
}
}
return result;
}
//////////////////////////////////////////////////////////////////////////////////
//
// Find_Portals
//
//////////////////////////////////////////////////////////////////////////////////
void
PathfindClass::Find_Portals
(
const Vector3 & p0,
const Vector3 & p1,
DynamicVectorClass<PathfindPortalClass *> & list,
bool action_portals_only
)
{
//
// Loop over all the portals
//
for (int index = 0; index < m_PortalList.Count (); index ++) {
PathfindPortalClass *portal = m_PortalList[index];
PathfindActionPortalClass *action_portal = portal->As_PathfindActionPortalClass ();
if (action_portals_only == false || (action_portal != NULL && portal->Get_Action_Type () != PathClass::ACTION_NONE)) {
//
// Get the bounding box for the portal
//
AABoxClass box;
portal->Get_Bounding_Box (box);
//
// Does the line pass through this box?
//
float percent1 = 0;
Vector3 intersection_pt (0, 0, 0);
if (::Find_Intersection_Point (box, p0, p1, &percent1, &intersection_pt)) {
bool is_valid = true;
//
// If this is an action portal request, we need to ensure that the
// line segment passes through to the exit portal
//
if (action_portals_only) {
is_valid = false;
PathfindSectorClass *sector = action_portal->Peek_Dest_Sector (NULL);
if (sector != NULL) {
//
// Get a pointer to the exit portal
//
PathfindPortalClass *exit_portal = action_portal->Get_Exit_Portal ();
if (exit_portal != NULL) {
//
// Does the line segment pass through to the exit portal?
//
AABoxClass exit_box;
exit_portal->Get_Bounding_Box (exit_box);
if (::Find_Intersection_Point (exit_box, p0, p1,
&percent1, &intersection_pt))
{
float dist1 = (box.Center - p0).Length2 ();
float dist2 = (exit_box.Center - p0).Length2 ();
if (dist1 < dist2) {
is_valid = true;
}
}
}
}
}
//
// Add the portal to the list
//
if (is_valid) {
list.Add (portal);
}
}
}
}
return ;
}
//////////////////////////////////////////////////////////////////////////////////
//
// Render_Debug_Widgets
//
//////////////////////////////////////////////////////////////////////////////////
void
PathfindClass::Render_Debug_Widgets (RenderInfoClass &rinfo)
{
m_SectorDisplayWidgets.Render_Debug_Widgets (rinfo);
m_PortalDisplayWidgets.Render_Debug_Widgets (rinfo);
return ;
}