Esempio n. 1
0
        /** Bias towards the right side of agents.
         * Rotate desiredVelocity at most [value] number of radians. 1 radian ≈ 57°
         * This breaks up symmetries.
         *
         * The desired velocity will only be rotated if it is inside a velocity obstacle (VO).
         * If it is inside one, it will not be rotated further than to the edge of it
         *
         * The targetPointInVelocitySpace will be rotated by the same amount as the desired velocity
         *
         * \returns True if the desired velocity was inside any VO
         */
        static bool BiasDesiredVelocity(VOBuffer vos, ref TSVector2 desiredVelocity, ref TSVector2 targetPointInVelocitySpace, FP maxBiasRadians)
        {
            var desiredVelocityMagn = desiredVelocity.magnitude;
            FP  maxValue            = FP.Zero;

            for (int i = 0; i < vos.length; i++)
            {
                FP value;
                // The value is approximately the distance to the edge of the VO
                // so taking the maximum will give us the distance to the edge of the VO
                // which the desired velocity is furthest inside
                vos.buffer[i].Gradient(desiredVelocity, out value);
                maxValue = TSMath.Max(maxValue, value);
            }

            // Check if the agent was inside any VO
            var inside = maxValue > 0;

            // Avoid division by zero below
            if (desiredVelocityMagn < FP.One / 1000)
            {
                return(inside);
            }

            // Rotate the desired velocity clockwise (to the right) at most maxBiasRadians number of radians
            // Assuming maxBiasRadians is small, we can just move it instead and it will give approximately the same effect
            // See https://en.wikipedia.org/wiki/Small-angle_approximation
            var angle = TSMath.Min(maxBiasRadians, maxValue / desiredVelocityMagn);

            desiredVelocity            += new TSVector2(desiredVelocity.y, -desiredVelocity.x) * angle;
            targetPointInVelocitySpace += new TSVector2(targetPointInVelocitySpace.y, -targetPointInVelocitySpace.x) * angle;
            return(inside);
        }
Esempio n. 2
0
        Vector2 GradientDescent(VOBuffer vos, Vector2 sampleAround1, Vector2 sampleAround2)
        {
            float score1;
            var   minima1 = Trace(vos, sampleAround1, out score1);

            if (DebugDraw)
            {
                DrawCross(minima1 + position, Color.yellow, 0.5f);
            }

            // Can be uncommented for higher quality local avoidance
            // for ( int i = 0; i < 3; i++ ) {
            //	Vector2 p = desiredVelocity + new Vector2(Mathf.Cos(Mathf.PI*2*(i/3.0f)), Mathf.Sin(Mathf.PI*2*(i/3.0f)));
            //	float score;Vector2 res = Trace ( vos, p, velocity.magnitude*simulator.qualityCutoff, out score );
            //
            //	if ( score < best ) {
            //		result = res;
            //		best = score;
            //	}
            // }

            float   score2;
            Vector2 minima2 = Trace(vos, sampleAround2, out score2);

            if (DebugDraw)
            {
                DrawCross(minima2 + position, Color.magenta, 0.5f);
            }

            return(score1 < score2 ? minima1 : minima2);
        }
Esempio n. 3
0
        TSVector2 GradientDescent(VOBuffer vos, TSVector2 sampleAround1, TSVector2 sampleAround2)
        {
            FP  score1;
            var minima1 = Trace(vos, sampleAround1, out score1);

            //  if (DebugDraw) Draw.Debug.CrossXZ(FromXZ(minima1 + position), Color.yellow, 0.5f);

            // Can be uncommented for higher quality local avoidance
            // for ( int i = 0; i < 3; i++ ) {
            //	TSVector2 p = desiredVelocity + new TSVector2(TSMath.Cos(TSMath.PI*2*(i/3)), TSMath.Sin(TSMath.PI*2*(i/3)));
            //	FP score;TSVector2 res = Trace ( vos, p, velocity.magnitude*simulator.qualityCutoff, out score );
            //
            //	if ( score < best ) {
            //		result = res;
            //		best = score;
            //	}
            // }

            FP        score2;
            TSVector2 minima2 = Trace(vos, sampleAround2, out score2);

            //  if (DebugDraw) Draw.Debug.CrossXZ(FromXZ(minima2 + position), Color.magenta, 0.5f);

            return(score1 < score2 ? minima1 : minima2);
        }
