//-----------------------------------------------------------------------------
// Fraktaly v pocitacove grafice
// Demonstracni priklad 54.1
// Autor: Pavel Tisnovsky
//
// Vykresleni krivky podobne Sierpinskemu trojuhelniku na zaklade L-systemu,
// ve kterem je provaden paralelni prepis retezcu.
// Po prekladu a spusteni programu se nejprve provede nekolikere rozepsani
// gramatiky. Vysledny retezec je posleze pouzit pro vykresleni L-systemu
// za pomoci zelvi grafiky (turtle graphics).
// Zmena velikosti fraktalu se provadi stiskem klaves [PageUp] a [PageDown],
// posun fraktalu je mozne provest kurzorovymi klavesami (sipkami).
// Zmena poctu prepisu symbolu se provadi pomoci klaves [1]-[3], pricemz
// po stlaceni klavesy [1] je proveden pouze jeden prepis axiomu.
// Ukonceni aplikace se provede klavesou [Esc] nebo klavesou [Q].
//-----------------------------------------------------------------------------

#ifdef __BORLANDC__
#include <windows.h>
#endif

#include <GL/glut.h>                            // hlavickovy soubor funkci GLUTu a OpenGL
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define WINDOW_TITLE    "Fraktaly 54.1"         // titulek okna
#define WINDOW_WIDTH    400                     // pocatecni velikost okna
#define WINDOW_HEIGHT   470

#define MAX_LENGTH     116384                   // maximalni delka retezce

double xpos=0.0;                                // pozice fraktalu v okne
double ypos=0.87*WINDOW_WIDTH/3;
char   retx[MAX_LENGTH];                        // pocatecni symbol
int    itersCount=1;                            // pocet aplikaci prepisovaciho pravidla

char  *rules[]={                                // jednotliva prepisovaci pravidla
    "X=YF+XF+Y",
    "Y=XF-YF-X",
    NULL
};

char   axiom[]="YF";                            // axiom - prvotni naplneni vysledneho retezce

double step=10;;                                // krok zelvy
double x, y, alpha;                             // souradnice a natoceni zelvy
double delta=3.1415927/3.0;



//-----------------------------------------------------------------------------
// Aplikace prepisovacich pravidel na retezec
//-----------------------------------------------------------------------------
void applyRules(
        char *rules[],                          // pole prepisovacich pravidel
        char *axiom,                            // axiom - prvotni naplneni retezce
        char *ret,                              // retezec, do ktereho se ma ulozit vysledek
        int maxiters)                           // maximalni pocet iteraci (aplikaci pravidel)
{
    int rulesCount;                             // pocet pravidel
    int rule;                                   // prave aplikovane pravidlo
    char *leftSide;                             // pole levych casti prepisovacich pravidel
    char **rightSideSrc;                        // pole pravych casti prepisovacich pravidel
    char **rightSideDest;
    int i, j, k, iter;                          // pocitadla smycek a indexy znaku

    // zjistit celkovy pocet prepisovacich pravidel
    for (rulesCount=0; rules[rulesCount]; rulesCount++)
        ;
    // nyni mame v promenne rulesCount ulozen pocet prepisovacich pravidel
    printf("celkovy pocet pravidel=%d\n", rulesCount);

    // alokace pameti pro levou stranu prepisovacich pravidel
    // a inicializace pole znaku
    leftSide=(char *)malloc(rulesCount*sizeof(char));
    for (i=0; i<rulesCount; i++)
        leftSide[i]=rules[i][0];

    // alokace pameti pro pravou stranu prepisovacich pravidel
    // a inicializace pravych stran
    rightSideSrc=(char **)malloc(rulesCount*sizeof(char *));
    rightSideDest=(char **)malloc(rulesCount*sizeof(char *));
    for (i=0; i<rulesCount; i++) {
        rightSideSrc[i]=(char *)malloc(MAX_LENGTH);
        rightSideDest[i]=(char *)malloc(MAX_LENGTH);
        strcpy(rightSideSrc[i], rules[i]+2); // podretezec za znakem '='
    }

    // nastaveni axiomu
    strcpy(ret, axiom);

    // hlavni iteracni smycka
    for (iter=0; iter<=maxiters; iter++) {
        printf("iteration=%d\n", iter);
        // pro vsechna pravidla
        for (rule=0; rule<rulesCount; rule++) {
            // vypis pravidla pred prepisem
            printf("%c\t%s\t", leftSide[rule], rightSideSrc[rule]);
            // projit celym retezcem a zpracovat jednotlive znaky
            for (i=0, j=0; rightSideSrc[rule][i]; i++) {
                int left;
                int found=0;
                // pro kazdy znak zjistit, zda pro nej neexistuje prepisovaci pravidlo
                // a pokud ano, provest prepis
                for (left=0; left<rulesCount; left++) {
                    if (leftSide[left]==rightSideSrc[rule][i]) {
                        for (k=0; rightSideSrc[left][k]; k++)          // provest prepis
                            rightSideDest[rule][j++]=rightSideSrc[left][k];
                        found=1;
                    }
                }
                // zadne pravidlo pro dany znak jsme nenasli, proto se znak
                // pouze zkopiruje
                if (!found) {
                    rightSideDest[rule][j]=rightSideSrc[rule][i];
                    j++;
                }
            }
            // ukonceni retezce
            rightSideDest[rule][j]=0;
            // vypis pravidla po prepisu
            printf("%s\n", rightSideDest[rule]);
        }
        // "paralelni" prepis novych retezcu na misto stavajicich
        // (lezi mimo predchozi smycku!)
        for (rule=0; rule<rulesCount; rule++)
            strcpy(rightSideSrc[rule], rightSideDest[rule]);
    }

    // prepis vysledku
    strcpy(ret, rightSideSrc[1]);
    // a doplneni posledniho posunu zelvy
    strcat(ret, "F");
    puts("");
    // vypis retezce v podobe, kdy jsou vypsany pouze znaky ovladajici zelvu
    for (i=0; i<strlen(ret); i++)
        if (ret[i]=='F' || ret[i]=='+' || ret[i]=='-') putchar(ret[i]);
}



