2799 lines
88 KiB
C++
2799 lines
88 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 "UnrealEdGlobals.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 "Engine/BrushBuilder.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 "Serialization/ObjectWriter.h"
|
|
#include "Serialization/ObjectReader.h"
|
|
#include "IAssetTools.h"
|
|
#include "AssetToolsModule.h"
|
|
#include "AssetSelection.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#include "Widgets/Notifications/SNotificationList.h"
|
|
#include "Elements/Framework/EngineElementsLibrary.h"
|
|
#include "Elements/Framework/TypedElementSelectionSet.h"
|
|
#include "EdMode.h"
|
|
#include "Subsystems/BrushEditingSubsystem.h"
|
|
#include "Misc/ScopedSlowTask.h"
|
|
#include "Subsystems/AssetEditorSubsystem.h"
|
|
#include "SceneInterface.h"
|
|
#include "SDeleteReferencedActorDialog.h"
|
|
#include "Elements/Interfaces/TypedElementDataStorageCompatibilityInterface.h"
|
|
#include "Elements/Common/EditorDataStorageFeatures.h"
|
|
#include "Elements/Common/EditorDataStorageFeatures.h"
|
|
#include "Elements/Columns/TypedElementVisibilityColumns.h"
|
|
#include "Elements/Columns/TypedElementMiscColumns.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 = FVector3f::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;
|
|
GetSelectedComponents()->GetSelectedObjects<UActorComponent>(SelectedComponents);
|
|
CopyComponents(SelectedComponents, DestinationData);
|
|
}
|
|
else
|
|
{
|
|
// Copy selected actors
|
|
TArray<AActor*> SelectedActors;
|
|
GetSelectedActors()->GetSelectedObjects<AActor>(SelectedActors);
|
|
CopyActors(SelectedActors, InWorld, DestinationData);
|
|
}
|
|
}
|
|
|
|
void UUnrealEdEngine::CopyComponents(const TArray<UActorComponent*>& InComponentsToCopy, FString* DestinationData) const
|
|
{
|
|
// Remove:
|
|
// - Components that cannot be copied.
|
|
TArray<UActorComponent*> ComponentsToCopy = InComponentsToCopy;
|
|
ComponentsToCopy.RemoveAll([](UActorComponent* InComponent)
|
|
{
|
|
return !FComponentEditorUtils::CanCopyComponent(InComponent);
|
|
});
|
|
|
|
FComponentEditorUtils::CopyComponents(ComponentsToCopy, DestinationData);
|
|
}
|
|
|
|
void UUnrealEdEngine::CopyActors(const TArray<AActor*>& InActorsToCopy, UWorld* InWorld, FString* DestinationData) const
|
|
{
|
|
// Remove:
|
|
// - Actors belonging to prefabs unless all actors in the prefab are selected.
|
|
// - Builder brushes.
|
|
// - World Settings.
|
|
TArray<AActor*> ActorsToCopy = InActorsToCopy;
|
|
{
|
|
bool bSomeSelectedActorsNotInCurrentLevel = false;
|
|
ActorsToCopy.RemoveAll([&bSomeSelectedActorsNotInCurrentLevel](AActor* InActor)
|
|
{
|
|
// Remove any selected builder brushes.
|
|
ABrush* Brush = Cast<ABrush>(InActor);
|
|
if (Brush && FActorEditorUtils::IsABuilderBrush(Brush))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Remove world settings
|
|
if (InActor->IsA<AWorldSettings>())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// If any selected actors are not in the current level, warn the user that some actors will not be copied.
|
|
if (!bSomeSelectedActorsNotInCurrentLevel && !InActor->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."));
|
|
}
|
|
|
|
return false;
|
|
});
|
|
}
|
|
// Filter out other actors.
|
|
OnFilterCopiedActors().Broadcast(ActorsToCopy);
|
|
|
|
// Export the actors.
|
|
FStringOutputDevice Ar;
|
|
const FSelectedActorExportObjectInnerContext Context(ActorsToCopy);
|
|
const bool bExportSucceeded = UExporter::ExportToOutputDevice(&Context, InWorld, NULL, Ar, TEXT("copy"), 0, PPF_DeepCompareInstances | PPF_ExportsNotFullyQualified);
|
|
|
|
if (bExportSucceeded)
|
|
{
|
|
if (DestinationData)
|
|
{
|
|
*DestinationData = MoveTemp(Ar);
|
|
}
|
|
else
|
|
{
|
|
FPlatformApplicationMisc::ClipboardCopy(*Ar);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UUnrealEdEngine::WarnIfDestinationLevelIsHidden( UWorld* InWorld ) const
|
|
{
|
|
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, const FString* SourceData)
|
|
{
|
|
if (GetSelectedComponentCount() > 0)
|
|
{
|
|
AActor* SelectedActor = CastChecked<AActor>(*GetSelectedActorIterator());
|
|
|
|
TArray<UActorComponent*> PastedComponents;
|
|
PasteComponents(PastedComponents, SelectedActor, bWarnIfHidden, SourceData);
|
|
|
|
if (PastedComponents.Num() > 0)
|
|
{
|
|
// Select the new clones
|
|
USelection* ComponentSelection = GetSelectedComponents();
|
|
ComponentSelection->Modify();
|
|
ComponentSelection->BeginBatchSelectOperation();
|
|
ComponentSelection->DeselectAll();
|
|
|
|
for (UActorComponent* PastedComponent : PastedComponents)
|
|
{
|
|
GEditor->SelectComponent(PastedComponent, true, false, true);
|
|
}
|
|
|
|
ComponentSelection->EndBatchSelectOperation(true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TArray<AActor*> PastedActors;
|
|
PasteActors(PastedActors, InWorld, bOffsetLocations ? GEditor->GetGridLocationOffset(/*bUniformOffset*/true) : FVector::ZeroVector, bDuplicate, bWarnIfHidden, SourceData);
|
|
|
|
if (PastedActors.Num() > 0)
|
|
{
|
|
// Select the new clones
|
|
USelection* ActorSelection = GetSelectedActors();
|
|
ActorSelection->Modify();
|
|
ActorSelection->BeginBatchSelectOperation();
|
|
ActorSelection->DeselectAll();
|
|
|
|
for (AActor* PastedActor : PastedActors)
|
|
{
|
|
GEditor->SelectActor(PastedActor, true, false, true);
|
|
}
|
|
|
|
ActorSelection->EndBatchSelectOperation(true);
|
|
|
|
// Note the selection change. This will also redraw level viewports and update the pivot.
|
|
NoteSelectionChange();
|
|
}
|
|
}
|
|
}
|
|
|
|
void UUnrealEdEngine::PasteComponents(TArray<UActorComponent*>& OutPastedComponents, AActor* TargetActor, const bool bWarnIfHidden, const FString* SourceData)
|
|
{
|
|
OutPastedComponents.Reset();
|
|
|
|
// Check and warn if the user is trying to paste to a hidden level. This will return if they wish to abort the process...
|
|
if (bWarnIfHidden && WarnIfDestinationLevelIsHidden(TargetActor->GetWorld()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
FComponentEditorUtils::PasteComponents(OutPastedComponents, TargetActor, TargetActor->GetRootComponent(), SourceData);
|
|
|
|
if (OutPastedComponents.Num() > 0)
|
|
{
|
|
// Make sure all the SCS trees have a chance to update
|
|
FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
|
|
LevelEditor.BroadcastComponentsEdited();
|
|
}
|
|
}
|
|
|
|
void UUnrealEdEngine::PasteActors(TArray<AActor*>& OutPastedActors, UWorld* InWorld, const FVector& LocationOffset, bool bDuplicate, bool bWarnIfHidden, const FString* SourceData)
|
|
{
|
|
OutPastedActors.Reset();
|
|
|
|
// Check and warn if the user is trying to paste to a hidden level. This will return if they wish to abort the process...
|
|
if (bWarnIfHidden && WarnIfDestinationLevelIsHidden(InWorld))
|
|
{
|
|
return;
|
|
}
|
|
|
|
OnPasteActorsBeginDelegate.Broadcast();
|
|
|
|
ON_SCOPE_EXIT{ OnPasteActorsEndDelegate.Broadcast(OutPastedActors); };
|
|
|
|
const FScopedBusyCursor BusyCursor;
|
|
|
|
FCachedActorLabels ActorLabels(InWorld);
|
|
|
|
// Get pasted text.
|
|
FString PasteString;
|
|
if (SourceData)
|
|
{
|
|
PasteString = *SourceData;
|
|
}
|
|
else
|
|
{
|
|
FPlatformApplicationMisc::ClipboardPaste(PasteString);
|
|
}
|
|
const TCHAR* Paste = *PasteString;
|
|
|
|
USelection* ActorSelection = GetSelectedActors();
|
|
|
|
// FactoryCreateText will mutate the selection, so cache it here so we can restore it later
|
|
TArray<uint8> OriginalSelectionState;
|
|
FObjectWriter(ActorSelection, OriginalSelectionState);
|
|
|
|
// 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.
|
|
TStrongObjectPtr<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;
|
|
|
|
// FactoryCreateText set the selection to the new actors, so copy that into OutPastedActors and restore the original selection
|
|
ActorSelection->GetSelectedObjects<AActor>(OutPastedActors);
|
|
FObjectReader(ActorSelection, OriginalSelectionState);
|
|
|
|
// 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>();
|
|
bool bRebuildBSP = false;
|
|
for (AActor* Actor : OutPastedActors)
|
|
{
|
|
if (!bRebuildBSP)
|
|
{
|
|
if (ABrush* Brush = Cast<ABrush>(Actor))
|
|
{
|
|
bRebuildBSP = Brush->IsStaticBrush();
|
|
}
|
|
}
|
|
|
|
if (!LocationOffset.IsZero())
|
|
{
|
|
// 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
|
|
AActor* ParentActor = Actor->GetAttachParentActor();
|
|
if (!ParentActor)
|
|
{
|
|
Actor->TeleportTo(Actor->GetActorLocation() + LocationOffset, Actor->GetActorRotation(), false, true);
|
|
}
|
|
else if(!OutPastedActors.Contains(ParentActor))
|
|
{
|
|
FName SocketName = Actor->GetAttachParentSocketName();
|
|
Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
|
|
Actor->TeleportTo(Actor->GetActorLocation() + LocationOffset, Actor->GetActorRotation(), false, true);
|
|
Actor->AttachToActor(ParentActor, FAttachmentTransformRules::KeepWorldTransform, SocketName);
|
|
}
|
|
}
|
|
|
|
if (!GetDefault<ULevelEditorMiscSettings>()->bAvoidRelabelOnPasteSelected)
|
|
{
|
|
// Re-label duplicated actors so that labels become unique
|
|
FActorLabelUtilities::SetActorLabelUnique(Actor, Actor->GetActorLabel(), &ActorLabels);
|
|
ActorLabels.Add(Actor->GetActorLabel());
|
|
}
|
|
else
|
|
{
|
|
// Make sure to broadcast label change after actor import as it may change between spawn and import
|
|
FCoreDelegates::OnActorLabelChanged.Broadcast(Actor);
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
if (bRebuildBSP)
|
|
{
|
|
RebuildAlteredBSP();
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
/**
|
|
* 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 @see PasteActors.
|
|
*/
|
|
void DuplicateActorsToLevel(TArray<AActor*>& OutNewActors, ULevel* DestLevel, const FVector& LocationOffset)
|
|
{
|
|
// 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;
|
|
}
|
|
|
|
FString ScratchData;
|
|
|
|
// Copy actors from src level
|
|
{
|
|
// Cache the current source level
|
|
ULevel* CurrentSrcLevel = SrcLevel->OwningWorld->GetCurrentLevel();
|
|
SrcLevel->OwningWorld->SetCurrentLevel(SrcLevel);
|
|
GUnrealEd->CopyActors(Actors, SrcLevel->OwningWorld, &ScratchData);
|
|
SrcLevel->OwningWorld->SetCurrentLevel(CurrentSrcLevel);
|
|
}
|
|
|
|
// Paste to the dest level
|
|
{
|
|
ULevel* CurrentDestLevel = DestLevel->OwningWorld->GetCurrentLevel();
|
|
DestLevel->OwningWorld->SetCurrentLevel(DestLevel);
|
|
|
|
TArray<AActor*> PastedActors;
|
|
GUnrealEd->PasteActors(PastedActors, DestLevel->OwningWorld, LocationOffset, /*bDuplicate*/true, /*bWarnIfHidden*/true, &ScratchData);
|
|
|
|
DestLevel->OwningWorld->SetCurrentLevel(CurrentDestLevel);
|
|
|
|
// We generate new seeds when we duplicate
|
|
for (AActor* Actor : PastedActors)
|
|
{
|
|
Actor->SeedAllRandomStreams();
|
|
}
|
|
|
|
OutNewActors.Append(MoveTemp(PastedActors));
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
void UUnrealEdEngine::edactDuplicateSelected( ULevel* InLevel, bool bOffsetLocations )
|
|
{
|
|
if (GetSelectedComponentCount() > 0)
|
|
{
|
|
USelection* ComponentSelection = GetSelectedComponents();
|
|
|
|
TArray<UActorComponent*> SelectedComponents;
|
|
ComponentSelection->GetSelectedObjects<UActorComponent>(SelectedComponents);
|
|
|
|
TArray<UActorComponent*> NewComponents;
|
|
DuplicateComponents(SelectedComponents, NewComponents);
|
|
|
|
if (NewComponents.Num() > 0)
|
|
{
|
|
// Select the new clones
|
|
ComponentSelection->Modify();
|
|
ComponentSelection->BeginBatchSelectOperation();
|
|
ComponentSelection->DeselectAll();
|
|
|
|
for (UActorComponent* NewComponent : NewComponents)
|
|
{
|
|
GEditor->SelectComponent(NewComponent, true, false);
|
|
}
|
|
|
|
ComponentSelection->EndBatchSelectOperation(true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
USelection* ActorSelection = GetSelectedActors();
|
|
|
|
TArray<AActor*> SelectedActors;
|
|
ActorSelection->GetSelectedObjects<AActor>(SelectedActors);
|
|
|
|
TArray<AActor*> NewActors;
|
|
DuplicateActors(SelectedActors, NewActors, InLevel, bOffsetLocations ? GEditor->GetGridLocationOffset(/*bUniformOffset*/false) : FVector::ZeroVector);
|
|
|
|
if (NewActors.Num() > 0)
|
|
{
|
|
// Select the new clones
|
|
ActorSelection->Modify();
|
|
ActorSelection->BeginBatchSelectOperation();
|
|
ActorSelection->DeselectAll();
|
|
|
|
for (AActor* Actor : NewActors)
|
|
{
|
|
GEditor->SelectActor(Actor, true, false);
|
|
}
|
|
|
|
ActorSelection->EndBatchSelectOperation(true);
|
|
|
|
// Note the selection change. This will also redraw level viewports and update the pivot.
|
|
NoteSelectionChange();
|
|
|
|
GLevelEditorModeTools().ActorsDuplicatedNotify(SelectedActors, NewActors, bOffsetLocations);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UUnrealEdEngine::DuplicateComponents(const TArray<UActorComponent*>& InComponentsToDuplicate, TArray<UActorComponent*>& OutNewComponents)
|
|
{
|
|
OutNewComponents.Reset(InComponentsToDuplicate.Num());
|
|
|
|
for (UActorComponent* Component : InComponentsToDuplicate)
|
|
{
|
|
if (FComponentEditorUtils::CanCopyComponent(Component))
|
|
{
|
|
if (UActorComponent* NewComponent = FComponentEditorUtils::DuplicateComponent(Component))
|
|
{
|
|
OutNewComponents.Add(NewComponent);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (OutNewComponents.Num() > 0)
|
|
{
|
|
// Make sure all the SCS trees have a chance to update
|
|
FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
|
|
LevelEditor.BroadcastComponentsEdited();
|
|
}
|
|
}
|
|
|
|
void UUnrealEdEngine::DuplicateActors(const TArray<AActor*>& InActorsToDuplicate, TArray<AActor*>& OutNewActors, ULevel* InLevel, const FVector& LocationOffset)
|
|
{
|
|
OutNewActors.Reset(InActorsToDuplicate.Num());
|
|
|
|
const FScopedBusyCursor BusyCursor;
|
|
|
|
// Create per-level job lists.
|
|
TMap<ULevel*, TSharedPtr<FDuplicateJob>> DuplicateJobs;
|
|
for (AActor* Actor : InActorsToDuplicate)
|
|
{
|
|
ULevel* SrcLevel = Actor->GetLevel();
|
|
TSharedPtr<FDuplicateJob> Job = DuplicateJobs.FindRef(SrcLevel);
|
|
if (!Job)
|
|
{
|
|
// Allocate a new job for the level.
|
|
Job = DuplicateJobs.Add(SrcLevel, MakeShared<FDuplicateJob>());
|
|
Job->SrcLevel = SrcLevel;
|
|
}
|
|
Job->Actors.Add(Actor);
|
|
}
|
|
|
|
// For each level, select the actors in that level and copy-paste into the destination level.
|
|
for (const auto& DuplicateJobPair : DuplicateJobs)
|
|
{
|
|
FDuplicateJob& Job = *DuplicateJobPair.Value;
|
|
Job.DuplicateActorsToLevel(OutNewActors, InLevel, LocationOffset);
|
|
}
|
|
|
|
// Finally, cleanup.
|
|
DuplicateJobs.Reset();
|
|
}
|
|
|
|
bool UUnrealEdEngine::CanDeleteSelectedActors( const UWorld* InWorld, const bool bStopAtFirst, const bool bLogUndeletable, TArray<AActor*>* OutDeletableActors ) const
|
|
{
|
|
// 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()) );
|
|
|
|
bool bDeletable = false;
|
|
FText CannotDeleteReason;
|
|
if (CanDeleteActor(Actor, &CannotDeleteReason))
|
|
{
|
|
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("CannotDeleteActor", "Cannot delete 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::CanDeleteComponent(const UActorComponent* InComponent, FText* OutReason) const
|
|
{
|
|
if (!InComponent->IsEditableWhenInherited())
|
|
{
|
|
if (OutReason)
|
|
{
|
|
*OutReason = LOCTEXT("CanDeleteComponent_Error_ComponentNotEditableWhenInherited", "Can't delete non-editable component.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (!FComponentEditorUtils::CanDeleteComponent(InComponent))
|
|
{
|
|
if (OutReason)
|
|
{
|
|
*OutReason = LOCTEXT("CanDeleteComponent_Error_InvalidComponent", "Can't delete non-instanced components or the scene root.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UUnrealEdEngine::CanDeleteActor(const AActor* InActor, FText* OutReason) const
|
|
{
|
|
if (!InActor->HasAllFlags(RF_Transactional))
|
|
{
|
|
if (OutReason)
|
|
{
|
|
*OutReason = LOCTEXT("CanDeleteActor_Error_NotTransactional", "Can't delete non-transactional actors.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
{
|
|
FText TmpReason;
|
|
if (!InActor->CanDeleteSelectedActor(TmpReason))
|
|
{
|
|
if (OutReason)
|
|
{
|
|
*OutReason = TmpReason;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UUnrealEdEngine::ShouldAbortComponentDeletion(const TArray<UActorComponent*>& InComponentsToDelete, FText* OutReason) const
|
|
{
|
|
for (UActorComponent* Component : InComponentsToDelete)
|
|
{
|
|
AActor* OwnerActor = Component->GetOwner();
|
|
if (OwnerActor && FLevelUtils::IsLevelLocked(OwnerActor))
|
|
{
|
|
if (OutReason)
|
|
{
|
|
*OutReason = FText::Format(LOCTEXT("ShouldAbortComponentDeletion_Error_LevelLockedDuringComponentDeletion", "Cannot delete component '{0}' because its owner level is locked."), FText::FromString(Component->GetPathName()));
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UUnrealEdEngine::ShouldAbortActorDeletion(const TArray<AActor*>& InActorsToDelete, FText* OutReason) const
|
|
{
|
|
for (AActor* Actor : InActorsToDelete)
|
|
{
|
|
if (FLevelUtils::IsLevelLocked(Actor))
|
|
{
|
|
if (OutReason)
|
|
{
|
|
*OutReason = FText::Format(LOCTEXT("ShouldAbortActorDeletion_LevelLockedDuringActorDeletion", "Cannot delete actor '{0}' because its owner level is locked."), FText::FromString(Actor->GetPathName()));
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UUnrealEdEngine::DeleteComponents(const TArray<UActorComponent*>& InComponentsToDelete, UTypedElementSelectionSet* InSelectionSet, const bool bVerifyDeletionCanHappen)
|
|
{
|
|
if (bVerifyDeletionCanHappen)
|
|
{
|
|
FText ErrorMsg;
|
|
if (ShouldAbortComponentDeletion(InComponentsToDelete, &ErrorMsg))
|
|
{
|
|
FSlateNotificationManager::Get().AddNotification(FNotificationInfo(ErrorMsg));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const double StartSeconds = FPlatformTime::Seconds();
|
|
|
|
FSlateApplication::Get().CancelDragDrop();
|
|
|
|
// Get a list of all the deletable components
|
|
TArray<UActorComponent*> EditableComponentsToDelete;
|
|
EditableComponentsToDelete.Reserve(InComponentsToDelete.Num());
|
|
for (UActorComponent* ComponentToDelete : InComponentsToDelete)
|
|
{
|
|
FText ErrorMsg;
|
|
if (!CanDeleteComponent(ComponentToDelete, &ErrorMsg))
|
|
{
|
|
UE_LOG(LogEditorActor, Log, TEXT("Cannot delete component %s: %s"), *ComponentToDelete->GetFullName(), *ErrorMsg.ToString());
|
|
continue;
|
|
}
|
|
|
|
// Modify the actor that owns the component
|
|
if (AActor* OwnerActor = ComponentToDelete->GetOwner())
|
|
{
|
|
OwnerActor->Modify();
|
|
}
|
|
|
|
EditableComponentsToDelete.Add(ComponentToDelete);
|
|
}
|
|
|
|
if (EditableComponentsToDelete.Num() == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TUniquePtr<FTypedElementList::FLegacySyncScopedBatch> LegacySyncBatch = MakeUnique<FTypedElementList::FLegacySyncScopedBatch>(*InSelectionSet->GetElementList());
|
|
|
|
const FTypedElementSelectionOptions SelectionOptions = FTypedElementSelectionOptions()
|
|
.SetAllowHidden(true)
|
|
.SetAllowGroups(false)
|
|
.SetWarnIfLocked(false)
|
|
.SetChildElementInclusionMethod(ETypedElementChildInclusionMethod::Recursive);
|
|
|
|
// Clear the selection of any components we may delete
|
|
for (UActorComponent* ComponentToDelete : EditableComponentsToDelete)
|
|
{
|
|
if (FTypedElementHandle ComponentHandle = UEngineElementsLibrary::AcquireEditorComponentElementHandle(ComponentToDelete, /*bAllowCreate*/false))
|
|
{
|
|
InSelectionSet->DeselectElement(ComponentHandle, SelectionOptions);
|
|
}
|
|
}
|
|
|
|
// Delete the components
|
|
UActorComponent* ComponentToSelect = nullptr;
|
|
const int32 NumDeletedComponents = FComponentEditorUtils::DeleteComponents(EditableComponentsToDelete, ComponentToSelect);
|
|
|
|
if (NumDeletedComponents > 0)
|
|
{
|
|
// Make sure all the SCS trees have a chance to rebuild
|
|
FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
|
|
LevelEditor.BroadcastComponentsEdited();
|
|
|
|
// Remove all references to destroyed components once at the end, instead of once for each component destroyed
|
|
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
|
|
}
|
|
|
|
// Update the editor component selection if possible
|
|
if (ComponentToSelect)
|
|
{
|
|
InSelectionSet->SelectElement(UEngineElementsLibrary::AcquireEditorComponentElementHandle(ComponentToSelect), SelectionOptions);
|
|
}
|
|
|
|
// Make sure the selection changed event fires so the SCS trees can update their selection
|
|
LegacySyncBatch->ForceDirty();
|
|
LegacySyncBatch.Reset();
|
|
NoteSelectionChange();
|
|
|
|
UE_LOG(LogEditorActor, Log, TEXT("Deleted %d Components (%3.3f secs)"), NumDeletedComponents, FPlatformTime::Seconds() - StartSeconds);
|
|
return NumDeletedComponents > 0;
|
|
}
|
|
|
|
bool UUnrealEdEngine::DeleteActors(const TArray<AActor*>& InActorsToDelete, UWorld* InWorld, UTypedElementSelectionSet* InSelectionSet, const bool bVerifyDeletionCanHappen, const bool bWarnAboutReferences, const bool bWarnAboutSoftReferences)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(UUnrealEdEngine::DeleteActors);
|
|
if (bVerifyDeletionCanHappen)
|
|
{
|
|
// TODO: Bubble up error message?
|
|
FText ErrorMsg;
|
|
if (ShouldAbortActorDeletion(InActorsToDelete, &ErrorMsg))
|
|
{
|
|
FSlateNotificationManager::Get().AddNotification(FNotificationInfo(ErrorMsg));
|
|
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;
|
|
|
|
// Fire ULevel::LevelDirtiedEvent when falling out of scope.
|
|
FScopedLevelDirtied LevelDirtyCallback;
|
|
|
|
// Get a list of all the deletable actors
|
|
TArray<AActor*> ActorsToDelete;
|
|
TArray<FSoftObjectPath> ActorsToDeletePaths;
|
|
ActorsToDelete.Reserve(InActorsToDelete.Num());
|
|
ActorsToDeletePaths.Reserve(InActorsToDelete.Num());
|
|
for (AActor* ActorToDelete : InActorsToDelete)
|
|
{
|
|
FText ErrorMsg;
|
|
if (!CanDeleteActor(ActorToDelete, &ErrorMsg))
|
|
{
|
|
// If this was a temporary editor preview actor, go ahead and delete it now
|
|
if (ActorToDelete->bIsEditorPreviewActor)
|
|
{
|
|
InWorld->DestroyActor(ActorToDelete);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogEditorActor, Log, TEXT("Cannot delete actor %s: %s"), *ActorToDelete->GetFullName(), *ErrorMsg.ToString());
|
|
}
|
|
continue;
|
|
}
|
|
|
|
ActorsToDelete.Add(ActorToDelete);
|
|
ActorsToDeletePaths.Add(ActorToDelete);
|
|
}
|
|
|
|
TMap<AActor*, TArray<AActor*>> ReferencingActorsMap;
|
|
TMap<FSoftObjectPath, TArray<UObject*>> SoftReferencingObjectsMap;
|
|
{
|
|
TArray<UClass*> ClassTypesToIgnore;
|
|
ClassesToIgnoreDeleteReferenceWarning.AddUnique(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.
|
|
ClassesToIgnoreDeleteReferenceWarning.AddUnique(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,MutableView(ClassesToIgnoreDeleteReferenceWarning), ReferencingActorsMap);
|
|
|
|
if (bWarnAboutSoftReferences)
|
|
{
|
|
FScopedSlowTask SlowTask(static_cast<float>(ActorsToDeletePaths.Num()), LOCTEXT("ComputeActorSoftReferences", "Computing References"));
|
|
SlowTask.MakeDialogDelayed(1.0f);
|
|
|
|
FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
|
|
AssetToolsModule.Get().FindSoftReferencesToObjects(ActorsToDeletePaths, SoftReferencingObjectsMap);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Maintain a list of levels that have already been Modify()'d so that each level is modified 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;
|
|
bool bRequestedDeleteAllByGroup = false;
|
|
int32 DeleteCount = 0;
|
|
|
|
TUniquePtr<FTypedElementList::FLegacySyncScopedBatch> LegacySyncBatch = MakeUnique<FTypedElementList::FLegacySyncScopedBatch>(*InSelectionSet->GetElementList());
|
|
|
|
const FTypedElementSelectionOptions SelectionOptions = FTypedElementSelectionOptions()
|
|
.SetAllowHidden(true)
|
|
.SetAllowGroups(false)
|
|
.SetWarnIfLocked(false)
|
|
.SetChildElementInclusionMethod(ETypedElementChildInclusionMethod::Recursive);
|
|
|
|
ULayersSubsystem* LayersSubsystem = GEditor->GetEditorSubsystem<ULayersSubsystem>();
|
|
for (AActor* Actor : ActorsToDelete)
|
|
{
|
|
//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 bReferencedByGroupActor = false;
|
|
bool bReferencedBySoftReference = false;
|
|
TArray<UObject*>* SoftReferencingObjects = nullptr;
|
|
const UPackage* ActorPackage = Actor->GetPackage();
|
|
|
|
if (bWarnAboutSoftReferences)
|
|
{
|
|
SoftReferencingObjects = SoftReferencingObjectsMap.Find(Actor);
|
|
|
|
if (SoftReferencingObjects)
|
|
{
|
|
// Remove any references from object types marked to be ignored
|
|
for (int32 i = SoftReferencingObjects->Num() - 1; i >= 0; --i)
|
|
{
|
|
const UObject* SoftReferencingObject = (*SoftReferencingObjects)[i];
|
|
if (const AActor* ReferencingActor = Cast<AActor>(SoftReferencingObject); ReferencingActor && ActorsToDelete.Contains(ReferencingActor))
|
|
{
|
|
SoftReferencingObjects->RemoveAt(i);
|
|
} // Object is inside the actor being deleted, so reference should be ignored (check that they share the same package because that could mean we are in a special case where warnings are needed)
|
|
else if (SoftReferencingObject->IsInOuter(Actor) && SoftReferencingObject->GetPackage() == ActorPackage)
|
|
{
|
|
SoftReferencingObjects->RemoveAt(i);
|
|
}
|
|
else
|
|
{
|
|
for (const TObjectPtr<UClass>& ClassToIgnore : ClassesToIgnoreDeleteReferenceWarning)
|
|
{
|
|
if (SoftReferencingObject->IsA(ClassToIgnore))
|
|
{
|
|
SoftReferencingObjects->RemoveAt(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bReferencedBySoftReference = SoftReferencingObjects->Num() > 0;
|
|
}
|
|
}
|
|
|
|
// 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 || ActorsToDelete.Contains(ReferencingActor))
|
|
{
|
|
continue;
|
|
}
|
|
else if (Cast<ALODActor>(ReferencingActor))
|
|
{
|
|
bReferencedByLODActor = true;
|
|
}
|
|
else if (Cast<AGroupActor>(ReferencingActor))
|
|
{
|
|
bReferencedByGroupActor = 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 || bReferencedByGroupActor)
|
|
{
|
|
if ((bReferencedByLevelScript && !bRequestedDeleteAllByLevel) ||
|
|
(bReferencedByActor && !bRequestedDeleteAllByActor) ||
|
|
(bReferencedBySoftReference && !bRequestedDeleteAllBySoftReference) ||
|
|
(bReferencedByGroupActor && !bRequestedDeleteAllByGroup))
|
|
{
|
|
FText ConfirmDelete;
|
|
|
|
// We separate groups and other actors, so we can visually display them separately in the list
|
|
TArray<TSharedPtr<FText>> GroupActorReferencers;
|
|
TArray<TSharedPtr<FText>> OtherReferencers;
|
|
|
|
if (ReferencingActors != nullptr)
|
|
{
|
|
for (AActor* ReferencingActor : *ReferencingActors)
|
|
{
|
|
if (ReferencingActor->IsA<ALevelScriptActor>())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FString ActorLabel = FString::Printf(TEXT("%s"), *ReferencingActor->GetActorLabel());
|
|
|
|
if (ReferencingActor->IsA<AGroupActor>())
|
|
{
|
|
GroupActorReferencers.Add(MakeShared<FText>(FText::FromString(ActorLabel)));
|
|
}
|
|
else if (!ReferencingActor->IsA<ALevelScriptActor>())
|
|
{
|
|
OtherReferencers.Add(MakeShared<FText>(FText::FromString(ActorLabel)));
|
|
}
|
|
}
|
|
}
|
|
|
|
OtherReferencers.Append(GroupActorReferencers);
|
|
|
|
if (bReferencedBySoftReference)
|
|
{
|
|
for (UObject* ReferencingObject : *SoftReferencingObjects)
|
|
{
|
|
FString ActorLabel;
|
|
|
|
if (AActor* ReferencingActor = Cast<AActor>(ReferencingObject))
|
|
{
|
|
ActorLabel = FString::Printf(TEXT("%s in %s (soft reference)"), *ReferencingActor->GetActorLabel(), *FPackageName::GetLongPackageAssetName(ReferencingActor->GetOutermost()->GetName()));
|
|
}
|
|
else
|
|
{
|
|
ActorLabel = FString::Printf(TEXT("%s (soft reference)"), *ReferencingObject->GetPathName());
|
|
}
|
|
|
|
OtherReferencers.Add(MakeShared<FText>(FText::FromString(ActorLabel)));
|
|
}
|
|
}
|
|
|
|
EDeletedActorReferenceTypes DeletedActorReferenceTypes = EDeletedActorReferenceTypes::None;
|
|
|
|
if (bReferencedByGroupActor)
|
|
{
|
|
EnumAddFlags(DeletedActorReferenceTypes, EDeletedActorReferenceTypes::Group);
|
|
}
|
|
|
|
if (bReferencedByActor || bReferencedBySoftReference)
|
|
{
|
|
EnumAddFlags(DeletedActorReferenceTypes, EDeletedActorReferenceTypes::ActorOrAsset);
|
|
}
|
|
|
|
if (bReferencedByLevelScript)
|
|
{
|
|
EnumAddFlags(DeletedActorReferenceTypes, EDeletedActorReferenceTypes::LevelBlueprint);
|
|
}
|
|
|
|
const double DialogStartSeconds = FPlatformTime::Seconds();
|
|
|
|
const bool bCanShowApplyToAll = ActorsToDelete.Num() > 1;
|
|
|
|
TSharedRef<SDeleteReferencedActorDialog> ConfirmDialog = SNew(SDeleteReferencedActorDialog)
|
|
.Referencers(OtherReferencers)
|
|
.ShowApplyToAll(bCanShowApplyToAll)
|
|
.ActorToDeleteLabel(Actor->GetActorLabel())
|
|
.ReferenceTypes(DeletedActorReferenceTypes);
|
|
|
|
uint32 Result = ConfirmDialog->ShowModal();
|
|
|
|
DialogWaitingSeconds += FPlatformTime::Seconds() - DialogStartSeconds;
|
|
|
|
if (ConfirmDialog->GetApplyToAll())
|
|
{
|
|
if (Result == 0)
|
|
{
|
|
bRequestedDeleteAllByLevel |= bReferencedByLevelScript;
|
|
bRequestedDeleteAllByActor |= bReferencedByActor;
|
|
bRequestedDeleteAllBySoftReference |= bReferencedBySoftReference;
|
|
bRequestedDeleteAllByGroup |= bReferencedByGroupActor;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else if (Result == 1)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (bReferencedByLevelScript)
|
|
{
|
|
FBlueprintEditorUtils::ModifyActorReferencedGraphNodes(LSB, Actor);
|
|
}
|
|
|
|
if (bReferencedByActor || bReferencedByLODActor || bReferencedByGroupActor)
|
|
{
|
|
check(ReferencingActors != nullptr);
|
|
for (AActor* ReferencingActor : *ReferencingActors)
|
|
{
|
|
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(false);
|
|
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.
|
|
if (FTypedElementHandle ActorHandle = UEngineElementsLibrary::AcquireEditorActorElementHandle(Actor, /*bAllowCreate*/false))
|
|
{
|
|
InSelectionSet->DeselectElement(ActorHandle, SelectionOptions);
|
|
}
|
|
UEngineElementsLibrary::UnregisterActorElement(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 a transient actor, or an external actor when the level is in `use external actors` mode.
|
|
bool bShouldDirty = !Actor->HasAllFlags(RF_Transient) && (!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);
|
|
|
|
LegacySyncBatch.Reset();
|
|
NoteSelectionChange();
|
|
|
|
// If any brush actors were modified, update the Bsp in the appropriate levels
|
|
if (LevelsToRebuildBSP.Num())
|
|
{
|
|
FlushRenderingCommands();
|
|
|
|
for (ULevel* Level : LevelsToRebuildBSP)
|
|
{
|
|
GEditor->RebuildLevel(*Level);
|
|
}
|
|
}
|
|
|
|
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::edactDeleteSelected( UWorld* InWorld, bool bVerifyDeletionCanHappen, bool bWarnAboutReferences, bool bWarnAboutSoftReferences)
|
|
{
|
|
if (GetSelectedComponentCount() > 0)
|
|
{
|
|
// Delete selected components
|
|
TArray<UActorComponent*> SelectedComponents;
|
|
GetSelectedComponents()->GetSelectedObjects<UActorComponent>(SelectedComponents);
|
|
return DeleteComponents(SelectedComponents, GetSelectedComponents()->GetElementSelectionSet(), bVerifyDeletionCanHappen);
|
|
}
|
|
else
|
|
{
|
|
// Delete selected actors
|
|
TArray<AActor*> SelectedActors;
|
|
GetSelectedActors()->GetSelectedObjects<AActor>(SelectedActors);
|
|
return DeleteActors(SelectedActors, InWorld, GetSelectedActors()->GetElementSelectionSet(), bVerifyDeletionCanHappen, bWarnAboutReferences, bWarnAboutSoftReferences);
|
|
}
|
|
}
|
|
|
|
bool UUnrealEdEngine::ShouldAbortActorDeletion() const
|
|
{
|
|
TArray<AActor*> SelectedActors;
|
|
GetSelectedActors()->GetSelectedObjects<AActor>(SelectedActors);
|
|
|
|
FText ErrorMsg;
|
|
if (ShouldAbortActorDeletion(SelectedActors, &ErrorMsg))
|
|
{
|
|
FSlateNotificationManager::Get().AddNotification(FNotificationInfo(ErrorMsg));
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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 )
|
|
{
|
|
if( NewBrush->GetBrushBuilder() )
|
|
{
|
|
FActorLabelUtilities::SetActorLabelUnique(NewBrush, FText::Format(NSLOCTEXT("UnrealEd", "BrushName", "{0} Brush"), FText::FromString(NewBrush->GetBrushBuilder()->GetClass()->GetDescription())).ToString());
|
|
}
|
|
|
|
SrcBrush->MarkPackageDirty();
|
|
NewBrush->MarkPackageDirty();
|
|
|
|
LevelDirtyCallback.Request();
|
|
|
|
NewBrush->Modify(false);
|
|
|
|
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->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();
|
|
}
|
|
|
|
static void SetActorVisibility(AActor* Actor, bool bVisible)
|
|
{
|
|
using namespace UE::Editor::DataStorage;
|
|
|
|
// 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);
|
|
|
|
if (const ICompatibilityProvider* DataStoreCompat = GetDataStorageFeature<ICompatibilityProvider>(CompatibilityFeatureName))
|
|
{
|
|
RowHandle Row = DataStoreCompat->FindRowWithCompatibleObject(Actor);
|
|
if (ICoreProvider* DataStore = GetMutableDataStorageFeature<ICoreProvider>(StorageFeatureName))
|
|
{
|
|
FVisibleInEditorColumn* VisibilityColumn = DataStore->GetColumn<FVisibleInEditorColumn>(Row);
|
|
if (VisibilityColumn)
|
|
{
|
|
VisibilityColumn->bIsVisibleInEditor = bVisible;
|
|
}
|
|
DataStore->AddColumn<FTypedElementSyncBackToWorldTag>(Row);
|
|
}
|
|
}
|
|
}
|
|
|
|
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 ];
|
|
|
|
SetActorVisibility(Actor, false);
|
|
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->IsActorOrSelectionParentSelected() && !Actor->IsHiddenEd() )
|
|
{
|
|
SetActorVisibility(Actor, false);
|
|
}
|
|
}
|
|
|
|
// 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() )
|
|
{
|
|
SetActorVisibility(Actor, true);
|
|
}
|
|
}
|
|
|
|
// 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->IsActorOrSelectionParentSelected() || (CurSurface.PolyFlags & PF_Selected);
|
|
if (bSelected && !CurSurface.IsHiddenEdAtStartup() && !CurSurface.IsHiddenEd())
|
|
{
|
|
CurLevelModel.Modify(false);
|
|
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(false);
|
|
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->IsActorOrSelectionParentSelected() || (CurSurface.PolyFlags & PF_Selected);
|
|
if (bSelected && CurSurface.IsHiddenEdAtStartup())
|
|
{
|
|
CurLevelModel.Modify(false);
|
|
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->IsActorOrSelectionParentSelected() && 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 ];
|
|
|
|
SetActorVisibility(Actor, true);
|
|
}
|
|
}
|
|
|
|
// 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( !IsValid(Actor) )
|
|
{
|
|
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()->GetSkeletalMeshAsset());
|
|
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->GetSkeletalMeshAsset());
|
|
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()->GetSkeletalMeshAsset()) )
|
|
{
|
|
bSelectActor = true;
|
|
}
|
|
}
|
|
|
|
if(bSelectPawns)
|
|
{
|
|
APawn* Pawn = Cast<APawn>(Actor);
|
|
if (Pawn)
|
|
{
|
|
USkeletalMeshComponent* PawnSkeletalMesh = Pawn->FindComponentByClass<USkeletalMeshComponent>();
|
|
if (PawnSkeletalMesh && SelectedMeshes.Contains(PawnSkeletalMesh->GetSkeletalMeshAsset()) )
|
|
{
|
|
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(false);
|
|
|
|
//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(false);
|
|
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 FVector3f Vertex = Poly->Vertices[VertIdx];
|
|
const FVector VertexWorld = BrushTransform.TransformPosition((FVector)Vertex);
|
|
const FVector3f VertexSnapped(FMath::RoundToFloat(VertexWorld.X / GridSize) * GridSize,
|
|
FMath::RoundToFloat(VertexWorld.Y / GridSize) * GridSize,
|
|
FMath::RoundToFloat(VertexWorld.Z / GridSize) * GridSize);
|
|
const FVector VertexSnappedLocal = BrushTransform.InverseTransformPosition((FVector)VertexSnapped);
|
|
|
|
Poly->Vertices[VertIdx] = (FVector3f)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
|