RVOAgent.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. namespace Pathfinding.RVO.Sampled {
  4. using Pathfinding;
  5. using Pathfinding.RVO;
  6. using Pathfinding.Util;
  7. /// <summary>
  8. /// Internal agent for the RVO system.
  9. /// Usually you will interface with the IAgent interface instead.
  10. ///
  11. /// See: IAgent
  12. /// </summary>
  13. public class Agent : IAgent {
  14. //Current values for double buffer calculation
  15. internal float radius, height, desiredSpeed, maxSpeed, agentTimeHorizon, obstacleTimeHorizon;
  16. internal bool locked = false;
  17. RVOLayer layer, collidesWith;
  18. int maxNeighbours;
  19. internal Vector2 position;
  20. float elevationCoordinate;
  21. Vector2 currentVelocity;
  22. /// <summary>Desired target point - position</summary>
  23. Vector2 desiredTargetPointInVelocitySpace;
  24. Vector2 desiredVelocity;
  25. Vector2 nextTargetPoint;
  26. float nextDesiredSpeed;
  27. float nextMaxSpeed;
  28. Vector2 collisionNormal;
  29. bool manuallyControlled;
  30. bool debugDraw;
  31. #region IAgent Properties
  32. /// <summary>\copydoc Pathfinding::RVO::IAgent::Position</summary>
  33. public Vector2 Position { get; set; }
  34. /// <summary>\copydoc Pathfinding::RVO::IAgent::ElevationCoordinate</summary>
  35. public float ElevationCoordinate { get; set; }
  36. /// <summary>\copydoc Pathfinding::RVO::IAgent::CalculatedTargetPoint</summary>
  37. public Vector2 CalculatedTargetPoint { get; private set; }
  38. /// <summary>\copydoc Pathfinding::RVO::IAgent::CalculatedSpeed</summary>
  39. public float CalculatedSpeed { get; private set; }
  40. /// <summary>\copydoc Pathfinding::RVO::IAgent::Locked</summary>
  41. public bool Locked { get; set; }
  42. /// <summary>\copydoc Pathfinding::RVO::IAgent::Radius</summary>
  43. public float Radius { get; set; }
  44. /// <summary>\copydoc Pathfinding::RVO::IAgent::Height</summary>
  45. public float Height { get; set; }
  46. /// <summary>\copydoc Pathfinding::RVO::IAgent::AgentTimeHorizon</summary>
  47. public float AgentTimeHorizon { get; set; }
  48. /// <summary>\copydoc Pathfinding::RVO::IAgent::ObstacleTimeHorizon</summary>
  49. public float ObstacleTimeHorizon { get; set; }
  50. /// <summary>\copydoc Pathfinding::RVO::IAgent::MaxNeighbours</summary>
  51. public int MaxNeighbours { get; set; }
  52. /// <summary>\copydoc Pathfinding::RVO::IAgent::NeighbourCount</summary>
  53. public int NeighbourCount { get; private set; }
  54. /// <summary>\copydoc Pathfinding::RVO::IAgent::Layer</summary>
  55. public RVOLayer Layer { get; set; }
  56. /// <summary>\copydoc Pathfinding::RVO::IAgent::CollidesWith</summary>
  57. public RVOLayer CollidesWith { get; set; }
  58. /// <summary>\copydoc Pathfinding::RVO::IAgent::DebugDraw</summary>
  59. public bool DebugDraw {
  60. get {
  61. return debugDraw;
  62. }
  63. set {
  64. debugDraw = value && simulator != null && !simulator.Multithreading;
  65. }
  66. }
  67. /// <summary>\copydoc Pathfinding::RVO::IAgent::Priority</summary>
  68. public float Priority { get; set; }
  69. /// <summary>\copydoc Pathfinding::RVO::IAgent::PreCalculationCallback</summary>
  70. public System.Action PreCalculationCallback { private get; set; }
  71. #endregion
  72. #region IAgent Methods
  73. /// <summary>\copydoc Pathfinding::RVO::IAgent::SetTarget</summary>
  74. public void SetTarget (Vector2 targetPoint, float desiredSpeed, float maxSpeed) {
  75. maxSpeed = System.Math.Max(maxSpeed, 0);
  76. desiredSpeed = System.Math.Min(System.Math.Max(desiredSpeed, 0), maxSpeed);
  77. nextTargetPoint = targetPoint;
  78. nextDesiredSpeed = desiredSpeed;
  79. nextMaxSpeed = maxSpeed;
  80. }
  81. /// <summary>\copydoc Pathfinding::RVO::IAgent::SetCollisionNormal</summary>
  82. public void SetCollisionNormal (Vector2 normal) {
  83. collisionNormal = normal;
  84. }
  85. /// <summary>\copydoc Pathfinding::RVO::IAgent::ForceSetVelocity</summary>
  86. public void ForceSetVelocity (Vector2 velocity) {
  87. // A bit hacky, but it is approximately correct
  88. // assuming the agent does not move significantly
  89. nextTargetPoint = CalculatedTargetPoint = position + velocity * 1000;
  90. nextDesiredSpeed = CalculatedSpeed = velocity.magnitude;
  91. manuallyControlled = true;
  92. }
  93. #endregion
  94. /// <summary>Used internally for a linked list</summary>
  95. internal Agent next;
  96. float calculatedSpeed;
  97. Vector2 calculatedTargetPoint;
  98. /// <summary>
  99. /// Simulator which handles this agent.
  100. /// Used by this script as a reference and to prevent
  101. /// adding this agent to multiple simulations.
  102. /// </summary>
  103. internal Simulator simulator;
  104. List<Agent> neighbours = new List<Agent>();
  105. List<float> neighbourDists = new List<float>();
  106. List<ObstacleVertex> obstaclesBuffered = new List<ObstacleVertex>();
  107. List<ObstacleVertex> obstacles = new List<ObstacleVertex>();
  108. const float DesiredVelocityWeight = 0.1f;
  109. /// <summary>Extra weight that walls will have</summary>
  110. const float WallWeight = 5;
  111. public List<ObstacleVertex> NeighbourObstacles {
  112. get {
  113. return null;
  114. }
  115. }
  116. public Agent (Vector2 pos, float elevationCoordinate) {
  117. AgentTimeHorizon = 2;
  118. ObstacleTimeHorizon = 2;
  119. Height = 5;
  120. Radius = 5;
  121. MaxNeighbours = 10;
  122. Locked = false;
  123. Position = pos;
  124. ElevationCoordinate = elevationCoordinate;
  125. Layer = RVOLayer.DefaultAgent;
  126. CollidesWith = (RVOLayer)(-1);
  127. Priority = 0.5f;
  128. CalculatedTargetPoint = pos;
  129. CalculatedSpeed = 0;
  130. SetTarget(pos, 0, 0);
  131. }
  132. /// <summary>
  133. /// Reads public properties and stores them in internal fields.
  134. /// This is required because multithreading is used and if another script
  135. /// updated the fields at the same time as this class used them in another thread
  136. /// weird things could happen.
  137. ///
  138. /// Will also set CalculatedTargetPoint and CalculatedSpeed to the result
  139. /// which was last calculated.
  140. /// </summary>
  141. public void BufferSwitch () {
  142. // <== Read public properties
  143. radius = Radius;
  144. height = Height;
  145. maxSpeed = nextMaxSpeed;
  146. desiredSpeed = nextDesiredSpeed;
  147. agentTimeHorizon = AgentTimeHorizon;
  148. obstacleTimeHorizon = ObstacleTimeHorizon;
  149. maxNeighbours = MaxNeighbours;
  150. // Manually controlled overrides the agent being locked
  151. // (if one for some reason uses them at the same time)
  152. locked = Locked && !manuallyControlled;
  153. position = Position;
  154. elevationCoordinate = ElevationCoordinate;
  155. collidesWith = CollidesWith;
  156. layer = Layer;
  157. if (locked) {
  158. // Locked agents do not move at all
  159. desiredTargetPointInVelocitySpace = position;
  160. desiredVelocity = currentVelocity = Vector2.zero;
  161. } else {
  162. desiredTargetPointInVelocitySpace = nextTargetPoint - position;
  163. // Estimate our current velocity
  164. // This is necessary because other agents need to know
  165. // how this agent is moving to be able to avoid it
  166. currentVelocity = (CalculatedTargetPoint - position).normalized * CalculatedSpeed;
  167. // Calculate the desired velocity from the point we want to reach
  168. desiredVelocity = desiredTargetPointInVelocitySpace.normalized*desiredSpeed;
  169. if (collisionNormal != Vector2.zero) {
  170. collisionNormal.Normalize();
  171. var dot = Vector2.Dot(currentVelocity, collisionNormal);
  172. // Check if the velocity is going into the wall
  173. if (dot < 0) {
  174. // If so: remove that component from the velocity
  175. currentVelocity -= collisionNormal * dot;
  176. }
  177. // Clear the normal
  178. collisionNormal = Vector2.zero;
  179. }
  180. }
  181. }
  182. public void PreCalculation () {
  183. if (PreCalculationCallback != null) {
  184. PreCalculationCallback();
  185. }
  186. }
  187. public void PostCalculation () {
  188. // ==> Set public properties
  189. if (!manuallyControlled) {
  190. CalculatedTargetPoint = calculatedTargetPoint;
  191. CalculatedSpeed = calculatedSpeed;
  192. }
  193. List<ObstacleVertex> tmp = obstaclesBuffered;
  194. obstaclesBuffered = obstacles;
  195. obstacles = tmp;
  196. manuallyControlled = false;
  197. }
  198. /// <summary>Populate the neighbours and neighbourDists lists with the closest agents to this agent</summary>
  199. public void CalculateNeighbours () {
  200. neighbours.Clear();
  201. neighbourDists.Clear();
  202. if (MaxNeighbours > 0 && !locked) simulator.Quadtree.Query(position, maxSpeed, agentTimeHorizon, radius, this);
  203. NeighbourCount = neighbours.Count;
  204. }
  205. /// <summary>Square a number</summary>
  206. static float Sqr (float x) {
  207. return x*x;
  208. }
  209. /// <summary>
  210. /// Used by the Quadtree.
  211. /// See: CalculateNeighbours
  212. /// </summary>
  213. internal float InsertAgentNeighbour (Agent agent, float rangeSq) {
  214. // Check if this agent collides with the other agent
  215. if (this == agent || (agent.layer & collidesWith) == 0) return rangeSq;
  216. // 2D distance
  217. float dist = (agent.position - position).sqrMagnitude;
  218. if (dist < rangeSq) {
  219. if (neighbours.Count < maxNeighbours) {
  220. neighbours.Add(null);
  221. neighbourDists.Add(float.PositiveInfinity);
  222. }
  223. // Insert the agent into the ordered list of neighbours
  224. int i = neighbours.Count-1;
  225. if (dist < neighbourDists[i]) {
  226. while (i != 0 && dist < neighbourDists[i-1]) {
  227. neighbours[i] = neighbours[i-1];
  228. neighbourDists[i] = neighbourDists[i-1];
  229. i--;
  230. }
  231. neighbours[i] = agent;
  232. neighbourDists[i] = dist;
  233. }
  234. if (neighbours.Count == maxNeighbours) {
  235. rangeSq = neighbourDists[neighbourDists.Count-1];
  236. }
  237. }
  238. return rangeSq;
  239. }
  240. /// <summary>(x, 0, y)</summary>
  241. static Vector3 FromXZ (Vector2 p) {
  242. return new Vector3(p.x, 0, p.y);
  243. }
  244. /// <summary>(x, z)</summary>
  245. static Vector2 ToXZ (Vector3 p) {
  246. return new Vector2(p.x, p.z);
  247. }
  248. /// <summary>
  249. /// Converts a 3D vector to a 2D vector in the movement plane.
  250. /// If movementPlane is XZ it will be projected onto the XZ plane
  251. /// and the elevation coordinate will be the Y coordinate
  252. /// otherwise it will be projected onto the XY plane and elevation
  253. /// will be the Z coordinate.
  254. /// </summary>
  255. Vector2 To2D (Vector3 p, out float elevation) {
  256. if (simulator.movementPlane == MovementPlane.XY) {
  257. elevation = -p.z;
  258. return new Vector2(p.x, p.y);
  259. } else {
  260. elevation = p.y;
  261. return new Vector2(p.x, p.z);
  262. }
  263. }
  264. static void DrawVO (Vector2 circleCenter, float radius, Vector2 origin) {
  265. float alpha = Mathf.Atan2((origin - circleCenter).y, (origin - circleCenter).x);
  266. float gamma = radius/(origin-circleCenter).magnitude;
  267. float delta = gamma <= 1.0f ? Mathf.Abs(Mathf.Acos(gamma)) : 0;
  268. Draw.Debug.CircleXZ(FromXZ(circleCenter), radius, Color.black, alpha-delta, alpha+delta);
  269. Vector2 p1 = new Vector2(Mathf.Cos(alpha-delta), Mathf.Sin(alpha-delta)) * radius;
  270. Vector2 p2 = new Vector2(Mathf.Cos(alpha+delta), Mathf.Sin(alpha+delta)) * radius;
  271. Vector2 p1t = -new Vector2(-p1.y, p1.x);
  272. Vector2 p2t = new Vector2(-p2.y, p2.x);
  273. p1 += circleCenter;
  274. p2 += circleCenter;
  275. Debug.DrawRay(FromXZ(p1), FromXZ(p1t).normalized*100, Color.black);
  276. Debug.DrawRay(FromXZ(p2), FromXZ(p2t).normalized*100, Color.black);
  277. }
  278. /// <summary>
  279. /// Velocity Obstacle.
  280. /// This is a struct to avoid too many allocations.
  281. ///
  282. /// See: https://en.wikipedia.org/wiki/Velocity_obstacle
  283. /// </summary>
  284. internal struct VO {
  285. Vector2 line1, line2, dir1, dir2;
  286. Vector2 cutoffLine, cutoffDir;
  287. Vector2 circleCenter;
  288. bool colliding;
  289. float radius;
  290. float weightFactor;
  291. float weightBonus;
  292. Vector2 segmentStart, segmentEnd;
  293. bool segment;
  294. /// <summary>Creates a VO for avoiding another agent.</summary>
  295. /// <param name="center">The position of the other agent relative to this agent.</param>
  296. /// <param name="offset">Offset of the velocity obstacle. For example to account for the agents' relative velocities.</param>
  297. /// <param name="radius">Combined radius of the two agents (radius1 + radius2).</param>
  298. /// <param name="inverseDt">1 divided by the local avoidance time horizon (e.g avoid agents that we will hit within the next 2 seconds).</param>
  299. /// <param name="inverseDeltaTime">1 divided by the time step length.</param>
  300. public VO (Vector2 center, Vector2 offset, float radius, float inverseDt, float inverseDeltaTime) {
  301. // Adjusted so that a parameter weightFactor of 1 will be the default ("natural") weight factor
  302. this.weightFactor = 1;
  303. weightBonus = 0;
  304. //this.radius = radius;
  305. Vector2 globalCenter;
  306. circleCenter = center*inverseDt + offset;
  307. this.weightFactor = 4*Mathf.Exp(-Sqr(center.sqrMagnitude/(radius*radius))) + 1;
  308. // Collision?
  309. if (center.magnitude < radius) {
  310. colliding = true;
  311. // 0.001 is there to make sure lin1.magnitude is not so small that the normalization
  312. // below will return Vector2.zero as that will make the VO invalid and it will be ignored.
  313. line1 = center.normalized * (center.magnitude - radius - 0.001f) * 0.3f * inverseDeltaTime;
  314. dir1 = new Vector2(line1.y, -line1.x).normalized;
  315. line1 += offset;
  316. cutoffDir = Vector2.zero;
  317. cutoffLine = Vector2.zero;
  318. dir2 = Vector2.zero;
  319. line2 = Vector2.zero;
  320. this.radius = 0;
  321. } else {
  322. colliding = false;
  323. center *= inverseDt;
  324. radius *= inverseDt;
  325. globalCenter = center+offset;
  326. // 0.001 is there to make sure cutoffDistance is not so small that the normalization
  327. // below will return Vector2.zero as that will make the VO invalid and it will be ignored.
  328. var cutoffDistance = center.magnitude - radius + 0.001f;
  329. cutoffLine = center.normalized * cutoffDistance;
  330. cutoffDir = new Vector2(-cutoffLine.y, cutoffLine.x).normalized;
  331. cutoffLine += offset;
  332. float alpha = Mathf.Atan2(-center.y, -center.x);
  333. float delta = Mathf.Abs(Mathf.Acos(radius/center.magnitude));
  334. this.radius = radius;
  335. // Bounding Lines
  336. // Point on circle
  337. line1 = new Vector2(Mathf.Cos(alpha+delta), Mathf.Sin(alpha+delta));
  338. // Vector tangent to circle which is the correct line tangent
  339. // Note that this vector is normalized
  340. dir1 = new Vector2(line1.y, -line1.x);
  341. // Point on circle
  342. line2 = new Vector2(Mathf.Cos(alpha-delta), Mathf.Sin(alpha-delta));
  343. // Vector tangent to circle which is the correct line tangent
  344. // Note that this vector is normalized
  345. dir2 = new Vector2(line2.y, -line2.x);
  346. line1 = line1 * radius + globalCenter;
  347. line2 = line2 * radius + globalCenter;
  348. }
  349. segmentStart = Vector2.zero;
  350. segmentEnd = Vector2.zero;
  351. segment = false;
  352. }
  353. /// <summary>
  354. /// Creates a VO for avoiding another agent.
  355. /// Note that the segment is directed, the agent will want to be on the left side of the segment.
  356. /// </summary>
  357. public static VO SegmentObstacle (Vector2 segmentStart, Vector2 segmentEnd, Vector2 offset, float radius, float inverseDt, float inverseDeltaTime) {
  358. var vo = new VO();
  359. // Adjusted so that a parameter weightFactor of 1 will be the default ("natural") weight factor
  360. vo.weightFactor = 1;
  361. // Just higher than anything else
  362. vo.weightBonus = Mathf.Max(radius, 1)*40;
  363. var closestOnSegment = VectorMath.ClosestPointOnSegment(segmentStart, segmentEnd, Vector2.zero);
  364. // Collision?
  365. if (closestOnSegment.magnitude <= radius) {
  366. vo.colliding = true;
  367. vo.line1 = closestOnSegment.normalized * (closestOnSegment.magnitude - radius) * 0.3f * inverseDeltaTime;
  368. vo.dir1 = new Vector2(vo.line1.y, -vo.line1.x).normalized;
  369. vo.line1 += offset;
  370. vo.cutoffDir = Vector2.zero;
  371. vo.cutoffLine = Vector2.zero;
  372. vo.dir2 = Vector2.zero;
  373. vo.line2 = Vector2.zero;
  374. vo.radius = 0;
  375. vo.segmentStart = Vector2.zero;
  376. vo.segmentEnd = Vector2.zero;
  377. vo.segment = false;
  378. } else {
  379. vo.colliding = false;
  380. segmentStart *= inverseDt;
  381. segmentEnd *= inverseDt;
  382. radius *= inverseDt;
  383. var cutoffTangent = (segmentEnd - segmentStart).normalized;
  384. vo.cutoffDir = cutoffTangent;
  385. vo.cutoffLine = segmentStart + new Vector2(-cutoffTangent.y, cutoffTangent.x) * radius;
  386. vo.cutoffLine += offset;
  387. // See documentation for details
  388. // The call to Max is just to prevent floating point errors causing NaNs to appear
  389. var startSqrMagnitude = segmentStart.sqrMagnitude;
  390. var normal1 = -VectorMath.ComplexMultiply(segmentStart, new Vector2(radius, Mathf.Sqrt(Mathf.Max(0, startSqrMagnitude - radius*radius)))) / startSqrMagnitude;
  391. var endSqrMagnitude = segmentEnd.sqrMagnitude;
  392. var normal2 = -VectorMath.ComplexMultiply(segmentEnd, new Vector2(radius, -Mathf.Sqrt(Mathf.Max(0, endSqrMagnitude - radius*radius)))) / endSqrMagnitude;
  393. vo.line1 = segmentStart + normal1 * radius + offset;
  394. vo.line2 = segmentEnd + normal2 * radius + offset;
  395. // Note that the normals are already normalized
  396. vo.dir1 = new Vector2(normal1.y, -normal1.x);
  397. vo.dir2 = new Vector2(normal2.y, -normal2.x);
  398. vo.segmentStart = segmentStart;
  399. vo.segmentEnd = segmentEnd;
  400. vo.radius = radius;
  401. vo.segment = true;
  402. }
  403. return vo;
  404. }
  405. /// <summary>
  406. /// Returns a negative number of if p lies on the left side of a line which with one point in a and has a tangent in the direction of dir.
  407. /// The number can be seen as the double signed area of the triangle {a, a+dir, p} multiplied by the length of dir.
  408. /// If dir.magnitude=1 this is also the distance from p to the line {a, a+dir}.
  409. /// </summary>
  410. public static float SignedDistanceFromLine (Vector2 a, Vector2 dir, Vector2 p) {
  411. return (p.x - a.x) * (dir.y) - (dir.x) * (p.y - a.y);
  412. }
  413. /// <summary>
  414. /// Gradient and value of the cost function of this VO.
  415. /// Very similar to the <see cref="Gradient"/> method however the gradient
  416. /// and value have been scaled and tweaked slightly.
  417. /// </summary>
  418. public Vector2 ScaledGradient (Vector2 p, out float weight) {
  419. var grad = Gradient(p, out weight);
  420. if (weight > 0) {
  421. const float Scale = 2;
  422. grad *= Scale * weightFactor;
  423. weight *= Scale * weightFactor;
  424. weight += 1 + weightBonus;
  425. }
  426. return grad;
  427. }
  428. /// <summary>
  429. /// Gradient and value of the cost function of this VO.
  430. /// The VO has a cost function which is 0 outside the VO
  431. /// and increases inside it as the point moves further into
  432. /// the VO.
  433. ///
  434. /// This is the negative gradient of that function as well as its
  435. /// value (the weight). The negative gradient points in the direction
  436. /// where the function decreases the fastest.
  437. ///
  438. /// The value of the function is the distance to the closest edge
  439. /// of the VO and the gradient is normalized.
  440. /// </summary>
  441. public Vector2 Gradient (Vector2 p, out float weight) {
  442. if (colliding) {
  443. // Calculate double signed area of the triangle consisting of the points
  444. // {line1, line1+dir1, p}
  445. float l1 = SignedDistanceFromLine(line1, dir1, p);
  446. // Serves as a check for which side of the line the point p is
  447. if (l1 >= 0) {
  448. weight = l1;
  449. return new Vector2(-dir1.y, dir1.x);
  450. } else {
  451. weight = 0;
  452. return new Vector2(0, 0);
  453. }
  454. }
  455. float det3 = SignedDistanceFromLine(cutoffLine, cutoffDir, p);
  456. if (det3 <= 0) {
  457. weight = 0;
  458. return Vector2.zero;
  459. } else {
  460. // Signed distances to the two edges along the sides of the VO
  461. float det1 = SignedDistanceFromLine(line1, dir1, p);
  462. float det2 = SignedDistanceFromLine(line2, dir2, p);
  463. if (det1 >= 0 && det2 >= 0) {
  464. // We are inside both of the half planes
  465. // (all three if we count the cutoff line)
  466. // and thus inside the forbidden region in velocity space
  467. // Actually the negative gradient because we want the
  468. // direction where it slopes the most downwards, not upwards
  469. Vector2 gradient;
  470. // Check if we are in the semicircle region near the cap of the VO
  471. if (Vector2.Dot(p - line1, dir1) > 0 && Vector2.Dot(p - line2, dir2) < 0) {
  472. if (segment) {
  473. // This part will only be reached for line obstacles (i.e not other agents)
  474. if (det3 < radius) {
  475. var closestPointOnLine = (Vector2)VectorMath.ClosestPointOnSegment(segmentStart, segmentEnd, p);
  476. var dirFromCenter = p - closestPointOnLine;
  477. float distToCenter;
  478. gradient = VectorMath.Normalize(dirFromCenter, out distToCenter);
  479. // The weight is the distance to the edge
  480. weight = radius - distToCenter;
  481. return gradient;
  482. }
  483. } else {
  484. var dirFromCenter = p - circleCenter;
  485. float distToCenter;
  486. gradient = VectorMath.Normalize(dirFromCenter, out distToCenter);
  487. // The weight is the distance to the edge
  488. weight = radius - distToCenter;
  489. return gradient;
  490. }
  491. }
  492. if (segment && det3 < det1 && det3 < det2) {
  493. weight = det3;
  494. gradient = new Vector2(-cutoffDir.y, cutoffDir.x);
  495. return gradient;
  496. }
  497. // Just move towards the closest edge
  498. // The weight is the distance to the edge
  499. if (det1 < det2) {
  500. weight = det1;
  501. gradient = new Vector2(-dir1.y, dir1.x);
  502. } else {
  503. weight = det2;
  504. gradient = new Vector2(-dir2.y, dir2.x);
  505. }
  506. return gradient;
  507. }
  508. weight = 0;
  509. return Vector2.zero;
  510. }
  511. }
  512. }
  513. /// <summary>
  514. /// Very simple list.
  515. /// Cannot use a List<T> because when indexing into a List<T> and T is
  516. /// a struct (which VO is) then the whole struct will be copied.
  517. /// When indexing into an array, that copy can be skipped.
  518. /// </summary>
  519. internal class VOBuffer {
  520. public VO[] buffer;
  521. public int length;
  522. public void Clear () {
  523. length = 0;
  524. }
  525. public VOBuffer (int n) {
  526. buffer = new VO[n];
  527. length = 0;
  528. }
  529. public void Add (VO vo) {
  530. if (length >= buffer.Length) {
  531. var nbuffer = new VO[buffer.Length * 2];
  532. buffer.CopyTo(nbuffer, 0);
  533. buffer = nbuffer;
  534. }
  535. buffer[length++] = vo;
  536. }
  537. }
  538. internal void CalculateVelocity (Pathfinding.RVO.Simulator.WorkerContext context) {
  539. if (manuallyControlled) {
  540. return;
  541. }
  542. if (locked) {
  543. calculatedSpeed = 0;
  544. calculatedTargetPoint = position;
  545. return;
  546. }
  547. // Buffer which will be filled up with velocity obstacles (VOs)
  548. var vos = context.vos;
  549. vos.Clear();
  550. GenerateObstacleVOs(vos);
  551. GenerateNeighbourAgentVOs(vos);
  552. bool insideAnyVO = BiasDesiredVelocity(vos, ref desiredVelocity, ref desiredTargetPointInVelocitySpace, simulator.symmetryBreakingBias);
  553. if (!insideAnyVO) {
  554. // Desired velocity can be used directly since it was not inside any velocity obstacle.
  555. // No need to run optimizer because this will be the global minima.
  556. // This is also a special case in which we can set the
  557. // calculated target point to the desired target point
  558. // instead of calculating a point based on a calculated velocity
  559. // which is an important difference when the agent is very close
  560. // to the target point
  561. // TODO: Not actually guaranteed to be global minima if desiredTargetPointInVelocitySpace.magnitude < desiredSpeed
  562. // maybe do something different here?
  563. calculatedTargetPoint = desiredTargetPointInVelocitySpace + position;
  564. calculatedSpeed = desiredSpeed;
  565. if (DebugDraw) Draw.Debug.CrossXZ(FromXZ(calculatedTargetPoint), Color.white);
  566. return;
  567. }
  568. Vector2 result = Vector2.zero;
  569. result = GradientDescent(vos, currentVelocity, desiredVelocity);
  570. if (DebugDraw) Draw.Debug.CrossXZ(FromXZ(result+position), Color.white);
  571. //Debug.DrawRay (To3D (position), To3D (result));
  572. calculatedTargetPoint = position + result;
  573. calculatedSpeed = Mathf.Min(result.magnitude, maxSpeed);
  574. }
  575. static Color Rainbow (float v) {
  576. Color c = new Color(v, 0, 0);
  577. if (c.r > 1) { c.g = c.r - 1; c.r = 1; }
  578. if (c.g > 1) { c.b = c.g - 1; c.g = 1; }
  579. return c;
  580. }
  581. void GenerateObstacleVOs (VOBuffer vos) {
  582. var range = maxSpeed * obstacleTimeHorizon;
  583. // Iterate through all obstacles that we might need to avoid
  584. for (int i = 0; i < simulator.obstacles.Count; i++) {
  585. var obstacle = simulator.obstacles[i];
  586. var vertex = obstacle;
  587. // Iterate through all edges (defined by vertex and vertex.dir) in the obstacle
  588. do {
  589. // Ignore the edge if the agent should not collide with it
  590. if (vertex.ignore || (vertex.layer & collidesWith) == 0) {
  591. vertex = vertex.next;
  592. continue;
  593. }
  594. // Start and end points of the current segment
  595. float elevation1, elevation2;
  596. var p1 = To2D(vertex.position, out elevation1);
  597. var p2 = To2D(vertex.next.position, out elevation2);
  598. Vector2 dir = (p2 - p1).normalized;
  599. // Signed distance from the line (not segment, lines are infinite)
  600. // TODO: Can be optimized
  601. float dist = VO.SignedDistanceFromLine(p1, dir, position);
  602. if (dist >= -0.01f && dist < range) {
  603. float factorAlongSegment = Vector2.Dot(position - p1, p2 - p1) / (p2 - p1).sqrMagnitude;
  604. // Calculate the elevation (y) coordinate of the point on the segment closest to the agent
  605. var segmentY = Mathf.Lerp(elevation1, elevation2, factorAlongSegment);
  606. // Calculate distance from the segment (not line)
  607. var sqrDistToSegment = (Vector2.Lerp(p1, p2, factorAlongSegment) - position).sqrMagnitude;
  608. // Ignore the segment if it is too far away
  609. // or the agent is too high up (or too far down) on the elevation axis (usually y axis) to avoid it.
  610. // If the XY plane is used then all elevation checks are disabled
  611. if (sqrDistToSegment < range*range && (simulator.movementPlane == MovementPlane.XY || (elevationCoordinate <= segmentY + vertex.height && elevationCoordinate+height >= segmentY))) {
  612. vos.Add(VO.SegmentObstacle(p2 - position, p1 - position, Vector2.zero, radius * 0.01f, 1f / ObstacleTimeHorizon, 1f / simulator.DeltaTime));
  613. }
  614. }
  615. vertex = vertex.next;
  616. } while (vertex != obstacle && vertex != null && vertex.next != null);
  617. }
  618. }
  619. void GenerateNeighbourAgentVOs (VOBuffer vos) {
  620. float inverseAgentTimeHorizon = 1.0f/agentTimeHorizon;
  621. // The RVO algorithm assumes we will continue to
  622. // move in roughly the same direction
  623. Vector2 optimalVelocity = currentVelocity;
  624. for (int o = 0; o < neighbours.Count; o++) {
  625. Agent other = neighbours[o];
  626. // Don't avoid ourselves
  627. if (other == this)
  628. continue;
  629. // Interval along the y axis in which the agents overlap
  630. float maxY = System.Math.Min(elevationCoordinate + height, other.elevationCoordinate + other.height);
  631. float minY = System.Math.Max(elevationCoordinate, other.elevationCoordinate);
  632. // The agents cannot collide since they are on different y-levels
  633. if (maxY - minY < 0) {
  634. continue;
  635. }
  636. float totalRadius = radius + other.radius;
  637. // Describes a circle on the border of the VO
  638. Vector2 voBoundingOrigin = other.position - position;
  639. float avoidanceStrength;
  640. if (other.locked || other.manuallyControlled) {
  641. avoidanceStrength = 1;
  642. } else if (other.Priority > 0.00001f || Priority > 0.00001f) {
  643. avoidanceStrength = other.Priority / (Priority + other.Priority);
  644. } else {
  645. // Both this agent's priority and the other agent's priority is zero or negative
  646. // Assume they have the same priority
  647. avoidanceStrength = 0.5f;
  648. }
  649. // We assume that the other agent will continue to move with roughly the same velocity if the priorities for the agents are similar.
  650. // If the other agent has a higher priority than this agent (avoidanceStrength > 0.5) then we will assume it will move more along its
  651. // desired velocity. This will have the effect of other agents trying to clear a path for where a high priority agent wants to go.
  652. // If this is not done then even high priority agents can get stuck when it is really crowded and they have had to slow down.
  653. Vector2 otherOptimalVelocity = Vector2.Lerp(other.currentVelocity, other.desiredVelocity, 2*avoidanceStrength - 1);
  654. var voCenter = Vector2.Lerp(optimalVelocity, otherOptimalVelocity, avoidanceStrength);
  655. vos.Add(new VO(voBoundingOrigin, voCenter, totalRadius, inverseAgentTimeHorizon, 1 / simulator.DeltaTime));
  656. if (DebugDraw)
  657. DrawVO(position + voBoundingOrigin * inverseAgentTimeHorizon + voCenter, totalRadius * inverseAgentTimeHorizon, position + voCenter);
  658. }
  659. }
  660. Vector2 GradientDescent (VOBuffer vos, Vector2 sampleAround1, Vector2 sampleAround2) {
  661. float score1;
  662. var minima1 = Trace(vos, sampleAround1, out score1);
  663. if (DebugDraw) Draw.Debug.CrossXZ(FromXZ(minima1 + position), Color.yellow, 0.5f);
  664. // Can be uncommented for higher quality local avoidance
  665. // for ( int i = 0; i < 3; i++ ) {
  666. // Vector2 p = desiredVelocity + new Vector2(Mathf.Cos(Mathf.PI*2*(i/3.0f)), Mathf.Sin(Mathf.PI*2*(i/3.0f)));
  667. // float score;Vector2 res = Trace ( vos, p, velocity.magnitude*simulator.qualityCutoff, out score );
  668. //
  669. // if ( score < best ) {
  670. // result = res;
  671. // best = score;
  672. // }
  673. // }
  674. float score2;
  675. Vector2 minima2 = Trace(vos, sampleAround2, out score2);
  676. if (DebugDraw) Draw.Debug.CrossXZ(FromXZ(minima2 + position), Color.magenta, 0.5f);
  677. return score1 < score2 ? minima1 : minima2;
  678. }
  679. /// <summary>
  680. /// Bias towards the right side of agents.
  681. /// Rotate desiredVelocity at most [value] number of radians. 1 radian ≈ 57°
  682. /// This breaks up symmetries.
  683. ///
  684. /// The desired velocity will only be rotated if it is inside a velocity obstacle (VO).
  685. /// If it is inside one, it will not be rotated further than to the edge of it
  686. ///
  687. /// The targetPointInVelocitySpace will be rotated by the same amount as the desired velocity
  688. ///
  689. /// Returns: True if the desired velocity was inside any VO
  690. /// </summary>
  691. static bool BiasDesiredVelocity (VOBuffer vos, ref Vector2 desiredVelocity, ref Vector2 targetPointInVelocitySpace, float maxBiasRadians) {
  692. var desiredVelocityMagn = desiredVelocity.magnitude;
  693. var maxValue = 0f;
  694. for (int i = 0; i < vos.length; i++) {
  695. float value;
  696. // The value is approximately the distance to the edge of the VO
  697. // so taking the maximum will give us the distance to the edge of the VO
  698. // which the desired velocity is furthest inside
  699. vos.buffer[i].Gradient(desiredVelocity, out value);
  700. maxValue = Mathf.Max(maxValue, value);
  701. }
  702. // Check if the agent was inside any VO
  703. var inside = maxValue > 0;
  704. // Avoid division by zero below
  705. if (desiredVelocityMagn < 0.001f) {
  706. return inside;
  707. }
  708. // Rotate the desired velocity clockwise (to the right) at most maxBiasRadians number of radians
  709. // Assuming maxBiasRadians is small, we can just move it instead and it will give approximately the same effect
  710. // See https://en.wikipedia.org/wiki/Small-angle_approximation
  711. var angle = Mathf.Min(maxBiasRadians, maxValue / desiredVelocityMagn);
  712. desiredVelocity += new Vector2(desiredVelocity.y, -desiredVelocity.x) * angle;
  713. targetPointInVelocitySpace += new Vector2(targetPointInVelocitySpace.y, -targetPointInVelocitySpace.x) * angle;
  714. return inside;
  715. }
  716. /// <summary>Evaluate gradient and value of the cost function at velocity p</summary>
  717. Vector2 EvaluateGradient (VOBuffer vos, Vector2 p, out float value) {
  718. Vector2 gradient = Vector2.zero;
  719. value = 0;
  720. // Avoid other agents
  721. for (int i = 0; i < vos.length; i++) {
  722. float w;
  723. var grad = vos.buffer[i].ScaledGradient(p, out w);
  724. if (w > value) {
  725. value = w;
  726. gradient = grad;
  727. }
  728. }
  729. // Move closer to the desired velocity
  730. var dirToDesiredVelocity = desiredVelocity - p;
  731. var distToDesiredVelocity = dirToDesiredVelocity.magnitude;
  732. if (distToDesiredVelocity > 0.0001f) {
  733. gradient += dirToDesiredVelocity * (DesiredVelocityWeight/distToDesiredVelocity);
  734. value += distToDesiredVelocity * DesiredVelocityWeight;
  735. }
  736. // Prefer speeds lower or equal to the desired speed
  737. // and avoid speeds greater than the max speed
  738. var sqrSpeed = p.sqrMagnitude;
  739. if (sqrSpeed > desiredSpeed*desiredSpeed) {
  740. var speed = Mathf.Sqrt(sqrSpeed);
  741. if (speed > maxSpeed) {
  742. const float MaxSpeedWeight = 3;
  743. value += MaxSpeedWeight * (speed - maxSpeed);
  744. gradient -= MaxSpeedWeight * (p/speed);
  745. }
  746. // Scale needs to be strictly greater than DesiredVelocityWeight
  747. // otherwise the agent will not prefer the desired speed over
  748. // the maximum speed
  749. float scale = 2*DesiredVelocityWeight;
  750. value += scale * (speed - desiredSpeed);
  751. gradient -= scale * (p/speed);
  752. }
  753. return gradient;
  754. }
  755. /// <summary>
  756. /// Traces the vector field constructed out of the velocity obstacles.
  757. /// Returns the position which gives the minimum score (approximately).
  758. ///
  759. /// See: https://en.wikipedia.org/wiki/Gradient_descent
  760. /// </summary>
  761. Vector2 Trace (VOBuffer vos, Vector2 p, out float score) {
  762. // Pick a reasonable initial step size
  763. float stepSize = Mathf.Max(radius, 0.2f * desiredSpeed);
  764. float bestScore = float.PositiveInfinity;
  765. Vector2 bestP = p;
  766. // TODO: Add momentum to speed up convergence?
  767. const int MaxIterations = 50;
  768. for (int s = 0; s < MaxIterations; s++) {
  769. float step = 1.0f - (s/(float)MaxIterations);
  770. step = Sqr(step) * stepSize;
  771. float value;
  772. var gradient = EvaluateGradient(vos, p, out value);
  773. if (value < bestScore) {
  774. bestScore = value;
  775. bestP = p;
  776. }
  777. // TODO: Add cutoff for performance
  778. gradient.Normalize();
  779. gradient *= step;
  780. Vector2 prev = p;
  781. p += gradient;
  782. if (DebugDraw) Debug.DrawLine(FromXZ(prev + position), FromXZ(p + position), Rainbow(s*0.1f) * new Color(1, 1, 1, 1f));
  783. }
  784. score = bestScore;
  785. return bestP;
  786. }
  787. }
  788. }