Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

NavMesh.SamplePosition

Discussion in 'Navigation' started by nathanjams, Aug 20, 2021.

  1. nathanjams

    nathanjams

    Joined:
    Jul 27, 2016
    Posts:
    302
    Searched and found a few unresolved threads but now answers, does NavMesh.SamplePosition work?

    Trying this, to get a point on a specific Area Mask but it retruns an area on a different Area Mask. I have the


    Code (CSharp):
    1.  NavMeshHit hit;
    2.             Debug.Log(NavMesh.GetAreaFromName(areaName) + " NavMesh.GetAreaFromName(areaName))");
    3.             if (NavMesh.SamplePosition(randomPoint, out hit, range, NavMesh.GetAreaFromName(areaName)))//-1 << areaIndex))
    4.                 {
    5.                     setVector3Variable.Value = hit.position;
    6.                 }
    Any ideas of if I'm doing something wrong of are there still issues withi this?

    Thanks
    Nathan
     
  2. ChrisKurhan

    ChrisKurhan

    Joined:
    Dec 28, 2015
    Posts:
    266
    Sample position does work - it will return the closest point on a NavMesh with the given area mask. If you can provide more information about the specifics of your problem it would help provide a better answer. I don't fully understand what you are trying to do or the result you are getting
     
  3. nathanjams

    nathanjams

    Joined:
    Jul 27, 2016
    Posts:
    302
    Hey, thanks

    I'm trying to write a Behaviour Tree node for apoligies for the slightly modifed code with things like .value and SharedVector3.

    I'm trying to write a node that checks the npcs position relative to a pathway navmesh and return the closest point along that navmesh. The problem is that this is not returning a point on that navment but a raondom point elsewhere.



    What i have is
    Code (CSharp):
    1.  //[Tooltip("The Vector3 to get the values of")]
    2.         private SharedVector3 getVector3Variable;
    3.         public SharedGameObject m_TargetGameObject;
    4.         [Tooltip("The Vector3 to set the values of")]
    5.         public SharedVector3 setVector3Variable;
    6.         public float range = 10.0f;
    7.         private GameObject target;
    8.        
    9.         public string areaName;
    10.         public override void OnStart()
    11.         {
    12.             target = GetDefaultGameObject(m_TargetGameObject.Value);
    13.         }
    14.             public override TaskStatus OnUpdate()
    15.         {
    16.             for (int i = 0; i < 1000; i++)
    17.             {
    18.                 getVector3Variable = target.transform.position;
    19.                 Vector3 tempVector = new Vector3(getVector3Variable.Value.x, getVector3Variable.Value.y, getVector3Variable.Value.z);
    20.                 Vector3 randomPoint = tempVector + Random.insideUnitSphere * range;
    21.                 NavMeshHit hit;
    22.                 Debug.Log(NavMesh.GetAreaFromName(areaName) + " NavMesh.GetAreaFromName(areaName))");
    23.                 if (NavMesh.SamplePosition(randomPoint, out hit, range, NavMesh.GetAreaFromName(areaName)))
    24.                 {
    25.                     setVector3Variable.Value = hit.position;
    26.                     return base.OnUpdate();
    27.                 }
    28.             }
    29.          
    30.             return base.OnUpdate();
    31.         }
    32.     }
     
  4. ChrisKurhan

    ChrisKurhan

    Joined:
    Dec 28, 2015
    Posts:
    266
    I see you are sampling within, by default, a random point inside a unit circle * 10. Then you are NavMesh.SamplePositioning again with a radius of 10. This can give you wildly random results.

    In the attached image assuming the orange dot is your getVector3Varabile.value, the orange circle represents the possible radius you are choosing random points within. The green dot represents the offset after you do the randomPoint with Random.insideUnitSphere * range. Finally the green circle represents the possible outcome from a NavMesh.SamplePosition with the same radius.

    If you want to find a location on the NavMesh closest to the provided point, I would just NavMesh.SamplePosition on the point with a small radius. Unity recommends using at most 2x the agent height for performance.

    Unless your target can move very far off a NavMesh, you should have no issues choosing the same, or very similar location to the target's location.
     

    Attached Files:

    nathanjams likes this.
  5. nathanjams

    nathanjams

    Joined:
    Jul 27, 2016
    Posts:
    302
    Thanks Chris,

    I should have been more explicit in what I am attempting, I'm sorry for being so vague.

    Basically, I have multiple navmesh in my scene and I'm trying to make a system where an agent can move from one to another. I am able to change the Agent Area easily.

    What I am struggling with is that I have a pathway system of raods made with RAM and getting my npc to find the closest point on that pathway navmesh when they are off it. I have exported these as meshes and then baked all the roads as a "Pathway" navmesh. So, now say I have two other navmesh, a camp and a field. If my NPC is in the camp Navmesh and I want them to travel along the pathway to the closest point on the field navmesh I would hope to do two checks. When they are in the camp do a check for the closest point along the pathway and then walk to that, then do a seach for the closest point on the pathway navmesh to the field navmesh.

    Perhaps this is not possible and I am trying to use the SamplePosition incorrectly. I'll try out your suggestions and report back.

    Thanks again for your time and help
    Nathan
     
    Last edited: Aug 24, 2021
  6. nathanjams

    nathanjams

    Joined:
    Jul 27, 2016
    Posts:
    302
    OK, so I think I'm using the wrong function,
    Code (CSharp):
    1.  NavMeshHit hit;
    2.             if (NavMesh.FindClosestEdge(transform.position, out hit, NavMesh.GetAreaFromName(areaName)))
    3.             {
    4.                 setVector3Variable.Value = hit.position;
    5. }
    I'm thinking that this should work in returning the closest point on the specified navmesh but it's not. My NPC still wanders in random directions.
     
  7. nathanjams

    nathanjams

    Joined:
    Jul 27, 2016
    Posts:
    302
    Nah, that wasn't it, I believe that that returns the closest point on the navmesh the agent is on. Think something alongs these lines is what I want but I'm still just returning random points that are not on the desired navmesh.

    I feel like there is someting obvious I can't get through my head but I just don't know what it is.

    public override TaskStatus OnUpdate()

    {
    getVector3Variable = target.transform.position;
    Vector3 tempVector = new Vector3(getVector3Variable.Value.x, getVector3Variable.Value.y, getVector3Variable.Value.z);

    NavMeshHit hit;
    Debug.Log(NavMesh.GetAreaFromName("Walkable") + " NavMesh.GetAreaFromName(Walk");
    Debug.Log(NavMesh.GetAreaFromName(areaName) + " NavMesh.GetAreaFromName(areaName))");
    for (int i = 0; i < 1000; i++)
    {
    Vector3 randomPoint = tempVector + Random.insideUnitSphere * range;
    if (NavMesh.SamplePosition(randomPoint, out hit, 2, NavMesh.GetAreaFromName(areaName)))
    {
    setVector3Variable.Value = hit.position;
    break;
    }
    Debug.Log(i + "IIIIII");
    }
    return base.OnUpdate();
    }
     
  8. nathanjams

    nathanjams

    Joined:
    Jul 27, 2016
    Posts:
    302
    Here is a screen grab of the current terrain I am working with. The blue is the normal "Walkable" Navmesh, the Purple is the "Pathway" Navmesh.

    With Unity's Navmesh system is there a way that if my player is on the "Walkable" navmesh that they do a check to find the closest point on the "Pathway" navmesh?


    Thanks in advance,
    Nathan
     
    Last edited: Aug 27, 2021
  9. ChrisKurhan

    ChrisKurhan

    Joined:
    Dec 28, 2015
    Posts:
    266
    While I haven't specifically used the FindClosestEdge you mentioned, I do believe that is a way you can achieve that, provided the Area Masks are configured correctly. I will reproduce a similar scene to see if I can provide you with some concrete advice on how to make it work.
     
  10. ChrisKurhan

    ChrisKurhan

    Joined:
    Dec 28, 2015
    Posts:
    266
    Hey nathanjams,

    I wanted to make sure I understood what you were doing correctly. Some of your posts mention multiple NavMeshes, which would actually require you connect them with a NavMeshLink, and possibly (depending on your bake settings) that you change the NavMeshAgent's type to swap between being able to walk on one NavMesh or the other, since all NavMeshes using the NavMesh Components are bound to an Agent Type.
    It's really important to understand the difference between the NavMesh area and a separate NavMesh.
    More info on the manual about it here: https://docs.unity3d.com/Manual/class-NavMeshSurface.html

    It is probably more convenient to manage the road as a different NavMesh Area.
    Swapping a NavMeshAgent over to totally different NavMeshes requires a NavMeshLink component to allow the agent to traverse between them, but you still have to consider swapping the Agent's type there which ends up having other ramifications as well (such as possibly inconsistent agent radiuses for the bake).

    If you are able to swap to a single NavMesh baked per NavMeshAgent and have the road configured as a different NavMeshArea, you can use FindClosestEdge as you were trying to do before.
    The example code below requires that you have only 1 NavMesh with multiple NavMeshAreas, configured via NavMeshModifiers / NavMeshModifierVolumes.

    However if you do want to keep 2 separate NavMeshes still, FindClosestEdge also accepts a NavMeshQueryFilter which could be used in place of the NavMesh.GetAreaFromName("Water") that would find the closest edge on a separate NavMesh as well. You just have to pass it the Agent ID used to bake the second NavMesh.

    I set up a scene to replicate it that looks like this:
    upload_2021-8-28_5-57-56.png
    The blue area is configured to be the Water layer, everything else is Walkable.:
    upload_2021-8-28_5-58-37.png

    I've attached a very simple script to the Agent that shows a button that, when clicked, will make the agent go to the blue area.
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.AI;
    3.  
    4. [RequireComponent(typeof(NavMeshAgent))]
    5. public class GoToWater : MonoBehaviour
    6. {
    7.     private NavMeshAgent Agent;
    8.  
    9.     private void Awake()
    10.     {
    11.         Agent = GetComponent<NavMeshAgent>();
    12.     }
    13.  
    14.     private void OnGUI()
    15.     {
    16.         if (GUI.Button(new Rect(20, 20, 300,50), "Go To Water"))
    17.         {
    18.             if (NavMesh.FindClosestEdge(Agent.transform.position, out NavMeshHit hit, NavMesh.GetAreaFromName("Water")))
    19.             {
    20.                 Debug.Log("Found closest edge at: " + hit.position);
    21.                 Agent.SetDestination(hit.position);
    22.             }
    23.             else
    24.             {
    25.                 Debug.Log("Could not find closest edge");
    26.             }
    27.         }
    28.     }
    29. }
    30.  
     
    Razputin and nathanjams like this.
  11. nathanjams

    nathanjams

    Joined:
    Jul 27, 2016
    Posts:
    302
    whoa!

    Thanks so much @ChrisKurhan

    I'm ging to look into this right now. I'll let you know how I go.

    And again. Thank you!
     
  12. nathanjams

    nathanjams

    Joined:
    Jul 27, 2016
    Posts:
    302
    Ok, first of all a massive thank you Chris for doing this and all of your time with walking me through this.

    You are correct that I was using multiple NavmeshSurface components in the scene, each for a different area. So, I finally got my head around Modifiers and now have a single navmesh with different areas. It's much better than what I was doing.

    I am able to get the NavMesh.FindClosestEdge to work but only if the agent is close to the other navmesh, within 10 units. Any farther than that and it fails and chooses a seemingly random value. Are you aware of any way to adjust the search distance value?

    I've tried to create a spiral pattern from my player's poisition while doing a check but both NavMesh.FindClosestEdge and (NavMesh.SamplePosition return "True" when I feel they shouldn't be. I've tried making a for loop that checks in a a spiral pattern from the player but the if check is always returning true.


    Code (CSharp):
    1.  public override TaskStatus OnUpdate()
    2.  
    3.         {
    4.             float sprialSize = 1;
    5.             Vector3 playerPos = target.transform.position;
    6.             Vector3 tempPos = playerPos;
    7.             NavMeshHit hit;
    8.             for (int i = 0; i < 1000; i++)
    9.             {
    10.  
    11.                 Vector3 newPos = new Vector3((tempPos.x + sprialSize), tempPos.y, (tempPos.z + sprialSize));
    12.                 Debug.Log(newPos);
    13.                 Debug.Log(i);
    14.                 Debug.Log(NavMesh.GetAreaFromName(areaName));
    15.                 if (NavMesh.FindClosestEdge(newPos, out hit, NavMesh.GetAreaFromName(areaName)))
    16.                 {
    17.                     if (NavMesh.SamplePosition(hit.position, out NavMeshHit HitIn, .1f, NavMesh.GetAreaFromName(areaName)))
    18.                     {
    19.                         float dist = Vector3.Distance(target.transform.position, hit.position);
    20.                         setVector3Variable.Value = HitIn.position;
    21.                         break;
    22.                     }
    23.                 }
    24.             }
    25.                
    26.             return base.OnUpdate();
    27.         }
    Again, a massive thank you for your help.
    Nathan
     
  13. ChrisKurhan

    ChrisKurhan

    Joined:
    Dec 28, 2015
    Posts:
    266
    That's interesting. I didn't know the FindClosestEdge had a max radius. I will play around on that same sample scene and see if I can reproduce what you've mentioned and get back to you later in the week.
     
    nathanjams likes this.
  14. ChrisKurhan

    ChrisKurhan

    Joined:
    Dec 28, 2015
    Posts:
    266
    Hi Nathan,

    Sorry I can't reproduce the issue you're talking about. In this video you can see it running up to 30 units away without any issue finding the closest edge, bouncing between edges as I move the Agent around. This is using the exact same code from above so it's hard to say what is happening in your particular case.



    You may simplify your scene to see if you can get it working, then slowly re-add scripts and GameObjects (I'd just disable everything not directly related to this, then slowly re-enable them to see where it breaks).


    Here's the Editor code so you can use it as a debugging tool to help you as well. You can make a folder called "Editor" in the "Assets" folder then put this script in that folder:
    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3. using UnityEngine.AI;
    4.  
    5. [CustomEditor(typeof(GoToWater))]
    6. public class GoToWaterEditor : Editor
    7. {
    8.     private void OnSceneGUI()
    9.     {
    10.         GoToWater goToWater = (GoToWater)target;
    11.  
    12.         GUIStyle normalStyle = new GUIStyle()
    13.         {
    14.             normal = new GUIStyleState()
    15.             {
    16.                 textColor = Color.black
    17.             },
    18.             fontSize = 24
    19.         };
    20.         GUIStyle errorStyle = new GUIStyle()
    21.         {
    22.             normal = new GUIStyleState()
    23.             {
    24.                 textColor = Color.red
    25.             },
    26.             fontSize = 24
    27.         };
    28.  
    29.         if (goToWater == null)
    30.         {
    31.             return;
    32.         }
    33.  
    34.         if (NavMesh.FindClosestEdge(goToWater.Agent.transform.position, out NavMeshHit hit, NavMesh.GetAreaFromName("Water")))
    35.         {
    36.             Handles.DrawLine(goToWater.Agent.transform.position, hit.position);
    37.             Handles.Label(goToWater.transform.position, $"Distance: {Vector3.Distance(goToWater.Agent.transform.position, hit.position)}", normalStyle);
    38.         }
    39.         else
    40.         {
    41.             Handles.Label(goToWater.transform.position, "Distance: UNKNOWN", errorStyle);
    42.         }
    43.          
    44.     }
    45. }
    46.  
     
    Razputin and BroVodo like this.