/* 
 * Texture feedback demo
 * Simon Green 6/97
 * si@sgi.com
 *
 * Compile:  
 * cc -o feedback feedback.c -lglut -lGLU -lGL -lXmu -lXext -lX11 -lm
 *
 * Description:
 * This is an old effect - it's kind of like pointing a video camera at a TV
 * displaying the signal from itself.
 * 
 * It also demonstrates the OpenGL 1.1 glCopyTexImage2D function to copy
 * texture data direct from the framebuffer. You'll need a machine with
 * reasonably fast texture mapping for it to be fun.
 *
 * Usage:
 * Start it up, hold down the left mouse button and move the mouse up and down
 * and left and right slowly. Play with the menus. Enjoy!
 *
 * Left mouse button - zoom / rotate
 * Right mouse button - translate (advanced users only)
 *
 * Bugs:
 * Don't try resizing the window. Don't stare at it for too long.
 */

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

#ifdef GL_VERSION_1_1

/* Some <math.h> files do not define M_PI... */
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

#define MAXSIZE 512             /* Set this to your maximum texture size (square)
*/
#define TEXT_MESSAGE    "OpenGL"

float ang = 2.0;
float scale = 1.05;
float tx = 0.0, ty = 0.0;

int oldx, oldy;
int lmb = 0;
int mmb = 0;
int autospin = 0;
float atime = 0.0;

int smooth = 1;
int seedmode = 0;
float seedsize = 0.1;

int primtype = GL_LINES;
float primsize = 1.0;
int nprims = 10;

float r, g, b;
float dr, dg, db;
int randomcolours = 0;


/* returns a random floating point number between 0.0 and 1.0 */
float frand(void) {
    return (float) (rand() / 32767.0);
}

void init_colours(float speed)
{
    r = frand(); g = frand(); b = frand();
    dr = frand() / speed; dg = frand() / speed; db = frand() / speed;    
}

void bounce(float *n, float *dn)
{
    *n += *dn;
    if (*n > 1.0) { *n = 1.0; *dn = -*dn; }
    if (*n < 0.0) { *n = 0.0; *dn = -*dn; }
}

/* generate pretty colours by bouncing rgb values up and down */
void set_colour(void)
{
    if (randomcolours) {
        glColor3f(frand(), frand(), frand());
    } else {
        bounce(&r, &dr);
        bounce(&g, &dg);
        bounce(&b, &db);
        glColor3f(r, g, b);
    }
}


/* seed pattern with some random primitives in centre of screen */
void seed(void)
{
    int i;
        
    glBegin(primtype);
    for(i=0; i<nprims; i++) {
        set_colour();
        glVertex2f((frand() - 0.5) * seedsize, (frand() - 0.5) * seedsize);
    }
    glEnd();
}


/* seed pattern with a circular pattern */
void seed_circle(void)
{   
    int i;
    double a;
        
    glBegin(primtype);
        for(i=0; i<nprims; i++) {
        a = ((double) i * 2 * M_PI) / nprims;
        glColor4f(0.0, 0.0, 0.0, 1.0);
        glVertex2d(0.0, 0.0);
        set_colour();
        glVertex2d(sin(a) * (seedsize / 2.0), cos(a) * (seedsize / 2.0));
    }
    glEnd();    
}

/* bit of a silly one, this */
void seed_teapot(void)
{
    glLoadIdentity();
    glTranslatef((frand() - 0.5) * seedsize, (frand() - 0.5) * seedsize, 0.0);
    glRotatef(frand() * 360.0, frand(), frand(), frand());

    set_colour();
    glutWireTeapot(seedsize);
}


/* seed with text string */
void seed_text(char *string)
{
    int i;
    int width = 0;

    for (i = 0; i < (int) strlen(string); i++) {
        width += glutStrokeWidth(GLUT_STROKE_ROMAN, string[i]);
    }

    glLoadIdentity();
    glScalef(seedsize / 100.0, seedsize / 100.0, seedsize / 100.0);
    glTranslatef(-width / 2.0, -50.0, 0.0);

    for (i = 0; i < (int) strlen(string); i++) {
        set_colour();
        glutStrokeCharacter(GLUT_STROKE_ROMAN, string[i]);
    }
}



