How to render your own navmesh debug? RAMA/EPIC help please?

I’ve been trying to write a plugin that understands level flow, but I’m a bit stuck and need some debug to help understand where my code is getting it wrong.
What I wanted to do was render the recast navmesh out, rendering each polygon as a color.

I found this code in the engine that looks like a good starting point

Github Navmesh Engine code

but when I grab the relevant functions and paste it in my code (copy and paste at present - I’ll start rewriting it once I actually get it to run) I get a compiler error as follows



1>     Creating library C:\Users\stuart\Documents\Unreal Projects\TanksVsZombies\Plugins\AIDirector\Intermediate/Build/Win64\UE4Editor\Development\UE4Editor-AIDirector.lib and object C:\Users\stuart\Documents\Unreal Projects\TanksVsZombies\Plugins\AIDirector\Intermediate/Build/Win64\UE4Editor\Development\UE4Editor-AIDirector.exp
1>AIDirectorDebugComponent.cpp.obj : error LNK2019: unresolved external symbol "public: __cdecl FRecastGeometryCache::FRecastGeometryCache(unsigned char const *)" (??0FRecastGeometryCache@@QEAA@PEBE@Z) referenced in function "public: void __cdecl UAIDirectorDebugComponent::GatherData(struct FNavMeshSceneProxyData &)const " (?GatherData@UAIDirectorDebugComponent@@QEBAXAEAUFNavMeshSceneProxyData@@@Z)
1>C:\Users\stuart\Documents\Unreal Projects\TanksVsZombies\Plugins\AIDirector\Binaries\Win64\UE4Editor-AIDirector.dll : fatal error LNK1120: 1 unresolved externals


I knew my code would need the NavMesh, and it turns out there were a couple of other dependencies that needed adding…



PublicDependencyModuleNames.AddRange(new string] { "AIModule", "Core", "CoreUObject", "Engine",  "InputCore", "RHI", "UMG", "ShaderCore", "Slate", "SlateCore", "Navmesh" });


But I cant figure out what I need to include to resolve FRecastGeometryCacheFRecastGeometryCache(unsigned char const *)

I was hoping Rama or someone from Epic might be able to help here as they obviously have lots of experience with interrogating the navmesh data. Rama shows lots of videos where he renders the navmesh for debug purposes.

Thanks in advance

Go to Engine\Source\Runtime\Engine\Public\AI\Navigation\RecastNavMeshGenerator.h
and change this line of code:

struct FRecastGeometryCache
to
struct ENGINE_API FRecastGeometryCache

then recompile whole the engine code and You should be able to use FRecastGeometryCache::FRecastGeometryCache in Your module, but if you can’t compile the engine code, you should wait for EPIC guys or some good samaritan who will make pull request with this piece of code :wink:

Regards

Thanks but I dont think any of the others that have achieved a visible navmesh are recompiling the whole engine… Rama has so many videos that have a visible navmesh that I’m sure he’d have said if he recompiled the engine to achieve it.

There is more than one way to skin a cat, and I think I’ve found a couple of approaches I can combine to get what I want. I think I’m getting somewhere, and I’ll post up what I’ve done if I get a working solution.

If the struct isn’t exported, you won’t be able to access it even if you include the required files.

Without ENGINE_API, it probably isn’t exported.

Yeah. I’ve switched tracks. You can get the vertexes of the tiles from the navmesh . I’m now trying to figure out how to plug that into a custom mesh component.

I wasn’t able to render it in the way I wanted using a custom mesh. I was able to draw the mesh, but the mesh still came out grey every time. Being able to get down to the polygon level meant I could use debuglines to get the output I wanted.

Is anyone else interested in how this is done? And can anyone else suggest how to get a fully rendered mesh in color. (This is what I tried when it came out grey, but the colors were the values shown in the image above)?



/** Scene proxy */
class FFlowMeshSceneProxy : public FPrimitiveSceneProxy
{
public:

	FFlowMeshSceneProxy(UFlowMeshComponent* Component)
		: FPrimitiveSceneProxy(Component)
		, MaterialRelevance(Component->GetMaterialRelevance(GetScene().GetFeatureLevel()))
	{
		// Add each triangle to the vertex/index buffer
		for (int TriIdx = 0; TriIdx<Component->ColoredMeshTris.Num(); TriIdx++)
		{
			FColoredMeshTriangle& Tri = Component->ColoredMeshTris[TriIdx];

			const FVector Edge01 = (Tri.Vertex1 - Tri.Vertex0);
			const FVector Edge02 = (Tri.Vertex2 - Tri.Vertex0);

			const FVector TangentX = Edge01.GetSafeNormal();
			const FVector TangentZ = (Edge02 ^ Edge01).GetSafeNormal();
			const FVector TangentY = (TangentX ^ TangentZ).GetSafeNormal();

			FDynamicMeshVertex Vert0;
			Vert0.Position = Tri.Vertex0;
			Vert0.Color = Tri.Color;
			Vert0.SetTangents(TangentX, TangentY, TangentZ);
			int32 VIndex = VertexBuffer.Vertices.Add(Vert0);
			IndexBuffer.Indices.Add(VIndex);

			FDynamicMeshVertex Vert1;
			Vert1.Position = Tri.Vertex1;
			Vert1.Color = Tri.Color;
			Vert1.SetTangents(TangentX, TangentY, TangentZ);
			VIndex = VertexBuffer.Vertices.Add(Vert1);
			IndexBuffer.Indices.Add(VIndex);

			FDynamicMeshVertex Vert2;
			Vert2.Position = Tri.Vertex2;
			Vert2.Color = Tri.Color;
			Vert2.SetTangents(TangentX, TangentY, TangentZ);
			VIndex = VertexBuffer.Vertices.Add(Vert2);
			IndexBuffer.Indices.Add(VIndex);
		}

		// Init vertex factory
		VertexFactory.Init(&VertexBuffer);

		// Enqueue initialization of render resource
		BeginInitResource(&VertexBuffer);
		BeginInitResource(&IndexBuffer);
		BeginInitResource(&VertexFactory);

		// Grab material
		Material = UMaterial::GetDefaultMaterial(MD_Surface);
	}


Very nice workaround :slight_smile:

I’m almost certain I’ll find this useful a bit later on, bookmarked it for future reference!

If you’re using a debug or development build, you could use ‘DrawDebugSolidPlane’ or even DrawMesh, and pass in the vertices you’ve got there.

Sorry, the code above is what DIDN’T work (in the hopes someone could give me a hint). I will check out DrawDebugSolidPlane, this thing is just for level developers to visualize flow and active area sets in their levels.

This is how you get the full mesh data. Its basically one of Rama’s posts that I found, simplified to just the bits I needed and then a bit of digging around in the API to figure out what else I could use (GetPolysInTile)…

How to work around every single poly in the navmesh



void AAIDirectorPawn::GenerateFlowData(FVector Start, FVector End)
{
	UNavigationSystem* NavSys = GetWorld()->GetNavigationSystem();
	if (!NavSys)
		return;


	UNavigationPath* Path = // Magic sauce here;

	const ARecastNavMesh* NavMesh = Cast<ARecastNavMesh>(NavSys->GetMainNavData(FNavigationSystem::ECreateIfEmpty::Create));
	if (NavMesh == nullptr)
		return;


	TArray<FNavPoly> TilePolys;
	for (int32 TileIndex = 0; TileIndex < NavMesh->GetNavMeshTilesCount(); TileIndex++)
	{

		//CHECK IS VALID FIRST OR WILL CRASH!!!
		// use continue in case the valid polys are not stored sequentially
		if (!NavMesh->GetNavMeshTileBounds(TileIndex).IsValid)
			continue;

		NavMesh->GetPolysInTile(TileIndex, TilePolys);
		//Add them all!
		for (int32 Idx = 0; Idx < TilePolys.Num(); Idx++)
		{
		        // Magic sauce to calculate flow value of polygon here
			NavFlowData.AddUnique(PolyData);
		}
	}

	NavFlowData.Sort(AAIDirectorPawn::FlowSort);

}


A simple debug function to render it out as wireframe



bool AAIDirectorPawn::RenderFlowWireframe()
{
	UNavigationSystem* NavSys = GetWorld()->GetNavigationSystem();
	if (!NavSys)
		return true;
	
	const ARecastNavMesh* NavMesh = Cast<ARecastNavMesh>(NavSys->GetMainNavData(FNavigationSystem::ECreateIfEmpty::Create));
	if (NavMesh == nullptr)
		return true;

	TArray<FVector> Verts;
	for (auto& PolyData : NavFlowData)
	{
		Verts.Reset();
		NavMesh->GetPolyVerts(PolyData.PolyRef, Verts);

		//const FColor TestPolyColor = FColor::Green;

		//Lerp Color
		FLinearColor LinearColor = FMath::Lerp(FLinearColor::Red, FLinearColor::Blue, PolyData.FlowValue);
		FColor TestPolyColor = LinearColor.ToFColor(true);
		for (int32 VertIdx = 0; VertIdx < Verts.Num() - 2; VertIdx++)
		{
			const FVector Pt0 = Verts[VertIdx];
			const FVector Pt1 = Verts(VertIdx + 1)];
			const FVector Pt2 = Verts(VertIdx + 2)];

			DrawDebugLine(GetWorld(), Pt0 + 10.0f * 0.5f, Pt1 + 10.0f * 0.5f, TestPolyColor, false
				, /*LifeTime*/-1.f, /*DepthPriority*/0
				, /*Thickness*/10.0f);

			DrawDebugLine(GetWorld(), Pt1 + 10.0f * 0.5f, Pt2 + 10.0f * 0.5f, TestPolyColor, false
				, /*LifeTime*/-1.f, /*DepthPriority*/0
				, /*Thickness*/10.0f);

			DrawDebugLine(GetWorld(), Pt2 + 10.0f * 0.5f, Pt0 + 10.0f * 0.5f, TestPolyColor, false
				, /*LifeTime*/-1.f, /*DepthPriority*/0
				, /*Thickness*/10.0f);
		}

