//---------------------------------------------------------------------
// Ukazkovy priklad cislo 68
// Autor: Pavel Tisnovsky
//
// Program pro zobrazeni sceny s otexturocanymi telesy. Textury jsou nejdrive
// nacteny z rastrovych souboru typu TGA (Targa) a z techto jsou pote
// zobrazovany ve scene.
// Ovladani bud mysi: otaceni+tlacitka pohyb vpred (leve) a vzad (prave)
// nebo klavesnici:
// sipka nahoru, sipka dolu: pohyb vpred/vzad
// sipka doprava, sipka doleva: otaceni
// CTRL+sipka doprava, doleva: ukroky
//---------------------------------------------------------------------

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <gl/glut.h>

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#define MOVE_SCENE_DOWN         -5.0                // posun sceny dolu, aby byla videt rovina x-y
#define SCENE_SHIFT            -11.0                // odsunuti sceny od pozorovatele, aby se provadelo korektne otaceni

#define DEFAULT_WINDOW_WIDTH    450                 // velikost a pocatecni pozice okna na obrazovce
#define DEFAULT_WINDOW_HEIGHT   450
#define DEFAULT_WINDOW_TOP      10
#define DEFAULT_WINDOW_LEFT     10

#define TEXTURE_GROUND           0                  // jmena textur pro vyber
#define TEXTURE_WALL1            1
#define TEXTURE_WALL2            2
#define TEXTURE_TREASURE         3

int textures[4];                                    // cisla textur

typedef struct  Window {                            // informace o oknu
    int         width;
    int         height;
} Window;

typedef struct  View {                              // informace o pohledu na scenu
    float       fov;
    float       nearPlane;
    float       farPlane;
} View;

typedef struct  Avatar {                            // informace o hraci (pozorovateli)
    float       posX;
    float       posY;
    float       angle;
    float       moveSpeed;
} Avatar;

Window   window;
View     view={45.0, 2.0, 1000.0};
Avatar   avatar={0.0, -80.0, 0.0, 0.0};
int      btn=0;

char mapa[10][10]={                                 // mapa bludiste
    {".......###"},
    {".#.#### .#"},
    {".#.......#"},
    {".#...#####"},
    {"#*##..#..."},
    {"#.#*.....#"},
    {"#....#.###"},
    {"#.##.#...."},
    {".......#.."},
    {"########.."},
};


//---------------------------------------------------------------------
// Posun pohledu podle pozice a orientace avatara
//---------------------------------------------------------------------
void avatarMoveView(Avatar *avatar)
{
    glTranslatef(0.0, MOVE_SCENE_DOWN, 0.0);        // posun sceny dolu, aby byla videt rovina z=0
    glRotatef(90.0, 1.0, 0.0, 0.0);                 // otoceni sceny okolo osy X tak, aby osa Z smerovala nahoru
    glTranslatef(0.0, SCENE_SHIFT, 0.0);            // posun sceny od pozorovatele, aby se provadelo korektne otaceni
    glRotatef(avatar->angle, 0.0, 0.0, 1.0);        // otoceni pozorovatele
    glTranslatef(avatar->posX, avatar->posY, 0.0);  // posun pozorovatele
}



//---------------------------------------------------------------------
// Kontrola, zda se avatar nachazi uvnitr sceny s pripadnym omezenim pohybu
//---------------------------------------------------------------------
void avatarCheckRanges(Avatar *avatar)
{
    if (avatar->posX>90.0) avatar->posX=90.0;
    if (avatar->posX<-90.0) avatar->posX=-90.0;
    if (avatar->posY>90.0) avatar->posY=90.0;
    if (avatar->posY<-90.0) avatar->posY=-90.0;
}



//---------------------------------------------------------------------
// Posun avatara ve smeru pohybu
//---------------------------------------------------------------------
void avatarMove(Avatar *avatar)
{
    avatar->posX+=avatar->moveSpeed*sin(avatar->angle*3.14/180.0);
    avatar->posY+=avatar->moveSpeed*cos(avatar->angle*3.14/180.0);
    avatarCheckRanges(avatar);
}



//---------------------------------------------------------------------
// Posun avatara dopredu (podle jeho orientace)
//---------------------------------------------------------------------
void avatarMoveForward(Avatar *avatar)
{
    avatar->posX+=1.0*sin(avatar->angle*3.14/180.0);
    avatar->posY+=1.0*cos(avatar->angle*3.14/180.0);
    avatarCheckRanges(avatar);
}



