Example #1
0
        /// <summary>
        /// Gets the silhouette of a solid along a given normal.
        /// </summary>
        /// <param name="faces"></param>
        /// <param name="normal"></param>
        /// <param name="minAngle"></param>
        /// <param name="minPathAreaToConsider"></param>
        /// <param name="depthOfPart"></param>
        /// <returns></returns>
        public static List <List <PointLight> > Slow(IList <PolygonalFace> faces, double[] normal, double minAngle = 0.1,
                                                     double minPathAreaToConsider = 0.0, double depthOfPart = 0.0)
        {
            var angleTolerance = Math.Cos((90 - minAngle) * Math.PI / 180);

            //Get the positive faces (defined as face normal along same direction as the silhoutte normal).
            var positiveFaces = new HashSet <PolygonalFace>();
            var vertices      = new HashSet <Vertex>();

            foreach (var face in faces)
            {
                var dot = normal.dotProduct(face.Normal, 3);
                if (dot.IsGreaterThanNonNegligible(angleTolerance))
                {
                    positiveFaces.Add(face);
                    //face.Color = new Color(KnownColors.Blue);
                    foreach (var vertex in face.Vertices)
                    {
                        vertices.Add(vertex);
                    }
                }
            }

            //Project all the vertices into points
            //The vertex is saved as a reference in the point
            var transform       = MiscFunctions.TransformToXYPlane(normal, out _);
            var projectedPoints = new Dictionary <int, PointLight>();

            foreach (var vertex in vertices)
            {
                projectedPoints.Add(vertex.IndexInList, MiscFunctions.Get2DProjectionPointAsLight(vertex, transform));
            }

            //Build a dictionary of faces to polygons
            //var projectedFacePolygons = positiveFaces.ToDictionary(f => f, f => GetPolygonFromFace(f, projectedPoints, true));
            //Use GetPolygonFromFace and force to be positive faces with true"
            var projectedFacePolygons2 = positiveFaces.Select(f => GetPolygonFromFace(f, projectedPoints, true)).ToList().Where(p => p.Area > minPathAreaToConsider).ToList();
            var solution = PolygonOperations.Union(projectedFacePolygons2, false).Select(p => p.Path).ToList();

            //Offset by enough to account for minimum angle
            var scale = Math.Tan(minAngle * Math.PI / 180) * depthOfPart;

            //Remove tiny polygons and slivers
            solution = PolygonOperations.SimplifyFuzzy(solution);
            var offsetPolygons      = PolygonOperations.OffsetMiter(solution, scale);
            var significantSolution = PolygonOperations.OffsetMiter(offsetPolygons, -scale);

            //Presenter.ShowAndHang(significantSolution);
            return(significantSolution); //.Select(p => p.Path).ToList();
        }
Example #2
0
        public void SetCrossSection2D(Flat plane)
        {
            var paths        = new List <List <PointLight> >();
            var positivePath = new PolygonLight(MiscFunctions.Get2DProjectionPointsAsLight(PositiveLoop.VertexLoop, plane.Normal).ToList());

            if (positivePath.Area < 0)
            {
                positivePath.Path.Reverse();
            }
            paths.Add(positivePath.Path);
            foreach (var loop in NegativeLoops)
            {
                var negativePath = new PolygonLight(MiscFunctions.Get2DProjectionPointsAsLight(loop.VertexLoop, plane.Normal).ToList());
                if (negativePath.Area > 0)
                {
                    negativePath.Path.Reverse();
                }
                paths.Add(negativePath.Path);
            }
            CrossSection2D = PolygonOperations.Union(paths).Select(p => new PolygonLight(p)).ToList();
        }
Example #3
0
        /// <summary>
        /// Create the Polygonal Faces for a new Tesselated Solid by extruding the given loop along the given normal.
        /// Setting midPlane to true, extrudes half forward and half reverse.
        /// </summary>
        /// <param name="loops"></param>
        /// <param name="extrudeDirection"></param>
        /// <param name="distance"></param>
        /// <param name="midPlane"></param>
        /// <returns></returns>
        public static List <PolygonalFace> ReturnFacesFromLoops(IEnumerable <IEnumerable <double[]> > loops, double[] extrudeDirection,
                                                                double distance, bool midPlane = false)
        {
            //This simplifies the cases we have to handle by always extruding in the positive direction
            if (distance < 0)
            {
                distance         = -distance;
                extrudeDirection = extrudeDirection.multiply(-1);
            }

            //First, make sure we are using "clean" loops. (e.g. not connected to any faces or edges)
            var cleanLoops = new List <List <Vertex> >();
            var i          = 0;

            foreach (var loop in loops)
            {
                var cleanLoop = new List <Vertex>();
                foreach (var vertexPosition in loop)
                {
                    //If a midPlane extrusion, move the original vertices backwards by 1/2 the extrude distance.
                    //These vertices will be used as the base for offsetting the paired vertices forward by the
                    //entire extrude distance.
                    if (midPlane)
                    {
                        var midPlaneVertexPosition = vertexPosition.add(extrudeDirection.multiply(-distance / 2), 3);
                        cleanLoop.Add(new Vertex(midPlaneVertexPosition, i));
                    }
                    else
                    {
                        cleanLoop.Add(new Vertex(vertexPosition, i));
                    }
                    i++;
                }
                cleanLoops.Add(cleanLoop);
            }
            var distanceFromOriginAlongDirection = extrudeDirection.dotProduct(cleanLoops.First().First().Position, 3);

            //First, triangulate the loops
            var listOfFaces   = new List <PolygonalFace>();
            var backTransform = new double[, ] {
            };
            var paths         = cleanLoops.Select(loop => MiscFunctions.Get2DProjectionPointsAsLightReorderingIfNecessary(loop.ToArray(), extrudeDirection, out backTransform)).ToList();
            List <PointLight[]> points2D;
            List <Vertex[]>     triangles;

            try
            {
                //Reset the list of triangles
                triangles = new List <Vertex[]>();

                //Do some polygon functions to clean up issues and try again
                //This is important because the Get2DProjections may produce invalid paths and because
                //triangulate will try 3 times before throwing the exception to go to the catch.
                paths = PolygonOperations.Union(paths, true, PolygonFillType.EvenOdd);

                //Since triangulate polygon needs the points to have references to their vertices, we need to add vertex references to each point
                //This also means we need to recreate cleanLoops
                //Also, give the vertices indices.
                cleanLoops = new List <List <Vertex> >();
                points2D   = new List <PointLight[]>();
                var j = 0;
                foreach (var path in paths)
                {
                    var pathAsPoints = path.Select(p => new PointLight(p.X, p.Y, true)).ToArray();
                    var area         = new PolygonLight(path).Area;
                    points2D.Add(pathAsPoints);
                    var cleanLoop = new List <Vertex>();
                    foreach (var point in pathAsPoints)
                    {
                        var position        = new[] { point.X, point.Y, 0.0, 1.0 };
                        var vertexPosition1 = backTransform.multiply(position).Take(3).ToArray();
                        //The point has been located back to its original position. It is not necessarily the correct distance along the cutting plane normal.
                        //So, we must move it to be on the plane
                        //This next line gets a second vertex to use for the point on plane function
                        var vertexPosition2 = vertexPosition1.add(extrudeDirection.multiply(5), 3);
                        var vertex          = MiscFunctions.PointOnPlaneFromIntersectingLine(extrudeDirection,
                                                                                             distanceFromOriginAlongDirection, new Vertex(vertexPosition1),
                                                                                             new Vertex(vertexPosition2));
                        vertex.IndexInList = j;
                        point.References.Add(vertex);
                        cleanLoop.Add(vertex);
                        j++;
                    }
                    cleanLoops.Add(cleanLoop);
                }

                bool[] isPositive       = null;
                var    triangleFaceList = TriangulatePolygon.Run2D(points2D, out _, ref isPositive);
                foreach (var face in triangleFaceList)
                {
                    triangles.AddRange(face);
                }
            }
            catch
            {
                try
                {
                    //Reset the list of triangles
                    triangles = new List <Vertex[]>();

                    //Do some polygon functions to clean up issues and try again
                    paths = PolygonOperations.Union(paths, true, PolygonFillType.EvenOdd);
                    paths = PolygonOperations.OffsetRound(paths, distance / 1000);
                    paths = PolygonOperations.OffsetRound(paths, -distance / 1000);
                    paths = PolygonOperations.Union(paths, true, PolygonFillType.EvenOdd);

                    //Since triangulate polygon needs the points to have references to their vertices, we need to add vertex references to each point
                    //This also means we need to recreate cleanLoops
                    //Also, give the vertices indices.
                    cleanLoops = new List <List <Vertex> >();
                    points2D   = new List <PointLight[]>();
                    var j = 0;
                    foreach (var path in paths)
                    {
                        var pathAsPoints = path.Select(p => new PointLight(p.X, p.Y, true)).ToArray();
                        points2D.Add(pathAsPoints);
                        var cleanLoop = new List <Vertex>();
                        foreach (var point in pathAsPoints)
                        {
                            var position        = new[] { point.X, point.Y, 0.0, 1.0 };
                            var vertexPosition1 = backTransform.multiply(position).Take(3).ToArray();
                            //The point has been located back to its original position. It is not necessarily the correct distance along the cutting plane normal.
                            //So, we must move it to be on the plane
                            //This next line gets a second vertex to use for the point on plane function
                            var vertexPosition2 = vertexPosition1.add(extrudeDirection.multiply(5), 3);
                            var vertex          = MiscFunctions.PointOnPlaneFromIntersectingLine(extrudeDirection,
                                                                                                 distanceFromOriginAlongDirection, new Vertex(vertexPosition1),
                                                                                                 new Vertex(vertexPosition2));
                            vertex.IndexInList = j;
                            point.References.Add(vertex);
                            cleanLoop.Add(vertex);
                            j++;
                        }
                        cleanLoops.Add(cleanLoop);
                    }

                    bool[] isPositive       = null;
                    var    triangleFaceList = TriangulatePolygon.Run2D(points2D, out _, ref isPositive);
                    foreach (var face in triangleFaceList)
                    {
                        triangles.AddRange(face);
                    }
                }
                catch
                {
                    Debug.WriteLine("Tried extrusion twice and failed.");
                    return(null);
                }
            }

            //Second, build up the a set of duplicate vertices
            var vertices = new HashSet <Vertex>();

            foreach (var vertex in cleanLoops.SelectMany(loop => loop))
            {
                vertices.Add(vertex);
            }
            var pairedVertices = new Dictionary <Vertex, Vertex>();

            foreach (var vertex in vertices)
            {
                var newVertex = new Vertex(vertex.Position.add(extrudeDirection.multiply(distance), 3));
                pairedVertices.Add(vertex, newVertex);
            }

            //Third, create the triangles on the two ends
            //var triangleDictionary = new Dictionary<PolygonalFace, PolygonalFace>();
            var topFaces = new List <PolygonalFace>();

            foreach (var triangle in triangles)
            {
                //Create the triangle in plane with the loops
                var v1 = triangle[1].Position.subtract(triangle[0].Position, 3);
                var v2 = triangle[2].Position.subtract(triangle[0].Position, 3);

                //This model reverses the triangle vertex ordering as necessary to line up with the normal.
                var topTriangle = v1.crossProduct(v2).dotProduct(extrudeDirection.multiply(-1), 3) < 0
                    ? new PolygonalFace(triangle.Reverse(), extrudeDirection.multiply(-1), true)
                    : new PolygonalFace(triangle, extrudeDirection.multiply(-1), true);
                topFaces.Add(topTriangle);
                listOfFaces.Add(topTriangle);

                //Create the triangle on the opposite side of the extrusion
                var bottomTriangle = new PolygonalFace(
                    new List <Vertex>
                {
                    pairedVertices[triangle[0]],
                    pairedVertices[triangle[2]],
                    pairedVertices[triangle[1]]
                }, extrudeDirection, false);
                listOfFaces.Add(bottomTriangle);
                //triangleDictionary.Add(topTriangle, bottomTriangle);
            }

            //Fourth, create the triangles on the sides
            //The normals of the faces are dependent on the whether the loops are ordered correctly from the view of the extrude direction
            //This influences which order the vertices are used to create triangles.
            for (var j = 0; j < cleanLoops.Count; j++)
            {
                var loop = cleanLoops[j];

                //Determine if the loop direction is correct by using the top face
                var v1 = loop[0];
                var v2 = loop[1];

                //Find the face with both of these vertices
                PolygonalFace firstFace = null;
                foreach (var face in topFaces)
                {
                    if (face.Vertices[0] == v1 || face.Vertices[1] == v1 || face.Vertices[2] == v1)
                    {
                        if (face.Vertices[0] == v2 || face.Vertices[1] == v2 || face.Vertices[2] == v2)
                        {
                            firstFace = face;
                            break;
                        }
                    }
                }
                if (firstFace == null)
                {
                    throw new Exception("Did not find face with both the vertices");
                }


                if (firstFace.NextVertexCCW(v1) == v2)
                {
                    //Do nothing
                }
                else if (firstFace.NextVertexCCW(v2) == v1)
                {
                    //Reverse the loop
                    loop.Reverse();
                }
                else
                {
                    throw new Exception();
                }

                //The loop is now ordered correctly
                //It does not matter whether the loop is positive or negative, only that it is ordered correctly for the given extrude direction
                for (var k = 0; k < loop.Count; k++)
                {
                    var g = k + 1;
                    if (k == loop.Count - 1)
                    {
                        g = 0;
                    }

                    //Create the new triangles
                    listOfFaces.Add(new PolygonalFace(new List <Vertex>()
                    {
                        loop[k], pairedVertices[loop[k]], pairedVertices[loop[g]]
                    }));
                    listOfFaces.Add(new PolygonalFace(new List <Vertex>()
                    {
                        loop[k], pairedVertices[loop[g]], loop[g]
                    }));
                }
            }

            return(listOfFaces);
        }
