You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#rb stephane.delmer #rnx #ROBOMERGE-SOURCE: CL 7678434 via CL 7678435 via CL 7683318 #ROBOMERGE-BOT: (v382-7680231) [CL 7683469 by maxime mercier in Main branch]
480 lines
16 KiB
C++
480 lines
16 KiB
C++
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "NavigationDataHandler.h"
|
|
#include "NavMesh/RecastNavMeshGenerator.h"
|
|
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogNavOctree, Warning, All);
|
|
|
|
namespace
|
|
{
|
|
int32 GetDirtyFlagHelper(int32 UpdateFlags, int32 DefaultValue)
|
|
{
|
|
return ((UpdateFlags & FNavigationOctreeController::OctreeUpdate_Geometry) != 0) ? ENavigationDirtyFlag::All :
|
|
((UpdateFlags & FNavigationOctreeController::OctreeUpdate_Modifiers) != 0) ? ENavigationDirtyFlag::DynamicModifier :
|
|
DefaultValue;
|
|
}
|
|
}
|
|
|
|
FNavigationDataHandler::FNavigationDataHandler(FNavigationOctreeController& InOctreeController, FNavigationDirtyAreasController& InDirtyAreasController)
|
|
: OctreeController(InOctreeController), DirtyAreasController(InDirtyAreasController)
|
|
{}
|
|
|
|
void FNavigationDataHandler::RemoveNavOctreeElementId(const FOctreeElementId& ElementId, int32 UpdateFlags)
|
|
{
|
|
if (ensure(OctreeController.IsValidElement(ElementId)))
|
|
{
|
|
const FNavigationOctreeElement& ElementData = OctreeController.NavOctree->GetElementById(ElementId);
|
|
const int32 DirtyFlag = GetDirtyFlagHelper(UpdateFlags, ElementData.Data->GetDirtyFlag());
|
|
// mark area occupied by given actor as dirty
|
|
DirtyAreasController.AddArea(ElementData.Bounds.GetBox(), DirtyFlag);
|
|
OctreeController.NavOctree->RemoveNode(ElementId);
|
|
}
|
|
}
|
|
|
|
FSetElementId FNavigationDataHandler::RegisterNavOctreeElement(UObject& ElementOwner, INavRelevantInterface& ElementInterface, int32 UpdateFlags)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_Navigation_RegisterNavOctreeElement);
|
|
|
|
FSetElementId SetId;
|
|
|
|
if (OctreeController.NavOctree.IsValid() == false)
|
|
{
|
|
return SetId;
|
|
}
|
|
|
|
if (OctreeController.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"), *ElementOwner.GetName(), bIsRelevant ? TEXT("[relevant]") : TEXT("[skip]"));
|
|
|
|
if (bIsRelevant)
|
|
{
|
|
bool bCanAdd = false;
|
|
|
|
UObject* ParentNode = ElementInterface.GetNavigationParent();
|
|
if (ParentNode)
|
|
{
|
|
OctreeController.OctreeChildNodesMap.AddUnique(ParentNode, FWeakObjectPtr(&ElementOwner));
|
|
bCanAdd = true;
|
|
}
|
|
else
|
|
{
|
|
bCanAdd = (OctreeController.HasObjectsNavOctreeId(ElementOwner) == false);
|
|
}
|
|
|
|
if (bCanAdd)
|
|
{
|
|
FNavigationDirtyElement UpdateInfo(&ElementOwner, &ElementInterface, GetDirtyFlagHelper(UpdateFlags, 0));
|
|
|
|
SetId = OctreeController.PendingOctreeUpdates.FindId(UpdateInfo);
|
|
if (SetId.IsValidId())
|
|
{
|
|
// make sure this request stays, in case it has been invalidated already
|
|
OctreeController.PendingOctreeUpdates[SetId] = UpdateInfo;
|
|
}
|
|
else
|
|
{
|
|
SetId = OctreeController.PendingOctreeUpdates.Add(UpdateInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
return SetId;
|
|
}
|
|
|
|
void FNavigationDataHandler::AddElementToNavOctree(const FNavigationDirtyElement& DirtyElement)
|
|
{
|
|
check(OctreeController.NavOctree.IsValid());
|
|
|
|
// handle invalidated requests first
|
|
if (DirtyElement.bInvalidRequest)
|
|
{
|
|
if (DirtyElement.bHasPrevData)
|
|
{
|
|
DirtyAreasController.AddArea(DirtyElement.PrevBounds, DirtyElement.PrevFlags);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
UObject* ElementOwner = DirtyElement.Owner.Get();
|
|
if (ElementOwner == nullptr || ElementOwner->IsPendingKill() || DirtyElement.NavInterface == nullptr)
|
|
{
|
|
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 = OctreeController.PendingOctreeUpdates.FindId(FNavigationDirtyElement(NavigationParent));
|
|
const FOctreeElementId* ParentId = OctreeController.GetObjectsNavOctreeId(*NavigationParent);
|
|
if (ParentRequestId.IsValidId() && ParentId == nullptr)
|
|
{
|
|
FNavigationDirtyElement& ParentNode = OctreeController.PendingOctreeUpdates[ParentRequestId];
|
|
AddElementToNavOctree(ParentNode);
|
|
|
|
// mark as invalid so it won't be processed twice
|
|
ParentNode.bInvalidRequest = true;
|
|
}
|
|
|
|
const FOctreeElementId* ElementId = ParentId ? ParentId : OctreeController.GetObjectsNavOctreeId(*NavigationParent);
|
|
if (ElementId && ensure(OctreeController.IsValidElement(*ElementId)))
|
|
{
|
|
UE_LOG(LogNavOctree, Log, TEXT("ADD %s to %s"), *GetNameSafe(ElementOwner), *GetNameSafe(NavigationParent));
|
|
OctreeController.NavOctree->AppendToNode(*ElementId, 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));
|
|
OctreeController.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();
|
|
DirtyAreasController.AddArea(BBox, DirtyFlag);
|
|
}
|
|
}
|
|
|
|
void FNavigationDataHandler::UnregisterNavOctreeElement(UObject& ElementOwner, INavRelevantInterface& ElementInterface, int32 UpdateFlags)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_Navigation_UnregisterNavOctreeElement);
|
|
|
|
if (OctreeController.NavOctree.IsValid() == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (OctreeController.IsNavigationOctreeLocked())
|
|
{
|
|
#if !WITH_EDITOR
|
|
UE_LOG(LogNavOctree, Log, TEXT("IGNORE(UnregisterNavOctreeElement) %s"), *GetPathNameSafe(&ElementOwner));
|
|
#endif // WITH_EDITOR
|
|
return;
|
|
}
|
|
|
|
const FOctreeElementId* ElementId = OctreeController.GetObjectsNavOctreeId(ElementOwner);
|
|
UE_LOG(LogNavOctree, Log, TEXT("UNREG %s %s"), *ElementOwner.GetName(), ElementId ? TEXT("[exists]") : TEXT("[does\'t exist]"));
|
|
|
|
if (ElementId != nullptr)
|
|
{
|
|
RemoveNavOctreeElementId(*ElementId, UpdateFlags);
|
|
OctreeController.RemoveObjectsNavOctreeId(ElementOwner);
|
|
}
|
|
else
|
|
{
|
|
const bool bCanRemoveChildNode = (UpdateFlags & FNavigationOctreeController::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
|
|
|
|
OctreeController.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 & FNavigationOctreeController::OctreeUpdate_Refresh) == 0;
|
|
if (bCanInvalidateQueue)
|
|
{
|
|
const FSetElementId RequestId = OctreeController.PendingOctreeUpdates.FindId(FNavigationDirtyElement(&ElementOwner));
|
|
if (RequestId.IsValidId())
|
|
{
|
|
OctreeController.PendingOctreeUpdates[RequestId].bInvalidRequest = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FNavigationDataHandler::UpdateNavOctreeElement(UObject& ElementOwner, INavRelevantInterface& ElementInterface, int32 UpdateFlags)
|
|
{
|
|
INC_DWORD_STAT(STAT_Navigation_UpdateNavOctree);
|
|
|
|
if (OctreeController.IsNavigationOctreeLocked())
|
|
{
|
|
UE_LOG(LogNavOctree, Log, TEXT("IGNORE(UpdateNavOctreeElement) %s"), *ElementOwner.GetPathName());
|
|
return;
|
|
}
|
|
|
|
// grab existing octree data
|
|
FBox CurrentBounds;
|
|
int32 CurrentFlags;
|
|
const bool bAlreadyExists = OctreeController.GetNavOctreeElementData(ElementOwner, CurrentFlags, CurrentBounds);
|
|
|
|
// don't invalidate pending requests
|
|
UpdateFlags |= FNavigationOctreeController::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 = OctreeController.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 FNavigationDataHandler::UpdateNavOctreeParentChain(UObject& ElementOwner, bool bSkipElementOwnerUpdate)
|
|
{
|
|
const int32 UpdateFlags = FNavigationOctreeController::OctreeUpdate_ParentChain | FNavigationOctreeController::OctreeUpdate_Refresh;
|
|
|
|
TArray<FWeakObjectPtr> ChildNodes;
|
|
OctreeController.OctreeChildNodesMap.MultiFind(&ElementOwner, ChildNodes);
|
|
|
|
if (ChildNodes.Num() == 0)
|
|
{
|
|
if (bSkipElementOwnerUpdate == false)
|
|
{
|
|
INavRelevantInterface* ElementInterface = Cast<INavRelevantInterface>(&ElementOwner);
|
|
if (ElementInterface)
|
|
{
|
|
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);
|
|
if (ChildNodeOb && ChildNavInterfaces[Idx])
|
|
{
|
|
UnregisterNavOctreeElement(*ChildNodeOb, *ChildNavInterfaces[Idx], UpdateFlags);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bSkipElementOwnerUpdate == false && ElementInterface)
|
|
{
|
|
UnregisterNavOctreeElement(ElementOwner, *ElementInterface, UpdateFlags);
|
|
RegisterNavOctreeElement(ElementOwner, *ElementInterface, UpdateFlags);
|
|
}
|
|
|
|
for (int32 Idx = 0; Idx < ChildNodes.Num(); Idx++)
|
|
{
|
|
UObject* ChildElement = ChildNodes[Idx].Get();
|
|
if (ChildElement && ChildNavInterfaces[Idx])
|
|
{
|
|
RegisterNavOctreeElement(*ChildElement, *ChildNavInterfaces[Idx], UpdateFlags);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FNavigationDataHandler::UpdateNavOctreeElementBounds(UActorComponent& Comp, const FBox& NewBounds, const FBox& DirtyArea)
|
|
{
|
|
const FOctreeElementId* ElementId = OctreeController.GetObjectsNavOctreeId(Comp);
|
|
if (ElementId != nullptr && ensure(OctreeController.IsValidElement(*ElementId)))
|
|
{
|
|
OctreeController.NavOctree->UpdateNode(*ElementId, NewBounds);
|
|
|
|
// Add dirty area
|
|
if (DirtyArea.IsValid)
|
|
{
|
|
// Refresh ElementId since components may be stored in a different node after updating bounds
|
|
ElementId = OctreeController.GetObjectsNavOctreeId(Comp);
|
|
if (ElementId != nullptr && ensure(OctreeController.IsValidElement(*ElementId)))
|
|
{
|
|
FNavigationOctreeElement& ElementData = OctreeController.NavOctree->GetElementById(*ElementId);
|
|
DirtyAreasController.AddArea(DirtyArea, ElementData.Data->GetDirtyFlag());
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FNavigationDataHandler::FindElementsInNavOctree(const FBox& QueryBox, const FNavigationOctreeFilter& Filter, TArray<FNavigationOctreeElement>& Elements)
|
|
{
|
|
if (OctreeController.NavOctree.IsValid() == false)
|
|
{
|
|
UE_LOG(LogNavOctree, Warning, TEXT("FNavigationDataHandler::FindElementsInNavOctree gets called while NavOctree is null"));
|
|
return;
|
|
}
|
|
|
|
for (FNavigationOctree::TConstElementBoxIterator<> It(*OctreeController.NavOctree, QueryBox); It.HasPendingElements(); It.Advance())
|
|
{
|
|
const FNavigationOctreeElement& Element = It.GetCurrentElement();
|
|
if (Element.IsMatchingFilter(Filter))
|
|
{
|
|
Elements.Add(Element);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FNavigationDataHandler::ReplaceAreaInOctreeData(const UObject& Object, TSubclassOf<UNavArea> OldArea, TSubclassOf<UNavArea> NewArea, bool bReplaceChildClasses)
|
|
{
|
|
FNavigationRelevantData* Data = OctreeController.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 FNavigationDataHandler::AddLevelCollisionToOctree(ULevel& Level)
|
|
{
|
|
#if WITH_RECAST
|
|
if (OctreeController.NavOctree.IsValid() &&
|
|
OctreeController.NavOctree->GetNavGeometryStoringMode() == FNavigationOctree::StoreNavGeometry)
|
|
{
|
|
const TArray<FVector>* LevelGeom = Level.GetStaticNavigableGeometry();
|
|
const FOctreeElementId* ElementId = OctreeController.GetObjectsNavOctreeId(Level);
|
|
|
|
if (!ElementId && LevelGeom && LevelGeom->Num() > 0)
|
|
{
|
|
FNavigationOctreeElement BSPElem(Level);
|
|
FRecastNavMeshGenerator::ExportVertexSoupGeometry(*LevelGeom, *BSPElem.Data);
|
|
|
|
const FBox& Bounds = BSPElem.Data->Bounds;
|
|
if (!Bounds.GetExtent().IsNearlyZero())
|
|
{
|
|
OctreeController.NavOctree->AddNode(&Level, nullptr, Bounds, BSPElem);
|
|
DirtyAreasController.AddArea(Bounds, ENavigationDirtyFlag::All);
|
|
|
|
UE_LOG(LogNavOctree, Log, TEXT("ADD %s"), *Level.GetName());
|
|
}
|
|
}
|
|
}
|
|
#endif// WITH_RECAST
|
|
}
|
|
|
|
void FNavigationDataHandler::RemoveLevelCollisionFromOctree(ULevel& Level)
|
|
{
|
|
if (OctreeController.NavOctree.IsValid())
|
|
{
|
|
const FOctreeElementId* ElementId = OctreeController.GetObjectsNavOctreeId(Level);
|
|
UE_LOG(LogNavOctree, Log, TEXT("UNREG %s %s"), *Level.GetName(), ElementId ? TEXT("[exists]") : TEXT(""));
|
|
|
|
if (ElementId != nullptr)
|
|
{
|
|
if (ensure(OctreeController.IsValidElement(*ElementId)))
|
|
{
|
|
// mark area occupied by given actor as dirty
|
|
FNavigationOctreeElement& ElementData = OctreeController.NavOctree->GetElementById(*ElementId);
|
|
DirtyAreasController.AddArea(ElementData.Bounds.GetBox(), ENavigationDirtyFlag::All);
|
|
}
|
|
|
|
OctreeController.NavOctree->RemoveNode(*ElementId);
|
|
OctreeController.RemoveObjectsNavOctreeId(Level);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FNavigationDataHandler::UpdateActorAndComponentsInNavOctree(AActor& Actor)
|
|
{
|
|
INavRelevantInterface* NavElement = Cast<INavRelevantInterface>(&Actor);
|
|
if (NavElement)
|
|
{
|
|
UpdateNavOctreeElement(Actor, *NavElement, FNavigationOctreeController::OctreeUpdate_Default);
|
|
}
|
|
|
|
for (UActorComponent* Component : Actor.GetComponents())
|
|
{
|
|
INavRelevantInterface* CompNavElement = Cast<INavRelevantInterface>(Component);
|
|
if (CompNavElement)
|
|
{
|
|
// Component != null is implied by successful INavRelevantInterface cast
|
|
if (Actor.IsComponentRelevantForNavigation(Component))
|
|
{
|
|
UpdateNavOctreeElement(*Component, *CompNavElement, FNavigationOctreeController::OctreeUpdate_Default);
|
|
}
|
|
else
|
|
{
|
|
UnregisterNavOctreeElement(*Component, *CompNavElement, FNavigationOctreeController::OctreeUpdate_Default);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FNavigationDataHandler::ProcessPendingOctreeUpdates()
|
|
{
|
|
if (OctreeController.PendingOctreeUpdates.Num() && OctreeController.NavOctree)
|
|
{
|
|
for (TSet<FNavigationDirtyElement>::TIterator It(OctreeController.PendingOctreeUpdates); It; ++It)
|
|
{
|
|
AddElementToNavOctree(*It);
|
|
}
|
|
}
|
|
OctreeController.PendingOctreeUpdates.Empty(32);
|
|
}
|