Glut Object

             A Priliminary OpenGL Object Model

A. Second Edition

Why do you need to write those transformation time and time again?

I write this small libray to ease your job. What you need to do is
just use my "GlutObjectXXX"'s to create your model.
for example, you need a cone to place it at position of (10, 3, 2)
with axis pointing to direction of (1,1,0) with color of red and the
final size of width=5, height=4, depth=10, how would you write your
code?
Simple! Just do like following:

GlutObjectCone cone;
cone.setColor(1,0,0);
cone.setPostion(10,3,2);
cone.setDirection(1,1,0);
cone.setVolume(5,4,10);
And that is it, which means you are done.

See this example in "glutobjects.h" for object "GlutObjectFace" and
you can even composite object within another object.
 

The following are idea recorded in last edition.

I spent almost three days including Thanks-giving Day to try to establish an object-oriented model for those primitives and commonly-used model in OpenGL. However, the result is very disappointing. At some point, I have to stop this futile exploration as deadline of assignment is approaching. Then I realized one of the major reason why this way is not so popular. It is efficiency. When talking about efficiency, there are two aspect regarding it. One is the performance efficiency and the other is the coding efficiency. Students from Engineering department can enumerate various reasons against OOP when performance is highly required. Even students want to argue that OOP helps improving coding efficiency, they have to realize that OpenGL transformation must be done at a proper order and sometimes lack of flexibility in OOP will eliminate all those benefits brought by class model.

The idea of my small project is straight forward and it is different from Dr. Grogono who tries to wrap those basic primitives in OpenGL which is handy and sound. My design concentrate on all higher level object developed by programmer and try to find out all the common property and method of them. Then define a base class which will be inherited by all models and also define most commonly operations of them. i.e. Every model object has a property of coordinate, color, direction which is the axis of the object, occupying dimension which is the enclosing cube created by scaling on three dimensional direction. And all objects have to implement an abstract method display which will be called by a global callback. However, within this function programmer doesn't have to think about rotation, translate, scaling operations because base class already take care of that with the property of derived classes such as coordinate, direction, dimension volume etc. Only the most basic simple standard drawing function need to be defined by programmer. And an object manager class handles all creation, storage, viewing jobs of all objects. This sounds quite nice to me and one immediate benefit to this system is that I can used it in my project design by creating a script system that art user can create their 3D world by script file which will be read by my glut Object manager class. However, some viewing problem puzzled me for quite some time and I stopped there. Just in case I need them again, I store a half-finished version here.

 

B.The problem

In openGL, those error-prone transformation is tedious and boring. Why should I write those similar codes a thousand times?

 

C.The idea of program
 
I combined a little matrix calculation tool in this edition which solves my headache in computing rotation transformations.
D.The major functions
Just in case you are not familiar with OpenGL and want a test-drive, you need to install an OpenGL programming environment in your VC++ by 
copying "glut.h" file in your "/include" directory and a compiling library "glut32.lib" must be copied in your "lib" directory. The run-time
"dll", "glut32.dll" must be copied to your "system" or "system32" director in your "windows" directory. Once created with a "win console" 
project in VC++, go to the "setting" of "project" and "linking" tab to add "glut32.lib". 
(This sounds trivial for programmers, but it is worthwhile for beginners.:) And of course I can use a VC++ key word: 
#pragma comment( lib, "glut32") to ask linker to search glut32.lib automatically, still those .h .lib and .dll file needs to be copied by 
programmer.

E.Further improvement
The next step?
F.File listing
1. glutObject.h
2. glutObject.cpp
3. glutObjects.h
4. glutObjects.cpp
5. utility.h
6. rotation.h
7. rotation.cpp
8. main.cpp
file name: glutObject.h
#ifndef  GLUTOBJECT_H
#define GLUTOBJECT_H

#include <GL/glut.h>
//#include "glutobjects.h"
#include <cstdlib>
#include "utility.h"
//#define NICKSPACE_BEGIN namespace NICKSPACE {
//#define NICKSPACE_END }

//NICKSPACE_BEGIN
//const GLfloat PI=3.14159;
const GLint MaximumObjectNumber=150;




//typedef GLfloat[3] GlutVector;

class GlutObjectList;

extern GlutObjectList* gObjectListPtr;
extern enum AdjustViewState;
//extern enum PerspetiveStyle;
const GLfloat DefaultPosition[3]={0,0,0};
const GLfloat DefaultDirection[3]={1,0,0};
const GLfloat DefaultColor[3]={1,0,0};
const GLfloat DefaultVolume[3]={1,1,1};
const GLfloat DefaultBackColor[3]={1,1,1};
const bool DefaultDrawingStyle=false;//this means solid instead of mesh
const GLfloat DefaultPerspectAngle=70;
const GLfloat DefaultViewVolume[3]={800, 600, 800};
const GLfloat DefaultNearPlane=0;
const GLfloat DefaultWindowWidth=800;
const GLfloat DefaultWindowHeight=600;
const GLint DefaultWindowPosition_x=0;
const GLint DefaultWindowPosition_y=0;
const GLfloat DefaultTheta=deg2rad(80);
const GLfloat DefaultPhi=deg2rad(20);
const GLfloat DefaultRadius=20;
const GLfloat DefaultXAngle=0;
const GLfloat DefaultYAngle=0;
const GLfloat DefaultZAngle=0;
const GLfloat ViewAngleOffset=0.01;
const GLfloat RotateAngleOffset=5;
const bool DefaultObjectInternalState=false;
//void display();



class GlutObject
{
	friend class GlutObjectList;
	friend 	void arrayCopy(GLfloat tgt[], const GLfloat src[]);
public:
	void setPosition(GLfloat x, GLfloat y, GLfloat z);
	void setPosition(GLfloat* array);
	void setDirection(GLfloat x, GLfloat y, GLfloat z);
	void setDirection(GLfloat* array);
	void setVolume(GLfloat w, GLfloat h, GLfloat l);
	void setColor(GLfloat r, GLfloat g, GLfloat b);
	void doRotation();
	//void doTransform();
	GlutObject();
	//virtual void keyboardFunction(unsigned char key, GLint x,  GLint y);
	void draw();
	virtual void lastDraw();
	virtual void display()=0;
	
	void setClipRatio(GLfloat ratio);
protected:
	GLfloat position[3];
	GLfloat direction[3];
	GLfloat color[3];
	GLfloat volume[3];
	GLdouble clipPlane[4];
	GLfloat clipRatio;
	bool bDisableClip;
	bool bVisible;
	bool bInternal;
	void setClipPlane();
	void normalizeDirection();
};


class GlutObjectList
{
	friend class GlutObject;
	friend void displayCallback();
	friend void reshapeCallback(int w, int h);
	friend void keyboardCallback(unsigned char key, int x, int y);
	friend void arrayCopy(GLfloat tgt[], const GLfloat src[]);
	friend void specialCallback(int key, int x, int y);
	friend void run();
	friend void init();
public:
	GlutObjectList();
//	void keyboardFunction(unsigned char key, GLint x, GLint y);
	

protected:
	bool bDrawingMesh;
	GLint objectCount;
	GLfloat perspectAngle;
	//PerspetiveStyle ePerspective;
	bool bPerspective;
	bool bDrawAxis;
	GLfloat x_Angle, y_Angle, z_Angle;
	GLfloat theta;
	GLfloat phi;
	GLfloat radius;
	AdjustViewState eAdjustView;
	GLfloat viewVolume[3];
	GLfloat windowWidth, windowHeight;
	GLfloat viewPortWidth, viewPortHeight;
	int windowPos_x, windowPos_y;
	GLfloat nearPlane;
	GLfloat backColor[3];
	GLfloat foreColor[3];
	GlutObject*  objectList[MaximumObjectNumber];
	void reshapeCallback(int w, int h);
	void specialCallback(int key, int x, int y);
	void displayCallback();
	void keyboardCallback(unsigned char key, int x, int y);
	void addNewObject(GlutObject* ptr);
	//void draw3DAxis(GLfloat width, GLfloat height);
	void setDisplayMode();
	void adjustView();
	void init();
	void run();
	void drawAll();
};
//NICKSPACE_END

#endif
file name: glutObject.cpp
#include <GL/glut.h>
#include <cstdlib>
#include <cmath>
#include "glutObject.h"
#include "glutobjects.h"
#include "rotation.h"

extern GlutObject3DAxis axis;
//NICKSPACE_BEGIN
const GLint AdjustScale=2;
GlutObjectList* gObjectListPtr=NULL;
//enum PerspetiveStyle{PERSPECTIVE, ORTHOGONAL};
enum AdjustViewState{ADV_KEEPSHAPE=0, ADV_ENLARGE=AdjustScale, ADV_REDUCE=-AdjustScale};

GLdouble STDPlane[3][3]=
{
	{1,0,0},
	{0,1,0},
	{0,0,1}
};

const bool DefaultPerspectiveStyle=true;


void GlutObject::setColor(GLfloat r, GLfloat g, GLfloat b)
{
	color[0]=r;
	color[1]=g;
	color[2]=b;
}


void GlutObject::setPosition(GLfloat x, GLfloat y, GLfloat z)
{
	position[0]=x;
	position[1]=y;
	position[2]=z;
	//setClipPlane();
}

void GlutObject::setPosition(GLfloat* array)
{
	for(int i=0; i<3; i++)
	{
		position[i]=array[i];
	}
	//setClipPlane();
}

void GlutObject::setDirection(GLfloat x, GLfloat y, GLfloat z)
{
	direction[0]=x;
	direction[1]=y;
	direction[2]=z;
}

void GlutObject::setDirection(GLfloat* array)
{
	for (int i=0; i<3; i++)
	{
		direction[i]=array[i];
	}
}

void GlutObject::setVolume(GLfloat w, GLfloat h, GLfloat l)
{
	volume[0]=w;
	volume[1]=h;
	volume[2]=l;
}


GlutObject::GlutObject()
{
	if (gObjectListPtr==NULL)
	{
		gObjectListPtr=new GlutObjectList;
	}
	gObjectListPtr->addNewObject(this);
	arrayCopy(direction, DefaultDirection);
	arrayCopy(volume, DefaultVolume);
	arrayCopy(color, DefaultColor);
	arrayCopy(position, DefaultPosition);
	
	clipRatio=0.0;	
	setClipPlane();
	bDisableClip=true;
	bVisible=true;
	//bInternal=DefaultInternalState;
}

void arrayCopy(GLfloat tgt[], const GLfloat src[])
{
	for (int i=0; i<3; i++)
	{
		tgt[i]=src[i];
	}
}

void GlutObject::normalizeDirection()
{
	GLfloat temp;
	temp=sqrt(direction[0]*direction[0]+direction[1]*direction[1]+
		direction[2]*direction[2]);
	direction[0]/=temp;
	direction[1]/=temp;
	direction[2]/=temp;
}

/*
void GlutObject::doRotation()
{
	Matrix matrix;
	Vector vect(direction);
	matrix.rotate(vect, 0);

/*
	GLfloat anglex, angley;
	//GLfloat d;
	//normalizeDirection();
	anglex=atan2(direction[1], direction[2])*360/2/PI;
	//d=sqrt(direction[1]*direction[1]+direction[0]*direction[0]);
	angley=atan2(direction[0], direction[2])*360/2/PI;
	glRotatef(anglex, 1,0,0);	
	glRotatef(angley, 0,1,0);
	*/
	/*
	for (int i=0; i<3; i++)
	{
		if (direction[i]!=0)
		{
			glRotatef(direction[i], STDPlane[i][0], STDPlane[i][1],STDPlane[i][2]);
		}
	}

}
*/


void GlutObject::setClipRatio(GLfloat ratio)
{
	clipPlane[3]=-ratio/2;
}

void GlutObject::setClipPlane()
{
	//this is a naive clip plane setting, which is xz plane 
	//at position
	clipPlane[0]=clipPlane[2]=0;
	clipPlane[1]=1;
	clipPlane[3]=0;
}

void GlutObject::doRotation()
{
	GLfloat output[16];
	Matrix matrix;
	//Vector vertex(position);
	Vector axis(direction);
	matrix.rotateAxis(axis);

	//glPushMatrix();
	matrix.outputArray(output);
	//glPushMatrix();
	glMultMatrixf(output);
	//glPopMatrix();
}

void GlutObject::lastDraw()
{
}

//no rotatations!!!
void GlutObject::draw()
{
	glPushMatrix();
	//glLoadIdentity();
	glTranslatef(position[0], position[1], position[2]);
	glScalef(volume[0], volume[1], volume[2]);
	lastDraw();
	doRotation();
	glColor3f(color[0], color[1], color[2]);
	//glColor3f(color[0], color[1], color[2]);
	
	//glTranslatef(position[0], position[1], position[2]);
	
	//glTranslatef(-position[0], -position[1], -position[2]);
	//doRotation();
	//glTranslatef(-position[0], -position[1], -position[2]);

	if (!bDisableClip)
	{
		glClipPlane (GL_CLIP_PLANE0, clipPlane);	
		glEnable (GL_CLIP_PLANE0);
		display();
		glDisable(GL_CLIP_PLANE0);
	}
	else
	{
		display();
	}
	//glTranslatef(-position[0], -position[1], -position[2]);
	glPopMatrix();
}
					


//GlutObjectList

void reshapeCallback(int w, int h)
{
	gObjectListPtr->reshapeCallback(w, h);
}

void displayCallback()
{
	gObjectListPtr->displayCallback();
}

void specialCallback(int key, int x, int y)
{
	gObjectListPtr->specialCallback(key, x, y);
}

void keyboardCallback(unsigned char key, int x, int y)
{
	gObjectListPtr->keyboardCallback(key, x, y);
}

void init()
{
	gObjectListPtr->init();
}

void run()
{
	gObjectListPtr->run();
}

void GlutObjectList::addNewObject(GlutObject* ptr)
{
	objectList[objectCount++]=ptr;
}

GlutObjectList::GlutObjectList()
{
	objectCount=0;
	arrayCopy(backColor, DefaultBackColor);
	arrayCopy(viewVolume, DefaultViewVolume);
	nearPlane=DefaultNearPlane;
	perspectAngle=DefaultPerspectAngle;
	windowWidth=DefaultWindowWidth;
	windowHeight=DefaultWindowHeight;
	windowPos_x=DefaultWindowPosition_x;
	windowPos_y=DefaultWindowPosition_y;
	viewPortWidth=windowWidth;
	viewPortHeight=windowHeight;
	theta=DefaultTheta;
	phi=DefaultPhi;
	radius=DefaultRadius;
	//ePerspective=DefaultPerspectiveStyle;
	bPerspective=DefaultPerspectiveStyle;
	eAdjustView=ADV_KEEPSHAPE;
	x_Angle=DefaultXAngle;
	y_Angle=DefaultYAngle;
	z_Angle=DefaultZAngle;
	setDisplayMode();
	bDrawAxis=true;
	bDrawingMesh=DefaultDrawingStyle;

}

void GlutObjectList::init()
{
	// Set the current openGL matrix to GL_PROJECTION ie projection matrix.
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	// Load identitiy values in the above.
	if(bPerspective)
	{
		gluPerspective(perspectAngle, (GLfloat)windowWidth/(GLfloat)windowHeight,
			nearPlane, nearPlane+viewVolume[2]);
							// This sets the view volume to be a Perspective one.
	}
	else
	{
		// This sets the view volume to be a Orthographic one.
		glOrtho(-viewVolume[0]/2, viewVolume[0]/2, -viewVolume[1]/2, viewVolume[1]/2, 
			nearPlane, nearPlane+viewVolume[2]);
							
	}
	glMatrixMode(GL_MODELVIEW);	
							// Change the current matrix mode to Model-View matrix.
	glLoadIdentity();
							
	// set the background color to black.
	glClearColor (0.5, 0.5, 0.5, 0.0);

	glShadeModel(GL_SMOOTH);
	glEnable(GL_DEPTH_TEST);
	
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	//showMenu();

}

void GlutObjectList::keyboardCallback(unsigned char key, int x, int y)
{
	int i;
	switch(key)
	{
	case 'a':
	case 'A':
		bDrawAxis=!bDrawAxis;
		break;
	case 'p':
	case 'P':
		bPerspective=!bPerspective;
		//glClear(GL_COLOR_BUFFER_BIT);
		setDisplayMode();
		//glFlush();
		break;
	case 'w':
	case 'W':
		if (!bDrawingMesh)			
		{
			bDrawingMesh = true;	// Wireframe model.
			glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		}
		else
		{
			bDrawingMesh = false;	// Solid model.
			glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		}
		break;

	case 's':
		eAdjustView=ADV_REDUCE;
		adjustView();
		break;
	case 'S':
		eAdjustView=ADV_ENLARGE;
		adjustView();
		break;
		//THIS is only useful for debugging
	case 'c':
	case 'C':
		for (i=0; i<objectCount; i++)
		{
			objectList[i]->clipRatio+=0.1;
			objectList[i]->setClipRatio(objectList[i]->clipRatio);
		}
		break;
	case 27:
		exit(0);
		break;
	case 'x':
		x_Angle += RotateAngleOffset;
		break;
	case 'X':
		x_Angle -= RotateAngleOffset;
		break;
	case 'y':
		y_Angle += RotateAngleOffset;
		break;
	case 'Y':
		y_Angle -= RotateAngleOffset;
		break;
	case 'z':
		z_Angle += RotateAngleOffset;
		break;
	case 'Z':
		z_Angle -= RotateAngleOffset;
		break;	
	default:
		break;
	}
	glutPostRedisplay();

}
void GlutObjectList::specialCallback(int key, int x, int y)						
{
	switch (key)		
	{
		case GLUT_KEY_LEFT:		// Rotate the model about the y-axis.
			phi -= ViewAngleOffset;
			break;

		case GLUT_KEY_RIGHT:	
			phi += ViewAngleOffset;
			break;

		case GLUT_KEY_UP :		// Rotate the model about the x-axis.
			theta -= ViewAngleOffset;
			break;

		case GLUT_KEY_DOWN :	
			theta += ViewAngleOffset;
			break;
	}
	glutPostRedisplay();
}



