Exemplo n.º 1
0
        }         // Cut()

        protected void collapse_degenerate_edges(HashSet <int> OnCutEdges, HashSet <int> ZeroEdges)
        {
            var sets = new HashSet <int>[2] {
                OnCutEdges, ZeroEdges
            };

            double   tol2 = DegenerateEdgeTol * DegenerateEdgeTol;
            Vector3d a = Vector3d.Zero, b = Vector3d.Zero;
            int      collapsed = 0;

            do
            {
                collapsed = 0;
                foreach (var edge_set in sets)
                {
                    foreach (int eid in edge_set)
                    {
                        if (Mesh.IsEdge(eid) == false)
                        {
                            continue;
                        }

                        Mesh.GetEdgeV(eid, ref a, ref b);
                        if (a.DistanceSquared(b) > tol2)
                        {
                            continue;
                        }

                        Index2i ev = Mesh.GetEdgeV(eid);
                        DMesh3.EdgeCollapseInfo collapseInfo;
                        MeshResult result = Mesh.CollapseEdge(ev.a, ev.b, out collapseInfo);
                        if (result == MeshResult.Ok)
                        {
                            collapsed++;
                        }
                    }
                }
            } while (collapsed != 0);
        }
Exemplo n.º 2
0
        public Vector3d Project(Vector3d vPoint, int identifier = -1)
        {
            Vector3d vNearest    = Vector3d.Zero;
            double   fNearestSqr = double.MaxValue;

            int N     = Curve.VertexCount;
            int NStop = (Curve.Closed) ? N : N - 1;

            for (int i = 0; i < NStop; ++i)
            {
                Segment3d seg  = new Segment3d(Curve[i], Curve[(i + 1) % N]);
                Vector3d  pt   = seg.NearestPoint(vPoint);
                double    dsqr = pt.DistanceSquared(vPoint);
                if (dsqr < fNearestSqr)
                {
                    fNearestSqr = dsqr;
                    vNearest    = pt;
                }
            }

            return((fNearestSqr < double.MaxValue) ? vNearest : vPoint);
        }
Exemplo n.º 3
0
        /// <summary>
        /// find closest vertex, within searchRadius
        /// </summary>
        protected int find_nearest_vertex(Vector3d pt, double searchRadius, int ignore_vid = -1)
        {
            KeyValuePair <int, double> found = (ignore_vid == -1) ?
                                               PointHash.FindNearestInRadius(pt, searchRadius,
                                                                             (b) => { return(pt.DistanceSquared(Target.GetVertex(b))); })
                            :
                                               PointHash.FindNearestInRadius(pt, searchRadius,
                                                                             (b) => { return(pt.DistanceSquared(Target.GetVertex(b))); },
                                                                             (vid) => { return(vid == ignore_vid); });

            if (found.Key == PointHash.InvalidValue)
            {
                return(-1);
            }
            return(found.Key);
        }
Exemplo n.º 4
0
        protected void find_nearest_point(int iBox, Vector3d p, ref double fNearestSqr, ref int tID)
        {
            int idx = box_to_index[iBox];

            if (idx < points_end)                // point-list case, array is [N t1 t2 ... tN]
            {
                int num_points = index_list[idx];
                for (int i = 1; i <= num_points; ++i)
                {
                    int ti = index_list[idx + i];
                    if (PointFilterF != null && PointFilterF(ti) == false)
                    {
                        continue;
                    }
                    Vector3d pt       = points.GetVertex(ti);
                    double   fDistSqr = pt.DistanceSquared(p);
                    if (fDistSqr < fNearestSqr)
                    {
                        fNearestSqr = fDistSqr;
                        tID         = ti;
                    }
                }
            }
            else                                    // internal node, either 1 or 2 child boxes
            {
                int iChild1 = index_list[idx];
                if (iChild1 < 0)                     // 1 child, descend if nearer than cur min-dist
                {
                    iChild1 = (-iChild1) - 1;
                    double fChild1DistSqr = box_distance_sqr(iChild1, ref p);
                    if (fChild1DistSqr <= fNearestSqr)
                    {
                        find_nearest_point(iChild1, p, ref fNearestSqr, ref tID);
                    }
                }
                else                                // 2 children, descend closest first
                {
                    iChild1 = iChild1 - 1;
                    int iChild2 = index_list[idx + 1] - 1;

                    double fChild1DistSqr = box_distance_sqr(iChild1, ref p);
                    double fChild2DistSqr = box_distance_sqr(iChild2, ref p);
                    if (fChild1DistSqr < fChild2DistSqr)
                    {
                        if (fChild1DistSqr < fNearestSqr)
                        {
                            find_nearest_point(iChild1, p, ref fNearestSqr, ref tID);
                            if (fChild2DistSqr < fNearestSqr)
                            {
                                find_nearest_point(iChild2, p, ref fNearestSqr, ref tID);
                            }
                        }
                    }
                    else
                    {
                        if (fChild2DistSqr < fNearestSqr)
                        {
                            find_nearest_point(iChild2, p, ref fNearestSqr, ref tID);
                            if (fChild1DistSqr < fNearestSqr)
                            {
                                find_nearest_point(iChild1, p, ref fNearestSqr, ref tID);
                            }
                        }
                    }
                }
            }
        }
