Para “traduzir” o código da pergunta para python, os passos apresentados no livro foram seguidos. Antes de qualquer coisa, os módulos a serem utilizados são importados:
import numpy as np
from turtle import *
import noise
Primeiramente, uma circunferência simples é desenhada usando trigonometria: os pontos (x,y) que formam uma circunferência de raio r centrada no ponto (centerX, centerY) são obtidos ao incrementar o ângulo \(\theta\) e calcular:
\[x=centerX + r*cos(\theta)\\
y=centerY + r*sen(\theta)\]
Usando o módulo turtle do python é possível desenhar a circunferência com um loop que, para cada ângulo, calcula os valores de x e y dado o raio e as coordenadas do centro da circunferência:
def trig_circle(radius, centerX=0, centerY=0):
for theta in range(360):
rad=np.radians(theta) #converts degrees to radians
x=centerX+radius*np.cos(rad)
y=centerY+radius*np.sin(rad)
goto(x,y)
pendown()
Para desenhar uma circunferência de raio 100:
if __name__=='__main__':
begin_fill()
speed(0)
penup()
trig_circle(100)
done()
O resultado é

Para criar uma espiral, o raio da circunferência é incrementado por uma constante a cada novo ponto (x,y). Na função abaixo, que difere da primeira apenas pela última linha que amplia o raio, a variável “size” indica quantas voltas terá a espiral:
def trig_spiral(size, radius, centerX=0, centerY=0, r_increment=0.1):
for theta in range(size*360):
rad=np.radians(theta)
x=centerX+radius*np.cos(rad)
y=centerY+radius*np.sin(rad)
goto(x,y)
pendown()
radius=radius+r_increment
Para desenhar a espiral:
if __name__=='__main__':
begin_fill()
speed(0)
penup()
trig_spiral(4, 10)
done()

Adiciona-se então um ruído à espiral usando o módulo “noise”, que permite criar ruídos de Perlin. Este tipo de ruído é utilizado em computação gráfica para criar texturas e padrões de ondulação mais “naturais” ou “suaves”, uma vez que a sequência de números é pseudoaleatória. Uma função que gera um ruído contínuo inicialmente gera pontos aleatórios e depois suavemente interpola estes pontos. O ruído de Perlin resulta da soma de funções de ruídos como estas com diferentes frequências e amplitudes.
Uma função foi definida para criar uma lista de ruídos de Perlin unidimensionais através da função “pnoise1”. Os argumentos desta função determinam a suavidade do ruído. O argumento “octaves” indica quantos ruídos serão sucessivamente adicionados para criar o ruído de Perlin (o padrão para um ruído simples é octaves=1). O argumento “persistence” especifica a amplitude de cada ruído sucessivo em relação ao anterior (default: persistence=0.5, de modo que a amplitude do ruído adicional é a metade da amplitude do ruído anterior). Por fim, “lacunarity” indica a frequência de cada ruído sucessivo relativamente ao anterior, sendo 2 o valor padrão. Na função abaixo, a lista de ruídos admite uma escala que em geral será igual a 100, de modo que os valores da lista variam entre -1 e 1.
def noise_generator(shape, scale=100, octaves=6, persistence=0.5, lacunarity=2):
noise_list=np.zeros(shape)
for i in range(shape):
noise_list[i] = noise.pnoise1(i/scale, octaves=octaves, persistence=persistence, lacunarity=lacunarity)
return noise_list
def noisy_spiral(size, radius, centerX=0, centerY=0, scale=100, r_increment=0.1):
noise_list=noise_generator(shape=size*360, scale=scale)
for theta in range(size*360):
rad=np.radians(theta)
radius_noise=noise_list[theta]*100
thisradius=radius+radius_noise
x=centerX+thisradius*np.cos(rad)
y=centerY+thisradius*np.sin(rad)
goto(x,y)
pendown()
radius=radius+r_increment*2
Desenhando a espiral com ruído:
if __name__=='__main__':
begin_fill()
speed(0)
penup()
noisy_spiral(4,10)
done()

A partir destas formas iniciais é possível desenhar padrões ondulados. Começando pelo desenho de um círculo formado por linhas que conectam pontos opostos de uma circunferência. A função abaixo segue a mesma lógica da primeira, porém desta vez são calculados os pontos (x,y) da circunferência e os seus pontos opostos. Uma linha conecta os dois pontos.
def circle_lines(radius, size, centerX=0, centerY=0):
for theta in range(size):
rad=np.radians(theta)
opp_rad=rad+np.pi
x=centerX+radius*np.cos(rad)
y=centerY+radius*np.sin(rad)
opp_x=centerX+radius*np.cos(opp_rad)
opp_y=centerY+radius*np.sin(opp_rad)
goto(x,y)
goto(opp_x, opp_y)
Para desenhar este tipo de círculo:
if __name__=='__main__':
begin_fill()
speed(0)
circle_lines(100, 130)
goto(0,0)
clear()
circle_lines(100, 180)
done()


