Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

SamplePathPosition is bugged

Discussion in 'Navigation' started by apollyonbob, May 24, 2020.

  1. apollyonbob

    apollyonbob

    Joined:
    Jan 16, 2012
    Posts:
    10
    Here's the setup:
    1) I have a player character and an AI agent that have similar control mechanisms - they choose a spot where they want to move.
    2) Complication: They have a limited range that they can move to - say 5 units. That length though is the length of the path. So if they have to curve around something they won't go as far as they could in a straight line.
    3) The Player needs to be alerted as to whether or not this is a valid move - moves beyond 5 units are invalid - before they start moving. This makes using "check distance as you go" difficult.
    4) I cannot use "straight line" determination because the nav path is probably not a straight line.

    What I'm trying to use at the moment is SamplePathPosition, and it appears to be bugged for curved lines.
    I started with this
    Code (CSharp):
    1.  
    2.         var navMeshPath = new NavMeshPath();
    3.         if (_agent.CalculatePath(destination, navMeshPath))
    4.         {
    5.             _agent.SetPath(navMeshPath);
    6.             var sampleHit = _agent.SamplePathPosition(_agent.areaMask, range, out NavMeshHit nextStart);
    7.             finalPoint = nextStart.position;
    8.            
    9.             _agent.ResetPath();
    10.         }
    11.  
    But I think that's not right, because that hits on the wrong thing?

    Code (CSharp):
    1. int passableMask = ~(1 << NavMesh.GetAreaFromName("Not Walkable"));
    2.             var sampleHit = _agent.SamplePathPosition(passableMask, range, out NavMeshHit nextStart);
    So that should only hit when it sees a not walkable area in the path, which it should NEVER do, because it's got a NavMeshPath that explicitly avoids those areas. (fwiw, I also tried NavMesh.AllAreas and it also had the same problem)

    The problem is this: If there is any sort of non-right-angle corners in the mesh, and Agent hits that corner, it fails.

    upload_2020-5-24_14-43-47.png

    I'm sending the black character to the blue character. When it gets to that "soft" corner in the bottom middle, SamplePathPosition starts returning "true", like it hit something, and starts returning NavMeshHit objects with distances of 0.

    But what could it possibly be hitting? However, I played around with some stuff, and in experimenting I tried adding a NavMeshObstacle to that pillar. If I don't turn Carve on, no change. If I turn Carve on, but I make it a capsule, and basically make the NavMesh look identical to the one above, the problem persists.

    However, if I turn Carve on, make the obstacle a box, and get this:
    upload_2020-5-24_14-45-43.png

    Suddenly IT WORKS. That's literally the ONLY change, and now SamplePathPosition stops returning hits, stops returning NavMeshHit objects with distances of 0. Again, the only difference was the SHAPE of the NavMeshObstacle.

    So, in going through all this I thought, SURELY I can just calculate the distance between the vectors of NavMeshPath.corners and not have to do even USE SamplePathPosition right? And sure enough, even though I tried that before and somehow it didn't work, doing it again, it worked this time. So the solution to my particular problem, in this particular place, is to not use SamplePathPosition.

    However, I wanted to still post this issue, because this almost certainly seems like unintended behavior.
     
  2. JustLoren

    JustLoren

    Joined:
    Nov 12, 2017
    Posts:
    5
    I'm experiencing an eerily similar problem. There are occasions where my SamplePathPosition is terminating early for no discernible reason:

    upload_2021-3-7_11-56-45.png

    You can see the navmesh agent path goes around the obstacle and towards a destination within the pink. But when I do a SamplePathPosition (passing NavMesh.AllAreas), it returns true and claims it stops at that exact point. If my agent is coming in from a different angle, it appears to work fine.

    Any help would be appreciated, although this is causing game breaking bugs in an already released game, so I am going to have to find a different solution.
     
  3. JustLoren

    JustLoren

    Joined:
    Nov 12, 2017
    Posts:
    5
    For anyone else who has this problem, here's how I've "solved" it for my use case. The code is below.

    Basically, I re-implemented SamplePathPosition using NavMesh.Raycast. In doing so, I discovered that NavMesh.Raycast would sometimes behave in the exact same fashion - declaring that it terminated early despite all indications that it should continue. In order to resolve this, I offset the starting point of every raycast by 1mm towards its destination. My game is done at standard scale (1 unit = 1 meter), and so shifting by 1mm is without consequence for me. You may have to apply your own special value.

    EDIT: In addition, it's looking like the raycast fails on its way into a destination as well. This is problematic, as it's hard to tell a glitch-termination from a real termination, but I'm just inspecting the distance. If it's small enough, then I consider it a glitch termination and just move on to the next raycast.

    Code (CSharp):
    1.    private bool LookAheadOnNavmesh(float distance, int mask, out Vector3 position)
    2.     {
    3.         position = Vector3.zero;
    4.         if (movement.agent.path.status == NavMeshPathStatus.PathInvalid)
    5.             return false;
    6.  
    7.         NavMeshHit navMeshHit;
    8.      
    9.         float distanceRemaining = distance;
    10.  
    11.         Vector3 corner, end, direction;
    12.         bool terminated;
    13.  
    14.         for (int i = 0; i < movement.agent.path.corners.Length - 1; i++)
    15.         {
    16.             corner = movement.agent.path.corners[i];
    17.             end = movement.agent.path.corners[i + 1];
    18.             direction = (end - corner);
    19.             if (direction.magnitude > distanceRemaining)
    20.             {
    21.                 end = corner + (direction.normalized * distanceRemaining);
    22.                 distanceRemaining = 0;
    23.             } else
    24.             {
    25.                 distanceRemaining -= direction.magnitude;
    26.             }
    27.  
    28.             //This is magic. Sometimes a raycast from the corner sucks, so move 1mm closer to our destination
    29.             corner += direction.normalized * .001f;
    30.  
    31.             terminated = NavMesh.Raycast(corner, end, out navMeshHit, NavMesh.AllAreas);
    32.  
    33.             if ((navMeshHit.mask & mask) != 0)
    34.             {
    35.                 position = navMeshHit.position;
    36.                 return true;
    37.             }
    38.  
    39.             //something got in the way, or we ran outta juice
    40.             //More magic here. Sometimes, the raycast fails within micrometers of the destination, so do a simple check.
    41.             if ((terminated && Vector3.Distance(navMeshHit.position, end) > .001f) || distanceRemaining == 0)
    42.                 break;
    43.         }
    44.  
    45.         return false;
    46.     }
     
    Last edited: Mar 7, 2021