2548 lines
96 KiB
C++
2548 lines
96 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "Misc/MessageDialog.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "UObject/ObjectMacros.h"
|
|
#include "UObject/Object.h"
|
|
#include "UObject/GarbageCollection.h"
|
|
#include "Templates/SubclassOf.h"
|
|
#include "Engine/EngineTypes.h"
|
|
#include "Engine/Level.h"
|
|
#include "Components/ActorComponent.h"
|
|
#include "GameFramework/Actor.h"
|
|
#include "GameFramework/Pawn.h"
|
|
#include "Engine/World.h"
|
|
#include "AI/NavigationSystemBase.h"
|
|
#include "Components/LightComponent.h"
|
|
#include "Model.h"
|
|
#include "Exporters/Exporter.h"
|
|
#include "Components/SkeletalMeshComponent.h"
|
|
#include "Engine/Brush.h"
|
|
#include "Editor/EditorEngine.h"
|
|
#include "Editor/UnrealEdEngine.h"
|
|
#include "Factories/LevelFactory.h"
|
|
#include "Editor/GroupActor.h"
|
|
#include "Animation/SkeletalMeshActor.h"
|
|
#include "Particles/Emitter.h"
|
|
#include "Misc/FeedbackContext.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
#include "UObject/PropertyPortFlags.h"
|
|
#include "GameFramework/WorldSettings.h"
|
|
#include "Engine/LevelScriptActor.h"
|
|
#include "Engine/Light.h"
|
|
#include "Engine/StaticMeshActor.h"
|
|
#include "Components/ChildActorComponent.h"
|
|
#include "Engine/Polys.h"
|
|
#include "Kismet2/ComponentEditorUtils.h"
|
|
#include "Engine/Selection.h"
|
|
#include "EngineUtils.h"
|
|
#include "EditorModeManager.h"
|
|
#include "EditorModes.h"
|
|
#include "Dialogs/Dialogs.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "Engine/LevelStreaming.h"
|
|
#include "LevelUtils.h"
|
|
#include "BusyCursor.h"
|
|
#include "BSPOps.h"
|
|
#include "EditorLevelUtils.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
#include "LevelEditorViewport.h"
|
|
#include "Layers/LayersSubsystem.h"
|
|
#include "ActorEditorUtils.h"
|
|
#include "Particles/ParticleSystemComponent.h"
|
|
#include "UnrealExporter.h"
|
|
#include "LevelEditor.h"
|
|
#include "Engine/LODActor.h"
|
|
#include "Settings/LevelEditorMiscSettings.h"
|
|
#include "Settings/EditorProjectSettings.h"
|
|
#include "ActorGroupingUtils.h"
|
|
#include "HAL/PlatformApplicationMisc.h"
|
|
#include "IAssetTools.h"
|
|
#include "AssetToolsModule.h"
|
|
#include "AssetSelection.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "EdMode.h"
|
|
#include "Subsystems/BrushEditingSubsystem.h"
|
|
#include "Misc/ScopedSlowTask.h"
|
|
#include "Subsystems/AssetEditorSubsystem.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "UnrealEd.EditorActor"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogEditorActor, Log, All);
|
|
|
|
static int32 RecomputePoly(ABrush* InOwner, FPoly* Poly)
|
|
{
|
|
// force recalculation of normal, and texture U and V coordinates in FPoly::Finalize()
|
|
Poly->Normal = FVector::ZeroVector;
|
|
|
|
return Poly->Finalize(InOwner, 0);
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
Actor adding/deleting functions.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
void UUnrealEdEngine::edactCopySelected(UWorld* InWorld, FString* DestinationData)
|
|
{
|
|
if (GetSelectedComponentCount() > 0)
|
|
{
|
|
// Copy selected components
|
|
TArray<UActorComponent*> SelectedComponents;
|
|
for (FSelectionIterator It(GetSelectedComponentIterator()); It; ++It)
|
|
{
|
|
SelectedComponents.Add(CastChecked<UActorComponent>(*It));
|
|
}
|
|
|
|
FComponentEditorUtils::CopyComponents(SelectedComponents);
|
|
}
|
|
else
|
|
{
|
|
// Copy Actors
|
|
// Before copying, deselect:
|
|
// - Actors belonging to prefabs unless all actors in the prefab are selected.
|
|
// - Builder brushes.
|
|
// - World Settings.
|
|
TArray<AActor*> ActorsToDeselect;
|
|
|
|
bool bSomeSelectedActorsNotInCurrentLevel = false;
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
|
|
// Deselect any selected builder brushes.
|
|
ABrush* Brush = Cast<ABrush>(Actor);
|
|
const bool bActorIsBuilderBrush = (Brush && FActorEditorUtils::IsABuilderBrush(Brush));
|
|
if (bActorIsBuilderBrush)
|
|
{
|
|
ActorsToDeselect.Add(Actor);
|
|
}
|
|
|
|
// Deselect world settings
|
|
if (Actor->IsA(AWorldSettings::StaticClass()))
|
|
{
|
|
ActorsToDeselect.Add(Actor);
|
|
}
|
|
|
|
// If any selected actors are not in the current level, warn the user that some actors will not be copied.
|
|
if (!bSomeSelectedActorsNotInCurrentLevel && !Actor->GetLevel()->IsCurrentLevel())
|
|
{
|
|
bSomeSelectedActorsNotInCurrentLevel = true;
|
|
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "CopySelectedActorsInNonCurrentLevel", "Some selected actors are not in the current level and will not be copied."));
|
|
}
|
|
}
|
|
|
|
const FScopedBusyCursor BusyCursor;
|
|
for (int32 ActorIndex = 0; ActorIndex < ActorsToDeselect.Num(); ++ActorIndex)
|
|
{
|
|
AActor* Actor = ActorsToDeselect[ActorIndex];
|
|
GetSelectedActors()->Deselect(Actor);
|
|
}
|
|
|
|
// Export the actors.
|
|
FStringOutputDevice Ar;
|
|
const FSelectedActorExportObjectInnerContext Context;
|
|
UExporter::ExportToOutputDevice(&Context, InWorld, NULL, Ar, TEXT("copy"), 0, PPF_DeepCompareInstances | PPF_ExportsNotFullyQualified);
|
|
FPlatformApplicationMisc::ClipboardCopy(*Ar);
|
|
if (DestinationData)
|
|
{
|
|
*DestinationData = MoveTemp(Ar);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates offsets for locations based on the editor grid size and active viewport.
|
|
*/
|
|
static FVector CreateLocationOffset(bool bDuplicate, bool bOffsetLocations)
|
|
{
|
|
const float Offset = static_cast<float>(bOffsetLocations ? GEditor->GetGridSize() : 0);
|
|
FVector LocationOffset(Offset, Offset, Offset);
|
|
if (bDuplicate && GCurrentLevelEditingViewportClient)
|
|
{
|
|
switch (GCurrentLevelEditingViewportClient->ViewportType)
|
|
{
|
|
case LVT_OrthoXZ:
|
|
LocationOffset = FVector(Offset, 0.f, Offset);
|
|
break;
|
|
case LVT_OrthoYZ:
|
|
LocationOffset = FVector(0.f, Offset, Offset);
|
|
break;
|
|
default:
|
|
LocationOffset = FVector(Offset, Offset, 0.f);
|
|
break;
|
|
}
|
|
}
|
|
return LocationOffset;
|
|
}
|
|
|
|
bool UUnrealEdEngine::WarnIfDestinationLevelIsHidden(UWorld* InWorld)
|
|
{
|
|
bool bShouldLoadHiddenLevels = true;
|
|
bool bShowPasteHiddenWarning = true;
|
|
TArray<ULevel*> Levels;
|
|
TArray<bool> bTheyShouldBeVisible;
|
|
// prepare the warning dialog
|
|
FSuppressableWarningDialog::FSetupInfo Info(LOCTEXT("Warning_PasteWarningBody", "You are trying to paste to a hidden level.\nSuppressing this will default to Do Not Paste"), LOCTEXT("Warning_PasteWarningHeader", "Pasting To Hidden Level"), "PasteHiddenWarning");
|
|
Info.ConfirmText = LOCTEXT("Warning_PasteContinue", "Unhide Level and paste");
|
|
Info.CancelText = LOCTEXT("Warning_PasteCancel", "Do not paste");
|
|
|
|
// check streaming levels first
|
|
for (ULevelStreaming* StreamedLevel: InWorld->GetStreamingLevels())
|
|
{
|
|
// this is the active level - check if it is visible
|
|
if (StreamedLevel && StreamedLevel->GetShouldBeVisibleInEditor() == false)
|
|
{
|
|
ULevel* Level = StreamedLevel->GetLoadedLevel();
|
|
if (Level && Level->IsCurrentLevel())
|
|
{
|
|
if (bShowPasteHiddenWarning)
|
|
{
|
|
bShowPasteHiddenWarning = false;
|
|
// the streamed level is not visible - check what the user wants to do
|
|
bShouldLoadHiddenLevels = (FSuppressableWarningDialog(Info).ShowModal() == FSuppressableWarningDialog::Confirm);
|
|
}
|
|
|
|
if (bShouldLoadHiddenLevels)
|
|
{
|
|
Levels.Add(Level);
|
|
bTheyShouldBeVisible.Add(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// now check the active level (this handles the persistent level also)
|
|
if (bShouldLoadHiddenLevels)
|
|
{
|
|
if (FLevelUtils::IsLevelVisible(InWorld->GetCurrentLevel()) == false)
|
|
{
|
|
if (bShowPasteHiddenWarning)
|
|
{
|
|
bShowPasteHiddenWarning = false;
|
|
// the streamed level is not visible - check what the user wants to do
|
|
bShouldLoadHiddenLevels = (FSuppressableWarningDialog(Info).ShowModal() == FSuppressableWarningDialog::Confirm);
|
|
}
|
|
|
|
if (bShouldLoadHiddenLevels)
|
|
{
|
|
Levels.Add(InWorld->GetCurrentLevel());
|
|
bTheyShouldBeVisible.Add(true);
|
|
}
|
|
}
|
|
}
|
|
// For efficiency, set visibility of all levels at once
|
|
if (Levels.Num() > 0)
|
|
{
|
|
EditorLevelUtils::SetLevelsVisibility(Levels, bTheyShouldBeVisible, true);
|
|
}
|
|
return !bShouldLoadHiddenLevels;
|
|
}
|
|
|
|
void UUnrealEdEngine::edactPasteSelected(UWorld* InWorld, bool bDuplicate, bool bOffsetLocations, bool bWarnIfHidden, FString* SourceData)
|
|
{
|
|
// check and warn if the user is trying to paste to a hidden level. This will return if he wishes to abort the process
|
|
if (bWarnIfHidden && WarnIfDestinationLevelIsHidden(InWorld) == true)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (GetSelectedComponentCount() > 0)
|
|
{
|
|
AActor* SelectedActor = CastChecked<AActor>(*GetSelectedActorIterator());
|
|
|
|
TArray<UActorComponent*> PastedComponents;
|
|
FComponentEditorUtils::PasteComponents(PastedComponents, SelectedActor, SelectedActor->GetRootComponent());
|
|
|
|
if (PastedComponents.Num() > 0)
|
|
{
|
|
// Make sure all the SCS trees have a chance to update
|
|
FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
|
|
LevelEditor.BroadcastComponentsEdited();
|
|
|
|
// Select the new clones
|
|
USelection* ComponentSelection = GetSelectedComponents();
|
|
ComponentSelection->Modify(false);
|
|
ComponentSelection->BeginBatchSelectOperation();
|
|
ComponentSelection->DeselectAll();
|
|
|
|
for (UActorComponent* PastedComp: PastedComponents)
|
|
{
|
|
GEditor->SelectComponent(PastedComp, true, false);
|
|
}
|
|
|
|
ComponentSelection->EndBatchSelectOperation(true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const FScopedBusyCursor BusyCursor;
|
|
|
|
// Create a location offset.
|
|
const FVector LocationOffset = CreateLocationOffset(bDuplicate, bOffsetLocations);
|
|
|
|
FCachedActorLabels ActorLabels(InWorld);
|
|
|
|
// Transact the current selection set.
|
|
USelection* SelectedActors = GetSelectedActors();
|
|
SelectedActors->Modify();
|
|
|
|
// Get pasted text.
|
|
FString PasteString;
|
|
if (SourceData)
|
|
{
|
|
PasteString = *SourceData;
|
|
}
|
|
else
|
|
{
|
|
FPlatformApplicationMisc::ClipboardPaste(PasteString);
|
|
}
|
|
const TCHAR* Paste = *PasteString;
|
|
|
|
// Turn off automatic BSP update while pasting to save rebuilding geometry potentially multiple times
|
|
const bool bBSPAutoUpdate = GetDefault<ULevelEditorMiscSettings>()->bBSPAutoUpdate;
|
|
GetMutableDefault<ULevelEditorMiscSettings>()->bBSPAutoUpdate = false;
|
|
|
|
// Import the actors.
|
|
ULevelFactory* Factory = NewObject<ULevelFactory>();
|
|
Factory->FactoryCreateText(ULevel::StaticClass(), InWorld->GetCurrentLevel(), InWorld->GetCurrentLevel()->GetFName(), RF_Transactional, NULL, bDuplicate ? TEXT("move") : TEXT("paste"), Paste, Paste + FCString::Strlen(Paste), GWarn);
|
|
|
|
// Reinstate old BSP update setting, and force a rebuild - any levels whose geometry has changed while pasting will be rebuilt
|
|
GetMutableDefault<ULevelEditorMiscSettings>()->bBSPAutoUpdate = bBSPAutoUpdate;
|
|
RebuildAlteredBSP();
|
|
|
|
// Fire ULevel::LevelDirtiedEvent when falling out of scope.
|
|
FScopedLevelDirtied LevelDirtyCallback;
|
|
|
|
// Update the actors' locations and update the global list of visible layers.
|
|
ULayersSubsystem* LayersSubsystem = GEditor->GetEditorSubsystem<ULayersSubsystem>();
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
|
|
// We only want to offset the location if this actor is the root of a selected attachment hierarchy
|
|
// Offsetting children of an attachment hierarchy would cause them to drift away from the node they're attached to
|
|
// as the offset would effectively get applied twice
|
|
const AActor* const ParentActor = Actor->GetAttachParentActor();
|
|
const FVector& ActorLocationOffset = (ParentActor && ParentActor->IsSelected()) ? FVector::ZeroVector : LocationOffset;
|
|
|
|
// Offset the actor's location.
|
|
Actor->TeleportTo(Actor->GetActorLocation() + ActorLocationOffset, Actor->GetActorRotation(), false, true);
|
|
|
|
if (!GetDefault<ULevelEditorMiscSettings>()->bAvoidRelabelOnPasteSelected)
|
|
{
|
|
// Re-label duplicated actors so that labels become unique
|
|
FActorLabelUtilities::SetActorLabelUnique(Actor, Actor->GetActorLabel(), &ActorLabels);
|
|
ActorLabels.Add(Actor->GetActorLabel());
|
|
}
|
|
|
|
LayersSubsystem->InitializeNewActorLayers(Actor);
|
|
|
|
// Ensure any layers this actor belongs to are visible
|
|
LayersSubsystem->SetLayersVisibility(Actor->Layers, true);
|
|
|
|
Actor->CheckDefaultSubobjects();
|
|
Actor->InvalidateLightingCache();
|
|
// Call PostEditMove to update components, etc.
|
|
Actor->PostEditMove(true);
|
|
Actor->PostDuplicate(EDuplicateMode::Normal);
|
|
Actor->CheckDefaultSubobjects();
|
|
|
|
// Request saves/refreshes.
|
|
Actor->MarkPackageDirty();
|
|
LevelDirtyCallback.Request();
|
|
}
|
|
// Note the selection change. This will also redraw level viewports and update the pivot.
|
|
NoteSelectionChange();
|
|
}
|
|
}
|
|
|
|
namespace DuplicateSelectedActors
|
|
{
|
|
/**
|
|
* A collection of actors to duplicate and prefabs to instance that all belong to the same level.
|
|
*/
|
|
class FDuplicateJob
|
|
{
|
|
public:
|
|
/** A list of actors to duplicate. */
|
|
TArray<AActor*> Actors;
|
|
|
|
/** The source level that all actors in the Actors array come from. */
|
|
ULevel* SrcLevel;
|
|
|
|
/**
|
|
* Duplicate the job's actors to the specified destination level. The new actors
|
|
* are appended to the specified output lists of actors.
|
|
*
|
|
* @param OutNewActors [out] Newly created actors are appended to this list.
|
|
* @param DestLevel The level to duplicate the actors in this job to.
|
|
* @param bOffsetLocations Passed to edactPasteSelected; true if new actor locations should be offset.
|
|
*/
|
|
void DuplicateActorsToLevel(TArray<AActor*>& OutNewActors, ULevel* DestLevel, bool bOffsetLocations)
|
|
{
|
|
// Check neither level is locked
|
|
if (FLevelUtils::IsLevelLocked(SrcLevel))
|
|
{
|
|
UE_LOG(LogEditorActor, Warning, TEXT("DuplicateActorsToLevel: The requested operation could not be completed because the level is locked."));
|
|
return;
|
|
}
|
|
|
|
TArray<FTransform> ActorTransforms;
|
|
for (AActor* Actor: Actors)
|
|
{
|
|
ActorTransforms.Add(Actor->GetActorTransform());
|
|
}
|
|
|
|
if (!ActorPlacementUtils::IsLevelValidForActorPlacement(DestLevel, ActorTransforms))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Cache the current source level
|
|
ULevel* OldLevel = SrcLevel->OwningWorld->GetCurrentLevel();
|
|
// Set the selection set to be precisely the actors belonging to this job.
|
|
SrcLevel->OwningWorld->SetCurrentLevel(SrcLevel);
|
|
GEditor->SelectNone(false, true);
|
|
for (int32 ActorIndex = 0; ActorIndex < Actors.Num(); ++ActorIndex)
|
|
{
|
|
AActor* Actor = Actors[ActorIndex];
|
|
GEditor->SelectActor(Actor, true, false, true);
|
|
}
|
|
|
|
FString ScratchData;
|
|
// Copy actors from src level.
|
|
GEditor->edactCopySelected(SrcLevel->OwningWorld, &ScratchData);
|
|
// Restore source level
|
|
SrcLevel->OwningWorld->SetCurrentLevel(OldLevel);
|
|
|
|
// Cache the current dest level
|
|
OldLevel = DestLevel->OwningWorld->GetCurrentLevel();
|
|
// Paste to the dest level.
|
|
{
|
|
FLevelPartitionOperationScope LevelPartitionScope(DestLevel);
|
|
DestLevel->OwningWorld->SetCurrentLevel(LevelPartitionScope.GetLevel());
|
|
GEditor->edactPasteSelected(DestLevel->OwningWorld, true, bOffsetLocations, true, &ScratchData);
|
|
// Restore dest level
|
|
DestLevel->OwningWorld->SetCurrentLevel(OldLevel);
|
|
}
|
|
// The selection set will be the newly created actors; copy them over to the output array.
|
|
for (FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
OutNewActors.Add(Actor);
|
|
}
|
|
}
|
|
};
|
|
} // namespace DuplicateSelectedActors
|
|
|
|
void UUnrealEdEngine::edactDuplicateSelected(ULevel* InLevel, bool bOffsetLocations)
|
|
{
|
|
int32 NumSelectedComponents = GetSelectedComponentCount();
|
|
if (NumSelectedComponents > 0)
|
|
{
|
|
TArray<UActorComponent*> NewComponentClones;
|
|
NewComponentClones.Reserve(NumSelectedComponents);
|
|
|
|
// Duplicate selected components
|
|
for (FSelectionIterator It(GetSelectedComponentIterator()); It; ++It)
|
|
{
|
|
UActorComponent* Component = CastChecked<UActorComponent>(*It);
|
|
|
|
if (FComponentEditorUtils::CanCopyComponent(Component))
|
|
{
|
|
if (UActorComponent* Clone = FComponentEditorUtils::DuplicateComponent(Component))
|
|
{
|
|
NewComponentClones.Add(Clone);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NewComponentClones.Num() > 0)
|
|
{
|
|
// Make sure all the SCS trees have a chance to update
|
|
FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
|
|
LevelEditor.BroadcastComponentsEdited();
|
|
|
|
// Select the new clones
|
|
USelection* ComponentSelection = GetSelectedComponents();
|
|
ComponentSelection->Modify(false);
|
|
ComponentSelection->BeginBatchSelectOperation();
|
|
ComponentSelection->DeselectAll();
|
|
|
|
for (UActorComponent* Clone: NewComponentClones)
|
|
{
|
|
GEditor->SelectComponent(Clone, true, false);
|
|
}
|
|
|
|
ComponentSelection->EndBatchSelectOperation(true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
using namespace DuplicateSelectedActors;
|
|
|
|
const FScopedBusyCursor BusyCursor;
|
|
GetSelectedActors()->Modify();
|
|
|
|
// Create per-level job lists.
|
|
typedef TMap<ULevel*, FDuplicateJob*> DuplicateJobMap;
|
|
DuplicateJobMap DuplicateJobs;
|
|
|
|
// Build set of selected actors before duplication
|
|
TArray<AActor*> PreDuplicateSelection;
|
|
|
|
// Add selected actors to the per-level job lists.
|
|
bool bHaveActorLocation = false;
|
|
FVector AnyActorLocation = FVector::ZeroVector;
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
|
|
if (!bHaveActorLocation)
|
|
{
|
|
bHaveActorLocation = true;
|
|
AnyActorLocation = Actor->GetActorLocation();
|
|
}
|
|
|
|
PreDuplicateSelection.Add(Actor);
|
|
|
|
ULevel* OldLevel = Actor->GetLevel();
|
|
FDuplicateJob** Job = DuplicateJobs.Find(OldLevel);
|
|
if (Job)
|
|
{
|
|
(*Job)->Actors.Add(Actor);
|
|
}
|
|
else
|
|
{
|
|
// Allocate a new job for the level.
|
|
FDuplicateJob* NewJob = new FDuplicateJob;
|
|
NewJob->SrcLevel = OldLevel;
|
|
NewJob->Actors.Add(Actor);
|
|
DuplicateJobs.Add(OldLevel, NewJob);
|
|
}
|
|
}
|
|
|
|
UWorld* World = InLevel->OwningWorld;
|
|
ULevel* DesiredLevel = InLevel;
|
|
|
|
USelection* SelectedActors = GetSelectedActors();
|
|
SelectedActors->BeginBatchSelectOperation();
|
|
SelectedActors->Modify();
|
|
|
|
// For each level, select the actors in that level and copy-paste into the destination level.
|
|
TArray<AActor*> NewActors;
|
|
for (DuplicateJobMap::TIterator It(DuplicateJobs); It; ++It)
|
|
{
|
|
FDuplicateJob* Job = It.Value();
|
|
check(Job);
|
|
Job->DuplicateActorsToLevel(NewActors, InLevel, bOffsetLocations);
|
|
}
|
|
|
|
// Select any newly created actors and prefabs.
|
|
SelectNone(false, true);
|
|
for (int32 ActorIndex = 0; ActorIndex < NewActors.Num(); ++ActorIndex)
|
|
{
|
|
AActor* Actor = NewActors[ActorIndex];
|
|
SelectActor(Actor, true, false);
|
|
}
|
|
SelectedActors->EndBatchSelectOperation();
|
|
NoteSelectionChange();
|
|
|
|
// Finally, cleanup.
|
|
for (DuplicateJobMap::TIterator It(DuplicateJobs); It; ++It)
|
|
{
|
|
FDuplicateJob* Job = It.Value();
|
|
delete Job;
|
|
}
|
|
|
|
// Build set of selected actors after duplication
|
|
TArray<AActor*> PostDuplicateSelection;
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
|
|
// We generate new seeds when we duplicate
|
|
Actor->SeedAllRandomStreams();
|
|
|
|
PostDuplicateSelection.Add(Actor);
|
|
}
|
|
|
|
GLevelEditorModeTools().ActorsDuplicatedNotify(PreDuplicateSelection, PostDuplicateSelection, bOffsetLocations);
|
|
}
|
|
}
|
|
|
|
bool UUnrealEdEngine::CanDeleteSelectedActors(const UWorld* InWorld, const bool bStopAtFirst, const bool bLogUndeletable, TArray<AActor*>* OutDeletableActors) const
|
|
{
|
|
// Iterate over all levels and create a list of world infos.
|
|
TArray<AWorldSettings*> WorldSettingss;
|
|
for (int32 LevelIndex = 0; LevelIndex < InWorld->GetNumLevels(); ++LevelIndex)
|
|
{
|
|
ULevel* Level = InWorld->GetLevel(LevelIndex);
|
|
WorldSettingss.Add(Level->GetWorldSettings());
|
|
}
|
|
|
|
// Iterate over selected actors and assemble a list of actors to delete.
|
|
bool bContainsDeletable = false;
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
|
|
// Only delete transactional actors that aren't a level's builder brush or worldsettings.
|
|
bool bDeletable = false;
|
|
FText CannotDeleteReason;
|
|
if (Actor->HasAllFlags(RF_Transactional) && Actor->CanDeleteSelectedActor(CannotDeleteReason))
|
|
{
|
|
// TODO: The Brush and WorldSettings logic should be moved to use the CanDeleteSelectedActor virtual
|
|
ABrush* Brush = Cast<ABrush>(Actor);
|
|
const bool bIsDefaultBrush = Brush && FActorEditorUtils::IsABuilderBrush(Brush);
|
|
if (!bIsDefaultBrush)
|
|
{
|
|
const bool bIsWorldSettings =
|
|
Actor->IsA(AWorldSettings::StaticClass()) && WorldSettingss.Contains(static_cast<AWorldSettings*>(Actor));
|
|
if (!bIsWorldSettings)
|
|
{
|
|
bContainsDeletable = true;
|
|
bDeletable = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Can this actor be deleted
|
|
if (bDeletable)
|
|
{
|
|
if (OutDeletableActors)
|
|
{
|
|
OutDeletableActors->Add(Actor);
|
|
}
|
|
if (bStopAtFirst)
|
|
{
|
|
break; // Did we only want to know if ANY of the actors were deletable
|
|
}
|
|
}
|
|
else if (bLogUndeletable)
|
|
{
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("Name"), FText::FromString(Actor->GetFullName()));
|
|
|
|
FText LogText;
|
|
if (CannotDeleteReason.IsEmpty())
|
|
{
|
|
LogText = FText::Format(LOCTEXT("CannotDeleteSpecialActor", "Cannot delete special actor {Name}"), Arguments);
|
|
}
|
|
else
|
|
{
|
|
Arguments.Add(TEXT("Reason"), CannotDeleteReason);
|
|
LogText = FText::Format(LOCTEXT("CannotDeleteActorWithReason", "Cannot delete actor {Name} - {Reason}"), Arguments);
|
|
}
|
|
UE_LOG(LogEditorActor, Log, TEXT("%s"), *LogText.ToString());
|
|
}
|
|
}
|
|
return bContainsDeletable;
|
|
}
|
|
|
|
bool UUnrealEdEngine::edactDeleteSelected(UWorld* InWorld, bool bVerifyDeletionCanHappen, bool bWarnAboutReferences, bool bWarnAboutSoftReferences)
|
|
{
|
|
if (bVerifyDeletionCanHappen)
|
|
{
|
|
// Provide the option to abort the delete
|
|
if (ShouldAbortActorDeletion())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const double StartSeconds = FPlatformTime::Seconds();
|
|
|
|
// Aggregate the time we waited on user input to remove it from the total time
|
|
double DialogWaitingSeconds = 0;
|
|
|
|
FSlateApplication::Get().CancelDragDrop();
|
|
|
|
if (GetSelectedComponentCount() > 0)
|
|
{
|
|
TArray<UActorComponent*> SelectedEditableComponents;
|
|
for (FSelectedEditableComponentIterator It(GetSelectedEditableComponentIterator()); It; ++It)
|
|
{
|
|
SelectedEditableComponents.Add(CastChecked<UActorComponent>(*It));
|
|
}
|
|
|
|
if (SelectedEditableComponents.Num() > 0)
|
|
{
|
|
// Modify the actor that owns the selected components
|
|
check(GetSelectedActorCount() == 1);
|
|
(*GetSelectedActorIterator())->Modify();
|
|
|
|
// Delete the selected components
|
|
UActorComponent* ComponentToSelect = nullptr;
|
|
int32 NumDeletedComponents = FComponentEditorUtils::DeleteComponents(SelectedEditableComponents, ComponentToSelect);
|
|
|
|
if (NumDeletedComponents > 0)
|
|
{
|
|
// Make sure all the SCS trees have a chance to rebuild
|
|
FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
|
|
LevelEditor.BroadcastComponentsEdited();
|
|
|
|
// Update the editor component selection if possible
|
|
if (ComponentToSelect)
|
|
{
|
|
USelection* ComponentSelection = GetSelectedComponents();
|
|
ComponentSelection->Modify(false);
|
|
ComponentSelection->BeginBatchSelectOperation();
|
|
ComponentSelection->DeselectAll();
|
|
|
|
GEditor->SelectComponent(ComponentToSelect, true, false);
|
|
|
|
// Make sure the selection changed event fires so the SCS trees can update their selection
|
|
ComponentSelection->MarkBatchDirty();
|
|
ComponentSelection->EndBatchSelectOperation(true);
|
|
|
|
// Notify the level editor of the new component selection
|
|
NoteSelectionChange();
|
|
}
|
|
|
|
UE_LOG(LogEditorActor, Log, TEXT("Deleted %d Components (%3.3f secs)"), NumDeletedComponents, FPlatformTime::Seconds() - StartSeconds);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
GetSelectedActors()->Modify();
|
|
|
|
// Fire ULevel::LevelDirtiedEvent when falling out of scope.
|
|
FScopedLevelDirtied LevelDirtyCallback;
|
|
|
|
// Get a list of all the deletable actors in the selection
|
|
TArray<AActor*> ActorsToDelete;
|
|
CanDeleteSelectedActors(InWorld, false, true, &ActorsToDelete);
|
|
|
|
// Maintain a list of levels that have already been Modify()'d so that each level
|
|
// is modify'd only once.
|
|
TArray<ULevel*> LevelsAlreadyModified;
|
|
// A list of levels that will need their Bsp updated after the deletion is complete
|
|
TSet<ULevel*> LevelsToRebuildBSP;
|
|
TSet<ULevel*> LevelsToRebuildNavigation;
|
|
|
|
bool bRequestedDeleteAllByLevel = false;
|
|
bool bRequestedDeleteAllByActor = false;
|
|
bool bRequestedDeleteAllBySoftReference = false;
|
|
EAppMsgType::Type MessageType = ActorsToDelete.Num() > 1 ? EAppMsgType::YesNoYesAllNoAll : EAppMsgType::YesNo;
|
|
int32 DeleteCount = 0;
|
|
|
|
USelection* SelectedActors = GetSelectedActors();
|
|
TMap<AActor*, TArray<AActor*>> ReferencingActorsMap;
|
|
TMap<AActor*, TArray<UObject*>> SoftReferencingObjectsMap;
|
|
TArray<UClass*> ClassTypesToIgnore;
|
|
ClassTypesToIgnore.Add(ALevelScriptActor::StaticClass());
|
|
// The delete warning is meant for actor references that affect gameplay. Group actors do not affect gameplay and should not show up as a warning.
|
|
ClassTypesToIgnore.Add(AGroupActor::StaticClass());
|
|
|
|
// If we want to warn about references to the actors to be deleted, it is a lot more efficient to query
|
|
// the world first and build a map of actors referenced by other actors. We can then quickly look this up later on in the loop.
|
|
if (bWarnAboutReferences)
|
|
{
|
|
FBlueprintEditorUtils::GetActorReferenceMap(InWorld, ClassTypesToIgnore, ReferencingActorsMap);
|
|
|
|
if (bWarnAboutSoftReferences)
|
|
{
|
|
FScopedSlowTask SlowTask(ActorsToDelete.Num(), LOCTEXT("ComputeActorSoftReferences", "Computing References"));
|
|
SlowTask.MakeDialogDelayed(1.0f);
|
|
|
|
FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
|
|
|
|
for (int32 ActorIndex = 0; ActorIndex < ActorsToDelete.Num(); ++ActorIndex)
|
|
{
|
|
SlowTask.EnterProgressFrame();
|
|
|
|
TArray<UObject*> SoftReferencingObjects;
|
|
AActor* Actor = ActorsToDelete[ActorIndex];
|
|
|
|
AssetToolsModule.Get().FindSoftReferencesToObject(Actor, SoftReferencingObjects);
|
|
|
|
if (SoftReferencingObjects.Num() > 0)
|
|
{
|
|
SoftReferencingObjectsMap.Add(Actor, SoftReferencingObjects);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ULayersSubsystem* LayersSubsystem = GEditor->GetEditorSubsystem<ULayersSubsystem>();
|
|
for (int32 ActorIndex = 0; ActorIndex < ActorsToDelete.Num(); ++ActorIndex)
|
|
{
|
|
AActor* Actor = ActorsToDelete[ActorIndex];
|
|
|
|
// If actor is referenced by script, ask user if they really want to delete
|
|
ULevelScriptBlueprint* LSB = Actor->GetLevel()->GetLevelScriptBlueprint(true);
|
|
|
|
// Get the array of actors that reference this actor from the cached map we built above.
|
|
TArray<AActor*>* ReferencingActors = nullptr;
|
|
if (bWarnAboutReferences)
|
|
{
|
|
ReferencingActors = ReferencingActorsMap.Find(Actor);
|
|
}
|
|
|
|
TArray<UK2Node*> ReferencedToActorsFromLevelScriptArray;
|
|
FBlueprintEditorUtils::FindReferencesToActorFromLevelScript(LSB, Actor, ReferencedToActorsFromLevelScriptArray);
|
|
|
|
bool bReferencedByLevelScript = bWarnAboutReferences && (nullptr != LSB && ReferencedToActorsFromLevelScriptArray.Num() > 0);
|
|
bool bReferencedByActor = false;
|
|
bool bReferencedByLODActor = false;
|
|
bool bReferencedBySoftReference = false;
|
|
TArray<UObject*>* SoftReferencingObjects = nullptr;
|
|
|
|
if (bWarnAboutSoftReferences)
|
|
{
|
|
SoftReferencingObjects = SoftReferencingObjectsMap.Find(Actor);
|
|
|
|
if (SoftReferencingObjects)
|
|
{
|
|
bReferencedBySoftReference = true;
|
|
}
|
|
}
|
|
|
|
// If there are any referencing actors, make sure that they are reference types that we care about.
|
|
if (ReferencingActors != nullptr)
|
|
{
|
|
for (AActor* ReferencingActor: (*ReferencingActors))
|
|
{
|
|
// Skip to next if we are referencing ourselves
|
|
if (ReferencingActor == Actor)
|
|
{
|
|
continue;
|
|
}
|
|
else if (Cast<ALODActor>(ReferencingActor))
|
|
{
|
|
bReferencedByLODActor = true;
|
|
}
|
|
else
|
|
{
|
|
// If the referencing actor is a child actor that is referencing us, do not treat it
|
|
// as referencing for the purposes of warning about deletion
|
|
UChildActorComponent* ParentComponent = ReferencingActor->GetParentComponent();
|
|
if (ParentComponent == nullptr || ParentComponent->GetOwner() != Actor)
|
|
{
|
|
bReferencedByActor = true;
|
|
|
|
FText ActorReferencedMessage = FText::Format(LOCTEXT("ActorDeleteReferencedMessage", "Actor {0} is referenced by {1}."),
|
|
FText::FromString(Actor->GetActorLabel()),
|
|
FText::FromString(ReferencingActor->GetActorLabel()));
|
|
UE_LOG(LogEditorActor, Log, TEXT("%s"), *ActorReferencedMessage.ToString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// We have references from one or more sources, prompt the user for feedback.
|
|
if (bReferencedByLevelScript || bReferencedByActor || bReferencedBySoftReference || bReferencedByLODActor)
|
|
{
|
|
if ((bReferencedByLevelScript && !bRequestedDeleteAllByLevel) ||
|
|
(bReferencedByActor && !bRequestedDeleteAllByActor) ||
|
|
(bReferencedBySoftReference && !bRequestedDeleteAllBySoftReference))
|
|
{
|
|
FText ConfirmDelete;
|
|
|
|
FString LevelScriptReferenceString;
|
|
|
|
for (int32 i = 0; i < ReferencedToActorsFromLevelScriptArray.Num(); ++i)
|
|
{
|
|
LevelScriptReferenceString += ReferencedToActorsFromLevelScriptArray[i]->GetFindReferenceSearchString();
|
|
|
|
if (bReferencedByLevelScript && bReferencedByActor)
|
|
{
|
|
LevelScriptReferenceString += TEXT(" (Level Blueprint)");
|
|
}
|
|
|
|
LevelScriptReferenceString += TEXT("\n");
|
|
}
|
|
|
|
LevelScriptReferenceString.TrimEndInline();
|
|
|
|
FString ActorReferenceString;
|
|
|
|
if (ReferencingActors != nullptr)
|
|
{
|
|
for (int32 i = 0; i < ReferencingActors->Num(); ++i)
|
|
{
|
|
ActorReferenceString += (*ReferencingActors)[i]->GetActorLabel();
|
|
|
|
if (bReferencedByLevelScript && bReferencedByActor)
|
|
{
|
|
ActorReferenceString += TEXT(" (Other Actor)");
|
|
}
|
|
|
|
ActorReferenceString += TEXT("\n");
|
|
}
|
|
}
|
|
|
|
if (bReferencedBySoftReference)
|
|
{
|
|
for (UObject* ReferencingObject: *SoftReferencingObjects)
|
|
{
|
|
if (AActor* ReferencingActor = Cast<AActor>(ReferencingObject))
|
|
{
|
|
ActorReferenceString += FString::Printf(TEXT("(Soft) Actor %s in %s\n"), *ReferencingActor->GetActorLabel(), *FPackageName::GetLongPackageAssetName(ReferencingActor->GetOutermost()->GetName()));
|
|
}
|
|
else
|
|
{
|
|
ActorReferenceString += FString::Printf(TEXT("(Soft) Object %s\n"), *ReferencingObject->GetPathName());
|
|
}
|
|
}
|
|
}
|
|
|
|
ActorReferenceString.TrimEndInline();
|
|
|
|
if (bReferencedByLevelScript && (bReferencedByActor || bReferencedBySoftReference))
|
|
{
|
|
ConfirmDelete = FText::Format(LOCTEXT("ConfirmDeleteActorReferenceByScriptAndActor", "Actor {0} is referenced by the level blueprint and other Actors/Objects.\nDo you really want to delete it? This will break references.\n\nReference List:\n\n{1}\n{2}"),
|
|
FText::FromString(Actor->GetActorLabel()), FText::FromString(LevelScriptReferenceString), FText::FromString(ActorReferenceString));
|
|
}
|
|
else if (bReferencedByLevelScript)
|
|
{
|
|
ConfirmDelete = FText::Format(LOCTEXT("ConfirmDeleteActorReferencedByScript", "Actor {0} is referenced by the level blueprint.\nDo you really want to delete it? This will break references.\n\nReference List:\n\n{1}"),
|
|
FText::FromString(Actor->GetActorLabel()), FText::FromString(LevelScriptReferenceString));
|
|
}
|
|
else
|
|
{
|
|
ConfirmDelete = FText::Format(LOCTEXT("ConfirmDeleteActorReferencedByActor", "Actor {0} is referenced by other Actors/Objects.\nDo you really want to delete it? This will break references.\n\nReference List:\n\n{1}"),
|
|
FText::FromString(Actor->GetActorLabel()), FText::FromString(ActorReferenceString));
|
|
}
|
|
|
|
const double DialogStartSeconds = FPlatformTime::Seconds();
|
|
|
|
int32 Result = FMessageDialog::Open(MessageType, ConfirmDelete);
|
|
|
|
DialogWaitingSeconds += FPlatformTime::Seconds() - DialogStartSeconds;
|
|
|
|
if (Result == EAppReturnType::YesAll)
|
|
{
|
|
bRequestedDeleteAllByLevel |= bReferencedByLevelScript;
|
|
bRequestedDeleteAllByActor |= bReferencedByActor;
|
|
bRequestedDeleteAllBySoftReference |= bReferencedBySoftReference;
|
|
}
|
|
else if (Result == EAppReturnType::NoAll)
|
|
{
|
|
break;
|
|
}
|
|
else if (Result == EAppReturnType::No || Result == EAppReturnType::Cancel)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (bReferencedByLevelScript)
|
|
{
|
|
FBlueprintEditorUtils::ModifyActorReferencedGraphNodes(LSB, Actor);
|
|
}
|
|
|
|
if (bReferencedByActor || bReferencedByLODActor)
|
|
{
|
|
check(ReferencingActors != nullptr);
|
|
for (int32 ReferencingActorIndex = 0; ReferencingActorIndex < ReferencingActors->Num(); ReferencingActorIndex++)
|
|
{
|
|
AActor* ReferencingActor = (*ReferencingActors)[ReferencingActorIndex];
|
|
ReferencingActor->Modify();
|
|
|
|
ALODActor* LODActor = Cast<ALODActor>(ReferencingActor);
|
|
// it's possible other actor is referencing this
|
|
if (LODActor)
|
|
{
|
|
LODActor->RemoveSubActor(Actor);
|
|
|
|
FText SubActorRemovedMessage = FText::Format(LOCTEXT("LODActorSubActorDeletedMessage", "Sub Actor '{0}' was removed from LODActor '{1}'."),
|
|
FText::FromString(Actor->GetActorLabel()),
|
|
FText::FromString(ReferencingActor->GetActorLabel()));
|
|
UE_LOG(LogEditorActor, Log, TEXT("%s"), *SubActorRemovedMessage.ToString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bRebuildNavigation = false;
|
|
|
|
ABrush* Brush = Cast<ABrush>(Actor);
|
|
if (Brush && !FActorEditorUtils::IsABuilderBrush(Brush)) // Track whether or not a brush actor was deleted.
|
|
{
|
|
ULevel* BrushLevel = Actor->GetLevel();
|
|
if (BrushLevel && !Brush->IsVolumeBrush())
|
|
{
|
|
BrushLevel->Model->Modify();
|
|
LevelsToRebuildBSP.Add(BrushLevel);
|
|
// Rebuilding bsp will also take care of navigation
|
|
LevelsToRebuildNavigation.Remove(BrushLevel);
|
|
}
|
|
else if (BrushLevel && !LevelsToRebuildBSP.Contains(BrushLevel))
|
|
{
|
|
LevelsToRebuildNavigation.Add(BrushLevel);
|
|
}
|
|
}
|
|
|
|
// If the actor about to be deleted is in a group, be sure to remove it from the group
|
|
AGroupActor* ActorParentGroup = AGroupActor::GetParentForActor(Actor);
|
|
if (ActorParentGroup)
|
|
{
|
|
ActorParentGroup->Remove(*Actor);
|
|
}
|
|
|
|
// Remove actor from all asset editors
|
|
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->RemoveAssetFromAllEditors(Actor);
|
|
|
|
// Mark the actor's level as dirty.
|
|
Actor->MarkPackageDirty();
|
|
LevelDirtyCallback.Request();
|
|
|
|
// Deselect the Actor.
|
|
SelectedActors->Deselect(Actor);
|
|
|
|
// Modify the level. Each level is modified only once.
|
|
// @todo DB: Shouldn't this be calling UWorld::ModifyLevel?
|
|
ULevel* Level = Actor->GetLevel();
|
|
if (LevelsAlreadyModified.Find(Level) == INDEX_NONE)
|
|
{
|
|
LevelsAlreadyModified.Add(Level);
|
|
// Don't mark the level dirty when deleting external actors and the level is in `use external actors` mode.
|
|
bool bShouldDirty = !(Actor->IsPackageExternal() && Level->IsUsingExternalActors());
|
|
Level->Modify(bShouldDirty);
|
|
}
|
|
|
|
UE_LOG(LogEditorActor, Log, TEXT("Deleted Actor: %s"), *Actor->GetClass()->GetName());
|
|
|
|
// Destroy actor and clear references.
|
|
LayersSubsystem->DisassociateActorFromLayers(Actor);
|
|
bool WasDestroyed = Actor->GetWorld()->EditorDestroyActor(Actor, false);
|
|
checkf(WasDestroyed, TEXT("Failed to destroy Actor %s (%s)"), *Actor->GetClass()->GetName(), *Actor->GetActorLabel());
|
|
|
|
DeleteCount++;
|
|
}
|
|
|
|
// Remove all references to destroyed actors once at the end, instead of once for each Actor destroyed..
|
|
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
|
|
|
|
// If any brush actors were modified, update the Bsp in the appropriate levels
|
|
if (LevelsToRebuildBSP.Num())
|
|
{
|
|
FlushRenderingCommands();
|
|
|
|
for (ULevel* Level: LevelsToRebuildBSP)
|
|
{
|
|
GEditor->RebuildLevel(*Level);
|
|
}
|
|
}
|
|
|
|
NoteSelectionChange();
|
|
|
|
if (LevelsToRebuildNavigation.Num())
|
|
{
|
|
for (ULevel* Level: LevelsToRebuildNavigation)
|
|
{
|
|
if (Level)
|
|
{
|
|
FNavigationSystem::UpdateLevelCollision(*Level);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (LevelsToRebuildBSP.Num() || LevelsToRebuildNavigation.Num())
|
|
{
|
|
RedrawLevelEditingViewports();
|
|
ULevel::LevelDirtiedEvent.Broadcast();
|
|
}
|
|
|
|
UE_LOG(LogEditorActor, Log, TEXT("Deleted %d Actors (%3.3f secs)"), DeleteCount, (FPlatformTime::Seconds() - StartSeconds) - DialogWaitingSeconds);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UUnrealEdEngine::ShouldAbortActorDeletion() const
|
|
{
|
|
bool bResult = false;
|
|
|
|
// Can't delete actors if Matinee is open.
|
|
const FText ErrorMsg = NSLOCTEXT("UnrealEd", "Error_WrongModeForActorDeletion", "Cannot delete actor while Matinee is open");
|
|
if (!GLevelEditorModeTools().EnsureNotInMode(FBuiltinEditorModes::EM_InterpEdit, ErrorMsg, true))
|
|
{
|
|
bResult = true;
|
|
}
|
|
|
|
if (!bResult)
|
|
{
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
|
|
ULevel* ActorLevel = Actor->GetLevel();
|
|
if (FLevelUtils::IsLevelLocked(ActorLevel))
|
|
{
|
|
UE_LOG(LogEditorActor, Warning, TEXT("Cannot perform action on actor %s because the actor's level is locked"), *Actor->GetName());
|
|
bResult = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
void UUnrealEdEngine::edactReplaceSelectedBrush(UWorld* InWorld)
|
|
{
|
|
// Make a list of brush actors to replace.
|
|
ABrush* DefaultBrush = InWorld->GetDefaultBrush();
|
|
|
|
TArray<ABrush*> BrushesToReplace;
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
ABrush* Brush = Cast<ABrush>(Actor);
|
|
if (Brush && Actor->HasAnyFlags(RF_Transactional) && Actor != DefaultBrush)
|
|
{
|
|
BrushesToReplace.Add(Brush);
|
|
}
|
|
}
|
|
|
|
// Fire ULevel::LevelDirtiedEvent when falling out of scope.
|
|
FScopedLevelDirtied LevelDirtyCallback;
|
|
|
|
USelection* SelectedActors = GetSelectedActors();
|
|
SelectedActors->BeginBatchSelectOperation();
|
|
SelectedActors->Modify();
|
|
|
|
// Replace brushes.
|
|
ULayersSubsystem* LayersSubsystem = GEditor->GetEditorSubsystem<ULayersSubsystem>();
|
|
for (int32 BrushIndex = 0; BrushIndex < BrushesToReplace.Num(); ++BrushIndex)
|
|
{
|
|
ABrush* SrcBrush = BrushesToReplace[BrushIndex];
|
|
ABrush* NewBrush = FBSPOps::csgAddOperation(DefaultBrush, SrcBrush->PolyFlags, (EBrushType)SrcBrush->BrushType);
|
|
if (NewBrush)
|
|
{
|
|
SrcBrush->MarkPackageDirty();
|
|
NewBrush->MarkPackageDirty();
|
|
|
|
LevelDirtyCallback.Request();
|
|
|
|
NewBrush->Modify();
|
|
|
|
NewBrush->Layers.Append(SrcBrush->Layers);
|
|
|
|
NewBrush->CopyPosRotScaleFrom(SrcBrush);
|
|
NewBrush->PostEditMove(true);
|
|
SelectActor(SrcBrush, false, false);
|
|
SelectActor(NewBrush, true, false);
|
|
|
|
LayersSubsystem->DisassociateActorFromLayers(SrcBrush);
|
|
InWorld->EditorDestroyActor(SrcBrush, true);
|
|
}
|
|
}
|
|
|
|
SelectedActors->EndBatchSelectOperation();
|
|
NoteSelectionChange();
|
|
}
|
|
|
|
AActor* UUnrealEdEngine::ReplaceActor(AActor* CurrentActor, UClass* NewActorClass, UObject* Archetype, bool bNoteSelectionChange)
|
|
{
|
|
FVector SpawnLoc = CurrentActor->GetActorLocation();
|
|
FRotator SpawnRot = CurrentActor->GetActorRotation();
|
|
FActorSpawnParameters SpawnInfo;
|
|
SpawnInfo.Template = Cast<AActor>(Archetype);
|
|
SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
|
AActor* NewActor = CurrentActor->GetWorld()->SpawnActor(NewActorClass, &SpawnLoc, &SpawnRot, SpawnInfo);
|
|
if (NewActor)
|
|
{
|
|
NewActor->Modify();
|
|
ULayersSubsystem* LayersSubsystem = GEditor->GetEditorSubsystem<ULayersSubsystem>();
|
|
LayersSubsystem->InitializeNewActorLayers(NewActor);
|
|
|
|
const bool bCurrentActorSelected = GetSelectedActors()->IsSelected(CurrentActor);
|
|
if (bCurrentActorSelected)
|
|
{
|
|
// The source actor was selected, so deselect the old actor and select the new one.
|
|
GetSelectedActors()->Modify();
|
|
SelectActor(NewActor, bCurrentActorSelected, false);
|
|
SelectActor(CurrentActor, false, false);
|
|
}
|
|
|
|
{
|
|
LayersSubsystem->DisassociateActorFromLayers(NewActor);
|
|
NewActor->Layers.Empty();
|
|
|
|
LayersSubsystem->AddActorToLayers(NewActor, CurrentActor->Layers);
|
|
|
|
NewActor->SetActorLabel(CurrentActor->GetActorLabel());
|
|
NewActor->Tags = CurrentActor->Tags;
|
|
|
|
NewActor->EditorReplacedActor(CurrentActor);
|
|
}
|
|
|
|
LayersSubsystem->DisassociateActorFromLayers(CurrentActor);
|
|
CurrentActor->GetWorld()->EditorDestroyActor(CurrentActor, true);
|
|
|
|
// Note selection change if necessary and requested.
|
|
if (bCurrentActorSelected && bNoteSelectionChange)
|
|
{
|
|
NoteSelectionChange();
|
|
}
|
|
|
|
// whenever selection changes, recompute whether the selection contains a locked actor
|
|
bCheckForLockActors = true;
|
|
|
|
// whenever selection changes, recompute whether the selection contains a world info actor
|
|
bCheckForWorldSettingsActors = true;
|
|
}
|
|
|
|
return NewActor;
|
|
}
|
|
|
|
void UUnrealEdEngine::edactReplaceSelectedNonBrushWithClass(UClass* Class)
|
|
{
|
|
// Make a list of actors to replace.
|
|
TArray<AActor*> ActorsToReplace;
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
ABrush* Brush = Cast<ABrush>(Actor);
|
|
if (!Brush && Actor->HasAnyFlags(RF_Transactional))
|
|
{
|
|
ActorsToReplace.Add(Actor);
|
|
}
|
|
}
|
|
|
|
// Fire ULevel::LevelDirtiedEvent when falling out of scope.
|
|
FScopedLevelDirtied LevelDirtyCallback;
|
|
|
|
// Replace actors.
|
|
for (int32 i = 0; i < ActorsToReplace.Num(); ++i)
|
|
{
|
|
AActor* SrcActor = ActorsToReplace[i];
|
|
AActor* NewActor = ReplaceActor(SrcActor, Class, NULL, false);
|
|
if (NewActor)
|
|
{
|
|
NewActor->MarkPackageDirty();
|
|
LevelDirtyCallback.Request();
|
|
}
|
|
}
|
|
|
|
NoteSelectionChange();
|
|
}
|
|
|
|
void UUnrealEdEngine::edactReplaceClassWithClass(UWorld* InWorld, UClass* SrcClass, UClass* DstClass)
|
|
{
|
|
// Make a list of actors to replace.
|
|
TArray<AActor*> ActorsToReplace;
|
|
for (TActorIterator<AActor> It(InWorld, SrcClass); It; ++It)
|
|
{
|
|
AActor* Actor = *It;
|
|
if (Actor->HasAnyFlags(RF_Transactional))
|
|
{
|
|
ActorsToReplace.Add(Actor);
|
|
}
|
|
}
|
|
|
|
// Fires ULevel::LevelDirtiedEvent when falling out of scope.
|
|
FScopedLevelDirtied LevelDirtyCallback;
|
|
|
|
// Replace actors.
|
|
for (int32 i = 0; i < ActorsToReplace.Num(); ++i)
|
|
{
|
|
AActor* SrcActor = ActorsToReplace[i];
|
|
AActor* NewActor = ReplaceActor(SrcActor, DstClass, NULL, false);
|
|
if (NewActor)
|
|
{
|
|
NewActor->MarkPackageDirty();
|
|
LevelDirtyCallback.Request();
|
|
}
|
|
}
|
|
|
|
NoteSelectionChange();
|
|
}
|
|
|
|
void UUnrealEdEngine::edactHideSelected(UWorld* InWorld)
|
|
{
|
|
// Assemble a list of actors to hide.
|
|
TArray<AActor*> ActorsToHide;
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
|
|
// Don't consider already hidden actors or the builder brush
|
|
if (!FActorEditorUtils::IsABuilderBrush(Actor) && !Actor->IsHiddenEd())
|
|
{
|
|
ActorsToHide.Add(Actor);
|
|
}
|
|
}
|
|
|
|
// Hide the actors that were selected and deselect them in the process
|
|
if (ActorsToHide.Num() > 0)
|
|
{
|
|
USelection* SelectedActors = GetSelectedActors();
|
|
SelectedActors->Modify();
|
|
|
|
for (int32 ActorIndex = 0; ActorIndex < ActorsToHide.Num(); ++ActorIndex)
|
|
{
|
|
AActor* Actor = ActorsToHide[ActorIndex];
|
|
|
|
// Save the actor to the transaction buffer to support undo/redo, but do
|
|
// not call Modify, as we do not want to dirty the actor's package and
|
|
// we're only editing temporary, transient values
|
|
SaveToTransactionBuffer(Actor, false);
|
|
Actor->SetIsTemporarilyHiddenInEditor(true);
|
|
SelectedActors->Deselect(Actor);
|
|
}
|
|
|
|
NoteSelectionChange();
|
|
}
|
|
|
|
// Iterate through all of the BSP models and hide any that were selected (deselecting them in the process)
|
|
if (InWorld)
|
|
{
|
|
for (TArray<ULevel*>::TConstIterator LevelIterator = InWorld->GetLevels().CreateConstIterator(); LevelIterator; ++LevelIterator)
|
|
{
|
|
UModel& CurLevelModel = *((*LevelIterator)->Model);
|
|
for (TArray<FBspSurf>::TIterator SurfaceIterator(CurLevelModel.Surfs); SurfaceIterator; ++SurfaceIterator)
|
|
{
|
|
FBspSurf& CurSurface = *SurfaceIterator;
|
|
if ((CurSurface.PolyFlags & PF_Selected) && !CurSurface.IsHiddenEd())
|
|
{
|
|
CurLevelModel.ModifySurf(SurfaceIterator.GetIndex(), false);
|
|
|
|
// Deselect the surface and mark it as hidden to the editor
|
|
CurSurface.PolyFlags &= ~PF_Selected;
|
|
CurSurface.bHiddenEdTemporary = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
RedrawLevelEditingViewports();
|
|
}
|
|
|
|
void UUnrealEdEngine::edactHideUnselected(UWorld* InWorld)
|
|
{
|
|
// Iterate through all of the actors and hide the ones which are not selected and are not already hidden
|
|
for (FActorIterator It(InWorld); It; ++It)
|
|
{
|
|
AActor* Actor = *It;
|
|
if (!FActorEditorUtils::IsABuilderBrush(Actor) && !Actor->IsSelected() && !Actor->IsHiddenEd())
|
|
{
|
|
// Save the actor to the transaction buffer to support undo/redo, but do
|
|
// not call Modify, as we do not want to dirty the actor's package and
|
|
// we're only editing temporary, transient values
|
|
SaveToTransactionBuffer(Actor, false);
|
|
Actor->SetIsTemporarilyHiddenInEditor(true);
|
|
}
|
|
}
|
|
|
|
// Iterate through all of the BSP models and hide the ones which are not selected and are not already hidden
|
|
if (InWorld)
|
|
{
|
|
for (TArray<ULevel*>::TConstIterator LevelIterator = InWorld->GetLevels().CreateConstIterator(); LevelIterator; ++LevelIterator)
|
|
{
|
|
UModel& CurLevelModel = *((*LevelIterator)->Model);
|
|
for (TArray<FBspSurf>::TIterator SurfaceIterator(CurLevelModel.Surfs); SurfaceIterator; ++SurfaceIterator)
|
|
{
|
|
FBspSurf& CurSurface = *SurfaceIterator;
|
|
|
|
// Only modify surfaces that aren't selected and aren't already hidden
|
|
if (!(CurSurface.PolyFlags & PF_Selected) && !CurSurface.IsHiddenEd())
|
|
{
|
|
CurLevelModel.ModifySurf(SurfaceIterator.GetIndex(), false);
|
|
CurSurface.bHiddenEdTemporary = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
RedrawLevelEditingViewports();
|
|
}
|
|
|
|
void UUnrealEdEngine::edactUnHideAll(UWorld* InWorld)
|
|
{
|
|
// Iterate through all of the actors and unhide them
|
|
for (FActorIterator It(InWorld); It; ++It)
|
|
{
|
|
AActor* Actor = *It;
|
|
if (!FActorEditorUtils::IsABuilderBrush(Actor) && Actor->IsTemporarilyHiddenInEditor())
|
|
{
|
|
// Save the actor to the transaction buffer to support undo/redo, but do
|
|
// not call Modify, as we do not want to dirty the actor's package and
|
|
// we're only editing temporary, transient values
|
|
SaveToTransactionBuffer(Actor, false);
|
|
Actor->SetIsTemporarilyHiddenInEditor(false);
|
|
}
|
|
}
|
|
|
|
// Iterate through all of the BSP models and unhide them if they are already hidden
|
|
if (InWorld)
|
|
{
|
|
for (TArray<ULevel*>::TConstIterator LevelIterator = InWorld->GetLevels().CreateConstIterator(); LevelIterator; ++LevelIterator)
|
|
{
|
|
UModel& CurLevelModel = *((*LevelIterator)->Model);
|
|
for (TArray<FBspSurf>::TIterator SurfaceIterator(CurLevelModel.Surfs); SurfaceIterator; ++SurfaceIterator)
|
|
{
|
|
FBspSurf& CurSurface = *SurfaceIterator;
|
|
if (CurSurface.bHiddenEdTemporary)
|
|
{
|
|
CurLevelModel.ModifySurf(SurfaceIterator.GetIndex(), false);
|
|
CurSurface.bHiddenEdTemporary = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
RedrawLevelEditingViewports();
|
|
}
|
|
|
|
void UUnrealEdEngine::edactHideSelectedStartup(UWorld* InWorld)
|
|
{
|
|
// Fires ULevel::LevelDirtiedEvent when falling out of scope.
|
|
FScopedLevelDirtied LevelDirtyCallback;
|
|
|
|
// Iterate through all of the selected actors
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
|
|
// Set the actor to hide at editor startup, if it's not already set that way
|
|
if (!FActorEditorUtils::IsABuilderBrush(Actor) && !Actor->IsHiddenEd() && !Actor->IsHiddenEdAtStartup())
|
|
{
|
|
Actor->Modify();
|
|
Actor->bHiddenEd = true;
|
|
LevelDirtyCallback.Request();
|
|
}
|
|
}
|
|
|
|
if (InWorld)
|
|
{
|
|
// Iterate through all of the selected BSP surfaces
|
|
for (TArray<ULevel*>::TConstIterator LevelIterator = InWorld->GetLevels().CreateConstIterator(); LevelIterator; ++LevelIterator)
|
|
{
|
|
UModel& CurLevelModel = *((*LevelIterator)->Model);
|
|
for (TArray<FBspSurf>::TIterator SurfaceIterator(CurLevelModel.Surfs); SurfaceIterator; ++SurfaceIterator)
|
|
{
|
|
FBspSurf& CurSurface = *SurfaceIterator;
|
|
|
|
// Set the BSP surface to hide at editor startup, if it's not already set that way
|
|
const bool bSelected = CurSurface.Actor->IsSelected() || (CurSurface.PolyFlags & PF_Selected);
|
|
if (bSelected && !CurSurface.IsHiddenEdAtStartup() && !CurSurface.IsHiddenEd())
|
|
{
|
|
CurLevelModel.Modify();
|
|
CurLevelModel.ModifySurf(SurfaceIterator.GetIndex(), false);
|
|
CurSurface.PolyFlags |= PF_HiddenEd;
|
|
LevelDirtyCallback.Request();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
RedrawLevelEditingViewports();
|
|
}
|
|
|
|
void UUnrealEdEngine::edactUnHideAllStartup(UWorld* InWorld)
|
|
{
|
|
// Fires ULevel::LevelDirtiedEvent when falling out of scope.
|
|
FScopedLevelDirtied LevelDirtyCallback;
|
|
|
|
// Iterate over all actors
|
|
for (FActorIterator It(InWorld); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
|
|
// If the actor is set to be hidden at editor startup, change it so that it will be shown at startup
|
|
if (!FActorEditorUtils::IsABuilderBrush(Actor) && Actor->IsHiddenEdAtStartup())
|
|
{
|
|
Actor->Modify();
|
|
Actor->bHiddenEd = false;
|
|
LevelDirtyCallback.Request();
|
|
}
|
|
}
|
|
|
|
if (InWorld)
|
|
{
|
|
// Iterate over all BSP surfaces
|
|
for (TArray<ULevel*>::TConstIterator LevelIterator = InWorld->GetLevels().CreateConstIterator(); LevelIterator; ++LevelIterator)
|
|
{
|
|
UModel& CurLevelModel = *((*LevelIterator)->Model);
|
|
for (TArray<FBspSurf>::TIterator SurfaceIterator(CurLevelModel.Surfs); SurfaceIterator; ++SurfaceIterator)
|
|
{
|
|
FBspSurf& CurSurface = *SurfaceIterator;
|
|
|
|
// If the BSP surface is set to be hidden at editor startup, change it so that it will be shown at startup
|
|
if (CurSurface.IsHiddenEdAtStartup())
|
|
{
|
|
CurLevelModel.Modify();
|
|
CurLevelModel.ModifySurf(SurfaceIterator.GetIndex(), false);
|
|
CurSurface.PolyFlags &= ~PF_HiddenEd;
|
|
LevelDirtyCallback.Request();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
RedrawLevelEditingViewports();
|
|
}
|
|
|
|
void UUnrealEdEngine::edactUnHideSelectedStartup(UWorld* InWorld)
|
|
{
|
|
// Fires ULevel::LevelDirtiedEvent when falling out of scope.
|
|
FScopedLevelDirtied LevelDirtyCallback;
|
|
|
|
// Iterate over all selected actors
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
|
|
// Mark the selected actor as showing at editor startup if it was currently set to be hidden
|
|
if (!FActorEditorUtils::IsABuilderBrush(Actor) && Actor->IsHiddenEdAtStartup())
|
|
{
|
|
Actor->Modify();
|
|
Actor->bHiddenEd = false;
|
|
LevelDirtyCallback.Request();
|
|
}
|
|
}
|
|
|
|
if (InWorld)
|
|
{
|
|
// Iterate over all selected BSP surfaces
|
|
for (TArray<ULevel*>::TConstIterator LevelIterator = InWorld->GetLevels().CreateConstIterator(); LevelIterator; ++LevelIterator)
|
|
{
|
|
UModel& CurLevelModel = *((*LevelIterator)->Model);
|
|
for (TArray<FBspSurf>::TIterator SurfaceIterator(CurLevelModel.Surfs); SurfaceIterator; ++SurfaceIterator)
|
|
{
|
|
FBspSurf& CurSurface = *SurfaceIterator;
|
|
|
|
// Mark the selected BSP surface as showing at editor startup if it was currently set to be hidden
|
|
const bool bSelected = CurSurface.Actor->IsSelected() || (CurSurface.PolyFlags & PF_Selected);
|
|
if (bSelected && CurSurface.IsHiddenEdAtStartup())
|
|
{
|
|
CurLevelModel.Modify();
|
|
CurLevelModel.ModifySurf(SurfaceIterator.GetIndex(), false);
|
|
CurSurface.PolyFlags &= ~PF_HiddenEd;
|
|
LevelDirtyCallback.Request();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
RedrawLevelEditingViewports();
|
|
}
|
|
|
|
void UUnrealEdEngine::edactUnhideSelected(UWorld* InWorld)
|
|
{
|
|
// Assemble a list of actors to hide.
|
|
TArray<AActor*> ActorsToShow;
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
|
|
// Don't consider already visible actors or the builder brush
|
|
if (!FActorEditorUtils::IsABuilderBrush(Actor) && Actor->IsHiddenEd())
|
|
{
|
|
ActorsToShow.Add(Actor);
|
|
}
|
|
}
|
|
|
|
// Show the actors that were selected
|
|
if (ActorsToShow.Num() > 0)
|
|
{
|
|
USelection* SelectedActors = GetSelectedActors();
|
|
SelectedActors->Modify();
|
|
|
|
for (int32 ActorIndex = 0; ActorIndex < ActorsToShow.Num(); ++ActorIndex)
|
|
{
|
|
AActor* Actor = ActorsToShow[ActorIndex];
|
|
|
|
// Save the actor to the transaction buffer to support undo/redo, but do
|
|
// not call Modify, as we do not want to dirty the actor's package and
|
|
// we're only editing temporary, transient values
|
|
SaveToTransactionBuffer(Actor, false);
|
|
Actor->SetIsTemporarilyHiddenInEditor(false);
|
|
}
|
|
}
|
|
|
|
// Iterate through all of the BSP models and show any that were selected
|
|
if (InWorld)
|
|
{
|
|
for (TArray<ULevel*>::TConstIterator LevelIterator = InWorld->GetLevels().CreateConstIterator(); LevelIterator; ++LevelIterator)
|
|
{
|
|
UModel& CurLevelModel = *((*LevelIterator)->Model);
|
|
for (TArray<FBspSurf>::TIterator SurfaceIterator(CurLevelModel.Surfs); SurfaceIterator; ++SurfaceIterator)
|
|
{
|
|
FBspSurf& CurSurface = *SurfaceIterator;
|
|
if ((CurSurface.PolyFlags & PF_Selected) && !CurSurface.IsHiddenEd())
|
|
{
|
|
CurLevelModel.ModifySurf(SurfaceIterator.GetIndex(), false);
|
|
CurSurface.bHiddenEdTemporary = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
RedrawLevelEditingViewports();
|
|
}
|
|
|
|
void UUnrealEdEngine::CreateBSPVisibilityMap(UWorld* InWorld, TMap<AActor*, TArray<int32>>& OutBSPMap, bool& bOutAllVisible)
|
|
{
|
|
// Start out true, we do not know otherwise.
|
|
bOutAllVisible = true;
|
|
|
|
// Iterate through all of the BSP models and any that are visible to the list.
|
|
if (InWorld)
|
|
{
|
|
for (TArray<ULevel*>::TConstIterator LevelIterator = InWorld->GetLevels().CreateConstIterator(); LevelIterator; ++LevelIterator)
|
|
{
|
|
UModel& CurLevelModel = *((*LevelIterator)->Model);
|
|
for (TArray<FBspSurf>::TIterator SurfaceIterator(CurLevelModel.Surfs); SurfaceIterator; ++SurfaceIterator)
|
|
{
|
|
FBspSurf& CurSurface = *SurfaceIterator;
|
|
|
|
// If the surface is visible, we will want to add it to the map.
|
|
if (CurSurface.bHiddenEdTemporary == false)
|
|
{
|
|
// First check if we have already added our surface's brush actor to the map.
|
|
TArray<int32>* BrushPolyList = OutBSPMap.Find(CurSurface.Actor);
|
|
if (BrushPolyList)
|
|
{
|
|
// We found the brush actor on the list, so add our polygon ID to the list.
|
|
BrushPolyList->Add(CurSurface.iBrushPoly);
|
|
}
|
|
else
|
|
{
|
|
// The brush actor has not been added to the map, add it.
|
|
OutBSPMap.Add(CurSurface.Actor, TArray<int32>());
|
|
|
|
// Grab the list out and add our brush poly to it.
|
|
BrushPolyList = OutBSPMap.Find(CurSurface.Actor);
|
|
BrushPolyList->Add(CurSurface.iBrushPoly);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We found one that is not visible, so they are not ALL visible. We will continue to map out geometry to come up with a complete Visibility map.
|
|
bOutAllVisible = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UUnrealEdEngine::MakeBSPMapVisible(const TMap<AActor*, TArray<int32>>& InBSPMap, UWorld* InWorld)
|
|
{
|
|
// Iterate through all of the BSP models and show any that were selected
|
|
if (InWorld)
|
|
{
|
|
for (TArray<ULevel*>::TConstIterator LevelIterator = InWorld->GetLevels().CreateConstIterator(); LevelIterator; ++LevelIterator)
|
|
{
|
|
UModel& CurLevelModel = *((*LevelIterator)->Model);
|
|
for (TArray<FBspSurf>::TIterator SurfaceIterator(CurLevelModel.Surfs); SurfaceIterator; ++SurfaceIterator)
|
|
{
|
|
FBspSurf& CurSurface = *SurfaceIterator;
|
|
|
|
// Check if we can find the surface's actor in the map.
|
|
const TArray<int32>* BrushPolyList = InBSPMap.Find(CurSurface.Actor);
|
|
if (BrushPolyList)
|
|
{
|
|
// We have the list of brush polygons that are visible, check if the current one is on the list.
|
|
if (BrushPolyList->FindByKey(CurSurface.iBrushPoly))
|
|
{
|
|
// Make the surface visible.
|
|
CurSurface.bHiddenEdTemporary = false;
|
|
}
|
|
else
|
|
{
|
|
// The brush poly was not in the map, so it should be hidden.
|
|
CurSurface.bHiddenEdTemporary = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// There was no brush poly list, that means no polygon on this brush was visible, make this surface hidden.
|
|
CurSurface.bHiddenEdTemporary = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
AActor* UUnrealEdEngine::GetDesiredAttachmentState(TArray<AActor*>& OutNewChildren)
|
|
{
|
|
// Get the selection set (first one will be the new base)
|
|
AActor* NewBase = NULL;
|
|
OutNewChildren.Empty();
|
|
for (FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* SelectedActor = Cast<AActor>(*It);
|
|
if (SelectedActor)
|
|
{
|
|
OutNewChildren.AddUnique(SelectedActor);
|
|
}
|
|
}
|
|
|
|
// Last element of the array becomes new base
|
|
if (OutNewChildren.Num() > 0)
|
|
{
|
|
NewBase = OutNewChildren.Pop();
|
|
}
|
|
|
|
return NewBase;
|
|
}
|
|
|
|
void UUnrealEdEngine::AttachSelectedActors()
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("Editor", "UndoAction_PerformAttachment", "Attach actors"));
|
|
|
|
// Get what we want attachment to be
|
|
TArray<AActor*> NewChildren;
|
|
AActor* NewBase = GetDesiredAttachmentState(NewChildren);
|
|
if (NewBase && NewBase->GetRootComponent() && (NewChildren.Num() > 0))
|
|
{
|
|
|
|
// Do the actual base change
|
|
for (int32 ChildIdx = 0; ChildIdx < NewChildren.Num(); ChildIdx++)
|
|
{
|
|
AActor* Child = NewChildren[ChildIdx];
|
|
if (Child)
|
|
{
|
|
ParentActors(NewBase, Child, NAME_None);
|
|
}
|
|
}
|
|
|
|
RedrawLevelEditingViewports();
|
|
}
|
|
}
|
|
|
|
void UUnrealEdEngine::edactSelectAll(UWorld* InWorld)
|
|
{
|
|
// If there are a lot of actors to process, pop up a warning "are you sure?" box
|
|
int32 NumActors = InWorld->GetActorCount();
|
|
bool bShowProgress = false;
|
|
if (NumActors >= EditorActorSelectionDefs::MaxActorsToSelectBeforeWarning)
|
|
{
|
|
bShowProgress = true;
|
|
|
|
const FText ConfirmText = FText::Format(NSLOCTEXT("UnrealEd", "Warning_ManyActorsForSelect", "There are {0} actors in the world. Are you sure you want to select them all?"), FText::AsNumber(NumActors));
|
|
|
|
FSuppressableWarningDialog::FSetupInfo Info(ConfirmText, NSLOCTEXT("UnrealEd", "Warning_ManyActors", "Warning: Many Actors"), "Warning_ManyActors");
|
|
Info.ConfirmText = NSLOCTEXT("ModalDialogs", "SelectAllConfirm", "Select All");
|
|
Info.CancelText = NSLOCTEXT("ModalDialogs", "SelectAllCancel", "Cancel");
|
|
|
|
FSuppressableWarningDialog ManyActorsWarning(Info);
|
|
if (ManyActorsWarning.ShowModal() == FSuppressableWarningDialog::Cancel)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (bShowProgress)
|
|
{
|
|
GWarn->BeginSlowTask(LOCTEXT("BeginSelectAllActorsTaskStatusMessage", "Selecting All Actors"), true);
|
|
}
|
|
|
|
// Add all selected actors' layer name to the LayerArray.
|
|
USelection* SelectedActors = GetSelectedActors();
|
|
|
|
SelectedActors->BeginBatchSelectOperation();
|
|
|
|
SelectedActors->Modify();
|
|
|
|
for (FActorIterator It(InWorld); It; ++It)
|
|
{
|
|
AActor* Actor = *It;
|
|
if (!Actor->IsSelected() && !Actor->IsHiddenEd() && Actor->IsSelectable())
|
|
{
|
|
SelectActor(Actor, 1, 0);
|
|
}
|
|
}
|
|
|
|
// Iterate through all of the BSP models and select them if they are not hidden
|
|
if (InWorld)
|
|
{
|
|
for (TArray<ULevel*>::TConstIterator LevelIterator = InWorld->GetLevels().CreateConstIterator(); LevelIterator; ++LevelIterator)
|
|
{
|
|
UModel& CurLevelModel = *((*LevelIterator)->Model);
|
|
for (TArray<FBspSurf>::TIterator SurfaceIterator(CurLevelModel.Surfs); SurfaceIterator; ++SurfaceIterator)
|
|
{
|
|
FBspSurf& CurSurface = *SurfaceIterator;
|
|
if (!CurSurface.IsHiddenEd())
|
|
{
|
|
CurLevelModel.ModifySurf(SurfaceIterator.GetIndex(), false);
|
|
CurSurface.PolyFlags |= PF_Selected;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SelectedActors->EndBatchSelectOperation();
|
|
|
|
NoteSelectionChange();
|
|
|
|
if (bShowProgress)
|
|
{
|
|
GWarn->EndSlowTask();
|
|
}
|
|
}
|
|
|
|
void UUnrealEdEngine::edactSelectInvert(UWorld* InWorld)
|
|
{
|
|
// If there are a lot of actors to process, pop up a warning "are you sure?" box
|
|
int32 NumActors = InWorld->GetActorCount();
|
|
bool bShowProgress = false;
|
|
if (NumActors >= EditorActorSelectionDefs::MaxActorsToSelectBeforeWarning)
|
|
{
|
|
bShowProgress = true;
|
|
const FText ConfirmText = FText::Format(NSLOCTEXT("UnrealEd", "Warning_ManyActorsForInvertSelect", "There are {0} actors in the world. Are you sure you want to invert selection on them all?"), FText::AsNumber(NumActors));
|
|
|
|
FSuppressableWarningDialog::FSetupInfo Info(ConfirmText, NSLOCTEXT("UnrealEd", "Warning_ManyActors", "Warning: Many Actors"), "Warning_ManyActors");
|
|
Info.ConfirmText = NSLOCTEXT("ModalDialogs", "InvertSelectionConfirm", "Invert Selection");
|
|
Info.CancelText = NSLOCTEXT("ModalDialogs", "InvertSelectionCancel", "Cancel");
|
|
|
|
FSuppressableWarningDialog ManyActorsWarning(Info);
|
|
if (ManyActorsWarning.ShowModal() == FSuppressableWarningDialog::Cancel)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (bShowProgress)
|
|
{
|
|
GWarn->BeginSlowTask(LOCTEXT("BeginInvertingActorSelectionTaskMessage", "Inverting Selected Actors"), true);
|
|
}
|
|
|
|
USelection* SelectedActors = GetSelectedActors();
|
|
SelectedActors->BeginBatchSelectOperation();
|
|
|
|
SelectedActors->Modify();
|
|
|
|
// Iterate through all of the actors and select them if they are not currently selected (and not hidden)
|
|
// or deselect them if they are currently selected
|
|
|
|
// Turn off Grouping during this process to avoid double toggling of selected actors via group selection
|
|
const bool bGroupingActiveSaved = UActorGroupingUtils::IsGroupingActive();
|
|
UActorGroupingUtils::SetGroupingActive(false);
|
|
for (FActorIterator It(InWorld); It; ++It)
|
|
{
|
|
AActor* Actor = *It;
|
|
if (!FActorEditorUtils::IsABuilderBrush(Actor) && !Actor->IsHiddenEd())
|
|
{
|
|
SelectActor(Actor, !Actor->IsSelected(), false);
|
|
}
|
|
}
|
|
// Restore bGroupingActive to its original value
|
|
UActorGroupingUtils::SetGroupingActive(bGroupingActiveSaved);
|
|
|
|
// Iterate through all of the BSP models and select them if they are not currently selected (and not hidden)
|
|
// or deselect them if they are currently selected
|
|
if (InWorld)
|
|
{
|
|
for (TArray<ULevel*>::TConstIterator LevelIterator = InWorld->GetLevels().CreateConstIterator(); LevelIterator; ++LevelIterator)
|
|
{
|
|
UModel& CurLevelModel = *((*LevelIterator)->Model);
|
|
for (TArray<FBspSurf>::TIterator SurfaceIterator(CurLevelModel.Surfs); SurfaceIterator; ++SurfaceIterator)
|
|
{
|
|
FBspSurf& CurSurface = *SurfaceIterator;
|
|
if (!CurSurface.IsHiddenEd())
|
|
{
|
|
CurLevelModel.ModifySurf(SurfaceIterator.GetIndex(), false);
|
|
CurSurface.PolyFlags ^= PF_Selected;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SelectedActors->EndBatchSelectOperation();
|
|
|
|
NoteSelectionChange();
|
|
|
|
if (bShowProgress)
|
|
{
|
|
GWarn->EndSlowTask();
|
|
}
|
|
}
|
|
|
|
static void GetAttachedActors(AActor* Actor, bool bRecurseChildren, TSet<AActor*>& OutActors)
|
|
{
|
|
TArray<AActor*> ChildrenActors;
|
|
Actor->GetAttachedActors(ChildrenActors);
|
|
for (AActor* ChildActor: ChildrenActors)
|
|
{
|
|
OutActors.Add(ChildActor);
|
|
|
|
if (bRecurseChildren)
|
|
{
|
|
GetAttachedActors(ChildActor, bRecurseChildren, OutActors);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UUnrealEdEngine::edactSelectAllChildren(bool bRecurseChildren)
|
|
{
|
|
USelection* CurrentSelection = GetSelectedActors();
|
|
|
|
TArray<AActor*> SelectedActors;
|
|
CurrentSelection->GetSelectedObjects<AActor>(SelectedActors);
|
|
|
|
CurrentSelection->BeginBatchSelectOperation();
|
|
CurrentSelection->Modify();
|
|
|
|
// Turn off Grouping during this process to avoid double toggling of selected actors via group selection
|
|
const bool bGroupingActiveSaved = UActorGroupingUtils::IsGroupingActive();
|
|
UActorGroupingUtils::SetGroupingActive(false);
|
|
|
|
// Iterate through all the selected actors and select their children if they are not currently selected
|
|
TSet<AActor*> ActorsToSelect;
|
|
for (AActor* Actor: SelectedActors)
|
|
{
|
|
// Don't recurse through the same actor twice
|
|
if (!bRecurseChildren || !ActorsToSelect.Contains(Actor))
|
|
{
|
|
GetAttachedActors(Actor, bRecurseChildren, ActorsToSelect);
|
|
}
|
|
}
|
|
|
|
for (AActor* Actor: ActorsToSelect)
|
|
{
|
|
if (!FActorEditorUtils::IsABuilderBrush(Actor) && !Actor->IsSelected())
|
|
{
|
|
// Select actor even if hidden
|
|
SelectActor(Actor, true, false, true);
|
|
}
|
|
}
|
|
|
|
// Restore bGroupingActive to its original value
|
|
UActorGroupingUtils::SetGroupingActive(bGroupingActiveSaved);
|
|
|
|
CurrentSelection->EndBatchSelectOperation();
|
|
|
|
NoteSelectionChange();
|
|
}
|
|
|
|
void UUnrealEdEngine::edactSelectOfClass(UWorld* InWorld, UClass* Class)
|
|
{
|
|
USelection* SelectedActors = GetSelectedActors();
|
|
SelectedActors->BeginBatchSelectOperation();
|
|
|
|
SelectedActors->Modify();
|
|
|
|
for (TActorIterator<AActor> It(InWorld, Class); It; ++It)
|
|
{
|
|
AActor* Actor = *It;
|
|
if (Actor->GetClass() == Class && !Actor->IsSelected() && !Actor->IsHiddenEd())
|
|
{
|
|
// Selection by class not permitted for actors belonging to prefabs.
|
|
// Selection by class not permitted for builder brushes.
|
|
if (!FActorEditorUtils::IsABuilderBrush(Actor))
|
|
{
|
|
SelectActor(Actor, 1, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
SelectedActors->EndBatchSelectOperation();
|
|
NoteSelectionChange();
|
|
}
|
|
|
|
void UUnrealEdEngine::edactSelectOfClassAndArchetype(UWorld* InWorld, const TSubclassOf<AActor> InClass, const UObject* InArchetype)
|
|
{
|
|
USelection* SelectedActors = GetSelectedActors();
|
|
SelectedActors->BeginBatchSelectOperation();
|
|
|
|
SelectedActors->Modify();
|
|
|
|
// Select all actors with of the provided class and archetype, assuming they aren't already selected,
|
|
// aren't hidden in the editor, aren't a member of a prefab, and aren't builder brushes
|
|
for (TActorIterator<AActor> ActorIter(InWorld, InClass); ActorIter; ++ActorIter)
|
|
{
|
|
AActor* CurActor = *ActorIter;
|
|
if (CurActor->GetClass() == InClass && CurActor->GetArchetype() == InArchetype && !CurActor->IsSelected() && !CurActor->IsHiddenEd() && !FActorEditorUtils::IsABuilderBrush(CurActor))
|
|
{
|
|
SelectActor(CurActor, true, false);
|
|
}
|
|
}
|
|
|
|
SelectedActors->EndBatchSelectOperation();
|
|
NoteSelectionChange();
|
|
}
|
|
|
|
void UUnrealEdEngine::edactSelectSubclassOf(UWorld* InWorld, UClass* Class)
|
|
{
|
|
USelection* SelectedActors = GetSelectedActors();
|
|
SelectedActors->BeginBatchSelectOperation();
|
|
|
|
SelectedActors->Modify();
|
|
|
|
for (TActorIterator<AActor> It(InWorld, Class); It; ++It)
|
|
{
|
|
AActor* Actor = *It;
|
|
if (!Actor->IsSelected() && !Actor->IsHiddenEd())
|
|
{
|
|
// Selection by class not permitted for actors belonging to prefabs.
|
|
// Selection by class not permitted for builder brushes.
|
|
if (!FActorEditorUtils::IsABuilderBrush(Actor))
|
|
{
|
|
SelectActor(Actor, 1, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
SelectedActors->EndBatchSelectOperation();
|
|
NoteSelectionChange();
|
|
}
|
|
|
|
void UUnrealEdEngine::edactSelectDeleted(UWorld* InWorld)
|
|
{
|
|
USelection* SelectedActors = GetSelectedActors();
|
|
SelectedActors->BeginBatchSelectOperation();
|
|
|
|
SelectedActors->Modify();
|
|
|
|
bool bSelectionChanged = false;
|
|
for (FActorIterator It(InWorld); It; ++It)
|
|
{
|
|
AActor* Actor = *It;
|
|
if (!Actor->IsSelected() && !Actor->IsHiddenEd())
|
|
{
|
|
if (Actor->IsPendingKill())
|
|
{
|
|
bSelectionChanged = true;
|
|
SelectActor(Actor, 1, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
SelectedActors->EndBatchSelectOperation();
|
|
|
|
if (bSelectionChanged)
|
|
{
|
|
NoteSelectionChange();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Select matching static meshes.
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace
|
|
{
|
|
|
|
/**
|
|
* Information about an actor and its static mesh.
|
|
*/
|
|
class FStaticMeshActor
|
|
{
|
|
public:
|
|
/** Non-NULL if the actor is a static mesh. */
|
|
AStaticMeshActor* StaticMeshActor;
|
|
/** Non-NULL if the actor has a static mesh. */
|
|
UStaticMesh* StaticMesh;
|
|
|
|
FStaticMeshActor()
|
|
: StaticMeshActor(NULL), StaticMesh(NULL)
|
|
{}
|
|
|
|
bool IsStaticMeshActor() const
|
|
{
|
|
return StaticMeshActor != NULL;
|
|
}
|
|
|
|
bool HasStaticMesh() const
|
|
{
|
|
return StaticMesh != NULL;
|
|
}
|
|
|
|
/**
|
|
* Extracts the static mesh information from the specified actor.
|
|
*/
|
|
static bool GetStaticMeshInfoFromActor(AActor* Actor, FStaticMeshActor& OutStaticMeshActor)
|
|
{
|
|
OutStaticMeshActor.StaticMeshActor = Cast<AStaticMeshActor>(Actor);
|
|
|
|
if (OutStaticMeshActor.IsStaticMeshActor())
|
|
{
|
|
if (OutStaticMeshActor.StaticMeshActor->GetStaticMeshComponent())
|
|
{
|
|
OutStaticMeshActor.StaticMesh = OutStaticMeshActor.StaticMeshActor->GetStaticMeshComponent()->GetStaticMesh();
|
|
}
|
|
}
|
|
return OutStaticMeshActor.HasStaticMesh();
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
void UUnrealEdEngine::edactSelectMatchingStaticMesh(bool bAllClasses)
|
|
{
|
|
TArray<FStaticMeshActor> StaticMeshActors;
|
|
|
|
TArray<UWorld*> SelectedWorlds;
|
|
// Make a list of selected actors with static meshes.
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
|
|
FStaticMeshActor ActorInfo;
|
|
if (FStaticMeshActor::GetStaticMeshInfoFromActor(Actor, ActorInfo))
|
|
{
|
|
if (ActorInfo.IsStaticMeshActor())
|
|
{
|
|
StaticMeshActors.Add(ActorInfo);
|
|
SelectedWorlds.AddUnique(Actor->GetWorld());
|
|
}
|
|
}
|
|
}
|
|
if (SelectedWorlds.Num() == 0)
|
|
{
|
|
UE_LOG(LogEditorActor, Log, TEXT("No worlds found in edactSelectMatchingStaticMesh"));
|
|
return;
|
|
}
|
|
// Make sure we have only 1 valid world
|
|
check(SelectedWorlds.Num() == 1);
|
|
USelection* SelectedActors = GetSelectedActors();
|
|
SelectedActors->BeginBatchSelectOperation();
|
|
SelectedActors->Modify();
|
|
|
|
// Loop through all non-hidden actors in visible levels, selecting those that have one of the
|
|
// static meshes in the list.
|
|
for (FActorIterator It(SelectedWorlds[0]); It; ++It)
|
|
{
|
|
AActor* Actor = *It;
|
|
if (!Actor->IsHiddenEd())
|
|
{
|
|
FStaticMeshActor ActorInfo;
|
|
if (FStaticMeshActor::GetStaticMeshInfoFromActor(Actor, ActorInfo))
|
|
{
|
|
bool bSelectActor = false;
|
|
if (bAllClasses || ActorInfo.IsStaticMeshActor())
|
|
{
|
|
for (int32 i = 0; i < StaticMeshActors.Num(); ++i)
|
|
{
|
|
if (StaticMeshActors[i].StaticMesh == ActorInfo.StaticMesh)
|
|
{
|
|
bSelectActor = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bSelectActor)
|
|
{
|
|
SelectActor(Actor, true, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SelectedActors->EndBatchSelectOperation();
|
|
NoteSelectionChange();
|
|
}
|
|
|
|
void UUnrealEdEngine::edactSelectMatchingSkeletalMesh(bool bAllClasses)
|
|
{
|
|
TArray<USkeletalMesh*> SelectedMeshes;
|
|
bool bSelectSkelMeshActors = false;
|
|
bool bSelectPawns = false;
|
|
|
|
TArray<UWorld*> SelectedWorlds;
|
|
// Make a list of skeletal meshes of selected actors, and note what classes we have selected.
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
|
|
// Look for SkelMeshActor
|
|
ASkeletalMeshActor* SkelMeshActor = Cast<ASkeletalMeshActor>(Actor);
|
|
if (SkelMeshActor && SkelMeshActor->GetSkeletalMeshComponent())
|
|
{
|
|
bSelectSkelMeshActors = true;
|
|
SelectedMeshes.AddUnique(SkelMeshActor->GetSkeletalMeshComponent()->SkeletalMesh);
|
|
SelectedWorlds.AddUnique(Actor->GetWorld());
|
|
}
|
|
|
|
// Look for Pawn
|
|
APawn* Pawn = Cast<APawn>(Actor);
|
|
if (Pawn)
|
|
{
|
|
USkeletalMeshComponent* PawnSkeletalMesh = Pawn->FindComponentByClass<USkeletalMeshComponent>();
|
|
if (PawnSkeletalMesh)
|
|
{
|
|
bSelectPawns = true;
|
|
SelectedMeshes.AddUnique(PawnSkeletalMesh->SkeletalMesh);
|
|
SelectedWorlds.AddUnique(Actor->GetWorld());
|
|
}
|
|
}
|
|
}
|
|
if (SelectedWorlds.Num() == 0)
|
|
{
|
|
UE_LOG(LogEditorActor, Log, TEXT("No worlds found in edactSelectMatchingSkeletalMesh"));
|
|
return;
|
|
}
|
|
// Make sure we have only 1 valid world
|
|
check(SelectedWorlds.Num() == 1);
|
|
// If desired, select all class types
|
|
if (bAllClasses)
|
|
{
|
|
bSelectSkelMeshActors = true;
|
|
bSelectPawns = true;
|
|
}
|
|
|
|
USelection* SelectedActors = GetSelectedActors();
|
|
SelectedActors->BeginBatchSelectOperation();
|
|
SelectedActors->Modify();
|
|
|
|
// Loop through all non-hidden actors in visible levels, selecting those that have one of the skeletal meshes in the list.
|
|
for (FActorIterator It(SelectedWorlds[0]); It; ++It)
|
|
{
|
|
AActor* Actor = *It;
|
|
if (!Actor->IsHiddenEd())
|
|
{
|
|
bool bSelectActor = false;
|
|
|
|
if (bSelectSkelMeshActors)
|
|
{
|
|
ASkeletalMeshActor* SkelMeshActor = Cast<ASkeletalMeshActor>(Actor);
|
|
if (SkelMeshActor &&
|
|
SkelMeshActor->GetSkeletalMeshComponent() &&
|
|
SelectedMeshes.Contains(SkelMeshActor->GetSkeletalMeshComponent()->SkeletalMesh))
|
|
{
|
|
bSelectActor = true;
|
|
}
|
|
}
|
|
|
|
if (bSelectPawns)
|
|
{
|
|
APawn* Pawn = Cast<APawn>(Actor);
|
|
if (Pawn)
|
|
{
|
|
USkeletalMeshComponent* PawnSkeletalMesh = Pawn->FindComponentByClass<USkeletalMeshComponent>();
|
|
if (PawnSkeletalMesh && SelectedMeshes.Contains(PawnSkeletalMesh->SkeletalMesh))
|
|
{
|
|
bSelectActor = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bSelectActor)
|
|
{
|
|
SelectActor(Actor, true, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
SelectedActors->EndBatchSelectOperation();
|
|
NoteSelectionChange();
|
|
}
|
|
|
|
void UUnrealEdEngine::edactSelectMatchingMaterial()
|
|
{
|
|
// Set for fast lookup of used materials.
|
|
TSet<UMaterialInterface*> MaterialsInSelection;
|
|
|
|
TArray<UWorld*> SelectedWorlds;
|
|
// For each selected actor, find all the materials used by this actor.
|
|
for (FSelectionIterator ActorItr(GetSelectedActorIterator()); ActorItr; ++ActorItr)
|
|
{
|
|
AActor* CurrentActor = Cast<AActor>(*ActorItr);
|
|
|
|
if (CurrentActor)
|
|
{
|
|
// Find the materials by iterating over every primitive component.
|
|
for (UActorComponent* Component: CurrentActor->GetComponents())
|
|
{
|
|
if (UPrimitiveComponent* CurrentComponent = Cast<UPrimitiveComponent>(Component))
|
|
{
|
|
TArray<UMaterialInterface*> UsedMaterials;
|
|
CurrentComponent->GetUsedMaterials(UsedMaterials);
|
|
MaterialsInSelection.Append(UsedMaterials);
|
|
SelectedWorlds.AddUnique(CurrentActor->GetWorld());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SelectedWorlds.Num() == 0)
|
|
{
|
|
UE_LOG(LogEditorActor, Log, TEXT("No worlds found in edactSelectMatchingMaterial"));
|
|
return;
|
|
}
|
|
// Make sure we have only 1 valid world
|
|
check(SelectedWorlds.Num() == 1);
|
|
|
|
USelection* SelectedActors = GetSelectedActors();
|
|
SelectedActors->BeginBatchSelectOperation();
|
|
SelectedActors->Modify();
|
|
|
|
// Now go over every actor and see if any of the actors are using any of the materials that
|
|
// we found above.
|
|
for (FActorIterator ActorIt(SelectedWorlds[0]); ActorIt; ++ActorIt)
|
|
{
|
|
AActor* Actor = *ActorIt;
|
|
|
|
// Do not bother checking hidden actors
|
|
if (!Actor->IsHiddenEd())
|
|
{
|
|
TInlineComponentArray<UPrimitiveComponent*> PrimitiveComponents;
|
|
Actor->GetComponents(PrimitiveComponents);
|
|
|
|
const int32 NumComponents = PrimitiveComponents.Num();
|
|
for (int32 ComponentIndex = 0; ComponentIndex < NumComponents; ++ComponentIndex)
|
|
{
|
|
UPrimitiveComponent* CurrentComponent = PrimitiveComponents[ComponentIndex];
|
|
|
|
TArray<UMaterialInterface*> UsedMaterials;
|
|
CurrentComponent->GetUsedMaterials(UsedMaterials);
|
|
const int32 NumMaterials = UsedMaterials.Num();
|
|
// Iterate over every material we found so far and see if its in the list of materials used by selected actors.
|
|
for (int32 MatIndex = 0; MatIndex < NumMaterials; ++MatIndex)
|
|
{
|
|
UMaterialInterface* Material = UsedMaterials[MatIndex];
|
|
// Is this material used by currently selected actors?
|
|
if (MaterialsInSelection.Find(Material))
|
|
{
|
|
SelectActor(Actor, true, false);
|
|
// We dont need to continue searching as this actor has already been selected
|
|
MatIndex = NumMaterials;
|
|
ComponentIndex = NumComponents;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SelectedActors->EndBatchSelectOperation();
|
|
NoteSelectionChange();
|
|
}
|
|
|
|
void UUnrealEdEngine::edactSelectMatchingEmitter()
|
|
{
|
|
TArray<UParticleSystem*> SelectedParticleSystemTemplates;
|
|
|
|
TArray<UWorld*> SelectedWorlds;
|
|
// Check all of the currently selected actors to find the relevant particle system templates to use to match
|
|
for (FSelectionIterator SelectedIterator(GetSelectedActorIterator()); SelectedIterator; ++SelectedIterator)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*SelectedIterator);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
|
|
AEmitter* Emitter = Cast<AEmitter>(Actor);
|
|
|
|
if (Emitter && Emitter->GetParticleSystemComponent() && Emitter->GetParticleSystemComponent()->Template)
|
|
{
|
|
SelectedParticleSystemTemplates.AddUnique(Emitter->GetParticleSystemComponent()->Template);
|
|
SelectedWorlds.AddUnique(Actor->GetWorld());
|
|
}
|
|
}
|
|
|
|
if (SelectedWorlds.Num() == 0)
|
|
{
|
|
UE_LOG(LogEditorActor, Log, TEXT("No worlds found in edactSelectMatchingEmitter"));
|
|
return;
|
|
}
|
|
// Make sure we have only 1 valid world
|
|
check(SelectedWorlds.Num() == 1);
|
|
|
|
USelection* SelectedActors = GetSelectedActors();
|
|
SelectedActors->BeginBatchSelectOperation();
|
|
SelectedActors->Modify();
|
|
// Iterate over all of the non-hidden actors, selecting those who have a particle system template that matches one from the previously-found list
|
|
for (TActorIterator<AEmitter> ActorIterator(SelectedWorlds[0]); ActorIterator; ++ActorIterator)
|
|
{
|
|
AEmitter* ActorAsEmitter = *ActorIterator;
|
|
if (!ActorAsEmitter->IsHiddenEd())
|
|
{
|
|
if (ActorAsEmitter->GetParticleSystemComponent() && SelectedParticleSystemTemplates.Contains(ActorAsEmitter->GetParticleSystemComponent()->Template))
|
|
{
|
|
SelectActor(ActorAsEmitter, true, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
SelectedActors->EndBatchSelectOperation();
|
|
NoteSelectionChange();
|
|
}
|
|
|
|
void UUnrealEdEngine::edactSelectRelevantLights(UWorld* InWorld)
|
|
{
|
|
TArray<ALight*> RelevantLightList;
|
|
// Make a list of selected actors with static meshes.
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
|
|
if (Actor->GetLevel()->IsCurrentLevel())
|
|
{
|
|
// Gather static lighting info from each of the actor's components.
|
|
for (UActorComponent* Component: Actor->GetComponents())
|
|
{
|
|
UPrimitiveComponent* Primitive = Cast<UPrimitiveComponent>(Component);
|
|
if (Primitive && Primitive->IsRegistered())
|
|
{
|
|
TArray<const ULightComponent*> RelevantLightComponents;
|
|
InWorld->Scene->GetRelevantLights(Primitive, &RelevantLightComponents);
|
|
|
|
for (int32 LightComponentIndex = 0; LightComponentIndex < RelevantLightComponents.Num(); LightComponentIndex++)
|
|
{
|
|
const ULightComponent* LightComponent = RelevantLightComponents[LightComponentIndex];
|
|
ALight* LightOwner = Cast<ALight>(LightComponent->GetOwner());
|
|
if (LightOwner)
|
|
{
|
|
RelevantLightList.AddUnique(LightOwner);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
USelection* SelectedActors = GetSelectedActors();
|
|
SelectedActors->BeginBatchSelectOperation();
|
|
SelectedActors->Modify();
|
|
|
|
SelectNone(false, true);
|
|
|
|
UE_LOG(LogEditorActor, Log, TEXT("Found %d relevant lights!"), RelevantLightList.Num());
|
|
for (int32 LightIdx = 0; LightIdx < RelevantLightList.Num(); LightIdx++)
|
|
{
|
|
ALight* Light = RelevantLightList[LightIdx];
|
|
if (Light)
|
|
{
|
|
SelectActor(Light, true, false);
|
|
UE_LOG(LogEditorActor, Log, TEXT("\t%s"), *(Light->GetPathName()));
|
|
}
|
|
}
|
|
|
|
SelectedActors->EndBatchSelectOperation();
|
|
NoteSelectionChange();
|
|
}
|
|
|
|
void UUnrealEdEngine::edactAlignOrigin()
|
|
{
|
|
// Fires ULevel::LevelDirtiedEvent when falling out of scope.
|
|
FScopedLevelDirtied LevelDirtyCallback;
|
|
|
|
// Apply transformations to all selected brushes.
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
|
|
ABrush* Brush = Cast<ABrush>(Actor);
|
|
if (Brush)
|
|
{
|
|
LevelDirtyCallback.Request();
|
|
|
|
Brush->PreEditChange(NULL);
|
|
Brush->Modify();
|
|
|
|
// Snap the location of the brush to the grid
|
|
FVector BrushLocation = Brush->GetActorLocation();
|
|
BrushLocation.X = FMath::RoundToFloat(BrushLocation.X / GetGridSize()) * GetGridSize();
|
|
BrushLocation.Y = FMath::RoundToFloat(BrushLocation.Y / GetGridSize()) * GetGridSize();
|
|
BrushLocation.Z = FMath::RoundToFloat(BrushLocation.Z / GetGridSize()) * GetGridSize();
|
|
Brush->SetActorLocation(BrushLocation, false);
|
|
|
|
// Update EditorMode locations to match the new brush location
|
|
FEditorModeTools& Tools = GLevelEditorModeTools();
|
|
Tools.SetPivotLocation(Brush->GetActorLocation(), true);
|
|
|
|
Brush->Brush->BuildBound();
|
|
Brush->PostEditChange();
|
|
}
|
|
}
|
|
}
|
|
|
|
void UUnrealEdEngine::edactAlignVertices()
|
|
{
|
|
// Fires ULevel::LevelDirtiedEvent when falling out of scope.
|
|
FScopedLevelDirtied LevelDirtyCallback;
|
|
|
|
// Before aligning verts, align the origin with the grid
|
|
edactAlignOrigin();
|
|
|
|
// Apply transformations to all selected brushes.
|
|
for (FSelectionIterator It(GetSelectedActorIterator()); It; ++It)
|
|
{
|
|
AActor* Actor = static_cast<AActor*>(*It);
|
|
checkSlow(Actor->IsA(AActor::StaticClass()));
|
|
ABrush* Brush = Cast<ABrush>(Actor);
|
|
if (Brush)
|
|
{
|
|
LevelDirtyCallback.Request();
|
|
|
|
Brush->PreEditChange(NULL);
|
|
Brush->Modify();
|
|
FVector BrushLocation = Brush->GetActorLocation();
|
|
const FTransform BrushTransform = Brush->GetRootComponent()->GetComponentTransform();
|
|
|
|
// Snap each vertex in the brush to an integer grid.
|
|
UPolys* Polys = Brush->Brush->Polys;
|
|
for (int32 PolyIdx = 0; PolyIdx < Polys->Element.Num(); PolyIdx++)
|
|
{
|
|
FPoly* Poly = &Polys->Element[PolyIdx];
|
|
for (int32 VertIdx = 0; VertIdx < Poly->Vertices.Num(); VertIdx++)
|
|
{
|
|
const float GridSize = GetGridSize();
|
|
|
|
// Snap each vertex to the nearest grid.
|
|
const FVector Vertex = Poly->Vertices[VertIdx];
|
|
const FVector VertexWorld = BrushTransform.TransformPosition(Vertex);
|
|
const FVector VertexSnapped(FMath::RoundToFloat(VertexWorld.X / GridSize) * GridSize,
|
|
FMath::RoundToFloat(VertexWorld.Y / GridSize) * GridSize,
|
|
FMath::RoundToFloat(VertexWorld.Z / GridSize) * GridSize);
|
|
const FVector VertexSnappedLocal = BrushTransform.InverseTransformPosition(VertexSnapped);
|
|
|
|
Poly->Vertices[VertIdx] = VertexSnappedLocal;
|
|
}
|
|
|
|
// If the snapping resulted in an off plane polygon, triangulate it to compensate.
|
|
if (!Poly->IsCoplanar() || !Poly->IsConvex())
|
|
{
|
|
|
|
FPoly BadPoly = *Poly;
|
|
// Remove the bad poly
|
|
Polys->Element.RemoveAt(PolyIdx);
|
|
|
|
// Triangulate the bad poly
|
|
TArray<FPoly> Triangles;
|
|
if (BadPoly.Triangulate(Brush, Triangles) > 0)
|
|
{
|
|
// Add all new triangles to the brush
|
|
for (int32 TriIdx = 0; TriIdx < Triangles.Num(); ++TriIdx)
|
|
{
|
|
Polys->Element.Add(Triangles[TriIdx]);
|
|
}
|
|
}
|
|
|
|
PolyIdx = -1;
|
|
}
|
|
else
|
|
{
|
|
if (RecomputePoly(Brush, &Polys->Element[PolyIdx]) == -2)
|
|
{
|
|
PolyIdx = -1;
|
|
}
|
|
|
|
if (UBrushEditingSubsystem* BrushSubsystem = GEditor->GetEditorSubsystem<UBrushEditingSubsystem>())
|
|
{
|
|
BrushSubsystem->UpdateGeometryFromBrush(Brush);
|
|
}
|
|
}
|
|
}
|
|
|
|
Brush->Brush->BuildBound();
|
|
|
|
Brush->PostEditChange();
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|