// gets slow for small res factor... public static DMesh3 MakeRemeshedCappedCylinder(double fResFactor = 1.0) { DMesh3 mesh = TestUtil.MakeCappedCylinder(false, 128); MeshUtil.ScaleMesh(mesh, Frame3f.Identity, new Vector3f(1, 2, 1)); // 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); if (fAngle > 30.0f) { 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, nSetID0)); cons.SetOrUpdateVertexConstraint(ev[1], new VertexConstraint(true, nSetID1)); } } Remesher r = new Remesher(mesh); r.SetExternalConstraints(cons); r.SetProjectionTarget(target); r.Precompute(); r.EnableFlips = r.EnableSplits = r.EnableCollapses = true; r.MinEdgeLength = 0.1f * fResFactor; r.MaxEdgeLength = 0.2f * fResFactor; r.EnableSmoothing = true; r.SmoothSpeedT = 0.5f; for (int k = 0; k < 20; ++k) { r.BasicRemeshPass(); } return(mesh); }
/// <summary> /// given list of edges of MeshA, and vertex map from A to B, map to list of edges on B /// </summary> public static List <int> MapEdgesViaVertexMap(IIndexMap AtoBV, NGonsCore.geometry3Sharp.mesh.DMesh3 MeshA, NGonsCore.geometry3Sharp.mesh.DMesh3 MeshB, List <int> edges) { int N = edges.Count; List <int> result = new List <int>(N); for (int i = 0; i < N; ++i) { int eid_a = edges[i]; Index2i aev = MeshA.GetEdgeV(eid_a); int bev0 = AtoBV[aev.a]; int bev1 = AtoBV[aev.b]; int eid_b = MeshB.FindEdge(bev0, bev0); Debug.Assert(eid_b != NGonsCore.geometry3Sharp.mesh.DMesh3.InvalidID); result.Add(eid_b); } return(result); }
// After remeshing we may create an internal edge between two boundary vertices [a,b]. // Those vertices will be merged with vertices c and d in the base mesh. If the edge // [c,d] already exists in the base mesh, then after the merge we would have at least // 3 triangles at this edge. Dang. // // A common example is a 'fin' triangle that would duplicate a // 'fin' on the border of the base mesh after removing the submesh, but this situation can // arise anywhere (eg think about one-triangle-wide strips). // // This is very hard to remove, but we can at least avoid creating non-manifold edges (which // with the current DMesh3 will be prevented, hence leaving a hole) by splitting the // internal edge in the submesh (which presumably we were remeshing anyway, so changes are ok). public void RepairPossibleNonManifoldEdges() { // [TODO] do we need to repeat this more than once? I don't think so... // repair submesh int NE = Region.SubMesh.MaxEdgeID; List <int> split_edges = new List <int>(); for (int eid = 0; eid < NE; ++eid) { if (Region.SubMesh.IsEdge(eid) == false) { continue; } if (Region.SubMesh.IsBoundaryEdge(eid)) { continue; } Index2i edgev = Region.SubMesh.GetEdgeV(eid); if (Region.SubMesh.Vertex_is_boundary(edgev.a) && Region.SubMesh.Vertex_is_boundary(edgev.b)) { // ok, we have an internal edge where both verts are on the boundary // now check if it is an edge in the base mesh int base_a = Region.MapVertexToBaseMesh(edgev.a); int base_b = Region.MapVertexToBaseMesh(edgev.b); if (base_a != NGonsCore.geometry3Sharp.mesh.DMesh3.InvalidID && base_b != NGonsCore.geometry3Sharp.mesh.DMesh3.InvalidID) { // both vertices in base mesh...right? Debug.Assert(Region.BaseMesh.IsVertex(base_a) && Region.BaseMesh.IsVertex(base_b)); int base_eid = Region.BaseMesh.FindEdge(base_a, base_b); if (base_eid != NGonsCore.geometry3Sharp.mesh.DMesh3.InvalidID) { split_edges.Add(eid); } } } } // split any problem edges we found and repeat this loop for (int i = 0; i < split_edges.Count; ++i) { NGonsCore.geometry3Sharp.mesh.DMesh3.EdgeSplitInfo split_info; Region.SubMesh.SplitEdge(split_edges[i], out split_info); } }
public void Debug_print_vertex(int v) { System.Console.WriteLine("Vertex " + v.ToString()); List <int> tris = new List <int>(); GetVtxTriangles(v, tris, false); System.Console.WriteLine(string.Format(" Tris {0} Edges {1} refcount {2}", tris.Count, GetVtxEdges(v).Count, vertices_refcount.RefCount(v))); foreach (int t in tris) { Index3i tv = GetTriangle(t), te = GetTriEdges(t); System.Console.WriteLine(string.Format(" t{6} {0} {1} {2} te {3} {4} {5}", tv[0], tv[1], tv[2], te[0], te[1], te[2], t)); } foreach (int e in GetVtxEdges(v)) { Index2i ev = GetEdgeV(e), et = GetEdgeT(e); System.Console.WriteLine(string.Format(" e{4} {0} {1} / {2} {3}", ev[0], ev[1], et[0], et[1], e)); } }
bool collapse_degenerate_edges( double minLength, bool bBoundaryOnly, out int collapseCount) { collapseCount = 0; // don't iterate sequentially because there may be pathological cases foreach (int eid in MathUtil.ModuloIteration(Mesh.MaxEdgeID)) { if (Cancelled()) { break; } if (Mesh.IsEdge(eid) == false) { continue; } bool is_boundary_edge = Mesh.IsBoundaryEdge(eid); if (bBoundaryOnly && is_boundary_edge == false) { continue; } Index2i ev = Mesh.GetEdgeV(eid); Vector3d a = Mesh.GetVertex(ev.a), b = Mesh.GetVertex(ev.b); if (a.Distance(b) < minLength) { int keep = Mesh.IsBoundaryVertex(ev.a) ? ev.a : ev.b; int discard = (keep == ev.a) ? ev.b : ev.a; DMesh3.EdgeCollapseInfo collapseInfo; MeshResult result = Mesh.CollapseEdge(keep, discard, out collapseInfo); if (result == MeshResult.Ok) { ++collapseCount; if (Mesh.IsBoundaryVertex(keep) == false || is_boundary_edge) { Mesh.SetVertex(keep, (a + b) * 0.5); } } } } return(true); }
// for all mesh boundary edges, disable flip/split/collapse // for all mesh boundary vertices, pin in current position public static void FixAllGroupBoundaryEdges(MeshConstraints cons, NGonsCore.geometry3Sharp.mesh.DMesh3 mesh, bool bPinVertices) { int NE = mesh.MaxEdgeID; for (int ei = 0; ei < NE; ++ei) { if (mesh.IsEdge(ei) && mesh.IsGroupBoundaryEdge(ei)) { cons.SetOrUpdateEdgeConstraint(ei, EdgeConstraint.FullyConstrained); if (bPinVertices) { Index2i ev = mesh.GetEdgeV(ei); cons.SetOrUpdateVertexConstraint(ev.a, VertexConstraint.Pinned); cons.SetOrUpdateVertexConstraint(ev.b, VertexConstraint.Pinned); } } } }
// for all mesh boundary vertices, pin in current position, but allow collapses public static void FixAllBoundaryEdges_AllowCollapse(MeshConstraints cons, NGonsCore.geometry3Sharp.mesh.DMesh3 mesh, int setID) { EdgeConstraint edgeCons = new EdgeConstraint(EdgeRefineFlags.NoFlip | EdgeRefineFlags.NoSplit); VertexConstraint vertCons = new VertexConstraint(true, setID); int NE = mesh.MaxEdgeID; for (int ei = 0; ei < NE; ++ei) { if (mesh.IsEdge(ei) && mesh.IsBoundaryEdge(ei)) { cons.SetOrUpdateEdgeConstraint(ei, edgeCons); Index2i ev = mesh.GetEdgeV(ei); cons.SetOrUpdateVertexConstraint(ev.a, vertCons); cons.SetOrUpdateVertexConstraint(ev.b, vertCons); } } }
// Support for ordering a set of unique indices into the vertex pool. On // output it is guaranteed that: v0 < v1 < v2. This is used to guarantee // consistent queries when the vertex ordering of a primitive is permuted, // a necessity when using floating-point arithmetic that suffers from // numerical round-off errors. The input indices are considered the // positive ordering. The output indices are either positively ordered // (an even number of transpositions occurs during sorting) or negatively // ordered (an odd number of transpositions occurs during sorting). The // functions return 'true' for a positive ordering and 'false' for a // negative ordering. bool Sort(ref int v0, ref int v1) { int j0, j1; bool positive; if (v0 < v1) { j0 = 0; j1 = 1; positive = true; } else { j0 = 1; j1 = 0; positive = false; } Index2i value = new Index2i(v0, v1); v0 = value[j0]; v1 = value[j1]; return(positive); }
private bool CollapseDegenerateEdges(DMesh3 mesh, CancellationToken cancellationToken, double minLength, bool bBoundaryOnly, out int collapseCount) { collapseCount = 0; // don't iterate sequentially because there may be pathological cases foreach (int eid in MathUtil.ModuloIteration(mesh.MaxEdgeID)) { cancellationToken.ThrowIfCancellationRequested(); if (mesh.IsEdge(eid) == false) { continue; } bool isBoundaryEdge = mesh.IsBoundaryEdge(eid); if (bBoundaryOnly && isBoundaryEdge == false) { continue; } Index2i ev = mesh.GetEdgeV(eid); Vector3d a = mesh.GetVertex(ev.a), b = mesh.GetVertex(ev.b); if (a.Distance(b) < minLength) { int keep = mesh.IsBoundaryVertex(ev.a) ? ev.a : ev.b; int discard = (keep == ev.a) ? ev.b : ev.a; MeshResult result = mesh.CollapseEdge(keep, discard, out DMesh3.EdgeCollapseInfo collapseInfo); if (result == MeshResult.Ok) { ++collapseCount; if (mesh.IsBoundaryVertex(keep) == false || isBoundaryEdge) { mesh.SetVertex(keep, (a + b) * 0.5); } } } } return(true); }
public virtual int[] AddTriangleFan_OrderedEdgeLoop(int center, int[] edge_loop, int group_id = -1) { int N = edge_loop.Length; int[] new_tris = new int[N]; int i = 0; for (i = 0; i < N; ++i) { if (Mesh.IsBoundaryEdge(edge_loop[i]) == false) { goto operation_failed; } Index2i ev = Mesh.GetOrientedBoundaryEdgeV(edge_loop[i]); int a = ev.a, b = ev.b; Index3i newT = new Index3i(center, b, a); int new_tid = Mesh.AppendTriangle(newT, group_id); if (new_tid == NGonsCore.geometry3Sharp.mesh.DMesh3.InvalidID) { goto operation_failed; } new_tris[i] = new_tid; } return(new_tris); operation_failed: // remove what we added so far if (i > 0) { if (Remove_triangles(new_tris, i - 1) == false) { throw new Exception("MeshConstructor.AddTriangleFan_OrderedEdgeLoop: failed to add fan, and also failed to back out changes."); } } return(null); }
public void ComputeBoundaryInfo(IEnumerable <int> triangles, int tri_count_est) { // set of base-mesh triangles that are in submesh IndexFlagSet sub_tris = new IndexFlagSet(BaseMesh.MaxTriangleID, tri_count_est); foreach (int ti in triangles) { sub_tris[ti] = true; } BaseBorderV = new IndexHashSet(); BaseBorderE = new IndexHashSet(); BaseBoundaryE = new IndexHashSet(); // Iterate through edges in submesh roi on base mesh. If // one of the tris of the edge is not in submesh roi, then this // is a boundary edge. // // (edge iteration via triangle iteration processes each internal edge twice...) foreach (int ti in triangles) { Index3i tedges = BaseMesh.GetTriEdges(ti); for (int j = 0; j < 3; ++j) { int eid = tedges[j]; Index2i tris = BaseMesh.GetEdgeT(eid); if (tris.b == NGonsCore.geometry3Sharp.mesh.DMesh3.InvalidID) // this is a boundary edge { BaseBoundaryE[eid] = true; } else if (sub_tris[tris.a] != sub_tris[tris.b]) // this is a border edge { BaseBorderE[eid] = true; Index2i ve = BaseMesh.GetEdgeV(eid); BaseBorderV[ve.a] = true; BaseBorderV[ve.b] = true; } } } }
public MeshRegionBoundaryLoops(NGonsCore.geometry3Sharp.mesh.DMesh3 mesh, int[] RegionTris, bool bAutoCompute = true) { this.Mesh = mesh; // make flag set for included triangles triangles = new IndexFlagSet(mesh.MaxTriangleID, RegionTris.Length); for (int i = 0; i < RegionTris.Length; ++i) { triangles[RegionTris[i]] = true; } // make flag set for included edges // NOTE: this currently processes non-boundary-edges twice. Could // avoid w/ another IndexFlagSet, but the check is inexpensive... edges = new IndexFlagSet(mesh.MaxEdgeID, RegionTris.Length); for (int i = 0; i < RegionTris.Length; ++i) { int tid = RegionTris[i]; Index3i te = Mesh.GetTriEdges(tid); for (int j = 0; j < 3; ++j) { int eid = te[j]; if (!edges.Contains(eid)) { Index2i et = mesh.GetEdgeT(eid); if (et.b == NGonsCore.geometry3Sharp.mesh.DMesh3.InvalidID || triangles[et.a] != triangles[et.b]) { edges.Add(eid); } } } } if (bAutoCompute) { Compute(); } }
/// <summary> /// specify subset of vertices that have known correspondences. /// </summary> public void AddKnownCorrespondences(int[] verts0, int[] verts1) { int N = verts0.Length; if (N != verts1.Length) { throw new Exception("MeshStitchLoops.AddKnownCorrespondence: lengths not the same!"); } // construct list of pair correspondences as loop indices List <Index2i> pairs = new List <Index2i>(); for (int k = 0; k < N; ++k) { int i0 = Loop0.FindVertexIndex(verts0[k]); int i1 = Loop1.FindVertexIndex(verts1[k]); pairs.Add(new Index2i(i0, i1)); } // sort by increasing index in loop0 (arbitrary) pairs.Sort((pair1, pair2) => { return(pair1.a.CompareTo(pair2.a)); }); // now construct spans List <span> new_spans = new List <span>(); for (int k = 0; k < pairs.Count; ++k) { Index2i p1 = pairs[k]; Index2i p2 = pairs[(k + 1) % pairs.Count]; span s = new span() { span0 = new Interval1i(p1.a, p2.a), span1 = new Interval1i(p1.b, p2.b) }; new_spans.Add(s); } spans = new_spans; }
static int find_pair_edge(DMesh3 mesh, int eid, List <int> candidates) { Index2i ev = mesh.GetEdgeV(eid); Vector3d a = mesh.GetVertex(ev.a), b = mesh.GetVertex(ev.b); double eps = 100 * MathUtil.Epsilonf; foreach (int eother in candidates) { if (eother == eid) { continue; } Index2i ov = mesh.GetEdgeV(eother); Vector3d c = mesh.GetVertex(ov.a), d = mesh.GetVertex(ov.b); if ((a.EpsilonEqual(c, eps) && b.EpsilonEqual(d, eps)) || (b.EpsilonEqual(c, eps) && a.EpsilonEqual(d, eps))) { return(eother); } } ; return(DMesh3.InvalidID); }
public static ValidationStatus IsBoundaryLoop(NGonsCore.geometry3Sharp.mesh.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 == NGonsCore.geometry3Sharp.mesh.DMesh3.InvalidID) { return(ValidationStatus.VerticesNotConnectedByEdge); } if (mesh.IsBoundaryEdge(eid) == false) { return(ValidationStatus.NotBoundaryEdge); } Index2i ev = mesh.GetOrientedBoundaryEdgeV(eid); if (!(ev.a == a && ev.b == b)) { return(ValidationStatus.IncorrectLoopOrientation); } } return(ValidationStatus.Ok); }
UseFillType classify_hole() { return(UseFillType.MinimalFill); #if false int NV = FillLoop.VertexCount; int NE = FillLoop.EdgeCount; Vector3d size = FillLoop.ToCurve().GetBoundingBox().Diagonal; NormalHistogram hist = new NormalHistogram(4096, true); for (int k = 0; k < NE; ++k) { int eid = FillLoop.Edges[k]; Index2i et = Mesh.GetEdgeT(eid); Vector3d n = Mesh.GetTriNormal(et.a); hist.Count(n, 1.0, true); } if (hist.UsedBins.Count == 1) { return(UseFillType.PlanarFill); } //int nontrivial_bins = 0; //foreach ( int bin in hist.UsedBins ) { // if (hist.Counts[bin] > 8) // nontrivial_bins++; //} //if (nontrivial_bins > 0) // return UseFillType.PlanarSpansFill; return(UseFillType.SmoothFill); #endif }
// this function collapses edges until it can't anymore static void collapse_to_convergence(DMesh3 mesh) { bool bContinue = true; while (bContinue) { bContinue = false; for (int eid = 0; eid < mesh.MaxEdgeID; ++eid) { if (!mesh.IsEdge(eid)) { continue; } Index2i ev = mesh.GetEdgeV(eid); DMesh3.EdgeCollapseInfo collapseInfo; MeshResult result = mesh.CollapseEdge(ev[0], ev[1], out collapseInfo); if (result == MeshResult.Ok) { bContinue = true; break; } } } }
public bool Apply() { // do a simple fill SimpleHoleFiller simplefill = new SimpleHoleFiller(Mesh, FillLoop); int fill_gid = Mesh.AllocateTriangleGroup(); bool bOK = simplefill.Fill(fill_gid); if (bOK == false) { return(false); } if (FillLoop.Vertices.Length <= 3) { FillTriangles = simplefill.NewTriangles; FillVertices = new int[0]; return(true); } // extract the simple fill mesh as a submesh, via RegionOperator, so we can backsub later HashSet <int> intial_fill_tris = new HashSet <int>(simplefill.NewTriangles); regionop = new RegionOperator(Mesh, simplefill.NewTriangles, (submesh) => { submesh.ComputeTriMaps = true; }); fillmesh = regionop.Region.SubMesh; // for each boundary vertex, compute the exterior angle sum // we will use this to compute gaussian curvature later boundaryv = new HashSet <int>(MeshIterators.BoundaryEdgeVertices(fillmesh)); exterior_angle_sums = new Dictionary <int, double>(); if (IgnoreBoundaryTriangles == false) { foreach (int sub_vid in boundaryv) { double angle_sum = 0; int base_vid = regionop.Region.MapVertexToBaseMesh(sub_vid); foreach (int tid in regionop.BaseMesh.VtxTrianglesItr(base_vid)) { if (intial_fill_tris.Contains(tid) == false) { Index3i et = regionop.BaseMesh.GetTriangle(tid); int idx = IndexUtil.find_tri_index(base_vid, ref et); angle_sum += regionop.BaseMesh.GetTriInternalAngleR(tid, idx); } } exterior_angle_sums[sub_vid] = angle_sum; } } // try to guess a reasonable edge length that will give us enough geometry to work with in simplify pass double loop_mine, loop_maxe, loop_avge, fill_mine, fill_maxe, fill_avge; MeshQueries.EdgeLengthStatsFromEdges(Mesh, FillLoop.Edges, out loop_mine, out loop_maxe, out loop_avge); MeshQueries.EdgeLengthStats(fillmesh, out fill_mine, out fill_maxe, out fill_avge); double remesh_target_len = loop_avge; if (fill_maxe / remesh_target_len > 10) { remesh_target_len = fill_maxe / 10; } //double remesh_target_len = Math.Min(loop_avge, fill_avge / 4); // remesh up to target edge length, ideally gives us some triangles to work with RemesherPro remesh1 = new RemesherPro(fillmesh); remesh1.SmoothSpeedT = 1.0; MeshConstraintUtil.FixAllBoundaryEdges(remesh1); //remesh1.SetTargetEdgeLength(remesh_target_len / 2); // would this speed things up? on large regions? //remesh1.FastestRemesh(); remesh1.SetTargetEdgeLength(remesh_target_len); remesh1.FastestRemesh(); /* * first round: collapse to minimal mesh, while flipping to try to * get to ballpark minimal mesh. We stop these passes as soon as * we have done two rounds where we couldn't do another collapse * * This is the most unstable part of the algorithm because there * are strong ordering effects. maybe we could sort the edges somehow?? */ int zero_collapse_passes = 0; int collapse_passes = 0; while (collapse_passes++ < 20 && zero_collapse_passes < 2) { // collapse pass int NE = fillmesh.MaxEdgeID; int collapses = 0; for (int ei = 0; ei < NE; ++ei) { if (fillmesh.IsEdge(ei) == false || fillmesh.IsBoundaryEdge(ei)) { continue; } Index2i ev = fillmesh.GetEdgeV(ei); bool a_bdry = boundaryv.Contains(ev.a), b_bdry = boundaryv.Contains(ev.b); if (a_bdry && b_bdry) { continue; } int keepv = (a_bdry) ? ev.a : ev.b; int otherv = (keepv == ev.a) ? ev.b : ev.a; Vector3d newv = fillmesh.GetVertex(keepv); if (MeshUtil.CheckIfCollapseCreatesFlip(fillmesh, ei, newv)) { continue; } DMesh3.EdgeCollapseInfo info; MeshResult result = fillmesh.CollapseEdge(keepv, otherv, out info); if (result == MeshResult.Ok) { collapses++; } } if (collapses == 0) { zero_collapse_passes++; } else { zero_collapse_passes = 0; } // flip pass. we flip in these cases: // 1) if angle between current triangles is too small (slightly more than 90 degrees, currently) // 2) if angle between flipped triangles is smaller than between current triangles // 3) if flipped edge length is shorter *and* such a flip won't flip the normal NE = fillmesh.MaxEdgeID; Vector3d n1, n2, on1, on2; for (int ei = 0; ei < NE; ++ei) { if (fillmesh.IsEdge(ei) == false || fillmesh.IsBoundaryEdge(ei)) { continue; } bool do_flip = false; Index2i ev = fillmesh.GetEdgeV(ei); MeshUtil.GetEdgeFlipNormals(fillmesh, ei, out n1, out n2, out on1, out on2); double dot_cur = n1.Dot(n2); double dot_flip = on1.Dot(on2); if (n1.Dot(n2) < 0.1 || dot_flip > dot_cur + MathUtil.Epsilonf) { do_flip = true; } if (do_flip == false) { Index2i otherv = fillmesh.GetEdgeOpposingV(ei); double len_e = fillmesh.GetVertex(ev.a).Distance(fillmesh.GetVertex(ev.b)); double len_flip = fillmesh.GetVertex(otherv.a).Distance(fillmesh.GetVertex(otherv.b)); if (len_flip < len_e) { if (MeshUtil.CheckIfEdgeFlipCreatesFlip(fillmesh, ei) == false) { do_flip = true; } } } if (do_flip) { DMesh3.EdgeFlipInfo info; MeshResult result = fillmesh.FlipEdge(ei, out info); } } } // Sometimes, for some reason, we have a remaining interior vertex (have only ever seen one?) // Try to force removal of such vertices, even if it makes ugly mesh remove_remaining_interior_verts(); // enable/disable passes. bool DO_FLATTER_PASS = true; bool DO_CURVATURE_PASS = OptimizeDevelopability && true; bool DO_AREA_PASS = OptimizeDevelopability && OptimizeTriangles && true; /* * In this pass we repeat the flipping iterations from the previous pass. * * Note that because of the always-flip-if-dot-is-small case (commented), * this pass will frequently not converge, as some number of edges will * be able to flip back and forth (because neither has large enough dot). * This is not ideal, but also, if we remove this behavior, then we * generally get worse fills. This case basically introduces a sort of * randomization factor that lets us escape local minima... * */ HashSet <int> remaining_edges = new HashSet <int>(fillmesh.EdgeIndices()); HashSet <int> updated_edges = new HashSet <int>(); int flatter_passes = 0; int zero_flips_passes = 0; while (flatter_passes++ < 40 && zero_flips_passes < 2 && remaining_edges.Count() > 0 && DO_FLATTER_PASS) { zero_flips_passes++; foreach (int ei in remaining_edges) { if (fillmesh.IsBoundaryEdge(ei)) { continue; } bool do_flip = false; Index2i ev = fillmesh.GetEdgeV(ei); Vector3d n1, n2, on1, on2; MeshUtil.GetEdgeFlipNormals(fillmesh, ei, out n1, out n2, out on1, out on2); double dot_cur = n1.Dot(n2); double dot_flip = on1.Dot(on2); if (flatter_passes < 20 && dot_cur < 0.1) // this check causes oscillatory behavior { do_flip = true; } if (dot_flip > dot_cur + MathUtil.Epsilonf) { do_flip = true; } if (do_flip) { DMesh3.EdgeFlipInfo info; MeshResult result = fillmesh.FlipEdge(ei, out info); if (result == MeshResult.Ok) { zero_flips_passes = 0; add_all_edges(ei, updated_edges); } } } var tmp = remaining_edges; remaining_edges = updated_edges; updated_edges = tmp; updated_edges.Clear(); } int curvature_passes = 0; if (DO_CURVATURE_PASS) { curvatures = new double[fillmesh.MaxVertexID]; foreach (int vid in fillmesh.VertexIndices()) { update_curvature(vid); } remaining_edges = new HashSet <int>(fillmesh.EdgeIndices()); updated_edges = new HashSet <int>(); /* * In this pass we try to minimize gaussian curvature at all the vertices. * This will recover sharp edges, etc, and do lots of good stuff. * However, this pass will not make much progress if we are not already * relatively close to a minimal mesh, so it really relies on the previous * passes getting us in the ballpark. */ while (curvature_passes++ < 40 && remaining_edges.Count() > 0 && DO_CURVATURE_PASS) { foreach (int ei in remaining_edges) { if (fillmesh.IsBoundaryEdge(ei)) { continue; } Index2i ev = fillmesh.GetEdgeV(ei); Index2i ov = fillmesh.GetEdgeOpposingV(ei); int find_other = fillmesh.FindEdge(ov.a, ov.b); if (find_other != DMesh3.InvalidID) { continue; } double total_curv_cur = curvature_metric_cached(ev.a, ev.b, ov.a, ov.b); if (total_curv_cur < MathUtil.ZeroTolerancef) { continue; } DMesh3.EdgeFlipInfo info; MeshResult result = fillmesh.FlipEdge(ei, out info); if (result != MeshResult.Ok) { continue; } double total_curv_flip = curvature_metric_eval(ev.a, ev.b, ov.a, ov.b); bool keep_flip = total_curv_flip < total_curv_cur - MathUtil.ZeroTolerancef; if (keep_flip == false) { result = fillmesh.FlipEdge(ei, out info); } else { update_curvature(ev.a); update_curvature(ev.b); update_curvature(ov.a); update_curvature(ov.b); add_all_edges(ei, updated_edges); } } var tmp = remaining_edges; remaining_edges = updated_edges; updated_edges = tmp; updated_edges.Clear(); } } //System.Console.WriteLine("collapse {0} flatter {1} curvature {2}", collapse_passes, flatter_passes, curvature_passes); /* * In this final pass, we try to improve triangle quality. We flip if * the flipped triangles have better total aspect ratio, and the * curvature doesn't change **too** much. The .DevelopabilityTolerance * parameter determines what is "too much" curvature change. */ if (DO_AREA_PASS) { remaining_edges = new HashSet <int>(fillmesh.EdgeIndices()); updated_edges = new HashSet <int>(); int area_passes = 0; while (remaining_edges.Count() > 0 && area_passes < 20) { area_passes++; foreach (int ei in remaining_edges) { if (fillmesh.IsBoundaryEdge(ei)) { continue; } Index2i ev = fillmesh.GetEdgeV(ei); Index2i ov = fillmesh.GetEdgeOpposingV(ei); int find_other = fillmesh.FindEdge(ov.a, ov.b); if (find_other != DMesh3.InvalidID) { continue; } double total_curv_cur = curvature_metric_cached(ev.a, ev.b, ov.a, ov.b); double a = aspect_metric(ei); if (a > 1) { continue; } DMesh3.EdgeFlipInfo info; MeshResult result = fillmesh.FlipEdge(ei, out info); if (result != MeshResult.Ok) { continue; } double total_curv_flip = curvature_metric_eval(ev.a, ev.b, ov.a, ov.b); bool keep_flip = Math.Abs(total_curv_cur - total_curv_flip) < DevelopabilityTolerance; if (keep_flip == false) { result = fillmesh.FlipEdge(ei, out info); } else { update_curvature(ev.a); update_curvature(ev.b); update_curvature(ov.a); update_curvature(ov.b); add_all_edges(ei, updated_edges); } } var tmp = remaining_edges; remaining_edges = updated_edges; updated_edges = tmp; updated_edges.Clear(); } } regionop.BackPropropagate(); FillTriangles = regionop.CurrentBaseTriangles; FillVertices = regionop.CurrentBaseInteriorVertices().ToArray(); 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>(); 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.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; 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); // 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) { throw new MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: found broken neighbourhood at vertex " + cure_b) { UnclosedLoop = true } } ; 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); if (EdgeFilterF != null) { num_be = BufferUtil.FilterInPlace(all_e, EdgeFilterF, num_be); } 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 MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: cannot find valid outgoing edge at bowtie vertex " + cure_b) { BowtieFailure = true } } ; } 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); } // [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 = math.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; } } 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); } }
private static DGraph2 CreateMinGraph(DGraph2 input, int NV, out Dictionary <int, List <int> > minEdgePaths, out DVector <double> edgeWeights) { /* * OK, as input we have a graph of our original polygon and a bunch of inserted * segments ("spans"). Orig polygon segments have gid < 0, and span segments >= 0. * However between polygon/span junctions, we have an arbitrary # of polygon edges. * So first step is to simplify these to single-edge "connectors", in new graph MinGraph. * the [connector-edge, path] mappings (if pathlen > 1) are stored in MinEdgePaths * We also store a weight for each connector edge in EdgeWeights (just distance for now) */ var minGraph = new DGraph2(); minEdgePaths = new Dictionary <int, List <int> >(); edgeWeights = new DVector <double>(); edgeWeights.resize(NV); BitArray done_edge = new BitArray(input.MaxEdgeID); // we should see each edge twice, this avoids repetition // vertex map from input graph to MinGraph int[] MapV = new int[NV]; for (int i = 0; i < NV; ++i) { MapV[i] = -1; } for (int a = 0; a < NV; ++a) { if (input.IsVertex(a) == false || input.IsJunctionVertex(a) == false) { continue; } if (MapV[a] == -1) { MapV[a] = minGraph.AppendVertex(input.GetVertex(a)); } foreach (int eid in input.VtxEdgesItr(a)) { if (done_edge[eid]) { continue; } Index2i ev = input.GetEdgeV(eid); int b = (ev.a == a) ? ev.b : ev.a; if (input.IsJunctionVertex(b)) { // if we have junction/juntion connection, we can just copy this edge to MinGraph if (MapV[b] == -1) { MapV[b] = minGraph.AppendVertex(input.GetVertex(b)); } int gid = input.GetEdgeGroup(eid); int existing = minGraph.FindEdge(MapV[a], MapV[b]); if (existing == DMesh3.InvalidID) { int new_eid = minGraph.AppendEdge(MapV[a], MapV[b], gid); double path_len = input.GetEdgeSegment(eid).Length; edgeWeights.insertAt(path_len, new_eid); } else { // we may have inserted this edge already in the simplify branch, this happens eg at the // edge of a circle where the minimal path is between the same vertices as the segment. // But if this is also a fill edge, we want to treat it that way (determind via positive gid) if (gid >= 0) { minGraph.SetEdgeGroup(existing, gid); } } } else { // not a junction - walk until we find other vtx, and add single edge to MinGraph List <int> path = DGraph2Util.WalkToNextNonRegularVtx(input, a, eid); if (path == null || path.Count < 2) { throw new Exception("build_min_graph: invalid walk!"); } int c = path[path.Count - 1]; // it is somehow possible to get loops... if (c == a) { goto skip_this_edge; } if (MapV[c] == -1) { MapV[c] = minGraph.AppendVertex(input.GetVertex(c)); } if (minGraph.FindEdge(MapV[a], MapV[c]) == DMesh3.InvalidID) { int new_eid = minGraph.AppendEdge(MapV[a], MapV[c], -2); path.Add(MapV[a]); path.Add(MapV[c]); minEdgePaths[new_eid] = path; double path_len = DGraph2Util.PathLength(input, path); edgeWeights.insertAt(path_len, new_eid); } } skip_this_edge: done_edge[eid] = true; } } return(minGraph); }
private FillCurveSet2d WalkPathGraph(DGraph2 pathGraph) { var boundaries = IdentifyBoundaryHashSet(pathGraph); var paths = new FillCurveSet2d(); // walk paths from boundary vertices while (boundaries.Count > 0) { int start_vid = boundaries.First(); boundaries.Remove(start_vid); int vid = start_vid; int eid = pathGraph.GetVtxEdges(vid)[0]; var path = new FillCurve <FillSegment>() { FillType = this.FillType }; path.BeginCurve(pathGraph.GetVertex(vid)); while (true) { Index2i next = DGraph2Util.NextEdgeAndVtx(eid, vid, pathGraph); eid = next.a; vid = next.b; int gid = pathGraph.GetEdgeGroup(eid); if (gid < 0) { path.AddToCurve(pathGraph.GetVertex(vid), new FillSegment(true)); } else { path.AddToCurve(pathGraph.GetVertex(vid)); } if (boundaries.Contains(vid)) { boundaries.Remove(vid); break; } } // discard paths that are too short if (path.TotalLength() < MinPathLengthMM) { continue; } // run polyline simplification to get rid of unneccesary detail in connectors // [TODO] we could do this at graph level...) // [TODO] maybe should be checking for collisions? we could end up creating // non-trivial overlaps here... if (SimplifyAmount != SimplificationLevel.None && path.Elements.Count > 1) { path = SimplifyPath(path); } paths.Append(path); } return(paths); }
public virtual void RemeshIteration() { if (mesh.TriangleCount == 0) // badness if we don't catch this... { return; } begin_pass(); // Iterate over all edges in the mesh at start of pass. // Some may be removed, so we skip those. // However, some old eid's may also be re-used, so we will touch // some new edges. Can't see how we could efficiently prevent this. // begin_ops(); IEnumerable <int> edgesItr = EdgesIterator(); if (modified_edges == null) { modified_edges = new HashSet <int>(); } else { edges_buffer.Clear(); edges_buffer.AddRange(modified_edges); edgesItr = edges_buffer; modified_edges.Clear(); } int startEdges = Mesh.EdgeCount; int flips = 0, splits = 0, collapes = 0; ModifiedEdgesLastPass = 0; int processedLastPass = 0; foreach (int cur_eid in edgesItr) { if (Cancelled()) { return; } if (mesh.IsEdge(cur_eid)) { Index2i ev = mesh.GetEdgeV(cur_eid); Index2i ov = mesh.GetEdgeOpposingV(cur_eid); // TODO: optimize the queuing here, are over-doing it! // TODO: be able to queue w/o flip (eg queue from smooth never requires flip check) processedLastPass++; ProcessResult result = ProcessEdge(cur_eid); if (result == ProcessResult.Ok_Collapsed) { queue_one_ring(ev.a); queue_one_ring(ev.b); queue_one_ring(ov.a); queue_one_ring(ov.b); ModifiedEdgesLastPass++; collapes++; } else if (result == ProcessResult.Ok_Split) { queue_one_ring(ev.a); queue_one_ring(ev.b); queue_one_ring(ov.a); queue_one_ring(ov.b); ModifiedEdgesLastPass++; splits++; } else if (result == ProcessResult.Ok_Flipped) { queue_one_ring(ev.a); queue_one_ring(ev.b); queue_one_ring(ov.a); queue_one_ring(ov.b); ModifiedEdgesLastPass++; flips++; } } } end_ops(); //System.Console.WriteLine("RemeshIteration: start {0} end {1} processed: {2} modified: {3} queue: {4}", // startEdges, Mesh.EdgeCount, processedLastPass, ModifiedEdgesLastPass, modified_edges.Count); //System.Console.WriteLine(" flips {0} splits {1} collapses {2}", flips, splits, collapes); if (Cancelled()) { return; } begin_smooth(); if (EnableSmoothing && SmoothSpeedT > 0) { TrackedSmoothPass(EnableParallelSmooth); DoDebugChecks(); } end_smooth(); if (Cancelled()) { return; } begin_project(); if (ProjectionTarget != null && ProjectionMode == TargetProjectionMode.AfterRefinement) { //FullProjectionPass(); if (UseFaceAlignedProjection) { for (int i = 0; i < FaceProjectionPassesPerIteration; ++i) { TrackedFaceProjectionPass(); } } else { TrackedProjectionPass(EnableParallelProjection); } DoDebugChecks(); } end_project(); end_pass(); }
List <Polygon2d> decompose_cluster_up(DMesh3 mesh) { optimize_mesh(mesh); mesh.CompactInPlace(); mesh.DiscardTriangleGroups(); mesh.EnableTriangleGroups(0); double minLength = Settings.MaxBridgeWidthMM * 0.75; double minArea = minLength * minLength; Dictionary <int, double> areas = new Dictionary <int, double>(); Dictionary <int, HashSet <int> > trisets = new Dictionary <int, HashSet <int> >(); HashSet <int> active_groups = new HashSet <int>(); Action <int, int> add_tri_to_group = (tid, gid) => { mesh.SetTriangleGroup(tid, gid); areas[gid] = areas[gid] + mesh.GetTriArea(tid); trisets[gid].Add(tid); }; Action <int, int> add_group_to_group = (gid, togid) => { var set = trisets[togid]; foreach (int tid in trisets[gid]) { mesh.SetTriangleGroup(tid, togid); set.Add(tid); } areas[togid] += areas[gid]; active_groups.Remove(gid); }; Func <IEnumerable <int>, int> find_min_area_group = (tri_itr) => { int min_gid = -1; double min_area = double.MaxValue; foreach (int tid in tri_itr) { int gid = mesh.GetTriangleGroup(tid); double a = areas[gid]; if (a < min_area) { min_area = a; min_gid = gid; } } return(min_gid); }; foreach (int eid in MeshIterators.InteriorEdges(mesh)) { Index2i et = mesh.GetEdgeT(eid); if (mesh.GetTriangleGroup(et.a) != 0 || mesh.GetTriangleGroup(et.b) != 0) { continue; } int gid = mesh.AllocateTriangleGroup(); areas[gid] = 0; trisets[gid] = new HashSet <int>(); active_groups.Add(gid); add_tri_to_group(et.a, gid); add_tri_to_group(et.b, gid); } foreach (int tid in mesh.TriangleIndices()) { if (mesh.GetTriangleGroup(tid) != 0) { continue; } int gid = find_min_area_group(mesh.TriTrianglesItr(tid)); add_tri_to_group(tid, gid); } IndexPriorityQueue pq = new IndexPriorityQueue(mesh.MaxGroupID); foreach (var pair in areas) { pq.Insert(pair.Key, (float)pair.Value); } while (pq.Count > 0) { int gid = pq.First; pq.Remove(gid); if (areas[gid] > minArea) // ?? { break; } List <int> nbr_groups = find_neighbour_groups(mesh, gid, trisets[gid]); int min_gid = -1; double min_area = double.MaxValue; foreach (int ngid in nbr_groups) { double a = areas[ngid]; if (a < min_area) { min_area = a; min_gid = ngid; } } if (min_gid != -1) { add_group_to_group(gid, min_gid); pq.Remove(min_gid); pq.Insert(min_gid, (float)areas[min_gid]); } } List <Polygon2d> result = new List <Polygon2d>(); int[][] sets = FaceGroupUtil.FindTriangleSetsByGroup(mesh); foreach (var set in sets) { result.Add(make_poly(mesh, set)); } return(result); }
// [TODO] projection pass // - only project vertices modified by smooth pass? // - and/or verts in set of modified edges? protected virtual void TrackedFaceProjectionPass() { IOrientedProjectionTarget normalTarget = ProjectionTarget as IOrientedProjectionTarget; if (normalTarget == null) { throw new Exception("RemesherPro.TrackedFaceProjectionPass: projection target does not have normals!"); } InitializeBuffersForFacePass(); SpinLock buffer_lock = new SpinLock(); // this function computes rotated position of triangle, such that it // aligns with face normal on target surface. We accumulate weighted-average // of vertex positions, which we will then use further down where possible. Action <int> process_triangle = (tid) => { Vector3d normal; double area; Vector3d centroid; mesh.GetTriInfo(tid, out normal, out area, out centroid); Vector3d projNormal; Vector3d projPos = normalTarget.Project(centroid, out projNormal); Index3i tv = mesh.GetTriangle(tid); Vector3d v0 = mesh.GetVertex(tv.a), v1 = mesh.GetVertex(tv.b), v2 = mesh.GetVertex(tv.c); // ugh could probably do this more efficiently... Frame3f triF = new Frame3f(centroid, normal); v0 = triF.ToFrameP(ref v0); v1 = triF.ToFrameP(ref v1); v2 = triF.ToFrameP(ref v2); triF.AlignAxis(2, (Vector3f)projNormal); triF.Origin = (Vector3f)projPos; v0 = triF.FromFrameP(ref v0); v1 = triF.FromFrameP(ref v1); v2 = triF.FromFrameP(ref v2); double dot = normal.Dot(projNormal); dot = MathUtil.Clamp(dot, 0, 1.0); double w = area * (dot * dot * dot); bool taken = false; buffer_lock.Enter(ref taken); vBufferV[tv.a] += w * v0; vBufferVWeights[tv.a] += w; vBufferV[tv.b] += w * v1; vBufferVWeights[tv.b] += w; vBufferV[tv.c] += w * v2; vBufferVWeights[tv.c] += w; buffer_lock.Exit(); }; // compute face-aligned vertex positions gParallel.ForEach(mesh.TriangleIndices(), process_triangle); // ok now we filter out all the positions we can't change, as well as vertices that // did not actually move. We also queue any edges that moved far enough to fall // under min/max edge length thresholds gParallel.ForEach(mesh.VertexIndices(), (vID) => { vModifiedV[vID] = false; if (vBufferVWeights[vID] < MathUtil.ZeroTolerance) { return; } if (vertex_is_constrained(vID)) { return; } if (VertexControlF != null && (VertexControlF(vID) & VertexControl.NoProject) != 0) { return; } Vector3d curpos = mesh.GetVertex(vID); Vector3d projPos = vBufferV[vID] / vBufferVWeights[vID]; if (curpos.EpsilonEqual(projPos, MathUtil.ZeroTolerancef)) { return; } vModifiedV[vID] = true; vBufferV[vID] = projPos; foreach (int eid in mesh.VtxEdgesItr(vID)) { Index2i ev = Mesh.GetEdgeV(eid); int othervid = (ev.a == vID) ? ev.b : ev.a; Vector3d otherv = mesh.GetVertex(othervid); double old_len = curpos.Distance(otherv); double new_len = projPos.Distance(otherv); if (new_len < MinEdgeLength || new_len > MaxEdgeLength) { queue_edge_safe(eid); } } }); // update vertices ApplyVertexBuffer(true); }
protected virtual void TrackedSmoothPass(bool bParallel) { InitializeVertexBufferForPass(); Func <DMesh3, int, double, Vector3d> smoothFunc = MeshUtil.UniformSmooth; if (CustomSmoothF != null) { smoothFunc = CustomSmoothF; } else { if (SmoothType == SmoothTypes.MeanValue) { smoothFunc = MeshUtil.MeanValueSmooth; } else if (SmoothType == SmoothTypes.Cotan) { smoothFunc = MeshUtil.CotanSmooth; } } Action <int> smooth = (vID) => { Vector3d vCur = Mesh.GetVertex(vID); bool bModified = false; Vector3d vSmoothed = ComputeSmoothedVertexPos(vID, smoothFunc, out bModified); //if (vCur.EpsilonEqual(vSmoothed, MathUtil.ZeroTolerancef)) // bModified = false; if (bModified) { vModifiedV[vID] = true; vBufferV[vID] = vSmoothed; foreach (int eid in mesh.VtxEdgesItr(vID)) { Index2i ev = Mesh.GetEdgeV(eid); int othervid = (ev.a == vID) ? ev.b : ev.a; Vector3d otherv = mesh.GetVertex(othervid); double old_len = vCur.Distance(otherv); double new_len = vSmoothed.Distance(otherv); if (new_len < MinEdgeLength || new_len > MaxEdgeLength) { queue_edge_safe(eid); } } } }; if (bParallel) { gParallel.ForEach <int>(smooth_vertices(), smooth); } else { foreach (int vID in smooth_vertices()) { smooth(vID); } } ApplyVertexBuffer(bParallel); //System.Console.WriteLine("Smooth Pass: queue: {0}", modified_edges.Count); }
public void CollapseEdge(int eid, Frame3f posL, bool bInteractive) { if (PreviewMesh.IsEdge(eid) == false) { DebugUtil.Log("MeshEditorTool.CollapseEdge: invalid eid!"); return; } Index2i ev = PreviewMesh.GetEdgeV(eid); int keep = ev.a, discard = ev.b; bool boundarya = PreviewMesh.IsBoundaryVertex(keep); bool boundaryb = PreviewMesh.IsBoundaryVertex(discard); if (boundaryb && !boundarya) { keep = ev.b; discard = ev.a; } HashSet <int> removeT = new HashSet <int>(); foreach (int tid in PreviewMesh.VtxTrianglesItr(keep)) { removeT.Add(tid); } foreach (int tid in PreviewMesh.VtxTrianglesItr(discard)) { removeT.Add(tid); } RemoveTrianglesMeshChange removeChange = null; AddTrianglesMeshChange addChange = null; previewSO.EditAndUpdateMesh((mesh) => { removeChange = new RemoveTrianglesMeshChange(); removeChange.InitializeFromExisting(mesh, removeT); DMesh3.EdgeCollapseInfo collapseInfo; if (mesh.CollapseEdge(keep, discard, out collapseInfo) == MeshResult.Ok) { foreach (int tid in removeT) { PreviewSpatial.RemoveTriangle(tid); } foreach (int tid in PreviewMesh.VtxTrianglesItr(keep)) { PreviewSpatial.AddTriangle(tid); } if (boundarya == false & boundaryb == false) { mesh.SetVertex(keep, posL.Origin); } addChange = new AddTrianglesMeshChange(); addChange.InitializeFromExisting(mesh, new List <int>() { keep }, get_vtx_tris(mesh, keep)); } }, GeometryEditTypes.ArbitraryEdit); if (addChange != null) { add_replace_change(removeChange, addChange, bInteractive); } }
private void FilterSelfOverlaps(double overlapRadius, bool bResample = true) { // [RMS] this tolerance business is not workign properly right now. The problem is // that decimator loses corners! // To simplify the computation we are going to resample the curve so that no adjacent // are within a given distance. Then we can use distance-to-segments, with the two adjacent // segments filtered out, to measure self-distance double dist_thresh = overlapRadius; double sharp_thresh_deg = 45; //Profiler.Start("InitialResample"); // resample graph. the degenerate-edge thing is necessary to // filter out tiny segments that are functionally sharp corners, // but geometrically are made of multiple angles < threshold // (maybe there is a better way to do this?) DGraph2Resampler r = new DGraph2Resampler(Graph); r.CollapseDegenerateEdges(overlapRadius / 10); if (bResample) { r.SplitToMaxEdgeLength(overlapRadius / 2); r.CollapseToMinEdgeLength(overlapRadius / 3); } r.CollapseDegenerateEdges(overlapRadius / 10); //Profiler.StopAndAccumulate("InitialResample"); //Profiler.Start("SharpCorners"); // find sharp corners List <int> sharp_corners = new List <int>(); foreach (int vid in Graph.VertexIndices()) { if (is_fixed_v(vid)) { continue; } double open_angle = Graph.OpeningAngle(vid); if (open_angle < sharp_thresh_deg) { sharp_corners.Add(vid); } } // disconnect at sharp corners foreach (int vid in sharp_corners) { if (Graph.IsVertex(vid) == false) { continue; } int e0 = Graph.GetVtxEdges(vid)[0]; Index2i ev = Graph.GetEdgeV(e0); int otherv = (ev.a == vid) ? ev.b : ev.a; Vector2d newpos = Graph.GetVertex(vid); //0.5 * (Graph.GetVertex(vid) + Graph.GetVertex(otherv)); Graph.RemoveEdge(e0, false); int newvid = Graph.AppendVertex(newpos); Graph.AppendEdge(newvid, otherv); } //Profiler.StopAndAccumulate("SharpCorners"); //Profiler.Start("HashTable"); // build edge hash table (cell size is just a ballpark guess here...) edge_hash = new SegmentHashGrid2d <int>(3 * overlapRadius, -1); foreach (int eid in Graph.EdgeIndices()) { Segment2d seg = Graph.GetEdgeSegment(eid); edge_hash.InsertSegment(eid, seg.Center, seg.Extent); } if (CollisionGraph.EdgeCount > 0) { collision_edge_hash = new SegmentHashGrid2d <int>(3 * CollisionRadius, -1); foreach (int eid in CollisionGraph.EdgeIndices()) { Segment2d seg = CollisionGraph.GetEdgeSegment(eid); collision_edge_hash.InsertSegment(eid, seg.Center, seg.Extent); } } //Profiler.StopAndAccumulate("HashTable"); //Profiler.Start("Erode1"); // Step 1: erode from boundary vertices List <int> boundaries = new List <int>(); foreach (int vid in Graph.VertexIndices()) { if (Graph.GetVtxEdgeCount(vid) == 1) { boundaries.Add(vid); } } foreach (int vid in boundaries) { if (Graph.IsVertex(vid) == false) { continue; } double dist = MinSelfSegDistance(vid, 2 * dist_thresh); double collision_dist = MinCollisionConstraintDistance(vid, CollisionRadius); if (dist < dist_thresh || collision_dist < CollisionRadius) { int eid = Graph.GetVtxEdges(vid)[0]; decimate_forward(vid, eid, dist_thresh); } } //Profiler.StopAndAccumulate("Erode1"); //Profiler.Start("OpenAngleSort"); // // Step 2: find any other possible self-overlaps and erode them. // // sort all vertices by opening angle. For any overlap, we can erode // on either side. Prefer to erode on side with higher curvature. List <Vector2d> remaining_v = new List <Vector2d>(Graph.MaxVertexID); foreach (int vid in Graph.VertexIndices()) { if (is_fixed_v(vid)) { continue; } double open_angle = Graph.OpeningAngle(vid); if (open_angle == double.MaxValue) { continue; } remaining_v.Add(new Vector2d(vid, open_angle)); } remaining_v.Sort((a, b) => { return((a.y < b.y) ? -1 : (a.y > b.y ? 1 : 0)); }); //Profiler.StopAndAccumulate("OpenAngleSort"); //Profiler.Start("Erode2"); // look for overlap vertices. When we find one, erode on both sides. foreach (Vector2d vinfo in remaining_v) { int vid = (int)vinfo.x; if (Graph.IsVertex(vid) == false) { continue; } double dist = MinSelfSegDistance(vid, 2 * dist_thresh); if (dist < dist_thresh) { List <int> nbrs = new List <int>(Graph.GetVtxEdges(vid)); foreach (int eid in nbrs) { if (Graph.IsEdge(eid)) // may have been decimated! { decimate_forward(vid, eid, dist_thresh); } } } } //Profiler.StopAndAccumulate("Erode2"); //Profiler.Start("FlatCollapse"); // get rid of extra vertices r.CollapseFlatVertices(FinalFlatCollapseAngleThreshDeg); //Profiler.StopAndAccumulate("FlatCollapse"); }
/// <summary> /// fill poly w/ adjacent straight line segments, connected by connectors /// </summary> protected FillCurveSet2d ComputeFillPaths(GeneralPolygon2d poly) { FillCurveSet2d paths = new FillCurveSet2d(); // smooth the input poly a little bit, this simplifies the filling // (simplify after?) //GeneralPolygon2d smoothed = poly.Duplicate(); //CurveUtils2.LaplacianSmoothConstrained(smoothed, 0.5, 5, ToolWidth / 2, true, false); //poly = smoothed; // compute 2D non-manifold graph consisting of original polygon and // inserted line segments DGraph2 spanGraph = ComputeSpanGraph(poly); if (spanGraph == null || spanGraph.VertexCount == poly.VertexCount) { return(paths); } DGraph2 pathGraph = BuildPathGraph(spanGraph); // filter out self-overlaps from graph if (FilterSelfOverlaps) { PathOverlapRepair repair = new PathOverlapRepair(pathGraph); repair.OverlapRadius = ToolWidth * SelfOverlapToolWidthX; repair.PreserveEdgeFilterF = (eid) => { return(repair.Graph.GetEdgeGroup(eid) > 0); }; repair.Compute(); pathGraph = repair.GetResultGraph(); } HashSet <int> boundaries = new HashSet <int>(); foreach (int vid in pathGraph.VertexIndices()) { if (pathGraph.IsBoundaryVertex(vid)) { boundaries.Add(vid); } if (pathGraph.IsJunctionVertex(vid)) { throw new Exception("DenseLinesFillPolygon: PathGraph has a junction???"); } } // walk paths from boundary vertices while (boundaries.Count > 0) { int start_vid = boundaries.First(); boundaries.Remove(start_vid); int vid = start_vid; int eid = pathGraph.GetVtxEdges(vid)[0]; FillPolyline2d path = new FillPolyline2d() { TypeFlags = this.TypeFlags }; path.AppendVertex(pathGraph.GetVertex(vid)); while (true) { Index2i next = DGraph2Util.NextEdgeAndVtx(eid, vid, pathGraph); eid = next.a; vid = next.b; int gid = pathGraph.GetEdgeGroup(eid); if (gid < 0) { path.AppendVertex(pathGraph.GetVertex(vid), TPVertexFlags.IsConnector); } else { path.AppendVertex(pathGraph.GetVertex(vid)); } if (boundaries.Contains(vid)) { boundaries.Remove(vid); break; } } // discard paths that are too short if (path.ArcLength < MinPathLengthMM) { continue; } // run polyline simplification to get rid of unneccesary detail in connectors // [TODO] we could do this at graph level...) // [TODO] maybe should be checkign for collisions? we could end up creating // non-trivial overlaps here... if (SimplifyAmount != SimplificationLevel.None && path.VertexCount > 2) { PolySimplification2 simp = new PolySimplification2(path); switch (SimplifyAmount) { default: case SimplificationLevel.Minor: simp.SimplifyDeviationThreshold = ToolWidth / 4; break; case SimplificationLevel.Aggressive: simp.SimplifyDeviationThreshold = ToolWidth; break; case SimplificationLevel.Moderate: simp.SimplifyDeviationThreshold = ToolWidth / 2; break; } simp.Simplify(); path = new FillPolyline2d(simp.Result.ToArray()) { TypeFlags = this.TypeFlags }; } paths.Append(path); } // Check to make sure that we are not putting way too much material in the // available volume. Computes extrusion volume from path length and if the // ratio is too high, scales down the path thickness // TODO: do we need to compute volume? If we just divide everything by // height we get the same scaling, no? Then we don't need layer height. if (MaxOverfillRatio > 0) { throw new NotImplementedException("this is not finished yet"); #if false double LayerHeight = 0.2; // AAAHHH hardcoded nonono double len = paths.TotalLength(); double extrude_vol = ExtrusionMath.PathLengthToVolume(LayerHeight, ToolWidth, len); double polygon_vol = LayerHeight * Math.Abs(poly.Area); double ratio = extrude_vol / polygon_vol; if (ratio > MaxOverfillRatio && PathSpacing == ToolWidth) { double use_width = ExtrusionMath.WidthFromTargetVolume(LayerHeight, len, polygon_vol); //System.Console.WriteLine("Extrusion volume: {0} PolyVolume: {1} % {2} ScaledWidth: {3}", //extrude_vol, polygon_vol, extrude_vol / polygon_vol, use_width); foreach (var path in paths.Curves) { path.CustomThickness = use_width; } } #endif } return(paths); }
/// <summary> /// Assumption is that input graph is a polygon with inserted ray-spans. We want to /// find a set of paths (ie no junctions) that cover all the spans, and travel between /// adjacent spans along edges of the input polygon. /// </summary> protected DGraph2 BuildPathGraph(DGraph2 input) { int NV = input.MaxVertexID; /* * OK, as input we have a graph of our original polygon and a bunch of inserted * segments ("spans"). Orig polygon segments have gid < 0, and span segments >= 0. * However between polygon/span junctions, we have an arbitrary # of polygon edges. * So first step is to simplify these to single-edge "connectors", in new graph MinGraph. * the [connector-edge, path] mappings (if pathlen > 1) are stored in MinEdgePaths * We also store a weight for each connector edge in EdgeWeights (just distance for now) */ DGraph2 MinGraph = new DGraph2(); Dictionary <int, List <int> > MinEdgePaths = new Dictionary <int, List <int> >(); DVector <double> EdgeWeights = new DVector <double>(); EdgeWeights.resize(NV); BitArray done_edge = new BitArray(input.MaxEdgeID); // we should see each edge twice, this avoids repetition // vertex map from input graph to MinGraph int[] MapV = new int[NV]; for (int i = 0; i < NV; ++i) { MapV[i] = -1; } for (int a = 0; a < NV; ++a) { if (input.IsVertex(a) == false || input.IsJunctionVertex(a) == false) { continue; } if (MapV[a] == -1) { MapV[a] = MinGraph.AppendVertex(input.GetVertex(a)); } foreach (int eid in input.VtxEdgesItr(a)) { if (done_edge[eid]) { continue; } Index2i ev = input.GetEdgeV(eid); int b = (ev.a == a) ? ev.b : ev.a; if (input.IsJunctionVertex(b)) { // if we have junction/juntion connection, we can just copy this edge to MinGraph if (MapV[b] == -1) { MapV[b] = MinGraph.AppendVertex(input.GetVertex(b)); } int gid = input.GetEdgeGroup(eid); int existing = MinGraph.FindEdge(MapV[a], MapV[b]); if (existing == DMesh3.InvalidID) { int new_eid = MinGraph.AppendEdge(MapV[a], MapV[b], gid); double path_len = input.GetEdgeSegment(eid).Length; EdgeWeights.insertAt(path_len, new_eid); } else { // we may have inserted this edge already in the simplify branch, this happens eg at the // edge of a circle where the minimal path is between the same vertices as the segment. // But if this is also a fill edge, we want to treat it that way (determind via positive gid) if (gid >= 0) { MinGraph.SetEdgeGroup(existing, gid); } } } else { // not a junction - walk until we find other vtx, and add single edge to MinGraph List <int> path = DGraph2Util.WalkToNextNonRegularVtx(input, a, eid); if (path == null || path.Count < 2) { throw new Exception("build_min_graph: invalid walk!"); } int c = path[path.Count - 1]; // it is somehow possible to get loops... if (c == a) { goto skip_this_edge; } if (MapV[c] == -1) { MapV[c] = MinGraph.AppendVertex(input.GetVertex(c)); } if (MinGraph.FindEdge(MapV[a], MapV[c]) == DMesh3.InvalidID) { int new_eid = MinGraph.AppendEdge(MapV[a], MapV[c], -2); path.Add(MapV[a]); path.Add(MapV[c]); MinEdgePaths[new_eid] = path; double path_len = DGraph2Util.PathLength(input, path); EdgeWeights.insertAt(path_len, new_eid); } } skip_this_edge: done_edge[eid] = true; } } // [TODO] filter MinGraph to remove invalid connectors // - can a connector between two connectors happen? that would be bad. /// - connector that is too close to paths should be ignored (ie avoid collisions) /* * Now that we have MinGraph, we can easily walk between the spans because * they are connected by at most one edge. To find a sequence of spans, we * pick one to start, then walk along connectors, discarding as we go, * so that we don't pass through these vertices again. Repeat until * there are no remaining spans. */ // [TODO] // do we actually have to delete from MinGraph? this prevents us from doing // certain things, like trying different options. Maybe could use a hash for // remaining vertices and edges instead? DGraph2 PathGraph = new DGraph2(); Vector2d sortAxis = Vector2d.FromAngleDeg(AngleDeg).Perp; while (true) { // find most extreme edge to start at // [TODO] could use segment gid here as we set them based on insertion span! // [TODO] could use a smarter metric? like, closest to previous last endpoint? Using // extrema like this tends to produce longest spans, though... double min_dot = double.MaxValue; int start_eid = -1; foreach (int eid in MinGraph.EdgeIndices()) { Index3i evg = MinGraph.GetEdge(eid); if (evg.c >= 0) { double dot = MinGraph.GetVertex(evg.a).Dot(sortAxis); if (dot < min_dot) { min_dot = dot; start_eid = eid; } } } if (start_eid == -1) { break; // if we could not find a start edge, we must be done! } // ok now walk forward through connectors and spans. We do this in // connector/span pairs - we are always at an end-of-span point, and // we pick a next-connector and then a next-span. // We need to keep track of vertices in both the pathgraph and mingraph, // these are the "new" and "old" vertices Index3i start_evg = MinGraph.GetEdge(start_eid); int new_start = PathGraph.AppendVertex(MinGraph.GetVertex(start_evg.a)); int new_prev = PathGraph.AppendVertex(MinGraph.GetVertex(start_evg.b)); int old_prev = start_evg.b; PathGraph.AppendEdge(new_start, new_prev, start_evg.c); MinGraph.RemoveVertex(start_evg.a, true); while (true) { // choose next connector edge, outgoing from current vtx int connector_e = -1; foreach (int eid in MinGraph.VtxEdgesItr(old_prev)) { Index3i evg = MinGraph.GetEdge(eid); if (evg.c >= 0) { continue; // what?? } if (connector_e == -1 || EdgeWeights[connector_e] > EdgeWeights[eid]) { connector_e = eid; } } if (connector_e == -1) { break; } // find the vertex at end of connector Index3i conn_evg = MinGraph.GetEdge(connector_e); int old_conn_v = (conn_evg.a == old_prev) ? conn_evg.b : conn_evg.a; // can never look at prev vertex again, or any edges connected to it // [TODO] are we sure none of these edges are unused spans?!? MinGraph.RemoveVertex(old_prev, true); // now find outgoing span edge int span_e = -1; foreach (int eid in MinGraph.VtxEdgesItr(old_conn_v)) { Index3i evg = MinGraph.GetEdge(eid); if (evg.c >= 0) { span_e = eid; break; } } if (span_e == -1) { break; // disaster! } // find vertex at far end of span Index3i span_evg = MinGraph.GetEdge(span_e); int old_span_v = (span_evg.a == old_conn_v) ? span_evg.b : span_evg.a; // ok we want to insert the connectr to the path graph, however the // connector might actually have come from a more complex path in the input graph. int new_conn_next = -1; if (MinEdgePaths.ContainsKey(connector_e)) { // complex path case. Note that the order [old_prev, old_conn_v] may be the opposite // of the order in the pathv. But above, we appended the [a,b] edge order to the pathv. // So we can check if we need to flip, but this means we need to be a bit clever w/ indices... List <int> pathv = MinEdgePaths[connector_e]; int N = pathv.Count; int path_prev = new_prev; int k = 1; if (pathv[N - 2] != old_prev) // case where order flipped { pathv.Reverse(); k = 3; } else { N = N - 2; } while (k < N) { int path_next = PathGraph.AppendVertex(input.GetVertex(pathv[k])); PathGraph.AppendEdge(path_prev, path_next); path_prev = path_next; k++; } new_conn_next = path_prev; } else { new_conn_next = PathGraph.AppendVertex(MinGraph.GetVertex(old_conn_v)); PathGraph.AppendEdge(new_prev, new_conn_next, conn_evg.c); } // add span to path int new_fill_next = PathGraph.AppendVertex(MinGraph.GetVertex(old_span_v)); PathGraph.AppendEdge(new_conn_next, new_fill_next, span_evg.c); // remove the connector vertex MinGraph.RemoveVertex(old_conn_v, true); // next iter starts at far end of span new_prev = new_fill_next; old_prev = old_span_v; } sortAxis = -sortAxis; } // for testing/debugging //SVGWriter writer = new SVGWriter(); ////writer.AddGraph(input, SVGWriter.Style.Outline("blue", 0.1f)); //writer.AddGraph(MinGraph, SVGWriter.Style.Outline("red", 0.1f)); ////foreach ( int eid in MinGraph.EdgeIndices() ) { //// if ( MinGraph.GetEdgeGroup(eid) >= 0 ) writer.AddLine(MinGraph.GetEdgeSegment(eid), SVGWriter.Style.Outline("green", 0.07f)); ////} ////writer.AddGraph(MinGraph, SVGWriter.Style.Outline("black", 0.03f)); //writer.AddGraph(PathGraph, SVGWriter.Style.Outline("black", 0.03f)); //foreach (int vid in PathGraph.VertexIndices()) { // if (PathGraph.IsBoundaryVertex(vid)) // writer.AddCircle(new Circle2d(PathGraph.GetVertex(vid), 0.5f), SVGWriter.Style.Outline("blue", 0.03f)); //} ////writer.AddGraph(IntervalGraph, SVGWriter.Style.Outline("black", 0.03f)); //writer.Write("c:\\scratch\\MIN_GRAPH.svg"); return(PathGraph); }
/// <summary> /// This pass only does edge splits. Returns number of split edges. /// Tracks previously-split /// </summary> public int FastSplitIteration() { if (mesh.TriangleCount == 0) // badness if we don't catch this... { return(0); } PushState(); EnableFlips = EnableCollapses = EnableSmoothing = false; ProjectionMode = TargetProjectionMode.NoProjection; begin_pass(); // Iterate over all edges in the mesh at start of pass. // Some may be removed, so we skip those. // However, some old eid's may also be re-used, so we will touch // some new edges. Can't see how we could efficiently prevent this. // begin_ops(); IEnumerable <int> edgesItr = EdgesIterator(); if (modified_edges == null) { modified_edges = new HashSet <int>(); } else { edges_buffer.Clear(); edges_buffer.AddRange(modified_edges); edgesItr = edges_buffer; modified_edges.Clear(); } int startEdges = Mesh.EdgeCount; int splitEdges = 0; // When we split an edge, we need to check it and the adjacent ones we added. // Because of overhead in ProcessEdge, it is worth it to do a distance-check here double max_edge_len_sqr = MaxEdgeLength * MaxEdgeLength; SplitF = (edgeID, a, b, vNew) => { Vector3d v = Mesh.GetVertex(vNew); foreach (int eid in Mesh.VtxEdgesItr(vNew)) { Index2i ev = Mesh.GetEdgeV(eid); int othervid = (ev.a == vNew) ? ev.b : ev.a; if (mesh.GetVertex(othervid).DistanceSquared(ref v) > max_edge_len_sqr) { queue_edge(eid); } } //queue_one_ring(vNew); }; ModifiedEdgesLastPass = 0; int processedLastPass = 0; foreach (int cur_eid in edgesItr) { if (Cancelled()) { goto abort_compute; } if (mesh.IsEdge(cur_eid)) { Index2i ev = mesh.GetEdgeV(cur_eid); Index2i ov = mesh.GetEdgeOpposingV(cur_eid); processedLastPass++; ProcessResult result = ProcessEdge(cur_eid); if (result == ProcessResult.Ok_Split) { // new edges queued by SplitF ModifiedEdgesLastPass++; splitEdges++; } } } end_ops(); //System.Console.WriteLine("FastSplitIteration: start {0} end {1} processed: {2} modified: {3} queue: {4}", // startEdges, Mesh.EdgeCount, processedLastPass, ModifiedEdgesLastPass, modified_edges.Count); abort_compute: SplitF = null; PopState(); end_pass(); return(splitEdges); }