} // Cut() protected void collapse_degenerate_edges(HashSet <int> OnCutEdges, HashSet <int> ZeroEdges) { var sets = new HashSet <int>[2] { OnCutEdges, ZeroEdges }; double tol2 = DegenerateEdgeTol * DegenerateEdgeTol; Vector3d a = Vector3d.Zero, b = Vector3d.Zero; int collapsed = 0; do { collapsed = 0; foreach (var edge_set in sets) { foreach (int eid in edge_set) { if (Mesh.IsEdge(eid) == false) { continue; } Mesh.GetEdgeV(eid, ref a, ref b); if (a.DistanceSquared(b) > tol2) { continue; } Index2i ev = Mesh.GetEdgeV(eid); DMesh3.EdgeCollapseInfo collapseInfo; MeshResult result = Mesh.CollapseEdge(ev.a, ev.b, out collapseInfo); if (result == MeshResult.Ok) { collapsed++; } } } } while (collapsed != 0); }
public Vector3d Project(Vector3d vPoint, int identifier = -1) { Vector3d vNearest = Vector3d.Zero; double fNearestSqr = double.MaxValue; int N = Curve.VertexCount; int NStop = (Curve.Closed) ? N : N - 1; for (int i = 0; i < NStop; ++i) { Segment3d seg = new Segment3d(Curve[i], Curve[(i + 1) % N]); Vector3d pt = seg.NearestPoint(vPoint); double dsqr = pt.DistanceSquared(vPoint); if (dsqr < fNearestSqr) { fNearestSqr = dsqr; vNearest = pt; } } return((fNearestSqr < double.MaxValue) ? vNearest : vPoint); }
/// <summary> /// find closest vertex, within searchRadius /// </summary> protected int find_nearest_vertex(Vector3d pt, double searchRadius, int ignore_vid = -1) { KeyValuePair <int, double> found = (ignore_vid == -1) ? PointHash.FindNearestInRadius(pt, searchRadius, (b) => { return(pt.DistanceSquared(Target.GetVertex(b))); }) : PointHash.FindNearestInRadius(pt, searchRadius, (b) => { return(pt.DistanceSquared(Target.GetVertex(b))); }, (vid) => { return(vid == ignore_vid); }); if (found.Key == PointHash.InvalidValue) { return(-1); } return(found.Key); }
protected void find_nearest_point(int iBox, Vector3d p, ref double fNearestSqr, ref int tID) { int idx = box_to_index[iBox]; if (idx < points_end) // point-list case, array is [N t1 t2 ... tN] { int num_points = index_list[idx]; for (int i = 1; i <= num_points; ++i) { int ti = index_list[idx + i]; if (PointFilterF != null && PointFilterF(ti) == false) { continue; } Vector3d pt = points.GetVertex(ti); double fDistSqr = pt.DistanceSquared(p); if (fDistSqr < fNearestSqr) { fNearestSqr = fDistSqr; tID = ti; } } } else // internal node, either 1 or 2 child boxes { int iChild1 = index_list[idx]; if (iChild1 < 0) // 1 child, descend if nearer than cur min-dist { iChild1 = (-iChild1) - 1; double fChild1DistSqr = box_distance_sqr(iChild1, ref p); if (fChild1DistSqr <= fNearestSqr) { find_nearest_point(iChild1, p, ref fNearestSqr, ref tID); } } else // 2 children, descend closest first { iChild1 = iChild1 - 1; int iChild2 = index_list[idx + 1] - 1; double fChild1DistSqr = box_distance_sqr(iChild1, ref p); double fChild2DistSqr = box_distance_sqr(iChild2, ref p); if (fChild1DistSqr < fChild2DistSqr) { if (fChild1DistSqr < fNearestSqr) { find_nearest_point(iChild1, p, ref fNearestSqr, ref tID); if (fChild2DistSqr < fNearestSqr) { find_nearest_point(iChild2, p, ref fNearestSqr, ref tID); } } } else { if (fChild2DistSqr < fNearestSqr) { find_nearest_point(iChild2, p, ref fNearestSqr, ref tID); if (fChild1DistSqr < fNearestSqr) { find_nearest_point(iChild1, p, ref fNearestSqr, ref tID); } } } } } }
protected virtual ProcessResult ProcessEdge(int edgeID) { RuntimeDebugCheck(edgeID); EdgeConstraint constraint = (constraints == null) ? EdgeConstraint.Unconstrained : constraints.GetEdgeConstraint(edgeID); if (constraint.NoModifications) { return(ProcessResult.Ignored_EdgeIsFullyConstrained); } // look up verts and tris for this edge int a = 0, b = 0, t0 = 0, t1 = 0; if (mesh.GetEdge(edgeID, ref a, ref b, ref t0, ref t1) == false) { return(ProcessResult.Failed_NotAnEdge); } bool bIsBoundaryEdge = (t1 == DMesh3.InvalidID); // look up 'other' verts c (from t0) and d (from t1, if it exists) Index2i ov = mesh.GetEdgeOpposingV(edgeID); int c = ov.a, d = ov.b; Vector3d vA = mesh.GetVertex(a); Vector3d vB = mesh.GetVertex(b); double edge_len_sqr = vA.DistanceSquared(vB); begin_collapse(); // check if we should collapse, and also find which vertex we should collapse to, // in cases where we have constraints/etc int collapse_to = -1; bool bCanCollapse = EnableCollapses && constraint.CanCollapse && edge_len_sqr < MinEdgeLength * MinEdgeLength && can_collapse_constraints(edgeID, a, b, c, d, t0, t1, out collapse_to); // optimization: if edge cd exists, we cannot collapse or flip. look that up here? // funcs will do it internally... // (or maybe we can collapse if cd exists? edge-collapse doesn't check for it explicitly...) // if edge length is too short, we want to collapse it bool bTriedCollapse = false; if (bCanCollapse) { int iKeep = b, iCollapse = a; Vector3d vNewPos = (vA + vB) * 0.5; // if either vtx is fixed, collapse to that position if (collapse_to == b) { vNewPos = vB; } else if (collapse_to == a) { iKeep = a; iCollapse = b; vNewPos = vA; } else { vNewPos = get_projected_collapse_position(iKeep, vNewPos); } // if new position would flip normal of one of the existing triangles // either one-ring, don't allow it if (PreventNormalFlips) { if (collapse_creates_flip_or_invalid(a, b, ref vNewPos, t0, t1) || collapse_creates_flip_or_invalid(b, a, ref vNewPos, t0, t1)) { goto abort_collapse; } } // TODO be smart about picking b (keep vtx). // - swap if one is bdry vtx, for example? // lots of cases where we cannot collapse, but we should just let // mesh sort that out, right? COUNT_COLLAPSES++; DMesh3.EdgeCollapseInfo collapseInfo; MeshResult result = mesh.CollapseEdge(iKeep, iCollapse, out collapseInfo); if (result == MeshResult.Ok) { mesh.SetVertex(iKeep, vNewPos); if (constraints != null) { constraints.ClearEdgeConstraint(edgeID); constraints.ClearEdgeConstraint(collapseInfo.eRemoved0); if (collapseInfo.eRemoved1 != DMesh3.InvalidID) { constraints.ClearEdgeConstraint(collapseInfo.eRemoved1); } constraints.ClearVertexConstraint(iCollapse); } OnEdgeCollapse(edgeID, iKeep, iCollapse, collapseInfo); DoDebugChecks(); return(ProcessResult.Ok_Collapsed); } else { bTriedCollapse = true; } } abort_collapse: end_collapse(); begin_flip(); // if this is not a boundary edge, maybe we want to flip bool bTriedFlip = false; if (EnableFlips && constraint.CanFlip && bIsBoundaryEdge == false) { // can we do this more efficiently somehow? bool a_is_boundary_vtx = (MeshIsClosed) ? false : (bIsBoundaryEdge || mesh.IsBoundaryVertex(a)); bool b_is_boundary_vtx = (MeshIsClosed) ? false : (bIsBoundaryEdge || mesh.IsBoundaryVertex(b)); bool c_is_boundary_vtx = (MeshIsClosed) ? false : mesh.IsBoundaryVertex(c); bool d_is_boundary_vtx = (MeshIsClosed) ? false : mesh.IsBoundaryVertex(d); int valence_a = mesh.GetVtxEdgeCount(a), valence_b = mesh.GetVtxEdgeCount(b); int valence_c = mesh.GetVtxEdgeCount(c), valence_d = mesh.GetVtxEdgeCount(d); int valence_a_target = (a_is_boundary_vtx) ? valence_a : 6; int valence_b_target = (b_is_boundary_vtx) ? valence_b : 6; int valence_c_target = (c_is_boundary_vtx) ? valence_c : 6; int valence_d_target = (d_is_boundary_vtx) ? valence_d : 6; // if total valence error improves by flip, we want to do it int curr_err = Math.Abs(valence_a - valence_a_target) + Math.Abs(valence_b - valence_b_target) + Math.Abs(valence_c - valence_c_target) + Math.Abs(valence_d - valence_d_target); int flip_err = Math.Abs((valence_a - 1) - valence_a_target) + Math.Abs((valence_b - 1) - valence_b_target) + Math.Abs((valence_c + 1) - valence_c_target) + Math.Abs((valence_d + 1) - valence_d_target); bool bTryFlip = flip_err < curr_err; if (bTryFlip && PreventNormalFlips && flip_inverts_normals(a, b, c, d, t0)) { bTryFlip = false; } if (bTryFlip) { DMesh3.EdgeFlipInfo flipInfo; COUNT_FLIPS++; MeshResult result = mesh.FlipEdge(edgeID, out flipInfo); if (result == MeshResult.Ok) { DoDebugChecks(); return(ProcessResult.Ok_Flipped); } else { bTriedFlip = true; } } } end_flip(); begin_split(); // if edge length is too long, we want to split it bool bTriedSplit = false; if (EnableSplits && constraint.CanSplit && edge_len_sqr > MaxEdgeLength * MaxEdgeLength) { DMesh3.EdgeSplitInfo splitInfo; COUNT_SPLITS++; MeshResult result = mesh.SplitEdge(edgeID, out splitInfo); if (result == MeshResult.Ok) { update_after_split(edgeID, a, b, ref splitInfo); OnEdgeSplit(edgeID, a, b, splitInfo); DoDebugChecks(); return(ProcessResult.Ok_Split); } else { bTriedSplit = true; } } end_split(); if (bTriedFlip || bTriedSplit || bTriedCollapse) { return(ProcessResult.Failed_OpNotSuccessful); } else { return(ProcessResult.Ignored_EdgeIsFine); } }
public virtual void FastCollapsePass(double fMinEdgeLength, int nRounds = 1, bool MeshIsClosedHint = false) { if (mesh.TriangleCount == 0) // badness if we don't catch this... { return; } MinEdgeLength = fMinEdgeLength; double min_sqr = MinEdgeLength * MinEdgeLength; // we don't collapse on the boundary HaveBoundary = false; begin_pass(); begin_setup(); Precompute(MeshIsClosedHint); if (Cancelled()) { return; } end_setup(); begin_ops(); begin_collapse(); int N = mesh.MaxEdgeID; int num_last_pass = 0; for (int ri = 0; ri < nRounds; ++ri) { num_last_pass = 0; Vector3d va = Vector3d.Zero, vb = Vector3d.Zero; for (int eid = 0; eid < N; ++eid) { if (!mesh.IsEdge(eid)) { continue; } if (mesh.IsBoundaryEdge(eid)) { continue; } if (Cancelled()) { return; } mesh.GetEdgeV(eid, ref va, ref vb); if (va.DistanceSquared(ref vb) > min_sqr) { continue; } COUNT_ITERATIONS++; Vector3d midpoint = (va + vb) * 0.5; int vKept; ProcessResult result = CollapseEdge(eid, midpoint, out vKept); if (result == ProcessResult.Ok_Collapsed) { ++num_last_pass; } } if (num_last_pass == 0) // converged { break; } } end_collapse(); end_ops(); if (Cancelled()) { return; } Reproject(); end_pass(); }
public DeformInfo UpdateDeformation(Vector3d vNextPos) { DeformInfo di = new DeformInfo() { maxEdgeLenSqr = 0, minEdgeLenSqr = double.MaxValue }; Vector3d vDelta = (vNextPos - prev); if (vDelta.Length < 0.0001f) { return(di); } double r2 = Radius * Radius; int N = Curve.VertexCount; int pending_smooth_i = -1; for (int i = 0; i < N; ++i) { bool bModified = false; Vector3d v = Curve[i]; double d2 = (v - prev).LengthSquared; if (d2 < r2) { double t = WeightFunc(Math.Sqrt(d2), r2); Vector3d vNew = v + t * vDelta; if (i > 0) { double dp = vNew.DistanceSquared(Curve[i - 1]); di.maxEdgeLenSqr = Math.Max(dp, di.maxEdgeLenSqr); di.minEdgeLenSqr = Math.Min(dp, di.minEdgeLenSqr); } if (i < N - 1) { double dn = vNew.DistanceSquared(Curve[i + 1]); di.maxEdgeLenSqr = Math.Max(dn, di.maxEdgeLenSqr); di.minEdgeLenSqr = Math.Min(dn, di.minEdgeLenSqr); } Curve[i] = vNew; bModified = true; } if (pending_smooth_i > 0) { Vector3d c = Curve.Centroid(pending_smooth_i); Curve[pending_smooth_i] = Vector3d.Lerp(Curve[pending_smooth_i], c, SmoothT); } if (bModified && SmoothT > 0 && i > 0 && i < N - 1) { pending_smooth_i = i; } } prev = vNextPos; return(di); }
/// <summary> /// Construct vertex correspondences between fill mesh boundary loop /// and input mesh boundary loop. In ideal case there is an easy 1-1 /// correspondence. If that is not true, then do a brute-force search /// to find the best correspondences we can. /// /// Currently only returns unique correspondences. If any vertex /// matches with multiple input vertices it is not merged. /// [TODO] we could do better in many cases... /// /// Return value is list of indices into fillLoopV that were not merged /// </summary> List <int> build_merge_map(DMesh3 fillMesh, int[] fillLoopV, DMesh3 targetMesh, int[] targetLoopV, double tol, IndexMap mergeMapV) { if (fillLoopV.Length == targetLoopV.Length) { if (build_merge_map_simple(fillMesh, fillLoopV, targetMesh, targetLoopV, tol, mergeMapV)) { return(null); } } int NF = fillLoopV.Length, NT = targetLoopV.Length; bool[] doneF = new bool[NF], doneT = new bool[NT]; int[] countF = new int[NF], countT = new int[NT]; var errorV = new List <int>(); var matchF = new SmallListSet(); matchF.Resize(NF); // find correspondences double tol_sqr = tol * tol; for (int i = 0; i < NF; ++i) { if (fillMesh.IsVertex(fillLoopV[i]) == false) { doneF[i] = true; errorV.Add(i); continue; } matchF.AllocateAt(i); Vector3d v = fillMesh.GetVertex(fillLoopV[i]); for (int j = 0; j < NT; ++j) { Vector3d v2 = targetMesh.GetVertex(targetLoopV[j]); if (v.DistanceSquared(ref v2) < tol_sqr) { matchF.Insert(i, j); } } } for (int i = 0; i < NF; ++i) { if (doneF[i]) { continue; } if (matchF.Count(i) == 1) { int j = matchF.First(i); mergeMapV[fillLoopV[i]] = targetLoopV[j]; doneF[i] = true; } } for (int i = 0; i < NF; ++i) { if (doneF[i] == false) { errorV.Add(i); } } return(errorV); }
public override DeformInfo Apply(Frame3f vNextPos) { Interval1d edgeRangeSqr = Interval1d.Empty; int N = Curve.VertexCount; if (N > NewV.size) { NewV.resize(N); } if (N > ModifiedV.Length) { ModifiedV = new BitArray(2 * N); } // clear modified ModifiedV.SetAll(false); bool bSmooth = (SmoothAlpha > 0 && SmoothIterations > 0); double r2 = Radius * Radius; // deform pass if (DeformF != null) { for (int i = 0; i < N; ++i) { Vector3d v = Curve[i]; double d2 = (v - vPreviousPos.Origin).LengthSquared; if (d2 < r2) { double t = WeightFunc(Math.Sqrt(d2), Radius); Vector3d vNew = DeformF(i, t); if (bSmooth == false) { if (i > 0) { edgeRangeSqr.Contain(vNew.DistanceSquared(Curve[i - 1])); } if (i < N - 1) { edgeRangeSqr.Contain(vNew.DistanceSquared(Curve[i + 1])); } } NewV[i] = vNew; ModifiedV[i] = true; } } } else { // anything? } // smooth pass if (bSmooth) { for (int j = 0; j < SmoothIterations; ++j) { int iStart = (Curve.Closed) ? 0 : 1; int iEnd = (Curve.Closed) ? N : N - 1; for (int i = iStart; i < iEnd; ++i) { Vector3d v = (ModifiedV[i]) ? NewV[i] : Curve[i]; double d2 = (v - vPreviousPos.Origin).LengthSquared; if (ModifiedV[i] || d2 < r2) // always smooth any modified verts { double a = SmoothAlpha * WeightFunc(Math.Sqrt(d2), Radius); int iPrev = (i == 0) ? N - 1 : i - 1; int iNext = (i + 1) % N; Vector3d vPrev = (ModifiedV[iPrev]) ? NewV[iPrev] : Curve[iPrev]; Vector3d vNext = (ModifiedV[iNext]) ? NewV[iNext] : Curve[iNext]; Vector3d c = (vPrev + vNext) * 0.5f; NewV[i] = (1 - a) * v + (a) * c; ModifiedV[i] = true; if (i > 0) { edgeRangeSqr.Contain(NewV[i].DistanceSquared(Curve[i - 1])); } if (i < N - 1) { edgeRangeSqr.Contain(NewV[i].DistanceSquared(Curve[i + 1])); } } } } } // bake for (int i = 0; i < N; ++i) { if (ModifiedV[i]) { Curve[i] = NewV[i]; } } return(new DeformInfo() { minEdgeLenSqr = edgeRangeSqr.a, maxEdgeLenSqr = edgeRangeSqr.b }); }
/// <summary> /// precompute constant coefficients of triangle winding number approximation /// p: 'center' of expansion for triangles (area-weighted centroid avg) /// r: max distance from p to triangles /// order1: first-order vector coeff /// order2: second-order matrix coeff /// triCache: optional precomputed triangle centroid/normal/area /// </summary> public static void ComputeCoeffs(DMesh3 mesh, IEnumerable <int> triangles, ref Vector3d p, ref double r, ref Vector3d order1, ref Matrix3d order2, MeshTriInfoCache triCache = null) { p = Vector3d.Zero; order1 = Vector3d.Zero; order2 = Matrix3d.Zero; r = 0; // compute area-weighted centroid of triangles, we use this as the expansion point Vector3d P0 = Vector3d.Zero, P1 = Vector3d.Zero, P2 = Vector3d.Zero; double sum_area = 0; foreach (int tid in triangles) { if (triCache != null) { double area = triCache.Areas[tid]; sum_area += area; p += area * triCache.Centroids[tid]; } else { mesh.GetTriVertices(tid, ref P0, ref P1, ref P2); double area = MathUtil.Area(ref P0, ref P1, ref P2); sum_area += area; p += area * ((P0 + P1 + P2) / 3.0); } } p /= sum_area; // compute first and second-order coefficients of FWN taylor expansion, as well as // 'radius' value r, which is max dist from any tri vertex to p Vector3d n = Vector3d.Zero, c = Vector3d.Zero; double a = 0; foreach (int tid in triangles) { mesh.GetTriVertices(tid, ref P0, ref P1, ref P2); if (triCache == null) { c = (1.0 / 3.0) * (P0 + P1 + P2); n = MathUtil.FastNormalArea(ref P0, ref P1, ref P2, out a); } else { triCache.GetTriInfo(tid, ref n, ref a, ref c); } order1 += a * n; Vector3d dcp = c - p; order2 += a * new Matrix3d(ref dcp, ref n); // this is just for return value... double maxdist = MathUtil.Max(P0.DistanceSquared(ref p), P1.DistanceSquared(ref p), P2.DistanceSquared(ref p)); r = Math.Max(r, Math.Sqrt(maxdist)); } }
public MeshResult MergeEdges(int eKeep, int eDiscard, out MergeEdgesInfo merge_info) { merge_info = new MergeEdgesInfo(); if (IsEdge(eKeep) == false || IsEdge(eDiscard) == false) { return(MeshResult.Failed_NotAnEdge); } Index4i edgeinfo_keep = GetEdge(eKeep); Index4i edgeinfo_discard = GetEdge(eDiscard); if (edgeinfo_keep.d != InvalidID || edgeinfo_discard.d != InvalidID) { return(MeshResult.Failed_NotABoundaryEdge); } int a = edgeinfo_keep.a, b = edgeinfo_keep.b; int tab = edgeinfo_keep.c; int eab = eKeep; int c = edgeinfo_discard.a, d = edgeinfo_discard.b; int tcd = edgeinfo_discard.c; int ecd = eDiscard; // Need to correctly orient a,b and c,d and then check that // we will not join triangles with incompatible winding order // I can't see how to do this purely topologically. // So relying on closest-pairs testing. IndexUtil.orient_tri_edge(ref a, ref b, GetTriangle(tab)); //int tcd_otherv = IndexUtil.orient_tri_edge_and_find_other_vtx(ref c, ref d, GetTriangle(tcd)); IndexUtil.orient_tri_edge(ref c, ref d, GetTriangle(tcd)); int x = c; c = d; d = x; // joinable bdry edges have opposing orientations, so flip to get ac and b/d correspondences Vector3d Va = GetVertex(a), Vb = GetVertex(b), Vc = GetVertex(c), Vd = GetVertex(d); if ((Va.DistanceSquared(Vc) + Vb.DistanceSquared(Vd)) > (Va.DistanceSquared(Vd) + Vb.DistanceSquared(Vc))) { return(MeshResult.Failed_SameOrientation); } // alternative that detects normal flip of triangle tcd. This is a more // robust geometric test, but fails if tri is degenerate...also more expensive //Vector3d otherv = GetVertex(tcd_otherv); //Vector3d Ncd = MathUtil.FastNormalDirection(GetVertex(c), GetVertex(d), otherv); //Vector3d Nab = MathUtil.FastNormalDirection(GetVertex(a), GetVertex(b), otherv); //if (Ncd.Dot(Nab) < 0) //return MeshResult.Failed_SameOrientation; merge_info.eKept = eab; merge_info.eRemoved = ecd; // [TODO] this acts on each interior tri twice. could avoid using vtx-tri iterator? List <int> edges_a = vertex_edges[a]; if (a != c) { // replace c w/ a in edges and tris connected to c, and move edges to a List <int> edges_c = vertex_edges[c]; for (int i = 0; i < edges_c.Count; ++i) { int eid = edges_c[i]; if (eid == eDiscard) { continue; } replace_edge_vertex(eid, c, a); short rc = 0; if (replace_tri_vertex(edges[4 * eid + 2], c, a) >= 0) { rc++; } if (edges[4 * eid + 3] != InvalidID) { if (replace_tri_vertex(edges[4 * eid + 3], c, a) >= 0) { rc++; } } edges_a.Add(eid); if (rc > 0) { vertices_refcount.increment(a, rc); vertices_refcount.decrement(c, rc); } } vertex_edges[c] = null; vertices_refcount.decrement(c); merge_info.vRemoved[0] = c; } else { edges_a.Remove(ecd); merge_info.vRemoved[0] = InvalidID; } merge_info.vKept[0] = a; List <int> edges_b = vertex_edges[b]; if (d != b) { // replace d w/ b in edges and tris connected to d, and move edges to b List <int> edges_d = vertex_edges[d]; for (int i = 0; i < edges_d.Count; ++i) { int eid = edges_d[i]; if (eid == eDiscard) { continue; } replace_edge_vertex(eid, d, b); short rc = 0; if (replace_tri_vertex(edges[4 * eid + 2], d, b) >= 0) { rc++; } if (edges[4 * eid + 3] != InvalidID) { if (replace_tri_vertex(edges[4 * eid + 3], d, b) >= 0) { rc++; } } edges_b.Add(eid); if (rc > 0) { vertices_refcount.increment(b, rc); vertices_refcount.decrement(d, rc); } } vertex_edges[d] = null; vertices_refcount.decrement(d); merge_info.vRemoved[1] = d; } else { edges_b.Remove(ecd); merge_info.vRemoved[1] = InvalidID; } merge_info.vKept[1] = b; // replace edge cd with edge ab in triangle tcd replace_triangle_edge(tcd, ecd, eab); edges_refcount.decrement(ecd); // update edge-tri adjacency set_edge_triangles(eab, tab, tcd); // Once we merge ab to cd, there may be additional edges (now) connected // to either a or b that are connected to the same vertex on their 'other' side. // So we now have two boundary edges connecting the same two vertices - disaster! // We need to find and merge these edges. // Q: I don't think it is possible to have multiple such edge-pairs at a or b // But I am not certain...is a bit tricky to handle because we modify edges_v... merge_info.eRemovedExtra = new Vector2i(InvalidID, InvalidID); merge_info.eKeptExtra = merge_info.eRemovedExtra; for (int vi = 0; vi < 2; ++vi) { int v1 = a, v2 = c; // vertices of merged edge if (vi == 1) { v1 = b; v2 = d; } if (v1 == v2) { continue; } List <int> edges_v = vertex_edges[v1]; int Nedges = edges_v.Count; bool found = false; // in this loop, we compare 'other' vert_1 and vert_2 of edges around v1. // problem case is when vert_1 == vert_2 (ie two edges w/ same other vtx). for (int i = 0; i < Nedges && found == false; ++i) { int edge_1 = edges_v[i]; if (edge_is_boundary(edge_1) == false) { continue; } int vert_1 = edge_other_v(edge_1, v1); for (int j = i + 1; j < Nedges; ++j) { int edge_2 = edges_v[j]; int vert_2 = edge_other_v(edge_2, v1); if (vert_1 == vert_2 && edge_is_boundary(edge_2)) // if ! boundary here, we are in deep trouble... // replace edge_2 w/ edge_1 in tri, update edge and vtx-edge-nbr lists { int tri_1 = edges[4 * edge_1 + 2]; int tri_2 = edges[4 * edge_2 + 2]; replace_triangle_edge(tri_2, edge_2, edge_1); set_edge_triangles(edge_1, tri_1, tri_2); vertex_edges[v1].Remove(edge_2); vertex_edges[vert_1].Remove(edge_2); edges_refcount.decrement(edge_2); merge_info.eRemovedExtra[vi] = edge_2; merge_info.eKeptExtra[vi] = edge_1; //Nedges = edges_v.Count; // this code allows us to continue checking, ie in case we had //i--; // multiple such edges. but I don't think it's possible. found = true; // exit outer i loop break; // exit inner j loop } } } } return(MeshResult.Ok); }