Using TileHandler to additively load Tiled Recast Graphs

Hello,
I have multiple 1km square terrains, with each one having their own tiled recast graph. All the graphs share a common edge, with exactly the same vertexes. Is there a way to get a path that crosses the boundary between adjacent recast graphs?

I believe my code is failing because each recastgraph does not contain both start/end points of the path. In this case the graphmask is doing nothing, right? Let me know if my understanding is correct

int graphMask = (1 << 0) | (1 << 3);
Path p = ABPath.Construct (start, end, null);
p.nnConstraint = NNConstraint.None;
p.nnConstraint.graphMask = graphMask;
StartPath(p, OnPathComplete, graphMask);

What’s the best solution for this problem?

Thank you,
-Mark

Hi

You are not supposed to have multiple graphs for that. Instead use a single large tiled recast graph.

Thanks for the quick response!
The problem is all my terrains are additively loaded in at runtime. There are over 400 total, and only 9 are in the game at any time.

One solution I was thinking of is getting a path to the edge of the current graph, then get the path on the adjacent graph, then add them together.

Or is there another way? Maybe merging graphs together every time a new one is loaded in, so only 1 exists in the game at a time.

Any hints would be greatly appreciated. Thanks,
-Mark

Hi

How large is the total world?
Is there a possibility for you to bake the whole world during development and load the baked navmesh data during runtime all at once?

The world is very large. There are 400 of 1km square terrains. Each 1km square is in it’s own scene and loaded additively during runtime. There is not enough memory to load all at once, so baking one large navmesh wouldn’t work out.

My idea is to have a Tiled RecastGraph in each 1km scene, covering everything. When the scenes are loaded, the new RecastGraph will be added to the AStarPath.active.graph list. I’ve already created an editor script that makes a Tiled RecastGraph for every scene, and I modified the graph flags to allocate enough bits for 511 graphIndexes. (I did a test with 511 graphs and it works great!)

The problem is trying to load in new graphs on the fly. I’ve been spending the last few days trying to get serialization / deserialization to work on a single tiled recast graph. I know this is not how you implemented ser/des, but I’m trying to adapt the code for my needs.

I’m running into problems with the meta data serialization, and the fact that the nodes are only serialized in SerializeExtraInfo. The ser/des works for the first graph element, but any graphs after that do not work since the metadata has the incorrect graphIndex. SerializeNodes then uses the incorrect graphIndex, etc. (Not sure if I am making sense, this is very hard to explain in text.)

Can you think of a better approach for this whole thing? I may just try to write my own generic ser/des code to store the RecastGraph information in my own structure, then feed that into the AStarpath.active during runtime.

Thank you,
-Mark

Hi

Try the latest beta, that will have updated those bits for the graphIndices so that you should be able to use 512 (or maybe 511) graphs, and I think I have fixed the bug with the graphIndex serialization.

I think the best option would be to use a tiled RecastGraph for the whole world, and use an experimental API (I did not intend it to be used for this purpose, I use it for navmesh cutting, but there seem to be A LOT of people that want to use it for this purpose) for loading the graphs directly into the tiles of the graph.

TileHandler handler;
Mesh thenavmesh;

public void Start () {
    handler = new TileHandler(AstarPath.active.astarData.recastGraph);
    TileHandler.TileType tp = handler.RegisterTileType ( thenavmesh, new Int3(0,0,0) );
    handler.LoadTile ( tp, 0, 0, 0, 0);
}

See documentation here: http://arongranberg.com/astar/docs_dev/class_pathfinding_1_1_util_1_1_tile_handler.php

The TileHandler has a method called CreateTileTypesFromGraph which you could use with a bit of hacking to get the TileType for the main tile in each of your scenes. These tile types are little more than just a vertex array and a triangle index array so you should be able to serialize them easily. And then when loading the worlds you could just load them in at the correct index. You don’t even need to convert them to a mesh because the RegisterTileType method is just a thin wrapper around the TileType constructor anyway.

I know this is a bit vague, sorry about that, the API is a bit experimental, but I think it would work for what you are trying to do.

Great! Thanks I’ll give it a try.
Minor thing I noticed, the beta changelog says “Extended the max number of graphs in the inspector to 256”. I’ll probably just increase it by another bit to get the 512.

