Files
UnrealEngine/Engine/Plugins/Experimental/MeshModelingToolsetExp/Source/MeshModelingToolsEditorOnlyExp/Private/HarvestInstancesTool.cpp
2025-05-18 13:04:45 +08:00

504 lines
15 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "HarvestInstancesTool.h"
#include "InteractiveToolManager.h"
#include "ToolTargetManager.h"
#include "TargetInterfaces/PrimitiveComponentBackedTarget.h"
#include "TargetInterfaces/AssetBackedTarget.h"
#include "ToolBuilderUtil.h"
#include "ToolSetupUtil.h"
#include "ModelingToolTargetUtil.h"
#include "Selection/ToolSelectionUtil.h"
#include "ModelingObjectsCreationAPI.h"
#include "ToolDataVisualizer.h"
#include "Util/ColorConstants.h"
#include "Components/PrimitiveComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Components/InstancedStaticMeshComponent.h"
#include "Components/HierarchicalInstancedStaticMeshComponent.h"
#include "Engine/StaticMesh.h"
#include "Engine/World.h"
#include "Editor/EditorEngine.h" // for FActorLabelUtilities
using namespace UE::Geometry;
#define LOCTEXT_NAMESPACE "UHarvestInstancesTool"
/*
* ToolBuilder
*/
bool UHarvestInstancesToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const
{
int32 ValidTargets = 0;
// only static mesh components currently supported
SceneState.TargetManager->EnumerateSelectedAndTargetableComponents(SceneState, GetTargetRequirements(), [&ValidTargets](UActorComponent* Component)
{
if (Cast<UStaticMeshComponent>(Component))
{
ValidTargets++;
}
});
return ValidTargets > 0;
}
UMultiSelectionMeshEditingTool* UHarvestInstancesToolBuilder::CreateNewTool(const FToolBuilderState& SceneState) const
{
UHarvestInstancesTool* NewTool = NewObject<UHarvestInstancesTool>(SceneState.ToolManager);
return NewTool;
}
void UHarvestInstancesToolBuilder::InitializeNewTool(UMultiSelectionMeshEditingTool* NewTool, const FToolBuilderState& SceneState) const
{
TArray<TObjectPtr<UToolTarget>> Targets;
SceneState.TargetManager->EnumerateSelectedAndTargetableComponents(SceneState, GetTargetRequirements(), [this, &Targets, &SceneState](UActorComponent* Component)
{
if (Cast<UStaticMeshComponent>(Component))
{
Targets.Add(SceneState.TargetManager->BuildTarget(Component, GetTargetRequirements()));
}
});
NewTool->SetTargets(Targets);
NewTool->SetWorld(SceneState.World);
}
const FToolTargetTypeRequirements& UHarvestInstancesToolBuilder::GetTargetRequirements() const
{
static FToolTargetTypeRequirements TypeRequirements({
UPrimitiveComponentBackedTarget::StaticClass(),
UAssetBackedTarget::StaticClass()
});
return TypeRequirements;
}
/*
*
*/
UHarvestInstancesTool::UHarvestInstancesTool()
{
}
void UHarvestInstancesTool::Setup()
{
UInteractiveTool::Setup();
OutputSettings = NewObject<UHarvestInstancesTool_OutputSettings>();
AddToolPropertySource(OutputSettings);
OutputSettings->RestoreProperties(this);
OutputSettings->WatchProperty(OutputSettings->bIncludeSingleInstances, [this](bool bNewValue) { UpdateInstanceSets(); });
Settings = NewObject<UHarvestInstancesToolSettings>();
AddToolPropertySource(Settings);
Settings->RestoreProperties(this);
InitializeInstanceSets();
UpdateInstanceSets();
SetToolDisplayName(LOCTEXT("ToolName", "Harvest Instances"));
GetToolManager()->DisplayMessage(
LOCTEXT("OnStartHarvestInstancesTool", "Harvest repeated StaticMesh Components from selected Actors into new InstancedStaticMeshComponents"),
EToolMessageLevel::UserNotification);
UpdateWarningMessages();
}
void UHarvestInstancesTool::OnShutdown(EToolShutdownType ShutdownType)
{
Settings->SaveProperties(this);
OutputSettings->SaveProperties(this);
if (ShutdownType == EToolShutdownType::Accept)
{
EmitResults();
}
}
void UHarvestInstancesTool::Render(IToolsContextRenderAPI* RenderAPI)
{
FToolDataVisualizer LineRenderer;
LineRenderer.BeginFrame(RenderAPI);
if (Settings->bDrawBounds)
{
int Counter = 0;
for (const FInstanceSet& InstanceSet : InstanceSets)
{
FColor SetColor = LinearColors::SelectFColor(Counter++);
LineRenderer.SetLineParameters(FLinearColor(0.95, 0.05, 0.05), 4.0);
FBox Bounds = InstanceSet.Bounds;
Bounds.Min -= 5.0 * FVector::One();
Bounds.Max += 5.0 * FVector::One();
for (const FTransform& Transform : InstanceSet.WorldInstanceTransforms)
{
LineRenderer.PushTransform(Transform);
LineRenderer.DrawWireBox(Bounds, SetColor, 2.0, true);
LineRenderer.PopTransform();
}
}
}
LineRenderer.EndFrame();
}
void UHarvestInstancesTool::InitializeInstanceSets()
{
// collect up source meshes
this->SourceMeshes.Reset();
int32 NumTargets = Targets.Num();
for (int32 TargetIdx = 0; TargetIdx < NumTargets; TargetIdx++)
{
UPrimitiveComponent* TargetComponent = UE::ToolTarget::GetTargetComponent(Targets[TargetIdx]);
if ( UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(TargetComponent) )
{
UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
if (!StaticMesh) continue;
TArray<UMaterialInterface*> InstanceMaterials = UE::ToolTarget::GetMaterialSet(Targets[TargetIdx], false).Materials;
FSourceMesh* UseSourceMesh = nullptr;
for (int32 k = 0; k < SourceMeshes.Num() && UseSourceMesh == nullptr; ++k)
{
if (SourceMeshes[k].Mesh == StaticMesh && SourceMeshes[k].InstanceMaterials == InstanceMaterials)
{
UseSourceMesh = &SourceMeshes[k];
}
}
if (UseSourceMesh == nullptr)
{
FSourceMesh NewSourceMesh;
NewSourceMesh.Mesh = StaticMesh;
NewSourceMesh.InstanceMaterials = InstanceMaterials;
SourceMeshes.Add(NewSourceMesh);
UseSourceMesh = &SourceMeshes.Last();
}
UseSourceMesh->SourceComponents.Add(StaticMeshComponent);
if (UInstancedStaticMeshComponent* ISMComponent = Cast<UInstancedStaticMeshComponent>(StaticMeshComponent))
{
int32 NumInstances = ISMComponent->GetInstanceCount();
for (int32 i = 0; i < NumInstances; ++i)
{
if (ISMComponent->IsValidInstance(i))
{
FTransform InstanceWorldTransform;
if (ensure(ISMComponent->GetInstanceTransform(i, InstanceWorldTransform, true)))
{
UseSourceMesh->InstanceTransformsWorld.Add(InstanceWorldTransform);
}
}
}
}
else
{
FTransform WorldTransform = UE::ToolTarget::GetLocalToWorldTransform(Targets[TargetIdx]);
UseSourceMesh->InstanceTransformsWorld.Add(WorldTransform);
}
}
}
// determine if we have 'loner' instances
for (int32 k = 0; k < SourceMeshes.Num(); ++k)
{
if (SourceMeshes[k].InstanceTransformsWorld.Num() == 1)
{
OutputSettings->bHaveLonerInstances = true;
}
}
}
void UHarvestInstancesTool::UpdateInstanceSets()
{
TArray<FSourceMesh> FilteredSourceMeshes = SourceMeshes;
// remove any meshes that only have one instance, if desired
if (OutputSettings->bIncludeSingleInstances == false)
{
for (int32 k = 0; k < FilteredSourceMeshes.Num(); ++k)
{
if (FilteredSourceMeshes[k].InstanceTransformsWorld.Num() <= 1)
{
FilteredSourceMeshes.RemoveAt(k);
k--;
}
}
}
InstanceSets.Reset();
SourceActors.Reset();
SourceComponents.Reset();
int32 NumInstanceSets = FilteredSourceMeshes.Num();
OutputSettings->bHaveSingleInstanceSet = (NumInstanceSets == 1);
InstanceSets.SetNum(NumInstanceSets);
for (int32 Index = 0; Index < NumInstanceSets; Index++)
{
FSourceMesh& SourceMesh = FilteredSourceMeshes[Index];
FInstanceSet& Element = InstanceSets[Index];
Element.SourceComponents = SourceMesh.SourceComponents;
for (UStaticMeshComponent* Component : Element.SourceComponents)
{
Element.SourceActors.AddUnique(Component->GetOwner());
SourceActors.AddUnique(Component->GetOwner());
SourceComponents.Add(Component);
}
Element.StaticMesh = SourceMesh.Mesh;
Element.Bounds = SourceMesh.Mesh->GetBoundingBox();
Element.InstanceMaterials = SourceMesh.InstanceMaterials;
Element.WorldInstanceTransforms = SourceMesh.InstanceTransformsWorld;
// do this?
//bHaveNonUniformScaleElements = bHaveNonUniformScaleElements || Element.BaseRotateScale.HasNonUniformScale();
}
// Check that all SceneComponents of Source Actors are included in instance set.
// If not, we cannot delete the Source Actors
bool bFoundUnhandledComponent = false;
for (AActor* SourceActor : SourceActors)
{
SourceActor->ForEachComponent(true, [&](UActorComponent* Component)
{
if (bFoundUnhandledComponent) return;
// only going to consider SceneComponents
if (Cast<USceneComponent>(Component) == nullptr) return;
if (UPrimitiveComponent* PrimComponent = Cast<UPrimitiveComponent>(Component))
{
if (SourceComponents.Contains(PrimComponent))
{
return;
}
}
// for now skip editor-only Components as some editor systems (like navigation) apparently attach additional hidden Components to static meshes...
if (Component->IsEditorOnly() == false)
{
bFoundUnhandledComponent = true;
}
});
}
OutputSettings->bCanDeleteInputs = (bFoundUnhandledComponent == false);
UpdateWarningMessages();
}
void UHarvestInstancesTool::UpdateWarningMessages()
{
FTextBuilder WarningTextBuilder;
if (OutputSettings->bCanDeleteInputs == false)
{
WarningTextBuilder.AppendLine(
LOCTEXT("CannotDeleteWarning", "Source Actors cannot be deleted because they contain additional Components that cannot be Instanced. "));
}
if (bHaveNonUniformScaleElements)
{
WarningTextBuilder.AppendLine(
LOCTEXT("NonUniformScaleWarning", "Source Objects have Non-Uniform Scaling, which may prevent Instance Transforms from working correctly."));
}
GetToolManager()->DisplayMessage(WarningTextBuilder.ToText(), EToolMessageLevel::UserWarning);
}
void UHarvestInstancesTool::OnPropertyModified(UObject* PropertySet, FProperty* Property)
{
if (PropertySet == Settings || PropertySet == OutputSettings)
{
return;
}
OnParametersUpdated();
}
void UHarvestInstancesTool::OnParametersUpdated()
{
//MarkPatternDirty();
}
void UHarvestInstancesTool::EmitResults()
{
GetToolManager()->BeginUndoTransaction(LOCTEXT("HarvestInstances", "Harvest Instances"));
bool bCreateHISMs = (OutputSettings->ComponentType == EHarvestInstancesToolOutputType::HISMC);
int32 NumInstanceSets = InstanceSets.Num();
TArray<AActor*> NewActors; // set of new actors created by operation
// TODO: investigate use of CopyPropertiesForUnrelatedObjects to transfer settings from source to target Components/Actors
auto ComputeComponentOriginFunc = [](FInstanceSet& Element) -> FVector
{
FVector ComponentPosition = FVector::Zero();
for (const FTransform& WorldTransform : Element.WorldInstanceTransforms)
{
ComponentPosition += WorldTransform.GetLocation();
}
ComponentPosition /= Element.WorldInstanceTransforms.Num();
return ComponentPosition;
};
auto CreateNewComponentFunc = [bCreateHISMs](FInstanceSet& Element, AActor* ParentActor, FString BaseName) -> UInstancedStaticMeshComponent*
{
TSubclassOf<UInstancedStaticMeshComponent> ComponentClass = (bCreateHISMs) ?
UHierarchicalInstancedStaticMeshComponent::StaticClass() : UInstancedStaticMeshComponent::StaticClass();
FString Suffix = (bCreateHISMs) ? TEXT("_HISM") : TEXT("_ISM");
FName UseName = (BaseName.Len() > 0) ? FName(BaseName + Suffix) : FName();
UInstancedStaticMeshComponent* ISMComponent = NewObject<UInstancedStaticMeshComponent>(ParentActor, ComponentClass,
MakeUniqueObjectName(ParentActor, ComponentClass, UseName));
ISMComponent->SetFlags(RF_Transactional);
ISMComponent->bHasPerInstanceHitProxies = true;
ISMComponent->SetStaticMesh(Element.StaticMesh);
for (int32 j = 0; j < Element.InstanceMaterials.Num(); ++j)
{
ISMComponent->SetMaterial(j, Element.InstanceMaterials[j]);
}
return ISMComponent;
};
AActor* SingleActor = nullptr;
if (OutputSettings->bSingleActor || NumInstanceSets == 1)
{
FActorSpawnParameters SpawnInfo;
SingleActor = GetTargetWorld()->SpawnActor<AActor>(SpawnInfo);
if ( SingleActor == nullptr )
{
return; // ??
}
FString UseBaseName = (OutputSettings->ActorName.Len() > 0) ? OutputSettings->ActorName : TEXT("Instances");
FActorLabelUtilities::SetActorLabelUnique(SingleActor, UseBaseName);
NewActors.Add(SingleActor);
bool bFirst = true;
for (int32 ElemIdx = 0; ElemIdx < NumInstanceSets; ++ElemIdx)
{
FInstanceSet& Element = InstanceSets[ElemIdx];
FString AssetBaseName = FPaths::GetBaseFilename(Element.StaticMesh->GetName());
AssetBaseName = UE::Modeling::StripGeneratedAssetSuffixFromName(AssetBaseName);
FVector ComponentPosition = ComputeComponentOriginFunc(Element);
UInstancedStaticMeshComponent* ISMComponent = CreateNewComponentFunc(Element, SingleActor, AssetBaseName);
if (bFirst)
{
SingleActor->SetRootComponent(ISMComponent);
ISMComponent->OnComponentCreated();
SingleActor->AddInstanceComponent(ISMComponent);
SingleActor->SetActorTransform(FTransform(ComponentPosition));
bFirst = false;
}
else
{
ISMComponent->OnComponentCreated();
ISMComponent->SetWorldTransform(FTransform(ComponentPosition));
ISMComponent->AttachToComponent(SingleActor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
SingleActor->AddInstanceComponent(ISMComponent);
}
for (FTransform WorldTransform : Element.WorldInstanceTransforms)
{
ISMComponent->AddInstance(WorldTransform, /*bTransformInWorldSpace=*/true);
}
ISMComponent->RegisterComponent();
}
}
else
{
for (int32 ElemIdx = 0; ElemIdx < NumInstanceSets; ++ElemIdx)
{
FInstanceSet& Element = InstanceSets[ElemIdx];
FVector ComponentPosition = ComputeComponentOriginFunc(Element);
FActorSpawnParameters SpawnInfo;
AActor* NewActor = GetTargetWorld()->SpawnActor<AActor>(SpawnInfo);
if (NewActor != nullptr)
{
FString AssetName = FPaths::GetBaseFilename(Element.StaticMesh->GetName());
FString UseBaseName = UE::Modeling::StripGeneratedAssetSuffixFromName(AssetName);
UseBaseName += TEXT("Instances");
FActorLabelUtilities::SetActorLabelUnique(NewActor, UseBaseName);
NewActors.Add(NewActor);
FString AssetBaseName = FPaths::GetBaseFilename(Element.StaticMesh->GetName());
AssetBaseName = UE::Modeling::StripGeneratedAssetSuffixFromName(AssetBaseName);
UInstancedStaticMeshComponent* ISMComponent = CreateNewComponentFunc(Element, NewActor, AssetBaseName);
NewActor->SetRootComponent(ISMComponent);
ISMComponent->OnComponentCreated();
NewActor->AddInstanceComponent(ISMComponent);
NewActor->SetActorTransform(FTransform(ComponentPosition));
for (FTransform WorldTransform : Element.WorldInstanceTransforms)
{
ISMComponent->AddInstance(WorldTransform, /*bTransformInWorldSpace=*/true);
}
ISMComponent->RegisterComponent();
}
}
}
// delete all source actors if desired
if (OutputSettings->bCanDeleteInputs && OutputSettings->bDeleteInputs)
{
// destroy any Actors that won't have any Components left
for (AActor* Actor : SourceActors)
{
Actor->Modify();
Actor->Destroy();
}
}
if (NewActors.Num() > 0)
{
ToolSelectionUtil::SetNewActorSelection(GetToolManager(), NewActors);
}
GetToolManager()->EndUndoTransaction();
}
#undef LOCTEXT_NAMESPACE