コード例 #1
0
        /// <summary>
        ///     Finds the minimum bounding circle
        /// </summary>
        /// <param name="points">The points.</param>
        /// <returns>System.Double.</returns>
        /// <exception cref="Exception">Bounding circle failed to converge</exception>
        /// <references>
        ///     Based on Emo Welzl's "move-to-front heuristic" and this paper (algorithm 1).
        ///     http://www.inf.ethz.ch/personal/gaertner/texts/own_work/esa99_final.pdf
        ///     This algorithm runs in near linear time. Visiting most points just a few times.
        ///     Though a linear algorithm was found by Meggi do, this algorithm is more robust
        ///     (doesn't care about multiple points on a line and fewer rounding functions)
        ///     and directly applicable to multiple dimensions (in our case, just 2 and 3 D).
        /// </references>
        public static BoundingCircle MinimumCircle(IList <PointLight> points)
        {
            #region Algorithm 1

            ////Randomize the list of points
            //var r = new Random();
            //var randomPoints = new List<PointLight>(points.OrderBy(p => r.Next()));

            //if (randomPoints.Count < 2) return new BoundingCircle(0.0, points[0]);
            ////Get any two points in the list points.
            //var point1 = randomPoints[0];
            //var point2 = randomPoints[1];
            //var previousPoints = new HashSet<PointLight>();
            //var circle = new InternalCircle(point1, point2);
            //var stallCounter = 0;
            //var i = 0;

            //while (i < randomPoints.Count && stallCounter < points.Count * 2)
            //{
            //    var currentPoint = randomPoints[i];
            //    //If the current point is part of the circle or inside the circle, go to the next iteration
            //    if (circle.Point0.Equals(currentPoint) ||
            //        circle.Point1.Equals(currentPoint) ||
            //        circle.Point2.Equals(currentPoint) ||
            //        circle.IsPointInsideCircle(currentPoint))
            //    {
            //        i++;
            //        continue;
            //    }

            //    //Else if the currentPoint is a previousPoint, increase dimension
            //    if (previousPoints.Contains(currentPoint))
            //    {
            //        //Make a new circle from the current two-point circle and the current point
            //        circle = new InternalCircle(circle.Point0, circle.Point1, currentPoint);
            //        previousPoints.Remove(currentPoint);
            //        i++;
            //    }
            //    else
            //    {
            //        //Find the point in the circle furthest from new point.
            //        circle.Furthest(currentPoint, out var furthestPoint, ref previousPoints);
            //        //Make a new circle from the furthest point and current point
            //        circle = new InternalCircle(currentPoint, furthestPoint);
            //        //Add previousPoints to the front of the list
            //        foreach (var previousPoint in previousPoints)
            //        {
            //            randomPoints.Remove(previousPoint);
            //            randomPoints.Insert(0, previousPoint);
            //        }
            //        //Restart the search
            //        stallCounter++;
            //        i = 0;
            //    }
            //}

            #endregion

            #region Algorithm 2: Furthest Point
            //var r = new Random();
            //var randomPoints = new List<PointLight>(points.OrderBy(p => r.Next()));

            //Algorithm 2
            //I tried using the extremes (X, Y, and also tried Sum, Diff) to do a first pass at the circle
            //or to define the starting circle for max dX or dY, but all of these were slower do to the extra
            //for loop at the onset. The current approach is faster and simpler; just start with some arbitrary points.
            var circle       = new InternalCircle(points[0], points[points.Count / 2]);
            var dummyPoint   = new PointLight(double.NaN, double.NaN);
            var stallCounter = 0;
            var successful   = false;
            var stallLimit   = points.Count * 1.5;
            if (stallLimit < 100)
            {
                stallLimit = 100;
            }
            var centerX               = circle.CenterX;
            var centerY               = circle.CenterY;
            var sqTolerance           = Math.Sqrt(Constants.BaseTolerance);
            var sqRadiusPlusTolerance = circle.SqRadius + sqTolerance;
            var nextPoint             = dummyPoint;
            var priorRadius           = circle.SqRadius;
            while (!successful && stallCounter < stallLimit)
            {
                //If stallCounter is getting big, add a bit extra to the circle radius to ensure convergence
                if (stallCounter > stallLimit / 2)
                {
                    sqRadiusPlusTolerance = sqRadiusPlusTolerance + Constants.BaseTolerance * sqRadiusPlusTolerance;
                }
                //Add it a second time if stallCounter is even bigger
                if (stallCounter > stallLimit * 2 / 3)
                {
                    sqRadiusPlusTolerance = sqRadiusPlusTolerance + Constants.BaseTolerance * sqRadiusPlusTolerance;
                }

                //Find the furthest point from the center point
                var maxDistancePlusTolerance = sqRadiusPlusTolerance;
                var nextPointIsSet           = false;
                foreach (var point in points)
                {
                    var dx = centerX - point.X;
                    var dy = centerY - point.Y;
                    var squareDistanceToPoint = dx * dx + dy * dy;
                    //If the square distance is less than or equal to the max square distance, continue.
                    if (squareDistanceToPoint < maxDistancePlusTolerance)
                    {
                        continue;
                    }
                    //Otherwise, set this as the next point to go to.
                    maxDistancePlusTolerance = squareDistanceToPoint + sqTolerance;
                    nextPoint      = point;
                    nextPointIsSet = true;
                }
                if (!nextPointIsSet)
                {
                    successful = true;
                    continue;
                }

                //Create a new circle with 2 points
                //Find the point in the circle furthest from new point.
                circle.Furthest(nextPoint, out var furthestPoint, out var previousPoint1,
                                out var previousPoint2, out var numPreviousPoints);
                //Make a new circle from the furthest point and current point
                circle  = new InternalCircle(nextPoint, furthestPoint);
                centerX = circle.CenterX;
                centerY = circle.CenterY;
                sqRadiusPlusTolerance = circle.SqRadius + sqTolerance;

                //Now check if the previous points are outside this circle.
                //To be outside the circle, it must be further out than the specified tolerance.
                //Otherwise, the loop can get caught in a loop due to rounding error
                //If you wanted to use a tighter tolerance, you would need to take the square roots to get the radius.
                //If they are, increase the dimension and use three points in a circle
                var dxP1             = centerX - previousPoint1.X;
                var dyP1             = centerY - previousPoint1.Y;
                var squareDistanceP1 = dxP1 * dxP1 + dyP1 * dyP1;
                if (squareDistanceP1 > sqRadiusPlusTolerance)
                {
                    //Make a new circle from the current two-point circle and the current point
                    circle  = new InternalCircle(circle.Point0, circle.Point1, previousPoint1);
                    centerX = circle.CenterX;
                    centerY = circle.CenterY;
                    sqRadiusPlusTolerance = circle.SqRadius + sqTolerance;
                }
                else if (numPreviousPoints == 2)
                {
                    var dxP2             = centerX - previousPoint2.X;
                    var dyP2             = centerY - previousPoint2.Y;
                    var squareDistanceP2 = dxP2 * dxP2 + dyP2 * dyP2;
                    if (squareDistanceP2 > sqRadiusPlusTolerance)
                    {
                        //Make a new circle from the current two-point circle and the current point
                        circle  = new InternalCircle(circle.Point0, circle.Point1, previousPoint2);
                        centerX = circle.CenterX;
                        centerY = circle.CenterY;
                        sqRadiusPlusTolerance = circle.SqRadius + sqTolerance;
                    }
                }

                if (circle.SqRadius < priorRadius)
                {
                    Debug.WriteLine("Bounding circle got smaller during this iteration");
                }
                priorRadius = circle.SqRadius;
                stallCounter++;
            }
            if (stallCounter >= stallLimit)
            {
                Debug.WriteLine("Bounding circle failed to converge to within " + (Constants.BaseTolerance * circle.SqRadius * 2));
            }

            #endregion

            #region Algorithm 3: Meggiddo's Linear-Time Algorithm

            //Pair up points into n/2 pairs.
            //If an odd number of points.....

            //Construct a bisecting line for each pair of points. This sets their slope.

            //Order the slopes.
            //Find the median (halfway in the set) slope of the bisector lines


            //Test

            #endregion

            var radius = circle.SqRadius.IsNegligible() ? 0 : Math.Sqrt(circle.SqRadius);
            return(new BoundingCircle(radius, new PointLight(centerX, centerY)));
        }
