Title: Place three small circles inside a larger circle so they are all tangent in Python
The first step in making an Apollonian circle packing (which I'll get to in a later post) is to place three small tangent circles inside a larger circle as shown in the picture at the top of this post.
Finding the Inner Circles
Let R be the radius of the outer circle, let r be the radius of the smaller circles, and consider the picture on the right. It's pretty obvious that angle ∠BAC is 30°, so triangle △ ACB is a 30-60-90 right triangle.
The distance from the big circle's center to its circumference is R, so the distance from that center to the center of the smaller circle at point A is R - r.
Because the triangle is a 30-60-90 triangle, the ratio of the side lengths gives us this:
Now that we know R, we can find the centers of the three smaller circles. Let C = (cx, cy).
The X coordinate of the center of the upper circle is cx. Its Y coordinate is cy minus (R - r).
The other two circles have center X coordinates equal to cx ±r.
The Y coordinate of those centers equals cy plus d (see the picture with the measurements on it). But d is the same as distance BC. Because ACB is a 30-60-90 triangle, we know:
That means the Y coordinate of the two lower circles' centers is cy + (R - r) / 2.
The following get_inner_circles function returns the centers and radii of the inner circles.
def get_inner_circles(cx, cy, R):
'''Find the three circles inside the circle at (cx, cy) with radius R.'''
r = R / (1 + 2 / math.sqrt(3))
return [
(cx, cy - (R - r), r),
(cx - r, cy + (R - r) / 2, r),
(cx + r, cy + (R - r) / 2, r),
]
Drawing the Circles
When the program starts, it calls the following method to draw the four circles.
def draw_circles(self):
'''Draw a big circle with three smaller circles inside it.'''
# Create a PIL image to hold the drawing.
wid = hgt = 400
transparent = (0, 0, 0, 0)
image = Image.new('RGBA', (wid, hgt), transparent)
dr = ImageDraw.Draw(image)
margin = 10
R = min(wid, hgt) / 2 - margin
cx = wid / 2
cy = hgt / 2
# Draw the big circle.
rect = (cx - R, cy - R, cx + R, cy + R)
dr.ellipse(rect, outline='red', fill='pink', width=5)
# Draw the smaller inside circles.
centers = get_inner_circles(cx, cy, R)
for x, y, r in centers:
rect = (x-r, y-r, x+r, y+r)
dr.ellipse(rect, outline='green', fill='lightgreen', width=2)
# Display the image.
self.canvas.config(width=wid, height=hgt)
self.photo_image = ImageTk.PhotoImage(image)
self.canvas.create_image(0, 0, anchor=tk.NW, image=self.photo_image)
# Save the image.
image.save('circles.png')
The code first creates a PIL image with a transparent background. It finds the center and radius R for the larger circle and draws that circle.
Next, the code calls get_inner_circles to get the inner circles' centers and radii. It loops through that information to draw the inner circles.
The code converts the image into a ImageTk.PhotoImage and displays it on the program's Canvas widget. It finishes by saving the image into the file circles.png.
Conclusion
Finding the three inner circles is the first step in Apollonian circle packing, which I'll describe in a future post. Meanwhile, download the example to experiment with it and to see additional details.
|