using System; using System.Collections.Generic; using System.Linq; namespace OpenNest.Geometry { /// /// Smallest circle containing every point (Welzl's algorithm, expected linear time). Points are /// visited in a fixed shuffled order, so the result is repeatable. /// public static class MinimumEnclosingCircle { public static Circle Compute(IEnumerable points) { var shuffled = points.ToList(); if (shuffled.Count == 0) throw new ArgumentException("At least one point is required.", nameof(points)); var random = new Random(0); for (var i = shuffled.Count - 1; i > 0; i--) { var j = random.Next(i + 1); (shuffled[i], shuffled[j]) = (shuffled[j], shuffled[i]); } var center = shuffled[0]; var radius = 0.0; for (var i = 1; i < shuffled.Count; i++) { if (Contains(center, radius, shuffled[i])) continue; center = shuffled[i]; radius = 0; for (var j = 0; j < i; j++) { if (Contains(center, radius, shuffled[j])) continue; center = (shuffled[i] + shuffled[j]) * 0.5; radius = center.DistanceTo(shuffled[i]); for (var k = 0; k < j; k++) { if (Contains(center, radius, shuffled[k])) continue; (center, radius) = Circumcircle(shuffled[i], shuffled[j], shuffled[k]); } } } // Rounding can leave a defining point a hair outside; grow to cover every point exactly. radius = shuffled.Max(p => p.DistanceTo(center)); return new Circle(center, radius); } private static bool Contains(Vector center, double radius, Vector point) => point.DistanceTo(center) <= radius * (1 + 1e-12) + 1e-12; private static (Vector Center, double Radius) Circumcircle(Vector a, Vector b, Vector c) { var bx = b.X - a.X; var by = b.Y - a.Y; var cx = c.X - a.X; var cy = c.Y - a.Y; var d = 2 * (bx * cy - by * cx); if (System.Math.Abs(d) <= double.Epsilon) { // Collinear: the circle on the two farthest-apart points covers the third. var pairs = new[] { (a, b), (a, c), (b, c) }; var (p, q) = pairs.OrderByDescending(pair => pair.Item1.DistanceTo(pair.Item2)).First(); var mid = (p + q) * 0.5; return (mid, mid.DistanceTo(p)); } var b2 = bx * bx + by * by; var c2 = cx * cx + cy * cy; var center = new Vector(a.X + (cy * b2 - by * c2) / d, a.Y + (bx * c2 - cx * b2) / d); return (center, center.DistanceTo(a)); } } }