2020-09-24 00:43:27 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
2021-07-26 17:57:22 -04:00
# include "ToolActivities/PolyEditInsertEdgeActivity.h"
2020-09-24 00:43:27 -04:00
# include "BaseBehaviors/SingleClickBehavior.h"
# include "BaseBehaviors/MouseHoverBehavior.h"
2021-07-26 17:57:22 -04:00
# include "ContextObjectStore.h"
2020-09-24 00:43:27 -04:00
# include "CuttingOps/GroupEdgeInsertionOp.h"
2021-06-13 00:36:02 -04:00
# include "DynamicMesh/DynamicMeshChangeTracker.h"
2020-09-24 00:43:27 -04:00
# include "InteractiveToolManager.h"
2021-07-26 17:57:22 -04:00
# include "MeshOpPreviewHelpers.h"
# include "Selection/PolygonSelectionMechanic.h"
# include "ToolActivities/PolyEditActivityContext.h"
2021-03-18 18:26:33 -04:00
2022-09-28 01:06:15 -04:00
# include UE_INLINE_GENERATED_CPP_BY_NAME(PolyEditInsertEdgeActivity)
2021-03-09 19:33:56 -04:00
using namespace UE : : Geometry ;
2021-07-26 17:57:22 -04:00
# define LOCTEXT_NAMESPACE "UPolyEditInsertEdgeActivity"
2020-09-24 00:43:27 -04:00
2021-07-26 17:57:22 -04:00
namespace PolyEditInsertEdgeActivityLocals
2020-09-24 00:43:27 -04:00
{
2021-07-26 17:57:22 -04:00
bool GetSharedBoundary ( const FGroupTopology & Topology ,
const FGroupEdgeInserter : : FGroupEdgeSplitPoint & StartPoint , int32 StartTopologyID , bool bStartIsCorner ,
const FGroupEdgeInserter : : FGroupEdgeSplitPoint & EndPoint , int32 EndTopologyID , bool bEndIsCorner ,
int32 & GroupIDOut , int32 & BoundaryIndexOut ) ;
bool DoesBoundaryContainPoint ( const FGroupTopology & Topology ,
const FGroupTopology : : FGroupBoundary & Boundary , int32 PointTopologyID , bool bPointIsCorner ) ;
2023-01-30 10:28:33 -05:00
FText GroupEdgeStartTransactionName = LOCTEXT ( " GroupEdgeStartTransactionName " , " Group Edge Start " ) ;
2020-09-24 00:43:27 -04:00
}
2021-07-26 17:57:22 -04:00
TUniquePtr < FDynamicMeshOperator > UPolyEditInsertEdgeActivity : : MakeNewOperator ( )
2020-09-24 00:43:27 -04:00
{
TUniquePtr < FGroupEdgeInsertionOp > Op = MakeUnique < FGroupEdgeInsertionOp > ( ) ;
2021-10-21 10:11:02 -04:00
Op - > OriginalMesh = ComputeStartMesh ;
Op - > OriginalTopology = ComputeStartTopology ;
2021-07-26 17:57:22 -04:00
Op - > SetTransform ( TargetTransform ) ;
2020-09-24 00:43:27 -04:00
2021-07-26 17:57:22 -04:00
if ( Settings - > InsertionMode = = EGroupEdgeInsertionMode : : PlaneCut )
2020-09-24 00:43:27 -04:00
{
Op - > Mode = FGroupEdgeInserter : : EInsertionMode : : PlaneCut ;
}
else
{
Op - > Mode = FGroupEdgeInserter : : EInsertionMode : : Retriangulate ;
}
2021-07-26 17:57:22 -04:00
Op - > VertexTolerance = Settings - > VertexTolerance ;
2020-09-24 00:43:27 -04:00
2021-07-26 17:57:22 -04:00
Op - > StartPoint = StartPoint ;
Op - > EndPoint = EndPoint ;
Op - > CommonGroupID = CommonGroupID ;
Op - > CommonBoundaryIndex = CommonBoundaryIndex ;
2020-09-24 00:43:27 -04:00
return Op ;
}
2021-07-26 17:57:22 -04:00
void UPolyEditInsertEdgeActivity : : Setup ( UInteractiveTool * ParentToolIn )
2020-09-24 00:43:27 -04:00
{
2021-07-26 17:57:22 -04:00
Super : : Setup ( ParentToolIn ) ;
2020-09-24 00:43:27 -04:00
// Set up properties
Settings = NewObject < UGroupEdgeInsertionProperties > ( this ) ;
2021-07-26 17:57:22 -04:00
Settings - > RestoreProperties ( ParentTool . Get ( ) ) ;
2020-09-24 00:43:27 -04:00
AddToolPropertySource ( Settings ) ;
2021-07-26 17:57:22 -04:00
SetToolPropertySourceEnabled ( Settings , false ) ;
Settings - > GetOnModified ( ) . AddUObject ( this , & UPolyEditInsertEdgeActivity : : OnPropertyModified ) ;
2020-09-24 00:43:27 -04:00
// These draw the group edges and the loops to be inserted
ExistingEdgesRenderer . LineColor = FLinearColor : : Red ;
ExistingEdgesRenderer . LineThickness = 2.0 ;
PreviewEdgeRenderer . LineColor = FLinearColor : : Green ;
PreviewEdgeRenderer . LineThickness = 4.0 ;
PreviewEdgeRenderer . PointColor = FLinearColor : : Green ;
PreviewEdgeRenderer . PointSize = 8.0 ;
PreviewEdgeRenderer . bDepthTested = false ;
2021-07-26 17:57:22 -04:00
// Set up the topology selector settings
2020-09-24 00:43:27 -04:00
TopologySelectorSettings . bEnableEdgeHits = true ;
TopologySelectorSettings . bEnableCornerHits = true ;
TopologySelectorSettings . bEnableFaceHits = false ;
2021-07-26 17:57:22 -04:00
// Set up our input routing
USingleClickInputBehavior * ClickBehavior = NewObject < USingleClickInputBehavior > ( ) ;
ClickBehavior - > Initialize ( this ) ;
ParentTool - > AddInputBehavior ( ClickBehavior ) ;
UMouseHoverBehavior * HoverBehavior = NewObject < UMouseHoverBehavior > ( ) ;
HoverBehavior - > Initialize ( this ) ;
ParentTool - > AddInputBehavior ( HoverBehavior ) ;
ActivityContext = ParentTool - > GetToolManager ( ) - > GetContextObjectStore ( ) - > FindContext < UPolyEditActivityContext > ( ) ;
2022-09-14 15:25:19 -04:00
// TODO: When the deprecated function GetTopologySelector is removed from UPolygonSelectionMechanic, we can remove the UMeshTopologySelectionMechanic:: prefix here
TopologySelector = ActivityContext - > SelectionMechanic - > UMeshTopologySelectionMechanic : : GetTopologySelector ( ) ;
2021-10-21 10:11:02 -04:00
ActivityContext - > OnUndoRedo . AddWeakLambda ( this , [ this ] ( bool bGroupTopologyModified )
{
UpdateComputeInputs ( ) ;
2023-01-27 14:53:43 -05:00
ToolState = EState : : GettingStart ;
ClearPreview ( true ) ;
2021-10-21 10:11:02 -04:00
} ) ;
2020-09-24 00:43:27 -04:00
}
2021-10-21 10:11:02 -04:00
void UPolyEditInsertEdgeActivity : : UpdateComputeInputs ( )
{
ComputeStartMesh = MakeShared < FDynamicMesh3 > ( * ActivityContext - > CurrentMesh ) ;
TSharedPtr < FGroupTopology > NonConstTopology = MakeShared < FGroupTopology > ( * ActivityContext - > CurrentTopology ) ;
NonConstTopology - > RetargetOnClonedMesh ( ComputeStartMesh . Get ( ) ) ;
ComputeStartTopology = NonConstTopology ;
}
2021-07-26 17:57:22 -04:00
void UPolyEditInsertEdgeActivity : : Shutdown ( EToolShutdownType ShutdownType )
2020-09-24 00:43:27 -04:00
{
2021-07-26 17:57:22 -04:00
if ( bIsRunning )
{
End ( ShutdownType ) ;
}
2020-09-24 00:43:27 -04:00
2021-07-26 17:57:22 -04:00
Settings - > SaveProperties ( ParentTool . Get ( ) ) ;
Settings - > GetOnModified ( ) . Clear ( ) ;
2020-09-24 00:43:27 -04:00
2021-07-26 17:57:22 -04:00
TopologySelector . Reset ( ) ;
Settings = nullptr ;
2021-10-21 10:11:02 -04:00
ActivityContext - > OnUndoRedo . RemoveAll ( this ) ;
2021-07-26 17:57:22 -04:00
ActivityContext = nullptr ;
Super : : Shutdown ( ShutdownType ) ;
}
EToolActivityStartResult UPolyEditInsertEdgeActivity : : Start ( )
{
ParentTool - > GetToolManager ( ) - > DisplayMessage (
LOCTEXT ( " InsertEdgeActivityDescription " , " Click two points on the boundary of a face to "
" insert a new edge between the points and split the face. " ) ,
EToolMessageLevel : : UserNotification ) ;
SetToolPropertySourceEnabled ( Settings , true ) ;
// We don't use selection, so clear it if necessary (have to issue an undo/redo event)
if ( ! ActivityContext - > SelectionMechanic - > GetActiveSelection ( ) . IsEmpty ( ) )
{
ActivityContext - > SelectionMechanic - > BeginChange ( ) ;
ActivityContext - > SelectionMechanic - > ClearSelection ( ) ;
ParentTool - > GetToolManager ( ) - > EmitObjectChange ( ActivityContext - > SelectionMechanic ,
ActivityContext - > SelectionMechanic - > EndChange ( ) , LOCTEXT ( " ClearSelection " , " Clear Selection " ) ) ;
}
TargetTransform = ActivityContext - > Preview - > PreviewMesh - > GetTransform ( ) ;
2021-10-21 10:11:02 -04:00
UpdateComputeInputs ( ) ;
2021-07-26 17:57:22 -04:00
SetupPreview ( ) ;
ToolState = EState : : GettingStart ;
bLastComputeSucceeded = false ;
bIsRunning = true ;
// Emit activity start transaction
ActivityContext - > EmitActivityStart ( LOCTEXT ( " BeginInsertEdgeActivity " , " Begin Insert Edge " ) ) ;
return EToolActivityStartResult : : Running ;
}
EToolActivityEndResult UPolyEditInsertEdgeActivity : : End ( EToolShutdownType ShutdownType )
{
if ( ! bIsRunning )
{
return EToolActivityEndResult : : ErrorDuringEnd ;
}
SetToolPropertySourceEnabled ( Settings , false ) ;
Settings - > GetOnModified ( ) . Clear ( ) ;
ActivityContext - > Preview - > OnOpCompleted . RemoveAll ( this ) ;
ActivityContext - > Preview - > OnMeshUpdated . RemoveAll ( this ) ;
2023-02-02 18:42:20 -05:00
ToolState = EState : : GettingStart ;
// Note that this does an ensure on us not being in a ToolState that requires the preview points, hence
// we do this after resetting ToolState.
ClearPreview ( true ) ;
2021-07-26 17:57:22 -04:00
ActivityContext - > Preview - > ClearOpFactory ( ) ;
LatestOpTopologyResult . Reset ( ) ;
LatestOpChangedTids . Reset ( ) ;
bIsRunning = false ;
return CanAccept ( ) ? EToolActivityEndResult : : Completed : EToolActivityEndResult : : Cancelled ;
}
void UPolyEditInsertEdgeActivity : : SetupPreview ( )
{
ActivityContext - > Preview - > ChangeOpFactory ( this ) ;
2020-09-24 00:43:27 -04:00
// Whenever we get a new result from the op, we need to extract the preview edges so that
// we can draw them if we want to.
2021-07-26 17:57:22 -04:00
ActivityContext - > Preview - > OnOpCompleted . AddWeakLambda ( this , [ this ] ( const FDynamicMeshOperator * UncastOp ) {
2020-09-24 00:43:27 -04:00
const FGroupEdgeInsertionOp * Op = static_cast < const FGroupEdgeInsertionOp * > ( UncastOp ) ;
LatestOpTopologyResult . Reset ( ) ;
2021-03-29 13:39:32 -04:00
LatestOpChangedTids . Reset ( ) ;
2020-09-24 00:43:27 -04:00
PreviewEdges . Reset ( ) ;
2021-10-21 10:11:02 -04:00
// See if this compute is actually outdated, i.e. we changed the mesh
// out from under it.
if ( Op - > OriginalMesh ! = ComputeStartMesh )
{
bLastComputeSucceeded = false ;
return ;
}
bLastComputeSucceeded = Op - > bSucceeded ;
2020-09-24 00:43:27 -04:00
if ( bLastComputeSucceeded )
{
Op - > GetEdgeLocations ( PreviewEdges ) ;
LatestOpTopologyResult = Op - > ResultTopology ;
2021-03-29 13:39:32 -04:00
LatestOpChangedTids = Op - > ChangedTids ;
2020-09-24 00:43:27 -04:00
}
2021-07-26 17:57:22 -04:00
} ) ;
2020-09-24 00:43:27 -04:00
2021-07-26 17:57:22 -04:00
// In case of failure, we want to hide the broken preview, since we wouldn't accept it on
// a click. Note that this can't be fired OnOpCompleted because the preview is updated
// with the op result after that callback, which would undo the reset. The preview edge
// extraction can't be lumped in here because it needs the op rather than the preview object.
ActivityContext - > Preview - > OnMeshUpdated . AddWeakLambda ( this , [ this ] ( UMeshOpPreviewWithBackgroundCompute * ) {
2020-09-24 00:43:27 -04:00
if ( ! bLastComputeSucceeded )
{
2021-07-26 17:57:22 -04:00
ActivityContext - > Preview - > PreviewMesh - > UpdatePreview ( ActivityContext - > CurrentMesh . Get ( ) ) ;
2020-09-24 00:43:27 -04:00
}
2021-07-26 17:57:22 -04:00
} ) ;
2020-09-24 00:43:27 -04:00
}
2021-07-26 17:57:22 -04:00
bool UPolyEditInsertEdgeActivity : : CanStart ( ) const
2020-09-24 00:43:27 -04:00
{
2021-07-26 17:57:22 -04:00
return true ;
}
2021-03-29 13:39:32 -04:00
2021-07-26 17:57:22 -04:00
void UPolyEditInsertEdgeActivity : : Tick ( float DeltaTime )
{
if ( ToolState = = EState : : WaitingForInsertComplete & & ActivityContext - > Preview - > HaveValidResult ( ) )
2021-03-29 13:39:32 -04:00
{
2021-07-26 17:57:22 -04:00
if ( bLastComputeSucceeded )
2021-03-29 13:39:32 -04:00
{
2021-07-26 17:57:22 -04:00
FDynamicMeshChangeTracker ChangeTracker ( ActivityContext - > CurrentMesh . Get ( ) ) ;
ChangeTracker . BeginChange ( ) ;
ChangeTracker . SaveTriangles ( * LatestOpChangedTids , true /*bSaveVertices*/ ) ;
// Update current mesh
ActivityContext - > CurrentMesh - > Copy ( * ActivityContext - > Preview - > PreviewMesh - > GetMesh ( ) ,
true , true , true , true ) ;
* ActivityContext - > CurrentTopology = * LatestOpTopologyResult ;
ActivityContext - > CurrentTopology - > RetargetOnClonedMesh ( ActivityContext - > CurrentMesh . Get ( ) ) ;
2021-10-21 10:11:02 -04:00
UpdateComputeInputs ( ) ;
2021-07-26 17:57:22 -04:00
// Emit transaction
FGroupTopologySelection EmptySelection ;
ActivityContext - > EmitCurrentMeshChangeAndUpdate ( LOCTEXT ( " EdgeInsertionTransactionName " , " Edge Insertion " ) ,
2022-01-11 06:48:50 -05:00
ChangeTracker . EndChange ( ) , EmptySelection ) ;
2021-07-26 17:57:22 -04:00
ToolState = EState : : GettingStart ;
2022-11-07 12:37:29 -05:00
if ( Settings - > bContinuousInsertion )
{
// If continuous insertion is enabled, try to find information associated with new start point.
// If found, remain in GettingEnd mode.
FVector3d PreviewPoint ;
if ( GetHoveredItem ( LastEndPointWorldRay , StartPoint , StartTopologyID , bStartIsCorner , PreviewPoint ) )
{
PreviewPoints . Reset ( ) ;
PreviewPoints . Add ( PreviewPoint ) ;
ToolState = EState : : GettingEnd ;
2023-01-30 10:28:33 -05:00
ParentTool - > GetToolManager ( ) - > EmitObjectChange ( this ,
MakeUnique < FGroupEdgeInsertionFirstPointChange > ( CurrentChangeStamp ) ,
PolyEditInsertEdgeActivityLocals : : GroupEdgeStartTransactionName ) ;
2022-11-07 12:37:29 -05:00
}
}
2021-07-26 17:57:22 -04:00
}
else
{
2023-01-27 14:53:43 -05:00
ToolState = PreviewPoints . Num ( ) > 0 ? EState : : GettingEnd : EState : : GettingStart ;
2021-07-26 17:57:22 -04:00
}
PreviewEdges . Reset ( ) ;
2021-03-29 13:39:32 -04:00
}
2020-09-24 00:43:27 -04:00
}
2021-07-26 17:57:22 -04:00
void UPolyEditInsertEdgeActivity : : Render ( IToolsContextRenderAPI * RenderAPI )
2020-09-24 00:43:27 -04:00
{
2021-07-26 17:57:22 -04:00
ParentTool - > GetToolManager ( ) - > GetContextQueriesAPI ( ) - > GetCurrentViewState ( CameraState ) ;
2020-09-24 00:43:27 -04:00
// Draw the existing group edges
FViewCameraState RenderCameraState = RenderAPI - > GetCameraState ( ) ;
ExistingEdgesRenderer . BeginFrame ( RenderAPI , RenderCameraState ) ;
2021-07-26 17:57:22 -04:00
ExistingEdgesRenderer . SetTransform ( TargetTransform ) ;
2020-09-24 00:43:27 -04:00
2021-07-26 17:57:22 -04:00
for ( const FGroupTopology : : FGroupEdge & Edge : ActivityContext - > CurrentTopology - > Edges )
2020-09-24 00:43:27 -04:00
{
FVector3d A , B ;
for ( int32 eid : Edge . Span . Edges )
{
2021-07-26 17:57:22 -04:00
ActivityContext - > CurrentMesh - > GetEdgeV ( eid , A , B ) ;
2020-09-24 00:43:27 -04:00
ExistingEdgesRenderer . DrawLine ( A , B ) ;
}
}
ExistingEdgesRenderer . EndFrame ( ) ;
// Draw the preview edges and points
PreviewEdgeRenderer . BeginFrame ( RenderAPI , RenderCameraState ) ;
2021-07-26 17:57:22 -04:00
PreviewEdgeRenderer . SetTransform ( TargetTransform ) ;
2020-09-24 00:43:27 -04:00
for ( const TPair < FVector3d , FVector3d > & EdgeVerts : PreviewEdges )
{
PreviewEdgeRenderer . DrawLine ( EdgeVerts . Key , EdgeVerts . Value ) ;
}
for ( const FVector3d & Point : PreviewPoints )
{
PreviewEdgeRenderer . DrawPoint ( Point ) ;
}
PreviewEdgeRenderer . EndFrame ( ) ;
}
2021-07-26 17:57:22 -04:00
bool UPolyEditInsertEdgeActivity : : CanAccept ( ) const
2020-09-24 00:43:27 -04:00
{
2021-07-26 17:57:22 -04:00
return ToolState ! = EState : : WaitingForInsertComplete ;
2020-09-24 00:43:27 -04:00
}
2021-07-26 17:57:22 -04:00
void UPolyEditInsertEdgeActivity : : OnPropertyModified ( UObject * PropertySet , FProperty * Property )
2020-09-24 00:43:27 -04:00
{
2021-07-26 17:57:22 -04:00
PreviewEdges . Reset ( ) ;
2021-07-29 15:22:26 -04:00
// Don't clear drawn elements because we may be getting the second endpoint, and still
// need to keep the first.
ClearPreview ( false ) ;
2021-07-26 17:57:22 -04:00
}
void UPolyEditInsertEdgeActivity : : ClearPreview ( bool bClearDrawnElements )
{
ActivityContext - > Preview - > CancelCompute ( ) ;
ActivityContext - > Preview - > PreviewMesh - > UpdatePreview ( ActivityContext - > CurrentMesh . Get ( ) ) ;
bShowingBaseMesh = true ;
2020-09-24 00:43:27 -04:00
if ( bClearDrawnElements )
{
PreviewEdges . Reset ( ) ;
PreviewPoints . Reset ( ) ;
2023-01-27 14:53:43 -05:00
// If we're removing the start point, we shouldn't be in a state that requires
// it, i.e. getting the second point.
if ( ! ensure ( ToolState ! = EState : : GettingEnd ) )
{
ToolState = EState : : GettingStart ;
}
2020-09-24 00:43:27 -04:00
}
}
2021-07-26 17:57:22 -04:00
/**
* Update the preview unless we ' ve already computed one with the same parameters ( such as when snapping to
* the same vertex despite moving the mouse ) .
*/
void UPolyEditInsertEdgeActivity : : ConditionallyUpdatePreview (
const FGroupEdgeInserter : : FGroupEdgeSplitPoint & NewEndPoint , int32 NewEndTopologyID , bool bNewEndIsCorner ,
2020-09-24 00:43:27 -04:00
int32 NewCommonGroupID , int32 NewBoundaryIndex )
{
if ( bShowingBaseMesh
| | bEndIsCorner ! = bNewEndIsCorner | | EndTopologyID ! = NewEndTopologyID
| | EndPoint . bIsVertex ! = NewEndPoint . bIsVertex | | EndPoint . ElementID ! = NewEndPoint . ElementID
| | ( ! NewEndPoint . bIsVertex & & NewEndPoint . EdgeTValue ! = EndPoint . EdgeTValue )
| | CommonGroupID ! = NewCommonGroupID | | CommonBoundaryIndex ! = NewBoundaryIndex )
{
// Update the end variables, since they are apparently different
EndPoint = NewEndPoint ;
EndTopologyID = NewEndTopologyID ;
bEndIsCorner = bNewEndIsCorner ;
CommonGroupID = NewCommonGroupID ;
CommonBoundaryIndex = NewBoundaryIndex ;
// If either endpoint is a corner, we need to calculate its tangent. This will differ based on which
// boundary it is a part of.
if ( bStartIsCorner )
{
GetCornerTangent ( StartTopologyID , CommonGroupID , CommonBoundaryIndex , StartPoint . Tangent ) ;
}
if ( bEndIsCorner )
{
GetCornerTangent ( EndTopologyID , CommonGroupID , CommonBoundaryIndex , EndPoint . Tangent ) ;
}
bShowingBaseMesh = false ;
PreviewEdges . Reset ( ) ;
2021-07-26 17:57:22 -04:00
ActivityContext - > Preview - > InvalidateResult ( ) ;
2020-09-24 00:43:27 -04:00
}
}
2021-07-26 17:57:22 -04:00
FInputRayHit UPolyEditInsertEdgeActivity : : BeginHoverSequenceHitTest ( const FInputDeviceRay & PressPos )
2020-09-24 00:43:27 -04:00
{
FInputRayHit Hit ;
2021-07-26 17:57:22 -04:00
// Early out if the activity is not running. This is actually important because the behavior is
// always in the behavior list while the tool is running (we don't have a way to add/remove at
// will).
if ( ! bIsRunning )
{
return Hit ; // Hit.bHit is false
}
2020-09-24 00:43:27 -04:00
switch ( ToolState )
{
2021-07-26 17:57:22 -04:00
case EState : : WaitingForInsertComplete :
2020-09-24 00:43:27 -04:00
break ; // Keep hit invalid
2021-07-26 17:57:22 -04:00
case EState : : GettingStart :
2020-09-24 00:43:27 -04:00
{
PreviewPoints . Reset ( ) ;
FVector3d RayPoint ;
if ( TopologyHitTest ( PressPos . WorldRay , RayPoint ) )
{
Hit = FInputRayHit ( PressPos . WorldRay . GetParameter ( ( FVector ) RayPoint ) ) ;
}
2021-07-26 17:57:22 -04:00
break ;
2020-09-24 00:43:27 -04:00
}
2021-07-26 17:57:22 -04:00
case EState : : GettingEnd :
2020-09-24 00:43:27 -04:00
{
FVector3d RayPoint ;
FRay3d LocalRay ;
if ( TopologyHitTest ( PressPos . WorldRay , RayPoint , & LocalRay ) )
{
Hit = FInputRayHit ( PressPos . WorldRay . GetParameter ( ( FVector ) RayPoint ) ) ;
}
else
{
// If we don't hit a valid element, we still do a hover if we hit the mesh.
// We still do the topology check in the first place because it accepts missing
// rays that are close enough to snap.
double RayT = 0 ;
int32 Tid = FDynamicMesh3 : : InvalidID ;
2021-07-26 17:57:22 -04:00
if ( ActivityContext - > MeshSpatial - > FindNearestHitTriangle ( LocalRay , RayT , Tid ) )
2020-09-24 00:43:27 -04:00
{
Hit = FInputRayHit ( RayT ) ;
}
}
2021-07-26 17:57:22 -04:00
break ;
2020-09-24 00:43:27 -04:00
}
}
return Hit ;
}
2021-07-26 17:57:22 -04:00
bool UPolyEditInsertEdgeActivity : : OnUpdateHover ( const FInputDeviceRay & DevicePos )
2020-09-24 00:43:27 -04:00
{
2021-07-26 17:57:22 -04:00
using namespace PolyEditInsertEdgeActivityLocals ;
2021-07-29 15:22:26 -04:00
if ( ! bIsRunning )
{
return false ;
}
2020-09-24 00:43:27 -04:00
switch ( ToolState )
{
2021-07-26 17:57:22 -04:00
case EState : : WaitingForInsertComplete :
2020-09-24 00:43:27 -04:00
return false ; // Do nothing.
2021-07-26 17:57:22 -04:00
case EState : : GettingStart :
2020-09-24 00:43:27 -04:00
{
// Update start variables and show a preview of a point if it's on an edge or corner
PreviewPoints . Reset ( ) ;
FVector3d PreviewPoint ;
if ( GetHoveredItem ( DevicePos . WorldRay , StartPoint , StartTopologyID , bStartIsCorner , PreviewPoint ) )
{
PreviewPoints . Add ( PreviewPoint ) ;
return true ;
}
return false ;
}
2021-07-26 17:57:22 -04:00
case EState : : GettingEnd :
2020-09-24 00:43:27 -04:00
{
2023-01-27 14:53:43 -05:00
// This shouldn't happen- if it does, we messed up with our state handling somewhere. But don't crash,
// just reset.
if ( ! ensure ( PreviewPoints . Num ( ) > 0 ) )
{
ToolState = EState : : GettingStart ;
ClearPreview ( true ) ;
return false ;
}
2020-09-24 00:43:27 -04:00
PreviewPoints . SetNum ( 1 ) ; // Keep the first element, which is the start point
// Don't update the end variables right away so that we can check if they actually changed (they
// won't when we snap to the same corner as before).
FGroupEdgeInserter : : FGroupEdgeSplitPoint SnappedPoint ;
int32 PointTopologyID , GroupID , BoundaryIndex ;
bool bPointIsCorner ;
FVector3d PreviewPoint ;
FRay3d LocalRay ;
if ( GetHoveredItem ( DevicePos . WorldRay , SnappedPoint , PointTopologyID , bPointIsCorner , PreviewPoint , & LocalRay ) )
{
// See if the point is not on the same vertex/edge but is on the same boundary
if ( ! ( SnappedPoint . bIsVertex = = StartPoint . bIsVertex & & SnappedPoint . ElementID = = StartPoint . ElementID )
2021-07-26 17:57:22 -04:00
& & GetSharedBoundary ( * ActivityContext - > CurrentTopology , StartPoint , StartTopologyID , bStartIsCorner ,
2020-09-24 00:43:27 -04:00
SnappedPoint , PointTopologyID , bPointIsCorner , GroupID , BoundaryIndex ) )
{
ConditionallyUpdatePreview ( SnappedPoint , PointTopologyID , bPointIsCorner , GroupID , BoundaryIndex ) ;
}
else
{
PreviewEdges . Reset ( ) ; // TODO: Maybe we should show a different color edge on a fail, rather than hiding it?
}
PreviewPoints . Add ( PreviewPoint ) ;
return true ;
}
2023-01-27 14:53:43 -05:00
// If we're here, then we don't have a valid endpoint. Make sure our preview is reset and draw a line
// to the current hit location.
2020-09-24 00:43:27 -04:00
if ( ! bShowingBaseMesh )
{
ClearPreview ( false ) ;
}
PreviewEdges . Reset ( ) ;
double RayT = 0 ;
int32 Tid = FDynamicMesh3 : : InvalidID ;
2021-07-26 17:57:22 -04:00
if ( ActivityContext - > MeshSpatial - > FindNearestHitTriangle ( LocalRay , RayT , Tid ) )
2020-09-24 00:43:27 -04:00
{
PreviewEdges . Emplace ( PreviewPoints [ 0 ] , LocalRay . PointAt ( RayT ) ) ;
return true ;
}
return false ;
}
}
check ( false ) ; // Each case has its own return, so shouldn't get here
return false ;
}
2021-07-26 17:57:22 -04:00
void UPolyEditInsertEdgeActivity : : OnEndHover ( )
2020-09-24 00:43:27 -04:00
{
2021-07-29 15:22:26 -04:00
if ( ! bIsRunning )
{
return ;
}
2020-09-24 00:43:27 -04:00
switch ( ToolState )
{
2021-07-26 17:57:22 -04:00
case EState : : WaitingForInsertComplete :
case EState : : GettingStart :
2020-09-24 00:43:27 -04:00
ClearPreview ( true ) ;
break ;
2021-07-26 17:57:22 -04:00
case EState : : GettingEnd :
2020-09-24 00:43:27 -04:00
// Keep the first preview point.
ClearPreview ( false ) ;
PreviewPoints . SetNum ( 1 ) ;
PreviewEdges . Reset ( ) ;
}
}
2021-07-26 17:57:22 -04:00
FInputRayHit UPolyEditInsertEdgeActivity : : IsHitByClick ( const FInputDeviceRay & ClickPos )
2020-09-24 00:43:27 -04:00
{
FInputRayHit Hit ;
2021-07-26 17:57:22 -04:00
// Early out if the activity is not running.
if ( ! bIsRunning )
{
return Hit ; // Hit.bHit is false
}
2020-09-24 00:43:27 -04:00
switch ( ToolState )
{
2021-07-26 17:57:22 -04:00
case EState : : WaitingForInsertComplete :
2020-09-24 00:43:27 -04:00
break ; // Keep hit invalid
// Same requirement for the other two cases: the click should go on an edge
2021-07-26 17:57:22 -04:00
case EState : : GettingStart :
case EState : : GettingEnd :
2020-09-24 00:43:27 -04:00
{
FVector3d RayPoint ;
if ( TopologyHitTest ( ClickPos . WorldRay , RayPoint ) )
{
Hit = FInputRayHit ( ClickPos . WorldRay . GetParameter ( ( FVector ) RayPoint ) ) ;
}
break ;
}
}
return Hit ;
}
2021-07-26 17:57:22 -04:00
void UPolyEditInsertEdgeActivity : : OnClicked ( const FInputDeviceRay & ClickPos )
2020-09-24 00:43:27 -04:00
{
2021-07-26 17:57:22 -04:00
using namespace PolyEditInsertEdgeActivityLocals ;
2020-09-24 00:43:27 -04:00
switch ( ToolState )
{
2021-07-26 17:57:22 -04:00
case EState : : WaitingForInsertComplete :
2020-09-24 00:43:27 -04:00
break ; // Do nothing
2021-07-26 17:57:22 -04:00
case EState : : GettingStart :
2020-09-24 00:43:27 -04:00
{
// Update start variables and switch state if successful
FVector3d PreviewPoint ;
if ( GetHoveredItem ( ClickPos . WorldRay , StartPoint , StartTopologyID , bStartIsCorner , PreviewPoint ) )
{
PreviewPoints . Reset ( ) ;
PreviewPoints . Add ( PreviewPoint ) ;
2021-07-26 17:57:22 -04:00
ToolState = EState : : GettingEnd ;
2020-09-24 00:43:27 -04:00
2023-01-30 10:28:33 -05:00
ParentTool - > GetToolManager ( ) - > BeginUndoTransaction ( PolyEditInsertEdgeActivityLocals : : GroupEdgeStartTransactionName ) ;
2021-07-26 17:57:22 -04:00
ParentTool - > GetToolManager ( ) - > EmitObjectChange ( this , MakeUnique < FGroupEdgeInsertionFirstPointChange > ( CurrentChangeStamp ) ,
2023-01-30 10:28:33 -05:00
PolyEditInsertEdgeActivityLocals : : GroupEdgeStartTransactionName ) ;
2021-07-26 17:57:22 -04:00
ParentTool - > GetToolManager ( ) - > EndUndoTransaction ( ) ;
2020-09-24 00:43:27 -04:00
}
break ;
}
2021-07-26 17:57:22 -04:00
case EState : : GettingEnd :
2020-09-24 00:43:27 -04:00
{
// Don't update the end variables right away so that we can check if they actually changed (they
// won't when we snap to the same corner as before).
FVector3d PreviewPoint ;
FGroupEdgeInserter : : FGroupEdgeSplitPoint SnappedPoint ;
int32 PointTopologyID , GroupID , BoundaryIndex ;
bool bPointIsCorner ;
if ( GetHoveredItem ( ClickPos . WorldRay , SnappedPoint , PointTopologyID , bPointIsCorner , PreviewPoint ) )
{
// See if the point is not on the same vertex/edge but is on the same boundary
if ( ! ( SnappedPoint . bIsVertex = = StartPoint . bIsVertex & & SnappedPoint . ElementID = = StartPoint . ElementID )
2021-07-26 17:57:22 -04:00
& & GetSharedBoundary ( * ActivityContext - > CurrentTopology , StartPoint , StartTopologyID , bStartIsCorner ,
2020-09-24 00:43:27 -04:00
SnappedPoint , PointTopologyID , bPointIsCorner , GroupID , BoundaryIndex ) )
{
ConditionallyUpdatePreview ( SnappedPoint , PointTopologyID , bPointIsCorner , GroupID , BoundaryIndex ) ;
2021-07-26 17:57:22 -04:00
ToolState = EState : : WaitingForInsertComplete ;
2020-09-24 00:43:27 -04:00
}
else
{
ClearPreview ( false ) ;
}
}
2022-11-07 12:37:29 -05:00
LastEndPointWorldRay = ClickPos . WorldRay ;
2020-09-24 00:43:27 -04:00
break ;
}
}
}
2021-07-26 17:57:22 -04:00
bool UPolyEditInsertEdgeActivity : : TopologyHitTest ( const FRay & WorldRay ,
2020-09-24 00:43:27 -04:00
FVector3d & RayPositionOut , FRay3d * LocalRayOut )
{
2021-07-26 17:57:22 -04:00
FRay3d LocalRay ( ( FVector3d ) TargetTransform . InverseTransformPosition ( WorldRay . Origin ) ,
( FVector3d ) TargetTransform . InverseTransformVector ( WorldRay . Direction ) , false ) ;
2020-09-24 00:43:27 -04:00
if ( LocalRayOut )
{
* LocalRayOut = LocalRay ;
}
FGroupTopologySelection Selection ;
FVector3d Position , Normal ;
2022-01-04 11:23:06 -05:00
TopologySelectorSettings . bHitBackFaces = ActivityContext - > SelectionMechanic - > Properties - > bHitBackFaces ;
2021-07-26 17:57:22 -04:00
if ( TopologySelector - > FindSelectedElement ( TopologySelectorSettings ,
2020-09-24 00:43:27 -04:00
LocalRay , Selection , Position , Normal ) )
{
2021-07-26 17:57:22 -04:00
RayPositionOut = TargetTransform . TransformPosition ( Position ) ;
2020-09-24 00:43:27 -04:00
return true ;
}
return false ;
}
2021-07-26 17:57:22 -04:00
bool UPolyEditInsertEdgeActivity : : GetHoveredItem ( const FRay & WorldRay ,
2020-09-24 00:43:27 -04:00
FGroupEdgeInserter : : FGroupEdgeSplitPoint & PointOut ,
int32 & TopologyElementIDOut , bool & bIsCornerOut , FVector3d & PositionOut ,
FRay3d * LocalRayOut )
{
TopologyElementIDOut = FDynamicMesh3 : : InvalidID ;
PointOut . ElementID = FDynamicMesh3 : : InvalidID ;
// Cast the ray to see what we hit.
2021-07-26 17:57:22 -04:00
FRay3d LocalRay ( ( FVector3d ) TargetTransform . InverseTransformPosition ( WorldRay . Origin ) ,
( FVector3d ) TargetTransform . InverseTransformVector ( WorldRay . Direction ) , false ) ;
2020-09-24 00:43:27 -04:00
if ( LocalRayOut )
{
* LocalRayOut = LocalRay ;
}
FGroupTopologySelection Selection ;
FVector3d Position , Normal ;
int32 EdgeSegmentID ;
2022-01-04 11:23:06 -05:00
TopologySelectorSettings . bHitBackFaces = ActivityContext - > SelectionMechanic - > Properties - > bHitBackFaces ;
2021-07-26 17:57:22 -04:00
if ( ! TopologySelector - > FindSelectedElement (
2020-09-24 00:43:27 -04:00
TopologySelectorSettings , LocalRay , Selection , Position , Normal , & EdgeSegmentID ) )
{
return false ; // Didn't hit anything
}
else if ( Selection . SelectedCornerIDs . Num ( ) > 0 )
{
// Point is a corner
TopologyElementIDOut = Selection . GetASelectedCornerID ( ) ;
bIsCornerOut = true ;
PointOut . bIsVertex = true ;
2021-07-26 17:57:22 -04:00
PointOut . ElementID = ActivityContext - > CurrentTopology - > GetCornerVertexID ( TopologyElementIDOut ) ;
2020-09-24 00:43:27 -04:00
// We can't initialize the tangent yet because the tangent of a corner will
// depend on which boundary it is a part of.
2021-07-26 17:57:22 -04:00
PositionOut = ActivityContext - > CurrentMesh - > GetVertex ( PointOut . ElementID ) ;
2020-09-24 00:43:27 -04:00
}
else
{
// Point is an edge. We'll need to calculate the t value and some other things.
check ( Selection . SelectedEdgeIDs . Num ( ) > 0 ) ;
TopologyElementIDOut = Selection . GetASelectedEdgeID ( ) ;
bIsCornerOut = false ;
2021-07-26 17:57:22 -04:00
const FGroupTopology : : FGroupEdge & GroupEdge = ActivityContext - > CurrentTopology - > Edges [ TopologyElementIDOut ] ;
2020-09-24 00:43:27 -04:00
int32 Eid = GroupEdge . Span . Edges [ EdgeSegmentID ] ;
int32 StartVid = GroupEdge . Span . Vertices [ EdgeSegmentID ] ;
int32 EndVid = GroupEdge . Span . Vertices [ EdgeSegmentID + 1 ] ;
2021-07-26 17:57:22 -04:00
FVector3d StartVert = ActivityContext - > CurrentMesh - > GetVertex ( StartVid ) ;
FVector3d EndVert = ActivityContext - > CurrentMesh - > GetVertex ( EndVid ) ;
2020-09-24 00:43:27 -04:00
FVector3d EdgeVector = EndVert - StartVert ;
double EdgeLength = EdgeVector . Length ( ) ;
check ( EdgeLength > 0 ) ;
PointOut . Tangent = EdgeVector / EdgeLength ;
FRay EdgeRay ( ( FVector ) StartVert , ( FVector ) PointOut . Tangent , true ) ;
2021-03-30 21:25:22 -04:00
float DistDownEdge = EdgeRay . GetParameter ( ( FVector ) Position ) ;
2020-09-24 00:43:27 -04:00
// See if the point is at a vertex in the group edge span.
if ( DistDownEdge < = Settings - > VertexTolerance )
{
PointOut . bIsVertex = true ;
PointOut . ElementID = StartVid ;
2022-01-24 12:04:10 -05:00
PositionOut = StartVert ;
2020-09-24 00:43:27 -04:00
if ( EdgeSegmentID > 0 )
{
// Average with previous normalized edge vector
2021-07-26 17:57:22 -04:00
PointOut . Tangent + =
UE : : Geometry : : Normalized ( StartVert - ActivityContext - > CurrentMesh - > GetVertex ( GroupEdge . Span . Vertices [ EdgeSegmentID - 1 ] ) ) ;
2021-03-17 19:32:44 -04:00
UE : : Geometry : : Normalize ( PointOut . Tangent ) ;
2020-09-24 00:43:27 -04:00
}
}
else if ( abs ( DistDownEdge - EdgeLength ) < = Settings - > VertexTolerance )
{
PointOut . bIsVertex = true ;
PointOut . ElementID = EndVid ;
2022-01-24 12:04:10 -05:00
PositionOut = EndVert ;
2020-09-24 00:43:27 -04:00
if ( EdgeSegmentID + 2 < GroupEdge . Span . Vertices . Num ( ) )
{
2021-07-26 17:57:22 -04:00
PointOut . Tangent + = UE : : Geometry : : Normalized (
ActivityContext - > CurrentMesh - > GetVertex ( GroupEdge . Span . Vertices [ EdgeSegmentID + 2 ] ) - EndVert ) ;
2021-03-17 19:32:44 -04:00
UE : : Geometry : : Normalize ( PointOut . Tangent ) ;
2020-09-24 00:43:27 -04:00
}
}
else
{
PointOut . bIsVertex = false ;
PointOut . ElementID = Eid ;
PointOut . EdgeTValue = DistDownEdge / EdgeLength ;
2022-01-24 12:04:10 -05:00
PositionOut = ( FVector3d ) EdgeRay . PointAt ( DistDownEdge ) ;
2021-07-26 17:57:22 -04:00
if ( ActivityContext - > CurrentMesh - > GetEdgeV ( Eid ) . A ! = StartVid )
2020-09-24 00:43:27 -04:00
{
PointOut . EdgeTValue = 1 - PointOut . EdgeTValue ;
}
}
}
return true ;
}
2021-07-26 17:57:22 -04:00
void UPolyEditInsertEdgeActivity : : GetCornerTangent ( int32 CornerID , int32 GroupID , int32 BoundaryIndex , FVector3d & TangentOut )
2020-09-24 00:43:27 -04:00
{
TangentOut = FVector3d : : Zero ( ) ;
2021-07-26 17:57:22 -04:00
int32 CornerVid = ActivityContext - > CurrentTopology - > GetCornerVertexID ( CornerID ) ;
2020-09-24 00:43:27 -04:00
check ( CornerVid ! = FDynamicMesh3 : : InvalidID ) ;
2021-07-26 17:57:22 -04:00
const FGroupTopology : : FGroup * Group = ActivityContext - > CurrentTopology - > FindGroupByID ( GroupID ) ;
2020-09-24 00:43:27 -04:00
check ( Group & & BoundaryIndex > = 0 & & BoundaryIndex < Group - > Boundaries . Num ( ) ) ;
const FGroupTopology : : FGroupBoundary & Boundary = Group - > Boundaries [ BoundaryIndex ] ;
TArray < FVector3d > AdjacentPoints ;
for ( int32 GroupEdgeID : Boundary . GroupEdges )
{
2021-07-26 17:57:22 -04:00
TArray < int32 > Vertices = ActivityContext - > CurrentTopology - > Edges [ GroupEdgeID ] . Span . Vertices ;
2020-09-24 00:43:27 -04:00
if ( Vertices [ 0 ] = = CornerVid )
{
2021-07-26 17:57:22 -04:00
AdjacentPoints . Add ( ActivityContext - > CurrentMesh - > GetVertex ( Vertices [ 1 ] ) ) ;
2020-09-24 00:43:27 -04:00
}
else if ( Vertices . Last ( ) = = CornerVid )
{
2021-07-26 17:57:22 -04:00
AdjacentPoints . Add ( ActivityContext - > CurrentMesh - > GetVertex ( Vertices [ Vertices . Num ( ) - 2 ] ) ) ;
2020-09-24 00:43:27 -04:00
}
}
check ( AdjacentPoints . Num ( ) = = 2 ) ;
2021-07-26 17:57:22 -04:00
FVector3d CornerPosition = ActivityContext - > CurrentMesh - > GetVertex ( CornerVid ) ;
2021-03-17 19:32:44 -04:00
TangentOut = UE : : Geometry : : Normalized ( CornerPosition - AdjacentPoints [ 0 ] ) ;
TangentOut + = UE : : Geometry : : Normalized ( AdjacentPoints [ 1 ] - CornerPosition ) ;
UE : : Geometry : : Normalize ( TangentOut ) ;
2020-09-24 00:43:27 -04:00
}
2021-07-26 17:57:22 -04:00
bool PolyEditInsertEdgeActivityLocals : : GetSharedBoundary ( const FGroupTopology & Topology ,
2020-09-24 00:43:27 -04:00
const FGroupEdgeInserter : : FGroupEdgeSplitPoint & StartPoint , int32 StartTopologyID , bool bStartIsCorner ,
const FGroupEdgeInserter : : FGroupEdgeSplitPoint & EndPoint , int32 EndTopologyID , bool bEndIsCorner ,
int32 & GroupIDOut , int32 & BoundaryIndexOut )
{
// The start and endpoints could be on the same boundary of multiple groups at
// the same time, and sometimes we won't be able to resolve the ambiguity
// (one example is a sphere split into two equal groups, but could even happen
// with more than two groups when endpoints are corners).
// Sometimes there are things we can do to eliminate some contenders- the best
// approach is probably trying to do a plane cut for all of the options and
// removing those that fail. However, it's worth noting that such issues won't
// arise in the standard application of this tool for low-poly modeling, where
// groups are planar, so it's not worth the bother.
// Instead, we'll just take one of the results arbitrarily, though we will try to
// take one that has a single boundary (this will prefer a cylinder cap over
// a cylinder side).
// TODO: The code would be simpler if we didn't even want to do that filtering- we'd
// just return the first result we found. Should we consider doing that?
GroupIDOut = FDynamicMesh3 : : InvalidID ;
BoundaryIndexOut = FDynamicMesh3 : : InvalidID ;
2021-07-26 17:57:22 -04:00
TArray < TPair < int32 , int32 > > CandidateGroupIDsAndBoundaryIndices ;
2020-09-24 00:43:27 -04:00
if ( bStartIsCorner )
{
// Go through all neighboring groups and their boundaries to find a shared one.
const FGroupTopology : : FCorner & StartCorner = Topology . Corners [ StartTopologyID ] ;
for ( int32 GroupID : StartCorner . NeighbourGroupIDs )
{
const FGroupTopology : : FGroup * Group = Topology . FindGroupByID ( GroupID ) ;
for ( int32 i = 0 ; i < Group - > Boundaries . Num ( ) ; + + i )
{
const FGroupTopology : : FGroupBoundary & Boundary = Group - > Boundaries [ i ] ;
if ( DoesBoundaryContainPoint ( Topology , Boundary , EndTopologyID , bEndIsCorner )
& & DoesBoundaryContainPoint ( Topology , Boundary , StartTopologyID , bStartIsCorner ) )
{
CandidateGroupIDsAndBoundaryIndices . Emplace ( GroupID , i ) ;
break ; // Can't share more than one boundary in the same group
}
}
}
}
else
{
// Start is on an edge, so there are fewer boundaries to look through.
const FGroupTopology : : FGroupEdge & GroupEdge = Topology . Edges [ StartTopologyID ] ;
const FGroupTopology : : FGroup * Group = Topology . FindGroupByID ( GroupEdge . Groups . A ) ;
for ( int32 i = 0 ; i < Group - > Boundaries . Num ( ) ; + + i )
{
const FGroupTopology : : FGroupBoundary & Boundary = Group - > Boundaries [ i ] ;
if ( DoesBoundaryContainPoint ( Topology , Boundary , EndTopologyID , bEndIsCorner )
& & DoesBoundaryContainPoint ( Topology , Boundary , StartTopologyID , bStartIsCorner ) )
{
CandidateGroupIDsAndBoundaryIndices . Emplace ( GroupEdge . Groups . A , i ) ;
break ;
}
}
if ( GroupEdge . Groups . B ! = FDynamicMesh3 : : InvalidID )
{
Group = Topology . FindGroupByID ( GroupEdge . Groups . B ) ;
for ( int32 i = 0 ; i < Group - > Boundaries . Num ( ) ; + + i )
{
const FGroupTopology : : FGroupBoundary & Boundary = Group - > Boundaries [ i ] ;
if ( DoesBoundaryContainPoint ( Topology , Boundary , EndTopologyID , bEndIsCorner )
& & DoesBoundaryContainPoint ( Topology , Boundary , StartTopologyID , bStartIsCorner ) )
{
CandidateGroupIDsAndBoundaryIndices . Emplace ( GroupEdge . Groups . B , i ) ;
break ;
}
}
}
}
if ( CandidateGroupIDsAndBoundaryIndices . Num ( ) = = 0 )
{
return false ;
}
// Prefer a result that has a single boundary if there are multiple.
if ( CandidateGroupIDsAndBoundaryIndices . Num ( ) > 1 )
{
for ( const TPair < int32 , int32 > & GroupIDBoundaryIdxPair : CandidateGroupIDsAndBoundaryIndices )
{
if ( Topology . FindGroupByID ( GroupIDBoundaryIdxPair . Key ) - > Boundaries . Num ( ) = = 1 )
{
GroupIDOut = GroupIDBoundaryIdxPair . Key ;
BoundaryIndexOut = 0 ;
return true ;
}
}
}
GroupIDOut = CandidateGroupIDsAndBoundaryIndices [ 0 ] . Key ;
BoundaryIndexOut = CandidateGroupIDsAndBoundaryIndices [ 0 ] . Value ;
return true ;
}
2021-07-26 17:57:22 -04:00
bool PolyEditInsertEdgeActivityLocals : : DoesBoundaryContainPoint ( const FGroupTopology & Topology ,
2020-09-24 00:43:27 -04:00
const FGroupTopology : : FGroupBoundary & Boundary , int32 PointTopologyID , bool bPointIsCorner )
{
for ( int32 GroupEdgeID : Boundary . GroupEdges )
{
if ( ! bPointIsCorner & & GroupEdgeID = = PointTopologyID )
{
return true ;
}
const FGroupTopology : : FGroupEdge & GroupEdge = Topology . Edges [ GroupEdgeID ] ;
if ( bPointIsCorner & & ( GroupEdge . EndpointCorners . A = = PointTopologyID
| | GroupEdge . EndpointCorners . B = = PointTopologyID ) )
{
return true ;
}
}
return false ;
}
void FGroupEdgeInsertionFirstPointChange : : Revert ( UObject * Object )
{
2021-07-26 17:57:22 -04:00
UPolyEditInsertEdgeActivity * Activity = Cast < UPolyEditInsertEdgeActivity > ( Object ) ;
2020-09-24 00:43:27 -04:00
2023-01-27 14:53:43 -05:00
check ( Activity - > ToolState = = UPolyEditInsertEdgeActivity : : EState : : GettingEnd
| | Activity - > ToolState = = UPolyEditInsertEdgeActivity : : EState : : WaitingForInsertComplete ) ;
2021-07-26 17:57:22 -04:00
Activity - > ToolState = UPolyEditInsertEdgeActivity : : EState : : GettingStart ;
2020-09-24 00:43:27 -04:00
2023-01-27 14:53:43 -05:00
Activity - > ClearPreview ( true ) ;
2020-09-24 00:43:27 -04:00
bHaveDoneUndo = true ;
}
2022-09-28 01:06:15 -04:00
# undef LOCTEXT_NAMESPACE