Files
UnrealEngineUWP/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/RevolveBoundaryTool.cpp
Ryan Schmidt fdf11b67d2 ModelingComponents: Clean up DynamicMeshComponent API. Update Component and Proxy handling of Tangents to use Attribute Overlay if available. Update affected Tools and also convert most of the affected Tools to use UE::ToolTarget helper functions.
- Add UE::ToolTarget::CommitMaterialSetUpdate() and ::CommitDynamicMeshUpdate(). ::GetDynamicMeshCopy() can now return tangents if requested.
- Add IMeshDescriptionProvider::CalculateAutoGeneratedAttributes(). Default implementation does nothing, UStaticMeshComponentToolTarget implementation initializes auto-generated MeshDescription attributes. Used in ::GetDynamicMeshCopy() to get tangents (but requires a MeshDescription copy).
- Clean up handling of Tangents in Simple/OctreeDynamicMeshComponent. Add local MakeTangentsFunc() to generate the Tangents lambda, handle different cases and no-tangents fallbacks consistently.
- UDynamicMesh: add optional info arguments to EditMesh() and ChangeInfo struct. Add support for deferring change events from Edit funcs.
- Remove UBaseDynamicMeshComponent::InitializeMesh(), ::Bake() APIs, and add ::SetMesh(). Implement in Simple/Octree implementations, update all Tools that used those APIs.
- Add USimpleDynamicMeshComponent::ProcessMesh(), EditMesh(). These are now the preferred ways to read/write mesh.
- Update USimpleDynamicMeshComponent tangents handling. Externally-computed tangents are now taken directly from the FDynamicMesh3 attribute set. Autogenerated tangents are still computed and stored in an internal FMeshTangentsf, but this is no longer exposed for external updates.
- Remove UPreviewMesh pass-through functions for Tangents access, InitializeMesh() and Bake(). Add ProcessMesh()
- Update all affected Tools. In most cases these Tools have also been converted to use ModelingToolTargetUtil functions, instead of direct ToolTarget interface casting.

#rb none
#rnx
#jira none
#preflight 60c3e71d3e1b3c00015668af

[CL 16650666 by Ryan Schmidt in ue5-main branch]
2021-06-11 22:39:18 -04:00

