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