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);
}
}
}
}