Using AIPath on Moving (similar to example) platform

I’m attempting to create something similar to the Moving example, however in my case I am using a PointGraph, therefore while RichAI can successfully calculate a path, it cannot move the Player. I’ve taken a look at modifying AIPath, as the example mentions it should not be difficult to apply LocalSpace transform, however I am having difficulties as AIPath utilizes Path (as opposed to RichPath) which does not contain any method/variable for adjusting the next waypoint using a GraphTransform. Any advice on how I would go about achieving this?

Hi

I haven’t tested this myself, but I think you should be able to use mostly the same logic but inside the PathInterpolator class. Essentially in the position and tangent properties you transform from graph space to world space and in the MoveToClosestPoint, MoveToLocallyClosestPoint and MoveToCircleIntersection2D methods you transform the point/circleCenter3D parameters to graph space.

Hey Aron, I did manage to figure that out after a little bit of fiddling. I ended up modifying AIPath itself though so that it InverseTransforms the parameters when calling methods in the PathInterpolator and Transforms the return back so as to avoid modifying the Interpolator itself. This makes it slightly more flexible if there are any changes to the Interpolator itself in future releases :slight_smile:

1 Like

Update: NM got it working just after I posted this. Thanks.

Update 2: nope was wrong. Just worked until the ship turned about half way through then was all jacked up but only if speed is set to 1. If its set to 2 works fine. Tested that in the original moving scene and that works fine with 1 so not sure what is going on there.

Working on the same issue for a grid graph but struggling to get it working correctly. Any chance you could drop a bit of code showing what changes you made? I can see the path being generated on the grid correctly in the editor but cannot seem to get it remapped to global afterwards.

Thanks.

So how would we handle getting the correct velocity for a object AIPath on a moving platform?

After digging into this a bit more I am wondering if more needs to change then just the ins and outs of the Interpolator. For instance this issue with velocity you have prevPosition1 and prevPosition2 which are updating from position and simulated position which is why you mentioned to also override position as well. Will give that a go.

I am guessing something along these lines.

                return updatePosition ? localGraph.transformation.Transform(tr.position) : simulatedPosition;

Ok still not working. The following script will move the object around correctly but the velocity is messed up. Overriding position breaks the movements.

using Pathfinding.Util;
using UnityEngine;

namespace Pathfinding.Examples
{
public class LocalSpaceAIPath : AIPath
{
/** Root of the object we are moving on */
public LocalSpaceGraph localGraph;

    void RefreshTransform()
    {
        localGraph.Refresh();
        movementPlane = localGraph.transformation;
    }

    protected override void Start()
    {
        RefreshTransform();
        base.Start();
    }

    protected override void CalculatePathRequestEndpoints(out Vector3 start, out Vector3 end)
    {
        RefreshTransform();

        base.CalculatePathRequestEndpoints(out start, out end);

        start = localGraph.transformation.InverseTransform(start);
        end = localGraph.transformation.InverseTransform(end);
    }

    public override Vector3 position
    {
        get
        {
            //if (localGraph != null && localGraph.transformation != null)
            //    return updatePosition ? localGraph.transformation.Transform(tr.position) : simulatedPosition;
            //else
                return base.position;
        }
    }

    public override Vector3 steeringTarget
    {
        get
        {
            return interpolator.valid ? localGraph.transformation.Transform(interpolator.position) : position;
        }
    }

    public override float remainingDistance
    {
        get
        {
            return interpolator.valid ? interpolator.remainingDistance + movementPlane.ToPlane(localGraph.transformation.Transform(interpolator.position) - position).magnitude : float.PositiveInfinity;
        }
    }

    protected override void OnPathComplete(Path newPath)
    {
        ABPath p = newPath as ABPath;

        if (p == null) throw new System.Exception("This function only handles ABPaths, do not use special path types");

        waitingForPathCalculation = false;

        // Increase the reference count on the new path.
        // This is used for object pooling to reduce allocations.
        p.Claim(this);

        // Path couldn't be calculated of some reason.
        // More info in p.errorLog (debug string)
        if (p.error)
        {
            p.Release(this);
            return;
        }

        // Release the previous path.
        if (path != null) path.Release(this);

        // Replace the old path
        path = p;

        // Make sure the path contains at least 2 points
        if (path.vectorPath.Count == 1) path.vectorPath.Add(path.vectorPath[0]);
        interpolator.SetPath(path.vectorPath);

        var graph = AstarData.GetGraph(path.path[0]) as ITransformedGraph;
        movementPlane = graph != null ? graph.transform : (rotationIn2D ? new GraphTransform(Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(-90, 270, 90), Vector3.one)) : GraphTransform.identityTransform);

        // Reset some variables
        reachedEndOfPath = false;

        // Simulate movement from the point where the path was requested
        // to where we are right now. This reduces the risk that the agent
        // gets confused because the first point in the path is far away
        // from the current position (possibly behind it which could cause
        // the agent to turn around, and that looks pretty bad).

        interpolator.MoveToLocallyClosestPoint(localGraph.transformation.InverseTransform((GetFeetPosition() + p.originalStartPoint) * 0.5f));
        interpolator.MoveToLocallyClosestPoint(localGraph.transformation.InverseTransform(GetFeetPosition()));

        // Update which point we are moving towards.
        // Note that we need to do this here because otherwise the remainingDistance field might be incorrect for 1 frame.
        // (due to interpolator.remainingDistance being incorrect).