Exemplo n.º 5
0
        protected virtual ProcessResult ProcessEdge(int edgeID)
        {
            RuntimeDebugCheck(edgeID);

            EdgeConstraint constraint =
                (constraints == null) ? EdgeConstraint.Unconstrained : constraints.GetEdgeConstraint(edgeID);

            if (constraint.NoModifications)
            {
                return(ProcessResult.Ignored_EdgeIsFullyConstrained);
            }

            // look up verts and tris for this edge
            int a = 0, b = 0, t0 = 0, t1 = 0;

            if (mesh.GetEdge(edgeID, ref a, ref b, ref t0, ref t1) == false)
            {
                return(ProcessResult.Failed_NotAnEdge);
            }
            bool bIsBoundaryEdge = (t1 == DMesh3.InvalidID);

            // look up 'other' verts c (from t0) and d (from t1, if it exists)
            Index2i ov = mesh.GetEdgeOpposingV(edgeID);
            int     c = ov.a, d = ov.b;

            Vector3d vA           = mesh.GetVertex(a);
            Vector3d vB           = mesh.GetVertex(b);
            double   edge_len_sqr = vA.DistanceSquared(vB);

            begin_collapse();

            // check if we should collapse, and also find which vertex we should collapse to,
            // in cases where we have constraints/etc
            int  collapse_to  = -1;
            bool bCanCollapse = EnableCollapses &&
                                constraint.CanCollapse &&
                                edge_len_sqr < MinEdgeLength * MinEdgeLength &&
                                can_collapse_constraints(edgeID, a, b, c, d, t0, t1, out collapse_to);

            // optimization: if edge cd exists, we cannot collapse or flip. look that up here?
            //  funcs will do it internally...
            //  (or maybe we can collapse if cd exists? edge-collapse doesn't check for it explicitly...)

            // if edge length is too short, we want to collapse it
            bool bTriedCollapse = false;

            if (bCanCollapse)
            {
                int      iKeep = b, iCollapse = a;
                Vector3d vNewPos = (vA + vB) * 0.5;

                // if either vtx is fixed, collapse to that position
                if (collapse_to == b)
                {
                    vNewPos = vB;
                }
                else if (collapse_to == a)
                {
                    iKeep   = a; iCollapse = b;
                    vNewPos = vA;
                }
                else
                {
                    vNewPos = get_projected_collapse_position(iKeep, vNewPos);
                }

                // if new position would flip normal of one of the existing triangles
                // either one-ring, don't allow it
                if (PreventNormalFlips)
                {
                    if (collapse_creates_flip_or_invalid(a, b, ref vNewPos, t0, t1) || collapse_creates_flip_or_invalid(b, a, ref vNewPos, t0, t1))
                    {
                        goto abort_collapse;
                    }
                }

                // TODO be smart about picking b (keep vtx).
                //    - swap if one is bdry vtx, for example?
                // lots of cases where we cannot collapse, but we should just let
                // mesh sort that out, right?
                COUNT_COLLAPSES++;
                DMesh3.EdgeCollapseInfo collapseInfo;
                MeshResult result = mesh.CollapseEdge(iKeep, iCollapse, out collapseInfo);
                if (result == MeshResult.Ok)
                {
                    mesh.SetVertex(iKeep, vNewPos);
                    if (constraints != null)
                    {
                        constraints.ClearEdgeConstraint(edgeID);
                        constraints.ClearEdgeConstraint(collapseInfo.eRemoved0);
                        if (collapseInfo.eRemoved1 != DMesh3.InvalidID)
                        {
                            constraints.ClearEdgeConstraint(collapseInfo.eRemoved1);
                        }
                        constraints.ClearVertexConstraint(iCollapse);
                    }
                    OnEdgeCollapse(edgeID, iKeep, iCollapse, collapseInfo);
                    DoDebugChecks();

                    return(ProcessResult.Ok_Collapsed);
                }
                else
                {
                    bTriedCollapse = true;
                }
            }
abort_collapse:

            end_collapse();
            begin_flip();

            // if this is not a boundary edge, maybe we want to flip
            bool bTriedFlip = false;

            if (EnableFlips && constraint.CanFlip && bIsBoundaryEdge == false)
            {
                // can we do this more efficiently somehow?
                bool a_is_boundary_vtx = (MeshIsClosed) ? false : (bIsBoundaryEdge || mesh.IsBoundaryVertex(a));
                bool b_is_boundary_vtx = (MeshIsClosed) ? false : (bIsBoundaryEdge || mesh.IsBoundaryVertex(b));
                bool c_is_boundary_vtx = (MeshIsClosed) ? false : mesh.IsBoundaryVertex(c);
                bool d_is_boundary_vtx = (MeshIsClosed) ? false :  mesh.IsBoundaryVertex(d);
                int  valence_a = mesh.GetVtxEdgeCount(a), valence_b = mesh.GetVtxEdgeCount(b);
                int  valence_c = mesh.GetVtxEdgeCount(c), valence_d = mesh.GetVtxEdgeCount(d);
                int  valence_a_target = (a_is_boundary_vtx) ? valence_a : 6;
                int  valence_b_target = (b_is_boundary_vtx) ? valence_b : 6;
                int  valence_c_target = (c_is_boundary_vtx) ? valence_c : 6;
                int  valence_d_target = (d_is_boundary_vtx) ? valence_d : 6;


                // if total valence error improves by flip, we want to do it
                int curr_err = Math.Abs(valence_a - valence_a_target) + Math.Abs(valence_b - valence_b_target)
                               + Math.Abs(valence_c - valence_c_target) + Math.Abs(valence_d - valence_d_target);
                int flip_err = Math.Abs((valence_a - 1) - valence_a_target) + Math.Abs((valence_b - 1) - valence_b_target)
                               + Math.Abs((valence_c + 1) - valence_c_target) + Math.Abs((valence_d + 1) - valence_d_target);

                bool bTryFlip = flip_err < curr_err;
                if (bTryFlip && PreventNormalFlips && flip_inverts_normals(a, b, c, d, t0))
                {
                    bTryFlip = false;
                }

                if (bTryFlip)
                {
                    DMesh3.EdgeFlipInfo flipInfo;
                    COUNT_FLIPS++;
                    MeshResult result = mesh.FlipEdge(edgeID, out flipInfo);
                    if (result == MeshResult.Ok)
                    {
                        DoDebugChecks();
                        return(ProcessResult.Ok_Flipped);
                    }
                    else
                    {
                        bTriedFlip = true;
                    }
                }
            }

            end_flip();
            begin_split();

            // if edge length is too long, we want to split it
            bool bTriedSplit = false;

            if (EnableSplits && constraint.CanSplit && edge_len_sqr > MaxEdgeLength * MaxEdgeLength)
            {
                DMesh3.EdgeSplitInfo splitInfo;
                COUNT_SPLITS++;
                MeshResult result = mesh.SplitEdge(edgeID, out splitInfo);
                if (result == MeshResult.Ok)
                {
                    update_after_split(edgeID, a, b, ref splitInfo);
                    OnEdgeSplit(edgeID, a, b, splitInfo);
                    DoDebugChecks();
                    return(ProcessResult.Ok_Split);
                }
                else
                {
                    bTriedSplit = true;
                }
            }

            end_split();


            if (bTriedFlip || bTriedSplit || bTriedCollapse)
            {
                return(ProcessResult.Failed_OpNotSuccessful);
            }
            else
            {
                return(ProcessResult.Ignored_EdgeIsFine);
            }
        }
