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

429 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "UVTransferTool.h"
#include "Drawing/MeshElementsVisualizer.h"
#include "InteractiveToolManager.h"
#include "MeshOpPreviewHelpers.h"
#include "ModelingOperators.h" // FDynamicMeshOperator
#include "ModelingToolTargetUtil.h"
#include "Parameterization/UVTransfer.h"
#include "Properties/MeshMaterialProperties.h"
#include "Properties/MeshUVChannelProperties.h"
#include "Selections/GeometrySelectionUtil.h" // EnumerateSelectionTriangles
#include "TargetInterfaces/DynamicMeshCommitter.h"
#include "TargetInterfaces/DynamicMeshProvider.h"
#include "ToolContextInterfaces.h" // FToolBuilderState
#include "ToolSetupUtil.h"
#include "ToolTargetManager.h"
#define LOCTEXT_NAMESPACE "UUVTransferTool"
namespace UVTransferToolLocals
{
using namespace UE::Geometry;
FString CacheIdentifier = TEXT("UVTransferTool");
class FTransferUVsOp : public FDynamicMeshOperator
{
public:
virtual ~FTransferUVsOp() {}
// inputs
TSharedPtr<const FDynamicMesh3> SourceMesh;
TSharedPtr<const FDynamicMesh3> DestinationMesh;
TOptional<TSet<int32>> SourceSelectionTids;
TOptional<TSet<int32>> DestinationSelectionTids;
int UVLayerIndex = 0;
bool bTransferSeamsOnly = false;
bool bClearExistingSeams = true;
double VertexSearchDistance = KINDA_SMALL_NUMBER;
double VertexSearchCellSize = VertexSearchDistance * 3.0;
double PathSimilarityWeight = 200.0;
void SetTransform(const FTransformSRT3d& Transform)
{
ResultTransform = Transform;
}
virtual void CalculateResult(FProgressCancel* Progress) override
{
if (Progress && Progress->Cancelled()) { return; }
if (!DestinationMesh || !SourceMesh
|| UVLayerIndex < 0 || UVLayerIndex > 7
|| !SourceMesh->HasAttributes()
|| SourceMesh->Attributes()->NumUVLayers() <= UVLayerIndex)
{
ensure(false);
return;
}
ResultMesh->Copy(*DestinationMesh);
if (Progress && Progress->Cancelled()) { return; }
UE::Geometry::FDynamicMeshUVTransfer Transferer(SourceMesh.Get(), ResultMesh.Get(), UVLayerIndex);
Transferer.VertexSearchDistance = VertexSearchDistance;
Transferer.VertexSearchCellSize = VertexSearchCellSize;
Transferer.PathSimilarityWeight = PathSimilarityWeight;
Transferer.bClearExistingSeamsInDestination = bClearExistingSeams;
// TODO SELECTIONS: Once we support multi-mesh selections, we would uncomment these
//Transferer.SourceSelectionTids = SourceSelectionTids.GetPtrOrNull();
//Transferer.DestinationSelectionTids = DestinationSelectionTids.GetPtrOrNull();
if (bTransferSeamsOnly)
{
bTransferSucceeded = Transferer.TransferSeams(Progress);
}
else
{
bTransferSucceeded = Transferer.TransferSeamsAndUVs(Progress);
}
}
bool bTransferSucceeded = false;
private:
};
}
void UUVTransferTool::OnShutdown(EToolShutdownType ShutdownType)
{
using namespace UVTransferToolLocals;
Settings->SaveProperties(this);
DestinationMaterialSettings->SaveProperties(this, CacheIdentifier);
UVChannelProperties->SaveProperties(this);
SourceSeamVisualizer->Disconnect();
DestinationSeamVisualizer->Disconnect();
for (TObjectPtr<UToolTarget> Target : Targets)
{
UE::ToolTarget::ShowSourceObject(Target);
}
FDynamicMeshOpResult Result = DestinationPreview->Shutdown();
if (ShutdownType == EToolShutdownType::Accept)
{
GenerateAsset(Result);
}
SourcePreview->Disconnect();
Settings = nullptr;
UVChannelProperties = nullptr;
DestinationMaterialSettings = nullptr;
DestinationPreview = nullptr;
SourcePreview = nullptr;
SourceSeamVisualizer = nullptr;
DestinationSeamVisualizer = nullptr;
for (int i = 0; i < 2; ++i)
{
Meshes[i].Reset();
SelectionTidSets[i].Reset();
}
}
void UUVTransferTool::Setup()
{
using namespace UVTransferToolLocals;
UInteractiveTool::Setup();
// Set up our property sets
Settings = NewObject<UUVTransferToolProperties>(this);
Settings->RestoreProperties(this);
AddToolPropertySource(Settings);
DestinationMaterialSettings = NewObject<UExistingMeshMaterialProperties>(this);
DestinationMaterialSettings->RestoreProperties(this, CacheIdentifier);
AddToolPropertySource(DestinationMaterialSettings);
UVChannelProperties = NewObject<UMeshUVChannelProperties>(this);
UVChannelProperties->RestoreProperties(this);
AddToolPropertySource(UVChannelProperties);
UVChannelProperties->ValidateSelection(true);
// Hide inputs
for (TObjectPtr<UToolTarget> Target : Targets)
{
UE::ToolTarget::HideSourceObject(Target);
}
// Extract the input meshes and selections
for (int i = 0; i < 2; ++i)
{
Meshes[i] = MakeShared<UE::Geometry::FDynamicMesh3>(UE::ToolTarget::GetDynamicMeshCopy(Targets[i]));
if (HasGeometrySelection(i))
{
SelectionTidSets[i].Emplace();
EnumerateSelectionTriangles(GetGeometrySelection(0), *Meshes[i], [this, i](int32 Tid)
{
SelectionTidSets[i]->Add(Tid);
});
}
}
// Set up previews
DestinationPreview = NewObject<UMeshOpPreviewWithBackgroundCompute>(this);
DestinationPreview->Setup(GetTargetWorld(), this);
DestinationPreview->OnOpCompleted.AddWeakLambda(this, [this](const UE::Geometry::FDynamicMeshOperator* UncastOp)
{
const FTransferUVsOp* Op = static_cast<const FTransferUVsOp*>(UncastOp);
if (!Op->bTransferSucceeded)
{
GetToolManager()->DisplayMessage(LOCTEXT("TransferUnsuccessful",
"Transfer was not fully successful, possibly because correspondence couldn't be found. The "
"source mesh is expected to be a version of the destination mesh simplified via \"Existing Positions\" "
"option so that any UV layout done on the simplified mesh can be mapped back to the (original) destination "
"mesh via vertex positions."),
EToolMessageLevel::UserWarning);
}
else
{
GetToolManager()->DisplayMessage(FText(), EToolMessageLevel::UserWarning);
}
});
DestinationPreview->OnMeshUpdated.AddWeakLambda(this, [this](UMeshOpPreviewWithBackgroundCompute* Compute)
{
if (DestinationSeamVisualizer)
{
DestinationSeamVisualizer->NotifyMeshChanged();
}
});
SourcePreview = NewObject<UPreviewMesh>(this);
SourcePreview->CreateInWorld(GetTargetWorld(), FTransform::Identity);
ReinitializePreviews();
UVChannelProperties->WatchProperty(UVChannelProperties->UVChannel, [this](const FString& NewValue)
{
DestinationMaterialSettings->UpdateUVChannels(
UVChannelProperties->UVChannelNamesList.IndexOfByKey(UVChannelProperties->UVChannel),
UVChannelProperties->UVChannelNamesList);
InvalidatePreview();
});
SourceSeamVisualizer = NewObject<UMeshElementsVisualizer>(this);
SourceSeamVisualizer->CreateInWorld(GetWorld(), SourcePreview->GetTransform());
if (ensure(SourceSeamVisualizer->Settings))
{
SourceSeamVisualizer->Settings->ShowAllElements(false);
}
SourceSeamVisualizer->SetMeshAccessFunction([this](UMeshElementsVisualizer::ProcessDynamicMeshFunc ProcessFunc) {
SourcePreview->ProcessMesh(ProcessFunc);
});
DestinationSeamVisualizer = NewObject<UMeshElementsVisualizer>(this);
DestinationSeamVisualizer->CreateInWorld(GetWorld(), DestinationPreview->PreviewMesh->GetTransform());
if (ensure(DestinationSeamVisualizer->Settings))
{
DestinationSeamVisualizer->Settings->ShowAllElements(false);
}
DestinationSeamVisualizer->SetMeshAccessFunction([this](UMeshElementsVisualizer::ProcessDynamicMeshFunc ProcessFunc) {
DestinationPreview->ProcessCurrentMesh(ProcessFunc);
});
UpdateVisualizations();
Settings->WatchProperty(Settings->bReverseDirection, [this](bool)
{
ReinitializePreviews();
InvalidatePreview();
});
// TODO SELECTIONS: Once we support multi-mesh selections, we would add this bool to the settings
//Settings->WatchProperty(Settings->bRespectSelection, [this](bool) { InvalidatePreview(); });
Settings->WatchProperty(Settings->VertexSearchDistance, [this](double) { InvalidatePreview(); });
Settings->WatchProperty(Settings->PathSimilarityWeight, [this](double) { InvalidatePreview(); });
Settings->WatchProperty(Settings->bTransferSeamsOnly, [this](bool) { InvalidatePreview(); });
Settings->WatchProperty(Settings->bClearExistingSeams, [this](bool) { InvalidatePreview(); });
Settings->WatchProperty(Settings->bShowWireframes, [this](bool) { UpdateVisualizations(); });
Settings->WatchProperty(Settings->bShowSeams, [this](bool) { UpdateVisualizations(); });
Settings->SilentUpdateWatched();
UVChannelProperties->SilentUpdateWatched();
DestinationMaterialSettings->SilentUpdateWatched();
SetToolDisplayName(LOCTEXT("ToolName", "UV Transfer"));
GetToolManager()->DisplayMessage(LOCTEXT("OnStart", "Transfer UVs from a low-res mesh to a high-res mesh."),
EToolMessageLevel::UserNotification);
InvalidatePreview();
}
void UUVTransferTool::ReinitializePreviews()
{
int SourceIndex = Settings->bReverseDirection ? 1 : 0;
int DestinationIndex = Settings->bReverseDirection ? 0 : 1;
ToolSetupUtil::ApplyRenderingConfigurationToPreview(DestinationPreview->PreviewMesh, Targets[DestinationIndex]);
DestinationPreview->ConfigureMaterials(UE::ToolTarget::GetMaterialSet(Targets[DestinationIndex]).Materials,
ToolSetupUtil::GetDefaultWorkingMaterial(GetToolManager())
);
DestinationPreview->OverrideMaterial = DestinationMaterialSettings->GetActiveOverrideMaterial();
DestinationPreview->PreviewMesh->UpdatePreview(Meshes[DestinationIndex].Get());
DestinationPreview->PreviewMesh->SetTransform((FTransform)UE::ToolTarget::GetLocalToWorldTransform(Targets[DestinationIndex]));
ToolSetupUtil::ApplyRenderingConfigurationToPreview(SourcePreview, Targets[SourceIndex]);
SourcePreview->SetMaterials(UE::ToolTarget::GetMaterialSet(Targets[SourceIndex]).Materials);
SourcePreview->UpdatePreview(Meshes[SourceIndex].Get());
SourcePreview->SetTransform((FTransform)UE::ToolTarget::GetLocalToWorldTransform(Targets[SourceIndex]));
UVChannelProperties->Initialize(FMath::Min(
Meshes[SourceIndex] && Meshes[SourceIndex]->HasAttributes() ? Meshes[SourceIndex]->Attributes()->NumUVLayers() : 0,
Meshes[DestinationIndex] && Meshes[DestinationIndex]->HasAttributes() ? Meshes[DestinationIndex]->Attributes()->NumUVLayers() : 0), true);
UVChannelProperties->ValidateSelection(true);
DestinationMaterialSettings->UpdateUVChannels(
UVChannelProperties->GetSelectedChannelIndex(),
UVChannelProperties->UVChannelNamesList);
DestinationMaterialSettings->UpdateMaterials();
DestinationPreview->OverrideMaterial = DestinationMaterialSettings->GetActiveOverrideMaterial();
if (SourceSeamVisualizer)
{
SourceSeamVisualizer->SetTransform(SourcePreview->GetTransform());
SourceSeamVisualizer->NotifyMeshChanged();
}
if (DestinationSeamVisualizer)
{
DestinationSeamVisualizer->SetTransform(DestinationPreview->PreviewMesh->GetTransform());
DestinationSeamVisualizer->NotifyMeshChanged();
}
}
void UUVTransferTool::UpdateVisualizations()
{
// The visualizers expect that we add their settings objects to our property sets, which
// we don't want to do, so we need to do their updates ourselves.
if (ensure(SourceSeamVisualizer->Settings))
{
SourceSeamVisualizer->Settings->bShowWireframe = Settings->bShowWireframes;
SourceSeamVisualizer->Settings->bShowUVSeams = Settings->bShowSeams;
SourceSeamVisualizer->Settings->CheckAndUpdateWatched();
}
if (ensure(DestinationSeamVisualizer->Settings))
{
DestinationSeamVisualizer->Settings->bShowWireframe = Settings->bShowWireframes;
DestinationSeamVisualizer->Settings->bShowUVSeams = Settings->bShowSeams;
DestinationSeamVisualizer->Settings->CheckAndUpdateWatched();
}
}
void UUVTransferTool::OnTick(float DeltaTime)
{
if (DestinationPreview)
{
DestinationPreview->Tick(DeltaTime);
}
if (DestinationSeamVisualizer)
{
DestinationSeamVisualizer->OnTick(DeltaTime);
}
if (SourceSeamVisualizer)
{
SourceSeamVisualizer->OnTick(DeltaTime);
}
}
void UUVTransferTool::Render(IToolsContextRenderAPI* RenderAPI)
{
}
bool UUVTransferTool::CanAccept() const
{
return DestinationPreview && DestinationPreview->HaveValidResult();
}
void UUVTransferTool::OnPropertyModified(UObject* PropertySet, FProperty* Property)
{
if (PropertySet == DestinationMaterialSettings)
{
DestinationMaterialSettings->UpdateMaterials();
DestinationPreview->OverrideMaterial = DestinationMaterialSettings->GetActiveOverrideMaterial();
}
}
TUniquePtr<UE::Geometry::FDynamicMeshOperator> UUVTransferTool::MakeNewOperator()
{
using namespace UVTransferToolLocals;
TUniquePtr<FTransferUVsOp> Op = MakeUnique<FTransferUVsOp>();
int SourceIndex = Settings->bReverseDirection ? 1 : 0;
int DestinationIndex = Settings->bReverseDirection ? 0 : 1;
Op->SourceMesh = Meshes[SourceIndex];
Op->DestinationMesh = Meshes[DestinationIndex];
Op->SetTransform(UE::ToolTarget::GetLocalToWorldTransform(Targets[DestinationIndex]));
// TODO SELECTIONS: Once we support multi-mesh selections, we would uncomment these
//if (Settings->bRespectSelection)
//{
// Op->SourceSelectionTids = SelectionTidSets[SourceIndex];
// Op->DestinationSelectionTids = SelectionTidSets[DestinationIndex];
//}
Op->UVLayerIndex = UVChannelProperties->GetSelectedChannelIndex(true);
Op->bTransferSeamsOnly = Settings->bTransferSeamsOnly;
Op->bClearExistingSeams = Settings->bClearExistingSeams;
Op->VertexSearchDistance = Settings->VertexSearchDistance;
Op->VertexSearchCellSize = Settings->VertexSearchDistance;
Op->PathSimilarityWeight = Settings->PathSimilarityWeight;
return Op;
}
void UUVTransferTool::InvalidatePreview()
{
if (DestinationPreview)
{
DestinationPreview->InvalidateResult();
}
}
void UUVTransferTool::GenerateAsset(const FDynamicMeshOpResult& Result)
{
GetToolManager()->BeginUndoTransaction(LOCTEXT("UVLayoutToolTransactionName", "UV Layout Tool"));
UE::ToolTarget::CommitDynamicMeshUVUpdate(
Targets[Settings->bReverseDirection ? 0 : 1],
Result.Mesh.Get());
GetToolManager()->EndUndoTransaction();
}
// Builder:
UMultiTargetWithSelectionTool* UUVTransferToolBuilder::CreateNewTool(const FToolBuilderState& SceneState) const
{
return NewObject<UUVTransferTool>(SceneState.ToolManager);
}
bool UUVTransferToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const
{
const int32 NumTargets = SceneState.TargetManager->CountSelectedAndTargetable(SceneState, GetTargetRequirements());
return NumTargets == 2;
}
const FToolTargetTypeRequirements& UUVTransferToolBuilder::GetTargetRequirements() const
{
static FToolTargetTypeRequirements TypeRequirements({
UDynamicMeshProvider::StaticClass(),
UDynamicMeshCommitter::StaticClass()
});
return TypeRequirements;
}
#undef LOCTEXT_NAMESPACE