// Copyright Epic Games, Inc. All Rights Reserved. // HoleFillTool: Fill one or more boundary loops on a selected mesh. Several hole-filling methods are available. #pragma once #include "CoreMinimal.h" #include "InteractiveToolBuilder.h" #include "SingleSelectionTool.h" #include "CleaningOps/HoleFillOp.h" #include "BaseTools/MeshSurfacePointTool.h" #include "BaseBehaviors/BehaviorTargetInterfaces.h" #include "DynamicMesh/DynamicMeshAABBTree3.h" #include "BaseTools/SingleSelectionMeshEditingTool.h" #include "MeshBoundaryLoops.h" #include "HoleFillTool.generated.h" #define UE_API MESHMODELINGTOOLS_API class UBoundarySelectionMechanic; class UDynamicMeshReplacementChangeTarget; class UMeshOpPreviewWithBackgroundCompute; class UDynamicMeshComponent; struct FDynamicMeshOpResult; class UHoleFillTool; /* * Tool builder */ UCLASS(MinimalAPI) class UHoleFillToolBuilder : public USingleSelectionMeshEditingToolBuilder { GENERATED_BODY() public: UE_API virtual USingleSelectionMeshEditingTool* CreateNewTool(const FToolBuilderState& SceneState) const override; UE_API virtual bool CanBuildTool(const FToolBuilderState& SceneState) const override; }; /* * Properties. This class reflects the parameters in FSmoothFillOptions, but is decorated to allow use in the UI system. */ UCLASS(MinimalAPI) class USmoothHoleFillProperties : public UInteractiveToolPropertySet { GENERATED_BODY() public: /** Allow smoothing and remeshing of triangles outside of the fill region */ UPROPERTY(EditAnywhere, Category = SmoothHoleFillOptions) bool bConstrainToHoleInterior; /** Number of vertex rings outside of the fill region to allow remeshing */ UPROPERTY(EditAnywhere, Category = SmoothHoleFillOptions, meta = (UIMin = "0", ClampMin = "0", EditCondition = "!bConstrainToHoleInterior", Delta = 1, LinearDeltaSensitivity = 50)) int RemeshingExteriorRegionWidth; /** Number of vertex rings outside of the fill region to perform smoothing */ UPROPERTY(EditAnywhere, Category = SmoothHoleFillOptions, meta = (UIMin = "0", ClampMin = "0", Delta = 1, LinearDeltaSensitivity = 50)) int SmoothingExteriorRegionWidth; /** Number of vertex rings away from the fill region boundary to constrain smoothing */ UPROPERTY(EditAnywhere, Category = SmoothHoleFillOptions, meta = (UIMin = "0", ClampMin = "0", Delta = 1, LinearDeltaSensitivity = 50)) int SmoothingInteriorRegionWidth; /** Desired Smoothness. This is not a linear quantity, but larger numbers produce smoother results */ UPROPERTY(EditAnywhere, Category = SmoothHoleFillOptions, meta = (UIMin = "0.0", UIMax = "1.0", ClampMin = "0.0", ClampMax = "100.0")) float InteriorSmoothness; /** Relative triangle density of fill region */ UPROPERTY(EditAnywhere, Category = SmoothHoleFillOptions, meta = (UIMin = "0.001", UIMax = "10.0", ClampMin = "0.001", ClampMax = "10.0")) double FillDensityScalar = 1.0; /** * Whether to project to the original mesh during post-smooth remeshing. This can be expensive on large meshes with * many holes. */ UPROPERTY(EditAnywhere, Category = SmoothHoleFillOptions) bool bProjectDuringRemesh = false; // Set default property values USmoothHoleFillProperties() { // Create a default FSmoothFillOptions and populate this class with its defaults. UE::Geometry::FSmoothFillOptions DefaultOptionsObject; bConstrainToHoleInterior = DefaultOptionsObject.bConstrainToHoleInterior; RemeshingExteriorRegionWidth = DefaultOptionsObject.RemeshingExteriorRegionWidth; SmoothingExteriorRegionWidth = DefaultOptionsObject.SmoothingExteriorRegionWidth; SmoothingInteriorRegionWidth = DefaultOptionsObject.SmoothingInteriorRegionWidth; FillDensityScalar = DefaultOptionsObject.FillDensityScalar; InteriorSmoothness = DefaultOptionsObject.InteriorSmoothness; bProjectDuringRemesh = DefaultOptionsObject.bProjectDuringRemesh; } UE::Geometry::FSmoothFillOptions ToSmoothFillOptions() const { UE::Geometry::FSmoothFillOptions Options; Options.bConstrainToHoleInterior = bConstrainToHoleInterior; Options.RemeshingExteriorRegionWidth = RemeshingExteriorRegionWidth; Options.SmoothingExteriorRegionWidth = SmoothingExteriorRegionWidth; Options.SmoothingInteriorRegionWidth = SmoothingInteriorRegionWidth; Options.InteriorSmoothness = InteriorSmoothness; Options.FillDensityScalar = FillDensityScalar; Options.bProjectDuringRemesh = bProjectDuringRemesh; return Options; } }; UCLASS(MinimalAPI) class UHoleFillToolProperties : public UInteractiveToolPropertySet { GENERATED_BODY() public: UPROPERTY(EditAnywhere, Category = Options) EHoleFillOpFillType FillType = EHoleFillOpFillType::Minimal; /** Clean up triangles that have no neighbors */ UPROPERTY(EditAnywhere, Category = Options) bool bRemoveIsolatedTriangles = false; /** Identify and quickly fill single-triangle holes */ UPROPERTY(EditAnywhere, Category = Options) bool bQuickFillSmallHoles = false; }; UENUM() enum class EHoleFillToolActions { NoAction, SelectAll, ClearSelection }; UCLASS(MinimalAPI) class UHoleFillToolActions : public UInteractiveToolPropertySet { GENERATED_BODY() TWeakObjectPtr ParentTool; public: void Initialize(UHoleFillTool* ParentToolIn) { ParentTool = ParentToolIn; } UE_API void PostAction(EHoleFillToolActions Action); UFUNCTION(CallInEditor, Category = SelectionEdits, meta = (DisplayName = "Select All", DisplayPriority = 1)) void SelectAll() { PostAction(EHoleFillToolActions::SelectAll); } UFUNCTION(CallInEditor, Category = SelectionEdits, meta = (DisplayName = "Clear", DisplayPriority = 1)) void Clear() { PostAction(EHoleFillToolActions::ClearSelection); } }; UCLASS(MinimalAPI) class UHoleFillStatisticsProperties : public UInteractiveToolPropertySet { GENERATED_BODY() public: UPROPERTY(VisibleAnywhere, Category = HoleFillStatistics, meta = (NoResetToDefault)) FString InitialHoles; UPROPERTY(VisibleAnywhere, Category = HoleFillStatistics, meta = (NoResetToDefault)) FString SelectedHoles; UPROPERTY(VisibleAnywhere, Category = HoleFillStatistics, meta = (NoResetToDefault)) FString SuccessfulFills; UPROPERTY(VisibleAnywhere, Category = HoleFillStatistics, meta = (NoResetToDefault)) FString FailedFills; UPROPERTY(VisibleAnywhere, Category = HoleFillStatistics, meta = (NoResetToDefault)) FString RemainingHoles; UE_API void Initialize(const UHoleFillTool& HoleFillTool); UE_API void Update(const UHoleFillTool& HoleFillTool, const UE::Geometry::FHoleFillOp& HoleFillOp); }; /* * Operator factory */ UCLASS(MinimalAPI) class UHoleFillOperatorFactory : public UObject, public UE::Geometry::IDynamicMeshOperatorFactory { GENERATED_BODY() public: UE_API TUniquePtr MakeNewOperator() override; UPROPERTY() TObjectPtr FillTool; }; /* * Tool * Inherit from IClickBehaviorTarget so we can click on boundary loops. */ UCLASS(MinimalAPI) class UHoleFillTool : public USingleSelectionMeshEditingTool { GENERATED_BODY() public: // UMeshSurfacePointTool UE_API void Setup() override; UE_API void OnTick(float DeltaTime) override; UE_API void OnPropertyModified(UObject* PropertySet, FProperty* Property) override; bool HasCancel() const override { return true; } bool HasAccept() const override { return true; } UE_API bool CanAccept() const override; UE_API void OnShutdown(EToolShutdownType ShutdownType) override; UE_API void OnSelectionModified(); UE_API virtual void RequestAction(EHoleFillToolActions Action); protected: friend UHoleFillOperatorFactory; friend UHoleFillToolBuilder; friend UHoleFillStatisticsProperties; UPROPERTY() TObjectPtr SmoothHoleFillProperties = nullptr; UPROPERTY() TObjectPtr Properties = nullptr; UPROPERTY() TObjectPtr Actions = nullptr; UPROPERTY() TObjectPtr Statistics = nullptr; UPROPERTY() TObjectPtr Preview = nullptr; UPROPERTY() TObjectPtr SelectionMechanic = nullptr; // Input mesh. Ownership shared with Op. TSharedPtr OriginalMesh; // UV Scale factor is cached based on the bounding box of the mesh before any fills are performed float MeshUVScaleFactor = 0.0f; // Used for hit querying UE::Geometry::FDynamicMeshAABBTree3 MeshSpatial; TSet NewTriangleIDs; // Create the Preview object UE_API void SetupPreview(); // Invalidate background compute result (some input changed) UE_API void InvalidatePreviewResult(); bool bHavePendingAction = false; EHoleFillToolActions PendingAction; UE_API virtual void ApplyAction(EHoleFillToolActions ActionType); UE_API void SelectAll(); UE_API void ClearSelection(); TUniquePtr BoundaryLoops; struct FSelectedBoundaryLoop { int32 EdgeTopoID; TArray EdgeIDs; }; TArray ActiveBoundaryLoopSelection; UE_API void UpdateActiveBoundaryLoopSelection(); // Just call the SelectionMechanism's Render function UE_API void Render(IToolsContextRenderAPI* RenderAPI) override; // Populate an array of Edge Loops to be processed by an FHoleFillOp. Returns the edge loops currently selected // by this tool. UE_API void GetLoopsToFill(TArray& OutLoops) const; }; #undef UE_API