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