SmallestEnclosingCircle.cs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. /*
  2. * Smallest enclosing circle - Library (C#)
  3. *
  4. * Copyright (c) 2017 Project Nayuki
  5. * https://www.nayuki.io/page/smallest-enclosing-circle
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU Lesser General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public License
  18. * along with this program (see COPYING.txt and COPYING.LESSER.txt).
  19. * If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. using System;
  22. using System.Collections.Generic;
  23. using System.Linq;
  24. namespace MatterHackers.VectorMath
  25. {
  26. public struct Circle
  27. {
  28. public Vector2 Center { get; set; }
  29. // Center
  30. public double Radius { get; set; }
  31. private const double MULTIPLICATIVE_EPSILON = 1 + 1e-14;
  32. // Radius
  33. public Circle(Vector2 c, double r)
  34. {
  35. this.Center = c;
  36. this.Radius = r;
  37. }
  38. public bool Contains(Vector2 p)
  39. {
  40. return Center.Distance(p) <= Radius * MULTIPLICATIVE_EPSILON;
  41. }
  42. public bool Contains(ICollection<Vector2> ps)
  43. {
  44. foreach (Vector2 p in ps)
  45. {
  46. if (!Contains(p))
  47. return false;
  48. }
  49. return true;
  50. }
  51. }
  52. public sealed class SmallestEnclosingCircle
  53. {
  54. /*
  55. * Returns the smallest circle that encloses all the given points. Runs in expected O(n) time, randomized.
  56. * Note: If 0 points are given, a circle of radius -1 is returned. If 1 point is given, a circle of radius 0 is returned.
  57. */
  58. // Initially: No boundary points known
  59. public static Circle MakeCircle(IEnumerable<Vector2> points)
  60. {
  61. var circle = new Circle(new Vector2(0, 0), -1);
  62. if (points?.Any() == true)
  63. {
  64. // Clone list to preserve the caller's data, do Durstenfeld shuffle
  65. List<Vector2> shuffled = new List<Vector2>(points);
  66. Random rand = new Random();
  67. for (int i = shuffled.Count - 1; i > 0; i--)
  68. {
  69. int j = rand.Next(i + 1);
  70. Vector2 temp = shuffled[i];
  71. shuffled[i] = shuffled[j];
  72. shuffled[j] = temp;
  73. }
  74. // Progressively add points to circle or recompute circle
  75. for (int i = 0; i < shuffled.Count; i++)
  76. {
  77. Vector2 p = shuffled[i];
  78. if (circle.Radius < 0 || !circle.Contains(p))
  79. {
  80. circle = MakeCircleOnePoint(shuffled.GetRange(0, i + 1), p);
  81. }
  82. }
  83. }
  84. return circle;
  85. }
  86. public static Circle MakeCircumcircle(Vector2 a, Vector2 b, Vector2 c)
  87. {
  88. // Mathematical algorithm from Wikipedia: Circumscribed circle
  89. double ox = (Math.Min(Math.Min(a.X, b.X), c.X) + Math.Max(Math.Min(a.X, b.X), c.X)) / 2;
  90. double oy = (Math.Min(Math.Min(a.Y, b.Y), c.Y) + Math.Max(Math.Min(a.Y, b.Y), c.Y)) / 2;
  91. double ax = a.X - ox, ay = a.Y - oy;
  92. double bx = b.X - ox, by = b.Y - oy;
  93. double cx = c.X - ox, cy = c.Y - oy;
  94. double d = (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by)) * 2;
  95. if (d == 0)
  96. {
  97. return new Circle(new Vector2(0, 0), -1);
  98. }
  99. double x = ((ax * ax + ay * ay) * (by - cy) + (bx * bx + by * by) * (cy - ay) + (cx * cx + cy * cy) * (ay - by)) / d;
  100. double y = ((ax * ax + ay * ay) * (cx - bx) + (bx * bx + by * by) * (ax - cx) + (cx * cx + cy * cy) * (bx - ax)) / d;
  101. Vector2 p = new Vector2(ox + x, oy + y);
  102. double r = Math.Max(Math.Max(p.Distance(a), p.Distance(b)), p.Distance(c));
  103. return new Circle(p, r);
  104. }
  105. public static Circle MakeDiameter(Vector2 a, Vector2 b)
  106. {
  107. Vector2 c = new Vector2((a.X + b.X) / 2, (a.Y + b.Y) / 2);
  108. return new Circle(c, Math.Max(c.Distance(a), c.Distance(b)));
  109. }
  110. // One boundary point known
  111. private static Circle MakeCircleOnePoint(List<Vector2> points, Vector2 p)
  112. {
  113. Circle c = new Circle(p, 0);
  114. for (int i = 0; i < points.Count; i++)
  115. {
  116. Vector2 q = points[i];
  117. if (!c.Contains(q))
  118. {
  119. if (c.Radius == 0)
  120. {
  121. c = MakeDiameter(p, q);
  122. }
  123. else
  124. {
  125. c = MakeCircleTwoPoints(points.GetRange(0, i + 1), p, q);
  126. }
  127. }
  128. }
  129. return c;
  130. }
  131. // Two boundary points known
  132. private static Circle MakeCircleTwoPoints(List<Vector2> points, Vector2 p, Vector2 q)
  133. {
  134. Circle circ = MakeDiameter(p, q);
  135. Circle left = new Circle(new Vector2(0, 0), -1);
  136. Circle right = new Circle(new Vector2(0, 0), -1);
  137. // For each point not in the two-point circle
  138. Vector2 pq = q - p;
  139. foreach (Vector2 r in points)
  140. {
  141. if (circ.Contains(r))
  142. {
  143. continue;
  144. }
  145. // Form a circumcircle and classify it on left or right side
  146. double cross = pq.Cross(r - p);
  147. Circle c = MakeCircumcircle(p, q, r);
  148. if (c.Radius < 0)
  149. {
  150. continue;
  151. }
  152. else if (cross > 0 && (left.Radius < 0 || pq.Cross(c.Center - p) > pq.Cross(left.Center - p)))
  153. {
  154. left = c;
  155. }
  156. else if (cross < 0 && (right.Radius < 0 || pq.Cross(c.Center - p) < pq.Cross(right.Center - p)))
  157. {
  158. right = c;
  159. }
  160. }
  161. // Select which circle to return
  162. if (left.Radius < 0 && right.Radius < 0)
  163. {
  164. return circ;
  165. }
  166. else if (left.Radius < 0)
  167. {
  168. return right;
  169. }
  170. else if (right.Radius < 0)
  171. {
  172. return left;
  173. }
  174. else
  175. {
  176. return left.Radius <= right.Radius ? left : right;
  177. }
  178. }
  179. }
  180. }