2020-04-18 18:42:59 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "HoleFillTool.h"
# include "ToolBuilderUtil.h"
# include "InteractiveToolManager.h"
# include "MeshDescriptionToDynamicMesh.h"
# include "ToolSetupUtil.h"
2021-06-13 00:36:02 -04:00
# include "DynamicMesh/MeshNormals.h"
2020-04-18 18:42:59 -04:00
# include "Changes/DynamicMeshChangeTarget.h"
# include "DynamicMeshToMeshDescription.h"
# include "BaseBehaviors/SingleClickBehavior.h"
# include "BaseBehaviors/MouseHoverBehavior.h"
# include "MeshBoundaryLoops.h"
# include "MeshOpPreviewHelpers.h"
# include "Selection/PolygonSelectionMechanic.h"
2021-03-24 11:11:02 -04:00
# include "TargetInterfaces/MaterialProvider.h"
# include "TargetInterfaces/PrimitiveComponentBackedTarget.h"
2021-12-06 12:42:19 -05:00
# include "ModelingToolTargetUtil.h"
2021-03-24 11:11:02 -04:00
2021-03-09 19:33:56 -04:00
using namespace UE : : Geometry ;
2020-04-18 18:42:59 -04:00
# define LOCTEXT_NAMESPACE "UHoleFillTool"
/*
* ToolBuilder
*/
2021-03-24 11:11:02 -04:00
USingleSelectionMeshEditingTool * UHoleFillToolBuilder : : CreateNewTool ( const FToolBuilderState & SceneState ) const
2020-04-18 18:42:59 -04:00
{
2021-03-24 11:11:02 -04:00
return NewObject < UHoleFillTool > ( SceneState . ToolManager ) ;
2020-04-18 18:42:59 -04:00
}
/*
* Tool properties
*/
void UHoleFillToolActions : : PostAction ( EHoleFillToolActions Action )
{
if ( ParentTool . IsValid ( ) )
{
ParentTool - > RequestAction ( Action ) ;
}
}
2020-08-11 01:36:57 -04:00
void UHoleFillStatisticsProperties : : Initialize ( const UHoleFillTool & HoleFillTool )
{
if ( HoleFillTool . Topology = = nullptr )
{
return ;
}
int Initial = HoleFillTool . Topology - > Edges . Num ( ) ;
int Selected = 0 ;
int Successful = 0 ;
int Failed = 0 ;
int Remaining = Initial ;
InitialHoles = FString : : FromInt ( Initial ) ;
SelectedHoles = FString : : FromInt ( Selected ) ;
SuccessfulFills = FString : : FromInt ( Successful ) ;
FailedFills = FString : : FromInt ( Failed ) ;
RemainingHoles = FString : : FromInt ( Remaining ) ;
}
void UHoleFillStatisticsProperties : : Update ( const UHoleFillTool & HoleFillTool , const FHoleFillOp & Op )
{
if ( HoleFillTool . Topology = = nullptr )
{
return ;
}
int Initial = HoleFillTool . Topology - > Edges . Num ( ) ;
int Selected = Op . Loops . Num ( ) ;
int Failed = Op . NumFailedLoops ;
int Successful = Selected - Failed ;
int Remaining = Initial - Successful ;
InitialHoles = FString : : FromInt ( Initial ) ;
SelectedHoles = FString : : FromInt ( Selected ) ;
SuccessfulFills = FString : : FromInt ( Successful ) ;
FailedFills = FString : : FromInt ( Failed ) ;
RemainingHoles = FString : : FromInt ( Remaining ) ;
}
2020-04-18 18:42:59 -04:00
/*
* Op Factory
*/
TUniquePtr < FDynamicMeshOperator > UHoleFillOperatorFactory : : MakeNewOperator ( )
{
TUniquePtr < FHoleFillOp > FillOp = MakeUnique < FHoleFillOp > ( ) ;
2021-03-24 11:11:02 -04:00
FTransform LocalToWorld = Cast < IPrimitiveComponentBackedTarget > ( FillTool - > Target ) - > GetWorldTransform ( ) ;
2022-01-29 14:37:53 -05:00
FillOp - > SetResultTransform ( ( FTransformSRT3d ) LocalToWorld ) ;
2020-06-23 18:40:00 -04:00
FillOp - > OriginalMesh = FillTool - > OriginalMesh ;
2020-04-18 18:42:59 -04:00
FillOp - > MeshUVScaleFactor = FillTool - > MeshUVScaleFactor ;
FillTool - > GetLoopsToFill ( FillOp - > Loops ) ;
2020-06-23 18:40:00 -04:00
FillOp - > FillType = FillTool - > Properties - > FillType ;
FillOp - > FillOptions . bRemoveIsolatedTriangles = FillTool - > Properties - > bRemoveIsolatedTriangles ;
2021-03-30 17:39:22 -04:00
FillOp - > FillOptions . bQuickFillSmallHoles = FillTool - > Properties - > bQuickFillSmallHoles ;
2020-06-23 18:40:00 -04:00
// Smooth fill properties
FillOp - > SmoothFillOptions = FillTool - > SmoothHoleFillProperties - > ToSmoothFillOptions ( ) ;
2020-04-18 18:42:59 -04:00
return FillOp ;
}
/*
* Tool
*/
void UHoleFillTool : : Setup ( )
{
USingleSelectionTool : : Setup ( ) ;
2021-03-24 11:11:02 -04:00
if ( ! Target )
2020-04-18 18:42:59 -04:00
{
return ;
}
2020-06-23 18:40:00 -04:00
// create mesh to operate on
2021-02-17 11:50:23 -04:00
OriginalMesh = MakeShared < FDynamicMesh3 , ESPMode : : ThreadSafe > ( ) ;
2020-06-23 18:40:00 -04:00
FMeshDescriptionToDynamicMesh Converter ;
2021-12-06 12:42:19 -05:00
Converter . Convert ( UE : : ToolTarget : : GetMeshDescription ( Target ) , * OriginalMesh ) ;
2020-06-23 18:40:00 -04:00
// initialize properties
Properties = NewObject < UHoleFillToolProperties > ( this , TEXT ( " Hole Fill Settings " ) ) ;
Properties - > RestoreProperties ( this ) ;
AddToolPropertySource ( Properties ) ;
SetToolPropertySourceEnabled ( Properties , true ) ;
SmoothHoleFillProperties = NewObject < USmoothHoleFillProperties > ( this , TEXT ( " Smooth Fill Settings " ) ) ;
SmoothHoleFillProperties - > RestoreProperties ( this ) ;
AddToolPropertySource ( SmoothHoleFillProperties ) ;
SetToolPropertySourceEnabled ( SmoothHoleFillProperties , Properties - > FillType = = EHoleFillOpFillType : : Smooth ) ;
// Set up a callback for when the type of fill changes
Properties - > WatchProperty ( Properties - > FillType ,
[ this ] ( EHoleFillOpFillType NewType )
{
SetToolPropertySourceEnabled ( SmoothHoleFillProperties , ( NewType = = EHoleFillOpFillType : : Smooth ) ) ;
} ) ;
Actions = NewObject < UHoleFillToolActions > ( this , TEXT ( " Hole Fill Actions " ) ) ;
Actions - > Initialize ( this ) ;
AddToolPropertySource ( Actions ) ;
SetToolPropertySourceEnabled ( Actions , true ) ;
2020-08-11 01:36:57 -04:00
Statistics = NewObject < UHoleFillStatisticsProperties > ( ) ;
AddToolPropertySource ( Statistics ) ;
SetToolPropertySourceEnabled ( Statistics , true ) ;
2020-06-23 18:40:00 -04:00
ToolPropertyObjects . Add ( this ) ;
2020-04-18 18:42:59 -04:00
// initialize hit query
2020-06-23 18:40:00 -04:00
MeshSpatial . SetMesh ( OriginalMesh . Get ( ) ) ;
2020-04-18 18:42:59 -04:00
// initialize topology
2020-06-23 18:40:00 -04:00
Topology = MakeUnique < FBasicTopology > ( OriginalMesh . Get ( ) , false ) ;
bool bTopologyOK = Topology - > RebuildTopology ( ) ;
2020-04-18 18:42:59 -04:00
// Set up selection mechanic to find and select edges
2021-03-24 11:11:02 -04:00
IPrimitiveComponentBackedTarget * TargetComponent = Cast < IPrimitiveComponentBackedTarget > ( Target ) ;
2020-04-18 18:42:59 -04:00
SelectionMechanic = NewObject < UPolygonSelectionMechanic > ( this ) ;
SelectionMechanic - > bAddSelectionFilterPropertiesToParentTool = false ;
SelectionMechanic - > Setup ( this ) ;
SelectionMechanic - > Properties - > bSelectEdges = true ;
SelectionMechanic - > Properties - > bSelectFaces = false ;
SelectionMechanic - > Properties - > bSelectVertices = false ;
2020-06-23 18:40:00 -04:00
SelectionMechanic - > Initialize ( OriginalMesh . Get ( ) ,
2022-02-24 15:01:41 -05:00
( FTransform3d ) TargetComponent - > GetWorldTransform ( ) ,
2022-01-28 10:18:10 -05:00
GetTargetWorld ( ) ,
2020-04-18 18:42:59 -04:00
Topology . Get ( ) ,
2020-11-13 12:09:55 -04:00
[ this ] ( ) { return & MeshSpatial ; }
2020-04-18 18:42:59 -04:00
) ;
2020-11-13 12:09:55 -04:00
// allow toggling selection without modifier key
SelectionMechanic - > SetShouldAddToSelectionFunc ( [ ] ( ) { return true ; } ) ;
SelectionMechanic - > SetShouldRemoveFromSelectionFunc ( [ ] ( ) { return true ; } ) ;
SelectionMechanic - > OnSelectionChanged . AddUObject ( this , & UHoleFillTool : : OnSelectionModified ) ;
2020-04-18 18:42:59 -04:00
// Store a UV scale based on the original mesh bounds
2020-06-23 18:40:00 -04:00
MeshUVScaleFactor = ( 1.0 / OriginalMesh - > GetBounds ( ) . MaxDim ( ) ) ;
2020-04-18 18:42:59 -04:00
2020-08-11 01:36:57 -04:00
Statistics - > Initialize ( * this ) ;
2020-04-18 18:42:59 -04:00
// initialize the PreviewMesh+BackgroundCompute object
SetupPreview ( ) ;
2020-06-23 18:40:00 -04:00
InvalidatePreviewResult ( ) ;
if ( ! bTopologyOK )
{
GetToolManager ( ) - > DisplayMessage (
LOCTEXT ( " LoopFindError " , " Error finding hole boundary loops. " ) ,
EToolMessageLevel : : UserWarning ) ;
SetToolPropertySourceEnabled ( Properties , false ) ;
SetToolPropertySourceEnabled ( SmoothHoleFillProperties , false ) ;
SetToolPropertySourceEnabled ( Actions , false ) ;
}
else if ( Topology - > Edges . Num ( ) = = 0 )
{
GetToolManager ( ) - > DisplayMessage (
LOCTEXT ( " NoHoleNotification " , " This mesh has no holes to fill. " ) ,
EToolMessageLevel : : UserWarning ) ;
SetToolPropertySourceEnabled ( Properties , false ) ;
SetToolPropertySourceEnabled ( SmoothHoleFillProperties , false ) ;
SetToolPropertySourceEnabled ( Actions , false ) ;
}
else
{
2020-08-11 01:36:57 -04:00
GetToolManager ( ) - > DisplayMessage (
2020-10-22 19:19:16 -04:00
LOCTEXT ( " HoleFillToolHighlighted " , " Holes in the mesh are highlighted. Select individual holes to fill or use the Select All or Clear buttons. " ) ,
2020-08-11 01:36:57 -04:00
EToolMessageLevel : : UserNotification ) ;
2020-06-23 18:40:00 -04:00
// Hide all meshes except the Preview
2021-03-24 11:11:02 -04:00
TargetComponent - > SetOwnerVisibility ( false ) ;
2020-06-23 18:40:00 -04:00
}
2020-04-18 18:42:59 -04:00
2021-02-08 17:02:09 -04:00
SetToolDisplayName ( LOCTEXT ( " ToolName " , " Fill Holes " ) ) ;
2020-09-24 00:43:27 -04:00
GetToolManager ( ) - > DisplayMessage (
2020-10-22 19:19:16 -04:00
LOCTEXT ( " HoleFillToolDescription " , " Fill Holes in the selected Mesh by adding triangles. Click on individual holes to fill them, or use the Select All button to fill all holes. " ) ,
2020-09-24 00:43:27 -04:00
EToolMessageLevel : : UserNotification ) ;
2020-04-18 18:42:59 -04:00
}
void UHoleFillTool : : OnTick ( float DeltaTime )
{
2020-06-23 18:40:00 -04:00
if ( Preview )
{
Preview - > Tick ( DeltaTime ) ;
}
2020-04-18 18:42:59 -04:00
if ( bHavePendingAction )
{
ApplyAction ( PendingAction ) ;
bHavePendingAction = false ;
PendingAction = EHoleFillToolActions : : NoAction ;
}
}
void UHoleFillTool : : OnPropertyModified ( UObject * PropertySet , FProperty * Property )
{
2020-06-23 18:40:00 -04:00
InvalidatePreviewResult ( ) ;
2020-04-18 18:42:59 -04:00
}
bool UHoleFillTool : : CanAccept ( ) const
{
2020-11-24 18:42:39 -04:00
return Super : : CanAccept ( ) & & Preview - > HaveValidResult ( ) ;
2020-04-18 18:42:59 -04:00
}
2022-01-28 18:40:54 -05:00
void UHoleFillTool : : OnShutdown ( EToolShutdownType ShutdownType )
2020-04-18 18:42:59 -04:00
{
Properties - > SaveProperties ( this ) ;
2020-06-23 18:40:00 -04:00
SmoothHoleFillProperties - > SaveProperties ( this ) ;
if ( SelectionMechanic )
{
SelectionMechanic - > Shutdown ( ) ;
}
2020-04-18 18:42:59 -04:00
2021-03-24 11:11:02 -04:00
Cast < IPrimitiveComponentBackedTarget > ( Target ) - > SetOwnerVisibility ( true ) ;
2020-04-18 18:42:59 -04:00
FDynamicMeshOpResult Result = Preview - > Shutdown ( ) ;
if ( ShutdownType = = EToolShutdownType : : Accept )
{
GetToolManager ( ) - > BeginUndoTransaction ( LOCTEXT ( " HoleFillToolTransactionName " , " Hole Fill Tool " ) ) ;
check ( Result . Mesh . Get ( ) ! = nullptr ) ;
2021-12-06 12:42:19 -05:00
UE : : ToolTarget : : CommitMeshDescriptionUpdateViaDynamicMesh ( Target , * Result . Mesh . Get ( ) , true ) ;
2020-04-18 18:42:59 -04:00
GetToolManager ( ) - > EndUndoTransaction ( ) ;
}
}
2020-11-13 12:09:55 -04:00
void UHoleFillTool : : OnSelectionModified ( )
2020-04-18 18:42:59 -04:00
{
2020-11-13 12:09:55 -04:00
UpdateActiveBoundaryLoopSelection ( ) ;
InvalidatePreviewResult ( ) ;
2020-04-18 18:42:59 -04:00
}
void UHoleFillTool : : RequestAction ( EHoleFillToolActions ActionType )
{
if ( bHavePendingAction )
{
return ;
}
PendingAction = ActionType ;
bHavePendingAction = true ;
}
2020-06-23 18:40:00 -04:00
void UHoleFillTool : : InvalidatePreviewResult ( )
{
// Clear any warning message
GetToolManager ( ) - > DisplayMessage ( { } , EToolMessageLevel : : UserWarning ) ;
Preview - > InvalidateResult ( ) ;
}
2020-04-18 18:42:59 -04:00
void UHoleFillTool : : SetupPreview ( )
{
UHoleFillOperatorFactory * OpFactory = NewObject < UHoleFillOperatorFactory > ( ) ;
OpFactory - > FillTool = this ;
Preview = NewObject < UMeshOpPreviewWithBackgroundCompute > ( OpFactory , " Preview " ) ;
2022-01-28 10:18:10 -05:00
Preview - > Setup ( GetTargetWorld ( ) , OpFactory ) ;
2021-10-07 22:25:54 -04:00
ToolSetupUtil : : ApplyRenderingConfigurationToPreview ( Preview - > PreviewMesh , Target ) ;
2020-04-18 18:42:59 -04:00
FComponentMaterialSet MaterialSet ;
2021-03-24 11:11:02 -04:00
Cast < IMaterialProvider > ( Target ) - > GetMaterialSet ( MaterialSet ) ;
2020-04-18 18:42:59 -04:00
Preview - > ConfigureMaterials ( MaterialSet . Materials ,
ToolSetupUtil : : GetDefaultWorkingMaterial ( GetToolManager ( ) )
) ;
// configure secondary render material
UMaterialInterface * SelectionMaterial = ToolSetupUtil : : GetSelectionMaterial ( FLinearColor ( 0.8f , 0.75f , 0.0f ) , GetToolManager ( ) ) ;
if ( SelectionMaterial ! = nullptr )
{
Preview - > PreviewMesh - > SetSecondaryRenderMaterial ( SelectionMaterial ) ;
}
// enable secondary triangle buffers
Preview - > OnOpCompleted . AddLambda (
[ this ] ( const FDynamicMeshOperator * Op )
{
2020-06-23 18:40:00 -04:00
const FHoleFillOp * HoleFillOp = ( const FHoleFillOp * ) ( Op ) ;
NewTriangleIDs = TSet < int32 > ( HoleFillOp - > NewTriangles ) ;
// Notify the user if any holes could not be filled
if ( HoleFillOp - > NumFailedLoops > 0 )
{
GetToolManager ( ) - > DisplayMessage (
FText : : Format ( LOCTEXT ( " FillFailNotification " , " Failed to fill {0} holes. " ) , HoleFillOp - > NumFailedLoops ) ,
EToolMessageLevel : : UserWarning ) ;
}
2020-08-11 01:36:57 -04:00
Statistics - > Update ( * this , * HoleFillOp ) ;
2020-04-18 18:42:59 -04:00
} ) ;
Preview - > PreviewMesh - > EnableSecondaryTriangleBuffers (
[ this ] ( const FDynamicMesh3 * Mesh , int32 TriangleID )
{
return NewTriangleIDs . Contains ( TriangleID ) ;
} ) ;
// set initial preview to un-processed mesh
2021-03-24 11:11:02 -04:00
Preview - > PreviewMesh - > SetTransform ( Cast < IPrimitiveComponentBackedTarget > ( Target ) - > GetWorldTransform ( ) ) ;
2020-06-23 18:40:00 -04:00
Preview - > PreviewMesh - > UpdatePreview ( OriginalMesh . Get ( ) ) ;
2020-04-18 18:42:59 -04:00
Preview - > SetVisibility ( true ) ;
}
void UHoleFillTool : : ApplyAction ( EHoleFillToolActions ActionType )
{
switch ( ActionType )
{
case EHoleFillToolActions : : SelectAll :
SelectAll ( ) ;
break ;
case EHoleFillToolActions : : ClearSelection :
ClearSelection ( ) ;
break ;
}
}
void UHoleFillTool : : SelectAll ( )
{
FGroupTopologySelection NewSelection ;
for ( int32 i = 0 ; i < Topology - > Edges . Num ( ) ; + + i )
{
NewSelection . SelectedEdgeIDs . Add ( i ) ;
}
SelectionMechanic - > SetSelection ( NewSelection ) ;
UpdateActiveBoundaryLoopSelection ( ) ;
2020-06-23 18:40:00 -04:00
InvalidatePreviewResult ( ) ;
2020-04-18 18:42:59 -04:00
}
void UHoleFillTool : : ClearSelection ( )
{
SelectionMechanic - > ClearSelection ( ) ;
UpdateActiveBoundaryLoopSelection ( ) ;
2020-06-23 18:40:00 -04:00
InvalidatePreviewResult ( ) ;
2020-04-18 18:42:59 -04:00
}
void UHoleFillTool : : UpdateActiveBoundaryLoopSelection ( )
{
ActiveBoundaryLoopSelection . Reset ( ) ;
const FGroupTopologySelection & ActiveSelection = SelectionMechanic - > GetActiveSelection ( ) ;
int NumEdges = ActiveSelection . SelectedEdgeIDs . Num ( ) ;
if ( NumEdges = = 0 )
{
return ;
}
ActiveBoundaryLoopSelection . Reserve ( NumEdges ) ;
2020-09-24 00:43:27 -04:00
for ( int32 EdgeID : ActiveSelection . SelectedEdgeIDs )
2020-04-18 18:42:59 -04:00
{
if ( Topology - > IsBoundaryEdge ( EdgeID ) )
{
FSelectedBoundaryLoop & Loop = ActiveBoundaryLoopSelection . Emplace_GetRef ( ) ;
Loop . EdgeTopoID = EdgeID ;
Loop . EdgeIDs = Topology - > GetGroupEdgeEdges ( EdgeID ) ;
}
}
}
void UHoleFillTool : : Render ( IToolsContextRenderAPI * RenderAPI )
{
2020-06-23 18:40:00 -04:00
if ( SelectionMechanic )
{
SelectionMechanic - > Render ( RenderAPI ) ;
}
2020-04-18 18:42:59 -04:00
}
void UHoleFillTool : : GetLoopsToFill ( TArray < FEdgeLoop > & OutLoops ) const
{
OutLoops . Reset ( ) ;
2020-06-23 18:40:00 -04:00
FMeshBoundaryLoops BoundaryLoops ( OriginalMesh . Get ( ) ) ;
2020-04-18 18:42:59 -04:00
for ( const FSelectedBoundaryLoop & FillEdge : ActiveBoundaryLoopSelection )
{
2020-06-23 18:40:00 -04:00
if ( OriginalMesh - > IsBoundaryEdge ( FillEdge . EdgeIDs [ 0 ] ) ) // may no longer be boundary due to previous fill
2020-04-18 18:42:59 -04:00
{
int32 LoopID = BoundaryLoops . FindLoopContainingEdge ( FillEdge . EdgeIDs [ 0 ] ) ;
if ( LoopID > = 0 )
{
OutLoops . Add ( BoundaryLoops . Loops [ LoopID ] ) ;
}
}
}
}
# undef LOCTEXT_NAMESPACE