/* (c) 1999-2000 Tino Schwarze, see COPYING for details */
/**
 * class providing interface to OpenGL/GLUT
 *
 * #include "cWorld.hh"
 *
 * -lglut
 * -lMesaGL or -lGL
 * -lMesaGLU or -lGLU
 *
 */

#include <GL/glu.h>
#include <string>	// include STL strings

#include "cWorld.hh"
// we need some constants for events
#include "cInteractiveObject.hh"
#include "cIntEvent.hh"
#include "events_cWorld.hh"
#include "events_cInteractiveObject.hh"

//#if DEBUG
// we might want to output a cVertex or cMatrix
#include "iocVertex.hh"
#include "iocMatrix.hh"
//#endif

/*
:s/^extern const string ccW_\([^;]*\);/const string ccW_\1 = string ("\1");/
*/
const string ccW_DoRedisplay = string("Redisplay");
const string ccW_WireFrameMode = string ("SetWireFrameMode");
const string ccW_ShadingMode = string ("SetShadingMode");
const string ccW_WalkMove = string ("WalkMove");
const string ccW_WalkTurn = string ("WalkTurn");
const string ccW_EnableLocalViewer = string ("EnableLocalViewer");
const string ccW_DisableLocalViewer = string ("DisableLocalViewer");
const string ccW_MoveLookAt = string ("MoveLookAt");
const string ccW_OrbitCamera = string ("OrbitCamera");
const string ccW_EnableZBuffer = string ("EnableZBuffer");
const string ccW_DisableZBuffer = string ("DisableZBuffer");
const string ccW_Animate = string ("Animate");
const string ccW_StartAnimation = string ("StartAnimation");
const string ccW_StopAnimation = string ("StopAnimation");
const string ccW_IncrAnimationSpeed = string ("IncrAnimationSpeed");
const string ccW_DecrAnimationSpeed = string ("DecrAnimationSpeed");



/**
 * As we need to use GLUT's callback functions which are C functions,
 * we cannot use cWorld methods for that purpose. Therefore we need to
 * have a pointer to the world object to be able to call any of it's
 * methods.
 *
 */
static cWorld *gsWorldObject = NULL; // gs == global static

/*
 * Forward declarations of all used CALLBACKs.
 */
// called for displaying (calls cWorld::Draw())
static void GLUTDisplayCB ();	

// called if window was resized
static void GLUTReshapeCB (int width, int height);

/* called if the visibility of the current window changes
static void GLUTVisibilityCB (
	int state	// GLUT_VISIBLE or GLUT_NOT_VISIBLE
	);

// called if the mouse enters or leaves the current window
static void GLUTMouseEnterLeaveCB (
	int state	// GLUT_LEFT or GLUT_ENTERED
	);
*/
// called if a menu entry has been selected
static void GLUTMenuEntryCB (
	int value);	// number of entry

/*
// called if the menu status changes (menu gets visible or invisible)
static void GLUTMenuStatusCB (
	int state, 	// GLUT_MENU_IN_USE or GLUT_MENU_NOT_IN_USE
	int x, 		// mouse position
	int y);

// called if there is nothing else to do (no events)
static void GLUTIdleCB ();
*/

// called if a specified number of milliseconds has passed
static void GLUTTimerCB (
	int value	// number of expired timer
	);


/**
 * default constructor w/ optional name
 	* @param disp cEventDispatcher to receive events from
 	* @param name optional name of world object
	*/
cWorld::cWorld (
	cEventDispatcher *disp,
	const char *name)
:	cObject (name),
	cEventConsumer (disp),
	mGlutDisplayMode (mkDefaultDisplayMode),
	mDisplayWidth (mkDefaultDisplayWidth),
	mDisplayHeight (mkDefaultDisplayHeight),
	mAnimationSpeed (100)
{
#if DEBUG
	if (gsWorldObject != NULL)
	{
		cerr << "Interal error: There may be only one cWorld instance at a time!" << endl;
		exit (1);
	}
#endif

	gsWorldObject = this;

	ENTER_OBJECT_METHOD("cWorld::cWorld (const char *)");

	// set up default camera
	SetCamera (
		cVertex (0.0, 0.0, 10.0),
		cVertex (0.0, 0.0, 0.0),
		cVertex (0.0, 1.0, 0.0)
		);

}

