public void Add(GeneralPolygon2d path, int outer_gid = -1, int hole_gid = -1) { Graph.AppendPolygon(path.Outer, outer_gid); foreach (Polygon2d hole in path.Holes) { Graph.AppendPolygon(hole, hole_gid); } }
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); }
/// <summary> /// Collision polygons are fixed thickened paths we need to clip against /// </summary> public void AddCollisionConstraint(Polygon2d poly) { CollisionGraph.AppendPolygon(poly); }
/// <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); }
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 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")); }
public static void test_cells() { Polygon2d outer = Polygon2d.MakeCircle(1000, 17); Polygon2d hole = Polygon2d.MakeCircle(100, 32); hole.Reverse(); GeneralPolygon2d gpoly = new GeneralPolygon2d(outer); gpoly.AddHole(hole); DGraph2 graph = new DGraph2(); graph.AppendPolygon(gpoly); GraphSplitter2d splitter = new GraphSplitter2d(graph); splitter.InsideTestF = gpoly.Contains; for (int k = 0; k < outer.VertexCount; ++k) { Line2d line = new Line2d(outer[k], Vector2d.AxisY); splitter.InsertLine(line); } for (int k = 0; k < outer.VertexCount; ++k) { Line2d line = new Line2d(outer[k], Vector2d.AxisX); splitter.InsertLine(line); } for (int k = 0; k < outer.VertexCount; ++k) { Line2d line = new Line2d(outer[k], Vector2d.One.Normalized); splitter.InsertLine(line); } for (int k = 0; k < outer.VertexCount; ++k) { Line2d line = new Line2d(outer[k], new Vector2d(1, -1).Normalized); splitter.InsertLine(line); } GraphCells2d cells = new GraphCells2d(graph); cells.FindCells(); List <Polygon2d> polys = cells.ContainedCells(gpoly); for (int k = 0; k < polys.Count; ++k) { double offset = polys[k].IsClockwise ? 4 : 20; polys[k].PolyOffset(offset); } PlanarComplex cp = new PlanarComplex(); for (int k = 0; k < polys.Count; ++k) { cp.Add(polys[k]); } // convert back to solids var options = PlanarComplex.FindSolidsOptions.Default; options.WantCurveSolids = false; options.SimplifyDeviationTolerance = 0; var solids = cp.FindSolidRegions(options); SVGWriter svg = new SVGWriter(); svg.AddGraph(graph, SVGWriter.Style.Outline("red", 5)); for (int k = 0; k < polys.Count; ++k) { svg.AddPolygon(polys[k], SVGWriter.Style.Outline("black", 1)); } svg.Write(TestUtil.GetTestOutputPath("cells_graph.svg")); }