1348 lines
44 KiB
C++
1348 lines
44 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "DrawPolygonTool.h"
|
|
#include "InteractiveToolManager.h"
|
|
#include "InteractiveGizmoManager.h"
|
|
#include "ToolBuilderUtil.h"
|
|
#include "ToolSetupUtil.h"
|
|
#include "BaseBehaviors/MultiClickSequenceInputBehavior.h"
|
|
#include "BaseBehaviors/KeyAsModifierInputBehavior.h"
|
|
|
|
#include "Polygon2.h"
|
|
#include "Curve/GeneralPolygon2.h"
|
|
#include "FrameTypes.h"
|
|
#include "MatrixTypes.h"
|
|
|
|
#include "Generators/FlatTriangulationMeshGenerator.h"
|
|
#include "Generators/DiscMeshGenerator.h"
|
|
#include "Generators/RectangleMeshGenerator.h"
|
|
#include "Operations/ExtrudeMesh.h"
|
|
#include "Distance/DistLine3Ray3.h"
|
|
#include "Intersection/IntrSegment2Segment2.h"
|
|
#include "ToolSceneQueriesUtil.h"
|
|
#include "PrimitiveDrawingUtils.h" // FPrimitiveDrawInterface
|
|
#include "SceneQueries/SceneSnappingManager.h"
|
|
#include "ConstrainedDelaunay2.h"
|
|
#include "Arrangement2d.h"
|
|
#include "DynamicMesh/MeshTangents.h"
|
|
|
|
#include "DynamicMeshEditor.h"
|
|
|
|
#include "BaseGizmos/GizmoComponents.h"
|
|
#include "BaseGizmos/TransformGizmoUtil.h"
|
|
#include "Drawing/MeshDebugDrawing.h"
|
|
|
|
#include "Selection/SelectClickedAction.h"
|
|
#include "Selection/StoredMeshSelectionUtil.h"
|
|
#include "Selection/ToolSelectionUtil.h"
|
|
#include "ModelingObjectsCreationAPI.h"
|
|
#include "Mechanics/ConstructionPlaneMechanic.h"
|
|
|
|
#include "Mechanics/DragAlignmentMechanic.h"
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(DrawPolygonTool)
|
|
|
|
using namespace UE::Geometry;
|
|
|
|
#define LOCTEXT_NAMESPACE "UDrawPolygonTool"
|
|
|
|
/*
|
|
* ToolBuilder
|
|
*/
|
|
constexpr int StartPointSnapID = FPointPlanarSnapSolver::BaseExternalPointID + 1;
|
|
constexpr int CurrentSceneSnapID = FPointPlanarSnapSolver::BaseExternalPointID + 2;
|
|
constexpr int CurrentGridSnapID = FPointPlanarSnapSolver::BaseExternalPointID + 3;
|
|
|
|
bool UDrawPolygonToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
UInteractiveTool* UDrawPolygonToolBuilder::BuildTool(const FToolBuilderState& SceneState) const
|
|
{
|
|
UDrawPolygonTool* NewTool = NewObject<UDrawPolygonTool>(SceneState.ToolManager);
|
|
NewTool->SetWorld(SceneState.World);
|
|
NewTool->SetInitialDrawFrame(ToolSetupUtil::GetDefaultWorldReferenceFrame(SceneState.ToolManager));
|
|
return NewTool;
|
|
}
|
|
|
|
/*
|
|
* Properties
|
|
*/
|
|
UDrawPolygonToolStandardProperties::UDrawPolygonToolStandardProperties()
|
|
{
|
|
}
|
|
|
|
/*
|
|
* Tool
|
|
*/
|
|
UDrawPolygonTool::UDrawPolygonTool()
|
|
{
|
|
bInInteractiveExtrude = false;
|
|
UInteractiveTool::SetToolDisplayName(LOCTEXT("ToolName", "Polygon Extrude"));
|
|
}
|
|
|
|
void UDrawPolygonTool::SetWorld(UWorld* World)
|
|
{
|
|
this->TargetWorld = World;
|
|
}
|
|
|
|
void UDrawPolygonTool::Setup()
|
|
{
|
|
UInteractiveTool::Setup();
|
|
|
|
// add default button input behaviors for devices
|
|
UMultiClickSequenceInputBehavior* MouseBehavior = NewObject<UMultiClickSequenceInputBehavior>();
|
|
MouseBehavior->Initialize(this);
|
|
MouseBehavior->Modifiers.RegisterModifier(IgnoreSnappingModifier, FInputDeviceState::IsShiftKeyDown);
|
|
AddInputBehavior(MouseBehavior);
|
|
|
|
OutputTypeProperties = NewObject<UCreateMeshObjectTypeProperties>(this);
|
|
OutputTypeProperties->RestoreProperties(this);
|
|
OutputTypeProperties->InitializeDefault();
|
|
OutputTypeProperties->WatchProperty(OutputTypeProperties->OutputType, [this](FString) { OutputTypeProperties->UpdatePropertyVisibility(); });
|
|
|
|
PolygonProperties = NewObject<UDrawPolygonToolStandardProperties>(this);
|
|
PolygonProperties->RestoreProperties(this);
|
|
PolygonProperties->WatchProperty(PolygonProperties->bShowGridGizmo,
|
|
[this](bool bNewValue) { PlaneMechanic->PlaneTransformGizmo->SetVisibility(bNewValue); });
|
|
|
|
PlaneMechanic = NewObject<UConstructionPlaneMechanic>(this);
|
|
PlaneMechanic->Setup(this);
|
|
PlaneMechanic->CanUpdatePlaneFunc = [this]() { return AllowDrawPlaneUpdates(); };
|
|
PlaneMechanic->OnPlaneChanged.AddLambda([this]() { SnapEngine.Plane = PlaneMechanic->Plane; }); // Keep SnapEngine plane up to date with PlaneMechanic's plane
|
|
PlaneMechanic->Initialize(TargetWorld, InitialDrawFrame);
|
|
|
|
DragAlignmentMechanic = NewObject<UDragAlignmentMechanic>(this);
|
|
DragAlignmentMechanic->Setup(this);
|
|
DragAlignmentMechanic->AddToGizmo(PlaneMechanic->PlaneTransformGizmo);
|
|
|
|
// initialize material properties for new objects
|
|
MaterialProperties = NewObject<UNewMeshMaterialProperties>(this);
|
|
MaterialProperties->RestoreProperties(this);
|
|
MaterialProperties->bShowExtendedOptions = true;
|
|
|
|
// create preview mesh object
|
|
PreviewMesh = NewObject<UPreviewMesh>(this);
|
|
PreviewMesh->CreateInWorld(this->TargetWorld, FTransform::Identity);
|
|
ToolSetupUtil::ApplyRenderingConfigurationToPreview(PreviewMesh, nullptr);
|
|
PreviewMesh->SetVisible(false);
|
|
{
|
|
UMaterialInterface* Material = nullptr;
|
|
if ( MaterialProperties->Material.IsValid() )
|
|
{
|
|
Material = MaterialProperties->Material.Get();
|
|
}
|
|
PreviewMesh->SetMaterial(Material);
|
|
}
|
|
bPreviewUpdatePending = false;
|
|
|
|
// initialize snapping engine and properties
|
|
SnapEngine.SnapMetricTolerance = ToolSceneQueriesUtil::GetDefaultVisualAngleSnapThreshD();
|
|
SnapEngine.SnapMetricFunc = [this](const FVector3d& Position1, const FVector3d& Position2) {
|
|
return ToolSceneQueriesUtil::CalculateNormalizedViewVisualAngleD(this->CameraState, Position1, Position2);
|
|
};
|
|
SnapEngine.Plane = PlaneMechanic->Plane;
|
|
|
|
SnapProperties = NewObject<UDrawPolygonToolSnapProperties>(this);
|
|
SnapProperties->RestoreProperties(this);
|
|
SnapProperties->bSnapToWorldGrid = GetToolManager()->GetContextQueriesAPI()
|
|
->GetCurrentSnappingSettings().bEnablePositionGridSnapping;
|
|
|
|
// register tool properties
|
|
if (OutputTypeProperties->ShouldShowPropertySet())
|
|
{
|
|
AddToolPropertySource(OutputTypeProperties);
|
|
}
|
|
AddToolPropertySource(PolygonProperties);
|
|
AddToolPropertySource(SnapProperties);
|
|
AddToolPropertySource(MaterialProperties);
|
|
|
|
ShowStartupMessage();
|
|
}
|
|
|
|
|
|
void UDrawPolygonTool::Shutdown(EToolShutdownType ShutdownType)
|
|
{
|
|
if (bHasSavedExtrudeHeight)
|
|
{
|
|
PolygonProperties->ExtrudeHeight = SavedExtrudeHeight;
|
|
bHasSavedExtrudeHeight = false;
|
|
}
|
|
|
|
PreviewMesh->Disconnect();
|
|
PreviewMesh = nullptr;
|
|
|
|
DragAlignmentMechanic->Shutdown();
|
|
|
|
PlaneMechanic->Shutdown();
|
|
PlaneMechanic = nullptr;
|
|
|
|
GetToolManager()->GetPairedGizmoManager()->DestroyAllGizmosByOwner(this);
|
|
|
|
OutputTypeProperties->SaveProperties(this);
|
|
PolygonProperties->SaveProperties(this);
|
|
SnapProperties->SaveProperties(this);
|
|
MaterialProperties->SaveProperties(this);
|
|
}
|
|
|
|
void UDrawPolygonTool::RegisterActions(FInteractiveToolActionSet& ActionSet)
|
|
{
|
|
// This is an uncommon hotkey to want, and can make tool seem broken if accidentally pressed. The only
|
|
// reason a user might want it is if the gizmo is in the way of their drawing, in which case they could
|
|
// presumably translate it in the plane... Still, allow it to be settable if the user wants.
|
|
ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 2,
|
|
TEXT("ToggleGizmoVisibility"),
|
|
LOCTEXT("ToggleGizmoVisibilityUIName", "Toggle Gizmo"),
|
|
LOCTEXT("ToggleGizmoVisibilityTooltip", "Toggle visibility of the transformation gizmo"),
|
|
EModifierKey::None, EKeys::Invalid,
|
|
[this]() { PolygonProperties->bShowGridGizmo = !PolygonProperties->bShowGridGizmo; });
|
|
}
|
|
|
|
|
|
|
|
void UDrawPolygonTool::ApplyUndoPoints(const TArray<FVector3d>& ClickPointsIn, const TArray<FVector3d>& PolygonVerticesIn)
|
|
{
|
|
if (bInInteractiveExtrude || PolygonVertices.Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bHaveSelfIntersection = false;
|
|
|
|
if (bInFixedPolygonMode == false)
|
|
{
|
|
PolygonVertices = PolygonVerticesIn;
|
|
if ( PolygonVertices.Num() == 0 )
|
|
{
|
|
bAbortActivePolygonDraw = true;
|
|
CurrentCurveTimestamp++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FixedPolygonClickPoints = ClickPointsIn;
|
|
if ( FixedPolygonClickPoints.Num() == 0 )
|
|
{
|
|
bAbortActivePolygonDraw = true;
|
|
CurrentCurveTimestamp++;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UDrawPolygonTool::IsInputSelectionValidOnOutput()
|
|
{
|
|
return bRestoreInputSelection;
|
|
}
|
|
|
|
|
|
void UDrawPolygonTool::OnTick(float DeltaTime)
|
|
{
|
|
if (SnapProperties)
|
|
{
|
|
bool bSnappingEnabledInViewport = GetToolManager()->GetContextQueriesAPI()
|
|
->GetCurrentSnappingSettings().bEnablePositionGridSnapping;
|
|
if (SnapProperties->bSnapToWorldGrid != bSnappingEnabledInViewport)
|
|
{
|
|
SnapProperties->bSnapToWorldGrid = bSnappingEnabledInViewport;
|
|
NotifyOfPropertyChangeByTool(SnapProperties);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void DrawEdgeTicks(FPrimitiveDrawInterface* PDI,
|
|
const FSegment3d& Segment, float Height,
|
|
const FVector3d& PlaneNormal,
|
|
const FLinearColor& Color, uint8 DepthPriorityGroup, float LineThickness, bool bIsScreenSpace)
|
|
{
|
|
FVector3d Center = Segment.Center;
|
|
FVector3d X = Segment.Direction;
|
|
FVector3d Y = X.Cross(PlaneNormal);
|
|
UE::Geometry::Normalize(Y);
|
|
FVector3d A = Center - Height * 0.25*X - Height * Y;
|
|
FVector3d B = Center + Height * 0.25*X + Height * Y;
|
|
PDI->DrawLine((FVector)A, (FVector)B, Color, DepthPriorityGroup, LineThickness, 0.0f, bIsScreenSpace);
|
|
A += Height * 0.5*X;
|
|
B += Height * 0.5*X;
|
|
PDI->DrawLine((FVector)A, (FVector)B, Color, DepthPriorityGroup, LineThickness, 0.0f, bIsScreenSpace);
|
|
}
|
|
|
|
void UDrawPolygonTool::Render(IToolsContextRenderAPI* RenderAPI)
|
|
{
|
|
FPrimitiveDrawInterface* PDI = RenderAPI->GetPrimitiveDrawInterface();
|
|
// Cache here for usage during interaction, should probably happen in ::Tick() or elsewhere
|
|
GetToolManager()->GetContextQueriesAPI()->GetCurrentViewState(CameraState);
|
|
|
|
FViewCameraState RenderCameraState = RenderAPI->GetCameraState();
|
|
float PDIScale = RenderCameraState.GetPDIScalingFactor();
|
|
|
|
if (bPreviewUpdatePending)
|
|
{
|
|
UpdateLivePreview();
|
|
bPreviewUpdatePending = false;
|
|
}
|
|
|
|
double CurViewSizeFactor = ToolSceneQueriesUtil::CalculateDimensionFromVisualAngleD(RenderCameraState, PreviewVertex, 1.0);
|
|
|
|
FColor PreviewColor = FColor::Green;
|
|
FColor OpenPolygonColor = FColor::Orange;
|
|
FColor ClosedPolygonColor = FColor::Yellow;
|
|
FColor ErrorColor = FColor::Magenta;
|
|
float HiddenLineThickness = 1.0f*PDIScale;
|
|
float LineThickness = 4.0f*PDIScale;
|
|
FColor SnapLineColor = FColor::Yellow;
|
|
FColor SnapHighlightColor = SnapLineColor;
|
|
float ElementSize = CurViewSizeFactor;
|
|
|
|
bool bIsClosed = bInInteractiveExtrude
|
|
|| (SnapEngine.HaveActiveSnap() && SnapEngine.GetActiveSnapTargetID() == StartPointSnapID);
|
|
|
|
//
|
|
// Render the plane mechanic after correctly setting bShowGrid
|
|
//
|
|
PlaneMechanic->bShowGrid = !bInInteractiveExtrude;
|
|
PlaneMechanic->Render(RenderAPI);
|
|
|
|
//
|
|
// Generate the fixed polygon contour
|
|
//
|
|
if ((bInFixedPolygonMode) && (FixedPolygonClickPoints.Num() > 0))
|
|
{
|
|
TArray<FVector3d> PreviewClickPoints = FixedPolygonClickPoints;
|
|
if ( !bInInteractiveExtrude ){ PreviewClickPoints.Add(PreviewVertex); }
|
|
GenerateFixedPolygon(PreviewClickPoints, PolygonVertices, PolygonHolesVertices);
|
|
}
|
|
bIsClosed |= bInFixedPolygonMode;
|
|
|
|
int NumVerts = PolygonVertices.Num();
|
|
|
|
//
|
|
// Render snap indicators
|
|
//
|
|
if (SnapEngine.HaveActiveSnap())
|
|
{
|
|
PDI->DrawPoint((FVector)SnapEngine.GetActiveSnapToPoint(), ClosedPolygonColor, 10.0f*PDIScale, SDPG_Foreground);
|
|
|
|
PDI->DrawPoint((FVector)SnapEngine.GetActiveSnapFromPoint(), SnapHighlightColor, 15.0f*PDIScale, SDPG_Foreground);
|
|
PDI->DrawLine((FVector)SnapEngine.GetActiveSnapToPoint(), (FVector)SnapEngine.GetActiveSnapFromPoint(),
|
|
ClosedPolygonColor, SDPG_Foreground, 0.5f*PDIScale, 0.0f, true);
|
|
if (SnapEngine.GetActiveSnapTargetID() == CurrentSceneSnapID)
|
|
{
|
|
if (LastSnapGeometry.PointCount == 1) {
|
|
DrawCircle(PDI, (FVector)LastSnapGeometry.Points[0], RenderCameraState.Right(), RenderCameraState.Up(),
|
|
SnapHighlightColor, ElementSize, 32, SDPG_Foreground, 1.0f*PDIScale, 0.0f, true);
|
|
}
|
|
else
|
|
{
|
|
PDI->DrawLine((FVector)LastSnapGeometry.Points[0], (FVector)LastSnapGeometry.Points[1],
|
|
SnapHighlightColor, SDPG_Foreground, 1.0f*PDIScale, 0.0f, true);
|
|
}
|
|
}
|
|
else if (SnapEngine.GetActiveSnapTargetID() == CurrentGridSnapID)
|
|
{
|
|
DrawCircle(PDI, (FVector)LastGridSnapPoint, RenderCameraState.Right(), RenderCameraState.Up(),
|
|
SnapHighlightColor, ElementSize, 4, SDPG_Foreground, 1.0f*PDIScale, 0.0f, true);
|
|
}
|
|
|
|
if (SnapEngine.HaveActiveSnapLine())
|
|
{
|
|
// clip this line to the view plane because if it goes through the view plane the pixel-line-thickness
|
|
// calculation appears to fail
|
|
FLine3d DrawSnapLine = SnapEngine.GetActiveSnapLine();
|
|
FVector3d P0(DrawSnapLine.PointAt(-99999.0)), P1(DrawSnapLine.PointAt(99999.0)); // should be smarter here...
|
|
if (RenderCameraState.bIsOrthographic == false)
|
|
{
|
|
UE::Geometry::FPlane3d CameraPlane((FVector3d)RenderCameraState.Forward(), (FVector3d)RenderCameraState.Position + 1.0*(FVector3d)RenderCameraState.Forward());
|
|
CameraPlane.ClipSegment(P0, P1);
|
|
}
|
|
PDI->DrawLine((FVector)P0, (FVector)P1, SnapLineColor, SDPG_Foreground, 0.5 * PDIScale, 0.0f, true);
|
|
|
|
if (SnapEngine.HaveActiveSnapDistance())
|
|
{
|
|
int iSegment = SnapEngine.GetActiveSnapDistanceID();
|
|
TArray<FVector3d>& HistoryPoints = (bInFixedPolygonMode) ? FixedPolygonClickPoints : PolygonVertices;
|
|
FVector3d UseNormal = PlaneMechanic->Plane.Rotation.AxisZ();
|
|
DrawEdgeTicks(PDI, FSegment3d(HistoryPoints[iSegment], HistoryPoints[iSegment+1]),
|
|
0.75f*ElementSize, UseNormal, SnapHighlightColor, SDPG_Foreground, 1.0f*PDIScale, true);
|
|
DrawEdgeTicks(PDI, FSegment3d(HistoryPoints[HistoryPoints.Num()-1], PreviewVertex),
|
|
0.75f*ElementSize, UseNormal, SnapHighlightColor, SDPG_Foreground, 1.0f*PDIScale, true);
|
|
// Drawing a highlight
|
|
PDI->DrawLine((FVector)HistoryPoints[iSegment], (FVector)HistoryPoints[iSegment + 1],
|
|
SnapHighlightColor, SDPG_Foreground, 2.0f*PDIScale, 1.0f, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Draw Surface Hit Indicator
|
|
//
|
|
if (bHaveSurfaceHit)
|
|
{
|
|
PDI->DrawPoint((FVector)SurfaceHitPoint, ClosedPolygonColor, 10*PDIScale, SDPG_Foreground);
|
|
if (SnapProperties->SnapToSurfacesOffset != 0)
|
|
{
|
|
PDI->DrawPoint((FVector)SurfaceOffsetPoint, OpenPolygonColor, 15*PDIScale, SDPG_Foreground);
|
|
PDI->DrawLine((FVector)SurfaceOffsetPoint, (FVector)SurfaceHitPoint,
|
|
ClosedPolygonColor, SDPG_Foreground, 0.5f*PDIScale, 0.0f, true);
|
|
}
|
|
PDI->DrawLine((FVector)SurfaceOffsetPoint, (FVector)PreviewVertex,
|
|
ClosedPolygonColor, SDPG_Foreground, 0.5f*PDIScale, 0.0f, true);
|
|
}
|
|
|
|
|
|
//
|
|
// Draw the polygon contour preview
|
|
//
|
|
if (PolygonVertices.Num() > 0)
|
|
{
|
|
FColor UseColor = bIsClosed ? ClosedPolygonColor
|
|
: bHaveSelfIntersection ? ErrorColor : OpenPolygonColor;
|
|
FColor LastSegmentColor = bIsClosed ? ClosedPolygonColor
|
|
: bHaveSelfIntersection ? ErrorColor : PreviewColor;
|
|
FVector3d UseLastVertex = bIsClosed ? PolygonVertices[0] : PreviewVertex;
|
|
|
|
auto DrawVertices = [&PDI, &UseColor](const TArray<FVector3d>& Vertices, ESceneDepthPriorityGroup Group, float Thickness)
|
|
{
|
|
for (int lasti = Vertices.Num() - 1, i = 0, NumVertices = Vertices.Num(); i < NumVertices; lasti = i++)
|
|
|
|
{
|
|
PDI->DrawLine((FVector)Vertices[lasti], (FVector)Vertices[i], UseColor, Group, Thickness, 0.0f, true);
|
|
}
|
|
};
|
|
|
|
// draw thin no-depth (x-ray draw)
|
|
//DrawVertices(PolygonVertices, SDPG_Foreground, HiddenLineThickness);
|
|
for (int i = 0; i < NumVerts - 1; ++i)
|
|
{
|
|
PDI->DrawLine((FVector)PolygonVertices[i], (FVector)PolygonVertices[i + 1],
|
|
UseColor, SDPG_Foreground, HiddenLineThickness, 0.0f, true);
|
|
}
|
|
PDI->DrawLine((FVector)PolygonVertices[NumVerts - 1], (FVector)UseLastVertex,
|
|
LastSegmentColor, SDPG_Foreground, HiddenLineThickness, 0.0f, true);
|
|
for (int HoleIdx = 0; HoleIdx < PolygonHolesVertices.Num(); HoleIdx++)
|
|
{
|
|
DrawVertices(PolygonHolesVertices[HoleIdx], SDPG_Foreground, HiddenLineThickness);
|
|
}
|
|
|
|
// draw thick depth-tested
|
|
//DrawVertices(PolygonVertices, SDPG_World, LineThickness);
|
|
for (int i = 0; i < NumVerts - 1; ++i)
|
|
{
|
|
PDI->DrawLine((FVector)PolygonVertices[i], (FVector)PolygonVertices[i + 1],
|
|
UseColor, SDPG_World, LineThickness, 0.0f, true);
|
|
}
|
|
PDI->DrawLine((FVector)PolygonVertices[NumVerts - 1], (FVector)UseLastVertex,
|
|
LastSegmentColor, SDPG_World, LineThickness, 0.0f, true);
|
|
for (int HoleIdx = 0; HoleIdx < PolygonHolesVertices.Num(); HoleIdx++)
|
|
{
|
|
DrawVertices(PolygonHolesVertices[HoleIdx], SDPG_World, LineThickness);
|
|
}
|
|
|
|
// Intersection point
|
|
if (bHaveSelfIntersection && !bInInteractiveExtrude)
|
|
{
|
|
PDI->DrawPoint((FVector)SelfIntersectionPoint, SnapHighlightColor, 12*PDIScale, SDPG_Foreground);
|
|
}
|
|
}
|
|
|
|
// draw preview vertex
|
|
if (!bInInteractiveExtrude)
|
|
{
|
|
PDI->DrawPoint((FVector)PreviewVertex, PreviewColor, 10 * PDIScale, SDPG_Foreground);
|
|
}
|
|
|
|
// draw height preview stuff
|
|
if (bInInteractiveExtrude)
|
|
{
|
|
HeightMechanic->Render(RenderAPI);
|
|
}
|
|
}
|
|
|
|
|
|
void UDrawPolygonTool::ResetPolygon()
|
|
{
|
|
PolygonVertices.Reset();
|
|
PolygonHolesVertices.Reset();
|
|
SnapEngine.Reset();
|
|
bHaveSurfaceHit = false;
|
|
bInFixedPolygonMode = false;
|
|
bHaveSelfIntersection = false;
|
|
CurrentCurveTimestamp++;
|
|
}
|
|
|
|
void UDrawPolygonTool::UpdatePreviewVertex(const FVector3d& PreviewVertexIn)
|
|
{
|
|
PreviewVertex = PreviewVertexIn;
|
|
|
|
// update length and angle
|
|
if (PolygonVertices.Num() > 0)
|
|
{
|
|
const FVector3d LastVertex = PolygonVertices[PolygonVertices.Num() - 1];
|
|
if (bInFixedPolygonMode)
|
|
{
|
|
double FixedDistance = 0; // get a representative distance for the shape, to show to the user
|
|
if (FixedPolygonClickPoints.Num() > 0)
|
|
{
|
|
// Build standard polygon parameters
|
|
TArray<FVector3d> PreviewClickPoints = FixedPolygonClickPoints;
|
|
PreviewClickPoints.Add(PreviewVertex);
|
|
FVector2d FirstReferencePt, BoxSize;
|
|
double YSign, AngleRad;
|
|
GetPolygonParametersFromFixedPoints(PreviewClickPoints, FirstReferencePt, BoxSize, YSign, AngleRad);
|
|
double Width = BoxSize.X, Height = BoxSize.Y;
|
|
// For rectangles, interface uses width for earlier click, height for later
|
|
if (PolygonProperties->PolygonDrawMode == EDrawPolygonDrawMode::Rectangle || PolygonProperties->PolygonDrawMode == EDrawPolygonDrawMode::RoundedRectangle)
|
|
{
|
|
if (PreviewClickPoints.Num() == 2)
|
|
{
|
|
FixedDistance = Width;
|
|
}
|
|
else
|
|
{
|
|
FixedDistance = Height;
|
|
}
|
|
}
|
|
else // For all else (circles, discs and squares), only width is used
|
|
{
|
|
FixedDistance = Width;
|
|
}
|
|
}
|
|
PolygonProperties->Distance = (float)FixedDistance;
|
|
}
|
|
else
|
|
{
|
|
PolygonProperties->Distance = Distance(LastVertex, PreviewVertex);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UDrawPolygonTool::AppendVertex(const FVector3d& Vertex)
|
|
{
|
|
PolygonVertices.Add(Vertex);
|
|
}
|
|
|
|
bool UDrawPolygonTool::FindDrawPlaneHitPoint(const FInputDeviceRay& ClickPos, FVector3d& HitPosOut)
|
|
{
|
|
bHaveSurfaceHit = false;
|
|
|
|
const FFrame3d& Frame = PlaneMechanic->Plane;
|
|
FVector3d HitPos;
|
|
FVector3d SceneSnapPos;
|
|
bool bHitPlane = Frame.RayPlaneIntersection((FVector3d)ClickPos.WorldRay.Origin, (FVector3d)ClickPos.WorldRay.Direction, 2, HitPos);
|
|
if (!bHitPlane && (!SnapProperties->bEnableSnapping || bIgnoreSnappingToggle))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// if we found a scene snap point, add to snap set
|
|
if (bIgnoreSnappingToggle || SnapProperties->bEnableSnapping == false)
|
|
{
|
|
// if snapping is disabled, still snap to the first vertex (so the polygon can be closed)
|
|
SnapEngine.Reset();
|
|
if (bInFixedPolygonMode == false && PolygonVertices.Num() > 0)
|
|
{
|
|
SnapEngine.AddPointTarget(PolygonVertices[0], StartPointSnapID, 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Since we do not early out if we do not hit the tool plane when snapping is enabled,
|
|
// HitPos is not guaranteed to be meaningful here unless bHitPlane is true
|
|
FVector3d WorldGridSnapPos;
|
|
if (bHitPlane && ToolSceneQueriesUtil::FindWorldGridSnapPoint(this, HitPos, WorldGridSnapPos))
|
|
{
|
|
WorldGridSnapPos = Frame.ToPlane(WorldGridSnapPos, 2);
|
|
SnapEngine.AddPointTarget(WorldGridSnapPos, CurrentGridSnapID,
|
|
FBasePositionSnapSolver3::FCustomMetric::Replace(999), SnapEngine.MinInternalPriority() - 5);
|
|
LastGridSnapPoint = WorldGridSnapPos;
|
|
}
|
|
|
|
if (SnapProperties->bSnapToVertices || SnapProperties->bSnapToEdges)
|
|
{
|
|
const FVector3d PointOnWorldRay = ClickPos.WorldRay.PointAt(1);
|
|
if (ToolSceneQueriesUtil::FindSceneSnapPoint(this, bHitPlane ? HitPos : PointOnWorldRay, SceneSnapPos, SnapProperties->bSnapToVertices, SnapProperties->bSnapToEdges, 0, &LastSnapGeometry))
|
|
{
|
|
SnapEngine.AddPointTarget(SceneSnapPos, CurrentSceneSnapID, SnapEngine.MinInternalPriority() - 10);
|
|
}
|
|
}
|
|
|
|
const TArray<FVector3d>& HistoryPoints = (bInFixedPolygonMode) ? FixedPolygonClickPoints : PolygonVertices;
|
|
SnapEngine.UpdatePointHistory(HistoryPoints);
|
|
if (SnapProperties->bSnapToAxes)
|
|
{
|
|
SnapEngine.RegenerateTargetLines(true, true);
|
|
}
|
|
SnapEngine.bEnableSnapToKnownLengths = SnapProperties->bSnapToLengths;
|
|
}
|
|
|
|
// ignore snapping to start point unless we have at least 3 vertices
|
|
if (bInFixedPolygonMode == false && PolygonVertices.Num() > 0)
|
|
{
|
|
if (PolygonVertices.Num() < 3)
|
|
{
|
|
SnapEngine.AddIgnoreTarget(StartPointSnapID);
|
|
}
|
|
else
|
|
{
|
|
SnapEngine.RemoveIgnoreTarget(StartPointSnapID);
|
|
}
|
|
}
|
|
|
|
SnapEngine.UpdateSnappedPoint(bHitPlane ? HitPos : SceneSnapPos);
|
|
|
|
// remove scene snap point
|
|
SnapEngine.RemovePointTargetsByID(CurrentSceneSnapID);
|
|
SnapEngine.RemovePointTargetsByID(CurrentGridSnapID);
|
|
|
|
// Success case 1: HitPosOut is set to a snap point.
|
|
if (SnapEngine.HaveActiveSnap())
|
|
{
|
|
HitPosOut = SnapEngine.GetActiveSnapToPoint();
|
|
return true;
|
|
}
|
|
|
|
// if not yet snapped and we want to hit objects, do that
|
|
if (SnapProperties->bEnableSnapping && SnapProperties->bSnapToSurfaces && !bIgnoreSnappingToggle)
|
|
{
|
|
FHitResult Result;
|
|
bool bWorldHit = ToolSceneQueriesUtil::FindNearestVisibleObjectHit(this, Result, ClickPos.WorldRay);
|
|
|
|
// Success case 2: HitPosOut is set to a point on the tool plane by projecting a found point in the world onto the plane.
|
|
if (bWorldHit)
|
|
{
|
|
bHaveSurfaceHit = true;
|
|
SurfaceHitPoint = (FVector3d)Result.ImpactPoint;
|
|
const FVector3d UseHitPos = Result.ImpactPoint + static_cast<double>(SnapProperties->SnapToSurfacesOffset) * Result.Normal;
|
|
HitPos = Frame.ToPlane(UseHitPos, 2);
|
|
SurfaceOffsetPoint = UseHitPos;
|
|
|
|
HitPosOut = HitPos;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Success case 3: HitPosOut is set to a point on the tool plane based on raycast intersection with the plane.
|
|
if (bHitPlane)
|
|
{
|
|
HitPosOut = HitPos;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void UDrawPolygonTool::OnBeginSequencePreview(const FInputDeviceRay& DevicePos)
|
|
{
|
|
// just update snapped point preview
|
|
FVector3d HitPos;
|
|
if (FindDrawPlaneHitPoint(DevicePos, HitPos))
|
|
{
|
|
PreviewVertex = HitPos;
|
|
}
|
|
|
|
}
|
|
|
|
bool UDrawPolygonTool::CanBeginClickSequence(const FInputDeviceRay& ClickPos)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void UDrawPolygonTool::OnBeginClickSequence(const FInputDeviceRay& ClickPos)
|
|
{
|
|
ResetPolygon();
|
|
|
|
FVector3d HitPos;
|
|
bool bHit = FindDrawPlaneHitPoint(ClickPos, HitPos);
|
|
if (bHit == false)
|
|
{
|
|
bAbortActivePolygonDraw = true;
|
|
return;
|
|
}
|
|
if (ToolSceneQueriesUtil::IsPointVisible(CameraState, HitPos) == false)
|
|
{
|
|
bAbortActivePolygonDraw = true;
|
|
return; // cannot start a poly an a point that is not visible, this is almost certainly an error due to draw plane
|
|
}
|
|
|
|
UpdatePreviewVertex(HitPos);
|
|
|
|
bInFixedPolygonMode = (PolygonProperties->PolygonDrawMode != EDrawPolygonDrawMode::Freehand);
|
|
FixedPolygonClickPoints.Reset();
|
|
|
|
// Actually process the click.
|
|
// TODO: This slightly awkward organization is a reflection of an earlier time when
|
|
// MultiClickSequenceInputBehavior issued a duplicate OnNextSequenceClick() call
|
|
// immediately after OnBeginClickSequence(). The code could be cleaned up.
|
|
OnNextSequenceClick(ClickPos);
|
|
}
|
|
|
|
void UDrawPolygonTool::OnNextSequencePreview(const FInputDeviceRay& ClickPos)
|
|
{
|
|
if (bInInteractiveExtrude)
|
|
{
|
|
HeightMechanic->UpdateCurrentDistance(ClickPos.WorldRay);
|
|
PolygonProperties->ExtrudeHeight = HeightMechanic->CurrentHeight;
|
|
bPreviewUpdatePending = true;
|
|
return;
|
|
}
|
|
|
|
FVector3d HitPos;
|
|
bool bHit = FindDrawPlaneHitPoint(ClickPos, HitPos);
|
|
if (bHit == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (bInFixedPolygonMode)
|
|
{
|
|
UpdatePreviewVertex(HitPos);
|
|
bPreviewUpdatePending = true;
|
|
return;
|
|
}
|
|
|
|
UpdatePreviewVertex(HitPos);
|
|
UpdateSelfIntersection();
|
|
if (PolygonVertices.Num() > 2)
|
|
{
|
|
bPreviewUpdatePending = true;
|
|
}
|
|
}
|
|
|
|
bool UDrawPolygonTool::OnNextSequenceClick(const FInputDeviceRay& ClickPos)
|
|
{
|
|
if (bInInteractiveExtrude)
|
|
{
|
|
EndInteractiveExtrude();
|
|
return false;
|
|
}
|
|
|
|
FVector3d HitPos;
|
|
bool bHit = FindDrawPlaneHitPoint(ClickPos, HitPos);
|
|
if (bHit == false)
|
|
{
|
|
return true; // ignore click but continue accepting clicks
|
|
}
|
|
|
|
// Construct the change now for the undo queue so it reflects the current state. We might not do anything, in which
|
|
// case we will not emit the change
|
|
TUniquePtr<FDrawPolygonStateChange> Change =
|
|
MakeUnique<FDrawPolygonStateChange>(CurrentCurveTimestamp, FixedPolygonClickPoints, PolygonVertices);
|
|
|
|
bool bDonePolygon;
|
|
if (bInFixedPolygonMode)
|
|
{
|
|
// ignore very close click points
|
|
if (FixedPolygonClickPoints.Num() > 0 && ToolSceneQueriesUtil::PointSnapQuery(this, FixedPolygonClickPoints[FixedPolygonClickPoints.Num()-1], HitPos) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
FixedPolygonClickPoints.Add(HitPos);
|
|
int NumTargetPoints = (PolygonProperties->PolygonDrawMode == EDrawPolygonDrawMode::Rectangle || PolygonProperties->PolygonDrawMode == EDrawPolygonDrawMode::RoundedRectangle) ? 3 : 2;
|
|
bDonePolygon = (FixedPolygonClickPoints.Num() == NumTargetPoints);
|
|
if (bDonePolygon)
|
|
{
|
|
GenerateFixedPolygon(FixedPolygonClickPoints, PolygonVertices, PolygonHolesVertices);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// ignore very close click points
|
|
if (PolygonVertices.Num() > 0 && ToolSceneQueriesUtil::PointSnapQuery(this, PolygonVertices[PolygonVertices.Num()-1], HitPos))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (bHaveSelfIntersection)
|
|
{
|
|
// If the self-intersection point is coincident with a polygon vertex, don't add that point twice (it would produce a degenerate polygon edge)
|
|
if (SelfIntersectSegmentIdx < PolygonVertices.Num()-1 && FVector3d::PointsAreSame(SelfIntersectionPoint, PolygonVertices[SelfIntersectSegmentIdx+1]))
|
|
{
|
|
++SelfIntersectSegmentIdx;
|
|
}
|
|
|
|
// discard vertex in segments before intersection (this is redundant if idx is 0)
|
|
for (int j = SelfIntersectSegmentIdx; j < PolygonVertices.Num(); ++j)
|
|
{
|
|
PolygonVertices[j-SelfIntersectSegmentIdx] = PolygonVertices[j];
|
|
}
|
|
PolygonVertices.SetNum(PolygonVertices.Num() - SelfIntersectSegmentIdx);
|
|
PolygonVertices[0] = PreviewVertex = SelfIntersectionPoint;
|
|
bDonePolygon = true;
|
|
}
|
|
else
|
|
{
|
|
// close polygon if we clicked on start point
|
|
bDonePolygon = SnapEngine.HaveActiveSnap() && SnapEngine.GetActiveSnapTargetID() == StartPointSnapID;
|
|
}
|
|
}
|
|
|
|
// emit change event
|
|
GetToolManager()->EmitObjectChange(this, MoveTemp(Change), LOCTEXT("DrawPolyAddPoint", "Add Point"));
|
|
|
|
if (bDonePolygon)
|
|
{
|
|
//SnapEngine.Reset();
|
|
bHaveSurfaceHit = false;
|
|
if (PolygonProperties->ExtrudeMode == EDrawPolygonExtrudeMode::Interactive)
|
|
{
|
|
BeginInteractiveExtrude();
|
|
|
|
PreviewMesh->ClearPreview();
|
|
PreviewMesh->SetVisible(true);
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
EmitCurrentPolygon();
|
|
|
|
PreviewMesh->ClearPreview();
|
|
PreviewMesh->SetVisible(false);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
AppendVertex(HitPos);
|
|
|
|
// if we are starting a freehand poly, add start point as snap target.
|
|
// Note that logic in FindDrawPlaneHitPoint will ignore it until we get 3 verts
|
|
if (bInFixedPolygonMode == false && PolygonVertices.Num() == 1)
|
|
{
|
|
SnapEngine.AddPointTarget(PolygonVertices[0], StartPointSnapID, 1);
|
|
}
|
|
|
|
UpdatePreviewVertex(HitPos);
|
|
return true;
|
|
}
|
|
|
|
void UDrawPolygonTool::OnTerminateClickSequence()
|
|
{
|
|
ResetPolygon();
|
|
}
|
|
|
|
bool UDrawPolygonTool::RequestAbortClickSequence()
|
|
{
|
|
if (bAbortActivePolygonDraw)
|
|
{
|
|
bAbortActivePolygonDraw = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void UDrawPolygonTool::OnUpdateModifierState(int ModifierID, bool bIsOn)
|
|
{
|
|
if (ModifierID == IgnoreSnappingModifier)
|
|
{
|
|
bIgnoreSnappingToggle = bIsOn;
|
|
}
|
|
else if (ModifierID == AngleSnapModifier)
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
bool UDrawPolygonTool::UpdateSelfIntersection()
|
|
{
|
|
bHaveSelfIntersection = false;
|
|
if (bInFixedPolygonMode || PolygonProperties->bAllowSelfIntersections == true)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int NumVertices = PolygonVertices.Num();
|
|
if (NumVertices < 3)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const FFrame3d& DrawFrame = PlaneMechanic->Plane;
|
|
FSegment2d PreviewSegment(DrawFrame.ToPlaneUV(PolygonVertices[NumVertices - 1],2), DrawFrame.ToPlaneUV(PreviewVertex,2));
|
|
|
|
double BestIntersectionParameter = FMathd::MaxReal;
|
|
for (int k = 0; k < NumVertices - 2; ++k)
|
|
{
|
|
FSegment2d Segment(DrawFrame.ToPlaneUV(PolygonVertices[k],2), DrawFrame.ToPlaneUV(PolygonVertices[k + 1],2));
|
|
FIntrSegment2Segment2d Intersection(PreviewSegment, Segment);
|
|
if (Intersection.Find())
|
|
{
|
|
bHaveSelfIntersection = true;
|
|
if (Intersection.Parameter0 < BestIntersectionParameter)
|
|
{
|
|
BestIntersectionParameter = Intersection.Parameter0;
|
|
SelfIntersectSegmentIdx = k;
|
|
SelfIntersectionPoint = DrawFrame.FromPlaneUV(Intersection.Point0, 2);
|
|
}
|
|
}
|
|
}
|
|
return bHaveSelfIntersection;
|
|
}
|
|
|
|
void UDrawPolygonTool::GetPolygonParametersFromFixedPoints(const TArray<FVector3d>& FixedPoints, FVector2d& FirstReferencePt, FVector2d& BoxSize, double& YSign, double& AngleRad)
|
|
{
|
|
if (FixedPoints.Num() < 2)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FFrame3d& DrawFrame = PlaneMechanic->Plane;
|
|
FirstReferencePt = DrawFrame.ToPlaneUV(FixedPoints[0], 2);
|
|
|
|
FVector2d EdgePt = DrawFrame.ToPlaneUV(FixedPoints[1], 2);
|
|
FVector2d Delta = EdgePt - FirstReferencePt;
|
|
AngleRad = FMathd::Atan2(Delta.Y, Delta.X);
|
|
|
|
double Radius = Delta.Length();
|
|
FVector2d AxisX = Radius != 0 ? Delta / Radius
|
|
: FVector2d(1,0); // arbitrary if delta was 0 vector
|
|
FVector2d AxisY = -UE::Geometry::PerpCW(AxisX);
|
|
FVector2d HeightPt = DrawFrame.ToPlaneUV((FixedPoints.Num() == 3) ? FixedPoints[2] : FixedPoints[1], 2);
|
|
FVector2d HeightDelta = HeightPt - FirstReferencePt;
|
|
YSign = FMathd::Sign(HeightDelta.Dot(AxisY));
|
|
BoxSize.X = Radius;
|
|
BoxSize.Y = FMathd::Abs(HeightDelta.Dot(AxisY));
|
|
}
|
|
|
|
void UDrawPolygonTool::GenerateFixedPolygon(const TArray<FVector3d>& FixedPoints, TArray<FVector3d>& VerticesOut, TArray<TArray<FVector3d>>& HolesVerticesOut)
|
|
{
|
|
FVector2d FirstReferencePt, BoxSize;
|
|
double YSign, AngleRad;
|
|
GetPolygonParametersFromFixedPoints(FixedPoints, FirstReferencePt, BoxSize, YSign, AngleRad);
|
|
double Width = BoxSize.X, Height = BoxSize.Y;
|
|
FMatrix2d RotationMat = FMatrix2d::RotationRad(AngleRad);
|
|
|
|
FPolygon2d Polygon;
|
|
TArray<FPolygon2d> PolygonHoles;
|
|
if (PolygonProperties->PolygonDrawMode == EDrawPolygonDrawMode::Square)
|
|
{
|
|
Polygon = FPolygon2d::MakeRectangle(FVector2d::Zero(), 2*Width, 2*Width);
|
|
}
|
|
else if (PolygonProperties->PolygonDrawMode == EDrawPolygonDrawMode::Rectangle || PolygonProperties->PolygonDrawMode == EDrawPolygonDrawMode::RoundedRectangle)
|
|
{
|
|
if (PolygonProperties->PolygonDrawMode == EDrawPolygonDrawMode::Rectangle)
|
|
{
|
|
Polygon = FPolygon2d::MakeRectangle(FVector2d(Width / 2, YSign*Height / 2), Width, Height);
|
|
}
|
|
else // PolygonProperties->PolygonDrawMode == EDrawPolygonDrawMode::RoundedRectangle
|
|
{
|
|
Polygon = FPolygon2d::MakeRoundedRectangle(FVector2d(Width / 2, YSign*Height / 2), Width, Height, FMathd::Min(Width,Height) * FMathd::Clamp(PolygonProperties->FeatureSizeRatio, .01, .99) * .5, PolygonProperties->RadialSlices);
|
|
}
|
|
}
|
|
else // Circle or Ring
|
|
{
|
|
Polygon = FPolygon2d::MakeCircle(Width, PolygonProperties->RadialSlices, 0);
|
|
if (PolygonProperties->PolygonDrawMode == EDrawPolygonDrawMode::Ring)
|
|
{
|
|
PolygonHoles.Add(FPolygon2d::MakeCircle(Width * FMathd::Clamp(PolygonProperties->FeatureSizeRatio, .01, .99), PolygonProperties->RadialSlices, 0));
|
|
}
|
|
}
|
|
Polygon.Transform([RotationMat](const FVector2d& Pt) { return RotationMat * Pt; });
|
|
for (FPolygon2d& Hole : PolygonHoles)
|
|
{
|
|
Hole.Transform([RotationMat](const FVector2d& Pt) { return RotationMat * Pt; });
|
|
}
|
|
|
|
const FFrame3d& DrawFrame = PlaneMechanic->Plane;
|
|
VerticesOut.SetNum(Polygon.VertexCount());
|
|
for (int k = 0; k < Polygon.VertexCount(); ++k)
|
|
{
|
|
FVector2d NewPt = FirstReferencePt + Polygon[k];
|
|
VerticesOut[k] = DrawFrame.FromPlaneUV(NewPt, 2);
|
|
}
|
|
|
|
HolesVerticesOut.SetNum(PolygonHoles.Num());
|
|
for (int HoleIdx = 0; HoleIdx < PolygonHoles.Num(); HoleIdx++)
|
|
{
|
|
int NumHoleVerts = PolygonHoles[HoleIdx].VertexCount();
|
|
HolesVerticesOut[HoleIdx].SetNum(NumHoleVerts);
|
|
for (int k = 0; k < NumHoleVerts; ++k)
|
|
{
|
|
FVector2d NewPt = FirstReferencePt + PolygonHoles[HoleIdx][k];
|
|
HolesVerticesOut[HoleIdx][k] = DrawFrame.FromPlaneUV(NewPt, 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void UDrawPolygonTool::BeginInteractiveExtrude()
|
|
{
|
|
bInInteractiveExtrude = true;
|
|
|
|
bHasSavedExtrudeHeight = true;
|
|
SavedExtrudeHeight = PolygonProperties->ExtrudeHeight;
|
|
|
|
SnapEngine.ResetActiveSnap();
|
|
|
|
HeightMechanic = NewObject<UPlaneDistanceFromHitMechanic>(this);
|
|
HeightMechanic->Setup(this);
|
|
|
|
HeightMechanic->WorldHitQueryFunc = [this](const FRay& WorldRay, FHitResult& HitResult)
|
|
{
|
|
if (this->bIgnoreSnappingToggle == false)
|
|
{
|
|
return ToolSceneQueriesUtil::FindNearestVisibleObjectHit(this, HitResult, WorldRay);
|
|
}
|
|
return false;
|
|
};
|
|
HeightMechanic->WorldPointSnapFunc = [this](const FVector3d& WorldPos, FVector3d& SnapPos)
|
|
{
|
|
if (bIgnoreSnappingToggle == false && SnapProperties->bEnableSnapping)
|
|
{
|
|
return ToolSceneQueriesUtil::FindWorldGridSnapPoint(this, WorldPos, SnapPos);
|
|
}
|
|
return false;
|
|
};
|
|
HeightMechanic->CurrentHeight = 1.0f; // initialize to something non-zero...prob should be based on polygon bounds maybe?
|
|
|
|
FDynamicMesh3 HeightMesh;
|
|
FFrame3d WorldMeshFrame;
|
|
GeneratePolygonMesh(PolygonVertices, PolygonHolesVertices, &HeightMesh, WorldMeshFrame, false, 99999, true);
|
|
HeightMechanic->Initialize( MoveTemp(HeightMesh), WorldMeshFrame, false);
|
|
|
|
ShowExtrudeMessage();
|
|
}
|
|
|
|
void UDrawPolygonTool::EndInteractiveExtrude()
|
|
{
|
|
EmitCurrentPolygon();
|
|
|
|
PreviewMesh->ClearPreview();
|
|
PreviewMesh->SetVisible(false);
|
|
|
|
bInInteractiveExtrude = false;
|
|
HeightMechanic = nullptr;
|
|
|
|
ShowStartupMessage();
|
|
}
|
|
|
|
|
|
bool UDrawPolygonTool::AllowDrawPlaneUpdates()
|
|
{
|
|
if (bInInteractiveExtrude)
|
|
{
|
|
return false;
|
|
}
|
|
if (bInFixedPolygonMode)
|
|
{
|
|
return FixedPolygonClickPoints.IsEmpty();
|
|
}
|
|
else
|
|
{
|
|
return PolygonVertices.IsEmpty();
|
|
}
|
|
}
|
|
|
|
|
|
void UDrawPolygonTool::EmitCurrentPolygon()
|
|
{
|
|
FString BaseName = (PolygonProperties->ExtrudeMode == EDrawPolygonExtrudeMode::Flat) ?
|
|
TEXT("Polygon") : TEXT("Extrude");
|
|
|
|
// generate new mesh
|
|
FFrame3d PlaneFrameOut;
|
|
FDynamicMesh3 Mesh;
|
|
const double ExtrudeDist = (PolygonProperties->ExtrudeMode == EDrawPolygonExtrudeMode::Flat) ?
|
|
0 : PolygonProperties->ExtrudeHeight;
|
|
bool bSucceeded = GeneratePolygonMesh(PolygonVertices, PolygonHolesVertices, &Mesh, PlaneFrameOut, false, ExtrudeDist, false);
|
|
if (!bSucceeded) // somehow made a polygon with no valid triangulation; just throw it away ...
|
|
{
|
|
ResetPolygon();
|
|
return;
|
|
}
|
|
UE::Geometry::FMeshTangentsf::ComputeDefaultOverlayTangents(Mesh);
|
|
|
|
GetToolManager()->BeginUndoTransaction(LOCTEXT("CreatePolygon", "Create Polygon"));
|
|
|
|
FCreateMeshObjectParams NewMeshObjectParams;
|
|
NewMeshObjectParams.TargetWorld = TargetWorld;
|
|
NewMeshObjectParams.Transform = PlaneFrameOut.ToFTransform();
|
|
NewMeshObjectParams.BaseName = BaseName;
|
|
NewMeshObjectParams.Materials.Add(MaterialProperties->Material.Get());
|
|
NewMeshObjectParams.SetMesh(&Mesh);
|
|
OutputTypeProperties->ConfigureCreateMeshObjectParams(NewMeshObjectParams);
|
|
FCreateMeshObjectResult Result = UE::Modeling::CreateMeshObject(GetToolManager(), MoveTemp(NewMeshObjectParams));
|
|
if (Result.IsOK() && Result.NewActor != nullptr)
|
|
{
|
|
bRestoreInputSelection = false;
|
|
ToolSelectionUtil::SetNewActorSelection(GetToolManager(), Result.NewActor);
|
|
}
|
|
|
|
GetToolManager()->EndUndoTransaction();
|
|
|
|
if (bHasSavedExtrudeHeight)
|
|
{
|
|
PolygonProperties->ExtrudeHeight = SavedExtrudeHeight;
|
|
bHasSavedExtrudeHeight = false;
|
|
}
|
|
|
|
ResetPolygon();
|
|
}
|
|
|
|
void UDrawPolygonTool::UpdateLivePreview()
|
|
{
|
|
int NumVerts = PolygonVertices.Num();
|
|
if (NumVerts < 2 || PreviewMesh == nullptr || PreviewMesh->IsVisible() == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FFrame3d PlaneFrame;
|
|
FDynamicMesh3 Mesh;
|
|
const double ExtrudeDist = (PolygonProperties->ExtrudeMode == EDrawPolygonExtrudeMode::Flat) ?
|
|
0 : PolygonProperties->ExtrudeHeight;
|
|
if (GeneratePolygonMesh(PolygonVertices, PolygonHolesVertices, &Mesh, PlaneFrame, false, ExtrudeDist, false))
|
|
{
|
|
PreviewMesh->SetTransform(PlaneFrame.ToFTransform());
|
|
PreviewMesh->SetMaterial(MaterialProperties->Material.Get());
|
|
PreviewMesh->EnableWireframe(MaterialProperties->bShowWireframe);
|
|
PreviewMesh->UpdatePreview(&Mesh);
|
|
}
|
|
}
|
|
|
|
bool UDrawPolygonTool::GeneratePolygonMesh(const TArray<FVector3d>& Polygon, const TArray<TArray<FVector3d>>& PolygonHoles, FDynamicMesh3* ResultMeshOut, FFrame3d& WorldFrameOut, bool bIncludePreviewVtx, double ExtrudeDistance, bool bExtrudeSymmetric)
|
|
{
|
|
// construct centered frame for polygon
|
|
WorldFrameOut = PlaneMechanic->Plane;
|
|
|
|
int NumVerts = Polygon.Num();
|
|
FVector3d Centroid3d(0, 0, 0);
|
|
for (int k = 0; k < NumVerts; ++k)
|
|
{
|
|
Centroid3d += Polygon[k];
|
|
}
|
|
Centroid3d /= (double)NumVerts;
|
|
FVector2d CentroidInDrawPlane = WorldFrameOut.ToPlaneUV(Centroid3d);
|
|
WorldFrameOut.Origin = Centroid3d;
|
|
|
|
// Compute outer polygon & bounds
|
|
auto VertexArrayToPolygon = [&WorldFrameOut](const TArray<FVector3d>& Vertices)
|
|
{
|
|
FPolygon2d OutPolygon;
|
|
for (int k = 0, N = Vertices.Num(); k < N; ++k)
|
|
{
|
|
OutPolygon.AppendVertex(WorldFrameOut.ToPlaneUV(Vertices[k], 2));
|
|
}
|
|
return OutPolygon;
|
|
};
|
|
FPolygon2d OuterPolygon = VertexArrayToPolygon(Polygon);
|
|
// add preview vertex
|
|
if (bIncludePreviewVtx)
|
|
{
|
|
if (Distance(PreviewVertex, Polygon[NumVerts-1]) > 0.1)
|
|
{
|
|
OuterPolygon.AppendVertex(WorldFrameOut.ToPlaneUV(PreviewVertex, 2));
|
|
}
|
|
}
|
|
FAxisAlignedBox2d Bounds(OuterPolygon.Bounds());
|
|
|
|
// special case paths
|
|
if (PolygonProperties->PolygonDrawMode == EDrawPolygonDrawMode::Ring || PolygonProperties->PolygonDrawMode == EDrawPolygonDrawMode::Circle || PolygonProperties->PolygonDrawMode == EDrawPolygonDrawMode::RoundedRectangle)
|
|
{
|
|
// get polygon parameters
|
|
FVector2d FirstReferencePt, BoxSize;
|
|
double YSign, AngleRad;
|
|
GetPolygonParametersFromFixedPoints(FixedPolygonClickPoints, FirstReferencePt, BoxSize, YSign, AngleRad);
|
|
FirstReferencePt -= CentroidInDrawPlane;
|
|
FMatrix2d RotationMat = FMatrix2d::RotationRad(AngleRad);
|
|
|
|
// translate general polygon parameters to specific mesh generator parameters, and generate mesh
|
|
if (PolygonProperties->PolygonDrawMode == EDrawPolygonDrawMode::Ring)
|
|
{
|
|
FPuncturedDiscMeshGenerator HCGen;
|
|
HCGen.AngleSamples = PolygonProperties->RadialSlices;
|
|
HCGen.RadialSamples = 1;
|
|
HCGen.Radius = BoxSize.X;
|
|
HCGen.HoleRadius = BoxSize.X * FMathd::Clamp(PolygonProperties->FeatureSizeRatio, .01f, .99f);
|
|
ResultMeshOut->Copy(&HCGen.Generate());
|
|
}
|
|
else if (PolygonProperties->PolygonDrawMode == EDrawPolygonDrawMode::Circle)
|
|
{
|
|
FDiscMeshGenerator CGen;
|
|
CGen.AngleSamples = PolygonProperties->RadialSlices;
|
|
CGen.RadialSamples = 1;
|
|
CGen.Radius = BoxSize.X;
|
|
ResultMeshOut->Copy(&CGen.Generate());
|
|
}
|
|
else if (PolygonProperties->PolygonDrawMode == EDrawPolygonDrawMode::RoundedRectangle)
|
|
{
|
|
FRoundedRectangleMeshGenerator RRGen;
|
|
FirstReferencePt += RotationMat * (FVector2d(BoxSize.X, BoxSize.Y * YSign)*.5f);
|
|
RRGen.AngleSamples = PolygonProperties->RadialSlices;
|
|
RRGen.Radius = .5 * FMathd::Min(BoxSize.X, BoxSize.Y) * FMathd::Clamp(PolygonProperties->FeatureSizeRatio, .01f, .99f);
|
|
RRGen.Height = BoxSize.Y - RRGen.Radius * 2.;
|
|
RRGen.Width = BoxSize.X - RRGen.Radius * 2.;
|
|
RRGen.WidthVertexCount = 1;
|
|
RRGen.HeightVertexCount = 1;
|
|
ResultMeshOut->Copy(&RRGen.Generate());
|
|
}
|
|
|
|
// transform generated mesh
|
|
for (int VertIdx : ResultMeshOut->VertexIndicesItr())
|
|
{
|
|
FVector3d V = ResultMeshOut->GetVertex(VertIdx);
|
|
FVector2d VTransformed = RotationMat * FVector2d(V.X, V.Y) + FirstReferencePt;
|
|
ResultMeshOut->SetVertex(VertIdx, FVector3d(VTransformed.X, VTransformed.Y, 0));
|
|
}
|
|
}
|
|
else // generic path: triangulate using polygon vertices
|
|
{
|
|
// triangulate polygon into the MeshDescription
|
|
FGeneralPolygon2d GeneralPolygon;
|
|
FFlatTriangulationMeshGenerator TriangulationMeshGen;
|
|
|
|
if (OuterPolygon.IsClockwise() == false)
|
|
{
|
|
OuterPolygon.Reverse();
|
|
}
|
|
|
|
GeneralPolygon.SetOuter(OuterPolygon);
|
|
|
|
for (int HoleIdx = 0; HoleIdx < PolygonHoles.Num(); HoleIdx++)
|
|
{
|
|
// attempt to add holes (skipping if safety checks fail)
|
|
GeneralPolygon.AddHole(VertexArrayToPolygon(PolygonHoles[HoleIdx]), true, false /*currently don't care about hole orientation; we'll just set the triangulation algo not to care*/);
|
|
}
|
|
|
|
FConstrainedDelaunay2d Triangulator;
|
|
if (PolygonProperties->bAllowSelfIntersections)
|
|
{
|
|
FArrangement2d Arrangement(OuterPolygon.Bounds());
|
|
// arrangement2d builds a general 2d graph that discards orientation info ...
|
|
Triangulator.FillRule = FConstrainedDelaunay2d::EFillRule::Odd;
|
|
Triangulator.bOrientedEdges = false;
|
|
Triangulator.bSplitBowties = true;
|
|
for (FSegment2d Seg : GeneralPolygon.GetOuter().Segments())
|
|
{
|
|
Arrangement.Insert(Seg);
|
|
}
|
|
Triangulator.Add(Arrangement.Graph);
|
|
for (const FPolygon2d& Hole : GeneralPolygon.GetHoles())
|
|
{
|
|
Triangulator.Add(Hole, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Triangulator.Add(GeneralPolygon);
|
|
}
|
|
|
|
|
|
Triangulator.Triangulate([&GeneralPolygon](const TArray<FVector2d>& Vertices, FIndex3i Tri)
|
|
{
|
|
// keep triangles based on the input polygon's winding
|
|
return GeneralPolygon.Contains((Vertices[Tri.A] + Vertices[Tri.B] + Vertices[Tri.C]) / 3.0);
|
|
});
|
|
// only truly fail if we got zero triangles back from the triangulator; if it just returned false it may still have managed to partially generate something
|
|
if (Triangulator.Triangles.Num() == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TriangulationMeshGen.Vertices2D = Triangulator.Vertices;
|
|
TriangulationMeshGen.Triangles2D = Triangulator.Triangles;
|
|
|
|
ResultMeshOut->Copy(&TriangulationMeshGen.Generate());
|
|
}
|
|
|
|
// for symmetric extrude we translate the first poly by -dist along axis
|
|
if (bExtrudeSymmetric)
|
|
{
|
|
FVector3d ShiftNormal = FVector3d::UnitZ();
|
|
for (int vid : ResultMeshOut->VertexIndicesItr())
|
|
{
|
|
FVector3d Pos = ResultMeshOut->GetVertex(vid);
|
|
ResultMeshOut->SetVertex(vid, Pos - ExtrudeDistance * ShiftNormal);
|
|
}
|
|
// double extrude dist
|
|
ExtrudeDistance *= 2.0;
|
|
}
|
|
|
|
if (ExtrudeDistance != 0)
|
|
{
|
|
FExtrudeMesh Extruder(ResultMeshOut);
|
|
Extruder.DefaultExtrudeDistance = ExtrudeDistance;
|
|
|
|
Extruder.UVScaleFactor = 1.0 / Bounds.MaxDim();
|
|
if (ExtrudeDistance < 0)
|
|
{
|
|
Extruder.IsPositiveOffset = false;
|
|
}
|
|
|
|
FVector3d ExtrudeNormal = FVector3d::UnitZ();
|
|
Extruder.ExtrudedPositionFunc = [&ExtrudeDistance, &ExtrudeNormal](const FVector3d& Position, const FVector3f& Normal, int VertexID)
|
|
{
|
|
return Position + ExtrudeDistance * (FVector3d)ExtrudeNormal;
|
|
};
|
|
|
|
Extruder.Apply();
|
|
}
|
|
|
|
FDynamicMeshEditor Editor(ResultMeshOut);
|
|
float InitialUVScale = 1.0 / Bounds.MaxDim(); // this is the UV scale used by both the polymeshgen and the extruder above
|
|
// default global rescale -- initial scale doesn't factor in extrude distance; rescale so UVScale of 1.0 fits in the unit square texture
|
|
float GlobalUVRescale = MaterialProperties->UVScale / FMathf::Max(1.0f, FMathd::Abs(ExtrudeDistance) * InitialUVScale);
|
|
if (MaterialProperties->bWorldSpaceUVScale)
|
|
{
|
|
// since we know the initial uv scale, directly compute the global scale (relative to 1 meter as a standard scale)
|
|
GlobalUVRescale = MaterialProperties->UVScale * .01 / InitialUVScale;
|
|
}
|
|
Editor.RescaleAttributeUVs(GlobalUVRescale, false);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
void UDrawPolygonTool::ShowStartupMessage()
|
|
{
|
|
GetToolManager()->DisplayMessage(
|
|
LOCTEXT("OnStartDraw", "Draw a polygon on the drawing plane, and extrude it. Left-click to place polygon vertices. Hold Shift to ignore snapping while drawing."),
|
|
EToolMessageLevel::UserNotification);
|
|
}
|
|
|
|
void UDrawPolygonTool::ShowExtrudeMessage()
|
|
{
|
|
GetToolManager()->DisplayMessage(
|
|
LOCTEXT("OnStartExtrude", "Set the height of the extrusion by positioning the mouse over the extrusion volume, or over objects to snap to their heights. Hold Shift to ignore snapping."),
|
|
EToolMessageLevel::UserNotification);
|
|
}
|
|
|
|
|
|
|
|
|
|
void UDrawPolygonTool::UndoCurrentOperation(const TArray<FVector3d>& ClickPointsIn, const TArray<FVector3d>& PolygonVerticesIn)
|
|
{
|
|
if (bInInteractiveExtrude)
|
|
{
|
|
PreviewMesh->ClearPreview();
|
|
PreviewMesh->SetVisible(false);
|
|
bInInteractiveExtrude = false;
|
|
}
|
|
ApplyUndoPoints(ClickPointsIn, PolygonVerticesIn);
|
|
}
|
|
|
|
|
|
void FDrawPolygonStateChange::Revert(UObject* Object)
|
|
{
|
|
Cast<UDrawPolygonTool>(Object)->UndoCurrentOperation( FixedVertexPoints, PolyPoints );
|
|
bHaveDoneUndo = true;
|
|
}
|
|
bool FDrawPolygonStateChange::HasExpired(UObject* Object) const
|
|
{
|
|
return bHaveDoneUndo || (Cast<UDrawPolygonTool>(Object)->CheckInCurve(CurveTimestamp) == false);
|
|
}
|
|
FString FDrawPolygonStateChange::ToString() const
|
|
{
|
|
return TEXT("FDrawPolygonStateChange");
|
|
}
|
|
|
|
|
|
|
|
#undef LOCTEXT_NAMESPACE
|
|
|