//This code was created by Jordan Isaak
//Code written in March & April, 2002
//My website is at members.shaw.ca/jordanisaak
//My email is jordanisaak@hotmail.com

#include <GL/glut.h>
#include <math.h>
#include <stdio.h>

/*Defines*/
#define PI 3.1415926535897932384626433832795f
#define OBJECT_RESOLUTION 64
#define TEXTURE_SIZE 64
#define OBJECT_DISPLAY_LIST 1
#define TANGENT_DISPLAY_LIST 2
#define NORMAL_DISPLAY_LIST 3
#define NUMBER_OF_LIGHTS 4
#define TORUS_MAJOR_RADIUS 1.0f
#define TORUS_MINOR_RADIUS 0.3f

/*Anisotropic lighting texture*/
GLuint anisotropiclightingtexture;

/*Anisotropic lighting texture data*/
unsigned char anisotropictexturedata[3 * TEXTURE_SIZE * TEXTURE_SIZE];

/*Diffuse color for the anisotropic lighting*/
float r = 0.8f, g = 0.0f, b = 0.0f;

/*Ambient lighting*/
float ambientlight[3] = {0.1f, 0.1f, 0.1f};

/*Position and radius of the anisotropically lit object*/
float object[4] = {0.0f, 0.0f, 0.0f, 1.0f};

/*Specular exponent*/
float specularexponent = 16.0f;

/*Lighting control variables*/
float lightrotation[NUMBER_OF_LIGHTS];
float lightelevation[NUMBER_OF_LIGHTS];
float lightpos[NUMBER_OF_LIGHTS][4];
float lightambient[4] = {0.0f, 0.0f, 0.0f, 1.0f};
float lightdiffuse[NUMBER_OF_LIGHTS][4];

/*Material variables*/
float material[4] = {1.0f, 1.0f, 1.0f, 1.0f};

/*Keeps track of where the mouse was last*/
int lastx = 0, lasty = 0;

/*Camera control variables*/
float camerarotation = 0.0f;
float cameraelevation = 1.0f;
float cameradistance = 8.0f;
float camerapos[3] = {0.0f, 0.0f, 0.0f};

/*Controls what mouse position changes update*/
int controlmode = 0;

/*Changes whether or not to display tangents*/
int displaytangentvectors = 0;

/*Changes whether or not to display normals*/
int displaynormalvectors = 0;

/*Changes whether or not to display light vectors*/
int displaylightvectors = 0;

/*Current light being manipulated by the mouse*/
int currentlight = 0;

/*Changes if color adjustments affect the current light or the sphere color*/
int colorchangetarget = 0;

/*Controls which object is currently being drawn*/
int currentobject = 0;

/*Controls which lights are active*/
int lightactive[NUMBER_OF_LIGHTS];

/*Generates a texture that holds anisotropic lighting data*/
void generateanisotropiclighting(void)
{
	int i, j;
	float u, v;
	float diffuselightingvalue, specularlightingvalue;
	float red, green, blue;

	for(i = 0; i < TEXTURE_SIZE; i++)
	{
		v = 2.0f * ((float)i + 0.5f) / TEXTURE_SIZE - 1.0f;
		for(j = 0; j < TEXTURE_SIZE; j++)
		{
			u = 2.0f * ((float)j + 0.5f) / TEXTURE_SIZE - 1.0f;
			diffuselightingvalue = (float)sqrt(u * u);
			specularlightingvalue = (float)sqrt(1.0f - u * u) * (float)sqrt(1.0f - v * v) - u * v;
			specularlightingvalue = pow(specularlightingvalue, specularexponent);

			//Calculate lighting values
			red = r * diffuselightingvalue + specularlightingvalue;
			green = g * diffuselightingvalue + specularlightingvalue;
			blue = b * diffuselightingvalue + specularlightingvalue;

			//Clamp to fit within range of an unsigned char
			if(red < 0.0f)
				red = 0.0f;
			else if(red > 1.0f)
				red = 1.0f;
			if(green < 0.0f)
				green = 0.0f;
			else if(green > 1.0f)
				green = 1.0f;
			if(blue < 0.0f)
				blue = 0.0f;
			else if(blue > 1.0f)
				blue = 1.0f;

			//Put into the texture map
			anisotropictexturedata[3 * (i * TEXTURE_SIZE + j)] =
				(unsigned char)(255 * red);
			anisotropictexturedata[3 * (i * TEXTURE_SIZE + j) + 1] =
				(unsigned char)(255 * green);
			anisotropictexturedata[3 * (i * TEXTURE_SIZE + j) + 2] =
				(unsigned char)(255 * blue);
		}
	}

	/*Load the texture into OpenGL*/
	glBindTexture(GL_TEXTURE_2D, anisotropiclightingtexture);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, 
					GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, 
					GL_LINEAR_MIPMAP_LINEAR);
	gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, TEXTURE_SIZE, TEXTURE_SIZE, GL_RGB, GL_UNSIGNED_BYTE, anisotropictexturedata);
}

