461 lines
12 KiB
C++
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);
|
|
}
|
|
}
|
|
}
|