// split edges longer than fMinLen // NOTE: basically the same as DGraph2Resampler.SplitToMaxEdgeLength, but updates // our internal caches. Could we merge somehow? protected void SplitToMaxEdgeLength(DGraph2 graph, double fMaxLen) { List <int> queue = new List <int>(); int NE = graph.MaxEdgeID; for (int eid = 0; eid < NE; ++eid) { if (!graph.IsEdge(eid)) { continue; } Index2i ev = graph.GetEdgeV(eid); double dist = graph.GetVertex(ev.a).Distance(graph.GetVertex(ev.b)); if (dist > fMaxLen) { DGraph2.EdgeSplitInfo splitInfo; if (graph.SplitEdge(eid, out splitInfo) == MeshResult.Ok) { if (graph_cache != null) { graph_cache.InsertPointUnsafe(splitInfo.vNew, graph.GetVertex(splitInfo.vNew)); } if (dist > 2 * fMaxLen) { queue.Add(eid); queue.Add(splitInfo.eNewBN); } } } } while (queue.Count > 0) { int eid = queue[queue.Count - 1]; queue.RemoveAt(queue.Count - 1); if (!graph.IsEdge(eid)) { continue; } Index2i ev = graph.GetEdgeV(eid); double dist = graph.GetVertex(ev.a).Distance(graph.GetVertex(ev.b)); if (dist > fMaxLen) { DGraph2.EdgeSplitInfo splitInfo; if (graph.SplitEdge(eid, out splitInfo) == MeshResult.Ok) { if (graph_cache != null) { graph_cache.InsertPointUnsafe(splitInfo.vNew, graph.GetVertex(splitInfo.vNew)); } if (dist > 2 * fMaxLen) { queue.Add(eid); queue.Add(splitInfo.eNewBN); } } } } }
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"); }
// collapse edges shorter than fMinLen // NOTE: basically the same as DGraph2Resampler.CollapseToMinEdgeLength, but updates // our internal caches. Could we merge somehow? protected void CollapseToMinEdgeLength(DGraph2 graph, double fMinLen) { double sharp_threshold_deg = 140.0f; double minLenSqr = fMinLen * fMinLen; bool done = false; int max_passes = 100; int pass_count = 0; while (done == false && pass_count++ < max_passes) { done = true; // [RMS] do modulo-indexing here to avoid pathological cases where we do things like // continually collapse a short edge adjacent to a long edge (which will result in crazy over-collapse) int N = graph.MaxEdgeID; const int nPrime = 31337; // any prime will do... int cur_eid = 0; do { int eid = cur_eid; cur_eid = (cur_eid + nPrime) % N; if (!graph.IsEdge(eid)) { continue; } Index2i ev = graph.GetEdgeV(eid); Vector2d va = graph.GetVertex(ev.a); Vector2d vb = graph.GetVertex(ev.b); double distSqr = va.DistanceSquared(vb); if (distSqr < minLenSqr) { int vtx_idx = -1; // collapse to this vertex // check valences. want to preserve positions of non-valence-2 int na = graph.GetVtxEdgeCount(ev.a); int nb = graph.GetVtxEdgeCount(ev.b); if (na != 2 && nb != 2) { continue; } if (na != 2) { vtx_idx = 0; } else if (nb != 2) { vtx_idx = 1; } // check opening angles. want to preserve sharp(er) angles if (vtx_idx == -1) { double opena = Math.Abs(graph.OpeningAngle(ev.a)); double openb = Math.Abs(graph.OpeningAngle(ev.b)); if (opena < sharp_threshold_deg && openb < sharp_threshold_deg) { continue; } else if (opena < sharp_threshold_deg) { vtx_idx = 0; } else if (openb < sharp_threshold_deg) { vtx_idx = 1; } } Vector2d newPos = (vtx_idx == -1) ? 0.5 * (va + vb) : ((vtx_idx == 0) ? va : vb); int keep = ev.a, remove = ev.b; if (vtx_idx == 1) { remove = ev.a; keep = ev.b; } Vector2d remove_pos = graph.GetVertex(remove); Vector2d keep_pos = graph.GetVertex(keep); DGraph2.EdgeCollapseInfo collapseInfo; if (graph.CollapseEdge(keep, remove, out collapseInfo) == MeshResult.Ok) { graph_cache.RemovePointUnsafe(collapseInfo.vRemoved, remove_pos); last_step_size[collapseInfo.vRemoved] = 0; graph_cache.UpdatePointUnsafe(collapseInfo.vKept, keep_pos, newPos); graph.SetVertex(collapseInfo.vKept, newPos); done = false; } } } while (cur_eid != 0); } }