Esempio n. 1
0
        /// <summary>
        /// shortcut to construct graph for mesh triangles
        /// </summary>
        public static DijkstraGraphDistance MeshTriangles(DMesh3 mesh, bool bSparse = false)
        {
            Func <int, int, float> tri_dist_f = (a, b) => {
                return((float)mesh.GetTriCentroid(a).Distance(mesh.GetTriCentroid(b)));
            };

            return((bSparse) ?
                   new DijkstraGraphDistance(mesh.MaxTriangleID, true,
                                             (id) => { return mesh.IsTriangle(id); }, tri_dist_f,
                                             mesh.TriTrianglesItr, null)
                        : new DijkstraGraphDistance(mesh.MaxTriangleID, false,
                                                    (id) => { return true; }, tri_dist_f,
                                                    mesh.TriTrianglesItr, null));
        }
Esempio n. 2
0
        public void RemoveContained()
        {
            DMeshAABBTree3 spatial = new DMeshAABBTree3(CutMesh, true);

            spatial.WindingNumber(Vector3d.Zero);
            SafeListBuilder <int> removeT = new SafeListBuilder <int>();

            gParallel.ForEach(Target.TriangleIndices(), (tid) => {
                Vector3d v = Target.GetTriCentroid(tid);
                if (spatial.WindingNumber(v) > 0.9)
                {
                    removeT.SafeAdd(tid);
                }
            });
            MeshEditor.RemoveTriangles(Target, removeT.Result);

            // [RMS] construct set of on-cut vertices? This is not
            // necessarily all boundary vertices...
            CutVertices = new List <int>();
            foreach (int vid in SegmentInsertVertices)
            {
                if (Target.IsVertex(vid))
                {
                    CutVertices.Add(vid);
                }
            }
        }
        int[] get_tri_order_by_axis_sort()
        {
            int i = 0;

            int[] tri_order = new int[mesh.TriangleCount];

            int nT = mesh.MaxTriangleID;

            for (int ti = 0; ti < nT; ++ti)
            {
                if (mesh.IsTriangle(ti))
                {
                    tri_order[i++] = ti;
                }
            }

            // precompute triangle centroids - wildly expensive to
            // do it inline in sort (!)  I guess O(N) vs O(N log N)
            Vector3d[] centroids = new Vector3d[mesh.MaxTriangleID];
            gParallel.ForEach(mesh.TriangleIndices(), (ti) => {
                if (mesh.IsTriangle(ti))
                {
                    centroids[ti] = mesh.GetTriCentroid(ti);
                }
            });


            Array.Sort(tri_order, (t0, t1) => {
                double f0 = centroids[t0].x;
                double f1 = centroids[t1].x;
                return((f0 == f1) ? 0 : (f0 < f1) ? -1 : 1);
            });

            return(tri_order);
        }
Esempio n. 4
0
        void build_top_down(bool bSorted)
        {
            int i = 0;

            int[]      triangles = new int[mesh.TriangleCount];
            Vector3d[] centers   = new Vector3d[mesh.TriangleCount];
            foreach (int ti in mesh.TriangleIndices())
            {
                triangles[i] = ti;
                centers[i++] = mesh.GetTriCentroid(ti);
            }

            boxes_set        tris  = new boxes_set();
            boxes_set        nodes = new boxes_set();
            AxisAlignedBox3f rootBox;
            int rootnode = (bSorted) ?
                           split_tri_set_sorted(triangles, centers, 0, mesh.TriangleCount, 0, TopDownLeafMaxTriCount, tris, nodes, out rootBox)
                : split_tri_set_midpoint(triangles, centers, 0, mesh.TriangleCount, 0, TopDownLeafMaxTriCount, tris, nodes, out rootBox);

            box_to_index  = tris.box_to_index;
            box_centers   = tris.box_centers;
            box_extents   = tris.box_extents;
            index_list    = tris.index_list;
            triangles_end = tris.iIndicesCur;
            int iIndexShift = triangles_end;
            int iBoxShift   = tris.iBoxCur;

            // ok now append internal node boxes & index ptrs
            for (i = 0; i < nodes.iBoxCur; ++i)
            {
                box_centers.insert(nodes.box_centers[i], iBoxShift + i);
                box_extents.insert(nodes.box_extents[i], iBoxShift + i);
                // internal node indices are shifted
                box_to_index.insert(iIndexShift + nodes.box_to_index[i], iBoxShift + i);
            }

            // now append index list
            for (i = 0; i < nodes.iIndicesCur; ++i)
            {
                int child_box = nodes.index_list[i];
                if (child_box < 0)            // this is a triangles box
                {
                    child_box = (-child_box) - 1;
                }
                else
                {
                    child_box += iBoxShift;
                }
                child_box = child_box + 1;
                index_list.insert(child_box, iIndexShift + i);
            }

            root_index = rootnode + iBoxShift;
        }
        int[] get_tri_order_by_axis_sort()
        {
            int i = 0;

            int[] tri_order = new int[mesh.TriangleCount];

            int nT = mesh.MaxTriangleID;

            for (int ti = 0; ti < nT; ++ti)
            {
                if (mesh.IsTriangle(ti))
                {
                    tri_order[i++] = ti;
                }
            }

            Array.Sort(tri_order, (t0, t1) => {
                double f0 = mesh.GetTriCentroid(t0).x;
                double f1 = mesh.GetTriCentroid(t1).x;
                return((f0 == f1) ? 0 : (f0 < f1) ? -1 : 1);
            });

            return(tri_order);
        }
