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. }
/// <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)); }