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.

410 lines
10 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "ai_movesolver.h"
  9. #include "ndebugoverlay.h"
  10. // memdbgon must be the last include file in a .cpp file!!!
  11. #include "tier0/memdbgon.h"
  12. //-----------------------------------------------------------------------------
  13. inline float V_round( float f )
  14. {
  15. return (float)( (int)( f + 0.5 ) );
  16. }
  17. //-----------------------------------------------------------------------------
  18. // CAI_MoveSolver
  19. //-----------------------------------------------------------------------------
  20. // The epsilon used by the solver
  21. const float AIMS_EPS = 0.01;
  22. //-----------------------------------------------------------------------------
  23. // Visualization
  24. //-----------------------------------------------------------------------------
  25. void CAI_MoveSolver::VisualizeRegulations( const Vector& origin )
  26. {
  27. if ( m_Regulations.Count() )
  28. {
  29. CAI_MoveSuggestions regulations;
  30. regulations.AddVectorToTail( m_Regulations );
  31. NormalizeSuggestions( &regulations[0], (&regulations[0]) + regulations.Count() );
  32. Vector side1, mid, side2;
  33. for (int i = regulations.Count(); --i >= 0; )
  34. {
  35. // Compute the positions of the angles...
  36. float flMinAngle = regulations[i].arc.center - regulations[i].arc.span * 0.5f;
  37. float flMaxAngle = regulations[i].arc.center + regulations[i].arc.span * 0.5f;
  38. side1 = UTIL_YawToVector( flMinAngle );
  39. side2 = UTIL_YawToVector( flMaxAngle );
  40. mid = UTIL_YawToVector( regulations[i].arc.center );
  41. // Stronger weighted ones are bigger
  42. if ( regulations[i].weight < 0 )
  43. {
  44. float flLength = 10 + 40 * ( regulations[i].weight * -1.0);
  45. side1 *= flLength;
  46. side2 *= flLength;
  47. mid *= flLength;
  48. side1 += origin;
  49. side2 += origin;
  50. mid += origin;
  51. NDebugOverlay::Triangle(origin, mid, side1, 255, 0, 0, 48, true, 0.1f );
  52. NDebugOverlay::Triangle(origin, side2, mid, 255, 0, 0, 48, true, 0.1f );
  53. }
  54. }
  55. }
  56. }
  57. //-------------------------------------
  58. // Purpose: The actual solver function. Reweights according to type and sums
  59. // all the suggestions, identifying the best.
  60. //-------------------------------------
  61. bool CAI_MoveSolver::Solve( const AI_MoveSuggestion_t *pSuggestions, int nSuggestions, AI_MoveSolution_t *pResult)
  62. {
  63. //---------------------------------
  64. //
  65. // Quick out
  66. //
  67. if ( !nSuggestions )
  68. return false;
  69. if ( nSuggestions == 1 && m_Regulations.Count() == 0 && pSuggestions->type == AIMST_MOVE )
  70. {
  71. pResult->dir = pSuggestions->arc.center;
  72. return true;
  73. }
  74. //---------------------------------
  75. //
  76. // Setup
  77. //
  78. CAI_MoveSuggestions suggestions;
  79. suggestions.EnsureCapacity( m_Regulations.Count() + nSuggestions );
  80. suggestions.CopyArray( pSuggestions, nSuggestions);
  81. suggestions.AddVectorToTail( m_Regulations );
  82. // Initialize the solver
  83. const int NUM_SOLUTIONS = 120;
  84. const int SOLUTION_ANG = 360 / NUM_SOLUTIONS;
  85. COMPILE_TIME_ASSERT( ( 360 % NUM_SOLUTIONS ) == 0 );
  86. struct Solution_t
  87. {
  88. // The sum bias
  89. float bias;
  90. float highBias;
  91. AI_MoveSuggestion_t *pHighSuggestion;
  92. };
  93. Solution_t solutions[NUM_SOLUTIONS] = { { 0, 0, NULL } };
  94. //---------------------------------
  95. // The first thing we do is reweight and normalize the weights into a range of [-1..1], where
  96. // a negative weight is a repulsion. This becomes a bias for the solver.
  97. // @TODO (toml 06-18-02): this can be made sligtly more optimal by precalculating regulation adjusted weights
  98. Assert( suggestions.Count() >= 1 );
  99. NormalizeSuggestions( &suggestions[0], (&suggestions[0]) + suggestions.Count() );
  100. //
  101. // Add the biased suggestions to the solutions
  102. //
  103. for ( int iSuggestion = 0; iSuggestion < suggestions.Count(); ++iSuggestion )
  104. {
  105. AI_MoveSuggestion_t &current = suggestions[iSuggestion];
  106. // Convert arc values to solution indices relative to right post. Right is angle down, left is angle up.
  107. float halfSpan = current.arc.span * 0.5;
  108. int center = V_round( ( halfSpan * NUM_SOLUTIONS ) / 360 );
  109. int left = ( current.arc.span * NUM_SOLUTIONS ) / 360;
  110. float angRight = current.arc.center - halfSpan;
  111. if (angRight < 0.0)
  112. angRight += 360;
  113. int base = ( angRight * NUM_SOLUTIONS ) / 360;
  114. // Sweep from left to right, summing the bias. For positive suggestions,
  115. // the bias is further weighted to favor the center of the arc.
  116. const float positiveDegradePer180 = 0.05; // i.e., lose 5% of weight by the time hit 180 degrees off center
  117. const float positiveDegrade = ( positiveDegradePer180 / ( NUM_SOLUTIONS * 0.5 ) );
  118. for ( int i = 0; i < left + 1; ++i )
  119. {
  120. float bias = 0.0;
  121. if ( current.weight > 0)
  122. {
  123. int iOffset = center - i;
  124. float degrade = abs( iOffset ) * positiveDegrade;
  125. if ( ( (current.flags & AIMS_FAVOR_LEFT ) && i > center ) ||
  126. ( (current.flags & AIMS_FAVOR_RIGHT) && i < center ) )
  127. {
  128. degrade *= 0.9;
  129. }
  130. bias = current.weight - ( current.weight * degrade );
  131. }
  132. else
  133. bias = current.weight;
  134. int iCurSolution = (base + i) % NUM_SOLUTIONS;
  135. solutions[iCurSolution].bias += bias;
  136. if ( bias > solutions[iCurSolution].highBias )
  137. {
  138. solutions[iCurSolution].highBias = bias;
  139. solutions[iCurSolution].pHighSuggestion = &current;
  140. }
  141. }
  142. }
  143. //
  144. // Find the best solution
  145. //
  146. int best = -1;
  147. float biasBest = 0;
  148. for ( int i = 0; i < NUM_SOLUTIONS; ++i )
  149. {
  150. if ( solutions[i].bias > biasBest )
  151. {
  152. best = i;
  153. biasBest = solutions[i].bias;
  154. }
  155. }
  156. if ( best == -1 )
  157. return false; // no solution
  158. //
  159. // Construct the results
  160. //
  161. float result = best * SOLUTION_ANG;
  162. // If the matching suggestion is within the solution, use that as the result,
  163. // as it is valid and more precise.
  164. const float suggestionCenter = solutions[best].pHighSuggestion->arc.center;
  165. if ( suggestionCenter > result && suggestionCenter <= result + SOLUTION_ANG )
  166. result = suggestionCenter;
  167. pResult->dir = result;
  168. return true;
  169. }
  170. //-------------------------------------
  171. // Purpose: Adjusts the suggestion weights according to the type of the suggestion,
  172. // apply the appropriate sign, ensure values are in expected ranges
  173. //-------------------------------------
  174. struct AI_MoveSuggWeights
  175. {
  176. float min;
  177. float max;
  178. };
  179. static AI_MoveSuggWeights g_AI_MoveSuggWeights[] = // @TODO (toml 06-18-02): these numbers need tuning
  180. {
  181. { 0.20, 1.00 }, // AIMST_MOVE
  182. { -0.00, -0.25 }, // AIMST_AVOID_DANGER
  183. { -0.00, -0.25 }, // AIMST_AVOID_OBJECT
  184. { -0.00, -0.25 }, // AIMST_AVOID_NPC
  185. { -0.00, -0.25 }, // AIMST_AVOID_WORLD
  186. { -1.00, -1.00 }, // AIMST_NO_KNOWLEDGE
  187. { -0.60, -0.60 }, // AIMST_OSCILLATION_DETERRANCE
  188. { 0.00, 0.00 }, // AIMST_INVALID
  189. };
  190. void CAI_MoveSolver::NormalizeSuggestions( AI_MoveSuggestion_t *pBegin, AI_MoveSuggestion_t *pEnd )
  191. {
  192. while ( pBegin != pEnd )
  193. {
  194. const float min = g_AI_MoveSuggWeights[pBegin->type].min;
  195. const float max = g_AI_MoveSuggWeights[pBegin->type].max;
  196. Assert( pBegin->weight >= -AIMS_EPS && pBegin->weight <= 1.0 + AIMS_EPS );
  197. if ( pBegin->weight < AIMS_EPS ) // zero normalizes to zero
  198. pBegin->weight = 0.0;
  199. else
  200. pBegin->weight = ( ( max - min ) * pBegin->weight ) + min;
  201. while (pBegin->arc.center < 0)
  202. pBegin->arc.center += 360;
  203. while (pBegin->arc.center >= 360)
  204. pBegin->arc.center -= 360;
  205. ++pBegin;
  206. }
  207. }
  208. //-------------------------------------
  209. bool CAI_MoveSolver::HaveRegulationForObstacle( CBaseEntity *pEntity)
  210. {
  211. for ( int i = 0; i < m_Regulations.Count(); ++i )
  212. {
  213. if ( m_Regulations[i].hObstacleEntity != NULL &&
  214. pEntity == m_Regulations[i].hObstacleEntity.Get() )
  215. {
  216. return true;
  217. }
  218. }
  219. return false;
  220. }
  221. //-----------------------------------------------------------------------------
  222. //
  223. // Commands and tests
  224. //
  225. #ifdef DEBUG
  226. CON_COMMAND(ai_test_move_solver, "Tests the AI move solver system")
  227. {
  228. #ifdef DEBUG
  229. const float EPS = 0.001;
  230. #endif
  231. DevMsg( "Beginning move solver tests...\n" );
  232. CAI_MoveSolver solver;
  233. AI_MoveSolution_t solution;
  234. int i;
  235. //
  236. // Value in, no regulations, should yield value out
  237. //
  238. {
  239. DevMsg( "Simple... " );
  240. for (i = 0; i < 360; ++i)
  241. {
  242. Assert( solver.Solve( AI_MoveSuggestion_t( AIMST_MOVE, 1, i, 180 ), &solution ) );
  243. Assert( solution.dir == (float)i );
  244. }
  245. DevMsg( "pass.\n" );
  246. solver.ClearRegulations();
  247. }
  248. //
  249. // Two values in, should yield the first
  250. //
  251. {
  252. DevMsg( "Two positive... " );
  253. AI_MoveSuggestion_t suggestions[2];
  254. suggestions[0].Set( AIMST_MOVE, 1.0, 180, 100 );
  255. suggestions[1].Set( AIMST_MOVE, 0.5, 0, 100 );
  256. Assert( solver.Solve( suggestions, 2, &solution ) );
  257. Assert( solution.dir == (float)suggestions[0].arc.center );
  258. DevMsg( "pass.\n" );
  259. solver.ClearRegulations();
  260. }
  261. //
  262. // Two values in, first regulated, should yield the second
  263. //
  264. {
  265. DevMsg( "Avoid one of two... " );
  266. AI_MoveSuggestion_t suggestions[2];
  267. solver.AddRegulation(AI_MoveSuggestion_t( AIMST_AVOID_OBJECT, 1, 260, 60 ) );
  268. suggestions[0].Set( AIMST_MOVE, 1.0, 270, 45 );
  269. suggestions[1].Set( AIMST_MOVE, 1.0, 0, 45 );
  270. Assert( solver.Solve( suggestions, 2, &solution ) );
  271. Assert( solution.dir == (float)suggestions[1].arc.center );
  272. DevMsg( "pass.\n" );
  273. solver.ClearRegulations();
  274. }
  275. //
  276. // No solution
  277. //
  278. {
  279. DevMsg( "No solution... " );
  280. AI_MoveSuggestion_t suggestions[2];
  281. suggestions[0].Set( AIMST_MOVE, 1.0, 270, 90 );
  282. suggestions[1].Set( AIMST_AVOID_OBJECT, 1.0, 260, 180 );
  283. Assert( !solver.Solve( suggestions, 2, &solution ) );
  284. DevMsg( "pass.\n" );
  285. solver.ClearRegulations();
  286. }
  287. //
  288. // Nearest solution, in tolerance
  289. //
  290. {
  291. DevMsg( "Nearest solution, in tolerance... " );
  292. AI_MoveSuggestion_t suggestions[2];
  293. suggestions[0].Set( AIMST_MOVE, 1.0, 278, 90 );
  294. suggestions[1].Set( AIMST_AVOID_OBJECT, 1.0, 260, 24 );
  295. Assert( solver.Solve( suggestions, 2, &solution ) );
  296. Assert( solution.dir == (float)suggestions[0].arc.center );
  297. DevMsg( "pass.\n" );
  298. solver.ClearRegulations();
  299. }
  300. //
  301. // Nearest solution
  302. //
  303. {
  304. DevMsg( "Nearest solution... " );
  305. AI_MoveSuggestion_t suggestions[2];
  306. suggestions[0].Set( AIMST_MOVE, 1.0, 270, 90 );
  307. suggestions[1].Set( AIMST_AVOID_OBJECT, 1.0, 260, 40 );
  308. Assert( solver.Solve( suggestions, 2, &solution ) );
  309. Assert( solution.dir - 282 < EPS ); // given 60 solutions
  310. DevMsg( "pass.\n" );
  311. solver.ClearRegulations();
  312. }
  313. }
  314. #endif
  315. //=============================================================================