// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "DataprepOperation.h" #include "Properties/RemeshProperties.h" #include "CleaningOps/RemeshMeshOp.h" #include "CleaningOps/SimplifyMeshOp.h" #include "DynamicMesh/DynamicMesh3.h" #include "ModelingOperators.h" #include "BakeTransformTool.h" #include "DataprepGeometryOperations.generated.h" DECLARE_LOG_CATEGORY_EXTERN( LogDataprepGeometryOperations, Log, All ); class AStaticMeshActor; class IMeshBuilderModule; class UStaticMesh; class UStaticMeshComponent; class UWorld; UCLASS(Experimental, Category = ObjectOperation, Meta = (DisplayName="Remesh", ToolTip = "Experimental - Remesh input meshes") ) class UDataprepRemeshOperation : public UDataprepEditingOperation { GENERATED_BODY() public: /** Target triangle count */ UPROPERTY(EditAnywhere, Category = Remeshing) int TargetTriangleCount = 1000; /** Amount of Vertex Smoothing applied within Remeshing */ UPROPERTY(EditAnywhere, Category = Remeshing, meta = (DisplayName = "Smoothing Rate", UIMin = "0.0", UIMax = "1.0", ClampMin = "0.0", ClampMax = "1.0")) float SmoothingStrength = 0.25; /** Discard UVs and existing normals, allowing the remesher to ignore any UV and normal seams. New per-vertex normals are computed. */ UPROPERTY(EditAnywhere, Category = Remeshing) bool bDiscardAttributes = false; /** Remeshing type */ UPROPERTY(EditAnywhere, Category = Remeshing) ERemeshType RemeshType = ERemeshType::Standard; /** Number of Remeshing passes */ UPROPERTY(EditAnywhere, Category = Remeshing, meta = (EditCondition = "RemeshType == ERemeshType::FullPass", UIMin = "0", UIMax = "50", ClampMin = "0", ClampMax = "1000")) int RemeshIterations = 20; /** Mesh Boundary Constraint Type */ UPROPERTY(EditAnywhere, Category = "Remeshing|Boundary Constraints", meta = (DisplayName = "Mesh Boundary")) EMeshBoundaryConstraint MeshBoundaryConstraint = EMeshBoundaryConstraint::Free; /** Group Boundary Constraint Type */ UPROPERTY(EditAnywhere, Category = "Remeshing|Boundary Constraints", meta = (DisplayName = "Group Boundary")) EGroupBoundaryConstraint GroupBoundaryConstraint = EGroupBoundaryConstraint::Free; /** Material Boundary Constraint Type */ UPROPERTY(EditAnywhere, Category = "Remeshing|Boundary Constraints", meta = (DisplayName = "Material Boundary")) EMaterialBoundaryConstraint MaterialBoundaryConstraint = EMaterialBoundaryConstraint::Free; //~ Begin UDataprepOperation Interface public: virtual FText GetCategory_Implementation() const override { return FDataprepOperationCategories::MeshOperation; } protected: virtual void OnExecution_Implementation(const FDataprepContext& InContext) override; //~ End UDataprepOperation Interface }; UCLASS(Experimental, Category = ObjectOperation, Meta = (DisplayName="Bake Transform", ToolTip = "Experimental - Bake transform of input meshes") ) class UDataprepBakeTransformOperation : public UDataprepEditingOperation { GENERATED_BODY() public: /** Bake rotation */ UPROPERTY(EditAnywhere, Category = BakeTransform) bool bBakeRotation = true; /** Bake scale */ UPROPERTY(EditAnywhere, Category = BakeTransform) EBakeScaleMethod BakeScale = EBakeScaleMethod::BakeNonuniformScale; /** Recenter pivot after baking transform */ UPROPERTY(EditAnywhere, Category = BakeTransform) bool bRecenterPivot = false; //~ Begin UDataprepOperation Interface public: virtual FText GetCategory_Implementation() const override { return FDataprepOperationCategories::MeshOperation; } protected: virtual void OnExecution_Implementation(const FDataprepContext& InContext) override; //~ End UDataprepOperation Interface }; UCLASS(Experimental, Category = ObjectOperation, Meta = (DisplayName="Weld Edges", ToolTip = "Experimental - Weld edges of input meshes") ) class UDataprepWeldEdgesOperation : public UDataprepEditingOperation { GENERATED_BODY() public: /** Merge search tolerance */ UPROPERTY(EditAnywhere, Category = Options, meta = (UIMin = "0.000001", UIMax = "0.01", ClampMin = "0.00000001", ClampMax = "1000.0")) float Tolerance; /** Apply to only unique pairs */ UPROPERTY(EditAnywhere, Category = Options) bool bOnlyUnique; //~ Begin UDataprepOperation Interface public: virtual FText GetCategory_Implementation() const override { return FDataprepOperationCategories::MeshOperation; } protected: virtual void OnExecution_Implementation(const FDataprepContext& InContext) override; //~ End UDataprepOperation Interface }; UCLASS(Experimental, Category = ObjectOperation, Meta = (DisplayName="Simplify Mesh", ToolTip = "Experimental - Simplify input meshes") ) class UDataprepSimplifyMeshOperation : public UDataprepEditingOperation { GENERATED_BODY() public: /** Target percentage of original triangle count */ UPROPERTY(EditAnywhere, Category = Options, meta = (UIMin = "0", UIMax = "100")) int TargetPercentage = 50; /** Discard UVs and existing normals, allowing the simplifier to ignore any UV and normal seams. New per-vertex normals are computed. */ UPROPERTY(EditAnywhere, Category = Options) bool bDiscardAttributes = false; /** Mesh Boundary Constraint Type */ UPROPERTY(EditAnywhere, Category = "SimplifyMesh|Boundary Constraints", meta = (DisplayName = "Mesh Boundary")) EMeshBoundaryConstraint MeshBoundaryConstraint = EMeshBoundaryConstraint::Free; /** Group Boundary Constraint Type */ UPROPERTY(EditAnywhere, Category = "SimplifyMesh|Boundary Constraints", meta = (DisplayName = "Group Boundary")) EGroupBoundaryConstraint GroupBoundaryConstraint = EGroupBoundaryConstraint::Ignore; /** Material Boundary Constraint Type */ UPROPERTY(EditAnywhere, Category = "SimplifyMesh|Boundary Constraints", meta = (DisplayName = "Material Boundary")) EMaterialBoundaryConstraint MaterialBoundaryConstraint = EMaterialBoundaryConstraint::Ignore; //~ Begin UDataprepOperation Interface public: virtual FText GetCategory_Implementation() const override { return FDataprepOperationCategories::MeshOperation; } protected: virtual void OnExecution_Implementation(const FDataprepContext& InContext) override; //~ End UDataprepOperation Interface }; UENUM() enum class EPlaneCutKeepSide : uint8 { Positive, Negative, Both }; UCLASS(Experimental, Category = MeshOperation, Meta = (DisplayName="Plane Cut", ToolTip = "Experimental - Plane cut input meshes") ) class UDataprepPlaneCutOperation : public UDataprepEditingOperation { GENERATED_BODY() public: UDataprepPlaneCutOperation() { CutPlaneOrigin = FVector(0, 0, 0); CutPlaneKeepSide = EPlaneCutKeepSide::Positive; CutPlaneNormalAngles = FVector(0.0f, 0.0f, 0.0f); SpacingBetweenHalves = 1.0f; bFillCutHole = false; bExportSeparatePieces = true; } /** Origin of the cutting plane */ UPROPERTY(EditAnywhere, Category = PlaneCut, meta = (DisplayName = "Plane's Origin")) FVector CutPlaneOrigin; /** Euler angles of the normal to the cutting plane (default plane is XY plane) */ UPROPERTY(EditAnywhere, Category = PlaneCut, meta = (DisplayName = "Plane's Orientation")) FVector CutPlaneNormalAngles; /** Specify which section(s) of the cut to keep. If 'Keep Both' is selected, both sides of the cut are computed and a new actor added with the negative side. */ UPROPERTY(EditAnywhere, Category = PlaneCut, meta = (DisplayName = "Side(s) To Keep")) EPlaneCutKeepSide CutPlaneKeepSide; /** If keeping both halves, separate the two pieces by this amount */ UPROPERTY(EditAnywhere, Category = PlaneCut, meta = (EditCondition = "CutPlaneKeepSide == EPlaneCutKeepSide::Both", UIMin = "0", ClampMin = "0")) float SpacingBetweenHalves; /** If true, the cut surface is filled with simple planar hole fill surface(s) */ UPROPERTY(EditAnywhere, Category = PlaneCut, meta = (DisplayName = "Fill Holes")) bool bFillCutHole; /** If true, meshes cut into multiple pieces will be saved as separate assets. */ UPROPERTY(EditAnywhere, Category = PlaneCut, meta = (DisplayName = "Export Separated Pieces As New Mesh Assets")) bool bExportSeparatePieces; //~ Begin UDataprepOperation Interface public: virtual FText GetCategory_Implementation() const override { return FDataprepOperationCategories::MeshOperation; } protected: virtual void OnExecution_Implementation(const FDataprepContext& InContext) override; //~ End UDataprepOperation Interface private: /** * Create new mesh component and assign new static mesh to it, and then add it to the Actor. * If Actor is a AStaticMeshActor instance, then the new mesh component will be it's existing StaticMeshComponent, and we will assign the new static mesh to it. * Otherwise, we create new StaticMeshComponent, assign NewStaticMesh, and set it as the Root Component of the Actor */ UStaticMeshComponent* FinalizeStaticMeshActor( AStaticMeshActor* InActor, const FString& InMeshName, const FMeshDescription* InMeshDescription, int InNumMaterialSlots, const UStaticMesh* InOriginalMesh); TUniquePtr CutStaticMesh( const FTransform& InTransform, const UStaticMesh* InStaticMesh); void PerformCutting( bool bKeepOriginalMesh, TArray& InStaticMeshes, const TArray& InCutPlaneTransforms, TArray>& InReferencingComponentsToUpdate, TArray& OutModifiedStaticMeshes, TArray& OutCutawayMeshes); // Borrowed from UPlaneCutOperatorFactory::MakeNewOperator TUniquePtr MakeNewOperator( const FTransform& InMeshLocalToWorld, TSharedPtr InOriginalMesh, float InMeshUVScaleFactor); };