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

461 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MeshRefinerBase.h"
#include "DynamicMesh/DynamicMeshAttributeSet.h"
#include "DynamicMesh/DynamicMeshChangeTracker.h"
using namespace UE::Geometry;
/*
* 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
*/
bool FMeshRefinerBase::CheckIfCollapseCreatesFlipOrInvalid(int vid, int vother, const FVector3d& newv, int tc, int td) const
{
FVector3d va = FVector3d::Zero(), vb = FVector3d::Zero(), vc = FVector3d::Zero();
for (int tid : Mesh->VtxTrianglesItr(vid))
{
if (tid == tc || tid == td)
{
continue;
}
FIndex3i curt = Mesh->GetTriangle(tid);
if (curt[0] == vother || curt[1] == vother || curt[2] == vother)
{
return true; // invalid nbrhood for collapse
}
Mesh->GetTriVertices(tid, va, vb, vc);
FVector3d ncur = (vb - va).Cross(vc - va);
double sign = 0;
if (curt[0] == vid)
{
FVector3d nnew = (vb - newv).Cross(vc - newv);
sign = ComputeEdgeFlipMetric(ncur, nnew);
}
else if (curt[1] == vid)
{
FVector3d nnew = (newv - va).Cross(vc - va);
sign = ComputeEdgeFlipMetric(ncur, nnew);
}
else if (curt[2] == vid)
{
FVector3d nnew = (vb - va).Cross(newv - va);
sign = ComputeEdgeFlipMetric(ncur, nnew);
}
else
{
check(false); // this should never happen!
}
if (sign <= EdgeFlipTolerance)
{
return true;
}
}
return false;
}
bool FMeshRefinerBase::CheckIfCollapseCreatesTinyTriangle(int VertexID, int OtherVertexID, const FVector3d& NewVertexPosition, int IncidentTriangleC, int IncidentTriangleD) const
{
for (int TriangleID : Mesh->VtxTrianglesItr(VertexID))
{
if (TriangleID == IncidentTriangleC || TriangleID == IncidentTriangleD)
{
continue;
}
FIndex3i CurrentTriangle = Mesh->GetTriangle(TriangleID);
if (CurrentTriangle[0] == OtherVertexID || CurrentTriangle[1] == OtherVertexID || CurrentTriangle[2] == OtherVertexID)
{
return true; // invalid nbrhood for collapse
}
FVector3d TriangleVertices[3];
Mesh->GetTriVertices(TriangleID, TriangleVertices[0], TriangleVertices[1], TriangleVertices[2]);
// If the current triangle is already tiny, *do* allow the creation of a new tiny triangle (to allow remeshing to continue and hopefully improve things)
FVector3d CurrentNormal = (TriangleVertices[1] - TriangleVertices[0]).Cross(TriangleVertices[2] - TriangleVertices[0]);
if (CurrentNormal.SquaredLength() < TinyTriangleThreshold)
{
continue;
}
// Now find the size of the triangle that would result
TriangleVertices[CurrentTriangle.IndexOf(VertexID)] = NewVertexPosition;
FVector3d NewNormal = (TriangleVertices[1] - TriangleVertices[0]).Cross(TriangleVertices[2] - TriangleVertices[0]);
if (NewNormal.SquaredLength() < TinyTriangleThreshold)
{
return true;
}
}
return false;
}
/**
* 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.
*/
bool FMeshRefinerBase::CheckIfFlipInvertsNormals(int a, int b, int c, int d, int t0) const
{
FVector3d vC = Mesh->GetVertex(c), vD = Mesh->GetVertex(d);
FIndex3i tri_v = Mesh->GetTriangle(t0);
int oa = a, ob = b;
IndexUtil::OrientTriEdge(oa, ob, tri_v);
FVector3d vOA = Mesh->GetVertex(oa), vOB = Mesh->GetVertex(ob);
FVector3d n0 = VectorUtil::NormalDirection(vOA, vOB, vC);
FVector3d n1 = VectorUtil::NormalDirection(vOB, vOA, vD);
FVector3d f0 = VectorUtil::NormalDirection(vC, vD, vOB);
if (ComputeEdgeFlipMetric(n0, f0) <= EdgeFlipTolerance || ComputeEdgeFlipMetric(n1, f0) <= EdgeFlipTolerance)
{
return true;
}
FVector3d f1 = VectorUtil::NormalDirection(vD, vC, vOA);
if (ComputeEdgeFlipMetric(n0, f1) <= EdgeFlipTolerance || ComputeEdgeFlipMetric(n1, f1) <= EdgeFlipTolerance)
{
return true;
}
// this only checks if output faces are pointing towards eachother, which seems
// to still result in normal-flips in some cases
//if (f0.Dot(f1) < 0)
// return true;
return false;
}
bool FMeshRefinerBase::CheckIfFlipCreatesTinyTriangle(int OriginalEdgeVertexA, int OriginalEdgeVertexB, int OppositeEdgeVertexC, int OppositeEdgeVertexD, int OriginalTriangleIndex) const
{
FVector3d vC = Mesh->GetVertex(OppositeEdgeVertexC);
FVector3d vD = Mesh->GetVertex(OppositeEdgeVertexD);
FVector3d vA = Mesh->GetVertex(OriginalEdgeVertexA);
FVector3d vB = Mesh->GetVertex(OriginalEdgeVertexB);
// If the current triangles are already tiny, allow new triangles to be tiny as well
double CurrNormalSquaredLen = (vA - vC).Cross(vB - vC).SquaredLength();
if (CurrNormalSquaredLen < TinyTriangleThreshold)
{
return false;
}
CurrNormalSquaredLen = (vA - vD).Cross(vB - vD).SquaredLength();
if (CurrNormalSquaredLen < TinyTriangleThreshold)
{
return false;
}
// Now check triangle area of potential new triangle
double NewNormalSquaredLen = (vD - vC).Cross(vD - vB).SquaredLength();
if (NewNormalSquaredLen < TinyTriangleThreshold)
{
return true;
}
NewNormalSquaredLen = (vD - vC).Cross(vD - vA).SquaredLength();
if (NewNormalSquaredLen < TinyTriangleThreshold)
{
return true;
}
return false;
}
/**
* 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.
*/
bool FMeshRefinerBase::CanCollapseEdge(int eid, int a, int b, int c, int d, int tc, int td, int& collapse_to) const
{
collapse_to = -1;
if (!Constraints)
{
return true;
}
// are the vertices themselves constrained in a way that prevents this collapse?
// nb: this modifies collapse_to if we have to keep a particular vert.
bool bVtx = CanCollapseVertex(eid, a, b, collapse_to);
if (bVtx == false)
{
return false;
}
// determine if edge constraints require keeping either vert.
bool bMustRetainA = false;
bool bMustRetainB = false;
// if edge ac is constrained, we must keep it and thus vertex a, likewise for edge bc and vertex b.
if (c != IndexConstants::InvalidID)
{
int32 eac = Mesh->FindEdgeFromTri(a, c, tc);
int32 ebc = Mesh->FindEdgeFromTri(b, c, tc);
bMustRetainA = bMustRetainA || (Constraints->GetEdgeConstraint(eac).IsUnconstrained() == false);
bMustRetainB = bMustRetainB || (Constraints->GetEdgeConstraint(ebc).IsUnconstrained() == false);
}
// if edge ad is constrained, we must keep it and thus vertex a, likewise for edge bd and vertex b.
if (d != IndexConstants::InvalidID)
{
int32 ead = Mesh->FindEdgeFromTri(a, d, td);
int32 ebd = Mesh->FindEdgeFromTri(b, d, td);
bMustRetainA = bMustRetainA || (Constraints->GetEdgeConstraint(ead).IsUnconstrained() == false);
bMustRetainB = bMustRetainB || (Constraints->GetEdgeConstraint(ebd).IsUnconstrained() == false);
}
bool bCanCollapse = true;
// adjacent edge constraints want us to keep both verts.. no can do.
if (bMustRetainA && bMustRetainB)
{
bCanCollapse = false;
}
else
{
if (collapse_to == -1)
{
// the vertex constraints didn't require either vertex.
// if the adjacent edge constraints require one, record it
if (bMustRetainA && !bMustRetainB)
{
collapse_to = a;
}
else if (bMustRetainB && !bMustRetainA)
{
collapse_to = b;
}
}
else if (collapse_to == a && bMustRetainB)
{
// vertex and adjacent edge constraints conflict
bCanCollapse = false;
}
else if (collapse_to == b && bMustRetainA)
{
// vertex and adjacent edge constraints conflict
bCanCollapse = false;
}
}
return bCanCollapse;
}
/**
* 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 non-deletable, then we want to keep that vertex. This vertex (a or b) will be returned in collapse_to,
* which is -1 otherwise.
* If a *and* b are non-deletable, then things are complicated (and documented below).
*/
bool FMeshRefinerBase::CanCollapseVertex(int eid, int a, int b, int& collapse_to) const
{
collapse_to = -1;
if (!Constraints)
{
return true;
}
FVertexConstraint ca = Constraints->GetVertexConstraint(a);
FVertexConstraint cb = Constraints->GetVertexConstraint(b);
bool bIsFixedA = (ca.bCanMove == false);
bool bIsFixedB = (cb.bCanMove == false);
if (bIsFixedA && bIsFixedB)
{
return false;
}
bool CanDeleteA = (ca.bCannotDelete == false);
bool CanDeleteB = (cb.bCannotDelete == false);
// no constraint at all
if ( CanDeleteA && CanDeleteB && ca.Target == nullptr && cb.Target == nullptr )
{
return true;
}
// handle a or b non-deletable
if ( ca.bCannotDelete && CanDeleteB )
{
// if b has a projection target, and it is different than a's target, we can't collapse
if (cb.Target != nullptr && cb.Target != ca.Target)
{
return false;
}
collapse_to = a;
return true;
}
if ( cb.bCannotDelete && CanDeleteA )
{
if (ca.Target != nullptr && ca.Target != cb.Target)
{
return false;
}
collapse_to = b;
return true;
}
// if both are non-deletable, and options allow, treat this edge as unconstrained (eg collapse to midpoint)
// TODO: tried picking a or b here, but something weird happens, where
// eg cylinder cap will entirely erode away. Somehow edge lengths stay below threshold??
if (AllowCollapseFixedVertsWithSameSetID
&& ca.FixedSetID >= 0
&& ca.FixedSetID == cb.FixedSetID)
{
return true;
}
// handle a or b w/ target
if (ca.Target != nullptr && cb.Target == nullptr)
{
collapse_to = a;
return true;
}
if (cb.Target != nullptr && ca.Target == nullptr)
{
collapse_to = b;
return true;
}
// if both vertices are on the same target, and the edge is on that target,
// then we can collapse to either and use the midpoint (which will be projected
// to the target). *However*, if the edge is not on the same target, then we
// cannot collapse because we would be changing the constraint topology!
if (cb.Target != nullptr && ca.Target != nullptr && ca.Target == cb.Target)
{
if (Constraints->GetEdgeConstraint(eid).Target == ca.Target)
{
return true;
}
}
return false;
}
void FMeshRefinerBase::SetMeshChangeTracker(FDynamicMeshChangeTracker* Tracker)
{
ActiveChangeTracker = Tracker;
}
void FMeshRefinerBase::SaveTriangleBeforeModify(int32 TriangleID)
{
if (ActiveChangeTracker)
{
ActiveChangeTracker->SaveTriangle(TriangleID, true);
}
}
void FMeshRefinerBase::SaveVertexTrianglesBeforeModify(int32 VertexID)
{
if (ActiveChangeTracker)
{
Mesh->EnumerateVertexTriangles(VertexID, [&](int32 TriangleID)
{
ActiveChangeTracker->SaveTriangle(TriangleID, true);
});
}
}
void FMeshRefinerBase::SaveEdgeBeforeModify(int32 EdgeID)
{
if (ActiveChangeTracker)
{
FIndex2i EdgeVerts = Mesh->GetEdgeV(EdgeID);
SaveVertexTrianglesBeforeModify(EdgeVerts.A);
SaveVertexTrianglesBeforeModify(EdgeVerts.B);
}
}
void FMeshRefinerBase::RuntimeDebugCheck(int eid)
{
if (DebugEdges.Contains(eid))
ensure(false);
}
void FMeshRefinerBase::DoDebugChecks(bool bEndOfPass)
{
if (DEBUG_CHECK_LEVEL == 0)
return;
DebugCheckVertexConstraints();
if ((DEBUG_CHECK_LEVEL > 2) || (bEndOfPass && DEBUG_CHECK_LEVEL > 1))
{
Mesh->CheckValidity(FDynamicMesh3::FValidityOptions::Permissive());
DebugCheckUVSeamConstraints();
}
}
void FMeshRefinerBase::DebugCheckUVSeamConstraints()
{
// verify UV constraints (temporary?)
if (Mesh->HasAttributes() && Mesh->Attributes()->PrimaryUV() != nullptr && Constraints)
{
for (int eid : Mesh->EdgeIndicesItr())
{
if (Mesh->Attributes()->PrimaryUV()->IsSeamEdge(eid))
{
auto cons = Constraints->GetEdgeConstraint(eid);
check(cons.IsUnconstrained() == false);
}
}
for (int vid : Mesh->VertexIndicesItr())
{
if (Mesh->Attributes()->PrimaryUV()->IsSeamVertex(vid))
{
auto cons = Constraints->GetVertexConstraint(vid);
check(cons.bCannotDelete == true);
}
}
}
}
void FMeshRefinerBase::DebugCheckVertexConstraints()
{
if (!Constraints)
{
return;
}
auto AllVtxConstraints = Constraints->GetVertexConstraints();
for (const TPair<int, FVertexConstraint>& vc : AllVtxConstraints)
{
int vid = vc.Key;
if (vc.Value.Target != nullptr)
{
FVector3d curpos = Mesh->GetVertex(vid);
FVector3d projected = vc.Value.Target->Project(curpos, vid);
check((curpos - projected).SquaredLength() < 0.0001f);
}
}
}