/******************************************************************************
* Programm-Benutzung:
* Um eine Linie zu zeichnen, waehlen Sie mit der linken Maustaste eine 
* Starposition aus und ziehen Sie dann die Maus mit gedrueckter Taste
* zur Endposition.
* Um zwischen den beiden Algorithmen zu wechseln, oeffnen Sie das 
* Kontextmenue durch einen Rechtsklick oder druecken Sie 'b' fuer
* Bresenham bzw. 'd' fuer DDA.
* Zum Beenden des Programms druecken Sie 'q' oder Escape.
******************************************************************************/


#ifdef _WIN32
#define _USE_MATH_DEFINES
#include <windows.h>
#define GLUT_DISABLE_ATEXIT_HACK
#endif /* _WIN32 */

#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#include <GL/glut.h>

static int winwidth,winheight;
static float scalex = 25;
static float scaley = 15;
static int offsetx = 1;
static int offsety = 1;

static int valid = 0;
static int startx, starty;
static int stopx, stopy;

static int algorithm = 0;


/******************************************************************************
 * Hilfsfunktion zum mathematisch korrekten Runden eines Float-Werts
 *****************************************************************************/
int roundFloat (float pos) {
    return (int) floor(pos + 0.5f);
}


/******************************************************************************
 * Hilfsfunktion zum Zeichnen eines Markierungskreises
 *****************************************************************************/
void drawMark(float x, float y) {
    int i;
    int numSegments = 10;
    float radius = 1 / 2.0;

    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

    glBegin(GL_LINE_LOOP);
    for (i = 0; i < numSegments; i++) {
        glVertex3f(x + radius * sin(i * 2 * M_PI / (float)numSegments),
            y + radius * cos(i * 2 * M_PI / (float)numSegments), 0.0f);
    }
    glEnd();
}


/******************************************************************************
 * Hilfsfunktion zum Zeichnen eines Pixelkreises
 *****************************************************************************/
void setPixel(float x, float y) {
    int i;
    int numSegments = 10;
    float radius = 1 / 4.0;

    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

    glBegin(GL_TRIANGLE_FAN);
        for (i = 0; i < numSegments; i++) {
            glVertex3f(x + radius * sin(i * 2 * M_PI / (float) numSegments),
                y + radius * cos(i * 2 * M_PI / (float) numSegments), 0.0f); 
        } 
    glEnd(); 
}


/******************************************************************************
 * Funktion zum Zeichnen des Rasterisierungsgitters.
 *****************************************************************************/
void drawGrid(void) {
    int x, y;
    /* Start- und Endwerte des sichtbaren Gitters */
    int xstart = 0;  
    int xend   = 20;
    int ystart = 0;
    int yend   = 10;

    /* Zeichnen der Gitterlinien */
    glLineWidth(1.0f);
    
    /*
     * fuegen Sie hier den Code ein, um die Gitterlinien gestrichelt zu zeichnen
     */
    glColor3f(1.0, 1.0, 1.0);
    glBegin(GL_LINES);
    for (x = xstart; x <= xend; x++) {
        glVertex3i(x, ystart, 0); 
        glVertex3i(x, yend, 0); 
    }
    for (y = ystart; y <= yend; y++) {
        glVertex3i(xstart, y, 0); 
        glVertex3i(xend, y, 0); 
    }
    glEnd(); 

    /* Zeichnen der "normalen" (roten) Bildpunkte */
    glColor3f(1.0f, 0.0f, 0.0f);
    for (y = ystart; y <= yend; y++) {
        for (x = xstart; x <= xend; x++) {
            setPixel(x, y);
        }
    }
}


/******************************************************************************
 * Digital differential analyzer
 *****************************************************************************/
void dda(int xa, int ya, int xe, int ye) {
    /* Vervollstaendigen Sie die Implementierung des DDA-Algorithmus */

    int x = xa;
    float y = ya;
    float m = (float)(ye - ya) / (float)(xe - xa);
    for(x = xa; x <= xe; x++) {
        setPixel(x, roundFloat(y));
        y = y + m;
    }
}


/******************************************************************************
 * Bresenham
 *****************************************************************************/
void bresenham(int xa, int ya, int xe, int ye) {
    /* Vervollstaendigen Sie die Implementierung des Bresenham-Algorithmus */
    int x = xa;
    int y = ya;
    int dx = xe - xa;
    int dy = ye - ya;
    int error;

    setPixel(x,y);
    error = 2 * dy - dx;
    for (x = xa + 1; x <= xe; x++) {
        if (error <= 0)
            error += 2 * dy;
        else {
            error += 2 * (dy - dx);
            y++;
        }
        setPixel(x,y);
    }
}


/******************************************************************************
 * Funktion zum Zeichnen des Fensterinhalts.
 *****************************************************************************/
void drawgraphix(void) {
    /* vorherigen Fensterinhalt löschen. */
    glClear(GL_COLOR_BUFFER_BIT);
    glViewport(0, 0, winwidth, winheight);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    /* Pixel-Gitter zeichnen */
    drawGrid();

    /* Startmarkierung der Linie zeichnen */
    glColor3f(0.0f, 1.0f, 0.0f);
    drawMark(startx, starty);

    if (valid) { /* wenn die Linie gueltig ist */
        /* Endmarkierung der Linie zeichnen */
        drawMark(stopx, stopy);

        /* Linie dicker zeichnen. */
        glLineWidth(5.0f);
        glBegin(GL_LINES); { 
            glVertex3i(startx, starty, 0); 
            glVertex3i(stopx, stopy, 0); 
        } glEnd(); 
        glLineWidth(1.0f);

        /* rasterisierte Pixel zeichnen */
        switch (algorithm) {
        case 0:
            glColor3f(0.0f, 0.0f, 1.0f);
            bresenham(startx, starty, stopx, stopy);
            break;
        case 1:
            glColor3f(1.0f, 1.0f, 0.0f);
            dda(startx, starty, stopx, stopy);
            break;
        }
    }

    /* Zeichnen beenden und Fensterpuffer welchseln */
    glutSwapBuffers();
}


/******************************************************************************
 * Glut Callback-Funktion zum Zeichnen des Fensterinhalts
 *****************************************************************************/
void displayfunc(void) {
    drawgraphix();
}


/******************************************************************************
 * Glut Callback-Funktion fuer Groessenaenderungen des Fensters
 *****************************************************************************/
void reshapefunc(int width,int height) {
    winwidth = width;
    winheight = height;

    glDisable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);

    glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION); /* orthogonale Projektion */
    glLoadIdentity();
    gluOrtho2D(-offsetx, scalex - offsetx, -offsety, scaley - offsety);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}


/******************************************************************************
 * Glut Callback-Funktion fuer Maustasten
 *****************************************************************************/
void mousefunc(int button,int state,int x,int y) {
    if (button == GLUT_LEFT_BUTTON) {
        if (state == GLUT_DOWN) {
            /* beim Druecken der Maustaste eine neue Linie beginnen und Startposition merken */
            startx = roundFloat(x * scalex / winwidth - offsetx);
            starty = roundFloat((winheight - y) * scaley / winheight - offsety);
            valid = 0;
        } else {
            /* beim Loslassen der Maustaste die neue Linie beenden und Endposition merken */
            stopx = roundFloat(x * scalex / winwidth - offsetx);
            stopy = roundFloat((winheight - y) * scaley / winheight - offsety);
            valid = 1;
        }

        /* Fensterinhalt neu zeichnen */
        drawgraphix(); 
    }
}


/******************************************************************************
 * Glut Callback-Funktion fuer Mausbewegungen
 *****************************************************************************/
void motionfunc(int x,int y) {  
    stopx = roundFloat(x * scalex / winwidth - offsetx);
    stopy = roundFloat((winheight - y) * scaley / winheight - offsety);
    valid = 1;

    drawgraphix();
}


/******************************************************************************
 * Glut Callback-Funktion fuer das Kontextmenue
 * Aktiviert gleichzeitig einen der scan-conversion-Algorithmen
 *  0 fuer Bresenham
 *  1 fuer DDA
 * und zeichnet den Fensterinhalt neu
 *****************************************************************************/
void select_scanconversion(int value) {
    algorithm = value;
    drawgraphix();
}


/******************************************************************************
 * Glut Callback-Funktion zur Bearbeitung von Tastatureingaben
 *****************************************************************************/
void keyboardfunc(unsigned char key,int x,int y) {
    if ((key == 'q') || (key == 27)) { /* key 27 = ESC */
        exit(0);
    } else if (key == 'b') {
        select_scanconversion(0);
    } else if (key == 'd') {
        select_scanconversion(1);
    }
}


/******************************************************************************
 * Hauptfunktion des Programms
 *****************************************************************************/
int main(int argc, char **argv) {
    int menu;

    /* glut fenster initialisieren */
    glutInit(&argc, argv);
    glutInitWindowSize((int)(30 * scalex), (int)(30 * scaley));
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutCreateWindow("Scan Conversion Algorithms");

    /* glut callback funktionen registrieren. */
    glutDisplayFunc(displayfunc);
    glutReshapeFunc(reshapefunc);
    glutMouseFunc(mousefunc);
    glutMotionFunc(motionfunc);
    glutKeyboardFunc(keyboardfunc);

    /* glut contextmenue fuer die Algorithmenauswahl erzeugen */
    menu = glutCreateMenu(select_scanconversion);
    glutAddMenuEntry("Bresenham", 0);
    glutAddMenuEntry("DDA", 1);
    glutAttachMenu(GLUT_RIGHT_BUTTON);

    /* glut starten */
    glutMainLoop();

    return 0;
}

