// Dissolves adjacent edges when face normals are equal. Adjecent vertices are dissolved /*public static int RemoveAdjecentEdges(HMesh hmesh, double thresholdDistance = 0.001, double thresholdAngle = 0.1) * { * bool changed = false; * int count = 0; * do * { * changed = false; * var heCopy = new List<Halfedge>(hmesh.GetHalfedgesRaw()); * for (int i = 0; i < heCopy.Count; i++) * { * var he1 = heCopy[i]; * if (he1.IsDestroyed()) * { * continue; * } * for (int j = i + 1; j < heCopy.Count; j++) * { * var he2 = heCopy[j]; * if (he2.IsDestroyed()) * { * continue; * } * if (he1.opp == he2) * { * continue; * } * var distVert1 = he1.prev.vert.positionD - he2.vert.positionD; * var distVert2 = he1.vert.positionD - he2.prev.vert.positionD; * * bool isAdjacentHalfedges = distVert1.magnitude < thresholdDistance && * distVert2.magnitude < thresholdDistance; * if (isAdjacentHalfedges){ * var he1Normal = he1.face.GetNormal(); * var he2Normal = he2.face.GetNormal(); * bool isSameNormal = Vector3D.Angle(he1Normal, he2Normal) < thresholdAngle; * if (isSameNormal) * { * changed = true; * * //Debug.Log("Joining he "+he1.id+" and "+he2.id); * //Debug.Log("Joining he "+he1.face.ToString() +" and "+he2.face.ToString()); * * var he2Vert = he2.vert; * var he2PrevVert = he2.prev.vert; * //Debug.Log("Replace "+he2Vert.id+" with "+he1.prev.vert.id); * //Debug.Log("Replace "+he2PrevVert.id+" with "+he1.vert.id); * he2Vert.ReplaceVertex(he1.prev.vert); * he2PrevVert.ReplaceVertex(he1.vert); * * //Debug.Log("Destroy vert "+he2Vert.id); * //Debug.Log("Destroy vert "+he2PrevVert.id); * hmesh.Destroy(he2Vert); * hmesh.Destroy(he2PrevVert); * * he1.Glue(he2); * //hmesh.IsValid(HMeshValidationRules.All); * //he1.Dissolve(); * hmesh.IsValid(HMeshValidationRules.All); * count++; * } * } * } * } * } while (changed); * return count; * }*/ /// <summary> /// Enforces triangular mesh /// </summary> /// <param name="hmesh"></param> /// <returns></returns> public static int FixDegenerateFaces(HMesh hmesh) { int count = 0; var faces = new List <Face>(hmesh.GetFaces()); for (int i = 0; i < faces.Count; i++)// (var face in hmesh.GetFaces())} { var face = faces[i]; //var faceWas = face.ToString(); if (face.IsDestroyed()) { continue; } // triangulate if (face.Circulate().Count > 3) { count++; #if HMDebug var str = face.ToString(); var debugFaceExp = face.ExportLocalNeighbourhoodToObj(); System.IO.StringWriter sw = new System.IO.StringWriter(); var res = face.Triangulate(false, sw); #else var res = face.Triangulate(); #endif if (res.Count == 0) { #if HMDebug Debug.LogWarning("Cannot triangulate " + str + " face is now " + face.ToString() + " " + sw.ToString()); Debug.LogWarning(debugFaceExp); #endif // face destroyed continue; } // add new faces for (int j = 0; j < res.Count; j++) { if (res[j] != face) { faces.Add(res[j]); } } } // fix degenerate due to zero length edge foreach (var he in face.Circulate()) { if (he.IsDestroyed()) { continue; } if (he.GetDirection().sqrMagnitude <= hmesh.zeroMagnitudeTresholdSqr) { Vector3D[] positions = { he.GetCenter(), he.vert.positionD, he.prev.vert.positionD }; bool collapsed = false; foreach (var p in positions) { var collapsePrecondition = he.CollapsePrecondition(p, Halfedge.CollapsePreconditionReason.EdgeIsBoundary | Halfedge.CollapsePreconditionReason.VertexIsBoundary | Halfedge.CollapsePreconditionReason.NormalFlipped); if (collapsePrecondition == Halfedge.CollapsePreconditionReason.Ok) { count++; he.Collapse(); collapsed = true; break; } else { Debug.Log("Cannot collapse - precondition failed " + collapsePrecondition); } } if (!collapsed) { he.Collapse(); } } } } RemoveFacesWithTwoEdges(hmesh); // fix degenerate due to zero area faces = new List <Face>(hmesh.GetFaces()); for (int i = faces.Count - 1; i >= 0; i--)// (var face in hmesh.GetFaces())} { var face = faces[i]; if (face.IsDestroyed()) { continue; } if (face.IsDegenerate()) { // find longest edge Halfedge longestEdge = null; double maxLength = -1; var edges = face.Circulate(); Debug.Assert(edges.Count == 3, "Edges was " + edges.Count); foreach (var he in edges) { double length = he.GetDirection().sqrMagnitude; if (length > maxLength) { maxLength = length; longestEdge = he; } } if (longestEdge == null) { Debug.LogError("Face " + face.id + " only has zero length edges"); continue; } var oppVert = longestEdge.next.vert; Vertex oppHeOppVert = null; if (longestEdge.opp != null) { oppHeOppVert = longestEdge.opp.next.vert; } var newVert = longestEdge.Split(); newVert.positionD = oppVert.positionD; var longestEdgeFace = longestEdge.face; var res = longestEdgeFace.Cut(oppVert, newVert); if (res == longestEdgeFace) { Debug.Log("vertices"); } if (oppHeOppVert != null) { var longestEdgeOppFace = longestEdge.opp.face; var newFace = longestEdgeOppFace.Cut(newVert, oppHeOppVert); faces.Add(newFace); faces.Add(longestEdgeOppFace); // reevaluate face } var sharedEdge = oppVert.GetSharedEdge(newVert); if (sharedEdge == null) { Debug.Log("Cannot find shared edge between " + oppVert + " " + newVert); continue; } var sharedEdgeFace1 = sharedEdge.face; var sharedEdgeFace2 = sharedEdge.opp.face; // ensured to exist (just created) var precond = sharedEdge.CollapsePrecondition(true, Halfedge.CollapsePreconditionReason.NormalFlipped); if (precond == Halfedge.CollapsePreconditionReason.Ok) { sharedEdge.Collapse(true); Debug.Assert(sharedEdgeFace1.IsDestroyed()); Debug.Assert(sharedEdgeFace2.IsDestroyed()); count++; } else { Debug.Log(precond); } } } return(count); }