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); }
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); }