/// <summary>
        /// Decompose graph into simple polylines and polygons.
        /// </summary>
        public static CurveCollection ExtractCurves(DGraph2 graph)
        {
            CurveCollection c = new CurveCollection();

            HashSet <int> used = new HashSet <int>();

            // find boundary and junction vertices
            HashSet <int> boundaries = new HashSet <int>();
            HashSet <int> junctions  = new HashSet <int>();

            foreach (int vid in graph.VertexIndices())
            {
                if (graph.IsBoundaryVertex(vid))
                {
                    boundaries.Add(vid);
                }
                if (graph.IsJunctionVertex(vid))
                {
                    junctions.Add(vid);
                }
            }

            // walk paths from boundary vertices
            foreach (int start_vid in boundaries)
            {
                int vid = start_vid;
                int eid = graph.GetVtxEdges(vid)[0];
                if (used.Contains(eid))
                {
                    continue;
                }

                PolyLine2d path = new PolyLine2d();
                path.AppendVertex(graph.GetVertex(vid));
                while (true)
                {
                    used.Add(eid);
                    Index2i next = NextEdgeAndVtx(eid, vid, graph);
                    eid = next.a;
                    vid = next.b;
                    path.AppendVertex(graph.GetVertex(vid));
                    if (boundaries.Contains(vid) || junctions.Contains(vid))
                    {
                        break;  // done!
                    }
                }
                c.Paths.Add(path);
            }

            // ok we should be done w/ boundary verts now...
            boundaries.Clear();


            foreach (int start_vid in junctions)
            {
                foreach (int outgoing_eid in graph.VtxEdgesItr(start_vid))
                {
                    if (used.Contains(outgoing_eid))
                    {
                        continue;
                    }
                    int vid = start_vid;
                    int eid = outgoing_eid;

                    PolyLine2d path = new PolyLine2d();
                    path.AppendVertex(graph.GetVertex(vid));
                    bool is_loop = false;
                    while (true)
                    {
                        used.Add(eid);
                        Index2i next = NextEdgeAndVtx(eid, vid, graph);
                        eid = next.a;
                        vid = next.b;
                        if (vid == start_vid)
                        {
                            is_loop = true;
                            break;
                        }
                        path.AppendVertex(graph.GetVertex(vid));
                        if (eid == int.MaxValue || junctions.Contains(vid))
                        {
                            break;
                        }
                    }
                    if (is_loop)
                    {
                        c.Loops.Add(new Polygon2d(path.Vertices));
                    }
                    else
                    {
                        c.Paths.Add(path);
                    }
                }
            }


            // all that should be left are continuous loops...
            foreach (int start_eid in graph.EdgeIndices())
            {
                if (used.Contains(start_eid))
                {
                    continue;
                }

                int     eid = start_eid;
                Index2i ev  = graph.GetEdgeV(eid);
                int     vid = ev.a;

                Polygon2d poly = new Polygon2d();
                poly.AppendVertex(graph.GetVertex(vid));
                while (true)
                {
                    used.Add(eid);
                    Index2i next = NextEdgeAndVtx(eid, vid, graph);
                    eid = next.a;
                    vid = next.b;
                    poly.AppendVertex(graph.GetVertex(vid));
                    if (eid == int.MaxValue || junctions.Contains(vid))
                    {
                        throw new Exception("how did this happen??");
                    }
                    if (used.Contains(eid))
                    {
                        break;
                    }
                }
                poly.RemoveVertex(poly.VertexCount - 1);
                c.Loops.Add(poly);
            }

            return(c);
        }
 public void Add(CurveCollection other)
 {
     Loops.AddRange(other.Loops);
     Paths.AddRange(other.Paths);
 }
        public static void ChainOpenPaths(CurveCollection c, double epsilon = MathUtil.Epsilon)
        {
            List <PolyLine2d> to_process = new List <PolyLine2d>(c.Paths);

            c.Paths.Clear();

            // first we separate out 'dangling' curves that have no match on at least one side
            List <PolyLine2d> dangling  = new List <PolyLine2d>();
            List <PolyLine2d> remaining = new List <PolyLine2d>();

            bool bContinue = true;

            while (bContinue && to_process.Count > 0)
            {
                bContinue = false;
                foreach (PolyLine2d p in to_process)
                {
                    var matches_start = find_connected_start(p, to_process, epsilon);
                    var matches_end   = find_connected_end(p, to_process, epsilon);
                    if (matches_start.Count == 0 || matches_end.Count == 0)
                    {
                        dangling.Add(p);
                        bContinue = true;
                    }
                    else
                    {
                        remaining.Add(p);
                    }
                }
                to_process.Clear(); to_process.AddRange(remaining); remaining.Clear();
            }

            //to_process.Clear(); to_process.AddRange(remaining); remaining.Clear();

            // now incrementally merge together unique matches
            // [TODO] this will not match across junctions!
            bContinue = true;
            while (bContinue && to_process.Count > 0)
            {
                bContinue = false;
restart_itr:
                foreach (PolyLine2d p in to_process)
                {
                    var matches_start = find_connected_start(p, to_process, epsilon);
                    var matches_end   = find_connected_end(p, to_process, 2 * epsilon);
                    if (matches_start.Count == 1 && matches_end.Count == 1 &&
                        matches_start[0] == matches_end[0])
                    {
                        c.Loops.Add(to_loop(p, matches_start[0], epsilon));
                        to_process.Remove(p);
                        to_process.Remove(matches_start[0]);
                        remaining.Remove(matches_start[0]);
                        bContinue = true;
                        goto restart_itr;
                    }
                    else if (matches_start.Count == 1 && matches_end.Count < 2)
                    {
                        remaining.Add(merge_paths(matches_start[0], p, 2 * epsilon));
                        to_process.Remove(p);
                        to_process.Remove(matches_start[0]);
                        remaining.Remove(matches_start[0]);
                        bContinue = true;
                        goto restart_itr;
                    }
                    else if (matches_end.Count == 1 && matches_start.Count < 2)
                    {
                        remaining.Add(merge_paths(p, matches_end[0], 2 * epsilon));
                        to_process.Remove(p);
                        to_process.Remove(matches_end[0]);
                        remaining.Remove(matches_end[0]);
                        bContinue = true;
                        goto restart_itr;
                    }
                    else
                    {
                        remaining.Add(p);
                    }
                }
                to_process.Clear(); to_process.AddRange(remaining); remaining.Clear();
            }

            c.Paths.AddRange(to_process);

            // [TODO] now that we have found all loops, we can chain in dangling curves

            c.Paths.AddRange(dangling);
        }