Files
Brice Criswell 73e2f7d494 Fix no-Recast builds [Github Pull Request: 9579]
#jira UE-164492
#rb Maxime.Mercier, Aris.Theophanidis

[CL 26785685 by Brice Criswell in 5.3 branch]
2023-08-02 16:01:19 -04:00

3818 lines
124 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "NavMesh/RecastNavMesh.h"
#include "Misc/Paths.h"
#include "EngineGlobals.h"
#include "Engine/World.h"
#include "NavigationSystem.h"
#include "Engine/Engine.h"
#include "DrawDebugHelpers.h"
#include "Misc/ConfigCacheIni.h"
#include "EngineUtils.h"
#include "NavMesh/RecastHelpers.h"
#include "NavMesh/RecastVersion.h"
#include "NavAreas/NavArea.h"
#include "NavAreas/NavArea_Null.h"
#include "NavAreas/NavArea_Default.h"
#include "NavAreas/NavArea_LowHeight.h"
#include "NavLinkCustomInterface.h"
#include "NavMesh/RecastNavMeshDataChunk.h"
#include "NavMesh/RecastQueryFilter.h"
#include "VisualLogger/VisualLogger.h"
#include "WorldPartition/NavigationData/NavigationDataChunkActor.h"
#include "Math/Color.h"
#include "NavigationDataHandler.h"
#if WITH_EDITOR
#include "EditorSupportDelegates.h"
#include "ObjectEditorUtils.h"
#include "WorldPartition/WorldPartition.h"
#include "WorldPartition/WorldPartitionEditorLoaderAdapter.h"
#include "WorldPartition/LoaderAdapter/LoaderAdapterShape.h"
#include "LevelInstance/LevelInstanceSubsystem.h"
#endif
#if WITH_RECAST
#include "Detour/DetourAlloc.h"
#endif // WITH_RECAST
#include "NavMesh/NavMeshRenderingComponent.h"
#include "UObject/FortniteMainBranchObjectVersion.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(RecastNavMesh)
#if WITH_RECAST
/// Helper for accessing navigation query from different threads
#define INITIALIZE_NAVQUERY(NavQueryVariable, NumNodes) \
dtNavMeshQuery NavQueryVariable##Private; \
dtNavMeshQuery& NavQueryVariable = IsInGameThread() ? RecastNavMeshImpl->SharedNavQuery : NavQueryVariable##Private; \
NavQueryVariable.init(RecastNavMeshImpl->DetourNavMesh, NumNodes);
#define INITIALIZE_NAVQUERY_WLINKFILTER(NavQueryVariable, NumNodes, LinkFilter) \
dtNavMeshQuery NavQueryVariable##Private; \
dtNavMeshQuery& NavQueryVariable = IsInGameThread() ? RecastNavMeshImpl->SharedNavQuery : NavQueryVariable##Private; \
NavQueryVariable.init(RecastNavMeshImpl->DetourNavMesh, NumNodes, &LinkFilter);
#endif // WITH_RECAST
namespace UE::NavMesh::Private
{
// Max tile size in voxels. Larger than this tiles will start to get slow to build.
constexpr int32 ArbitraryMaxTileSizeVoxels = 1024;
// Min tile size on voxels. Smaller tiles than this waste computation during voxelization because the border are will be larger than usable area.
constexpr int32 ArbitraryMinTileSizeVoxels = 16;
// Minimum tile size in multiples of agent radius.
constexpr int32 ArbitraryMinTileSizeAgentRadius = 4;
/** this helper function supplies a consistent way to keep TileSizeUU within defined bounds */
float GetClampedTileSizeUU(const float InTileSizeUU, const float CellSize, const float AgentRadius)
{
const float MinTileSize = FMath::Max3(RECAST_MIN_TILE_SIZE, CellSize * ArbitraryMinTileSizeVoxels, AgentRadius * ArbitraryMinTileSizeAgentRadius);
const float MaxTileSize = FMath::Max(RECAST_MIN_TILE_SIZE, CellSize * ArbitraryMaxTileSizeVoxels);
return FMath::Clamp<float>(InTileSizeUU, MinTileSize, MaxTileSize);
}
// These should reflect the property clamping of FNavMeshResolutionParam::CellSize.
// Minimum cell size.
constexpr float ArbitraryMinCellSize = 1.0f;
// Maximum cell size.
constexpr float ArbitraryMaxCellSize = 1024.0f;
float GetClampedCellSize(const float CellSize)
{
return FMath::Clamp(CellSize, ArbitraryMinCellSize, ArbitraryMaxCellSize);
}
#if WITH_RECAST
FNavTileRef GetTileRefFromPolyRef(const dtNavMesh& DetourMesh, const NavNodeRef PolyRef)
{
unsigned int Salt = 0;
unsigned int TileIndex = 0;
unsigned int PolyIndex = 0;
DetourMesh.decodePolyId(PolyRef, Salt, TileIndex, PolyIndex);
const dtTileRef TileRef = DetourMesh.encodePolyId(Salt, TileIndex, 0);
return FNavTileRef(TileRef);
}
#endif // WITH_RECAST
} // namespace UE::NavMesh::Private
FDetourTileLayout::FDetourTileLayout(const dtMeshTile& tile)
{
#if WITH_RECAST
const dtMeshHeader* header = tile.header;
if (header && (header->version == DT_NAVMESH_VERSION))
{
FDetourTileSizeInfo SizeInfo;
SizeInfo.VertCount = header->vertCount;
SizeInfo.PolyCount = header->polyCount;
SizeInfo.MaxLinkCount = header->maxLinkCount;
SizeInfo.DetailMeshCount = header->detailMeshCount;
SizeInfo.DetailVertCount = header->detailVertCount;
SizeInfo.DetailTriCount = header->detailTriCount;
SizeInfo.BvNodeCount = header->bvNodeCount;
SizeInfo.OffMeshConCount = header->offMeshConCount;
#if WITH_NAVMESH_SEGMENT_LINKS
SizeInfo.OffMeshSegConCount = header->offMeshSegConCount;
#endif // WITH_NAVMESH_SEGMENT_LINKS
#if WITH_NAVMESH_CLUSTER_LINKS
SizeInfo.ClusterCount = header->clusterCount;
SizeInfo.OffMeshBase = header->offMeshBase;
#endif // WITH_NAVMESH_CLUSTER_LINKS
InitFromSizeInfo(SizeInfo);
}
#endif // WITH_RECAST
}
FDetourTileLayout::FDetourTileLayout(const FDetourTileSizeInfo& SizeInfo)
{
InitFromSizeInfo(SizeInfo);
}
void FDetourTileLayout::InitFromSizeInfo(const FDetourTileSizeInfo& SizeInfo)
{
#if WITH_RECAST
// Patch header pointers.
HeaderSize = dtAlign(sizeof(dtMeshHeader));
VertsSize = dtAlign(sizeof(dtReal) * 3 * SizeInfo.VertCount);
PolysSize = dtAlign(sizeof(dtPoly) * SizeInfo.PolyCount);
LinksSize = dtAlign(sizeof(dtLink) * (SizeInfo.MaxLinkCount));
DetailMeshesSize = dtAlign(sizeof(dtPolyDetail) * SizeInfo.DetailMeshCount);
DetailVertsSize = dtAlign(sizeof(dtReal) * 3 * SizeInfo.DetailVertCount);
DetailTrisSize = dtAlign(sizeof(unsigned char) * 4 * SizeInfo.DetailTriCount);
BvTreeSize = dtAlign(sizeof(dtBVNode) * SizeInfo.BvNodeCount);
OffMeshConsSize = dtAlign(sizeof(dtOffMeshConnection) * SizeInfo.OffMeshConCount);
#if WITH_NAVMESH_SEGMENT_LINKS
OffMeshSegsSize = dtAlign(sizeof(dtOffMeshSegmentConnection) * SizeInfo.OffMeshSegConCount);
#endif // WITH_NAVMESH_SEGMENT_LINKS
#if WITH_NAVMESH_CLUSTER_LINKS
ClustersSize = dtAlign(sizeof(dtCluster) * SizeInfo.ClusterCount);
PolyClustersSize = dtAlign(sizeof(unsigned short) * SizeInfo.OffMeshBase);
#endif // WITH_NAVMESH_CLUSTER_LINKS
TileSize = HeaderSize + VertsSize + PolysSize + LinksSize + DetailMeshesSize + DetailVertsSize + DetailTrisSize
+ BvTreeSize + OffMeshConsSize + OffMeshSegsSize + ClustersSize + PolyClustersSize;
#endif // WITH_RECAST
}
FNavMeshTileData::FNavData::~FNavData()
{
#if WITH_RECAST
dtFree(RawNavData, DT_ALLOC_PERM_TILE_DATA);
#else
FMemory::Free(RawNavData);
#endif // WITH_RECAST
}
// Temporary test to narrow a crash.
void FNavMeshTileData::FNavData::TestPtr() const
{
if (RawNavData != nullptr)
{
static uint8 Temp = 0;
Temp = *RawNavData;
const SIZE_T Size = FMemory::GetAllocSize((void*)RawNavData);
check(Size == 0 || Size == AllocatedSize);
}
}
FNavMeshTileData::FNavMeshTileData(uint8* RawData, int32 RawDataSize, int32 LayerIdx, FBox LayerBounds)
: LayerIndex(LayerIdx)
, LayerBBox(LayerBounds)
, DataSize(RawDataSize)
{
INC_MEMORY_STAT_BY(STAT_Navigation_TileCacheMemory, DataSize);
NavData = MakeShareable(new FNavData(RawData, DataSize));
}
FNavMeshTileData::~FNavMeshTileData()
{
if (NavData.IsUnique() && NavData->GetRawNavData())
{
// @todo this isn't accounting for the fact that NavData is a shared pointer
DEC_MEMORY_STAT_BY(STAT_Navigation_TileCacheMemory, DataSize);
}
}
uint8* FNavMeshTileData::Release()
{
uint8* RawData = nullptr;
if (NavData.IsValid() && NavData->GetRawNavData())
{
RawData = NavData->GetMutableRawNavData();
NavData->Reset();
DEC_MEMORY_STAT_BY(STAT_Navigation_TileCacheMemory, DataSize);
}
DataSize = 0;
LayerIndex = 0;
return RawData;
}
void FNavMeshTileData::MakeUnique()
{
if (DataSize > 0 && !NavData.IsUnique())
{
INC_MEMORY_STAT_BY(STAT_Navigation_TileCacheMemory, DataSize);
#if WITH_RECAST
uint8* UniqueRawData = (uint8*)dtAlloc(sizeof(uint8)*DataSize, DT_ALLOC_PERM_TILE_DATA);
#else
uint8* UniqueRawData = (uint8*)FMemory::Malloc(sizeof(uint8)*DataSize);
#endif //WITH_RECAST
NavData->TestPtr();
FMemory::Memcpy(UniqueRawData, NavData->GetRawNavData(), DataSize);
NavData = MakeShareable(new FNavData(UniqueRawData, DataSize));
}
}
FVector::FReal ARecastNavMesh::DrawDistanceSq = 0.;
float ARecastNavMesh::MinimumSizeForChaosNavMeshInfluenceSq = 0.0f;
#if !WITH_RECAST
ARecastNavMesh::ARecastNavMesh(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
}
void ARecastNavMesh::Serialize( FArchive& Ar )
{
Super::Serialize(Ar);
uint32 NavMeshVersion;
Ar << NavMeshVersion;
Ar.UsingCustomVersion(FFortniteMainBranchObjectVersion::GUID);
//@todo: How to handle loading nav meshes saved w/ recast when recast isn't present????
// when writing, write a zero here for now. will come back and fill it in later.
uint32 RecastNavMeshSizeBytes = 0;
int64 RecastNavMeshSizePos = Ar.Tell();
Ar << RecastNavMeshSizeBytes;
if (Ar.IsLoading())
{
// incompatible, just skip over this data. navmesh needs rebuilt.
Ar.Seek( RecastNavMeshSizePos + RecastNavMeshSizeBytes );
// Mark self for delete
CleanUpAndMarkPendingKill();
}
}
ARecastNavMesh::~ARecastNavMesh()
{
}
#if WITH_EDITOR
bool ARecastNavMesh::CanEditChange(const FProperty* InPropery) const
{
return false;
}
#endif // WITH_EDITOR
#else // WITH_RECAST
#include "Detour/DetourNavMesh.h"
#include "Detour/DetourNavMeshQuery.h"
#include "NavMesh/PImplRecastNavMesh.h"
#include "NavMesh/RecastNavMeshGenerator.h"
//----------------------------------------------------------------------//
// FRecastDebugGeometry
//----------------------------------------------------------------------//
uint32 FRecastDebugGeometry::GetAllocatedSize() const
{
SIZE_T Size = sizeof(*this) + MeshVerts.GetAllocatedSize()
+ BuiltMeshIndices.GetAllocatedSize()
+ PolyEdges.GetAllocatedSize()
+ NavMeshEdges.GetAllocatedSize()
+ OffMeshLinks.GetAllocatedSize()
#if WITH_NAVMESH_SEGMENT_LINKS
+ OffMeshSegments.GetAllocatedSize()
#endif // WITH_NAVMESH_SEGMENT_LINKS
;
for (int i = 0; i < RECAST_MAX_AREAS; ++i)
{
Size += AreaIndices[i].GetAllocatedSize();
}
#if RECAST_INTERNAL_DEBUG_DATA
for (int i = 0; i < BuildTimeBucketsCount; ++i)
{
Size += TileBuildTimesIndices[i].GetAllocatedSize();
}
#endif // RECAST_INTERNAL_DEBUG_DATA
#if WITH_NAVMESH_CLUSTER_LINKS
Size += Clusters.GetAllocatedSize() + ClusterLinks.GetAllocatedSize();
for (int i = 0; i < Clusters.Num(); ++i)
{
Size += Clusters[i].MeshIndices.GetAllocatedSize();
}
#endif // WITH_NAVMESH_CLUSTER_LINKS
return IntCastChecked<uint32>(Size);
}
//----------------------------------------------------------------------//
// FNavTileRef
//----------------------------------------------------------------------//
// Only use for deprecation
void FNavTileRef::DeprecatedGetTileIdsFromNavTileRefs(const FPImplRecastNavMesh* RecastNavMeshImpl, const TArray<FNavTileRef>& InTileRefs, TArray<uint32>& OutTileIds)
{
if (RecastNavMeshImpl)
{
if (const dtNavMesh* DetourMesh = RecastNavMeshImpl->DetourNavMesh)
{
OutTileIds.Reserve(InTileRefs.Num());
for (const FNavTileRef TileRef : InTileRefs)
{
OutTileIds.Add(DetourMesh->decodePolyIdTile((dtTileRef)TileRef));
}
}
}
}
// Only use for deprecation
void FNavTileRef::DeprecatedMakeTileRefsFromTileIds(const FPImplRecastNavMesh* RecastNavMeshImpl, const TArray<uint32>& InTileIds, TArray<FNavTileRef>& OutTileRefs)
{
if (RecastNavMeshImpl)
{
if (const dtNavMesh* DetourMesh = RecastNavMeshImpl->DetourNavMesh)
{
OutTileRefs.Reserve(InTileIds.Num());
for (const uint32 TileId : InTileIds)
{
const dtTileRef TileRef = DetourMesh->encodePolyId(0, TileId, 0);
OutTileRefs.Add(FNavTileRef(TileRef));
}
}
}
}
//----------------------------------------------------------------------//
// ARecastNavMesh
//----------------------------------------------------------------------//
namespace ERecastNamedFilter
{
FRecastQueryFilter FilterOutNavLinksImpl;
FRecastQueryFilter FilterOutAreasImpl;
FRecastQueryFilter FilterOutNavLinksAndAreasImpl;
}
const FRecastQueryFilter* ARecastNavMesh::NamedFilters[] = {
&ERecastNamedFilter::FilterOutNavLinksImpl
, &ERecastNamedFilter::FilterOutAreasImpl
, &ERecastNamedFilter::FilterOutNavLinksAndAreasImpl
};
namespace FNavMeshConfig
{
ARecastNavMesh::FNavPolyFlags NavLinkFlag = ARecastNavMesh::FNavPolyFlags(0);
FRecastNamedFiltersCreator::FRecastNamedFiltersCreator(bool bVirtualFilters)
{
// setting up the last bit available in dtPoly::flags
NavLinkFlag = ARecastNavMesh::FNavPolyFlags(1 << (sizeof(((dtPoly*)0)->flags) * 8 - 1));
ERecastNamedFilter::FilterOutNavLinksImpl.SetIsVirtual(bVirtualFilters);
ERecastNamedFilter::FilterOutAreasImpl.SetIsVirtual(bVirtualFilters);
ERecastNamedFilter::FilterOutNavLinksAndAreasImpl.SetIsVirtual(bVirtualFilters);
ERecastNamedFilter::FilterOutNavLinksImpl.setExcludeFlags(NavLinkFlag);
ERecastNamedFilter::FilterOutNavLinksAndAreasImpl.setExcludeFlags(NavLinkFlag);
for (int32 AreaID = 0; AreaID < RECAST_MAX_AREAS; ++AreaID)
{
ERecastNamedFilter::FilterOutAreasImpl.setAreaCost(AreaID, RECAST_UNWALKABLE_POLY_COST);
ERecastNamedFilter::FilterOutNavLinksAndAreasImpl.setAreaCost(AreaID, RECAST_UNWALKABLE_POLY_COST);
}
ERecastNamedFilter::FilterOutAreasImpl.setAreaCost(RECAST_DEFAULT_AREA, 1.f);
ERecastNamedFilter::FilterOutNavLinksAndAreasImpl.setAreaCost(RECAST_DEFAULT_AREA, 1.f);
}
}
#endif // WITH_RECAST
// Deprecated
FRecastNavMeshGenerationProperties::FRecastNavMeshGenerationProperties()
{
TilePoolSize = 1024;
TileSizeUU = 988.f;
CellSize = 19;
CellHeight = 10;
AgentRadius = 34.f;
AgentHeight = 144.f;
AgentMaxSlope = 44.f;
AgentMaxStepHeight = 35.f;
MinRegionArea = 0.f;
MergeRegionSize = 400.f;
MaxSimplificationError = 1.3f; // from RecastDemo
TileNumberHardLimit = 1 << 20;
RegionPartitioning = ERecastPartitioning::Watershed;
LayerPartitioning = ERecastPartitioning::Watershed;
RegionChunkSplits = 2;
LayerChunkSplits = 2;
bSortNavigationAreasByCost = true;
bPerformVoxelFiltering = true;
bMarkLowHeightAreas = false;
bUseExtraTopCellWhenMarkingAreas = true;
bFilterLowSpanSequences = false;
bFilterLowSpanFromTileCache = false;
bFixedTilePoolSize = false;
bIsWorldPartitioned = false;
}
#if WITH_RECAST
// Deprecated
FRecastNavMeshGenerationProperties::FRecastNavMeshGenerationProperties(const ARecastNavMesh& RecastNavMesh)
{
TilePoolSize = RecastNavMesh.TilePoolSize;
TileSizeUU = RecastNavMesh.TileSizeUU;
CellSize = RecastNavMesh.GetCellSize(ENavigationDataResolution::Default);
CellHeight = RecastNavMesh.GetCellHeight(ENavigationDataResolution::Default);
AgentRadius = RecastNavMesh.AgentRadius;
AgentHeight = RecastNavMesh.AgentHeight;
AgentMaxSlope = RecastNavMesh.AgentMaxSlope;
AgentMaxStepHeight = RecastNavMesh.GetAgentMaxStepHeight(ENavigationDataResolution::Default); //FRecastNavMeshGenerationProperties is getting deprecated
MinRegionArea = RecastNavMesh.MinRegionArea;
MergeRegionSize = RecastNavMesh.MergeRegionSize;
MaxSimplificationError = RecastNavMesh.MaxSimplificationError;
TileNumberHardLimit = RecastNavMesh.TileNumberHardLimit;
RegionPartitioning = RecastNavMesh.RegionPartitioning;
LayerPartitioning = RecastNavMesh.LayerPartitioning;
RegionChunkSplits = RecastNavMesh.RegionChunkSplits;
LayerChunkSplits = RecastNavMesh.LayerChunkSplits;
bSortNavigationAreasByCost = RecastNavMesh.bSortNavigationAreasByCost;
bPerformVoxelFiltering = RecastNavMesh.bPerformVoxelFiltering;
bMarkLowHeightAreas = RecastNavMesh.bMarkLowHeightAreas;
bUseExtraTopCellWhenMarkingAreas = RecastNavMesh.bUseExtraTopCellWhenMarkingAreas;
bFilterLowSpanSequences = RecastNavMesh.bFilterLowSpanSequences;
bFilterLowSpanFromTileCache = RecastNavMesh.bFilterLowSpanFromTileCache;
bFixedTilePoolSize = RecastNavMesh.bFixedTilePoolSize;
bIsWorldPartitioned = RecastNavMesh.bIsWorldPartitioned;
}
#endif // WITH_RECAST
FRecastNavMeshTileGenerationDebug::FRecastNavMeshTileGenerationDebug()
{
bEnabled = false;
TileCoordinate = FIntVector::ZeroValue;
bGenerateDebugTileOnly = false;
bCollisionGeometry = false;
HeightFieldRenderMode = EHeightFieldRenderMode::Solid;
bHeightfieldFromRasterization = false;
bHeightfieldPostInclusionBoundsFiltering = false;
bHeightfieldPostHeightFiltering = false;
bHeightfieldBounds = false;
bCompactHeightfield = false;
bCompactHeightfieldEroded = false;
bCompactHeightfieldRegions = false;
bCompactHeightfieldDistances = false;
bTileCacheLayerAreas = false;
bTileCacheLayerRegions = false;
bTileCacheContours = false;
bTileCachePolyMesh = false;
bTileCacheDetailMesh = false;
}
#if WITH_RECAST
ARecastNavMesh::FNavPolyFlags ARecastNavMesh::NavLinkFlag = ARecastNavMesh::FNavPolyFlags(0);
ARecastNavMesh::ARecastNavMesh(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, bDrawFilledPolys(true)
, bDrawNavMeshEdges(true)
, bDrawNavLinks(true)
, bDrawOctreeDetails(true)
, bDrawMarkedForbiddenPolys(false)
, bDistinctlyDrawTilesBeingBuilt(true)
, DrawOffset(10.f)
, TilePoolSize(1024)
, MaxSimplificationError(1.3f) // from RecastDemo
, DefaultMaxSearchNodes(RECAST_MAX_SEARCH_NODES)
, DefaultMaxHierarchicalSearchNodes(RECAST_MAX_SEARCH_NODES)
, bSortNavigationAreasByCost(true)
, bIsWorldPartitioned(false)
, bPerformVoxelFiltering(true)
, bMarkLowHeightAreas(false)
, bUseExtraTopCellWhenMarkingAreas(true)
, bFilterLowSpanSequences(false)
, bFilterLowSpanFromTileCache(false)
, bStoreEmptyTileLayers(false)
, bUseVirtualFilters(true)
, bUseVirtualGeometryFilteringAndDirtying(false)
, bAllowNavLinkAsPathEnd(false)
#if WITH_EDITORONLY_DATA
, bAllowWorldPartitionedNavMesh(false)
#endif // WITH_EDITORONLY_DATA
, TileSetUpdateInterval(1.0f)
, NavMeshVersion(NAVMESHVER_LATEST)
, RecastNavMeshImpl(NULL)
{
HeuristicScale = 0.999f;
RegionPartitioning = ERecastPartitioning::Watershed;
LayerPartitioning = ERecastPartitioning::Watershed;
RegionChunkSplits = 2;
LayerChunkSplits = 2;
MaxSimultaneousTileGenerationJobsCount = 1024;
bDoFullyAsyncNavDataGathering = false;
TileNumberHardLimit = 1 << 20;
#if RECAST_ASYNC_REBUILDING
BatchQueryCounter = 0;
#endif // RECAST_ASYNC_REBUILDING
if (HasAnyFlags(RF_ClassDefaultObject) == false)
{
INC_DWORD_STAT_BY( STAT_NavigationMemory, sizeof(*this) );
FindPathImplementation = FindPath;
FindHierarchicalPathImplementation = FindPath;
TestPathImplementation = TestPath;
TestHierarchicalPathImplementation = TestHierarchicalPath;
RaycastImplementation = NavMeshRaycast;
RecastNavMeshImpl = new FPImplRecastNavMesh(this);
// add predefined areas up front
SupportedAreas.Add(FSupportedAreaData(UNavArea_Null::StaticClass(), RECAST_NULL_AREA));
SupportedAreas.Add(FSupportedAreaData(UNavArea_LowHeight::StaticClass(), RECAST_LOW_AREA));
SupportedAreas.Add(FSupportedAreaData(UNavArea_Default::StaticClass(), RECAST_DEFAULT_AREA));
}
}
ARecastNavMesh::~ARecastNavMesh()
{
if (HasAnyFlags(RF_ClassDefaultObject) == false)
{
DEC_DWORD_STAT_BY( STAT_NavigationMemory, sizeof(*this) );
DestroyRecastPImpl();
}
}
void ARecastNavMesh::DestroyRecastPImpl()
{
if (RecastNavMeshImpl != NULL)
{
delete RecastNavMeshImpl;
RecastNavMeshImpl = NULL;
}
}
UPrimitiveComponent* ARecastNavMesh::ConstructRenderingComponent()
{
return NewObject<UNavMeshRenderingComponent>(this, TEXT("NavRenderingComp"), RF_Transient);
}
void ARecastNavMesh::UpdateNavMeshDrawing()
{
#if !UE_BUILD_SHIPPING
UNavMeshRenderingComponent* NavMeshRenderComp = Cast<UNavMeshRenderingComponent>(RenderingComp);
if (NavMeshRenderComp != nullptr && NavMeshRenderComp->GetVisibleFlag() && (NavMeshRenderComp->IsForcingUpdate() || UNavMeshRenderingComponent::IsNavigationShowFlagSet(GetWorld())))
{
RenderingComp->MarkRenderStateDirty();
#if WITH_EDITOR
FEditorSupportDelegates::RedrawAllViewports.Broadcast();
#endif
}
#endif // UE_BUILD_SHIPPING
}
void ARecastNavMesh::LoadBeforeGeneratorRebuild()
{
#if WITH_EDITOR
// If it's not a world partitioned navmesh but it's in a partitioned world, we need to make sure the navigable world is loaded before building the navmesh.
if (!bIsWorldPartitioned)
{
UWorld* World = GetWorld();
if (World && World->IsPartitionedWorld() && !World->IsGameWorld())
{
const UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys)
{
UWorldPartition* WorldPartition = World->GetWorldPartition();
check(WorldPartition);
const FBox Bounds = NavSys->GetNavigableWorldBounds();
UWorldPartitionEditorLoaderAdapter* EditorLoaderAdapter = WorldPartition->CreateEditorLoaderAdapter<FLoaderAdapterShape>(World, Bounds, TEXT("Navigable World"));
EditorLoaderAdapter->GetLoaderAdapter()->SetUserCreated(false);
EditorLoaderAdapter->GetLoaderAdapter()->Load();
// Make sure level instances are loaded.
World->BlockTillLevelStreamingCompleted();
}
}
}
#endif //WITH_EDITOR
}
void ARecastNavMesh::CleanUp()
{
Super::CleanUp();
if (NavDataGenerator.IsValid())
{
NavDataGenerator->CancelBuild();
NavDataGenerator.Reset();
}
DestroyRecastPImpl();
}
void ARecastNavMesh::PostLoad()
{
UE_LOG(LogNavigation, Verbose, TEXT("%s %s"), ANSI_TO_TCHAR(__FUNCTION__), *GetFullNameSafe(this));
Super::PostLoad();
if (const UWorld* World = GetWorld())
{
const UNavigationSystemBase* NavSys = World->GetNavigationSystem();
if (NavSys && NavSys->IsWorldInitDone())
{
CheckToDiscardSubLevelNavData(*NavSys);
}
else
{
UNavigationSystemBase::OnNavigationInitStartStaticDelegate().AddUObject(this, &ARecastNavMesh::CheckToDiscardSubLevelNavData);
}
}
PRAGMA_DISABLE_DEPRECATION_WARNINGS
// If needed, initialize from deprecated value.
if (NavMeshVersion < NAVMESHVER_TILE_RESOLUTIONS)
{
for (int i = 0; i < (uint8)ENavigationDataResolution::MAX; ++i)
{
SetCellSize((ENavigationDataResolution)i, CellSize);
}
}
// If needed, initialize CellHeight from the deprecated value.
if (NavMeshVersion < NAVMESHVER_TILE_RESOLUTIONS_CELLHEIGHT)
{
for (int i = 0; i < (uint8)ENavigationDataResolution::MAX; ++i)
{
SetCellHeight((ENavigationDataResolution)i, CellHeight);
}
}
// If needed, initialize AgentMaxStepHeight from the deprecated value.
if (NavMeshVersion < NAVMESHVER_TILE_RESOLUTIONS_AGENTMAXSTEPHEIGHT)
{
for (int i = 0; i < (uint8)ENavigationDataResolution::MAX; ++i)
{
SetAgentMaxStepHeight((ENavigationDataResolution)i, AgentMaxStepHeight);
}
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
for (uint8 Index = 0; Index < (uint8)ENavigationDataResolution::MAX; Index++)
{
UE_CLOG(TileSizeUU < GetCellSize((ENavigationDataResolution)Index), LogNavigation, Error, TEXT("%s: TileSizeUU (%f) being less than CellSize (%f) is an invalid case and will cause navmesh generation issues.")
, *GetName(), TileSizeUU, GetCellSize((ENavigationDataResolution)Index));
}
if (!UWorld::IsPartitionedWorld(GetWorld()))
{
bIsWorldPartitioned = false;
}
RecreateDefaultFilter();
UpdatePolyRefBitsPreview();
}
void ARecastNavMesh::BeginDestroy()
{
UNavigationSystemBase::OnNavigationInitStartStaticDelegate().RemoveAll(this);
Super::BeginDestroy();
}
void ARecastNavMesh::CheckToDiscardSubLevelNavData(const UNavigationSystemBase& BaseNavSys)
{
// This used to be in ARecastNavMesh::PostInitProperties() but the OwningWorld is not always available, it might be too soon to query it.
// Moved here so sublevel data can be discarded when requested.
const UWorld* OwningWorld = GetWorld();
const UNavigationSystemV1* NavSys = Cast<UNavigationSystemV1>(&BaseNavSys);
if (OwningWorld && NavSys->ShouldDiscardSubLevelNavData(this))
{
// Get rid of instances saved within levels that are streamed-in
if ((GEngine->IsSettingUpPlayWorld() == false) // this is a @HACK
&& (OwningWorld->PersistentLevel != GetLevel())
// If we are cooking, then let them all pass.
// They will be handled at load-time when running.
&& (IsRunningCommandlet() == false))
{
UE_LOG(LogNavigation, Verbose, TEXT("%s Discarding %s due to it not being part of PersistentLevel."), ANSI_TO_TCHAR(__FUNCTION__), *GetFullNameSafe(this));
// Marking self for deletion
CleanUpAndMarkPendingKill();
}
}
}
void ARecastNavMesh::PostRegisterAllComponents()
{
Super::PostRegisterAllComponents();
if (GetActorLocation().IsNearlyZero() == false)
{
ApplyWorldOffset(GetActorLocation(), /*unused*/false);
}
}
void ARecastNavMesh::PostInitProperties()
{
if (HasAnyFlags(RF_ClassDefaultObject) == true)
{
SetDrawDistance(DefaultDrawDistance);
static const FNavMeshConfig::FRecastNamedFiltersCreator RecastNamedFiltersCreator(bUseVirtualFilters);
NavLinkFlag = FNavMeshConfig::NavLinkFlag;
}
Super::PostInitProperties();
if (HasAnyFlags(RF_ClassDefaultObject | RF_NeedLoad) == false)
{
RecreateDefaultFilter();
}
// voxel cache requires the same rasterization setup for all navmeshes, as it's stored in octree
if (IsVoxelCacheEnabled() && !HasAnyFlags(RF_ClassDefaultObject))
{
ARecastNavMesh* DefOb = (ARecastNavMesh*)ARecastNavMesh::StaticClass()->GetDefaultObject();
if (TileSizeUU != DefOb->TileSizeUU)
{
UE_LOG(LogNavigation, Warning, TEXT("%s param: TileSizeUU(%f) differs from config settings, forcing value %f so it can be used with voxel cache!"),
*GetNameSafe(this), TileSizeUU, DefOb->TileSizeUU);
TileSizeUU = DefOb->TileSizeUU;
}
for (int i = 0; i < (uint8)ENavigationDataResolution::MAX; ++i)
{
const float CurrentCellSize = NavMeshResolutionParams[i].CellSize;
const float DefaultObjectCellSize = DefOb->NavMeshResolutionParams[i].CellSize;
if (CurrentCellSize != DefaultObjectCellSize)
{
UE_LOG(LogNavigation, Warning, TEXT("%s param: CellSize(%f) differs from config settings, forcing value %f so it can be used with voxel cache!"),
*GetNameSafe(this), CurrentCellSize, DefaultObjectCellSize);
NavMeshResolutionParams[i].CellSize = DefaultObjectCellSize;
}
const float CurrentCellHeight = NavMeshResolutionParams[i].CellHeight;
const float DefaultObjectCellHeight = DefOb->NavMeshResolutionParams[i].CellHeight;
if (CurrentCellHeight != DefaultObjectCellHeight)
{
UE_LOG(LogNavigation, Warning, TEXT("%s param: CellHeight(%f) differs from config settings, forcing value %f so it can be used with voxel cache!"),
*GetNameSafe(this), CurrentCellHeight, DefaultObjectCellHeight);
NavMeshResolutionParams[i].CellHeight = DefaultObjectCellHeight;
}
const float CurrentAgentMaxStepHeight = NavMeshResolutionParams[i].AgentMaxStepHeight;
const float DefaultObjectAgentMaxStepHeight = DefOb->NavMeshResolutionParams[i].AgentMaxStepHeight;
if (CurrentAgentMaxStepHeight != DefaultObjectAgentMaxStepHeight)
{
UE_LOG(LogNavigation, Warning, TEXT("%s param: AgentMaxStepHeight(%f) differs from config settings, forcing value %f so it can be used with voxel cache!"),
*GetNameSafe(this), CurrentAgentMaxStepHeight, DefaultObjectAgentMaxStepHeight);
NavMeshResolutionParams[i].AgentMaxStepHeight = DefaultObjectAgentMaxStepHeight;
}
}
if (AgentMaxSlope != DefOb->AgentMaxSlope)
{
UE_LOG(LogNavigation, Warning, TEXT("%s param: AgentMaxSlope(%f) differs from config settings, forcing value %f so it can be used with voxel cache!"),
*GetNameSafe(this), AgentMaxSlope, DefOb->AgentMaxSlope);
AgentMaxSlope = DefOb->AgentMaxSlope;
}
}
#if WITH_EDITORONLY_DATA
bAllowWorldPartitionedNavMesh = UWorld::IsPartitionedWorld(GetWorld());
#endif // WITH_EDITORONLY_DATA
UpdatePolyRefBitsPreview();
}
void ARecastNavMesh::RecreateDefaultFilter()
{
DefaultQueryFilter->SetFilterType<FRecastQueryFilter>();
check(DefaultMaxSearchNodes >= 0 && DefaultMaxSearchNodes <= (float)TNumericLimits<uint32>::Max());
DefaultQueryFilter->SetMaxSearchNodes(static_cast<int32>(DefaultMaxSearchNodes));
FRecastQueryFilter* DetourFilter = static_cast<FRecastQueryFilter*>(DefaultQueryFilter->GetImplementation());
DetourFilter->SetIsVirtual(bUseVirtualFilters);
DetourFilter->setHeuristicScale(HeuristicScale);
// clearing out the 'navlink flag' from included flags since it would make
// dtQueryFilter::passInlineFilter pass navlinks of area classes with
// AreaFlags == 0 (like NavArea_Null), which should mean 'unwalkable'
DetourFilter->setIncludeFlags(DetourFilter->getIncludeFlags() & (~ARecastNavMesh::GetNavLinkFlag()));
for (int32 Idx = 0; Idx < SupportedAreas.Num(); Idx++)
{
const FSupportedAreaData& AreaData = SupportedAreas[Idx];
UNavArea* DefArea = nullptr;
if (AreaData.AreaClass)
{
DefArea = ((UClass*)AreaData.AreaClass)->GetDefaultObject<UNavArea>();
}
if (DefArea)
{
DetourFilter->SetAreaCost(IntCastChecked<uint8>(AreaData.AreaID), DefArea->DefaultCost);
DetourFilter->SetFixedAreaEnteringCost(IntCastChecked<uint8>(AreaData.AreaID), DefArea->GetFixedAreaEnteringCost());
}
}
}
void ARecastNavMesh::UpdatePolyRefBitsPreview()
{
static const int32 TotalBits = (sizeof(dtPolyRef) * 8);
FRecastNavMeshGenerator::CalcPolyRefBits(this, PolyRefTileBits, PolyRefNavPolyBits);
PolyRefSaltBits = TotalBits - PolyRefTileBits - PolyRefNavPolyBits;
}
void ARecastNavMesh::OnNavAreaAdded(const UClass* NavAreaClass, int32 AgentIndex)
{
Super::OnNavAreaAdded(NavAreaClass, AgentIndex);
// update navmesh query filter with area costs
const int32 AreaID = GetAreaID(NavAreaClass);
if (AreaID != INDEX_NONE)
{
UNavArea* DefArea = ((UClass*)NavAreaClass)->GetDefaultObject<UNavArea>();
DefaultQueryFilter->SetAreaCost(IntCastChecked<uint8>(AreaID), DefArea->DefaultCost);
DefaultQueryFilter->SetFixedAreaEnteringCost(IntCastChecked<uint8>(AreaID), DefArea->GetFixedAreaEnteringCost());
}
// update generator's cached data
FRecastNavMeshGenerator* MyGenerator = static_cast<FRecastNavMeshGenerator*>(GetGenerator());
if (MyGenerator)
{
MyGenerator->OnAreaAdded(NavAreaClass, AreaID);
}
}
void ARecastNavMesh::OnNavAreaRemoved(const UClass* NavAreaClass)
{
// In an ideal world we'd reset the DefaultQueryFilter Costs here for the AreaID but its
// not really worth changing the API as we shouldn't be using these values anyway.
FRecastNavMeshGenerator* MyGenerator = static_cast<FRecastNavMeshGenerator*>(GetGenerator());
if (MyGenerator)
{
MyGenerator->OnAreaRemoved(NavAreaClass);
}
Super::OnNavAreaRemoved(NavAreaClass);
}
void ARecastNavMesh::OnNavAreaChanged()
{
if (RecastNavMeshImpl)
{
RecastNavMeshImpl->OnAreaCostChanged();
}
}
int32 ARecastNavMesh::GetNewAreaID(const UClass* AreaClass) const
{
if (AreaClass == FNavigationSystem::GetDefaultWalkableArea())
{
return RECAST_DEFAULT_AREA;
}
if (AreaClass == UNavArea_Null::StaticClass())
{
return RECAST_NULL_AREA;
}
if (AreaClass == UNavArea_LowHeight::StaticClass())
{
return RECAST_LOW_AREA;
}
int32 FreeAreaID = Super::GetNewAreaID(AreaClass);
while (FreeAreaID == RECAST_NULL_AREA || FreeAreaID == RECAST_DEFAULT_AREA || FreeAreaID == RECAST_LOW_AREA)
{
FreeAreaID++;
}
check(FreeAreaID < GetMaxSupportedAreas());
return FreeAreaID;
}
FColor ARecastNavMesh::GetAreaIDColor(uint8 AreaID) const
{
const UClass* AreaClass = GetAreaClass(AreaID);
const UNavArea* DefArea = AreaClass ? ((UClass*)AreaClass)->GetDefaultObject<UNavArea>() : NULL;
return DefArea ? DefArea->DrawColor : FColor::Red;
}
void ARecastNavMesh::SortAreasForGenerator(TArray<FRecastAreaNavModifierElement>& Modifiers) const
{
// initialize costs for sorting
float AreaCosts[RECAST_MAX_AREAS];
float AreaFixedCosts[RECAST_MAX_AREAS];
DefaultQueryFilter->GetAllAreaCosts(AreaCosts, AreaFixedCosts, RECAST_MAX_AREAS);
for (auto& Element : Modifiers)
{
if (Element.Areas.Num())
{
FAreaNavModifier& AreaMod = Element.Areas[0];
const int32 AreaId = GetAreaID(AreaMod.GetAreaClass());
if (AreaId >= 0 && AreaId < RECAST_MAX_AREAS)
{
AreaMod.Cost = AreaCosts[AreaId];
AreaMod.FixedCost = AreaFixedCosts[AreaId];
}
}
}
struct FNavAreaSortPredicate
{
FORCEINLINE bool operator()(const FRecastAreaNavModifierElement& ElA, const FRecastAreaNavModifierElement& ElB) const
{
if (ElA.Areas.Num() == 0 || ElB.Areas.Num() == 0)
{
return ElA.Areas.Num() <= ElB.Areas.Num();
}
// assuming composite modifiers has same area type
const FAreaNavModifier& A = ElA.Areas[0];
const FAreaNavModifier& B = ElB.Areas[0];
const bool bIsAReplacing = (A.GetAreaClassToReplace() != NULL);
const bool bIsBReplacing = (B.GetAreaClassToReplace() != NULL);
if (bIsAReplacing != bIsBReplacing)
{
return bIsAReplacing;
}
return A.Cost != B.Cost ? A.Cost < B.Cost : A.FixedCost < B.FixedCost;
}
};
Modifiers.Sort(FNavAreaSortPredicate());
}
const TArray<FIntPoint>& ARecastNavMesh::GetActiveTiles() const
{
const FRecastNavMeshGenerator* MyGenerator = static_cast<const FRecastNavMeshGenerator*>(GetGenerator());
check(MyGenerator);
return MyGenerator->ActiveTiles;
}
TArray<FIntPoint>& ARecastNavMesh::GetActiveTiles()
{
FRecastNavMeshGenerator* MyGenerator = static_cast<FRecastNavMeshGenerator*>(GetGenerator());
check(MyGenerator);
return MyGenerator->ActiveTiles;
}
void ARecastNavMesh::LogRecastTile(const TCHAR* Caller, const FName& Prefix, const FName& OperationName, const dtNavMesh& DetourMesh, const int32 TileX, const int32 TileY, const int32 LayerIndex, const dtTileRef TileRef) const
{
UE_LOG(LogNavigation, VeryVerbose, TEXT("%s> %s Tile (%d,%d:%d), %s TileId: %d, Salt: %d, TileRef: 0x%llx (%s)"),
*GetName(), *Prefix.ToString(),
TileX, TileY, LayerIndex, *OperationName.ToString(),
DetourMesh.decodePolyIdTile(TileRef), DetourMesh.decodePolyIdSalt(TileRef), TileRef,
Caller);
}
void ARecastNavMesh::RestrictBuildingToActiveTiles(bool InRestrictBuildingToActiveTiles)
{
FRecastNavMeshGenerator* MyGenerator = static_cast<FRecastNavMeshGenerator*>(GetGenerator());
if (MyGenerator)
{
MyGenerator->RestrictBuildingToActiveTiles(InRestrictBuildingToActiveTiles);
}
}
void ARecastNavMesh::OnRegistered()
{
Super::OnRegistered();
check(RecastNavMeshImpl);
// This check can fail when the NavMeshVersion indicates the map needs the nav mesh rebuilt
ensure(RecastNavMeshImpl->GetRecastMesh() == nullptr || RecastNavMeshImpl->GetRecastMesh()->getBVQuantFactor((uint8)ENavigationDataResolution::Default) != 0);
}
void ARecastNavMesh::SerializeRecastNavMesh(FArchive& Ar, FPImplRecastNavMesh*& NavMesh, int32 InNavMeshVersion)
{
if (!Ar.IsLoading() && NavMesh == NULL)
{
return;
}
if (Ar.IsLoading())
{
// allocate if necessary
if (RecastNavMeshImpl == NULL)
{
RecastNavMeshImpl = new FPImplRecastNavMesh(this);
}
}
if (RecastNavMeshImpl)
{
RecastNavMeshImpl->Serialize(Ar, InNavMeshVersion);
}
}
void ARecastNavMesh::Serialize( FArchive& Ar )
{
Super::Serialize(Ar);
Ar << NavMeshVersion;
//@todo: How to handle loading nav meshes saved w/ recast when recast isn't present????
// when writing, write a zero here for now. will come back and fill it in later.
uint32 RecastNavMeshSizeBytes = 0;
int64 RecastNavMeshSizePos = Ar.Tell();
{
Ar << RecastNavMeshSizeBytes;
}
if (Ar.IsLoading())
{
auto CleanUpBadVersion = [&Ar, RecastNavMeshSizePos, RecastNavMeshSizeBytes, this]()
{
// incompatible, just skip over this data. navmesh needs rebuilt.
Ar.Seek(RecastNavMeshSizePos + RecastNavMeshSizeBytes);
// Mark self for delete
CleanUpAndMarkPendingKill();
};
if (NavMeshVersion >= NAVMESHVER_MIN_COMPATIBLE && NavMeshVersion < NAVMESHVER_LATEST)
{
UE_LOG(LogNavigation, Display, TEXT("%s: ARecastNavMesh: Navmesh version %d is compatible but not at latest (%d). Rebuild navmesh to use the latest features. \n"), *GetFullName(), NavMeshVersion, NAVMESHVER_LATEST);
}
if (NavMeshVersion < NAVMESHVER_MIN_COMPATIBLE)
{
UE_LOG(LogNavigation, Warning, TEXT("%s: ARecastNavMesh: Navmesh version %d < Min compatible %d. Navmesh needs to be rebuilt. \n"), *GetFullName(), NavMeshVersion, NAVMESHVER_MIN_COMPATIBLE);
CleanUpBadVersion();
}
else if (NavMeshVersion > NAVMESHVER_LATEST)
{
UE_LOG(LogNavigation, Warning, TEXT("%s: ARecastNavMesh: Navmesh version %d > NAVMESHVER_LATEST %d. Newer navmesh should not be loaded by older code. At a minimum the nav mesh needs to be rebuilt. \n"), *GetFullName(), NavMeshVersion, NAVMESHVER_LATEST);
CleanUpBadVersion();
}
else if (RecastNavMeshSizeBytes > 4)
{
SerializeRecastNavMesh(Ar, RecastNavMeshImpl, NavMeshVersion);
#if !(UE_BUILD_SHIPPING)
RequestDrawingUpdate();
#endif //!(UE_BUILD_SHIPPING)
}
else
{
// empty, just skip over this data
Ar.Seek( RecastNavMeshSizePos + RecastNavMeshSizeBytes );
// if it's not getting filled it's better to just remove it
if (RecastNavMeshImpl)
{
RecastNavMeshImpl->ReleaseDetourNavMesh();
}
}
}
else
{
SerializeRecastNavMesh(Ar, RecastNavMeshImpl, NavMeshVersion);
if (Ar.IsSaving())
{
int64 CurPos = Ar.Tell();
RecastNavMeshSizeBytes = IntCastChecked<uint32>(CurPos - RecastNavMeshSizePos);
Ar.Seek(RecastNavMeshSizePos);
Ar << RecastNavMeshSizeBytes;
Ar.Seek(CurPos);
}
}
}
#if WITH_EDITOR
bool ARecastNavMesh::CanEditChange(const FProperty* InProperty) const
{
#if !WITH_NAVMESH_CLUSTER_LINKS
if (InProperty)
{
const FName PropertyName = InProperty->GetFName();
if (PropertyName == GET_MEMBER_NAME_CHECKED(ARecastNavMesh, bDrawClusters))
{
return false;
}
}
#endif // WITH_NAVMESH_CLUSTER_LINKS
return Super::CanEditChange(InProperty);
}
#endif // WITH_EDITOR
void ARecastNavMesh::SetConfig(const FNavDataConfig& Src)
{
NavDataConfig = Src;
AgentHeight = Src.AgentHeight;
AgentRadius = Src.AgentRadius;
if (Src.HasStepHeightOverride())
{
// If there is an override, apply it to all resolutions
for (int32 Index = 0; Index < (int32)ENavigationDataResolution::MAX; Index++)
{
SetAgentMaxStepHeight((ENavigationDataResolution)Index, Src.AgentStepHeight);
}
}
}
void ARecastNavMesh::FillConfig(FNavDataConfig& Dest)
{
Dest = NavDataConfig;
Dest.AgentHeight = AgentHeight;
Dest.AgentRadius = AgentRadius;
Dest.AgentStepHeight = GetAgentMaxStepHeight(ENavigationDataResolution::Default);
}
void ARecastNavMesh::BeginBatchQuery() const
{
#if RECAST_ASYNC_REBUILDING
// lock critical section when no other batch queries are active
if (BatchQueryCounter <= 0)
{
BatchQueryCounter = 0;
}
BatchQueryCounter++;
#endif // RECAST_ASYNC_REBUILDING
}
void ARecastNavMesh::FinishBatchQuery() const
{
#if RECAST_ASYNC_REBUILDING
BatchQueryCounter--;
#endif // RECAST_ASYNC_REBUILDING
}
FBox ARecastNavMesh::GetNavMeshBounds() const
{
FBox Bounds;
if (RecastNavMeshImpl)
{
Bounds = RecastNavMeshImpl->GetNavMeshBounds();
}
return Bounds;
}
FBox ARecastNavMesh::GetNavMeshTileBounds(int32 TileIndex) const
{
FBox Bounds(ForceInit);
if (RecastNavMeshImpl)
{
Bounds = RecastNavMeshImpl->GetNavMeshTileBounds(TileIndex);
}
return Bounds;
}
bool ARecastNavMesh::GetNavMeshTileXY(int32 TileIndex, int32& OutX, int32& OutY, int32& OutLayer) const
{
return RecastNavMeshImpl && RecastNavMeshImpl->GetNavMeshTileXY(TileIndex, OutX, OutY, OutLayer);
}
bool ARecastNavMesh::GetNavMeshTileXY(const FVector& Point, int32& OutX, int32& OutY) const
{
return RecastNavMeshImpl && RecastNavMeshImpl->GetNavMeshTileXY(Point, OutX, OutY);
}
bool ARecastNavMesh::GetNavmeshTileResolution(int32 TileIndex, ENavigationDataResolution& OutResolution) const
{
if (RecastNavMeshImpl)
{
if (const dtNavMesh* const RecastNavMesh = RecastNavMeshImpl->DetourNavMesh)
{
if (const dtMeshTile* const Tile = RecastNavMesh->getTile(TileIndex))
{
if (const dtMeshHeader* const Header = Tile->header)
{
OutResolution = ENavigationDataResolution(Header->resolution);
return true;
}
}
}
}
return false;
}
bool ARecastNavMesh::CheckTileIndicesInValidRange(const FVector& Point, bool& bOutInRange) const
{
const dtNavMesh* const DetourNavMesh = RecastNavMeshImpl ? RecastNavMeshImpl->DetourNavMesh : nullptr;
const bool bValidMesh = DetourNavMesh != nullptr;
if (bValidMesh)
{
const FVector RecastPt = Unreal2RecastPoint(Point);
bOutInRange = DetourNavMesh->isTileLocInValidRange(&RecastPt.X);
}
return bValidMesh;
}
void ARecastNavMesh::GetNavMeshTilesAt(int32 TileX, int32 TileY, TArray<int32>& Indices) const
{
if (RecastNavMeshImpl)
{
RecastNavMeshImpl->GetNavMeshTilesAt(TileX, TileY, Indices);
}
}
bool ARecastNavMesh::GetPolysInTile(int32 TileIndex, TArray<FNavPoly>& Polys) const
{
return RecastNavMeshImpl && RecastNavMeshImpl->GetPolysInTile(TileIndex, Polys);
}
bool ARecastNavMesh::GetNavLinksInTile(const int32 TileIndex, TArray<FNavPoly>& Polys, const bool bIncludeLinksFromNeighborTiles) const
{
if (RecastNavMeshImpl == nullptr || RecastNavMeshImpl->DetourNavMesh == nullptr
|| TileIndex < 0 || TileIndex >= RecastNavMeshImpl->DetourNavMesh->getMaxTiles())
{
return false;
}
const dtNavMesh* DetourNavMesh = RecastNavMeshImpl->DetourNavMesh;
const int32 InitialLinkCount = Polys.Num();
const dtMeshTile* Tile = DetourNavMesh->getTile(TileIndex);
if (Tile && Tile->header)
{
const int32 LinkCount = Tile->header->offMeshConCount;
if (LinkCount > 0)
{
const int32 BaseIdx = Polys.Num();
Polys.AddZeroed(LinkCount);
const dtPoly* Poly = Tile->polys;
for (int32 LinkIndex = 0; LinkIndex < LinkCount; ++LinkIndex, ++Poly)
{
FNavPoly& OutPoly = Polys[BaseIdx + LinkIndex];
const int32 PolyIndex = Tile->header->offMeshBase + LinkIndex;
OutPoly.Ref = DetourNavMesh->encodePolyId(Tile->salt, TileIndex, PolyIndex);
OutPoly.Center = (Recast2UnrealPoint(&Tile->verts[Poly->verts[0] * 3]) + Recast2UnrealPoint(&Tile->verts[Poly->verts[1] * 3])) / 2;
}
}
if (bIncludeLinksFromNeighborTiles)
{
TArray<const dtMeshTile*> NeighborTiles;
NeighborTiles.Reserve(32);
for (int32 SideIndex = 0; SideIndex < 8; ++SideIndex)
{
const int32 StartIndex = NeighborTiles.Num();
const int32 NeighborCount = DetourNavMesh->getNeighbourTilesCountAt(Tile->header->x, Tile->header->y, SideIndex);
if (NeighborCount > 0)
{
const unsigned char oppositeSide = (unsigned char)dtOppositeTile(SideIndex);
NeighborTiles.AddZeroed(NeighborCount);
int32 NeighborX = Tile->header->x;
int32 NeighborY = Tile->header->y;
if (DetourNavMesh->getNeighbourCoords(Tile->header->x, Tile->header->y, SideIndex, NeighborX, NeighborY))
{
DetourNavMesh->getTilesAt(NeighborX, NeighborY, NeighborTiles.GetData() + StartIndex, NeighborCount);
}
for (const dtMeshTile* NeighborTile : NeighborTiles)
{
if (NeighborTile && NeighborTile->header && NeighborTile->offMeshCons)
{
const dtTileRef NeighborTileId = DetourNavMesh->getTileRef(NeighborTile);
for (int32 LinkIndex = 0; LinkIndex < NeighborTile->header->offMeshConCount; ++LinkIndex)
{
dtOffMeshConnection* targetCon = &NeighborTile->offMeshCons[LinkIndex];
if (targetCon->side != oppositeSide)
{
continue;
}
const unsigned char biDirFlag = targetCon->getBiDirectional() ? DT_LINK_FLAG_OFFMESH_CON_BIDIR : 0;
const dtPoly* targetPoly = &NeighborTile->polys[targetCon->poly];
// Skip off-mesh connections which start location could not be connected at all.
if (targetPoly->firstLink == DT_NULL_LINK)
{
continue;
}
FNavPoly& OutPoly = Polys[Polys.AddZeroed()];
OutPoly.Ref = NeighborTileId | targetCon->poly;
OutPoly.Center = (Recast2UnrealPoint(&targetCon->pos[0]) + Recast2UnrealPoint(&targetCon->pos[3])) / 2;
}
}
}
NeighborTiles.Reset();
}
}
}
}
return (Polys.Num() - InitialLinkCount > 0);
}
int32 ARecastNavMesh::GetNavMeshTilesCount() const
{
int32 NumTiles = 0;
if (RecastNavMeshImpl)
{
NumTiles = RecastNavMeshImpl->GetNavMeshTilesCount();
}
return NumTiles;
}
void ARecastNavMesh::RemoveTileCacheLayers(int32 TileX, int32 TileY)
{
if (RecastNavMeshImpl)
{
RecastNavMeshImpl->RemoveTileCacheLayers(TileX, TileY);
}
}
void ARecastNavMesh::AddTileCacheLayers(int32 TileX, int32 TileY, const TArray<FNavMeshTileData>& InLayers)
{
if (RecastNavMeshImpl)
{
RecastNavMeshImpl->AddTileCacheLayers(TileX, TileY, InLayers);
}
}
#if RECAST_INTERNAL_DEBUG_DATA
void ARecastNavMesh::RemoveTileDebugData(int32 TileX, int32 TileY)
{
if (RecastNavMeshImpl)
{
RecastNavMeshImpl->DebugDataMap.Remove(FIntPoint(TileX, TileY));
}
}
void ARecastNavMesh::AddTileDebugData(int32 TileX, int32 TileY, const FRecastInternalDebugData& InTileDebugData)
{
if (RecastNavMeshImpl)
{
RecastNavMeshImpl->DebugDataMap.Add(FIntPoint(TileX, TileY), InTileDebugData);
}
}
#endif //RECAST_INTERNAL_DEBUG_DATA
void ARecastNavMesh::MarkEmptyTileCacheLayers(int32 TileX, int32 TileY)
{
if (RecastNavMeshImpl && bStoreEmptyTileLayers)
{
RecastNavMeshImpl->MarkEmptyTileCacheLayers(TileX, TileY);
}
}
TArray<FNavMeshTileData> ARecastNavMesh::GetTileCacheLayers(int32 TileX, int32 TileY) const
{
if (RecastNavMeshImpl)
{
return RecastNavMeshImpl->GetTileCacheLayers(TileX, TileY);
}
return TArray<FNavMeshTileData>();
}
#if !UE_BUILD_SHIPPING
int32 ARecastNavMesh::GetCompressedTileCacheSize()
{
return RecastNavMeshImpl ? RecastNavMeshImpl->GetCompressedTileCacheSize() : 0;
}
#endif
bool ARecastNavMesh::IsResizable() const
{
// Ignore bFixedTilePoolSize when in EditorWorldPartitionBuildMode
if (const UWorld* World = GetWorld())
{
const UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys && NavSys->GetRunMode() == FNavigationSystemRunMode::EditorWorldPartitionBuildMode)
{
return true;
}
}
return !bFixedTilePoolSize;
}
float ARecastNavMesh::GetTileSizeUU() const
{
const float DefaultCellSize = GetCellSize(ENavigationDataResolution::Default);
const float RcTileSize = FMath::TruncToFloat(TileSizeUU / DefaultCellSize);
return RcTileSize * DefaultCellSize;
}
void ARecastNavMesh::GetEdgesForPathCorridor(const TArray<NavNodeRef>* PathCorridor, TArray<FNavigationPortalEdge>* PathCorridorEdges) const
{
check(PathCorridor != NULL && PathCorridorEdges != NULL);
if (RecastNavMeshImpl)
{
RecastNavMeshImpl->GetEdgesForPathCorridor(PathCorridor, PathCorridorEdges);
}
}
FNavLocation ARecastNavMesh::GetRandomPoint(FSharedConstNavQueryFilter Filter, const UObject* QueryOwner) const
{
FNavLocation RandomPt;
if (RecastNavMeshImpl)
{
RandomPt = RecastNavMeshImpl->GetRandomPoint(GetRightFilterRef(Filter), QueryOwner);
}
return RandomPt;
}
bool ARecastNavMesh::GetRandomReachablePointInRadius(const FVector& Origin, float Radius, FNavLocation& OutResult, FSharedConstNavQueryFilter Filter, const UObject* QueryOwner) const
{
if (RecastNavMeshImpl == nullptr || RecastNavMeshImpl->DetourNavMesh == nullptr || Radius < 0.f)
{
return false;
}
const FNavigationQueryFilter& FilterInstance = GetRightFilterRef(Filter);
FRecastSpeciaLinkFilter LinkFilter(FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld()), QueryOwner);
INITIALIZE_NAVQUERY_WLINKFILTER(NavQuery, FilterInstance.GetMaxSearchNodes(), LinkFilter);
// inits to "pass all"
const dtQueryFilter* QueryFilter = (static_cast<const FRecastQueryFilter*>(FilterInstance.GetImplementation()))->GetAsDetourQueryFilter();
ensure(QueryFilter);
if (QueryFilter)
{
// find starting poly
const FVector ProjectionExtent(NavDataConfig.DefaultQueryExtent.X, NavDataConfig.DefaultQueryExtent.Y, BIG_NUMBER);
const FVector RcExtent = Unreal2RecastPoint(ProjectionExtent).GetAbs();
// convert start/end pos to Recast coords
const FVector RecastOrigin = Unreal2RecastPoint(Origin);
NavNodeRef OriginPolyID = INVALID_NAVNODEREF;
NavQuery.findNearestPoly(&RecastOrigin.X, &RcExtent.X, QueryFilter, &OriginPolyID, nullptr);
if (OriginPolyID != INVALID_NAVNODEREF)
{
dtPolyRef Poly;
FVector::FReal RandPt[3];
dtStatus Status = NavQuery.findRandomPointAroundCircle(OriginPolyID, &RecastOrigin.X, Radius
, QueryFilter, FMath::FRand, &Poly, RandPt);
if (dtStatusSucceed(Status))
{
OutResult = FNavLocation(Recast2UnrealPoint(RandPt), Poly);
return true;
}
}
OutResult = FNavLocation(Origin, OriginPolyID);
}
return false;
}
bool ARecastNavMesh::GetRandomPointInNavigableRadius(const FVector& Origin, float Radius, FNavLocation& OutResult, FSharedConstNavQueryFilter Filter, const UObject* Querier) const
{
const FVector ProjectionExtent(NavDataConfig.DefaultQueryExtent.X, NavDataConfig.DefaultQueryExtent.Y, BIG_NUMBER);
OutResult = FNavLocation(FNavigationSystem::InvalidLocation);
const FVector::FReal RandomAngle = 2.f * PI * FMath::FRand();
const FVector::FReal U = FMath::FRand() + FMath::FRand();
const FVector::FReal RandomRadius = Radius * (U > 1 ? 2.f - U : U);
const FVector RandomOffset(FMath::Cos(RandomAngle) * RandomRadius, FMath::Sin(RandomAngle) * RandomRadius, 0);
FVector RandomLocationInRadius = Origin + RandomOffset;
// naive implementation
ProjectPoint(RandomLocationInRadius, OutResult, ProjectionExtent, Filter);
// if failed get a list of all nav polys in the area and do it the hard way
if (OutResult.HasNodeRef() == false && RecastNavMeshImpl)
{
const FVector::FReal RadiusSq = FMath::Square(Radius);
TArray<FNavPoly> Polys;
const FVector FallbackExtent(Radius, Radius, HALF_WORLD_MAX); //Using HALF_WORLD_MAX instead of BIG_NUMBER, else the box size will be NaN.
const FVector BoxOrigin(Origin.X, Origin.Y, 0.f);
const FBox Box(BoxOrigin - FallbackExtent, BoxOrigin + FallbackExtent);
GetPolysInBox(Box, Polys, Filter, Querier);
// @todo extremely naive implementation, barely random. To be improved
while (Polys.Num() > 0)
{
const int32 RandomIndex = FMath::RandHelper(Polys.Num());
const FNavPoly& Poly = Polys[RandomIndex];
FVector PointOnPoly(0);
if (RecastNavMeshImpl->GetClosestPointOnPoly(Poly.Ref, Origin, PointOnPoly)
&& FVector::DistSquared(PointOnPoly, Origin) < RadiusSq)
{
OutResult = FNavLocation(PointOnPoly, Poly.Ref);
break;
}
Polys.RemoveAtSwap(RandomIndex, 1, /*bAllowShrinking=*/false);
}
}
return OutResult.HasNodeRef() == true;
}
#if WITH_NAVMESH_CLUSTER_LINKS
bool ARecastNavMesh::GetRandomPointInCluster(NavNodeRef ClusterRef, FNavLocation& OutLocation) const
{
return RecastNavMeshImpl && RecastNavMeshImpl->GetRandomPointInCluster(ClusterRef, OutLocation);
}
NavNodeRef ARecastNavMesh::GetClusterRef(NavNodeRef PolyRef) const
{
NavNodeRef ClusterRef = 0;
if (RecastNavMeshImpl)
{
ClusterRef = RecastNavMeshImpl->GetClusterRefFromPolyRef(PolyRef);
}
return ClusterRef;
}
#endif // WITH_NAVMESH_CLUSTER_LINKS
bool ARecastNavMesh::FindMoveAlongSurface(const FNavLocation& StartLocation, const FVector& TargetPosition, FNavLocation& OutLocation, FSharedConstNavQueryFilter Filter, const UObject* QueryOwner) const
{
bool bSuccess = false;
if (RecastNavMeshImpl)
{
bSuccess = RecastNavMeshImpl->FindMoveAlongSurface(StartLocation, TargetPosition, OutLocation, GetRightFilterRef(Filter), QueryOwner);
}
return bSuccess;
}
bool ARecastNavMesh::FindOverlappingEdges(const FNavLocation& StartLocation, TConstArrayView<FVector> ConvexPolygon, TArray<FVector>& OutEdges, FSharedConstNavQueryFilter Filter, const UObject* Querier) const
{
if (RecastNavMeshImpl == NULL || RecastNavMeshImpl->DetourNavMesh == NULL)
{
return false;
}
const FNavigationQueryFilter& FilterToUse = GetRightFilterRef(Filter);
FRecastSpeciaLinkFilter LinkFilter(FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld()), Querier);
INITIALIZE_NAVQUERY_WLINKFILTER(NavQuery, FilterToUse.GetMaxSearchNodes(), LinkFilter);
const dtQueryFilter* QueryFilter = static_cast<const FRecastQueryFilter*>(FilterToUse.GetImplementation())->GetAsDetourQueryFilter();
const int32 MaxWalls = 64;
int32 NumWalls = 0;
FVector::FReal WallSegments[MaxWalls * 3 * 2] = { 0 };
dtPolyRef WallPolys[MaxWalls * 2] = { 0 };
const int32 MaxNeis = 64;
int32 NumNeis = 0;
dtPolyRef NeiPolys[MaxNeis] = { 0 };
const int32 MaxConvexPolygonPoints = 8;
int32 NumConvexPolygonPoints = FMath::Min(ConvexPolygon.Num(), MaxConvexPolygonPoints);
FVector::FReal RcConvexPolygon[MaxConvexPolygonPoints * 3] = { 0 };
for (int32 i = 0; i < NumConvexPolygonPoints; i++)
{
const FVector RcPoint = Unreal2RecastPoint(ConvexPolygon[i]);
RcConvexPolygon[i*3+0] = RcPoint.X;
RcConvexPolygon[i*3+1] = RcPoint.Y;
RcConvexPolygon[i*3+2] = RcPoint.Z;
}
dtStatus Status = NavQuery.findWallsOverlappingShape(StartLocation.NodeRef, RcConvexPolygon, NumConvexPolygonPoints, QueryFilter,
NeiPolys, &NumNeis, MaxNeis, WallSegments, WallPolys, &NumWalls, MaxWalls);
if (dtStatusSucceed(Status))
{
OutEdges.Reset(NumWalls*2);
for (int32 Idx = 0; Idx < NumWalls; Idx++)
{
OutEdges.Add(Recast2UnrealPoint(&WallSegments[Idx * 6]));
OutEdges.Add(Recast2UnrealPoint(&WallSegments[Idx * 6 + 3]));
}
return true;
}
return false;
}
bool ARecastNavMesh::GetPathSegmentBoundaryEdges(const FNavigationPath& Path, const FNavPathPoint& StartPoint, const FNavPathPoint& EndPoint, const TConstArrayView<FVector> SearchArea, TArray<FVector>& OutEdges, const float MaxAreaEnterCost, FSharedConstNavQueryFilter Filter, const UObject* Querier) const
{
if (RecastNavMeshImpl == NULL || RecastNavMeshImpl->DetourNavMesh == NULL)
{
return false;
}
const FNavMeshPath* NavMeshPath = Path.CastPath<const FNavMeshPath>();
if (NavMeshPath == nullptr)
{
return false;
}
const FNavigationQueryFilter& FilterToUse = GetRightFilterRef(Filter);
FRecastSpeciaLinkFilter LinkFilter(FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld()), Querier);
INITIALIZE_NAVQUERY_WLINKFILTER(NavQuery, FilterToUse.GetMaxSearchNodes(), LinkFilter);
const dtQueryFilter* QueryFilter = static_cast<const FRecastQueryFilter*>(FilterToUse.GetImplementation())->GetAsDetourQueryFilter();
// Find all the polygon refs between the path points.
TArray<NavNodeRef> SegmentPathPolys;
if (NavMeshPath->PathCorridor.Num() > 0)
{
const int32 StartIndex = NavMeshPath->GetNodeRefIndex(StartPoint.NodeRef);
if (StartIndex != INDEX_NONE)
{
const NavNodeRef NextNodeRef = EndPoint.NodeRef;
for (int32 NodeIndex = StartIndex; NodeIndex < NavMeshPath->PathCorridor.Num(); NodeIndex++)
{
const NavNodeRef NodeRef = NavMeshPath->PathCorridor[NodeIndex];
SegmentPathPolys.Add(NodeRef);
if (NodeRef == NextNodeRef)
{
break;
}
}
}
}
if (SegmentPathPolys.IsEmpty())
{
SegmentPathPolys.Add(StartPoint.NodeRef);
}
const int32 MaxWalls = 64;
int32 NumWalls = 0;
FVector::FReal WallSegments[MaxWalls * 3 * 2] = { 0 };
dtPolyRef WallPolys[MaxWalls * 2] = { 0 };
const int32 MaxNeis = 64;
int32 NumNeis = 0;
dtPolyRef NeiPolys[MaxNeis] = { 0 };
const int32 MaxConvexPolygonPoints = 8;
int32 NumConvexPolygonPoints = FMath::Min(SearchArea.Num(), MaxConvexPolygonPoints);
FVector::FReal RcConvexPolygon[MaxConvexPolygonPoints * 3] = { 0 };
for (int32 i = 0; i < NumConvexPolygonPoints; i++)
{
const FVector RcPoint = Unreal2RecastPoint(SearchArea[i]);
RcConvexPolygon[i*3+0] = RcPoint.X;
RcConvexPolygon[i*3+1] = RcPoint.Y;
RcConvexPolygon[i*3+2] = RcPoint.Z;
}
dtStatus Status = NavQuery.findWallsAroundPath(SegmentPathPolys.GetData(), SegmentPathPolys.Num(), RcConvexPolygon, NumConvexPolygonPoints, MaxAreaEnterCost, QueryFilter,
NeiPolys, &NumNeis, MaxNeis, WallSegments, WallPolys, &NumWalls, MaxWalls);
if (dtStatusSucceed(Status))
{
OutEdges.Reset(NumWalls*2);
for (int32 Idx = 0; Idx < NumWalls; Idx++)
{
OutEdges.Add(Recast2UnrealPoint(&WallSegments[Idx * 6]));
OutEdges.Add(Recast2UnrealPoint(&WallSegments[Idx * 6 + 3]));
}
return true;
}
return false;
}
bool ARecastNavMesh::ProjectPoint(const FVector& Point, FNavLocation& OutLocation, const FVector& Extent, FSharedConstNavQueryFilter Filter, const UObject* QueryOwner) const
{
bool bSuccess = false;
if (RecastNavMeshImpl)
{
bSuccess = RecastNavMeshImpl->ProjectPointToNavMesh(Point, OutLocation, Extent, GetRightFilterRef(Filter), QueryOwner);
}
return bSuccess;
}
bool ARecastNavMesh::IsNodeRefValid(NavNodeRef NodeRef) const
{
if (NodeRef == INVALID_NAVNODEREF)
{
return false;
}
const dtNavMesh* NavMesh = RecastNavMeshImpl ? RecastNavMeshImpl->GetRecastMesh() : nullptr;
if (!NavMesh)
{
return false;
}
dtPoly const* Poly = 0;
dtMeshTile const* Tile = 0;
const dtStatus Status = NavMesh->getTileAndPolyByRef(NodeRef, &Tile, &Poly);
const bool bSuccess = dtStatusSucceed(Status);
return bSuccess;
}
void ARecastNavMesh::BatchProjectPoints(TArray<FNavigationProjectionWork>& Workload, const FVector& Extent, FSharedConstNavQueryFilter Filter, const UObject* Querier) const
{
if (Workload.Num() == 0 || RecastNavMeshImpl == NULL || RecastNavMeshImpl->DetourNavMesh == NULL)
{
return;
}
const FNavigationQueryFilter& FilterToUse = GetRightFilterRef(Filter);
FRecastSpeciaLinkFilter LinkFilter(FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld()), Querier);
INITIALIZE_NAVQUERY_WLINKFILTER(NavQuery, FilterToUse.GetMaxSearchNodes(), LinkFilter);
const dtQueryFilter* QueryFilter = static_cast<const FRecastQueryFilter*>(FilterToUse.GetImplementation())->GetAsDetourQueryFilter();
if (ensure(QueryFilter))
{
const FVector ModifiedExtent = GetModifiedQueryExtent(Extent);
FVector RcExtent = Unreal2RecastPoint(ModifiedExtent).GetAbs();
FVector::FReal ClosestPoint[3];
dtPolyRef PolyRef;
for (int32 Idx = 0; Idx < Workload.Num(); Idx++)
{
FVector RcPoint = Unreal2RecastPoint(Workload[Idx].Point);
if (Workload[Idx].bHintProjection2D)
{
NavQuery.findNearestPoly2D(&RcPoint.X, &RcExtent.X, QueryFilter, &PolyRef, ClosestPoint);
}
else
{
NavQuery.findNearestPoly(&RcPoint.X, &RcExtent.X, QueryFilter, &PolyRef, ClosestPoint);
}
// one last step required due to recast's BVTree imprecision
if (PolyRef > 0)
{
const FVector& UnrealClosestPoint = Recast2UnrealPoint(ClosestPoint);
if (FVector::DistSquared(UnrealClosestPoint, Workload[Idx].Point) <= ModifiedExtent.SizeSquared())
{
Workload[Idx].OutLocation = FNavLocation(UnrealClosestPoint, PolyRef);
Workload[Idx].bResult = true;
}
}
}
}
}
void ARecastNavMesh::BatchProjectPoints(TArray<FNavigationProjectionWork>& Workload, FSharedConstNavQueryFilter Filter, const UObject* Querier) const
{
if (Workload.Num() == 0 || RecastNavMeshImpl == NULL || RecastNavMeshImpl->DetourNavMesh == NULL)
{
return;
}
const FNavigationQueryFilter& FilterToUse = GetRightFilterRef(Filter);
FRecastSpeciaLinkFilter LinkFilter(FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld()), Querier);
INITIALIZE_NAVQUERY_WLINKFILTER(NavQuery, FilterToUse.GetMaxSearchNodes(), LinkFilter);
const dtQueryFilter* QueryFilter = static_cast<const FRecastQueryFilter*>(FilterToUse.GetImplementation())->GetAsDetourQueryFilter();
if (ensure(QueryFilter))
{
FVector::FReal ClosestPoint[3];
dtPolyRef PolyRef;
for (FNavigationProjectionWork& Work : Workload)
{
ensure(Work.ProjectionLimit.IsValid);
const FVector RcReferencePoint = Unreal2RecastPoint(Work.Point);
const FVector ModifiedExtent = GetModifiedQueryExtent(Work.ProjectionLimit.GetExtent());
const FVector RcExtent = Unreal2RecastPoint(ModifiedExtent).GetAbs();
const FVector RcBoxCenter = Unreal2RecastPoint(Work.ProjectionLimit.GetCenter());
if (Work.bHintProjection2D)
{
NavQuery.findNearestPoly2D(&RcBoxCenter.X, &RcExtent.X, QueryFilter, &PolyRef, ClosestPoint, &RcReferencePoint.X);
}
else
{
NavQuery.findNearestPoly(&RcBoxCenter.X, &RcExtent.X, QueryFilter, &PolyRef, ClosestPoint, &RcReferencePoint.X);
}
// one last step required due to recast's BVTree imprecision
if (PolyRef > 0)
{
const FVector& UnrealClosestPoint = Recast2UnrealPoint(ClosestPoint);
if (FVector::DistSquared(UnrealClosestPoint, Work.Point) <= ModifiedExtent.SizeSquared())
{
Work.OutLocation = FNavLocation(UnrealClosestPoint, PolyRef);
Work.bResult = true;
}
}
}
}
}
bool ARecastNavMesh::GetPolysInBox(const FBox& Box, TArray<FNavPoly>& Polys, FSharedConstNavQueryFilter Filter, const UObject* InOwner) const
{
// sanity check
if (RecastNavMeshImpl->GetRecastMesh() == NULL)
{
return false;
}
bool bSuccess = false;
const FNavigationQueryFilter& FilterToUse = GetRightFilterRef(Filter);
FRecastSpeciaLinkFilter LinkFilter(FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld()), InOwner);
INITIALIZE_NAVQUERY_WLINKFILTER(NavQuery, FilterToUse.GetMaxSearchNodes(), LinkFilter);
const dtQueryFilter* QueryFilter = ((const FRecastQueryFilter*)(FilterToUse.GetImplementation()))->GetAsDetourQueryFilter();
ensure(QueryFilter);
if (QueryFilter)
{
const FVector ModifiedExtent = GetModifiedQueryExtent(Box.GetExtent());
const FVector RcPoint = Unreal2RecastPoint( Box.GetCenter() );
const FVector RcExtent = Unreal2RecastPoint( ModifiedExtent ).GetAbs();
const int32 MaxHitPolys = 256;
dtPolyRef HitPolys[MaxHitPolys];
int32 NumHitPolys = 0;
dtStatus status = NavQuery.queryPolygons(&RcPoint.X, &RcExtent.X, QueryFilter, HitPolys, &NumHitPolys, MaxHitPolys);
if (dtStatusSucceed(status))
{
// only ground type polys
int32 BaseIdx = Polys.Num();
Polys.AddZeroed(NumHitPolys);
for (int32 i = 0; i < NumHitPolys; i++)
{
dtPoly const* Poly;
dtMeshTile const* Tile;
dtStatus Status = RecastNavMeshImpl->GetRecastMesh()->getTileAndPolyByRef(HitPolys[i], &Tile, &Poly);
if (dtStatusSucceed(Status))
{
FVector PolyCenter(0);
for (int k = 0; k < Poly->vertCount; ++k)
{
PolyCenter += Recast2UnrealPoint(&Tile->verts[Poly->verts[k]*3]);
}
PolyCenter /= Poly->vertCount;
FNavPoly& OutPoly = Polys[BaseIdx + i];
OutPoly.Ref = HitPolys[i];
OutPoly.Center = PolyCenter;
}
}
bSuccess = true;
}
}
return bSuccess;
}
bool ARecastNavMesh::FindEdges(const NavNodeRef CenterNodeRef, const FVector Center, const FVector::FReal Radius, const FSharedConstNavQueryFilter Filter, TArray<FNavigationWallEdge>& OutEdges) const
{
const FNavigationQueryFilter& FilterToUse = GetRightFilterRef(Filter);
INITIALIZE_NAVQUERY(NavQuery, FilterToUse.GetMaxSearchNodes());
const dtQueryFilter* QueryFilter = static_cast<const FRecastQueryFilter*>(FilterToUse.GetImplementation())->GetAsDetourQueryFilter();
const int32 MaxWalls = 64;
int32 NumWalls = 0;
FVector::FReal WallSegments[MaxWalls * 3 * 2] = { 0 };
dtPolyRef WallPolys[MaxWalls * 2] = { 0 };
const int32 MaxNeis = 64;
int32 NumNeis = 0;
dtPolyRef NeiPolys[MaxNeis] = { 0 };
const FVector RcCenter = Unreal2RecastPoint(Center);
dtStatus Status = NavQuery.findWallsInNeighbourhood(CenterNodeRef, &RcCenter.X, Radius, QueryFilter,
NeiPolys, &NumNeis, MaxNeis, WallSegments, WallPolys, &NumWalls, MaxWalls);
if (dtStatusSucceed(Status))
{
OutEdges.Reset(NumWalls);
FNavigationWallEdge NewEdge;
for (int32 Idx = 0; Idx < NumWalls; Idx++)
{
NewEdge.Start = Recast2UnrealPoint(&WallSegments[Idx * 6]);
NewEdge.End = Recast2UnrealPoint(&WallSegments[Idx * 6 + 3]);
OutEdges.Add(NewEdge);
}
return true;
}
return false;
}
bool ARecastNavMesh::ProjectPointMulti(const FVector& Point, TArray<FNavLocation>& OutLocations, const FVector& Extent,
FVector::FReal MinZ, FVector::FReal MaxZ, FSharedConstNavQueryFilter Filter, const UObject* QueryOwner) const
{
return RecastNavMeshImpl && RecastNavMeshImpl->ProjectPointMulti(Point, OutLocations, Extent, MinZ, MaxZ, GetRightFilterRef(Filter), QueryOwner);
}
ENavigationQueryResult::Type ARecastNavMesh::CalcPathCost(const FVector& PathStart, const FVector& PathEnd, FVector::FReal& OutPathCost, FSharedConstNavQueryFilter QueryFilter, const UObject* QueryOwner) const
{
FVector::FReal TmpPathLength = 0.f;
ENavigationQueryResult::Type Result = CalcPathLengthAndCost(PathStart, PathEnd, TmpPathLength, OutPathCost, QueryFilter, QueryOwner);
return Result;
}
ENavigationQueryResult::Type ARecastNavMesh::CalcPathLength(const FVector& PathStart, const FVector& PathEnd, FVector::FReal& OutPathLength, FSharedConstNavQueryFilter QueryFilter, const UObject* QueryOwner) const
{
FVector::FReal TmpPathCost = 0.f;
ENavigationQueryResult::Type Result = CalcPathLengthAndCost(PathStart, PathEnd, OutPathLength, TmpPathCost, QueryFilter, QueryOwner);
return Result;
}
ENavigationQueryResult::Type ARecastNavMesh::CalcPathLengthAndCost(const FVector& PathStart, const FVector& PathEnd, FVector::FReal& OutPathLength, FVector::FReal& OutPathCost, FSharedConstNavQueryFilter QueryFilter, const UObject* QueryOwner) const
{
ENavigationQueryResult::Type Result = ENavigationQueryResult::Invalid;
if (RecastNavMeshImpl)
{
if ((PathStart - PathEnd).IsNearlyZero() == true)
{
OutPathLength = 0.;
Result = ENavigationQueryResult::Success;
}
else
{
TSharedRef<FNavMeshPath> Path = MakeShareable(new FNavMeshPath());
Path->SetWantsStringPulling(false);
Path->SetWantsPathCorridor(true);
// LWC_TODO_AI: CostLimit should be FVector::FReal. Not until after 5.0!
constexpr FVector::FReal CostLimit = TNumericLimits<FVector::FReal>::Max();
constexpr bool bRequireNavigableEndLocation = true;
Result = RecastNavMeshImpl->FindPath(PathStart, PathEnd, CostLimit, bRequireNavigableEndLocation, Path.Get(), GetRightFilterRef(QueryFilter), QueryOwner);
if (Result == ENavigationQueryResult::Success || (Result == ENavigationQueryResult::Fail && Path->IsPartial()))
{
OutPathLength = Path->GetTotalPathLength();
OutPathCost = Path->GetCost();
}
}
}
return Result;
}
bool ARecastNavMesh::DoesNodeContainLocation(NavNodeRef NodeRef, const FVector& WorldSpaceLocation) const
{
bool bResult = false;
if (RecastNavMeshImpl != nullptr && RecastNavMeshImpl->GetRecastMesh() != nullptr)
{
dtNavMeshQuery NavQuery;
NavQuery.init(RecastNavMeshImpl->GetRecastMesh(), 0);
const FVector RcLocation = Unreal2RecastPoint(WorldSpaceLocation);
if (dtStatusFailed(NavQuery.isPointInsidePoly(NodeRef, &RcLocation.X, bResult)))
{
bResult = false;
}
}
return bResult;
}
bool ARecastNavMesh::FindPolysAroundCircle(const FVector& CenterPos, const NavNodeRef CenterNodeRef, const FVector::FReal Radius, const FSharedConstNavQueryFilter& Filter, const UObject* QueryOwner, TArray<NavNodeRef>* OutPolys, TArray<NavNodeRef>* OutPolysParent, TArray<float>* OutPolysCost, int32* OutPolysCount) const
{
return RecastNavMeshImpl ? RecastNavMeshImpl->FindPolysAroundCircle(CenterPos, CenterNodeRef, Radius, GetRightFilterRef(Filter), QueryOwner, OutPolys, OutPolysParent, OutPolysCost, OutPolysCount) : false;
}
NavNodeRef ARecastNavMesh::FindNearestPoly(FVector const& Loc, FVector const& Extent, FSharedConstNavQueryFilter Filter, const UObject* QueryOwner) const
{
NavNodeRef PolyRef = 0;
if (RecastNavMeshImpl)
{
PolyRef = RecastNavMeshImpl->FindNearestPoly(Loc, Extent, GetRightFilterRef(Filter), QueryOwner);
}
return PolyRef;
}
FVector::FReal ARecastNavMesh::FindDistanceToWall(const FVector& StartLoc, FSharedConstNavQueryFilter Filter, FVector::FReal MaxDistance, FVector* OutClosestPointOnWall) const
{
if (HasValidNavmesh() == false)
{
return 0.;
}
const FNavigationQueryFilter& FilterToUse = GetRightFilterRef(Filter);
INITIALIZE_NAVQUERY(NavQuery, FilterToUse.GetMaxSearchNodes());
const dtQueryFilter* QueryFilter = ((const FRecastQueryFilter*)(FilterToUse.GetImplementation()))->GetAsDetourQueryFilter();
if (QueryFilter == nullptr)
{
UE_VLOG(this, LogNavigation, Warning, TEXT("ARecastNavMesh::FindDistanceToWall failing due to QueryFilter == NULL"));
return 0.;
}
const FVector NavExtent = GetModifiedQueryExtent(GetDefaultQueryExtent());
const FVector::FReal Extent[3] = { NavExtent.X, NavExtent.Z, NavExtent.Y };
const FVector RecastStart = Unreal2RecastPoint(StartLoc);
NavNodeRef StartNode = INVALID_NAVNODEREF;
NavQuery.findNearestPoly(&RecastStart.X, Extent, QueryFilter, &StartNode, NULL);
if (StartNode != INVALID_NAVNODEREF)
{
FVector::FReal TmpHitPos[3], TmpHitNormal[3];
FVector::FReal DistanceToWall = 0.f;
const dtStatus RaycastStatus = NavQuery.findDistanceToWall(StartNode, &RecastStart.X, MaxDistance, QueryFilter
, &DistanceToWall, TmpHitPos, TmpHitNormal);
if (dtStatusSucceed(RaycastStatus))
{
if (OutClosestPointOnWall)
{
*OutClosestPointOnWall = Recast2UnrealPoint(TmpHitPos);
}
return DistanceToWall;
}
}
return 0.;
}
void ARecastNavMesh::UpdateCustomLink(const INavLinkCustomInterface* CustomLink)
{
TSubclassOf<UNavArea> AreaClass = CustomLink->GetLinkAreaClass();
const FNavLinkId UserId = CustomLink->GetId();
const int32 AreaId = GetAreaID(AreaClass);
if (AreaId >= 0 && RecastNavMeshImpl)
{
UNavArea* DefArea = (UNavArea*)(AreaClass->GetDefaultObject());
const uint16 PolyFlags = DefArea->GetAreaFlags() | ARecastNavMesh::GetNavLinkFlag();
RecastNavMeshImpl->UpdateNavigationLinkArea(UserId, IntCastChecked<uint8>(AreaId), PolyFlags);
#if WITH_NAVMESH_SEGMENT_LINKS
// SegmentLinks are an unsupported feature that was never completed to production quality, for now at least FNavLinkId ids are not supported here.
RecastNavMeshImpl->UpdateSegmentLinkArea((int32)UserId.GetId(), IntCastChecked<uint8>(AreaId), PolyFlags);
#endif // WITH_NAVMESH_SEGMENT_LINKS
#if !UE_BUILD_SHIPPING
RequestDrawingUpdate(false);
#endif
}
}
void ARecastNavMesh::UpdateNavigationLinkArea(FNavLinkId UserId, TSubclassOf<UNavArea> AreaClass) const
{
int32 AreaId = GetAreaID(AreaClass);
if (AreaId >= 0 && RecastNavMeshImpl)
{
UNavArea* DefArea = (UNavArea*)(AreaClass->GetDefaultObject());
const uint16 PolyFlags = DefArea->GetAreaFlags() | ARecastNavMesh::GetNavLinkFlag();
RecastNavMeshImpl->UpdateNavigationLinkArea(UserId, IntCastChecked<uint8>(AreaId), PolyFlags);
}
}
#if WITH_NAVMESH_SEGMENT_LINKS
void ARecastNavMesh::UpdateSegmentLinkArea(int32 UserId, TSubclassOf<UNavArea> AreaClass) const
{
int32 AreaId = GetAreaID(AreaClass);
if (AreaId >= 0 && RecastNavMeshImpl)
{
UNavArea* DefArea = (UNavArea*)(AreaClass->GetDefaultObject());
const uint16 PolyFlags = DefArea->GetAreaFlags() | ARecastNavMesh::GetNavLinkFlag();
RecastNavMeshImpl->UpdateSegmentLinkArea(UserId, IntCastChecked<uint8>(AreaId), PolyFlags);
}
}
#endif // WITH_NAVMESH_SEGMENT_LINKS
bool ARecastNavMesh::GetPolyCenter(NavNodeRef PolyID, FVector& OutCenter) const
{
return RecastNavMeshImpl && RecastNavMeshImpl->GetPolyCenter(PolyID, OutCenter);
}
bool ARecastNavMesh::GetPolyVerts(NavNodeRef PolyID, TArray<FVector>& OutVerts) const
{
return RecastNavMeshImpl && RecastNavMeshImpl->GetPolyVerts(PolyID, OutVerts);
}
bool ARecastNavMesh::GetRandomPointInPoly(NavNodeRef PolyID, FVector& OutPoint) const
{
return RecastNavMeshImpl && RecastNavMeshImpl->GetRandomPointInPoly(PolyID, OutPoint);
}
uint32 ARecastNavMesh::GetPolyAreaID(NavNodeRef PolyID) const
{
uint32 AreaID = RECAST_DEFAULT_AREA;
if (RecastNavMeshImpl)
{
AreaID = RecastNavMeshImpl->GetPolyAreaID(PolyID);
}
return AreaID;
}
bool ARecastNavMesh::SetPolyArea(NavNodeRef PolyID, TSubclassOf<UNavArea> AreaClass)
{
bool bSuccess = false;
if (AreaClass && RecastNavMeshImpl)
{
dtNavMesh* NavMesh = RecastNavMeshImpl->GetRecastMesh();
const int32 AreaId = GetAreaID(AreaClass);
const uint16 AreaFlags = AreaClass->GetDefaultObject<UNavArea>()->GetAreaFlags();
if (AreaId != INDEX_NONE && NavMesh)
{
// @todo implement a single detour function that would do both
bSuccess = dtStatusSucceed(NavMesh->setPolyArea(PolyID, IntCastChecked<unsigned char>(AreaId)));
bSuccess = (bSuccess && dtStatusSucceed(NavMesh->setPolyFlags(PolyID, AreaFlags)));
}
}
return bSuccess;
}
void ARecastNavMesh::SetPolyArrayArea(const TArray<FNavPoly>& Polys, TSubclassOf<UNavArea> AreaClass)
{
if (AreaClass && RecastNavMeshImpl)
{
dtNavMesh* NavMesh = RecastNavMeshImpl->GetRecastMesh();
const int32 AreaId = GetAreaID(AreaClass);
const uint16 AreaFlags = AreaClass->GetDefaultObject<UNavArea>()->GetAreaFlags();
if (AreaId != INDEX_NONE && NavMesh)
{
for (int32 Idx = 0; Idx < Polys.Num(); Idx++)
{
NavMesh->setPolyArea(Polys[Idx].Ref, IntCastChecked<unsigned char>(AreaId));
NavMesh->setPolyFlags(Polys[Idx].Ref, AreaFlags);
}
}
}
}
int32 ARecastNavMesh::ReplaceAreaInTileBounds(const FBox& Bounds, TSubclassOf<UNavArea> OldArea, TSubclassOf<UNavArea> NewArea, bool ReplaceLinks, TArray<NavNodeRef>* OutTouchedNodes)
{
int32 PolysTouched = 0;
if (RecastNavMeshImpl && RecastNavMeshImpl->GetRecastMesh())
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_RecastNavMesh_ReplaceAreaInTiles);
const int32 OldAreaID = GetAreaID(OldArea);
ensure(OldAreaID != INDEX_NONE);
const int32 NewAreaID = GetAreaID(NewArea);
ensure(NewAreaID != INDEX_NONE);
ensure(NewAreaID != OldAreaID);
// workaround for privacy issue in the recast API
const dtNavMesh* DetourNavMesh = RecastNavMeshImpl->GetRecastMesh();
dtNavMesh const* const ConstDetourNavMesh = RecastNavMeshImpl->GetRecastMesh();
const FVector RcNavMeshOrigin = Unreal2RecastPoint(NavMeshOriginOffset);
const float TileSizeInWorldUnits = GetTileSizeUU();
const FRcTileBox TileBox(Bounds, RcNavMeshOrigin, TileSizeInWorldUnits);
for (int32 TileY = TileBox.YMin; TileY <= TileBox.YMax; ++TileY)
{
for (int32 TileX = TileBox.XMin; TileX <= TileBox.XMax; ++TileX)
{
const int32 MaxTiles = ConstDetourNavMesh->getTileCountAt(TileX, TileY);
if (MaxTiles == 0)
{
continue;
}
TArray<const dtMeshTile*> Tiles;
Tiles.AddZeroed(MaxTiles);
const int32 NumTiles = ConstDetourNavMesh->getTilesAt(TileX, TileY, Tiles.GetData(), MaxTiles);
for (int32 i = 0; i < NumTiles; i++)
{
dtTileRef TileRef = ConstDetourNavMesh->getTileRef(Tiles[i]);
if (TileRef)
{
const int32 TileIndex = (int32)ConstDetourNavMesh->decodePolyIdTile(TileRef);
const dtMeshTile* Tile = DetourNavMesh->getTile(TileIndex);
//const int32 MaxPolys = Tile && Tile->header ? Tile->header->offMeshBase : 0;
const int32 MaxPolys = Tile && Tile->header
? (ReplaceLinks ? Tile->header->polyCount : Tile->header->offMeshBase)
: 0;
if (MaxPolys > 0)
{
dtPoly* Poly = Tile->polys;
for (int32 PolyIndex = 0; PolyIndex < MaxPolys; PolyIndex++, Poly++)
{
if (Poly->getArea() == OldAreaID)
{
Poly->setArea(IntCastChecked<unsigned char>(NewAreaID));
++PolysTouched;
}
}
}
}
}
}
}
}
return PolysTouched;
}
bool ARecastNavMesh::GetPolyFlags(NavNodeRef PolyID, uint16& PolyFlags, uint16& AreaFlags) const
{
bool bFound = false;
if (RecastNavMeshImpl)
{
uint8 AreaType = RECAST_DEFAULT_AREA;
bFound = RecastNavMeshImpl->GetPolyData(PolyID, PolyFlags, AreaType);
if (bFound)
{
const UClass* AreaClass = GetAreaClass(AreaType);
const UNavArea* DefArea = AreaClass ? ((UClass*)AreaClass)->GetDefaultObject<UNavArea>() : NULL;
AreaFlags = DefArea ? DefArea->GetAreaFlags() : 0;
}
}
return bFound;
}
bool ARecastNavMesh::GetPolyFlags(NavNodeRef PolyID, FNavMeshNodeFlags& Flags) const
{
bool bFound = false;
if (RecastNavMeshImpl)
{
uint16 PolyFlags = 0;
bFound = RecastNavMeshImpl->GetPolyData(PolyID, PolyFlags, Flags.Area);
if (bFound)
{
const UClass* AreaClass = GetAreaClass(Flags.Area);
const UNavArea* DefArea = AreaClass ? ((UClass*)AreaClass)->GetDefaultObject<UNavArea>() : NULL;
Flags.AreaFlags = DefArea ? DefArea->GetAreaFlags() : 0;
// @todo what is this literal?
Flags.PathFlags = (PolyFlags & GetNavLinkFlag()) ? 4 : 0;
}
}
return bFound;
}
bool ARecastNavMesh::GetPolyNeighbors(NavNodeRef PolyID, TArray<FNavigationPortalEdge>& Neighbors) const
{
return RecastNavMeshImpl && RecastNavMeshImpl->GetPolyNeighbors(PolyID, Neighbors);
}
bool ARecastNavMesh::GetPolyNeighbors(NavNodeRef PolyID, TArray<NavNodeRef>& Neighbors) const
{
return RecastNavMeshImpl && RecastNavMeshImpl->GetPolyNeighbors(PolyID, Neighbors);
}
bool ARecastNavMesh::GetPolyEdges(NavNodeRef PolyID, TArray<FNavigationPortalEdge>& Neighbors) const
{
bool bFound = false;
if (RecastNavMeshImpl)
{
bFound = RecastNavMeshImpl->GetPolyEdges(PolyID, Neighbors);
}
return bFound;
}
bool ARecastNavMesh::GetClosestPointOnPoly(NavNodeRef PolyID, const FVector& TestPt, FVector& PointOnPoly) const
{
return RecastNavMeshImpl && RecastNavMeshImpl->GetClosestPointOnPoly(PolyID, TestPt, PointOnPoly);
}
bool ARecastNavMesh::GetPolyTileIndex(NavNodeRef PolyID, uint32& PolyIndex, uint32& TileIndex) const
{
return RecastNavMeshImpl && RecastNavMeshImpl->GetPolyTileIndex(PolyID, PolyIndex, TileIndex);
}
bool ARecastNavMesh::GetLinkEndPoints(NavNodeRef LinkPolyID, FVector& PointA, FVector& PointB) const
{
return RecastNavMeshImpl && RecastNavMeshImpl->GetLinkEndPoints(LinkPolyID, PointA, PointB);
}
bool ARecastNavMesh::IsCustomLink(NavNodeRef LinkPolyID) const
{
return RecastNavMeshImpl && RecastNavMeshImpl->IsCustomLink(LinkPolyID);
}
#if WITH_NAVMESH_CLUSTER_LINKS
bool ARecastNavMesh::GetClusterBounds(NavNodeRef ClusterRef, FBox& OutBounds) const
{
return RecastNavMeshImpl && RecastNavMeshImpl->GetClusterBounds(ClusterRef, OutBounds);
}
#endif // WITH_NAVMESH_CLUSTER_LINKS
bool ARecastNavMesh::GetPolysWithinPathingDistance(FVector const& StartLoc, const FVector::FReal PathingDistance, TArray<NavNodeRef>& FoundPolys,
FSharedConstNavQueryFilter Filter, const UObject* QueryOwner, FRecastDebugPathfindingData* DebugData) const
{
return RecastNavMeshImpl && RecastNavMeshImpl->GetPolysWithinPathingDistance(StartLoc, PathingDistance, GetRightFilterRef(Filter), QueryOwner, FoundPolys, DebugData);
}
// Deprecated
void ARecastNavMesh::GetDebugGeometry(FRecastDebugGeometry& OutGeometry, int32 TileIndex) const
{
GetDebugGeometryForTile(OutGeometry, TileIndex);
}
bool ARecastNavMesh::GetDebugGeometryForTile(FRecastDebugGeometry& OutGeometry, int32 TileIndex) const
{
if (RecastNavMeshImpl)
{
return RecastNavMeshImpl->GetDebugGeometryForTile(OutGeometry, TileIndex);
}
return true;
}
void ARecastNavMesh::RequestDrawingUpdate(bool bForce)
{
#if !UE_BUILD_SHIPPING
if (bForce || UNavMeshRenderingComponent::IsNavigationShowFlagSet(GetWorld()))
{
if (bForce)
{
UNavMeshRenderingComponent* NavRenderingComp = Cast<UNavMeshRenderingComponent>(RenderingComp);
if (NavRenderingComp)
{
NavRenderingComp->ForceUpdate();
}
}
DECLARE_CYCLE_STAT(TEXT("FSimpleDelegateGraphTask.Requesting navmesh redraw"),
STAT_FSimpleDelegateGraphTask_RequestingNavmeshRedraw,
STATGROUP_TaskGraphTasks);
FSimpleDelegateGraphTask::CreateAndDispatchWhenReady(
FSimpleDelegateGraphTask::FDelegate::CreateUObject(this, &ARecastNavMesh::UpdateDrawing),
GET_STATID(STAT_FSimpleDelegateGraphTask_RequestingNavmeshRedraw), NULL, ENamedThreads::GameThread);
}
#endif // !UE_BUILD_SHIPPING
}
void ARecastNavMesh::UpdateDrawing()
{
UpdateNavMeshDrawing();
}
void ARecastNavMesh::DrawDebugPathCorridor(NavNodeRef const* PathPolys, int32 NumPathPolys, bool bPersistent) const
{
#if ENABLE_DRAW_DEBUG
static const FColor PathLineColor(255, 128, 0);
UWorld* World = GetWorld();
// draw poly outlines
TArray<FVector> PolyVerts;
for (int32 PolyIdx=0; PolyIdx < NumPathPolys; ++PolyIdx)
{
if ( GetPolyVerts(PathPolys[PolyIdx], PolyVerts) )
{
for (int32 VertIdx=0; VertIdx < PolyVerts.Num()-1; ++VertIdx)
{
DrawDebugLine(World, PolyVerts[VertIdx], PolyVerts[VertIdx+1], PathLineColor, bPersistent);
}
DrawDebugLine(World, PolyVerts[PolyVerts.Num()-1], PolyVerts[0], PathLineColor, bPersistent);
}
}
// draw ordered poly links
if (NumPathPolys > 0)
{
FVector PolyCenter;
FVector NextPolyCenter;
if ( GetPolyCenter(PathPolys[0], NextPolyCenter) ) // prime the pump
{
for (int32 PolyIdx=0; PolyIdx < NumPathPolys-1; ++PolyIdx)
{
PolyCenter = NextPolyCenter;
if ( GetPolyCenter(PathPolys[PolyIdx+1], NextPolyCenter) )
{
DrawDebugLine(World, PolyCenter, NextPolyCenter, PathLineColor, bPersistent);
DrawDebugBox(World, PolyCenter, FVector(5.f), PathLineColor, bPersistent);
}
}
}
}
#endif // ENABLE_DRAW_DEBUG
}
// Deprecated
void ARecastNavMesh::OnNavMeshTilesUpdated(const TArray<uint32>& ChangedTiles)
{
TArray<FNavTileRef> ChangedTileRefs;
FNavTileRef::DeprecatedMakeTileRefsFromTileIds(RecastNavMeshImpl, ChangedTiles, ChangedTileRefs);
OnNavMeshTilesUpdated(ChangedTileRefs);
}
void ARecastNavMesh::OnNavMeshTilesUpdated(const TArray<FNavTileRef>& ChangedTiles)
{
InvalidateAffectedPaths(ChangedTiles);
}
// Deprecated
void ARecastNavMesh::InvalidateAffectedPaths(const TArray<uint32>& ChangedTiles)
{
TArray<FNavTileRef> ChangedTileRefs;
FNavTileRef::DeprecatedMakeTileRefsFromTileIds(RecastNavMeshImpl, ChangedTiles, ChangedTileRefs);
InvalidateAffectedPaths(ChangedTileRefs);
}
void ARecastNavMesh::InvalidateAffectedPaths(const TArray<FNavTileRef>& ChangedTiles)
{
const int32 PathsCount = ActivePaths.Num();
const int32 ChangedTilesCount = ChangedTiles.Num();
if (ChangedTilesCount == 0 || PathsCount == 0)
{
return;
}
const dtNavMesh* DetourMesh = RecastNavMeshImpl->DetourNavMesh;
if (DetourMesh == nullptr)
{
return;
}
// Paths can be registered from async pathfinding thread.
// Theoretically paths are invalidated synchronously by the navigation system
// before starting async queries task but protecting ActivePaths will make
// the system safer in case of future timing changes.
{
FScopeLock PathLock(&ActivePathsLock);
for (int32 PathIndex = PathsCount - 1; PathIndex >= 0; --PathIndex)
{
FNavPathWeakPtr* WeakPathPtr = &ActivePaths[PathIndex];
FNavPathSharedPtr SharedPath = WeakPathPtr->Pin();
if (WeakPathPtr->IsValid() == false)
{
ActivePaths.RemoveAtSwap(PathIndex, 1, /*bAllowShrinking=*/false);
}
else
{
const FNavigationPath* NavPath = SharedPath.Get();
const FNavMeshPath* Path = NavPath ? NavPath->CastPath<FNavMeshPath>() : nullptr;
if (Path == nullptr ||
Path->IsReady() == false ||
Path->GetIgnoreInvalidation() == true)
{
// path not filled yet or doesn't care about invalidation
continue;
}
const int32 PathLenght = Path->PathCorridor.Num();
const NavNodeRef* PathPoly = Path->PathCorridor.GetData();
for (int32 NodeIndex = 0; NodeIndex < PathLenght; ++NodeIndex, ++PathPoly)
{
const FNavTileRef NavTileRef = UE::NavMesh::Private::GetTileRefFromPolyRef(*DetourMesh, *PathPoly);
if (ChangedTiles.Contains(NavTileRef))
{
SharedPath->Invalidate();
ActivePaths.RemoveAtSwap(PathIndex, 1, /*bAllowShrinking=*/false);
break;
}
}
}
}
}
}
URecastNavMeshDataChunk* ARecastNavMesh::GetNavigationDataChunk(ULevel* InLevel) const
{
return GetNavigationDataChunk(InLevel->NavDataChunks);
}
URecastNavMeshDataChunk* ARecastNavMesh::GetNavigationDataChunk(const ANavigationDataChunkActor& InActor) const
{
return GetNavigationDataChunk(InActor.GetNavDataChunk());
}
URecastNavMeshDataChunk* ARecastNavMesh::GetNavigationDataChunk(const TArray<UNavigationDataChunk*>& InChunks) const
{
FName ThisName = GetFName();
int32 ChunkIndex = InChunks.IndexOfByPredicate([&](UNavigationDataChunk* Chunk)
{
return Chunk->NavigationDataName == ThisName;
});
URecastNavMeshDataChunk* RcNavDataChunk = nullptr;
if (ChunkIndex != INDEX_NONE)
{
RcNavDataChunk = Cast<URecastNavMeshDataChunk>(InChunks[ChunkIndex]);
}
return RcNavDataChunk;
}
void ARecastNavMesh::EnsureBuildCompletion()
{
Super::EnsureBuildCompletion();
// Doing this as a safety net solution due to UE-20646, which was basically a result of random
// over-releasing of default filter's shared pointer (it seemed). We might have time to get
// back to this time some time in next 3 years :D
RecreateDefaultFilter();
}
void ARecastNavMesh::OnNavMeshGenerationFinished()
{
UWorld* World = GetWorld();
if (IsValid(World))
{
#if WITH_EDITOR
// For navmeshes that support streaming create navigation data holders in each streaming level
// so parts of navmesh can be streamed in/out with those levels
if (!World->IsGameWorld())
{
const auto& Levels = World->GetLevels();
for (auto Level : Levels)
{
if (Level->IsPersistentLevel())
{
continue;
}
URecastNavMeshDataChunk* NavDataChunk = GetNavigationDataChunk(Level);
if (SupportsStreaming())
{
// We use navigation volumes that belongs to this streaming level to find tiles we want to save
TArray<int32> LevelTiles;
TArray<FBox> LevelNavBounds = GetNavigableBoundsInLevel(Level);
RecastNavMeshImpl->GetNavMeshTilesIn(LevelNavBounds, LevelTiles);
if (LevelTiles.Num())
{
// Create new chunk only if we have something to save in it
if (NavDataChunk == nullptr)
{
NavDataChunk = NewObject<URecastNavMeshDataChunk>(Level);
NavDataChunk->NavigationDataName = GetFName();
Level->NavDataChunks.Add(NavDataChunk);
}
const EGatherTilesCopyMode CopyMode = RecastNavMeshImpl->NavMeshOwner->SupportsRuntimeGeneration() ? EGatherTilesCopyMode::CopyDataAndCacheData : EGatherTilesCopyMode::CopyData;
NavDataChunk->GetTiles(RecastNavMeshImpl, LevelTiles, CopyMode);
NavDataChunk->MarkPackageDirty();
continue;
}
}
// stale data that is left in the level
if (NavDataChunk)
{
// clear it
NavDataChunk->ReleaseTiles();
NavDataChunk->MarkPackageDirty();
Level->NavDataChunks.Remove(NavDataChunk);
}
}
}
// force navmesh drawing update
RequestDrawingUpdate(/*bForce=*/true);
#endif// WITH_EDITOR
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys)
{
NavSys->OnNavigationGenerationFinished(*this);
}
}
}
#if !UE_BUILD_SHIPPING
uint32 ARecastNavMesh::LogMemUsed() const
{
const uint32 SuperMemUsed = Super::LogMemUsed();
uint32 MemUsed = 0;
if (RecastNavMeshImpl && RecastNavMeshImpl->DetourNavMesh)
{
const dtNavMesh* const ConstNavMesh = RecastNavMeshImpl->DetourNavMesh;
for (int TileIndex = 0; TileIndex < RecastNavMeshImpl->DetourNavMesh->getMaxTiles(); ++TileIndex)
{
const dtMeshTile* Tile = ConstNavMesh->getTile(TileIndex);
if (Tile)
{
dtMeshHeader* const H = (dtMeshHeader*)(Tile->header);
const FDetourTileLayout TileLayout(*Tile);
MemUsed += TileLayout.TileSize;
}
}
}
UE_LOG(LogNavigation, Warning, TEXT("%s: ARecastNavMesh: %u\n self: %d"), *GetName(), MemUsed, sizeof(ARecastNavMesh));
return MemUsed + SuperMemUsed;
}
#endif // !UE_BUILD_SHIPPING
uint16 ARecastNavMesh::GetDefaultForbiddenFlags() const
{
return FPImplRecastNavMesh::GetFilterForbiddenFlags((const FRecastQueryFilter*)DefaultQueryFilter->GetImplementation());
}
void ARecastNavMesh::SetDefaultForbiddenFlags(uint16 ForbiddenAreaFlags)
{
FPImplRecastNavMesh::SetFilterForbiddenFlags((FRecastQueryFilter*)DefaultQueryFilter->GetImplementation(), ForbiddenAreaFlags);
}
void ARecastNavMesh::SetMaxSimultaneousTileGenerationJobsCount(int32 NewJobsCountLimit)
{
const int32 NewCount = NewJobsCountLimit > 0 ? NewJobsCountLimit : 1;
if (MaxSimultaneousTileGenerationJobsCount != NewCount)
{
MaxSimultaneousTileGenerationJobsCount = NewCount;
if (GetGenerator() != nullptr)
{
FRecastNavMeshGenerator* MyGenerator = static_cast<FRecastNavMeshGenerator*>(GetGenerator());
MyGenerator->SetMaxTileGeneratorTasks(NewCount);
}
}
}
bool ARecastNavMesh::FilterPolys(TArray<NavNodeRef>& PolyRefs, const FRecastQueryFilter* Filter, const UObject* QueryOwner) const
{
bool bSuccess = false;
if (RecastNavMeshImpl)
{
bSuccess = RecastNavMeshImpl->FilterPolys(PolyRefs, Filter, QueryOwner);
}
return bSuccess;
}
void ARecastNavMesh::ApplyWorldOffset(const FVector& InOffset, bool bWorldShift)
{
if (RecastNavMeshImpl)
{
RecastNavMeshImpl->ApplyWorldOffset(InOffset, bWorldShift);
}
Super::ApplyWorldOffset(InOffset, bWorldShift);
RequestDrawingUpdate();
}
void ARecastNavMesh::FillNavigationDataChunkActor(const FBox& QueryBounds, ANavigationDataChunkActor& DataChunkActor, FBox& OutTilesBounds) const
{
if (RecastNavMeshImpl)
{
UE_LOG(LogNavigation, Verbose, TEXT("%s Bounds pos: (%s) size: (%s)."), ANSI_TO_TCHAR(__FUNCTION__), *QueryBounds.GetCenter().ToString(), *QueryBounds.GetSize().ToString());
const TArray<FBox> Boxes({ QueryBounds });
TArray<int32> TileIndices;
RecastNavMeshImpl->GetNavMeshTilesIn(Boxes, TileIndices);
if (!TileIndices.IsEmpty())
{
// Add a data chunk for this navmesh
URecastNavMeshDataChunk* DataChunk = NewObject<URecastNavMeshDataChunk>(&DataChunkActor);
DataChunk->NavigationDataName = GetFName();
DataChunkActor.GetMutableNavDataChunk().Add(DataChunk);
DataChunk->GetTiles(RecastNavMeshImpl, TileIndices, SupportsRuntimeGeneration() ? EGatherTilesCopyMode::CopyDataAndCacheData : EGatherTilesCopyMode::CopyData);
DataChunk->GetTilesBounds(*RecastNavMeshImpl, TileIndices, OutTilesBounds);
}
}
}
void ARecastNavMesh::OnStreamingNavDataAdded(ANavigationDataChunkActor& InActor)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_RecastNavMesh_OnStreamingNavDataAdded);
if (SupportsStreaming() && RecastNavMeshImpl)
{
UE_VLOG_BOX(this, LogNavigation, Log, InActor.GetBounds(), FColor::Blue, TEXT(""));
URecastNavMeshDataChunk* NavDataChunk = GetNavigationDataChunk(InActor);
if (NavDataChunk)
{
AttachNavMeshDataChunk(*NavDataChunk);
}
if (IsWorldPartitionedDynamicNavmesh())
{
// Add dirtiness for preexisting elements that are not part of the base navmesh.
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
if (NavSys)
{
FNavigationOctreeFilter Filter;
Filter.bIncludeGeometry = true;
Filter.bExcludeLoadedData = true;
TArray<FNavigationOctreeElement> NavElements;
NavSys->FindElementsInNavOctree(InActor.GetBounds(), Filter, NavElements);
for (const FNavigationOctreeElement& NavElement : NavElements)
{
UE_VLOG_BOX(this, LogNavigation, Verbose, NavElement.Bounds.GetBox(), FColor::Orange, TEXT(""));
NavSys->AddDirtyArea(NavElement.Bounds.GetBox(), ENavigationDirtyFlag::All, [&NavElement] { return NavElement.Data->SourceObject.Get(); }, "Streaming data added");
}
}
}
}
}
void ARecastNavMesh::OnStreamingNavDataRemoved(ANavigationDataChunkActor& InActor)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_RecastNavMesh_OnStreamingNavDataRemoved);
if (SupportsStreaming() && RecastNavMeshImpl)
{
UE_VLOG_BOX(this, LogNavigation, Log, InActor.GetBounds(), FColor::Red, TEXT(""));
URecastNavMeshDataChunk* NavDataChunk = GetNavigationDataChunk(InActor);
if (NavDataChunk)
{
DetachNavMeshDataChunk(*NavDataChunk);
}
}
}
void ARecastNavMesh::OnStreamingLevelAdded(ULevel* InLevel, UWorld* InWorld)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_RecastNavMesh_OnStreamingLevelAdded);
if (SupportsStreaming() && RecastNavMeshImpl)
{
URecastNavMeshDataChunk* NavDataChunk = GetNavigationDataChunk(InLevel);
if (NavDataChunk)
{
AttachNavMeshDataChunk(*NavDataChunk);
}
}
}
void ARecastNavMesh::AttachNavMeshDataChunk(URecastNavMeshDataChunk& NavDataChunk)
{
const TArray<FNavTileRef> AttachedIndices = NavDataChunk.AttachTiles(*this);
if (AttachedIndices.Num() > 0)
{
InvalidateAffectedPaths(AttachedIndices);
RequestDrawingUpdate();
}
}
void ARecastNavMesh::OnStreamingLevelRemoved(ULevel* InLevel, UWorld* InWorld)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_RecastNavMesh_OnStreamingLevelRemoved);
if (SupportsStreaming() && RecastNavMeshImpl)
{
URecastNavMeshDataChunk* NavDataChunk = GetNavigationDataChunk(InLevel);
if (NavDataChunk)
{
DetachNavMeshDataChunk(*NavDataChunk);
}
}
}
#if WITH_EDITOR
double ARecastNavMesh::GetWorldPartitionNavigationDataBuilderOverlap() const
{
return TileSizeUU;
}
#endif //WITH_EDITOR
void ARecastNavMesh::DetachNavMeshDataChunk(URecastNavMeshDataChunk& NavDataChunk)
{
const TArray<FNavTileRef> DetachedIndices = NavDataChunk.DetachTiles(*this);
if (DetachedIndices.Num() > 0)
{
InvalidateAffectedPaths(DetachedIndices);
RequestDrawingUpdate();
}
}
bool ARecastNavMesh::AdjustLocationWithFilter(const FVector& StartLoc, FVector& OutAdjustedLocation, const FNavigationQueryFilter& Filter, const UObject* QueryOwner) const
{
INITIALIZE_NAVQUERY(NavQuery, Filter.GetMaxSearchNodes());
const FVector NavExtent = GetModifiedQueryExtent(GetDefaultQueryExtent());
const FVector::FReal Extent[3] = { NavExtent.X, NavExtent.Z, NavExtent.Y };
const dtQueryFilter* QueryFilter = ((const FRecastQueryFilter*)(Filter.GetImplementation()))->GetAsDetourQueryFilter();
ensure(QueryFilter);
FVector RecastStart = Unreal2RecastPoint(StartLoc);
FVector RecastAdjustedPoint = Unreal2RecastPoint(StartLoc);
NavNodeRef StartPolyID = INVALID_NAVNODEREF;
NavQuery.findNearestPoly(&RecastStart.X, Extent, QueryFilter, &StartPolyID, &RecastAdjustedPoint.X);
if (FVector::DistSquared(RecastStart, RecastAdjustedPoint) < KINDA_SMALL_NUMBER)
{
OutAdjustedLocation = StartLoc;
return false;
}
else
{
OutAdjustedLocation = Recast2UnrealPoint(RecastAdjustedPoint);
// move it just a bit further - otherwise recast can still pick "wrong" poly when
// later projecting StartLoc (meaning a poly we want to filter out with
// QueryFilter here)
OutAdjustedLocation += (OutAdjustedLocation - StartLoc).GetSafeNormal() * 0.1f;
return true;
}
}
FPathFindingResult ARecastNavMesh::FindPath(const FNavAgentProperties& AgentProperties, const FPathFindingQuery& Query)
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_RecastPathfinding);
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(Pathfinding);
const ANavigationData* Self = Query.NavData.Get();
check(Cast<const ARecastNavMesh>(Self));
const ARecastNavMesh* RecastNavMesh = (const ARecastNavMesh*)Self;
if (Self == NULL || RecastNavMesh->RecastNavMeshImpl == NULL)
{
return ENavigationQueryResult::Error;
}
FPathFindingResult Result(ENavigationQueryResult::Error);
FNavigationPath* NavPath = Query.PathInstanceToFill.Get();
FNavMeshPath* NavMeshPath = NavPath ? NavPath->CastPath<FNavMeshPath>() : nullptr;
if (NavMeshPath)
{
Result.Path = Query.PathInstanceToFill;
NavMeshPath->ResetForRepath();
}
else
{
Result.Path = Self->CreatePathInstance<FNavMeshPath>(Query);
NavPath = Result.Path.Get();
NavMeshPath = NavPath ? NavPath->CastPath<FNavMeshPath>() : nullptr;
}
const FNavigationQueryFilter* NavFilter = Query.QueryFilter.Get();
if (NavMeshPath && NavFilter)
{
NavMeshPath->ApplyFlags(Query.NavDataFlags);
const FVector AdjustedEndLocation = NavFilter->GetAdjustedEndLocation(Query.EndLocation);
if ((Query.StartLocation - AdjustedEndLocation).IsNearlyZero() == true)
{
Result.Path->GetPathPoints().Reset();
Result.Path->GetPathPoints().Add(FNavPathPoint(AdjustedEndLocation));
Result.Result = ENavigationQueryResult::Success;
}
else
{
Result.Result = RecastNavMesh->RecastNavMeshImpl->FindPath(Query.StartLocation, AdjustedEndLocation, Query.CostLimit, Query.bRequireNavigableEndLocation, *NavMeshPath, *NavFilter, Query.Owner.Get());
const bool bPartialPath = Result.IsPartial();
if (bPartialPath)
{
Result.Result = Query.bAllowPartialPaths ? ENavigationQueryResult::Success : ENavigationQueryResult::Fail;
}
}
}
return Result;
}
bool ARecastNavMesh::TestPath(const FNavAgentProperties& AgentProperties, const FPathFindingQuery& Query, int32* NumVisitedNodes)
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_RecastTestPath);
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(Pathfinding);
const ANavigationData* Self = Query.NavData.Get();
check(Cast<const ARecastNavMesh>(Self));
const ARecastNavMesh* RecastNavMesh = (const ARecastNavMesh*)Self;
if (Self == NULL || RecastNavMesh->RecastNavMeshImpl == NULL)
{
return false;
}
bool bPathExists = true;
const FNavigationQueryFilter* NavFilter = Query.QueryFilter.Get();
if (NavFilter)
{
const FVector AdjustedEndLocation = NavFilter->GetAdjustedEndLocation(Query.EndLocation);
if ((Query.StartLocation - AdjustedEndLocation).IsNearlyZero() == false)
{
ENavigationQueryResult::Type Result = RecastNavMesh->RecastNavMeshImpl->TestPath(Query.StartLocation, AdjustedEndLocation, Query.bRequireNavigableEndLocation, *NavFilter, Query.Owner.Get(), NumVisitedNodes);
bPathExists = (Result == ENavigationQueryResult::Success);
}
}
return bPathExists;
}
bool ARecastNavMesh::TestHierarchicalPath(const FNavAgentProperties& AgentProperties, const FPathFindingQuery& Query, int32* NumVisitedNodes)
{
const ANavigationData* Self = Query.NavData.Get();
check(Cast<const ARecastNavMesh>(Self));
const ARecastNavMesh* RecastNavMesh = (const ARecastNavMesh*)Self;
if (Self == nullptr || RecastNavMesh->RecastNavMeshImpl == nullptr || RecastNavMesh->RecastNavMeshImpl->DetourNavMesh == nullptr)
{
return false;
}
const bool bCanUseHierachicalPath = (Query.QueryFilter == RecastNavMesh->GetDefaultQueryFilter());
bool bPathExists = true;
const FNavigationQueryFilter* NavFilter = Query.QueryFilter.Get();
if (NavFilter)
{
const FVector AdjustedEndLocation = NavFilter->GetAdjustedEndLocation(Query.EndLocation);
if ((Query.StartLocation - AdjustedEndLocation).IsNearlyZero() == false)
{
bool bUseFallbackSearch = false;
if (bCanUseHierachicalPath)
{
#if WITH_NAVMESH_CLUSTER_LINKS
ENavigationQueryResult::Type Result = RecastNavMesh->RecastNavMeshImpl->TestClusterPath(Query.StartLocation, AdjustedEndLocation, NumVisitedNodes);
#else
UE_LOG(LogNavigation, Error, TEXT("Navmesh requires generation of clusters for hierarchical path. Set WITH_NAVMESH_CLUSTER_LINKS to 1 to generate them."));
ENavigationQueryResult::Type Result = ENavigationQueryResult::Invalid;
#endif // WITH_NAVMESH_CLUSTER_LINKS
bPathExists = (Result == ENavigationQueryResult::Success);
if (Result == ENavigationQueryResult::Error)
{
bUseFallbackSearch = true;
}
}
else
{
UE_LOG(LogNavigation, Log, TEXT("Hierarchical path finding test failed: filter doesn't match!"));
bUseFallbackSearch = true;
}
if (bUseFallbackSearch)
{
ENavigationQueryResult::Type Result = RecastNavMesh->RecastNavMeshImpl->TestPath(Query.StartLocation, AdjustedEndLocation, Query.bRequireNavigableEndLocation, *NavFilter, Query.Owner.Get(), NumVisitedNodes);
bPathExists = (Result == ENavigationQueryResult::Success);
}
}
}
return bPathExists;
}
bool ARecastNavMesh::NavMeshRaycast(const ANavigationData* Self, const FVector& RayStart, const FVector& RayEnd, FVector& HitLocation, FSharedConstNavQueryFilter QueryFilter,const UObject* QueryOwner, FRaycastResult& Result)
{
check(Cast<const ARecastNavMesh>(Self));
const ARecastNavMesh* RecastNavMesh = (const ARecastNavMesh*)Self;
if (Self == NULL || RecastNavMesh->RecastNavMeshImpl == NULL)
{
HitLocation = RayStart;
return true;
}
RecastNavMesh->RecastNavMeshImpl->Raycast(RayStart, RayEnd, RecastNavMesh->GetRightFilterRef(QueryFilter), QueryOwner, Result);
HitLocation = Result.HasHit() ? (RayStart + (RayEnd - RayStart) * Result.HitTime) : RayEnd;
return Result.HasHit();
}
bool ARecastNavMesh::NavMeshRaycast(const ANavigationData* Self, NavNodeRef RayStartNode, const FVector& RayStart, const FVector& RayEnd, FVector& HitLocation, FSharedConstNavQueryFilter QueryFilter, const UObject* QueryOwner)
{
check(Cast<const ARecastNavMesh>(Self));
const ARecastNavMesh* RecastNavMesh = (const ARecastNavMesh*)Self;
if (Self == NULL || RecastNavMesh->RecastNavMeshImpl == NULL)
{
HitLocation = RayStart;
return true;
}
FRaycastResult Result;
RecastNavMesh->RecastNavMeshImpl->Raycast(RayStart, RayEnd, RecastNavMesh->GetRightFilterRef(QueryFilter), QueryOwner, Result, RayStartNode);
HitLocation = Result.HasHit() ? (RayStart + (RayEnd - RayStart) * Result.HitTime) : RayEnd;
return Result.HasHit();
}
void ARecastNavMesh::BatchRaycast(TArray<FNavigationRaycastWork>& Workload, FSharedConstNavQueryFilter Filter, const UObject* Querier) const
{
if (RecastNavMeshImpl == NULL || Workload.Num() == 0 || RecastNavMeshImpl->DetourNavMesh == NULL)
{
return;
}
const FNavigationQueryFilter& FilterToUse = GetRightFilterRef(Filter);
FRecastSpeciaLinkFilter LinkFilter(FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld()), Querier);
INITIALIZE_NAVQUERY_WLINKFILTER(NavQuery, FilterToUse.GetMaxSearchNodes(), LinkFilter);
const dtQueryFilter* QueryFilter = ((const FRecastQueryFilter*)(FilterToUse.GetImplementation()))->GetAsDetourQueryFilter();
if (QueryFilter == NULL)
{
UE_VLOG(this, LogNavigation, Warning, TEXT("FPImplRecastNavMesh::FindPath failing due to QueryFilter == NULL"));
return;
}
const FVector NavExtent = GetModifiedQueryExtent(GetDefaultQueryExtent());
const FVector::FReal Extent[3] = { NavExtent.X, NavExtent.Z, NavExtent.Y };
for (FNavigationRaycastWork& WorkItem : Workload)
{
ARecastNavMesh::FRaycastResult RaycastResult;
const FVector RecastStart = Unreal2RecastPoint(WorkItem.RayStart);
const FVector RecastEnd = Unreal2RecastPoint(WorkItem.RayEnd);
NavNodeRef StartNode = INVALID_NAVNODEREF;
NavQuery.findNearestContainingPoly(&RecastStart.X, Extent, QueryFilter, &StartNode, NULL);
if (StartNode != INVALID_NAVNODEREF)
{
FVector::FReal RecastHitNormal[3];
const dtStatus RaycastStatus = NavQuery.raycast(StartNode, &RecastStart.X, &RecastEnd.X
, QueryFilter, &RaycastResult.HitTime, RecastHitNormal
, RaycastResult.CorridorPolys, &RaycastResult.CorridorPolysCount, RaycastResult.GetMaxCorridorSize());
if (dtStatusSucceed(RaycastStatus) && RaycastResult.HasHit())
{
WorkItem.bDidHit = true;
WorkItem.HitLocation = FNavLocation(WorkItem.RayStart + (WorkItem.RayEnd - WorkItem.RayStart) * RaycastResult.HitTime, RaycastResult.GetLastNodeRef());
}
}
}
}
bool ARecastNavMesh::IsSegmentOnNavmesh(const FVector& SegmentStart, const FVector& SegmentEnd, FSharedConstNavQueryFilter Filter, const UObject* QueryOwner) const
{
if (RecastNavMeshImpl == NULL)
{
return false;
}
FRaycastResult Result;
RecastNavMeshImpl->Raycast(SegmentStart, SegmentEnd, GetRightFilterRef(Filter), QueryOwner, Result);
return Result.bIsRaycastEndInCorridor && !Result.HasHit();
}
bool ARecastNavMesh::FindStraightPath(const FVector& StartLoc, const FVector& EndLoc, const TArray<NavNodeRef>& PathCorridor, TArray<FNavPathPoint>& PathPoints, TArray<FNavLinkId>* CustomLinks) const
{
return RecastNavMeshImpl && RecastNavMeshImpl->FindStraightPath(StartLoc, EndLoc, PathCorridor, PathPoints, CustomLinks);
}
int32 ARecastNavMesh::DebugPathfinding(const FPathFindingQuery& Query, TArray<FRecastDebugPathfindingData>& Steps)
{
int32 NumSteps = 0;
const ANavigationData* Self = Query.NavData.Get();
check(Cast<const ARecastNavMesh>(Self));
const ARecastNavMesh* RecastNavMesh = (const ARecastNavMesh*)Self;
if (Self == NULL || RecastNavMesh->RecastNavMeshImpl == NULL)
{
return false;
}
if ((Query.StartLocation - Query.EndLocation).IsNearlyZero() == false)
{
NumSteps = RecastNavMesh->RecastNavMeshImpl->DebugPathfinding(Query.StartLocation, Query.EndLocation, Query.CostLimit, Query.bRequireNavigableEndLocation, *(Query.QueryFilter.Get()), Query.Owner.Get(), Steps);
}
return NumSteps;
}
void ARecastNavMesh::UpdateNavVersion()
{
NavMeshVersion = NAVMESHVER_LATEST;
}
#if WITH_EDITOR
void ARecastNavMesh::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedChainEvent)
{
static const FName NAME_Generation = FName(TEXT("Generation"));
static const FName NAME_NavMeshResolutionParams = FName(TEXT("NavMeshResolutionParams"));
Super::PostEditChangeChainProperty(PropertyChangedChainEvent);
if (PropertyChangedChainEvent.Property != NULL)
{
const FName CategoryName = FObjectEditorUtils::GetCategoryFName(PropertyChangedChainEvent.Property);
if (CategoryName == NAME_Generation)
{
const FName PropName = PropertyChangedChainEvent.Property->GetFName();
bool bRebuild = false;
if (PropName == GET_MEMBER_NAME_CHECKED(FNavMeshResolutionParam, CellSize))
{
const int32 ChangedIndex = PropertyChangedChainEvent.GetArrayIndex(NAME_NavMeshResolutionParams.ToString());
if (ChangedIndex != INDEX_NONE)
{
float& RefCellSize = NavMeshResolutionParams[ChangedIndex].CellSize;
RefCellSize = UE::NavMesh::Private::GetClampedCellSize(RefCellSize);
TileSizeUU = UE::NavMesh::Private::GetClampedTileSizeUU(TileSizeUU, RefCellSize, AgentRadius);
// Adjust tile size to be a multiple of RefCellSize
const float RefCellCount = FMath::TruncToFloat( TileSizeUU / RefCellSize);
TileSizeUU = RefCellCount * RefCellSize;
// Adjust the other cell size (find count of cells and set the size of the cell)
for (uint8 Index = 0; Index < (uint8)ENavigationDataResolution::MAX; Index++)
{
if (Index != ChangedIndex)
{
float& ResolutionCellSize = NavMeshResolutionParams[Index].CellSize;
const float ResolutionCellCount = FMath::TruncToFloat(TileSizeUU / ResolutionCellSize);
ResolutionCellSize = UE::NavMesh::Private::GetClampedCellSize(TileSizeUU / ResolutionCellCount);
}
}
PRAGMA_DISABLE_DEPRECATION_WARNINGS
// Update the deprecated CellSize to fit the default resolution CellSize
CellSize = NavMeshResolutionParams[(uint8)ENavigationDataResolution::Default].CellSize;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
bRebuild = true;
}
}
else if (PropName == GET_MEMBER_NAME_CHECKED(FNavMeshResolutionParam, CellHeight))
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
// Update the deprecated CellHeight to fit the default resolution CellHeight
CellHeight = NavMeshResolutionParams[(uint8)ENavigationDataResolution::Default].CellHeight;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
bRebuild = true;
}
if (bRebuild)
{
// update config
FillConfig(NavDataConfig);
const UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
if (!HasAnyFlags(RF_ClassDefaultObject)
&& NavSys && NavSys->GetIsAutoUpdateEnabled()
&& PropName != GET_MEMBER_NAME_CHECKED(ARecastNavMesh, MaxSimultaneousTileGenerationJobsCount))
{
RebuildAll();
}
}
}
}
}
void ARecastNavMesh::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
static const FName NAME_Generation = FName(TEXT("Generation"));
static const FName NAME_Display = FName(TEXT("Display"));
static const FName NAME_RuntimeGeneration = FName(TEXT("RuntimeGeneration"));
static const FName NAME_TileNumberHardLimit = GET_MEMBER_NAME_CHECKED(ARecastNavMesh, TileNumberHardLimit);
static const FName NAME_Query = FName(TEXT("Query"));
Super::PostEditChangeProperty(PropertyChangedEvent);
if (PropertyChangedEvent.Property != NULL)
{
const FName CategoryName = FObjectEditorUtils::GetCategoryFName(PropertyChangedEvent.Property);
if (CategoryName == NAME_Generation)
{
const FName PropName = PropertyChangedEvent.Property->GetFName();
const FName MemberName = PropertyChangedEvent.MemberProperty->GetFName();
if (PropName == GET_MEMBER_NAME_CHECKED(ARecastNavMesh, AgentRadius))
{
// changing AgentRadius is no longer affecting TileSizeUU since
// that's not how we use it. It's actually not really supported to
// modify AgentRadius directly on navmesh instance, since such
// a navmesh will get discarded during navmesh registration with
// the navigation system.
// @todo consider hiding it (we might already have a ticket for that).
UE_LOG(LogNavigation, Warning, TEXT("Changing AgentRadius directly on RecastNavMesh instance is unsupported. Please use Project Settings > NavigationSystem > SupportedAgents to change AgentRadius"));
}
else if (PropName == GET_MEMBER_NAME_CHECKED(ARecastNavMesh, TileSizeUU))
{
SetCellSize(ENavigationDataResolution::Default, UE::NavMesh::Private::GetClampedCellSize(GetCellSize(ENavigationDataResolution::Default)));
TileSizeUU = UE::NavMesh::Private::GetClampedTileSizeUU(TileSizeUU, GetCellSize(ENavigationDataResolution::Default), AgentRadius);
// Match cell sizes to tile size.
for (uint8 Index = 0; Index < (uint8)ENavigationDataResolution::MAX; Index++)
{
SetCellSize((ENavigationDataResolution)Index, TileSizeUU / FMath::TruncToFloat(TileSizeUU / GetCellSize((ENavigationDataResolution)Index)));
}
PRAGMA_DISABLE_DEPRECATION_WARNINGS
// Set deprecated CellSize
CellSize = GetCellSize(ENavigationDataResolution::Default);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
// update config
FillConfig(NavDataConfig);
}
else if (PropName == NAME_TileNumberHardLimit)
{
TileNumberHardLimit = 1 << (FMath::CeilToInt(FMath::Log2(static_cast<float>(TileNumberHardLimit))));
UpdatePolyRefBitsPreview();
}
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
if (!HasAnyFlags(RF_ClassDefaultObject)
&& NavSys && NavSys->GetIsAutoUpdateEnabled()
&& PropName != GET_MEMBER_NAME_CHECKED(ARecastNavMesh, MaxSimultaneousTileGenerationJobsCount))
{
RebuildAll();
}
}
else if (CategoryName == NAME_Display)
{
RequestDrawingUpdate();
}
else if (PropertyChangedEvent.Property->GetFName() == NAME_RuntimeGeneration)
{
// @todo this contraption is required to clear RuntimeGeneration value in DefaultEngine.ini
// if it gets set to its default value (UE-23762). This is hopefully a temporary solution
// since it's an Core-level issue (UE-23873).
if (RuntimeGeneration == ERuntimeGenerationType::Static)
{
const FString EngineIniFilename = FPaths::ConvertRelativePathToFull(GetDefault<UEngine>()->GetDefaultConfigFilename());
GConfig->SetString(TEXT("/Script/NavigationSystem.RecastNavMesh"), *NAME_RuntimeGeneration.ToString(), TEXT("Static"), *EngineIniFilename);
GConfig->Flush(false);
}
}
else if (CategoryName == NAME_Query)
{
RecreateDefaultFilter();
}
}
}
#endif // WITH_EDITOR
bool ARecastNavMesh::NeedsRebuild() const
{
bool bLooksLikeNeeded = !RecastNavMeshImpl || RecastNavMeshImpl->GetRecastMesh() == 0;
if (NavDataGenerator.IsValid())
{
return bLooksLikeNeeded || NavDataGenerator->GetNumRemaningBuildTasks() > 0;
}
return bLooksLikeNeeded;
}
bool ARecastNavMesh::SupportsRuntimeGeneration() const
{
// Generator should be disabled for Static navmesh
return (RuntimeGeneration != ERuntimeGenerationType::Static);
}
bool ARecastNavMesh::SupportsStreaming() const
{
// Actually nothing prevents us to support streaming with dynamic generation
// Right now streaming in sub-level causes navmesh to build itself, so no point to stream tiles in
return (RuntimeGeneration != ERuntimeGenerationType::Dynamic) || bIsWorldPartitioned;
}
bool ARecastNavMesh::IsWorldPartitionedDynamicNavmesh() const
{
return bIsWorldPartitioned && SupportsRuntimeGeneration();
}
FRecastNavMeshGenerator* ARecastNavMesh::CreateGeneratorInstance()
{
return new FRecastNavMeshGenerator(*this);
}
bool ARecastNavMesh::IsUsingActiveTilesGeneration(const UNavigationSystemV1& NavSys) const
{
return SupportsRuntimeGeneration() && (NavSys.IsActiveTilesGenerationEnabled() || bIsWorldPartitioned);
}
void ARecastNavMesh::ConditionalConstructGenerator()
{
if (NavDataGenerator.IsValid())
{
NavDataGenerator->CancelBuild();
NavDataGenerator.Reset();
}
UWorld* World = GetWorld();
check(World);
const bool bRequiresGenerator = SupportsRuntimeGeneration() || !World->IsGameWorld();
if (bRequiresGenerator)
{
FRecastNavMeshGenerator* Generator = CreateGeneratorInstance();
if (Generator)
{
NavDataGenerator = MakeShareable((FNavDataGenerator*)Generator);
Generator->Init();
}
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys)
{
RestrictBuildingToActiveTiles(IsUsingActiveTilesGeneration(*NavSys));
}
}
}
// Deprecated
PRAGMA_DISABLE_DEPRECATION_WARNINGS
void ARecastNavMesh::UpdateGenerationProperties(const FRecastNavMeshGenerationProperties& GenerationProps)
{
TilePoolSize = GenerationProps.TilePoolSize;
TileSizeUU = GenerationProps.TileSizeUU;
CellSize = GenerationProps.CellSize;
CellHeight = GenerationProps.CellHeight;
AgentRadius = GenerationProps.AgentRadius;
AgentHeight = GenerationProps.AgentHeight;
AgentMaxSlope = GenerationProps.AgentMaxSlope;
AgentMaxStepHeight = GenerationProps.AgentMaxStepHeight;
MinRegionArea = GenerationProps.MinRegionArea;
MergeRegionSize = GenerationProps.MergeRegionSize;
MaxSimplificationError = GenerationProps.MaxSimplificationError;
TileNumberHardLimit = GenerationProps.TileNumberHardLimit;
RegionPartitioning = GenerationProps.RegionPartitioning;
LayerPartitioning = GenerationProps.LayerPartitioning;
RegionChunkSplits = GenerationProps.RegionChunkSplits;
LayerChunkSplits = GenerationProps.LayerChunkSplits;
bSortNavigationAreasByCost = GenerationProps.bSortNavigationAreasByCost;
bPerformVoxelFiltering = GenerationProps.bPerformVoxelFiltering;
bMarkLowHeightAreas = GenerationProps.bMarkLowHeightAreas;
bUseExtraTopCellWhenMarkingAreas = GenerationProps.bUseExtraTopCellWhenMarkingAreas;
bFilterLowSpanSequences = GenerationProps.bFilterLowSpanSequences;
bFilterLowSpanFromTileCache = GenerationProps.bFilterLowSpanFromTileCache;
bFixedTilePoolSize = GenerationProps.bFixedTilePoolSize;
bIsWorldPartitioned = GenerationProps.bIsWorldPartitioned;
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
bool ARecastNavMesh::IsVoxelCacheEnabled()
{
#if RECAST_ASYNC_REBUILDING
// voxel cache is using static buffers to minimize memory impact
// therefore it can run only with synchronous navmesh rebuilds
return false;
#endif
ARecastNavMesh* DefOb = (ARecastNavMesh*)ARecastNavMesh::StaticClass()->GetDefaultObject();
return DefOb && DefOb->bUseVoxelCache;
}
const FRecastQueryFilter* ARecastNavMesh::GetNamedFilter(ERecastNamedFilter::Type FilterType)
{
check(FilterType < ERecastNamedFilter::NamedFiltersCount);
return NamedFilters[FilterType];
}
#undef INITIALIZE_NAVQUERY
void ARecastNavMesh::UpdateNavObject()
{
OnNavMeshUpdate.Broadcast();
}
#endif //WITH_RECAST
bool ARecastNavMesh::HasValidNavmesh() const
{
#if WITH_RECAST
return (RecastNavMeshImpl && RecastNavMeshImpl->DetourNavMesh && RecastNavMeshImpl->DetourNavMesh->isEmpty() == false);
#else
return false;
#endif // WITH_RECAST
}
#if WITH_RECAST
bool ARecastNavMesh::HasCompleteDataInRadius(const FVector& TestLocation, FVector::FReal TestRadius) const
{
if (HasValidNavmesh() == false)
{
return false;
}
const dtNavMesh* NavMesh = RecastNavMeshImpl->DetourNavMesh;
const dtNavMeshParams* NavParams = RecastNavMeshImpl->DetourNavMesh->getParams();
const float NavTileSize = GetTileSizeUU();
const FVector RcNavOrigin(NavParams->orig[0], NavParams->orig[1], NavParams->orig[2]);
const FBox RcBounds = Unreal2RecastBox(FBox::BuildAABB(TestLocation, FVector(TestRadius, TestRadius, 0)));
const FVector RcTestLocation = Unreal2RecastPoint(TestLocation);
const int32 MinTileX = IntCastChecked<int32>(FMath::FloorToInt((RcBounds.Min.X - RcNavOrigin.X) / NavTileSize));
const int32 MaxTileX = IntCastChecked<int32>(FMath::CeilToInt((RcBounds.Max.X - RcNavOrigin.X) / NavTileSize));
const int32 MinTileY = IntCastChecked<int32>(FMath::FloorToInt((RcBounds.Min.Z - RcNavOrigin.Z) / NavTileSize));
const int32 MaxTileY = IntCastChecked<int32>(FMath::CeilToInt((RcBounds.Max.Z - RcNavOrigin.Z) / NavTileSize));
const FVector RcTileExtent2D(NavTileSize * 0.5f, 0.f, NavTileSize * 0.5f);
const FVector::FReal RadiusSq = FMath::Square(TestRadius);
for (int32 TileX = MinTileX; TileX <= MaxTileX; TileX++)
{
for (int32 TileY = MinTileY; TileY <= MaxTileY; TileY++)
{
const FVector RcTileCenter(RcNavOrigin.X + ((TileX + 0.5f) * NavTileSize), RcTestLocation.Y, RcNavOrigin.Z + ((TileY + 0.5f) * NavTileSize));
const bool bInside = FMath::SphereAABBIntersection(RcTestLocation, RadiusSq, FBox::BuildAABB(RcTileCenter, RcTileExtent2D));
if (bInside)
{
const int32 NumTiles = NavMesh->getTileCountAt(TileX, TileY);
if (NumTiles <= 0)
{
const bool bHasFailsafeData = bStoreEmptyTileLayers && RecastNavMeshImpl->HasTileCacheLayers(TileX, TileY);
if (!bHasFailsafeData)
{
return false;
}
}
}
}
}
return true;
}
//----------------------------------------------------------------------//
// RecastNavMesh: Active Tiles
//----------------------------------------------------------------------//
void ARecastNavMesh::UpdateActiveTiles(const TArray<FNavigationInvokerRaw>& InvokerLocations)
{
TRACE_CPUPROFILER_EVENT_SCOPE(ARecastNavMesh::UpdateActiveTiles);
if (HasValidNavmesh() == false)
{
return;
}
const FRecastNavMeshGenerator* MyGenerator = static_cast<FRecastNavMeshGenerator*>(GetGenerator());
if (MyGenerator == nullptr)
{
return;
}
const dtNavMeshParams* NavParams = GetRecastNavMeshImpl()->DetourNavMesh->getParams();
check(NavParams && MyGenerator);
const FRecastBuildConfig& Config = MyGenerator->GetConfig();
const FVector NavmeshOrigin = Recast2UnrealPoint(NavParams->orig);
const FVector::FReal TileDim = Config.GetTileSizeUU();
TArray<FIntPoint>& ActiveTiles = GetActiveTiles();
TArray<FIntPoint> OldActiveSet = ActiveTiles;
TArray<FNavMeshDirtyTileElement> TilesInMinDistance;
TArray<FIntPoint> TilesInMaxDistance;
TArray<FIntPoint> TileToAppend;
TilesInMinDistance.Reserve(ActiveTiles.Num());
TilesInMaxDistance.Reserve(ActiveTiles.Num());
TileToAppend.Reserve(ActiveTiles.Num());
ActiveTiles.Reset();
for (const FNavigationInvokerRaw& Invoker : InvokerLocations)
{
if (!Invoker.SupportedAgents.Contains(Config.AgentIndex))
{
continue;
}
const FVector InvokerRelativeLocation = (NavmeshOrigin - Invoker.Location);
const FVector::FReal TileCenterDistanceToRemoveSq = FMath::Square(TileDim * UE_SQRT_2 / 2 + Invoker.RadiusMax);
const FVector::FReal TileCenterDistanceToAddSq = FMath::Square(TileDim * UE_SQRT_2 / 2 + Invoker.RadiusMin);
const int32 MinTileX = IntCastChecked<int32>(FMath::FloorToInt((InvokerRelativeLocation.X - Invoker.RadiusMax) / TileDim));
const int32 MaxTileX = IntCastChecked<int32>(FMath::CeilToInt((InvokerRelativeLocation.X + Invoker.RadiusMax) / TileDim));
const int32 MinTileY = IntCastChecked<int32>(FMath::FloorToInt((InvokerRelativeLocation.Y - Invoker.RadiusMax) / TileDim));
const int32 MaxTileY = IntCastChecked<int32>(FMath::CeilToInt((InvokerRelativeLocation.Y + Invoker.RadiusMax) / TileDim));
for (int32 X = MinTileX; X <= MaxTileX; ++X)
{
for (int32 Y = MinTileY; Y <= MaxTileY; ++Y)
{
const FVector::FReal DistanceSq = (InvokerRelativeLocation - FVector(X * TileDim + TileDim / 2, Y * TileDim + TileDim / 2, 0.f)).SizeSquared2D();
if (DistanceSq < TileCenterDistanceToRemoveSq)
{
TilesInMaxDistance.AddUnique(FIntPoint(X, Y));
if (DistanceSq < TileCenterDistanceToAddSq)
{
// Add unique tile
FNavMeshDirtyTileElement* FoundTile = TilesInMinDistance.FindByPredicate([X, Y](const FNavMeshDirtyTileElement& Tile){ return Tile.Coordinates == FIntPoint(X, Y);});
if (FoundTile)
{
// Update the priority if already existing
FoundTile->InvokerPriority = FMath::Max(FoundTile->InvokerPriority, Invoker.Priority);
}
else
{
TilesInMinDistance.Add(FNavMeshDirtyTileElement{FIntPoint(X,Y), DistanceSq, Invoker.Priority});
TileToAppend.Add(FIntPoint(X,Y));
}
}
}
}
}
}
ActiveTiles.Append(TileToAppend);
TArray<FIntPoint> TilesToRemove;
TilesToRemove.Reserve(OldActiveSet.Num());
for (int32 Index = OldActiveSet.Num() - 1; Index >= 0; --Index)
{
const FIntPoint& Tile = OldActiveSet[Index];
if (TilesInMaxDistance.Find(Tile) == INDEX_NONE)
{
TilesToRemove.Add(Tile);
OldActiveSet.RemoveAtSwap(Index, 1, /*bAllowShrinking=*/false);
}
else
{
ActiveTiles.AddUnique(Tile);
}
}
// Find tiles to update
TArray<FNavMeshDirtyTileElement> TilesToUpdate;
TilesToUpdate.Reserve(ActiveTiles.Num());
for (int32 Index = TilesInMinDistance.Num() - 1; Index >= 0; --Index)
{
// Check if it's a new tile (not in the active set)
const FNavMeshDirtyTileElement& Tile = TilesInMinDistance[Index];
if (OldActiveSet.Find(Tile.Coordinates) == INDEX_NONE)
{
TilesToUpdate.Add(Tile);
}
}
UE_SUPPRESS(LogNavigation, Log,
{
if (TilesToRemove.Num() != 0 || TilesToUpdate.Num() != 0)
{
UE_VLOG(this, LogNavigation, Log, TEXT("Updating active tiles: %d to remove, %d to update"), TilesToRemove.Num(), TilesToUpdate.Num());
}
});
RemoveTiles(TilesToRemove);
RebuildTile(TilesToUpdate);
if (TilesToRemove.Num() > 0 || TilesToUpdate.Num() > 0)
{
UpdateNavMeshDrawing();
}
}
void ARecastNavMesh::RemoveTiles(const TArray<FIntPoint>& Tiles)
{
if (Tiles.Num() > 0)
{
FRecastNavMeshGenerator* MyGenerator = static_cast<FRecastNavMeshGenerator*>(GetGenerator());
if (MyGenerator)
{
MyGenerator->RemoveTiles(Tiles);
}
}
}
// Deprecated
void ARecastNavMesh::RebuildTile(const TArray<FIntPoint>& Tiles)
{
TArray<FNavMeshDirtyTileElement> ActiveTiles;
ActiveTiles.Reserve(Tiles.Num());
for (const FIntPoint& Point : Tiles)
{
ActiveTiles.Add(FNavMeshDirtyTileElement{Point, TNumericLimits<FVector::FReal>::Max(), ENavigationInvokerPriority::Default});
}
RebuildTile(ActiveTiles);
}
void ARecastNavMesh::RebuildTile(const TArray<FNavMeshDirtyTileElement>& Tiles)
{
if (Tiles.Num() > 0)
{
FRecastNavMeshGenerator* MyGenerator = static_cast<FRecastNavMeshGenerator*>(GetGenerator());
if (MyGenerator)
{
MyGenerator->ReAddTiles(Tiles);
}
}
}
void ARecastNavMesh::DirtyTilesInBounds(const FBox& Bounds)
{
if (HasValidNavmesh() == false)
{
return;
}
FRecastNavMeshGenerator* MyGenerator = static_cast<FRecastNavMeshGenerator*>(GetGenerator());
if (MyGenerator)
{
MyGenerator->DiscardCurrentBuildingTasks();
}
const dtNavMesh* DetourNavMesh = RecastNavMeshImpl->GetRecastMesh();
// Remove all tiles
const int32 TileCount = GetNavMeshTilesCount();
TArray<FIntPoint> TilesToRemove;
TilesToRemove.Reserve(TileCount);
for (int32 TileIndex = 0; TileIndex < TileCount; ++TileIndex)
{
const dtMeshTile* Tile = DetourNavMesh->getTile(TileIndex);
const dtMeshHeader* Header = Tile != nullptr ? Tile->header : nullptr;
if (Header)
{
TilesToRemove.Add(FIntPoint(Header->x, Header->y));
}
}
RemoveTiles(TilesToRemove);
const UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
const FBox OverlappingBounds = Bounds.Overlap(NavSys->GetWorldBounds());
if (OverlappingBounds.IsValid)
{
// Add tiles within the overlapping bounds
TArray<FNavMeshDirtyTileElement> Tiles;
const FVector RcNavMeshOrigin = Unreal2RecastPoint(NavMeshOriginOffset);
const float TileSizeInWorldUnits = GetTileSizeUU();
const FRcTileBox TileBox(OverlappingBounds, RcNavMeshOrigin, TileSizeInWorldUnits);
UE_LOG(LogNavigation, VeryVerbose, TEXT("RebuildTilesFromBounds %i tiles: (%i,%i) to (%i,%i)"), (TileBox.XMax-TileBox.XMin)*(TileBox.YMax-TileBox.YMin), TileBox.XMin, TileBox.YMin, TileBox.XMax, TileBox.YMax);
for (int32 TileY = TileBox.YMin; TileY <= TileBox.YMax; ++TileY)
{
for (int32 TileX = TileBox.XMin; TileX <= TileBox.XMax; ++TileX)
{
// For now, new dirtiness is made with default priority.
Tiles.Add(FNavMeshDirtyTileElement{FIntPoint(TileX, TileY), TNumericLimits<FVector::FReal>::Max(), ENavigationInvokerPriority::Default});
}
}
RebuildTile(Tiles);
}
}
#if RECAST_INTERNAL_DEBUG_DATA
const TMap<FIntPoint, struct FRecastInternalDebugData>* ARecastNavMesh::GetDebugDataMap() const
{
if (RecastNavMeshImpl)
{
return &RecastNavMeshImpl->DebugDataMap;
}
return nullptr;
}
#endif //RECAST_INTERNAL_DEBUG_DATA
//----------------------------------------------------------------------//
// FRecastNavMeshCachedData
//----------------------------------------------------------------------//
FRecastNavMeshCachedData FRecastNavMeshCachedData::Construct(const ARecastNavMesh* RecastNavMeshActor)
{
check(RecastNavMeshActor);
FRecastNavMeshCachedData CachedData;
CachedData.ActorOwner = RecastNavMeshActor;
// create copies from crucial ARecastNavMesh data
CachedData.bUseSortFunction = RecastNavMeshActor->bSortNavigationAreasByCost;
TArray<FSupportedAreaData> Areas;
RecastNavMeshActor->GetSupportedAreas(Areas);
FMemory::Memzero(CachedData.FlagsPerArea, sizeof(ARecastNavMesh::FNavPolyFlags) * RECAST_MAX_AREAS);
for (int32 i = 0; i < Areas.Num(); i++)
{
const UClass* AreaClass = Areas[i].AreaClass;
const UNavArea* DefArea = AreaClass ? ((UClass*)AreaClass)->GetDefaultObject<UNavArea>() : NULL;
if (DefArea)
{
CachedData.AreaClassToIdMap.Add(AreaClass, Areas[i].AreaID);
CachedData.FlagsPerArea[Areas[i].AreaID] = DefArea->GetAreaFlags();
}
}
FMemory::Memcpy(CachedData.FlagsPerOffMeshLinkArea, CachedData.FlagsPerArea, sizeof(CachedData.FlagsPerArea));
static const ARecastNavMesh::FNavPolyFlags NavLinkFlag = ARecastNavMesh::GetNavLinkFlag();
if (NavLinkFlag != 0)
{
ARecastNavMesh::FNavPolyFlags* AreaFlag = CachedData.FlagsPerOffMeshLinkArea;
for (int32 AreaIndex = 0; AreaIndex < RECAST_MAX_AREAS; ++AreaIndex, ++AreaFlag)
{
*AreaFlag |= NavLinkFlag;
}
}
return CachedData;
}
void FRecastNavMeshCachedData::OnAreaAdded(const UClass* AreaClass, int32 AreaID)
{
const UNavArea* DefArea = AreaClass ? ((UClass*)AreaClass)->GetDefaultObject<UNavArea>() : NULL;
if (DefArea && AreaID >= 0)
{
AreaClassToIdMap.Add(AreaClass, AreaID);
FlagsPerArea[AreaID] = DefArea->GetAreaFlags();
static const ARecastNavMesh::FNavPolyFlags NavLinkFlag = ARecastNavMesh::GetNavLinkFlag();
if (NavLinkFlag != 0)
{
FlagsPerOffMeshLinkArea[AreaID] = FlagsPerArea[AreaID] | NavLinkFlag;
}
}
}
void FRecastNavMeshCachedData::OnAreaRemoved(const UClass* AreaClass)
{
const int32* AreaID = AreaClass ? AreaClassToIdMap.Find(AreaClass) : nullptr;
if (AreaID != nullptr)
{
AreaClassToIdMap.Remove(AreaClass);
FlagsPerArea[*AreaID] = 0;
FlagsPerOffMeshLinkArea[*AreaID] = 0;
}
}
FNavLinkId ARecastNavMesh::GetNavLinkUserId(NavNodeRef LinkPolyID) const
{
return RecastNavMeshImpl ? RecastNavMeshImpl->GetNavLinkUserId(LinkPolyID) : FNavLinkId::Invalid;
}
dtNavMesh* ARecastNavMesh::GetRecastMesh()
{
return RecastNavMeshImpl ? RecastNavMeshImpl->GetRecastMesh() : nullptr;
}
const dtNavMesh* ARecastNavMesh::GetRecastMesh() const
{
return RecastNavMeshImpl ? RecastNavMeshImpl->GetRecastMesh() : nullptr;
}
#endif// WITH_RECAST
//----------------------------------------------------------------------//
// BP API
//----------------------------------------------------------------------//
bool ARecastNavMesh::K2_ReplaceAreaInTileBounds(FBox Bounds, TSubclassOf<UNavArea> OldArea, TSubclassOf<UNavArea> NewArea, bool ReplaceLinks)
{
bool bReplaced = false;
#if WITH_RECAST
bReplaced = ReplaceAreaInTileBounds(Bounds, OldArea, NewArea, ReplaceLinks) > 0;
if (bReplaced)
{
RequestDrawingUpdate();
}
#endif // WITH_RECAST
return bReplaced;
}