// Walls and Balls
//
// controls:
//  1: toggle light source 1
//  2: toggle light source 2
//  3: toggle light source 3
//  h: toggle ambient light
//  j: toggle diffuse light
//  k: toggle specular light
//  l,r: move light source 1 left or right
//  u,d: move light source 1 up or down
//  f,b: move light source 1 forward or backward

#include <GL/glut.h>
#include <stdio.h>
#include <math.h>

GLfloat off[4] = {0,0,0,1};

void mySolidSphere(float radius)
{
	glutSolidSphere(radius,(int)(160*radius),(int)(160*radius));
}

void myRadiantSphere()
{
	glutSolidSphere(0.2,16,16);
}

class LightSource {
private:
	int id;
	GLfloat position[4];
	GLfloat ambient[4];
	GLfloat diffuse[4];
	GLfloat specular[4];
	GLfloat off[4];
	int on;
	int a,d,s;
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];
		}
		off[0]=off[1]=off[2]=0.0; off[3]=1.0;
		on=0;
		a=s=d=0;
	}
	void move(GLfloat dx,GLfloat dy,GLfloat dz)
	{ position[0]+=dx; position[1]+=dy; position[2]+=dz; }
	void shine(){
		if(on){
			glLightfv(id, GL_POSITION, off);
			if(a)
				glLightfv(id, GL_AMBIENT, ambient);
			else
				glLightfv(id, GL_AMBIENT, off);
			if(d)
				glLightfv(id, GL_DIFFUSE, diffuse);
			else
				glLightfv(id, GL_DIFFUSE, off);
			if(s)
				glLightfv(id, GL_SPECULAR, specular);
			else
				glLightfv(id, GL_SPECULAR, off);
			glEnable(id);
		}
		else
			glDisable(id);
	}
	void addRed(float x){
		ambient[0]+=x;
		if(ambient[0]>1)ambient[0]=1;
		if(ambient[0]<0)ambient[0]=0;
		diffuse[0]+=x;
		if(diffuse[0]>1)diffuse[0]=1;
		if(diffuse[0]<0)diffuse[0]=0;
	}
	void addGreen(float x){
		ambient[1]+=x;
		if(ambient[1]>1)ambient[1]=1;
		if(ambient[1]<0)ambient[1]=0;
		diffuse[0]+=x;
		if(diffuse[1]>1)diffuse[1]=1;
		if(diffuse[1]<0)diffuse[1]=0;
	}
	void addBlue(float x){
		ambient[2]+=x;
		if(ambient[2]>1)ambient[2]=1;
		if(ambient[2]<0)ambient[2]=0;
		diffuse[2]+=x;
		if(diffuse[2]>1)diffuse[2]=1;
		if(diffuse[2]<0)diffuse[2]=0;
	}
	void toggleAmbient(){
		a=!a;
	}
	void toggleDiffuse(){
		d=!d;
	}
	void toggleSpecular(){
		s=!s;
	}
	void toggle(){
		on=!on;
	}
};

class Object {
private:
	GLfloat position[4];
	GLfloat scale[4];
	GLfloat rotate[4];
	GLfloat ambient[4];
	GLfloat diffuse[4];
	GLfloat specular[4];
	GLfloat emission[4];
	GLfloat shininess;
	class Object*child;
	virtual void drawModel(){};
public:
	Object(){}
	Object(GLfloat attributes[][4],Object *parent)
	{
		for(int i=0;i<4;i++){
			position[i]=attributes[0][i];
			scale[i]=attributes[1][i];
			rotate[i]=attributes[2][i];
			ambient[i]=attributes[3][i];
			diffuse[i]=attributes[4][i];
			specular[i]=attributes[5][i];
			emission[i]=attributes[6][i];
		}
		shininess=attributes[7][0];
		child=0;
		if(parent)parent->child=this;
	}

	void move(GLfloat dx,GLfloat dy,GLfloat dz)
	{  position[0]+=dx; position[1]+=dy; position[2]+=dz; }

