//---------------------------------------------------------------------
// Ukazkovy priklad ke clanku fraktaly v pocitacove grafice.
// Autor: Pavel Tisnovsky
//
// Jednoduchy program, ktery slouzi pro zobrazeni souboru s ulozenymi
// trojuhelniky.
// Pro osvetleni telesa je pouzito dvou bodovych svetelnych zdroju.
// Jeden svetelny zdroj zustava umisteny pevne v prostoru, druhy se
// pohybuje soucasne s kamerou.
// Pomoci leveho tlacitka mysi lze telesem otacet, prave tlacitko slouzi
// k priblizeni nebo vzdaleni telesa.
//---------------------------------------------------------------------

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

#define SCALE 10.0                              // zmena meritka

enum {                                          // operace, ktere se mohou provadet s mysi:
    ROTATE,                                     // rotace objektu
    TRANSLATE,                                  // posun objektu
} operation=ROTATE;

int   gl_list;                                  // cislo display listu s nactenym objektem

int   xnew=0, ynew=0, znew=20;                  // soucasna pozice mysi, ze ktere se pocitaji rotace a posuvy
int   xold=0, yold=0, zold=20;                  // minula pozice mysi, ze ktere se pocitaji rotace a posuvy
int   xx, yy, zz;                               // bod, ve kterem se nachazi kurzor mysi

int   windowWidth;                              // sirka okna
int   windowHeight;                             // vyska okna

// parametry, ktere ovlivnuji osvetleni
GLfloat materialAmbient[]={0.4f, 0.4f, 0.4f, 1.0f};  // ambientni slozka barvy materialu
GLfloat materialDiffuse[]={0.8f, 0.4f, 0.4f, 1.0f};  // difuzni slozka barvy materialu
GLfloat materialSpecular[]={1.0f, 1.0f, 1.0f, 1.0f}; // barva odlesku
GLfloat materialShininess[]={50.0f};                 // faktor odlesku
GLfloat light_position1[]={1.0f, 0.0f,  1.0f, 1.0f}; // pozice prvniho svetla
GLfloat light_position2[]={0.0f, 1.0f,100.0f, 1.0f}; // pozice druheho svetla
GLfloat light_color1[]={1.0f, 1.0f, 1.0f};           // barva prvniho svetla
GLfloat light_color2[]={1.0f, 1.0f, 0.0f};           // barva druheho svetla



