How to deal with the new tiles type (unsafe span)? Removing nodes marked as unwalkable

In versions previous to 5.0 I have used the following code to remove nodes tagged as unwalkable. This no longer works as it appears tiles have been changed to an unmanaged memory type.

// Destroy nodes tagged not walkable
                foreach (NavmeshTile tile in recastGraphs[r].GetTiles()) {
                    // Filter the node and triangle arrays and keep only the nodes that you want to keep
                    var newNodes = new List<TriangleMeshNode>();
                    var newTris = new List<int>();
                    for (int i = tile.nodes.Length - 1; i >= 0; i--) {
                        if (!tile.nodes[i].Walkable) {
                            tile.nodes[i].Destroy();
                        }
                        else {
                            // Keep this node
                            newNodes.Add(tile.nodes[i]);
                            newTris.Add(tile.tris[i * 3 + 0]);
                            newTris.Add(tile.tris[i * 3 + 1]);
                            newTris.Add(tile.tris[i * 3 + 2]);
                        }
                    }
                    tile.nodes = newNodes.ToArray();
                    tile.tris = newTris.ToArray();
                    tile.bbTree.RebuildFrom(tile);
                }

So the line “tile.tris = newTris,ToArray();” fails and “bbTree.RebuildFrom” does not appear to exist any more. Not sure how to do the managed / unmanaged conversion and other implications.

Hi

I’d recommend that you do something like this:

recastGraph.StartBatchTileUpdate();
foreach (var tile in recastGraph.GetTiles()) {
    var trianglesToKeep = new List<int>();
    for (int i = 0; i < tile.nodes.Length; i++) {
        var node = tile.nodes[i];
        if (node.Walkable) {
            trianglesToKeep.Add(tile.tris[i*3+0]);
            trianglesToKeep.Add(tile.tris[i*3+1]);
            trianglesToKeep.Add(tile.tris[i*3+2]);
       }
    }
    recastGraph.ReplaceTile(tile.x, tile.z, tile.verts, trianglesToKeep.ToArray());
}
recastGraph.EndBatchTileUpdate();

Note: I haven’t tested this code, but I think something like it should work.

ReplaceTile is expecting Int3[] for the verts so I tried this to convert:

Int3[] newVerts = new Int3[tile.verts.Length];
for (int i = 0; i < tile.verts.Length; i++) {
     newVerts[i]= tile.verts[i];
}
recastGraphs[r].ReplaceTile(tile.x, tile.z, newVerts, trianglesToKeep.ToArray());

I think it is removing the unwalkable nodes ok but the results are odd. Original graph, unwalkable nodes in grey:

Graph after running this code:

Right. I think you’ll need to do:

var offset = (Int3) new Vector3((tile.x * graph.TileWorldSizeX), 0, (tile.z * graph.TileWorldSizeZ));
Int3[] newVerts = new Int3[tile.vertsInGraphSpace.Length];
for (int i = 0; i < tile.vertsInGraphSpace.Length; i++) {
     newVerts[i]= tile.vertsInGraphSpace[i] - offset;
}

That seems to work great. Thanks!

1 Like

In case this is useful to anyone else here is the full code. This is a helper for removing unwanted areas from tiled recast graphs.

using System.Collections.Generic;
using UnityEngine;

namespace Pathfinding
{
    public class PathRemoveUnconnected : GraphModifier
    {
        // Remove unconnected areas in tiled recast graphs that are not relevant
        // After graphs are scanned, this will mark all nodes that are not relevant as not walkable and then remove them
        // Desired functionality is similar to relevantGraphSurface but without having to place objects in each tile
        // Usage: place an empty GameObject inside areas you want to keep and add these to the areaMarkers array

        public Transform[] areaMarkers;
        Vector3[] connectedPoints;
        HashSet<GraphNode> connectedNodes = new HashSet<GraphNode>();
        List<GraphNode> toSearch = new List<GraphNode>();
        GraphNode nearestNode;
        GraphNode searchNode;
        int removedNodeCounter;

        public override void OnPostScan()
        {
            Debug.Log("Graphs-->OnPostScan");
            // Get connected points from area markers
            connectedPoints = new Vector3[areaMarkers.Length];
            for (int x = 0; x < areaMarkers.Length; x++) {
                connectedPoints[x] = areaMarkers[x].transform.position;
            }
            // Create a list of the recast graphs in scene
            List<RecastGraph> recastGraphs = new List<RecastGraph>();
            foreach (RecastGraph r in AstarPath.active.data.FindGraphsOfType(typeof(RecastGraph))) {
                recastGraphs.Add(r);
            }
            if (recastGraphs.Count == 0) return;
            // Process each graph
            toSearch.Clear();
            connectedNodes.Clear();
            for (int r = 0; r < recastGraphs.Count; r++) {
                // Get the nearest node to each area marker and add to the search
                for (int x = 0; x < connectedPoints.Length; x++) {
                    nearestNode = recastGraphs[r].GetNearest(connectedPoints[x], NNConstraint.Walkable).node;
                    if (nearestNode != null) {
                        connectedNodes.Add(nearestNode);
                        toSearch.Add(nearestNode);
                    }
                }
                // Go through all connected nodes and add them to the connected list
                while (toSearch.Count > 0) {
                    searchNode = toSearch[toSearch.Count - 1];
                    toSearch.RemoveAt(toSearch.Count - 1);
                    searchNode.GetConnections(n =>
                    {
                        if (n.Walkable && !connectedNodes.Contains(n)) {
                            toSearch.Add(n);
                            connectedNodes.Add(n);
                        }
                    });
                }
                // Mark unconnected nodes as not walkable
                recastGraphs[r].GetNodes(node =>
                {
                    if (!connectedNodes.Contains(node)) {
                        node.Walkable = false;
                        node.ClearConnections(true);
                    }
                });
                // Update each tile and discard non walkable nodes
                removedNodeCounter = 0;
                recastGraphs[r].StartBatchTileUpdate();
                foreach (var tile in recastGraphs[r].GetTiles()) {
                    var trianglesToKeep = new List<int>();
                    for (int i = 0; i < tile.nodes.Length; i++) {
                        var node = tile.nodes[i];
                        if (node.Walkable) {
                            trianglesToKeep.Add(tile.tris[i * 3 + 0]);
                            trianglesToKeep.Add(tile.tris[i * 3 + 1]);
                            trianglesToKeep.Add(tile.tris[i * 3 + 2]);
                        }
                        else {
                            removedNodeCounter++;
                        }
                    }
                    var offset = (Int3)new Vector3((tile.x * recastGraphs[r].TileWorldSizeX), 0, (tile.z * recastGraphs[r].TileWorldSizeZ));
                    Int3[] newVerts = new Int3[tile.verts.Length];
                    for (int i = 0; i < tile.verts.Length; i++) {
                        newVerts[i] = tile.vertsInGraphSpace[i] - offset;
                    }
                    recastGraphs[r].ReplaceTile(tile.x, tile.z, newVerts, trianglesToKeep.ToArray());
                }
                recastGraphs[r].EndBatchTileUpdate();
                // Info
                Debug.Log("Graph #" + r + ", Tile count: " + recastGraphs[r].GetTiles().Length + ", Nodes removed: " + removedNodeCounter);
            }
        }
    }
}
1 Like