示例#1
0
        void remove_remaining_interior_verts()
        {
            HashSet <int> interiorv  = new HashSet <int>(MeshIterators.InteriorVertices(fillmesh));
            int           prev_count = 0;

            while (interiorv.Count > 0 && interiorv.Count != prev_count)
            {
                prev_count = interiorv.Count;
                int[] curv = interiorv.ToArray();
                foreach (int vid in curv)
                {
                    foreach (int e in fillmesh.VtxEdgesItr(vid))
                    {
                        Index2i ev     = fillmesh.GetEdgeV(e);
                        int     otherv = (ev.a == vid) ? ev.b : ev.a;
                        DMesh3.EdgeCollapseInfo info;
                        MeshResult result = fillmesh.CollapseEdge(otherv, vid, out info);
                        if (result == MeshResult.Ok)
                        {
                            break;
                        }
                    }
                    if (fillmesh.IsVertex(vid) == false)
                    {
                        interiorv.Remove(vid);
                    }
                }
            }
            if (interiorv.Count > 0)
            {
                Util.gBreakToDebugger();
            }
        }
示例#2
0
        public static void collapse_tests(bool bTestBoundary, int N = 100)
        {
            bool write_debug_meshes = false;

            DMesh3 mesh = TestUtil.MakeCappedCylinder(bTestBoundary);

            mesh.CheckValidity();

            System.Console.WriteLine(string.Format("DMesh3:collapse_tests() starting - test bdry {2}, verts {0} tris {1}",
                                                   mesh.VertexCount, mesh.TriangleCount, bTestBoundary));

            if (write_debug_meshes)
            {
                TestUtil.WriteDebugMesh(mesh, string.Format("before_collapse_{0}.obj", ((bTestBoundary)?"boundary":"noboundary")));
            }


            Random r = new Random(31377);

            for (int k = 0; k < N; ++k)
            {
                int eid = r.Next() % mesh.EdgeCount;
                if (!mesh.IsEdge(eid))
                {
                    continue;
                }
                //bool bBoundary = mesh.IsBoundaryEdge(eid);
                //if (bTestBoundary && bBoundary == false)
                //	 continue;
                Index2i ev = mesh.GetEdgeV(eid);

                DMesh3.EdgeCollapseInfo collapseInfo;
                MeshResult result = mesh.CollapseEdge(ev[0], ev[1], out collapseInfo);
                Debug.Assert(
                    result != MeshResult.Failed_NotAnEdge &&
                    result != MeshResult.Failed_FoundDuplicateTriangle);

                mesh.CheckValidity();
            }

            System.Console.WriteLine(string.Format("random collapses ok - verts {0} tris {1}",
                                                   mesh.VertexCount, mesh.TriangleCount));


            collapse_to_convergence(mesh);

            System.Console.WriteLine(string.Format("all possible collapses ok - verts {0} tris {1}",
                                                   mesh.VertexCount, mesh.TriangleCount));

            if (write_debug_meshes)
            {
                TestUtil.WriteDebugMesh(mesh, string.Format("after_collapse_{0}.obj", ((bTestBoundary)?"boundary":"noboundary")));
            }
        }
示例#3
0
        bool collapse_degenerate_edges(
            double minLength, bool bBoundaryOnly,
            out int collapseCount)
        {
            collapseCount = 0;
            // don't iterate sequentially because there may be pathological cases
            foreach (int eid in MathUtil.ModuloIteration(Mesh.MaxEdgeID))
            {
                if (Cancelled())
                {
                    break;
                }

                if (Mesh.IsEdge(eid) == false)
                {
                    continue;
                }

                bool is_boundary_edge = Mesh.IsBoundaryEdge(eid);
                if (bBoundaryOnly && is_boundary_edge == false)
                {
                    continue;
                }

                Index2i  ev = Mesh.GetEdgeV(eid);
                Vector3d a = Mesh.GetVertex(ev.a), b = Mesh.GetVertex(ev.b);
                if (a.Distance(b) < minLength)
                {
                    int keep    = Mesh.IsBoundaryVertex(ev.a) ? ev.a : ev.b;
                    int discard = (keep == ev.a) ? ev.b : ev.a;
                    DMesh3.EdgeCollapseInfo collapseInfo;
                    MeshResult result = Mesh.CollapseEdge(keep, discard, out collapseInfo);
                    if (result == MeshResult.Ok)
                    {
                        ++collapseCount;
                        if (Mesh.IsBoundaryVertex(keep) == false || is_boundary_edge)
                        {
                            Mesh.SetVertex(keep, (a + b) * 0.5);
                        }
                    }
                }
            }
            return(true);
        }
