Files
UnrealEngineUWP/Engine/Plugins/Runtime/MeshModelingToolset/Source/MeshModelingTools/Private/DrawPolygonTool.cpp
Nickolas Drake 479dc14918 Draw Polygon Tool: Add construction plane mechanic to tool.
The tool seems to predate the construction plane mechanic and ought to use it so that changes to the construction plane mechanic are reflected in this tool.

#jira none
#rb semion.piskarev
#preflight 6373f66e0c74adb48ba697f6

[CL 23155352 by Nickolas Drake in ue5-main branch]
2022-11-16 11:40:11 -05:00

1323 lines
42 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 "SceneManagement.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/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);
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, FFrame3d());
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)
{
ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 2,
TEXT("ToggleGizmo"),
LOCTEXT("ToggleGizmo", "Toggle Gizmo"),
LOCTEXT("ToggleGizmoTooltip", "Toggle visibility of the transformation gizmo"),
EModifierKey::None, EKeys::A,
[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++;
}
}
}
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;
bool bHit = Frame.RayPlaneIntersection((FVector3d)ClickPos.WorldRay.Origin, (FVector3d)ClickPos.WorldRay.Direction, 2, HitPos);
if (bHit == false)
{
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
{
FVector3d WorldGridSnapPos;
if (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)
{
FVector3d SceneSnapPos;
if (ToolSceneQueriesUtil::FindSceneSnapPoint(this, HitPos, 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(HitPos);
// remove scene snap point
SnapEngine.RemovePointTargetsByID(CurrentSceneSnapID);
SnapEngine.RemovePointTargetsByID(CurrentGridSnapID);
if (SnapEngine.HaveActiveSnap())
{
HitPosOut = SnapEngine.GetActiveSnapToPoint();
return true;
}
// if not yet snapped and we want to hit objects, do that
if (SnapProperties->bSnapToSurfaces && !bIgnoreSnappingToggle)
{
FHitResult Result;
bool bWorldHit = ToolSceneQueriesUtil::FindNearestVisibleObjectHit(this, Result, ClickPos.WorldRay);
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;
}
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 = Delta / Radius;
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)
{
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