void GlutObjectList::displayCallback()
{
	GLfloat eye_x, eye_y, eye_z;	
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glColor3f(1.0, 1.0, 1.0);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

/************************************************************
The view transformation		
*************************************************************/
	//theta_rad=deg2rad(theta);
	//phi_rad=deg2rad(phi);
	eye_x=radius*sin(theta)*sin(phi);
	eye_y=radius*cos(theta);
	eye_z=radius*sin(theta)*cos(phi);

	//glPushMatrix();

	gluLookAt(eye_x, eye_y, eye_z, 0, 0, 0, 0, 1, 0);

	//the following sequence is HIGHLY important because
	//you want the object to rotate w.r.t. to original world 
	//origin and you need to transform it between world coordinate and
	//view-coordinate
	glTranslatef(-eye_x, -eye_y, -eye_z);
	//showReferenceAxis();
	//glScalef(2, 2, 2);

	glRotatef(x_Angle, 1.0, 0.0, 0.0);	// Rotate the object by x_Angle about x-axis
	glRotatef(y_Angle, 0.0, 1.0, 0.0);	// Rotate the object by y_Angle about y-axis
	glRotatef(z_Angle, 0.0, 0.0, 1.0);	// Rotate the object by z_Angle about z-axis

	//drawHelicopter();
	drawAll();
	glTranslatef(eye_x, eye_y, eye_z);
	
	glutSwapBuffers();	// Use of double buffering to avoid flicker.
	//glClear(GL_COLOR_BUFFER_BIT);

	
//	glutPostRedisplay();
}

void GlutObjectList::setDisplayMode()
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	if(bPerspective)
	{
		gluPerspective(perspectAngle, viewVolume[0]/viewVolume[1], nearPlane, 
			nearPlane+viewVolume[2]);
	}
	else
	{	
		glOrtho(-viewVolume[0]/2, viewVolume[0]/2, -viewVolume[1]/2, viewVolume[1]/2, 
			nearPlane, nearPlane+viewVolume[2]);
	}					// Change the view volume to maintain the aspect ratio wrt to viewport.

	glMatrixMode(GL_MODELVIEW);
}

void GlutObjectList::adjustView()
{
	GLfloat ratio;
	ratio=viewVolume[0]/viewVolume[1];
	
	if (eAdjustView==ADV_KEEPSHAPE)
	{		
		if (windowWidth/windowHeight>ratio)
		{
			//too wider
			viewPortWidth= viewPortHeight*ratio;
		}
		else
		{
			//become thinner
			viewPortHeight=viewPortWidth/ratio;
		}
	}
	else
	{
		if (bPerspective)
		{
			perspectAngle-=eAdjustView;
			setDisplayMode();
		}
		else
		{
			viewVolume[0]+=eAdjustView;
			viewVolume[1]+=eAdjustView*ratio;
			viewVolume[2]+=eAdjustView;
			setDisplayMode();
			/*
			viewPortHeight+=eAdjustView;
			viewPortWidth+=eAdjustView*ratio;
			
			glViewport((windowWidth-viewPortWidth)/2, (windowHeight-viewPortHeight)/2,
				(windowWidth+viewPortWidth)/2, (windowHeight+viewPortHeight)/2);
			*/
			//glViewport(0, 0, viewPortWidth, viewPortHeight);
		}
		eAdjustView=ADV_KEEPSHAPE;
	}
}


void GlutObjectList::reshapeCallback(int w, int h)
{
	windowWidth=w;
	windowHeight=h;
	//adjustView();

	//glViewport((windowWidth-viewPortWidth)/2, (windowHeight-viewPortHeight)/2,
	//			(windowWidth+viewPortWidth)/2, (windowHeight+viewPortHeight)/2);

	//glViewport(0, 0, viewPortWidth, viewPortHeight);

	//adjustView();
	setDisplayMode();

	glMatrixMode(GL_MODELVIEW);
	glutPostRedisplay();
}

void GlutObjectList::drawAll()
{
	glMatrixMode(GL_MODELVIEW);
	//glLoadIdentity();
	//glTranslatef(0, 0, -50);
	
	if (bDrawAxis)
	{
		axis.draw();		
	}
	
	//glTranslatef(0, 0, 50);

	for (int i=0; i<objectCount; i++)
	{
		objectList[i]->draw();
	}
	glFlush();
}

	
void GlutObjectList::run()
{
	glutInitWindowSize(windowWidth, windowHeight);
	glutInitWindowPosition(windowPos_x, windowPos_y);
	glutCreateWindow("A generic object system");

	glutDisplayFunc(::displayCallback);
	glutReshapeFunc(::reshapeCallback);
	glutKeyboardFunc(::keyboardCallback);
	glutSpecialFunc(::specialCallback);
	glutMainLoop();
}

GLfloat deg2rad(GLfloat degree)
{
	return degree/360.0*2*PI;
}

//NICKSPACE_END
 

  
file name: glutObjects.h
#ifndef  GLUTOBJECTS_H
#define GLUTOBJECTS_H

#include "glutObject.h"

//NICKSPACE_BEGIN
const GLfloat DefaultTopDownRatio=0.5;

class GlutObjectSphere : public GlutObject
{
public:
	virtual void display();
};


class GlutObjectCube: public GlutObject
{
public:
	virtual void display();
};
	

class GlutObjectHalfCone: public GlutObject
{
public:
	virtual void display();
};


class GlutObjectTorus: public GlutObject
{
protected:
	GLfloat sectionAxisRatio;
public:	
	GlutObjectTorus(GLfloat ratio=0.5);
	virtual void display();
};

class GlutObjectSweepTorus: public GlutObjectTorus
{
protected:
	GLfloat sweepRatio;
public:
	GlutObjectSweepTorus(GLfloat ratio=0.5, GLfloat theSweepRatio=0.25);
	virtual void display();
};

class GlutObjectHalfTorus: public GlutObjectTorus
{
public:
	GlutObjectHalfTorus(GLfloat ratio=0.5);
	virtual void display();
};

class GlutObjectHalfSphere: public GlutObject
{
public:
	GlutObjectHalfSphere();
	virtual void display();
protected:
	//GLdouble clipPlane[4];
};

class GlutObject3DAxis: public GlutObject
{
public:
	GlutObject3DAxis();
	virtual void display();
};


class GlutObjectCylinder: public GlutObject
{
public:
	GlutObjectCylinder();
	virtual void display();
	void setRatio(GLfloat theRatio){ratio=theRatio;}
protected:
	GLfloat ratio;
};

class GlutObjectCone: public GlutObject
{
public:
	GlutObjectCone();
	virtual void display();
protected:
	
};

class GlutObjectSliceCone: public GlutObject
{
protected:
public:
	void lastDraw();
	void display();
};



