Files
UnrealEngine/Engine/Source/Runtime/NavigationSystem/Private/NavigationData.cpp
2025-05-18 13:04:45 +08:00

934 lines
27 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "NavigationData.h"
#include "AI/NavDataGenerator.h"
#include "AI/Navigation/NavAgentInterface.h"
#include "AI/Navigation/NavAreaBase.h"
#include "AI/Navigation/NavigationDirtyArea.h"
#include "AssetCompilingManager.h"
#include "Components/PrimitiveComponent.h"
#include "Engine/Engine.h"
#include "NavAreas/NavArea.h"
#include "NavFilters/NavigationQueryFilter.h"
#include "NavigationSystem.h"
#include "VisualLogger/VisualLogger.h"
#include "Misc/ScopeLock.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(NavigationData)
// set to NAVMESHVER_LANDSCAPE_HEIGHT at the moment of refactoring navigation
// code out of the engine module. No point in using RecastNavMesh versioning
// for NavigationData
#define NAVDATAVER_LATEST 13
namespace UE::Navigation::Private
{
static int32 bDestroyNavDataInCleanUpAndMarkPendingKill = 1;
static FAutoConsoleVariableRef CVarDestroyNavDataInCleanUpAndMarkPendingKill(
TEXT("ai.DestroyNavDataInCleanUpAndMarkPendingKill"),
bDestroyNavDataInCleanUpAndMarkPendingKill,
TEXT("If set to 1 NavData will be destroyed in CleanUpAndMarkPendingKill rather than being marked as garbage.\n"),
ECVF_Default);
// This prevents accumulating SuspendedDirtyAreas indefinitely and eventually running out of memory. That could happen if SetRebuildSuspended
// is enabled for long periods of time. The limit cannot be too low because moving complex geometry made of hundreds or thousands of Actors
// can quickly create several thousands of DirtyAreas.
// Careful: Reaching this limit causes a Rebuild All for this navigation data (if it ever resumes generation).
static int32 MaxDirtyAreasCountWhileSuspended = 32768;
static FAutoConsoleVariableRef CVarMaxDirtyAreasCountWhileSuspended(
TEXT("ai.navigation.MaxDirtyAreasCountWhileSuspended"),
MaxDirtyAreasCountWhileSuspended,
TEXT("If a navigation data accumulates too many dirty areas because it's been indefinitely suspended, we'll stop accumulating them and rebuild the entire navigation data if it ever resumes building.\n"),
ECVF_Default);
}
//----------------------------------------------------------------------//
// FPathFindingQuery
//----------------------------------------------------------------------//
FPathFindingQuery::FPathFindingQuery(const UObject* InOwner, const ANavigationData& InNavData, const FVector& Start, const FVector& End, FSharedConstNavQueryFilter SourceQueryFilter, FNavPathSharedPtr InPathInstanceToFill, const FVector::FReal CostLimit, const bool bInRequireNavigableEndLocation) :
FPathFindingQueryData(InOwner, Start, End, SourceQueryFilter, 0 /*InNavDataFlags*/, true /*bInAllowPartialPaths*/, CostLimit, bInRequireNavigableEndLocation),
NavData(&InNavData), PathInstanceToFill(InPathInstanceToFill), NavAgentProperties(InNavData.GetConfig())
{
if (!QueryFilter.IsValid() && NavData.IsValid())
{
QueryFilter = NavData->GetDefaultQueryFilter();
}
}
FPathFindingQuery::FPathFindingQuery(const INavAgentInterface& InNavAgent, const ANavigationData& InNavData, const FVector& Start, const FVector& End, FSharedConstNavQueryFilter SourceQueryFilter, FNavPathSharedPtr InPathInstanceToFill, const FVector::FReal CostLimit, const bool bInRequireNavigableEndLocation) :
FPathFindingQueryData(Cast<UObject>(&InNavAgent), Start, End, SourceQueryFilter, 0 /*InNavDataFlags*/, true /*bInAllowPartialPaths*/, CostLimit, bInRequireNavigableEndLocation),
NavData(&InNavData), PathInstanceToFill(InPathInstanceToFill), NavAgentProperties(InNavAgent.GetNavAgentPropertiesRef())
{
if (!QueryFilter.IsValid() && NavData.IsValid())
{
QueryFilter = NavData->GetDefaultQueryFilter();
}
}
FPathFindingQuery::FPathFindingQuery(FNavPathSharedRef PathToRecalculate, const ANavigationData* NavDataOverride) :
FPathFindingQueryData(PathToRecalculate->GetQueryData()),
NavData(NavDataOverride ? NavDataOverride : PathToRecalculate->GetNavigationDataUsed()), PathInstanceToFill(PathToRecalculate), NavAgentProperties(FNavAgentProperties::DefaultProperties)
{
if (PathToRecalculate->ShouldUpdateStartPointOnRepath() && (PathToRecalculate->GetSourceActor() != nullptr))
{
const FVector NewStartLocation = PathToRecalculate->GetPathFindingStartLocation();
if (FNavigationSystem::IsValidLocation(NewStartLocation))
{
StartLocation = NewStartLocation;
}
}
if (PathToRecalculate->ShouldUpdateEndPointOnRepath() && (PathToRecalculate->GetGoalActor() != nullptr))
{
const FVector NewEndLocation = PathToRecalculate->GetGoalLocation();
if (FNavigationSystem::IsValidLocation(NewEndLocation))
{
EndLocation = NewEndLocation;
}
}
if (NavData.IsValid())
{
if (!QueryFilter.IsValid())
{
QueryFilter = NavData->GetDefaultQueryFilter();
}
NavAgentProperties = NavData->GetConfig();
}
}
FVector::FReal FPathFindingQuery::ComputeCostLimitFromHeuristic(const FVector& StartPos, const FVector& EndPos, const FVector::FReal HeuristicScale, const FVector::FReal CostLimitFactor, const FVector::FReal MinimumCostLimit)
{
if (CostLimitFactor == FLT_MAX)
{
return FLT_MAX;
}
else
{
const FVector::FReal OriginalHeuristicEstimate = HeuristicScale * FVector::Dist(StartPos, EndPos);
return FMath::Clamp(CostLimitFactor * OriginalHeuristicEstimate, MinimumCostLimit, TNumericLimits<FVector::FReal>::Max());
}
}
//----------------------------------------------------------------------//
// FAsyncPathFindingQuery
//----------------------------------------------------------------------//
uint32 FAsyncPathFindingQuery::LastPathFindingUniqueID = INVALID_NAVQUERYID;
FAsyncPathFindingQuery::FAsyncPathFindingQuery(const UObject* InOwner, const ANavigationData& InNavData, const FVector& Start, const FVector& End, const FNavPathQueryDelegate& Delegate, FSharedConstNavQueryFilter SourceQueryFilter, const FVector::FReal CostLimit)
: FPathFindingQuery(InOwner, InNavData, Start, End, SourceQueryFilter)
, QueryID(GetUniqueID())
, OnDoneDelegate(Delegate)
, Mode(EPathFindingMode::Regular)
{
}
FAsyncPathFindingQuery::FAsyncPathFindingQuery(const FPathFindingQuery& Query, const FNavPathQueryDelegate& Delegate, const EPathFindingMode::Type QueryMode)
: FPathFindingQuery(Query)
, QueryID(GetUniqueID())
, OnDoneDelegate(Delegate)
, Mode(QueryMode)
{
}
//----------------------------------------------------------------------//
// FSupportedAreaData
//----------------------------------------------------------------------//
FSupportedAreaData::FSupportedAreaData(TSubclassOf<UNavArea> NavAreaClass, int32 InAreaID)
: AreaID(InAreaID), AreaClass(NavAreaClass)
{
if (AreaClass != NULL)
{
AreaClassName = AreaClass->GetName();
}
else
{
AreaClassName = TEXT("Invalid");
}
}
//----------------------------------------------------------------------//
// ANavigationData
//----------------------------------------------------------------------//
ANavigationData::ANavigationData(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, bEnableDrawing(false)
, bForceRebuildOnLoad(false)
, bAutoDestroyWhenNoNavigation(false)
, bCanBeMainNavData(true)
, bCanSpawnOnRebuild(true)
, RuntimeGeneration(ERuntimeGenerationType::LegacyGeneration) //TODO: set to a valid value once bRebuildAtRuntime_DEPRECATED is removed
, DataVersion(NAVDATAVER_LATEST)
, FindPathImplementation(NULL)
, FindHierarchicalPathImplementation(NULL)
, bRegistered(false)
, bRebuildingSuspended(false)
#if WITH_EDITORONLY_DATA
, bIsBuildingOnLoad(false)
#endif
, NavDataUniqueID(GetNextUniqueID())
{
PrimaryActorTick.bCanEverTick = true;
bNetLoadOnClient = false;
SetCanBeDamaged(false);
DefaultQueryFilter = MakeShareable(new FNavigationQueryFilter());
ObservedPathsTickInterval = 0.5;
// by giving NavigationData a root component we can detect changes to
// actor's location and react to it (see ARecastNavMesh::PostRegisterAllComponents)
USceneComponent* SceneComponent = CreateDefaultSubobject<USceneComponent>(TEXT("SceneComp"));
RootComponent = SceneComponent;
RootComponent->Mobility = EComponentMobility::Static;
#if WITH_EDITORONLY_DATA
bIsSpatiallyLoaded = false;
#endif
}
uint16 ANavigationData::GetNextUniqueID()
{
static FThreadSafeCounter StaticID(INVALID_NAVDATA);
return IntCastChecked<uint16>(StaticID.Increment());
}
void ANavigationData::PostInitProperties()
{
Super::PostInitProperties();
if (!IsValidChecked(this))
{
return;
}
if (HasAnyFlags(RF_ClassDefaultObject))
{
if (RuntimeGeneration == ERuntimeGenerationType::LegacyGeneration)
{
RuntimeGeneration = bRebuildAtRuntime_DEPRECATED ? ERuntimeGenerationType::Dynamic : ERuntimeGenerationType::Static;
}
}
else
{
bNetLoadOnClient = FNavigationSystem::ShouldLoadNavigationOnClient(*this);
RequestRegistration();
#if UE_ENABLE_DEBUG_DRAWING
RenderingComp = ConstructRenderingComponent();
#endif // UE_ENABLE_DEBUG_DRAWING
}
}
void ANavigationData::PostInitializeComponents()
{
Super::PostInitializeComponents();
UWorld* MyWorld = GetWorld();
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(MyWorld);
if (bAutoDestroyWhenNoNavigation && (MyWorld == nullptr ||
(MyWorld->GetNetMode() != NM_Client && NavSys == nullptr) ||
(MyWorld->GetNetMode() == NM_Client && !bNetLoadOnClient)))
{
UE_VLOG_UELOG(this, LogNavigation, Log, TEXT("Marking %s as PendingKill due to %s"), *GetName()
, !MyWorld ? TEXT("No World") : (MyWorld->GetNetMode() == NM_Client ? TEXT("not creating navigation on clients") : TEXT("missing navigation system")));
CleanUpAndMarkPendingKill();
}
}
void ANavigationData::PostLoad()
{
Super::PostLoad();
InstantiateAndRegisterRenderingComponent();
bNetLoadOnClient = FNavigationSystem::ShouldLoadNavigationOnClient(*this);
RequestRegistration();
}
void ANavigationData::RequestRegistration()
{
if (IsRegistered() == false
&& HasAnyFlags(RF_ClassDefaultObject) == false)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
if (NavSys)
{
NavSys->RequestRegistrationDeferred(*this);
}
}
}
void ANavigationData::TickActor(float DeltaTime, enum ELevelTick TickType, FActorTickFunction& ThisTickFunction)
{
Super::TickActor(DeltaTime, TickType, ThisTickFunction);
PurgeUnusedPaths();
INC_DWORD_STAT_BY(STAT_Navigation_ObservedPathsCount, ObservedPaths.Num());
if (NextObservedPathsTickInSeconds >= 0.f)
{
NextObservedPathsTickInSeconds -= DeltaTime;
if (NextObservedPathsTickInSeconds <= 0.f)
{
RepathRequests.Reserve(ObservedPaths.Num());
for (int32 PathIndex = ObservedPaths.Num() - 1; PathIndex >= 0; --PathIndex)
{
if (ObservedPaths[PathIndex].IsValid())
{
FNavPathSharedPtr SharedPath = ObservedPaths[PathIndex].Pin();
FNavigationPath* Path = SharedPath.Get();
EPathObservationResult::Type Result = Path->TickPathObservation();
switch (Result)
{
case EPathObservationResult::NoLongerObserving:
ObservedPaths.RemoveAtSwap(PathIndex, EAllowShrinking::No);
break;
case EPathObservationResult::NoChange:
// do nothing
break;
case EPathObservationResult::RequestRepath:
RepathRequests.Add(FNavPathRecalculationRequest(SharedPath, ENavPathUpdateType::GoalMoved));
break;
default:
check(false && "unhandled EPathObservationResult::Type in ANavigationData::TickActor");
break;
}
}
else
{
ObservedPaths.RemoveAtSwap(PathIndex, EAllowShrinking::No);
}
}
if (ObservedPaths.Num() > 0)
{
NextObservedPathsTickInSeconds = ObservedPathsTickInterval;
}
}
}
if (RepathRequests.Num() > 0)
{
double TimeStamp = GetWorldTimeStamp();
const UWorld* World = GetWorld();
// @todo batch-process it!
const int32 MaxProcessedRequests = 1000;
// make a copy of path requests and reset (remove up to MaxProcessedRequests) from navdata's array
// this allows storing new requests in the middle of loop (e.g. used by meta path corrections)
TArray<FNavPathRecalculationRequest> WorkQueue(RepathRequests);
if (WorkQueue.Num() > MaxProcessedRequests)
{
UE_VLOG(this, LogNavigation, Error, TEXT("Too many repath requests! (%d/%d)"), WorkQueue.Num(), MaxProcessedRequests);
WorkQueue.RemoveAt(MaxProcessedRequests, WorkQueue.Num() - MaxProcessedRequests);
RepathRequests.RemoveAt(0, MaxProcessedRequests);
}
else
{
RepathRequests.Reset();
}
for (int32 Idx = 0; Idx < WorkQueue.Num(); Idx++)
{
FNavPathRecalculationRequest& RecalcRequest = WorkQueue[Idx];
// check if it can be updated right now
FNavPathSharedPtr PinnedPath = RecalcRequest.Path.Pin();
if (PinnedPath.IsValid() == false)
{
continue;
}
const UObject* PathQuerier = PinnedPath->GetQuerier();
const INavAgentInterface* PathNavAgent = Cast<const INavAgentInterface>(PathQuerier);
if (PathNavAgent && PathNavAgent->ShouldPostponePathUpdates())
{
RepathRequests.Add(RecalcRequest);
continue;
}
FPathFindingQuery Query(PinnedPath.ToSharedRef());
const FPathFindingResult Result = FindPath(Query.NavAgentProperties, Query.SetPathInstanceToUpdate(PinnedPath));
// update time stamp to give observers any means of telling if it has changed
PinnedPath->SetTimeStamp(TimeStamp);
// partial paths are still valid and can change to full path when moving goal gets back on navmesh
if (Result.IsSuccessful() || Result.IsPartial())
{
PinnedPath->UpdateLastRepathGoalLocation();
PinnedPath->DoneUpdating(RecalcRequest.Reason);
if (RecalcRequest.Reason == ENavPathUpdateType::NavigationChanged)
{
RegisterActivePath(PinnedPath);
}
}
else
{
PinnedPath->RePathFailed();
}
}
}
}
#if WITH_EDITOR
void ANavigationData::RerunConstructionScripts()
{
Super::RerunConstructionScripts();
InstantiateAndRegisterRenderingComponent();
}
#endif
void ANavigationData::OnRegistered()
{
InstantiateAndRegisterRenderingComponent();
bRegistered = true;
ConditionalConstructGenerator();
}
void ANavigationData::OnUnregistered()
{
bRegistered = false;
}
void ANavigationData::InstantiateAndRegisterRenderingComponent()
{
#if UE_ENABLE_DEBUG_DRAWING
PRAGMA_DISABLE_DEPRECATION_WARNINGS
if (IsThisNotNull(this, "ANavigationData::InstantiateAndRegisterRenderingComponent") && IsValidChecked(this) && !IsValid(RenderingComp))
PRAGMA_ENABLE_DEPRECATION_WARNINGS
{
const bool bRootIsRenderComp = (RenderingComp == RootComponent);
if (RenderingComp)
{
// rename the old rendering component out of the way
RenderingComp->Rename(NULL, NULL, REN_DontCreateRedirectors | REN_ForceGlobalUnique | REN_DoNotDirty | REN_NonTransactional);
}
RenderingComp = ConstructRenderingComponent();
UWorld* World = GetWorld();
if (World && World->bIsWorldInitialized && RenderingComp)
{
RenderingComp->RegisterComponent();
}
if (bRootIsRenderComp)
{
RootComponent = RenderingComp;
}
}
#endif // UE_ENABLE_DEBUG_DRAWING
}
void ANavigationData::PurgeUnusedPaths()
{
check(IsInGameThread());
// Paths can be registered from async pathfinding thread
// while unused paths are purged in main thread (actor tick)
UE::TScopeLock PathLock(ActivePathsLock);
const int32 Count = ActivePaths.Num();
for (int32 PathIndex = Count - 1; PathIndex >= 0; --PathIndex)
{
FNavPathWeakPtr* WeakPathPtr = &ActivePaths[PathIndex];
if (WeakPathPtr->IsValid() == false)
{
ActivePaths.RemoveAtSwap(PathIndex, EAllowShrinking::No);
}
}
}
void ANavigationData::RegisterActivePath(FNavPathSharedPtr SharedPath)
{
// Paths can be registered from main thread and async pathfinding thread
UE::TScopeLock PathLock(ActivePathsLock);
ActivePaths.Add(SharedPath);
}
#if WITH_EDITOR
void ANavigationData::PostEditUndo()
{
// make sure that rendering component is not pending kill before trying to register all components
InstantiateAndRegisterRenderingComponent();
Super::PostEditUndo();
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
if (NavSys)
{
if (IsPendingKillPending())
{
NavSys->UnregisterNavData(this);
}
else
{
NavSys->RequestRegistrationDeferred(*this);
}
}
}
#endif // WITH_EDITOR
bool ANavigationData::DoesSupportAgent(const FNavAgentProperties& AgentProps) const
{
return NavDataConfig.IsEquivalent(AgentProps);
}
void ANavigationData::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
UnregisterAndCleanUp();
Super::EndPlay(EndPlayReason);
}
void ANavigationData::Destroyed()
{
UnregisterAndCleanUp();
Super::Destroyed();
}
void ANavigationData::UnregisterAndCleanUp()
{
if (bRegistered)
{
bRegistered = false;
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
if (NavSys)
{
NavSys->UnregisterNavData(this);
}
}
// Cleanup is not tied to the registration state.
CleanUp();
}
void ANavigationData::CleanUp()
{
bRegistered = false;
}
void ANavigationData::ApplyWorldOffset(const FVector& InOffset, bool bWorldShift)
{
// do nothing, will be handled by NavigationSystem
}
void ANavigationData::CleanUpAndMarkPendingKill()
{
CleanUp();
/* Need to check if the world is valid since, when this is called from Serialize, the World won't be set and Destroy will do nothing,
* in which case it will crash when it tries to register with the NavSystem in UNavigationSystemV1::ProcessRegistrationCandidates. */
if (UE::Navigation::Private::bDestroyNavDataInCleanUpAndMarkPendingKill && IsValid(GetWorld()))
{
Destroy();
}
else
{
SetActorHiddenInGame(true);
if (UWorld* World = GetWorld())
{
// This part is not thread-safe and should only happen on the GT...
check(IsInGameThread());
World->RemoveNetworkActor(this);
}
MarkAsGarbage();
MarkComponentsAsGarbage();
}
}
bool ANavigationData::SupportsRuntimeGeneration() const
{
return false;
}
bool ANavigationData::SupportsStreaming() const
{
return false;
}
void ANavigationData::ConditionalConstructGenerator()
{
}
void ANavigationData::RebuildAll()
{
const double LoadTime = FPlatformTime::Seconds();
LoadBeforeGeneratorRebuild();
FAssetCompilingManager::Get().FinishAllCompilation();
UE_LOG(LogNavigationDataBuild, Display, TEXT(" %s load time: %.2fs"), ANSI_TO_TCHAR(__FUNCTION__), (FPlatformTime::Seconds() - LoadTime));
PostLoadPreRebuild();
ConditionalConstructGenerator(); //recreate generator
if (NavDataGenerator.IsValid())
{
#if WITH_EDITOR
if (!IsBuildingOnLoad())
{
MarkPackageDirty();
}
#endif // WITH_EDITOR
NavDataGenerator->RebuildAll();
}
}
void ANavigationData::EnsureBuildCompletion()
{
if (NavDataGenerator.IsValid())
{
NavDataGenerator->EnsureBuildCompletion();
}
}
void ANavigationData::CancelBuild()
{
if (NavDataGenerator.IsValid())
{
NavDataGenerator->CancelBuild();
}
}
void ANavigationData::OnNavigationBoundsChanged()
{
// Create generator if it wasn't yet
if (NavDataGenerator.Get() == nullptr)
{
ConditionalConstructGenerator();
}
if (NavDataGenerator.IsValid())
{
NavDataGenerator->OnNavigationBoundsChanged();
}
}
void ANavigationData::TickAsyncBuild(float DeltaSeconds)
{
if (NavDataGenerator.IsValid())
{
NavDataGenerator->TickAsyncBuild(DeltaSeconds);
}
}
void ANavigationData::RebuildDirtyAreas(const TArray<FNavigationDirtyArea>& DirtyAreas)
{
if (bRebuildingSuspended)
{
if (bReachedSuspendedAreasMaxCount)
{
return; // Already reached max count
}
const int32 TotalDirtyAreasCount = SuspendedDirtyAreas.Num() + DirtyAreas.Num();
if (TotalDirtyAreasCount > UE::Navigation::Private::MaxDirtyAreasCountWhileSuspended)
{
// Rebuilding has been suspended for too long and we're accumulating too many dirty areas.
// Let's just stop accumulating them and remember that the whole navigation data might now be invalid.
UE_VLOG_UELOG(this, LogNavigation, Display, TEXT("%s reached ai.navigation.MaxDirtyAreasCountWhileSuspended (%d). Ignoring dirty areas until rebuilding is unsuspended."),
*GetName(),
UE::Navigation::Private::MaxDirtyAreasCountWhileSuspended);
bReachedSuspendedAreasMaxCount = true;
SuspendedDirtyAreas.Empty();
}
else
{
SuspendedDirtyAreas.Append(DirtyAreas);
}
}
else
{
if (NavDataGenerator.IsValid())
{
NavDataGenerator->RebuildDirtyAreas(DirtyAreas);
}
}
}
void ANavigationData::SetRebuildingSuspended(const bool bNewSuspend)
{
if (bRebuildingSuspended != bNewSuspend)
{
bRebuildingSuspended = bNewSuspend;
UE_VLOG_UELOG(this, LogNavigation, Verbose, TEXT("%s nav generation %s")
, *GetName(), bNewSuspend ? TEXT("SUSPENDED") : TEXT("ACTIVE"));
if (bNewSuspend == false)
{
if (bReachedSuspendedAreasMaxCount)
{
UE_VLOG_UELOG(this, LogNavigation, Warning, TEXT("%s resuming nav generation after MaxDirtyAreasCountWhileSuspended was reached. This will rebuild the entire navigation data."), *GetName());
// We stopped tracking dirty areas so we'll need to rebuild all.
bReachedSuspendedAreasMaxCount = false;
RebuildAll();
}
else if (SuspendedDirtyAreas.Num() > 0)
{
UE_VLOG_UELOG(this, LogNavigation, Verbose, TEXT("%s resuming nav generation with %d dirty areas")
, *GetName(), SuspendedDirtyAreas.Num());
// resuming the generation so we need to utilize SuspendedDirtyAreas and clean it
RebuildDirtyAreas(SuspendedDirtyAreas);
SuspendedDirtyAreas.Empty();
}
}
}
}
TArray<FBox> ANavigationData::GetNavigableBounds() const
{
TArray<FBox> Result;
const UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<const UNavigationSystemV1>(GetWorld());
if (NavSys)
{
// @note this has been switched over from calling GetNavigationBounds
// to get navigable bounds relevant to this one nav data instance
// This implements the original intension of the function
NavSys->GetNavigationBoundsForNavData(*this, Result);
}
return Result;
}
TArray<FBox> ANavigationData::GetNavigableBoundsInLevel(ULevel* InLevel) const
{
TArray<FBox> Result;
const UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<const UNavigationSystemV1>(GetWorld());
if (NavSys)
{
NavSys->GetNavigationBoundsForNavData(*this, Result, InLevel);
}
return Result;
}
void ANavigationData::DrawDebugPath(FNavigationPath* Path, const FColor PathColor, UCanvas* Canvas, const bool bPersistent, const float LifeTime, const uint32 NextPathPointIndex) const
{
Path->DebugDraw(this, PathColor, Canvas, bPersistent, LifeTime, NextPathPointIndex);
}
double ANavigationData::GetWorldTimeStamp() const
{
const UWorld* World = GetWorld();
return World ? World->GetTimeSeconds() : 0.f;
}
void ANavigationData::OnNavAreaAdded(const UClass* NavAreaClass, int32 AgentIndex)
{
// check if area can be added
const UNavArea* DefArea = GetDefault<UNavArea>(const_cast<UClass*>(NavAreaClass));
const bool bIsMetaArea = DefArea != nullptr && DefArea->IsMetaArea();
if (!DefArea || bIsMetaArea || !DefArea->IsSupportingAgent(AgentIndex))
{
UE_VLOG_UELOG(this, LogNavigation, Verbose, TEXT("%s discarded area %s (valid:%s meta:%s validAgent[%d]:%s)"),
*GetName(), *GetNameSafe(NavAreaClass),
DefArea ? TEXT("yes") : TEXT("NO"),
bIsMetaArea ? TEXT("YES") : TEXT("no"),
AgentIndex, (DefArea && DefArea->IsSupportingAgent(AgentIndex)) ? TEXT("yes") : TEXT("NO"));
return;
}
// check if area is already on supported list
FString AreaClassName = NavAreaClass->GetName();
for (int32 i = 0; i < SupportedAreas.Num(); i++)
{
if (SupportedAreas[i].AreaClassName == AreaClassName)
{
SupportedAreas[i].AreaClass = NavAreaClass;
AreaClassToIdMap.Add(NavAreaClass, SupportedAreas[i].AreaID);
UE_VLOG_UELOG(this, LogNavigation, Verbose, TEXT("%s: updated area %s with ID %d"), *GetFullName(), *AreaClassName, SupportedAreas[i].AreaID);
return;
}
}
// try adding new one
const int32 MaxSupported = GetMaxSupportedAreas();
if (SupportedAreas.Num() >= MaxSupported)
{
UE_VLOG_UELOG(this, LogNavigation, Error, TEXT("%s can't support area %s - limit reached! (%d)"), *GetName(), *AreaClassName, MaxSupported);
return;
}
FSupportedAreaData NewAgentData;
NewAgentData.AreaClass = NavAreaClass;
NewAgentData.AreaClassName = AreaClassName;
NewAgentData.AreaID = GetNewAreaID(NavAreaClass);
SupportedAreas.Add(NewAgentData);
AreaClassToIdMap.Add(NavAreaClass, NewAgentData.AreaID);
UE_VLOG_UELOG(this, LogNavigation, Verbose, TEXT("%s registered area %s with ID %d"), *GetName(), *AreaClassName, NewAgentData.AreaID);
}
void ANavigationData::OnNavAreaEvent(const UClass* NavAreaClass, ENavAreaEvent::Type Event)
{
if (Event == ENavAreaEvent::Registered)
{
const UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<const UNavigationSystemV1>(GetWorld());
const int32 AgentIndex = NavSys->GetSupportedAgentIndex(this);
OnNavAreaAdded(NavAreaClass, AgentIndex);
}
else // Unregistered
{
OnNavAreaRemoved(NavAreaClass);
}
OnNavAreaChanged();
}
void ANavigationData::OnNavAreaRemoved(const UClass* NavAreaClass)
{
for (int32 i = 0; i < SupportedAreas.Num(); i++)
{
if (SupportedAreas[i].AreaClass == NavAreaClass)
{
AreaClassToIdMap.Remove(NavAreaClass);
SupportedAreas.RemoveAt(i);
break;
}
}
}
void ANavigationData::OnNavAreaChanged()
{
// empty in base class
}
void ANavigationData::ProcessNavAreas(const TSet<const UClass*>& AreaClasses, int32 AgentIndex)
{
for (const UClass* AreaClass : AreaClasses)
{
OnNavAreaAdded(AreaClass, AgentIndex);
}
OnNavAreaChanged();
}
int32 ANavigationData::GetNewAreaID(const UClass* AreaClass) const
{
int TestId = 0;
while (TestId < SupportedAreas.Num())
{
const bool bIsTaken = IsAreaAssigned(TestId);
if (bIsTaken)
{
TestId++;
}
else
{
break;
}
}
return TestId;
}
const UClass* ANavigationData::GetAreaClass(int32 AreaID) const
{
for (int32 i = 0; i < SupportedAreas.Num(); i++)
{
if (SupportedAreas[i].AreaID == AreaID)
{
return SupportedAreas[i].AreaClass;
}
}
return NULL;
}
bool ANavigationData::IsAreaAssigned(int32 AreaID) const
{
for (int32 i = 0; i < SupportedAreas.Num(); i++)
{
if (SupportedAreas[i].AreaID == AreaID)
{
return true;
}
}
return false;
}
int32 ANavigationData::GetAreaID(const UClass* AreaClass) const
{
const int32* PtrId = AreaClassToIdMap.Find(AreaClass);
return PtrId ? *PtrId : INDEX_NONE;
}
void ANavigationData::SetNavRenderingEnabled(bool bEnable)
{
if (bEnableDrawing != bEnable)
{
bEnableDrawing = bEnable;
MarkComponentsRenderStateDirty();
}
}
void ANavigationData::UpdateCustomLink(const INavLinkCustomInterface* CustomLink)
{
// no implementation for abstract class
}
FSharedConstNavQueryFilter ANavigationData::GetQueryFilter(TSubclassOf<UNavigationQueryFilter> FilterClass) const
{
return QueryFilters.FindRef(FilterClass);
}
void ANavigationData::StoreQueryFilter(TSubclassOf<UNavigationQueryFilter> FilterClass, FSharedConstNavQueryFilter NavFilter)
{
QueryFilters.Add(FilterClass, NavFilter);
}
void ANavigationData::RemoveQueryFilter(TSubclassOf<UNavigationQueryFilter> FilterClass)
{
QueryFilters.Remove(FilterClass);
}
uint32 ANavigationData::LogMemUsed() const
{
SIZE_T ActivePathsMemSize = 0;
{
// Paths can be registered from async pathfinding thread
// while logging is requested on main thread (console command)
UE::TScopeLock PathLock(ActivePathsLock);
ActivePathsMemSize = ActivePaths.GetAllocatedSize();
}
const uint32 MemUsed = IntCastChecked<uint32>(ActivePathsMemSize + SupportedAreas.GetAllocatedSize() +
QueryFilters.GetAllocatedSize() + AreaClassToIdMap.GetAllocatedSize());
UE_VLOG_UELOG(this, LogNavigation, Display, TEXT("%s: ANavigationData: %u\n self: %d"), *GetName(), MemUsed, sizeof(ANavigationData));
if (NavDataGenerator.IsValid())
{
NavDataGenerator->LogMemUsed();
}
return MemUsed;
}
void ANavigationData::SetConfig(const FNavDataConfig& Src)
{
SetNavAgentProperties(Src);
NavDataConfig = Src;
}