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.

904 lines
26 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include <float.h> // for FLT_MAX
  9. #include "ai_planesolver.h"
  10. #include "ai_moveprobe.h"
  11. #include "ai_motor.h"
  12. #include "ai_basenpc.h"
  13. #include "ai_route.h"
  14. #include "ndebugoverlay.h"
  15. // memdbgon must be the last include file in a .cpp file!!!
  16. #include "tier0/memdbgon.h"
  17. //-----------------------------------------------------------------------------
  18. const float PLANE_SOLVER_THINK_FREQUENCY[2] = { 0.0f, 0.2f };
  19. const float MAX_PROBE_DIST[2] = { (10.0f*12.0f), (8.0f*12.0f) };
  20. //#define PROFILE_PLANESOLVER 1
  21. #ifdef PROFILE_PLANESOLVER
  22. #define PLANESOLVER_PROFILE_SCOPE( tag ) AI_PROFILE_SCOPE( tag )
  23. #else
  24. #define PLANESOLVER_PROFILE_SCOPE( tag ) ((void)0)
  25. #endif
  26. #define ProbeForNpcs() 0
  27. //#define TESTING_SUGGESTIONS
  28. //-----------------------------------------------------------------------------
  29. inline float sq( float f )
  30. {
  31. return ( f * f );
  32. }
  33. inline float cube( float f )
  34. {
  35. return ( f * f * f );
  36. }
  37. //-----------------------------------------------------------------------------
  38. // Constructor
  39. //-----------------------------------------------------------------------------
  40. CAI_PlaneSolver::CAI_PlaneSolver( CAI_BaseNPC *pNpc )
  41. : m_pNpc( pNpc ),
  42. m_fSolvedPrev( false ),
  43. m_PrevTarget( FLT_MAX, FLT_MAX, FLT_MAX ),
  44. m_PrevSolution( 0 ),
  45. m_ClosestHaveBeenToCurrent( FLT_MAX ),
  46. m_TimeLastProgress( FLT_MAX ),
  47. m_fCannotSolveCurrent( false ),
  48. m_RefreshSamplesTimer( PLANE_SOLVER_THINK_FREQUENCY[AIStrongOpt()] - 0.05 )
  49. {
  50. }
  51. //-----------------------------------------------------------------------------
  52. // Convenience accessors
  53. //-----------------------------------------------------------------------------
  54. inline CAI_BaseNPC *CAI_PlaneSolver::GetNpc()
  55. {
  56. return m_pNpc;
  57. }
  58. inline CAI_Motor *CAI_PlaneSolver::GetMotor()
  59. {
  60. return m_pNpc->GetMotor();
  61. }
  62. inline const Vector &CAI_PlaneSolver::GetLocalOrigin()
  63. {
  64. return m_pNpc->GetLocalOrigin();
  65. }
  66. //-----------------------------------------------------------------------------
  67. // class CAI_PlaneSolver
  68. //-----------------------------------------------------------------------------
  69. bool CAI_PlaneSolver::MoveLimit( Navigation_t navType, const Vector &target, bool ignoreTransients, bool fCheckStep, int contents, AIMoveTrace_t *pMoveTrace )
  70. {
  71. AI_PROFILE_SCOPE( CAI_PlaneSolver_MoveLimit );
  72. int flags = ( navType == NAV_GROUND ) ? AIMLF_2D : AIMLF_DEFAULT;
  73. if ( ignoreTransients )
  74. {
  75. Assert( !ProbeForNpcs() );
  76. flags |= AIMLF_IGNORE_TRANSIENTS;
  77. }
  78. CAI_MoveProbe *pProbe = m_pNpc->GetMoveProbe();
  79. return pProbe->MoveLimit( navType, GetLocalOrigin(), target, contents,
  80. m_pNpc->GetNavTargetEntity(), (fCheckStep) ? 100 : 0,
  81. flags,
  82. pMoveTrace );
  83. }
  84. bool CAI_PlaneSolver::MoveLimit( Navigation_t navType, const Vector &target, bool ignoreTransients, bool fCheckStep, AIMoveTrace_t *pMoveTrace )
  85. {
  86. return MoveLimit( navType, target, ignoreTransients, fCheckStep, MASK_NPCSOLID, pMoveTrace );
  87. }
  88. //-----------------------------------------------------------------------------
  89. bool CAI_PlaneSolver::DetectUnsolvable( const AILocalMoveGoal_t &goal )
  90. {
  91. #ifndef TESTING_SUGGESTIONS
  92. float curDistance = ( goal.target.AsVector2D() - GetLocalOrigin().AsVector2D() ).Length();
  93. if ( m_PrevTarget != goal.target )
  94. {
  95. m_TimeLastProgress = gpGlobals->curtime;
  96. m_ClosestHaveBeenToCurrent = curDistance;
  97. m_fCannotSolveCurrent = false;
  98. }
  99. else
  100. {
  101. if ( m_fCannotSolveCurrent )
  102. {
  103. return true;
  104. }
  105. if ( m_ClosestHaveBeenToCurrent - curDistance > 0 )
  106. {
  107. m_TimeLastProgress = gpGlobals->curtime;
  108. m_ClosestHaveBeenToCurrent = curDistance;
  109. }
  110. else
  111. {
  112. if ( gpGlobals->curtime - m_TimeLastProgress > 0.75 )
  113. {
  114. m_fCannotSolveCurrent = true;
  115. return true;
  116. }
  117. }
  118. }
  119. #endif
  120. return false;
  121. }
  122. //-----------------------------------------------------------------------------
  123. float CAI_PlaneSolver::AdjustRegulationWeight( CBaseEntity *pEntity, float weight )
  124. {
  125. if ( pEntity->MyNPCPointer() != NULL )
  126. {
  127. // @TODO (toml 10-03-02): How to do this with non-NPC entities. Should be using intended solve velocity...
  128. Vector2D velOwner = GetNpc()->GetMotor()->GetCurVel().AsVector2D();
  129. Vector2D velBlocker = ((CAI_BaseNPC *)pEntity)->GetMotor()->GetCurVel().AsVector2D();
  130. Vector2D velOwnerNorm = velOwner;
  131. Vector2D velBlockerNorm = velBlocker;
  132. float speedOwner = Vector2DNormalize( velOwnerNorm );
  133. float speedBlocker = Vector2DNormalize( velBlockerNorm );
  134. float dot = velOwnerNorm.Dot( velBlockerNorm );
  135. if ( speedBlocker > 0 )
  136. {
  137. if ( dot > 0 && speedBlocker >= speedOwner * 0.9 )
  138. {
  139. if ( dot > 0.86 )
  140. {
  141. // @Note (toml 10-10-02): Even in the case of no obstacle, we generate
  142. // a suggestion in because we still want to continue sweeping the
  143. // search
  144. weight = 0;
  145. }
  146. else if ( dot > 0.7 )
  147. {
  148. weight *= sq( weight );
  149. }
  150. else
  151. weight *= weight;
  152. }
  153. }
  154. }
  155. return weight;
  156. }
  157. //-----------------------------------------------------------------------------
  158. float CAI_PlaneSolver::CalculateRegulationWeight( const AIMoveTrace_t &moveTrace, float pctBlocked )
  159. {
  160. float weight = 0;
  161. if ( pctBlocked > 0.9)
  162. weight = 1;
  163. else if ( pctBlocked < 0.1)
  164. weight = 0;
  165. else
  166. {
  167. weight = sq( ( pctBlocked - 0.1 ) / 0.8 );
  168. weight = AdjustRegulationWeight( moveTrace.pObstruction, weight );
  169. }
  170. return weight;
  171. }
  172. //-----------------------------------------------------------------------------
  173. void CAI_PlaneSolver::GenerateSuggestionFromTrace( const AILocalMoveGoal_t &goal,
  174. const AIMoveTrace_t &moveTrace, float probeDist,
  175. float arcCenter, float arcSpan, int probeOffset )
  176. {
  177. AI_MoveSuggestion_t suggestion;
  178. AI_MoveSuggType_t type;
  179. switch ( moveTrace.fStatus )
  180. {
  181. case AIMR_BLOCKED_ENTITY: type = AIMST_AVOID_OBJECT; break;
  182. case AIMR_BLOCKED_WORLD: type = AIMST_AVOID_WORLD; break;
  183. case AIMR_BLOCKED_NPC: type = AIMST_AVOID_NPC; break;
  184. case AIMR_ILLEGAL: type = AIMST_AVOID_DANGER; break;
  185. default: type = AIMST_NO_KNOWLEDGE; AssertMsg( 0, "Unexpected mode status" ); break;
  186. }
  187. if ( goal.pMoveTarget != NULL && goal.pMoveTarget == moveTrace.pObstruction )
  188. {
  189. suggestion.Set( type, 0,
  190. arcCenter, arcSpan,
  191. moveTrace.pObstruction );
  192. m_Solver.AddRegulation( suggestion );
  193. return;
  194. }
  195. float clearDist = probeDist - moveTrace.flDistObstructed;
  196. float pctBlocked = 1.0 - ( clearDist / probeDist );
  197. float weight = CalculateRegulationWeight( moveTrace, pctBlocked );
  198. if ( weight < 0.001 )
  199. return;
  200. if ( pctBlocked < 0.5 )
  201. {
  202. arcSpan *= pctBlocked * 2.0;
  203. }
  204. Vector vecToEnd = moveTrace.vEndPosition - GetLocalOrigin();
  205. Vector crossProduct;
  206. bool favorLeft = false, favorRight = false;
  207. if ( moveTrace.fStatus == AIMR_BLOCKED_NPC )
  208. {
  209. Vector vecToOther = moveTrace.pObstruction->GetLocalOrigin() - GetLocalOrigin();
  210. CrossProduct(vecToEnd, vecToOther, crossProduct);
  211. favorLeft = ( crossProduct.z < 0 );
  212. favorRight = ( crossProduct.z > 0 );
  213. }
  214. else if ( moveTrace.vHitNormal != vec3_origin )
  215. {
  216. CrossProduct(vecToEnd, moveTrace.vHitNormal, crossProduct);
  217. favorLeft = ( crossProduct.z > 0 );
  218. favorRight = ( crossProduct.z < 0 );
  219. }
  220. float thirdSpan = arcSpan / 3.0;
  221. float favoredWeight = weight * pctBlocked;
  222. suggestion.Set( type, weight,
  223. arcCenter, thirdSpan,
  224. moveTrace.pObstruction );
  225. m_Solver.AddRegulation( suggestion );
  226. suggestion.Set( type, ( favorRight ) ? favoredWeight : weight,
  227. arcCenter - thirdSpan, thirdSpan,
  228. moveTrace.pObstruction );
  229. m_Solver.AddRegulation( suggestion );
  230. suggestion.Set( type, ( favorLeft ) ? favoredWeight : weight,
  231. arcCenter + thirdSpan, thirdSpan,
  232. moveTrace.pObstruction );
  233. m_Solver.AddRegulation( suggestion );
  234. }
  235. //-----------------------------------------------------------------------------
  236. void CAI_PlaneSolver::CalcYawsFromOffset( float yawScanCenter, float spanPerProbe, int probeOffset,
  237. float *pYawTest, float *pYawCenter )
  238. {
  239. if ( probeOffset != 0 )
  240. {
  241. float sign = ( probeOffset > 0 ) ? 1 : -1;
  242. *pYawCenter = yawScanCenter + probeOffset * spanPerProbe;
  243. if ( *pYawCenter < 0 )
  244. *pYawCenter += 360;
  245. else if ( *pYawCenter >= 360 )
  246. *pYawCenter -= 360;
  247. *pYawTest = *pYawCenter - ( sign * spanPerProbe * 0.5 );
  248. if ( *pYawTest < 0 )
  249. *pYawTest += 360;
  250. else if ( *pYawTest >= 360 )
  251. *pYawTest -= 360;
  252. }
  253. else
  254. {
  255. *pYawCenter = *pYawTest = yawScanCenter;
  256. }
  257. }
  258. //-----------------------------------------------------------------------------
  259. void CAI_PlaneSolver::GenerateObstacleNpcs( const AILocalMoveGoal_t &goal, float probeDist )
  260. {
  261. if ( !ProbeForNpcs() )
  262. {
  263. CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
  264. Vector minsSelf, maxsSelf;
  265. m_pNpc->CollisionProp()->WorldSpaceSurroundingBounds( &minsSelf, &maxsSelf );
  266. float radiusSelf = (minsSelf.AsVector2D() - maxsSelf.AsVector2D()).Length() * 0.5;
  267. for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ )
  268. {
  269. CAI_BaseNPC *pAI = ppAIs[i];
  270. if ( pAI != m_pNpc && pAI->IsAlive() && ( !goal.pPath || pAI != goal.pPath->GetTarget() ) )
  271. {
  272. Vector mins, maxs;
  273. pAI->CollisionProp()->WorldSpaceSurroundingBounds( &mins, &maxs );
  274. if ( mins.z < maxsSelf.z + 12.0 && maxs.z > minsSelf.z - 12.0 )
  275. {
  276. float radius = (mins.AsVector2D() - maxs.AsVector2D()).Length() * 0.5;
  277. float distance = ( pAI->GetAbsOrigin().AsVector2D() - m_pNpc->GetAbsOrigin().AsVector2D() ).Length();
  278. if ( distance - radius < radiusSelf + probeDist )
  279. {
  280. AddObstacle( pAI->WorldSpaceCenter(), radius, pAI, AIMST_AVOID_NPC );
  281. }
  282. }
  283. }
  284. }
  285. CBaseEntity *pPlayer = UTIL_PlayerByIndex( 1 );
  286. if ( pPlayer )
  287. {
  288. Vector mins, maxs;
  289. pPlayer->CollisionProp()->WorldSpaceSurroundingBounds( &mins, &maxs );
  290. if ( mins.z < maxsSelf.z + 12.0 && maxs.z > minsSelf.z - 12.0 )
  291. {
  292. float radius = (mins.AsVector2D() - maxs.AsVector2D()).Length();
  293. float distance = ( pPlayer->GetAbsOrigin().AsVector2D() - m_pNpc->GetAbsOrigin().AsVector2D() ).Length();
  294. if ( distance - radius < radiusSelf + probeDist )
  295. {
  296. AddObstacle( pPlayer->WorldSpaceCenter(), radius, pPlayer, AIMST_AVOID_NPC );
  297. }
  298. }
  299. }
  300. }
  301. }
  302. //-----------------------------------------------------------------------------
  303. AI_SuggestorResult_t CAI_PlaneSolver::GenerateObstacleSuggestion( const AILocalMoveGoal_t &goal, float yawScanCenter,
  304. float probeDist, float spanPerProbe, int probeOffset)
  305. {
  306. AIMoveTrace_t moveTrace;
  307. float yawTest;
  308. float arcCenter;
  309. CalcYawsFromOffset( yawScanCenter, spanPerProbe, probeOffset, &yawTest, &arcCenter );
  310. Vector probeDir = UTIL_YawToVector( yawTest );
  311. float requiredMovement = goal.speed * GetMotor()->GetMoveInterval();
  312. // Probe immediate move with footing, then look further out ignoring footing
  313. bool fTraceClear = true;
  314. if ( probeDist > requiredMovement )
  315. {
  316. if ( !MoveLimit( goal.navType, GetLocalOrigin() + probeDir * requiredMovement, !ProbeForNpcs(), true, &moveTrace ) )
  317. {
  318. fTraceClear = false;
  319. moveTrace.flDistObstructed = (probeDist - requiredMovement) + moveTrace.flDistObstructed;
  320. }
  321. }
  322. if ( fTraceClear )
  323. {
  324. fTraceClear = MoveLimit( goal.navType, GetLocalOrigin() + probeDir * probeDist, !ProbeForNpcs(), false, &moveTrace );
  325. }
  326. if ( !fTraceClear )
  327. {
  328. GenerateSuggestionFromTrace( goal, moveTrace, probeDist, arcCenter, spanPerProbe, probeOffset );
  329. return SR_OK;
  330. }
  331. return SR_NONE;
  332. }
  333. //-----------------------------------------------------------------------------
  334. AI_SuggestorResult_t CAI_PlaneSolver::GenerateObstacleSuggestions( const AILocalMoveGoal_t &goal, const AIMoveTrace_t &directTrace,
  335. float distClear, float probeDist, float degreesToProbe, int nProbes )
  336. {
  337. Assert( nProbes % 2 == 1 );
  338. PLANESOLVER_PROFILE_SCOPE( CAI_PlaneSolver_GenerateObstacleSuggestions );
  339. AI_SuggestorResult_t seekResult = SR_NONE;
  340. bool fNewTarget = ( !m_fSolvedPrev || m_PrevTarget != goal.target );
  341. if ( fNewTarget )
  342. m_RefreshSamplesTimer.Force();
  343. if ( PLANE_SOLVER_THINK_FREQUENCY[AIStrongOpt()] == 0.0 || m_RefreshSamplesTimer.Expired() )
  344. {
  345. m_Solver.ClearRegulations();
  346. if ( !ProbeForNpcs() )
  347. GenerateObstacleNpcs( goal, probeDist );
  348. if ( GenerateCircleObstacleSuggestions( goal, probeDist ) )
  349. seekResult = SR_OK;
  350. float spanPerProbe = degreesToProbe / nProbes;
  351. int nSideProbes = (nProbes - 1) / 2;
  352. float yawGoalDir = UTIL_VecToYaw( goal.dir );
  353. Vector probeTarget;
  354. AIMoveTrace_t moveTrace;
  355. int i;
  356. // Generate suggestion from direct trace, or probe if direct trace doesn't match
  357. if ( fabs( probeDist - ( distClear + directTrace.flDistObstructed ) ) < 0.1 &&
  358. ( ProbeForNpcs() || directTrace.fStatus != AIMR_BLOCKED_NPC ) )
  359. {
  360. if ( directTrace.fStatus != AIMR_OK )
  361. {
  362. seekResult = SR_OK;
  363. GenerateSuggestionFromTrace( goal, directTrace, probeDist, yawGoalDir, spanPerProbe, 0 );
  364. }
  365. }
  366. else if ( GenerateObstacleSuggestion( goal, yawGoalDir, probeDist, spanPerProbe, 0 ) == SR_OK )
  367. {
  368. seekResult = SR_OK;
  369. }
  370. // Scan left. Note that in the left and right scan, the algorithm stops as soon
  371. // as there is a clear path. This is an optimization in anticipation of the
  372. // behavior of the underlying solver. This will break more often the higher
  373. // PLANE_SOLVER_THINK_FREQUENCY becomes
  374. bool foundClear = false;
  375. for ( i = 1; i <= nSideProbes; i++ )
  376. {
  377. if ( !foundClear )
  378. {
  379. AI_SuggestorResult_t curSeekResult = GenerateObstacleSuggestion( goal, yawGoalDir, probeDist,
  380. spanPerProbe, i );
  381. if ( curSeekResult == SR_OK )
  382. {
  383. seekResult = SR_OK;
  384. }
  385. else
  386. foundClear = true;
  387. }
  388. else
  389. {
  390. float ignored;
  391. float arcCenter;
  392. CalcYawsFromOffset( yawGoalDir, spanPerProbe, i, &ignored, &arcCenter );
  393. m_Solver.AddRegulation( AI_MoveSuggestion_t( AIMST_NO_KNOWLEDGE, 1, arcCenter, spanPerProbe ) );
  394. }
  395. }
  396. // Scan right
  397. foundClear = false;
  398. for ( i = -1; i >= -nSideProbes; i-- )
  399. {
  400. if ( !foundClear )
  401. {
  402. AI_SuggestorResult_t curSeekResult = GenerateObstacleSuggestion( goal, yawGoalDir, probeDist,
  403. spanPerProbe, i );
  404. if ( curSeekResult == SR_OK )
  405. {
  406. seekResult = SR_OK;
  407. }
  408. else
  409. foundClear = true;
  410. }
  411. else
  412. {
  413. float ignored;
  414. float arcCenter;
  415. CalcYawsFromOffset( yawGoalDir, spanPerProbe, i, &ignored, &arcCenter );
  416. m_Solver.AddRegulation( AI_MoveSuggestion_t( AIMST_NO_KNOWLEDGE, 1, arcCenter, spanPerProbe ) );
  417. }
  418. }
  419. if ( seekResult == SR_OK )
  420. {
  421. float arcCenter = yawGoalDir - 180;
  422. if ( arcCenter < 0 )
  423. arcCenter += 360;
  424. // Since these are not sampled every think, place a negative arc in all directions not sampled
  425. m_Solver.AddRegulation( AI_MoveSuggestion_t( AIMST_NO_KNOWLEDGE, 1, arcCenter, 360 - degreesToProbe ) );
  426. }
  427. m_RefreshSamplesTimer.Reset( PLANE_SOLVER_THINK_FREQUENCY[AIStrongOpt()] );
  428. }
  429. else if ( m_Solver.HaveRegulations() )
  430. seekResult = SR_OK;
  431. return seekResult;
  432. }
  433. //-----------------------------------------------------------------------------
  434. // Visualizes the regulations for debugging purposes
  435. //-----------------------------------------------------------------------------
  436. void CAI_PlaneSolver::VisualizeRegulations()
  437. {
  438. // Visualization of regulations
  439. if ((GetNpc()->m_debugOverlays & OVERLAY_NPC_STEERING_REGULATIONS) != 0)
  440. {
  441. m_Solver.VisualizeRegulations( GetNpc()->WorldSpaceCenter() );
  442. }
  443. }
  444. void CAI_PlaneSolver::VisualizeSolution( const Vector &vecGoal, const Vector& vecActual )
  445. {
  446. if ((GetNpc()->m_debugOverlays & OVERLAY_NPC_STEERING_REGULATIONS) != 0)
  447. {
  448. // Compute centroid...
  449. Vector centroid = GetNpc()->WorldSpaceCenter();
  450. Vector goalPt, actualPt;
  451. VectorMA( centroid, 20, vecGoal, goalPt );
  452. VectorMA( centroid, 20, vecActual, actualPt );
  453. NDebugOverlay::Line(centroid, goalPt, 255, 255, 255, true, 0.1f );
  454. NDebugOverlay::Line(centroid, actualPt, 255, 255, 0, true, 0.1f );
  455. }
  456. }
  457. //-----------------------------------------------------------------------------
  458. // Adjust the solution for fliers
  459. //-----------------------------------------------------------------------------
  460. #define MIN_ZDIR_TO_RADIUS 0.1f
  461. void CAI_PlaneSolver::AdjustSolutionForFliers( const AILocalMoveGoal_t &goal, float flSolutionYaw, Vector *pSolution )
  462. {
  463. // Fliers should move up if there are local obstructions...
  464. // A hacky solution, but the bigger the angle of deflection, the more likely
  465. // we're close to a problem and the higher we should go up.
  466. Assert( pSolution->z == 0.0f );
  467. // If we're largely needing to move down, then blow off the upward motion...
  468. Vector vecDelta, vecDir;
  469. VectorSubtract( goal.target, GetLocalOrigin(), vecDelta );
  470. vecDir = vecDelta;
  471. VectorNormalize( vecDir );
  472. float flRadius = sqrt( vecDir.x * vecDir.x + vecDir.y * vecDir.y );
  473. *pSolution *= flRadius;
  474. pSolution->z = vecDir.z;
  475. AssertFloatEquals( pSolution->LengthSqr(), 1.0f, 1e-3 );
  476. // Move up 0 when we have to move forward as much as we have to move down z (45 degree angle)
  477. // Move up max when we have to move forward 5x as much as we have to move down z,
  478. // or if we have to move up z.
  479. float flUpAmount = 0.0f;
  480. if ( vecDir.z >= -flRadius * MIN_ZDIR_TO_RADIUS)
  481. {
  482. flUpAmount = 1.0f;
  483. }
  484. else if ((vecDir.z <= -flRadius) || (fabs(vecDir.z) < 1e-3))
  485. {
  486. flUpAmount = 0.0f;
  487. }
  488. else
  489. {
  490. flUpAmount = (-flRadius / vecDir.z) - 1.0f;
  491. flUpAmount *= MIN_ZDIR_TO_RADIUS;
  492. Assert( (flUpAmount >= 0.0f) && (flUpAmount <= 1.0f) );
  493. }
  494. // Check the deflection amount...
  495. pSolution->z += flUpAmount * 5.0f;
  496. // FIXME: Also, if we've got a bunch of regulations, we may
  497. // also wish to raise up a little bit..because this indicates
  498. // that we've got a bunch of stuff to avoid
  499. VectorNormalize( *pSolution );
  500. }
  501. //-----------------------------------------------------------------------------
  502. unsigned CAI_PlaneSolver::ComputeTurnBiasFlags( const AILocalMoveGoal_t &goal, const AIMoveTrace_t &directTrace )
  503. {
  504. if ( directTrace.fStatus == AIMR_BLOCKED_WORLD )
  505. {
  506. // @TODO (toml 11-11-02): stuff plane normal of hit into trace Use here to compute a bias?
  507. //
  508. return 0;
  509. }
  510. if ( directTrace.fStatus == AIMR_BLOCKED_NPC )
  511. {
  512. return AIMS_FAVOR_LEFT;
  513. }
  514. return 0;
  515. }
  516. //-----------------------------------------------------------------------------
  517. bool CAI_PlaneSolver::RunMoveSolver( const AILocalMoveGoal_t &goal, const AIMoveTrace_t &directTrace, float degreesPositiveArc,
  518. bool fDeterOscillation, Vector *pResult )
  519. {
  520. PLANESOLVER_PROFILE_SCOPE( CAI_PlaneSolver_RunMoveSolver );
  521. AI_MoveSolution_t solution;
  522. if ( m_Solver.HaveRegulations() )
  523. {
  524. // @TODO (toml 07-19-02): add a movement threshhold here (the target may be the same,
  525. // but the ai is nowhere near where the last solution was derived)
  526. bool fNewTarget = ( !m_fSolvedPrev || m_PrevTarget != goal.target );
  527. // For debugging, visualize our regulations
  528. VisualizeRegulations();
  529. AI_MoveSuggestion_t moveSuggestions[2];
  530. int nSuggestions = 1;
  531. moveSuggestions[0].Set( AIMST_MOVE, 1, UTIL_VecToYaw( goal.dir ), degreesPositiveArc );
  532. moveSuggestions[0].flags |= ComputeTurnBiasFlags( goal, directTrace );
  533. if ( fDeterOscillation && !fNewTarget )
  534. {
  535. #ifndef TESTING_SUGGESTIONS
  536. moveSuggestions[nSuggestions++].Set( AIMST_OSCILLATION_DETERRANCE, 1, m_PrevSolution - 180, 180 );
  537. #endif
  538. }
  539. if ( m_Solver.Solve( moveSuggestions, nSuggestions, &solution ) )
  540. {
  541. *pResult = UTIL_YawToVector( solution.dir );
  542. if (goal.navType == NAV_FLY)
  543. {
  544. // FIXME: Does the z component have to occur during the goal
  545. // setting because it's there & only there where MoveLimit
  546. // will report contact with the world if we move up?
  547. AdjustSolutionForFliers( goal, solution.dir, pResult );
  548. }
  549. // A crude attempt at oscillation detection: if we solved last time, and this time, and the same target is
  550. // involved, and we resulted in nearly a 180, we are probably oscillating
  551. #ifndef TESTING_SUGGESTIONS
  552. if ( !fNewTarget )
  553. {
  554. float delta = solution.dir - m_PrevSolution;
  555. if ( delta < 0 )
  556. delta += 360;
  557. if ( delta > 165 && delta < 195 )
  558. return false;
  559. }
  560. #endif
  561. m_PrevSolution = solution.dir;
  562. m_PrevSolutionVector = *pResult;
  563. Vector curVelocity = m_pNpc->GetSmoothedVelocity();
  564. if ( curVelocity != vec3_origin )
  565. {
  566. VectorNormalize( curVelocity );
  567. if ( !fNewTarget )
  568. {
  569. *pResult = curVelocity * 0.1 + m_PrevSolutionVector * 0.1 + *pResult * 0.8;
  570. }
  571. else
  572. {
  573. *pResult = curVelocity * 0.2 + *pResult * 0.8;
  574. }
  575. }
  576. return true;
  577. }
  578. }
  579. else
  580. {
  581. if (goal.navType != NAV_FLY)
  582. {
  583. *pResult = goal.dir;
  584. }
  585. else
  586. {
  587. VectorSubtract( goal.target, GetLocalOrigin(), *pResult );
  588. VectorNormalize( *pResult );
  589. }
  590. return true;
  591. }
  592. return false;
  593. }
  594. //-----------------------------------------------------------------------------
  595. float CAI_PlaneSolver::CalcProbeDist( float speed )
  596. {
  597. // one second or one hull
  598. float result = GetLookaheadTime() * speed;
  599. if ( result < m_pNpc->GetMoveProbe()->GetHullWidth() )
  600. return m_pNpc->GetMoveProbe()->GetHullWidth();
  601. if ( result > MAX_PROBE_DIST[AIStrongOpt()] )
  602. return MAX_PROBE_DIST[AIStrongOpt()];
  603. return result;
  604. }
  605. //-----------------------------------------------------------------------------
  606. void CAI_PlaneSolver::AddObstacle( const Vector &center, float radius, CBaseEntity *pEntity, AI_MoveSuggType_t type )
  607. {
  608. m_Obstacles.AddToTail( CircleObstacles_t( center, radius, pEntity, type ) );
  609. }
  610. //-----------------------------------------------------------------------------
  611. bool CAI_PlaneSolver::GenerateCircleObstacleSuggestions( const AILocalMoveGoal_t &moveGoal, float probeDist )
  612. {
  613. bool result = false;
  614. Vector npcLoc = m_pNpc->WorldSpaceCenter();
  615. Vector mins, maxs;
  616. m_pNpc->CollisionProp()->WorldSpaceSurroundingBounds( &mins, &maxs );
  617. float radiusNpc = (mins.AsVector2D() - maxs.AsVector2D()).Length() * 0.5;
  618. for ( int i = 0; i < m_Obstacles.Count(); i++ )
  619. {
  620. CBaseEntity *pObstacleEntity = NULL;
  621. float zDistTooFar;
  622. if ( m_Obstacles[i].hEntity && m_Obstacles[i].hEntity->CollisionProp() )
  623. {
  624. pObstacleEntity = m_Obstacles[i].hEntity.Get();
  625. if( pObstacleEntity == moveGoal.pMoveTarget && (pObstacleEntity->IsNPC() || pObstacleEntity->IsPlayer()) )
  626. {
  627. // HEY! I'm trying to avoid the very thing I'm trying to get to. This will make we wobble like a drunk as I approach. Don't do it.
  628. continue;
  629. }
  630. pObstacleEntity->CollisionProp()->WorldSpaceSurroundingBounds( &mins, &maxs );
  631. zDistTooFar = ( maxs.z - mins.z ) * 0.5 + GetNpc()->GetHullHeight() * 0.5;
  632. }
  633. else
  634. zDistTooFar = GetNpc()->GetHullHeight();
  635. if ( fabs( m_Obstacles[i].center.z - npcLoc.z ) > zDistTooFar )
  636. continue;
  637. Vector vecToNpc = npcLoc - m_Obstacles[i].center;
  638. vecToNpc.z = 0;
  639. float distToObstacleSq = sq(vecToNpc.x) + sq(vecToNpc.y);
  640. float radius = m_Obstacles[i].radius + radiusNpc;
  641. if ( distToObstacleSq > 0.001 && distToObstacleSq < sq( radius + probeDist ) )
  642. {
  643. Vector vecToObstacle = vecToNpc * -1;
  644. float distToObstacle = VectorNormalize( vecToObstacle );
  645. float weight;
  646. float arc;
  647. float radiusSq = sq(radius);
  648. float flDot = DotProduct( vecToObstacle, moveGoal.dir );
  649. // Don't steer around to avoid obstacles we've already passed, unless we're right up against them.
  650. // That is, do this computation without the probeDist added in.
  651. if( flDot < 0.0f && distToObstacleSq > radiusSq )
  652. {
  653. continue;
  654. }
  655. if ( radiusSq < distToObstacleSq )
  656. {
  657. Vector vecTangent;
  658. float distToTangent = FastSqrt( distToObstacleSq - radiusSq );
  659. float oneOverDistToObstacleSq = 1 / distToObstacleSq;
  660. vecTangent.x = ( -distToTangent * vecToNpc.x + radius * vecToNpc.y ) * oneOverDistToObstacleSq;
  661. vecTangent.y = ( -distToTangent * vecToNpc.y - radius * vecToNpc.x ) * oneOverDistToObstacleSq;
  662. vecTangent.z = 0;
  663. float cosHalfArc = vecToObstacle.Dot( vecTangent );
  664. arc = RAD2DEG(acosf( cosHalfArc )) * 2.0;
  665. weight = 1.0 - (distToObstacle - radius) / probeDist;
  666. if ( weight > 0.75 )
  667. arc += (arc * 0.5) * (weight - 0.75) / 0.25;
  668. Assert( weight >= 0.0 && weight <= 1.0 );
  669. #if DEBUG_OBSTACLES
  670. // -------------------------
  671. Msg( "Adding arc %f, w %f\n", arc, weight );
  672. Vector pointTangent = npcLoc + ( vecTangent * distToTangent );
  673. NDebugOverlay::Line( npcLoc - Vector( 0, 0, 64 ), npcLoc + Vector(0,0,64), 0,255,0, false, 0.1 );
  674. NDebugOverlay::Line( center - Vector( 0, 0, 64 ), center + Vector(0,0,64), 0,255,0, false, 0.1 );
  675. NDebugOverlay::Line( pointTangent - Vector( 0, 0, 64 ), pointTangent + Vector(0,0,64), 0,255,0, false, 0.1 );
  676. NDebugOverlay::Line( npcLoc + Vector(0,0,64), center + Vector(0,0,64), 0,0,255, false, 0.1 );
  677. NDebugOverlay::Line( center + Vector(0,0,64), pointTangent + Vector(0,0,64), 0,0,255, false, 0.1 );
  678. NDebugOverlay::Line( pointTangent + Vector(0,0,64), npcLoc + Vector(0,0,64), 0,0,255, false, 0.1 );
  679. #endif
  680. }
  681. else
  682. {
  683. arc = 210;
  684. weight = 1.0;
  685. }
  686. if ( m_Obstacles[i].hEntity != NULL )
  687. {
  688. weight = AdjustRegulationWeight( m_Obstacles[i].hEntity, weight );
  689. }
  690. AI_MoveSuggestion_t suggestion( m_Obstacles[i].type, weight, UTIL_VecToYaw(vecToObstacle), arc );
  691. m_Solver.AddRegulation( suggestion );
  692. result = true;
  693. }
  694. }
  695. m_Obstacles.RemoveAll();
  696. return result;
  697. }
  698. //-----------------------------------------------------------------------------
  699. bool CAI_PlaneSolver::Solve( const AILocalMoveGoal_t &goal, float distClear, Vector *pSolution )
  700. {
  701. bool solved = false;
  702. //---------------------------------
  703. if ( goal.speed == 0 )
  704. return false;
  705. if ( DetectUnsolvable( goal ) )
  706. return false;
  707. //---------------------------------
  708. bool fVeryClose = ( distClear < 1.0 );
  709. float degreesPositiveArc = ( !fVeryClose ) ? DEGREES_POSITIVE_ARC : DEGREES_POSITIVE_ARC_CLOSE_OBSTRUCTION;
  710. float probeDist = CalcProbeDist( goal.speed );
  711. if ( goal.flags & ( AILMG_TARGET_IS_TRANSITION | AILMG_TARGET_IS_GOAL ) )
  712. {
  713. probeDist = MIN( goal.maxDist, probeDist );
  714. }
  715. if ( GenerateObstacleSuggestions( goal, goal.directTrace, distClear, probeDist, degreesPositiveArc, NUM_PROBES ) != SR_FAIL )
  716. {
  717. if ( RunMoveSolver( goal, goal.directTrace, degreesPositiveArc, !fVeryClose, pSolution ) )
  718. {
  719. // Visualize desired + actual directions
  720. VisualizeSolution( goal.dir, *pSolution );
  721. AIMoveTrace_t moveTrace;
  722. float requiredMovement = goal.speed * GetMotor()->GetMoveInterval();
  723. MoveLimit( goal.navType, GetLocalOrigin() + *pSolution * requiredMovement, false, true, &moveTrace );
  724. if ( !IsMoveBlocked( moveTrace ) )
  725. solved = true;
  726. else
  727. solved = false;
  728. }
  729. }
  730. m_fSolvedPrev = ( solved && goal.speed != 0 ); // a solution found when speed is zero is not meaningful
  731. m_PrevTarget = goal.target;
  732. return solved;
  733. }
  734. //=============================================================================