class GlutObjectFace: public GlutObject
{
protected:
	GlutObjectSphere head;
	GlutObjectHalfTorus hatBrim;
	GlutObjectHalfSphere hatTop;
	GlutObjectSphere leftEye, rightEye;
	GlutObjectHalfSphere mouth;
	GlutObjectSliceCone nose;

public:
	GlutObjectFace();
	void display();
};

//NICKSPACE_END

#endif


file name: glutObjects.cpp
#include "glutObjects.h"
#include <GL/glut.h>
#include <cmath>



const GLfloat DefaultAxisLength=DefaultWindowWidth/40;
const GLfloat ArrowLength=0.5;
const GLint DrawingLineNumber=20;
//const GLfloat DefaultXAxisColor[3]={1,0,0};
GlutObject3DAxis axis;

//NICKSPACE_BEGIN
GLdouble xy[4] = {0.0, 0.0, 1.0, 0.0};
GLdouble yz[4] = {1.0, 0.0, 0.0, 0.0};
GLdouble zx[4] = {0.0, 1.0, 0.0, 0.0};


void GlutObjectCube::display()
{
	glutSolidCube(1);
}

void GlutObjectSphere::display()
{
	glutSolidSphere(1, DrawingLineNumber, DrawingLineNumber);

}



GlutObjectHalfSphere::GlutObjectHalfSphere()
{
	bDisableClip=false;
}

GlutObjectSweepTorus::GlutObjectSweepTorus(GLfloat ratio, GLfloat theSweepRatio)
{
	sectionAxisRatio=ratio;
	sweepRatio=theSweepRatio;
}

void GlutObjectSweepTorus::display()
{
	double clipPlanePlus[4]={0};
	clipPlane[0]=0;
	clipPlane[1]=1;
	clipPlane[2]=0;
	clipPlane[3]=0;

	clipPlanePlus[2]=clipPlanePlus[3]=0;
	if (sweepRatio!=0.25)
	{
		clipPlanePlus[0]=tan(sweepRatio*2*PI);
		clipPlanePlus[1]=-1;
	}
	else
	{
		clipPlanePlus[0]=1;
		clipPlanePlus[1]=0;
	}
	
	glRotatef(90, 0, 1, 0);
	glClipPlane(GL_CLIP_PLANE0, clipPlane);
	glClipPlane(GL_CLIP_PLANE1, clipPlanePlus);
	
	glEnable (GL_CLIP_PLANE0);
	glEnable (GL_CLIP_PLANE1);
	//glTranslatef(position[0], position[1]-volume[1]/2.0, position[2]);
	glutSolidTorus(sectionAxisRatio, 1, DrawingLineNumber, DrawingLineNumber);
	glDisable(GL_CLIP_PLANE0);
	glDisable(GL_CLIP_PLANE1);
	
}
GlutObjectHalfTorus::GlutObjectHalfTorus(GLfloat ratio)
{
	sectionAxisRatio=ratio;
}

void GlutObjectHalfTorus::display()
{
	clipPlane[0]=0;
	clipPlane[1]=0;
	clipPlane[2]=1;
	clipPlane[3]=0;
	
	glRotatef(90, 0, 1, 0);
	glClipPlane (GL_CLIP_PLANE0, clipPlane);
	
	glEnable (GL_CLIP_PLANE0);
	//glTranslatef(position[0], position[1]-volume[1]/2.0, position[2]);
	glutSolidTorus(sectionAxisRatio, 1, DrawingLineNumber, DrawingLineNumber);

	glDisable(GL_CLIP_PLANE0);
}


void GlutObjectHalfSphere::display()
{
	//assume clipplane is xy parallel

	clipPlane[0]=clipPlane[2]=0;
	clipPlane[1]=1;
	clipPlane[3]=0;

	glRotatef(90, 0, 0, 1);
	glClipPlane (GL_CLIP_PLANE0, clipPlane);	
	glEnable (GL_CLIP_PLANE0);
	//glTranslatef(position[0], position[1]-volume[1]/2.0, position[2]);
	glutSolidSphere(1, DrawingLineNumber, DrawingLineNumber);

	glDisable(GL_CLIP_PLANE0);
}

void GlutObjectHalfCone::display()
{
	clipPlane[0]=0;
	clipPlane[1]=0;
	clipPlane[2]=-1;
	clipPlane[3]=0.5;

	glRotatef(90, 0, 1, 0);
	glClipPlane (GL_CLIP_PLANE0, clipPlane);	
	glEnable (GL_CLIP_PLANE0);
	//glTranslatef(position[0], position[1]-volume[1]/2.0, position[2]);
	glutSolidCone(1, 1, DrawingLineNumber, DrawingLineNumber);

	glDisable(GL_CLIP_PLANE0);
}

GlutObjectTorus::GlutObjectTorus(GLfloat ratio)
{
	sectionAxisRatio=ratio;
}

void GlutObjectTorus::display()
{
	glRotatef(90, 0, 1, 0);
	glutSolidTorus(sectionAxisRatio, 1, DrawingLineNumber, DrawingLineNumber);
}

GlutObject3DAxis::GlutObject3DAxis()
{
	position[0]=position[1]=0;
	position[2]=0;
	color[0]=color[1]=0; 
	color[2]=1;
}

