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.

1094 lines
28 KiB

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