Пример #1
0
        /// <summary>
        /// walk through graph from fromVtx, in direction of eid, until we hit the next junction vertex
        /// </summary>
        public static List <int> WalkToNextNonRegularVtx(DGraph3 graph, int fromVtx, int eid)
        {
            List <int> path = new List <int>();

            path.Add(fromVtx);
            int  cur_vid   = fromVtx;
            int  cur_eid   = eid;
            bool bContinue = true;

            while (bContinue)
            {
                Index2i next     = DGraph3Util.NextEdgeAndVtx(cur_eid, cur_vid, graph);
                int     next_eid = next.a;
                int     next_vtx = next.b;
                if (next_eid == int.MaxValue)
                {
                    if (graph.IsRegularVertex(next_vtx) == false)
                    {
                        path.Add(next_vtx);
                        bContinue = false;
                    }
                    else
                    {
                        throw new Exception("WalkToNextNonRegularVtx: have no next edge but vtx is regular - how?");
                    }
                }
                else
                {
                    path.Add(next_vtx);
                    cur_vid = next_vtx;
                    cur_eid = next_eid;
                }
            }
            return(path);
        }
Пример #2
0
 public GraphTubeMesher(GraphSupportGenerator support_gen)
 {
     Graph               = support_gen.Graph;
     TipVertices         = support_gen.TipVertices;
     GroundVertices      = support_gen.GroundVertices;
     SamplerCellSizeHint = support_gen.CellSize;
 }
Пример #3
0
        /// <summary>
        /// If we are at edge eid, which as one vertex prev_vid, find 'other' vertex, and other edge connected to that vertex,
        /// and return pair [next_edge, shared_vtx]
        /// Returns [int.MaxValue, shared_vtx] if shared_vtx is not valence=2   (ie stops at boundaries and complex junctions)
        /// </summary>
        public static Index2i NextEdgeAndVtx(int eid, int prev_vid, DGraph3 graph)
        {
            Index2i ev = graph.GetEdgeV(eid);

            if (ev.a == DGraph3.InvalidID)
            {
                return(Index2i.Max);
            }

            int next_vid = (ev.a == prev_vid) ? ev.b : ev.a;

            if (graph.GetVtxEdgeCount(next_vid) != 2)
            {
                return(new Index2i(int.MaxValue, next_vid));
            }

            foreach (int next_eid in graph.VtxEdgesItr(next_vid))
            {
                if (next_eid != eid)
                {
                    return(new Index2i(next_eid, next_vid));
                }
            }
            return(Index2i.Max);
        }
Пример #4
0
 public void AppendGraph(DGraph3 graph, int gid = -1)
 {
     int[] mapV = new int[graph.MaxVertexID];
     foreach (int vid in graph.VertexIndices())
     {
         mapV[vid] = this.AppendVertex(graph.GetVertex(vid));
     }
     foreach (int eid in graph.EdgeIndices())
     {
         Index2i ev      = graph.GetEdgeV(eid);
         int     use_gid = (gid == -1) ? graph.GetEdgeGroup(eid) : gid;
         this.AppendEdge(mapV[ev.a], mapV[ev.b], use_gid);
     }
 }
Пример #5
0
        /// <summary>
        /// foreach edge [vid,b] connected to junction vertex vid, remove, add new vertex c,
        /// and then add new edge [b,c]. Optionally move c a bit back along edge from vid.
        /// </summary>
        public static void DisconnectJunction(DGraph3 graph, int vid, double shrinkFactor = 1.0)
        {
            Vector3d v = graph.GetVertex(vid);

            int[] nbr_verts = graph.VtxVerticesItr(vid).ToArray();
            for (int k = 0; k < nbr_verts.Length; ++k)
            {
                int eid = graph.FindEdge(vid, nbr_verts[k]);
                graph.RemoveEdge(eid, true);
                if (graph.IsVertex(nbr_verts[k]))
                {
                    Vector3d newpos = Vector3d.Lerp(graph.GetVertex(nbr_verts[k]), v, shrinkFactor);
                    int      newv   = graph.AppendVertex(newpos);
                    graph.AppendEdge(nbr_verts[k], newv);
                }
            }
        }