	void addRed(float x){
		ambient[0]+=x;
		if(ambient[0]>1)ambient[0]=1;
		if(ambient[0]<0)ambient[0]=0;
		diffuse[0]+=x;
		if(diffuse[0]>1)diffuse[0]=1;
		if(diffuse[0]<0)diffuse[0]=0;
	}
	void addGreen(float x){
		ambient[1]+=x;
		if(ambient[1]>1)ambient[1]=1;
		if(ambient[1]<0)ambient[1]=0;
		diffuse[0]+=x;
		if(diffuse[1]>1)diffuse[1]=1;
		if(diffuse[1]<0)diffuse[1]=0;
	}
	void addBlue(float x){
		ambient[2]+=x;
		if(ambient[2]>1)ambient[2]=1;
		if(ambient[2]<0)ambient[2]=0;
		diffuse[2]+=x;
		if(diffuse[2]>1)diffuse[2]=1;
		if(diffuse[2]<0)diffuse[2]=0;
	}
	void draw(){
		glPushMatrix();

//  Apply global transformations
//		glTranslatef(dx,dy,dz);
//		glRotatef(ay,0.0,1.0,0.0);
//		glRotatef(az,0.0,0.0,1.0);

		if(child)
			child->draw();

//  Apply local transformations

		glTranslatef(position[0],position[1],position[2]);
		glRotatef(rotate[0],1,0,0);
		glRotatef(rotate[1],0,1,0);
		glRotatef(rotate[2],0,0,1);
		glScalef(scale[0],scale[1],scale[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);

		drawModel();

		glPopMatrix();
	}


};

class SolidSphere : public Object {
private:
	float radius;
public:
	SolidSphere(float radius,GLfloat attributes[][4],Object *parent):Object(attributes,parent){
		this->radius=radius;
//		move(0,radius,0);
	}
	void drawModel(){
		mySolidSphere(radius);
	}
};

class RadiantSphere : public Object {
private:
	int on;
	LightSource *light;
public:
	RadiantSphere(LightSource*light,
		GLfloat attributes[][4],Object *parent):Object(attributes,parent){
		this->light=light;
		on=0;
	}
	void drawModel(){
		light->shine();
		if(on){
			myRadiantSphere();
		}
	}
	void toggle(){
		on=!on;
		light->toggle();
	}
};

class Wall : public Object {
private:
	int mesh;
public:

Wall(GLfloat sx,GLfloat sy,GLfloat sz,
	 GLfloat dx,GLfloat dy,GLfloat dz,
	 GLfloat xtheta,GLfloat ytheta,GLfloat ztheta);
Wall(int n,GLfloat attributes[][4],Object *parent):Object(attributes,parent){ mesh=n; }

void drawModel(){
	float w=2.0/mesh;

	for(int i=0;i<mesh;i++)
		for(int j=0; j<mesh;j++){
			glPushMatrix();
			glTranslatef(i*w,j*w,0);
			glBegin(GL_POLYGON);
			glNormal3f(0,0,1);
			glVertex3f(-1,-1,0);
			glVertex3f(-1,-1+w,0);
			glVertex3f(-1+w,-1+w,0);
			glVertex3f(-1+w,-1,0);
			glEnd();
			glPopMatrix();
		}
	}
};


int perspective=1;

GLfloat l1Attributes[][4]={ {-2,1.0,0,1}, {0.3,0.3,0.3,1}, {.7,.7,.7,1}, {.7,.7,.7,1}};
GLfloat l2Attributes[][4]={ {0,1.0,-1,1}, {0.3,0.3,0.3,1}, {.7,.7,.7,1}, {.7,.7,.7,1}};
GLfloat l3Attributes[][4]={ {2,1.0,1,1},  {0.3,0.3,0.3,1}, {.7,.7,.7,1}, {.7,.7,.7,1}};

float radius=0.5;

GLfloat o1Attributes[][4]={ {0,-2+radius,0,0},{1,1,1,1},{0,0,0,1},
	{.8,.4,0,1.0},{0.8,0.4,0,1.0},{1.0,1.0,1.0,1.0},{0,0,0,1.0},{100}};    
GLfloat o2Attributes[][4]={ {2*radius,-2+radius,0,0},{1,1,1,1},{0,0,0,1},
	{0,0.7,0.5,1.0},{0,0.7,0.5,1.0},{1.0,1.0,1.0,1.0},{0,0,0,1.0},{100}}; 
GLfloat o3Attributes[][4]={ {radius,-2+radius,radius*sqrt(3.0),0},{1,1,1,1},{0,0,0,1},
	{0,0.5,0.7,1.0},{0,0.5,0.7,1.0},{1.0,1.0,1.0,1.0},{0,0,0,1.0},{100}};    
GLfloat o4Attributes[][4]={ {radius,-2+radius+radius*sqrt(8.0/3.0),
	radius/sqrt(3.0)},{1,1,1,1},{0,0,0,1},
	{0.8,0,0.3,1.0},{0.8,0,0.3,1.0},{1.0,1.0,1.0,1.0},{0,0,0,1.0},{100}};
 
GLfloat r1Attributes[][4]={ {-2,1.5,0,1},{1,1,1,1},{0,0,0,1},
	{1.0,1.0,0.7,1.0},{1.0,1.0,0.7,1.0},{1.0,1.0,1.0,1.0},{1.0,1.0,1.0,1.0},{100}}; 
GLfloat r2Attributes[][4]={ {0,1.2,-1,1},{1,1,1,1},{0,0,0,1},
	{1.0,1.0,0.7,1.0},{1.0,1.0,0.7,1.0},{1.0,1.0,1.0,1.0},{1.0,1.0,1.0,1.0},{100}}; 
GLfloat r3Attributes[][4]={ {1,1,6,1},{1,1,1,1},{0,0,0,1},
	{1.0,1.0,0.7,1.0},{1.0,1.0,0.7,1.0},{1.0,1.0,1.0,1.0},{1.0,1.0,1.0,1.0},{100}}; 

GLfloat w1Attributes[][4]={ {0,0,-3,1},{4,4,1,1},{0,0,0},
	{0.3,0.7,0.7,1.0},{0.3,0.7,0.7,1.0},{.4,.4,.4,1.0},{0,0,0,0},{20}}; 
GLfloat w2Attributes[][4]={ {0,-2,0,1},{4,4,1,1},{-90,0,0},
	{0.3,0.7,0.7,1.0},{0.3,0.7,0.7,1.0},{.4,.4,.4,1.0},{0,0,0,0},{20}}; 
GLfloat w3Attributes[][4]={ {0,2,0,1},{4,4,1,1},{90,0,0},
	{0.3,0.7,0.7,1.0},{0.3,0.7,0.7,1.0},{.4,.4,.4,1.0},{0,0,0,0},{20}}; 
GLfloat w4Attributes[][4]={ {3,0,0,1},{4,4,1,1},{0,-90,0},
	{0.3,0.7,0.7,1.0},{0.3,0.7,0.7,1.0},{.4,.4,.4,1.0},{0,0,0,0},{20}}; 
GLfloat w5Attributes[][4]={ {-3,0,0,1},{4,4,1,1},{0,90,0},
	{0.3,0.7,0.7,1.0},{0.3,0.7,0.7,1.0},{.4,.4,.4,1.0},{0,0,0,0},{20}}; 

LightSource l1(GL_LIGHT0,l1Attributes );
LightSource l2(GL_LIGHT1,l2Attributes );
LightSource l3(GL_LIGHT2,l3Attributes);


Object *o1 = new SolidSphere(radius,o1Attributes,0);
Object *o2 = new SolidSphere(radius,o2Attributes,0);
Object *o3 = new SolidSphere(radius,o3Attributes,0);
Object *o4 = new SolidSphere(radius,o4Attributes,0);

RadiantSphere *r1 = new RadiantSphere(&l1,r1Attributes,0);
RadiantSphere *r2 = new RadiantSphere(&l2,r2Attributes,0);
RadiantSphere *r3 = new RadiantSphere(&l3,r3Attributes,0);

int mesh=100;
Object *w1 = new Wall(mesh,w1Attributes,0);
Object *w2 = new Wall(mesh,w2Attributes,0);
Object *w3 = new Wall(mesh,w3Attributes,0);
Object *w4 = new Wall(mesh,w4Attributes,0);
Object *w5 = new Wall(mesh,w5Attributes,0);
int ww,wh;


void myinit(void)
{
  glLightModelfv(GL_LIGHT_MODEL_AMBIENT, off);

  glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER,GL_TRUE);