//---------------------------------------------------------------------
// Posun avatara dozadu (podle jeho orientace)
//---------------------------------------------------------------------
void avatarMoveBackward(Avatar *avatar)
{
    avatar->posX-=1.0*sin(avatar->angle*3.14/180.0);
    avatar->posY-=1.0*cos(avatar->angle*3.14/180.0);
    avatarCheckRanges(avatar);
}



//---------------------------------------------------------------------
// Posun avatara doleva (ukrok podle jeho orientace)
//---------------------------------------------------------------------
void avatarMoveLeft(Avatar *avatar)
{
    avatar->posX+=1.0*cos(avatar->angle*3.14/180.0);
    avatar->posY-=1.0*sin(avatar->angle*3.14/180.0);
    avatarCheckRanges(avatar);
}



//---------------------------------------------------------------------
// Posun avatara doprava (ukrok podle jeho orientace)
//---------------------------------------------------------------------
void avatarMoveRight(Avatar *avatar)
{
    avatar->posX-=1.0*cos(avatar->angle*3.14/180.0);
    avatar->posY+=1.0*sin(avatar->angle*3.14/180.0);
    avatarCheckRanges(avatar);
}



//---------------------------------------------------------------------
// Otoceni avatara smerem doleva
//---------------------------------------------------------------------
void avatarTurnLeft(Avatar *avatar)
{
    avatar->angle+=3.0;
}



//---------------------------------------------------------------------
// Otoceni avatara smerem doprava
//---------------------------------------------------------------------
void avatarTurnRight(Avatar *avatar)
{
    avatar->angle-=3.0;
}



//---------------------------------------------------------------------
// Nacteni bitmapy ze souboru typu TGA
//---------------------------------------------------------------------
int bitmapLoadFromTGA(int texture, const char *filename)
{
    FILE               *fin;
    unsigned short int width=0, height=0;           // sirka a vyska obrazku
    unsigned short int palettelength;               // delta palety
    unsigned char      bpp=0;                       // pocet bitu na pixel
    int                size;
    unsigned char      *bitmap;
    unsigned char      tgaHeader[18];               // hlavicka formatu TGA
    if (!filename) return -1;
    fin=fopen(filename, "rb");
    if (!fin) return -1;                            // otevreni souboru se nezdarilo
    if (fread(tgaHeader, 18, 1, fin)!=1) return -1; // nacist hlavicku BMP souboru
    memcpy(&width, tgaHeader+12, 2);                // nacist sirku obrazku v pixelech
    memcpy(&height, tgaHeader+14, 2);               // nacist vysku obrazku v pixelech
    memcpy(&bpp, tgaHeader+16, 1);                  // nacist pocet bitu na pixel
    memcpy(&palettelength, tgaHeader+5, 2);         // nacist delku palety
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    switch (bpp) {
        case 8:
            size=width*height;
            bitmap=(unsigned char *)malloc(size*sizeof(unsigned char));
            if (fread(bitmap, palettelength, 1, fin)!=1) return -1;
            if (fread(bitmap, size, 1, fin)!=1) return -1;
            fclose(fin);
            glTexImage2D(GL_TEXTURE_2D, 0, 1, width, height,// nacteni textury do GPU
                         0, GL_LUMINANCE, GL_UNSIGNED_BYTE, bitmap);
            break;
        case 24:
            size=width*height*3;
            bitmap=(unsigned char *)malloc(size*sizeof(unsigned char));
            if (fread(bitmap, size, 1, fin)!=1) return -1;
            fclose(fin);
            glTexImage2D(GL_TEXTURE_2D, 0, 3, width, height,// nacteni textury do GPU
                         0, GL_RGB, GL_UNSIGNED_BYTE, bitmap);
            break;
        case 32:
            size=width*height*4;
            bitmap=(unsigned char *)malloc(size*sizeof(unsigned char));
            if (fread(bitmap, size, 1, fin)!=1) return -1;
            fclose(fin);
            glTexImage2D(GL_TEXTURE_2D, 0, 4, width, height,// nacteni textury do GPU
                         0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap);
            break;
        default:
            break;
    }
    free(bitmap);
    return 0;
}



