I created the image back in 2018, way before I learned programming. Now I am trying to write a function that creates images like this programmatically.
Basically, you start with a regular triangle (all three angles are 60°) whose side length is 1, you construct a square with side length 1 from each of its sides, then in the next iteration, you construct a unit pentagon from each of the three squares, then in the next iteration you construct a unit hexagon from each of the three pentagons from the previous iteration from the sides that will make the thing grow as depicted, and so on, and so forth.
In each iterations you construct three regular polygons with number of sides equal to one plus the number of sides of polygons in the previous iteration, from the sides that will make the shape grow as depicted, until a given number of iterations is reached.
I know how to compute the vertices of the shapes programmatically, I just don't know how to determine from which side I need to construct the next polygon. I tried to Google search this but I don't know what the proper term for it is, and image searching yields no results. Can anyone help me?
Update
I have done it, I have already wrote a Python function that generates images like this, here is an example output:
Here is the code if anyone is interested:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.collections import PolyCollection
from PIL import Image
def sin(d: float): return np.sin(np.radians(d))
def cos(d: float): return np.cos(np.radians(d))
def tan(d: float): return np.tan(np.radians(d))
def atan2(x, y): return np.rad2deg(np.arctan2(y, x))
def rotate(pos, angle, center=(0, 0)):
cx, cy = center
px, py = pos
diff_x, diff_y = (px - cx), (py - cy)
cosa, sina = cos(angle), sin(angle)
px1 = cosa * diff_x - sina * diff_y + cx
py1 = sina * diff_x + cosa * diff_y + cy
return (px1, py1)
def spectrum_position(n, string=False):
if not isinstance(n, int):
raise TypeError('`n` should be an integer')
if n < 0:
raise ValueError('`n` must be non-negative')
n %= 1530
if 0 <= n < 255:
return (255, n, 0) if not string else f'ff{n:02x}00'
elif 255 <= n < 510:
return (510-n, 255, 0) if not string else f'{510-n:02x}ff00'
elif 510 <= n < 765:
return (0, 255, n-510) if not string else f'00ff{n-510:02x}'
elif 765 <= n < 1020:
return (0, 1020-n, 255) if not string else f'00{1020-n:02x}ff'
elif 1020 <= n < 1275:
return (n-1020, 0, 255) if not string else f'{n-1020:02x}00ff'
elif 1275 <= n < 1530:
return (255, 0, 1530-n) if not string else f'ff00{1530-n:02x}'
def increment_rotate(pos1, pos2, rotation):
x1, y1 = pos1
x2, y2 = pos2
side = ((x2-x1)**2+(y2-y1)**2)**.5
angle = atan2((x2 - x1), (y2 - y1))
new_x, new_y = x2+side*cos(angle), y2+side*sin(angle)
new_x, new_y = rotate((new_x, new_y), rotation, (x2, y2))
return new_x, new_y
def make_polygon(pos1, pos2, sides):
assert sides >= 3
unit_rotation = 360/sides
x1, y1 = pos1
x2, y2 = pos2
side = ((x2-x1)**2+(y2-y1)**2)**.5
positions = [pos1]
prev_pos = pos2
cur_pos = pos1
for i in range(sides-2):
new_pos = increment_rotate(prev_pos, cur_pos, unit_rotation)
positions.append(new_pos)
prev_pos = cur_pos
cur_pos = new_pos
positions.append(pos2)
return positions
def polygon_spiral(unit, iterations, num_colors=12):
step = 1530/num_colors
palette = ['#'+spectrum_position(round(step*i), 1) for i in range(num_colors)]
colors = [palette[0]]
radius = unit*cos(30)/1.5
y1 = -radius/2
x1 = -unit/2
x2 = unit/2
points = [(0, radius), (x2, y1), (x1, y1)]
polygons = [points]
side = 4
left_start, left_end = points[0], points[2]
down_start, down_end = points[2], points[1]
right_start, right_end = points[1], points[0]
for i in range(iterations):
left = make_polygon(left_start, left_end, side)
down = make_polygon(down_start, down_end, side)
right = make_polygon(right_start, right_end, side)
polygons.append(left)
polygons.append(down)
polygons.append(right)
colors.extend([palette[(i+1)%num_colors]]*3)
half = (side/2).__ceil__()
left_start, left_end = left[half-1:half+1]
down_start, down_end = down[half-1:half+1]
right_start, right_end = right[half-1:half+1]
side += 1
return {'polygons': polygons, 'colors': colors}
def plot_polygon_spiral(iterations, unit=1, width=1920, height=1080, lw=2, alpha=1, num_colors=12, show=True):
polygons, colors = polygon_spiral(unit, iterations, num_colors).values()
fig = plt.figure(figsize=(width/100, height/100),
dpi=100, facecolor='black')
ax = fig.add_subplot(111)
ax.set_axis_off()
collection = PolyCollection(polygons, facecolors=colors, lw=lw, alpha=alpha, edgecolor='w')
ax.add_collection(collection)
plt.axis('scaled')
fig.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=0, hspace=0)
fig.canvas.draw()
image = Image.frombytes(
'RGB', fig.canvas.get_width_height(), fig.canvas.tostring_rgb())
if not show:
plt.close(fig)
else:
plt.show()
return image