/*
    licht2a.c

    - Zeigt weitere Features der OpenGl-Beleuchtung (basiert auf licht1a.c):
    - Punktlichtquelle
    - glänzend reflektierende Oberflächen
    - Lichtquelle in unterschiedlichen Koordinatensystemen

    - Leertaste:    Animation ein/aus
    - Taste 1:      Wireframe/Polygon umschalten
    - Taste 2:      Tiefenpuffer-Test ein/aus
    - Taste 3:      Weisse, parallele Lichtquelle ein/aus
    - Taste 4:      Punkt-Lichtquelle ein/aus
    - Taste 5:      Glänzende Oberfläche ein/aus
    - Taste 6:      Parallele Lichtquelle fest/mitbewegt
    - Taste q:      Programm-Abbruch


    > Die Oberflächen- oder "Material"eigenschaften sind hier
      ein wenig ausgefeilter. glColor3f(...) definiert die
      Körperfarben, dies wird mit Hilfe von glColorMaterial(...)
      eingestellt. -> siehe Aufruf von glColorMaterial.
      Ausserdem kann "glänzende" Reflektion (specular reflection) benutzt
      werden (Taste 5). Dazu muss der GL_SPECULAR-Parameter sowohl in
      glLightfv(...) als auch in glMaterialfv(...) angeben werden.
      Man kann beliebige RGB-Farben angeben, nicht nur weiß...
      Was ändert sich, wenn man die GL_SHININESS erhöht oder verkleinert?
      (Man sieht es am besten auf der Kugel.)

    > Die parallele, weisse Lichtquelle (Taste 3) kann entweder
      fest in der Szene positioniert sein (wie ein Studioscheinwerfer)
      oder aber in fester Lage relativ zur virtuellen Kamera
      (wie ein Autoscheinwerfer für den Fahrer, eine Taschenlampe
      in der Hand oder eine Grubenlampe am Helm.) -- umschalten mit
      Taste 6. Der Unterschied besteht darin, dass die Lichtrichtung
      in verschiedenen Koordinatensystemen definiert wird: in
      Augen-Koordinaten (Kamera) oder in Welt-Koordinaten (Szene).
      Entsprechend geschieht der Aufruf von
      glLightfv (GL_LIGHT0, GL_POSITION, ...);
      entweder VOR gluLookAt(...) oder DAHINTER.

    > Die punktförmige Lichtquelle (Taste 4) wird genauso wie
      die parallele Lichtquelle definiert, nur bekommt sie als
      GL_POSITION einen "echten" Punkt im Raum, d.h. die vierte
      Komponente in den homogenen Koordinaten ist 1.
      Nebenbei: Man kann natürlich alle Parameter der Lichtquellen,
      wie Farbe und Position oder Richtung animieren...

    > Mit glLightfv(...) lassen sich noch weitere Aspekte der
      GL-Lichtquellen modifizieren: Spotlights, entfernungsabhängige
      Abschwächung der Intensität, etc. Vielleicht macht die man-Page
      zu glLightfv ja neugierig...

    > Farbiges Licht auf farbigen Oberflächen stellt eine besondere
      Herausforderung dar -- es geschieht nämlich sehr leicht, dass man
      nicht mehr erkennt, wie die Szene "eigentlich" aussieht -- welche
      die Körperfarben und welche die Lichtfarben sind. Wetten?

    > F i n a l e -- die ultimative Herausforderung:
      Wer hat Lust, eine beleuchtete 3D-Szene mit Animation, Texturen
      und Blending zu kreieren? Sei es realistisch-konkret oder abstrakt.
      Aus Spaß an der Technik oder aus Spaß am Bild...
      Anregungen für Themen und technische Hilfe gebe ich gerne -- Peter.
*/



# include       <stdio.h>
# include       <stdlib.h>
# include       <math.h>
# include       <GL/glut.h>


# define        MILLISEC_PRO_FRAME      50



static GLuint   objekt_raum;
static int      animation_laeuft = 0,
                polygone_als_linien = 0,
                tiefenpuffer_test = 1,
                weisse_lichtquelle = 0,
                punktlichtquelle = 0,
                lichtquelle_fest_im_raum = 1,
                glaenzend = 0,
                fenster_breite,
                fenster_hoehe;
static float    theta = 0.5 * M_PI;  /* der aktuelle Rotationswinkel */



static void     timer_func (int value)
/************************************/
{
    /* printf ("timer_func (value=%d)\n", value); */

    theta += 0.005;
    glutPostRedisplay ();
}



