private CircularLayoutInfo CalculateEnclosingCircle(IHierarchicalData root, FrontChain frontChain)
        {
            // Get extreme points by extending the vector from origin to center by the radius
            var layouts = frontChain.ToList();
            var points  = layouts.Select(x => Geometry.MovePointAlongLine(new Point(), x.Center, x.Radius)).ToList();

            var layout = GetLayout(root);

            Debug.Assert(layout.Center == new Point()); // Since we process bottom up this node was not considered yet.

            double radius;
            Point  center;

            if (frontChain.Head.Next == frontChain.Head) // Single element
            {
                center = frontChain.Head.Value.Center;
                radius = frontChain.Head.Value.Radius;
            }
            else
            {
                // 2 and 3 points seem to be handled correctly.
                Geometry.FindMinimalBoundingCircle(points, out center, out radius);
            }

            layout.Center = center;
            Debug.Assert(Math.Abs(radius) > 0.00001);
            layout.Radius = radius * 1.03; // Just a little larger 3%.
            return(layout);

            // Note that this is not exact. It is a difficult problem.
            // We may have a little overlap. The larger the increment in radius
            // the smaller this problem gets.
        }
示例#2
0
        /// <summary>
        /// Usually we have two solutions for tangent circles. We have to decide which to use.
        /// Theoretically that is the one that is outside the front chain polygon.
        /// However there are many edge cases. So I do not have an exact mathematical solution to this problem.
        /// Therefore I use different heuristics.
        /// </summary>
        private CircularLayoutInfo SelectCircle(FrontChain frontChain, CircularLayoutInfo circle1, CircularLayoutInfo circle2)
        {
            var poly = frontChain.ToList().Select(x => x.Center).ToList();

            Debug.Assert(IsPointValid(circle1.Center) || IsPointValid(circle2.Center));

            // ----------------------------------------------------------------------------
            // If exactly one of the two points is valid that is the solution.
            // ----------------------------------------------------------------------------

            if (IsPointValid(circle1.Center) && !IsPointValid(circle2.Center))
            {
                return(circle1);
            }

            if (!IsPointValid(circle1.Center) && IsPointValid(circle2.Center))
            {
                return(circle2);
            }

            // We have two solutions. Which point to chose?

            // ----------------------------------------------------------------------------
            // If one center is inside and one outside the polygon take the outside.
            // ----------------------------------------------------------------------------

            var center1Inside = MathHelper.PointInPolygon(poly, circle1.Center.X, circle1.Center.Y);
            var center2Inside = MathHelper.PointInPolygon(poly, circle2.Center.X, circle2.Center.Y);

            if (center1Inside && !center2Inside)
            {
                return(circle2);
            }

            if (!center1Inside && center2Inside)
            {
                return(circle1);
            }

            // Both centers outside: Examples\Both_centers_outside_polygon.html.
            // Note that the purple circle (center) may also be inside the polygon if it was a little bit smaller.
            // Debug.Assert(center2Inside && center2Inside);

            // Both centers inside: Examples\Both_centers_inside_polygon.html
            // Happens when centers are on the polygon edges.
            // Debug.Assert(!center2Inside && !center2Inside);

            // ----------------------------------------------------------------------------
            // If one circle is crossed by an polygon edge and the other is not,
            // take the other.
            // ----------------------------------------------------------------------------
            var circle1HitByEdge = false;
            var circle2HitByEdge = false;
            var iter             = frontChain.Head;

            while (iter != null)
            {
                var first  = iter.Value;
                var second = iter.Next.Value;

                Point intersect1;
                Point intersect2;

                // Note we consider a line here, not a line segment
                var solutions = MathHelper.FindLineCircleIntersections(circle1.Center.X,
                                                                       circle1.Center.Y, circle1.Radius, first.Center, second.Center, out intersect1, out intersect2);

                if (solutions > 0)
                {
                    circle1HitByEdge |= MathHelper.PointOnLineSegment(first.Center, second.Center, intersect1);
                }

                if (solutions > 1)
                {
                    circle1HitByEdge |= MathHelper.PointOnLineSegment(first.Center, second.Center, intersect2);
                }

                solutions = MathHelper.FindLineCircleIntersections(circle2.Center.X,
                                                                   circle2.Center.Y, circle2.Radius, first.Center, second.Center, out intersect1, out intersect2);

                if (solutions > 0)
                {
                    circle2HitByEdge |= MathHelper.PointOnLineSegment(first.Center, second.Center, intersect1);
                }

                if (solutions > 1)
                {
                    circle2HitByEdge |= MathHelper.PointOnLineSegment(first.Center, second.Center, intersect2);
                }

                iter = iter.Next;
                if (iter == frontChain.Head)
                {
                    // Ensure that the segment from tail to head is also processed.
                    iter = null;
                }
            }

            if (circle1HitByEdge && !circle2HitByEdge)
            {
                return(circle2);
            }

            if (!circle1HitByEdge && circle2HitByEdge)
            {
                return(circle1);
            }

            if (DebugEnabled)
            {
                DebugHelper.WriteDebugOutput(frontChain, "not_sure_which_circle", circle1, circle2);
            }

            // Still inconclusive which solution to take.
            // I choose the one with the largst distance from the origin.
            // Background is following example: Inconclusive_solution.html
            // I need to get rid of the inner (green) circle If I want to grow outwards.

            // In my understanding this inner node is always m. We chose it because it had the smallest distance to the origin.
            // My hope is that the selected solution leads to removal of m. Cannot prove it, but I think so.
            return(SelectCircleWithLargerDistanceFromOrigin(circle1, circle2));
        }