Acceleration + Deceleration using RVOExampleAgent.cs

Hi,

I have been looking to replace my current pathfinding solution with this. I had bought it long ago but never used it. I have gotten a lot what I needed working with very little effort and the last thing I need to evaluate is to have agents accelerate and decelerate. Currently one of my agents is too fast, so when it reaches its destination, it overshoots the target and then comes back, sort of like a pendulum until finally coming to rest.

I have looked at many of the other examples including AIPath and RichAI. Rich AI seems to do this but I haven’t had any luck in trying to integrate such behavior properly. No matter what I do there is always an issue or the agent going wonky.

Is it possible for you to give us some example code of how to implement this to the RVOExampleAgent? I am using this as the basis for my agent.

Thank you.

Hi

What I did for the RichAI component was to solve an equation.

When it detects that the next point in the path is the target point of the path, it will enter a special code block which calculates the acceleration differently.

What it does is that it answers the question of “assuming we will have a linear acceleration (like a = a0 + q*t where t = time) for X seconds (where X is the specified slowdown time) and after X seconds we want to be right at the end point and we want to have a velocity of zero (stand still), what should our acceleration be?”.

In math this becomes

diff = target point - current position
t = slowdownTime
diff = vt + at^2/2 + qt^3/6 // Make sure we reach the target after t seconds
0 = v + at + qt^2/2 // Make sure we have a velocity of zero when we reach the target
solve for a

Using wolfram alpha we get
http://www.wolframalpha.com/input/?i=solve+d+%3D+vt+%2B+at%5E2%2F2+%2B+qt%5E3%2F6%3B+0+%3D+v+%2B+at+%2B+q*t%5E2%2F2+for+a%2C+q

a = (6*diff - 4*slowdownTime*velocity)/(slowdownTime*slowdownTime);

Which we can use to modify our current velocity and then move the agent.

Note that the RVO system adds a slight latency, you can minimize it by turning off double buffering and increasing the simulation fps.

Wow, what a fast response. I heard your support was excellent, but I wasn’t aware to this extent.

Now I understand the math much better as well as the equation. However, I this is the same equation I found in the RichAI that I had tried. It causes the agent to slowly start and speed up when it gets to the final point in the path. If there is only one point in the path, it will do as before, overshooting. My implementation is very wrong. Here is the direction vector I calculate in Update and send to the RVOController - it’s a bit of a mess as I have been prototyping it and trying to get it to work before cleaning it up:

