Files
UnrealEngineUWP/Engine/Source/Runtime/NavigationSystem/Private/NavigationSystem.cpp
Ben Zeigler e15c6d6e7d Fix AI module load order issue. The AIModule is implicitly loaded early in editor startup from DetailCustomizations, but if you break that dependency link the AI module does not get loaded until PreDefault, which is way after when the Pawn CDO gets initialized.
I was able to fix a NavigationSystemBase case because it only needed a soft ref, but the Pawn needs a hard ref to AIController so AIController must be loaded before Pawn's CDO gets initialized
The AISupportModule now has a hard reference to an AI module symbol, this causes that module to get loaded very early, but the StarutpModule happens at the same time it used to, fairly late in startup
#codereview mieszko.zielinski
#rb gil.gribb

[CL 6317743 by Ben Zeigler in Dev-Framework branch]
2019-05-06 12:16:22 -04:00

4619 lines
140 KiB
C++

// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#include "NavigationSystem.h"
#include "Misc/ScopeLock.h"
#include "Stats/StatsMisc.h"
#include "Modules/ModuleManager.h"
#include "AI/Navigation/NavAgentInterface.h"
#include "Engine/World.h"
#include "GameFramework/Controller.h"
#include "AI/Navigation/NavRelevantInterface.h"
#include "UObject/UObjectIterator.h"
#include "EngineUtils.h"
#include "Logging/MessageLog.h"
#include "ProfilingDebugging/CsvProfiler.h"
#include "NavAreas/NavArea.h"
#include "NavigationOctree.h"
#include "VisualLogger/VisualLogger.h"
#include "NavMesh/NavMeshBoundsVolume.h"
#include "NavigationInvokerComponent.h"
#include "AI/Navigation/NavigationDataChunk.h"
#include "Engine/Engine.h"
#include "UObject/Package.h"
#include "Components/PrimitiveComponent.h"
#include "UObject/UObjectThreadContext.h"
#if WITH_RECAST
#include "NavMesh/RecastNavMesh.h"
#include "NavMesh/RecastHelpers.h"
#include "NavMesh/RecastNavMeshGenerator.h"
#endif // WITH_RECAST
#if WITH_EDITOR
#include "EditorModeManager.h"
#include "EditorModes.h"
#include "Editor/GeometryMode/Public/GeometryEdMode.h"
#include "Editor/GeometryMode/Public/EditorGeometry.h"
#endif
#if WITH_HOT_RELOAD
#include "Misc/HotReloadInterface.h"
#endif
#include "NavAreas/NavArea_Null.h"
#include "NavAreas/NavArea_Obstacle.h"
#include "NavAreas/NavArea_Default.h"
#include "NavAreas/NavAreaMeta_SwitchByAgent.h"
#include "NavLinkCustomInterface.h"
#include "NavigationPath.h"
#include "AbstractNavData.h"
#include "CrowdManagerBase.h"
#include "AI/NavigationModifier.h"
static const uint32 INITIAL_ASYNC_QUERIES_SIZE = 32;
static const uint32 REGISTRATION_QUEUE_SIZE = 16; // and we'll not reallocate
#define LOCTEXT_NAMESPACE "Navigation"
DEFINE_LOG_CATEGORY_STATIC(LogNavOctree, Warning, All);
DECLARE_CYCLE_STAT(TEXT("Nav Tick: mark dirty"), STAT_Navigation_TickMarkDirty, STATGROUP_Navigation);
DECLARE_CYCLE_STAT(TEXT("Nav Tick: async build"), STAT_Navigation_TickAsyncBuild, STATGROUP_Navigation);
DECLARE_CYCLE_STAT(TEXT("Nav Tick: async pathfinding"), STAT_Navigation_TickAsyncPathfinding, STATGROUP_Navigation);
DECLARE_CYCLE_STAT(TEXT("Debug NavOctree Time"), STAT_DebugNavOctree, STATGROUP_Navigation);
//----------------------------------------------------------------------//
// Stats
//----------------------------------------------------------------------//
DEFINE_STAT(STAT_Navigation_QueriesTimeSync);
DEFINE_STAT(STAT_Navigation_RequestingAsyncPathfinding);
DEFINE_STAT(STAT_Navigation_PathfindingSync);
DEFINE_STAT(STAT_Navigation_PathfindingAsync);
DEFINE_STAT(STAT_Navigation_TileNavAreaSorting);
DEFINE_STAT(STAT_Navigation_TileGeometryExportToObjAsync);
DEFINE_STAT(STAT_Navigation_TileVoxelFilteringAsync);
DEFINE_STAT(STAT_Navigation_TileBuildAsync);
DEFINE_STAT(STAT_Navigation_TileBuildPreparationSync);
DEFINE_STAT(STAT_Navigation_BSPExportSync);
DEFINE_STAT(STAT_Navigation_GatheringNavigationModifiersSync);
DEFINE_STAT(STAT_Navigation_ActorsGeometryExportSync);
DEFINE_STAT(STAT_Navigation_ProcessingActorsForNavMeshBuilding);
DEFINE_STAT(STAT_Navigation_AdjustingNavLinks);
DEFINE_STAT(STAT_Navigation_RegisterNavOctreeElement);
DEFINE_STAT(STAT_Navigation_UnregisterNavOctreeElement);
DEFINE_STAT(STAT_Navigation_AddingActorsToNavOctree);
DEFINE_STAT(STAT_Navigation_RecastAddGeneratedTiles);
DEFINE_STAT(STAT_Navigation_RecastTick);
DEFINE_STAT(STAT_Navigation_RecastPathfinding);
DEFINE_STAT(STAT_Navigation_RecastTestPath);
DEFINE_STAT(STAT_Navigation_RecastBuildCompressedLayers);
DEFINE_STAT(STAT_Navigation_RecastCreateHeightField);
DEFINE_STAT(STAT_Navigation_RecastRasterizeTriangles);
DEFINE_STAT(STAT_Navigation_RecastVoxelFilter);
DEFINE_STAT(STAT_Navigation_RecastFilter);
DEFINE_STAT(STAT_Navigation_RecastBuildCompactHeightField);
DEFINE_STAT(STAT_Navigation_RecastErodeWalkable);
DEFINE_STAT(STAT_Navigation_RecastBuildLayers);
DEFINE_STAT(STAT_Navigation_RecastBuildTileCache);
DEFINE_STAT(STAT_Navigation_RecastBuildPolyMesh);
DEFINE_STAT(STAT_Navigation_RecastBuildPolyDetail);
DEFINE_STAT(STAT_Navigation_RecastGatherOffMeshData);
DEFINE_STAT(STAT_Navigation_RecastCreateNavMeshData);
DEFINE_STAT(STAT_Navigation_RecastMarkAreas);
DEFINE_STAT(STAT_Navigation_RecastBuildContours);
DEFINE_STAT(STAT_Navigation_RecastBuildNavigation);
DEFINE_STAT(STAT_Navigation_RecastBuildRegions);
DEFINE_STAT(STAT_Navigation_UpdateNavOctree);
DEFINE_STAT(STAT_Navigation_CollisionTreeMemory);
DEFINE_STAT(STAT_Navigation_NavDataMemory);
DEFINE_STAT(STAT_Navigation_TileCacheMemory);
DEFINE_STAT(STAT_Navigation_OutOfNodesPath);
DEFINE_STAT(STAT_Navigation_PartialPath);
DEFINE_STAT(STAT_Navigation_CumulativeBuildTime);
DEFINE_STAT(STAT_Navigation_BuildTime);
DEFINE_STAT(STAT_Navigation_OffsetFromCorners);
DEFINE_STAT(STAT_Navigation_PathVisibilityOptimisation);
DEFINE_STAT(STAT_Navigation_ObservedPathsCount);
DEFINE_STAT(STAT_Navigation_RecastMemory);
//----------------------------------------------------------------------//
// consts
//----------------------------------------------------------------------//
namespace FNavigationSystem
{
FORCEINLINE bool IsValidExtent(const FVector& Extent)
{
return Extent != INVALID_NAVEXTENT;
}
FCustomLinkOwnerInfo::FCustomLinkOwnerInfo(INavLinkCustomInterface* Link)
{
LinkInterface = Link;
LinkOwner = Link->GetLinkOwner();
}
bool ShouldLoadNavigationOnClient(ANavigationData& NavData)
{
const UWorld* World = NavData.GetWorld();
if (World && World->GetNavigationSystem())
{
const UNavigationSystemV1* NavSys = Cast<UNavigationSystemV1>(World->GetNavigationSystem());
return NavSys && NavSys->ShouldLoadNavigationOnClient(&NavData);
}
else if (GEngine->NavigationSystemClass && GEngine->NavigationSystemClass->IsChildOf<UNavigationSystemV1>())
{
const UNavigationSystemV1* NavSysCDO = GEngine->NavigationSystemClass->GetDefaultObject<const UNavigationSystemV1>();
return NavSysCDO && NavSysCDO->ShouldLoadNavigationOnClient(&NavData);
}
return false;
}
bool ShouldDiscardSubLevelNavData(ANavigationData& NavData)
{
const UWorld* World = NavData.GetWorld();
if (World && World->GetNavigationSystem())
{
const UNavigationSystemV1* NavSys = Cast<UNavigationSystemV1>(World->GetNavigationSystem());
if (NavSys)
{
return NavSys->ShouldDiscardSubLevelNavData(&NavData);
}
}
const UNavigationSystemV1* NavSysCDO = (*GEngine->NavigationSystemClass != nullptr)
? (GEngine->NavigationSystemClass->GetDefaultObject<const UNavigationSystemV1>())
: (const UNavigationSystemV1*)nullptr;
return NavSysCDO == nullptr || NavSysCDO->ShouldDiscardSubLevelNavData(&NavData);
}
void MakeAllComponentsNeverAffectNav(AActor& Actor)
{
const TSet<UActorComponent*> Components = Actor.GetComponents();
for (UActorComponent* ActorComp : Components)
{
ActorComp->SetCanEverAffectNavigation(false);
}
}
}
namespace NavigationDebugDrawing
{
const float PathLineThickness = 3.f;
const FVector PathOffset(0,0,15);
const FVector PathNodeBoxExtent(16.f);
}
//----------------------------------------------------------------------//
// FNavigationInvoker
//----------------------------------------------------------------------//
FNavigationInvoker::FNavigationInvoker()
: Actor(nullptr)
, GenerationRadius(0)
, RemovalRadius(0)
{
}
FNavigationInvoker::FNavigationInvoker(AActor& InActor, float InGenerationRadius, float InRemovalRadius)
: Actor(&InActor)
, GenerationRadius(InGenerationRadius)
, RemovalRadius(InRemovalRadius)
{
}
//----------------------------------------------------------------------//
// helpers
//----------------------------------------------------------------------//
namespace
{
#if ENABLE_VISUAL_LOG
void NavigationDataDump(const UObject* Object, const FName& CategoryName, const ELogVerbosity::Type Verbosity, const FBox& Box, const UWorld& World, FVisualLogEntry& CurrentEntry)
{
const ANavigationData* MainNavData = FNavigationSystem::GetCurrent<UNavigationSystemV1>(&World)->GetDefaultNavDataInstance();
const FNavDataGenerator* Generator = MainNavData ? MainNavData->GetGenerator() : nullptr;
if (Generator)
{
Generator->GrabDebugSnapshot(&CurrentEntry, FMath::IsNearlyZero(Box.GetVolume()) ? MainNavData->GetBounds().ExpandBy(FVector(20, 20, 20)) : Box, CategoryName, Verbosity);
}
}
#endif // ENABLE_VISUAL_LOG
}
//----------------------------------------------------------------------//
// UNavigationSystemV1
//----------------------------------------------------------------------//
bool UNavigationSystemV1::bNavigationAutoUpdateEnabled = true;
TMap<INavLinkCustomInterface*, FWeakObjectPtr> UNavigationSystemV1::PendingCustomLinkRegistration;
FCriticalSection UNavigationSystemV1::CustomLinkRegistrationSection;
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
FNavigationSystemExec UNavigationSystemV1::ExecHandler;
#endif // !UE_BUILD_SHIPPING
/** called after navigation influencing event takes place*/
UNavigationSystemV1::FOnNavigationDirty UNavigationSystemV1::NavigationDirtyEvent;
bool UNavigationSystemV1::bUpdateNavOctreeOnComponentChange = true;
bool UNavigationSystemV1::bStaticRuntimeNavigation = false;
bool UNavigationSystemV1::bIsPIEActive = false;
//----------------------------------------------------------------------//
// life cycle stuff
//----------------------------------------------------------------------//
UNavigationSystemV1::UNavigationSystemV1(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, bTickWhilePaused(false)
, bWholeWorldNavigable(false)
, bSkipAgentHeightCheckWhenPickingNavData(false)
, DirtyAreasUpdateFreq(60)
, OperationMode(FNavigationSystemRunMode::InvalidMode)
, NavOctree(NULL)
, NavBuildingLockFlags(0)
, InitialNavBuildingLockFlags(0)
, bNavOctreeLock(false)
, bInitialSetupHasBeenPerformed(false)
, bInitialLevelsAdded(false)
, bWorldInitDone(false)
, CurrentlyDrawnNavDataIndex(0)
, DirtyAreasUpdateTime(0)
{
#if WITH_EDITOR
NavUpdateLockFlags = 0;
#endif
struct FDelegatesInitializer
{
FDelegatesInitializer()
{
UNavigationSystemBase::UpdateActorDataDelegate().BindStatic(&UNavigationSystemV1::UpdateActorInNavOctree);
UNavigationSystemBase::UpdateComponentDataDelegate().BindStatic(&UNavigationSystemV1::UpdateComponentInNavOctree);
UNavigationSystemBase::UpdateComponentDataAfterMoveDelegate().BindLambda([](USceneComponent& Comp) { UNavigationSystemV1::UpdateNavOctreeAfterMove(&Comp); });
UNavigationSystemBase::OnActorBoundsChangedDelegate().BindLambda([](AActor& Actor) { UNavigationSystemV1::UpdateNavOctreeBounds(&Actor); });
UNavigationSystemBase::OnPostEditActorMoveDelegate().BindLambda([](AActor& Actor) {
// update actor and all its components in navigation system after finishing move
// USceneComponent::UpdateNavigationData works only in game world
UNavigationSystemV1::UpdateNavOctreeBounds(&Actor);
TArray<AActor*> ParentedActors;
Actor.GetAttachedActors(ParentedActors);
for (int32 Idx = 0; Idx < ParentedActors.Num(); Idx++)
{
UNavigationSystemV1::UpdateNavOctreeBounds(ParentedActors[Idx]);
}
// not doing manual update of all attached actors since UpdateActorAndComponentsInNavOctree should take care of it
UNavigationSystemV1::UpdateActorAndComponentsInNavOctree(Actor);
});
UNavigationSystemBase::OnComponentTransformChangedDelegate().BindLambda([](USceneComponent& Comp) {
if (UNavigationSystemV1::ShouldUpdateNavOctreeOnComponentChange())
{
UWorld* World = Comp.GetWorld();
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys != nullptr
&& (NavSys->ShouldAllowClientSideNavigation() || !World->IsNetMode(ENetMode::NM_Client)))
{
// use propagated component's transform update in editor OR server game with additional navsys check
UNavigationSystemV1::UpdateNavOctreeAfterMove(&Comp);
}
}
});
UNavigationSystemBase::OnActorRegisteredDelegate().BindLambda([](AActor& Actor) { UNavigationSystemV1::OnActorRegistered(&Actor); });
UNavigationSystemBase::OnActorUnregisteredDelegate().BindLambda([](AActor& Actor) { UNavigationSystemV1::OnActorUnregistered(&Actor); });
UNavigationSystemBase::OnComponentRegisteredDelegate().BindLambda([](UActorComponent& Comp) { UNavigationSystemV1::OnComponentRegistered(&Comp); });
UNavigationSystemBase::OnComponentUnregisteredDelegate().BindLambda([](UActorComponent& Comp) { UNavigationSystemV1::OnComponentUnregistered(&Comp); });
UNavigationSystemBase::RemoveActorDataDelegate().BindLambda([](AActor& Actor) { UNavigationSystemV1::ClearNavOctreeAll(&Actor); });
UNavigationSystemBase::HasComponentDataDelegate().BindLambda([](UActorComponent& Comp) {
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(Comp.GetWorld());
return (NavSys && (NavSys->GetObjectsNavOctreeId(Comp) || NavSys->HasPendingObjectNavOctreeId(&Comp)));
});
UNavigationSystemBase::GetDefaultSupportedAgentDelegate().BindStatic(&UNavigationSystemV1::GetDefaultSupportedAgent);
UNavigationSystemBase::UpdateActorAndComponentDataDelegate().BindStatic(&UNavigationSystemV1::UpdateActorAndComponentsInNavOctree);
UNavigationSystemBase::OnComponentBoundsChangedDelegate().BindLambda([](UActorComponent& Comp, const FBox& NewBounds, const FBox& DirtyArea) {
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(Comp.GetWorld());
if (NavSys)
{
NavSys->UpdateNavOctreeElementBounds(&Comp, NewBounds, DirtyArea);
}
});
//UNavigationSystemBase::GetNavDataForPropsDelegate();
UNavigationSystemBase::GetNavDataForActorDelegate().BindStatic(&UNavigationSystemV1::GetNavDataForActor);
#if WITH_RECAST
UNavigationSystemBase::GetDefaultNavDataClassDelegate().BindLambda([]() { return ARecastNavMesh::StaticClass(); });
#endif // WITH_RECAST
UNavigationSystemBase::VerifyNavigationRenderingComponentsDelegate().BindLambda([](UWorld& World, const bool bShow) {
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(&World);
if (NavSys)
{
NavSys->VerifyNavigationRenderingComponents(bShow);
}
});
UNavigationSystemBase::BuildDelegate().BindLambda([](UWorld& World) {
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(&World);
if (NavSys)
{
NavSys->Build();
}
});
#if WITH_EDITOR
UNavigationSystemBase::OnPIEStartDelegate().BindLambda([](UWorld& World) {
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(&World);
if (NavSys)
{
NavSys->OnPIEStart();
}
});
UNavigationSystemBase::OnPIEEndDelegate().BindLambda([](UWorld& World) {
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(&World);
if (NavSys)
{
NavSys->OnPIEEnd();
}
});
UNavigationSystemBase::UpdateLevelCollisionDelegate().BindLambda([](ULevel& Level) {
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(&Level);
if (NavSys)
{
NavSys->UpdateLevelCollision(&Level);
}
});
UNavigationSystemBase::SetNavigationAutoUpdateEnableDelegate().BindStatic(&UNavigationSystemV1::SetNavigationAutoUpdateEnabled);
/*.BindLambda([](const bool bNewEnable, UNavigationSystemBase* InNavigationSystem) {
})*/
UNavigationSystemBase::AddNavigationUpdateLockDelegate().BindLambda([](UWorld& World, uint8 Flags) {
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(&World);
if (NavSys)
{
NavSys->AddNavigationUpdateLock(Flags);
}
});
UNavigationSystemBase::RemoveNavigationUpdateLockDelegate().BindLambda([](UWorld& World, uint8 Flags) {
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(&World);
if (NavSys)
{
NavSys->RemoveNavigationUpdateLock(Flags);
}
});
#endif // WITH_EDITOR
#if ENABLE_VISUAL_LOG
FVisualLogger::NavigationDataDumpDelegate.AddStatic(&NavigationDataDump);
#endif // ENABLE_VISUAL_LOG
}
};
static FDelegatesInitializer DelegatesInitializer;
// Set to the ai module's crowd manager, this module may not exist at spawn time but then it will just fail to load
CrowdManagerClass = FSoftObjectPath(TEXT("/Script/AIModule.CrowdManager"));
// active tiles
NextInvokersUpdateTime = 0.f;
ActiveTilesUpdateInterval = 1.f;
bGenerateNavigationOnlyAroundNavigationInvokers = false;
DataGatheringMode = ENavDataGatheringModeConfig::Instant;
bCanAccumulateDirtyAreas = true;
bShouldDiscardSubLevelNavData = true;
#if !UE_BUILD_SHIPPING
bDirtyAreasReportedWhileAccumulationLocked = false;
#endif // !UE_BUILD_SHIPPING
if (HasAnyFlags(RF_ClassDefaultObject) == false)
{
// reserve some arbitrary size
AsyncPathFindingQueries.Reserve( INITIAL_ASYNC_QUERIES_SIZE );
NavDataRegistrationQueue.Reserve( REGISTRATION_QUEUE_SIZE );
FWorldDelegates::LevelAddedToWorld.AddUObject(this, &UNavigationSystemV1::OnLevelAddedToWorld);
FWorldDelegates::LevelRemovedFromWorld.AddUObject(this, &UNavigationSystemV1::OnLevelRemovedFromWorld);
#if !UE_BUILD_SHIPPING
FCoreDelegates::OnGetOnScreenMessages.AddUObject(this, &UNavigationSystemV1::GetOnScreenMessages);
#endif // !UE_BUILD_SHIPPING
}
else if (GetClass() == UNavigationSystemV1::StaticClass())
{
SetDefaultWalkableArea(UNavArea_Default::StaticClass());
SetDefaultObstacleArea(UNavArea_Obstacle::StaticClass());
const FTransform RecastToUnrealTransfrom(Recast2UnrealMatrix());
SetCoordTransform(ENavigationCoordSystem::Navigation, ENavigationCoordSystem::Unreal, RecastToUnrealTransfrom);
}
#if WITH_EDITOR
if (GIsEditor && HasAnyFlags(RF_ClassDefaultObject) == false)
{
FEditorDelegates::EditorModeEnter.AddUObject(this, &UNavigationSystemV1::OnEditorModeChanged, true);
FEditorDelegates::EditorModeExit.AddUObject(this, &UNavigationSystemV1::OnEditorModeChanged, false);
}
#endif // WITH_EDITOR
}
UNavigationSystemV1::~UNavigationSystemV1()
{
CleanUp(FNavigationSystem::ECleanupMode::CleanupUnsafe);
#if WITH_EDITOR
if (GIsEditor)
{
FEditorDelegates::EditorModeEnter.RemoveAll(this);
FEditorDelegates::EditorModeExit.RemoveAll(this);
}
#endif // WITH_EDITOR
#if !UE_BUILD_SHIPPING
FCoreDelegates::OnGetOnScreenMessages.RemoveAll(this);
#endif // !UE_BUILD_SHIPPING
}
void UNavigationSystemV1::ConfigureAsStatic()
{
bStaticRuntimeNavigation = true;
SetWantsComponentChangeNotifies(false);
}
void UNavigationSystemV1::SetUpdateNavOctreeOnComponentChange(bool bNewUpdateOnComponentChange)
{
bUpdateNavOctreeOnComponentChange = bNewUpdateOnComponentChange;
}
void UNavigationSystemV1::DoInitialSetup()
{
if (bInitialSetupHasBeenPerformed)
{
return;
}
UpdateAbstractNavData();
CreateCrowdManager();
bInitialSetupHasBeenPerformed = true;
}
void UNavigationSystemV1::UpdateAbstractNavData()
{
if (AbstractNavData != nullptr && !AbstractNavData->IsPendingKill())
{
return;
}
// spawn abstract nav data separately
// it's responsible for direct paths and shouldn't be picked for any agent type as default one
UWorld* NavWorld = GetWorld();
for (TActorIterator<AAbstractNavData> It(NavWorld); It; ++It)
{
AAbstractNavData* Nav = (*It);
if (Nav && !Nav->IsPendingKill())
{
AbstractNavData = Nav;
break;
}
}
if (AbstractNavData == NULL)
{
FNavDataConfig DummyConfig;
DummyConfig.NavigationDataClass = AAbstractNavData::StaticClass();
AbstractNavData = CreateNavigationDataInstance(DummyConfig);
if (AbstractNavData)
{
AbstractNavData->SetFlags(RF_Transient);
}
}
}
void UNavigationSystemV1::SetSupportedAgentsNavigationClass(int32 AgentIndex, TSubclassOf<ANavigationData> NavigationDataClass)
{
check(SupportedAgents.IsValidIndex(AgentIndex));
SupportedAgents[AgentIndex].NavigationDataClass = NavigationDataClass;
// keep preferred navigation data class in sync with actual class
// this will be passed to navigation data actor and will be required
// for comparisons done in DoesSupportAgent calls
//
// "Any" navigation data preference is valid only for instanced agents
SupportedAgents[AgentIndex].SetPreferredNavData(NavigationDataClass);
if (NavigationDataClass != nullptr)
{
SupportedAgents[AgentIndex].NavigationDataClassName = FSoftClassPath::GetOrCreateIDForClass(NavigationDataClass);
}
else
{
SupportedAgents[AgentIndex].NavigationDataClassName.Reset();
}
#if WITH_EDITOR
if (GIsEditor)
{
if (HasAnyFlags(RF_ClassDefaultObject) == false)
{
// set it at CDO to properly show up in project settings
// @hack the reason for doing it this way is that engine doesn't handle default TSubclassOf properties
// set to game-specific classes;
UNavigationSystemV1* NavigationSystemCDO = GetMutableDefault<UNavigationSystemV1>(GetClass());
NavigationSystemCDO->SetSupportedAgentsNavigationClass(AgentIndex, NavigationDataClass);
}
}
#endif // WITH_EDITOR
}
void UNavigationSystemV1::PostInitProperties()
{
Super::PostInitProperties();
if (HasAnyFlags(RF_ClassDefaultObject) == false)
{
// Populate our NavAreaClasses list with all known nav area classes.
// If more are loaded after this they will be registered as they come
TArray<UClass*> CurrentNavAreaClasses;
GetDerivedClasses(UNavArea::StaticClass(), CurrentNavAreaClasses);
for (UClass* NavAreaClass : CurrentNavAreaClasses)
{
RegisterNavAreaClass(NavAreaClass);
}
// make sure there's at least one supported navigation agent size
if (SupportedAgents.Num() == 0)
{
SupportedAgents.Add(FNavDataConfig(FNavigationSystem::FallbackAgentRadius, FNavigationSystem::FallbackAgentHeight));
}
else
{
for (int32 AgentIndex = 0; AgentIndex < SupportedAgents.Num(); ++AgentIndex)
{
FNavDataConfig& SupportedAgentConfig = SupportedAgents[AgentIndex];
// a piece of legacy maintanance
if (SupportedAgentConfig.NavigationDataClass != nullptr && SupportedAgentConfig.NavigationDataClassName.IsValid() == false)
{
// fill NavigationDataClassName
SupportedAgentConfig.NavigationDataClassName = FSoftClassPath(SupportedAgentConfig.NavigationDataClass);
}
else
{
TSubclassOf<ANavigationData> NavigationDataClass = SupportedAgentConfig.NavigationDataClassName.IsValid()
? LoadClass<ANavigationData>(NULL, *SupportedAgentConfig.NavigationDataClassName.ToString(), NULL, LOAD_None, NULL)
: nullptr;
SetSupportedAgentsNavigationClass(AgentIndex, NavigationDataClass);
}
}
}
if (bInitialBuildingLocked)
{
InitialNavBuildingLockFlags |= ENavigationBuildLock::InitialLock;
}
uint8 UseLockFlags = InitialNavBuildingLockFlags;
AddNavigationBuildLock(UseLockFlags);
// register for any actor move change
#if WITH_EDITOR
if ( GIsEditor )
{
GEngine->OnActorMoved().AddUObject(this, &UNavigationSystemV1::OnActorMoved);
}
#endif
FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(this, &UNavigationSystemV1::OnPostLoadMap);
UNavigationSystemV1::NavigationDirtyEvent.AddUObject(this, &UNavigationSystemV1::OnNavigationDirtied);
#if WITH_HOT_RELOAD
IHotReloadInterface& HotReloadSupport = FModuleManager::LoadModuleChecked<IHotReloadInterface>("HotReload");
HotReloadDelegateHandle = HotReloadSupport.OnHotReload().AddUObject(this, &UNavigationSystemV1::OnHotReload);
#endif
}
}
bool UNavigationSystemV1::ConditionalPopulateNavOctree()
{
// Discard all navigation updates caused by octree construction
TGuardValue<TArray<FNavigationDirtyArea>> DirtyGuard(DirtyAreas, TArray<FNavigationDirtyArea>());
// We are going to fully re-populate NavOctree so all pending update request are outdated
PendingOctreeUpdates.Empty(32);
// Discard current octree
DestroyNavOctree();
// See if any of registered navigation data need navoctree
bSupportRebuilding = RequiresNavOctree();
if (bSupportRebuilding)
{
NavOctree = MakeShareable(new FNavigationOctree(FVector(0,0,0), 64000));
NavOctree->SetDataGatheringMode(DataGatheringMode);
const ERuntimeGenerationType RuntimeGenerationType = GetRuntimeGenerationType();
const bool bStoreNavGeometry = (RuntimeGenerationType == ERuntimeGenerationType::Dynamic);
NavOctree->SetNavigableGeometryStoringMode(bStoreNavGeometry ? FNavigationOctree::StoreNavGeometry : FNavigationOctree::SkipNavGeometry);
if (bStoreNavGeometry)
{
#if WITH_RECAST
NavOctree->ComponentExportDelegate = FNavigationOctree::FNavigableGeometryComponentExportDelegate::CreateStatic(&FRecastNavMeshGenerator::ExportComponentGeometry);
#endif // WITH_RECAST
}
if (!IsNavigationOctreeLocked())
{
UWorld* World = GetWorld();
check(World);
// now process all actors on all levels
for (int32 LevelIndex = 0; LevelIndex < World->GetNumLevels(); ++LevelIndex)
{
ULevel* Level = World->GetLevel(LevelIndex);
AddLevelCollisionToOctree(Level);
for (int32 ActorIndex = 0; ActorIndex < Level->Actors.Num(); ActorIndex++)
{
AActor* Actor = Level->Actors[ActorIndex];
const bool bLegalActor = Actor && !Actor->IsPendingKill();
if (bLegalActor)
{
ConditionalPopulateNavOctreeActor(*Actor);
}
}
}
}
}
// Add all found elements to octree, this will not add new dirty areas to navigation
if (PendingOctreeUpdates.Num())
{
for (TSet<FNavigationDirtyElement>::TIterator It(PendingOctreeUpdates); It; ++It)
{
AddElementToNavOctree(*It);
}
PendingOctreeUpdates.Empty(32);
}
return bSupportRebuilding;
}
#if WITH_EDITOR
void UNavigationSystemV1::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent)
{
static const FName NAME_NavigationDataClass = GET_MEMBER_NAME_CHECKED(FNavDataConfig, NavigationDataClass);
static const FName NAME_SupportedAgents = GET_MEMBER_NAME_CHECKED(UNavigationSystemV1, SupportedAgents);
static const FName NAME_AllowClientSideNavigation = GET_MEMBER_NAME_CHECKED(UNavigationSystemV1, bAllowClientSideNavigation);
Super::PostEditChangeChainProperty(PropertyChangedEvent);
if (PropertyChangedEvent.Property)
{
FName PropName = PropertyChangedEvent.Property->GetFName();
if (PropName == NAME_NavigationDataClass)
{
int32 SupportedAgentIndex = PropertyChangedEvent.GetArrayIndex(NAME_SupportedAgents.ToString());
if (SupportedAgents.IsValidIndex(SupportedAgentIndex))
{
// reflect the change to SupportedAgent's
TSubclassOf<ANavigationData> NavClass = SupportedAgents[SupportedAgentIndex].NavigationDataClass.Get();
SetSupportedAgentsNavigationClass(SupportedAgentIndex, NavClass);
SaveConfig();
}
}
else if (PropName == NAME_AllowClientSideNavigation && HasAnyFlags(RF_ClassDefaultObject))
{
for (FObjectIterator It(UNavigationSystemModuleConfig::StaticClass()); It; ++It)
{
((UNavigationSystemModuleConfig*)*It)->UpdateWithNavSysCDO(*this);
}
}
}
}
void UNavigationSystemV1::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
static const FName NAME_EnableActiveTiles = GET_MEMBER_NAME_CHECKED(UNavigationSystemV1, bGenerateNavigationOnlyAroundNavigationInvokers);
Super::PostEditChangeProperty(PropertyChangedEvent);
if (PropertyChangedEvent.Property)
{
FName PropName = PropertyChangedEvent.Property->GetFName();
if (PropName == NAME_EnableActiveTiles)
{
if (NavOctree.IsValid())
{
NavOctree->SetDataGatheringMode(DataGatheringMode);
}
for (auto NavData : NavDataSet)
{
if (NavData)
{
NavData->RestrictBuildingToActiveTiles(bGenerateNavigationOnlyAroundNavigationInvokers);
}
}
}
}
}
#endif // WITH_EDITOR
void UNavigationSystemV1::OnInitializeActors()
{
}
void UNavigationSystemV1::OnWorldInitDone(FNavigationSystemRunMode Mode)
{
static const bool bSkipRebuildInEditor = true;
OperationMode = Mode;
DoInitialSetup();
UWorld* World = GetWorld();
if (IsThereAnywhereToBuildNavigation() == false
// Simulation mode is a special case - better not do it in this case
&& OperationMode != FNavigationSystemRunMode::SimulationMode)
{
// remove all navigation data instances
for (TActorIterator<ANavigationData> It(World); It; ++It)
{
ANavigationData* Nav = (*It);
if (Nav != NULL && Nav->IsPendingKill() == false && Nav != GetAbstractNavData())
{
UnregisterNavData(Nav);
Nav->CleanUpAndMarkPendingKill();
bNavDataRemovedDueToMissingNavBounds = true;
}
}
if (OperationMode == FNavigationSystemRunMode::EditorMode)
{
RemoveNavigationBuildLock(InitialNavBuildingLockFlags, bSkipRebuildInEditor);
}
}
else
{
// Discard all bounds updates that was submitted during world initialization,
// to avoid navigation rebuild right after map is loaded
PendingNavBoundsUpdates.Empty();
// gather navigable bounds
GatherNavigationBounds();
// gather all navigation data instances and register all not-yet-registered
// (since it's quite possible navigation system was not ready by the time
// those instances were serialized-in or spawned)
RegisterNavigationDataInstances();
if (bAutoCreateNavigationData == true)
{
SpawnMissingNavigationData();
// in case anything spawned has registered
ProcessRegistrationCandidates();
}
else
{
const bool bIsBuildLocked = IsNavigationBuildingLocked();
const bool bAllowRebuild = !bIsBuildLocked && GetIsAutoUpdateEnabled();
if (GetDefaultNavDataInstance(FNavigationSystem::DontCreate) != NULL)
{
// trigger navmesh update
for (TActorIterator<ANavigationData> It(World); It; ++It)
{
ANavigationData* NavData = (*It);
if (NavData != NULL)
{
ERegistrationResult Result = RegisterNavData(NavData);
if (Result == RegistrationSuccessful)
{
if (bAllowRebuild)
{
NavData->RebuildAll();
}
}
else if (Result != RegistrationFailed_DataPendingKill
&& Result != RegistrationFailed_AgentNotValid
)
{
NavData->CleanUpAndMarkPendingKill();
}
}
}
}
}
if (OperationMode == FNavigationSystemRunMode::EditorMode)
{
// don't lock navigation building in editor
RemoveNavigationBuildLock(InitialNavBuildingLockFlags, bSkipRebuildInEditor);
}
// See if any of registered navigation data needs NavOctree
ConditionalPopulateNavOctree();
// All navigation actors are registered
// Add NavMesh parts from all sub-levels that were streamed in prior NavMesh registration
const auto& Levels = World->GetLevels();
for (ULevel* Level : Levels)
{
if (!Level->IsPersistentLevel() && Level->bIsVisible)
{
for (ANavigationData* NavData : NavDataSet)
{
if (NavData)
{
NavData->OnStreamingLevelAdded(Level, World);
}
}
}
}
}
#if WITH_EDITOR
if (Mode == FNavigationSystemRunMode::EditorMode)
{
// make sure this static get applied to this instance
bNavigationAutoUpdateEnabled = !bNavigationAutoUpdateEnabled;
SetNavigationAutoUpdateEnabled(!bNavigationAutoUpdateEnabled, this);
// update navigation invokers
if (bGenerateNavigationOnlyAroundNavigationInvokers)
{
for (TObjectIterator<UNavigationInvokerComponent> It; It; ++It)
{
if (World == It->GetWorld())
{
It->RegisterWithNavigationSystem(*this);
}
}
}
// update navdata after loading world
if (GetIsAutoUpdateEnabled())
{
const bool bIsLoadTime = true;
RebuildAll(bIsLoadTime);
}
}
#endif
if (!bCanAccumulateDirtyAreas)
{
DirtyAreas.Empty();
}
bWorldInitDone = true;
OnNavigationInitDone.Broadcast();
}
void UNavigationSystemV1::RegisterNavigationDataInstances()
{
UWorld* World = GetWorld();
bool bProcessRegistration = false;
for (TActorIterator<ANavigationData> It(World); It; ++It)
{
ANavigationData* Nav = (*It);
if (Nav != NULL && Nav->IsPendingKill() == false && Nav->IsRegistered() == false)
{
RequestRegistration(Nav, false);
bProcessRegistration = true;
}
}
if (bProcessRegistration == true)
{
ProcessRegistrationCandidates();
}
}
void UNavigationSystemV1::CreateCrowdManager()
{
UClass* CrowdManagerClassInstance = CrowdManagerClass.Get();
if (CrowdManagerClassInstance)
{
UCrowdManagerBase* ManagerInstance = NewObject<UCrowdManagerBase>(this, CrowdManagerClassInstance);
// creating an instance when we have a valid class should never fail
check(ManagerInstance);
SetCrowdManager(ManagerInstance);
}
}
void UNavigationSystemV1::SetCrowdManager(UCrowdManagerBase* NewCrowdManager)
{
if (NewCrowdManager == CrowdManager.Get())
{
return;
}
if (CrowdManager.IsValid())
{
CrowdManager->RemoveFromRoot();
}
CrowdManager = NewCrowdManager;
if (NewCrowdManager != NULL)
{
CrowdManager->AddToRoot();
}
}
void UNavigationSystemV1::Tick(float DeltaSeconds)
{
SET_DWORD_STAT(STAT_Navigation_ObservedPathsCount, 0);
UWorld* World = GetWorld();
if (World == nullptr
|| (bTickWhilePaused == false && World->IsPaused())
#if WITH_EDITOR
|| (bIsPIEActive && !World->IsGameWorld())
#endif // WITH_EDITOR
)
{
return;
}
const bool bIsGame = World->IsGameWorld();
if (PendingCustomLinkRegistration.Num())
{
ProcessCustomLinkPendingRegistration();
}
if (PendingNavBoundsUpdates.Num() > 0)
{
PerformNavigationBoundsUpdate(PendingNavBoundsUpdates);
PendingNavBoundsUpdates.Reset();
}
if (PendingOctreeUpdates.Num() > 0)
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_AddingActorsToNavOctree);
SCOPE_CYCLE_COUNTER(STAT_Navigation_BuildTime)
STAT(double ThisTime = 0);
{
SCOPE_SECONDS_COUNTER(ThisTime);
for (TSet<FNavigationDirtyElement>::TIterator It(PendingOctreeUpdates); It; ++It)
{
AddElementToNavOctree(*It);
}
PendingOctreeUpdates.Empty(32);
}
INC_FLOAT_STAT_BY(STAT_Navigation_CumulativeBuildTime,(float)ThisTime*1000);
}
DirtyAreasUpdateTime += DeltaSeconds;
if (IsNavigationBuildingLocked() == false)
{
if (bGenerateNavigationOnlyAroundNavigationInvokers)
{
UpdateInvokers();
}
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_TickMarkDirty);
const float DirtyAreasUpdateDeltaTime = 1.0f / DirtyAreasUpdateFreq;
const bool bCanRebuildNow = (DirtyAreasUpdateTime >= DirtyAreasUpdateDeltaTime) || !bIsGame;
if (DirtyAreas.Num() > 0 && bCanRebuildNow)
{
for (int32 NavDataIndex = 0; NavDataIndex < NavDataSet.Num(); ++NavDataIndex)
{
ANavigationData* NavData = NavDataSet[NavDataIndex];
if (NavData)
{
NavData->RebuildDirtyAreas(DirtyAreas);
}
}
DirtyAreasUpdateTime = 0;
DirtyAreas.Reset();
}
}
// Tick navigation mesh async builders
if (bAsyncBuildPaused == false)
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_TickAsyncBuild);
for (ANavigationData* NavData : NavDataSet)
{
if (NavData)
{
NavData->TickAsyncBuild(DeltaSeconds);
}
}
}
}
if (AsyncPathFindingQueries.Num() > 0)
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_TickAsyncPathfinding);
TriggerAsyncQueries(AsyncPathFindingQueries);
AsyncPathFindingQueries.Reset();
}
if (CrowdManager.IsValid())
{
CrowdManager->Tick(DeltaSeconds);
}
}
void UNavigationSystemV1::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector)
{
UNavigationSystemV1* This = CastChecked<UNavigationSystemV1>(InThis);
UCrowdManagerBase* CrowdManager = This->GetCrowdManager();
Collector.AddReferencedObject(CrowdManager, InThis);
// don't reference NavAreaClasses in editor (unless PIE is active)
if (This->OperationMode != FNavigationSystemRunMode::EditorMode)
{
Collector.AddReferencedObjects(This->NavAreaClasses, InThis);
}
}
#if WITH_EDITOR
void UNavigationSystemV1::SetNavigationAutoUpdateEnabled(bool bNewEnable, UNavigationSystemBase* InNavigationSystemBase)
{
if (bNewEnable != bNavigationAutoUpdateEnabled)
{
bNavigationAutoUpdateEnabled = bNewEnable;
UNavigationSystemV1* NavSystem = Cast<UNavigationSystemV1>(InNavigationSystemBase);
if (NavSystem)
{
const bool bCurrentIsEnabled = NavSystem->GetIsAutoUpdateEnabled();
NavSystem->bCanAccumulateDirtyAreas = bCurrentIsEnabled
|| (NavSystem->OperationMode != FNavigationSystemRunMode::EditorMode && NavSystem->OperationMode != FNavigationSystemRunMode::InvalidMode);
if (bCurrentIsEnabled)
{
const bool bSkipRebuildsInEditor = false;
NavSystem->RemoveNavigationBuildLock(ENavigationBuildLock::NoUpdateInEditor, bSkipRebuildsInEditor);
}
else
{
#if !UE_BUILD_SHIPPING
NavSystem->bDirtyAreasReportedWhileAccumulationLocked = false;
#endif // !UE_BUILD_SHIPPING
NavSystem->AddNavigationBuildLock(ENavigationBuildLock::NoUpdateInEditor);
}
}
}
}
#endif // WITH_EDITOR
//----------------------------------------------------------------------//
// Public querying interface
//----------------------------------------------------------------------//
FPathFindingResult UNavigationSystemV1::FindPathSync(const FNavAgentProperties& AgentProperties, FPathFindingQuery Query, EPathFindingMode::Type Mode)
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_PathfindingSync);
if (Query.NavData.IsValid() == false)
{
Query.NavData = GetNavDataForProps(AgentProperties);
}
FPathFindingResult Result(ENavigationQueryResult::Error);
if (Query.NavData.IsValid())
{
if (Mode == EPathFindingMode::Hierarchical)
{
Result = Query.NavData->FindHierarchicalPath(AgentProperties, Query);
}
else
{
Result = Query.NavData->FindPath(AgentProperties, Query);
}
}
return Result;
}
FPathFindingResult UNavigationSystemV1::FindPathSync(FPathFindingQuery Query, EPathFindingMode::Type Mode)
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_PathfindingSync);
if (Query.NavData.IsValid() == false)
{
Query.NavData = GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
}
FPathFindingResult Result(ENavigationQueryResult::Error);
if (Query.NavData.IsValid())
{
if (Mode == EPathFindingMode::Regular)
{
Result = Query.NavData->FindPath(Query.NavAgentProperties, Query);
}
else // EPathFindingMode::Hierarchical
{
Result = Query.NavData->FindHierarchicalPath(Query.NavAgentProperties, Query);
}
}
return Result;
}
bool UNavigationSystemV1::TestPathSync(FPathFindingQuery Query, EPathFindingMode::Type Mode, int32* NumVisitedNodes) const
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_PathfindingSync);
if (Query.NavData.IsValid() == false)
{
Query.NavData = GetDefaultNavDataInstance();
}
bool bExists = false;
if (Query.NavData.IsValid())
{
if (Mode == EPathFindingMode::Hierarchical)
{
bExists = Query.NavData->TestHierarchicalPath(Query.NavAgentProperties, Query, NumVisitedNodes);
}
else
{
bExists = Query.NavData->TestPath(Query.NavAgentProperties, Query, NumVisitedNodes);
}
}
return bExists;
}
void UNavigationSystemV1::AddAsyncQuery(const FAsyncPathFindingQuery& Query)
{
check(IsInGameThread());
AsyncPathFindingQueries.Add(Query);
}
uint32 UNavigationSystemV1::FindPathAsync(const FNavAgentProperties& AgentProperties, FPathFindingQuery Query, const FNavPathQueryDelegate& ResultDelegate, EPathFindingMode::Type Mode)
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_RequestingAsyncPathfinding);
if (Query.NavData.IsValid() == false)
{
Query.NavData = GetNavDataForProps(AgentProperties);
}
if (Query.NavData.IsValid())
{
FAsyncPathFindingQuery AsyncQuery(Query, ResultDelegate, Mode);
if (AsyncQuery.QueryID != INVALID_NAVQUERYID)
{
AddAsyncQuery(AsyncQuery);
}
return AsyncQuery.QueryID;
}
return INVALID_NAVQUERYID;
}
void UNavigationSystemV1::AbortAsyncFindPathRequest(uint32 AsynPathQueryID)
{
check(IsInGameThread());
FAsyncPathFindingQuery* Query = AsyncPathFindingQueries.GetData();
for (int32 Index = 0; Index < AsyncPathFindingQueries.Num(); ++Index, ++Query)
{
if (Query->QueryID == AsynPathQueryID)
{
AsyncPathFindingQueries.RemoveAtSwap(Index);
break;
}
}
}
FAutoConsoleTaskPriority CPrio_TriggerAsyncQueries(
TEXT("TaskGraph.TaskPriorities.NavTriggerAsyncQueries"),
TEXT("Task and thread priority for UNavigationSystemV1::PerformAsyncQueries."),
ENamedThreads::BackgroundThreadPriority, // if we have background priority task threads, then use them...
ENamedThreads::NormalTaskPriority, // .. at normal task priority
ENamedThreads::NormalTaskPriority // if we don't have background threads, then use normal priority threads at normal task priority instead
);
void UNavigationSystemV1::TriggerAsyncQueries(TArray<FAsyncPathFindingQuery>& PathFindingQueries)
{
DECLARE_CYCLE_STAT(TEXT("FSimpleDelegateGraphTask.NavigationSystem batched async queries"),
STAT_FSimpleDelegateGraphTask_NavigationSystemBatchedAsyncQueries,
STATGROUP_TaskGraphTasks);
FSimpleDelegateGraphTask::CreateAndDispatchWhenReady(
FSimpleDelegateGraphTask::FDelegate::CreateUObject(this, &UNavigationSystemV1::PerformAsyncQueries, PathFindingQueries),
GET_STATID(STAT_FSimpleDelegateGraphTask_NavigationSystemBatchedAsyncQueries), nullptr, CPrio_TriggerAsyncQueries.Get());
}
static void AsyncQueryDone(FAsyncPathFindingQuery Query)
{
Query.OnDoneDelegate.ExecuteIfBound(Query.QueryID, Query.Result.Result, Query.Result.Path);
}
void UNavigationSystemV1::PerformAsyncQueries(TArray<FAsyncPathFindingQuery> PathFindingQueries)
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_PathfindingAsync);
if (PathFindingQueries.Num() == 0)
{
return;
}
for (FAsyncPathFindingQuery& Query : PathFindingQueries)
{
// @todo this is not necessarily the safest way to use UObjects outside of main thread.
// think about something else.
const ANavigationData* NavData = Query.NavData.IsValid() ? Query.NavData.Get() : GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
// perform query
if (NavData)
{
if (Query.Mode == EPathFindingMode::Hierarchical)
{
Query.Result = NavData->FindHierarchicalPath(Query.NavAgentProperties, Query);
}
else
{
Query.Result = NavData->FindPath(Query.NavAgentProperties, Query);
}
}
else
{
Query.Result = ENavigationQueryResult::Error;
}
// @todo make it return more informative results (bResult == false)
// trigger calling delegate on main thread - otherwise it may depend too much on stuff being thread safe
DECLARE_CYCLE_STAT(TEXT("FSimpleDelegateGraphTask.Async nav query finished"),
STAT_FSimpleDelegateGraphTask_AsyncNavQueryFinished,
STATGROUP_TaskGraphTasks);
FSimpleDelegateGraphTask::CreateAndDispatchWhenReady(
FSimpleDelegateGraphTask::FDelegate::CreateStatic(AsyncQueryDone, Query),
GET_STATID(STAT_FSimpleDelegateGraphTask_AsyncNavQueryFinished), NULL, ENamedThreads::GameThread);
}
}
bool UNavigationSystemV1::GetRandomPoint(FNavLocation& ResultLocation, ANavigationData* NavData, FSharedConstNavQueryFilter QueryFilter)
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_QueriesTimeSync);
if (NavData == NULL)
{
NavData = MainNavData;
}
if (NavData != NULL)
{
ResultLocation = NavData->GetRandomPoint(QueryFilter);
return true;
}
return false;
}
bool UNavigationSystemV1::GetRandomReachablePointInRadius(const FVector& Origin, float Radius, FNavLocation& ResultLocation, ANavigationData* NavData, FSharedConstNavQueryFilter QueryFilter) const
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_QueriesTimeSync);
if (NavData == nullptr)
{
NavData = MainNavData;
}
return NavData != nullptr && NavData->GetRandomReachablePointInRadius(Origin, Radius, ResultLocation, QueryFilter);
}
bool UNavigationSystemV1::GetRandomPointInNavigableRadius(const FVector& Origin, float Radius, FNavLocation& ResultLocation, ANavigationData* NavData, FSharedConstNavQueryFilter QueryFilter) const
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_QueriesTimeSync);
if (NavData == nullptr)
{
NavData = MainNavData;
}
return NavData != nullptr && NavData->GetRandomPointInNavigableRadius(Origin, Radius, ResultLocation, QueryFilter);
}
ENavigationQueryResult::Type UNavigationSystemV1::GetPathCost(const FVector& PathStart, const FVector& PathEnd, float& OutPathCost, const ANavigationData* NavData, FSharedConstNavQueryFilter QueryFilter) const
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_QueriesTimeSync);
if (NavData == NULL)
{
NavData = GetDefaultNavDataInstance();
}
return NavData != NULL ? NavData->CalcPathCost(PathStart, PathEnd, OutPathCost, QueryFilter) : ENavigationQueryResult::Error;
}
ENavigationQueryResult::Type UNavigationSystemV1::GetPathLength(const FVector& PathStart, const FVector& PathEnd, float& OutPathLength, const ANavigationData* NavData, FSharedConstNavQueryFilter QueryFilter) const
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_QueriesTimeSync);
if (NavData == NULL)
{
NavData = GetDefaultNavDataInstance();
}
return NavData != NULL ? NavData->CalcPathLength(PathStart, PathEnd, OutPathLength, QueryFilter) : ENavigationQueryResult::Error;
}
ENavigationQueryResult::Type UNavigationSystemV1::GetPathLengthAndCost(const FVector& PathStart, const FVector& PathEnd, float& OutPathLength, float& OutPathCost, const ANavigationData* NavData, FSharedConstNavQueryFilter QueryFilter) const
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_QueriesTimeSync);
if (NavData == NULL)
{
NavData = GetDefaultNavDataInstance();
}
return NavData != NULL ? NavData->CalcPathLengthAndCost(PathStart, PathEnd, OutPathLength, OutPathCost, QueryFilter) : ENavigationQueryResult::Error;
}
bool UNavigationSystemV1::ProjectPointToNavigation(const FVector& Point, FNavLocation& OutLocation, const FVector& Extent, const ANavigationData* NavData, FSharedConstNavQueryFilter QueryFilter) const
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_QueriesTimeSync);
if (NavData == NULL)
{
NavData = GetDefaultNavDataInstance();
}
return NavData != NULL && NavData->ProjectPoint(Point, OutLocation
, FNavigationSystem::IsValidExtent(Extent) ? Extent : NavData->GetConfig().DefaultQueryExtent
, QueryFilter);
}
UNavigationPath* UNavigationSystemV1::FindPathToActorSynchronously(UObject* WorldContextObject, const FVector& PathStart, AActor* GoalActor, float TetherDistance, AActor* PathfindingContext, TSubclassOf<UNavigationQueryFilter> FilterClass)
{
if (GoalActor == NULL)
{
return NULL;
}
INavAgentInterface* NavAgent = Cast<INavAgentInterface>(GoalActor);
UNavigationPath* GeneratedPath = FindPathToLocationSynchronously(WorldContextObject, PathStart, NavAgent ? NavAgent->GetNavAgentLocation() : GoalActor->GetActorLocation(), PathfindingContext, FilterClass);
if (GeneratedPath != NULL && GeneratedPath->GetPath().IsValid() == true)
{
GeneratedPath->GetPath()->SetGoalActorObservation(*GoalActor, TetherDistance);
}
return GeneratedPath;
}
UNavigationPath* UNavigationSystemV1::FindPathToLocationSynchronously(UObject* WorldContextObject, const FVector& PathStart, const FVector& PathEnd, AActor* PathfindingContext, TSubclassOf<UNavigationQueryFilter> FilterClass)
{
UWorld* World = NULL;
if (WorldContextObject != NULL)
{
World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
}
if (World == NULL && PathfindingContext != NULL)
{
World = GEngine->GetWorldFromContextObject(PathfindingContext, EGetWorldErrorMode::LogAndReturnNull);
}
UNavigationPath* ResultPath = NULL;
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys != nullptr && NavSys->GetDefaultNavDataInstance() != nullptr)
{
ResultPath = NewObject<UNavigationPath>(NavSys);
bool bValidPathContext = false;
const ANavigationData* NavigationData = NULL;
if (PathfindingContext != NULL)
{
INavAgentInterface* NavAgent = Cast<INavAgentInterface>(PathfindingContext);
if (NavAgent != NULL)
{
const FNavAgentProperties& AgentProps = NavAgent->GetNavAgentPropertiesRef();
NavigationData = NavSys->GetNavDataForProps(AgentProps);
bValidPathContext = true;
}
else if (Cast<ANavigationData>(PathfindingContext))
{
NavigationData = (ANavigationData*)PathfindingContext;
bValidPathContext = true;
}
}
if (bValidPathContext == false)
{
// just use default
NavigationData = NavSys->GetDefaultNavDataInstance();
}
check(NavigationData);
const FPathFindingQuery Query(PathfindingContext, *NavigationData, PathStart, PathEnd, UNavigationQueryFilter::GetQueryFilter(*NavigationData, PathfindingContext, FilterClass));
const FPathFindingResult Result = NavSys->FindPathSync(Query, EPathFindingMode::Regular);
if (Result.IsSuccessful())
{
ResultPath->SetPath(Result.Path);
}
}
return ResultPath;
}
bool UNavigationSystemV1::NavigationRaycast(UObject* WorldContextObject, const FVector& RayStart, const FVector& RayEnd, FVector& HitLocation, TSubclassOf<UNavigationQueryFilter> FilterClass, AController* Querier)
{
UWorld* World = NULL;
if (WorldContextObject != NULL)
{
World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
}
if (World == NULL && Querier != NULL)
{
World = GEngine->GetWorldFromContextObject(Querier, EGetWorldErrorMode::LogAndReturnNull);
}
// blocked, i.e. not traversable, by default
bool bRaycastBlocked = true;
HitLocation = RayStart;
const UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys)
{
// figure out which navigation data to use
const ANavigationData* NavData = NULL;
INavAgentInterface* MyNavAgent = Cast<INavAgentInterface>(Querier);
if (MyNavAgent)
{
const FNavAgentProperties& AgentProps = MyNavAgent->GetNavAgentPropertiesRef();
NavData = NavSys->GetNavDataForProps(AgentProps);
}
if (NavData == NULL)
{
NavData = NavSys->GetDefaultNavDataInstance();
}
if (NavData != NULL)
{
bRaycastBlocked = NavData->Raycast(RayStart, RayEnd, HitLocation, UNavigationQueryFilter::GetQueryFilter(*NavData, Querier, FilterClass));
}
}
return bRaycastBlocked;
}
void UNavigationSystemV1::GetNavAgentPropertiesArray(TArray<FNavAgentProperties>& OutNavAgentProperties) const
{
AgentToNavDataMap.GetKeys(OutNavAgentProperties);
}
ANavigationData* UNavigationSystemV1::GetNavDataForProps(const FNavAgentProperties& AgentProperties)
{
const UNavigationSystemV1* ConstThis = AsConst(this);
return const_cast<ANavigationData*>(ConstThis->GetNavDataForProps(AgentProperties));
}
// @todo could optimize this by having "SupportedAgentIndex" in FNavAgentProperties
const ANavigationData* UNavigationSystemV1::GetNavDataForProps(const FNavAgentProperties& AgentProperties) const
{
if (SupportedAgents.Num() <= 1)
{
return MainNavData;
}
const TWeakObjectPtr<ANavigationData>* NavDataForAgent = AgentToNavDataMap.Find(AgentProperties);
const ANavigationData* NavDataInstance = NavDataForAgent ? NavDataForAgent->Get() : nullptr;
if (NavDataInstance == nullptr)
{
TArray<FNavAgentProperties> AgentPropertiesList;
AgentToNavDataMap.GenerateKeyArray(AgentPropertiesList);
FNavAgentProperties BestFitNavAgent;
float BestExcessHeight = -FLT_MAX;
float BestExcessRadius = -FLT_MAX;
float ExcessRadius = -FLT_MAX;
float ExcessHeight = -FLT_MAX;
const float AgentHeight = bSkipAgentHeightCheckWhenPickingNavData ? 0.f : AgentProperties.AgentHeight;
for (TArray<FNavAgentProperties>::TConstIterator It(AgentPropertiesList); It; ++It)
{
const FNavAgentProperties& NavIt = *It;
const bool bNavClassMatch = NavIt.IsNavDataMatching(AgentProperties);
if (!bNavClassMatch)
{
continue;
}
ExcessRadius = NavIt.AgentRadius - AgentProperties.AgentRadius;
ExcessHeight = bSkipAgentHeightCheckWhenPickingNavData ? 0.f : (NavIt.AgentHeight - AgentHeight);
const bool bExcessRadiusIsBetter = ((ExcessRadius == 0) && (BestExcessRadius != 0))
|| ((ExcessRadius > 0) && (BestExcessRadius < 0))
|| ((ExcessRadius > 0) && (BestExcessRadius > 0) && (ExcessRadius < BestExcessRadius))
|| ((ExcessRadius < 0) && (BestExcessRadius < 0) && (ExcessRadius > BestExcessRadius));
const bool bExcessHeightIsBetter = ((ExcessHeight == 0) && (BestExcessHeight != 0))
|| ((ExcessHeight > 0) && (BestExcessHeight < 0))
|| ((ExcessHeight > 0) && (BestExcessHeight > 0) && (ExcessHeight < BestExcessHeight))
|| ((ExcessHeight < 0) && (BestExcessHeight < 0) && (ExcessHeight > BestExcessHeight));
const bool bBestIsValid = (BestExcessRadius >= 0) && (BestExcessHeight >= 0);
const bool bRadiusEquals = (ExcessRadius == BestExcessRadius);
const bool bHeightEquals = (ExcessHeight == BestExcessHeight);
bool bValuesAreBest = ((bExcessRadiusIsBetter || bRadiusEquals) && (bExcessHeightIsBetter || bHeightEquals));
if (!bValuesAreBest && !bBestIsValid)
{
bValuesAreBest = bExcessRadiusIsBetter || (bRadiusEquals && bExcessHeightIsBetter);
}
if (bValuesAreBest)
{
BestFitNavAgent = NavIt;
BestExcessHeight = ExcessHeight;
BestExcessRadius = ExcessRadius;
}
}
if (BestFitNavAgent.IsValid())
{
NavDataForAgent = AgentToNavDataMap.Find(BestFitNavAgent);
NavDataInstance = NavDataForAgent ? NavDataForAgent->Get() : nullptr;
}
}
return NavDataInstance ? NavDataInstance : MainNavData;
}
ANavigationData* UNavigationSystemV1::GetDefaultNavDataInstance(FNavigationSystem::ECreateIfMissing CreateNewIfNoneFound)
{
checkSlow(IsInGameThread() == true);
if (MainNavData == NULL || MainNavData->IsPendingKill())
{
MainNavData = NULL;
// @TODO this should be done a differently. There should be specified a "default agent"
for (int32 NavDataIndex = 0; NavDataIndex < NavDataSet.Num(); ++NavDataIndex)
{
ANavigationData* NavData = NavDataSet[NavDataIndex];
if (NavData && !NavData->IsPendingKill() && NavData->CanBeMainNavData())
{
MainNavData = NavData;
break;
}
}
#if WITH_RECAST
if ( /*GIsEditor && */(MainNavData == NULL) && CreateNewIfNoneFound == FNavigationSystem::Create )
{
// Spawn a new one if we're in the editor. In-game, either we loaded one or we don't get one.
MainNavData = GetWorld()->SpawnActor<ANavigationData>(ARecastNavMesh::StaticClass());
}
#endif // WITH_RECAST
// either way make sure it's registered. Registration stores unique
// navmeshes, so we have nothing to lose
RegisterNavData(MainNavData);
}
return MainNavData;
}
FSharedNavQueryFilter UNavigationSystemV1::CreateDefaultQueryFilterCopy() const
{
return MainNavData ? MainNavData->GetDefaultQueryFilter()->GetCopy() : NULL;
}
bool UNavigationSystemV1::IsNavigationBuilt(const AWorldSettings* Settings) const
{
if (Settings == nullptr || Settings->IsNavigationSystemEnabled() == false || IsThereAnywhereToBuildNavigation() == false)
{
return true;
}
bool bIsBuilt = true;
for (int32 NavDataIndex = 0; NavDataIndex < NavDataSet.Num(); ++NavDataIndex)
{
ANavigationData* NavData = NavDataSet[NavDataIndex];
if (NavData != NULL && NavData->GetWorldSettings() == Settings)
{
FNavDataGenerator* Generator = NavData->GetGenerator();
if ((NavData->GetRuntimeGenerationMode() != ERuntimeGenerationType::Static
#if WITH_EDITOR
|| GEditor != NULL
#endif // WITH_EDITOR
) && (Generator == NULL || Generator->IsBuildInProgress(/*bCheckDirtyToo=*/true) == true))
{
bIsBuilt = false;
break;
}
}
}
return bIsBuilt;
}
bool UNavigationSystemV1::IsThereAnywhereToBuildNavigation() const
{
// not check if there are any volumes or other structures requiring/supporting navigation building
if (bWholeWorldNavigable == true)
{
return true;
}
for (const FNavigationBounds& Bounds : RegisteredNavBounds)
{
if (Bounds.AreaBox.IsValid)
{
return true;
}
}
// @TODO this should be made more flexible to be able to trigger this from game-specific
// code (like Navigation System's subclass maybe)
bool bCreateNavigation = false;
for (TActorIterator<ANavMeshBoundsVolume> It(GetWorld()); It; ++It)
{
ANavMeshBoundsVolume const* const V = (*It);
if (V != NULL && !V->IsPendingKill())
{
bCreateNavigation = true;
break;
}
}
return bCreateNavigation;
}
bool UNavigationSystemV1::IsNavigationRelevant(const AActor* TestActor) const
{
const INavRelevantInterface* NavInterface = Cast<const INavRelevantInterface>(TestActor);
if (NavInterface && NavInterface->IsNavigationRelevant())
{
return true;
}
if (TestActor)
{
TInlineComponentArray<UActorComponent*> Components;
for (int32 Idx = 0; Idx < Components.Num(); Idx++)
{
NavInterface = Cast<const INavRelevantInterface>(Components[Idx]);
if (NavInterface && NavInterface->IsNavigationRelevant())
{
return true;
}
}
}
return false;
}
FBox UNavigationSystemV1::GetWorldBounds() const
{
checkSlow(IsInGameThread() == true);
NavigableWorldBounds = FBox(ForceInit);
if (GetWorld() != nullptr)
{
if (bWholeWorldNavigable == false)
{
for (const FNavigationBounds& Bounds : RegisteredNavBounds)
{
NavigableWorldBounds += Bounds.AreaBox;
}
}
else
{
// @TODO - super slow! Need to ask tech guys where I can get this from
for (FActorIterator It(GetWorld()); It; ++It)
{
if (IsNavigationRelevant(*It))
{
NavigableWorldBounds += (*It)->GetComponentsBoundingBox();
}
}
}
}
return NavigableWorldBounds;
}
FBox UNavigationSystemV1::GetLevelBounds(ULevel* InLevel) const
{
FBox NavigableLevelBounds(ForceInit);
if (InLevel)
{
AActor** Actor = InLevel->Actors.GetData();
const int32 ActorCount = InLevel->Actors.Num();
for (int32 ActorIndex = 0; ActorIndex < ActorCount; ++ActorIndex, ++Actor)
{
if (IsNavigationRelevant(*Actor))
{
NavigableLevelBounds += (*Actor)->GetComponentsBoundingBox();
}
}
}
return NavigableLevelBounds;
}
const TSet<FNavigationBounds>& UNavigationSystemV1::GetNavigationBounds() const
{
return RegisteredNavBounds;
}
void UNavigationSystemV1::ApplyWorldOffset(const FVector& InOffset, bool bWorldShift)
{
// Attempt at generation of new nav mesh after the shift
// dynamic navmesh, we regenerate completely
if (GetRuntimeGenerationType() == ERuntimeGenerationType::Dynamic)
{
//stop generators from building navmesh
CancelBuild();
ConditionalPopulateNavOctree();
Build();
for (ANavigationData* NavData : NavDataSet)
{
if (NavData)
{
NavData->ConditionalConstructGenerator();
ARecastNavMesh* RecastNavMesh = Cast<ARecastNavMesh>(NavData);
if (RecastNavMesh)
{
RecastNavMesh->RequestDrawingUpdate();
}
}
}
}
else // static navmesh
{
//not sure what happens when we shift farther than the extents of the NavOctree are
for (ANavigationData* NavData : NavDataSet)
{
if (NavData)
{
NavData->ApplyWorldOffset(InOffset, bWorldShift);
}
}
}
}
//----------------------------------------------------------------------//
// Bookkeeping
//----------------------------------------------------------------------//
void UNavigationSystemV1::RequestRegistration(ANavigationData* NavData, bool bTriggerRegistrationProcessing)
{
FScopeLock RegistrationLock(&NavDataRegistrationSection);
if (NavDataRegistrationQueue.Num() < REGISTRATION_QUEUE_SIZE)
{
NavDataRegistrationQueue.AddUnique(NavData);
// checking if bWorldInitDone since requesting out-of-order registration
// processing when we're still setting up can result in odd cases,
// like initializing navmesh generators while the nav system doesn't have
// the navmesh bounds collected yet.
if (bTriggerRegistrationProcessing && bWorldInitDone)
{
// trigger registration candidates processing
DECLARE_CYCLE_STAT(TEXT("FSimpleDelegateGraphTask.Process registration candidates"),
STAT_FSimpleDelegateGraphTask_ProcessRegistrationCandidates,
STATGROUP_TaskGraphTasks);
FSimpleDelegateGraphTask::CreateAndDispatchWhenReady(
FSimpleDelegateGraphTask::FDelegate::CreateUObject(this, &UNavigationSystemV1::ProcessRegistrationCandidates),
GET_STATID(STAT_FSimpleDelegateGraphTask_ProcessRegistrationCandidates), NULL, ENamedThreads::GameThread);
}
}
else
{
UE_LOG(LogNavigation, Error, TEXT("Navigation System: registration queue full!"));
}
}
void UNavigationSystemV1::ProcessRegistrationCandidates()
{
//if (FUObjectThreadContext::Get().IsRoutingPostLoad)
//{
// // postopne
// return;
//}
FScopeLock RegistrationLock(&NavDataRegistrationSection);
if (NavDataRegistrationQueue.Num() == 0)
{
return;
}
ANavigationData** NavDataPtr = NavDataRegistrationQueue.GetData();
const int CandidatesCount = NavDataRegistrationQueue.Num();
for (int32 CandidateIndex = 0; CandidateIndex < CandidatesCount; ++CandidateIndex, ++NavDataPtr)
{
if (*NavDataPtr != NULL)
{
ERegistrationResult Result = RegisterNavData(*NavDataPtr);
if (Result == RegistrationSuccessful)
{
continue;
}
else if (Result != RegistrationFailed_DataPendingKill)
{
(*NavDataPtr)->CleanUpAndMarkPendingKill();
if ((*NavDataPtr) == MainNavData)
{
MainNavData = NULL;
}
}
}
}
MainNavData = GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
// we processed all candidates so clear the queue
NavDataRegistrationQueue.Reset();
}
void UNavigationSystemV1::ProcessCustomLinkPendingRegistration()
{
FScopeLock AccessLock(&CustomLinkRegistrationSection);
TMap<INavLinkCustomInterface*, FWeakObjectPtr> TempPending = PendingCustomLinkRegistration;
PendingCustomLinkRegistration.Empty();
for (TMap<INavLinkCustomInterface*, FWeakObjectPtr>::TIterator It(TempPending); It; ++It)
{
INavLinkCustomInterface* ILink = It.Key();
FWeakObjectPtr LinkOb = It.Value();
if (LinkOb.IsValid() && ILink)
{
RegisterCustomLink(*ILink);
}
}
}
UNavigationSystemV1::ERegistrationResult UNavigationSystemV1::RegisterNavData(ANavigationData* NavData)
{
if (NavData == NULL)
{
return RegistrationError;
}
else if (NavData->IsPendingKill() == true)
{
return RegistrationFailed_DataPendingKill;
}
// still to be seen if this is really true, but feels right
else if (NavData->IsRegistered() == true)
{
return RegistrationSuccessful;
}
FScopeLock Lock(&NavDataRegistration);
UNavigationSystemV1::ERegistrationResult Result = RegistrationError;
// find out which, if any, navigation agents are supported by this nav data
// if none then fail the registration
FNavDataConfig NavConfig = NavData->GetConfig();
// not discarding navmesh when there's only one Supported Agent
if (NavConfig.IsValid() == false && SupportedAgents.Num() == 1)
{
// fill in AgentProps with whatever is the instance's setup
NavConfig = SupportedAgents[0];
NavData->SetConfig(SupportedAgents[0]);
NavData->SetSupportsDefaultAgent(true);
NavData->ProcessNavAreas(NavAreaClasses, 0);
}
if (NavConfig.IsValid() == true)
{
// check if this kind of agent has already its navigation implemented
TWeakObjectPtr<ANavigationData>* NavDataForAgent = AgentToNavDataMap.Find(NavConfig);
ANavigationData* NavDataInstanceForAgent = NavDataForAgent ? NavDataForAgent->Get() : nullptr;
if (NavDataInstanceForAgent == nullptr)
{
if (NavData->IsA(AAbstractNavData::StaticClass()) == false)
{
// ok, so this navigation agent doesn't have its navmesh registered yet, but do we want to support it?
bool bAgentSupported = false;
for (int32 AgentIndex = 0; AgentIndex < SupportedAgents.Num(); ++AgentIndex)
{
if (NavData->GetClass() == SupportedAgents[AgentIndex].NavigationDataClass && SupportedAgents[AgentIndex].IsEquivalent(NavConfig) == true)
{
// it's supported, then just in case it's not a precise match (IsEquivalent succeeds with some precision)
// update NavData with supported Agent
bAgentSupported = true;
NavData->SetConfig(SupportedAgents[AgentIndex]);
AgentToNavDataMap.Add(SupportedAgents[AgentIndex], NavData);
NavData->SetSupportsDefaultAgent(AgentIndex == 0);
NavData->ProcessNavAreas(NavAreaClasses, AgentIndex);
OnNavDataRegisteredEvent.Broadcast(NavData);
NavDataSet.AddUnique(NavData);
NavData->OnRegistered();
break;
}
}
Result = bAgentSupported == true ? RegistrationSuccessful : RegistrationFailed_AgentNotValid;
}
else
{
// fake registration since it's a special navigation data type
// and it would get discarded for not implementing any particular
// navigation agent
// Node that we don't add abstract navigation data to NavDataSet
NavData->OnRegistered();
Result = RegistrationSuccessful;
}
}
else if (NavDataInstanceForAgent == NavData)
{
ensure(NavDataSet.Find(NavData) != INDEX_NONE);
// let's treat double registration of the same nav data with the same agent as a success
Result = RegistrationSuccessful;
}
else
{
// otherwise specified agent type already has its navmesh implemented, fail redundant instance
Result = RegistrationFailed_AgentAlreadySupported;
}
}
else
{
Result = RegistrationFailed_AgentNotValid;
}
// @todo else might consider modifying this NavData to implement navigation for one of the supported agents
// care needs to be taken to not make it implement navigation for agent who's real implementation has
// not been loaded yet.
return Result;
}
void UNavigationSystemV1::UnregisterNavData(ANavigationData* NavData)
{
NavDataSet.RemoveSingle(NavData);
if (NavData == NULL)
{
return;
}
FScopeLock Lock(&NavDataRegistration);
NavData->OnUnregistered();
}
void UNavigationSystemV1::RegisterCustomLink(INavLinkCustomInterface& CustomLink)
{
uint32 LinkId = CustomLink.GetLinkId();
// if there's already a link with that Id registered, assign new Id and mark dirty area
// this won't fix baked data in static navmesh (in game), but every other case will regenerate affected tiles
if (CustomLinksMap.Contains(LinkId))
{
LinkId = INavLinkCustomInterface::GetUniqueId();
CustomLink.UpdateLinkId(LinkId);
UObject* CustomLinkOb = CustomLink.GetLinkOwner();
UActorComponent* OwnerComp = Cast<UActorComponent>(CustomLinkOb);
AActor* OwnerActor = OwnerComp ? OwnerComp->GetOwner() : Cast<AActor>(CustomLinkOb);
if (OwnerActor)
{
ENavLinkDirection::Type DummyDir = ENavLinkDirection::BothWays;
FVector RelativePtA, RelativePtB;
CustomLink.GetLinkData(RelativePtA, RelativePtB, DummyDir);
const FTransform OwnerActorTM = OwnerActor->GetTransform();
const FVector WorldPtA = OwnerActorTM.TransformPosition(RelativePtA);
const FVector WorldPtB = OwnerActorTM.TransformPosition(RelativePtB);
FBox LinkBounds(ForceInitToZero);
LinkBounds += WorldPtA;
LinkBounds += WorldPtB;
AddDirtyArea(LinkBounds, OctreeUpdate_Modifiers);
}
}
CustomLinksMap.Add(LinkId, FNavigationSystem::FCustomLinkOwnerInfo(&CustomLink));
}
void UNavigationSystemV1::UnregisterCustomLink(INavLinkCustomInterface& CustomLink)
{
CustomLinksMap.Remove(CustomLink.GetLinkId());
}
INavLinkCustomInterface* UNavigationSystemV1::GetCustomLink(uint32 UniqueLinkId) const
{
const FNavigationSystem::FCustomLinkOwnerInfo* LinkInfo = CustomLinksMap.Find(UniqueLinkId);
return (LinkInfo && LinkInfo->IsValid()) ? LinkInfo->LinkInterface : nullptr;
}
void UNavigationSystemV1::UpdateCustomLink(const INavLinkCustomInterface* CustomLink)
{
for (TMap<FNavAgentProperties, TWeakObjectPtr<ANavigationData> >::TIterator It(AgentToNavDataMap); It; ++It)
{
ANavigationData* NavData = It.Value().Get();
if (NavData)
{
NavData->UpdateCustomLink(CustomLink);
}
}
}
void UNavigationSystemV1::RequestCustomLinkRegistering(INavLinkCustomInterface& CustomLink, UObject* OwnerOb)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(OwnerOb);
if (NavSys)
{
NavSys->RegisterCustomLink(CustomLink);
}
else
{
FScopeLock AccessLock(&CustomLinkRegistrationSection);
PendingCustomLinkRegistration.Add(&CustomLink, OwnerOb);
}
}
void UNavigationSystemV1::RequestCustomLinkUnregistering(INavLinkCustomInterface& CustomLink, UObject* OwnerOb)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(OwnerOb);
if (NavSys)
{
NavSys->UnregisterCustomLink(CustomLink);
}
else
{
FScopeLock AccessLock(&CustomLinkRegistrationSection);
PendingCustomLinkRegistration.Remove(&CustomLink);
}
}
void UNavigationSystemV1::RequestAreaUnregistering(UClass* NavAreaClass)
{
for (TObjectIterator<UNavigationSystemV1> NavSysIt; NavSysIt; ++NavSysIt)
{
NavSysIt->UnregisterNavAreaClass(NavAreaClass);
}
}
void UNavigationSystemV1::UnregisterNavAreaClass(UClass* NavAreaClass)
{
// remove from known areas
if (NavAreaClasses.Remove(NavAreaClass) > 0)
{
// notify navigation data
// notify existing nav data
OnNavigationAreaEvent(NavAreaClass, ENavAreaEvent::Unregistered);
}
}
void UNavigationSystemV1::RequestAreaRegistering(UClass* NavAreaClass)
{
for (TObjectIterator<UNavigationSystemV1> NavSysIt; NavSysIt; ++NavSysIt)
{
NavSysIt->RegisterNavAreaClass(NavAreaClass);
}
}
void UNavigationSystemV1::RegisterNavAreaClass(UClass* AreaClass)
{
// can't be null
if (AreaClass == NULL)
{
return;
}
// can't be abstract
if (AreaClass->HasAnyClassFlags(CLASS_Abstract))
{
return;
}
// special handling of blueprint based areas
if (AreaClass->HasAnyClassFlags(CLASS_CompiledFromBlueprint))
{
// can't be skeleton of blueprint class
if (AreaClass->GetName().Contains(TEXT("SKEL_")))
{
return;
}
// can't be class from Developers folder (won't be saved properly anyway)
const UPackage* Package = AreaClass->GetOutermost();
if (Package && Package->GetName().Contains(TEXT("/Developers/")))
{
return;
}
}
if (NavAreaClasses.Contains(AreaClass))
{
// Already added
return;
}
UNavArea* AreaClassCDO = GetMutableDefault<UNavArea>(AreaClass);
check(AreaClassCDO);
// initialize flags
AreaClassCDO->InitializeArea();
// add to know areas
NavAreaClasses.Add(AreaClass);
// notify existing nav data
OnNavigationAreaEvent(AreaClass, ENavAreaEvent::Registered);
#if WITH_EDITOR
UNavAreaMeta_SwitchByAgent* SwitchByAgentCDO = Cast<UNavAreaMeta_SwitchByAgent>(AreaClassCDO);
// update area properties
if (SwitchByAgentCDO)
{
SwitchByAgentCDO->UpdateAgentConfig();
}
#endif
}
void UNavigationSystemV1::OnNavigationAreaEvent(UClass* AreaClass, ENavAreaEvent::Type Event)
{
// notify existing nav data
for (auto NavigationData : NavDataSet)
{
if (NavigationData != NULL && NavigationData->IsPendingKillPending() == false)
{
NavigationData->OnNavAreaEvent(AreaClass, Event);
}
}
}
int32 UNavigationSystemV1::GetSupportedAgentIndex(const ANavigationData* NavData) const
{
if (SupportedAgents.Num() < 2)
{
return 0;
}
const FNavDataConfig& TestConfig = NavData->GetConfig();
for (int32 AgentIndex = 0; AgentIndex < SupportedAgents.Num(); AgentIndex++)
{
if (SupportedAgents[AgentIndex].IsEquivalent(TestConfig))
{
return AgentIndex;
}
}
return INDEX_NONE;
}
int32 UNavigationSystemV1::GetSupportedAgentIndex(const FNavAgentProperties& NavAgent) const
{
if (SupportedAgents.Num() < 2)
{
return 0;
}
for (int32 AgentIndex = 0; AgentIndex < SupportedAgents.Num(); AgentIndex++)
{
if (SupportedAgents[AgentIndex].IsEquivalent(NavAgent))
{
return AgentIndex;
}
}
return INDEX_NONE;
}
void UNavigationSystemV1::DescribeFilterFlags(UEnum* FlagsEnum) const
{
#if WITH_EDITOR
TArray<FString> FlagDesc;
FString EmptyStr;
FlagDesc.Init(EmptyStr, 16);
const int32 NumEnums = FMath::Min(16, FlagsEnum->NumEnums() - 1); // skip _MAX
for (int32 FlagIndex = 0; FlagIndex < NumEnums; FlagIndex++)
{
FlagDesc[FlagIndex] = FlagsEnum->GetDisplayNameTextByIndex(FlagIndex).ToString();
}
DescribeFilterFlags(FlagDesc);
#endif
}
void UNavigationSystemV1::DescribeFilterFlags(const TArray<FString>& FlagsDesc) const
{
#if WITH_EDITOR
const int32 MaxFlags = 16;
TArray<FString> UseDesc = FlagsDesc;
FString EmptyStr;
while (UseDesc.Num() < MaxFlags)
{
UseDesc.Add(EmptyStr);
}
// get special value from recast's navmesh
#if WITH_RECAST
uint16 NavLinkFlag = ARecastNavMesh::GetNavLinkFlag();
for (int32 FlagIndex = 0; FlagIndex < MaxFlags; FlagIndex++)
{
if ((NavLinkFlag >> FlagIndex) & 1)
{
UseDesc[FlagIndex] = TEXT("Navigation link");
break;
}
}
#endif
// setup properties
UStructProperty* StructProp1 = FindField<UStructProperty>(UNavigationQueryFilter::StaticClass(), TEXT("IncludeFlags"));
UStructProperty* StructProp2 = FindField<UStructProperty>(UNavigationQueryFilter::StaticClass(), TEXT("ExcludeFlags"));
check(StructProp1);
check(StructProp2);
UStruct* Structs[] = { StructProp1->Struct, StructProp2->Struct };
const FString CustomNameMeta = TEXT("DisplayName");
for (int32 StructIndex = 0; StructIndex < ARRAY_COUNT(Structs); StructIndex++)
{
for (int32 FlagIndex = 0; FlagIndex < MaxFlags; FlagIndex++)
{
FString PropName = FString::Printf(TEXT("bNavFlag%d"), FlagIndex);
UProperty* Prop = FindField<UProperty>(Structs[StructIndex], *PropName);
check(Prop);
if (UseDesc[FlagIndex].Len())
{
Prop->SetPropertyFlags(CPF_Edit);
Prop->SetMetaData(*CustomNameMeta, *UseDesc[FlagIndex]);
}
else
{
Prop->ClearPropertyFlags(CPF_Edit);
}
}
}
#endif
}
void UNavigationSystemV1::ResetCachedFilter(TSubclassOf<UNavigationQueryFilter> FilterClass)
{
for (int32 NavDataIndex = 0; NavDataIndex < NavDataSet.Num(); NavDataIndex++)
{
if (NavDataSet[NavDataIndex])
{
NavDataSet[NavDataIndex]->RemoveQueryFilter(FilterClass);
}
}
}
UNavigationSystemV1* UNavigationSystemV1::CreateNavigationSystem(UWorld* WorldOwner)
{
UNavigationSystemV1* NavSys = NULL;
// create navigation system for editor and server targets, but remove it from game clients
if (WorldOwner && (*GEngine->NavigationSystemClass != nullptr)
&& (GEngine->NavigationSystemClass->GetDefaultObject<UNavigationSystemV1>()->bAllowClientSideNavigation || WorldOwner->GetNetMode() != NM_Client))
{
AWorldSettings* WorldSettings = WorldOwner->GetWorldSettings();
if (WorldSettings == NULL || WorldSettings->IsNavigationSystemEnabled())
{
NavSys = NewObject<UNavigationSystemV1>(WorldOwner, GEngine->NavigationSystemClass);
WorldOwner->SetNavigationSystem(NavSys);
}
}
return NavSys;
}
void UNavigationSystemV1::InitializeForWorld(UWorld& World, FNavigationSystemRunMode Mode)
{
OnWorldInitDone(Mode);
}
UNavigationSystemV1* UNavigationSystemV1::GetCurrent(UWorld* World)
{
return FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
}
UNavigationSystemV1* UNavigationSystemV1::GetCurrent(UObject* WorldContextObject)
{
return FNavigationSystem::GetCurrent<UNavigationSystemV1>(WorldContextObject);
}
ANavigationData* UNavigationSystemV1::GetNavDataWithID(const uint16 NavDataID) const
{
for (int32 NavDataIndex = 0; NavDataIndex < NavDataSet.Num(); ++NavDataIndex)
{
const ANavigationData* NavData = NavDataSet[NavDataIndex];
if (NavData != NULL && NavData->GetNavDataUniqueID() == NavDataID)
{
return const_cast<ANavigationData*>(NavData);
}
}
return NULL;
}
void UNavigationSystemV1::AddDirtyArea(const FBox& NewArea, int32 Flags)
{
if (Flags > 0 && bCanAccumulateDirtyAreas && NewArea.IsValid)
{
DirtyAreas.Add(FNavigationDirtyArea(NewArea, Flags));
}
#if !UE_BUILD_SHIPPING
bDirtyAreasReportedWhileAccumulationLocked = bDirtyAreasReportedWhileAccumulationLocked || (Flags > 0 && !bCanAccumulateDirtyAreas);
#endif // !UE_BUILD_SHIPPING
}
void UNavigationSystemV1::AddDirtyAreas(const TArray<FBox>& NewAreas, int32 Flags)
{
for (int32 NewAreaIndex = 0; NewAreaIndex < NewAreas.Num(); NewAreaIndex++)
{
AddDirtyArea(NewAreas[NewAreaIndex], Flags);
}
}
bool UNavigationSystemV1::HasDirtyAreasQueued() const
{
return DirtyAreas.Num() > 0;
}
int32 GetDirtyFlagHelper(int32 UpdateFlags, int32 DefaultValue)
{
return ((UpdateFlags & UNavigationSystemV1::OctreeUpdate_Geometry) != 0) ? ENavigationDirtyFlag::All :
((UpdateFlags & UNavigationSystemV1::OctreeUpdate_Modifiers) != 0) ? ENavigationDirtyFlag::DynamicModifier :
DefaultValue;
}
FSetElementId UNavigationSystemV1::RegisterNavOctreeElement(UObject* ElementOwner, INavRelevantInterface* ElementInterface, int32 UpdateFlags)
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_RegisterNavOctreeElement);
FSetElementId SetId;
#if WITH_EDITOR
if (IsNavigationRegisterLocked())
{
return SetId;
}
#endif
if (NavOctree.IsValid() == false || ElementOwner == NULL || ElementInterface == NULL)
{
return SetId;
}
if (IsNavigationOctreeLocked())
{
UE_LOG(LogNavOctree, Log, TEXT("IGNORE(RegisterNavOctreeElement) %s"), *GetPathNameSafe(ElementOwner));
return SetId;
}
const bool bIsRelevant = ElementInterface->IsNavigationRelevant();
UE_LOG(LogNavOctree, Log, TEXT("REG %s %s"), *GetNameSafe(ElementOwner), bIsRelevant ? TEXT("[relevant]") : TEXT(""));
if (bIsRelevant)
{
bool bCanAdd = false;
UObject* ParentNode = ElementInterface->GetNavigationParent();
if (ParentNode)
{
OctreeChildNodesMap.AddUnique(ParentNode, FWeakObjectPtr(ElementOwner));
bCanAdd = true;
}
else
{
const FOctreeElementId* ElementId = GetObjectsNavOctreeId(*ElementOwner);
bCanAdd = (ElementId == NULL);
}
if (bCanAdd)
{
FNavigationDirtyElement UpdateInfo(ElementOwner, ElementInterface, GetDirtyFlagHelper(UpdateFlags, 0));
SetId = PendingOctreeUpdates.FindId(UpdateInfo);
if (SetId.IsValidId())
{
// make sure this request stays, in case it has been invalidated already
PendingOctreeUpdates[SetId] = UpdateInfo;
}
else
{
SetId = PendingOctreeUpdates.Add(UpdateInfo);
}
}
}
return SetId;
}
void UNavigationSystemV1::AddElementToNavOctree(const FNavigationDirtyElement& DirtyElement)
{
// handle invalidated requests first
if (DirtyElement.bInvalidRequest)
{
if (DirtyElement.bHasPrevData)
{
AddDirtyArea(DirtyElement.PrevBounds, DirtyElement.PrevFlags);
}
return;
}
UObject* ElementOwner = DirtyElement.Owner.Get();
if (ElementOwner == NULL || ElementOwner->IsPendingKill() || DirtyElement.NavInterface == NULL)
{
return;
}
FNavigationOctreeElement GeneratedData(*ElementOwner);
const FBox ElementBounds = DirtyElement.NavInterface->GetNavigationBounds();
UObject* NavigationParent = DirtyElement.NavInterface->GetNavigationParent();
if (NavigationParent)
{
// check if parent node is waiting in queue
const FSetElementId ParentRequestId = PendingOctreeUpdates.FindId(FNavigationDirtyElement(NavigationParent));
const FOctreeElementId* ParentId = GetObjectsNavOctreeId(*NavigationParent);
if (ParentRequestId.IsValidId() && ParentId == NULL)
{
FNavigationDirtyElement& ParentNode = PendingOctreeUpdates[ParentRequestId];
AddElementToNavOctree(ParentNode);
// mark as invalid so it won't be processed twice
ParentNode.bInvalidRequest = true;
}
const FOctreeElementId* UseParentId = ParentId ? ParentId : GetObjectsNavOctreeId(*NavigationParent);
if (UseParentId && NavOctree->IsValidElementId(*UseParentId))
{
UE_LOG(LogNavOctree, Log, TEXT("ADD %s to %s"), *GetNameSafe(ElementOwner), *GetNameSafe(NavigationParent));
NavOctree->AppendToNode(*UseParentId, DirtyElement.NavInterface, ElementBounds, GeneratedData);
}
else
{
UE_LOG(LogNavOctree, Warning, TEXT("Can't add node [%s] - parent [%s] not found in octree!"), *GetNameSafe(ElementOwner), *GetNameSafe(NavigationParent));
}
}
else
{
UE_LOG(LogNavOctree, Log, TEXT("ADD %s"), *GetNameSafe(ElementOwner));
NavOctree->AddNode(ElementOwner, DirtyElement.NavInterface, ElementBounds, GeneratedData);
}
const FBox BBox = GeneratedData.Bounds.GetBox();
const bool bValidBBox = BBox.IsValid && !BBox.GetSize().IsNearlyZero();
if (bValidBBox && !GeneratedData.IsEmpty())
{
const int32 DirtyFlag = DirtyElement.FlagsOverride ? DirtyElement.FlagsOverride : GeneratedData.Data->GetDirtyFlag();
AddDirtyArea(BBox, DirtyFlag);
}
}
bool UNavigationSystemV1::GetNavOctreeElementData(const UObject& NodeOwner, int32& DirtyFlags, FBox& DirtyBounds)
{
const FOctreeElementId* ElementId = GetObjectsNavOctreeId(NodeOwner);
if (ElementId != NULL)
{
if (NavOctree->IsValidElementId(*ElementId))
{
// mark area occupied by given actor as dirty
FNavigationOctreeElement& ElementData = NavOctree->GetElementById(*ElementId);
DirtyFlags = ElementData.Data->GetDirtyFlag();
DirtyBounds = ElementData.Bounds.GetBox();
return true;
}
}
return false;
}
void UNavigationSystemV1::UnregisterNavOctreeElement(UObject* ElementOwner, INavRelevantInterface* ElementInterface, int32 UpdateFlags)
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_UnregisterNavOctreeElement);
#if WITH_EDITOR
if (IsNavigationUnregisterLocked())
{
return;
}
#endif
if (NavOctree.IsValid() == false || ElementOwner == NULL || ElementInterface == NULL)
{
return;
}
if (IsNavigationOctreeLocked())
{
UE_LOG(LogNavOctree, Log, TEXT("IGNORE(UnregisterNavOctreeElement) %s"), *GetPathNameSafe(ElementOwner));
return;
}
const FOctreeElementId* ElementId = GetObjectsNavOctreeId(*ElementOwner);
UE_LOG(LogNavOctree, Log, TEXT("UNREG %s %s"), *GetNameSafe(ElementOwner), ElementId ? TEXT("[exists]") : TEXT(""));
if (ElementId != NULL)
{
RemoveNavOctreeElementId(*ElementId, UpdateFlags);
RemoveObjectsNavOctreeId(*ElementOwner);
}
else
{
const bool bCanRemoveChildNode = (UpdateFlags & OctreeUpdate_ParentChain) == 0;
UObject* ParentNode = ElementInterface->GetNavigationParent();
if (ParentNode && bCanRemoveChildNode)
{
// if node has navigation parent (= doesn't exists in octree on its own)
// and it's not part of parent chain update
// remove it from map and force update on parent to rebuild octree element
OctreeChildNodesMap.RemoveSingle(ParentNode, FWeakObjectPtr(ElementOwner));
UpdateNavOctreeParentChain(ParentNode);
}
}
// mark pending update as invalid, it will be dirtied according to currently active settings
const bool bCanInvalidateQueue = (UpdateFlags & OctreeUpdate_Refresh) == 0;
if (bCanInvalidateQueue)
{
const FSetElementId RequestId = PendingOctreeUpdates.FindId(FNavigationDirtyElement(ElementOwner));
if (RequestId.IsValidId())
{
PendingOctreeUpdates[RequestId].bInvalidRequest = true;
}
}
}
void UNavigationSystemV1::RemoveNavOctreeElementId(const FOctreeElementId& ElementId, int32 UpdateFlags)
{
if (NavOctree->IsValidElementId(ElementId))
{
const FNavigationOctreeElement& ElementData = NavOctree->GetElementById(ElementId);
const int32 DirtyFlag = GetDirtyFlagHelper(UpdateFlags, ElementData.Data->GetDirtyFlag());
// mark area occupied by given actor as dirty
AddDirtyArea(ElementData.Bounds.GetBox(), DirtyFlag);
NavOctree->RemoveNode(ElementId);
}
}
const FNavigationRelevantData* UNavigationSystemV1::GetDataForObject(const UObject& Object) const
{
check(NavOctree.IsValid());
const FOctreeElementId* OctreeID = GetObjectsNavOctreeId(Object);
if (OctreeID != nullptr && OctreeID->IsValidId() == true)
{
return NavOctree->GetDataForID(*OctreeID);
}
return nullptr;
}
FNavigationRelevantData* UNavigationSystemV1::GetMutableDataForObject(const UObject& Object)
{
return const_cast<FNavigationRelevantData*>(GetDataForObject(Object));
}
void UNavigationSystemV1::UpdateActorInNavOctree(AActor& Actor)
{
if (IsNavigationSystemStatic())
{
return;
}
SCOPE_CYCLE_COUNTER(STAT_DebugNavOctree);
INavRelevantInterface* NavElement = Cast<INavRelevantInterface>(&Actor);
if (NavElement)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(Actor.GetWorld());
if (NavSys)
{
NavSys->UpdateNavOctreeElement(&Actor, NavElement, OctreeUpdate_Default);
}
}
}
void UNavigationSystemV1::UpdateComponentInNavOctree(UActorComponent& Comp)
{
SCOPE_CYCLE_COUNTER(STAT_DebugNavOctree);
if (ShouldUpdateNavOctreeOnComponentChange() == false)
{
return;
}
// special case for early out: use cached nav relevancy
if (Comp.bNavigationRelevant == true)
{
INavRelevantInterface* NavElement = Cast<INavRelevantInterface>(&Comp);
if (NavElement)
{
AActor* OwnerActor = Comp.GetOwner();
if (OwnerActor)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(OwnerActor->GetWorld());
if (NavSys)
{
if (OwnerActor->IsComponentRelevantForNavigation(&Comp))
{
NavSys->UpdateNavOctreeElement(&Comp, NavElement, OctreeUpdate_Default);
}
else
{
NavSys->UnregisterNavOctreeElement(&Comp, NavElement, OctreeUpdate_Default);
}
}
}
}
}
else if (Comp.CanEverAffectNavigation())
{
// could have been relevant before and not it isn't. Need to check if there's an octree element ID for it
INavRelevantInterface* NavElement = Cast<INavRelevantInterface>(&Comp);
if (NavElement)
{
AActor* OwnerActor = Comp.GetOwner();
if (OwnerActor)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(OwnerActor->GetWorld());
if (NavSys)
{
NavSys->UnregisterNavOctreeElement(&Comp, NavElement, OctreeUpdate_Default);
}
}
}
}
}
void UNavigationSystemV1::UpdateActorAndComponentsInNavOctree(AActor& Actor, bool bUpdateAttachedActors)
{
UpdateActorInNavOctree(Actor);
for (UActorComponent* Component : Actor.GetComponents())
{
if (Component)
{
UpdateComponentInNavOctree(*Component);
}
}
if (bUpdateAttachedActors)
{
UpdateAttachedActorsInNavOctree(Actor);
}
}
void UNavigationSystemV1::UpdateNavOctreeAfterMove(USceneComponent* Comp)
{
AActor* OwnerActor = Comp->GetOwner();
if (OwnerActor && OwnerActor->GetRootComponent() == Comp)
{
UpdateActorAndComponentsInNavOctree(*OwnerActor, true);
}
}
void UNavigationSystemV1::UpdateAttachedActorsInNavOctree(AActor& RootActor)
{
TArray<AActor*> UniqueAttachedActors;
UniqueAttachedActors.Add(&RootActor);
TArray<AActor*> TempAttachedActors;
for (int32 ActorIndex = 0; ActorIndex < UniqueAttachedActors.Num(); ++ActorIndex)
{
check(UniqueAttachedActors[ActorIndex]);
// find all attached actors
UniqueAttachedActors[ActorIndex]->GetAttachedActors(TempAttachedActors);
for (int32 AttachmentIndex = 0; AttachmentIndex < TempAttachedActors.Num(); ++AttachmentIndex)
{
// and store the ones we don't know about yet
UniqueAttachedActors.AddUnique(TempAttachedActors[AttachmentIndex]);
}
}
// skipping the first item since that's the root, and we just care about the attached actors
for (int32 ActorIndex = 1; ActorIndex < UniqueAttachedActors.Num(); ++ActorIndex)
{
UpdateActorAndComponentsInNavOctree(*UniqueAttachedActors[ActorIndex], /*bUpdateAttachedActors = */false);
}
}
void UNavigationSystemV1::UpdateNavOctreeBounds(AActor* Actor)
{
for (UActorComponent* Component : Actor->GetComponents())
{
INavRelevantInterface* NavElement = Cast<INavRelevantInterface>(Component);
if (NavElement)
{
NavElement->UpdateNavigationBounds();
}
}
}
void UNavigationSystemV1::ClearNavOctreeAll(AActor* Actor)
{
if (Actor)
{
OnActorUnregistered(Actor);
TInlineComponentArray<UActorComponent*> Components;
Actor->GetComponents(Components);
for (int32 Idx = 0; Idx < Components.Num(); Idx++)
{
OnComponentUnregistered(Components[Idx]);
}
}
}
void UNavigationSystemV1::UpdateNavOctreeElement(UObject* ElementOwner, INavRelevantInterface* ElementInterface, int32 UpdateFlags)
{
INC_DWORD_STAT(STAT_Navigation_UpdateNavOctree);
if (IsNavigationOctreeLocked())
{
UE_LOG(LogNavOctree, Log, TEXT("IGNORE(UpdateNavOctreeElement) %s"), *GetPathNameSafe(ElementOwner));
return;
}
else if (ElementOwner == nullptr)
{
return;
}
// grab existing octree data
FBox CurrentBounds;
int32 CurrentFlags;
const bool bAlreadyExists = GetNavOctreeElementData(*ElementOwner, CurrentFlags, CurrentBounds);
// don't invalidate pending requests
UpdateFlags |= OctreeUpdate_Refresh;
// always try to unregister, even if element owner doesn't exists in octree (parent nodes)
UnregisterNavOctreeElement(ElementOwner, ElementInterface, UpdateFlags);
const FSetElementId RequestId = RegisterNavOctreeElement(ElementOwner, ElementInterface, UpdateFlags);
// add original data to pending registration request
// so it could be dirtied properly when system receive unregister request while actor is still queued
if (RequestId.IsValidId())
{
FNavigationDirtyElement& UpdateInfo = PendingOctreeUpdates[RequestId];
UpdateInfo.PrevFlags = CurrentFlags;
if (UpdateInfo.PrevBounds.IsValid)
{
// Is we have something stored already we want to
// sum it up, since we care about the whole bounding
// box of changes that potentially took place
UpdateInfo.PrevBounds += CurrentBounds;
}
else
{
UpdateInfo.PrevBounds = CurrentBounds;
}
UpdateInfo.bHasPrevData = bAlreadyExists;
}
UpdateNavOctreeParentChain(ElementOwner, /*bSkipElementOwnerUpdate=*/ true);
}
void UNavigationSystemV1::UpdateNavOctreeParentChain(UObject* ElementOwner, bool bSkipElementOwnerUpdate)
{
const int32 UpdateFlags = OctreeUpdate_ParentChain | OctreeUpdate_Refresh;
TArray<FWeakObjectPtr> ChildNodes;
OctreeChildNodesMap.MultiFind(ElementOwner, ChildNodes);
if (ChildNodes.Num() == 0)
{
if (bSkipElementOwnerUpdate == false)
{
INavRelevantInterface* ElementInterface = Cast<INavRelevantInterface>(ElementOwner);
UpdateNavOctreeElement(ElementOwner, ElementInterface, UpdateFlags);
}
return;
}
INavRelevantInterface* ElementInterface = Cast<INavRelevantInterface>(ElementOwner);
TArray<INavRelevantInterface*> ChildNavInterfaces;
ChildNavInterfaces.AddZeroed(ChildNodes.Num());
for (int32 Idx = 0; Idx < ChildNodes.Num(); Idx++)
{
if (ChildNodes[Idx].IsValid())
{
UObject* ChildNodeOb = ChildNodes[Idx].Get();
ChildNavInterfaces[Idx] = Cast<INavRelevantInterface>(ChildNodeOb);
UnregisterNavOctreeElement(ChildNodeOb, ChildNavInterfaces[Idx], UpdateFlags);
}
}
if (bSkipElementOwnerUpdate == false)
{
UnregisterNavOctreeElement(ElementOwner, ElementInterface, UpdateFlags);
RegisterNavOctreeElement(ElementOwner, ElementInterface, UpdateFlags);
}
for (int32 Idx = 0; Idx < ChildNodes.Num(); Idx++)
{
if (ChildNodes[Idx].IsValid())
{
RegisterNavOctreeElement(ChildNodes[Idx].Get(), ChildNavInterfaces[Idx], UpdateFlags);
}
}
}
bool UNavigationSystemV1::UpdateNavOctreeElementBounds(UActorComponent* Comp, const FBox& NewBounds, const FBox& DirtyArea)
{
if (Comp == nullptr)
{
return false;
}
const FOctreeElementId* ElementId = GetObjectsNavOctreeId(*Comp);
if (ElementId && ElementId->IsValidId())
{
NavOctree->UpdateNode(*ElementId, NewBounds);
// Add dirty area
if (DirtyArea.IsValid)
{
ElementId = GetObjectsNavOctreeId(*Comp);
if (ElementId && ElementId->IsValidId())
{
FNavigationOctreeElement& ElementData = NavOctree->GetElementById(*ElementId);
AddDirtyArea(DirtyArea, ElementData.Data->GetDirtyFlag());
}
}
return true;
}
return false;
}
bool UNavigationSystemV1::ReplaceAreaInOctreeData(const UObject& Object, TSubclassOf<UNavArea> OldArea, TSubclassOf<UNavArea> NewArea, bool bReplaceChildClasses)
{
FNavigationRelevantData* Data = GetMutableDataForObject(Object);
if (Data == nullptr || Data->HasModifiers() == false)
{
return false;
}
for (FAreaNavModifier& AreaModifier : Data->Modifiers.GetMutableAreas())
{
if (AreaModifier.GetAreaClass() == OldArea
|| (bReplaceChildClasses && AreaModifier.GetAreaClass()->IsChildOf(OldArea)))
{
AreaModifier.SetAreaClass(NewArea);
}
}
for (FSimpleLinkNavModifier& SimpleLink : Data->Modifiers.GetSimpleLinks())
{
for (FNavigationLink& Link : SimpleLink.Links)
{
if (Link.GetAreaClass() == OldArea
|| (bReplaceChildClasses && Link.GetAreaClass()->IsChildOf(OldArea)))
{
Link.SetAreaClass(NewArea);
}
}
for (FNavigationSegmentLink& Link : SimpleLink.SegmentLinks)
{
if (Link.GetAreaClass() == OldArea
|| (bReplaceChildClasses && Link.GetAreaClass()->IsChildOf(OldArea)))
{
Link.SetAreaClass(NewArea);
}
}
}
for (FCustomLinkNavModifier& CustomLink : Data->Modifiers.GetCustomLinks())
{
ensureMsgf(false, TEXT("Not implemented yet"));
}
return true;
}
void UNavigationSystemV1::OnComponentRegistered(UActorComponent* Comp)
{
if (IsNavigationSystemStatic())
{
return;
}
SCOPE_CYCLE_COUNTER(STAT_DebugNavOctree);
INavRelevantInterface* NavInterface = Cast<INavRelevantInterface>(Comp);
if (NavInterface)
{
AActor* OwnerActor = Comp ? Comp->GetOwner() : NULL;
if (OwnerActor && OwnerActor->IsComponentRelevantForNavigation(Comp))
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(OwnerActor->GetWorld());
if (NavSys)
{
NavSys->RegisterNavOctreeElement(Comp, NavInterface, OctreeUpdate_Default);
}
}
}
}
void UNavigationSystemV1::OnComponentUnregistered(UActorComponent* Comp)
{
if (IsNavigationSystemStatic())
{
return;
}
SCOPE_CYCLE_COUNTER(STAT_DebugNavOctree);
INavRelevantInterface* NavInterface = Cast<INavRelevantInterface>(Comp);
if (NavInterface)
{
AActor* OwnerActor = Comp ? Comp->GetOwner() : NULL;
if (OwnerActor)
{
// skip IsComponentRelevantForNavigation check, it's only for adding new stuff
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(OwnerActor->GetWorld());
if (NavSys)
{
NavSys->UnregisterNavOctreeElement(Comp, NavInterface, OctreeUpdate_Default);
}
}
}
}
void UNavigationSystemV1::OnActorRegistered(AActor* Actor)
{
if (IsNavigationSystemStatic())
{
return;
}
SCOPE_CYCLE_COUNTER(STAT_DebugNavOctree);
INavRelevantInterface* NavInterface = Cast<INavRelevantInterface>(Actor);
if (NavInterface)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(Actor->GetWorld());
if (NavSys)
{
NavSys->RegisterNavOctreeElement(Actor, NavInterface, OctreeUpdate_Default);
}
}
}
void UNavigationSystemV1::OnActorUnregistered(AActor* Actor)
{
if (IsNavigationSystemStatic())
{
return;
}
SCOPE_CYCLE_COUNTER(STAT_DebugNavOctree);
INavRelevantInterface* NavInterface = Cast<INavRelevantInterface>(Actor);
if (NavInterface)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(Actor->GetWorld());
if (NavSys)
{
NavSys->UnregisterNavOctreeElement(Actor, NavInterface, OctreeUpdate_Default);
}
}
}
void UNavigationSystemV1::ConditionalPopulateNavOctreeActor(AActor& Actor)
{
UpdateActorAndComponentsInNavOctree(Actor);
}
void UNavigationSystemV1::FindElementsInNavOctree(const FBox& QueryBox, const FNavigationOctreeFilter& Filter, TArray<FNavigationOctreeElement>& Elements)
{
if (NavOctree.IsValid() == false)
{
UE_LOG(LogNavigation, Warning, TEXT("UNavigationSystemV1::FindElementsInNavOctree gets called while NavOctree is null"));
return;
}
for (FNavigationOctree::TConstElementBoxIterator<> It(*NavOctree, QueryBox); It.HasPendingElements(); It.Advance())
{
const FNavigationOctreeElement& Element = It.GetCurrentElement();
if (Element.IsMatchingFilter(Filter))
{
Elements.Add(Element);
}
}
}
void UNavigationSystemV1::ReleaseInitialBuildingLock()
{
RemoveNavigationBuildLock(ENavigationBuildLock::InitialLock);
}
void UNavigationSystemV1::InitializeLevelCollisions()
{
if (IsNavigationSystemStatic())
{
bInitialLevelsAdded = true;
return;
}
UWorld* World = GetWorld();
if (!bInitialLevelsAdded && FNavigationSystem::GetCurrent<UNavigationSystemV1>(World) == this)
{
// Process all visible levels
const auto& Levels = World->GetLevels();
for (ULevel* Level : Levels)
{
if (Level->bIsVisible)
{
AddLevelCollisionToOctree(Level);
}
}
bInitialLevelsAdded = true;
}
}
#if WITH_EDITOR
void UNavigationSystemV1::UpdateLevelCollision(ULevel* InLevel)
{
if (InLevel != NULL)
{
UWorld* World = GetWorld();
OnLevelRemovedFromWorld(InLevel, World);
OnLevelAddedToWorld(InLevel, World);
}
}
void UNavigationSystemV1::OnEditorModeChanged(FEdMode* Mode, bool IsEntering)
{
if (Mode == NULL)
{
return;
}
if (IsEntering == false && Mode->GetID() == FBuiltinEditorModes::EM_Geometry)
{
// check if any of modified brushes belongs to an ANavMeshBoundsVolume
FEdModeGeometry* GeometryMode = (FEdModeGeometry*)Mode;
for (auto GeomObjectIt = GeometryMode->GeomObjectItor(); GeomObjectIt; GeomObjectIt++)
{
ANavMeshBoundsVolume* Volume = Cast<ANavMeshBoundsVolume>((*GeomObjectIt)->GetActualBrush());
if (Volume)
{
OnNavigationBoundsUpdated(Volume);
}
}
}
}
#endif
void UNavigationSystemV1::OnNavigationBoundsUpdated(ANavMeshBoundsVolume* NavVolume)
{
if (NavVolume == nullptr || IsNavigationSystemStatic())
{
return;
}
FNavigationBoundsUpdateRequest UpdateRequest;
UpdateRequest.NavBounds.UniqueID = NavVolume->GetUniqueID();
UpdateRequest.NavBounds.AreaBox = NavVolume->GetComponentsBoundingBox(true);
UpdateRequest.NavBounds.Level = NavVolume->GetLevel();
UpdateRequest.NavBounds.SupportedAgents = NavVolume->SupportedAgents;
UpdateRequest.UpdateRequest = FNavigationBoundsUpdateRequest::Updated;
AddNavigationBoundsUpdateRequest(UpdateRequest);
}
void UNavigationSystemV1::OnNavigationBoundsAdded(ANavMeshBoundsVolume* NavVolume)
{
if (NavVolume == nullptr || IsNavigationSystemStatic())
{
return;
}
FNavigationBoundsUpdateRequest UpdateRequest;
UpdateRequest.NavBounds.UniqueID = NavVolume->GetUniqueID();
UpdateRequest.NavBounds.AreaBox = NavVolume->GetComponentsBoundingBox(true);
UpdateRequest.NavBounds.Level = NavVolume->GetLevel();
UpdateRequest.NavBounds.SupportedAgents = NavVolume->SupportedAgents;
UpdateRequest.UpdateRequest = FNavigationBoundsUpdateRequest::Added;
AddNavigationBoundsUpdateRequest(UpdateRequest);
}
void UNavigationSystemV1::OnNavigationBoundsRemoved(ANavMeshBoundsVolume* NavVolume)
{
if (NavVolume == nullptr || IsNavigationSystemStatic())
{
return;
}
FNavigationBoundsUpdateRequest UpdateRequest;
UpdateRequest.NavBounds.UniqueID = NavVolume->GetUniqueID();
UpdateRequest.NavBounds.AreaBox = NavVolume->GetComponentsBoundingBox(true);
UpdateRequest.NavBounds.Level = NavVolume->GetLevel();
UpdateRequest.NavBounds.SupportedAgents = NavVolume->SupportedAgents;
UpdateRequest.UpdateRequest = FNavigationBoundsUpdateRequest::Removed;
AddNavigationBoundsUpdateRequest(UpdateRequest);
}
void UNavigationSystemV1::AddNavigationBoundsUpdateRequest(const FNavigationBoundsUpdateRequest& UpdateRequest)
{
int32 ExistingIdx = PendingNavBoundsUpdates.IndexOfByPredicate([&](const FNavigationBoundsUpdateRequest& Element) {
return UpdateRequest.NavBounds.UniqueID == Element.NavBounds.UniqueID;
});
if (ExistingIdx != INDEX_NONE)
{
// catch the case where the bounds was removed and immediately re-added with the same bounds as before
// in that case, we can cancel any update at all
bool bCanCancelUpdate = false;
if (PendingNavBoundsUpdates[ExistingIdx].UpdateRequest == FNavigationBoundsUpdateRequest::Removed && UpdateRequest.UpdateRequest == FNavigationBoundsUpdateRequest::Added)
{
for (TSet<FNavigationBounds>::TConstIterator It(RegisteredNavBounds); It; ++It)
{
if (*It == UpdateRequest.NavBounds)
{
bCanCancelUpdate = true;
break;
}
}
}
if (bCanCancelUpdate)
{
PendingNavBoundsUpdates.RemoveAt(ExistingIdx);
}
else
{
// Overwrite any previous updates
PendingNavBoundsUpdates[ExistingIdx] = UpdateRequest;
}
}
else
{
PendingNavBoundsUpdates.Add(UpdateRequest);
}
}
void UNavigationSystemV1::PerformNavigationBoundsUpdate(const TArray<FNavigationBoundsUpdateRequest>& UpdateRequests)
{
// NOTE: we used to create missing nav data first, before updating nav bounds,
// but some nav data classes (like RecastNavMesh) may depend on the nav bounds
// being already known at the moment of creation or serialization, so it makes more
// sense to update bounds first
// Create list of areas that needs to be updated
TArray<FBox> UpdatedAreas;
for (const FNavigationBoundsUpdateRequest& Request : UpdateRequests)
{
FSetElementId ExistingElementId = RegisteredNavBounds.FindId(Request.NavBounds);
switch (Request.UpdateRequest)
{
case FNavigationBoundsUpdateRequest::Removed:
{
if (ExistingElementId.IsValidId())
{
UpdatedAreas.Add(RegisteredNavBounds[ExistingElementId].AreaBox);
RegisteredNavBounds.Remove(ExistingElementId);
}
}
break;
case FNavigationBoundsUpdateRequest::Added:
case FNavigationBoundsUpdateRequest::Updated:
{
if (ExistingElementId.IsValidId())
{
const FBox ExistingBox = RegisteredNavBounds[ExistingElementId].AreaBox;
const bool bSameArea = (Request.NavBounds.AreaBox == ExistingBox);
if (!bSameArea)
{
UpdatedAreas.Add(ExistingBox);
}
// always assign new bounds data, it may have different properties (like supported agents)
RegisteredNavBounds[ExistingElementId] = Request.NavBounds;
}
else
{
AddNavigationBounds(Request.NavBounds);
}
UpdatedAreas.Add(Request.NavBounds.AreaBox);
}
break;
}
}
if (!IsNavigationBuildingLocked())
{
if (UpdatedAreas.Num())
{
for (ANavigationData* NavData : NavDataSet)
{
if (NavData)
{
NavData->OnNavigationBoundsChanged();
}
}
}
// Propagate to generators areas that needs to be updated
AddDirtyAreas(UpdatedAreas, ENavigationDirtyFlag::All | ENavigationDirtyFlag::NavigationBounds);
}
// I'm not sure why we even do the following as part of this function
// @TODO investigate if we can extract it into a separate function and
// call it directly
if (NavDataSet.Num() == 0)
{
//TODO: will hitch when user places first navigation volume in the world
if (NavDataRegistrationQueue.Num() > 0)
{
ProcessRegistrationCandidates();
}
if (NavDataSet.Num() == 0 && bAutoCreateNavigationData == true)
{
SpawnMissingNavigationData();
ProcessRegistrationCandidates();
}
ConditionalPopulateNavOctree();
}
}
void UNavigationSystemV1::AddNavigationBounds(const FNavigationBounds& NewBounds)
{
RegisteredNavBounds.Add(NewBounds);
}
void UNavigationSystemV1::GatherNavigationBounds()
{
// Gather all available navigation bounds
RegisteredNavBounds.Empty();
for (TActorIterator<ANavMeshBoundsVolume> It(GetWorld()); It; ++It)
{
ANavMeshBoundsVolume* V = (*It);
if (V != nullptr && !V->IsPendingKill())
{
FNavigationBounds NavBounds;
NavBounds.UniqueID = V->GetUniqueID();
NavBounds.AreaBox = V->GetComponentsBoundingBox(true);
NavBounds.Level = V->GetLevel();
NavBounds.SupportedAgents = V->SupportedAgents;
AddNavigationBounds(NavBounds);
}
}
}
void UNavigationSystemV1::Build()
{
UWorld* World = GetWorld();
if (!World)
{
UE_LOG(LogNavigation, Error, TEXT("Unable to build navigation due to missing World pointer"));
return;
}
FNavigationSystem::DiscardNavigationDataChunks(*World);
const bool bHasWork = IsThereAnywhereToBuildNavigation();
const bool bLockedIgnoreEditor = (NavBuildingLockFlags & ~ENavigationBuildLock::NoUpdateInEditor) != 0;
if (!bHasWork || bLockedIgnoreEditor)
{
return;
}
const double BuildStartTime = FPlatformTime::Seconds();
if (bAutoCreateNavigationData == true
#if WITH_EDITOR
|| OperationMode == FNavigationSystemRunMode::EditorMode
#endif // WITH_EDITOR
)
{
SpawnMissingNavigationData();
}
// make sure freshly created navigation instances are registered before we try to build them
ProcessRegistrationCandidates();
// update invokers in case we're not updating navmesh automatically, in which case
// navigation generators wouldn't have up-to-date info.
if (bGenerateNavigationOnlyAroundNavigationInvokers)
{
UpdateInvokers();
}
// and now iterate through all registered and just start building them
RebuildAll();
// Block until build is finished
for (ANavigationData* NavData : NavDataSet)
{
if (NavData)
{
NavData->EnsureBuildCompletion();
}
}
#if !UE_BUILD_SHIPPING
// no longer report that navmesh needs to be rebuild
bDirtyAreasReportedWhileAccumulationLocked = false;
#endif // !UE_BUILD_SHIPPING
UE_LOG(LogNavigation, Display, TEXT("UNavigationSystemV1::Build total execution time: %.5f"), float(FPlatformTime::Seconds() - BuildStartTime));
}
void UNavigationSystemV1::CancelBuild()
{
for (ANavigationData* NavData : NavDataSet)
{
if (NavData)
{
if (NavData->GetGenerator())
{
NavData->GetGenerator()->CancelBuild();
}
}
}
}
void UNavigationSystemV1::SpawnMissingNavigationData()
{
const int32 SupportedAgentsCount = SupportedAgents.Num();
check(SupportedAgentsCount >= 0);
// Bit array might be a bit of an overkill here, but this function will be called very rarely
TBitArray<> AlreadyInstantiated(false, SupportedAgentsCount);
uint8 NumberFound = 0;
UWorld* NavWorld = GetWorld();
// 1. check whether any of required navigation data has already been instantiated
for (TActorIterator<ANavigationData> It(NavWorld); It && NumberFound < SupportedAgentsCount; ++It)
{
ANavigationData* Nav = (*It);
if (Nav != nullptr
&& Nav->IsPendingKill() == false
// mz@todo the 'is level in' condition is temporary
&& (Nav->GetTypedOuter<UWorld>() == NavWorld || NavWorld->GetLevels().Contains(Nav->GetLevel())))
{
// find out which one it is
for (int32 AgentIndex = 0; AgentIndex < SupportedAgentsCount; ++AgentIndex)
{
if (AlreadyInstantiated[AgentIndex] == false
&& Nav->GetClass() == SupportedAgents[AgentIndex].NavigationDataClass
&& Nav->DoesSupportAgent(SupportedAgents[AgentIndex]) == true)
{
AlreadyInstantiated[AgentIndex] = true;
++NumberFound;
break;
}
}
}
}
// 2. for any not already instantiated navigation data call creator functions
if (NumberFound < SupportedAgentsCount)
{
for (int32 AgentIndex = 0; AgentIndex < SupportedAgentsCount; ++AgentIndex)
{
const FNavDataConfig& NavConfig = SupportedAgents[AgentIndex];
if (AlreadyInstantiated[AgentIndex] == false && NavConfig.NavigationDataClass != nullptr)
{
bool bHandled = false;
const ANavigationData* NavDataCDO = NavConfig.NavigationDataClass->GetDefaultObject<ANavigationData>();
if (NavDataCDO == nullptr || !NavDataCDO->CanSpawnOnRebuild())
{
continue;
}
if (NavWorld->WorldType != EWorldType::Editor && NavDataCDO->GetRuntimeGenerationMode() == ERuntimeGenerationType::Static)
{
// if we're not in the editor, and specified navigation class is configured
// to be static, then we don't want to create an instance
UE_LOG(LogNavigation, Log, TEXT("Not spawning navigation data for %s since indivated NavigationData type is not configured for dynamic generation")
, *NavConfig.Name.ToString());
continue;
}
ANavigationData* Instance = CreateNavigationDataInstance(NavConfig);
if (Instance)
{
RequestRegistration(Instance);
}
else
{
UE_LOG(LogNavigation, Warning, TEXT("Was not able to create navigation data for SupportedAgent[%d]: %s"), AgentIndex, *NavConfig.Name.ToString());
}
}
}
}
if (MainNavData == nullptr || MainNavData->IsPendingKillPending())
{
// update
MainNavData = GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
}
}
ANavigationData* UNavigationSystemV1::CreateNavigationDataInstance(const FNavDataConfig& NavConfig)
{
UWorld* World = GetWorld();
check(World);
FActorSpawnParameters SpawnInfo;
if (bSpawnNavDataInNavBoundsLevel && RegisteredNavBounds.Num() > 0)
{
// pick the first valid level
for (const FNavigationBounds& Bounds : RegisteredNavBounds)
{
if (Bounds.Level.IsValid())
{
SpawnInfo.OverrideLevel = Bounds.Level.Get();
break;
}
}
}
if (SpawnInfo.OverrideLevel == nullptr)
{
SpawnInfo.OverrideLevel = World->PersistentLevel;
}
ANavigationData* Instance = World->SpawnActor<ANavigationData>(*NavConfig.NavigationDataClass, SpawnInfo);
if (Instance != NULL)
{
Instance->SetConfig(NavConfig);
if (NavConfig.Name != NAME_None)
{
FString StrName = FString::Printf(TEXT("%s-%s"), *(Instance->GetFName().GetPlainNameString()), *(NavConfig.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)
{
ANavigationData* ExistingNavigationData = Cast<ANavigationData>(ExistingObject);
if (ExistingNavigationData)
{
UnregisterNavData(ExistingNavigationData);
AgentToNavDataMap.Remove(ExistingNavigationData->GetConfig());
}
ExistingObject->Rename(NULL, NULL, REN_DontCreateRedirectors | REN_ForceGlobalUnique | REN_DoNotDirty | REN_NonTransactional | REN_ForceNoResetLoaders);
}
// Set descriptive name
Instance->Rename(*StrName, NULL, REN_DoNotDirty | REN_ForceNoResetLoaders);
#if WITH_EDITOR
if (World->WorldType == EWorldType::Editor)
{
const bool bMarkDirty = false;
Instance->SetActorLabel(StrName, bMarkDirty);
}
#endif // WITH_EDITOR
}
}
return Instance;
}
void UNavigationSystemV1::OnPIEStart()
{
bIsPIEActive = true;
// no updates for editor world while PIE is active
const UWorld* MyWorld = GetWorld();
if (MyWorld && !MyWorld->IsGameWorld())
{
bAsyncBuildPaused = true;
AddNavigationBuildLock(ENavigationBuildLock::NoUpdateInEditor);
}
}
void UNavigationSystemV1::OnPIEEnd()
{
bIsPIEActive = false;
const UWorld* MyWorld = GetWorld();
if (MyWorld && !MyWorld->IsGameWorld())
{
bAsyncBuildPaused = false;
// there's no need to request while navigation rebuilding just because PIE has ended
RemoveNavigationBuildLock(ENavigationBuildLock::NoUpdateInEditor, /*bSkipRebuildInEditor=*/true);
}
}
void UNavigationSystemV1::RemoveNavigationBuildLock(uint8 Flags, bool bSkipRebuildInEditor)
{
const bool bWasLocked = IsNavigationBuildingLocked();
NavBuildingLockFlags &= ~Flags;
const bool bIsLocked = IsNavigationBuildingLocked();
const bool bSkipRebuild = (OperationMode == FNavigationSystemRunMode::EditorMode) && bSkipRebuildInEditor;
if (bWasLocked && !bIsLocked && !bSkipRebuild)
{
RebuildAll();
}
}
void UNavigationSystemV1::RebuildAll(bool bIsLoadTime)
{
const bool bIsInGame = GetWorld()->IsGameWorld();
GatherNavigationBounds();
// make sure that octree is up to date
for (TSet<FNavigationDirtyElement>::TIterator It(PendingOctreeUpdates); It; ++It)
{
AddElementToNavOctree(*It);
}
PendingOctreeUpdates.Empty(32);
// discard all pending dirty areas, we are going to rebuild navmesh anyway
DirtyAreas.Reset();
PendingNavBoundsUpdates.Reset();
#if !UE_BUILD_SHIPPING
bDirtyAreasReportedWhileAccumulationLocked = false;
#endif // !UE_BUILD_SHIPPING
//
for (int32 NavDataIndex = 0; NavDataIndex < NavDataSet.Num(); ++NavDataIndex)
{
ANavigationData* NavData = NavDataSet[NavDataIndex];
if (NavData && (!bIsLoadTime || NavData->NeedsRebuildOnLoad()) && (!bIsInGame || NavData->SupportsRuntimeGeneration()))
{
NavData->RebuildAll();
}
}
}
bool UNavigationSystemV1::IsNavigationBuildInProgress(bool bCheckDirtyToo)
{
bool bRet = false;
if (NavDataSet.Num() == 0)
{
// @todo this is wrong! Should not need to create a navigation data instance in a "getter" like function
// update nav data. If none found this is the place to create one
GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
}
for (int32 NavDataIndex = 0; NavDataIndex < NavDataSet.Num(); ++NavDataIndex)
{
ANavigationData* NavData = NavDataSet[NavDataIndex];
if (NavData != NULL && NavData->GetGenerator() != NULL
&& NavData->GetGenerator()->IsBuildInProgress(bCheckDirtyToo) == true)
{
bRet = true;
break;
}
}
return bRet;
}
void UNavigationSystemV1::OnNavigationGenerationFinished(ANavigationData& NavData)
{
OnNavigationGenerationFinishedDelegate.Broadcast(&NavData);
}
int32 UNavigationSystemV1::GetNumRemainingBuildTasks() const
{
int32 NumTasks = 0;
for (ANavigationData* NavData : NavDataSet)
{
if (NavData && NavData->GetGenerator())
{
NumTasks+= NavData->GetGenerator()->GetNumRemaningBuildTasks();
}
}
return NumTasks;
}
int32 UNavigationSystemV1::GetNumRunningBuildTasks() const
{
int32 NumTasks = 0;
for (ANavigationData* NavData : NavDataSet)
{
if (NavData && NavData->GetGenerator())
{
NumTasks+= NavData->GetGenerator()->GetNumRunningBuildTasks();
}
}
return NumTasks;
}
void UNavigationSystemV1::OnLevelAddedToWorld(ULevel* InLevel, UWorld* InWorld)
{
if ((IsNavigationSystemStatic() == false) && (InWorld == GetWorld()))
{
AddLevelCollisionToOctree(InLevel);
if (!InLevel->IsPersistentLevel())
{
for (ANavigationData* NavData : NavDataSet)
{
if (NavData)
{
NavData->OnStreamingLevelAdded(InLevel, InWorld);
}
}
}
}
}
void UNavigationSystemV1::OnLevelRemovedFromWorld(ULevel* InLevel, UWorld* InWorld)
{
if ((IsNavigationSystemStatic() == false) && (InWorld == GetWorld()))
{
RemoveLevelCollisionFromOctree(InLevel);
if (InLevel && !InLevel->IsPersistentLevel())
{
for (int32 DataIndex = NavDataSet.Num() - 1; DataIndex >= 0; --DataIndex)
{
ANavigationData* NavData = NavDataSet[DataIndex];
if (NavData)
{
if (NavData->GetLevel() != InLevel)
{
NavData->OnStreamingLevelRemoved(InLevel, InWorld);
}
else
{
NavDataSet.RemoveAt(DataIndex, 1, /*bAllowShrinking=*/false);
}
}
}
}
}
}
void UNavigationSystemV1::AddLevelCollisionToOctree(ULevel* Level)
{
#if WITH_RECAST
if (Level && NavOctree.IsValid() &&
NavOctree->GetNavGeometryStoringMode() == FNavigationOctree::StoreNavGeometry)
{
const TArray<FVector>* LevelGeom = Level->GetStaticNavigableGeometry();
const FOctreeElementId* ElementId = GetObjectsNavOctreeId(*Level);
if (!ElementId && LevelGeom && LevelGeom->Num() > 0)
{
FNavigationOctreeElement BSPElem(*Level);
FRecastNavMeshGenerator::ExportVertexSoupGeometry(*LevelGeom, *BSPElem.Data);
const auto& Bounds = BSPElem.Data->Bounds;
if (!Bounds.GetExtent().IsNearlyZero())
{
NavOctree->AddNode(Level, NULL, Bounds, BSPElem);
AddDirtyArea(Bounds, ENavigationDirtyFlag::All);
UE_LOG(LogNavOctree, Log, TEXT("ADD %s"), *GetNameSafe(Level));
}
}
}
#endif// WITH_RECAST
}
void UNavigationSystemV1::RemoveLevelCollisionFromOctree(ULevel* Level)
{
if (Level && NavOctree.IsValid())
{
const FOctreeElementId* ElementId = GetObjectsNavOctreeId(*Level);
UE_LOG(LogNavOctree, Log, TEXT("UNREG %s %s"), *GetNameSafe(Level), ElementId ? TEXT("[exists]") : TEXT(""));
if (ElementId != NULL)
{
if (NavOctree->IsValidElementId(*ElementId))
{
// mark area occupied by given actor as dirty
FNavigationOctreeElement& ElementData = NavOctree->GetElementById(*ElementId);
AddDirtyArea(ElementData.Bounds.GetBox(), ENavigationDirtyFlag::All);
}
NavOctree->RemoveNode(*ElementId);
RemoveObjectsNavOctreeId(*Level);
}
}
}
void UNavigationSystemV1::OnPostLoadMap(UWorld*)
{
UE_LOG(LogNavigation, Log, TEXT("UNavigationSystemV1::OnPostLoadMap"));
// if map has been loaded and there are some navigation bounds volumes
// then create appropriate navigation structured
ANavigationData* NavData = GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
// Do this if there's currently no navigation
if (NavData == NULL && bAutoCreateNavigationData == true && IsThereAnywhereToBuildNavigation() == true)
{
NavData = GetDefaultNavDataInstance(FNavigationSystem::Create);
}
}
#if WITH_EDITOR
void UNavigationSystemV1::OnActorMoved(AActor* Actor)
{
if (Cast<ANavMeshBoundsVolume>(Actor) != NULL)
{
OnNavigationBoundsUpdated((ANavMeshBoundsVolume*)Actor);
}
}
#endif // WITH_EDITOR
void UNavigationSystemV1::OnNavigationDirtied(const FBox& Bounds)
{
AddDirtyArea(Bounds, ENavigationDirtyFlag::All);
}
#if WITH_HOT_RELOAD
void UNavigationSystemV1::OnHotReload(bool bWasTriggeredAutomatically)
{
if (RequiresNavOctree() && NavOctree.IsValid() == false)
{
ConditionalPopulateNavOctree();
if (bInitialBuildingLocked)
{
RemoveNavigationBuildLock(ENavigationBuildLock::InitialLock, /*bSkipRebuildInEditor=*/true);
}
}
}
#endif // WITH_HOT_RELOAD
void UNavigationSystemV1::CleanUp(FNavigationSystem::ECleanupMode Mode)
{
UE_LOG(LogNavigation, Log, TEXT("UNavigationSystemV1::CleanUp"));
#if WITH_EDITOR
if (GIsEditor && GEngine)
{
GEngine->OnActorMoved().RemoveAll(this);
}
#endif // WITH_EDITOR
FCoreUObjectDelegates::PostLoadMapWithWorld.RemoveAll(this);
UNavigationSystemV1::NavigationDirtyEvent.RemoveAll(this);
FWorldDelegates::LevelAddedToWorld.RemoveAll(this);
FWorldDelegates::LevelRemovedFromWorld.RemoveAll(this);
#if WITH_HOT_RELOAD
if (IHotReloadInterface* HotReloadSupport = FModuleManager::GetModulePtr<IHotReloadInterface>("HotReload"))
{
HotReloadSupport->OnHotReload().Remove(HotReloadDelegateHandle);
}
#endif
DestroyNavOctree();
SetCrowdManager(NULL);
NavDataSet.Reset();
// reset unique link Id for new map
const UWorld* MyWorld = (Mode == FNavigationSystem::ECleanupMode::CleanupWithWorld) ? GetWorld() : NULL;
if (MyWorld && (MyWorld->WorldType == EWorldType::Game || MyWorld->WorldType == EWorldType::Editor))
{
INavLinkCustomInterface::NextUniqueId = 1;
}
}
void UNavigationSystemV1::DestroyNavOctree()
{
if (NavOctree.IsValid())
{
NavOctree->Destroy();
NavOctree = NULL;
}
ObjectToOctreeId.Empty();
}
bool UNavigationSystemV1::RequiresNavOctree() const
{
UWorld* World = GetWorld();
check(World);
// We always require navoctree in editor worlds
if (!World->IsGameWorld())
{
return true;
}
for (ANavigationData* NavData : NavDataSet)
{
if (NavData && NavData->SupportsRuntimeGeneration())
{
return true;
}
}
return false;
}
ERuntimeGenerationType UNavigationSystemV1::GetRuntimeGenerationType() const
{
UWorld* World = GetWorld();
check(World);
// We always use ERuntimeGenerationType::Dynamic in editor worlds
if (!World->IsGameWorld())
{
return ERuntimeGenerationType::Dynamic;
}
ERuntimeGenerationType RuntimeGenerationType = ERuntimeGenerationType::Static;
for (ANavigationData* NavData : NavDataSet)
{
if (NavData && NavData->GetRuntimeGenerationMode() > RuntimeGenerationType)
{
RuntimeGenerationType = NavData->GetRuntimeGenerationMode();
}
}
return RuntimeGenerationType;
}
//----------------------------------------------------------------------//
// Blueprint functions
//----------------------------------------------------------------------//
UNavigationSystemV1* UNavigationSystemV1::GetNavigationSystem(UObject* WorldContextObject)
{
return GetCurrent(WorldContextObject);
}
bool UNavigationSystemV1::K2_ProjectPointToNavigation(UObject* WorldContextObject, const FVector& Point, FVector& ProjectedLocation, ANavigationData* NavData, TSubclassOf<UNavigationQueryFilter> FilterClass, const FVector QueryExtent)
{
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
ProjectedLocation = Point;
bool bResult = false;
if (NavSys)
{
FNavLocation OutNavLocation;
ANavigationData* UseNavData = NavData ? NavData : NavSys->GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
if (UseNavData)
{
bResult = NavSys->ProjectPointToNavigation(Point, OutNavLocation, QueryExtent, NavData
, UNavigationQueryFilter::GetQueryFilter(*UseNavData, WorldContextObject, FilterClass));
ProjectedLocation = OutNavLocation.Location;
}
}
return bResult;
}
bool UNavigationSystemV1::K2_GetRandomReachablePointInRadius(UObject* WorldContextObject, const FVector& Origin, FVector& RandomLocation, float Radius, ANavigationData* NavData, TSubclassOf<UNavigationQueryFilter> FilterClass)
{
FNavLocation RandomPoint(Origin);
bool bResult = false;
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys)
{
ANavigationData* UseNavData = NavData ? NavData : NavSys->GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
if (UseNavData)
{
bResult = NavSys->GetRandomReachablePointInRadius(Origin, Radius, RandomPoint, UseNavData, UNavigationQueryFilter::GetQueryFilter(*UseNavData, WorldContextObject, FilterClass));
RandomLocation = RandomPoint.Location;
}
}
return bResult;
}
bool UNavigationSystemV1::K2_GetRandomLocationInNavigableRadius(UObject* WorldContextObject, const FVector& Origin, FVector& RandomLocation, float Radius, ANavigationData* NavData, TSubclassOf<UNavigationQueryFilter> FilterClass)
{
FNavLocation RandomPoint(Origin);
bool bResult = false;
RandomLocation = Origin;
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys)
{
ANavigationData* UseNavData = NavData ? NavData : NavSys->GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
if (UseNavData)
{
if (NavSys->GetRandomPointInNavigableRadius(Origin, Radius, RandomPoint, UseNavData, UNavigationQueryFilter::GetQueryFilter(*UseNavData, WorldContextObject, FilterClass)))
{
bResult = true;
RandomLocation = RandomPoint.Location;
}
}
}
return bResult;
}
ENavigationQueryResult::Type UNavigationSystemV1::GetPathCost(UObject* WorldContextObject, const FVector& PathStart, const FVector& PathEnd, float& OutPathCost, ANavigationData* NavData, TSubclassOf<UNavigationQueryFilter> FilterClass)
{
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys)
{
ANavigationData* UseNavData = NavData ? NavData : NavSys->GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
if (UseNavData)
{
return NavSys->GetPathCost(PathStart, PathEnd, OutPathCost, UseNavData, UNavigationQueryFilter::GetQueryFilter(*UseNavData, WorldContextObject, FilterClass));
}
}
return ENavigationQueryResult::Error;
}
ENavigationQueryResult::Type UNavigationSystemV1::GetPathLength(UObject* WorldContextObject, const FVector& PathStart, const FVector& PathEnd, float& OutPathLength, ANavigationData* NavData, TSubclassOf<UNavigationQueryFilter> FilterClass)
{
float PathLength = 0.f;
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys)
{
ANavigationData* UseNavData = NavData ? NavData : NavSys->GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
if (UseNavData)
{
return NavSys->GetPathLength(PathStart, PathEnd, OutPathLength, UseNavData, UNavigationQueryFilter::GetQueryFilter(*UseNavData, WorldContextObject, FilterClass));
}
}
return ENavigationQueryResult::Error;
}
bool UNavigationSystemV1::IsNavigationBeingBuilt(UObject* WorldContextObject)
{
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys && !NavSys->IsNavigationBuildingPermanentlyLocked())
{
return NavSys->HasDirtyAreasQueued() || NavSys->IsNavigationBuildInProgress();
}
return false;
}
bool UNavigationSystemV1::IsNavigationBeingBuiltOrLocked(UObject* WorldContextObject)
{
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys)
{
return NavSys->IsNavigationBuildingLocked() || NavSys->HasDirtyAreasQueued() || NavSys->IsNavigationBuildInProgress();
}
return false;
}
bool UNavigationSystemV1::K2_ReplaceAreaInOctreeData(const UObject* Object, TSubclassOf<UNavArea> OldArea, TSubclassOf<UNavArea> NewArea)
{
return Object ? ReplaceAreaInOctreeData(*Object, OldArea, NewArea) : false;
}
//----------------------------------------------------------------------//
// HACKS!!!
//----------------------------------------------------------------------//
bool UNavigationSystemV1::ShouldGeneratorRun(const FNavDataGenerator* Generator) const
{
if (Generator != NULL && (IsNavigationSystemStatic() == false))
{
for (int32 NavDataIndex = 0; NavDataIndex < NavDataSet.Num(); ++NavDataIndex)
{
ANavigationData* NavData = NavDataSet[NavDataIndex];
if (NavData != NULL && NavData->GetGenerator() == Generator)
{
return true;
}
}
}
return false;
}
bool UNavigationSystemV1::HandleCycleNavDrawnCommand( const TCHAR* Cmd, FOutputDevice& Ar )
{
CycleNavigationDataDrawn();
return true;
}
bool UNavigationSystemV1::HandleCountNavMemCommand()
{
UE_LOG(LogNavigation, Warning, TEXT("Logging NavigationSystem memory usage:"));
if (NavOctree.IsValid())
{
UE_LOG(LogNavigation, Warning, TEXT("NavOctree memory: %d"), NavOctree->GetSizeBytes());
}
for (int32 NavDataIndex = 0; NavDataIndex < NavDataSet.Num(); ++NavDataIndex)
{
ANavigationData* NavData = NavDataSet[NavDataIndex];
if (NavData != NULL)
{
NavData->LogMemUsed();
}
}
return true;
}
//----------------------------------------------------------------------//
// Commands
//----------------------------------------------------------------------//
bool FNavigationSystemExec::Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(InWorld);
if (NavSys && NavSys->NavDataSet.Num() > 0)
{
if (FParse::Command(&Cmd, TEXT("CYCLENAVDRAWN")))
{
NavSys->HandleCycleNavDrawnCommand( Cmd, Ar );
// not returning true to enable all navigation systems to cycle their own data
return false;
}
else if (FParse::Command(&Cmd, TEXT("CountNavMem")))
{
NavSys->HandleCountNavMemCommand();
return false;
}
/** Builds the navigation mesh (or rebuilds it). **/
else if (FParse::Command(&Cmd, TEXT("RebuildNavigation")))
{
NavSys->Build();
}
}
return false;
}
void UNavigationSystemV1::CycleNavigationDataDrawn()
{
++CurrentlyDrawnNavDataIndex;
if (CurrentlyDrawnNavDataIndex >= NavDataSet.Num())
{
CurrentlyDrawnNavDataIndex = INDEX_NONE;
}
for (int32 NavDataIndex = 0; NavDataIndex < NavDataSet.Num(); ++NavDataIndex)
{
ANavigationData* NavData = NavDataSet[NavDataIndex];
if (NavData != NULL)
{
const bool bNewEnabledDrawing = (CurrentlyDrawnNavDataIndex == INDEX_NONE) || (NavDataIndex == CurrentlyDrawnNavDataIndex);
NavData->SetNavRenderingEnabled(bNewEnabledDrawing);
}
}
}
bool UNavigationSystemV1::IsNavigationDirty() const
{
#if !UE_BUILD_SHIPPING
if (bCanAccumulateDirtyAreas == false && bDirtyAreasReportedWhileAccumulationLocked)
{
return true;
}
#endif // !UE_BUILD_SHIPPING
for (int32 NavDataIndex=0; NavDataIndex < NavDataSet.Num(); ++NavDataIndex)
{
if (NavDataSet[NavDataIndex] && NavDataSet[NavDataIndex]->NeedsRebuild())
{
return true;
}
}
return false;
}
bool UNavigationSystemV1::CanRebuildDirtyNavigation() const
{
const bool bIsInGame = GetWorld()->IsGameWorld();
for (const ANavigationData* NavData : NavDataSet)
{
if (NavData)
{
const bool bIsDirty = NavData->NeedsRebuild();
const bool bCanRebuild = !bIsInGame || NavData->SupportsRuntimeGeneration();
if (bIsDirty && !bCanRebuild)
{
return false;
}
}
}
return true;
}
bool UNavigationSystemV1::DoesPathIntersectBox(const FNavigationPath* Path, const FBox& Box, uint32 StartingIndex, FVector* AgentExtent)
{
return Path != NULL && Path->DoesIntersectBox(Box, StartingIndex, NULL, AgentExtent);
}
bool UNavigationSystemV1::DoesPathIntersectBox(const FNavigationPath* Path, const FBox& Box, const FVector& AgentLocation, uint32 StartingIndex, FVector* AgentExtent)
{
return Path != NULL && Path->DoesIntersectBox(Box, AgentLocation, StartingIndex, NULL, AgentExtent);
}
void UNavigationSystemV1::SetMaxSimultaneousTileGenerationJobsCount(int32 MaxNumberOfJobs)
{
#if WITH_RECAST
for (auto NavigationData : NavDataSet)
{
ARecastNavMesh* RecastNavMesh = Cast<ARecastNavMesh>(NavigationData);
if (RecastNavMesh)
{
RecastNavMesh->SetMaxSimultaneousTileGenerationJobsCount(MaxNumberOfJobs);
}
}
#endif
}
void UNavigationSystemV1::ResetMaxSimultaneousTileGenerationJobsCount()
{
#if WITH_RECAST
for (auto NavigationData : NavDataSet)
{
ARecastNavMesh* RecastNavMesh = Cast<ARecastNavMesh>(NavigationData);
if (RecastNavMesh)
{
const ARecastNavMesh* CDO = RecastNavMesh->GetClass()->GetDefaultObject<ARecastNavMesh>();
RecastNavMesh->SetMaxSimultaneousTileGenerationJobsCount(CDO->MaxSimultaneousTileGenerationJobsCount);
}
}
#endif
}
//----------------------------------------------------------------------//
// Active tiles
//----------------------------------------------------------------------//
void UNavigationSystemV1::RegisterNavigationInvoker(AActor& Invoker, float TileGenerationRadius, float TileRemovalRadius)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(Invoker.GetWorld());
if (NavSys)
{
NavSys->RegisterInvoker(Invoker, TileGenerationRadius, TileRemovalRadius);
}
}
void UNavigationSystemV1::UnregisterNavigationInvoker(AActor& Invoker)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(Invoker.GetWorld());
if (NavSys)
{
NavSys->UnregisterInvoker(Invoker);
}
}
void UNavigationSystemV1::SetGeometryGatheringMode(ENavDataGatheringModeConfig NewMode)
{
DataGatheringMode = NewMode;
if (NavOctree.IsValid())
{
NavOctree->SetDataGatheringMode(DataGatheringMode);
}
}
void UNavigationSystemV1::RegisterInvoker(AActor& Invoker, float TileGenerationRadius, float TileRemovalRadius)
{
UE_CVLOG(bGenerateNavigationOnlyAroundNavigationInvokers == false, this, LogNavigation, Warning
, TEXT("Trying to register %s as enforcer, but NavigationSystem is not set up for enforcer-centric generation. See GenerateNavigationOnlyAroundNavigationInvokers in NavigationSystem's properties")
, *Invoker.GetName());
TileGenerationRadius = FMath::Clamp(TileGenerationRadius, 0.f, BIG_NUMBER);
TileRemovalRadius = FMath::Clamp(TileRemovalRadius, TileGenerationRadius, BIG_NUMBER);
FNavigationInvoker& Data = Invokers.FindOrAdd(&Invoker);
Data.Actor = &Invoker;
Data.GenerationRadius = TileGenerationRadius;
Data.RemovalRadius = TileRemovalRadius;
UE_VLOG_CYLINDER(this, LogNavigation, Log, Invoker.GetActorLocation(), Invoker.GetActorLocation() + FVector(0, 0, 20), TileGenerationRadius, FColorList::LimeGreen
, TEXT("%s %.0f %.0f"), *Invoker.GetName(), TileGenerationRadius, TileRemovalRadius);
UE_VLOG_CYLINDER(this, LogNavigation, Log, Invoker.GetActorLocation(), Invoker.GetActorLocation() + FVector(0, 0, 20), TileRemovalRadius, FColorList::IndianRed, TEXT(""));
}
void UNavigationSystemV1::UnregisterInvoker(AActor& Invoker)
{
UE_VLOG(this, LogNavigation, Log, TEXT("Removing %s from enforcers list"), *Invoker.GetName());
Invokers.Remove(&Invoker);
}
void UNavigationSystemV1::UpdateInvokers()
{
UWorld* World = GetWorld();
const float CurrentTime = World->GetTimeSeconds();
if (CurrentTime >= NextInvokersUpdateTime)
{
InvokerLocations.Reset();
if (Invokers.Num() > 0)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_NavSys_Clusterize);
const double StartTime = FPlatformTime::Seconds();
InvokerLocations.Reserve(Invokers.Num());
for (auto ItemIterator = Invokers.CreateIterator(); ItemIterator; ++ItemIterator)
{
AActor* Actor = ItemIterator->Value.Actor.Get();
if (Actor != nullptr
#if WITH_EDITOR
// Would like to ignore objects in transactional buffer here, but there's no flag for it
//&& (GIsEditor == false || Item.Actor->HasAnyFlags(RF_Transactional | RF_PendingKill) == false)
#endif //WITH_EDITOR
)
{
InvokerLocations.Add(FNavigationInvokerRaw(Actor->GetActorLocation(), ItemIterator->Value.GenerationRadius, ItemIterator->Value.RemovalRadius));
}
else
{
ItemIterator.RemoveCurrent();
}
}
#if ENABLE_VISUAL_LOG
const double CachingFinishTime = FPlatformTime::Seconds();
UE_VLOG(this, LogNavigation, Log, TEXT("Caching time %fms"), (CachingFinishTime - StartTime) * 1000);
for (const auto& InvokerData : InvokerLocations)
{
UE_VLOG_CYLINDER(this, LogNavigation, Log, InvokerData.Location, InvokerData.Location + FVector(0, 0, 20), InvokerData.RadiusMax, FColorList::Blue, TEXT(""));
UE_VLOG_CYLINDER(this, LogNavigation, Log, InvokerData.Location, InvokerData.Location + FVector(0, 0, 20), InvokerData.RadiusMin, FColorList::CadetBlue, TEXT(""));
}
#endif // ENABLE_VISUAL_LOG
}
#if WITH_RECAST
const double UpdateStartTime = FPlatformTime::Seconds();
for (TActorIterator<ARecastNavMesh> It(GetWorld()); It; ++It)
{
It->UpdateActiveTiles(InvokerLocations);
}
const double UpdateEndTime = FPlatformTime::Seconds();
UE_VLOG(this, LogNavigation, Log, TEXT("Marking tiles to update %fms (%d invokers)"), (UpdateEndTime - UpdateStartTime) * 1000, InvokerLocations.Num());
#endif
// once per second
NextInvokersUpdateTime = CurrentTime + ActiveTilesUpdateInterval;
}
}
void UNavigationSystemV1::RegisterNavigationInvoker(AActor* Invoker, float TileGenerationRadius, float TileRemovalRadius)
{
if (Invoker != nullptr)
{
RegisterInvoker(*Invoker, TileGenerationRadius, TileRemovalRadius);
}
}
void UNavigationSystemV1::UnregisterNavigationInvoker(AActor* Invoker)
{
if (Invoker != nullptr)
{
UnregisterInvoker(*Invoker);
}
}
//----------------------------------------------------------------------//
// DEPRECATED
//----------------------------------------------------------------------//
FVector UNavigationSystemV1::ProjectPointToNavigation(UObject* WorldContextObject, const FVector& Point, ANavigationData* NavData, TSubclassOf<UNavigationQueryFilter> FilterClass, const FVector QueryExtent)
{
FNavLocation ProjectedPoint(Point);
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys)
{
ANavigationData* UseNavData = NavData ? NavData : NavSys->GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
if (UseNavData)
{
NavSys->ProjectPointToNavigation(Point, ProjectedPoint, QueryExtent.IsNearlyZero() ? INVALID_NAVEXTENT : QueryExtent, UseNavData,
UNavigationQueryFilter::GetQueryFilter(*UseNavData, WorldContextObject, FilterClass));
}
}
return ProjectedPoint.Location;
}
FVector UNavigationSystemV1::GetRandomReachablePointInRadius(UObject* WorldContextObject, const FVector& Origin, float Radius, ANavigationData* NavData, TSubclassOf<UNavigationQueryFilter> FilterClass)
{
FNavLocation RandomPoint;
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys)
{
ANavigationData* UseNavData = NavData ? NavData : NavSys->GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
if (UseNavData)
{
NavSys->GetRandomReachablePointInRadius(Origin, Radius, RandomPoint, UseNavData, UNavigationQueryFilter::GetQueryFilter(*UseNavData, WorldContextObject, FilterClass));
}
}
return RandomPoint.Location;
}
FVector UNavigationSystemV1::GetRandomPointInNavigableRadius(UObject* WorldContextObject, const FVector& Origin, float Radius, ANavigationData* NavData, TSubclassOf<UNavigationQueryFilter> FilterClass)
{
FNavLocation RandomPoint;
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys)
{
ANavigationData* UseNavData = NavData ? NavData : NavSys->GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
if (UseNavData)
{
NavSys->GetRandomPointInNavigableRadius(Origin, Radius, RandomPoint, UseNavData, UNavigationQueryFilter::GetQueryFilter(*UseNavData, WorldContextObject, FilterClass));
}
}
return RandomPoint.Location;
}
bool UNavigationSystemV1::K2_GetRandomPointInNavigableRadius(UObject* WorldContextObject, const FVector& Origin, FVector& RandomLocation, float Radius, ANavigationData* NavData, TSubclassOf<UNavigationQueryFilter> FilterClass)
{
return K2_GetRandomLocationInNavigableRadius(WorldContextObject, Origin, RandomLocation, Radius, NavData, FilterClass);
}
void UNavigationSystemV1::SimpleMoveToActor(AController* Controller, const AActor* Goal)
{
UE_LOG(LogNavigation, Error, TEXT("SimpleMoveToActor is deprecated. Use UAIBlueprintHelperLibrary::SimpleMoveToActor instead"));
}
void UNavigationSystemV1::SimpleMoveToLocation(AController* Controller, const FVector& Goal)
{
UE_LOG(LogNavigation, Error, TEXT("SimpleMoveToLocation is deprecated. Use UAIBlueprintHelperLibrary::SimpleMoveToLocation instead"));
}
//----------------------------------------------------------------------//
// NEW STUFF!
//----------------------------------------------------------------------//
void UNavigationSystemV1::VerifyNavigationRenderingComponents(const bool bShow)
{
// make sure nav mesh has a rendering component
ANavigationData* const NavData = GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
if (NavData && NavData->RenderingComp == nullptr)
{
NavData->RenderingComp = NavData->ConstructRenderingComponent();
if (NavData->RenderingComp)
{
NavData->RenderingComp->SetVisibility(bShow);
NavData->RenderingComp->RegisterComponent();
}
}
if (NavData == nullptr)
{
UE_LOG(LogNavigation, Warning, TEXT("No NavData found when calling UNavigationSystemV1::VerifyNavigationRenderingComponents()"));
}
}
#if !UE_BUILD_SHIPPING
void UNavigationSystemV1::GetOnScreenMessages(TMultiMap<FCoreDelegates::EOnScreenMessageSeverity, FText>& OutMessages)
{
// check navmesh
#if WITH_EDITOR
const bool bIsNavigationAutoUpdateEnabled = GetIsAutoUpdateEnabled();
#else
const bool bIsNavigationAutoUpdateEnabled = true;
#endif
if (IsNavigationDirty()
&& ((OperationMode == FNavigationSystemRunMode::EditorMode && !bIsNavigationAutoUpdateEnabled)
|| !SupportsNavigationGeneration() || !CanRebuildDirtyNavigation()))
{
OutMessages.Add(FCoreDelegates::EOnScreenMessageSeverity::Error
, LOCTEXT("NAVMESHERROR", "NAVMESH NEEDS TO BE REBUILT"));
}
}
#endif // !UE_BUILD_SHIPPING
INavigationDataInterface* UNavigationSystemV1::GetNavDataForActor(const AActor& Actor)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(Actor.GetWorld());
ANavigationData* NavData = nullptr;
const INavAgentInterface* AsNavAgent = CastChecked<INavAgentInterface>(&Actor);
if (AsNavAgent)
{
const FNavAgentProperties& AgentProps = AsNavAgent->GetNavAgentPropertiesRef();
NavData = NavSys->GetNavDataForProps(AgentProps);
}
if (NavData == nullptr)
{
NavData = NavSys->GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
}
// Only RecastNavMesh supported
return (INavigationDataInterface*)(Cast<ARecastNavMesh>(NavData));
}
int UNavigationSystemV1::GetNavigationBoundsForNavData(const ANavigationData& NavData, TArray<FBox>& OutBounds) const
{
const int InitialBoundsCount = OutBounds.Num();
OutBounds.Reserve(InitialBoundsCount + RegisteredNavBounds.Num());
const int32 AgentIndex = GetSupportedAgentIndex(&NavData);
for (const FNavigationBounds& NavigationBounds : RegisteredNavBounds)
{
if (NavigationBounds.SupportedAgents.Contains(AgentIndex))
{
OutBounds.Add(NavigationBounds.AreaBox);
}
}
return OutBounds.Num() - InitialBoundsCount;
}
const FNavDataConfig& UNavigationSystemV1::GetDefaultSupportedAgent()
{
static const FNavDataConfig DefaultAgent;
const UNavigationSystemV1* NavSysCDO = GetDefault<UNavigationSystemV1>();
check(NavSysCDO);
return NavSysCDO->SupportedAgents.Num() > 0
? NavSysCDO->GetDefaultSupportedAgentConfig()
: DefaultAgent;
}
void UNavigationSystemV1::OverrideSupportedAgents(const TArray<FNavDataConfig>& NewSupportedAgents)
{
UE_CLOG(bWorldInitDone, LogNavigation, Warning, TEXT("Trying to override NavigationSystem\'s SupportedAgents past the World\'s initialization"));
SupportedAgents = NewSupportedAgents;
if (SupportedAgents.Num() == 0)
{
SupportedAgents.Add(FNavDataConfig(FNavigationSystem::FallbackAgentRadius, FNavigationSystem::FallbackAgentHeight));
}
}
void UNavigationSystemV1::Configure(const UNavigationSystemConfig& Config)
{
}
//----------------------------------------------------------------------//
// UNavigationSystemModuleConfig
//----------------------------------------------------------------------//
UNavigationSystemModuleConfig::UNavigationSystemModuleConfig(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
void UNavigationSystemModuleConfig::PostInitProperties()
{
Super::PostInitProperties();
const UNavigationSystemV1* NavSysCDO = GetDefault<UNavigationSystemV1>();
if (NavSysCDO)
{
UpdateWithNavSysCDO(*NavSysCDO);
}
}
void UNavigationSystemModuleConfig::UpdateWithNavSysCDO(const UNavigationSystemV1& NavSysCDO)
{
UClass* MyClass = NavigationSystemClass.ResolveClass();
if (MyClass != nullptr && MyClass->IsChildOf(NavSysCDO.GetClass()))
{
bStrictlyStatic = NavSysCDO.bStaticRuntimeNavigation;
bCreateOnClient = NavSysCDO.bAllowClientSideNavigation;
bAutoSpawnMissingNavData = NavSysCDO.bAutoCreateNavigationData;
bSpawnNavDataInNavBoundsLevel = NavSysCDO.bSpawnNavDataInNavBoundsLevel;
}
}
UNavigationSystemBase* UNavigationSystemModuleConfig::CreateAndConfigureNavigationSystem(UWorld& World) const
{
if (bCreateOnClient == false && World.GetNetMode() == NM_Client)
{
return nullptr;
}
UNavigationSystemBase* NewNavSys = Super::CreateAndConfigureNavigationSystem(World);
UNavigationSystemV1* NavSysInstance = Cast<UNavigationSystemV1>(NewNavSys);
UE_CLOG(NavSysInstance == nullptr && NewNavSys != nullptr, LogNavigation, Error
, TEXT("Unable to spawn navsys instance of class %s - unable to cast to UNavigationSystemV1")
, *NavigationSystemClass.GetAssetName()
);
if (NavSysInstance)
{
NavSysInstance->bAutoCreateNavigationData = bAutoSpawnMissingNavData;
NavSysInstance->bSpawnNavDataInNavBoundsLevel = bSpawnNavDataInNavBoundsLevel;
if (bStrictlyStatic)
{
NavSysInstance->ConfigureAsStatic();
}
}
return NavSysInstance;
}
#if WITH_EDITOR
void UNavigationSystemModuleConfig::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
static const FName NAME_NavigationSystemClass = GET_MEMBER_NAME_CHECKED(UNavigationSystemConfig, NavigationSystemClass);
Super::PostEditChangeProperty(PropertyChangedEvent);
if (PropertyChangedEvent.Property)
{
FName PropName = PropertyChangedEvent.Property->GetFName();
if (PropName == NAME_NavigationSystemClass)
{
if (NavigationSystemClass.IsValid() == false)
{
NavigationSystemClass = *GEngine->NavigationSystemClass;
}
else
{
NavigationSystemClass.TryLoad();
TSubclassOf<UNavigationSystemBase> NavSysClass = NavigationSystemClass.ResolveClass();
const UNavigationSystemV1* NavSysCDO = *NavSysClass
? NavSysClass->GetDefaultObject<UNavigationSystemV1>()
: (UNavigationSystemV1*)nullptr;
if (NavSysCDO)
{
UpdateWithNavSysCDO(*NavSysCDO);
}
}
}
}
}
#endif // WITH_EDITOR
#undef LOCTEXT_NAMESPACE