// This program combines object-oriented programming with graphics.
// One consequence of this choice is that the program contains fewer
// global variables than many graphics programs.

#include <gl/glut.h>
#include <iostream.h>
#include <ctype.h>

const int ESC = 27;

/////////////////////////////////////// Object Oriented stuff

///////////////////////////////////////////////// class Drawable

class Drawable {
	// An abstract class for objects that can be drawn.
public:
	Drawable(Drawable* inext = NULL, char inum = '0');
	virtual void draw() = 0;
	virtual void dokey(int key);
	virtual void move();
	Drawable *get_next() { return next; }
	char get_num() { return num; }
	void select (bool flag) { selected = flag; }
protected:
	Drawable *next;		// Pointer to next object in list.
	char num;			// Object's name.
	bool selected;		// Selected object receives keystrokes.
	GLfloat x, y, z;	// Object's position.
};

Drawable::Drawable(Drawable* inext, char inum) {
	// Constructor sets initial values.
	next = inext;
	num = inum;
	selected = false;
	x = 0.0;
	y = 0.0;
	z = -10.0;
}

void Drawable::dokey(int key) {
	// Keys change position of object.
	// Invalid keys have no effect.
	switch (key) {
	case 'l': 
		x -= 1.0;	
		break;
	case 'r': 
		x += 1.0; 
		break;
	case 'u': 
		y += 1.0; 
		break;
	case 'd': 
		y -= 1.0; 
		break;
	case 'f': 
		z += 1.0; 
		break;
	case 'b': 
		z -= 1.0; 
		break;
	}
}

void Drawable::move() {
	// Move the object to its current position.
	glTranslatef(x, y, z);
}

///////////////////////////////////////////////// class Sphere

class Sphere : public Drawable {
public:
	Sphere(Drawable *inext, char inum): Drawable(inext, inum) {
		r = 0.8f;
		g = 0.0f;
		b = 0.4f;
	}
	void draw();
private:
	GLfloat r, g, b;	// Colour components for sphere.
};

void Sphere::draw() {
	// A sphere is white if selected, and has its own colour otherwise.
	glPushMatrix();
	move();
	if (selected)
		glColor3f(1.0, 1.0, 1.0);
	else
		glColor3f(r, g, b);
	glutSolidSphere(1.0, 20, 20);
	glPopMatrix();
	if (next)
		next->draw();
}

///////////////////////////////////////////////// class Cone

class Cone : public Drawable {
public:
	Cone(Drawable *inext, char inum): Drawable(inext, inum) {
		r = 0.8f;
		g = 0.0f;
		b = 0.4f;
	}
	void draw();
private:
	GLfloat r, g, b;
};

void Cone::draw() {
	// A cone is a wire frame if selected, solid otherwise.
	glPushMatrix();
	move();
	glRotatef(90.0, -1.0, 0.0, 0.0);
	glColor3f(0.0, 0.6f, 0.8f);
	if (selected)
		glutWireCone(1.0, 2.0, 20, 20);
	else
		glutSolidCone(1.0, 2.0, 20, 20);
	glPopMatrix();
	if (next)
		next->draw();
}

/////////////////////////////////////// OpenGL stuff

// Global constants and variables.
const int MAXOBJ = 9;
char object_num = '0';
Drawable *first = NULL;
Drawable *selected_object = NULL;

void display() {
	// Draw each object in list, if any.
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	if (first)
		first->draw();
	glutSwapBuffers();
}

void keyboard(unsigned char key, int x, int y) {
	// Digits select objects, n creates a new object, ESC terminates, 
	// other keys sent to selected object.  An invalid digit deselects
	// all objects.
	Drawable *p;
	switch (key) {

	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	case '8':
	case '9':
		// Use digit to select an object.
		if (selected_object) {
			selected_object->select(false);
			selected_object = NULL;
		}
		p = first;
		while (p) {
			if (p->get_num() == key) {
				selected_object = p;
				selected_object->select(true);
				cout << "Object " << key << " selected." << endl;
				break;
			}
			p = p->get_next();
		}
		if (!selected_object)
			cout << "No object selected." << endl;
		break;
	
	case 'n':
		// Create a new object, up to MAXOBJ objects.
		if (object_num < MAXOBJ + '0') {
			if (object_num++ % 2)
				first = new Cone(first, object_num);
			else
				first = new Sphere(first, object_num);
			if (selected_object)
				selected_object->select(false);
			selected_object = first;
			selected_object->select(true);
			cout << "Object " << object_num << " created." << endl;
		}
		else
			cout << "You already have " << MAXOBJ << " objects!" << endl;
		break;
	
	case ESC:
		exit(0);
	
	default:
		// If there is a selected object, send the key to it.
		if (selected_object) 
			selected_object->dokey(key);
	}
	glutPostRedisplay();
}


void main(int argc, char *argv[]) {

	// GLUT initialization
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(600, 400);
	glutInitWindowPosition(0, 0);
	glutCreateWindow("Object Oriented Graphics");
	glutDisplayFunc(display);
	glutKeyboardFunc(keyboard);

	// GL initialization
	glEnable(GL_DEPTH_TEST);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45.0, 1.5, 1.0, 20.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	// Instructions
	cout << 
		"Press 'n' for each new object (maximum 9 objects)." << endl <<
		"Press '1', '2', ..., '9' to select an object." << endl <<
		"Press 'l'eft, 'r'ight, 'u'p, 'd'own, 'f'orwards, " << endl <<
		"or 'b'ackwards to move an object." << endl <<
		"Press ESC to exit." << endl << endl;

	glutMainLoop();
}

