DtCrowd.cs 52 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349
  1. /*
  2. Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
  3. recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
  4. DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
  5. This software is provided 'as-is', without any express or implied
  6. warranty. In no event will the authors be held liable for any damages
  7. arising from the use of this software.
  8. Permission is granted to anyone to use this software for any purpose,
  9. including commercial applications, and to alter it and redistribute it
  10. freely, subject to the following restrictions:
  11. 1. The origin of this software must not be misrepresented; you must not
  12. claim that you wrote the original software. If you use this software
  13. in a product, an acknowledgment in the product documentation would be
  14. appreciated but is not required.
  15. 2. Altered source versions must be plainly marked as such, and must not be
  16. misrepresented as being the original software.
  17. 3. This notice may not be removed or altered from any source distribution.
  18. */
  19. using System;
  20. using System.Collections.Concurrent;
  21. using System.Collections.Generic;
  22. using System.Collections.ObjectModel;
  23. using System.Linq;
  24. using DotRecast.Core;
  25. using DotRecast.Detour.Crowd.Tracking;
  26. namespace DotRecast.Detour.Crowd
  27. {
  28. using static DotRecast.Core.RcMath;
  29. /**
  30. * Members in this module implement local steering and dynamic avoidance features.
  31. *
  32. * The crowd is the big beast of the navigation features. It not only handles a lot of the path management for you, but
  33. * also local steering and dynamic avoidance between members of the crowd. I.e. It can keep your agents from running
  34. * into each other.
  35. *
  36. * Main class: Crowd
  37. *
  38. * The #dtNavMeshQuery and #dtPathCorridor classes provide perfectly good, easy to use path planning features. But in
  39. * the end they only give you points that your navigation client should be moving toward. When it comes to deciding
  40. * things like agent velocity and steering to avoid other agents, that is up to you to implement. Unless, of course, you
  41. * decide to use Crowd.
  42. *
  43. * Basically, you add an agent to the crowd, providing various configuration settings such as maximum speed and
  44. * acceleration. You also provide a local target to move toward. The crowd manager then provides, with every update, the
  45. * new agent position and velocity for the frame. The movement will be constrained to the navigation mesh, and steering
  46. * will be applied to ensure agents managed by the crowd do not collide with each other.
  47. *
  48. * This is very powerful feature set. But it comes with limitations.
  49. *
  50. * The biggest limitation is that you must give control of the agent's position completely over to the crowd manager.
  51. * You can update things like maximum speed and acceleration. But in order for the crowd manager to do its thing, it
  52. * can't allow you to constantly be giving it overrides to position and velocity. So you give up direct control of the
  53. * agent's movement. It belongs to the crowd.
  54. *
  55. * The second biggest limitation revolves around the fact that the crowd manager deals with local planning. So the
  56. * agent's target should never be more than 256 polygons away from its current position. If it is, you risk your agent
  57. * failing to reach its target. So you may still need to do long distance planning and provide the crowd manager with
  58. * intermediate targets.
  59. *
  60. * Other significant limitations:
  61. *
  62. * - All agents using the crowd manager will use the same #dtQueryFilter. - Crowd management is relatively expensive.
  63. * The maximum agents under crowd management at any one time is between 20 and 30. A good place to start is a maximum of
  64. * 25 agents for 0.5ms per frame.
  65. *
  66. * @note This is a summary list of members. Use the index or search feature to find minor members.
  67. *
  68. * @struct dtCrowdAgentParams
  69. * @see CrowdAgent, Crowd::AddAgent(), Crowd::UpdateAgentParameters()
  70. *
  71. * @var dtCrowdAgentParams::obstacleAvoidanceType
  72. * @par
  73. *
  74. * #dtCrowd permits agents to use different avoidance configurations. This value is the index of the
  75. * #dtObstacleAvoidanceParams within the crowd.
  76. *
  77. * @see dtObstacleAvoidanceParams, dtCrowd::SetObstacleAvoidanceParams(), dtCrowd::GetObstacleAvoidanceParams()
  78. *
  79. * @var dtCrowdAgentParams::collisionQueryRange
  80. * @par
  81. *
  82. * Collision elements include other agents and navigation mesh boundaries.
  83. *
  84. * This value is often based on the agent radius and/or maximum speed. E.g. radius * 8
  85. *
  86. * @var dtCrowdAgentParams::pathOptimizationRange
  87. * @par
  88. *
  89. * Only applicable if #updateFlags includes the #DT_CROWD_OPTIMIZE_VIS flag.
  90. *
  91. * This value is often based on the agent radius. E.g. radius * 30
  92. *
  93. * @see dtPathCorridor::OptimizePathVisibility()
  94. *
  95. * @var dtCrowdAgentParams::separationWeight
  96. * @par
  97. *
  98. * A higher value will result in agents trying to stay farther away from each other at the cost of more difficult
  99. * steering in tight spaces.
  100. *
  101. */
  102. /**
  103. * This is the core class of the refs crowd module. See the refs crowd documentation for a summary of the crowd
  104. * features. A common method for setting up the crowd is as follows: -# Allocate the crowd -# Set the avoidance
  105. * configurations using #SetObstacleAvoidanceParams(). -# Add agents using #AddAgent() and make an initial movement
  106. * request using #RequestMoveTarget(). A common process for managing the crowd is as follows: -# Call #Update() to allow
  107. * the crowd to manage its agents. -# Retrieve agent information using #GetActiveAgents(). -# Make movement requests
  108. * using #RequestMoveTarget() when movement goal changes. -# Repeat every frame. Some agent configuration settings can
  109. * be updated using #UpdateAgentParameters(). But the crowd owns the agent position. So it is not possible to update an
  110. * active agent's position. If agent position must be fed back into the crowd, the agent must be removed and re-added.
  111. * Notes: - Path related information is available for newly added agents only after an #Update() has been performed. -
  112. * Agent objects are kept in a pool and re-used. So it is important when using agent objects to check the value of
  113. * #dtCrowdAgent::active to determine if the agent is actually in use or not. - This class is meant to provide 'local'
  114. * movement. There is a limit of 256 polygons in the path corridor. So it is not meant to provide automatic pathfinding
  115. * services over long distances.
  116. *
  117. * @see DtAllocCrowd(), DtFreeCrowd(), Init(), dtCrowdAgent
  118. */
  119. public class DtCrowd
  120. {
  121. /// The maximum number of corners a crowd agent will look ahead in the path.
  122. /// This value is used for sizing the crowd agent corner buffers.
  123. /// Due to the behavior of the crowd manager, the actual number of useful
  124. /// corners will be one less than this number.
  125. /// @ingroup crowd
  126. public const int DT_CROWDAGENT_MAX_CORNERS = 4;
  127. /// The maximum number of crowd avoidance configurations supported by the
  128. /// crowd manager.
  129. /// @ingroup crowd
  130. /// @see dtObstacleAvoidanceParams, dtCrowd::SetObstacleAvoidanceParams(), dtCrowd::GetObstacleAvoidanceParams(),
  131. /// dtCrowdAgentParams::obstacleAvoidanceType
  132. public const int DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS = 8;
  133. /// The maximum number of query filter types supported by the crowd manager.
  134. /// @ingroup crowd
  135. /// @see dtQueryFilter, dtCrowd::GetFilter() dtCrowd::GetEditableFilter(),
  136. /// dtCrowdAgentParams::queryFilterType
  137. public const int DT_CROWD_MAX_QUERY_FILTER_TYPE = 16;
  138. private readonly RcAtomicInteger _agentId = new RcAtomicInteger();
  139. private readonly List<DtCrowdAgent> _agents;
  140. private readonly DtPathQueue _pathQ;
  141. private readonly DtObstacleAvoidanceParams[] _obstacleQueryParams = new DtObstacleAvoidanceParams[DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS];
  142. private readonly DtObstacleAvoidanceQuery _obstacleQuery;
  143. private DtProximityGrid _grid;
  144. private readonly RcVec3f _ext = new RcVec3f();
  145. private readonly IDtQueryFilter[] _filters = new IDtQueryFilter[DT_CROWD_MAX_QUERY_FILTER_TYPE];
  146. private DtNavMeshQuery _navQuery;
  147. private DtNavMesh _navMesh;
  148. private readonly DtCrowdConfig _config;
  149. private readonly DtCrowdTelemetry _telemetry = new DtCrowdTelemetry();
  150. private int _velocitySampleCount;
  151. public DtCrowd(DtCrowdConfig config, DtNavMesh nav) :
  152. this(config, nav, i => new DtQueryDefaultFilter())
  153. {
  154. }
  155. public DtCrowd(DtCrowdConfig config, DtNavMesh nav, Func<int, IDtQueryFilter> queryFilterFactory)
  156. {
  157. _config = config;
  158. _ext.Set(config.maxAgentRadius * 2.0f, config.maxAgentRadius * 1.5f, config.maxAgentRadius * 2.0f);
  159. _obstacleQuery = new DtObstacleAvoidanceQuery(config.maxObstacleAvoidanceCircles, config.maxObstacleAvoidanceSegments);
  160. for (int i = 0; i < DT_CROWD_MAX_QUERY_FILTER_TYPE; i++)
  161. {
  162. _filters[i] = queryFilterFactory.Invoke(i);
  163. }
  164. // Init obstacle query option.
  165. for (int i = 0; i < DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS; ++i)
  166. {
  167. _obstacleQueryParams[i] = new DtObstacleAvoidanceParams();
  168. }
  169. // Allocate temp buffer for merging paths.
  170. _pathQ = new DtPathQueue(config);
  171. _agents = new List<DtCrowdAgent>();
  172. // The navQuery is mostly used for local searches, no need for large node pool.
  173. _navMesh = nav;
  174. _navQuery = new DtNavMeshQuery(nav);
  175. }
  176. public void SetNavMesh(DtNavMesh nav)
  177. {
  178. _navMesh = nav;
  179. _navQuery = new DtNavMeshQuery(nav);
  180. }
  181. /// Sets the shared avoidance configuration for the specified index.
  182. /// @param[in] idx The index. [Limits: 0 <= value <
  183. /// #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS]
  184. /// @param[in] option The new configuration.
  185. public void SetObstacleAvoidanceParams(int idx, DtObstacleAvoidanceParams option)
  186. {
  187. if (idx >= 0 && idx < DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS)
  188. {
  189. _obstacleQueryParams[idx] = new DtObstacleAvoidanceParams(option);
  190. }
  191. }
  192. /// Gets the shared avoidance configuration for the specified index.
  193. /// @param[in] idx The index of the configuration to retreive.
  194. /// [Limits: 0 <= value < #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS]
  195. /// @return The requested configuration.
  196. public DtObstacleAvoidanceParams GetObstacleAvoidanceParams(int idx)
  197. {
  198. if (idx >= 0 && idx < DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS)
  199. {
  200. return _obstacleQueryParams[idx];
  201. }
  202. return null;
  203. }
  204. /// Updates the specified agent's configuration.
  205. /// @param[in] idx The agent index. [Limits: 0 <= value < #GetAgentCount()]
  206. /// @param[in] params The new agent configuration.
  207. public void UpdateAgentParameters(DtCrowdAgent agent, DtCrowdAgentParams option)
  208. {
  209. agent.option = option;
  210. }
  211. /**
  212. * Adds a new agent to the crowd.
  213. *
  214. * @param pos
  215. * The requested position of the agent. [(x, y, z)]
  216. * @param params
  217. * The configuration of the agent.
  218. * @return The newly created agent object
  219. */
  220. public DtCrowdAgent AddAgent(RcVec3f pos, DtCrowdAgentParams option)
  221. {
  222. DtCrowdAgent ag = new DtCrowdAgent(_agentId.GetAndIncrement());
  223. _agents.Add(ag);
  224. UpdateAgentParameters(ag, option);
  225. // Find nearest position on navmesh and place the agent there.
  226. var status = _navQuery.FindNearestPoly(pos, _ext, _filters[ag.option.queryFilterType], out var refs, out var nearestPt, out var _);
  227. if (status.Failed())
  228. {
  229. nearestPt = pos;
  230. refs = 0;
  231. }
  232. ag.corridor.Reset(refs, nearestPt);
  233. ag.boundary.Reset();
  234. ag.partial = false;
  235. ag.topologyOptTime = 0;
  236. ag.targetReplanTime = 0;
  237. ag.dvel = RcVec3f.Zero;
  238. ag.nvel = RcVec3f.Zero;
  239. ag.vel = RcVec3f.Zero;
  240. ag.npos = nearestPt;
  241. ag.desiredSpeed = 0;
  242. if (refs != 0)
  243. {
  244. ag.state = DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING;
  245. }
  246. else
  247. {
  248. ag.state = DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID;
  249. }
  250. ag.targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE;
  251. return ag;
  252. }
  253. /**
  254. * Removes the agent from the crowd.
  255. *
  256. * @param agent
  257. * Agent to be removed
  258. */
  259. public void RemoveAgent(DtCrowdAgent agent)
  260. {
  261. _agents.Remove(agent);
  262. }
  263. private bool RequestMoveTargetReplan(DtCrowdAgent ag, long refs, RcVec3f pos)
  264. {
  265. ag.SetTarget(refs, pos);
  266. ag.targetReplan = true;
  267. return true;
  268. }
  269. /// Submits a new move request for the specified agent.
  270. /// @param[in] idx The agent index. [Limits: 0 <= value < #GetAgentCount()]
  271. /// @param[in] ref The position's polygon reference.
  272. /// @param[in] pos The position within the polygon. [(x, y, z)]
  273. /// @return True if the request was successfully submitted.
  274. ///
  275. /// This method is used when a new target is set.
  276. ///
  277. /// The position will be constrained to the surface of the navigation mesh.
  278. ///
  279. /// The request will be processed during the next #Update().
  280. public bool RequestMoveTarget(DtCrowdAgent agent, long refs, RcVec3f pos)
  281. {
  282. if (refs == 0)
  283. {
  284. return false;
  285. }
  286. // Initialize request.
  287. agent.SetTarget(refs, pos);
  288. agent.targetReplan = false;
  289. return true;
  290. }
  291. /// Submits a new move request for the specified agent.
  292. /// @param[in] idx The agent index. [Limits: 0 <= value < #GetAgentCount()]
  293. /// @param[in] vel The movement velocity. [(x, y, z)]
  294. /// @return True if the request was successfully submitted.
  295. public bool RequestMoveVelocity(DtCrowdAgent agent, RcVec3f vel)
  296. {
  297. // Initialize request.
  298. agent.targetRef = 0;
  299. agent.targetPos = vel;
  300. agent.targetPathQueryResult = null;
  301. agent.targetReplan = false;
  302. agent.targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY;
  303. return true;
  304. }
  305. /// Resets any request for the specified agent.
  306. /// @param[in] idx The agent index. [Limits: 0 <= value < #GetAgentCount()]
  307. /// @return True if the request was successfully reseted.
  308. public bool ResetMoveTarget(DtCrowdAgent agent)
  309. {
  310. // Initialize request.
  311. agent.targetRef = 0;
  312. agent.targetPos = RcVec3f.Zero;
  313. agent.dvel = RcVec3f.Zero;
  314. agent.targetPathQueryResult = null;
  315. agent.targetReplan = false;
  316. agent.targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE;
  317. return true;
  318. }
  319. /**
  320. * Gets the active agents int the agent pool.
  321. *
  322. * @return List of active agents
  323. */
  324. public IList<DtCrowdAgent> GetActiveAgents()
  325. {
  326. return _agents;
  327. }
  328. public RcVec3f GetQueryExtents()
  329. {
  330. return _ext;
  331. }
  332. public IDtQueryFilter GetFilter(int i)
  333. {
  334. return i >= 0 && i < DT_CROWD_MAX_QUERY_FILTER_TYPE ? _filters[i] : null;
  335. }
  336. public DtProximityGrid GetGrid()
  337. {
  338. return _grid;
  339. }
  340. public DtPathQueue GetPathQueue()
  341. {
  342. return _pathQ;
  343. }
  344. public DtCrowdTelemetry Telemetry()
  345. {
  346. return _telemetry;
  347. }
  348. public DtCrowdConfig Config()
  349. {
  350. return _config;
  351. }
  352. public DtCrowdTelemetry Update(float dt, DtCrowdAgentDebugInfo debug)
  353. {
  354. _velocitySampleCount = 0;
  355. _telemetry.Start();
  356. IList<DtCrowdAgent> agents = GetActiveAgents();
  357. // Check that all agents still have valid paths.
  358. CheckPathValidity(agents, dt);
  359. // Update async move request and path finder.
  360. UpdateMoveRequest(agents, dt);
  361. // Optimize path topology.
  362. UpdateTopologyOptimization(agents, dt);
  363. // Register agents to proximity grid.
  364. BuildProximityGrid(agents);
  365. // Get nearby navmesh segments and agents to collide with.
  366. BuildNeighbours(agents);
  367. // Find next corner to steer to.
  368. FindCorners(agents, debug);
  369. // Trigger off-mesh connections (depends on corners).
  370. TriggerOffMeshConnections(agents);
  371. // Calculate steering.
  372. CalculateSteering(agents);
  373. // Velocity planning.
  374. PlanVelocity(debug, agents);
  375. // Integrate.
  376. Integrate(dt, agents);
  377. // Handle collisions.
  378. HandleCollisions(agents);
  379. MoveAgents(agents);
  380. // Update agents using off-mesh connection.
  381. UpdateOffMeshConnections(agents, dt);
  382. return _telemetry;
  383. }
  384. private void CheckPathValidity(IList<DtCrowdAgent> agents, float dt)
  385. {
  386. using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.CheckPathValidity);
  387. foreach (DtCrowdAgent ag in agents)
  388. {
  389. if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
  390. {
  391. continue;
  392. }
  393. ag.targetReplanTime += dt;
  394. bool replan = false;
  395. // First check that the current location is valid.
  396. RcVec3f agentPos = new RcVec3f();
  397. long agentRef = ag.corridor.GetFirstPoly();
  398. agentPos = ag.npos;
  399. if (!_navQuery.IsValidPolyRef(agentRef, _filters[ag.option.queryFilterType]))
  400. {
  401. // Current location is not valid, try to reposition.
  402. // TODO: this can snap agents, how to handle that?
  403. _navQuery.FindNearestPoly(ag.npos, _ext, _filters[ag.option.queryFilterType], out agentRef, out var nearestPt, out var _);
  404. agentPos = nearestPt;
  405. if (agentRef == 0)
  406. {
  407. // Could not find location in navmesh, set state to invalid.
  408. ag.corridor.Reset(0, agentPos);
  409. ag.partial = false;
  410. ag.boundary.Reset();
  411. ag.state = DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID;
  412. continue;
  413. }
  414. // Make sure the first polygon is valid, but leave other valid
  415. // polygons in the path so that replanner can adjust the path
  416. // better.
  417. ag.corridor.FixPathStart(agentRef, agentPos);
  418. // ag.corridor.TrimInvalidPath(agentRef, agentPos, m_navquery,
  419. // &m_filter);
  420. ag.boundary.Reset();
  421. ag.npos = agentPos;
  422. replan = true;
  423. }
  424. // If the agent does not have move target or is controlled by
  425. // velocity, no need to recover the target nor replan.
  426. if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE
  427. || ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY)
  428. {
  429. continue;
  430. }
  431. // Try to recover move request position.
  432. if (ag.targetState != DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE
  433. && ag.targetState != DtMoveRequestState.DT_CROWDAGENT_TARGET_FAILED)
  434. {
  435. if (!_navQuery.IsValidPolyRef(ag.targetRef, _filters[ag.option.queryFilterType]))
  436. {
  437. // Current target is not valid, try to reposition.
  438. _navQuery.FindNearestPoly(ag.targetPos, _ext, _filters[ag.option.queryFilterType], out ag.targetRef, out var nearestPt, out var _);
  439. ag.targetPos = nearestPt;
  440. replan = true;
  441. }
  442. if (ag.targetRef == 0)
  443. {
  444. // Failed to reposition target, fail moverequest.
  445. ag.corridor.Reset(agentRef, agentPos);
  446. ag.partial = false;
  447. ag.targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE;
  448. }
  449. }
  450. // If nearby corridor is not valid, replan.
  451. if (!ag.corridor.IsValid(_config.checkLookAhead, _navQuery, _filters[ag.option.queryFilterType]))
  452. {
  453. // Fix current path.
  454. // ag.corridor.TrimInvalidPath(agentRef, agentPos, m_navquery,
  455. // &m_filter);
  456. // ag.boundary.Reset();
  457. replan = true;
  458. }
  459. // If the end of the path is near and it is not the requested
  460. // location, replan.
  461. if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VALID)
  462. {
  463. if (ag.targetReplanTime > _config.targetReplanDelay && ag.corridor.GetPathCount() < _config.checkLookAhead
  464. && ag.corridor.GetLastPoly() != ag.targetRef)
  465. {
  466. replan = true;
  467. }
  468. }
  469. // Try to replan path to goal.
  470. if (replan)
  471. {
  472. if (ag.targetState != DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE)
  473. {
  474. RequestMoveTargetReplan(ag, ag.targetRef, ag.targetPos);
  475. }
  476. }
  477. }
  478. }
  479. private void UpdateMoveRequest(IList<DtCrowdAgent> agents, float dt)
  480. {
  481. using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.UpdateMoveRequest);
  482. RcSortedQueue<DtCrowdAgent> queue = new RcSortedQueue<DtCrowdAgent>((a1, a2) => a2.targetReplanTime.CompareTo(a1.targetReplanTime));
  483. // Fire off new requests.
  484. List<long> reqPath = new List<long>();
  485. foreach (DtCrowdAgent ag in agents)
  486. {
  487. if (ag.state == DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID)
  488. {
  489. continue;
  490. }
  491. if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE
  492. || ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY)
  493. {
  494. continue;
  495. }
  496. if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_REQUESTING)
  497. {
  498. List<long> path = ag.corridor.GetPath();
  499. if (0 == path.Count)
  500. {
  501. throw new ArgumentException("Empty path");
  502. }
  503. // Quick search towards the goal.
  504. _navQuery.InitSlicedFindPath(path[0], ag.targetRef, ag.npos, ag.targetPos,
  505. _filters[ag.option.queryFilterType], 0);
  506. _navQuery.UpdateSlicedFindPath(_config.maxTargetFindPathIterations, out var _);
  507. DtStatus status;
  508. if (ag.targetReplan) // && npath > 10)
  509. {
  510. // Try to use existing steady path during replan if possible.
  511. status = _navQuery.FinalizeSlicedFindPathPartial(path, ref reqPath);
  512. }
  513. else
  514. {
  515. // Try to move towards target when goal changes.
  516. status = _navQuery.FinalizeSlicedFindPath(ref reqPath);
  517. }
  518. RcVec3f reqPos = new RcVec3f();
  519. if (status.Succeeded() && reqPath.Count > 0)
  520. {
  521. // In progress or succeed.
  522. if (reqPath[reqPath.Count - 1] != ag.targetRef)
  523. {
  524. // Partial path, constrain target position inside the
  525. // last polygon.
  526. var cr = _navQuery.ClosestPointOnPoly(reqPath[reqPath.Count - 1], ag.targetPos, out reqPos, out var _);
  527. if (cr.Failed())
  528. {
  529. reqPath = new List<long>();
  530. }
  531. }
  532. else
  533. {
  534. reqPos = ag.targetPos;
  535. }
  536. }
  537. else
  538. {
  539. // Could not find path, start the request from current
  540. // location.
  541. reqPos = ag.npos;
  542. reqPath = new List<long>();
  543. reqPath.Add(path[0]);
  544. }
  545. ag.corridor.SetCorridor(reqPos, reqPath);
  546. ag.boundary.Reset();
  547. ag.partial = false;
  548. if (reqPath[reqPath.Count - 1] == ag.targetRef)
  549. {
  550. ag.targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_VALID;
  551. ag.targetReplanTime = 0;
  552. }
  553. else
  554. {
  555. // The path is longer or potentially unreachable, full plan.
  556. ag.targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE;
  557. }
  558. ag.targetReplanWaitTime = 0;
  559. }
  560. if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE)
  561. {
  562. queue.Enqueue(ag);
  563. }
  564. }
  565. while (!queue.IsEmpty())
  566. {
  567. DtCrowdAgent ag = queue.Dequeue();
  568. ag.targetPathQueryResult = _pathQ.Request(ag.corridor.GetLastPoly(), ag.targetRef, ag.corridor.GetTarget(), ag.targetPos, _filters[ag.option.queryFilterType]);
  569. if (ag.targetPathQueryResult != null)
  570. {
  571. ag.targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_WAITING_FOR_PATH;
  572. }
  573. else
  574. {
  575. _telemetry.RecordMaxTimeToEnqueueRequest(ag.targetReplanWaitTime);
  576. ag.targetReplanWaitTime += dt;
  577. }
  578. }
  579. // Update requests.
  580. using (var timer2 = _telemetry.ScopedTimer(DtCrowdTimerLabel.PathQueueUpdate))
  581. {
  582. _pathQ.Update(_navMesh);
  583. }
  584. // Process path results.
  585. foreach (DtCrowdAgent ag in agents)
  586. {
  587. if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE
  588. || ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY)
  589. {
  590. continue;
  591. }
  592. if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_WAITING_FOR_PATH)
  593. {
  594. // _telemetry.RecordPathWaitTime(ag.targetReplanTime);
  595. // Poll path queue.
  596. DtStatus status = ag.targetPathQueryResult.status;
  597. if (status.Failed())
  598. {
  599. // Path find failed, retry if the target location is still
  600. // valid.
  601. ag.targetPathQueryResult = null;
  602. if (ag.targetRef != 0)
  603. {
  604. ag.targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_REQUESTING;
  605. }
  606. else
  607. {
  608. ag.targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_FAILED;
  609. }
  610. ag.targetReplanTime = 0;
  611. }
  612. else if (status.Succeeded())
  613. {
  614. List<long> path = ag.corridor.GetPath();
  615. if (0 == path.Count)
  616. {
  617. throw new ArgumentException("Empty path");
  618. }
  619. // Apply results.
  620. var targetPos = ag.targetPos;
  621. bool valid = true;
  622. List<long> res = ag.targetPathQueryResult.path;
  623. if (status.Failed() || 0 == res.Count)
  624. {
  625. valid = false;
  626. }
  627. if (status.IsPartial())
  628. {
  629. ag.partial = true;
  630. }
  631. else
  632. {
  633. ag.partial = false;
  634. }
  635. // Merge result and existing path.
  636. // The agent might have moved whilst the request is
  637. // being processed, so the path may have changed.
  638. // We assume that the end of the path is at the same
  639. // location
  640. // where the request was issued.
  641. // The last ref in the old path should be the same as
  642. // the location where the request was issued..
  643. if (valid && path[path.Count - 1] != res[0])
  644. {
  645. valid = false;
  646. }
  647. if (valid)
  648. {
  649. // Put the old path infront of the old path.
  650. if (path.Count > 1)
  651. {
  652. path.RemoveAt(path.Count - 1);
  653. path.AddRange(res);
  654. res = path;
  655. // Remove trackbacks
  656. for (int j = 1; j < res.Count - 1; ++j)
  657. {
  658. if (j - 1 >= 0 && j + 1 < res.Count)
  659. {
  660. if (res[j - 1] == res[j + 1])
  661. {
  662. res.RemoveAt(j + 1);
  663. res.RemoveAt(j);
  664. j -= 2;
  665. }
  666. }
  667. }
  668. }
  669. // Check for partial path.
  670. if (res[res.Count - 1] != ag.targetRef)
  671. {
  672. // Partial path, constrain target position inside
  673. // the last polygon.
  674. var cr = _navQuery.ClosestPointOnPoly(res[res.Count - 1], targetPos, out var nearest, out var _);
  675. if (cr.Succeeded())
  676. {
  677. targetPos = nearest;
  678. }
  679. else
  680. {
  681. valid = false;
  682. }
  683. }
  684. }
  685. if (valid)
  686. {
  687. // Set current corridor.
  688. ag.corridor.SetCorridor(targetPos, res);
  689. // Force to update boundary.
  690. ag.boundary.Reset();
  691. ag.targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_VALID;
  692. }
  693. else
  694. {
  695. // Something went wrong.
  696. ag.targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_FAILED;
  697. }
  698. ag.targetReplanTime = 0;
  699. }
  700. _telemetry.RecordMaxTimeToFindPath(ag.targetReplanWaitTime);
  701. ag.targetReplanWaitTime += dt;
  702. }
  703. }
  704. }
  705. private void UpdateTopologyOptimization(IList<DtCrowdAgent> agents, float dt)
  706. {
  707. using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.UpdateTopologyOptimization);
  708. RcSortedQueue<DtCrowdAgent> queue = new RcSortedQueue<DtCrowdAgent>((a1, a2) => a2.topologyOptTime.CompareTo(a1.topologyOptTime));
  709. foreach (DtCrowdAgent ag in agents)
  710. {
  711. if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
  712. {
  713. continue;
  714. }
  715. if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE
  716. || ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY)
  717. {
  718. continue;
  719. }
  720. if ((ag.option.updateFlags & DtCrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO) == 0)
  721. {
  722. continue;
  723. }
  724. ag.topologyOptTime += dt;
  725. if (ag.topologyOptTime >= _config.topologyOptimizationTimeThreshold)
  726. {
  727. queue.Enqueue(ag);
  728. }
  729. }
  730. while (!queue.IsEmpty())
  731. {
  732. DtCrowdAgent ag = queue.Dequeue();
  733. ag.corridor.OptimizePathTopology(_navQuery, _filters[ag.option.queryFilterType], _config.maxTopologyOptimizationIterations);
  734. ag.topologyOptTime = 0;
  735. }
  736. }
  737. private void BuildProximityGrid(IList<DtCrowdAgent> agents)
  738. {
  739. using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.BuildProximityGrid);
  740. _grid = new DtProximityGrid(_config.maxAgentRadius * 3);
  741. foreach (DtCrowdAgent ag in agents)
  742. {
  743. RcVec3f p = ag.npos;
  744. float r = ag.option.radius;
  745. _grid.AddItem(ag, p.x - r, p.z - r, p.x + r, p.z + r);
  746. }
  747. }
  748. private void BuildNeighbours(IList<DtCrowdAgent> agents)
  749. {
  750. using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.BuildNeighbours);
  751. foreach (DtCrowdAgent ag in agents)
  752. {
  753. if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
  754. {
  755. continue;
  756. }
  757. // Update the collision boundary after certain distance has been passed or
  758. // if it has become invalid.
  759. float updateThr = ag.option.collisionQueryRange * 0.25f;
  760. if (RcVec3f.Dist2DSqr(ag.npos, ag.boundary.GetCenter()) > Sqr(updateThr)
  761. || !ag.boundary.IsValid(_navQuery, _filters[ag.option.queryFilterType]))
  762. {
  763. ag.boundary.Update(ag.corridor.GetFirstPoly(), ag.npos, ag.option.collisionQueryRange, _navQuery,
  764. _filters[ag.option.queryFilterType]);
  765. }
  766. // Query neighbour agents
  767. GetNeighbours(ag.npos, ag.option.height, ag.option.collisionQueryRange, ag, ref ag.neis, _grid);
  768. }
  769. }
  770. private int GetNeighbours(RcVec3f pos, float height, float range, DtCrowdAgent skip, ref List<DtCrowdNeighbour> result, DtProximityGrid grid)
  771. {
  772. result.Clear();
  773. var proxAgents = new HashSet<DtCrowdAgent>();
  774. int nids = grid.QueryItems(pos.x - range, pos.z - range, pos.x + range, pos.z + range, ref proxAgents);
  775. foreach (DtCrowdAgent ag in proxAgents)
  776. {
  777. if (ag == skip)
  778. {
  779. continue;
  780. }
  781. // Check for overlap.
  782. RcVec3f diff = pos.Subtract(ag.npos);
  783. if (Math.Abs(diff.y) >= (height + ag.option.height) / 2.0f)
  784. {
  785. continue;
  786. }
  787. diff.y = 0;
  788. float distSqr = RcVec3f.LenSqr(diff);
  789. if (distSqr > Sqr(range))
  790. {
  791. continue;
  792. }
  793. result.Add(new DtCrowdNeighbour(ag, distSqr));
  794. }
  795. result.Sort((o1, o2) => o1.dist.CompareTo(o2.dist));
  796. return result.Count;
  797. }
  798. private void FindCorners(IList<DtCrowdAgent> agents, DtCrowdAgentDebugInfo debug)
  799. {
  800. using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.FindCorners);
  801. DtCrowdAgent debugAgent = debug != null ? debug.agent : null;
  802. foreach (DtCrowdAgent ag in agents)
  803. {
  804. if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
  805. {
  806. continue;
  807. }
  808. if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE
  809. || ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY)
  810. {
  811. continue;
  812. }
  813. // Find corners for steering
  814. ag.corridor.FindCorners(ref ag.corners, DT_CROWDAGENT_MAX_CORNERS, _navQuery, _filters[ag.option.queryFilterType]);
  815. // Check to see if the corner after the next corner is directly visible,
  816. // and short cut to there.
  817. if ((ag.option.updateFlags & DtCrowdAgentParams.DT_CROWD_OPTIMIZE_VIS) != 0 && ag.corners.Count > 0)
  818. {
  819. RcVec3f target = ag.corners[Math.Min(1, ag.corners.Count - 1)].pos;
  820. ag.corridor.OptimizePathVisibility(target, ag.option.pathOptimizationRange, _navQuery,
  821. _filters[ag.option.queryFilterType]);
  822. // Copy data for debug purposes.
  823. if (debugAgent == ag)
  824. {
  825. debug.optStart = ag.corridor.GetPos();
  826. debug.optEnd = target;
  827. }
  828. }
  829. else
  830. {
  831. // Copy data for debug purposes.
  832. if (debugAgent == ag)
  833. {
  834. debug.optStart = RcVec3f.Zero;
  835. debug.optEnd = RcVec3f.Zero;
  836. }
  837. }
  838. }
  839. }
  840. private void TriggerOffMeshConnections(IList<DtCrowdAgent> agents)
  841. {
  842. using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.TriggerOffMeshConnections);
  843. foreach (DtCrowdAgent ag in agents)
  844. {
  845. if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
  846. {
  847. continue;
  848. }
  849. if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE
  850. || ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY)
  851. {
  852. continue;
  853. }
  854. // Check
  855. float triggerRadius = ag.option.radius * 2.25f;
  856. if (ag.OverOffmeshConnection(triggerRadius))
  857. {
  858. // Prepare to off-mesh connection.
  859. DtCrowdAgentAnimation anim = ag.animation;
  860. // Adjust the path over the off-mesh connection.
  861. long[] refs = new long[2];
  862. if (ag.corridor.MoveOverOffmeshConnection(ag.corners[ag.corners.Count - 1].refs, refs, ref anim.startPos,
  863. ref anim.endPos, _navQuery))
  864. {
  865. anim.initPos = ag.npos;
  866. anim.polyRef = refs[1];
  867. anim.active = true;
  868. anim.t = 0.0f;
  869. anim.tmax = (RcVec3f.Dist2D(anim.startPos, anim.endPos) / ag.option.maxSpeed) * 0.5f;
  870. ag.state = DtCrowdAgentState.DT_CROWDAGENT_STATE_OFFMESH;
  871. ag.corners.Clear();
  872. ag.neis.Clear();
  873. continue;
  874. }
  875. else
  876. {
  877. // Path validity check will ensure that bad/blocked connections will be replanned.
  878. }
  879. }
  880. }
  881. }
  882. private void CalculateSteering(IList<DtCrowdAgent> agents)
  883. {
  884. using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.CalculateSteering);
  885. foreach (DtCrowdAgent ag in agents)
  886. {
  887. if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
  888. {
  889. continue;
  890. }
  891. if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE)
  892. {
  893. continue;
  894. }
  895. RcVec3f dvel = new RcVec3f();
  896. if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY)
  897. {
  898. dvel = ag.targetPos;
  899. ag.desiredSpeed = ag.targetPos.Length();
  900. }
  901. else
  902. {
  903. // Calculate steering direction.
  904. if ((ag.option.updateFlags & DtCrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS) != 0)
  905. {
  906. dvel = ag.CalcSmoothSteerDirection();
  907. }
  908. else
  909. {
  910. dvel = ag.CalcStraightSteerDirection();
  911. }
  912. // Calculate speed scale, which tells the agent to slowdown at the end of the path.
  913. float slowDownRadius = ag.option.radius * 2; // TODO: make less hacky.
  914. float speedScale = ag.GetDistanceToGoal(slowDownRadius) / slowDownRadius;
  915. ag.desiredSpeed = ag.option.maxSpeed;
  916. dvel = dvel.Scale(ag.desiredSpeed * speedScale);
  917. }
  918. // Separation
  919. if ((ag.option.updateFlags & DtCrowdAgentParams.DT_CROWD_SEPARATION) != 0)
  920. {
  921. float separationDist = ag.option.collisionQueryRange;
  922. float invSeparationDist = 1.0f / separationDist;
  923. float separationWeight = ag.option.separationWeight;
  924. float w = 0;
  925. RcVec3f disp = new RcVec3f();
  926. for (int j = 0; j < ag.neis.Count; ++j)
  927. {
  928. DtCrowdAgent nei = ag.neis[j].agent;
  929. RcVec3f diff = ag.npos.Subtract(nei.npos);
  930. diff.y = 0;
  931. float distSqr = RcVec3f.LenSqr(diff);
  932. if (distSqr < 0.00001f)
  933. {
  934. continue;
  935. }
  936. if (distSqr > Sqr(separationDist))
  937. {
  938. continue;
  939. }
  940. float dist = (float)Math.Sqrt(distSqr);
  941. float weight = separationWeight * (1.0f - Sqr(dist * invSeparationDist));
  942. disp = RcVec3f.Mad(disp, diff, weight / dist);
  943. w += 1.0f;
  944. }
  945. if (w > 0.0001f)
  946. {
  947. // Adjust desired velocity.
  948. dvel = RcVec3f.Mad(dvel, disp, 1.0f / w);
  949. // Clamp desired velocity to desired speed.
  950. float speedSqr = RcVec3f.LenSqr(dvel);
  951. float desiredSqr = Sqr(ag.desiredSpeed);
  952. if (speedSqr > desiredSqr)
  953. {
  954. dvel = dvel.Scale(desiredSqr / speedSqr);
  955. }
  956. }
  957. }
  958. // Set the desired velocity.
  959. ag.dvel = dvel;
  960. }
  961. }
  962. private void PlanVelocity(DtCrowdAgentDebugInfo debug, IList<DtCrowdAgent> agents)
  963. {
  964. using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.PlanVelocity);
  965. DtCrowdAgent debugAgent = debug != null ? debug.agent : null;
  966. foreach (DtCrowdAgent ag in agents)
  967. {
  968. if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
  969. {
  970. continue;
  971. }
  972. if ((ag.option.updateFlags & DtCrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE) != 0)
  973. {
  974. _obstacleQuery.Reset();
  975. // Add neighbours as obstacles.
  976. for (int j = 0; j < ag.neis.Count; ++j)
  977. {
  978. DtCrowdAgent nei = ag.neis[j].agent;
  979. _obstacleQuery.AddCircle(nei.npos, nei.option.radius, nei.vel, nei.dvel);
  980. }
  981. // Append neighbour segments as obstacles.
  982. for (int j = 0; j < ag.boundary.GetSegmentCount(); ++j)
  983. {
  984. RcVec3f[] s = ag.boundary.GetSegment(j);
  985. RcVec3f s3 = s[1];
  986. //Array.Copy(s, 3, s3, 0, 3);
  987. if (DtUtils.TriArea2D(ag.npos, s[0], s3) < 0.0f)
  988. {
  989. continue;
  990. }
  991. _obstacleQuery.AddSegment(s[0], s3);
  992. }
  993. DtObstacleAvoidanceDebugData vod = null;
  994. if (debugAgent == ag)
  995. {
  996. vod = debug.vod;
  997. }
  998. // Sample new safe velocity.
  999. bool adaptive = true;
  1000. int ns = 0;
  1001. DtObstacleAvoidanceParams option = _obstacleQueryParams[ag.option.obstacleAvoidanceType];
  1002. if (adaptive)
  1003. {
  1004. ns = _obstacleQuery.SampleVelocityAdaptive(ag.npos, ag.option.radius, ag.desiredSpeed,
  1005. ag.vel, ag.dvel, out ag.nvel, option, vod);
  1006. }
  1007. else
  1008. {
  1009. ns = _obstacleQuery.SampleVelocityGrid(ag.npos, ag.option.radius,
  1010. ag.desiredSpeed, ag.vel, ag.dvel, out ag.nvel, option, vod);
  1011. }
  1012. _velocitySampleCount += ns;
  1013. }
  1014. else
  1015. {
  1016. // If not using velocity planning, new velocity is directly the desired velocity.
  1017. ag.nvel = ag.dvel;
  1018. }
  1019. }
  1020. }
  1021. private void Integrate(float dt, IList<DtCrowdAgent> agents)
  1022. {
  1023. using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.Integrate);
  1024. foreach (DtCrowdAgent ag in agents)
  1025. {
  1026. if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
  1027. {
  1028. continue;
  1029. }
  1030. ag.Integrate(dt);
  1031. }
  1032. }
  1033. private void HandleCollisions(IList<DtCrowdAgent> agents)
  1034. {
  1035. using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.HandleCollisions);
  1036. for (int iter = 0; iter < 4; ++iter)
  1037. {
  1038. foreach (DtCrowdAgent ag in agents)
  1039. {
  1040. long idx0 = ag.idx;
  1041. if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
  1042. {
  1043. continue;
  1044. }
  1045. ag.disp = RcVec3f.Zero;
  1046. float w = 0;
  1047. for (int j = 0; j < ag.neis.Count; ++j)
  1048. {
  1049. DtCrowdAgent nei = ag.neis[j].agent;
  1050. long idx1 = nei.idx;
  1051. RcVec3f diff = ag.npos.Subtract(nei.npos);
  1052. diff.y = 0;
  1053. float dist = RcVec3f.LenSqr(diff);
  1054. if (dist > Sqr(ag.option.radius + nei.option.radius))
  1055. {
  1056. continue;
  1057. }
  1058. dist = (float)Math.Sqrt(dist);
  1059. float pen = (ag.option.radius + nei.option.radius) - dist;
  1060. if (dist < 0.0001f)
  1061. {
  1062. // Agents on top of each other, try to choose diverging separation directions.
  1063. if (idx0 > idx1)
  1064. {
  1065. diff.Set(-ag.dvel.z, 0, ag.dvel.x);
  1066. }
  1067. else
  1068. {
  1069. diff.Set(ag.dvel.z, 0, -ag.dvel.x);
  1070. }
  1071. pen = 0.01f;
  1072. }
  1073. else
  1074. {
  1075. pen = (1.0f / dist) * (pen * 0.5f) * _config.collisionResolveFactor;
  1076. }
  1077. ag.disp = RcVec3f.Mad(ag.disp, diff, pen);
  1078. w += 1.0f;
  1079. }
  1080. if (w > 0.0001f)
  1081. {
  1082. float iw = 1.0f / w;
  1083. ag.disp = ag.disp.Scale(iw);
  1084. }
  1085. }
  1086. foreach (DtCrowdAgent ag in agents)
  1087. {
  1088. if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
  1089. {
  1090. continue;
  1091. }
  1092. ag.npos = ag.npos.Add(ag.disp);
  1093. }
  1094. }
  1095. }
  1096. private void MoveAgents(IList<DtCrowdAgent> agents)
  1097. {
  1098. using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.MoveAgents);
  1099. foreach (DtCrowdAgent ag in agents)
  1100. {
  1101. if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
  1102. {
  1103. continue;
  1104. }
  1105. // Move along navmesh.
  1106. ag.corridor.MovePosition(ag.npos, _navQuery, _filters[ag.option.queryFilterType]);
  1107. // Get valid constrained position back.
  1108. ag.npos = ag.corridor.GetPos();
  1109. // If not using path, truncate the corridor to just one poly.
  1110. if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE
  1111. || ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY)
  1112. {
  1113. ag.corridor.Reset(ag.corridor.GetFirstPoly(), ag.npos);
  1114. ag.partial = false;
  1115. }
  1116. }
  1117. }
  1118. private void UpdateOffMeshConnections(IList<DtCrowdAgent> agents, float dt)
  1119. {
  1120. using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.UpdateOffMeshConnections);
  1121. foreach (DtCrowdAgent ag in agents)
  1122. {
  1123. DtCrowdAgentAnimation anim = ag.animation;
  1124. if (!anim.active)
  1125. {
  1126. continue;
  1127. }
  1128. anim.t += dt;
  1129. if (anim.t > anim.tmax)
  1130. {
  1131. // Reset animation
  1132. anim.active = false;
  1133. // Prepare agent for walking.
  1134. ag.state = DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING;
  1135. continue;
  1136. }
  1137. // Update position
  1138. float ta = anim.tmax * 0.15f;
  1139. float tb = anim.tmax;
  1140. if (anim.t < ta)
  1141. {
  1142. float u = Tween(anim.t, 0.0f, ta);
  1143. ag.npos = RcVec3f.Lerp(anim.initPos, anim.startPos, u);
  1144. }
  1145. else
  1146. {
  1147. float u = Tween(anim.t, ta, tb);
  1148. ag.npos = RcVec3f.Lerp(anim.startPos, anim.endPos, u);
  1149. }
  1150. // Update velocity.
  1151. ag.vel = RcVec3f.Zero;
  1152. ag.dvel = RcVec3f.Zero;
  1153. }
  1154. }
  1155. private float Tween(float t, float t0, float t1)
  1156. {
  1157. return Clamp((t - t0) / (t1 - t0), 0.0f, 1.0f);
  1158. }
  1159. }
  1160. }