/**
 * copy constructor
 	*/
cWorld::cWorld (const cWorld &w)
:	cEventConsumer (w)
{
#if DEBUG
	cerr << "Error: cWorld::cWorld (const cWorld &) called." << endl;
	cerr << "cWorld may not be instantiated more than once!" << endl;
	exit (1);
#endif
}


/**
 * destructor
 	*/
cWorld::~cWorld ()
{
	ENTER_OBJECT_METHOD("cWorld::~cWorld ()");

	// destroy GLUT window if we've got one
	if (mWindowId > 0)
		glutDestroyWindow (mWindowId);

	gsWorldObject = NULL;
	// ~cEventConsumer will get rid of all subscriptions
}


/**
 * initialize OpenGL window using GLUT
 	*/
int cWorld::Init (int *argc, char **argv)
{
	ENTER_OBJECT_METHOD("cWorld::Init (argc, argv)");

	glutInitWindowSize (mDisplayWidth, mDisplayHeight);

	glutInit (argc, argv);

	glutInitDisplayMode (mGlutDisplayMode);

	if (mName != NULL)
	{
		mWindowId = glutCreateWindow (GetName ());
	}
	else
	{
		mWindowId = glutCreateWindow ("cWorld");
	}

	// check for error (window ids start at 1)
	if (mWindowId < 1)
		return -1;

	// register redisplay callback
	glutDisplayFunc (GLUTDisplayCB);
	// register window resizing callback
	glutReshapeFunc (GLUTReshapeCB);

	// register menu handler
	mTopMenuId = glutCreateMenu (GLUTMenuEntryCB);

	// ease storing/retrieving of pixel data
	glPixelStorei (GL_PACK_ALIGNMENT, 1);
	glPixelStorei (GL_UNPACK_ALIGNMENT, 1);

	if ((mGlutDisplayMode & GLUT_DEPTH) != 0)
	{
		glEnable (GL_DEPTH_TEST);
	}

	if ((mGlutDisplayMode & GLUT_ALPHA) != 0)
	{
		glEnable (GL_BLEND);
		glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	}

	mDispatcher->SubscribeToEvent (cEvent (ccW_DoRedisplay), this);
	mDispatcher->SubscribeToEvent (cEvent (ccW_WireFrameMode), this);
	mDispatcher->SubscribeToEvent (cEvent (ccW_ShadingMode), this);
	mDispatcher->SubscribeToEvent (cVertexEvent (ccW_WalkMove), this);
	mDispatcher->SubscribeToEvent (cVertexEvent (ccW_WalkTurn), this);
	mDispatcher->SubscribeToEvent (cEvent (ccW_EnableLocalViewer), this);
	mDispatcher->SubscribeToEvent (cEvent (ccW_DisableLocalViewer), this);
	mDispatcher->SubscribeToEvent (cEvent (ccW_MoveLookAt), this);
	mDispatcher->SubscribeToEvent (cEvent (ccW_OrbitCamera), this);
	mDispatcher->SubscribeToEvent (cEvent (ccW_EnableZBuffer), this);
	mDispatcher->SubscribeToEvent (cEvent (ccW_DisableZBuffer), this);
	mDispatcher->SubscribeToEvent (cEvent (ccW_StartAnimation), this);
	mDispatcher->SubscribeToEvent (cEvent (ccW_StopAnimation), this);
	mDispatcher->SubscribeToEvent (cEvent (ccW_IncrAnimationSpeed), this);
	mDispatcher->SubscribeToEvent (cEvent (ccW_DecrAnimationSpeed), this);

	// call inherited Init to initialize all siblings
	return (cObject::Init ());
}