Пример #6
0
        protected void compute_full(IEnumerable <int> Triangles, bool bIsFullMeshHint = false)
        {
            Graph = new DGraph3();
            if (WantGraphEdgeInfo)
            {
                GraphEdges = new DVector <GraphEdgeInfo>();
            }

            Vertices = new Dictionary <Vector3d, int>();


            // multithreaded precomputation of per-vertex values
            double[] vertex_values = null;
            if (PrecomputeVertexValues)
            {
                vertex_values = new double[Mesh.MaxVertexID];
                IEnumerable <int> verts = Mesh.VertexIndices();
                if (bIsFullMeshHint == false)
                {
                    MeshVertexSelection vertices = new MeshVertexSelection(Mesh);
                    vertices.SelectTriangleVertices(Triangles);
                    verts = vertices;
                }
                gParallel.ForEach(verts, (vid) => {
                    vertex_values[vid] = ValueF(Mesh.GetVertex(vid));
                });
                VertexValueF = (vid) => { return(vertex_values[vid]); };
            }


            foreach (int tid in Triangles)
            {
                Vector3dTuple3 tv = new Vector3dTuple3();
                Mesh.GetTriVertices(tid, ref tv.V0, ref tv.V1, ref tv.V2);
                Index3i triVerts = Mesh.GetTriangle(tid);

                Vector3d f = (VertexValueF != null) ?
                             new Vector3d(VertexValueF(triVerts.a), VertexValueF(triVerts.b), VertexValueF(triVerts.c))
                    : new Vector3d(ValueF(tv.V0), ValueF(tv.V1), ValueF(tv.V2));

                // round f to 0 within epsilon?

                if (f.x < 0 && f.y < 0 && f.z < 0)
                {
                    continue;
                }
                if (f.x > 0 && f.y > 0 && f.z > 0)
                {
                    continue;
                }

                Index3i triEdges = Mesh.GetTriEdges(tid);

                if (f.x * f.y * f.z == 0)
                {
                    int z0 = (f.x == 0) ? 0 : ((f.y == 0) ? 1 : 2);
                    int i1 = (z0 + 1) % 3, i2 = (z0 + 2) % 3;
                    if (f[i1] * f[i2] > 0)
                    {
                        continue;       // single-vertex-crossing case, skip here and let other edges catch it
                    }
                    if (f[i1] == 0 || f[i2] == 0)
                    {
                        // on-edge case
                        int z1 = f[i1] == 0 ? i1 : i2;
                        if ((z0 + 1) % 3 != z1)
                        {
                            int tmp = z0; z0 = z1; z1 = tmp;        // catch reverse-orientation cases
                        }
                        int e0        = add_or_append_vertex(Mesh.GetVertex(triVerts[z0]));
                        int e1        = add_or_append_vertex(Mesh.GetVertex(triVerts[z1]));
                        int graph_eid = Graph.AppendEdge(e0, e1, (int)TriangleCase.OnEdge);
                        if (graph_eid >= 0 && WantGraphEdgeInfo)
                        {
                            add_on_edge(graph_eid, tid, triEdges[z0], new Index2i(e0, e1));
                        }
                    }
                    else
                    {
                        // edge/vertex case
                        Util.gDevAssert(f[i1] * f[i2] < 0);

                        int vert_vid = add_or_append_vertex(Mesh.GetVertex(triVerts[z0]));

                        int i = i1, j = i2;
                        if (triVerts[j] < triVerts[i])
                        {
                            int tmp = i; i = j; j = tmp;
                        }
                        Vector3d cross     = find_crossing(tv[i], tv[j], f[i], f[j]);
                        int      cross_vid = add_or_append_vertex(cross);
                        add_edge_pos(triVerts[i], triVerts[j], cross);

                        int graph_eid = Graph.AppendEdge(vert_vid, cross_vid, (int)TriangleCase.EdgeVertex);
                        if (graph_eid >= 0 && WantGraphEdgeInfo)
                        {
                            add_edge_vert(graph_eid, tid, triEdges[(z0 + 1) % 3], triVerts[z0], new Index2i(vert_vid, cross_vid));
                        }
                    }
                }
                else
                {
                    Index3i cross_verts = Index3i.Min;
                    int     less_than   = 0;
                    for (int tei = 0; tei < 3; ++tei)
                    {
                        int i = tei, j = (tei + 1) % 3;
                        if (f[i] < 0)
                        {
                            less_than++;
                        }
                        if (f[i] * f[j] > 0)
                        {
                            continue;
                        }
                        if (triVerts[j] < triVerts[i])
                        {
                            int tmp = i; i = j; j = tmp;
                        }
                        Vector3d cross = find_crossing(tv[i], tv[j], f[i], f[j]);
                        cross_verts[tei] = add_or_append_vertex(cross);
                        add_edge_pos(triVerts[i], triVerts[j], cross);
                    }
                    int e0 = (cross_verts.a == int.MinValue) ? 1 : 0;
                    int e1 = (cross_verts.c == int.MinValue) ? 1 : 2;
                    if (e0 == 0 && e1 == 2)         // preserve orientation order
                    {
                        e0 = 2; e1 = 0;
                    }

                    // preserving orientation does not mean we get a *consistent* orientation across faces.
                    // To do that, we need to assign "sides". Either we have 1 less-than-0 or 1 greater-than-0 vtx.
                    // Arbitrary decide that we want loops oriented like bdry loops would be if we discarded less-than side.
                    // In that case, when we are "cutting off" one vertex, orientation would end up flipped
                    if (less_than == 1)
                    {
                        int tmp = e0; e0 = e1; e1 = tmp;
                    }

                    int ev0 = cross_verts[e0];
                    int ev1 = cross_verts[e1];
                    // [RMS] if function is garbage, we can end up w/ case where both crossings
                    //   happen at same vertex, even though values are not the same (eg if
                    //   some values are double.MaxValue). We will just fail in these cases.
                    if (ev0 != ev1)
                    {
                        Util.gDevAssert(ev0 != int.MinValue && ev1 != int.MinValue);
                        int graph_eid = Graph.AppendEdge(ev0, ev1, (int)TriangleCase.EdgeEdge);
                        if (graph_eid >= 0 && WantGraphEdgeInfo)
                        {
                            add_edge_edge(graph_eid, tid, new Index2i(triEdges[e0], triEdges[e1]), new Index2i(ev0, ev1));
                        }
                    }
                }
            }


            Vertices = null;
        }
