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.

4986 lines
150 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. // nav_generate.cpp
  9. // Auto-generate a Navigation Mesh by sampling the current map
  10. // Author: Michael S. Booth ([email protected]), 2003
  11. #include "cbase.h"
  12. #include "util_shared.h"
  13. #include "nav_mesh.h"
  14. #include "nav_node.h"
  15. #include "nav_pathfind.h"
  16. #include "viewport_panel_names.h"
  17. //#include "terror/TerrorShared.h"
  18. #include "fmtstr.h"
  19. #include "usermessages.h"
  20. #ifdef TERROR
  21. #include "func_simpleladder.h"
  22. #endif
  23. // NOTE: This has to be the last file included!
  24. #include "tier0/memdbgon.h"
  25. enum { MAX_BLOCKED_AREAS = 256 };
  26. static unsigned int blockedID[ MAX_BLOCKED_AREAS ];
  27. static int blockedIDCount = 0;
  28. static float lastMsgTime = 0.0f;
  29. bool TraceAdjacentNode( int depth, const Vector& start, const Vector& end, trace_t *trace, float zLimit = DeathDrop );
  30. bool StayOnFloor( trace_t *trace, float zLimit = DeathDrop );
  31. ConVar nav_slope_limit( "nav_slope_limit", "0.7", FCVAR_CHEAT, "The ground unit normal's Z component must be greater than this for nav areas to be generated." );
  32. ConVar nav_slope_tolerance( "nav_slope_tolerance", "0.1", FCVAR_CHEAT, "The ground unit normal's Z component must be this close to the nav area's Z component to be generated." );
  33. ConVar nav_displacement_test( "nav_displacement_test", "10000", FCVAR_CHEAT, "Checks for nodes embedded in displacements (useful for in-development maps)" );
  34. ConVar nav_generate_fencetops( "nav_generate_fencetops", "1", FCVAR_CHEAT, "Autogenerate nav areas on fence and obstacle tops" );
  35. ConVar nav_generate_fixup_jump_areas( "nav_generate_fixup_jump_areas", "1", FCVAR_CHEAT, "Convert obsolete jump areas into 2-way connections" );
  36. ConVar nav_generate_incremental_range( "nav_generate_incremental_range", "2000", FCVAR_CHEAT );
  37. ConVar nav_generate_incremental_tolerance( "nav_generate_incremental_tolerance", "0", FCVAR_CHEAT, "Z tolerance for adding new nav areas." );
  38. ConVar nav_area_max_size( "nav_area_max_size", "50", FCVAR_CHEAT, "Max area size created in nav generation" );
  39. // Common bounding box for traces
  40. Vector NavTraceMins( -0.45, -0.45, 0 );
  41. Vector NavTraceMaxs( 0.45, 0.45, HumanCrouchHeight );
  42. bool FindGroundForNode( Vector *pos, Vector *normal ); // find a ground Z for pos that is clear for NavTraceMins -> NavTraceMaxs
  43. const float MaxTraversableHeight = StepHeight; // max internal obstacle height that can occur between nav nodes and safely disregarded
  44. const float MinObstacleAreaWidth = 10.0f; // min width of a nav area we will generate on top of an obstacle
  45. //--------------------------------------------------------------------------------------------------------------
  46. /**
  47. * Shortest path cost, paying attention to "blocked" areas
  48. */
  49. class ApproachAreaCost
  50. {
  51. public:
  52. float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator )
  53. {
  54. // check if this area is "blocked"
  55. for( int i=0; i<blockedIDCount; ++i )
  56. {
  57. if (area->GetID() == blockedID[i])
  58. {
  59. return -1.0f;
  60. }
  61. }
  62. if (fromArea == NULL)
  63. {
  64. // first area in path, no cost
  65. return 0.0f;
  66. }
  67. else
  68. {
  69. // compute distance traveled along path so far
  70. float dist;
  71. if (ladder)
  72. {
  73. dist = ladder->m_length;
  74. }
  75. else
  76. {
  77. dist = (area->GetCenter() - fromArea->GetCenter()).Length();
  78. }
  79. float cost = dist + fromArea->GetCostSoFar();
  80. return cost;
  81. }
  82. }
  83. };
  84. //--------------------------------------------------------------------------------------------------------------
  85. /**
  86. * Start at given position and find first area in given direction
  87. */
  88. inline CNavArea *findFirstAreaInDirection( const Vector *start, NavDirType dir, float range, float beneathLimit, CBaseEntity *traceIgnore = NULL, Vector *closePos = NULL )
  89. {
  90. CNavArea *area = NULL;
  91. Vector pos = *start;
  92. int end = (int)((range / GenerationStepSize) + 0.5f);
  93. for( int i=1; i<=end; i++ )
  94. {
  95. AddDirectionVector( &pos, dir, GenerationStepSize );
  96. // make sure we dont look thru the wall
  97. trace_t result;
  98. UTIL_TraceHull( *start, pos, NavTraceMins, NavTraceMaxs, TheNavMesh->GetGenerationTraceMask(), traceIgnore, COLLISION_GROUP_NONE, &result );
  99. if (result.fraction < 1.0f)
  100. break;
  101. area = TheNavMesh->GetNavArea( pos, beneathLimit );
  102. if (area)
  103. {
  104. if (closePos)
  105. {
  106. closePos->x = pos.x;
  107. closePos->y = pos.y;
  108. closePos->z = area->GetZ( pos.x, pos.y );
  109. }
  110. break;
  111. }
  112. }
  113. return area;
  114. }
  115. //--------------------------------------------------------------------------------------------------------------
  116. /**
  117. * For each ladder in the map, create a navigation representation of it.
  118. */
  119. void CNavMesh::BuildLadders( void )
  120. {
  121. // remove any left-over ladders
  122. DestroyLadders();
  123. #ifdef TERROR
  124. CFuncSimpleLadder *ladder = NULL;
  125. while( (ladder = dynamic_cast< CFuncSimpleLadder * >(gEntList.FindEntityByClassname( ladder, "func_simpleladder" ))) != NULL )
  126. {
  127. Vector mins, maxs;
  128. ladder->CollisionProp()->WorldSpaceSurroundingBounds( &mins, &maxs );
  129. CreateLadder( mins, maxs, 0.0f );
  130. }
  131. #endif
  132. }
  133. //--------------------------------------------------------------------------------------------------------------
  134. /**
  135. * Create a navigation representation of a ladder.
  136. */
  137. void CNavMesh::CreateLadder( const Vector& absMin, const Vector& absMax, float maxHeightAboveTopArea )
  138. {
  139. CNavLadder *ladder = new CNavLadder;
  140. // compute top & bottom of ladder
  141. ladder->m_top.x = (absMin.x + absMax.x) / 2.0f;
  142. ladder->m_top.y = (absMin.y + absMax.y) / 2.0f;
  143. ladder->m_top.z = absMax.z;
  144. ladder->m_bottom.x = ladder->m_top.x;
  145. ladder->m_bottom.y = ladder->m_top.y;
  146. ladder->m_bottom.z = absMin.z;
  147. // determine facing - assumes "normal" runged ladder
  148. float xSize = absMax.x - absMin.x;
  149. float ySize = absMax.y - absMin.y;
  150. trace_t result;
  151. if (xSize > ySize)
  152. {
  153. // ladder is facing north or south - determine which way
  154. // "pull in" traceline from bottom and top in case ladder abuts floor and/or ceiling
  155. Vector from = ladder->m_bottom + Vector( 0.0f, GenerationStepSize, GenerationStepSize/2 );
  156. Vector to = ladder->m_top + Vector( 0.0f, GenerationStepSize, -GenerationStepSize/2 );
  157. UTIL_TraceLine( from, to, GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );
  158. if (result.fraction != 1.0f || result.startsolid)
  159. ladder->SetDir( NORTH );
  160. else
  161. ladder->SetDir( SOUTH );
  162. ladder->m_width = xSize;
  163. }
  164. else
  165. {
  166. // ladder is facing east or west - determine which way
  167. Vector from = ladder->m_bottom + Vector( GenerationStepSize, 0.0f, GenerationStepSize/2 );
  168. Vector to = ladder->m_top + Vector( GenerationStepSize, 0.0f, -GenerationStepSize/2 );
  169. UTIL_TraceLine( from, to, GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );
  170. if (result.fraction != 1.0f || result.startsolid)
  171. ladder->SetDir( WEST );
  172. else
  173. ladder->SetDir( EAST );
  174. ladder->m_width = ySize;
  175. }
  176. // adjust top and bottom of ladder to make sure they are reachable
  177. // (cs_office has a crate right in front of the base of a ladder)
  178. Vector along = ladder->m_top - ladder->m_bottom;
  179. float length = along.NormalizeInPlace();
  180. Vector on, out;
  181. const float minLadderClearance = 32.0f;
  182. // adjust bottom to bypass blockages
  183. const float inc = 10.0f;
  184. float t;
  185. for( t = 0.0f; t <= length; t += inc )
  186. {
  187. on = ladder->m_bottom + t * along;
  188. out = on + ladder->GetNormal() * minLadderClearance;
  189. UTIL_TraceLine( on, out, GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );
  190. if (result.fraction == 1.0f && !result.startsolid)
  191. {
  192. // found viable ladder bottom
  193. ladder->m_bottom = on;
  194. break;
  195. }
  196. }
  197. // adjust top to bypass blockages
  198. for( t = 0.0f; t <= length; t += inc )
  199. {
  200. on = ladder->m_top - t * along;
  201. out = on + ladder->GetNormal() * minLadderClearance;
  202. UTIL_TraceLine( on, out, GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );
  203. if (result.fraction == 1.0f && !result.startsolid)
  204. {
  205. // found viable ladder top
  206. ladder->m_top = on;
  207. break;
  208. }
  209. }
  210. ladder->m_length = (ladder->m_top - ladder->m_bottom).Length();
  211. ladder->SetDir( ladder->GetDir() ); // now that we've adjusted the top and bottom, re-check the normal
  212. ladder->m_bottomArea = NULL;
  213. ladder->m_topForwardArea = NULL;
  214. ladder->m_topLeftArea = NULL;
  215. ladder->m_topRightArea = NULL;
  216. ladder->m_topBehindArea = NULL;
  217. ladder->ConnectGeneratedLadder( maxHeightAboveTopArea );
  218. // add ladder to global list
  219. m_ladders.AddToTail( ladder );
  220. }
  221. //--------------------------------------------------------------------------------------------------------------
  222. /**
  223. * Create a navigation representation of a ladder.
  224. */
  225. void CNavMesh::CreateLadder( const Vector &top, const Vector &bottom, float width, const Vector2D &ladderDir, float maxHeightAboveTopArea )
  226. {
  227. CNavLadder *ladder = new CNavLadder;
  228. ladder->m_top = top;
  229. ladder->m_bottom = bottom;
  230. ladder->m_width = width;
  231. if ( fabs( ladderDir.x ) > fabs( ladderDir.y ) )
  232. {
  233. if ( ladderDir.x > 0.0f )
  234. {
  235. ladder->SetDir( EAST );
  236. }
  237. else
  238. {
  239. ladder->SetDir( WEST );
  240. }
  241. }
  242. else
  243. {
  244. if ( ladderDir.y > 0.0f )
  245. {
  246. ladder->SetDir( SOUTH );
  247. }
  248. else
  249. {
  250. ladder->SetDir( NORTH );
  251. }
  252. }
  253. // adjust top and bottom of ladder to make sure they are reachable
  254. // (cs_office has a crate right in front of the base of a ladder)
  255. Vector along = ladder->m_top - ladder->m_bottom;
  256. float length = along.NormalizeInPlace();
  257. Vector on, out;
  258. const float minLadderClearance = 32.0f;
  259. // adjust bottom to bypass blockages
  260. const float inc = 10.0f;
  261. float t;
  262. trace_t result;
  263. for( t = 0.0f; t <= length; t += inc )
  264. {
  265. on = ladder->m_bottom + t * along;
  266. out = on + ladder->GetNormal() * minLadderClearance;
  267. UTIL_TraceLine( on, out, GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );
  268. if (result.fraction == 1.0f && !result.startsolid)
  269. {
  270. // found viable ladder bottom
  271. ladder->m_bottom = on;
  272. break;
  273. }
  274. }
  275. // adjust top to bypass blockages
  276. for( t = 0.0f; t <= length; t += inc )
  277. {
  278. on = ladder->m_top - t * along;
  279. out = on + ladder->GetNormal() * minLadderClearance;
  280. UTIL_TraceLine( on, out, GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );
  281. if (result.fraction == 1.0f && !result.startsolid)
  282. {
  283. // found viable ladder top
  284. ladder->m_top = on;
  285. break;
  286. }
  287. }
  288. ladder->m_length = (ladder->m_top - ladder->m_bottom).Length();
  289. ladder->SetDir( ladder->GetDir() ); // now that we've adjusted the top and bottom, re-check the normal
  290. ladder->m_bottomArea = NULL;
  291. ladder->m_topForwardArea = NULL;
  292. ladder->m_topLeftArea = NULL;
  293. ladder->m_topRightArea = NULL;
  294. ladder->m_topBehindArea = NULL;
  295. ladder->ConnectGeneratedLadder( maxHeightAboveTopArea );
  296. // add ladder to global list
  297. m_ladders.AddToTail( ladder );
  298. }
  299. //--------------------------------------------------------------------------------------------------------------
  300. void CNavLadder::ConnectGeneratedLadder( float maxHeightAboveTopArea )
  301. {
  302. const float nearLadderRange = 75.0f; // 50
  303. //
  304. // Find naviagtion area at bottom of ladder
  305. //
  306. // get approximate postion of player on ladder
  307. Vector center = m_bottom + Vector( 0, 0, GenerationStepSize );
  308. AddDirectionVector( &center, m_dir, HalfHumanWidth );
  309. m_bottomArea = TheNavMesh->GetNearestNavArea( center, true );
  310. if (!m_bottomArea)
  311. {
  312. DevMsg( "ERROR: Unconnected ladder bottom at ( %g, %g, %g )\n", m_bottom.x, m_bottom.y, m_bottom.z );
  313. }
  314. else
  315. {
  316. // store reference to ladder in the area
  317. m_bottomArea->AddLadderUp( this );
  318. }
  319. //
  320. // Find adjacent navigation areas at the top of the ladder
  321. //
  322. // get approximate postion of player on ladder
  323. center = m_top + Vector( 0, 0, GenerationStepSize );
  324. AddDirectionVector( &center, m_dir, HalfHumanWidth );
  325. float beneathLimit = MIN( 120.0f, m_top.z - m_bottom.z + HalfHumanWidth );
  326. // find "ahead" area
  327. m_topForwardArea = findFirstAreaInDirection( &center, OppositeDirection( m_dir ), nearLadderRange, beneathLimit, NULL );
  328. if (m_topForwardArea == m_bottomArea)
  329. m_topForwardArea = NULL;
  330. // find "left" area
  331. m_topLeftArea = findFirstAreaInDirection( &center, DirectionLeft( m_dir ), nearLadderRange, beneathLimit, NULL );
  332. if (m_topLeftArea == m_bottomArea)
  333. m_topLeftArea = NULL;
  334. // find "right" area
  335. m_topRightArea = findFirstAreaInDirection( &center, DirectionRight( m_dir ), nearLadderRange, beneathLimit, NULL );
  336. if (m_topRightArea == m_bottomArea)
  337. m_topRightArea = NULL;
  338. // find "behind" area - must look farther, since ladder is against the wall away from this area
  339. m_topBehindArea = findFirstAreaInDirection( &center, m_dir, 2.0f*nearLadderRange, beneathLimit, NULL );
  340. if (m_topBehindArea == m_bottomArea)
  341. m_topBehindArea = NULL;
  342. // can't include behind area, since it is not used when going up a ladder
  343. if (!m_topForwardArea && !m_topLeftArea && !m_topRightArea)
  344. DevMsg( "ERROR: Unconnected ladder top at ( %g, %g, %g )\n", m_top.x, m_top.y, m_top.z );
  345. // store reference to ladder in the area(s)
  346. if (m_topForwardArea)
  347. m_topForwardArea->AddLadderDown( this );
  348. if (m_topLeftArea)
  349. m_topLeftArea->AddLadderDown( this );
  350. if (m_topRightArea)
  351. m_topRightArea->AddLadderDown( this );
  352. if (m_topBehindArea)
  353. {
  354. m_topBehindArea->AddLadderDown( this );
  355. Disconnect( m_topBehindArea );
  356. }
  357. // adjust top of ladder to highest connected area
  358. float topZ = m_bottom.z + 5.0f;
  359. bool topAdjusted = false;
  360. CNavArea *topAreaList[4];
  361. topAreaList[0] = m_topForwardArea;
  362. topAreaList[1] = m_topLeftArea;
  363. topAreaList[2] = m_topRightArea;
  364. topAreaList[3] = m_topBehindArea;
  365. for( int a=0; a<4; ++a )
  366. {
  367. CNavArea *topArea = topAreaList[a];
  368. if (topArea == NULL)
  369. continue;
  370. Vector close;
  371. topArea->GetClosestPointOnArea( m_top, &close );
  372. if (topZ < close.z)
  373. {
  374. topZ = close.z;
  375. topAdjusted = true;
  376. }
  377. }
  378. if (topAdjusted)
  379. {
  380. if ( maxHeightAboveTopArea > 0.0f )
  381. {
  382. m_top.z = MIN( topZ + maxHeightAboveTopArea, m_top.z );
  383. }
  384. else
  385. {
  386. m_top.z = topZ; // not manually specifying a top, so snap exactly
  387. }
  388. }
  389. //
  390. // Determine whether this ladder is "dangling" or not
  391. // "Dangling" ladders are too high to go up
  392. //
  393. if (m_bottomArea)
  394. {
  395. Vector bottomSpot;
  396. m_bottomArea->GetClosestPointOnArea( m_bottom, &bottomSpot );
  397. if (m_bottom.z - bottomSpot.z > HumanHeight)
  398. {
  399. m_bottomArea->Disconnect( this );
  400. }
  401. }
  402. }
  403. //--------------------------------------------------------------------------------------------------------
  404. class JumpConnector
  405. {
  406. public:
  407. bool operator()( CNavArea *jumpArea )
  408. {
  409. if ( !(jumpArea->GetAttributes() & NAV_MESH_JUMP) )
  410. {
  411. return true;
  412. }
  413. for ( int i=0; i<NUM_DIRECTIONS; ++i )
  414. {
  415. NavDirType incomingDir = (NavDirType)i;
  416. NavDirType outgoingDir = OppositeDirection( incomingDir );
  417. const NavConnectVector *incoming = jumpArea->GetIncomingConnections( incomingDir );
  418. const NavConnectVector *from = jumpArea->GetAdjacentAreas( incomingDir );
  419. const NavConnectVector *dest = jumpArea->GetAdjacentAreas( outgoingDir );
  420. TryToConnect( jumpArea, incoming, dest, outgoingDir );
  421. TryToConnect( jumpArea, from, dest, outgoingDir );
  422. }
  423. return true;
  424. }
  425. private:
  426. struct Connection
  427. {
  428. CNavArea *source;
  429. CNavArea *dest;
  430. NavDirType direction;
  431. };
  432. void TryToConnect( CNavArea *jumpArea, const NavConnectVector *source, const NavConnectVector *dest, NavDirType outgoingDir )
  433. {
  434. FOR_EACH_VEC( (*source), sourceIt )
  435. {
  436. CNavArea *sourceArea = const_cast< CNavArea * >( (*source)[ sourceIt ].area );
  437. if ( !sourceArea->IsConnected( jumpArea, outgoingDir ) )
  438. {
  439. continue;
  440. }
  441. if ( sourceArea->HasAttributes( NAV_MESH_JUMP ) )
  442. {
  443. NavDirType incomingDir = OppositeDirection( outgoingDir );
  444. const NavConnectVector *in1 = sourceArea->GetIncomingConnections( incomingDir );
  445. const NavConnectVector *in2 = sourceArea->GetAdjacentAreas( incomingDir );
  446. TryToConnect( jumpArea, in1, dest, outgoingDir );
  447. TryToConnect( jumpArea, in2, dest, outgoingDir );
  448. continue;
  449. }
  450. TryToConnect( jumpArea, sourceArea, dest, outgoingDir );
  451. }
  452. }
  453. void TryToConnect( CNavArea *jumpArea, CNavArea *sourceArea, const NavConnectVector *dest, NavDirType outgoingDir )
  454. {
  455. FOR_EACH_VEC( (*dest), destIt )
  456. {
  457. CNavArea *destArea = const_cast< CNavArea * >( (*dest)[ destIt ].area );
  458. if ( destArea->HasAttributes( NAV_MESH_JUMP ) )
  459. {
  460. // Don't connect areas across 2 jump areas. This means we'll have some missing links due to sampling errors.
  461. // This is preferable to generating incorrect links across multiple jump areas, which is far more common.
  462. continue;
  463. }
  464. Vector center;
  465. float halfWidth;
  466. sourceArea->ComputePortal( destArea, outgoingDir, &center, &halfWidth );
  467. // Don't create corner-to-corner connections
  468. if ( halfWidth <= 0.0f )
  469. {
  470. continue;
  471. }
  472. Vector dir( vec3_origin );
  473. AddDirectionVector( &dir, outgoingDir, 5.0f );
  474. if ( halfWidth > 0.0f )
  475. {
  476. Vector sourcePos, destPos;
  477. sourceArea->GetClosestPointOnArea( center, &sourcePos );
  478. destArea->GetClosestPointOnArea( center, &destPos );
  479. // No jumping up from stairs.
  480. if ( sourceArea->HasAttributes( NAV_MESH_STAIRS ) && sourcePos.z + StepHeight < destPos.z )
  481. {
  482. continue;
  483. }
  484. if ( (sourcePos-destPos).AsVector2D().IsLengthLessThan( GenerationStepSize * 3 ) )
  485. {
  486. sourceArea->ConnectTo( destArea, outgoingDir );
  487. // DevMsg( "Connected %d->%d via %d (len %f)\n",
  488. // sourceArea->GetID(), destArea->GetID(), jumpArea->GetID(), sourcePos.DistTo( destPos ) );
  489. }
  490. }
  491. }
  492. }
  493. };
  494. //--------------------------------------------------------------------------------------------------------------
  495. void CNavMesh::MarkPlayerClipAreas( void )
  496. {
  497. #ifdef TERROR
  498. FOR_EACH_VEC( TheNavAreas, it )
  499. {
  500. TerrorNavArea *area = static_cast< TerrorNavArea * >(TheNavAreas[it]);
  501. // Trace upward a bit from our center point just colliding wtih PLAYERCLIP to see if we're in one, if we are, mark us as accordingly.
  502. trace_t trace;
  503. Vector start = area->GetCenter() + Vector(0.0f, 0.0f, 16.0f );
  504. Vector end = area->GetCenter() + Vector(0.0f, 0.0f, 32.0f );
  505. UTIL_TraceHull( start, end, Vector(0,0,0), Vector(0,0,0), CONTENTS_PLAYERCLIP, NULL, &trace);
  506. if ( trace.fraction < 1.0 )
  507. {
  508. area->SetAttributes( area->GetAttributes() | TerrorNavArea::NAV_PLAYERCLIP );
  509. }
  510. }
  511. #endif
  512. }
  513. //--------------------------------------------------------------------------------------------------------------
  514. /**
  515. * Mark all areas that require a jump to get through them.
  516. * This currently relies on jump areas having extreme slope.
  517. */
  518. void CNavMesh::MarkJumpAreas( void )
  519. {
  520. FOR_EACH_VEC( TheNavAreas, it )
  521. {
  522. CNavArea *area = TheNavAreas[ it ];
  523. if ( !area->HasNodes() )
  524. continue;
  525. Vector normal, otherNormal;
  526. area->ComputeNormal( &normal );
  527. area->ComputeNormal( &otherNormal, true );
  528. float lowestNormalZ = MIN( normal.z, otherNormal.z );
  529. if (lowestNormalZ < nav_slope_limit.GetFloat())
  530. {
  531. // The area is a jump area, and we don't merge jump areas together
  532. area->SetAttributes( area->GetAttributes() | NAV_MESH_JUMP | NAV_MESH_NO_MERGE );
  533. }
  534. else if ( lowestNormalZ < nav_slope_limit.GetFloat() + nav_slope_tolerance.GetFloat() )
  535. {
  536. Vector testPos = area->GetCenter();
  537. testPos.z += HalfHumanHeight;
  538. Vector groundNormal;
  539. float dummy;
  540. if ( GetSimpleGroundHeight( testPos, &dummy, &groundNormal ) )
  541. {
  542. // If the ground normal is divergent from the area's normal, mark it as a jump area - it's not
  543. // really representative of the ground.
  544. float deltaNormalZ = fabs( groundNormal.z - lowestNormalZ );
  545. if ( deltaNormalZ > nav_slope_tolerance.GetFloat() )
  546. {
  547. // The area is a jump area, and we don't merge jump areas together
  548. area->SetAttributes( area->GetAttributes() | NAV_MESH_JUMP | NAV_MESH_NO_MERGE );
  549. }
  550. }
  551. }
  552. }
  553. }
  554. //--------------------------------------------------------------------------------------------------------------
  555. /**
  556. * Remove all areas marked as jump areas and connect the areas connecting to them
  557. *
  558. */
  559. void CNavMesh::StichAndRemoveJumpAreas( void )
  560. {
  561. // Now, go through and remove jump areas, connecting areas to make up for it
  562. JumpConnector connector;
  563. ForAllAreas( connector );
  564. RemoveJumpAreas();
  565. }
  566. //--------------------------------------------------------------------------------------------------------------
  567. /**
  568. * Adjusts obstacle start and end distances such that obstacle width (end-start) is not less than MinObstacleAreaWidth,
  569. * and end distance is not greater than maxAllowedDist
  570. */
  571. void AdjustObstacleDistances( float *pObstacleStartDist, float *pObstacleEndDist, float maxAllowedDist )
  572. {
  573. float obstacleWidth = *pObstacleEndDist - *pObstacleStartDist;
  574. // is the obstacle width too narrow?
  575. if ( obstacleWidth < MinObstacleAreaWidth )
  576. {
  577. float halfDelta = ( MinObstacleAreaWidth - obstacleWidth ) /2;
  578. // move start so it's half of min width from center, but no less than zero
  579. *pObstacleStartDist = MAX( *pObstacleStartDist - halfDelta, 0 );
  580. // move end so it's min width from start
  581. *pObstacleEndDist = *pObstacleStartDist + MinObstacleAreaWidth;
  582. // if this pushes the end past max allowed distance, pull start and end back so that end is within allowed distance
  583. if ( *pObstacleEndDist > maxAllowedDist )
  584. {
  585. float delta = *pObstacleEndDist - maxAllowedDist;
  586. *pObstacleStartDist -= delta;
  587. *pObstacleEndDist -= delta;
  588. }
  589. }
  590. }
  591. //--------------------------------------------------------------------------------------------------------------
  592. /**
  593. * Makes sure tall, slim obstacles like fencetops, railings and narrow walls have nav areas placed on top of them
  594. * to allow climbing & traversal
  595. */
  596. void CNavMesh::HandleObstacleTopAreas( void )
  597. {
  598. if ( !nav_generate_fencetops.GetBool() )
  599. return;
  600. // For any 1x1 area that is internally blocked by an obstacle, raise it on top of the obstacle and size to fit.
  601. RaiseAreasWithInternalObstacles();
  602. // Create new areas as required
  603. CreateObstacleTopAreas();
  604. // It's possible for obstacle top areas to wind up overlapping one another, fix any such cases
  605. RemoveOverlappingObstacleTopAreas();
  606. }
  607. //--------------------------------------------------------------------------------------------------------------
  608. /**
  609. * For any nav area that has internal obstacles between its corners of greater than traversable height,
  610. * raise that nav area to sit at the top of the obstacle, and shrink it to fit the obstacle. Such nav
  611. * areas are already restricted to be 1x1 so this will only be performed on areas that are already small.
  612. */
  613. void CNavMesh::RaiseAreasWithInternalObstacles()
  614. {
  615. // obstacle areas next to stairs are bad - delete them
  616. CUtlVector< CNavArea * > areasToDelete;
  617. FOR_EACH_VEC( TheNavAreas, it )
  618. {
  619. CNavArea *area = TheNavAreas[ it ];
  620. // any nav area with internal obstacles will be 1x1 (width and height = GenerationStepSize), so
  621. // only need to consider areas of that size
  622. if ( ( area->GetSizeX() != GenerationStepSize ) || (area->GetSizeY() != GenerationStepSize ) )
  623. continue;
  624. float obstacleZ[2] = { -FLT_MAX, -FLT_MAX };
  625. float obstacleZMax = -FLT_MAX;
  626. NavDirType obstacleDir = NORTH;
  627. float obstacleStartDist = GenerationStepSize;
  628. float obstacleEndDist = 0;
  629. bool isStairNeighbor = false;
  630. // Look at all 4 directions and determine if there are obstacles in that direction. Find the direction with the highest obstacle, if any.
  631. for ( int i = 0; i < NUM_DIRECTIONS; i++ )
  632. {
  633. NavDirType dir = (NavDirType) i;
  634. // For this direction, look at the left and right edges of the nav area relative to this direction and determined if they are both blocked
  635. // by obstacles. We only consider this area obstructed if both edges are blocked (e.g. fence runs all the way through it).
  636. NavCornerType corner[2];
  637. int iEdgesBlocked = 0;
  638. corner[0] = (NavCornerType) ( ( i + 3 ) % NUM_CORNERS ); // lower left-hand corner relative to current direction
  639. corner[1] = (NavCornerType) ( ( i + 2 ) % NUM_CORNERS ); // lower right-hand corner relative to current direction
  640. float obstacleZThisDir[2] = { -FLT_MAX, -FLT_MAX }; // absolute Z pos of obstacle for left and right edge in this direction
  641. float obstacleStartDistThisDir = GenerationStepSize; // closest obstacle start distance in this direction
  642. float obstacleEndDistThisDir = 0; // farthest obstacle end distance in this direction
  643. // consider left and right edges of nav area relative to current direction
  644. for ( int iEdge = 0; iEdge < 2; iEdge++ )
  645. {
  646. NavCornerType cornerType = corner[iEdge];
  647. CNavNode *nodeFrom = area->m_node[cornerType];
  648. if ( nodeFrom )
  649. {
  650. // is there an obstacle going from corner to corner along this edge?
  651. float obstacleHeight = nodeFrom->m_obstacleHeight[dir];
  652. if ( obstacleHeight > MaxTraversableHeight )
  653. {
  654. // yes, this edge is blocked
  655. iEdgesBlocked++;
  656. // keep track of obstacle height and start and end distance for this edge
  657. float obstacleZ = nodeFrom->GetPosition()->z + obstacleHeight;
  658. if ( obstacleZ > obstacleZThisDir[iEdge] )
  659. {
  660. obstacleZThisDir[iEdge] = obstacleZ;
  661. }
  662. obstacleStartDistThisDir = MIN( nodeFrom->m_obstacleStartDist[dir], obstacleStartDistThisDir );
  663. obstacleEndDistThisDir = MAX( nodeFrom->m_obstacleEndDist[dir], obstacleEndDistThisDir );
  664. }
  665. }
  666. }
  667. int BlockedEdgeCutoff = 2;
  668. const NavConnectVector *connections = area->GetAdjacentAreas( dir );
  669. if ( connections )
  670. {
  671. for ( int conIndex=0; conIndex<connections->Count(); ++conIndex )
  672. {
  673. const CNavArea *connectedArea = connections->Element( conIndex ).area;
  674. if ( connectedArea && connectedArea->HasAttributes( NAV_MESH_STAIRS ) )
  675. {
  676. isStairNeighbor = true;
  677. BlockedEdgeCutoff = 1; // one blocked edge is already too much when we're next to a stair
  678. break;
  679. }
  680. }
  681. }
  682. // are both edged blocked in this direction, and is the obstacle height in this direction the tallest we've seen?
  683. if ( (iEdgesBlocked >= BlockedEdgeCutoff ) && ( MAX( obstacleZThisDir[0], obstacleZThisDir[1] ) ) > obstacleZMax )
  684. {
  685. // this is the tallest obstacle we've encountered so far, remember its details
  686. obstacleZ[0] = obstacleZThisDir[0];
  687. obstacleZ[1] = obstacleZThisDir[1];
  688. obstacleZMax = MAX( obstacleZ[0], obstacleZ[1] );
  689. obstacleDir = dir;
  690. obstacleStartDist = obstacleStartDistThisDir;
  691. obstacleEndDist = obstacleStartDistThisDir;
  692. }
  693. }
  694. if ( isStairNeighbor && obstacleZMax > -FLT_MAX )
  695. {
  696. areasToDelete.AddToTail( area );
  697. continue;
  698. }
  699. // if we found an obstacle, raise this nav areas and size it to fit
  700. if ( obstacleZMax > -FLT_MAX )
  701. {
  702. // enforce minimum obstacle width so we don't shrink to become a teensy nav area
  703. AdjustObstacleDistances( &obstacleStartDist, &obstacleEndDist, GenerationStepSize );
  704. Assert( obstacleEndDist - obstacleStartDist >= MinObstacleAreaWidth );
  705. // get current corner coords
  706. Vector corner[4];
  707. for ( int i = NORTH_WEST; i < NUM_CORNERS; i++ )
  708. {
  709. corner[i] = area->GetCorner( (NavCornerType) i );
  710. }
  711. // adjust our size to fit the obstacle
  712. switch ( obstacleDir )
  713. {
  714. case NORTH:
  715. corner[NORTH_WEST].y = corner[SOUTH_WEST].y - obstacleEndDist;
  716. corner[NORTH_EAST].y = corner[SOUTH_EAST].y - obstacleEndDist;
  717. corner[SOUTH_WEST].y -= obstacleStartDist;
  718. corner[SOUTH_EAST].y -= obstacleStartDist;
  719. break;
  720. case SOUTH:
  721. corner[SOUTH_WEST].y = corner[NORTH_WEST].y + obstacleEndDist;
  722. corner[SOUTH_EAST].y = corner[NORTH_EAST].y + obstacleEndDist;
  723. corner[NORTH_WEST].y += obstacleStartDist;
  724. corner[NORTH_EAST].y += obstacleStartDist;
  725. V_swap( obstacleZ[0], obstacleZ[1] ); // swap left and right Z heights for obstacle so we can run common code below
  726. break;
  727. case EAST:
  728. corner[NORTH_EAST].x = corner[NORTH_WEST].x + obstacleEndDist;
  729. corner[SOUTH_EAST].x = corner[SOUTH_WEST].x + obstacleEndDist;
  730. corner[NORTH_WEST].x += obstacleStartDist;
  731. corner[SOUTH_WEST].x += obstacleStartDist;
  732. case WEST:
  733. corner[NORTH_WEST].x = corner[NORTH_EAST].x - obstacleEndDist;
  734. corner[SOUTH_WEST].x = corner[SOUTH_EAST].x - obstacleEndDist;
  735. corner[NORTH_EAST].x -= obstacleStartDist;
  736. corner[SOUTH_EAST].x -= obstacleStartDist;
  737. V_swap( obstacleZ[0], obstacleZ[1] ); // swap left and right Z heights for obstacle so we can run common code below
  738. break;
  739. }
  740. // adjust Z positions to be z pos of obstacle top
  741. corner[NORTH_WEST].z = obstacleZ[0];
  742. corner[NORTH_EAST].z = obstacleZ[1];
  743. corner[SOUTH_EAST].z = obstacleZ[1];
  744. corner[SOUTH_WEST].z = obstacleZ[0];
  745. // move the area
  746. area->Build( corner[NORTH_WEST], corner[NORTH_EAST], corner[SOUTH_EAST], corner[SOUTH_WEST] );
  747. Assert( !area->IsDegenerate() );
  748. // AddToSelectedSet( area );
  749. // remove side-to-side connections if there are any so AI does try to do things like run along fencetops
  750. area->RemoveOrthogonalConnections( obstacleDir );
  751. area->SetAttributes( area->GetAttributes() | NAV_MESH_NO_MERGE | NAV_MESH_OBSTACLE_TOP );
  752. area->SetAttributes( area->GetAttributes() & ( ~NAV_MESH_JUMP ) );
  753. // clear out the nodes associated with this area's corners -- corners don't match the node positions any more
  754. for ( int i = 0; i < NUM_CORNERS; i++ )
  755. {
  756. area->m_node[i] = NULL;
  757. }
  758. }
  759. }
  760. for ( int i=0; i<areasToDelete.Count(); ++i )
  761. {
  762. TheNavAreas.FindAndRemove( areasToDelete[i] );
  763. DestroyArea( areasToDelete[i] );
  764. }
  765. }
  766. //--------------------------------------------------------------------------------------------------------------
  767. /**
  768. * For any two nav areas that have an obstacle between them such as a fence, railing or small wall, creates a new
  769. * nav area on top of the obstacle and connects it between the areas
  770. */
  771. void CNavMesh::CreateObstacleTopAreas()
  772. {
  773. // enumerate all areas
  774. FOR_EACH_VEC( TheNavAreas, it )
  775. {
  776. CNavArea *area = TheNavAreas[ it ];
  777. // if this is a jump node (which will ultimately get removed) or is an obstacle top, ignore it
  778. if ( area->GetAttributes() & ( NAV_MESH_JUMP | NAV_MESH_OBSTACLE_TOP ) )
  779. return;
  780. // Look in all directions
  781. for ( int i = NORTH; i < NUM_DIRECTIONS; i++ )
  782. {
  783. NavDirType dir = (NavDirType) i;
  784. // Look at all adjacent areas in this direction
  785. int iConnections = area->GetAdjacentCount( dir );
  786. for ( int j = 0; j < iConnections; j++ )
  787. {
  788. CNavArea *areaOther = area->GetAdjacentArea( dir, j );
  789. // if this is a jump node (which will ultimately get removed) or is an obstacle top, ignore it
  790. if ( areaOther->GetAttributes() & ( NAV_MESH_JUMP | NAV_MESH_OBSTACLE_TOP ) )
  791. continue;
  792. // create an obstacle top if there is a one-node separation between the areas and there is an intra-node obstacle within that separation
  793. if ( !CreateObstacleTopAreaIfNecessary( area, areaOther, dir, false ) )
  794. {
  795. // if not, create an obstacle top if there is a two-node separation between the areas and the intermediate node is significantly
  796. // higher than the two areas, which means there's some geometry there that causes the middle node to be higher
  797. CreateObstacleTopAreaIfNecessary( area, areaOther, dir, true );
  798. }
  799. }
  800. }
  801. }
  802. }
  803. //--------------------------------------------------------------------------------------------------------------
  804. /**
  805. * Creates a new nav area if an obstacle exists between the two nav areas. If bMultiNode is false, this checks
  806. * if there's a one-node separation between the areas, and if so if there is an obstacle detected between the nodes.
  807. * If bMultiNode is true, checks if there is a two-node separation between the areas, and if so if the middle node is
  808. * higher than the two areas, suggesting an obstacle in the middle.
  809. */
  810. bool CNavMesh::CreateObstacleTopAreaIfNecessary( CNavArea *area, CNavArea *areaOther, NavDirType dir, bool bMultiNode )
  811. {
  812. float obstacleHeightMin = FLT_MAX;
  813. float obstacleHeightMax = 0;
  814. float obstacleHeightStart = 0;
  815. float obstacleHeightEnd = 0;
  816. float obstacleDistMin = GenerationStepSize;
  817. float obstacleDistMax = 0;
  818. Vector center;
  819. float halfPortalWidth;
  820. area->ComputePortal( areaOther, dir, &center, &halfPortalWidth );
  821. if ( halfPortalWidth > 0 )
  822. {
  823. // get the corners to left and right of direction toward other area
  824. NavCornerType cornerStart = (NavCornerType) dir;
  825. NavCornerType cornerEnd = (NavCornerType) ( ( dir + 1 ) % NUM_CORNERS );
  826. CNavNode *node = area->m_node[cornerStart];
  827. CNavNode *nodeEnd = area->m_node[cornerEnd];
  828. NavDirType dirEdge = (NavDirType) ( ( dir + 1 ) % NUM_DIRECTIONS );
  829. obstacleHeightMin = FLT_MAX;
  830. float zStart = 0, zEnd = 0;
  831. // along the edge of this area that faces the other area, look at every node that's in the portal between the two
  832. while ( node )
  833. {
  834. Vector vecToPortalCenter = *node->GetPosition() - center;
  835. vecToPortalCenter.z = 0;
  836. if ( vecToPortalCenter.IsLengthLessThan( halfPortalWidth + 1.0f ) )
  837. {
  838. // this node is in the portal
  839. float obstacleHeight = 0;
  840. float obstacleDistStartCur = node->m_obstacleStartDist[dir];
  841. float obstacleDistEndCur = node->m_obstacleEndDist[dir];
  842. if ( !bMultiNode )
  843. {
  844. // use the inter-node obstacle height from this node toward the next area
  845. obstacleHeight = node->m_obstacleHeight[dir];
  846. }
  847. else
  848. {
  849. if ( !areaOther->Contains( *node->GetPosition() ) )
  850. {
  851. // step one node toward the other area
  852. CNavNode *nodeTowardOtherArea = node->GetConnectedNode( dir );
  853. if ( nodeTowardOtherArea )
  854. {
  855. // see if that step took us upward a significant amount
  856. float deltaZ = nodeTowardOtherArea->GetPosition()->z - node->GetPosition()->z;
  857. if ( deltaZ > MaxTraversableHeight )
  858. {
  859. // see if we've arrived in the other area
  860. bool bInOtherArea = false;
  861. if ( areaOther->Contains( *nodeTowardOtherArea->GetPosition() ) )
  862. {
  863. float z = areaOther->GetZ( nodeTowardOtherArea->GetPosition()->x, nodeTowardOtherArea->GetPosition()->y );
  864. float deltaZ = fabs( nodeTowardOtherArea->GetPosition()->z - z );
  865. if ( deltaZ < 2.0f )
  866. {
  867. bInOtherArea = true;
  868. }
  869. }
  870. // if we have not arrived in the other area yet, take one more step in the same direction
  871. if ( !bInOtherArea )
  872. {
  873. CNavNode *nodeTowardOtherArea2 = nodeTowardOtherArea->GetConnectedNode( dir );
  874. if ( nodeTowardOtherArea2 && areaOther->Contains( *nodeTowardOtherArea2->GetPosition() ) )
  875. {
  876. float areaDeltaZ = node->GetPosition()->z - nodeTowardOtherArea2->GetPosition()->z;
  877. if ( fabs( areaDeltaZ ) <= MaxTraversableHeight )
  878. {
  879. // if we arrived in the other area, the obstacle height to get here was the peak deltaZ of the node above to get here
  880. obstacleHeight = deltaZ;
  881. // make a nav area MinObstacleAreaWidth wide centered on the peak node, which is GenerationStepSize away from where we started
  882. obstacleDistStartCur = GenerationStepSize - (MinObstacleAreaWidth / 2);
  883. obstacleDistEndCur = GenerationStepSize + (MinObstacleAreaWidth / 2);
  884. }
  885. }
  886. }
  887. }
  888. }
  889. }
  890. }
  891. obstacleHeightMin = MIN( obstacleHeight, obstacleHeightMin );
  892. obstacleHeightMax = MAX( obstacleHeight, obstacleHeightMax );
  893. obstacleDistMin = MIN( obstacleDistStartCur, obstacleDistMin );
  894. obstacleDistMax = MAX( obstacleDistEndCur, obstacleDistMax );
  895. if ( obstacleHeightStart == 0 )
  896. {
  897. // keep track of the obstacle height and node z pos at the start of the edge
  898. obstacleHeightStart = obstacleHeight;
  899. zStart = node->GetPosition()->z;
  900. }
  901. // keep track of the obstacle height and node z pos at the end of the edge
  902. obstacleHeightEnd = obstacleHeight;
  903. zEnd = node->GetPosition()->z;
  904. }
  905. if ( node == nodeEnd )
  906. break;
  907. node = node->GetConnectedNode( dirEdge );
  908. }
  909. // if we had some obstacle height from EVERY node along the portal, then getting from this area to the other requires scaling an obstacle,
  910. // need to generate a nav area on top of it
  911. if ( ( obstacleHeightMax > MaxTraversableHeight ) && ( obstacleHeightMin > MaxTraversableHeight ) )
  912. {
  913. // If the maximum obstacle height was greater than both the height at start and end of the edge, then the obstacle is highest somewhere
  914. // in the middle. Use that as the height of both ends.
  915. if ( ( obstacleHeightMax > obstacleHeightStart ) && ( obstacleHeightMax > obstacleHeightEnd ) )
  916. {
  917. obstacleHeightStart = obstacleHeightMax;
  918. obstacleHeightEnd = obstacleHeightMax;
  919. }
  920. // for south and west, swap "start" and "end" values of edges so we can use common code below
  921. if ( dir == SOUTH || dir == WEST )
  922. {
  923. V_swap( obstacleHeightStart, obstacleHeightEnd );
  924. V_swap( zStart, zEnd );
  925. }
  926. // Enforce min area width for new area
  927. AdjustObstacleDistances( &obstacleDistMin, &obstacleDistMax, bMultiNode ? GenerationStepSize * 2 : GenerationStepSize );
  928. Assert( obstacleDistMin < obstacleDistMax );
  929. Assert( obstacleDistMax - obstacleDistMin >= MinObstacleAreaWidth );
  930. float newAreaWidth = obstacleDistMax - obstacleDistMin;
  931. Assert( newAreaWidth > 0 );
  932. // Calculate new area coordinates
  933. AddDirectionVector( &center, dir, obstacleDistMin + (newAreaWidth/2) );
  934. Vector cornerNW, cornerNE, cornerSE, cornerSW;
  935. switch ( dir )
  936. {
  937. case NORTH:
  938. case SOUTH:
  939. cornerNW.Init( center.x - halfPortalWidth, center.y - (newAreaWidth/2), zStart + obstacleHeightStart );
  940. cornerNE.Init( center.x + halfPortalWidth, center.y - (newAreaWidth/2), zEnd + obstacleHeightEnd );
  941. cornerSE.Init( center.x + halfPortalWidth, center.y + (newAreaWidth/2), zEnd + obstacleHeightEnd );
  942. cornerSW.Init( center.x - halfPortalWidth, center.y + (newAreaWidth/2), zStart + obstacleHeightStart );
  943. break;
  944. case EAST:
  945. case WEST:
  946. cornerNW.Init( center.x - (newAreaWidth/2), center.y - halfPortalWidth, zStart + obstacleHeightStart );
  947. cornerNE.Init( center.x + (newAreaWidth/2), center.y - halfPortalWidth, zEnd + obstacleHeightEnd );
  948. cornerSE.Init( center.x + (newAreaWidth/2), center.y + halfPortalWidth, zEnd + obstacleHeightEnd );
  949. cornerSW.Init( center.x - (newAreaWidth/2), center.y + halfPortalWidth, zStart + obstacleHeightStart );
  950. break;
  951. }
  952. CNavArea *areaNew = CreateArea();
  953. areaNew->Build( cornerNW, cornerNE, cornerSE, cornerSW );
  954. // add it to the nav area list
  955. TheNavAreas.AddToTail( areaNew );
  956. AddNavArea( areaNew );
  957. Assert( !areaNew->IsDegenerate() );
  958. Msg( "Created new fencetop area %d(%x) between %d(%x) and %d(%x)\n", areaNew->GetID(), areaNew->GetDebugID(), area->GetID(), area->GetDebugID(), areaOther->GetID(), areaOther->GetDebugID() );
  959. areaNew->SetAttributes( area->GetAttributes() );
  960. areaNew->SetAttributes( area->GetAttributes() | NAV_MESH_NO_MERGE | NAV_MESH_OBSTACLE_TOP );
  961. area->Disconnect( areaOther );
  962. area->ConnectTo( areaNew, dir );
  963. areaNew->ConnectTo( area, OppositeDirection( dir ) );
  964. areaNew->ConnectTo( areaOther, dir );
  965. if ( areaOther->IsConnected( area, OppositeDirection( dir ) ) )
  966. {
  967. areaOther->Disconnect( area );
  968. areaOther->ConnectTo( areaNew, OppositeDirection( dir ) );
  969. }
  970. // AddToSelectedSet( areaNew );
  971. return true;
  972. }
  973. }
  974. return false;
  975. }
  976. //--------------------------------------------------------------------------------------------------------------
  977. /**
  978. * Remove any obstacle top areas which overlap.
  979. */
  980. void CNavMesh::RemoveOverlappingObstacleTopAreas()
  981. {
  982. // What we really want is the union of all obstacle top areas that get generated. That would be hard to compute exactly,
  983. // so instead we'll just remove any that overlap. The obstacle top areas don't have to be exact, we just need enough of
  984. // them so there is generally a path to get over any obstacle.
  985. // make a list of just the obstacle top areas to reduce the N of the N squared operation we're about to do
  986. CUtlVector<CNavArea *> vecObstacleTopAreas;
  987. FOR_EACH_VEC( TheNavAreas, it )
  988. {
  989. CNavArea *area = TheNavAreas[ it ];
  990. if ( area->GetAttributes() & NAV_MESH_OBSTACLE_TOP )
  991. {
  992. vecObstacleTopAreas.AddToTail( area );
  993. }
  994. }
  995. // look at every pair of obstacle top areas
  996. CUtlVector<CNavArea *> vecAreasToRemove;
  997. FOR_EACH_VEC( vecObstacleTopAreas, it )
  998. {
  999. CNavArea *area = vecObstacleTopAreas[it];
  1000. Vector normal, otherNormal;
  1001. area->ComputeNormal( &normal );
  1002. area->ComputeNormal( &otherNormal, true );
  1003. // Remove any obstacle areas that are steep enough to be jump areas
  1004. float lowestNormalZ = MIN( normal.z, otherNormal.z );
  1005. if ( lowestNormalZ < nav_slope_limit.GetFloat() )
  1006. {
  1007. vecAreasToRemove.AddToTail( area );
  1008. }
  1009. for ( int it2 = it+1; it2 < vecObstacleTopAreas.Count(); it2++ )
  1010. {
  1011. CNavArea *areaOther = vecObstacleTopAreas[it2];
  1012. if ( area->IsOverlapping( areaOther ) )
  1013. {
  1014. if ( area->Contains( areaOther ) )
  1015. {
  1016. // if one entirely contains the other, mark the other for removal
  1017. vecAreasToRemove.AddToTail( areaOther );
  1018. }
  1019. else if ( areaOther->Contains( area ) )
  1020. {
  1021. // if one entirely contains the other, mark the other for removal
  1022. vecAreasToRemove.AddToTail( area );
  1023. }
  1024. else
  1025. {
  1026. // if they overlap without one being a superset of the other, just remove the smaller area
  1027. CNavArea *areaToRemove = ( area->GetSizeX() * area->GetSizeY() > areaOther->GetSizeX() * areaOther->GetSizeY() ? areaOther : area );
  1028. vecAreasToRemove.AddToTail( areaToRemove );
  1029. }
  1030. }
  1031. }
  1032. }
  1033. // now go delete all the areas we want to remove
  1034. while ( vecAreasToRemove.Count() > 0 )
  1035. {
  1036. CNavArea *areaToDelete = vecAreasToRemove[0];
  1037. RemoveFromSelectedSet( areaToDelete );
  1038. TheNavMesh->OnEditDestroyNotify( areaToDelete );
  1039. TheNavAreas.FindAndRemove( areaToDelete );
  1040. TheNavMesh->DestroyArea( areaToDelete );
  1041. // remove duplicates so we don't double-delete
  1042. while ( vecAreasToRemove.FindAndRemove( areaToDelete ) );
  1043. }
  1044. }
  1045. static void CommandNavCheckStairs( void )
  1046. {
  1047. TheNavMesh->MarkStairAreas();
  1048. }
  1049. static ConCommand nav_check_stairs( "nav_check_stairs", CommandNavCheckStairs, "Update the nav mesh STAIRS attribute", FCVAR_CHEAT );
  1050. //--------------------------------------------------------------------------------------------------------------
  1051. /**
  1052. * Mark all areas that are on stairs.
  1053. */
  1054. void CNavMesh::MarkStairAreas( void )
  1055. {
  1056. FOR_EACH_VEC( TheNavAreas, it )
  1057. {
  1058. CNavArea *area = TheNavAreas[ it ];
  1059. area->TestStairs();
  1060. }
  1061. }
  1062. //--------------------------------------------------------------------------------------------------------------
  1063. enum StairTestType
  1064. {
  1065. STAIRS_NO,
  1066. STAIRS_YES,
  1067. STAIRS_MAYBE,
  1068. };
  1069. //--------------------------------------------------------------------------------------------------------
  1070. // Test if a line across a nav area could be part of a stairway
  1071. StairTestType IsStairs( const Vector &start, const Vector &end, StairTestType ret )
  1072. {
  1073. if ( ret == STAIRS_NO )
  1074. return ret;
  1075. const float inc = 5.0f;
  1076. // the minimum height change each step to be a step and not a slope
  1077. const float minStepZ = inc * tan( acos( nav_slope_limit.GetFloat() ) );
  1078. const float MinStairNormal = 0.97f; // we don't care about ramps, just actual flat steps
  1079. float t;
  1080. Vector pos, normal;
  1081. float height, priorHeight;
  1082. // walk the line, checking for step height discontinuities
  1083. float length = start.AsVector2D().DistTo( end.AsVector2D() );
  1084. trace_t trace;
  1085. CTraceFilterNoNPCsOrPlayer filter( NULL, COLLISION_GROUP_PLAYER_MOVEMENT );
  1086. Vector hullMins( -inc/2, -inc/2, 0 );
  1087. Vector hullMaxs( inc/2, inc/2, 0 );
  1088. hullMaxs.z = 1; // don't care about vertical clearance
  1089. if ( fabs( start.x - end.x ) > fabs( start.y - end.y ) )
  1090. {
  1091. hullMins.x = -8;
  1092. hullMaxs.x = 8;
  1093. }
  1094. else
  1095. {
  1096. hullMins.y = -8;
  1097. hullMaxs.y = 8;
  1098. }
  1099. Vector traceOffset( 0, 0, VEC_DUCK_HULL_MAX.z );
  1100. // total height change must exceed a single step to be stairs
  1101. if ( abs( start.z - end.z ) > StepHeight )
  1102. {
  1103. // initialize the height delta
  1104. UTIL_TraceHull( start + traceOffset, start - traceOffset, hullMins, hullMaxs, MASK_NPCSOLID, &filter, &trace );
  1105. if ( trace.startsolid || trace.IsDispSurface() )
  1106. {
  1107. return STAIRS_NO;
  1108. }
  1109. priorHeight = trace.endpos.z;
  1110. // Save a copy for debug overlays
  1111. Vector prevGround = start;
  1112. prevGround.z = priorHeight;
  1113. float traceIncrement = inc / length;
  1114. for( t = 0.0f; t <= 1.0f; t += traceIncrement )
  1115. {
  1116. pos = start + t * ( end - start );
  1117. UTIL_TraceHull( pos + traceOffset, pos - traceOffset, hullMins, hullMaxs, MASK_NPCSOLID, &filter, &trace );
  1118. if ( trace.startsolid || trace.IsDispSurface() )
  1119. {
  1120. return STAIRS_NO;
  1121. }
  1122. height = trace.endpos.z;
  1123. normal = trace.plane.normal;
  1124. // Save a copy for debug overlays
  1125. Vector ground( pos );
  1126. ground.z = height;
  1127. //NDebugOverlay::Cross3D( ground, 3, 0, 0, 255, true, 100.0f );
  1128. //NDebugOverlay::Box( ground, hullMins, hullMaxs, 0, 0, 255, 0.0f, 100.0f );
  1129. if ( t == 0.0f && fabs( height - start.z ) > StepHeight )
  1130. {
  1131. // Discontinuity at start
  1132. return STAIRS_NO;
  1133. }
  1134. if ( t == 1.0f && fabs( height - end.z ) > StepHeight )
  1135. {
  1136. // Discontinuity at end
  1137. return STAIRS_NO;
  1138. }
  1139. if ( normal.z < MinStairNormal )
  1140. {
  1141. // too steep here
  1142. return STAIRS_NO;
  1143. }
  1144. float deltaZ = abs( height - priorHeight );
  1145. if ( deltaZ >= minStepZ && deltaZ <= StepHeight )
  1146. {
  1147. // found a step
  1148. ret = STAIRS_YES;
  1149. }
  1150. else if ( deltaZ > StepHeight )
  1151. {
  1152. // too steep here
  1153. //NDebugOverlay::Cross3D( ground, 5, 255, 0, 0, true, 10.0f );
  1154. //NDebugOverlay::Cross3D( prevGround, 5, 0, 255, 0, true, 10.0f );
  1155. return STAIRS_NO;
  1156. }
  1157. // Save a copy for debug overlays
  1158. prevGround = pos;
  1159. prevGround.z = height;
  1160. priorHeight = height;
  1161. }
  1162. }
  1163. return ret;
  1164. }
  1165. //--------------------------------------------------------------------------------------------------------------
  1166. /**
  1167. * Test an area for being on stairs
  1168. * NOTE: This assumes a globally constant "step height",
  1169. * and walkable surface normal, which really should be locomotor-specific.
  1170. */
  1171. bool CNavArea::TestStairs( void )
  1172. {
  1173. // clear STAIRS attribute
  1174. SetAttributes( GetAttributes() & ~NAV_MESH_STAIRS );
  1175. if ( GetSizeX() <= GenerationStepSize && GetSizeY() <= GenerationStepSize )
  1176. {
  1177. // Don't bother with stairs on small areas
  1178. return false;
  1179. }
  1180. const float MatchingNormalDot = 0.95f;
  1181. Vector firstNormal, secondNormal;
  1182. ComputeNormal( &firstNormal );
  1183. ComputeNormal( &secondNormal, true );
  1184. if ( firstNormal.Dot( secondNormal ) < MatchingNormalDot )
  1185. {
  1186. // area corners aren't coplanar - no stairs
  1187. return false;
  1188. }
  1189. // test center and edges north-to-south, and east-to-west
  1190. StairTestType ret = STAIRS_MAYBE;
  1191. Vector from, to;
  1192. const float inset = 5.0f; // inset to keep the tests completely inside the nav area
  1193. from = GetCorner( NORTH_WEST ) + Vector( inset, inset, 0 );
  1194. to = GetCorner( NORTH_EAST ) + Vector( -inset, inset, 0 );
  1195. ret = IsStairs( from, to, ret );
  1196. from = GetCorner( SOUTH_WEST ) + Vector( inset, -inset, 0 );
  1197. to = GetCorner( SOUTH_EAST ) + Vector( -inset, -inset, 0 );
  1198. ret = IsStairs( from, to, ret );
  1199. from = GetCorner( NORTH_WEST ) + Vector( inset, inset, 0 );
  1200. to = GetCorner( SOUTH_WEST ) + Vector( inset, -inset, 0 );
  1201. ret = IsStairs( from, to, ret );
  1202. from = GetCorner( NORTH_EAST ) + Vector( -inset, inset, 0 );
  1203. to = GetCorner( SOUTH_EAST ) + Vector( -inset, -inset, 0 );
  1204. ret = IsStairs( from, to, ret );
  1205. from = ( GetCorner( NORTH_WEST ) + GetCorner( NORTH_EAST ) ) / 2.0f + Vector( 0, inset, 0 );
  1206. to = ( GetCorner( SOUTH_WEST ) + GetCorner( SOUTH_EAST ) ) / 2.0f + Vector( 0, -inset, 0 );
  1207. ret = IsStairs( from, to, ret );
  1208. from = ( GetCorner( NORTH_EAST ) + GetCorner( SOUTH_EAST ) ) / 2.0f + Vector( -inset, 0, 0 );
  1209. to = ( GetCorner( NORTH_WEST ) + GetCorner( SOUTH_WEST ) ) / 2.0f + Vector( inset, 0, 0 );
  1210. ret = IsStairs( from, to, ret );
  1211. if ( ret == STAIRS_YES )
  1212. {
  1213. SetAttributes( NAV_MESH_STAIRS );
  1214. return true;
  1215. }
  1216. return false;
  1217. }
  1218. //--------------------------------------------------------------------------------------------------------------
  1219. CON_COMMAND_F( nav_test_stairs, "Test the selected set for being on stairs", FCVAR_CHEAT )
  1220. {
  1221. int count = 0;
  1222. const NavAreaVector &selectedSet = TheNavMesh->GetSelectedSet();
  1223. for ( int i=0; i<selectedSet.Count(); ++i )
  1224. {
  1225. CNavArea *area = selectedSet[i];
  1226. if ( area->TestStairs() )
  1227. {
  1228. ++count;
  1229. }
  1230. }
  1231. Msg( "Marked %d areas as stairs\n", count );
  1232. }
  1233. //--------------------------------------------------------------------------------------------------------------
  1234. /**
  1235. * Jump areas aren't used by the NextBot. Delete them, connecting adjacent areas.
  1236. */
  1237. void CNavMesh::RemoveJumpAreas( void )
  1238. {
  1239. if ( !nav_generate_fixup_jump_areas.GetBool() )
  1240. {
  1241. return;
  1242. }
  1243. CUtlVector< CNavArea * > unusedAreas;
  1244. int i;
  1245. for ( i=0; i<TheNavAreas.Count(); ++i )
  1246. {
  1247. CNavArea *testArea = TheNavAreas[i];
  1248. if ( !(testArea->GetAttributes() & NAV_MESH_JUMP) )
  1249. {
  1250. continue;
  1251. }
  1252. unusedAreas.AddToTail( testArea );
  1253. }
  1254. for ( i=0; i<unusedAreas.Count(); ++i )
  1255. {
  1256. CNavArea *areaToDelete = unusedAreas[i];
  1257. TheNavMesh->OnEditDestroyNotify( areaToDelete );
  1258. TheNavAreas.FindAndRemove( areaToDelete );
  1259. TheNavMesh->DestroyArea( areaToDelete );
  1260. }
  1261. StripNavigationAreas();
  1262. SetMarkedArea( NULL ); // unmark the mark area
  1263. m_markedCorner = NUM_CORNERS; // clear the corner selection
  1264. }
  1265. //--------------------------------------------------------------------------------------------------------------
  1266. void CNavMesh::CommandNavRemoveJumpAreas( void )
  1267. {
  1268. JumpConnector connector;
  1269. ForAllAreas( connector );
  1270. int before = TheNavAreas.Count();
  1271. RemoveJumpAreas();
  1272. int after = TheNavAreas.Count();
  1273. Msg( "Removed %d jump areas\n", before - after );
  1274. }
  1275. //--------------------------------------------------------------------------------------------------------------
  1276. /**
  1277. * Recursively chop area in half along X until child areas are roughly square
  1278. */
  1279. static void splitX( CNavArea *area )
  1280. {
  1281. if (area->IsRoughlySquare())
  1282. return;
  1283. float split = area->GetSizeX();
  1284. split /= 2.0f;
  1285. split += area->GetCorner( NORTH_WEST ).x;
  1286. split = TheNavMesh->SnapToGrid( split );
  1287. const float epsilon = 0.1f;
  1288. if (fabs(split - area->GetCorner( NORTH_WEST ).x) < epsilon ||
  1289. fabs(split - area->GetCorner( SOUTH_EAST ).x) < epsilon)
  1290. {
  1291. // too small to subdivide
  1292. return;
  1293. }
  1294. CNavArea *alpha, *beta;
  1295. if (area->SplitEdit( false, split, &alpha, &beta ))
  1296. {
  1297. // split each new area until square
  1298. splitX( alpha );
  1299. splitX( beta );
  1300. }
  1301. }
  1302. //--------------------------------------------------------------------------------------------------------------
  1303. /**
  1304. * Recursively chop area in half along Y until child areas are roughly square
  1305. */
  1306. static void splitY( CNavArea *area )
  1307. {
  1308. if (area->IsRoughlySquare())
  1309. return;
  1310. float split = area->GetSizeY();
  1311. split /= 2.0f;
  1312. split += area->GetCorner( NORTH_WEST ).y;
  1313. split = TheNavMesh->SnapToGrid( split );
  1314. const float epsilon = 0.1f;
  1315. if (fabs(split - area->GetCorner( NORTH_WEST ).y) < epsilon ||
  1316. fabs(split - area->GetCorner( SOUTH_EAST ).y) < epsilon)
  1317. {
  1318. // too small to subdivide
  1319. return;
  1320. }
  1321. CNavArea *alpha, *beta;
  1322. if (area->SplitEdit( true, split, &alpha, &beta ))
  1323. {
  1324. // split each new area until square
  1325. splitY( alpha );
  1326. splitY( beta );
  1327. }
  1328. }
  1329. //--------------------------------------------------------------------------------------------------------------
  1330. /**
  1331. * Split any long, thin, areas into roughly square chunks.
  1332. */
  1333. void CNavMesh::SquareUpAreas( void )
  1334. {
  1335. int it = 0;
  1336. while( it < TheNavAreas.Count() )
  1337. {
  1338. CNavArea *area = TheNavAreas[ it ];
  1339. // move the iterator in case the current area is split and deleted
  1340. ++it;
  1341. if (area->HasNodes() && !area->IsRoughlySquare())
  1342. {
  1343. // chop this area into square pieces
  1344. if (area->GetSizeX() > area->GetSizeY())
  1345. splitX( area );
  1346. else
  1347. splitY( area );
  1348. }
  1349. }
  1350. }
  1351. //--------------------------------------------------------------------------------------------------------------
  1352. static bool testStitchConnection( CNavArea *source, CNavArea *target, const Vector &sourcePos, const Vector &targetPos )
  1353. {
  1354. trace_t result;
  1355. Vector from( sourcePos );
  1356. Vector pos( targetPos );
  1357. CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_NONE, WALK_THRU_EVERYTHING );
  1358. Vector to, toNormal;
  1359. bool success = false;
  1360. if ( TraceAdjacentNode( 0, from, pos, &result ) )
  1361. {
  1362. to = result.endpos;
  1363. toNormal = result.plane.normal;
  1364. success = true;
  1365. }
  1366. else
  1367. {
  1368. // test going up ClimbUpHeight
  1369. bool success = false;
  1370. for ( float height = StepHeight; height <= ClimbUpHeight; height += 1.0f )
  1371. {
  1372. trace_t tr;
  1373. Vector start( from );
  1374. Vector end( pos );
  1375. start.z += height;
  1376. end.z += height;
  1377. UTIL_TraceHull( start, end, NavTraceMins, NavTraceMaxs, TheNavMesh->GetGenerationTraceMask(), &filter, &tr );
  1378. if ( !tr.startsolid && tr.fraction == 1.0f )
  1379. {
  1380. if ( !StayOnFloor( &tr ) )
  1381. {
  1382. break;
  1383. }
  1384. to = tr.endpos;
  1385. toNormal = tr.plane.normal;
  1386. start = end = from;
  1387. end.z += height;
  1388. UTIL_TraceHull( start, end, NavTraceMins, NavTraceMaxs, TheNavMesh->GetGenerationTraceMask(), &filter, &tr );
  1389. if ( tr.fraction < 1.0f )
  1390. {
  1391. break;
  1392. }
  1393. success = true;
  1394. break;
  1395. }
  1396. }
  1397. }
  1398. return success;
  1399. }
  1400. //--------------------------------------------------------------------------------------------------------
  1401. class IncrementallyGeneratedAreas
  1402. {
  1403. public:
  1404. bool operator()( CNavArea *area )
  1405. {
  1406. return area->HasNodes();
  1407. }
  1408. };
  1409. //--------------------------------------------------------------------------------------------------------
  1410. /**
  1411. * Incremental generation fixup for where edges lap up against the existing nav mesh:
  1412. * we have nodes, but the surrounding areas don't. So, we trace outward, to see if we
  1413. * can walk/fall to an adjacent area. This handles dropping down into existing areas etc.
  1414. * TODO: test pre-existing areas for drop-downs into the newly-generated areas.
  1415. */
  1416. void CNavMesh::StitchGeneratedAreas( void )
  1417. {
  1418. if ( m_generationMode == GENERATE_INCREMENTAL )
  1419. {
  1420. IncrementallyGeneratedAreas incrementalAreas;
  1421. StitchMesh( incrementalAreas );
  1422. }
  1423. }
  1424. //--------------------------------------------------------------------------------------------------------
  1425. class AreaSet
  1426. {
  1427. public:
  1428. AreaSet( CUtlVector< CNavArea * > *areas )
  1429. {
  1430. m_areas = areas;
  1431. }
  1432. bool operator()( CNavArea *area )
  1433. {
  1434. return ( m_areas->HasElement( area ) );
  1435. }
  1436. private:
  1437. CUtlVector< CNavArea * > *m_areas;
  1438. };
  1439. //--------------------------------------------------------------------------------------------------------
  1440. /**
  1441. * Stitches an arbitrary set of areas (newly-merged, for example) into the existing mesh
  1442. */
  1443. void CNavMesh::StitchAreaSet( CUtlVector< CNavArea * > *areas )
  1444. {
  1445. AreaSet areaSet( areas );
  1446. StitchMesh( areaSet );
  1447. }
  1448. //--------------------------------------------------------------------------------------------------------------
  1449. /**
  1450. * Determine if we can "jump down" from given point
  1451. */
  1452. inline bool testJumpDown( const Vector *fromPos, const Vector *toPos )
  1453. {
  1454. float dz = fromPos->z - toPos->z;
  1455. // drop can't be too far, or too short (or nonexistant)
  1456. if (dz <= JumpCrouchHeight || dz >= DeathDrop)
  1457. return false;
  1458. //
  1459. // Check LOS out and down
  1460. //
  1461. // +-----+
  1462. // | |
  1463. // F |
  1464. // |
  1465. // T
  1466. //
  1467. Vector from, to;
  1468. float up;
  1469. trace_t result;
  1470. // Try to go up and out, up to ClimbUpHeight, to get over obstacles
  1471. for ( up=1.0f; up<=ClimbUpHeight; up += 1.0f )
  1472. {
  1473. from = *fromPos;
  1474. to.Init( fromPos->x, fromPos->y, fromPos->z + up );
  1475. UTIL_TraceHull( from, to, NavTraceMins, NavTraceMaxs, TheNavMesh->GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );
  1476. if (result.fraction <= 0.0f || result.startsolid)
  1477. continue;
  1478. from.Init( fromPos->x, fromPos->y, result.endpos.z - 0.5f );
  1479. to.Init( toPos->x, toPos->y, from.z );
  1480. UTIL_TraceHull( from, to, NavTraceMins, NavTraceMaxs, TheNavMesh->GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );
  1481. if (result.fraction != 1.0f || result.startsolid)
  1482. continue;
  1483. // Success!
  1484. break;
  1485. }
  1486. if ( up > ClimbUpHeight )
  1487. return false;
  1488. // We've made it up and out, so see if we can drop down
  1489. from = to;
  1490. to.z = toPos->z + 2.0f;
  1491. UTIL_TraceHull( from, to, NavTraceMins, NavTraceMaxs, TheNavMesh->GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );
  1492. if (result.fraction <= 0.0f || result.startsolid)
  1493. return false;
  1494. // Allow a little fudge so we can drop down onto stairs
  1495. if ( result.endpos.z > to.z + StepHeight )
  1496. return false;
  1497. return true;
  1498. }
  1499. //--------------------------------------------------------------------------------------------------------------
  1500. inline CNavArea *findJumpDownArea( const Vector *fromPos, NavDirType dir )
  1501. {
  1502. Vector start( fromPos->x, fromPos->y, fromPos->z + HalfHumanHeight );
  1503. AddDirectionVector( &start, dir, GenerationStepSize/2.0f );
  1504. Vector toPos;
  1505. CNavArea *downArea = findFirstAreaInDirection( &start, dir, 4.0f * GenerationStepSize, DeathDrop, NULL, &toPos );
  1506. if (downArea && testJumpDown( fromPos, &toPos ))
  1507. return downArea;
  1508. return NULL;
  1509. }
  1510. //--------------------------------------------------------------------------------------------------------------
  1511. template < typename Functor >
  1512. void CNavMesh::StitchAreaIntoMesh( CNavArea *area, NavDirType dir, Functor &func )
  1513. {
  1514. Vector corner1, corner2;
  1515. switch ( dir )
  1516. {
  1517. case NORTH:
  1518. corner1 = area->GetCorner( NORTH_WEST );
  1519. corner2 = area->GetCorner( NORTH_EAST );
  1520. break;
  1521. case SOUTH:
  1522. corner1 = area->GetCorner( SOUTH_WEST );
  1523. corner2 = area->GetCorner( SOUTH_EAST );
  1524. break;
  1525. case EAST:
  1526. corner1 = area->GetCorner( NORTH_EAST );
  1527. corner2 = area->GetCorner( SOUTH_EAST );
  1528. break;
  1529. case WEST:
  1530. corner1 = area->GetCorner( NORTH_WEST );
  1531. corner2 = area->GetCorner( SOUTH_WEST );
  1532. break;
  1533. }
  1534. Vector edgeDir = corner2 - corner1;
  1535. edgeDir.z = 0.0f;
  1536. float edgeLength = edgeDir.NormalizeInPlace();
  1537. for ( float n=0; n<edgeLength - 1.0f; n += GenerationStepSize )
  1538. {
  1539. Vector sourcePos = corner1 + edgeDir * ( n + 0.5f );
  1540. sourcePos.z += HalfHumanHeight;
  1541. Vector targetPos = sourcePos;
  1542. switch ( dir )
  1543. {
  1544. case NORTH: targetPos.y -= GenerationStepSize * 0.5f; break;
  1545. case SOUTH: targetPos.y += GenerationStepSize * 0.5f; break;
  1546. case EAST: targetPos.x += GenerationStepSize * 0.5f; break;
  1547. case WEST: targetPos.x -= GenerationStepSize * 0.5f; break;
  1548. }
  1549. CNavArea *targetArea = TheNavMesh->GetNavArea( targetPos );
  1550. if ( targetArea && !func( targetArea ) )
  1551. {
  1552. targetPos.z = targetArea->GetZ( targetPos.x, targetPos.y ) + HalfHumanHeight;
  1553. // outgoing connection
  1554. if ( testStitchConnection( area, targetArea, sourcePos, targetPos ) )
  1555. {
  1556. area->ConnectTo( targetArea, dir );
  1557. }
  1558. // incoming connection
  1559. if ( testStitchConnection( targetArea, area, targetPos, sourcePos ) )
  1560. {
  1561. targetArea->ConnectTo( area, OppositeDirection( dir ) );
  1562. }
  1563. }
  1564. else
  1565. {
  1566. sourcePos.z -= HalfHumanHeight;
  1567. sourcePos.z += 1;
  1568. CNavArea *downArea = findJumpDownArea( &sourcePos, dir );
  1569. if ( downArea && downArea != area && !func( downArea ) )
  1570. {
  1571. area->ConnectTo( downArea, dir );
  1572. }
  1573. }
  1574. }
  1575. }
  1576. //--------------------------------------------------------------------------------------------------------------
  1577. /**
  1578. * Checks to see if there is a cliff - a drop of at least CliffHeight - in specified direction.
  1579. */
  1580. inline bool CheckCliff( const Vector *fromPos, NavDirType dir, bool bExhaustive = true )
  1581. {
  1582. // cliffs are half-baked, not used by any existing AI, and create poorly behaved nav areas (ie: long, thin, strips) (MSB 8/7/09)
  1583. return false;
  1584. Vector toPos( fromPos->x, fromPos->y, fromPos->z );
  1585. AddDirectionVector( &toPos, dir, GenerationStepSize );
  1586. trace_t trace;
  1587. // trace a step in specified direction and see where we'd find up
  1588. if ( TraceAdjacentNode( 0, *fromPos, toPos, &trace, DeathDrop * 10 ) && !trace.allsolid && !trace.startsolid )
  1589. {
  1590. float deltaZ = fromPos->z - trace.endpos.z;
  1591. // would we fall off a cliff?
  1592. if ( deltaZ > CliffHeight )
  1593. return true;
  1594. // if not, special case for south and east. South and east edges are not considered part of a nav area, so
  1595. // we look ahead two steps for south and east. This ensures that the n-1th row and column of nav nodes
  1596. // on the south and east sides of a nav area reflect any cliffs on the nth row and column.
  1597. // if we're looking to south or east, and the first node we found was approximately flat, and this is the top-level
  1598. // call, recurse one level to check one more step in this direction
  1599. if ( ( dir == SOUTH || dir == EAST ) && ( fabs( deltaZ ) < StepHeight ) && bExhaustive )
  1600. {
  1601. return CheckCliff( &trace.endpos, dir, false );
  1602. }
  1603. }
  1604. return false;
  1605. }
  1606. //--------------------------------------------------------------------------------------------------------------
  1607. /**
  1608. * Define connections between adjacent generated areas
  1609. */
  1610. void CNavMesh::ConnectGeneratedAreas( void )
  1611. {
  1612. Msg( "Connecting navigation areas...\n" );
  1613. FOR_EACH_VEC( TheNavAreas, it )
  1614. {
  1615. CNavArea *area = TheNavAreas[ it ];
  1616. // scan along edge nodes, stepping one node over into the next area
  1617. // for now, only use bi-directional connections
  1618. // north edge
  1619. CNavNode *node;
  1620. for( node = area->m_node[ NORTH_WEST ]; node != area->m_node[ NORTH_EAST ]; node = node->GetConnectedNode( EAST ) )
  1621. {
  1622. CNavNode *adj = node->GetConnectedNode( NORTH );
  1623. if (adj && adj->GetArea() && adj->GetConnectedNode( SOUTH ) == node )
  1624. {
  1625. area->ConnectTo( adj->GetArea(), NORTH );
  1626. }
  1627. else
  1628. {
  1629. CNavArea *downArea = findJumpDownArea( node->GetPosition(), NORTH );
  1630. if (downArea && downArea != area)
  1631. area->ConnectTo( downArea, NORTH );
  1632. }
  1633. }
  1634. // west edge
  1635. for( node = area->m_node[ NORTH_WEST ]; node != area->m_node[ SOUTH_WEST ]; node = node->GetConnectedNode( SOUTH ) )
  1636. {
  1637. CNavNode *adj = node->GetConnectedNode( WEST );
  1638. if (adj && adj->GetArea() && adj->GetConnectedNode( EAST ) == node )
  1639. {
  1640. area->ConnectTo( adj->GetArea(), WEST );
  1641. }
  1642. else
  1643. {
  1644. CNavArea *downArea = findJumpDownArea( node->GetPosition(), WEST );
  1645. if (downArea && downArea != area)
  1646. area->ConnectTo( downArea, WEST );
  1647. }
  1648. }
  1649. // south edge - this edge's nodes are actually part of adjacent areas
  1650. // move one node north, and scan west to east
  1651. /// @todo This allows one-node-wide areas - do we want this?
  1652. node = area->m_node[ SOUTH_WEST ];
  1653. if ( node ) // pre-existing areas in incremental generates won't have nodes
  1654. {
  1655. node = node->GetConnectedNode( NORTH );
  1656. }
  1657. if (node)
  1658. {
  1659. CNavNode *end = area->m_node[ SOUTH_EAST ]->GetConnectedNode( NORTH );
  1660. /// @todo Figure out why cs_backalley gets a NULL node in here...
  1661. for( ; node && node != end; node = node->GetConnectedNode( EAST ) )
  1662. {
  1663. CNavNode *adj = node->GetConnectedNode( SOUTH );
  1664. if (adj && adj->GetArea() && adj->GetConnectedNode( NORTH ) == node )
  1665. {
  1666. area->ConnectTo( adj->GetArea(), SOUTH );
  1667. }
  1668. else
  1669. {
  1670. CNavArea *downArea = findJumpDownArea( node->GetPosition(), SOUTH );
  1671. if (downArea && downArea != area)
  1672. area->ConnectTo( downArea, SOUTH );
  1673. }
  1674. }
  1675. }
  1676. // south edge part 2 - scan the actual south edge. If the node is not part of an adjacent area, then it
  1677. // really belongs to us. This will happen if our area runs right up against a ledge.
  1678. for( node = area->m_node[ SOUTH_WEST ]; node != area->m_node[ SOUTH_EAST ]; node = node->GetConnectedNode( EAST ) )
  1679. {
  1680. if ( node->GetArea() )
  1681. continue; // some other area owns this node, pay no attention to it
  1682. CNavNode *adj = node->GetConnectedNode( SOUTH );
  1683. if ( node->IsBlockedInAnyDirection() || (adj && adj->IsBlockedInAnyDirection()) )
  1684. continue; // The space around this node is blocked, so don't connect across it
  1685. // Don't directly connect to adj's area, since it's already 1 cell removed from our area.
  1686. // There was no area in between, presumably for good reason. Only look for jump down links.
  1687. if ( !adj || !adj->GetArea() )
  1688. {
  1689. CNavArea *downArea = findJumpDownArea( node->GetPosition(), SOUTH );
  1690. if (downArea && downArea != area)
  1691. area->ConnectTo( downArea, SOUTH );
  1692. }
  1693. }
  1694. // east edge - this edge's nodes are actually part of adjacent areas
  1695. node = area->m_node[ NORTH_EAST ];
  1696. if ( node ) // pre-existing areas in incremental generates won't have nodes
  1697. {
  1698. node = node->GetConnectedNode( WEST );
  1699. }
  1700. if (node)
  1701. {
  1702. CNavNode *end = area->m_node[ SOUTH_EAST ]->GetConnectedNode( WEST );
  1703. for( ; node && node != end; node = node->GetConnectedNode( SOUTH ) )
  1704. {
  1705. CNavNode *adj = node->GetConnectedNode( EAST );
  1706. if (adj && adj->GetArea() && adj->GetConnectedNode( WEST ) == node )
  1707. {
  1708. area->ConnectTo( adj->GetArea(), EAST );
  1709. }
  1710. else
  1711. {
  1712. CNavArea *downArea = findJumpDownArea( node->GetPosition(), EAST );
  1713. if (downArea && downArea != area)
  1714. area->ConnectTo( downArea, EAST );
  1715. }
  1716. }
  1717. }
  1718. // east edge part 2 - scan the actual east edge. If the node is not part of an adjacent area, then it
  1719. // really belongs to us. This will happen if our area runs right up against a ledge.
  1720. for( node = area->m_node[ NORTH_EAST ]; node != area->m_node[ SOUTH_EAST ]; node = node->GetConnectedNode( SOUTH ) )
  1721. {
  1722. if ( node->GetArea() )
  1723. continue; // some other area owns this node, pay no attention to it
  1724. CNavNode *adj = node->GetConnectedNode( EAST );
  1725. if ( node->IsBlockedInAnyDirection() || (adj && adj->IsBlockedInAnyDirection()) )
  1726. continue; // The space around this node is blocked, so don't connect across it
  1727. // Don't directly connect to adj's area, since it's already 1 cell removed from our area.
  1728. // There was no area in between, presumably for good reason. Only look for jump down links.
  1729. if ( !adj || !adj->GetArea() )
  1730. {
  1731. CNavArea *downArea = findJumpDownArea( node->GetPosition(), EAST );
  1732. if (downArea && downArea != area)
  1733. area->ConnectTo( downArea, EAST );
  1734. }
  1735. }
  1736. }
  1737. StitchGeneratedAreas();
  1738. }
  1739. //--------------------------------------------------------------------------------------------------------------
  1740. bool CNavArea::IsAbleToMergeWith( CNavArea *other ) const
  1741. {
  1742. if ( !HasNodes() || ( GetAttributes() & NAV_MESH_NO_MERGE ) )
  1743. return false;
  1744. if ( !other->HasNodes() || ( other->GetAttributes() & NAV_MESH_NO_MERGE ) )
  1745. return false;
  1746. return true;
  1747. }
  1748. //--------------------------------------------------------------------------------------------------------------
  1749. /**
  1750. * Merge areas together to make larger ones (must remain rectangular - convex).
  1751. * Areas can only be merged if their attributes match.
  1752. */
  1753. void CNavMesh::MergeGeneratedAreas( void )
  1754. {
  1755. Msg( "Merging navigation areas...\n" );
  1756. bool merged;
  1757. do
  1758. {
  1759. merged = false;
  1760. FOR_EACH_VEC( TheNavAreas, it )
  1761. {
  1762. CNavArea *area = TheNavAreas[ it ];
  1763. if ( !area->HasNodes() || ( area->GetAttributes() & NAV_MESH_NO_MERGE ) )
  1764. continue;
  1765. // north edge
  1766. FOR_EACH_VEC( area->m_connect[ NORTH ], nit )
  1767. {
  1768. CNavArea *adjArea = area->m_connect[ NORTH ][ nit ].area;
  1769. if ( !area->IsAbleToMergeWith( adjArea ) ) // pre-existing areas in incremental generates won't have nodes
  1770. continue;
  1771. if ( area->GetSizeY() + adjArea->GetSizeY() > GenerationStepSize * nav_area_max_size.GetInt() )
  1772. continue;
  1773. if (area->m_node[ NORTH_WEST ] == adjArea->m_node[ SOUTH_WEST ] &&
  1774. area->m_node[ NORTH_EAST ] == adjArea->m_node[ SOUTH_EAST ] &&
  1775. area->GetAttributes() == adjArea->GetAttributes() &&
  1776. area->IsCoplanar( adjArea ))
  1777. {
  1778. // merge vertical
  1779. area->m_node[ NORTH_WEST ] = adjArea->m_node[ NORTH_WEST ];
  1780. area->m_node[ NORTH_EAST ] = adjArea->m_node[ NORTH_EAST ];
  1781. merged = true;
  1782. //CONSOLE_ECHO( " Merged (north) areas #%d and #%d\n", area->m_id, adjArea->m_id );
  1783. area->FinishMerge( adjArea );
  1784. // restart scan - iterator is invalidated
  1785. break;
  1786. }
  1787. }
  1788. if (merged)
  1789. break;
  1790. // south edge
  1791. FOR_EACH_VEC( area->m_connect[ SOUTH ], sit )
  1792. {
  1793. CNavArea *adjArea = area->m_connect[ SOUTH ][ sit ].area;
  1794. if ( !area->IsAbleToMergeWith( adjArea ) ) // pre-existing areas in incremental generates won't have nodes
  1795. continue;
  1796. if ( area->GetSizeY() + adjArea->GetSizeY() > GenerationStepSize * nav_area_max_size.GetInt() )
  1797. continue;
  1798. if (adjArea->m_node[ NORTH_WEST ] == area->m_node[ SOUTH_WEST ] &&
  1799. adjArea->m_node[ NORTH_EAST ] == area->m_node[ SOUTH_EAST ] &&
  1800. area->GetAttributes() == adjArea->GetAttributes() &&
  1801. area->IsCoplanar( adjArea ))
  1802. {
  1803. // merge vertical
  1804. area->m_node[ SOUTH_WEST ] = adjArea->m_node[ SOUTH_WEST ];
  1805. area->m_node[ SOUTH_EAST ] = adjArea->m_node[ SOUTH_EAST ];
  1806. merged = true;
  1807. //CONSOLE_ECHO( " Merged (south) areas #%d and #%d\n", area->m_id, adjArea->m_id );
  1808. area->FinishMerge( adjArea );
  1809. // restart scan - iterator is invalidated
  1810. break;
  1811. }
  1812. }
  1813. if (merged)
  1814. break;
  1815. // west edge
  1816. FOR_EACH_VEC( area->m_connect[ WEST ], wit )
  1817. {
  1818. CNavArea *adjArea = area->m_connect[ WEST ][ wit ].area;
  1819. if ( !area->IsAbleToMergeWith( adjArea ) ) // pre-existing areas in incremental generates won't have nodes
  1820. continue;
  1821. if ( area->GetSizeX() + adjArea->GetSizeX() > GenerationStepSize * nav_area_max_size.GetInt() )
  1822. continue;
  1823. if (area->m_node[ NORTH_WEST ] == adjArea->m_node[ NORTH_EAST ] &&
  1824. area->m_node[ SOUTH_WEST ] == adjArea->m_node[ SOUTH_EAST ] &&
  1825. area->GetAttributes() == adjArea->GetAttributes() &&
  1826. area->IsCoplanar( adjArea ))
  1827. {
  1828. // merge horizontal
  1829. area->m_node[ NORTH_WEST ] = adjArea->m_node[ NORTH_WEST ];
  1830. area->m_node[ SOUTH_WEST ] = adjArea->m_node[ SOUTH_WEST ];
  1831. merged = true;
  1832. //CONSOLE_ECHO( " Merged (west) areas #%d and #%d\n", area->m_id, adjArea->m_id );
  1833. area->FinishMerge( adjArea );
  1834. // restart scan - iterator is invalidated
  1835. break;
  1836. }
  1837. }
  1838. if (merged)
  1839. break;
  1840. // east edge
  1841. FOR_EACH_VEC( area->m_connect[ EAST ], eit )
  1842. {
  1843. CNavArea *adjArea = area->m_connect[ EAST ][ eit ].area;
  1844. if ( !area->IsAbleToMergeWith( adjArea ) ) // pre-existing areas in incremental generates won't have nodes
  1845. continue;
  1846. if ( area->GetSizeX() + adjArea->GetSizeX() > GenerationStepSize * nav_area_max_size.GetInt() )
  1847. continue;
  1848. if (adjArea->m_node[ NORTH_WEST ] == area->m_node[ NORTH_EAST ] &&
  1849. adjArea->m_node[ SOUTH_WEST ] == area->m_node[ SOUTH_EAST ] &&
  1850. area->GetAttributes() == adjArea->GetAttributes() &&
  1851. area->IsCoplanar( adjArea ))
  1852. {
  1853. // merge horizontal
  1854. area->m_node[ NORTH_EAST ] = adjArea->m_node[ NORTH_EAST ];
  1855. area->m_node[ SOUTH_EAST ] = adjArea->m_node[ SOUTH_EAST ];
  1856. merged = true;
  1857. //CONSOLE_ECHO( " Merged (east) areas #%d and #%d\n", area->m_id, adjArea->m_id );
  1858. area->FinishMerge( adjArea );
  1859. // restart scan - iterator is invalidated
  1860. break;
  1861. }
  1862. }
  1863. if (merged)
  1864. break;
  1865. }
  1866. }
  1867. while( merged );
  1868. }
  1869. //--------------------------------------------------------------------------------------------------------------
  1870. /**
  1871. * Given arbitrary corners of a compass grid-aligned rectangle, classify them by compass direction.
  1872. * Input: vec[4]: arbitrary corners
  1873. * Output: vecNW, vecNE, vecSE, vecSW: filled in with which corner is in which compass direction
  1874. */
  1875. void ClassifyCorners( Vector vec[4], Vector &vecNW, Vector &vecNE, Vector &vecSE, Vector &vecSW )
  1876. {
  1877. vecNW = vecNE = vecSE = vecSW = vec[0];
  1878. for ( int i = 0; i < 4; i++ )
  1879. {
  1880. if ( ( vec[i].x <= vecNW.x ) && ( vec[i].y <= vecNW.y ) )
  1881. {
  1882. vecNW = vec[i];
  1883. }
  1884. if ( ( vec[i].x >= vecNE.x ) && ( vec[i].y <= vecNE.y ) )
  1885. {
  1886. vecNE = vec[i];
  1887. }
  1888. if ( ( vec[i].x >= vecSE.x ) && ( vec[i].y >= vecSE.y ) )
  1889. {
  1890. vecSE = vec[i];
  1891. }
  1892. if ( ( vec[i].x <= vecSW.x ) && ( vec[i].y >= vecSW.y ) )
  1893. {
  1894. vecSW = vec[i];
  1895. }
  1896. }
  1897. }
  1898. //--------------------------------------------------------------------------------------------------------------
  1899. /**
  1900. * Perform miscellaneous fixups to generated mesh
  1901. */
  1902. void CNavMesh::FixUpGeneratedAreas( void )
  1903. {
  1904. FixCornerOnCornerAreas();
  1905. FixConnections();
  1906. }
  1907. //--------------------------------------------------------------------------------------------------------------
  1908. void CNavMesh::FixConnections( void )
  1909. {
  1910. // Test the steep sides of stairs for any outgoing links that cross nodes that were partially obstructed.
  1911. FOR_EACH_VEC( TheNavAreas, it )
  1912. {
  1913. CNavArea *area = TheNavAreas[ it ];
  1914. if ( !area->HasAttributes( NAV_MESH_STAIRS ) )
  1915. continue;
  1916. if ( !area->HasNodes() )
  1917. continue;
  1918. for ( int dir=0; dir<NUM_DIRECTIONS; ++dir )
  1919. {
  1920. NavCornerType cornerType[2];
  1921. GetCornerTypesInDirection( (NavDirType)dir, &cornerType[0], &cornerType[1] );
  1922. // Flat edges of stairs need to connect. It's the slopes we don't want to climb over things for.
  1923. float cornerDeltaZ = fabs( area->GetCorner( cornerType[0] ).z - area->GetCorner( cornerType[1] ).z );
  1924. if ( cornerDeltaZ < StepHeight )
  1925. continue;
  1926. const NavConnectVector *connectedAreas = area->GetAdjacentAreas( (NavDirType)dir );
  1927. CUtlVector< CNavArea * > areasToDisconnect;
  1928. for ( int i=0; i<connectedAreas->Count(); ++i )
  1929. {
  1930. CNavArea *adjArea = connectedAreas->Element(i).area;
  1931. if ( !adjArea->HasNodes() )
  1932. continue;
  1933. Vector pos, adjPos;
  1934. float width;
  1935. area->ComputePortal( adjArea, (NavDirType)dir, &pos, &width );
  1936. adjArea->GetClosestPointOnArea( pos, &adjPos );
  1937. CNavNode *node = area->FindClosestNode( pos, (NavDirType)dir );
  1938. CNavNode *adjNode = adjArea->FindClosestNode( adjPos, OppositeDirection( (NavDirType)dir ) );
  1939. pos = *node->GetPosition();
  1940. adjPos = *adjNode->GetPosition();
  1941. if ( !node || !adjNode )
  1942. continue;
  1943. NavCornerType adjCornerType[2];
  1944. GetCornerTypesInDirection( OppositeDirection((NavDirType)dir), &adjCornerType[0], &adjCornerType[1] );
  1945. // From the stair's perspective, we can't go up more than step height to reach the adjacent area.
  1946. // Also, if the adjacent area has to jump up higher than StepHeight above the stair area to reach the stairs,
  1947. // there's an obstruction close to the adjacent area that could prevent walking from the stairs down.
  1948. if ( node->GetGroundHeightAboveNode( cornerType[0] ) > StepHeight )
  1949. {
  1950. areasToDisconnect.AddToTail( adjArea );
  1951. }
  1952. else if ( node->GetGroundHeightAboveNode( cornerType[1] ) > StepHeight )
  1953. {
  1954. areasToDisconnect.AddToTail( adjArea );
  1955. }
  1956. else if ( adjPos.z + adjNode->GetGroundHeightAboveNode( adjCornerType[0] ) > pos.z + StepHeight )
  1957. {
  1958. areasToDisconnect.AddToTail( adjArea );
  1959. }
  1960. else if ( adjPos.z + adjNode->GetGroundHeightAboveNode( adjCornerType[1] ) > pos.z + StepHeight )
  1961. {
  1962. areasToDisconnect.AddToTail( adjArea );
  1963. }
  1964. }
  1965. for ( int i=0; i<areasToDisconnect.Count(); ++i )
  1966. {
  1967. area->Disconnect( areasToDisconnect[i] );
  1968. }
  1969. }
  1970. }
  1971. // Test to prevent A->C if A->B->C. This can happen in doorways and dropdowns from rooftops.
  1972. // @TODO: find the root cause of A->C links.
  1973. FOR_EACH_VEC( TheNavAreas, it )
  1974. {
  1975. CNavArea *area = TheNavAreas[ it ];
  1976. CUtlVector< CNavArea * > areasToDisconnect;
  1977. for ( int dir=0; dir<NUM_DIRECTIONS; ++dir )
  1978. {
  1979. const NavConnectVector *connectedAreas = area->GetAdjacentAreas( (NavDirType)dir );
  1980. for ( int i=0; i<connectedAreas->Count(); ++i )
  1981. {
  1982. CNavArea *adjArea = connectedAreas->Element(i).area;
  1983. const NavConnectVector *adjConnectedAreas = adjArea->GetAdjacentAreas( (NavDirType)dir );
  1984. for ( int j=0; j<adjConnectedAreas->Count(); ++j )
  1985. {
  1986. CNavArea *farArea = adjConnectedAreas->Element(j).area;
  1987. if ( area->IsConnected( farArea, (NavDirType)dir ) )
  1988. {
  1989. areasToDisconnect.AddToTail( farArea );
  1990. }
  1991. }
  1992. }
  1993. }
  1994. for ( int i=0; i<areasToDisconnect.Count(); ++i )
  1995. {
  1996. area->Disconnect( areasToDisconnect[i] );
  1997. }
  1998. }
  1999. }
  2000. //--------------------------------------------------------------------------------------------------------------
  2001. /**
  2002. * Fix any spots where we there are nav nodes touching only corner-on-corner but we intend bots to be able to traverse
  2003. */
  2004. void CNavMesh::FixCornerOnCornerAreas( void )
  2005. {
  2006. const float MaxDrop = StepHeight; // don't make corner on corner areas that are too steep
  2007. FOR_EACH_VEC( TheNavAreas, it )
  2008. {
  2009. CNavArea *area = TheNavAreas[ it ];
  2010. // determine if we have any corners where the only nav area we touch is diagonally corner-to-corner.
  2011. // if there are, generate additional small (0.5 x 0.5 grid size) nav areas in the corners between
  2012. // them if map geometry allows and make connections in cardinal compass directions to create a path
  2013. // between the two areas.
  2014. //
  2015. // XXXXXXXXX XXXXXXXXX
  2016. // X X X X
  2017. // X other X ****X other X
  2018. // X X *newX X
  2019. // XXXXXXXXXXXXXXXXX => XXXXXXXXXXXXXXXXX
  2020. // X X X Xnew*
  2021. // X area X X area X****
  2022. // X X X X
  2023. // XXXXXXXXX XXXXXXXXX
  2024. //
  2025. // check each corner
  2026. for ( int iCorner = NORTH_WEST; iCorner < NUM_CORNERS; iCorner++ )
  2027. {
  2028. // get cardinal direction to right and left of this corner
  2029. NavDirType dirToRight = (NavDirType) iCorner;
  2030. NavDirType dirToLeft = (NavDirType) ( ( iCorner+3 ) % NUM_DIRECTIONS );
  2031. // if we have any connections on cardinal compass directions on edge on either side of corner we're OK, skip this nav area
  2032. if ( area->GetAdjacentCount( dirToLeft ) > 0 || area->GetAdjacentCount( dirToRight ) > 0 ||
  2033. area->GetIncomingConnections( dirToLeft )->Count() > 0 || area->GetIncomingConnections( dirToRight )->Count() > 0 )
  2034. continue;
  2035. Vector cornerPos = area->GetCorner( (NavCornerType) iCorner );
  2036. NavDirType dirToRightTwice = DirectionRight( dirToRight );
  2037. NavDirType dirToLeftTwice = DirectionLeft( dirToLeft );
  2038. NavDirType dirsAlongOtherEdge[2] = { dirToLeft, dirToRight };
  2039. NavDirType dirsAlongOurEdge[2] = { dirToLeftTwice, dirToRightTwice };
  2040. // consider 2 potential new nav areas, to left and right of the corner we're considering
  2041. for ( int iDir = 0; iDir < ARRAYSIZE( dirsAlongOtherEdge ); iDir++ )
  2042. {
  2043. NavDirType dirAlongOtherEdge = dirsAlongOtherEdge[iDir];
  2044. NavDirType dirAlongOurEdge = dirsAlongOurEdge[iDir];
  2045. // look at the point 0.5 grid units along edge of other nav area
  2046. Vector vecDeltaOtherEdge;
  2047. DirectionToVector2D( dirAlongOtherEdge, (Vector2D *) &vecDeltaOtherEdge );
  2048. vecDeltaOtherEdge.z = 0;
  2049. vecDeltaOtherEdge *= GenerationStepSize * 0.5;
  2050. Vector vecOtherEdgePos = cornerPos + vecDeltaOtherEdge;
  2051. // see if there is a nav area at that location
  2052. CNavArea *areaOther = GetNavArea( vecOtherEdgePos );
  2053. Assert( areaOther != area );
  2054. if ( !areaOther )
  2055. continue; // no other area in that location, we're not touching on corner
  2056. // see if we can move from our corner in that direction
  2057. trace_t result;
  2058. if ( !TraceAdjacentNode( 0, cornerPos, vecOtherEdgePos, &result, MaxDrop ) )
  2059. continue; // something is blocking movement, don't create additional nodes to aid movement
  2060. // get the corner of the other nav area that might touch our corner
  2061. int iCornerOther = ( ( iCorner + 2 ) % NUM_CORNERS );
  2062. Vector cornerPosOther = areaOther->GetCorner( (NavCornerType) iCornerOther );
  2063. if ( cornerPos != cornerPosOther )
  2064. continue; // that nav area does not touch us on corner
  2065. // we are touching corner-to-corner with the other nav area and don't have connections in cardinal directions around
  2066. // the corner that touches, this is a candidate to generate new small helper nav areas.
  2067. // calculate the corners of the 0.5 x 0.5 nav area we would consider building between us and the other nav area whose corner we touch
  2068. Vector vecDeltaOurEdge;
  2069. DirectionToVector2D( dirAlongOurEdge, (Vector2D *) &vecDeltaOurEdge );
  2070. vecDeltaOurEdge.z = 0;
  2071. vecDeltaOurEdge *= GenerationStepSize * 0.5;
  2072. Vector vecOurEdgePos = cornerPos + vecDeltaOurEdge;
  2073. Vector vecCorner[4];
  2074. vecCorner[0] = cornerPos + vecDeltaOtherEdge + vecDeltaOurEdge; // far corner of new nav area
  2075. vecCorner[1] = cornerPos + vecDeltaOtherEdge; // intersection of far edge of new nav area with other nav area we touch
  2076. vecCorner[2] = cornerPos; // common corner of this nav area, nav area we touch, and new nav area
  2077. vecCorner[3] = cornerPos + vecDeltaOurEdge; // intersection of far edge of new nav area with this nav area
  2078. CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_NONE, WALK_THRU_EVERYTHING );
  2079. if ( !TraceAdjacentNode( 0, vecCorner[1], vecCorner[0], &result, MaxDrop ) || // can we move from edge of other area to far corner of new node
  2080. !TraceAdjacentNode( 0, vecCorner[3], vecCorner[0], &result, MaxDrop ) ) // can we move from edge of this area to far corner of new node
  2081. continue; // new node would not fit
  2082. // as sanity check, make sure there's not already a nav area there, shouldn't be
  2083. CNavArea *areaTest = GetNavArea( vecCorner[0] );
  2084. Assert ( !areaTest );
  2085. if ( areaTest )
  2086. continue;
  2087. vecCorner[0] = result.endpos;
  2088. // create a new nav area
  2089. CNavArea *areaNew = CreateArea();
  2090. // arrange the corners of the new nav area by compass direction
  2091. Vector vecNW, vecNE, vecSE, vecSW;
  2092. ClassifyCorners( vecCorner, vecNW, vecNE, vecSE, vecSW );
  2093. areaNew->Build( vecNW, vecNE, vecSE, vecSW );
  2094. // add it to the nav area list
  2095. TheNavAreas.AddToTail( areaNew );
  2096. AddNavArea( areaNew );
  2097. areaNew->SetAttributes( area->GetAttributes() );
  2098. // reciprocally connect between this area and new area
  2099. area->ConnectTo( areaNew, dirAlongOtherEdge );
  2100. areaNew->ConnectTo( area, OppositeDirection( dirAlongOtherEdge ) );
  2101. // reciprocally connect between other area and new area
  2102. areaOther->ConnectTo( areaNew, dirAlongOurEdge );
  2103. areaNew->ConnectTo( areaOther, OppositeDirection( dirAlongOurEdge ) );
  2104. }
  2105. }
  2106. }
  2107. }
  2108. //--------------------------------------------------------------------------------------------------------------
  2109. /**
  2110. * Fix any areas where one nav area overhangs another and the two nav areas are connected. Subdivide the lower
  2111. * nav area such that the upper nav area doesn't overhang any area it's connected to.
  2112. */
  2113. void CNavMesh::SplitAreasUnderOverhangs( void )
  2114. {
  2115. // restart the whole process whenever this gets set to true
  2116. bool bRestartProcessing = false;
  2117. do
  2118. {
  2119. bRestartProcessing = false;
  2120. // iterate all nav areas
  2121. for ( int it = 0; it < TheNavAreas.Count() && !bRestartProcessing; it++ )
  2122. {
  2123. CNavArea *area = TheNavAreas[ it ];
  2124. Extent areaExtent;
  2125. area->GetExtent( &areaExtent );
  2126. // iterate all directions
  2127. for ( int dir = NORTH; dir < NUM_DIRECTIONS && !bRestartProcessing; dir++ )
  2128. {
  2129. // iterate all connections in that direction
  2130. const NavConnectVector *pConnections = area->GetAdjacentAreas( (NavDirType) dir );
  2131. for ( int iConnection = 0; iConnection < pConnections->Count() && !bRestartProcessing; iConnection++ )
  2132. {
  2133. CNavArea *otherArea = (*pConnections)[iConnection].area;
  2134. Extent otherAreaExtent;
  2135. otherArea->GetExtent( &otherAreaExtent );
  2136. // see if the area we are connected to overlaps our X/Y extents
  2137. if ( area->IsOverlapping( otherArea ) )
  2138. {
  2139. // if the upper area isn't at least crouch height above the lower area, this is some weird minor
  2140. // overlap, disregard it
  2141. const float flMinSeparation = HumanCrouchHeight;
  2142. if ( !( areaExtent.lo.z > otherAreaExtent.hi.z + flMinSeparation ) &&
  2143. !( otherAreaExtent.lo.z > areaExtent.hi.z + flMinSeparation ) )
  2144. continue;
  2145. // figure out which area is above and which is below
  2146. CNavArea *areaBelow = area, *areaAbove = otherArea;
  2147. NavDirType dirFromAboveToBelow = OppositeDirection( (NavDirType) dir );
  2148. if ( otherAreaExtent.lo.z < areaExtent.lo.z )
  2149. {
  2150. areaBelow = otherArea;
  2151. areaAbove = area;
  2152. dirFromAboveToBelow = OppositeDirection( dirFromAboveToBelow );
  2153. }
  2154. NavDirType dirFromBelowToAbove = OppositeDirection( dirFromAboveToBelow );
  2155. // Msg( "area %d overhangs area %d and is connected\n", areaAbove->GetID(), areaBelow->GetID() );
  2156. Extent extentBelow, extentAbove;
  2157. areaBelow->GetExtent( &extentBelow );
  2158. areaAbove->GetExtent( &extentAbove );
  2159. float splitCoord; // absolute world coordinate along which we will split lower nav area (X or Y, depending on axis we split on)
  2160. float splitLen; // length of the segment of lower nav area that is in shadow of the upper nav area
  2161. float splitEdgeSize; // current length of the edge of nav area that is getting split
  2162. bool bSplitAlongX = false;
  2163. // determine along what edge we are splitting and make some key measurements
  2164. if ( ( dirFromAboveToBelow == EAST ) || ( dirFromAboveToBelow == WEST ) )
  2165. {
  2166. splitEdgeSize = extentBelow.hi.x - extentBelow.lo.x;
  2167. if ( extentAbove.hi.x < extentBelow.hi.x )
  2168. {
  2169. splitCoord = extentAbove.hi.x;
  2170. splitLen = splitCoord - extentBelow.lo.x;
  2171. }
  2172. else
  2173. {
  2174. splitCoord = extentAbove.lo.x;
  2175. splitLen = extentBelow.hi.x - splitCoord;
  2176. }
  2177. }
  2178. else
  2179. {
  2180. splitEdgeSize = extentBelow.hi.y - extentBelow.lo.y;
  2181. bSplitAlongX = true;
  2182. if ( extentAbove.hi.y < extentBelow.hi.y )
  2183. {
  2184. splitCoord = extentAbove.hi.y;
  2185. splitLen = splitCoord - extentBelow.lo.y;
  2186. }
  2187. else
  2188. {
  2189. splitCoord = extentAbove.lo.y;
  2190. splitLen = extentBelow.hi.y - splitCoord;
  2191. }
  2192. }
  2193. Assert( splitLen >= 0 );
  2194. Assert( splitEdgeSize > 0 );
  2195. // if we split the lower nav area right where it's in shadow of the upper nav area, will it create a really tiny strip?
  2196. if ( splitLen < GenerationStepSize )
  2197. {
  2198. // if the "in shadow" part of the lower nav area is really small or the lower nav area is really small to begin with,
  2199. // don't split it, we're better off as is
  2200. if ( ( splitLen < GenerationStepSize*0.3 ) || ( splitEdgeSize <= GenerationStepSize * 2 ) )
  2201. continue;
  2202. // Move our split point so we don't create a really tiny strip on the lower nav area. Move the split point away from
  2203. // the upper nav area so the "in shadow" area expands to be GenerationStepSize. The checks above ensure we have room to do this.
  2204. float splitDelta = GenerationStepSize - splitLen;
  2205. splitCoord += splitDelta * ( ( ( dirFromAboveToBelow == NORTH ) || ( dirFromAboveToBelow == WEST ) ) ? -1 : 1 );
  2206. }
  2207. // remove any connections between the two areas (so they don't get inherited by the new areas when we split the lower area),
  2208. // but remember what the connections were.
  2209. bool bConnectionFromBelow = false, bConnectionFromAbove = false;
  2210. if ( areaBelow->IsConnected( areaAbove, dirFromBelowToAbove ) )
  2211. {
  2212. bConnectionFromBelow = true;
  2213. areaBelow->Disconnect( areaAbove );
  2214. }
  2215. if ( areaAbove->IsConnected( areaBelow, dirFromAboveToBelow ) )
  2216. {
  2217. bConnectionFromAbove = true;
  2218. areaAbove->Disconnect( areaBelow );
  2219. }
  2220. CNavArea *pNewAlpha = NULL,*pNewBeta = NULL;
  2221. // int idBelow = areaBelow->GetID();
  2222. // AddToSelectedSet( areaBelow );
  2223. // split the lower nav area
  2224. if ( areaBelow->SplitEdit( bSplitAlongX, splitCoord, &pNewAlpha, &pNewBeta ) )
  2225. {
  2226. // Msg( "Split area %d into %d and %d\n", idBelow, pNewAlpha->GetID(), pNewBeta->GetID() );
  2227. // determine which of the two new lower areas is the one *not* in shadow of the upper nav area. This is the one we want to
  2228. // reconnect to
  2229. CNavArea *pNewNonoverlappedArea = ( ( dirFromAboveToBelow == NORTH ) || ( dirFromAboveToBelow == WEST ) ) ? pNewAlpha : pNewBeta;
  2230. // restore the previous connections from the upper nav area to the new lower nav area that is not in shadow of the upper
  2231. if ( bConnectionFromAbove )
  2232. {
  2233. areaAbove->ConnectTo( pNewNonoverlappedArea, dirFromAboveToBelow );
  2234. }
  2235. if ( bConnectionFromBelow )
  2236. {
  2237. areaBelow->ConnectTo( pNewNonoverlappedArea, OppositeDirection( dirFromAboveToBelow ) );
  2238. }
  2239. // Now we need to just start the whole process over. We've just perturbed the list we're iterating on (removed a nav area, added two
  2240. // new ones, when we did the split), and it's possible we may have to subdivide a lower nav area twice if the upper nav area
  2241. // overhangs a corner of the lower area. We just start all over again each time we do a split until no more overhangs occur.
  2242. bRestartProcessing = true;
  2243. }
  2244. else
  2245. {
  2246. // Msg( "Failed to split area %d\n", idBelow );
  2247. }
  2248. }
  2249. }
  2250. }
  2251. }
  2252. }
  2253. while ( bRestartProcessing );
  2254. }
  2255. //--------------------------------------------------------------------------------------------------------------
  2256. bool TestForValidCrouchArea( CNavNode *node )
  2257. {
  2258. // must make sure we don't have a bogus crouch area. check up to JumpCrouchHeight above
  2259. // the node for a HumanCrouchHeight space.
  2260. CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_PLAYER_MOVEMENT, WALK_THRU_EVERYTHING );
  2261. trace_t tr;
  2262. Vector start( *node->GetPosition() );
  2263. Vector end( *node->GetPosition() );
  2264. end.z += JumpCrouchHeight;
  2265. Vector mins( 0, 0, 0 );
  2266. Vector maxs( GenerationStepSize, GenerationStepSize, HumanCrouchHeight );
  2267. UTIL_TraceHull(
  2268. start,
  2269. end,
  2270. mins,
  2271. maxs,
  2272. TheNavMesh->GetGenerationTraceMask(),
  2273. &filter,
  2274. &tr );
  2275. return ( !tr.allsolid );
  2276. }
  2277. //--------------------------------------------------------------------------------------------------------------
  2278. /**
  2279. * Make sure that if other* are similar, test is also close. Used in TestForValidJumpArea.
  2280. */
  2281. bool IsHeightDifferenceValid( float test, float other1, float other2, float other3 )
  2282. {
  2283. // Make sure the other nodes are level.
  2284. const float CloseDelta = StepHeight / 2;
  2285. if ( fabs( other1 - other2 ) > CloseDelta )
  2286. return true;
  2287. if ( fabs( other1 - other3 ) > CloseDelta )
  2288. return true;
  2289. if ( fabs( other2 - other3 ) > CloseDelta )
  2290. return true;
  2291. // Now make sure the test node is near the others. If it is more than StepHeight away,
  2292. // it'll form a distorted jump area.
  2293. const float MaxDelta = StepHeight;
  2294. if ( fabs( test - other1 ) > MaxDelta )
  2295. return false;
  2296. if ( fabs( test - other2 ) > MaxDelta )
  2297. return false;
  2298. if ( fabs( test - other3 ) > MaxDelta )
  2299. return false;
  2300. return true;
  2301. }
  2302. //--------------------------------------------------------------------------------------------------------------
  2303. /**
  2304. * Check that a 1x1 area with 'node' at the northwest corner has a valid shape - if 3 corners
  2305. * are flat, and the 4th is significantly higher or lower, it would form a jump area that bots
  2306. * can't navigate over well.
  2307. */
  2308. bool TestForValidJumpArea( CNavNode *node )
  2309. {
  2310. return true;
  2311. CNavNode *east = node->GetConnectedNode( EAST );
  2312. CNavNode *south = node->GetConnectedNode( SOUTH );
  2313. if ( !east || !south )
  2314. return false;
  2315. CNavNode *southEast = east->GetConnectedNode( SOUTH );
  2316. if ( !southEast )
  2317. return false;
  2318. if ( !IsHeightDifferenceValid(
  2319. node->GetPosition()->z,
  2320. south->GetPosition()->z,
  2321. southEast->GetPosition()->z,
  2322. east->GetPosition()->z ) )
  2323. return false;
  2324. if ( !IsHeightDifferenceValid(
  2325. south->GetPosition()->z,
  2326. node->GetPosition()->z,
  2327. southEast->GetPosition()->z,
  2328. east->GetPosition()->z ) )
  2329. return false;
  2330. if ( !IsHeightDifferenceValid(
  2331. southEast->GetPosition()->z,
  2332. south->GetPosition()->z,
  2333. node->GetPosition()->z,
  2334. east->GetPosition()->z ) )
  2335. return false;
  2336. if ( !IsHeightDifferenceValid(
  2337. east->GetPosition()->z,
  2338. south->GetPosition()->z,
  2339. southEast->GetPosition()->z,
  2340. node->GetPosition()->z ) )
  2341. return false;
  2342. return true;
  2343. }
  2344. //--------------------------------------------------------------------------------------------------------------
  2345. class TestOverlapping
  2346. {
  2347. Vector m_nw;
  2348. Vector m_ne;
  2349. Vector m_sw;
  2350. Vector m_se;
  2351. public:
  2352. TestOverlapping( const Vector &nw, const Vector &ne, const Vector &sw, const Vector &se ) :
  2353. m_nw( nw ), m_ne( ne ), m_sw( sw ), m_se( se )
  2354. {
  2355. }
  2356. // This approximates CNavArea::GetZ, so we can pretend our four corners delineate a nav area
  2357. float GetZ( const Vector &pos ) const
  2358. {
  2359. float dx = m_se.x - m_nw.x;
  2360. float dy = m_se.y - m_nw.y;
  2361. // guard against division by zero due to degenerate areas
  2362. if (dx == 0.0f || dy == 0.0f)
  2363. return m_ne.z;
  2364. float u = (pos.x - m_nw.x) / dx;
  2365. float v = (pos.y - m_nw.y) / dy;
  2366. // clamp Z values to (x,y) volume
  2367. if (u < 0.0f)
  2368. u = 0.0f;
  2369. else if (u > 1.0f)
  2370. u = 1.0f;
  2371. if (v < 0.0f)
  2372. v = 0.0f;
  2373. else if (v > 1.0f)
  2374. v = 1.0f;
  2375. float northZ = m_nw.z + u * (m_ne.z - m_nw.z);
  2376. float southZ = m_sw.z + u * (m_se.z - m_sw.z);
  2377. return northZ + v * (southZ - northZ);
  2378. }
  2379. bool OverlapsExistingArea( void )
  2380. {
  2381. CNavArea *overlappingArea = NULL;
  2382. CNavLadder *overlappingLadder = NULL;
  2383. Vector nw = m_nw;
  2384. Vector se = m_se;
  2385. Vector start = nw;
  2386. start.x += GenerationStepSize/2;
  2387. start.y += GenerationStepSize/2;
  2388. while ( start.x < se.x )
  2389. {
  2390. start.y = nw.y + GenerationStepSize/2;
  2391. while ( start.y < se.y )
  2392. {
  2393. start.z = GetZ( start );
  2394. Vector end = start;
  2395. start.z -= StepHeight;
  2396. end.z += HalfHumanHeight;
  2397. if ( TheNavMesh->FindNavAreaOrLadderAlongRay( start, end, &overlappingArea, &overlappingLadder, NULL ) )
  2398. {
  2399. if ( overlappingArea )
  2400. {
  2401. return true;
  2402. }
  2403. }
  2404. start.y += GenerationStepSize;
  2405. }
  2406. start.x += GenerationStepSize;
  2407. }
  2408. return false;
  2409. }
  2410. };
  2411. //--------------------------------------------------------------------------------------------------------------
  2412. /**
  2413. * Check if an rectangular area of the given size can be
  2414. * made starting from the given node as the NW corner.
  2415. * Only consider fully connected nodes for this check.
  2416. * All of the nodes within the test area must have the same attributes.
  2417. * All of the nodes must be approximately co-planar w.r.t the NW node's normal, with the
  2418. * exception of 1x1 areas which can be any angle.
  2419. */
  2420. bool CNavMesh::TestArea( CNavNode *node, int width, int height )
  2421. {
  2422. Vector normal = *node->GetNormal();
  2423. float d = -DotProduct( normal, *node->GetPosition() );
  2424. bool nodeCrouch = node->m_crouch[ SOUTH_EAST ];
  2425. // The area's interior will be the south-east side of this north-west node.
  2426. // If that interior space is blocked, there's no space to build an area.
  2427. if ( node->m_isBlocked[ SOUTH_EAST ] )
  2428. {
  2429. return false;
  2430. }
  2431. int nodeAttributes = node->GetAttributes() & ~NAV_MESH_CROUCH;
  2432. const float offPlaneTolerance = 5.0f;
  2433. CNavNode *vertNode, *horizNode;
  2434. vertNode = node;
  2435. int x,y;
  2436. for( y=0; y<height; y++ )
  2437. {
  2438. horizNode = vertNode;
  2439. for( x=0; x<width; x++ )
  2440. {
  2441. //
  2442. // Compute the crouch attributes for the test node, taking into account only the side(s) of the node
  2443. // that are in the area
  2444. // NOTE: The nodes on the south and east borders of an area aren't contained in the area. This means that
  2445. // crouch attributes and blocked state need to be checked to the south and east of the southEdge and eastEdge nodes.
  2446. bool horizNodeCrouch = false;
  2447. bool westEdge = (x == 0);
  2448. bool eastEdge = (x == width - 1);
  2449. bool northEdge = (y == 0);
  2450. bool southEdge = (y == height - 1);
  2451. // Check corners first
  2452. if ( northEdge && westEdge )
  2453. {
  2454. // The area's interior will be the south-east side of this north-west node.
  2455. // If that interior space is blocked, there's no space to build an area.
  2456. horizNodeCrouch = horizNode->m_crouch[ SOUTH_EAST ];
  2457. if ( horizNode->m_isBlocked[ SOUTH_EAST ] )
  2458. {
  2459. return false;
  2460. }
  2461. }
  2462. else if ( northEdge && eastEdge )
  2463. {
  2464. // interior space of the area extends one more cell to the east past the easternmost nodes.
  2465. // This means we need to check to the southeast as well as the southwest.
  2466. horizNodeCrouch = horizNode->m_crouch[ SOUTH_EAST ] || horizNode->m_crouch[ SOUTH_WEST ];
  2467. if ( horizNode->m_isBlocked[ SOUTH_EAST ] || horizNode->m_isBlocked[ SOUTH_WEST ] )
  2468. {
  2469. return false;
  2470. }
  2471. }
  2472. else if ( southEdge && westEdge )
  2473. {
  2474. // The interior space of the area extends one more cell to the south past the southernmost nodes.
  2475. // This means we need to check to the southeast as well as the southwest.
  2476. horizNodeCrouch = horizNode->m_crouch[ SOUTH_EAST ] || horizNode->m_crouch[ NORTH_EAST ];
  2477. if ( horizNode->m_isBlocked[ SOUTH_EAST ] || horizNode->m_isBlocked[ NORTH_EAST ] )
  2478. {
  2479. return false;
  2480. }
  2481. }
  2482. else if ( southEdge && eastEdge )
  2483. {
  2484. // This node is completely in the interior of the area, so we need to check in all directions.
  2485. horizNodeCrouch = (horizNode->GetAttributes() & NAV_MESH_CROUCH) != 0;
  2486. if ( horizNode->IsBlockedInAnyDirection() )
  2487. {
  2488. return false;
  2489. }
  2490. }
  2491. // check sides next
  2492. else if ( northEdge )
  2493. {
  2494. horizNodeCrouch = horizNode->m_crouch[ SOUTH_EAST ] || horizNode->m_crouch[ SOUTH_WEST ];
  2495. if ( horizNode->m_isBlocked[ SOUTH_EAST ] || horizNode->m_isBlocked[ SOUTH_WEST ] )
  2496. {
  2497. return false;
  2498. }
  2499. }
  2500. else if ( southEdge )
  2501. {
  2502. // This node is completely in the interior of the area, so we need to check in all directions.
  2503. horizNodeCrouch = (horizNode->GetAttributes() & NAV_MESH_CROUCH) != 0;
  2504. if ( horizNode->IsBlockedInAnyDirection() )
  2505. {
  2506. return false;
  2507. }
  2508. }
  2509. else if ( eastEdge )
  2510. {
  2511. // This node is completely in the interior of the area, so we need to check in all directions.
  2512. horizNodeCrouch = (horizNode->GetAttributes() & NAV_MESH_CROUCH) != 0;
  2513. if ( horizNode->IsBlockedInAnyDirection() )
  2514. {
  2515. return false;
  2516. }
  2517. }
  2518. else if ( westEdge )
  2519. {
  2520. horizNodeCrouch = horizNode->m_crouch[ SOUTH_EAST ] || horizNode->m_crouch[ NORTH_EAST ];
  2521. if ( horizNode->m_isBlocked[ SOUTH_EAST ] || horizNode->m_isBlocked[ NORTH_EAST ] )
  2522. {
  2523. return false;
  2524. }
  2525. }
  2526. // finally, we have a center node
  2527. else
  2528. {
  2529. // This node is completely in the interior of the area, so we need to check in all directions.
  2530. horizNodeCrouch = (horizNode->GetAttributes() & NAV_MESH_CROUCH) != 0;
  2531. if ( horizNode->IsBlockedInAnyDirection() )
  2532. {
  2533. return false;
  2534. }
  2535. }
  2536. // all nodes must be crouch/non-crouch
  2537. if ( nodeCrouch != horizNodeCrouch )
  2538. return false;
  2539. // all nodes must have the same non-crouch attributes
  2540. int horizNodeAttributes = horizNode->GetAttributes() & ~NAV_MESH_CROUCH;
  2541. if (horizNodeAttributes != nodeAttributes)
  2542. return false;
  2543. if (horizNode->IsCovered())
  2544. return false;
  2545. if (!horizNode->IsClosedCell())
  2546. return false;
  2547. if ( !CheckObstacles( horizNode, width, height, x, y ) )
  2548. return false;
  2549. horizNode = horizNode->GetConnectedNode( EAST );
  2550. if (horizNode == NULL)
  2551. return false;
  2552. // nodes must lie on/near the plane
  2553. if (width > 1 || height > 1)
  2554. {
  2555. float dist = (float)fabs( DotProduct( *horizNode->GetPosition(), normal ) + d );
  2556. if (dist > offPlaneTolerance)
  2557. return false;
  2558. }
  2559. }
  2560. // Check the final (x=width) node, the above only checks thru x=width-1
  2561. if ( !CheckObstacles( horizNode, width, height, x, y ) )
  2562. return false;
  2563. vertNode = vertNode->GetConnectedNode( SOUTH );
  2564. if (vertNode == NULL)
  2565. return false;
  2566. // nodes must lie on/near the plane
  2567. if (width > 1 || height > 1)
  2568. {
  2569. float dist = (float)fabs( DotProduct( *vertNode->GetPosition(), normal ) + d );
  2570. if (dist > offPlaneTolerance)
  2571. return false;
  2572. }
  2573. }
  2574. // check planarity of southern edge
  2575. if (width > 1 || height > 1)
  2576. {
  2577. horizNode = vertNode;
  2578. for( x=0; x<width; x++ )
  2579. {
  2580. if ( !CheckObstacles( horizNode, width, height, x, y ) )
  2581. return false;
  2582. horizNode = horizNode->GetConnectedNode( EAST );
  2583. if (horizNode == NULL)
  2584. return false;
  2585. // nodes must lie on/near the plane
  2586. float dist = (float)fabs( DotProduct( *horizNode->GetPosition(), normal ) + d );
  2587. if (dist > offPlaneTolerance)
  2588. return false;
  2589. }
  2590. // Check the final (x=width) node, the above only checks thru x=width-1
  2591. if ( !CheckObstacles( horizNode, width, height, x, y ) )
  2592. return false;
  2593. }
  2594. vertNode = node;
  2595. for( y=0; y<height; ++y )
  2596. {
  2597. horizNode = vertNode;
  2598. for( int x=0; x<width; ++x )
  2599. {
  2600. // look for odd jump areas (3 points on the ground, 1 point floating much higher or lower)
  2601. if ( !TestForValidJumpArea( horizNode ) )
  2602. {
  2603. return false;
  2604. }
  2605. // Now that we've done the quick checks, test for a valid crouch area.
  2606. // This finds pillars etc in the middle of 4 nodes, that weren't found initially.
  2607. if ( nodeCrouch && !TestForValidCrouchArea( horizNode ) )
  2608. {
  2609. return false;
  2610. }
  2611. horizNode = horizNode->GetConnectedNode( EAST );
  2612. }
  2613. vertNode = vertNode->GetConnectedNode( SOUTH );
  2614. }
  2615. if ( m_generationMode == GENERATE_INCREMENTAL )
  2616. {
  2617. // Incremental generation needs to check that it's not overlapping existing areas...
  2618. const Vector *nw = node->GetPosition();
  2619. vertNode = node;
  2620. for( int y=0; y<height; ++y )
  2621. {
  2622. vertNode = vertNode->GetConnectedNode( SOUTH );
  2623. }
  2624. const Vector *sw = vertNode->GetPosition();
  2625. horizNode = node;
  2626. for( int x=0; x<width; ++x )
  2627. {
  2628. horizNode = horizNode->GetConnectedNode( EAST );
  2629. }
  2630. const Vector *ne = horizNode->GetPosition();
  2631. vertNode = horizNode;
  2632. for( int y=0; y<height; ++y )
  2633. {
  2634. vertNode = vertNode->GetConnectedNode( SOUTH );
  2635. }
  2636. const Vector *se = vertNode->GetPosition();
  2637. TestOverlapping test( *nw, *ne, *sw, *se );
  2638. if ( test.OverlapsExistingArea() )
  2639. return false;
  2640. }
  2641. return true;
  2642. }
  2643. //--------------------------------------------------------------------------------------------------------------
  2644. /**
  2645. * Checks if a node has an untraversable obstacle in any direction to a neighbor.
  2646. * width and height are size of nav area this node would be a part of, x and y are node's position
  2647. * within that grid
  2648. */
  2649. bool CNavMesh::CheckObstacles( CNavNode *node, int width, int height, int x, int y )
  2650. {
  2651. // any area bigger than 1x1 can't have obstacles in any connection between nodes
  2652. if ( width > 1 || height > 1 )
  2653. {
  2654. if ( ( x > 0 ) && ( node->m_obstacleHeight[WEST] > MaxTraversableHeight ) )
  2655. return false;
  2656. if ( ( y > 0 ) && ( node->m_obstacleHeight[NORTH] > MaxTraversableHeight ) )
  2657. return false;
  2658. if ( ( x < width-1 ) && ( node->m_obstacleHeight[EAST] > MaxTraversableHeight ) )
  2659. return false;
  2660. if ( ( y < height-1 ) && ( node->m_obstacleHeight[SOUTH] > MaxTraversableHeight ) )
  2661. return false;
  2662. }
  2663. // 1x1 area can have obstacles, that area will get fixed up later
  2664. return true;
  2665. }
  2666. //--------------------------------------------------------------------------------------------------------------
  2667. /**
  2668. * Create a nav area, and mark all nodes it overlaps as "covered"
  2669. * NOTE: Nodes on the east and south edges are not included.
  2670. * Returns number of nodes covered by this area, or -1 for error;
  2671. */
  2672. int CNavMesh::BuildArea( CNavNode *node, int width, int height )
  2673. {
  2674. CNavNode *nwNode = node;
  2675. CNavNode *neNode = NULL;
  2676. CNavNode *swNode = NULL;
  2677. CNavNode *seNode = NULL;
  2678. CNavNode *vertNode = node;
  2679. CNavNode *horizNode;
  2680. int coveredNodes = 0;
  2681. for( int y=0; y<height; y++ )
  2682. {
  2683. horizNode = vertNode;
  2684. for( int x=0; x<width; x++ )
  2685. {
  2686. horizNode->Cover();
  2687. ++coveredNodes;
  2688. horizNode = horizNode->GetConnectedNode( EAST );
  2689. }
  2690. if (y == 0)
  2691. neNode = horizNode;
  2692. vertNode = vertNode->GetConnectedNode( SOUTH );
  2693. }
  2694. swNode = vertNode;
  2695. horizNode = vertNode;
  2696. for( int x=0; x<width; x++ )
  2697. {
  2698. horizNode = horizNode->GetConnectedNode( EAST );
  2699. }
  2700. seNode = horizNode;
  2701. if (!nwNode || !neNode || !seNode || !swNode)
  2702. {
  2703. Error( "BuildArea - NULL node.\n" );
  2704. return -1;
  2705. }
  2706. CNavArea *area = CreateArea();
  2707. if (area == NULL)
  2708. {
  2709. Error( "BuildArea: Out of memory.\n" );
  2710. return -1;
  2711. }
  2712. area->Build( nwNode, neNode, seNode, swNode );
  2713. TheNavAreas.AddToTail( area );
  2714. // since all internal nodes have the same attributes, set this area's attributes
  2715. area->SetAttributes( node->GetAttributes() );
  2716. // If any of the corners have an obstacle in the direction of another corner, then there's an internal obstruction of this nav node.
  2717. // Mark it as not mergable so it doesn't become a part of anything else and we will fix it up later.
  2718. if ( nwNode->m_obstacleHeight[SOUTH] > MaxTraversableHeight || nwNode->m_obstacleHeight[EAST] > MaxTraversableHeight ||
  2719. neNode->m_obstacleHeight[WEST] > MaxTraversableHeight || neNode->m_obstacleHeight[SOUTH] > MaxTraversableHeight ||
  2720. seNode->m_obstacleHeight[NORTH] > MaxTraversableHeight || seNode->m_obstacleHeight[WEST] > MaxTraversableHeight ||
  2721. swNode->m_obstacleHeight[EAST] > MaxTraversableHeight || swNode->m_obstacleHeight[NORTH] > MaxTraversableHeight )
  2722. {
  2723. Assert( width == 1 ); // We should only ever try to build a 1x1 area out of any two nodes that have an obstruction between them
  2724. Assert( height == 1 );
  2725. area->SetAttributes( area->GetAttributes() | NAV_MESH_NO_MERGE );
  2726. }
  2727. // Check that the node was crouch in the right direction
  2728. bool nodeCrouch = node->m_crouch[ SOUTH_EAST ];
  2729. if ( (area->GetAttributes() & NAV_MESH_CROUCH) && !nodeCrouch )
  2730. {
  2731. area->SetAttributes( area->GetAttributes() & ~NAV_MESH_CROUCH );
  2732. }
  2733. return coveredNodes;
  2734. }
  2735. //--------------------------------------------------------------------------------------------------------------
  2736. /**
  2737. * This function uses the CNavNodes that have been sampled from the map to
  2738. * generate CNavAreas - rectangular areas of "walkable" space. These areas
  2739. * are connected to each other, proving information on know how to move from
  2740. * area to area.
  2741. *
  2742. * This is a "greedy" algorithm that attempts to cover the walkable area
  2743. * with the fewest, largest, rectangles.
  2744. */
  2745. void CNavMesh::CreateNavAreasFromNodes( void )
  2746. {
  2747. // haven't yet seen a map use larger than 30...
  2748. int tryWidth = nav_area_max_size.GetInt();
  2749. int tryHeight = tryWidth;
  2750. int uncoveredNodes = CNavNode::GetListLength();
  2751. while( uncoveredNodes > 0 )
  2752. {
  2753. for( CNavNode *node = CNavNode::GetFirst(); node; node = node->GetNext() )
  2754. {
  2755. if (node->IsCovered())
  2756. continue;
  2757. if (TestArea( node, tryWidth, tryHeight ))
  2758. {
  2759. int covered = BuildArea( node, tryWidth, tryHeight );
  2760. if (covered < 0)
  2761. {
  2762. Error( "Generate: Error - Data corrupt.\n" );
  2763. return;
  2764. }
  2765. uncoveredNodes -= covered;
  2766. }
  2767. }
  2768. if (tryWidth >= tryHeight)
  2769. --tryWidth;
  2770. else
  2771. --tryHeight;
  2772. if (tryWidth <= 0 || tryHeight <= 0)
  2773. break;
  2774. }
  2775. if ( !TheNavAreas.Count() )
  2776. {
  2777. // If we somehow have no areas, don't try to create an impossibly-large grid
  2778. AllocateGrid( 0, 0, 0, 0 );
  2779. return;
  2780. }
  2781. Extent extent;
  2782. extent.lo.x = 9999999999.9f;
  2783. extent.lo.y = 9999999999.9f;
  2784. extent.hi.x = -9999999999.9f;
  2785. extent.hi.y = -9999999999.9f;
  2786. // compute total extent
  2787. FOR_EACH_VEC( TheNavAreas, it )
  2788. {
  2789. CNavArea *area = TheNavAreas[ it ];
  2790. Extent areaExtent;
  2791. area->GetExtent( &areaExtent );
  2792. if (areaExtent.lo.x < extent.lo.x)
  2793. extent.lo.x = areaExtent.lo.x;
  2794. if (areaExtent.lo.y < extent.lo.y)
  2795. extent.lo.y = areaExtent.lo.y;
  2796. if (areaExtent.hi.x > extent.hi.x)
  2797. extent.hi.x = areaExtent.hi.x;
  2798. if (areaExtent.hi.y > extent.hi.y)
  2799. extent.hi.y = areaExtent.hi.y;
  2800. }
  2801. // add the areas to the grid
  2802. AllocateGrid( extent.lo.x, extent.hi.x, extent.lo.y, extent.hi.y );
  2803. FOR_EACH_VEC( TheNavAreas, git )
  2804. {
  2805. AddNavArea( TheNavAreas[ git ] );
  2806. }
  2807. ConnectGeneratedAreas();
  2808. MarkPlayerClipAreas();
  2809. MarkJumpAreas(); // mark jump areas before we merge generated areas, so we don't merge jump and non-jump areas
  2810. MergeGeneratedAreas();
  2811. SplitAreasUnderOverhangs();
  2812. SquareUpAreas();
  2813. MarkStairAreas();
  2814. #ifndef CSTRIKE_DLL // CSBots use jump areas
  2815. StichAndRemoveJumpAreas();
  2816. #endif
  2817. HandleObstacleTopAreas();
  2818. FixUpGeneratedAreas();
  2819. /// @TODO: incremental generation doesn't create ladders yet
  2820. if ( m_generationMode != GENERATE_INCREMENTAL )
  2821. {
  2822. for ( int i=0; i<m_ladders.Count(); ++i )
  2823. {
  2824. CNavLadder *ladder = m_ladders[i];
  2825. ladder->ConnectGeneratedLadder( 0.0f );
  2826. }
  2827. }
  2828. }
  2829. //--------------------------------------------------------------------------------------------------------------
  2830. // adds walkable positions for any/all positions a mod specifies
  2831. void CNavMesh::AddWalkableSeeds( void )
  2832. {
  2833. CBaseEntity *spawn = gEntList.FindEntityByClassname( NULL, GetPlayerSpawnName() );
  2834. if (spawn )
  2835. {
  2836. // snap it to the sampling grid
  2837. Vector pos = spawn->GetAbsOrigin();
  2838. pos.x = TheNavMesh->SnapToGrid( pos.x );
  2839. pos.y = TheNavMesh->SnapToGrid( pos.y );
  2840. Vector normal;
  2841. if ( FindGroundForNode( &pos, &normal ) )
  2842. {
  2843. AddWalkableSeed( pos, normal );
  2844. }
  2845. }
  2846. }
  2847. //--------------------------------------------------------------------------------------------------------------
  2848. /**
  2849. * Initiate the generation process
  2850. */
  2851. void CNavMesh::BeginGeneration( bool incremental )
  2852. {
  2853. IGameEvent *event = gameeventmanager->CreateEvent( "nav_generate" );
  2854. if ( event )
  2855. {
  2856. gameeventmanager->FireEvent( event );
  2857. }
  2858. #ifdef TERROR
  2859. engine->ServerCommand( "director_stop\nnb_delete_all\n" );
  2860. if ( !incremental && !engine->IsDedicatedServer() )
  2861. {
  2862. CBasePlayer *host = UTIL_GetListenServerHost();
  2863. if ( host )
  2864. {
  2865. host->ChangeTeam( TEAM_SPECTATOR );
  2866. }
  2867. }
  2868. #else
  2869. engine->ServerCommand( "bot_kick\n" );
  2870. #endif
  2871. // Right now, incrementally-generated areas won't connect to existing areas automatically.
  2872. // Since this means hand-editing will be necessary, don't do a full analyze.
  2873. if ( incremental )
  2874. {
  2875. nav_quicksave.SetValue( 1 );
  2876. }
  2877. m_generationState = SAMPLE_WALKABLE_SPACE;
  2878. m_sampleTick = 0;
  2879. m_generationMode = (incremental) ? GENERATE_INCREMENTAL : GENERATE_FULL;
  2880. lastMsgTime = 0.0f;
  2881. // clear any previous mesh
  2882. DestroyNavigationMesh( incremental );
  2883. SetNavPlace( UNDEFINED_PLACE );
  2884. // build internal representations of ladders, which are used to find new walkable areas
  2885. if ( !incremental ) ///< @incremental update doesn't build ladders to avoid overlapping existing ones
  2886. {
  2887. BuildLadders();
  2888. }
  2889. // start sampling from a spawn point
  2890. if ( !incremental )
  2891. {
  2892. AddWalkableSeeds();
  2893. }
  2894. // the system will see this NULL and select the next walkable seed
  2895. m_currentNode = NULL;
  2896. // if there are no seed points, we can't generate
  2897. if (m_walkableSeeds.Count() == 0)
  2898. {
  2899. m_generationMode = GENERATE_NONE;
  2900. Msg( "No valid walkable seed positions. Cannot generate Navigation Mesh.\n" );
  2901. return;
  2902. }
  2903. // initialize seed list index
  2904. m_seedIdx = 0;
  2905. Msg( "Generating Navigation Mesh...\n" );
  2906. m_generationStartTime = Plat_FloatTime();
  2907. }
  2908. //--------------------------------------------------------------------------------------------------------------
  2909. /**
  2910. * Re-analyze an existing Mesh. Determine Hiding Spots, Encounter Spots, etc.
  2911. */
  2912. void CNavMesh::BeginAnalysis( bool quitWhenFinished )
  2913. {
  2914. #ifdef TERROR
  2915. if ( !engine->IsDedicatedServer() )
  2916. {
  2917. CBasePlayer *host = UTIL_GetListenServerHost();
  2918. if ( host )
  2919. {
  2920. host->ChangeTeam( TEAM_SPECTATOR );
  2921. engine->ServerCommand( "director_no_death_check 1\ndirector_stop\nnb_delete_all\n" );
  2922. ConVarRef mat_fullbright( "mat_fullbright" );
  2923. ConVarRef mat_hdr_level( "mat_hdr_level" );
  2924. if( mat_fullbright.GetBool() )
  2925. {
  2926. Warning( "Setting mat_fullbright 0\n" );
  2927. mat_fullbright.SetValue( 0 );
  2928. }
  2929. if ( mat_hdr_level.GetInt() < 2 )
  2930. {
  2931. Warning( "Enabling HDR and reloading materials\n" );
  2932. mat_hdr_level.SetValue( 2 );
  2933. engine->ClientCommand( host->edict(), "mat_reloadallmaterials\n" );
  2934. }
  2935. // Running a threaded server breaks our lighting calculations
  2936. ConVarRef host_thread_mode( "host_thread_mode" );
  2937. m_hostThreadModeRestoreValue = host_thread_mode.GetInt();
  2938. host_thread_mode.SetValue( 0 );
  2939. ConVarRef mat_queue_mode( "mat_queue_mode" );
  2940. mat_queue_mode.SetValue( 0 );
  2941. }
  2942. }
  2943. #endif
  2944. // Remove and re-add elements in TheNavAreas, to ensure indices are useful for progress feedback
  2945. NavAreaVector tmpSet;
  2946. {
  2947. FOR_EACH_VEC( TheNavAreas, it )
  2948. {
  2949. tmpSet.AddToTail( TheNavAreas[it] );
  2950. }
  2951. }
  2952. TheNavAreas.RemoveAll();
  2953. {
  2954. FOR_EACH_VEC( tmpSet, it )
  2955. {
  2956. TheNavAreas.AddToTail( tmpSet[it] );
  2957. }
  2958. }
  2959. CBaseEntity* pEnt = NULL;
  2960. while ( (pEnt = gEntList.FindEntityByClassname( pEnt, "point_hiding_spot" )) != NULL )
  2961. {
  2962. UTIL_Remove( pEnt );
  2963. }
  2964. DestroyHidingSpots();
  2965. m_generationState = FIND_HIDING_SPOTS;
  2966. m_generationIndex = 0;
  2967. m_generationMode = GENERATE_ANALYSIS_ONLY;
  2968. m_bQuitWhenFinished = quitWhenFinished;
  2969. lastMsgTime = 0.0f;
  2970. m_generationStartTime = Plat_FloatTime();
  2971. }
  2972. //--------------------------------------------------------------------------------------------------------------
  2973. void ShowViewPortPanelToAll( const char * name, bool bShow, KeyValues *data )
  2974. {
  2975. CRecipientFilter filter;
  2976. filter.AddAllPlayers();
  2977. filter.MakeReliable();
  2978. int count = 0;
  2979. KeyValues *subkey = NULL;
  2980. if ( data )
  2981. {
  2982. subkey = data->GetFirstSubKey();
  2983. while ( subkey )
  2984. {
  2985. count++; subkey = subkey->GetNextKey();
  2986. }
  2987. subkey = data->GetFirstSubKey(); // reset
  2988. }
  2989. CCSUsrMsg_VGUIMenu msg;
  2990. msg.set_name( name );
  2991. msg.set_show( bShow );
  2992. // write additional data (be careful not more than 192 bytes!)
  2993. while ( subkey )
  2994. {
  2995. CCSUsrMsg_VGUIMenu::Subkey *pMsgSubkey = msg.add_subkeys();
  2996. pMsgSubkey->set_name( subkey->GetName() );
  2997. pMsgSubkey->set_str( subkey-> GetString() );
  2998. subkey = subkey->GetNextKey();
  2999. }
  3000. SendUserMessage( filter, CS_UM_VGUIMenu, msg );
  3001. }
  3002. //--------------------------------------------------------------------------------------------------------------
  3003. static void AnalysisProgress( const char *msg, int ticks, int current, bool showPercent = true )
  3004. {
  3005. const float MsgInterval = 10.0f;
  3006. float now = Plat_FloatTime();
  3007. if ( now > lastMsgTime + MsgInterval )
  3008. {
  3009. if ( showPercent && ticks )
  3010. {
  3011. Msg( "%s %.0f%%\n", msg, current*100.0f/ticks );
  3012. }
  3013. else
  3014. {
  3015. Msg( "%s\n", msg );
  3016. }
  3017. lastMsgTime = now;
  3018. }
  3019. KeyValues *data = new KeyValues("data");
  3020. data->SetString( "msg", msg );
  3021. data->SetInt( "total", ticks );
  3022. data->SetInt( "current", current );
  3023. ShowViewPortPanelToAll( PANEL_NAV_PROGRESS, true, data );
  3024. data->deleteThis();
  3025. }
  3026. //--------------------------------------------------------------------------------------------------------------
  3027. static void HideAnalysisProgress( void )
  3028. {
  3029. KeyValues *data = new KeyValues("data");
  3030. ShowViewPortPanelToAll( PANEL_NAV_PROGRESS, false, data );
  3031. data->deleteThis();
  3032. }
  3033. //--------------------------------------------------------------------------------------------------------------
  3034. /**
  3035. * Process the auto-generation for 'maxTime' seconds. return false if generation is complete.
  3036. */
  3037. bool CNavMesh::UpdateGeneration( float maxTime )
  3038. {
  3039. double startTime = Plat_FloatTime();
  3040. static unsigned int s_movedPlayerToArea = 0; // Last area we moved a player to for lighting calcs
  3041. static CountdownTimer s_playerSettleTimer; // Settle time after moving the player for lighting calcs
  3042. static CUtlVector<CNavArea *> s_unlitAreas;
  3043. static CUtlVector<CNavArea *> s_unlitSeedAreas;
  3044. static ConVarRef host_thread_mode( "host_thread_mode" );
  3045. switch( m_generationState )
  3046. {
  3047. //---------------------------------------------------------------------------
  3048. case SAMPLE_WALKABLE_SPACE:
  3049. {
  3050. AnalysisProgress( "Sampling walkable space...", 100, m_sampleTick / 10, false );
  3051. m_sampleTick = ( m_sampleTick + 1 ) % 1000;
  3052. while ( SampleStep() )
  3053. {
  3054. if ( Plat_FloatTime() - startTime > maxTime )
  3055. {
  3056. return true;
  3057. }
  3058. }
  3059. // sampling is complete, now build nav areas
  3060. m_generationState = CREATE_AREAS_FROM_SAMPLES;
  3061. return true;
  3062. }
  3063. //---------------------------------------------------------------------------
  3064. case CREATE_AREAS_FROM_SAMPLES:
  3065. {
  3066. Msg( "Creating navigation areas from sampled data...\n" );
  3067. // Select all pre-existing areas
  3068. if ( m_generationMode == GENERATE_INCREMENTAL )
  3069. {
  3070. ClearSelectedSet();
  3071. FOR_EACH_VEC( TheNavAreas, nit )
  3072. {
  3073. CNavArea *area = TheNavAreas[nit];
  3074. AddToSelectedSet( area );
  3075. }
  3076. }
  3077. // Create new areas
  3078. CreateNavAreasFromNodes();
  3079. // And toggle the selection, so we end up with the new areas
  3080. if ( m_generationMode == GENERATE_INCREMENTAL )
  3081. {
  3082. CommandNavToggleSelectedSet();
  3083. }
  3084. DestroyHidingSpots();
  3085. // Remove and re-add elements in TheNavAreas, to ensure indices are useful for progress feedback
  3086. NavAreaVector tmpSet;
  3087. {
  3088. FOR_EACH_VEC( TheNavAreas, it )
  3089. {
  3090. tmpSet.AddToTail( TheNavAreas[it] );
  3091. }
  3092. }
  3093. TheNavAreas.RemoveAll();
  3094. {
  3095. FOR_EACH_VEC( tmpSet, it )
  3096. {
  3097. TheNavAreas.AddToTail( tmpSet[it] );
  3098. }
  3099. }
  3100. m_generationState = FIND_HIDING_SPOTS;
  3101. m_generationIndex = 0;
  3102. return true;
  3103. }
  3104. //---------------------------------------------------------------------------
  3105. case FIND_HIDING_SPOTS:
  3106. {
  3107. while( m_generationIndex < TheNavAreas.Count() )
  3108. {
  3109. CNavArea *area = TheNavAreas[ m_generationIndex ];
  3110. ++m_generationIndex;
  3111. area->ComputeHidingSpots();
  3112. // don't go over our time allotment
  3113. if( Plat_FloatTime() - startTime > maxTime )
  3114. {
  3115. AnalysisProgress( "Finding hiding spots...", 100, 100 * m_generationIndex / TheNavAreas.Count() );
  3116. return true;
  3117. }
  3118. }
  3119. Msg( "Finding hiding spots...DONE\n" );
  3120. m_generationState = FIND_ENCOUNTER_SPOTS;
  3121. m_generationIndex = 0;
  3122. return true;
  3123. }
  3124. //---------------------------------------------------------------------------
  3125. case FIND_ENCOUNTER_SPOTS:
  3126. {
  3127. while( m_generationIndex < TheNavAreas.Count() )
  3128. {
  3129. CNavArea *area = TheNavAreas[ m_generationIndex ];
  3130. ++m_generationIndex;
  3131. area->ComputeSpotEncounters();
  3132. // don't go over our time allotment
  3133. if( Plat_FloatTime() - startTime > maxTime )
  3134. {
  3135. AnalysisProgress( "Finding encounter spots...", 100, 100 * m_generationIndex / TheNavAreas.Count() );
  3136. return true;
  3137. }
  3138. }
  3139. Msg( "Finding encounter spots...DONE\n" );
  3140. m_generationState = FIND_SNIPER_SPOTS;
  3141. m_generationIndex = 0;
  3142. return true;
  3143. }
  3144. //---------------------------------------------------------------------------
  3145. case FIND_SNIPER_SPOTS:
  3146. {
  3147. while( m_generationIndex < TheNavAreas.Count() )
  3148. {
  3149. CNavArea *area = TheNavAreas[ m_generationIndex ];
  3150. ++m_generationIndex;
  3151. area->ComputeSniperSpots();
  3152. // don't go over our time allotment
  3153. if( Plat_FloatTime() - startTime > maxTime )
  3154. {
  3155. AnalysisProgress( "Finding sniper spots...", 100, 100 * m_generationIndex / TheNavAreas.Count() );
  3156. return true;
  3157. }
  3158. }
  3159. Msg( "Finding sniper spots...DONE\n" );
  3160. if ( IsMeshVisibilityGenerated() )
  3161. {
  3162. m_generationState = COMPUTE_MESH_VISIBILITY;
  3163. m_generationIndex = 0;
  3164. BeginVisibilityComputations();
  3165. Msg( "Computing mesh visibility...\n" );
  3166. }
  3167. else
  3168. {
  3169. // no visibility data desired - skip directly to next analyze step
  3170. FOR_EACH_VEC( TheNavAreas, it )
  3171. {
  3172. CNavArea *area = TheNavAreas[ it ];
  3173. area->ResetPotentiallyVisibleAreas();
  3174. }
  3175. m_generationState = FIND_EARLIEST_OCCUPY_TIMES;
  3176. m_generationIndex = 0;
  3177. }
  3178. return true;
  3179. }
  3180. //---------------------------------------------------------------------------
  3181. case COMPUTE_MESH_VISIBILITY:
  3182. {
  3183. while( m_generationIndex < TheNavAreas.Count() )
  3184. {
  3185. CNavArea *area = TheNavAreas[ m_generationIndex ];
  3186. ++m_generationIndex;
  3187. area->ComputeVisibilityToMesh();
  3188. // don't go over our time allotment
  3189. if ( Plat_FloatTime() - startTime > maxTime )
  3190. {
  3191. AnalysisProgress( "Computing mesh visibility...", 100, 100 * m_generationIndex / TheNavAreas.Count() );
  3192. return true;
  3193. }
  3194. }
  3195. Msg( "Optimizing mesh visibility...\n" );
  3196. EndVisibilityComputations();
  3197. Msg( "Computing mesh visibility...DONE\n" );
  3198. m_generationState = FIND_EARLIEST_OCCUPY_TIMES;
  3199. m_generationIndex = 0;
  3200. return true;
  3201. }
  3202. //---------------------------------------------------------------------------
  3203. case FIND_EARLIEST_OCCUPY_TIMES:
  3204. {
  3205. while( m_generationIndex < TheNavAreas.Count() )
  3206. {
  3207. CNavArea *area = TheNavAreas[ m_generationIndex ];
  3208. ++m_generationIndex;
  3209. area->ComputeEarliestOccupyTimes();
  3210. // don't go over our time allotment
  3211. if( Plat_FloatTime() - startTime > maxTime )
  3212. {
  3213. AnalysisProgress( "Finding earliest occupy times...", 100, 100 * m_generationIndex / TheNavAreas.Count() );
  3214. return true;
  3215. }
  3216. }
  3217. Msg( "Finding earliest occupy times...DONE\n" );
  3218. #ifdef NAV_ANALYZE_LIGHT_INTENSITY
  3219. bool shouldSkipLightComputation = ( m_generationMode == GENERATE_INCREMENTAL || engine->IsDedicatedServer() );
  3220. #else
  3221. bool shouldSkipLightComputation = true;
  3222. #endif
  3223. if ( shouldSkipLightComputation )
  3224. {
  3225. m_generationState = CUSTOM; // no light intensity calcs for incremental generation or dedicated servers
  3226. }
  3227. else
  3228. {
  3229. m_generationState = FIND_LIGHT_INTENSITY;
  3230. s_playerSettleTimer.Invalidate();
  3231. CNavArea::MakeNewMarker();
  3232. s_unlitAreas.RemoveAll();
  3233. FOR_EACH_VEC( TheNavAreas, nit )
  3234. {
  3235. s_unlitAreas.AddToTail( TheNavAreas[nit] );
  3236. s_unlitSeedAreas.AddToTail( TheNavAreas[nit] );
  3237. }
  3238. }
  3239. m_generationIndex = 0;
  3240. return true;
  3241. }
  3242. //---------------------------------------------------------------------------
  3243. case FIND_LIGHT_INTENSITY:
  3244. {
  3245. host_thread_mode.SetValue( 0 ); // need non-threaded server for light calcs
  3246. CBasePlayer *host = UTIL_GetListenServerHost();
  3247. if ( !s_unlitAreas.Count() || !host )
  3248. {
  3249. Msg( "Finding light intensity...DONE\n" );
  3250. m_generationState = CUSTOM;
  3251. m_generationIndex = 0;
  3252. return true;
  3253. }
  3254. if ( !s_playerSettleTimer.IsElapsed() )
  3255. return true; // wait for eyePos to settle
  3256. // Now try to compute lighting for remaining areas
  3257. int sit = 0;
  3258. while( sit < s_unlitAreas.Count() )
  3259. {
  3260. CNavArea *area = s_unlitAreas[sit];
  3261. if ( area->ComputeLighting() )
  3262. {
  3263. s_unlitSeedAreas.FindAndRemove( area );
  3264. s_unlitAreas.Remove( sit );
  3265. continue;
  3266. }
  3267. else
  3268. {
  3269. ++sit;
  3270. }
  3271. }
  3272. if ( s_unlitAreas.Count() )
  3273. {
  3274. if ( s_unlitSeedAreas.Count() )
  3275. {
  3276. CNavArea *moveArea = s_unlitSeedAreas[0];
  3277. s_unlitSeedAreas.FastRemove( 0 );
  3278. //Msg( "Moving to new area %d to compute lighting for %d/%d areas\n", moveArea->GetID(), s_unlitAreas.Count(), TheNavAreas.Count() );
  3279. Vector eyePos = moveArea->GetCenter();
  3280. float height;
  3281. if ( GetGroundHeight( eyePos, &height ) )
  3282. {
  3283. eyePos.z = height + HalfHumanHeight - StepHeight; // players light from their centers, and we light from slightly below that, to allow for low ceilings
  3284. }
  3285. else
  3286. {
  3287. eyePos.z += HalfHumanHeight - StepHeight; // players light from their centers, and we light from slightly below that, to allow for low ceilings
  3288. }
  3289. host->SetAbsOrigin( eyePos );
  3290. AnalysisProgress( "Finding light intensity...", 100, 100 * (TheNavAreas.Count() - s_unlitAreas.Count()) / TheNavAreas.Count() );
  3291. s_movedPlayerToArea = moveArea->GetID();
  3292. s_playerSettleTimer.Start( 0.1f );
  3293. return true;
  3294. }
  3295. else
  3296. {
  3297. Msg( "Finding light intensity...DONE (%d unlit areas)\n", s_unlitAreas.Count() );
  3298. if ( s_unlitAreas.Count() )
  3299. {
  3300. Warning( "To see unlit areas:\n" );
  3301. for ( int sit=0; sit<s_unlitAreas.Count(); ++sit )
  3302. {
  3303. CNavArea *area = s_unlitAreas[ sit ];
  3304. Warning( "nav_unmark; nav_mark %d; nav_warp_to_mark;\n", area->GetID() );
  3305. }
  3306. }
  3307. m_generationState = CUSTOM;
  3308. m_generationIndex = 0;
  3309. }
  3310. }
  3311. Msg( "Finding light intensity...DONE\n" );
  3312. m_generationState = CUSTOM;
  3313. m_generationIndex = 0;
  3314. return true;
  3315. }
  3316. //---------------------------------------------------------------------------
  3317. case CUSTOM:
  3318. {
  3319. if ( m_generationIndex == 0 )
  3320. {
  3321. BeginCustomAnalysis( m_generationMode == GENERATE_INCREMENTAL );
  3322. Msg( "Start custom...\n ");
  3323. }
  3324. while( m_generationIndex < TheNavAreas.Count() )
  3325. {
  3326. CNavArea *area = TheNavAreas[ m_generationIndex ];
  3327. ++m_generationIndex;
  3328. area->CustomAnalysis( m_generationMode == GENERATE_INCREMENTAL );
  3329. // don't go over our time allotment
  3330. if( Plat_FloatTime() - startTime > maxTime )
  3331. {
  3332. AnalysisProgress( "Custom game-specific analysis...", 100, 100 * m_generationIndex / TheNavAreas.Count() );
  3333. return true;
  3334. }
  3335. }
  3336. Msg( "Post custom...\n ");
  3337. PostCustomAnalysis();
  3338. EndCustomAnalysis();
  3339. Msg( "Custom game-specific analysis...DONE\n" );
  3340. m_generationState = SAVE_NAV_MESH;
  3341. m_generationIndex = 0;
  3342. ConVarRef mat_queue_mode( "mat_queue_mode" );
  3343. mat_queue_mode.SetValue( -1 );
  3344. host_thread_mode.SetValue( m_hostThreadModeRestoreValue ); // restore this
  3345. return true;
  3346. }
  3347. //---------------------------------------------------------------------------
  3348. case SAVE_NAV_MESH:
  3349. {
  3350. if ( m_generationMode == GENERATE_ANALYSIS_ONLY || m_generationMode == GENERATE_FULL )
  3351. {
  3352. m_isAnalyzed = true;
  3353. }
  3354. // generation complete!
  3355. float generationTime = Plat_FloatTime() - m_generationStartTime;
  3356. Msg( "Generation complete! %0.1f seconds elapsed.\n", generationTime );
  3357. bool restart = m_generationMode != GENERATE_INCREMENTAL;
  3358. m_generationMode = GENERATE_NONE;
  3359. m_isLoaded = true;
  3360. ClearWalkableSeeds();
  3361. HideAnalysisProgress();
  3362. // save the mesh
  3363. if (Save())
  3364. {
  3365. Msg( "Navigation map '%s' saved.\n", GetFilename() );
  3366. }
  3367. else
  3368. {
  3369. const char *filename = GetFilename();
  3370. Msg( "ERROR: Cannot save navigation map '%s'.\n", (filename) ? filename : "(null)" );
  3371. }
  3372. if ( m_bQuitWhenFinished )
  3373. {
  3374. engine->ServerCommand( "quit\n" );
  3375. }
  3376. else if ( restart )
  3377. {
  3378. engine->ChangeLevel( STRING( gpGlobals->mapname ), NULL );
  3379. }
  3380. else
  3381. {
  3382. FOR_EACH_VEC( TheNavAreas, it )
  3383. {
  3384. TheNavAreas[ it ]->ResetNodes();
  3385. }
  3386. #if !(DEBUG_NAV_NODES)
  3387. // destroy navigation nodes created during map generation
  3388. CNavNode *node, *next;
  3389. for( node = CNavNode::m_list; node; node = next )
  3390. {
  3391. next = node->m_next;
  3392. delete node;
  3393. }
  3394. CNavNode::m_list = NULL;
  3395. CNavNode::m_listLength = 0;
  3396. CNavNode::m_nextID = 1;
  3397. #endif // !(DEBUG_NAV_NODES)
  3398. }
  3399. return false;
  3400. }
  3401. }
  3402. return false;
  3403. }
  3404. //--------------------------------------------------------------------------------------------------------------
  3405. /**
  3406. * Define the name of player spawn entities
  3407. */
  3408. void CNavMesh::SetPlayerSpawnName( const char *name )
  3409. {
  3410. if (m_spawnName)
  3411. {
  3412. delete [] m_spawnName;
  3413. }
  3414. m_spawnName = new char [ strlen(name) + 1 ];
  3415. strcpy( m_spawnName, name );
  3416. }
  3417. //--------------------------------------------------------------------------------------------------------------
  3418. /**
  3419. * Return name of player spawn entity
  3420. */
  3421. const char *CNavMesh::GetPlayerSpawnName( void ) const
  3422. {
  3423. if (m_spawnName)
  3424. return m_spawnName;
  3425. // default value
  3426. return "info_player_start";
  3427. }
  3428. //--------------------------------------------------------------------------------------------------------------
  3429. /**
  3430. * Add a nav node and connect it.
  3431. * Node Z positions are ground level.
  3432. */
  3433. CNavNode *CNavMesh::AddNode( const Vector &destPos, const Vector &normal, NavDirType dir, CNavNode *source, bool isOnDisplacement,
  3434. float obstacleHeight, float obstacleStartDist, float obstacleEndDist )
  3435. {
  3436. // check if a node exists at this location
  3437. CNavNode *node = CNavNode::GetNode( destPos );
  3438. // if no node exists, create one
  3439. bool useNew = false;
  3440. if (node == NULL)
  3441. {
  3442. node = new CNavNode( destPos, normal, source, isOnDisplacement );
  3443. OnNodeAdded( node );
  3444. useNew = true;
  3445. }
  3446. // connect source node to new node
  3447. source->ConnectTo( node, dir, obstacleHeight, obstacleStartDist, obstacleEndDist );
  3448. // optimization: if deltaZ changes very little, assume connection is commutative
  3449. const float zTolerance = 50.0f;
  3450. float deltaZ = source->GetPosition()->z - destPos.z;
  3451. if (fabs( deltaZ ) < zTolerance)
  3452. {
  3453. if ( obstacleHeight > 0 )
  3454. {
  3455. obstacleHeight = MAX( obstacleHeight + deltaZ, 0 );
  3456. Assert( obstacleHeight > 0 );
  3457. }
  3458. node->ConnectTo( source, OppositeDirection( dir ), obstacleHeight, GenerationStepSize - obstacleEndDist, GenerationStepSize - obstacleStartDist );
  3459. node->MarkAsVisited( OppositeDirection( dir ) );
  3460. }
  3461. if (useNew)
  3462. {
  3463. // new node becomes current node
  3464. m_currentNode = node;
  3465. }
  3466. node->CheckCrouch();
  3467. // determine if there's a cliff nearby and set an attribute on this node
  3468. for ( int i = 0; i < NUM_DIRECTIONS; i++ )
  3469. {
  3470. NavDirType dir = (NavDirType) i;
  3471. if ( CheckCliff( node->GetPosition(), dir ) )
  3472. {
  3473. node->SetAttributes( node->GetAttributes() | NAV_MESH_CLIFF );
  3474. break;
  3475. }
  3476. }
  3477. return node;
  3478. }
  3479. //--------------------------------------------------------------------------------------------------------------
  3480. inline CNavNode *LadderEndSearch( const Vector *pos, NavDirType mountDir )
  3481. {
  3482. Vector center = *pos;
  3483. AddDirectionVector( &center, mountDir, HalfHumanWidth );
  3484. //
  3485. // Test the ladder dismount point first, then each cardinal direction one and two steps away
  3486. //
  3487. for( int d=(-1); d<2*NUM_DIRECTIONS; ++d )
  3488. {
  3489. Vector tryPos = center;
  3490. if (d >= NUM_DIRECTIONS)
  3491. AddDirectionVector( &tryPos, (NavDirType)(d - NUM_DIRECTIONS), 2.0f*GenerationStepSize );
  3492. else if (d >= 0)
  3493. AddDirectionVector( &tryPos, (NavDirType)d, GenerationStepSize );
  3494. // step up a rung, to ensure adjacent floors are below us
  3495. tryPos.z += GenerationStepSize;
  3496. tryPos.x = TheNavMesh->SnapToGrid( tryPos.x );
  3497. tryPos.y = TheNavMesh->SnapToGrid( tryPos.y );
  3498. // adjust height to account for sloping areas
  3499. Vector tryNormal;
  3500. if (TheNavMesh->GetGroundHeight( tryPos, &tryPos.z, &tryNormal ) == false)
  3501. continue;
  3502. // make sure this point is not on the other side of a wall
  3503. const float fudge = 4.0f;
  3504. trace_t result;
  3505. UTIL_TraceHull( center + Vector( 0, 0, fudge ), tryPos + Vector( 0, 0, fudge ), NavTraceMins, NavTraceMaxs, TheNavMesh->GetGenerationTraceMask(), NULL, COLLISION_GROUP_NONE, &result );
  3506. if (result.fraction != 1.0f || result.startsolid)
  3507. continue;
  3508. // if no node exists here, create one and continue the search
  3509. if (CNavNode::GetNode( tryPos ) == NULL)
  3510. {
  3511. return new CNavNode( tryPos, tryNormal, NULL, false );
  3512. }
  3513. }
  3514. return NULL;
  3515. }
  3516. //--------------------------------------------------------------------------------------------------------------
  3517. bool CNavMesh::FindGroundForNode( Vector *pos, Vector *normal )
  3518. {
  3519. CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_PLAYER_MOVEMENT, WALK_THRU_EVERYTHING );
  3520. trace_t tr;
  3521. Vector start( pos->x, pos->y, pos->z + VEC_DUCK_HULL_MAX.z - 0.1f );
  3522. Vector end( *pos );
  3523. end.z -= DeathDrop;
  3524. UTIL_TraceHull(
  3525. start,
  3526. end,
  3527. NavTraceMins,
  3528. NavTraceMaxs,
  3529. GetGenerationTraceMask(),
  3530. &filter,
  3531. &tr );
  3532. *pos = tr.endpos;
  3533. *normal = tr.plane.normal;
  3534. return ( !tr.allsolid );
  3535. }
  3536. //--------------------------------------------------------------------------------------------------------------
  3537. void DrawTrace( const trace_t *trace )
  3538. {
  3539. /*
  3540. if ( trace->fraction > 0.0f && !trace->startsolid )
  3541. {
  3542. NDebugOverlay::SweptBox( trace->startpos, trace->endpos, NavTraceMins, NavTraceMaxs, vec3_angle, 0, 255, 0, 45, 100 );
  3543. }
  3544. else
  3545. {
  3546. NDebugOverlay::SweptBox( trace->startpos, trace->endpos, NavTraceMins, NavTraceMaxs, vec3_angle, 255, 0, 0, 45, 100 );
  3547. }
  3548. */
  3549. }
  3550. //--------------------------------------------------------------------------------------------------------------
  3551. bool StayOnFloor( trace_t *trace, float zLimit /* = DeathDrop */ )
  3552. {
  3553. Vector start( trace->endpos );
  3554. Vector end( start );
  3555. end.z -= zLimit;
  3556. CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_NONE, WALK_THRU_EVERYTHING );
  3557. UTIL_TraceHull( start, end, NavTraceMins, NavTraceMaxs, TheNavMesh->GetGenerationTraceMask(), &filter, trace );
  3558. DrawTrace( trace );
  3559. if ( trace->startsolid || trace->fraction >= 1.0f )
  3560. {
  3561. return false;
  3562. }
  3563. if ( trace->plane.normal.z < nav_slope_limit.GetFloat() )
  3564. {
  3565. return false;
  3566. }
  3567. return true;
  3568. }
  3569. //--------------------------------------------------------------------------------------------------------------
  3570. bool TraceAdjacentNode( int depth, const Vector& start, const Vector& end, trace_t *trace, float zLimit /* = DeathDrop */ )
  3571. {
  3572. const float MinDistance = 1.0f; // if we can't move at least this far, don't bother stepping up.
  3573. CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_NONE, WALK_THRU_EVERYTHING );
  3574. UTIL_TraceHull( start, end, NavTraceMins, NavTraceMaxs, TheNavMesh->GetGenerationTraceMask(), &filter, trace );
  3575. DrawTrace( trace );
  3576. // If we started in the ground for some reason, bail
  3577. if ( trace->startsolid )
  3578. return false;
  3579. // If we made it, so try to find the floor
  3580. if ( end.x == trace->endpos.x && end.y == trace->endpos.y )
  3581. {
  3582. return StayOnFloor( trace, zLimit );
  3583. }
  3584. // If we didn't make enough progress, bail
  3585. if ( depth && start.AsVector2D().DistToSqr( trace->endpos.AsVector2D() ) < MinDistance * MinDistance )
  3586. {
  3587. return false;
  3588. }
  3589. // We made it more than MinDistance. If the slope is too steep, we can't go on.
  3590. if ( !StayOnFloor( trace, zLimit ) )
  3591. {
  3592. return false;
  3593. }
  3594. // Try to go up as if we stepped up, forward, and down.
  3595. Vector testStart( trace->endpos );
  3596. Vector testEnd( testStart );
  3597. testEnd.z += StepHeight;
  3598. UTIL_TraceHull( testStart, testEnd, NavTraceMins, NavTraceMaxs, TheNavMesh->GetGenerationTraceMask(), &filter, trace );
  3599. DrawTrace( trace );
  3600. Vector forwardTestStart = trace->endpos;
  3601. Vector forwardTestEnd = end;
  3602. forwardTestEnd.z = forwardTestStart.z;
  3603. return TraceAdjacentNode( depth+1, forwardTestStart, forwardTestEnd, trace );
  3604. }
  3605. //--------------------------------------------------------------------------------------------------------
  3606. static bool IsNodeOverlapped( const Vector& pos, const Vector& offset )
  3607. {
  3608. bool overlap = TheNavMesh->GetNavArea( pos + offset, HumanHeight ) != NULL;
  3609. if ( !overlap )
  3610. {
  3611. Vector mins( -0.5f, -0.5f, -0.5f );
  3612. Vector maxs( 0.5f, 0.5f, 0.5f );
  3613. Vector start = pos;
  3614. start.z += HalfHumanHeight;
  3615. Vector end = start;
  3616. end.x += offset.x * GenerationStepSize;
  3617. end.y += offset.y * GenerationStepSize;
  3618. trace_t trace;
  3619. CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_NONE, WALK_THRU_EVERYTHING );
  3620. UTIL_TraceHull( start, end, mins, maxs, TheNavMesh->GetGenerationTraceMask(), &filter, &trace );
  3621. if ( trace.startsolid || trace.allsolid )
  3622. {
  3623. return true;
  3624. }
  3625. if ( trace.fraction < 0.1f )
  3626. {
  3627. return true;
  3628. }
  3629. start = trace.endpos;
  3630. end.z -= HalfHumanHeight * 2;
  3631. UTIL_TraceHull( start, end, mins, maxs, TheNavMesh->GetGenerationTraceMask(), &filter, &trace );
  3632. if ( trace.startsolid || trace.allsolid )
  3633. {
  3634. return true;
  3635. }
  3636. if ( trace.fraction == 1.0f )
  3637. {
  3638. return true;
  3639. }
  3640. if ( trace.plane.normal.z < 0.7f )
  3641. {
  3642. return true;
  3643. }
  3644. }
  3645. return overlap;
  3646. }
  3647. //--------------------------------------------------------------------------------------------------------------
  3648. /**
  3649. * Search the world and build a map of possible movements.
  3650. * The algorithm begins at the bot's current location, and does a recursive search
  3651. * outwards, tracking all valid steps and generating a directed graph of CNavNodes.
  3652. *
  3653. * Sample the map one "step" in a cardinal direction to learn the map.
  3654. *
  3655. * Returns true if sampling needs to continue, or false if done.
  3656. */
  3657. bool CNavMesh::SampleStep( void )
  3658. {
  3659. // take a step
  3660. while( true )
  3661. {
  3662. if (m_currentNode == NULL)
  3663. {
  3664. // sampling is complete from current seed, try next one
  3665. m_currentNode = GetNextWalkableSeedNode();
  3666. if (m_currentNode == NULL)
  3667. {
  3668. if ( m_generationMode == GENERATE_INCREMENTAL || m_generationMode == GENERATE_SIMPLIFY )
  3669. {
  3670. return false;
  3671. }
  3672. // search is exhausted - continue search from ends of ladders
  3673. for ( int i=0; i<m_ladders.Count(); ++i )
  3674. {
  3675. CNavLadder *ladder = m_ladders[i];
  3676. // check ladder bottom
  3677. if ((m_currentNode = LadderEndSearch( &ladder->m_bottom, ladder->GetDir() )) != 0)
  3678. break;
  3679. // check ladder top
  3680. if ((m_currentNode = LadderEndSearch( &ladder->m_top, ladder->GetDir() )) != 0)
  3681. break;
  3682. }
  3683. if (m_currentNode == NULL)
  3684. {
  3685. // all seeds exhausted, sampling complete
  3686. return false;
  3687. }
  3688. }
  3689. }
  3690. //
  3691. // Take a step from this node
  3692. //
  3693. for( int dir = NORTH; dir < NUM_DIRECTIONS; dir++ )
  3694. {
  3695. if (!m_currentNode->HasVisited( (NavDirType)dir ))
  3696. {
  3697. // have not searched in this direction yet
  3698. // start at current node position
  3699. Vector pos = *m_currentNode->GetPosition();
  3700. // snap to grid
  3701. int cx = SnapToGrid( pos.x );
  3702. int cy = SnapToGrid( pos.y );
  3703. // attempt to move to adjacent node
  3704. switch( dir )
  3705. {
  3706. case NORTH: cy -= GenerationStepSize; break;
  3707. case SOUTH: cy += GenerationStepSize; break;
  3708. case EAST: cx += GenerationStepSize; break;
  3709. case WEST: cx -= GenerationStepSize; break;
  3710. }
  3711. pos.x = cx;
  3712. pos.y = cy;
  3713. m_generationDir = (NavDirType)dir;
  3714. // mark direction as visited
  3715. m_currentNode->MarkAsVisited( m_generationDir );
  3716. // sanity check to not generate across the world for incremental generation
  3717. const float incrementalRange = nav_generate_incremental_range.GetFloat();
  3718. if ( m_generationMode == GENERATE_INCREMENTAL && incrementalRange > 0 )
  3719. {
  3720. bool inRange = false;
  3721. for ( int i=0; i<m_walkableSeeds.Count(); ++i )
  3722. {
  3723. const Vector &seedPos = m_walkableSeeds[i].pos;
  3724. if ( (seedPos - pos).IsLengthLessThan( incrementalRange ) )
  3725. {
  3726. inRange = true;
  3727. break;
  3728. }
  3729. }
  3730. if ( !inRange )
  3731. {
  3732. return true;
  3733. }
  3734. }
  3735. if ( m_generationMode == GENERATE_SIMPLIFY )
  3736. {
  3737. if ( !m_simplifyGenerationExtent.Contains( pos ) )
  3738. {
  3739. return true;
  3740. }
  3741. }
  3742. // test if we can move to new position
  3743. trace_t result;
  3744. Vector from( *m_currentNode->GetPosition() );
  3745. CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_NONE, WALK_THRU_EVERYTHING );
  3746. Vector to, toNormal;
  3747. float obstacleHeight = 0, obstacleStartDist = 0, obstacleEndDist = GenerationStepSize;
  3748. if ( TraceAdjacentNode( 0, from, pos, &result ) )
  3749. {
  3750. to = result.endpos;
  3751. toNormal = result.plane.normal;
  3752. }
  3753. else
  3754. {
  3755. // test going up ClimbUpHeight
  3756. bool success = false;
  3757. for ( float height = StepHeight; height <= ClimbUpHeight; height += 1.0f )
  3758. {
  3759. trace_t tr;
  3760. Vector start( from );
  3761. Vector end( pos );
  3762. start.z += height;
  3763. end.z += height;
  3764. UTIL_TraceHull( start, end, NavTraceMins, NavTraceMaxs, GetGenerationTraceMask(), &filter, &tr );
  3765. if ( !tr.startsolid && tr.fraction == 1.0f )
  3766. {
  3767. if ( !StayOnFloor( &tr ) )
  3768. {
  3769. break;
  3770. }
  3771. to = tr.endpos;
  3772. toNormal = tr.plane.normal;
  3773. start = end = from;
  3774. end.z += height;
  3775. UTIL_TraceHull( start, end, NavTraceMins, NavTraceMaxs, GetGenerationTraceMask(), &filter, &tr );
  3776. if ( tr.fraction < 1.0f )
  3777. {
  3778. break;
  3779. }
  3780. // keep track of far up we had to go to find a path to the next node
  3781. obstacleHeight = height;
  3782. success = true;
  3783. break;
  3784. }
  3785. else
  3786. {
  3787. // Could not trace from node to node at this height, something is in the way.
  3788. // Trace in the other direction to see if we hit something
  3789. Vector vecToObstacleStart = tr.endpos - start;
  3790. Assert( vecToObstacleStart.LengthSqr() <= Square( GenerationStepSize ) );
  3791. if ( vecToObstacleStart.LengthSqr() <= Square( GenerationStepSize ) )
  3792. {
  3793. UTIL_TraceHull( end, start, NavTraceMins, NavTraceMaxs, GetGenerationTraceMask(), &filter, &tr );
  3794. if ( !tr.startsolid && tr.fraction < 1.0 )
  3795. {
  3796. // We hit something going the other direction. There is some obstacle between the two nodes.
  3797. Vector vecToObstacleEnd = tr.endpos - start;
  3798. Assert( vecToObstacleEnd.LengthSqr() <= Square( GenerationStepSize ) );
  3799. if ( vecToObstacleEnd.LengthSqr() <= Square( GenerationStepSize ) )
  3800. {
  3801. // Remember the distances to start and end of the obstacle (with respect to the "from" node).
  3802. // Keep track of the last distances to obstacle as we keep increasing the height we do a trace for.
  3803. // If we do eventually clear the obstacle, these values will be the start and end distance to the
  3804. // very tip of the obstacle.
  3805. obstacleStartDist = vecToObstacleStart.Length();
  3806. obstacleEndDist = vecToObstacleEnd.Length();
  3807. if ( obstacleEndDist == 0 )
  3808. {
  3809. obstacleEndDist = GenerationStepSize;
  3810. }
  3811. }
  3812. }
  3813. }
  3814. }
  3815. }
  3816. if ( !success )
  3817. {
  3818. return true;
  3819. }
  3820. }
  3821. // Don't generate nodes if we spill off the end of the world onto skybox
  3822. if ( result.surface.flags & ( SURF_SKY|SURF_SKY2D ) )
  3823. {
  3824. return true;
  3825. }
  3826. // If we're incrementally generating, don't overlap existing nav areas.
  3827. Vector testPos( to );
  3828. bool overlapSE = IsNodeOverlapped( testPos, Vector( 1, 1, HalfHumanHeight ) );
  3829. bool overlapSW = IsNodeOverlapped( testPos, Vector( -1, 1, HalfHumanHeight ) );
  3830. bool overlapNE = IsNodeOverlapped( testPos, Vector( 1, -1, HalfHumanHeight ) );
  3831. bool overlapNW = IsNodeOverlapped( testPos, Vector( -1, -1, HalfHumanHeight ) );
  3832. if ( overlapSE && overlapSW && overlapNE && overlapNW && m_generationMode != GENERATE_SIMPLIFY )
  3833. {
  3834. return true;
  3835. }
  3836. int nTolerance = nav_generate_incremental_tolerance.GetInt();
  3837. if ( nTolerance > 0 && m_generationMode == GENERATE_INCREMENTAL )
  3838. {
  3839. bool bValid = false;
  3840. int zPos = to.z;
  3841. for ( int i=0; i<m_walkableSeeds.Count(); ++i )
  3842. {
  3843. const Vector &seedPos = m_walkableSeeds[i].pos;
  3844. int zMin = seedPos.z - nTolerance;
  3845. int zMax = seedPos.z + nTolerance;
  3846. if ( zPos >= zMin && zPos <= zMax )
  3847. {
  3848. bValid = true;
  3849. break;
  3850. }
  3851. }
  3852. if ( !bValid )
  3853. return true;
  3854. }
  3855. bool isOnDisplacement = result.IsDispSurface();
  3856. if ( nav_displacement_test.GetInt() > 0 )
  3857. {
  3858. // Test for nodes under displacement surfaces.
  3859. // This happens during development, and is a pain because the space underneath a displacement
  3860. // is not 'solid'.
  3861. Vector start = to + Vector( 0, 0, 0 );
  3862. Vector end = start + Vector( 0, 0, nav_displacement_test.GetInt() );
  3863. UTIL_TraceHull( start, end, NavTraceMins, NavTraceMaxs, GetGenerationTraceMask(), &filter, &result );
  3864. if ( result.fraction > 0 )
  3865. {
  3866. end = start;
  3867. start = result.endpos;
  3868. UTIL_TraceHull( start, end, NavTraceMins, NavTraceMaxs, GetGenerationTraceMask(), &filter, &result );
  3869. if ( result.fraction < 1 )
  3870. {
  3871. // if we made it down to within StepHeight, maybe we're on a static prop
  3872. if ( result.endpos.z > to.z + StepHeight )
  3873. {
  3874. return true;
  3875. }
  3876. }
  3877. }
  3878. }
  3879. float deltaZ = to.z - m_currentNode->GetPosition()->z;
  3880. // If there's an obstacle in the way and it's traversable, or the obstacle is not higher than the destination node itself minus a small epsilon
  3881. // (meaning the obstacle was just the height change to get to the destination node, no extra obstacle between the two), clear obstacle height
  3882. // and distances
  3883. if ( ( obstacleHeight < MaxTraversableHeight ) || ( deltaZ > ( obstacleHeight - 2.0f ) ) )
  3884. {
  3885. obstacleHeight = 0;
  3886. obstacleStartDist = 0;
  3887. obstacleEndDist = GenerationStepSize;
  3888. }
  3889. // we can move here
  3890. // create a new navigation node, and update current node pointer
  3891. AddNode( to, toNormal, m_generationDir, m_currentNode, isOnDisplacement, obstacleHeight, obstacleStartDist, obstacleEndDist );
  3892. return true;
  3893. }
  3894. }
  3895. // all directions have been searched from this node - pop back to its parent and continue
  3896. m_currentNode = m_currentNode->GetParent();
  3897. }
  3898. }
  3899. //--------------------------------------------------------------------------------------------------------------
  3900. /**
  3901. * Add given walkable position to list of seed positions for map sampling
  3902. */
  3903. void CNavMesh::AddWalkableSeed( const Vector &pos, const Vector &normal )
  3904. {
  3905. WalkableSeedSpot seed;
  3906. seed.pos.x = RoundToUnits( pos.x, GenerationStepSize );
  3907. seed.pos.y = RoundToUnits( pos.y, GenerationStepSize );
  3908. seed.pos.z = pos.z;
  3909. seed.normal = normal;
  3910. m_walkableSeeds.AddToTail( seed );
  3911. }
  3912. //--------------------------------------------------------------------------------------------------------------
  3913. /**
  3914. * Return the next walkable seed as a node
  3915. */
  3916. CNavNode *CNavMesh::GetNextWalkableSeedNode( void )
  3917. {
  3918. if ( m_seedIdx >= m_walkableSeeds.Count() )
  3919. return NULL;
  3920. WalkableSeedSpot spot = m_walkableSeeds[ m_seedIdx ];
  3921. ++m_seedIdx;
  3922. // check if a node exists at this location
  3923. CNavNode *node = CNavNode::GetNode( spot.pos );
  3924. if ( node )
  3925. return NULL;
  3926. return new CNavNode( spot.pos, spot.normal, NULL, false );
  3927. }
  3928. //--------------------------------------------------------------------------------------------------------------
  3929. /**
  3930. * Check LOS, ignoring any entities that we can walk through
  3931. */
  3932. bool IsWalkableTraceLineClear( const Vector &from, const Vector &to, unsigned int flags )
  3933. {
  3934. trace_t result;
  3935. CBaseEntity *ignore = NULL;
  3936. Vector useFrom = from;
  3937. CTraceFilterWalkableEntities traceFilter( NULL, COLLISION_GROUP_NONE, flags );
  3938. result.fraction = 0.0f;
  3939. const int maxTries = 50;
  3940. for( int t=0; t<maxTries; ++t )
  3941. {
  3942. UTIL_TraceLine( useFrom, to, MASK_PLAYERSOLID, &traceFilter, &result );
  3943. // if we hit a walkable entity, try again
  3944. if (result.fraction != 1.0f && IsEntityWalkable( result.m_pEnt, flags ))
  3945. {
  3946. ignore = result.m_pEnt;
  3947. // start from just beyond where we hit to avoid infinite loops
  3948. Vector dir = to - from;
  3949. dir.NormalizeInPlace();
  3950. useFrom = result.endpos + 5.0f * dir;
  3951. }
  3952. else
  3953. {
  3954. break;
  3955. }
  3956. }
  3957. if (result.fraction == 1.0f)
  3958. return true;
  3959. return false;
  3960. }
  3961. //--------------------------------------------------------------------------------------------------------------
  3962. /**
  3963. * Trace a hull, ignoring any entities that we can walk through
  3964. */
  3965. bool IsWalkableTraceHullClear( const Vector &from, const Vector &to, const Vector &mins, const Vector &maxs, unsigned int flags )
  3966. {
  3967. trace_t result;
  3968. CTraceFilterWalkableEntities traceFilter( NULL, COLLISION_GROUP_NONE, flags );
  3969. UTIL_TraceHull( from, to, mins, maxs, MASK_PLAYERSOLID, &traceFilter, &result );
  3970. if ( result.DidHitNonWorldEntity() && IsEntityWalkable( result.m_pEnt, flags ) )
  3971. {
  3972. return true;
  3973. }
  3974. return !result.DidHit();
  3975. }
  3976. //--------------------------------------------------------------------------------------------------------------
  3977. class Subdivider
  3978. {
  3979. public:
  3980. Subdivider( int depth )
  3981. {
  3982. m_depth = depth;
  3983. }
  3984. bool operator() ( CNavArea *area )
  3985. {
  3986. SubdivideX( area, true, true, m_depth );
  3987. return true;
  3988. }
  3989. void SubdivideX( CNavArea *area, bool canDivideX, bool canDivideY, int depth )
  3990. {
  3991. if (!canDivideX || depth <= 0)
  3992. return;
  3993. float split = area->GetSizeX() / 2.0f;
  3994. if (split < GenerationStepSize)
  3995. {
  3996. if (canDivideY)
  3997. {
  3998. SubdivideY( area, false, canDivideY, depth );
  3999. }
  4000. return;
  4001. }
  4002. split += area->GetCorner( NORTH_WEST ).x;
  4003. split = TheNavMesh->SnapToGrid( split );
  4004. CNavArea *alpha, *beta;
  4005. if (area->SplitEdit( false, split, &alpha, &beta ))
  4006. {
  4007. SubdivideY( alpha, canDivideX, canDivideY, depth );
  4008. SubdivideY( beta, canDivideX, canDivideY, depth );
  4009. }
  4010. }
  4011. void SubdivideY( CNavArea *area, bool canDivideX, bool canDivideY, int depth )
  4012. {
  4013. if (!canDivideY)
  4014. return;
  4015. float split = area->GetSizeY() / 2.0f;
  4016. if (split < GenerationStepSize)
  4017. {
  4018. if (canDivideX)
  4019. {
  4020. SubdivideX( area, canDivideX, false, depth-1 );
  4021. }
  4022. return;
  4023. }
  4024. split += area->GetCorner( NORTH_WEST ).y;
  4025. split = TheNavMesh->SnapToGrid( split );
  4026. CNavArea *alpha, *beta;
  4027. if (area->SplitEdit( true, split, &alpha, &beta ))
  4028. {
  4029. SubdivideX( alpha, canDivideX, canDivideY, depth-1 );
  4030. SubdivideX( beta, canDivideX, canDivideY, depth-1 );
  4031. }
  4032. }
  4033. int m_depth;
  4034. };
  4035. //--------------------------------------------------------------------------------------------------------------
  4036. /**
  4037. * Subdivide each nav area in X and Y to create 4 new areas
  4038. */
  4039. void CNavMesh::CommandNavSubdivide( const CCommand &args )
  4040. {
  4041. int depth = 1;
  4042. if (args.ArgC() == 2)
  4043. {
  4044. depth = atoi( args[1] );
  4045. }
  4046. Subdivider chop( depth );
  4047. TheNavMesh->ForAllSelectedAreas( chop );
  4048. }
  4049. CON_COMMAND_F( nav_subdivide, "Subdivides all selected areas.", FCVAR_GAMEDLL | FCVAR_CHEAT )
  4050. {
  4051. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  4052. return;
  4053. TheNavMesh->CommandNavSubdivide( args );
  4054. }
  4055. //--------------------------------------------------------------------------------------------------------------
  4056. /**
  4057. * Debugging code to verify that all nav area connections are internally consistent
  4058. */
  4059. void CNavMesh::ValidateNavAreaConnections( void )
  4060. {
  4061. // iterate all nav areas
  4062. NavConnect connect;
  4063. for ( int it = 0; it < TheNavAreas.Count(); it++ )
  4064. {
  4065. CNavArea *area = TheNavAreas[ it ];
  4066. for ( NavDirType dir = NORTH; dir < NUM_DIRECTIONS; dir = (NavDirType) ( ( (int) dir ) +1 ) )
  4067. {
  4068. const NavConnectVector *pOutgoing = area->GetAdjacentAreas( dir );
  4069. const NavConnectVector *pIncoming = area->GetIncomingConnections( dir );
  4070. for ( int iConnect = 0; iConnect < pOutgoing->Count(); iConnect++ )
  4071. {
  4072. // make sure no area is on both the connection and incoming list
  4073. CNavArea *areaOther = (*pOutgoing)[iConnect].area;
  4074. connect.area = areaOther;
  4075. if ( pIncoming->Find( connect ) != pIncoming->InvalidIndex() )
  4076. {
  4077. Msg( "Area %d has area %d on both 2-way and incoming list, should only be on one\n", area->GetID(), areaOther->GetID() );
  4078. Assert( false );
  4079. }
  4080. // make sure there are no duplicate connections on the list
  4081. for ( int iConnectCheck = iConnect+1; iConnectCheck < pOutgoing->Count(); iConnectCheck++ )
  4082. {
  4083. CNavArea *areaCheck = (*pOutgoing)[iConnectCheck].area;
  4084. if ( areaOther == areaCheck )
  4085. {
  4086. Msg( "Area %d has multiple outgoing connections to area %d in direction %d\n", area->GetID(), areaOther->GetID(), dir );
  4087. Assert( false );
  4088. }
  4089. }
  4090. const NavConnectVector *pOutgoingOther = areaOther->GetAdjacentAreas( OppositeDirection( dir ) );
  4091. const NavConnectVector *pIncomingOther = areaOther->GetIncomingConnections( OppositeDirection( dir ) );
  4092. // if we have a one-way outgoing connection, make sure we are on the other area's incoming list
  4093. connect.area = area;
  4094. bool bIsTwoWay = pOutgoingOther->Find( connect ) != pOutgoingOther->InvalidIndex();
  4095. if ( !bIsTwoWay )
  4096. {
  4097. connect.area = area;
  4098. bool bOnOthersIncomingList = pIncomingOther->Find( connect ) != pIncomingOther->InvalidIndex();
  4099. if ( !bOnOthersIncomingList )
  4100. {
  4101. Msg( "Area %d has one-way connect to area %d but does not appear on the latter's incoming list\n", area->GetID(), areaOther->GetID() );
  4102. }
  4103. }
  4104. }
  4105. for ( int iConnect = 0; iConnect < pIncoming->Count(); iConnect++ )
  4106. {
  4107. CNavArea *areaOther = (*pIncoming)[iConnect].area;
  4108. // make sure there are not duplicate areas on the incoming list
  4109. for ( int iConnectCheck = iConnect+1; iConnectCheck < pIncoming->Count(); iConnectCheck++ )
  4110. {
  4111. CNavArea *areaCheck = (*pIncoming)[iConnectCheck].area;
  4112. if ( areaOther == areaCheck )
  4113. {
  4114. Msg( "Area %d has multiple incoming connections to area %d in direction %d\n", area->GetID(), areaOther->GetID(), dir );
  4115. Assert( false );
  4116. }
  4117. }
  4118. const NavConnectVector *pOutgoingOther = areaOther->GetAdjacentAreas( OppositeDirection( dir ) );
  4119. connect.area = area;
  4120. bool bOnOthersOutgoingList = pOutgoingOther->Find( connect ) != pOutgoingOther->InvalidIndex();
  4121. if ( !bOnOthersOutgoingList )
  4122. {
  4123. Msg( "Area %d has incoming connection from area %d but does not appear on latter's outgoing connection list\n", area->GetID(), areaOther->GetID() );
  4124. Assert( false );
  4125. }
  4126. }
  4127. }
  4128. }
  4129. }
  4130. //--------------------------------------------------------------------------------------------------------------
  4131. /**
  4132. * Temp way to mark cliff areas after generation without regen'ing. Any area that is adjacent to a cliff
  4133. * gets marked as a cliff. This will leave some big areas marked as cliff just because one edge is adjacent to
  4134. * a cliff so it's not great. The code that does this at generation time is better because it ensures that
  4135. * areas next to cliffs don't get merged with no-cliff areas.
  4136. */
  4137. void CNavMesh::PostProcessCliffAreas()
  4138. {
  4139. for ( int it = 0; it < TheNavAreas.Count(); it++ )
  4140. {
  4141. CNavArea *area = TheNavAreas[ it ];
  4142. if ( area->GetAttributes() & NAV_MESH_CLIFF )
  4143. continue;
  4144. for ( int i = 0; i < NUM_DIRECTIONS; i++ )
  4145. {
  4146. bool bHasCliff = false;
  4147. NavDirType dir = (NavDirType) i;
  4148. NavCornerType corner[2];
  4149. // look at either corner along this edge
  4150. corner[0] = (NavCornerType) i;
  4151. corner[1] = (NavCornerType) ( ( i+ 1 ) % NUM_CORNERS );
  4152. for ( int j = 0; j < 2; j++ )
  4153. {
  4154. Vector cornerPos = area->GetCorner( corner[j] );
  4155. if ( CheckCliff( &cornerPos, dir ) )
  4156. {
  4157. bHasCliff = true;
  4158. break;
  4159. }
  4160. }
  4161. if ( bHasCliff )
  4162. {
  4163. area->SetAttributes( area->GetAttributes() | NAV_MESH_CLIFF );
  4164. break;
  4165. }
  4166. }
  4167. }
  4168. }
  4169. CON_COMMAND_F( nav_gen_cliffs_approx, "Mark cliff areas, post-processing approximation", FCVAR_CHEAT )
  4170. {
  4171. TheNavMesh->PostProcessCliffAreas();
  4172. }