Exemplo n.º 6
0
        public virtual void FastCollapsePass(double fMinEdgeLength, int nRounds = 1, bool MeshIsClosedHint = false)
        {
            if (mesh.TriangleCount == 0)                // badness if we don't catch this...
            {
                return;
            }

            MinEdgeLength = fMinEdgeLength;
            double min_sqr = MinEdgeLength * MinEdgeLength;

            // we don't collapse on the boundary
            HaveBoundary = false;

            begin_pass();

            begin_setup();
            Precompute(MeshIsClosedHint);
            if (Cancelled())
            {
                return;
            }

            end_setup();

            begin_ops();

            begin_collapse();

            int N             = mesh.MaxEdgeID;
            int num_last_pass = 0;

            for (int ri = 0; ri < nRounds; ++ri)
            {
                num_last_pass = 0;

                Vector3d va = Vector3d.Zero, vb = Vector3d.Zero;
                for (int eid = 0; eid < N; ++eid)
                {
                    if (!mesh.IsEdge(eid))
                    {
                        continue;
                    }

                    if (mesh.IsBoundaryEdge(eid))
                    {
                        continue;
                    }

                    if (Cancelled())
                    {
                        return;
                    }

                    mesh.GetEdgeV(eid, ref va, ref vb);
                    if (va.DistanceSquared(ref vb) > min_sqr)
                    {
                        continue;
                    }

                    COUNT_ITERATIONS++;

                    Vector3d      midpoint = (va + vb) * 0.5;
                    int           vKept;
                    ProcessResult result = CollapseEdge(eid, midpoint, out vKept);
                    if (result == ProcessResult.Ok_Collapsed)
                    {
                        ++num_last_pass;
                    }
                }

                if (num_last_pass == 0)                     // converged
                {
                    break;
                }
            }
            end_collapse();
            end_ops();

            if (Cancelled())
            {
                return;
            }

            Reproject();

            end_pass();
        }
Exemplo n.º 7
0
        public DeformInfo UpdateDeformation(Vector3d vNextPos)
        {
            DeformInfo di = new DeformInfo()
            {
                maxEdgeLenSqr = 0, minEdgeLenSqr = double.MaxValue
            };

            Vector3d vDelta = (vNextPos - prev);

            if (vDelta.Length < 0.0001f)
            {
                return(di);
            }

            double r2 = Radius * Radius;

            int N = Curve.VertexCount;
            int pending_smooth_i = -1;

            for (int i = 0; i < N; ++i)
            {
                bool     bModified = false;
                Vector3d v         = Curve[i];
                double   d2        = (v - prev).LengthSquared;
                if (d2 < r2)
                {
                    double   t    = WeightFunc(Math.Sqrt(d2), r2);
                    Vector3d vNew = v + t * vDelta;

                    if (i > 0)
                    {
                        double dp = vNew.DistanceSquared(Curve[i - 1]);
                        di.maxEdgeLenSqr = Math.Max(dp, di.maxEdgeLenSqr);
                        di.minEdgeLenSqr = Math.Min(dp, di.minEdgeLenSqr);
                    }
                    if (i < N - 1)
                    {
                        double dn = vNew.DistanceSquared(Curve[i + 1]);
                        di.maxEdgeLenSqr = Math.Max(dn, di.maxEdgeLenSqr);
                        di.minEdgeLenSqr = Math.Min(dn, di.minEdgeLenSqr);
                    }

                    Curve[i]  = vNew;
                    bModified = true;
                }

                if (pending_smooth_i > 0)
                {
                    Vector3d c = Curve.Centroid(pending_smooth_i);
                    Curve[pending_smooth_i] = Vector3d.Lerp(Curve[pending_smooth_i], c, SmoothT);
                }

                if (bModified && SmoothT > 0 && i > 0 && i < N - 1)
                {
                    pending_smooth_i = i;
                }
            }

            prev = vNextPos;
            return(di);
        }
