//---------------------------------------------------------------------
// Ukazkovy priklad k serii clanku OpenGL evaluatorech
//
// Autor:          Pavel Tisnovsky
// Cislo clanku:   2
// Cislo prikladu: 2
//
// Po spusteni tohoto prikladu se vykresli Bezierova krivka, ktera
// je vypoctena programove (bez pouziti evaluatoru). Vypocet probiha
// ve funkci drawBezier().
// Pomoci mysi lze jednotlivymi ridicimi body pohybovat po obrazovce
// a tak menit tvar Bezierovy krivky.
// Klavesou ESC je mozne program ukoncit, klavesa F prepina zobrazeni
// na celou obrazovku, klavesou W se zobrazeni prepne zpet do okna.
//---------------------------------------------------------------------



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

#include <stdio.h>
#include <stdlib.h>
#include <math.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_LEFT      30                         // pozice leveho horniho rohu okna na desktopu
#define WINDOW_TOP       30
#define WINDOW_TITLE    "OpenGL evaluatory, priklad cislo 2.2" // titulek okna

typedef struct {                                    // novy datovy typ zapouzdrujici velikost okna
    unsigned int    width;
    unsigned int    height;
} Window;

Window window;

GLfloat ctrlPoints[4][3] = {                        // ridici body Bezierovy krivky
    {100.0, 100.0, 0.0},
    {400.0, 100.0, 0.0},
    {400.0, 400.0, 0.0},
    {100.0, 400.0, 0.0}
};

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
}



//---------------------------------------------------------------------
// Tato funkce programove vykresli Bezierovu krivku
//---------------------------------------------------------------------
void drawBezier(GLfloat points[][3])
{
    GLfloat mx,my,nx,ny,px,py,qx,qy;                // vahy vypoctene na zaklade B. polynomu
    GLfloat x, y;                                   // vycislovane souradnice
    GLfloat t;                                      // parametr Bezierovy krivky
    GLfloat x0=points[0][0];                        // prejmenovani ridicich bodu pro vetsi nazornost
    GLfloat y0=points[0][1];
    GLfloat x1=points[1][0];
    GLfloat y1=points[1][1];
    GLfloat x2=points[2][0];
    GLfloat y2=points[2][1];
    GLfloat x3=points[3][0];
    GLfloat y3=points[3][1];
    mx = x0;                                        // vypocet vah jednotlivych bodu
    my = y0;                                        // podle hodnot B. polynomu
    nx = 3.0 * (x1-x0);
    ny = 3.0 * (y1-y0);
    px = 3.0 * (x0+x2-2.0*x1);
    py = 3.0 * (y0+y2-2.0*y1);
    qx = x3-3.0*x2+3.0*x1-x0;
    qy = y3-3.0*y2+3.0*y1-y0;

    glBegin(GL_LINE_STRIP);
    for (t=0; t<1.0; t+=0.01) {                     // krivku vykreslit jako 100 linearnich useku
        x = mx+nx*t+px*t*t+qx*t*t*t;                // vypocet souradnic [x, y] na zaklade
        y = my+ny*t+py*t*t+qy*t*t*t;                // parametru t
        glVertex2f(x, y);
    }
    glEnd();
}



//---------------------------------------------------------------------
// Tato funkce programove vykresli ridici body Bezierovy krivky
//---------------------------------------------------------------------
void drawControlPoints(GLfloat points[][3]) {
    int d;

    glBegin(GL_POINTS);
    for (d=0; d<4; d++)                             // vykreslit ridici body
        glVertex2f(points[d][0], points[d][1]);
    glEnd();
    glEnable(GL_LINE_STIPPLE);                      // povoleni maskovani pixelu na care
    glBegin(GL_LINE_STRIP);
    for (d=0; d<4; d++)                             // vykreslit spojnice ridicich bodu
        glVertex2f(points[d][0], points[d][1]);
    glEnd();
    glDisable(GL_LINE_STIPPLE);                     // zakazani maskovani pixelu na care
}



