#include <GL/glut.h> 
/*
  This program, which draws two illuminated spheres, is designed to 
  illustrate the use of lighting in OpenGL.

  Controls:
  0:  Toggles light source 0 on and off
  1:  Toggles light source 1 on and off
  +:  Increases sphere tesselation by 50%
  -:  Decreases sphere tesselation by 33%
  a:  Toggles ambient light (both light sources)
  d:  Toggles diffues light (both light sources)
  s:  Toggles specular light (both light sources)
*/

static GLfloat dark[4] = {0,0,0,0};

class LightSource {
private:
	int id;
	GLfloat position[4];
	GLfloat ambient[4];
	GLfloat diffuse[4];
	GLfloat specular[4];
	int on;
        int ambientOn;
        int diffuseOn;
        int specularOn;
public:
	LightSource(int id,float attributes[][4]){
		this->id=id;
		for(int i=0;i<4;i++){
			position[i]=attributes[0][i];
			ambient[i]=attributes[1][i];
			diffuse[i]=attributes[2][i];
			specular[i]=attributes[3][i];
		}
		on=0;
                ambientOn = diffuseOn = specularOn = 1;
	}
	void shine(){
		glLightfv(id, GL_POSITION, position);
		glLightfv(id, GL_AMBIENT, ambientOn?ambient:dark);
		glLightfv(id, GL_DIFFUSE, diffuseOn?diffuse:dark);
		glLightfv(id, GL_SPECULAR, specularOn?specular:dark);
		if(on){
			glEnable(id);
		}
		else
			glDisable(id);
	}
        void toggleAmbient(){
          ambientOn = !ambientOn;
        }
        void toggleDiffuse(){
          diffuseOn = !diffuseOn;
        }
        void toggleSpecular(){
          specularOn = !specularOn;
        }
	void toggle(){
		on=!on;
	}
};

class Sphere {
private:
	GLfloat position[4];
	GLfloat size;
	GLfloat ambient[4];
	GLfloat diffuse[4];
	GLfloat specular[4];
	GLfloat emission[4];
	GLfloat shininess;
        int tess;   // level of tesselation
	virtual void drawModel(){};
public:
     Sphere(){}
     Sphere(GLfloat position[4],GLfloat size,int tess,GLfloat attributes[][4])
	{
		for(int i=0;i<4;i++){
			this->position[i]=position[i];
			this->size=size;
			this->tess=tess;
			ambient[i]=attributes[0][i];
			diffuse[i]=attributes[1][i];
			specular[i]=attributes[2][i];
			emission[i]=attributes[3][i];
		}
		shininess=attributes[4][0];
	}

	void draw(){
		glPushMatrix();

		glTranslatef(position[0],position[1],position[2]);

		glMaterialfv(GL_FRONT, GL_AMBIENT, ambient);
		glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse);
		glMaterialfv(GL_FRONT, GL_SPECULAR, specular);
		glMaterialfv(GL_FRONT, GL_EMISSION, emission);
		glMaterialf(GL_FRONT, GL_SHININESS, shininess);

		glutSolidSphere(size,tess,tess);

		glPopMatrix();
	}
        void refine(float amount){
          tess=(int)(tess*amount);
	  if(tess<2)tess=2;
        }

};


GLfloat l0Attributes[][4]={ {3,3,3,1}, {0.5,0.5,0.5,1}, {.5,.5,.5,1}, {1,1,1,1}};
GLfloat l1Attributes[][4]={ {-3,0,2,1}, {0.5,0.5,0.5,1}, {.5,.5,.5,1}, {1,1,1,1}};

GLfloat s0Position[4]={0,0,0,1};
GLfloat s0Attributes[][4]={ {.8,.4,0,1.0},{.8,.4,0,1.0},{1.0,1.0,1.0,1.0},
   {0,0,0,1},{100}}; // lighting attributes for orange sphere
GLfloat s1Position[4]={1,1,0,1};
GLfloat s1Attributes[][4]={ {.6,.6,.6,1},{.6,.6,.6,1},{.6,.6,.6,1},
   {0,0,0,1},{100}}; // lighting attributes for gray sphere

LightSource l0(GL_LIGHT0,l0Attributes);
LightSource l1(GL_LIGHT1,l1Attributes);

Sphere s0(s0Position,1,20,s0Attributes);
Sphere s1(s1Position,.3,20,s1Attributes);

void myinit(void) 
{
  glEnable(GL_LIGHTING); 
  glDepthFunc(GL_LEQUAL); 
  glEnable(GL_DEPTH_TEST); 
} 

void display(void) 
{ 
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  gluLookAt(0,0,5, 0,0,0, 0,1,0);

  l0.shine();
  l1.shine();

  s0.draw();
  s1.draw();

  glFlush(); 
} 

void reshape(int w,int h)
{
	float aspect=(float)w/h;

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	gluPerspective(30.0,aspect,1,5);

	glViewport(0,0,w,h);

	glutPostRedisplay();
}

void keyboard(unsigned char key,int x,int y)
{
	switch(key){
	case '0':
          l0.toggle();break;
	case '1':
	  l1.toggle();break;
	case '+':
	  s0.refine(1.5);
	  s1.refine(1.5);
	  break;
	case '-':
	  s0.refine(1/1.5);
	  s1.refine(1/1.5);
	  break;
      case 'a':
        l0.toggleAmbient();
        l1.toggleAmbient();
        break;
      case 'd':
        l0.toggleDiffuse();
        l1.toggleDiffuse();
        break;
      case 's':
        l0.toggleSpecular();
        l1.toggleSpecular();
        break;
	}
	
	glutPostRedisplay();
}

int main( int argc, char** argv) { 
  glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA | GLUT_DEPTH); 
  glutInitWindowSize(500,500);
  glutInitWindowPosition(0,0);
  glutCreateWindow("Shaded Spheres");
  myinit(); 
  glutDisplayFunc(display);
  glutReshapeFunc(reshape);
  glutKeyboardFunc(keyboard);
  glutMainLoop();
  return 0;
} 
  
  

