Team Fortress 2 Source Code as on 22/4/2020
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

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. // nav_pathfind.h
  9. // Path-finding mechanisms using the Navigation Mesh
  10. // Author: Michael S. Booth ([email protected]), January 2003
  11. #ifndef _NAV_PATHFIND_H_
  12. #define _NAV_PATHFIND_H_
  13. #include "tier0/vprof.h"
  14. #include "mathlib/ssemath.h"
  15. #include "nav_area.h"
  16. #ifdef STAGING_ONLY
  17. extern int g_DebugPathfindCounter;
  18. #endif
  19. //-------------------------------------------------------------------------------------------------------------------
  20. /**
  21. * Used when building a path to determine the kind of path to build
  22. */
  23. enum RouteType
  24. {
  25. DEFAULT_ROUTE,
  26. FASTEST_ROUTE,
  27. SAFEST_ROUTE,
  28. RETREAT_ROUTE,
  29. };
  30. //--------------------------------------------------------------------------------------------------------------
  31. /**
  32. * Functor used with NavAreaBuildPath()
  33. */
  34. class ShortestPathCost
  35. {
  36. public:
  37. float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length )
  38. {
  39. if ( fromArea == NULL )
  40. {
  41. // first area in path, no cost
  42. return 0.0f;
  43. }
  44. else
  45. {
  46. // compute distance traveled along path so far
  47. float dist;
  48. if ( ladder )
  49. {
  50. dist = ladder->m_length;
  51. }
  52. else if ( length > 0.0 )
  53. {
  54. dist = length;
  55. }
  56. else
  57. {
  58. dist = ( area->GetCenter() - fromArea->GetCenter() ).Length();
  59. }
  60. float cost = dist + fromArea->GetCostSoFar();
  61. // if this is a "crouch" area, add penalty
  62. if ( area->GetAttributes() & NAV_MESH_CROUCH )
  63. {
  64. const float crouchPenalty = 20.0f; // 10
  65. cost += crouchPenalty * dist;
  66. }
  67. // if this is a "jump" area, add penalty
  68. if ( area->GetAttributes() & NAV_MESH_JUMP )
  69. {
  70. const float jumpPenalty = 5.0f;
  71. cost += jumpPenalty * dist;
  72. }
  73. return cost;
  74. }
  75. }
  76. };
  77. //--------------------------------------------------------------------------------------------------------------
  78. /**
  79. * Find path from startArea to goalArea via an A* search, using supplied cost heuristic.
  80. * If cost functor returns -1 for an area, that area is considered a dead end.
  81. * This doesn't actually build a path, but the path is defined by following parent
  82. * pointers back from goalArea to startArea.
  83. * If 'closestArea' is non-NULL, the closest area to the goal is returned (useful if the path fails).
  84. * If 'goalArea' is NULL, will compute a path as close as possible to 'goalPos'.
  85. * If 'goalPos' is NULL, will use the center of 'goalArea' as the goal position.
  86. * If 'maxPathLength' is nonzero, path building will stop when this length is reached.
  87. * Returns true if a path exists.
  88. */
  89. #define IGNORE_NAV_BLOCKERS true
  90. template< typename CostFunctor >
  91. 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 )
  92. {
  93. VPROF_BUDGET( "NavAreaBuildPath", "NextBotSpiky" );
  94. if ( closestArea )
  95. {
  96. *closestArea = startArea;
  97. }
  98. #ifdef STAGING_ONLY
  99. bool isDebug = ( g_DebugPathfindCounter-- > 0 );
  100. #endif
  101. if (startArea == NULL)
  102. return false;
  103. startArea->SetParent( NULL );
  104. if (goalArea != NULL && goalArea->IsBlocked( teamID, ignoreNavBlockers ))
  105. goalArea = NULL;
  106. if (goalArea == NULL && goalPos == NULL)
  107. return false;
  108. // if we are already in the goal area, build trivial path
  109. if (startArea == goalArea)
  110. {
  111. return true;
  112. }
  113. // determine actual goal position
  114. Vector actualGoalPos = (goalPos) ? *goalPos : goalArea->GetCenter();
  115. // start search
  116. CNavArea::ClearSearchLists();
  117. // compute estimate of path length
  118. /// @todo Cost might work as "manhattan distance"
  119. startArea->SetTotalCost( (startArea->GetCenter() - actualGoalPos).Length() );
  120. float initCost = costFunc( startArea, NULL, NULL, NULL, -1.0f );
  121. if (initCost < 0.0f)
  122. return false;
  123. startArea->SetCostSoFar( initCost );
  124. startArea->SetPathLengthSoFar( 0.0 );
  125. startArea->AddToOpenList();
  126. // keep track of the area we visit that is closest to the goal
  127. float closestAreaDist = startArea->GetTotalCost();
  128. // do A* search
  129. while( !CNavArea::IsOpenListEmpty() )
  130. {
  131. // get next area to check
  132. CNavArea *area = CNavArea::PopOpenList();
  133. #ifdef STAGING_ONLY
  134. if ( isDebug )
  135. {
  136. area->DrawFilled( 0, 255, 0, 128, 30.0f );
  137. }
  138. #endif
  139. // don't consider blocked areas
  140. if ( area->IsBlocked( teamID, ignoreNavBlockers ) )
  141. continue;
  142. // check if we have found the goal area or position
  143. if (area == goalArea || (goalArea == NULL && goalPos && area->Contains( *goalPos )))
  144. {
  145. if (closestArea)
  146. {
  147. *closestArea = area;
  148. }
  149. return true;
  150. }
  151. // search adjacent areas
  152. enum SearchType
  153. {
  154. SEARCH_FLOOR, SEARCH_LADDERS, SEARCH_ELEVATORS
  155. };
  156. SearchType searchWhere = SEARCH_FLOOR;
  157. int searchIndex = 0;
  158. int dir = NORTH;
  159. const NavConnectVector *floorList = area->GetAdjacentAreas( NORTH );
  160. bool ladderUp = true;
  161. const NavLadderConnectVector *ladderList = NULL;
  162. enum { AHEAD = 0, LEFT, RIGHT, BEHIND, NUM_TOP_DIRECTIONS };
  163. int ladderTopDir = AHEAD;
  164. bool bHaveMaxPathLength = ( maxPathLength > 0.0f );
  165. float length = -1;
  166. while( true )
  167. {
  168. CNavArea *newArea = NULL;
  169. NavTraverseType how;
  170. const CNavLadder *ladder = NULL;
  171. const CFuncElevator *elevator = NULL;
  172. //
  173. // Get next adjacent area - either on floor or via ladder
  174. //
  175. if ( searchWhere == SEARCH_FLOOR )
  176. {
  177. // if exhausted adjacent connections in current direction, begin checking next direction
  178. if ( searchIndex >= floorList->Count() )
  179. {
  180. ++dir;
  181. if ( dir == NUM_DIRECTIONS )
  182. {
  183. // checked all directions on floor - check ladders next
  184. searchWhere = SEARCH_LADDERS;
  185. ladderList = area->GetLadders( CNavLadder::LADDER_UP );
  186. searchIndex = 0;
  187. ladderTopDir = AHEAD;
  188. }
  189. else
  190. {
  191. // start next direction
  192. floorList = area->GetAdjacentAreas( (NavDirType)dir );
  193. searchIndex = 0;
  194. }
  195. continue;
  196. }
  197. const NavConnect &floorConnect = floorList->Element( searchIndex );
  198. newArea = floorConnect.area;
  199. length = floorConnect.length;
  200. how = (NavTraverseType)dir;
  201. ++searchIndex;
  202. if ( IsX360() && searchIndex < floorList->Count() )
  203. {
  204. PREFETCH360( floorList->Element( searchIndex ).area, 0 );
  205. }
  206. }
  207. else if ( searchWhere == SEARCH_LADDERS )
  208. {
  209. if ( searchIndex >= ladderList->Count() )
  210. {
  211. if ( !ladderUp )
  212. {
  213. // checked both ladder directions - check elevators next
  214. searchWhere = SEARCH_ELEVATORS;
  215. searchIndex = 0;
  216. ladder = NULL;
  217. }
  218. else
  219. {
  220. // check down ladders
  221. ladderUp = false;
  222. ladderList = area->GetLadders( CNavLadder::LADDER_DOWN );
  223. searchIndex = 0;
  224. }
  225. continue;
  226. }
  227. if ( ladderUp )
  228. {
  229. ladder = ladderList->Element( searchIndex ).ladder;
  230. // do not use BEHIND connection, as its very hard to get to when going up a ladder
  231. if ( ladderTopDir == AHEAD )
  232. {
  233. newArea = ladder->m_topForwardArea;
  234. }
  235. else if ( ladderTopDir == LEFT )
  236. {
  237. newArea = ladder->m_topLeftArea;
  238. }
  239. else if ( ladderTopDir == RIGHT )
  240. {
  241. newArea = ladder->m_topRightArea;
  242. }
  243. else
  244. {
  245. ++searchIndex;
  246. ladderTopDir = AHEAD;
  247. continue;
  248. }
  249. how = GO_LADDER_UP;
  250. ++ladderTopDir;
  251. }
  252. else
  253. {
  254. newArea = ladderList->Element( searchIndex ).ladder->m_bottomArea;
  255. how = GO_LADDER_DOWN;
  256. ladder = ladderList->Element(searchIndex).ladder;
  257. ++searchIndex;
  258. }
  259. if ( newArea == NULL )
  260. continue;
  261. length = -1.0f;
  262. }
  263. else // if ( searchWhere == SEARCH_ELEVATORS )
  264. {
  265. const NavConnectVector &elevatorAreas = area->GetElevatorAreas();
  266. elevator = area->GetElevator();
  267. if ( elevator == NULL || searchIndex >= elevatorAreas.Count() )
  268. {
  269. // done searching connected areas
  270. elevator = NULL;
  271. break;
  272. }
  273. newArea = elevatorAreas[ searchIndex++ ].area;
  274. if ( newArea->GetCenter().z > area->GetCenter().z )
  275. {
  276. how = GO_ELEVATOR_UP;
  277. }
  278. else
  279. {
  280. how = GO_ELEVATOR_DOWN;
  281. }
  282. length = -1.0f;
  283. }
  284. // don't backtrack
  285. Assert( newArea );
  286. if ( newArea == area->GetParent() )
  287. continue;
  288. if ( newArea == area ) // self neighbor?
  289. continue;
  290. // don't consider blocked areas
  291. if ( newArea->IsBlocked( teamID, ignoreNavBlockers ) )
  292. continue;
  293. float newCostSoFar = costFunc( newArea, area, ladder, elevator, length );
  294. // NaNs really mess this function up causing tough to track down hangs. If
  295. // we get inf back, clamp it down to a really high number.
  296. DebuggerBreakOnNaN_StagingOnly( newCostSoFar );
  297. if ( IS_NAN( newCostSoFar ) )
  298. newCostSoFar = 1e30f;
  299. // check if cost functor says this area is a dead-end
  300. if ( newCostSoFar < 0.0f )
  301. continue;
  302. // Safety check against a bogus functor. The cost of the path
  303. // A...B, C should always be at least as big as the path A...B.
  304. Assert( newCostSoFar >= area->GetCostSoFar() );
  305. // And now that we've asserted, let's be a bit more defensive.
  306. // Make sure that any jump to a new area incurs some pathfinsing
  307. // cost, to avoid us spinning our wheels over insignificant cost
  308. // benefit, floating point precision bug, or busted cost functor.
  309. float minNewCostSoFar = area->GetCostSoFar() * 1.00001f + 0.00001f;
  310. newCostSoFar = Max( newCostSoFar, minNewCostSoFar );
  311. // stop if path length limit reached
  312. if ( bHaveMaxPathLength )
  313. {
  314. // keep track of path length so far
  315. float deltaLength = ( newArea->GetCenter() - area->GetCenter() ).Length();
  316. float newLengthSoFar = area->GetPathLengthSoFar() + deltaLength;
  317. if ( newLengthSoFar > maxPathLength )
  318. continue;
  319. newArea->SetPathLengthSoFar( newLengthSoFar );
  320. }
  321. if ( ( newArea->IsOpen() || newArea->IsClosed() ) && newArea->GetCostSoFar() <= newCostSoFar )
  322. {
  323. // this is a worse path - skip it
  324. continue;
  325. }
  326. else
  327. {
  328. // compute estimate of distance left to go
  329. float distSq = ( newArea->GetCenter() - actualGoalPos ).LengthSqr();
  330. float newCostRemaining = ( distSq > 0.0 ) ? FastSqrt( distSq ) : 0.0 ;
  331. // track closest area to goal in case path fails
  332. if ( closestArea && newCostRemaining < closestAreaDist )
  333. {
  334. *closestArea = newArea;
  335. closestAreaDist = newCostRemaining;
  336. }
  337. newArea->SetCostSoFar( newCostSoFar );
  338. newArea->SetTotalCost( newCostSoFar + newCostRemaining );
  339. if ( newArea->IsClosed() )
  340. {
  341. newArea->RemoveFromClosedList();
  342. }
  343. if ( newArea->IsOpen() )
  344. {
  345. // area already on open list, update the list order to keep costs sorted
  346. newArea->UpdateOnOpenList();
  347. }
  348. else
  349. {
  350. newArea->AddToOpenList();
  351. }
  352. newArea->SetParent( area, how );
  353. }
  354. }
  355. // we have searched this area
  356. area->AddToClosedList();
  357. }
  358. return false;
  359. }
  360. //--------------------------------------------------------------------------------------------------------------
  361. /**
  362. * Compute distance between two areas. Return -1 if can't reach 'endArea' from 'startArea'.
  363. */
  364. template< typename CostFunctor >
  365. float NavAreaTravelDistance( CNavArea *startArea, CNavArea *endArea, CostFunctor &costFunc, float maxPathLength = 0.0f )
  366. {
  367. if (startArea == NULL)
  368. return -1.0f;
  369. if (endArea == NULL)
  370. return -1.0f;
  371. if (startArea == endArea)
  372. return 0.0f;
  373. // compute path between areas using given cost heuristic
  374. if (NavAreaBuildPath( startArea, endArea, NULL, costFunc, NULL, maxPathLength ) == false)
  375. return -1.0f;
  376. // compute distance along path
  377. float distance = 0.0f;
  378. for( CNavArea *area = endArea; area->GetParent(); area = area->GetParent() )
  379. {
  380. distance += (area->GetCenter() - area->GetParent()->GetCenter()).Length();
  381. }
  382. return distance;
  383. }
  384. //--------------------------------------------------------------------------------------------------------------
  385. /**
  386. * Do a breadth-first search, invoking functor on each area.
  387. * If functor returns 'true', continue searching from this area.
  388. * If functor returns 'false', the area's adjacent areas are not explored (dead end).
  389. * If 'maxRange' is 0 or less, no range check is done (all areas will be examined).
  390. *
  391. * NOTE: Returns all areas that overlap range, even partially
  392. *
  393. * @todo Use ladder connections
  394. */
  395. // helper function
  396. inline void AddAreaToOpenList( CNavArea *area, CNavArea *parent, const Vector &startPos, float maxRange )
  397. {
  398. if (area == NULL)
  399. return;
  400. if (!area->IsMarked())
  401. {
  402. area->Mark();
  403. area->SetTotalCost( 0.0f );
  404. area->SetParent( parent );
  405. if (maxRange > 0.0f)
  406. {
  407. // make sure this area overlaps range
  408. Vector closePos;
  409. area->GetClosestPointOnArea( startPos, &closePos );
  410. if ((closePos - startPos).AsVector2D().IsLengthLessThan( maxRange ))
  411. {
  412. // compute approximate distance along path to limit travel range, too
  413. float distAlong = parent->GetCostSoFar();
  414. distAlong += (area->GetCenter() - parent->GetCenter()).Length();
  415. area->SetCostSoFar( distAlong );
  416. // allow for some fudge due to large size areas
  417. if (distAlong <= 1.5f * maxRange)
  418. area->AddToOpenList();
  419. }
  420. }
  421. else
  422. {
  423. // infinite range
  424. area->AddToOpenList();
  425. }
  426. }
  427. }
  428. /****************************************************************
  429. * DEPRECATED: Use filter-based SearchSurroundingAreas below
  430. ****************************************************************/
  431. #define INCLUDE_INCOMING_CONNECTIONS 0x1
  432. #define INCLUDE_BLOCKED_AREAS 0x2
  433. #define EXCLUDE_OUTGOING_CONNECTIONS 0x4
  434. #define EXCLUDE_ELEVATORS 0x8
  435. template < typename Functor >
  436. void SearchSurroundingAreas( CNavArea *startArea, const Vector &startPos, Functor &func, float maxRange = -1.0f, unsigned int options = 0, int teamID = TEAM_ANY )
  437. {
  438. if (startArea == NULL)
  439. return;
  440. CNavArea::MakeNewMarker();
  441. CNavArea::ClearSearchLists();
  442. startArea->AddToOpenList();
  443. startArea->SetTotalCost( 0.0f );
  444. startArea->SetCostSoFar( 0.0f );
  445. startArea->SetParent( NULL );
  446. startArea->Mark();
  447. while( !CNavArea::IsOpenListEmpty() )
  448. {
  449. // get next area to check
  450. CNavArea *area = CNavArea::PopOpenList();
  451. // don't use blocked areas
  452. if ( area->IsBlocked( teamID ) && !(options & INCLUDE_BLOCKED_AREAS) )
  453. continue;
  454. // invoke functor on area
  455. if (func( area ))
  456. {
  457. // explore adjacent floor areas
  458. for( int dir=0; dir<NUM_DIRECTIONS; ++dir )
  459. {
  460. int count = area->GetAdjacentCount( (NavDirType)dir );
  461. for( int i=0; i<count; ++i )
  462. {
  463. CNavArea *adjArea = area->GetAdjacentArea( (NavDirType)dir, i );
  464. if ( options & EXCLUDE_OUTGOING_CONNECTIONS )
  465. {
  466. if ( !adjArea->IsConnected( area, NUM_DIRECTIONS ) )
  467. {
  468. continue; // skip this outgoing connection
  469. }
  470. }
  471. AddAreaToOpenList( adjArea, area, startPos, maxRange );
  472. }
  473. }
  474. // potentially include areas that connect TO this area via a one-way link
  475. if (options & INCLUDE_INCOMING_CONNECTIONS)
  476. {
  477. for( int dir=0; dir<NUM_DIRECTIONS; ++dir )
  478. {
  479. const NavConnectVector *list = area->GetIncomingConnections( (NavDirType)dir );
  480. FOR_EACH_VEC( (*list), it )
  481. {
  482. NavConnect connect = (*list)[ it ];
  483. AddAreaToOpenList( connect.area, area, startPos, maxRange );
  484. }
  485. }
  486. }
  487. // explore adjacent areas connected by ladders
  488. // check up ladders
  489. const NavLadderConnectVector *ladderList = area->GetLadders( CNavLadder::LADDER_UP );
  490. if (ladderList)
  491. {
  492. FOR_EACH_VEC( (*ladderList), it )
  493. {
  494. const CNavLadder *ladder = (*ladderList)[ it ].ladder;
  495. // do not use BEHIND connection, as its very hard to get to when going up a ladder
  496. AddAreaToOpenList( ladder->m_topForwardArea, area, startPos, maxRange );
  497. AddAreaToOpenList( ladder->m_topLeftArea, area, startPos, maxRange );
  498. AddAreaToOpenList( ladder->m_topRightArea, area, startPos, maxRange );
  499. }
  500. }
  501. // check down ladders
  502. ladderList = area->GetLadders( CNavLadder::LADDER_DOWN );
  503. if (ladderList)
  504. {
  505. FOR_EACH_VEC( (*ladderList), it )
  506. {
  507. const CNavLadder *ladder = (*ladderList)[ it ].ladder;
  508. AddAreaToOpenList( ladder->m_bottomArea, area, startPos, maxRange );
  509. }
  510. }
  511. if ( (options & EXCLUDE_ELEVATORS) == 0 )
  512. {
  513. const NavConnectVector &elevatorList = area->GetElevatorAreas();
  514. FOR_EACH_VEC( elevatorList, it )
  515. {
  516. CNavArea *elevatorArea = elevatorList[ it ].area;
  517. AddAreaToOpenList( elevatorArea, area, startPos, maxRange );
  518. }
  519. }
  520. }
  521. }
  522. }
  523. //--------------------------------------------------------------------------------------------------------------
  524. /**
  525. * Derive your own custom search functor from this interface method for use with SearchSurroundingAreas below.
  526. */
  527. class ISearchSurroundingAreasFunctor
  528. {
  529. public:
  530. virtual ~ISearchSurroundingAreasFunctor() { }
  531. /**
  532. * Perform user-defined action on area.
  533. * Return 'false' to end the search (ie: you found what you were looking for)
  534. */
  535. virtual bool operator() ( CNavArea *area, CNavArea *priorArea, float travelDistanceSoFar ) = 0;
  536. // return true if 'adjArea' should be included in the ongoing search
  537. virtual bool ShouldSearch( CNavArea *adjArea, CNavArea *currentArea, float travelDistanceSoFar )
  538. {
  539. return !adjArea->IsBlocked( TEAM_ANY );
  540. }
  541. /**
  542. * Collect adjacent areas to continue the search by calling 'IncludeInSearch' on each
  543. */
  544. virtual void IterateAdjacentAreas( CNavArea *area, CNavArea *priorArea, float travelDistanceSoFar )
  545. {
  546. // search adjacent outgoing connections
  547. for( int dir=0; dir<NUM_DIRECTIONS; ++dir )
  548. {
  549. int count = area->GetAdjacentCount( (NavDirType)dir );
  550. for( int i=0; i<count; ++i )
  551. {
  552. CNavArea *adjArea = area->GetAdjacentArea( (NavDirType)dir, i );
  553. if ( ShouldSearch( adjArea, area, travelDistanceSoFar ) )
  554. {
  555. IncludeInSearch( adjArea, area );
  556. }
  557. }
  558. }
  559. }
  560. // Invoked after the search has completed
  561. virtual void PostSearch( void ) { }
  562. // consider 'area' in upcoming search steps
  563. void IncludeInSearch( CNavArea *area, CNavArea *priorArea )
  564. {
  565. if ( area == NULL )
  566. return;
  567. if ( !area->IsMarked() )
  568. {
  569. area->Mark();
  570. area->SetTotalCost( 0.0f );
  571. area->SetParent( priorArea );
  572. // compute approximate travel distance from start area of search
  573. if ( priorArea )
  574. {
  575. float distAlong = priorArea->GetCostSoFar();
  576. distAlong += ( area->GetCenter() - priorArea->GetCenter() ).Length();
  577. area->SetCostSoFar( distAlong );
  578. }
  579. else
  580. {
  581. area->SetCostSoFar( 0.0f );
  582. }
  583. // adding an area to the open list also marks it
  584. area->AddToOpenList();
  585. }
  586. }
  587. };
  588. /**
  589. * Do a breadth-first search starting from 'startArea' and continuing outward based on
  590. * adjacent areas that pass the given filter
  591. */
  592. inline void SearchSurroundingAreas( CNavArea *startArea, ISearchSurroundingAreasFunctor &func, float travelDistanceLimit = -1.0f )
  593. {
  594. if ( startArea )
  595. {
  596. CNavArea::MakeNewMarker();
  597. CNavArea::ClearSearchLists();
  598. startArea->AddToOpenList();
  599. startArea->SetTotalCost( 0.0f );
  600. startArea->SetCostSoFar( 0.0f );
  601. startArea->SetParent( NULL );
  602. startArea->Mark();
  603. CUtlVector< CNavArea * > adjVector;
  604. while( !CNavArea::IsOpenListEmpty() )
  605. {
  606. // get next area to check
  607. CNavArea *area = CNavArea::PopOpenList();
  608. if ( travelDistanceLimit > 0.0f && area->GetCostSoFar() > travelDistanceLimit )
  609. continue;
  610. if ( func( area, area->GetParent(), area->GetCostSoFar() ) )
  611. {
  612. func.IterateAdjacentAreas( area, area->GetParent(), area->GetCostSoFar() );
  613. }
  614. else
  615. {
  616. // search aborted
  617. break;
  618. }
  619. }
  620. }
  621. func.PostSearch();
  622. }
  623. //--------------------------------------------------------------------------------------------------------------
  624. /**
  625. * Starting from 'startArea', collect adjacent areas via a breadth-first search continuing outward until
  626. * 'travelDistanceLimit' is reached.
  627. * Areas in the collection will be "marked", returning true for IsMarked().
  628. * Each area in the collection's GetCostSoFar() will be approximate travel distance from 'startArea'.
  629. */
  630. inline void CollectSurroundingAreas( CUtlVector< CNavArea * > *nearbyAreaVector, CNavArea *startArea, float travelDistanceLimit = 1500.0f, float maxStepUpLimit = StepHeight, float maxDropDownLimit = 100.0f )
  631. {
  632. nearbyAreaVector->RemoveAll();
  633. if ( startArea )
  634. {
  635. CNavArea::MakeNewMarker();
  636. CNavArea::ClearSearchLists();
  637. startArea->AddToOpenList();
  638. startArea->SetTotalCost( 0.0f );
  639. startArea->SetCostSoFar( 0.0f );
  640. startArea->SetParent( NULL );
  641. startArea->Mark();
  642. CUtlVector< CNavArea * > adjVector;
  643. while( !CNavArea::IsOpenListEmpty() )
  644. {
  645. // get next area to check
  646. CNavArea *area = CNavArea::PopOpenList();
  647. if ( travelDistanceLimit > 0.0f && area->GetCostSoFar() > travelDistanceLimit )
  648. continue;
  649. if ( area->GetParent() )
  650. {
  651. float deltaZ = area->GetParent()->ComputeAdjacentConnectionHeightChange( area );
  652. if ( deltaZ > maxStepUpLimit )
  653. continue;
  654. if ( deltaZ < -maxDropDownLimit )
  655. continue;
  656. }
  657. nearbyAreaVector->AddToTail( area );
  658. // mark here to ensure all marked areas are also valid areas that are in the collection
  659. area->Mark();
  660. // search adjacent outgoing connections
  661. for( int dir=0; dir<NUM_DIRECTIONS; ++dir )
  662. {
  663. int count = area->GetAdjacentCount( (NavDirType)dir );
  664. for( int i=0; i<count; ++i )
  665. {
  666. CNavArea *adjArea = area->GetAdjacentArea( (NavDirType)dir, i );
  667. if ( adjArea->IsBlocked( TEAM_ANY ) )
  668. {
  669. continue;
  670. }
  671. if ( !adjArea->IsMarked() )
  672. {
  673. adjArea->SetTotalCost( 0.0f );
  674. adjArea->SetParent( area );
  675. // compute approximate travel distance from start area of search
  676. float distAlong = area->GetCostSoFar();
  677. distAlong += ( adjArea->GetCenter() - area->GetCenter() ).Length();
  678. adjArea->SetCostSoFar( distAlong );
  679. adjArea->AddToOpenList();
  680. }
  681. }
  682. }
  683. }
  684. }
  685. }
  686. //--------------------------------------------------------------------------------------------------------------
  687. /**
  688. * Functor that returns lowest cost for farthest away areas
  689. * For use with FindMinimumCostArea()
  690. */
  691. class FarAwayFunctor
  692. {
  693. public:
  694. float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder )
  695. {
  696. if (area == fromArea)
  697. return 9999999.9f;
  698. return 1.0f/(fromArea->GetCenter() - area->GetCenter()).Length();
  699. }
  700. };
  701. /**
  702. * Functor that returns lowest cost for areas farthest from given position
  703. * For use with FindMinimumCostArea()
  704. */
  705. class FarAwayFromPositionFunctor
  706. {
  707. public:
  708. FarAwayFromPositionFunctor( const Vector &pos ) : m_pos( pos )
  709. {
  710. }
  711. float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder )
  712. {
  713. return 1.0f/(m_pos - area->GetCenter()).Length();
  714. }
  715. private:
  716. const Vector &m_pos;
  717. };
  718. /**
  719. * Pick a low-cost area of "decent" size
  720. */
  721. template< typename CostFunctor >
  722. CNavArea *FindMinimumCostArea( CNavArea *startArea, CostFunctor &costFunc )
  723. {
  724. const float minSize = 150.0f;
  725. // collect N low-cost areas of a decent size
  726. enum { NUM_CHEAP_AREAS = 32 };
  727. struct
  728. {
  729. CNavArea *area;
  730. float cost;
  731. }
  732. cheapAreaSet[ NUM_CHEAP_AREAS ] = {};
  733. int cheapAreaSetCount = 0;
  734. FOR_EACH_VEC( TheNavAreas, iter )
  735. {
  736. CNavArea *area = TheNavAreas[iter];
  737. // skip the small areas
  738. if ( area->GetSizeX() < minSize || area->GetSizeY() < minSize)
  739. continue;
  740. // compute cost of this area
  741. // HPE_FIX[pfreese]: changed this to only pass three parameters, in accord with the two functors above
  742. float cost = costFunc( area, startArea, NULL );
  743. if (cheapAreaSetCount < NUM_CHEAP_AREAS)
  744. {
  745. cheapAreaSet[ cheapAreaSetCount ].area = area;
  746. cheapAreaSet[ cheapAreaSetCount++ ].cost = cost;
  747. }
  748. else
  749. {
  750. // replace most expensive cost if this is cheaper
  751. int expensive = 0;
  752. for( int i=1; i<NUM_CHEAP_AREAS; ++i )
  753. if (cheapAreaSet[i].cost > cheapAreaSet[expensive].cost)
  754. expensive = i;
  755. if (cheapAreaSet[expensive].cost > cost)
  756. {
  757. cheapAreaSet[expensive].area = area;
  758. cheapAreaSet[expensive].cost = cost;
  759. }
  760. }
  761. }
  762. if (cheapAreaSetCount)
  763. {
  764. // pick one of the areas at random
  765. return cheapAreaSet[ RandomInt( 0, cheapAreaSetCount-1 ) ].area;
  766. }
  767. else
  768. {
  769. // degenerate case - no decent sized areas - pick a random area
  770. int numAreas = TheNavAreas.Count();
  771. int which = RandomInt( 0, numAreas-1 );
  772. FOR_EACH_VEC( TheNavAreas, iter )
  773. {
  774. if (which-- == 0)
  775. return TheNavAreas[iter];
  776. }
  777. }
  778. return cheapAreaSet[ RandomInt( 0, cheapAreaSetCount-1 ) ].area;
  779. }
  780. //--------------------------------------------------------------------------------------------------------
  781. //
  782. // Given a vector of CNavAreas (or derived types), 'inVector', populate 'outVector' with a randomly shuffled set
  783. // of 'maxCount' areas that are at least 'minSeparation' travel distance apart from each other.
  784. //
  785. template< typename T >
  786. void SelectSeparatedShuffleSet( int maxCount, float minSeparation, const CUtlVector< T * > &inVector, CUtlVector< T * > *outVector )
  787. {
  788. if ( !outVector )
  789. return;
  790. outVector->RemoveAll();
  791. CUtlVector< T * > shuffledVector;
  792. int i, j;
  793. for( i=0; i<inVector.Count(); ++i )
  794. {
  795. shuffledVector.AddToTail( inVector[i] );
  796. }
  797. // randomly shuffle the order
  798. int n = shuffledVector.Count();
  799. while( n > 1 )
  800. {
  801. int k = RandomInt( 0, n-1 );
  802. n--;
  803. T *tmp = shuffledVector[n];
  804. shuffledVector[n] = shuffledVector[k];
  805. shuffledVector[k] = tmp;
  806. }
  807. // enforce minSeparation between shuffled areas
  808. for( i=0; i<shuffledVector.Count(); ++i )
  809. {
  810. T *area = shuffledVector[i];
  811. CUtlVector< CNavArea * > nearVector;
  812. CollectSurroundingAreas( &nearVector, area, minSeparation, 2.0f * StepHeight, 2.0f * StepHeight );
  813. for( j=0; j<i; ++j )
  814. {
  815. if ( nearVector.HasElement( (CNavArea *)shuffledVector[j] ) )
  816. {
  817. // this area is too near an area earlier in the vector
  818. break;
  819. }
  820. }
  821. if ( j == i )
  822. {
  823. // separated from all prior areas
  824. outVector->AddToTail( area );
  825. if ( outVector->Count() >= maxCount )
  826. return;
  827. }
  828. }
  829. }
  830. #endif // _NAV_PATHFIND_H_