Ejemplo n.º 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();
        }
Ejemplo n.º 2
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);
        }
Ejemplo n.º 3
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);
        }
Ejemplo n.º 4
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);
        }