Ability to remove areas from navmesh by ID

Hello!

I’ve seen mentions of this before, saying that it can be done with custom code, but not officially supported. I think it should be.
At the very least, give me some pointers, where would one begin to write this custom code?

Hi

What do you mean by “ID”? There are several fields that I can think of that could be called an ID.

Hey!

Honestly, no idea, I just saw an old thread where they called it ID.
I just want to remove all the small islands and other areas based on a custom logic.

Are you using a recast graph or some other graph type?

Recast graphs, several of them.

There is the RelevantGraphSurface component that you could use.

See https://arongranberg.com/astar/docs/recastgraph.html#relevantGraphSurfaceMode

Essentially you would place a GameObject with the a RelevantGraphSurface component attached in the region that you want to keep. Then set the proper relevantGraphSurfaceMode mode on the recast graph and after that all the other regions will be removed from the graph. If you are using tiles you will have use a RelevantGraphSurface component per tile.

Yeah, I don’t want to do that.
We do use tiles, and creating potentially hundreds of these objects doesn’t sound appealing to me.
I’d much rather iterate over the areas after the scan is done and remove them based on a custom logic.

Ok. Actually removing the nodes is a bit more complicated, but you can make the surfaces unwalkable easily.
Something like this should work I think (just wrote it from memory, I haven’t tested it)

AstarPath.active.AddWorkItem(() => {
     var area = AstarPath.active.GetNearest(somePosition).node.Area;
     AstarPath.active.data.GetGraphs(graph => {
              graph.GetNodes(node => {
                       if (node.Area != area) node.Walkable = false;
              });
     }
});

Where ‘somePosition’ is a point inside the area that you want to keep.

Well… sounds like I will have to make do with the RelevantGraphSurface-s somehow, as I need those areas gone completely.
The reason is, that our units can get ragdolled, fall outside of the navmesh, or fall onto an isolated island, and be stuck forever. The solution is to check for them being off-mesh and move them back on, but it doesn’t work if there are any isolated islands after scanning. The navmesh also becomes fragmented during runtime, as doors and barricades cut it apart

Not sure if I’m doing it wrong or if it’s bugged, but RelevantGraphSurfaceMode.RequireForAll doesn’t seem to work at all. I’ve put RelevantGraphSurface components on our spawn-points, expecting 90% of the navmesh getting removed, as only tiles with a spawn-point inside it should be kept.
Instead the navmesh is completely empty.

So instead I started stitching together my custom code to remove all the triangles of disconnected islands, but based on our discussion in the navmehcut bug thread, I foresee a problem:
Navmeshcuts don’t replace the original navmesh, and serializing it to a file only saved the original uncut mesh. As such, my custom code based on navmeshcuts will also be unable to affect the orignal and be completely useless.

So my question is, how can I make my changes to the navmesh final, so when it gets serialized it saves the updated version?

Here is my custom code so far, didn’t get to writing the actual filtering yet:


    private void RemoveNavmeshIslands(Vector3[] spawnPoints, RecastGraph graph)
    {
        NavmeshTile[] tiles = graph.GetTiles();
        for (int z = 0; z < graph.tileZCount; z++)
        {
            for (int x = 0; x < graph.tileXCount; x++)
            {
                NavmeshTile tile = tiles[x + z * graph.tileXCount];
                Int3 size = (Int3)new Vector3(graph.TileWorldSizeX, 0, graph.TileWorldSizeZ);
                Bounds b = graph.GetTileBoundsInGraphSpace(x, z);
                var centerOffset = -((Int3)b.min + new Int3(size.x * tile.w / 2, 0, size.z * tile.d / 2));

                var tileType = new Pathfinding.Util.TileHandler.TileType(tile.vertsInGraphSpace, tile.tris, size, centerOffset, tile.w, tile.d);

                AstarPath.active.AddWorkItem(new AstarWorkItem((context, force) =>
                {
                    //load tile
                    Int3[] verts;
                    int[] tris;
                    tileType.Load(out verts, out tris, 0, 0);

                    List<Int3> inputverts = new List<Int3>(verts);
                    List<int> inputtris = new List<int>(tris);

                    // Cut the polygon
                    Pathfinding.Util.TileHandler.CuttingResult cuttingResult = new Pathfinding.Util.TileHandler.CuttingResult();

                    //TODO remove unnecessary vertices and triangles 
                    //CutPoly(verts, tris, null, graph.transform, new IntRect(x, z, x + tileType.Width - 1, z + tileType.Depth - 1));


                    Pathfinding.Polygon.CompressMesh(inputverts, inputtris, out cuttingResult.verts, out cuttingResult.tris);

                    var tCount = cuttingResult.tris.Length;
                    if (tCount != cuttingResult.tris.Length) cuttingResult.tris = Pathfinding.Util.Memory.ShrinkArray(cuttingResult.tris, tCount);

                    // Replace the tile using the final vertices and triangles
                    // The vertices are still in local space
                    graph.ReplaceTile(x, z, cuttingResult.verts, cuttingResult.tris);

                    // Flood fill everything to make sure graph areas are still valid
                    // This tends to take more than 50% of the calculation time
                    context.QueueFloodFill();

                    return true;
                }));
            }
        }
    }```