public static void TestOffsetAnimation() { Window window = new Window("TestFill"); window.SetDefaultSize(600, 600); window.SetPosition(WindowPosition.Center); DMesh3 mesh = StandardMeshReader.ReadMesh("c:\\scratch\\remesh.obj"); MeshBoundaryLoops loops = new MeshBoundaryLoops(mesh); DCurve3 curve = loops[0].ToCurve(); Polygon2d poly = new Polygon2d(); foreach (Vector3d v in curve.Vertices) { poly.AppendVertex(v.xy); } Outer = new GeneralPolygon2d(poly); DebugViewCanvas view = new DebugViewCanvas(); view.AddPolygon(Outer, Colorf.Black); DGraph2 graph = TopoOffset2d.QuickCompute(Outer, AnimOffset, AnimSpacing); view.AddGraph(graph, Colorf.Red); window.Add(view); window.ShowAll(); Active = view; }
/// <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?) if (SimplifyBeforeFilling) { poly = SimplifyInputPolygon(poly); } // 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) { pathGraph = FilterPathGraphSelfOverlaps(pathGraph); } return(WalkPathGraph(pathGraph)); }
public static void WriteTestOutputGraph(DGraph2 graph, string sFilename, double vtxRadius = 2.5) { WriteTestOutputGraphs(new List <DGraph2>() { graph }, sFilename, SVGWriter.Style.Outline("black", 1.0f), SVGWriter.Style.Outline("red", 1.0f), vtxRadius); }
public static void UpdateOffsetAnimation() { AnimOffset += 0.1f; DGraph2 graph = TopoOffset2d.QuickCompute(Outer, AnimOffset, AnimSpacing); Active.Graphs.Clear(); Active.AddGraph(graph, Colorf.Red); }
// 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 DGraph2 FilterPathGraphSelfOverlaps(DGraph2 pathGraph) { PathOverlapRepair repair = new PathOverlapRepair(pathGraph); repair.OverlapRadius = ToolWidth * SelfOverlapToolWidthX; repair.PreserveEdgeFilterF = (eid) => { return(repair.Graph.GetEdgeGroup(eid) > 0); }; repair.Compute(); pathGraph = repair.GetResultGraph(); return(pathGraph); }
/// <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; var minGraph = CreateMinGraph(input, NV, out var MinEdgePaths, out var EdgeWeights); var pathGraph = CreatePathGraph(input, minGraph, MinEdgePaths, EdgeWeights); if (exportToSVG) { ExportToSVG(input, minGraph, pathGraph); } return(pathGraph); }
public static SKPath ToSKPath(DGraph2 g, Func <Vector2d, SKPoint> mapF) { SKPath p = new SKPath(); foreach (Index3i edge in g.Edges()) { Vector2d a = g.GetVertex(edge.a); Vector2d b = g.GetVertex(edge.b); p.MoveTo(mapF(a)); p.LineTo(mapF(b)); p.Close(); } return(p); }
public static void smooth_pass(DGraph2 graph, float alpha, Vector2d[] newPos) { foreach (int vid in graph.VertexIndices()) { Vector2d v = graph.GetVertex(vid); bool isvalid; Vector2d l = DGraph2Util.VertexLaplacian(graph, vid, out isvalid); v += alpha * l; newPos[vid] = v; } foreach (int vid in graph.VertexIndices()) { graph.SetVertex(vid, newPos[vid]); } }
public static DGraph2 perturb_fill_2(DGraph2 graphIn, GeneralPolygon2d bounds, double waveWidth, double stepSize) { DGraph2Util.Curves curves = DGraph2Util.ExtractCurves(graphIn); Polygon2d poly = curves.Loops[0]; GeneralPolygon2dBoxTree gpTree = new GeneralPolygon2dBoxTree(bounds); Polygon2dBoxTree outerTree = new Polygon2dBoxTree(bounds.Outer); Polygon2dBoxTree innerTree = new Polygon2dBoxTree(bounds.Holes[0]); DGraph2 graph = new DGraph2(); graph.EnableVertexColors(Vector3f.Zero); graph.AppendPolygon(poly); DGraph2Resampler resampler = new DGraph2Resampler(graph); resampler.CollapseToMinEdgeLength(waveWidth); if (graph.VertexCount % 2 != 0) { // TODO smallest edge Index2i ev = graph.GetEdgeV(graph.EdgeIndices().First()); DGraph2.EdgeCollapseInfo cinfo; graph.CollapseEdge(ev.a, ev.b, out cinfo); } // move to borders int startv = graph.VertexIndices().First(); int eid = graph.VtxEdgesItr(startv).First(); int curv = startv; bool outer = true; do { Polygon2dBoxTree use_tree = (outer) ? outerTree : innerTree; outer = !outer; graph.SetVertex(curv, use_tree.NearestPoint(graph.GetVertex(curv))); Index2i next = DGraph2Util.NextEdgeAndVtx(eid, curv, graph); eid = next.a; curv = next.b; } while (curv != startv); return(graph); }
private static HashSet <int> IdentifyBoundaryHashSet(DGraph2 pathGraph) { 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???"); } } return(boundaries); }
public static void TestFill() { Window window = new Window("TestFill"); window.SetDefaultSize(600, 600); window.SetPosition(WindowPosition.Center); DebugViewCanvas view = new DebugViewCanvas(); GeneralPolygon2d poly = new GeneralPolygon2d( Polygon2d.MakeCircle(20, 32)); Polygon2d hole = Polygon2d.MakeCircle(15, 32); hole.Reverse(); hole.Translate(2 * Vector2d.AxisX); poly.AddHole(hole); view.AddPolygon(poly, Colorf.Black); double spacing = 0.5; double[] offsets = new double[] { 5 }; foreach (double offset in offsets) { DGraph2 graph = TopoOffset2d.QuickCompute(poly, offset, spacing); DGraph2Util.Curves c = DGraph2Util.ExtractCurves(graph); //view.AddGraph(graph, Colorf.Red); //DGraph2 perturbGraph = perturb_fill(graph, poly, 5.0f, spacing); DGraph2 perturbGraph = perturb_fill_2(graph, poly, 1.0f, spacing); //DGraph2Util.Curves c2 = DGraph2Util.ExtractCurves(perturbGraph); view.AddGraph(perturbGraph, Colorf.Orange); } window.Add(view); window.ShowAll(); Active = view; }
void DrawGraph(SKCanvas canvas, SKPaint paint, DGraph2 graph, Func <Vector2d, SKPoint> mapF) { Colorf color = Colorf.Red; if (Colors.ContainsKey(graph)) { color = Colors[graph]; } paint.Color = SkiaUtil.Color(color); SKPath path = SkiaUtil.ToSKPath(graph, mapF); paint.StrokeWidth = 2; canvas.DrawPath(path, paint); paint.StrokeWidth = 1; //paint.Color = SKColors.Black; foreach (Vector2d v in graph.Vertices()) { SKPoint c = mapF(v); canvas.DrawCircle(c.X, c.Y, 3.0f, paint); } }
private static void ExportToSVG(DGraph2 input, DGraph2 minGraph, DGraph2 pathGraph) { 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(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.Write("MIN_GRAPH.svg"); }
/// <summary> /// shoot parallel set of 2D rays at input polygon, and find portions /// of rays that are inside the polygon (we call these "spans"). These /// are inserted into the polygon, resulting in a non-manifold 2D graph. /// </summary> protected DGraph2 ComputeSpanGraph(GeneralPolygon2d poly) { double angleRad = AngleDeg * MathUtil.Deg2Rad; Vector2d dir = new Vector2d(Math.Cos(angleRad), Math.Sin(angleRad)); // compute projection span along axis Vector2d axis = dir.Perp; Interval1d axisInterval = Interval1d.Empty; Interval1d dirInterval = Interval1d.Empty; foreach (Vector2d v in poly.Outer.Vertices) { dirInterval.Contain(v.Dot(dir)); axisInterval.Contain(v.Dot(axis)); } // [TODO] also check holes? or assume they are contained? should be // classified as outside by winding check anyway... // construct interval we will step along to shoot parallel rays dirInterval.a -= 10 * ToolWidth; dirInterval.b += 10 * ToolWidth; double extent = dirInterval.Length; // nudge in a very tiny amount so that if poly is a rectangle, first // line is not directly on boundary axisInterval.a += ToolWidth * 0.01; axisInterval.b -= ToolWidth * 0.01; axisInterval.a -= PathShift; if (axisInterval.b < axisInterval.a) { return(null); // [RMS] is this right? I guess so. interval is too small to fill? } // If we are doing a dense fill, we want to pack as tightly as possible. // But if we are doing a sparse fill, then we want layers to stack. // So in that case, snap the interval to increments of the spacing // (does this work?) bool bIsSparse = (PathSpacing > ToolWidth * 2); if (bIsSparse) { // snap axisInterval.a to grid so that layers are aligned double snapped_a = Snapping.SnapToIncrement(axisInterval.a, PathSpacing); if (snapped_a > axisInterval.a) { snapped_a -= PathSpacing; } axisInterval.a = snapped_a; } Vector2d startCorner = axisInterval.a * axis + dirInterval.a * dir; double range = axisInterval.Length; int N = (int)(range / PathSpacing) + 1; // nudge spacing so that we exactly fill the available space double use_spacing = PathSpacing; if (bIsSparse == false && AdjustSpacingToMaximizeFill) { int nn = (int)(range / use_spacing); use_spacing = range / (double)nn; N = (int)(range / use_spacing) + 1; } DGraph2 graph = new DGraph2(); graph.AppendPolygon(poly); GraphSplitter2d splitter = new GraphSplitter2d(graph); splitter.InsideTestF = poly.Contains; // insert sequential rays for (int ti = 0; ti <= N; ++ti) { Vector2d o = startCorner + (double)ti * use_spacing * axis; Line2d ray = new Line2d(o, dir); splitter.InsertLine(ray, ti); } return(graph); }
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 DGraph2 CreatePathGraph(DGraph2 input, DGraph2 minGraph, Dictionary <int, List <int> > MinEdgePaths, DVector <double> EdgeWeights) { // [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? var pathGraph = new DGraph2(); var 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; 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; } return(pathGraph); }
// join disconnected vertices within distance threshold protected int JoinInTolerance_Parallel(DGraph2 graph, double fMergeDist) { double mergeSqr = fMergeDist * fMergeDist; int NV = graph.MaxVertexID; if (collapse_cache.size < NV) { collapse_cache.resize(NV); } gParallel.ForEach(Interval1i.Range(NV), (a) => { collapse_cache[a] = new Vector2d(-1, double.MaxValue); if (!graph.IsVertex(a)) { return; } Vector2d va = graph.GetVertex(a); int bNearest = -1; double nearDistSqr = double.MaxValue; for (int b = a + 1; b < NV; ++b) { if (b == a || graph.IsVertex(b) == false) { continue; } double distsqr = va.DistanceSquared(graph.GetVertex(b)); if (distsqr < mergeSqr && distsqr < nearDistSqr) { if (graph.FindEdge(a, b) == DGraph2.InvalidID) { nearDistSqr = distsqr; bNearest = b; } } } if (bNearest != -1) { collapse_cache[a] = new Vector2d(bNearest, nearDistSqr); } }); // [TODO] sort int merged = 0; for (int a = 0; a < NV; ++a) { if (collapse_cache[a].x == -1) { continue; } int bNearest = (int)collapse_cache[a].x; Vector2d pos_a = graph.GetVertex(a); Vector2d pos_bNearest = graph.GetVertex(bNearest); /*int eid = */ graph.AppendEdge(a, bNearest); DGraph2.EdgeCollapseInfo collapseInfo; graph.CollapseEdge(bNearest, a, out collapseInfo); graph_cache.RemovePointUnsafe(a, pos_a); last_step_size[a] = 0; graph_cache.UpdatePointUnsafe(bNearest, pos_bNearest, graph.GetVertex(bNearest)); merged++; } return(merged); }
// smooth vertices, but don't move further than max_move protected void smooth_pass(DGraph2 graph, int passes, double smooth_alpha, double max_move) { double max_move_sqr = max_move * max_move; int NV = graph.MaxVertexID; DVector <Vector2d> smoothedV = offset_cache; if (smoothedV.size < NV) { smoothedV.resize(NV); } if (position_cache.size < NV) { position_cache.resize(NV); } for (int pi = 0; pi < passes; ++pi) { gParallel.ForEach(Interval1i.Range(NV), (vid) => { if (!graph.IsVertex(vid)) { return; } Vector2d v = graph.GetVertex(vid); Vector2d c = Vector2d.Zero; int n = 0; foreach (int vnbr in graph.VtxVerticesItr(vid)) { c += graph.GetVertex(vnbr); n++; } if (n >= 2) { c /= n; Vector2d dv = (smooth_alpha) * (c - v); if (dv.LengthSquared > max_move_sqr) { /*double d = */ dv.Normalize(); dv *= max_move; } v += dv; } smoothedV[vid] = v; }); if (pi == 0) { for (int vid = 0; vid < NV; ++vid) { if (graph.IsVertex(vid)) { position_cache[vid] = graph.GetVertex(vid); graph.SetVertex(vid, smoothedV[vid]); } } } else { for (int vid = 0; vid < NV; ++vid) { if (graph.IsVertex(vid)) { graph.SetVertex(vid, smoothedV[vid]); } } } } for (int vid = 0; vid < NV; ++vid) { if (graph.IsVertex(vid)) { graph_cache.UpdatePointUnsafe(vid, position_cache[vid], smoothedV[vid]); } } }
public DGraph2 Compute() { reset_caches(); Graph = compute_result(Polygon, Offset, PointSpacing); return(Graph); }
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); }
/// <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); }
public static DGraph2 perturb_fill(DGraph2 graphIn, GeneralPolygon2d bounds, double waveWidth, double stepSize) { DGraph2Util.Curves curves = DGraph2Util.ExtractCurves(graphIn); Polygon2d poly = curves.Loops[0]; GeneralPolygon2dBoxTree gpTree = new GeneralPolygon2dBoxTree(bounds); Polygon2dBoxTree outerTree = new Polygon2dBoxTree(bounds.Outer); Polygon2dBoxTree innerTree = new Polygon2dBoxTree(bounds.Holes[0]); DGraph2 graph = new DGraph2(); graph.EnableVertexColors(Vector3f.Zero); double len = poly.Perimeter; int waves = (int)(len / waveWidth); double lenScale = len / (MathUtil.TwoPI * waves); double accum_len = 0; int prev_vid = -1, start_vid = -1; int N = poly.VertexCount; for (int k = 0; k < N; ++k) { double t = accum_len / lenScale; t = Math.Cos(t); //Vector2d normal = poly.GetNormal(k); Vector2d normal = poly[k].Normalized; int vid = graph.AppendVertex(poly[k], new Vector3f(t, normal.x, normal.y)); if (prev_vid != -1) { graph.AppendEdge(prev_vid, vid); accum_len += graph.GetVertex(prev_vid).Distance(graph.GetVertex(vid)); } else { start_vid = vid; } prev_vid = vid; } graph.AppendEdge(prev_vid, start_vid); Vector2d[] newPos = new Vector2d[graph.MaxVertexID]; for (int k = 0; k < 10; ++k) { smooth_pass(graph, 0.5f, newPos); } for (int k = 0; k < 20; ++k) { foreach (int vid in graph.VertexIndices()) { Vector2d v = graph.GetVertex(vid); Vector3f c = graph.GetVertexColor(vid); float t = c.x; Vector2d n = new Vector2d(c.y, c.z); if (k == 0 || Math.Abs(t) > 0.9) { v += t * stepSize * n; if (!bounds.Contains(v)) { v = gpTree.NearestPoint(v); } } newPos[vid] = v; } foreach (int vid in graph.VertexIndices()) { graph.SetVertex(vid, newPos[vid]); } for (int j = 0; j < 5; ++j) { smooth_pass(graph, 0.1f, newPos); } } return(graph); }
// join disconnected vertices within distance threshold. Use point-hashtable to make this faster. protected int JoinInTolerance_Parallel_Cache(DGraph2 graph, double fMergeDist) { double mergeSqr = fMergeDist * fMergeDist; int NV = graph.MaxVertexID; if (collapse_cache.size < NV) { collapse_cache.resize(NV); } gParallel.ForEach(Interval1i.Range(NV), (a) => { collapse_cache[a] = new Vector2d(-1, double.MaxValue); if (!graph.IsVertex(a)) { return; } Vector2d va = graph.GetVertex(a); KeyValuePair <int, double> found = graph_cache.FindNearestInRadius(va, mergeSqr, (b) => { return(va.DistanceSquared(graph.GetVertex(b))); }, (b) => { return(b <= a || (graph.FindEdge(a, b) != DGraph2.InvalidID)); }); if (found.Key != -1) { collapse_cache[a] = new Vector2d(found.Key, found.Value); } }); // [TODO] sort int merged = 0; for (int a = 0; a < NV; ++a) { if (collapse_cache[a].x == -1) { continue; } int bNearest = (int)collapse_cache[a].x; if (!graph.IsVertex(bNearest)) { continue; } Vector2d pos_a = graph.GetVertex(a); Vector2d pos_bNearest = graph.GetVertex(bNearest); /*int eid = */ graph.AppendEdge(a, bNearest); DGraph2.EdgeCollapseInfo collapseInfo; graph.CollapseEdge(bNearest, a, out collapseInfo); graph_cache.RemovePointUnsafe(a, pos_a); last_step_size[a] = 0; graph_cache.UpdatePointUnsafe(bNearest, pos_bNearest, graph.GetVertex(bNearest)); collapse_cache[bNearest] = new Vector2d(-1, double.MaxValue); merged++; } return(merged); }
public PathOverlapRepair() { Graph = new DGraph2(); }
// 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); } }
protected DGraph2 compute_result(GeneralPolygon2d poly, double fOffset, double fTargetSpacing) { double dt = fTargetSpacing / 2; int nSteps = (int)(Math.Abs(fOffset) / dt); if (nSteps < 10) { nSteps = 10; } // [TODO] we could cache this over multiple runs... DGraph2 graph = new DGraph2(); graph.AppendPolygon(poly.Outer); foreach (var h in poly.Holes) { graph.AppendPolygon(h); } // resample to nbrhood of target spacing SplitToMaxEdgeLength(graph, fTargetSpacing * 1.33); // build bvtree for polygon if (poly_tree == null || poly_tree.Polygon != poly) { poly_tree = new GeneralPolygon2dBoxTree(poly); } // allocate and resize caches as necessary if (offset_cache == null) { offset_cache = new DVector <Vector2d>(); } if (offset_cache.size < graph.VertexCount) { offset_cache.resize(graph.VertexCount * 2); } if (position_cache == null) { position_cache = new DVector <Vector2d>(); } if (position_cache.size < graph.VertexCount) { position_cache.resize(graph.VertexCount * 2); } if (collapse_cache == null) { collapse_cache = new DVector <Vector2d>(); } if (collapse_cache.size < graph.VertexCount) { collapse_cache.resize(graph.VertexCount * 2); } if (last_step_size == null) { last_step_size = new DVector <double>(); } if (last_step_size.size < graph.VertexCount) { last_step_size.resize(graph.VertexCount * 2); } // insert all points into a hashgrid. We will dynamically update this grid as we proceed // [TODO] is this a good bounds-size? graph_cache = new PointHashGrid2d <int>(poly.Bounds.MaxDim / 64, -1); foreach (int vid in graph.VertexIndices()) { graph_cache.InsertPoint(vid, graph.GetVertex(vid)); } LocalProfiler p = (_enable_profiling) ? new LocalProfiler() : null; if (_enable_profiling) { p.Start("All"); } // run a bunch of steps. The last few are tuning steps where we use half-steps, // which seems to help? int TUNE_STEPS = nSteps / 2; nSteps *= 2; for (int i = 0; i < nSteps; ++i) { if (_enable_profiling) { p.Start("offset"); } double step_dt = dt; if (i > nSteps - TUNE_STEPS) { step_dt = dt / 2; } if (last_step_size.size < graph.VertexCount) { last_step_size.resize(graph.VertexCount + 256); } // Each vertex steps forward. In fact we compute two steps and average them, // this helps w/ convergence. To produce more accurate convergence, we track // the size of the actual step we took at the last round, and use that the next // time. (The assumption is that the steps will get smaller at the target distance). gParallel.ForEach(graph.VertexIndices(), (vid) => { // use tracked step size if we have it double use_dt = step_dt; if (last_step_size[vid] > 0) { use_dt = Math.Min(last_step_size[vid], dt); } Vector2d cur_pos = graph.GetVertex(vid); double err, err_2; // take two sequential steps and average them. this vastly improves convergence. Vector2d new_pos = compute_offset_step(cur_pos, poly, fOffset, use_dt, out err); Vector2d new_pos_2 = compute_offset_step(new_pos, poly, fOffset, use_dt, out err_2); // weighted blend of points - prefer one w/ smaller error //double w = 1.0 / Math.Max(err, MathUtil.ZeroTolerancef); //double w_2 = 1.0 / Math.Max(err_2, MathUtil.ZeroTolerancef); //new_pos = w * new_pos + w_2 * new_pos_2; //new_pos /= (w + w_2); // [RMS] weighted blend doesn't seem to matter if we are tracking per-vertex step size. new_pos = Vector2d.Lerp(new_pos, new_pos_2, 0.5); // keep track of actual step we are taking and use that next iteration double actual_step_dist = cur_pos.Distance(new_pos); if (last_step_size[vid] == 0) { last_step_size[vid] = actual_step_dist; } else { last_step_size[vid] = (0.75) * last_step_size[vid] + (0.25) * actual_step_dist; } // update point in hashtable and graph graph_cache.UpdatePoint(vid, cur_pos, new_pos); graph.SetVertex(vid, new_pos); }); if (_enable_profiling) { p.StopAndAccumulate("offset"); p.Start("smooth"); } // Do a smoothing pass, but for the last few steps, reduce smoothing // (otherwise it pulls away from target solution) int smooth_steps = 5; double smooth_alpha = 0.75; if (i > nSteps - TUNE_STEPS) { smooth_steps = 2; smooth_alpha = 0.25; } smooth_pass(graph, smooth_steps, smooth_alpha, fTargetSpacing / 2); if (_enable_profiling) { p.StopAndAccumulate("smooth"); p.Start("join"); } // if a vertex is within targetSpacing from another vertex, and they are // not geodesically connected in the graph, them we merge/weld them together. int joined = 0; do { //joined = JoinInTolerance(graph, fMergeThresh); //joined = JoinInTolerance_Parallel(graph, fMergeThresh); joined = JoinInTolerance_Parallel_Cache(graph, fTargetSpacing); } while (joined > 0); if (_enable_profiling) { p.StopAndAccumulate("join"); p.Start("refine"); } // now do a pass of graph refinement, to collapse short edges and split long ones CollapseToMinEdgeLength(graph, fTargetSpacing * 0.66f); SplitToMaxEdgeLength(graph, fTargetSpacing * 1.33); if (_enable_profiling) { p.StopAndAccumulate("refine"); } } if (_enable_profiling) { p.Stop("All"); System.Console.WriteLine("All: " + p.Elapsed("All")); System.Console.WriteLine(p.AllAccumulatedTimes()); } // get rid of junction vertices, if requested if (DisconnectGraphJunctions) { DGraph2Util.DisconnectJunctions(graph); } return(graph); }
public PathOverlapRepair(DGraph2 graph) { Graph = graph; }
public static void TestDGraph2() { Window window = new Window("TestDGraph2"); window.SetDefaultSize(600, 600); window.SetPosition(WindowPosition.Center); DebugViewCanvas view = new DebugViewCanvas(); GeneralPolygon2d poly = new GeneralPolygon2d( Polygon2d.MakeCircle(10, 32)); //Polygon2d hole = Polygon2d.MakeCircle(9, 32); //hole.Reverse(); //poly.AddHole(hole); Polygon2d hole = Polygon2d.MakeCircle(5, 32); hole.Translate(new Vector2d(2, 0)); hole.Reverse(); poly.AddHole(hole); Polygon2d hole2 = Polygon2d.MakeCircle(1, 32); hole2.Translate(-6 * Vector2d.AxisX); hole2.Reverse(); poly.AddHole(hole2); Polygon2d hole3 = Polygon2d.MakeCircle(1, 32); hole3.Translate(-6 * Vector2d.One); hole3.Reverse(); poly.AddHole(hole3); Polygon2d hole4 = Polygon2d.MakeCircle(1, 32); hole4.Translate(7 * Vector2d.AxisY); hole4.Reverse(); poly.AddHole(hole4); view.AddPolygon(poly, Colorf.Black); double spacing = 0.2; //double[] offsets = new double[] { 0.5, 1, 1.5, 2, 2.5 }; double[] offsets = new double[] { 0.2, 0.6 }; TopoOffset2d o = new TopoOffset2d(poly) { PointSpacing = spacing }; foreach (double offset in offsets) { o.Offset = offset; DGraph2 graph = o.Compute(); DGraph2Util.Curves c = DGraph2Util.ExtractCurves(graph); view.AddGraph(graph, Colorf.Red); } window.Add(view); window.ShowAll(); }