/**************************************************
 *   File:     CGWorld.cpp
 *   Author:   Dr. Dalton R. Hunkins     
 *   Date:     February 2009
**************************************************/
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <GL/Utils/MathFunctions.h>

#include "CGWorld.h"

#include <cmath>
using std::abs;
using std::pow;
using std::cos;
using std::sin;

  CGWorld::CGWorld() {
  }
 
  
  void CGWorld::createSubWindow(int parentHandle,
                                 int parentWidth, int parentHeight,
                                 int startX,      int startY,
                                 int width,        int height) {
      windowHandle = glutCreateSubWindow(parentHandle,
                                         startX*parentWidth/100, startY*parentHeight/100,
                                         width*parentWidth/100,  height*parentHeight/100);
      CGWorld::cornerX = startX;
      CGWorld::cornerY = startY;
      CGWorld::width   = width;
      CGWorld::height  = height;                                                                              
  }

  void CGWorld::init(float clearColor[]) { 
     glClearColor(clearColor[0], clearColor[1], clearColor[2], 1.0);
     
     lightColor[0] = lightColor[1] = lightColor[2] = 1.0;
     
     geometryColor[0] = 1.0;
     geometryColor[1] = 1.0;
     geometryColor[2] = 0.0;
     geometryColor[3] = 1.0;
     
     hsvSquare[0].setHSV(25.0, 1.0, 0.85);
     hsvSquare[1].setHSV(25.0, 0.6, 0.85);
     hsvSquare[2].setHSV(25.0, 0.4, 0.85);         
     
     coordinates[0].setXYZ( 0.16,  0.35, 0.0);
     coordinates[1].setXYZ(-0.04,  0.24, 0.0);
     coordinates[2].setXYZ( 0.09,  0.07, 0.0);
     coordinates[3].setXYZ( 0.40,  0.06, 0.0);
     coordinates[4].setXYZ( 0.30, -0.11, 0.0);
     coordinates[5].setXYZ( 0.23, -0.27, 0.0);
     coordinates[6].setXYZ( 0.12, -0.33, 0.0);
     coordinates[7].setXYZ( 0.00, -0.31, 0.0);
          
     glMatrixMode(GL_MODELVIEW);
     glLoadIdentity();
     gluLookAt(0.0, 2.0, 2.0,    // eyePoint
               0.0, 0.0, 0.0,    // lookAtPoint
               0.0, 1.0, 0.0);   // upVector
               
     glMatrixMode(GL_PROJECTION);
     glLoadIdentity();
     gluPerspective(30.0, 1.0, 0.1, 100.0);
     lightColor[0] = lightColor[1] = lightColor[2] = 1.0;

     setLighting();
  }
  
  void CGWorld::display() {
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      glEnable(GL_DEPTH_TEST);
      glEnable(GL_BLEND);
      
      glDisable(GL_LIGHTING);
      glBlendFunc(GL_ONE, GL_ZERO);
      
      glMatrixMode(GL_MODELVIEW);
      glPushMatrix();
      glTranslatef(-1.0, -0.5, -1.0);
      drawBoard();
      glPopMatrix();
      
      glEnable(GL_LIGHTING);
      glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
      glPushMatrix();
      glScalef(2.0, 1.5, 2.0);
      drawSurfaceOfRevolution();
      glPopMatrix();
      
      glutSwapBuffers();
      glDisable(GL_DEPTH_TEST);
  }

  void CGWorld::setLighting() {
       float directionalPosition[] = { 0.0, 0.0, 3.0, 0.0};

	 glLightModelfv(GL_LIGHT_MODEL_AMBIENT, darkGray);
	 glLightfv(GL_LIGHT1, GL_DIFFUSE, lightColor);
	 glLightfv(GL_LIGHT1, GL_POSITION, directionalPosition);
	 glEnable(GL_LIGHT1);

       glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
  }
  
  void CGWorld::setAlpha(float value) {
        geometryColor[3] = value;
	  glutSetWindow(windowHandle);
	  glutPostRedisplay();		
  }      

  void CGWorld::xzSquare(int which) { 
      if (which == 1) {       
         glBegin(GL_TRIANGLE_FAN);
           glColor3fv(hsvSquare[0].getRGB());
           glVertex3d(0.5, 0.0, 0.5);
           glColor3fv(hsvSquare[1].getRGB());
           glVertex3d(0.0, 0.0, 0.0);
           glColor3fv(hsvSquare[2].getRGB());
           glVertex3d(1.0, 0.0, 0.0);
           glColor3fv(hsvSquare[1].getRGB());
           glVertex3d(1.0, 0.0, 1.0);
           glColor3fv(hsvSquare[2].getRGB());
           glVertex3d(0.0, 0.0, 1.0);
           glColor3fv(hsvSquare[1].getRGB());
           glVertex3d(0.0, 0.0, 0.0);
         glEnd();
      }         
      else {
         glBegin(GL_TRIANGLE_FAN);
           glColor3f(0.8, 0.8, 0.8);
           glVertex3d(0.5,0.0,  0.5);
           glColor3f(0.4, 0.4, 0.4);
           glVertex3d(0.0, 0.0, 0.0);
           glColor3f(0.6, 0.6, 0.6);
           glVertex3d(1.0, 0.0, 0.0);
           glColor3f(0.4, 0.4, 0.4);
           glVertex3d(1.0, 0.0, 1.0);
           glColor3f(0.6, 0.6, 0.6);
           glVertex3d(0.0, 0.0, 1.0);
           glColor3f(0.4, 0.4, 0.4);
           glVertex3d(0.0, 0.0, 0.0);
         glEnd();
      }           
  }

  void CGWorld::xzRow(int first, int second) { 
     for (int i=0; i<numberOfSquares; i+=2) {
   	    glPushMatrix();
   	    glTranslated(i*sizeOfSquare, 0.0, 0.0);
   	    glScaled(sizeOfSquare, 1.0, sizeOfSquare);
   	    xzSquare(first); 
   	    glPopMatrix();
     }   	
   
     for (int i=1; i<numberOfSquares; i+=2) {
   	    glPushMatrix();
   	    glTranslated(i*sizeOfSquare, 0.0, 0.0);
   	    glScaled(sizeOfSquare, 1.0, sizeOfSquare);
   	    xzSquare(second); 
   	    glPopMatrix();
     }	 
  }		

    void CGWorld::drawBoard() {
     glMatrixMode(GL_MODELVIEW);
     for (int i = 0; i<numberOfSquares; i+=2) {
     	glPushMatrix();
     	glTranslated(0.0, 0.0, i*sizeOfSquare);
     	xzRow(2, 1); 
     	glTranslated(0.0, 0.0, sizeOfSquare);
     	xzRow(1, 2); 
        glPopMatrix();
     }      	
  }	
  
  void CGWorld::drawSurfaceOfRevolution() {
       const double angleIncrement    = 10.0;
       const double angleOfRevolution = 360.0;
       const int    numberOfDivisions = 32;

       glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, geometryColor);
       glEnable(GL_NORMALIZE);

       glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
          
       double point[3];
          
       glMatrixMode(GL_MODELVIEW);
       glPushMatrix();
       glScaled(0.7, 1.2, 0.7);          
       double angle = 0.0;
       while (angle < angleOfRevolution) {   
          glBegin(GL_QUAD_STRIP);
             for (int i=1; i<=numberOfDivisions; i++) {
                 getPointOnCurve(i/ (double) numberOfDivisions, point);
                 glNormal3d(cos(angle*PI/180.0)*abs(point[0]), 0.0, 
                            sin(angle*PI/180.0)*abs(point[0]));
                 glVertex3d(cos(angle*PI/180.0)*abs(point[0]), point[1], 
                            sin(angle*PI/180.0)*abs(point[0]));
                 glNormal3d(cos((angle+angleIncrement)*PI/180.0)*abs(point[0]), 0.0, 
                            sin((angle+angleIncrement)*PI/180.0)*abs(point[0])); 
                 glVertex3d(cos((angle+angleIncrement)*PI/180.0)*abs(point[0]), point[1], 
                            sin((angle+angleIncrement)*PI/180.0)*abs(point[0]));                 
            }  
          glEnd(); 
          angle += angleIncrement;
       }      
       glPopMatrix();
  }

    void CGWorld::getPointOnCurve(double U, double pointOnCurve[3]) {
        int numberOfControlPoints = 8;
		double sumX = 0.0;
		double sumY = 0.0;
		double sumZ = 0.0;
		
        if (U > 0.99) {
           pointOnCurve[0] = coordinates[7].getX();
           pointOnCurve[1] = coordinates[7].getY();
           pointOnCurve[2] = coordinates[7].getZ();
        }
        else if (U < 0.01) {
           pointOnCurve[0] = coordinates[0].getX();
           pointOnCurve[1] = coordinates[0].getY();
           pointOnCurve[2] = coordinates[0].getZ();
        } 
        else {
           for (int k = 0; k < numberOfControlPoints; k++) {               
               double factor = combinations(numberOfControlPoints, k) * pow(U, k)
								* pow(1 - U, numberOfControlPoints - k);
               sumX += factor*coordinates[k].getX();
               sumY += factor*coordinates[k].getY();
	           sumZ += factor*coordinates[k].getZ();
           }
           double factor = combinations(numberOfControlPoints, 
                                        numberOfControlPoints) * pow(U, numberOfControlPoints) * pow(1- U, 0);
           sumX += factor*coordinates[7].getX();
           sumY += factor*coordinates[7].getY();
           sumZ += factor*coordinates[7].getZ();             
           pointOnCurve[0] = sumX;
           pointOnCurve[1] = sumY;
           pointOnCurve[2] = sumZ;
       }
     }
          
     int CGWorld::combinations(int n, int i) {
	     int returnValue = 0;
	     int denominator = 1;
	     int numerator   = 1;
         if (i == 0)
    	    returnValue = 1;
         else if (i <= n) {
	        for (int k=1; k<=i; k++)
		       denominator *= k;
            for (int k=n; k>=(n-i+1); k--)
        	   numerator *= k;
            returnValue = numerator/denominator;
	     }
	     return returnValue;
     }

 

  int CGWorld::getWindowHandle() {
   	  return windowHandle;
  }
  
  int CGWorld::getCornerX() {
  	  return cornerX;
  }
  
  int CGWorld::getCornerY() {
  	  return cornerY;
  }
  
  int CGWorld::getWidth() {
  	  return width;
  }
  
  int CGWorld::getHeight() {
  	  return height;
  }