//---------------------------------------------------------------------
// Nacteni obsahu souboru do display listu
//---------------------------------------------------------------------
void loadObject(char *fileName) {
#define MIN 1e-10
#define MAX 1e+10
    // makra pro zjisteni maxima a minima
#define SET_MIN(a, min) if ((a)<(min)) (min)=(a)
#define SET_MAX(a, max) if ((a)>(max)) (max)=(a)

    float x1, y1, z1, x2, y2, z2, x3, y3, z3;
    float xmin=MAX, xmax=MIN, ymin=MAX, ymax=MIN, zmin=MAX, zmax=MIN;
    float x0, y0, z0;
    int   line=0;
    FILE *fin=fopen(fileName, "r");

    gl_list=glGenLists(1);                      // zjistime si pro jistotu cislo volneho listu
    glNewList(gl_list, GL_COMPILE);

    // nacist cely soubor a zjistit meze objektu
    while (fscanf(fin, "%f %f %f %f %f %f %f %f %f\n", &x1, &y1, &z1,
                &x2, &y2, &z2,
                &x3, &y3, &z3)==9) {
        SET_MIN(x1, xmin);    SET_MIN(x2, xmin);    SET_MIN(x3, xmin);
        SET_MAX(x1, xmax);    SET_MAX(x2, xmax);    SET_MAX(x3, xmax);
        SET_MIN(y1, ymin);    SET_MIN(y2, ymin);    SET_MIN(y3, ymin);
        SET_MAX(y1, ymax);    SET_MAX(y2, ymax);    SET_MAX(y3, ymax);
        SET_MIN(z1, zmin);    SET_MIN(z2, zmin);    SET_MIN(z3, zmin);
        SET_MAX(z1, zmax);    SET_MAX(z2, zmax);    SET_MAX(z3, zmax);
    }
    printf("Boundary:   [%f, %f, %f] - [%f, %f, %f]\n", xmin, ymin, zmin, xmax, ymax, zmax);

    // vypocet stredu objektu
    x0=(xmax+xmin)/2.0;
    y0=(ymax+ymin)/2.0;
    z0=(zmax+zmin)/2.0;
    printf("Center:     [%f, %f, %f]\n", x0, y0, z0);

    fseek(fin, 0L, SEEK_SET);
    // znovunacteni vsech radku ze souboru
    while (fscanf(fin, "%f %f %f %f %f %f %f %f %f\n", &x1, &y1, &z1,
                &x2, &y2, &z2,
                &x3, &y3, &z3)==9) {
        float nx, ny, nz;
        float vx1, vy1, vz1;
        float vx2, vy2, vz2;
        float d;

        // posun objektu do stredu [0,0,0]
        x1-=x0;    x2-=x0;    x3-=x0;
        y1-=y0;    y2-=y0;    y3-=y0;
        z1-=z0;    z2-=z0;    z3-=z0;

        // zmena meritka pro velke objekty
        x1/=SCALE;
        y1/=SCALE;
        z1/=SCALE;
        x2/=SCALE;
        y2/=SCALE;
        z2/=SCALE;
        x3/=SCALE;
        y3/=SCALE;
        z3/=SCALE;

        // vypocet dvou vektoru na plosce
        vx1=x2-x1;
        vy1=y2-y1;
        vz1=z2-z1;
        vx2=x3-x1;
        vy2=y3-y1;
        vz2=z3-z1;

        // vypocet normaloveho vektoru plosky
        nx=vy1*vz2-vy2*vz1;
        ny=vx2*vz1-vx1*vz2;
        nz=vx1*vy2-vx2*vy1;

        // normalizace normaloveho vektoru
        d=sqrt(nx*nx+ny*ny+nz*nz);
        if (d>1e-10) {
            nx/=d;
            ny/=d;
            nz/=d;
        }
        else {
            nx=1.0; ny=0.0; nz=0.0;
        }

        // zapis trojuhelniku do display listu
        glBegin(GL_TRIANGLE_STRIP);
            glNormal3f(nx, ny, nz);
            glVertex3f(x1, y1, z1);
            glVertex3f(x2, y2, z2);
            glVertex3f(x3, y3, z3);
        glEnd();
        line++;
    }
    fclose(fin);
    printf("Triangles:  %d\n", line);
    glEndList();                                // konec zadavani display listu

//    znew=zold=-zmax;
}



//---------------------------------------------------------------------
// Funkce pro inicializaci vykreslovani
//---------------------------------------------------------------------
void onInit(char *fileName)
{
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);       // barva pozadi obrazku
    glClearDepth(1.0f);                         // implicitni hloubka ulozena v pameti hloubky
    glEnable(GL_DEPTH_TEST);                    // povoleni funkce pro testovani hodnot v pameti hloubky
    glDepthFunc(GL_LESS);                       // funkce pro testovani fragmentu
    glShadeModel(GL_SMOOTH);                    // nastaveni stinovaciho rezimu
    glPolygonMode(GL_FRONT, GL_FILL);           // nastaveni rezimu vykresleni modelu
    glPolygonMode(GL_BACK, GL_FILL);            // jak pro predni tak pro zadni steny
    glDisable(GL_CULL_FACE);                    // zadne hrany ani steny se nebudou odstranovat
    glMaterialfv(GL_FRONT, GL_AMBIENT, materialAmbient);    // nastaveni ambientni slozky barvy materialu
    glMaterialfv(GL_FRONT, GL_DIFFUSE, materialDiffuse);    // nastaveni difuzni slozky barvy materialu
    glMaterialfv(GL_FRONT, GL_SPECULAR, materialSpecular);  // nastaveni barvy odlesku
    glMaterialfv(GL_FRONT, GL_SHININESS, materialShininess);// nastaveni faktoru odlesku
    glLightfv(GL_LIGHT0, GL_POSITION, light_position1);     // nastaveni pozice prvniho svetla
    glLightfv(GL_LIGHT1, GL_POSITION, light_position2);     // nastaveni pozice druheho svetla
    glLightfv(GL_LIGHT0, GL_DIFFUSE, light_color1);         // nastaveni barvy prvniho svetla
    glLightfv(GL_LIGHT1, GL_DIFFUSE, light_color2);         // nastaveni barvy druheho svetla
    glEnable(GL_LIGHTING);                      // globalni povoleni stinovani
    glEnable(GL_LIGHT0);                        // povoleni prvniho svetla
    glEnable(GL_LIGHT1);                        // povoleni prvniho svetla
    loadObject(fileName);
}



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



