Files
UnrealEngine/Engine/Source/Editor/HierarchicalLODOutliner/Private/LODActorItem.cpp
2025-05-18 13:04:45 +08:00

392 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "LODActorItem.h"
#include "Containers/Array.h"
#include "Delegates/Delegate.h"
#include "Engine/Level.h"
#include "Framework/Commands/UIAction.h"
#include "GameFramework/Actor.h"
#include "HAL/Platform.h"
#include "HAL/PlatformCrt.h"
#include "HLODOutliner.h"
#include "HierarchicalLODType.h"
#include "HierarchicalLODUtilitiesModule.h"
#include "IHierarchicalLODUtilities.h"
#include "ITreeItem.h"
#include "Internationalization/Internationalization.h"
#include "Math/Color.h"
#include "Misc/Optional.h"
#include "Modules/ModuleManager.h"
#include "Templates/Casts.h"
#include "Textures/SlateIcon.h"
#include "ToolMenu.h"
#include "ToolMenuSection.h"
#include "UObject/Object.h"
#include "UObject/ObjectPtr.h"
class SWidget;
#define LOCTEXT_NAMESPACE "LODActorItem"
HLODOutliner::FLODActorItem::FLODActorItem(ALODActor* InLODActor)
: LODActor(InLODActor)
, ID(InLODActor)
{
Type = ITreeItem::HierarchicalLODActor;
}
bool HLODOutliner::FLODActorItem::CanInteract() const
{
return true;
}
void HLODOutliner::FLODActorItem::GenerateContextMenu(UToolMenu* Menu, SHLODOutliner& Outliner)
{
auto SharedOutliner = StaticCastSharedRef<SHLODOutliner>(Outliner.AsShared());
FToolMenuSection& Section = Menu->AddSection("Section");
Section.AddMenuEntry("SelectLODActor", LOCTEXT("SelectLODActor", "Select LOD Actor"), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(&Outliner, &SHLODOutliner::SelectLODActor)));
Section.AddMenuEntry("SelectContainedActors", LOCTEXT("SelectContainedActors", "Select Contained Actors"), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(&Outliner, &SHLODOutliner::SelectContainedActors)));
if (!LODActor->IsBuilt())
{
Section.AddMenuEntry("BuildLODActorMesh", LOCTEXT("BuildLODActorMesh", "Build Proxy Mesh"), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(&Outliner, &SHLODOutliner::BuildLODActor)));
}
else
{
Section.AddMenuEntry("ForceView", LOCTEXT("ForceView", "ForceView"), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(&Outliner, &SHLODOutliner::ForceViewLODActor)));
Section.AddMenuEntry("RebuildLODActorMesh", LOCTEXT("RebuildLODActorMesh", "Rebuild Proxy Mesh"), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(&Outliner, &SHLODOutliner::RebuildLODActor)));
}
Section.AddMenuEntry("CreateHLODVolume", LOCTEXT("CreateHLODVolume", "Create Containing Hierarchical Volume"), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(&Outliner, &SHLODOutliner::CreateHierarchicalVolumeForActor)));
AActor* Actor = LODActor.Get();
FHierarchicalLODUtilitiesModule& Module = FModuleManager::LoadModuleChecked<FHierarchicalLODUtilitiesModule>("HierarchicalLODUtilities");
IHierarchicalLODUtilities* Utilities = Module.GetUtilities();
ALODActor* ParentActor = Utilities->GetParentLODActor(Actor);
if (ParentActor && Parent.Pin()->GetTreeItemType() == TreeItemType::HierarchicalLODActor)
{
Section.AddMenuEntry("RemoveChildFromCluster", LOCTEXT("RemoveChildFromCluster", "Remove from cluster"), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(&Outliner, &SHLODOutliner::RemoveLODActorFromCluster)));
}
else
{
Section.AddMenuEntry("DeleteCluster", LOCTEXT("DeleteCluster", "Delete Cluster"), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(&Outliner, &SHLODOutliner::DeleteCluster)));
}
}
FString HLODOutliner::FLODActorItem::GetDisplayString() const
{
if (ALODActor* ActorPtr = LODActor.Get())
{
FString DisplayName = ActorPtr->GetName() + (!ActorPtr->HasValidSubActors() ? FString(" (Not enough mesh components)") : ((!ActorPtr->IsBuilt()) ? FString(" (Not built)") : FString()));
// Temporary indication of custom settings
if (ActorPtr->bOverrideMaterialMergeSettings || ActorPtr->bOverrideScreenSize || ActorPtr->bOverrideTransitionScreenSize)
{
DisplayName += " (Using Cluster-based settings)";
}
return DisplayName;
}
return FString();
}
HLODOutliner::FTreeItemID HLODOutliner::FLODActorItem::GetID()
{
return ID;
}
FSlateColor HLODOutliner::FLODActorItem::GetTint() const
{
ALODActor* LODActorPtr = LODActor.Get();
if (LODActorPtr)
{
return !LODActorPtr->IsBuilt() ? FSlateColor::UseSubduedForeground() : FLinearColor(1.0f, 1.0f, 1.0f);
}
return FLinearColor(1.0f, 1.0f, 1.0f);
}
FText HLODOutliner::FLODActorItem::GetRawNumTrianglesAsText() const
{
if (LODActor.IsValid())
{
const uint32 SubActorsTriangleCount = LODActor->GetNumTrianglesInSubActors();
return FText::FromString(FString::FromInt(SubActorsTriangleCount));
}
else
{
return FText::FromString(TEXT("Not available"));
}
}
FText HLODOutliner::FLODActorItem::GetReducedNumTrianglesAsText() const
{
if (LODActor.IsValid())
{
const uint32 MergedCount = LODActor->GetNumTrianglesInMergedMesh();
return FText::FromString(FString::FromInt(MergedCount));
}
else
{
return FText::FromString(TEXT("Not available"));
}
}
FText HLODOutliner::FLODActorItem::GetReductionPercentageAsText() const
{
if (LODActor.IsValid())
{
const uint32 SubActorCount = LODActor->GetNumTrianglesInSubActors();
const uint32 MergedCount = LODActor->GetNumTrianglesInMergedMesh();
const float PercentageOfOriginal = ((float)MergedCount / (float)SubActorCount) * 100.0f;
return FText::FromString("%" + ((SubActorCount != 0) ? FString::SanitizeFloat(PercentageOfOriginal) : TEXT("0")));
}
else
{
return FText::FromString(TEXT("Not available"));
}
}
FText HLODOutliner::FLODActorItem::GetLevelAsText() const
{
if (LODActor.IsValid())
{
return FText::FromString(LODActor->SubActors.Num() ? LODActor->SubActors[0]->GetLevel()->GetOuter()->GetName() : TEXT(""));
}
else
{
return FText::FromString(TEXT("Not available"));
}
}
void HLODOutliner::FLODActorItem::PopulateDragDropPayload(FDragDropPayload& Payload) const
{
ALODActor* ActorPtr = LODActor.Get();
if (ActorPtr)
{
if (!Payload.LODActors)
{
Payload.LODActors = TArray<TWeakObjectPtr<AActor>>();
}
Payload.LODActors->Add(LODActor);
}
}
HLODOutliner::FDragValidationInfo HLODOutliner::FLODActorItem::ValidateDrop(FDragDropPayload& DraggedObjects) const
{
if (Parent.IsValid())
{
if (Parent.Pin()->GetTreeItemType() == ITreeItem::HierarchicalLODActor)
{
return FDragValidationInfo(EHierarchicalLODActionType::InvalidAction, FHLODOutlinerDragDropOp::ToolTip_Incompatible, LOCTEXT("CannotAddToChildCluster", "Cannot Add to Child cluster"));
}
}
FLODActorDropTarget Target(LODActor.Get());
return Target.ValidateDrop(DraggedObjects);
}
void HLODOutliner::FLODActorItem::OnDrop(FDragDropPayload& DraggedObjects, const FDragValidationInfo& ValidationInfo, TSharedRef<SWidget> DroppedOnWidget)
{
FLODActorDropTarget Target(LODActor.Get());
Target.OnDrop(DraggedObjects, ValidationInfo, DroppedOnWidget);
// Expand this HLOD actor item
bIsExpanded = true;
}
HLODOutliner::FDragValidationInfo HLODOutliner::FLODActorDropTarget::ValidateDrop(FDragDropPayload& DraggedObjects) const
{
if (DraggedObjects.StaticMeshActors.IsSet() && DraggedObjects.StaticMeshActors->Num() > 0)
{
if (DraggedObjects.LODActors->Num() == 0)
{
if (DraggedObjects.bSceneOutliner == false)
{
bool bContaining = false;
// Check if this StaticMesh Actor is not already inside this cluster
for (int32 ActorIndex = 0; ActorIndex < DraggedObjects.StaticMeshActors->Num(); ++ActorIndex)
{
if (LODActor->SubActors.Contains(DraggedObjects.StaticMeshActors.GetValue()[ActorIndex]))
{
bContaining = true;
break;
}
}
if (!bContaining)
{
if (DraggedObjects.StaticMeshActors->Num() > 1)
{
return FDragValidationInfo(EHierarchicalLODActionType::MoveActorToCluster, FHLODOutlinerDragDropOp::ToolTip_MultipleSelection_Compatible, LOCTEXT("MoveMultipleToCluster", "Move Actors to Cluster"));
}
else
{
return FDragValidationInfo(EHierarchicalLODActionType::MoveActorToCluster, FHLODOutlinerDragDropOp::ToolTip_Compatible, LOCTEXT("MoveToCluster", "Move Actor to Cluster"));
}
}
else
{
return FDragValidationInfo(EHierarchicalLODActionType::InvalidAction, FHLODOutlinerDragDropOp::ToolTip_Incompatible, LOCTEXT("AlreadyInCluster", "Cannot Add to Existing cluster"));
}
}
else
{
TArray<AActor*> Actors;
for (auto StaticMeshActor : DraggedObjects.StaticMeshActors.GetValue())
{
Actors.Add(StaticMeshActor.Get());
}
Actors.Add(LODActor.Get());
const bool MultipleActors = DraggedObjects.StaticMeshActors->Num() > 1;
FHierarchicalLODUtilitiesModule& Module = FModuleManager::LoadModuleChecked<FHierarchicalLODUtilitiesModule>("HierarchicalLODUtilities");
IHierarchicalLODUtilities* Utilities = Module.GetUtilities();
if (Utilities->AreActorsInSamePersistingLevel(Actors))
{
return FDragValidationInfo(EHierarchicalLODActionType::AddActorToCluster,
MultipleActors ? FHLODOutlinerDragDropOp::ToolTip_MultipleSelection_Compatible : FHLODOutlinerDragDropOp::ToolTip_Compatible,
MultipleActors ? LOCTEXT("AddMultipleToCluster", "Add Actors to Cluster") : LOCTEXT("AddToCluster", "Add Actor to Cluster"));
}
else
{
return FDragValidationInfo(EHierarchicalLODActionType::InvalidAction,
MultipleActors ? FHLODOutlinerDragDropOp::ToolTip_MultipleSelection_Incompatible : FHLODOutlinerDragDropOp::ToolTip_Incompatible,
LOCTEXT("NotInSameLODLevel", "Actors are not all in the same persisting level"));
}
}
}
if (DraggedObjects.bSceneOutliner)
{
return FDragValidationInfo(EHierarchicalLODActionType::InvalidAction, FHLODOutlinerDragDropOp::ToolTip_Incompatible, LOCTEXT("AlreadyInHLOD", "Actor is already in one of the Hierarchical LOD clusters"));
}
}
else if (DraggedObjects.LODActors.IsSet() && DraggedObjects.LODActors->Num() > 0)
{
// Only valid if dragging inside the HLOD outliner
if (!DraggedObjects.bSceneOutliner)
{
bool bValidForMerge = true;
bool bValidForChilding = true;
int32 FirstLODLevel = -1;
UObject* LevelOuter = nullptr;
UObject* SubActorOuter = (LODActor->SubActors.Num()) ? LODActor->SubActors[0]->GetOuter() : nullptr;
for (auto Actor : DraggedObjects.LODActors.GetValue())
{
ALODActor* InLODActor = Cast<ALODActor>(Actor.Get());
if (InLODActor)
{
// If dragged onto self or already containing LODActor early out
if (InLODActor == LODActor.Get() || LODActor->SubActors.Contains(InLODActor))
{
bValidForMerge = false;
bValidForChilding = false;
break;
}
// Check in case of multiple selected LODActor items to make sure all of them come from the same LOD level
if (FirstLODLevel == -1)
{
FirstLODLevel = InLODActor->LODLevel;
}
if (InLODActor->LODLevel != LODActor->LODLevel)
{
bValidForMerge = false;
if (InLODActor->LODLevel != FirstLODLevel)
{
bValidForChilding = false;
}
}
// Check if in same level asset
if (LevelOuter == nullptr)
{
LevelOuter = InLODActor->GetOuter();
}
else if (LevelOuter != InLODActor->GetOuter())
{
bValidForMerge = false;
bValidForChilding = false;
}
if (InLODActor->SubActors.Num() && SubActorOuter != InLODActor->SubActors[0]->GetOuter())
{
bValidForChilding = false;
bValidForMerge = false;
}
}
}
if (bValidForMerge)
{
return FDragValidationInfo(EHierarchicalLODActionType::MergeClusters, (DraggedObjects.LODActors->Num() == 1) ? FHLODOutlinerDragDropOp::ToolTip_Compatible : FHLODOutlinerDragDropOp::ToolTip_MultipleSelection_Compatible, LOCTEXT("MergeHLODClusters", "Merge Cluster(s)"));
}
else if (bValidForChilding)
{
return FDragValidationInfo(EHierarchicalLODActionType::ChildCluster, (DraggedObjects.LODActors->Num() == 1) ? FHLODOutlinerDragDropOp::ToolTip_Compatible : FHLODOutlinerDragDropOp::ToolTip_MultipleSelection_Compatible, LOCTEXT("ChildHLODClusters", "Add Child Cluster(s)"));
}
else
{
return FDragValidationInfo(EHierarchicalLODActionType::InvalidAction, FHLODOutlinerDragDropOp::ToolTip_Incompatible, LOCTEXT("InvalidOperation", "Invalid Operation"));
}
}
}
return FDragValidationInfo(EHierarchicalLODActionType::InvalidAction, FHLODOutlinerDragDropOp::ToolTip_Incompatible, LOCTEXT("NotImplemented", "Not implemented"));
}
void HLODOutliner::FLODActorDropTarget::OnDrop(FDragDropPayload& DraggedObjects, const FDragValidationInfo& ValidationInfo, TSharedRef<SWidget> DroppedOnWidget)
{
AActor* DropActor = LODActor.Get();
if (!DropActor)
{
return;
}
FHierarchicalLODUtilitiesModule& Module = FModuleManager::LoadModuleChecked<FHierarchicalLODUtilitiesModule>("HierarchicalLODUtilities");
IHierarchicalLODUtilities* Utilities = Module.GetUtilities();
auto& DraggedStaticMeshActors = DraggedObjects.StaticMeshActors.GetValue();
auto& DraggedLODActors = DraggedObjects.LODActors.GetValue();
if (ValidationInfo.ActionType == EHierarchicalLODActionType::AddActorToCluster || ValidationInfo.ActionType == EHierarchicalLODActionType::MoveActorToCluster)
{
for (int32 ActorIndex = 0; ActorIndex < DraggedStaticMeshActors.Num(); ++ActorIndex)
{
auto Actor = DraggedStaticMeshActors[ActorIndex];
Utilities->AddActorToCluster(Actor.Get(), LODActor.Get());
}
}
else if (ValidationInfo.ActionType == EHierarchicalLODActionType::MergeClusters)
{
for (int32 ActorIndex = 0; ActorIndex < DraggedLODActors.Num(); ++ActorIndex)
{
ALODActor* InLODActor = Cast<ALODActor>(DraggedLODActors[ActorIndex].Get());
Utilities->MergeClusters(LODActor.Get(), InLODActor);
}
}
else if (ValidationInfo.ActionType == EHierarchicalLODActionType::ChildCluster)
{
for (int32 ActorIndex = 0; ActorIndex < DraggedLODActors.Num(); ++ActorIndex)
{
auto Actor = DraggedLODActors[ActorIndex];
Utilities->AddActorToCluster(Actor.Get(), LODActor.Get());
}
}
}
#undef LOCTEXT_NAMESPACE