//-----------------------------------------------------------------------------
// Inicializace L-systemu
//-----------------------------------------------------------------------------
void initLSystem(int maxiters)
{
    // aplikace prepisovacich pravidel
    applyRules(rules, axiom, retx, maxiters);
}



//-----------------------------------------------------------------------------
// Nastaveni zelvy do pocatecni (domaci) pozice
//-----------------------------------------------------------------------------
void logo_home(double xpos, double ypos)
{
    x=xpos;
    y=ypos;
    alpha=0.0;
}



//-----------------------------------------------------------------------------
// Posun zelvy dopredu s kreslenim
//-----------------------------------------------------------------------------
void logo_forward(void)
{
    glBegin(GL_LINES);
    glVertex2d(x,y);
    x+=step*cos(alpha);                         // posun v zadanem smeru
    y+=step*sin(alpha);
    glVertex2d(x,y);
    glEnd();
}



//-----------------------------------------------------------------------------
// Posun zelvy dozadu s kreslenim
//-----------------------------------------------------------------------------
void logo_backward(void)
{
    glBegin(GL_LINES);
    glVertex2d(x,y);
    x-=step*cos(alpha);                         // posun v zadanem smeru
    y-=step*sin(alpha);
    glVertex2d(x,y);
    glEnd();
}



//-----------------------------------------------------------------------------
// Posun zelvy dopredu bez kresleni
//-----------------------------------------------------------------------------
void logo_move(void)
{
    x+=step*cos(alpha);                         // posun v zadanem smeru
    y+=step*sin(alpha);
}



//-----------------------------------------------------------------------------
// Otoceni zelvy doleva
//-----------------------------------------------------------------------------
void logo_left(void)
{
    alpha+=delta;                               // zmena uhlu
}



//-----------------------------------------------------------------------------
// Otoceni zelvy doprava
//-----------------------------------------------------------------------------
void logo_right(void)
{
    alpha-=delta;                               // zmena uhlu
}



//-----------------------------------------------------------------------------
// Prekresleni L-systemu
//-----------------------------------------------------------------------------
void recalcLsys(const char *ret,                // ridici retezec
                double xpos,                    // posun obrazce
                double ypos)
{
    int i;
    logo_home(xpos, ypos);                      // inicializace zelvy
    for (i=0; ret[i]; i++) {                    // projit celym retezcem
        switch (ret[i]) {                       // a reagovat na prikazy
            case 'F':                           // posun v zadanem smeru
                logo_forward();
                break;
            case 'B':                           // zpetny posun v zadanem smeru
                logo_backward();
                break;
            case 'G':                           // posun v zadanem smeru bez kresleni
                logo_move();
                break;
            case '+':                           // zmena uhlu
                logo_left();
                break;
            case '-':                           // zmena uhlu
                logo_right();
                break;
            default:
                break;
        }
    }
}



//-----------------------------------------------------------------------------
// Funkce volana pro inicializaci vykreslovani
//-----------------------------------------------------------------------------
void onInit(void)
{
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);       // barva pozadi
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);      // mod ulozeni pixelu
    initLSystem(itersCount);
}



