L'espirògraf és una joguina de dibuix geomètric que permet traçar corbes matemàtiques estil ruleta, tècnicament conegudes com a hipotrocoide i epitrocoide. Va ser desenvolupat per l'enginyer britànic Denys Fisher i venut per primera vegada el 1965.
En computació, la idea es pot ampliar de forma recursiva per generar un espirògraf fractal, on el cercle generatiu actua a la vegada de cercle directiu d'un de més petit, i així successivament generant cada vegada més detall en el traç final d'una manera similar a les sèries de Fourier complexes. Dit d'una altra manera, un espirògraf bàsic de dos cercles (l'immòbil, anomenat directiu, i el que fa la rotació, anomenat generatiu) es pot considerar el cas més simple d'una família de corbes trocoides.
Base matemàtica
Es necessiten definir varis de paràmetres per construir un espirògraf; el nombre total de cercles directius (n), la ratio entre els radis dels diferents cercles (m), la ratio entre la velocitat de gir dels diferents cercles (k), i la distància de cada cercle (d) respecte el centre de l'anterior.
El radi del cercle actual es calcula utilitzant la ratio descrita, .
La distància del cercle actual respecte el centre es calcula tenint en compte el radi del cercle anterior i el del cercle actual, , aplicant la suma en el cas de l'epitrocoide i la resta en el de l'hipotrocoide.
La velocitat de gir es calcula partint de la velocitat de gir del cercle anterior i aplicant una ratio constant. Aquesta ratio és gairebé sempre negativa, és a dir, la direcció de rotació va alternant-se a cada cercle.
La fórmula més bàsica seria . Aquesta velocitat és la que s'utilitza per actualitzar l'angle .
Obtenim la posició de cada cercle:
Aquesta posició de l'últim cercle serà la utilitzada per aplicar el traçat final de l'espirògraf.
Exemple en pseudocodi:
// Definir els paràmetres necessaris
n = 11
r_ratio = 3
d_prev = 0.5
d_current = 0.5
k = -4
// Crear el cercle inicial
ary orbits = []
circle = Circle.new(size) // Mida del cercle immòbil
// Emmagatzemar tota la informació necessària del cercle
orbit = Orbit.new(circle, d=0, angle=0, speed=1)
orbits.push(o)
// Crear cadascun dels cercles partint de l'anterior
for i in 1...n
prev = orbits[i - 1]
size = previous.circle.size / r_ratio
circle = Circle.new(size)
circle.x = prev.circle.x
circle.y = prev.circle.y
d = previous.ciecle.size * d_prev + circle.size * d_current
angle = previous.angle
speed = previous.speed * k
orbit = Orbit.new(circle, d, angle, speed)
orbits.push(orbit)
end
// Actualitzar el dibuix canviant l'angle constantment
loop do
// Actualitzar la posició de cada cercle
Graphics.update
for i in 1...orbits.size
prev = orbits[i - 1]
orbit = orbits[i]
orbit.angle += orbit.speed
d = orbit.distance
dx = prev.circle.x + d * Math.sin(angle)
dy = prev.circle.y + d * Math.cos(angle)
end
// Traçar el dibuix
last = orbits[-1].circle
if (last_drawn != NULL)
sx = last_drawn.x
sy = last_drawn.y
draw_line(sx, sy, last.x, last.y)
else
set_pixel(last.x, last.y)
end
last_drawn = Point.new(last.x, last.y)
end
Bibliografia
Maurer, Peter M. «A Rose is a Rose...». The American Mathematical Monthly, 94, 7, 1987, pàg. 631-645 [Consulta: 24 octubre 2021].