Compute convex hull
MIConvexHull - https://designengrlab.github.io/MIConvexHull/ - is a high-performance convex hull implementation in C#, supporting higher-dimensional convex hulls too. LGPL license.
Below is a transliteration to C# of the same Java source used in Qwertie's answer but without a dependency on non-standard classes beyond a Point class with double fields.
class ConvexHull
{
public static double cross(Point O, Point A, Point B)
{
return (A.X - O.X) * (B.Y - O.Y) - (A.Y - O.Y) * (B.X - O.X);
}
public static List<Point> GetConvexHull(List<Point> points)
{
if (points == null)
return null;
if (points.Count() <= 1)
return points;
int n = points.Count(), k = 0;
List<Point> H = new List<Point>(new Point[2 * n]);
points.Sort((a, b) =>
a.X == b.X ? a.Y.CompareTo(b.Y) : a.X.CompareTo(b.X));
// Build lower hull
for (int i = 0; i < n; ++i)
{
while (k >= 2 && cross(H[k - 2], H[k - 1], points[i]) <= 0)
k--;
H[k++] = points[i];
}
// Build upper hull
for (int i = n - 2, t = k + 1; i >= 0; i--)
{
while (k >= t && cross(H[k - 2], H[k - 1], points[i]) <= 0)
k--;
H[k++] = points[i];
}
return H.Take(k - 1).ToList();
}
}
Here's a 2D convex hull algorithm that I wrote using the Monotone Chain algorithm, a.k.a. Andrew's Algorithm.
public static IListSource<Point> ComputeConvexHull(List<Point> points, bool sortInPlace = false)
{
if (!sortInPlace)
points = new List<Point>(points);
points.Sort((a, b) =>
a.X == b.X ? a.Y.CompareTo(b.Y) : a.X.CompareTo(b.X));
// Importantly, DList provides O(1) insertion at beginning and end
DList<Point> hull = new DList<Point>();
int L = 0, U = 0; // size of lower and upper hulls
// Builds a hull such that the output polygon starts at the leftmost point.
for (int i = points.Count - 1; i >= 0 ; i--)
{
Point p = points[i], p1;
// build lower hull (at end of output list)
while (L >= 2 && (p1 = hull.Last).Sub(hull[hull.Count-2]).Cross(p.Sub(p1)) >= 0) {
hull.RemoveAt(hull.Count-1);
L--;
}
hull.PushLast(p);
L++;
// build upper hull (at beginning of output list)
while (U >= 2 && (p1 = hull.First).Sub(hull[1]).Cross(p.Sub(p1)) <= 0)
{
hull.RemoveAt(0);
U--;
}
if (U != 0) // when U=0, share the point added above
hull.PushFirst(p);
U++;
Debug.Assert(U + L == hull.Count + 1);
}
hull.RemoveAt(hull.Count - 1);
return hull;
}
It relies on some things that are assumed to exist, see my blog post for details.