You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Problem is that during remesh passes the affected triangle set can expand (due to splits/collapses on ROI boundary) and our tracking of it is not perfect. Remesher now tells ChangeTracker before it modifies an edge, rather than Sculpt tool trying to predict set of modified triangles up front. In theory this allows for smaller changes, because often only a subset of triangles are modified. However ChangeTracker currently only emits modified vertices as part of modified triangles. So we still need to save the full triangle ROI. Made several MeshChangeTracker functions protected for now, because they cannot be used externally due to this limitation. Updated call sites to those functions (which were likely also buggy). #rb jimmy.andrews #rnx #jira none #ROBOMERGE-SOURCE: CL 12459928 in //UE4/Release-4.25/... via CL 12459930 via CL 12459991 #ROBOMERGE-BOT: RELEASE (Release-Engine-Staging -> Main) (v672-12450963) [CL 12460009 by ryan schmidt in Main branch]
339 lines
8.6 KiB
C++
339 lines
8.6 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "MeshRefinerBase.h"
|
|
#include "DynamicMeshAttributeSet.h"
|
|
#include "DynamicMeshChangeTracker.h"
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
bool bVtx = CanCollapseVertex(eid, a, b, collapse_to);
|
|
if (bVtx == false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// when we lose a vtx in a collapse, we also lose two edges [iCollapse,c] and [iCollapse,d].
|
|
// If either of those edges is constrained, we would lose that constraint.
|
|
// This would be bad.
|
|
int iCollapse = (collapse_to == a) ? b : a;
|
|
if (c != IndexConstants::InvalidID)
|
|
{
|
|
int ec = Mesh->FindEdgeFromTri(iCollapse, c, tc);
|
|
if (Constraints->GetEdgeConstraint(ec).IsUnconstrained() == false)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
if (d != IndexConstants::InvalidID)
|
|
{
|
|
int ed = Mesh->FindEdgeFromTri(iCollapse, d, td);
|
|
if (Constraints->GetEdgeConstraint(ed).IsUnconstrained() == false)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* 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).
|
|
*/
|
|
bool FMeshRefinerBase::CanCollapseVertex(int eid, int a, int b, int& collapse_to) const
|
|
{
|
|
collapse_to = -1;
|
|
if (!Constraints)
|
|
{
|
|
return true;
|
|
}
|
|
FVertexConstraint ca, cb;
|
|
Constraints->GetVertexConstraint(a, ca);
|
|
Constraints->GetVertexConstraint(b, cb);
|
|
|
|
// no constraint at all
|
|
if (ca.Fixed == false && cb.Fixed == false && ca.Target == nullptr && cb.Target == nullptr)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// handle a or b fixed
|
|
if (ca.Fixed == true && cb.Fixed == false)
|
|
{
|
|
// if b is fixed to a 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.Fixed == true && ca.Fixed == false)
|
|
{
|
|
if (ca.Target != nullptr && ca.Target != cb.Target)
|
|
{
|
|
return false;
|
|
}
|
|
collapse_to = b;
|
|
return true;
|
|
}
|
|
// if both fixed, and options allow, treat this edge as unconstrained (eg collapse to midpoint)
|
|
// [RMS] 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);
|
|
checkSlow(ActiveChangeTracker->VerifyTriangleIsSaved(TriangleID));
|
|
}
|
|
}
|
|
|
|
void FMeshRefinerBase::SaveVertexTrianglesBeforeModify(int32 VertexID)
|
|
{
|
|
if (ActiveChangeTracker)
|
|
{
|
|
for (int32 TriangleID : Mesh->VtxTrianglesItr(VertexID))
|
|
{
|
|
ActiveChangeTracker->SaveTriangle(TriangleID, true );
|
|
checkSlow(ActiveChangeTracker->VerifyTriangleIsSaved(TriangleID));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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.Fixed == 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);
|
|
}
|
|
}
|
|
}
|