public void AddFillLoop(EdgeLoop loop) { Loops.Add(new FillLoop() { edgeLoop = loop }); }
// 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(); }
// 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 void UpdateLoop(EdgeLoop loop) { InputLoop = loop; OutputLoop = null; CurrentLoopE = new List <int>(loop.Edges); CurrentLoopV = new List <int>(loop.Vertices); }
public virtual bool Extrude(int group_id = -1) { // duplicate loop vertices int NV = Loop.Vertices.Length; NewLoop = new EdgeLoop(Mesh); NewLoop.Vertices = new int[NV]; for (int i = 0; i < NV; ++i) { int vid = Loop.Vertices[i]; NewLoop.Vertices[i] = Mesh.AppendVertex(Mesh, vid); } // move to offset positions for (int i = 0; i < NV; ++i) { Vector3d v = Mesh.GetVertex(Loop.Vertices[i]); Vector3f n = Mesh.GetVertexNormal(Loop.Vertices[i]); Vector3d new_v = PositionF(v, n, i); Mesh.SetVertex(NewLoop.Vertices[i], new_v); } // stitch interior MeshEditor edit = new MeshEditor(Mesh); NewTriangles = edit.StitchLoop(Loop.Vertices, NewLoop.Vertices, group_id); return(true); }
public SimpleHoleFiller(DMesh3 mesh, EdgeLoop loop) { Mesh = mesh; Loop = loop; NewVertex = DMesh3.InvalidID; NewTriangles = null; }
public EdgeLoopRemesher(DMesh3 m, EdgeLoop loop) : base(m) { UpdateLoop(loop); EnableFlips = false; CustomSmoothF = loop_smooth_vertex; }
/// <summary> /// Generally after calling Apply(), we have over-triangulated the mesh, because we have split /// the original edges multiple times, etc. This function will walk the edges and collapse /// the unnecessary edges/vertices along the inserted loops. /// </summary> public void Simplify() { for (int k = 0; k < Loops.Count; ++k) { EdgeLoop newloop = simplify(Loops[k]); Loops[k] = newloop; } }
public MeshLoopSmooth(DMesh3 mesh, EdgeLoop loop) { Mesh = mesh; Loop = loop; SmoothedPostions = new Vector3d[Loop.Vertices.Length]; ProjectF = null; }
public MeshExtrudeLoop(DMesh3 mesh, EdgeLoop loop) { Mesh = mesh; Loop = loop; PositionF = (pos, normal, idx) => { return(pos + Vector3d.AxisY); }; }
public EdgeLoop(EdgeLoop copy) { Mesh = copy.Mesh; Vertices = new int[copy.Vertices.Length]; Array.Copy(copy.Vertices, Vertices, Vertices.Length); Edges = new int[copy.Edges.Length]; Array.Copy(copy.Edges, Edges, Edges.Length); BowtieVertices = new int[copy.BowtieVertices.Length]; Array.Copy(copy.BowtieVertices, BowtieVertices, BowtieVertices.Length); }
void find_cut_paths(HashSet <int> CutEdges) { Spans = new List <EdgeSpan>(); Loops = new List <EdgeLoop>(); // [TODO] what about if vert appears more than twice in list? we should check for that! var Remaining = new HashSet <int>(CutEdges); while (Remaining.Count > 0) { int start_edge = Remaining.First(); Remaining.Remove(start_edge); Index2i start_edge_v = Mesh.GetEdgeV(start_edge); bool isLoop; List <int> forwardSpan = walk_edge_span_forward(Mesh, start_edge, start_edge_v.a, Remaining, out isLoop); if (isLoop == false) { List <int> backwardSpan = walk_edge_span_forward(Mesh, start_edge, start_edge_v.b, Remaining, out isLoop); if (isLoop) { throw new Exception("find_cut_paths: how did this possibly happen?!?"); } if (backwardSpan.Count > 1) { backwardSpan.Reverse(); backwardSpan.RemoveAt(backwardSpan.Count - 1); backwardSpan.AddRange(forwardSpan); Index2i start_ev = Mesh.GetEdgeV(backwardSpan[0]); Index2i end_ev = Mesh.GetEdgeV(backwardSpan[backwardSpan.Count - 1]); // [RMS] >2 check here catches two-edge span case, where we do have shared vert but // can never be loop unless we have duplicate edge (!) isLoop = backwardSpan.Count > 2 && IndexUtil.find_shared_edge_v(ref start_ev, ref end_ev) != DMesh3.InvalidID; forwardSpan = backwardSpan; } } if (isLoop) { var loop = EdgeLoop.FromEdges(Mesh, forwardSpan); Util.gDevAssert(loop.CheckValidity()); Loops.Add(loop); } else { var span = EdgeSpan.FromEdges(Mesh, forwardSpan); Util.gDevAssert(span.CheckValidity()); Spans.Add(span); } } }
// Check if Loop2 is the same set of positions on another mesh. // Does not require the indexing to be the same // Currently doesn't handle loop-reversal public bool IsSameLoop(EdgeLoop Loop2, bool bReverse2 = false, double tolerance = MathUtil.ZeroTolerance) { // find a duplicate starting vertex int N = Vertices.Length; int N2 = Loop2.Vertices.Length; if (N != N2) { return(false); } DMesh3 Mesh2 = Loop2.Mesh; int start_i = 0, start_j = -1; // try to find a unique same-vertex on each loop. Do not // use vertices that have duplicate positions. bool bFoundGoodStart = false; while (!bFoundGoodStart && start_i < N) { Vector3d start_v = Mesh.GetVertex(start_i); int count = Loop2.CountWithinTolerance(start_v, tolerance, out start_j); if (count == 1) { bFoundGoodStart = true; } else { start_i++; } } if (!bFoundGoodStart) { return(false); // no within-tolerance duplicate vtx to start at } for (int ii = 0; ii < N; ++ii) { int i = (start_i + ii) % N; int j = (bReverse2) ? MathUtil.WrapSignedIndex(start_j - ii, N2) : (start_j + ii) % N2; Vector3d v = Mesh.GetVertex(Vertices[i]); Vector3d v2 = Mesh2.GetVertex(Loop2.Vertices[j]); if (v.Distance(v2) > tolerance) { return(false); } } return(true); }
// 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)); }
void compute_polygons() { Bounds = AxisAlignedBox2d.Empty; for (int i = 0; i < Loops.Count; ++i) { EdgeLoop loop = Loops[i].edgeLoop; Polygon2d poly = new Polygon2d(); foreach (int vid in loop.Vertices) { Vector2d v = to2D(Mesh.GetVertex(vid)); poly.AppendVertex(v); } Loops[i].poly = poly; Bounds.Contain(poly.Bounds); } }
public static ValidationStatus IsBoundaryLoop(DMesh3 mesh, EdgeLoop loop) { int N = loop.Vertices.Length; for (int i = 0; i < N; ++i) { if (!mesh.vertex_is_boundary(loop.Vertices[i])) { return(ValidationStatus.NotBoundaryVertex); } } for (int i = 0; i < N; ++i) { int a = loop.Vertices[i]; int b = loop.Vertices[(i + 1) % N]; int eid = mesh.FindEdge(a, b); if (eid == DMesh3.InvalidID) { return(ValidationStatus.VerticesNotConnectedByEdge); } if (mesh.edge_is_boundary(eid) == false) { return(ValidationStatus.NotBoundaryEdge); } Index2i ev = mesh.GetOrientedBoundaryEdgeV(eid); if (!(ev.a == a && ev.b == b)) { return(ValidationStatus.IncorrectLoopOrientation); } } return(ValidationStatus.Ok); }
public static ValidationStatus IsEdgeLoop(DMesh3 mesh, EdgeLoop loop) { int N = loop.Vertices.Length; for (int i = 0; i < N; ++i) { if (!mesh.IsVertex(loop.Vertices[i])) { return(ValidationStatus.NotAVertex); } } for (int i = 0; i < N; ++i) { int a = loop.Vertices[i]; int b = loop.Vertices[(i + 1) % N]; int eid = mesh.FindEdge(a, b); if (eid == DMesh3.InvalidID) { return(ValidationStatus.VerticesNotConnectedByEdge); } } return(ValidationStatus.Ok); }
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); }
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); }
/// <summary> /// given EdgeLoop on MeshA, and vertex map from A to B, map to EdgeLoop on B /// </summary> public static EdgeLoop MapLoopViaVertexMap(IIndexMap AtoBV, DMesh3 MeshA, DMesh3 MeshB, EdgeLoop loopIn) { int NV = loopIn.VertexCount, NE = loopIn.EdgeCount; int[] newVerts = new int[NV]; for (int i = 0; i < NV; ++i) { newVerts[i] = AtoBV[loopIn.Vertices[i]]; } int[] newEdges = new int[NE]; for (int i = 0; i < NE; ++i) { int eid_a = loopIn.Edges[i]; Index2i aev = MeshA.GetEdgeV(eid_a); int bev0 = AtoBV[aev.a]; int bev1 = AtoBV[aev.b]; newEdges[i] = MeshB.FindEdge(bev0, bev1); Debug.Assert(newEdges[i] != DMesh3.InvalidID); } return(new EdgeLoop(MeshB, newVerts, newEdges, false)); }
// 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 Exception("extract_subloops: argh"); } 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.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.BowtieVertices = bowties.ToArray(); subs.Add(loop); } return(subs); }
// This algorithm assumes that triangles are oriented consistently, // so boundary-loop can be followed public bool Compute() { Loops = new List <EdgeLoop>(); 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]; // 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.edge_is_boundary(eid) == false) { 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; while (!bClosed) { Index2i ev = Mesh.GetOrientedBoundaryEdgeV(eCur); int cure_a = ev.a, cure_b = ev.b; loop_verts.Add(cure_a); int e0 = -1, e1 = 1; int bdry_nbrs = Mesh.VtxBoundaryEdges(cure_b, ref e0, ref e1); if (bdry_nbrs < 2) { throw new Exception("MeshBoundaryLoops.Compute: found broken neighbourhood at vertex " + cure_b); } 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[bdry_nbrs]; } int num_be = Mesh.VtxAllBoundaryEdges(cure_b, all_e); Debug.Assert(num_be == bdry_nbrs); // 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) { throw new Exception("MeshBoundaryLoops.Compute: cannot find valid outgoing edge at bowtie vertex " + cure_b); } } if (bowties.Contains(cure_b) == false) { bowties.Add(cure_b); } } else { 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 { // push onto accumulated list Debug.Assert(used_edge[eNext] == false); loop_edges.Add(eNext); eCur = eNext; used_edge[eCur] = true; } } // if we saw a bowtie vertex, we might need to break up this loop, // so call extract_subloops if (bowties.Count > 0) { 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); }
protected override void end_pass() { OutputLoop = new EdgeLoop(mesh, CurrentLoopV.ToArray(), CurrentLoopE.ToArray(), false); }
public MeshLoopClosure(DMesh3 mesh, EdgeLoop border_loop) { Mesh = mesh; InitialBorderLoop = border_loop; }
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); }
// 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 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); }