Multiple SupportedAgent navmesh setup & alternate collision profiles

Howdy,

We were trying to get a new navmesh set up for a larger creature in our game, so I figured this could be done through Supported Agents. I set up two supported agents: The first keeps the default values while the second is the one for the big guy.

The test level I have set up contains a 200m x 200m Floor, with a NavMeshBoundsVolume with transform scale (110,110,200) which is overkill vertically, but I figured better safe than sorry. In the World Outliner, I do see RecastNavMesh-Default and RecastNavMesh-Big. The Default navmesh is generating properly, but the Big navmesh, while navigation claims to be built, doesn’t appear to generate (meaning when I press P to show navigation in editor, the Big navmesh doesn’t even show up). I tried running the PIE as well, using “Show Navigation” and trying “CycleNavDrawn” (which shows up as a predicted command, but has console returning “Command Not Recognized” and isn’t working for me), but again, only the Default navmesh would show.

I tried using the NavigationTestingActor - it always points at the origin in error, so I figure something is messed up in my Supported Agents settings, as my characters do not move (they do move without any specification of Supported Agents). Are there limits to nav agent radius or height that may be messing with things here? Any insight on this would be very helpful.

A note off of that, as well: is there a default collision profile that the navmesh generation is performed with? We wanted to ignore certain pieces of geometry when generating the Big navmesh. I saw a post involving UPrimitiveComponent::IsNavigationRelevant, which checks for the pawn or vehicle collision response to be set to Block. Is there a particular way of handling this on a per-navmesh basis? For instance, with the Default navmesh, objects like lampposts should still affect the navmesh, but for the Big navmesh, lampposts should not block the navigation.

Thank you for your time in advance.

Pressing P / setting Navigation show flag will show only navmesh associated with default agent. Have you tried checking bEnableDrawing flag in RecastNavMesh-Big? It should show it in editor, as long as Navigation show flag is on. You can also switch LogNavigation vebosity to Log or lower ( log lognavigation verbose ), if navmesh is generating it will spam in output log.

There might be a problem with voxelization params though. RecastNavMesh: CellSize, CellHeight, TileSizeUU are shared across all navmeshes through config. Since you’re using huge radius and height values it might be getting tiles with single/none cells, while it should really fit at least 10x10? agents in single tile.

You can prevent that from happening by adding custom class derived from ARecastNavMesh, setting different generation params depending on agent type (e.g. PostLoad + PostInitProperties overrides) and using that class in your SupportedAgents entries. Sounds like something that sound be handled automatically by engine, but that’s the first time I’m seeing such discrepancy between agents.

Custom collision export is possible, but it requires adding FNavigationRelevantData.ShouldUseGeometryDelegate param in all results returned by GetNavigationData() from engine classes, so… it’s not the best solution. What you can use instead are navigation areas. Using lamp post example: StaticMesh gives you NavCollision param, which can define mesh as obstacle ( bIsDynamicObstacle flag). It will no longer export geometry (so no navmesh on top), but instead marks navmesh with given area. You can either use custom class derived from UNavAreaMeta_SwitchByAgent
to have two separate area classes for each navmesh, or just create obstacle area supported only by one navmesh (checkbox with name of agent in area blueprint / bSupportsAgent* property in code).

Downside of using NavCollision approach are frequent errors, because artist forgot to add navigation setup to new mesh. Fortnite project is using agent depended marking heavily and we chosen to have navigation flags in actor instead, so it can be applied to all child classes of given type. Check UStaticMeshComponent.bOverrideNavigationExport, UStaticMeshComponent.bForceNavigationObstacle and UNavigationSystem.GetDefaultObstacleArea() for details.

If you need actors that export collisions only for single navmesh type (to create walkable navmesh patches on them), you’ll have to create custom class and override GetNavigationData() to include ShouldUseGeometryDelegate.

Awesome, thank you for all the information!