//Sets up the texture matrix to properly index
//the anisotropic lighting texture, given
//the light for which to do so
void setupanisotropictexturematrix(int lightnum)
{
	float viewpointvector[3];
	float veclen;
	float texturematrix[16];

	viewpointvector[0] = camerapos[0] - object[0];
	viewpointvector[1] = camerapos[1] - object[1];
	viewpointvector[2] = camerapos[2] - object[2];

	veclen = 1.0f / (float) sqrt(viewpointvector[0] * viewpointvector[0] +
							viewpointvector[1] * viewpointvector[1] +
							viewpointvector[2] * viewpointvector[2]);

	viewpointvector[0] *= veclen;
	viewpointvector[1] *= veclen;
	viewpointvector[2] *= veclen;

	texturematrix[0] = 0.5f * lightpos[lightnum][0];
	texturematrix[1] = 0.5f * viewpointvector[0];
	texturematrix[2] = 0.0f;
	texturematrix[3] = 0.0f;
	texturematrix[4] = 0.5f * lightpos[lightnum][1];
	texturematrix[5] = 0.5f * viewpointvector[1];
	texturematrix[6] = 0.0f;
	texturematrix[7] = 0.0f;
	texturematrix[8] = 0.5f * lightpos[lightnum][2];
	texturematrix[9] = 0.5f * viewpointvector[2];
	texturematrix[10] = 0.0f;
	texturematrix[11] = 0.0f;
	texturematrix[12] = 1.0f;
	texturematrix[13] = 1.0f;
	texturematrix[14] = 0.0f;
	texturematrix[15] = 1.0f;

	glMatrixMode(GL_TEXTURE);
	glLoadMatrixf(texturematrix);
	glMatrixMode(GL_MODELVIEW);
}

//Generate a display list for sphere normals
void generatespherenormals(void)
{
	int i, j;
	float u, v;
	float xpos = 0.0f, ypos = 0.0f, zpos = 0.0f;

	glNewList(NORMAL_DISPLAY_LIST, GL_COMPILE);
	for(i = 0; i <= OBJECT_RESOLUTION; i++)
	{
		glBegin(GL_LINES);
		for(j = 0; j <= OBJECT_RESOLUTION; j++)
		{
			u = 2 * PI * ((float)(j % OBJECT_RESOLUTION) / OBJECT_RESOLUTION);
			v = PI * ((float)(i) / OBJECT_RESOLUTION);
			xpos = sin(u) * sin(v);
			ypos = cos(v);
			zpos = cos(u) * sin(v);
			glVertex3f(xpos, ypos, zpos);
			glVertex3f(1.1f * xpos, 1.1f * ypos, 1.1f * zpos);
		}
		glEnd();
	}
	glEndList();
}

//Generates a sphere with tangents for lighting
//going in the direction from pole to pole
void generateaxialanisotropicsphere(void)
{
	int i, j;
	float u, v;
	float xpos = 0.0f, ypos = 0.0f, zpos = 0.0f;
	float xtan = 0.0f, ytan = 0.0f, ztan = 0.0f;

	glNewList(OBJECT_DISPLAY_LIST, GL_COMPILE);
	for(i = 0; i < OBJECT_RESOLUTION; i++)
	{
		glBegin(GL_TRIANGLE_STRIP);
		for(j = 0; j <= OBJECT_RESOLUTION; j++)
		{
			u = 2 * PI * ((float)(j % OBJECT_RESOLUTION) / OBJECT_RESOLUTION);
			v = PI * ((float)(i) / OBJECT_RESOLUTION);
			xpos = sin(u) * sin(v);
			ypos = cos(v);
			zpos = cos(u) * sin(v);
			xtan = sin(u) * cos(v);
			ytan = -sin(v);
			ztan = cos(u) * cos(v);
			glNormal3f(xpos, ypos, zpos);
			glTexCoord3f(xtan, ytan, ztan);
			glVertex3f(xpos, ypos, zpos);
			v = PI * ((float)((i + 1)) / OBJECT_RESOLUTION);
			xpos = sin(u) * sin(v);
			ypos = cos(v);
			zpos = cos(u) * sin(v);
			xtan = sin(u) * cos(v);
			ytan = -sin(v);
			ztan = cos(u) * cos(v);
			glNormal3f(xpos, ypos, zpos);
			glTexCoord3f(xtan, ytan, ztan);
			glVertex3f(xpos, ypos, zpos);
		}
		glEnd();
	}
	glEndList();

	generatespherenormals();
}