void GlutObject3DAxis::display()
{
	glPushMatrix();
	
	//glMatrixMode(GL_MODELVIEW);
	//glLoadIdentity();

	//glRotatef(30, 1,0, 0);
	//glRotatef(-45, 0,1, 0);
	//glRotatef(60, 0, 0, 1);
	
	glBegin(GL_LINES);
		glColor3f(1,0,0);
		//X axis
		glVertex3fv(position);
		glVertex3f(position[0]+DefaultAxisLength, position[1], position[2]);
		glColor3f(0,1,0);
		//Y axis
		glVertex3fv(position);
		glVertex3f(position[0], position[1]+DefaultAxisLength, position[2]);
		glColor3f(0,0,1);
		//Z axis
		glVertex3fv(position);
		glVertex3f(position[0], position[1], position[2]+DefaultAxisLength);

	glEnd();

	//draw three solid cone as arrows of axis
	//glLoadIdentity();
	//save current TM matrix
	//draw cone for X axis
	glPushMatrix();
	glTranslatef(position[0]+DefaultAxisLength-ArrowLength, position[1], position[2]);
	glRotatef(90, 0, 1, 0);
	//glTranslatef(x/2-ArrowLength, 0.0, 0.0);
	//glTranslatef(100, 0.0, 0.0);
	glutSolidCone(ArrowLength, ArrowLength*4, 10, 10);
	//glTranslatef(x/2+ArrowLength, ArrowLength, 0);
	//glutBitmapCharacter(GLUT_BITMAP_9_BY_15, 'X');
	
	//glLoadIdentity();
	glPopMatrix();
	glPushMatrix();
	//glRotatef(-90, 0, 1, 0);
	//glTranslatef(-(x/2-ArrowLength), 0.0, 0.0);
	glTranslatef(position[0], position[1]+DefaultAxisLength-ArrowLength, position[2]);
	glRotatef(-90, 1, 0, 0);
	glutSolidCone(ArrowLength, ArrowLength*4, 10, 10);
	glPopMatrix();
	//glLoadIdentity();
	
	glTranslatef(position[0], position[1], position[2]+DefaultAxisLength-ArrowLength);
	//glRotatef(-90, 1, 0, 0);
	glutSolidCone(ArrowLength, ArrowLength*4, 10, 10);
	glPopMatrix();
}

GlutObjectCylinder::GlutObjectCylinder()
{
	ratio=DefaultTopDownRatio;
}

void GlutObjectCylinder::display()
{
	GLUquadricObj* pObj;
	pObj = gluNewQuadric();
	// Creates a new quadrics object and returns a pointer to it.
	//glTranslatef(position[0], position[1], -position[2]/2);
	gluQuadricDrawStyle(pObj, GLU_FILL);
	
	//gluCylinder(pObj, volume[0]*ratio, volume[0], volume[2], volume[2]/2, volume[0]/2);
	//gluCylinder(pObj, (GLdouble)volume[0], (GLdouble)volume[0], (GLdouble)volume[2], 
	//	(int)volume[2], (int)volume[0]);
	glRotatef(90, 0, 1, 0);
	gluCylinder(pObj, 0.8, 0.8, 0.5, DrawingLineNumber, DrawingLineNumber);
	//gluSphere(pObj, volume[0], volume[0]/2, volume[0]/2);
							// Draw the cylinder with a radius : fRadius.
	gluDeleteQuadric(pObj);
}

void GlutObjectSliceCone::lastDraw()
{
	glRotatef(-90, 0,1,0);
}

void GlutObjectSliceCone::display()
{
	clipPlane[0]=1;
	clipPlane[1]=clipPlane[2]=clipPlane[2]=0;
	
	glRotatef(90, 0, 1,0);
	glClipPlane (GL_CLIP_PLANE0, clipPlane);	
	glEnable (GL_CLIP_PLANE0);
	//glTranslatef(position[0], position[1]-volume[1]/2.0, position[2]);
	glutSolidCone(1, 1, DrawingLineNumber, DrawingLineNumber);

	glDisable(GL_CLIP_PLANE0);
}

	

GlutObjectCone::GlutObjectCone()
{
	
}

void GlutObjectCone::display()
{
	//GLUquadricObj* pObj;
	//pObj = gluNewQuadric();
	// Creates a new quadrics object and returns a pointer to it.
	//glTranslatef(position[0], position[1], -position[2]/2);
	
	//glPushMatrix();
		glRotatef(90, 0,1,0);
		glutSolidCone(1, 1, DrawingLineNumber, DrawingLineNumber);
	//glPopMatrix();
	/*
	if (bDrawingMesh)
	{
		//gluQuadricDrawStyle(pObj, GLU_LINE);
		glutWireCone(volume[0], volume[2], volume[0], volume[2]);
	}
	else
	{
		//gluQuadricDrawStyle(pObj, GLU_FILL);
		glutSolidCone(volume[0], volume[2], volume[0], volume[2]);
	}
	*/
	
	//gluCylinder(pObj, volume[0]*ratio, volume[0], volume[2], volume[2]/2, volume[0]/2);
	//gluCylinder(pObj, (GLdouble)volume[0], (GLdouble)volume[0], (GLdouble)volume[2], 
	//	(int)volume[2], (int)volume[0]);
	//gluCylinder(pObj, 0.8, 0.8, 0.5, 20, 20);
	//gluPartialDisk(pObj, volume[0]*ratio, volume[0], volume[0], volume[1], 0, 90);
	//gluSphere(pObj, volume[0], volume[0]/2, volume[0]/2);
							// Draw the cylinder with a radius : fRadius.
	//gluDeleteQuadric(pObj);
}


GlutObjectFace::GlutObjectFace()
{
	head.setVolume(3, 5, 3);
	head.setDirection(0, 0,1);
	head.setPosition(0,0,0);
	head.setColor(0.5, 0.5, 0);
	hatBrim.setVolume(3, 1, 3);
	hatBrim.setColor(1, 1, 1);
	hatBrim.setDirection(0,1,0);
	hatBrim.setPosition(0, 3, 0);

	
	hatTop.setVolume(3, 3,3);
	hatTop.setDirection(0, -1,0);
	hatTop.setColor(1,0.6, 0.7);

	hatTop.setPosition(0, 3, 0);
	
	leftEye.setVolume(0.5, 0.5, 0.1);
	leftEye.setPosition(-1, -0.1, 2.5);
	rightEye.setPosition(1, -0.1,  2.5);

	leftEye.setDirection(0,0,1);
	rightEye.setVolume(0.5, 0.5, 0.1);
	rightEye.setDirection(0,0,1);
	leftEye.setColor(0,0,0);
	rightEye.setColor(0,0,0);

	mouth.setColor(1,0,0);
	mouth.setPosition(0, -3.2, 2.4);
	mouth.setVolume(1.0, 0.3, 0.3);
	mouth.setDirection(0,0,-1);

	nose.setColor(0,1,0);
	nose.setDirection(0,1,0);
	nose.setVolume(0.8, 1.8, 0.5);
	nose.setPosition(0, -1.8, 3);
		
}

void GlutObjectFace::display()
{

}
//NICKSPACE_END
	
file name: utility.h
#ifndef UTILITY_H
#define UTILITY_H
#include <GL/glut.h>
const double PI=3.1415926535;

GLfloat deg2rad(GLfloat degree);

//double rad2deg(double rad);
GLfloat rad2deg(GLfloat rad);


