Exemplo n.º 8
0
        /// <summary>
        /// Construct vertex correspondences between fill mesh boundary loop
        /// and input mesh boundary loop. In ideal case there is an easy 1-1
        /// correspondence. If that is not true, then do a brute-force search
        /// to find the best correspondences we can.
        ///
        /// Currently only returns unique correspondences. If any vertex
        /// matches with multiple input vertices it is not merged.
        /// [TODO] we could do better in many cases...
        ///
        /// Return value is list of indices into fillLoopV that were not merged
        /// </summary>
        List <int> build_merge_map(DMesh3 fillMesh, int[] fillLoopV,
                                   DMesh3 targetMesh, int[] targetLoopV,
                                   double tol, IndexMap mergeMapV)
        {
            if (fillLoopV.Length == targetLoopV.Length)
            {
                if (build_merge_map_simple(fillMesh, fillLoopV, targetMesh, targetLoopV, tol, mergeMapV))
                {
                    return(null);
                }
            }

            int NF = fillLoopV.Length, NT = targetLoopV.Length;

            bool[] doneF  = new bool[NF], doneT = new bool[NT];
            int[]  countF = new int[NF], countT = new int[NT];
            var    errorV = new List <int>();

            var matchF = new SmallListSet(); matchF.Resize(NF);

            // find correspondences
            double tol_sqr = tol * tol;

            for (int i = 0; i < NF; ++i)
            {
                if (fillMesh.IsVertex(fillLoopV[i]) == false)
                {
                    doneF[i] = true;
                    errorV.Add(i);
                    continue;
                }
                matchF.AllocateAt(i);
                Vector3d v = fillMesh.GetVertex(fillLoopV[i]);
                for (int j = 0; j < NT; ++j)
                {
                    Vector3d v2 = targetMesh.GetVertex(targetLoopV[j]);
                    if (v.DistanceSquared(ref v2) < tol_sqr)
                    {
                        matchF.Insert(i, j);
                    }
                }
            }

            for (int i = 0; i < NF; ++i)
            {
                if (doneF[i])
                {
                    continue;
                }

                if (matchF.Count(i) == 1)
                {
                    int j = matchF.First(i);
                    mergeMapV[fillLoopV[i]] = targetLoopV[j];
                    doneF[i] = true;
                }
            }

            for (int i = 0; i < NF; ++i)
            {
                if (doneF[i] == false)
                {
                    errorV.Add(i);
                }
            }

            return(errorV);
        }