//Calculates the tangent lines for a sphere with tangents
//going in the direction from pole to pole
void generateaxialspheretangentvectors(void)
{
	int i, j;
	float u, v;
	float xpos = 0.0f, ypos = 0.0f, zpos = 0.0f;
	float xtan = 0.0f, ytan = 0.0f, ztan = 0.0f;

	glNewList(TANGENT_DISPLAY_LIST, GL_COMPILE);
	for(i = 0; i <= OBJECT_RESOLUTION; i++)
	{
		glBegin(GL_LINES);
		for(j = 0; j <= OBJECT_RESOLUTION; j++)
		{
			u = 2 * PI * ((float)(j % OBJECT_RESOLUTION) / OBJECT_RESOLUTION);
			v = PI * ((float)(i) / OBJECT_RESOLUTION);
			xpos = sin(u) * sin(v);
			ypos = cos(v);
			zpos = cos(u) * sin(v);
			xtan = sin(u) * cos(v);
			ytan = -sin(v);
			ztan = cos(u) * cos(v);
			glVertex3f(xpos, ypos, zpos);
			glVertex3f(xpos + xtan * 0.3f, ypos + ytan * 0.3f, zpos + ztan * 0.3f);
		}
		glEnd();
	}
	glEndList();
}

//Generates a sphere with tangents for lighting
//going radially around the sphere
void generateradialanisotropicsphere(void)
{
	int i, j;
	float u, v;
	float xpos = 0.0f, ypos = 0.0f, zpos = 0.0f;
	float xtan = 0.0f, ytan = 0.0f, ztan = 0.0f;

	glNewList(OBJECT_DISPLAY_LIST, GL_COMPILE);
	for(i = 0; i < OBJECT_RESOLUTION; i++)
	{
		glBegin(GL_TRIANGLE_STRIP);
		for(j = 0; j <= OBJECT_RESOLUTION; j++)
		{
			u = 2 * PI * ((float)(j % OBJECT_RESOLUTION) / OBJECT_RESOLUTION);
			v = PI * ((float)(i) / OBJECT_RESOLUTION);
			xpos = sin(u) * sin(v);
			ypos = cos(v);
			zpos = cos(u) * sin(v);
			xtan = -cos(u);
			ytan = 0;
			ztan = sin(u);
			glNormal3f(xpos, ypos, zpos);
			glTexCoord3f(xtan, ytan, ztan);
			glVertex3f(xpos, ypos, zpos);
			v = PI * ((float)((i + 1)) / OBJECT_RESOLUTION);
			xpos = sin(u) * sin(v);
			ypos = cos(v);
			zpos = cos(u) * sin(v);
			xtan = -cos(u);
			ytan = 0;
			ztan = sin(u);
			glNormal3f(xpos, ypos, zpos);
			glTexCoord3f(xtan, ytan, ztan);
			glVertex3f(xpos, ypos, zpos);
		}
		glEnd();
	}
	glEndList();

	generatespherenormals();
}

//Calculates the tangent lines for a sphere with tangents
//going radially around the sphere
void generateradialspheretangentvectors(void)
{
	int i, j;
	float u, v;
	float xpos = 0.0f, ypos = 0.0f, zpos = 0.0f;
	float xtan = 0.0f, ytan = 0.0f, ztan = 0.0f;

	glNewList(TANGENT_DISPLAY_LIST, GL_COMPILE);
	for(i = 0; i <= OBJECT_RESOLUTION; i++)
	{
		glBegin(GL_LINES);
		for(j = 0; j <= OBJECT_RESOLUTION; j++)
		{
			u = 2 * PI * ((float)(j % OBJECT_RESOLUTION) / OBJECT_RESOLUTION);
			v = PI * ((float)(i) / OBJECT_RESOLUTION);
			xpos = sin(u) * sin(v);
			ypos = cos(v);
			zpos = cos(u) * sin(v);
			xtan = -cos(u);
			ytan = 0;
			ztan = sin(u);
			glVertex3f(xpos, ypos, zpos);
			glVertex3f(xpos + xtan * 0.3f, ypos + ytan * 0.3f, zpos + ztan * 0.3f);
		}
		glEnd();
	}
	glEndList();
}

