|
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
|
|
|
namespace FortAnalysis
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// The vector2.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
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<Vector2[]> getAllBoundingRectangles(ICollection<Vector2> points)
|
|
|
|
|
|
{
|
|
|
|
|
|
List<Vector2[]> rectangles = new List<Vector2[]>();
|
|
|
|
|
|
|
|
|
|
|
|
List<Vector2> 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<Vector2> points)
|
|
|
|
|
|
{
|
|
|
|
|
|
List<Vector2[]> 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<Vector2> 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<Vector2> convexHull;
|
|
|
|
|
|
int pointIndex;
|
|
|
|
|
|
public double currentAngle;
|
|
|
|
|
|
|
|
|
|
|
|
public Caliper(List<Vector2> 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
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Find convex hull for the given set of input points.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <remarks>See this for how it works: http://softsurfer.com/Archive/algorithm_0109/algorithm_0109.htm </remarks>
|
|
|
|
|
|
/// <param name="lPoints">The set of input points.</param>
|
|
|
|
|
|
/// <returns>The points which form the outside of the convex hull.</returns>
|
|
|
|
|
|
public static List<Vector2> FindConvexHull(IEnumerable<Vector2> lPoints)
|
|
|
|
|
|
{
|
|
|
|
|
|
// If we have no points, return nothing.
|
|
|
|
|
|
if (lPoints == null)
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
|
|
// Convert the input point list to our comparable class.
|
|
|
|
|
|
List<GrahamPointData> lPts = new List<GrahamPointData>();
|
|
|
|
|
|
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<GrahamPointData>();
|
|
|
|
|
|
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<Vector2> lOutput = new List<Vector2>();
|
|
|
|
|
|
foreach (GrahamPointData pt in lHull)
|
|
|
|
|
|
lOutput.Add(pt.ToVector2());
|
|
|
|
|
|
return lOutput;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// A graham scan datapoint which sorts on tangent and Euclidian distance.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private class GrahamPointData : IComparable
|
|
|
|
|
|
{
|
|
|
|
|
|
public double X;
|
|
|
|
|
|
public double Y;
|
|
|
|
|
|
public double K;
|
|
|
|
|
|
public double Distance;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Create a new comparison.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="Vector2"></param>
|
|
|
|
|
|
public GrahamPointData(Vector2 Vector2)
|
|
|
|
|
|
{
|
|
|
|
|
|
X = Vector2.X;
|
|
|
|
|
|
Y = Vector2.Y;
|
|
|
|
|
|
|
|
|
|
|
|
K = 0;
|
|
|
|
|
|
Distance = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Set the values for this point from a vector2.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="tVector2"></param>
|
|
|
|
|
|
public void fromVector2(Vector2 tVector2)
|
|
|
|
|
|
{
|
|
|
|
|
|
this.X = tVector2.X;
|
|
|
|
|
|
this.Y = tVector2.Y;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Compare this to another object.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="obj"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Extract a Vector2 from this point.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public Vector2 ToVector2()
|
|
|
|
|
|
{
|
|
|
|
|
|
return new Vector2((double)X, (double)Y);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|