You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
One problematic scenario is when all components from an actor are unregistered and one of them is the navigation parent of the others. In this case, the parent may be unregistered before some of its children and then calls to UpdateNavOctreeParentChain from a child may queue back the unregistered parent. UpdateNavOctreeParentChain will now consider the actual status of the parent (Added or queued for add) before registering it or its children. [at]mieszko.zielinski [at]guillaume.guay #rb mieszko.zielinski, guillaume.guay #ROBOMERGE-SOURCE: CL 8552772 via CL 8557168 #ROBOMERGE-BOT: (v406-8472469) [CL 8557314 by yoan stamant in Main branch]
506 lines
17 KiB
C++
506 lines
17 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);
|
|
}
|
|
}
|
|
|
|
bool FNavigationDataHandler::UnregisterNavOctreeElement(UObject& ElementOwner, INavRelevantInterface& ElementInterface, int32 UpdateFlags)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_Navigation_UnregisterNavOctreeElement);
|
|
|
|
if (OctreeController.NavOctree.IsValid() == false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (OctreeController.IsNavigationOctreeLocked())
|
|
{
|
|
#if !WITH_EDITOR
|
|
UE_LOG(LogNavOctree, Log, TEXT("IGNORE(UnregisterNavOctreeElement) %s"), *GetPathNameSafe(&ElementOwner));
|
|
#endif // WITH_EDITOR
|
|
return false;
|
|
}
|
|
|
|
bool bUnregistered = false;
|
|
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);
|
|
bUnregistered = true;
|
|
}
|
|
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())
|
|
{
|
|
FNavigationDirtyElement& DirtyElement = OctreeController.PendingOctreeUpdates[RequestId];
|
|
|
|
// Only consider as unregistered when pending update was not already invalidated since return value must indicate
|
|
// that ElementOwner was fully added or about to be added (valid pending update).
|
|
bUnregistered |= (DirtyElement.bInvalidRequest == false);
|
|
|
|
DirtyElement.bInvalidRequest = true;
|
|
}
|
|
}
|
|
|
|
return bUnregistered;
|
|
}
|
|
|
|
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);
|
|
|
|
auto ElementOwnerUpdateFunc = [&]()->bool
|
|
{
|
|
bool bShouldRegisterChildren = true;
|
|
if (bSkipElementOwnerUpdate == false)
|
|
{
|
|
INavRelevantInterface* ElementInterface = Cast<INavRelevantInterface>(&ElementOwner);
|
|
if (ElementInterface != nullptr)
|
|
{
|
|
// We don't want to register NavOctreeElement if owner was not already registered or queued
|
|
// so we use Unregister/Register combo instead of UpdateNavOctreeElement
|
|
if (UnregisterNavOctreeElement(ElementOwner, *ElementInterface, UpdateFlags))
|
|
{
|
|
FSetElementId NewId = RegisterNavOctreeElement(ElementOwner, *ElementInterface, UpdateFlags);
|
|
bShouldRegisterChildren = NewId.IsValidId();
|
|
}
|
|
else
|
|
{
|
|
bShouldRegisterChildren = false;
|
|
}
|
|
}
|
|
}
|
|
return bShouldRegisterChildren;
|
|
};
|
|
|
|
if (ChildNodes.Num() == 0)
|
|
{
|
|
// Last child was removed, only need to rebuild owner's NavOctreeElement
|
|
ElementOwnerUpdateFunc();
|
|
return;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
const bool bShouldRegisterChildren = ElementOwnerUpdateFunc();
|
|
|
|
if (bShouldRegisterChildren)
|
|
{
|
|
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);
|
|
}
|