#endif
 
 
file name: rotation.h
#ifndef ROTATION_H
#define ROTATION_H
#include <GL/glut.h>

enum AXIS {X_AXIS, Y_AXIS, Z_AXIS};

class Matrix;

class Vertex
{
public:
	Vertex(GLfloat x, GLfloat y, GLfloat z);
	GLfloat array[3];
};

class Vector
{
	friend class Matrix;
	//friend Vector operator*(const Vector& vect, GLfloat scalor);
public:
	GLfloat operator*(const Vector& other)const;//dot product
	Vector();
	Vector(GLfloat* otherArray);
	//Vector operator -();
	Vector operator*(GLfloat scalor) const;
	Vector(GLfloat d0, GLfloat d1, GLfloat d2);
	void clear();
	void display();
	GLfloat array[4];
	void normalize();

protected:
};

class Matrix
{
public:
	Matrix operator*(const Matrix& other);
	Vector operator*(const Vector& other);
	const Vector colVector(int index) const;
	void loadIdentity();
	Matrix();
	void translate(const Vector& vect);
	void translate(GLfloat d0, GLfloat d1, GLfloat d2);
	void rotate(AXIS axis, GLfloat degree);
	void rotateAxis(const Vector& axis);
	void rotateAxis(const Vector& vertex, const Vector& axis);
	void rotate(const Vector& vect, GLfloat degree);
	void rotate(const Vector& vertex, const Vector& axis);
	void rotate(const Vector& vertex, const Vector& axis, GLfloat degree);
	void display();
	Matrix arrayMultiply(Matrix* matrixArray, int number);
	Matrix transpose();
	void outputArray(GLfloat* output);
protected:
	Vector rowVector[4];
};

#endif
 
 
file name: rotation.cpp
#include <iostream>
#include <cmath>
#include "rotation.h"
#include "utility.h"


using namespace std;

GLfloat rad2deg(GLfloat rad)
{
	return rad/2/PI*360;
}


Vertex::Vertex(GLfloat x, GLfloat y, GLfloat z)
{
	array[0]=x;
	array[1]=y;
	array[2]=z;
}

//for the last one is just a scalor and shouldn't be multiply
Vector Vector::operator *(GLfloat scalor)const
{
	Vector result=*this;
	for (int i=0; i<3; i++)
	{
		result.array[i]=array[i]*scalor;
	}
	return result;
}
/*
Vector operator*(const Vector& vect, GLfloat scalor)
{
	Vector result=vect;
	return result.operator *(scalor);
}
*/

//to do:
void Vector::normalize()
{
	GLfloat temp=0;
	int i;
	for (i=0; i<3; i++)
	{
		temp+=array[i]*array[i];
	}
	temp=sqrt(temp);
	if (temp!=0)
	{
		for (i=0; i<4; i++)
		{
		}
	}
}

GLfloat Vector::operator *(const Vector& other)const
{
	GLfloat result=0;
	for (int i=0; i<4; i++)
	{
		result+=array[i]*other.array[i];
	}
	return result;
}

Vector::Vector()
{
	clear();
	array[3]=1;
}

Vector::Vector(GLfloat d0, GLfloat d1, GLfloat d2)
{
	array[0]=d0;
	array[1]=d1;
	array[2]=d2;
	array[3]=1;
}

Vector::Vector(GLfloat* otherArray)
{
	for (int i=0; i<3; i++)
	{
		array[i]=otherArray[i];
	}
	array[3]=1;
}

void Vector::clear()
{
	for (int i=0; i<4; i++)
	{
		array[i]=0;
	}
}

const Vector Matrix::colVector(int index) const
{
	Vector result;
	for (int i=0; i<4; i++)
	{
		result.array[i]=rowVector[i].array[index];
	}
	return result;
}

/*
Vector Vector::operator -()
{
	Vector result=*this;
	for (int i=0; i<3; i++)
	{
		result.array[i]=-result.array[i];
	}
	return result;
}
*/

void Vector::display()
{
	cout<<"\n\t(";
	for (int i=0; i<4; i++)
	{
		cout<<array[i];
		if (i!=3)
		{
			cout<<", ";
		}
	}
	cout<<")\n";
}

Matrix::Matrix()
{
	loadIdentity();
}

void Matrix::display()
{
	for(int i=0; i<4; i++)
	{
		rowVector[i].display();
	}
}

void Matrix::loadIdentity()
{
	for(int i=0; i<4; i++)
	{
		rowVector[i].clear();
		rowVector[i].array[i]=1;
	}
}


Matrix Matrix::operator *(const Matrix& other)
{
	Matrix result;
	for (int i=0; i<4; i++)
	{
		for (int j=0; j<4; j++)
		{
			result.rowVector[i].array[j]=rowVector[i]*other.colVector(j);
		}
	}
	return result;
}



void Matrix::translate(const Vector& vect)
{
	loadIdentity();
	for (int i=0; i<3; i++)
	{
		rowVector[i].array[3]=vect.array[i];
	}
}

void Matrix::translate(GLfloat d0, GLfloat d1, GLfloat d2)
{
	Vector vect(d0, d1, d2);
	translate(vect);	
}


void Matrix::rotate(enum AXIS axis, GLfloat degree)
{
	loadIdentity();
	GLfloat rad=deg2rad(degree);
	switch(axis)
	{
	case X_AXIS:
		rowVector[1].array[1]=cos(rad);
		rowVector[2].array[2]=cos(rad);
		rowVector[1].array[2]=-sin(rad);
		rowVector[2].array[1]=sin(rad);
		break;
	case Y_AXIS:
		rowVector[0].array[0]=cos(rad);
		rowVector[2].array[2]=cos(rad);
		rowVector[0].array[2]=sin(rad);
		rowVector[2].array[0]=-sin(rad);
		break;
	case Z_AXIS:
		rowVector[0].array[0]=cos(rad);
		rowVector[1].array[1]=cos(rad);
		rowVector[0].array[1]=-sin(rad);
		rowVector[1].array[0]=sin(rad);
		break;
	}
}

void Matrix::outputArray(GLfloat* output)
{
	for (int r=0; r<4; r++)
	{
		for (int c=0; c<4; c++)
		{
			output[r*4+c]=colVector(r).array[c];
		}
	}
}

