/// <summary> /// 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 (ie angle tolerance) /// </summary> public static bool CheckIfEdgeFlipCreatesFlip(DMesh3 mesh, int eID, double flip_dot_tol = 0.0) { Util.gDevAssert(mesh.IsBoundaryEdge(eID) == false); Index4i einfo = mesh.GetEdge(eID); Index2i ov = mesh.GetEdgeOpposingV(eID); int a = einfo.a, b = einfo.b, c = ov.a, d = ov.b; int t0 = einfo.c; Vector3d vC = mesh.GetVertex(c), vD = mesh.GetVertex(d); Index3i tri_v = mesh.GetTriangle(t0); int oa = a, ob = b; IndexUtil.orient_tri_edge(ref oa, ref ob, ref tri_v); Vector3d vOA = mesh.GetVertex(oa), vOB = mesh.GetVertex(ob); Vector3d n0 = MathUtil.FastNormalDirection(ref vOA, ref vOB, ref vC); Vector3d n1 = MathUtil.FastNormalDirection(ref vOB, ref vOA, ref vD); Vector3d f0 = MathUtil.FastNormalDirection(ref vC, ref vD, ref vOB); if (edge_flip_metric(ref n0, ref f0, flip_dot_tol) <= flip_dot_tol || edge_flip_metric(ref n1, ref f0, flip_dot_tol) <= flip_dot_tol) { return(true); } Vector3d f1 = MathUtil.FastNormalDirection(ref vD, ref vC, ref vOA); if (edge_flip_metric(ref n0, ref f1, flip_dot_tol) <= flip_dot_tol || edge_flip_metric(ref n1, ref f1, flip_dot_tol) <= flip_dot_tol) { return(true); } return(false); }
/// <summary> /// Check if edge flip might reverse normal direction. /// /// Not entirely clear on how to implement this test. /// Currently checking if any normal-pairs are reversed. /// </summary> protected bool flip_inverts_normals(int a, int b, int c, int d, int t0) { Vector3d vC = mesh.GetVertex(c), vD = mesh.GetVertex(d); Index3i tri_v = mesh.GetTriangle(t0); int oa = a, ob = b; IndexUtil.orient_tri_edge(ref oa, ref ob, ref tri_v); Vector3d vOA = mesh.GetVertex(oa), vOB = mesh.GetVertex(ob); Vector3d n0 = MathUtil.FastNormalDirection(ref vOA, ref vOB, ref vC); Vector3d n1 = MathUtil.FastNormalDirection(ref vOB, ref vOA, ref vD); Vector3d f0 = MathUtil.FastNormalDirection(ref vC, ref vD, ref vOB); if (n0.Dot(f0) < 0 || n1.Dot(f0) < 0) { return(true); } Vector3d f1 = MathUtil.FastNormalDirection(ref vD, ref vC, ref vOA); if (n0.Dot(f1) < 0 || n1.Dot(f1) < 0) { 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); }
/// <summary> /// For given edge, return it's triangles and the triangles that would /// be created if it was flipped (used in edge-flip optimizers) /// </summary> public static void GetEdgeFlipTris(DMesh3 mesh, int eID, out Index3i orig_t0, out Index3i orig_t1, out Index3i flip_t0, out Index3i flip_t1) { Index4i einfo = mesh.GetEdge(eID); Index2i ov = mesh.GetEdgeOpposingV(eID); int a = einfo.a, b = einfo.b, c = ov.a, d = ov.b; int t0 = einfo.c; Index3i tri_v = mesh.GetTriangle(t0); int oa = a, ob = b; IndexUtil.orient_tri_edge(ref oa, ref ob, ref tri_v); orig_t0 = new Index3i(oa, ob, c); orig_t1 = new Index3i(ob, oa, d); flip_t0 = new Index3i(c, d, ob); flip_t1 = new Index3i(d, c, oa); }
/// <summary> /// For given edge, return normals of it's two triangles, and normals /// of the triangles created if edge is flipped (used in edge-flip optimizers) /// </summary> public static void GetEdgeFlipNormals(DMesh3 mesh, int eID, out Vector3d n1, out Vector3d n2, out Vector3d on1, out Vector3d on2) { Index4i einfo = mesh.GetEdge(eID); Index2i ov = mesh.GetEdgeOpposingV(eID); int a = einfo.a, b = einfo.b, c = ov.a, d = ov.b; int t0 = einfo.c; Vector3d vC = mesh.GetVertex(c), vD = mesh.GetVertex(d); Index3i tri_v = mesh.GetTriangle(t0); int oa = a, ob = b; IndexUtil.orient_tri_edge(ref oa, ref ob, ref tri_v); Vector3d vOA = mesh.GetVertex(oa), vOB = mesh.GetVertex(ob); n1 = MathUtil.Normal(ref vOA, ref vOB, ref vC); n2 = MathUtil.Normal(ref vOB, ref vOA, ref vD); on1 = MathUtil.Normal(ref vC, ref vD, ref vOB); on2 = MathUtil.Normal(ref vD, ref vC, ref vOA); }
/// <summary> /// Stitch two sets of boundary edges that are provided as unordered pairs of edges, by /// adding triangulated quads between each edge pair. /// If a failure is encountered during stitching, the triangles added up to that point are removed. /// </summary> public virtual int[] StitchUnorderedEdges(List <Index2i> EdgePairs, int group_id = -1) { int N = EdgePairs.Count; int[] new_tris = new int[N * 2]; int i = 0; for (; i < N; ++i) { Index2i edges = EdgePairs[i]; // look up and orient the first edge Index4i edge_a = Mesh.GetEdge(edges.a); if (edge_a.d != DMesh3.InvalidID) { goto operation_failed; } Index3i edge_a_tri = Mesh.GetTriangle(edge_a.c); int a = edge_a.a, b = edge_a.b; IndexUtil.orient_tri_edge(ref a, ref b, edge_a_tri); // look up and orient the second edge Index4i edge_b = Mesh.GetEdge(edges.b); if (edge_b.d != DMesh3.InvalidID) { goto operation_failed; } Index3i edge_b_tri = Mesh.GetTriangle(edge_b.c); int c = edge_b.a, d = edge_b.b; IndexUtil.orient_tri_edge(ref c, ref d, edge_b_tri); // swap second edge (right? should this be a parameter?) int tmp = c; c = d; d = tmp; Index3i t1 = new Index3i(b, a, d); Index3i t2 = new Index3i(a, c, d); int tid1 = Mesh.AppendTriangle(t1, group_id); int tid2 = Mesh.AppendTriangle(t2, group_id); if (tid1 == DMesh3.InvalidID || tid2 == DMesh3.InvalidID) { goto operation_failed; } new_tris[2 * i] = tid1; new_tris[2 * i + 1] = tid2; } return(new_tris); operation_failed: // remove what we added so far if (i > 0) { if (remove_triangles(new_tris, 2 * (i - 1)) == false) { throw new Exception("MeshConstructor.StitchLoop: failed to add all triangles, and also failed to back out changes."); } } return(null); }
/// <summary> /// Stitch two sets of boundary edges that are provided as unordered pairs of edges, by /// adding triangulated quads between each edge pair. /// If bAbortOnFailure==true and a failure is encountered during stitching, the triangles added up to that point are removed. /// If bAbortOnFailure==false, failures are ignored and the returned triangle list may contain invalid values! /// </summary> public virtual int[] StitchUnorderedEdges(List <Index2i> EdgePairs, int group_id, bool bAbortOnFailure, out bool stitch_incomplete) { int N = EdgePairs.Count; int[] new_tris = new int[N * 2]; if (bAbortOnFailure == false) { for (int k = 0; k < new_tris.Length; ++k) { new_tris[k] = DMesh3.InvalidID; } } stitch_incomplete = false; int i = 0; for (; i < N; ++i) { Index2i edges = EdgePairs[i]; // look up and orient the first edge Index4i edge_a = Mesh.GetEdge(edges.a); if (edge_a.d != DMesh3.InvalidID) { if (bAbortOnFailure) { goto operation_failed; } else { stitch_incomplete = true; continue; } } Index3i edge_a_tri = Mesh.GetTriangle(edge_a.c); int a = edge_a.a, b = edge_a.b; IndexUtil.orient_tri_edge(ref a, ref b, edge_a_tri); // look up and orient the second edge Index4i edge_b = Mesh.GetEdge(edges.b); if (edge_b.d != DMesh3.InvalidID) { if (bAbortOnFailure) { goto operation_failed; } else { stitch_incomplete = true; continue; } } Index3i edge_b_tri = Mesh.GetTriangle(edge_b.c); int c = edge_b.a, d = edge_b.b; IndexUtil.orient_tri_edge(ref c, ref d, edge_b_tri); // swap second edge (right? should this be a parameter?) int tmp = c; c = d; d = tmp; var t1 = new Index3i(b, a, d); var t2 = new Index3i(a, c, d); int tid1 = Mesh.AppendTriangle(t1, group_id); int tid2 = Mesh.AppendTriangle(t2, group_id); if (tid1 < 0 || tid2 < 0) { if (bAbortOnFailure) { goto operation_failed; } else { stitch_incomplete = true; continue; } } new_tris[2 * i] = tid1; new_tris[2 * i + 1] = tid2; } return(new_tris); operation_failed: // remove what we added so far if (i > 0) { if (remove_triangles(new_tris, 2 * (i - 1)) == false) { throw new Exception("MeshEditor.StitchLoop: failed to add all triangles, and also failed to back out changes."); } } return(null); }
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); }