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.

960 lines
28 KiB

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