Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1079 lines
27 KiB

  1. // NextBotPath.cpp
  2. // Encapsulate and manipulate a path through the world
  3. // Author: Michael Booth, February 2006
  4. // Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
  5. #include "cbase.h"
  6. #include "nav_mesh.h"
  7. #include "fmtstr.h"
  8. #include "NextBotPath.h"
  9. #include "NextBotInterface.h"
  10. #include "NextBotLocomotionInterface.h"
  11. #include "NextBotBodyInterface.h"
  12. #include "NextBotUtil.h"
  13. #include "tier0/vprof.h"
  14. // memdbgon must be the last include file in a .cpp file!!!
  15. #include "tier0/memdbgon.h"
  16. ConVar NextBotPathDrawIncrement( "nb_path_draw_inc", "100", FCVAR_CHEAT );
  17. ConVar NextBotPathDrawSegmentCount( "nb_path_draw_segment_count", "100", FCVAR_CHEAT );
  18. ConVar NextBotPathSegmentInfluenceRadius( "nb_path_segment_influence_radius", "100", FCVAR_CHEAT );
  19. //--------------------------------------------------------------------------------------------------------------
  20. Path::Path( void )
  21. {
  22. m_segmentCount = 0;
  23. m_cursorPos = 0.0f;
  24. m_isCursorDataDirty = true;
  25. m_cursorData.segmentPrior = NULL;
  26. m_ageTimer.Invalidate();
  27. m_subject = NULL;
  28. }
  29. //--------------------------------------------------------------------------------------------------------------
  30. /**
  31. * Determine actual path positions
  32. */
  33. bool Path::ComputePathDetails( INextBot *bot, const Vector &start )
  34. {
  35. VPROF_BUDGET( "Path::ComputePathDetails", "NextBot" );
  36. if (m_segmentCount == 0)
  37. return false;
  38. IBody *body = bot->GetBodyInterface();
  39. ILocomotion *mover = bot->GetLocomotionInterface();
  40. const float stepHeight = ( mover ) ? mover->GetStepHeight() : 18.0f;
  41. // inflate hull width slightly as a safety margin
  42. const float hullWidth = ( body ) ? body->GetHullWidth() + 5.0f : 1.0f;
  43. // set first path position
  44. if ( m_path[0].area->Contains( start ) )
  45. {
  46. m_path[0].pos = start;
  47. }
  48. else
  49. {
  50. // start in first area's center
  51. m_path[0].pos = m_path[0].area->GetCenter();
  52. }
  53. m_path[0].ladder = NULL;
  54. m_path[0].how = NUM_TRAVERSE_TYPES;
  55. m_path[0].type = ON_GROUND;
  56. // set positions along the path
  57. for( int i=1; i<m_segmentCount; ++i )
  58. {
  59. Segment *from = &m_path[ i-1 ];
  60. Segment *to = &m_path[ i ];
  61. if ( to->how <= GO_WEST ) // walk along the floor to the next area
  62. {
  63. to->ladder = NULL;
  64. from->area->ComputePortal( to->area, (NavDirType)to->how, &to->m_portalCenter, &to->m_portalHalfWidth );
  65. // compute next point
  66. ComputeAreaCrossing( bot, from->area, from->pos, to->area, (NavDirType)to->how, &to->pos );
  67. // we need to walk out of "from" area, so keep Z where we can reach it
  68. to->pos.z = from->area->GetZ( to->pos );
  69. // if this is a "jump down" connection, we must insert an additional point on the path
  70. float expectedHeightDrop = from->area->GetZ( from->pos ) - to->area->GetZ( to->pos );
  71. if ( expectedHeightDrop > mover->GetStepHeight() )
  72. {
  73. // NOTE: We can't know this is a drop-down yet, because of subtle interactions
  74. // between nav area links and "portals" and "area crossings"
  75. // compute direction of path just prior to "jump down"
  76. Vector2D dir;
  77. DirectionToVector2D( (NavDirType)to->how, &dir );
  78. // shift top of "jump down" out a bit to "get over the ledge"
  79. const float inc = 0.25f * hullWidth;
  80. const float maxPushDist = 75.0f;
  81. float halfWidth = hullWidth/2.0f;
  82. float hullHeight = ( body ) ? body->GetCrouchHullHeight() : 1.0f;
  83. float pushDist;
  84. for( pushDist = 0.0f; pushDist <= maxPushDist; pushDist += inc )
  85. {
  86. Vector pos = to->pos + Vector( pushDist * dir.x, pushDist * dir.y, 0.0f );
  87. trace_t ground;
  88. NextBotTraceFilterIgnoreActors filter( bot->GetEntity(), COLLISION_GROUP_NONE );
  89. UTIL_TraceHull( pos, pos + Vector( 0, 0, -stepHeight ),
  90. Vector( -halfWidth, -halfWidth, 0 ), Vector( halfWidth, halfWidth, hullHeight ),
  91. MASK_NPCSOLID, &filter, &ground );
  92. if (ground.fraction >= 1.0f)
  93. {
  94. // found clearance to drop
  95. break;
  96. }
  97. }
  98. Vector startDrop( to->pos.x + pushDist * dir.x, to->pos.y + pushDist * dir.y, to->pos.z );
  99. Vector endDrop( startDrop.x, startDrop.y, to->area->GetZ( to->pos ) );
  100. if ( bot->IsDebugging( NEXTBOT_PATH ) )
  101. {
  102. NDebugOverlay::Cross3D( startDrop, 5.0f, 255, 0, 0, true, 5.0f );
  103. NDebugOverlay::Cross3D( endDrop, 5.0f, 0, 255, 0, true, 5.0f );
  104. NDebugOverlay::HorzArrow( startDrop, endDrop, 5.0f, 255, 100, 0, 255, true, 5.0f );
  105. }
  106. // verify that there is actually ground down there in case this is a far jump dropdown
  107. float ground;
  108. if ( TheNavMesh->GetGroundHeight( startDrop, &ground ) )
  109. {
  110. if ( startDrop.z > ground + stepHeight )
  111. {
  112. // if "ground" is lower than the next segment along the path
  113. // there is a chasm between - this is not a drop down
  114. // NOTE next->pos is not yet valid - this loop is computing it!
  115. const Segment *next = NextSegment( to );
  116. if ( !next || next->area->GetCenter().z < ground + stepHeight )
  117. {
  118. // this is a "jump down" link
  119. to->pos = startDrop;
  120. to->type = DROP_DOWN;
  121. // insert a duplicate node to represent the bottom of the fall
  122. if ( m_segmentCount < MAX_PATH_SEGMENTS-1 )
  123. {
  124. // copy nodes down
  125. for( int j=m_segmentCount; j>i; --j )
  126. m_path[j] = m_path[j-1];
  127. // path is one node longer
  128. ++m_segmentCount;
  129. // move index ahead into the new node we just duplicated
  130. ++i;
  131. m_path[i].pos.x = endDrop.x;
  132. m_path[i].pos.y = endDrop.y;
  133. m_path[i].pos.z = ground;
  134. m_path[i].type = ON_GROUND;
  135. }
  136. }
  137. }
  138. }
  139. }
  140. }
  141. else if ( to->how == GO_LADDER_UP ) // to get to next area, must go up a ladder
  142. {
  143. // find our ladder
  144. const NavLadderConnectVector *ladders = from->area->GetLadders( CNavLadder::LADDER_UP );
  145. int it;
  146. for( it=0; it<ladders->Count(); ++it )
  147. {
  148. CNavLadder *ladder = (*ladders)[ it ].ladder;
  149. // can't use "behind" area when ascending...
  150. if (ladder->m_topForwardArea == to->area ||
  151. ladder->m_topLeftArea == to->area ||
  152. ladder->m_topRightArea == to->area)
  153. {
  154. to->ladder = ladder;
  155. to->pos = ladder->m_bottom + ladder->GetNormal() * 2.0f * HalfHumanWidth;
  156. to->type = LADDER_UP;
  157. break;
  158. }
  159. }
  160. if (it == ladders->Count())
  161. {
  162. //PrintIfWatched( "ERROR: Can't find ladder in path\n" );
  163. return false;
  164. }
  165. }
  166. else if ( to->how == GO_LADDER_DOWN ) // to get to next area, must go down a ladder
  167. {
  168. // find our ladder
  169. const NavLadderConnectVector *ladders = from->area->GetLadders( CNavLadder::LADDER_DOWN );
  170. int it;
  171. for( it=0; it<ladders->Count(); ++it )
  172. {
  173. CNavLadder *ladder = (*ladders)[ it ].ladder;
  174. if (ladder->m_bottomArea == to->area)
  175. {
  176. to->ladder = ladder;
  177. to->pos = ladder->m_top;
  178. to->pos = ladder->m_top - ladder->GetNormal() * 2.0f * HalfHumanWidth;
  179. to->type = LADDER_DOWN;
  180. break;
  181. }
  182. }
  183. if (it == ladders->Count())
  184. {
  185. //PrintIfWatched( "ERROR: Can't find ladder in path\n" );
  186. return false;
  187. }
  188. }
  189. else if ( to->how == GO_ELEVATOR_UP || to->how == GO_ELEVATOR_DOWN )
  190. {
  191. to->pos = to->area->GetCenter();
  192. to->ladder = NULL;
  193. }
  194. }
  195. //
  196. // Scan for non-adjacent nav areas and add gap-jump-target nodes
  197. // and jump-up target nodes for adjacent ledge mantling
  198. // @todo Adjacency should be baked into the mesh data
  199. //
  200. for( int i=0; i<m_segmentCount-1; ++i )
  201. {
  202. Segment *from = &m_path[ i ];
  203. Segment *to = &m_path[ i+1 ];
  204. // first segment doesnt have a direction
  205. if ( from->how != NUM_TRAVERSE_TYPES && from->how > GO_WEST )
  206. continue;
  207. if ( to->how > GO_WEST || !to->type == ON_GROUND )
  208. continue;
  209. // if areas are separated, we may need to 'gap jump' between them
  210. // add a node to minimize the jump distance
  211. Vector closeFrom, closeTo;
  212. to->area->GetClosestPointOnArea( from->pos, &closeTo );
  213. from->area->GetClosestPointOnArea( closeTo, &closeFrom );
  214. if ( bot->IsDebugging( NEXTBOT_PATH ) )
  215. {
  216. NDebugOverlay::Line( closeFrom, closeTo, 255, 0, 255, true, 5.0f );
  217. }
  218. const float separationTolerance = 1.9f * GenerationStepSize;
  219. if ( (closeFrom - closeTo).AsVector2D().IsLengthGreaterThan( separationTolerance ) && ( closeTo - closeFrom ).AsVector2D().IsLengthGreaterThan( 0.5f * fabs( closeTo.z - closeFrom.z ) ) )
  220. {
  221. // areas are disjoint and mostly level - add gap jump target
  222. // compute landing spot in 'to' area
  223. Vector landingPos;
  224. to->area->GetClosestPointOnArea( to->pos, &landingPos );
  225. // compute launch spot in 'from' area
  226. Vector launchPos;
  227. from->area->GetClosestPointOnArea( landingPos, &launchPos );
  228. Vector forward = landingPos - launchPos;
  229. forward.NormalizeInPlace();
  230. const float halfWidth = hullWidth/2.0f;
  231. // adjust path position to landing spot
  232. to->pos = landingPos + forward * halfWidth;
  233. // insert launch position just before that segment to ensure bot is
  234. // positioned for minimal jump distance
  235. Segment newSegment = *from;
  236. newSegment.pos = launchPos - forward * halfWidth;
  237. newSegment.type = JUMP_OVER_GAP;
  238. InsertSegment( newSegment, i+1 );
  239. ++i;
  240. }
  241. else if ( (closeTo.z - closeFrom.z) > stepHeight )
  242. {
  243. // areas are adjacent, but need a jump-up - add a jump-to target
  244. // adjust goal to be at top of ledge
  245. //to->pos.z = to->area->GetZ( to->pos.x, to->pos.y );
  246. // use center of climb-up destination area to make sure bot moves onto actual ground once they finish their climb
  247. to->pos = to->area->GetCenter();
  248. // add launch position at base of jump
  249. Segment newSegment = *from;
  250. Vector launchPos;
  251. from->area->GetClosestPointOnArea( to->pos, &launchPos );
  252. newSegment.pos = launchPos;
  253. newSegment.type = CLIMB_UP;
  254. if ( bot->IsDebugging( NEXTBOT_PATH ) )
  255. {
  256. NDebugOverlay::Cross3D( newSegment.pos, 15.0f, 255, 100, 255, true, 3.0f );
  257. }
  258. InsertSegment( newSegment, i+1 );
  259. ++i;
  260. }
  261. /** RETHINK THIS. It doesn't work in general cases, and messes up on doorways
  262. else if ( from->type == ON_GROUND && from->how <= GO_WEST )
  263. {
  264. // if any segment is not directly walkable, add a segment
  265. // fixup corners that are being cut too tightly
  266. if ( mover && !mover->IsPotentiallyTraversable( from->pos, to->pos ) )
  267. {
  268. Segment newSegment = *from;
  269. if ( bot->IsDebugging( INextBot::PATH ) )
  270. {
  271. NDebugOverlay::HorzArrow( from->pos, to->pos, 3.0f, 255, 0, 0, 255, true, 3.0f );
  272. }
  273. //newSegment.pos = from->area->GetCenter();
  274. Vector2D shift;
  275. DirectionToVector2D( OppositeDirection( (NavDirType)to->how ), &shift );
  276. newSegment.pos = to->pos;
  277. newSegment.pos.x += hullWidth * shift.x;
  278. newSegment.pos.y += hullWidth * shift.y;
  279. newSegment.type = ON_GROUND;
  280. if ( bot->IsDebugging( INextBot::PATH ) )
  281. {
  282. NDebugOverlay::Cross3D( newSegment.pos, 15.0f, 255, 0, 255, true, 3.0f );
  283. }
  284. InsertSegment( newSegment, i+1 );
  285. i += 2;
  286. }
  287. }
  288. */
  289. }
  290. return true;
  291. }
  292. //--------------------------------------------------------------------------------------------------------------
  293. /**
  294. * Insert new segment at index i
  295. */
  296. void Path::InsertSegment( Segment newSegment, int i )
  297. {
  298. if (m_segmentCount < MAX_PATH_SEGMENTS-1)
  299. {
  300. // shift segments to make room for new one
  301. for( int j=m_segmentCount; j>i; --j )
  302. m_path[j] = m_path[j-1];
  303. // path is one node longer
  304. ++m_segmentCount;
  305. m_path[i] = newSegment;
  306. }
  307. }
  308. //--------------------------------------------------------------------------------------------------------------
  309. /**
  310. * Build trivial path when start and goal are in the same nav area
  311. */
  312. bool Path::BuildTrivialPath( INextBot *bot, const Vector &goal )
  313. {
  314. const Vector &start = bot->GetPosition();
  315. m_segmentCount = 0;
  316. /// @todo Dangerous to use "nearset" nav area - could be far away
  317. CNavArea *startArea = TheNavMesh->GetNearestNavArea( start );
  318. if (startArea == NULL)
  319. return false;
  320. CNavArea *goalArea = TheNavMesh->GetNearestNavArea( goal );
  321. if (goalArea == NULL)
  322. return false;
  323. m_segmentCount = 2;
  324. m_path[0].area = startArea;
  325. m_path[0].pos.x = start.x;
  326. m_path[0].pos.y = start.y;
  327. m_path[0].pos.z = startArea->GetZ( start );
  328. m_path[0].ladder = NULL;
  329. m_path[0].how = NUM_TRAVERSE_TYPES;
  330. m_path[0].type = ON_GROUND;
  331. m_path[1].area = goalArea;
  332. m_path[1].pos.x = goal.x;
  333. m_path[1].pos.y = goal.y;
  334. m_path[1].pos.z = goalArea->GetZ( goal );
  335. m_path[1].ladder = NULL;
  336. m_path[1].how = NUM_TRAVERSE_TYPES;
  337. m_path[1].type = ON_GROUND;
  338. m_path[0].forward = m_path[1].pos - m_path[0].pos;
  339. m_path[0].length = m_path[0].forward.NormalizeInPlace();
  340. m_path[0].distanceFromStart = 0.0f;
  341. m_path[0].curvature = 0.0f;
  342. m_path[1].forward = m_path[0].forward;
  343. m_path[1].length = 0.0f;
  344. m_path[1].distanceFromStart = m_path[0].length;
  345. m_path[1].curvature = 0.0f;
  346. OnPathChanged( bot, COMPLETE_PATH );
  347. return true;
  348. }
  349. //--------------------------------------------------------------------------------------------------------------
  350. /**
  351. * Draw the path for debugging.
  352. */
  353. void Path::Draw( const Path::Segment *start ) const
  354. {
  355. if ( !IsValid() )
  356. return;
  357. CFmtStr msg;
  358. // limit length of path we draw
  359. int count = NextBotPathDrawSegmentCount.GetInt();
  360. const Segment *s = start ? start : FirstSegment();
  361. int i=0;
  362. while( s && count-- )
  363. {
  364. const Segment *next = NextSegment( s );
  365. if ( next == NULL )
  366. {
  367. // end of the path
  368. break;
  369. }
  370. Vector to = next->pos - s->pos;
  371. float horiz = MAX( abs(to.x), abs(to.y) );
  372. float vert = abs( to.z );
  373. int r,g,b;
  374. switch( s->type )
  375. {
  376. case DROP_DOWN: r = 255; g = 0; b = 255; break;
  377. case CLIMB_UP: r = 0; g = 0; b = 255; break;
  378. case JUMP_OVER_GAP: r = 0; g = 255; b = 255; break;
  379. case LADDER_UP: r = 0; g = 255; b = 0; break;
  380. case LADDER_DOWN: r = 0; g = 100; b = 0; break;
  381. default: r = 255; g = 77; b = 0; break; // ON_GROUND
  382. }
  383. if ( s->ladder )
  384. {
  385. NDebugOverlay::VertArrow( s->ladder->m_bottom, s->ladder->m_top, 5.0f, r, g, b, 255, true, 0.1f );
  386. }
  387. else
  388. {
  389. NDebugOverlay::Line( s->pos, next->pos, r, g, b, true, 0.1f );
  390. }
  391. const float nodeLength = 25.0f;
  392. if ( horiz > vert )
  393. {
  394. NDebugOverlay::HorzArrow( s->pos, s->pos + nodeLength * s->forward, 5.0f, r, g, b, 255, true, 0.1f );
  395. }
  396. else
  397. {
  398. NDebugOverlay::VertArrow( s->pos, s->pos + nodeLength * s->forward, 5.0f, r, g, b, 255, true, 0.1f );
  399. }
  400. NDebugOverlay::Text( s->pos, msg.sprintf( "%d", i ), true, 0.1f );
  401. //NDebugOverlay::Text( s->pos, msg.sprintf( "%d (%3.2f)", i, s->curvature ), false, 0.1f );
  402. s = next;
  403. ++i;
  404. }
  405. }
  406. //--------------------------------------------------------------------------------------------------------------
  407. /**
  408. * Draw the path for debugging - MODIFIES cursor position
  409. */
  410. void Path::DrawInterpolated( float from, float to )
  411. {
  412. if ( !IsValid() )
  413. {
  414. return;
  415. }
  416. float t = from;
  417. MoveCursor( t );
  418. const Data &data = GetCursorData();
  419. Vector lastPos = data.pos;
  420. do
  421. {
  422. t += NextBotPathDrawIncrement.GetFloat();
  423. MoveCursor( t );
  424. const Data &data = GetCursorData();
  425. float curvePower = 3.0f * data.curvature;
  426. int r = 255 * ( 1.0f - curvePower );
  427. r = clamp( r, 0, 255 );
  428. int g = 255 * ( 1.0f + curvePower );
  429. g = clamp( g, 0, 255 );
  430. NDebugOverlay::Line( lastPos, data.pos, r, g, 0, true, 0.1f );
  431. /*
  432. int i = 0xFF & (int)( data.pos.x + data.pos.y + data.pos.z );
  433. i >>= 1;
  434. i += 128;
  435. NDebugOverlay::Line( data.pos, data.pos + 10.0f * data.forward, 0, i, i, true, 0.1f );
  436. */
  437. lastPos = data.pos;
  438. }
  439. while( t < to );
  440. }
  441. //--------------------------------------------------------------------------------------------------------------
  442. /**
  443. * Check line of sight from 'anchor' node on path to subsequent nodes until
  444. * we find a node that can't been seen from 'anchor'.
  445. */
  446. int Path::FindNextOccludedNode( INextBot *bot, int anchorIndex )
  447. {
  448. ILocomotion *mover = bot->GetLocomotionInterface();
  449. if ( mover == NULL)
  450. {
  451. return m_segmentCount;
  452. }
  453. Segment *anchor = &m_path[ anchorIndex ];
  454. for( int i=anchorIndex+1; i<m_segmentCount; ++i )
  455. {
  456. Segment *to = &m_path[i];
  457. // if this segment is not on the ground, or is precise, don't skip past it
  458. if ( !to->type == ON_GROUND || (to->area->GetAttributes() & NAV_MESH_PRECISE) )
  459. {
  460. return i;
  461. }
  462. if ( !mover->IsPotentiallyTraversable( anchor->pos, to->pos, ILocomotion::IMMEDIATELY ) )
  463. {
  464. // cant reach this node directly from anchor node
  465. return i;
  466. }
  467. if ( mover->HasPotentialGap( anchor->pos, to->pos ) )
  468. {
  469. // we would fall into a gap if we took this cutoff
  470. return i;
  471. }
  472. }
  473. return m_segmentCount;
  474. }
  475. //--------------------------------------------------------------------------------------------------------------
  476. /**
  477. * Smooth out path, removing redundant nodes
  478. */
  479. void Path::Optimize( INextBot *bot )
  480. {
  481. // this is SUPER expensive - especially the IsGap() check
  482. return;
  483. VPROF_BUDGET( "Path::Optimize", "NextBot" );
  484. if (m_segmentCount < 3)
  485. return;
  486. int anchor = 0;
  487. while( anchor < m_segmentCount )
  488. {
  489. int occluded = FindNextOccludedNode( bot, anchor );
  490. int nextAnchor = occluded-1;
  491. if (nextAnchor > anchor)
  492. {
  493. // remove redundant nodes between anchor and nextAnchor
  494. int removeCount = nextAnchor - anchor - 1;
  495. if (removeCount > 0)
  496. {
  497. for( int i=nextAnchor; i<m_segmentCount; ++i )
  498. {
  499. m_path[i-removeCount] = m_path[i];
  500. }
  501. m_segmentCount -= removeCount;
  502. }
  503. }
  504. ++anchor;
  505. }
  506. }
  507. //--------------------------------------------------------------------------------------------------------------
  508. /**
  509. * Compute final data for completed path
  510. */
  511. void Path::PostProcess( void )
  512. {
  513. VPROF_BUDGET( "Path::PostProcess", "NextBot" );
  514. m_ageTimer.Start();
  515. if (m_segmentCount == 0)
  516. return;
  517. if (m_segmentCount == 1)
  518. {
  519. m_path[0].forward = vec3_origin;
  520. m_path[0].length = 0.0f;
  521. m_path[0].distanceFromStart = 0.0f;
  522. m_path[0].curvature = 0.0f;
  523. return;
  524. }
  525. float distanceSoFar = 0.0f;
  526. int i;
  527. for( i=0; i < m_segmentCount-1; ++i )
  528. {
  529. Segment *from = &m_path[ i ];
  530. Segment *to = &m_path[ i+1 ];
  531. from->forward = to->pos - from->pos;
  532. from->length = from->forward.NormalizeInPlace();
  533. from->distanceFromStart = distanceSoFar;
  534. distanceSoFar += from->length;
  535. }
  536. // compute curvature in XY plane
  537. Vector2D from, to;
  538. for( i=1; i < m_segmentCount-1; ++i )
  539. {
  540. if (m_path[ i ].type != ON_GROUND)
  541. {
  542. m_path[ i ].curvature = 0.0f;
  543. }
  544. else
  545. {
  546. from = m_path[ i-1 ].forward.AsVector2D();
  547. from.NormalizeInPlace();
  548. to = m_path[ i ].forward.AsVector2D();
  549. to.NormalizeInPlace();
  550. m_path[ i ].curvature = 0.5f * ( 1.0f - from.Dot( to ) );
  551. Vector2D right( -from.y, from.x );
  552. if ( to.Dot( right ) < 0.0f )
  553. {
  554. m_path[ i ].curvature = -m_path[ i ].curvature;
  555. }
  556. }
  557. }
  558. // first segment has no curvature
  559. m_path[ 0 ].curvature = 0.0f;
  560. // last segment maintains direction
  561. m_path[ i ].forward = m_path[ i-1 ].forward;
  562. m_path[ i ].length = 0.0f;
  563. m_path[ i ].distanceFromStart = distanceSoFar;
  564. m_path[ i ].curvature = 0.0f;
  565. }
  566. //--------------------------------------------------------------------------------------------------------------
  567. /**
  568. * Return a position on the path at the given distance from the path start
  569. */
  570. const Vector &Path::GetPosition( float distanceFromStart, const Segment *start ) const
  571. {
  572. if (!IsValid())
  573. {
  574. return vec3_origin;
  575. }
  576. float lengthSoFar;
  577. const Segment *segment;
  578. if (start)
  579. {
  580. segment = start;
  581. lengthSoFar = start->distanceFromStart;
  582. }
  583. else
  584. {
  585. segment = &m_path[0];
  586. lengthSoFar = 0.0f;
  587. }
  588. if (segment->distanceFromStart > distanceFromStart)
  589. {
  590. // clamp to path start
  591. return segment->pos;
  592. }
  593. const Segment *next = NextSegment( segment );
  594. Vector delta;
  595. float length;
  596. while( next )
  597. {
  598. delta = next->pos - segment->pos;
  599. length = segment->length;
  600. if (lengthSoFar + length >= distanceFromStart)
  601. {
  602. // desired point is on this segment of the path
  603. float overlap = distanceFromStart - lengthSoFar;
  604. float t = overlap / length;
  605. m_pathPos = segment->pos + t * delta;
  606. return m_pathPos;
  607. }
  608. lengthSoFar += length;
  609. segment = next;
  610. next = NextSegment( next );
  611. }
  612. // clamp to path end
  613. return segment->pos;
  614. }
  615. //--------------------------------------------------------------------------------------------------------------
  616. /**
  617. * Return the closest point on the path to the given position
  618. */
  619. const Vector &Path::GetClosestPosition( const Vector &pos, const Segment *start, float alongLimit ) const
  620. {
  621. const Segment *segment = (start) ? start : &m_path[0];
  622. if (segment == NULL)
  623. {
  624. return pos;
  625. }
  626. m_closePos = pos;
  627. float closeRangeSq = 99999999999.9f;
  628. float distanceSoFar = 0.0f;
  629. while( alongLimit == 0.0f || distanceSoFar <= alongLimit )
  630. {
  631. const Segment *nextSegment = NextSegment( segment );
  632. if (nextSegment)
  633. {
  634. Vector close;
  635. CalcClosestPointOnLineSegment( pos, segment->pos, nextSegment->pos, close );
  636. float rangeSq = (close - pos).LengthSqr();
  637. if (rangeSq < closeRangeSq)
  638. {
  639. m_closePos = close;
  640. closeRangeSq = rangeSq;
  641. }
  642. }
  643. else
  644. {
  645. // end of the path
  646. break;
  647. }
  648. distanceSoFar += segment->length;
  649. segment = nextSegment;
  650. }
  651. return m_closePos;
  652. }
  653. //--------------------------------------------------------------------------------------------------------------
  654. /**
  655. * Replace this path with the given path's data
  656. */
  657. void Path::Copy( INextBot *bot, const Path &path )
  658. {
  659. VPROF_BUDGET( "Path::Copy", "NextBot" );
  660. Invalidate();
  661. for( int i = 0; i < path.m_segmentCount; ++i )
  662. {
  663. m_path[i] = path.m_path[i];
  664. }
  665. m_segmentCount = path.m_segmentCount;
  666. OnPathChanged( bot, COMPLETE_PATH );
  667. }
  668. //--------------------------------------------------------------------------------------------------------------
  669. /**
  670. * Set cursor position to closest point on path to given position
  671. */
  672. void Path::MoveCursorToClosestPosition( const Vector &pos, SeekType type, float alongLimit ) const
  673. {
  674. if ( !IsValid() )
  675. {
  676. return;
  677. }
  678. if ( type == SEEK_ENTIRE_PATH || type == SEEK_AHEAD )
  679. {
  680. const Segment *segment;
  681. if ( type == SEEK_AHEAD )
  682. {
  683. // continue search from cursor position onward
  684. if ( m_cursorData.segmentPrior )
  685. {
  686. segment = m_cursorData.segmentPrior;
  687. }
  688. else
  689. {
  690. // no prior segment, start from the start
  691. segment = &m_path[ 0 ];
  692. }
  693. }
  694. else
  695. {
  696. // search entire path from the start
  697. segment = &m_path[ 0 ];
  698. }
  699. m_cursorData.pos = pos;
  700. m_cursorData.segmentPrior = segment;
  701. float closeRangeSq = 99999999999.9f;
  702. float distanceSoFar = 0.0f;
  703. while( alongLimit == 0.0f || distanceSoFar <= alongLimit )
  704. {
  705. const Segment *nextSegment = NextSegment( segment );
  706. if ( nextSegment )
  707. {
  708. Vector close;
  709. CalcClosestPointOnLineSegment( pos, segment->pos, nextSegment->pos, close );
  710. float rangeSq = ( close - pos ).LengthSqr();
  711. if ( rangeSq < closeRangeSq )
  712. {
  713. m_cursorData.pos = close;
  714. m_cursorData.segmentPrior = segment;
  715. closeRangeSq = rangeSq;
  716. }
  717. }
  718. else
  719. {
  720. // end of the path
  721. break;
  722. }
  723. distanceSoFar += segment->length;
  724. segment = nextSegment;
  725. }
  726. //
  727. // Move cursor to closest point on path
  728. //
  729. segment = m_cursorData.segmentPrior;
  730. float t = ( m_cursorData.pos - segment->pos ).Length() / segment->length;
  731. m_cursorPos = segment->distanceFromStart + t * segment->length;
  732. m_isCursorDataDirty = true;
  733. }
  734. else
  735. {
  736. AssertMsg( false, "SEEK_BEHIND not implemented" );
  737. }
  738. }
  739. //--------------------------------------------------------------------------------------------------------------
  740. /**
  741. * Return path state at the current cursor position
  742. */
  743. const Path::Data &Path::GetCursorData( void ) const
  744. {
  745. if ( IsValid() )
  746. {
  747. if ( m_isCursorDataDirty )
  748. {
  749. const float epsilon = 0.0001f;
  750. if ( m_cursorPos < epsilon || m_segmentCount < 2 )
  751. {
  752. // start of path
  753. m_cursorData.pos = m_path[0].pos;
  754. m_cursorData.forward = m_path[0].forward;
  755. m_cursorData.curvature = m_path[0].curvature;
  756. m_cursorData.segmentPrior = &m_path[0];
  757. }
  758. else if ( m_cursorPos > GetLength() - epsilon )
  759. {
  760. // end of path
  761. m_cursorData.pos = m_path[ m_segmentCount-2 ].pos;
  762. m_cursorData.forward = m_path[ m_segmentCount-2 ].forward;
  763. m_cursorData.curvature = m_path[ m_segmentCount-2 ].curvature;
  764. m_cursorData.segmentPrior = &m_path[ m_segmentCount-2 ];
  765. }
  766. else
  767. {
  768. // along path
  769. float lengthSoFar = 0.0f;
  770. const Segment *segment = &m_path[0];
  771. const Segment *next = NextSegment( segment );
  772. while( next )
  773. {
  774. float length = segment->length;
  775. if ( lengthSoFar + length >= m_cursorPos )
  776. {
  777. // desired point is on this segment of the path
  778. float overlap = m_cursorPos - lengthSoFar;
  779. float t = 1.0f; // 0-length segments are assumed to be complete, to avoid NaNs
  780. if ( length > 0.0f )
  781. {
  782. t = overlap / length;
  783. }
  784. // interpolate data at this point along the path
  785. m_cursorData.pos = segment->pos + t * ( next->pos - segment->pos );
  786. m_cursorData.forward = segment->forward + t * ( next->forward - segment->forward );
  787. m_cursorData.segmentPrior = segment;
  788. // curvature fades to zero along midpoint of long straight segments
  789. // and is influenced as it nears ends of segment
  790. if ( overlap < NextBotPathSegmentInfluenceRadius.GetFloat() )
  791. {
  792. if ( length - overlap < NextBotPathSegmentInfluenceRadius.GetFloat() )
  793. {
  794. // near start and end - interpolate
  795. float startCurvature = segment->curvature * ( 1.0f - ( overlap / NextBotPathSegmentInfluenceRadius.GetFloat() ) );
  796. float endCurvature = next->curvature * ( 1.0f - ( ( length - overlap ) / NextBotPathSegmentInfluenceRadius.GetFloat() ) );
  797. m_cursorData.curvature = ( startCurvature + endCurvature ) / 2.0f;
  798. }
  799. else
  800. {
  801. // near start only
  802. m_cursorData.curvature = segment->curvature * ( 1.0f - ( overlap / NextBotPathSegmentInfluenceRadius.GetFloat() ) );
  803. }
  804. }
  805. else if ( length - overlap < NextBotPathSegmentInfluenceRadius.GetFloat() )
  806. {
  807. // near end only
  808. m_cursorData.curvature = next->curvature * ( 1.0f - ( ( length - overlap ) / NextBotPathSegmentInfluenceRadius.GetFloat() ) );
  809. }
  810. break;
  811. }
  812. lengthSoFar += length;
  813. segment = next;
  814. next = NextSegment( next );
  815. }
  816. }
  817. // data is up to date
  818. m_isCursorDataDirty = false;
  819. }
  820. }
  821. else
  822. {
  823. // path is not valid
  824. m_cursorData.pos = vec3_origin;
  825. m_cursorData.forward = Vector( 1.0f, 0, 0 );
  826. m_cursorData.curvature = 0.0f;
  827. m_cursorData.segmentPrior = NULL;
  828. }
  829. return m_cursorData;
  830. }
  831. //--------------------------------------------------------------------------------------------------------------
  832. /**
  833. * Determine exactly where the path goes between the given two areas
  834. * on the path. Return this point in 'crossPos'.
  835. */
  836. void Path::ComputeAreaCrossing( INextBot *bot, const CNavArea *from, const Vector &fromPos, const CNavArea *to, NavDirType dir, Vector *crossPos ) const
  837. {
  838. from->ComputeClosestPointInPortal( to, dir, fromPos, crossPos );
  839. // move goal position into the goal area a bit to avoid running directly along the edge of an area against a wall, etc
  840. // don't do this unless area is against a wall - and what if our hull is wider than the area?
  841. // AddDirectionVector( crossPos, dir, bot->GetBodyInterface()->GetHullWidth()/2.0f );
  842. }