/* copy screen image to texture memory */
void grab_screen(void)
{
    glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, MAXSIZE, MAXSIZE,
0);

    if (smooth) {
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    } else {
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    }
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,  GL_DECAL);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
}


void reset(void)
{
    ang = 0.0;
    scale = 1.0;
    tx = ty = 0.0;
    autospin = 0;

    glClear(GL_COLOR_BUFFER_BIT);
    grab_screen();
}


void redraw(void)
{
    glClear(GL_COLOR_BUFFER_BIT);           

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(tx, ty, 0.0);
    glRotatef(ang, 0.0, 0.0, 1.0);
    glScalef(scale, scale, scale);

    if (autospin) {
        ang = 3.0 * cos(atime);
        scale = 1.0 + ( sin(atime / 4.0) * 0.1) ;
        atime += 0.01;
    }

    /* draw feedback square */
    glEnable(GL_TEXTURE_2D);
    glBegin(GL_QUADS);
        glTexCoord2f(0.0, 0.0); glVertex2f(-1.0, -1.0);
        glTexCoord2f(1.0, 0.0); glVertex2f(1.0, -1.0);
        glTexCoord2f(1.0, 1.0); glVertex2f(1.0, 1.0);
        glTexCoord2f(0.0, 1.0); glVertex2f(-1.0, 1.0);
    glEnd();
    glDisable(GL_TEXTURE_2D);
    
    /* draw square outline */
    glColor3f(1.0, 1.0, 1.0);
    glBegin(GL_LINE_LOOP);
        glVertex2f(-1.0, -1.0);
        glVertex2f(1.0, -1.0);
        glVertex2f(1.0, 1.0);
        glVertex2f(-1.0, 1.0);
    glEnd();
    
    /* seed pattern */
    glLoadIdentity();
    switch(seedmode) {
    case 0:
        seed();
        break;
    case 1:
        seed_circle();
        break;
    case 2:
        seed_teapot();
        break;
    case 3:
        seed_text(TEXT_MESSAGE);
        break;
    }

    /* grab screen as texture */
    grab_screen();
     
    glutSwapBuffers();
}


void mouse(int button, int state, int x, int y)
{
    if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
        lmb = 1;
        oldx = x; oldy = y;
    }
    if (button == GLUT_LEFT_BUTTON && state == GLUT_UP) {
        lmb = 0;
    }
  
    if (button == GLUT_MIDDLE_BUTTON && state == GLUT_DOWN) {
        mmb = 1;
        oldx = x; oldy = y;
    }
    if (button == GLUT_MIDDLE_BUTTON && state == GLUT_UP) {
        mmb = 0;
    }

}

void motion(int x, int y)
{
    if (lmb) {
        ang += ((oldx - x) / 4.0 );
        scale += ((oldy - y) / 400.0);

        oldx = x; oldy = y;
        glutPostRedisplay();
    }
    if (mmb) {
        tx += ((float) (x - oldx)) / 500.0;
        ty += ((float) (oldy - y)) / 500.0;

        oldx = x; oldy = y;
        glutPostRedisplay();      
    }
}


void main_menu(int i)
{
    switch(i) {
    case 1:
        autospin = !autospin;
        atime = 0.0;
        break;
    case 2:
        reset();
        break;
    case 3:
        exit(0);
    }
}

void mode_menu(int i)
{
    smooth = i;
}

void seed_menu(int i)
{
    seedmode = i;
}

void prim_menu(int i)
{
    primtype = i;
}

void size_menu(int i)
{
    seedsize = 1.0 / i;
}

void psize_menu(int i)
{
    primsize = (float) i;

    glPointSize(primsize);
    glLineWidth(primsize);
}


void no_menu(int i)
{
    nprims = i;
}


