Files
2025-05-18 13:04:45 +08:00

676 lines
19 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CompElementManager.h"
#include "CompositingElement.h"
#include "Engine/Level.h"
#include "Selection.h"
#include "UObject/UObjectIterator.h"
#include "CompositingElements/CompositingElementOutputs.h"
#include "ContentBrowserModule.h"
#include "IContentBrowserSingleton.h"
#include "MediaOutput.h"
#include "ScopedWorldLevelContext.h"
#include "Misc/IFilter.h"
#include "EditorCompElementContainer.h"
#include "CompositingViewportClient.h"
#include "Framework/Application/SlateApplication.h"
#define LOCTEXT_NAMESPACE "CompElementManager"
FCompElementManager::FCompElementManager(const TWeakObjectPtr<UEditorEngine>& InEditor)
: Editor(InEditor)
, ElementsContainer(nullptr)
{
ensure(InEditor.IsValid());
}
FCompElementManager::~FCompElementManager()
{
if (Editor.IsValid())
{
Editor->OnLevelActorDeleted().RemoveAll(this);
Editor->OnLevelActorAdded().RemoveAll(this);
Editor->OnLevelActorListChanged().RemoveAll(this);
Editor->OnWorldDestroyed().RemoveAll(this);
Editor->OnWorldAdded().RemoveAll(this);
Editor->OnBlueprintCompiled().RemoveAll(this);
}
FWorldDelegates::LevelRemovedFromWorld.RemoveAll(this);
FWorldDelegates::LevelAddedToWorld.RemoveAll(this);
FCoreUObjectDelegates::PostLoadMapWithWorld.RemoveAll(this);
FEditorDelegates::MapChange.RemoveAll(this);
}
void FCompElementManager::Initialize()
{
ElementsContainer = NewObject<UEditorCompElementContainer>(GetTransientPackage(), NAME_None, RF_Transactional);
if (FSlateApplication::IsInitialized())
{
EditorCompositingViewport = MakeShareable(new FCompositingViewportClient(ElementsContainer));
}
FEditorDelegates::MapChange.AddRaw(this, &FCompElementManager::OnEditorMapChange);
FCoreUObjectDelegates::PostLoadMapWithWorld.AddRaw(this, &FCompElementManager::OnWorldAdded);
FWorldDelegates::LevelAddedToWorld.AddRaw(this, &FCompElementManager::OnWorldLevelsChange);
FWorldDelegates::LevelRemovedFromWorld.AddRaw(this, &FCompElementManager::OnWorldLevelsChange);
if (ensure(Editor.IsValid()))
{
Editor->OnWorldAdded().AddRaw(this, &FCompElementManager::OnWorldAdded);
Editor->OnWorldDestroyed().AddRaw(this, &FCompElementManager::OnWorldRemoved);
Editor->OnLevelActorListChanged().AddRaw(this, &FCompElementManager::OnLevelActorsListChange);
Editor->OnLevelActorAdded().AddRaw(this, &FCompElementManager::OnLevelActorAdded);
Editor->OnLevelActorDeleted().AddRaw(this, &FCompElementManager::OnLevelActorRemoved);
Editor->OnBlueprintCompiled().AddRaw(this, &FCompElementManager::OnBlueprintCompiled);
}
}
TWeakObjectPtr<ACompositingElement> FCompElementManager::CreateElement(const FName& ElementName, TSubclassOf<ACompositingElement> ClassType, AActor* LevelContext, EObjectFlags ObjectFlags)
{
ACompositingElement* SpawnedActor = nullptr;
UWorld* TargetWorld = nullptr;
if (LevelContext)
{
TargetWorld = LevelContext->GetWorld();
}
else
{
for (TWeakObjectPtr<ACompositingElement> ElementPtr : *ElementsContainer)
{
if (ElementPtr.IsValid())
{
TargetWorld = ElementPtr->GetWorld();
LevelContext = ElementPtr.Get();
break;
}
}
}
if (!TargetWorld)
{
TargetWorld = this->GetWorld();
}
if (TargetWorld != nullptr)
{
FScopedWorldLevelContext ScopedLevelContext(TargetWorld, LevelContext);
FActorSpawnParameters SpawnParams;
SpawnParams.Name = ElementName;
SpawnParams.NameMode = FActorSpawnParameters::ESpawnActorNameMode::Requested;
SpawnParams.ObjectFlags |= ObjectFlags;
SpawnedActor = TargetWorld->SpawnActor<ACompositingElement>(ClassType, FTransform::Identity, SpawnParams);
if (SpawnedActor)
{
SpawnedActor->SetElementName(ElementName);
}
}
return SpawnedActor;
}
TWeakObjectPtr<ACompositingElement> FCompElementManager::GetElement(const FName& ElementName) const
{
for (TWeakObjectPtr<ACompositingElement> Element : *ElementsContainer)
{
if (Element.IsValid() && Element->GetCompElementName() == ElementName)
{
return Element;
}
}
return nullptr;
}
bool FCompElementManager::TryGetElement(const FName& ElementName, TWeakObjectPtr<ACompositingElement>& OutElement)
{
OutElement = GetElement(ElementName);
return OutElement != nullptr;
}
TArray<FName> FCompElementManager::FindNamesOfAllChildElements(FName ElementName) const
{
TArray<FName> Output;
TWeakObjectPtr<ACompositingElement> Root = GetElement(ElementName);
if (Root.IsValid())
{
for (ACompositingElement* Child: Root->GetChildElements())
{
if (Child)
{
Output.Append(FindNamesOfAllChildElements(Child->GetCompElementName()));
Output.Add(Child->GetCompElementName());
}
}
}
return Output;
}
void FCompElementManager::AddAllCompElementsTo(TArray< TWeakObjectPtr<ACompositingElement> >& OutElements) const
{
OutElements = *ElementsContainer;
}
void FCompElementManager::DeleteElementAndChildren(const FName& ElementToDelete, bool bIsCalledFromEditor)
{
TArray<FName> NameCollection = FindNamesOfAllChildElements(ElementToDelete);
NameCollection.Add(ElementToDelete);
DeleteElements(NameCollection,bIsCalledFromEditor);
}
void FCompElementManager::DeleteElements(const TArray<FName>& ElementsToDelete, bool bIsCalledFromEditor)
{
ensure(PendingDeletion.Num() == 0);
//If called from BP/C++, deselect all the actors first to avoid accidentally delete any actor.
if (!bIsCalledFromEditor)
{
Editor->SelectNone(/*bNoteSelectionChange=*/false,/*bDeselectBSPSurfs=*/false,/*WarnAboutManyActors=*/false);
}
TArray< TWeakObjectPtr<ACompositingElement> > ValidElementsToDelete;
ValidElementsToDelete.Reserve(ElementsToDelete.Num());
for (const FName& ElementName : ElementsToDelete)
{
TWeakObjectPtr<ACompositingElement> Element;
if (TryGetElement(ElementName, OUT Element))
{
ValidElementsToDelete.Add(Element);
}
}
TArray< TWeakObjectPtr<UObject> > ExcessSelectedObjs;
int32 SelectionCount = 0;
for (FSelectionIterator SelectionIt(Editor->GetSelectedActorIterator()); SelectionIt; ++SelectionIt, ++SelectionCount)
{
TWeakObjectPtr<UObject> SelectedObjPtr(*SelectionIt);
if (!ValidElementsToDelete.Contains(SelectedObjPtr))
{
ExcessSelectedObjs.Add(*SelectionIt);
}
}
//No need to ensure excess selected objects if this function is not called from editor
if (bIsCalledFromEditor)
{
ensure(ExcessSelectedObjs.Num() == 0);
}
TArray< TWeakObjectPtr<ACompositingElement> > OldParents;
OldParents.Reserve(ValidElementsToDelete.Num());
PendingDeletion.Reserve(ValidElementsToDelete.Num());
for (const TWeakObjectPtr<ACompositingElement>& Element : ValidElementsToDelete)
{
PendingDeletion.Add(Element);
if (!Element->IsSubElement() || ValidElementsToDelete.Contains(Element->GetParentActor()))
{
OldParents.Add(nullptr);
}
else
{
ACompositingElement* ElementParent = Element->GetElementParent();
OldParents.Add(ElementParent);
// remove to avoid "actor is referenced by other actors" warning
Element->Modify();
ElementParent->Modify();
ElementParent->DetatchAsChildLayer(Element.Get());
}
}
if (ExcessSelectedObjs.Num() > 0 || ValidElementsToDelete.Num() != SelectionCount)
{
USelection* EdSelectionManager = Editor->GetSelectedActors();
EdSelectionManager->BeginBatchSelectOperation();
for (const TWeakObjectPtr<ACompositingElement>& Element : ValidElementsToDelete)
{
EdSelectionManager->Modify();
Editor->SelectActor(Element.Get(), /*bSelect =*/true, /*bNotifyForActor =*/false, /*bSelectEvenIfHidden =*/true);
}
EdSelectionManager->EndBatchSelectOperation();
}
if (UWorld* World = GetWorld())
{
//Disable prompt window if this function is called directly from BP/C++
Editor->edactDeleteSelected(World, /*bVerifyDeletionCanHappen =*/true,/*bWarnAboutReferences=*/bIsCalledFromEditor,/*bWarnAboutSoftReferences=*/bIsCalledFromEditor);
}
for (const TWeakObjectPtr<ACompositingElement>& Element : PendingDeletion)
{
if (Element.IsValid())
{
const int32 ElementIndex = ValidElementsToDelete.Find(Element);
if (ElementIndex != INDEX_NONE)
{
const TWeakObjectPtr<ACompositingElement>& ElementParent = OldParents[ElementIndex];
if (ElementParent.IsValid())
{
ElementParent->Modify();
ElementParent->AttachAsChildLayer(Element.Get());
}
}
}
}
PendingDeletion.Empty();
// @TODO: restore selection
CompsChanged.Broadcast(ECompElementEdActions::Delete, nullptr, NAME_None);
RequestRedraw();
}
bool FCompElementManager::RenameElement(const FName OriginalElementName, const FName& NewElementName)
{
// We specifically don't pass the original ElementName by reference to avoid it changing
// it's original value, in case, it would be the reference of the Element's actual FName
if (OriginalElementName == NewElementName)
{
return false;
}
TWeakObjectPtr<ACompositingElement> Element;
if (!TryGetElement(OriginalElementName, Element))
{
return false;
}
Element->Modify();
Element->SetElementName(NewElementName);
CompsChanged.Broadcast(ECompElementEdActions::Rename, Element, "CompShotIdName");
RequestRedraw();
return true;
}
bool FCompElementManager::AttachCompElement(const FName ParentName, const FName ElementName)
{
bool bChangesOccurred = false;
TWeakObjectPtr<ACompositingElement> FoundParent;
TWeakObjectPtr<ACompositingElement> FoundElement;
if (TryGetElement(ParentName, FoundParent) && TryGetElement(ElementName, FoundElement))
{
if (FoundParent.IsValid() && FoundElement.IsValid())
{
const bool bIsParentChildActor = FoundParent->GetParentComponent() != nullptr;
const bool bIsElementChildActor = FoundElement->GetParentComponent() != nullptr;
if (!bIsParentChildActor && !bIsElementChildActor)
{
if (FoundParent->GetLevel() == FoundElement->GetLevel())
{
FoundParent->Modify();
FoundElement->Modify();
bChangesOccurred = FoundParent->AttachAsChildLayer(FoundElement.Get());
}
else
{
// @TODO: Log/Toast an error - preventing elements from different levels linking together
}
}
}
}
if (bChangesOccurred)
{
//TODO: Call full reset from View Model so we don't need to here
//CompsChanged.Broadcast(ECompElementEdActions::Attached, FoundElement, NAME_None);
CompsChanged.Broadcast(ECompElementEdActions::Reset, nullptr, NAME_None);
}
return bChangesOccurred;
}
bool FCompElementManager::SelectElementActors(const TArray<FName>& ElementNames, bool bSelect, bool bNotify, bool bSelectEvenIfHidden, const TSharedPtr<FActorFilter>& Filter)
{
if (ElementNames.Num() == 0)
{
return true;
}
Editor->GetSelectedActors()->BeginBatchSelectOperation();
bool bChangesOccurred = false;
for (const TWeakObjectPtr<ACompositingElement>& ElementPtr : *ElementsContainer)
{
if (!ElementPtr.IsValid())
{
continue;
}
ACompositingElement* Element = ElementPtr.Get();
if (!ElementNames.Contains(Element->GetCompElementName()))
{
continue;
}
if (Filter.IsValid() && !Filter->PassesFilter(Element))
{
continue;
}
Editor->GetSelectedActors()->Modify();
Editor->SelectActor(Element, bSelect, /*bNotifyForActor =*/false, bSelectEvenIfHidden);
bChangesOccurred = true;
}
Editor->GetSelectedActors()->EndBatchSelectOperation();
if (bNotify)
{
Editor->NoteSelectionChange();
}
return bChangesOccurred;
}
void FCompElementManager::ToggleElementRendering(const FName& ElementName)
{
const TWeakObjectPtr<ACompositingElement> Element = EnsureElementExists(ElementName);
if (ensure(Element != nullptr))
{
Element->Modify();
Element->SetAutoRun(!Element->bAutoRun);
CompsChanged.Broadcast(ECompElementEdActions::Modify, Element, TEXT("bAutoRun"));
}
}
void FCompElementManager::ToggleElementFreezeFrame(const FName& ElementName)
{
const TWeakObjectPtr<ACompositingElement> Element = EnsureElementExists(ElementName);
if (ensure(Element != nullptr))
{
Element->Modify();
ETargetUsageFlags FreezeFlags = ETargetUsageFlags::USAGE_Input | ETargetUsageFlags::USAGE_Transform;
if (Element->FreezeFrameController.HasAllFlags(FreezeFlags))
{
if (Element->FreezeFrameController.ClearFreezeFlags())
{
RequestRedraw();
}
}
else
{
Element->FreezeFrameController.SetFreezeFlags(FreezeFlags);
}
CompsChanged.Broadcast(ECompElementEdActions::Modify, Element, TEXT("FreezeFrameMask"));
}
}
void FCompElementManager::ToggleMediaCapture(const FName& ElementName)
{
TWeakObjectPtr<ACompositingElement> FoundComp;
if (TryGetElement(ElementName, FoundComp) && FoundComp.IsValid())
{
UCompositingMediaCaptureOutput* MediaOutputPass = Cast<UCompositingMediaCaptureOutput>(FoundComp->FindOutputPass(UCompositingMediaCaptureOutput::StaticClass()));
if (!MediaOutputPass || !MediaOutputPass->CaptureOutput)
{
MediaOutputPass = ResetMediaCapture(ElementName);
MediaOutputPass->SetPassEnabled(true);
RequestRedraw();
}
else
{
const bool bRequestRedraw = !MediaOutputPass->IsPassEnabled();
MediaOutputPass->Modify();
MediaOutputPass->SetPassEnabled(!MediaOutputPass->IsPassEnabled());
if (bRequestRedraw)
{
RequestRedraw();
}
}
}
}
UCompositingMediaCaptureOutput* FCompElementManager::ResetMediaCapture(const FName& ElementName)
{
TWeakObjectPtr<ACompositingElement> FoundComp;
if (TryGetElement(ElementName, FoundComp) && FoundComp.IsValid())
{
UCompositingMediaCaptureOutput* MediaOutputPass = Cast<UCompositingMediaCaptureOutput>(FoundComp->FindOutputPass(UCompositingMediaCaptureOutput::StaticClass()));
if (!MediaOutputPass)
{
FoundComp->Modify();
MediaOutputPass = FoundComp->AddNewPass<UCompositingMediaCaptureOutput>(TEXT("MediaCapture"));
}
FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
FOpenAssetDialogConfig SelectAssetConfig;
SelectAssetConfig.DialogTitleOverride = LOCTEXT("ChooseMediaOutputTitle", "Choose a media output");
SelectAssetConfig.bAllowMultipleSelection = false;
SelectAssetConfig.DefaultPath = TEXT("/Game");
for (TObjectIterator<UClass> ClassIt; ClassIt; ++ClassIt)
{
if (ClassIt->IsChildOf(UMediaOutput::StaticClass()) && !ClassIt->HasAnyClassFlags(CLASS_Abstract))
{
SelectAssetConfig.AssetClassNames.Add(ClassIt->GetClassPathName());
}
}
TArray<FAssetData> AssetData = ContentBrowserModule.Get().CreateModalOpenAssetDialog(SelectAssetConfig);
if (AssetData.Num() > 0)
{
UMediaOutput* MediaOutputAsset = Cast<UMediaOutput>(AssetData[0].GetAsset());
if (MediaOutputAsset)
{
MediaOutputPass->Modify();
MediaOutputPass->CaptureOutput = MediaOutputAsset;
}
}
return MediaOutputPass;
}
return nullptr;
}
void FCompElementManager::RemoveMediaCapture(const FName& ElementName)
{
TWeakObjectPtr<ACompositingElement> FoundComp;
if (TryGetElement(ElementName, FoundComp) && FoundComp.IsValid())
{
FoundComp->Modify();
FoundComp->RemovePassesOfType(UCompositingMediaCaptureOutput::StaticClass());
}
}
void FCompElementManager::RefreshElementsList()
{
if (ElementsContainer)
{
ElementsContainer->RebuildEditorElementsList();
}
CompsChanged.Broadcast(ECompElementEdActions::Reset, nullptr, NAME_None);
}
void FCompElementManager::RequestRedraw()
{
if (!EditorCompositingViewport.IsValid() && FSlateApplication::IsInitialized())
{
EditorCompositingViewport = MakeShareable(new FCompositingViewportClient(ElementsContainer));
}
if (EditorCompositingViewport.IsValid())
{
EditorCompositingViewport->RedrawRequested(EditorCompositingViewport->Viewport);
}
}
bool FCompElementManager::IsDrawing(ACompositingElement* CompElement) const
{
if (CompElement && EditorCompositingViewport.IsValid() && EditorCompositingViewport->IsDrawing())
{
return ElementsContainer && ElementsContainer->Contains(CompElement);
}
return false;
}
void FCompElementManager::AddReferencedObjects(FReferenceCollector& Collector)
{
Collector.AddReferencedObject(ElementsContainer);
}
UWorld* FCompElementManager::GetWorld() const
{
UWorld* TargetWorld = ElementsContainer->GetWorld();
if (!TargetWorld && Editor.IsValid())
{
TargetWorld = Editor->GetEditorWorldContext().World();
}
return TargetWorld;
}
TWeakObjectPtr<ACompositingElement> FCompElementManager::EnsureElementExists(const FName& ElementName)
{
TWeakObjectPtr<ACompositingElement> Element;
if (!TryGetElement(ElementName, Element))
{
Element = CreateElement(ElementName, ACompositingElement::StaticClass());
}
return Element;
}
void FCompElementManager::OnLevelActorAdded(AActor* InActor)
{
if (ACompositingElement* AsCompElement = Cast<ACompositingElement>(InActor))
{
const bool bAdded = ElementsContainer->Add(AsCompElement);
if (bAdded)
{
AsCompElement->OnConstructed.AddRaw(this, &FCompElementManager::OnCompElementConstructed);
TWeakObjectPtr<ACompositingElement> NewCompPtr(AsCompElement);
CompsChanged.Broadcast(ECompElementEdActions::Add, NewCompPtr, NAME_None);
}
}
}
void FCompElementManager::OnLevelActorRemoved(AActor* InActor)
{
ACompositingElement* ElementActor = Cast<ACompositingElement>(InActor);
if (ElementActor)
{
if (!GIsReinstancing)
{
if (ACompositingElement* Parent = ElementActor->GetElementParent())
{
Parent->Modify();
Parent->DetatchAsChildLayer(ElementActor);
}
for (ACompositingElement* Child : ElementActor->GetChildElements())
{
if (Child)
{
Child->Modify();
ElementActor->DetatchAsChildLayer(Child);
}
}
}
PendingDeletion.Remove(ElementActor);
bool bRemoved = ElementsContainer->Remove(ElementActor);
if (bRemoved)
{
CompsChanged.Broadcast(ECompElementEdActions::Delete, nullptr, NAME_None);
}
}
if (GIsReinstancing)
{
CompsChanged.Broadcast(ECompElementEdActions::Reset, nullptr, NAME_None);
}
}
void FCompElementManager::OnBlueprintCompiled()
{
CompsChanged.Broadcast(ECompElementEdActions::Reset, nullptr, NAME_None);
}
void FCompElementManager::OnCompElementConstructed(ACompositingElement* ConstructedElement)
{
ConstructedElement->OnConstructed.RemoveAll(this);
if (UChildActorComponent* ChildActorComp = ConstructedElement->GetParentComponent())
{
// @TODO: this reset only needs to happen once for the whole actor, but as far as I can tell
// there's no hook for after an actor and all its children have been constructed
CompsChanged.Broadcast(ECompElementEdActions::Reset, ConstructedElement, NAME_None);
}
}
void FCompElementManager::OnEditorMapChange(uint32 /*MapChangeFlags*/)
{
RefreshElementsList();
}
void FCompElementManager::OnWorldAdded(UWorld* InWorld)
{
if (InWorld && InWorld->WorldType == EWorldType::Editor)
{
RefreshElementsList();
}
}
void FCompElementManager::OnWorldRemoved(UWorld* InWorld)
{
if (!InWorld || InWorld->WorldType == EWorldType::Editor)
{
RefreshElementsList();
}
}
void FCompElementManager::OnWorldLevelsChange(ULevel* /*InLevel*/, UWorld* InWorld)
{
if (InWorld && InWorld->WorldType == EWorldType::Editor)
{
RefreshElementsList();
}
}
void FCompElementManager::OnLevelActorsListChange()
{
RefreshElementsList();
}
void FCompElementManager::OnCreateNewElement(AActor* NewElement)
{
OnLevelActorAdded(NewElement);
RefreshElementsList();
}
void FCompElementManager::OnDeleteElement(AActor* ElementToDelete)
{
OnLevelActorRemoved(ElementToDelete);
RefreshElementsList();
}
#undef LOCTEXT_NAMESPACE