예제 #1
0
        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");
        }
예제 #2
0
        /// <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);
        }
예제 #3
0
        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);
        }
예제 #4
0
        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);
        }
예제 #5
0
        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"));
        }