Пример #7
0
 public GraphTubeMesher(DGraph3 graph)
 {
     Graph = graph;
 }
Пример #8
0
        protected void compute_full(IEnumerable <int> Triangles)
        {
            Graph = new DGraph3();
            if (WantGraphEdgeInfo)
            {
                GraphEdges = new DVector <GraphEdgeInfo>();
            }

            Vertices = new Dictionary <Vector3d, int>();

            foreach (int tid in Triangles)
            {
                Vector3dTuple3 tv = new Vector3dTuple3();
                Mesh.GetTriVertices(tid, ref tv.V0, ref tv.V1, ref tv.V2);
                Vector3d f = new Vector3d(ValueF(tv.V0), ValueF(tv.V1), ValueF(tv.V2));

                // round f to 0 within epsilon?

                if (f.x < 0 && f.y < 0 && f.z < 0)
                {
                    continue;
                }
                if (f.x > 0 && f.y > 0 && f.z > 0)
                {
                    continue;
                }

                Index3i triVerts = Mesh.GetTriangle(tid);
                Index3i triEdges = Mesh.GetTriEdges(tid);

                if (f.x * f.y * f.z == 0)
                {
                    int z0 = (f.x == 0) ? 0 : ((f.y == 0) ? 1 : 2);
                    int i1 = (z0 + 1) % 3, i2 = (z0 + 2) % 3;
                    if (f[i1] * f[i2] > 0)
                    {
                        continue;       // single-vertex-crossing case, skip here and let other edges catch it
                    }
                    if (f[i1] == 0 || f[i2] == 0)
                    {
                        // on-edge case
                        int z1        = f[i1] == 0 ? i1 : i2;
                        int e0        = add_or_append_vertex(Mesh.GetVertex(triVerts[z0]));
                        int e1        = add_or_append_vertex(Mesh.GetVertex(triVerts[z1]));
                        int graph_eid = Graph.AppendEdge(e0, e1, (int)TriangleCase.OnEdge);
                        if (WantGraphEdgeInfo)
                        {
                            add_on_edge(graph_eid, tid, triEdges[z0]);
                        }
                    }
                    else
                    {
                        // edge/vertex case
                        Util.gDevAssert(f[i1] * f[i2] < 0);

                        int vert_vid = add_or_append_vertex(Mesh.GetVertex(triVerts[z0]));

                        int i = i1, j = i2;
                        if (triVerts[j] < triVerts[i])
                        {
                            int tmp = i; i = j; j = tmp;
                        }
                        Vector3d cross     = find_crossing(tv[i], tv[j], f[i], f[j]);
                        int      cross_vid = add_or_append_vertex(cross);

                        int graph_eid = Graph.AppendEdge(vert_vid, cross_vid, (int)TriangleCase.EdgeVertex);
                        if (WantGraphEdgeInfo)
                        {
                            add_edge_edge(graph_eid, tid, new Index2i(triEdges[(z0 + 1) % 3], triVerts[z0]));
                        }
                    }
                }
                else
                {
                    Index3i cross_verts = Index3i.Min;
                    for (int ti = 0; ti < 3; ++ti)
                    {
                        int i = ti, j = (ti + 1) % 3;
                        if (f[i] * f[j] > 0)
                        {
                            continue;
                        }
                        if (triVerts[j] < triVerts[i])
                        {
                            int tmp = i; i = j; j = tmp;
                        }
                        Vector3d cross = find_crossing(tv[i], tv[j], f[i], f[j]);
                        cross_verts[ti] = add_or_append_vertex(cross);
                    }
                    int e0  = (cross_verts.a == int.MinValue) ? 1 : 0;
                    int e1  = (cross_verts.c == int.MinValue) ? 1 : 2;
                    int ev0 = cross_verts[e0];
                    int ev1 = cross_verts[e1];
                    Util.gDevAssert(ev0 != int.MinValue && ev1 != int.MinValue);
                    int graph_eid = Graph.AppendEdge(ev0, ev1, (int)TriangleCase.EdgeEdge);
                    if (WantGraphEdgeInfo)
                    {
                        add_edge_edge(graph_eid, tid, new Index2i(triEdges[e0], triEdges[e1]));
                    }
                }
            }


            Vertices = null;
        }
