using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace FortAnalysis { /// /// The vector2. /// public class Vector2 { public double X { get; set; } public double Y { get; set; } public Vector2() { } public Vector2(double x, double y) { this.X = x; this.Y = y; } } public class RotatingCalipers2 { protected enum Corner { UPPER_RIGHT, UPPER_LEFT, LOWER_LEFT, LOWER_RIGHT } public static double getArea(Vector2[] rectangle) { double deltaXAB = rectangle[0].X - rectangle[1].X; double deltaYAB = rectangle[0].Y - rectangle[1].Y; double deltaXBC = rectangle[1].X - rectangle[2].X; double deltaYBC = rectangle[1].Y - rectangle[2].Y; double lengthAB = Math.Sqrt((deltaXAB * deltaXAB) + (deltaYAB * deltaYAB)); double lengthBC = Math.Sqrt((deltaXBC * deltaXBC) + (deltaYBC * deltaYBC)); return lengthAB * lengthBC; } public static List getAllBoundingRectangles(ICollection points) { List rectangles = new List(); List convexHull = GrahamScan.FindConvexHull(points.ToList()); convexHull.Add(convexHull[0]); Caliper I = new Caliper(convexHull, getIndex(convexHull, Corner.UPPER_RIGHT), 90); Caliper J = new Caliper(convexHull, getIndex(convexHull, Corner.UPPER_LEFT), 180); Caliper K = new Caliper(convexHull, getIndex(convexHull, Corner.LOWER_LEFT), 270); Caliper L = new Caliper(convexHull, getIndex(convexHull, Corner.LOWER_RIGHT), 0); while (L.currentAngle < 90.0) { rectangles.Add(new Vector2[]{ L.getIntersection(I), I.getIntersection(J), J.getIntersection(K), K.getIntersection(L) }); double smallestTheta = getSmallestTheta(I, J, K, L); I.rotateBy(smallestTheta); J.rotateBy(smallestTheta); K.rotateBy(smallestTheta); L.rotateBy(smallestTheta); } return rectangles; } public static Vector2[] getMinimumBoundingRectangle(ICollection points) { List rectangles = getAllBoundingRectangles(points); Vector2[] minimum = null; double area = double.MaxValue; foreach (Vector2[] rectangle in rectangles) { double tempArea = getArea(rectangle); if (minimum == null || tempArea < area) { minimum = rectangle; area = tempArea; } } return minimum; } private static double getSmallestTheta(Caliper I, Caliper J, Caliper K, Caliper L) { double thetaI = I.getDeltaAngleNextPoint(); double thetaJ = J.getDeltaAngleNextPoint(); double thetaK = K.getDeltaAngleNextPoint(); double thetaL = L.getDeltaAngleNextPoint(); if (thetaI <= thetaJ && thetaI <= thetaK && thetaI <= thetaL) { return thetaI; } else if (thetaJ <= thetaK && thetaJ <= thetaL) { return thetaJ; } else if (thetaK <= thetaL) { return thetaK; } else { return thetaL; } } protected static int getIndex(List convexHull, Corner corner) { int index = 0; Vector2 point = convexHull[index]; for (int i = 1; i < convexHull.Count - 1; i++) { Vector2 temp = convexHull[i]; bool change = false; switch (corner) { case Corner.UPPER_RIGHT: change = (temp.X > point.X || (temp.X == point.X && temp.Y > point.Y)); break; case Corner.UPPER_LEFT: change = (temp.Y > point.Y || (temp.Y == point.Y && temp.X < point.X)); break; case Corner.LOWER_LEFT: change = (temp.X < point.X || (temp.X == point.X && temp.Y < point.Y)); break; case Corner.LOWER_RIGHT: change = (temp.Y < point.Y || (temp.Y == point.Y && temp.X > point.X)); break; } if (change) { index = i; point = temp; } } return index; } protected class Caliper { static double SIGMA = 0.00000001; List convexHull; int pointIndex; public double currentAngle; public Caliper(List convexHull, int pointIndex, double currentAngle) { this.convexHull = convexHull; this.pointIndex = pointIndex; this.currentAngle = currentAngle; } double getAngleNextPoint() { Vector2 p1 = convexHull[pointIndex]; Vector2 p2 = convexHull[(pointIndex + 1) % convexHull.Count]; double deltaX = p2.X - p1.X; double deltaY = p2.Y - p1.Y; double angle = Math.Atan2(deltaY, deltaX) * 180 / Math.PI; return angle < 0 ? 360 + angle : angle; } double getConstant() { Vector2 p = convexHull[pointIndex]; return p.Y - (getSlope() * p.X); } public double getDeltaAngleNextPoint() { double angle = getAngleNextPoint(); angle = angle < 0 ? 360 + angle - currentAngle : angle - currentAngle; return angle < 0 ? 360 : angle; } public Vector2 getIntersection(Caliper that) { // the x-intercept of 'this' and 'that': x = ((c2 - c1) / (m1 - m2)) double x; // the y-intercept of 'this' and 'that', given 'x': (m*x) + c double y; if (this.isVertical()) { x = convexHull[pointIndex].X; } else if (this.isHorizontal()) { x = that.convexHull[that.pointIndex].X; } else { x = (that.getConstant() - this.getConstant()) / (this.getSlope() - that.getSlope()); } if (this.isVertical()) { y = that.getConstant(); } else if (this.isHorizontal()) { y = this.getConstant(); } else { y = (this.getSlope() * x) + this.getConstant(); } return new Vector2(x, y); } double getSlope() { return Math.Tan(currentAngle * Math.PI / 180); } bool isHorizontal() { return (Math.Abs(currentAngle) < SIGMA) || (Math.Abs(currentAngle - 180.0) < SIGMA); } bool isVertical() { return (Math.Abs(currentAngle - 90.0) < SIGMA) || (Math.Abs(currentAngle - 270.0) < SIGMA); } public void rotateBy(double angle) { if (this.getDeltaAngleNextPoint() == angle) { pointIndex++; } this.currentAngle = (this.currentAngle + angle) % 360; } } } public abstract class GrahamScan { /// /// Find convex hull for the given set of input points. /// /// See this for how it works: http://softsurfer.com/Archive/algorithm_0109/algorithm_0109.htm /// The set of input points. /// The points which form the outside of the convex hull. public static List FindConvexHull(IEnumerable lPoints) { // If we have no points, return nothing. if (lPoints == null) return null; // Convert the input point list to our comparable class. List lPts = new List(); foreach (Vector2 Vector2 in lPoints) { lPts.Add(new GrahamPointData(Vector2)); } // If we don't have enough data, bail. if (lPts.Count <= 1) return null; // Find the point with lowest X and lowest Y. int iStartIndex = 0; var pStart = lPts[0]; for (int i = 1, n = lPts.Count; i < n; i++) { // If it is is further left pr it is the same left but lower down. if ((lPts[i].X < pStart.X) || ((lPts[i].X == pStart.X) && (lPts[i].Y < pStart.Y))) { pStart = lPts[i]; iStartIndex = i; } } // Remove that point from the array. lPts.RemoveAt(iStartIndex); // Compute the delta, distance and tangent of each point to our start point. for (int i = 0, n = lPts.Count; i < n; i++) { var dx = lPts[i].X - pStart.X; var dy = lPts[i].Y - pStart.Y; lPts[i].Distance = dx * dx + dy * dy; lPts[i].K = (dx == 0) ? double.PositiveInfinity : dy / dx; } // Sort points by angle and distance. lPts.Sort(); // Create a list to contain the convex hull and add the first corner. var lHull = new List(); lHull.Add(pStart); // Add our first point. lHull.Add(lPts[0]); lPts.RemoveAt(0); // Setup pointers. var pPrev = lHull[0]; var pLast = lHull[1]; // While we still have points to process. while (lPts.Count != 0) { // Bring the next point into scope. var pCurrent = lPts[0]; // Remove from the process list if the point has the same tangent or it is in the same place. if ((pCurrent.K == pLast.K) || (pCurrent.Distance == 0)) { lPts.RemoveAt(0); continue; } // If we can accept our current point (i.e. it is to the left of both the last and the previous). if ((pCurrent.X - pPrev.X) * (pLast.Y - pCurrent.Y) - (pLast.X - pCurrent.X) * (pCurrent.Y - pPrev.Y) < 0) { // Add the point to the hull and remove it from the processing list. lPts.RemoveAt(0); lHull.Add(pCurrent); // Update the references. pPrev = pLast; pLast = pCurrent; } // Otherwise else { // Remove the last points from the hull. lHull.RemoveAt(lHull.Count - 1); // Update the references. pLast = pPrev; pPrev = lHull[lHull.Count - 2]; } } // Convert points back into a format we know and love. List lOutput = new List(); foreach (GrahamPointData pt in lHull) lOutput.Add(pt.ToVector2()); return lOutput; } /// /// A graham scan datapoint which sorts on tangent and Euclidian distance. /// private class GrahamPointData : IComparable { public double X; public double Y; public double K; public double Distance; /// /// Create a new comparison. /// /// public GrahamPointData(Vector2 Vector2) { X = Vector2.X; Y = Vector2.Y; K = 0; Distance = 0; } /// /// Set the values for this point from a vector2. /// /// public void fromVector2(Vector2 tVector2) { this.X = tVector2.X; this.Y = tVector2.Y; } /// /// Compare this to another object. /// /// /// public int CompareTo(object obj) { GrahamPointData another = (GrahamPointData)obj; return (K < another.K) ? -1 : (K > another.K) ? 1 : ((Distance > another.Distance) ? -1 : (Distance < another.Distance) ? 1 : 0); } /// /// Extract a Vector2 from this point. /// /// public Vector2 ToVector2() { return new Vector2((double)X, (double)Y); } } } }