You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
2702 lines
84 KiB
C++
2702 lines
84 KiB
C++
// Copyright 1998-2019 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"
|
|
|
|
#if WITH_EDITOR
|
|
#include "ObjectEditorUtils.h"
|
|
#endif
|
|
|
|
#if WITH_RECAST
|
|
#include "Detour/DetourAlloc.h"
|
|
#endif // WITH_RECAST
|
|
|
|
#include "NavMesh/NavMeshRenderingComponent.h"
|
|
|
|
#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
|
|
|
|
static const int32 ArbitraryMaxVoxelTileSize = 1024;
|
|
|
|
FNavMeshTileData::FNavData::~FNavData()
|
|
{
|
|
#if WITH_RECAST
|
|
dtFree(RawNavData);
|
|
#else
|
|
FMemory::Free(RawNavData);
|
|
#endif // WITH_RECAST
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
FNavMeshTileData::~FNavMeshTileData()
|
|
{
|
|
if (NavData.IsUnique() && NavData->RawNavData)
|
|
{
|
|
DEC_MEMORY_STAT_BY(STAT_Navigation_TileCacheMemory, DataSize);
|
|
}
|
|
}
|
|
|
|
uint8* FNavMeshTileData::Release()
|
|
{
|
|
uint8* RawData = nullptr;
|
|
|
|
if (NavData.IsValid() && NavData->RawNavData)
|
|
{
|
|
RawData = NavData->RawNavData;
|
|
NavData->RawNavData = nullptr;
|
|
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);
|
|
#else
|
|
uint8* UniqueRawData = (uint8*)FMemory::Malloc(sizeof(uint8)*DataSize);
|
|
#endif //WITH_RECAST
|
|
FMemory::Memcpy(UniqueRawData, NavData->RawNavData, DataSize);
|
|
NavData = MakeShareable(new FNavData(UniqueRawData));
|
|
}
|
|
}
|
|
|
|
float ARecastNavMesh::DrawDistanceSq = 0.0f;
|
|
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;
|
|
|
|
//@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();
|
|
}
|
|
}
|
|
|
|
#else // WITH_RECAST
|
|
|
|
#include "Detour/DetourNavMesh.h"
|
|
#include "Detour/DetourNavMeshQuery.h"
|
|
#include "NavMesh/PImplRecastNavMesh.h"
|
|
#include "NavMesh/RecastNavMeshGenerator.h"
|
|
|
|
//----------------------------------------------------------------------//
|
|
// FRecastDebugGeometry
|
|
//----------------------------------------------------------------------//
|
|
uint32 FRecastDebugGeometry::GetAllocatedSize() const
|
|
{
|
|
uint32 Size = sizeof(*this) + MeshVerts.GetAllocatedSize()
|
|
+ BuiltMeshIndices.GetAllocatedSize()
|
|
+ PolyEdges.GetAllocatedSize()
|
|
+ NavMeshEdges.GetAllocatedSize()
|
|
+ OffMeshLinks.GetAllocatedSize()
|
|
+ OffMeshSegments.GetAllocatedSize()
|
|
+ Clusters.GetAllocatedSize()
|
|
+ ClusterLinks.GetAllocatedSize();
|
|
|
|
for (int i = 0; i < RECAST_MAX_AREAS; ++i)
|
|
{
|
|
Size += AreaIndices[i].GetAllocatedSize();
|
|
}
|
|
|
|
for (int i = 0; i < Clusters.Num(); ++i)
|
|
{
|
|
Size += Clusters[i].MeshIndices.GetAllocatedSize();
|
|
}
|
|
|
|
return Size;
|
|
}
|
|
|
|
//----------------------------------------------------------------------//
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
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)
|
|
, bDrawNavMesh(true)
|
|
, DrawOffset(10.f)
|
|
, TilePoolSize(1024)
|
|
, MaxSimplificationError(1.3f) // from RecastDemo
|
|
, DefaultMaxSearchNodes(RECAST_MAX_SEARCH_NODES)
|
|
, DefaultMaxHierarchicalSearchNodes(RECAST_MAX_SEARCH_NODES)
|
|
, bPerformVoxelFiltering(true)
|
|
, bMarkLowHeightAreas(false)
|
|
, bFilterLowSpanSequences(false)
|
|
, bFilterLowSpanFromTileCache(false)
|
|
, bStoreEmptyTileLayers(false)
|
|
, bUseVirtualFilters(true)
|
|
, bAllowNavLinkAsPathEnd(false)
|
|
, 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;
|
|
}
|
|
}
|
|
|
|
ARecastNavMesh* ARecastNavMesh::SpawnInstance(UNavigationSystem* NavSys, const FNavDataConfig* AgentProps)
|
|
{
|
|
FActorSpawnParameters SpawnInfo;
|
|
SpawnInfo.OverrideLevel = NavSys->GetWorld()->PersistentLevel;
|
|
ARecastNavMesh* Instance = NavSys->GetWorld()->SpawnActor<ARecastNavMesh>( SpawnInfo );
|
|
|
|
if (Instance != NULL && AgentProps != NULL)
|
|
{
|
|
Instance->SetConfig(*AgentProps);
|
|
if (AgentProps->Name != NAME_None)
|
|
{
|
|
FString StrName = FString::Printf(TEXT("%s-%s"), *(Instance->GetFName().GetPlainNameString()), *(AgentProps->Name.ToString()));
|
|
// temporary solution to make sure we don't try to change name while there's already
|
|
// an object with this name
|
|
UObject* ExistingObject = StaticFindObject(/*Class=*/ NULL, Instance->GetOuter(), *StrName, true);
|
|
if (ExistingObject != NULL)
|
|
{
|
|
ExistingObject->Rename(NULL, NULL, REN_DontCreateRedirectors | REN_ForceGlobalUnique | REN_DoNotDirty | REN_NonTransactional);
|
|
}
|
|
|
|
// Set descriptive name
|
|
Instance->Rename(*StrName);
|
|
#if WITH_EDITOR
|
|
Instance->SetActorLabel(StrName);
|
|
#endif // WITH_EDITOR
|
|
}
|
|
}
|
|
|
|
return Instance;
|
|
}
|
|
|
|
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->bVisible && (NavMeshRenderComp->IsForcingUpdate() || UNavMeshRenderingComponent::IsNavigationShowFlagSet(GetWorld())))
|
|
{
|
|
RenderingComp->MarkRenderStateDirty();
|
|
}
|
|
#endif // UE_BUILD_SHIPPING
|
|
}
|
|
|
|
void ARecastNavMesh::CleanUp()
|
|
{
|
|
Super::CleanUp();
|
|
if (NavDataGenerator.IsValid())
|
|
{
|
|
NavDataGenerator->CancelBuild();
|
|
NavDataGenerator.Reset();
|
|
}
|
|
DestroyRecastPImpl();
|
|
}
|
|
|
|
void ARecastNavMesh::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
// @TODO tilesize validation. This is temporary and should get removed by 4.9
|
|
TileSizeUU = FMath::Clamp(TileSizeUU, CellSize, ArbitraryMaxVoxelTileSize * CellSize);
|
|
|
|
RecreateDefaultFilter();
|
|
UpdatePolyRefBitsPreview();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
UWorld* MyWorld = GetWorld();
|
|
if (MyWorld != nullptr
|
|
&& HasAnyFlags(RF_NeedLoad) // was loaded
|
|
&& FNavigationSystem::ShouldDiscardSubLevelNavData(*this))
|
|
{
|
|
// get rid of instances saved within levels that are streamed-in
|
|
if ((GEngine->IsSettingUpPlayWorld() == false) // this is a @HACK
|
|
&& (MyWorld->GetOutermost() != GetOutermost())
|
|
// If we are cooking, then let them all pass.
|
|
// They will be handled at load-time when running.
|
|
&& (IsRunningCommandlet() == false))
|
|
{
|
|
UE_LOG(LogNavigation, Log, TEXT("Discarding %s due to it not being part of PersistentLevel")
|
|
, *GetNameSafe(this));
|
|
|
|
// marking self for deletion
|
|
CleanUpAndMarkPendingKill();
|
|
}
|
|
}
|
|
|
|
Super::PostInitProperties();
|
|
|
|
TileSizeUU = FMath::Clamp(TileSizeUU, CellSize, ArbitraryMaxVoxelTileSize * CellSize);
|
|
|
|
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;
|
|
}
|
|
|
|
if (CellSize != DefOb->CellSize)
|
|
{
|
|
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), CellSize, DefOb->CellSize);
|
|
|
|
CellSize = DefOb->CellSize;
|
|
}
|
|
|
|
if (CellHeight != DefOb->CellHeight)
|
|
{
|
|
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), CellHeight, DefOb->CellHeight);
|
|
|
|
CellHeight = DefOb->CellHeight;
|
|
}
|
|
|
|
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 (AgentMaxStepHeight != DefOb->AgentMaxStepHeight)
|
|
{
|
|
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), AgentMaxStepHeight, DefOb->AgentMaxStepHeight);
|
|
|
|
AgentMaxStepHeight = DefOb->AgentMaxStepHeight;
|
|
}
|
|
}
|
|
|
|
UpdatePolyRefBitsPreview();
|
|
}
|
|
|
|
void ARecastNavMesh::RecreateDefaultFilter()
|
|
{
|
|
DefaultQueryFilter->SetFilterType<FRecastQueryFilter>();
|
|
DefaultQueryFilter->SetMaxSearchNodes(DefaultMaxSearchNodes);
|
|
|
|
FRecastQueryFilter* DetourFilter = static_cast<FRecastQueryFilter*>(DefaultQueryFilter->GetImplementation());
|
|
DetourFilter->SetIsVirtual(bUseVirtualFilters);
|
|
DetourFilter->setHeuristicScale(HeuristicScale);
|
|
|
|
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(AreaData.AreaID, DefArea->DefaultCost);
|
|
DetourFilter->SetFixedAreaEnteringCost(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(AreaID, DefArea->DefaultCost);
|
|
DefaultQueryFilter->SetFixedAreaEnteringCost(AreaID, DefArea->GetFixedAreaEnteringCost());
|
|
}
|
|
|
|
// update generator's cached data
|
|
FRecastNavMeshGenerator* MyGenerator = static_cast<FRecastNavMeshGenerator*>(GetGenerator());
|
|
if (MyGenerator)
|
|
{
|
|
MyGenerator->OnAreaAdded(NavAreaClass, AreaID);
|
|
}
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
TArray<FIntPoint>& ARecastNavMesh::GetActiveTiles()
|
|
{
|
|
FRecastNavMeshGenerator* MyGenerator = static_cast<FRecastNavMeshGenerator*>(GetGenerator());
|
|
check(MyGenerator);
|
|
return MyGenerator->ActiveTiles;
|
|
}
|
|
|
|
void ARecastNavMesh::RestrictBuildingToActiveTiles(bool InRestrictBuildingToActiveTiles)
|
|
{
|
|
FRecastNavMeshGenerator* MyGenerator = static_cast<FRecastNavMeshGenerator*>(GetGenerator());
|
|
if (MyGenerator)
|
|
{
|
|
MyGenerator->RestrictBuildingToActiveTiles(InRestrictBuildingToActiveTiles);
|
|
}
|
|
}
|
|
|
|
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();
|
|
{
|
|
#if WITH_EDITOR
|
|
FArchive::FScopeSetDebugSerializationFlags S(Ar, DSF_IgnoreDiff);
|
|
#endif
|
|
Ar << RecastNavMeshSizeBytes;
|
|
}
|
|
if (Ar.IsLoading())
|
|
{
|
|
if (NavMeshVersion < NAVMESHVER_MIN_COMPATIBLE)
|
|
{
|
|
// incompatible, just skip over this data. navmesh needs rebuilt.
|
|
Ar.Seek( RecastNavMeshSizePos + RecastNavMeshSizeBytes );
|
|
|
|
// Mark self for delete
|
|
CleanUpAndMarkPendingKill();
|
|
}
|
|
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 = CurPos - RecastNavMeshSizePos;
|
|
Ar.Seek(RecastNavMeshSizePos);
|
|
Ar << RecastNavMeshSizeBytes;
|
|
Ar.Seek(CurPos);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ARecastNavMesh::SetConfig(const FNavDataConfig& Src)
|
|
{
|
|
NavDataConfig = Src;
|
|
AgentMaxHeight = AgentHeight = Src.AgentHeight;
|
|
AgentRadius = Src.AgentRadius;
|
|
|
|
if (Src.HasStepHeightOverride())
|
|
{
|
|
AgentMaxStepHeight = Src.AgentStepHeight;
|
|
}
|
|
}
|
|
|
|
void ARecastNavMesh::FillConfig(FNavDataConfig& Dest)
|
|
{
|
|
Dest = NavDataConfig;
|
|
Dest.AgentHeight = AgentHeight;
|
|
Dest.AgentRadius = AgentRadius;
|
|
Dest.AgentStepHeight = AgentMaxStepHeight;
|
|
}
|
|
|
|
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;
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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
|
|
{
|
|
return !bFixedTilePoolSize;
|
|
}
|
|
|
|
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;
|
|
float 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 float RandomAngle = 2.f * PI * FMath::FRand();
|
|
const float U = FMath::FRand() + FMath::FRand();
|
|
const float 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 float RadiusSq = FMath::Square(Radius);
|
|
TArray<FNavPoly> Polys;
|
|
const FVector FallbackExtent(Radius, Radius, BIG_NUMBER);
|
|
const FBox Box(Origin - FallbackExtent, Origin + 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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();
|
|
float 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))
|
|
{
|
|
float 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::ProjectPointMulti(const FVector& Point, TArray<FNavLocation>& OutLocations, const FVector& Extent,
|
|
float MinZ, float 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, float& OutPathCost, FSharedConstNavQueryFilter QueryFilter, const UObject* QueryOwner) const
|
|
{
|
|
float 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, float& OutPathLength, FSharedConstNavQueryFilter QueryFilter, const UObject* QueryOwner) const
|
|
{
|
|
float 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, float& OutPathLength, float& OutPathCost, FSharedConstNavQueryFilter QueryFilter, const UObject* QueryOwner) const
|
|
{
|
|
ENavigationQueryResult::Type Result = ENavigationQueryResult::Invalid;
|
|
|
|
if (RecastNavMeshImpl)
|
|
{
|
|
if ((PathStart - PathEnd).IsNearlyZero() == true)
|
|
{
|
|
OutPathLength = 0.f;
|
|
Result = ENavigationQueryResult::Success;
|
|
}
|
|
else
|
|
{
|
|
TSharedRef<FNavMeshPath> Path = MakeShareable(new FNavMeshPath());
|
|
Path->SetWantsStringPulling(false);
|
|
Path->SetWantsPathCorridor(true);
|
|
|
|
Result = RecastNavMeshImpl->FindPath(PathStart, PathEnd, 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
float ARecastNavMesh::FindDistanceToWall(const FVector& StartLoc, FSharedConstNavQueryFilter Filter, float MaxDistance, FVector* OutClosestPointOnWall) const
|
|
{
|
|
if (HasValidNavmesh() == false)
|
|
{
|
|
return 0.f;
|
|
}
|
|
|
|
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.f;
|
|
}
|
|
|
|
const FVector NavExtent = GetModifiedQueryExtent(GetDefaultQueryExtent());
|
|
const float 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)
|
|
{
|
|
float TmpHitPos[3], TmpHitNormal[3];
|
|
float 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.f;
|
|
}
|
|
|
|
void ARecastNavMesh::UpdateCustomLink(const INavLinkCustomInterface* CustomLink)
|
|
{
|
|
TSubclassOf<UNavArea> AreaClass = CustomLink->GetLinkAreaClass();
|
|
const int32 UserId = CustomLink->GetLinkId();
|
|
const int32 AreaId = GetAreaID(AreaClass);
|
|
if (AreaId >= 0 && RecastNavMeshImpl)
|
|
{
|
|
UNavArea* DefArea = (UNavArea*)(AreaClass->GetDefaultObject());
|
|
const uint16 PolyFlags = DefArea->GetAreaFlags() | ARecastNavMesh::GetNavLinkFlag();
|
|
|
|
RecastNavMeshImpl->UpdateNavigationLinkArea(UserId, AreaId, PolyFlags);
|
|
RecastNavMeshImpl->UpdateSegmentLinkArea(UserId, AreaId, PolyFlags);
|
|
}
|
|
}
|
|
|
|
void ARecastNavMesh::UpdateNavigationLinkArea(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->UpdateNavigationLinkArea(UserId, AreaId, PolyFlags);
|
|
}
|
|
}
|
|
|
|
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, AreaId, PolyFlags);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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, 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, 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 = 34;// GetAreaID(NewArea);
|
|
ensure(NewAreaID != INDEX_NONE);
|
|
ensure(NewAreaID != OldAreaID);
|
|
|
|
// workaround for privacy issue in the recast API
|
|
dtNavMesh* DetourNavMesh = RecastNavMeshImpl->GetRecastMesh();
|
|
dtNavMesh const* const ConstDetourNavMesh = RecastNavMeshImpl->GetRecastMesh();
|
|
|
|
const FVector RcNavMeshOrigin = Unreal2RecastPoint(NavMeshOriginOffset);
|
|
const float RcTileSize = FMath::TruncToInt(TileSizeUU / CellSize);
|
|
const float TileSizeInWorldUnits = RcTileSize * CellSize;
|
|
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 = ((const dtNavMesh*)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(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;
|
|
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);
|
|
}
|
|
|
|
bool ARecastNavMesh::GetClusterBounds(NavNodeRef ClusterRef, FBox& OutBounds) const
|
|
{
|
|
return RecastNavMeshImpl && RecastNavMeshImpl->GetClusterBounds(ClusterRef, OutBounds);
|
|
}
|
|
|
|
bool ARecastNavMesh::GetPolysWithinPathingDistance(FVector const& StartLoc, const float PathingDistance, TArray<NavNodeRef>& FoundPolys,
|
|
FSharedConstNavQueryFilter Filter, const UObject* QueryOwner, FRecastDebugPathfindingData* DebugData) const
|
|
{
|
|
return RecastNavMeshImpl && RecastNavMeshImpl->GetPolysWithinPathingDistance(StartLoc, PathingDistance, GetRightFilterRef(Filter), QueryOwner, FoundPolys, DebugData);
|
|
}
|
|
|
|
void ARecastNavMesh::GetDebugGeometry(FRecastDebugGeometry& OutGeometry, int32 TileIndex) const
|
|
{
|
|
if (RecastNavMeshImpl)
|
|
{
|
|
RecastNavMeshImpl->GetDebugGeometry(OutGeometry, TileIndex);
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
void ARecastNavMesh::OnNavMeshTilesUpdated(const TArray<uint32>& ChangedTiles)
|
|
{
|
|
InvalidateAffectedPaths(ChangedTiles);
|
|
}
|
|
|
|
void ARecastNavMesh::InvalidateAffectedPaths(const TArray<uint32>& ChangedTiles)
|
|
{
|
|
const int32 PathsCount = ActivePaths.Num();
|
|
const int32 ChangedTilesCount = ChangedTiles.Num();
|
|
|
|
if (ChangedTilesCount == 0 || PathsCount == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FNavPathWeakPtr* WeakPathPtr = (ActivePaths.GetData() + PathsCount - 1);
|
|
for (int32 PathIndex = PathsCount - 1; PathIndex >= 0; --PathIndex, --WeakPathPtr)
|
|
{
|
|
FNavPathSharedPtr SharedPath = WeakPathPtr->Pin();
|
|
if (WeakPathPtr->IsValid() == false)
|
|
{
|
|
ActivePaths.RemoveAtSwap(PathIndex, 1, /*bAllowShrinking=*/false);
|
|
}
|
|
else
|
|
{
|
|
// iterate through all tile refs in FreshTilesCopy and
|
|
const FNavMeshPath* Path = (const FNavMeshPath*)(SharedPath.Get());
|
|
if (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 uint32 NodeTileIdx = RecastNavMeshImpl->GetTileIndexFromPolyRef(*PathPoly);
|
|
if (ChangedTiles.Contains(NodeTileIdx))
|
|
{
|
|
SharedPath->Invalidate();
|
|
ActivePaths.RemoveAtSwap(PathIndex, 1, /*bAllowShrinking=*/false);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
URecastNavMeshDataChunk* ARecastNavMesh::GetNavigationDataChunk(ULevel* InLevel) const
|
|
{
|
|
FName ThisName = GetFName();
|
|
int32 ChunkIndex = InLevel->NavDataChunks.IndexOfByPredicate([&](UNavigationDataChunk* Chunk)
|
|
{
|
|
return Chunk->NavigationDataName == ThisName;
|
|
});
|
|
|
|
URecastNavMeshDataChunk* RcNavDataChunk = nullptr;
|
|
if (ChunkIndex != INDEX_NONE)
|
|
{
|
|
RcNavDataChunk = Cast<URecastNavMeshDataChunk>(InLevel->NavDataChunks[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 (World != nullptr && World->IsPendingKill() == false)
|
|
{
|
|
#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);
|
|
}
|
|
|
|
NavDataChunk->GatherTiles(RecastNavMeshImpl, LevelTiles);
|
|
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();
|
|
const int32 headerSize = dtAlign4(sizeof(dtMeshHeader));
|
|
|
|
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 && Tile->header)
|
|
{
|
|
dtMeshHeader* const H = (dtMeshHeader*)(Tile->header);
|
|
const int32 vertsSize = dtAlign4(sizeof(float) * 3 * H->vertCount);
|
|
const int32 polysSize = dtAlign4(sizeof(dtPoly) * H->polyCount);
|
|
const int32 linksSize = dtAlign4(sizeof(dtLink) * H->maxLinkCount);
|
|
const int32 detailMeshesSize = dtAlign4(sizeof(dtPolyDetail) * H->detailMeshCount);
|
|
const int32 detailVertsSize = dtAlign4(sizeof(float) * 3 * H->detailVertCount);
|
|
const int32 detailTrisSize = dtAlign4(sizeof(unsigned char) * 4 * H->detailTriCount);
|
|
const int32 bvTreeSize = dtAlign4(sizeof(dtBVNode) * H->bvNodeCount);
|
|
const int32 offMeshConsSize = dtAlign4(sizeof(dtOffMeshConnection) * H->offMeshConCount);
|
|
const int32 offMeshSegsSize = dtAlign4(sizeof(dtOffMeshSegmentConnection) * H->offMeshSegConCount);
|
|
const int32 clusterSize = dtAlign4(sizeof(dtCluster) * H->clusterCount);
|
|
const int32 polyClustersSize = dtAlign4(sizeof(unsigned short) * H->detailMeshCount);
|
|
|
|
const int32 TileDataSize = headerSize + vertsSize + polysSize + linksSize +
|
|
detailMeshesSize + detailVertsSize + detailTrisSize +
|
|
bvTreeSize + offMeshConsSize + offMeshSegsSize +
|
|
clusterSize + polyClustersSize;
|
|
|
|
MemUsed += TileDataSize;
|
|
}
|
|
}
|
|
}
|
|
|
|
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::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)
|
|
{
|
|
TArray<uint32> AttachedIndices = NavDataChunk.AttachTiles(*RecastNavMeshImpl);
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ARecastNavMesh::DetachNavMeshDataChunk(URecastNavMeshDataChunk& NavDataChunk)
|
|
{
|
|
TArray<uint32> DetachedIndices = NavDataChunk.DetachTiles(*RecastNavMeshImpl);
|
|
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 float 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, *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, *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)
|
|
{
|
|
ENavigationQueryResult::Type Result = RecastNavMesh->RecastNavMeshImpl->TestClusterPath(Query.StartLocation, AdjustedEndLocation, NumVisitedNodes);
|
|
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, *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 float 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)
|
|
{
|
|
float 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<uint32>* CustomLinks) const
|
|
{
|
|
bool bResult = false;
|
|
if (RecastNavMeshImpl)
|
|
{
|
|
bResult = RecastNavMeshImpl->FindStraightPath(StartLoc, EndLoc, PathCorridor, PathPoints, CustomLinks);
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
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.QueryFilter.Get()), Query.Owner.Get(), Steps);
|
|
}
|
|
|
|
return NumSteps;
|
|
}
|
|
|
|
void ARecastNavMesh::UpdateNavVersion()
|
|
{
|
|
NavMeshVersion = NAVMESHVER_LATEST;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
|
|
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)
|
|
{
|
|
FName PropName = PropertyChangedEvent.Property->GetFName();
|
|
|
|
if (PropName == GET_MEMBER_NAME_CHECKED(ARecastNavMesh,AgentRadius) ||
|
|
PropName == GET_MEMBER_NAME_CHECKED(ARecastNavMesh,TileSizeUU) ||
|
|
PropName == GET_MEMBER_NAME_CHECKED(ARecastNavMesh,CellSize))
|
|
{
|
|
// rule of thumb, dimension tile shouldn't be less than 16 * AgentRadius
|
|
if (TileSizeUU < 16.f * AgentRadius)
|
|
{
|
|
TileSizeUU = FMath::Max(16.f * AgentRadius, RECAST_MIN_TILE_SIZE);
|
|
}
|
|
|
|
// tile's can't be too big, otherwise we'll crash while tryng to allocate
|
|
// memory during navmesh generation
|
|
TileSizeUU = FMath::Clamp(TileSizeUU, CellSize, ArbitraryMaxVoxelTileSize * CellSize);
|
|
|
|
// update config
|
|
FillConfig(NavDataConfig);
|
|
}
|
|
else if (PropName == NAME_TileNumberHardLimit)
|
|
{
|
|
TileNumberHardLimit = 1 << (FMath::CeilToInt(FMath::Log2(TileNumberHardLimit)));
|
|
UpdatePolyRefBitsPreview();
|
|
}
|
|
|
|
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
|
|
if (!HasAnyFlags(RF_ClassDefaultObject)
|
|
&& 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);
|
|
}
|
|
|
|
FRecastNavMeshGenerator* ARecastNavMesh::CreateGeneratorInstance()
|
|
{
|
|
return new FRecastNavMeshGenerator(*this);
|
|
}
|
|
|
|
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(Generator);
|
|
}
|
|
|
|
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
|
|
if (NavSys)
|
|
{
|
|
RestrictBuildingToActiveTiles(NavSys->IsActiveTilesGenerationEnabled());
|
|
}
|
|
}
|
|
}
|
|
|
|
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, float TestRadius) const
|
|
{
|
|
if (HasValidNavmesh() == false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const dtNavMesh* NavMesh = RecastNavMeshImpl->DetourNavMesh;
|
|
const dtNavMeshParams* NavParams = RecastNavMeshImpl->DetourNavMesh->getParams();
|
|
const float NavTileSize = CellSize * FMath::TruncToInt(TileSizeUU / CellSize);
|
|
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 = FMath::FloorToInt((RcBounds.Min.X - RcNavOrigin.X) / NavTileSize);
|
|
const int32 MaxTileX = FMath::CeilToInt((RcBounds.Max.X - RcNavOrigin.X) / NavTileSize);
|
|
const int32 MinTileY = FMath::FloorToInt((RcBounds.Min.Z - RcNavOrigin.Z) / NavTileSize);
|
|
const int32 MaxTileY = FMath::CeilToInt((RcBounds.Max.Z - RcNavOrigin.Z) / NavTileSize);
|
|
const FVector RcTileExtent2D(NavTileSize * 0.5f, 0.f, NavTileSize * 0.5f);
|
|
const float 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)
|
|
{
|
|
if (HasValidNavmesh() == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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 float TileDim = Config.tileSize * Config.cs;
|
|
const FVector TileCenterOffset(TileDim, TileDim, 0);
|
|
|
|
TArray<FIntPoint>& ActiveTiles = GetActiveTiles();
|
|
TArray<FIntPoint> OldActiveSet = ActiveTiles;
|
|
TArray<FIntPoint> TilesInMinDistance;
|
|
TArray<FIntPoint> TilesInMaxDistance;
|
|
TilesInMinDistance.Reserve(ActiveTiles.Num());
|
|
TilesInMaxDistance.Reserve(ActiveTiles.Num());
|
|
ActiveTiles.Reset();
|
|
|
|
//const int32 TileRadius = FMath::CeilToInt(Radius / TileDim);
|
|
static const float SqareRootOf2 = FMath::Sqrt(2.f);
|
|
|
|
for (const FNavigationInvokerRaw& Invoker : InvokerLocations)
|
|
{
|
|
const FVector InvokerRelativeLocation = (NavmeshOrigin - Invoker.Location);
|
|
const float TileCenterDistanceToRemoveSq = FMath::Square(TileDim * SqareRootOf2 / 2 + Invoker.RadiusMax);
|
|
const float TileCenterDistanceToAddSq = FMath::Square(TileDim * SqareRootOf2 / 2 + Invoker.RadiusMin);
|
|
|
|
const int32 MinTileX = FMath::FloorToInt((InvokerRelativeLocation.X - Invoker.RadiusMax) / TileDim);
|
|
const int32 MaxTileX = FMath::CeilToInt((InvokerRelativeLocation.X + Invoker.RadiusMax) / TileDim);
|
|
const int32 MinTileY = FMath::FloorToInt((InvokerRelativeLocation.Y - Invoker.RadiusMax) / TileDim);
|
|
const int32 MaxTileY = FMath::CeilToInt((InvokerRelativeLocation.Y + Invoker.RadiusMax) / TileDim);
|
|
|
|
for (int32 X = MinTileX; X <= MaxTileX; ++X)
|
|
{
|
|
for (int32 Y = MinTileY; Y <= MaxTileY; ++Y)
|
|
{
|
|
const float 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)
|
|
{
|
|
TilesInMinDistance.AddUnique(FIntPoint(X, Y));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ActiveTiles.Append(TilesInMinDistance);
|
|
|
|
TArray<FIntPoint> TilesToRemove;
|
|
TilesToRemove.Reserve(OldActiveSet.Num());
|
|
for (int32 Index = OldActiveSet.Num() - 1; Index >= 0; --Index)
|
|
{
|
|
if (TilesInMaxDistance.Find(OldActiveSet[Index]) == INDEX_NONE)
|
|
{
|
|
TilesToRemove.Add(OldActiveSet[Index]);
|
|
OldActiveSet.RemoveAtSwap(Index, 1, /*bAllowShrinking=*/false);
|
|
}
|
|
else
|
|
{
|
|
ActiveTiles.AddUnique(OldActiveSet[Index]);
|
|
}
|
|
}
|
|
|
|
TArray<FIntPoint> TilesToUpdate;
|
|
TilesToUpdate.Reserve(ActiveTiles.Num());
|
|
for (int32 Index = TilesInMinDistance.Num() - 1; Index >= 0; --Index)
|
|
{
|
|
// check if it's a new tile
|
|
if (OldActiveSet.Find(TilesInMinDistance[Index]) == INDEX_NONE)
|
|
{
|
|
TilesToUpdate.Add(TilesInMinDistance[Index]);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ARecastNavMesh::RebuildTile(const TArray<FIntPoint>& Tiles)
|
|
{
|
|
if (Tiles.Num() > 0)
|
|
{
|
|
FRecastNavMeshGenerator* MyGenerator = static_cast<FRecastNavMeshGenerator*>(GetGenerator());
|
|
if (MyGenerator)
|
|
{
|
|
MyGenerator->ReAddTiles(Tiles);
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------//
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32 ARecastNavMesh::GetLinkUserId(NavNodeRef LinkPolyID) const
|
|
{
|
|
return RecastNavMeshImpl ? RecastNavMeshImpl->GetLinkUserId(LinkPolyID) : 0;
|
|
}
|
|
|
|
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 = ReplaceAreaInTileBounds(Bounds, OldArea, NewArea, ReplaceLinks) > 0;
|
|
if (bReplaced)
|
|
{
|
|
RequestDrawingUpdate();
|
|
}
|
|
return bReplaced;
|
|
}
|