Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4956 lines
149 KiB

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