362 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "RevolveBoundaryTool.h"
#include "ModelingObjectsCreationAPI.h"
#include "BaseBehaviors/SingleClickBehavior.h"
#include "CoreMinimal.h"
#include "CompositionOps/CurveSweepOp.h"
#include "InteractiveToolManager.h"
#include "Mechanics/ConstructionPlaneMechanic.h"
#include "Selection/PolygonSelectionMechanic.h"
#include "GroupTopology.h"
#include "ToolBuilderUtil.h"
#include "Selection/ToolSelectionUtil.h"
#include "ToolSceneQueriesUtil.h"
#include "ToolSetupUtil.h"
#include "TargetInterfaces/PrimitiveComponentBackedTarget.h"
#include "ExplicitUseGeometryMathTypes.h" // using UE::Geometry::(math types)
using namespace UE::Geometry;
#define LOCTEXT_NAMESPACE "URevolveBoundaryTool"
// Tool builder
USingleSelectionMeshEditingTool* URevolveBoundaryToolBuilder::CreateNewTool(const FToolBuilderState& SceneState) const
{
return NewObject<URevolveBoundaryTool>(SceneState.ToolManager);
}
// Operator factory
TUniquePtr<FDynamicMeshOperator> URevolveBoundaryOperatorFactory::MakeNewOperator()
{
TUniquePtr<FCurveSweepOp> CurveSweepOp = MakeUnique<FCurveSweepOp>();
// Assemble profile curve
const FGroupTopologySelection& ActiveSelection = RevolveBoundaryTool->SelectionMechanic->GetActiveSelection();
if (ActiveSelection.SelectedEdgeIDs.Num() == 1)
{
int32 EdgeID = ActiveSelection.GetASelectedEdgeID();
if (RevolveBoundaryTool->Topology->IsBoundaryEdge(EdgeID))
{
const TArray<int32>& VertexIndices = RevolveBoundaryTool->Topology->GetGroupEdgeVertices(EdgeID);
FTransform ToWorld = Cast<IPrimitiveComponentBackedTarget>(RevolveBoundaryTool->Target)->GetWorldTransform();
// Boundary loop includes the last vertex twice, so stop early.
CurveSweepOp->ProfileCurve.Reserve(VertexIndices.Num() - 1);
for (int32 i = 0; i < VertexIndices.Num()-1; ++i)
{
int32 VertIndex = VertexIndices[i];
FVector3d NewPos = (FVector3d)ToWorld.TransformPosition((FVector)RevolveBoundaryTool->OriginalMesh->GetVertex(VertIndex));
CurveSweepOp->ProfileCurve.Add(NewPos);
}
CurveSweepOp->bProfileCurveIsClosed = true;
}
}
RevolveBoundaryTool->Settings->ApplyToCurveSweepOp(*RevolveBoundaryTool->MaterialProperties,
RevolveBoundaryTool->RevolutionAxisOrigin, RevolveBoundaryTool->RevolutionAxisDirection, *CurveSweepOp);
return CurveSweepOp;
}
// Tool itself
void URevolveBoundaryTool::Setup()
{
UMeshBoundaryToolBase::Setup();
// We're actually going to handle the selection clicks ourselves so that we can align axis if
// we want to.
SelectionMechanic->DisableBehaviors(this);
SelectionMechanic->SetShouldAddToSelectionFunc([]() {return false; });
SelectionMechanic->SetShouldRemoveFromSelectionFunc([]() {return false; });
USingleClickInputBehavior* ClickBehavior = NewObject<USingleClickInputBehavior>();
ClickBehavior->Initialize(this);
ClickBehavior->Modifiers.RegisterModifier(AlignAxisModifier, FInputDeviceState::IsCtrlKeyDown);
AddInputBehavior(ClickBehavior);
Settings = NewObject<URevolveBoundaryToolProperties>(this);
Settings->RestoreProperties(this);
AddToolPropertySource(Settings);
MaterialProperties = NewObject<UNewMeshMaterialProperties>(this);
AddToolPropertySource(MaterialProperties);
MaterialProperties->RestoreProperties(this);
UpdateRevolutionAxis();
// The plane mechanic is used for the revolution axis
PlaneMechanic = NewObject<UConstructionPlaneMechanic>(this);
PlaneMechanic->Setup(this);
PlaneMechanic->Initialize(TargetWorld, FFrame3d(Settings->AxisOrigin,
FRotator(Settings->AxisPitch, Settings->AxisYaw, 0).Quaternion()));
PlaneMechanic->UpdateClickPriority(ClickBehavior->GetPriority().MakeLower());
PlaneMechanic->bShowGrid = false;
PlaneMechanic->OnPlaneChanged.AddLambda([this]() {
Settings->AxisOrigin = (FVector)PlaneMechanic->Plane.Origin;
FRotator AxisOrientation = ((FQuat)PlaneMechanic->Plane.Rotation).Rotator();
Settings->AxisPitch = AxisOrientation.Pitch;
Settings->AxisYaw = AxisOrientation.Yaw;
UpdateRevolutionAxis();
});
PlaneMechanic->SetEnableGridSnaping(Settings->bSnapToWorldGrid);
Cast<IPrimitiveComponentBackedTarget>(Target)->SetOwnerVisibility(Settings->bDisplayOriginalMesh);
SetToolDisplayName(LOCTEXT("ToolName", "Revolve Boundary"));
GetToolManager()->DisplayMessage(
LOCTEXT("OnStartRevolveBoundaryTool", "Revolve an open mesh boundary loop around an axis to create a new mesh. Ctrl+click will reposition the revolution axis, potentially aligning it with an edge."),
EToolMessageLevel::UserNotification);
if (Topology->Edges.Num() == 1)
{
FGroupTopologySelection Selection;
Selection.SelectedEdgeIDs.Add(0);
SelectionMechanic->SetSelection(Selection);
StartPreview();
}
else if (Topology->Edges.Num() == 0)
{
GetToolManager()->DisplayMessage(
LOCTEXT("NoBoundaryLoops", "This mesh does not have any boundary loops to display and revolve. Delete some faces or use a different mesh."),
EToolMessageLevel::UserWarning);
}
else
{
GetToolManager()->DisplayMessage(
LOCTEXT("OnStartRevolveBoundaryToolMultipleBoundaries", "Your mesh has multiple boundaries- Click the one you wish to use"),
EToolMessageLevel::UserWarning);
}
}
void URevolveBoundaryTool::OnUpdateModifierState(int ModifierId, bool bIsOn)
{
if (ModifierId == AlignAxisModifier)
{
bAlignAxisOnClick = bIsOn;
}
}
FInputRayHit URevolveBoundaryTool::IsHitByClick(const FInputDeviceRay& ClickPos)
{
FHitResult OutHit;
if (SelectionMechanic->TopologyHitTest(ClickPos.WorldRay, OutHit))
{
return FInputRayHit(OutHit.Distance);
}
return FInputRayHit(); // bHit is false
}
void URevolveBoundaryTool::OnClicked(const FInputDeviceRay& ClickPos)
{
// Update selection only if we clicked on something. We don't want to be able to
// clear a selection with a click.
FHitResult HitResult;
if (SelectionMechanic->TopologyHitTest(ClickPos.WorldRay, HitResult))
{
FVector3d LocalHitPosition, LocalHitNormal;
SelectionMechanic->UpdateSelection(ClickPos.WorldRay, LocalHitPosition, LocalHitNormal);
// Clear the "multiple boundaries" warning, since we've selected one.
GetToolManager()->DisplayMessage(FText(), EToolMessageLevel::UserWarning);
// If Ctrl is pressed, we also want to align the revolution axis to the edge that we clicked
if (bAlignAxisOnClick)
{
const FGroupTopologySelection& Selection = SelectionMechanic->GetActiveSelection();
int32 ClickedEid = Topology->GetGroupEdgeEdges(Selection.GetASelectedEdgeID())[HitResult.Item];
FVector3d VertexA, VertexB;
OriginalMesh->GetEdgeV(ClickedEid, VertexA, VertexB);
FTransform ToWorldTranform = Cast<IPrimitiveComponentBackedTarget>(Target)->GetWorldTransform();
FLine3d EdgeLine = FLine3d::FromPoints((FVector3d)ToWorldTranform.TransformPosition((FVector)VertexA),
(FVector3d)ToWorldTranform.TransformPosition((FVector)VertexB));
FFrame3d RevolutionAxisFrame;
RevolutionAxisFrame.Origin = EdgeLine.NearestPoint((FVector3d)HitResult.ImpactPoint);
RevolutionAxisFrame.AlignAxis(0, EdgeLine.Direction);
PlaneMechanic->SetPlaneWithoutBroadcast(RevolutionAxisFrame);
Settings->AxisOrigin = (FVector)RevolutionAxisFrame.Origin;
FRotator AxisOrientation = ((FQuat)RevolutionAxisFrame.Rotation).Rotator();
Settings->AxisPitch = AxisOrientation.Pitch;
Settings->AxisYaw = AxisOrientation.Yaw;
UpdateRevolutionAxis();
}
// Update the preview
if (Preview == nullptr)
{
StartPreview();
}
else
{
Preview->InvalidateResult();
}
}
}
bool URevolveBoundaryTool::CanAccept() const
{
return Preview != nullptr && Preview->HaveValidNonEmptyResult();
}
/**
* Uses the settings stored in the properties object to update the revolution axis
*/
void URevolveBoundaryTool::UpdateRevolutionAxis()
{
RevolutionAxisOrigin = (FVector3d)Settings->AxisOrigin;
RevolutionAxisDirection = (FVector3d)FRotator(Settings->AxisPitch, Settings->AxisYaw, 0).RotateVector(FVector(1, 0, 0));
if (Preview)
{
Preview->InvalidateResult();
}
}
void URevolveBoundaryTool::StartPreview()
{
URevolveBoundaryOperatorFactory* RevolveBoundaryOpCreator = NewObject<URevolveBoundaryOperatorFactory>();
RevolveBoundaryOpCreator->RevolveBoundaryTool = this;
Preview = NewObject<UMeshOpPreviewWithBackgroundCompute>(RevolveBoundaryOpCreator);
Preview->Setup(TargetWorld, RevolveBoundaryOpCreator);
Preview->PreviewMesh->SetTangentsMode(EDynamicMeshComponentTangentsMode::AutoCalculated);
Preview->ConfigureMaterials(MaterialProperties->Material.Get(),
ToolSetupUtil::GetDefaultWorkingMaterial(GetToolManager()));
Preview->PreviewMesh->EnableWireframe(MaterialProperties->bWireframe);
Preview->OnMeshUpdated.AddLambda(
[this](const UMeshOpPreviewWithBackgroundCompute* UpdatedPreview)
{
UpdateAcceptWarnings(UpdatedPreview->HaveEmptyResult() ? EAcceptWarning::EmptyForbidden : EAcceptWarning::NoWarning);
}
);
Preview->SetVisibility(true);
Preview->InvalidateResult();
}
void URevolveBoundaryTool::Shutdown(EToolShutdownType ShutdownType)
{
UMeshBoundaryToolBase::Shutdown(ShutdownType);
Settings->SaveProperties(this);
MaterialProperties->SaveProperties(this);
PlaneMechanic->Shutdown();
Cast<IPrimitiveComponentBackedTarget>(Target)->SetOwnerVisibility(true);
if (Preview)
{
if (ShutdownType == EToolShutdownType::Accept)
{
GenerateAsset(Preview->Shutdown());
}
else
{
Preview->Cancel();
}
}
}
void URevolveBoundaryTool::GenerateAsset(const FDynamicMeshOpResult& OpResult)
{
if (OpResult.Mesh->TriangleCount() <= 0)
{
return;
}
GetToolManager()->BeginUndoTransaction(LOCTEXT("RevolveBoundaryToolTransactionName", "Revolve Tool"));
FCreateMeshObjectParams NewMeshObjectParams;
NewMeshObjectParams.TargetWorld = TargetWorld;
NewMeshObjectParams.Transform = (FTransform)OpResult.Transform;
NewMeshObjectParams.BaseName = TEXT("Revolve");
NewMeshObjectParams.Materials.Add(MaterialProperties->Material.Get());
NewMeshObjectParams.SetMesh(OpResult.Mesh.Get());
FCreateMeshObjectResult Result = UE::Modeling::CreateMeshObject(GetToolManager(), MoveTemp(NewMeshObjectParams));
if (Result.IsOK() && Result.NewActor != nullptr)
{
ToolSelectionUtil::SetNewActorSelection(GetToolManager(), Result.NewActor);
}
GetToolManager()->EndUndoTransaction();
}
void URevolveBoundaryTool::OnTick(float DeltaTime)
{
if (PlaneMechanic != nullptr)
{
PlaneMechanic->Tick(DeltaTime);
}
if (Preview)
{
Preview->Tick(DeltaTime);
}
}
void URevolveBoundaryTool::Render(IToolsContextRenderAPI* RenderAPI)
{
UMeshBoundaryToolBase::Render(RenderAPI);
FViewCameraState CameraState;
GetToolManager()->GetContextQueriesAPI()->GetCurrentViewState(CameraState);
if (PlaneMechanic != nullptr)
{
PlaneMechanic->Render(RenderAPI);
// Draw the axis of rotation
float PdiScale = CameraState.GetPDIScalingFactor();
FPrimitiveDrawInterface* PDI = RenderAPI->GetPrimitiveDrawInterface();
FColor AxisColor(240, 16, 240);
double AxisThickness = 1 * PdiScale;
double AxisHalfLength = ToolSceneQueriesUtil::CalculateDimensionFromVisualAngleD(CameraState, RevolutionAxisOrigin, 90);
FVector3d StartPoint = RevolutionAxisOrigin - (RevolutionAxisDirection * (AxisHalfLength * PdiScale));
FVector3d EndPoint = RevolutionAxisOrigin + (RevolutionAxisDirection * (AxisHalfLength * PdiScale));
PDI->DrawLine((FVector)StartPoint, (FVector)EndPoint, AxisColor, SDPG_Foreground,
AxisThickness, 0.0f, true);
}
}
void URevolveBoundaryTool::OnPropertyModified(UObject* PropertySet, FProperty* Property)
{
PlaneMechanic->SetPlaneWithoutBroadcast(FFrame3d(Settings->AxisOrigin,
FRotator(Settings->AxisPitch, Settings->AxisYaw, 0).Quaternion()));
UpdateRevolutionAxis();
Cast<IPrimitiveComponentBackedTarget>(Target)->SetOwnerVisibility(Settings->bDisplayOriginalMesh);
PlaneMechanic->SetEnableGridSnaping(Settings->bSnapToWorldGrid);
if (Preview)
{
if (Property && (Property->GetFName() == GET_MEMBER_NAME_CHECKED(UNewMeshMaterialProperties, Material)))
{
Preview->ConfigureMaterials(MaterialProperties->Material.Get(),
ToolSetupUtil::GetDefaultWorkingMaterial(GetToolManager()));
}
Preview->PreviewMesh->EnableWireframe(MaterialProperties->bWireframe);
Preview->InvalidateResult();
}
}
#undef LOCTEXT_NAMESPACE