// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "BaseTools/SingleTargetWithSelectionTool.h" #include "MeshOpPreviewHelpers.h" #include "ToolDataVisualizer.h" #include "ParameterizationOps/UVProjectionOp.h" #include "Properties/MeshMaterialProperties.h" #include "Properties/MeshUVChannelProperties.h" #include "Drawing/PreviewGeometryActor.h" #include "Selection/SelectClickedAction.h" #include "OrientedBoxTypes.h" #include "UVProjectionTool.generated.h" #define UE_API MESHMODELINGTOOLS_API // Forward declarations struct FMeshDescription; class UDynamicMeshComponent; class UCombinedTransformGizmo; class UTransformProxy; class USingleClickInputBehavior; class UUVProjectionTool; /** * */ UCLASS(MinimalAPI) class UUVProjectionToolBuilder : public USingleTargetWithSelectionToolBuilder { GENERATED_BODY() public: UE_API virtual USingleTargetWithSelectionTool* CreateNewTool(const FToolBuilderState& SceneState) const override; virtual bool RequiresInputSelection() const override { return false; } UE_API virtual bool CanBuildTool(const FToolBuilderState& SceneState) const override; }; UENUM() enum class EUVProjectionToolActions { NoAction, AutoFit, AutoFitAlign, Reset }; UCLASS(MinimalAPI) class UUVProjectionToolEditActions : public UInteractiveToolPropertySet { GENERATED_BODY() public: TWeakObjectPtr ParentTool; void Initialize(UUVProjectionTool* ParentToolIn) { ParentTool = ParentToolIn; } UE_API void PostAction(EUVProjectionToolActions Action); /** Automatically fit the UV Projection Dimensions based on the current projection orientation */ UFUNCTION(CallInEditor, Category = Actions, meta = (DisplayName = "AutoFit", DisplayPriority = 1)) void AutoFit() { PostAction(EUVProjectionToolActions::AutoFit); } /** Automatically orient the projection and then automatically fit the UV Projection Dimensions */ UFUNCTION(CallInEditor, Category = Actions, meta = (DisplayName = "AutoFitAlign", DisplayPriority = 2)) void AutoFitAlign() { PostAction(EUVProjectionToolActions::AutoFitAlign); } /** Re-initialize the projection based on the UV Projection Initialization property */ UFUNCTION(CallInEditor, Category = Actions, meta = (DisplayName = "Reset", DisplayPriority = 3)) void Reset() { PostAction(EUVProjectionToolActions::Reset); } }; UENUM() enum class EUVProjectionToolInitializationMode { /** Initialize projection to bounding box center */ Default, /** Initialize projection based on previous usage of the Project tool */ UsePrevious, /** Initialize projection using Auto Fitting for the initial projection type */ AutoFit, /** Initialize projection using Auto Fitting with Alignment for the initial projection type */ AutoFitAlign }; /** * Standard properties */ UCLASS(MinimalAPI) class UUVProjectionToolProperties : public UInteractiveToolPropertySet { GENERATED_BODY() public: /** Shape and/or algorithm to use for UV projection */ UPROPERTY(EditAnywhere, Category = "UV Projection") EUVProjectionMethod ProjectionType = EUVProjectionMethod::Plane; /** Width, length, and height of the projection shape before rotation */ UPROPERTY(EditAnywhere, Category = "UV Projection", meta = (Delta = 0.5, LinearDeltaSensitivity = 1)) FVector Dimensions = FVector(100.0f, 100.0f, 100.0f); /** If true, changes to Dimensions result in all components be changed proportionally */ UPROPERTY(EditAnywhere, Category = "UV Projection") bool bProportionalDimensions = false; /** Determines how projection settings will be initialized; this only takes effect if the projection shape dimensions or position are unchanged */ UPROPERTY(EditAnywhere, Category = "UV Projection") EUVProjectionToolInitializationMode Initialization = EUVProjectionToolInitializationMode::Default; // // Cylinder projection options // /** Angle in degrees to determine whether faces should be assigned to the cylinder or the flat end caps */ UPROPERTY(EditAnywhere, Category = CylinderProjection, meta = (DisplayName = "Split Angle", UIMin = "0", UIMax = "90", EditCondition = "ProjectionType == EUVProjectionMethod::Cylinder", EditConditionHides)) float CylinderSplitAngle = 45.0f; // // ExpMap projection options // /** Blend between surface normals and projection normal; ExpMap projection becomes Plane projection when this value is 1 */ UPROPERTY(EditAnywhere, Category = "ExpMap Projection", meta = (DisplayName = "Normal Blending", UIMin = "0", UIMax = "1", EditCondition = "ProjectionType == EUVProjectionMethod::ExpMap", EditConditionHides)) float ExpMapNormalBlending = 0.0f; /** Number of smoothing steps to apply; this slightly increases distortion but produces more stable results. */ UPROPERTY(EditAnywhere, Category = "ExpMap Projection", meta = (DisplayName = "Smoothing Steps", UIMin = "0", UIMax = "100", EditCondition = "ProjectionType == EUVProjectionMethod::ExpMap", EditConditionHides)) int ExpMapSmoothingSteps = 0; /** Smoothing parameter; larger values result in faster smoothing in each step. */ UPROPERTY(EditAnywhere, Category = "ExpMap Projection", meta = (DisplayName = "Smoothing Alpha", UIMin = "0", UIMax = "1", EditCondition = "ProjectionType == EUVProjectionMethod::ExpMap", EditConditionHides)) float ExpMapSmoothingAlpha = 0.25f; // // UV-space transform options // /** Rotation in degrees applied after computing projection */ UPROPERTY(EditAnywhere, Category = "UV Transform", meta = (ClampMin = -360, ClampMax = 360)) float Rotation = 0.0; /** Scaling applied after computing projection */ UPROPERTY(EditAnywhere, Category = "UV Transform", meta = (Delta = 0.01, LinearDeltaSensitivity = 1)) FVector2D Scale = FVector2D::UnitVector; /** Translation applied after computing projection */ UPROPERTY(EditAnywhere, Category = "UV Transform") FVector2D Translation = FVector2D::ZeroVector; // // Saved State. These are used internally to support UsePrevious initialization mode // UPROPERTY() FVector SavedDimensions = FVector::ZeroVector; UPROPERTY() bool bSavedProportionalDimensions = false; UPROPERTY() FTransform SavedTransform; }; /** * Factory with enough info to spawn the background-thread Operator to do a chunk of work for the tool * stores a pointer to the tool and enough info to know which specific operator it should spawn */ UCLASS(MinimalAPI) class UUVProjectionOperatorFactory : public UObject, public UE::Geometry::IDynamicMeshOperatorFactory { GENERATED_BODY() public: // IDynamicMeshOperatorFactory API UE_API virtual TUniquePtr MakeNewOperator() override; UPROPERTY() TObjectPtr Tool; }; /** * UV projection tool */ UCLASS(MinimalAPI) class UUVProjectionTool : public USingleTargetWithSelectionTool { GENERATED_BODY() public: friend UUVProjectionOperatorFactory; UE_API virtual void Setup() override; UE_API virtual void OnShutdown(EToolShutdownType ShutdownType) override; UE_API virtual void OnTick(float DeltaTime) override; UE_API virtual void Render(IToolsContextRenderAPI* RenderAPI) override; UE_API virtual void OnPropertyModified(UObject* PropertySet, FProperty* Property) override; virtual bool HasCancel() const override { return true; } virtual bool HasAccept() const override { return true; } UE_API virtual bool CanAccept() const override; UE_API virtual void RequestAction(EUVProjectionToolActions ActionType); protected: UPROPERTY() TObjectPtr UVChannelProperties = nullptr; UPROPERTY() TObjectPtr BasicProperties = nullptr; UPROPERTY() TObjectPtr EditActions = nullptr; UPROPERTY() TObjectPtr MaterialSettings = nullptr; UPROPERTY() TObjectPtr Preview = nullptr; UPROPERTY() TObjectPtr CheckerMaterial = nullptr; UPROPERTY() TObjectPtr TransformGizmo = nullptr; UPROPERTY() TObjectPtr TransformProxy = nullptr; UPROPERTY() TObjectPtr OperatorFactory = nullptr; UPROPERTY() TObjectPtr EdgeRenderer = nullptr; TSharedPtr InputMesh; TSharedPtr, ESPMode::ThreadSafe> TriangleROI; TSharedPtr, ESPMode::ThreadSafe> VertexROI; TSharedPtr InputMeshROISpatial; TSet TriangleROISet; FTransform3d WorldTransform; UE::Geometry::FAxisAlignedBox3d WorldBounds; FVector CachedDimensions; FVector InitialDimensions; bool bInitialProportionalDimensions; FTransform InitialTransform; int32 DimensionsWatcher = -1; int32 DimensionsModeWatcher = -1; bool bTransformModified = false; UE_API void OnInitializationModeChanged(); UE_API void ApplyInitializationMode(); FViewCameraState CameraState; FToolDataVisualizer ProjectionShapeVisualizer; UE_API void InitializeMesh(); UE_API void UpdateNumPreviews(); UE_API void TransformChanged(UTransformProxy* Proxy, FTransform Transform); UE_API void OnMaterialSettingsChanged(); UE_API void OnMeshUpdated(UMeshOpPreviewWithBackgroundCompute* PreviewCompute); UE_API UE::Geometry::FOrientedBox3d GetProjectionBox() const; // // Support for ctrl+click to set plane from hit point // TUniquePtr SetPlaneCtrlClickBehaviorTarget; UPROPERTY() TObjectPtr ClickToSetPlaneBehavior; UE_API void UpdatePlaneFromClick(const FVector3d& Position, const FVector3d& Normal, bool bTransitionOnly); // // Support for Action Buttons // bool bHavePendingAction = false; EUVProjectionToolActions PendingAction; UE_API virtual void ApplyAction(EUVProjectionToolActions ActionType); UE_API void ApplyAction_AutoFit(bool bAlign); UE_API void ApplyAction_Reset(); }; #undef UE_API