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);
}
}
}
}
3 Likes
Note for the above: Newer versions of Astar now apply navmesh cuts in the editor. The info from the cuts will be missing when PathRemoveUnconnected() is run OnPostScan(). My simple fix for this is to disable any navmesh cuts in OnPreScan(), and then revert them back in OnPostScan() after the pruning. Another option may be to analyze the pre cut data.
1 Like