        interpolator.MoveToCircleIntersection2D(localGraph.transformation.InverseTransform(position), pickNextWaypointDist, movementPlane);

        //tr.position = localGraph.transformation.Transform(position);
        //simulatedPosition = localGraph.transformation.Transform(position);

        var distanceToEnd = remainingDistance;
        if (distanceToEnd <= endReachedDistance)
        {
            reachedEndOfPath = true;
            OnTargetReached();
        }
    }

    /** Called during either Update or FixedUpdate depending on if rigidbodies are used for movement or not */
    protected override void MovementUpdateInternal(float deltaTime, out Vector3 nextPosition, out Quaternion nextRotation)
    {
        float currentAcceleration = maxAcceleration;

        // If negative, calculate the acceleration from the max speed
        if (currentAcceleration < 0) currentAcceleration *= -maxSpeed;

        if (updatePosition)
        {
            // Get our current position. We read from transform.position as few times as possible as it is relatively slow
            // (at least compared to a local variable)
            simulatedPosition = tr.position;
            //simulatedPosition = position;
        }

        if (updateRotation)
            simulatedRotation = tr.rotation;

        var currentPosition = simulatedPosition;

        // Update which point we are moving towards
        interpolator.MoveToCircleIntersection2D(localGraph.transformation.InverseTransform(currentPosition), pickNextWaypointDist, movementPlane);
        //interpolator.MoveToCircleIntersection2D(currentPosition, pickNextWaypointDist, movementPlane);
        var dir = movementPlane.ToPlane(steeringTarget - currentPosition);

        // Calculate the distance to the end of the path
        float distanceToEnd = dir.magnitude + Mathf.Max(0, interpolator.remainingDistance);

        // Check if we have reached the target
        var prevTargetReached = reachedEndOfPath;
        reachedEndOfPath = distanceToEnd <= endReachedDistance && interpolator.valid;
        if (!prevTargetReached && reachedEndOfPath) OnTargetReached();
        float slowdown;

        // Normalized direction of where the agent is looking
        var forwards = movementPlane.ToPlane(simulatedRotation * (rotationIn2D ? Vector3.up : Vector3.forward));

        // Check if we have a valid path to follow and some other script has not stopped the character
        if (interpolator.valid && !isStopped)
        {
            // How fast to move depending on the distance to the destination.
            // Move slower as the character gets closer to the destination.
            // This is always a value between 0 and 1.
            slowdown = distanceToEnd < slowdownDistance ? Mathf.Sqrt(distanceToEnd / slowdownDistance) : 1;

            if (reachedEndOfPath && whenCloseToDestination == CloseToDestinationMode.Stop)
            {
                // Slow down as quickly as possible
                velocity2D -= Vector2.ClampMagnitude(velocity2D, currentAcceleration * deltaTime);
            }
            else
            {
                velocity2D += MovementUtilities.CalculateAccelerationToReachPoint(dir, dir.normalized * maxSpeed, velocity2D, currentAcceleration, rotationSpeed, maxSpeed, forwards) * deltaTime;
            }
        }
        else
        {
            slowdown = 1;
            // Slow down as quickly as possible
            velocity2D -= Vector2.ClampMagnitude(velocity2D, currentAcceleration * deltaTime);
        }

        velocity2D = MovementUtilities.ClampVelocity(velocity2D, maxSpeed, slowdown, slowWhenNotFacingTarget, forwards);

        ApplyGravity(deltaTime);

        if (rvoController != null && rvoController.enabled)
        {
            // Send a message to the RVOController that we want to move
            // with this velocity. In the next simulation step, this
            // velocity will be processed and it will be fed back to the
            // rvo controller and finally it will be used by this script
            // when calling the CalculateMovementDelta method below

            // Make sure that we don't move further than to the end point
            // of the path. If the RVO simulation FPS is low and we did
            // not do this, the agent might overshoot the target a lot.
            var rvoTarget = currentPosition + movementPlane.ToWorld(Vector2.ClampMagnitude(velocity2D, distanceToEnd), 0f);
            rvoController.SetTarget(rvoTarget, velocity2D.magnitude, maxSpeed);
        }

        // Set how much the agent wants to move during this frame
        var delta2D = lastDeltaPosition = CalculateDeltaToMoveThisFrame(movementPlane.ToPlane(currentPosition), distanceToEnd, deltaTime);
        nextPosition = currentPosition + movementPlane.ToWorld(delta2D, verticalVelocity * lastDeltaTime);
        CalculateNextRotation(slowdown, out nextRotation);
    }

    protected override void Update()
    {
        RefreshTransform();
        base.Update();

        Debug.Log(string.Format("Default {0} Local {1} World {2} Inv Dir {3} Dir {4}", this.velocity, localGraph.transformation.InverseTransform(this.velocity), localGraph.transformation.Transform(this.velocity), transform.InverseTransformDirection(this.velocity), transform.TransformDirection(this.velocity)));
    }
}

}

Ok I guess I am back to my original question since you were talking about overriding property and tangent of Interpolator not AIBase which is what I was talking about for handling velocity. Seems like we have to also do some modification of that but no luck yet…

Did you ever find a solution that worked as Aron mentioned above without having to override AIBase?

Hey, this might be helpful for you: Local Space AI Path, LocalSpaceRichAI equivalent for 2D

2 Likes