void colour_menu(int i)
{
    switch(i) {
    case 0:
        init_colours(500.0);
        randomcolours = 0;
        break;
    case 1:
        init_colours(100.0);
        randomcolours = 0;
        break;
    case 2:
        init_colours(10.0);
        randomcolours = 0;
        break;
    case 3:
        randomcolours = 1;
        break;
    }
}

int mainmenu;
int modemenu, seedmenu, primmenu, sizemenu, psizemenu, nomenu,
colourmenu;

void create_menus(void)
{
    modemenu = glutCreateMenu(mode_menu);    
    glutAddMenuEntry("Chunky", 0);
    glutAddMenuEntry("Smooth", 1);

    seedmenu = glutCreateMenu(seed_menu);    
    glutAddMenuEntry("Primitives", 0);
    glutAddMenuEntry("Circle", 1);
    glutAddMenuEntry("Teapot", 2);
    glutAddMenuEntry("Text", 3);    

    colourmenu = glutCreateMenu(colour_menu);    
    glutAddMenuEntry("Slow", 0);
    glutAddMenuEntry("Medium", 1);
    glutAddMenuEntry("Fast", 2);
    glutAddMenuEntry("Random", 3);
    
    primmenu = glutCreateMenu(prim_menu);    
    glutAddMenuEntry("Dots", GL_POINTS);
    glutAddMenuEntry("Lines", GL_LINES);
    glutAddMenuEntry("Triangles", GL_TRIANGLES);

    sizemenu = glutCreateMenu(size_menu);
    glutAddMenuEntry("Tiny", 20);
    glutAddMenuEntry("Small", 10);
    glutAddMenuEntry("Medium", 5);
    glutAddMenuEntry("Large", 2);

    nomenu = glutCreateMenu(no_menu);
    glutAddMenuEntry("1", 1);
    glutAddMenuEntry("2", 2);
    glutAddMenuEntry("3", 3);
    glutAddMenuEntry("5", 5);
    glutAddMenuEntry("10", 10);
    glutAddMenuEntry("20", 20);
    glutAddMenuEntry("30", 30);
    glutAddMenuEntry("50", 50);

    psizemenu = glutCreateMenu(psize_menu);
    glutAddMenuEntry("1", 1);
    glutAddMenuEntry("3", 3);
    glutAddMenuEntry("5", 5);

    mainmenu = glutCreateMenu(main_menu);
    glutAddSubMenu("Texture mode", modemenu);
    glutAddSubMenu("Seed mode", seedmenu);
    glutAddSubMenu("Seed size", sizemenu);
    glutAddSubMenu("Colours", colourmenu);

    glutAddMenuEntry("-----------", -1);

    glutAddSubMenu("Primitive", primmenu);
    glutAddSubMenu("Number", nomenu);
    glutAddSubMenu("Size", psizemenu);

    glutAddMenuEntry("-----------", 0);
    glutAddMenuEntry("Autospin", 1);
    glutAddMenuEntry("Reset", 2);
    glutAddMenuEntry("Quit", 3);
    
    glutAttachMenu(GLUT_RIGHT_BUTTON);
}


int main(int argc, char **argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
    glutCreateWindow("feedback");
    glutReshapeWindow(MAXSIZE, MAXSIZE);
    glutDisplayFunc(redraw);
    glutIdleFunc(redraw);
    glutMouseFunc(mouse);
    glutMotionFunc(motion);
  
    create_menus();
    init_colours(100.0);
  
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-1.0, 1.0, -1.0, 1.0, 0.0, 1.0);
    glViewport(0, 0, MAXSIZE, MAXSIZE);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glutMainLoop();
    return 0;             /* ANSI C requires main to return int. */
}

#else

int
main(int argc, char** argv)
{
    fprintf (stderr, "This program demonstrates a feature which is not in OpenGL Version 1.0.\n");
    fprintf (stderr, "If your implementation of OpenGL Version 1.0 has the right extensions,\n");
    fprintf (stderr, "you may be able to modify this program to make it run.\n");
    return 0;
}

#endif