Exemplo n.º 9
0
        public override DeformInfo Apply(Frame3f vNextPos)
        {
            Interval1d edgeRangeSqr = Interval1d.Empty;

            int N = Curve.VertexCount;

            if (N > NewV.size)
            {
                NewV.resize(N);
            }
            if (N > ModifiedV.Length)
            {
                ModifiedV = new BitArray(2 * N);
            }

            // clear modified
            ModifiedV.SetAll(false);

            bool   bSmooth = (SmoothAlpha > 0 && SmoothIterations > 0);
            double r2      = Radius * Radius;

            // deform pass
            if (DeformF != null)
            {
                for (int i = 0; i < N; ++i)
                {
                    Vector3d v  = Curve[i];
                    double   d2 = (v - vPreviousPos.Origin).LengthSquared;
                    if (d2 < r2)
                    {
                        double t = WeightFunc(Math.Sqrt(d2), Radius);

                        Vector3d vNew = DeformF(i, t);

                        if (bSmooth == false)
                        {
                            if (i > 0)
                            {
                                edgeRangeSqr.Contain(vNew.DistanceSquared(Curve[i - 1]));
                            }
                            if (i < N - 1)
                            {
                                edgeRangeSqr.Contain(vNew.DistanceSquared(Curve[i + 1]));
                            }
                        }

                        NewV[i]      = vNew;
                        ModifiedV[i] = true;
                    }
                }
            }
            else
            {
                // anything?
            }

            // smooth pass
            if (bSmooth)
            {
                for (int j = 0; j < SmoothIterations; ++j)
                {
                    int iStart = (Curve.Closed) ? 0 : 1;
                    int iEnd   = (Curve.Closed) ? N : N - 1;
                    for (int i = iStart; i < iEnd; ++i)
                    {
                        Vector3d v  = (ModifiedV[i]) ? NewV[i] : Curve[i];
                        double   d2 = (v - vPreviousPos.Origin).LengthSquared;
                        if (ModifiedV[i] || d2 < r2)            // always smooth any modified verts
                        {
                            double a = SmoothAlpha * WeightFunc(Math.Sqrt(d2), Radius);

                            int      iPrev = (i == 0) ? N - 1 : i - 1;
                            int      iNext = (i + 1) % N;
                            Vector3d vPrev = (ModifiedV[iPrev]) ? NewV[iPrev] : Curve[iPrev];
                            Vector3d vNext = (ModifiedV[iNext]) ? NewV[iNext] : Curve[iNext];
                            Vector3d c     = (vPrev + vNext) * 0.5f;
                            NewV[i]      = (1 - a) * v + (a) * c;
                            ModifiedV[i] = true;

                            if (i > 0)
                            {
                                edgeRangeSqr.Contain(NewV[i].DistanceSquared(Curve[i - 1]));
                            }
                            if (i < N - 1)
                            {
                                edgeRangeSqr.Contain(NewV[i].DistanceSquared(Curve[i + 1]));
                            }
                        }
                    }
                }
            }

            // bake
            for (int i = 0; i < N; ++i)
            {
                if (ModifiedV[i])
                {
                    Curve[i] = NewV[i];
                }
            }

            return(new DeformInfo()
            {
                minEdgeLenSqr = edgeRangeSqr.a, maxEdgeLenSqr = edgeRangeSqr.b
            });
        }
Exemplo n.º 10
0
        /// <summary>
        /// precompute constant coefficients of triangle winding number approximation
        /// p: 'center' of expansion for triangles (area-weighted centroid avg)
        /// r: max distance from p to triangles
        /// order1: first-order vector coeff
        /// order2: second-order matrix coeff
        /// triCache: optional precomputed triangle centroid/normal/area
        /// </summary>
        public static void ComputeCoeffs(DMesh3 mesh, IEnumerable <int> triangles,
                                         ref Vector3d p, ref double r,
                                         ref Vector3d order1, ref Matrix3d order2,
                                         MeshTriInfoCache triCache = null)
        {
            p      = Vector3d.Zero;
            order1 = Vector3d.Zero;
            order2 = Matrix3d.Zero;
            r      = 0;

            // compute area-weighted centroid of triangles, we use this as the expansion point
            Vector3d P0 = Vector3d.Zero, P1 = Vector3d.Zero, P2 = Vector3d.Zero;
            double   sum_area = 0;

            foreach (int tid in triangles)
            {
                if (triCache != null)
                {
                    double area = triCache.Areas[tid];
                    sum_area += area;
                    p        += area * triCache.Centroids[tid];
                }
                else
                {
                    mesh.GetTriVertices(tid, ref P0, ref P1, ref P2);
                    double area = MathUtil.Area(ref P0, ref P1, ref P2);
                    sum_area += area;
                    p        += area * ((P0 + P1 + P2) / 3.0);
                }
            }
            p /= sum_area;

            // compute first and second-order coefficients of FWN taylor expansion, as well as
            // 'radius' value r, which is max dist from any tri vertex to p
            Vector3d n = Vector3d.Zero, c = Vector3d.Zero; double a = 0;

            foreach (int tid in triangles)
            {
                mesh.GetTriVertices(tid, ref P0, ref P1, ref P2);

                if (triCache == null)
                {
                    c = (1.0 / 3.0) * (P0 + P1 + P2);
                    n = MathUtil.FastNormalArea(ref P0, ref P1, ref P2, out a);
                }
                else
                {
                    triCache.GetTriInfo(tid, ref n, ref a, ref c);
                }

                order1 += a * n;

                Vector3d dcp = c - p;
                order2 += a * new Matrix3d(ref dcp, ref n);

                // this is just for return value...
                double maxdist = MathUtil.Max(P0.DistanceSquared(ref p), P1.DistanceSquared(ref p), P2.DistanceSquared(ref p));
                r = Math.Max(r, Math.Sqrt(maxdist));
            }
        }
