//---------------------------------------------------------------------
// Ukazkovy priklad k serii clanku OpenGL a GLU
//
// Autor:          Pavel Tisnovsky
// Cislo clanku:   5
// Cislo prikladu: 1
//
// Tento program po svem spusteni zobrazi 3D teleso.
// Pomoci leveho tlacitka mysi je mozne telesem rotovat, pravym
// tlacitkem se meni vzdalenost telesa od pozorovatele.
// Pri pasivnim pohybu mysi se v titulkovem pruhu okna zobrazuji
// souradnice kurzoru ve "screen coordinates" a "world coordinates".
// z-ova souradnice kurzoru je nastavena na nulu.
// Pomoci klavesy 'f' lze provest prepnuti do celeho okna, klavesou 'w'
// se provede nastaveni puvodni velikosti okna, tj. 450x450 pixelu.
// Klavesou ESC je mozne program ukoncit.
//---------------------------------------------------------------------



#ifdef __BORLANDC__
#include <windows.h>                                // oprava chyby v nekterych Borlandskych prekladacich
#endif

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

#ifdef __BORLANDC__
#pragma hdrstop                                     // konec predkompilovanych hlavicek pro Borlandske prekladace
#endif

#define WINDOW_WIDTH    450                         // velikost okna
#define WINDOW_HEIGHT   450
#define WINDOW_TITLE    "OpenGL a GLU, priklad 5.1" // titulek okna - bude prepisovan



float materialAmbient[]={0.3, 0.2, 0.2, 1.0};       // ambientni slozka materialu
float materialDiffuse[]={0.9, 0.1, 0.1, 1.0};       // difuzni slozka materialu
float materialSpecular[]={0.0, 1.0, 1.0, 0.5};      // barva odlesku
float lightAmbient[]= {0.5, 0.5, 0.5, 1.0};         // ambientni slozka svetla
float lightDiffuse[]= {0.9, 0.9, 0.9, 1.0};         // difuzni slozka svetla
float lightSpecular[]={0.0, 0.0, 0.0, 1.0};         // barva odlesku
float lightPosition[]={1.0, 1.0, 1.0, 0.0};         // definice pozice svetla

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 mouseStatus;                                    // stav tlacitek mysi

float fov=45.0;                                     // hodnota zorneho uhlu - field of view
int   windowWidth;                                  // sirka okna
int   windowHeight;                                 // vyska okna

float fieldOfView=45.0;                             // zorny uhel - field of view
float nearClippingPlane=5.0;                        // blizsi orezavaci rovina
float farClippingPlane=200.0;                       // vzdalenejsi orezavaci rovina



//---------------------------------------------------------------------
// Pomocna funkce pro aplikaci zpetne projekce. Pro bod zadany
// v souradnicich obrazovky [x, y, z] se vypocte odpovidajici bod ve
// svetovych souradnicich [xw, yw, zw].
//---------------------------------------------------------------------
void myUnProject(
    GLdouble x,                                     // vstupy
    GLdouble y,
    GLdouble z,
    GLdouble *xw,                                   // vystupy
    GLdouble *yw,
    GLdouble *zw)
{
    // do techto pomocnych promennych se ulozi
    // souradnice vysledneho bodu
    GLdouble txw, tyw, tzw;

    // pole pro ulozeni aktualne
    // nastaveneho ViewPortu
    GLint viewPort[4];

    // pole pro ulozeni aktualne
    // nastavene matice ModelView
    GLdouble modelViewMatrix[16];

    // pole pro ulozeni aktualne
    // nastavene matice Projection
    GLdouble projectionMatrix[16];

    // ziskat aktualni nastaveni
    // ViewPortu
    glGetIntegerv(GL_VIEWPORT, viewPort);

    // ziskat aktualni nastaveni
    // transformacni matice ModelView
    glGetDoublev(GL_MODELVIEW_MATRIX,
                 modelViewMatrix);

    // ziskat aktualni nastaveni
    // transformacni matice Projection
    glGetDoublev(GL_PROJECTION_MATRIX,
                 projectionMatrix);

    // provest vlastni zpetnou projekci bodu
    gluUnProject(x, y, z, modelViewMatrix,
                 projectionMatrix, viewPort,
                 &txw, &tyw, &tzw);

    // predani parametrý - neni nutne,
    // pokud se v predchozi funkci
    // primo predaji ukazatele xw, yw, zw,
    // zde je uvedeno pouze pro prehlednost
    *xw=txw;
    *yw=tyw;
    *zw=tzw;
}



