diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/nav_pathfind.h | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/server/nav_pathfind.h')
| -rw-r--r-- | game/server/nav_pathfind.h | 998 |
1 files changed, 998 insertions, 0 deletions
diff --git a/game/server/nav_pathfind.h b/game/server/nav_pathfind.h new file mode 100644 index 0000000..e9c2d7e --- /dev/null +++ b/game/server/nav_pathfind.h @@ -0,0 +1,998 @@ +//========= 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_ |