//---------------------------------------------------------------------
// Nastaveni perspektivni projekce
//---------------------------------------------------------------------
void setPerspectiveProjection(void)
{
    glMatrixMode(GL_PROJECTION);                // zacatek modifikace projekcni matice
    glLoadIdentity();                           // vymazani projekcni matice (=identita)
    gluPerspective(70.0, (double)windowWidth/(double)windowHeight, 0.1f, 10000.0f);// nastaveni perspektivni kamery
    glMatrixMode(GL_MODELVIEW);                 // bude se menit modelova matice
    glLoadIdentity();                           // nahrat jednotkovou matici
}



//--------------------------------------------------------------------
// Vykresleni objektu
//--------------------------------------------------------------------
void drawObjectNormal(void)
{
    glCallList(gl_list);                        // vykresleni naseho objektu
}



//--------------------------------------------------------------------
// Tato funkce je volana pri kazdem prekresleni okna
//--------------------------------------------------------------------
void onDisplay(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// vymazani barvoveho bufferu i pameti hloubky
    setPerspectiveProjection();                 // nastaveni perspektivni kamery
    glTranslatef(0.0f, 0.0f, -50.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);
    //glPushMatrix();                             // ulozeni matice na zasobnik a zmena pozice druheho svetla
    //glLightfv(GL_LIGHT1, GL_POSITION, light_position2);
    //glPopMatrix();
    drawObjectNormal();                         // vykresleni objektu
    glFlush();                                  // provedeni a vykresleni vsech zmen
    glutSwapBuffers();                          // a prohozeni predniho a zadniho bufferu
}



//---------------------------------------------------------------------
// Tato funkce je volana pri stlaceni ASCII klavesy
//---------------------------------------------------------------------
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
                    // nastaveni rezimu vykreslovani plosek
        case '1':   glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); glutPostRedisplay(); break;
        case '2':   glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);  glutPostRedisplay(); break;
        case '3':   glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);  glutPostRedisplay(); break;
        default:                        break;
    }
}



//---------------------------------------------------------------------
// Tato funkce je volana pri stisku ci pusteni tlacitka mysi
//---------------------------------------------------------------------
void onMouseButton(int button, int state, int x, int y)
{
    if (button==GLUT_LEFT_BUTTON) {             // pri zmene stavu leveho tlacitka
        operation=ROTATE;
        if (state==GLUT_DOWN) {                 // pri stlaceni tlacitka
            xx=x;                               // zapamatovat pozici kurzoru mysi
            yy=y;
        }
        else {                                  // pri pusteni tlacitka
            xold=xnew;                          // zapamatovat novy pocatek
            yold=ynew;
        }
        glutPostRedisplay();                    // prekresleni sceny
    }
    if (button==GLUT_RIGHT_BUTTON) {
        operation=TRANSLATE;
        if (state==GLUT_DOWN) zz=y;             // pri stlaceni tlacitka zapamatovat polohu kurzoru mysi
        else zold=znew;                         // pri pusteni tlacitka zapamatovat novy pocatek
        glutPostRedisplay();                    // prekresleni sceny
    }
}



//---------------------------------------------------------------------
// Tato funkce je volana pri pohybu mysi se stlacenym tlacitkem.
// To, ktere tlacitko je stlaceno si musime predem zaznamenat do
// globalni promenne stav ve funkci onMouseButton()
//---------------------------------------------------------------------
void onMouseMotion(int x, int y)
{
    switch (operation) {
        case ROTATE:                            // stav rotace objektu
            xnew=xold+x-xx;                     // vypocitat novou pozici
            ynew=yold+y-yy;
            glutPostRedisplay();                // a prekreslit scenu
            break;
        case TRANSLATE:                         // stav priblizeni/oddaleni objektu
            znew=zold+y-zz;                     // vypocitat novou pozici
            glutPostRedisplay();                // a prekreslit scenu
            break;
    }
}



//---------------------------------------------------------------------
// 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(450, 450);               // pocatecni velikost okna
    glutCreateWindow("RAW Viewer");             // 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(onMouseButton);               // registrace funkce volane pri stlaceni ci pusteni tlacitka
    glutMotionFunc(onMouseMotion);              // registrace funkce volane pri pohybu mysi se stlacenym tlacitkem
    if (argc<2) {                               // kontrola, zda je zadan vstupni soubor
        puts("Usage raw_view file.raw\n");
        return 1;
    }
    onInit(argv[1]);                            // inicializace vykreslovani
    glutMainLoop();                             // nekonecna smycka, kde se volaji zaregistrovane funkce
    return 0;                                   // navratova hodnota vracena operacnimu systemu
}



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