//---------------------------------------------------------------------
// Tato funkce vykresli retezec zadanym bitmapovym fontem
//---------------------------------------------------------------------
void printGlutBitmapFont(char *string, void *font, int x, int y, float r, float g, float b)
{
    glColor3f(r, g, b);                             // nastaveni barvy vykreslovanych bitmap
    glRasterPos2i(x, y);                            // nastaveni pozice pocatku bitmapy
    while (*string)                                 // projit celym retezcem
        glutBitmapCharacter(font, *string++);       // vykresleni jednoho znaku
}



//---------------------------------------------------------------------
// Vykresleni informaci o zobrazovane scene
//---------------------------------------------------------------------
void drawInformations(void)
{
    char str[100];

    glMatrixMode(GL_PROJECTION);                    // zacatek modifikace projekcni matice
    glLoadIdentity();                               // vymazani projekcni matice (=identita)
    gluOrtho2D(0, windowWidth, 0, windowHeight);    // mapovani abstraktnich souradnic do souradnic okna
    glScalef(1, -1, 1);                             // inverze y-ove osy, aby se y zvetsovalo smerem dolu
    glTranslatef(0, -windowHeight, 0);              // posun pocatku do leveho horniho rohu

    glMatrixMode(GL_MODELVIEW);                     // bude se menit modelova matice
    glLoadIdentity();                               // nahrat jednotkovou matici

    glDisable(GL_LIGHTING);
    sprintf(str, "x-rot:    %4d          (left mouse button)", xnew % 360);
    printGlutBitmapFont(str, GLUT_BITMAP_8_BY_13, 10, 16, 0.2, 1.0, 1.0);
    sprintf(str, "y-rot:    %4d          (left mouse button)", ynew % 360);
    printGlutBitmapFont(str, GLUT_BITMAP_8_BY_13, 10, 32, 0.4, 1.0, 1.0);
    sprintf(str, "z-transf: %4d          (right mouse button)", znew);
    printGlutBitmapFont(str, GLUT_BITMAP_8_BY_13, 10, 48, 0.6, 1.0, 0.8);
    sprintf(str, "field of view: %4d     ", (int)fieldOfView);
    printGlutBitmapFont(str, GLUT_BITMAP_8_BY_13, 10, 64, 0.8, 1.0, 0.6);
    sprintf(str, "width/height: %3d/%3d   ", windowWidth, windowHeight);
    printGlutBitmapFont(str, GLUT_BITMAP_8_BY_13, 10, 80, 1.0, 1.0, 0.4);
    glEnable(GL_LIGHTING);
}



//---------------------------------------------------------------------
// Vykresleni 3D sceny
//---------------------------------------------------------------------
void drawScene(void)
{
    glMatrixMode(GL_PROJECTION);                    // zacatek modifikace projekcni matice
    glLoadIdentity();                               // vymazani projekcni matice (=identita)
                                                    // nastaveni perspektivni transformace
    gluPerspective(fieldOfView, (double)windowWidth/(double)windowHeight, nearClippingPlane, farClippingPlane);
    glMatrixMode(GL_MODELVIEW);                     // bude se menit modelova matice
    glLoadIdentity();                               // nahrat jednotkovou matici

    glTranslatef(0.0f, 0.0f, -40.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(5);                             // vykresleni 3D telesa
}



//---------------------------------------------------------------------
// 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
    glShadeModel(GL_SMOOTH);                        // nastaveni stinovaciho rezimu
    glEnable(GL_DEPTH_TEST);                        // nastaveni funkce pro testovani hodnot v Z-bufferu
    glDepthFunc(GL_LESS);                           // nastaveni porovnavaci funkce pro Z-buffer
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);// vylepseni zobrazovani pri vypoctu perspektivy
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);      // stav vykresleni vyplnenych polygonu
    glLightfv(GL_LIGHT0,GL_AMBIENT,lightAmbient);   // nastaveni parametru svetla
    glLightfv(GL_LIGHT0,GL_DIFFUSE,lightDiffuse);
    glLightfv(GL_LIGHT0,GL_POSITION,lightPosition);
    glEnable(GL_LIGHTING);                          // povoleni osvetleni
    glEnable(GL_LIGHT0);                            // zapnuti prvniho svetla
    glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT,materialAmbient);// nastaveni vlastnosti materialu
    glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,materialDiffuse);
    glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,materialSpecular);
    glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 20.0);
}



