// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "BaseTools/MultiSelectionMeshEditingTool.h" #include "InteractiveToolBuilder.h" #include "BaseBehaviors/BehaviorTargetInterfaces.h" #include "Changes/TransformChange.h" #include "FrameTypes.h" #include "BoxTypes.h" #include "ToolDataVisualizer.h" #include "TransformTypes.h" #include "PatternTool.generated.h" #define UE_API MESHMODELINGTOOLSEXP_API class UBaseAxisTranslationGizmo; class UAxisAngleGizmo; class UDragAlignmentMechanic; class UCombinedTransformGizmo; class UTransformProxy; class UPreviewGeometry; class UDynamicMesh; class UDynamicMeshComponent; class UInteractiveGizmo; class UStaticMesh; class UStaticMeshComponent; class UMaterialInterface; class UConstructionPlaneMechanic; class UComponentBoundTransformProxy; /** * */ UCLASS(MinimalAPI) class UPatternToolBuilder : public UMultiSelectionMeshEditingToolBuilder { GENERATED_BODY() public: UE_API virtual bool CanBuildTool(const FToolBuilderState& SceneState) const override; UE_API virtual UMultiSelectionMeshEditingTool* CreateNewTool(const FToolBuilderState& SceneState) const override; UE_API virtual void InitializeNewTool(UMultiSelectionMeshEditingTool* NewTool, const FToolBuilderState& SceneState) const override; bool bEnableCreateISMCs = true; protected: UE_API virtual const FToolTargetTypeRequirements& GetTargetRequirements() const override; }; UENUM() enum class EPatternToolShape : uint8 { /** Arrange pattern elements along a Line */ Line = 0, /** Arrange pattern elements in a 2D Grid */ Grid = 1, /** Arrange pattern elements in a Circle */ Circle = 2 }; UENUM() enum class EPatternToolSingleAxis : uint8 { XAxis = 0, YAxis = 1, ZAxis = 2 }; UENUM() enum class EPatternToolSinglePlane : uint8 { XYPlane = 0, XZPlane = 1, YZPlane = 2 }; UENUM() enum class EPatternToolAxisSpacingMode : uint8 { /** Place a specific number of Pattern Elements along the pattern geometry */ ByCount = 0, /** Place Pattern Elements at regular increments along the Pattern Geometry (on-center) */ StepSize = 1, /** Pack in as many Pattern Elements as fits in the available space */ Packed = 2 }; /** * Settings for the Pattern Tool */ UCLASS(MinimalAPI) class UPatternToolSettings : public UInteractiveToolPropertySet { GENERATED_BODY() public: /** The seed used to introduce random transform variations when enabled */ UPROPERTY(EditAnywhere, Category = General, meta = (NoResetToDefault, Delta = 1, LinearDeltaSensitivity = 50)) int32 Seed = FMath::Rand(); /** Whether or not the pattern items should be projected along the negative Z axis of the plane mechanic */ UPROPERTY(EditAnywhere, Category = General) bool bProjectElementsDown = false; /** How much each pattern item should be moved along the negative Z axis of the plane mechanic if Project Elements Down is enabled */ UPROPERTY(EditAnywhere, Category = General, meta = (EditCondition = "bProjectElementsDown == true", EditConditionHides, Delta = 0.1, LinearDeltaSensitivity = 1)) float ProjectionOffset = 0.0f; /** Hide the source meshes when enabled */ UPROPERTY(EditAnywhere, Category = General) bool bHideSources = true; /** If false, all pattern elements will be positioned at the origin of the first pattern element */ UPROPERTY(EditAnywhere, Category = General) bool bUseRelativeTransforms = true; /** Whether to randomly pick which source mesh is scattered at each location, or to always use all source meshes */ UPROPERTY(EditAnywhere, Category = General) bool bRandomlyPickElements = false; /** Shape of the underlying Pattern */ UPROPERTY(EditAnywhere, Category = Shape) EPatternToolShape Shape = EPatternToolShape::Line; /** Axis direction used for the Pattern geometry */ UPROPERTY(EditAnywhere, Category = Shape, meta = (DisplayName = "Direction", EditCondition = "Shape == EPatternToolShape::Line", EditConditionHides)) EPatternToolSingleAxis SingleAxis = EPatternToolSingleAxis::XAxis; /** Plane used for the Pattern geometry */ UPROPERTY(EditAnywhere, Category = Shape, meta = (DisplayName = "Plane", EditCondition = "Shape != EPatternToolShape::Line", EditConditionHides)) EPatternToolSinglePlane SinglePlane = EPatternToolSinglePlane::XYPlane; }; /** * Settings for Bounding Box adjustments in the Pattern Tool */ UCLASS(MinimalAPI) class UPatternTool_BoundingBoxSettings : public UInteractiveToolPropertySet { GENERATED_BODY() public: /** If true, pattern element bounding boxes are not changed to account for StartScale or StartRotation */ UPROPERTY(EditAnywhere, Category = BoundingBox) bool bIgnoreTransforms = false; /** Value added to the all pattern elements' bounding boxes for adjusting the behavior of packed spacing mode manually */ UPROPERTY(EditAnywhere, Category = BoundingBox, meta = (Delta = 0.1, LinearDeltaSensitivity = 1)) float Adjustment = 0.0f; /** If true, the bounding boxes of each element are rendered in green and the combined bounding box of all source elements is rendered in red */ UPROPERTY(EditAnywhere, Category = BoundingBox) bool bVisualize = false; }; /** * Settings for Linear Patterns in the Pattern Tool */ UCLASS(MinimalAPI) class UPatternTool_LinearSettings : public UInteractiveToolPropertySet { GENERATED_BODY() public: /** Spacing Technique used to distribute Pattern Elements */ UPROPERTY(EditAnywhere, Category = LinearPattern) EPatternToolAxisSpacingMode SpacingMode = EPatternToolAxisSpacingMode::ByCount; /** Number of Pattern Elements to place */ UPROPERTY(EditAnywhere, Category = LinearPattern, meta = (ClampMin = 1, UIMax = 25, EditCondition = "SpacingMode == EPatternToolAxisSpacingMode::ByCount", EditConditionHides)) int32 Count = 10; /** Fixed Increment used to place Pattern Elements */ UPROPERTY(EditAnywhere, Category = LinearPattern, meta = (ClampMin = 0, EditCondition = "SpacingMode == EPatternToolAxisSpacingMode::StepSize", EditConditionHides)) double StepSize = 100.0; /** Length of Pattern along the Axis */ UPROPERTY(EditAnywhere, Category = LinearPattern, meta = (ClampMin = 0)) double Extent = 1000.0; /** If true, Pattern is centered at the Origin, otherwise Pattern starts at the Origin */ UPROPERTY(EditAnywhere, Category = LinearPattern) bool bCentered = true; }; /** * Settings for Grid Patterns in the Pattern Tool * TODO: maybe we can just re-use UPatternTool_LinearSettings for this?? */ UCLASS(MinimalAPI) class UPatternTool_GridSettings : public UInteractiveToolPropertySet { GENERATED_BODY() public: /** Spacing Technique used to distribute Pattern Elements along the Main axis */ UPROPERTY(EditAnywhere, Category = GridPatternX) EPatternToolAxisSpacingMode SpacingX = EPatternToolAxisSpacingMode::ByCount; /** Number of Pattern Elements to place along the Main axis */ UPROPERTY(EditAnywhere, Category = GridPatternX, meta = (ClampMin = 1, UIMax = 25, EditCondition = "SpacingX == EPatternToolAxisSpacingMode::ByCount", EditConditionHides)) int32 CountX = 10; /** Fixed Increment used to place Pattern Elements along the Main axis */ UPROPERTY(EditAnywhere, Category = GridPatternX, meta = (ClampMin = 0, EditCondition = "SpacingX == EPatternToolAxisSpacingMode::StepSize", EditConditionHides)) double StepSizeX = 100.0; /** Length/Extent of Pattern falong the Main Axis */ UPROPERTY(EditAnywhere, Category = GridPatternX, meta = (ClampMin = 0)) double ExtentX = 1000.0; /** If true, Pattern is centered at the Origin along the Main axis, otherwise Pattern starts at the Origin */ UPROPERTY(EditAnywhere, Category = GridPatternX) bool bCenteredX = true; /** Spacing Technique used to distribute Pattern Elements along the Secondary axis*/ UPROPERTY(EditAnywhere, Category = GridPatternY) EPatternToolAxisSpacingMode SpacingY = EPatternToolAxisSpacingMode::ByCount; /** Number of Pattern Elements to place along the Secondary axis */ UPROPERTY(EditAnywhere, Category = GridPatternY, meta = (ClampMin = 1, UIMax = 25, EditCondition = "SpacingY == EPatternToolAxisSpacingMode::ByCount", EditConditionHides)) int32 CountY = 10; /** Fixed Increment used to place Pattern Elements along the Secondary axis */ UPROPERTY(EditAnywhere, Category = GridPatternY, meta = (ClampMin = 0, EditCondition = "SpacingY == EPatternToolAxisSpacingMode::StepSize", EditConditionHides)) double StepSizeY = 100.0; /** Length/Extent of Pattern falong the Secondary Axis */ UPROPERTY(EditAnywhere, Category = GridPatternY, meta = (ClampMin = 0)) double ExtentY = 1000.0; /** If true, Pattern is centered at the Origin along the Secondary axis, otherwise Pattern starts at the Origin */ UPROPERTY(EditAnywhere, Category = GridPatternY) bool bCenteredY = true; }; /** * Settings for Radial Patterns in the Pattern Tool */ UCLASS(MinimalAPI) class UPatternTool_RadialSettings : public UInteractiveToolPropertySet { GENERATED_BODY() public: /** Spacing Technique used to distribute Pattern Elements around the Circle/Arc */ UPROPERTY(EditAnywhere, Category = RadialPattern) EPatternToolAxisSpacingMode SpacingMode = EPatternToolAxisSpacingMode::ByCount; /** Number of Pattern Elements to place */ UPROPERTY(EditAnywhere, Category = RadialPattern, meta = (ClampMin = 1, UIMax = 25, EditCondition = "SpacingMode == EPatternToolAxisSpacingMode::ByCount", EditConditionHides)) int32 Count = 10; /** Fixed Increment (in Degrees) used to position Pattern Elements around the Circle/Arc */ UPROPERTY(EditAnywhere, Category = RadialPattern, meta = (Units = "Degrees", ClampMin = 0, EditCondition = "SpacingMode == EPatternToolAxisSpacingMode::StepSize", EditConditionHides)) double StepSize = 100.0; /** Radius of the Circle/Arc */ UPROPERTY(EditAnywhere, Category = RadialPattern, meta = (ClampMin = 0)) double Radius = 250; /** Start angle of the Circle/Arc */ UPROPERTY(EditAnywhere, Category = RadialPattern, meta = (Units = "Degrees", UIMin = -360, UIMax = 360)) double StartAngle = 0.0; /** End angle of the Circle/Arc */ UPROPERTY(EditAnywhere, Category = RadialPattern, meta = (Units = "Degrees", UIMin = -360, UIMax = 360)) double EndAngle = 360.0; /** Fixed offset added to Start/End Angles */ UPROPERTY(EditAnywhere, Category = RadialPattern, meta = (Units = "Degrees", ClampMin = -180, ClampMax = 180)) double AngleShift = 0.0; /** If true, Pattern elements are rotated to align with the Circle tangent */ UPROPERTY(EditAnywhere, Category = RadialPattern) bool bOriented = true; }; /** * Settings for Per Element Rotation in the Pattern Tool */ UCLASS(MinimalAPI) class UPatternTool_RotationSettings : public UInteractiveToolPropertySet { GENERATED_BODY() public: /** If true, Rotation is linearly interpolated between StartRotation and Rotation values */ UPROPERTY(EditAnywhere, Category = Rotation, meta = (InlineEditConditionToggle)) bool bInterpolate = false; /** If true, Rotation at each Pattern Element is offset by a uniformly chosen random value in the range of [-RotationJitterRange, RotationJitterRange] */ UPROPERTY(EditAnywhere, Category = Rotation, meta = (InlineEditConditionToggle)) bool bJitter = false; /** Rotation applied to all Pattern Elements, or to first Pattern Element for Interpolated rotation */ UPROPERTY(EditAnywhere, Category = Rotation, meta = (UIMin = -360, UIMax = 360)) FRotator StartRotation = FRotator::ZeroRotator; /** Rotation applied to last Pattern Elements for Interpolated rotation */ UPROPERTY(EditAnywhere, Category = Rotation, meta = (EditCondition = "bInterpolate", UIMin = -360, UIMax = 360)) FRotator EndRotation = FRotator::ZeroRotator; /** Upper bound of the range which is sampled to randomly rotate each Pattern Element if Jitter is true */ UPROPERTY(EditAnywhere, Category = Rotation, meta = (ClampMin = 0, EditCondition = "bJitter", UIMin = -360, UIMax = 360)) FRotator Jitter = FRotator::ZeroRotator; }; /** * Settings for Per Element Translation in the Pattern Tool */ UCLASS(MinimalAPI) class UPatternTool_TranslationSettings : public UInteractiveToolPropertySet { GENERATED_BODY() public: /** If true, Translation is linearly interpolated between StartTranslation and Translation values */ UPROPERTY(EditAnywhere, Category = Translation, meta = (InlineEditConditionToggle)) bool bInterpolate = false; /** If true, Translation at each Pattern Element is offset by a uniformly chosen random value in the range of [-TranslationJitterRange, TranslationJitterRange] */ UPROPERTY(EditAnywhere, Category = Translation, meta = (InlineEditConditionToggle)) bool bJitter = false; /** Translation applied to all Pattern Elements, or to first Pattern Element for Interpolated translation */ UPROPERTY(EditAnywhere, Category = Translation) FVector StartTranslation = FVector::ZeroVector; /** Translation applied to last Pattern Element for Interpolated translation */ UPROPERTY(EditAnywhere, Category = Translation, meta = (EditCondition = "bInterpolate")) FVector EndTranslation = FVector::ZeroVector; /** Upper bound of the range which is sampled to randomly translate each Pattern Element if Jitter is true */ UPROPERTY(EditAnywhere, Category = Translation, meta = (ClampMin = 0, EditCondition = "bJitter")) FVector Jitter = FVector::ZeroVector; }; /** * Settings for Per Element Scale in the Pattern Tool */ UCLASS(MinimalAPI) class UPatternTool_ScaleSettings : public UInteractiveToolPropertySet { GENERATED_BODY() public: /** Initial value for Jitter and referenced in FPatternGenerator when applying scale jitter. Can't be used for ClampMin. */ static constexpr double MinScale = 0.001; /** If true, changes to Start Scale, End Scale, and Jitter are proportional along all the axes */ UPROPERTY(EditAnywhere, Category = Scale) bool bProportional = true; /** If true, Scale is linearly interpolated between StartScale and Scale values */ UPROPERTY(EditAnywhere, Category = Scale, meta = (InlineEditConditionToggle)) bool bInterpolate = false; /** If true, Scale at each Pattern Element is offset by a uniformly chosen random value in the range of [-ScaleJitterRange, ScaleJitterRange] */ UPROPERTY(EditAnywhere, Category = Scale, meta = (InlineEditConditionToggle)) bool bJitter = false; /** Scale applied to all Pattern Elements, or to first Pattern Element for Interpolated scale */ UPROPERTY(EditAnywhere, Category = Scale, meta = (ClampMin = 0.001, Delta = 0.01, LinearDeltaSensitivity = 1)) FVector StartScale = FVector::OneVector; /** Scale applied to last Pattern Element for Interpolated scale */ UPROPERTY(EditAnywhere, Category = Scale, meta = (ClampMin = 0.001, EditCondition = "bInterpolate", Delta = 0.01, LinearDeltaSensitivity = 1)) FVector EndScale = FVector::OneVector; /** Upper bound of the range which is sampled to randomly scale each Pattern Element if Jitter is true */ UPROPERTY(EditAnywhere, Category = Scale, meta = (ClampMin = 0.001, EditCondition = "bJitter", Delta = 0.01, LinearDeltaSensitivity = 1)) FVector Jitter = FVector(MinScale); }; /** * Output Settings for the Pattern Tool */ UCLASS(MinimalAPI) class UPatternTool_OutputSettings : public UInteractiveToolPropertySet { GENERATED_BODY() public: /** Emit a separate Actor for each pattern element */ UPROPERTY(EditAnywhere, Category = Output) bool bSeparateActors = false; /** Emit StaticMesh pattern elements as DynamicMeshes */ UPROPERTY(EditAnywhere, Category = Output, meta = (EditCondition = "bHaveStaticMeshes == true", HideEditConditionToggle)) bool bConvertToDynamic = false; /** Create InstancedStaticMeshComponents instead multiple StaticMeshComponents, for StaticMesh pattern elements */ UPROPERTY(EditAnywhere, Category = Output, meta = (EditCondition = "bHaveStaticMeshes == true && bSeparateActors == false && bConvertToDynamic == false && bEnableCreateISMCs == true", HideEditConditionToggle)) bool bCreateISMCs = false; /** internal, used to control state of Instance settings */ UPROPERTY(meta = (TransientToolProperty)) bool bHaveStaticMeshes = false; // internal, used to disable the creation of ISMCs UPROPERTY(meta = (TransientToolProperty)) bool bEnableCreateISMCs = true; }; /** * UPatternTool takes input meshes and generates 3D Patterns of those meshes, by * placing repeated copies along geometric paths like lines, grids, circles, etc. * The output can be a single Actor per pattern Element, or combined into single * Actors in various ways depending on the input mesh type. */ UCLASS(MinimalAPI) class UPatternTool : public UMultiSelectionMeshEditingTool { GENERATED_BODY() public: UE_API UPatternTool(); UE_API virtual void Setup() override; UE_API virtual void OnShutdown(EToolShutdownType ShutdownType) 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; } virtual bool CanAccept() const override { return true; } UE_API virtual void SetEnableCreateISMCs(bool bEnable); public: UPROPERTY() TObjectPtr Settings; UPROPERTY() TObjectPtr BoundingBoxSettings; UPROPERTY() TObjectPtr LinearSettings; UPROPERTY() TObjectPtr GridSettings; UPROPERTY() TObjectPtr RadialSettings; UPROPERTY() TObjectPtr RotationSettings; UPROPERTY() TObjectPtr TranslationSettings; UPROPERTY() TObjectPtr ScaleSettings; FVector CachedStartScale; FVector CachedEndScale; FVector CachedJitterScale; int32 StartScaleWatcherIdx; int32 EndScaleWatcherIdx; int32 JitterScaleWatcherIdx; UPROPERTY() TObjectPtr OutputSettings; protected: /** * Pattern Gizmo: */ UPROPERTY() TObjectPtr PatternGizmoProxy = nullptr; UPROPERTY() TObjectPtr PatternGizmo = nullptr; UPROPERTY() TObjectPtr PatternGizmoComponent = nullptr; // If true, Settings->SingleAxis is being used. If false, Settings->SinglePlane is being used bool bUsingSingleAxis; int32 LinearExtentWatcherIdx; int32 GridExtentXWatcherIdx; int32 GridExtentYWatcherIdx; int32 RadiusWatcherIdx; UE_API void OnTransformGizmoUpdated(UTransformProxy* Proxy, FTransform Transform); UE_API void ResetTransformGizmoPosition(); UE_API void ReconstructTransformGizmos(); UPROPERTY() TObjectPtr DragAlignmentMechanic = nullptr; UPROPERTY() TObjectPtr PlaneMechanic = nullptr; UE::Geometry::FFrame3d CurrentStartFrameWorld; TArray CurrentPattern; bool bPatternNeedsUpdating = false; FDateTime LastPatternUpdateTime; UE_API void MarkPatternDirty(); UE_API void OnSourceVisibilityToggled(bool bVisible); UE_API void OnMainFrameUpdated(); UE_API void OnShapeUpdated(); UE_API void OnSingleAxisUpdated(); UE_API void OnSinglePlaneUpdated(); UE_API void OnSpacingModeUpdated(); UE_API void OnParametersUpdated(); UE_API void UpdatePattern(); UE_API void ComputeWorldTransform(FTransform& OutWorldTransform, const FTransform& InElementTransform, const FTransform& InPatternTransform) const; UE_API void GetPatternTransforms_Linear(TArray& TransformsOut); UE_API void GetPatternTransforms_Grid(TArray& TransformsOut); UE_API void GetPatternTransforms_Radial(TArray& TransformsOut); UE_API void RenderBoundingBoxes(IToolsContextRenderAPI* RenderAPI); FToolDataVisualizer BoundingBoxVisualizer; struct FPatternElement { int32 TargetIndex = 0; // todo: This is no longer necessary now that InitializeNewTool filters out invalid targets. bool bValid = true; UPrimitiveComponent* SourceComponent = nullptr; TArray SourceMaterials; UE::Geometry::FTransformSRT3d SourceTransform = UE::Geometry::FTransformSRT3d::Identity(); UE::Geometry::FTransformSRT3d BaseRotateScale = UE::Geometry::FTransformSRT3d::Identity(); // We don't need to store rotation or scale relative to first // element because that is handled by BaseRotateScale FVector3d RelativePosition = FVector3d::ZeroVector; UDynamicMesh* SourceDynamicMesh = nullptr; UStaticMesh* SourceStaticMesh = nullptr; UE::Geometry::FAxisAlignedBox3d LocalBounds = UE::Geometry::FAxisAlignedBox3d::Empty(); // The unchanged bounding box of the source mesh. Only used indirectly through PatternBounds UE::Geometry::FAxisAlignedBox3d PatternBounds = UE::Geometry::FAxisAlignedBox3d::Empty(); // The bounding box used for computing CombinedPatternBounds for packed spacing. By default this box adapts to StartScale/StartRotation }; TArray Elements; UE::Geometry::FAxisAlignedBox3d CombinedPatternBounds = UE::Geometry::FAxisAlignedBox3d::Empty(); // The bounding box that contains all of the elements' PatternBounds bounding boxes. Used for packed mode spacing UE_API void ComputePatternBounds(int32 ElemIdx); UE_API void ComputeCombinedPatternBounds(); // Given an Element index and an FTransformSRT3d, determine the bounding box that contains the transformed underlying mesh // BoundingBox is made empty before growing to contain the transformed mesh. UE_API void ComputeBoundingBoxWithTransform(int32 ElemIdx, UE::Geometry::FAxisAlignedBox3d& BoundingBox, const UE::Geometry::FTransformSRT3d& Transform); bool bHaveNonUniformScaleElements = false; bool bEnableCreateISMCs = true; struct FComponentSet { TArray Components; }; TArray PreviewComponents; // This duplicates the data stored by PreviewComponents but it is necessary to have a simple // TArray of all preview components when raycasting, otherwise the raycasts // will hit the preview components from the previous frame. TArray AllPreviewComponents; // This is passed to FindNearestVisibleObjectHit as ComponentsToIgnore protected: UPROPERTY() TSet> AllComponents; // to keep components in FComponentSet alive TMap StaticMeshPools; UE_API UStaticMeshComponent* GetPreviewStaticMesh(const FPatternElement& Element); UE_API void ReturnStaticMeshes(FPatternElement& Element, FComponentSet& ComponentSet); TMap DynamicMeshPools; UE_API UDynamicMeshComponent* GetPreviewDynamicMesh(const FPatternElement& Element); UE_API void ReturnDynamicMeshes(FPatternElement& Element, FComponentSet& ComponentSet); UE_API void HideReturnedPreviewMeshes(); UPROPERTY() TObjectPtr PreviewGeometry = nullptr; // parent actor for all preview components UE_API void InitializeElements(); UE_API void ResetPreviews(); UE_API void DestroyPreviews(); UE_API void EmitResults(); }; #undef UE_API