/* (c) 1999-2000 Tino Schwarze, see COPYING for details */
/**@pkg cObject.cWorld*/
/**
 * class cWorld, providing interface to OpenGL/GLUT
 *
 * #include "cWorld.hh"
 *
 * -lglut
 *
 * -lMesaGL or -lGL
 *
 * -lMesaGLU or -lGLU
 *
 * This is the root of the scene. It's like init(8) but for cObjects.
 *
 * @pkgdoc cObject.cWorld
 */

#ifndef cWorld_hh
#define cWorld_hh

// get STL map (associative array)
#include <map>

// get function declarations for OpenGL and GLUT
#include <GL/gl.h>
#include <GL/glut.h>

#include "cObject.hh"
#include "cVertex.hh"
#include "cMatrix.hh"
#include "cEventConsumer.hh"
#include "common.hh"

/**
 * The world class. The interface to OpenGL/GLUT.
 *
 * Only _one_ cWorld object may be active at once.
 * This object _has_ to be the root of the scene graph.
 */
class cWorld 
:	public cObject,
	public cEventConsumer
{
public:

// LIFECYCLE
	/**
	 * default constructor w/ optional name
	 	* @param name optional name of object
	 	*/
	cWorld (cEventDispatcher *disp, const char *name = NULL);

	/**
	 * copy constructor
	 	*/
	cWorld (const cWorld &);

	/**
	 * destructor
	 	*/
	virtual ~cWorld ();

// OPERATIONS
	/**
	 * initialize world (and OpenGL window using GLUT)
	    * @param argc passed from main
		* @param argv passed from main
	 	*/
	virtual int Init (int *argc, char **argv);

	/**
	 * Initialize world and OpenGL window using GLUT and providing 
	 * several defaults
	 	*
		* 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.
		*
	    * @param argc passed from main
		* @param argv passed 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)
		*
	 	*/
	virtual int Init (
		int *argc,
		char **argv,
		unsigned int glut_display_mode,
		int default_width = -1,
		int default_height = -1,
		int default_pos_x = -1,
		int default_pos_y = -1);

	/**
	 * set new window dimensions
		*
		* @param width new width of window
		* @param height new height of window
		*/
	virtual void ReshapeDisplay (
		int width,
		int height);

	/**
	 * start drawing the whole world (wrapper for Activate())
		*
		* note: this is called by GLUTDisplayCB, there should be no
		* need to call it from somewhere else!
	 	*/
	virtual void Draw ()
	{
		ENTER_OBJECT_METHOD("cWorld::Draw ()");
		Activate();
	}

// MENU OPERATIONS
	/**
	 * make scene graph visible via menu
	 	* @param menu id of menu to add scene graph to
	 	*/
	virtual void SceneGraphToMenu (int menu);

	/**
	 * set current menu where entries are added to
	 	* @param id number of menu to activate
		*/
	virtual void SetWorkingMenu (int id);

	/**
	 * get current menu where entries are added to
	 	* @return id number of active menu
		*/
	virtual int GetWorkingMenu ();

	/**
	 * add menu entry to actual menu
	 	* @param entry text to show as the entry
		* @param event event to trigger if menu is selected
		*
		* @return id of menu entry for later reference
		*/
	virtual int AddMenuEntry (
		const char *entry,
		const cEvent &event);

	/**
	 * add sub menu to actual menu
	 	* @param name text to show as sub menu name
		*
		* @return id of sub menu entry for later reference
		*/
	virtual int AddSubMenu (const char *name);

	/**
	 * attach menu to given mouse button
	 	* @param button GLUT_x_BUTTON (x = LEFT|MIDDLE|RIGHT)
		*/
	virtual void AttachMenu (int button);

// ACCESS
	/**
	 * 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 SetCamera (
		const cVertex &pos, 
		const cVertex &lookat, 
		const cVertex &up);

	/**
	 * 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, if our up vector
		*       degenerates too much.
		*/
	int MoveCameraTo (const cVertex &pos);

	/**
	 * move camera by a given amount without changing LookAt
	 	* @param dif amount to move camera (it is added to camera pos.)
		*
		* @return -1 on error, 0 on success
		*
		* @note Might fail if the up vector degenerates too much.
		*/
	int MoveCameraBy (const cVertex &dif)
	{
		return MoveCameraTo (mCameraPos + dif);
	}

	/**
	 * for "walk" navigation
	 	* @param dif amount to move by
	 	*/
	void MoveCameraAndLookatBy (const cVertex &dif)
	{
		cVertex corr_dif = mCameraMatrix/dif;

		mCameraPos += corr_dif;
		mCameraLookAt += corr_dif;
	}

	/**
	 * for "walk" navigation: look around
	 	* @param x_angle angle to turn around x
		* @param y_angle angle to turn around y
		*/
	void PanCamera (GLfloat x, GLfloat y);

	/**
	 * for "orbit navigation": orbit around look-at
	 	* @param x_angle angle to turn around x
		* @param y_angle angle to turn around y
		*/
	void OrbitCamera (GLfloat x, GLfloat y);

	/**
	 * for "orbit navigation": move orbit center = look-at
	 	* @param dif amount to move look-at
		*/
	void MoveLookAtBy (const cVertex &dif);

	/**
	 * query width of display
	 	* @returns width of viewing window
		*/
	unsigned int GetDisplayWidth ()
	{
		return mDisplayWidth;
	}

	/**
	 * query height of display
	 	* @returns height of viewing window
		*/
	unsigned int GetDisplayHeight ()
	{
		return mDisplayHeight;
	}

	/**
	 * activate pespective projection
	 	* @param fovy field of view angle (degree) in y direction
	 	* @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
		*/
	int SetPerspectiveProjection (
		GLdouble fovy,
		GLdouble near,
		GLdouble far);

	/**
	 * activate orthogonal projection
	 	* @param fovy field of view angle (degree) in y direction
	 	* @param near coordinate for near clipping plane (>0 !)
	 	* @param far coordinate for far clipping plane (>0 !)
		*
		* @return 0 (always succeeds)
		*/
	int SetOrthoProjection (
		GLdouble fovy,
		GLdouble near,
		GLdouble far);

	/**
	 * trigger event bound to given menu id
	 	*
		* this is basically an internal function, but can be called
		* from outside without any risk
		* @param id menu entry id to activate
		* @return -1 if invalid ID was given
		*/
	int ActivateMenuEntry (int id);

	/**
	 * start animation 
		*/
	void EnableAnimation ();

	/** 
	 * set or change animation speed
	 	*
		* @param speed ms per frame (0 - stop animation)
		*/
	void SetAnimationSpeed (int speed);

	/**
	 * stop animation
	 	*/
	void DisableAnimation ();

	/**
	 * event receiver
	 	*/
	virtual int ReceiveEvent (const cEvent &event);

	/**
	 * broadcast animation event, re-register timer callback if
	    * animation still activated
		*
		* Should not be called by user.
		*
		* @param value value to send around with animation event
		* (a frame counter)
	 	*/
	void Animate (int value);

protected:
	/**
	 * set up for drawing
	 	*/
	virtual void Activate ();

	/**
	 * clean up after drawing the world
	 	*/
	virtual void Deactivate ();

	/**
	 * return name of class (for poor RTTI and as fallback for GetName)
	 	*/
	virtual const char *GetDefaultName () const
	{
		return "cWorld";
	}

private:
	/**
	 * internal function for SceneGraphToMenu (recursive!)
	 	*/
	void ChildToMenu (int menu, cObject *o);

	/**
	 * reactivate actual perspective projection
	 * (useful if aspect ratio changes by resizing window)
	 */
	void UpdateProjection ();

// PRIVATE DATA
	static const unsigned int mkDefaultDisplayMode = GLUT_RGB | GLUT_DEPTH;
	static const unsigned int mkDefaultDisplayWidth = 300;
	static const unsigned int mkDefaultDisplayHeight = 300;

	/** definition of camera */
	cVertex mCameraPos, mCameraLookAt, mCameraUpVector;

	/** definition of camera matrix */
	cMatrix mCameraMatrix;

	/** window id for GLUT */
	int mWindowId;

	/** display mode for GLUT (only temporarily stored here) */
	int mGlutDisplayMode;

	/** width of OpenGL display */
	int mDisplayWidth;

	/** height of OpenGL display */
	int mDisplayHeight;

	/** do we want perspective or orthogonal projection? */
	bool mPerspectiveProjection;
	/** field of view in y direction */
	GLdouble mPerspectiveFovY;
	/** near z clipping plane */
	GLdouble mPerspectiveZNear;
	/** far z clipping plane */
	GLdouble mPerspectiveZFar;

	// we provide wrappers for GLUT's menu functions so we are able
	// to keep track of used IDs as we need them to call appropiate
	// callbacks.
	/** menu id of first level menu */
	int mTopMenuId;

	/** id of last created menu entry */
	int mLastMenuEntryId;

	/** a type for our menu-ID-to-cEvent mapping */
	typedef map<int, cStorableEvent> tMenuMap;

	/** the mapping of menu IDs to cEvents */
	tMenuMap mMenuMap;

	/** alter the way polygons are drawn? (glPolygonMode) */
	bool mWireMode;

	/** animation activated? */
	bool mAnimationActive;

	/** animation speed - ms per frame */
	int mAnimationSpeed;

	/** how many frames have been animated yet? */
	int mAnimationCounter;

	/** 
	 * how may milliseconds have passed during animation (rough estimate!)
	 */
	int mAnimationTime;
};

#endif	// ifndef cLight_hh

