Wiele form występujących w przyrodzie posiada cechy samopodobieństwa - to znaczy że mały fragment jest podobny do całości. Jest to także cecha specyficzna fraktali, za pomocą których możemy opisać na przykład góry, rośliny i... płatki śniegu.
Dzisiaj zaprezentuję jak korzystając z biblioteki Visual narysować śnieżkę Kocha. Początkującym radzę, żeby się nie zrażali, bo rzeczy związane z fraktalami, czy ogólnie z rekurencją nie dla każdego są jasne od samego początku, ale - będzie dobrze. Śnieżkę Kocha rysuje się w następujący sposób:
- Za pomocą linii rysujemy trójkąt równoboczny.
- Każdą z linii dzielimy na 3 części
- W miejscu środkowej części dodajemy dwie linie tak aby powstał trójkąt równoboczny.
- Usuwamy podstawę trójkąta
- Dla wszystkich istniejących linii, wykonujemy punkty od 2 do 5, do momentu aż się nam znudzi.
#!/usr/bin/env python
from __future__ import division (obsługa dzielenia niecałkowitego za pomocą znaku "/")
from visual import *
Tworzymy funkcję sniezka(), która:
- Przyjmuje parametry:
- etapy - ilość podziałów do wykonania
- l - długość boku trójkąta
- r - promień linii, którą rysujemy
- Tworzy ogólną ramkę "gwiazdka", która ma w sobie zawierać cały fraktal
- Tworzy 3 ramki dla każdej linii
- Obraca ramki w taki sposób, by narysowane w nich obiekty tworzyły zamknięty trójkąt
- Oblicza wysokość trójkąta, aby następnie narysować 3 linie w swoich ramkach
- Generuje fraktale oparte na stworzonych liniach
- Zwraca efekt końcowy
def sniezka(etapy,l,r):
gwiazdka=frame()
f1=frame(frame=gwiazdka)
f2=frame(frame=gwiazdka)
f3=frame(frame=gwiazdka)
f2.rotate(angle=-2*pi/3, axis=(0,0,1))
f3.rotate(angle=2*pi/3, axis=(0,0,1))
h=(l)*3**(1/2)/2
l1=curve(pos=((-l/2,h/3),(l/2,h/3)),frame=f1, radius=r)
l2=curve(pos=((-l/2,h/3),(l/2,h/3)),frame=f2, radius=r)
l3=curve(pos=((-l/2,h/3),(l/2,h/3)),frame=f3, radius=r)
fract(l1,etapy,r,f1)
fract(l2,etapy,r,f2)
fract(l3,etapy,r,f3)
return gwiazdka
gwiazdka=frame()
f1=frame(frame=gwiazdka)
f2=frame(frame=gwiazdka)
f3=frame(frame=gwiazdka)
f2.rotate(angle=-2*pi/3, axis=(0,0,1))
f3.rotate(angle=2*pi/3, axis=(0,0,1))
h=(l)*3**(1/2)/2
l1=curve(pos=((-l/2,h/3),(l/2,h/3)),frame=f1, radius=r)
l2=curve(pos=((-l/2,h/3),(l/2,h/3)),frame=f2, radius=r)
l3=curve(pos=((-l/2,h/3),(l/2,h/3)),frame=f3, radius=r)
fract(l1,etapy,r,f1)
fract(l2,etapy,r,f2)
fract(l3,etapy,r,f3)
return gwiazdka
Funkcja korzystała z funkcji generującej fraktale fract(), która wygląda następująco:
def fract(linia,etapy,r,f):
x1=linia.pos[0][0]
y1=linia.pos[0][1]
x2=linia.pos[1][0]
y2=linia.pos[1][1]
if etapy>0:
rate(50) # opóźnienie w celu zrobienia animacji
p1=(x1,y1,0)
p2=(x1+(x2-x1)/3,y1+(y2-y1)/3,0)
p3=(x2+(x1-x2)/3,y2+(y1-y2)/3,0)
p4=(x2,y2,0)
p5=obrocony(p3,p2,pi/3)
c1=curve(frame=f, pos=(p1,p2),radius=r)
c2=curve(frame=f, pos=(p2,p5),radius=r)
c3=curve(frame=f, pos=(p5,p3),radius=r)
c4=curve(frame=f, pos=(p3,p4),radius=r)
linia.visible=False
fract(c1,etapy-1,r,f)
fract(c2,etapy-1,r,f)
fract(c3,etapy-1,r,f)
fract(c4,etapy-1,r,f)
Funkcja działa w następujący sposób:
- Określa współrzędne początku i końca linii, której podziału ma dokonać
- Jeśli nie została wykonana określona ilość podziałów to:
- Ogranicza ilość wyświetlanych klatek do 50
- Oblicza współrzędne punktów podziałowych znajdujących się na linii
- Oblicza współrzędne punktu, który ma być wierzchołkiem nowego trójkąta, za pomocą funkcji obrocony()
- Usuwa starą linię
- Rysuje 4 nowe linie (2 boczne + 2 tworzące boki nowego trójkąta)
- Dokonuje podziałów nowo powstałych linii wywołując samą siebie (rekurencja)
def obrocony(p,ps,L):
X=p[0]
Y=p[1]
Xs=ps[0]
Ys=ps[1]
return ((X-Xs)*cos(L) - (Y-Ys)*sin(L) + Xs),((X-Xs)*sin(L) + (Y-Ys)*cos(L) + Ys),0
Parametry:
- p - punkt do obrócenia
- ps - punkt środkowy
- L - kąt obrotu podany w radianach
sniezka(5,5,0.02)
Można by się pokusić o obracającą się snieżkę:
def rot(f):
while True:
f.rotate(angle=radians(1), axis=(0,1,0))
rate(50)
s = sniezka(4,5,0.02)
rot(s)
Znalazłem nawet praktyczne zastosowanie dla wyprodukowanego śniegu: