//---------------------------------------------------------------------
// Ukazkovy priklad k serii clanku OpenGL a GLU
//
// Autor:          Pavel Tisnovsky
// Cislo clanku:   15
// Cislo prikladu: 2
//
// Po spusteni tohoto demonstracniho prikladu se zobrazi osvetlena
// NURB plocha specifikovana pomoci 36 ridicich bodu, pricemz jsou
// pouzity nasobne body, ktere umoznuji vymodelovat prudke zmeny
// na NURB plose.
// Pro ilustraci jsou znazorneny i ridici body NURB plochy.
//
// 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 15.2"// titulek okna

float materialAmbient[]={0.3, 0.3, 0.3, 1.0};       // ambientni slozka materialu
float materialDiffuse[]={0.9, 0.1, 0.1, 1.0};       // difuzni slozka materialu
float materialSpecular[]={1.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

#define U_POINTS 6                                  // pocet ridicich bodu
#define V_POINTS 6

#define U_ORDER 4                                  // stupen plochy+1
#define V_ORDER 4

#define U_KNOT (U_POINTS+U_ORDER)                  // pocet slozek uzloveho vektoru
#define V_KNOT (V_POINTS+V_ORDER)

// ridici body
GLfloat ctlpoints[U_POINTS][V_POINTS][3];

// uzlove vektory
GLfloat knots1[]={0.0, 0.0, 0.0, 0.0, 0.33, 0.66, 1.0, 1.0, 1.0, 1.0};
GLfloat knots2[]={0.0, 0.0, 0.0, 0.0, 0.33, 0.66, 1.0, 1.0, 1.0, 1.0};

GLUnurbs *nurbs;                                    // objekt NURB plochy

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

    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)
{
    int u,v;
    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);

    gluBeginSurface(nurbs);                         // zacatek specifikace NURB plochy
    gluNurbsSurface(nurbs,                          // nastaveni parametru NURB plochy
        U_KNOT, knots1,                             // uzlovy vektor ve smeru parametru u
        V_KNOT, knots2,                             // uzlovy vektor ve smeru parametru v
        V_POINTS * 3,                               // vzdalenost mezi polozkami v poli
        3,
        &ctlpoints[0][0][0],                        // ridici body
        U_ORDER, V_ORDER,                           // stupen plochy+1 v obou smerech
        GL_MAP2_VERTEX_3);                          // typ ridicich bodu
    gluEndSurface(nurbs);                           // konec specifikace NURB plochy

    // vykreslit ridici body a pospojovat je useckami
    glDisable(GL_LIGHTING);
    glColor3f(0.6, 0.6, 0.6);
    for (u=0; u<U_POINTS; u++) {
        glBegin(GL_LINE_STRIP);
        for (v=0; v<V_POINTS; v++) {
            glVertex3f(ctlpoints[u][v][0],
                       ctlpoints[u][v][1],
                       ctlpoints[u][v][2]);
        }
        glEnd();
    }
    for (v=0; v<U_POINTS; v++) {
        glBegin(GL_LINE_STRIP);
        for (u=0; u<V_POINTS; u++) {
            glVertex3f(ctlpoints[u][v][0],
                       ctlpoints[u][v][1],
                       ctlpoints[u][v][2]);
        }
        glEnd();
    }
    glColor3f(1.0, 1.0, 1.0);
    glBegin(GL_POINTS);
    for (v=0; v<U_POINTS; v++) {
        for (u=0; u<V_POINTS; u++) {
            glVertex3f(ctlpoints[u][v][0],
                       ctlpoints[u][v][1],
                       ctlpoints[u][v][2]);
        }
    }
    glEnd();
    glEnable(GL_LIGHTING);
}



//---------------------------------------------------------------------
// 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
    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);
    glPointSize(5.0f);

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

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

            if ((u==1 || u==2 || u==4) && (v==1 || v==2 || v==4))  // vybrane ridici body uprostred
                ctlpoints[u][v][2]=3.0;
            else
                ctlpoints[u][v][2]=-3.0;             // body na okraji plochy
        }
    }

    // vytvoreni nasobnych ridicich bodu
    for (u=0; u<U_POINTS; u++) {
        ctlpoints[u][2][0]=ctlpoints[u][1][0];
        ctlpoints[u][2][1]=ctlpoints[u][1][1];
        ctlpoints[u][2][2]=ctlpoints[u][1][2];
    }
    for (v=0; v<U_POINTS; v++) {
        ctlpoints[2][v][0]=ctlpoints[1][v][0];
        ctlpoints[2][v][1]=ctlpoints[1][v][1];
        ctlpoints[2][v][2]=ctlpoints[1][v][2];
        ctlpoints[3][v][0]=ctlpoints[2][v][0];
        ctlpoints[3][v][1]=ctlpoints[2][v][1];
        ctlpoints[3][v][2]=ctlpoints[2][v][2];
    }
}



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