Advertisement
Guest User

Untitled

a guest
Apr 24th, 2019
167
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 41.15 KB | None | 0 0
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4.  
  5. namespace Pathfinding.RVO.Sampled {
  6.     using Pathfinding;
  7.     using Pathfinding.RVO;
  8.     using Pathfinding.Util;
  9.  
  10.     /** Internal agent for the RVO system.
  11.      * Usually you will interface with the IAgent interface instead.
  12.      *
  13.      * \see IAgent
  14.      */
  15.     public class Agent : IAgent {
  16.         //Current values for double buffer calculation
  17.  
  18.         internal float radius, height, desiredSpeed, maxSpeed, agentTimeHorizon, obstacleTimeHorizon;
  19.         internal bool locked = false;
  20.  
  21.         RVOLayer layer, collidesWith;
  22.  
  23.         int maxNeighbours;
  24.         internal Vector2 position;
  25.         float elevationCoordinate;
  26.         Vector2 currentVelocity;
  27.  
  28.         /** Desired target point - position */
  29.         Vector2 desiredTargetPointInVelocitySpace;
  30.         Vector2 desiredVelocity;
  31.  
  32.         Vector2 nextTargetPoint;
  33.         float nextDesiredSpeed;
  34.         float nextMaxSpeed;
  35.         Vector2 collisionNormal;
  36.         bool manuallyControlled;
  37.         bool debugDraw;
  38.  
  39.         #region IAgent Properties
  40.  
  41.         /** \copydoc Pathfinding::RVO::IAgent::Position */
  42.         public Vector2 Position { get; set; }
  43.  
  44.         /** \copydoc Pathfinding::RVO::IAgent::ElevationCoordinate */
  45.         public float ElevationCoordinate { get; set; }
  46.  
  47.         /** \copydoc Pathfinding::RVO::IAgent::CalculatedTargetPoint */
  48.         public Vector2 CalculatedTargetPoint { get; private set; }
  49.  
  50.         /** \copydoc Pathfinding::RVO::IAgent::CalculatedSpeed */
  51.         public float CalculatedSpeed { get; private set; }
  52.  
  53.         /** \copydoc Pathfinding::RVO::IAgent::Locked */
  54.         public bool Locked { get; set; }
  55.  
  56.         /** \copydoc Pathfinding::RVO::IAgent::Radius */
  57.         public float Radius { get; set; }
  58.  
  59.         /** \copydoc Pathfinding::RVO::IAgent::Height */
  60.         public float Height { get; set; }
  61.  
  62.         /** \copydoc Pathfinding::RVO::IAgent::AgentTimeHorizon */
  63.         public float AgentTimeHorizon { get; set; }
  64.  
  65.         /** \copydoc Pathfinding::RVO::IAgent::ObstacleTimeHorizon */
  66.         public float ObstacleTimeHorizon { get; set; }
  67.  
  68.         /** \copydoc Pathfinding::RVO::IAgent::MaxNeighbours */
  69.         public int MaxNeighbours { get; set; }
  70.  
  71.         /** \copydoc Pathfinding::RVO::IAgent::NeighbourCount */
  72.         public int NeighbourCount { get; private set; }
  73.  
  74.         /** \copydoc Pathfinding::RVO::IAgent::Layer */
  75.         public RVOLayer Layer { get; set; }
  76.  
  77.         /** \copydoc Pathfinding::RVO::IAgent::CollidesWith */
  78.         public RVOLayer CollidesWith { get; set; }
  79.  
  80.         /** \copydoc Pathfinding::RVO::IAgent::FlowFollowingStrength */
  81.         public float FlowFollowingStrength { get; set; }
  82.  
  83.         /** \copydoc Pathfinding::RVO::IAgent::DebugDraw */
  84.         public bool DebugDraw {
  85.             get {
  86.                 return debugDraw;
  87.             }
  88.             set {
  89.                 debugDraw = value && simulator != null && !simulator.Multithreading;
  90.             }
  91.         }
  92.  
  93.         /** \copydoc Pathfinding::RVO::IAgent::Priority */
  94.         public float Priority { get; set; }
  95.  
  96.         /** \copydoc Pathfinding::RVO::IAgent::PreCalculationCallback */
  97.         public System.Action PreCalculationCallback { private get; set; }
  98.  
  99.         #endregion
  100.  
  101.         #region IAgent Methods
  102.  
  103.         /** \copydoc Pathfinding::RVO::IAgent::SetTarget */
  104.         public void SetTarget (Vector2 targetPoint, float desiredSpeed, float maxSpeed) {
  105.             maxSpeed = System.Math.Max(maxSpeed, 0);
  106.             desiredSpeed = System.Math.Min(System.Math.Max(desiredSpeed, 0), maxSpeed);
  107.  
  108.             nextTargetPoint = targetPoint;
  109.             nextDesiredSpeed = desiredSpeed;
  110.             nextMaxSpeed = maxSpeed;
  111.         }
  112.  
  113.         /** \copydoc Pathfinding::RVO::IAgent::SetCollisionNormal */
  114.         public void SetCollisionNormal (Vector2 normal) {
  115.             collisionNormal = normal;
  116.         }
  117.  
  118.         /** \copydoc Pathfinding::RVO::IAgent::ForceSetVelocity */
  119.         public void ForceSetVelocity (Vector2 velocity) {
  120.             // A bit hacky, but it is approximately correct
  121.             // assuming the agent does not move significantly
  122.             nextTargetPoint = CalculatedTargetPoint = position + velocity * 1000;
  123.             nextDesiredSpeed = CalculatedSpeed = velocity.magnitude;
  124.             manuallyControlled = true;
  125.         }
  126.  
  127.         #endregion
  128.  
  129.         /** Used internally for a linked list */
  130.         internal Agent next;
  131.  
  132.         float calculatedSpeed;
  133.         Vector2 calculatedTargetPoint;
  134.  
  135.         /** Simulator which handles this agent.
  136.          * Used by this script as a reference and to prevent
  137.          * adding this agent to multiple simulations.
  138.          */
  139.         internal Simulator simulator;
  140.  
  141.         List<Agent> neighbours = new List<Agent>();
  142.         List<float> neighbourDists = new List<float>();
  143.         List<ObstacleVertex> obstaclesBuffered = new List<ObstacleVertex>();
  144.         List<ObstacleVertex> obstacles = new List<ObstacleVertex>();
  145. #if !AstarRelease
  146.         List<float> obstacleDists = new List<float>();
  147. #endif
  148.  
  149.         const float DesiredVelocityWeight = 0.1f;
  150.  
  151.         /** Extra weight that walls will have */
  152.         const float WallWeight = 5;
  153.  
  154.         public List<ObstacleVertex> NeighbourObstacles {
  155.             get {
  156.                 return null;
  157.             }
  158.         }
  159.  
  160.         public Agent (Vector2 pos, float elevationCoordinate) {
  161.             AgentTimeHorizon = 2;
  162.             ObstacleTimeHorizon = 2;
  163.             Height = 5;
  164.             Radius = 5;
  165.             MaxNeighbours = 10;
  166.             Locked = false;
  167.             Position = pos;
  168.             ElevationCoordinate = elevationCoordinate;
  169.             Layer = RVOLayer.DefaultAgent;
  170.             CollidesWith = (RVOLayer)(-1);
  171.             Priority = 0.5f;
  172.             CalculatedTargetPoint = pos;
  173.             CalculatedSpeed = 0;
  174.             SetTarget(pos, 0, 0);
  175.         }
  176.  
  177.         /** Reads public properties and stores them in internal fields.
  178.          * This is required because multithreading is used and if another script
  179.          * updated the fields at the same time as this class used them in another thread
  180.          * weird things could happen.
  181.          *
  182.          * Will also set CalculatedTargetPoint and CalculatedSpeed to the result
  183.          * which was last calculated.
  184.          */
  185.         public void BufferSwitch () {
  186.             // <== Read public properties
  187.             radius = Radius;
  188.             height = Height;
  189.             maxSpeed = nextMaxSpeed;
  190.             desiredSpeed = nextDesiredSpeed;
  191.             agentTimeHorizon = AgentTimeHorizon;
  192.             obstacleTimeHorizon = ObstacleTimeHorizon;
  193.             maxNeighbours = MaxNeighbours;
  194.             // Manually controlled overrides the agent being locked
  195.             // (if one for some reason uses them at the same time)
  196.             locked = Locked && !manuallyControlled;
  197.             position = Position;
  198.             elevationCoordinate = ElevationCoordinate;
  199.             collidesWith = CollidesWith;
  200.             layer = Layer;
  201.  
  202.             if (locked) {
  203.                 // Locked agents do not move at all
  204.                 desiredTargetPointInVelocitySpace = position;
  205.                 desiredVelocity = currentVelocity = Vector2.zero;
  206.             } else {
  207.                 desiredTargetPointInVelocitySpace = nextTargetPoint - position;
  208.  
  209.                 // Estimate our current velocity
  210.                 // This is necessary because other agents need to know
  211.                 // how this agent is moving to be able to avoid it
  212.                 currentVelocity = (CalculatedTargetPoint - position).normalized * CalculatedSpeed;
  213.  
  214.                 // Calculate the desired velocity from the point we want to reach
  215.                 desiredVelocity = desiredTargetPointInVelocitySpace.normalized*desiredSpeed;
  216.  
  217.                 if (collisionNormal != Vector2.zero) {
  218.                     collisionNormal.Normalize();
  219.                     var dot = Vector2.Dot(currentVelocity, collisionNormal);
  220.  
  221.                     // Check if the velocity is going into the wall
  222.                     if (dot < 0) {
  223.                         // If so: remove that component from the velocity
  224.                         currentVelocity -= collisionNormal * dot;
  225.                     }
  226.  
  227.                     // Clear the normal
  228.                     collisionNormal = Vector2.zero;
  229.                 }
  230.             }
  231.         }
  232.  
  233.         public void PreCalculation () {
  234.             if (PreCalculationCallback != null) {
  235.                 PreCalculationCallback();
  236.             }
  237.         }
  238.  
  239.         public void PostCalculation () {
  240.             // ==> Set public properties
  241.             if (!manuallyControlled) {
  242.                 CalculatedTargetPoint = calculatedTargetPoint;
  243.                 CalculatedSpeed = calculatedSpeed;
  244.             }
  245.  
  246.             List<ObstacleVertex> tmp = obstaclesBuffered;
  247.             obstaclesBuffered = obstacles;
  248.             obstacles = tmp;
  249.  
  250.             manuallyControlled = false;
  251.         }
  252.  
  253.         /** Populate the neighbours and neighbourDists lists with the closest agents to this agent */
  254.         public void CalculateNeighbours () {
  255.             neighbours.Clear();
  256.             neighbourDists.Clear();
  257.  
  258.             if (MaxNeighbours > 0 && !locked) simulator.Quadtree.Query(position, maxSpeed, agentTimeHorizon, radius, this);
  259.  
  260.             NeighbourCount = neighbours.Count;
  261.         }
  262.  
  263.         /** Square a number */
  264.         static float Sqr (float x) {
  265.             return x*x;
  266.         }
  267.  
  268.         /** Used by the Quadtree.
  269.          * \see CalculateNeighbours
  270.          */
  271.         internal float InsertAgentNeighbour (Agent agent, float rangeSq) {
  272.             // Check if this agent collides with the other agent
  273.             if (this == agent || (agent.layer & collidesWith) == 0) return rangeSq;
  274.  
  275.             // 2D distance
  276.             float dist = (agent.position - position).sqrMagnitude;
  277.  
  278.             if (dist < rangeSq) {
  279.                 // Interval along the y axis in which the agents overlap
  280.                 float maxY = System.Math.Min(elevationCoordinate + height, agent.elevationCoordinate + agent.height);
  281.                 float minY = System.Math.Max(elevationCoordinate, agent.elevationCoordinate);
  282.  
  283.                 // The agents cannot collide since they are on different y-levels
  284.                 if (maxY - minY < 0) return rangeSq;
  285.  
  286.                 if (neighbours.Count < maxNeighbours) {
  287.                     neighbours.Add(null);
  288.                     neighbourDists.Add(float.PositiveInfinity);
  289.                 }
  290.  
  291.                 // Insert the agent into the ordered list of neighbours
  292.                 int i = neighbours.Count-1;
  293.                 if (dist < neighbourDists[i]) {
  294.                     while (i != 0 && dist < neighbourDists[i-1]) {
  295.                         neighbours[i] = neighbours[i-1];
  296.                         neighbourDists[i] = neighbourDists[i-1];
  297.                         i--;
  298.                     }
  299.                     neighbours[i] = agent;
  300.                     neighbourDists[i] = dist;
  301.                 }
  302.  
  303.                 if (neighbours.Count == maxNeighbours) {
  304.                     rangeSq = neighbourDists[neighbourDists.Count-1];
  305.                 }
  306.             }
  307.             return rangeSq;
  308.         }
  309.  
  310. #if !AstarRelease
  311.         public void InsertObstacleNeighbour (ObstacleVertex ob1, float rangeSq) {
  312.             ObstacleVertex ob2 = ob1.next;
  313.  
  314.             float dummy;
  315.             var a2D = To2D(ob1.position, out dummy);
  316.             var b2D = To2D(ob2.position, out dummy);
  317.             float dist = VectorMath.SqrDistancePointSegment(a2D, b2D, Position);
  318.  
  319.             if (dist < rangeSq) {
  320.                 obstacles.Add(ob1);
  321.                 obstacleDists.Add(dist);
  322.  
  323.                 int i = obstacles.Count-1;
  324.                 while (i != 0 && dist < obstacleDists[i-1]) {
  325.                     obstacles[i] = obstacles[i-1];
  326.                     obstacleDists[i] = obstacleDists[i-1];
  327.                     i--;
  328.                 }
  329.                 obstacles[i] = ob1;
  330.                 obstacleDists[i] = dist;
  331.             }
  332.         }
  333. #endif
  334.  
  335.         /** (x, 0, y) */
  336.         static Vector3 FromXZ (Vector2 p) {
  337.             return new Vector3(p.x, 0, p.y);
  338.         }
  339.  
  340.         /** (x, z) */
  341.         static Vector2 ToXZ (Vector3 p) {
  342.             return new Vector2(p.x, p.z);
  343.         }
  344.  
  345.         /** Converts a 3D vector to a 2D vector in the movement plane.
  346.          * If movementPlane is XZ it will be projected onto the XZ plane
  347.          * and the elevation coordinate will be the Y coordinate
  348.          * otherwise it will be projected onto the XY plane and elevation
  349.          * will be the Z coordinate.
  350.          */
  351.         Vector2 To2D (Vector3 p, out float elevation) {
  352.             if (simulator.movementPlane == MovementPlane.XY) {
  353.                 elevation = -p.z;
  354.                 return new Vector2(p.x, p.y);
  355.             } else {
  356.                 elevation = p.y;
  357.                 return new Vector2(p.x, p.z);
  358.             }
  359.         }
  360.  
  361.         static void DrawVO (Vector2 circleCenter, float radius, Vector2 origin) {
  362.             float alpha = Mathf.Atan2((origin - circleCenter).y, (origin - circleCenter).x);
  363.             float gamma = radius/(origin-circleCenter).magnitude;
  364.             float delta = gamma <= 1.0f ? Mathf.Abs(Mathf.Acos(gamma)) : 0;
  365.  
  366.             Draw.Debug.CircleXZ(FromXZ(circleCenter), radius, Color.black, alpha-delta, alpha+delta);
  367.             Vector2 p1 = new Vector2(Mathf.Cos(alpha-delta), Mathf.Sin(alpha-delta)) * radius;
  368.             Vector2 p2 = new Vector2(Mathf.Cos(alpha+delta), Mathf.Sin(alpha+delta)) * radius;
  369.  
  370.             Vector2 p1t = -new Vector2(-p1.y, p1.x);
  371.             Vector2 p2t = new Vector2(-p2.y, p2.x);
  372.             p1 += circleCenter;
  373.             p2 += circleCenter;
  374.  
  375.             Debug.DrawRay(FromXZ(p1), FromXZ(p1t).normalized*100, Color.black);
  376.             Debug.DrawRay(FromXZ(p2), FromXZ(p2t).normalized*100, Color.black);
  377.         }
  378.  
  379.         /** Velocity Obstacle.
  380.          * This is a struct to avoid too many allocations.
  381.          *
  382.          * \see https://en.wikipedia.org/wiki/Velocity_obstacle
  383.          */
  384.         internal struct VO {
  385.             Vector2 line1, line2, dir1, dir2;
  386.  
  387.             Vector2 cutoffLine, cutoffDir;
  388.             Vector2 circleCenter;
  389.  
  390.             bool colliding;
  391.             float radius;
  392.             float weightFactor;
  393.             float weightBonus;
  394.  
  395.             Vector2 segmentStart, segmentEnd;
  396.             bool segment;
  397.  
  398.             /** Creates a VO for avoiding another agent.
  399.              * \param center The position of the other agent relative to this agent.
  400.              * \param offset Offset of the velocity obstacle. For example to account for the agents' relative velocities.
  401.              * \param radius Combined radius of the two agents (radius1 + radius2).
  402.              * \param inverseDt 1 divided by the local avoidance time horizon (e.g avoid agents that we will hit within the next 2 seconds).
  403.              * \param inverseDeltaTime 1 divided by the time step length.
  404.              */
  405.             public VO (Vector2 center, Vector2 offset, float radius, float inverseDt, float inverseDeltaTime) {
  406.                 // Adjusted so that a parameter weightFactor of 1 will be the default ("natural") weight factor
  407.                 this.weightFactor = 1;
  408.                 weightBonus = 0;
  409.  
  410.                 //this.radius = radius;
  411.                 Vector2 globalCenter;
  412.  
  413.                 circleCenter = center*inverseDt + offset;
  414.  
  415.                 this.weightFactor = 4*Mathf.Exp(-Sqr(center.sqrMagnitude/(radius*radius))) + 1;
  416.                 // Collision?
  417.                 if (center.magnitude < radius) {
  418.                     colliding = true;
  419.  
  420.                     // 0.001 is there to make sure lin1.magnitude is not so small that the normalization
  421.                     // below will return Vector2.zero as that will make the VO invalid and it will be ignored.
  422.                     line1 = center.normalized * (center.magnitude - radius - 0.001f) * 0.3f * inverseDeltaTime;
  423.                     dir1 = new Vector2(line1.y, -line1.x).normalized;
  424.                     line1 += offset;
  425.  
  426.                     cutoffDir = Vector2.zero;
  427.                     cutoffLine = Vector2.zero;
  428.                     dir2 = Vector2.zero;
  429.                     line2 = Vector2.zero;
  430.                     this.radius = 0;
  431.                 } else {
  432.                     colliding = false;
  433.  
  434.                     center *= inverseDt;
  435.                     radius *= inverseDt;
  436.                     globalCenter = center+offset;
  437.  
  438.                     // 0.001 is there to make sure cutoffDistance is not so small that the normalization
  439.                     // below will return Vector2.zero as that will make the VO invalid and it will be ignored.
  440.                     var cutoffDistance = center.magnitude - radius + 0.001f;
  441.  
  442.                     cutoffLine = center.normalized * cutoffDistance;
  443.                     cutoffDir = new Vector2(-cutoffLine.y, cutoffLine.x).normalized;
  444.                     cutoffLine += offset;
  445.  
  446.                     float alpha = Mathf.Atan2(-center.y, -center.x);
  447.  
  448.                     float delta = Mathf.Abs(Mathf.Acos(radius/center.magnitude));
  449.  
  450.                     this.radius = radius;
  451.  
  452.                     // Bounding Lines
  453.  
  454.                     // Point on circle
  455.                     line1 = new Vector2(Mathf.Cos(alpha+delta), Mathf.Sin(alpha+delta));
  456.                     // Vector tangent to circle which is the correct line tangent
  457.                     // Note that this vector is normalized
  458.                     dir1 = new Vector2(line1.y, -line1.x);
  459.  
  460.                     // Point on circle
  461.                     line2 = new Vector2(Mathf.Cos(alpha-delta), Mathf.Sin(alpha-delta));
  462.                     // Vector tangent to circle which is the correct line tangent
  463.                     // Note that this vector is normalized
  464.                     dir2 = new Vector2(line2.y, -line2.x);
  465.  
  466.                     line1 = line1 * radius + globalCenter;
  467.                     line2 = line2 * radius + globalCenter;
  468.                 }
  469.  
  470.                 segmentStart = Vector2.zero;
  471.                 segmentEnd = Vector2.zero;
  472.                 segment = false;
  473.             }
  474.  
  475.             /** Creates a VO for avoiding another agent.
  476.              * Note that the segment is directed, the agent will want to be on the left side of the segment.
  477.              */
  478.             public static VO SegmentObstacle (Vector2 segmentStart, Vector2 segmentEnd, Vector2 offset, float radius, float inverseDt, float inverseDeltaTime) {
  479.                 var vo = new VO();
  480.  
  481.                 // Adjusted so that a parameter weightFactor of 1 will be the default ("natural") weight factor
  482.                 vo.weightFactor = 1;
  483.                 // Just higher than anything else
  484.                 vo.weightBonus = Mathf.Max(radius, 1)*40;
  485.  
  486.                 var closestOnSegment = VectorMath.ClosestPointOnSegment(segmentStart, segmentEnd, Vector2.zero);
  487.  
  488.                 // Collision?
  489.                 if (closestOnSegment.magnitude <= radius) {
  490.                     vo.colliding = true;
  491.  
  492.                     vo.line1 = closestOnSegment.normalized * (closestOnSegment.magnitude - radius) * 0.3f * inverseDeltaTime;
  493.                     vo.dir1 = new Vector2(vo.line1.y, -vo.line1.x).normalized;
  494.                     vo.line1 += offset;
  495.  
  496.                     vo.cutoffDir = Vector2.zero;
  497.                     vo.cutoffLine = Vector2.zero;
  498.                     vo.dir2 = Vector2.zero;
  499.                     vo.line2 = Vector2.zero;
  500.                     vo.radius = 0;
  501.  
  502.                     vo.segmentStart = Vector2.zero;
  503.                     vo.segmentEnd = Vector2.zero;
  504.                     vo.segment = false;
  505.                 } else {
  506.                     vo.colliding = false;
  507.  
  508.                     segmentStart *= inverseDt;
  509.                     segmentEnd *= inverseDt;
  510.                     radius *= inverseDt;
  511.  
  512.                     var cutoffTangent = (segmentEnd - segmentStart).normalized;
  513.                     vo.cutoffDir = cutoffTangent;
  514.                     vo.cutoffLine = segmentStart + new Vector2(-cutoffTangent.y, cutoffTangent.x) * radius;
  515.                     vo.cutoffLine += offset;
  516.  
  517.                     // See documentation for details
  518.                     // The call to Max is just to prevent floating point errors causing NaNs to appear
  519.                     var startSqrMagnitude = segmentStart.sqrMagnitude;
  520.                     var normal1 = -VectorMath.ComplexMultiply(segmentStart, new Vector2(radius, Mathf.Sqrt(Mathf.Max(0, startSqrMagnitude - radius*radius)))) / startSqrMagnitude;
  521.                     var endSqrMagnitude = segmentEnd.sqrMagnitude;
  522.                     var normal2 = -VectorMath.ComplexMultiply(segmentEnd, new Vector2(radius, -Mathf.Sqrt(Mathf.Max(0, endSqrMagnitude - radius*radius)))) / endSqrMagnitude;
  523.  
  524.                     vo.line1 = segmentStart + normal1 * radius + offset;
  525.                     vo.line2 = segmentEnd + normal2 * radius + offset;
  526.  
  527.                     // Note that the normals are already normalized
  528.                     vo.dir1 = new Vector2(normal1.y, -normal1.x);
  529.                     vo.dir2 = new Vector2(normal2.y, -normal2.x);
  530.  
  531.                     vo.segmentStart = segmentStart;
  532.                     vo.segmentEnd = segmentEnd;
  533.                     vo.radius = radius;
  534.                     vo.segment = true;
  535.                 }
  536.  
  537.                 return vo;
  538.             }
  539.  
  540.             /** Returns a negative number of if \a p lies on the left side of a line which with one point in \a a and has a tangent in the direction of \a dir.
  541.              * The number can be seen as the double signed area of the triangle {a, a+dir, p} multiplied by the length of \a dir.
  542.              * If dir.magnitude=1 this is also the distance from p to the line {a, a+dir}.
  543.              */
  544.             public static float SignedDistanceFromLine (Vector2 a, Vector2 dir, Vector2 p) {
  545.                 return (p.x - a.x) * (dir.y) - (dir.x) * (p.y - a.y);
  546.             }
  547.  
  548.             /** Gradient and value of the cost function of this VO.
  549.              * Very similar to the #Gradient method however the gradient
  550.              * and value have been scaled and tweaked slightly.
  551.              */
  552.             public Vector2 ScaledGradient (Vector2 p, out float weight) {
  553.                 var grad = Gradient(p, out weight);
  554.  
  555.                 if (weight > 0) {
  556.                     const float Scale = 2;
  557.                     grad *= Scale * weightFactor;
  558.                     weight *= Scale * weightFactor;
  559.                     weight += 1 + weightBonus;
  560.                 }
  561.  
  562.                 return grad;
  563.             }
  564.  
  565.             /** Gradient and value of the cost function of this VO.
  566.              * The VO has a cost function which is 0 outside the VO
  567.              * and increases inside it as the point moves further into
  568.              * the VO.
  569.              *
  570.              * This is the negative gradient of that function as well as its
  571.              * value (the weight). The negative gradient points in the direction
  572.              * where the function decreases the fastest.
  573.              *
  574.              * The value of the function is the distance to the closest edge
  575.              * of the VO and the gradient is normalized.
  576.              */
  577.             public Vector2 Gradient (Vector2 p, out float weight) {
  578.                 if (colliding) {
  579.                     // Calculate double signed area of the triangle consisting of the points
  580.                     // {line1, line1+dir1, p}
  581.                     float l1 = SignedDistanceFromLine(line1, dir1, p);
  582.  
  583.                     // Serves as a check for which side of the line the point p is
  584.                     if (l1 >= 0) {
  585.                         weight = l1;
  586.                         return new Vector2(-dir1.y, dir1.x);
  587.                     } else {
  588.                         weight = 0;
  589.                         return new Vector2(0, 0);
  590.                     }
  591.                 }
  592.  
  593.                 float det3 = SignedDistanceFromLine(cutoffLine, cutoffDir, p);
  594.                 if (det3 <= 0) {
  595.                     weight = 0;
  596.                     return Vector2.zero;
  597.                 } else {
  598.                     // Signed distances to the two edges along the sides of the VO
  599.                     float det1 = SignedDistanceFromLine(line1, dir1, p);
  600.                     float det2 = SignedDistanceFromLine(line2, dir2, p);
  601.                     if (det1 >= 0 && det2 >= 0) {
  602.                         // We are inside both of the half planes
  603.                         // (all three if we count the cutoff line)
  604.                         // and thus inside the forbidden region in velocity space
  605.  
  606.                         // Actually the negative gradient because we want the
  607.                         // direction where it slopes the most downwards, not upwards
  608.                         Vector2 gradient;
  609.  
  610.                         // Check if we are in the semicircle region near the cap of the VO
  611.                         if (Vector2.Dot(p - line1, dir1) > 0 && Vector2.Dot(p - line2, dir2) < 0) {
  612.                             if (segment) {
  613.                                 // This part will only be reached for line obstacles (i.e not other agents)
  614.                                 if (det3 < radius) {
  615.                                     var closestPointOnLine = (Vector2)VectorMath.ClosestPointOnSegment(segmentStart, segmentEnd, p);
  616.                                     var dirFromCenter = p - closestPointOnLine;
  617.                                     float distToCenter;
  618.                                     gradient = VectorMath.Normalize(dirFromCenter, out distToCenter);
  619.                                     // The weight is the distance to the edge
  620.                                     weight = radius - distToCenter;
  621.                                     return gradient;
  622.                                 }
  623.                             } else {
  624.                                 var dirFromCenter = p - circleCenter;
  625.                                 float distToCenter;
  626.                                 gradient = VectorMath.Normalize(dirFromCenter, out distToCenter);
  627.                                 // The weight is the distance to the edge
  628.                                 weight = radius - distToCenter;
  629.                                 return gradient;
  630.                             }
  631.                         }
  632.  
  633.                         if (segment && det3 < det1 && det3 < det2) {
  634.                             weight = det3;
  635.                             gradient = new Vector2(-cutoffDir.y, cutoffDir.x);
  636.                             return gradient;
  637.                         }
  638.  
  639.                         // Just move towards the closest edge
  640.                         // The weight is the distance to the edge
  641.                         if (det1 < det2) {
  642.                             weight = det1;
  643.                             gradient = new Vector2(-dir1.y, dir1.x);
  644.                         } else {
  645.                             weight = det2;
  646.                             gradient = new Vector2(-dir2.y, dir2.x);
  647.                         }
  648.  
  649.                         return gradient;
  650.                     }
  651.  
  652.                     weight = 0;
  653.                     return Vector2.zero;
  654.                 }
  655.             }
  656.         }
  657.  
  658.         /** Very simple list.
  659.          * Cannot use a List<T> because when indexing into a List<T> and T is
  660.          * a struct (which VO is) then the whole struct will be copied.
  661.          * When indexing into an array, that copy can be skipped.
  662.          */
  663.         internal class VOBuffer {
  664.             public VO[] buffer;
  665.             public int length;
  666.  
  667.             public void Clear () {
  668.                 length = 0;
  669.             }
  670.  
  671.             public VOBuffer (int n) {
  672.                 buffer = new VO[n];
  673.                 length = 0;
  674.             }
  675.  
  676.             public void Add (VO vo) {
  677.                 if (length >= buffer.Length) {
  678.                     var nbuffer = new VO[buffer.Length * 2];
  679.                     buffer.CopyTo(nbuffer, 0);
  680.                     buffer = nbuffer;
  681.                 }
  682.                 buffer[length++] = vo;
  683.             }
  684.         }
  685.  
  686.         bool InAngleRange (float a, float b, float x) {
  687.             if (a > b) return x >= a || x <= b;
  688.             else return x >= a && x <= b;
  689.         }
  690.  
  691.         int horizonSide = 0;
  692.         float horizonBias = 0;
  693.         float horizonMinAngle = 0;
  694.         float horizonMaxAngle = 0;
  695.  
  696.         class FloatComparer : IComparer<float> {
  697.             public int Compare (float a, float b) {
  698.                 if (a == b) return 0;
  699.                 return a < b ? -1 : 1;
  700.             }
  701.         }
  702.  
  703.         /** Required to avoid allocations on Mono.
  704.          * The default comparator for floats will box the floats and thus cause allocations.
  705.          */
  706.         static readonly FloatComparer floatComparer = new FloatComparer();
  707.  
  708.         /** Inspired by StarCraft 2's avoidance of locked units.
  709.          * \see http://www.gdcvault.com/play/1014514/AI-Navigation-It-s-Not
  710.          */
  711.         internal void CalculateHorizonAvoidancePhase1 (Pathfinding.RVO.Simulator.WorkerContext context) {
  712.             if (locked || manuallyControlled) {
  713.                 horizonSide = 0;
  714.                 horizonBias = 0;
  715.                 return;
  716.             }
  717.  
  718.             float minAngle = 0;
  719.             float maxAngle = 0;
  720.  
  721.             float desiredAngle = Mathf.Atan2(desiredVelocity.y, desiredVelocity.x);
  722.  
  723.             if (context.horizonAngleBuffer.Length < neighbours.Count*2) {
  724.                 context.horizonAngleBuffer = new float[neighbours.Count*2];
  725.                 context.horizonEventBuffer = new int[neighbours.Count*2];
  726.             }
  727.  
  728.             float[] angles = context.horizonAngleBuffer;
  729.             int[] deltas = context.horizonEventBuffer;
  730.             int eventCount = 0;
  731.  
  732.             int inside = 0;
  733.  
  734.             float targetDistance = desiredTargetPointInVelocitySpace.magnitude;
  735.            
  736.             for (int i = 0; i < neighbours.Count; i++) {
  737.                 var other = neighbours[i];
  738.                 // Ignore agents we don't care about
  739.                 if (other == this) continue;
  740.                 if (!other.locked && !other.manuallyControlled) continue;
  741.  
  742.  
  743.                 var relativePosition = other.position - position;
  744.                 float dist = relativePosition.magnitude;
  745.  
  746.                 float angle = Mathf.Atan2(relativePosition.y, relativePosition.x) - desiredAngle;
  747.                 float deltaAngle;
  748.  
  749.                 if (dist < radius + other.radius) {
  750.                     // Collision
  751.                     deltaAngle = Mathf.PI * 0.49f;
  752.                 } else if (dist < targetDistance + other.radius && dist > targetDistance - other.radius) {
  753.                     // Other agent may overlap destination, ignore it (we don't want to just move around that agent in circles forever)
  754.                     continue;
  755.                 } else {
  756.                     const float AngleMargin = Mathf.PI / 180f;
  757.                     deltaAngle = Mathf.Asin((radius + other.radius)/dist) + AngleMargin;
  758.                 }
  759.  
  760.                 float aMin = Mathf.Deg2Rad*Mathf.DeltaAngle(0, Mathf.Rad2Deg*(angle - deltaAngle));
  761.                 float aMax = aMin + Mathf.Deg2Rad*Mathf.DeltaAngle(aMin*Mathf.Rad2Deg, Mathf.Rad2Deg*(angle + deltaAngle));
  762.                 //Debug.Log(aMin*Mathf.Rad2Deg + " " + aMax*Mathf.Rad2Deg + " : " + deltaAngle*Mathf.Rad2Deg);
  763.                 //Draw.Debug.CircleXZ(FromXZ(position), radius*2 + i * 0.01f, Color.black, (desiredAngle + aMin), (desiredAngle + aMax));
  764.                 if (aMin < 0 && aMax > 0) inside++;
  765.  
  766.                 angles[eventCount] = aMin;
  767.                 deltas[eventCount] = 1;
  768.                 eventCount++;
  769.                 angles[eventCount] = aMax;
  770.                 deltas[eventCount] = -1;
  771.                 eventCount++;
  772.             }
  773.  
  774.             // If no angle range include angle 0 then we are already done
  775.             if (inside == 0) {
  776.                 horizonSide = 0;
  777.                 horizonBias = 0;
  778.                 return;
  779.             }
  780.  
  781.             // Sort the events by their angle in ascending order
  782.             System.Array.Sort(angles, deltas, 0, eventCount, floatComparer);
  783.  
  784.             // Find the first index for which the angle is positive
  785.             int firstPositiveIndex = 0;
  786.             for (; firstPositiveIndex < eventCount; firstPositiveIndex++) if (angles[firstPositiveIndex] > 0) break;
  787.  
  788.             // Walk in the positive direction from angle 0 until the end of the group of angle ranges that include angle 0
  789.             int tmpInside = inside;
  790.             int tmpIndex = firstPositiveIndex;
  791.             for (; tmpIndex < eventCount; tmpIndex++) {
  792.                 tmpInside += deltas[tmpIndex];
  793.                 if (tmpInside == 0) break;
  794.             }
  795.             maxAngle = tmpIndex == eventCount ? Mathf.PI : angles[tmpIndex];
  796.  
  797.             // Walk in the negative direction from angle 0 until the end of the group of angle ranges that include angle 0
  798.             tmpInside = inside;
  799.             tmpIndex = firstPositiveIndex - 1;
  800.             for (; tmpIndex >= 0; tmpIndex--) {
  801.                 tmpInside -= deltas[tmpIndex];
  802.                 if (tmpInside == 0) break;
  803.             }
  804.             minAngle = tmpIndex == -1 ? -Mathf.PI : angles[tmpIndex];
  805.  
  806.             horizonBias = -(minAngle + maxAngle);
  807.  
  808.             if (horizonSide == 0) horizonSide = 2;
  809.             //else horizonBias = Mathf.PI * horizonSide;
  810.  
  811.             horizonMinAngle = minAngle + desiredAngle;
  812.             horizonMaxAngle = maxAngle + desiredAngle;
  813.         }
  814.  
  815.         internal void CalculateHorizonAvoidancePhase2 (Pathfinding.RVO.Simulator.WorkerContext context) {
  816.             // Note: Assumes this code is run synchronous (i.e not included in the double buffering part)
  817.             offsetVelocity = (position - Position) / simulator.DeltaTime;
  818.  
  819.             if (locked || manuallyControlled || horizonSide == 0) {
  820.                 return;
  821.             }
  822.  
  823.             if (horizonSide == 2) {
  824.                 float sum = 0;
  825.                 for (int i = 0; i < neighbours.Count; i++) {
  826.                     var other = neighbours[i];
  827.                     sum += other.horizonBias;
  828.                 }
  829.                 sum += horizonBias;
  830.  
  831.                 horizonSide = sum < 0 ? -1 : 1;
  832.             }
  833.  
  834.             float bestAngle = horizonSide < 0 ? horizonMinAngle : horizonMaxAngle;
  835.             desiredVelocity = desiredVelocity.magnitude * new Vector2(Mathf.Cos(bestAngle), Mathf.Sin(bestAngle));
  836.             desiredTargetPointInVelocitySpace = desiredVelocity.normalized * desiredTargetPointInVelocitySpace.magnitude;
  837.  
  838.             //Debug.DrawLine(FromXZ(position), FromXZ(position + desiredVelocity.normalized), Color.yellow);
  839.         }
  840.  
  841.         Vector2 offsetVelocity;
  842.         float collisionStrength = 0.25f; // in the range [0,1]
  843.         internal void CalculateHardCollisions () {
  844.             for (int i = 0; i < neighbours.Count; i++) {
  845.                 var dir = position - neighbours[i].position;
  846.                 var dirSqrLength = dir.sqrMagnitude;
  847.                 if (dirSqrLength < Sqr(neighbours[i].radius + radius) && dirSqrLength > 0.00000001f) {
  848.                     // Collision
  849.                     var offset = dir * (1.0f / Mathf.Sqrt(dirSqrLength) * ((neighbours[i].radius + radius) - dir.magnitude) * 0.5f * collisionStrength);
  850.                     position += offset;
  851.                     if ((neighbours[i].collidesWith & layer) != 0) neighbours[i].position -= offset;
  852.                 }
  853.             }
  854.         }
  855.  
  856.         internal void CalculateVelocity (Pathfinding.RVO.Simulator.WorkerContext context) {
  857.             if (manuallyControlled) {
  858.                 return;
  859.             }
  860.  
  861.             if (locked) {
  862.                 calculatedSpeed = 0;
  863.                 calculatedTargetPoint = position;
  864.                 return;
  865.             }
  866.  
  867.             // Buffer which will be filled up with velocity obstacles (VOs)
  868.             var vos = context.vos;
  869.             vos.Clear();
  870.  
  871.             GenerateObstacleVOs(vos);
  872.             GenerateNeighbourAgentVOs(vos);
  873.  
  874.             bool insideAnyVO = BiasDesiredVelocity(vos, ref desiredVelocity, ref desiredTargetPointInVelocitySpace, simulator.symmetryBreakingBias);
  875.  
  876.             if (!insideAnyVO && offsetVelocity == Vector2.zero) {
  877.                 // Desired velocity can be used directly since it was not inside any velocity obstacle.
  878.                 // No need to run optimizer because this will be the global minima.
  879.                 // This is also a special case in which we can set the
  880.                 // calculated target point to the desired target point
  881.                 // instead of calculating a point based on a calculated velocity
  882.                 // which is an important difference when the agent is very close
  883.                 // to the target point
  884.                 // TODO: Not actually guaranteed to be global minima if desiredTargetPointInVelocitySpace.magnitude < desiredSpeed
  885.                 // maybe do something different here?
  886.                 calculatedTargetPoint = desiredTargetPointInVelocitySpace + position;
  887.                 calculatedSpeed = desiredSpeed;
  888.                 if (DebugDraw) Draw.Debug.CrossXZ(FromXZ(calculatedTargetPoint), Color.white);
  889.                 return;
  890.             }
  891.  
  892.             Vector2 result = Vector2.zero;
  893.  
  894.             result = GradientDescent(vos, currentVelocity, desiredVelocity);
  895.             result += offsetVelocity;
  896.  
  897.             if (DebugDraw) Draw.Debug.CrossXZ(FromXZ(result+position), Color.white);
  898.             //Debug.DrawRay (To3D (position), To3D (result));
  899.  
  900.             calculatedTargetPoint = position + result;
  901.             calculatedSpeed = Mathf.Min(result.magnitude, maxSpeed);
  902.         }
  903.  
  904.         static Color Rainbow (float v) {
  905.             Color c = new Color(v, 0, 0);
  906.  
  907.             if (c.r > 1) { c.g = c.r - 1; c.r = 1; }
  908.             if (c.g > 1) { c.b = c.g - 1; c.g = 1; }
  909.             return c;
  910.         }
  911.  
  912.         void GenerateObstacleVOs (VOBuffer vos) {
  913.             var range = maxSpeed * obstacleTimeHorizon;
  914.  
  915.             // Iterate through all obstacles that we might need to avoid
  916.             for (int i = 0; i < simulator.obstacles.Count; i++) {
  917.                 var obstacle = simulator.obstacles[i];
  918.                 var vertex = obstacle;
  919.                 // Iterate through all edges (defined by vertex and vertex.dir) in the obstacle
  920.                 do {
  921.                     // Ignore the edge if the agent should not collide with it
  922.                     if (vertex.ignore || (vertex.layer & collidesWith) == 0) {
  923.                         vertex = vertex.next;
  924.                         continue;
  925.                     }
  926.  
  927.                     // Start and end points of the current segment
  928.                     float elevation1, elevation2;
  929.                     var p1 = To2D(vertex.position, out elevation1);
  930.                     var p2 = To2D(vertex.next.position, out elevation2);
  931.  
  932.                     Vector2 dir = (p2 - p1).normalized;
  933.  
  934.                     // Signed distance from the line (not segment, lines are infinite)
  935.                     // TODO: Can be optimized
  936.                     float dist = VO.SignedDistanceFromLine(p1, dir, position);
  937.  
  938.                     if (dist >= -0.01f && dist < range) {
  939.                         float factorAlongSegment = Vector2.Dot(position - p1, p2 - p1) / (p2 - p1).sqrMagnitude;
  940.  
  941.                         // Calculate the elevation (y) coordinate of the point on the segment closest to the agent
  942.                         var segmentY = Mathf.Lerp(elevation1, elevation2, factorAlongSegment);
  943.  
  944.                         // Calculate distance from the segment (not line)
  945.                         var sqrDistToSegment = (Vector2.Lerp(p1, p2, factorAlongSegment) - position).sqrMagnitude;
  946.  
  947.                         // Ignore the segment if it is too far away
  948.                         // or the agent is too high up (or too far down) on the elevation axis (usually y axis) to avoid it.
  949.                         // If the XY plane is used then all elevation checks are disabled
  950.                         if (sqrDistToSegment < range*range && (simulator.movementPlane == MovementPlane.XY || (elevationCoordinate <= segmentY + vertex.height && elevationCoordinate+height >= segmentY))) {
  951.                             vos.Add(VO.SegmentObstacle(p2 - position, p1 - position, Vector2.zero, radius * 0.01f, 1f / ObstacleTimeHorizon, 1f / simulator.DeltaTime));
  952.                         }
  953.                     }
  954.  
  955.                     vertex = vertex.next;
  956.                 } while (vertex != obstacle && vertex != null && vertex.next != null);
  957.             }
  958.         }
  959.  
  960.         void GenerateNeighbourAgentVOs (VOBuffer vos) {
  961.             float inverseAgentTimeHorizon = 1.0f/agentTimeHorizon;
  962.  
  963.             // The RVO algorithm assumes we will continue to
  964.             // move in roughly the same direction
  965.             Vector2 optimalVelocity = currentVelocity;
  966.  
  967.             for (int o = 0; o < neighbours.Count; o++) {
  968.                 Agent other = neighbours[o];
  969.  
  970.                 // Don't avoid ourselves
  971.                 if (other == this)
  972.                     continue;
  973.  
  974.                 float totalRadius = radius + other.radius;
  975.  
  976.                 // Describes a circle on the border of the VO
  977.                 Vector2 voBoundingOrigin = other.position - position;
  978.  
  979.                 float avoidanceStrength;
  980.                 if (other.locked || other.manuallyControlled) {
  981.                     avoidanceStrength = 1;
  982.                 } else if (other.Priority > 0.00001f || Priority > 0.00001f) {
  983.                     avoidanceStrength = other.Priority / (Priority + other.Priority);
  984.                 } else {
  985.                     // Both this agent's priority and the other agent's priority is zero or negative
  986.                     // Assume they have the same priority
  987.                     avoidanceStrength = 0.5f;
  988.                 }
  989.  
  990.                 // We assume that the other agent will continue to move with roughly the same velocity if the priorities for the agents are similar.
  991.                 // If the other agent has a higher priority than this agent (avoidanceStrength > 0.5) then we will assume it will move more along its
  992.                 // desired velocity. This will have the effect of other agents trying to clear a path for where a high priority agent wants to go.
  993.                 // 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.
  994.                 Vector2 otherOptimalVelocity = Vector2.Lerp(other.currentVelocity, other.desiredVelocity, 2*avoidanceStrength - 1);
  995.  
  996.                 if (other.FlowFollowingStrength > 0) {
  997.                     var relativePosition = position - other.position;
  998.                     otherOptimalVelocity -= relativePosition.normalized * (FlowFollowingStrength * Mathf.Max(0, Vector2.Dot(otherOptimalVelocity, relativePosition.normalized)));
  999.                 }
  1000.  
  1001.                 var voCenter = Vector2.Lerp(optimalVelocity, otherOptimalVelocity, avoidanceStrength);
  1002.  
  1003.                 vos.Add(new VO(voBoundingOrigin, voCenter, totalRadius, inverseAgentTimeHorizon, 1 / simulator.DeltaTime));
  1004.                 if (DebugDraw)
  1005.                     DrawVO(position + voBoundingOrigin * inverseAgentTimeHorizon + voCenter, totalRadius * inverseAgentTimeHorizon, position + voCenter);
  1006.             }
  1007.         }
  1008.  
  1009.         Vector2 GradientDescent (VOBuffer vos, Vector2 sampleAround1, Vector2 sampleAround2) {
  1010. #if !AstarRelease
  1011.             if (DebugDraw) PlotHeightmap(vos);
  1012. #endif
  1013.  
  1014.             float score1;
  1015.             var minima1 = Trace(vos, sampleAround1, out score1);
  1016.             if (DebugDraw) Draw.Debug.CrossXZ(FromXZ(minima1 + position), Color.yellow, 0.5f);
  1017.  
  1018.             // Can be uncommented for higher quality local avoidance
  1019.             // for ( int i = 0; i < 3; i++ ) {
  1020.             //  Vector2 p = desiredVelocity + new Vector2(Mathf.Cos(Mathf.PI*2*(i/3.0f)), Mathf.Sin(Mathf.PI*2*(i/3.0f)));
  1021.             //  float score;Vector2 res = Trace ( vos, p, velocity.magnitude*simulator.qualityCutoff, out score );
  1022.             //
  1023.             //  if ( score < best ) {
  1024.             //      result = res;
  1025.             //      best = score;
  1026.             //  }
  1027.             // }
  1028.  
  1029.             float score2;
  1030.             Vector2 minima2 = Trace(vos, sampleAround2, out score2);
  1031.             if (DebugDraw) Draw.Debug.CrossXZ(FromXZ(minima2 + position), Color.magenta, 0.5f);
  1032.  
  1033.             return score1 < score2 ? minima1 : minima2;
  1034.         }
  1035.  
  1036. #if !AstarRelease
  1037.         void PlotHeightmap (VOBuffer vos) {
  1038.             const int PlotWidth = 200;
  1039.             const float WorldPlotWidth = 15;
  1040.  
  1041.             var heightmap = new float[PlotWidth, PlotWidth];
  1042.  
  1043.             for (int x = 0; x < PlotWidth; x++) {
  1044.                 for (int y = 0; y < PlotWidth; y++) {
  1045.                     Vector2 p = new Vector2(((x / (float)PlotWidth) - 0.5f), (y / (float)PlotWidth) - 0.5f) * WorldPlotWidth;
  1046.  
  1047.                     float weight;
  1048.                     EvaluateGradient(vos, p, out weight);
  1049.  
  1050.                     heightmap[x, y] = weight;
  1051.                 }
  1052.             }
  1053.  
  1054.             float mn = float.PositiveInfinity;
  1055.             float mx = float.NegativeInfinity;
  1056.             for (int x = 0; x < PlotWidth; x++) {
  1057.                 for (int y = 0; y < PlotWidth; y++) {
  1058.                     mn = Mathf.Min(mn, heightmap[x, y]);
  1059.                     mx = Mathf.Max(mx, heightmap[x, y]);
  1060.                 }
  1061.             }
  1062.  
  1063.             const float PlotHeight = 2;
  1064.             float scaleFactor;
  1065.             if (mx != mn) {
  1066.                 scaleFactor = 1/(mx - mn);
  1067.             } else {
  1068.                 scaleFactor = 1;
  1069.             }
  1070.  
  1071.             for (int x = 0; x < PlotWidth; x++) {
  1072.                 for (int y = 0; y < PlotWidth; y++) {
  1073.                     heightmap[x, y] = (heightmap[x, y] - mn) * scaleFactor;
  1074.                 }
  1075.             }
  1076.  
  1077.             RVODebugger.SetHeightmap(heightmap, FromXZ(position), WorldPlotWidth, PlotHeight);
  1078.         }
  1079. #endif
  1080.  
  1081.         /** Bias towards the right side of agents.
  1082.          * Rotate desiredVelocity at most [value] number of radians. 1 radian ≈ 57°
  1083.          * This breaks up symmetries.
  1084.          *
  1085.          * The desired velocity will only be rotated if it is inside a velocity obstacle (VO).
  1086.          * If it is inside one, it will not be rotated further than to the edge of it
  1087.          *
  1088.          * The targetPointInVelocitySpace will be rotated by the same amount as the desired velocity
  1089.          *
  1090.          * \returns True if the desired velocity was inside any VO
  1091.          */
  1092.         static bool BiasDesiredVelocity (VOBuffer vos, ref Vector2 desiredVelocity, ref Vector2 targetPointInVelocitySpace, float maxBiasRadians) {
  1093.             var desiredVelocityMagn = desiredVelocity.magnitude;
  1094.             var maxValue = 0f;
  1095.  
  1096.             for (int i = 0; i < vos.length; i++) {
  1097.                 float value;
  1098.                 // The value is approximately the distance to the edge of the VO
  1099.                 // so taking the maximum will give us the distance to the edge of the VO
  1100.                 // which the desired velocity is furthest inside
  1101.                 vos.buffer[i].Gradient(desiredVelocity, out value);
  1102.                 maxValue = Mathf.Max(maxValue, value);
  1103.             }
  1104.  
  1105.             // Check if the agent was inside any VO
  1106.             var inside = maxValue > 0;
  1107.  
  1108.             // Avoid division by zero below
  1109.             if (desiredVelocityMagn < 0.001f) {
  1110.                 return inside;
  1111.             }
  1112.  
  1113.             // Rotate the desired velocity clockwise (to the right) at most maxBiasRadians number of radians
  1114.             // Assuming maxBiasRadians is small, we can just move it instead and it will give approximately the same effect
  1115.             // See https://en.wikipedia.org/wiki/Small-angle_approximation
  1116.             var angle = Mathf.Min(maxBiasRadians, maxValue / desiredVelocityMagn);
  1117.             desiredVelocity += new Vector2(desiredVelocity.y, -desiredVelocity.x) * angle;
  1118.             targetPointInVelocitySpace += new Vector2(targetPointInVelocitySpace.y, -targetPointInVelocitySpace.x) * angle;
  1119.             return inside;
  1120.         }
  1121.  
  1122.         /** Evaluate gradient and value of the cost function at velocity p */
  1123.         Vector2 EvaluateGradient (VOBuffer vos, Vector2 p, out float value) {
  1124.             Vector2 gradient = Vector2.zero;
  1125.  
  1126.             value = 0;
  1127.  
  1128.             // Avoid other agents
  1129.             for (int i = 0; i < vos.length; i++) {
  1130.                 float w;
  1131.                 var grad = vos.buffer[i].ScaledGradient(p, out w);
  1132.                 if (w > value) {
  1133.                     value = w;
  1134.                     gradient = grad;
  1135.                 }
  1136.             }
  1137.  
  1138.             // Move closer to the desired velocity
  1139.             var dirToDesiredVelocity = desiredVelocity - p;
  1140.             var distToDesiredVelocity = dirToDesiredVelocity.magnitude;
  1141.             if (distToDesiredVelocity > 0.0001f) {
  1142.                 gradient += dirToDesiredVelocity * (DesiredVelocityWeight/distToDesiredVelocity);
  1143.                 value += distToDesiredVelocity * DesiredVelocityWeight;
  1144.             }
  1145.  
  1146.             // Prefer speeds lower or equal to the desired speed
  1147.             // and avoid speeds greater than the max speed
  1148.             var sqrSpeed = p.sqrMagnitude;
  1149.             if (sqrSpeed > desiredSpeed*desiredSpeed) {
  1150.                 var speed = Mathf.Sqrt(sqrSpeed);
  1151.  
  1152.                 if (speed > maxSpeed) {
  1153.                     const float MaxSpeedWeight = 3;
  1154.                     value += MaxSpeedWeight * (speed - maxSpeed);
  1155.                     gradient -= MaxSpeedWeight * (p/speed);
  1156.                 }
  1157.  
  1158.                 // Scale needs to be strictly greater than DesiredVelocityWeight
  1159.                 // otherwise the agent will not prefer the desired speed over
  1160.                 // the maximum speed
  1161.                 float scale = 2*DesiredVelocityWeight;
  1162.                 value += scale * (speed - desiredSpeed);
  1163.                 gradient -= scale * (p/speed);
  1164.             }
  1165.  
  1166.             return gradient;
  1167.         }
  1168.  
  1169.         /** Traces the vector field constructed out of the velocity obstacles.
  1170.          * Returns the position which gives the minimum score (approximately).
  1171.          *
  1172.          * \see https://en.wikipedia.org/wiki/Gradient_descent
  1173.          */
  1174.         Vector2 Trace (VOBuffer vos, Vector2 p, out float score) {
  1175.             // Pick a reasonable initial step size
  1176.             float stepSize = Mathf.Max(radius, 0.2f * desiredSpeed);
  1177.  
  1178.             float bestScore = float.PositiveInfinity;
  1179.             Vector2 bestP = p;
  1180.  
  1181.             // TODO: Add momentum to speed up convergence?
  1182.  
  1183.             const int MaxIterations = 50;
  1184.  
  1185.             for (int s = 0; s < MaxIterations; s++) {
  1186.                 float step = 1.0f - (s/(float)MaxIterations);
  1187.                 step = Sqr(step) * stepSize;
  1188.  
  1189.                 float value;
  1190.                 var gradient = EvaluateGradient(vos, p, out value);
  1191.  
  1192.                 if (value < bestScore) {
  1193.                     bestScore = value;
  1194.                     bestP = p;
  1195.                 }
  1196.  
  1197.                 // TODO: Add cutoff for performance
  1198.  
  1199.                 gradient.Normalize();
  1200.  
  1201.                 gradient *= step;
  1202.                 Vector2 prev = p;
  1203.                 p += gradient;
  1204.  
  1205.                 if (DebugDraw) Debug.DrawLine(FromXZ(prev + position), FromXZ(p + position), Rainbow(s*0.1f) * new Color(1, 1, 1, 1f));
  1206.             }
  1207.  
  1208.             score = bestScore;
  1209.             return bestP;
  1210.         }
  1211.     }
  1212. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement