You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#rb none #rnx #jira none #preflight 60d16a58367e6700015e9020 [CL 16744001 by Ryan Schmidt in ue5-main branch]
571 lines
19 KiB
C++
571 lines
19 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "MirrorTool.h"
|
|
|
|
#include "ModelingObjectsCreationAPI.h"
|
|
#include "BaseBehaviors/KeyAsModifierInputBehavior.h"
|
|
#include "BaseBehaviors/SingleClickBehavior.h"
|
|
#include "CompositionOps/MirrorOp.h"
|
|
#include "Drawing/MeshDebugDrawing.h"
|
|
#include "DynamicMeshToMeshDescription.h"
|
|
#include "InteractiveToolManager.h"
|
|
#include "MeshDescriptionToDynamicMesh.h"
|
|
#include "Misc/MessageDialog.h"
|
|
#include "ToolBuilderUtil.h"
|
|
#include "ToolSetupUtil.h"
|
|
|
|
#include "TargetInterfaces/MaterialProvider.h"
|
|
#include "TargetInterfaces/MeshDescriptionCommitter.h"
|
|
#include "TargetInterfaces/MeshDescriptionProvider.h"
|
|
#include "TargetInterfaces/PrimitiveComponentBackedTarget.h"
|
|
#include "ToolTargetManager.h"
|
|
|
|
#include "ExplicitUseGeometryMathTypes.h" // using UE::Geometry::(math types)
|
|
using namespace UE::Geometry;
|
|
|
|
#define LOCTEXT_NAMESPACE "UMirrorTool"
|
|
|
|
|
|
//Tool builder functions
|
|
|
|
const FToolTargetTypeRequirements& UMirrorToolBuilder::GetTargetRequirements() const
|
|
{
|
|
static FToolTargetTypeRequirements TypeRequirements({
|
|
UMaterialProvider::StaticClass(),
|
|
UMeshDescriptionCommitter::StaticClass(),
|
|
UMeshDescriptionProvider::StaticClass(),
|
|
UPrimitiveComponentBackedTarget::StaticClass()
|
|
});
|
|
return TypeRequirements;
|
|
}
|
|
|
|
bool UMirrorToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const
|
|
{
|
|
return SceneState.TargetManager->CountSelectedAndTargetable(SceneState, GetTargetRequirements()) > 0;
|
|
}
|
|
|
|
UInteractiveTool* UMirrorToolBuilder::BuildTool(const FToolBuilderState& SceneState) const
|
|
{
|
|
UMirrorTool* NewTool = NewObject<UMirrorTool>(SceneState.ToolManager);
|
|
|
|
TArray<TObjectPtr<UToolTarget>> Targets = SceneState.TargetManager->BuildAllSelectedTargetable(SceneState, GetTargetRequirements());
|
|
NewTool->SetTargets(MoveTemp(Targets));
|
|
NewTool->SetWorld(SceneState.World);
|
|
|
|
return NewTool;
|
|
}
|
|
|
|
|
|
// Operator factory
|
|
|
|
TUniquePtr<FDynamicMeshOperator> UMirrorOperatorFactory::MakeNewOperator()
|
|
{
|
|
TUniquePtr<FMirrorOp> MirrorOp = MakeUnique<FMirrorOp>();
|
|
|
|
// Set up inputs and settings
|
|
MirrorOp->OriginalMesh = MirrorTool->MeshesToMirror[ComponentIndex]->GetMesh();
|
|
MirrorOp->bAppendToOriginal = MirrorTool->Settings->OperationMode == EMirrorOperationMode::MirrorAndAppend;
|
|
MirrorOp->bCropFirst = MirrorTool->Settings->bCropAlongMirrorPlaneFirst;
|
|
MirrorOp->bWeldAlongPlane = MirrorTool->Settings->bWeldVerticesOnMirrorPlane;
|
|
MirrorOp->bAllowBowtieVertexCreation = MirrorTool->Settings->bAllowBowtieVertexCreation;
|
|
|
|
FTransform LocalToWorld = MirrorTool->TargetComponentInterface(ComponentIndex)->GetWorldTransform();
|
|
MirrorOp->SetTransform(LocalToWorld);
|
|
|
|
// We also need WorldToLocal. Threshold the LocalToWorld scaling transform so we can get the inverse.
|
|
FVector LocalToWorldScale = LocalToWorld.GetScale3D();
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
float DimScale = FMathf::Abs(LocalToWorldScale[i]);
|
|
float Tolerance = KINDA_SMALL_NUMBER;
|
|
if (DimScale < Tolerance)
|
|
{
|
|
LocalToWorldScale[i] = Tolerance * FMathf::SignNonZero(LocalToWorldScale[i]);
|
|
}
|
|
}
|
|
LocalToWorld.SetScale3D(LocalToWorldScale);
|
|
UE::Geometry::FTransform3d WorldToLocal = UE::Geometry::FTransform3d(LocalToWorld).Inverse();
|
|
|
|
// Now we can get the plane parameters in local space.
|
|
MirrorOp->LocalPlaneOrigin = WorldToLocal.TransformPosition(MirrorTool->MirrorPlaneOrigin);;
|
|
|
|
FVector3d WorldNormal = MirrorTool->MirrorPlaneNormal;
|
|
MirrorOp->LocalPlaneNormal = WorldToLocal.TransformNormal(MirrorTool->MirrorPlaneNormal);
|
|
|
|
return MirrorOp;
|
|
}
|
|
|
|
|
|
// Tool property functions
|
|
|
|
void UMirrorToolActionPropertySet::PostAction(EMirrorToolAction Action)
|
|
{
|
|
if (ParentTool.IsValid())
|
|
{
|
|
ParentTool->RequestAction(Action);
|
|
}
|
|
}
|
|
|
|
|
|
// Tool itself
|
|
UMirrorTool::UMirrorTool()
|
|
{
|
|
}
|
|
|
|
bool UMirrorTool::CanAccept() const
|
|
{
|
|
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
|
|
{
|
|
if (!Preview->HaveValidResult())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return Super::CanAccept();
|
|
}
|
|
|
|
void UMirrorTool::SetWorld(UWorld* World)
|
|
{
|
|
TargetWorld = World;
|
|
}
|
|
|
|
void UMirrorTool::OnPropertyModified(UObject* PropertySet, FProperty* Property)
|
|
{
|
|
// Editing the "show preview" option changes whether we need to be displaying the preview or the original mesh.
|
|
if (Property && (Property->GetFName() == GET_MEMBER_NAME_CHECKED(UMirrorToolProperties, bShowPreview)))
|
|
{
|
|
for (int32 ComponentIdx = 0; ComponentIdx < Targets.Num(); ComponentIdx++)
|
|
{
|
|
TargetComponentInterface(ComponentIdx)->SetOwnerVisibility(!Settings->bShowPreview);
|
|
}
|
|
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
|
|
{
|
|
Preview->SetVisibility(Settings->bShowPreview);
|
|
}
|
|
}
|
|
|
|
// Regardless of what changed, update the previews.
|
|
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
|
|
{
|
|
Preview->InvalidateResult();
|
|
}
|
|
}
|
|
|
|
void UMirrorTool::OnTick(float DeltaTime)
|
|
{
|
|
// Deal with any buttons that may have been clicked
|
|
if (PendingAction != EMirrorToolAction::NoAction)
|
|
{
|
|
ApplyAction(PendingAction);
|
|
PendingAction = EMirrorToolAction::NoAction;
|
|
}
|
|
|
|
if (PlaneMechanic != nullptr)
|
|
{
|
|
// Update snapping behavior based on modifier key.
|
|
PlaneMechanic->SetEnableGridSnaping(Settings->bSnapToWorldGrid ^ bSnappingToggle);
|
|
|
|
PlaneMechanic->Tick(DeltaTime);
|
|
}
|
|
|
|
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
|
|
{
|
|
Preview->Tick(DeltaTime);
|
|
}
|
|
}
|
|
|
|
void UMirrorTool::Render(IToolsContextRenderAPI* RenderAPI)
|
|
{
|
|
// Have the plane draw itself.
|
|
PlaneMechanic->Render(RenderAPI);
|
|
}
|
|
|
|
void UMirrorTool::Setup()
|
|
{
|
|
UInteractiveTool::Setup();
|
|
|
|
SetToolDisplayName(LOCTEXT("ToolName", "Mirror"));
|
|
GetToolManager()->DisplayMessage(
|
|
LOCTEXT("OnStartMirrorTool", "Mirror one or more meshes across a plane. Grid snapping behavior is swapped while the shift key is down. The plane can be set by using the preset buttons, moving the gizmo, or ctrl+clicking on a spot on the original mesh."),
|
|
EToolMessageLevel::UserNotification);
|
|
|
|
// Set up the properties
|
|
Settings = NewObject<UMirrorToolProperties>(this, TEXT("Mirror Tool Settings"));
|
|
Settings->RestoreProperties(this);
|
|
AddToolPropertySource(Settings);
|
|
|
|
ToolActions = NewObject<UMirrorToolActionPropertySet>(this);
|
|
ToolActions->Initialize(this);
|
|
AddToolPropertySource(ToolActions);
|
|
|
|
CheckAndDisplayWarnings();
|
|
|
|
// Fill in the MeshesToMirror array with suitably converted meshes.
|
|
for (int i = 0; i < Targets.Num(); i++)
|
|
{
|
|
IPrimitiveComponentBackedTarget* TargetComponent = TargetComponentInterface(i);
|
|
IMeshDescriptionProvider* TargetMeshProvider = TargetMeshProviderInterface(i);
|
|
|
|
// Convert into dynamic mesh
|
|
TSharedPtr<FDynamicMesh3, ESPMode::ThreadSafe> DynamicMesh = MakeShared<FDynamicMesh3, ESPMode::ThreadSafe>();
|
|
FMeshDescriptionToDynamicMesh Converter;
|
|
Converter.Convert(TargetMeshProvider->GetMeshDescription(), *DynamicMesh);
|
|
|
|
// Wrap the dynamic mesh in a replacement change target
|
|
UDynamicMeshReplacementChangeTarget* WrappedTarget = MeshesToMirror.Add_GetRef(NewObject<UDynamicMeshReplacementChangeTarget>());
|
|
|
|
// Set callbacks so previews are invalidated on undo/redo changing the meshes
|
|
WrappedTarget->SetMesh(DynamicMesh);
|
|
WrappedTarget->OnMeshChanged.AddLambda([this, i]() { Previews[i]->InvalidateResult(); });
|
|
}
|
|
|
|
// Set the visibility of the StaticMeshComponents depending on whether we are showing them or the preview.
|
|
for (int32 ComponentIdx = 0; ComponentIdx < Targets.Num(); ComponentIdx++)
|
|
{
|
|
TargetComponentInterface(ComponentIdx)->SetOwnerVisibility(!Settings->bShowPreview);
|
|
}
|
|
|
|
// Initialize the PreviewMesh and BackgroundCompute objects
|
|
SetupPreviews();
|
|
|
|
// Update the bounding box of the meshes.
|
|
CombinedBounds.Init();
|
|
for (int32 ComponentIdx = 0; ComponentIdx < Targets.Num(); ComponentIdx++)
|
|
{
|
|
FVector ComponentOrigin, ComponentExtents;
|
|
TargetComponentInterface(ComponentIdx)->GetOwnerActor()->GetActorBounds(false, ComponentOrigin, ComponentExtents);
|
|
CombinedBounds += FBox::BuildAABB(ComponentOrigin, ComponentExtents);
|
|
}
|
|
|
|
// Set the initial mirror plane. We want the plane to start in the middle if we're doing a simple
|
|
// mirror (i.e., not appending, and not cropping). Otherwise, we want the plane to start to one side.
|
|
MirrorPlaneOrigin = (FVector3d)CombinedBounds.GetCenter();
|
|
MirrorPlaneNormal = FVector3d(0, -1, 0);
|
|
if (Settings->OperationMode == EMirrorOperationMode::MirrorAndAppend || Settings->bCropAlongMirrorPlaneFirst)
|
|
{
|
|
MirrorPlaneOrigin.Y = CombinedBounds.Min.Y;
|
|
}
|
|
|
|
// Set up the mirror plane mechanic, which manages the gizmo
|
|
PlaneMechanic = NewObject<UConstructionPlaneMechanic>(this);
|
|
PlaneMechanic->Setup(this);
|
|
PlaneMechanic->Initialize(TargetWorld, FFrame3d(MirrorPlaneOrigin, MirrorPlaneNormal));
|
|
|
|
// Have the plane mechanic update things properly
|
|
PlaneMechanic->OnPlaneChanged.AddLambda([this]() {
|
|
MirrorPlaneNormal = PlaneMechanic->Plane.Rotation.AxisZ();
|
|
MirrorPlaneOrigin = PlaneMechanic->Plane.Origin;
|
|
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
|
|
{
|
|
Preview->InvalidateResult();
|
|
}
|
|
});
|
|
|
|
// Modify the Ctrl+click set plane behavior to respond to our CtrlClickBehavior property
|
|
PlaneMechanic->SetPlaneCtrlClickBehaviorTarget->OnClickedPositionFunc = [this](const FHitResult& Hit)
|
|
{
|
|
bool bIgnoreNormal = (Settings->CtrlClickBehavior == EMirrorCtrlClickBehavior::Reposition);
|
|
PlaneMechanic->SetDrawPlaneFromWorldPos((FVector3d)Hit.ImpactPoint, (FVector3d)Hit.ImpactNormal, bIgnoreNormal);
|
|
};
|
|
// Also include the original components in the ctrl+click hit testing even though we made them
|
|
// invisible, since we want to be able to reposition the plane onto the original mesh.
|
|
for (int32 ComponentIdx = 0; ComponentIdx < Targets.Num(); ComponentIdx++)
|
|
{
|
|
IPrimitiveComponentBackedTarget* TargetComponent = TargetComponentInterface(ComponentIdx);
|
|
PlaneMechanic->SetPlaneCtrlClickBehaviorTarget->InvisibleComponentsToHitTest.Add(TargetComponent->GetOwnerComponent());
|
|
}
|
|
|
|
|
|
// Add modifier button for snapping
|
|
UKeyAsModifierInputBehavior* SnapToggleBehavior = NewObject<UKeyAsModifierInputBehavior>();
|
|
SnapToggleBehavior->Initialize(this, SnappingToggleModifierId, FInputDeviceState::IsShiftKeyDown);
|
|
AddInputBehavior(SnapToggleBehavior);
|
|
|
|
// Start the preview calculations
|
|
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
|
|
{
|
|
Preview->InvalidateResult();
|
|
}
|
|
}
|
|
|
|
void UMirrorTool::SetupPreviews()
|
|
{
|
|
// Create a preview (with an op) for each selected component.
|
|
int32 NumMeshes = MeshesToMirror.Num();
|
|
for (int32 PreviewIndex = 0; PreviewIndex < NumMeshes; ++PreviewIndex)
|
|
{
|
|
UMirrorOperatorFactory* MirrorOpCreator = NewObject<UMirrorOperatorFactory>();
|
|
MirrorOpCreator->MirrorTool = this;
|
|
MirrorOpCreator->ComponentIndex = PreviewIndex;
|
|
|
|
UMeshOpPreviewWithBackgroundCompute* Preview = Previews.Add_GetRef(
|
|
NewObject<UMeshOpPreviewWithBackgroundCompute>(MirrorOpCreator, "Preview"));
|
|
Preview->Setup(TargetWorld, MirrorOpCreator);
|
|
Preview->PreviewMesh->SetTangentsMode(EDynamicMeshComponentTangentsMode::AutoCalculated);
|
|
|
|
FComponentMaterialSet MaterialSet;
|
|
TargetMaterialInterface(PreviewIndex)->GetMaterialSet(MaterialSet);
|
|
Preview->ConfigureMaterials(MaterialSet.Materials, ToolSetupUtil::GetDefaultWorkingMaterial(GetToolManager()));
|
|
|
|
// Set initial preview to unprocessed mesh, so that things don't disappear initially
|
|
Preview->PreviewMesh->UpdatePreview(MeshesToMirror[PreviewIndex]->GetMesh().Get());
|
|
Preview->PreviewMesh->SetTransform(TargetComponentInterface(PreviewIndex)->GetWorldTransform());
|
|
Preview->SetVisibility(Settings->bShowPreview);
|
|
}
|
|
}
|
|
|
|
void UMirrorTool::CheckAndDisplayWarnings()
|
|
{
|
|
// We can have more than one warning, which makes this a bit more work.
|
|
FText SameSourceWarning;
|
|
FText NonUniformScaleWarning;
|
|
|
|
// See if any of the selected components have the same source.
|
|
TArray<int32> MapToFirstOccurrences;
|
|
bool bAnyHaveSameSource = GetMapToSharedSourceData(MapToFirstOccurrences);
|
|
if (bAnyHaveSameSource)
|
|
{
|
|
SameSourceWarning = LOCTEXT("MirrorMultipleAssetsWithSameSource", "WARNING: Multiple meshes in your selection use the same source asset! Only the \"Create New Assets\" save mode is supported.");
|
|
|
|
// We could forcefully set the save mode to CreateNewAssets, but the setting will persist on new invocations
|
|
// of the tool, which may surprise the user. So, it's up to them to set it.
|
|
}
|
|
|
|
// See if any of the selected components have a nonuniform scaling transform.
|
|
IPrimitiveComponentBackedTarget* NonUniformScalingTarget = nullptr;
|
|
for (int32 i = 0; i < Targets.Num(); ++i)
|
|
{
|
|
FVector Scaling = TargetComponentInterface(i)->GetWorldTransform().GetScale3D();
|
|
if (Scaling.X != Scaling.Y || Scaling.Y != Scaling.Z)
|
|
{
|
|
NonUniformScalingTarget = TargetComponentInterface(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (NonUniformScalingTarget)
|
|
{
|
|
NonUniformScaleWarning = FText::Format(
|
|
LOCTEXT("MirrorNonUniformScaledAsset", "WARNING: The item \"{0}\" has a non-uniform scaling transform. This is not supported because mirroring acts on the underlying mesh, and mirroring is not commutative with non-uniform scaling. Consider deforming the mesh rather than scaling it non-uniformly."),
|
|
FText::FromString(NonUniformScalingTarget->GetOwnerActor()->GetName()));
|
|
}
|
|
|
|
if (bAnyHaveSameSource && NonUniformScalingTarget)
|
|
{
|
|
// Concatenates the two warnings with an extra line in between.
|
|
GetToolManager()->DisplayMessage(FText::Format(LOCTEXT("CombinedWarnings", "{0}\n\n{1}"),
|
|
SameSourceWarning, NonUniformScaleWarning), EToolMessageLevel::UserWarning);
|
|
}
|
|
else if (bAnyHaveSameSource)
|
|
{
|
|
GetToolManager()->DisplayMessage(SameSourceWarning, EToolMessageLevel::UserWarning);
|
|
}
|
|
else if (NonUniformScalingTarget)
|
|
{
|
|
GetToolManager()->DisplayMessage(NonUniformScaleWarning, EToolMessageLevel::UserWarning);
|
|
}
|
|
}
|
|
|
|
void UMirrorTool::Shutdown(EToolShutdownType ShutdownType)
|
|
{
|
|
Settings->SaveProperties(this);
|
|
|
|
PlaneMechanic->Shutdown();
|
|
|
|
// Restore (unhide) the source meshes
|
|
for (int32 ComponentIdx = 0; ComponentIdx < Targets.Num(); ComponentIdx++)
|
|
{
|
|
TargetComponentInterface(ComponentIdx)->SetOwnerVisibility(true);
|
|
}
|
|
|
|
// Swap in results, if appropriate
|
|
if (ShutdownType == EToolShutdownType::Accept)
|
|
{
|
|
// Gather results
|
|
TArray<FDynamicMeshOpResult> Results;
|
|
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
|
|
{
|
|
Results.Emplace(Preview->Shutdown());
|
|
}
|
|
|
|
// Convert to output. This will also edit the selection.
|
|
GenerateAsset(Results);
|
|
}
|
|
else
|
|
{
|
|
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
|
|
{
|
|
Preview->Cancel();
|
|
}
|
|
}
|
|
}
|
|
|
|
void UMirrorTool::GenerateAsset(const TArray<FDynamicMeshOpResult>& Results)
|
|
{
|
|
if (Results.Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
GetToolManager()->BeginUndoTransaction(LOCTEXT("MirrorToolTransactionName", "Mirror Tool"));
|
|
|
|
ensure(Results.Num() > 0);
|
|
|
|
int32 NumSourceMeshes = MeshesToMirror.Num();
|
|
|
|
// check if we entirely cut away any meshes
|
|
bool bWantToDestroy = false;
|
|
for (int OrigMeshIdx = 0; OrigMeshIdx < NumSourceMeshes; OrigMeshIdx++)
|
|
{
|
|
if (Results[OrigMeshIdx].Mesh->TriangleCount() == 0)
|
|
{
|
|
bWantToDestroy = true;
|
|
break;
|
|
}
|
|
}
|
|
// if so ask user what to do
|
|
if (bWantToDestroy)
|
|
{
|
|
FText Title = LOCTEXT("MirrorDestroyTitle", "Delete mesh components?");
|
|
EAppReturnType::Type Ret = FMessageDialog::Open(EAppMsgType::YesNo,
|
|
LOCTEXT("PlaneCutDestroyQuestion", "The mirror plane cropping has entirely cut away at least one mesh. Actually destroy these mesh components?"), &Title);
|
|
if (Ret == EAppReturnType::No)
|
|
{
|
|
bWantToDestroy = false;
|
|
}
|
|
}
|
|
|
|
// Properly deal with each result, setting up the selection at the same time.
|
|
FSelectedOjectsChangeList NewSelection;
|
|
NewSelection.ModificationType = ESelectedObjectsModificationType::Replace;
|
|
for (int OrigMeshIdx = 0; OrigMeshIdx < NumSourceMeshes; OrigMeshIdx++)
|
|
{
|
|
IPrimitiveComponentBackedTarget* TargetComponent = TargetComponentInterface(OrigMeshIdx);
|
|
FDynamicMesh3* Mesh = Results[OrigMeshIdx].Mesh.Get();
|
|
check(Mesh != nullptr);
|
|
|
|
if (Mesh->TriangleCount() == 0)
|
|
{
|
|
if (bWantToDestroy)
|
|
{
|
|
TargetComponent->GetOwnerComponent()->DestroyComponent();
|
|
}
|
|
continue;
|
|
}
|
|
else if (Settings->SaveMode == EMirrorSaveMode::UpdateAssets)
|
|
{
|
|
NewSelection.Actors.Add(TargetComponent->GetOwnerActor());
|
|
|
|
TargetMeshCommitterInterface(OrigMeshIdx)->CommitMeshDescription([&Mesh](const IMeshDescriptionCommitter::FCommitterParams& CommitParams)
|
|
{
|
|
FDynamicMeshToMeshDescription Converter;
|
|
Converter.Convert(Mesh, *CommitParams.MeshDescriptionOut);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
// Build array of materials from the original.
|
|
TArray<UMaterialInterface*> Materials;
|
|
IMaterialProvider* TargetMaterial = TargetMaterialInterface(OrigMeshIdx);
|
|
for (int MaterialIdx = 0, NumMaterials = TargetMaterial->GetNumMaterials(); MaterialIdx < NumMaterials; MaterialIdx++)
|
|
{
|
|
Materials.Add(TargetMaterial->GetMaterial(MaterialIdx));
|
|
}
|
|
|
|
FCreateMeshObjectParams NewMeshObjectParams;
|
|
NewMeshObjectParams.TargetWorld = TargetWorld;
|
|
NewMeshObjectParams.Transform = (FTransform)Results[OrigMeshIdx].Transform;
|
|
NewMeshObjectParams.BaseName = TEXT("Mirror");
|
|
NewMeshObjectParams.Materials = Materials;
|
|
NewMeshObjectParams.SetMesh(Mesh);
|
|
FCreateMeshObjectResult Result = UE::Modeling::CreateMeshObject(GetToolManager(), MoveTemp(NewMeshObjectParams));
|
|
if (Result.IsOK() && Result.NewActor != nullptr)
|
|
{
|
|
NewSelection.Actors.Add(Result.NewActor);
|
|
}
|
|
|
|
// Remove the original actor
|
|
TargetComponent->GetOwnerComponent()->DestroyComponent();
|
|
}
|
|
}
|
|
|
|
// Update the selection
|
|
if (NewSelection.Actors.Num() > 0)
|
|
{
|
|
GetToolManager()->RequestSelectionChange(NewSelection);
|
|
}
|
|
|
|
GetToolManager()->EndUndoTransaction();
|
|
}
|
|
|
|
|
|
// Action support
|
|
|
|
void UMirrorTool::RequestAction(EMirrorToolAction ActionType)
|
|
{
|
|
if (PendingAction == EMirrorToolAction::NoAction)
|
|
{
|
|
PendingAction = ActionType;
|
|
}
|
|
}
|
|
|
|
void UMirrorTool::ApplyAction(EMirrorToolAction ActionType)
|
|
{
|
|
FVector3d ShiftedPlaneOrigin = (FVector3d)CombinedBounds.GetCenter();
|
|
|
|
if (ActionType == EMirrorToolAction::ShiftToCenter)
|
|
{
|
|
// We keep the same orientation here
|
|
PlaneMechanic->SetDrawPlaneFromWorldPos(ShiftedPlaneOrigin, FVector3d(), true);
|
|
}
|
|
else
|
|
{
|
|
// We still start from the center, but adjust one of the coordinates and set direction.
|
|
FVector3d DirectionVector;
|
|
switch (ActionType)
|
|
{
|
|
case EMirrorToolAction::Left:
|
|
ShiftedPlaneOrigin.Y = CombinedBounds.Min.Y;
|
|
DirectionVector = FVector3d(0, -1.0, 0);
|
|
break;
|
|
case EMirrorToolAction::Right:
|
|
ShiftedPlaneOrigin.Y = CombinedBounds.Max.Y;
|
|
DirectionVector = FVector3d(0, 1.0, 0);
|
|
break;
|
|
case EMirrorToolAction::Up:
|
|
ShiftedPlaneOrigin.Z = CombinedBounds.Max.Z;
|
|
DirectionVector = FVector3d(0, 0, 1.0);
|
|
break;
|
|
case EMirrorToolAction::Down:
|
|
ShiftedPlaneOrigin.Z = CombinedBounds.Min.Z;
|
|
DirectionVector = FVector3d(0, 0, -1.0);
|
|
break;
|
|
case EMirrorToolAction::Forward:
|
|
ShiftedPlaneOrigin.X = CombinedBounds.Max.X;
|
|
DirectionVector = FVector3d(1.0, 0, 0);
|
|
break;
|
|
case EMirrorToolAction::Backward:
|
|
ShiftedPlaneOrigin.X = CombinedBounds.Min.X;
|
|
DirectionVector = FVector3d(-1.0, 0, 0);
|
|
break;
|
|
}
|
|
|
|
// The user can optionally have the button change the direction only
|
|
if (Settings->bButtonsOnlyChangeOrientation)
|
|
{
|
|
ShiftedPlaneOrigin = MirrorPlaneOrigin; // Keeps the same
|
|
}
|
|
PlaneMechanic->SetDrawPlaneFromWorldPos(ShiftedPlaneOrigin, DirectionVector, false);
|
|
}
|
|
}
|
|
|
|
void UMirrorTool::OnUpdateModifierState(int ModifierID, bool bIsOn)
|
|
{
|
|
if (ModifierID == SnappingToggleModifierId)
|
|
{
|
|
bSnappingToggle = bIsOn;
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|