//Generate a display list for lines that represent the normals for a torus
void generatetorusnormals(void)
{
	int i, j;
	float u, v;
	float xnorm = 0.0f, ynorm = 0.0f, znorm = 0.0f;
	float xpos = 0.0f, ypos = 0.0f, zpos = 0.0f;

	glNewList(NORMAL_DISPLAY_LIST, GL_COMPILE);
	for(i = 0; i < OBJECT_RESOLUTION; i++)
	{
		glBegin(GL_LINES);
		for(j = 0; j < OBJECT_RESOLUTION; j++)
		{
			u = 2 * PI * ((float)(j) / OBJECT_RESOLUTION);
			v = 2 * PI * ((float)(i) / OBJECT_RESOLUTION);
			xpos = cos(u) * (TORUS_MAJOR_RADIUS + TORUS_MINOR_RADIUS * cos(v));
			ypos = TORUS_MINOR_RADIUS * sin(v);
			zpos = sin(u) * (TORUS_MAJOR_RADIUS + TORUS_MINOR_RADIUS * cos(v));
			xnorm = cos(u) * cos(v);
			ynorm = sin(v);
			znorm = sin(u) * cos(v);
			glVertex3f(xpos, ypos, zpos);
			glVertex3f(xpos + xnorm * 0.1f, ypos + ynorm * 0.1f, zpos + znorm * 0.1f);
		}
		glEnd();
	}
	glEndList();
}

//Generates a torus with tangents for lighting
//going around the torus in vertical lines
void generateaxialanisotropictorus(void)
{
	int i, j;
	float u, v;
	float xnorm = 0.0f, ynorm = 0.0f, znorm = 0.0f;
	float xpos = 0.0f, ypos = 0.0f, zpos = 0.0f;
	float xtan = 0.0f, ytan = 0.0f, ztan = 0.0f;

	glNewList(OBJECT_DISPLAY_LIST, GL_COMPILE);
	for(i = 0; i < OBJECT_RESOLUTION; i++)
	{
		glBegin(GL_TRIANGLE_STRIP);
		for(j = 0; j <= OBJECT_RESOLUTION; j++)
		{
			u = 2 * PI * ((float)(j % OBJECT_RESOLUTION) / OBJECT_RESOLUTION);
			v = 2 * PI * ((float)(i) / OBJECT_RESOLUTION);
			xpos = cos(u) * (TORUS_MAJOR_RADIUS + TORUS_MINOR_RADIUS * cos(v));
			ypos = TORUS_MINOR_RADIUS * sin(v);
			zpos = sin(u) * (TORUS_MAJOR_RADIUS + TORUS_MINOR_RADIUS * cos(v));
			xnorm = cos(u) * cos(v);
			ynorm = sin(v);
			znorm = sin(u) * cos(v);
			xtan = -cos(u) * sin(v);
			ytan = cos(v);
			ztan = -sin(u) * sin(v);
			glNormal3f(xnorm, ynorm, znorm);
			glTexCoord3f(xtan, ytan, ztan);
			glVertex3f(xpos, ypos, zpos);
			v = 2 * PI * ((float)((i + 1)) / OBJECT_RESOLUTION);
			xpos = cos(u) * (TORUS_MAJOR_RADIUS + TORUS_MINOR_RADIUS * cos(v));
			ypos = TORUS_MINOR_RADIUS * sin(v);
			zpos = sin(u) * (TORUS_MAJOR_RADIUS + TORUS_MINOR_RADIUS * cos(v));
			xnorm = cos(u) * cos(v);
			ynorm = sin(v);
			znorm = sin(u) * cos(v);
			xtan = -cos(u) * sin(v);
			ytan = cos(v);
			ztan = -sin(u) * sin(v);
			glNormal3f(xnorm, ynorm, znorm);
			glTexCoord3f(xtan, ytan, ztan);
			glVertex3f(xpos, ypos, zpos);
		}
		glEnd();
	}
	glEndList();

	generatetorusnormals();
}

//Calculates tangents for a torus
//going around the torus in vertical lines
void generateaxialtorustangentvectors(void)
{
	int i, j;
	float u, v;
	float xpos = 0.0f, ypos = 0.0f, zpos = 0.0f;
	float xtan = 0.0f, ytan = 0.0f, ztan = 0.0f;

	glNewList(TANGENT_DISPLAY_LIST, GL_COMPILE);
	for(i = 0; i < OBJECT_RESOLUTION; i++)
	{
		glBegin(GL_LINES);
		for(j = 0; j < OBJECT_RESOLUTION; j++)
		{
			u = 2 * PI * ((float)(j) / OBJECT_RESOLUTION);
			v = 2 * PI * ((float)(i) / OBJECT_RESOLUTION);
			xpos = cos(u) * (TORUS_MAJOR_RADIUS + TORUS_MINOR_RADIUS * cos(v));
			ypos = TORUS_MINOR_RADIUS * sin(v);
			zpos = sin(u) * (TORUS_MAJOR_RADIUS + TORUS_MINOR_RADIUS * cos(v));
			xtan = -cos(u) * sin(v);
			ytan = cos(v);
			ztan = -sin(u) * sin(v);
			glVertex3f(xpos, ypos, zpos);
			glVertex3f(xpos + xtan * 0.3f, ypos + ytan * 0.3f, zpos + ztan * 0.3f);
		}
		glEnd();
	}
	glEndList();
}

//Generates a torus with tangents for lighting
//going around the torus in radially
void generateradialanisotropictorus(void)
{
	int i, j;
	float u, v;
	float xnorm = 0.0f, ynorm = 0.0f, znorm = 0.0f;
	float xpos = 0.0f, ypos = 0.0f, zpos = 0.0f;
	float xtan = 0.0f, ytan = 0.0f, ztan = 0.0f;

	glNewList(OBJECT_DISPLAY_LIST, GL_COMPILE);
	for(i = 0; i < OBJECT_RESOLUTION; i++)
	{
		glBegin(GL_TRIANGLE_STRIP);
		for(j = 0; j <= OBJECT_RESOLUTION; j++)
		{
			u = 2 * PI * ((float)(j % OBJECT_RESOLUTION) / OBJECT_RESOLUTION);
			v = 2 * PI * ((float)(i) / OBJECT_RESOLUTION);
			xpos = cos(u) * (TORUS_MAJOR_RADIUS + TORUS_MINOR_RADIUS * cos(v));
			ypos = TORUS_MINOR_RADIUS * sin(v);
			zpos = sin(u) * (TORUS_MAJOR_RADIUS + TORUS_MINOR_RADIUS * cos(v));
			xnorm = cos(u) * cos(v);
			ynorm = sin(v);
			znorm = sin(u) * cos(v);
			xtan = -sin(u);
			ytan = 0;
			ztan = cos(u);
			glNormal3f(xnorm, ynorm, znorm);
			glTexCoord3f(xtan, ytan, ztan);
			glVertex3f(xpos, ypos, zpos);
			v = 2 * PI * ((float)((i + 1)) / OBJECT_RESOLUTION);
			xpos = cos(u) * (TORUS_MAJOR_RADIUS + TORUS_MINOR_RADIUS * cos(v));
			ypos = TORUS_MINOR_RADIUS * sin(v);
			zpos = sin(u) * (TORUS_MAJOR_RADIUS + TORUS_MINOR_RADIUS * cos(v));
			xnorm = cos(u) * cos(v);
			ynorm = sin(v);
			znorm = sin(u) * cos(v);
			xtan = -sin(u);
			ytan = 0;
			ztan = cos(u);
			glNormal3f(xnorm, ynorm, znorm);
			glTexCoord3f(xtan, ytan, ztan);
			glVertex3f(xpos, ypos, zpos);
		}
		glEnd();
	}
	glEndList();

	generatetorusnormals();
}

//Calculates tangent lines for a torus
//going around the torus radially
void generateradialtorustangentvectors(void)
{
	int i, j;
	float u, v;
	float xpos = 0.0f, ypos = 0.0f, zpos = 0.0f;
	float xtan = 0.0f, ytan = 0.0f, ztan = 0.0f;

	glNewList(TANGENT_DISPLAY_LIST, GL_COMPILE);
	for(i = 0; i < OBJECT_RESOLUTION; i++)
	{
		glBegin(GL_LINES);
		for(j = 0; j < OBJECT_RESOLUTION; j++)
		{
			u = 2 * PI * ((float)(j) / OBJECT_RESOLUTION);
			v = 2 * PI * ((float)(i) / OBJECT_RESOLUTION);
			xpos = cos(u) * (TORUS_MAJOR_RADIUS + TORUS_MINOR_RADIUS * cos(v));
			ypos = TORUS_MINOR_RADIUS * sin(v);
			zpos = sin(u) * (TORUS_MAJOR_RADIUS + TORUS_MINOR_RADIUS * cos(v));
			xtan = -sin(u);
			ytan = 0;
			ztan = cos(u);
			glVertex3f(xpos, ypos, zpos);
			glVertex3f(xpos + xtan * 0.3f, ypos + ytan * 0.3f, zpos + ztan * 0.3f);
		}
		glEnd();
	}
	glEndList();
}

//Draws the current object with anisotropic lighting
void drawanisotropicobject(void)
{
	glCallList(OBJECT_DISPLAY_LIST);
}

//Draws the tangent vectors for the current object
void drawtangentvectors(void)
{
	glCallList(TANGENT_DISPLAY_LIST);
}

//Draws the normal vectors for the current object
void drawnormalvectors(void)
{
	glCallList(NORMAL_DISPLAY_LIST);
}

void menu(int selection)
{
	controlmode = selection;
}

void idle(void)
{
	glutPostRedisplay();
}

void init(void)
{
	int i;

	//General OpenGL setup
	glShadeModel(GL_SMOOTH);
	glEnable(GL_CULL_FACE);
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);

	//Setup texturing
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glGenTextures(1, &anisotropiclightingtexture);
	generateanisotropiclighting();
	glEnable(GL_TEXTURE_2D);

	//Set up blending
	glBlendFunc(GL_ONE, GL_ONE);

	//Pre-calculate sphere and tangents
	generateaxialanisotropicsphere();
	generateaxialspheretangentvectors();

	//Set up all lighting variables
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lightambient);
	glLightfv(GL_LIGHT0, GL_AMBIENT, lightambient);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 0);
	glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, material);

	lightactive[0] = 1;

	for(i = 1; i < NUMBER_OF_LIGHTS; i++)
		lightactive[i] = 0;

	//Set lighting values
	for(i = 0; i < NUMBER_OF_LIGHTS; i++)
	{
		lightrotation[i] = 1.0f;
		lightelevation[i] = 1.0f;
		lightdiffuse[i][0] = 0.0f;
		lightdiffuse[i][1] = 0.0f;
		lightdiffuse[i][2] = 0.0f;
		lightdiffuse[i][3] = 0.0f;
	}
	lightdiffuse[0][0] = 1.0f;
	lightdiffuse[0][1] = 1.0f;
	lightdiffuse[0][2] = 1.0f;
	lightdiffuse[0][3] = 1.0f;
}

void display(void)
{
	int i;

	//Set the camera position
	camerapos[0] = sin(camerarotation) * sin(cameraelevation) * cameradistance;
	camerapos[1] = cos(cameraelevation) * cameradistance;
	camerapos[2] = cos(camerarotation) * sin(cameraelevation) * cameradistance;

	//Set up for rendering
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	gluLookAt(camerapos[0], camerapos[1], camerapos[2],
				0.0f, 0.0f, 0.0f,
				0.0f, 1.0f, 0.0f);
	glTranslatef(object[0], object[1], object[2]);

	//Draw ambient pass over the object
	glDisable(GL_TEXTURE_2D);
	glDisable(GL_LIGHTING);
	glColor3f(r * ambientlight[0], g * ambientlight[1], b * ambientlight[2]);
	drawanisotropicobject();

	//Add in each light's contribution to the scene
	for(i = 0; i < NUMBER_OF_LIGHTS; i++)
	{
		if(!lightactive[i])
			continue;

		//Set up for the rendering pass
		glColor3f(1.0f, 1.0f, 1.0f);
		glEnable(GL_TEXTURE_2D);
		glEnable(GL_LIGHTING);
		glEnable(GL_BLEND);

		//Set the light position for the current light
		lightpos[i][0] = sin(lightrotation[i]) * sin(lightelevation[i]);
		lightpos[i][1] = cos(lightelevation[i]);
		lightpos[i][2] = cos(lightrotation[i]) * sin(lightelevation[i]);

		//Set light attributes for the current light
		glLightfv(GL_LIGHT0, GL_DIFFUSE, lightdiffuse[i]);
		glLightfv(GL_LIGHT0, GL_POSITION, lightpos[i]);

		//Set up the texture matrix to correctly calculate coordinates
		//given the tangent vectors, and the light and camera directions
		setupanisotropictexturematrix(i);
		//Draw the sphere using anisotropic lighting
		drawanisotropicobject();

		//Set up to render the light vector
		glColor3f(0.0f, 0.0f, 1.0f);
		glDisable(GL_TEXTURE_2D);
		glDisable(GL_LIGHTING);
		glDisable(GL_BLEND);

		//Draw light vector
		glBegin(GL_LINES);
		glVertex3f(lightpos[i][0] * 5.0f, lightpos[i][1] * 5.0f, lightpos[i][2] * 5.0f);
		glVertex3f(0.0f, 0.0f, 0.0f);
		glEnd();
	}

	//Draw tangent vectors on the object
	glColor3f(0.0f, 1.0f, 0.0f);
	if(displaytangentvectors == 1)
		drawtangentvectors();

	//Draw normal vectors on the object
	glColor3f(0.0f, 0.0f, 1.0f);
	if(displaynormalvectors == 1)
		drawnormalvectors();

	glutSwapBuffers();
}