Esempio n. 6
0
        // Modes: 0: centroids, 1: any vertex, 2: 2 vertices, 3: all vertices
        // ContainF should return true if 3D position is in set (eg inside box, etc)
        // If mode = 0, will be called with (centroid, tri_idx)
        // If mode = 1/2/3, will be called with (vtx_pos, vtx_idx)
        // AddF is called with triangle IDs that are in set
        public static void TrianglesContained(DMesh3 mesh, Func <Vector3d, int, bool> ContainF, Action <int> AddF, int nMode = 0)
        {
            BitArray inV = null;

            if (nMode != 0)
            {
                inV = new BitArray(mesh.MaxVertexID);
                foreach (int vid in mesh.VertexIndices())
                {
                    if (ContainF(mesh.GetVertex(vid), vid))
                    {
                        inV[vid] = true;
                    }
                }
            }

            foreach (int tid in mesh.TriangleIndices())
            {
                Index3i tri = mesh.GetTriangle(tid);

                bool bIn = false;
                if (nMode == 0)
                {
                    if (ContainF(mesh.GetTriCentroid(tid), tid))
                    {
                        bIn = true;
                    }
                }
                else
                {
                    int countIn = (inV[tri.a] ? 1 : 0) + (inV[tri.b] ? 1 : 0) + (inV[tri.c] ? 1 : 0);
                    bIn = (countIn >= nMode);
                }

                if (bIn)
                {
                    AddF(tid);
                }
            }
        }