Vector3 CalculateRVODir()
{

    Vector3 dir = Vector3.zero;

    Vector3 pos = transform.position;

    if (vectorPath != null && vectorPath.Count != 0)
    {
        Vector3 waypoint = vectorPath[wp];
        waypoint.y = pos.y;

        while ((pos - waypoint).sqrMagnitude < moveNextDist * moveNextDist && wp != vectorPath.Count - 1)
        {
            wp++;
            waypoint = vectorPath[wp];
            waypoint.y = pos.y;
        }

        if (vectorPath.Count == 1)
        {
            vectorPath.Insert(0, pos);
        }

        if (currentWaypointIndex >= vectorPath.Count) { currentWaypointIndex = vectorPath.Count - 1; }

        if (currentWaypointIndex <= 1) currentWaypointIndex = 1;

        while (true)
        {
            if (currentWaypointIndex < vectorPath.Count - 1)
            {
                //There is a "next path segment"
                float dist = XZSqrMagnitude(vectorPath[currentWaypointIndex], pos);
                //Mathfx.DistancePointSegmentStrict (vPath[currentWaypointIndex+1],vPath[currentWaypointIndex+2],currentPosition);
                if (dist < pickNextWaypointDist * pickNextWaypointDist)
                {
                    lastFoundWaypointPosition = pos;
                    lastFoundWaypointTime = Time.time;
                    currentWaypointIndex++;
                }
                else
                {
                    break;
                }
            }
            else
            {
                break;
            }
        }

        Vector3 wayPointDir = vectorPath[currentWaypointIndex] - vectorPath[currentWaypointIndex - 1];
        Vector3 targetPosition = CalculateTargetPoint(pos, vectorPath[currentWaypointIndex - 1], vectorPath[currentWaypointIndex]);

        wayPointDir = targetPosition - pos;
        wayPointDir.y = 0;

        Vector3 accel = Vector3.one;

        //Get the last point of the path
        if (vectorPath.Count - currentWaypointIndex == 1)
        {
            //Clamp to avoid divide by zero
            if (slowdownTime < 0.001f)
            {
                slowdownTime = 0.001f;
            }

            if (preciseSlowdown)
            {
                //{ t = slowdownTime
                //{ diff = vt + at^2/2 + qt^3/6
                //{ 0 = at + qt^2/2
                //{ solve for a
                accel = (6 * dir - 4 * slowdownTime * controller.velocity) / (slowdownTime * slowdownTime);
            }
            else
            {
                accel = 2 * (dir - slowdownTime * controller.velocity) / (slowdownTime * slowdownTime);
            }
            Debug.Log("Decelerate!" + vectorPath.Count + " " + currentWaypointIndex);
        }

        float targetDist = wayPointDir.magnitude;

        float slowdown = Mathf.Clamp01(targetDist / slowdownDistance);

        dir = waypoint - pos;
        float magn = dir.magnitude;
        if (magn > 0)
        {
            float newMagn = Mathf.Min(magn, controller.maxSpeed);
            dir *= newMagn / magn;
        }

        if (currentWaypointIndex == vectorPath.Count - 1 && targetDist <= endReachedDistance)
        {
            if (!targetReached) { targetReached = true; OnTargetReached(); }

            //Send a move request, this ensures gravity is applied
            return Vector3.zero;
        }

        float dot = Vector3.Dot(dir.normalized, tr.forward);
        float speed = controller.maxSpeed * Mathf.Max(dot, minMoveScale) * slowdown;

        if (Time.deltaTime > 0)
        {
            speed = Mathf.Clamp(speed, 0, targetDist / (Time.deltaTime * 2));
        }

        //This keeps us from zig zagging when traveling at higher velocities
        dir = Vector3.ClampMagnitude(waypoint - pos, 1.0f) * accel.magnitude;
    }
    return dir;
}

Could you possibly steer me in the direction where I am going wrong here? I normally have on the last line the dir being multiplied by the speed value rather than the accel.magnitude. Thank you so much.

Edit: just in case it might be easier to read: http://hastebin.com/imexacojox.avrasm

Hi

Note that the RichAI equation calculates an acceleration not a velocity. If you just want a velocity you can probably use something simpler like

velocity = Vector3.ClampMagnitude (velocity, distanceToTargetPoint);

The acceleration that is calculated may be directed away from the point you want to go to (if it needs to slow down, that will be the case).

Hmm, I think I am just not understanding what exactly needs to be done, from a code sense.

In any case I spent the day with it and came up with a solution that does all that I need it to. It could still use some polish and I am not happy about how the ‘end of path’ reached functionality (as well as currentlyPathing and arrived) are calculated. But it will have to do. The waypoint code in the CalculateDirectionAndVelocity() also needs some love.

For anyone else out there struggling with this or looking to see how I did it at least I have posted it below. I hope this will save some of you time and effort. I left a liberal amount of comments & tooltips for everything I added / changed as well as some notes as follows:

It will accelerate to the max speed defined by the RVOController by a given acceleration. For each waypoint (or every turn) it will calculate a stopping distance using a simple motion equation. If the distance to the waypoint is more than the stopping distance, it will automatically increase the deceleration rate by a given step (you can set both in the inspector) until it can stop in time. I have tested this with incredibly fast speeds and it never overshoots the target and will only ever speed up to the max speed on straight-a ways. If you don’t want this behavior you can have no braking on corners by un-commenting ‘distanceToEndPoint’ and replacing all occurrences of ‘distanceToNextWp’ with it. This will cause for some interesting behavior :smile: Editing the maxSpeed on RVO controller, ‘moveNextDist’ as well as acceleration, deceleration and auto decel step will work in conjunction to get the results you should desire. The class is also built for inheritance, so you can override and change behaviors of stuff at will.

http://pastebin.com/0qsHiX12