You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
We now early out when the raycast misses the plane if snapping is disabled. If the tool plane is missed, then we use a point along the camera ray to find scene snap point (passed to FindSceneSnapPoint). A related bug is also fixed in this change in the snapping manager which caused only small sections of snappable edges to be considered. This was because the point passed to FindSceneSnapPoint was used to find a point on the 3 edges of a hit triangle, which is sensitive to camera position. Instead, we now use the raycast's impact point for the purposes of finding snap points on edges. #rb Jimmy.Andrews, semion.piskarev #jira UE-203169 [CL 31479814 by nickolas drake in 5.4 branch]
1342 lines
43 KiB
C++
1342 lines
43 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)
|
|
{
|
|
// 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++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
{
|
|
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
|
|
|