//-----------------------------------------------------------------------------
// Nastaveni souradneho systemu v zavislosti na velikosti okna
//-----------------------------------------------------------------------------
void onResize(int w, int h)                     // argumenty w a h reprezentuji novou velikost okna
{
    glViewport(0, 0, w, h);                     // viditelna oblast pres cele okno
    glMatrixMode(GL_PROJECTION);                // zacatek modifikace projekcni matice
    glLoadIdentity();                           // vymazani projekcni matice (=identita)
    glOrtho(0, w, 0, h, -1, 1);                 // mapovani abstraktnich souradnic do souradnic okna
}



//-----------------------------------------------------------------------------
// Tato callback funkce je zavolana pri kazdem prekresleni okna
//-----------------------------------------------------------------------------
void onDisplay(void)
{
    glClear(GL_COLOR_BUFFER_BIT);               // vymazani vsech bitovych rovin barvoveho bufferu
    glDrawBuffer(GL_BACK);                      // pixmapa se bude kreslit do zadniho barvoveho bufferu
    recalcLsys(retx, xpos, ypos);               // prekresleni L-systemu
    glFlush();                                  // provedeni a vykresleni vsech zmen
    glutSwapBuffers();
}



//-----------------------------------------------------------------------------
// Tato callback funkce je zavolana pri stlaceni ASCII klavesy
//-----------------------------------------------------------------------------
#ifdef __BORLANDC__
#pragma option -w-par
#endif
void onKeyboard(unsigned char key, int x, int y)
{
    key=(key>='A' && key<='Z') ? key-'A'+'a': key;
    // zmena poctu prepsani retezce
    if (key>='1' && key<='3') {
        itersCount=key-'1';
        // zmena kroku v zavislosti na poctu prepisu
        //step=WINDOW_WIDTH/(pow(5, itersCount));
        initLSystem(itersCount);
        glutPostRedisplay();
        return;
    }
    switch (key) {
        case 27:                                // pokud byla stlacena klavesa ESC, konec programu
        case 'q': exit(0);               break; // totez co klavesa ESC
        default:                         break;
    }
}
#ifdef __BORLANDC__
#pragma option -w+par
#endif



//-----------------------------------------------------------------------------
// Tato callback funkce je zavolana pri stlaceni non-ASCII klavesy
//-----------------------------------------------------------------------------
#ifdef __BORLANDC__
#pragma option -w-par
#endif
void onSpecial(int key, int x, int y)
{
    // posun fraktalu a zmena meritka
    switch (key) {
        case GLUT_KEY_LEFT:      xpos-=5;   glutPostRedisplay(); break;
        case GLUT_KEY_RIGHT:     xpos+=5;   glutPostRedisplay(); break;
        case GLUT_KEY_UP:        ypos+=5;   glutPostRedisplay(); break;
        case GLUT_KEY_DOWN:      ypos-=5;   glutPostRedisplay(); break;
        case GLUT_KEY_PAGE_UP:   step+=0.5; glutPostRedisplay(); break;
        case GLUT_KEY_PAGE_DOWN: step-=0.5; glutPostRedisplay(); break;
        default:                                                                                 break;
    }
}
#ifdef __BORLANDC__
#pragma option -w+par
#endif



//-----------------------------------------------------------------------------
// Hlavni funkce konzolove aplikace
//-----------------------------------------------------------------------------
int main(int argc, char **argv)
{
    glutInit(&argc, argv);                      // inicializace knihovny GLUT
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
    glutCreateWindow(WINDOW_TITLE);             // vytvoreni okna pro kresleni
    glutReshapeWindow(WINDOW_WIDTH, WINDOW_HEIGHT);// zmena velikosti okna
    glutPositionWindow(100, 100);               // pozice leveho horniho rohu okna
    glutDisplayFunc(onDisplay);                 // registrace funkce volane pri prekreslovani okna
    glutReshapeFunc(onResize);                  // registrace funkce volane pri zmene velikosti okna
    glutKeyboardFunc(onKeyboard);               // registrace funkce volane pri stlaceni klavesy
    glutSpecialFunc(onSpecial);                 // registrace funkce volane pri stlaceni specialni klavesy
    onInit();                                   // inicializace vykreslovani
    glutMainLoop();                             // nekonecna smycka, kde se volaji zaregistrovane funkce
    return 0;                                   // navratova hodnota vracena operacnimu systemu
}



//-----------------------------------------------------------------------------
// finito
//-----------------------------------------------------------------------------