I changed CellHeight from the default 10.0 value up to 16.0, the Tile Size UU increased itself to 12800.0 (based on clamping), and the mesh then generated nicely (overlaying the Default mesh when Enable Drawing was set). If the navmesh no longer draws for some reason, I will use the LogNavigation as you suggested to test it.

Some more questions, if I may:

  1. Since Enable Drawing is transient, it never gets saved (when set through the RecastNavMesh settings- I set bEnableDrawing=True in the DefaultEngine.ini), correct?

  2. I’m not sure I understand the lower bound for CellHeight, and this may go into a larger discussion outside of the original question, but is there a means of calculating the minimum CellSize/CellHeight necessary based on the agent’s radius/height? Is there also a minimum Agent Radius that will result in an immediate failure to construct navmesh?

With the bIsDynamicObstacle flag, the navmesh still cuts out the area for objects, which is what we are trying to avoid. If the Big wants to walk down a path with a lamppost in the center, it should not try to walk around it, but through it. I guess the functionality I’m looking for is something along the lines of “Can Ever Affect Navigation” being unchecked with respect to certain navmeshes instead of all of them, which leads me back to looking at UPrimitiveComponent::IsNavigationRelevant. I will dig some more into this.

Ah, excellent, changing the Area Class setting on the static mesh to NavArea_Default did the trick. I’ll have a look at that with NavAreaMeta_SwitchByAgent. Thanks!

Another question about these settings:

With regards to setting a static mesh component to be a dynamic obstacle, what is the cost tradeoff for setting it as dynamic if the object never moves? For instance, in the case of a small pile of rubble, we will probably have a lot of these strewn about, but they will be static in all cases aside from how we want the navmesh to generate for the Big.

Oh wow, that’s very nice!

I ran into another dilemma today - what happens if we want to keep the navmesh on top of a static mesh component for the default characters?

Setting bIsDynamicObstacle doesn’t export it, but we may end up wanting to add nav link proxies in order for NPCs to reach the top of the static mesh component. I dug a little bit into UStaticMeshComponent::DoCustomNavigableGeometryExport and UStaticMeshComponent::GetNavigationData but didn’t have much luck finding where the top area of the component was actually removed (or removed from consideration, if before generation) from the navmesh data.

  1. bEnableDrawing flag is changed at runtime in ANavigationData.SetSupportsDefaultAgent. You’ll need to modify that code to make it enabled by default.

  2. CellSize & CellHeight defines size of voxel grid which will be used later to generate navmesh on. It’s mostly about accuracy vs performance + hard limits from recasts type sizes.

CellSize (XY) needs to be low enough to allow agents passing through narrow corridors. Take ceil(AgentRadius/CellSize) cell from each side, 1? for generation artifacts and if there’s something left navmesh will be able to generate. Usually it’s ~3x smaller than agent radius, but again, lower it gets, more memory and time will be required.

CellHeight (Z) works with AgentMaxStepHeight and MaxAgentHeight params. Voxel cells will be able to connect vertically up to ceil(StepHeight/CellHeight), and require at least ceil(MaxHeight/CellHeight) free space up. Usually it’s ~8…10x smaller than height of agent.

  1. Dynamic obstacle cuts out holes because default area is set to NavArea_Null for all agent types. If one of agents will be applying either NavArea_Default or simply will not support area type, navmesh will not change.

Dynamic obstacle is actually cheaper than regular collision export. In Fortnite project where we use LOTS of dynamic obstacles, runtime rebuild without collision changes (e.g. player destroyed a lamp post = dynamic nav obstacle) is ~10x faster. If you don’t have any runtime rebuilds, you don’t need to worry about time costs at all, since navmesh will just be there.

Navmesh on top requires collisions to generate on. Dynamic obstacles are not exporting them at all. From what you’re saying, one agent requires regular collisions and another doesn’t need this obstacle on navmesh at all. Since navigation data in octree is used by all agents during navmesh generation and exported only once from component, you’ll have to rely on ShouldUseGeometryDelegate in struct returned by GetNavigationData.

Easiest way would be creating project specific actor class for handling those meshes and setting delegate in GetNavigationData override.