  glEnable(GL_LIGHTING);

  glDepthFunc(GL_LEQUAL);
  glEnable(GL_DEPTH_TEST);
}


void display(void)
{
  GLfloat background_color[] = {0.8,0.6,0,0};
  GLfloat background_spec[] = {0,0,0,1};

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt(0,0,10, 0,0,0, 0,1,0);

  r1->draw();
  r2->draw();
  r3->draw();

  o1->draw();
  o2->draw();
  o3->draw();
  o4->draw();

  w1->draw();
  w2->draw();
  w3->draw();
  w4->draw();
  w5->draw();

  glFlush();
}

/*
void reshape(GLsizei w, GLsizei h)
{
  glViewport(0, 0, w, h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  if(w <= h)
     glOrtho(-1.5,1.5,-1.5*(GLfloat)h/(GLfloat)w,1.5*(GLfloat)h/(GLfloat)w,-10.0,10.0);
  else
     glOrtho(-1.5*(GLfloat)w/(GLfloat)h,1.5*(GLfloat)w/(GLfloat)h,-1.5,1.5,-10.0,10.0);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}
*/

void reshape(int w,int h)
{
	float aspect=(float)w/h;
	float gsx,gsy;

	ww=w; wh=h;

	
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	if(perspective){
		gluPerspective(30.0,aspect,5,15.0);
	}
	else {
		if(w<h){
			gsy=1/aspect;
			glOrtho(-1.0,1.0,-gsy,gsy,0.01,20.0);
		}
		else{
			gsx=aspect;
			glOrtho(-gsx,gsx,-1.0,1.0,0.01,20.0);
		}
	}

	glViewport(0,0,w,h);

	glutPostRedisplay();
}