示例#4
0
        private bool CollapseDegenerateEdges(DMesh3 mesh,
                                             CancellationToken cancellationToken,
                                             double minLength,
                                             bool bBoundaryOnly,
                                             out int collapseCount)
        {
            collapseCount = 0;
            // don't iterate sequentially because there may be pathological cases
            foreach (int eid in MathUtil.ModuloIteration(mesh.MaxEdgeID))
            {
                cancellationToken.ThrowIfCancellationRequested();
                if (mesh.IsEdge(eid) == false)
                {
                    continue;
                }

                bool isBoundaryEdge = mesh.IsBoundaryEdge(eid);
                if (bBoundaryOnly && isBoundaryEdge == false)
                {
                    continue;
                }

                Index2i  ev = mesh.GetEdgeV(eid);
                Vector3d a = mesh.GetVertex(ev.a), b = mesh.GetVertex(ev.b);
                if (a.Distance(b) < minLength)
                {
                    int        keep    = mesh.IsBoundaryVertex(ev.a) ? ev.a : ev.b;
                    int        discard = (keep == ev.a) ? ev.b : ev.a;
                    MeshResult result  = mesh.CollapseEdge(keep, discard, out DMesh3.EdgeCollapseInfo collapseInfo);
                    if (result == MeshResult.Ok)
                    {
                        ++collapseCount;
                        if (mesh.IsBoundaryVertex(keep) == false || isBoundaryEdge)
                        {
                            mesh.SetVertex(keep, (a + b) * 0.5);
                        }
                    }
                }
            }

            return(true);
        }
示例#5
0
        // this function collapses edges until it can't anymore
        static void collapse_to_convergence(DMesh3 mesh)
        {
            bool bContinue = true;

            while (bContinue)
            {
                bContinue = false;
                for (int eid = 0; eid < mesh.MaxEdgeID; ++eid)
                {
                    if (!mesh.IsEdge(eid))
                    {
                        continue;
                    }
                    Index2i ev = mesh.GetEdgeV(eid);
                    DMesh3.EdgeCollapseInfo collapseInfo;
                    MeshResult result = mesh.CollapseEdge(ev[0], ev[1], out collapseInfo);
                    if (result == MeshResult.Ok)
                    {
                        bContinue = true;
                        break;
                    }
                }
            }
        }
