Exemple #1
0
        public static Circle2 FromThreePoints(Vector2 a, Vector2 b, Vector2 c)
        {
            // form the three chords
            var ab = LineSegment2.FromOriginAndDestination(a, b);
            var bc = LineSegment2.FromOriginAndDestination(b, c);
            var ca = LineSegment2.FromOriginAndDestination(c, a);

            // for numerical reasons, use the longest two
            var chords = new LineSegment2[] { ab, bc, ca };

            chords = chords.OrderByDescending(chord => chord.SquaredLength).Take(2).ToArray();

            // take the perpendicular bisectors of these two chords
            var p1 = chords[0].PerpendicularBisector;
            var p2 = chords[1].PerpendicularBisector;

            // where they intersect is the center
            var maybeCenter = Line2.Intersection(p1, p2);

            if (maybeCenter == null)
            {
                throw new IllConditionedProblemException();
            }

            var center = maybeCenter.Value;

            // find the radius as the distance from one of the points to the center
            // for symmetry we could do all three and average
            // because we care about conservative inclusion, instead we will take the maximum
            var r = Math.Max(Vector2.DistanceBetweenPoints(center, a), Math.Max(Vector2.DistanceBetweenPoints(center, b), Vector2.DistanceBetweenPoints(center, c)));

            return(new Circle2(center, r));
        }
Exemple #2
0
        public Circle2 ComputeMinimumCircumscribingCircle()
        {
            var n = _points.Count;

            if (n == 0)
            {
                return(new Circle2(Vector2.Zero, 0));
            }
            else if (n == 1)
            {
                return(new Circle2(_points[0], 0));
            }
            else if (n == 2)
            {
                var segment = LineSegment2.FromOriginAndDestination(_points[0], _points[1]);

                return(new Circle2(segment.Midpoint, segment.Length / 2));
            }
            else if (n == 3)
            {
                return(Circle2.FromThreePoints(_points[0], _points[1], _points[2]));
            }
            else
            {
                // brute force!
                Circle2 incumbent            = new Circle2(Vector2.Zero, double.PositiveInfinity);
                double  incumbentRadius      = double.PositiveInfinity;
                Circle2 incumbentMiss        = new Circle2(Vector2.Zero, double.PositiveInfinity);
                double  incumbentMissPenalty = double.PositiveInfinity;

                for (int i = 0; i < n - 2; i++)
                {
                    for (int j = i + 1; j < n - 1; j++)
                    {
                        for (int k = j + 1; k < n; k++)
                        {
                            var candidate = Circle2.FromThreePoints(_points[i], _points[j], _points[k]);

                            // the order of these tests is very important because one is much cheaper than the other
                            if (candidate.Radius < incumbentRadius)
                            {
                                if (double.IsInfinity(incumbentRadius))
                                {
                                    // we have not yet found one that includes all points, so we might still need to update the incumbent miss
                                    var candidateMissPenalty = _points.Max(p => candidate.SignedDistanceFromCircle(p));
                                    if (candidateMissPenalty <= 0)
                                    {
                                        // all points pass strict inclusion, this is the new incumbent
                                        incumbent       = candidate;
                                        incumbentRadius = candidate.Radius;
                                    }

                                    if (double.IsInfinity(incumbentRadius) && candidateMissPenalty < incumbentMissPenalty)
                                    {
                                        // this one doesn't fit, but it's the best we've seen so far so we might need it if things go poorly numerically
                                        incumbentMiss        = candidate;
                                        incumbentMissPenalty = candidateMissPenalty;
                                    }
                                }
                                else
                                {
                                    // since we don't care about updating the miss, we can instead use short-circuiting while evaluating inclusion
                                    if (_points.All(p => candidate.Contains(p)))
                                    {
                                        incumbent       = candidate;
                                        incumbentRadius = candidate.Radius;
                                    }
                                }
                            }
                        }
                    }
                }

                if (double.IsInfinity(incumbentRadius))
                {
                    // we didn't find one that numerically included all the points
                    // instead return the closest
                    return(incumbentMiss);
                }
                else
                {
                    return(incumbent);
                }
            }
        }