Esempio n. 4
0
        /** Evaluate gradient and value of the cost function at velocity p */
        TSVector2 EvaluateGradient(VOBuffer vos, TSVector2 p, out FP value)
        {
            TSVector2 gradient = TSVector2.zero;

            value = 0;

            // Avoid other agents
            for (int i = 0; i < vos.length; i++)
            {
                FP  w;
                var grad = vos.buffer[i].ScaledGradient(p, out w);
                if (w > value)
                {
                    value    = w;
                    gradient = grad;
                }
            }

            // Move closer to the desired velocity
            var dirToDesiredVelocity  = desiredVelocity - p;
            var distToDesiredVelocity = dirToDesiredVelocity.magnitude;

            if (distToDesiredVelocity > CustomMath.EPSILON)
            {
                gradient += dirToDesiredVelocity * (DesiredVelocityWeight / distToDesiredVelocity);
                value    += distToDesiredVelocity * DesiredVelocityWeight;
            }

            // Prefer speeds lower or equal to the desired speed
            // and avoid speeds greater than the max speed
            var sqrSpeed = p.LengthSquared();

            if (sqrSpeed > desiredSpeed * desiredSpeed)
            {
                var speed = TSMath.Sqrt(sqrSpeed);

                if (speed > maxSpeed)
                {
                    FP MaxSpeedWeight = 3;
                    value    += MaxSpeedWeight * (speed - maxSpeed);
                    gradient -= MaxSpeedWeight * (p / speed);
                }

                // Scale needs to be strictly greater than DesiredVelocityWeight
                // otherwise the agent will not prefer the desired speed over
                // the maximum speed
                FP scale = 2 * DesiredVelocityWeight;
                value    += scale * (speed - desiredSpeed);
                gradient -= scale * (p / speed);
            }

            return(gradient);
        }
Esempio n. 5
0
        void GenerateObstacleVOs(VOBuffer vos)
        {
            var range = maxSpeed * obstacleTimeHorizon;

            // Iterate through all obstacles that we might need to avoid
            for (int i = 0; i < simulator.obstacles.Count; i++)
            {
                var obstacle = simulator.obstacles[i];
                var vertex   = obstacle;
                // Iterate through all edges (defined by vertex and vertex.dir) in the obstacle
                do
                {
                    // Ignore the edge if the agent should not collide with it
                    if (vertex.ignore || (vertex.layer & collidesWith) == 0)
                    {
                        vertex = vertex.next;
                        continue;
                    }

                    // Start and end points of the current segment
                    float elevation1, elevation2;
                    var   p1 = To2D(vertex.position, out elevation1);
                    var   p2 = To2D(vertex.next.position, out elevation2);

                    Vector2 dir = (p2 - p1).normalized;

                    // Signed distance from the line (not segment, lines are infinite)
                    // TODO: Can be optimized
                    float dist = VO.SignedDistanceFromLine(p1, dir, position);

                    if (dist >= -0.01f && dist < range)
                    {
                        float factorAlongSegment = Vector2.Dot(position - p1, p2 - p1) / (p2 - p1).sqrMagnitude;

                        // Calculate the elevation (y) coordinate of the point on the segment closest to the agent
                        var segmentY = Mathf.Lerp(elevation1, elevation2, factorAlongSegment);

                        // Calculate distance from the segment (not line)
                        var sqrDistToSegment = (Vector2.Lerp(p1, p2, factorAlongSegment) - position).sqrMagnitude;

                        // Ignore the segment if it is too far away
                        // or the agent is too high up (or too far down) on the elevation axis (usually y axis) to avoid it.
                        // If the XY plane is used then all elevation checks are disabled
                        if (sqrDistToSegment < range * range && (simulator.movementPlane == MovementPlane.XY || (elevationCoordinate <= segmentY + vertex.height && elevationCoordinate + height >= segmentY)))
                        {
                            vos.Add(VO.SegmentObstacle(p2 - position, p1 - position, Vector2.zero, radius * 0.01f, 1f / ObstacleTimeHorizon, 1f / simulator.DeltaTime));
                        }
                    }

                    vertex = vertex.next;
                } while (vertex != obstacle && vertex != null && vertex.next != null);
            }
        }
Esempio n. 6
0
        /// <summary>
        /// Traces the vector field constructed out of the velocity obstacles.
        /// Returns the position which gives the minimum score (approximately).
        ///
        /// See: https://en.wikipedia.org/wiki/Gradient_descent
        /// </summary>
        Vector2 Trace(VOBuffer vos, Vector2 p, out float score)
        {
            // Pick a reasonable initial step size
            float stepSize = Mathf.Max(radius, 0.2f * desiredSpeed);

            float   bestScore = float.PositiveInfinity;
            Vector2 bestP     = p;

            // TODO: Add momentum to speed up convergence?

            const int MaxIterations = 50;

            for (int s = 0; s < MaxIterations; s++)
            {
                float step = 1.0f - (s / (float)MaxIterations);
                step = Sqr(step) * stepSize;

                float value;
                var   gradient = EvaluateGradient(vos, p, out value);

                if (value < bestScore)
                {
                    bestScore = value;
                    bestP     = p;
                }

                // TODO: Add cutoff for performance

                gradient.Normalize();

                gradient *= step;
                Vector2 prev = p;
                p += gradient;

                if (DebugDraw)
                {
                    Debug.DrawLine(FromXZ(prev + position), FromXZ(p + position), Rainbow(s * 0.1f) * new Color(1, 1, 1, 1f));
                }
            }

            score = bestScore;
            return(bestP);
        }
