//---------------------------------------------------------------------
// Ukazkovy priklad cislo 69
// Autor: Pavel Tisnovsky
//
// V tomto ukazkovem programu se provede nastaveni kamery v takzvanem
// perspektivnim rezimu, tj. rezimu, kdy se na stinitku meni vzdalenosti
// zadavanych vrcholu podle jejich hloubky.
// Pote se vykresli jednoduche teleso ve tvaru domecku, se kterym lze
// rotovat a pohybovat pomoci mysi. Domecek se vykresli jako vyplneny
// model. Pred domeckem jsou zobrazeny dve polopruhledne roviny, kazda
// ma jinou barvu a vzdalenost. Pomoci klavesy 'f' lze provest prepnuti
// do celeho okna, klavesou 'w' se provede nastaveni velikosti okna na
// puvodni velikost 500x500
// pixelu. Program lze ukoncit stlacenim klavesy 'q' nebo ESC.
// Pomoci klavesy 'e' lze zapnout funkci pameti hloubky, klavesou 'd' se
// pamet hloubky naopak zakaze. Klavesami '.' a ',' (tecka a carka) je
// mozne menit zorny uhel kamery.
//---------------------------------------------------------------------

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



int xnew=0, ynew=0, znew=0;                     // soucasna pozice mysi, ze ktere se pocitaji rotace a posuvy
int xold=0, yold=0, zold=0;                     // minula pozice mysi, ze ktere se pocitaji rotace a posuvy
int xx, yy, zz;                                 // bod, ve kterem se nachazi kurzor mysi
int stav;                                       // stav tlacitek mysi

float fov=70.0;                                 // hodnota zorneho uhlu - field of view
float nearPlane=0.1;                            // blizsi orezavaci rovina
float farPlane=90.0;                            // vzdalenejsi orezavaci rovina
int   windowWidth;                              // sirka okna
int   windowHeight;                             // vyska okna
int   depthBufferEnabled=1;                     // priznak, zda je povolena pamet hloubky



//---------------------------------------------------------------------
// Funkce pro inicializaci vykreslovani
//---------------------------------------------------------------------
void onInit(void)
{
    glClearColor(0.7f, 0.7f, 0.7f, 0.0f);       // barva pozadi obrazku
    glClearDepth(1.0f);                         // implicitni hloubka ulozena v pameti hloubky
    glEnable(GL_DEPTH_TEST);                    // povoleni funkce pro testovani hodnot v pameti hloubky
    glDepthFunc(GL_LESS);                       // funkce pro testovani fragmentu
    glShadeModel(GL_SMOOTH);                    // nastaveni stinovaciho rezimu
    glPolygonMode(GL_FRONT, GL_FILL);           // nastaveni rezimu vykresleni modelu
    glPolygonMode(GL_BACK, GL_FILL);            // jak pro predni tak pro zadni steny
    glDisable(GL_CULL_FACE);                    // zadne hrany ani steny se nebudou odstranovat
}



//---------------------------------------------------------------------
// 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)
    gluPerspective(fov, (double)w/(double)h, nearPlane, farPlane);// nastaveni perspektivni kamery
    windowWidth=w;                              // zapamatovat si velikost okna
    windowHeight=h;
}



