/*
    raum1a.c

    - Zeichnet eine kleine 3D-Szene
    - Verwendung der GL_PROJECTION- und GL_MODELVIEW-Matrizen
    - Einsatz eines Matrix-Stacks (glPushMatrix/glPopMatrix)
    - Einsatz des Attribut-Stacks (glPushAttrib/glPopAttrib)
    - Darstellung als Wireframe oder als Polygone
    - Tieferpuffer-Test für verdeckte Flächen

    - Leertaste:    Animation ein/aus
    - Taste 1:      Wireframe/Polygon umschalten
    - Taste 2:      Tiefenpuffer-Test ein/aus
    - Taste q:      Programm-Abbruch


    > Auch ohne Beleuchtung entsteht hier ein
      räumlicher Eindruck. Wodurch?

    > Wird der Tiefenpuffer-Test ausgeschaltet (Taste 2)
      so "stimmt" nichts mehr. Alle Objekte behalten
      ihre Position, aber die Verdeckungen entsprechen
      nicht der räumlichen Anordnung, sondern nur
      der Reihenfolge beim Rendern. Damit der Tieferpuffer-Test
      funktioniert, sind drei Schritte nötig:
      1. glutInitDisplayMode (... | GLUT_DEPTH);
      2. glClear (... | GL_DEPTH_BUFFER_BIT);
      3. glEnable (... | GL_DEPTH_TEST);

    > Wo ist der Ursprung des Koordinatensystems,
      und wo liegen die Achsen? Einfach mal die Achsen einzeichnen!

    > Die "Optik" der Kamera wird mit gluPerspective(...) bestimmt.
      Wie simuliert man ein Weitwinkel- oder ein Teleobjektiv?
      Was passiert, wenn man die "Nähe" weiter von
      der Kamera entfernt (größerer Wert), bzw. die "Ferne"
      näher an die Kamera heransetzt (kleinerer Wert)?

    > Die Position der Kamera ist so animiert (gluLookAt(...)),
      dass sie die Szene umkreist. Wie könnte
      man z.B. durch die Szene hindurchfliegen?

    > Die einzelnen Objekt werden in der display_func()
      mit glTranslatef() an ihre Position gesetzt
      und mit glScalef() in "Form" gebracht.
      glPushMatrix/glPopMatrix sind nötig, damit
      jedes Objekt wieder vom Original-Koordinatensystem
      ausgeht. Wenn man also die Aufrufe von glPushMatrix/glPopMatrix
      entfernt (kommentiert), dann...?
      Man kann die Szene umbauen oder neue Objekte hinzufügen;
      in Glut gibt es noch mehr Objekte: man -w glutSolid*

    - Der Attribut-Stacks (glPushAttrib/glPopAttrib)
      dient zum "Ordnung halten" bei den vielen
      GL-Einstellungen, die vorgenommen werden ("State Machine").
      Jedes glPushAttrib(...) speichert die aktuellen Werte
      derjenigen Attributgruppen (GL_..._BIT), die als Parameter
      angegeben werden. (Die Liste der Attributgruppen steht in der
      man-page zu glPushAttrib.) Alle Änderungen werden mit
      glPopAttrib() wieder rückgänging gemacht und auf den Zustand
      gesetzt, der beim Aufruf von glPushAttrib(...) galt.
      Beispielsweise sind alle Einstellungen, die mit
      glEnable/glDisable vorgenommen werden, in der Attributgruppe
      GL_ENABLE_BIT. (_BIT heißt wieder: Bit-Maske.)
*/



# 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 = 1,
                tiefenpuffer_test = 1,
                fenster_breite,
                fenster_hoehe;
static float    theta = 0.5 * M_PI;  /* der aktuelle Rotationswinkel */



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

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

    /* Säule aus Punkten */
    glPushMatrix ();
        glTranslatef (1, 0, 1);
        glScalef (1, 4, 1);
        glPointSize (5);
        glColor3f (0, 0, 0);
        glBegin (GL_POINTS);
        for (i = 0; i < n_points; i ++)
            glVertex3f (drand48 (), drand48 (), drand48 ());
        glEnd ();
    glPopMatrix ();

    /* Säule aus Linien */
    glPushMatrix ();
        glTranslatef (-1.5, 0, 1.5);
        glScalef (0.5, 4, 0.5);
        glLineWidth (2);
        glColor3f (0, 0, 0);
        glBegin (GL_LINES);
        for (i = 0; i < n_lines; i ++)
        {
            phi = 2 * M_PI * i / (float) n_lines;
            x = cos (phi);
            z = sin (phi);
            glVertex3f (x, 0, z);
            glVertex3f (x, 1, z);
        }
        glEnd ();
    glPopMatrix ();

    /* Säule aus Viereck-Flächen */
    glPushMatrix ();
        glTranslatef (1, 0, -2);
        glScalef (1, 4, 1);
        glBegin (GL_QUADS);
        for (i = 0; i <= n_quads; i ++)
        {
            z = i / (float) n_quads;
            glColor3f (z, z, z);
            glVertex3f (0, z, 0);
            glVertex3f (1, z, 0);
            glVertex3f (1, z, 1);
            glVertex3f (0, z, 1);
        }
        glEnd ();
    glPopMatrix ();

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

    /* kleiner "Teppich" */
    glPushMatrix ();
        glTranslatef (0, -0.01, 0);
        glColor3f (0.8, 0.8, 0.8);
        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);
        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     timer_func (int value)
/************************************/
{
    /* printf ("timer_func (value=%d)\n", value); */

    theta += 0.005;
    glutPostRedisplay ();
}



static void     display_func (void)
/*********************************/
{
    float       x, z;

    /* 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: Projektion = "Optik" */

    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    /*
    Parameter:
    gluPerspective (Öffnungswinkel, Seitenverhältnis, "Nähe", "Ferne");
    */
    gluPerspective (60, fenster_breite / (float) fenster_hoehe, 1, 30);

    /* Kamera: (MODEL)VIEW = "Stativ" */

    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity ();
    x = 8 * cos (theta);
    z = 8 * sin (theta);
    /*         Auge:      Target:    "oben":   */
    gluLookAt (x, 5, z,   0, 1, 0,   0, 1, 0);


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

        if (polygone_als_linien)
            glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);

        if (tiefenpuffer_test)
            glEnable (GL_DEPTH_TEST);

        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 '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 ("raum1");

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

    erzeuge_objekt_liste ();

    glutMainLoop ();

    return 0;
}