		const FVector Pt0 = Verts[0];
		const FVector Pt1 = Verts(Verts.Num() - 2)];
		const FVector Pt2 = Verts(Verts.Num() - 1)];

		DrawDebugLine(GetWorld(), Pt0 + 10.0f * 0.5f, Pt1 + 10.0f * 0.5f, TestPolyColor, false
			, /*LifeTime*/-1.f, /*DepthPriority*/0
			, /*Thickness*/10.0f);

		DrawDebugLine(GetWorld(), Pt1 + 10.0f * 0.5f, Pt2 + 10.0f * 0.5f, TestPolyColor, false
			, /*LifeTime*/-1.f, /*DepthPriority*/0
			, /*Thickness*/10.0f);

		DrawDebugLine(GetWorld(), Pt2 + 10.0f * 0.5f, Pt0 + 10.0f * 0.5f, TestPolyColor, false
			, /*LifeTime*/-1.f, /*DepthPriority*/0
			, /*Thickness*/10.0f);
	}
	return false;
}



You know the engine has a navigation viewmode already? If you’re in the editor (and navigation is set up) pressing ‘P’ (default) or clicking the “Show” button (next to “Perspective” and “Lit” or whatever viewmode you’re in currently) and then selecting “Navigation” in the viewport will show it. I guess you can manually trigger the same action from code. Although I didn’t look at all of it (whether it’s possible to access everything required in both editor and ingame) but if you’re interested I’d suggest starting with <path/to/UE4>/Engine/Source/Editor/LevelEditor/Public/LevelViewportActions.h and <path/to/UE4>/Engine/Source/Editor/LevelEditor/Private/LevelViewportActions.cpp (FLevelViewportCommands::RegisterCommands and FLevelViewportCommands::ShowFlagCommands). From here you can find the existing navigation rendering code and probably call it from your code.

Edit: I think I found it, check out UNavigationPath::DrawDebug. It is protected but you can get access to SharedPath which is used by it. All you need is a Canvas…
See files Engine/Source/Runtime/Engine/Classes/AI/Navigation/NavigationPath.h Engine/Source/Runtime/Engine/Classes/AI/Navigation/NavigationPath.cpp and Engine/Source/Runtime/Engine/Classes/AI/Navigation/NavigationData.h

So your code would look something like this (untested):


UCanvas* Canvas = <Get Canvas from somewhere, viewport client, HUD, ...?>; //isn't even used in FNavigationPath::DebugDraw so may as well be 'nullptr'
UNagivationPath* NavigationPath = <Get UNagivationPath from somewhere, TObjectIterator, ...?>;
FColor PathColor = FColor::White; //normally NavigationPath->DebugDrawingColor, initialized to White in UNagivationPath::UNagivationPath and set in UNavigationPath::EnableDebugDrawing() which is never called from anywhere as far as I can tell (searching entire solution came up with two things only: definition and declaration.
bool bPersistent = false; //default used in UNavigationPath
const uint32 NextPathPointIndex = 0; //function argument which defaults to 0 if not passed in

if (NavigationPath)
{
	FNavPathSharedPtr SharedPath = NavigationPath->GetPath();
	if (SharedPath.IsValid())
	{
		SharedPath->DebugDraw(SharedPath->GetNavigationDataUsed(), PathColor, Canvas, bPersistent, NextPathPointIndex);
	}
}

I think you mis-understood what I am trying to achieve. I’m not trying to draw a path I’m trying to render the entire mesh showing the value of the flow at each node using a color value.

I know about using P to display the mesh, but all that does is show you the mesh. I’m trying to show the mesh with the flow data that I’ve generated.

Thanks for suggesting stuff though. If you can think of a way of showing the navmesh in the way I want please shout back (or if you think the path stuff can display the entire mesh and I haven’t understood you correctly)

Hey!
I know it’s an old post but I need to render a nav mesh on runtime but I have no idea of how to do it.
Did you manage to finally draw the nav mesh?
If you acquired it, could you share your solution?
Thanks

The closest I got was whats in the screenshot. In the end that was as good as I needed, so I moved onto trying to figure out why bots stop following the navpath after you kill them off about 30 or 40 times (never figured that out either). The working code for the polyogon outline version is in post #8.