static void     erzeuge_objekt_liste (void)
/*****************************************/
{
    int         i,
                n_lines = 20,
                n_quads = 10;
    float       phi,
                x, z;


    objekt_raum = glGenLists (1);
    glNewList (objekt_raum, GL_COMPILE);

    /* Icosaeder */
    glPushMatrix ();
        glTranslatef (1.5, 1, 1.5);
        glColor3f (1, 0, 0);
        glutSolidIcosahedron ();
    glPopMatrix ();

    /* Kugel */
    glPushMatrix ();
        glTranslatef (-1.5, 1, 1.5);
        glColor3f (1.0, 0.7, 0);
        glutSolidSphere (0.95, 20, 20);
    glPopMatrix ();

    /* Rundkegel */
    glPushMatrix ();
        glTranslatef (1.5, 0, -1.5);
        glRotatef (-90, 1, 0, 0);
        glColor3f (0.1, 0, 0.7);
        glutSolidCone (0.75, 4, 10, 3);
    glPopMatrix ();

    /* Quader-Säule */
    glPushMatrix ();
        glTranslatef (-1.5, 2, -1.5);
        glScalef (1, 4, 1);
        glColor3f (0.2, 0.2, 0.2);
        glutSolidCube (1);
    glPopMatrix ();

    /* kleiner "Teppich" */
    glPushMatrix ();
        glTranslatef (0, -0.01, 0);
        glColor3f (0.8, 0.8, 0.8);
        glNormal3f (0, 1, 0);
        glBegin (GL_QUADS);
            glVertex3f (-1, 0, -1);
            glVertex3f (+1, 0, -1);
            glVertex3f (+1, 0, +1);
            glVertex3f (-1, 0, +1);
        glEnd ();
    glPopMatrix ();

    /* Boden-Ebene */
    glPushMatrix ();
        glTranslatef (0, -0.02, 0);
        glColor3f (1, 1, 1);
        glNormal3f (0, 1, 0);
        glBegin (GL_QUADS);
            glVertex3f (-5, 0, -5);
            glVertex3f (+5, 0, -5);
            glVertex3f (+5, 0, +5);
            glVertex3f (-5, 0, +5);
        glEnd ();
    glPopMatrix ();

    glEndList ();  /* Ende objekt_raum */
}



static void     beleuchtung_einstellen (void)
/*******************************************/
{
    /* Licht-Parameter ähnlich wie in licht1a.c */
    /* Veränderungen sich kommentiert: */

    if (weisse_lichtquelle)
    {
        GLfloat weiss[4]      = { 1, 1, 1, 1 },
                schwarz[4]    = { 0, 0, 0, 1 };

        glLightfv (GL_LIGHT0, GL_DIFFUSE,  weiss);

        /* Lichtquelle soll nun auch "glänzendes" Licht ausstrahlen: */
        /* (Unphysikalisch, da der "Glanz" ja eine Eigenschaft des */
        /* Materials ist, siehe glMaterialfv(...) weiter unten.) */
        glLightfv (GL_LIGHT0, GL_SPECULAR, weiss);

        /* wie gehabt: */
        glLightfv (GL_LIGHT0, GL_AMBIENT,  schwarz);

        /* Wird in display_func() aufgerufen: */
        /* glLightfv (GL_LIGHT0, GL_POSITION, ...); */

        /* wie gehabt: */
        glEnable  (GL_LIGHT0);
    }

    if (punktlichtquelle)
    {
        GLfloat weiss[4]   = { 1, 1, 1, 1 },
                schwarz[4] = { 0, 0, 0, 1 };

        /* Wie oben bei der ersten Lichtquelle: */
        glLightfv (GL_LIGHT1, GL_DIFFUSE,  weiss);
        glLightfv (GL_LIGHT1, GL_SPECULAR, weiss);
        /* glLightfv (GL_LIGHT1, GL_POSITION, ...); */
        glEnable  (GL_LIGHT1);
    }

    if (weisse_lichtquelle || punktlichtquelle)
    {
        /* Wie in licht1a.c: */
        glEnable (GL_LIGHTING);
        glEnable (GL_NORMALIZE);
    }

    if (glaenzend)
    {
        GLfloat glanz[4] = { 1.0, 1.0, 1.0, 1.0 };

        /* Neu: "glänzende" (specular) Reflektion der Oberflächen. */
        /* Nur sichtbar, wenn die Lichtquellen auch GL_SPECULAR-"Licht" */
        /* ausstrahlen, siehe oben. */
        glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,  glanz);
        glMaterialf  (GL_FRONT_AND_BACK, GL_SHININESS, 30);
    }

    /* Neu: hierdurch verändern alle folgenden glColor3f(...)-Aufrufe die */
    /* aktuelle Oberflächen-Eigenschaften anstatt ignoriert zu werden. */
    /* glColor3f(...) stellt die Körperfarbe (für diffuse Reflektion) */
    /* ein und wirkt damit praktisch sinngemäß wie im Falle der */
    /* ausgeschalteten Beleuchtung. */
    glColorMaterial (GL_FRONT_AND_BACK, GL_DIFFUSE);
    glEnable (GL_COLOR_MATERIAL);
}



