Geometry: Expose Remesh function in Geometry Script, and add basic convergence check to queue remesher

- add FQueueRemesher::MinActiveEdgeFraction. If fraction of modified/total edges in remesh pass is below this parameter (default 1%), consider result converged.
- Expose as option in FRemeshMeshOp, and add sane defaults for all parameters of Op
- move CalculateTargetEdgeLength function from RemeshMeshTool to static function in FRemeshMeshOp, update Tool
- add Geometry Script function ApplyUniformRemesh
#rb jimmy.andrews
#preflight 62a0f1923f1e313c6ad23c21

[CL 20562425 by Ryan Schmidt in ue5-main branch]
This commit is contained in:
Ryan Schmidt
2022-06-08 15:29:16 -04:00
parent 557df102ae
commit 050edd4706
10 changed files with 382 additions and 32 deletions
@@ -0,0 +1,148 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "GeometryScript/MeshRemeshFunctions.h"
#include "DynamicMesh/DynamicMesh3.h"
#include "DynamicMesh/DynamicMeshAABBTree3.h"
#include "DynamicMesh/DynamicMeshAttributeSet.h"
#include "DynamicMesh/MeshAttributeUtil.h"
#include "MeshConstraintsUtil.h"
#include "ProjectionTargets.h"
#include "DynamicMesh/MeshNormals.h"
#include "CleaningOps/RemeshMeshOp.h"
#include "UDynamicMesh.h"
using namespace UE::Geometry;
#define LOCTEXT_NAMESPACE "UGeometryScriptLibrary_MeshRemeshFunctions"
static EEdgeRefineFlags MakeEdgeRefineFlagsFromConstraintType(EGeometryScriptRemeshEdgeConstraintType ConstraintType)
{
switch (ConstraintType)
{
case EGeometryScriptRemeshEdgeConstraintType::Fixed: return EEdgeRefineFlags::FullyConstrained;
case EGeometryScriptRemeshEdgeConstraintType::Refine: return EEdgeRefineFlags::SplitsOnly;
case EGeometryScriptRemeshEdgeConstraintType::Free: return EEdgeRefineFlags::NoFlip;
case EGeometryScriptRemeshEdgeConstraintType::Ignore: return EEdgeRefineFlags::NoConstraint;
}
return EEdgeRefineFlags::NoFlip;
}
UDynamicMesh* UGeometryScriptLibrary_RemeshingFunctions::ApplyUniformRemesh(
UDynamicMesh* TargetMesh,
FGeometryScriptRemeshOptions RemeshOptions,
FGeometryScriptUniformRemeshOptions UniformOptions,
UGeometryScriptDebug* Debug)
{
if (TargetMesh == nullptr)
{
UE::Geometry::AppendError(Debug, EGeometryScriptErrorType::InvalidInputs, LOCTEXT("ApplyUniformRemesh_InvalidInput", "ApplyUniformRemesh: TargetMesh is Null"));
return TargetMesh;
}
TargetMesh->EditMesh([&](FDynamicMesh3& EditMesh)
{
TSharedPtr<FDynamicMesh3> SourceMesh = MakeShared<FDynamicMesh3>(MoveTemp(EditMesh));
TSharedPtr<FDynamicMeshAABBTree3> SourceSpatial;
if (RemeshOptions.bReprojectToInputMesh)
{
SourceSpatial = MakeShared<FDynamicMeshAABBTree3>(SourceMesh.Get(), true);
}
FRemeshMeshOp RemeshOp;
RemeshOp.OriginalMesh = SourceMesh;
RemeshOp.OriginalMeshSpatial = SourceSpatial;
RemeshOp.bDiscardAttributes = RemeshOptions.bDiscardAttributes;
RemeshOp.RemeshType = (RemeshOptions.bUseFullRemeshPasses) ? ERemeshType::FullPass : ERemeshType::Standard;
RemeshOp.RemeshIterations = RemeshOptions.RemeshIterations;
RemeshOp.MaxRemeshIterations = RemeshOptions.RemeshIterations;
RemeshOp.ExtraProjectionIterations = 0; // unused for regular remeshing
RemeshOp.TriangleCountHint = 0; // unused for regular remeshing
// smoothing options
RemeshOp.SmoothingStrength = FMath::Clamp(RemeshOptions.SmoothingRate, 0.0f, 1.0f);
switch (RemeshOptions.SmoothingType)
{
case EGeometryScriptRemeshSmoothingType::Mixed:
RemeshOp.SmoothingType = ERemeshSmoothingType::MeanValue;
break;
case EGeometryScriptRemeshSmoothingType::UVPreserving:
RemeshOp.SmoothingType = ERemeshSmoothingType::Cotangent;
break;
case EGeometryScriptRemeshSmoothingType::Uniform:
default:
RemeshOp.SmoothingType = ERemeshSmoothingType::Uniform;
break;
}
if (UniformOptions.TargetType == EGeometryScriptUniformRemeshTargetType::TriangleCount)
{
RemeshOp.TargetEdgeLength = FRemeshMeshOp::CalculateTargetEdgeLength(SourceMesh.Get(), UniformOptions.TargetTriangleCount);
}
else
{
RemeshOp.TargetEdgeLength = UniformOptions.TargetEdgeLength;
}
// currently not exposing this option. It seems to control multiple things that should be independent...
RemeshOp.bPreserveSharpEdges = (RemeshOp.bDiscardAttributes == false);
RemeshOp.bFlips = RemeshOptions.bAllowFlips;
RemeshOp.bSplits = RemeshOptions.bAllowSplits;
RemeshOp.bCollapses = RemeshOptions.bAllowCollapses;
RemeshOp.bPreventNormalFlips = RemeshOptions.bPreventNormalFlips;
RemeshOp.bPreventTinyTriangles = RemeshOptions.bPreventTinyTriangles;
RemeshOp.MeshBoundaryConstraint = MakeEdgeRefineFlagsFromConstraintType(RemeshOptions.MeshBoundaryConstraint);
RemeshOp.GroupBoundaryConstraint = MakeEdgeRefineFlagsFromConstraintType(RemeshOptions.GroupBoundaryConstraint);
RemeshOp.MaterialBoundaryConstraint = MakeEdgeRefineFlagsFromConstraintType(RemeshOptions.MaterialBoundaryConstraint);
RemeshOp.bReproject = RemeshOptions.bReprojectToInputMesh;
RemeshOp.ProjectionTarget = nullptr;
RemeshOp.ProjectionTargetSpatial = nullptr;
RemeshOp.bReprojectConstraints = false;
RemeshOp.BoundaryCornerAngleThreshold = 45.0;
RemeshOp.TargetMeshLocalToWorld = FTransformSRT3d::Identity();
RemeshOp.ToolMeshLocalToWorld = FTransformSRT3d::Identity();
RemeshOp.bUseWorldSpace = false;
RemeshOp.bParallel = true;
RemeshOp.CalculateResult(nullptr);
if (RemeshOp.GetResultInfo().Result == EGeometryResultType::Success)
{
TUniquePtr<FDynamicMesh3> ResultMesh = RemeshOp.ExtractResult();
EditMesh = MoveTemp(*ResultMesh);
}
else
{
EditMesh = MoveTemp(*SourceMesh);
UE::Geometry::AppendError(Debug, EGeometryScriptErrorType::InvalidInputs, LOCTEXT("ApplyUniformRemesh_ComputeError", "ApplyUniformRemesh: Error computing result, returning input mesh"));
}
// if we discarded attributes, re-enable the standard attributes
if (RemeshOptions.bDiscardAttributes)
{
EditMesh.EnableTriangleGroups();
EditMesh.EnableAttributes();
EditMesh.Attributes()->EnableMaterialID();
}
}, EDynamicMeshChangeType::GeneralEdit, EDynamicMeshAttributeChangeFlags::Unknown, false);
return TargetMesh;
}
#undef LOCTEXT_NAMESPACE
@@ -0,0 +1,157 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "GeometryScript/GeometryScriptTypes.h"
#include "MeshRemeshFunctions.generated.h"
class UDynamicMesh;
/** Goal types for Uniform Remeshing */
UENUM(BlueprintType)
enum class EGeometryScriptUniformRemeshTargetType : uint8
{
/** Approximate Desired Triangle Count. This is used to compute a Target Edge Length, and is not an explicit target */
TriangleCount = 0,
/** Attempt to Remesh such that all edges have approximately this length */
TargetEdgeLength = 1
};
/** Types of edge constraints, specified for different mesh attributes */
UENUM(BlueprintType)
enum class EGeometryScriptRemeshEdgeConstraintType : uint8
{
/** Constrained edges cannot be flipped, split or collapsed, and vertices will not move */
Fixed = 0,
/** Constrained edges can be split, but not flipped or collapsed. Vertices will not move. */
Refine = 1,
/** Constrained edges cannot be flipped, but otherwise are free to move */
Free = 2,
/** Edges are not constrained, ie the Attribute used to derive the Constraints will not be considered */
Ignore = 3
};
/** The Vertex Smoothing strategy used in a Remeshing operation */
UENUM(BlueprintType)
enum class EGeometryScriptRemeshSmoothingType : uint8
{
/** Vertices move towards their 3D one-ring centroids, UVs are ignored. This produces the most regular mesh possible. */
Uniform = 0,
/** Vertices move towards the projection of their one-ring centroids onto their normal vectors, preserving UVs */
UVPreserving = 1,
/** Similar to UV Preserving, but allows some tangential drift (causing UV distortion) when vertices would otherwise be "stuck" */
Mixed = 2
};
/**
* Standard Remeshing Options
*/
USTRUCT(Blueprintable)
struct GEOMETRYSCRIPTINGCORE_API FGeometryScriptRemeshOptions
{
GENERATED_BODY()
public:
/** When enabled, all mesh attributes are discarded, so UV and Normal Seams can be freely */
UPROPERTY(BlueprintReadWrite, Category = Options)
bool bDiscardAttributes = false;
/** When enabled, mesh vertices are projected back onto the input mesh surface during Remeshing, preserving the shape */
UPROPERTY(BlueprintReadWrite, Category = Options)
bool bReprojectToInputMesh = true;
/** Type of 3D Mesh Smoothing to apply during Remeshing. Disable by setting SmoothingRate = 0 */
UPROPERTY(BlueprintReadWrite, Category = Options)
EGeometryScriptRemeshSmoothingType SmoothingType = EGeometryScriptRemeshSmoothingType::Mixed;
/** Smoothing Rate/Speed. Faster Smoothing results in a more regular mesh, but also more potential for undesirable 3D shape change and UV distortion */
UPROPERTY(BlueprintReadWrite, Category = Options, meta = (UIMin = 0, UIMax = 1, ClampMin = 0, ClampMax = 1))
float SmoothingRate = 0.25f;
/** Constraints on the open mesh boundary/border edges */
UPROPERTY(BlueprintReadWrite, Category = Options)
EGeometryScriptRemeshEdgeConstraintType MeshBoundaryConstraint = EGeometryScriptRemeshEdgeConstraintType::Free;
/** Constraints on the mesh boundary/border edges between different PolyGroups of the Mesh */
UPROPERTY(BlueprintReadWrite, Category = Options)
EGeometryScriptRemeshEdgeConstraintType GroupBoundaryConstraint = EGeometryScriptRemeshEdgeConstraintType::Free;
/** Constraints on the mesh boundary/border edges between different Material Results of the Mesh */
UPROPERTY(BlueprintReadWrite, Category = Options)
EGeometryScriptRemeshEdgeConstraintType MaterialBoundaryConstraint = EGeometryScriptRemeshEdgeConstraintType::Free;
/** Enable/Disable Edge Flips during Remeshing. Disabling flips will significantly reduce the output mesh quality */
UPROPERTY(BlueprintReadWrite, Category = Options)
bool bAllowFlips = true;
/** Enable/Disable Edge Splits during Remeshing. Disabling Splits will prevent the mesh density from increasing. */
UPROPERTY(BlueprintReadWrite, Category = Options)
bool bAllowSplits = true;
/** Enable/Disable Edge Collapses during Remeshing. Disabling Collapses will prevent the mesh density from decreasing. */
UPROPERTY(BlueprintReadWrite, Category = Options)
bool bAllowCollapses = true;
/** When Enabled, Flips and Collapses will be skipped if they would flip any triangle face normals */
UPROPERTY(BlueprintReadWrite, Category = Options)
bool bPreventNormalFlips = true;
/** When Enabled, Flips and Collapses will be skipped if they would create tiny degenerate triangles */
UPROPERTY(BlueprintReadWrite, Category = Options)
bool bPreventTinyTriangles = true;
/** By default, remeshing is accelerated by tracking a queue of edges that need to be processed. This is signficantly faster but can produce a lower quality output. Enable this option to use a more expensive strategy that guarantees maximum quality. */
UPROPERTY(BlueprintReadWrite, Category = Options)
bool bUseFullRemeshPasses = false;
/** Maximum Number of iterations of the Remeshing Strategy to apply to the Mesh. More iterations are generally more expensive (much moreso with bUseFullRemeshPasses = true) */
UPROPERTY(BlueprintReadWrite, Category = Options, meta = (UIMin = 0, ClampMin = 0))
int32 RemeshIterations = 20;
};
/**
* Uniform Remeshing Options
*/
USTRUCT(Blueprintable)
struct GEOMETRYSCRIPTINGCORE_API FGeometryScriptUniformRemeshOptions
{
GENERATED_BODY()
public:
/** Method used to define target/goal of Uniform Remeshing */
UPROPERTY(BlueprintReadWrite, Category = Options)
EGeometryScriptUniformRemeshTargetType TargetType = EGeometryScriptUniformRemeshTargetType::TriangleCount;
/** Approximate Target Triangle Count, combined with mesh surface area to derive a TargetEdgeLength */
UPROPERTY(BlueprintReadWrite, Category = Options, meta = (UIMin = 0, ClampMin = 0, EditCondition = "TargetType == EGeometryScriptUniformRemeshTargetType::TriangleCount"))
int32 TargetTriangleCount = 5000;
/** Explicit Target Edge Length that is desired in the output uniform mesh */
UPROPERTY(BlueprintReadWrite, Category = Options, meta = (UIMin = 0, ClampMin = 0, EditCondition = "TargetType == EGeometryScriptUniformRemeshTargetType::TargetEdgeLength"))
float TargetEdgeLength = 1.0f;
};
UCLASS(meta = (ScriptName = "GeometryScript_Remeshing"))
class GEOMETRYSCRIPTINGCORE_API UGeometryScriptLibrary_RemeshingFunctions : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/**
* Apply Uniform Remeshing to the TargetMesh.
*/
UFUNCTION(BlueprintCallable, Category = "GeometryScript|Simplification", meta=(ScriptMethod))
static UPARAM(DisplayName = "Target Mesh") UDynamicMesh*
ApplyUniformRemesh(
UDynamicMesh* TargetMesh,
FGeometryScriptRemeshOptions RemeshOptions,
FGeometryScriptUniformRemeshOptions UniformOptions,
UGeometryScriptDebug* Debug = nullptr);
};