/*
	The Sierpinski gasket in 3D through successive iterations, with full rotation
	capability in the image space.
   
	Source file to be used with
	Cunningham, Computer Graphics: Programming in OpenGL for Visual Communication, Prentice-Hall, 2007

	Intended for class use only
*/
#include <GLUT/glut.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>

// function prototypes
void myinit(void);
void drawAxis(void);
void drawAxes(void);
void display( void );
void gasket(void);
void reshape(int w,int h);
void keyboard(unsigned char key, int x, int y);
void animate(void);

#define NPOINTS 50000
#define INITBOX 10.0
#define GRAIN 512
#define ANGLE 3.

typedef GLfloat point3[3];
point3 gasketPoints[NPOINTS];	// the gasket points that move towards the attraction points
//	Note below that 4.33 = 5*sqrt(3)/2
point3 fixedPoints[4] = { {  5.0,  0.0 ,  -2.5},
						  { -2.5,  4.33,  -2.5},
						  { -2.5, -4.33,  -2.5},
						  {  0.0,  0.0 ,  4.33} };	// the attraction points

static char ch; // character from the keyboard that controls the rotation
GLfloat xAngle = 0., yAngle = 0., zAngle = 0.;

void myinit(void)
{ 
	int i;

	glClearColor( 0.3, 0.3, 0.3, 0.0 );
	glEnable(GL_DEPTH_TEST); // allow z-buffer display
	
//	initialize random number generator
	srand( clock() );
	
	//	generate NPOINTS points in the cube from -INITBOX to INITBOX in each dimension
	for (i=0; i<NPOINTS; i++) {
		gasketPoints[i][0] = -INITBOX+2.0*INITBOX*(float)(rand()%GRAIN)/(float)GRAIN;
		gasketPoints[i][1] = -INITBOX+2.0*INITBOX*(float)(rand()%GRAIN)/(float)GRAIN;
		gasketPoints[i][2] = -INITBOX+2.0*INITBOX*(float)(rand()%GRAIN)/(float)GRAIN;
	}
}

void drawAxis(void)
{
	#define RAD 0.03
// draw one axis, the Z-axis, as the template for all axes
//  Draw the standard axis in Z-orientation
    glBegin(GL_QUAD_STRIP);
      glVertex3f( RAD, RAD,  6.0 );
      glVertex3f( RAD, RAD, -6.0 );
      glVertex3f(-RAD, RAD,  6.0 );
      glVertex3f(-RAD, RAD, -6.0 );
      glVertex3f(-RAD,-RAD,  6.0 );
      glVertex3f(-RAD,-RAD, -6.0 );
      glVertex3f( RAD,-RAD,  6.0 );
      glVertex3f( RAD,-RAD, -6.0 );
      glVertex3f( RAD, RAD,  6.0 );
      glVertex3f( RAD, RAD, -6.0 );
    glEnd();
    glPushMatrix();
    glTranslatef( 0.0, 0.0, 6.0 );
    glutSolidCone( 0.2, 0.3, 20, 20 );
    glPopMatrix();
}

void drawAxes(void)
{
//  Axes are to be colored white
    GLfloat white[]={1.0, 1.0, 1.0, 1.0};
	glColor4fv(white);
//  Draw the Z-axis
    drawAxis();
//  Draw the standard axis in X-orientation
    glPushMatrix();
    glRotatef( 90.0, 0.0, 1.0, 0.0 );
    drawAxis();
    glPopMatrix();
//  Draw the standard axis in Y-orientation
    glPushMatrix();
    glRotatef( -90.0, 1.0, 0.0, 0.0 );
    drawAxis();
    glPopMatrix();
}

void gasket(void)
{
	int i;
	
	glColor4f(1.0, 0.0, 0.0, 1.0);	// attraction points in red
	glPointSize(5.);
	glBegin(GL_POINTS);
		for (i=0; i<4; i++)
			glVertex3fv(fixedPoints[i]);
	glEnd();
	
	glColor4f(0.0, 1.0, 1.0, 0.5);	// moving points in cyan
	glPointSize(1.);
	glBegin(GL_POINTS);
		for (i=0; i<NPOINTS; i++)
			glVertex3fv(gasketPoints[i]);
	glEnd();
}

void display( void )
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glPushMatrix();
	glRotatef(xAngle, 1.0, 0.0, 0.0);
	glRotatef(yAngle, 0.0, 1.0, 0.0);
	glRotatef(zAngle, 0.0, 0.0, 1.0);
	if (ch != ' ') {	// keyboard indicated a rotation...
	//	rotation control uses two keypads embedded in standard keyboard
	//    q  w                     o  p  -- rotate around x-axis + -
	//     a  s                   k  l   -- rotate around y-axis + -
	//      z  x                 m  ,    -- rotate around z-axis + -
		switch(ch) {
			case 'q':
			case 'o':
				xAngle += ANGLE;
				glRotatef( ANGLE, 1.0, 0.0, 0.0); break;
			case 'w':
			case 'p':
				xAngle -= ANGLE;
				glRotatef(-ANGLE, 1.0, 0.0, 0.0); break;
        	case 'a':
        	case 'k':
				yAngle += ANGLE;
            	glRotatef( ANGLE, 0.0, 1.0, 0.0); break;
        	case 's':
        	case 'l':
				yAngle -= ANGLE;
            	glRotatef(-ANGLE, 0.0, 1.0, 0.0); break;
        	case 'z':
        	case 'm':
				zAngle += ANGLE;
            	glRotatef( ANGLE, 0.0, 0.0, 1.0); break;
        	case 'x':
        	case ',':
				zAngle -= ANGLE;
            	glRotatef(-ANGLE, 0.0, 0.0, 1.0); break;
	    }
    }
	ch = ' ';	// ensure any residual rotation is killed

    drawAxes();
    gasket();
    
    glPopMatrix();
    glutSwapBuffers();
 }

void reshape(int w,int h)
{
        glViewport(0,0,(GLsizei)w,(GLsizei)h);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluPerspective(60.0,1.0,1.0,30.0);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        /*           eye point        center of view      up   */
        gluLookAt( 13.0,  13.0, 13.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0);
}

void keyboard(unsigned char key, int x, int y)
{
	int i, j;

	ch = ' ';
	switch (key) {
        case 'q' :     // rotate around X; q = positive, w = negative
        case 'w' :
        case 'a' :    // rotate around Y; a = positive, s = negative
        case 's' :
        case 'z' :    // rotate around Z; z = positive, x = negative
        case 'x' :
           ch = key; break;
        case ' ' :
			//	move each of the gasket points halfway from its original position
			//	to the location of a randomly chosen attraction point
			for (i=0; i<NPOINTS; i++) {
				j = rand()%4;
				gasketPoints[i][0] = 0.5*(gasketPoints[i][0]+fixedPoints[j][0]);
				gasketPoints[i][1] = 0.5*(gasketPoints[i][1]+fixedPoints[j][1]);
				gasketPoints[i][2] = 0.5*(gasketPoints[i][2]+fixedPoints[j][2]);
			}
			break;
	}
	glutPostRedisplay();
}

int main(int argc, char** argv)
{
/* Standard GLUT initialization */
	glutInit(&argc,argv);
	glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(800, 800);
	glutInitWindowPosition(0, 0);
	glutCreateWindow("spinning cube");
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboard); // enable keyboard callback

	myinit();

	glutMainLoop();
}