protected DMesh3 MakeDebugGraphMesh()
        {
            DMesh3 graphMesh = new DMesh3();

            graphMesh.EnableVertexColors(Vector3f.One);
            foreach (int vid in Graph.VertexIndices())
            {
                if (TipVertices.Contains(vid))
                {
                    MeshEditor.AppendBox(graphMesh, Graph.GetVertex(vid), 0.3f, Colorf.Green);
                }
                else if (TipBaseVertices.Contains(vid))
                {
                    MeshEditor.AppendBox(graphMesh, Graph.GetVertex(vid), 0.225f, Colorf.Magenta);
                }
                else if (GroundVertices.Contains(vid))
                {
                    MeshEditor.AppendBox(graphMesh, Graph.GetVertex(vid), 0.35f, Colorf.Blue);
                }
                else
                {
                    MeshEditor.AppendBox(graphMesh, Graph.GetVertex(vid), 0.15f, Colorf.White);
                }
            }
            foreach (int eid in Graph.EdgeIndices())
            {
                Segment3d seg = Graph.GetEdgeSegment(eid);
                MeshEditor.AppendLine(graphMesh, seg, 0.1f);
            }
            return(graphMesh);
        }
        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]);
                }
            }
        }