// 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" 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_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(World->GetNavigationSystem()); return NavSys && NavSys->ShouldLoadNavigationOnClient(&NavData); } else if (GEngine->NavigationSystemClass && GEngine->NavigationSystemClass->IsChildOf()) { const UNavigationSystemV1* NavSysCDO = GEngine->NavigationSystemClass->GetDefaultObject(); return NavSysCDO && NavSysCDO->ShouldLoadNavigationOnClient(&NavData); } return false; } bool ShouldDiscardSubLevelNavData(ANavigationData& NavData) { const UWorld* World = NavData.GetWorld(); if (World && World->GetNavigationSystem()) { const UNavigationSystemV1* NavSys = Cast(World->GetNavigationSystem()); if (NavSys) { return NavSys->ShouldDiscardSubLevelNavData(&NavData); } } const UNavigationSystemV1* NavSysCDO = (*GEngine->NavigationSystemClass != nullptr) ? (GEngine->NavigationSystemClass->GetDefaultObject()) : (const UNavigationSystemV1*)nullptr; return NavSysCDO == nullptr || NavSysCDO->ShouldDiscardSubLevelNavData(&NavData); } void MakeAllComponentsNeverAffectNav(AActor& Actor) { const TSet 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(&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 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 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(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(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(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(&World); if (NavSys) { NavSys->VerifyNavigationRenderingComponents(bShow); } }); UNavigationSystemBase::BuildDelegate().BindLambda([](UWorld& World) { UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(&World); if (NavSys) { NavSys->Build(); } }); #if WITH_EDITOR UNavigationSystemBase::OnPIEStartDelegate().BindLambda([](UWorld& World) { UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(&World); if (NavSys) { NavSys->OnPIEStart(); } }); UNavigationSystemBase::OnPIEEndDelegate().BindLambda([](UWorld& World) { UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(&World); if (NavSys) { NavSys->OnPIEEnd(); } }); UNavigationSystemBase::UpdateLevelCollisionDelegate().BindLambda([](ULevel& Level) { UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(&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(&World); if (NavSys) { NavSys->AddNavigationUpdateLock(Flags); } }); UNavigationSystemBase::RemoveNavigationUpdateLockDelegate().BindLambda([](UWorld& World, uint8 Flags) { UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(&World); if (NavSys) { NavSys->RemoveNavigationUpdateLock(Flags); } }); #endif // WITH_EDITOR #if ENABLE_VISUAL_LOG FVisualLogger::NavigationDataDumpDelegate.AddStatic(&NavigationDataDump); #endif // ENABLE_VISUAL_LOG } }; static FDelegatesInitializer DelegatesInitializer; // @hack, trying to load AIModule's CrowdManager UClass* Class = StaticLoadClass(UCrowdManagerBase::StaticClass(), nullptr, TEXT("/Script/AIModule.CrowdManager")); CrowdManagerClass = Class ? Class : UCrowdManagerBase::StaticClass(); // 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 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 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(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 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 NavigationDataClass = SupportedAgentConfig.NavigationDataClassName.IsValid() ? LoadClass(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("HotReload"); HotReloadDelegateHandle = HotReloadSupport.OnHotReload().AddUObject(this, &UNavigationSystemV1::OnHotReload); #endif } } bool UNavigationSystemV1::ConditionalPopulateNavOctree() { // Discard all navigation updates caused by octree construction TGuardValue> DirtyGuard(DirtyAreas, TArray()); // 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) { UpdateActorAndComponentsInNavOctree(*Actor); } } } } } // Add all found elements to octree, this will not add new dirty areas to navigation if (PendingOctreeUpdates.Num()) { for (TSet::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 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 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 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 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 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(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::TIterator It(PendingOctreeUpdates); It; ++It) { AddElementToNavOctree(*It); } PendingOctreeUpdates.Empty(32); } INC_FLOAT_STAT_BY(STAT_Navigation_CumulativeBuildTime,(float)ThisTime*1000); } if (bGenerateNavigationOnlyAroundNavigationInvokers) { UpdateInvokers(); } { SCOPE_CYCLE_COUNTER(STAT_Navigation_TickMarkDirty); DirtyAreasUpdateTime += DeltaSeconds; const float DirtyAreasUpdateDeltaTime = 1.0f / DirtyAreasUpdateFreq; const bool bCanRebuildNow = (DirtyAreasUpdateTime >= DirtyAreasUpdateDeltaTime) || !bIsGame; const bool bIsLocked = IsNavigationBuildingLocked(); if (DirtyAreas.Num() > 0 && bCanRebuildNow && !bIsLocked) { 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) { 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(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(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& 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 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 FilterClass) { if (GoalActor == NULL) { return NULL; } INavAgentInterface* NavAgent = Cast(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 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(World); if (NavSys != nullptr && NavSys->GetDefaultNavDataInstance() != nullptr) { ResultPath = NewObject(NavSys); bool bValidPathContext = false; const ANavigationData* NavigationData = NULL; if (PathfindingContext != NULL) { INavAgentInterface* NavAgent = Cast(PathfindingContext); if (NavAgent != NULL) { const FNavAgentProperties& AgentProps = NavAgent->GetNavAgentPropertiesRef(); NavigationData = NavSys->GetNavDataForProps(AgentProps); bValidPathContext = true; } else if (Cast(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 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(World); if (NavSys) { // figure out which navigation data to use const ANavigationData* NavData = NULL; INavAgentInterface* MyNavAgent = Cast(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& OutNavAgentProperties) const { AgentToNavDataMap.GetKeys(OutNavAgentProperties); } ANavigationData* UNavigationSystemV1::GetNavDataForProps(const FNavAgentProperties& AgentProperties) { const UNavigationSystemV1* ConstThis = AsConst(this); return const_cast(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* NavDataForAgent = AgentToNavDataMap.Find(AgentProperties); const ANavigationData* NavDataInstance = NavDataForAgent ? NavDataForAgent->Get() : nullptr; if (NavDataInstance == nullptr) { TArray 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::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(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 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(TestActor); if (NavInterface && NavInterface->IsNavigationRelevant()) { return true; } if (TestActor) { TInlineComponentArray Components; for (int32 Idx = 0; Idx < Components.Num(); Idx++) { NavInterface = Cast(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& 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(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 TempPending = PendingCustomLinkRegistration; PendingCustomLinkRegistration.Empty(); for (TMap::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* 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(CustomLinkOb); AActor* OwnerActor = OwnerComp ? OwnerComp->GetOwner() : Cast(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 >::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(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(OwnerOb); if (NavSys) { NavSys->UnregisterCustomLink(CustomLink); } else { FScopeLock AccessLock(&CustomLinkRegistrationSection); PendingCustomLinkRegistration.Remove(&CustomLink); } } void UNavigationSystemV1::RequestAreaUnregistering(UClass* NavAreaClass) { for (TObjectIterator 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 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(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(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 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& FlagsDesc) const { #if WITH_EDITOR const int32 MaxFlags = 16; TArray 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(UNavigationQueryFilter::StaticClass(), TEXT("IncludeFlags")); UStructProperty* StructProp2 = FindField(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(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 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()->bAllowClientSideNavigation || WorldOwner->GetNetMode() != NM_Client)) { AWorldSettings* WorldSettings = WorldOwner->GetWorldSettings(); if (WorldSettings == NULL || WorldSettings->IsNavigationSystemEnabled()) { NavSys = NewObject(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(World); } UNavigationSystemV1* UNavigationSystemV1::GetCurrent(UObject* WorldContextObject) { return FNavigationSystem::GetCurrent(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(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& 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) { 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) { #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; } void UNavigationSystemV1::UpdateActorInNavOctree(AActor& Actor) { if (IsNavigationSystemStatic()) { return; } SCOPE_CYCLE_COUNTER(STAT_DebugNavOctree); INavRelevantInterface* NavElement = Cast(&Actor); if (NavElement) { UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(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(&Comp); if (NavElement) { AActor* OwnerActor = Comp.GetOwner(); if (OwnerActor) { UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(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(&Comp); if (NavElement) { AActor* OwnerActor = Comp.GetOwner(); if (OwnerActor) { UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(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 UniqueAttachedActors; UniqueAttachedActors.Add(&RootActor); TArray 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(Component); if (NavElement) { NavElement->UpdateNavigationBounds(); } } } void UNavigationSystemV1::ClearNavOctreeAll(AActor* Actor) { if (Actor) { OnActorUnregistered(Actor); TInlineComponentArray 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 ChildNodes; OctreeChildNodesMap.MultiFind(ElementOwner, ChildNodes); if (ChildNodes.Num() == 0) { if (bSkipElementOwnerUpdate == false) { INavRelevantInterface* ElementInterface = Cast(ElementOwner); UpdateNavOctreeElement(ElementOwner, ElementInterface, UpdateFlags); } return; } INavRelevantInterface* ElementInterface = Cast(ElementOwner); TArray 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(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; } void UNavigationSystemV1::OnComponentRegistered(UActorComponent* Comp) { if (IsNavigationSystemStatic()) { return; } SCOPE_CYCLE_COUNTER(STAT_DebugNavOctree); INavRelevantInterface* NavInterface = Cast(Comp); if (NavInterface) { AActor* OwnerActor = Comp ? Comp->GetOwner() : NULL; if (OwnerActor && OwnerActor->IsComponentRelevantForNavigation(Comp)) { UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(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(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(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(Actor); if (NavInterface) { UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(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(Actor); if (NavInterface) { UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(Actor->GetWorld()); if (NavSys) { NavSys->UnregisterNavOctreeElement(Actor, NavInterface, OctreeUpdate_Default); } } } void UNavigationSystemV1::FindElementsInNavOctree(const FBox& QueryBox, const FNavigationOctreeFilter& Filter, TArray& 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(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((*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::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& 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 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 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(); // 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 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() == 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(); 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(*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(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::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* 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(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("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 FilterClass, const FVector QueryExtent) { UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(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 FilterClass) { FNavLocation RandomPoint(Origin); bool bResult = false; UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(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_GetRandomPointInNavigableRadius(UObject* WorldContextObject, const FVector& Origin, FVector& RandomLocation, float Radius, ANavigationData* NavData, TSubclassOf FilterClass) { FNavLocation RandomPoint(Origin); bool bResult = false; UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(World); if (NavSys) { ANavigationData* UseNavData = NavData ? NavData : NavSys->GetDefaultNavDataInstance(FNavigationSystem::DontCreate); if (UseNavData) { bResult = NavSys->GetRandomPointInNavigableRadius(Origin, Radius, RandomPoint, UseNavData, UNavigationQueryFilter::GetQueryFilter(*UseNavData, WorldContextObject, FilterClass)); RandomLocation = RandomPoint.Location; } } return bResult; } ENavigationQueryResult::Type UNavigationSystemV1::GetPathCost(UObject* WorldContextObject, const FVector& PathStart, const FVector& PathEnd, float& OutPathCost, ANavigationData* NavData, TSubclassOf FilterClass) { UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(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 FilterClass) { float PathLength = 0.f; UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(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(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(World); if (NavSys) { return NavSys->IsNavigationBuildingLocked() || NavSys->HasDirtyAreasQueued() || NavSys->IsNavigationBuildInProgress(); } return 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(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(NavigationData); if (RecastNavMesh) { RecastNavMesh->SetMaxSimultaneousTileGenerationJobsCount(MaxNumberOfJobs); } } #endif } void UNavigationSystemV1::ResetMaxSimultaneousTileGenerationJobsCount() { #if WITH_RECAST for (auto NavigationData : NavDataSet) { ARecastNavMesh* RecastNavMesh = Cast(NavigationData); if (RecastNavMesh) { const ARecastNavMesh* CDO = RecastNavMesh->GetClass()->GetDefaultObject(); RecastNavMesh->SetMaxSimultaneousTileGenerationJobsCount(CDO->MaxSimultaneousTileGenerationJobsCount); } } #endif } //----------------------------------------------------------------------// // Active tiles //----------------------------------------------------------------------// void UNavigationSystemV1::RegisterNavigationInvoker(AActor& Invoker, float TileGenerationRadius, float TileRemovalRadius) { UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(Invoker.GetWorld()); if (NavSys) { NavSys->RegisterInvoker(Invoker, TileGenerationRadius, TileRemovalRadius); } } void UNavigationSystemV1::UnregisterNavigationInvoker(AActor& Invoker) { UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(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) { TArray InvokerLocations; 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 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 FilterClass, const FVector QueryExtent) { FNavLocation ProjectedPoint(Point); UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(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 FilterClass) { FNavLocation RandomPoint; UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(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 FilterClass) { FNavLocation RandomPoint; UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(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; } 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& 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(Actor.GetWorld()); ANavigationData* NavData = nullptr; const INavAgentInterface* AsNavAgent = CastChecked(&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(NavData)); } int UNavigationSystemV1::GetNavigationBoundsForNavData(const ANavigationData& NavData, TArray& 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(); check(NavSysCDO); return NavSysCDO->SupportedAgents.Num() > 0 ? NavSysCDO->GetDefaultSupportedAgentConfig() : DefaultAgent; } void UNavigationSystemV1::OverrideSupportedAgents(const TArray& 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(); 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(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 NavSysClass = NavigationSystemClass.ResolveClass(); const UNavigationSystemV1* NavSysCDO = *NavSysClass ? NavSysClass->GetDefaultObject() : (UNavigationSystemV1*)nullptr; if (NavSysCDO) { UpdateWithNavSysCDO(*NavSysCDO); } } } } } #endif // WITH_EDITOR #undef LOCTEXT_NAMESPACE