from math import pi, cos, sqrt, acos, atan
from dataclasses import dataclass
import cmath
import numpy as np
from .geometry import distance, homogenize, side_len_to_radius, angle_between_lines2
from ..graphics.affine import rotate, scale_matrix, rotation_matrix
from ..graphics.shapes import Circle
array = np.array
dot = np.dot
linalg = np.linalg
[docs]
@dataclass
class Circle_:
"""A simple circle class."""
center: tuple
radius: float
[docs]
def circle_tangent_to_3_circles(c1, r1, c2, r2, c3, r3, s1=-1, s2=-1, s3=-1):
"""Given the centers and radii of 3 circles, return the center and radius
of a circle that is tangent to all 3 circles.
Args:
c1 (tuple): Center of the first circle.
r1 (float): Radius of the first circle.
c2 (tuple): Center of the second circle.
r2 (float): Radius of the second circle.
c3 (tuple): Center of the third circle.
r3 (float): Radius of the third circle.
s1 (int, optional): Sign for the first circle. Defaults to -1.
s2 (int, optional): Sign for the second circle. Defaults to -1.
s3 (int, optional): Sign for the third circle. Defaults to -1.
Returns:
tuple: Center (x, y) and radius of the tangent circle.
"""
x1, y1 = c1
x2, y2 = c2
x3, y3 = c3
v11 = 2 * x2 - 2 * x1
v12 = 2 * y2 - 2 * y1
v13 = x1 * x1 - x2 * x2 + y1 * y1 - y2 * y2 - r1 * r1 + r2 * r2
v14 = 2 * s2 * r2 - 2 * s1 * r1
v21 = 2 * x3 - 2 * x2
v22 = 2 * y3 - 2 * y2
v23 = x2 * x2 - x3 * x3 + y2 * y2 - y3 * y3 - r2 * r2 + r3 * r3
v24 = 2 * s3 * r3 - 2 * s3 * r2
w12 = v12 / v11
w13 = v13 / v11
w14 = v14 / v11
w22 = v22 / v21 - w12
w23 = v23 / v21 - w13
w24 = v24 / v21 - w14
P = -w23 / w22
Q = w24 / w22
M = -w12 * P - w13
N = w14 - w12 * Q
a = N*N + Q*Q - 1
b = 2 * M * N - 2 * N * x1 + 2 * P * Q - 2 * Q * y1 + 2 * s1 * r1
c = x1 * x1 + M * M - 2 * M * x1 + P * P + y1 * y1 - 2 * P * y1 - r1 * r1
# Find a root of a quadratic equation.
# This requires the circle centers not to be collinear
D = b * b - 4 * a * c
rs = (-b - sqrt(D)) / (2 * a)
xs = M + N * rs
ys = P + Q * rs
return (xs, ys, rs)
[docs]
def apollonius(r1, r2, r3, z1, z2, z3, plus_minus=1):
"""Solves the Problem of Apollonius using Descartes' Theorem.
Args:
r1 (float): Radius of the first circle.
r2 (float): Radius of the second circle.
r3 (float): Radius of the third circle.
z1 (complex): Center of the first circle.
z2 (complex): Center of the second circle.
z3 (complex): Center of the third circle.
plus_minus (int, optional): +1 for outer tangent circle, -1 for inner tangent circle. Defaults to 1.
Returns:
tuple: Radius and center coordinates (x, y) of the tangent circle, or None if no solution is found.
"""
k1, k2, k3 = 1/r1, 1/r2, 1/r3
# Applying Descartes' Theorem
k4_values = (k1 + k2 + k3) + plus_minus * 2 * sqrt(k1*k2 + k2*k3 + k3*k1)
# Handle cases where no solution exists (e.g., division by zero)
if k4_values == 0:
return None
r4 = 1/k4_values
z4 = ((k1*z1 + k2*z2 + k3*z3 + plus_minus * 2 *
cmath.sqrt(k1*k2*z1*z2 + k2*k3*z2*z3 + k3*k1*z3*z1)) / k4_values)
return r4, z4
[docs]
def circle_tangent_to_2_circles(c1, r1, c2, r2, r):
"""Given the centers and radii of 2 circles, return the center
of a circle with radius r that is tangent to both circles.
Args:
c1 (tuple): Center of the first circle.
r1 (float): Radius of the first circle.
c2 (tuple): Center of the second circle.
r2 (float): Radius of the second circle.
r (float): Radius of the tangent circle.
Returns:
tuple: Centers (x1, y1) and (x2, y2) of the tangent circle.
"""
x1, y1 = c1
x2, y2 = c2
r12 = r1**2
r12y1 = r12 * y1
r12y2 = r12 * y2
r1r2 = r1 * r2
r22 = r2**2
r22y2 = r22 * y2
r_2 = r**2
rr1 = r * r1
rr1y1 = rr1 * y1
rr1y2 = rr1 * y2
rr2 = r * r2
rr2y1 = rr2 * y1
rr2y2 = rr2 * y2
x12 = x1**2
x12y1 = x12 * y1
x12y2 = x12 * y2
x1_x2 = x1 - x2
x1x2 = x1 * x2
x1x2y1 = x1x2 * y1
x1x2y2 = x1x2 * y2
x22 = x2**2
x22y1 = x22 * y1
x22y2 = x22 * y2
y12 = y1**2
y12y2 = y12 * y2
y13 = y1**3
y1y2 = y1 * y2
y22 = y2**2
y1y22 = y1 * y22
y23 = y2**3
x_1 = (
-(y1 - y2)
* (
-2 * rr1y1
+ 2 * rr1y2
+ 2 * rr2y1
- 2 * rr2y2
- r12y1
+ r12y2
+ r22 * y1
- r22y2
+ x12y1
+ x12y2
- 2 * x1x2y1
- 2 * x1x2y2
+ x22y1
+ x22y2
+ y13
- y12y2
- y1y22
+ y23
+ sqrt(
(-r12 + 2 * r1r2 - r22 + x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22)
* (
4 * r_2
+ 4 * rr1
+ 4 * rr2
+ r12
+ 2 * r1r2
+ r22
- x12
+ 2 * x1x2
- x22
- y12
+ 2 * y1y2
- y22
)
)
* (-x1 + x2)
)
- (x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22)
* (2 * rr1 - 2 * rr2 + r12 - r22 - x12 + x22 - y12 + y22)
) / (2 * (x1_x2) * (x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22))
y_1 = (
-2 * rr1y1
+ 2 * rr1y2
+ 2 * rr2y1
- 2 * rr2y2
- r12y1
+ r12y2
+ r22 * y1
- r22y2
+ x12y1
+ x12y2
- 2 * x1x2y1
- 2 * x1x2y2
+ x22y1
+ x22y2
+ y13
- y12y2
- y1y22
+ y23
+ sqrt(
(-r12 + 2 * r1r2 - r22 + x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22)
* (
4 * r_2
+ 4 * rr1
+ 4 * rr2
+ r12
+ 2 * r1r2
+ r22
- x12
+ 2 * x1x2
- x22
- y12
+ 2 * y1y2
- y22
)
)
* (-x1 + x2)
) / (2 * (x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22))
x_2 = (
-(y1 - y2)
* (
-2 * rr1y1
+ 2 * rr1y2
+ 2 * rr2y1
- 2 * rr2y2
- r12y1
+ r12y2
+ r22 * y1
- r22y2
+ x12y1
+ x12y2
- 2 * x1x2y1
- 2 * x1x2y2
+ x22y1
+ x22y2
+ y13
- y12y2
- y1y22
+ y23
+ sqrt(
(-r12 + 2 * r1r2 - r22 + x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22)
* (
4 * r_2
+ 4 * rr1
+ 4 * rr2
+ r12
+ 2 * r1r2
+ r22
- x12
+ 2 * x1x2
- x22
- y12
+ 2 * y1y2
- y22
)
)
* (x1_x2)
)
- (x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22)
* (2 * rr1 - 2 * rr2 + r12 - r22 - x12 + x22 - y12 + y22)
) / (2 * (x1_x2) * (x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22))
y_2 = (
-2 * rr1y1
+ 2 * rr1y2
+ 2 * rr2y1
- 2 * rr2y2
- r12y1
+ r12y2
+ r22 * y1
- r22y2
+ x12y1
+ x12y2
- 2 * x1x2y1
- 2 * x1x2y2
+ x22y1
+ x22y2
+ y13
- y12y2
- y1y22
+ y23
+ sqrt(
(-r12 + 2 * r1r2 - r22 + x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22)
* (
4 * r_2
+ 4 * rr1
+ 4 * rr2
+ r12
+ 2 * r1r2
+ r22
- x12
+ 2 * x1x2
- x22
- y12
+ 2 * y1y2
- y22
)
)
* (x1_x2)
) / (2 * (x12 - 2 * x1x2 + x22 + y12 - 2 * y1y2 + y22))
return ((x_1, y_1), (x_2, y_2))
[docs]
def tangent_points(center1, radius, center2, radius2, cross=False):
"""Returns the tangent points (p1, p2, p3, p4) in world coordinates.
Args:
center1 (tuple): Center of the first circle.
radius (float): Radius of the first circle.
center2 (tuple): Center of the second circle.
radius2 (float): Radius of the second circle.
cross (bool, optional): Whether to calculate crossing tangents. Defaults to False.
Returns:
tuple: Tangent points (p1, p2, p3, p4) in world coordinates.
"""
c1 = Circle_(center1, radius)
c2 = Circle_(center2, radius2)
if radius < radius2:
c1, c2 = c2, c1
pos = c1.center
dist = distance(pos, c2.center)
r1 = c1.radius
r2 = c2.radius
if cross:
dr = r1 + r2
else:
dr = r1 - r2
x = sqrt(dist**2 - dr**2)
y = pos[1] + r1
p1 = [pos[0], y]
p2 = [pos[0] + x, y]
points = homogenize([p1, p2])
alpha = angle_between_lines2((pos[0] + x, pos[1] + dr), pos, c2.center)
tp1w, tp2w = rotate(points, alpha, pos)
if x == 0:
beta = 0
else:
beta = pi / 2 - atan(dr / x)
tp3w = rotate([tp1w], -2 * beta, pos)[0]
tp4w = rotate([tp2w], -2 * beta, c2.center)[0]
return (tp1w, tp2w, tp3w, tp4w)
[docs]
def circle_area(rad):
"""Given the radius of a circle, return the area of the circle.
Args:
rad (float): Radius of the circle.
Returns:
float: Area of the circle.
"""
return pi * rad**2
[docs]
def circle_circumference(rad):
"""Given the radius of a circle, return the circumference of the circle.
Args:
rad (float): Radius of the circle.
Returns:
float: Circumference of the circle.
"""
return 2 * pi * rad
[docs]
def flower_angle(r1, r2, r3):
"""Given the radii of 3 circles forming an interstice, return the angle between
the lines connecting circles' centers to center of the circle with r1 radius.
Args:
r1 (float): Radius of the first circle.
r2 (float): Radius of the second circle.
r3 (float): Radius of the third circle.
Returns:
float: Angle between the lines connecting circles' centers.
"""
angle = acos(
((r1 + r2) ** 2 + (r1 + r3) ** 2 - (r2 + r3) ** 2) / (2 * (r1 + r2) * (r1 + r3))
)
return angle
ratios = {8: 0.4974, 9: 0.5394, 10: 0.575, 11: 0.6056,
12: 0.6321, 13: 0.6553, 14: 0.6757, 15: 0.6939, 16: 0.7101,
17: 0.7248, 18: 0.738, 19: 0.75, 20: 0.7609}
[docs]
def circle_flower(n, radius=25, layers=6, ratio = None):
"""Steiner chain. Return a list of circles that form a flower-like pattern.
Args:
n (int): Number of circles.
radius (float, optional): Radius of the circles. Defaults to 25.
layers (int, optional): Number of layers. Defaults to 6.
ratio (float, optional): Ratio for scaling. Defaults to None.
Returns:
list: List of circles forming a flower-like pattern.
Raises:
ValueError: If n is less than 8.
"""
if n<8:
raise ValueError('n must be greater than 7')
if ratio is None:
if n<21:
ratio = ratios[n]
else:
ratio = (-0.000000089767 * n**4 + 0.000015821834 * n**3 +
-0.001100867708 * n **2+ 0.038096046379 * n + 0.327363569038)
r1 = side_len_to_radius(n, 2*radius)
circles = Circle((r1, 0), radius).rotate(pi/(n/2), (0, 0), reps=n-1)
xform = scale_matrix(ratio) @ rotation_matrix(pi/n)
return circles.transform(xform_matrix=xform, reps=layers)