Files
UnrealEngine/Engine/Plugins/Runtime/MeshModelingToolset/Source/MeshModelingTools/Private/ToolActivities/PolyEditExtrudeEdgeActivity.cpp
2025-05-18 13:04:45 +08:00

738 lines
24 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ToolActivities/PolyEditExtrudeEdgeActivity.h"
#include "BaseGizmos/CombinedTransformGizmo.h"
#include "BaseGizmos/TransformGizmoUtil.h"
#include "BaseGizmos/TransformProxy.h"
#include "Containers/Map.h"
#include "ContextObjectStore.h"
#include "Drawing/PreviewGeometryActor.h"
#include "DynamicMesh/DynamicMeshChangeTracker.h"
#include "DynamicMeshEditor.h"
#include "InteractiveToolManager.h"
#include "MeshOpPreviewHelpers.h"
#include "ModelingOperators.h" // FDynamicMeshOperator
#include "PrimitiveDrawingUtils.h"
#include "Selection/PolygonSelectionMechanic.h"
#include "Selection/StoredMeshSelectionUtil.h"
#include "ToolActivities/PolyEditActivityContext.h"
#include "ToolContextInterfaces.h" // IToolsContextRenderAPI
#define LOCTEXT_NAMESPACE "PolyEditExtrudeEdgeActivity"
namespace PolyEditExtrudeEdgeActivityLocals
{
// When adjusting extrusion distance to keep edges parallel, maximal allowed increase factor
const double MAX_VERT_MOVEMENT_ADJUSTMENT_SCALE = 4.0;
// These should match what is used in UMeshTopologySelectionMechanic (or better, live in a common place)
const FColor GroupLineColor = FColor::Red;
const float GroupLineThickness = 1.0f;
const float GroupLineDepthBias = 0.5f;
const FColor ExtrudeFrameLineColor = FColor::Orange;
const float ExtrudeFrameLineThickness = 0.5f;
const double ExtrudeFrameLineLength = 1000;
FText TransactionLabel = LOCTEXT("ExtrudeEdgeTransactionLabel", "Extrude Edges");
class FExtrudeEdgeOp : public UE::Geometry::FDynamicMeshOperator
{
public:
virtual ~FExtrudeEdgeOp() {}
// Inputs:
TSharedPtr<const FDynamicMesh3, ESPMode::ThreadSafe> OriginalMesh;
FTransform OriginalMeshTransform;
TArray<int32> SelectedEids;
TArray<int32> GroupsToSetPerEid;
EPolyEditExtrudeEdgeDirectionMode DirectionMode;
FVector3d SingleDirectionVector;
FVector3d LocalFrameParams;
double ScalingAdjustmentLimit = 1;
bool bAssignAnyBoundaryNeighborToUnmatched = false;
bool bGatherGroupBoundariesForRender = false;
bool bGatherAllEdgesForRender = false;
// Outputs
TArray<int32> NewSelectionEids;
TArray<int32> EdgesToRender;
protected:
TMap<int32, FVector3d> DesiredExtrudeDirections;
public:
// FDynamicMeshOperator
virtual void CalculateResult(FProgressCancel* Progress) override
{
using namespace UE::Geometry;
if (Progress && Progress->Cancelled()) { return; }
ResultMesh->Copy(*OriginalMesh);
ResultTransform = OriginalMeshTransform;
if (Progress && Progress->Cancelled()) { return; }
if ((DirectionMode == EPolyEditExtrudeEdgeDirectionMode::LocalExtrudeFrames && LocalFrameParams.IsNearlyZero())
|| (DirectionMode == EPolyEditExtrudeEdgeDirectionMode::SingleDirection && SingleDirectionVector.IsNearlyZero()))
{
return;
}
FExtrudeBoundaryEdges Extruder(ResultMesh.Get());
switch (DirectionMode)
{
case EPolyEditExtrudeEdgeDirectionMode::LocalExtrudeFrames:
Extruder.OffsetPositionFunc = [this](const FVector3d& Position, const FExtrudeBoundaryEdges::FExtrudeFrame& ExtrudeFrame, int32 SourceVid)
{
return ExtrudeFrame.FromFramePoint(LocalFrameParams);
};
break;
case EPolyEditExtrudeEdgeDirectionMode::SingleDirection:
Extruder.OffsetPositionFunc = [this](const FVector3d& Position, const FExtrudeBoundaryEdges::FExtrudeFrame& ExtrudeFrame, int32 SourceVid)
{
return Position + SingleDirectionVector;
};
break;
}
Extruder.InputEids = SelectedEids;
Extruder.GroupsToSetPerEid = GroupsToSetPerEid;
Extruder.ScalingAdjustmentLimit = ScalingAdjustmentLimit;
Extruder.bUsePerVertexExtrudeFrames = DirectionMode == EPolyEditExtrudeEdgeDirectionMode::LocalExtrudeFrames;
Extruder.bAssignAnyBoundaryNeighborToUnmatched = bAssignAnyBoundaryNeighborToUnmatched;
if (Progress && Progress->Cancelled()) { return; }
Extruder.Apply(Progress);
if (Progress && Progress->Cancelled()) { return; }
NewSelectionEids = Extruder.NewExtrudedEids;
TSet<int32> EdgesToRenderSet;
if (bGatherAllEdgesForRender)
{
for (int32 Tid : Extruder.NewTids)
{
FIndex3i TriEids = ResultMesh->GetTriEdges(Tid);
for (int i = 0; i < 3; ++i)
{
EdgesToRenderSet.Add(TriEids[i]);
}
}
}
else if (bGatherGroupBoundariesForRender)
{
for (int32 Tid : Extruder.NewTids)
{
FIndex3i TriEids = ResultMesh->GetTriEdges(Tid);
for (int i = 0; i < 3; ++i)
{
if (ResultMesh->IsBoundaryEdge(TriEids[i]) || ResultMesh->IsGroupBoundaryEdge(TriEids[i]))
{
EdgesToRenderSet.Add(TriEids[i]);
}
}
}
}
EdgesToRender = EdgesToRenderSet.Array();
}
};
}
TUniquePtr<UE::Geometry::FDynamicMeshOperator> UPolyEditExtrudeEdgeActivity::MakeNewOperator()
{
using namespace PolyEditExtrudeEdgeActivityLocals;
TUniquePtr<FExtrudeEdgeOp> Op = MakeUnique<FExtrudeEdgeOp>();
Op->OriginalMesh = MakeShared<FDynamicMesh3, ESPMode::ThreadSafe>(*ActivityContext->CurrentMesh);
Op->OriginalMeshTransform = ActivityContext->Preview->PreviewMesh->GetTransform();
Op->SelectedEids = SelectedEids;
Op->GroupsToSetPerEid = GroupsToSetPerEid;
Op->ScalingAdjustmentLimit = Settings->bAdjustToExtrudeEvenly ? MAX_VERT_MOVEMENT_ADJUSTMENT_SCALE : 1.0;
Op->DirectionMode = Settings->DirectionMode;
Op->bGatherAllEdgesForRender = ActivityContext->bTriangleMode;
Op->bGatherGroupBoundariesForRender = true;
Op->bAssignAnyBoundaryNeighborToUnmatched = Settings->bUseUnselectedForFrames;
if (Settings->DirectionMode == EPolyEditExtrudeEdgeDirectionMode::SingleDirection)
{
FVector3d WorldDestination = SingleDirectionVectorWorldSpace + ExtrudeFrameForGizmoWorldSpace.Origin;
Op->SingleDirectionVector = CurrentMeshTransform.InverseTransformPosition(WorldDestination)
- ExtrudeFrameForGizmoMeshSpace.Frame.Origin;
}
else
{
FVector3d WorldDestination = ExtrudeFrameForGizmoWorldSpace.FromFramePoint(ParamsInWorldExtrudeFrame);
Op->LocalFrameParams = ExtrudeFrameForGizmoMeshSpace.ToFramePoint(
CurrentMeshTransform.InverseTransformPosition(WorldDestination));
}
return Op;
}
void UPolyEditExtrudeEdgeActivity::Setup(UInteractiveTool* ParentToolIn)
{
Super::Setup(ParentToolIn);
ActivityContext = ParentTool->GetToolManager()->GetContextObjectStore()->FindContext<UPolyEditActivityContext>();
Settings = NewObject<UPolyEditExtrudeEdgeActivityProperties>();
Settings->RestoreProperties(ParentTool.Get());
AddToolPropertySource(Settings);
SetToolPropertySourceEnabled(Settings, false);
ExtrudeFrameProxy = NewObject<UTransformProxy>(this);
ExtrudeFrameProxy->OnTransformChanged.AddWeakLambda(this, [this](UTransformProxy*, FTransform Transform)
{
// TODO: It's possible to get these callbacks from undo while the activity is not running because the
// transform changes are only expired by exiting the tool, not the activity. This is easy enough to
// guard against, but may need a broader solution someday.
if (bIsRunning && ActivityContext && ActivityContext->Preview)
{
ParamsInWorldExtrudeFrame = ExtrudeFrameForGizmoWorldSpace.ToFramePoint(Transform.GetLocation());
ActivityContext->Preview->InvalidateResult();
}
});
SingleDirectionProxy = NewObject<UTransformProxy>(this);
SingleDirectionProxy->OnTransformChanged.AddWeakLambda(this, [this](UTransformProxy*, FTransform Transform)
{
if (bIsRunning && ActivityContext && ActivityContext->Preview)
{
SingleDirectionVectorWorldSpace = Transform.GetLocation() - ExtrudeFrameForGizmoWorldSpace.Origin;
ActivityContext->Preview->InvalidateResult();
}
});
Settings->WatchProperty(Settings->DirectionMode, [this](EPolyEditExtrudeEdgeDirectionMode)
{
ConvertToNewDirectionMode(Settings->DirectionMode == EPolyEditExtrudeEdgeDirectionMode::SingleDirection);
});
Settings->WatchProperty(Settings->Distance, [this](double)
{
if (!bIsRunning) { return; }
// When distance changes, we keep the same extrude direction (set by gizmo or automatically) and
// just adjust the length of extrusion in world space.
auto AdjustVectorLength = [this](FVector3d& VectorToAdjust)
{
if (bExtrudeDistanceWasNegative)
{
// The normlized extrude vector is actually in the opposite direction
VectorToAdjust *= -1;
}
VectorToAdjust.Normalize();
if (VectorToAdjust.IsZero())
{
VectorToAdjust = FVector3d::UnitX();
}
VectorToAdjust *= Settings->Distance;
};
if (Settings->DirectionMode == EPolyEditExtrudeEdgeDirectionMode::SingleDirection)
{
AdjustVectorLength(SingleDirectionVectorWorldSpace);
}
else
{
AdjustVectorLength(ParamsInWorldExtrudeFrame);
}
bExtrudeDistanceWasNegative = Settings->Distance < 0;
if (bIsRunning)
{
ActivityContext->Preview->InvalidateResult();
}
});
Settings->WatchProperty(Settings->DistanceMode, [this](EPolyEditExtrudeEdgeDistanceMode)
{
if (!bIsRunning) { return; }
// When swapping distance mode, we would like things to stay pretty much where they are.
// This can be done by updating from params, since both methods of operation are just
// setting those.
if (Settings->DistanceMode == EPolyEditExtrudeEdgeDistanceMode::Fixed)
{
UpdateDistanceFromParams();
}
else
{
UpdateGizmosFromCurrentParams();
}
UpdateGizmoVisibility();
});
auto UpdateExtrudeFrame = [this]()
{
if (!bIsRunning) { return; }
RecalculateGizmoExtrudeFrame();
UpdateGizmosFromCurrentParams();
ActivityContext->Preview->InvalidateResult();
};
Settings->WatchProperty(Settings->bUseUnselectedForFrames, [UpdateExtrudeFrame](bool)
{
UpdateExtrudeFrame();
});
Settings->WatchProperty(Settings->bAdjustToExtrudeEvenly, [UpdateExtrudeFrame](bool)
{
UpdateExtrudeFrame();
});
Settings->SilentUpdateWatched();
}
EToolActivityStartResult UPolyEditExtrudeEdgeActivity::Start()
{
using namespace PolyEditExtrudeEdgeActivityLocals;
if (!CanStart())
{
ParentTool->GetToolManager()->DisplayMessage(
LOCTEXT("OnExtrudeFailedMesssage", "Action requires boundary edge selection."),
EToolMessageLevel::UserWarning);
return EToolActivityStartResult::FailedStart;
}
CurrentMeshTransform = ActivityContext->Preview->PreviewMesh->GetTransform();
ActivityContext->Preview->ChangeOpFactory(this);
ActivityContext->Preview->OnOpCompleted.AddWeakLambda(this,
[this](const UE::Geometry::FDynamicMeshOperator* UncastOp)
{
const FExtrudeEdgeOp* Op = static_cast<const FExtrudeEdgeOp*>(UncastOp);
NewSelectionEids = Op->NewSelectionEids;
EidsToRender = Op->EdgesToRender;
});
ActivityContext->Preview->OnMeshUpdated.AddWeakLambda(this,
[this](UMeshOpPreviewWithBackgroundCompute*)
{
// Do this here instead of OnOpCompleted, because we need access to the updated mesh.
UpdateDrawnPreviewEdges();
});
PreviewGeometry = NewObject<UPreviewGeometry>();
PreviewGeometry->CreateInWorld(ActivityContext->Preview->GetWorld(), FTransform::Identity);
PreviewGeometry->SetTransform(CurrentMeshTransform);
SetToolPropertySourceEnabled(Settings, true);
bIsRunning = true;
// Things seem to look a little better if we clear the selection highlighting. But this means we need to
// revert the selection before issuing any undo/redo transactions. This is done either in ApplyExtrude or
// EndInternal.
ActiveSelection = ActivityContext->SelectionMechanic->GetActiveSelection();
ActivityContext->SelectionMechanic->ClearSelection();
bRevertedSelection = false;
GatherSelectedEids();
UInteractiveGizmoManager* GizmoManager = GetParentTool()->GetToolManager()->GetPairedGizmoManager();
ExtrudeFrameGizmo = UE::TransformGizmoUtil::CreateCustomTransformGizmo(GizmoManager,
ETransformGizmoSubElements::TranslateAxisX | ETransformGizmoSubElements::TranslateAxisZ | ETransformGizmoSubElements::TranslatePlaneXZ,
this);
ExtrudeFrameGizmo->SetActiveTarget(ExtrudeFrameProxy, GetParentTool()->GetToolManager());
ExtrudeFrameGizmo->SetVisibility(false);
// We force the coordinate system to be local so that the gizmo only moves in the plane we specify
ExtrudeFrameGizmo->bUseContextCoordinateSystem = false;
ExtrudeFrameGizmo->CurrentCoordinateSystem = EToolContextCoordinateSystem::Local;
SingleDirectionGizmo = UE::TransformGizmoUtil::CreateCustomTransformGizmo(GizmoManager,
ETransformGizmoSubElements::TranslateAllAxes | ETransformGizmoSubElements::TranslateAllPlanes,
this);
SingleDirectionGizmo->SetActiveTarget(SingleDirectionProxy);
SingleDirectionGizmo->SetVisibility(false);
RecalculateGizmoExtrudeFrame();
ResetParams();
UpdateGizmosFromCurrentParams();
UpdateGizmoVisibility();
// Do this as the last thing so that everything is set up for it.
ActivityContext->Preview->InvalidateResult();
ActivityContext->EmitActivityStart(LOCTEXT("BeginExtrudeEdgeActivity", "Begin Extrude Edges"));
return EToolActivityStartResult::Running;
}
EToolActivityEndResult UPolyEditExtrudeEdgeActivity::End(EToolShutdownType ShutdownType)
{
if (!ensure(bIsRunning))
{
EndInternal();
return EToolActivityEndResult::ErrorDuringEnd;
}
if (ShutdownType == EToolShutdownType::Cancel)
{
EndInternal();
// Reset the preview
ActivityContext->Preview->PreviewMesh->UpdatePreview(ActivityContext->CurrentMesh.Get());
return EToolActivityEndResult::Cancelled;
}
else
{
// Stop the current compute if there is one.
ActivityContext->Preview->CancelCompute();
// Apply whatever we happen to have at the moment.
ApplyExtrude();
EndInternal();
return EToolActivityEndResult::Completed;
}
}
void UPolyEditExtrudeEdgeActivity::ApplyExtrude()
{
using namespace UE::Geometry;
using namespace PolyEditExtrudeEdgeActivityLocals;
const FDynamicMesh3* ResultMesh = ActivityContext->Preview->PreviewMesh->GetMesh();
UE::Geometry::FMeshEdgesFromTriangleSubIndices StableEdgeIDs;
StableEdgeIDs.InitializeFromEdgeIDs(*ResultMesh, NewSelectionEids);
FDynamicMeshChangeTracker ChangeTracker(ActivityContext->CurrentMesh.Get());
ChangeTracker.BeginChange();
ActivityContext->CurrentMesh->Copy(*ResultMesh);
// We need to reset the old selection back before we give the new one, so that undo reverts back
// to the correct selection state. Currently, the boolean check is only necessary in EndInternal,
// but we'll keep it in case we move code around.
if (!bRevertedSelection)
{
ActivityContext->SelectionMechanic->SetSelection(ActiveSelection, false);
bRevertedSelection = true;
}
ParentTool->GetToolManager()->BeginUndoTransaction(TransactionLabel);
FGroupTopologySelection NewSelection;
ActivityContext->EmitCurrentMeshChangeAndUpdate(TransactionLabel, ChangeTracker.EndChange(), NewSelection);
ActivityContext->SelectionMechanic->BeginChange();
StableEdgeIDs.GetEdgeIDs(*ActivityContext->CurrentMesh, NewSelectionEids);
for (int32 Eid : NewSelectionEids)
{
NewSelection.SelectedEdgeIDs.Add(ActivityContext->CurrentTopology->FindGroupEdgeID(Eid));
}
ActivityContext->SelectionMechanic->SetSelection(NewSelection);
ActivityContext->SelectionMechanic->EndChangeAndEmitIfModified();
ParentTool->GetToolManager()->EndUndoTransaction();
}
void UPolyEditExtrudeEdgeActivity::EndInternal()
{
// Fix up the selection we cleared to remove the highlighting (if we haven't already done it via an Apply)
if (!bRevertedSelection)
{
ActivityContext->SelectionMechanic->SetSelection(ActiveSelection, false);
bRevertedSelection = true;
}
ActivityContext->Preview->ClearOpFactory();
ActivityContext->Preview->OnOpCompleted.RemoveAll(this);
ActivityContext->Preview->OnMeshUpdated.RemoveAll(this);
SetToolPropertySourceEnabled(Settings, false);
UInteractiveGizmoManager* GizmoManager = GetParentTool()->GetToolManager()->GetPairedGizmoManager();
GizmoManager->DestroyAllGizmosByOwner(this);
PreviewGeometry->Disconnect();
PreviewGeometry = nullptr;
bIsRunning = false;
}
void UPolyEditExtrudeEdgeActivity::Shutdown(EToolShutdownType ShutdownType)
{
if (IsRunning())
{
End(ShutdownType);
}
Super::Shutdown(ShutdownType);
}
bool UPolyEditExtrudeEdgeActivity::CanStart() const
{
using namespace UE::Geometry;
if (!ActivityContext)
{
return false;
}
const FGroupTopologySelection& Selection = ActivityContext->SelectionMechanic->GetActiveSelection();
if (Selection.SelectedEdgeIDs.IsEmpty())
{
return false;
}
for (int32 GroupEdgeID : Selection.SelectedEdgeIDs)
{
if (ActivityContext->CurrentTopology->IsBoundaryEdge(GroupEdgeID))
{
return true;
}
}
return false;
}
bool UPolyEditExtrudeEdgeActivity::CanAccept() const
{
return true;
}
void UPolyEditExtrudeEdgeActivity::UpdateGizmoVisibility()
{
if (!bIsRunning || !ensure(SingleDirectionGizmo && ExtrudeFrameGizmo))
{
return;
}
if (Settings->DistanceMode == EPolyEditExtrudeEdgeDistanceMode::Fixed)
{
SingleDirectionGizmo->SetVisibility(false);
ExtrudeFrameGizmo->SetVisibility(false);
}
else
{
ExtrudeFrameGizmo->SetVisibility(Settings->DirectionMode == EPolyEditExtrudeEdgeDirectionMode::LocalExtrudeFrames);
SingleDirectionGizmo->SetVisibility(Settings->DirectionMode == EPolyEditExtrudeEdgeDirectionMode::SingleDirection);
}
}
void UPolyEditExtrudeEdgeActivity::GatherSelectedEids()
{
SelectedEids.Reset();
GroupsToSetPerEid.Reset();
int32 GroupID = ActivityContext->CurrentMesh->MaxGroupID();
// Only used in triangle mode, to avoid creating a bunch of new groups
TMap<int32, int32> NeighborGroupToNewGroup;
for (int32 GroupEdgeID : ActiveSelection.SelectedEdgeIDs)
{
if (!ActivityContext->CurrentTopology->IsBoundaryEdge(GroupEdgeID))
{
continue;
}
if (ActivityContext->bTriangleMode)
{
int32 MeshEdgeID = ActivityContext->CurrentTopology->Edges[GroupEdgeID].Span.Edges[0];
SelectedEids.Add(MeshEdgeID);
int32 Tid = ActivityContext->CurrentMesh->GetEdgeT(MeshEdgeID).A;
int32 NeighborGroup = ActivityContext->CurrentMesh->GetTriangleGroup(Tid);
int32* NewGroupID = NeighborGroupToNewGroup.Find(NeighborGroup);
if (!NewGroupID)
{
NewGroupID = &NeighborGroupToNewGroup.Add(NeighborGroup, GroupID);
++GroupID;
}
GroupsToSetPerEid.Add(*NewGroupID);
}
else
{
for (int32 Eid : ActivityContext->CurrentTopology->GetGroupEdgeEdges(GroupEdgeID))
{
SelectedEids.Add(Eid);
GroupsToSetPerEid.Add(GroupID);
}
++GroupID;
}
}
}
// We draw the boundary edges for our extrusion preview. This is not something we do in other
// cases, for instance while in push/pull and the face extrude activity. But for edge extrusion,
// it is helpful to see the boundary because you may be looking behind the face that you are
// extruding, making it invisible.
void UPolyEditExtrudeEdgeActivity::UpdateDrawnPreviewEdges()
{
using namespace PolyEditExtrudeEdgeActivityLocals;
if (!bIsRunning
|| !ActivityContext || !ActivityContext->Preview || !ActivityContext->Preview->PreviewMesh
|| !PreviewGeometry)
{
return;
}
ActivityContext->Preview->PreviewMesh->ProcessMesh([this](const FDynamicMesh3& Mesh)
{
PreviewGeometry->CreateOrUpdateLineSet(TEXT("NewBoundaries"), EidsToRender.Num(), [this, &Mesh]
(int32 Index, TArray<FRenderableLine>& Lines)
{
int32 Eid = EidsToRender[Index];
if (!ensure(Mesh.IsEdge(Eid)))
{
return;
}
FIndex2i EdgeVids = Mesh.GetEdgeV(Eid);
Lines.Add(FRenderableLine(Mesh.GetVertex(EdgeVids.A), Mesh.GetVertex(EdgeVids.B),
GroupLineColor, GroupLineThickness, GroupLineDepthBias));
});
});
}
// Updates Settings->Distance from the parameters
void UPolyEditExtrudeEdgeActivity::UpdateDistanceFromParams()
{
if (Settings->DirectionMode == EPolyEditExtrudeEdgeDirectionMode::SingleDirection)
{
Settings->Distance = SingleDirectionVectorWorldSpace.Length();
}
else // if (Settings->DirectionMode == EPolyEditExtrudeEdgeDirectionMode::LocalExtrudeFrames)
{
Settings->Distance = ExtrudeFrameForGizmoWorldSpace.FromFrameVector(ParamsInWorldExtrudeFrame).Length();
}
}
void UPolyEditExtrudeEdgeActivity::UpdateGizmosFromCurrentParams()
{
FVector3d SingleDirectionGizmoLocation = SingleDirectionVectorWorldSpace + ExtrudeFrameForGizmoWorldSpace.Origin;
FVector3d ExtrudeFrameGizmoLocation = ExtrudeFrameForGizmoWorldSpace.FromFramePoint(ParamsInWorldExtrudeFrame);
SingleDirectionGizmo->ReinitializeGizmoTransform(FTransform(
FQuat(ExtrudeFrameForGizmoWorldSpace.Rotation),
SingleDirectionGizmoLocation));
ExtrudeFrameGizmo->ReinitializeGizmoTransform(FTransform(
FQuat(ExtrudeFrameForGizmoWorldSpace.Rotation),
ExtrudeFrameGizmoLocation));
ExtrudeFrameGizmo->SetDisplaySpaceTransform(ExtrudeFrameForGizmoWorldSpace.ToFTransform());
}
void UPolyEditExtrudeEdgeActivity::ConvertToNewDirectionMode(bool bToSingleDirection)
{
if (bToSingleDirection)
{
// Convert fixed parameter
FVector3d CurrentPosition = ExtrudeFrameForGizmoWorldSpace.FromFramePoint(ParamsInWorldExtrudeFrame);
SingleDirectionVectorWorldSpace = CurrentPosition - ExtrudeFrameForGizmoWorldSpace.Origin;
// Convert gizmo location
CurrentPosition = ExtrudeFrameGizmo->GetGizmoTransform().GetLocation();
FTransform NewTransform(FQuat(ExtrudeFrameForGizmoWorldSpace.Rotation), CurrentPosition);
SingleDirectionGizmo->ReinitializeGizmoTransform(NewTransform);
}
else
{
// Convert fixed parameter
FVector3d CurrentPosition = ExtrudeFrameForGizmoWorldSpace.Origin + SingleDirectionVectorWorldSpace;
ParamsInWorldExtrudeFrame = ExtrudeFrameForGizmoWorldSpace.ToFramePoint(CurrentPosition);
ParamsInWorldExtrudeFrame.Y = 0;
// Convert gizmo location
CurrentPosition = SingleDirectionGizmo->GetGizmoTransform().GetLocation();
FVector3d FramePoint = ExtrudeFrameForGizmoWorldSpace.ToFramePoint(CurrentPosition);
FramePoint.Y = 0;
FTransform NewTransform(FQuat(ExtrudeFrameForGizmoWorldSpace.Rotation),
ExtrudeFrameForGizmoWorldSpace.FromFramePoint(FramePoint));
ExtrudeFrameGizmo->ReinitializeGizmoTransform(NewTransform);
}
UpdateGizmoVisibility();
if (bIsRunning)
{
ActivityContext->Preview->InvalidateResult();
}
}
void UPolyEditExtrudeEdgeActivity::ResetParams()
{
SingleDirectionVectorWorldSpace = ExtrudeFrameForGizmoWorldSpace.X() * Settings->Distance;
ParamsInWorldExtrudeFrame = FVector3d::UnitX() * Settings->Distance;
}
void UPolyEditExtrudeEdgeActivity::RecalculateGizmoExtrudeFrame()
{
using namespace PolyEditExtrudeEdgeActivityLocals;
using namespace UE::Geometry;
// Getting an extrude frame is actually tricky because we have to grab two boundary edges that are
// adjacent to some vertex in the selection, and the way these get selected is dependent on the
// implementation of FExtrudeBoundaryEdges (because there is ambiguity in the case of bowties).
TArray<FExtrudeBoundaryEdges::FNewVertSourceData> NewVertData;
TMap<int32, FIndex2i> EidToIndicesIntoNewVerts;
FExtrudeBoundaryEdges::GetInputEdgePairings(
*ActivityContext->CurrentMesh, SelectedEids, Settings->bUseUnselectedForFrames,
NewVertData, EidToIndicesIntoNewVerts);
if (!ensure(!NewVertData.IsEmpty()))
{
return;
}
FExtrudeBoundaryEdges::FExtrudeFrame ExtrudeFrame;
FExtrudeBoundaryEdges::GetExtrudeFrame(
*ActivityContext->CurrentMesh,
NewVertData[0].SourceVid,
NewVertData[0].SourceEidPair.A,
NewVertData[0].SourceEidPair.B,
ExtrudeFrame, Settings->bAdjustToExtrudeEvenly ? MAX_VERT_MOVEMENT_ADJUSTMENT_SCALE : 1.0);
ExtrudeFrameForGizmoMeshSpace = ExtrudeFrame;
// This method of finding the extrude frame in world space isn't quite right in the case of nonuniform scale. However
// it seems hard to find any perfect solution, and in the end, it's not critical because we'll probably be able to get
// extrusion parameters out of an imperfect frame anyway.
ExtrudeFrameForGizmoWorldSpace = FFrame3d(
CurrentMeshTransform.TransformPosition(ExtrudeFrameForGizmoMeshSpace.Frame.Origin),
CurrentMeshTransform.TransformRotation(FQuat(ExtrudeFrameForGizmoMeshSpace.Frame.Rotation)));
}
void UPolyEditExtrudeEdgeActivity::Render(IToolsContextRenderAPI* RenderAPI)
{
using namespace PolyEditExtrudeEdgeActivityLocals;
if (bIsRunning)
{
// Draw a line showing where the X axis of the extrude frame is. We could use the preview actor for this but
// this is simpler and doesn't require us to set up the foreground material.
FPrimitiveDrawInterface* PDI = RenderAPI->GetPrimitiveDrawInterface();
PDI->DrawLine(ExtrudeFrameForGizmoWorldSpace.Origin,
ExtrudeFrameForGizmoWorldSpace.FromFramePoint(FVector3d(1000, 0, 0)),
ExtrudeFrameLineColor, SDPG_Foreground, ExtrudeFrameLineThickness, 1.0, true);
}
}
void UPolyEditExtrudeEdgeActivity::Tick(float DeltaTime)
{
}
#undef LOCTEXT_NAMESPACE