// Copyright Epic Games, Inc. All Rights Reserved. #include "Operations/MinimalHoleFiller.h" #include "Operations/SimpleHoleFiller.h" #include "MeshQueries.h" using namespace UE::Geometry; namespace { TSet GetBoundaryEdgeVertices(const FDynamicMesh3& Mesh) { TSet BoundaryVertices; for (int EdgeID : Mesh.BoundaryEdgeIndicesItr()) { const FDynamicMesh3::FEdge Edge = Mesh.GetEdge(EdgeID); BoundaryVertices.Add(Edge.Vert[0]); BoundaryVertices.Add(Edge.Vert[1]); } return BoundaryVertices; } /// Check if collapsing edge EdgeID to point NewVertexPosition will flip normal of any attached face bool CheckIfCollapseCreatesFlip(const FDynamicMesh3& Mesh, int EdgeID, const FVector3d& NewVertexPosition, double EdgeFlipTolerance = 0.0) { const FDynamicMesh3::FEdge Edge = Mesh.GetEdge(EdgeID); int TriangleC = Edge.Tri[0]; int TriangleD = Edge.Tri[1]; for (int j = 0; j < 2; ++j) { int VertexID = Edge.Vert[j]; int OtherVertexID = Edge.Vert[(j + 1) % 2]; for (int TriangleID : Mesh.VtxTrianglesItr(VertexID)) { if (TriangleID == TriangleC || TriangleID == TriangleD) { continue; } FIndex3i CurrentTriangle = Mesh.GetTriangle(TriangleID); if (CurrentTriangle.A == OtherVertexID || CurrentTriangle.B == OtherVertexID || CurrentTriangle.C == OtherVertexID) { return true; // invalid nbrhood for collapse } FVector3d VertexA = Mesh.GetVertex(CurrentTriangle.A); FVector3d VertexB = Mesh.GetVertex(CurrentTriangle.B); FVector3d VertexC = Mesh.GetVertex(CurrentTriangle.C); FVector3d CurrentTriangleNormal = (VertexB - VertexA).Cross(VertexC - VertexA); double Sign = 0; if (CurrentTriangle.A == VertexID) { FVector3d NewTriangleNormal = (VertexB - NewVertexPosition).Cross(VertexC - NewVertexPosition); Sign = CurrentTriangleNormal.Dot(NewTriangleNormal); } else if (CurrentTriangle.B == VertexID) { FVector3d NewTriangleNormal = (NewVertexPosition - VertexA).Cross(VertexC - VertexA); Sign = CurrentTriangleNormal.Dot(NewTriangleNormal); } else if (CurrentTriangle.C == VertexID) { FVector3d NewTriangleNormal = (VertexB - VertexA).Cross(NewVertexPosition - VertexA); Sign = CurrentTriangleNormal.Dot(NewTriangleNormal); } else { check(!"Should not get here"); return false; } if (Sign <= 0.0) { return true; } } } return false; } double EdgeFlipMetric(FVector3d Normal0, FVector3d Normal1, double FlipDotTol) { return (FlipDotTol == 0) ? Normal0.Dot(Normal1) : Normalized(Normal0).Dot(Normalized(Normal1)); } void GetEdgeFlipTris(const FDynamicMesh3& Mesh, int EdgeID, FIndex3i& IncidentTriangle0, FIndex3i& IncidentTriangle1, FIndex3i& NewTriangle0, FIndex3i& NewTriangle1) { const FDynamicMesh3::FEdge Edge = Mesh.GetEdge(EdgeID); FIndex2i OpposingVertices = Mesh.GetEdgeOpposingV(EdgeID); int a = Edge.Vert[0]; int b = Edge.Vert[1]; int c = OpposingVertices.A; int d = OpposingVertices.B; FIndex3i IncidentTriangle = Mesh.GetTriangle(Edge.Tri[0]); int oa = a; int ob = b; IndexUtil::OrientTriEdge(oa, ob, IncidentTriangle); IncidentTriangle0 = { oa, ob, c }; IncidentTriangle1 = { ob, oa, d }; NewTriangle0 = { c, d, ob }; NewTriangle1 = { d, c, oa }; } /// For given edge, return normals of its two triangles, and normals /// of the triangles created if edge is flipped (used in edge-flip optimizers) void GetEdgeFlipNormals(const FDynamicMesh3& Mesh, int EdgeID, FVector3d& IncidentTriangleNormal0, FVector3d& IncidentTriangleNormal1, FVector3d& NewTriangleNormal0, FVector3d& NewTriangleNormal1) { const FDynamicMesh3::FEdge Edge = Mesh.GetEdge(EdgeID); FIndex2i OpposingVertices = Mesh.GetEdgeOpposingV(EdgeID); FVector3d vC = Mesh.GetVertex(OpposingVertices.A), vD = Mesh.GetVertex(OpposingVertices.B); int IncidentTriangleIndex = Edge.Tri[0]; FIndex3i IncidentTriangle = Mesh.GetTriangle(IncidentTriangleIndex); int OrientedA = Edge.Vert[0], OrientedB = Edge.Vert[1]; IndexUtil::OrientTriEdge(OrientedA, OrientedB, IncidentTriangle); FVector3d OrientedAPosition = Mesh.GetVertex(OrientedA), OrientedBPosition = Mesh.GetVertex(OrientedB); IncidentTriangleNormal0 = VectorUtil::Normal(OrientedAPosition, OrientedBPosition, vC); IncidentTriangleNormal1 = VectorUtil::Normal(OrientedBPosition, OrientedAPosition, vD); NewTriangleNormal0 = VectorUtil::Normal(vC, vD, OrientedBPosition); NewTriangleNormal1 = VectorUtil::Normal(vD, vC, OrientedAPosition); } /// If before a flip we have normals (n1,n2) and after we have (m1,m2), check if /// the dot between any of the 4 pairs changes sign after the flip, or is /// less than the dot-product tolerance (i.e. angle tolerance) bool CheckIfEdgeFlipCreatesFlip(const FDynamicMesh3 Mesh, int EdgeID, double FlipDotTol = 0.0) { check(Mesh.IsBoundaryEdge(EdgeID) == false); const FDynamicMesh3::FEdge Edge = Mesh.GetEdge(EdgeID); FIndex2i OpposingVertex = Mesh.GetEdgeOpposingV(EdgeID); FIndex3i TriangleVertices = Mesh.GetTriangle(Edge.Tri[0]); int OrientedA = Edge.Vert[0]; int OrientedB = Edge.Vert[1]; IndexUtil::OrientTriEdge(OrientedA, OrientedB, TriangleVertices); FVector3d OrientedAPosition = Mesh.GetVertex(OrientedA), OrientedBPosition = Mesh.GetVertex(OrientedB); FVector3d CPosition = Mesh.GetVertex(OpposingVertex.A), DPosition = Mesh.GetVertex(OpposingVertex.B); FVector3d Normal0 = VectorUtil::NormalDirection(OrientedAPosition, OrientedBPosition, CPosition); FVector3d Normal1 = VectorUtil::NormalDirection(OrientedBPosition, OrientedAPosition, DPosition); FVector3d NewNormal0 = VectorUtil::NormalDirection(CPosition, DPosition, OrientedBPosition); if (EdgeFlipMetric(Normal0, NewNormal0, FlipDotTol) <= FlipDotTol || EdgeFlipMetric(Normal1, NewNormal0, FlipDotTol) <= FlipDotTol) { return true; } FVector3d NewNormal1 = VectorUtil::NormalDirection(DPosition, CPosition, OrientedAPosition); if (EdgeFlipMetric(Normal0, NewNormal1, FlipDotTol) <= FlipDotTol || EdgeFlipMetric(Normal1, NewNormal1, FlipDotTol) <= FlipDotTol) { return true; } return false; } // TODO: Not sure why, but std::swap doesn't compile with TSet void SwapSets(TSet& A, TSet& B) { TSet tmp = MoveTemp(A); A = MoveTemp(B); B = MoveTemp(tmp); } } // namespace double FMinimalHoleFiller::AspectMetric(int EdgeID) { FIndex3i IncidentTriangle0, IncidentTriangle1, NewTriangle0, NewTriangle1; GetEdgeFlipTris(*FillMesh, EdgeID, IncidentTriangle0, IncidentTriangle1, NewTriangle0, NewTriangle1); double AspectTriangle0 = GetTriAspect(*FillMesh, IncidentTriangle0); double AspectTriangle1 = GetTriAspect(*FillMesh, IncidentTriangle1); double AspectNewTriangle0 = GetTriAspect(*FillMesh, NewTriangle0); double AspectNewTriangle1 = GetTriAspect(*FillMesh, NewTriangle1); double MetricExistingTriangles = FMath::Abs(AspectTriangle0 - 1.0) + FMath::Abs(AspectTriangle1 - 1.0); double MetricNewTriangles = FMath::Abs(AspectNewTriangle0 - 1.0) + FMath::Abs(AspectNewTriangle1 - 1.0); return MetricNewTriangles / MetricExistingTriangles; } void FMinimalHoleFiller::RemoveRemainingInteriorVerts() { TSet InteriorVertices; for (int VertexIndex : FillMesh->VertexIndicesItr()) { if (!FillMesh->IsBoundaryVertex(VertexIndex)) { InteriorVertices.Add(VertexIndex); } } int PreviousCount = 0; while (InteriorVertices.Num() > 0 && InteriorVertices.Num() != PreviousCount) { PreviousCount = InteriorVertices.Num(); TArray CurrentVertices = InteriorVertices.Array(); for (int VertexID : CurrentVertices) { for (int EdgeID : FillMesh->VtxEdgesItr(VertexID)) { FIndex2i EdgeVertices = FillMesh->GetEdgeV(EdgeID); int OtherVertexID = (EdgeVertices.A == VertexID) ? EdgeVertices.B : EdgeVertices.A; FDynamicMesh3::FEdgeCollapseInfo Info; EMeshResult Result = FillMesh->CollapseEdge(OtherVertexID, VertexID, Info); if (Result == EMeshResult::Ok) { break; } } if (FillMesh->IsVertex(VertexID) == false) { InteriorVertices.Remove(VertexID); } } } } void FMinimalHoleFiller::UpdateCurvature(int VertexID) { double AngleSum = 0; if (ExteriorAngleSums.Contains(VertexID)) { AngleSum = ExteriorAngleSums[VertexID]; } for (int TriangleID : FillMesh->VtxTrianglesItr(VertexID)) { FIndex3i Triangle = FillMesh->GetTriangle(TriangleID); int IndexInTriangle = IndexUtil::FindTriIndex(VertexID, Triangle); AngleSum += FillMesh->GetTriInternalAngleR(TriangleID, IndexInTriangle); } Curvatures[VertexID] = AngleSum - 2.0 * PI; } double FMinimalHoleFiller::CurvatureMetricCached(int A, int B, int C, int D) { double DefectA = Curvatures[A]; double DefectB = Curvatures[B]; double DefectC = Curvatures[C]; double DefectD = Curvatures[D]; return FMath::Abs(DefectA) + FMath::Abs(DefectB) + FMath::Abs(DefectC) + FMath::Abs(DefectD); } double FMinimalHoleFiller::CurvatureMetricEval(int A, int B, int C, int D) { double DefectA = ComputeGaussCurvature(A); double DefectB = ComputeGaussCurvature(B); double DefectC = ComputeGaussCurvature(C); double DefectD = ComputeGaussCurvature(D); return FMath::Abs(DefectA) + FMath::Abs(DefectB) + FMath::Abs(DefectC) + FMath::Abs(DefectD); } double FMinimalHoleFiller::ComputeGaussCurvature(int VertexID) { double AngleSum = 0; if (ExteriorAngleSums.Contains(VertexID)) { AngleSum = ExteriorAngleSums[VertexID]; } for (int TriangleID : FillMesh->VtxTrianglesItr(VertexID)) { FIndex3i Triangle = FillMesh->GetTriangle(TriangleID); int IndexInTriangle = IndexUtil::FindTriIndex(VertexID, Triangle); AngleSum += FillMesh->GetTriInternalAngleR(TriangleID, IndexInTriangle); } return AngleSum - 2.0 * PI; } double FMinimalHoleFiller::GetTriAspect(const FDynamicMesh3& Mesh, FIndex3i& Triangle) { return VectorUtil::AspectRatio(Mesh.GetVertex(Triangle.A), Mesh.GetVertex(Triangle.B), Mesh.GetVertex(Triangle.C)); } namespace { bool ShouldFlip_CollapseToMinimal(const FDynamicMesh3* FillMesh, int EdgeID) { if (FillMesh->IsEdge(EdgeID) == false || FillMesh->IsBoundaryEdge(EdgeID)) { return false; } bool bDoFlip = false; FIndex2i EdgeVertices = FillMesh->GetEdgeV(EdgeID); FVector3d IncidentTriangleNormal0, IncidentTriangleNormal1, NewTriangleNormal0, NewTriangleNormal1; GetEdgeFlipNormals(*FillMesh, EdgeID, IncidentTriangleNormal0, IncidentTriangleNormal1, NewTriangleNormal0, NewTriangleNormal1); double CurrentTrianglesDot = IncidentTriangleNormal0.Dot(IncidentTriangleNormal1); double FlipTrianglesDot = NewTriangleNormal0.Dot(NewTriangleNormal1); if (CurrentTrianglesDot < 0.1 || FlipTrianglesDot > CurrentTrianglesDot + FMathd::ZeroTolerance) { bDoFlip = true; } if (bDoFlip == false) { FIndex2i OpposingVertices = FillMesh->GetEdgeOpposingV(EdgeID); double CurrentEdgeLength = Distance(FillMesh->GetVertex(EdgeVertices.A), FillMesh->GetVertex(EdgeVertices.B)); double FlipEdgeLength = Distance(FillMesh->GetVertex(OpposingVertices.A), FillMesh->GetVertex(OpposingVertices.B)); if (FlipEdgeLength < CurrentEdgeLength) { if (CheckIfEdgeFlipCreatesFlip(*FillMesh, EdgeID) == false) { bDoFlip = true; } } } return bDoFlip; } bool ShouldFlip_FlipToFlatter(const FDynamicMesh3* FillMesh, int EdgeID, int FlatterPasses) { if (FillMesh->IsEdge(EdgeID) == false || FillMesh->IsBoundaryEdge(EdgeID)) { return false; } bool bDoFlip = false; FIndex2i EdgeVertices = FillMesh->GetEdgeV(EdgeID); FVector3d IncidentTriangleNormal0, IncidentTriangleNormal1, NewTriangleNormal0, NewTriangleNormal1; GetEdgeFlipNormals(*FillMesh, EdgeID, IncidentTriangleNormal0, IncidentTriangleNormal1, NewTriangleNormal0, NewTriangleNormal1); double CurrentTrianglesDot = IncidentTriangleNormal0.Dot(IncidentTriangleNormal1); double FlipTrianglesDot = NewTriangleNormal0.Dot(NewTriangleNormal1); if (FlatterPasses < 20 && CurrentTrianglesDot < 0.1) // this check causes oscillatory behavior { bDoFlip = true; } if (FlipTrianglesDot > CurrentTrianglesDot + FMathd::ZeroTolerance) { bDoFlip = true; } return bDoFlip; } } void FMinimalHoleFiller::CollapseToMinimal() { FRemesher Remesher(FillMesh); FMeshConstraints Constraints; FMeshConstraintsUtil::FullyConstrainEdges(Constraints, *FillMesh, FillMesh->BoundaryEdgeIndicesItr()); if (Constraints.HasConstraints()) { Remesher.SetExternalConstraints(MoveTemp(Constraints)); } // Try to collapse every edge except boundaries Remesher.bEnableCollapses = true; Remesher.bPreventNormalFlips = true; Remesher.SetTargetEdgeLength(BIG_NUMBER); Remesher.bEnableFlips = false; Remesher.bEnableSplits = false; Remesher.bEnableSmoothing = false; Remesher.ProjectionMode = FMeshRefinerBase::ETargetProjectionMode::NoProjection; int ZeroCollapsePasses = 0; int CollapsePasses = 0; while (CollapsePasses++ < 20 && ZeroCollapsePasses < 2) { // collapse pass Remesher.Precompute(); Remesher.BasicRemeshPass(); // flip pass. we flip in these cases: // 1) if angle between current triangles is too small (slightly more than 90 degrees, currently) // 2) if angle between flipped triangles is smaller than between current triangles // 3) if flipped edge length is shorter *and* such a flip won't flip the normal int NumEdges = FillMesh->MaxEdgeID(); for (int EdgeID = 0; EdgeID < NumEdges; ++EdgeID) { bool bDoFlip = ShouldFlip_CollapseToMinimal(FillMesh, EdgeID); if (bDoFlip) { FDynamicMesh3::FEdgeFlipInfo Info; EMeshResult Result = FillMesh->FlipEdge(EdgeID, Info); } } } } void FMinimalHoleFiller::AddAllEdges(int EdgeID, TSet& EdgeSet) { FIndex2i EdgeTriangles = FillMesh->GetEdgeT(EdgeID); FIndex3i TriangleEdges = FillMesh->GetTriEdges(EdgeTriangles.A); EdgeSet.Add(TriangleEdges.A); EdgeSet.Add(TriangleEdges.B); EdgeSet.Add(TriangleEdges.C); TriangleEdges = FillMesh->GetTriEdges(EdgeTriangles.B); EdgeSet.Add(TriangleEdges.A); EdgeSet.Add(TriangleEdges.B); EdgeSet.Add(TriangleEdges.C); } void FMinimalHoleFiller::FlipToFlatter() { int FlatterPasses = 0; TSet RemainingEdges; for (auto EdgeID : FillMesh->EdgeIndicesItr()) { RemainingEdges.Add(EdgeID); } TSet UpdatedEdges; int ZeroFlipsPasses = 0; while (FlatterPasses++ < 40 && ZeroFlipsPasses < 2 && RemainingEdges.Num() > 0) { ++ZeroFlipsPasses; for (int EdgeID : RemainingEdges) { bool bDoFlip = ShouldFlip_FlipToFlatter(FillMesh, EdgeID, FlatterPasses); if (bDoFlip) { FDynamicMesh3::FEdgeFlipInfo Info; EMeshResult Result = FillMesh->FlipEdge(EdgeID, Info); if (Result == EMeshResult::Ok) { ZeroFlipsPasses = 0; AddAllEdges(EdgeID, UpdatedEdges); } } } SwapSets(RemainingEdges, UpdatedEdges); UpdatedEdges.Reset(); } } void FMinimalHoleFiller::FlipToMinimizeCurvature() { int CurvaturePasses = 0; Curvatures.SetNum(FillMesh->MaxVertexID()); for (int VertexID : FillMesh->VertexIndicesItr()) { UpdateCurvature(VertexID); } TSet RemainingEdges; for (auto EdgeID : FillMesh->EdgeIndicesItr()) { RemainingEdges.Add(EdgeID); } TSet UpdatedEdges; while (CurvaturePasses++ < 40 && RemainingEdges.Num() > 0) { for (int EdgeID : RemainingEdges) { if (FillMesh->IsBoundaryEdge(EdgeID)) continue; FIndex2i EdgeVertices = FillMesh->GetEdgeV(EdgeID); FIndex2i OpposingVertices = FillMesh->GetEdgeOpposingV(EdgeID); int FoundOtherEdge = FillMesh->FindEdge(OpposingVertices.A, OpposingVertices.B); if (FoundOtherEdge != FDynamicMesh3::InvalidID) { continue; } double CurrentTotalCurvature = CurvatureMetricCached(EdgeVertices.A, EdgeVertices.B, OpposingVertices.A, OpposingVertices.B); if (CurrentTotalCurvature < FMathd::ZeroTolerance) { continue; } FDynamicMesh3::FEdgeFlipInfo Info; EMeshResult Result = FillMesh->FlipEdge(EdgeID, Info); if (Result != EMeshResult::Ok) { continue; } double FlipTotalCurvature = CurvatureMetricEval(EdgeVertices.A, EdgeVertices.B, OpposingVertices.A, OpposingVertices.B); bool bKeepFlip = FlipTotalCurvature < CurrentTotalCurvature - FMathd::ZeroTolerance; if (bKeepFlip == false) { Result = FillMesh->FlipEdge(EdgeID, Info); } else { UpdateCurvature(EdgeVertices.A); UpdateCurvature(EdgeVertices.B); UpdateCurvature(OpposingVertices.A); UpdateCurvature(OpposingVertices.B); AddAllEdges(EdgeID, UpdatedEdges); } } SwapSets(RemainingEdges, UpdatedEdges); } } void FMinimalHoleFiller::FlipToImproveAspectRatios() { TSet RemainingEdges; for (auto EdgeID : FillMesh->EdgeIndicesItr()) { RemainingEdges.Add(EdgeID); } TSet UpdatedEdges; int AreaPasses = 0; while (RemainingEdges.Num() > 0 && AreaPasses++ < 20) { for (int EdgeID : RemainingEdges) { if (!FillMesh->IsEdge(EdgeID) || FillMesh->IsBoundaryEdge(EdgeID)) { continue; } FIndex2i EdgeVertices = FillMesh->GetEdgeV(EdgeID); FIndex2i OpposingVertices = FillMesh->GetEdgeOpposingV(EdgeID); int FoundOtherEdge = FillMesh->FindEdge(OpposingVertices.A, OpposingVertices.B); if (FoundOtherEdge != FDynamicMesh3::InvalidID) { continue; } double Aspect = AspectMetric(EdgeID); if (Aspect > 1.0) { continue; } double CurrentTotalCurvature = CurvatureMetricCached(EdgeVertices.A, EdgeVertices.B, OpposingVertices.A, OpposingVertices.B); FDynamicMesh3::FEdgeFlipInfo Info; EMeshResult Result = FillMesh->FlipEdge(EdgeID, Info); if (Result != EMeshResult::Ok) { continue; } double NewTotalCurvature = CurvatureMetricEval(EdgeVertices.A, EdgeVertices.B, OpposingVertices.A, OpposingVertices.B); bool bKeepFlip = FMath::Abs(CurrentTotalCurvature - NewTotalCurvature) < DevelopabilityTolerance; if (bKeepFlip == false) { Result = FillMesh->FlipEdge(EdgeID, Info); } else { UpdateCurvature(EdgeVertices.A); UpdateCurvature(EdgeVertices.B); UpdateCurvature(OpposingVertices.A); UpdateCurvature(OpposingVertices.B); AddAllEdges(EdgeID, UpdatedEdges); } } SwapSets(RemainingEdges, UpdatedEdges); } } bool FMinimalHoleFiller::Fill(int32 GroupID) { if (GroupID < 0 && Mesh->HasTriangleGroups()) { GroupID = Mesh->AllocateTriangleGroup(); } // do a simple fill FSimpleHoleFiller Simplefill(Mesh, FillLoop); bool bOK = Simplefill.Fill(GroupID); if (bOK == false) { return false; } if (FillLoop.Vertices.Num() <= 3) { NewTriangles = Simplefill.NewTriangles; return true; } // if the loop is tiny just use the simple fill double LoopTotalLength = TMeshQueries::TotalEdgeLength(*Mesh, FillLoop.Edges); if (LoopTotalLength < 100.0f*FMathf::ZeroTolerance) { NewTriangles = Simplefill.NewTriangles; return true; } // extract the simple fill mesh as a submesh, via RegionOperator, so we can backsub later TSet IntialFillTris(Simplefill.NewTriangles); RegionOp = MakeUnique(Mesh, Simplefill.NewTriangles, [](FDynamicSubmesh3& SubMesh) { SubMesh.bComputeTriMaps = true; }); FillMesh = &(RegionOp->Region.GetSubmesh()); // for each boundary vertex, compute the exterior angle sum // we will use this to compute gaussian curvature later BoundaryVertices = TSet(GetBoundaryEdgeVertices(*FillMesh)); if (bIgnoreBoundaryTriangles == false) { RegionOp->BackPropropagate(true); for (int SubmeshVertexID : BoundaryVertices) { double AngleSum = 0; int BaseMeshVertexID = RegionOp->ReinsertSubToBaseMapV[SubmeshVertexID]; for (int TriangleID : RegionOp->BaseMesh->VtxTrianglesItr(BaseMeshVertexID)) { if (IntialFillTris.Contains(TriangleID) == false) { FIndex3i Triangle = RegionOp->BaseMesh->GetTriangle(TriangleID); int IndexInTriangle = IndexUtil::FindTriIndex(BaseMeshVertexID, Triangle); AngleSum += RegionOp->BaseMesh->GetTriInternalAngleR(TriangleID, IndexInTriangle); } } ExteriorAngleSums.Emplace(SubmeshVertexID, AngleSum); } } // try to guess a reasonable edge length that will give us enough geometry to work with in simplify pass double LoopMinEdgeLength, LoopMaxEdgeLength, LoopAverageEdgeLength; TMeshQueries::EdgeLengthStatsFromEdges(*Mesh, FillLoop.Edges, LoopMinEdgeLength, LoopMaxEdgeLength, LoopAverageEdgeLength); double FillMinEdgeLength, FillMaxEdgeLength, FillAverageEdgeLength; TMeshQueries::EdgeLengthStats(*FillMesh, FillMinEdgeLength, FillMaxEdgeLength, FillAverageEdgeLength); double RemeshTargetEdgeLength = LoopAverageEdgeLength; if (FillMaxEdgeLength / RemeshTargetEdgeLength > 10) RemeshTargetEdgeLength = FillMaxEdgeLength / 10; // remesh up to target edge length, ideally gives us some triangles to work with FQueueRemesher Remesher(FillMesh); Remesher.SmoothSpeedT = 1.0; // Constrain all boundary edges FMeshConstraints Constraints; FMeshConstraintsUtil::FullyConstrainEdges(Constraints, *FillMesh, FillMesh->BoundaryEdgeIndicesItr()); if (Constraints.HasConstraints()) { Remesher.SetExternalConstraints(MoveTemp(Constraints)); } // TODO: we should estimate/valide and/or try to cap the number of triangles being created here. // If RemeshTargetEdgeLength value is extremely tiny this could be trying to create infinity triangles. Remesher.SetTargetEdgeLength(RemeshTargetEdgeLength); Remesher.FastestRemesh(); /* * first round: collapse to minimal mesh, while flipping to try to * get to ballpark minimal mesh. We stop these passes as soon as * we have done two rounds where we couldn't do another collapse * * This is the most unstable part of the algorithm because there * are strong ordering effects. maybe we could sort the edges somehow?? */ CollapseToMinimal(); // Sometimes, for some reason, we have a remaining interior vertex (have only ever seen one?) // Try to force removal of such vertices, even if it makes ugly mesh RemoveRemainingInteriorVerts(); // enable/disable passes. const bool DO_FLATTER_PASS = true; const bool DO_CURVATURE_PASS = bOptimizeDevelopability && true; const bool DO_AREA_PASS = bOptimizeDevelopability && bOptimizeTriangles && true; /* * In this pass we repeat the flipping iterations from the previous pass. * * Note that because of the always-flip-if-dot-is-small case (commented), * this pass will frequently not converge, as some number of edges will * be able to flip back and forth (because neither has large enough dot). * This is not ideal, but also, if we remove this behavior, then we * generally get worse fills. This case basically introduces a sort of * randomization factor that lets us escape local minima... * */ if (DO_FLATTER_PASS) { FlipToFlatter(); } /* * In this pass we try to minimize gaussian curvature at all the vertices. * This will recover sharp edges, etc, and do lots of good stuff. * However, this pass will not make much progress if we are not already * relatively close to a minimal mesh, so it really relies on the previous * passes getting us in the ballpark. */ if (DO_CURVATURE_PASS) { FlipToMinimizeCurvature(); } /* * In this final pass, we try to improve triangle quality. We flip if * the flipped triangles have better total aspect ratio, and the * curvature doesn't change **too** much. The DevelopabilityTolerance * parameter determines what is "too much" curvature change. */ if (DO_AREA_PASS) { FlipToImproveAspectRatios(); } RegionOp->BackPropropagate(true); NewTriangles = RegionOp->CurrentBaseTriangles(); return true; }