Esempio n. 7
0
        /** Traces the vector field constructed out of the velocity obstacles.
         * Returns the position which gives the minimum score (approximately).
         *
         * \see https://en.wikipedia.org/wiki/Gradient_descent
         */
        TSVector2 Trace(VOBuffer vos, TSVector2 p, out FP score)
        {
            // Pick a reasonable initial step size
            FP stepSize = TSMath.Max(radius, FP.One / 5 * desiredSpeed);

            FP        bestScore = FP.MaxValue;
            TSVector2 bestP     = p;

            // TODO: Add momentum to speed up convergence?

            const int MaxIterations = 50;

            for (int s = 0; s < MaxIterations; s++)
            {
                FP step = 1 - (s * FP.One / MaxIterations);
                step = Sqr(step) * stepSize;

                FP  value;
                var gradient = EvaluateGradient(vos, p, out value);

                if (value < bestScore)
                {
                    bestScore = value;
                    bestP     = p;
                }

                // TODO: Add cutoff for performance
                gradient.Normalize();
                gradient *= step;
                TSVector2 prev = p;
                p += gradient;

                // if (DebugDraw) Debug.DrawLine(FromXZ(prev + position), FromXZ(p + position), Rainbow(s * 0.1f) * new Color(1, 1, 1, 1f));
            }

            score = bestScore;
            return(bestP);
        }
Esempio n. 8
0
        //void GenerateObstacleVOs(VOBuffer vos)
        //{
        //    var range = maxSpeed * obstacleTimeHorizon;

        //    // Iterate through all obstacles that we might need to avoid
        //    for (int i = 0; i < simulator.obstacles.Count; i++)
        //    {
        //        var obstacle = simulator.obstacles[i];
        //        var vertex = obstacle;
        //        // Iterate through all edges (defined by vertex and vertex.dir) in the obstacle
        //        do
        //        {
        //            // Ignore the edge if the agent should not collide with it
        //            if (vertex.ignore || (vertex.layer & collidesWith) == 0)
        //            {
        //                vertex = vertex.next;
        //                continue;
        //            }

        //            // Start and end points of the current segment
        //            FP elevation1, elevation2;
        //            var p1 = To2D(vertex.position, out elevation1);
        //            var p2 = To2D(vertex.next.position, out elevation2);

        //            TSVector2 dir = (p2 - p1).normalized;

        //            // Signed distance from the line (not segment, lines are infinite)
        //            // TODO: Can be optimized
        //            FP dist = VO.SignedDistanceFromLine(p1, dir, position);

        //            if (dist >= -FP.One/100 && dist < range)
        //            {
        //                FP factorAlongSegment = TSVector2.Dot(position - p1, p2 - p1) / (p2 - p1).sqrMagnitude;

        //                // Calculate the elevation (y) coordinate of the point on the segment closest to the agent
        //                var segmentY = TSMath.Lerp(elevation1, elevation2, factorAlongSegment);

        //                // Calculate distance from the segment (not line)
        //                var sqrDistToSegment = (TSVector2.Lerp(p1, p2, factorAlongSegment) - position).sqrMagnitude;

        //                // Ignore the segment if it is too far away
        //                // or the agent is too high up (or too far down) on the elevation axis (usually y axis) to avoid it.
        //                // If the XY plane is used then all elevation checks are disabled
        //                if (sqrDistToSegment < range * range && (simulator.movementPlane == MovementPlane.XY || (elevationCoordinate <= segmentY + vertex.height && elevationCoordinate + height >= segmentY)))
        //                {
        //                    vos.Add(VO.SegmentObstacle(p2 - position, p1 - position, TSVector2.zero, radius * FP.One/100, 1 / ObstacleTimeHorizon, 1 / simulator.DeltaTime));
        //                }
        //            }

        //            vertex = vertex.next;
        //        } while (vertex != obstacle && vertex != null && vertex.next != null);
        //    }
        //}
