You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
998 lines
26 KiB
998 lines
26 KiB
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//
|
|
//=============================================================================//
|
|
// nav_pathfind.h
|
|
// Path-finding mechanisms using the Navigation Mesh
|
|
// Author: Michael S. Booth ([email protected]), January 2003
|
|
|
|
#ifndef _NAV_PATHFIND_H_
|
|
#define _NAV_PATHFIND_H_
|
|
|
|
#include "tier0/vprof.h"
|
|
#include "mathlib/ssemath.h"
|
|
#include "nav_area.h"
|
|
|
|
#ifdef STAGING_ONLY
|
|
extern int g_DebugPathfindCounter;
|
|
#endif
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Used when building a path to determine the kind of path to build
|
|
*/
|
|
enum RouteType
|
|
{
|
|
DEFAULT_ROUTE,
|
|
FASTEST_ROUTE,
|
|
SAFEST_ROUTE,
|
|
RETREAT_ROUTE,
|
|
};
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Functor used with NavAreaBuildPath()
|
|
*/
|
|
class ShortestPathCost
|
|
{
|
|
public:
|
|
float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length )
|
|
{
|
|
if ( fromArea == NULL )
|
|
{
|
|
// first area in path, no cost
|
|
return 0.0f;
|
|
}
|
|
else
|
|
{
|
|
// compute distance traveled along path so far
|
|
float dist;
|
|
|
|
if ( ladder )
|
|
{
|
|
dist = ladder->m_length;
|
|
}
|
|
else if ( length > 0.0 )
|
|
{
|
|
dist = length;
|
|
}
|
|
else
|
|
{
|
|
dist = ( area->GetCenter() - fromArea->GetCenter() ).Length();
|
|
}
|
|
|
|
float cost = dist + fromArea->GetCostSoFar();
|
|
|
|
// if this is a "crouch" area, add penalty
|
|
if ( area->GetAttributes() & NAV_MESH_CROUCH )
|
|
{
|
|
const float crouchPenalty = 20.0f; // 10
|
|
cost += crouchPenalty * dist;
|
|
}
|
|
|
|
// if this is a "jump" area, add penalty
|
|
if ( area->GetAttributes() & NAV_MESH_JUMP )
|
|
{
|
|
const float jumpPenalty = 5.0f;
|
|
cost += jumpPenalty * dist;
|
|
}
|
|
|
|
return cost;
|
|
}
|
|
}
|
|
};
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Find path from startArea to goalArea via an A* search, using supplied cost heuristic.
|
|
* If cost functor returns -1 for an area, that area is considered a dead end.
|
|
* This doesn't actually build a path, but the path is defined by following parent
|
|
* pointers back from goalArea to startArea.
|
|
* If 'closestArea' is non-NULL, the closest area to the goal is returned (useful if the path fails).
|
|
* If 'goalArea' is NULL, will compute a path as close as possible to 'goalPos'.
|
|
* If 'goalPos' is NULL, will use the center of 'goalArea' as the goal position.
|
|
* If 'maxPathLength' is nonzero, path building will stop when this length is reached.
|
|
* Returns true if a path exists.
|
|
*/
|
|
#define IGNORE_NAV_BLOCKERS true
|
|
template< typename CostFunctor >
|
|
bool NavAreaBuildPath( CNavArea *startArea, CNavArea *goalArea, const Vector *goalPos, CostFunctor &costFunc, CNavArea **closestArea = NULL, float maxPathLength = 0.0f, int teamID = TEAM_ANY, bool ignoreNavBlockers = false )
|
|
{
|
|
VPROF_BUDGET( "NavAreaBuildPath", "NextBotSpiky" );
|
|
|
|
if ( closestArea )
|
|
{
|
|
*closestArea = startArea;
|
|
}
|
|
|
|
#ifdef STAGING_ONLY
|
|
bool isDebug = ( g_DebugPathfindCounter-- > 0 );
|
|
#endif
|
|
|
|
if (startArea == NULL)
|
|
return false;
|
|
|
|
startArea->SetParent( NULL );
|
|
|
|
if (goalArea != NULL && goalArea->IsBlocked( teamID, ignoreNavBlockers ))
|
|
goalArea = NULL;
|
|
|
|
if (goalArea == NULL && goalPos == NULL)
|
|
return false;
|
|
|
|
// if we are already in the goal area, build trivial path
|
|
if (startArea == goalArea)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// determine actual goal position
|
|
Vector actualGoalPos = (goalPos) ? *goalPos : goalArea->GetCenter();
|
|
|
|
// start search
|
|
CNavArea::ClearSearchLists();
|
|
|
|
// compute estimate of path length
|
|
/// @todo Cost might work as "manhattan distance"
|
|
startArea->SetTotalCost( (startArea->GetCenter() - actualGoalPos).Length() );
|
|
|
|
float initCost = costFunc( startArea, NULL, NULL, NULL, -1.0f );
|
|
if (initCost < 0.0f)
|
|
return false;
|
|
startArea->SetCostSoFar( initCost );
|
|
startArea->SetPathLengthSoFar( 0.0 );
|
|
|
|
startArea->AddToOpenList();
|
|
|
|
// keep track of the area we visit that is closest to the goal
|
|
float closestAreaDist = startArea->GetTotalCost();
|
|
|
|
// do A* search
|
|
while( !CNavArea::IsOpenListEmpty() )
|
|
{
|
|
// get next area to check
|
|
CNavArea *area = CNavArea::PopOpenList();
|
|
|
|
#ifdef STAGING_ONLY
|
|
if ( isDebug )
|
|
{
|
|
area->DrawFilled( 0, 255, 0, 128, 30.0f );
|
|
}
|
|
#endif
|
|
|
|
// don't consider blocked areas
|
|
if ( area->IsBlocked( teamID, ignoreNavBlockers ) )
|
|
continue;
|
|
|
|
// check if we have found the goal area or position
|
|
if (area == goalArea || (goalArea == NULL && goalPos && area->Contains( *goalPos )))
|
|
{
|
|
if (closestArea)
|
|
{
|
|
*closestArea = area;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// search adjacent areas
|
|
enum SearchType
|
|
{
|
|
SEARCH_FLOOR, SEARCH_LADDERS, SEARCH_ELEVATORS
|
|
};
|
|
SearchType searchWhere = SEARCH_FLOOR;
|
|
int searchIndex = 0;
|
|
|
|
int dir = NORTH;
|
|
const NavConnectVector *floorList = area->GetAdjacentAreas( NORTH );
|
|
|
|
bool ladderUp = true;
|
|
const NavLadderConnectVector *ladderList = NULL;
|
|
enum { AHEAD = 0, LEFT, RIGHT, BEHIND, NUM_TOP_DIRECTIONS };
|
|
int ladderTopDir = AHEAD;
|
|
bool bHaveMaxPathLength = ( maxPathLength > 0.0f );
|
|
float length = -1;
|
|
|
|
while( true )
|
|
{
|
|
CNavArea *newArea = NULL;
|
|
NavTraverseType how;
|
|
const CNavLadder *ladder = NULL;
|
|
const CFuncElevator *elevator = NULL;
|
|
|
|
//
|
|
// Get next adjacent area - either on floor or via ladder
|
|
//
|
|
if ( searchWhere == SEARCH_FLOOR )
|
|
{
|
|
// if exhausted adjacent connections in current direction, begin checking next direction
|
|
if ( searchIndex >= floorList->Count() )
|
|
{
|
|
++dir;
|
|
|
|
if ( dir == NUM_DIRECTIONS )
|
|
{
|
|
// checked all directions on floor - check ladders next
|
|
searchWhere = SEARCH_LADDERS;
|
|
|
|
ladderList = area->GetLadders( CNavLadder::LADDER_UP );
|
|
searchIndex = 0;
|
|
ladderTopDir = AHEAD;
|
|
}
|
|
else
|
|
{
|
|
// start next direction
|
|
floorList = area->GetAdjacentAreas( (NavDirType)dir );
|
|
searchIndex = 0;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
const NavConnect &floorConnect = floorList->Element( searchIndex );
|
|
newArea = floorConnect.area;
|
|
length = floorConnect.length;
|
|
how = (NavTraverseType)dir;
|
|
++searchIndex;
|
|
|
|
if ( IsX360() && searchIndex < floorList->Count() )
|
|
{
|
|
PREFETCH360( floorList->Element( searchIndex ).area, 0 );
|
|
}
|
|
}
|
|
else if ( searchWhere == SEARCH_LADDERS )
|
|
{
|
|
if ( searchIndex >= ladderList->Count() )
|
|
{
|
|
if ( !ladderUp )
|
|
{
|
|
// checked both ladder directions - check elevators next
|
|
searchWhere = SEARCH_ELEVATORS;
|
|
searchIndex = 0;
|
|
ladder = NULL;
|
|
}
|
|
else
|
|
{
|
|
// check down ladders
|
|
ladderUp = false;
|
|
ladderList = area->GetLadders( CNavLadder::LADDER_DOWN );
|
|
searchIndex = 0;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if ( ladderUp )
|
|
{
|
|
ladder = ladderList->Element( searchIndex ).ladder;
|
|
|
|
// do not use BEHIND connection, as its very hard to get to when going up a ladder
|
|
if ( ladderTopDir == AHEAD )
|
|
{
|
|
newArea = ladder->m_topForwardArea;
|
|
}
|
|
else if ( ladderTopDir == LEFT )
|
|
{
|
|
newArea = ladder->m_topLeftArea;
|
|
}
|
|
else if ( ladderTopDir == RIGHT )
|
|
{
|
|
newArea = ladder->m_topRightArea;
|
|
}
|
|
else
|
|
{
|
|
++searchIndex;
|
|
ladderTopDir = AHEAD;
|
|
continue;
|
|
}
|
|
|
|
how = GO_LADDER_UP;
|
|
++ladderTopDir;
|
|
}
|
|
else
|
|
{
|
|
newArea = ladderList->Element( searchIndex ).ladder->m_bottomArea;
|
|
how = GO_LADDER_DOWN;
|
|
ladder = ladderList->Element(searchIndex).ladder;
|
|
++searchIndex;
|
|
}
|
|
|
|
if ( newArea == NULL )
|
|
continue;
|
|
|
|
length = -1.0f;
|
|
}
|
|
else // if ( searchWhere == SEARCH_ELEVATORS )
|
|
{
|
|
const NavConnectVector &elevatorAreas = area->GetElevatorAreas();
|
|
|
|
elevator = area->GetElevator();
|
|
|
|
if ( elevator == NULL || searchIndex >= elevatorAreas.Count() )
|
|
{
|
|
// done searching connected areas
|
|
elevator = NULL;
|
|
break;
|
|
}
|
|
|
|
newArea = elevatorAreas[ searchIndex++ ].area;
|
|
if ( newArea->GetCenter().z > area->GetCenter().z )
|
|
{
|
|
how = GO_ELEVATOR_UP;
|
|
}
|
|
else
|
|
{
|
|
how = GO_ELEVATOR_DOWN;
|
|
}
|
|
|
|
length = -1.0f;
|
|
}
|
|
|
|
|
|
// don't backtrack
|
|
Assert( newArea );
|
|
if ( newArea == area->GetParent() )
|
|
continue;
|
|
if ( newArea == area ) // self neighbor?
|
|
continue;
|
|
|
|
// don't consider blocked areas
|
|
if ( newArea->IsBlocked( teamID, ignoreNavBlockers ) )
|
|
continue;
|
|
|
|
float newCostSoFar = costFunc( newArea, area, ladder, elevator, length );
|
|
|
|
// NaNs really mess this function up causing tough to track down hangs. If
|
|
// we get inf back, clamp it down to a really high number.
|
|
DebuggerBreakOnNaN_StagingOnly( newCostSoFar );
|
|
if ( IS_NAN( newCostSoFar ) )
|
|
newCostSoFar = 1e30f;
|
|
|
|
// check if cost functor says this area is a dead-end
|
|
if ( newCostSoFar < 0.0f )
|
|
continue;
|
|
|
|
// Safety check against a bogus functor. The cost of the path
|
|
// A...B, C should always be at least as big as the path A...B.
|
|
Assert( newCostSoFar >= area->GetCostSoFar() );
|
|
|
|
// And now that we've asserted, let's be a bit more defensive.
|
|
// Make sure that any jump to a new area incurs some pathfinsing
|
|
// cost, to avoid us spinning our wheels over insignificant cost
|
|
// benefit, floating point precision bug, or busted cost functor.
|
|
float minNewCostSoFar = area->GetCostSoFar() * 1.00001f + 0.00001f;
|
|
newCostSoFar = Max( newCostSoFar, minNewCostSoFar );
|
|
|
|
// stop if path length limit reached
|
|
if ( bHaveMaxPathLength )
|
|
{
|
|
// keep track of path length so far
|
|
float deltaLength = ( newArea->GetCenter() - area->GetCenter() ).Length();
|
|
float newLengthSoFar = area->GetPathLengthSoFar() + deltaLength;
|
|
if ( newLengthSoFar > maxPathLength )
|
|
continue;
|
|
|
|
newArea->SetPathLengthSoFar( newLengthSoFar );
|
|
}
|
|
|
|
if ( ( newArea->IsOpen() || newArea->IsClosed() ) && newArea->GetCostSoFar() <= newCostSoFar )
|
|
{
|
|
// this is a worse path - skip it
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
// compute estimate of distance left to go
|
|
float distSq = ( newArea->GetCenter() - actualGoalPos ).LengthSqr();
|
|
float newCostRemaining = ( distSq > 0.0 ) ? FastSqrt( distSq ) : 0.0 ;
|
|
|
|
// track closest area to goal in case path fails
|
|
if ( closestArea && newCostRemaining < closestAreaDist )
|
|
{
|
|
*closestArea = newArea;
|
|
closestAreaDist = newCostRemaining;
|
|
}
|
|
|
|
newArea->SetCostSoFar( newCostSoFar );
|
|
newArea->SetTotalCost( newCostSoFar + newCostRemaining );
|
|
|
|
if ( newArea->IsClosed() )
|
|
{
|
|
newArea->RemoveFromClosedList();
|
|
}
|
|
|
|
if ( newArea->IsOpen() )
|
|
{
|
|
// area already on open list, update the list order to keep costs sorted
|
|
newArea->UpdateOnOpenList();
|
|
}
|
|
else
|
|
{
|
|
newArea->AddToOpenList();
|
|
}
|
|
|
|
newArea->SetParent( area, how );
|
|
}
|
|
}
|
|
|
|
// we have searched this area
|
|
area->AddToClosedList();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Compute distance between two areas. Return -1 if can't reach 'endArea' from 'startArea'.
|
|
*/
|
|
template< typename CostFunctor >
|
|
float NavAreaTravelDistance( CNavArea *startArea, CNavArea *endArea, CostFunctor &costFunc, float maxPathLength = 0.0f )
|
|
{
|
|
if (startArea == NULL)
|
|
return -1.0f;
|
|
|
|
if (endArea == NULL)
|
|
return -1.0f;
|
|
|
|
if (startArea == endArea)
|
|
return 0.0f;
|
|
|
|
// compute path between areas using given cost heuristic
|
|
if (NavAreaBuildPath( startArea, endArea, NULL, costFunc, NULL, maxPathLength ) == false)
|
|
return -1.0f;
|
|
|
|
// compute distance along path
|
|
float distance = 0.0f;
|
|
for( CNavArea *area = endArea; area->GetParent(); area = area->GetParent() )
|
|
{
|
|
distance += (area->GetCenter() - area->GetParent()->GetCenter()).Length();
|
|
}
|
|
|
|
return distance;
|
|
}
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Do a breadth-first search, invoking functor on each area.
|
|
* If functor returns 'true', continue searching from this area.
|
|
* If functor returns 'false', the area's adjacent areas are not explored (dead end).
|
|
* If 'maxRange' is 0 or less, no range check is done (all areas will be examined).
|
|
*
|
|
* NOTE: Returns all areas that overlap range, even partially
|
|
*
|
|
* @todo Use ladder connections
|
|
*/
|
|
|
|
// helper function
|
|
inline void AddAreaToOpenList( CNavArea *area, CNavArea *parent, const Vector &startPos, float maxRange )
|
|
{
|
|
if (area == NULL)
|
|
return;
|
|
|
|
if (!area->IsMarked())
|
|
{
|
|
area->Mark();
|
|
area->SetTotalCost( 0.0f );
|
|
area->SetParent( parent );
|
|
|
|
if (maxRange > 0.0f)
|
|
{
|
|
// make sure this area overlaps range
|
|
Vector closePos;
|
|
area->GetClosestPointOnArea( startPos, &closePos );
|
|
if ((closePos - startPos).AsVector2D().IsLengthLessThan( maxRange ))
|
|
{
|
|
// compute approximate distance along path to limit travel range, too
|
|
float distAlong = parent->GetCostSoFar();
|
|
distAlong += (area->GetCenter() - parent->GetCenter()).Length();
|
|
area->SetCostSoFar( distAlong );
|
|
|
|
// allow for some fudge due to large size areas
|
|
if (distAlong <= 1.5f * maxRange)
|
|
area->AddToOpenList();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// infinite range
|
|
area->AddToOpenList();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/****************************************************************
|
|
* DEPRECATED: Use filter-based SearchSurroundingAreas below
|
|
****************************************************************/
|
|
#define INCLUDE_INCOMING_CONNECTIONS 0x1
|
|
#define INCLUDE_BLOCKED_AREAS 0x2
|
|
#define EXCLUDE_OUTGOING_CONNECTIONS 0x4
|
|
#define EXCLUDE_ELEVATORS 0x8
|
|
template < typename Functor >
|
|
void SearchSurroundingAreas( CNavArea *startArea, const Vector &startPos, Functor &func, float maxRange = -1.0f, unsigned int options = 0, int teamID = TEAM_ANY )
|
|
{
|
|
if (startArea == NULL)
|
|
return;
|
|
|
|
CNavArea::MakeNewMarker();
|
|
CNavArea::ClearSearchLists();
|
|
|
|
startArea->AddToOpenList();
|
|
startArea->SetTotalCost( 0.0f );
|
|
startArea->SetCostSoFar( 0.0f );
|
|
startArea->SetParent( NULL );
|
|
startArea->Mark();
|
|
|
|
while( !CNavArea::IsOpenListEmpty() )
|
|
{
|
|
// get next area to check
|
|
CNavArea *area = CNavArea::PopOpenList();
|
|
|
|
// don't use blocked areas
|
|
if ( area->IsBlocked( teamID ) && !(options & INCLUDE_BLOCKED_AREAS) )
|
|
continue;
|
|
|
|
// invoke functor on area
|
|
if (func( area ))
|
|
{
|
|
// explore adjacent floor areas
|
|
for( int dir=0; dir<NUM_DIRECTIONS; ++dir )
|
|
{
|
|
int count = area->GetAdjacentCount( (NavDirType)dir );
|
|
for( int i=0; i<count; ++i )
|
|
{
|
|
CNavArea *adjArea = area->GetAdjacentArea( (NavDirType)dir, i );
|
|
if ( options & EXCLUDE_OUTGOING_CONNECTIONS )
|
|
{
|
|
if ( !adjArea->IsConnected( area, NUM_DIRECTIONS ) )
|
|
{
|
|
continue; // skip this outgoing connection
|
|
}
|
|
}
|
|
|
|
AddAreaToOpenList( adjArea, area, startPos, maxRange );
|
|
}
|
|
}
|
|
|
|
// potentially include areas that connect TO this area via a one-way link
|
|
if (options & INCLUDE_INCOMING_CONNECTIONS)
|
|
{
|
|
for( int dir=0; dir<NUM_DIRECTIONS; ++dir )
|
|
{
|
|
const NavConnectVector *list = area->GetIncomingConnections( (NavDirType)dir );
|
|
|
|
FOR_EACH_VEC( (*list), it )
|
|
{
|
|
NavConnect connect = (*list)[ it ];
|
|
|
|
AddAreaToOpenList( connect.area, area, startPos, maxRange );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// explore adjacent areas connected by ladders
|
|
|
|
// check up ladders
|
|
const NavLadderConnectVector *ladderList = area->GetLadders( CNavLadder::LADDER_UP );
|
|
if (ladderList)
|
|
{
|
|
FOR_EACH_VEC( (*ladderList), it )
|
|
{
|
|
const CNavLadder *ladder = (*ladderList)[ it ].ladder;
|
|
|
|
// do not use BEHIND connection, as its very hard to get to when going up a ladder
|
|
AddAreaToOpenList( ladder->m_topForwardArea, area, startPos, maxRange );
|
|
AddAreaToOpenList( ladder->m_topLeftArea, area, startPos, maxRange );
|
|
AddAreaToOpenList( ladder->m_topRightArea, area, startPos, maxRange );
|
|
}
|
|
}
|
|
|
|
// check down ladders
|
|
ladderList = area->GetLadders( CNavLadder::LADDER_DOWN );
|
|
if (ladderList)
|
|
{
|
|
FOR_EACH_VEC( (*ladderList), it )
|
|
{
|
|
const CNavLadder *ladder = (*ladderList)[ it ].ladder;
|
|
|
|
AddAreaToOpenList( ladder->m_bottomArea, area, startPos, maxRange );
|
|
}
|
|
}
|
|
|
|
if ( (options & EXCLUDE_ELEVATORS) == 0 )
|
|
{
|
|
const NavConnectVector &elevatorList = area->GetElevatorAreas();
|
|
FOR_EACH_VEC( elevatorList, it )
|
|
{
|
|
CNavArea *elevatorArea = elevatorList[ it ].area;
|
|
AddAreaToOpenList( elevatorArea, area, startPos, maxRange );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Derive your own custom search functor from this interface method for use with SearchSurroundingAreas below.
|
|
*/
|
|
class ISearchSurroundingAreasFunctor
|
|
{
|
|
public:
|
|
virtual ~ISearchSurroundingAreasFunctor() { }
|
|
|
|
/**
|
|
* Perform user-defined action on area.
|
|
* Return 'false' to end the search (ie: you found what you were looking for)
|
|
*/
|
|
virtual bool operator() ( CNavArea *area, CNavArea *priorArea, float travelDistanceSoFar ) = 0;
|
|
|
|
// return true if 'adjArea' should be included in the ongoing search
|
|
virtual bool ShouldSearch( CNavArea *adjArea, CNavArea *currentArea, float travelDistanceSoFar )
|
|
{
|
|
return !adjArea->IsBlocked( TEAM_ANY );
|
|
}
|
|
|
|
/**
|
|
* Collect adjacent areas to continue the search by calling 'IncludeInSearch' on each
|
|
*/
|
|
virtual void IterateAdjacentAreas( CNavArea *area, CNavArea *priorArea, float travelDistanceSoFar )
|
|
{
|
|
// search adjacent outgoing connections
|
|
for( int dir=0; dir<NUM_DIRECTIONS; ++dir )
|
|
{
|
|
int count = area->GetAdjacentCount( (NavDirType)dir );
|
|
for( int i=0; i<count; ++i )
|
|
{
|
|
CNavArea *adjArea = area->GetAdjacentArea( (NavDirType)dir, i );
|
|
|
|
if ( ShouldSearch( adjArea, area, travelDistanceSoFar ) )
|
|
{
|
|
IncludeInSearch( adjArea, area );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Invoked after the search has completed
|
|
virtual void PostSearch( void ) { }
|
|
|
|
// consider 'area' in upcoming search steps
|
|
void IncludeInSearch( CNavArea *area, CNavArea *priorArea )
|
|
{
|
|
if ( area == NULL )
|
|
return;
|
|
|
|
if ( !area->IsMarked() )
|
|
{
|
|
area->Mark();
|
|
area->SetTotalCost( 0.0f );
|
|
area->SetParent( priorArea );
|
|
|
|
// compute approximate travel distance from start area of search
|
|
if ( priorArea )
|
|
{
|
|
float distAlong = priorArea->GetCostSoFar();
|
|
distAlong += ( area->GetCenter() - priorArea->GetCenter() ).Length();
|
|
area->SetCostSoFar( distAlong );
|
|
}
|
|
else
|
|
{
|
|
area->SetCostSoFar( 0.0f );
|
|
}
|
|
|
|
// adding an area to the open list also marks it
|
|
area->AddToOpenList();
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Do a breadth-first search starting from 'startArea' and continuing outward based on
|
|
* adjacent areas that pass the given filter
|
|
*/
|
|
inline void SearchSurroundingAreas( CNavArea *startArea, ISearchSurroundingAreasFunctor &func, float travelDistanceLimit = -1.0f )
|
|
{
|
|
if ( startArea )
|
|
{
|
|
CNavArea::MakeNewMarker();
|
|
CNavArea::ClearSearchLists();
|
|
|
|
startArea->AddToOpenList();
|
|
startArea->SetTotalCost( 0.0f );
|
|
startArea->SetCostSoFar( 0.0f );
|
|
startArea->SetParent( NULL );
|
|
startArea->Mark();
|
|
|
|
CUtlVector< CNavArea * > adjVector;
|
|
|
|
while( !CNavArea::IsOpenListEmpty() )
|
|
{
|
|
// get next area to check
|
|
CNavArea *area = CNavArea::PopOpenList();
|
|
|
|
if ( travelDistanceLimit > 0.0f && area->GetCostSoFar() > travelDistanceLimit )
|
|
continue;
|
|
|
|
if ( func( area, area->GetParent(), area->GetCostSoFar() ) )
|
|
{
|
|
func.IterateAdjacentAreas( area, area->GetParent(), area->GetCostSoFar() );
|
|
}
|
|
else
|
|
{
|
|
// search aborted
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
func.PostSearch();
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Starting from 'startArea', collect adjacent areas via a breadth-first search continuing outward until
|
|
* 'travelDistanceLimit' is reached.
|
|
* Areas in the collection will be "marked", returning true for IsMarked().
|
|
* Each area in the collection's GetCostSoFar() will be approximate travel distance from 'startArea'.
|
|
*/
|
|
inline void CollectSurroundingAreas( CUtlVector< CNavArea * > *nearbyAreaVector, CNavArea *startArea, float travelDistanceLimit = 1500.0f, float maxStepUpLimit = StepHeight, float maxDropDownLimit = 100.0f )
|
|
{
|
|
nearbyAreaVector->RemoveAll();
|
|
|
|
if ( startArea )
|
|
{
|
|
CNavArea::MakeNewMarker();
|
|
CNavArea::ClearSearchLists();
|
|
|
|
startArea->AddToOpenList();
|
|
startArea->SetTotalCost( 0.0f );
|
|
startArea->SetCostSoFar( 0.0f );
|
|
startArea->SetParent( NULL );
|
|
startArea->Mark();
|
|
|
|
CUtlVector< CNavArea * > adjVector;
|
|
|
|
while( !CNavArea::IsOpenListEmpty() )
|
|
{
|
|
// get next area to check
|
|
CNavArea *area = CNavArea::PopOpenList();
|
|
|
|
if ( travelDistanceLimit > 0.0f && area->GetCostSoFar() > travelDistanceLimit )
|
|
continue;
|
|
|
|
if ( area->GetParent() )
|
|
{
|
|
float deltaZ = area->GetParent()->ComputeAdjacentConnectionHeightChange( area );
|
|
|
|
if ( deltaZ > maxStepUpLimit )
|
|
continue;
|
|
|
|
if ( deltaZ < -maxDropDownLimit )
|
|
continue;
|
|
}
|
|
|
|
nearbyAreaVector->AddToTail( area );
|
|
|
|
// mark here to ensure all marked areas are also valid areas that are in the collection
|
|
area->Mark();
|
|
|
|
// search adjacent outgoing connections
|
|
for( int dir=0; dir<NUM_DIRECTIONS; ++dir )
|
|
{
|
|
int count = area->GetAdjacentCount( (NavDirType)dir );
|
|
for( int i=0; i<count; ++i )
|
|
{
|
|
CNavArea *adjArea = area->GetAdjacentArea( (NavDirType)dir, i );
|
|
|
|
if ( adjArea->IsBlocked( TEAM_ANY ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( !adjArea->IsMarked() )
|
|
{
|
|
adjArea->SetTotalCost( 0.0f );
|
|
adjArea->SetParent( area );
|
|
|
|
// compute approximate travel distance from start area of search
|
|
float distAlong = area->GetCostSoFar();
|
|
distAlong += ( adjArea->GetCenter() - area->GetCenter() ).Length();
|
|
adjArea->SetCostSoFar( distAlong );
|
|
adjArea->AddToOpenList();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Functor that returns lowest cost for farthest away areas
|
|
* For use with FindMinimumCostArea()
|
|
*/
|
|
class FarAwayFunctor
|
|
{
|
|
public:
|
|
float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder )
|
|
{
|
|
if (area == fromArea)
|
|
return 9999999.9f;
|
|
|
|
return 1.0f/(fromArea->GetCenter() - area->GetCenter()).Length();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Functor that returns lowest cost for areas farthest from given position
|
|
* For use with FindMinimumCostArea()
|
|
*/
|
|
class FarAwayFromPositionFunctor
|
|
{
|
|
public:
|
|
FarAwayFromPositionFunctor( const Vector &pos ) : m_pos( pos )
|
|
{
|
|
}
|
|
|
|
float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder )
|
|
{
|
|
return 1.0f/(m_pos - area->GetCenter()).Length();
|
|
}
|
|
|
|
private:
|
|
const Vector &m_pos;
|
|
};
|
|
|
|
|
|
/**
|
|
* Pick a low-cost area of "decent" size
|
|
*/
|
|
template< typename CostFunctor >
|
|
CNavArea *FindMinimumCostArea( CNavArea *startArea, CostFunctor &costFunc )
|
|
{
|
|
const float minSize = 150.0f;
|
|
|
|
// collect N low-cost areas of a decent size
|
|
enum { NUM_CHEAP_AREAS = 32 };
|
|
struct
|
|
{
|
|
CNavArea *area;
|
|
float cost;
|
|
}
|
|
cheapAreaSet[ NUM_CHEAP_AREAS ] = {};
|
|
int cheapAreaSetCount = 0;
|
|
|
|
FOR_EACH_VEC( TheNavAreas, iter )
|
|
{
|
|
CNavArea *area = TheNavAreas[iter];
|
|
|
|
// skip the small areas
|
|
if ( area->GetSizeX() < minSize || area->GetSizeY() < minSize)
|
|
continue;
|
|
|
|
// compute cost of this area
|
|
|
|
// HPE_FIX[pfreese]: changed this to only pass three parameters, in accord with the two functors above
|
|
float cost = costFunc( area, startArea, NULL );
|
|
|
|
if (cheapAreaSetCount < NUM_CHEAP_AREAS)
|
|
{
|
|
cheapAreaSet[ cheapAreaSetCount ].area = area;
|
|
cheapAreaSet[ cheapAreaSetCount++ ].cost = cost;
|
|
}
|
|
else
|
|
{
|
|
// replace most expensive cost if this is cheaper
|
|
int expensive = 0;
|
|
for( int i=1; i<NUM_CHEAP_AREAS; ++i )
|
|
if (cheapAreaSet[i].cost > cheapAreaSet[expensive].cost)
|
|
expensive = i;
|
|
|
|
if (cheapAreaSet[expensive].cost > cost)
|
|
{
|
|
cheapAreaSet[expensive].area = area;
|
|
cheapAreaSet[expensive].cost = cost;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cheapAreaSetCount)
|
|
{
|
|
// pick one of the areas at random
|
|
return cheapAreaSet[ RandomInt( 0, cheapAreaSetCount-1 ) ].area;
|
|
}
|
|
else
|
|
{
|
|
// degenerate case - no decent sized areas - pick a random area
|
|
int numAreas = TheNavAreas.Count();
|
|
int which = RandomInt( 0, numAreas-1 );
|
|
|
|
FOR_EACH_VEC( TheNavAreas, iter )
|
|
{
|
|
if (which-- == 0)
|
|
return TheNavAreas[iter];
|
|
}
|
|
|
|
}
|
|
return cheapAreaSet[ RandomInt( 0, cheapAreaSetCount-1 ) ].area;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------
|
|
//
|
|
// Given a vector of CNavAreas (or derived types), 'inVector', populate 'outVector' with a randomly shuffled set
|
|
// of 'maxCount' areas that are at least 'minSeparation' travel distance apart from each other.
|
|
//
|
|
template< typename T >
|
|
void SelectSeparatedShuffleSet( int maxCount, float minSeparation, const CUtlVector< T * > &inVector, CUtlVector< T * > *outVector )
|
|
{
|
|
if ( !outVector )
|
|
return;
|
|
|
|
outVector->RemoveAll();
|
|
|
|
CUtlVector< T * > shuffledVector;
|
|
|
|
int i, j;
|
|
|
|
for( i=0; i<inVector.Count(); ++i )
|
|
{
|
|
shuffledVector.AddToTail( inVector[i] );
|
|
}
|
|
|
|
// randomly shuffle the order
|
|
int n = shuffledVector.Count();
|
|
while( n > 1 )
|
|
{
|
|
int k = RandomInt( 0, n-1 );
|
|
n--;
|
|
|
|
T *tmp = shuffledVector[n];
|
|
shuffledVector[n] = shuffledVector[k];
|
|
shuffledVector[k] = tmp;
|
|
}
|
|
|
|
// enforce minSeparation between shuffled areas
|
|
for( i=0; i<shuffledVector.Count(); ++i )
|
|
{
|
|
T *area = shuffledVector[i];
|
|
|
|
CUtlVector< CNavArea * > nearVector;
|
|
CollectSurroundingAreas( &nearVector, area, minSeparation, 2.0f * StepHeight, 2.0f * StepHeight );
|
|
|
|
for( j=0; j<i; ++j )
|
|
{
|
|
if ( nearVector.HasElement( (CNavArea *)shuffledVector[j] ) )
|
|
{
|
|
// this area is too near an area earlier in the vector
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( j == i )
|
|
{
|
|
// separated from all prior areas
|
|
outVector->AddToTail( area );
|
|
|
|
if ( outVector->Count() >= maxCount )
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#endif // _NAV_PATHFIND_H_
|