Algorytm wzrostu
-
- Andrzej Stasiewicz,
- 01.09.2005
W serii Prog&Play nauczyliśmy się już tworzyć komputerowe dywany, eksplorować nieskończoną głębię fraktali i generować grafikę za pomocą wirtualnych nici. Dzisiaj zajmiemy się tak zwanym algorytmem wzrostu i wykorzystamy go do generacji form roślinnych.
W serii Prog&Play nauczyliśmy się już tworzyć komputerowe dywany, eksplorować nieskończoną głębię fraktali i generować grafikę za pomocą wirtualnych nici. Dzisiaj zajmiemy się tak zwanym algorytmem wzrostu i wykorzystamy go do generacji form roślinnych.
Namalować realistyczną ścianę lasu jest bardzo trudno. Ta masa zieleni nie może być utworzona kilkoma pociągnięciami pędzla. Jeśli chcemy uzyskać najwyższy stopień realizmu, musimy odtworzyć na płótnie wszystkie listki, gałązki i igiełki, strukturę pokrytej mchem kory drzew i przemykające przez zielone sito promienie światła. Tajemnica realizmu ukrywa się w najdrobniejszych szczegółach. Brak tych szczegółów pozwala natychmiast odróżnić film animowany od prawdziwego filmowania. Żaden "klasyczny" artysta nie namaluje dostatecznie realistycznej ściany lasu.

Formy roślinne są zbyt złożone, żeby narysować je pędzlem, jednocześnie zachowując fotograficzny realizm. Za to można je wyhodować za pomocą specjalnego algorytmu.
Skoro nie potrafimy narysować ściany lasu, to wyhodujmy jej komputerowy obraz wraz ze wszystkimi mikroszczegółami formy. Niech będą tam wszystkie niemożliwe do bezpośredniego narysowania listki i igiełki. Zamiast rysować jurajską paproć, zaproponujmy jej formułę wzrostu i program, który wyświetli ją na ekranie.

Prosta formuła początkowa, cztery reguły jej wzrostu i kilka pokoleń rośliny.
Niech roślina składa się z dwóch gałązek, oznaczonych symbolami A i B. Możemy sobie na przykład wyobrazić, że symbol A oznacza witkę, symbol B jakiś listek lub kwiatek - wszystko to będziemy musieli uściślić w drugiej części programu, odpowiadającej za obraz rośliny. Niech początkowa roślinka będzie zdefiniowana za pomocą symbolu A. Ot, taki badylek...
Teraz zdefiniujmy reguły wzrostu małej roślinki. Niech w każdym cyklu wzrostu każdy symbol A zostanie zastąpiony sekwencją AB, a każdy symbol B symbolem A. Nietrudno zauważyć, że w wyniku konsekwentnego stosowania tych reguł roślinka rośnie, bo jej symboliczna receptura wydłuża się:
A
AB
ABA
ABAAB
ABAABABA
...
Algorytm ten zrealizujemy z łatwością choćby za pomocą operacji tekstowych, wprowadzających zamiast jednych symboli inne ich sekwencje:
String FORMULA = "AB";
void rozwin()
{
String rozwiniecie_A = "AB";
String rozwiniecie_B = "A";
String nowa_formula;
for( int i=1; i<=FORMULA.Length(); i++)
{
switch( FORMULA[i])
{
case 'A':
nowa_formula = nowa_formula + rozwiniecie_A;
break;
case 'B':
nowa_formula = nowa_formula + rozwiniecie_B;
break;
// ewentualne inne symbole
// i ich rozwijanie...
}
}
FORMULA = nowa_formula;
}

Wprowadzenie parametru odpowiadającego za długość gałązki było bardzo dobrym posunięciem - dzięki niemu potrafimy skalować rośliny, aby w miarę rozwoju stawały się coraz bardziej skomplikowane, a nie coraz większe.
Rozwijanych symboli może być więcej, a dwa z nich będą miały znaczenie szczególne - będą odpowiadały za boczne odrosty. Każdy wie, że realistyczna forma roślinna musi mieć boczne gałązki, listki, kwiatki.
Niech symbol ( oznacza, że z pnia głównego roślinki gdzieś w bok wyrasta dodatkowa gałązka, a symbol ) niech kończy to odrastanie.
Teraz naszkicujmy drugą część algorytmu, odpowiadającą za wykreślanie graficznego obrazu rośliny, ukrytej w dopiero co rozwiniętej formule tekstowej. Po kolei przejrzymy wszystkie symbole i odpowiednio zamienimy je na kolorowe punkty czy linie. Spodziewamy się drobnych kłopotów w momencie realizacji odrostu bocznej gałązki - do jej wykreślenia użyjemy tego samego algorytmu, który kreśli główny pień. Mówiąc ściślej, algorytm kreślenia rośliny w momencie napotkania symbolu odrostu ( wywoła sam siebie, czyli będzie to algorytm rekurencyjny:
int POZYCJA = 1;
void rysuj(int x0, int y0, double kat, int dlg)
{
double dkat = 45;
int x1, y1;
char znak;
while( POZYCJA <= FORMULA.Length() )
{
znak = FORMULA[POZYCJA];
POZYCJA = POZYCJA + 1;
switch( znak)
{
case 'A': // gałązka A - odcinek biały
x1 = x0 + dlg * cos( kat * M_PI / 180.);
y1 = y0 + dlg * sin( kat * M_PI / 180.);
Line( x0, y0, x1, y1, White);
x0 = x1;
y0 = y1;
break;
case 'B': // gałązka B - odcinek czarny
x1 = x0 + dlg * cos( kat * M_PI / 180.);
y1 = y0 + dlg * sin( kat * M_PI / 180.);
Line( x0, y0, x1, y1, Black);
x0 = x1;
y0 = y1;
break;
case '(': // odrost
dkat = -dkat; // raz w lewo, raz w prawo
rysuj( x0, y0, kat + dkat, dlg);
break;
case ')': // koniec odrostu
return; // powrót na pień główny
}
}
}