//---------------------------------------------------------------------
// Ukazkovy priklad cislo 77
// Autor: Pavel Tisnovsky
//
// Po spusteni tohoto demonstracniho programu se zobrazi cajova konvicka,
// na jejimz povrchu je nanesena jednoducha 2D textura. Pri vykreslovani
// konvicky se pocita s osvetlenim, pricemz je nanasena textura vypoctenym
// svetlem modulovana. Textura neni na teleso nanesena primo, ale je pozit
// tzv. environment mapping -- mapovani okolniho prostredi.
// Pomoci leveho tlacitka mysi lze telesem otacet, prave tlacitko
// slouzi k priblizeni nebo vzdaleni telesa od kamery.
//---------------------------------------------------------------------

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

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

#define TEXTURE_WIDTH   64                          // sirka textury zadana v texelech
#define TEXTURE_HEIGHT  64                          // vyska textury zadana v texelech

#define WINDOW_WIDTH    450                         // velikost okna
#define WINDOW_HEIGHT   450
#define WINDOW_TITLE    "Priklad na OpenGL cislo 77"// titulek okna

enum {                                              // operace, ktere se mohou provadet s mysi:
    ROTATE,                                         // rotace objektu
    TRANSLATE,                                      // posun objektu
} operation=ROTATE;

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

int   windowWidth;                                  // sirka okna
int   windowHeight;                                 // vyska okna

unsigned char texture[TEXTURE_HEIGHT][TEXTURE_WIDTH][4];// pole pro ulozeni pixmapy textury

// parametry, ktere ovlivnuji osvetleni
GLfloat materialAmbient[]={0.3f, 0.3f, 0.3f, 1.0f}; // ambientni slozka barvy materialu
GLfloat materialDiffuse[]={0.8f, 0.4f, 0.4f, 1.0f}; // difuzni slozka barvy materialu
GLfloat materialSpecular[]={1.0f, 1.0f, 1.0f, 1.0f};// barva odlesku
GLfloat materialShininess[]={30.0f};                // faktor odlesku
GLfloat lightPosition[]={1.0f, 1.0f, 1.0f, 0.0f};   // pozice svetla
GLfloat lightAmbient[]={1.0f, 1.0f, 1.0f, 1.0f};    // ambientni slozka svetla



//---------------------------------------------------------------------
// Vytvoreni rastroveho vzorku pro texturu
//---------------------------------------------------------------------
void makeRasterTexture(void)
{
    int i,j,c;                                      // pocitadla smycek
    unsigned char * P;                              // ukazatel na zapisovany subtexel

    for (j=0; j<TEXTURE_HEIGHT; j++) {              // pro vsechny radky pixmapy
        P=texture[j][0];
        for (i=0; i<TEXTURE_WIDTH; i++) {           // pro vsechny texely na radku
            c=((((i&0x20)==0)^((j&0x20))==0)) * 255;
            *P++=(unsigned char)c;                  // zapsat barevne slozky RGB
            *P++=(unsigned char)c;
            *P++=(unsigned char)c;
            *P++=(unsigned char)255;                // alfa slozka texelu
        }
    }
}



//---------------------------------------------------------------------
// Nastaveni parametru textury
//---------------------------------------------------------------------
void setTextureParameters(void)
{
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);          // zpusob ulozeni bytu v texture
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);      // opakovani textury
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);  // volba filtru
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);  // pri zmene meritka
    glTexImage2D(GL_TEXTURE_2D, 0, 4,               // nahrani rastrovych dat do textury
                 TEXTURE_WIDTH, TEXTURE_HEIGHT,
                 0, GL_RGBA, GL_UNSIGNED_BYTE, texture);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);// nastaveni rezimu textury
    glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);// korektni mapovani textur
    glEnable(GL_TEXTURE_2D);                        // povoleni texturovani
    glTexGeni(GL_S, GL_SPHERE_MAP, 0);              // nastaveni environment mappingu
    glTexGeni(GL_T, GL_SPHERE_MAP, 0);              // v obou smerech
    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);
}



//---------------------------------------------------------------------
// Nastaveni parametru svetla
//---------------------------------------------------------------------
void setLightParameters(void)
{
    glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);// nastaveni pozice svetla
    glLightfv(GL_LIGHT0, GL_AMBIENT, lightAmbient);
    glEnable(GL_LIGHTING);                          // globalni povoleni stinovani
    glEnable(GL_LIGHT0);                            // povoleni prvniho svetla
}



