Files
2025-05-18 13:04:45 +08:00

395 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
// Port of geometry3cpp Remesher
#pragma once
#include "MeshRefinerBase.h"
#define UE_API DYNAMICMESH_API
namespace UE
{
namespace Geometry
{
/**
* FRemesher implements edge flip/split/collapse/smooth Remeshing.
* The basic process is very similar to the one described in:
* A Remeshing Approach to Multiresolution Modeling. Botsch & Kobbelt, SGP 2004.
* https://graphics.uni-bielefeld.de/publications/sgp04.pdf
*
* In the iterative usage, each call to BasicRemeshPass() makes a pass over the mesh
* and attempts to update the topology and shape to satisfy the target criteria.
* This function iterates over the mesh edges and calls ProcessEdge(), which tries to
* Collapse, then Split, then Flip the edge. After the topology pass, we optionally
* do a full-mesh Smoothing pass, and a full-mesh Reprojection pass, to project
* onto a target surface.
*
* A highly flexible constraint system can be used to control what is allowed to
* happen to individual edges and vertices, as well as Project vertices onto arbitrary
* proejction targets. This is done via FMeshConstraints which is set in the MeshRefinerBase base class.
*
* Many aspects of the algorithm can be controlled by the public variables in the block below.
* In addition many of the core internal functions can be overriden to customize behavior.
* Various callbacks functions are called when topology changes occur which allows
* you to for instance track changed regions of the mesh.
*
* @todo parallel smoothing and projection
* @todo port fast queue-based convergent remeshing
*
*/
class FRemesher : public FMeshRefinerBase
{
public:
//
// Configuration variables and flags
//
/** Controls whether edge-flips are allowed during remeshing pass */
bool bEnableFlips = true;
/** Controls whether edge collapses are allowed during remeshing pass */
bool bEnableCollapses = true;
/** Controls whether edge splits are allowed during remeshing pass */
bool bEnableSplits = true;
/** Controls whether vertex smoothing is applied during remeshing pass */
bool bEnableSmoothing = true;
/** Controls whether we try to prevent normal flips inside operations and smoothing.
* This is moderately expensive and often is not needed. Disabled by default.
*/
bool bPreventNormalFlips = false;
/** Controls whether we disallow creation of triangles with small areas inside edge operations.
* This is moderately expensive and in some cases can result in lower-quality meshes. Disabled by default.
*/
bool bPreventTinyTriangles = false;
/** Available Edge Flip metrics */
enum class EFlipMetric
{
OptimalValence = 0, /** Flip towards Valence=6 */
MinEdgeLength = 1 /** Flip towards minimum edge lengths */
};
/** Type of flip metric that will be applied */
EFlipMetric FlipMetric = EFlipMetric::OptimalValence;
/** For MinEdgeLength metric, only flip if NewLength < (MinLengthFlipThresh * CurLength) */
double MinLengthFlipThresh = 0.9;
/** Minimum target edge length. Edges shorter than this will be collapsed if possible. */
double MinEdgeLength = 1.0;
/** Maximum target edge length. Edges longer than this will be split if possible. */
double MaxEdgeLength = 3.0;
/** Smoothing speed, in range [0,1] */
double SmoothSpeedT = 0.1;
/** Built-in Smoothing types */
enum class ESmoothTypes
{
Uniform = 0, /** Uniform weights, produces regular mesh and fastest convergence */
Cotan = 1, /** Cotangent weights prevent tangential flow and hence preserve triangle shape / texture coordinates, but can become unstable... */
MeanValue = 2 /** Mean Value weights also have reduced tangential flow but are never negative and hence more stable */
};
/** Type of smoothing that will be applied unless overridden by CustomSmoothF */
ESmoothTypes SmoothType = ESmoothTypes::Uniform;
/** Override default smoothing function */
TFunction<FVector3d(const FDynamicMesh3&, int, double)> CustomSmoothF;
/** Override constant SmoothSpeedT with function */
TFunction<double(const FDynamicMesh3&, int)> CustomSmoothSpeedF;
/** Scale the computed edge length to allow for adaptive remeshing */
TFunction<double(const FDynamicMesh3& Mesh, int VertexA, int VertexB)> CustomEdgeLengthScaleF;
/** enable parallel projection. Only applied in AfterRefinement mode */
bool bEnableParallelProjection = true;
/**
* Enable parallel smoothing. This will produce slightly different results
* across runs because we smooth in-place and hence there will be order side-effects.
*/
bool bEnableParallelSmooth = true;
/**
* If smoothing is done in-place, we don't need an extra buffer, but also there will some randomness introduced in results. Probably worse.
*/
bool bEnableSmoothInPlace = false;
public:
//
// Public API
//
FRemesher(FDynamicMesh3* MeshIn) : FMeshRefinerBase(MeshIn)
{
}
virtual ~FRemesher() {}
/** Set min/max edge-lengths to sane values for given target edge length */
UE_API void SetTargetEdgeLength(double fLength);
/**
* We can speed things up if we precompute some invariants.
* You need to re-run this if you are changing the mesh externally
* between remesh passes, otherwise you will get weird results.
* But you will probably still come out ahead, computation-time-wise
*/
UE_API virtual void Precompute();
/**
* Linear edge-refinement pass, followed by smoothing and projection
* - Edges are processed in prime-modulo-order to break symmetry
* - smoothing is done in parallel if EnableParallelSmooth = true
* - Projection pass if ProjectionMode == AfterRefinement
* - number of modified edges returned in ModifiedEdgesLastPass
*/
UE_API virtual void BasicRemeshPass();
public:
//
// Callbacks and in-progress information
//
/** Callback for subclasses to override to implement custom behavior */
virtual void OnEdgeSplit(int EdgeID, int VertexA, int VertexB, const FDynamicMesh3::FEdgeSplitInfo& SplitInfo)
{
// this is for subclasses...
}
/** Callback for subclasses to override to implement custom behavior */
virtual void OnEdgeCollapse(int EdgeID, int VertexA, int VertexB, const FDynamicMesh3::FEdgeCollapseInfo& CollapseInfo)
{
// this is for subclasses...
}
/** Callback for subclasses to override to implement custom behavior */
virtual void OnEdgeFlip(int EdgeID, const FDynamicMesh3::FEdgeFlipInfo& FlipInfo)
{
// this is for subclasses...
}
/**
* Number of edges that were modified in previous Remesh pass.
* If this number gets small relative to edge count, you have probably converged (ish)
*/
int ModifiedEdgesLastPass = 0;
protected:
FRemesher() // for subclasses that extend our behavior
{
}
/** we can avoid some checks/etc if we know that the mesh has no boundary edges. Set by Precompute() */
bool bMeshIsClosed = false;
// this just lets us write more concise code
bool EnableInlineProjection() const { return ProjectionMode == ETargetProjectionMode::Inline; }
// StartEdges() and GetNextEdge() control the iteration over edges that will be refined.
// Default here is to iterate over entire mesh->
// Subclasses can override these two functions to restrict the affected edges (eg EdgeLoopFRemesher)
// We are using a modulo-index loop to break symmetry/pathological conditions.
// For example in a highly tessellated minimal cylinder, if the top/bottom loops have
// sequential edge IDs, and all edges are < min edge length, then we can easily end
// up successively collapsing each tiny edge, and eroding away the entire mesh!
// By using modulo-index loop we jump around and hence this is unlikely to happen.
uint64 MaxEdgeID = 0;
virtual int StartEdges()
{
MaxEdgeID = static_cast<uint64>(Mesh->MaxEdgeID());
return 0;
}
virtual int GetNextEdge(int CurEdgeID, bool& bDone)
{
constexpr uint64 ModuloPrime = 4294967311ull; // choose prime > max uint32, to always be co-prime with MaxEdgeID
int new_eid = static_cast<int>( (static_cast<uint64>(CurEdgeID) + ModuloPrime) % MaxEdgeID );
bDone = (new_eid == 0);
return new_eid;
}
enum class EProcessResult
{
Ok_Collapsed,
Ok_Flipped,
Ok_Split,
Ignored_EdgeIsFine,
Ignored_EdgeIsFullyConstrained,
Failed_OpNotSuccessful,
Failed_NotAnEdge
};
UE_API virtual EProcessResult ProcessEdge(int edgeID);
// After we split an edge, we have created a new edge and a new vertex.
// The edge needs to inherit the constraint on the other pre-existing edge that we kept.
// In addition, if the edge vertices were both constrained, then we /might/
// want to also constrain this new vertex, possibly project to constraint target.
UE_API virtual void UpdateAfterSplit(int edgeID, int va, int vb, const FDynamicMesh3::FEdgeSplitInfo& splitInfo);
TDynamicVector<FVector3d> TempPosBuffer; // this is a temporary buffer used by smoothing and projection
TArray<bool> TempFlagBuffer; // list of which indices in TempPosBuffer were modified
UE_API virtual void InitializeVertexBufferForPass();
UE_API virtual void ApplyVertexBuffer(bool bParallel);
// applies a smoothing pass to the mesh, storing intermediate positions in a buffer and then writing them at the end (so, no order effect)
UE_API virtual void FullSmoothPass_Buffer(bool bParallel);
// computes smoothed vertex position w/ proper constraints/etc. Does not modify mesh.
UE_API virtual FVector3d ComputeSmoothedVertexPos(int VertexID,
TFunction<FVector3d(const FDynamicMesh3 &, int, double)> SmoothFunc, bool& bModified);
// FullSmoothPass_Buffer this to calls VertexSmoothFunc for each vertex of the mesh
UE_API virtual void ApplyToSmoothVertices(const TFunction<void(int)>& VertexSmoothFunc);
// returns the function we want to use to compute a smoothed vertex position - will be CustomSmoothF if set, otherwise one of cotan/meanvalue/uniform
UE_API virtual TFunction<FVector3d(const FDynamicMesh3&, int, double)> GetSmoothFunction();
// Project vertices onto projection target.
UE_API virtual void FullProjectionPass(bool bParallel);
// Project a single vertex using the given target
UE_API virtual void ProjectVertex(int VertexID, IProjectionTarget* UseTarget);
// used by collapse-edge to get projected position for new vertex
UE_API virtual FVector3d GetProjectedCollapsePosition(int VertexID, const FVector3d& vNewPos);
// Apply the given projection function to all vertices in the mesh
UE_API virtual void ApplyToProjectVertices(const TFunction<void(int)>& VertexProjectFunc);
// Move all vertices in parallel. Update the timestamps.
// The NewVertexPosition function accepts a vertex index and returns a new vertex position, as well as setting the
// bool out-param to indicate that the vertex has moved.
UE_API void MoveVerticesParallel(TFunction<FVector3d(int, bool&)> NewVertexPosition);
/*
* testing/debug/profiling stuff
*/
protected:
//
// profiling functions, turn on ENABLE_PROFILING to see output in console
//
int COUNT_SPLITS, COUNT_COLLAPSES, COUNT_FLIPS;
//Stopwatch AllOpsW, SmoothW, ProjectW, FlipW, SplitW, CollapseW;
virtual void ProfileBeginPass()
{
if (ENABLE_PROFILING)
{
COUNT_SPLITS = COUNT_COLLAPSES = COUNT_FLIPS = 0;
//AllOpsW = new Stopwatch();
//SmoothW = new Stopwatch();
//ProjectW = new Stopwatch();
//FlipW = new Stopwatch();
//SplitW = new Stopwatch();
//CollapseW = new Stopwatch();
}
}
virtual void ProfileEndPass()
{
if (ENABLE_PROFILING) {
//System.Console.WriteLine(string.Format(
// "RemeshPass: T {0} V {1} splits {2} flips {3} collapses {4}", mesh->TriangleCount, mesh->VertexCount, COUNT_SPLITS, COUNT_FLIPS, COUNT_COLLAPSES
// ));
//System.Console.WriteLine(string.Format(
// " Timing1: ops {0} smooth {1} project {2}", Util.ToSecMilli(AllOpsW.Elapsed), Util.ToSecMilli(SmoothW.Elapsed), Util.ToSecMilli(ProjectW.Elapsed)
// ));
//System.Console.WriteLine(string.Format(
// " Timing2: collapse {0} flip {1} split {2}", Util.ToSecMilli(CollapseW.Elapsed), Util.ToSecMilli(FlipW.Elapsed), Util.ToSecMilli(SplitW.Elapsed)
// ));
}
}
virtual void ProfileBeginOps()
{
//if ( ENABLE_PROFILING ) AllOpsW.Start();
}
virtual void ProfileEndOps()
{
//if ( ENABLE_PROFILING ) AllOpsW.Stop();
}
virtual void ProfileBeginSmooth()
{
//if ( ENABLE_PROFILING ) SmoothW.Start();
}
virtual void ProfileEndSmooth()
{
//if ( ENABLE_PROFILING ) SmoothW.Stop();
}
virtual void ProfileBeginProject()
{
//if ( ENABLE_PROFILING ) ProjectW.Start();
}
virtual void ProfileEndProject()
{
//if ( ENABLE_PROFILING ) ProjectW.Stop();
}
virtual void ProfileBeginCollapse()
{
//if ( ENABLE_PROFILING ) CollapseW.Start();
}
virtual void ProfileEndCollapse()
{
//if ( ENABLE_PROFILING ) CollapseW.Stop();
}
virtual void ProfileBeginFlip()
{
//if ( ENABLE_PROFILING ) FlipW.Start();
}
virtual void ProfileEndFlip()
{
//if ( ENABLE_PROFILING ) FlipW.Stop();
}
virtual void ProfileBeginSplit()
{
//if ( ENABLE_PROFILING ) SplitW.Start();
}
virtual void ProfileEndSplit()
{
//if ( ENABLE_PROFILING ) SplitW.Stop();
}
};
} // end namespace UE::Geometry
} // end namespace UE
#undef UE_API