InfoC adventi naptár

Teknőcgrafika

A programozásban sokszor egy probléma megoldásának úgy érdemes nekiindulni, hogy kitalálunk egy új nyelvet, amellyel (amelyen) a megoldást könnyebben megfogalmazhatjuk. Gondoljunk bele: ha a kitalált nyelv szavait, kifejezéseit lefordítjuk C nyelvre (pl. C- beli függvényhívásokra), akkor a C nyelv lehetőségeit (ciklusok, változók, függvények stb.) automatikusan kapjuk melléjük kiterjesztésként. Vagyis a kitalált nyelvünknek automatikusan részévé válnak a C nyelv kombinációs és absztrakciós lehetőségei.

Ilyen kitalált nyelv a teknőcgrafika, amely sokak számára Comenius Logo néven lehet ismerős. Képzeljük el, hogy egy teknős sétál a képernyőn. Van nála egy ceruza, amellyel vagy húz vonalat séta közben, vagy nem. A következő utasításokat kaphatja:

Ezen a nyelven egy négyzet rajzolása így fogalmazható meg:

Tedd le a ceruzát.
Sétálj előre 5 centit.
Fordulj balra 90 fokkal.
Sétálj előre 5 centit.
Fordulj balra 90 fokkal.
Sétálj előre 5 centit.
Fordulj balra 90 fokkal.
Sétálj előre 5 centit.

Ha megírjuk az alap teknőcműveleteket C-ben, akkor annak képességei kiterjesztik a teknőcgrafika képességeit is. A fenti példában az ismétlés lesz a C for() ciklusára cserélhető:

Tedd le a ceruzát.
for (i=0; i<4; ++i) {
   Sétálj előre 5 centit.
   Fordulj balra 90 fokkal.
}

Innentől kezdve pikk-pakk megy minden, használhatunk változókat, ciklusokat, írhatunk függvényeket, amelyek bonyolultabb műveleteket végeznek el és így tovább. Együtt használhatjuk a C nyelv adta lehetőségeket a grafikus nyelvünkkel. A fenti négyzet könnyen általánosítható akárhány oldalú szabályos sokszögre, ha nem 360/4=90 fokot fordulunk, hanem 360/n fokot, ahol n a csúcsok száma. Erre is írhatunk C függvényt:

void sokszög(int csúcsok, double oldalhossz) {
   int i;
   for (i=0; i<csúcsok; ++i) {
      Sétálj előre oldalhossz centit.
      Fordulj balra 360/csúcsok fokot.
   }
}

1 A teknőc implementálása

A rajzolás megvalósításához az SDL függvénykönyvtárat használja a letölthető program. Ez egy viszonylag egyszerű, játékok írásához kifejlesztett függvénykönyvtár. A telepítéséről és használatáról az Érdekességek menü SDL menüpontja alatt már írtunk.

typedef struct Teknoc {
   double x, y;   /* pozicio */
   double szog;   /* szog */
   int rajzol;    /* rajzol-e mozgas kozben */
   Uint32 szin;   /* szin */
} Teknoc;

A teknőc a következő adatokkal rendelkezik, amelyeket egy struktúrában tárolhatunk:

Az egyes alap lépésekhez mind érdemes függvényeket írni, amelyek első paraméterükként átveszik azt a teknőcöt, amellyel dolgoznak. Így ingyen kapjuk azt a lehetőséget is a teknőcgrafikánkat most megalapozó C-ből, hogy több teknőcöt hozzunk létre, amelyek egymástól függetlenül tudnak rajzolni.

A függvények implementálása egyszerű. A legbonyolultabb függvény lent látható, és az előrefelé sétálást valósítja meg. (Ez is csak néhány sorból áll.) Ebben kapunk egy hosszt – ennyit kell a teknőcnek előre mennie. Az elmozdulás irányát az adja meg, hogy éppen merre néz. A képernyőn ennek komponensei az r·cos(szög) és r·sin(szög) képletekkel számolhatóak. A math.h szögfüggvényeinek a szöget radiánban kell megadnunk. A lenti kódrészletben a line() az SDL függvénye, és egy szép, „simított” szakaszt rajzol. Első paraméterével adjuk meg, hogy hova rajzoljon – screen azt jelenti, hogy közvetlenül a képernyőre. A további paraméterei a kezdő- és végpontok, illetve a szín.

void teknoc_elore(Teknoc *t, double r) {
   double const deg2rad=3.14159265358979323846/180.0;
   double ujx=t->x+r*cos(t->szog*deg2rad),
          ujy=t->y-r*sin(t->szog*deg2rad);
   if (t->rajzol)
      aalineColor(screen, t->x, t->y, ujx, ujy, t->szin);
   t->x=ujx;
   t->y=ujy;
}