/**
 * initialize OpenGL window using glut and providing several defaults
 	* @param argc passed over from main
	* @param argv passed over from main
    * @param glut_display_mod use GLUT_xxx constants ORed together
    *        to describe the display type you need
    *        (e.g. GLUT_RGB|GLUT_DEPTH|GLUT_DOUBLE)
    *        default: GLUT_RGB | GLUT_DEPTH
    * @param default_width default width (-1 = don't care)
    * @param default_height default height (-1 = don't care)
    * @param default_pos_x default x position of window (-1 = don't care)
    * @param default_pos_y default y position of window (-1 = don't care)
    *
    * If you provide one of default_width or default_height you have to
    * provide the "opposite" as well. The same rule applies to
    * default_pos_x and default_pos_y.
    */
int cWorld::Init (
	int *argc, 
	char **argv,
    unsigned int glut_display_mode,
    int default_width,
    int default_height,
    int default_pos_x,
    int default_pos_y)
{
	mGlutDisplayMode = glut_display_mode;

	if ((default_width != -1) && (default_height != -1))
	{
		mDisplayWidth = default_width;
		mDisplayHeight = default_height;
	}

	if ((default_pos_x != -1) && (default_pos_y != -1))
	{
		glutInitWindowPosition (default_pos_x, default_pos_y);
	}

	return (Init (argc, argv));
}


/**
 * make scene graph visible via menu
 	* @param menu id of menu to add scene graph to
	*/
void cWorld::SceneGraphToMenu (int menu)
{
	int oldmenuid = GetWorkingMenu ();

	ChildToMenu (menu, this);

	SetWorkingMenu (oldmenuid);
}

// function implemented here for easier editing
/**
 * internal function for SceneGraphToMenu (recursive!)
 	* @param menu id of menu to add submenu to
	* @param o const reference to cObject - hierarchy to add
	*
	* @return id of submenu
	*/
void cWorld::ChildToMenu (int menu, cObject *o)
{
	ENTER_OBJECT_METHOD("cWorld::ChildToMenu (int, cObject *)");

	const string entry_name = 
		(o->GetName () == NULL ? "a cObject instance" : o->GetName ());

	// add menu entry for object plus activation event note: this is relatively
	// secure since only an cInteractiveObject is able to receive this event
	// (it needs to have subscribed for it!)
	AddMenuEntry (entry_name.c_str(), 
		cAddressEvent (ccIO_SelectObject, (void *)o));

	// does the cObject have any siblings?
	if (o->HasChilds ())
	{ 
		// yes, it has childs, so add a submenu
#if 0
		static const string child_suffix("'s childs");

		int newentry = AddSubMenu (string (entry_name + child_suffix).c_str());
#else
		int newentry = AddSubMenu ("- childs");
#endif

		for (childs_t::const_iterator iterator = o->GetChildsBeginIterator ();
			iterator != o->GetChildsEndIterator ();
			++iterator) // that's the way STL implements for_each
		{
			ChildToMenu (newentry, (*iterator));

			// the child might have changed the active menu!
			SetWorkingMenu (newentry);
		}

	}

}


/**
 * set current menu where entries are added to
 	* @param id number of menu to activate
	*/
void cWorld::SetWorkingMenu (int id)
{
#if DEBUG
	cerr << "made menu # " << id << " the active menu" << endl;
#endif
	glutSetMenu (id);
}


/**
 * get current menu where entries are added to
 	* @return id number of active menu
	*/
int cWorld::GetWorkingMenu ()
{
	return glutGetMenu ();
}


/**
 * add menu entry to actual menu
 	* @param entry text to show as the entry
	*
	* @return id of menu entry for later reference
	*/
