971 lines
33 KiB
C++
971 lines
33 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "ActorHierarchy.h"
|
|
|
|
#include "Engine/Engine.h"
|
|
#include "Engine/World.h"
|
|
#include "EngineUtils.h"
|
|
#include "WorldTreeItem.h"
|
|
#include "LevelTreeItem.h"
|
|
#include "ActorTreeItem.h"
|
|
#include "ActorDescTreeItem.h"
|
|
#include "ComponentTreeItem.h"
|
|
#include "ActorFolderTreeItem.h"
|
|
#include "ISceneOutlinerMode.h"
|
|
#include "ActorEditorUtils.h"
|
|
#include "LevelUtils.h"
|
|
#include "GameFramework/WorldSettings.h"
|
|
#include "EditorActorFolders.h"
|
|
#include "EditorFolderUtils.h"
|
|
#include "LevelInstance/LevelInstanceInterface.h"
|
|
#include "LevelInstance/LevelInstanceSubsystem.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "WorldPartition/ActorDescContainerInstance.h"
|
|
#include "WorldPartition/WorldPartitionActorDescInstance.h"
|
|
#include "WorldPartition/WorldPartition.h"
|
|
#include "WorldPartition/WorldPartitionHelpers.h"
|
|
#include "WorldPartition/WorldPartitionActorDesc.h"
|
|
#include "WorldPartition/IWorldPartitionEditorModule.h"
|
|
#include "WorldPartition/WorldPartitionSubsystem.h"
|
|
#include "ActorFolder.h"
|
|
#include "ActorMode.h"
|
|
#include "SSceneOutliner.h"
|
|
|
|
TUniquePtr<FActorHierarchy> FActorHierarchy::Create(ISceneOutlinerMode* Mode, const TWeakObjectPtr<UWorld>& World)
|
|
{
|
|
FActorHierarchy* Hierarchy = new FActorHierarchy(Mode, World);
|
|
|
|
Create_Internal(Hierarchy, World);
|
|
|
|
return TUniquePtr<FActorHierarchy>(Hierarchy);
|
|
}
|
|
|
|
void FActorHierarchy::Create_Internal(FActorHierarchy* Hierarchy, const TWeakObjectPtr<UWorld>& World)
|
|
{
|
|
GEngine->OnLevelActorAdded().AddRaw(Hierarchy, &FActorHierarchy::OnLevelActorAdded);
|
|
GEngine->OnLevelActorDeleted().AddRaw(Hierarchy, &FActorHierarchy::OnLevelActorDeleted);
|
|
GEngine->OnLevelActorDetached().AddRaw(Hierarchy, &FActorHierarchy::OnLevelActorDetached);
|
|
GEngine->OnLevelActorAttached().AddRaw(Hierarchy, &FActorHierarchy::OnLevelActorAttached);
|
|
GEngine->OnLevelActorFolderChanged().AddRaw(Hierarchy, &FActorHierarchy::OnLevelActorFolderChanged);
|
|
GEngine->OnLevelActorListChanged().AddRaw(Hierarchy, &FActorHierarchy::OnLevelActorListChanged);
|
|
GEngine->OnActorFolderAdded().AddRaw(Hierarchy, &FActorHierarchy::OnActorFolderAdded);
|
|
GEngine->OnActorFoldersUpdatedEvent().AddRaw(Hierarchy, &FActorHierarchy::OnActorFoldersUpdatedEvent);
|
|
|
|
IWorldPartitionEditorModule::Get().OnWorldPartitionCreated().AddRaw(Hierarchy, &FActorHierarchy::OnWorldPartitionCreated);
|
|
|
|
if (World.IsValid())
|
|
{
|
|
if (World->PersistentLevel)
|
|
{
|
|
World->PersistentLevel->OnLoadedActorAddedToLevelEvent.AddRaw(Hierarchy, &FActorHierarchy::OnLoadedActorAdded);
|
|
World->PersistentLevel->OnLoadedActorRemovedFromLevelEvent.AddRaw(Hierarchy, &FActorHierarchy::OnLoadedActorRemoved);
|
|
}
|
|
|
|
World->OnWorldPartitionInitialized().AddRaw(Hierarchy, &FActorHierarchy::OnWorldPartitionInitialized);
|
|
World->OnWorldPartitionUninitialized().AddRaw(Hierarchy, &FActorHierarchy::OnWorldPartitionUninitialized);
|
|
|
|
if (UWorldPartition* WorldPartition = World->GetWorldPartition())
|
|
{
|
|
WorldPartition->OnActorDescInstanceAddedEvent.AddRaw(Hierarchy, &FActorHierarchy::OnActorDescInstanceAdded);
|
|
WorldPartition->OnActorDescInstanceRemovedEvent.AddRaw(Hierarchy, &FActorHierarchy::OnActorDescInstanceRemoved);
|
|
}
|
|
}
|
|
|
|
FWorldDelegates::LevelAddedToWorld.AddRaw(Hierarchy, &FActorHierarchy::OnLevelAdded);
|
|
FWorldDelegates::LevelRemovedFromWorld.AddRaw(Hierarchy, &FActorHierarchy::OnLevelRemoved);
|
|
|
|
auto& Folders = FActorFolders::Get();
|
|
Folders.OnFolderCreated.AddRaw(Hierarchy, &FActorHierarchy::OnBroadcastFolderCreate);
|
|
Folders.OnFolderMoved.AddRaw(Hierarchy, &FActorHierarchy::OnBroadcastFolderMove);
|
|
Folders.OnFolderDeleted.AddRaw(Hierarchy, &FActorHierarchy::OnBroadcastFolderDelete);
|
|
}
|
|
|
|
FActorHierarchy::FActorHierarchy(ISceneOutlinerMode* Mode, const TWeakObjectPtr<UWorld>& World)
|
|
: ISceneOutlinerHierarchy(Mode)
|
|
, RepresentingWorld(World)
|
|
{
|
|
}
|
|
|
|
FActorHierarchy::~FActorHierarchy()
|
|
{
|
|
if (GEngine)
|
|
{
|
|
GEngine->OnLevelActorAdded().RemoveAll(this);
|
|
GEngine->OnLevelActorDeleted().RemoveAll(this);
|
|
GEngine->OnLevelActorDetached().RemoveAll(this);
|
|
GEngine->OnLevelActorAttached().RemoveAll(this);
|
|
GEngine->OnLevelActorFolderChanged().RemoveAll(this);
|
|
GEngine->OnLevelActorListChanged().RemoveAll(this);
|
|
GEngine->OnActorFolderAdded().RemoveAll(this);
|
|
GEngine->OnActorFoldersUpdatedEvent().RemoveAll(this);
|
|
}
|
|
|
|
IWorldPartitionEditorModule::Get().OnWorldPartitionCreated().RemoveAll(this);
|
|
|
|
if (RepresentingWorld.IsValid())
|
|
{
|
|
if (RepresentingWorld->PersistentLevel)
|
|
{
|
|
RepresentingWorld->PersistentLevel->OnLoadedActorAddedToLevelEvent.RemoveAll(this);
|
|
RepresentingWorld->PersistentLevel->OnLoadedActorRemovedFromLevelEvent.RemoveAll(this);
|
|
}
|
|
|
|
RepresentingWorld->OnWorldPartitionInitialized().RemoveAll(this);
|
|
RepresentingWorld->OnWorldPartitionUninitialized().RemoveAll(this);
|
|
|
|
if (UWorldPartition* WorldPartition = RepresentingWorld->GetWorldPartition())
|
|
{
|
|
WorldPartition->OnActorDescInstanceAddedEvent.RemoveAll(this);
|
|
WorldPartition->OnActorDescInstanceRemovedEvent.RemoveAll(this);
|
|
}
|
|
}
|
|
|
|
FWorldDelegates::LevelAddedToWorld.RemoveAll(this);
|
|
FWorldDelegates::LevelRemovedFromWorld.RemoveAll(this);
|
|
|
|
auto& Folders = FActorFolders::Get();
|
|
Folders.OnFolderCreated.RemoveAll(this);
|
|
Folders.OnFolderMoved.RemoveAll(this);
|
|
Folders.OnFolderDeleted.RemoveAll(this);
|
|
}
|
|
|
|
FSceneOutlinerTreeItemPtr FActorHierarchy::FindOrCreateParentItem(const ISceneOutlinerTreeItem& Item, const TMap<FSceneOutlinerTreeItemID, FSceneOutlinerTreeItemPtr>& Items, bool bCreate)
|
|
{
|
|
if (Item.IsA<FWorldTreeItem>())
|
|
{
|
|
return nullptr;
|
|
}
|
|
else if (const FActorTreeItem* ActorTreeItem = Item.CastTo<FActorTreeItem>())
|
|
{
|
|
if (AActor* Actor = ActorTreeItem->Actor.Get())
|
|
{
|
|
// Parent Actor (Actor attachement / parenting)
|
|
if (AActor* ParentActor = Actor->GetSceneOutlinerParent())
|
|
{
|
|
if (const FSceneOutlinerTreeItemPtr* ParentItem = Items.Find(ParentActor))
|
|
{
|
|
return *ParentItem;
|
|
}
|
|
// If Parent can be listed in SceneOutliner return nullptr so it gets created
|
|
else if (ParentActor->IsListedInSceneOutliner())
|
|
{
|
|
return bCreate ? CreateItemForActor(ParentActor, true) : nullptr;
|
|
}
|
|
}
|
|
|
|
// Parent Folder
|
|
FFolder Folder = Actor->GetFolder();
|
|
if (Mode->ShouldShowFolders() && !Folder.IsNone())
|
|
{
|
|
if (const FSceneOutlinerTreeItemPtr* ParentItem = Items.Find(Folder))
|
|
{
|
|
return *ParentItem;
|
|
}
|
|
else
|
|
{
|
|
return bCreate ? CreateItemForFolder(Folder, ActorTreeItem->Actor->GetWorld(), true) : nullptr;
|
|
}
|
|
}
|
|
|
|
// Parent Level Instance
|
|
if (ILevelInstanceInterface* OwningLevelInstance = Cast<ILevelInstanceInterface>(Folder.GetRootObjectPtr()))
|
|
{
|
|
const ILevelInstanceInterface* LevelInstance = Cast<ILevelInstanceInterface>(Actor);
|
|
const bool bIsAnEditingLevelInstance = LevelInstance ? LevelInstance->IsEditing() : false;
|
|
// Parent this to a LevelInstance if the parent LevelInstance is being edited or if this is a sub LevelInstance which is being edited
|
|
if (bShowingLevelInstances || (OwningLevelInstance->IsEditing() || bIsAnEditingLevelInstance))
|
|
{
|
|
AActor* OwningActor = CastChecked<AActor>(OwningLevelInstance);
|
|
if (const FSceneOutlinerTreeItemPtr* ParentItem = Items.Find(OwningActor))
|
|
{
|
|
return *ParentItem;
|
|
}
|
|
else
|
|
{
|
|
return bCreate ? CreateItemForActor(OwningActor, true) : nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parent Level Using Actor Folders
|
|
ULevel* OwningLevel = Cast<ULevel>(Folder.GetRootObjectPtr());
|
|
// For the persistent level, fallback on the world
|
|
if (FActorMode::IsActorLevelDisplayable(OwningLevel))
|
|
{
|
|
if (const FSceneOutlinerTreeItemPtr* ParentItem = Items.Find(OwningLevel))
|
|
{
|
|
return *ParentItem;
|
|
}
|
|
else
|
|
{
|
|
return bCreate ? Mode->CreateItemFor<FLevelTreeItem>(OwningLevel, true) : nullptr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (const FActorFolderTreeItem* FolderItem = Item.CastTo<FActorFolderTreeItem>())
|
|
{
|
|
// We should never call FindParents on a folder item if folders are not being shown
|
|
check(Mode->ShouldShowFolders());
|
|
|
|
const FFolder ParentPath = FolderItem->GetFolder().GetParent();
|
|
|
|
// Parent Folder
|
|
if (!ParentPath.IsNone())
|
|
{
|
|
if (const FSceneOutlinerTreeItemPtr* ParentItem = Items.Find(ParentPath))
|
|
{
|
|
return *ParentItem;
|
|
}
|
|
else
|
|
{
|
|
return bCreate ? CreateItemForFolder(ParentPath, FolderItem->World, true) : nullptr;
|
|
}
|
|
}
|
|
// Parent Level Instance
|
|
else if (ILevelInstanceInterface* OwningLevelInstance = Cast<ILevelInstanceInterface>(ParentPath.GetRootObjectPtr()))
|
|
{
|
|
if (bShowingLevelInstances || OwningLevelInstance->IsEditing())
|
|
{
|
|
AActor* OwningActor = CastChecked<AActor>(OwningLevelInstance);
|
|
if (const FSceneOutlinerTreeItemPtr* ParentItem = Items.Find(OwningActor))
|
|
{
|
|
return *ParentItem;
|
|
}
|
|
else
|
|
{
|
|
return bCreate ? CreateItemForActor(OwningActor, true) : nullptr;
|
|
}
|
|
}
|
|
}
|
|
// Parent Level Using Actor Folders
|
|
else if (ULevel* OwningLevel = Cast<ULevel>(ParentPath.GetRootObjectPtr()))
|
|
{
|
|
if (FActorMode::IsActorLevelDisplayable(OwningLevel))
|
|
{
|
|
if (const FSceneOutlinerTreeItemPtr* ParentItem = Items.Find(OwningLevel))
|
|
{
|
|
return *ParentItem;
|
|
}
|
|
else
|
|
{
|
|
return bCreate ? Mode->CreateItemFor<FLevelTreeItem>(OwningLevel, true) : nullptr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (const FComponentTreeItem* ComponentTreeItem = Item.CastTo<FComponentTreeItem>())
|
|
{
|
|
if (AActor* Owner = ComponentTreeItem->Component->GetOwner())
|
|
{
|
|
if (const FSceneOutlinerTreeItemPtr* ParentItem = Items.Find(Owner))
|
|
{
|
|
return *ParentItem;
|
|
}
|
|
else if (Owner->IsListedInSceneOutliner())
|
|
{
|
|
return bCreate ? CreateItemForActor(Owner, true) : nullptr;
|
|
}
|
|
}
|
|
// do not default to world on Component items
|
|
return nullptr;
|
|
}
|
|
else if (const FActorDescTreeItem* ActorDescItem = Item.CastTo<FActorDescTreeItem>())
|
|
{
|
|
if (const FWorldPartitionActorDescInstance* ActorDescInstance = *ActorDescItem->ActorDescHandle)
|
|
{
|
|
if (UWorld* RepresentingWorldPtr = RepresentingWorld.Get())
|
|
{
|
|
const FFolder ActorDescFolder = FActorFolders::GetActorDescInstanceFolder(*RepresentingWorldPtr, ActorDescInstance);
|
|
if (!ActorDescFolder.IsNone())
|
|
{
|
|
if (const FSceneOutlinerTreeItemPtr* ParentItem = Items.Find(ActorDescFolder))
|
|
{
|
|
return *ParentItem;
|
|
}
|
|
else
|
|
{
|
|
return bCreate ? CreateItemForFolder(ActorDescFolder, RepresentingWorldPtr, true) : nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parent Actor (Actor attachement / parenting)
|
|
const FGuid& ParentActorGuid = ActorDescInstance->GetSceneOutlinerParent();
|
|
if (ParentActorGuid.IsValid())
|
|
{
|
|
if (UActorDescContainerInstance* ContainerInstance = ActorDescInstance->GetContainerInstance())
|
|
{
|
|
if (const FWorldPartitionActorDescInstance* ParentActorDesc = ContainerInstance->GetActorDescInstance(ParentActorGuid))
|
|
{
|
|
// If parent actor is loaded
|
|
if (AActor* ParentActor = ParentActorDesc->GetActor())
|
|
{
|
|
// Find loaded parent actor node (from the object ptr)
|
|
if (const FSceneOutlinerTreeItemPtr* ParentItem = Items.Find(ParentActor))
|
|
{
|
|
return *ParentItem;
|
|
}
|
|
else
|
|
{
|
|
return bCreate ? CreateItemForActor(ParentActor, true) : nullptr;
|
|
}
|
|
}
|
|
|
|
// Find unloaded parent actor node (from the guid)
|
|
if (const FSceneOutlinerTreeItemPtr* ParentItem = Items.Find(FActorDescTreeItem::ComputeTreeItemID(ParentActorGuid, ContainerInstance)))
|
|
{
|
|
return *ParentItem;
|
|
}
|
|
else
|
|
{
|
|
return bCreate ? Mode->CreateItemFor<FActorDescTreeItem>(FActorDescTreeItem(ParentActorGuid, ContainerInstance)) : nullptr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (UActorDescContainerInstance* ContainerInstance = ActorDescInstance->GetContainerInstance())
|
|
{
|
|
const ULevelInstanceSubsystem* LevelInstanceSubsystem = UWorld::GetSubsystem<ULevelInstanceSubsystem>(RepresentingWorld.Get());
|
|
UWorld* OuterWorld = ContainerInstance->GetTypedOuter<UWorld>();
|
|
// If parent actor is loaded
|
|
if (AActor* ParentActor = OuterWorld ? Cast<AActor>(LevelInstanceSubsystem->GetOwningLevelInstance(OuterWorld->PersistentLevel)) : nullptr)
|
|
{
|
|
if (const FSceneOutlinerTreeItemPtr* ParentItem = Items.Find(ParentActor))
|
|
{
|
|
return *ParentItem;
|
|
}
|
|
else
|
|
{
|
|
return bCreate ? CreateItemForActor(ParentActor, true) : nullptr;
|
|
}
|
|
}
|
|
// If parent actor is not loaded
|
|
else if (UActorDescContainerInstance* ParentContainerInstance = Cast<UActorDescContainerInstance>(ContainerInstance->GetOuter()))
|
|
{
|
|
if (const FWorldPartitionActorDescInstance* ParentActorDescInstance = ParentContainerInstance->GetActorDescInstance(ContainerInstance->GetContainerActorGuid()))
|
|
{
|
|
if (const FSceneOutlinerTreeItemPtr* ParentItem = Items.Find(FActorDescTreeItem::ComputeTreeItemID(ParentActorDescInstance->GetGuid(), ParentContainerInstance)))
|
|
{
|
|
return *ParentItem;
|
|
}
|
|
else
|
|
{
|
|
return bCreate ? CreateItemForActorDescInstance(ParentActorDescInstance, true) : nullptr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we get here return world item
|
|
if (UWorld* RepresentingWorldPtr = RepresentingWorld.Get())
|
|
{
|
|
if (const FSceneOutlinerTreeItemPtr* ParentItem = Items.Find(RepresentingWorldPtr))
|
|
{
|
|
return *ParentItem;
|
|
}
|
|
else
|
|
{
|
|
return bCreate ? Mode->CreateItemFor<FWorldTreeItem>(RepresentingWorldPtr, true) : nullptr;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void FActorHierarchy::CreateComponentItems(const AActor* Actor, TArray<FSceneOutlinerTreeItemPtr>& OutItems) const
|
|
{
|
|
check(Actor);
|
|
// Add all this actors components if showing components and the owning actor was created
|
|
if (bShowingComponents)
|
|
{
|
|
for (UActorComponent* Component : Actor->GetComponents())
|
|
{
|
|
if (Component != nullptr && !Component->GetClass()->HasAnyClassFlags(CLASS_Hidden))
|
|
{
|
|
if (FSceneOutlinerTreeItemPtr ComponentItem =
|
|
Mode->CreateItemFor<FComponentTreeItem>(FComponentTreeItem(Component, bSearchComponentsByActorName)))
|
|
{
|
|
OutItems.Add(ComponentItem);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FActorHierarchy::IsShowingUnloadedActors() const
|
|
{
|
|
return bShowingUnloadedActors && RepresentingWorld.IsValid() && !RepresentingWorld->IsPlayInEditor();
|
|
}
|
|
|
|
void FActorHierarchy::CreateFolderItems(UWorld* InWorld, TArray<FSceneOutlinerTreeItemPtr>& OutItems) const
|
|
{
|
|
// In game world, create folder through FindOrCreateParentItem
|
|
if (InWorld->IsGameWorld())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Mode->ShouldShowFolders() && bShowingEmptyFolders)
|
|
{
|
|
// Add any folders which might match the current search terms
|
|
FActorFolders::Get().ForEachFolder(*InWorld, [this, &InWorld, &OutItems](const FFolder& Folder)
|
|
{
|
|
if (FSceneOutlinerTreeItemPtr FolderItem = CreateItemForFolder(Folder, InWorld))
|
|
{
|
|
OutItems.Add(FolderItem);
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
|
|
FSceneOutlinerTreeItemPtr FActorHierarchy::CreateItemForActor(AActor* InActor, bool bForce) const
|
|
{
|
|
return Mode->CreateItemFor<FActorTreeItem>(InActor, bForce);
|
|
}
|
|
|
|
FSceneOutlinerTreeItemPtr FActorHierarchy::CreateItemForActorDescInstance(const FWorldPartitionActorDescInstance* InActorDescInstance, bool bForce) const
|
|
{
|
|
return Mode->CreateItemFor<FActorDescTreeItem>(InActorDescInstance, bForce);
|
|
}
|
|
|
|
FSceneOutlinerTreeItemPtr FActorHierarchy::CreateItemForFolder(const FFolder& InFolder, const TWeakObjectPtr<UWorld>& InWorld, bool bForce) const
|
|
{
|
|
return Mode->CreateItemFor<FActorFolderTreeItem>(FActorFolderTreeItem(InFolder, InWorld), bForce);
|
|
}
|
|
|
|
bool FActorHierarchy::CheckLevelInstanceEditing(UWorld* World, AActor* Actor) const
|
|
{
|
|
const ULevelInstanceSubsystem* LevelInstanceSubsystem = World->GetSubsystem<ULevelInstanceSubsystem>();
|
|
// If we are not showing LevelInstances, LevelInstance sub actor items should not be created unless they belong to a LevelInstance which is being edited
|
|
if (LevelInstanceSubsystem)
|
|
{
|
|
if (const ILevelInstanceInterface* ParentLevelInstance = LevelInstanceSubsystem->GetParentLevelInstance(Actor))
|
|
{
|
|
if (!bShowingLevelInstances && !ParentLevelInstance->IsEditing())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FActorHierarchy::InsertActorItemAndCreateComponents(AActor* InActor, FSceneOutlinerTreeItemPtr ActorItem, TArray<FSceneOutlinerTreeItemPtr>& OutItems) const
|
|
{
|
|
const int32 InsertLocation = OutItems.Num();
|
|
|
|
// Create all component items
|
|
CreateComponentItems(InActor, OutItems);
|
|
|
|
// If we are only showing actors with valid components, don't add the actor if no components were added
|
|
if (bShowingOnlyActorWithValidComponents)
|
|
{
|
|
// If the number of items remained the same after component insertion no components were inserted
|
|
if (OutItems.Num() == InsertLocation)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (ActorItem)
|
|
{
|
|
// Add the actor before the components
|
|
OutItems.Insert(ActorItem, InsertLocation);
|
|
}
|
|
}
|
|
|
|
void FActorHierarchy::CreateUnloadedItems(UWorld* World, TArray<FSceneOutlinerTreeItemPtr>& OutItems) const
|
|
{
|
|
TFunction<void(const FWorldPartitionActorDescInstance*)> AddChildContainer = [this, &OutItems, &AddChildContainer](const FWorldPartitionActorDescInstance* ActorDescInstance)
|
|
{
|
|
FWorldPartitionActorDesc::FContainerInstance SubContainerInstance;
|
|
if (ActorDescInstance->GetChildContainerInstance(SubContainerInstance))
|
|
{
|
|
for (FActorDescInstanceList::TIterator<> It(SubContainerInstance.ContainerInstance); It; ++It)
|
|
{
|
|
const FWorldPartitionActorDescInstance* SubActorDescInstance = *It;
|
|
|
|
if (SubActorDescInstance && FActorDescTreeItem::ShouldDisplayInOutliner(SubActorDescInstance))
|
|
{
|
|
if (FSceneOutlinerTreeItemPtr SubActorDescItem = Mode->CreateItemFor<FActorDescTreeItem>(SubActorDescInstance))
|
|
{
|
|
OutItems.Add(SubActorDescItem);
|
|
}
|
|
|
|
AddChildContainer(SubActorDescInstance);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
if (IsShowingUnloadedActors())
|
|
{
|
|
if (UWorldPartitionSubsystem* WorldPartitionSubsystem = UWorld::GetSubsystem<UWorldPartitionSubsystem>(World))
|
|
{
|
|
const ULevelInstanceSubsystem* LevelInstanceSubsystem = World->GetSubsystem<ULevelInstanceSubsystem>();
|
|
|
|
WorldPartitionSubsystem->ForEachWorldPartition([this, LevelInstanceSubsystem, &OutItems, &AddChildContainer](UWorldPartition* WorldPartition)
|
|
{
|
|
UWorld* OuterWorld = WorldPartition->GetTypedOuter<UWorld>();
|
|
ULevel* OuterLevel = OuterWorld ? OuterWorld->PersistentLevel : nullptr;
|
|
|
|
// Skip unloaded actors if they are part of a non editing level instance and the outliner hides the content of level instances
|
|
if (!bShowingLevelInstances)
|
|
{
|
|
ILevelInstanceInterface* LevelInstance = LevelInstanceSubsystem ? LevelInstanceSubsystem->GetOwningLevelInstance(OuterLevel) : nullptr;
|
|
if (LevelInstance && !LevelInstance->IsEditing())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
const TMap<FGuid, AActor*> LoadedActors = FWorldPartitionHelpers::GetLoadedActorsForLevel(OuterLevel);
|
|
const TMap<FGuid, AActor*> RegisteredActors = FWorldPartitionHelpers::GetRegisteredActorsForLevel(OuterLevel);
|
|
|
|
FWorldPartitionHelpers::ForEachActorDescInstance(WorldPartition, [this, &LoadedActors, &RegisteredActors, &OutItems, &AddChildContainer, LevelInstanceSubsystem](const FWorldPartitionActorDescInstance* ActorDescInstance)
|
|
{
|
|
if (ActorDescInstance && FActorDescTreeItem::ShouldDisplayInOutliner(ActorDescInstance))
|
|
{
|
|
if (AActor* const* LoadedActor = LoadedActors.Find(ActorDescInstance->GetGuid()))
|
|
{
|
|
if (!IsValid(*LoadedActor) || RegisteredActors.Contains(ActorDescInstance->GetGuid()))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
UActorDescContainerInstance* ContainerInstance = ActorDescInstance->GetContainerInstance();
|
|
|
|
if (const FSceneOutlinerTreeItemPtr ActorDescItem = Mode->CreateItemFor<FActorDescTreeItem>(FActorDescTreeItem(ActorDescInstance->GetGuid(), ContainerInstance)))
|
|
{
|
|
OutItems.Add(ActorDescItem);
|
|
}
|
|
|
|
if (bShowingLevelInstances)
|
|
{
|
|
// If parent actor is not loaded, recurse
|
|
UWorld* OuterWorld = ContainerInstance->GetTypedOuter<UWorld>();
|
|
if (AActor* ParentActor = OuterWorld ? Cast<AActor>(LevelInstanceSubsystem->GetOwningLevelInstance(OuterWorld->PersistentLevel)) : nullptr; !ParentActor)
|
|
{
|
|
AddChildContainer(ActorDescInstance);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
void FActorHierarchy::CreateWorldChildren(UWorld* World, TArray<FSceneOutlinerTreeItemPtr>& OutItems) const
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FActorHierarchy::CreateWorldChildren);
|
|
|
|
check(World);
|
|
|
|
CreateFolderItems(World, OutItems);
|
|
|
|
// Create all actor items
|
|
for (FActorIterator ActorIt(World); ActorIt; ++ActorIt)
|
|
{
|
|
AActor* Actor = *ActorIt;
|
|
|
|
if (!CheckLevelInstanceEditing(World, Actor))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FSceneOutlinerTreeItemPtr ActorItem = CreateItemForActor(Actor);
|
|
|
|
InsertActorItemAndCreateComponents(Actor, ActorItem, OutItems);
|
|
|
|
}
|
|
|
|
CreateUnloadedItems(World, OutItems);
|
|
}
|
|
|
|
void FActorHierarchy::CreateItems(TArray<FSceneOutlinerTreeItemPtr>& OutItems) const
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FActorHierarchy::CreateItems);
|
|
|
|
if (RepresentingWorld.IsValid())
|
|
{
|
|
UWorld* RepresentingWorldPtr = RepresentingWorld.Get();
|
|
check(RepresentingWorldPtr);
|
|
if (FSceneOutlinerTreeItemPtr WorldItem = Mode->CreateItemFor<FWorldTreeItem>(RepresentingWorldPtr))
|
|
{
|
|
OutItems.Add(WorldItem);
|
|
}
|
|
// Create world children regardless of if a world item was created
|
|
CreateWorldChildren(RepresentingWorldPtr, OutItems);
|
|
}
|
|
}
|
|
|
|
void FActorHierarchy::CreateChildren(const FSceneOutlinerTreeItemPtr& Item, TArray<FSceneOutlinerTreeItemPtr>& OutChildren) const
|
|
{
|
|
auto CreateChildrenFolders = [this](UWorld* InWorld, const FFolder& InParentFolder, const FFolder::FRootObject& InFolderRootObject, TArray<FSceneOutlinerTreeItemPtr>& OutChildren)
|
|
{
|
|
// In game world, create folder through FindOrCreateParentItem
|
|
if (InWorld->IsGameWorld())
|
|
{
|
|
return;
|
|
}
|
|
|
|
FActorFolders::Get().ForEachFolderWithRootObject(*InWorld, InFolderRootObject, [this, InWorld, &InParentFolder, &OutChildren](const FFolder& Folder)
|
|
{
|
|
if (Folder.IsChildOf(InParentFolder))
|
|
{
|
|
if (FSceneOutlinerTreeItemPtr NewFolderItem = CreateItemForFolder(Folder, InWorld))
|
|
{
|
|
OutChildren.Add(NewFolderItem);
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
};
|
|
|
|
UWorld* World = RepresentingWorld.Get();
|
|
if (FWorldTreeItem* WorldItem = Item->CastTo<FWorldTreeItem>())
|
|
{
|
|
check(WorldItem->World == RepresentingWorld);
|
|
CreateWorldChildren(WorldItem->World.Get(), OutChildren);
|
|
}
|
|
else if (const FActorTreeItem* ParentActorItem = Item->CastTo<FActorTreeItem>())
|
|
{
|
|
AActor* ParentActor = ParentActorItem->Actor.Get();
|
|
check(ParentActor->GetWorld() == RepresentingWorld);
|
|
|
|
CreateComponentItems(ParentActor, OutChildren);
|
|
|
|
TArray<AActor*> ChildActors;
|
|
|
|
if (const ILevelInstanceInterface* LevelInstanceParent = Cast<ILevelInstanceInterface>(ParentActor))
|
|
{
|
|
const ULevelInstanceSubsystem* LevelInstanceSubsystem = RepresentingWorld->GetSubsystem<ULevelInstanceSubsystem>();
|
|
check(LevelInstanceSubsystem);
|
|
|
|
LevelInstanceSubsystem->ForEachActorInLevelInstance(LevelInstanceParent, [this, LevelInstanceParent, LevelInstanceSubsystem, &ChildActors](AActor* SubActor)
|
|
{
|
|
const ILevelInstanceInterface* LevelInstance = Cast<ILevelInstanceInterface>(SubActor);
|
|
const bool bIsAnEditingLevelInstance = LevelInstance ? LevelInstanceSubsystem->IsEditingLevelInstance(LevelInstance) : false;
|
|
if (bShowingLevelInstances || (LevelInstanceSubsystem->IsEditingLevelInstance(LevelInstanceParent) || bIsAnEditingLevelInstance))
|
|
{
|
|
ChildActors.Add(SubActor);
|
|
}
|
|
return true;
|
|
});
|
|
|
|
check(World == CastChecked<AActor>(LevelInstanceParent)->GetWorld());
|
|
FFolder ParentFolder = ParentActor->GetFolder();
|
|
CreateChildrenFolders(World, ParentFolder, ParentActor, OutChildren);
|
|
}
|
|
else
|
|
{
|
|
// NOTE: GetAttachedActors can sometimes return the actor itself or its parent depending on how the component ownership is setup
|
|
// but we don't have to check for that here because the Outliner will simply discard duplicates
|
|
ParentActor->GetAttachedActors(ChildActors, true, true);
|
|
}
|
|
|
|
for (auto ChildActor : ChildActors)
|
|
{
|
|
if (FSceneOutlinerTreeItemPtr ChildActorItem = CreateItemForActor(ChildActor))
|
|
{
|
|
OutChildren.Add(ChildActorItem);
|
|
|
|
CreateComponentItems(ChildActor, OutChildren);
|
|
}
|
|
}
|
|
}
|
|
else if (FActorFolderTreeItem* FolderItem = Item->CastTo<FActorFolderTreeItem>())
|
|
{
|
|
check(Mode->ShouldShowFolders());
|
|
|
|
check(World == FolderItem->World.Get());
|
|
FFolder ParentFolder = FolderItem->GetFolder();
|
|
check(!ParentFolder.IsNone());
|
|
CreateChildrenFolders(World, ParentFolder, ParentFolder.GetRootObject(), OutChildren);
|
|
}
|
|
}
|
|
|
|
void FActorHierarchy::FullRefreshEvent()
|
|
{
|
|
FSceneOutlinerHierarchyChangedData EventData;
|
|
EventData.Type = FSceneOutlinerHierarchyChangedData::FullRefresh;
|
|
|
|
HierarchyChangedEvent.Broadcast(EventData);
|
|
}
|
|
|
|
void FActorHierarchy::OnWorldPartitionCreated(UWorld* InWorld)
|
|
{
|
|
if (RepresentingWorld.Get() == InWorld)
|
|
{
|
|
FullRefreshEvent();
|
|
}
|
|
}
|
|
|
|
void FActorHierarchy::OnLevelActorAdded(AActor* InActor)
|
|
{
|
|
if (InActor != nullptr && RepresentingWorld.Get() == InActor->GetWorld())
|
|
{
|
|
// CreateItemForActor already checks this by calling FActorMode::IsActorDisplayable, but that is after the actor tree item has
|
|
// already been created so this is a small optimization to avoid creating it and then calling Actor->GetActorLabel() which can be expensive
|
|
if (InActor->IsListedInSceneOutliner())
|
|
{
|
|
FSceneOutlinerHierarchyChangedData EventData;
|
|
EventData.Type = FSceneOutlinerHierarchyChangedData::Added;
|
|
EventData.Items.Add(CreateItemForActor(InActor));
|
|
HierarchyChangedEvent.Broadcast(EventData);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FActorHierarchy::OnLevelActorDeleted(AActor* InActor)
|
|
{
|
|
if (InActor != nullptr && RepresentingWorld.Get() == InActor->GetWorld())
|
|
{
|
|
FSceneOutlinerHierarchyChangedData EventData;
|
|
EventData.Type = FSceneOutlinerHierarchyChangedData::Removed;
|
|
EventData.ItemIDs.Add(InActor);
|
|
HierarchyChangedEvent.Broadcast(EventData);
|
|
}
|
|
}
|
|
|
|
void FActorHierarchy::OnLevelActorAttached(AActor* InActor, const AActor* InParent)
|
|
{
|
|
if (InActor != nullptr && RepresentingWorld.Get() == InActor->GetWorld())
|
|
{
|
|
FSceneOutlinerHierarchyChangedData EventData;
|
|
EventData.Type = FSceneOutlinerHierarchyChangedData::Moved;
|
|
EventData.ItemIDs.Add(InActor);
|
|
HierarchyChangedEvent.Broadcast(EventData);
|
|
}
|
|
}
|
|
|
|
void FActorHierarchy::OnLevelActorDetached(AActor* InActor, const AActor* InParent)
|
|
{
|
|
if (InActor != nullptr && RepresentingWorld.Get() == InActor->GetWorld())
|
|
{
|
|
FSceneOutlinerHierarchyChangedData EventData;
|
|
EventData.Type = FSceneOutlinerHierarchyChangedData::Moved;
|
|
EventData.ItemIDs.Add(InActor);
|
|
HierarchyChangedEvent.Broadcast(EventData);
|
|
}
|
|
}
|
|
|
|
void FActorHierarchy::RemoveActorDesc(AActor& InActor)
|
|
{
|
|
// Loaded actors can be in sub-world partitions so we use the outer world
|
|
if (UWorldPartition* WorldPartition = FWorldPartitionHelpers::GetWorldPartition(&InActor))
|
|
{
|
|
const FGuid& ActorGuid = InActor.GetActorGuid();
|
|
if (FWorldPartitionActorDescInstance* ActorDescInstance = WorldPartition->GetActorDescInstance(ActorGuid))
|
|
{
|
|
FSceneOutlinerHierarchyChangedData EventData;
|
|
EventData.Type = FSceneOutlinerHierarchyChangedData::Removed;
|
|
EventData.ItemIDs.Add(FActorDescTreeItem::ComputeTreeItemID(ActorGuid, ActorDescInstance->GetContainerInstance()));
|
|
HierarchyChangedEvent.Broadcast(EventData);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FActorHierarchy::AddActorDesc(AActor& InActor)
|
|
{
|
|
if (IsShowingUnloadedActors())
|
|
{
|
|
if (UWorldPartition* WorldPartition = FWorldPartitionHelpers::GetWorldPartition(&InActor))
|
|
{
|
|
const FGuid& ActorGuid = InActor.GetActorGuid();
|
|
if (FWorldPartitionActorDescInstance* ActorDescInstance = WorldPartition->GetActorDescInstance(ActorGuid); FActorDescTreeItem::ShouldDisplayInOutliner(ActorDescInstance))
|
|
{
|
|
FSceneOutlinerHierarchyChangedData EventData;
|
|
EventData.Type = FSceneOutlinerHierarchyChangedData::Added;
|
|
EventData.Items.Add(Mode->CreateItemFor<FActorDescTreeItem>(FActorDescTreeItem(ActorDescInstance->GetGuid(), ActorDescInstance->GetContainerInstance())));
|
|
HierarchyChangedEvent.Broadcast(EventData);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FActorHierarchy::OnLoadedActorAdded(AActor& InActor)
|
|
{
|
|
OnLevelActorAdded(&InActor);
|
|
|
|
RemoveActorDesc(InActor);
|
|
}
|
|
|
|
void FActorHierarchy::OnLoadedActorRemoved(AActor& InActor)
|
|
{
|
|
OnLevelActorDeleted(&InActor);
|
|
|
|
AddActorDesc(InActor);
|
|
}
|
|
|
|
void FActorHierarchy::OnActorDescInstanceAdded(FWorldPartitionActorDescInstance* InActorDescInstance)
|
|
{
|
|
if (IsShowingUnloadedActors() && InActorDescInstance && !InActorDescInstance->IsLoaded(true) && FActorDescTreeItem::ShouldDisplayInOutliner(InActorDescInstance))
|
|
{
|
|
FSceneOutlinerHierarchyChangedData EventData;
|
|
EventData.Type = FSceneOutlinerHierarchyChangedData::Added;
|
|
EventData.Items.Add(Mode->CreateItemFor<FActorDescTreeItem>(FActorDescTreeItem(InActorDescInstance->GetGuid(), InActorDescInstance->GetContainerInstance())));
|
|
HierarchyChangedEvent.Broadcast(EventData);
|
|
}
|
|
}
|
|
|
|
void FActorHierarchy::OnActorDescInstanceRemoved(FWorldPartitionActorDescInstance* InActorDescInstance)
|
|
{
|
|
if (IsShowingUnloadedActors() && InActorDescInstance)
|
|
{
|
|
FSceneOutlinerHierarchyChangedData EventData;
|
|
EventData.Type = FSceneOutlinerHierarchyChangedData::Removed;
|
|
EventData.ItemIDs.Add(FActorDescTreeItem::ComputeTreeItemID(InActorDescInstance->GetGuid(), InActorDescInstance->GetContainerInstance()));
|
|
HierarchyChangedEvent.Broadcast(EventData);
|
|
}
|
|
}
|
|
|
|
void FActorHierarchy::OnWorldPartitionInitialized(UWorldPartition* InWorldPartition)
|
|
{
|
|
FullRefreshEvent();
|
|
}
|
|
|
|
void FActorHierarchy::OnWorldPartitionUninitialized(UWorldPartition* InWorldPartition)
|
|
{
|
|
FullRefreshEvent();
|
|
}
|
|
|
|
void FActorHierarchy::OnComponentsUpdated()
|
|
{
|
|
FullRefreshEvent();
|
|
}
|
|
|
|
void FActorHierarchy::OnLevelActorListChanged()
|
|
{
|
|
FullRefreshEvent();
|
|
}
|
|
|
|
void FActorHierarchy::OnActorFoldersUpdatedEvent(ULevel* InLevel)
|
|
{
|
|
FullRefreshEvent();
|
|
}
|
|
|
|
void FActorHierarchy::OnActorFolderAdded(UActorFolder* InActorFolder)
|
|
{
|
|
check(InActorFolder);
|
|
ULevel* Level = InActorFolder->GetOuterULevel();
|
|
if (Level && Mode->ShouldShowFolders() && (RepresentingWorld.Get() == Level->GetWorld()))
|
|
{
|
|
FSceneOutlinerHierarchyChangedData EventData;
|
|
EventData.Type = FSceneOutlinerHierarchyChangedData::Added;
|
|
EventData.Items.Add(CreateItemForFolder(InActorFolder->GetFolder(), RepresentingWorld));
|
|
HierarchyChangedEvent.Broadcast(EventData);
|
|
}
|
|
}
|
|
|
|
void FActorHierarchy::OnLevelAdded(ULevel* InLevel, UWorld* InWorld)
|
|
{
|
|
if (InLevel != nullptr && RepresentingWorld.Get() == InWorld)
|
|
{
|
|
FSceneOutlinerHierarchyChangedData EventData;
|
|
EventData.Type = FSceneOutlinerHierarchyChangedData::Added;
|
|
|
|
EventData.Items.Reserve(InLevel->Actors.Num());
|
|
for (AActor* Actor : InLevel->Actors)
|
|
{
|
|
if (Actor != nullptr)
|
|
{
|
|
EventData.Items.Add(CreateItemForActor(Actor));
|
|
}
|
|
}
|
|
HierarchyChangedEvent.Broadcast(EventData);
|
|
}
|
|
}
|
|
|
|
void FActorHierarchy::RemoveLevelActorFolders(ULevel* InLevel, UWorld* InWorld)
|
|
{
|
|
// If either this level or the owning world are using actor folders, remove level's actor folders
|
|
if (InLevel->IsUsingActorFolders() || InWorld->PersistentLevel->IsUsingActorFolders())
|
|
{
|
|
FSceneOutlinerHierarchyChangedData EventData;
|
|
EventData.Type = FSceneOutlinerHierarchyChangedData::Removed;
|
|
EventData.ItemIDs.Add(InLevel);
|
|
HierarchyChangedEvent.Broadcast(EventData);
|
|
}
|
|
}
|
|
|
|
void FActorHierarchy::OnLevelRemoved(ULevel* InLevel, UWorld* InWorld)
|
|
{
|
|
if (InLevel != nullptr && RepresentingWorld.Get() == InWorld)
|
|
{
|
|
{
|
|
FSceneOutlinerHierarchyChangedData EventData;
|
|
EventData.Type = FSceneOutlinerHierarchyChangedData::Removed;
|
|
EventData.ItemIDs.Reserve(InLevel->Actors.Num());
|
|
for (AActor* Actor : InLevel->Actors)
|
|
{
|
|
if (Actor != nullptr)
|
|
{
|
|
EventData.ItemIDs.Add(Actor);
|
|
}
|
|
}
|
|
HierarchyChangedEvent.Broadcast(EventData);
|
|
}
|
|
|
|
RemoveLevelActorFolders(InLevel, InWorld);
|
|
}
|
|
}
|
|
|
|
/** Called when a folder is to be created */
|
|
void FActorHierarchy::OnBroadcastFolderCreate(UWorld& InWorld, const FFolder& InNewFolder)
|
|
{
|
|
if (Mode->ShouldShowFolders() && RepresentingWorld.Get() == &InWorld)
|
|
{
|
|
FSceneOutlinerHierarchyChangedData EventData;
|
|
EventData.Type = FSceneOutlinerHierarchyChangedData::Added;
|
|
EventData.Items.Add(CreateItemForFolder(InNewFolder, &InWorld));
|
|
EventData.ItemActions = SceneOutliner::ENewItemAction::Select | SceneOutliner::ENewItemAction::Rename;
|
|
HierarchyChangedEvent.Broadcast(EventData);
|
|
}
|
|
}
|
|
|
|
/** Called when a folder is to be moved */
|
|
void FActorHierarchy::OnBroadcastFolderMove(UWorld& InWorld, const FFolder& InOldFolder, const FFolder& InNewFolder)
|
|
{
|
|
if (Mode->ShouldShowFolders() && RepresentingWorld.Get() == &InWorld)
|
|
{
|
|
FSceneOutlinerHierarchyChangedData EventData;
|
|
EventData.Type = FSceneOutlinerHierarchyChangedData::FolderMoved;
|
|
EventData.ItemIDs.Add(InOldFolder);
|
|
EventData.NewPaths.Add(InNewFolder);
|
|
HierarchyChangedEvent.Broadcast(EventData);
|
|
}
|
|
}
|
|
|
|
/** Called when a folder is to be deleted */
|
|
void FActorHierarchy::OnBroadcastFolderDelete(UWorld& InWorld, const FFolder& InFolder)
|
|
{
|
|
if (Mode->ShouldShowFolders() && RepresentingWorld.Get() == &InWorld)
|
|
{
|
|
FSceneOutlinerHierarchyChangedData EventData;
|
|
EventData.Type = FSceneOutlinerHierarchyChangedData::Removed;
|
|
EventData.ItemIDs.Add(InFolder);
|
|
HierarchyChangedEvent.Broadcast(EventData);
|
|
}
|
|
}
|
|
|
|
void FActorHierarchy::OnLevelActorFolderChanged(const AActor* InActor, FName OldPath)
|
|
{
|
|
if (Mode->ShouldShowFolders() && RepresentingWorld.Get() == InActor->GetWorld())
|
|
{
|
|
FSceneOutlinerHierarchyChangedData EventData;
|
|
EventData.Type = FSceneOutlinerHierarchyChangedData::Moved;
|
|
EventData.ItemIDs.Add(FSceneOutlinerTreeItemID(InActor));
|
|
HierarchyChangedEvent.Broadcast(EventData);
|
|
}
|
|
}
|