void reshape(int w, int h)
{
	glViewport(0, 0, (GLsizei) w, (GLsizei) h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 0.1, 100.0);
	glMatrixMode(GL_MODELVIEW);
}

void mousemove(int x, int y)
{
	double modelviewmatrix[16], projectionmatrix[16];
	int viewport[4];
	double projectedpoint[3];
	float u;

	//Move camera
	if(controlmode == 0)
	{
		camerarotation += (x - lastx) / 100.0f;
		cameraelevation += (y - lasty) / 100.0f;

		if(cameraelevation < 0.1f)
			cameraelevation = 0.1f;
		if(cameraelevation > 3.0f)
			cameraelevation = 3.0f;

		camerapos[0] = sin(camerarotation) * sin(cameraelevation) * cameradistance;
		camerapos[1] = cos(cameraelevation) * cameradistance;
		camerapos[2] = cos(camerarotation) * sin(cameraelevation) * cameradistance;
	}
	//Move object
	else if(controlmode == 1)
	{
		glLoadIdentity();
		gluLookAt(camerapos[0], camerapos[1], camerapos[2],
				0.0f, -0.5f, 0.0f,
				0.0f, 1.0f, 0.0f);
		glGetDoublev(GL_MODELVIEW_MATRIX, modelviewmatrix);
		glGetDoublev(GL_PROJECTION_MATRIX, projectionmatrix);
		glGetIntegerv(GL_VIEWPORT, viewport);

		gluUnProject(x, (viewport[3] - y), 1.0, modelviewmatrix, projectionmatrix, viewport,
				&(projectedpoint[0]), &(projectedpoint[1]), &(projectedpoint[2]));

		u = camerapos[1] / (camerapos[1] - projectedpoint[1]);

		object[0] = camerapos[0] + u * (projectedpoint[0] - camerapos[0]);
		object[2] = camerapos[2] + u * (projectedpoint[2] - camerapos[2]);
	}
	//Move light
	else if(controlmode == 2)
	{
		lightrotation[currentlight] += (x - lastx) / 100.0f;
		lightelevation[currentlight] += (y - lasty) / 100.0f;

		if(lightelevation[currentlight] < 0.1f)
			lightelevation[currentlight] = 0.1f;
		if(lightelevation[currentlight] > 3.0f)
			lightelevation[currentlight] = 3.0f;
	}

	lastx = x;
	lasty = y;
	idle();
}

void mousedown(int button, int state, int x, int y)
{
	lastx = x;
	lasty = y;
}