//---------------------------------------------------------------------
// Tato funkce je volana pri kazdem prekresleni okna
//---------------------------------------------------------------------
void onDisplay(void)
{
    if (depthBufferEnabled)                     // podle povoleni pameti hloubky
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// vymazani barvoveho bufferu i pameti hloubky
    else
        glClear(GL_COLOR_BUFFER_BIT);           // vymazani vsech bitovych rovin barvoveho bufferu

    glMatrixMode(GL_MODELVIEW);                 // bude se menit modelova matice
    glLoadIdentity();                           // nahrat jednotkovou matici
                                                // zde se bude provadet nastaveni kamery
    glTranslatef(0.0f, 0.0f, -50.0f);           // posun objektu dale od kamery
    glTranslatef(0.0f, 0.0f, znew);             // priblizeni ci vzdaleni objektu podle pohybu kurzoru mysi
    glRotatef(ynew, 1.0f, 0.0f, 0.0f);          // rotace objektu podle pohybu kurzoru mysi
    glRotatef(xnew, 0.0f, 1.0f, 0.0f);

    glBegin(GL_QUADS);                          // vykresleni otevrene krychle - sten domecku
        glColor3f(0.2f, 0.2f, 0.8f); glVertex3f(-5.0f, -5.0f, -5.0f);
        glColor3f(0.2f, 0.8f, 0.2f); glVertex3f(-5.0f, -5.0f,  5.0f);
        glColor3f(0.2f, 0.8f, 0.8f); glVertex3f( 5.0f, -5.0f,  5.0f);
        glColor3f(0.8f, 0.2f, 0.2f); glVertex3f( 5.0f, -5.0f, -5.0f);

        glColor3f(0.8f, 0.2f, 0.8f); glVertex3f(-5.0f,  5.0f, -5.0f);
        glColor3f(0.8f, 0.8f, 0.2f); glVertex3f(-5.0f,  5.0f,  5.0f);
        glColor3f(0.8f, 0.8f, 0.8f); glVertex3f( 5.0f,  5.0f,  5.0f);
        glColor3f(0.2f, 0.2f, 0.2f); glVertex3f( 5.0f,  5.0f, -5.0f);

        glColor3f(0.2f, 0.8f, 0.2f); glVertex3f(-5.0f, -5.0f, -5.0f);
        glColor3f(0.2f, 0.8f, 0.8f); glVertex3f(-5.0f, -5.0f,  5.0f);
        glColor3f(0.8f, 0.2f, 0.2f); glVertex3f(-5.0f,  5.0f,  5.0f);
        glColor3f(0.8f, 0.2f, 0.8f); glVertex3f(-5.0f,  5.0f, -5.0f);

        glColor3f(0.2f, 0.8f, 0.2f); glVertex3f( 5.0f, -5.0f, -5.0f);
        glColor3f(0.2f, 0.8f, 0.8f); glVertex3f( 5.0f, -5.0f,  5.0f);
        glColor3f(0.8f, 0.2f, 0.2f); glVertex3f( 5.0f,  5.0f,  5.0f);
        glColor3f(0.8f, 0.2f, 0.8f); glVertex3f( 5.0f,  5.0f, -5.0f);
    glEnd();

    glBegin(GL_TRIANGLES);                      // vykresleni strechy domecku z trojuhelniku
        glColor3f(0.2f, 0.2f, 0.8f); glVertex3f(-5.0f,  5.0f, -5.0f);
        glColor3f(0.2f, 0.8f, 0.8f); glVertex3f( 5.0f,  5.0f, -5.0f);
        glColor3f(0.8f, 0.8f, 0.8f); glVertex3f( 0.0f, 11.0f,  0.0f);

        glColor3f(0.8f, 0.2f, 0.2f); glVertex3f( 5.0f,  5.0f, -5.0f);
        glColor3f(0.8f, 0.8f, 0.2f); glVertex3f( 5.0f,  5.0f,  5.0f);
        glColor3f(0.8f, 0.8f, 0.8f); glVertex3f( 0.0f, 11.0f,  0.0f);

        glColor3f(0.2f, 0.8f, 0.2f); glVertex3f( 5.0f,  5.0f,  5.0f);
        glColor3f(0.2f, 0.8f, 0.8f); glVertex3f(-5.0f,  5.0f,  5.0f);
        glColor3f(0.8f, 0.8f, 0.8f); glVertex3f( 0.0f, 11.0f,  0.0f);

        glColor3f(0.2f, 0.8f, 0.2f); glVertex3f(-5.0f,  5.0f,  5.0f);
        glColor3f(0.8f, 0.8f, 0.2f); glVertex3f(-5.0f,  5.0f, -5.0f);
        glColor3f(0.8f, 0.8f, 0.8f); glVertex3f( 0.0f, 11.0f,  0.0f);
    glEnd();

    glMatrixMode(GL_MODELVIEW);                 // bude se menit modelova matice
    glLoadIdentity();                           // nahrat jednotkovou matici
    glTranslatef(0.0f, 0.0f, -50.0f);           // posun objektu dale od kamery
    glDepthMask(GL_FALSE);                      // Z-buffer v rezimu read-only
    glEnable(GL_BLEND);                         // povoleni michani
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);// michaci funkce
    glBegin(GL_QUADS);                          // vykresleni dvou rovin
        glColor4f(1.0, 0.5, 0.5, 0.7);
        glVertex3f(-14.0, -8.0, 30.0);
        glVertex3f( -0.0, -8.0, 30.0);
        glVertex3f( -0.0,  8.0, 30.0);
        glVertex3f(-14.0,  8.0, 30.0);
        glColor4f(0.5, 0.5, 1.0, 0.7);
        glVertex3f(  0.0, -8.0, 25.0);
        glVertex3f( 14.0, -8.0, 25.0);
        glVertex3f( 14.0,  8.0, 25.0);
        glVertex3f(  0.0,  8.0, 25.0);
    glEnd();
    glDepthMask(GL_TRUE);                       // Z-buffer v rezimu read-write
    glDisable(GL_BLEND);                        // zakazani michani
    glFlush();                                  // provedeni a vykresleni vsech zmen
    glutSwapBuffers();                          // a prohozeni predniho a zadniho bufferu
}