Exemplo n.º 11
0
        public MeshResult MergeEdges(int eKeep, int eDiscard, out MergeEdgesInfo merge_info)
        {
            merge_info = new MergeEdgesInfo();
            if (IsEdge(eKeep) == false || IsEdge(eDiscard) == false)
            {
                return(MeshResult.Failed_NotAnEdge);
            }

            Index4i edgeinfo_keep    = GetEdge(eKeep);
            Index4i edgeinfo_discard = GetEdge(eDiscard);

            if (edgeinfo_keep.d != InvalidID || edgeinfo_discard.d != InvalidID)
            {
                return(MeshResult.Failed_NotABoundaryEdge);
            }

            int a = edgeinfo_keep.a, b = edgeinfo_keep.b;
            int tab = edgeinfo_keep.c;
            int eab = eKeep;
            int c = edgeinfo_discard.a, d = edgeinfo_discard.b;
            int tcd = edgeinfo_discard.c;
            int ecd = eDiscard;

            // Need to correctly orient a,b and c,d and then check that
            // we will not join triangles with incompatible winding order
            // I can't see how to do this purely topologically.
            // So relying on closest-pairs testing.
            IndexUtil.orient_tri_edge(ref a, ref b, GetTriangle(tab));
            //int tcd_otherv = IndexUtil.orient_tri_edge_and_find_other_vtx(ref c, ref d, GetTriangle(tcd));
            IndexUtil.orient_tri_edge(ref c, ref d, GetTriangle(tcd));
            int      x = c; c = d; d = x;          // joinable bdry edges have opposing orientations, so flip to get ac and b/d correspondences
            Vector3d Va = GetVertex(a), Vb = GetVertex(b), Vc = GetVertex(c), Vd = GetVertex(d);

            if ((Va.DistanceSquared(Vc) + Vb.DistanceSquared(Vd)) >
                (Va.DistanceSquared(Vd) + Vb.DistanceSquared(Vc)))
            {
                return(MeshResult.Failed_SameOrientation);
            }

            // alternative that detects normal flip of triangle tcd. This is a more
            // robust geometric test, but fails if tri is degenerate...also more expensive
            //Vector3d otherv = GetVertex(tcd_otherv);
            //Vector3d Ncd = MathUtil.FastNormalDirection(GetVertex(c), GetVertex(d), otherv);
            //Vector3d Nab = MathUtil.FastNormalDirection(GetVertex(a), GetVertex(b), otherv);
            //if (Ncd.Dot(Nab) < 0)
            //return MeshResult.Failed_SameOrientation;

            merge_info.eKept    = eab;
            merge_info.eRemoved = ecd;

            // [TODO] this acts on each interior tri twice. could avoid using vtx-tri iterator?

            List <int> edges_a = vertex_edges[a];

            if (a != c)
            {
                // replace c w/ a in edges and tris connected to c, and move edges to a
                List <int> edges_c = vertex_edges[c];
                for (int i = 0; i < edges_c.Count; ++i)
                {
                    int eid = edges_c[i];
                    if (eid == eDiscard)
                    {
                        continue;
                    }
                    replace_edge_vertex(eid, c, a);
                    short rc = 0;
                    if (replace_tri_vertex(edges[4 * eid + 2], c, a) >= 0)
                    {
                        rc++;
                    }
                    if (edges[4 * eid + 3] != InvalidID)
                    {
                        if (replace_tri_vertex(edges[4 * eid + 3], c, a) >= 0)
                        {
                            rc++;
                        }
                    }
                    edges_a.Add(eid);
                    if (rc > 0)
                    {
                        vertices_refcount.increment(a, rc);
                        vertices_refcount.decrement(c, rc);
                    }
                }
                vertex_edges[c] = null;
                vertices_refcount.decrement(c);
                merge_info.vRemoved[0] = c;
            }
            else
            {
                edges_a.Remove(ecd);
                merge_info.vRemoved[0] = InvalidID;
            }
            merge_info.vKept[0] = a;

            List <int> edges_b = vertex_edges[b];

            if (d != b)
            {
                // replace d w/ b in edges and tris connected to d, and move edges to b
                List <int> edges_d = vertex_edges[d];
                for (int i = 0; i < edges_d.Count; ++i)
                {
                    int eid = edges_d[i];
                    if (eid == eDiscard)
                    {
                        continue;
                    }
                    replace_edge_vertex(eid, d, b);
                    short rc = 0;
                    if (replace_tri_vertex(edges[4 * eid + 2], d, b) >= 0)
                    {
                        rc++;
                    }
                    if (edges[4 * eid + 3] != InvalidID)
                    {
                        if (replace_tri_vertex(edges[4 * eid + 3], d, b) >= 0)
                        {
                            rc++;
                        }
                    }
                    edges_b.Add(eid);
                    if (rc > 0)
                    {
                        vertices_refcount.increment(b, rc);
                        vertices_refcount.decrement(d, rc);
                    }
                }
                vertex_edges[d] = null;
                vertices_refcount.decrement(d);
                merge_info.vRemoved[1] = d;
            }
            else
            {
                edges_b.Remove(ecd);
                merge_info.vRemoved[1] = InvalidID;
            }
            merge_info.vKept[1] = b;

            // replace edge cd with edge ab in triangle tcd
            replace_triangle_edge(tcd, ecd, eab);
            edges_refcount.decrement(ecd);

            // update edge-tri adjacency
            set_edge_triangles(eab, tab, tcd);

            // Once we merge ab to cd, there may be additional edges (now) connected
            // to either a or b that are connected to the same vertex on their 'other' side.
            // So we now have two boundary edges connecting the same two vertices - disaster!
            // We need to find and merge these edges.
            // Q: I don't think it is possible to have multiple such edge-pairs at a or b
            //    But I am not certain...is a bit tricky to handle because we modify edges_v...
            merge_info.eRemovedExtra = new Vector2i(InvalidID, InvalidID);
            merge_info.eKeptExtra    = merge_info.eRemovedExtra;
            for (int vi = 0; vi < 2; ++vi)
            {
                int v1 = a, v2 = c;                   // vertices of merged edge
                if (vi == 1)
                {
                    v1 = b; v2 = d;
                }
                if (v1 == v2)
                {
                    continue;
                }
                List <int> edges_v = vertex_edges[v1];
                int        Nedges  = edges_v.Count;
                bool       found   = false;
                // in this loop, we compare 'other' vert_1 and vert_2 of edges around v1.
                // problem case is when vert_1 == vert_2  (ie two edges w/ same other vtx).
                for (int i = 0; i < Nedges && found == false; ++i)
                {
                    int edge_1 = edges_v[i];
                    if (edge_is_boundary(edge_1) == false)
                    {
                        continue;
                    }
                    int vert_1 = edge_other_v(edge_1, v1);
                    for (int j = i + 1; j < Nedges; ++j)
                    {
                        int edge_2 = edges_v[j];
                        int vert_2 = edge_other_v(edge_2, v1);
                        if (vert_1 == vert_2 && edge_is_boundary(edge_2))                           // if ! boundary here, we are in deep trouble...
                        // replace edge_2 w/ edge_1 in tri, update edge and vtx-edge-nbr lists
                        {
                            int tri_1 = edges[4 * edge_1 + 2];
                            int tri_2 = edges[4 * edge_2 + 2];
                            replace_triangle_edge(tri_2, edge_2, edge_1);
                            set_edge_triangles(edge_1, tri_1, tri_2);
                            vertex_edges[v1].Remove(edge_2);
                            vertex_edges[vert_1].Remove(edge_2);
                            edges_refcount.decrement(edge_2);
                            merge_info.eRemovedExtra[vi] = edge_2;
                            merge_info.eKeptExtra[vi]    = edge_1;

                            //Nedges = edges_v.Count; // this code allows us to continue checking, ie in case we had
                            //i--;					  // multiple such edges. but I don't think it's possible.
                            found = true;                                                 // exit outer i loop
                            break;                                                        // exit inner j loop
                        }
                    }
                }
            }

            return(MeshResult.Ok);
        }