//---------------------------------------------------------------------
// Nacteni a vytvoreni vsech textur
//---------------------------------------------------------------------
int loadTextures(void)
{
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);          // zpusob ulozeni bytu v texure
    glGenTextures(4, textures);                     // vytvoreni jmena textur
    if (bitmapLoadFromTGA(textures[TEXTURE_GROUND],   "ground.tga"))   exit(0);
    if (bitmapLoadFromTGA(textures[TEXTURE_WALL1],    "wall1.tga"))    exit(0);
    if (bitmapLoadFromTGA(textures[TEXTURE_WALL2],    "wall2.tga"))    exit(0);
    if (bitmapLoadFromTGA(textures[TEXTURE_TREASURE], "treasure.tga")) exit(0);
}



//---------------------------------------------------------------------
// Callback funkce zavolana pri inicializaci aplikace
//---------------------------------------------------------------------
void onInit(void)
{
    glClearColor(0.2, 0.2, 0.4, 0.0);               // barva pro mazani color-bufferu
    glShadeModel(GL_SMOOTH);                        // nastaveni stinovaciho rezimu
    loadTextures();                                 // nacist vsechny textury
    glClearDepth(1.0f);                             // barva pro mazani z-bufferu
    glEnable(GL_DEPTH_TEST);                        // nastaveni funkce pro testovani hodnot v z-bufferu
    glDepthFunc(GL_LESS);                           // kterou funkci vybrat pro testovani z-bufferu
    glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);// vylepseni zobrazovani textur
    glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);       // nastaveni vykresleni vyplnenych polygonu
    glPointSize(10.0);                              // nastaveni velikosti vykreslovanych bodu
    glEnable(GL_POINT_SMOOTH);
    glEnable(GL_LINE_SMOOTH);
    glutSetCursor(GLUT_CURSOR_NONE);
}



//---------------------------------------------------------------------
// Callback funkce zavolana pri zmene velikosti okna aplikace
//---------------------------------------------------------------------
void onResize(int width, int height)
{
    glViewport(0, 0, width, height);                // viditelna oblast
    window.width=width;
    window.height=height;
}