//---------------------------------------------------------------------
// Tato funkce je volana pri stlaceni ASCII klavesy
//---------------------------------------------------------------------
void onKeyboard(unsigned char key, int x, int y)
{
    if (key>='A' && key<='Z')                   // uprava velkych pismen na mala
        key+='a'-'A';                           // pro zjednoduseni prikazu switch

    switch (key) {
        case 27:
        case 'q':
            exit(0);
            break;                              // pokud byla stlacena klavesa ESC, konec programu
        case 'f':
            glutFullScreen();                   // prepnuti na celou obrazovku
            break;
        case 'w':
            glutReshapeWindow(500, 500);        // prepnuti zpet do okna
            break;
        case 'e':                               // povoleni funkce pameti hloubky
            depthBufferEnabled=1;
            glEnable(GL_DEPTH_TEST);
            glutPostRedisplay();                // prekresleni sceny
            break;
        case 'd':                               // zakazani funkce pameti hloubky
            depthBufferEnabled=0;
            glDisable(GL_DEPTH_TEST);
            glutPostRedisplay();                // prekresleni sceny
            break;
        case '.':
            fov--;                              // zmenseni zorneho uhlu kamery
            glMatrixMode(GL_PROJECTION);        // zacatek modifikace projekcni matice
            glLoadIdentity();                   // vymazani projekcni matice (=identita)
            gluPerspective(fov, (double)windowWidth/(double)windowHeight, nearPlane, farPlane);
            glutPostRedisplay();                // prekresleni sceny
            break;
        case ',':
            fov++;                              // zvetseni zorneho uhlu kamery
            glMatrixMode(GL_PROJECTION);        // zacatek modifikace projekcni matice
            glLoadIdentity();                   // vymazani projekcni matice (=identita)
            gluPerspective(fov, (double)windowWidth/(double)windowHeight, nearPlane, farPlane);
            glutPostRedisplay();                // prekresleni sceny
            break;
        default:
            break;
    }
}



//---------------------------------------------------------------------
// Tato funkce je volana pri stisku ci pusteni tlacitka mysi
//---------------------------------------------------------------------
void onMouseButton(int button, int state, int x, int y)
{
    if (button==GLUT_LEFT_BUTTON) {             // pri zmene stavu leveho tlacitka
        if (state==GLUT_DOWN) {                 // pri stlaceni tlacitka
            stav=1;                             // nastaveni pro funkci motion
            xx=x;                               // zapamatovat pozici kurzoru mysi
            yy=y;
        }
        else {                                  // pri pusteni tlacitka
            stav=0;                             // normalni stav
            xold=xnew;                          // zapamatovat novy pocatek
            yold=ynew;
        }
    }
    if (button==GLUT_RIGHT_BUTTON) {            // pri zmene stavu praveho tlacitka
        if (state==GLUT_DOWN) {                 // pri stlaceni tlacitka
            stav=2;                             // nastaveni pro funkci motion
            zz=y;                               // zapamatovat pozici kurzoru mysi
        }
        else {                                  // pri pusteni tlacitka
            stav=0;
            zold=znew;                          // zapamatovat novy pocatek
        }
    }
    glutPostRedisplay();                        // prekresleni sceny
}



//---------------------------------------------------------------------
// Tato funkce je volana pri pohybu mysi se stlacenym tlacitkem.
// To, ktere tlacitko je stlaceno si musime predem zaznamenat do
// globalni promenne stav ve funkci onMouseButton()
//---------------------------------------------------------------------
void onMouseMotion(int x, int y)
{
    if (stav==1) {                              // stav rotace objektu
        xnew=xold+x-xx;                         // vypocitat novou pozici
        ynew=yold+y-yy;
        glutPostRedisplay();                    // a prekreslit scenu
    }
    if (stav==2) {                              // stav priblizeni/oddaleni objektu
        znew=zold+y-zz;                         // vypocitat novou pozici
        glutPostRedisplay();                    // a prekreslit scenu
    }
}



//---------------------------------------------------------------------
// Hlavni funkce konzolove aplikace
//---------------------------------------------------------------------
int main(int argc, char **argv)
{
    glutInit(&argc, argv);                      // inicializace knihovny GLUT
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);// nastaveni dvou barvovych bufferu a pameti hloubky
    glutInitWindowPosition(30, 30);             // pocatecni pozice leveho horniho rohu okna
    glutInitWindowSize(500, 500);               // pocatecni velikost okna
    glutCreateWindow("Priklad na OpenGL cislo 69");// vytvoreni okna pro kresleni
    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
    glutMouseFunc(onMouseButton);               // registrace funkce volane pri stlaceni ci pusteni tlacitka
    glutMotionFunc(onMouseMotion);              // registrace funkce volane pri pohybu mysi se stlacenym tlacitkem
    onInit();                                   // inicializace vykreslovani
    glutMainLoop();                             // nekonecna smycka, kde se volaji zaregistrovane funkce
    return 0;                                   // navratova hodnota vracena operacnimu systemu
}



//---------------------------------------------------------------------
// Konec zdrojoveho souboru
//---------------------------------------------------------------------