The finished product, a tribute to Bridget Riley's Rustle 6 (2015).

```
import math
canvasX = 600
canvasY = 600
nFrames = 120
side = canvasX/14
height = (side * sqrt(3))/2
center = (side/sqrt(3))/2
nX = 14
nY = 15
offsetX = side
offsetY = height
from datetime import *
time = datetime.now()
def interpolate(pt1, pt2, ratio):
assert ratio <= 1
return (pt1[0] + (ratio * (pt2[0] - pt1[0])), pt1[1] + (ratio * (pt2[1] - pt1[1])))
def triangle(s, h, c, inout):
assert inout >= 0 and inout <= 1
start = (-s/2, h/2)
pivot = (0, h/2 - (c * 3))
target = (s/2, h/2)
control = interpolate((s/2, -c/2), (0, h/2 - c), inout)
radius = s
# optimal distance to control points is (4/3)*tan(pi/(2n); thank you https://stackoverflow.com/users/16673/suma for answering this question https://stackoverflow.com/questions/1734745/how-to-create-circle-with-b%C3%A9zier-curves
control_ratio = ((4/3 * tan(pi/12)) * radius) / (2 * c)
controlP = interpolate(pivot, control, control_ratio)
controlT = interpolate(target, control, control_ratio)
triangle = BezierPath()
triangle.moveTo(start)
triangle.lineTo(pivot)
triangle.curveTo(controlP, controlT, target)
triangle.closePath()
drawPath(triangle)
def frame(x, y, m):
f = BezierPath()
g = BezierPath()
f.rect(0, 0, x, y)
g.rect(m, m, x - (2 * m), y - (2 * m))
f = f.difference(g)
f.closePath()
drawPath(f)
# original Riley plan for Rustle 6
plan = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 0, 1, 0, 0],
[-1, 0, -1, 0, 0, 0, 0, 0, 1, 0, -1, 0, 0],
[1, 0, 1, -1, 0, 0, -1, 0, 0, 0, 1, 0, 1, 0],
[0, -1, 0, -1, 0, 0, 0, 1, 0, -1, 0, -1, 0],
[0, 1, -1, 1, 0, 1, 0, 0, 0, 0, -1, 1, 0, 0],
[-1, 0, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, -1],
[1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
[0, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, -1, 0],
[0, -1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, -1, 0],
[-1, 0, -1, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1],
[-1, 0, -1, 0, 1, 0, 1, 0, 0, 0, 1, -1, -1, 0],
[0, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, -1, -1, 0],
[0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
]
for n in range(nFrames):
newPage(canvasX, canvasY)
frameDuration(1/30)
ex = sin(radians(n * (360/nFrames)))
fill(1)
rect(0, 0, canvasX, canvasY)
with savedState():
translate(canvasX/2, canvasY/2)
fill(0)
translate(-(nX - 1) * offsetX/2, (nY - 1) * offsetY/2)
for i in range(nY):
with savedState():
translate(0, i * -offsetY)
if i % 2 == 0:
for j in range(nX - 1):
with savedState():
translate(offsetX/2 + (j * offsetX), 0)
triangle(side, height, center, .5 - (ex * (plan[i][j] * .5)))
else:
for j in range(nX):
with savedState():
translate(j * offsetX, 0)
triangle(side, height, center, .5 - (ex * (plan[i][j] * .5)))
fill(1)
frame(canvasX, canvasY, side/2)
saveImage('~/Desktop/riley_tribute_test_01_%s.gif' %time)
```

The reference image: