//---------------------------------------------------------------------
// Ukazkovy priklad k serii clanku OpenGL a GLU
//
// Autor:          Pavel Tisnovsky
// Cislo clanku:   17
// Cislo prikladu: 1
//
// Tento program po svem spusteni zobrazi teleso s nanesenou 2D texturou.
// Textura je vytvorena jako mipmapa se zakladnim rozlisenim 64x64 texelu.
//
// Pri vykreslovani zmenseneho telesa se vyberou dve nejblizsi textury
// z mipmapy a z techto se linearni interpolaci vypoctou dve barvy.
// Vysledna barva je vycislena jako dalsi  linearni interpolace techto
// dvou barev. Pri zvetsovani vykresleneho telesa se pouziva linearni
// interpolace nejblizsich texelu v texture.
//
// Proto jsou pouzity filtry:
//  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
// Pomoci leveho tlacitka mysi lze telesem otacet, prave tlacitko
// slouzi k priblizeni nebo vzdaleni telesa od kamery.
//---------------------------------------------------------------------

#include <stdio.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

#define WINDOW_WIDTH    450                         // velikost okna
#define WINDOW_HEIGHT   450
#define WINDOW_TITLE    "OpenGL a GLU, priklad 17.1"// titulek okna

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



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

int   xnew=0, ynew=0, znew=30;                  // soucasna pozice mysi, ze ktere se pocitaji rotace a posuvy
int   xold=0, yold=0, 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 texture0[TEXTURE_HEIGHT][TEXTURE_WIDTH][3];         // pole pro RGB texturu v plnem rozliseni
unsigned char texture1[TEXTURE_HEIGHT >>1][TEXTURE_WIDTH >>1][3]; // pole pro RGB texturu na prvni urovni v mipmape
unsigned char texture2[TEXTURE_HEIGHT >>2][TEXTURE_WIDTH >>2][3]; // pole pro RGB texturu na druhe urovni v mipmape
unsigned char texture3[TEXTURE_HEIGHT >>3][TEXTURE_WIDTH >>3][3]; // pole pro RGB texturu na treti urovni v mipmape
unsigned char texture4[TEXTURE_HEIGHT >>4][TEXTURE_WIDTH >>4][3]; // pole pro RGB texturu na ctvrte urovni v mipmape
unsigned char texture5[TEXTURE_HEIGHT >>5][TEXTURE_WIDTH >>5][3]; // pole pro RGB texturu na pate urovni v mipmape
unsigned char texture6[TEXTURE_HEIGHT >>6][TEXTURE_WIDTH >>6][3]; // pole pro RGB texturu na seste urovni v mipmape

GLuint   textureName;                           // jmeno textury



//---------------------------------------------------------------------
// Vytvoreni rastroveho vzorku pro textury v mipmape
//---------------------------------------------------------------------
void makeRasterTexture(void)
{
    int i,j,c;                                  // pocitadla smycek
    unsigned char * P;                          // ukazatel na zapisovany subtexel
                                                // textura na urovni 0 v mipmape
    for (j=0; j<TEXTURE_HEIGHT; j++) {          // pro vsechny radky pixmapy
        P=texture0[j][0];                       // prvni pixel na radku j
        for (i=0; i<TEXTURE_WIDTH; i++) {       // pro vsechny pixely na radku pixmapy
            c=((((i&0x10)==0)^((j&0x10))==0)) * 255;
            *P++=(unsigned char)c;              // cernobila sachovnice
            *P++=(unsigned char)c;
            *P++=(unsigned char)c;
        }
    }
                                                // textura na urovni 1 v mipmape
    for (j=0; j<TEXTURE_HEIGHT >> 1; j++) {     // pro vsechny radky pixmapy
        P=texture1[j][0];                       // prvni pixel na radku j
        for (i=0; i<TEXTURE_WIDTH >> 1; i++) {  // pro vsechny pixely na radku pixmapy
            c=((((i&0x08)==0)^((j&0x08))==0)) * 255;
            *P++=(unsigned char)c;
            *P++=(unsigned char)c;
            *P++=(unsigned char)0;              // jednu barvu vynechame
        }
    }
                                                // textura na urovni 2 v mipmape
    for (j=0; j<TEXTURE_HEIGHT >> 2; j++) {     // pro vsechny radky pixmapy
        P=texture2[j][0];                       // prvni pixel na radku j
        for (i=0; i<TEXTURE_WIDTH >> 2; i++) {  // pro vsechny pixely na radku pixmapy
            c=((((i&0x04)==0)^((j&0x04))==0)) * 255;
            *P++=(unsigned char)c;
            *P++=(unsigned char)0;              // opet jednu barvu vynechame
            *P++=(unsigned char)c;
        }
    }
                                                // textura na urovni 3 v mipmape
    for (j=0; j<TEXTURE_HEIGHT >> 3; j++) {     // pro vsechny radky pixmapy
        P=texture3[j][0];                       // prvni pixel na radku j
        for (i=0; i<TEXTURE_WIDTH >> 3; i++) {  // pro vsechny pixely na radku pixmapy
            c=((((i&0x02)==0)^((j&0x02))==0)) * 255;
            *P++=(unsigned char)0;
            *P++=(unsigned char)c;
            *P++=(unsigned char)c;
        }
    }
                                                // textura na urovni 4 v mipmape
    for (j=0; j<TEXTURE_HEIGHT >> 4; j++) {     // pro vsechny radky pixmapy
        P=texture4[j][0];                       // prvni pixel na radku j
        for (i=0; i<TEXTURE_WIDTH >> 4; i++) {  // pro vsechny pixely na radku pixmapy
            *P++=(unsigned char)c;
            *P++=(unsigned char)c;
            *P++=(unsigned char)c;
        }
    }
                                                // textura na urovni 5 v mipmape
    for (j=0; j<TEXTURE_HEIGHT >> 5; j++) {     // pro vsechny radky pixmapy
        P=texture5[j][0];                       // prvni pixel na radku j
        for (i=0; i<TEXTURE_WIDTH >> 5; i++) {  // pro vsechny pixely na radku pixmapy
            *P++=(unsigned char)c;
            *P++=(unsigned char)0;              // opet jednu barvu vynechame
            *P++=(unsigned char)c;
        }
    }
                                                // textura na urovni 6 v mipmape
    texture6[0][0][0]=(unsigned char)0;         // posledni textura v mipmape
    texture6[0][0][1]=(unsigned char)c;         // obsahuje pouze jeden texel
    texture6[0][0][2]=(unsigned char)0;
}



//---------------------------------------------------------------------
// Nastaveni parametru textur
//---------------------------------------------------------------------
void setTextures(void)
{
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);      // zpusob ulozeni bytu v texture
    glGenTextures(1, &textureName);
    glBindTexture(GL_TEXTURE_2D, textureName);
    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_MIPMAP_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, 64, 64, 0, GL_RGB, GL_UNSIGNED_BYTE, texture0);
    glTexImage2D(GL_TEXTURE_2D, 1, 3, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, texture1);
    glTexImage2D(GL_TEXTURE_2D, 2, 3, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, texture2);
    glTexImage2D(GL_TEXTURE_2D, 3, 3,  8,  8, 0, GL_RGB, GL_UNSIGNED_BYTE, texture3);
    glTexImage2D(GL_TEXTURE_2D, 4, 3,  4,  4, 0, GL_RGB, GL_UNSIGNED_BYTE, texture4);
    glTexImage2D(GL_TEXTURE_2D, 5, 3,  2,  2, 0, GL_RGB, GL_UNSIGNED_BYTE, texture5);
    glTexImage2D(GL_TEXTURE_2D, 6, 3,  1,  1, 0, GL_RGB, GL_UNSIGNED_BYTE, texture6);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST); // vylepseni zobrazovani
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); // zpusob vykresleni textury
    glEnable(GL_TEXTURE_2D);                    // povoleni texturovani
}



//---------------------------------------------------------------------
// Funkce pro inicializaci vykreslovani
//---------------------------------------------------------------------
void onInit(void)
{
    glClearColor(0.0f, 0.0f, 0.0f, 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
    setTextures();                              // vytvoreni textur a nastaveni parametru
}



//---------------------------------------------------------------------
// 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(60.0, (double)windowWidth/(double)windowHeight, 2.0f, 190.0f);// nastaveni perspektivni kamery
    glMatrixMode(GL_MODELVIEW);                 // bude se menit modelova matice
    glLoadIdentity();                           // nahrat jednotkovou matici
}



//--------------------------------------------------------------------
// Vykresleni objektu
//--------------------------------------------------------------------
void drawObject(void)
{
    glBegin(GL_QUADS);                          // vykresleni otevrene krychle - sten domecku
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-5.0f, -5.0f, -5.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-5.0f, -5.0f,  5.0f);
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 5.0f, -5.0f,  5.0f);
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 5.0f, -5.0f, -5.0f);

        glTexCoord2f(0.0f, 0.0f); glVertex3f(-5.0f,  5.0f, -5.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-5.0f,  5.0f,  5.0f);
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 5.0f,  5.0f,  5.0f);
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 5.0f,  5.0f, -5.0f);

        glTexCoord2f(0.0f, 0.0f); glVertex3f(-5.0f, -5.0f, -5.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-5.0f, -5.0f,  5.0f);
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-5.0f,  5.0f,  5.0f);
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-5.0f,  5.0f, -5.0f);

        glTexCoord2f(0.0f, 0.0f); glVertex3f( 5.0f, -5.0f, -5.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex3f( 5.0f, -5.0f,  5.0f);
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 5.0f,  5.0f,  5.0f);
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 5.0f,  5.0f, -5.0f);
    glEnd();

    glBegin(GL_TRIANGLES);                      // vykresleni strechy domecku z trojuhelniku
        glTexCoord2f(0.0, 0.0); glVertex3f(-5.0f,  5.0f, -5.0f);
        glTexCoord2f(0.0, 1.0); glVertex3f( 5.0f,  5.0f, -5.0f);
        glTexCoord2f(0.5, 0.8); glVertex3f( 0.0f, 11.0f,  0.0f);

        glTexCoord2f(0.0, 0.0); glVertex3f( 5.0f,  5.0f, -5.0f);
        glTexCoord2f(0.0, 1.0); glVertex3f( 5.0f,  5.0f,  5.0f);
        glTexCoord2f(0.5, 0.8); glVertex3f( 0.0f, 11.0f,  0.0f);

        glTexCoord2f(0.0, 0.0); glVertex3f( 5.0f,  5.0f,  5.0f);
        glTexCoord2f(0.0, 1.0); glVertex3f(-5.0f,  5.0f,  5.0f);
        glTexCoord2f(0.5, 0.8); glVertex3f( 0.0f, 11.0f,  0.0f);

        glTexCoord2f(0.0, 0.0); glVertex3f(-5.0f,  5.0f,  5.0f);
        glTexCoord2f(0.0, 1.0); glVertex3f(-5.0f,  5.0f, -5.0f);
        glTexCoord2f(0.5, 0.8); glVertex3f( 0.0f, 11.0f,  0.0f);
    glEnd();
}



//--------------------------------------------------------------------
// 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);
    glBindTexture(GL_TEXTURE_2D, textureName);  // navazani textury na vykreslovany objekt
    drawObject();                               // vykresleni objektu
    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:    exit(0);            break;  // ukonceni aplikace
        case 'q':   exit(0);            break;  // ukonceni aplikace
        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
        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
//---------------------------------------------------------------------