//---------------------------------------------------------------------
// Nastaveni parametru materialu telesa
//---------------------------------------------------------------------
void setMaterialParameters(void)
{
    glMaterialfv(GL_FRONT, GL_AMBIENT, materialAmbient);    // nastaveni ambientni slozky barvy materialu
    glMaterialfv(GL_FRONT, GL_DIFFUSE, materialDiffuse);    // nastaveni difuzni slozky barvy materialu
    glMaterialfv(GL_FRONT, GL_SPECULAR, materialSpecular);  // nastaveni barvy odlesku
    glMaterialfv(GL_FRONT, GL_SHININESS, materialShininess);// nastaveni faktoru odlesku
}



//---------------------------------------------------------------------
// Funkce pro inicializaci vykreslovani
//---------------------------------------------------------------------
void onInit(void)
{
    glClearColor(0.5f, 0.5f, 0.5f, 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
    makeRasterTexture();                            // vytvoreni vzorku v texure
    setTextureParameters();                         // nastaveni parametru textury
    setLightParameters();                           // nastaveni parametru osvetleni
    setMaterialParameters();                        // nastaveni parametru materialu telesa
}



//---------------------------------------------------------------------
// 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
    windowWidth=w;                                  // zapamatovat si velikost okna
    windowHeight=h;
}



//---------------------------------------------------------------------
// Nastaveni perspektivni projekce
//---------------------------------------------------------------------
void setPerspectiveProjection(void)
{
    glMatrixMode(GL_PROJECTION);                    // zacatek modifikace projekcni matice
    glLoadIdentity();                               // vymazani projekcni matice (=identita)
    gluPerspective(50.0, (double)windowWidth/(double)windowHeight, 0.1f, 90.0f);// nastaveni perspektivni kamery
    glMatrixMode(GL_MODELVIEW);                     // bude se menit modelova matice
    glLoadIdentity();                               // nahrat jednotkovou matici
}



//--------------------------------------------------------------------
// Tato funkce je volana pri kazdem prekresleni okna
//--------------------------------------------------------------------
void onDisplay(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// vymazani barvoveho bufferu i pameti hloubky
    setPerspectiveProjection();                     // nastaveni perspektivni 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);
    glutSolidTeapot(7.0f);                          // vykresleni cajove konvicky
    glFlush();                                      // provedeni a vykresleni vsech zmen
    glutSwapBuffers();                              // a prohozeni predniho a zadniho bufferu
}



//---------------------------------------------------------------------
// Tato funkce je volana pri stlaceni ASCII klavesy
//---------------------------------------------------------------------
#ifdef __BORLANDC__
#pragma option -w-par
#endif
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:    exit(0);            break;      // ukonceni aplikace
        case 'q':   exit(0);            break;      // ukonceni aplikace
        default:                        break;
    }
}
#ifdef __BORLANDC__
#pragma option -w+par
#endif



//---------------------------------------------------------------------
// 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
        operation=ROTATE;
        if (state==GLUT_DOWN) {                     // pri stlaceni tlacitka
            xx=x;                                   // zapamatovat pozici kurzoru mysi
            yy=y;
        }
        else {                                      // pri pusteni tlacitka
            xold=xnew;                              // zapamatovat novy pocatek
            yold=ynew;
        }
        glutPostRedisplay();                        // prekresleni sceny
    }
    if (button==GLUT_RIGHT_BUTTON) {
        operation=TRANSLATE;
        if (state==GLUT_DOWN) zz=y;                 // pri stlaceni tlacitka zapamatovat polohu kurzoru mysi
        else zold=znew;                             // pri pusteni tlacitka 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)
{
    switch (operation) {
        case ROTATE:                                // stav rotace objektu
            xnew=xold+x-xx;                         // vypocitat novou pozici
            ynew=yold+y-yy;
            glutPostRedisplay();                    // a prekreslit scenu
            break;
        case TRANSLATE:                             // stav priblizeni/oddaleni objektu
            znew=zold+y-zz;                         // vypocitat novou pozici
            glutPostRedisplay();                    // a prekreslit scenu
            break;
    }
}



//---------------------------------------------------------------------
// 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(WINDOW_WIDTH, WINDOW_HEIGHT);// pocatecni velikost okna
    glutCreateWindow(WINDOW_TITLE);                 // 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
//---------------------------------------------------------------------