// GLUT drawing code

// by including draw.h, we ensure that the exported prototypes
//   match the function definitions
#include "draw.h"

// Apple's annoying non-standard GL include location
#if defined(__APPLE__) || defined(MACOSX)
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif

#include <unistd.h>
#include <stdio.h>
#include <math.h>

using namespace std;

// style to use when drawing
DrawStyle drawStyle = DRAW_TEAPOT;

// update frame count
void updateFrame(int m)
{
    static int frame[4]={0,0,0,0};
    static int inc[4]={0,0,0,0};
    const int max=500;

    if (m==9)			// reset to start
	frame[0] = frame[1] = frame[2] = frame[3] = 0;
    else if (m==1) --inc[0];	// red
    else if (m==2) ++inc[0];
    else if (m==3) --inc[1];	// green
    else if (m==4) ++inc[1];
    else if (m==5) --inc[2];	// blue
    else if (m==6) ++inc[2];
    else if (m==7) --inc[3];	// alpha
    else if (m==8) ++inc[3];

    if (m==0 || m==9) {		// stop changing
	inc[0] = inc[1] = inc[2] = inc[3] = 0;
	glutPostRedisplay();
    }
    else if (inc[0] != 0 || inc[1] != 0 || inc[2] != 0 || inc[3] != 0)
	glutPostRedisplay();

    float color[4];
    for(int i=0; i<4; ++i) {
	frame[i] += inc[i];
	if (frame[i] < 0)   {inc[i] = 0; frame[i] = 0;}
	if (frame[i] > max) {inc[i] = 0; frame[i] = max;}
	color[i] = (float)frame[i]/max;
    }

    glColor4fv(color);
}

// draw teapot (scaled to 90, center of rotation translated to middle)
void drawTeapot()
{
    // draw teapot
    glPushMatrix();
    glTranslatef(-10,0,0);
    glutSolidTeapot(90);
    glPopMatrix();
}

// draw sphere
// hand coded since GLU reuses some texture coordinates along
// the seam, which isn't too good for my "unwrapping" examples
void drawSphere()
{
    // draw sphere
    glPushMatrix();
    glScalef(90,90,90);

    const int n_s=40, n_t=20;

    GLfloat ds=1./n_s, dt=1./n_t;
    GLfloat s=0, cos_s=1, sin_s=0;
    GLfloat cos_ds = cos(2*M_PI*ds), sin_ds = sin(2*M_PI*ds);
    GLfloat cos_dt = cos(  M_PI*dt), sin_dt = sin(  M_PI*dt);

    for(int i = 0; i<n_s; ++i) {
	GLfloat s1 = s+ds;
	GLfloat cos_s1 = cos_s*cos_ds - sin_s*sin_ds;
	GLfloat sin_s1 = sin_s*cos_ds + cos_s*sin_ds;

	glBegin(GL_QUAD_STRIP);

	GLfloat t=0, cos_t=1, sin_t=0;
	for (int j=0; j<=n_t; ++j) {
	    GLfloat t1 = t+dt;
	    GLfloat cos_t1 = cos_t*cos_dt - sin_t*sin_dt;
	    GLfloat sin_t1 = sin_t*cos_dt + cos_t*sin_dt;
	    
	    glTexCoord2f(s1, t);
	    glNormal3f(cos_s1*sin_t, sin_s1*sin_t, cos_t);
	    glVertex3f(cos_s1*sin_t, sin_s1*sin_t, cos_t);

	    glTexCoord2f(s, t);
	    glNormal3f(cos_s*sin_t, sin_s*sin_t, cos_t);
	    glVertex3f(cos_s*sin_t, sin_s*sin_t, cos_t);

	    t=t1; cos_t=cos_t1; sin_t=sin_t1;
	}
	glEnd();
	
	s=s1; cos_s=cos_s1; sin_s=sin_s1;
    }

    glPopMatrix();
}

// draw torus (donut)
// hand-coded since GLUT's doesn't provide texture coordinates
void drawDonut()
{
    // draw torus
    glPushMatrix();
    glScalef(90,90,90);

    // loosely based on code found in the MESA distribution of GLUT
    const int n_s=40, n_t=20;
    const float r=.5, R=1;

    GLfloat ds=1./n_s, dt=1./n_t;
    GLfloat s=0, cos_s=1, sin_s=0;
    GLfloat cos_ds = cos(2*M_PI*ds), sin_ds = sin(2*M_PI*ds);
    GLfloat cos_dt = cos(2*M_PI*dt), sin_dt = sin(2*M_PI*dt);

    for(int i = 0; i<n_s; ++i) {
	GLfloat s1 = s+ds;
	GLfloat cos_s1 = cos_s*cos_ds - sin_s*sin_ds;
	GLfloat sin_s1 = sin_s*cos_ds + cos_s*sin_ds;

	glBegin(GL_QUAD_STRIP);

	GLfloat t=0, cos_t=1, sin_t=0;
	for (int j=0; j<=n_t; ++j) {
	    GLfloat t1 = t+dt;
	    GLfloat cos_t1 = cos_t*cos_dt - sin_t*sin_dt;
	    GLfloat sin_t1 = sin_t*cos_dt + cos_t*sin_dt;
	    
	    GLfloat dist = R + r * cos_t;

	    glTexCoord2f(s1, t);
	    glNormal3f(cos_s1*cos_t, -sin_s1*cos_t, sin_t);
	    glVertex3f(cos_s1*dist, -sin_s1*dist, r*sin_t);
	    
	    glTexCoord2f(s,t);
	    glNormal3f(cos_s*cos_t, -sin_s*cos_t, sin_t);
	    glVertex3f(cos_s*dist, -sin_s*dist, r*sin_t);

	    t=t1; cos_t=cos_t1; sin_t=sin_t1;
	}
	glEnd();
	
	s=s1; cos_s=cos_s1; sin_s=sin_s1;
    }
    glPopMatrix();
}

// draw a square
void drawPlane()
{
    // draw square
    glBegin(GL_QUADS);
    glTexCoord2f(0,0); glNormal3f(0,0,1); glVertex3f(-90,-90,0);
    glTexCoord2f(1,0); glNormal3f(0,0,1); glVertex3f( 90,-90,0);
    glTexCoord2f(1,1); glNormal3f(0,0,1); glVertex3f( 90, 90,0);
    glTexCoord2f(0,1); glNormal3f(0,0,1); glVertex3f(-90, 90,0);
    glPushMatrix();
    glEnd();
}

// GLUT draw callback
// updates color animation
// draws currently selected object
extern "C" void
draw()
{
    // clear old screen contents
    glClearColor(1,1,1,1);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // send current frame
    updateFrame(-1);

    // draw appropriate object
    switch (drawStyle) {
    case DRAW_TEAPOT:
	drawTeapot();
	break;
    case DRAW_SPHERE:
	drawSphere();
	break;
    case DRAW_DONUT:
	drawDonut();
	break;
    case DRAW_PLANE:
	drawPlane();
	break;
    }

    // tell OpenGL to finish drawing and switch the double buffer
    glutSwapBuffers();
}