Adicionando ruído ao raio do círculo anterior, assim como foi feito na primeira parte ao desenhar apenas a circunferência:
def noisy_circle_lines(radius, size, centerX=0, centerY=0, scale=100):
noise_list=noise_generator(shape=size, scale=scale)
for theta in range(size):
rad=np.radians(theta)
opp_rad=rad+np.pi
radius_noise=noise_list[theta]*100
thisradius=radius+radius_noise
x=centerX+thisradius*np.cos(rad)
y=centerY+thisradius*np.sin(rad)
opp_x=centerX+thisradius*np.cos(opp_rad)
opp_y=centerY+thisradius*np.sin(opp_rad)
goto(x,y)
goto(opp_x, opp_y)
O desenho é feito a partir do código:
if __name__=='__main__':
begin_fill()
speed(0)
noisy_circle_lines(100, 180)
done()

Finalmente, é possível adicionar ruído também ao ponto que define o centro da forma circular de modo que a cada passo do loop o centro da figura é alterado. Esta última alteração permite a criação de padrões similares aos da figura 4.15. Para gerar estes padrões foi definida uma classe:
class Wave_clock:
def __init__ (self,
size, #number of "laps" for a spiral
radius, #radius
detail=2, #increase this parameter for more detail
centerX=0, #center of the circle (x coordinate)
centerY=0, #center of the circle (y coordinate)
r_increment=2, #increment to the radius
scale=100, #scale of the noise list (100 for a list between -1 and 1)
octaves=6, #number of noise functions added
persistence=0.5, #amplitude of each successive octave added relative to the one below it
lacunarity=2, #frequency of each successive octave added relative to the one below it
xnoise=12, #noise to x coordinate for the center of the spiral
ynoise=20 #noise to y coordinate for the center of the spiral
):
self.size=size
self.radius=radius
self.detail=detail
self.centerX=centerX
self.centerY=centerY
self.r_increment=r_increment
self.scale=scale
self.octaves=octaves
self.persistence=persistence
self.lacunarity=lacunarity
self.xnoise=xnoise
self.ynoise=ynoise
def noise_generator(self): #creates a list of noises between 0 and
noise_list=np.zeros(self.size*360)
for i in range(self.size*360):
noise_list[i] = noise.pnoise1(i/self.scale, octaves=self.octaves, persistence=self.persistence, lacunarity=self.lacunarity)
return noise_list
def wave_clock(self): #adds noise to the center of the circumference
noise_list=self.noise_generator()
radius=self.radius
centerX=self.centerX
centerY=self.centerY
for i in range(self.size*360):
x_noise=noise_list[i]*self.xnoise
y_noise=noise_list[i]*self.ynoise
radius_noise=noise_list[i]*10
angle=i*(1/self.detail)
thisradius=radius*5+radius_noise
rad=np.radians(angle)
opp_rad=rad + np.pi
x=centerX+thisradius*np.cos(rad)
y=centerY+thisradius*np.sin(rad)
opp_x=centerX+thisradius*np.cos(opp_rad)
opp_y=centerY+thisradius*np.sin(opp_rad)
goto(x,y)
penup()
goto(opp_x, opp_y)
pendown()
radius=radius+self.r_increment
centerX=centerX+x_noise*2
centerY=centerY+y_noise*2
Usando os parâmetros padrão da classe, obtém-se a imagem:
if __name__=='__main__':
my_waveclock=Wave_clock(2,100)
begin_fill()
speed(0)
screensize(15000,15000)
my_waveclock.wave_clock()
done()

Alterando alguns parâmetros é possível criar diferentes padrões. Os parâmetros a seguir dão origem às imagens abaixo, respectivamente:
if __name__=='__main__':
my_wave_clock1 = Wave_clock(1, 100, detail = 2)
my_wave_clock2 = Wave_clock(1, 100, detail = 2, xnoise=-14, ynoise=-10)
my_wave_clock3 = Wave_clock(1, 100, detail = 2, xnoise=-12, ynoise=12)
my_wave_clock4 = Wave_clock(1, 100, detail = 2, xnoise=0, ynoise=10)
my_wave_clock5 = Wave_clock(2, 100, detail = 4)
my_wave_clock6 = Wave_clock(2, 100, detail = 8)





