Dynamic WP Recast Navmesh regenerates tiles too often

Hi,

Our project uses Recast Navmesh actors with bIsWorldPartitioned enabled, set to Dynamic generation. We cannot use invokers to generate the navmesh around players only due to design reasons.

Looking at performance, especially on lower end hardware we noticed that the navmesh was regenerating tiles for various reasons and I’d like to describe what we put in place, if you have general feedback, and possibly see if some of these changes could be brought to the engine to ease our future upgrade process.

The goal we wanted to achieve was to have a base navmesh that is valid and is rebuilt as little as possible at runtime.

When a ANavigationDataChunkActor is streamed in, many navmesh tiles were rebuilt (ARecastNavMesh::OnStreamingNavDataAdded)

Pre-existing elements dirty the navmesh, this is expected since the engine cannot know how pre-existing elements might have changed before streaming in the WP cell. Problem is that for some elements that are non-spatially loaded have huge bounds (i.e. LandscapeHeightfieldCollisionComponent) - these dirty the navmesh with bounds unclamped to the ANavDataChunkActor bounds.

We ended up creating a custom Navmesh actor that inherits from ARecastNavMesh, overriding OnStreamingNavDataAdded to allow filtering out invalidations on specific classes (that by design we know these will never be scaled/moved/.., so we can retain the base navmesh). This was the main thing with WP Recast navmeshes that was “hiding” a lot of other issues that are described after. This required a small tweak to ARecastNavMesh.h (see attachment).

NAVMESH NEEDS TO BE REBUILT always displayed

This caused confusion amongst designers, we fixed it like seen in the attached file. This is not really a fix, what are your recommendations here ?

ISM / HISM do not properly invalidate the navmesh when streamed in

This was reported in these issues, we fixed it like seen in the attached file

[Content removed]

[Content removed]

Some Navlinks are missing from the base navmesh if they are parented to another actor

This was hidden due to constant rebuild of the navmesh, the issue appeared since we have ANavLinkProxy actors parented to other actors in the level. See fix in attached file

Runtime data layers

Overall, it was not clear what is part of the base navmesh when in editor. We added to the custom RecastNavMeshActor a boolean ‘bVisualizeBaseNavmesh’ that simply forbids dirtying the Navmesh when in editor and outside of PIE. It could be useful to have this part of ARecastNavMesh instead.

Custom navmesh build commandlet

The default behavior of the WP navigation build commandlet is to load in all Runtime Data Layers set to initially active, and all editor-only data layers. This caused issues such as some of our Runtime Data Layers were editor-only data layers (i.e. they contain blockout actors), so we ended up with the base navmesh (not always rebuilt at runtime for many reasons anymore) to be built from blockout meshes too.

These editor-only runtime data layers were processed during cook using the WP Cells transformers stack, but the WP Cell transformers stack is not applied when running the WP Navigation build commandlet.

Our solution for this to clarify what the base navmesh is was to create a custom WP Navigation build commandlet that inherits from the default one. This commandlet disables bCanEverAffectNavigation on components / deletes ANavModifierVolume and ANavMeshBoundsVolume part of Runtime Data Layers, unless the RDL is allowed in the world settings

This provided a base navmesh that is predictable and seems always valid, the result of this is greatly reduced number of invalidated tiles when streaming in WP cells at runtime (less main thread stutters).

Thanks in advance for your feedback / thoughts on this topic,

Best,

Hugo

[Attachment Removed]

Hi,

I should also mention the UWorldPartitionNavigationDataBuilder class was modified on our end like so to allow creating a child class:

UCLASS(MinimalAPI) <---- MinimalAPI Added
class UWorldPartitionNavigationDataBuilder : public UWorldPartitionBuilder
{
	UNREALED_API virtual bool PreRun(UWorld* World, FPackageSourceControlHelper& PackageHelper) override; <---- UNREALED_API Added
	UNREALED_API virtual bool RunInternal(UWorld* World, const FCellInfo& InCellInfo, FPackageSourceControlHelper& PackageHelper) override; <---- UNREALED_API Added
	UNREALED_API virtual bool PostRun(UWorld* World, FPackageSourceControlHelper& PackageHelper, const bool bInRunSuccess) override; <---- UNREALED_API Added
};

Best,

Hugo

[Attachment Removed]

The WP navmesh support is very experimental and more of a first draft of how we would approach it. The data layer support is quite rough and limited. I believe runtime data layers were added after our initial efforts and unsurprisingly, there are edge cases in how they are handled.

Q1. Filtering out certain meshes is possible in a single project, but that might not be practical for a general solution as not all projects may have the same guarantee for the actors to be static.

Q2. I know we have made several changes for trying to remove the navmesh needs rebuilt message from being displayed. This happens all the time when playing a WP level for you? I remember a change made a while back to prevent this from appearing in CitySample, but there may be another edge for us to address in a general WP level.

Q3. ISM/HISM being seen for navmesh was working in 5.7 when I last tested it. I think there may still be an issue for PLAs. Did your new function correct for PLAs not having navmesh when loaded?

Q4. Can you elaborate a bit more on what you mean by “navlinks parented to another actor”? There is a limitation for navlinks that cross over cell boundaries that we have an open JIRA to address.

Q5. Data layer support is limited. It is something we would want to address when/if we get back to development for WP navmesh. Including a filter for data layers was something we were trying to accomplish with the navigation data layers in the world settings.

Q6. The custom builder commandlet deriving from the WP navigation builder seems correct for extending functionality. I believe it should be allowable, but likely was forgotten in the initial attempt to get things running. The decision may have also been done to follow the other builders at the time that inherited from the base WP builder rather than inheriting from other derived builders.

-James

[Attachment Removed]

Q2. This happens outside PIE, as long as the recast navmesh actor is WP (even after building paths)

Quite strange. We will look into this. Do you happen to see this on 5.7 as well?

Q3) I did see the issue in 5.6, but upgrading the same project with the issue to run on 5.7 fixed the issue of ISM/HISM not properly updating navigation. I will look into it again to see if I had a random time when it was working as expected.

Q4) Are you attempting to use the NavLinkProxy in a child actor component? Child actor comonents are on the whole a can of worms, and our advice is to not use them. If you need smart link behavior, I would create your own NavLinkCustomComponent where you can supply the link reached logic similar to what NavLinkProxy has. We had another question about navlinks as children at the end of last year where the behavior was inconsistent as well.

[Attachment Removed]

Q2. This is weird and a bug, but we are not really working on WP navmesh at the moment, so I do not know when/if it will get corrected.

Q3. I need to look into this to get a repro going. I thought it had been addressed, but it seems there may still be some issues.

Q4, we have a bug report for this issue. I am not sure why it is happening, but the goal is to have it fixed by 5.8. Link to this on our public issue tracker: https://issues.unrealengine.com/issue/UE-348648

-James

[Attachment Removed]

Hi James,

Thanks for your feedback

Q1. Understood, we’ll keep our changes like that

Q2. This happens outside PIE, as long as the recast navmesh actor is WP (even after building paths)

Q3. To reproduce the issue you can add a breakpoint in `UInstancedStaticMeshComponent::PartialNavigateUpdateForCurrentInstances()` called during `OnRegister()`. If you follow the trail up to `FNavigationSystem::OnObjectBoundsChanged()` -> `FNavigationDataHandler::UpdateNavOctreeElementBounds()` you’ll see that the element is not yet registered in the nav octree so this call is no-op. This is expected since during a component OnRegister(), the component is not yet registered in the nav octree (due to UE.Rollback.Navigation.ComponentShouldWaitForActorToRegister). My guess would be that in your test, you have another code path triggering `UInstancedStaticMeshComponent::PartialNavigationUpdates()` after the initial OnRegister call (maybe a transforms update or else) which hides the issue.

Q4. This is what I meant:

[Image Removed]

Q5/Q6. Understood

Best,

Hugo

[Attachment Removed]

Hi James,

Q2. I can confirm the issue is present in UE5.7 too (this screenshot is after opening the level / after the commandlet ran):

[Image Removed]

Q3. I was able to repro in 5.7, the issue is hidden whenever the construction scripts are re-run (in editor).

The setup is 1 BP in a runtime DLA, the BP contains one static mesh component (cube) and 1 ISM component with 3 instances (cubes).

Toggling on/off the DLA multiple times, at some point the navmesh is only rebuilt for the static mesh component (cases where the ISM correctly invalidate the navmesh seem to come from the actor re-running the construction scripts, this occurs when forcing GC every frame for instance).

No DLA loaded:

[Image Removed]DLA loaded, only the static mesh component invalides the navmesh:

[Image Removed]DLA loaded again, but before doing so, forcing GC every frame for some reason triggers re-run of construction script in editor which causes OnRegister() to be called twice on the ISM -> The area are correctly dirtied

[Image Removed]

I hope this helps narrow down the issue but in my initial post there is a proposed fix for the root cause, to confirm if that is the correct way to go.

Q4. No, it is an actor simply parented in the outliner to another actor (AttachParent/Children), there is a proposed fix in my initial post too

Best,

Hugo

[Attachment Removed]