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