//---------------------------------------------------------------------
// 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;
}



//--------------------------------------------------------------------
// Tato funkce je volana pri kazdem prekresleni okna
//--------------------------------------------------------------------
void onDisplay(void)
{
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); // vymazani barvoveho a hloubkoveho bufferu

    drawInformations();                             // vykresleni informaci o zobrazovane scene
    drawScene();                                    // vlastni vykresleni 3D sceny

    glFlush();                                      // provedeni a vykresleni vsech zmen
    glutSwapBuffers();
}



//---------------------------------------------------------------------
// Tato funkce je volana pri stlaceni ASCII klavesy
//---------------------------------------------------------------------
#ifdef __BORLANDC__
#pragma option -w-par                               // zabranit warningum pri prekladu
#endif                                              // u borlandskych prekladacu
void onKeyboard(unsigned char key, int x, int y)
{
    if (key>='A' && key<='Z')                       // uprava velkych pismen na mala
        key+=(unsigned char)('a'-'A');              // pro zjednoduseni prikazu switch

    switch (key) {
        case 27:    exit(0);            break;      // ukonceni aplikace
        case 'q':   exit(0);            break;      // ukonceni aplikace
        case 'f':   glutFullScreen();   break;      // prepnuti na celou obrazovku
        case 'w':   glutReshapeWindow(WINDOW_WIDTH, WINDOW_HEIGHT); break;
        default:                        break;
    }
}
#ifdef __BORLANDC__
#pragma option -w+par
#endif



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



//---------------------------------------------------------------------
// Tato funkce je volana pri posunu mysi se stlacenym tlacitkem
//---------------------------------------------------------------------
void onMouseDrag(int x, int y)
{
    if (mouseStatus==1) {                           //  stav rotace objektu
        xnew=xold+x-xx;                             // vypocitat novou pozici
        ynew=yold+y-yy;
        glutPostRedisplay();                        // a prekreslit scenu
    }
    if (mouseStatus==2) {                           // stav priblizeni/oddaleni objektu
        znew=zold+y-zz;                             // vypocitat novou pozici
        glutPostRedisplay();                        // a prekreslit scenu
    }
}



//---------------------------------------------------------------------
// Tato funkce je volana pri posunu mysi bez stlaceneho tlacitka mysi
//---------------------------------------------------------------------
void onPassiveMotion(int x, int y)
{
    char str[100];                                  // retezec pro ulozeni titulku
    GLdouble wx, wy, wz;                            // svetove souradnice bodu
    myUnProject((GLdouble)x, (GLdouble)(windowHeight-y), 0, &wx, &wy, &wz); // zpetna projekce bodu
    sprintf(str, "screen: %5i %5i   world: %4.0f %4.0f %4.0f", x, y, wx, wy, wz);
    glutSetWindowTitle(str);                        // nastaveni titulku okna
}



//---------------------------------------------------------------------
// 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(onMouseClick);                    // registrace funkce volane pri stlaceni nebo pusteni tlacitka mysi
    glutMotionFunc(onMouseDrag);                    // registrace funkce volane pri posunu mysi se stlacenym tlacitkem
    glutPassiveMotionFunc(onPassiveMotion);         // registrace funkce volane pri posunu mysi bez stlaceneho tlacitka
    onInit();                                       // inicializace vykreslovani
    glutMainLoop();                                 // nekonecna smycka, kde se volaji zaregistrovane funkce
    return 0;                                       // navratova hodnota vracena operacnimu systemu
}



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