//---------------------------------------------------------------------
// Callback funkce zavolana pri zmene prekreslovani okna
//---------------------------------------------------------------------
void onDisplay(void)
{
    int i, j, k;
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// vymazani barvoveho a z-bufferu

    // Nastaveni ModelView matice tak, aby se pozorovatel prochazel
    // scenou ve stylu dungeonu
    glMatrixMode(GL_PROJECTION);                    // projekcni matice
    glLoadIdentity();
    gluPerspective(view.fov,(double)window.width/(double)window.height, view.nearPlane, view.farPlane);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    avatarMoveView(&avatar);

    // nakresleni texturovane podlahy
    glEnable(GL_TEXTURE_2D);                        // povoleni texturovani
    glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_REPLACE);
    glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_GROUND]);   // navazani textury
    glBegin(GL_QUADS);
        glTexCoord2f(0.0, 0.0); glVertex3f(-100, -100, 1);
        glTexCoord2f(4.0, 0.0); glVertex3f(+100, -100, 1);
        glTexCoord2f(4.0, 4.0); glVertex3f(+100, +100, 1);
        glTexCoord2f(0.0, 4.0); glVertex3f(-100, +100, 1);
    glEnd();

    // nakresleni obvodovych zdi
    glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_WALL1]);
    glBegin(GL_QUADS);
        glTexCoord2f(0.0, 0.0); glVertex3f(-100,  100,  1);
        glTexCoord2f(9.0, 0.0); glVertex3f( 100,  100,  1);
        glTexCoord2f(9.0, 1.0); glVertex3f( 100,  100, -10);
        glTexCoord2f(0.0, 1.0); glVertex3f(-100,  100, -10);
        glTexCoord2f(0.0, 0.0); glVertex3f(-100,  100,  1);
        glTexCoord2f(9.0, 0.0); glVertex3f(-100, -100,  1);
        glTexCoord2f(9.0, 1.0); glVertex3f(-100, -100, -10);
        glTexCoord2f(0.0, 1.0); glVertex3f(-100,  100, -10);
        glTexCoord2f(0.0, 0.0); glVertex3f(-100, -100,  1);
        glTexCoord2f(9.0, 0.0); glVertex3f( 100, -100,  1);
        glTexCoord2f(9.0, 1.0); glVertex3f( 100, -100, -10);
        glTexCoord2f(0.0, 1.0); glVertex3f(-100, -100, -10);
        glTexCoord2f(0.0, 0.0); glVertex3f( 100,  100,  1);
        glTexCoord2f(9.0, 0.0); glVertex3f( 100, -100,  1);
        glTexCoord2f(9.0, 1.0); glVertex3f( 100, -100, -10);
        glTexCoord2f(0.0, 1.0); glVertex3f( 100,  100, -10);
    glEnd();

    // nakresleni vnitrnich zdi a pokladu
    for (j=0; j<10; j++) {
        for (i=0; i<10; i++) {
            if (mapa[i][j]!='.') {                  // vykreslit zed nebo poklad
                if (mapa[i][j]=='#')
                    glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_WALL2]);
                else
                    glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_TREASURE]);
                glBegin(GL_QUADS);
                    glTexCoord2f(0.0, 0.0); glVertex3f( -90+i*20,  -90+j*20,  1);
                    glTexCoord2f(1.0, 0.0); glVertex3f( -80+i*20,  -90+j*20,  1);
                    glTexCoord2f(1.0, 1.0); glVertex3f( -80+i*20,  -90+j*20, -10);
                    glTexCoord2f(0.0, 1.0); glVertex3f( -90+i*20,  -90+j*20, -10);
                    glTexCoord2f(0.0, 0.0); glVertex3f( -90+i*20,  -80+j*20,  1);
                    glTexCoord2f(1.0, 0.0); glVertex3f( -80+i*20,  -80+j*20,  1);
                    glTexCoord2f(1.0, 1.0); glVertex3f( -80+i*20,  -80+j*20, -10);
                    glTexCoord2f(0.0, 1.0); glVertex3f( -90+i*20,  -80+j*20, -10);
                    glTexCoord2f(0.0, 0.0); glVertex3f( -80+i*20,  -90+j*20,  1);
                    glTexCoord2f(1.0, 0.0); glVertex3f( -80+i*20,  -80+j*20,  1);
                    glTexCoord2f(1.0, 1.0); glVertex3f( -80+i*20,  -80+j*20, -10);
                    glTexCoord2f(0.0, 1.0); glVertex3f( -80+i*20,  -90+j*20, -10);
                    glTexCoord2f(0.0, 0.0); glVertex3f( -90+i*20,  -90+j*20,  1);
                    glTexCoord2f(1.0, 0.0); glVertex3f( -90+i*20,  -80+j*20,  1);
                    glTexCoord2f(1.0, 1.0); glVertex3f( -90+i*20,  -80+j*20, -10);
                    glTexCoord2f(0.0, 1.0); glVertex3f( -90+i*20,  -90+j*20, -10);
                glEnd();
            }
        }
    }

    glDisable(GL_TEXTURE_2D);
    glFlush();                                      // provedeni vsech prikazu
    glutSwapBuffers();                              // a prohozeni bufferu
}



//---------------------------------------------------------------------
// Callback funkce zavolana pri stlaceni ASCII klavesy
//---------------------------------------------------------------------
void onKeyPress(unsigned char key, int x, int y)
{
    if (key>='A' && key<='Z')                       // uprava velkych pismen na mala
        key+='a'-'A';

    switch (key) {                                  // rozeskok podle stlacene klavesy
        case 27:                                    // klavesa Escape
        case 'q':
        case 'x':
            exit(0);                                // ukonceni programu
            break;
        case 'f':
            glutFullScreen();                       // prepnuti na celou obrazovku
            break;
        case 'w':                                   // prepnuti do okna
            glutReshapeWindow(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT);
            glutPositionWindow(DEFAULT_WINDOW_LEFT, DEFAULT_WINDOW_TOP);
            break;
        default:
            break;
    }
    glutPostRedisplay();
}