Esempio n. 7
0
        void build_top_down(bool bSorted)
        {
            // build list of valid triangles & centers. We skip any
            // triangles that have infinite/garbage vertices...
            int i = 0;

            int[]      triangles = new int[mesh.TriangleCount];
            Vector3d[] centers   = new Vector3d[mesh.TriangleCount];
            foreach (int ti in mesh.TriangleIndices())
            {
                Vector3d centroid = mesh.GetTriCentroid(ti);
                double   d2       = centroid.LengthSquared;
                bool     bInvalid = double.IsNaN(d2) || double.IsInfinity(d2);
                Debug.Assert(bInvalid == false);
                if (bInvalid == false)
                {
                    triangles[i] = ti;
                    centers[i]   = mesh.GetTriCentroid(ti);
                    i++;
                } // otherwise skip this tri
            }

            boxes_set        tris  = new boxes_set();
            boxes_set        nodes = new boxes_set();
            AxisAlignedBox3f rootBox;
            int rootnode = (bSorted) ?
                           split_tri_set_sorted(triangles, centers, 0, mesh.TriangleCount, 0, TopDownLeafMaxTriCount, tris, nodes, out rootBox)
                : split_tri_set_midpoint(triangles, centers, 0, mesh.TriangleCount, 0, TopDownLeafMaxTriCount, tris, nodes, out rootBox);

            box_to_index  = tris.box_to_index;
            box_centers   = tris.box_centers;
            box_extents   = tris.box_extents;
            index_list    = tris.index_list;
            triangles_end = tris.iIndicesCur;
            int iIndexShift = triangles_end;
            int iBoxShift   = tris.iBoxCur;

            // ok now append internal node boxes & index ptrs
            for (i = 0; i < nodes.iBoxCur; ++i)
            {
                box_centers.insert(nodes.box_centers[i], iBoxShift + i);
                box_extents.insert(nodes.box_extents[i], iBoxShift + i);
                // internal node indices are shifted
                box_to_index.insert(iIndexShift + nodes.box_to_index[i], iBoxShift + i);
            }

            // now append index list
            for (i = 0; i < nodes.iIndicesCur; ++i)
            {
                int child_box = nodes.index_list[i];
                if (child_box < 0)            // this is a triangles box
                {
                    child_box = (-child_box) - 1;
                }
                else
                {
                    child_box += iBoxShift;
                }
                child_box = child_box + 1;
                index_list.insert(child_box, iIndexShift + i);
            }

            root_index = rootnode + iBoxShift;
        }
        public bool Fill()
        {
            compute_polygon();

            // translate/scale fill loops to unit box. This will improve
            // accuracy in the calcs below...
            Vector2d shiftOrigin = Bounds.Center;
            double   scale       = 1.0 / Bounds.MaxDim;

            SpansPoly.Translate(-shiftOrigin);
            SpansPoly.Scale(scale * Vector2d.One, Vector2d.Zero);

            var ElemToLoopMap = new Dictionary <PlanarComplex.Element, int>();

            // generate planar mesh that we will insert polygons into
            MeshGenerator meshgen;
            float         planeW     = 1.5f;
            int           nDivisions = 0;

            if (FillTargetEdgeLen < double.MaxValue && FillTargetEdgeLen > 0)
            {
                int n = (int)((planeW / (float)scale) / FillTargetEdgeLen) + 1;
                nDivisions = (n <= 1) ? 0 : n;
            }

            if (nDivisions == 0)
            {
                meshgen = new TrivialRectGenerator()
                {
                    IndicesMap = new Index2i(1, 2),
                    Width      = planeW,
                    Height     = planeW,
                };
            }
            else
            {
                meshgen = new GriddedRectGenerator()
                {
                    IndicesMap   = new Index2i(1, 2),
                    Width        = planeW,
                    Height       = planeW,
                    EdgeVertices = nDivisions
                };
            }
            DMesh3 FillMesh = meshgen.Generate().MakeDMesh();

            FillMesh.ReverseOrientation();               // why?!?

            int[] polyVertices = null;

            // insert each poly
            var insert = new MeshInsertUVPolyCurve(FillMesh, SpansPoly);
            ValidationStatus status = insert.Validate(MathUtil.ZeroTolerancef * scale);
            bool             failed = true;

            if (status == ValidationStatus.Ok)
            {
                if (insert.Apply())
                {
                    insert.Simplify();
                    polyVertices = insert.CurveVertices;
                    failed       = false;
                }
            }
            if (failed)
            {
                return(false);
            }

            // remove any triangles not contained in gpoly
            // [TODO] degenerate triangle handling? may be 'on' edge of gpoly...
            var removeT = new List <int>();

            foreach (int tid in FillMesh.TriangleIndices())
            {
                Vector3d v = FillMesh.GetTriCentroid(tid);
                if (SpansPoly.Contains(v.xy) == false)
                {
                    removeT.Add(tid);
                }
            }
            foreach (int tid in removeT)
            {
                FillMesh.RemoveTriangle(tid, true, false);
            }

            //Util.WriteDebugMesh(FillMesh, "c:\\scratch\\CLIPPED_MESH.obj");

            // transform fill mesh back to 3d
            MeshTransforms.PerVertexTransform(FillMesh, (v) =>
            {
                Vector2d v2 = v.xy;
                v2         /= scale;
                v2         += shiftOrigin;
                return(to3D(v2));
            });


            //Util.WriteDebugMesh(FillMesh, "c:\\scratch\\PLANAR_MESH_WITH_LOOPS.obj");
            //Util.WriteDebugMesh(MeshEditor.Combine(FillMesh, Mesh), "c:\\scratch\\FILLED_MESH.obj");

            // figure out map between new mesh and original edge loops
            // [TODO] if # of verts is different, we can still find correspondence, it is just harder
            // [TODO] should check that edges (ie sequential verts) are boundary edges on fill mesh
            //    if not, can try to delete nbr tris to repair
            var mergeMapV = new IndexMap(true);

            if (MergeFillBoundary && polyVertices != null)
            {
                throw new NotImplementedException("PlanarSpansFiller: merge fill boundary not implemented!");

                //int[] fillLoopVerts = polyVertices;
                //int NV = fillLoopVerts.Length;

                //PlanarComplex.Element sourceElem = (pi == 0) ? gsolid.Outer : gsolid.Holes[pi - 1];
                //int loopi = ElemToLoopMap[sourceElem];
                //EdgeLoop sourceLoop = Loops[loopi].edgeLoop;

                //for (int k = 0; k < NV; ++k) {
                //    Vector3d fillV = FillMesh.GetVertex(fillLoopVerts[k]);
                //    Vector3d sourceV = Mesh.GetVertex(sourceLoop.Vertices[k]);
                //    if (fillV.Distance(sourceV) < MathUtil.ZeroTolerancef)
                //        mergeMapV[fillLoopVerts[k]] = sourceLoop.Vertices[k];
                //}
            }

            // append this fill to input mesh
            var editor = new MeshEditor(Mesh);

            int[] mapV;
            editor.AppendMesh(FillMesh, mergeMapV, out mapV, Mesh.AllocateTriangleGroup());

            // [TODO] should verify that we actually merged the loops...

            return(true);
        }
        public bool Fill()
        {
            compute_polygons();

            // translate/scale fill loops to unit box. This will improve
            // accuracy in the calcs below...
            Vector2d shiftOrigin = Bounds.Center;
            double   scale       = 1.0 / Bounds.MaxDim;

            foreach (var floop in Loops)
            {
                floop.poly.Translate(-shiftOrigin);
                floop.poly.Scale(scale * Vector2d.One, Vector2d.Zero);
            }

            Dictionary <PlanarComplex.Element, int> ElemToLoopMap = new Dictionary <PlanarComplex.Element, int>();

            // [TODO] if we have multiple components in input mesh, we could do this per-component.
            // This also helps avoid nested shells creating holes.
            // *However*, we shouldn't *have* to because FindSolidRegions will do the right thing if
            // the polygons have the same orientation

            // add all loops to planar complex
            PlanarComplex complex = new PlanarComplex();

            for (int i = 0; i < Loops.Count; ++i)
            {
                var elem = complex.Add(Loops[i].poly);
                ElemToLoopMap[elem] = i;
            }

            // sort into separate 2d solids
            PlanarComplex.SolidRegionInfo solids =
                complex.FindSolidRegions(PlanarComplex.FindSolidsOptions.SortPolygons);

            // fill each 2d solid
            List <Index2i> failed_inserts = new List <Index2i>();
            List <Index2i> failed_merges  = new List <Index2i>();

            for (int fi = 0; fi < solids.Polygons.Count; ++fi)
            {
                var gpoly = solids.Polygons[fi];
                PlanarComplex.GeneralSolid gsolid = solids.PolygonsSources[fi];

                // [TODO] could do scale/translate here, per-polygon would be more precise

                // generate planar mesh that we will insert polygons into
                MeshGenerator meshgen;
                float         planeW     = 1.5f;
                int           nDivisions = 0;
                if (FillTargetEdgeLen < double.MaxValue && FillTargetEdgeLen > 0)
                {
                    int n = (int)((planeW / (float)scale) / FillTargetEdgeLen) + 1;
                    nDivisions = (n <= 1) ? 0 : n;
                }

                if (nDivisions == 0)
                {
                    meshgen = new TrivialRectGenerator()
                    {
                        IndicesMap = new Index2i(1, 2), Width = planeW, Height = planeW,
                    };
                }
                else
                {
                    meshgen = new GriddedRectGenerator()
                    {
                        IndicesMap   = new Index2i(1, 2), Width = planeW, Height = planeW,
                        EdgeVertices = nDivisions
                    };
                }
                DMesh3 FillMesh = meshgen.Generate().MakeDMesh();
                FillMesh.ReverseOrientation();   // why?!?

                // convenient list
                List <Polygon2d> polys = new List <Polygon2d>()
                {
                    gpoly.Outer
                };
                polys.AddRange(gpoly.Holes);

                // for each poly, we track the set of vertices inserted into mesh
                int[][] polyVertices = new int[polys.Count][];

                // insert each poly
                for (int pi = 0; pi < polys.Count; ++pi)
                {
                    MeshInsertUVPolyCurve insert = new MeshInsertUVPolyCurve(FillMesh, polys[pi]);
                    ValidationStatus      status = insert.Validate(MathUtil.ZeroTolerancef * scale);
                    bool failed = true;
                    if (status == ValidationStatus.Ok)
                    {
                        if (insert.Apply())
                        {
                            insert.Simplify();
                            polyVertices[pi] = insert.CurveVertices;
                            failed           = (insert.Loops.Count != 1) ||
                                               (insert.Loops[0].VertexCount != polys[pi].VertexCount);
                        }
                    }
                    if (failed)
                    {
                        failed_inserts.Add(new Index2i(fi, pi));
                    }
                }

                // remove any triangles not contained in gpoly
                // [TODO] degenerate triangle handling? may be 'on' edge of gpoly...
                List <int> removeT = new List <int>();
                foreach (int tid in FillMesh.TriangleIndices())
                {
                    Vector3d v = FillMesh.GetTriCentroid(tid);
                    if (gpoly.Contains(v.xy) == false)
                    {
                        removeT.Add(tid);
                    }
                }
                foreach (int tid in removeT)
                {
                    FillMesh.RemoveTriangle(tid, true, false);
                }

                //Util.WriteDebugMesh(FillMesh, "c:\\scratch\\CLIPPED_MESH.obj");

                // transform fill mesh back to 3d
                MeshTransforms.PerVertexTransform(FillMesh, (v) => {
                    Vector2d v2 = v.xy;
                    v2         /= scale;
                    v2         += shiftOrigin;
                    return(to3D(v2));
                });


                //Util.WriteDebugMesh(FillMesh, "c:\\scratch\\PLANAR_MESH_WITH_LOOPS.obj");
                //Util.WriteDebugMesh(MeshEditor.Combine(FillMesh, Mesh), "c:\\scratch\\FILLED_MESH.obj");

                // figure out map between new mesh and original edge loops
                // [TODO] if # of verts is different, we can still find correspondence, it is just harder
                // [TODO] should check that edges (ie sequential verts) are boundary edges on fill mesh
                //    if not, can try to delete nbr tris to repair
                IndexMap mergeMapV = new IndexMap(true);
                if (MergeFillBoundary)
                {
                    for (int pi = 0; pi < polys.Count; ++pi)
                    {
                        if (polyVertices[pi] == null)
                        {
                            continue;
                        }
                        int[] fillLoopVerts = polyVertices[pi];
                        int   NV            = fillLoopVerts.Length;

                        PlanarComplex.Element sourceElem = (pi == 0) ? gsolid.Outer : gsolid.Holes[pi - 1];
                        int      loopi      = ElemToLoopMap[sourceElem];
                        EdgeLoop sourceLoop = Loops[loopi].edgeLoop;

                        if (sourceLoop.VertexCount != NV)
                        {
                            failed_merges.Add(new Index2i(fi, pi));
                            continue;
                        }

                        for (int k = 0; k < NV; ++k)
                        {
                            Vector3d fillV   = FillMesh.GetVertex(fillLoopVerts[k]);
                            Vector3d sourceV = Mesh.GetVertex(sourceLoop.Vertices[k]);
                            if (fillV.Distance(sourceV) < MathUtil.ZeroTolerancef)
                            {
                                mergeMapV[fillLoopVerts[k]] = sourceLoop.Vertices[k];
                            }
                        }
                    }
                }

                // append this fill to input mesh
                MeshEditor editor = new MeshEditor(Mesh);
                int[]      mapV;
                editor.AppendMesh(FillMesh, mergeMapV, out mapV, Mesh.AllocateTriangleGroup());

                // [TODO] should verify that we actually merged the loops...
            }

            if (failed_inserts.Count > 0 || failed_merges.Count > 0)
            {
                return(false);
            }

            return(true);
        }
