// smooths embedded loop in mesh, by first smoothing edge loop and then // smoothing vertex neighbourhood // [TODO] geodesic nbrhoold instead of # of rings // [TODO] reprojection? public static void smooth_loop(DMesh3 mesh, EdgeLoop loop, int nRings) { MeshFaceSelection roi_t = new MeshFaceSelection(mesh); roi_t.SelectVertexOneRings(loop.Vertices); for (int i = 0; i < nRings; ++i) { roi_t.ExpandToOneRingNeighbours(); } roi_t.LocalOptimize(true, true); MeshVertexSelection roi_v = new MeshVertexSelection(mesh); roi_v.SelectTriangleVertices(roi_t.ToArray()); roi_v.Deselect(loop.Vertices); MeshLoopSmooth loop_smooth = new MeshLoopSmooth(mesh, loop); loop_smooth.Rounds = 1; MeshIterativeSmooth mesh_smooth = new MeshIterativeSmooth(mesh, roi_v.ToArray(), true); mesh_smooth.Rounds = 1; for (int i = 0; i < 10; ++i) { loop_smooth.Smooth(); mesh_smooth.Smooth(); } }
// convert vertex selection to face selection. Require at least minCount verts of // tri to be selected (valid values are 1,2,3) public MeshFaceSelection(DMesh3 mesh, MeshVertexSelection convertV, int minCount = 3) : this(mesh) { minCount = MathUtil.Clamp(minCount, 1, 3); foreach (int tid in mesh.TriangleIndices()) { Index3i tri = mesh.GetTriangle(tid); if (minCount == 1) { if (convertV.IsSelected(tri.a) || convertV.IsSelected(tri.b) || convertV.IsSelected(tri.c)) { add(tid); } } else if (minCount == 3) { if (convertV.IsSelected(tri.a) && convertV.IsSelected(tri.b) && convertV.IsSelected(tri.c)) { add(tid); } } else { int n = (convertV.IsSelected(tri.a) ? 1 : 0) + (convertV.IsSelected(tri.b) ? 1 : 0) + (convertV.IsSelected(tri.c) ? 1 : 0); if (n >= minCount) { add(tid); } } } }
protected override void begin_smooth() { base.begin_smooth(); if (LocalSmoothingRings > 0) { smoothV.Clear(); if (LocalSmoothingRings == 1) { for (int i = 0; i < CurrentLoopV.Count; ++i) { smoothV.Add(CurrentLoopV[i]); foreach (int nbrv in mesh.VtxVerticesItr(CurrentLoopV[i])) { smoothV.Add(nbrv); } } } else { MeshVertexSelection select = new MeshVertexSelection(mesh); select.Select(CurrentLoopV); select.ExpandToOneRingNeighbours(LocalSmoothingRings); foreach (int vid in select) { smoothV.Add(vid); } } } }
void insert_segment(IntersectSegment seg) { List <int> subfaces = get_all_baseface_tris(seg.base_tid); var op = new RegionOperator(Target, subfaces); Vector3d n = BaseFaceNormals[seg.base_tid]; Vector3d c = BaseFaceCentroids[seg.base_tid]; Vector3d e0, e1; Vector3d.MakePerpVectors(ref n, out e0, out e1); DMesh3 mesh = op.Region.SubMesh; MeshTransforms.PerVertexTransform(mesh, (v) => { v -= c; return(new Vector3d(v.Dot(e0), v.Dot(e1), 0)); }); Vector3d end0 = seg.v0.v, end1 = seg.v1.v; end0 -= c; end1 -= c; var p0 = new Vector2d(end0.Dot(e0), end0.Dot(e1)); var p1 = new Vector2d(end1.Dot(e0), end1.Dot(e1)); var path = new PolyLine2d(); path.AppendVertex(p0); path.AppendVertex(p1); var insert = new MeshInsertUVPolyCurve(mesh, path); insert.Apply(); var cutVerts = new MeshVertexSelection(mesh); cutVerts.SelectEdgeVertices(insert.OnCutEdges); MeshTransforms.PerVertexTransform(mesh, (v) => { return(c + v.x * e0 + v.y * e1); }); op.BackPropropagate(); // add new cut vertices to cut list foreach (int vid in cutVerts) { SegmentInsertVertices.Add(op.ReinsertSubToBaseMapV[vid]); } add_regionop_subfaces(seg.base_tid, op); }
// convert vertex selection to edge selection. Require at least minCount verts of edge to be selected public MeshEdgeSelection(DMesh3 mesh, MeshVertexSelection convertV, int minCount = 2) : this(mesh) { minCount = MathUtil.Clamp(minCount, 1, 2); // [TODO] if minCount == 1, and convertV is small, it is faster to iterate over convertV!! foreach (int eid in mesh.EdgeIndices()) { Index2i ev = mesh.GetEdgeV(eid); int n = (convertV.IsSelected(ev.a) ? 1 : 0) + (convertV.IsSelected(ev.b) ? 1 : 0); if (n >= minCount) { add(eid); } } }
// local mesh smooth applied to all vertices in N-rings around input list public static void smooth_region(DMesh3 mesh, IEnumerable <int> vertices, int nRings) { MeshFaceSelection roi_t = new MeshFaceSelection(mesh); roi_t.SelectVertexOneRings(vertices); for (int i = 0; i < nRings; ++i) { roi_t.ExpandToOneRingNeighbours(); } roi_t.LocalOptimize(true, true); MeshVertexSelection roi_v = new MeshVertexSelection(mesh); roi_v.SelectTriangleVertices(roi_t.ToArray()); MeshIterativeSmooth mesh_smooth = new MeshIterativeSmooth(mesh, roi_v.ToArray(), true); mesh_smooth.Alpha = 0.2f; mesh_smooth.Rounds = 10; mesh_smooth.Smooth(); }
public virtual bool Extrude() { MeshEditor editor = new MeshEditor(Mesh); editor.SeparateTriangles(Triangles, true, out EdgePairs); MeshNormals normals = null; bool bHaveNormals = Mesh.HasVertexNormals; if (!bHaveNormals) { normals = new MeshNormals(Mesh); normals.Compute(); } ExtrudeVertices = new MeshVertexSelection(Mesh); ExtrudeVertices.SelectTriangleVertices(Triangles); Vector3d[] NewVertices = new Vector3d[ExtrudeVertices.Count]; int k = 0; foreach (int vid in ExtrudeVertices) { Vector3d v = Mesh.GetVertex(vid); Vector3f n = (bHaveNormals) ? Mesh.GetVertexNormal(vid) : (Vector3f)normals.Normals[vid]; NewVertices[k++] = ExtrudedPositionF(v, n, vid); } k = 0; foreach (int vid in ExtrudeVertices) { Mesh.SetVertex(vid, NewVertices[k++]); } SetGroupID = Group.GetGroupID(Mesh); JoinTriangles = editor.StitchUnorderedEdges(EdgePairs, SetGroupID); return(true); }
protected bool check_for_cracks(DMesh3 mesh, out int boundary_edge_count, double crack_tol = MathUtil.ZeroTolerancef) { boundary_edge_count = 0; var boundary_verts = new MeshVertexSelection(mesh); foreach (int eid in mesh.BoundaryEdgeIndices()) { Index2i ev = mesh.GetEdgeV(eid); boundary_verts.Select(ev.a); boundary_verts.Select(ev.b); boundary_edge_count++; } if (boundary_verts.Count == 0) { return(false); } AxisAlignedBox3d bounds = mesh.CachedBounds; var borderV = new PointHashGrid3d <int>(bounds.MaxDim / 128, -1); foreach (int vid in boundary_verts) { Vector3d v = mesh.GetVertex(vid); var result = borderV.FindNearestInRadius(v, crack_tol, (existing_vid) => { return(v.Distance(mesh.GetVertex(existing_vid))); }); if (result.Key != -1) { return(true); // we found a crack vertex! } borderV.InsertPoint(vid, v); } // found no cracks return(false); }
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; }
/// <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); }
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()