Yeah… Maybe I just extended it to 256.
I do have bit 18 free, but I did not want to change it because that would require reordering fields and it would break backwards compatibility.
Maybe in the next version I will write an upgrade script and do the change.

If you are going to use the TileHandler you will only need a single graph however.

Hi Aron,
Good news is I’ve got this basically working! Except for one problem. Sometimes the seeker will calculate a path that takes a very long route. The path will almost completely wrap around a whole recast tile even when the start and end points of the path are in adjacent nodes!

Is this a matter of the original node data not being saved correctly?

I’m not following your exact directions, and that may be causing the problem. I am using nearly the exact same code you gave me, but using my own offsets, and storing meshes in the resources folder.

`string meshfilename = “World Data/Navmeshes/” + navMeshName;
Mesh theNavmesh = Resources.Load(meshfilename);
int scaleFactor = -1000;
int tileOffset = 500;
int xPosition = (int)(transform.position.x + tileOffset) * scaleFactor;
int zPosition = (int)(transform.position.z + tileOffset) * scaleFactor;

Int3 meshOffset = new Int3(xPosition, 0, zPosition);

TileHandler.TileType tp = handler.RegisterTileType (theNavmesh, meshOffset);
handler.LoadTile (tp, xGlobalTileCoordinate, zGlobalTileCoordinate, 0, 0);`

I have a problem trying to implement your instructions, or maybe I lack understanding.
My individual scenes are 5x5 tiled recast graphs. I call CreateTileTypesFromGraph() on it to get a list of 25 TileTypes. I then store these TileTypes into a serializable data format (pretty much copied your class and made it public…). Next, I need to call TileHandler.LoadTile() on each TileType for the global RecastGraph. However this global recast has to be huge! My world is 20x24km. At 5x5 per km, this equates to 12000 tiles for the global RecastGraph. Either I run out of bits for TileIndexMask in the global RecastGraph, or I run out of bits for VertexIndexMask in the individual scene 5x5 recast tiles.

Am I missing somethin?

Thanks so much for your help
-Mark

Hi

Ok, 12000 is definitely a bit of a problem.
I guess you are switching between using the ASTAR_RECAST_LARGER_TILES define or not. I think if you try to edit those values to use the middle ground it might work better.
Right now the vertex index mask is either 0xFFFFF or 0xFFF, but if you make it 0xFFFF, then 12000 graphs will fit, and hopefully your tiles will be small enough to fit as well.

Sometimes the seeker will calculate a path that takes a very long route. The path will almost completely wrap around a whole recast tile even when the start and end points of the path are in adjacent nodes!
This could be a floating point problem. Sometimes it can be tricky to get the exact correct offset so that the corners of the tiles line up with each other. Enable Recast Graph -> Show Connections and check those adjacent tiles and see if a connection has been generated between them, if not, they are not perfectly aligned.

I am not sure if this could cause it, but I know that after around 16000, floats starts to run out of precision, so maybe you could try to adjust your world so that it runs from -10000 to +10000 on each axis instead of from 0 to 20000.
Another option could be to edit the Int3.Precision variable (and some similar fields defined in it, it should be obvious which ones), to change it from a precision of 1000 to a precision of 100. I haven’t tested this myself, but I think no pathfinding code should break, however penalties might need to be adjusted. This would (probably) solve the precision problem.

Everything is working great now! I changed VertexIndexMask to 0xFFFF and it works with 12000 tiles and has enough resolution per tile. Now I load in all the TileTypes using serialized data from the original recast graphs. I overloaded the RegisterTileType()

public TileType RegisterTileType (TileType tp) { tileTypes.Add(tp); return tp; }

All my problems were from loading meshes for the source of the TileType instead of the serialized recast TileType data. For some reason when tiles were loaded as meshes, adjacent nodes were not connected along some tile boundardies. I tried different Int3.Precisions, but it didn’t affect it.

Everything is fixed now though, thanks so much! Truly a 5-star asset and support :wink:

Awesome!

Btw, if you want to support the project a bit, it would be really nice if you would rate it and/or review it on the Asset Store : )

Gladly