/// <summary>
        /// Simplifies the model by merging the eliminating edges that are closer together
        /// than double the shortest edge length
        /// </summary>
        /// <param name="ts">The ts.</param>
        public static void SimplifyFlatPatches(this TessellatedSolid ts)
        {
            //   throw new NotImplementedException();
            var edgesToRemove    = new List <Edge>();
            var edgesToAdd       = new List <Edge>();
            var facesToRemove    = new List <PolygonalFace>();
            var facesToAdd       = new List <PolygonalFace>();
            var verticesToRemove = new List <Vertex>();
            var flats            = TVGL.MiscFunctions.FindFlats(ts.Faces);

            if (ts.Primitives == null)
            {
                ts.Primitives = new List <PrimitiveSurface>();
            }
            foreach (var flat in flats)
            {
                if (flat.InnerEdges.Count < flat.Faces.Count)
                {
                    continue;
                }
                var newFaces         = new List <PolygonalFace>();
                var outerEdgeHashSet = new HashSet <Edge>(flat.OuterEdges);
                facesToRemove.AddRange(flat.Faces);
                edgesToRemove.AddRange(flat.InnerEdges);
                var innerVertices = new HashSet <Vertex>(flat.InnerEdges.Select(e => e.To));
                innerVertices.UnionWith(flat.InnerEdges.Select(e => e.From));
                innerVertices.RemoveWhere(v => outerEdgeHashSet.Overlaps(v.Edges));
                verticesToRemove.AddRange(innerVertices);
                var vertexLoops = OrganizeIntoLoop(flat.OuterEdges, flat.Normal);
                List <List <Vertex[]> > triangulatedListofLists = TriangulatePolygon.Run(new[] { vertexLoops }, flat.Normal);
                var triangulatedList  = triangulatedListofLists.SelectMany(tl => tl).ToList();
                var oldEdgeDictionary = flat.OuterEdges.ToDictionary(TessellatedSolid.SetAndGetEdgeChecksum);
                Dictionary <long, Edge> newEdgeDictionary = new Dictionary <long, Edge>();
                foreach (var triangle in triangulatedList)
                {
                    var newFace = new PolygonalFace(triangle, flat.Normal);
                    if (newFace.Area.IsNegligible() && newFace.Normal.Any(double.IsNaN))
                    {
                        continue;
                    }
                    newFaces.Add(newFace);
                    for (var j = 0; j < 3; j++)
                    {
                        var fromVertex = newFace.Vertices[j];
                        var toVertex   = newFace.NextVertexCCW(fromVertex);
                        var checksum   = TessellatedSolid.GetEdgeChecksum(fromVertex, toVertex);
                        if (oldEdgeDictionary.ContainsKey(checksum))
                        {
                            //fix up old outer edge.
                            var edge = oldEdgeDictionary[checksum];
                            if (fromVertex == edge.From)
                            {
                                edge.OwnedFace = newFace;
                            }
                            else
                            {
                                edge.OtherFace = newFace;
                            }
                            newFace.AddEdge(edge);
                            oldEdgeDictionary.Remove(checksum);
                        }
                        else if (newEdgeDictionary.ContainsKey(checksum))
                        {
                            //Finish creating edge.
                            var newEdge = newEdgeDictionary[checksum];
                            newEdge.OtherFace = newFace;
                            newFace.AddEdge(newEdge);
                            newEdgeDictionary.Remove(checksum);
                            edgesToAdd.Add(newEdge);
                        }
                        else
                        {
                            newEdgeDictionary.Add(checksum, new Edge(fromVertex, toVertex, newFace, null, false, checksum));
                        }
                    }
                }
                ts.Primitives.Add(new Flat(newFaces));
            }
            ts.RemoveVertices(verticesToRemove);  //todo: check if the order of these five commands
            ts.RemoveFaces(facesToRemove);        // matters. There may be an ordering that is more efficient
            ts.AddFaces(facesToAdd);
            ts.RemoveEdges(edgesToRemove);
            ts.AddEdges(edgesToAdd);
        }
        /// <summary>
        /// Simplifies the tessellation so that no edge are shorter than provided the minimum edge length
        /// or until the provided number of faces are removed - whichever comes first.
        /// </summary>
        /// <param name="ts">The ts.</param>
        /// <param name="numberOfFaces">The number of faces to remove.</param>
        /// <param name="minLength">The minimum length.</param>
        public static void Simplify(TessellatedSolid ts, int numberOfFaces, double minLength)
        {
            if (ts.Errors != null)
            {
                Message.output(
                    "** The model should be free of errors before running this routine (run TessellatedSolid.Repair()).",
                    1);
            }
            var sortedEdges     = ts.Edges.OrderBy(e => e.Length).ToList();
            var removedEdges    = new SortedSet <Edge>(new SortByIndexInList());
            var removedVertices = new SortedSet <Vertex>(new SortByIndexInList());
            var removedFaces    = new SortedSet <PolygonalFace>(new SortByIndexInList());

            var edge       = sortedEdges[0];
            var iterations = numberOfFaces > 0 ? (int)Math.Ceiling(numberOfFaces / 2.0) : numberOfFaces;

            while (iterations != 0 && edge.Length <= minLength)
            {
                sortedEdges.RemoveAt(0);
                // naming conventions to ease the latter topological changes
                var removedVertex   = edge.From;
                var keepVertex      = edge.To;
                var leftFace        = edge.OtherFace;
                var rightFace       = edge.OwnedFace;
                var leftRemoveEdge  = leftFace.OtherEdge(keepVertex);
                var rightRemoveEdge = rightFace.OtherEdge(keepVertex);
                var leftKeepEdge    = leftFace.OtherEdge(removedVertex);
                var rightKeepEdge   = rightFace.OtherEdge(removedVertex);
                var leftFarVertex   = leftFace.OtherVertex(edge);
                var rightFarVertex  = rightFace.OtherVertex(edge);

                // this is a topologically important check. It ensures that the edge is not deleted if
                // it serves an important role in ensuring the proper topology of the solid
                var otherEdgesOnTheKeepSide = keepVertex.Edges.Where(e => e != edge && e != leftKeepEdge && e != rightKeepEdge).ToList();
                otherEdgesOnTheKeepSide.Remove(edge);
                var otherEdgesOnTheRemoveSide = removedVertex.Edges.Where(e => e != edge && e != leftRemoveEdge && e != rightRemoveEdge).ToList();
                otherEdgesOnTheRemoveSide.Remove(edge);
                if (leftFarVertex != rightFarVertex &&
                    !otherEdgesOnTheKeepSide.Select(e => e.OtherVertex(keepVertex))
                    .Intersect(otherEdgesOnTheRemoveSide.Select(e => e.OtherVertex(removedVertex)))
                    .Any())
                {
                    iterations--; //now that we passed that test, we can be assured that the reduction will go through
                                  // move the keepVertex
                    keepVertex.Position = DetermineIntermediateVertexPosition(removedVertex, keepVertex);
                    // add and remove to the lists at the top of this method
                    removedEdges.Add(edge);
                    removedEdges.Add(leftRemoveEdge);
                    sortedEdges.Remove(leftRemoveEdge);
                    removedEdges.Add(rightRemoveEdge);
                    sortedEdges.Remove(rightRemoveEdge);
                    removedFaces.Add(leftFace);
                    removedFaces.Add(rightFace);
                    removedVertices.Add(removedVertex);

                    keepVertex.Faces.Remove(leftFace);
                    keepVertex.Faces.Remove(rightFace);
                    // the keepVertex's other faces need to be updated given the change in position of keepVertex
                    foreach (var face in keepVertex.Faces)
                    {
                        face.Update();
                    }
                    // remove the removedVertex from the faces and update their positions with the keepVertex
                    foreach (var face in removedVertex.Faces)
                    {
                        if (face == leftFace || face == rightFace)
                        {
                            continue;
                        }
                        var index = face.Vertices.IndexOf(removedVertex);
                        face.Vertices[index] = keepVertex;
                        face.Update();
                        keepVertex.Faces.Add(face);
                    }

                    keepVertex.Edges.Remove(edge);
                    // update the edges since the keepVertex moved
                    foreach (var currentEdge in keepVertex.Edges)
                    {
                        if (currentEdge != leftKeepEdge && currentEdge != rightKeepEdge)
                        {
                            currentEdge.Update();
                        }
                    }
                    // transfer the edges from the removedVertex to the keepVertex
                    foreach (var transferEdge in removedVertex.Edges)
                    {
                        if (transferEdge == leftRemoveEdge || transferEdge == rightRemoveEdge)
                        {
                            continue;
                        }
                        if (transferEdge.From == removedVertex)
                        {
                            transferEdge.From = keepVertex;
                        }
                        else
                        {
                            transferEdge.To = keepVertex;
                        }
                        transferEdge.Update();
                        keepVertex.Edges.Add(transferEdge);
                    }

                    leftFarVertex.Edges.Remove(leftRemoveEdge);
                    leftFarVertex.Faces.Remove(leftFace);
                    rightFarVertex.Edges.Remove(rightRemoveEdge);
                    rightFarVertex.Faces.Remove(rightFace);

                    var upperFace = leftRemoveEdge.OwnedFace == leftFace
                        ? leftRemoveEdge.OtherFace
                        : leftRemoveEdge.OwnedFace;
                    if (leftKeepEdge.OwnedFace == leftFace)
                    {
                        leftKeepEdge.OwnedFace = upperFace;
                    }
                    else
                    {
                        leftKeepEdge.OtherFace = upperFace;
                    }
                    upperFace.AddEdge(leftKeepEdge);
                    leftKeepEdge.Update();

                    upperFace = rightRemoveEdge.OwnedFace == rightFace
                        ? rightRemoveEdge.OtherFace
                        : rightRemoveEdge.OwnedFace;
                    if (rightKeepEdge.OwnedFace == rightFace)
                    {
                        rightKeepEdge.OwnedFace = upperFace;
                    }
                    else
                    {
                        rightKeepEdge.OtherFace = upperFace;
                    }
                    upperFace.AddEdge(rightKeepEdge);
                    rightKeepEdge.Update();
                }
                if (sortedEdges.Any())
                {
                    edge = sortedEdges[0];
                }
                else
                {
                    break;
                }
            }
            ts.RemoveEdges(removedEdges.Select(e => e.IndexInList).ToList());
            ts.RemoveFaces(removedFaces.Select(f => f.IndexInList).ToList());
            ts.RemoveVertices(removedVertices.Select(v => v.IndexInList).ToList());
        }