示例#6
0
        public bool Apply()
        {
            // do a simple fill
            SimpleHoleFiller simplefill = new SimpleHoleFiller(Mesh, FillLoop);
            int  fill_gid = Mesh.AllocateTriangleGroup();
            bool bOK      = simplefill.Fill(fill_gid);

            if (bOK == false)
            {
                return(false);
            }

            if (FillLoop.Vertices.Length <= 3)
            {
                FillTriangles = simplefill.NewTriangles;
                FillVertices  = new int[0];
                return(true);
            }

            // extract the simple fill mesh as a submesh, via RegionOperator, so we can backsub later
            HashSet <int> intial_fill_tris = new HashSet <int>(simplefill.NewTriangles);

            regionop = new RegionOperator(Mesh, simplefill.NewTriangles,
                                          (submesh) => { submesh.ComputeTriMaps = true; });
            fillmesh = regionop.Region.SubMesh;

            // for each boundary vertex, compute the exterior angle sum
            // we will use this to compute gaussian curvature later
            boundaryv           = new HashSet <int>(MeshIterators.BoundaryEdgeVertices(fillmesh));
            exterior_angle_sums = new Dictionary <int, double>();
            if (IgnoreBoundaryTriangles == false)
            {
                foreach (int sub_vid in boundaryv)
                {
                    double angle_sum = 0;
                    int    base_vid  = regionop.Region.MapVertexToBaseMesh(sub_vid);
                    foreach (int tid in regionop.BaseMesh.VtxTrianglesItr(base_vid))
                    {
                        if (intial_fill_tris.Contains(tid) == false)
                        {
                            Index3i et  = regionop.BaseMesh.GetTriangle(tid);
                            int     idx = IndexUtil.find_tri_index(base_vid, ref et);
                            angle_sum += regionop.BaseMesh.GetTriInternalAngleR(tid, idx);
                        }
                    }
                    exterior_angle_sums[sub_vid] = angle_sum;
                }
            }


            // try to guess a reasonable edge length that will give us enough geometry to work with in simplify pass
            double loop_mine, loop_maxe, loop_avge, fill_mine, fill_maxe, fill_avge;

            MeshQueries.EdgeLengthStatsFromEdges(Mesh, FillLoop.Edges, out loop_mine, out loop_maxe, out loop_avge);
            MeshQueries.EdgeLengthStats(fillmesh, out fill_mine, out fill_maxe, out fill_avge);
            double remesh_target_len = loop_avge;

            if (fill_maxe / remesh_target_len > 10)
            {
                remesh_target_len = fill_maxe / 10;
            }
            //double remesh_target_len = Math.Min(loop_avge, fill_avge / 4);

            // remesh up to target edge length, ideally gives us some triangles to work with
            RemesherPro remesh1 = new RemesherPro(fillmesh);

            remesh1.SmoothSpeedT = 1.0;
            MeshConstraintUtil.FixAllBoundaryEdges(remesh1);
            //remesh1.SetTargetEdgeLength(remesh_target_len / 2);       // would this speed things up? on large regions?
            //remesh1.FastestRemesh();
            remesh1.SetTargetEdgeLength(remesh_target_len);
            remesh1.FastestRemesh();

            /*
             * first round: collapse to minimal mesh, while flipping to try to
             * get to ballpark minimal mesh. We stop these passes as soon as
             * we have done two rounds where we couldn't do another collapse
             *
             * This is the most unstable part of the algorithm because there
             * are strong ordering effects. maybe we could sort the edges somehow??
             */

            int zero_collapse_passes = 0;
            int collapse_passes      = 0;

            while (collapse_passes++ < 20 && zero_collapse_passes < 2)
            {
                // collapse pass
                int NE        = fillmesh.MaxEdgeID;
                int collapses = 0;
                for (int ei = 0; ei < NE; ++ei)
                {
                    if (fillmesh.IsEdge(ei) == false || fillmesh.IsBoundaryEdge(ei))
                    {
                        continue;
                    }
                    Index2i ev = fillmesh.GetEdgeV(ei);
                    bool    a_bdry = boundaryv.Contains(ev.a), b_bdry = boundaryv.Contains(ev.b);
                    if (a_bdry && b_bdry)
                    {
                        continue;
                    }
                    int      keepv  = (a_bdry) ? ev.a : ev.b;
                    int      otherv = (keepv == ev.a) ? ev.b : ev.a;
                    Vector3d newv   = fillmesh.GetVertex(keepv);
                    if (MeshUtil.CheckIfCollapseCreatesFlip(fillmesh, ei, newv))
                    {
                        continue;
                    }
                    DMesh3.EdgeCollapseInfo info;
                    MeshResult result = fillmesh.CollapseEdge(keepv, otherv, out info);
                    if (result == MeshResult.Ok)
                    {
                        collapses++;
                    }
                }
                if (collapses == 0)
                {
                    zero_collapse_passes++;
                }
                else
                {
                    zero_collapse_passes = 0;
                }

                // flip pass. we flip in these cases:
                //  1) if angle between current triangles is too small (slightly more than 90 degrees, currently)
                //  2) if angle between flipped triangles is smaller than between current triangles
                //  3) if flipped edge length is shorter *and* such a flip won't flip the normal
                NE = fillmesh.MaxEdgeID;
                Vector3d n1, n2, on1, on2;
                for (int ei = 0; ei < NE; ++ei)
                {
                    if (fillmesh.IsEdge(ei) == false || fillmesh.IsBoundaryEdge(ei))
                    {
                        continue;
                    }
                    bool do_flip = false;

                    Index2i ev = fillmesh.GetEdgeV(ei);
                    MeshUtil.GetEdgeFlipNormals(fillmesh, ei, out n1, out n2, out on1, out on2);
                    double dot_cur  = n1.Dot(n2);
                    double dot_flip = on1.Dot(on2);
                    if (n1.Dot(n2) < 0.1 || dot_flip > dot_cur + MathUtil.Epsilonf)
                    {
                        do_flip = true;
                    }

                    if (do_flip == false)
                    {
                        Index2i otherv   = fillmesh.GetEdgeOpposingV(ei);
                        double  len_e    = fillmesh.GetVertex(ev.a).Distance(fillmesh.GetVertex(ev.b));
                        double  len_flip = fillmesh.GetVertex(otherv.a).Distance(fillmesh.GetVertex(otherv.b));
                        if (len_flip < len_e)
                        {
                            if (MeshUtil.CheckIfEdgeFlipCreatesFlip(fillmesh, ei) == false)
                            {
                                do_flip = true;
                            }
                        }
                    }

                    if (do_flip)
                    {
                        DMesh3.EdgeFlipInfo info;
                        MeshResult          result = fillmesh.FlipEdge(ei, out info);
                    }
                }
            }

            // Sometimes, for some reason, we have a remaining interior vertex (have only ever seen one?)
            // Try to force removal of such vertices, even if it makes ugly mesh
            remove_remaining_interior_verts();


            // enable/disable passes.
            bool DO_FLATTER_PASS   = true;
            bool DO_CURVATURE_PASS = OptimizeDevelopability && true;
            bool DO_AREA_PASS      = OptimizeDevelopability && OptimizeTriangles && true;


            /*
             * In this pass we repeat the flipping iterations from the previous pass.
             *
             * Note that because of the always-flip-if-dot-is-small case (commented),
             * this pass will frequently not converge, as some number of edges will
             * be able to flip back and forth (because neither has large enough dot).
             * This is not ideal, but also, if we remove this behavior, then we
             * generally get worse fills. This case basically introduces a sort of
             * randomization factor that lets us escape local minima...
             *
             */

            HashSet <int> remaining_edges = new HashSet <int>(fillmesh.EdgeIndices());
            HashSet <int> updated_edges   = new HashSet <int>();

            int flatter_passes    = 0;
            int zero_flips_passes = 0;

            while (flatter_passes++ < 40 && zero_flips_passes < 2 && remaining_edges.Count() > 0 && DO_FLATTER_PASS)
            {
                zero_flips_passes++;
                foreach (int ei in remaining_edges)
                {
                    if (fillmesh.IsBoundaryEdge(ei))
                    {
                        continue;
                    }

                    bool do_flip = false;

                    Index2i  ev = fillmesh.GetEdgeV(ei);
                    Vector3d n1, n2, on1, on2;
                    MeshUtil.GetEdgeFlipNormals(fillmesh, ei, out n1, out n2, out on1, out on2);
                    double dot_cur  = n1.Dot(n2);
                    double dot_flip = on1.Dot(on2);
                    if (flatter_passes < 20 && dot_cur < 0.1)   // this check causes oscillatory behavior
                    {
                        do_flip = true;
                    }
                    if (dot_flip > dot_cur + MathUtil.Epsilonf)
                    {
                        do_flip = true;
                    }

                    if (do_flip)
                    {
                        DMesh3.EdgeFlipInfo info;
                        MeshResult          result = fillmesh.FlipEdge(ei, out info);
                        if (result == MeshResult.Ok)
                        {
                            zero_flips_passes = 0;
                            add_all_edges(ei, updated_edges);
                        }
                    }
                }

                var tmp = remaining_edges;
                remaining_edges = updated_edges;
                updated_edges   = tmp; updated_edges.Clear();
            }


            int curvature_passes = 0;

            if (DO_CURVATURE_PASS)
            {
                curvatures = new double[fillmesh.MaxVertexID];
                foreach (int vid in fillmesh.VertexIndices())
                {
                    update_curvature(vid);
                }

                remaining_edges = new HashSet <int>(fillmesh.EdgeIndices());
                updated_edges   = new HashSet <int>();

                /*
                 *  In this pass we try to minimize gaussian curvature at all the vertices.
                 *  This will recover sharp edges, etc, and do lots of good stuff.
                 *  However, this pass will not make much progress if we are not already
                 *  relatively close to a minimal mesh, so it really relies on the previous
                 *  passes getting us in the ballpark.
                 */
                while (curvature_passes++ < 40 && remaining_edges.Count() > 0 && DO_CURVATURE_PASS)
                {
                    foreach (int ei in remaining_edges)
                    {
                        if (fillmesh.IsBoundaryEdge(ei))
                        {
                            continue;
                        }

                        Index2i ev = fillmesh.GetEdgeV(ei);
                        Index2i ov = fillmesh.GetEdgeOpposingV(ei);

                        int find_other = fillmesh.FindEdge(ov.a, ov.b);
                        if (find_other != DMesh3.InvalidID)
                        {
                            continue;
                        }

                        double total_curv_cur = curvature_metric_cached(ev.a, ev.b, ov.a, ov.b);
                        if (total_curv_cur < MathUtil.ZeroTolerancef)
                        {
                            continue;
                        }

                        DMesh3.EdgeFlipInfo info;
                        MeshResult          result = fillmesh.FlipEdge(ei, out info);
                        if (result != MeshResult.Ok)
                        {
                            continue;
                        }

                        double total_curv_flip = curvature_metric_eval(ev.a, ev.b, ov.a, ov.b);

                        bool keep_flip = total_curv_flip < total_curv_cur - MathUtil.ZeroTolerancef;
                        if (keep_flip == false)
                        {
                            result = fillmesh.FlipEdge(ei, out info);
                        }
                        else
                        {
                            update_curvature(ev.a); update_curvature(ev.b);
                            update_curvature(ov.a); update_curvature(ov.b);
                            add_all_edges(ei, updated_edges);
                        }
                    }
                    var tmp = remaining_edges;
                    remaining_edges = updated_edges;
                    updated_edges   = tmp; updated_edges.Clear();
                }
            }
            //System.Console.WriteLine("collapse {0}   flatter {1}   curvature {2}", collapse_passes, flatter_passes, curvature_passes);

            /*
             * In this final pass, we try to improve triangle quality. We flip if
             * the flipped triangles have better total aspect ratio, and the
             * curvature doesn't change **too** much. The .DevelopabilityTolerance
             * parameter determines what is "too much" curvature change.
             */
            if (DO_AREA_PASS)
            {
                remaining_edges = new HashSet <int>(fillmesh.EdgeIndices());
                updated_edges   = new HashSet <int>();
                int area_passes = 0;
                while (remaining_edges.Count() > 0 && area_passes < 20)
                {
                    area_passes++;
                    foreach (int ei in remaining_edges)
                    {
                        if (fillmesh.IsBoundaryEdge(ei))
                        {
                            continue;
                        }

                        Index2i ev = fillmesh.GetEdgeV(ei);
                        Index2i ov = fillmesh.GetEdgeOpposingV(ei);

                        int find_other = fillmesh.FindEdge(ov.a, ov.b);
                        if (find_other != DMesh3.InvalidID)
                        {
                            continue;
                        }

                        double total_curv_cur = curvature_metric_cached(ev.a, ev.b, ov.a, ov.b);

                        double a = aspect_metric(ei);
                        if (a > 1)
                        {
                            continue;
                        }

                        DMesh3.EdgeFlipInfo info;
                        MeshResult          result = fillmesh.FlipEdge(ei, out info);
                        if (result != MeshResult.Ok)
                        {
                            continue;
                        }

                        double total_curv_flip = curvature_metric_eval(ev.a, ev.b, ov.a, ov.b);

                        bool keep_flip = Math.Abs(total_curv_cur - total_curv_flip) < DevelopabilityTolerance;
                        if (keep_flip == false)
                        {
                            result = fillmesh.FlipEdge(ei, out info);
                        }
                        else
                        {
                            update_curvature(ev.a); update_curvature(ev.b);
                            update_curvature(ov.a); update_curvature(ov.b);
                            add_all_edges(ei, updated_edges);
                        }
                    }
                    var tmp = remaining_edges;
                    remaining_edges = updated_edges;
                    updated_edges   = tmp; updated_edges.Clear();
                }
            }


            regionop.BackPropropagate();
            FillTriangles = regionop.CurrentBaseTriangles;
            FillVertices  = regionop.CurrentBaseInteriorVertices().ToArray();

            return(true);
        }
