Dynamic navmesh incorrectly placed after world origin shift

Version: Im on vanilla 4.14 (no hotfixes)

Replication steps:
Ive replicated it in content examples for 4.14. Pick one long corridor map (i used Volumes map), add in navmesh bounds(centered on the player spawn ideally), stretch it across the map so it covers the floor.

Edit the players pawn, add event for on release keypress (F for example) and call exec node “SetWorldOrigin” (that sets the world origin to current players position)

Also add navigation invoker on the pawn. Ive used inner radius 1000 and outer 1500.

Setup the dynamic navigation generation (i cant find the link, but theres video with mieszkoZ where he explains the runtime navmesh generation) Its in project settings.

Then run PIE, use “show collision” in console, turn towards the corridor and walk forward, you will see the navmesh generate before you, then when you press F, the world origin is shifted, and then when you walk around the corridor and generate new navmesh, it will be incorrectly placed. (to best show the incorrect offset is to walk only a bit away from the center of the nav mesh bounds, so the resulting navmesh is offset only slightly).

Hi Mtrx,

Thanks for the report. I was able to reproduce this behavior and I’ve created JIRA UE-41293. Our developers will be investigating further. Follow that link to monitor the status of the bug report.

Cheers,

TJ

Hi, I run into a similar issue with static Navmeshes. Maybe this is the same bug:
When I start playing in a Streaming Level all Navmeshes are correctly loaded. If this level is unloaded and loaded again (after shifting world origin) the navmesh is loaded at a different location.

I’ve implemented a dirty fix for my issue. Maybe this helps to implement a good one. My changes are marked with HACK START/END:

TArray<uint32> URecastNavMeshDataChunk::AttachTiles(FPImplRecastNavMesh& NavMeshImpl)
{
	TArray<uint32> Result;
	Result.Reserve(Tiles.Num());

#if WITH_RECAST	
	check(NavMeshImpl.NavMeshOwner && NavMeshImpl.NavMeshOwner->GetWorld());
	const bool bIsGame = NavMeshImpl.NavMeshOwner->GetWorld()->IsGameWorld();
	dtNavMesh* NavMesh = NavMeshImpl.DetourNavMesh;

	if (NavMesh != nullptr)
	{
		// HACK START!!!
		FIntVector OffsetInt = NavMeshImpl.NavMeshOwner->GetWorld()->OriginLocation;
		FVector Offset((float)OffsetInt.X, (float)OffsetInt.Y, (float)OffsetInt.Z);
		NavMeshImpl.ApplyWorldOffset(Offset, true);
		// HACK END!!!

		for (FRecastTileData& TileData : Tiles)
		{
			if (TileData.TileRawData.IsValid())
			{
				// Attach mesh tile to target nav mesh 
				dtStatus status = NavMesh->addTile(TileData.TileRawData->RawData, TileData.TileDataSize, DT_TILE_FREE_DATA, 0, &TileData.TileRef);
				if (dtStatusFailed(status))
				{
					TileData.TileRef = 0;
					continue;
				}

				if (bIsGame)
				{
					// We don't own tile data anymore it will be released by recast navmesh 
					TileData.TileDataSize = 0;
					TileData.TileRawData->RawData = nullptr;
				}
				else
				{
					// In the editor we still need to own data, so make a copy of it
					TileData.TileRawData->RawData = DuplicateRecastRawData(TileData.TileRawData->RawData, TileData.TileDataSize);
				}

				// Attach tile cache layer to target nav mesh
				if (TileData.TileCacheDataSize > 0)
				{
					const dtMeshTile* MeshTile = NavMesh->getTileByRef(TileData.TileRef);
					check(MeshTile);
					int32 TileX = MeshTile->header->x;
					int32 TileY = MeshTile->header->y;
					int32 TileLayerIdx = MeshTile->header->layer;
					FBox TileBBox = Recast2UnrealBox(MeshTile->header->bmin, MeshTile->header->bmax);

					FNavMeshTileData LayerData(TileData.TileCacheRawData->RawData, TileData.TileCacheDataSize, TileLayerIdx, TileBBox);
					NavMeshImpl.AddTileCacheLayer(TileX, TileY, TileLayerIdx, LayerData);

					if (bIsGame)
					{
						// We don't own tile cache data anymore it will be released by navmesh
						TileData.TileCacheDataSize = 0;
						TileData.TileCacheRawData->RawData = nullptr;
					}
					else
					{
						// In the editor we still need to own data, so make a copy of it
						TileData.TileCacheRawData->RawData = DuplicateRecastRawData(TileData.TileCacheRawData->RawData, TileData.TileCacheDataSize);
					}
				}

				Result.Add(NavMesh->decodePolyIdTile(TileData.TileRef));
			}
		}

		// HACK START!!!
		NavMeshImpl.ApplyWorldOffset(-Offset, true);
		// HACK END!!!
	}

#endif// WITH_RECAST

	UE_LOG(LogNavigation, Log, TEXT("Attached %d tiles to NavMesh - %s"), Result.Num(), *NavigationDataName.ToString());
	return Result;
}