int cWorld::AddMenuEntry (
	const char *entry,
	const cEvent &event)
{
	// this is easy - we do not keep track of the menu
	// structure ourself
	mLastMenuEntryId++;

#if DEBUG
	cerr << "adding menu entry \"" << entry << "\", id = " << mLastMenuEntryId << ", current menu is # " << GetWorkingMenu () << endl;
#endif

	mMenuMap.insert (
		tMenuMap::value_type (mLastMenuEntryId, cStorableEvent (event)));

	glutAddMenuEntry (entry, mLastMenuEntryId);
	
	return mLastMenuEntryId;
}


/**
 * add sub menu to actual menu and makes it active
 	* @param name text to show as sub menu name
	*
	* @return id of sub menu entry for later reference
	*/
int cWorld::AddSubMenu (const char *name)
{
	int oldmenu = GetWorkingMenu ();

	int newsubid = glutCreateMenu (GLUTMenuEntryCB);

#if DEBUG
	cerr << "created sub menu # " << newsubid << endl;
#endif

	SetWorkingMenu (oldmenu);	// add entry to old menu!

	glutAddSubMenu (name, newsubid);

#if DEBUG
	cerr << "added sub menu \"" << name << "\". Current menu is #" << GetWorkingMenu () << endl;
#endif

	// make the new menu the active menu
	SetWorkingMenu (newsubid);

	return newsubid;
}


/**
 * attach menu to given mouse button
 	* @param button GLUT_x_BUTTON (x = LEFT|MIDDLE|RIGHT)
	*/
void cWorld::AttachMenu (int button)
{
	SetWorkingMenu (mTopMenuId);
	glutAttachMenu (button);
}

/**
 * set up camera
 	* @param pos camera position
	* @param lookat point the camera looks at
	* @param up the direction which is "up" from the camera's point of view
	*
	* @return -1 on error, 0 on success
	*
	* @note (lookat-pos,up) have to be linearly independent vectors!
	*/
int cWorld::SetCamera (
		const cVertex &pos,
		const cVertex &lookat,
		const cVertex &up)
{
	// check for linear independence
	if (! FLOATS_EQUAL ((pos - lookat) * up, 0.0))
	{
		return -1;
	}

	mCameraPos = pos;
	mCameraLookAt = lookat;
	mCameraUpVector = up;

	return 0;
}

/**
 * move camera to a give point without changing LookAt, but
 * probably with changing UpVector
 	* @param pos position to move camera to
	*
	* @return -1 on error, 0 on success
	*
	* @note This call might actually fail!
	*/
int cWorld::MoveCameraTo (const cVertex &pos)
{
// TODO: THIS IS STILL UNTESTED.
	const cVertex cam_dist = (mCameraLookAt - pos);

	// we need to correct the up vector, so we first build a "side"
	// vector ...
	cVertex side_vector = 
		cam_dist.CrossProd (mCameraUpVector);

	// ... and take the side vector to calculate the new up vector
	cVertex new_up = 
		cam_dist.CrossProd (side_vector);
		
	// Check for anomalies...
	if (FLOATS_EQUAL (cam_dist * new_up, 0.0))
	{
		return -1;
	}

	mCameraPos = pos;
	mCameraUpVector = new_up;

	return 0;
}


/**
 * for "walk" navigation: look around
 	*/
void cWorld::PanCamera (GLfloat x, GLfloat y)
{
#if 0
	cVertex side_v = (mCameraUpVector.CrossProd (mCameraPos - mCameraLookAt)).Normalize();

	// calculate complete rotation
	cQuaternion full_rot = 
		cQuaternion (x, side_v)
		* cQuaternion (y, mCameraUpVector);

	// rotate up vector
	mCameraUpVector = full_rot * mCameraUpVector;

	cerr << "new up-vector is: " << mCameraUpVector;

	// rotate look-at around camera position
	mCameraLookAt = mCameraPos + full_rot * (mCameraLookAt - mCameraPos);

	cerr << "new look-at is: " << mCameraLookAt;
#else
	cVertex side_v = (mCameraUpVector.CrossProd (mCameraLookAt - mCameraPos)).Normalize();

	// calculate complete rotation
	cQuaternion full_rot = 
		cQuaternion (x, side_v)
		* cQuaternion (y, mCameraUpVector);

	// rotate up vector
	mCameraUpVector = full_rot * mCameraUpVector;

	// rotate look-at around camera position
	mCameraLookAt = mCameraPos + full_rot * (mCameraLookAt - mCameraPos);

	//cerr << "new up-vector is: " << mCameraUpVector;
	//cerr << "new look-at is: " << mCameraLookAt;
#endif
}