static void     display_func (void)
/*********************************/
{
    float       x = 8 * cos (theta),
                z = 8 * sin (theta);
    static GLfloat
                /* homogene Koordinaten: letzte 0 -> "Richtungsvektor": */
                lichtquellen_richtung[4] = { 0.0, 0.5, 1.0, 0 },
                /* homogene Koordinaten: letzte 1 -> Vektor = "Punkt im Raum" */
                punktquellen_position[4] = { 0.0, 1.0, 0.0, 1 };


    /* printf ("display_func()\n"); */

    if (animation_laeuft)
        glutTimerFunc (MILLISEC_PRO_FRAME, timer_func, 1);

    glClearColor (0.75, 0.75, 0.75, 0);
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    /* Kamera wie in raum1.c: */

    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    gluPerspective (60, fenster_breite / (float) fenster_hoehe, 1, 30);

    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity ();
    /* gluLookAt(...) weiter unten */


    /* Die aktuelle Einstellung bestimmer Attributgruppen sichern: */
    glPushAttrib (GL_ENABLE_BIT | GL_POLYGON_BIT | GL_LIGHTING_BIT);

    if (polygone_als_linien)
        glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);

    if (tiefenpuffer_test)
        glEnable (GL_DEPTH_TEST);

    /* Eigene Funktion zur besseren übersichtlichkeit: */
    beleuchtung_einstellen ();

    if (!lichtquelle_fest_im_raum)
        /* Feste Richtung der Lichtquelle bezogen auf die KAMERA-Position: */
        /* Aufruf VOR dem gluLookAt(...), also in Kamera-(Augen-)Koordinaten:*/
        glLightfv (GL_LIGHT0, GL_POSITION, lichtquellen_richtung);

    /*         Auge:      Target:    "oben":   */
    gluLookAt (x, 5, z,   0, 1, 0,   0, 1, 0);

    if (lichtquelle_fest_im_raum)
        /* Feste Richtung der Lichtquelle bezogen auf den RAUM: */
        /* Aufruf NACH dem gluLookAt(...), also in Raum-(Welt-)Koordinaten: */
        glLightfv (GL_LIGHT0, GL_POSITION, lichtquellen_richtung);

    /* Die Punktlichtquelle wird immer fest im Raum positioniert: */
    glLightfv (GL_LIGHT1, GL_POSITION, punktquellen_position);

    glCallList (objekt_raum);

    /* Die ursprüngliche Einstellung der gesicherten Attributgruppen */
    /* wiederherstellen: */
    glPopAttrib ();

    glutSwapBuffers ();
    glutReportErrors ();
}



static void     reshape_func (int width, int height)
/**************************************************/
{
    /* printf ("reshape_func (width=%d, height=%d)\n", width, height); */

    glViewport (0, 0, width, height);

    fenster_breite = width;
    fenster_hoehe = height;
}



static void     keyboard_func (unsigned char key, int x, int y)
/*************************************************************/
{
    switch (key)
    {
        case ' ':   /* Leertaste */
            if (animation_laeuft)
                animation_laeuft = 0;
            else
                animation_laeuft = 1;
            printf ("Animation %s\n", animation_laeuft ? "ein" : "aus");
            glutPostRedisplay ();
            break;

        case '1':
            polygone_als_linien = ! polygone_als_linien;
            printf ("Darstellung als %s\n",
                polygone_als_linien ? "Wireframe" : "Flächen");
            glutPostRedisplay ();
            break;

        case '2':
            tiefenpuffer_test = ! tiefenpuffer_test;
            printf ("Tiefenpuffer-Test (verdeckte Flächen) %s\n",
                tiefenpuffer_test ? "ein" : "aus");
            glutPostRedisplay ();
            break;

        case '3':
            weisse_lichtquelle = ! weisse_lichtquelle;
            printf ("Weisse Lichtquelle %s\n",
                weisse_lichtquelle ? "ein" : "aus");
            glutPostRedisplay ();
            break;

        case '4':
            punktlichtquelle = ! punktlichtquelle;
            printf ("Punktlichtquelle %s\n",
                punktlichtquelle ? "ein" : "aus");
            glutPostRedisplay ();
            break;

        case '5':
            glaenzend = ! glaenzend;
            printf ("Materialoberfläche %s\n",
                glaenzend ? "glaenzend" : "matt");
            glutPostRedisplay ();
            break;

        case '6':
            lichtquelle_fest_im_raum = ! lichtquelle_fest_im_raum;
            printf ("Weisse Lichtquelle %s\n", lichtquelle_fest_im_raum ?
                "fest im Raum positioniert" : "mit der Kamera mitbewegt");
            glutPostRedisplay ();
            break;

        case 'q':
        case 'Q':
            exit (0);
    }
}



extern int      main (int argc, char **argv)
/******************************************/
{
    glutInit (&argc, argv);
    glutInitDisplayMode (GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);

    glutInitWindowPosition (200, 100);
    glutInitWindowSize (600, 600);
    glutCreateWindow ("licht2");

    glutReshapeFunc (reshape_func);
    glutDisplayFunc (display_func);
    glutKeyboardFunc (keyboard_func);

    erzeuge_objekt_liste ();

    glutMainLoop ();

    return 0;
}