#if  USING_RVO
        void GenerateNeighbourAgentVOs(VOBuffer vos, FP deltaTime)
        {
            FP inverseAgentTimeHorizon = 1 / agentTimeHorizon;

            // The RVO algorithm assumes we will continue to
            // move in roughly the same direction
            TSVector2 optimalVelocity = currentVelocity;
            int       count           = _behaviour.neighbours.Count;

            for (int o = 0; o < count; o++)
            {
                IAgentBehaviour other = _behaviour.neighbours[o];

                // Don't avoid ourselves
                if (other == this)
                {
                    continue;
                }

                // Interval along the y axis in which the agents overlap
                FP maxY = TSMath.Min(elevationCoordinate + height, other.OrcaAgent.elevationCoordinate + other.OrcaAgent.height);
                FP minY = TSMath.Max(elevationCoordinate, other.OrcaAgent.elevationCoordinate);

                // The agents cannot collide since they are on different y-levels
                if (maxY - minY < 0)
                {
                    continue;
                }

                FP totalRadius = radius + other.colliderRadius;

                // Describes a circle on the border of the VO
                TSVector2 voBoundingOrigin = CustomMath.TSVecToVec2(other.position - _behaviour.position);

                FP avoidanceStrength;
                if (other.OrcaAgent.locked || other.OrcaAgent.manuallyControlled)
                {
                    avoidanceStrength = 1;
                }
                else if (other.OrcaAgent.Priority > CustomMath.EPSILON || Priority > CustomMath.EPSILON)
                {
                    avoidanceStrength = other.OrcaAgent.Priority / (Priority + other.OrcaAgent.Priority);
                }
                else
                {
                    // Both this agent's priority and the other agent's priority is zero or negative
                    // Assume they have the same priority
                    avoidanceStrength = CustomMath.FPHalf;
                }

                // We assume that the other agent will continue to move with roughly the same velocity if the priorities for the agents are similar.
                // If the other agent has a higher priority than this agent (avoidanceStrength > 0.5) then we will assume it will move more along its
                // desired velocity. This will have the effect of other agents trying to clear a path for where a high priority agent wants to go.
                // 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.
                TSVector2 otherOptimalVelocity = TSVector2.Lerp(other.OrcaAgent.currentVelocity, other.OrcaAgent.desiredVelocity, 2 * avoidanceStrength - 1);

                var voCenter = TSVector2.Lerp(optimalVelocity, otherOptimalVelocity, avoidanceStrength);

                vos.Add(new VO(voBoundingOrigin, voCenter, totalRadius, inverseAgentTimeHorizon, 1 / deltaTime));
                if (DebugDraw)
                {
                    DrawVO(position + voBoundingOrigin * inverseAgentTimeHorizon + voCenter, totalRadius * inverseAgentTimeHorizon, position + voCenter);
                }
            }
        }
Esempio n. 9
0
        void GenerateNeighbourAgentVOs(VOBuffer vos)
        {
            float inverseAgentTimeHorizon = 1.0f / agentTimeHorizon;

            // The RVO algorithm assumes we will continue to
            // move in roughly the same direction
            Vector2 optimalVelocity = currentVelocity;

            for (int o = 0; o < neighbours.Count; o++)
            {
                Agent other = neighbours[o];

                // Don't avoid ourselves
                if (other == this)
                {
                    continue;
                }

                // Interval along the y axis in which the agents overlap
                float maxY = System.Math.Min(elevationCoordinate + height, other.elevationCoordinate + other.height);
                float minY = System.Math.Max(elevationCoordinate, other.elevationCoordinate);

                // The agents cannot collide since they are on different y-levels
                if (maxY - minY < 0)
                {
                    continue;
                }

                float totalRadius = radius + other.radius;

                // Describes a circle on the border of the VO
                Vector2 voBoundingOrigin = other.position - position;

                float avoidanceStrength;
                if (other.locked || other.manuallyControlled)
                {
                    avoidanceStrength = 1;
                }
                else if (other.Priority > 0.00001f || Priority > 0.00001f)
                {
                    avoidanceStrength = other.Priority / (Priority + other.Priority);
                }
                else
                {
                    // Both this agent's priority and the other agent's priority is zero or negative
                    // Assume they have the same priority
                    avoidanceStrength = 0.5f;
                }

                // We assume that the other agent will continue to move with roughly the same velocity if the priorities for the agents are similar.
                // If the other agent has a higher priority than this agent (avoidanceStrength > 0.5) then we will assume it will move more along its
                // desired velocity. This will have the effect of other agents trying to clear a path for where a high priority agent wants to go.
                // 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.
                Vector2 otherOptimalVelocity = Vector2.Lerp(other.currentVelocity, other.desiredVelocity, 2 * avoidanceStrength - 1);

                var voCenter = Vector2.Lerp(optimalVelocity, otherOptimalVelocity, avoidanceStrength);

                vos.Add(new VO(voBoundingOrigin, voCenter, totalRadius, inverseAgentTimeHorizon, 1 / simulator.DeltaTime));
                if (DebugDraw)
                {
                    DrawVO(position + voBoundingOrigin * inverseAgentTimeHorizon + voCenter, totalRadius * inverseAgentTimeHorizon, position + voCenter);
                }
            }
        }