/**
 * for "orbit" navigation
 */
void cWorld::MoveLookAtBy (
	const cVertex &dif)
{
	mCameraLookAt += mCameraMatrix/dif;

	cVertex look_dif = (mCameraPos - mCameraLookAt);

	mCameraUpVector = (look_dif.CrossProd (mCameraUpVector)).CrossProd (look_dif).Normalize();
}


/**
 * for "orbit" navigation
 */
void cWorld::OrbitCamera (
	GLfloat x,
	GLfloat y)
{
	cVertex side_v = (mCameraUpVector.CrossProd (mCameraPos - mCameraLookAt)).Normalize();

	// calculate complete rotation
	cQuaternion full_rot = 
		cQuaternion (x, side_v)
		* cQuaternion (y, mCameraUpVector);

	// rotate up vector
	mCameraUpVector = full_rot * mCameraUpVector;

	// rotate look-at around camera position
	mCameraPos = mCameraLookAt + full_rot * (mCameraPos - mCameraLookAt);

	//cerr << "new up-vector is: " << mCameraUpVector;
	//cerr << "new look-at is: " << mCameraLookAt;
}

/**
 * activate pespective projection
 	* @param fovy field of view angle (degree) in y direction
 	* @param aspect ration x/y
 	* @param near coordinate for near clipping plane (>0 !)
 	* @param far coordinate for far clipping plane (>0 !)
	*
	* @return -1 on error (invalid values), 0 on success
	*
	* (left,bottom,-near) and (right,top,-near) are the points of
	* the near clipping plane which are mapped to the lower left and
	* upper right corner of the viewing window.
	*/
int cWorld::SetPerspectiveProjection (
	GLdouble fovy,
	GLdouble near,
	GLdouble far)
{
	// check for correctness
	if (! ((near > 0) && (far > 0)))
	{
		return -1;
	}

	mPerspectiveFovY = fovy;
	mPerspectiveZNear = near;
	mPerspectiveZFar = far;
	mPerspectiveProjection = true;

	UpdateProjection ();

	return 0;
}


int cWorld::SetOrthoProjection (
	GLdouble fovy,
	GLdouble near,
	GLdouble far)
{
	mPerspectiveFovY = fovy;
	mPerspectiveZNear = near;
	mPerspectiveZFar = far;
	mPerspectiveProjection = false;

	UpdateProjection ();

	// will not fail
	return 0;
}


/** internal function: (re)activate current perspective projection */
void cWorld::UpdateProjection ()
{
	glMatrixMode (GL_PROJECTION);
	glLoadIdentity ();

	GLdouble aspect = mDisplayWidth / mDisplayHeight;

	if (mPerspectiveProjection)
	{
		gluPerspective (
			mPerspectiveFovY, aspect, mPerspectiveZNear, mPerspectiveZFar);
	}
	else
	{
		// stolen from gluPerspective...
		GLdouble xsize, ysize;

		ysize = mPerspectiveZNear * tan (mPerspectiveFovY * M_PI / 360.0);
		xsize = ysize * aspect;

		glOrtho (-xsize, xsize, -ysize, ysize, mPerspectiveZNear, mPerspectiveZFar);
	}

	glMatrixMode (GL_MODELVIEW);
}


/** 
 * trigger event bound to menu entry id 
 */
int cWorld::ActivateMenuEntry (int id)
{

	// do we have an event to send around for this menu entry?
	if (mMenuMap.count (id) != 1)
	{
		return -1;	// nope... :-(
	}
	else
	{
		tMenuMap::iterator it = mMenuMap.find (id);

		// conversion from cStorableEvent to cEvent is implicit
		// (cStorableEvent has an appropiate operator)
		mDispatcher->SendEvent ((*it).second);

		return 0;
	}

}