//---------------------------------------------------------------------
// Callback funkce zavolana pri stlaceni non-ASCII klavesy
//---------------------------------------------------------------------
void onKeyDown(int key, int x, int y)
{
    int modifiers=glutGetModifiers();               // ziskat stav klaves ALT, CTRL a SHIFT
    switch (key) {
        case GLUT_KEY_UP:
            avatarMoveForward(&avatar);
            break;
        case GLUT_KEY_DOWN:
            avatarMoveBackward(&avatar);
            break;
        case GLUT_KEY_LEFT:
            if (modifiers & GLUT_ACTIVE_CTRL)       // CTRL+sipka je ukrok
                avatarMoveLeft(&avatar);
            else
                avatarTurnLeft(&avatar);
            break;
        case GLUT_KEY_RIGHT:
            if (modifiers & GLUT_ACTIVE_CTRL)       // CTRL+sipka je ukrok
                avatarMoveRight(&avatar);
            else
                avatarTurnRight(&avatar);
            break;
        default: break;
    }
    glutPostRedisplay();
}



//---------------------------------------------------------------------
// Callback funkce zavolana pri stlaceni nebo pusteni tlacitka mysi
//---------------------------------------------------------------------
void onMouseButton(int button, int state, int x, int y)
{
    if (button==GLUT_LEFT_BUTTON) {
        if (state==GLUT_DOWN) {
            btn=1;                                  // zrychleni dopredu
        }
        else {
            btn=0;
        }
    }
    if (button==GLUT_RIGHT_BUTTON) {
        if (state==GLUT_DOWN) {
            btn=2;                                  // zrychleni dozadu
        }
        else {
            btn=0;
        }
    }
}



//---------------------------------------------------------------------
// Callback funkce zavolana pri pohybu mysi
//---------------------------------------------------------------------
void onMousePassiveMotion(int x, int y)
{
    static int first=1;
    static int old_x;
    if (first) {
        old_x=x;
        first=0;
    }
    else {
        avatar.angle=-x+old_x;
    }
    glutPostRedisplay();
}



//---------------------------------------------------------------------
// Callback funkce zavolana pri tiku casovace kazdych 10ms
//---------------------------------------------------------------------
void onTimer(int timer)
{
    if (btn==1) avatar.moveSpeed=5.0;               // leve tlacitko mysi -> plna rychlost dopredu
    if (btn==2) avatar.moveSpeed=-5.0;              // prave tlacitko mysi -> plna rychlost dozadu
    if (btn==0) {                                   // zadne tlacitko mysi -> zpomaleni
        if (avatar.moveSpeed>0.1) avatar.moveSpeed-=0.5;
        if (avatar.moveSpeed<-0.1) avatar.moveSpeed+=0.5;
    }
    if (abs(avatar.moveSpeed)>0.01) {
        avatarMove(&avatar);
        glutPostRedisplay();
    }
    else {
        avatar.moveSpeed=0.0;
    }
    glutTimerFunc(10, onTimer, timer);
}



//---------------------------------------------------------------------
// Callback funkce zavolana pri tiku casovace kazdych 10ms
//---------------------------------------------------------------------
int main(int argc, char **argv)
{
    glutInit(&argc,argv);                           // inicializace knihovny GLUT
    glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE|GLUT_DEPTH); // graficky mod okna
    glutInitWindowSize(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT);// pocatecni velikost okna
    glutInitWindowPosition(DEFAULT_WINDOW_LEFT, DEFAULT_WINDOW_TOP);              // pocatecni pozice okna
    glutCreateWindow("Priklad cislo 68");           // vytvoreni okna pro kresleni
    glutDisplayFunc(onDisplay);                     // registrace funkce volane pri prekreslovani
    glutReshapeFunc(onResize);                      // registrace funkce volane pri zmene velikosti
    glutKeyboardFunc(onKeyPress);                   // registrace funkce volane pri stisku ASCII klavesy
    glutSpecialFunc(onKeyDown);                     // registrace funkce volane pri stisku non-ASCII klavesy
    glutMouseFunc(onMouseButton);                   // registrace funkce volane pri stisku ci pusteni tlacitka mysi
    glutPassiveMotionFunc(onMousePassiveMotion);    // registrace funkce volane pri pohybu mysi
    glutTimerFunc(10, onTimer, 0x1234);             // registrace funkce volane pri tiku casovace
    onInit();                                       // inicializace aplikace 
    glutMainLoop();                                 // nekonecna smycka, kde se volaji zaregistrovane funkce
    return 0;                                       // ANSI C potrebuje ukoncit fci main prikazem return
                                                    // i kdyz se sem program nikdy nedostane
}



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