using System;
using System.Collections.Generic;
using CandyboxPlugin.Geometry;
namespace Inspectron.Hawkeye.Vision.Geometry
{
public class GrahamConvexHull
{
///
/// Find convex hull for the given set of points.
///
///
/// Set of points to search convex hull for.
///
/// Returns set of points, which form a convex hull for the given .
/// The first point in the list is the point with lowest X coordinate (and with lowest Y if there are
/// several points with the same X value). Points are provided in counter clockwise order
/// (Cartesian
/// coordinate system).
///
public List FindHull(List points)
{
// do nothing if there 3 points or less
if (points.Count <= 3)
{
return new List(points);
}
// find a point, with lowest X and lowest Y
int firstCornerIndex = 0;
IntPoint pointFirstCorner = points[0];
for (int i = 1, n = points.Count; i < n; i++)
{
if ((points[i].X < pointFirstCorner.X) ||
((points[i].X == pointFirstCorner.X) && (points[i].Y < pointFirstCorner.Y)))
{
pointFirstCorner = points[i];
firstCornerIndex = i;
}
}
// convert input points to points we can process
PointToProcess firstCorner = new PointToProcess(pointFirstCorner);
// Points to process must exclude the first corner that we've already found
PointToProcess[] arrPointsToProcess = new PointToProcess[points.Count - 1];
for (int i = 0; i < points.Count - 1; i++)
{
IntPoint point = points[i >= firstCornerIndex ? i + 1 : i];
arrPointsToProcess[i] = new PointToProcess(point);
}
// find K (tangent of line's angle) and distance to the first corner
for (int i = 0, n = arrPointsToProcess.Length; i < n; i++)
{
int dx = arrPointsToProcess[i].X - firstCorner.X;
int dy = arrPointsToProcess[i].Y - firstCorner.Y;
// don't need square root, since it is not important in our case
arrPointsToProcess[i].Distance = dx * dx + dy * dy;
// tangent of lines angle
arrPointsToProcess[i].K = (dx == 0) ? float.PositiveInfinity : (float)dy / dx;
}
// sort points by angle and distance
Array.Sort(arrPointsToProcess);
// Convert points to process to a queue. Continually removing the first item of an array list
// is highly inefficient
Queue queuePointsToProcess = new Queue(arrPointsToProcess);
LinkedList convexHullTemp = new LinkedList();
// add first corner, which is always on the hull
PointToProcess prevPoint = convexHullTemp.AddLast(firstCorner).Value;
// add another point, which forms a line with lowest slope
PointToProcess lastPoint = convexHullTemp.AddLast(queuePointsToProcess.Dequeue()).Value;
while (queuePointsToProcess.Count != 0)
{
PointToProcess newPoint = queuePointsToProcess.Peek();
// skip any point, which has the same slope as the last one or
// has 0 distance to the first point
if ((newPoint.K == lastPoint.K) || (newPoint.Distance == 0))
{
queuePointsToProcess.Dequeue();
continue;
}
// check if current point is on the left side from two last points
if ((newPoint.X - prevPoint.X) * (lastPoint.Y - newPoint.Y) - (lastPoint.X - newPoint.X) * (newPoint.Y - prevPoint.Y) < 0)
{
// add the point to the hull
convexHullTemp.AddLast(newPoint);
// and remove it from the list of points to process
queuePointsToProcess.Dequeue();
prevPoint = lastPoint;
lastPoint = newPoint;
}
else
{
// remove the last point from the hull
convexHullTemp.RemoveLast();
lastPoint = prevPoint;
prevPoint = convexHullTemp.Last.Previous.Value;
}
}
// convert points back
List convexHull = new List();
foreach (PointToProcess pt in convexHullTemp)
{
convexHull.Add(pt.ToPoint());
}
return convexHull;
}
// Internal comparer for sorting points
private class PointToProcess : IComparable
{
public int X;
public int Y;
public float K;
public float Distance;
public PointToProcess(IntPoint point)
{
X = point.X;
Y = point.Y;
K = 0;
Distance = 0;
}
public int CompareTo(object obj)
{
PointToProcess another = (PointToProcess)obj;
return (K < another.K) ? -1 : (K > another.K) ? 1 :
((Distance > another.Distance) ? -1 : (Distance < another.Distance) ? 1 : 0);
}
public IntPoint ToPoint()
{
return new IntPoint(X, Y);
}
}
}
}