EM_Task/UnrealEd/Private/GroupActor.cpp
Boshuang Zhao 5144a49c9b add
2026-02-13 16:18:33 +08:00

969 lines
32 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Editor/GroupActor.h"
#include "Misc/MessageDialog.h"
#include "Editor/UnrealEdEngine.h"
#include "Components/BillboardComponent.h"
#include "Engine/Selection.h"
#include "EditorModeManager.h"
#include "EditorModes.h"
#include "UnrealEdGlobals.h"
#include "ScopedTransaction.h"
#include "LevelEditorViewport.h"
#include "Layers/LayersSubsystem.h"
#include "ActorGroupingUtils.h"
const FLinearColor BOXCOLOR_LOCKEDGROUPS(0.0f, 1.0f, 0.0f);
const FLinearColor BOXCOLOR_UNLOCKEDGROUPS(1.0f, 0.0f, 0.0f);
AGroupActor::AGroupActor(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
bLocked = true;
USceneComponent* GroupComponent = CreateDefaultSubobject<USceneComponent>(TEXT("GroupComponent"));
RootComponent = GroupComponent;
}
void AGroupActor::PostActorCreated()
{
// Cache our newly created group
if (!GetWorld()->IsPlayInEditor() && !IsRunningCommandlet() && GIsEditor)
{
GetWorld()->ActiveGroupActors.AddUnique(this);
}
Super::PostActorCreated();
}
void AGroupActor::PostLoad()
{
if (!GetWorld()->IsPlayInEditor() && !IsRunningCommandlet() && GIsEditor)
{
// Cache group on de-serialization
GetWorld()->ActiveGroupActors.AddUnique(this);
// Fix up references for GetParentForActor()
for (int32 i = 0; i < GroupActors.Num(); ++i)
{
if (GroupActors[i] != NULL)
{
GroupActors[i]->GroupActor = this;
}
}
}
Super::PostLoad();
}
void AGroupActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
// Re-instate group as active if it had children after undo/redo
if (GroupActors.Num() || SubGroups.Num())
{
GetWorld()->ActiveGroupActors.AddUnique(this);
}
else // Otherwise, attempt to remove them
{
GetWorld()->ActiveGroupActors.Remove(this);
}
Super::PostEditChangeProperty(PropertyChangedEvent);
}
void AGroupActor::PostEditUndo()
{
Super::PostEditUndo();
if (IsPendingKill())
{
GetWorld()->ActiveGroupActors.RemoveSwap(this);
}
else
{
// Cache group on de-serialization
GetWorld()->ActiveGroupActors.AddUnique(this);
// Fix up references for GetParentForActor()
for (int32 i = 0; i < GroupActors.Num(); ++i)
{
if (GroupActors[i] != NULL)
{
GroupActors[i]->GroupActor = this;
}
}
}
}
bool AGroupActor::IsSelected() const
{
// Group actors can only count as 'selected' if they are locked
return (IsLocked() && HasSelectedActors()) || Super::IsSelected();
}
bool ActorHasParentInGroup(const TArray<class AActor*>& GroupActors, const AActor* Actor)
{
check(Actor);
// Check that we've not got a parent attachment within the group.
USceneComponent* Curr = Actor->GetRootComponent();
for (int32 OtherIndex = 0; OtherIndex < GroupActors.Num(); ++OtherIndex)
{
const AActor* OtherActor = GroupActors[OtherIndex];
if (OtherActor != NULL && OtherActor != Actor)
{
USceneComponent* Other = OtherActor->GetRootComponent();
if (Curr->IsAttachedTo(Other))
{
// We do have parent so don't apply the delta - our parent object will apply it instead.
return true;
}
}
}
return false;
}
bool ActorHasParentInSelection(const AActor* Actor)
{
check(Actor);
bool bHasParentInSelection = false;
AActor* ParentActor = Actor->GetAttachParentActor();
while (ParentActor != NULL && !bHasParentInSelection)
{
if (ParentActor->IsSelected())
{
bHasParentInSelection = true;
}
ParentActor = ParentActor->GetAttachParentActor();
}
return bHasParentInSelection;
}
void AGroupActor::GroupApplyDelta(FLevelEditorViewportClient* Viewport, const FVector& InDrag, const FRotator& InRot, const FVector& InScale)
{
check(Viewport);
for (int32 ActorIndex = 0; ActorIndex < GroupActors.Num(); ++ActorIndex)
{
if (GroupActors[ActorIndex] != NULL)
{
// Check that we've not got a parent attachment within the group/selection
const bool bCanApplyDelta = !ActorHasParentInGroup(GroupActors, GroupActors[ActorIndex]) && !ActorHasParentInSelection(GroupActors[ActorIndex]);
if (bCanApplyDelta)
{
Viewport->ApplyDeltaToActor(GroupActors[ActorIndex], InDrag, InRot, InScale);
}
}
}
for (int32 SubGroupIndex = 0; SubGroupIndex < SubGroups.Num(); ++SubGroupIndex)
{
if (SubGroups[SubGroupIndex] != NULL)
{
SubGroups[SubGroupIndex]->GroupApplyDelta(Viewport, InDrag, InRot, InScale);
}
}
Viewport->ApplyDeltaToActor(this, InDrag, InRot, InScale);
}
void AGroupActor::GroupApplyDelta(const FVector& InDrag, const FRotator& InRot, const FVector& InScale)
{
for (int32 ActorIndex = 0; ActorIndex < GroupActors.Num(); ++ActorIndex)
{
if (GroupActors[ActorIndex] != NULL)
{
// Check that we've not got a parent attachment within the group/selection
const bool bCanApplyDelta = !ActorHasParentInGroup(GroupActors, GroupActors[ActorIndex]) && !ActorHasParentInSelection(GroupActors[ActorIndex]);
if (bCanApplyDelta)
{
GEditor->ApplyDeltaToActor(GroupActors[ActorIndex], true, &InDrag, &InRot, &InScale);
}
}
}
for (int32 SubGroupIndex = 0; SubGroupIndex < SubGroups.Num(); ++SubGroupIndex)
{
if (SubGroups[SubGroupIndex] != NULL)
{
SubGroups[SubGroupIndex]->GroupApplyDelta(InDrag, InRot, InScale);
}
}
GEditor->ApplyDeltaToActor(this, true, &InDrag, &InRot, &InScale);
}
bool AGroupActor::Modify(bool bAlwaysMarkDirty /*=true*/)
{
bool bSavedToTransactionBuffer = false;
for (int32 ActorIndex = 0; ActorIndex < GroupActors.Num(); ++ActorIndex)
{
if (GroupActors[ActorIndex] != NULL)
{
bSavedToTransactionBuffer = GroupActors[ActorIndex]->Modify(bAlwaysMarkDirty) || bSavedToTransactionBuffer;
}
}
for (int32 SubGroupIndex = 0; SubGroupIndex < SubGroups.Num(); ++SubGroupIndex)
{
if (SubGroups[SubGroupIndex] != NULL)
{
bSavedToTransactionBuffer = SubGroups[SubGroupIndex]->Modify(bAlwaysMarkDirty) || bSavedToTransactionBuffer;
}
}
bSavedToTransactionBuffer = Super::Modify(bAlwaysMarkDirty) || bSavedToTransactionBuffer;
return bSavedToTransactionBuffer;
}
void AGroupActor::InvalidateLightingCacheDetailed(bool bTranslationOnly)
{
for (int32 ActorIndex = 0; ActorIndex < GroupActors.Num(); ++ActorIndex)
{
if (GroupActors[ActorIndex] != NULL)
{
GroupActors[ActorIndex]->InvalidateLightingCacheDetailed(bTranslationOnly);
}
}
for (int32 SubGroupIndex = 0; SubGroupIndex < SubGroups.Num(); ++SubGroupIndex)
{
if (SubGroups[SubGroupIndex] != NULL)
{
SubGroups[SubGroupIndex]->InvalidateLightingCacheDetailed(bTranslationOnly);
}
}
Super::InvalidateLightingCacheDetailed(bTranslationOnly);
}
void AGroupActor::PostEditMove(bool bFinished)
{
for (int32 ActorIndex = 0; ActorIndex < GroupActors.Num(); ++ActorIndex)
{
if (GroupActors[ActorIndex] != NULL)
{
GroupActors[ActorIndex]->PostEditMove(bFinished);
}
}
for (int32 SubGroupIndex = 0; SubGroupIndex < SubGroups.Num(); ++SubGroupIndex)
{
if (SubGroups[SubGroupIndex] != NULL)
{
SubGroups[SubGroupIndex]->PostEditMove(bFinished);
}
}
Super::PostEditMove(bFinished);
}
void AGroupActor::SetIsTemporarilyHiddenInEditor(bool bIsHidden)
{
Super::SetIsTemporarilyHiddenInEditor(bIsHidden);
for (int32 ActorIndex = 0; ActorIndex < GroupActors.Num(); ++ActorIndex)
{
if (GroupActors[ActorIndex] != NULL)
{
GroupActors[ActorIndex]->SetIsTemporarilyHiddenInEditor(bIsHidden);
}
}
for (int32 SubGroupIndex = 0; SubGroupIndex < SubGroups.Num(); ++SubGroupIndex)
{
if (SubGroups[SubGroupIndex] != NULL)
{
SubGroups[SubGroupIndex]->SetIsTemporarilyHiddenInEditor(bIsHidden);
}
}
}
void GetBoundingVectorsForGroup(AGroupActor* GroupActor, FViewport* Viewport, FVector& OutVectorMin, FVector& OutVectorMax)
{
// Draw a bounding box for grouped actors using the vector range we can gather from any child actors (including subgroups)
OutVectorMin = FVector(BIG_NUMBER);
OutVectorMax = FVector(-BIG_NUMBER);
// Grab all actors for this group, including those within subgroups
TArray<AActor*> ActorsInGroup;
GroupActor->GetGroupActors(ActorsInGroup, true);
// Loop through and collect each actor, using their bounding box to create the bounds for this group
for (int32 ActorIndex = 0; ActorIndex < ActorsInGroup.Num(); ++ActorIndex)
{
AActor* Actor = ActorsInGroup[ActorIndex];
uint64 HiddenClients = Actor->HiddenEditorViews;
bool bActorHiddenForViewport = false;
if (!Actor->IsHiddenEd())
{
if (Viewport)
{
for (int32 ViewIndex = 0; ViewIndex < GEditor->GetLevelViewportClients().Num(); ++ViewIndex)
{
// If the current viewport is hiding this actor, don't draw brackets around it
if (Viewport->GetClient() == GEditor->GetLevelViewportClients()[ViewIndex] && HiddenClients & ((uint64)1 << ViewIndex))
{
bActorHiddenForViewport = true;
break;
}
}
}
if (!bActorHiddenForViewport)
{
FBox ActorBox;
// First check to see if we're dealing with a sprite, otherwise just use the normal bounding box
UBillboardComponent* SpriteComponent = Actor->FindComponentByClass<UBillboardComponent>();
if (SpriteComponent != NULL)
{
ActorBox = SpriteComponent->Bounds.GetBox();
}
else
{
ActorBox = Actor->GetComponentsBoundingBox(true);
}
// MinVector
OutVectorMin.X = FMath::Min<float>(ActorBox.Min.X, OutVectorMin.X);
OutVectorMin.Y = FMath::Min<float>(ActorBox.Min.Y, OutVectorMin.Y);
OutVectorMin.Z = FMath::Min<float>(ActorBox.Min.Z, OutVectorMin.Z);
// MaxVector
OutVectorMax.X = FMath::Max<float>(ActorBox.Max.X, OutVectorMax.X);
OutVectorMax.Y = FMath::Max<float>(ActorBox.Max.Y, OutVectorMax.Y);
OutVectorMax.Z = FMath::Max<float>(ActorBox.Max.Z, OutVectorMax.Z);
}
}
}
}
/**
* Draw brackets around all given groups
* @param PDI FPrimitiveDrawInterface used to draw lines in active viewports
* @param Viewport Current viewport being rendered
* @param InGroupList Array of groups to draw brackets for
*/
void PrivateDrawBracketsForGroups(FPrimitiveDrawInterface* PDI, FViewport* Viewport, const TArray<AGroupActor*>& InGroupList)
{
// Loop through each given group and draw all subgroups and actors
for (int32 GroupIndex = 0; GroupIndex < InGroupList.Num(); ++GroupIndex)
{
AGroupActor* GroupActor = InGroupList[GroupIndex];
if (GroupActor->GetWorld() == PDI->View->Family->Scene->GetWorld())
{
const FLinearColor GROUP_COLOR = GroupActor->IsLocked() ? BOXCOLOR_LOCKEDGROUPS : BOXCOLOR_UNLOCKEDGROUPS;
FVector MinVector;
FVector MaxVector;
GetBoundingVectorsForGroup(GroupActor, Viewport, MinVector, MaxVector);
// Create a bracket offset to pad the space between brackets and actor(s) and determine the length of our corner axises
float BracketOffset = FVector::Dist(MinVector, MaxVector) * 0.1f;
MinVector = MinVector - BracketOffset;
MaxVector = MaxVector + BracketOffset;
// Calculate bracket corners based on min/max vectors
TArray<FVector> BracketCorners;
// Bottom Corners
BracketCorners.Add(FVector(MinVector.X, MinVector.Y, MinVector.Z));
BracketCorners.Add(FVector(MinVector.X, MaxVector.Y, MinVector.Z));
BracketCorners.Add(FVector(MaxVector.X, MaxVector.Y, MinVector.Z));
BracketCorners.Add(FVector(MaxVector.X, MinVector.Y, MinVector.Z));
// Top Corners
BracketCorners.Add(FVector(MinVector.X, MinVector.Y, MaxVector.Z));
BracketCorners.Add(FVector(MinVector.X, MaxVector.Y, MaxVector.Z));
BracketCorners.Add(FVector(MaxVector.X, MaxVector.Y, MaxVector.Z));
BracketCorners.Add(FVector(MaxVector.X, MinVector.Y, MaxVector.Z));
for (int32 BracketCornerIndex = 0; BracketCornerIndex < BracketCorners.Num(); ++BracketCornerIndex)
{
// Direction corner axis should be pointing based on min/max
const FVector CORNER = BracketCorners[BracketCornerIndex];
const int32 DIR_X = CORNER.X == MaxVector.X ? -1 : 1;
const int32 DIR_Y = CORNER.Y == MaxVector.Y ? -1 : 1;
const int32 DIR_Z = CORNER.Z == MaxVector.Z ? -1 : 1;
PDI->DrawLine(CORNER, FVector(CORNER.X + (BracketOffset * DIR_X), CORNER.Y, CORNER.Z), GROUP_COLOR, SDPG_Foreground);
PDI->DrawLine(CORNER, FVector(CORNER.X, CORNER.Y + (BracketOffset * DIR_Y), CORNER.Z), GROUP_COLOR, SDPG_Foreground);
PDI->DrawLine(CORNER, FVector(CORNER.X, CORNER.Y, CORNER.Z + (BracketOffset * DIR_Z)), GROUP_COLOR, SDPG_Foreground);
}
// Recurse through to any subgroups
TArray<AGroupActor*> SubGroupsInGroup;
GroupActor->GetSubGroups(SubGroupsInGroup);
PrivateDrawBracketsForGroups(PDI, Viewport, SubGroupsInGroup);
}
}
}
void AGroupActor::DrawBracketsForGroups(FPrimitiveDrawInterface* PDI, FViewport* Viewport, bool bMustBeSelected /*=true*/)
{
// Don't draw group actor brackets in game view
if (Viewport->GetClient()->IsInGameView())
{
return;
}
if (UActorGroupingUtils::IsGroupingActive())
{
check(PDI);
UWorld* EditorWorld = GEditor->GetEditorWorldContext().World();
if (EditorWorld)
{
TArray<AGroupActor*> GroupsToDraw;
for (int32 GroupIndex = 0; GroupIndex < EditorWorld->ActiveGroupActors.Num(); ++GroupIndex)
{
AGroupActor* GroupActor = Cast<AGroupActor>(EditorWorld->ActiveGroupActors[GroupIndex]);
if (GroupActor != NULL)
{
if (bMustBeSelected)
{
// If we're only drawing for selected group, grab only those that have currently selected actors
if (GroupActor->HasSelectedActors())
{
// We want to start drawing groups from the highest root level.
// Subgroups will be propagated through during the draw code.
GroupActor = AGroupActor::GetRootForActor(GroupActor);
GroupsToDraw.Add(GroupActor);
}
}
else
{
// Otherwise, just add all group actors
GroupsToDraw.Add(GroupActor);
}
}
}
PrivateDrawBracketsForGroups(PDI, Viewport, GroupsToDraw);
}
}
}
/**
* Checks to see if the given GroupActor has any parents in the given Array.
* @param InGroupActor Group to check lineage
* @param InGroupArray Array to search for the given group's parent
* @return True if a parent was found.
*/
bool GroupHasParentInArray(AGroupActor* InGroupActor, TArray<AGroupActor*>& InGroupArray)
{
check(InGroupActor);
AGroupActor* CurrentParentNode = AGroupActor::GetParentForActor(InGroupActor);
// Use a cursor pointer to continually move up from our starting pointer (InGroupActor) through the hierarchy until
// we find a valid parent in the given array, or run out of nodes.
while (CurrentParentNode)
{
if (InGroupArray.Contains(CurrentParentNode))
{
return true;
}
CurrentParentNode = AGroupActor::GetParentForActor(CurrentParentNode);
}
return false;
}
void AGroupActor::RemoveSubGroupsFromArray(TArray<AGroupActor*>& GroupArray)
{
for (int32 GroupIndex = 0; GroupIndex < GroupArray.Num(); ++GroupIndex)
{
AGroupActor* GroupToCheck = GroupArray[GroupIndex];
if (GroupHasParentInArray(GroupToCheck, GroupArray))
{
GroupArray.Remove(GroupToCheck);
--GroupIndex;
}
}
}
AGroupActor* AGroupActor::GetRootForActor(AActor* InActor, bool bMustBeLocked /*=false*/, bool bMustBeSelected /*=false*/, bool bMustBeUnlocked /*=false*/, bool bMustBeUnselected /*=false*/)
{
AGroupActor* RootNode = NULL;
// If InActor is a group, use that as the beginning iteration node, else try to find the parent
AGroupActor* InGroupActor = Cast<AGroupActor>(InActor);
AGroupActor* IteratingNode = InGroupActor == NULL ? AGroupActor::GetParentForActor(InActor) : InGroupActor;
while (IteratingNode)
{
if ((!bMustBeLocked || IteratingNode->IsLocked()) && (!bMustBeSelected || IteratingNode->HasSelectedActors()) && (!bMustBeUnlocked || !IteratingNode->IsLocked()) && (!bMustBeUnselected || !IteratingNode->HasSelectedActors()))
{
RootNode = IteratingNode;
}
IteratingNode = AGroupActor::GetParentForActor(IteratingNode);
}
return RootNode;
}
AGroupActor* AGroupActor::GetParentForActor(AActor* InActor)
{
return Cast<AGroupActor>(InActor->GroupActor);
}
const int32 AGroupActor::NumActiveGroups(bool bSelected /*=false*/, bool bDeepSearch /*=true*/)
{
UWorld* EditorWorld = GEditor->GetEditorWorldContext().World();
if (EditorWorld)
{
if (!bSelected)
{
return EditorWorld->ActiveGroupActors.Num();
}
int32 ActiveSelectedGroups = 0;
for (int32 GroupIdx = 0; GroupIdx < EditorWorld->ActiveGroupActors.Num(); ++GroupIdx)
{
AGroupActor* GroupActor = Cast<AGroupActor>(EditorWorld->ActiveGroupActors[GroupIdx]);
if (GroupActor != NULL)
{
if (GroupActor->HasSelectedActors(bDeepSearch))
{
++ActiveSelectedGroups;
}
}
}
return ActiveSelectedGroups;
}
return 0;
}
void AGroupActor::AddSelectedActorsToSelectedGroup()
{
UWorld* EditorWorld = GEditor->GetEditorWorldContext().World();
if (EditorWorld)
{
int32 SelectedGroupIndex = -1;
for (int32 GroupIdx = 0; GroupIdx < EditorWorld->ActiveGroupActors.Num(); ++GroupIdx)
{
AGroupActor* GroupActor = Cast<AGroupActor>(EditorWorld->ActiveGroupActors[GroupIdx]);
if (GroupActor != NULL)
{
if (GroupActor->HasSelectedActors(false))
{
// Assign the index of the selected group.
// If this is the second group we find, too many groups are selected, return.
if (SelectedGroupIndex == -1)
{
SelectedGroupIndex = GroupIdx;
}
else
{
return;
}
}
}
}
AGroupActor* SelectedGroup = Cast<AGroupActor>(EditorWorld->ActiveGroupActors[SelectedGroupIndex]);
if (SelectedGroupIndex != -1 && SelectedGroup != NULL)
{
ULevel* GroupLevel = SelectedGroup->GetLevel();
// We've established that only one group is selected, so we can just call Add on all these actors.
// Any actors already in the group will be ignored.
TArray<AActor*> ActorsToAdd;
bool bActorsInSameLevel = true;
for (FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It)
{
AActor* Actor = CastChecked<AActor>(*It);
if (Actor->GetLevel() == GroupLevel)
{
ActorsToAdd.Add(Actor);
}
else
{
bActorsInSameLevel = false;
break;
}
}
if (bActorsInSameLevel)
{
if (ActorsToAdd.Num() > 0)
{
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "Group_Add", "Add Actors to Group"));
for (int32 ActorIndex = 0; ActorIndex < ActorsToAdd.Num(); ++ActorIndex)
{
if (ActorsToAdd[ActorIndex] != SelectedGroup)
{
SelectedGroup->Add(*ActorsToAdd[ActorIndex]);
}
}
SelectedGroup->CenterGroupLocation();
}
}
else
{
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "Group_CantCreateGroupMultipleLevels", "Can't group the selected actors because they are in different levels."));
}
}
}
}
void AGroupActor::LockSelectedGroups()
{
UWorld* EditorWorld = GEditor->GetEditorWorldContext().World();
if (EditorWorld)
{
TArray<AGroupActor*> GroupsToLock;
for (int32 GroupIndex = 0; GroupIndex < EditorWorld->ActiveGroupActors.Num(); ++GroupIndex)
{
AGroupActor* GroupToLock = Cast<AGroupActor>(EditorWorld->ActiveGroupActors[GroupIndex]);
if (GroupToLock != NULL)
{
if (GroupToLock->HasSelectedActors(false))
{
// If our selected group is already locked, move up a level to add it's potential parent for locking
if (GroupToLock->IsLocked())
{
AGroupActor* GroupParent = AGroupActor::GetParentForActor(GroupToLock);
if (GroupParent && !GroupParent->IsLocked())
{
GroupsToLock.AddUnique(GroupParent);
}
}
else // if it's not locked, add it instead!
{
GroupsToLock.AddUnique(GroupToLock);
}
}
}
}
if (GroupsToLock.Num() > 0)
{
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "Group_Lock", "Lock Groups"));
for (int32 GroupIndex = 0; GroupIndex < GroupsToLock.Num(); ++GroupIndex)
{
AGroupActor* GroupToLock = GroupsToLock[GroupIndex];
GroupToLock->Modify();
GroupToLock->Lock();
GEditor->SelectGroup(GroupToLock, false);
}
GEditor->NoteSelectionChange();
}
}
}
void AGroupActor::UnlockSelectedGroups()
{
UWorld* EditorWorld = GEditor->GetEditorWorldContext().World();
if (EditorWorld)
{
TArray<AGroupActor*> GroupsToUnlock;
for (int32 GroupIndex = 0; GroupIndex < EditorWorld->ActiveGroupActors.Num(); ++GroupIndex)
{
AGroupActor* GroupToUnlock = Cast<AGroupActor>(EditorWorld->ActiveGroupActors[GroupIndex]);
if (GroupToUnlock != NULL)
{
if (GroupToUnlock->IsSelected())
{
GroupsToUnlock.Add(GroupToUnlock);
}
}
}
// Only unlock topmost selected group(s)
AGroupActor::RemoveSubGroupsFromArray(GroupsToUnlock);
if (GroupsToUnlock.Num() > 0)
{
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "Group_Unlock", "Unlock Groups"));
for (int32 GroupIndex = 0; GroupIndex < GroupsToUnlock.Num(); ++GroupIndex)
{
AGroupActor* GroupToUnlock = GroupsToUnlock[GroupIndex];
GroupToUnlock->Modify();
GroupToUnlock->Unlock();
}
GEditor->NoteSelectionChange();
}
}
}
void AGroupActor::ToggleGroupMode()
{
// Group mode can only be toggled when not in InterpEdit mode
if (!GLevelEditorModeTools().IsModeActive(FBuiltinEditorModes::EM_InterpEdit))
{
UActorGroupingUtils::SetGroupingActive(!UActorGroupingUtils::IsGroupingActive());
// Update group selection in the editor to reflect the toggle
SelectGroupsInSelection();
GEditor->RedrawAllViewports();
GEditor->SaveConfig();
}
}
void AGroupActor::SelectGroupsInSelection()
{
if (UActorGroupingUtils::IsGroupingActive())
{
TArray<AGroupActor*> GroupsToSelect;
for (FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It)
{
AActor* Actor = static_cast<AActor*>(*It);
checkSlow(Actor->IsA(AActor::StaticClass()));
AGroupActor* GroupActor = AGroupActor::GetRootForActor(Actor, true);
if (GroupActor)
{
GroupsToSelect.AddUnique(GroupActor);
}
}
// Select any groups from the currently selected actors
for (int32 GroupIndex = 0; GroupIndex < GroupsToSelect.Num(); ++GroupIndex)
{
AGroupActor* GroupToSelect = GroupsToSelect[GroupIndex];
GEditor->SelectGroup(GroupToSelect);
}
GEditor->NoteSelectionChange();
}
}
void AGroupActor::Lock()
{
bLocked = true;
for (int32 SubGroupIdx = 0; SubGroupIdx < SubGroups.Num(); ++SubGroupIdx)
{
if (SubGroups[SubGroupIdx] != NULL)
{
SubGroups[SubGroupIdx]->Lock();
}
}
}
void AGroupActor::Add(AActor& InActor)
{
// See if the incoming actor already belongs to a group
AGroupActor* InActorParent = AGroupActor::GetParentForActor(&InActor);
if (InActorParent) // If so, detach it first
{
if (InActorParent == this)
{
return;
}
InActorParent->Modify();
InActorParent->Remove(InActor);
}
Modify();
AGroupActor* InGroupPtr = Cast<AGroupActor>(&InActor);
if (InGroupPtr)
{
check(InGroupPtr != this);
SubGroups.AddUnique(InGroupPtr);
}
else
{
GroupActors.AddUnique(&InActor);
InActor.Modify();
InActor.GroupActor = this;
}
}
void AGroupActor::Remove(AActor& InActor)
{
AGroupActor* InGroupPtr = Cast<AGroupActor>(&InActor);
if (InGroupPtr && SubGroups.Contains(InGroupPtr))
{
Modify();
SubGroups.Remove(InGroupPtr);
}
else if (GroupActors.Contains(&InActor))
{
Modify();
GroupActors.Remove(&InActor);
InActor.Modify();
InActor.GroupActor = NULL;
}
PostRemove();
}
void AGroupActor::PostRemove()
{
// If all children have been removed (or only one subgroup remains), this group is no longer active.
if (GroupActors.Num() == 0 && SubGroups.Num() <= 1)
{
// Remove any potentially remaining subgroups
SubGroups.Empty();
// Destroy the actor and remove it from active groups
AGroupActor* ParentGroup = AGroupActor::GetParentForActor(this);
if (ParentGroup)
{
ParentGroup->Modify();
ParentGroup->Remove(*this);
}
UWorld* MyWorld = GetWorld();
if (MyWorld)
{
// Group is no longer active
MyWorld->ActiveGroupActors.Remove(this);
MyWorld->ModifyLevel(GetLevel());
// Mark the group actor for removal
MarkPackageDirty();
// If not currently garbage collecting (changing maps, saving, etc), remove the group immediately
if (!IsGarbageCollecting())
{
// Refresh all editor browsers after removal
FScopedRefreshAllBrowsers LevelRefreshAllBrowsers;
// Destroy group and clear references.
ULayersSubsystem* LayersSubsystem = GEditor->GetEditorSubsystem<ULayersSubsystem>();
LayersSubsystem->DisassociateActorFromLayers(this);
MyWorld->EditorDestroyActor(this, false);
LevelRefreshAllBrowsers.Request();
}
}
}
}
bool AGroupActor::Contains(AActor& InActor) const
{
AActor* InActorPtr = &InActor;
AGroupActor* InGroupPtr = Cast<AGroupActor>(InActorPtr);
if (InGroupPtr)
{
return SubGroups.Contains(InGroupPtr);
}
return GroupActors.Contains(InActorPtr);
}
bool AGroupActor::HasSelectedActors(bool bDeepSearch /*=true*/) const
{
for (int32 ActorIndex = 0; ActorIndex < GroupActors.Num(); ++ActorIndex)
{
if (GroupActors[ActorIndex] != NULL)
{
if (GroupActors[ActorIndex]->IsSelected())
{
return true;
}
}
}
if (bDeepSearch)
{
for (int32 GroupIndex = 0; GroupIndex < SubGroups.Num(); ++GroupIndex)
{
if (SubGroups[GroupIndex] != NULL)
{
if (SubGroups[GroupIndex]->HasSelectedActors(bDeepSearch))
{
return true;
}
}
}
}
return false;
}
void AGroupActor::ClearAndRemove()
{
// Actors can potentially be NULL here. Some older maps can serialize invalid Actors
// into GroupActors or SubGroups.
for (int32 ActorIndex = 0; ActorIndex < GroupActors.Num(); ++ActorIndex)
{
if (GroupActors[ActorIndex])
{
Remove(*GroupActors[ActorIndex]);
}
else
{
GroupActors.RemoveAt(ActorIndex);
PostRemove();
}
--ActorIndex;
}
for (int32 SubGroupIndex = 0; SubGroupIndex < SubGroups.Num(); ++SubGroupIndex)
{
if (SubGroups[SubGroupIndex])
{
Remove(*SubGroups[SubGroupIndex]);
}
else
{
SubGroups.RemoveAt(SubGroupIndex);
PostRemove();
}
--SubGroupIndex;
}
}
void AGroupActor::CenterGroupLocation()
{
FVector MinVector;
FVector MaxVector;
GetBoundingVectorsForGroup(this, NULL, MinVector, MaxVector);
SetActorLocation((MinVector + MaxVector) * 0.5f, false);
GEditor->NoteSelectionChange();
}
void AGroupActor::GetGroupActors(TArray<AActor*>& OutGroupActors, bool bRecurse /*=false*/) const
{
if (bRecurse)
{
for (int32 i = 0; i < SubGroups.Num(); ++i)
{
if (SubGroups[i] != NULL)
{
SubGroups[i]->GetGroupActors(OutGroupActors, bRecurse);
}
}
}
else
{
OutGroupActors.Empty();
}
for (int32 i = 0; i < GroupActors.Num(); ++i)
{
if (GroupActors[i] != NULL)
{
OutGroupActors.Add(GroupActors[i]);
}
}
}
void AGroupActor::GetSubGroups(TArray<AGroupActor*>& OutSubGroups, bool bRecurse /*=false*/) const
{
if (bRecurse)
{
for (int32 i = 0; i < SubGroups.Num(); ++i)
{
if (SubGroups[i] != NULL)
{
SubGroups[i]->GetSubGroups(OutSubGroups, bRecurse);
}
}
}
else
{
OutSubGroups.Empty();
}
for (int32 i = 0; i < SubGroups.Num(); ++i)
{
if (SubGroups[i] != NULL)
{
OutSubGroups.Add(SubGroups[i]);
}
}
}
void AGroupActor::GetAllChildren(TArray<AActor*>& OutChildren, bool bRecurse /*=false*/) const
{
GetGroupActors(OutChildren, bRecurse);
TArray<AGroupActor*> OutSubGroups;
GetSubGroups(OutSubGroups, bRecurse);
for (int32 SubGroupIdx = 0; SubGroupIdx < OutSubGroups.Num(); ++SubGroupIdx)
{
OutChildren.Add(OutSubGroups[SubGroupIdx]);
}
}
int32 AGroupActor::GetActorNum() const
{
return GroupActors.Num();
}