ProceduralGraphMover with RecastGraph long distance teleport problem

I’m using origin shifting in our 2d game so both the ProceduralGraphMover and agents need to teleport long distances when it happens.

 tr.position = Vector2.zero; // Teleport target transform of GraphMover to origin
 procGraphMover.UpdateGraph(false);
 eventRelay.Raise_On_OriginShift(_shift); // Teleports agents

I also tried this:

recastGraph.forcedBoundsCenter -= tr.position;
recastGraph.Scan();

But both approaches don’t work. Agent teleports to outer bound of recast graph at worst, it stutters visibly at best. What’s the correct way to approach this problem?

So you have a (seemingly non-player) agent far off from Vector2.zero, then move them to zero, but when you do, they end up not positioned correctly? I’m assuming tr is specifically your PreceduralGraphMover in a standalone GameObject? Let me know if this is correct so I’m not giving you the run-around :sweat_smile:. Although, I might have to do more research on origin shifting- seems straightforward but I also don’t want to pretend I understand something that I might not :slight_smile:

That said, the first thing that comes to mind is if you’re using the Teleport method to move your agent? Or are you directly moving it’s position/a parent’s position or something else? I’m curious what’s going on in Raise_On_OriginShift()

Let me know what you think/find!

Apologies for the bad explanation. ProceduralGraphMover is targeting SimulationZone(which is like a reality bubble, stuff outside of it is disabled or lightly simulated etc). To prevent precision errors whole world shifts back to origin after a certain threshold is passed. When this origin shift happens ProceduralGraphMover rightfully assumes that it’s target (SimulationZone) changed it’s position completely so does a full scan to update the recast graph however there’s no need for that because everything moved together so the recast mesh should stay the same. This needless full update causes a fps drop. Yes I’m using Teleport with agents but their constrainInsideGraph messes up during the origin shift. Here’s the bad solution I came up with to these problems

This code is executed during origin shift and is relocating recast graph manually:

AstarPath.active.AddWorkItem(() => {
recastGraph.forcedBoundsCenter += shiftOffset;
recastGraph.RelocateNodes(recastGraph.CalculateTransform());
});

Now because I change the bounds center, ProceduralGraphMover.RecastGraphTileShift() thinks that there’s no change in delta and to prevent a full graph scan I modified NavmeshBase.RelocateNodes() and commented out both DirtyBounds(bounds):

public void RelocateNodes (GraphTransform newTransform) {
...
/* DirtyBounds(bounds); */
...
}

I also changed ProceduralGraphMover.Update to ProceduralGraphMover.ManualUpdate so I can control it myself to prevent it from updating during an origin shift. (shiftOffset == Vector3.zero) means no origin shift happening in this frame

if (shiftOffset == Vector3.zero) procGraphMover.ManualUpdate();

I believe this works because I’m not seeing the big fps drop during the origin shift anymore and no problems with recast graph but I’m not %100 sure. I “solved” agent teleportation problem by setting constrainInsideGraph to false during the origin shift and then setting it to true one frame later after the Teleport like this:

public void TeleportNPC(Vector2 pos)
{
     ...
     Agent.Teleport(pos);
     DelayedEnable().Forget();
}

private async UniTask DelayedEnable()
{
     await UniTask.NextFrame();
     Agent.constrainInsideGraph = true;
}

Now all of these hacks work (I guess) but it doesn’t feel right. I searched the forums and doc but couldn’t find anything about origin shifting with ProceduralGraphMover. Is there a better way to handle this? Am I successfully tricking ProceduralGraphMover into believing that it didn’t move during the origin shift at all?

I still have the same performance hit. Tldr: How to prevent complete recast graph rebuild when teleporting it? There’s nothing about this here or the docs. You can’t have large worlds without origin shifting or something similar so what I’m asking is not some rare edge case @tealtxgr @aron_granberg

Hi there, sorry for the delay, looks like this one got lost in the cracks.

Your solution above seemed like it was pretty good, but if it’s still having performance issues that’s not good :frowning: I’ve definitely never ran across any mention of origin shifting, either on the forums or on the documentation. So while you may not consider it an edge case, it’s certainly novel, at least as far as I’m aware.

I want to ask really quick, what’s the depth of the performance hit you’re seeing? This isn’t an attempt to gauge the importance or anything like that, just trying to figure out where the issue may lie and what can be done to circumvent it.

Also, I’d be remised if I didn’t bring up the performance tips in the ProceduralGraphMover documentation. You’ve probably either noticed this section already or have already implemented these changes, but I did want to call notice to them just in case they could be of use:

General advice:

  • Turn on multithreading (A* Inspector → Settings)
  • Make sure you have ‘Show Graphs’ disabled in the A* inspector, since gizmos in the scene view can take some time to update when the graph moves, and thus make it seem like this script is slower than it actually is.

For recast graphs:

  • Rasterize colliders instead of meshes. This is typically faster.
  • Use a reasonable tile size. Very small tiles can cause more overhead, and too large tiles might mean that you are updating too much in one go. Typical values are around 64 to 256 voxels.
  • Use a larger cell size. A lower cell size will give better quality graphs, but it will also be slower to scan.

The graph updates will be offloaded to worker threads as much as possible.

If any of these help let us know, and if none of these are particularly useful, we can continue to look into this.

Hi

Doing an origin shift like this should work:

var graph = AstarPath.active.data.recastGraph;
var offset = new Vector3(200, 0, 0);

AstarPath.active.AddWorkItem(() => {
	graph.forcedBoundsCenter += offset;
	graph.RelocateNodes(graph.CalculateTransform());
});
AstarPath.active.FlushWorkItems();

ai.Teleport(ai.position + offset);
ai.destination += offset;

I see no problems with snapping to the navmesh when using this order of calls.

Sorry for the late reply. Sure I tried the documented approach before. This is normal update while moving:


This is during origin shift:

Spike at the end of graph is when shift happens:
graph
It’s somewhat inconsistent too, another shift:


And here’s the result with changes I made (posted above). It doesnt help at all, still recalculating the whole graph

Are you sure you did it the exact same?

I have a unit test that validates that after the origin shift, it doesn’t try to update the whole graph.

Yes but I think I forgot to write that origin shift is happening at FixedUpdate