Example #4
0
        /// <summary>
        /// Gets the silhouette of a solid along a given normal. Depth of part is only used if removing tiny polygons.
        /// </summary>
        /// <param name="faces"></param>
        /// <param name="normal"></param>
        /// <param name="originalSolid"></param>
        /// <param name="minAngle"></param>
        /// <param name="minPathAreaToConsider"></param>
        /// <param name="depthOfPart"></param>
        /// <returns></returns>
        public static List <List <PointLight> > Run(IList <PolygonalFace> faces, double[] normal, TessellatedSolid originalSolid, double minAngle = 0.1,
                                                    double minPathAreaToConsider = 0.0, double depthOfPart = 0.0)
        {
            //Get the positive faces into a dictionary
            if (minAngle > 4.999)
            {
                minAngle = 4.999;                   //min angle must be between 0 and 5 degrees. 0.1 degree has proven to be good.
            }
            //Note also that the offset is based on the min angle.
            var angleTolerance  = Math.Cos((90 - minAngle) * Math.PI / 180); //Angle of 89.9 Degrees from normal
            var angleTolerance2 = Math.Cos((90 - 5) * Math.PI / 180);        //Angle of 85 Degrees from normal

            var positiveFaces     = new HashSet <PolygonalFace>();
            var smallFaces        = new List <PolygonalFace>();
            var allPositives      = new Dictionary <int, PolygonalFace>();
            var allVertices       = new HashSet <Vertex>();
            var positiveEdgeFaces = new HashSet <PolygonalFace>();

            foreach (var face in faces)
            {
                if (face.Area.IsNegligible())
                {
                    continue;
                }
                var dot = normal.dotProduct(face.Normal, 3);
                if (dot.IsGreaterThanNonNegligible(angleTolerance2))
                {
                    allPositives.Add(face.IndexInList, face);
                    positiveFaces.Add(face);
                }
                else if (dot.IsGreaterThanNonNegligible(angleTolerance))
                {
                    //allPositives.Add(face.IndexInList, face);
                    positiveEdgeFaces.Add(face);
                }
                else if (Math.Sign(dot) > 0 && face.Area < 1.0)
                {
                    smallFaces.Add(face);
                }
                foreach (var vertex in face.Vertices)
                {
                    allVertices.Add(vertex);
                }
            }
            //Add any small sliver faces that are sandwinched between two positive faces.
            foreach (var smallFace in smallFaces)
            {
                var largerEdges    = smallFace.Edges.OrderBy(e => e.Length).Take(2).ToList();
                var addToPositives = true;
                foreach (var edge in largerEdges)
                {
                    if (edge.OwnedFace == smallFace && allPositives.ContainsKey(edge.OtherFace.IndexInList))
                    {
                    }
                    else if (edge.OtherFace == smallFace && allPositives.ContainsKey(edge.OwnedFace.IndexInList))
                    {
                    }
                    else
                    {
                        addToPositives = false;
                    }
                }
                if (addToPositives)
                {
                    //allPositives.Add(smallFace.IndexInList, smallFace);
                    positiveEdgeFaces.Add(smallFace);
                }
            }

            //Get the polygons of all the positive faces. Force the polygons to be positive CCW
            var vertices = new HashSet <Vertex>();

            foreach (var face in allPositives.Values)
            {
                foreach (var vertex in face.Vertices)
                {
                    vertices.Add(vertex);
                }
            }
            var transform       = MiscFunctions.TransformToXYPlane(normal, out _);
            var projectedPoints = new Dictionary <int, PointLight>();

            foreach (var vertex in vertices)
            {
                projectedPoints.Add(vertex.IndexInList, MiscFunctions.Get2DProjectionPointAsLight(vertex, transform));
            }
            var projectedFacePolygons = positiveFaces.ToDictionary(f => f.IndexInList, f => GetPathFromFace(f, projectedPoints, true));

            //Get all the surfaces
            var allSurfaces = SeperateIntoSurfaces(allPositives);
            //var colors = new List<Color>()
            //{
            //    new Color(KnownColors.Blue),
            //    new Color(KnownColors.Red),
            //    new Color(KnownColors.Green),
            //    new Color(KnownColors.Yellow),
            //    new Color(KnownColors.Purple),
            //    new Color(KnownColors.Pink),
            //    new Color(KnownColors.Orange),
            //    new Color(KnownColors.Turquoise),
            //    new Color(KnownColors.White),
            //    new Color(KnownColors.Tan)
            //};
            //originalSolid.HasUniformColor = false;
            //var i = 0;
            //foreach (var surface in allSurfaces)
            //{
            //    if (i == colors.Count) i = 0;
            //    var color = colors[i];
            //    i++;
            //    foreach (var face in surface)
            //    {
            //        face.Color = color;
            //    }
            //}
            //Presenter.ShowAndHang(originalSolid);

            //Get the surface paths from all the surfaces and union them together
            var solution = GetSurfacePaths(allSurfaces, normal, minPathAreaToConsider, originalSolid, projectedFacePolygons).ToList();

            var positiveEdgeFacePolygons = new List <List <PointLight> >();

            foreach (var face in positiveEdgeFaces)
            {
                var polygon = new PolygonLight(MiscFunctions.Get2DProjectionPointsAsLight(face.Vertices, normal));
                if (!polygon.IsPositive)
                {
                    polygon.Path.Reverse();
                }
                positiveEdgeFacePolygons.Add(polygon.Path);
            }

            try //Try to merge them all at once
            {
                solution = PolygonOperations.Union(solution, positiveEdgeFacePolygons, false, PolygonFillType.NonZero);
            }
            catch
            {
                //Do them one at a time, skipping those that fail
                foreach (var face in positiveEdgeFacePolygons)
                {
                    try
                    {
                        solution = PolygonOperations.Union(solution, face, false, PolygonFillType.NonZero);
                    }
                    catch
                    {
                        continue;
                    }
                }
            }


            //Offset by enough to account for minimum angle
            var scale = Math.Tan(minAngle * Math.PI / 180) * depthOfPart;

            //Remove tiny polygons and slivers
            //First, Offset out and then perform a quick check for overhang polygons.
            //This is helpful when the polygon is nearly self-intersecting.
            //Then offset back out.

            solution = PolygonOperations.SimplifyFuzzy(solution, Math.Min(scale / 1000, Constants.LineLengthMinimum),
                                                       Math.Min(angleTolerance / 1000, Constants.LineSlopeTolerance));
            var offsetPolygons = PolygonOperations.OffsetMiter(solution, scale);

            offsetPolygons = EliminateOverhangPolygons(offsetPolygons, projectedFacePolygons);
            var significantSolution = PolygonOperations.OffsetMiter(offsetPolygons, -scale);

            return(significantSolution);
        }
