//---------------------------------------------------------------------
// Ukazkovy priklad k serii clanku OpenGL a GLU
//
// Autor:          Pavel Tisnovsky
// Cislo clanku:   8
// Cislo prikladu: 1
//
// Tento program po svem spusteni zobrazi 3D teleso - kouli - pomoci
// funkci z nadstavbove knihovny GLU. Model koule se zobrazi pomoci
// usecek spojujicich jednotlive vrcholy (wireframe model).
// Pomoci leveho tlacitka mysi je mozne telesem rotovat, pravym
// tlacitkem se meni vzdalenost telesa od pozorovatele.
// Klavesami '1' a '2' se meni polomer koule, klavesami '3' a '4' pocet
// "poledniku" a klavesami '5' a '6' pocet "rovnobezek", pricemz
// "poledniky" a "rovnobezky" urcuji pocet plosek, ze kterych je model
// telesa vytvoren.
// 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 8.1" // titulek okna



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

float radius=5.0;                                   // polomer telesa
int   slices=15;                                    // rozdeleni telesa na casti - "poledniky" a "rovnobezky"
int   stacks=15;



//---------------------------------------------------------------------
// 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);
    sprintf(str, "radius:      %3.1f        ('1' and '2' to change)", radius);
    printGlutBitmapFont(str, GLUT_BITMAP_8_BY_13, 10, 96, 1.0, 0.8, 0.4);
    sprintf(str, "slices:    %4d         ('3' and '4' to change)", slices);
    printGlutBitmapFont(str, GLUT_BITMAP_8_BY_13, 10, 112, 1.0, 0.6, 0.6);
    sprintf(str, "stacks:    %4d         ('5' and '6' to change)", stacks);
    printGlutBitmapFont(str, GLUT_BITMAP_8_BY_13, 10, 128, 1.0, 0.4, 0.8);
    glEnable(GL_LIGHTING);
}



//---------------------------------------------------------------------
// Vykresleni 3D sceny
//---------------------------------------------------------------------
void drawScene(void)
{
    GLUquadric *quadric;

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

    quadric=gluNewQuadric();                        // vytvoreni kvadriky
    gluQuadricDrawStyle(quadric, GLU_LINE);         // nastaveni vlastnosti kvadriky
    gluQuadricNormals(quadric, GLU_SMOOTH);         // smer generovanych normal
    gluSphere(quadric, (GLdouble)radius, (GLint)slices, (GLint)stacks);
    gluDeleteQuadric(quadric);                      // zruseni kvadriky
}



//---------------------------------------------------------------------
// 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;
        // zmena parametru zobrazovaneho modelu
        case '1':   if (radius>1.0) {radius-=0.5; glutPostRedisplay();} break;
        case '2':   if (radius<20) {radius+=0.5; glutPostRedisplay(); } break;
        case '3':   if (slices>2) {slices--; glutPostRedisplay();}      break;
        case '4':   if (slices<30) {slices++; glutPostRedisplay();}     break;
        case '5':   if (stacks>2) {stacks--; glutPostRedisplay();}      break;
        case '6':   if (stacks<30) {stacks++; glutPostRedisplay();}     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
    }
}



//---------------------------------------------------------------------
// 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
    onInit();                                       // inicializace vykreslovani
    glutMainLoop();                                 // nekonecna smycka, kde se volaji zaregistrovane funkce
    return 0;                                       // navratova hodnota vracena operacnimu systemu
}



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