Counter Strike : Global Offensive Source Code
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.

5858 lines
149 KiB

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