Example #5
0
        private static IEnumerable <List <PointLight> > GetSurfacePaths(List <HashSet <PolygonalFace> > surfaces, double[] normal,
                                                                        double minAreaToConsider, TessellatedSolid originalSolid, Dictionary <int, List <PointLight> > projectedFacePolygons)
        {
            originalSolid.HasUniformColor = false;

            var red      = new Color(KnownColors.Red);
            var allPaths = new List <List <PointLight> >();

            foreach (var surface in surfaces)
            {
                //Get the surface inner and outer edges
                var outerEdges = new HashSet <Edge>();
                var innerEdges = new HashSet <Edge>();
                foreach (var face in surface)
                {
                    if (face.Edges.Count != 3)
                    {
                        throw new Exception();
                    }
                    foreach (var edge in face.Edges)
                    {
                        //if (innerEdges.Contains(edge)) continue;
                        if (!outerEdges.Contains(edge))
                        {
                            outerEdges.Add(edge);
                        }
                        else if (outerEdges.Contains(edge))
                        {
                            innerEdges.Add(edge);
                            outerEdges.Remove(edge);
                        }
                        else
                        {
                            throw new Exception();
                        }
                    }
                }

                var surfacePaths  = new List <List <PointLight> >();
                var assignedEdges = new HashSet <Edge>();
                while (outerEdges.Any())
                {
                    //Get the start vertex and edge and save them to the lists
                    var startEdge   = outerEdges.First();
                    var startVertex = startEdge.From;
                    var loop        = new List <Vertex> {
                        startVertex
                    };

                    var nextVertex = startEdge.To;
                    var edgeLoop   = new List <(Edge, Vertex, Vertex)> {
                        (startEdge, startVertex, nextVertex)
                    };
                    assignedEdges.Add(startEdge);

                    //Initialize the current vertex and edge
                    var vertex      = startEdge.From;
                    var currentEdge = startEdge;

                    //Loop until back to the start vertex
                    while (nextVertex.IndexInList != startVertex.IndexInList)
                    {
                        //Get the next edge
                        var nextEdges = nextVertex.Edges.Where(e => !assignedEdges.Contains(e)).Where(e => outerEdges.Contains(e))
                                        .ToList();
                        if (nextEdges.Count == 0)
                        {
                            Debug.WriteLine("Surface paths do not wrap around properly. Artificially closing loop.");
                            break;
                        }
                        if (nextEdges.Count > 1)
                        {
                            //There are multiple edges to go to next. Simply reversing will cause an issue
                            //if the same thing happens along the other direction.
                            //To avoid this, we go in the direction of the current edge's surface face, until we
                            //hit an edge.
                            var minAngle    = 2 * Math.PI;
                            var currentFace = surface.Contains(currentEdge.OtherFace) ? currentEdge.OtherFace : currentEdge.OwnedFace;
                            //currentFace.Color = new Color(KnownColors.White);
                            var otherVertex = currentFace.OtherVertex(currentEdge.To, currentEdge.From);
                            var angle1      = MiscFunctions.ProjectedExteriorAngleBetweenVerticesCCW(vertex, nextVertex, otherVertex, normal);
                            var angle2      = MiscFunctions.ProjectedInteriorAngleBetweenVerticesCCW(vertex, nextVertex, otherVertex, normal);
                            if (angle1 < angle2)
                            {
                                //Use the exterior angle
                                foreach (var edge in nextEdges)
                                {
                                    var furtherVertex = edge.OtherVertex(nextVertex);
                                    var angle         = MiscFunctions.ProjectedExteriorAngleBetweenVerticesCCW(vertex, nextVertex, furtherVertex, normal);
                                    if (!(angle < minAngle))
                                    {
                                        continue;
                                    }
                                    minAngle = angle;
                                    //Update the current edge
                                    currentEdge = edge;
                                }
                            }
                            else
                            {
                                //Use the interior angle
                                foreach (var edge in nextEdges)
                                {
                                    var furtherVertex = edge.OtherVertex(nextVertex);
                                    var angle         = MiscFunctions.ProjectedInteriorAngleBetweenVerticesCCW(vertex, nextVertex, furtherVertex, normal);
                                    if (!(angle < minAngle))
                                    {
                                        continue;
                                    }
                                    minAngle = angle;
                                    //Update the current edge
                                    currentEdge = edge;

                                    PolygonalFace faceInQuestion = null;
                                    if (surface.Contains(edge.OwnedFace) && surface.Contains(edge.OtherFace))
                                    {
                                        //edge.OwnedFace.Color = new Color(KnownColors.Green);
                                        //edge.OtherFace.Color = red;
                                        //Presenter.ShowVertexPathsWithSolid(new List<List<List<Vertex>>> { error2Loops },
                                        //    new List<TessellatedSolid> { originalSolid });
                                    }
                                    else if (surface.Contains(edge.OwnedFace))
                                    {
                                        faceInQuestion = edge.OtherFace;
                                    }
                                    else if (surface.Contains(edge.OtherFace))
                                    {
                                        faceInQuestion = edge.OwnedFace;
                                    }
                                    if (faceInQuestion != null)
                                    {
                                        //faceInQuestion.Color = red;
                                        //var n2 = PolygonalFace.DetermineNormal(faceInQuestion.Vertices, out _);
                                        //Presenter.ShowAndHang(originalSolid);
                                        //Presenter.ShowVertexPathsWithSolid(new List<List<List<Vertex>>> { error2Loops },
                                        //    new List<TessellatedSolid> { originalSolid });
                                    }
                                }
                            }
                            foreach (var edge in nextEdges)
                            {
                                if (currentFace.Edges.Contains(edge))
                                {
                                    if (edge == currentEdge)
                                    {
                                        break;                      //This is what we want
                                    }
                                    //Presenter.ShowVertexPathsWithSolid(new List<List<List<Vertex>>> { error2Loops },
                                    //    new List<TessellatedSolid> { originalSolid });
                                }
                            }
                        }
                        else
                        {
                            //Update the current edge
                            currentEdge = nextEdges.First();
                        }
                        //Update the current vertex
                        vertex = nextVertex;
                        loop.Add(vertex);
                        //Get the next vertex
                        nextVertex = currentEdge.OtherVertex(vertex);
                        edgeLoop.Add((currentEdge, vertex, nextVertex));
                        assignedEdges.Add(currentEdge);
                    }

                    //To determine order:
                    //The vertices should be listed such that their edge vector cross producted with the second point,
                    //toward a third point on the positive face that provided this edge lines up with the normal.
                    //If that is incorrect, then this may be a hole.
                    //All edges should agree on this test.
                    var correct       = 0;
                    var needsReversal = 0;
                    foreach (var edgeTuple in edgeLoop)
                    {
                        var edge = edgeTuple.Item1;
                        outerEdges.Remove(edge);
                        var isOtherFace = surface.Contains(edge.OtherFace);
                        var isOwnedFace = surface.Contains(edge.OwnedFace);
                        if (isOwnedFace == isOtherFace)
                        {
                            throw new Exception("Should be one and only one face for this edge on this surface");
                        }
                        var positiveFaceBelongingToEdge = isOwnedFace ? edge.OwnedFace : edge.OtherFace;
                        var vertex3 = positiveFaceBelongingToEdge.OtherVertex(edge);
                        var v1      = vertex3.Position.subtract(edgeTuple.Item3.Position, 3);         //To point according to our loop
                        var v2      = edgeTuple.Item3.Position.subtract(edgeTuple.Item2.Position, 3); //To minus from
                        var dot     = v2.crossProduct(v1).dotProduct(positiveFaceBelongingToEdge.Normal, 3);
                        if (dot > 0)
                        {
                            correct++;
                        }
                        else
                        {
                            needsReversal++;
                        }
                    }
                    if (needsReversal > correct)
                    {
                        loop.Reverse();
                    }
                    //if(needsReversal*correct != 0) Debug.WriteLine("Reversed Loop Count: " + needsReversal + " Forward Loop Count: " + correct);

                    //Get2DProjections does not project directionally (normal and normal.multiply(-1) return the same transform)
                    //However, the way we are unioning the polygons and eliminating overhand polygons seems to be taking care of this
                    var surfacePath = MiscFunctions.Get2DProjectionPointsAsLight(loop, normal).ToList();
                    var area2D      = MiscFunctions.AreaOfPolygon(surfacePath);
                    if (area2D.IsNegligible(minAreaToConsider))
                    {
                        continue;
                    }

                    //Trust the ordering from the face normals. A self intersecting polygon may have a negative area,
                    //but in-fact be positive once it undergoes a Fill Positive union. Same goes for positive areas.
                    //if (Math.Sign(area2D) != Math.Sign(area3D)) surfacePath.Reverse();
                    surfacePaths.Add(surfacePath);
                }
                if (!surfacePaths.Any())
                {
                    continue;
                }
                allPaths.AddRange(surfacePaths);
            }

            //By unioning the path into non-self intersecting paths,
            //partially covered holes will be reduced to their final non-covered size.
            //This is necessary for the next few checks in determining if it is a hole or an overhang.
            //This union operation is the trickiest union in the silhouette function to reason through.
            //Using positive fill or even/odd perform pretty well, but they union overlapping
            //negative regions. This is undesirable, since we do not want to union a hole
            //with an overlapping region. For this reason, Union Non-Zero is used. It keeps
            //the holes in their proper orientation and does not combine them together.
            var nonSelfIntersectingPaths = PolygonOperations.Union(allPaths, false, PolygonFillType.NonZero);
            var correctedSurfacePath     = EliminateOverhangPolygons(nonSelfIntersectingPaths, projectedFacePolygons);

            //if (allPaths.Sum(p => p.Count) > 10) Presenter.ShowAndHang(nonSelfIntersectingPaths);

            return(correctedSurfacePath);
        }