Esempio n. 10
0
        public bool Insert()
        {
            OuterInsert = new MeshInsertUVPolyCurve(Mesh, Polygon.Outer);
            Util.gDevAssert(OuterInsert.Validate() == ValidationStatus.Ok);
            bool outerApplyOK = OuterInsert.Apply();

            if (outerApplyOK == false || OuterInsert.Loops.Count == 0)
            {
                return(false);
            }

            if (SimplifyInsertion)
            {
                OuterInsert.Simplify();
            }

            HoleInserts = new List <MeshInsertUVPolyCurve>(Polygon.Holes.Count);
            for (int hi = 0; hi < Polygon.Holes.Count; ++hi)
            {
                var insert = new MeshInsertUVPolyCurve(Mesh, Polygon.Holes[hi]);
                Util.gDevAssert(insert.Validate() == ValidationStatus.Ok);
                insert.Apply();
                if (SimplifyInsertion)
                {
                    insert.Simplify();
                }

                HoleInserts.Add(insert);
            }


            // find a triangle connected to loop that is inside the polygon
            //   [TODO] maybe we could be a bit more robust about this? at least
            //   check if triangle is too degenerate...
            int      seed_tri   = -1;
            EdgeLoop outer_loop = OuterInsert.Loops[0];

            for (int i = 0; i < outer_loop.EdgeCount; ++i)
            {
                if (!Mesh.IsEdge(outer_loop.Edges[i]))
                {
                    continue;
                }

                Index2i  et   = Mesh.GetEdgeT(outer_loop.Edges[i]);
                Vector3d ca   = Mesh.GetTriCentroid(et.a);
                bool     in_a = Polygon.Outer.Contains(ca.xy);
                Vector3d cb   = Mesh.GetTriCentroid(et.b);
                bool     in_b = Polygon.Outer.Contains(cb.xy);
                if (in_a && in_b == false)
                {
                    seed_tri = et.a;
                    break;
                }
                else if (in_b && in_a == false)
                {
                    seed_tri = et.b;
                    break;
                }
            }
            if (seed_tri == -1)
            {
                throw new Exception("MeshPolygonsInserter: could not find seed triangle!");
            }

            // make list of all outer & hole edges
            InsertedPolygonEdges = new HashSet <int>(outer_loop.Edges);
            foreach (var insertion in HoleInserts)
            {
                foreach (int eid in insertion.Loops[0].Edges)
                {
                    InsertedPolygonEdges.Add(eid);
                }
            }

            // flood-fill inside loop from seed triangle
            InteriorTriangles = new MeshFaceSelection(Mesh);
            InteriorTriangles.FloodFill(seed_tri, null, (eid) => { return(InsertedPolygonEdges.Contains(eid) == false); });

            return(true);
        }
