// return point that minimizes quadric error for edge [ea,eb] Vector3d OptimalPoint(int eid, ref QuadricError q, int ea, int eb) { // if we would like to preserve boundary, we need to know that here // so that we properly score these edges if (HaveBoundary && PreserveBoundary) { if (mesh.IsBoundaryEdge(eid)) { return((mesh.GetVertex(ea) + mesh.GetVertex(eb)) * 0.5); } else { if (IsBoundaryV(ea)) { return(mesh.GetVertex(ea)); } else if (IsBoundaryV(eb)) { return(mesh.GetVertex(eb)); } } } // [TODO] if we have constraints, we should apply them here, for same reason as bdry above... if (MinimizeQuadricPositionError == false) { return(project((mesh.GetVertex(ea) + mesh.GetVertex(eb)) * 0.5)); } else { Vector3d result = Vector3d.Zero; if (q.OptimalPoint(ref result)) { return(project(result)); } // degenerate matrix, evaluate quadric at edge end and midpoints // (could do line search here...) Vector3d va = mesh.GetVertex(ea); Vector3d vb = mesh.GetVertex(eb); Vector3d c = project((va + vb) * 0.5); double fa = q.Evaluate(va); double fb = q.Evaluate(vb); double fc = q.Evaluate(c); double m = MathUtil.Min(fa, fb, fc); if (m == fa) { return(va); } else if (m == fb) { return(vb); } return(c); } }
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) Index3i T0tv = mesh.GetTriangle(t0); int c = IndexUtil.find_tri_other_vtx(a, b, T0tv); Index3i T1tv = (bIsBoundaryEdge) ? DMesh3.InvalidTriangle : mesh.GetTriangle(t1); int d = (bIsBoundaryEdge) ? DMesh3.InvalidID : IndexUtil.find_tri_other_vtx(a, b, T1tv); Vector3d vA = mesh.GetVertex(a); Vector3d vB = mesh.GetVertex(b); double edge_len_sqr = (vA - vB).LengthSquared; 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); } // 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; } } 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) { // don't want to flip if it will invert triangle...tetrahedron sign?? // can we do this more efficiently somehow? bool a_is_boundary_vtx = (MeshIsClosed) ? false : (bIsBoundaryEdge || mesh.vertex_is_boundary(a)); bool b_is_boundary_vtx = (MeshIsClosed) ? false : (bIsBoundaryEdge || mesh.vertex_is_boundary(b)); bool c_is_boundary_vtx = (MeshIsClosed) ? false : mesh.vertex_is_boundary(c); bool d_is_boundary_vtx = (MeshIsClosed) ? false : mesh.vertex_is_boundary(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); if (flip_err < curr_err) { // try flip 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, 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 Vector3d GetVertex(int i) { return Mesh.GetVertex(Vertices[i]); }
// return point that minimizes quadric error for edge [ea,eb] Vector3d OptimalPoint(QuadricError q, int ea, int eb) { if (MinimizeQuadricPositionError == false) { return((mesh.GetVertex(ea) + mesh.GetVertex(eb)) * 0.5); } else { try { return(q.OptimalPoint()); } catch { // degenerate matrix, evaluate quadric at edge end and midpoints // (could do line search here...) Vector3d va = mesh.GetVertex(ea); Vector3d vb = mesh.GetVertex(eb); Vector3d c = (va + vb) * 0.5; double fa = q.Evaluate(va); double fb = q.Evaluate(vb); double fc = q.Evaluate(c); double m = MathUtil.Min(fa, fb, fc); if (m == fa) { return(va); } else if (m == fb) { return(vb); } return(c); } } }
/// <summary> /// Apply LaplacianMeshSmoother to subset of mesh triangles. /// border of subset always has soft constraint with borderWeight, /// but is then snapped back to original vtx pos after solve. /// nConstrainLoops inner loops are also soft-constrained, with weight falloff via square roots (defines continuity) /// interiorWeight is soft constraint added to all vertices /// </summary> public static void RegionSmooth(DMesh3 mesh, IEnumerable <int> triangles, int nConstrainLoops, int nIncludeExteriorRings, bool bPreserveExteriorRings, double borderWeight = 10.0, double interiorWeight = 0.0) { HashSet <int> fixedVerts = new HashSet <int>(); if (nIncludeExteriorRings > 0) { MeshFaceSelection expandTris = new MeshFaceSelection(mesh); expandTris.Select(triangles); if (bPreserveExteriorRings) { MeshEdgeSelection bdryEdges = new MeshEdgeSelection(mesh); bdryEdges.SelectBoundaryTriEdges(expandTris); expandTris.ExpandToOneRingNeighbours(nIncludeExteriorRings); MeshVertexSelection startVerts = new MeshVertexSelection(mesh); startVerts.SelectTriangleVertices(triangles); startVerts.DeselectEdges(bdryEdges); MeshVertexSelection expandVerts = new MeshVertexSelection(mesh, expandTris); foreach (int vid in expandVerts) { if (startVerts.IsSelected(vid) == false) { fixedVerts.Add(vid); } } } else { expandTris.ExpandToOneRingNeighbours(nIncludeExteriorRings); } triangles = expandTris; } RegionOperator region = new RegionOperator(mesh, triangles); DSubmesh3 submesh = region.Region; DMesh3 smoothMesh = submesh.SubMesh; LaplacianMeshSmoother smoother = new LaplacianMeshSmoother(smoothMesh); // map fixed verts to submesh HashSet <int> subFixedVerts = new HashSet <int>(); foreach (int base_vid in fixedVerts) { subFixedVerts.Add(submesh.MapVertexToSubmesh(base_vid)); } // constrain borders double w = borderWeight; HashSet <int> constrained = (submesh.BaseBorderV.Count > 0) ? new HashSet <int>() : null; foreach (int base_vid in submesh.BaseBorderV) { int sub_vid = submesh.BaseToSubV[base_vid]; smoother.SetConstraint(sub_vid, smoothMesh.GetVertex(sub_vid), w, true); if (constrained != null) { constrained.Add(sub_vid); } } if (constrained.Count > 0) { w = Math.Sqrt(w); for (int k = 0; k < nConstrainLoops; ++k) { HashSet <int> next_layer = new HashSet <int>(); foreach (int sub_vid in constrained) { foreach (int nbr_vid in smoothMesh.VtxVerticesItr(sub_vid)) { if (constrained.Contains(nbr_vid) == false) { if (smoother.IsConstrained(nbr_vid) == false) { smoother.SetConstraint(nbr_vid, smoothMesh.GetVertex(nbr_vid), w, subFixedVerts.Contains(nbr_vid)); } next_layer.Add(nbr_vid); } } } constrained.UnionWith(next_layer); w = Math.Sqrt(w); } } // soft constraint on all interior vertices, if requested if (interiorWeight > 0) { foreach (int vid in smoothMesh.VertexIndices()) { if (smoother.IsConstrained(vid) == false) { smoother.SetConstraint(vid, smoothMesh.GetVertex(vid), interiorWeight, subFixedVerts.Contains(vid)); } } } else if (subFixedVerts.Count > 0) { foreach (int vid in subFixedVerts) { if (smoother.IsConstrained(vid) == false) { smoother.SetConstraint(vid, smoothMesh.GetVertex(vid), 0, true); } } } smoother.SolveAndUpdateMesh(); region.BackPropropagateVertices(true); }
protected void compute_full(IEnumerable <int> Triangles) { Graph = new DGraph3(); if (WantGraphEdgeInfo) { GraphEdges = new DVector <GraphEdgeInfo>(); } Vertices = new Dictionary <Vector3d, int>(); foreach (int tid in Triangles) { Vector3dTuple3 tv = new Vector3dTuple3(); Mesh.GetTriVertices(tid, ref tv.V0, ref tv.V1, ref tv.V2); Vector3d f = new Vector3d(ValueF(tv.V0), ValueF(tv.V1), ValueF(tv.V2)); // round f to 0 within epsilon? if (f.x < 0 && f.y < 0 && f.z < 0) { continue; } if (f.x > 0 && f.y > 0 && f.z > 0) { continue; } Index3i triVerts = Mesh.GetTriangle(tid); Index3i triEdges = Mesh.GetTriEdges(tid); if (f.x * f.y * f.z == 0) { int z0 = (f.x == 0) ? 0 : ((f.y == 0) ? 1 : 2); int i1 = (z0 + 1) % 3, i2 = (z0 + 2) % 3; if (f[i1] * f[i2] > 0) { continue; // single-vertex-crossing case, skip here and let other edges catch it } if (f[i1] == 0 || f[i2] == 0) { // on-edge case int z1 = f[i1] == 0 ? i1 : i2; int e0 = add_or_append_vertex(Mesh.GetVertex(triVerts[z0])); int e1 = add_or_append_vertex(Mesh.GetVertex(triVerts[z1])); int graph_eid = Graph.AppendEdge(e0, e1, (int)TriangleCase.OnEdge); if (WantGraphEdgeInfo) { add_on_edge(graph_eid, tid, triEdges[z0]); } } else { // edge/vertex case Util.gDevAssert(f[i1] * f[i2] < 0); int vert_vid = add_or_append_vertex(Mesh.GetVertex(triVerts[z0])); int i = i1, j = i2; if (triVerts[j] < triVerts[i]) { int tmp = i; i = j; j = tmp; } Vector3d cross = find_crossing(tv[i], tv[j], f[i], f[j]); int cross_vid = add_or_append_vertex(cross); int graph_eid = Graph.AppendEdge(vert_vid, cross_vid, (int)TriangleCase.EdgeVertex); if (WantGraphEdgeInfo) { add_edge_edge(graph_eid, tid, new Index2i(triEdges[(z0 + 1) % 3], triVerts[z0])); } } } else { Index3i cross_verts = Index3i.Min; for (int ti = 0; ti < 3; ++ti) { int i = ti, j = (ti + 1) % 3; if (f[i] * f[j] > 0) { continue; } if (triVerts[j] < triVerts[i]) { int tmp = i; i = j; j = tmp; } Vector3d cross = find_crossing(tv[i], tv[j], f[i], f[j]); cross_verts[ti] = add_or_append_vertex(cross); } int e0 = (cross_verts.a == int.MinValue) ? 1 : 0; int e1 = (cross_verts.c == int.MinValue) ? 1 : 2; int ev0 = cross_verts[e0]; int ev1 = cross_verts[e1]; Util.gDevAssert(ev0 != int.MinValue && ev1 != int.MinValue); int graph_eid = Graph.AppendEdge(ev0, ev1, (int)TriangleCase.EdgeEdge); if (WantGraphEdgeInfo) { add_edge_edge(graph_eid, tid, new Index2i(triEdges[e0], triEdges[e1])); } } } Vertices = null; }
internal bool RandomAdjust(g3.DMesh3 mesh, DMeshAABBTree3 tree, Random r, double max, double moveTries, double targetArea) { bool moved = false; if (this.locked) { return(false); } for (int i = 0; i < moveTries; i++) { var v0 = mesh.GetVertex(vertex_index.a); var v1 = mesh.GetVertex(vertex_index.b); var v2 = mesh.GetVertex(vertex_index.c); var v0_old = mesh.GetVertex(vertex_index.a); var v1_old = mesh.GetVertex(vertex_index.b); var v2_old = mesh.GetVertex(vertex_index.c); v0.x += (r.NextDouble() * max * 2 - max); v0.y += (r.NextDouble() * max * 2 - max); v0.z += (r.NextDouble() * max * 2 - max); v1.x += (r.NextDouble() * max * 2 - max); v1.y += (r.NextDouble() * max * 2 - max); v1.z += (r.NextDouble() * max * 2 - max); v2.x += (r.NextDouble() * max * 2 - max); v2.y += (r.NextDouble() * max * 2 - max); v2.z += (r.NextDouble() * max * 2 - max); int tNearestID = tree.FindNearestTriangle(v0); DistPoint3Triangle3 q = MeshQueries.TriangleDistance(tree.Mesh, tNearestID, v0); v0 = q.TriangleClosest; tNearestID = tree.FindNearestTriangle(v1); q = MeshQueries.TriangleDistance(tree.Mesh, tNearestID, v1); v1 = q.TriangleClosest; tNearestID = tree.FindNearestTriangle(v2); q = MeshQueries.TriangleDistance(tree.Mesh, tNearestID, v2); v2 = q.TriangleClosest; double oldArea = (HowCloseToTargetArea(mesh, targetArea, 2) / targetArea) * 3; double oldAngleQuality = GetTriangleTotalAnglesQualityHelper(mesh, 2); var n = mesh.GetTriNormal(meshIndex); double oldNormalQuality = GetNormalQuality(mesh, n, 2) * 6; mesh.SetVertex(vertex_index.a, v0); mesh.SetVertex(vertex_index.b, v1); mesh.SetVertex(vertex_index.c, v2); double newArea = (HowCloseToTargetArea(mesh, targetArea, 2) / targetArea) * 3; double newAngleQuality = GetTriangleTotalAnglesQualityHelper(mesh, 2); double newNormalQuality = GetNormalQuality(mesh, n, 2) * 6; if ((oldArea + oldAngleQuality + oldNormalQuality) < (newArea + newAngleQuality + newNormalQuality)) { mesh.SetVertex(vertex_index.a, v0_old); mesh.SetVertex(vertex_index.b, v1_old); mesh.SetVertex(vertex_index.c, v2_old); } else { moved = true; } } return(moved); }
public void Initialize() { ToMeshV = new int[Mesh.MaxVertexID]; ToIndex = new int[Mesh.MaxVertexID]; N = 0; foreach (int vid in Mesh.VertexIndices()) { ToMeshV[N] = vid; ToIndex[vid] = N; N++; } Px = new double[N]; Py = new double[N]; Pz = new double[N]; nbr_counts = new int[N]; M = new SymmetricSparseMatrix(); for (int i = 0; i < N; ++i) { int vid = ToMeshV[i]; Vector3d v = Mesh.GetVertex(vid); Px[i] = v.x; Py[i] = v.y; Pz[i] = v.z; nbr_counts[i] = Mesh.GetVtxEdgeCount(vid); } // construct laplacian matrix for (int i = 0; i < N; ++i) { int vid = ToMeshV[i]; int n = nbr_counts[i]; double sum_w = 0; foreach (int nbrvid in Mesh.VtxVerticesItr(vid)) { int j = ToIndex[nbrvid]; int n2 = nbr_counts[j]; // weight options //double w = -1; double w = -1.0 / Math.Sqrt(n + n2); //double w = -1.0 / n; M.Set(i, j, w); sum_w += w; } sum_w = -sum_w; M.Set(vid, vid, sum_w); } // compute laplacian vectors of initial mesh positions MLx = new double[N]; MLy = new double[N]; MLz = new double[N]; M.Multiply(Px, MLx); M.Multiply(Py, MLy); M.Multiply(Pz, MLz); // allocate memory for internal buffers Preconditioner = new DiagonalMatrix(N); WeightsM = new DiagonalMatrix(N); Cx = new double[N]; Cy = new double[N]; Cz = new double[N]; Bx = new double[N]; By = new double[N]; Bz = new double[N]; Sx = new double[N]; Sy = new double[N]; Sz = new double[N]; UpdateForSolve(); }
// public static DMesh3 RemeshMeshNew(g3.DMesh3 mesh, float minEdgeLength, float maxEdgeLength, float contraintAngle, float smoothSpeed, int smoothPasses, g3.DMesh3 projectMeshInput = null, float projectAmount = 1.0f, float projectedDistance = float.MaxValue) { g3.DMesh3 projectMesh = projectMeshInput; if (projectMesh == null) { projectMesh = mesh; } DMesh3 projectMeshCopy = new DMesh3(projectMesh); DMeshAABBTree3 treeProject = new DMeshAABBTree3(projectMeshCopy); treeProject.Build(); GopherMeshProjectionTarget targetProject = new GopherMeshProjectionTarget() { Mesh = projectMeshCopy, Spatial = treeProject, amount = projectAmount, maxDistance = projectedDistance }; MeshConstraints cons = new MeshConstraints(); EdgeRefineFlags useFlags = EdgeRefineFlags.NoFlip; foreach (int eid in mesh.EdgeIndices()) { double fAngle = MeshUtil.OpeningAngleD(mesh, eid); if (fAngle > contraintAngle) { cons.SetOrUpdateEdgeConstraint(eid, new EdgeConstraint(useFlags)); Index2i ev = mesh.GetEdgeV(eid); //int nSetID0 = (mesh.GetVertex(ev[0]).y > 1) ? 1 : 2; //int nSetID1 = (mesh.GetVertex(ev[1]).y > 1) ? 1 : 2; cons.SetOrUpdateVertexConstraint(ev[0], new VertexConstraint(true)); cons.SetOrUpdateVertexConstraint(ev[1], new VertexConstraint(true)); } } // TODO Constrain Vertices too far away foreach (int vid in mesh.VertexIndices()) { var v = mesh.GetVertex(vid); //v.Distance() //targetProject.Project() } Remesher rProjected = new Remesher(mesh); rProjected.SetExternalConstraints(cons); rProjected.SetProjectionTarget(targetProject); rProjected.Precompute(); rProjected.EnableFlips = rProjected.EnableSplits = rProjected.EnableCollapses = true; rProjected.MinEdgeLength = minEdgeLength; //0.1f; rProjected.MaxEdgeLength = maxEdgeLength; // 0.2f; rProjected.EnableSmoothing = true; rProjected.SmoothSpeedT = smoothSpeed; // .5; if (projectMeshInput != null) { float bestSmoothPassProjectAmount = projectAmount / smoothPasses; float testbestSmoothPassProjectAmount = float.MaxValue; for (float smoothPassProjectAmount = -.1f; smoothPassProjectAmount < 1.1f; smoothPassProjectAmount += 0.005f) { double test = 0; for (int i = 0; i < smoothPasses; i++) { test = 1.0 * smoothPassProjectAmount + test * (1 - smoothPassProjectAmount); } if (Math.Abs(test - projectAmount) < Math.Abs(testbestSmoothPassProjectAmount - projectAmount)) { bestSmoothPassProjectAmount = (float)smoothPassProjectAmount; testbestSmoothPassProjectAmount = (float)test; } } targetProject.amount = bestSmoothPassProjectAmount; targetProject.maxDistance = projectedDistance; for (int k = 0; k < smoothPasses; ++k) // smoothPasses = 20 { rProjected.BasicRemeshPass(); } } else { for (int k = 0; k < smoothPasses; ++k) // smoothPasses = 20 { rProjected.BasicRemeshPass(); } } return(mesh); }
public static DMesh3 RemeshMesh(g3.DMesh3 mesh, float minEdgeLength, float maxEdgeLength, float contraintAngle, float smoothSpeed, int smoothPasses, List <Line3d> constrainedLines) { // construct mesh projection target DMesh3 meshCopy = new DMesh3(mesh); DMeshAABBTree3 tree = new DMeshAABBTree3(meshCopy); tree.Build(); MeshProjectionTarget target = new MeshProjectionTarget() { Mesh = meshCopy, Spatial = tree }; MeshConstraints cons = new MeshConstraints(); EdgeRefineFlags useFlags = EdgeRefineFlags.NoFlip; foreach (int eid in mesh.EdgeIndices()) { double fAngle = MeshUtil.OpeningAngleD(mesh, eid); Index2i ev = mesh.GetEdgeV(eid); if (fAngle > contraintAngle) { cons.SetOrUpdateEdgeConstraint(eid, new EdgeConstraint(useFlags)); // TODO Ids based off of ?? What? int nSetID0 = (mesh.GetVertex(ev[0]).y > 1) ? 1 : 2; int nSetID1 = (mesh.GetVertex(ev[1]).y > 1) ? 1 : 2; cons.SetOrUpdateVertexConstraint(ev[0], new VertexConstraint(true, nSetID0)); cons.SetOrUpdateVertexConstraint(ev[1], new VertexConstraint(true, nSetID1)); } Vector3d p1 = mesh.GetVertex(ev.a); Vector3d p2 = mesh.GetVertex(ev.b); foreach (var v in constrainedLines) { if (p1.CompareTo(v.Origin) == 0) { Vector3d p = v.PointAt(1.0); if (p2.CompareTo(p) == 0) { cons.SetOrUpdateEdgeConstraint(eid, EdgeConstraint.FullyConstrained); break; } } } foreach (var v in constrainedLines) { if (p2.CompareTo(v.Origin) == 0) { Vector3d p = v.PointAt(1.0); if (p1.CompareTo(p) == 0) { cons.SetOrUpdateEdgeConstraint(eid, EdgeConstraint.FullyConstrained); break; } } } } Remesher r = new Remesher(mesh); r.SetExternalConstraints(cons); r.SetProjectionTarget(target); r.Precompute(); r.EnableFlips = r.EnableSplits = r.EnableCollapses = true; r.MinEdgeLength = minEdgeLength; //0.1f; r.MaxEdgeLength = maxEdgeLength; // 0.2f; r.EnableSmoothing = true; r.SmoothSpeedT = smoothSpeed; // .5; for (int k = 0; k < smoothPasses; ++k) // smoothPasses = 20 { r.BasicRemeshPass(); } return(mesh); }
//public static void VoronoiMesh(List<g3.PolyLine3d> mesh, out List<g3.Line3d> listLines, out List<g3.PolyLine3d> listPolylines) //{ // System.Collections.Generic.SortedDictionary<int, MeshNode> faces = new System.Collections.Generic.SortedDictionary<int, MeshNode>(); // int index = 0; // foreach (var meshFaceIndex in mesh.TriangleIndices()) // { // var frame = mesh.GetTriFrame(meshFaceIndex); // g3.Index3i neighbors = mesh.GetTriNeighbourTris(meshFaceIndex); // g3.Index3i vertex_index = mesh.GetTriangle(meshFaceIndex); // faces.Add(meshFaceIndex, new MeshNode(index++, meshFaceIndex, frame, neighbors, vertex_index)); // } // foreach (var f in faces) // { // f.Value.neighbors.Clear(); // f.Value.neighbors.Capacity = 3; // for (int i = 0; i < 3; ++i) // { // int fn = f.Value.neighbors_index[i]; // if (fn >= 0) // f.Value.neighbors.Add(faces[fn]); // } // if (f.Value.neighbors.Count < 3) // { // f.Value.locked = true; // foreach (var n in f.Value.neighbors) // n.locked = true; // } // } // outputMesh = new g3.DMesh3(g3.MeshComponents.None); // listLines = new List<g3.Line3d>(); // listPolylines = new List<g3.PolyLine3d>(); // foreach (var f in faces) // { // outputMesh.AppendVertex(f.Value.frame.Origin); // } // HashSet<int> processedPoints = new HashSet<int>(); // foreach (var f in faces) // { // for (int i = 0; i < 3; i++) // { // List<int> outputLine = new List<int>(); // if (processedPoints.Contains(f.Value.vertex_index[i])) // continue; // int checkVertex = f.Value.vertex_index[i]; // MeshNode currentFaces = f.Value; // MeshNode prevFace = null; // bool fullLoop = false; // while (true) // { // for (int j = 0; j < currentFaces.neighbors.Count; j++) // { // var neighbor = currentFaces.neighbors[j]; // if (neighbor.UsesVertex(checkVertex)) // { // if (neighbor == prevFace) // continue; // if (neighbor == f.Value) // { // fullLoop = true; // break; // Found full loop // } // outputLine.Add(neighbor.index); // prevFace = currentFaces; // currentFaces = neighbor; // j = -1; // } // } // break; // } // if (fullLoop) // { // processedPoints.Add(checkVertex); // var polyline = new g3.PolyLine3d(); // if (outputLine.Count > 2) // { // g3.Vector3d centerPoint = f.Value.frame.Origin; // foreach (var p in outputLine) // centerPoint += outputMesh.GetVertex(p); // centerPoint /= (outputLine.Count + 1); // int center = outputMesh.AppendVertex(centerPoint); // var pS = outputMesh.GetVertex(f.Value.index); // var p0 = outputMesh.GetVertex(outputLine[0]); // var pE = outputMesh.GetVertex(outputLine[outputLine.Count - 1]); // var normal = mesh.GetTriNormal(f.Value.meshIndex); // polyline.AppendVertex(pS); // polyline.AppendVertex(p0); // listLines.Add(new g3.Line3d(pS, p0 - pS)); // var n = MathUtil.Normal(centerPoint, pS, p0); // bool reverseTri = n.Dot(normal) < 0; // if (!reverseTri) // outputMesh.AppendTriangle(center, f.Value.index, outputLine[0]); // else // outputMesh.AppendTriangle(center, outputLine[0], f.Value.index); // for (int j = 0; j < outputLine.Count - 1; j++) // { // var p1 = outputMesh.GetVertex(outputLine[j]); // var p2 = outputMesh.GetVertex(outputLine[j + 1]); // listLines.Add(new g3.Line3d(p1, p2 - p1)); // polyline.AppendVertex(p2); // if (!reverseTri) // outputMesh.AppendTriangle(center, outputLine[j], outputLine[j + 1]); // else // outputMesh.AppendTriangle(center, outputLine[j + 1], outputLine[j]); // } // polyline.AppendVertex(pS); // listLines.Add(new g3.Line3d(pE, pS - pE)); // listPolylines.Add(polyline); // if (!reverseTri) // outputMesh.AppendTriangle(center, outputLine[outputLine.Count - 1], f.Value.index); // else // outputMesh.AppendTriangle(center, f.Value.index, outputLine[outputLine.Count - 1]); // } // } // } // } //} public static void VoronoiMesh(g3.DMesh3 mesh, out g3.DMesh3 outputMesh, out List <g3.Line3d> listLines, out List <g3.PolyLine3d> listPolylines) { System.Collections.Generic.SortedDictionary <int, MeshNode> faces = new System.Collections.Generic.SortedDictionary <int, MeshNode>(); int index = 0; foreach (var meshFaceIndex in mesh.TriangleIndices()) { var frame = mesh.GetTriFrame(meshFaceIndex); g3.Index3i neighbors = mesh.GetTriNeighbourTris(meshFaceIndex); g3.Index3i vertex_index = mesh.GetTriangle(meshFaceIndex); faces.Add(meshFaceIndex, new MeshNode(index++, meshFaceIndex, frame, neighbors, vertex_index)); } foreach (var f in faces) { f.Value.neighbors.Clear(); f.Value.neighbors.Capacity = 3; for (int i = 0; i < 3; ++i) { int fn = f.Value.neighbors_index[i]; if (fn >= 0) { f.Value.neighbors.Add(faces[fn]); } } if (f.Value.neighbors.Count < 3) { f.Value.locked = true; foreach (var n in f.Value.neighbors) { n.locked = true; } } } outputMesh = new g3.DMesh3(g3.MeshComponents.None); listLines = new List <g3.Line3d>(); listPolylines = new List <g3.PolyLine3d>(); foreach (var f in faces) { outputMesh.AppendVertex(f.Value.frame.Origin); } HashSet <int> processedPoints = new HashSet <int>(); foreach (var f in faces) { for (int i = 0; i < 3; i++) { List <int> outputLine = new List <int>(); if (processedPoints.Contains(f.Value.vertex_index[i])) { continue; } int checkVertex = f.Value.vertex_index[i]; MeshNode currentFaces = f.Value; MeshNode prevFace = null; bool fullLoop = false; while (true) { for (int j = 0; j < currentFaces.neighbors.Count; j++) { var neighbor = currentFaces.neighbors[j]; if (neighbor.UsesVertex(checkVertex)) { if (neighbor == prevFace) { continue; } if (neighbor == f.Value) { fullLoop = true; break; // Found full loop } outputLine.Add(neighbor.index); prevFace = currentFaces; currentFaces = neighbor; j = -1; } } break; } if (fullLoop) { processedPoints.Add(checkVertex); var polyline = new g3.PolyLine3d(); if (outputLine.Count > 2) { g3.Vector3d centerPoint = f.Value.frame.Origin; foreach (var p in outputLine) { centerPoint += outputMesh.GetVertex(p); } centerPoint /= (outputLine.Count + 1); int center = outputMesh.AppendVertex(centerPoint); var pS = outputMesh.GetVertex(f.Value.index); var p0 = outputMesh.GetVertex(outputLine[0]); var pE = outputMesh.GetVertex(outputLine[outputLine.Count - 1]); var normal = mesh.GetTriNormal(f.Value.meshIndex); polyline.AppendVertex(pS); polyline.AppendVertex(p0); listLines.Add(new g3.Line3d(pS, p0 - pS)); var n = MathUtil.Normal(centerPoint, pS, p0); bool reverseTri = n.Dot(normal) < 0; if (!reverseTri) { outputMesh.AppendTriangle(center, f.Value.index, outputLine[0]); } else { outputMesh.AppendTriangle(center, outputLine[0], f.Value.index); } for (int j = 0; j < outputLine.Count - 1; j++) { var p1 = outputMesh.GetVertex(outputLine[j]); var p2 = outputMesh.GetVertex(outputLine[j + 1]); listLines.Add(new g3.Line3d(p1, p2 - p1)); polyline.AppendVertex(p2); if (!reverseTri) { outputMesh.AppendTriangle(center, outputLine[j], outputLine[j + 1]); } else { outputMesh.AppendTriangle(center, outputLine[j + 1], outputLine[j]); } } polyline.AppendVertex(pS); listLines.Add(new g3.Line3d(pE, pS - pE)); listPolylines.Add(polyline); if (!reverseTri) { outputMesh.AppendTriangle(center, outputLine[outputLine.Count - 1], f.Value.index); } else { outputMesh.AppendTriangle(center, f.Value.index, outputLine[outputLine.Count - 1]); } } } } } }
/// <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); }
/// <summary> /// Check if this m2 is the same as this mesh. By default only checks /// vertices and triangles, turn on other parameters w/ flags /// </summary> public bool IsSameMesh(DMesh3 m2, bool bCheckConnectivity, bool bCheckEdgeIDs = false, bool bCheckNormals = false, bool bCheckColors = false, bool bCheckUVs = false, bool bCheckGroups = false, float Epsilon = MathUtil.Epsilonf) { if (VertexCount != m2.VertexCount) { return(false); } if (TriangleCount != m2.TriangleCount) { return(false); } foreach (int vid in VertexIndices()) { if (m2.IsVertex(vid) == false || GetVertex(vid).EpsilonEqual(m2.GetVertex(vid), Epsilon) == false) { return(false); } } foreach (int tid in TriangleIndices()) { if (m2.IsTriangle(tid) == false || GetTriangle(tid).Equals(m2.GetTriangle(tid)) == false) { return(false); } } if (bCheckConnectivity) { foreach (int eid in EdgeIndices()) { Index4i e = GetEdge(eid); int other_eid = m2.FindEdge(e.a, e.b); if (other_eid == InvalidID) { return(false); } Index4i oe = m2.GetEdge(other_eid); if (Math.Min(e.c, e.d) != Math.Min(oe.c, oe.d) || Math.Max(e.c, e.d) != Math.Max(oe.c, oe.d)) { return(false); } } } if (bCheckEdgeIDs) { if (EdgeCount != m2.EdgeCount) { return(false); } foreach (int eid in EdgeIndices()) { if (m2.IsEdge(eid) == false || GetEdge(eid).Equals(m2.GetEdge(eid)) == false) { return(false); } } } if (bCheckNormals) { if (HasVertexNormals != m2.HasVertexNormals) { return(false); } if (HasVertexNormals) { foreach (int vid in VertexIndices()) { if (GetVertexNormal(vid).EpsilonEqual(m2.GetVertexNormal(vid), Epsilon) == false) { return(false); } } } } if (bCheckColors) { if (HasVertexColors != m2.HasVertexColors) { return(false); } if (HasVertexColors) { foreach (int vid in VertexIndices()) { if (GetVertexColor(vid).EpsilonEqual(m2.GetVertexColor(vid), Epsilon) == false) { return(false); } } } } if (bCheckUVs) { if (HasVertexUVs != m2.HasVertexUVs) { return(false); } if (HasVertexUVs) { foreach (int vid in VertexIndices()) { if (GetVertexUV(vid).EpsilonEqual(m2.GetVertexUV(vid), Epsilon) == false) { return(false); } } } } if (bCheckGroups) { if (HasTriangleGroups != m2.HasTriangleGroups) { return(false); } if (HasTriangleGroups) { foreach (int tid in TriangleIndices()) { if (GetTriangleGroup(tid) != m2.GetTriangleGroup(tid)) { return(false); } } } } return(true); }
/// <summary> /// Find the set of boundary EdgeLoops. Note that if we encounter topological /// issues, we will throw MeshBoundaryLoopsException w/ more info (if possible) /// </summary> public bool Compute() { // This algorithm assumes that triangles are oriented consistently, // so closed boundary-loop can be followed by walking edges in-order Loops = new List <EdgeLoop>(); Spans = new List <EdgeSpan>(); int NE = Mesh.MaxEdgeID; // Temporary memory used to indicate when we have "used" an edge. BitArray used_edge = new BitArray(NE); used_edge.SetAll(false); // current loop is stored here, cleared after each loop extracted List <int> loop_edges = new List <int>(); // [RMS] not sure we need this... List <int> loop_verts = new List <int>(); List <int> bowties = new List <int>(); // Temp buffer for reading back all boundary edges of a vertex. // probably always small but in pathological cases it could be large... int[] all_e = new int[16]; // [TODO] might make sense to precompute some things here, like num_be for each bdry vtx? // process all edges of mesh for (int eid = 0; eid < NE; ++eid) { if (!Mesh.IsEdge(eid)) { continue; } if (used_edge[eid] == true) { continue; } if (Mesh.IsBoundaryEdge(eid) == false) { continue; } if (EdgeFilterF != null && EdgeFilterF(eid) == false) { used_edge[eid] = true; continue; } // ok this is start of a boundary chain int eStart = eid; used_edge[eStart] = true; loop_edges.Add(eStart); int eCur = eid; // follow the chain in order of oriented edges bool bClosed = false; bool bIsOpenSpan = false; while (!bClosed) { Index2i ev = Mesh.GetOrientedBoundaryEdgeV(eCur); int cure_a = ev.a, cure_b = ev.b; if (bIsOpenSpan) { cure_a = ev.b; cure_b = ev.a; } else { loop_verts.Add(cure_a); } int e0 = -1, e1 = 1; int bdry_nbrs = Mesh.VtxBoundaryEdges(cure_b, ref e0, ref e1); // have to filter this list, if we are filtering. this is ugly. if (EdgeFilterF != null) { if (bdry_nbrs > 2) { if (bdry_nbrs >= all_e.Length) { all_e = new int[bdry_nbrs]; } // we may repreat this below...irritating... int num_be = Mesh.VtxAllBoundaryEdges(cure_b, all_e); num_be = BufferUtil.CountValid(all_e, EdgeFilterF, num_be); } else { if (EdgeFilterF(e0) == false) { bdry_nbrs--; } if (EdgeFilterF(e1) == false) { bdry_nbrs--; } } } if (bdry_nbrs < 2) // hit an 'endpoint' vertex (should only happen when Filter is on...) { if (SpanBehavior == SpanBehaviors.ThrowException) { throw new MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: found open span at vertex " + cure_b) { UnclosedLoop = true } } ; if (bIsOpenSpan) { bClosed = true; continue; } else { bIsOpenSpan = true; // begin open span eCur = loop_edges[0]; // restart at other end of loop loop_edges.Reverse(); // do this so we can push to front continue; } } int eNext = -1; if (bdry_nbrs > 2) { // found "bowtie" vertex...things just got complicated! if (cure_b == loop_verts[0]) { // The "end" of the current edge is the same as the start vertex. // This means we can close the loop here. Might as well! eNext = -2; // sentinel value used below } else { // try to find an unused outgoing edge that is oriented properly. // This could create sub-loops, we will handle those later if (bdry_nbrs >= all_e.Length) { all_e = new int[2 * bdry_nbrs]; } int num_be = Mesh.VtxAllBoundaryEdges(cure_b, all_e); Debug.Assert(num_be == bdry_nbrs); if (EdgeFilterF != null) { num_be = BufferUtil.FilterInPlace(all_e, EdgeFilterF, num_be); } // Try to pick the best "turn left" vertex. eNext = find_left_turn_edge(eCur, cure_b, all_e, num_be, used_edge); if (eNext == -1) { if (FailureBehavior == FailureBehaviors.ThrowException || SpanBehavior == SpanBehaviors.ThrowException) { throw new MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: cannot find valid outgoing edge at bowtie vertex " + cure_b) { BowtieFailure = true } } ; // ok, we are stuck. all we can do now is terminate this loop and keep it as a span if (bIsOpenSpan) { bClosed = true; } else { bIsOpenSpan = true; bClosed = true; } continue; } } if (bowties.Contains(cure_b) == false) { bowties.Add(cure_b); } } else { // walk forward to next available edge Debug.Assert(e0 == eCur || e1 == eCur); eNext = (e0 == eCur) ? e1 : e0; } if (eNext == -2) { // found a bowtie vert that is the same as start-of-loop, so we // are just closing it off explicitly bClosed = true; } else if (eNext == eStart) { // found edge at start of loop, so loop is done. bClosed = true; } else if (used_edge[eNext] != false) { // disaster case - the next edge is already used, but it is not the start of our loop // All we can do is convert to open span and terminate if (FailureBehavior == FailureBehaviors.ThrowException || SpanBehavior == SpanBehaviors.ThrowException) { throw new MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: encountered repeated edge " + eNext) { RepeatedEdge = true } } ; bIsOpenSpan = true; bClosed = true; } else { // push onto accumulated list Debug.Assert(used_edge[eNext] == false); loop_edges.Add(eNext); used_edge[eNext] = true; eCur = eNext; } } if (bIsOpenSpan) { SawOpenSpans = true; if (SpanBehavior == SpanBehaviors.Compute) { loop_edges.Reverse(); // orient properly EdgeSpan span = EdgeSpan.FromEdges(Mesh, loop_edges); Spans.Add(span); } } else if (bowties.Count > 0) { // if we saw a bowtie vertex, we might need to break up this loop, // so call extract_subloops List <EdgeLoop> subloops = extract_subloops(loop_verts, loop_edges, bowties); for (int i = 0; i < subloops.Count; ++i) { Loops.Add(subloops[i]); } } else { // clean simple loop, convert to EdgeLoop instance EdgeLoop loop = new EdgeLoop(Mesh); loop.Vertices = loop_verts.ToArray(); loop.Edges = loop_edges.ToArray(); Loops.Add(loop); } // reset these lists loop_edges.Clear(); loop_verts.Clear(); bowties.Clear(); } return(true); } // [TODO] cache this in a dictionary? we will not need very many, but we will // need each multiple times! Vector3d get_vtx_normal(int vid) { Vector3d n = Vector3d.Zero; foreach (int ti in Mesh.VtxTrianglesItr(vid)) { n += Mesh.GetTriNormal(ti); } n.Normalize(); return(n); } // ok, bdry_edges[0...bdry_edges_count] contains the boundary edges coming out of bowtie_v. // We want to pick the best one to continue the loop that came in to bowtie_v on incoming_e. // If the loops are all sane, then we will get the smallest loops by "turning left" at bowtie_v. // So, we compute the tangent plane at bowtie_v, and then the signed angle for each // viable edge in this plane. // // [TODO] handle degenerate edges. what do we do then? Currently will only chose // degenerate edge if there are no other options (I think...) int find_left_turn_edge(int incoming_e, int bowtie_v, int[] bdry_edges, int bdry_edges_count, BitArray used_edges) { // compute normal and edge [a,bowtie] Vector3d n = get_vtx_normal(bowtie_v); int other_v = Mesh.edge_other_v(incoming_e, bowtie_v); Vector3d ab = Mesh.GetVertex(bowtie_v) - Mesh.GetVertex(other_v); // our winner int best_e = -1; double best_angle = double.MaxValue; for (int i = 0; i < bdry_edges_count; ++i) { int bdry_eid = bdry_edges[i]; if (used_edges[bdry_eid] == true) { continue; // this edge is already used } Index2i bdry_ev = Mesh.GetOrientedBoundaryEdgeV(bdry_eid); if (bdry_ev.a != bowtie_v) { continue; // have to be able to chain to end of current edge, orientation-wise } // compute projected angle Vector3d bc = Mesh.GetVertex(bdry_ev.b) - Mesh.GetVertex(bowtie_v); float fAngleS = MathUtil.PlaneAngleSignedD((Vector3f)ab, (Vector3f)bc, (Vector3f)n); // turn left! if (best_angle == double.MaxValue || fAngleS < best_angle) { best_angle = fAngleS; best_e = bdry_eid; } } // [RMS] w/ bowtie vertices and open spans, this does happen //Debug.Assert(best_e != -1); return(best_e); } // This is called when loopV contains one or more "bowtie" vertices. // These vertices *might* be duplicated in loopV (but not necessarily) // If they are, we have to break loopV into subloops that don't contain duplicates. // // The list bowties contains all the possible duplicates // (all v in bowties occur in loopV at least once) // // Currently loopE is not used, and the returned EdgeLoop objects do not have their Edges // arrays initialized. Perhaps to improve in future. List <EdgeLoop> extract_subloops(List <int> loopV, List <int> loopE, List <int> bowties) { List <EdgeLoop> subs = new List <EdgeLoop>(); // figure out which bowties we saw are actually duplicated in loopV List <int> dupes = new List <int>(); foreach (int bv in bowties) { if (count_in_list(loopV, bv) > 1) { dupes.Add(bv); } } // we might not actually have any duplicates, if we got luck. Early out in that case if (dupes.Count == 0) { subs.Add(new EdgeLoop(Mesh) { Vertices = loopV.ToArray(), Edges = loopE.ToArray(), BowtieVertices = bowties.ToArray() }); return(subs); } // This loop extracts subloops until we have dealt with all the // duplicate vertices in loopV while (dupes.Count > 0) { // Find shortest "simple" loop, ie a loop from a bowtie to itself that // does not contain any other bowties. This is an independent loop. // We're doing a lot of extra work here if we only have one element in dupes... int bi = 0, bv = 0; int start_i = -1, end_i = -1; int bv_shortest = -1; int shortest = int.MaxValue; for ( ; bi < dupes.Count; ++bi) { bv = dupes[bi]; if (is_simple_bowtie_loop(loopV, dupes, bv, out start_i, out end_i)) { int len = count_span(loopV, start_i, end_i); if (len < shortest) { bv_shortest = bv; shortest = len; } } } if (bv_shortest == -1) { throw new MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: Cannot find a valid simple loop"); } if (bv != bv_shortest) { bv = bv_shortest; // running again just to get start_i and end_i... is_simple_bowtie_loop(loopV, dupes, bv, out start_i, out end_i); } Debug.Assert(loopV[start_i] == bv && loopV[end_i] == bv); EdgeLoop loop = new EdgeLoop(Mesh); loop.Vertices = extract_span(loopV, start_i, end_i, true); loop.Edges = EdgeLoop.VertexLoopToEdgeLoop(Mesh, loop.Vertices); loop.BowtieVertices = bowties.ToArray(); subs.Add(loop); // If there are no more duplicates of this bowtie, we can treat // it like a regular vertex now if (count_in_list(loopV, bv) < 2) { dupes.Remove(bv); } } // Should have one loop left that contains duplicates. // Extract this as a separate loop int nLeft = 0; for (int i = 0; i < loopV.Count; ++i) { if (loopV[i] != -1) { nLeft++; } } if (nLeft > 0) { EdgeLoop loop = new EdgeLoop(Mesh); loop.Vertices = new int[nLeft]; int vi = 0; for (int i = 0; i < loopV.Count; ++i) { if (loopV[i] != -1) { loop.Vertices[vi++] = loopV[i]; } } loop.Edges = EdgeLoop.VertexLoopToEdgeLoop(Mesh, loop.Vertices); loop.BowtieVertices = bowties.ToArray(); subs.Add(loop); } return(subs); } /* * In all the functions below, the list loopV is assumed to possibly * contain "removed" vertices indicated by -1. These are ignored. */ // Check if the loop from bowtieV to bowtieV inside loopV contains any other bowtie verts. // Also returns start and end indices in loopV of "clean" loop // Note that start may be < end, if the "clean" loop wraps around the end bool is_simple_bowtie_loop(List <int> loopV, List <int> bowties, int bowtieV, out int start_i, out int end_i) { // find two indices of bowtie vert start_i = find_index(loopV, 0, bowtieV); end_i = find_index(loopV, start_i + 1, bowtieV); if (is_simple_path(loopV, bowties, bowtieV, start_i, end_i)) { return(true); } else if (is_simple_path(loopV, bowties, bowtieV, end_i, start_i)) { int tmp = start_i; start_i = end_i; end_i = tmp; return(true); } else { return(false); // not a simple bowtie loop! } } // check if forward path from loopV[i1] to loopV[i2] contains any bowtie verts other than bowtieV bool is_simple_path(List <int> loopV, List <int> bowties, int bowtieV, int i1, int i2) { int N = loopV.Count; for (int i = i1; i != i2; i = (i + 1) % N) { int vi = loopV[i]; if (vi == -1) { continue; // skip removed vertices } if (vi != bowtieV && bowties.Contains(vi)) { return(false); } } return(true); } // Read out the span from loop[i0] to loop [i1-1] into an array. // If bMarkInvalid, then these values are set to -1 in loop int[] extract_span(List <int> loop, int i0, int i1, bool bMarkInvalid) { int num = count_span(loop, i0, i1); int[] a = new int[num]; int ai = 0; int N = loop.Count; for (int i = i0; i != i1; i = (i + 1) % N) { if (loop[i] != -1) { a[ai++] = loop[i]; if (bMarkInvalid) { loop[i] = -1; } } } return(a); } // count number of valid vertices in l between loop[i0] and loop[i1-1] int count_span(List <int> l, int i0, int i1) { int c = 0; int N = l.Count; for (int i = i0; i != i1; i = (i + 1) % N) { if (l[i] != -1) { c++; } } return(c); } // find the index of item in loop, starting at start index int find_index(List <int> loop, int start, int item) { for (int i = start; i < loop.Count; ++i) { if (loop[i] == item) { return(i); } } return(-1); } // count number of times item appears in loop int count_in_list(List <int> loop, int item) { int c = 0; for (int i = 0; i < loop.Count; ++i) { if (loop[i] == item) { c++; } } return(c); } } }
public virtual bool Fill(int group_id = -1) { if (Loop.Vertices.Length < 3) { return(false); } // this just needs one triangle if (Loop.Vertices.Length == 3) { var tri = new Index3i(Loop.Vertices[0], Loop.Vertices[2], Loop.Vertices[1]); int new_tid = Mesh.AppendTriangle(tri, group_id); if (new_tid < 0) { return(false); } NewTriangles = new int[1] { new_tid }; NewVertex = DMesh3.InvalidID; return(true); } // [TODO] 4-case? could check nbr normals to figure out best internal edge... // compute centroid Vector3d c = Vector3d.Zero; for (int i = 0; i < Loop.Vertices.Length; ++i) { c += Mesh.GetVertex(Loop.Vertices[i]); } c *= 1.0 / Loop.Vertices.Length; // add centroid vtx NewVertex = Mesh.AppendVertex(c); // stitch triangles var editor = new MeshEditor(Mesh); try { NewTriangles = editor.AddTriangleFan_OrderedVertexLoop(NewVertex, Loop.Vertices, group_id); } catch { NewTriangles = null; } // if fill failed, back out vertex-add if (NewTriangles == null) { Mesh.RemoveVertex(NewVertex, true, false); NewVertex = DMesh3.InvalidID; return(false); } else { return(true); } }
public bool Fill() { compute_polygons(); // translate/scale fill loops to unit box. This will improve // accuracy in the calcs below... Vector2d shiftOrigin = Bounds.Center; double scale = 1.0 / Bounds.MaxDim; foreach (var floop in Loops) { floop.poly.Translate(-shiftOrigin); floop.poly.Scale(scale * Vector2d.One, Vector2d.Zero); } Dictionary <PlanarComplex.Element, int> ElemToLoopMap = new Dictionary <PlanarComplex.Element, int>(); // [TODO] if we have multiple components in input mesh, we could do this per-component. // This also helps avoid nested shells creating holes. // *However*, we shouldn't *have* to because FindSolidRegions will do the right thing if // the polygons have the same orientation // add all loops to planar complex PlanarComplex complex = new PlanarComplex(); for (int i = 0; i < Loops.Count; ++i) { var elem = complex.Add(Loops[i].poly); ElemToLoopMap[elem] = i; } // sort into separate 2d solids PlanarComplex.SolidRegionInfo solids = complex.FindSolidRegions(PlanarComplex.FindSolidsOptions.SortPolygons); // fill each 2d solid List <Index2i> failed_inserts = new List <Index2i>(); List <Index2i> failed_merges = new List <Index2i>(); for (int fi = 0; fi < solids.Polygons.Count; ++fi) { var gpoly = solids.Polygons[fi]; PlanarComplex.GeneralSolid gsolid = solids.PolygonsSources[fi]; // [TODO] could do scale/translate here, per-polygon would be more precise // generate planar mesh that we will insert polygons into MeshGenerator meshgen; float planeW = 1.5f; int nDivisions = 0; if (FillTargetEdgeLen < double.MaxValue && FillTargetEdgeLen > 0) { int n = (int)((planeW / (float)scale) / FillTargetEdgeLen) + 1; nDivisions = (n <= 1) ? 0 : n; } if (nDivisions == 0) { meshgen = new TrivialRectGenerator() { IndicesMap = new Index2i(1, 2), Width = planeW, Height = planeW, }; } else { meshgen = new GriddedRectGenerator() { IndicesMap = new Index2i(1, 2), Width = planeW, Height = planeW, EdgeVertices = nDivisions }; } DMesh3 FillMesh = meshgen.Generate().MakeDMesh(); FillMesh.ReverseOrientation(); // why?!? // convenient list List <Polygon2d> polys = new List <Polygon2d>() { gpoly.Outer }; polys.AddRange(gpoly.Holes); // for each poly, we track the set of vertices inserted into mesh int[][] polyVertices = new int[polys.Count][]; // insert each poly for (int pi = 0; pi < polys.Count; ++pi) { MeshInsertUVPolyCurve insert = new MeshInsertUVPolyCurve(FillMesh, polys[pi]); ValidationStatus status = insert.Validate(MathUtil.ZeroTolerancef * scale); bool failed = true; if (status == ValidationStatus.Ok) { if (insert.Apply()) { insert.Simplify(); polyVertices[pi] = insert.CurveVertices; failed = (insert.Loops.Count != 1) || (insert.Loops[0].VertexCount != polys[pi].VertexCount); } } if (failed) { failed_inserts.Add(new Index2i(fi, pi)); } } // remove any triangles not contained in gpoly // [TODO] degenerate triangle handling? may be 'on' edge of gpoly... List <int> removeT = new List <int>(); foreach (int tid in FillMesh.TriangleIndices()) { Vector3d v = FillMesh.GetTriCentroid(tid); if (gpoly.Contains(v.xy) == false) { removeT.Add(tid); } } foreach (int tid in removeT) { FillMesh.RemoveTriangle(tid, true, false); } //Util.WriteDebugMesh(FillMesh, "c:\\scratch\\CLIPPED_MESH.obj"); // transform fill mesh back to 3d MeshTransforms.PerVertexTransform(FillMesh, (v) => { Vector2d v2 = v.xy; v2 /= scale; v2 += shiftOrigin; return(to3D(v2)); }); //Util.WriteDebugMesh(FillMesh, "c:\\scratch\\PLANAR_MESH_WITH_LOOPS.obj"); //Util.WriteDebugMesh(MeshEditor.Combine(FillMesh, Mesh), "c:\\scratch\\FILLED_MESH.obj"); // figure out map between new mesh and original edge loops // [TODO] if # of verts is different, we can still find correspondence, it is just harder // [TODO] should check that edges (ie sequential verts) are boundary edges on fill mesh // if not, can try to delete nbr tris to repair IndexMap mergeMapV = new IndexMap(true); if (MergeFillBoundary) { for (int pi = 0; pi < polys.Count; ++pi) { if (polyVertices[pi] == null) { continue; } int[] fillLoopVerts = polyVertices[pi]; int NV = fillLoopVerts.Length; PlanarComplex.Element sourceElem = (pi == 0) ? gsolid.Outer : gsolid.Holes[pi - 1]; int loopi = ElemToLoopMap[sourceElem]; EdgeLoop sourceLoop = Loops[loopi].edgeLoop; if (sourceLoop.VertexCount != NV) { failed_merges.Add(new Index2i(fi, pi)); continue; } for (int k = 0; k < NV; ++k) { Vector3d fillV = FillMesh.GetVertex(fillLoopVerts[k]); Vector3d sourceV = Mesh.GetVertex(sourceLoop.Vertices[k]); if (fillV.Distance(sourceV) < MathUtil.ZeroTolerancef) { mergeMapV[fillLoopVerts[k]] = sourceLoop.Vertices[k]; } } } } // append this fill to input mesh MeshEditor editor = new MeshEditor(Mesh); int[] mapV; editor.AppendMesh(FillMesh, mergeMapV, out mapV, Mesh.AllocateTriangleGroup()); // [TODO] should verify that we actually merged the loops... } if (failed_inserts.Count > 0 || failed_merges.Count > 0) { return(false); } return(true); }
void make_level_set3(DMesh3 mesh, /*const std::vector<Vec3ui> &tri, const std::vector<Vec3f> &x*/ Vector3f origin, float dx, int ni, int nj, int nk, DenseGrid3f phi, int exact_band) { phi.resize(ni, nj, nk); phi.assign((ni + nj + nk) * dx); // upper bound on distance DenseGrid3i closest_tri = new DenseGrid3i(ni, nj, nk, -1); DenseGrid3i intersection_count = new DenseGrid3i(ni, nj, nk, 0); // intersection_count(i,j,k) is # of tri intersections in (i-1,i]x{j}x{k} // we begin by initializing distances near the mesh, and figuring out intersection counts System.Console.WriteLine("start"); //Vector3f ijkmin, ijkmax; // [RMS] unused in original code double ddx = (double)dx; double ox = (double)origin[0], oy = (double)origin[1], oz = (double)origin[2]; foreach (int t in mesh.TriangleIndices()) { Index3i triangle = mesh.GetTriangle(t); int p = triangle.a, q = triangle.b, r = triangle.c; Vector3d xp = mesh.GetVertex(p); Vector3d xq = mesh.GetVertex(q); Vector3d xr = mesh.GetVertex(r); // coordinates in grid to high precision double fip = (xp[0] - ox) / ddx, fjp = (xp[1] - oy) / ddx, fkp = (xp[2] - oz) / ddx; double fiq = (xq[0] - ox) / ddx, fjq = (xq[1] - oy) / ddx, fkq = (xq[2] - oz) / ddx; double fir = (xr[0] - ox) / ddx, fjr = (xr[1] - oy) / ddx, fkr = (xr[2] - oz) / ddx; // do distances nearby int i0 = MathUtil.Clamp(((int)MathUtil.Min(fip, fiq, fir)) - exact_band, 0, ni - 1); int i1 = MathUtil.Clamp(((int)MathUtil.Max(fip, fiq, fir)) + exact_band + 1, 0, ni - 1); int j0 = MathUtil.Clamp(((int)MathUtil.Min(fjp, fjq, fjr)) - exact_band, 0, nj - 1); int j1 = MathUtil.Clamp(((int)MathUtil.Max(fjp, fjq, fjr)) + exact_band + 1, 0, nj - 1); int k0 = MathUtil.Clamp(((int)MathUtil.Min(fkp, fkq, fkr)) - exact_band, 0, nk - 1); int k1 = MathUtil.Clamp(((int)MathUtil.Max(fkp, fkq, fkr)) + exact_band + 1, 0, nk - 1); for (int k = k0; k <= k1; ++k) { for (int j = j0; j <= j1; ++j) { for (int i = i0; i <= i1; ++i) { Vector3f gx = new Vector3f((float)i * dx + origin[0], (float)j * dx + origin[1], (float)k * dx + origin[2]); float d = point_triangle_distance(gx, (Vector3f)xp, (Vector3f)xq, (Vector3f)xr); if (d < phi[i, j, k]) { phi[i, j, k] = d; closest_tri[i, j, k] = t; } } } } // and do intersection counts j0 = MathUtil.Clamp((int)Math.Ceiling(MathUtil.Min(fjp, fjq, fjr)), 0, nj - 1); j1 = MathUtil.Clamp((int)Math.Floor(MathUtil.Max(fjp, fjq, fjr)), 0, nj - 1); k0 = MathUtil.Clamp((int)Math.Ceiling(MathUtil.Min(fkp, fkq, fkr)), 0, nk - 1); k1 = MathUtil.Clamp((int)Math.Floor(MathUtil.Max(fkp, fkq, fkr)), 0, nk - 1); for (int k = k0; k <= k1; ++k) { for (int j = j0; j <= j1; ++j) { double a, b, c; if (point_in_triangle_2d(j, k, fjp, fkp, fjq, fkq, fjr, fkr, out a, out b, out c)) { double fi = a * fip + b * fiq + c * fir; // intersection i coordinate int i_interval = (int)(Math.Ceiling(fi)); // intersection is in (i_interval-1,i_interval] if (i_interval < 0) { intersection_count.increment(0, j, k); // we enlarge the first interval to include everything to the -x direction } else if (i_interval < ni) { intersection_count.increment(i_interval, j, k); } // we ignore intersections that are beyond the +x side of the grid } } } } System.Console.WriteLine("done narrow-band"); // and now we fill in the rest of the distances with fast sweeping for (int pass = 0; pass < 2; ++pass) { sweep(mesh, phi, closest_tri, origin, dx, +1, +1, +1); sweep(mesh, phi, closest_tri, origin, dx, -1, -1, -1); sweep(mesh, phi, closest_tri, origin, dx, +1, +1, -1); sweep(mesh, phi, closest_tri, origin, dx, -1, -1, +1); sweep(mesh, phi, closest_tri, origin, dx, +1, -1, +1); sweep(mesh, phi, closest_tri, origin, dx, -1, +1, -1); sweep(mesh, phi, closest_tri, origin, dx, +1, -1, -1); sweep(mesh, phi, closest_tri, origin, dx, -1, +1, +1); } System.Console.WriteLine("done sweeping"); // then figure out signs (inside/outside) from intersection counts for (int k = 0; k < nk; ++k) { for (int j = 0; j < nj; ++j) { int total_count = 0; for (int i = 0; i < ni; ++i) { total_count += intersection_count[i, j, k]; if (total_count % 2 == 1) // if parity of intersections so far is odd, { phi[i, j, k] = -phi[i, j, k]; // we are inside the mesh } } } } System.Console.WriteLine("done signs"); } // end make_level_set_3
public void Initialize() { ToMeshV = new int[Mesh.MaxVertexID]; ToIndex = new int[Mesh.MaxVertexID]; N = 0; foreach (int vid in Mesh.VertexIndices()) { ToMeshV[N] = vid; ToIndex[vid] = N; N++; } Px = new double[N]; Py = new double[N]; Pz = new double[N]; nbr_counts = new int[N]; SymmetricSparseMatrix M = new SymmetricSparseMatrix(); for (int i = 0; i < N; ++i) { int vid = ToMeshV[i]; Vector3d v = Mesh.GetVertex(vid); Px[i] = v.x; Py[i] = v.y; Pz[i] = v.z; nbr_counts[i] = Mesh.GetVtxEdgeCount(vid); } // construct laplacian matrix for (int i = 0; i < N; ++i) { int vid = ToMeshV[i]; int n = nbr_counts[i]; double sum_w = 0; foreach (int nbrvid in Mesh.VtxVerticesItr(vid)) { int j = ToIndex[nbrvid]; int n2 = nbr_counts[j]; // weight options //double w = -1; double w = -1.0 / Math.Sqrt(n + n2); //double w = -1.0 / n; M.Set(i, j, w); sum_w += w; } sum_w = -sum_w; // TODO: Investigate: is this ia bug? // Source https://github.com/ZelimDamian/geometry3Sharp/commit/7a50d8de10faad762e726e60956acc4bdc5456b5 // makes the following line M.Set(i, i, sum_w); M.Set(vid, vid, sum_w); } // transpose(L) * L, but matrix is symmetric... if (UseSoftConstraintNormalEquations) { //M = M.Multiply(M); // only works if M is symmetric!! PackedM = M.SquarePackedParallel(); } else { PackedM = new PackedSparseMatrix(M); } // compute laplacian vectors of initial mesh positions MLx = new double[N]; MLy = new double[N]; MLz = new double[N]; PackedM.Multiply(Px, MLx); PackedM.Multiply(Py, MLy); PackedM.Multiply(Pz, MLz); // allocate memory for internal buffers Preconditioner = new DiagonalMatrix(N); WeightsM = new DiagonalMatrix(N); Cx = new double[N]; Cy = new double[N]; Cz = new double[N]; Bx = new double[N]; By = new double[N]; Bz = new double[N]; Sx = new double[N]; Sy = new double[N]; Sz = new double[N]; need_solve_update = true; UpdateForSolve(); }
// Walk along edge loop and collapse to inserted curve vertices. EdgeLoop simplify(EdgeLoop loop) { HashSet <int> curve_verts = new HashSet <int>(CurveVertices); List <int> remaining_edges = new List <int>(); for (int li = 0; li < loop.EdgeCount; ++li) { int eid = loop.Edges[li]; Index2i ev = Mesh.GetEdgeV(eid); // cannot collapse edge between two "original" polygon verts (ie created by face pokes) if (curve_verts.Contains(ev.a) && curve_verts.Contains(ev.b)) { remaining_edges.Add(eid); continue; } // if we have an original vert, we need to keep it (and its position!) int keep = ev.a, discard = ev.b; Vector3d set_to = Vector3d.Zero; if (curve_verts.Contains(ev.b)) { keep = ev.b; discard = ev.a; set_to = Mesh.GetVertex(ev.b); } else if (curve_verts.Contains(ev.a)) { set_to = Mesh.GetVertex(ev.a); } else { set_to = 0.5 * (Mesh.GetVertex(ev.a) + Mesh.GetVertex(ev.b)); } // make sure we are not going to flip any normals // [OPTIMIZATION] May be possible to do this more efficiently because we know we are in // 2D and each tri should have same cw/ccw orientation. But we don't quite "know" we // are in 2D here, as CollapseEdge function is operating on the mesh coordinates... if (MeshUtil.CheckIfCollapseCreatesFlip(Mesh, eid, set_to)) { remaining_edges.Add(eid); continue; } // cannot collapse if the 'other' edges we would discard are OnCutEdges. This would // result in loop potentially being broken. bad! Index4i einfo = Mesh.GetEdge(eid); int c = IndexUtil.find_tri_other_vtx(keep, discard, Mesh.GetTriangle(einfo.c)); int d = IndexUtil.find_tri_other_vtx(keep, discard, Mesh.GetTriangle(einfo.d)); int ec = Mesh.FindEdge(discard, c); int ed = Mesh.FindEdge(discard, d); if (OnCutEdges.Contains(ec) || OnCutEdges.Contains(ed)) { remaining_edges.Add(eid); continue; } // do collapse and update internal data structures DMesh3.EdgeCollapseInfo collapse; MeshResult result = Mesh.CollapseEdge(keep, discard, out collapse); if (result == MeshResult.Ok) { Mesh.SetVertex(collapse.vKept, set_to); OnCutEdges.Remove(collapse.eCollapsed); } else { remaining_edges.Add(eid); } } return(EdgeLoop.FromEdges(Mesh, remaining_edges)); }
public void Initialize() { ToMeshV = new int[Mesh.MaxVertexID]; ToIndex = new int[Mesh.MaxVertexID]; N = 0; foreach (int vid in Mesh.VertexIndices()) { ToMeshV[N] = vid; ToIndex[vid] = N; N++; } Px = new double[N]; Py = new double[N]; Pz = new double[N]; nbr_counts = new int[N]; SymmetricSparseMatrix M = new SymmetricSparseMatrix(); for (int i = 0; i < N; ++i) { int vid = ToMeshV[i]; Vector3d v = Mesh.GetVertex(vid); Px[i] = v.x; Py[i] = v.y; Pz[i] = v.z; nbr_counts[i] = Mesh.GetVtxEdgeCount(vid); } // construct laplacian matrix for (int i = 0; i < N; ++i) { int vid = ToMeshV[i]; int n = nbr_counts[i]; double sum_w = 0; foreach (int nbrvid in Mesh.VtxVerticesItr(vid)) { int j = ToIndex[nbrvid]; int n2 = nbr_counts[j]; // weight options //double w = -1; double w = -1.0 / Math.Sqrt(n + n2); //double w = -1.0 / n; M.Set(i, j, w); sum_w += w; } sum_w = -sum_w; M.Set(vid, vid, sum_w); } // transpose(L) * L, but matrix is symmetric... if (UseSoftConstraintNormalEquations) { //M = M.Multiply(M); // only works if M is symmetric!! PackedM = M.SquarePackedParallel(); } else { PackedM = new PackedSparseMatrix(M); } // compute laplacian vectors of initial mesh positions MLx = new double[N]; MLy = new double[N]; MLz = new double[N]; PackedM.Multiply(Px, MLx); PackedM.Multiply(Py, MLy); PackedM.Multiply(Pz, MLz); // zero out...this is the smoothing bit! for (int i = 0; i < Px.Length; ++i) { MLx[i] = 0; MLy[i] = 0; MLz[i] = 0; } // allocate memory for internal buffers Preconditioner = new DiagonalMatrix(N); WeightsM = new DiagonalMatrix(N); Cx = new double[N]; Cy = new double[N]; Cz = new double[N]; Bx = new double[N]; By = new double[N]; Bz = new double[N]; Sx = new double[N]; Sy = new double[N]; Sz = new double[N]; need_solve_update = true; UpdateForSolve(); }
/// <summary> /// Check if this m2 is the same as this mesh. By default only checks /// vertices and triangles, turn on other parameters w/ flags /// </summary> public bool IsSameMesh(DMesh3 m2, bool bCheckEdges = false, bool bCheckNormals = false, bool bCheckColors = false, bool bCheckUVs = false, bool bCheckGroups = false, float Epsilon = MathUtil.Epsilonf) { if (VertexCount != m2.VertexCount) { return(false); } if (TriangleCount != m2.TriangleCount) { return(false); } foreach (int vid in VertexIndices()) { if (m2.IsVertex(vid) == false || GetVertex(vid).EpsilonEqual(m2.GetVertex(vid), Epsilon) == false) { return(false); } } foreach (int tid in TriangleIndices()) { if (m2.IsTriangle(tid) == false || GetTriangle(tid).Equals(m2.GetTriangle(tid)) == false) { return(false); } } if (bCheckEdges) { if (EdgeCount != m2.EdgeCount) { return(false); } foreach (int eid in EdgeIndices()) { if (m2.IsEdge(eid) == false || GetEdge(eid).Equals(m2.GetEdge(eid)) == false) { return(false); } } } if (bCheckNormals) { if (HasVertexNormals != m2.HasVertexNormals) { return(false); } if (HasVertexNormals) { foreach (int vid in VertexIndices()) { if (GetVertexNormal(vid).EpsilonEqual(m2.GetVertexNormal(vid), Epsilon) == false) { return(false); } } } } if (bCheckColors) { if (HasVertexColors != m2.HasVertexColors) { return(false); } if (HasVertexColors) { foreach (int vid in VertexIndices()) { if (GetVertexColor(vid).EpsilonEqual(m2.GetVertexColor(vid), Epsilon) == false) { return(false); } } } } if (bCheckUVs) { if (HasVertexUVs != m2.HasVertexUVs) { return(false); } if (HasVertexUVs) { foreach (int vid in VertexIndices()) { if (GetVertexUV(vid).EpsilonEqual(m2.GetVertexUV(vid), Epsilon) == false) { return(false); } } } } if (bCheckGroups) { if (HasTriangleGroups != m2.HasTriangleGroups) { return(false); } if (HasTriangleGroups) { foreach (int tid in TriangleIndices()) { if (GetTriangleGroup(tid) != m2.GetTriangleGroup(tid)) { return(false); } } } } return(true); }
/// <summary> /// Find the set of boundary EdgeLoops. Note that if we encounter topological /// issues, we will throw MeshBoundaryLoopsException w/ more info (if possible) /// </summary> public bool Compute() { // This algorithm assumes that triangles are oriented consistently, // so closed boundary-loop can be followed by walking edges in-order Loops = new List <EdgeLoop>(); Spans = new List <EdgeSpan>(); // early-out if we don't actually have boundaries if (Mesh.CachedIsClosed) { return(true); } int NE = Mesh.MaxEdgeID; // Temporary memory used to indicate when we have "used" an edge. BitArray used_edge = new BitArray(NE); used_edge.SetAll(false); // current loop is stored here, cleared after each loop extracted List <int> loop_edges = new List <int>(); // [RMS] not sure we need this... List <int> loop_verts = new List <int>(); List <int> bowties = new List <int>(); // Temp buffer for reading back all boundary edges of a vertex. // probably always small but in pathological cases it could be large... int[] all_e = new int[16]; // [TODO] might make sense to precompute some things here, like num_be for each bdry vtx? // process all edges of mesh for (int eid = 0; eid < NE; ++eid) { if (!Mesh.IsEdge(eid)) { continue; } if (used_edge[eid] == true) { continue; } if (Mesh.IsBoundaryEdge(eid) == false) { continue; } if (EdgeFilterF != null && EdgeFilterF(eid) == false) { used_edge[eid] = true; continue; } // ok this is start of a boundary chain int eStart = eid; used_edge[eStart] = true; loop_edges.Add(eStart); int eCur = eid; // follow the chain in order of oriented edges bool bClosed = false; bool bIsOpenSpan = false; while (!bClosed) { Index2i ev = Mesh.GetOrientedBoundaryEdgeV(eCur); int cure_a = ev.a, cure_b = ev.b; if (bIsOpenSpan) { cure_a = ev.b; cure_b = ev.a; } else { loop_verts.Add(cure_a); } int e0 = -1, e1 = 1; int bdry_nbrs = Mesh.VtxBoundaryEdges(cure_b, ref e0, ref e1); // have to filter this list, if we are filtering. this is ugly. if (EdgeFilterF != null) { if (bdry_nbrs > 2) { if (bdry_nbrs >= all_e.Length) { all_e = new int[bdry_nbrs]; } // we may repreat this below...irritating... int num_be = Mesh.VtxAllBoundaryEdges(cure_b, all_e); num_be = BufferUtil.CountValid(all_e, EdgeFilterF, num_be); } else { if (EdgeFilterF(e0) == false) { bdry_nbrs--; } if (EdgeFilterF(e1) == false) { bdry_nbrs--; } } } if (bdry_nbrs < 2) // hit an 'endpoint' vertex (should only happen when Filter is on...) { if (SpanBehavior == SpanBehaviors.ThrowException) { throw new MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: found open span at vertex " + cure_b) { UnclosedLoop = true } } ; if (bIsOpenSpan) { bClosed = true; continue; } else { bIsOpenSpan = true; // begin open span eCur = loop_edges[0]; // restart at other end of loop loop_edges.Reverse(); // do this so we can push to front continue; } } int eNext = -1; if (bdry_nbrs > 2) { // found "bowtie" vertex...things just got complicated! if (cure_b == loop_verts[0]) { // The "end" of the current edge is the same as the start vertex. // This means we can close the loop here. Might as well! eNext = -2; // sentinel value used below } else { // try to find an unused outgoing edge that is oriented properly. // This could create sub-loops, we will handle those later if (bdry_nbrs >= all_e.Length) { all_e = new int[2 * bdry_nbrs]; } int num_be = Mesh.VtxAllBoundaryEdges(cure_b, all_e); Debug.Assert(num_be == bdry_nbrs); if (EdgeFilterF != null) { num_be = BufferUtil.FilterInPlace(all_e, EdgeFilterF, num_be); } // Try to pick the best "turn left" vertex. eNext = find_left_turn_edge(eCur, cure_b, all_e, num_be, used_edge); if (eNext == -1) { if (FailureBehavior == FailureBehaviors.ThrowException || SpanBehavior == SpanBehaviors.ThrowException) { throw new MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: cannot find valid outgoing edge at bowtie vertex " + cure_b) { BowtieFailure = true } } ; // ok, we are stuck. all we can do now is terminate this loop and keep it as a span if (bIsOpenSpan) { bClosed = true; } else { bIsOpenSpan = true; bClosed = true; } continue; } } if (bowties.Contains(cure_b) == false) { bowties.Add(cure_b); } } else { // walk forward to next available edge Debug.Assert(e0 == eCur || e1 == eCur); eNext = (e0 == eCur) ? e1 : e0; } if (eNext == -2) { // found a bowtie vert that is the same as start-of-loop, so we // are just closing it off explicitly bClosed = true; } else if (eNext == eStart) { // found edge at start of loop, so loop is done. bClosed = true; } else if (used_edge[eNext] != false) { // disaster case - the next edge is already used, but it is not the start of our loop // All we can do is convert to open span and terminate if (FailureBehavior == FailureBehaviors.ThrowException || SpanBehavior == SpanBehaviors.ThrowException) { throw new MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: encountered repeated edge " + eNext) { RepeatedEdge = true } } ; bIsOpenSpan = true; bClosed = true; } else { // push onto accumulated list Debug.Assert(used_edge[eNext] == false); loop_edges.Add(eNext); used_edge[eNext] = true; eCur = eNext; } } if (bIsOpenSpan) { SawOpenSpans = true; if (SpanBehavior == SpanBehaviors.Compute) { loop_edges.Reverse(); // orient properly EdgeSpan span = EdgeSpan.FromEdges(Mesh, loop_edges); Spans.Add(span); } } else if (bowties.Count > 0) { // if we saw a bowtie vertex, we might need to break up this loop, // so call extract_subloops Subloops subloops = extract_subloops(loop_verts, loop_edges, bowties); foreach (var loop in subloops.Loops) { Loops.Add(loop); } if (subloops.Spans.Count > 0) { FellBackToSpansOnFailure = true; foreach (var span in subloops.Spans) { Spans.Add(span); } } } else { // clean simple loop, convert to EdgeLoop instance EdgeLoop loop = new EdgeLoop(Mesh); loop.Vertices = loop_verts.ToArray(); loop.Edges = loop_edges.ToArray(); Loops.Add(loop); } // reset these lists loop_edges.Clear(); loop_verts.Clear(); bowties.Clear(); } return(true); } // [TODO] cache this in a dictionary? we will not need very many, but we will // need each multiple times! Vector3d get_vtx_normal(int vid) { Vector3d n = Vector3d.Zero; foreach (int ti in Mesh.VtxTrianglesItr(vid)) { n += Mesh.GetTriNormal(ti); } n.Normalize(); return(n); } // ok, bdry_edges[0...bdry_edges_count] contains the boundary edges coming out of bowtie_v. // We want to pick the best one to continue the loop that came in to bowtie_v on incoming_e. // If the loops are all sane, then we will get the smallest loops by "turning left" at bowtie_v. // So, we compute the tangent plane at bowtie_v, and then the signed angle for each // viable edge in this plane. // // [TODO] handle degenerate edges. what do we do then? Currently will only chose // degenerate edge if there are no other options (I think...) int find_left_turn_edge(int incoming_e, int bowtie_v, int[] bdry_edges, int bdry_edges_count, BitArray used_edges) { // compute normal and edge [a,bowtie] Vector3d n = get_vtx_normal(bowtie_v); int other_v = Mesh.edge_other_v(incoming_e, bowtie_v); Vector3d ab = Mesh.GetVertex(bowtie_v) - Mesh.GetVertex(other_v); // our winner int best_e = -1; double best_angle = double.MaxValue; for (int i = 0; i < bdry_edges_count; ++i) { int bdry_eid = bdry_edges[i]; if (used_edges[bdry_eid] == true) { continue; // this edge is already used } Index2i bdry_ev = Mesh.GetOrientedBoundaryEdgeV(bdry_eid); if (bdry_ev.a != bowtie_v) { continue; // have to be able to chain to end of current edge, orientation-wise } // compute projected angle Vector3d bc = Mesh.GetVertex(bdry_ev.b) - Mesh.GetVertex(bowtie_v); float fAngleS = MathUtil.PlaneAngleSignedD((Vector3f)ab, (Vector3f)bc, (Vector3f)n); // turn left! if (best_angle == double.MaxValue || fAngleS < best_angle) { best_angle = fAngleS; best_e = bdry_eid; } } // [RMS] w/ bowtie vertices and open spans, this does happen //Debug.Assert(best_e != -1); return(best_e); }
public virtual bool Cut() { double invalidDist = double.MinValue; MeshEdgeSelection CutEdgeSet = null; MeshVertexSelection CutVertexSet = null; if (CutFaceSet != null) { CutEdgeSet = new MeshEdgeSelection(Mesh, CutFaceSet); CutVertexSet = new MeshVertexSelection(Mesh, CutEdgeSet); } // compute signs int MaxVID = Mesh.MaxVertexID; double[] signs = new double[MaxVID]; gParallel.ForEach(Interval1i.Range(MaxVID), (vid) => { if (Mesh.IsVertex(vid)) { Vector3d v = Mesh.GetVertex(vid); signs[vid] = (v - PlaneOrigin).Dot(PlaneNormal); } else { signs[vid] = invalidDist; } }); HashSet <int> ZeroEdges = new HashSet <int>(); HashSet <int> ZeroVertices = new HashSet <int>(); HashSet <int> OnCutEdges = new HashSet <int>(); // have to skip processing of new edges. If edge id // is > max at start, is new. Otherwise if in NewEdges list, also new. int MaxEID = Mesh.MaxEdgeID; HashSet <int> NewEdges = new HashSet <int>(); IEnumerable <int> edgeItr = Interval1i.Range(MaxEID); if (CutEdgeSet != null) { edgeItr = CutEdgeSet; } // cut existing edges with plane, using edge split foreach (int eid in edgeItr) { if (Mesh.IsEdge(eid) == false) { continue; } if (eid >= MaxEID || NewEdges.Contains(eid)) { continue; } Index2i ev = Mesh.GetEdgeV(eid); double f0 = signs[ev.a]; double f1 = signs[ev.b]; // If both signs are 0, this edge is on-contour // If one sign is 0, that vertex is on-contour int n0 = (Math.Abs(f0) < MathUtil.Epsilon) ? 1 : 0; int n1 = (Math.Abs(f1) < MathUtil.Epsilon) ? 1 : 0; if (n0 + n1 > 0) { if (n0 + n1 == 2) { ZeroEdges.Add(eid); } else { ZeroVertices.Add((n0 == 1) ? ev[0] : ev[1]); } continue; } // no crossing if (f0 * f1 > 0) { continue; } DMesh3.EdgeSplitInfo splitInfo; MeshResult result = Mesh.SplitEdge(eid, out splitInfo); if (result != MeshResult.Ok) { throw new Exception("MeshPlaneCut.Cut: failed in SplitEdge"); //return false; } // SplitEdge just bisects edge - use plane intersection instead double t = f0 / (f0 - f1); Vector3d newPos = (1 - t) * Mesh.GetVertex(ev.a) + (t) * Mesh.GetVertex(ev.b); Mesh.SetVertex(splitInfo.vNew, newPos); NewEdges.Add(splitInfo.eNewBN); NewEdges.Add(splitInfo.eNewCN); OnCutEdges.Add(splitInfo.eNewCN); if (splitInfo.eNewDN != DMesh3.InvalidID) { NewEdges.Add(splitInfo.eNewDN); OnCutEdges.Add(splitInfo.eNewDN); } } // remove one-rings of all positive-side vertices. IEnumerable <int> vertexSet = Interval1i.Range(MaxVID); if (CutVertexSet != null) { vertexSet = CutVertexSet; } foreach (int vid in vertexSet) { if (signs[vid] > 0 && Mesh.IsVertex(vid)) { Mesh.RemoveVertex(vid, true, false); } } // ok now we extract boundary loops, but restricted // to either the zero-edges we found, or the edges we created! bang!! Func <int, bool> CutEdgeFilterF = (eid) => { if (OnCutEdges.Contains(eid) || ZeroEdges.Contains(eid)) { return(true); } return(false); }; try { MeshBoundaryLoops loops = new MeshBoundaryLoops(Mesh, false); loops.EdgeFilterF = CutEdgeFilterF; loops.Compute(); CutLoops = loops.Loops; CutSpans = loops.Spans; CutLoopsFailed = false; FoundOpenSpans = CutSpans.Count > 0; } catch { CutLoops = new List <EdgeLoop>(); CutLoopsFailed = true; } return(true); } // Cut()
protected void compute_full(IEnumerable <int> Triangles, bool bIsFullMeshHint = false) { Graph = new DGraph3(); if (WantGraphEdgeInfo) { GraphEdges = new DVector <GraphEdgeInfo>(); } Vertices = new Dictionary <Vector3d, int>(); // multithreaded precomputation of per-vertex values double[] vertex_values = null; if (PrecomputeVertexValues) { vertex_values = new double[Mesh.MaxVertexID]; IEnumerable <int> verts = Mesh.VertexIndices(); if (bIsFullMeshHint == false) { MeshVertexSelection vertices = new MeshVertexSelection(Mesh); vertices.SelectTriangleVertices(Triangles); verts = vertices; } gParallel.ForEach(verts, (vid) => { vertex_values[vid] = ValueF(Mesh.GetVertex(vid)); }); VertexValueF = (vid) => { return(vertex_values[vid]); }; } foreach (int tid in Triangles) { Vector3dTuple3 tv = new Vector3dTuple3(); Mesh.GetTriVertices(tid, ref tv.V0, ref tv.V1, ref tv.V2); Index3i triVerts = Mesh.GetTriangle(tid); Vector3d f = (VertexValueF != null) ? new Vector3d(VertexValueF(triVerts.a), VertexValueF(triVerts.b), VertexValueF(triVerts.c)) : new Vector3d(ValueF(tv.V0), ValueF(tv.V1), ValueF(tv.V2)); // round f to 0 within epsilon? if (f.x < 0 && f.y < 0 && f.z < 0) { continue; } if (f.x > 0 && f.y > 0 && f.z > 0) { continue; } Index3i triEdges = Mesh.GetTriEdges(tid); if (f.x * f.y * f.z == 0) { int z0 = (f.x == 0) ? 0 : ((f.y == 0) ? 1 : 2); int i1 = (z0 + 1) % 3, i2 = (z0 + 2) % 3; if (f[i1] * f[i2] > 0) { continue; // single-vertex-crossing case, skip here and let other edges catch it } if (f[i1] == 0 || f[i2] == 0) { // on-edge case int z1 = f[i1] == 0 ? i1 : i2; if ((z0 + 1) % 3 != z1) { int tmp = z0; z0 = z1; z1 = tmp; // catch reverse-orientation cases } int e0 = add_or_append_vertex(Mesh.GetVertex(triVerts[z0])); int e1 = add_or_append_vertex(Mesh.GetVertex(triVerts[z1])); int graph_eid = Graph.AppendEdge(e0, e1, (int)TriangleCase.OnEdge); if (graph_eid >= 0 && WantGraphEdgeInfo) { add_on_edge(graph_eid, tid, triEdges[z0], new Index2i(e0, e1)); } } else { // edge/vertex case Util.gDevAssert(f[i1] * f[i2] < 0); int vert_vid = add_or_append_vertex(Mesh.GetVertex(triVerts[z0])); int i = i1, j = i2; if (triVerts[j] < triVerts[i]) { int tmp = i; i = j; j = tmp; } Vector3d cross = find_crossing(tv[i], tv[j], f[i], f[j]); int cross_vid = add_or_append_vertex(cross); add_edge_pos(triVerts[i], triVerts[j], cross); int graph_eid = Graph.AppendEdge(vert_vid, cross_vid, (int)TriangleCase.EdgeVertex); if (graph_eid >= 0 && WantGraphEdgeInfo) { add_edge_vert(graph_eid, tid, triEdges[(z0 + 1) % 3], triVerts[z0], new Index2i(vert_vid, cross_vid)); } } } else { Index3i cross_verts = Index3i.Min; int less_than = 0; for (int tei = 0; tei < 3; ++tei) { int i = tei, j = (tei + 1) % 3; if (f[i] < 0) { less_than++; } if (f[i] * f[j] > 0) { continue; } if (triVerts[j] < triVerts[i]) { int tmp = i; i = j; j = tmp; } Vector3d cross = find_crossing(tv[i], tv[j], f[i], f[j]); cross_verts[tei] = add_or_append_vertex(cross); add_edge_pos(triVerts[i], triVerts[j], cross); } int e0 = (cross_verts.a == int.MinValue) ? 1 : 0; int e1 = (cross_verts.c == int.MinValue) ? 1 : 2; if (e0 == 0 && e1 == 2) // preserve orientation order { e0 = 2; e1 = 0; } // preserving orientation does not mean we get a *consistent* orientation across faces. // To do that, we need to assign "sides". Either we have 1 less-than-0 or 1 greater-than-0 vtx. // Arbitrary decide that we want loops oriented like bdry loops would be if we discarded less-than side. // In that case, when we are "cutting off" one vertex, orientation would end up flipped if (less_than == 1) { int tmp = e0; e0 = e1; e1 = tmp; } int ev0 = cross_verts[e0]; int ev1 = cross_verts[e1]; // [RMS] if function is garbage, we can end up w/ case where both crossings // happen at same vertex, even though values are not the same (eg if // some values are double.MaxValue). We will just fail in these cases. if (ev0 != ev1) { Util.gDevAssert(ev0 != int.MinValue && ev1 != int.MinValue); int graph_eid = Graph.AppendEdge(ev0, ev1, (int)TriangleCase.EdgeEdge); if (graph_eid >= 0 && WantGraphEdgeInfo) { add_edge_edge(graph_eid, tid, new Index2i(triEdges[e0], triEdges[e1]), new Index2i(ev0, ev1)); } } } } Vertices = null; }
public virtual bool Extrude() { MeshNormals normals = null; bool bHaveNormals = Mesh.HasVertexNormals; if (!bHaveNormals) { normals = new MeshNormals(Mesh); normals.Compute(); } InitialLoops = new MeshBoundaryLoops(Mesh); InitialTriangles = Mesh.TriangleIndices().ToArray(); InitialVertices = Mesh.VertexIndices().ToArray(); // duplicate triangles of mesh InitialToOffsetMapV = new IndexMap(Mesh.MaxVertexID, Mesh.MaxVertexID); OffsetGroupID = OffsetGroup.GetGroupID(Mesh); var editor = new MeshEditor(Mesh); OffsetTriangles = editor.DuplicateTriangles(InitialTriangles, ref InitialToOffsetMapV, OffsetGroupID); // set vertices to new positions foreach (int vid in InitialVertices) { int newvid = InitialToOffsetMapV[vid]; if (!Mesh.IsVertex(newvid)) { continue; } Vector3d v = Mesh.GetVertex(vid); Vector3f n = (bHaveNormals) ? Mesh.GetVertexNormal(vid) : (Vector3f)normals.Normals[vid]; Vector3d newv = ExtrudedPositionF(v, n, vid); Mesh.SetVertex(newvid, newv); } // we need to reverse one side if (IsPositiveOffset) { editor.ReverseTriangles(InitialTriangles); } else { editor.ReverseTriangles(OffsetTriangles); } // stitch each loop NewLoops = new EdgeLoop[InitialLoops.Count]; StitchTriangles = new int[InitialLoops.Count][]; StitchGroupIDs = new int[InitialLoops.Count]; int li = 0; foreach (var loop in InitialLoops) { int[] loop2 = new int[loop.VertexCount]; for (int k = 0; k < loop2.Length; ++k) { loop2[k] = InitialToOffsetMapV[loop.Vertices[k]]; } StitchGroupIDs[li] = StitchGroups.GetGroupID(Mesh); if (IsPositiveOffset) { StitchTriangles[li] = editor.StitchLoop(loop2, loop.Vertices, StitchGroupIDs[li]); } else { StitchTriangles[li] = editor.StitchLoop(loop.Vertices, loop2, StitchGroupIDs[li]); } NewLoops[li] = EdgeLoop.FromVertices(Mesh, loop2); li++; } return(true); }