@@ -70,7 +70,8 @@ public static Circle MinimumCircle(this IEnumerable<Vector2> pointsInput)
7070 for ( int i = startIndex ; i < numPoints ; i ++ )
7171 {
7272 var dist = ( points [ i ] - circle . Center ) . LengthSquared ( ) ;
73- if ( dist > maxDistSqared )
73+ if ( dist . IsGreaterThanNonNegligible ( maxDistSqared ,
74+ Math . Max ( dist , maxDistSqared ) * Constants . DefaultEqualityTolerance ) )
7475 {
7576 //Set max distance ONLY AFTER handling the stall counter logic.
7677 maxDistSqared = dist ;
@@ -101,15 +102,28 @@ public static Circle MinimumCircle(this IEnumerable<Vector2> pointsInput)
101102 } while ( newPointFoundOutsideCircle && totalIterationCounter < maxIterations ) ;
102103 if ( totalIterationCounter == maxIterations )
103104 {
104- var filePathOut = Path . Join ( Environment . GetFolderPath ( Environment . SpecialFolder . Desktop ) , "cvxpoints.csv" ) ;
105- OutputServices . Logger . LogError ( "MinimumCircle function reach max iteration counter: " + maxIterations ) ;
106- OutputServices . Logger . LogInformation ( "points saved to: " + filePathOut ) ;
107- File . WriteAllLines ( filePathOut , pointsInput . Select ( p => p . X + "," + p . Y ) ) ;
108- //Presenter.ShowAndHang(points, plot2DType: Plot2DType.Scatter);
105+ OutputServices . Logger . LogError ( "MinimumCircle reached its iteration limit {IterationLimit}." , maxIterations ) ;
109106 }
110107
111- return circle ;
108+ // Boundary comparisons allow floating-point roundoff; the returned enclosure must still contain every input.
109+ var enclosingRadiusSquared = points . Max ( point => ( point - circle . Center ) . LengthSquared ( ) ) ;
110+ return new Circle ( circle . Center , Math . Max ( circle . RadiusSquared , enclosingRadiusSquared ) ) ;
112111 }
112+
113+ /// <summary>
114+ /// Determines whether a point is within a circle, allowing a scale-relative tolerance for
115+ /// floating-point roundoff at the boundary.
116+ /// </summary>
117+ /// <param name="circle">The circle to test.</param>
118+ /// <param name="point">The point to test.</param>
119+ /// <returns><see langword="true"/> when the point is inside or negligibly outside the circle.</returns>
120+ private static bool ContainsCirclePoint ( Circle circle , Vector2 point )
121+ {
122+ var distanceSquared = ( point - circle . Center ) . LengthSquared ( ) ;
123+ return distanceSquared <= circle . RadiusSquared || distanceSquared . IsPracticallySame ( circle . RadiusSquared ,
124+ Math . Max ( distanceSquared , circle . RadiusSquared ) * Constants . DefaultEqualityTolerance ) ;
125+ }
126+
113127 private static Circle FirstCircle ( Vector2 [ ] points )
114128 {
115129 // during the main loop, the most outside point will be moved to the front
@@ -120,18 +134,18 @@ private static Circle FirstCircle(Vector2[] points)
120134 // permutations in this function. This will ensure that FindCircle runs faster (without
121135 // extra conditions and ensures that it won't miss a case
122136 var circle = Circle . CreateFrom2Points ( points [ 0 ] , points [ 1 ] ) ;
123- if ( ( points [ 2 ] - circle . Center ) . LengthSquared ( ) <= circle . RadiusSquared )
137+ if ( ContainsCirclePoint ( circle , points [ 2 ] ) )
124138 return circle ;
125139 circle = Circle . CreateFrom2Points ( points [ 0 ] , points [ 2 ] ) ;
126- if ( ( points [ 1 ] - circle . Center ) . LengthSquared ( ) <= circle . RadiusSquared )
140+ if ( ContainsCirclePoint ( circle , points [ 1 ] ) )
127141 {
128142 // since 0 and 2 are furthest apart, we need to swap 1 and 2
129143 // so that the two points in the circle are at the beinning of the list
130144 Global . SwapItemsInList ( 1 , 2 , points ) ;
131145 return circle ;
132146 }
133147 circle = Circle . CreateFrom2Points ( points [ 1 ] , points [ 2 ] ) ;
134- if ( ( points [ 0 ] - circle . Center ) . LengthSquared ( ) <= circle . RadiusSquared )
148+ if ( ContainsCirclePoint ( circle , points [ 0 ] ) )
135149 {
136150 // since 1 and 2 are furthest apart, we need to swap 0 and 2
137151 // so that the two points in the circle are at the beinning of the list
@@ -157,22 +171,22 @@ private static Circle FindCircle(Vector2[] points)
157171
158172 // 1. make the 0-1 circle and check with 2 & 3
159173 var circle = Circle . CreateFrom2Points ( points [ 0 ] , points [ 1 ] ) ;
160- if ( ( points [ 2 ] - circle . Center ) . LengthSquared ( ) <= circle . RadiusSquared
161- && ( points [ 3 ] - circle . Center ) . LengthSquared ( ) <= circle . RadiusSquared )
174+ if ( ContainsCirclePoint ( circle , points [ 2 ] )
175+ && ContainsCirclePoint ( circle , points [ 3 ] ) )
162176 return circle ;
163177
164178 // 2. make the 0-2 circle and check with 1 & 3
165179 circle = Circle . CreateFrom2Points ( points [ 0 ] , points [ 2 ] ) ;
166- if ( ( points [ 1 ] - circle . Center ) . LengthSquared ( ) <= circle . RadiusSquared
167- && ( points [ 3 ] - circle . Center ) . LengthSquared ( ) <= circle . RadiusSquared )
180+ if ( ContainsCirclePoint ( circle , points [ 1 ] )
181+ && ContainsCirclePoint ( circle , points [ 3 ] ) )
168182 {
169183 Global . SwapItemsInList ( 1 , 2 , points ) ;
170184 return circle ;
171185 }
172186 // 3. make the 0-3 circle and check with 1 & 2
173187 circle = Circle . CreateFrom2Points ( points [ 0 ] , points [ 3 ] ) ;
174- if ( ( points [ 1 ] - circle . Center ) . LengthSquared ( ) <= circle . RadiusSquared
175- && ( points [ 2 ] - circle . Center ) . LengthSquared ( ) <= circle . RadiusSquared )
188+ if ( ContainsCirclePoint ( circle , points [ 1 ] )
189+ && ContainsCirclePoint ( circle , points [ 2 ] ) )
176190 {
177191 Global . SwapItemsInList ( 1 , 3 , points ) ;
178192 return circle ;
@@ -183,15 +197,15 @@ private static Circle FindCircle(Vector2[] points)
183197 // circle 0-1-2
184198 var minRadiusSqd = double . PositiveInfinity ;
185199 if ( Circle . CreateFrom3Points ( points [ 0 ] , points [ 1 ] , points [ 2 ] , out tempCircle )
186- && ( points [ 3 ] - tempCircle . Center ) . LengthSquared ( ) <= tempCircle . RadiusSquared )
200+ && ContainsCirclePoint ( tempCircle , points [ 3 ] ) )
187201 {
188202 circle = tempCircle ;
189203 minRadiusSqd = circle . RadiusSquared ;
190204 }
191205 // circle 0-1-3
192206 var swap3And2 = false ;
193207 if ( Circle . CreateFrom3Points ( points [ 0 ] , points [ 1 ] , points [ 3 ] , out tempCircle )
194- && ( points [ 2 ] - tempCircle . Center ) . LengthSquared ( ) <= tempCircle . RadiusSquared
208+ && ContainsCirclePoint ( tempCircle , points [ 2 ] )
195209 && tempCircle . RadiusSquared < minRadiusSqd )
196210 {
197211 swap3And2 = true ;
@@ -201,7 +215,7 @@ private static Circle FindCircle(Vector2[] points)
201215 // circle 0-2-3
202216 var swap3And1 = false ;
203217 if ( Circle . CreateFrom3Points ( points [ 0 ] , points [ 2 ] , points [ 3 ] , out tempCircle )
204- && ( points [ 1 ] - tempCircle . Center ) . LengthSquared ( ) <= tempCircle . RadiusSquared
218+ && ContainsCirclePoint ( tempCircle , points [ 1 ] )
205219 && tempCircle . RadiusSquared < minRadiusSqd )
206220 {
207221 swap3And1 = true ;
@@ -535,4 +549,4 @@ public static Vector3 MinimumBoundingCylinderAxis<T>(this IEnumerable<T> vertice
535549 return likelyAxis ;
536550 }
537551 }
538- }
552+ }
0 commit comments