// 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& 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(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 FCompElementManager::CreateElement(const FName& ElementName, TSubclassOf ClassType, AActor* LevelContext, EObjectFlags ObjectFlags) { ACompositingElement* SpawnedActor = nullptr; UWorld* TargetWorld = nullptr; if (LevelContext) { TargetWorld = LevelContext->GetWorld(); } else { for (TWeakObjectPtr 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(ClassType, FTransform::Identity, SpawnParams); if (SpawnedActor) { SpawnedActor->SetElementName(ElementName); } } return SpawnedActor; } TWeakObjectPtr FCompElementManager::GetElement(const FName& ElementName) const { for (TWeakObjectPtr Element : *ElementsContainer) { if (Element.IsValid() && Element->GetCompElementName() == ElementName) { return Element; } } return nullptr; } bool FCompElementManager::TryGetElement(const FName& ElementName, TWeakObjectPtr& OutElement) { OutElement = GetElement(ElementName); return OutElement != nullptr; } TArray FCompElementManager::FindNamesOfAllChildElements(FName ElementName) const { TArray Output; TWeakObjectPtr 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 >& OutElements) const { OutElements = *ElementsContainer; } void FCompElementManager::DeleteElementAndChildren(const FName& ElementToDelete, bool bIsCalledFromEditor) { TArray NameCollection = FindNamesOfAllChildElements(ElementToDelete); NameCollection.Add(ElementToDelete); DeleteElements(NameCollection,bIsCalledFromEditor); } void FCompElementManager::DeleteElements(const TArray& 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 > ValidElementsToDelete; ValidElementsToDelete.Reserve(ElementsToDelete.Num()); for (const FName& ElementName : ElementsToDelete) { TWeakObjectPtr Element; if (TryGetElement(ElementName, OUT Element)) { ValidElementsToDelete.Add(Element); } } TArray< TWeakObjectPtr > ExcessSelectedObjs; int32 SelectionCount = 0; for (FSelectionIterator SelectionIt(Editor->GetSelectedActorIterator()); SelectionIt; ++SelectionIt, ++SelectionCount) { TWeakObjectPtr 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 > OldParents; OldParents.Reserve(ValidElementsToDelete.Num()); PendingDeletion.Reserve(ValidElementsToDelete.Num()); for (const TWeakObjectPtr& 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& 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& Element : PendingDeletion) { if (Element.IsValid()) { const int32 ElementIndex = ValidElementsToDelete.Find(Element); if (ElementIndex != INDEX_NONE) { const TWeakObjectPtr& 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 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 FoundParent; TWeakObjectPtr 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& ElementNames, bool bSelect, bool bNotify, bool bSelectEvenIfHidden, const TSharedPtr& Filter) { if (ElementNames.Num() == 0) { return true; } Editor->GetSelectedActors()->BeginBatchSelectOperation(); bool bChangesOccurred = false; for (const TWeakObjectPtr& 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 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 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 FoundComp; if (TryGetElement(ElementName, FoundComp) && FoundComp.IsValid()) { UCompositingMediaCaptureOutput* MediaOutputPass = Cast(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 FoundComp; if (TryGetElement(ElementName, FoundComp) && FoundComp.IsValid()) { UCompositingMediaCaptureOutput* MediaOutputPass = Cast(FoundComp->FindOutputPass(UCompositingMediaCaptureOutput::StaticClass())); if (!MediaOutputPass) { FoundComp->Modify(); MediaOutputPass = FoundComp->AddNewPass(TEXT("MediaCapture")); } FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); FOpenAssetDialogConfig SelectAssetConfig; SelectAssetConfig.DialogTitleOverride = LOCTEXT("ChooseMediaOutputTitle", "Choose a media output"); SelectAssetConfig.bAllowMultipleSelection = false; SelectAssetConfig.DefaultPath = TEXT("/Game"); for (TObjectIterator ClassIt; ClassIt; ++ClassIt) { if (ClassIt->IsChildOf(UMediaOutput::StaticClass()) && !ClassIt->HasAnyClassFlags(CLASS_Abstract)) { SelectAssetConfig.AssetClassNames.Add(ClassIt->GetClassPathName()); } } TArray AssetData = ContentBrowserModule.Get().CreateModalOpenAssetDialog(SelectAssetConfig); if (AssetData.Num() > 0) { UMediaOutput* MediaOutputAsset = Cast(AssetData[0].GetAsset()); if (MediaOutputAsset) { MediaOutputPass->Modify(); MediaOutputPass->CaptureOutput = MediaOutputAsset; } } return MediaOutputPass; } return nullptr; } void FCompElementManager::RemoveMediaCapture(const FName& ElementName) { TWeakObjectPtr 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 FCompElementManager::EnsureElementExists(const FName& ElementName) { TWeakObjectPtr Element; if (!TryGetElement(ElementName, Element)) { Element = CreateElement(ElementName, ACompositingElement::StaticClass()); } return Element; } void FCompElementManager::OnLevelActorAdded(AActor* InActor) { if (ACompositingElement* AsCompElement = Cast(InActor)) { const bool bAdded = ElementsContainer->Add(AsCompElement); if (bAdded) { AsCompElement->OnConstructed.AddRaw(this, &FCompElementManager::OnCompElementConstructed); TWeakObjectPtr NewCompPtr(AsCompElement); CompsChanged.Broadcast(ECompElementEdActions::Add, NewCompPtr, NAME_None); } } } void FCompElementManager::OnLevelActorRemoved(AActor* InActor) { ACompositingElement* ElementActor = Cast(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