// This program uses mouse movements to demonstrate 
// the effect of properties of lights and materials.
// The program also uses bitmapped text.
 
#include <GL/glut.h>
#include <stdio.h>
#include <string.h>

#define ESC 27

// Dimensions of current window (reshaping is supported). 

int width = 800;
int height = 600;

// Global variables that are set by keystrokes or 
// mouse movements and used to control the display.
 
unsigned char key = ' ';	// Last key pressed 
int colour = -1;			// Colour selection: 0=r, 1=g, 2=b, -1=w. 

GLfloat ar = 1.33f;			// Aspect ratio 
GLfloat xm = 0.5;			// X mouse position 0 < xm < 1 
GLfloat ym = 0.5;			// Y mouse position 0 < ym < 1 

// Light properties (controlled by mouse position) 

GLfloat l_pos[] = { 5.0, 1.0, 5.0, 1.0 };		// Position 
GLfloat l_diff[] = { 1.0, 1.0, 1.0, 1.0 };		// Diffuse 
GLfloat l_amb[] = { 0.2f, 0.2f, 0.2f, 1.0 };	// Ambient 
GLfloat l_spec[] = { 0.0, 0.0, 0.0, 1.0 };		// Specular 

// Model properties (controlled by mouse position) 

GLfloat m_diff[] = { 1.0, 1.0, 1.0, 1.0 };		// Diffuse 
GLfloat m_amb[] = { 0.2f, 0.2f, 0.2f, 1.0 };	// Ambient 
GLfloat m_spec[] = { 0.0, 0.0, 0.0, 1.0 };		// Specular 
GLfloat m_emit[] = { 0.0, 0.0, 0.0, 1.0 };		// Emission 
GLfloat m_shine[] = { 0.0 };					// Shininess 

// Update the value of a property.  If the current colour is 
// 0, 1, or 2, change the corresponding colour component only.  
// If colour=-1, change all components to the same value.
 	
void set (GLfloat p[], GLfloat v) {
	if (colour >= 0)
		p[colour] = v;
	else {
		int i;
		for (i = 0; i < 3; i++)
			p[i] = v;
	}
}

// Display a bit-mapped character string. 

void showstring (GLfloat x, GLfloat y, char *string) {
	int len, i;
	glRasterPos2f(x, y);
	len = (int) strlen(string);
	for (i = 0; i < len; i++) 
		glutBitmapCharacter(GLUT_BITMAP_9_BY_15, string[i]);
}

// Display the value of a property represented as an array with n components. 

void show (GLfloat y, char *name, GLfloat a[], int n) {
	char buf[100];
	showstring(0.0, y, name);
	if (n == 1)
		sprintf(buf, "%8.3f", a[0]);
	else
		sprintf(buf, "%8.3f %8.3f %8.3f ", a[0], a[1], a[2]);
	showstring(0.7f, y, buf);
}

// Display the values of all lighting parameters. 

void report ()
{	
	char buf[100];
	showstring(0.0, 0.0, "Light        R        G        B");
	show(-0.1f, "d Diffuse", l_diff, 3);
	show(-0.2f, "a Ambient", l_amb, 3);
	show(-0.3f, "s Specular", l_spec, 3);
	show(-0.4f, "p Position", l_pos, 3);
	showstring(0.0, -0.6f, "Model        R        G        B");
	show(-0.7f, "d Diffuse", m_diff, 3);
	show(-0.8f, "a Ambient", m_amb, 3);
	show(-0.9f, "s Specular", m_spec, 3);
	show(-1.0f, "e Emissive", m_emit, 3);
	show(-1.1f, "i Shininess", m_shine, 1);
	strcpy(buf, "");
	switch (colour)
	{
		case -1:
			strcpy(buf, "Colour: white.");
			break;
		case 0:
			strcpy(buf, "Colour: red.");
			break;
		case 1:
			strcpy(buf, "Colour: green.");
			break;
		case 2:
			strcpy(buf, "Colour: blue.");
			break;
	}
	showstring(0.0, -1.3f, buf);
	strcpy(buf, "");
	switch (key) 
	{
		case 'd':
			strcpy(buf, "Property: diffuse.");
			break;
		case 'a':
			strcpy(buf, "Property: ambient.");
			break;
		case 's':
			strcpy(buf, "Property: specular.");
			break;
		case 'e':
			strcpy(buf, "Property: emission.");
			break;
		case 'i':
			strcpy(buf, "Property: shininess.");
			break;
		case 'p':
			strcpy(buf, "Property: light position.");
			break;
	}
	showstring(1.5, -1.3f, buf);
}

// The display function uses the current key to select light and model
// properties, then it uses the current mouse position to change those
// properties.  After the properties have been set, the lights and model
// are defined and drawn on the screen, along with parameter values.
 
void display (void) {
    // Use the mouse position to set values of the specified properties. 

    switch (key)     {
	case 'd':
		set(l_diff, xm);
		set(m_diff, ym);
		break;
	case 'a':
		set(l_amb, xm);
		set(m_amb, ym);
		break;
	case 's':
		set(l_spec, xm);
		set(m_spec, ym);
		break;
	case 'e':
		set(m_emit, ym);
		break;
	case 'i':
		m_shine[0] = 128.0 * ym;
		break;
    	case 'p':
		l_pos[0] = 20.0 * (xm - 0.5f);
		l_pos[1] = 20.0 * (ym - 0.5f);
		break;
	default:
		break;
    }
    
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Assign the chosen values. 

    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, m_diff);
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, m_amb);
    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, m_spec);
    glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, m_shine);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, l_diff);
    glLightfv(GL_LIGHT0, GL_AMBIENT, l_amb);
    glLightfv(GL_LIGHT0, GL_SPECULAR, l_spec);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    // Position the light and then move the model into the viewing volume. 

    glLightfv(GL_LIGHT0, GL_POSITION, l_pos);
    glTranslatef(-1.0, -1.0, -8.0);

    // Disable lighting while displaying text. 

    glDisable(GL_LIGHTING);
    glPushMatrix();
    glTranslatef(0.8f, 0.8f, 0.0);
    report();
    glEnable(GL_LIGHTING);
    glPopMatrix();

    // Display the objects. 

    glPushMatrix();
    glRotatef(90.0, -1.0, 0.0, 0.0);
    glutSolidCone(0.5, 2.0, 40, 40);
    glPopMatrix();
    glTranslatef(0.0, 2.5, 0.0);
    glutSolidSphere(0.5, 40,40); 

    // Swap buffers for smooth animation. 

    glutSwapBuffers();
}

// Respond to reshape request by changing the viewport and the projection. 

void reshape (int w, int h) {
	glViewport(0, 0, w, h);
	width = w;
	height = h;
	ar = (GLfloat) width / (GLfloat) height;
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(30.0, ar, 1.0, 10.0);
	glutPostRedisplay();
}

// Respond to mouse movements by setting xm and ym to values in [0,1]. 

void drag (int x, int y) {
	xm = (GLfloat) x / (GLfloat) width;
	ym = 1.0 - (GLfloat) y / (GLfloat) height;
	glutPostRedisplay();
}

// Display some useful remarks in the Xterm window. 

void help () {
	printf("Press a key to select a property.\n");
	printf("Move the mouse to see the effect of changing the property.\n\n");
	printf("Key   X motion (light)  Y motion (object)\n");
	printf(" d       diffuse              diffuse\n");
	printf(" a       ambient              ambient\n");
	printf(" s       specular             specular\n");
	printf(" i                            shininess\n");
	printf(" e                            emission\n\n");
	printf("By default, mouse movement will change all colour components.\n");
	printf("To select one colour component:\n");
	printf("   Press 'r' for red.\n");
	printf("   Press 'g' for green.\n");
	printf("   Press 'b' for blue.\n");
	printf("   Press 'w' (white) to change all components.\n\n");
}

// Respond to a keystroke.  Some responses are processed here; the default 
// action is to record the keystroke and use it in the display callback function.
 
void keys (unsigned char thiskey, int x, int y) {
	switch (thiskey) {
		case ESC:
			exit(0);
		case 'h':		// Display help in text window 
			help();
			break;
		case 'r':		// Change red values only 
			colour = 0;
			break;
		case 'g':		// Change green values only 
			colour = 1;
			break;
		case 'b':		// Change blue values only 
			colour = 2;
			break;
		case 'w':		// Change all colours 
			colour = -1;
			break;
		default:		// Save key value for display() 
			key = thiskey;
			break;
	}
	glutPostRedisplay();
}
	
// The main program initializes everything and passes control to GLUT. 

int main (int argc, char *argv[]) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);
    glutInitWindowSize(width, height);
    glutCreateWindow("Lighting and Materials.  Press 'h' for instructions.");
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutKeyboardFunc(keys);
    glutMotionFunc(drag);
    glClearColor(0.0, 0.0, 0.0, 1.0);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_LIGHT0);
    glutMainLoop();
    return 0;
}