/** enable animation/set speed */
void cWorld::EnableAnimation ()
{

	if ((mAnimationSpeed > 0) && (! mAnimationActive))
	{
		glutTimerFunc (mAnimationSpeed, GLUTTimerCB, mAnimationCounter);
		mAnimationActive = true;
	}

}

void cWorld::SetAnimationSpeed (
	int speed)
{

	if (speed > 0)
	{
		mAnimationSpeed = speed;
	}

}

void cWorld::DisableAnimation ()
{
	mAnimationActive = false;
}

void cWorld::Animate (
	int value)
{
	// basically not neccessary (GLUT doesn't do asynchronuous timers)
	static bool overrun = false;

	if (mAnimationActive)	// re-register timer callback if neccessary
	{
		glutTimerFunc (mAnimationSpeed, GLUTTimerCB, value + 1);
	}

	// semaphore like thing... though we're not multithreaded! ;-)
	if (overrun)	
	{
		cerr << "animation overrun" << endl;
	}
	else
	{
		overrun = true;

		mAnimationTime += mAnimationSpeed*(value - mAnimationCounter);
		mAnimationCounter = value;

		// send global animation event
		mDispatcher->SendEvent (cIntEvent (ccW_Animate, mAnimationTime), true);

		overrun = false;
	}

}

/**
 * event receiver
 	*/
int cWorld::ReceiveEvent (
	const cEvent &event)
{
	const string &en = event.GetName ();
	// ease casting to different event types
	//const cEvent *ev = &event;

	if (en == ccW_DoRedisplay)
	{
		glutPostRedisplay();
		return 0;
	}

	if (en == ccW_WireFrameMode)
	{
		glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
		glutPostRedisplay();
		return 0;
	}

	if (en == ccW_ShadingMode)
	{
		glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
		glutPostRedisplay();
		return 0;
	}

	if (en == ccW_WalkMove)
	{
		MoveCameraAndLookatBy (((cVertexEvent *)&event)->GetVertex ());
		glutPostRedisplay ();
		return 0;
	}

	if (en == ccW_WalkTurn)
	{
		const cVertex v = ((cVertexEvent *)&event)->GetVertex ();

		PanCamera (v.GetX(), v.GetY());

		glutPostRedisplay ();
		return 0;
	}

	if (en == ccW_EnableLocalViewer)
	{
		glLightModeli (GL_LIGHT_MODEL_LOCAL_VIEWER, 1);
		glutPostRedisplay ();
		return 0;
	}

	if (en == ccW_DisableLocalViewer)
	{
		glLightModeli (GL_LIGHT_MODEL_LOCAL_VIEWER, 0);
		glutPostRedisplay ();
		return 0;
	}

	if (en == ccW_MoveLookAt)
	{
		MoveLookAtBy (((cVertexEvent *)&event)->GetVertex ());
		glutPostRedisplay ();
		return 0;
	}

	if (en == ccW_OrbitCamera)
	{
		const cVertex v = ((cVertexEvent *)&event)->GetVertex ();

		OrbitCamera (v.GetX(), v.GetY());
		glutPostRedisplay ();
		return 0;
	}

	if (en == ccW_EnableZBuffer)
	{
		glEnable (GL_DEPTH_TEST);
		glutPostRedisplay ();
		return 0;
	}

	if (en == ccW_DisableZBuffer)
	{
		glDisable (GL_DEPTH_TEST);
		glutPostRedisplay ();
		return 0;
	}

	if (en == ccW_StartAnimation)
	{
		EnableAnimation ();
		return 0;
	}

	if (en == ccW_StopAnimation)
	{
		DisableAnimation ();
		return 0;
	}

	if (en == ccW_IncrAnimationSpeed)
	{
		const int newspeed = (int) (mAnimationSpeed/1.1);

		if (newspeed > 0)
		{
			SetAnimationSpeed (newspeed);
		}

		return 0;
	}

	if (en == ccW_DecrAnimationSpeed)
	{
		const int newspeed = (int) (mAnimationSpeed*1.1);

		SetAnimationSpeed (newspeed);

		return 0;
	}

	return -1;
}