void keyboard (unsigned char key, int x, int y)
{
	int i;
	switch (key)
	{
		//Quit the program
		case 27:
			exit(0);
			break;
		//Bring the camera farther away from the sphere
		case 'z':
			cameradistance += 0.5f;
			break;
		//Bring the camera closer to the sphere
		case 'x':
			cameradistance -= 0.5f;
			if(cameradistance < 3.0f)
				cameradistance = 3.0f;
			break;
		//Increase the amount of green in the sphere
		//or in the current light, depending on the
		//current color change target
		case 't':
			if(!colorchangetarget)
			{
				r += 0.1f;
				if(r > 1.0f)
					r = 1.0f;
				generateanisotropiclighting();
			}
			else
			{
				lightdiffuse[currentlight][0] += 0.1f;
				if(lightdiffuse[currentlight][0] > 1.0f)
					lightdiffuse[currentlight][0] = 1.0f;
			}
			break;
		//Decrease the amount of red in the sphere
		//or in the current light, depending on the
		//current color change target
		case 'g':
			if(!colorchangetarget)
			{
				r -= 0.1f;
				if(r < 0.0f)
					r = 0.0f;
				generateanisotropiclighting();
			}
			else
			{
				lightdiffuse[currentlight][0] -= 0.1f;
				if(lightdiffuse[currentlight][0] < 0.0f)
					lightdiffuse[currentlight][0] = 0.0f;
			}
			break;
		//Increase the amount of green in the sphere
		//or in the current light, depending on the
		//current color change target
		case 'y':
			if(!colorchangetarget)
			{
				g += 0.1f;
				if(g > 1.0f)
					g = 1.0f;
				generateanisotropiclighting();
			}
			else
			{
				lightdiffuse[currentlight][1] += 0.1f;
				if(lightdiffuse[currentlight][1] > 1.0f)
					lightdiffuse[currentlight][1] = 1.0f;
			}
			break;
		//Decrease the amount of green in the sphere
		//or in the current light, depending on the
		//current color change target
		case 'h':
			if(!colorchangetarget)
			{
				g -= 0.1f;
				if(g < 0.0f)
					g = 0.0f;
				generateanisotropiclighting();
			}
			else
			{
				lightdiffuse[currentlight][1] -= 0.1f;
				if(lightdiffuse[currentlight][1] < 0.0f)
					lightdiffuse[currentlight][1] = 0.0f;
			}
			break;
		//Increase the amount of blue in the sphere
		//or in the current light, depending on the
		//current color change target
		case 'u':
			if(!colorchangetarget)
			{
				b += 0.1f;
				if(b > 1.0f)
					b = 1.0f;
				generateanisotropiclighting();
			}
			else
			{
				lightdiffuse[currentlight][2] += 0.1f;
				if(lightdiffuse[currentlight][2] > 1.0f)
					lightdiffuse[currentlight][2] = 1.0f;
			}
			break;
		//Decrease the amount of blue in the sphere
		//or in the current light, depending on the
		//current color change target
		case 'j':
			if(!colorchangetarget)
			{
				b -= 0.1f;
				if(b < 0.0f)
					b = 0.0f;
				generateanisotropiclighting();
			}
			else
			{
				lightdiffuse[currentlight][2] -= 0.1f;
				if(lightdiffuse[currentlight][2] < 0.0f)
					lightdiffuse[currentlight][2] = 0.0f;
			}
			break;
		//Toggle tangent vector display on or off
		case 'v':
			displaytangentvectors = -displaytangentvectors + 1;
			break;
		//Toggle normal vector display on or off
		case 'n':
			displaynormalvectors = -displaynormalvectors + 1;
			break;
		//Toggle through the available lights in the scene
		case 'l':
			do
			{
				currentlight++;
				currentlight %= NUMBER_OF_LIGHTS;
			} while(!lightactive[currentlight]);
			break;
		//Toggle the color change target to be the sphere
		//or the current light
		case 'c':
			colorchangetarget = -colorchangetarget + 1;
			break;
		//Increase the specular exponent and
		//regenerate the lighting texture map
		case 'e':
			specularexponent += 2.0f;
			generateanisotropiclighting();
			break;
		//Decrease the specular exponent and
		//regenerate the lighting texture map
		case 'd':
			specularexponent -= 2.0f;
			if(specularexponent < 2.0f)
				specularexponent = 2.0f;
			generateanisotropiclighting();
			break;
		//Toggles what type of object to draw
		case 'o':
			currentobject++;
			currentobject %= 4;

			if(currentobject == 0)
			{
				generateaxialanisotropicsphere();
				generateaxialspheretangentvectors();
			}
			else if(currentobject == 1)
			{
				generateradialanisotropicsphere();
				generateradialspheretangentvectors();
			}
			else if(currentobject == 2)
			{
				generateaxialanisotropictorus();
				generateaxialtorustangentvectors();
			}
			else if(currentobject == 3)
			{
				generateradialanisotropictorus();
				generateradialtorustangentvectors();
			}
			break;
		//Add a light, if there is room for one
		case 'a':
			for(i = 0; i < NUMBER_OF_LIGHTS; i++)
			{
				if(!lightactive[i])
				{
					lightrotation[i] = 1.0f;
					lightelevation[i] = 1.0f;
					lightactive[i] = 1;
					currentlight = i;
					lightdiffuse[i][0] = 1.0f;
					lightdiffuse[i][1] = 1.0f;
					lightdiffuse[i][2] = 1.0f;
					lightdiffuse[i][3] = 1.0f;
					break;
				}
			}
			break;
		//Remove a light, if there is more than one active
		case 'r':
			lightactive[currentlight] = 0;
			for(i = 0; i < NUMBER_OF_LIGHTS; i++)
			{
				currentlight++;
				currentlight %= NUMBER_OF_LIGHTS;

				if(lightactive[currentlight])
					break;
			}
			if(i = NUMBER_OF_LIGHTS)
				lightactive[currentlight] = 1;

			break;
		default:
			break;
	}
}

int main(int argc, char** argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(800, 600);
	glutInitWindowPosition(100, 100);
	glutCreateWindow(argv[0]);
	init();

	//Setup menu
	glutCreateMenu(menu);
	glutAddMenuEntry("Mouse changes view direction", 0);
	glutAddMenuEntry("Mouse changes object position", 1);
	glutAddMenuEntry("Mouse changes light direction", 2);
	glutAttachMenu(GLUT_RIGHT_BUTTON);

	//Set up control functions
	glutIdleFunc(idle);
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboard);
	glutMotionFunc(mousemove);
	glutMouseFunc(mousedown);
	glutMainLoop();
	return 0; 
}