void keyboard(unsigned char key,int x,int y)
{
	static int which=1;  // select walls=1 or balls=0

	switch(key){
	case '1':
		r1->toggle(); break;
	case '2':
		r2->toggle(); break;
	case '3':
		r3->toggle(); break;
	case 'l': case 'L':
		l1.move(-.1,0,0); r1->move(-.1,0,0); break;
	case 'r':case 'R':
		l1.move(.1,0,0); r1->move(.1,0,0); break;
	case 'u':case 'U':
		l1.move(0,.1,0); r1->move(0,.1,0); break;
	case 'd':case 'D':
		l1.move(0,-.1,0); r1->move(0,-.1,0); break;
	case 'f':case 'F':
		l1.move(0,0,-.1); r1->move(0,0,-.1); break;
	case 'b':case 'B':
		l1.move(0,0,.1); r1->move(0,0,.1); break;
	case 'h':case 'H':
		l1.toggleAmbient();
		l2.toggleAmbient();
		l3.toggleAmbient();
		break;
	case 'j':case 'J':
		l1.toggleDiffuse();
		l2.toggleDiffuse();
		l3.toggleDiffuse();
		break;
	case 'k': case 'K':
		l1.toggleSpecular();
		l2.toggleSpecular();
		l3.toggleSpecular();
		break;
	case 'm':case 'M':
		which=!which; break;
	case 'i':
		if(which){
			w1->addRed(.05);
			w2->addRed(.05);
			w3->addRed(.05);
			w4->addRed(.05);
			w5->addRed(.05);
		}
		else{
			o1->addRed(.05);
			o2->addRed(.05);
		}
		break;
	case 'I':
		//red
		if(which){
			w1->addRed(-.05);
			w2->addRed(-.05);
			w3->addRed(-.05);
			w4->addRed(-.05);
			w5->addRed(-.05);
		}
		else{
			o1->addRed(-.05);
			o2->addRed(-.05);
		}
		break;
	case 'o':
		if(which){
			w1->addGreen(.05);
			w2->addGreen(.05);
			w3->addGreen(.05);
			w4->addGreen(.05);
			w5->addGreen(.05);
		}
		else{
			o1->addGreen(.05);
			o2->addGreen(.05);
		}
		break;
	case 'O':
		if(which){
			w1->addGreen(-.05);
			w2->addGreen(-.05);
			w3->addGreen(-.05);
			w4->addGreen(-.05);
			w5->addGreen(-.05);
		}
		else{
			o1->addGreen(-.05);
			o2->addGreen(-.05);
		}
		break;
	case 'p':
		if(which){
			w1->addBlue(.05);
			w2->addBlue(.05);
			w3->addBlue(.05);
			w4->addBlue(.05);
			w5->addBlue(.05);
		}
		else{
			o1->addBlue(.05);
			o2->addBlue(.05);
		}
		break;
	case 'P':
		if(which){
			w1->addBlue(-.05);
			w2->addBlue(-.05);
			w3->addBlue(-.05);
			w4->addBlue(-.05);
			w5->addBlue(-.05);
		}
		else{
			o1->addBlue(-.05);
			o2->addBlue(-.05);
		}
		break;
	default: break;
	}
	glutPostRedisplay();
}



int main(int argc,char**argv)
{
	glutInit(&argc,argv);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA | GLUT_DEPTH);
	glutInitWindowSize(500,500);
	glutInitWindowPosition(0,0);
	glutCreateWindow("Walls and Balls");
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboard);
	myinit();
	glutMainLoop();
	return 0;
}