Esempio n. 11
0
        private void Remove(TriangleRemoval rem = TriangleRemoval.contained)
        {
#if ACAD
            var lastColor = 0;
#endif

            DMeshAABBTree3 spatial = new DMeshAABBTree3(CutMesh, true);
            spatial.WindingNumber(Vector3d.Zero);
            SafeListBuilder <int> containedT    = new SafeListBuilder <int>();
            SafeListBuilder <int> removeAnywayT = new SafeListBuilder <int>();

            // if the windinging number for the centroid point candidate triangles
            // is one or more (or close for safety), then it's inside the volume of cutMesh
            //
            gParallel.ForEach(Target.TriangleIndices(), (tid) =>
            {
                if (Target.GetTriArea(tid) < VertexSnapTol)
                {
                    removeAnywayT.SafeAdd(tid);
                    return; // parallel: equivalent to continue.
                }
                Vector3d v = Target.GetTriCentroid(tid);
                if (AttemptPlanarRemoval)
                {
                    // slightly offset the point to be evaluated.
                    //
                    var nrm = Target.GetTriNormal(tid);
                    v      -= nrm * 5 * VertexSnapTol;
                }

                var winding     = spatial.WindingNumber(v);
                bool IsInternal = winding > 0.9;
#if ACAD
                // temporarily here for debug purposes
                var wantColor = IsInternal ? 1 : 2;
                if (lastColor != wantColor)
                {
                    Debug.WriteLine($"-LAYER set L{wantColor}");
                    Debug.WriteLine($"");
                    lastColor = wantColor;
                }
                Triangle3d tri = new Triangle3d();
                Target.GetTriVertices(tid, ref tri.V0, ref tri.V1, ref tri.V2);
                Debug.WriteLine($"3DPOLY {tri.V0.CommaDelimited} {tri.V1.CommaDelimited} {tri.V2.CommaDelimited} {tri.V0.CommaDelimited} {v.CommaDelimited} ");
#endif
                if (IsInternal)
                {
                    containedT.SafeAdd(tid);
                }
            });
            if (rem == TriangleRemoval.contained)
            {
                MeshEditor.RemoveTriangles(Target, containedT.Result);
            }
            else if (rem == TriangleRemoval.external)
            {
                var ext = Target.TriangleIndices().Except(containedT.Result);
                MeshEditor.RemoveTriangles(Target, ext);
            }

            MeshEditor.RemoveTriangles(Target, removeAnywayT.Result);

            // [RMS] construct set of on-cut vertices? This is not
            // necessarily all boundary vertices...
            CutVertices = new List <int>();
            foreach (int vid in SegmentInsertVertices)
            {
                if (Target.IsVertex(vid))
                {
                    CutVertices.Add(vid);
                }
            }
        }