Example #6
0
        /// <summary>
        ///     Updates the with.
        /// </summary>
        /// <param name="face">The face.</param>
        public bool BuildIfCylinderIsHole(bool isPositive)
        {
            if (isPositive)
            {
                throw new Exception("BuildIfCylinderIsHole assumes that the faces have already been collected, " +
                                    "such that the cylinder is negative");
            }
            IsPositive = false;

            //To truly be a hole, there should be two loops of vertices that form circles on either ends of the faces.
            //These are easy to capture because all the edges between them should be shared by two of the faces
            //Start by collecting the edges at either end. Each edge belongs to only two faces, so any edge that only
            //comes up once, must be at the edge of the cylinder (assuming it is a cylinder).
            var edges = new HashSet <Edge>();

            foreach (var face in Faces)
            {
                foreach (var edge in face.Edges)
                {
                    if (edges.Contains(edge))
                    {
                        edges.Remove(edge);
                    }
                    else
                    {
                        edges.Add(edge);
                    }
                }
            }
            //Now we can loop through the edges to form two loops
            if (edges.Count < 5)
            {
                return(false);                 //5 is the minimum number of vertices to look remotely circular (8 is more likely)
            }
            var(allLoopsClosed, edgeLoops, loops) = GetLoops(edges, true);
            if (loops.Count != 2)
            {
                return(false);                  //There must be two and only two loops.
            }
            Loop1     = new HashSet <Vertex>(loops[0]);
            Loop2     = new HashSet <Vertex>(loops[1]);
            EdgeLoop1 = edgeLoops[0];
            EdgeLoop2 = edgeLoops[1];

            //Next, we need to get the central axis.
            //The ends of the cylinder could be any shape (flat, curved, angled) and the shapes
            //on each end do not need to match. This rules out using the vertex loops to form
            //a plane (most accurate) and creating a plane from edge midpoints (next most accurate).
            //The next most accurate thing is to use the edge vectors to set the axis.
            //This is more precise than taking a bunch of cross products with the faces.
            //And it is more universal than creating a plane from the loops, since it works
            //for holes that enter and exit at an angle.
            var throughEdgeVectors = new Dictionary <Vertex, double[]>();
            var dotFromSharpestEdgesConnectedToVertex = new Dictionary <Vertex, double>();

            foreach (var edge in InnerEdges)
            {
                //Skip those edges that are on "flat" surfaces
                var dot = edge.OwnedFace.Normal.dotProduct(edge.OtherFace.Normal);
                if (dot.IsPracticallySame(1.0, Constants.ErrorForFaceInSurface))
                {
                    continue;
                }
                //This uses a for loop to remove duplicate code, to decide which vertex to check with which loop
                for (var i = 0; i < 2; i++)
                {
                    var A         = i == 0 ? edge.To : edge.From;
                    var B         = i == 0 ? edge.From : edge.To;
                    var direction = edge.Vector.normalize();
                    //Positive if B is further along
                    var previousDistance = direction.dotProduct(B.Position);
                    var sign             = Math.Sign(direction.dotProduct(B.Position) - direction.dotProduct(A.Position));
                    if (Loop1.Contains(A))
                    {
                        bool reachedEnd = Loop2.Contains(B);
                        if (!reachedEnd)
                        {
                            //Check if this edge needs to "extended" to reach the end of the cylinder
                            var previousEdge   = edge;
                            var previousVertex = B;
                            while (!reachedEnd)
                            {
                                var  maxDot        = 0.0;
                                Edge extensionEdge = null;
                                foreach (var otherEdge in previousVertex.Edges.Where(e => e != previousEdge))
                                {
                                    //This other edge must be contained in the InnerEdges and along the same direction
                                    if (!InnerEdges.Contains(otherEdge))
                                    {
                                        continue;
                                    }
                                    var edgeDot = Math.Abs(otherEdge.Vector.normalize().dotProduct(previousEdge.Vector.normalize()));
                                    if (!edgeDot.IsPracticallySame(1.0, Constants.ErrorForFaceInSurface))
                                    {
                                        continue;
                                    }
                                    var distance = sign * (direction.dotProduct(otherEdge.OtherVertex(previousVertex).Position) - previousDistance);
                                    if (!distance.IsGreaterThanNonNegligible())
                                    {
                                        continue;                                         //This vertex is not any further along
                                    }
                                    //Choose the edge that is most along the previous edge
                                    if (edgeDot > maxDot)
                                    {
                                        maxDot        = edgeDot;
                                        extensionEdge = otherEdge;
                                    }
                                }
                                if (extensionEdge == null)
                                {
                                    break;                        //go to the next edge
                                }
                                if (Loop2.Contains(extensionEdge.OtherVertex(previousVertex)))
                                {
                                    reachedEnd = true;
                                    B          = extensionEdge.OtherVertex(previousVertex);
                                }
                                else
                                {
                                    previousVertex = extensionEdge.OtherVertex(previousVertex);
                                    previousEdge   = extensionEdge;
                                }
                            }
                        }
                        //If there was a vertex from the edge or edges in the second loop.
                        if (reachedEnd)
                        {
                            if (!dotFromSharpestEdgesConnectedToVertex.ContainsKey(A))
                            {
                                throughEdgeVectors.Add(A, B.Position.subtract(A.Position));
                                dotFromSharpestEdgesConnectedToVertex.Add(A, edge.InternalAngle);
                            }
                            else if (dot < dotFromSharpestEdgesConnectedToVertex[A])
                            {
                                throughEdgeVectors[A] = B.Position.subtract(A.Position);
                                dotFromSharpestEdgesConnectedToVertex[A] = dot;
                            }
                            break; //Go to the next edge
                        }
                    }
                }
            }
            if (throughEdgeVectors.Count < 3)
            {
                return(false);
            }

            //Estimate the axis from the sum of the through edge vectors
            //The axis points from loop 1 to loop 2, since we always start the edge vector from Loop1
            var edgeVectors = new List <double[]>(throughEdgeVectors.Values);
            var numEdges    = edgeVectors.Count;
            var axis        = new double[] { 0.0, 0.0, 0.0 };

            foreach (var edgeVector in edgeVectors)
            {
                axis = axis.add(edgeVector);
            }
            Axis = axis.normalize();

            /* to adjust the Axis, we will average the cross products of the new face with all the old faces */
            //Since we will be taking cross products, we need to be sure not to have faces along the same normal
            var faces = MiscFunctions.FacesWithDistinctNormals(Faces.ToList());
            var n     = faces.Count;

            //Check if the loops are circular along the axis
            var path1 = MiscFunctions.Get2DProjectionPointsAsLight(Loop1, Axis, out var backTransform);
            var poly  = new PolygonLight(path1);

            if (!PolygonOperations.IsCircular(new Polygon(poly), out var centerCircle, Constants.MediumConfidence))
            {
                return(false);
            }
            var path2 = MiscFunctions.Get2DProjectionPointsAsLight(Loop2, Axis, out var backTransform2);
            var poly2 = new PolygonLight(path2);

            if (!PolygonOperations.IsCircular(new Polygon(poly2), out var centerCircle2, Constants.MediumConfidence))
            {
                return(false);
            }
            Radius = (centerCircle.Radius + centerCircle2.Radius) / 2; //Average
            //Set the Anchor/Center point
            var center = centerCircle.Center.Add(centerCircle2.Center).divide(2);

            Anchor = MiscFunctions.Convert2DVectorTo3DVector(center, backTransform);
            return(true);
        }
        private double[,] CreateDistanceGrid(IList <Polygon> layer)
        {
            var allIntersections = PolygonOperations.AllPolygonIntersectionPointsAlongHorizontalLines(layer, _yMin, numGridY, discretization, out var firstIntersectingIndex);

            using var allIntersectionsEnumerator = allIntersections.GetEnumerator();
            var grid = new double[numGridX, numGridY];

            for (int j = 0; j < numGridY; j++)
            {
                double[] intersections = null;
                if (j >= firstIntersectingIndex)
                {
                    allIntersectionsEnumerator.MoveNext();
                    intersections = allIntersectionsEnumerator.Current;
                }
                if (intersections == null)
                {
                    for (int i = 0; i < numGridX; i++)
                    {
                        grid[i, j] = double.PositiveInfinity;
                    }
                }
                else
                {
                    var xIndex = 0;
                    var x      = _xMin;
                    for (int i = 0; i < numGridX; i++)
                    {
                        while (xIndex < intersections.Length && x > intersections[xIndex])
                        {
                            xIndex++;
                        }
                        if (xIndex % 2 == 0)
                        {
                            grid[i, j] = double.PositiveInfinity;
                        }
                        else
                        {
                            grid[i, j] = double.NegativeInfinity;
                        }
                        x += discretization;
                    }
                }
            }
            foreach (var polygon in layer)
            {
                var numSegments = polygon.Path.Count;
                var fromPoint   = polygon.Path[numSegments - 1];
                var lastPoint   = polygon.Path[numSegments - 2];
                var polygonMinX = polygon.MinX;
                var polygonMinY = polygon.MinY;
                var polygonMaxX = polygon.MaxX;
                var polygonMaxY = polygon.MaxY;
                var iMin        = Math.Max((int)((polygonMinX - _xMin) * coordToGridFactor) - Constants.MarchingCubesBufferFactor, 0);
                var iMax        = Math.Min((int)((polygonMaxX - _xMin) * coordToGridFactor) + Constants.MarchingCubesBufferFactor + 1, numGridX);
                var jMin        = Math.Max((int)((polygonMinY - _yMin) * coordToGridFactor) - Constants.MarchingCubesBufferFactor, 0);
                var jMax        = Math.Min((int)((polygonMaxY - _yMin) * coordToGridFactor) + Constants.MarchingCubesBufferFactor + 1, numGridY);
                foreach (var toPoint in polygon.Path)
                {
                    if (Math.Abs(toPoint.Y - fromPoint.Y) > Math.Abs(toPoint.X - fromPoint.X))
                    {
                        ExpandHorizontally(lastPoint, fromPoint, toPoint, grid, iMin, iMax, jMin, jMax);
                    }
                    else
                    {
                        ExpandVertically(lastPoint, fromPoint, toPoint, grid, iMin, iMax, jMin, jMax);
                    }
                    lastPoint = fromPoint;
                    fromPoint = toPoint;
                }
            }
            return(grid);
        }
