DeserializeGraphsAdditive() and RelocateNodes()

I have a game that consists of a hub that periodically has other levels open up around it randomly. These other scenes are loaded additivity (usually one at a time) and relocated randomly at runtime. Both the hub and the levels have recast graphs for navigation.

It seems as though if I should be able pre-scan and serialize the graphs in the additive levels then I’d be able to use DeserializeGraphsAdditive() and RelocateNodes() to load and reposition these graphs at runtime. However neither of these seem to work. DeserializeGraphsAdditive corrupts both the hub graph and the additive graph (The non-additive DeserializeGraphs does seem to work, but obviously replaces my hub graph with the additive one). RelocateNodes doesn’t seem to work at all, even if I skip the additive loading and just try to Relocate the hub graph, it never moves.

I found a very old thread of someone trying to do the same thing and encountering several bugs, and was wondering what the current status of these methods is?

http://arongranberg.com/vanillaforums/discussion/332/using-relocatenodes-with-a-navmesh-graph

Experimenting some more it seems to be a problem with additively loading recast graphs.

If I have my hub and additive level both set up with grid graphs, then the additive grid graph loads and relocates correctly (Hub on the left, additive bit on the right).

Likewise if the hub has a recast graph and the additive level has a grid graph then it works.

If the hub has a grid and the additive level has a recast then the additive graph loads correctly, but does not relocate.

The worst situation is the original I posted about, with both level having recast graphs, then both graphs become corrupted and the log start spewing

IndexOutOfRangeException: Array index is out of range.
Pathfinding.RecastGraph+NavmeshTile.GetVertex (Int32 index) (at Assets/AstarPathfindingProject/Generators/RecastGenerator.cs:408)
Pathfinding.RecastGraph.GetVertex (Int32 index) (at Assets/AstarPathfindingProject/Generators/RecastGenerator.cs:426)
Pathfinding.TriangleMeshNode.GetVertex (Int32 i) (at Assets/AstarPathfindingProject/Generators/NodeClasses/TriangleMeshNode.cs:57)
Pathfinding.RecastGraph+<OnDrawGizmos>c__AnonStoreyBC.<>m__450 (Pathfinding.GraphNode _node) (at Assets/AstarPathfindingProject/Generators/RecastGenerator.cs:2612)
Pathfinding.RecastGraph.GetNodes (Pathfinding.GraphNodeDelegateCancelable del) (at Assets/AstarPathfindingProject/Generators/RecastGenerator.cs:514)
Pathfinding.RecastGraph.OnDrawGizmos (Boolean drawNodes) (at Assets/AstarPathfindingProject/Generators/RecastGenerator.cs:2623)
AstarPath.OnDrawGizmos () (at Assets/AstarPathfindingProject/Core/AstarPath.cs:722)
UnityEditor.DockArea:OnGUI()

Some more unpicking. I can see that that the working AstarData.DeserializeGraphsPart() first calls sr.DeserializeExtraInfo(), which allocates the nodes and THEN reassigns each node’s GraphIndex.

However AstarData.DeserializeGraphsPartAdditive() first attempts to reassign the graphs nodeIDs and then calls sr.DeserializeExtraInfo(). Because the newly loaded graphs nodes array hasn’t been initialised yet this has the effect of (redundantly) rewriting the existing graphs’ nodes’ GraphIndex property, but leaving the newly loaded graph’s nodes with the original GraphIndex (which is usually 0).

Swapping these bits around seems to go part way to fixing the problem (but I have no idea what side effects it will have). but jut reveals more problems a bit further along. I’ll keep digging.

More progress: when additively loading a Recast graph, It uses the serialised graph index (from GraphSerializationContext.graphIndex) to call TriangleMeshNode.SetNavmeshHolder() and register itself. this was causing the second loaded recast graph (with serialised graphIndex 0) to overwrite the NavMeshHolder regsitered by the first graph loaded (which really is graphIndex 0).

I’ve fixed this by passing a graphIndex offset to DeserializeExtraInfo:

public void DeserializeExtraInfo ( <strong>int graphIndexOffset</stromg> ) {

And then adding it whenever constructing a GraphSerializationContext:

GraphSerializationContext ctx = new GraphSerializationContext(reader, null, i<strong>+graphIndexOffset</strong>);

When loading normally I pass a graphIndexOffset of 0, and when loading additivly I pass a graphIndexOffset that is equal to the number of existing graphs.

With these changes I can load multiple Grid and Recast graphs successfully (Although the Recast graphs still don’t relocate).

I implemented RecastGenerator.RelocateNodes as follows, and now it all seems to work. I’d be curious if there is anything you think I missed.


public override void RelocateNodes(Matrix4x4 oldMatrix, Matrix4x4 newMatrix) {
    Matrix4x4 inv = oldMatrix.inverse;
    Matrix4x4 m = inv * newMatrix;


    // move our vertices.
    if (_vertices != null) {
        for (int vertexIndex = 0; vertexIndex < _vertices.Length; vertexIndex++) {
            _vertices[vertexIndex] =  ((Int3)m.MultiplyPoint ((Vector3)_vertices[vertexIndex])); 	                        
        }
    }

    //move all the vertices in each tile
    if (tiles != null) {
        for (int tileIndex = 0; tileIndex < tiles.Length; tileIndex++) {
            NavmeshTile tile = tiles[tileIndex];
            if (tile != null) {
                Int3[] tileVerts = tile.verts;
                for (int vertexIndex = 0; vertexIndex < tileVerts.Length; vertexIndex++) {
                    tileVerts[vertexIndex] =  ((Int3)m.MultiplyPoint ((Vector3)tileVerts[vertexIndex])); 	                        
                }

                tile.bbTree = new BBTree(tile);
                for (int nodeIndex = 0; nodeIndex < tile.nodes.Length; nodeIndex++) {
                    TriangleMeshNode node = tile.nodes[nodeIndex];
                    node.UpdatePositionFromVertices();
                    tile.bbTree.Insert(node);
                }
            }
        }
    }

    // Nuke this so it gets rebuilt if necessary
    _vectorVertices = null;

    SetMatrix (newMatrix);
}

Hi

Sorry for the late answer.
You seem to have it all covered though. Thanks for looking in to it yourself.
I will take a look at this and see if I can merge it in to the next version.

We found one more bug to do with loading graphs additively.

In RecastGenerator.DeserializeExtraInfo, when creating the nodes the current code does:

node.GraphIndex = (uint)ctx.graphIndex;
node.DeserializeNode (ctx);

However node.DeserializeNode, overwrites node.Flags, which stamps on GraphIndex (since GraphIndexd is actually stored as a bitfield inside Flags).

swapping these around to

node.DeserializeNode (ctx);
node.GraphIndex = (uint)ctx.graphIndex;

Fixed the problem.

Interestingly NavMeshGenerator seems to get it correct already, but none of the other Generators ever set GraphIndex at all, so will (I suspect) fail if they try to use it after being loaded additivity.

Thanks @tmcsweeney, I will make a note of that

Hi

Just uploaded 3.6.1 beta. It includes your changes and a few more. So hopefully additive loading should now be more stable.