/// <summary> /// Usually we have two solutions for tangent circles. We have to decide which to use. /// I assume the polygon goes always in direction m to n. /// We chose the solution that is outside the polygon. I want to grow outside. /// 1. Calculate segment m-n /// 2. Calculate normal vector form /// 3. Chose m as vector to the segment /// 4. Use vector normal form to check if solution1 or solution2 is outside or inside. /// Both scalars may be positive or negative. So I chose the larger on. Not quite sure if this is ok. /// </summary> private CircularLayoutInfo SelectCircle(CircularLayoutInfo m, CircularLayoutInfo n, CircularLayoutInfo circle1, CircularLayoutInfo circle2) { Debug.Assert(IsPointValid(circle1.Center) || IsPointValid(circle2.Center)); // If only one point is valid, take it 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? var segment_m_n = m.Center - n.Center; var anyNormal = new Vector(-segment_m_n.Y, segment_m_n.X) - new Vector(segment_m_n.Y, -segment_m_n.X); var value1 = Vector.Multiply(circle1.Center - m.Center, anyNormal); var value2 = Vector.Multiply(circle2.Center - m.Center, anyNormal); if (value1 > 0 && value2 < 0) { return(circle2); } if (value2 > 0 && value1 < 0) { return(circle1); } return(value1 > value2 ? circle2 : circle1); }
/// <summary> /// Layouting of items starts with arranging the first three items around the center 0,0 /// Note that we do not set the radius. The radius was determined for the leaf nodes before. /// The non leaf nodes don't reflect the sum of the children (area metric!). /// </summary> private FrontChain InitializeFrontChain(List <IHierarchicalData> children) { var frontChain = new FrontChain(); var left = new CircularLayoutInfo(); var right = new CircularLayoutInfo(); var top = new CircularLayoutInfo(); IHierarchicalData child; if (children.Count >= 1) { // Left child = children[0]; left = GetLayout(child); // If child has children its origin is not (0,0) any more. So move this node to origin first. var displacement = -(Vector)left.Center + new Vector(-left.Radius, 0); left.Move(displacement); } if (children.Count >= 2) { // Right child = children[1]; right = GetLayout(child); // If child has children its origin is not (0,0) any more. So move this node to origin first. var displacement = -(Vector)right.Center + new Vector(right.Radius, 0); right.Move(displacement); } if (children.Count >= 3) { // Top child = children[2]; top = GetLayout(child); var solutions = Geometry.FindCircleCircleIntersections( left.Center, left.Radius + top.Radius, right.Center, right.Radius + top.Radius, out var solution1, out var solution2); // If not maybe you did not remove zero weights? Debug.Assert(solutions == 2); var solution = solution1.Y > solution2.Y ? solution1 : solution2; var displacement = -(Vector)top.Center + (Vector)solution; top.Move(displacement); } // This order makes a huge difference. // left -> right -> top did not work and leads to overlapping circles! frontChain.Add(left); frontChain.Add(top); frontChain.Add(right); return(frontChain); }
private CircularLayoutInfo GetLayout(IHierarchicalData item) { if (item.Layout == null) { var layout = new CircularLayoutInfo(); item.Layout = layout; } return(item.Layout as CircularLayoutInfo); }
private CircularLayoutInfo SelectCircleWithLargerDistanceFromOrigin(CircularLayoutInfo circle1, CircularLayoutInfo circle2) { Debug.Assert(IsPointValid(circle1.Center) && IsPointValid(circle2.Center)); Debug.Assert(circle1.Radius.Equals(circle2.Radius)); if (((Vector)circle1.Center).Length > ((Vector)circle2.Center).Length) { return(circle1); } return(circle2); }
private bool IsOverlapping(CircularLayoutInfo layout1, CircularLayoutInfo layout2) { if (layout1 == layout2) { return(true); } var intersections = Geometry.FindCircleCircleIntersections(layout1.Center, layout1.Radius, layout2.Center, layout2.Radius, out var intersection1, out var intersection2); // Two solutions or one circle inside the other. return(intersections == 2 || intersections == -1); }
public Node InsertAfter(Node node, CircularLayoutInfo layout) { var newNode = new Node(layout); // Fix new node newNode.Next = node.Next; newNode.Previous = node; // Fix successor var successor = node.Next; successor.Previous = newNode; // Fix node node.Next = newNode; return(newNode); }
/// <summary> /// Find the circle tangent with the two others. /// </summary> private Tuple <CircularLayoutInfo, CircularLayoutInfo> FindTangentCircle(CircularLayoutInfo circle1, CircularLayoutInfo circle2, double radiusOfTangentCircle) { var solutions = Geometry.FindCircleCircleIntersections(circle1.Center, circle1.Radius + radiusOfTangentCircle, circle2.Center, circle2.Radius + radiusOfTangentCircle, out var solution1 , out var solution2); Debug.Assert(solutions >= 1); return(new Tuple <CircularLayoutInfo, CircularLayoutInfo>( new CircularLayoutInfo { Center = solution1, Radius = radiusOfTangentCircle }, new CircularLayoutInfo { Center = solution2, Radius = radiusOfTangentCircle })); }
public Node Add(CircularLayoutInfo layout) { var newNode = new Node(layout); if (Head == null) { Head = newNode; newNode.Next = Head; newNode.Previous = Head; } else { // Add to the end (before head) var tail = Head.Previous; tail.Next = newNode; newNode.Previous = tail; newNode.Next = Head; Head.Previous = newNode; } return(newNode); }
private Node FindOverlappingCircleOnFrontChain(FrontChain frontChain, CircularLayoutInfo tmpCircle) { return(frontChain.Find(node => IsOverlapping(node.Value, tmpCircle))); }
/// <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)); }
public Node(CircularLayoutInfo value) { Value = value; }