Пример #9
0
        /// <summary>
        /// Decompose graph into simple polylines and polygons.
        /// </summary>
        public static Curves ExtractCurves(DGraph3 graph)
        {
            Curves c = new Curves();

            c.Loops = new List <DCurve3>();
            c.Paths = new List <DCurve3>();

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

                DCurve3 path = new DCurve3()
                {
                    Closed = false
                };
                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;

                    DCurve3 path = new DCurve3()
                    {
                        Closed = false
                    };
                    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 (eid == int.MaxValue || junctions.Contains(vid))
                        {
                            break;  // done!
                        }
                    }

                    // we could end up back at our start junction vertex!
                    if (vid == start_vid)
                    {
                        path.RemoveVertex(path.VertexCount - 1);
                        path.Closed = true;
                        c.Loops.Add(path);
                        // need to mark incoming edge as used...but is it valid now?
                        //Util.gDevAssert(eid != int.MaxValue);
                        if (eid != int.MaxValue)
                        {
                            used.Add(eid);
                        }
                    }
                    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;

                DCurve3 poly = new DCurve3()
                {
                    Closed = true
                };
                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);
        }
Пример #10
0
 public DGraph3(DGraph3 copy) : base()
 {
     vertices = new DVector <double>();
     AppendGraph(copy);
 }
Пример #11
0
        /// <summary>
        /// Erode inwards from open boundary vertices of graph (ie vtx with single edge).
        /// Resulting graph is not compact (!)
        /// </summary>
        public static void ErodeOpenSpurs(DGraph3 graph)
        {
            var used = new HashSet <int>();                // do we need this?

            // find boundary and junction vertices
            var boundaries = new HashSet <int>();
            var 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)
            {
                if (graph.IsVertex(start_vid) == false)
                {
                    continue;
                }

                int vid = start_vid;
                int eid = graph.GetVtxEdges(vid)[0];
                if (used.Contains(eid))
                {
                    continue;
                }

                var pathE = new List <int>();
                if (pathE != null)
                {
                    pathE.Add(eid);
                }

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

                    if (pathE != null)
                    {
                        pathE.Add(eid);
                    }
                }

                // delete this path
                foreach (int path_eid in pathE)
                {
                    graph.RemoveEdge(path_eid, true);
                }
            }
        }