Matrix Matrix::transpose()
{
	Matrix result;
	for (int r=0; r<4; r++)
	{
		result.rowVector[r]=colVector(r);
	}
	return result;
}


Matrix Matrix::arrayMultiply(Matrix* matrixArray, int number)
{
	Matrix result;
	for (int i=0; i<number; i++)
	{
		result=result*matrixArray[i];
	}
	return result;
}


void Matrix::rotateAxis(const Vector& vertex, const Vector& axis)
{
	Matrix matrixArray[2];
	matrixArray[0].translate(vertex);
	matrixArray[1].rotateAxis(axis);
	//matrixArray[2].translate(vertex*(-1));
	*this=arrayMultiply(matrixArray, 2);	
}

void Matrix::rotateAxis(const Vector& axis)
{
	GLfloat a,b,c,norm, d;
	Matrix matrixArray[2];
	a=axis.array[0];
	b=axis.array[1];
	c=axis.array[2];
	norm=sqrt(a*a+b*b+c*c);
	if (norm!=0)
	{
		a/=norm;
		b/=norm;
		c/=norm;
	}
	else
	{
		printf("zero vector detected!\n");
		exit(1);
	}

	d=sqrt(c*c+b*b);
	//if it is not coincide with x_axis
	if (b==1||b==-1)
	{
		matrixArray[0].rotate(Y_AXIS, -90);
	}
	else
	{
		matrixArray[0].rotate(Y_AXIS, -rad2deg(acos(a/sqrt(1-b*b))));
	}
	matrixArray[1].rotate(Z_AXIS, rad2deg(asin(b)));

	

	//(matrixArray[0]*matrixArray[1]).display();
	//cout<<"\n";
	*this=matrixArray[0]*matrixArray[1];

}

void Matrix::rotate(const Vector& vertex, const Vector& axis, GLfloat degree)
{
	Matrix matrixArray[3];
	Vector newVertex;
	newVertex=vertex*(-1.0);
	matrixArray[0].translate(vertex);
	matrixArray[1].rotate(axis, degree);
	matrixArray[2].translate(newVertex);
	/*
	for (int i=0; i<3; i++)
	{
		matrixArray[i].display();
	}
	*/
	*this=matrixArray[0]*matrixArray[1]*matrixArray[2];
}


void Matrix::rotate(const Vector& vect, GLfloat degree)
{
	GLfloat a,b,c,norm, d;
	Matrix matrixArray[5];
	a=vect.array[0];
	b=vect.array[1];
	c=vect.array[2];
	norm=sqrt(a*a+b*b+c*c);
	if (norm!=0)
	{
		a/=norm;
		b/=norm;
		c/=norm;
	}
	d=sqrt(b*b+c*c);
	//if it is not coincide with x_axis
	if (d!=0)
	{	
		matrixArray[0].rotate(X_AXIS, rad2deg(acos(c/d)));
	}
	matrixArray[1].rotate(Y_AXIS, rad2deg(acos(d)));
	matrixArray[2].rotate(Z_AXIS, degree);
	matrixArray[3].rotate(Y_AXIS, -rad2deg(acos(d)));
	if (d!=0)
	{
		matrixArray[4].rotate(X_AXIS, -rad2deg(acos(c/d)));
	}
	for (int i=0; i<3; i++)
	{
		matrixArray[i].display();
	}
	//(matrixArray[0]*matrixArray[1]).display();
	//cout<<"\n";
	*this=matrixArray[0]*matrixArray[1]*matrixArray[2]*matrixArray[3]
		*matrixArray[4];
}


/*
int main()
{
	Matrix translate;
	Matrix rotate;
	Vector vertex(4,5,6), axis(3,2,5);

	rotate.rotate(vertex, axis, 45);
	rotate.display();
	//translate.display();


	return 0;
}
*/
 
 
 
file name: main.cpp
#include <GL/glut.h>
#include "glutObject.h"
#include "glutobjects.h"
#include <stdio.h>
#include <iostream>
#include <cmath>

using namespace std;

//#include "glutObject.h"

//using namespace NICKSPACE;
extern void displayCallback();

int main(int argc, char** argv)
{
	glutInit(&argc, argv);	
	//glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	//GlutObjectSphere sphere;
	//GlutObjectCube cube;
	/*
	GlutObjectCone cone;
	cone.setColor(0, 1, 0);
	cone.setPosition(10, 0, 10);
	cone.setVolume(8, 2, 2);
	cone.setDirection(1, 1, 1);
	
	GlutObjectCylinder cylinder;
	cylinder.setDirection(0, 1, 1);
	cylinder.setPosition(10, 10, 0);
	cylinder.setVolume(4, 2, 1);
	cylinder.setColor(0, 1, 0.5);

	GlutObjectHalfCone halfCone;
	halfCone.setVolume(10, 5, 5);
	halfCone.setDirection(0, 0, 1);
	halfCone.setColor(1,1,0);
	halfCone.setPosition(5, 5, 0);
	GlutObjectSweepTorus o(0.13, 0.125);
	o.setDirection(1, 0, 0);
	o.setPosition(0, 0, 0);
	o.setColor(1, 1, 0);
	o.setVolume(4, 4, 4);
	//cout<<cos(3.1415/2.0)<<endl;
	//sphere.setPosition(0, 0, 0);
	//sphere.setVolume(2, 2, 2);

	
	//cube.setPosition(10, 0, 0);
	//cube.setColor(0,1,0);
	//cube.setVolume(2, 2, 2);
	//cube.setDirection(5, 5,0);
/*
	GlutObject3DAxis axis;

	GlutObjectHalfSphere halfSphere;
	halfSphere.setPosition(0, 2, 2);
	halfSphere.setVolume(1, 1, 1);
	halfSphere.setDirection(0,0,30);
	halfSphere.setClipRatio(0.7);
/*
	GlutObjectCylinder cylinder;
	cylinder.setColor(0.5, 0.5, 0.0);
	//cylinder.setDirection(0, 60, 0);
	cylinder.setVolume(9, 9, 14);
	cylinder.setPosition(0, 0, -60);

	GlutObjectCone cone;
	cone.setColor(0, 1, 0);
	cone.setPosition(0, 1, -2);
	cone.setVolume(2, 2, 1);
	*/
	GlutObjectFace face;
	run();
	
	return 0;
}

				 back.gif (341 bytes)       up.gif (335 bytes)         next.gif (337 bytes)