Example #8
0
        /// <summary>
        ///     Rotating the calipers 2D method. Convex hull must be a counter clockwise loop.
        /// </summary>
        /// <param name="points">The points.</param>
        /// <param name="pointsAreConvexHull">if set to <c>true</c> [points are convex hull].</param>
        /// <returns>System.Double.</returns>
        /// <exception cref="Exception">
        ///     Area should never be negligilbe unless data is messed up.
        /// </exception>
        private static BoundingRectangle RotatingCalipers2DMethod(IList <PointLight> points, bool pointsAreConvexHull = false)
        {
            if (points.Count < 3)
            {
                throw new Exception("Rotating Calipers requires at least 3 points.");
            }
            var cvxPoints = pointsAreConvexHull ? points : ConvexHull2D(points).ToList();
            //Simplify the points to make sure they are the minimal convex hull
            //Only set it as the convex hull if it contains more than three points.
            var cvxPointsSimple = PolygonOperations.SimplifyFuzzy(cvxPoints);

            if (cvxPointsSimple.Count >= 3)
            {
                cvxPoints = cvxPointsSimple;
            }
            /* the cvxPoints will be arranged from a point with minimum X-value around in a CCW loop to the last point */
            //First, check to make sure the given convex hull has the min x-value at 0.
            var minX         = cvxPoints[0].X;
            var numCvxPoints = cvxPoints.Count;
            var startIndex   = 0;

            for (var i = 1; i < numCvxPoints; i++)
            {
                if (!(cvxPoints[i].X < minX))
                {
                    continue;
                }
                minX       = cvxPoints[i].X;
                startIndex = i;
            }
            //Reorder if necessary
            var tempList = new List <PointLight>();

            if (startIndex != 0)
            {
                for (var i = startIndex; i < numCvxPoints; i++)
                {
                    tempList.Add(cvxPoints[i]);
                }
                for (var i = 0; i < startIndex; i++)
                {
                    tempList.Add(cvxPoints[i]);
                }
                cvxPoints = tempList;
            }


            var extremeIndices = new int[4];

            //Good picture of extreme vertices in the following link
            //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.155.5671&rep=rep1&type=pdf
            //Godfried Toussaint: Solving Geometric Problems with the Rotating Calipers
            //Note that while these points are ordered counter clockwise, we are rotating the calipers in reverse (clockwise),
            //Which is why the points are directed this way.
            //Point0 = min X, with max Y for ties
            //Point1 = min Y, with min X for ties
            //Point2 = max X, with min Y for ties
            //Point3 = max Y, with max X for ties

            // extremeIndices[3] => max-Y, with max X for ties
            extremeIndices[3] = cvxPoints.Count - 1;
            // this is likely rare, but first we check if the first point has a higher y value (only when point is both min-x and max-Y)
            if (cvxPoints[0].Y > cvxPoints[extremeIndices[3]].Y)
            {
                extremeIndices[3] = 0;
            }
            else
            {
                while (extremeIndices[3] > 0 && cvxPoints[extremeIndices[3]].Y <= cvxPoints[extremeIndices[3] - 1].Y)
                {
                    extremeIndices[3]--;
                }
            }
            /* at this point, the max-Y point has been established. Next we walk backwards in the list until we hit the max-X point */
            // extremeIndices[2] => max-X, with min Y for ties
            extremeIndices[2] = extremeIndices[3] == 0 ? cvxPoints.Count - 1 : extremeIndices[3];
            while (extremeIndices[2] > 0 && cvxPoints[extremeIndices[2]].X <= cvxPoints[extremeIndices[2] - 1].X)
            {
                extremeIndices[2]--;
            }
            // extremeIndices[1] => min-Y, with min X for ties
            extremeIndices[1] = extremeIndices[2] == 0 ? cvxPoints.Count - 1 : extremeIndices[2];
            while (extremeIndices[1] > 0 && cvxPoints[extremeIndices[1]].Y >= cvxPoints[extremeIndices[1] - 1].Y)
            {
                extremeIndices[1]--;
            }
            // extrememIndices[0] => min-X, with max Y for ties
            // First we check if the last point has an eqaully small x value, if it does we will need to walk backwards.
            if (cvxPoints.Last().X > cvxPoints[0].X)
            {
                extremeIndices[0] = 0;
            }
            else
            {
                extremeIndices[0] = cvxPoints.Count - 1;
                while (cvxPoints[extremeIndices[0]].X >= cvxPoints[extremeIndices[0] - 1].X)
                {
                    extremeIndices[0]--;
                }
            }

            #region Cycle through 90-degrees

            var deltaAngles   = new double[4];
            var offsetAngles  = new[] { Math.PI / 2, Math.PI, -Math.PI / 2, 0.0 };
            var bestRectangle = new BoundingRectangle {
                Area = double.PositiveInfinity
            };
            var bestRectanglePointsOnSide = new List <PointLight> [4];
            do
            {
                #region update the deltaAngles from the current orientation

                //For each of the 4 supporting points (those forming the rectangle),
                for (var i = 0; i < 4; i++)
                {
                    var index     = extremeIndices[i];
                    var prev      = index == 0 ? numCvxPoints - 1 : index - 1;
                    var tempDelta = Math.Atan2(cvxPoints[prev].Y - cvxPoints[index].Y,
                                               cvxPoints[prev].X - cvxPoints[index].X);
                    deltaAngles[i] = offsetAngles[i] - tempDelta;
                    //If the angle has rotated beyond the 90 degree bounds, it will be negative
                    //And should never be chosen from then on.
                    if (deltaAngles[i] < 0)
                    {
                        deltaAngles[i] = double.PositiveInfinity;
                    }
                }
                var angle = deltaAngles.Min();
                if (angle.IsGreaterThanNonNegligible(Math.PI / 2))
                {
                    break;
                }
                var refIndex = deltaAngles.FindIndex(angle);

                #endregion

                #region find area

                //Get unit normal for current edge
                var otherIndex = extremeIndices[refIndex] == 0 ? numCvxPoints - 1 : extremeIndices[refIndex] - 1;
                var direction  =
                    cvxPoints[extremeIndices[refIndex]].Position.subtract(cvxPoints[otherIndex].Position)
                    .normalize();
                //If point type = 1 or 3, then use inversed Direction
                if (refIndex == 1 || refIndex == 3)
                {
                    direction = new[] { -direction[1], direction[0] };
                }
                var vectorWidth = new[]
                {
                    cvxPoints[extremeIndices[2]].X - cvxPoints[extremeIndices[0]].X,
                    cvxPoints[extremeIndices[2]].Y - cvxPoints[extremeIndices[0]].Y
                };

                var angleVector1 = new[] { -direction[1], direction[0] };
                var width        = Math.Abs(vectorWidth.dotProduct(angleVector1));
                var vectorHeight = new[]
                {
                    cvxPoints[extremeIndices[3]].X - cvxPoints[extremeIndices[1]].X,
                    cvxPoints[extremeIndices[3]].Y - cvxPoints[extremeIndices[1]].Y
                };
                var angleVector2 = new[] { direction[0], direction[1] };
                var height       = Math.Abs(vectorHeight.dotProduct(angleVector2));
                var area         = height * width;

                #endregion

                var xDir          = new[] { angleVector1[0], angleVector1[1] };
                var yDir          = new[] { angleVector2[0], angleVector2[1] };
                var pointsOnSides = new List <PointLight> [4];
                for (var i = 0; i < 4; i++)
                {
                    pointsOnSides[i] = new List <PointLight>();
                    var dir       = i % 2 == 0 ? xDir : yDir;
                    var distance  = cvxPoints[extremeIndices[i]].Position.dotProduct(dir, 2);
                    var prevIndex = extremeIndices[i];
                    do
                    {
                        extremeIndices[i] = prevIndex;
                        pointsOnSides[i].Add(cvxPoints[extremeIndices[i]]);
                        prevIndex = extremeIndices[i] == 0 ? numCvxPoints - 1 : extremeIndices[i] - 1;
                    } while (distance.IsPracticallySame(cvxPoints[prevIndex].Position.dotProduct(dir, 2),
                                                        Constants.BaseTolerance));
                }

                if (bestRectangle.Area > area)
                {
                    bestRectanglePointsOnSide  = pointsOnSides;
                    bestRectangle.Area         = area;
                    bestRectangle.Dimensions   = new[] { width, height };
                    bestRectangle.Directions2D = new[] { xDir, yDir };
                }
            } while (true); //process will end on its own by the break statement in line 314

            #endregion

            var pointsOnSidesAsPoints = new List <Point> [4];
            for (var i = 0; i < 4; i++)
            {
                pointsOnSidesAsPoints[i] = bestRectanglePointsOnSide[i].Select(p => new Point(p)).ToList();
            }
            bestRectangle.PointsOnSides = pointsOnSidesAsPoints;

            if (bestRectangle.Area.IsNegligible())
            {
                throw new Exception("Area should never be negligilbe unless data is messed up.");
            }
            bestRectangle.SetCornerPoints();
            return(bestRectangle);
        }