示例#7
0
        /// <summary>
        /// This function tries to remove vertices from loop to hit TargetVtxCount. We call this
        /// when polygon-insertion returns more vertices than polygon. Strategy is to try to find
        /// co-linear vertices, ie that can be removed without changing shape. If that fails,
        /// we remove vertices that result in smallest length change (probably should do proper simplification
        /// here instead).
        ///
        /// Basically this is to handle failures in MeshInsertUVPolyCurve.Simplify(), which sometimes
        /// fails to remove extra vertices because it would case triangle flips. Here we don't
        /// care about triangle flips.
        ///
        /// Note that if the input polygon had splits on edges, this function would remove those
        /// vertices. Which is not ideal.
        /// </summary>
        protected EdgeLoop simplify_loop(DMesh3 mesh, EdgeLoop loop, int TargetVtxCount)
        {
            while (loop.VertexCount > TargetVtxCount)
            {
                DCurve3 curve = loop.ToCurve();

                DMesh3.EdgeCollapseInfo cinfo;

                int NV = loop.VertexCount;
                for (int k = 1; k <= NV; ++k)
                {
                    int prevv = k - 1;
                    int curv  = k % NV;
                    int nextv = (k + 1) % NV;
                    //if (curve[prevv].Distance(curve[curv]) < 0.0001 ||
                    //        curve[nextv].Distance(curve[curv]) < 0.0001)
                    //    f3.DebugUtil.Log("DEGENERATE!!");
                    double angle = curve.OpeningAngleDeg(curv);
                    if (angle > 179)
                    {
                        MeshResult r = mesh.CollapseEdge(loop.Vertices[prevv], loop.Vertices[curv], out cinfo);
                        mesh.SetVertex(loop.Vertices[prevv], curve[prevv]);
                        if (r == MeshResult.Ok)
                        {
                            goto done_this_iter;
                        }
                        else
                        {
                            f3.DebugUtil.Log("collinear collapse failed!");
                        }
                    }
                }
                f3.DebugUtil.Log("Did not find collinear vert...");

                int i_shortest = -1; double shortest_len = double.MaxValue;
                for (int k = 1; k <= NV; ++k)
                {
                    int      prevv   = k - 1;
                    int      curv    = k % NV;
                    int      nextv   = (k + 1) % NV;
                    Vector3d pc      = curve[curv] - curve[prevv];
                    Vector3d pn      = curve[nextv] - curve[curv];
                    double   len_sum = pc.Length + pn.Length;
                    if (len_sum < shortest_len)
                    {
                        i_shortest   = curv;
                        shortest_len = len_sum;
                    }
                }
                if (i_shortest != -1)
                {
                    int        curv   = i_shortest;
                    int        prevv  = (curv == 0) ? NV - 1 : curv - 1;
                    int        nextv  = (curv + 1) % NV;
                    Vector3d   pc     = curve[curv] - curve[prevv];
                    Vector3d   pn     = curve[nextv] - curve[curv];
                    int        iWhich = (pc.Length < pn.Length) ? prevv : nextv;
                    MeshResult r      = mesh.CollapseEdge(loop.Vertices[iWhich], loop.Vertices[curv], out cinfo);
                    if (r == MeshResult.Ok)
                    {
                        goto done_this_iter;
                    }
                    else
                    {
                        f3.DebugUtil.Log("shortest failed!");
                    }
                }
                f3.DebugUtil.Log("Did not find shortest vert...");

                // if we didn't find a vert to remove yet, just arbitrarily remove first one
                int        v0 = loop.Vertices[0], v1 = loop.Vertices[1];
                MeshResult result = mesh.CollapseEdge(v1, v0, out cinfo);

done_this_iter:
                List <int> new_verts = new List <int>();
                for (int k = 0; k < loop.Vertices.Count(); ++k)
                {
                    if (mesh.IsVertex(loop.Vertices[k]))
                    {
                        new_verts.Add(loop.Vertices[k]);
                    }
                }
                loop = EdgeLoop.FromVertices(mesh, new_verts);
            }

            return(loop);
        }
        protected void merge_loops(DMesh3 mesh, EdgeLoop cutLoop, EdgeLoop connectorLoop, bool is_outer)
        {
            /*
             * To join the loops, we are going to first make a circle, then snap both
             * open loops to that circle. Then we sample a set of vertices on the circle
             * and remesh the loops while also snapping them to the circle vertices.
             * The result is two perfectly-matching edge loops.
             */

            //AxisAlignedBox3d cutLoopBounds =
            //    BoundsUtil.Bounds(cutLoop.Vertices, (vid) => { return mesh.GetVertex(vid); });

            AxisAlignedBox3d cutLoopBounds       = cutLoop.GetBounds();
            AxisAlignedBox3d connectorLoopBounds = connectorLoop.GetBounds();
            Vector3d         midPt = (cutLoopBounds.Center + connectorLoopBounds.Center) * 0.5;
            double           midY  = midPt.y;

            // this mess construcst the circle and the sampled version
            Frame3f           circFrame       = new Frame3f(midPt);
            Circle3d          circ            = new Circle3d(circFrame, connectorLoopBounds.Width * 0.5, 1);
            DistPoint3Circle3 dist            = new DistPoint3Circle3(Vector3d.Zero, circ);
            DCurve3           sampled         = new DCurve3();
            double            target_edge_len = TargetMeshEdgeLength;
            int N = (int)(circ.ArcLength / target_edge_len);

            for (int k = 0; k < N; ++k)
            {
                sampled.AppendVertex(circ.SampleT((double)k / (double)N));
            }

            MergeProjectionTarget circleTarget = new MergeProjectionTarget()
            {
                Mesh = mesh, CircleDist = dist, CircleLoop = sampled
            };

            EdgeLoop[] loops = new EdgeLoop[2] {
                cutLoop, connectorLoop
            };
            EdgeLoop[] outputLoops = new EdgeLoop[2];   // loops after this remeshing/etc (but might be missing some verts/edges!)
            for (int li = 0; li < 2; ++li)
            {
                EdgeLoop loop = loops[li];

                // snap the loop verts onto the analytic circle
                foreach (int vid in loop.Vertices)
                {
                    Vector3d v = mesh.GetVertex(vid);
                    dist.Point = new Vector3d(v.x, midY, v.z);
                    mesh.SetVertex(vid, dist.Compute().CircleClosest);
                }

                if (DebugStep <= 5)
                {
                    continue;
                }

                // remesh around the edge loop while we snap it to the sampled circle verts
                EdgeLoopRemesher loopRemesh = new EdgeLoopRemesher(mesh, loop)
                {
                    LocalSmoothingRings = 3
                };
                loopRemesh.EnableParallelProjection = false;
                loopRemesh.SetProjectionTarget(circleTarget);
                loopRemesh.SetTargetEdgeLength(TargetMeshEdgeLength);
                loopRemesh.SmoothSpeedT = 0.5f;
                for (int k = 0; k < 5; ++k)
                {
                    loopRemesh.BasicRemeshPass();
                }
                loopRemesh.SmoothSpeedT = 0;
                for (int k = 0; k < 2; ++k)
                {
                    loopRemesh.BasicRemeshPass();
                }
                EdgeLoop newLoop = loopRemesh.OutputLoop;
                outputLoops[li] = newLoop;

                if (DebugStep <= 6)
                {
                    continue;
                }

                // hard snap the loop vertices to the sampled circle verts
                foreach (int vid in newLoop.Vertices)
                {
                    Vector3d v = mesh.GetVertex(vid);
                    v = circleTarget.Project(v, vid);
                    mesh.SetVertex(vid, v);
                }

                // [TODO] we could re-order newLoop verts/edges to match the sampled verts order,
                // then the pair of loops would be consistently ordered (currently no guarantee)

                if (DebugStep <= 7)
                {
                    continue;
                }

                // collapse any degenerate edges on loop (just in case)
                // DANGER: if this actually happens, then outputLoops[li] has some invalid verts/edges!
                foreach (int eid in newLoop.Edges)
                {
                    if (mesh.IsEdge(eid))
                    {
                        Index2i  ev = mesh.GetEdgeV(eid);
                        Vector3d a = mesh.GetVertex(ev.a), b = mesh.GetVertex(ev.b);
                        if (a.Distance(b) < TargetMeshEdgeLength * 0.001)
                        {
                            DMesh3.EdgeCollapseInfo collapse;
                            mesh.CollapseEdge(ev.a, ev.b, out collapse);
                        }
                    }
                }
            }

            if (DebugStep <= 7)
            {
                return;
            }


            /*
             * Ok now we want to merge the loops and make them nice
             */

            // would be more efficient to find loops and stitch them...
            MergeCoincidentEdges merge = new MergeCoincidentEdges(mesh);

            merge.Apply();

            // fill any fail-holes??

            // remesh merge region
            MeshVertexSelection remesh_roi_v = new MeshVertexSelection(mesh);

            remesh_roi_v.Select(outputLoops[0].Vertices);
            remesh_roi_v.Select(outputLoops[1].Vertices);
            remesh_roi_v.ExpandToOneRingNeighbours(5);
            MeshFaceSelection remesh_roi = new MeshFaceSelection(mesh, remesh_roi_v, 1);

            remesh_roi.LocalOptimize(true, true);

            IProjectionTarget projTarget = null;

            if (is_outer)
            {
                projTarget = new NoPenetrationProjectionTarget()
                {
                    Spatial = this.OuterOffsetMeshSpatial
                };
            }
            else
            {
                projTarget = new NoPenetrationProjectionTarget()
                {
                    Spatial = this.InnerOffsetMeshSpatial
                };
            }

            RegionRemesher join_remesh =
                RegionRemesher.QuickRemesh(mesh, remesh_roi.ToArray(), TargetMeshEdgeLength, 0.5, 5, projTarget);

            if (DebugStep <= 8)
            {
                return;
            }


            if (false && is_outer)
            {
                Func <int, bool> filterF = (tid) => {
                    return(mesh.GetTriCentroid(tid).y > connectorLoopBounds.Max.y);
                };

                MeshFaceSelection tris = new MeshFaceSelection(mesh);
                foreach (int tid in join_remesh.CurrentBaseTriangles)
                {
                    if (filterF(tid))
                    {
                        tris.Select(tid);
                    }
                }
                tris.ExpandToOneRingNeighbours(5, filterF);

                MeshVertexSelection verts  = new MeshVertexSelection(mesh, tris);
                MeshIterativeSmooth smooth = new MeshIterativeSmooth(mesh, verts.ToArray(), true);
                smooth.Alpha    = 1.0f;
                smooth.Rounds   = 25;
                smooth.ProjectF = (v, n, vid) => { return(projTarget.Project(v)); };
                smooth.Smooth();
            }


            // [RMS] this smooths too far. we basically only want to smooth 'up' from top of socket...
            //LaplacianMeshSmoother.RegionSmooth(mesh, join_remesh.CurrentBaseTriangles, 1, 10);

            // need to post-enforce thickness, which we aren't doing above - we could though
        }