/// <summary> /// for each vertex of input triangle set, select vertex if all /// one-ring triangles are contained in triangle set (ie vertex is not on boundary of triangle set). /// </summary> public void SelectInteriorVertices(MeshFaceSelection triangles) { var borderv = new HashSet <int>(); foreach (int tid in triangles) { Index3i tv = Mesh.GetTriangle(tid); for (int j = 0; j < 3; ++j) { int vid = tv[j]; if (Selected.Contains(vid) || borderv.Contains(vid)) { continue; } bool full_ring = true; foreach (int ring_tid in Mesh.VtxTrianglesItr(vid)) { if (triangles.IsSelected(ring_tid) == false) { full_ring = false; break; } } if (full_ring) { add(vid); } else { borderv.Add(vid); } } } }
// 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(); } }
public MeshFaceSelection(MeshFaceSelection copy) { Mesh = copy.Mesh; Selected = new HashSet <int>(copy.Selected); temp = new List <int>(); temp2 = new List <int>(); }
// This function does local remeshing around a boundary loop within a fixed # of // rings, to try to 'massage' it into a cleaner shape/topology // [TODO] use geodesic distance instead of fixed # of rings? public static void cleanup_boundary(DMesh3 mesh, EdgeLoop loop, double target_edge_len, int nRings = 3) { Debug.Assert(loop.IsBoundaryLoop()); MeshFaceSelection roi = new MeshFaceSelection(mesh); roi.SelectVertexOneRings(loop.Vertices); for (int i = 0; i < nRings; ++i) { roi.ExpandToOneRingNeighbours(); } roi.LocalOptimize(true, true); RegionRemesher r = new RegionRemesher(mesh, roi.ToArray()); r.Precompute(); r.EnableFlips = r.EnableSplits = r.EnableCollapses = true; r.MinEdgeLength = target_edge_len; r.MaxEdgeLength = 2 * target_edge_len; r.EnableSmoothing = true; r.SmoothSpeedT = 0.1f; for (int k = 0; k < nRings * 3; ++k) { r.BasicRemeshPass(); } Debug.Assert(mesh.CheckValidity()); r.BackPropropagate(); }
public void SelectTriangleVertices(MeshFaceSelection triangles) { foreach (int tid in triangles) { Index3i tri = Mesh.GetTriangle(tid); add(tri.a); add(tri.b); add(tri.c); } }
public MeshFaceSelection ToSelection() { MeshFaceSelection selection = new MeshFaceSelection(Mesh); //selection.Select(PathT); selection.Select(InteriorT); return(selection); }
// convert face selection to vertex selection. public MeshVertexSelection(DMesh3 mesh, MeshFaceSelection convertT) : this(mesh) { foreach (int tid in convertT) { Index3i tv = mesh.GetTriangle(tid); add(tv.a); add(tv.b); add(tv.c); } }
// assumes PathT contains set of triangles // that are fully connected, ie a flood-fill cannot escape! void find_interior_from_seed(int tSeed) { MeshFaceSelection selection = new MeshFaceSelection(Mesh); selection.Select(PathT); selection.FloodFill(tSeed); InteriorT = new List <int>(selection); }
/// <summary> /// Automatically construct fastest projection target for region of mesh /// </summary> public static MeshProjectionTarget Auto(DMesh3 mesh, IEnumerable <int> triangles, int nExpandRings = 5) { var targetRegion = new MeshFaceSelection(mesh); targetRegion.Select(triangles); targetRegion.ExpandToOneRingNeighbours(nExpandRings); var submesh = new DSubmesh3(mesh, targetRegion); return(new MeshProjectionTarget(submesh.SubMesh)); }
/// <summary> /// Actually computes the insertion. In some cases we would like more info /// coming back than we get by using Generate() api. Note that resulting /// mesh is *not* compacted. /// </summary> public DMesh3 ComputeResult(out MeshInsertPolygon insertion) { AxisAlignedBox2d bounds = Polygon.Bounds; double padding = 0.1 * bounds.DiagonalLength; bounds.Expand(padding); TrivialRectGenerator rectgen = (Subdivisions == 1) ? new TrivialRectGenerator() : new GriddedRectGenerator() { EdgeVertices = Subdivisions }; rectgen.Width = (float)bounds.Width; rectgen.Height = (float)bounds.Height; rectgen.IndicesMap = new Index2i(1, 2); rectgen.UVMode = UVMode; rectgen.Clockwise = true; // MeshPolygonInserter assumes mesh faces are CW? (except code says CCW...) rectgen.Generate(); var base_mesh = new DMesh3(); rectgen.MakeMesh(base_mesh); var shiftPolygon = new GeneralPolygon2d(Polygon); Vector2d shift = bounds.Center; shiftPolygon.Translate(-shift); var insert = new MeshInsertPolygon() { Mesh = base_mesh, Polygon = shiftPolygon }; bool bOK = insert.Insert(); if (!bOK) { throw new Exception("TriangulatedPolygonGenerator: failed to Insert()"); } MeshFaceSelection selected = insert.InteriorTriangles; var editor = new MeshEditor(base_mesh); editor.RemoveTriangles((tid) => { return(selected.IsSelected(tid) == false); }, true); var shift3 = new Vector3d(shift.x, shift.y, 0); MeshTransforms.Translate(base_mesh, shift3); insertion = insert; return(base_mesh); }
// This function does local remeshing around a boundary loop within a fixed # of // rings, to try to 'massage' it into a cleaner shape/topology. // The result_edges list is the mapped edges of loop on the resulting mesh, but it is *not* in-order // [TODO] use geodesic distance instead of fixed # of rings? public static void cleanup_boundary(DMesh3 mesh, EdgeLoop loop, double target_edge_len, out List <int> result_edges, int nRings = 3) { Debug.Assert(loop.IsBoundaryLoop()); var roi = new MeshFaceSelection(mesh); roi.SelectVertexOneRings(loop.Vertices); for (int i = 0; i < nRings; ++i) { roi.ExpandToOneRingNeighbours(); } roi.LocalOptimize(true, true); var r = new RegionRemesher(mesh, roi.ToArray()); // tag the input loop edges in the remesher, so that we can find this loop afterwards int[] init_loop_edges = new int[loop.EdgeCount]; Array.Copy(loop.Edges, init_loop_edges, loop.EdgeCount); r.Region.MapEdgesToSubmesh(init_loop_edges); MeshConstraintUtil.AddTrackedEdges(r.Constraints, init_loop_edges, 100); //foreach (int eid in init_loop_edges) // Debug.Assert(r.Region.SubMesh.IsBoundaryEdge(eid)); r.Precompute(); r.EnableFlips = r.EnableSplits = r.EnableCollapses = true; r.MinEdgeLength = target_edge_len; r.MaxEdgeLength = 2 * target_edge_len; r.EnableSmoothing = true; r.SmoothSpeedT = 0.1f; for (int k = 0; k < nRings * 3; ++k) { r.BasicRemeshPass(); } Debug.Assert(mesh.CheckValidity()); // extract the edges we tagged (they are unordered) List <int> new_loop_edges = r.Constraints.FindConstrainedEdgesBySetID(100); //foreach (int eid in new_loop_edges) // Debug.Assert(r.Region.SubMesh.IsBoundaryEdge(eid)); r.BackPropropagate(); // map the extracted edges back to the backpropped input mesh result_edges = MeshIndexUtil.MapEdgesViaVertexMap(r.ReinsertSubToBaseMapV, r.Region.SubMesh, r.BaseMesh, new_loop_edges); //foreach (int eid in result_edges) // Debug.Assert(mesh.IsBoundaryEdge(eid)); }
public void SelectBoundaryTriEdges(MeshFaceSelection triangles) { foreach (int tid in triangles) { Index3i te = Mesh.GetTriEdges(tid); for (int j = 0; j < 3; ++j) { Index2i et = Mesh.GetEdgeT(te[j]); int other_tid = (et.a == tid) ? et.b : et.a; if (triangles.IsSelected(other_tid) == false) { add(te[j]); } } } }
// assumes PathT is contains set of triangles // that are fully connected, ie a flood-fill cannot escape! void find_interior_from_tris() { var pathNbrs = new MeshFaceSelection(Mesh); pathNbrs.Select(PathT); pathNbrs.ExpandToOneRingNeighbours(); pathNbrs.Deselect(PathT); var connected = new MeshConnectedComponents(Mesh); connected.FilterSet = pathNbrs; connected.FindConnectedT(); int N = connected.Count; if (N < 2) { throw new Exception("MeshFacesFromLoop.find_interior: only found one connected component!"); } // only consider 2 largest. somehow we are sometimes getting additional // "outside" components, and if we do growing from there, it covers whole mesh?? connected.SortByCount(false); N = 2; var selections = new MeshFaceSelection[N]; bool[] done = new bool[N]; for (int i = 0; i < N; ++i) { selections[i] = new MeshFaceSelection(Mesh); selections[i].Select(connected.Components[i].Indices); done[i] = false; } var border_tris = new HashSet <int>(PathT); Func <int, bool> borderF = (tid) => { return(border_tris.Contains(tid) == false); }; // 'largest' flood fill is expensive...if we had a sense of tooth size we could reduce cost? for (int i = 0; i < N; ++i) { selections[i].FloodFill(connected.Components[i].Indices, borderF); } Array.Sort(selections, (a, b) => { return(a.Count.CompareTo(b.Count)); }); InteriorT = new List <int>(selections[0]); }
// 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(); }
// convert face selection to edge selection. Require at least minCount tris of edge to be selected public MeshEdgeSelection(DMesh3 mesh, MeshFaceSelection convertT, int minCount = 1) : this(mesh) { minCount = MathUtil.Clamp(minCount, 1, 2); if (minCount == 1) { foreach (int tid in convertT) { Index3i te = mesh.GetTriEdges(tid); add(te.a); add(te.b); add(te.c); } } else { foreach (int eid in mesh.EdgeIndices()) { Index2i et = mesh.GetEdgeT(eid); if (convertT.IsSelected(et.a) && convertT.IsSelected(et.b)) { add(eid); } } } }
public virtual bool Trim() { if (Spatial == null) { Spatial = new DMeshAABBTree3(new DMesh3(Mesh, false, MeshComponents.None)); Spatial.Build(); } if (seed_tri == -1) { seed_tri = Spatial.FindNearestTriangle(seed_pt); } var loop = new MeshFacesFromLoop(Mesh, TrimLine, Spatial, seed_tri); MeshFaceSelection selection = loop.ToSelection(); selection.LocalOptimize(true, true); var editor = new MeshEditor(Mesh); editor.RemoveTriangles(selection, true); var components = new MeshConnectedComponents(Mesh); components.FindConnectedT(); if (components.Count > 1) { int keep = components.LargestByCount; for (int i = 0; i < components.Count; ++i) { if (i != keep) { editor.RemoveTriangles(components[i].Indices, true); } } } editor.RemoveAllBowtieVertices(true); var loops = new MeshBoundaryLoops(Mesh); bool loopsOK = false; try { loopsOK = loops.Compute(); } catch (Exception) { return(false); } if (!loopsOK) { return(false); } // [TODO] to support trimming mesh w/ existing holes, we need to figure out which // loop we created in RemoveTriangles above! if (loops.Count > 1) { return(false); } int[] loopVerts = loops[0].Vertices; var borderTris = new MeshFaceSelection(Mesh); borderTris.SelectVertexOneRings(loopVerts); borderTris.ExpandToOneRingNeighbours(RemeshBorderRings); var remesh = new RegionRemesher(Mesh, borderTris.ToArray()); remesh.Region.MapVerticesToSubmesh(loopVerts); double target_len = TargetEdgeLength; if (target_len <= 0) { double mine, maxe, avge; MeshQueries.EdgeLengthStatsFromEdges(Mesh, loops[0].Edges, out mine, out maxe, out avge); target_len = avge; } var meshTarget = new MeshProjectionTarget(Spatial.Mesh, Spatial); remesh.SetProjectionTarget(meshTarget); remesh.SetTargetEdgeLength(target_len); remesh.SmoothSpeedT = SmoothingAlpha; var curveTarget = new DCurveProjectionTarget(TrimLine); var multiTarget = new SequentialProjectionTarget(curveTarget, meshTarget); int set_id = 3; MeshConstraintUtil.ConstrainVtxLoopTo(remesh, loopVerts, multiTarget, set_id); for (int i = 0; i < RemeshRounds; ++i) { remesh.BasicRemeshPass(); } remesh.BackPropropagate(); // [TODO] output loop somehow...use MeshConstraints.FindConstrainedEdgesBySetID(set_id)... return(true); } // Trim()
/// <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 void Close_Flat() { double minlen, maxlen, avglen; MeshQueries.EdgeLengthStats(Mesh, out minlen, out maxlen, out avglen, 1000); double target_edge_len = (TargetEdgeLen <= 0) ? avglen : TargetEdgeLen; // massage around boundary loop List <int> refinedBorderEdges; cleanup_boundary(Mesh, InitialBorderLoop, avglen, out refinedBorderEdges, 3); // find new border loop. try to find new loop containing edges from loop we refined in cleanup_boundary, // if that fails just use largest loop. MeshBoundaryLoops loops = new MeshBoundaryLoops(Mesh); int iloop = loops.FindLoopContainingEdge(refinedBorderEdges[0]); if (iloop == -1) { iloop = loops.MaxVerticesLoopIndex; } EdgeLoop fill_loop = loops.Loops[iloop]; int extrude_group = (ExtrudeGroup == -1) ? Mesh.AllocateTriangleGroup() : ExtrudeGroup; int fill_group = (FillGroup == -1) ? Mesh.AllocateTriangleGroup() : FillGroup; // decide on projection plane //AxisAlignedBox3d loopbox = fill_loop.GetBounds(); //Vector3d topPt = loopbox.Center; //if ( bIsUpper ) { // topPt.y = loopbox.Max.y + 0.25 * dims.y; //} else { // topPt.y = loopbox.Min.y - 0.25 * dims.y; //} //Frame3f plane = new Frame3f((Vector3f)topPt); // extrude loop to this plane MeshExtrudeLoop extrude = new MeshExtrudeLoop(Mesh, fill_loop); extrude.PositionF = (v, n, i) => { return(FlatClosePlane.ProjectToPlane((Vector3f)v, 1)); }; extrude.Extrude(extrude_group); MeshValidation.IsBoundaryLoop(Mesh, extrude.NewLoop); Debug.Assert(Mesh.CheckValidity()); // smooth the extrude loop MeshLoopSmooth loop_smooth = new MeshLoopSmooth(Mesh, extrude.NewLoop); loop_smooth.ProjectF = (v, i) => { return(FlatClosePlane.ProjectToPlane((Vector3f)v, 1)); }; loop_smooth.Alpha = 0.5f; loop_smooth.Rounds = 100; loop_smooth.Smooth(); Debug.Assert(Mesh.CheckValidity()); // fill result SimpleHoleFiller filler = new SimpleHoleFiller(Mesh, extrude.NewLoop); filler.Fill(fill_group); Debug.Assert(Mesh.CheckValidity()); // make selection for remesh region MeshFaceSelection remesh_roi = new MeshFaceSelection(Mesh); remesh_roi.Select(extrude.NewTriangles); remesh_roi.Select(filler.NewTriangles); remesh_roi.ExpandToOneRingNeighbours(); remesh_roi.ExpandToOneRingNeighbours(); remesh_roi.LocalOptimize(true, true); int[] new_roi = remesh_roi.ToArray(); // get rid of extrude group FaceGroupUtil.SetGroupToGroup(Mesh, extrude_group, 0); /* clean up via remesh * - constrain loop we filled to itself */ RegionRemesher r = new RegionRemesher(Mesh, new_roi); DCurve3 top_curve = MeshUtil.ExtractLoopV(Mesh, extrude.NewLoop.Vertices); DCurveProjectionTarget curve_target = new DCurveProjectionTarget(top_curve); int[] top_loop = (int[])extrude.NewLoop.Vertices.Clone(); r.Region.MapVerticesToSubmesh(top_loop); MeshConstraintUtil.ConstrainVtxLoopTo(r.Constraints, r.Mesh, top_loop, curve_target); DMeshAABBTree3 spatial = new DMeshAABBTree3(Mesh); spatial.Build(); MeshProjectionTarget target = new MeshProjectionTarget(Mesh, spatial); r.SetProjectionTarget(target); bool bRemesh = true; if (bRemesh) { r.Precompute(); r.EnableFlips = r.EnableSplits = r.EnableCollapses = true; r.MinEdgeLength = target_edge_len; r.MaxEdgeLength = 2 * target_edge_len; r.EnableSmoothing = true; r.SmoothSpeedT = 1.0f; for (int k = 0; k < 40; ++k) { r.BasicRemeshPass(); } r.SetProjectionTarget(null); r.SmoothSpeedT = 0.25f; for (int k = 0; k < 10; ++k) { r.BasicRemeshPass(); } Debug.Assert(Mesh.CheckValidity()); r.BackPropropagate(); } // smooth around the join region to clean up ugliness smooth_region(Mesh, r.Region.BaseBorderV, 3); }
public bool Insert() { OuterInsert = new MeshInsertUVPolyCurve(Mesh, Polygon.Outer); Util.gDevAssert(OuterInsert.Validate() == ValidationStatus.Ok); bool outerApplyOK = OuterInsert.Apply(); if (outerApplyOK == false || OuterInsert.Loops.Count == 0) { return(false); } if (SimplifyInsertion) { OuterInsert.Simplify(); } HoleInserts = new List <MeshInsertUVPolyCurve>(Polygon.Holes.Count); for (int hi = 0; hi < Polygon.Holes.Count; ++hi) { var insert = new MeshInsertUVPolyCurve(Mesh, Polygon.Holes[hi]); Util.gDevAssert(insert.Validate() == ValidationStatus.Ok); insert.Apply(); if (SimplifyInsertion) { insert.Simplify(); } HoleInserts.Add(insert); } // find a triangle connected to loop that is inside the polygon // [TODO] maybe we could be a bit more robust about this? at least // check if triangle is too degenerate... int seed_tri = -1; EdgeLoop outer_loop = OuterInsert.Loops[0]; for (int i = 0; i < outer_loop.EdgeCount; ++i) { if (!Mesh.IsEdge(outer_loop.Edges[i])) { continue; } Index2i et = Mesh.GetEdgeT(outer_loop.Edges[i]); Vector3d ca = Mesh.GetTriCentroid(et.a); bool in_a = Polygon.Outer.Contains(ca.xy); Vector3d cb = Mesh.GetTriCentroid(et.b); bool in_b = Polygon.Outer.Contains(cb.xy); if (in_a && in_b == false) { seed_tri = et.a; break; } else if (in_b && in_a == false) { seed_tri = et.b; break; } } if (seed_tri == -1) { throw new Exception("MeshPolygonsInserter: could not find seed triangle!"); } // make list of all outer & hole edges InsertedPolygonEdges = new HashSet <int>(outer_loop.Edges); foreach (var insertion in HoleInserts) { foreach (int eid in insertion.Loops[0].Edges) { InsertedPolygonEdges.Add(eid); } } // flood-fill inside loop from seed triangle InteriorTriangles = new MeshFaceSelection(Mesh); InteriorTriangles.FloodFill(seed_tri, null, (eid) => { return(InsertedPolygonEdges.Contains(eid) == false); }); return(true); }