// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "BaseBehaviors/BehaviorTargetInterfaces.h" #include "DeformationOps/ExtrudeOp.h" // EPolyEditExtrudeMode #include "GeometryBase.h" #include "GroupTopology.h" // FGroupTopologySelection #include "InteractiveToolActivity.h" #include "FrameTypes.h" #include "PolyEditExtrudeActivity.generated.h" #define UE_API MESHMODELINGTOOLS_API class UPolyEditActivityContext; class UPolyEditPreviewMesh; class UPlaneDistanceFromHitMechanic; PREDECLARE_GEOMETRY(class FDynamicMesh3); UENUM() enum class EPolyEditExtrudeDirection { SelectionNormal, WorldX, WorldY, WorldZ, LocalX, LocalY, LocalZ }; UENUM() enum class EPolyEditExtrudeDistanceMode { /** Set distance by clicking in the viewport. */ ClickInViewport, /** Set distance with an explicit numerical value, then explictly accept. */ Fixed, //~ TODO: Add someday // Gizmo, }; // There is a lot of overlap in the options for Extrude, Offset, and Push/Pull, and they map to // the same op behind the scenes. However, we want to keep them as separate buttons to keep some // amount of shallowness in the UI, to make it more likely that new users will find the setting // they are looking for. // A couple of settings are entirely replicated: namely, doing an offset or "extrude" with SelectedTriangleNormals // or SelectedTriangleNormalsEven as the movement direction is actually equivalent. Properly speaking, these // two should only be options under Offset, not Extrude, but we keep them as (non-default) options // in both because an "extrude along local normals" is a common operation that some users are likely // to look for under extrude, regardless of it not lining up with the physical meaning of extrusion. // We use different property set objects so that we can customize category names, etc, as well as // have different defaults and saved settings. UENUM() enum class EPolyEditExtrudeModeOptions { // Extrude all triangles in the same direction regardless of their facing. SingleDirection = static_cast(UE::Geometry::FExtrudeOp::EDirectionMode::SingleDirection), // Take the angle-weighed average of the selected triangles around each // extruded vertex to determine vertex movement direction. SelectedTriangleNormals = static_cast(UE::Geometry::FExtrudeOp::EDirectionMode::SelectedTriangleNormals), // Like Selected Triangle Normals, but also adjusts the distances moved in // an attempt to keep triangles parallel to their original facing. SelectedTriangleNormalsEven = static_cast(UE::Geometry::FExtrudeOp::EDirectionMode::SelectedTriangleNormalsEven), }; UENUM() enum class EPolyEditOffsetModeOptions { // Vertex normals, regardless of selection. VertexNormals = static_cast(UE::Geometry::FExtrudeOp::EDirectionMode::VertexNormals), // Take the angle-weighed average of the selected triangles around // offset vertex to determine vertex movement direction. SelectedTriangleNormals = static_cast(UE::Geometry::FExtrudeOp::EDirectionMode::SelectedTriangleNormals), // Like Selected Triangle Normals, but also adjusts the distances moved in // an attempt to keep triangles parallel to their original facing. SelectedTriangleNormalsEven = static_cast(UE::Geometry::FExtrudeOp::EDirectionMode::SelectedTriangleNormalsEven), }; UENUM() enum class EPolyEditPushPullModeOptions { // Take the angle-weighed average of the selected triangles around // offset vertex to determine vertex movement direction. SelectedTriangleNormals = static_cast(EPolyEditOffsetModeOptions::SelectedTriangleNormals), // Like Selected Triangle Normals, but also adjusts the distances moved in // an attempt to keep triangles parallel to their original facing. SelectedTriangleNormalsEven = static_cast(UE::Geometry::FExtrudeOp::EDirectionMode::SelectedTriangleNormalsEven), // Move all triangles in the same direction regardless of their facing. SingleDirection = static_cast(UE::Geometry::FExtrudeOp::EDirectionMode::SingleDirection), // Vertex normals, regardless of selection. VertexNormals = static_cast(UE::Geometry::FExtrudeOp::EDirectionMode::VertexNormals), }; UCLASS(MinimalAPI) class UPolyEditExtrudeProperties : public UInteractiveToolPropertySet { GENERATED_BODY() public: /** How the extrude distance is set. */ UPROPERTY(EditAnywhere, Category = Extrude) EPolyEditExtrudeDistanceMode DistanceMode = EPolyEditExtrudeDistanceMode::ClickInViewport; /** Distance to extrude. */ UPROPERTY(EditAnywhere, Category = Extrude, meta = (UIMin = "-1000", UIMax = "1000", ClampMin = "-10000", ClampMax = "10000", EditConditionHides, EditCondition = "DistanceMode == EPolyEditExtrudeDistanceMode::Fixed")) double Distance = 100; /** How to move the vertices during the extrude */ UPROPERTY(EditAnywhere, Category = Extrude) EPolyEditExtrudeModeOptions DirectionMode = EPolyEditExtrudeModeOptions::SingleDirection; /** Direction in which to extrude. */ UPROPERTY(EditAnywhere, Category = Extrude, meta = (EditConditionHides, EditCondition = "DirectionMode == EPolyEditExtrudeModeOptions::SingleDirection")) EPolyEditExtrudeDirection Direction = EPolyEditExtrudeDirection::SelectionNormal; /** Controls the maximum distance vertices can move from the target distance in order to stay parallel with their source triangles. */ UPROPERTY(EditAnywhere, Category = Extrude, meta = (ClampMin = "1", EditConditionHides, EditCondition = "DirectionMode == EPolyEditExtrudeModeOptions::SelectedTriangleNormalsEven")) double MaxDistanceScaleFactor = 4.0; /** Controls whether extruding an entire open-border patch should create a solid or an open shell */ UPROPERTY(EditAnywhere, Category = Extrude) bool bShellsToSolids = true; /** What axis to measure the extrusion distance along. */ UPROPERTY(EditAnywhere, Category = Extrude, AdvancedDisplay, meta = (EditConditionHides, EditCondition = "DirectionMode != EPolyEditExtrudeModeOptions::SingleDirection && DistanceMode == EPolyEditExtrudeDistanceMode::ClickInViewport")) EPolyEditExtrudeDirection MeasureDirection = EPolyEditExtrudeDirection::SelectionNormal; /** * When extruding regions that touch the mesh border, assign the side groups (groups on the * stitched side of the extrude) in a way that considers edge colinearity. For instance, when * true, extruding a flat rectangle will give four different groups on its sides rather than * one connected group. */ UPROPERTY(EditAnywhere, Category = Extrude, AdvancedDisplay) bool bUseColinearityForSettingBorderGroups = true; }; UCLASS(MinimalAPI) class UPolyEditOffsetProperties : public UInteractiveToolPropertySet { GENERATED_BODY() public: /** How the offset distance is set. */ UPROPERTY(EditAnywhere, Category = Offset) EPolyEditExtrudeDistanceMode DistanceMode = EPolyEditExtrudeDistanceMode::ClickInViewport; /** Offset distance. */ UPROPERTY(EditAnywhere, Category = Offset, meta = (EditConditionHides, EditCondition = "DistanceMode == EPolyEditExtrudeDistanceMode::Fixed")) double Distance = 100; /** Which way to move vertices during the offset */ UPROPERTY(EditAnywhere, Category = Offset) EPolyEditOffsetModeOptions DirectionMode = EPolyEditOffsetModeOptions::VertexNormals; /** Controls the maximum distance vertices can move from the target distance in order to stay parallel with their source triangles. */ UPROPERTY(EditAnywhere, Category = Offset, meta = (ClampMin = "1", EditConditionHides, EditCondition = "DirectionMode == EPolyEditOffsetModeOptions::SelectedTriangleNormalsEven")) double MaxDistanceScaleFactor = 4.0; /** Controls whether offsetting an entire open-border patch should create a solid or an open shell */ UPROPERTY(EditAnywhere, Category = Offset) bool bShellsToSolids = true; /** What axis to measure the offset distance along. */ UPROPERTY(EditAnywhere, Category = Offset, AdvancedDisplay, meta = (EditConditionHides, EditCondition = "DistanceMode == EPolyEditExtrudeDistanceMode::ClickInViewport")) EPolyEditExtrudeDirection MeasureDirection = EPolyEditExtrudeDirection::SelectionNormal; /** * When offsetting regions that touch the mesh border, assign the side groups (groups on the * stitched side of the offset) in a way that considers edge colinearity. For instance, when * true, extruding a flat rectangle will give four different groups on its sides rather than * one connected group. */ UPROPERTY(EditAnywhere, Category = Offset, AdvancedDisplay) bool bUseColinearityForSettingBorderGroups = true; }; UCLASS(MinimalAPI) class UPolyEditPushPullProperties : public UInteractiveToolPropertySet { GENERATED_BODY() public: /** How the extrusion distance is set. */ UPROPERTY(EditAnywhere, Category = ExtrusionOptions) EPolyEditExtrudeDistanceMode DistanceMode = EPolyEditExtrudeDistanceMode::ClickInViewport; /** Extrusion distance. */ UPROPERTY(EditAnywhere, Category = ExtrusionOptions, meta = (EditConditionHides, EditCondition = "DistanceMode == EPolyEditExtrudeDistanceMode::Fixed")) double Distance = 100; /** Which way to move vertices while extruding. */ UPROPERTY(EditAnywhere, Category = ExtrusionOptions) EPolyEditPushPullModeOptions DirectionMode = EPolyEditPushPullModeOptions::SelectedTriangleNormals; /** Direction in which to extrude. */ UPROPERTY(EditAnywhere, Category = ExtrusionOptions, meta = (DisplayName="Direction", EditConditionHides, EditCondition = "DirectionMode == EPolyEditPushPullModeOptions::SingleDirection")) EPolyEditExtrudeDirection SingleDirection = EPolyEditExtrudeDirection::SelectionNormal; /** Controls the maximum distance vertices can move from the target distance in order to stay parallel with their source triangles. */ UPROPERTY(EditAnywhere, Category = ExtrusionOptions, meta = (ClampMin = "1", EditConditionHides, EditCondition = "DirectionMode == EPolyEditPushPullModeOptions::SelectedTriangleNormalsEven")) double MaxDistanceScaleFactor = 4.0; /** Controls whether offsetting an entire open-border patch should create a solid or an open shell */ UPROPERTY(EditAnywhere, Category = ExtrusionOptions) bool bShellsToSolids = true; /** What axis to measure the extrusion distance along. */ UPROPERTY(EditAnywhere, Category = ExtrusionOptions, AdvancedDisplay, meta = (EditConditionHides, EditCondition = "DirectionMode != EPolyEditPushPullModeOptions::SingleDirection && DistanceMode == EPolyEditExtrudeDistanceMode::ClickInViewport")) EPolyEditExtrudeDirection MeasureDirection = EPolyEditExtrudeDirection::SelectionNormal; /** * When offsetting regions that touch the mesh border, assign the side groups (groups on the * stitched side of the extrude) in a way that considers edge colinearity. For instance, when * true, extruding a flat rectangle will give four different groups on its sides rather than * one connected group. */ UPROPERTY(EditAnywhere, Category = ExtrusionOptions, AdvancedDisplay) bool bUseColinearityForSettingBorderGroups = true; }; /** * */ UCLASS(MinimalAPI) class UPolyEditExtrudeActivity : public UInteractiveToolActivity, public UE::Geometry::IDynamicMeshOperatorFactory, public IClickBehaviorTarget, public IHoverBehaviorTarget { GENERATED_BODY() public: using FExtrudeOp = UE::Geometry::FExtrudeOp; enum class EPropertySetToUse { Extrude, Offset, PushPull }; // Set to different values depending on whether we're using this activity on behalf of // extrude, offset, or push/pull FExtrudeOp::EExtrudeMode ExtrudeMode = FExtrudeOp::EExtrudeMode::MoveAndStitch; EPropertySetToUse PropertySetToUse = EPropertySetToUse::Extrude; // IInteractiveToolActivity UE_API virtual void Setup(UInteractiveTool* ParentTool) override; UE_API virtual void Shutdown(EToolShutdownType ShutdownType) override; UE_API virtual bool CanStart() const override; UE_API virtual EToolActivityStartResult Start() override; virtual bool IsRunning() const override { return bIsRunning; } virtual bool HasAccept() const { return true; }; UE_API virtual bool CanAccept() const override; UE_API virtual EToolActivityEndResult End(EToolShutdownType) override; UE_API virtual void Render(IToolsContextRenderAPI* RenderAPI) override; UE_API virtual void Tick(float DeltaTime) override; // IDynamicMeshOperatorFactory UE_API virtual TUniquePtr MakeNewOperator() override; // IClickBehaviorTarget API UE_API virtual FInputRayHit IsHitByClick(const FInputDeviceRay& ClickPos) override; UE_API virtual void OnClicked(const FInputDeviceRay& ClickPos) override; // IHoverBehaviorTarget implementation UE_API virtual FInputRayHit BeginHoverSequenceHitTest(const FInputDeviceRay& PressPos) override; virtual void OnBeginHover(const FInputDeviceRay& DevicePos) override {} UE_API virtual bool OnUpdateHover(const FInputDeviceRay& DevicePos) override; virtual void OnEndHover() override {} UPROPERTY() TObjectPtr ExtrudeProperties = nullptr; UPROPERTY() TObjectPtr OffsetProperties = nullptr; UPROPERTY() TObjectPtr PushPullProperties = nullptr; protected: UE_API FVector3d GetExtrudeDirection() const; UE_API virtual void BeginExtrude(); UE_API virtual void ApplyExtrude(); UE_API virtual void ReinitializeExtrudeHeightMechanic(); UE_API virtual void EndInternal(); UPROPERTY() TObjectPtr ExtrudeHeightMechanic = nullptr; UPROPERTY() TObjectPtr ActivityContext; TSharedPtr PatchMesh; TArray NewSelectionGids; bool bIsRunning = false; UE::Geometry::FGroupTopologySelection ActiveSelection; UE::Geometry::FFrame3d ActiveSelectionFrameWorld; float UVScaleFactor = 1.0f; bool bRequestedApply = false; }; #undef UE_API