Пример #12
0
        /// <summary>
        /// Decompose graph into simple polylines and polygons.
        /// </summary>
        public static Curves ExtractCurves(DGraph3 graph,
                                           bool bWantLoopIndices = false,
                                           Func <int, bool> CurveOrientationF = null)
        {
            var c = new Curves();

            c.Loops = new List <DCurve3>();
            c.Paths = new List <DCurve3>();
            if (bWantLoopIndices)
            {
                c.LoopEdges = new List <List <int> >();
                c.PathEdges = new List <List <int> >();
            }

            var used = new HashSet <int>();

            // find boundary and junction vertices
            var boundaries = new HashSet <int>();
            var 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;
                }

                bool reverse = (CurveOrientationF != null) ? CurveOrientationF(eid) : false;

                var path = new DCurve3()
                {
                    Closed = false
                };
                List <int> pathE = (bWantLoopIndices) ? new List <int>() : null;
                path.AppendVertex(graph.GetVertex(vid));
                if (pathE != null)
                {
                    pathE.Add(eid);
                }

                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!
                    }

                    if (pathE != null)
                    {
                        pathE.Add(eid);
                    }
                }
                if (reverse)
                {
                    path.Reverse();
                }

                c.Paths.Add(path);

                if (pathE != null)
                {
                    Util.gDevAssert(pathE.Count == path.VertexCount - 1);
                    if (reverse)
                    {
                        pathE.Reverse();
                    }

                    c.PathEdges.Add(pathE);
                }
            }

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


            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;

                    bool reverse = (CurveOrientationF != null) ? CurveOrientationF(eid) : false;

                    var path = new DCurve3()
                    {
                        Closed = false
                    };
                    List <int> pathE = (bWantLoopIndices) ? new List <int>() : null;
                    path.AppendVertex(graph.GetVertex(vid));
                    if (pathE != null)
                    {
                        pathE.Add(eid);
                    }

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

                        if (pathE != null)
                        {
                            pathE.Add(eid);
                        }
                    }

                    // we could end up back at our start junction vertex!
                    if (vid == start_vid)
                    {
                        path.RemoveVertex(path.VertexCount - 1);
                        path.Closed = true;
                        if (reverse)
                        {
                            path.Reverse();
                        }

                        c.Loops.Add(path);

                        if (pathE != null)
                        {
                            Util.gDevAssert(pathE.Count == path.VertexCount);
                            if (reverse)
                            {
                                pathE.Reverse();
                            }

                            c.LoopEdges.Add(pathE);
                        }

                        // need to mark incoming edge as used...but is it valid now?
                        //Util.gDevAssert(eid != int.MaxValue);
                        if (eid != int.MaxValue)
                        {
                            used.Add(eid);
                        }
                    }
                    else
                    {
                        if (reverse)
                        {
                            path.Reverse();
                        }

                        c.Paths.Add(path);

                        if (pathE != null)
                        {
                            Util.gDevAssert(pathE.Count == path.VertexCount - 1);
                            if (reverse)
                            {
                                pathE.Reverse();
                            }

                            c.PathEdges.Add(pathE);
                        }
                    }
                }
            }
            c.JunctionV = junctions;


            // 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;

                bool reverse = (CurveOrientationF != null) ? CurveOrientationF(eid) : false;

                var poly = new DCurve3()
                {
                    Closed = true
                };
                List <int> polyE = (bWantLoopIndices) ? new List <int>() : null;
                poly.AppendVertex(graph.GetVertex(vid));
                if (polyE != null)
                {
                    polyE.Add(eid);
                }

                while (true)
                {
                    used.Add(eid);
                    Index2i next = NextEdgeAndVtx(eid, vid, graph);
                    eid = next.a;
                    vid = next.b;
                    poly.AppendVertex(graph.GetVertex(vid));
                    if (polyE != null)
                    {
                        polyE.Add(eid);
                    }

                    if (eid == int.MaxValue || junctions.Contains(vid))
                    {
                        throw new Exception("how did this happen??");
                    }

                    if (used.Contains(eid))
                    {
                        break;
                    }
                }
                poly.RemoveVertex(poly.VertexCount - 1);
                if (reverse)
                {
                    poly.Reverse();
                }

                c.Loops.Add(poly);

                if (polyE != null)
                {
                    polyE.RemoveAt(polyE.Count - 1);
                    Util.gDevAssert(polyE.Count == poly.VertexCount);
                    if (reverse)
                    {
                        polyE.Reverse();
                    }

                    c.LoopEdges.Add(polyE);
                }
            }

            return(c);
        }
        void generate_graph(DenseGrid3f supportGrid, DenseGridTrilinearImplicit distanceField)
        {
            int ni = supportGrid.ni, nj = supportGrid.nj, nk = supportGrid.nk;
            float dx = (float)CellSize;
            Vector3f origin = this.GridOrigin;

            // parameters for initializing cost grid
            float MODEL_SPACE = 0.01f;      // needs small positive so that points on triangles count as inside (eg on ground plane)
            //float MODEL_SPACE = 2.0f*(float)CellSize;
            float CRAZY_DISTANCE = 99999.0f;
            bool UNIFORM_DISTANCE = true;
            float MAX_DIST = 10 * (float)CellSize;

            // parameters for sorting seeds
            Vector3i center_idx = new Vector3i(ni / 2, 0, nk / 2);      // middle
            //Vector3i center_idx = new Vector3i(0, 0, 0);              // corner
            bool reverse_per_layer = true;

            DenseGrid3f costGrid = new DenseGrid3f(supportGrid);
            foreach ( Vector3i ijk in costGrid.Indices() ) {
                Vector3d cell_center = new Vector3f(ijk.x * dx, ijk.y * dx, ijk.z * dx) + origin;
                float f = (float)distanceField.Value(ref cell_center);
                if (f <= MODEL_SPACE)
                    f = CRAZY_DISTANCE;
                else if (UNIFORM_DISTANCE)
                    f = 1.0f;
                else if (f > MAX_DIST)
                    f = MAX_DIST;
                costGrid[ijk] = f;
            }

            // Find seeds on each layer, sort, and add to accumulated bottom-up seeds list.
            // This sorting has an *enormous* effect on the support generation.

            List<Vector3i> seeds = new List<Vector3i>();
            List<Vector3i> layer_seeds = new List<Vector3i>();
            for (int j = 0; j < nj; ++j) {
                layer_seeds.Clear();
                for (int k = 0; k < nk; ++k) {
                    for (int i = 0; i < ni; ++i) {
                        if (supportGrid[i, j, k] == SUPPORT_TIP_BASE)
                            layer_seeds.Add(new Vector3i(i, j, k));
                    }
                }

                layer_seeds.Sort((a, b) => {
                    Vector3i pa = a; pa.y = 0;
                    Vector3i pb = b; pb.y = 0;
                    int sa = (pa-center_idx).LengthSquared, sb = (pb-center_idx).LengthSquared;
                    return sa.CompareTo(sb);
                });

                // reversing sort order is intresting?
                if(reverse_per_layer)
                    layer_seeds.Reverse();

                seeds.AddRange(layer_seeds);
            }
            HashSet<Vector3i> seed_indices = new HashSet<Vector3i>(seeds);

            // gives very different results...
            if (ProcessBottomUp == false)
                seeds.Reverse();

            // for linear index a, is this a node we allow in graph? (ie graph bounds)
            Func<int, bool> node_filter_f = (a) => {
                Vector3i ai = costGrid.to_index(a);
                // why not y check??
                return ai.x > 0 &&  ai.z > 0 && ai.x != ni - 1 && ai.y != nj - 1 && ai.z != nk - 1;
            };

            // distance from linear index a to linear index b
            // this defines the cost field we want to find shortest path through
            Func<int, int, float> node_dist_f = (a, b) => {
                Vector3i ai = costGrid.to_index(a), bi = costGrid.to_index(b);
                if (bi.y >= ai.y)               // b.y should always be a.y-1
                    return float.MaxValue;
                float sg = supportGrid[bi];

                // don't connect to tips
                //if (sg == SUPPORT_TIP_BASE || sg == SUPPORT_TIP_TOP)
                //    return float.MaxValue;
                if (sg == SUPPORT_TIP_TOP)
                    return float.MaxValue;

                if (sg < 0)
                    return -999999;    // if b is already used, we will terminate there, so this is a good choice

                // otherwise cost is sqr-grid-distance + costGrid value  (which is basically distance to surface)
                float c = costGrid[b];
                float f = (float)(Math.Sqrt((bi - ai).LengthSquared) * CellSize);
                //float f = 0;
                return c + f;
            };

            // which linear-index nbrs to consider for linear index a
            Func<int, IEnumerable<int>> neighbour_f = (a) => {
                Vector3i ai = costGrid.to_index(a);
                return down_neighbours(ai, costGrid);
            };

            // when do we terminate
            Func<int, bool> terminate_f = (a) => {
                Vector3i ai = costGrid.to_index(a);
                // terminate if we hit existing support path
                if (seed_indices.Contains(ai) == false && supportGrid[ai] < 0)
                    return true;
                // terminate if we hit ground plane
                if (ai.y == 0)
                    return true;
                return false;
            };

            DijkstraGraphDistance dijkstra = new DijkstraGraphDistance(ni * nj * nk, false,
                node_filter_f, node_dist_f, neighbour_f);
            dijkstra.TrackOrder = true;

            List<int> path = new List<int>();

            Graph = new DGraph3();
            Dictionary<Vector3i, int> CellToGraph = new Dictionary<Vector3i, int>();
            TipVertices = new HashSet<int>();
            TipBaseVertices = new HashSet<int>();
            GroundVertices = new HashSet<int>();

            // seeds are tip-base points
            for (int k = 0; k < seeds.Count; ++k) {
                // add seed point (which is a tip-base vertex) as seed for dijkstra prop
                int seed = costGrid.to_linear(seeds[k]);
                dijkstra.Reset();
                dijkstra.AddSeed(seed, 0);

                // compute to termination (ground, existing node, etc)
                int base_node = dijkstra.ComputeToNode(terminate_f);
                if (base_node < 0)
                    base_node = dijkstra.GetOrder().Last();

                // extract the path
                path.Clear();
                dijkstra.GetPathToSeed(base_node, path);
                int N = path.Count;

                // first point on path is termination point.
                // create vertex for it if we have not yet
                Vector3i basept_idx = supportGrid.to_index(path[0]);
                int basept_vid;
                if ( CellToGraph.TryGetValue(basept_idx, out basept_vid) == false ) {
                    Vector3d curv = get_cell_center(basept_idx);
                    if (basept_idx.y == 0) {
                        curv.y = 0;
                    }
                    basept_vid = Graph.AppendVertex(curv);
                    if (basept_idx.y == 0) {
                        GroundVertices.Add(basept_vid);
                    }
                    CellToGraph[basept_idx] = basept_vid;
                }

                int cur_vid = basept_vid;

                // now walk up path and create vertices as necessary
                for (int i = 0; i < N; ++i) {
                    int idx = path[i];
                    if ( supportGrid[idx] >= 0 )
                        supportGrid[idx] = SUPPORT_GRID_USED;
                    if ( i > 0 ) {
                        Vector3i next_idx = supportGrid.to_index(path[i]);
                        int next_vid;
                        if (CellToGraph.TryGetValue(next_idx, out next_vid) == false) {
                            Vector3d nextv = get_cell_center(next_idx);
                            next_vid = Graph.AppendVertex(nextv);
                            CellToGraph[next_idx] = next_vid;
                        }
                        Graph.AppendEdge(cur_vid, next_vid);
                        cur_vid = next_vid;
                    }
                }

                // seed was tip-base so we should always get back there. Then we
                // explicitly add tip-top and edge to it.
                if ( supportGrid[path[N-1]] == SUPPORT_TIP_BASE ) {
                    Vector3i vec_idx = supportGrid.to_index(path[N-1]);
                    TipBaseVertices.Add(CellToGraph[vec_idx]);

                    Vector3i tip_idx = vec_idx + Vector3i.AxisY;
                    int tip_vid;
                    if (CellToGraph.TryGetValue(tip_idx, out tip_vid) == false) {
                        Vector3d tipv = get_cell_center(tip_idx);
                        tip_vid = Graph.AppendVertex(tipv);
                        CellToGraph[tip_idx] = tip_vid;
                        Graph.AppendEdge(cur_vid, tip_vid);
                        TipVertices.Add(tip_vid);
                    }
                }

            }

            /*
             * Snap tips to surface
             */

            gParallel.ForEach(TipVertices, (tip_vid) => {
                bool snapped = false;
                Vector3d v = Graph.GetVertex(tip_vid);
                Frame3f hitF;
                // try shooting ray straight up. if that hits, and point is close, we use it
                if (MeshQueries.RayHitPointFrame(Mesh, MeshSpatial, new Ray3d(v, Vector3d.AxisY), out hitF)) {
                    if (v.Distance(hitF.Origin) < 2 * CellSize) {
                        v = hitF.Origin;
                        snapped = true;
                    }
                }

                // if that failed, try straight down
                if (!snapped) {
                    if (MeshQueries.RayHitPointFrame(Mesh, MeshSpatial, new Ray3d(v, -Vector3d.AxisY), out hitF)) {
                        if (v.Distance(hitF.Origin) < CellSize) {
                            v = hitF.Origin;
                            snapped = true;
                        }
                    }
                }

                // if it missed, or hit pt was too far, find nearest point and try that
                if (!snapped) {
                    hitF = MeshQueries.NearestPointFrame(Mesh, MeshSpatial, v);
                    if (v.Distance(hitF.Origin) < 2 * CellSize) {
                        v = hitF.Origin;
                        snapped = true;
                    }
                    // can this ever fail? tips should always be within 2 cells...
                }
                if (snapped)
                    Graph.SetVertex(tip_vid, v);
            });
        }
        void constrained_smooth(DGraph3 graph, double surfDist, double dotThresh, double alpha, int rounds)
        {
            int NV = graph.MaxVertexID;
            Vector3d[] pos = new Vector3d[NV];

            for (int ri = 0; ri < rounds; ++ri) {

                gParallel.ForEach(graph.VertexIndices(), (vid) => {
                    Vector3d v = graph.GetVertex(vid);

                    if ( GroundVertices.Contains(vid) || TipVertices.Contains(vid) ) {
                        pos[vid] = v;
                        return;
                    }

                    // for tip base vertices, we could allow them to move down and away within angle cone...
                    if (TipBaseVertices.Contains(vid)) {
                        pos[vid] = v;
                        return;
                    }

                    // compute smoothed position of vtx
                    Vector3d centroid = Vector3d.Zero; int nbr_count = 0;
                    foreach (int nbr_vid in graph.VtxVerticesItr(vid)) {
                        centroid += graph.GetVertex(nbr_vid);
                        nbr_count++;
                    }
                    if (nbr_count == 1) {
                        pos[vid] = v;
                        return;
                    }
                    centroid /= nbr_count;
                    Vector3d vnew = (1 - alpha) * v + (alpha) * centroid;

                    // make sure we don't violate angle constraint to any nbrs
                    int attempt = 0;
                    try_again:
                    foreach ( int nbr_vid in graph.VtxVerticesItr(vid)) {
                        Vector3d dv = graph.GetVertex(nbr_vid) - vnew;
                        dv.Normalize();
                        double dot = dv.Dot(Vector3d.AxisY);
                        if ( Math.Abs(dot) < dotThresh ) {
                            if (attempt++ < 3) {
                                vnew = Vector3d.Lerp(v, vnew, 0.66);
                                goto try_again;
                            } else {
                                pos[vid] = v;
                                return;
                            }
                        }
                    }

                    // offset from nearest point on surface
                    Frame3f fNearest = MeshQueries.NearestPointFrame(Mesh, MeshSpatial, vnew, true);
                    Vector3d vNearest = fNearest.Origin;
                    double dist = vnew.Distance(vNearest);
                    bool inside = MeshSpatial.IsInside(vnew);

                    if (inside || dist < surfDist) {
                        Vector3d normal = fNearest.Z;
                        // don't push down?
                        if (normal.Dot(Vector3d.AxisY) < 0) {
                            normal.y = 0; normal.Normalize();
                        }
                        vnew = fNearest.Origin + surfDist * normal;
                    }

                    pos[vid] = vnew;
                });

                foreach (int vid in graph.VertexIndices())
                    graph.SetVertex(vid, pos[vid]);
            }
        }