Example #9
0
        /// <summary>
        ///     Rotating the calipers 2D method. Convex hull must be a counter clockwise loop.
        ///     Optional booleans for what information should be set in the Bounding Rectangle.
        ///     Example: If you really just need the area, you don't need the corner points or
        ///     points on side.
        /// </summary>
        /// <param name="points">The points.</param>
        /// <param name="pointsAreConvexHull">if set to <c>true</c> [points are convex hull].</param>
        /// <param name="setCornerPoints"></param>
        /// <param name="setPointsOnSide"></param>
        /// <returns>System.Double.</returns>
        /// <exception cref="Exception">
        ///     Area should never be negligilbe unless data is messed up.
        /// </exception>

        /*
         * private static BoundingRectangle RotatingCalipers2DMethod(IList<Point> points, bool pointsAreConvexHull = false,
         *  bool setCornerPoints = true, bool setPointsOnSide = true)
         * {
         #region Initialization
         *  if(points.Count < 3) throw new Exception("Rotating Calipers requires at least 3 points.");
         *  var cvxPoints = pointsAreConvexHull ? points : ConvexHull2D(points);
         *  //Simplify the points to make sure they are the minimal convex hull
         *  //Only set it as the convex hull if it contains more than three points.
         *  var cvxPointsSimple = PolygonOperations.SimplifyFuzzy(cvxPoints);
         *  if (cvxPointsSimple.Count >= 3) cvxPoints = cvxPointsSimple;
         *  // the cvxPoints will be arranged from a point with minimum X-value around in a CCW loop to the last point
         *  //First, check to make sure the given convex hull has the min x-value at 0.
         *  var minX = cvxPoints[0].X;
         *  var numCvxPoints = cvxPoints.Count;
         *  var startIndex = 0;
         *  for (var i = 1; i < numCvxPoints; i++)
         *  {
         *      if (!(cvxPoints[i].X < minX)) continue;
         *      minX = cvxPoints[i].X;
         *      startIndex = i;
         *  }
         *  //Reorder if necessary
         *  var tempList = new List<Point>();
         *  if (startIndex != 0)
         *  {
         *      for (var i = startIndex; i < numCvxPoints; i++)
         *      {
         *          tempList.Add(cvxPoints[i]);
         *      }
         *      for (var i = 0; i < startIndex; i++)
         *      {
         *          tempList.Add(cvxPoints[i]);
         *      }
         *      cvxPoints = tempList;
         *  }
         *
         *
         *  var extremeIndices = new int[4];
         *
         *  //Good picture of extreme vertices in the following link
         *  //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.155.5671&rep=rep1&type=pdf
         *  //Godfried Toussaint: Solving Geometric Problems with the Rotating Calipers
         *  //Note that while these points are ordered counter clockwise, we are rotating the calipers in reverse (clockwise),
         *  //Which is why the points are directed this way.
         *  //Point0 = min X, with max Y for ties
         *  //Point1 = min Y, with min X for ties
         *  //Point2 = max X, with min Y for ties
         *  //Point3 = max Y, with max X for ties
         *
         *  // extremeIndices[3] => max-Y, with max X for ties
         *  extremeIndices[3] = cvxPoints.Count - 1;
         *  // this is likely rare, but first we check if the first point has a higher y value (only when point is both min-x and max-Y)
         *  if (cvxPoints[0].Y > cvxPoints[extremeIndices[3]].Y) extremeIndices[3] = 0;
         *  else
         *  {
         *      while (extremeIndices[3] > 0 && cvxPoints[extremeIndices[3]].Y <= cvxPoints[extremeIndices[3] - 1].Y)
         *          extremeIndices[3]--;
         *  }
         *  // at this point, the max-Y point has been established. Next we walk backwards in the list until we hit the max-X point
         *  // extremeIndices[2] => max-X, with min Y for ties
         *  extremeIndices[2] = extremeIndices[3] == 0 ? cvxPoints.Count - 1 : extremeIndices[3];
         *  while (extremeIndices[2] > 0 && cvxPoints[extremeIndices[2]].X <= cvxPoints[extremeIndices[2] - 1].X)
         *      extremeIndices[2]--;
         *  // extremeIndices[1] => min-Y, with min X for ties
         *  extremeIndices[1] = extremeIndices[2] == 0 ? cvxPoints.Count - 1 : extremeIndices[2];
         *  while (extremeIndices[1] > 0 && cvxPoints[extremeIndices[1]].Y >= cvxPoints[extremeIndices[1] - 1].Y)
         *      extremeIndices[1]--;
         *  // extrememIndices[0] => min-X, with max Y for ties
         *  // First we check if the last point has an eqaully small x value, if it does we will need to walk backwards.
         *  if (cvxPoints.Last().X > cvxPoints[0].X) extremeIndices[0] = 0;
         *  else
         *  {
         *      extremeIndices[0] = cvxPoints.Count - 1;
         *      while (cvxPoints[extremeIndices[0]].X >= cvxPoints[extremeIndices[0] - 1].X)
         *          extremeIndices[0]--;
         *  }
         *
         #endregion
         *
         #region Cycle through 90-degrees
         *  var bestRectangle = new BoundingRectangle { Area = double.MaxValue };
         *  var deltaAngles = new double[4];
         *  do
         *  {
         #region update the deltaAngles from the current orientation
         *
         *      //For each of the 4 supporting points (those forming the rectangle),
         *      for (var i = 0; i < 4; i++)
         *      {
         *          var index = extremeIndices[i];
         *          var prev = index == 0 ? numCvxPoints - 1 : index - 1;
         *          var tempDelta = Math.Atan2(cvxPoints[prev].Y - cvxPoints[index].Y,
         *              cvxPoints[prev].X - cvxPoints[index].X);
         *          deltaAngles[i] = CaliperOffsetAngles[i] - tempDelta;
         *          //If the angle has rotated beyond the 90 degree bounds, it will be negative
         *          //And should never be chosen from then on.
         *          if (deltaAngles[i] < 0) deltaAngles[i] = double.PositiveInfinity;
         *      }
         *      var angle = deltaAngles.Min();
         *      if (angle.IsGreaterThanNonNegligible(Math.PI/2))
         *          break;
         *      var refIndex = deltaAngles.FindIndex(angle);
         *
         #endregion
         *
         #region find area
         *
         *      //Get unit normal for current edge
         *      var otherIndex = extremeIndices[refIndex] == 0 ? numCvxPoints - 1 : extremeIndices[refIndex] - 1;
         *      var direction =
         *          cvxPoints[extremeIndices[refIndex]].Position.subtract(cvxPoints[otherIndex].Position, 2)
         *              .normalize(2);
         *      //If point type = 1 or 3, then use inversed Direction
         *      if (refIndex == 1 || refIndex == 3)
         *      {
         *          direction = new[] {-direction[1], direction[0]};
         *      }
         *      var vectorLength = new[]
         *      {
         *          cvxPoints[extremeIndices[2]][0] - cvxPoints[extremeIndices[0]][0],
         *          cvxPoints[extremeIndices[2]][1] - cvxPoints[extremeIndices[0]][1]
         *      };
         *
         *      var angleVector1 = new[] {-direction[1], direction[0]};
         *      var length = Math.Abs(vectorLength.dotProduct(angleVector1, 2));
         *      var vectorWidth = new[]
         *      {
         *          cvxPoints[extremeIndices[3]][0] - cvxPoints[extremeIndices[1]][0],
         *          cvxPoints[extremeIndices[3]][1] - cvxPoints[extremeIndices[1]][1]
         *      };
         *      var angleVector2 = new[] {direction[0], direction[1]};
         *      var width = Math.Abs(vectorWidth.dotProduct(angleVector2, 2));
         *      var area = length * width;
         *
         #endregion
         *
         *      var d1Max = double.MinValue;
         *      var d1Min = double.MaxValue;
         *      var d2Max = double.MinValue;
         *      var d2Min = double.MaxValue;
         *      var pointsOnSides = new List<Point>[4];
         *      for (var i = 0; i < 4; i++)
         *      {
         *          pointsOnSides[i] = new List<Point>();
         *          var dir = i%2 == 0 ? angleVector1 : angleVector2;
         *          var distance = cvxPoints[extremeIndices[i]].Position.dotProduct(dir, 2);
         *          if (i % 2 == 0) //D1
         *          {
         *              if (distance > d1Max) d1Max = distance;
         *              if (distance < d1Min) d1Min = distance;
         *          }
         *          else //D2
         *          {
         *              if (distance > d2Max) d2Max = distance;
         *              if (distance < d2Min) d2Min = distance;
         *          }
         *          var prevIndex = extremeIndices[i];
         *          do
         *          {
         *              extremeIndices[i] = prevIndex;
         *              if (setPointsOnSide)
         *              {
         *                  pointsOnSides[i].Add(cvxPoints[extremeIndices[i]]);
         *              }
         *              prevIndex = extremeIndices[i] == 0 ? numCvxPoints - 1 : extremeIndices[i] - 1;
         *          } while (distance.IsPracticallySame(cvxPoints[prevIndex].Position.dotProduct(dir, 2),
         *              Constants.BaseTolerance));
         *      }
         *
         *      //If this is an improvement, set the parameters for the best bounding rectangle.
         *      if (area < bestRectangle.Area)
         *      {
         *          bestRectangle.Area = area;
         *          bestRectangle.Length = length; //Length corresponds with Direction 1
         *          bestRectangle.Width = width;
         *          bestRectangle.LengthDirection = angleVector1;
         *          bestRectangle.WidthDirection = angleVector2;
         *          bestRectangle.LengthDirectionMax = d1Max;
         *          bestRectangle.LengthDirectionMin = d1Min;
         *          bestRectangle.WidthDirectionMax = d2Max;
         *          bestRectangle.WidthDirectionMin = d2Min;
         *          bestRectangle.PointsOnSides = pointsOnSides;
         *      }
         *  } while (true); //process will end on its own by the break statement in line 314
         *
         #endregion
         *
         *  if (bestRectangle.Area.IsNegligible())
         *      throw new Exception("Area should never be negligilbe unless data is messed up.");
         *
         *  if (setCornerPoints)
         *      bestRectangle.SetCornerPoints();
         *
         *  return bestRectangle;
         * }
         */

        /// <summary>
        ///     Rotating the calipers 2D method. Convex hull must be a counter clockwise loop.
        ///     Optional booleans for what information should be set in the Bounding Rectangle.
        ///     Example: If you really just need the area, you don't need the corner points or
        ///     points on side.
        /// </summary>
        /// <param name="points">The points.</param>
        /// <param name="pointsAreConvexHull">if set to <c>true</c> [points are convex hull].</param>
        /// <param name="setCornerPoints"></param>
        /// <param name="setPointsOnSide"></param>
        /// <returns>System.Double.</returns>
        /// <exception cref="Exception">
        ///     Area should never be negligible unless data is messed up.
        /// </exception>
        private static BoundingRectangle RotatingCalipers2DMethod(IList <PointLight> points,
                                                                  bool pointsAreConvexHull = false, bool setCornerPoints = true,
                                                                  bool setPointsOnSide     = true)
        {
            if (points.Count < 3)
            {
                throw new Exception("Rotating Calipers requires at least 3 points.");
            }
            var cvxPoints = pointsAreConvexHull ? points : ConvexHull2D(points).ToList();

            //Simplify the points to make sure they are the minimal convex hull
            //Only set it as the convex hull if it contains more than three points.
            var cvxPointsSimple = PolygonOperations.SimplifyFuzzy(cvxPoints);

            if (cvxPointsSimple.Count >= 3)
            {
                cvxPoints = cvxPointsSimple;
            }
            /* the cvxPoints will be arranged from a point with minimum X-value around in a CCW loop to the last point */
            //First, check to make sure the given convex hull has the min x-value at 0.
            var minX         = cvxPoints[0].X;
            var numCvxPoints = cvxPoints.Count;
            var startIndex   = 0;

            for (var i = 1; i < numCvxPoints; i++)
            {
                if (!(cvxPoints[i].X < minX))
                {
                    continue;
                }
                minX       = cvxPoints[i].X;
                startIndex = i;
            }
            //Reorder if necessary
            var tempList = new List <PointLight>();

            if (startIndex != 0)
            {
                for (var i = startIndex; i < numCvxPoints; i++)
                {
                    tempList.Add(cvxPoints[i]);
                }
                for (var i = 0; i < startIndex; i++)
                {
                    tempList.Add(cvxPoints[i]);
                }
                cvxPoints = tempList;
            }

            #region Get Extreme Points
            var extremeIndices = new int[4];

            //Good picture of extreme vertices in the following link
            //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.155.5671&rep=rep1&type=pdf
            //Godfried Toussaint: Solving Geometric Problems with the Rotating Calipers
            //Note that while these points are ordered counter clockwise, we are rotating the calipers in reverse (clockwise),
            //Which is why the points are directed this way.
            //Point0 = min X, with max Y for ties
            //Point1 = min Y, with min X for ties
            //Point2 = max X, with min Y for ties
            //Point3 = max Y, with max X for ties

            // extremeIndices[3] => max-Y, with max X for ties
            extremeIndices[3] = cvxPoints.Count - 1;
            // this is likely rare, but first we check if the first point has a higher y value (only when point is both min-x and max-Y)
            if (cvxPoints[0].Y > cvxPoints[extremeIndices[3]].Y)
            {
                extremeIndices[3] = 0;
            }
            else
            {
                while (extremeIndices[3] > 0 && cvxPoints[extremeIndices[3]].Y <= cvxPoints[extremeIndices[3] - 1].Y)
                {
                    extremeIndices[3]--;
                }
            }
            /* at this point, the max-Y point has been established. Next we walk backwards in the list until we hit the max-X point */
            // extremeIndices[2] => max-X, with min Y for ties
            extremeIndices[2] = extremeIndices[3] == 0 ? cvxPoints.Count - 1 : extremeIndices[3];
            while (extremeIndices[2] > 0 && cvxPoints[extremeIndices[2]].X <= cvxPoints[extremeIndices[2] - 1].X)
            {
                extremeIndices[2]--;
            }
            // extremeIndices[1] => min-Y, with min X for ties
            extremeIndices[1] = extremeIndices[2] == 0 ? cvxPoints.Count - 1 : extremeIndices[2];
            while (extremeIndices[1] > 0 && cvxPoints[extremeIndices[1]].Y >= cvxPoints[extremeIndices[1] - 1].Y)
            {
                extremeIndices[1]--;
            }
            // extrememIndices[0] => min-X, with max Y for ties
            // First we check if the last point has an eqaully small x value, if it does we will need to walk backwards.
            if (cvxPoints.Last().X > cvxPoints[0].X)
            {
                extremeIndices[0] = 0;
            }
            else
            {
                extremeIndices[0] = cvxPoints.Count - 1;
                while (cvxPoints[extremeIndices[0]].X >= cvxPoints[extremeIndices[0] - 1].X)
                {
                    extremeIndices[0]--;
                }
            }
            #endregion

            var bestRectangle = new BoundingRectangle {
                Area = double.MaxValue
            };
            var PointsOnSides = new List <PointLight> [4];

            #region Cycle through 90-degrees
            var deltaAngles = new double[4];
            do
            {
                #region update the deltaAngles from the current orientation

                //For each of the 4 supporting points (those forming the rectangle),
                var minAngle = double.PositiveInfinity;
                var refIndex = 0;
                for (var i = 0; i < 4; i++)
                {
                    var index     = extremeIndices[i];
                    var prev      = index == 0 ? cvxPoints[numCvxPoints - 1] : cvxPoints[index - 1];
                    var current   = cvxPoints[index];
                    var tempDelta = Math.Atan2(prev.Y - current.Y, prev.X - current.X);
                    var angle     = CaliperOffsetAngles[i] - tempDelta;

                    //If the angle has rotated beyond the 90 degree bounds, it will be negative
                    //And should never be chosen from then on.
                    if (angle < 0)
                    {
                        deltaAngles[i] = double.PositiveInfinity;
                    }
                    else
                    {
                        deltaAngles[i] = angle;
                        if (angle < minAngle)
                        {
                            minAngle = angle;
                            refIndex = i;
                        }
                    }
                }
                if (minAngle.IsGreaterThanNonNegligible(Math.PI / 2))
                {
                    break;
                }

                #endregion

                #region find area

                //Get unit normal for current edge
                var otherIndex = extremeIndices[refIndex] == 0 ? numCvxPoints - 1 : extremeIndices[refIndex] - 1;
                var direction  = cvxPoints[extremeIndices[refIndex]].Subtract(cvxPoints[otherIndex]).normalize(2);
                //If point type = 1 or 3, then use inversed Direction
                if (refIndex == 1 || refIndex == 3)
                {
                    direction = new[] { -direction[1], direction[0] };
                }
                var vectorLength = new[]
                {
                    cvxPoints[extremeIndices[2]].X - cvxPoints[extremeIndices[0]].X,
                    cvxPoints[extremeIndices[2]].Y - cvxPoints[extremeIndices[0]].Y
                };

                var angleVector1 = new[] { -direction[1], direction[0] };
                var length       = Math.Abs(vectorLength.dotProduct(angleVector1, 2));
                var vectorWidth  = new[]
                {
                    cvxPoints[extremeIndices[3]].X - cvxPoints[extremeIndices[1]].X,
                    cvxPoints[extremeIndices[3]].Y - cvxPoints[extremeIndices[1]].Y
                };
                var angleVector2 = new[] { direction[0], direction[1] };
                var width        = Math.Abs(vectorWidth.dotProduct(angleVector2, 2));
                var area         = length * width;

                #endregion

                var d1Max         = double.MinValue;
                var d1Min         = double.MaxValue;
                var d2Max         = double.MinValue;
                var d2Min         = double.MaxValue;
                var pointsOnSides = new List <PointLight> [4];
                for (var i = 0; i < 4; i++)
                {
                    pointsOnSides[i] = new List <PointLight>();
                    var dir      = i % 2 == 0 ? angleVector1 : angleVector2;
                    var distance = cvxPoints[extremeIndices[i]].dotProduct(dir);
                    if (i % 2 == 0) //D1
                    {
                        if (distance > d1Max)
                        {
                            d1Max = distance;
                        }
                        if (distance < d1Min)
                        {
                            d1Min = distance;
                        }
                    }
                    else //D2
                    {
                        if (distance > d2Max)
                        {
                            d2Max = distance;
                        }
                        if (distance < d2Min)
                        {
                            d2Min = distance;
                        }
                    }
                    var prevIndex = extremeIndices[i];
                    do
                    {
                        extremeIndices[i] = prevIndex;
                        if (setPointsOnSide)
                        {
                            pointsOnSides[i].Add(cvxPoints[extremeIndices[i]]);
                        }
                        prevIndex = extremeIndices[i] == 0 ? numCvxPoints - 1 : extremeIndices[i] - 1;
                    } while (distance.IsPracticallySame(cvxPoints[prevIndex].dotProduct(dir),
                                                        Constants.BaseTolerance));
                }

                //If this is an improvement, set the parameters for the best bounding rectangle.
                if (area < bestRectangle.Area)
                {
                    bestRectangle.Area               = area;
                    bestRectangle.Length             = length; //Lenght corresponds with direction 1.
                    bestRectangle.Width              = width;
                    bestRectangle.LengthDirection    = angleVector1;
                    bestRectangle.WidthDirection     = angleVector2;
                    bestRectangle.LengthDirectionMax = d1Max;
                    bestRectangle.LengthDirectionMin = d1Min;
                    bestRectangle.WidthDirectionMax  = d2Max;
                    bestRectangle.WidthDirectionMin  = d2Min;
                    PointsOnSides = pointsOnSides;
                }
            } while (true); //process will end on its own by the break statement in line 314

            #endregion

            if (bestRectangle.Area.IsNegligible())
            {
                var polygon   = new Polygon(cvxPoints.Select(p => new Point(p)));
                var allPoints = new List <PointLight>(points);
                if (!polygon.IsConvex())
                {
                    var c          = 0;
                    var random     = new Random(1);   //Use a specific random generator to make this repeatable
                    var pointCount = allPoints.Count;
                    while (pointCount > 10 && c < 10) //Ten points would be ideal
                    {
                        //Remove a random point
                        var max   = pointCount - 1;
                        var index = random.Next(0, max);
                        var point = allPoints[index];
                        allPoints.RemoveAt(index);

                        //Check if it is still invalid
                        var newConvexHull = ConvexHull2D(allPoints).ToList();
                        polygon = new Polygon(newConvexHull.Select(p => new Point(p)));
                        if (polygon.IsConvex())
                        {
                            //Don't remove the point
                            c++;
                            allPoints.Insert(index, point);
                        }
                        else
                        {
                            pointCount--;
                        }
                    }
                }

                var polyLight = new PolygonLight(allPoints);
                var date      = DateTime.Now.ToString("MM.dd.yy_HH.mm");
                polyLight.Serialize("ConvexHullError_" + date + ".PolyLight");
                var cvxHullLight = new PolygonLight(polygon);
                cvxHullLight.Serialize("ConvexHull_" + date + ".PolyLight");
                throw new Exception("Error in Minimum Bounding Box, likely due to faulty convex hull.");
            }


            if (setCornerPoints)
            {
                bestRectangle.SetCornerPoints();
            }

            if (setPointsOnSide)
            {
                var pointsOnSidesAsPoints = new List <Point> [4];
                for (var i = 0; i < 4; i++)
                {
                    pointsOnSidesAsPoints[i] = PointsOnSides[i].Select(p => new Point(p)).ToList();
                }
                bestRectangle.PointsOnSides = pointsOnSidesAsPoints;
            }

            return(bestRectangle);
        }