//---------------------------------------------------------------------
// 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
    glEnable(GL_POINT_SMOOTH);                      // povoleni antialiasingu bodu
    glPointSize(5.0f);                              // velikost vykreslovanych ridicich bodu
    glLineStipple(2, 0x00ff);                       // nastaveni masky pri kresleni usecek
}



//---------------------------------------------------------------------
// 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
    glMatrixMode(GL_PROJECTION);                    // zacatek modifikace projekcni matice
    glLoadIdentity();                               // vymazani projekcni matice (=identita)
    glOrtho(0, w, 0, h, -1, 1);                     // mapovani abstraktnich souradnic do souradnic okna
    glScalef(1, -1, 1);                             // inverze y-ove osy, aby se y zvetsovalo smerem dolu
    glTranslatef(0, -h, 0);                         // posun pocatku do leveho horniho rohu
    window.width=w;                                 // zapamatovat si velikost okna
    window.height=h;
}



//--------------------------------------------------------------------
// Tato funkce je volana pri kazdem prekresleni okna
//--------------------------------------------------------------------
void onDisplay(void)
{
    char str[100];
    int d;

    glClear(GL_COLOR_BUFFER_BIT);                   // smazani barvoveho bufferu

    for (d=0; d<4; d++) {
        sprintf(str, "Control point: %d   x=%d   y=%d", d, (int)ctrlPoints[d][0], (int)ctrlPoints[d][1]);
        printGlutBitmapFont(str, GLUT_BITMAP_8_BY_13, 20, 20+(d<<4), 0.4f, 0.4f, 0.6f);
    }
    if (mouseState) sprintf(str, "Selected point: %d", selected);
    else            strcpy(str, "Selected point: none");
    printGlutBitmapFont(str, GLUT_BITMAP_8_BY_13, 20, 84, 0.8f, 0.4f, 0.4f);
    glColor3f(1.0f, 0.4f, 0.4f);
    drawBezier(ctrlPoints);                         // programove vykresleni Bezierovy krivky
    glColor3f(0.4f, 1.0f, 0.4f);
    drawControlPoints(ctrlPoints);                  // vykresleni ridicich bodu Bezierovy krivky
    glFlush();                                      // provedeni a vykresleni vsech zmen
    glutSwapBuffers();                              // a prohozeni predniho a zadniho bufferu
}



//---------------------------------------------------------------------
// Tato funkce je volana pri stlaceni ASCII klavesy
//---------------------------------------------------------------------
#ifdef __BORLANDC__
#pragma option -w-par                               // aby Borlandi prekladace nehlasily
#endif                                              // warningy ze argumenty nejsou pouzity
void onKeyboard(unsigned char key, int x, int y)
{
    if (key>='A' && key<='Z')                       // uprava velkych pismen na mala
        key+='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;      // full screen
        case 'w':   glutReshapeWindow(WINDOW_WIDTH, WINDOW_HEIGHT);// prepnuti zpet do okna
                    glutPositionWindow(WINDOW_LEFT, WINDOW_TOP);
                                        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)
{
    mouseState=0;                                   // konec presunu ridicich bodu
    if (state==GLUT_DOWN) {                         // pokud je tlacitko stlaceno
        int i;
        for (i=0; i<4; i++) {                       // najit ridici bod v miste kurzoru mysi
            if (abs(x-ctrlPoints[i][0])<10 &&
                abs(y-ctrlPoints[i][1])<10) {
                    mouseState=1;                   // bod byl nalezen
                    selected=i;
                    glutPostRedisplay();
                    return;
                }
        }
    }
    glutPostRedisplay();
}



//---------------------------------------------------------------------
// Callback funkce volana pri pohybu mysi
//---------------------------------------------------------------------
void onMouseMotion(int x, int y)
{
    if (mouseState) {                               // pokud je nejake tlacitko stlaceno
        ctrlPoints[selected][0]=x;                  // posun vybraneho ridiciho bodu
        ctrlPoints[selected][1]=y;
        glutPostRedisplay();                        // a prekresleni obrazovky
    }
}



//---------------------------------------------------------------------
// 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(WINDOW_LEFT, WINDOW_TOP);// 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
//---------------------------------------------------------------------