Raycast to find point on the navmesh

Is there a way I can raycast to find a point on the Recast navmesh? I can see I can linecast the mesh for obstacles but what I’m looking for is the ability to test if the user is clicking a point on the walkable mesh by raycasting from the camera.

Hi

There’s nothing like this built-in.
There is the AstarPath.active.GetNearest(ray) method, but it only considers node centers, not the whole surface.
What I would do is to use physics raycasting to find the point in the world the player clicked on, and then if you need to, use AstarPath.active.GetNearest(point) to find the closest point on the navmesh from that.

Thanks for the response, this is what I am doing. It’s just a shame we can’t turn the navmesh into a collider for this as having to use physics means I need to set up layers for things that would otherwise be “in the way” of the smartly calculated NavMesh I’m trying to hit (the mesh goes into buildings, through forests etc). Do you think this is something I could do by examining the points in the graph and constructing a collider I then cast directly?

Answering my own question - it’s easy to create a collider for the navmesh and cast that directly by processing the recast tiles into a normal mesh and adding it to a collider.

The code at the bottom of this post provides a class that can be attached to the thing with the Pathfinder (AStarPath) attached to it. Probably best to set its layer to something you don’t care about for normal world collisions - but this is a lot easier than handling all of the other layers.

You can then use either the singleton Main static property or GetComponent to find it and then call Raycast on it.

Here’s my example placing component that I used to check it out:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlaceMe : MonoBehaviour
{

    public Camera camera;

// Update is called once per frame
    void Update()
    {
        Ray ray = camera.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;

        if (CreateModelFromAStar.Main.Raycast(ray, out hit))
        {
            transform.position = hit.point + Vector3.up * 1;
        }
    }
}

Here’s the code for the converter. It waits for graph updates and changes the collider when they do. It’s possible that this should be done faster, but seems ok for now.

using System;
using System.Collections;
using System.Collections.Generic;

using Pathfinding;
using UnityEngine;
using UnityEngine.Events;

public class CreateModelFromAStar : MonoBehaviour
{
    public static CreateModelFromAStar Main;
    public UnityEvent<List<Mesh>, List<Mesh>, CreateModelFromAStar> meshesChanged;
    private readonly List<MeshCollider> _colliders = new List<MeshCollider>();
    private AstarPath _path;
    [NonSerialized]
    public readonly List<Mesh> GraphMeshes = new();

    [NonSerialized] public readonly List<Mesh> ActiveMeshes = new List<Mesh>();

    private void Awake()
    {
        Main = this;
    }

    private IEnumerator Start()
    {
        _path = GetComponent<AstarPath>();
        do
        {
            while (_path.IsAnyGraphUpdateQueued || _path.IsAnyGraphUpdateInProgress)
            {
                yield return null;
            }

            yield return ConstructObject();

            foreach (var currentCollider in GetComponents<MeshCollider>())
            {
                DestroyImmediate(currentCollider);
            }
            _colliders.Clear();
            foreach (var mesh in ActiveMeshes)
            {
                var meshCollider = gameObject.AddComponent<MeshCollider>();
                meshCollider.sharedMesh = mesh;
                _colliders.Add(meshCollider);
            }

            if (meshesChanged != null)
            {
                meshesChanged.Invoke(ActiveMeshes, GraphMeshes, this);
            }

            while (!_path.IsAnyGraphUpdateQueued && !_path.IsAnyGraphUpdateInProgress)
            {
                yield return null;
            }

        } while (true);
    }

    private static readonly RaycastHit DummyHit;

    public bool Raycast(Ray ray, out RaycastHit hitInfo, float maxDistance = Single.PositiveInfinity)
    {

        for (int i = 0, l = _colliders.Count; i < l; i++)
        {
            if (_colliders[i].Raycast(ray, out hitInfo, maxDistance)) return true;
        }

        hitInfo = DummyHit;
        return false;
    }

    private IEnumerator ConstructObject()
    {
        var graphs = _path.graphs;
        for (var i = 0; i < graphs.Length; i++)
        {
            var graph = graphs[i] as RecastGraph;
            if (graph != null)
            {
                var allVertices = new List<Vector3>();
                var allTriangles = new List<int>();
                var tiles = graph.GetTiles();
                if(tiles == null) continue;
                var graphTransform = graph.CalculateTransform();
                var boundsOffsetFromPosition = (graph.forcedBoundsCenter - transform.position);
                var vectorOffset = -graph.forcedBoundsSize * 0.5f + boundsOffsetFromPosition;
                for (var t = 0; t < tiles.Length; t++)
                {
                    var tile = tiles[t];
                    if(tile == null) continue;
                    var verts = tile.verts;
                    var offset = allVertices.Count;
                    for (var v = 0; v < verts.Length; v++)
                    {
                        var pt = graphTransform.InverseTransform((Vector3) verts[v]);
                        allVertices.Add(pt + vectorOffset);
                    }

                    var nodes = tile.nodes;
                    for (var n = 0; n < nodes.Length; n++)
                    {
                        var node = nodes[n];
                        allTriangles.Add(node.GetVertexArrayIndex(0) + offset);
                        allTriangles.Add(node.GetVertexArrayIndex(1) + offset);
                        allTriangles.Add(node.GetVertexArrayIndex(2) + offset);
                    }

                }

                var mesh = new Mesh
                {
                    vertices = allVertices.ToArray(),
                    triangles = allTriangles.ToArray()
                };
                GraphMeshes.Add(mesh);
                ActiveMeshes.Add(mesh);
            }
            else
            {
                GraphMeshes.Add(null);
            }
        }
        yield return null;
    }

}

1 Like