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); }
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 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); }
public static void test_splitter() { Polygon2d poly = Polygon2d.MakeCircle(1000, 16); Polygon2d hole = Polygon2d.MakeCircle(500, 32); hole.Reverse(); GeneralPolygon2d gpoly = new GeneralPolygon2d(poly); gpoly.AddHole(hole); //Polygon2d poly = Polygon2d.MakeRectangle(Vector2d.Zero, 1000, 1000); DGraph2 graph = new DGraph2(); graph.AppendPolygon(gpoly); System.Console.WriteLine("Stats before: verts {0} edges {1} ", graph.VertexCount, graph.EdgeCount); GraphSplitter2d splitter = new GraphSplitter2d(graph); splitter.InsideTestF = gpoly.Contains; for (int k = 0; k < poly.VertexCount; ++k) { Line2d line = new Line2d(poly[k], Vector2d.AxisY); splitter.InsertLine(line); } System.Console.WriteLine("Stats after 1: verts {0} edges {1} ", graph.VertexCount, graph.EdgeCount); for (int k = 0; k < poly.VertexCount; ++k) { Line2d line = new Line2d(poly[k], Vector2d.AxisX); splitter.InsertLine(line); } for (int k = 0; k < poly.VertexCount; ++k) { Line2d line = new Line2d(poly[k], Vector2d.One.Normalized); splitter.InsertLine(line); } for (int k = 0; k < poly.VertexCount; ++k) { Line2d line = new Line2d(poly[k], new Vector2d(1, -1).Normalized); splitter.InsertLine(line); } System.Console.WriteLine("Stats after: verts {0} edges {1} ", graph.VertexCount, graph.EdgeCount); Random r = new Random(31337); foreach (int vid in graph.VertexIndices()) { Vector2d v = graph.GetVertex(vid); v += TestUtil.RandomPoints2(1, r, v, 25)[0]; graph.SetVertex(vid, v); } SVGWriter svg = new SVGWriter(); svg.AddGraph(graph); var vtx_style = SVGWriter.Style.Outline("red", 1.0f); foreach (int vid in graph.VertexIndices()) { Vector2d v = graph.GetVertex(vid); svg.AddCircle(new Circle2d(v, 10), vtx_style); } svg.Write(TestUtil.GetTestOutputPath("split_graph.svg")); }