/**
 * set up for drawing (perform camera transformation)
 	*/
void cWorld::Activate ()
{
	ENTER_OBJECT_METHOD ("cWorld::SetUpForDrawing ()");

    glClear (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

	// note: there is no neccessity to save the current matrix
	// we replace it anyway and there has not to be any matrix
	// active (we're root, we're god... at least in terms of
	// the scene graph...)
	glMatrixMode (GL_MODELVIEW);

	glLoadIdentity ();
	// I got hopelessly fucked up with my own camera code.
	// Then I used gluLookAt and it worked instantly! Phew.
	gluLookAt (
		mCameraPos.GetX(), mCameraPos.GetY(), mCameraPos.GetZ(),
		mCameraLookAt.GetX(), mCameraLookAt.GetY(), mCameraLookAt.GetZ(),
		mCameraUpVector.GetX(), mCameraUpVector.GetY(), mCameraUpVector.GetZ());

	glGetFloatv (GL_MODELVIEW_MATRIX, mCameraMatrix);

#if 0	// display look-at for debugging purposes
	glPushMatrix ();
	glTranslatef (mCameraLookAt.GetX(), mCameraLookAt.GetY(), mCameraLookAt.GetZ());
	glColor3f (0.0, 1.0, 0.0);
	glutSolidSphere (1.0, 8, 8);
	glPopMatrix ();
#endif

	// go on, activate every other object in the scene
	// (also Deactivate()s)
	cObject::Activate ();
}


/**
 * clean up after drawing the world
 	*/
void cWorld::Deactivate ()
{
	ENTER_OBJECT_METHOD ("cWorld::CleanUpAfterDrawing ()");

	// activate double buffering (does nothing if disabled)
	glutSwapBuffers ();

	cObject::Deactivate ();
}


// called by GLUTReshapeCB but may also be called by the user
void cWorld::ReshapeDisplay (int width, int height)
{
#if 0
	cerr << "cWorld::ReshapeDisplay (): old " 
		<< mDisplayWidth << "x" << mDisplayHeight << ", new "
		<< width << "x" << height << endl;
#endif

	// prevent superfluous work
	if ((width != mDisplayWidth) || (height != mDisplayHeight))
	{
		mDisplayWidth = width;
		mDisplayHeight = height;

		// tell OpenGL the new size
		glViewport (0, 0, width, height);

		//cerr << "Updating projection..." << endl;
		// fit projection to new aspect ratio
		UpdateProjection ();
	}

}

// called for displaying (calls cWorld::Draw())
static void GLUTDisplayCB ()
{
	gsWorldObject->Draw ();
}

// called if window was resized
static void GLUTReshapeCB (int width, int height)
{
	gsWorldObject->ReshapeDisplay (width, height);
}

// called if a menu entry has been selected
static void GLUTMenuEntryCB (int value)
{
	// TODO: to be implemented
#if DEBUG
	cerr << "Menu entry #" << value << " has been selected." << endl;
#endif
	gsWorldObject->ActivateMenuEntry (value);
}

/*
// called if the menu status changes (menu gets visible or invisible)
static void GLUTMenuStatusCB (
	int state, 	// GLUT_MENU_IN_USE or GLUT_MENU_NOT_IN_USE
	int x, 		// mouse position
	int y)
{}

// called if there is nothing else to do (no events)
static void GLUTIdleCB ()
{}
*/
// called if a specified number of milliseconds has passed
static void GLUTTimerCB (
	int value	// number of expired timer
	)
{
	// will re-register the timer callback if needed
	gsWorldObject->Animate (value);
}