コード例 #2
0
ファイル: MinimumCircleCylinder.cs プロジェクト: PeterZs/TVGL
        /// <summary>
        ///     Finds the minimum bounding circle
        /// </summary>
        /// <param name="points">The points.</param>
        /// <returns>System.Double.</returns>
        /// <exception cref="Exception">Bounding circle failed to converge</exception>
        /// <references>
        ///     Based on Emo Welzl's "move-to-front heuristic" and this paper (algorithm 1).
        ///     http://www.inf.ethz.ch/personal/gaertner/texts/own_work/esa99_final.pdf
        ///     This algorithm runs in near linear time. Visiting most points just a few times.
        ///     Though a linear algorithm was found by Meggi do, this algorithm is more robust
        ///     (doesn't care about multiple points on a line and fewer rounding functions)
        ///     and directly applicable to multiple dimensions (in our case, just 2 and 3 D).
        /// </references>
        public static BoundingCircle MinimumCircle(IList <Point> points)
        {
            #region Algorithm 1

            //Randomize the list of points
            //var r = new Random();
            //var randomPoints = new List<Point>(points.OrderBy(p=>r.Next()));

            //if (randomPoints.Count < 2) return new BoundingCircle(0.0, points[0]);
            ////Get any two points in the list points.
            //var point1 = randomPoints[0];
            //var point2 = randomPoints[1];
            //var previousPoints = new List<Point>();
            //var circle = new InternalCircle(new List<Point> {point1, point2});
            //var stallCounter = 0;
            //var i = 0;

            //Algorithm 1
            //while (i < randomPoints.Count && stallCounter < points.Count * 2)
            //{
            //    var currentPoint = randomPoints[i];
            //    //If the current point is part of the circle or inside the circle, go to the next iteration
            //    if (circle.Points.Contains(currentPoint) || circle.IsPointInsideCircle(currentPoint))
            //    {
            //        i++;
            //        continue;
            //    }

            //    //Else if the currentPoint is a previousPoint, increase dimension
            //    if (previousPoints.Contains(currentPoint))
            //    {
            //        //Make a new circle from the current two-point circle and the current point
            //        circle = new InternalCircle(new List<Point> {circle.Points[0], circle.Points[1], currentPoint});
            //        previousPoints.Remove(currentPoint);
            //        i++;
            //    }
            //    else
            //    {
            //        //Find the point in the circle furthest from new point.
            //        Point furthestPoint;
            //        circle.Furthest(currentPoint, out furthestPoint, ref previousPoints);
            //        //Make a new circle from the furthest point and current point
            //        circle = new InternalCircle(new List<Point> {currentPoint, furthestPoint});
            //        //Add previousPoints to the front of the list
            //        foreach (var previousPoint in previousPoints)
            //        {
            //            randomPoints.Remove(previousPoint);
            //            randomPoints.Insert(0, previousPoint);
            //        }
            //        //Restart the search
            //        stallCounter++;
            //        i = 0;
            //    }
            //}

            #endregion

            #region Algorithm 2: Furthest Point

            //Algorithm 2
            var listPoints = new List <Point>(points);
            var point1     = listPoints.First();
            var point2     = listPoints[listPoints.Count / 2];
            var circle     = new InternalCircle(new List <Point> {
                point1, point2
            });
            var stallCounter   = 0;
            var previousPoints = new List <Point>();
            var successful     = false;
            var stallLimit     = listPoints.Count * 3;
            if (stallLimit < 100)
            {
                stallLimit = 100;
            }

            while (!successful && stallCounter < stallLimit)
            {
                //Find the furthest point from the center point
                //If stallCounter is getting big, add a bit extra to the circle radius to ensure convergence
                if (stallCounter > stallLimit / 2)
                {
                    circle.SqRadius = circle.SqRadius + Constants.BaseTolerance * circle.SqRadius;
                }
                //Add it a second time if stallCounter is even bigger
                if (stallCounter > stallLimit * 2 / 3)
                {
                    circle.SqRadius = circle.SqRadius + Constants.BaseTolerance * circle.SqRadius;
                }
                var   maxDistance        = circle.SqRadius;
                Point nextPoint          = null;
                var   create3PointCircle = false;
                foreach (var point in listPoints)
                {
                    if (previousPoints.Contains(point) && !circle.IsPointInsideCircle(point))
                    {
                        create3PointCircle = true;
                        nextPoint          = point;
                        break;
                    }
                    if (circle.Points.Contains(point))
                    {
                        continue;                                //Check if point is already part of the circle
                    }
                    var squareDistanceToPoint = (circle.Center.X - point.X) * (circle.Center.X - point.X) +
                                                (circle.Center.Y - point.Y) * (circle.Center.Y - point.Y);
                    if (squareDistanceToPoint <= maxDistance)
                    {
                        continue;                                       //Beginning with the circle's square radius
                    }
                    maxDistance = squareDistanceToPoint;
                    nextPoint   = point;
                }
                if (nextPoint == null)
                {
                    successful = true;
                    continue;
                }

                //Create a new circle of either 2 or 3 points
                //if the currentPoint is a previousPoint, increase dimension
                if (create3PointCircle)
                {
                    //Make a new circle from the current two-point circle and the current point
                    circle = new InternalCircle(new List <Point> {
                        circle.Points[0], circle.Points[1], nextPoint
                    });
                    previousPoints.Remove(nextPoint);
                }
                else
                {
                    //Find the point in the circle furthest from new point.
                    Point furthestPoint;
                    circle.Furthest(nextPoint, out furthestPoint, ref previousPoints);
                    //Make a new circle from the furthest point and current point
                    circle = new InternalCircle(new List <Point> {
                        nextPoint, furthestPoint
                    });
                    //Add previousPoints to the front of the list
                    foreach (var previousPoint in previousPoints)
                    {
                        listPoints.Remove(previousPoint);
                        listPoints.Insert(0, previousPoint);
                    }
                }
                stallCounter++;
            }

            #endregion

            #region Algorithm 3: Meggiddo's Linear-Time Algorithm

            //Pair up points into n/2 pairs.
            //If an odd number of points.....

            //Construct a bisecting line for each pair of points. This sets their slope.

            //Order the slopes.
            //Find the median (halfway in the set) slope of the bisector lines


            //Test

            #endregion

            //Return information about minimum circle
            if (stallCounter >= stallLimit)
            {
                Debug.WriteLine("Bounding circle failed to converge to within " + (Constants.BaseTolerance * circle.SqRadius * 2));
            }
            var radius = circle.SqRadius.IsNegligible() ? 0 : Math.Sqrt(circle.SqRadius);
            return(new BoundingCircle(radius, circle.Center));
        }