//---------------------------------------------------------------------
// Ukazkovy priklad k serii clanku OpenGL a GLU
//
// Autor:          Pavel Tisnovsky
// Cislo clanku:   13
// Cislo prikladu: 1
//
// Po spusteni tohoto demonstracniho prikladu se zobrazi
// NURB plocha specifikovana pomoci sestnacti ridicich bodu.
// Pomoci leveho tlacitka mysi je mozne telesem rotovat, pravym
// tlacitkem se meni vzdalenost telesa od pozorovatele.
//
// Pri vyskytu chyby se zavola registrovana callback funkce.
//
// 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.
//---------------------------------------------------------------------

#include <windows.h>
#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 13.1"// titulek okna

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

// ridici body
GLfloat ctlpoints[4][4][3];

// uzlovy vektor
GLfloat knots[8]={0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0};

GLUnurbs *nurbs;                                    // objekt NURB krivky

int mouseState=0;                                   // stav tlacitek mysi
int selected=0;                                     // vybrany ridici bod



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



//---------------------------------------------------------------------
// Callback funkce volana pri vyskytu chyby pri renderovani NURB krivek
//---------------------------------------------------------------------
void __stdcall onError(int errorCode)
{
    printf("Error: %d\nError string: %s\n\n", errorCode, gluErrorString(errorCode));
}



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

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



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

    glColor3f(1.0f, 1.0f, 1.0f);                    // barva plochy

    gluBeginSurface(nurbs);                         // zacatek specifikace NURB plochy
    gluNurbsSurface(nurbs,                          // nastaveni parametru NURB plochy
        8, knots,                                   // uzlovy vektor ve smeru parametru u
        8, knots,                                   // uzlovy vektor ve smeru parametru v
        4 * 3,                                      // vzdalenost mezi polozkami v poli
        3,
        &ctlpoints[0][0][0],                        // ridici body
        4, 4,                                       // stupen krivky+1 v obou smerech
        GL_MAP2_VERTEX_3);                          // typ ridicich bodu
    gluEndSurface(nurbs);                           // konec specifikace NURB plochy
}



//---------------------------------------------------------------------
// Funkce pro inicializaci vykreslovani
//---------------------------------------------------------------------
void onInit(void)
{
    int u, v;

    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    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
    glPointSize(5.0f);

    nurbs=gluNewNurbsRenderer();                    // vytvoreni NURBS
    gluNurbsCallback(nurbs, GLU_ERROR, onError);    // registrace callback funkce
    gluNurbsProperty(nurbs, GLU_SAMPLING_TOLERANCE, 50.0);
    gluNurbsProperty(nurbs, GLU_DISPLAY_MODE, GLU_OUTLINE_POLYGON);
    glEnable(GL_AUTO_NORMAL);

    for (u=0; u<4; u++) {                           // nastavit vsechny ridici body
        for (v=0; v<4; v++) {
            ctlpoints[u][v][0]=2.0*((GLfloat)u-1.5);
            ctlpoints[u][v][1]=2.0*((GLfloat)v-1.5);

            if ((u==1 || u==2) && (v==1 || v==2))   // ctyri body uprostred
                ctlpoints[u][v][2]=6.0;
            else
                ctlpoints[u][v][2]=0.0;             // body na okraji plochy
        }
    }
}



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



//---------------------------------------------------------------------
// Nastaveni souradneho systemu v zavislosti na velikosti okna
//---------------------------------------------------------------------
void onResize(GLsizei w, GLsizei h)
{
    glViewport(0, 0, w, h);                         // viditelna oblast pres cele okno
    windowWidth=w;                                  // zapamatovat si velikost okna
    windowHeight=h;
}



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



//---------------------------------------------------------------------
// Callback funkce volana pri stisku ci pusteni tlacitka mysi
//---------------------------------------------------------------------
void onMouse(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
}



//---------------------------------------------------------------------
// Callback funkce volana pri pohybu mysi
//---------------------------------------------------------------------
void onMouseMotion(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
    }
}



//---------------------------------------------------------------------
// Hlavni funkce konzolove aplikace
//---------------------------------------------------------------------
int main(int argc, char** argv)
{
    glutInit(&argc, argv);                          // inicializace knihovny GLUT
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);    // 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(onMouse);                         // registrace funkce volane pri stisku tlacitka mysi
    glutMotionFunc(onMouseMotion);                  // registrace funkce volane pri pohybu mysi
    onInit();                                       // inicializace vykreslovani
    glutMainLoop();                                 // nekonecna smycka, kde se volaji zaregistrovane funkce
    return 0;                                       // navratova hodnota vracena operacnimu systemu
}



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