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.

5880 lines
150 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. // nav_area.cpp
  9. // AI Navigation areas
  10. // Author: Michael S. Booth ([email protected]), January 2003
  11. #include "cbase.h"
  12. #include "tier0/vprof.h"
  13. #include "tier0/tslist.h"
  14. #include "tier1/utlhash.h"
  15. #include "vstdlib/jobthread.h"
  16. #include "nav_mesh.h"
  17. #include "nav_node.h"
  18. #include "nav_pathfind.h"
  19. #include "nav_colors.h"
  20. #include "fmtstr.h"
  21. #include "props_shared.h"
  22. #include "func_breakablesurf.h"
  23. #ifdef TERROR
  24. #include "func_elevator.h"
  25. #include "AmbientLight.h"
  26. #endif
  27. #include "Color.h"
  28. #include "collisionutils.h"
  29. #include "functorutils.h"
  30. #include "team.h"
  31. #include "nav_entities.h"
  32. // memdbgon must be the last include file in a .cpp file!!!
  33. #include "tier0/memdbgon.h"
  34. extern void HintMessageToAllPlayers( const char *message );
  35. unsigned int CNavArea::m_nextID = 1;
  36. NavAreaVector TheNavAreas;
  37. unsigned int CNavArea::m_masterMarker = 1;
  38. CNavArea *CNavArea::m_openList = NULL;
  39. CNavArea *CNavArea::m_openListTail = NULL;
  40. bool CNavArea::m_isReset = false;
  41. uint32 CNavArea::s_nCurrVisTestCounter = 0;
  42. ConVar nav_coplanar_slope_limit( "nav_coplanar_slope_limit", "0.99", FCVAR_CHEAT );
  43. ConVar nav_coplanar_slope_limit_displacement( "nav_coplanar_slope_limit_displacement", "0.7", FCVAR_CHEAT );
  44. ConVar nav_split_place_on_ground( "nav_split_place_on_ground", "0", FCVAR_CHEAT, "If true, nav areas will be placed flush with the ground when split." );
  45. ConVar nav_area_bgcolor( "nav_area_bgcolor", "0 0 0 30", FCVAR_CHEAT, "RGBA color to draw as the background color for nav areas while editing." );
  46. ConVar nav_corner_adjust_adjacent( "nav_corner_adjust_adjacent", "18", FCVAR_CHEAT, "radius used to raise/lower corners in nearby areas when raising/lowering corners." );
  47. ConVar nav_show_light_intensity( "nav_show_light_intensity", "0", FCVAR_CHEAT );
  48. ConVar nav_debug_blocked( "nav_debug_blocked", "0", FCVAR_CHEAT );
  49. ConVar nav_show_contiguous( "nav_show_continguous", "0", FCVAR_CHEAT, "Highlight non-contiguous connections" );
  50. const float DEF_NAV_VIEW_DISTANCE = 1500.0;
  51. ConVar nav_max_view_distance( "nav_max_view_distance", "6000", FCVAR_CHEAT, "Maximum range for precomputed nav mesh visibility (0 = default 1500 units)" );
  52. ConVar nav_update_visibility_on_edit( "nav_update_visibility_on_edit", "0", FCVAR_CHEAT, "If nonzero editing the mesh will incrementally recompue visibility" );
  53. ConVar nav_potentially_visible_dot_tolerance( "nav_potentially_visible_dot_tolerance", "0.98", FCVAR_CHEAT );
  54. ConVar nav_show_potentially_visible( "nav_show_potentially_visible", "0", FCVAR_CHEAT, "Show areas that are potentially visible from the current nav area" );
  55. Color s_selectedSetColor( 255, 255, 200, 96 );
  56. Color s_selectedSetBorderColor( 100, 100, 0, 255 );
  57. Color s_dragSelectionSetBorderColor( 50, 50, 50, 255 );
  58. static void SelectedSetColorChaged( IConVar *var, const char *pOldValue, float flOldValue )
  59. {
  60. ConVarRef colorVar( var->GetName() );
  61. Color *color = &s_selectedSetColor;
  62. if ( FStrEq( var->GetName(), "nav_selected_set_border_color" ) )
  63. {
  64. color = &s_selectedSetBorderColor;
  65. }
  66. // Xbox compiler needs these to be in this explicit form
  67. // likely due to sscanf expecting word aligned boundaries
  68. int r = color->r();
  69. int g = color->r();
  70. int b = color->b();
  71. int a = color->a();
  72. int numFound = sscanf( colorVar.GetString(), "%d %d %d %d", &r, &g, &b, &a );
  73. (*color)[0] = r;
  74. (*color)[1] = g;
  75. (*color)[2] = b;
  76. if ( numFound > 3 )
  77. {
  78. (*color)[3] = a;
  79. }
  80. }
  81. ConVar nav_selected_set_color( "nav_selected_set_color", "255 255 200 96", FCVAR_CHEAT, "Color used to draw the selected set background while editing.", false, 0.0f, false, 0.0f, SelectedSetColorChaged );
  82. ConVar nav_selected_set_border_color( "nav_selected_set_border_color", "100 100 0 255", FCVAR_CHEAT, "Color used to draw the selected set borders while editing.", false, 0.0f, false, 0.0f, SelectedSetColorChaged );
  83. //--------------------------------------------------------------------------------------------------------------
  84. CMemoryStack CNavVectorNoEditAllocator::m_memory;
  85. void *CNavVectorNoEditAllocator::m_pCurrent;
  86. int CNavVectorNoEditAllocator::m_nBytesCurrent;
  87. CNavVectorNoEditAllocator::CNavVectorNoEditAllocator()
  88. {
  89. m_pCurrent = NULL;
  90. m_nBytesCurrent = 0;
  91. }
  92. void CNavVectorNoEditAllocator::Reset()
  93. {
  94. m_memory.FreeAll();
  95. m_pCurrent = NULL;
  96. m_nBytesCurrent = 0;
  97. }
  98. void *CNavVectorNoEditAllocator::Alloc( size_t nSize )
  99. {
  100. if ( !m_memory.GetBase() )
  101. {
  102. m_memory.Init( 1024*1024, 0, 0, 4 );
  103. }
  104. m_pCurrent = (int *)m_memory.Alloc( nSize );
  105. m_nBytesCurrent = nSize;
  106. return m_pCurrent;
  107. }
  108. void *CNavVectorNoEditAllocator::Realloc( void *pMem, size_t nSize )
  109. {
  110. if ( pMem != m_pCurrent )
  111. {
  112. Assert( 0 );
  113. Error( "Nav mesh cannot be mutated after load\n" );
  114. }
  115. if ( nSize > (size_t)m_nBytesCurrent )
  116. {
  117. m_memory.Alloc( nSize - m_nBytesCurrent );
  118. m_nBytesCurrent = nSize;
  119. }
  120. return m_pCurrent;
  121. }
  122. void CNavVectorNoEditAllocator::Free( void *pMem )
  123. {
  124. }
  125. size_t CNavVectorNoEditAllocator::GetSize( void *pMem )
  126. {
  127. if ( pMem != m_pCurrent )
  128. {
  129. Assert( 0 );
  130. Error( "Nav mesh cannot be mutated after load\n" );
  131. }
  132. return m_nBytesCurrent;
  133. }
  134. //--------------------------------------------------------------------------------------------------------------
  135. void CNavArea::CompressIDs( void )
  136. {
  137. m_nextID = 1;
  138. FOR_EACH_VEC( TheNavAreas, id )
  139. {
  140. CNavArea *area = TheNavAreas[id];
  141. area->m_id = m_nextID++;
  142. // remove and re-add the area from the nav mesh to update the hashed ID
  143. TheNavMesh->RemoveNavArea( area );
  144. TheNavMesh->AddNavArea( area );
  145. }
  146. }
  147. //--------------------------------------------------------------------------------------------------------------
  148. /**
  149. * Constructor used during normal runtime.
  150. */
  151. CNavArea::CNavArea( void )
  152. {
  153. m_marker = 0;
  154. m_nearNavSearchMarker = 0;
  155. m_damagingTickCount = 0;
  156. m_openMarker = 0;
  157. m_parent = NULL;
  158. m_parentHow = GO_NORTH;
  159. m_attributeFlags = 0;
  160. m_place = TheNavMesh->GetNavPlace();
  161. m_isUnderwater = false;
  162. m_avoidanceObstacleHeight = 0.0f;
  163. m_totalCost = 0.0f;
  164. m_costSoFar = 0.0f;
  165. m_pathLengthSoFar = 0.0f;
  166. ResetNodes();
  167. int i;
  168. for ( i=0; i<MAX_NAV_TEAMS; ++i )
  169. {
  170. m_isBlocked[i] = false;
  171. m_danger[i] = 0.0f;
  172. m_dangerTimestamp[i] = 0.0f;
  173. m_clearedTimestamp[i] = 0.0f;
  174. m_earliestOccupyTime[i] = 0.0f;
  175. m_playerCount[i] = 0;
  176. }
  177. // set an ID for splitting and other interactive editing - loads will overwrite this
  178. m_id = m_nextID++;
  179. m_debugid = 0;
  180. m_prevHash = NULL;
  181. m_nextHash = NULL;
  182. m_isBattlefront = false;
  183. for( i = 0; i<NUM_DIRECTIONS; ++i )
  184. {
  185. m_connect[i].RemoveAll();
  186. }
  187. for( i=0; i<CNavLadder::NUM_LADDER_DIRECTIONS; ++i )
  188. {
  189. m_ladder[i].RemoveAll();
  190. }
  191. for ( i=0; i<NUM_CORNERS; ++i )
  192. {
  193. m_lightIntensity[i] = 1.0f;
  194. }
  195. m_elevator = NULL;
  196. m_elevatorAreas.RemoveAll();
  197. m_invDxCorners = 0;
  198. m_invDyCorners = 0;
  199. m_inheritVisibilityFrom.area = NULL;
  200. m_isInheritedFrom = false;
  201. m_funcNavCostVector.RemoveAll();
  202. m_nVisTestCounter = (uint32)-1;
  203. }
  204. //--------------------------------------------------------------------------------------------------------------
  205. /**
  206. * Assumes Z is flat
  207. */
  208. void CNavArea::Build( const Vector &corner, const Vector &otherCorner )
  209. {
  210. if (corner.x < otherCorner.x)
  211. {
  212. m_nwCorner.x = corner.x;
  213. m_seCorner.x = otherCorner.x;
  214. }
  215. else
  216. {
  217. m_seCorner.x = corner.x;
  218. m_nwCorner.x = otherCorner.x;
  219. }
  220. if (corner.y < otherCorner.y)
  221. {
  222. m_nwCorner.y = corner.y;
  223. m_seCorner.y = otherCorner.y;
  224. }
  225. else
  226. {
  227. m_seCorner.y = corner.y;
  228. m_nwCorner.y = otherCorner.y;
  229. }
  230. m_nwCorner.z = corner.z;
  231. m_seCorner.z = corner.z;
  232. m_center.x = (m_nwCorner.x + m_seCorner.x)/2.0f;
  233. m_center.y = (m_nwCorner.y + m_seCorner.y)/2.0f;
  234. m_center.z = (m_nwCorner.z + m_seCorner.z)/2.0f;
  235. if ( ( m_seCorner.x - m_nwCorner.x ) > 0.0f && ( m_seCorner.y - m_nwCorner.y ) > 0.0f )
  236. {
  237. m_invDxCorners = 1.0f / ( m_seCorner.x - m_nwCorner.x );
  238. m_invDyCorners = 1.0f / ( m_seCorner.y - m_nwCorner.y );
  239. }
  240. else
  241. {
  242. m_invDxCorners = m_invDyCorners = 0;
  243. }
  244. m_neZ = corner.z;
  245. m_swZ = otherCorner.z;
  246. CalcDebugID();
  247. }
  248. //--------------------------------------------------------------------------------------------------------------
  249. /**
  250. * Build a nav area given the positions of its four corners.
  251. */
  252. void CNavArea::Build( const Vector &nwCorner, const Vector &neCorner, const Vector &seCorner, const Vector &swCorner )
  253. {
  254. m_nwCorner = nwCorner;
  255. m_seCorner = seCorner;
  256. m_center.x = (m_nwCorner.x + m_seCorner.x)/2.0f;
  257. m_center.y = (m_nwCorner.y + m_seCorner.y)/2.0f;
  258. m_center.z = (m_nwCorner.z + m_seCorner.z)/2.0f;
  259. m_neZ = neCorner.z;
  260. m_swZ = swCorner.z;
  261. if ( ( m_seCorner.x - m_nwCorner.x ) > 0.0f && ( m_seCorner.y - m_nwCorner.y ) > 0.0f )
  262. {
  263. m_invDxCorners = 1.0f / ( m_seCorner.x - m_nwCorner.x );
  264. m_invDyCorners = 1.0f / ( m_seCorner.y - m_nwCorner.y );
  265. }
  266. else
  267. {
  268. m_invDxCorners = m_invDyCorners = 0;
  269. }
  270. CalcDebugID();
  271. }
  272. //--------------------------------------------------------------------------------------------------------------
  273. /**
  274. * Used during generation phase to build nav areas from sampled nodes.
  275. */
  276. void CNavArea::Build( CNavNode *nwNode, CNavNode *neNode, CNavNode *seNode, CNavNode *swNode )
  277. {
  278. m_nwCorner = *nwNode->GetPosition();
  279. m_seCorner = *seNode->GetPosition();
  280. m_center.x = (m_nwCorner.x + m_seCorner.x)/2.0f;
  281. m_center.y = (m_nwCorner.y + m_seCorner.y)/2.0f;
  282. m_center.z = (m_nwCorner.z + m_seCorner.z)/2.0f;
  283. m_neZ = neNode->GetPosition()->z;
  284. m_swZ = swNode->GetPosition()->z;
  285. m_node[ NORTH_WEST ] = nwNode;
  286. m_node[ NORTH_EAST ] = neNode;
  287. m_node[ SOUTH_EAST ] = seNode;
  288. m_node[ SOUTH_WEST ] = swNode;
  289. if ( ( m_seCorner.x - m_nwCorner.x ) > 0.0f && ( m_seCorner.y - m_nwCorner.y ) > 0.0f )
  290. {
  291. m_invDxCorners = 1.0f / ( m_seCorner.x - m_nwCorner.x );
  292. m_invDyCorners = 1.0f / ( m_seCorner.y - m_nwCorner.y );
  293. }
  294. else
  295. {
  296. m_invDxCorners = m_invDyCorners = 0;
  297. }
  298. // mark internal nodes as part of this area
  299. AssignNodes( this );
  300. CalcDebugID();
  301. }
  302. //--------------------------------------------------------------------------------------------------------------
  303. // Return a computed extent (XY is in m_nwCorner and m_seCorner, Z is computed)
  304. void CNavArea::GetExtent( Extent *extent ) const
  305. {
  306. extent->lo = m_nwCorner;
  307. extent->hi = m_seCorner;
  308. extent->lo.z = MIN( extent->lo.z, m_nwCorner.z );
  309. extent->lo.z = MIN( extent->lo.z, m_seCorner.z );
  310. extent->lo.z = MIN( extent->lo.z, m_neZ );
  311. extent->lo.z = MIN( extent->lo.z, m_swZ );
  312. extent->hi.z = MAX( extent->hi.z, m_nwCorner.z );
  313. extent->hi.z = MAX( extent->hi.z, m_seCorner.z );
  314. extent->hi.z = MAX( extent->hi.z, m_neZ );
  315. extent->hi.z = MAX( extent->hi.z, m_swZ );
  316. }
  317. //--------------------------------------------------------------------------------------------------------------
  318. // returns the closest node along the given edge to the given point
  319. CNavNode *CNavArea::FindClosestNode( const Vector &pos, NavDirType dir ) const
  320. {
  321. if ( !HasNodes() )
  322. return NULL;
  323. CUtlVector< CNavNode * > nodes;
  324. GetNodes( dir, &nodes );
  325. CNavNode *bestNode = NULL;
  326. float bestDistanceSq = FLT_MAX;
  327. for ( int i=0; i<nodes.Count(); ++i )
  328. {
  329. float distSq = pos.DistToSqr( *nodes[i]->GetPosition() );
  330. if ( distSq < bestDistanceSq )
  331. {
  332. bestDistanceSq = distSq;
  333. bestNode = nodes[i];
  334. }
  335. }
  336. return bestNode;
  337. }
  338. //--------------------------------------------------------------------------------------------------------------
  339. // build a vector of nodes along the given direction
  340. void CNavArea::GetNodes( NavDirType dir, CUtlVector< CNavNode * > *nodes ) const
  341. {
  342. if ( !nodes )
  343. return;
  344. nodes->RemoveAll();
  345. NavCornerType startCorner;
  346. NavCornerType endCorner;
  347. NavDirType traversalDirection;
  348. switch ( dir )
  349. {
  350. case NORTH:
  351. startCorner = NORTH_WEST;
  352. endCorner = NORTH_EAST;
  353. traversalDirection = EAST;
  354. break;
  355. case SOUTH:
  356. startCorner = SOUTH_WEST;
  357. endCorner = SOUTH_EAST;
  358. traversalDirection = EAST;
  359. break;
  360. case EAST:
  361. startCorner = NORTH_EAST;
  362. endCorner = SOUTH_EAST;
  363. traversalDirection = SOUTH;
  364. break;
  365. case WEST:
  366. startCorner = NORTH_WEST;
  367. endCorner = SOUTH_WEST;
  368. traversalDirection = SOUTH;
  369. break;
  370. default:
  371. return;
  372. }
  373. CNavNode *node;
  374. for ( node = m_node[ startCorner ]; node && node != m_node[ endCorner ]; node = node->GetConnectedNode( traversalDirection ) )
  375. {
  376. nodes->AddToTail( node );
  377. }
  378. if ( node && node == m_node[ endCorner ] )
  379. {
  380. nodes->AddToTail( node );
  381. }
  382. }
  383. //--------------------------------------------------------------------------------------------------------------
  384. class ForgetArea
  385. {
  386. public:
  387. ForgetArea( CNavArea *area )
  388. {
  389. m_area = area;
  390. }
  391. bool operator() ( CBasePlayer *player )
  392. {
  393. player->OnNavAreaRemoved( m_area );
  394. return true;
  395. }
  396. bool operator() ( CBaseCombatCharacter *player )
  397. {
  398. player->OnNavAreaRemoved( m_area );
  399. return true;
  400. }
  401. CNavArea *m_area;
  402. };
  403. //--------------------------------------------------------------------------------------------------------------
  404. class AreaDestroyNotification
  405. {
  406. CNavArea *m_area;
  407. public:
  408. AreaDestroyNotification( CNavArea *area )
  409. {
  410. m_area = area;
  411. }
  412. bool operator()( CNavLadder *ladder )
  413. {
  414. ladder->OnDestroyNotify( m_area );
  415. return true;
  416. }
  417. bool operator()( CNavArea *area )
  418. {
  419. if ( area != m_area )
  420. {
  421. area->OnDestroyNotify( m_area );
  422. }
  423. return true;
  424. }
  425. };
  426. //--------------------------------------------------------------------------------------------------------------
  427. /**
  428. * Destructor
  429. */
  430. CNavArea::~CNavArea()
  431. {
  432. // spot encounters aren't owned by anything else, so free them up here
  433. m_spotEncounters.PurgeAndDeleteElements();
  434. // if we are resetting the system, don't bother cleaning up - all areas are being destroyed
  435. if (m_isReset)
  436. return;
  437. // tell the other areas and ladders we are going away
  438. AreaDestroyNotification notification( this );
  439. TheNavMesh->ForAllAreas( notification );
  440. TheNavMesh->ForAllLadders( notification );
  441. // remove the area from the grid
  442. TheNavMesh->RemoveNavArea( this );
  443. // make sure no players keep a pointer to this area
  444. ForgetArea forget( this );
  445. ForEachActor( forget );
  446. }
  447. //--------------------------------------------------------------------------------------------------------------
  448. /**
  449. * Find elevator connections between areas
  450. */
  451. void CNavArea::ConnectElevators( void )
  452. {
  453. m_elevator = NULL;
  454. m_attributeFlags &= ~NAV_MESH_HAS_ELEVATOR;
  455. m_elevatorAreas.RemoveAll();
  456. #ifdef TERROR
  457. // connect elevators
  458. CFuncElevator *elevator = NULL;
  459. while( ( elevator = (CFuncElevator *)gEntList.FindEntityByClassname( elevator, "func_elevator" ) ) != NULL )
  460. {
  461. if ( elevator->GetNumFloors() < 2 )
  462. {
  463. // broken elevator
  464. continue;
  465. }
  466. Extent elevatorExtent;
  467. elevator->CollisionProp()->WorldSpaceSurroundingBounds( &elevatorExtent.lo, &elevatorExtent.hi );
  468. if ( IsOverlapping( elevatorExtent ) )
  469. {
  470. // overlaps in 2D - check that this area is within the shaft of the elevator
  471. const Vector &center = GetCenter();
  472. for( int f=0; f<elevator->GetNumFloors(); ++f )
  473. {
  474. const FloorInfo *floor = elevator->GetFloor( f );
  475. const float tolerance = 30.0f;
  476. if ( center.z <= floor->height + tolerance && center.z >= floor->height - tolerance )
  477. {
  478. if ( m_elevator )
  479. {
  480. Warning( "Multiple elevators overlap navigation area #%d\n", GetID() );
  481. break;
  482. }
  483. // this area is part of an elevator system
  484. m_elevator = elevator;
  485. m_attributeFlags |= NAV_MESH_HAS_ELEVATOR;
  486. // find the largest area overlapping this elevator on each other floor
  487. for( int of=0; of<elevator->GetNumFloors(); ++of )
  488. {
  489. if ( of == f )
  490. {
  491. // we are on this floor
  492. continue;
  493. }
  494. const FloorInfo *otherFloor = elevator->GetFloor( of );
  495. // find the largest area at this floor
  496. CNavArea *floorArea = NULL;
  497. float floorAreaSize = 0.0f;
  498. FOR_EACH_VEC( TheNavAreas, it )
  499. {
  500. CNavArea *area = TheNavAreas[ it ];
  501. if ( area->IsOverlapping( elevatorExtent ) )
  502. {
  503. if ( area->GetCenter().z <= otherFloor->height + tolerance && area->GetCenter().z >= otherFloor->height - tolerance )
  504. {
  505. float size = area->GetSizeX() * area->GetSizeY();
  506. if ( size > floorAreaSize )
  507. {
  508. floorArea = area;
  509. floorAreaSize = size;
  510. }
  511. }
  512. }
  513. }
  514. if ( floorArea )
  515. {
  516. // add this area to the set of areas reachable via elevator
  517. NavConnect con;
  518. con.area = floorArea;
  519. con.length = ( floorArea->GetCenter() - GetCenter() ).Length();
  520. m_elevatorAreas.AddToTail( con );
  521. }
  522. else
  523. {
  524. Warning( "Floor %d ('%s') of elevator at ( %3.2f, %3.2f, %3.2f ) has no matching navigation areas\n",
  525. of,
  526. otherFloor->name.ToCStr(),
  527. elevator->GetAbsOrigin().x, elevator->GetAbsOrigin().y, elevator->GetAbsOrigin().z );
  528. }
  529. }
  530. // we found our floor
  531. break;
  532. }
  533. }
  534. }
  535. }
  536. #endif // TERROR
  537. }
  538. //--------------------------------------------------------------------------------------------------------------
  539. /**
  540. * Invoked when map is initially loaded
  541. */
  542. void CNavArea::OnServerActivate( void )
  543. {
  544. ConnectElevators();
  545. m_damagingTickCount = 0;
  546. ClearAllNavCostEntities();
  547. }
  548. //--------------------------------------------------------------------------------------------------------------
  549. /**
  550. * Invoked for each area when the round restarts
  551. */
  552. void CNavArea::OnRoundRestart( void )
  553. {
  554. // need to redo this here since func_elevators are deleted and recreated at round restart
  555. ConnectElevators();
  556. m_damagingTickCount = 0;
  557. ClearAllNavCostEntities();
  558. }
  559. #ifdef DEBUG_AREA_PLAYERCOUNTS
  560. //--------------------------------------------------------------------------------------------------------------
  561. void CNavArea::IncrementPlayerCount( int teamID, int entIndex )
  562. {
  563. ConColorMsg( Color( 128, 255, 128, 255 ), "%f: Adding ent %d (team %d) to area %d\n", gpGlobals->curtime, entIndex, teamID, GetID() );
  564. teamID = teamID % MAX_NAV_TEAMS;
  565. Assert( !m_playerEntIndices[teamID].HasElement( entIndex ) );
  566. if ( !m_playerEntIndices[teamID].HasElement( entIndex ) )
  567. {
  568. m_playerEntIndices[teamID].AddToTail( entIndex );
  569. }
  570. if (m_playerCount[ teamID ] == 255)
  571. {
  572. Warning( "CNavArea::IncrementPlayerCount: Overflow\n" );
  573. return;
  574. }
  575. ++m_playerCount[ teamID ];
  576. }
  577. //--------------------------------------------------------------------------------------------------------------
  578. void CNavArea::DecrementPlayerCount( int teamID, int entIndex )
  579. {
  580. ConColorMsg( Color( 128, 128, 255, 255 ), "%f: Removing ent %d (team %d) from area %d\n", gpGlobals->curtime, entIndex, teamID, GetID() );
  581. teamID = teamID % MAX_NAV_TEAMS;
  582. Assert( m_playerEntIndices[teamID].HasElement( entIndex ) );
  583. m_playerEntIndices[teamID].FindAndFastRemove( entIndex );
  584. if (m_playerCount[ teamID ] == 0)
  585. {
  586. Warning( "CNavArea::IncrementPlayerCount: Underflow\n" );
  587. return;
  588. }
  589. --m_playerCount[ teamID ];
  590. }
  591. #endif // DEBUG_AREA_PLAYERCOUNTS
  592. //--------------------------------------------------------------------------------------------------------------
  593. /**
  594. * This is invoked at the start of an incremental nav generation on pre-existing areas.
  595. */
  596. void CNavArea::ResetNodes( void )
  597. {
  598. for ( int i=0; i<NUM_CORNERS; ++i )
  599. {
  600. m_node[i] = NULL;
  601. }
  602. }
  603. //--------------------------------------------------------------------------------------------------------------
  604. bool CNavArea::HasNodes( void ) const
  605. {
  606. for ( int i=0; i<NUM_CORNERS; ++i )
  607. {
  608. if ( m_node[i] )
  609. {
  610. return true;
  611. }
  612. }
  613. return false;
  614. }
  615. //--------------------------------------------------------------------------------------------------------------
  616. /**
  617. * This is invoked when an area is going away.
  618. * Remove any references we have to it.
  619. */
  620. void CNavArea::OnDestroyNotify( CNavArea *dead )
  621. {
  622. NavConnect con;
  623. con.area = dead;
  624. for( int d=0; d<NUM_DIRECTIONS; ++d )
  625. {
  626. m_connect[ d ].FindAndRemove( con );
  627. m_incomingConnect[ d ].FindAndRemove( con );
  628. }
  629. // remove all visibility info, since we're editing the mesh anyways
  630. m_inheritVisibilityFrom.area = NULL;
  631. m_potentiallyVisibleAreas.RemoveAll();
  632. m_isInheritedFrom = false;
  633. }
  634. //--------------------------------------------------------------------------------------------------------------
  635. /**
  636. * This is invoked when a ladder is going away.
  637. * Remove any references we have to it.
  638. */
  639. void CNavArea::OnDestroyNotify( CNavLadder *dead )
  640. {
  641. Disconnect( dead );
  642. }
  643. //--------------------------------------------------------------------------------------------------------------
  644. /**
  645. * Connect this area to given area in given direction
  646. */
  647. void CNavArea::ConnectTo( CNavArea *area, NavDirType dir )
  648. {
  649. // don't allow self-referential connections
  650. if ( area == this )
  651. return;
  652. // check if already connected
  653. FOR_EACH_VEC( m_connect[ dir ], it )
  654. {
  655. if (m_connect[ dir ][ it ].area == area)
  656. return;
  657. }
  658. NavConnect con;
  659. con.area = area;
  660. con.length = ( area->GetCenter() - GetCenter() ).Length();
  661. m_connect[ dir ].AddToTail( con );
  662. m_incomingConnect[ dir ].FindAndRemove( con );
  663. NavDirType dirOpposite = OppositeDirection( dir );
  664. con.area = this;
  665. if ( area->m_connect[ dirOpposite ].Find( con ) == area->m_connect[ dirOpposite ].InvalidIndex() )
  666. {
  667. area->AddIncomingConnection( this, dirOpposite );
  668. }
  669. //static char *dirName[] = { "NORTH", "EAST", "SOUTH", "WEST" };
  670. //CONSOLE_ECHO( " Connected area #%d to #%d, %s\n", m_id, area->m_id, dirName[ dir ] );
  671. }
  672. //--------------------------------------------------------------------------------------------------------------
  673. /**
  674. * Connect this area to given ladder
  675. */
  676. void CNavArea::ConnectTo( CNavLadder *ladder )
  677. {
  678. float center = (ladder->m_top.z + ladder->m_bottom.z) * 0.5f;
  679. Disconnect( ladder ); // just in case
  680. if ( GetCenter().z > center )
  681. {
  682. AddLadderDown( ladder );
  683. }
  684. else
  685. {
  686. AddLadderUp( ladder );
  687. }
  688. }
  689. //--------------------------------------------------------------------------------------------------------------
  690. /**
  691. * Disconnect this area from given area
  692. */
  693. void CNavArea::Disconnect( CNavArea *area )
  694. {
  695. NavConnect connect;
  696. connect.area = area;
  697. for( int i = 0; i<NUM_DIRECTIONS; i++ )
  698. {
  699. NavDirType dir = (NavDirType) i;
  700. NavDirType dirOpposite = OppositeDirection( dir );
  701. int index = m_connect[ dir ].Find( connect );
  702. if ( index != m_connect[ dir ].InvalidIndex() )
  703. {
  704. m_connect[ dir ].Remove( index );
  705. if ( area->IsConnected( this, dirOpposite ) )
  706. {
  707. AddIncomingConnection( area, dir );
  708. }
  709. else
  710. {
  711. connect.area = this;
  712. area->m_incomingConnect[ dirOpposite ].FindAndRemove( connect );
  713. }
  714. }
  715. }
  716. }
  717. //--------------------------------------------------------------------------------------------------------------
  718. /**
  719. * Disconnect this area from given ladder
  720. */
  721. void CNavArea::Disconnect( CNavLadder *ladder )
  722. {
  723. NavLadderConnect con;
  724. con.ladder = ladder;
  725. for( int i=0; i<CNavLadder::NUM_LADDER_DIRECTIONS; ++i )
  726. {
  727. m_ladder[i].FindAndRemove( con );
  728. }
  729. }
  730. //--------------------------------------------------------------------------------------------------------------
  731. void CNavArea::AddLadderUp( CNavLadder *ladder )
  732. {
  733. Disconnect( ladder ); // just in case
  734. NavLadderConnect tmp;
  735. tmp.ladder = ladder;
  736. m_ladder[ CNavLadder::LADDER_UP ].AddToTail( tmp );
  737. }
  738. //--------------------------------------------------------------------------------------------------------------
  739. void CNavArea::AddLadderDown( CNavLadder *ladder )
  740. {
  741. Disconnect( ladder ); // just in case
  742. NavLadderConnect tmp;
  743. tmp.ladder = ladder;
  744. m_ladder[ CNavLadder::LADDER_DOWN ].AddToTail( tmp );
  745. }
  746. //--------------------------------------------------------------------------------------------------------------
  747. /**
  748. * Recompute internal data once nodes have been adjusted during merge
  749. * Destroy adjArea.
  750. */
  751. void CNavArea::FinishMerge( CNavArea *adjArea )
  752. {
  753. // update extent
  754. m_nwCorner = *m_node[ NORTH_WEST ]->GetPosition();
  755. m_seCorner = *m_node[ SOUTH_EAST ]->GetPosition();
  756. m_center.x = (m_nwCorner.x + m_seCorner.x)/2.0f;
  757. m_center.y = (m_nwCorner.y + m_seCorner.y)/2.0f;
  758. m_center.z = (m_nwCorner.z + m_seCorner.z)/2.0f;
  759. m_neZ = m_node[ NORTH_EAST ]->GetPosition()->z;
  760. m_swZ = m_node[ SOUTH_WEST ]->GetPosition()->z;
  761. if ( ( m_seCorner.x - m_nwCorner.x ) > 0.0f && ( m_seCorner.y - m_nwCorner.y ) > 0.0f )
  762. {
  763. m_invDxCorners = 1.0f / ( m_seCorner.x - m_nwCorner.x );
  764. m_invDyCorners = 1.0f / ( m_seCorner.y - m_nwCorner.y );
  765. }
  766. else
  767. {
  768. m_invDxCorners = m_invDyCorners = 0;
  769. }
  770. // reassign the adjacent area's internal nodes to the final area
  771. adjArea->AssignNodes( this );
  772. // merge adjacency links - we gain all the connections that adjArea had
  773. MergeAdjacentConnections( adjArea );
  774. // remove subsumed adjacent area
  775. TheNavAreas.FindAndRemove( adjArea );
  776. TheNavMesh->OnEditDestroyNotify( adjArea );
  777. TheNavMesh->DestroyArea( adjArea );
  778. }
  779. //--------------------------------------------------------------------------------------------------------------
  780. class LadderConnectionReplacement
  781. {
  782. CNavArea *m_originalArea;
  783. CNavArea *m_replacementArea;
  784. public:
  785. LadderConnectionReplacement( CNavArea *originalArea, CNavArea *replacementArea )
  786. {
  787. m_originalArea = originalArea;
  788. m_replacementArea = replacementArea;
  789. }
  790. bool operator()( CNavLadder *ladder )
  791. {
  792. if ( ladder->m_topForwardArea == m_originalArea )
  793. ladder->m_topForwardArea = m_replacementArea;
  794. if ( ladder->m_topRightArea == m_originalArea )
  795. ladder->m_topRightArea = m_replacementArea;
  796. if ( ladder->m_topLeftArea == m_originalArea )
  797. ladder->m_topLeftArea = m_replacementArea;
  798. if ( ladder->m_topBehindArea == m_originalArea )
  799. ladder->m_topBehindArea = m_replacementArea;
  800. if ( ladder->m_bottomArea == m_originalArea )
  801. ladder->m_bottomArea = m_replacementArea;
  802. return true;
  803. }
  804. };
  805. //--------------------------------------------------------------------------------------------------------------
  806. /**
  807. * For merging with "adjArea" - pick up all of "adjArea"s connections
  808. */
  809. void CNavArea::MergeAdjacentConnections( CNavArea *adjArea )
  810. {
  811. // merge adjacency links - we gain all the connections that adjArea had
  812. int dir;
  813. for( dir = 0; dir<NUM_DIRECTIONS; dir++ )
  814. {
  815. FOR_EACH_VEC( adjArea->m_connect[ dir ], it )
  816. {
  817. NavConnect connect = adjArea->m_connect[ dir ][ it ];
  818. if (connect.area != adjArea && connect.area != this)
  819. ConnectTo( connect.area, (NavDirType)dir );
  820. }
  821. }
  822. // remove any references from this area to the adjacent area, since it is now part of us
  823. Disconnect( adjArea );
  824. // Change other references to adjArea to refer instead to us
  825. // We can't just replace existing connections, as several adjacent areas may have been merged into one,
  826. // resulting in a large area adjacent to all of them ending up with multiple redunandant connections
  827. // into the merged area, one for each of the adjacent subsumed smaller ones.
  828. // If an area has a connection to the merged area, we must remove all references to adjArea, and add
  829. // a single connection to us.
  830. FOR_EACH_VEC( TheNavAreas, it )
  831. {
  832. CNavArea *area = TheNavAreas[ it ];
  833. if (area == this || area == adjArea)
  834. continue;
  835. for( dir = 0; dir<NUM_DIRECTIONS; dir++ )
  836. {
  837. // check if there are any references to adjArea in this direction
  838. bool connected = false;
  839. FOR_EACH_VEC( area->m_connect[ dir ], cit )
  840. {
  841. NavConnect connect = area->m_connect[ dir ][ cit ];
  842. if (connect.area == adjArea)
  843. {
  844. connected = true;
  845. break;
  846. }
  847. }
  848. if (connected)
  849. {
  850. // remove all references to adjArea
  851. area->Disconnect( adjArea );
  852. // remove all references to the new area
  853. area->Disconnect( this );
  854. // add a single connection to the new area
  855. area->ConnectTo( this, (NavDirType) dir );
  856. }
  857. }
  858. }
  859. // We gain all ladder connections adjArea had
  860. for( dir=0; dir<CNavLadder::NUM_LADDER_DIRECTIONS; ++dir )
  861. {
  862. FOR_EACH_VEC( adjArea->m_ladder[ dir ], it )
  863. {
  864. ConnectTo( adjArea->m_ladder[ dir ][ it ].ladder );
  865. }
  866. }
  867. // All ladders that point to adjArea should point to us now
  868. LadderConnectionReplacement replacement( adjArea, this );
  869. TheNavMesh->ForAllLadders( replacement );
  870. }
  871. //--------------------------------------------------------------------------------------------------------------
  872. /**
  873. * Assign internal nodes to the given area
  874. * NOTE: "internal" nodes do not include the east or south border nodes
  875. */
  876. void CNavArea::AssignNodes( CNavArea *area )
  877. {
  878. CNavNode *horizLast = m_node[ NORTH_EAST ];
  879. for( CNavNode *vertNode = m_node[ NORTH_WEST ]; vertNode != m_node[ SOUTH_WEST ]; vertNode = vertNode->GetConnectedNode( SOUTH ) )
  880. {
  881. for( CNavNode *horizNode = vertNode; horizNode != horizLast; horizNode = horizNode->GetConnectedNode( EAST ) )
  882. {
  883. horizNode->AssignArea( area );
  884. }
  885. horizLast = horizLast->GetConnectedNode( SOUTH );
  886. }
  887. }
  888. //--------------------------------------------------------------------------------------------------------------
  889. class SplitNotification
  890. {
  891. CNavArea *m_originalArea;
  892. CNavArea *m_alphaArea;
  893. CNavArea *m_betaArea;
  894. public:
  895. SplitNotification( CNavArea *originalArea, CNavArea *alphaArea, CNavArea *betaArea )
  896. {
  897. m_originalArea = originalArea;
  898. m_alphaArea = alphaArea;
  899. m_betaArea = betaArea;
  900. }
  901. bool operator()( CNavLadder *ladder )
  902. {
  903. ladder->OnSplit( m_originalArea, m_alphaArea, m_betaArea );
  904. return true;
  905. }
  906. };
  907. //--------------------------------------------------------------------------------------------------------------
  908. /**
  909. * Split this area into two areas at the given edge.
  910. * Preserve all adjacency connections.
  911. * NOTE: This does not update node connections, only areas.
  912. */
  913. bool CNavArea::SplitEdit( bool splitAlongX, float splitEdge, CNavArea **outAlpha, CNavArea **outBeta )
  914. {
  915. CNavArea *alpha = NULL;
  916. CNavArea *beta = NULL;
  917. if (splitAlongX)
  918. {
  919. // +-----+->X
  920. // | A |
  921. // +-----+
  922. // | B |
  923. // +-----+
  924. // |
  925. // Y
  926. // don't do split if at edge of area
  927. if (splitEdge <= m_nwCorner.y + 1.0f)
  928. return false;
  929. if (splitEdge >= m_seCorner.y - 1.0f)
  930. return false;
  931. alpha = TheNavMesh->CreateArea();
  932. alpha->m_nwCorner = m_nwCorner;
  933. alpha->m_seCorner.x = m_seCorner.x;
  934. alpha->m_seCorner.y = splitEdge;
  935. alpha->m_seCorner.z = GetZ( alpha->m_seCorner );
  936. beta = TheNavMesh->CreateArea();
  937. beta->m_nwCorner.x = m_nwCorner.x;
  938. beta->m_nwCorner.y = splitEdge;
  939. beta->m_nwCorner.z = GetZ( beta->m_nwCorner );
  940. beta->m_seCorner = m_seCorner;
  941. alpha->ConnectTo( beta, SOUTH );
  942. beta->ConnectTo( alpha, NORTH );
  943. FinishSplitEdit( alpha, SOUTH );
  944. FinishSplitEdit( beta, NORTH );
  945. }
  946. else
  947. {
  948. // +--+--+->X
  949. // | | |
  950. // | A|B |
  951. // | | |
  952. // +--+--+
  953. // |
  954. // Y
  955. // don't do split if at edge of area
  956. if (splitEdge <= m_nwCorner.x + 1.0f)
  957. return false;
  958. if (splitEdge >= m_seCorner.x - 1.0f)
  959. return false;
  960. alpha = TheNavMesh->CreateArea();
  961. alpha->m_nwCorner = m_nwCorner;
  962. alpha->m_seCorner.x = splitEdge;
  963. alpha->m_seCorner.y = m_seCorner.y;
  964. alpha->m_seCorner.z = GetZ( alpha->m_seCorner );
  965. beta = TheNavMesh->CreateArea();
  966. beta->m_nwCorner.x = splitEdge;
  967. beta->m_nwCorner.y = m_nwCorner.y;
  968. beta->m_nwCorner.z = GetZ( beta->m_nwCorner );
  969. beta->m_seCorner = m_seCorner;
  970. alpha->ConnectTo( beta, EAST );
  971. beta->ConnectTo( alpha, WEST );
  972. FinishSplitEdit( alpha, EAST );
  973. FinishSplitEdit( beta, WEST );
  974. }
  975. if ( !TheNavMesh->IsGenerating() && nav_split_place_on_ground.GetBool() )
  976. {
  977. alpha->PlaceOnGround( NUM_CORNERS );
  978. beta->PlaceOnGround( NUM_CORNERS );
  979. }
  980. // For every ladder we pointed to, alpha or beta should point to it, based on
  981. // their distance to the ladder
  982. int dir;
  983. for( dir=0; dir<CNavLadder::NUM_LADDER_DIRECTIONS; ++dir )
  984. {
  985. FOR_EACH_VEC( m_ladder[ dir ], it )
  986. {
  987. CNavLadder *ladder = m_ladder[ dir ][ it ].ladder;
  988. Vector ladderPos = ladder->m_top; // doesn't matter if we choose top or bottom
  989. float alphaDistance = alpha->GetDistanceSquaredToPoint( ladderPos );
  990. float betaDistance = beta->GetDistanceSquaredToPoint( ladderPos );
  991. if ( alphaDistance < betaDistance )
  992. {
  993. alpha->ConnectTo( ladder );
  994. }
  995. else
  996. {
  997. beta->ConnectTo( ladder );
  998. }
  999. }
  1000. }
  1001. // For every ladder that pointed to us, connect that ladder to the closer of alpha and beta
  1002. SplitNotification notify( this, alpha, beta );
  1003. TheNavMesh->ForAllLadders( notify );
  1004. // return new areas
  1005. if (outAlpha)
  1006. *outAlpha = alpha;
  1007. if (outBeta)
  1008. *outBeta = beta;
  1009. TheNavMesh->OnEditCreateNotify( alpha );
  1010. TheNavMesh->OnEditCreateNotify( beta );
  1011. if ( TheNavMesh->IsInSelectedSet( this ) )
  1012. {
  1013. TheNavMesh->AddToSelectedSet( alpha );
  1014. TheNavMesh->AddToSelectedSet( beta );
  1015. }
  1016. // remove original area
  1017. TheNavMesh->OnEditDestroyNotify( this );
  1018. TheNavAreas.FindAndRemove( this );
  1019. TheNavMesh->RemoveFromSelectedSet( this );
  1020. TheNavMesh->DestroyArea( this );
  1021. return true;
  1022. }
  1023. //--------------------------------------------------------------------------------------------------------------
  1024. /**
  1025. * Return true if given ladder is connected in given direction
  1026. * @todo Formalize "asymmetric" flag on connections
  1027. */
  1028. bool CNavArea::IsConnected( const CNavLadder *ladder, CNavLadder::LadderDirectionType dir ) const
  1029. {
  1030. FOR_EACH_VEC( m_ladder[ dir ], it )
  1031. {
  1032. if ( ladder == m_ladder[ dir ][ it ].ladder )
  1033. {
  1034. return true;
  1035. }
  1036. }
  1037. return false;
  1038. }
  1039. //--------------------------------------------------------------------------------------------------------------
  1040. /**
  1041. * Return true if given area is connected in given direction
  1042. * if dir == NUM_DIRECTIONS, check all directions (direction is unknown)
  1043. * @todo Formalize "asymmetric" flag on connections
  1044. */
  1045. bool CNavArea::IsConnected( const CNavArea *area, NavDirType dir ) const
  1046. {
  1047. // we are connected to ourself
  1048. if (area == this)
  1049. return true;
  1050. if (dir == NUM_DIRECTIONS)
  1051. {
  1052. // search all directions
  1053. for( int d=0; d<NUM_DIRECTIONS; ++d )
  1054. {
  1055. FOR_EACH_VEC( m_connect[ d ], it )
  1056. {
  1057. if (area == m_connect[ d ][ it ].area)
  1058. return true;
  1059. }
  1060. }
  1061. // check ladder connections
  1062. FOR_EACH_VEC( m_ladder[ CNavLadder::LADDER_UP ], it )
  1063. {
  1064. CNavLadder *ladder = m_ladder[ CNavLadder::LADDER_UP ][ it ].ladder;
  1065. if (ladder->m_topBehindArea == area ||
  1066. ladder->m_topForwardArea == area ||
  1067. ladder->m_topLeftArea == area ||
  1068. ladder->m_topRightArea == area)
  1069. return true;
  1070. }
  1071. FOR_EACH_VEC( m_ladder[ CNavLadder::LADDER_DOWN ], dit )
  1072. {
  1073. CNavLadder *ladder = m_ladder[ CNavLadder::LADDER_DOWN ][ dit ].ladder;
  1074. if (ladder->m_bottomArea == area)
  1075. return true;
  1076. }
  1077. }
  1078. else
  1079. {
  1080. // check specific direction
  1081. FOR_EACH_VEC( m_connect[ dir ], it )
  1082. {
  1083. if (area == m_connect[ dir ][ it ].area)
  1084. return true;
  1085. }
  1086. }
  1087. return false;
  1088. }
  1089. //--------------------------------------------------------------------------------------------------------------
  1090. /**
  1091. * Compute change in actual ground height from this area to given area
  1092. */
  1093. float CNavArea::ComputeGroundHeightChange( const CNavArea *area )
  1094. {
  1095. VPROF_BUDGET( "CNavArea::ComputeHeightChange", "NextBot" );
  1096. Vector closeFrom, closeTo;
  1097. area->GetClosestPointOnArea( GetCenter(), &closeTo );
  1098. GetClosestPointOnArea( area->GetCenter(), &closeFrom );
  1099. // find actual ground height at each point in case
  1100. // areas are below/above actual terrain
  1101. float toZ, fromZ;
  1102. if ( TheNavMesh->GetSimpleGroundHeight( closeTo + Vector( 0, 0, StepHeight ), &toZ ) == false )
  1103. {
  1104. return 0.0f;
  1105. }
  1106. if ( TheNavMesh->GetSimpleGroundHeight( closeFrom + Vector( 0, 0, StepHeight ), &fromZ ) == false )
  1107. {
  1108. return 0.0f;
  1109. }
  1110. return toZ - fromZ;
  1111. }
  1112. //--------------------------------------------------------------------------------------------------------------
  1113. /**
  1114. * The area 'source' is connected to us along our 'incomingEdgeDir' edge
  1115. */
  1116. void CNavArea::AddIncomingConnection( CNavArea *source, NavDirType incomingEdgeDir )
  1117. {
  1118. NavConnect con;
  1119. con.area = source;
  1120. if ( m_incomingConnect[ incomingEdgeDir ].Find( con ) == m_incomingConnect[ incomingEdgeDir ].InvalidIndex() )
  1121. {
  1122. con.length = ( source->GetCenter() - GetCenter() ).Length();
  1123. m_incomingConnect[ incomingEdgeDir ].AddToTail( con );
  1124. }
  1125. }
  1126. //--------------------------------------------------------------------------------------------------------------
  1127. /**
  1128. * Given the portion of the original area, update its internal data
  1129. * The "ignoreEdge" direction defines the side of the original area that the new area does not include
  1130. */
  1131. void CNavArea::FinishSplitEdit( CNavArea *newArea, NavDirType ignoreEdge )
  1132. {
  1133. newArea->InheritAttributes( this );
  1134. newArea->m_center.x = (newArea->m_nwCorner.x + newArea->m_seCorner.x)/2.0f;
  1135. newArea->m_center.y = (newArea->m_nwCorner.y + newArea->m_seCorner.y)/2.0f;
  1136. newArea->m_center.z = (newArea->m_nwCorner.z + newArea->m_seCorner.z)/2.0f;
  1137. newArea->m_neZ = GetZ( newArea->m_seCorner.x, newArea->m_nwCorner.y );
  1138. newArea->m_swZ = GetZ( newArea->m_nwCorner.x, newArea->m_seCorner.y );
  1139. if ( ( m_seCorner.x - m_nwCorner.x ) > 0.0f && ( m_seCorner.y - m_nwCorner.y ) > 0.0f )
  1140. {
  1141. newArea->m_invDxCorners = 1.0f / ( newArea->m_seCorner.x - newArea->m_nwCorner.x );
  1142. newArea->m_invDyCorners = 1.0f / ( newArea->m_seCorner.y - newArea->m_nwCorner.y );
  1143. }
  1144. else
  1145. {
  1146. newArea->m_invDxCorners = newArea->m_invDyCorners = 0;
  1147. }
  1148. // connect to adjacent areas
  1149. for( int d=0; d<NUM_DIRECTIONS; ++d )
  1150. {
  1151. if (d == ignoreEdge)
  1152. continue;
  1153. int count = GetAdjacentCount( (NavDirType)d );
  1154. for( int a=0; a<count; ++a )
  1155. {
  1156. CNavArea *adj = GetAdjacentArea( (NavDirType)d, a );
  1157. switch( d )
  1158. {
  1159. case NORTH:
  1160. case SOUTH:
  1161. if (newArea->IsOverlappingX( adj ))
  1162. {
  1163. newArea->ConnectTo( adj, (NavDirType)d );
  1164. // add reciprocal connection if needed
  1165. if (adj->IsConnected( this, OppositeDirection( (NavDirType)d )))
  1166. adj->ConnectTo( newArea, OppositeDirection( (NavDirType)d ) );
  1167. }
  1168. break;
  1169. case EAST:
  1170. case WEST:
  1171. if (newArea->IsOverlappingY( adj ))
  1172. {
  1173. newArea->ConnectTo( adj, (NavDirType)d );
  1174. // add reciprocal connection if needed
  1175. if (adj->IsConnected( this, OppositeDirection( (NavDirType)d )))
  1176. adj->ConnectTo( newArea, OppositeDirection( (NavDirType)d ) );
  1177. }
  1178. break;
  1179. }
  1180. for ( int a = 0; a < m_incomingConnect[d].Count(); a++ )
  1181. {
  1182. CNavArea *adj = m_incomingConnect[d][a].area;
  1183. switch( d )
  1184. {
  1185. case NORTH:
  1186. case SOUTH:
  1187. if (newArea->IsOverlappingX( adj ))
  1188. {
  1189. adj->ConnectTo( newArea, OppositeDirection( (NavDirType)d ) );
  1190. }
  1191. break;
  1192. case EAST:
  1193. case WEST:
  1194. if (newArea->IsOverlappingY( adj ))
  1195. {
  1196. adj->ConnectTo( newArea, OppositeDirection( (NavDirType)d ) );
  1197. }
  1198. break;
  1199. }
  1200. }
  1201. }
  1202. }
  1203. TheNavAreas.AddToTail( newArea );
  1204. TheNavMesh->AddNavArea( newArea );
  1205. // Assign nodes
  1206. if ( HasNodes() )
  1207. {
  1208. // first give it all our nodes...
  1209. newArea->m_node[ NORTH_WEST ] = m_node[ NORTH_WEST ];
  1210. newArea->m_node[ NORTH_EAST ] = m_node[ NORTH_EAST ];
  1211. newArea->m_node[ SOUTH_EAST ] = m_node[ SOUTH_EAST ];
  1212. newArea->m_node[ SOUTH_WEST ] = m_node[ SOUTH_WEST ];
  1213. // ... then pull in one edge...
  1214. NavDirType dir = NUM_DIRECTIONS;
  1215. NavCornerType corner[2] = { NUM_CORNERS, NUM_CORNERS };
  1216. switch ( ignoreEdge )
  1217. {
  1218. case NORTH:
  1219. dir = SOUTH;
  1220. corner[0] = NORTH_WEST;
  1221. corner[1] = NORTH_EAST;
  1222. break;
  1223. case SOUTH:
  1224. dir = NORTH;
  1225. corner[0] = SOUTH_WEST;
  1226. corner[1] = SOUTH_EAST;
  1227. break;
  1228. case EAST:
  1229. dir = WEST;
  1230. corner[0] = NORTH_EAST;
  1231. corner[1] = SOUTH_EAST;
  1232. break;
  1233. case WEST:
  1234. dir = EAST;
  1235. corner[0] = NORTH_WEST;
  1236. corner[1] = SOUTH_WEST;
  1237. break;
  1238. }
  1239. while ( !newArea->IsOverlapping( *newArea->m_node[ corner[0] ]->GetPosition(), GenerationStepSize/2 ) )
  1240. {
  1241. for ( int i=0; i<2; ++i )
  1242. {
  1243. Assert( newArea->m_node[ corner[i] ] );
  1244. Assert( newArea->m_node[ corner[i] ]->GetConnectedNode( dir ) );
  1245. newArea->m_node[ corner[i] ] = newArea->m_node[ corner[i] ]->GetConnectedNode( dir );
  1246. }
  1247. }
  1248. // assign internal nodes...
  1249. newArea->AssignNodes( newArea );
  1250. // ... and grab the node heights for our corner heights.
  1251. newArea->m_neZ = newArea->m_node[ NORTH_EAST ]->GetPosition()->z;
  1252. newArea->m_nwCorner.z = newArea->m_node[ NORTH_WEST ]->GetPosition()->z;
  1253. newArea->m_swZ = newArea->m_node[ SOUTH_WEST ]->GetPosition()->z;
  1254. newArea->m_seCorner.z = newArea->m_node[ SOUTH_EAST ]->GetPosition()->z;
  1255. }
  1256. }
  1257. //--------------------------------------------------------------------------------------------------------------
  1258. /**
  1259. * Create a new area between this area and given area
  1260. */
  1261. bool CNavArea::SpliceEdit( CNavArea *other )
  1262. {
  1263. CNavArea *newArea = NULL;
  1264. Vector nw, ne, se, sw;
  1265. if (m_nwCorner.x > other->m_seCorner.x)
  1266. {
  1267. // 'this' is east of 'other'
  1268. float top = MAX( m_nwCorner.y, other->m_nwCorner.y );
  1269. float bottom = MIN( m_seCorner.y, other->m_seCorner.y );
  1270. nw.x = other->m_seCorner.x;
  1271. nw.y = top;
  1272. nw.z = other->GetZ( nw );
  1273. se.x = m_nwCorner.x;
  1274. se.y = bottom;
  1275. se.z = GetZ( se );
  1276. ne.x = se.x;
  1277. ne.y = nw.y;
  1278. ne.z = GetZ( ne );
  1279. sw.x = nw.x;
  1280. sw.y = se.y;
  1281. sw.z = other->GetZ( sw );
  1282. newArea = TheNavMesh->CreateArea();
  1283. if (newArea == NULL)
  1284. {
  1285. Warning( "SpliceEdit: Out of memory.\n" );
  1286. return false;
  1287. }
  1288. newArea->Build( nw, ne, se, sw );
  1289. this->ConnectTo( newArea, WEST );
  1290. newArea->ConnectTo( this, EAST );
  1291. other->ConnectTo( newArea, EAST );
  1292. newArea->ConnectTo( other, WEST );
  1293. }
  1294. else if (m_seCorner.x < other->m_nwCorner.x)
  1295. {
  1296. // 'this' is west of 'other'
  1297. float top = MAX( m_nwCorner.y, other->m_nwCorner.y );
  1298. float bottom = MIN( m_seCorner.y, other->m_seCorner.y );
  1299. nw.x = m_seCorner.x;
  1300. nw.y = top;
  1301. nw.z = GetZ( nw );
  1302. se.x = other->m_nwCorner.x;
  1303. se.y = bottom;
  1304. se.z = other->GetZ( se );
  1305. ne.x = se.x;
  1306. ne.y = nw.y;
  1307. ne.z = other->GetZ( ne );
  1308. sw.x = nw.x;
  1309. sw.y = se.y;
  1310. sw.z = GetZ( sw );
  1311. newArea = TheNavMesh->CreateArea();
  1312. if (newArea == NULL)
  1313. {
  1314. Warning( "SpliceEdit: Out of memory.\n" );
  1315. return false;
  1316. }
  1317. newArea->Build( nw, ne, se, sw );
  1318. this->ConnectTo( newArea, EAST );
  1319. newArea->ConnectTo( this, WEST );
  1320. other->ConnectTo( newArea, WEST );
  1321. newArea->ConnectTo( other, EAST );
  1322. }
  1323. else // 'this' overlaps in X
  1324. {
  1325. if (m_nwCorner.y > other->m_seCorner.y)
  1326. {
  1327. // 'this' is south of 'other'
  1328. float left = MAX( m_nwCorner.x, other->m_nwCorner.x );
  1329. float right = MIN( m_seCorner.x, other->m_seCorner.x );
  1330. nw.x = left;
  1331. nw.y = other->m_seCorner.y;
  1332. nw.z = other->GetZ( nw );
  1333. se.x = right;
  1334. se.y = m_nwCorner.y;
  1335. se.z = GetZ( se );
  1336. ne.x = se.x;
  1337. ne.y = nw.y;
  1338. ne.z = other->GetZ( ne );
  1339. sw.x = nw.x;
  1340. sw.y = se.y;
  1341. sw.z = GetZ( sw );
  1342. newArea = TheNavMesh->CreateArea();
  1343. if (newArea == NULL)
  1344. {
  1345. Warning( "SpliceEdit: Out of memory.\n" );
  1346. return false;
  1347. }
  1348. newArea->Build( nw, ne, se, sw );
  1349. this->ConnectTo( newArea, NORTH );
  1350. newArea->ConnectTo( this, SOUTH );
  1351. other->ConnectTo( newArea, SOUTH );
  1352. newArea->ConnectTo( other, NORTH );
  1353. }
  1354. else if (m_seCorner.y < other->m_nwCorner.y)
  1355. {
  1356. // 'this' is north of 'other'
  1357. float left = MAX( m_nwCorner.x, other->m_nwCorner.x );
  1358. float right = MIN( m_seCorner.x, other->m_seCorner.x );
  1359. nw.x = left;
  1360. nw.y = m_seCorner.y;
  1361. nw.z = GetZ( nw );
  1362. se.x = right;
  1363. se.y = other->m_nwCorner.y;
  1364. se.z = other->GetZ( se );
  1365. ne.x = se.x;
  1366. ne.y = nw.y;
  1367. ne.z = GetZ( ne );
  1368. sw.x = nw.x;
  1369. sw.y = se.y;
  1370. sw.z = other->GetZ( sw );
  1371. newArea = TheNavMesh->CreateArea();
  1372. if (newArea == NULL)
  1373. {
  1374. Warning( "SpliceEdit: Out of memory.\n" );
  1375. return false;
  1376. }
  1377. newArea->Build( nw, ne, se, sw );
  1378. this->ConnectTo( newArea, SOUTH );
  1379. newArea->ConnectTo( this, NORTH );
  1380. other->ConnectTo( newArea, NORTH );
  1381. newArea->ConnectTo( other, SOUTH );
  1382. }
  1383. else
  1384. {
  1385. // areas overlap
  1386. return false;
  1387. }
  1388. }
  1389. newArea->InheritAttributes( this, other );
  1390. TheNavAreas.AddToTail( newArea );
  1391. TheNavMesh->AddNavArea( newArea );
  1392. TheNavMesh->OnEditCreateNotify( newArea );
  1393. return true;
  1394. }
  1395. //--------------------------------------------------------------------------------------------------------------
  1396. /**
  1397. * Calculates a constant ID for an area at this location, for debugging
  1398. */
  1399. void CNavArea::CalcDebugID()
  1400. {
  1401. if ( m_debugid == 0 )
  1402. {
  1403. // calculate a debug ID which will be constant for this nav area across generation runs
  1404. int coord[6] = { (int) m_nwCorner.x, (int) m_nwCorner.x, (int) m_nwCorner.z, (int) m_seCorner.x, (int) m_seCorner.y, (int) m_seCorner.z };
  1405. m_debugid = CRC32_ProcessSingleBuffer( &coord, sizeof( coord ) );
  1406. }
  1407. }
  1408. //--------------------------------------------------------------------------------------------------------------
  1409. /**
  1410. * Merge this area and given adjacent area
  1411. */
  1412. bool CNavArea::MergeEdit( CNavArea *adj )
  1413. {
  1414. // can only merge if attributes of both areas match
  1415. // check that these areas can be merged
  1416. const float tolerance = 1.0f;
  1417. bool merge = false;
  1418. if (fabs( m_nwCorner.x - adj->m_nwCorner.x ) < tolerance &&
  1419. fabs( m_seCorner.x - adj->m_seCorner.x ) < tolerance)
  1420. merge = true;
  1421. if (fabs( m_nwCorner.y - adj->m_nwCorner.y ) < tolerance &&
  1422. fabs( m_seCorner.y - adj->m_seCorner.y ) < tolerance)
  1423. merge = true;
  1424. if (merge == false)
  1425. return false;
  1426. Vector originalNWCorner = m_nwCorner;
  1427. Vector originalSECorner = m_seCorner;
  1428. // update extent
  1429. if (m_nwCorner.x > adj->m_nwCorner.x || m_nwCorner.y > adj->m_nwCorner.y)
  1430. m_nwCorner = adj->m_nwCorner;
  1431. if (m_seCorner.x < adj->m_seCorner.x || m_seCorner.y < adj->m_seCorner.y)
  1432. m_seCorner = adj->m_seCorner;
  1433. m_center.x = (m_nwCorner.x + m_seCorner.x)/2.0f;
  1434. m_center.y = (m_nwCorner.y + m_seCorner.y)/2.0f;
  1435. m_center.z = (m_nwCorner.z + m_seCorner.z)/2.0f;
  1436. if ( ( m_seCorner.x - m_nwCorner.x ) > 0.0f && ( m_seCorner.y - m_nwCorner.y ) > 0.0f )
  1437. {
  1438. m_invDxCorners = 1.0f / ( m_seCorner.x - m_nwCorner.x );
  1439. m_invDyCorners = 1.0f / ( m_seCorner.y - m_nwCorner.y );
  1440. }
  1441. else
  1442. {
  1443. m_invDxCorners = m_invDyCorners = 0;
  1444. }
  1445. if (m_seCorner.x > originalSECorner.x || m_nwCorner.y < originalNWCorner.y)
  1446. m_neZ = adj->GetZ( m_seCorner.x, m_nwCorner.y );
  1447. else
  1448. m_neZ = GetZ( m_seCorner.x, m_nwCorner.y );
  1449. if (m_nwCorner.x < originalNWCorner.x || m_seCorner.y > originalSECorner.y)
  1450. m_swZ = adj->GetZ( m_nwCorner.x, m_seCorner.y );
  1451. else
  1452. m_swZ = GetZ( m_nwCorner.x, m_seCorner.y );
  1453. // merge adjacency links - we gain all the connections that adjArea had
  1454. MergeAdjacentConnections( adj );
  1455. InheritAttributes( adj );
  1456. // remove subsumed adjacent area
  1457. TheNavAreas.FindAndRemove( adj );
  1458. TheNavMesh->OnEditDestroyNotify( adj );
  1459. TheNavMesh->DestroyArea( adj );
  1460. TheNavMesh->OnEditCreateNotify( this );
  1461. return true;
  1462. }
  1463. //--------------------------------------------------------------------------------------------------------------
  1464. void CNavArea::InheritAttributes( CNavArea *first, CNavArea *second )
  1465. {
  1466. if ( first && second )
  1467. {
  1468. SetAttributes( first->GetAttributes() | second->GetAttributes() );
  1469. // if both areas have the same place, the new area inherits it
  1470. if ( first->GetPlace() == second->GetPlace() )
  1471. {
  1472. SetPlace( first->GetPlace() );
  1473. }
  1474. else if ( first->GetPlace() == UNDEFINED_PLACE )
  1475. {
  1476. SetPlace( second->GetPlace() );
  1477. }
  1478. else if ( second->GetPlace() == UNDEFINED_PLACE )
  1479. {
  1480. SetPlace( first->GetPlace() );
  1481. }
  1482. else
  1483. {
  1484. // both have valid, but different places - pick on at random
  1485. if ( RandomInt( 0, 100 ) < 50 )
  1486. SetPlace( first->GetPlace() );
  1487. else
  1488. SetPlace( second->GetPlace() );
  1489. }
  1490. }
  1491. else if ( first )
  1492. {
  1493. SetAttributes( GetAttributes() | first->GetAttributes() );
  1494. if ( GetPlace() == UNDEFINED_PLACE )
  1495. {
  1496. SetPlace( first->GetPlace() );
  1497. }
  1498. }
  1499. }
  1500. //--------------------------------------------------------------------------------------------------------------
  1501. void ApproachAreaAnalysisPrep( void )
  1502. {
  1503. }
  1504. //--------------------------------------------------------------------------------------------------------------
  1505. void CleanupApproachAreaAnalysisPrep( void )
  1506. {
  1507. }
  1508. //--------------------------------------------------------------------------------------------------------------
  1509. /**
  1510. * Remove "analyzed" data from nav area
  1511. */
  1512. void CNavArea::Strip( void )
  1513. {
  1514. m_spotEncounters.PurgeAndDeleteElements(); // this calls delete on each element
  1515. }
  1516. //--------------------------------------------------------------------------------------------------------------
  1517. /**
  1518. * Return true if area is more or less square.
  1519. * This is used when merging to prevent long, thin, areas being created.
  1520. */
  1521. bool CNavArea::IsRoughlySquare( void ) const
  1522. {
  1523. float aspect = GetSizeX() / GetSizeY();
  1524. const float maxAspect = 3.01;
  1525. const float minAspect = 1.0f / maxAspect;
  1526. if (aspect < minAspect || aspect > maxAspect)
  1527. return false;
  1528. return true;
  1529. }
  1530. //--------------------------------------------------------------------------------------------------------------
  1531. /**
  1532. * Return true if 'pos' is within 2D extents of area.
  1533. */
  1534. bool CNavArea::IsOverlapping( const Vector &pos, float tolerance ) const
  1535. {
  1536. if (pos.x + tolerance >= m_nwCorner.x && pos.x - tolerance <= m_seCorner.x &&
  1537. pos.y + tolerance >= m_nwCorner.y && pos.y - tolerance <= m_seCorner.y)
  1538. return true;
  1539. return false;
  1540. }
  1541. //--------------------------------------------------------------------------------------------------------------
  1542. /**
  1543. * Return true if 'area' overlaps our 2D extents
  1544. */
  1545. bool CNavArea::IsOverlapping( const CNavArea *area ) const
  1546. {
  1547. if (area->m_nwCorner.x < m_seCorner.x && area->m_seCorner.x > m_nwCorner.x &&
  1548. area->m_nwCorner.y < m_seCorner.y && area->m_seCorner.y > m_nwCorner.y)
  1549. return true;
  1550. return false;
  1551. }
  1552. //--------------------------------------------------------------------------------------------------------------
  1553. /**
  1554. * Return true if 'extent' overlaps our 2D extents
  1555. */
  1556. bool CNavArea::IsOverlapping( const Extent &extent ) const
  1557. {
  1558. return ( extent.lo.x < m_seCorner.x && extent.hi.x > m_nwCorner.x &&
  1559. extent.lo.y < m_seCorner.y && extent.hi.y > m_nwCorner.y );
  1560. }
  1561. //--------------------------------------------------------------------------------------------------------------
  1562. /**
  1563. * Return true if 'area' overlaps our X extent
  1564. */
  1565. bool CNavArea::IsOverlappingX( const CNavArea *area ) const
  1566. {
  1567. if (area->m_nwCorner.x < m_seCorner.x && area->m_seCorner.x > m_nwCorner.x)
  1568. return true;
  1569. return false;
  1570. }
  1571. //--------------------------------------------------------------------------------------------------------------
  1572. /**
  1573. * Return true if 'area' overlaps our Y extent
  1574. */
  1575. bool CNavArea::IsOverlappingY( const CNavArea *area ) const
  1576. {
  1577. if (area->m_nwCorner.y < m_seCorner.y && area->m_seCorner.y > m_nwCorner.y)
  1578. return true;
  1579. return false;
  1580. }
  1581. //--------------------------------------------------------------------------------------------------------------
  1582. class COverlapCheck
  1583. {
  1584. public:
  1585. COverlapCheck( const CNavArea *me, const Vector &pos ) : m_pos( pos )
  1586. {
  1587. m_me = me;
  1588. m_myZ = me->GetZ( pos );
  1589. }
  1590. bool operator() ( CNavArea *area )
  1591. {
  1592. // skip self
  1593. if ( area == m_me )
  1594. return true;
  1595. // check 2D overlap
  1596. if ( !area->IsOverlapping( m_pos ) )
  1597. return true;
  1598. float theirZ = area->GetZ( m_pos );
  1599. if ( theirZ > m_pos.z )
  1600. {
  1601. // they are above the point
  1602. return true;
  1603. }
  1604. if ( theirZ > m_myZ )
  1605. {
  1606. // we are below an area that is beneath the given position
  1607. return false;
  1608. }
  1609. return true;
  1610. }
  1611. const CNavArea *m_me;
  1612. float m_myZ;
  1613. const Vector &m_pos;
  1614. };
  1615. //--------------------------------------------------------------------------------------------------------------
  1616. /**
  1617. * Return true if given point is on or above this area, but no others
  1618. */
  1619. bool CNavArea::Contains( const Vector &pos ) const
  1620. {
  1621. // check 2D overlap
  1622. if (!IsOverlapping( pos ))
  1623. return false;
  1624. // the point overlaps us, check that it is above us, but not above any areas that overlap us
  1625. float myZ = GetZ( pos );
  1626. // if the nav area is above the given position, fail
  1627. // allow nav area to be as much as a step height above the given position
  1628. if (myZ - StepHeight > pos.z)
  1629. return false;
  1630. Extent areaExtent;
  1631. GetExtent( &areaExtent );
  1632. COverlapCheck overlap( this, pos );
  1633. return TheNavMesh->ForAllAreasOverlappingExtent( overlap, areaExtent );
  1634. }
  1635. //--------------------------------------------------------------------------------------------------------------
  1636. /**
  1637. * Returns true if area completely contains other area
  1638. */
  1639. bool CNavArea::Contains( const CNavArea *area ) const
  1640. {
  1641. return ( ( m_nwCorner.x <= area->m_nwCorner.x ) && ( m_seCorner.x >= area->m_seCorner.x ) &&
  1642. ( m_nwCorner.y <= area->m_nwCorner.y ) && ( m_seCorner.y >= area->m_seCorner.y ) &&
  1643. ( m_nwCorner.z <= area->m_nwCorner.z ) && ( m_seCorner.z >= area->m_seCorner.z ) );
  1644. }
  1645. //--------------------------------------------------------------------------------------------------------------
  1646. void CNavArea::ComputeNormal( Vector *normal, bool alternate ) const
  1647. {
  1648. if ( !normal )
  1649. return;
  1650. Vector u, v;
  1651. if ( !alternate )
  1652. {
  1653. u.x = m_seCorner.x - m_nwCorner.x;
  1654. u.y = 0.0f;
  1655. u.z = m_neZ - m_nwCorner.z;
  1656. v.x = 0.0f;
  1657. v.y = m_seCorner.y - m_nwCorner.y;
  1658. v.z = m_swZ - m_nwCorner.z;
  1659. }
  1660. else
  1661. {
  1662. u.x = m_nwCorner.x - m_seCorner.x;
  1663. u.y = 0.0f;
  1664. u.z = m_swZ - m_seCorner.z;
  1665. v.x = 0.0f;
  1666. v.y = m_nwCorner.y - m_seCorner.y;
  1667. v.z = m_neZ - m_seCorner.z;
  1668. }
  1669. *normal = CrossProduct( u, v );
  1670. normal->NormalizeInPlace();
  1671. }
  1672. //--------------------------------------------------------------------------------------------------------------
  1673. /**
  1674. * Removes all connections in directions to left and right of specified direction
  1675. */
  1676. void CNavArea::RemoveOrthogonalConnections( NavDirType dir )
  1677. {
  1678. NavDirType dirToRemove[2];
  1679. dirToRemove[0] = DirectionLeft( dir );
  1680. dirToRemove[1] = DirectionRight( dir );
  1681. for ( int i = 0; i < 2; i++ )
  1682. {
  1683. dir = dirToRemove[i];
  1684. while ( GetAdjacentCount( dir ) > 0 )
  1685. {
  1686. CNavArea *adj = GetAdjacentArea( dir, 0 );
  1687. Disconnect( adj );
  1688. adj->Disconnect( this );
  1689. }
  1690. }
  1691. }
  1692. //--------------------------------------------------------------------------------------------------------------
  1693. /**
  1694. * Return true if the area is approximately flat, using normals computed from opposite corners
  1695. */
  1696. bool CNavArea::IsFlat( void ) const
  1697. {
  1698. Vector normal, otherNormal;
  1699. ComputeNormal( &normal );
  1700. ComputeNormal( &otherNormal, true );
  1701. float tolerance = nav_coplanar_slope_limit.GetFloat();
  1702. if ( ( m_node[ NORTH_WEST ] && m_node[ NORTH_WEST ]->IsOnDisplacement() ) ||
  1703. ( m_node[ NORTH_EAST ] && m_node[ NORTH_EAST ]->IsOnDisplacement() ) ||
  1704. ( m_node[ SOUTH_EAST ] && m_node[ SOUTH_EAST ]->IsOnDisplacement() ) ||
  1705. ( m_node[ SOUTH_WEST ] && m_node[ SOUTH_WEST ]->IsOnDisplacement() ) )
  1706. {
  1707. tolerance = nav_coplanar_slope_limit_displacement.GetFloat();
  1708. }
  1709. if (DotProduct( normal, otherNormal ) > tolerance)
  1710. return true;
  1711. return false;
  1712. }
  1713. //--------------------------------------------------------------------------------------------------------------
  1714. /**
  1715. * Return true if this area and given area are approximately co-planar
  1716. */
  1717. bool CNavArea::IsCoplanar( const CNavArea *area ) const
  1718. {
  1719. Vector u, v;
  1720. bool isOnDisplacement = ( m_node[ NORTH_WEST ] && m_node[ NORTH_WEST ]->IsOnDisplacement() ) ||
  1721. ( m_node[ NORTH_EAST ] && m_node[ NORTH_EAST ]->IsOnDisplacement() ) ||
  1722. ( m_node[ SOUTH_EAST ] && m_node[ SOUTH_EAST ]->IsOnDisplacement() ) ||
  1723. ( m_node[ SOUTH_WEST ] && m_node[ SOUTH_WEST ]->IsOnDisplacement() );
  1724. if ( !isOnDisplacement && !IsFlat() )
  1725. return false;
  1726. bool areaIsOnDisplacement = ( area->m_node[ NORTH_WEST ] && area->m_node[ NORTH_WEST ]->IsOnDisplacement() ) ||
  1727. ( area->m_node[ NORTH_EAST ] && area->m_node[ NORTH_EAST ]->IsOnDisplacement() ) ||
  1728. ( area->m_node[ SOUTH_EAST ] && area->m_node[ SOUTH_EAST ]->IsOnDisplacement() ) ||
  1729. ( area->m_node[ SOUTH_WEST ] && area->m_node[ SOUTH_WEST ]->IsOnDisplacement() );
  1730. if ( !areaIsOnDisplacement && !area->IsFlat() )
  1731. return false;
  1732. // compute our unit surface normal
  1733. Vector normal, otherNormal;
  1734. ComputeNormal( &normal );
  1735. area->ComputeNormal( &otherNormal );
  1736. // can only merge areas that are nearly planar, to ensure areas do not differ from underlying geometry much
  1737. float tolerance = nav_coplanar_slope_limit.GetFloat();
  1738. if ( ( m_node[ NORTH_WEST ] && m_node[ NORTH_WEST ]->IsOnDisplacement() ) ||
  1739. ( m_node[ NORTH_EAST ] && m_node[ NORTH_EAST ]->IsOnDisplacement() ) ||
  1740. ( m_node[ SOUTH_EAST ] && m_node[ SOUTH_EAST ]->IsOnDisplacement() ) ||
  1741. ( m_node[ SOUTH_WEST ] && m_node[ SOUTH_WEST ]->IsOnDisplacement() ) )
  1742. {
  1743. tolerance = nav_coplanar_slope_limit_displacement.GetFloat();
  1744. }
  1745. if (DotProduct( normal, otherNormal ) > tolerance)
  1746. return true;
  1747. return false;
  1748. }
  1749. //--------------------------------------------------------------------------------------------------------------
  1750. /**
  1751. * Return Z of area at (x,y) of 'pos'
  1752. * Trilinear interpolation of Z values at quad edges.
  1753. * NOTE: pos->z is not used.
  1754. */
  1755. float CNavArea::GetZ( float x, float y ) const RESTRICT
  1756. {
  1757. // guard against division by zero due to degenerate areas
  1758. #ifdef _X360
  1759. // do the compare-against-zero on the integer unit to avoid a fcmp
  1760. // IEEE754 float positive zero is simply 0x00. There is also a
  1761. // floating-point negative zero (-0.0f == 0x80000000), but given
  1762. // how m_inv is computed earlier, that's not a possible value for
  1763. // it here, so we don't have to check for that.
  1764. //
  1765. // oddly, the compiler isn't smart enough to do this on its own
  1766. if ( *reinterpret_cast<const unsigned *>(&m_invDxCorners) == 0 ||
  1767. *reinterpret_cast<const unsigned *>(&m_invDyCorners) == 0 )
  1768. return m_neZ;
  1769. #else
  1770. if (m_invDxCorners == 0.0f || m_invDyCorners == 0.0f)
  1771. return m_neZ;
  1772. #endif
  1773. float u = (x - m_nwCorner.x) * m_invDxCorners;
  1774. float v = (y - m_nwCorner.y) * m_invDyCorners;
  1775. // clamp Z values to (x,y) volume
  1776. u = fsel( u, u, 0 ); // u >= 0 ? u : 0
  1777. u = fsel( u - 1.0f, 1.0f, u ); // u >= 1 ? 1 : u
  1778. v = fsel( v, v, 0 ); // v >= 0 ? v : 0
  1779. v = fsel( v - 1.0f, 1.0f, v ); // v >= 1 ? 1 : v
  1780. float northZ = m_nwCorner.z + u * (m_neZ - m_nwCorner.z);
  1781. float southZ = m_swZ + u * (m_seCorner.z - m_swZ);
  1782. return northZ + v * (southZ - northZ);
  1783. }
  1784. //--------------------------------------------------------------------------------------------------------------
  1785. /**
  1786. * Return closest point to 'pos' on 'area'.
  1787. * Returned point is in 'close'.
  1788. */
  1789. void CNavArea::GetClosestPointOnArea( const Vector * RESTRICT pPos, Vector *close ) const RESTRICT
  1790. {
  1791. float x, y, z;
  1792. // Using fsel rather than compares, as much faster on 360 [7/28/2008 tom]
  1793. x = fsel( pPos->x - m_nwCorner.x, pPos->x, m_nwCorner.x );
  1794. x = fsel( x - m_seCorner.x, m_seCorner.x, x );
  1795. y = fsel( pPos->y - m_nwCorner.y, pPos->y, m_nwCorner.y );
  1796. y = fsel( y - m_seCorner.y, m_seCorner.y, y );
  1797. z = GetZ( x, y );
  1798. close->Init( x, y, z );
  1799. }
  1800. //--------------------------------------------------------------------------------------------------------------
  1801. /**
  1802. * Return shortest distance squared between point and this area
  1803. */
  1804. float CNavArea::GetDistanceSquaredToPoint( const Vector &pos ) const
  1805. {
  1806. if (pos.x < m_nwCorner.x)
  1807. {
  1808. if (pos.y < m_nwCorner.y)
  1809. {
  1810. // position is north-west of area
  1811. return (m_nwCorner - pos).LengthSqr();
  1812. }
  1813. else if (pos.y > m_seCorner.y)
  1814. {
  1815. // position is south-west of area
  1816. Vector d;
  1817. d.x = m_nwCorner.x - pos.x;
  1818. d.y = m_seCorner.y - pos.y;
  1819. d.z = m_swZ - pos.z;
  1820. return d.LengthSqr();
  1821. }
  1822. else
  1823. {
  1824. // position is west of area
  1825. float d = m_nwCorner.x - pos.x;
  1826. return d * d;
  1827. }
  1828. }
  1829. else if (pos.x > m_seCorner.x)
  1830. {
  1831. if (pos.y < m_nwCorner.y)
  1832. {
  1833. // position is north-east of area
  1834. Vector d;
  1835. d.x = m_seCorner.x - pos.x;
  1836. d.y = m_nwCorner.y - pos.y;
  1837. d.z = m_neZ - pos.z;
  1838. return d.LengthSqr();
  1839. }
  1840. else if (pos.y > m_seCorner.y)
  1841. {
  1842. // position is south-east of area
  1843. return (m_seCorner - pos).LengthSqr();
  1844. }
  1845. else
  1846. {
  1847. // position is east of area
  1848. float d = pos.x - m_seCorner.x;
  1849. return d * d;
  1850. }
  1851. }
  1852. else if (pos.y < m_nwCorner.y)
  1853. {
  1854. // position is north of area
  1855. float d = m_nwCorner.y - pos.y;
  1856. return d * d;
  1857. }
  1858. else if (pos.y > m_seCorner.y)
  1859. {
  1860. // position is south of area
  1861. float d = pos.y - m_seCorner.y;
  1862. return d * d;
  1863. }
  1864. else
  1865. {
  1866. // position is inside of 2D extent of area - find delta Z
  1867. float z = GetZ( pos );
  1868. float d = z - pos.z;
  1869. return d * d;
  1870. }
  1871. }
  1872. //--------------------------------------------------------------------------------------------------------------
  1873. CNavArea *CNavArea::GetRandomAdjacentArea( NavDirType dir ) const
  1874. {
  1875. int count = m_connect[ dir ].Count();
  1876. int which = RandomInt( 0, count-1 );
  1877. int i = 0;
  1878. FOR_EACH_VEC( m_connect[ dir ], it )
  1879. {
  1880. if (i == which)
  1881. return m_connect[ dir ][ it ].area;
  1882. ++i;
  1883. }
  1884. return NULL;
  1885. }
  1886. //--------------------------------------------------------------------------------------------------------------
  1887. // Build a vector of all adjacent areas
  1888. void CNavArea::CollectAdjacentAreas( CUtlVector< CNavArea * > *adjVector ) const
  1889. {
  1890. for( int d=0; d<NUM_DIRECTIONS; ++d )
  1891. {
  1892. for( int i=0; i<m_connect[d].Count(); ++i )
  1893. {
  1894. adjVector->AddToTail( m_connect[d].Element(i).area );
  1895. }
  1896. }
  1897. }
  1898. //--------------------------------------------------------------------------------------------------------------
  1899. /**
  1900. * Compute "portal" between two adjacent areas.
  1901. * Return center of portal opening, and half-width defining sides of portal from center.
  1902. * NOTE: center->z is unset.
  1903. */
  1904. void CNavArea::ComputePortal( const CNavArea *to, NavDirType dir, Vector *center, float *halfWidth ) const
  1905. {
  1906. if ( dir == NORTH || dir == SOUTH )
  1907. {
  1908. if ( dir == NORTH )
  1909. {
  1910. center->y = m_nwCorner.y;
  1911. }
  1912. else
  1913. {
  1914. center->y = m_seCorner.y;
  1915. }
  1916. float left = MAX( m_nwCorner.x, to->m_nwCorner.x );
  1917. float right = MIN( m_seCorner.x, to->m_seCorner.x );
  1918. // clamp to our extent in case areas are disjoint
  1919. if ( left < m_nwCorner.x )
  1920. {
  1921. left = m_nwCorner.x;
  1922. }
  1923. else if ( left > m_seCorner.x )
  1924. {
  1925. left = m_seCorner.x;
  1926. }
  1927. if ( right < m_nwCorner.x )
  1928. {
  1929. right = m_nwCorner.x;
  1930. }
  1931. else if ( right > m_seCorner.x )
  1932. {
  1933. right = m_seCorner.x;
  1934. }
  1935. center->x = ( left + right )/2.0f;
  1936. *halfWidth = ( right - left )/2.0f;
  1937. }
  1938. else // EAST or WEST
  1939. {
  1940. if ( dir == WEST )
  1941. {
  1942. center->x = m_nwCorner.x;
  1943. }
  1944. else
  1945. {
  1946. center->x = m_seCorner.x;
  1947. }
  1948. float top = MAX( m_nwCorner.y, to->m_nwCorner.y );
  1949. float bottom = MIN( m_seCorner.y, to->m_seCorner.y );
  1950. // clamp to our extent in case areas are disjoint
  1951. if ( top < m_nwCorner.y )
  1952. {
  1953. top = m_nwCorner.y;
  1954. }
  1955. else if ( top > m_seCorner.y )
  1956. {
  1957. top = m_seCorner.y;
  1958. }
  1959. if ( bottom < m_nwCorner.y )
  1960. {
  1961. bottom = m_nwCorner.y;
  1962. }
  1963. else if ( bottom > m_seCorner.y )
  1964. {
  1965. bottom = m_seCorner.y;
  1966. }
  1967. center->y = (top + bottom)/2.0f;
  1968. *halfWidth = (bottom - top)/2.0f;
  1969. }
  1970. center->z = GetZ( center->x, center->y );
  1971. }
  1972. //--------------------------------------------------------------------------------------------------------------
  1973. // compute largest portal to adjacent area, returning direction
  1974. NavDirType CNavArea::ComputeLargestPortal( const CNavArea *to, Vector *center, float *halfWidth ) const
  1975. {
  1976. NavDirType bestDir = NUM_DIRECTIONS;
  1977. Vector bestCenter( vec3_origin );
  1978. float bestHalfWidth = 0.0f;
  1979. Vector centerDir = to->GetCenter() - GetCenter();
  1980. for ( int i=0; i<NUM_DIRECTIONS; ++i )
  1981. {
  1982. NavDirType testDir = (NavDirType)i;
  1983. Vector testCenter;
  1984. float testHalfWidth;
  1985. // Make sure we're not picking the opposite direction
  1986. switch ( testDir )
  1987. {
  1988. case NORTH: // -y
  1989. if ( centerDir.y >= 0.0f )
  1990. continue;
  1991. break;
  1992. case SOUTH: // +y
  1993. if ( centerDir.y <= 0.0f )
  1994. continue;
  1995. break;
  1996. case WEST: // -x
  1997. if ( centerDir.x >= 0.0f )
  1998. continue;
  1999. break;
  2000. case EAST: // +x
  2001. if ( centerDir.x <= 0.0f )
  2002. continue;
  2003. break;
  2004. }
  2005. ComputePortal( to, testDir, &testCenter, &testHalfWidth );
  2006. if ( testHalfWidth > bestHalfWidth )
  2007. {
  2008. bestDir = testDir;
  2009. bestCenter = testCenter;
  2010. bestHalfWidth = testHalfWidth;
  2011. }
  2012. }
  2013. *center = bestCenter;
  2014. *halfWidth = bestHalfWidth;
  2015. return bestDir;
  2016. }
  2017. //--------------------------------------------------------------------------------------------------------------
  2018. /**
  2019. * Compute closest point within the "portal" between to adjacent areas.
  2020. */
  2021. void CNavArea::ComputeClosestPointInPortal( const CNavArea *to, NavDirType dir, const Vector &fromPos, Vector *closePos ) const
  2022. {
  2023. // const float margin = 0.0f; //GenerationStepSize/2.0f; // causes trouble with very small/narrow nav areas
  2024. const float margin = GenerationStepSize;
  2025. if ( dir == NORTH || dir == SOUTH )
  2026. {
  2027. if ( dir == NORTH )
  2028. {
  2029. closePos->y = m_nwCorner.y;
  2030. }
  2031. else
  2032. {
  2033. closePos->y = m_seCorner.y;
  2034. }
  2035. float left = MAX( m_nwCorner.x, to->m_nwCorner.x );
  2036. float right = MIN( m_seCorner.x, to->m_seCorner.x );
  2037. // clamp to our extent in case areas are disjoint
  2038. // no good - need to push into to area for margins
  2039. /*
  2040. if (left < m_nwCorner.x)
  2041. left = m_nwCorner.x;
  2042. else if (left > m_seCorner.x)
  2043. left = m_seCorner.x;
  2044. if (right < m_nwCorner.x)
  2045. right = m_nwCorner.x;
  2046. else if (right > m_seCorner.x)
  2047. right = m_seCorner.x;
  2048. */
  2049. // keep margin if against edge
  2050. /// @todo Need better check whether edge is outer edge or not - partial overlap is missed
  2051. float leftMargin = ( to->IsEdge( WEST ) ) ? ( left + margin ) : left;
  2052. float rightMargin = ( to->IsEdge( EAST ) ) ? ( right - margin ) : right;
  2053. // if area is narrow, margins may have crossed
  2054. if ( leftMargin > rightMargin )
  2055. {
  2056. // use midline
  2057. float mid = ( left + right )/2.0f;
  2058. leftMargin = mid;
  2059. rightMargin = mid;
  2060. }
  2061. // limit x to within portal
  2062. if ( fromPos.x < leftMargin )
  2063. {
  2064. closePos->x = leftMargin;
  2065. }
  2066. else if ( fromPos.x > rightMargin )
  2067. {
  2068. closePos->x = rightMargin;
  2069. }
  2070. else
  2071. {
  2072. closePos->x = fromPos.x;
  2073. }
  2074. }
  2075. else // EAST or WEST
  2076. {
  2077. if ( dir == WEST )
  2078. {
  2079. closePos->x = m_nwCorner.x;
  2080. }
  2081. else
  2082. {
  2083. closePos->x = m_seCorner.x;
  2084. }
  2085. float top = MAX( m_nwCorner.y, to->m_nwCorner.y );
  2086. float bottom = MIN( m_seCorner.y, to->m_seCorner.y );
  2087. // clamp to our extent in case areas are disjoint
  2088. // no good - need to push into to area for margins
  2089. /*
  2090. if (top < m_nwCorner.y)
  2091. top = m_nwCorner.y;
  2092. else if (top > m_seCorner.y)
  2093. top = m_seCorner.y;
  2094. if (bottom < m_nwCorner.y)
  2095. bottom = m_nwCorner.y;
  2096. else if (bottom > m_seCorner.y)
  2097. bottom = m_seCorner.y;
  2098. */
  2099. // keep margin if against edge
  2100. float topMargin = ( to->IsEdge( NORTH ) ) ? ( top + margin ) : top;
  2101. float bottomMargin = ( to->IsEdge( SOUTH ) ) ? ( bottom - margin ) : bottom;
  2102. // if area is narrow, margins may have crossed
  2103. if ( topMargin > bottomMargin )
  2104. {
  2105. // use midline
  2106. float mid = ( top + bottom )/2.0f;
  2107. topMargin = mid;
  2108. bottomMargin = mid;
  2109. }
  2110. // limit y to within portal
  2111. if ( fromPos.y < topMargin )
  2112. {
  2113. closePos->y = topMargin;
  2114. }
  2115. else if ( fromPos.y > bottomMargin )
  2116. {
  2117. closePos->y = bottomMargin;
  2118. }
  2119. else
  2120. {
  2121. closePos->y = fromPos.y;
  2122. }
  2123. }
  2124. closePos->z = GetZ( closePos->x, closePos->y );
  2125. }
  2126. //--------------------------------------------------------------------------------------------------------------
  2127. /**
  2128. * Return true if the given area and 'other' share a colinear edge (ie: no drop-down or step/jump/climb)
  2129. */
  2130. bool CNavArea::IsContiguous( const CNavArea *other ) const
  2131. {
  2132. VPROF_BUDGET( "CNavArea::IsContiguous", "NextBot" );
  2133. // find which side it is connected on
  2134. int dir;
  2135. for( dir=0; dir<NUM_DIRECTIONS; ++dir )
  2136. {
  2137. if ( IsConnected( other, (NavDirType)dir ) )
  2138. break;
  2139. }
  2140. if ( dir == NUM_DIRECTIONS )
  2141. return false;
  2142. Vector myEdge;
  2143. float halfWidth;
  2144. ComputePortal( other, (NavDirType)dir, &myEdge, &halfWidth );
  2145. Vector otherEdge;
  2146. other->ComputePortal( this, OppositeDirection( (NavDirType)dir ), &otherEdge, &halfWidth );
  2147. // must use stepheight because rough terrain can have gaps/cracks between adjacent nav areas
  2148. return ( myEdge - otherEdge ).IsLengthLessThan( StepHeight );
  2149. }
  2150. //--------------------------------------------------------------------------------------------------------------
  2151. /**
  2152. * Return height change between edges of adjacent nav areas (not actual underlying ground)
  2153. */
  2154. float CNavArea::ComputeAdjacentConnectionHeightChange( const CNavArea *destinationArea ) const
  2155. {
  2156. VPROF_BUDGET( "CNavArea::ComputeAdjacentConnectionHeightChange", "NextBot" );
  2157. // find which side it is connected on
  2158. int dir;
  2159. for( dir=0; dir<NUM_DIRECTIONS; ++dir )
  2160. {
  2161. if ( IsConnected( destinationArea, (NavDirType)dir ) )
  2162. break;
  2163. }
  2164. if ( dir == NUM_DIRECTIONS )
  2165. return FLT_MAX;
  2166. Vector myEdge;
  2167. float halfWidth;
  2168. ComputePortal( destinationArea, (NavDirType)dir, &myEdge, &halfWidth );
  2169. Vector otherEdge;
  2170. destinationArea->ComputePortal( this, OppositeDirection( (NavDirType)dir ), &otherEdge, &halfWidth );
  2171. return otherEdge.z - myEdge.z;
  2172. }
  2173. //--------------------------------------------------------------------------------------------------------------
  2174. /**
  2175. * Return true if there are no bi-directional links on the given side
  2176. */
  2177. bool CNavArea::IsEdge( NavDirType dir ) const
  2178. {
  2179. FOR_EACH_VEC( m_connect[ dir ], it )
  2180. {
  2181. const NavConnect connect = m_connect[ dir ][ it ];
  2182. if (connect.area->IsConnected( this, OppositeDirection( dir ) ))
  2183. return false;
  2184. }
  2185. return true;
  2186. }
  2187. //--------------------------------------------------------------------------------------------------------------
  2188. /**
  2189. * Return direction from this area to the given point
  2190. */
  2191. NavDirType CNavArea::ComputeDirection( Vector *point ) const
  2192. {
  2193. if (point->x >= m_nwCorner.x && point->x <= m_seCorner.x)
  2194. {
  2195. if (point->y < m_nwCorner.y)
  2196. return NORTH;
  2197. else if (point->y > m_seCorner.y)
  2198. return SOUTH;
  2199. }
  2200. else if (point->y >= m_nwCorner.y && point->y <= m_seCorner.y)
  2201. {
  2202. if (point->x < m_nwCorner.x)
  2203. return WEST;
  2204. else if (point->x > m_seCorner.x)
  2205. return EAST;
  2206. }
  2207. // find closest direction
  2208. Vector to = *point - m_center;
  2209. if (fabs(to.x) > fabs(to.y))
  2210. {
  2211. if (to.x > 0.0f)
  2212. return EAST;
  2213. return WEST;
  2214. }
  2215. else
  2216. {
  2217. if (to.y > 0.0f)
  2218. return SOUTH;
  2219. return NORTH;
  2220. }
  2221. return NUM_DIRECTIONS;
  2222. }
  2223. //--------------------------------------------------------------------------------------------------------------
  2224. bool CNavArea::GetCornerHotspot( NavCornerType corner, Vector hotspot[NUM_CORNERS] ) const
  2225. {
  2226. Vector nw = GetCorner( NORTH_WEST );
  2227. Vector ne = GetCorner( NORTH_EAST );
  2228. Vector sw = GetCorner( SOUTH_WEST );
  2229. Vector se = GetCorner( SOUTH_EAST );
  2230. float size = 9.0f;
  2231. size = MIN( size, GetSizeX()/3 ); // make sure the hotspot doesn't extend outside small areas
  2232. size = MIN( size, GetSizeY()/3 );
  2233. switch ( corner )
  2234. {
  2235. case NORTH_WEST:
  2236. hotspot[0] = nw;
  2237. hotspot[1] = hotspot[0] + Vector( size, 0, 0 );
  2238. hotspot[2] = hotspot[0] + Vector( size, size, 0 );
  2239. hotspot[3] = hotspot[0] + Vector( 0, size, 0 );
  2240. break;
  2241. case NORTH_EAST:
  2242. hotspot[0] = ne;
  2243. hotspot[1] = hotspot[0] + Vector( -size, 0, 0 );
  2244. hotspot[2] = hotspot[0] + Vector( -size, size, 0 );
  2245. hotspot[3] = hotspot[0] + Vector( 0, size, 0 );
  2246. break;
  2247. case SOUTH_WEST:
  2248. hotspot[0] = sw;
  2249. hotspot[1] = hotspot[0] + Vector( size, 0, 0 );
  2250. hotspot[2] = hotspot[0] + Vector( size, -size, 0 );
  2251. hotspot[3] = hotspot[0] + Vector( 0, -size, 0 );
  2252. break;
  2253. case SOUTH_EAST:
  2254. hotspot[0] = se;
  2255. hotspot[1] = hotspot[0] + Vector( -size, 0, 0 );
  2256. hotspot[2] = hotspot[0] + Vector( -size, -size, 0 );
  2257. hotspot[3] = hotspot[0] + Vector( 0, -size, 0 );
  2258. break;
  2259. default:
  2260. return false;
  2261. }
  2262. for ( int i=1; i<NUM_CORNERS; ++i )
  2263. {
  2264. hotspot[i].z = GetZ( hotspot[i] );
  2265. }
  2266. Vector eyePos, eyeForward;
  2267. TheNavMesh->GetEditVectors( &eyePos, &eyeForward );
  2268. Ray_t ray;
  2269. ray.Init( eyePos, eyePos + 10000.0f * eyeForward, vec3_origin, vec3_origin );
  2270. float dist = IntersectRayWithTriangle( ray, hotspot[0], hotspot[1], hotspot[2], false );
  2271. if ( dist > 0 )
  2272. {
  2273. return true;
  2274. }
  2275. dist = IntersectRayWithTriangle( ray, hotspot[2], hotspot[3], hotspot[0], false );
  2276. if ( dist > 0 )
  2277. {
  2278. return true;
  2279. }
  2280. return false;
  2281. }
  2282. //--------------------------------------------------------------------------------------------------------------
  2283. NavCornerType CNavArea::GetCornerUnderCursor( void ) const
  2284. {
  2285. Vector eyePos, eyeForward;
  2286. TheNavMesh->GetEditVectors( &eyePos, &eyeForward );
  2287. for ( int i=0; i<NUM_CORNERS; ++i )
  2288. {
  2289. Vector hotspot[NUM_CORNERS];
  2290. if ( GetCornerHotspot( (NavCornerType)i, hotspot ) )
  2291. {
  2292. return (NavCornerType)i;
  2293. }
  2294. }
  2295. return NUM_CORNERS;
  2296. }
  2297. //--------------------------------------------------------------------------------------------------------------
  2298. /**
  2299. * Draw area for debugging
  2300. */
  2301. void CNavArea::Draw( void ) const
  2302. {
  2303. NavEditColor color;
  2304. bool useAttributeColors = true;
  2305. const float DebugDuration = NDEBUG_PERSIST_TILL_NEXT_SERVER;
  2306. if ( TheNavMesh->IsEditMode( CNavMesh::PLACE_PAINTING ) )
  2307. {
  2308. useAttributeColors = false;
  2309. if ( m_place == UNDEFINED_PLACE )
  2310. {
  2311. color = NavNoPlaceColor;
  2312. }
  2313. else if ( TheNavMesh->GetNavPlace() == m_place )
  2314. {
  2315. color = NavSamePlaceColor;
  2316. }
  2317. else
  2318. {
  2319. color = NavDifferentPlaceColor;
  2320. }
  2321. }
  2322. else
  2323. {
  2324. // normal edit mode
  2325. if ( this == TheNavMesh->GetMarkedArea() )
  2326. {
  2327. useAttributeColors = false;
  2328. color = NavMarkedColor;
  2329. }
  2330. else if ( this == TheNavMesh->GetSelectedArea() )
  2331. {
  2332. color = NavSelectedColor;
  2333. }
  2334. else
  2335. {
  2336. color = NavNormalColor;
  2337. }
  2338. }
  2339. if ( IsDegenerate() )
  2340. {
  2341. static IntervalTimer blink;
  2342. static bool blinkOn = false;
  2343. if (blink.GetElapsedTime() > 1.0f)
  2344. {
  2345. blink.Reset();
  2346. blinkOn = !blinkOn;
  2347. }
  2348. useAttributeColors = false;
  2349. if (blinkOn)
  2350. color = NavDegenerateFirstColor;
  2351. else
  2352. color = NavDegenerateSecondColor;
  2353. NDebugOverlay::Text( GetCenter(), UTIL_VarArgs( "Degenerate area %d", GetID() ), true, DebugDuration );
  2354. }
  2355. Vector nw, ne, sw, se;
  2356. nw = m_nwCorner;
  2357. se = m_seCorner;
  2358. ne.x = se.x;
  2359. ne.y = nw.y;
  2360. ne.z = m_neZ;
  2361. sw.x = nw.x;
  2362. sw.y = se.y;
  2363. sw.z = m_swZ;
  2364. if ( nav_show_light_intensity.GetBool() )
  2365. {
  2366. for ( int i=0; i<NUM_CORNERS; ++i )
  2367. {
  2368. Vector pos = GetCorner( (NavCornerType)i );
  2369. Vector end = pos;
  2370. float lightIntensity = GetLightIntensity(pos);
  2371. end.z += HumanHeight*lightIntensity;
  2372. lightIntensity *= 255; // for color
  2373. NDebugOverlay::Line( end, pos, lightIntensity, lightIntensity, MAX( 192, lightIntensity ), true, DebugDuration );
  2374. }
  2375. }
  2376. int bgcolor[4];
  2377. if ( 4 == sscanf( nav_area_bgcolor.GetString(), "%d %d %d %d", &(bgcolor[0]), &(bgcolor[1]), &(bgcolor[2]), &(bgcolor[3]) ) )
  2378. {
  2379. for ( int i=0; i<4; ++i )
  2380. bgcolor[i] = clamp( bgcolor[i], 0, 255 );
  2381. if ( bgcolor[3] > 0 )
  2382. {
  2383. const Vector offset( 0, 0, 0.8f );
  2384. NDebugOverlay::Triangle( nw+offset, se+offset, ne+offset, bgcolor[0], bgcolor[1], bgcolor[2], bgcolor[3], true, DebugDuration );
  2385. NDebugOverlay::Triangle( se+offset, nw+offset, sw+offset, bgcolor[0], bgcolor[1], bgcolor[2], bgcolor[3], true, DebugDuration );
  2386. }
  2387. }
  2388. const float inset = 0.2f;
  2389. nw.x += inset;
  2390. nw.y += inset;
  2391. ne.x -= inset;
  2392. ne.y += inset;
  2393. sw.x += inset;
  2394. sw.y -= inset;
  2395. se.x -= inset;
  2396. se.y -= inset;
  2397. if ( GetAttributes() & NAV_MESH_TRANSIENT )
  2398. {
  2399. NavDrawDashedLine( nw, ne, color );
  2400. NavDrawDashedLine( ne, se, color );
  2401. NavDrawDashedLine( se, sw, color );
  2402. NavDrawDashedLine( sw, nw, color );
  2403. }
  2404. else
  2405. {
  2406. NavDrawLine( nw, ne, color );
  2407. NavDrawLine( ne, se, color );
  2408. NavDrawLine( se, sw, color );
  2409. NavDrawLine( sw, nw, color );
  2410. }
  2411. if ( this == TheNavMesh->GetMarkedArea() && TheNavMesh->m_markedCorner != NUM_CORNERS )
  2412. {
  2413. Vector p[NUM_CORNERS];
  2414. GetCornerHotspot( TheNavMesh->m_markedCorner, p );
  2415. NavDrawLine( p[1], p[2], NavMarkedColor );
  2416. NavDrawLine( p[2], p[3], NavMarkedColor );
  2417. }
  2418. if ( this != TheNavMesh->GetMarkedArea() && this == TheNavMesh->GetSelectedArea() && TheNavMesh->IsEditMode( CNavMesh::NORMAL ) )
  2419. {
  2420. NavCornerType bestCorner = GetCornerUnderCursor();
  2421. Vector p[NUM_CORNERS];
  2422. if ( GetCornerHotspot( bestCorner, p ) )
  2423. {
  2424. NavDrawLine( p[1], p[2], NavSelectedColor );
  2425. NavDrawLine( p[2], p[3], NavSelectedColor );
  2426. }
  2427. }
  2428. if (GetAttributes() & NAV_MESH_CROUCH)
  2429. {
  2430. if ( useAttributeColors )
  2431. color = NavAttributeCrouchColor;
  2432. NavDrawLine( nw, se, color );
  2433. }
  2434. if (GetAttributes() & NAV_MESH_JUMP)
  2435. {
  2436. if ( useAttributeColors )
  2437. color = NavAttributeJumpColor;
  2438. if ( !(GetAttributes() & NAV_MESH_CROUCH) )
  2439. {
  2440. NavDrawLine( nw, se, color );
  2441. }
  2442. NavDrawLine( ne, sw, color );
  2443. }
  2444. if (GetAttributes() & NAV_MESH_PRECISE)
  2445. {
  2446. if ( useAttributeColors )
  2447. color = NavAttributePreciseColor;
  2448. float size = 8.0f;
  2449. Vector up( m_center.x, m_center.y - size, m_center.z );
  2450. Vector down( m_center.x, m_center.y + size, m_center.z );
  2451. NavDrawLine( up, down, color );
  2452. Vector left( m_center.x - size, m_center.y, m_center.z );
  2453. Vector right( m_center.x + size, m_center.y, m_center.z );
  2454. NavDrawLine( left, right, color );
  2455. }
  2456. if (GetAttributes() & NAV_MESH_NO_JUMP)
  2457. {
  2458. if ( useAttributeColors )
  2459. color = NavAttributeNoJumpColor;
  2460. float size = 8.0f;
  2461. Vector up( m_center.x, m_center.y - size, m_center.z );
  2462. Vector down( m_center.x, m_center.y + size, m_center.z );
  2463. Vector left( m_center.x - size, m_center.y, m_center.z );
  2464. Vector right( m_center.x + size, m_center.y, m_center.z );
  2465. NavDrawLine( up, right, color );
  2466. NavDrawLine( right, down, color );
  2467. NavDrawLine( down, left, color );
  2468. NavDrawLine( left, up, color );
  2469. }
  2470. if (GetAttributes() & NAV_MESH_STAIRS)
  2471. {
  2472. if ( useAttributeColors )
  2473. color = NavAttributeStairColor;
  2474. float northZ = ( GetCorner( NORTH_WEST ).z + GetCorner( NORTH_EAST ).z ) / 2.0f;
  2475. float southZ = ( GetCorner( SOUTH_WEST ).z + GetCorner( SOUTH_EAST ).z ) / 2.0f;
  2476. float westZ = ( GetCorner( NORTH_WEST ).z + GetCorner( SOUTH_WEST ).z ) / 2.0f;
  2477. float eastZ = ( GetCorner( NORTH_EAST ).z + GetCorner( SOUTH_EAST ).z ) / 2.0f;
  2478. float deltaEastWest = abs( westZ - eastZ );
  2479. float deltaNorthSouth = abs( northZ - southZ );
  2480. float stepSize = StepHeight / 2.0f;
  2481. float t;
  2482. if ( deltaEastWest > deltaNorthSouth )
  2483. {
  2484. float inc = stepSize / GetSizeX();
  2485. for( t = 0.0f; t <= 1.0f; t += inc )
  2486. {
  2487. float x = m_nwCorner.x + t * GetSizeX();
  2488. NavDrawLine( Vector( x, m_nwCorner.y, GetZ( x, m_nwCorner.y ) ),
  2489. Vector( x, m_seCorner.y, GetZ( x, m_seCorner.y ) ),
  2490. color );
  2491. }
  2492. }
  2493. else
  2494. {
  2495. float inc = stepSize / GetSizeY();
  2496. for( t = 0.0f; t <= 1.0f; t += inc )
  2497. {
  2498. float y = m_nwCorner.y + t * GetSizeY();
  2499. NavDrawLine( Vector( m_nwCorner.x, y, GetZ( m_nwCorner.x, y ) ),
  2500. Vector( m_seCorner.x, y, GetZ( m_seCorner.x, y ) ),
  2501. color );
  2502. }
  2503. }
  2504. }
  2505. // Stop is represented by an octagon
  2506. if (GetAttributes() & NAV_MESH_STOP)
  2507. {
  2508. if ( useAttributeColors )
  2509. color = NavAttributeStopColor;
  2510. float dist = 8.0f;
  2511. float length = dist/2.5f;
  2512. Vector start, end;
  2513. start = m_center + Vector( dist, -length, 0 );
  2514. end = m_center + Vector( dist, length, 0 );
  2515. NavDrawLine( start, end, color );
  2516. start = m_center + Vector( dist, length, 0 );
  2517. end = m_center + Vector( length, dist, 0 );
  2518. NavDrawLine( start, end, color );
  2519. start = m_center + Vector( -dist, -length, 0 );
  2520. end = m_center + Vector( -dist, length, 0 );
  2521. NavDrawLine( start, end, color );
  2522. start = m_center + Vector( -dist, length, 0 );
  2523. end = m_center + Vector( -length, dist, 0 );
  2524. NavDrawLine( start, end, color );
  2525. start = m_center + Vector( -length, dist, 0 );
  2526. end = m_center + Vector( length, dist, 0 );
  2527. NavDrawLine( start, end, color );
  2528. start = m_center + Vector( -dist, -length, 0 );
  2529. end = m_center + Vector( -length, -dist, 0 );
  2530. NavDrawLine( start, end, color );
  2531. start = m_center + Vector( -length, -dist, 0 );
  2532. end = m_center + Vector( length, -dist, 0 );
  2533. NavDrawLine( start, end, color );
  2534. start = m_center + Vector( length, -dist, 0 );
  2535. end = m_center + Vector( dist, -length, 0 );
  2536. NavDrawLine( start, end, color );
  2537. }
  2538. // Walk is represented by an arrow
  2539. if (GetAttributes() & NAV_MESH_WALK)
  2540. {
  2541. if ( useAttributeColors )
  2542. color = NavAttributeWalkColor;
  2543. float size = 8.0f;
  2544. NavDrawHorizontalArrow( m_center + Vector( -size, 0, 0 ), m_center + Vector( size, 0, 0 ), 4, color );
  2545. }
  2546. // Walk is represented by a double arrow
  2547. if (GetAttributes() & NAV_MESH_RUN)
  2548. {
  2549. if ( useAttributeColors )
  2550. color = NavAttributeRunColor;
  2551. float size = 8.0f;
  2552. float dist = 4.0f;
  2553. NavDrawHorizontalArrow( m_center + Vector( -size, dist, 0 ), m_center + Vector( size, dist, 0 ), 4, color );
  2554. NavDrawHorizontalArrow( m_center + Vector( -size, -dist, 0 ), m_center + Vector( size, -dist, 0 ), 4, color );
  2555. }
  2556. // Avoid is represented by an exclamation point
  2557. if (GetAttributes() & NAV_MESH_AVOID)
  2558. {
  2559. if ( useAttributeColors )
  2560. color = NavAttributeAvoidColor;
  2561. float topHeight = 8.0f;
  2562. float topWidth = 3.0f;
  2563. float bottomHeight = 3.0f;
  2564. float bottomWidth = 2.0f;
  2565. NavDrawTriangle( m_center, m_center + Vector( -topWidth, topHeight, 0 ), m_center + Vector( +topWidth, topHeight, 0 ), color );
  2566. NavDrawTriangle( m_center + Vector( 0, -bottomHeight, 0 ), m_center + Vector( -bottomWidth, -bottomHeight*2, 0 ), m_center + Vector( bottomWidth, -bottomHeight*2, 0 ), color );
  2567. }
  2568. if ( IsBlocked( TEAM_ANY ) || HasAvoidanceObstacle() || IsDamaging() )
  2569. {
  2570. NavEditColor color = (IsBlocked( TEAM_ANY ) && ( m_attributeFlags & NAV_MESH_NAV_BLOCKER ) ) ? NavBlockedByFuncNavBlockerColor : NavBlockedByDoorColor;
  2571. const float blockedInset = 4.0f;
  2572. nw.x += blockedInset;
  2573. nw.y += blockedInset;
  2574. ne.x -= blockedInset;
  2575. ne.y += blockedInset;
  2576. sw.x += blockedInset;
  2577. sw.y -= blockedInset;
  2578. se.x -= blockedInset;
  2579. se.y -= blockedInset;
  2580. NavDrawLine( nw, ne, color );
  2581. NavDrawLine( ne, se, color );
  2582. NavDrawLine( se, sw, color );
  2583. NavDrawLine( sw, nw, color );
  2584. }
  2585. }
  2586. //--------------------------------------------------------------------------------------------------------
  2587. /**
  2588. * Draw area as a filled rect of the given color
  2589. */
  2590. void CNavArea::DrawFilled( int r, int g, int b, int a, float deltaT, bool noDepthTest, float margin ) const
  2591. {
  2592. Vector nw = GetCorner( NORTH_WEST ) + Vector( margin, margin, 0.0f );
  2593. Vector ne = GetCorner( NORTH_EAST ) + Vector( -margin, margin, 0.0f );
  2594. Vector sw = GetCorner( SOUTH_WEST ) + Vector( margin, -margin, 0.0f );
  2595. Vector se = GetCorner( SOUTH_EAST ) + Vector( -margin, -margin, 0.0f );
  2596. if ( a == 0 )
  2597. {
  2598. NDebugOverlay::Line( nw, ne, r, g, b, true, deltaT );
  2599. NDebugOverlay::Line( nw, sw, r, g, b, true, deltaT );
  2600. NDebugOverlay::Line( sw, se, r, g, b, true, deltaT );
  2601. NDebugOverlay::Line( se, ne, r, g, b, true, deltaT );
  2602. }
  2603. else
  2604. {
  2605. NDebugOverlay::Triangle( nw, se, ne, r, g, b, a, noDepthTest, deltaT );
  2606. NDebugOverlay::Triangle( se, nw, sw, r, g, b, a, noDepthTest, deltaT );
  2607. }
  2608. // backside
  2609. // NDebugOverlay::Triangle( nw, ne, se, r, g, b, a, noDepthTest, deltaT );
  2610. // NDebugOverlay::Triangle( se, sw, nw, r, g, b, a, noDepthTest, deltaT );
  2611. }
  2612. //--------------------------------------------------------------------------------------------------------
  2613. void CNavArea::DrawSelectedSet( const Vector &shift ) const
  2614. {
  2615. const float deltaT = NDEBUG_PERSIST_TILL_NEXT_SERVER;
  2616. int r = s_selectedSetColor.r();
  2617. int g = s_selectedSetColor.g();
  2618. int b = s_selectedSetColor.b();
  2619. int a = s_selectedSetColor.a();
  2620. Vector nw = GetCorner( NORTH_WEST ) + shift;
  2621. Vector ne = GetCorner( NORTH_EAST ) + shift;
  2622. Vector sw = GetCorner( SOUTH_WEST ) + shift;
  2623. Vector se = GetCorner( SOUTH_EAST ) + shift;
  2624. NDebugOverlay::Triangle( nw, se, ne, r, g, b, a, true, deltaT );
  2625. NDebugOverlay::Triangle( se, nw, sw, r, g, b, a, true, deltaT );
  2626. r = s_selectedSetBorderColor.r();
  2627. g = s_selectedSetBorderColor.g();
  2628. b = s_selectedSetBorderColor.b();
  2629. NDebugOverlay::Line( nw, ne, r, g, b, true, deltaT );
  2630. NDebugOverlay::Line( nw, sw, r, g, b, true, deltaT );
  2631. NDebugOverlay::Line( sw, se, r, g, b, true, deltaT );
  2632. NDebugOverlay::Line( se, ne, r, g, b, true, deltaT );
  2633. }
  2634. //--------------------------------------------------------------------------------------------------------
  2635. void CNavArea::DrawDragSelectionSet( Color &dragSelectionSetColor ) const
  2636. {
  2637. const float deltaT = NDEBUG_PERSIST_TILL_NEXT_SERVER;
  2638. int r = dragSelectionSetColor.r();
  2639. int g = dragSelectionSetColor.g();
  2640. int b = dragSelectionSetColor.b();
  2641. int a = dragSelectionSetColor.a();
  2642. Vector nw = GetCorner( NORTH_WEST );
  2643. Vector ne = GetCorner( NORTH_EAST );
  2644. Vector sw = GetCorner( SOUTH_WEST );
  2645. Vector se = GetCorner( SOUTH_EAST );
  2646. NDebugOverlay::Triangle( nw, se, ne, r, g, b, a, true, deltaT );
  2647. NDebugOverlay::Triangle( se, nw, sw, r, g, b, a, true, deltaT );
  2648. r = s_dragSelectionSetBorderColor.r();
  2649. g = s_dragSelectionSetBorderColor.g();
  2650. b = s_dragSelectionSetBorderColor.b();
  2651. NDebugOverlay::Line( nw, ne, r, g, b, true, deltaT );
  2652. NDebugOverlay::Line( nw, sw, r, g, b, true, deltaT );
  2653. NDebugOverlay::Line( sw, se, r, g, b, true, deltaT );
  2654. NDebugOverlay::Line( se, ne, r, g, b, true, deltaT );
  2655. }
  2656. //--------------------------------------------------------------------------------------------------------------
  2657. /**
  2658. * Draw navigation areas and edit them
  2659. */
  2660. void CNavArea::DrawHidingSpots( void ) const
  2661. {
  2662. const HidingSpotVector *hidingSpots = GetHidingSpots();
  2663. FOR_EACH_VEC( (*hidingSpots), it )
  2664. {
  2665. const HidingSpot *spot = (*hidingSpots)[ it ];
  2666. NavEditColor color;
  2667. if (spot->IsIdealSniperSpot())
  2668. {
  2669. color = NavIdealSniperColor;
  2670. }
  2671. else if (spot->IsGoodSniperSpot())
  2672. {
  2673. color = NavGoodSniperColor;
  2674. }
  2675. else if (spot->HasGoodCover())
  2676. {
  2677. color = NavGoodCoverColor;
  2678. }
  2679. else
  2680. {
  2681. color = NavExposedColor;
  2682. }
  2683. NavDrawLine( spot->GetPosition(), spot->GetPosition() + Vector( 0, 0, 50 ), color );
  2684. }
  2685. }
  2686. //--------------------------------------------------------------------------------------------------------------
  2687. /**
  2688. * Draw ourselves and adjacent areas
  2689. */
  2690. void CNavArea::DrawConnectedAreas( void ) const
  2691. {
  2692. int i;
  2693. CBasePlayer *player = UTIL_GetListenServerHost();
  2694. if (player == NULL)
  2695. return;
  2696. // draw self
  2697. if (TheNavMesh->IsEditMode( CNavMesh::PLACE_PAINTING ))
  2698. {
  2699. Draw();
  2700. }
  2701. else
  2702. {
  2703. Draw();
  2704. DrawHidingSpots();
  2705. }
  2706. // draw connected ladders
  2707. {
  2708. FOR_EACH_VEC( m_ladder[ CNavLadder::LADDER_UP ], it )
  2709. {
  2710. CNavLadder *ladder = m_ladder[ CNavLadder::LADDER_UP ][ it ].ladder;
  2711. ladder->DrawLadder();
  2712. if ( !ladder->IsConnected( this, CNavLadder::LADDER_DOWN ) )
  2713. {
  2714. NavDrawLine( m_center, ladder->m_bottom + Vector( 0, 0, GenerationStepSize ), NavConnectedOneWayColor );
  2715. }
  2716. }
  2717. }
  2718. {
  2719. FOR_EACH_VEC( m_ladder[ CNavLadder::LADDER_DOWN ], it )
  2720. {
  2721. CNavLadder *ladder = m_ladder[ CNavLadder::LADDER_DOWN ][ it ].ladder;
  2722. ladder->DrawLadder();
  2723. if ( !ladder->IsConnected( this, CNavLadder::LADDER_UP ) )
  2724. {
  2725. NavDrawLine( m_center, ladder->m_top, NavConnectedOneWayColor );
  2726. }
  2727. }
  2728. }
  2729. // draw connected areas
  2730. for( i=0; i<NUM_DIRECTIONS; ++i )
  2731. {
  2732. NavDirType dir = (NavDirType)i;
  2733. int count = GetAdjacentCount( dir );
  2734. for( int a=0; a<count; ++a )
  2735. {
  2736. CNavArea *adj = GetAdjacentArea( dir, a );
  2737. adj->Draw();
  2738. if ( !TheNavMesh->IsEditMode( CNavMesh::PLACE_PAINTING ) )
  2739. {
  2740. adj->DrawHidingSpots();
  2741. Vector from, to;
  2742. Vector hookPos;
  2743. float halfWidth;
  2744. float size = 5.0f;
  2745. ComputePortal( adj, dir, &hookPos, &halfWidth );
  2746. switch( dir )
  2747. {
  2748. case NORTH:
  2749. from = hookPos + Vector( 0.0f, size, 0.0f );
  2750. to = hookPos + Vector( 0.0f, -size, 0.0f );
  2751. break;
  2752. case SOUTH:
  2753. from = hookPos + Vector( 0.0f, -size, 0.0f );
  2754. to = hookPos + Vector( 0.0f, size, 0.0f );
  2755. break;
  2756. case EAST:
  2757. from = hookPos + Vector( -size, 0.0f, 0.0f );
  2758. to = hookPos + Vector( +size, 0.0f, 0.0f );
  2759. break;
  2760. case WEST:
  2761. from = hookPos + Vector( size, 0.0f, 0.0f );
  2762. to = hookPos + Vector( -size, 0.0f, 0.0f );
  2763. break;
  2764. }
  2765. from.z = GetZ( from );
  2766. to.z = adj->GetZ( to );
  2767. Vector drawTo;
  2768. adj->GetClosestPointOnArea( to, &drawTo );
  2769. if ( nav_show_contiguous.GetBool() )
  2770. {
  2771. if ( IsContiguous( adj ) )
  2772. NavDrawLine( from, drawTo, NavConnectedContiguous );
  2773. else
  2774. NavDrawLine( from, drawTo, NavConnectedNonContiguous );
  2775. }
  2776. else
  2777. {
  2778. if ( adj->IsConnected( this, OppositeDirection( dir ) ) )
  2779. NavDrawLine( from, drawTo, NavConnectedTwoWaysColor );
  2780. else
  2781. NavDrawLine( from, drawTo, NavConnectedOneWayColor );
  2782. }
  2783. }
  2784. }
  2785. }
  2786. }
  2787. //--------------------------------------------------------------------------------------------------------------
  2788. /**
  2789. * Add to open list in decreasing value order
  2790. */
  2791. void CNavArea::AddToOpenList( void )
  2792. {
  2793. Assert( (m_openList && m_openList->m_prevOpen == NULL) || m_openList == NULL );
  2794. if ( IsOpen() )
  2795. {
  2796. // already on list
  2797. return;
  2798. }
  2799. // mark as being on open list for quick check
  2800. m_openMarker = m_masterMarker;
  2801. // if list is empty, add and return
  2802. if ( m_openList == NULL )
  2803. {
  2804. m_openList = this;
  2805. m_openListTail = this;
  2806. this->m_prevOpen = NULL;
  2807. this->m_nextOpen = NULL;
  2808. return;
  2809. }
  2810. // insert self in ascending cost order
  2811. CNavArea *area, *last = NULL;
  2812. for( area = m_openList; area; area = area->m_nextOpen )
  2813. {
  2814. if ( GetTotalCost() < area->GetTotalCost() )
  2815. {
  2816. break;
  2817. }
  2818. last = area;
  2819. }
  2820. if ( area )
  2821. {
  2822. // insert before this area
  2823. this->m_prevOpen = area->m_prevOpen;
  2824. if ( this->m_prevOpen )
  2825. {
  2826. this->m_prevOpen->m_nextOpen = this;
  2827. }
  2828. else
  2829. {
  2830. m_openList = this;
  2831. }
  2832. this->m_nextOpen = area;
  2833. area->m_prevOpen = this;
  2834. }
  2835. else
  2836. {
  2837. // append to end of list
  2838. last->m_nextOpen = this;
  2839. this->m_prevOpen = last;
  2840. this->m_nextOpen = NULL;
  2841. m_openListTail = this;
  2842. }
  2843. Assert( (m_openList && m_openList->m_prevOpen == NULL) || m_openList == NULL );
  2844. }
  2845. //--------------------------------------------------------------------------------------------------------------
  2846. /**
  2847. * Add to tail of the open list
  2848. */
  2849. void CNavArea::AddToOpenListTail( void )
  2850. {
  2851. Assert( (m_openList && m_openList->m_prevOpen == NULL) || m_openList == NULL );
  2852. if ( IsOpen() )
  2853. {
  2854. // already on list
  2855. return;
  2856. }
  2857. // mark as being on open list for quick check
  2858. m_openMarker = m_masterMarker;
  2859. // if list is empty, add and return
  2860. if ( m_openList == NULL )
  2861. {
  2862. m_openList = this;
  2863. m_openListTail = this;
  2864. this->m_prevOpen = NULL;
  2865. this->m_nextOpen = NULL;
  2866. Assert( (m_openList && m_openList->m_prevOpen == NULL) || m_openList == NULL );
  2867. return;
  2868. }
  2869. // append to end of list
  2870. m_openListTail->m_nextOpen = this;
  2871. this->m_prevOpen = m_openListTail;
  2872. this->m_nextOpen = NULL;
  2873. m_openListTail = this;
  2874. Assert( (m_openList && m_openList->m_prevOpen == NULL) || m_openList == NULL );
  2875. }
  2876. //--------------------------------------------------------------------------------------------------------------
  2877. /**
  2878. * A smaller value has been found, update this area on the open list
  2879. * @todo "bubbling" does unnecessary work, since the order of all other nodes will be unchanged - only this node is altered
  2880. */
  2881. void CNavArea::UpdateOnOpenList( void )
  2882. {
  2883. // since value can only decrease, bubble this area up from current spot
  2884. while( m_prevOpen && this->GetTotalCost() < m_prevOpen->GetTotalCost() )
  2885. {
  2886. // swap position with predecessor
  2887. CNavArea *other = m_prevOpen;
  2888. CNavArea *before = other->m_prevOpen;
  2889. CNavArea *after = this->m_nextOpen;
  2890. this->m_nextOpen = other;
  2891. this->m_prevOpen = before;
  2892. other->m_prevOpen = this;
  2893. other->m_nextOpen = after;
  2894. if ( before )
  2895. {
  2896. before->m_nextOpen = this;
  2897. }
  2898. else
  2899. {
  2900. m_openList = this;
  2901. }
  2902. if ( after )
  2903. {
  2904. after->m_prevOpen = other;
  2905. }
  2906. else
  2907. {
  2908. m_openListTail = this;
  2909. }
  2910. }
  2911. }
  2912. //--------------------------------------------------------------------------------------------------------------
  2913. void CNavArea::RemoveFromOpenList( void )
  2914. {
  2915. if ( m_openMarker == 0 )
  2916. {
  2917. // not on the list
  2918. return;
  2919. }
  2920. if ( m_prevOpen )
  2921. {
  2922. m_prevOpen->m_nextOpen = m_nextOpen;
  2923. }
  2924. else
  2925. {
  2926. m_openList = m_nextOpen;
  2927. }
  2928. if ( m_nextOpen )
  2929. {
  2930. m_nextOpen->m_prevOpen = m_prevOpen;
  2931. }
  2932. else
  2933. {
  2934. m_openListTail = m_prevOpen;
  2935. }
  2936. // zero is an invalid marker
  2937. m_openMarker = 0;
  2938. }
  2939. //--------------------------------------------------------------------------------------------------------------
  2940. /**
  2941. * Clears the open and closed lists for a new search
  2942. */
  2943. void CNavArea::ClearSearchLists( void )
  2944. {
  2945. // effectively clears all open list pointers and closed flags
  2946. CNavArea::MakeNewMarker();
  2947. m_openList = NULL;
  2948. m_openListTail = NULL;
  2949. }
  2950. //--------------------------------------------------------------------------------------------------------------
  2951. void CNavArea::SetCorner( NavCornerType corner, const Vector& newPosition )
  2952. {
  2953. switch( corner )
  2954. {
  2955. case NORTH_WEST:
  2956. m_nwCorner = newPosition;
  2957. break;
  2958. case NORTH_EAST:
  2959. m_seCorner.x = newPosition.x;
  2960. m_nwCorner.y = newPosition.y;
  2961. m_neZ = newPosition.z;
  2962. break;
  2963. case SOUTH_WEST:
  2964. m_nwCorner.x = newPosition.x;
  2965. m_seCorner.y = newPosition.y;
  2966. m_swZ = newPosition.z;
  2967. break;
  2968. case SOUTH_EAST:
  2969. m_seCorner = newPosition;
  2970. break;
  2971. default:
  2972. {
  2973. Vector oldPosition = GetCenter();
  2974. Vector delta = newPosition - oldPosition;
  2975. m_nwCorner += delta;
  2976. m_seCorner += delta;
  2977. m_neZ += delta.z;
  2978. m_swZ += delta.z;
  2979. }
  2980. }
  2981. m_center.x = (m_nwCorner.x + m_seCorner.x)/2.0f;
  2982. m_center.y = (m_nwCorner.y + m_seCorner.y)/2.0f;
  2983. m_center.z = (m_nwCorner.z + m_seCorner.z)/2.0f;
  2984. if ( ( m_seCorner.x - m_nwCorner.x ) > 0.0f && ( m_seCorner.y - m_nwCorner.y ) > 0.0f )
  2985. {
  2986. m_invDxCorners = 1.0f / ( m_seCorner.x - m_nwCorner.x );
  2987. m_invDyCorners = 1.0f / ( m_seCorner.y - m_nwCorner.y );
  2988. }
  2989. else
  2990. {
  2991. m_invDxCorners = m_invDyCorners = 0;
  2992. }
  2993. CalcDebugID();
  2994. }
  2995. //--------------------------------------------------------------------------------------------------------------
  2996. /**
  2997. * Returns true if an existing hiding spot is too close to given position
  2998. */
  2999. bool CNavArea::IsHidingSpotCollision( const Vector &pos ) const
  3000. {
  3001. const float collisionRange = 30.0f;
  3002. FOR_EACH_VEC( m_hidingSpots, it )
  3003. {
  3004. const HidingSpot *spot = m_hidingSpots[ it ];
  3005. if ((spot->GetPosition() - pos).IsLengthLessThan( collisionRange ))
  3006. return true;
  3007. }
  3008. return false;
  3009. }
  3010. //--------------------------------------------------------------------------------------------------------------
  3011. bool IsHidingSpotInCover( const Vector &spot )
  3012. {
  3013. int coverCount = 0;
  3014. trace_t result;
  3015. Vector from = spot;
  3016. from.z += HalfHumanHeight;
  3017. Vector to;
  3018. // if we are crouched underneath something, that counts as good cover
  3019. to = from + Vector( 0, 0, 20.0f );
  3020. UTIL_TraceLine( from, to, MASK_NPCSOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &result );
  3021. if (result.fraction != 1.0f)
  3022. return true;
  3023. const float coverRange = 100.0f;
  3024. const float inc = M_PI / 8.0f;
  3025. for( float angle = 0.0f; angle < 2.0f * M_PI; angle += inc )
  3026. {
  3027. to = from + Vector( coverRange * (float)cos(angle), coverRange * (float)sin(angle), HalfHumanHeight );
  3028. UTIL_TraceLine( from, to, MASK_NPCSOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &result );
  3029. // if traceline hit something, it hit "cover"
  3030. if (result.fraction != 1.0f)
  3031. ++coverCount;
  3032. }
  3033. // if more than half of the circle has no cover, the spot is not "in cover"
  3034. const int halfCover = 8;
  3035. if (coverCount < halfCover)
  3036. return false;
  3037. return true;
  3038. }
  3039. //--------------------------------------------------------------------------------------------------------------
  3040. /**
  3041. * Finds the hiding spot position in a corner's area. If the typical inset is off the nav area (small
  3042. * hand-constructed areas), it tries to fit the position inside the area.
  3043. */
  3044. static Vector FindPositionInArea( CNavArea *area, NavCornerType corner )
  3045. {
  3046. int multX = 1, multY = 1;
  3047. switch ( corner )
  3048. {
  3049. case NORTH_WEST:
  3050. break;
  3051. case NORTH_EAST:
  3052. multX = -1;
  3053. break;
  3054. case SOUTH_WEST:
  3055. multY = -1;
  3056. break;
  3057. case SOUTH_EAST:
  3058. multX = -1;
  3059. multY = -1;
  3060. break;
  3061. }
  3062. const float offset = 12.5f;
  3063. Vector cornerPos = area->GetCorner( corner );
  3064. // Try the basic inset
  3065. Vector pos = cornerPos + Vector( offset*multX, offset*multY, 0.0f );
  3066. if ( !area->IsOverlapping( pos ) )
  3067. {
  3068. // Try pulling the Y offset to the area's center
  3069. pos = cornerPos + Vector( offset*multX, area->GetSizeY()*0.5f*multY, 0.0f );
  3070. if ( !area->IsOverlapping( pos ) )
  3071. {
  3072. // Try pulling the X offset to the area's center
  3073. pos = cornerPos + Vector( area->GetSizeX()*0.5f*multX, offset*multY, 0.0f );
  3074. if ( !area->IsOverlapping( pos ) )
  3075. {
  3076. // Try pulling the X and Y offsets to the area's center
  3077. pos = cornerPos + Vector( area->GetSizeX()*0.5f*multX, area->GetSizeY()*0.5f*multY, 0.0f );
  3078. if ( !area->IsOverlapping( pos ) )
  3079. {
  3080. AssertMsg( false, "A Hiding Spot can't be placed on its area at (%.0f %.0f %.0f)", cornerPos.x, cornerPos.y, cornerPos.z );
  3081. // Just pull the position to a small offset
  3082. pos = cornerPos + Vector( 1.0f*multX, 1.0f*multY, 0.0f );
  3083. if ( !area->IsOverlapping( pos ) )
  3084. {
  3085. // Nothing is working (degenerate area?), so just put it directly on the corner
  3086. pos = cornerPos;
  3087. }
  3088. }
  3089. }
  3090. }
  3091. }
  3092. return pos;
  3093. }
  3094. //--------------------------------------------------------------------------------------------------------------
  3095. /**
  3096. * Analyze local area neighborhood to find "hiding spots" for this area
  3097. */
  3098. void CNavArea::ComputeHidingSpots( void )
  3099. {
  3100. struct
  3101. {
  3102. float lo, hi;
  3103. }
  3104. extent;
  3105. m_hidingSpots.PurgeAndDeleteElements();
  3106. // "jump areas" cannot have hiding spots
  3107. if ( GetAttributes() & NAV_MESH_JUMP )
  3108. return;
  3109. // "don't hide areas" cannot have hiding spots
  3110. if ( GetAttributes() & NAV_MESH_DONT_HIDE )
  3111. return;
  3112. int cornerCount[NUM_CORNERS];
  3113. for( int i=0; i<NUM_CORNERS; ++i )
  3114. cornerCount[i] = 0;
  3115. const float cornerSize = 20.0f;
  3116. // for each direction, find extents of adjacent areas along the wall
  3117. for( int d=0; d<NUM_DIRECTIONS; ++d )
  3118. {
  3119. extent.lo = 999999.9f;
  3120. extent.hi = -999999.9f;
  3121. bool isHoriz = (d == NORTH || d == SOUTH) ? true : false;
  3122. FOR_EACH_VEC( m_connect[d], it )
  3123. {
  3124. NavConnect connect = m_connect[ d ][ it ];
  3125. // if connection is only one-way, it's a "jump down" connection (ie: a discontinuity that may mean cover)
  3126. // ignore it
  3127. if (connect.area->IsConnected( this, OppositeDirection( static_cast<NavDirType>( d ) ) ) == false)
  3128. continue;
  3129. // ignore jump areas
  3130. if (connect.area->GetAttributes() & NAV_MESH_JUMP)
  3131. continue;
  3132. if (isHoriz)
  3133. {
  3134. if (connect.area->m_nwCorner.x < extent.lo)
  3135. extent.lo = connect.area->m_nwCorner.x;
  3136. if (connect.area->m_seCorner.x > extent.hi)
  3137. extent.hi = connect.area->m_seCorner.x;
  3138. }
  3139. else
  3140. {
  3141. if (connect.area->m_nwCorner.y < extent.lo)
  3142. extent.lo = connect.area->m_nwCorner.y;
  3143. if (connect.area->m_seCorner.y > extent.hi)
  3144. extent.hi = connect.area->m_seCorner.y;
  3145. }
  3146. }
  3147. switch( d )
  3148. {
  3149. case NORTH:
  3150. if (extent.lo - m_nwCorner.x >= cornerSize)
  3151. ++cornerCount[ NORTH_WEST ];
  3152. if (m_seCorner.x - extent.hi >= cornerSize)
  3153. ++cornerCount[ NORTH_EAST ];
  3154. break;
  3155. case SOUTH:
  3156. if (extent.lo - m_nwCorner.x >= cornerSize)
  3157. ++cornerCount[ SOUTH_WEST ];
  3158. if (m_seCorner.x - extent.hi >= cornerSize)
  3159. ++cornerCount[ SOUTH_EAST ];
  3160. break;
  3161. case EAST:
  3162. if (extent.lo - m_nwCorner.y >= cornerSize)
  3163. ++cornerCount[ NORTH_EAST ];
  3164. if (m_seCorner.y - extent.hi >= cornerSize)
  3165. ++cornerCount[ SOUTH_EAST ];
  3166. break;
  3167. case WEST:
  3168. if (extent.lo - m_nwCorner.y >= cornerSize)
  3169. ++cornerCount[ NORTH_WEST ];
  3170. if (m_seCorner.y - extent.hi >= cornerSize)
  3171. ++cornerCount[ SOUTH_WEST ];
  3172. break;
  3173. }
  3174. }
  3175. for ( int c=0; c<NUM_CORNERS; ++c )
  3176. {
  3177. // if a corner count is 2, then it really is a corner (walls on both sides)
  3178. if (cornerCount[c] == 2)
  3179. {
  3180. Vector pos = FindPositionInArea( this, (NavCornerType)c );
  3181. if ( !c || !IsHidingSpotCollision( pos ) )
  3182. {
  3183. HidingSpot *spot = TheNavMesh->CreateHidingSpot();
  3184. spot->SetPosition( pos );
  3185. spot->SetFlags( IsHidingSpotInCover( pos ) ? HidingSpot::IN_COVER : HidingSpot::EXPOSED );
  3186. m_hidingSpots.AddToTail( spot );
  3187. }
  3188. }
  3189. }
  3190. }
  3191. //--------------------------------------------------------------------------------------------------------------
  3192. /**
  3193. * Determine how much walkable area we can see from the spot, and how far away we can see.
  3194. */
  3195. void ClassifySniperSpot( HidingSpot *spot )
  3196. {
  3197. Vector eye = spot->GetPosition();
  3198. CNavArea *hidingArea = TheNavMesh->GetNavArea( spot->GetPosition() );
  3199. if (hidingArea && (hidingArea->GetAttributes() & NAV_MESH_STAND))
  3200. {
  3201. // we will be standing at this hiding spot
  3202. eye.z += HumanEyeHeight;
  3203. }
  3204. else
  3205. {
  3206. // we are crouching when at this hiding spot
  3207. eye.z += HumanCrouchEyeHeight;
  3208. }
  3209. Vector walkable;
  3210. trace_t result;
  3211. Extent sniperExtent;
  3212. float farthestRangeSq = 0.0f;
  3213. const float minSniperRangeSq = 1000.0f * 1000.0f;
  3214. bool found = false;
  3215. // to make compiler stop warning me
  3216. sniperExtent.lo = Vector( 0.0f, 0.0f, 0.0f );
  3217. sniperExtent.hi = Vector( 0.0f, 0.0f, 0.0f );
  3218. Extent areaExtent;
  3219. FOR_EACH_VEC( TheNavAreas, it )
  3220. {
  3221. CNavArea *area = TheNavAreas[ it ];
  3222. area->GetExtent( &areaExtent );
  3223. // scan this area
  3224. for( walkable.y = areaExtent.lo.y + GenerationStepSize/2.0f; walkable.y < areaExtent.hi.y; walkable.y += GenerationStepSize )
  3225. {
  3226. for( walkable.x = areaExtent.lo.x + GenerationStepSize/2.0f; walkable.x < areaExtent.hi.x; walkable.x += GenerationStepSize )
  3227. {
  3228. walkable.z = area->GetZ( walkable ) + HalfHumanHeight;
  3229. // check line of sight
  3230. UTIL_TraceLine( eye, walkable, CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_PLAYERCLIP, NULL, COLLISION_GROUP_NONE, &result );
  3231. if (result.fraction == 1.0f && !result.startsolid)
  3232. {
  3233. // can see this spot
  3234. // keep track of how far we can see
  3235. float rangeSq = (eye - walkable).LengthSqr();
  3236. if (rangeSq > farthestRangeSq)
  3237. {
  3238. farthestRangeSq = rangeSq;
  3239. if (rangeSq >= minSniperRangeSq)
  3240. {
  3241. // this is a sniper spot
  3242. // determine how good of a sniper spot it is by keeping track of the snipable area
  3243. if (found)
  3244. {
  3245. if (walkable.x < sniperExtent.lo.x)
  3246. sniperExtent.lo.x = walkable.x;
  3247. if (walkable.x > sniperExtent.hi.x)
  3248. sniperExtent.hi.x = walkable.x;
  3249. if (walkable.y < sniperExtent.lo.y)
  3250. sniperExtent.lo.y = walkable.y;
  3251. if (walkable.y > sniperExtent.hi.y)
  3252. sniperExtent.hi.y = walkable.y;
  3253. }
  3254. else
  3255. {
  3256. sniperExtent.lo = walkable;
  3257. sniperExtent.hi = walkable;
  3258. found = true;
  3259. }
  3260. }
  3261. }
  3262. }
  3263. }
  3264. }
  3265. }
  3266. if (found)
  3267. {
  3268. // if we can see a large snipable area, it is an "ideal" spot
  3269. float snipableArea = sniperExtent.Area();
  3270. const float minIdealSniperArea = 200.0f * 200.0f;
  3271. const float longSniperRangeSq = 1500.0f * 1500.0f;
  3272. if (snipableArea >= minIdealSniperArea || farthestRangeSq >= longSniperRangeSq)
  3273. spot->m_flags |= HidingSpot::IDEAL_SNIPER_SPOT;
  3274. else
  3275. spot->m_flags |= HidingSpot::GOOD_SNIPER_SPOT;
  3276. }
  3277. }
  3278. //--------------------------------------------------------------------------------------------------------------
  3279. /**
  3280. * Analyze local area neighborhood to find "sniper spots" for this area
  3281. */
  3282. void CNavArea::ComputeSniperSpots( void )
  3283. {
  3284. if (nav_quicksave.GetBool())
  3285. return;
  3286. FOR_EACH_VEC( m_hidingSpots, it )
  3287. {
  3288. HidingSpot *spot = m_hidingSpots[ it ];
  3289. ClassifySniperSpot( spot );
  3290. }
  3291. }
  3292. //--------------------------------------------------------------------------------------------------------------
  3293. /**
  3294. * Given the areas we are moving between, return the spots we will encounter
  3295. */
  3296. SpotEncounter *CNavArea::GetSpotEncounter( const CNavArea *from, const CNavArea *to )
  3297. {
  3298. if (from && to)
  3299. {
  3300. SpotEncounter *e;
  3301. FOR_EACH_VEC( m_spotEncounters, it )
  3302. {
  3303. e = m_spotEncounters[ it ];
  3304. if (e->from.area == from && e->to.area == to)
  3305. return e;
  3306. }
  3307. }
  3308. return NULL;
  3309. }
  3310. //--------------------------------------------------------------------------------------------------------------
  3311. /**
  3312. * Add spot encounter data when moving from area to area
  3313. */
  3314. void CNavArea::AddSpotEncounters( const CNavArea *from, NavDirType fromDir, const CNavArea *to, NavDirType toDir )
  3315. {
  3316. SpotEncounter *e = new SpotEncounter;
  3317. e->from.area = const_cast<CNavArea *>( from );
  3318. e->fromDir = fromDir;
  3319. e->to.area = const_cast<CNavArea *>( to );
  3320. e->toDir = toDir;
  3321. float halfWidth;
  3322. ComputePortal( to, toDir, &e->path.to, &halfWidth );
  3323. ComputePortal( from, fromDir, &e->path.from, &halfWidth );
  3324. const float eyeHeight = HumanEyeHeight;
  3325. e->path.from.z = from->GetZ( e->path.from ) + eyeHeight;
  3326. e->path.to.z = to->GetZ( e->path.to ) + eyeHeight;
  3327. // step along ray and track which spots can be seen
  3328. Vector dir = e->path.to - e->path.from;
  3329. float length = dir.NormalizeInPlace();
  3330. // create unique marker to flag used spots
  3331. HidingSpot::ChangeMasterMarker();
  3332. const float stepSize = 25.0f; // 50
  3333. const float seeSpotRange = 2000.0f; // 3000
  3334. trace_t result;
  3335. Vector eye, delta;
  3336. HidingSpot *spot;
  3337. SpotOrder spotOrder;
  3338. // step along path thru this area
  3339. bool done = false;
  3340. for( float along = 0.0f; !done; along += stepSize )
  3341. {
  3342. // make sure we check the endpoint of the path segment
  3343. if (along >= length)
  3344. {
  3345. along = length;
  3346. done = true;
  3347. }
  3348. // move the eyepoint along the path segment
  3349. eye = e->path.from + along * dir;
  3350. // check each hiding spot for visibility
  3351. FOR_EACH_VEC( TheHidingSpots, it )
  3352. {
  3353. spot = TheHidingSpots[ it ];
  3354. // only look at spots with cover (others are out in the open and easily seen)
  3355. if (!spot->HasGoodCover())
  3356. continue;
  3357. if (spot->IsMarked())
  3358. continue;
  3359. const Vector &spotPos = spot->GetPosition();
  3360. delta.x = spotPos.x - eye.x;
  3361. delta.y = spotPos.y - eye.y;
  3362. delta.z = (spotPos.z + eyeHeight) - eye.z;
  3363. // check if in range
  3364. if (delta.IsLengthGreaterThan( seeSpotRange ))
  3365. continue;
  3366. // check if we have LOS
  3367. // BOTPORT: ignore glass here
  3368. UTIL_TraceLine( eye, Vector( spotPos.x, spotPos.y, spotPos.z + HalfHumanHeight ), MASK_NPCSOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &result );
  3369. if (result.fraction != 1.0f)
  3370. continue;
  3371. // if spot is in front of us along our path, ignore it
  3372. delta.NormalizeInPlace();
  3373. float dot = DotProduct( dir, delta );
  3374. if (dot < 0.7071f && dot > -0.7071f)
  3375. {
  3376. // we only want to keep spots that BECOME visible as we walk past them
  3377. // therefore, skip ALL visible spots at the start of the path segment
  3378. if (along > 0.0f)
  3379. {
  3380. // add spot to encounter
  3381. spotOrder.spot = spot;
  3382. spotOrder.t = along/length;
  3383. e->spots.AddToTail( spotOrder );
  3384. }
  3385. }
  3386. // mark spot as encountered
  3387. spot->Mark();
  3388. }
  3389. }
  3390. // add encounter to list
  3391. m_spotEncounters.AddToTail( e );
  3392. }
  3393. //--------------------------------------------------------------------------------------------------------------
  3394. /**
  3395. * Compute "spot encounter" data. This is an ordered list of spots to look at
  3396. * for each possible path thru a nav area.
  3397. */
  3398. void CNavArea::ComputeSpotEncounters( void )
  3399. {
  3400. m_spotEncounters.RemoveAll();
  3401. if (nav_quicksave.GetBool())
  3402. return;
  3403. // for each adjacent area
  3404. for( int fromDir=0; fromDir<NUM_DIRECTIONS; ++fromDir )
  3405. {
  3406. FOR_EACH_VEC( m_connect[ fromDir ], it )
  3407. {
  3408. NavConnect *fromCon = &(m_connect[ fromDir ][ it ]);
  3409. // compute encounter data for path to each adjacent area
  3410. for( int toDir=0; toDir<NUM_DIRECTIONS; ++toDir )
  3411. {
  3412. FOR_EACH_VEC( m_connect[ toDir ], ot )
  3413. {
  3414. NavConnect *toCon = &(m_connect[ toDir ][ ot ]);
  3415. if (toCon == fromCon)
  3416. continue;
  3417. // just do our direction, as we'll loop around for other direction
  3418. AddSpotEncounters( fromCon->area, (NavDirType)fromDir, toCon->area, (NavDirType)toDir );
  3419. }
  3420. }
  3421. }
  3422. }
  3423. }
  3424. //--------------------------------------------------------------------------------------------------------------
  3425. /**
  3426. * Decay the danger values
  3427. */
  3428. void CNavArea::DecayDanger( void )
  3429. {
  3430. for( int i=0; i<MAX_NAV_TEAMS; ++i )
  3431. {
  3432. float deltaT = gpGlobals->curtime - m_dangerTimestamp[i];
  3433. float decayAmount = GetDangerDecayRate() * deltaT;
  3434. m_danger[i] -= decayAmount;
  3435. if (m_danger[i] < 0.0f)
  3436. m_danger[i] = 0.0f;
  3437. // update timestamp
  3438. m_dangerTimestamp[i] = gpGlobals->curtime;
  3439. }
  3440. }
  3441. //--------------------------------------------------------------------------------------------------------------
  3442. /**
  3443. * Increase the danger of this area for the given team
  3444. */
  3445. void CNavArea::IncreaseDanger( int teamID, float amount )
  3446. {
  3447. // before we add the new value, decay what's there
  3448. DecayDanger();
  3449. int teamIdx = teamID % MAX_NAV_TEAMS;
  3450. m_danger[ teamIdx ] += amount;
  3451. m_dangerTimestamp[ teamIdx ] = gpGlobals->curtime;
  3452. }
  3453. //--------------------------------------------------------------------------------------------------------------
  3454. /**
  3455. * Return the danger of this area (decays over time)
  3456. */
  3457. float CNavArea::GetDanger( int teamID )
  3458. {
  3459. DecayDanger();
  3460. int teamIdx = teamID % MAX_NAV_TEAMS;
  3461. return m_danger[ teamIdx ];
  3462. }
  3463. //--------------------------------------------------------------------------------------------------------------
  3464. /**
  3465. * Returns a 0..1 light intensity for the given point
  3466. */
  3467. float CNavArea::GetLightIntensity( const Vector &pos ) const
  3468. {
  3469. Vector testPos;
  3470. testPos.x = clamp( pos.x, m_nwCorner.x, m_seCorner.x );
  3471. testPos.y = clamp( pos.y, m_nwCorner.y, m_seCorner.y );
  3472. testPos.z = pos.z;
  3473. float dX = (testPos.x - m_nwCorner.x) / (m_seCorner.x - m_nwCorner.x);
  3474. float dY = (testPos.y - m_nwCorner.y) / (m_seCorner.y - m_nwCorner.y);
  3475. float northLight = m_lightIntensity[ NORTH_WEST ] * ( 1 - dX ) + m_lightIntensity[ NORTH_EAST ] * dX;
  3476. float southLight = m_lightIntensity[ SOUTH_WEST ] * ( 1 - dX ) + m_lightIntensity[ SOUTH_EAST ] * dX;
  3477. float light = northLight * ( 1 - dY ) + southLight * dY;
  3478. return light;
  3479. }
  3480. //--------------------------------------------------------------------------------------------------------------
  3481. /**
  3482. * Returns a 0..1 light intensity for the given point
  3483. */
  3484. float CNavArea::GetLightIntensity( float x, float y ) const
  3485. {
  3486. return GetLightIntensity( Vector( x, y, 0 ) );
  3487. }
  3488. //--------------------------------------------------------------------------------------------------------------
  3489. /**
  3490. * Returns a 0..1 light intensity averaged over the whole area
  3491. */
  3492. float CNavArea::GetLightIntensity( void ) const
  3493. {
  3494. float light = m_lightIntensity[ NORTH_WEST ];
  3495. light += m_lightIntensity[ NORTH_EAST ];
  3496. light += m_lightIntensity[ SOUTH_WEST];
  3497. light += m_lightIntensity[ SOUTH_EAST ];
  3498. return light / 4.0f;
  3499. }
  3500. //--------------------------------------------------------------------------------------------------------------
  3501. /**
  3502. * Compute light intensity at corners and center (requires client via listenserver)
  3503. */
  3504. bool CNavArea::ComputeLighting( void )
  3505. {
  3506. if ( engine->IsDedicatedServer() )
  3507. {
  3508. for ( int i=0; i<NUM_CORNERS; ++i )
  3509. {
  3510. m_lightIntensity[i] = 1.0f;
  3511. }
  3512. return true;
  3513. }
  3514. // Calculate light at the corners
  3515. for ( int i=0; i<NUM_CORNERS; ++i )
  3516. {
  3517. Vector pos = FindPositionInArea( this, (NavCornerType)i );
  3518. pos.z = GetZ( pos ) + HalfHumanHeight - StepHeight; // players light from their centers, and we light from slightly below that, to allow for low ceilings
  3519. float height;
  3520. if ( TheNavMesh->GetGroundHeight( pos, &height ) )
  3521. {
  3522. pos.z = height + HalfHumanHeight - StepHeight; // players light from their centers, and we light from slightly below that, to allow for low ceilings
  3523. }
  3524. Vector light( 0, 0, 0 );
  3525. // FIXMEL4DTOMAINMERGE
  3526. //if ( !engine->GetLightForPointListenServerOnly( pos, false, &light ) )
  3527. //{
  3528. //NDebugOverlay::Line( pos, pos + Vector( 0, 0, -100 ), 255, 0, 0, false, 100.0f );
  3529. // return false;
  3530. //}
  3531. Vector ambientColor;
  3532. // FIXMEL4DTOMAINMERGE
  3533. //if ( !GetTerrainAmbientLightAtPoint( pos, &ambientColor ) )
  3534. {
  3535. //NDebugOverlay::Line( pos, pos + Vector( 0, 0, -100 ), 255, 127, 0, false, 100.0f );
  3536. return false;
  3537. }
  3538. //NDebugOverlay::Line( pos, pos + Vector( 0, 0, -100 ), 0, 255, 127, false, 100.0f );
  3539. float ambientIntensity = ambientColor.x + ambientColor.y + ambientColor.z;
  3540. float lightIntensity = light.x + light.y + light.z;
  3541. lightIntensity = clamp( lightIntensity, 0.f, 1.f ); // sum can go well over 1.0, but it's the lower region we care about. if it's bright, we don't need to know *how* bright.
  3542. lightIntensity = MAX( lightIntensity, ambientIntensity );
  3543. m_lightIntensity[i] = lightIntensity;
  3544. }
  3545. return true;
  3546. }
  3547. //--------------------------------------------------------------------------------------------------------------
  3548. CON_COMMAND_F( nav_update_lighting, "Recomputes lighting values", FCVAR_CHEAT )
  3549. {
  3550. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  3551. return;
  3552. int numComputed = 0;
  3553. if ( args.ArgC() == 2 )
  3554. {
  3555. int areaID = atoi( args[1] );
  3556. CNavArea *area = TheNavMesh->GetNavAreaByID( areaID );
  3557. if ( area )
  3558. {
  3559. if ( area->ComputeLighting() )
  3560. {
  3561. ++numComputed;
  3562. }
  3563. }
  3564. }
  3565. else
  3566. {
  3567. FOR_EACH_VEC( TheNavAreas, index )
  3568. {
  3569. CNavArea *area = TheNavAreas[ index ];
  3570. if ( area->ComputeLighting() )
  3571. {
  3572. ++numComputed;
  3573. }
  3574. }
  3575. }
  3576. DevMsg( "Computed lighting for %d/%d areas\n", numComputed, TheNavAreas.Count() );
  3577. }
  3578. //--------------------------------------------------------------------------------------------------------------
  3579. /**
  3580. * Raise/lower a corner
  3581. */
  3582. void CNavArea::RaiseCorner( NavCornerType corner, int amount, bool raiseAdjacentCorners )
  3583. {
  3584. if ( corner == NUM_CORNERS )
  3585. {
  3586. RaiseCorner( NORTH_WEST, amount, raiseAdjacentCorners );
  3587. RaiseCorner( NORTH_EAST, amount, raiseAdjacentCorners );
  3588. RaiseCorner( SOUTH_WEST, amount, raiseAdjacentCorners );
  3589. RaiseCorner( SOUTH_EAST, amount, raiseAdjacentCorners );
  3590. return;
  3591. }
  3592. // Move the corner
  3593. switch (corner)
  3594. {
  3595. case NORTH_WEST:
  3596. m_nwCorner.z += amount;
  3597. break;
  3598. case NORTH_EAST:
  3599. m_neZ += amount;
  3600. break;
  3601. case SOUTH_WEST:
  3602. m_swZ += amount;
  3603. break;
  3604. case SOUTH_EAST:
  3605. m_seCorner.z += amount;
  3606. break;
  3607. }
  3608. // Recompute the center
  3609. m_center.x = (m_nwCorner.x + m_seCorner.x)/2.0f;
  3610. m_center.y = (m_nwCorner.y + m_seCorner.y)/2.0f;
  3611. m_center.z = (m_nwCorner.z + m_seCorner.z)/2.0f;
  3612. if ( ( m_seCorner.x - m_nwCorner.x ) > 0.0f && ( m_seCorner.y - m_nwCorner.y ) > 0.0f )
  3613. {
  3614. m_invDxCorners = 1.0f / ( m_seCorner.x - m_nwCorner.x );
  3615. m_invDyCorners = 1.0f / ( m_seCorner.y - m_nwCorner.y );
  3616. }
  3617. else
  3618. {
  3619. m_invDxCorners = m_invDyCorners = 0;
  3620. }
  3621. if ( !raiseAdjacentCorners || nav_corner_adjust_adjacent.GetFloat() <= 0.0f )
  3622. {
  3623. return;
  3624. }
  3625. // Find nearby areas that share the corner
  3626. CNavArea::MakeNewMarker();
  3627. Mark();
  3628. const float tolerance = nav_corner_adjust_adjacent.GetFloat();
  3629. Vector cornerPos = GetCorner( corner );
  3630. cornerPos.z -= amount; // use the pre-adjustment corner for adjacency checks
  3631. int gridX = TheNavMesh->WorldToGridX( cornerPos.x );
  3632. int gridY = TheNavMesh->WorldToGridY( cornerPos.y );
  3633. const int shift = 1; // try a 3x3 set of grids in case we're on the edge
  3634. for( int x = gridX - shift; x <= gridX + shift; ++x )
  3635. {
  3636. if (x < 0 || x >= TheNavMesh->m_gridSizeX)
  3637. continue;
  3638. for( int y = gridY - shift; y <= gridY + shift; ++y )
  3639. {
  3640. if (y < 0 || y >= TheNavMesh->m_gridSizeY)
  3641. continue;
  3642. NavAreaVector *areas = &TheNavMesh->m_grid[ x + y*TheNavMesh->m_gridSizeX ];
  3643. // find closest area in this cell
  3644. FOR_EACH_VEC( (*areas), it )
  3645. {
  3646. CNavArea *area = (*areas)[ it ];
  3647. // skip if we've already visited this area
  3648. if (area->IsMarked())
  3649. continue;
  3650. area->Mark();
  3651. Vector areaPos;
  3652. for ( int i=0; i<NUM_CORNERS; ++i )
  3653. {
  3654. areaPos = area->GetCorner( NavCornerType(i) );
  3655. if ( areaPos.DistTo( cornerPos ) < tolerance )
  3656. {
  3657. float heightDiff = (cornerPos.z + amount ) - areaPos.z;
  3658. area->RaiseCorner( NavCornerType(i), heightDiff, false );
  3659. }
  3660. }
  3661. }
  3662. }
  3663. }
  3664. }
  3665. //--------------------------------------------------------------------------------------------------------------
  3666. /**
  3667. * FindGroundZFromPoint walks from the start position to the end position in GenerationStepSize increments,
  3668. * checking the ground height along the way.
  3669. */
  3670. float FindGroundZFromPoint( const Vector& end, const Vector& start )
  3671. {
  3672. Vector step( 0, 0, StepHeight );
  3673. if ( fabs( end.x - start.x ) > fabs( end.y - start.y ) )
  3674. {
  3675. step.x = GenerationStepSize;
  3676. if ( end.x < start.x )
  3677. {
  3678. step.x = -step.x;
  3679. }
  3680. }
  3681. else
  3682. {
  3683. step.y = GenerationStepSize;
  3684. if ( end.y < start.y )
  3685. {
  3686. step.y = -step.y;
  3687. }
  3688. }
  3689. // step towards our end point
  3690. Vector point = start;
  3691. float z;
  3692. while ( point.AsVector2D().DistTo( end.AsVector2D() ) > GenerationStepSize )
  3693. {
  3694. point = point + step;
  3695. z = point.z;
  3696. if ( TheNavMesh->GetGroundHeight( point, &z ) )
  3697. {
  3698. point.z = z;
  3699. }
  3700. else
  3701. {
  3702. point.z -= step.z;
  3703. }
  3704. }
  3705. // now do the exact one once we're within GenerationStepSize of it
  3706. z = point.z + step.z;
  3707. point = end;
  3708. point.z = z;
  3709. if ( TheNavMesh->GetGroundHeight( point, &z ) )
  3710. {
  3711. point.z = z;
  3712. }
  3713. else
  3714. {
  3715. point.z -= step.z;
  3716. }
  3717. return point.z;
  3718. }
  3719. //--------------------------------------------------------------------------------------------------------------
  3720. /**
  3721. * Finds the Z value for a corner given two other corner points. This walks along the edges of the nav area
  3722. * in GenerationStepSize increments, to increase accuracy.
  3723. */
  3724. float FindGroundZ( const Vector& original, const Vector& corner1, const Vector& corner2 )
  3725. {
  3726. float first = FindGroundZFromPoint( original, corner1 );
  3727. float second = FindGroundZFromPoint( original, corner2 );
  3728. if ( fabs( first - second ) > StepHeight )
  3729. {
  3730. // approaching the point from the two directions didn't agree. Take the one closest to the original z.
  3731. if ( fabs( original.z - first ) > fabs( original.z - second ) )
  3732. {
  3733. return second;
  3734. }
  3735. else
  3736. {
  3737. return first;
  3738. }
  3739. }
  3740. return first;
  3741. }
  3742. //--------------------------------------------------------------------------------------------------------------
  3743. /**
  3744. * Places a corner (or all corners if corner == NUM_CORNERS) on the ground
  3745. */
  3746. void CNavArea::PlaceOnGround( NavCornerType corner, float inset )
  3747. {
  3748. trace_t result;
  3749. Vector from, to;
  3750. Vector nw = m_nwCorner + Vector ( inset, inset, 0 );
  3751. Vector se = m_seCorner + Vector ( -inset, -inset, 0 );
  3752. Vector ne, sw;
  3753. ne.x = se.x;
  3754. ne.y = nw.y;
  3755. ne.z = m_neZ;
  3756. sw.x = nw.x;
  3757. sw.y = se.y;
  3758. sw.z = m_swZ;
  3759. if ( corner == NORTH_WEST || corner == NUM_CORNERS )
  3760. {
  3761. float newZ = FindGroundZ( nw, ne, sw );
  3762. RaiseCorner( NORTH_WEST, newZ - nw.z );
  3763. }
  3764. if ( corner == NORTH_EAST || corner == NUM_CORNERS )
  3765. {
  3766. float newZ = FindGroundZ( ne, nw, se );
  3767. RaiseCorner( NORTH_EAST, newZ - ne.z );
  3768. }
  3769. if ( corner == SOUTH_WEST || corner == NUM_CORNERS )
  3770. {
  3771. float newZ = FindGroundZ( sw, nw, se );
  3772. RaiseCorner( SOUTH_WEST, newZ - sw.z );
  3773. }
  3774. if ( corner == SOUTH_EAST || corner == NUM_CORNERS )
  3775. {
  3776. float newZ = FindGroundZ( se, ne, sw );
  3777. RaiseCorner( SOUTH_EAST, newZ - se.z );
  3778. }
  3779. }
  3780. //--------------------------------------------------------------------------------------------------------------
  3781. /**
  3782. * Shift the nav area
  3783. */
  3784. void CNavArea::Shift( const Vector &shift )
  3785. {
  3786. m_nwCorner += shift;
  3787. m_seCorner += shift;
  3788. m_center += shift;
  3789. }
  3790. //--------------------------------------------------------------------------------------------------------------
  3791. static void CommandNavUpdateBlocked( void )
  3792. {
  3793. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  3794. return;
  3795. if ( TheNavMesh->GetMarkedArea() )
  3796. {
  3797. CNavArea *area = TheNavMesh->GetMarkedArea();
  3798. area->UpdateBlocked( true );
  3799. if ( area->IsBlocked( TEAM_ANY ) )
  3800. {
  3801. DevMsg( "Area #%d %s is blocked\n", area->GetID(), VecToString( area->GetCenter() + Vector( 0, 0, HalfHumanHeight ) ) );
  3802. }
  3803. }
  3804. else
  3805. {
  3806. float start = Plat_FloatTime();
  3807. CNavArea *blockedArea = NULL;
  3808. FOR_EACH_VEC( TheNavAreas, nit )
  3809. {
  3810. CNavArea *area = TheNavAreas[ nit ];
  3811. area->UpdateBlocked( true );
  3812. if ( area->IsBlocked( TEAM_ANY ) )
  3813. {
  3814. DevMsg( "Area #%d %s is blocked\n", area->GetID(), VecToString( area->GetCenter() + Vector( 0, 0, HalfHumanHeight ) ) );
  3815. if ( !blockedArea )
  3816. {
  3817. blockedArea = area;
  3818. }
  3819. }
  3820. }
  3821. float end = Plat_FloatTime();
  3822. float time = (end - start) * 1000.0f;
  3823. DevMsg( "nav_update_blocked took %2.2f ms\n", time );
  3824. if ( blockedArea )
  3825. {
  3826. CBasePlayer *player = UTIL_GetListenServerHost();
  3827. if ( player )
  3828. {
  3829. if ( ( player->IsDead() || player->IsObserver() ) && player->GetObserverMode() == OBS_MODE_ROAMING )
  3830. {
  3831. Vector origin = blockedArea->GetCenter() + Vector( 0, 0, 0.75f * HumanHeight );
  3832. UTIL_SetOrigin( player, origin );
  3833. }
  3834. }
  3835. }
  3836. }
  3837. }
  3838. static ConCommand nav_update_blocked( "nav_update_blocked", CommandNavUpdateBlocked, "Updates the blocked/unblocked status for every nav area.", FCVAR_GAMEDLL );
  3839. //--------------------------------------------------------------------------------------------------------
  3840. bool CNavArea::IsBlocked( int teamID, bool ignoreNavBlockers ) const
  3841. {
  3842. if ( ignoreNavBlockers && ( m_attributeFlags & NAV_MESH_NAV_BLOCKER ) )
  3843. {
  3844. return false;
  3845. }
  3846. #ifdef TERROR
  3847. if ( ( teamID == TEAM_SURVIVOR ) && ( m_attributeFlags & CNavArea::NAV_PLAYERCLIP ) )
  3848. return true;
  3849. #endif
  3850. if ( teamID == TEAM_ANY )
  3851. {
  3852. bool isBlocked = false;
  3853. for ( int i=0; i<MAX_NAV_TEAMS; ++i )
  3854. {
  3855. isBlocked |= m_isBlocked[ i ];
  3856. }
  3857. return isBlocked;
  3858. }
  3859. int teamIdx = teamID % MAX_NAV_TEAMS;
  3860. return m_isBlocked[ teamIdx ];
  3861. }
  3862. //--------------------------------------------------------------------------------------------------------
  3863. void CNavArea::MarkAsBlocked( int teamID, CBaseEntity *blocker, bool bGenerateEvent )
  3864. {
  3865. if ( blocker && blocker->ClassMatches( "func_nav_blocker" ) )
  3866. {
  3867. m_attributeFlags |= NAV_MESH_NAV_BLOCKER;
  3868. }
  3869. bool wasBlocked = false;
  3870. if ( teamID == TEAM_ANY )
  3871. {
  3872. for ( int i=0; i<MAX_NAV_TEAMS; ++i )
  3873. {
  3874. wasBlocked |= m_isBlocked[ i ];
  3875. m_isBlocked[ i ] = true;
  3876. }
  3877. }
  3878. else
  3879. {
  3880. int teamIdx = teamID % MAX_NAV_TEAMS;
  3881. wasBlocked |= m_isBlocked[ teamIdx ];
  3882. m_isBlocked[ teamIdx ] = true;
  3883. }
  3884. if ( !wasBlocked )
  3885. {
  3886. if ( bGenerateEvent )
  3887. {
  3888. IGameEvent * event = gameeventmanager->CreateEvent( "nav_blocked" );
  3889. if ( event )
  3890. {
  3891. event->SetInt( "area", m_id );
  3892. event->SetInt( "blocked", 1 );
  3893. gameeventmanager->FireEvent( event );
  3894. }
  3895. }
  3896. if ( nav_debug_blocked.GetBool() )
  3897. {
  3898. if ( blocker )
  3899. {
  3900. ConColorMsg( Color( 0, 255, 128, 255 ), "%s %d blocked area %d\n", blocker->GetDebugName(), blocker->entindex(), GetID() );
  3901. }
  3902. else
  3903. {
  3904. ConColorMsg( Color( 0, 255, 128, 255 ), "non-entity blocked area %d\n", GetID() );
  3905. }
  3906. }
  3907. TheNavMesh->OnAreaBlocked( this );
  3908. }
  3909. else
  3910. {
  3911. if ( nav_debug_blocked.GetBool() )
  3912. {
  3913. if ( blocker )
  3914. {
  3915. ConColorMsg( Color( 0, 255, 128, 255 ), "DUPE: %s %d blocked area %d\n", blocker->GetDebugName(), blocker->entindex(), GetID() );
  3916. }
  3917. else
  3918. {
  3919. ConColorMsg( Color( 0, 255, 128, 255 ), "DUPE: non-entity blocked area %d\n", GetID() );
  3920. }
  3921. }
  3922. }
  3923. }
  3924. //--------------------------------------------------------------------------------------------------------
  3925. // checks if any func_nav_blockers are still blocking the area
  3926. void CNavArea::UpdateBlockedFromNavBlockers( void )
  3927. {
  3928. VPROF( "CNavArea::UpdateBlockedFromNavBlockers" );
  3929. Extent bounds;
  3930. GetExtent( &bounds );
  3931. // Save off old values, reset to not blocked state
  3932. m_attributeFlags &= ~NAV_MESH_NAV_BLOCKER;
  3933. bool oldBlocked[MAX_NAV_TEAMS];
  3934. bool wasBlocked = false;
  3935. for ( int i=0; i<MAX_NAV_TEAMS; ++i )
  3936. {
  3937. oldBlocked[i] = m_isBlocked[i];
  3938. wasBlocked = wasBlocked || m_isBlocked[i];
  3939. m_isBlocked[i] = false;
  3940. }
  3941. bool isBlocked = CFuncNavBlocker::CalculateBlocked( m_isBlocked, bounds.lo, bounds.hi );
  3942. if ( isBlocked )
  3943. {
  3944. m_attributeFlags |= NAV_MESH_NAV_BLOCKER;
  3945. }
  3946. // If we're unblocked, fire a nav_blocked event.
  3947. if ( wasBlocked != isBlocked )
  3948. {
  3949. IGameEvent * event = gameeventmanager->CreateEvent( "nav_blocked" );
  3950. if ( event )
  3951. {
  3952. event->SetInt( "area", m_id );
  3953. event->SetInt( "blocked", isBlocked );
  3954. gameeventmanager->FireEvent( event );
  3955. }
  3956. if ( isBlocked )
  3957. {
  3958. if ( nav_debug_blocked.GetBool() )
  3959. {
  3960. ConColorMsg( Color( 0, 255, 128, 255 ), "area %d is blocked by a nav blocker\n", GetID() );
  3961. }
  3962. TheNavMesh->OnAreaBlocked( this );
  3963. }
  3964. else
  3965. {
  3966. if ( nav_debug_blocked.GetBool() )
  3967. {
  3968. ConColorMsg( Color( 0, 128, 255, 255 ), "area %d is unblocked by a nav blocker\n", GetID() );
  3969. }
  3970. TheNavMesh->OnAreaUnblocked( this );
  3971. }
  3972. }
  3973. }
  3974. //--------------------------------------------------------------------------------------------------------------
  3975. void CNavArea::UnblockArea( int teamID )
  3976. {
  3977. bool wasBlocked = IsBlocked( teamID );
  3978. if ( teamID == TEAM_ANY )
  3979. {
  3980. for ( int i=0; i<MAX_NAV_TEAMS; ++i )
  3981. {
  3982. m_isBlocked[ i ] = false;
  3983. }
  3984. }
  3985. else
  3986. {
  3987. int teamIdx = teamID % MAX_NAV_TEAMS;
  3988. m_isBlocked[ teamIdx ] = false;
  3989. }
  3990. if ( wasBlocked )
  3991. {
  3992. IGameEvent * event = gameeventmanager->CreateEvent( "nav_blocked" );
  3993. if ( event )
  3994. {
  3995. event->SetInt( "area", m_id );
  3996. event->SetInt( "blocked", false );
  3997. gameeventmanager->FireEvent( event );
  3998. }
  3999. if ( nav_debug_blocked.GetBool() )
  4000. {
  4001. ConColorMsg( Color( 255, 0, 128, 255 ), "area %d is unblocked by UnblockArea\n", GetID() );
  4002. }
  4003. TheNavMesh->OnAreaUnblocked( this );
  4004. }
  4005. }
  4006. //--------------------------------------------------------------------------------------------------------------
  4007. /**
  4008. * Updates the (un)blocked status of the nav area
  4009. * The semantics of this method have gotten very muddled - needs refactoring (MSB 5/7/09)
  4010. */
  4011. void CNavArea::UpdateBlocked( bool force, int teamID )
  4012. {
  4013. VPROF( "CNavArea::UpdateBlocked" );
  4014. if ( !force && !m_blockedTimer.IsElapsed() )
  4015. {
  4016. return;
  4017. }
  4018. const float MaxBlockedCheckInterval = 5;
  4019. float interval = m_blockedTimer.GetCountdownDuration() + 1;
  4020. if ( interval > MaxBlockedCheckInterval )
  4021. {
  4022. interval = MaxBlockedCheckInterval;
  4023. }
  4024. m_blockedTimer.Start( interval );
  4025. if ( ( m_attributeFlags & NAV_MESH_NAV_BLOCKER ) )
  4026. {
  4027. if ( force )
  4028. {
  4029. UpdateBlockedFromNavBlockers();
  4030. }
  4031. return;
  4032. }
  4033. Vector origin = GetCenter();
  4034. origin.z += HalfHumanHeight;
  4035. const float sizeX = MAX( 1, MIN( GetSizeX()/2 - 5, HalfHumanWidth ) );
  4036. const float sizeY = MAX( 1, MIN( GetSizeY()/2 - 5, HalfHumanWidth ) );
  4037. Extent bounds;
  4038. bounds.lo.Init( -sizeX, -sizeY, 0 );
  4039. bounds.hi.Init( sizeX, sizeY, VEC_DUCK_HULL_MAX.z - HalfHumanHeight );
  4040. bool wasBlocked = IsBlocked( TEAM_ANY );
  4041. // See if spot is valid
  4042. #ifdef TERROR
  4043. // don't unblock func_doors
  4044. CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_PLAYER_MOVEMENT, WALK_THRU_PROP_DOORS | WALK_THRU_BREAKABLES );
  4045. #else
  4046. CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_PLAYER_MOVEMENT, WALK_THRU_DOORS | WALK_THRU_BREAKABLES );
  4047. #endif
  4048. trace_t tr;
  4049. {
  4050. VPROF( "CNavArea::UpdateBlocked-Trace" );
  4051. UTIL_TraceHull(
  4052. origin,
  4053. origin,
  4054. bounds.lo,
  4055. bounds.hi,
  4056. MASK_NPCSOLID_BRUSHONLY,
  4057. &filter,
  4058. &tr );
  4059. }
  4060. if ( !tr.startsolid )
  4061. {
  4062. // unblock ourself
  4063. #ifdef TERROR
  4064. extern ConVar DebugZombieBreakables;
  4065. if ( DebugZombieBreakables.GetBool() )
  4066. #else
  4067. if ( false )
  4068. #endif
  4069. {
  4070. NDebugOverlay::Box( origin, bounds.lo, bounds.hi, 0, 255, 0, 10, 5.0f );
  4071. }
  4072. else
  4073. {
  4074. for ( int i=0; i<MAX_NAV_TEAMS; ++i )
  4075. {
  4076. m_isBlocked[ i ] = false;
  4077. }
  4078. }
  4079. }
  4080. else if ( force )
  4081. {
  4082. if ( teamID == TEAM_ANY )
  4083. {
  4084. for ( int i=0; i<MAX_NAV_TEAMS; ++i )
  4085. {
  4086. m_isBlocked[ i ] = true;
  4087. }
  4088. }
  4089. else
  4090. {
  4091. int teamIdx = teamID % MAX_NAV_TEAMS;
  4092. m_isBlocked[ teamIdx ] = true;
  4093. }
  4094. }
  4095. bool isBlocked = IsBlocked( TEAM_ANY );
  4096. if ( wasBlocked != isBlocked )
  4097. {
  4098. VPROF( "CNavArea::UpdateBlocked-Event" );
  4099. IGameEvent * event = gameeventmanager->CreateEvent( "nav_blocked" );
  4100. if ( event )
  4101. {
  4102. event->SetInt( "area", m_id );
  4103. event->SetInt( "blocked", isBlocked );
  4104. gameeventmanager->FireEvent( event );
  4105. }
  4106. if ( isBlocked )
  4107. {
  4108. TheNavMesh->OnAreaBlocked( this );
  4109. }
  4110. else
  4111. {
  4112. TheNavMesh->OnAreaUnblocked( this );
  4113. }
  4114. }
  4115. if ( TheNavMesh->GetMarkedArea() == this )
  4116. {
  4117. if ( IsBlocked( teamID ) )
  4118. {
  4119. NDebugOverlay::Box( origin, bounds.lo, bounds.hi, 255, 0, 0, 64, 3.0f );
  4120. }
  4121. else
  4122. {
  4123. NDebugOverlay::Box( origin, bounds.lo, bounds.hi, 0, 255, 0, 64, 3.0f );
  4124. }
  4125. }
  4126. }
  4127. //--------------------------------------------------------------------------------------------------------------
  4128. /**
  4129. * Checks if there is a floor under the nav area, in case a breakable floor is gone
  4130. */
  4131. void CNavArea::CheckFloor( CBaseEntity *ignore )
  4132. {
  4133. if ( IsBlocked( TEAM_ANY ) )
  4134. return;
  4135. Vector origin = GetCenter();
  4136. origin.z -= JumpCrouchHeight;
  4137. const float size = GenerationStepSize * 0.5f;
  4138. Vector mins = Vector( -size, -size, 0 );
  4139. Vector maxs = Vector( size, size, JumpCrouchHeight + 10.0f );
  4140. // See if spot is valid
  4141. trace_t tr;
  4142. UTIL_TraceHull(
  4143. origin,
  4144. origin,
  4145. mins,
  4146. maxs,
  4147. MASK_NPCSOLID_BRUSHONLY,
  4148. ignore,
  4149. COLLISION_GROUP_PLAYER_MOVEMENT,
  4150. &tr );
  4151. // If the center is open space, we're effectively blocked
  4152. if ( !tr.startsolid )
  4153. {
  4154. MarkAsBlocked( TEAM_ANY, NULL );
  4155. }
  4156. /*
  4157. if ( IsBlocked( TEAM_ANY ) )
  4158. {
  4159. NDebugOverlay::Box( origin, mins, maxs, 255, 0, 0, 64, 3.0f );
  4160. }
  4161. else
  4162. {
  4163. NDebugOverlay::Box( origin, mins, maxs, 0, 255, 0, 64, 3.0f );
  4164. }
  4165. */
  4166. }
  4167. //--------------------------------------------------------------------------------------------------------
  4168. void CNavArea::MarkObstacleToAvoid( float obstructionHeight )
  4169. {
  4170. if ( m_avoidanceObstacleHeight < obstructionHeight )
  4171. {
  4172. if ( m_avoidanceObstacleHeight == 0 )
  4173. {
  4174. TheNavMesh->OnAvoidanceObstacleEnteredArea( this );
  4175. }
  4176. m_avoidanceObstacleHeight = obstructionHeight;
  4177. }
  4178. }
  4179. //--------------------------------------------------------------------------------------------------------------
  4180. /**
  4181. * Updates the (un)obstructed status of the nav area
  4182. */
  4183. void CNavArea::UpdateAvoidanceObstacles( void )
  4184. {
  4185. if ( !m_avoidanceObstacleTimer.IsElapsed() )
  4186. {
  4187. return;
  4188. }
  4189. const float MaxBlockedCheckInterval = 5;
  4190. float interval = m_blockedTimer.GetCountdownDuration() + 1;
  4191. if ( interval > MaxBlockedCheckInterval )
  4192. {
  4193. interval = MaxBlockedCheckInterval;
  4194. }
  4195. m_avoidanceObstacleTimer.Start( interval );
  4196. Vector mins = m_nwCorner;
  4197. Vector maxs = m_seCorner;
  4198. mins.z = MIN( m_nwCorner.z, m_seCorner.z );
  4199. maxs.z = MAX( m_nwCorner.z, m_seCorner.z ) + HumanCrouchHeight;
  4200. float obstructionHeight = 0.0f;
  4201. for ( int i=0; i<TheNavMesh->GetObstructions().Count(); ++i )
  4202. {
  4203. INavAvoidanceObstacle *obstruction = TheNavMesh->GetObstructions()[i];
  4204. CBaseEntity *obstructingEntity = obstruction->GetObstructingEntity();
  4205. if ( !obstructingEntity )
  4206. continue;
  4207. // check if the aabb intersects the search aabb.
  4208. Vector vecSurroundMins, vecSurroundMaxs;
  4209. obstructingEntity->CollisionProp()->WorldSpaceSurroundingBounds( &vecSurroundMins, &vecSurroundMaxs );
  4210. if ( !IsBoxIntersectingBox( mins, maxs, vecSurroundMins, vecSurroundMaxs ) )
  4211. continue;
  4212. if ( !obstruction->CanObstructNavAreas() )
  4213. continue;
  4214. float propHeight = obstruction->GetNavObstructionHeight();
  4215. obstructionHeight = MAX( obstructionHeight, propHeight );
  4216. }
  4217. m_avoidanceObstacleHeight = obstructionHeight;
  4218. if ( m_avoidanceObstacleHeight == 0.0f )
  4219. {
  4220. TheNavMesh->OnAvoidanceObstacleLeftArea( this );
  4221. }
  4222. }
  4223. //--------------------------------------------------------------------------------------------------------------
  4224. // Clear set of func_nav_cost entities that affect this area
  4225. void CNavArea::ClearAllNavCostEntities( void )
  4226. {
  4227. RemoveAttributes( NAV_MESH_FUNC_COST );
  4228. m_funcNavCostVector.RemoveAll();
  4229. }
  4230. //--------------------------------------------------------------------------------------------------------------
  4231. // Add the given func_nav_cost entity to the cost of this area
  4232. void CNavArea::AddFuncNavCostEntity( CFuncNavCost *cost )
  4233. {
  4234. SetAttributes( NAV_MESH_FUNC_COST );
  4235. m_funcNavCostVector.AddToTail( cost );
  4236. }
  4237. //--------------------------------------------------------------------------------------------------------------
  4238. // Return the cost multiplier of this area's func_nav_cost entities for the given actor
  4239. float CNavArea::ComputeFuncNavCost( CBaseCombatCharacter *who ) const
  4240. {
  4241. float funcCost = 1.0f;
  4242. for( int i=0; i<m_funcNavCostVector.Count(); ++i )
  4243. {
  4244. if ( m_funcNavCostVector[i] != NULL )
  4245. {
  4246. funcCost *= m_funcNavCostVector[i]->GetCostMultiplier( who );
  4247. }
  4248. }
  4249. return funcCost;
  4250. }
  4251. //--------------------------------------------------------------------------------------------------------------
  4252. bool CNavArea::HasFuncNavAvoid( void ) const
  4253. {
  4254. for( int i=0; i<m_funcNavCostVector.Count(); ++i )
  4255. {
  4256. CFuncNavAvoid *avoid = dynamic_cast< CFuncNavAvoid * >( m_funcNavCostVector[i].Get() );
  4257. if ( avoid )
  4258. {
  4259. return true;
  4260. }
  4261. }
  4262. return false;
  4263. }
  4264. //--------------------------------------------------------------------------------------------------------------
  4265. bool CNavArea::HasFuncNavPrefer( void ) const
  4266. {
  4267. for( int i=0; i<m_funcNavCostVector.Count(); ++i )
  4268. {
  4269. CFuncNavPrefer *prefer = dynamic_cast< CFuncNavPrefer * >( m_funcNavCostVector[i].Get() );
  4270. if ( prefer )
  4271. {
  4272. return true;
  4273. }
  4274. }
  4275. return false;
  4276. }
  4277. //--------------------------------------------------------------------------------------------------------------
  4278. void CNavArea::CheckWaterLevel( void )
  4279. {
  4280. Vector pos( GetCenter() );
  4281. if ( !TheNavMesh->GetGroundHeight( pos, &pos.z ) )
  4282. {
  4283. m_isUnderwater = false;
  4284. return;
  4285. }
  4286. pos.z += 1;
  4287. m_isUnderwater = (enginetrace->GetPointContents( pos ) & MASK_WATER ) != 0;
  4288. }
  4289. //--------------------------------------------------------------------------------------------------------------
  4290. static void CommandNavCheckFloor( void )
  4291. {
  4292. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  4293. return;
  4294. if ( TheNavMesh->GetMarkedArea() )
  4295. {
  4296. CNavArea *area = TheNavMesh->GetMarkedArea();
  4297. area->CheckFloor( NULL );
  4298. if ( area->IsBlocked( TEAM_ANY ) )
  4299. {
  4300. DevMsg( "Area #%d %s is blocked\n", area->GetID(), VecToString( area->GetCenter() + Vector( 0, 0, HalfHumanHeight ) ) );
  4301. }
  4302. }
  4303. else
  4304. {
  4305. float start = Plat_FloatTime();
  4306. FOR_EACH_VEC( TheNavAreas, nit )
  4307. {
  4308. CNavArea *area = TheNavAreas[ nit ];
  4309. area->CheckFloor( NULL );
  4310. if ( area->IsBlocked( TEAM_ANY ) )
  4311. {
  4312. DevMsg( "Area #%d %s is blocked\n", area->GetID(), VecToString( area->GetCenter() + Vector( 0, 0, HalfHumanHeight ) ) );
  4313. }
  4314. }
  4315. float end = Plat_FloatTime();
  4316. float time = (end - start) * 1000.0f;
  4317. DevMsg( "nav_check_floor took %2.2f ms\n", time );
  4318. }
  4319. }
  4320. static ConCommand nav_check_floor( "nav_check_floor", CommandNavCheckFloor, "Updates the blocked/unblocked status for every nav area.", FCVAR_GAMEDLL );
  4321. //--------------------------------------------------------------------------------------------------------------
  4322. bool SelectOverlappingAreas::operator()( CNavArea *area )
  4323. {
  4324. CNavArea *overlappingArea = NULL;
  4325. CNavLadder *overlappingLadder = NULL;
  4326. Vector nw = area->GetCorner( NORTH_WEST );
  4327. Vector se = area->GetCorner( SOUTH_EAST );
  4328. Vector start = nw;
  4329. start.x += GenerationStepSize/2;
  4330. start.y += GenerationStepSize/2;
  4331. while ( start.x < se.x )
  4332. {
  4333. start.y = nw.y + GenerationStepSize/2;
  4334. while ( start.y < se.y )
  4335. {
  4336. start.z = area->GetZ( start.x, start.y );
  4337. Vector end = start;
  4338. start.z -= StepHeight;
  4339. end.z += HalfHumanHeight;
  4340. if ( TheNavMesh->FindNavAreaOrLadderAlongRay( start, end, &overlappingArea, &overlappingLadder, area ) )
  4341. {
  4342. if ( overlappingArea )
  4343. {
  4344. TheNavMesh->AddToSelectedSet( overlappingArea );
  4345. TheNavMesh->AddToSelectedSet( area );
  4346. }
  4347. }
  4348. start.y += GenerationStepSize;
  4349. }
  4350. start.x += GenerationStepSize;
  4351. }
  4352. return true;
  4353. }
  4354. //--------------------------------------------------------------------------------------------------------------
  4355. static void CommandNavSelectOverlapping( void )
  4356. {
  4357. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  4358. return;
  4359. TheNavMesh->ClearSelectedSet();
  4360. SelectOverlappingAreas overlapCheck;
  4361. TheNavMesh->ForAllAreas( overlapCheck );
  4362. Msg( "%d overlapping areas selected\n", TheNavMesh->GetSelecteSetSize() );
  4363. }
  4364. static ConCommand nav_select_overlapping( "nav_select_overlapping", CommandNavSelectOverlapping, "Selects nav areas that are overlapping others.", FCVAR_GAMEDLL );
  4365. //--------------------------------------------------------------------------------------------------------
  4366. static byte m_PVS[PAD_NUMBER( MAX_MAP_CLUSTERS,8 ) / 8];
  4367. static int m_nPVSSize; // PVS size in bytes
  4368. CUtlHash< NavVisPair_t, CVisPairHashFuncs, CVisPairHashFuncs > *g_pNavVisPairHash;
  4369. #define MASK_NAV_VISION (MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE)
  4370. //--------------------------------------------------------------------------------------------------------
  4371. /**
  4372. * Set PVS to only include the Potentially Visible Set as seen from anywhere
  4373. * within this nav area
  4374. */
  4375. void CNavArea::SetupPVS( void ) const
  4376. {
  4377. m_nPVSSize = sizeof( m_PVS );
  4378. engine->ResetPVS( m_PVS, m_nPVSSize );
  4379. const float margin = GenerationStepSize/2.0f;
  4380. Vector eye( 0, 0, 0.75f * HumanHeight );
  4381. // step across area checking visibility to given area
  4382. Vector shift( eye );
  4383. for( shift.y = margin; shift.y <= GetSizeY() - margin; shift.y += GenerationStepSize )
  4384. {
  4385. for( shift.x = margin; shift.x <= GetSizeX() - margin; shift.x += GenerationStepSize )
  4386. {
  4387. // Optimization:
  4388. // If we are already POTENTIALLY_VISIBLE, and no longer COMPLETELY_VISIBLE, there's
  4389. // no way for vis to change again.
  4390. Vector testPos( GetCorner( NORTH_WEST ) + shift );
  4391. testPos.z = GetZ( testPos ) + eye.z;
  4392. engine->AddOriginToPVS( testPos );
  4393. }
  4394. }
  4395. }
  4396. //--------------------------------------------------------------------------------------------------------
  4397. /**
  4398. * Return true if this area is within the current PVS
  4399. */
  4400. bool CNavArea::IsInPVS( void ) const
  4401. {
  4402. Vector eye( 0, 0, 0.75f * HumanHeight );
  4403. Extent areaExtent;
  4404. areaExtent.lo = GetCenter() + eye;
  4405. areaExtent.hi = areaExtent.lo;
  4406. areaExtent.Encompass( GetCorner( NORTH_WEST ) + eye );
  4407. areaExtent.Encompass( GetCorner( NORTH_EAST ) + eye );
  4408. areaExtent.Encompass( GetCorner( SOUTH_WEST ) + eye );
  4409. areaExtent.Encompass( GetCorner( SOUTH_EAST ) + eye );
  4410. return engine->CheckBoxInPVS( areaExtent.lo, areaExtent.hi, m_PVS, m_nPVSSize );
  4411. }
  4412. //--------------------------------------------------------------------------------------------------------
  4413. /**
  4414. * Do actual line-of-sight traces to determine if any part of given area is visible from this area
  4415. */
  4416. CNavArea::VisibilityType CNavArea::ComputeVisibility( const CNavArea *area, bool isPVSValid, bool bCheckPVS, bool *pOutsidePVS ) const
  4417. {
  4418. float distanceSq = area->GetCenter().DistToSqr( GetCenter() );
  4419. if ( nav_max_view_distance.GetFloat() > 0.00001f )
  4420. {
  4421. // limit range of visibility check
  4422. if ( distanceSq > Sqr( nav_max_view_distance.GetFloat() ) )
  4423. {
  4424. // too far to be visible
  4425. return NOT_VISIBLE;
  4426. }
  4427. }
  4428. if ( !isPVSValid )
  4429. {
  4430. SetupPVS();
  4431. }
  4432. Vector eye( 0, 0, 0.75f * HumanHeight );
  4433. if ( bCheckPVS )
  4434. {
  4435. Extent areaExtent;
  4436. areaExtent.lo = areaExtent.hi = area->GetCenter() + eye;
  4437. areaExtent.Encompass( area->GetCorner( NORTH_WEST ) + eye );
  4438. areaExtent.Encompass( area->GetCorner( NORTH_EAST ) + eye );
  4439. areaExtent.Encompass( area->GetCorner( SOUTH_WEST ) + eye );
  4440. areaExtent.Encompass( area->GetCorner( SOUTH_EAST ) + eye );
  4441. if ( !engine->CheckBoxInPVS( areaExtent.lo, areaExtent.hi, m_PVS, m_nPVSSize ) )
  4442. {
  4443. if ( pOutsidePVS )
  4444. *pOutsidePVS = true;
  4445. return NOT_VISIBLE;
  4446. }
  4447. if ( pOutsidePVS )
  4448. *pOutsidePVS = false;
  4449. }
  4450. //------------------------------------
  4451. Vector vThisNW = GetCorner( NORTH_WEST ) + eye;
  4452. Vector vThisNE = GetCorner( NORTH_EAST ) + eye;
  4453. Vector vThisSW = GetCorner( SOUTH_WEST ) + eye;
  4454. Vector vThisSE = GetCorner( SOUTH_EAST ) + eye;
  4455. Vector vThisCenter = GetCenter() + eye;
  4456. Vector vTraceMins( vThisNW );
  4457. Vector vTraceMaxs( vThisSE );
  4458. vTraceMins.z = MIN( MIN( MIN( vThisNW.z, vThisNE.z ), vThisSE.z ), vThisSW.z );
  4459. vTraceMaxs.z = MAX( MAX( MAX( vThisNW.z, vThisNE.z ), vThisSE.z ), vThisSW.z ) + 0.1;
  4460. vTraceMins -= vThisCenter;
  4461. vTraceMaxs -= vThisCenter;
  4462. Vector vOtherMins( area->GetCorner( NORTH_WEST) );
  4463. Vector vOtherMaxs( area->GetCorner( SOUTH_EAST) );
  4464. Vector vTarget;
  4465. CalcClosestPointOnAABB( vOtherMins, vOtherMaxs, vThisCenter, vTarget );
  4466. vTarget.z = area->GetZ( vTarget ) + eye.z;
  4467. trace_t tr;
  4468. CTraceFilterNoNPCsOrPlayer traceFilter( NULL, COLLISION_GROUP_NONE );
  4469. UTIL_TraceHull( vThisCenter, vTarget, vTraceMins, vTraceMaxs, MASK_NAV_VISION, &traceFilter, &tr );
  4470. if ( tr.fraction == 1.0 || ( tr.endpos.x > vOtherMins.x && tr.endpos.x < vOtherMaxs.x && tr.endpos.y > vOtherMins.y && tr.endpos.y < vOtherMaxs.y ) )
  4471. {
  4472. return COMPLETELY_VISIBLE; // Counter-intuitive: the way this function was written, "COMPLETELY_VISIBLE" actually means "I am completely visible to the other"
  4473. }
  4474. //------------------------------------
  4475. // check line of sight between areas
  4476. unsigned char vis = COMPLETELY_VISIBLE;
  4477. const float margin = GenerationStepSize/2.0f;
  4478. Vector shift( 0, 0, 0.75f * HumanHeight );
  4479. // always check center to catch very small areas
  4480. if ( area->IsPartiallyVisible( GetCenter() + eye ) )
  4481. {
  4482. vis |= POTENTIALLY_VISIBLE;
  4483. }
  4484. else
  4485. {
  4486. vis &= ~COMPLETELY_VISIBLE;
  4487. }
  4488. Vector eyeToCenter( GetCenter() - area->GetCenter() );
  4489. eyeToCenter.NormalizeInPlace();
  4490. float angleTolerance = nav_potentially_visible_dot_tolerance.GetFloat(); // if corner-to-eye angles are this close to center-to-eye angles, assume the same result and skip the trace
  4491. // step across area checking visibility to given area
  4492. for( shift.y = margin; shift.y <= GetSizeY() - margin; shift.y += GenerationStepSize )
  4493. {
  4494. for( shift.x = margin; shift.x <= GetSizeX() - margin; shift.x += GenerationStepSize )
  4495. {
  4496. // Optimization:
  4497. // If we are already POTENTIALLY_VISIBLE, and no longer COMPLETELY_VISIBLE, there's
  4498. // no way for vis to change again.
  4499. if ( vis == POTENTIALLY_VISIBLE )
  4500. return POTENTIALLY_VISIBLE;
  4501. Vector testPos( GetCorner( NORTH_WEST ) + shift );
  4502. testPos.z = GetZ( testPos ) + eye.z;
  4503. // Optimization - treat long-distance traces that are effectively collinear as the same
  4504. if ( distanceSq > Sqr( 1000 ) )
  4505. {
  4506. Vector eyeToCorner( testPos - (GetCenter() + eye) );
  4507. eyeToCorner.NormalizeInPlace();
  4508. if ( eyeToCorner.Dot( eyeToCenter ) >= angleTolerance )
  4509. {
  4510. continue;
  4511. }
  4512. }
  4513. if ( area->IsPartiallyVisible( testPos ) )
  4514. {
  4515. vis |= POTENTIALLY_VISIBLE;
  4516. }
  4517. else
  4518. {
  4519. vis &= ~COMPLETELY_VISIBLE;
  4520. }
  4521. }
  4522. }
  4523. return (VisibilityType)vis;
  4524. }
  4525. //--------------------------------------------------------------------------------------------------------
  4526. /**
  4527. * Return a list of the delta between our visibility list and the given adjacent area
  4528. */
  4529. const CNavArea::CAreaBindInfoArray &CNavArea::ComputeVisibilityDelta( const CNavArea *other ) const
  4530. {
  4531. static CAreaBindInfoArray delta;
  4532. delta.RemoveAll();
  4533. // do not delta from a delta - if 'other' is already inheriting, use its inherited source directly
  4534. if ( other->m_inheritVisibilityFrom.area != NULL )
  4535. {
  4536. Assert( false && "Visibility inheriting from inherited area" );
  4537. delta = m_potentiallyVisibleAreas;
  4538. return delta;
  4539. }
  4540. // add any visible areas in my list that are not in 'others' list into the delta
  4541. int i, j;
  4542. for( i=0; i<m_potentiallyVisibleAreas.Count(); ++i )
  4543. {
  4544. if ( m_potentiallyVisibleAreas[i].area )
  4545. {
  4546. // is my visible area also in adjacent area's vis list
  4547. for( j=0; j<other->m_potentiallyVisibleAreas.Count(); ++j )
  4548. {
  4549. if ( m_potentiallyVisibleAreas[i].area == other->m_potentiallyVisibleAreas[j].area &&
  4550. m_potentiallyVisibleAreas[i].attributes == other->m_potentiallyVisibleAreas[j].attributes )
  4551. {
  4552. // mutually identically visible
  4553. break;
  4554. }
  4555. }
  4556. if ( j == other->m_potentiallyVisibleAreas.Count() )
  4557. {
  4558. // my vis area not in adjacent area's vis list or has different visibility attributes - add to delta
  4559. delta.AddToTail( m_potentiallyVisibleAreas[i] );
  4560. }
  4561. }
  4562. }
  4563. // add explicit NOT_VISIBLE references to areas in 'others' list that are NOT in mine
  4564. for( j=0; j<other->m_potentiallyVisibleAreas.Count(); ++j )
  4565. {
  4566. if ( other->m_potentiallyVisibleAreas[j].area )
  4567. {
  4568. for( i=0; i<m_potentiallyVisibleAreas.Count(); ++i )
  4569. {
  4570. if ( m_potentiallyVisibleAreas[i].area == other->m_potentiallyVisibleAreas[j].area )
  4571. {
  4572. // area in both lists - already handled in delta above
  4573. break;
  4574. }
  4575. }
  4576. if ( i == m_potentiallyVisibleAreas.Count() )
  4577. {
  4578. // 'other' has area in their list that we don't - mark it explicitly NOT_VISIBLE
  4579. AreaBindInfo info;
  4580. info.area = other->m_potentiallyVisibleAreas[j].area;
  4581. info.attributes = NOT_VISIBLE;
  4582. delta.AddToTail( info );
  4583. }
  4584. }
  4585. }
  4586. return delta;
  4587. }
  4588. //--------------------------------------------------------------------------------------------------------
  4589. void CNavArea::ResetPotentiallyVisibleAreas()
  4590. {
  4591. m_potentiallyVisibleAreas.RemoveAll();
  4592. }
  4593. //--------------------------------------------------------------------------------------------------------
  4594. /**
  4595. * Determine visibility between areas.
  4596. * Compute full list of all areas visible for each area. This list will be compressed into deltas
  4597. * in the PostCustomAnalysis() step.
  4598. */
  4599. CNavArea *g_pCurVisArea;
  4600. CTSListWithFreeList< CNavArea::AreaBindInfo > g_ComputedVis;
  4601. void CNavArea::ComputeVisToArea( CNavArea *&pOtherArea )
  4602. {
  4603. CNavArea *area = assert_cast< CNavArea * >( pOtherArea );
  4604. VisibilityType visThisToOther = ( area == g_pCurVisArea ) ? COMPLETELY_VISIBLE : NOT_VISIBLE;
  4605. VisibilityType visOtherToThis = NOT_VISIBLE;
  4606. if ( area != g_pCurVisArea )
  4607. {
  4608. bool bOutsidePVS;
  4609. visOtherToThis = g_pCurVisArea->ComputeVisibility( area, true, true, &bOutsidePVS ); // TODO: Hacky right now. Compute visibility for the "complete" case actually returns how completely visible the area is to the other. Should fix it to be more clear [1/30/2009 tom]
  4610. if ( !bOutsidePVS && ( visOtherToThis || ( g_pCurVisArea->GetCenter() - area->GetCenter() ).LengthSqr() < Sqr( nav_max_view_distance.GetFloat() ) ) )
  4611. {
  4612. visThisToOther = area->ComputeVisibility( g_pCurVisArea, true, false );
  4613. }
  4614. if ( !visOtherToThis && visThisToOther )
  4615. {
  4616. visOtherToThis = POTENTIALLY_VISIBLE;
  4617. }
  4618. if ( !visThisToOther && visOtherToThis )
  4619. {
  4620. visThisToOther = POTENTIALLY_VISIBLE;
  4621. }
  4622. }
  4623. CNavArea::AreaBindInfo info;
  4624. if ( visThisToOther != NOT_VISIBLE )
  4625. {
  4626. info.area = area;
  4627. info.attributes = visThisToOther;
  4628. g_ComputedVis.PushItem( info );
  4629. }
  4630. if ( visOtherToThis != NOT_VISIBLE )
  4631. {
  4632. info.area = g_pCurVisArea;
  4633. info.attributes = visOtherToThis;
  4634. area->m_potentiallyVisibleAreas.AddToTail( info );
  4635. }
  4636. }
  4637. //--------------------------------------------------------------------------------------------------------
  4638. /**
  4639. * Determine visibility from this area to all potentially/completely visible areas in the mesh
  4640. */
  4641. void CNavArea::ComputeVisibilityToMesh( void )
  4642. {
  4643. m_inheritVisibilityFrom.area = NULL;
  4644. m_isInheritedFrom = false;
  4645. // collect all possible nav areas that could be visible from this area
  4646. NavAreaCollector collector;
  4647. float radius = nav_max_view_distance.GetFloat();
  4648. if ( radius == 0.0f )
  4649. {
  4650. radius = DEF_NAV_VIEW_DISTANCE;
  4651. }
  4652. collector.m_area.EnsureCapacity( 1000 );
  4653. TheNavMesh->ForAllAreasInRadius( collector, GetCenter(), radius );
  4654. NavVisPair_t visPair;
  4655. UtlHashHandle_t hHash;
  4656. // First eliminate the ones already calculated
  4657. for ( int i = collector.m_area.Count() - 1; i >= 0; --i )
  4658. {
  4659. visPair.SetPair( this, collector.m_area[i] );
  4660. hHash = g_pNavVisPairHash->Find( visPair );
  4661. if ( hHash != g_pNavVisPairHash->InvalidHandle() )
  4662. {
  4663. collector.m_area.FastRemove( i );
  4664. }
  4665. }
  4666. SetupPVS();
  4667. g_pCurVisArea = this;
  4668. ParallelProcess( "CNavArea::ComputeVisibilityToMesh", collector.m_area.Base(), collector.m_area.Count(), &ComputeVisToArea );
  4669. m_potentiallyVisibleAreas.EnsureCapacity( g_ComputedVis.Count() );
  4670. while ( g_ComputedVis.Count() )
  4671. {
  4672. g_ComputedVis.PopItem( &m_potentiallyVisibleAreas[ m_potentiallyVisibleAreas.AddToTail() ] );
  4673. }
  4674. FOR_EACH_VEC( collector.m_area, it )
  4675. {
  4676. visPair.SetPair( this, (CNavArea *)collector.m_area[it] );
  4677. Assert( g_pNavVisPairHash->Find( visPair ) == g_pNavVisPairHash->InvalidHandle() );
  4678. g_pNavVisPairHash->Insert( visPair );
  4679. }
  4680. }
  4681. //--------------------------------------------------------------------------------------------------------
  4682. /**
  4683. * The center and all four corners must ALL be visible
  4684. */
  4685. bool CNavArea::IsEntirelyVisible( const Vector &eye, const CBaseEntity *ignore ) const
  4686. {
  4687. Vector corner;
  4688. trace_t result;
  4689. CTraceFilterNoNPCsOrPlayer traceFilter( ignore, COLLISION_GROUP_NONE );
  4690. const float offset = 0.75f * HumanHeight;
  4691. // check center
  4692. UTIL_TraceLine( eye, GetCenter() + Vector( 0, 0, offset ), MASK_NAV_VISION, &traceFilter, &result );
  4693. if (result.fraction < 1.0f)
  4694. {
  4695. return false;
  4696. }
  4697. for( int c=0; c<NUM_CORNERS; ++c )
  4698. {
  4699. corner = GetCorner( (NavCornerType)c );
  4700. UTIL_TraceLine( eye, corner + Vector( 0, 0, offset ), MASK_NAV_VISION, &traceFilter, &result );
  4701. if (result.fraction < 1.0f)
  4702. {
  4703. return false;
  4704. }
  4705. }
  4706. // all points are visible
  4707. return true;
  4708. }
  4709. //--------------------------------------------------------------------------------------------------------
  4710. /**
  4711. * The center or any of the four corners may be visible
  4712. */
  4713. bool CNavArea::IsPartiallyVisible( const Vector &eye, const CBaseEntity *ignore ) const
  4714. {
  4715. Vector corner;
  4716. trace_t result;
  4717. CTraceFilterNoNPCsOrPlayer traceFilter( ignore, COLLISION_GROUP_NONE );
  4718. const float offset = 0.75f * HumanHeight;
  4719. // check center
  4720. UTIL_TraceLine( eye, GetCenter() + Vector( 0, 0, offset ), MASK_NAV_VISION, &traceFilter, &result );
  4721. if (result.fraction >= 1.0f)
  4722. {
  4723. return true;
  4724. }
  4725. Vector eyeToCenter( GetCenter() + Vector( 0, 0, offset ) - eye );
  4726. eyeToCenter.NormalizeInPlace();
  4727. float angleTolerance = nav_potentially_visible_dot_tolerance.GetFloat(); // if corner-to-eye angles are this close to center-to-eye angles, assume the same result and skip the trace
  4728. for( int c=0; c<NUM_CORNERS; ++c )
  4729. {
  4730. corner = GetCorner( (NavCornerType)c ) + Vector( 0, 0, offset );
  4731. // Optimization - treat traces that are effectively collinear as the same
  4732. Vector eyeToCorner( corner - eye );
  4733. eyeToCorner.NormalizeInPlace();
  4734. if ( eyeToCorner.Dot( eyeToCenter ) >= angleTolerance )
  4735. {
  4736. continue;
  4737. }
  4738. UTIL_TraceLine( eye, corner + Vector( 0, 0, offset ), MASK_NAV_VISION, &traceFilter, &result );
  4739. if (result.fraction >= 1.0f)
  4740. {
  4741. return true;
  4742. }
  4743. }
  4744. // nothing is visible
  4745. return false;
  4746. }
  4747. //--------------------------------------------------------------------------------------------------------
  4748. bool CNavArea::IsPotentiallyVisible( const CNavArea *viewedArea ) const
  4749. {
  4750. VPROF_BUDGET( "CNavArea::IsPotentiallyVisible", "NextBot" );
  4751. if ( viewedArea == NULL )
  4752. {
  4753. return false;
  4754. }
  4755. // can always see ourselves
  4756. if ( viewedArea == this )
  4757. {
  4758. return true;
  4759. }
  4760. // normal visibility check
  4761. for ( int i=0; i<m_potentiallyVisibleAreas.Count(); ++i )
  4762. {
  4763. if ( m_potentiallyVisibleAreas[i].area == viewedArea )
  4764. {
  4765. // Found area in our list. We might be a delta from another list,
  4766. // and NOT_VISIBLE overrides that list.
  4767. return ( m_potentiallyVisibleAreas[i].attributes != NOT_VISIBLE );
  4768. }
  4769. }
  4770. // viewedArea is not in our visibility list, check inherited set
  4771. if ( m_inheritVisibilityFrom.area )
  4772. {
  4773. CAreaBindInfoArray &inherited = m_inheritVisibilityFrom.area->m_potentiallyVisibleAreas;
  4774. for ( int i=0; i<inherited.Count(); ++i )
  4775. {
  4776. if ( inherited[i].area == viewedArea )
  4777. {
  4778. return ( inherited[i].attributes != NOT_VISIBLE );
  4779. }
  4780. }
  4781. }
  4782. return false;
  4783. }
  4784. //--------------------------------------------------------------------------------------------------------
  4785. bool CNavArea::IsCompletelyVisible( const CNavArea *viewedArea ) const
  4786. {
  4787. VPROF_BUDGET( "CNavArea::IsCompletelyVisible", "NextBot" );
  4788. if ( viewedArea == NULL )
  4789. {
  4790. return false;
  4791. }
  4792. // can always see ourselves
  4793. if ( viewedArea == this )
  4794. {
  4795. return true;
  4796. }
  4797. // normal visibility check
  4798. for ( int i=0; i<m_potentiallyVisibleAreas.Count(); ++i )
  4799. {
  4800. if ( m_potentiallyVisibleAreas[i].area == viewedArea )
  4801. {
  4802. // our list is definitive - viewedArea is in our list, but is not completely visible
  4803. return ( m_potentiallyVisibleAreas[i].attributes & COMPLETELY_VISIBLE ) ? true : false;
  4804. }
  4805. }
  4806. // viewedArea is not in our visibility list, check inherited set
  4807. if ( m_inheritVisibilityFrom.area )
  4808. {
  4809. CAreaBindInfoArray &inherited = m_inheritVisibilityFrom.area->m_potentiallyVisibleAreas;
  4810. for ( int i=0; i<inherited.Count(); ++i )
  4811. {
  4812. if ( inherited[i].area == viewedArea )
  4813. {
  4814. return ( inherited[i].attributes & COMPLETELY_VISIBLE ) ? true : false;
  4815. }
  4816. }
  4817. }
  4818. return false;
  4819. }
  4820. //--------------------------------------------------------------------------------------------------------
  4821. /**
  4822. * Return true if any portion of this area is visible to anyone on the given team
  4823. */
  4824. bool CNavArea::IsPotentiallyVisibleToTeam( int teamIndex ) const
  4825. {
  4826. VPROF_BUDGET( "CNavArea::IsPotentiallyVisibleToTeam", "NextBot" );
  4827. CTeam *team = GetGlobalTeam( teamIndex );
  4828. for( int i = 0; i < team->GetNumPlayers(); ++i )
  4829. {
  4830. if ( team->GetPlayer(i)->IsAlive() )
  4831. {
  4832. CNavArea *from = (CNavArea *)team->GetPlayer(i)->GetLastKnownArea();
  4833. if ( from && from->IsPotentiallyVisible( this ) )
  4834. {
  4835. return true;
  4836. }
  4837. }
  4838. }
  4839. return false;
  4840. }
  4841. //--------------------------------------------------------------------------------------------------------
  4842. /**
  4843. * Return true if given area is completely visible from somewhere in this area by someone on the team (very fast)
  4844. */
  4845. bool CNavArea::IsCompletelyVisibleToTeam( int teamIndex ) const
  4846. {
  4847. VPROF_BUDGET( "CNavArea::IsCompletelyVisibleToTeam", "NextBot" );
  4848. CTeam *team = GetGlobalTeam( teamIndex );
  4849. for( int i = 0; i < team->GetNumPlayers(); ++i )
  4850. {
  4851. if ( team->GetPlayer(i)->IsAlive() )
  4852. {
  4853. CNavArea *from = (CNavArea *)team->GetPlayer(i)->GetLastKnownArea();
  4854. if ( from && from->IsCompletelyVisible( this ) )
  4855. {
  4856. return true;
  4857. }
  4858. }
  4859. }
  4860. return false;
  4861. }
  4862. //--------------------------------------------------------------------------------------------------------
  4863. Vector CNavArea::GetRandomPoint( void ) const
  4864. {
  4865. Extent extent;
  4866. GetExtent( &extent );
  4867. Vector spot;
  4868. spot.x = RandomFloat( extent.lo.x, extent.hi.x );
  4869. spot.y = RandomFloat( extent.lo.y, extent.hi.y );
  4870. spot.z = GetZ( spot.x, spot.y );
  4871. return spot;
  4872. }