Na dzisiejszych zajęciach będziemy zajmować się fraktalami. Fraktale to zbiory matematyczne charakteryzujące się tzw. samopodobieństwem – w każdym powiększeniu wyglądają identycznie1.
Idea trójkąta Sierpińskiego polega na tym, że każdy trójkąt zawiera w sobie trzy takie same trójkąty o dwukrotnie krótszych bokach (patrz Rys. 1).
Trójkąt Sierpińskiego można stworzyć na różne sposoby. My wykorzystamy rekurencję.
Rekurencyjne wywoływanie funkcji polega na tym, że dana funkcja wywołuje samą siebie. Jak łatwo się domyślić, takie wywoływanie trwałoby w nieskończoność. Aby tego uniknąć, nakłada się warunek na wywołanie funkcji.
Cały mechanizm, ilustruje to poniższy przykład:
void recursiveDraw(double x, double y, double R) {
circle(x, y, R);
if(x < 500) {
recursiveDraw(x + 5, y + 5, 0.88 * R);
}
}
Funkcja recursiveDraw
rysuje okrąg o zadanych
parametrach oraz wywołuje samą siebie z innymi wartościami argumentów,
dopóki wartość \(x\) nie przekroczy
\(500\).
W wypadku trójkąta Sierpińskiego, napiszemy funkcję, która generuje fraktal do pewnej ,,głębokości’’. Głębokość \(1\) oznacza pojedynczy trójkąt, \(2\) – trzy trójkąty, \(3\) – dziewięć, itd. Funkcję taką można napisać zauważając, że trójkąt o głębokości \(n\) składa się z trzech trójkątów o głębokości \(n - 1\).
Napisz funkcję sierpinski(x, y, d, n)
, która:
sierpinski()
z:
animate()
w celu
spowolnienia rysowania i zobacz w jakiej kolejności powstają kolejne
trójkąty.Wyobraźmy sobie ciąg liczb zespolonych takich, że każdy wyraz zależy od poprzedniego zgodnie ze wzorem: \(z_{n+1} = z_n^2 + c\). Taki ciąg ma nietypowe własności. Między innymi, jego rozbieżność zależy w bardzo złożony sposób od wyrazu początkowego \(z_0\) i stałej \(c\). Zbiór takich \(z_0\), dla których ciąg ten nie jest rozbieżny nazwany został zbiorem Julii (zależy on od \(c\)). Zaś zbiór takich \(c\), dla których ciąg ten nie jest rozbieżny oraz \(z_0 = 0\) – zbiorem Mandelbrota.
Do zobrazowania tych zbiorów potrzebna jest umiejętność kolorowania
pojedynczych pikseli. Wyobraźmy sobie, że nasze okno grafiki to układ
dwuwymiarowy, gdzie \(x \in [-4, 4]\) i
\(y \in [-3, 3]\). Używając funkcji
setcolor()
, możemy narysować wykres funkcji \(\sin(x^2 + y^2)\) dla tych
współrzędnych.
Funkcja setcolor()
jako argument przyjmuje liczbę z
przedziału od \(0\) do \(1\) i ustawia kolor rysowania.
Przeanalizuj poniższy program:
Napisz funkcję divergence(zR, zI, cR, cI)
, która
sprawdza czy ciąg opisany równaniem \[
\left\{
\begin{array}{l}
z_0 = zR + zI \cdot i \\
z_{n+1} = z_n^2 + c \\
c = cR + cI \cdot i
\end{array}
\right.
\] jest zbieżny.
Przyjmij, że maksymalna liczba iteracji to \(600\). Jeśli ciąg jest rozbieżny, niech funkcja zwraca liczbę iteracji, po której \(|z_n| > 2\) podzieloną przez \(600\). Jeżeli ciąg jest zbieżny (tzn. po \(600\) iteracjach nadal \(|z_n| < 2\)), niech funkcja zwraca \(0\). Przypomnienie: Działania na liczbach zespolonych wykonuj jak zwykłe mnożenie dwumianów, pamiętając jedynie, że \(i^2 = -1\).
divergence(x, y, -0.3, 0.63)
. Dla ułatwienia, wywołanie to
możesz umieścić wewnątrz fun(x, y)
albo bezpośrednio wpisać
jego wynik do zmiennej \(r\).divergence(0, 0, x, y)
.
Powinieneś otrzymać zbiór Mandelbrota.Antyaliasing ma na celu usunięcie efektów reprezentacji ze skończoną
rozdzielczością. Dla przykładu, jeśli mamy literę, to jej krawędź jest
gładką krzywą, która przechodzi tylko częściowo przez piksel. Jeśli
zaczernimy tylko piksele wewnątrz krzywej, uzyskamy bardzo nienaturalny
efekt. Jeśli jednak użyjemy odcienia szarości, proporcjonalnego do
,,pola’’ przykrytego tą literą, uzyskamy ładną, gładką czcionkę.
Wyobraźmy sobie, że mamy obraz składający się z dużej liczby gęsto
ułożonych czarnych linii na białym tle. Chcąc taki obraz zmniejszyć,
możemy wziąć np. co trzeci piksel w każdą stronę. Jednak takie podejście
spowoduje, że raz trafimy na w pełni czarny, a raz na w pełni biały
piksel. Ostatecznie uzyskamy biało-czarną kaszę. Drugim podejściem
byłoby wyznaczenie średniej z każdej kostki \(3 \times 3\). Takie podejście da nam
pożądany efekt rozmazania zbyt małych struktur i kolor szary w miejscu
losowej kaszy. Taki rodzaj antyaliasingu nazywany jest
super-samplingiem. W wypadku obrazów takich jak zbiór
Mandelbrota czy zbiór Julii, możemy dla każdego
piksela obliczyć \(k \times k\)
wartości funkcji fun()
i uśrednić wynik. Taki zabieg
wygładzi obraz i usunie ,,odstające’’ piksele.
fun2(x, y)
, która oblicza średnią
wartość funkcji fun
w czterech punktach: \((x, y)\), \((x +
s, y)\), \((x, y + s)\) i \((x + s, y + s)\), gdzie \(s = 1/800\). Pokoloruj piksele za jej
pomocą.setcolor()
na
setgray()
.Z tego powodu wydają się bardzo skomplikowane. Okazuje się jednak, że fraktale stanowią całkiem niezły sposób opisu przyrody – wystarczy spojrzeć na kawałek wybrzeża Wielkiej Brytanii albo na gałąź choinki, żeby stwierdzić, że przypominają całość, tylko w pomniejszeniu.↩︎