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

314 lines
10 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
// Port of geometry3cpp FMeshRefinerBase
#pragma once
#include "DynamicMesh/DynamicMesh3.h"
#include "MeshConstraints.h"
#include "Util/ProgressCancel.h"
namespace UE
{
namespace Geometry
{
class FDynamicMeshChangeTracker;
// Hacky base class to avoid 8 bytes of padding after the vtable
class FMeshRefinerBaseFixLayout
{
public:
virtual ~FMeshRefinerBaseFixLayout() = default;
};
/**
* This is a base class that implements common functionality for various triangle mesh resampling strategies
* (ie FRemesher and FReducer). You probably should not use this class directly.
*/
class FMeshRefinerBase : public FMeshRefinerBaseFixLayout
{
protected:
/** Mesh that will be refined */
FDynamicMesh3* Mesh = nullptr;
/** Constraints are used to control how certain edges and vertices can be modified */
TOptional<FMeshConstraints> Constraints;
/** Vertices can be projected onto this surface when they are modified */
IProjectionTarget* ProjTarget = nullptr;
FDynamicMeshChangeTracker* ActiveChangeTracker = nullptr;
FMeshRefinerBase(FDynamicMesh3* MeshIn)
{
this->Mesh = MeshIn;
}
FMeshRefinerBase()
{
}
public:
enum class EVertexControl : uint8
{
AllowAll = 0,
NoSmooth = 1,
NoProject = 2,
NoMovement = NoSmooth | NoProject
};
/**
* This function allows client to specify fine-grained control over what happens to specific vertices.
* Somewhat redundant w/ FVertexConstraints, but simpler to code and has the option to be dynamic during remesh pass.
*/
TFunction<EVertexControl(int)> VertexControlF = nullptr;
/** Set this to be able to cancel running Remesher/Reducer*/
FProgressCancel* Progress = nullptr;
/** This is a debugging aid, will break to debugger if these edges are touched, in debug builds */
TArray<int> DebugEdges;
/** Options for projecting vertices onto target surface */
enum class ETargetProjectionMode : uint8
{
NoProjection = 0, // disable projection
AfterRefinement = 1, // do all projection after the refine/smooth pass
Inline = 2 // project after each vertex update. Better results but more
// expensive because eg we might create a vertex with
// split, then project, then smooth, then project again.
};
/** Method to use to project vertices onto target surface. Default is no projection. */
ETargetProjectionMode ProjectionMode = ETargetProjectionMode::NoProjection;
/**
* If true, then when two Fixed vertices have the same non-invalid SetID,
* we treat them as not fixed and allow collapse
*/
bool AllowCollapseFixedVertsWithSameSetID = true;
/** Set to true to profile various passes @todo re-enable this! */
bool ENABLE_PROFILING = false;
/** 0 = no checking, 1 = check constraints each pass, 2 = and check validity each pass, 3 = and check validity after every mesh change (v slow but best for debugging) */
int DEBUG_CHECK_LEVEL = 0;
public:
FMeshRefinerBase(const FMeshRefinerBase&) = delete;
FMeshRefinerBase(FMeshRefinerBase&&) = delete;
FMeshRefinerBase& operator=(const FMeshRefinerBase&) = delete;
FMeshRefinerBase& operator=(FMeshRefinerBase&&) = delete;
virtual ~FMeshRefinerBase() {}
/** Get the current mesh we are operating on */
FDynamicMesh3* GetMesh() { return Mesh; }
/** Get the current mesh constraints */
const TOptional<FMeshConstraints>& GetConstraints() { return Constraints; }
/**
* Set external constraints.
* Note that this object will be updated during computation.
*/
void SetExternalConstraints(TOptional<FMeshConstraints> ConstraintsIn) { Constraints = MoveTemp(ConstraintsIn); }
/** Get the current Projection Target */
IProjectionTarget* ProjectionTarget() { return this->ProjTarget; }
/** Set a Projection Target */
void SetProjectionTarget(IProjectionTarget* TargetIn) { this->ProjTarget = TargetIn; }
/** @return edge flip tolerance */
double GetEdgeFlipTolerance() { return EdgeFlipTolerance; }
/** Set edge flip tolerance. Value is clamped to range [-1,1] */
void SetEdgeFlipTolerance(double NewTolerance) { EdgeFlipTolerance = VectorUtil::Clamp(NewTolerance, -1.0, 1.0); }
/** If this returns true, abort computation. */
virtual bool Cancelled()
{
return (Progress == nullptr) ? false : Progress->Cancelled();
}
protected:
/** If normals dot product is less than this, we consider it a normal flip. default = 0 */
double EdgeFlipTolerance = 0.0f;
/**
* @return edge-flip dotproduct metric in range [-1,1] measured between two possibly-not-normalized normal directions. if EdgeFlipTolerance is 0, only the sign of the returned value is valid
*/
inline double ComputeEdgeFlipMetric(const FVector3d& Direction0, const FVector3d& Direction1) const
{
if (EdgeFlipTolerance == 0)
{
return Direction0.Dot(Direction1);
}
else
{
double ZeroTolerance = FMathd::ZeroTolerance;
return Normalized(Direction0, ZeroTolerance).Dot(Normalized(Direction1, ZeroTolerance));
}
}
/**
* Check if edge collapse will create a face-normal flip.
* Also checks if collapse would violate link condition, since we are iterating over one-ring anyway.
* This only checks one-ring of vid, so you have to call it twice, with vid and vother reversed, to check both one-rings
* @param vid first vertex of edge
* @param vother other vertex of edge
* @param newv new vertex position after collapse
* @param tc triangle on one side of edge
* @param td triangle on other side of edge
*/
DYNAMICMESH_API bool CheckIfCollapseCreatesFlipOrInvalid(int vid, int vother, const FVector3d& newv, int tc, int td) const;
/** Avoid creation of triangles smaller than this value.
* We compare the triangle cross product's norm squared to this value since it's cheaper to compute than the triangle area. So the actual
* area threshold is sqrt(TinyTriangleThreshold)/2.
* Default is based on the threshold for creating triangles in a simulation mesh
*/
double TinyTriangleThreshold = SMALL_NUMBER;
/**
* Check if edge collapse will create a triangle with small area (either all vertices are close together, or in a sliver configuration)
* This only checks one-ring of vid, so you have to call it twice, with vid and vother reversed, to check both one-rings
* @param vid first vertex of edge
* @param vother other vertex of edge
* @param newv new vertex position after collapse
* @param tc triangle on one side of edge
* @param td triangle on other side of edge
*/
DYNAMICMESH_API bool CheckIfCollapseCreatesTinyTriangle(int vid, int vother, const FVector3d& newv, int tc, int td) const;
/**
* Check if edge flip might reverse normal direction.
* Not entirely clear on how to best implement this test. Currently checking if any normal-pairs are reversed.
* @param a first vertex of edge
* @param b second vertex of edge
* @param c opposing vertex 1
* @param d opposing vertex 2
* @param t0 index of triangle containing [a,b,c]
*/
DYNAMICMESH_API bool CheckIfFlipInvertsNormals(int a, int b, int c, int d, int t0) const;
/**
* Check if edge flip might create a triangle with small area (either all vertices are close together, or in a sliver configuration)
* @param a first vertex of edge
* @param b second vertex of edge
* @param c opposing vertex 1
* @param d opposing vertex 2
* @param t0 index of triangle containing [a,b,c]
*/
DYNAMICMESH_API bool CheckIfFlipCreatesTinyTriangle(int OriginalEdgeVertexA, int OriginalEdgeVertexB, int OppositeEdgeVertexC, int OppositeEdgeVertexD, int OriginalTriangleIndex) const;
/**
* Figure out if we can collapse edge eid=[a,b] under current constraint set.
* First we resolve vertex constraints using CanCollapseVertex(). However this
* does not catch some topological cases at the edge-constraint level, which
* which we will only be able to detect once we know if we are losing a or b.
* See comments on CanCollapseVertex() for what collapse_to is for.
* @param a first vertex of edge
* @param b second vertex of edge
* @param c opposing vertex 1
* @param d opposing vertex 2
* @param tc index of triangle [a,b,c]
* @param td index of triangle [a,b,d]
* @param collapse_to either a or b if we should collapse to one of those, or -1 if either is acceptable
*/
DYNAMICMESH_API bool CanCollapseEdge(int eid, int a, int b, int c, int d, int tc, int td, int& collapse_to) const;
/**
* Resolve vertex constraints for collapsing edge eid=[a,b]. Generally we would
* collapse a to b, and set the new position as 0.5*(v_a+v_b). However if a *or* b
* are constrained, then we want to keep that vertex and collapse to its position.
* This vertex (a or b) will be returned in collapse_to, which is -1 otherwise.
* If a *and* b are constrained, then things are complicated (and documented below).
* @param eid edge ID
* @param a first vertex of edge
* @param b second vertex of edge*
* @param collapse_to either a or b if we should collapse to one of those, or -1 if either is acceptable
*/
DYNAMICMESH_API bool CanCollapseVertex(int eid, int a, int b, int& collapse_to) const;
/**
* @return true if given vertex can't move, or has a projection target
*/
inline bool IsVertexPositionConstrained(int VertexID)
{
if (Constraints)
{
FVertexConstraint vc = Constraints->GetVertexConstraint(VertexID);
return (!vc.bCanMove || vc.Target != nullptr);
}
return false;
}
/**
* @return constraint for given vertex
*/
inline FVertexConstraint GetVertexConstraint(int VertexID)
{
if (Constraints)
{
return Constraints->GetVertexConstraint(VertexID);
}
return FVertexConstraint::Unconstrained();
}
/**
* @return true if vertex has constraint
*/
inline bool GetVertexConstraint(int VertexID, FVertexConstraint& OutConstraint)
{
return Constraints &&
Constraints->GetVertexConstraint(VertexID, OutConstraint);
}
public:
//
// Mesh Change Tracking support
//
DYNAMICMESH_API void SetMeshChangeTracker(FDynamicMeshChangeTracker* Tracker);
protected:
DYNAMICMESH_API virtual void SaveTriangleBeforeModify(int32 TriangleID);
DYNAMICMESH_API virtual void SaveEdgeBeforeModify(int32 EdgeID);
DYNAMICMESH_API virtual void SaveVertexTrianglesBeforeModify(int32 VertexID);
protected:
/*
* testing/debug/profiling stuff
*/
DYNAMICMESH_API void RuntimeDebugCheck(int EdgeID);
DYNAMICMESH_API virtual void DoDebugChecks(bool bEndOfPass = false);
DYNAMICMESH_API void DebugCheckUVSeamConstraints();
DYNAMICMESH_API void DebugCheckVertexConstraints();
};
} // end namespace UE::Geometry
} // end namespace UE