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