A letölthető kódban a további megvalósított függvények az alábbiak:

/* inicializal egy teknocot: kepernyo kozepe, jobbra nez, nem rajzol */
   void teknoc_init(Teknoc *t);
/* beallitja a ceruza szinet, rgb: voros, zold, kek komponensek 0..255 */
   void teknoc_szin(Teknoc *t, Uint8 r, Uint8 g, Uint8 b);
/* leteszi a ceruzat */
   void teknoc_rajzol(Teknoc *t);
/* felemeli a ceruzat */
   void teknoc_nemrajzol(Teknoc *t);
/* balra fordul valahany fokkal */
   void teknoc_fordul(Teknoc *t, double dszog);
/* teleportal az adott kepernyokoordinatakra es iranyba */
   void teknoc_teleport(Teknoc *t, double ujx, double ujy, double ujszog);

2 Fraktálok

Fraktálok ismétlődő részekből álló görbék, minták, amelyek dimenziószáma nem egész szám. (Vigyázat: nem ez a pontos, matematikai definíció.) Mit jelent ez? A pontokról tudjuk, hogy nincs kiterjedésük: nulla dimenziósak. Egy görbének hossza van: egy dimenziós. Egy síkidomnak két irányban van kiterjedése, ezért két dimenziós, míg egy testnek három irányba. A fraktáloknál nem ilyen egyszerű a helyzet.

Nézzük meg egy kicsit jobban az ábrát. Ez a következőképpen keletkezik. Először adott egy szakasz (legyen ez egységnyi hosszú). Ezt a szakaszt elharmadoljuk, és a középső harmadot kicseréljük két egyharmad hosszú szakaszra (minta egy egyenlő oldalú háromszög lenne). Az így kapott töröttvonal hossza 4×1/3, azaz 4/3 lesz, ami az eredeti szakasz hosszának 4/3-adszorosa.

Ha ezt a műveletet megismételjük minden 1/3 hosszú szakaszra, a keletkező töröttvonalak hossza 4/3×1/3 lesz, és mivel összesen 4 van belőlük – az összegzet hosszuk 4/3×4/3, tehát (4/3)2. Így haladva minden lépésben a hossz az előző 4/3-szorosa lesz, tehát L=(4/3)n az n-edik lépés után. Ez nem korlátos: n növelésével a végtelenbe tart! Miért érdekes ez annyira? Azért, mert az ábra mérete viszont korlátos. Könnyen rajzolhatunk egy téglalapot, amelybe belefér, tehát az elfoglalt területe korlátos, a töröttvonal hossza mégsem az! Mivel csak egy vonalról van szó, nem mondhatjuk, hogy két dimenziós. Azt sem, hogy egy dimenziós, mivel a hossza bármely két tetszőleges pontja között is végtelen.

3 Rekurzív rajzok

Mivel a fenti, teknőcös függvényeket használhatjuk a saját, C-ben megírt rekurzív függvényünkben, így ezek a rajzok könnyen elkészíthetőek. Az előbb leírt görbéhez, Koch görbéjéhez az alábbi rekurzív algoritmus vezet:

A fentiek C-ben, vagy éppen teknőcgrafikában, kinek hogy tetszik:

void fraktal(Teknoc *t, int mennyi, double mekkora) {
   if (mennyi==0)
      teknoc_elore(t, mekkora);
   else {
      fraktal(t, mennyi-1, mekkora);
      teknoc_fordul(t, 60);
      fraktal(t, mennyi-1, mekkora);
      teknoc_fordul(t, -120);
      fraktal(t, mennyi-1, mekkora);
      teknoc_fordul(t, 60);
      fraktal(t, mennyi-1, mekkora);
   }
}

Ha egy hatszög oldalait cseréljük ilyen rajzokra, akkor egy hópelyhet kapunk. Kifordítva a rajzokat megkapjuk Koch hópelyhét is. Rengeteg ilyen tulajdonságú görbét lehet találni: David Hilbert térkitöltő görbéje is ilyen. Ezeket mind nagyon könnyű teknőcgrafikával, és az azt most megvalósító C nyelv egyik eszközével – a rekurzióval – megrajzolni.

/* hópelyhet rajzol */
for (i=0; i<6; ++i) {
   fraktal(&t, 2, 10);
   teknoc_fordul(&t, 60);
}

4 A letölthető program

A program letölthető innen: advent8-teknoc.c. A lefordításhoz az SDL című írás ad segítséget (lásd az Érdekességek menüpontot – Windowshoz és Linuxhoz).