/* (c) 1999-2000 Tino Schwarze, see COPYING for details */
/**
 * das Hauptprogramm
 */

// SYSTEM-INCLUDES
#include <stdlib.h>	// for on_exit(3)

// PROJECT-INCLUDES
#include "cWorld.hh"
#include "cLight.hh"
#include "cDirectionLight.hh"
#include "cSpotLight.hh"
#include "cHoledPlane.hh"
#include "cWater.hh"

#include "cKeyEvent.hh"
#include "cWorldControl.hh"
#include "cEventInput.hh"
#include "cIntEvent.hh"

#include "cTextureMaterial.hh"
#include "cEnvTextureMaterial.hh"
#include "events_cWorld.hh"
#include "events_cEventInput.hh"
#include "events_cInteractiveObject.hh"
#include "events_cWater.hh"


const string ccMP_LowQuality = string ("SetLowQuality");
const string ccMP_MedQuality = string ("SetMedQuality");
const string ccMP_HiQuality = string ("SetHiQuality");
const string ccMP_EnvShadeWater = string ("EnvShadeWater");
const string ccMP_SmoothShadeWater = string ("SmoothShadeWater");
const string ccMP_SetPerspectiveProjection = string ("SetPerspectiveProjection");
const string ccMP_SetOrthoProjection = string ("SetOrthoProjection");

/*
 * several tuneable parameters for the scene
 */
// parameters for pool size
const GLfloat GRASS_SIZE = 10.0;
const GLfloat POOL_SIZE_X = 4.0;
const GLfloat POOL_SIZE_Y = 3.0;
const GLfloat POOL_SIZE_Z = 2.0;
// how far to embed the pool into the ground
const GLfloat POOL_DEPTH_Z = 1.5; 

// how many percent of outer pool size will be filled
const GLfloat POOL_IN_PERC_X = 80;
const GLfloat POOL_IN_PERC_Y = 80;
const GLfloat POOL_IN_PERC_Z = 80;

// how transparent is the water
const GLfloat WATER_TRANSP = 0.7;
const GLfloat MAX_WATER_AMP = 0.25; // max. amplitude
const GLfloat WATER_SHININESS = 16;

// size of environment map for water
const int ENVMAP_SIZE = 256;

/*
 * calculates constants - should not be altered
 */
// calculate percentages for grass' hole
const GLfloat GRASS_HOLE_PERC_X = 50.0*POOL_SIZE_X/GRASS_SIZE;
const GLfloat GRASS_HOLE_PERC_Y = 50.0*POOL_SIZE_Y/GRASS_SIZE;

// calculate size of inner pool
const GLfloat POOL_IN_SIZE_X = POOL_IN_PERC_X*POOL_SIZE_X/100.0;
const GLfloat POOL_IN_SIZE_Y = POOL_IN_PERC_Y*POOL_SIZE_Y/100.0;
const GLfloat POOL_IN_SIZE_Z = POOL_IN_PERC_Z*POOL_SIZE_Z/100.0;
const GLfloat POOL_IN_POS_Z = (POOL_SIZE_Z - POOL_DEPTH_Z)-POOL_IN_SIZE_Z;

class cMainProgram
:	public cEventConsumer
{
public:
	/**
	 * default constructor w/ optional name
	 */
	cMainProgram (
		cEventDispatcher *disp,
		const char *name = "cMainProgram");

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

	/**
	 * initialization function (builds scene graph and stuff)
	 */
	int Init (int &argc, char **argv);

	/**
	 * get it going (note: will never return, enters glutMainLoop())
	 	*/
	int Run ();

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

protected:
	/** build the whole scene */
	void InitScene ();

	/** initialize GLUT menu */
	void InitMenu ();

	/** create env map for water surface */
	void CreateEnvTexture (
		cEnvTextureMaterial *env, 
		const cColor &diff_col,
		const cColor &spec_col);

	// cEventDispatcher *mDispatcher; /* inherited from cEventConsumer */
	cWorld *mWorld;
	cWorldControl *mWorldControl;
	cEventInput *mEventInput;

	cLight *mPointLight1;
	cDirectionLight *mDirLight1;
	cSpotLight *mSpotLight1;
	cHoledPlane *mGrassPlane;
	cWater *mWater;

	cTextureMaterial *mGrassTexture; 
	cMaterial *mPoolOutsideMaterial, *mPoolInsideMaterial;
	cMaterial *mWaterMaterial;
	cEnvTextureMaterial *mWaterEnvTexture;

	// field of view
	GLfloat mFovY;
	// front clipping plane can be altered
	GLfloat mClipFront;
	// back clipping plane
	GLfloat mClipBack;
};

/**
 * constructor
 */
cMainProgram::cMainProgram(
		cEventDispatcher *disp,
		const char *name)
/* we're a very special event consumer: we allocate the dispatcher
 * ourself */
:	cEventConsumer (disp)
{
	mWorld = new cWorld (mDispatcher, name);
	mWorldControl = new cWorldControl (mDispatcher);

	// allow changing texture quality
	mDispatcher->SubscribeToEvent (cEvent (ccMP_HiQuality), this);
	mDispatcher->SubscribeToEvent (cEvent (ccMP_MedQuality), this);
	mDispatcher->SubscribeToEvent (cEvent (ccMP_LowQuality), this);

	// we need to trigger a Redisplay for animation
	mDispatcher->SubscribeToEvent (cIntEvent (ccW_Animate), this);

	// change shading style of water
	mDispatcher->SubscribeToEvent (cEvent (ccMP_EnvShadeWater), this);
	mDispatcher->SubscribeToEvent (cEvent (ccMP_SmoothShadeWater), this);

	// we want to check some keys
	mDispatcher->SubscribeToEvent (cKeyEvent (ccEI_KeyPress), this);

	// change perspective
	mDispatcher->SubscribeToEvent (cEvent (ccMP_SetPerspectiveProjection), this);
	mDispatcher->SubscribeToEvent (cEvent (ccMP_SetOrthoProjection), this);

	mFovY = 45;
	mClipFront = 0.5;
	mClipBack = 50;
}

/**
 * destructor
 */
cMainProgram::~cMainProgram ()
{
	delete mWorldControl;
	delete mEventInput;

	// will destroy everything within the scene graph!
	// (apart from materials used by cVisibleObjects)
	delete mWorld;	

	delete mGrassTexture;
}


void cMainProgram::InitScene ()	// :SCENE:
{
	// we'll build several planes which we don't need to reference later, 
	// so we simply save one at a time here until it is completely set up
	cPlane *tmpPlane;

	mDirLight1 = (cDirectionLight *)mWorld->AddChild (
		new cDirectionLight (
			mDispatcher, cVertex (0, 1, 1),
			cADSEColor (
				cColor (0.1, 0.1, 0.1),
				cColor (1.0, 0.2, 0.2),
				cColor (1.0, 0.2, 0.2)),
			cLight::LIGHT_ON,
			"dir light 1"));

	mSpotLight1 = (cSpotLight *)mDirLight1->AddChild (
		new cSpotLight (
			mDispatcher, 
			cVertex (-10, 0, 10), cVertex (1, 1, -1),
			cADSEColor (
				cColor (0.0, 0.0, 0.0),
				cColor (0.8, 0.8, 1.0),
				cColor (0.8, 0.8, 1.0)),
			cLight::LIGHT_ON,
			"spot light 1"));
	mSpotLight1->SetCutOff (20);
	mSpotLight1->SetExponent (12);

	mPointLight1 = (cLight *)mSpotLight1->AddChild (
		new cLight (
			mDispatcher, cVertex (10, 0, 10),
			cADSEColor (
				cColor (0.0, 0.0, 0.0),	// no extra ambient light
//				cColor (0.1, 0.1, 0.1),
				cColor (1.0, 1.0, 1.0),
				cColor (1.0, 1.0, 1.0)
			),
			cLight::LIGHT_ON,
			"point light 1")
		);

	mGrassTexture = new cTextureMaterial (
			"textures/wiese2.jpg",
			false,	// don't use mipmapping
			cADSEColor (
				cColor (0.1, 0.1, 0.1),
				cColor (0.2, 0.5, 0.2),
				cColor (0.8, 1.0, 0.8)),
			16,
			"grass texture");
	
	mGrassPlane = (cHoledPlane *)mPointLight1->AddChild (
		new cHoledPlane (
			mDispatcher,
			GRASS_SIZE, GRASS_SIZE,
			50-GRASS_HOLE_PERC_X, 50+GRASS_HOLE_PERC_X,		// hole from-x, to-x
			50-GRASS_HOLE_PERC_Y, 50+GRASS_HOLE_PERC_Y,		// hole from-y, to-y
			cPlane::PLANE_SOLID,
			8, "grass")
		);

	mGrassPlane->UseMaterial (mGrassTexture);

	//:POOLOUT:
	mPoolOutsideMaterial = (cMaterial *)mGrassPlane->AddChild (
		new cMaterial (
			cADSEColor (
				cColor (0.1, 0.1, 0.2),
				cColor (0.2, 0.2, 0.7),
				cColor (0.9, 0.9, 0.9)
			),
			32,
			"pool outside mat")
		);

	// build outside of pool
	cPlane *PoolBottomOut = (cPlane *)mPoolOutsideMaterial->AddChild (
			new cPlane (
				mDispatcher,
				POOL_SIZE_X, POOL_SIZE_Y,
				cPlane::PLANE_SOLID, 4,
				"pool bottom out")
			);
	PoolBottomOut->MoveBy (cVertex (0.0, 0.0, -POOL_DEPTH_Z));
	PoolBottomOut->RotateBy (180, cVertex (1.0, 0.0, 0.0));


	tmpPlane = (cPlane *)PoolBottomOut->AddChild (
			new cPlane (
				mDispatcher,
				POOL_SIZE_X, POOL_SIZE_Z,
				cPlane::PLANE_SOLID, 4,
				"pool front out")
			);
	tmpPlane->MoveBy (cVertex (0.0, -POOL_SIZE_Y/2, -POOL_SIZE_Z/2));
	tmpPlane->RotateBy (90, cVertex (1.0, 0.0, 0.0));

	tmpPlane = (cPlane *)PoolBottomOut->AddChild (
			new cPlane (
				mDispatcher,
				POOL_SIZE_X, POOL_SIZE_Z,
				cPlane::PLANE_SOLID, 4,
				"pool back out")
			);
	tmpPlane->MoveBy (cVertex (0.0, POOL_SIZE_Y/2, -POOL_SIZE_Z/2));
	tmpPlane->RotateBy (-90, cVertex (1.0, 0.0, 0.0));

	tmpPlane = (cPlane *)PoolBottomOut->AddChild (
			new cPlane (
				mDispatcher,
				POOL_SIZE_Z, POOL_SIZE_Y,
				cPlane::PLANE_SOLID, 4,
				"pool right out")
			);
	tmpPlane->MoveBy (cVertex (POOL_SIZE_X/2, 0.0, -POOL_SIZE_Z/2));
	tmpPlane->RotateBy (90, cVertex (0.0, 1.0, 0.0));

	tmpPlane = (cPlane *)PoolBottomOut->AddChild (
			new cPlane (
				mDispatcher,
				POOL_SIZE_Z, POOL_SIZE_Y,
				cPlane::PLANE_SOLID, 4,
				"pool left out")
			);

	tmpPlane->MoveBy (cVertex (-POOL_SIZE_X/2, 0.0, -POOL_SIZE_Z/2));
	tmpPlane->RotateBy (-90, cVertex (0.0, 1.0, 0.0));

	// build top of pool (connection of outer to inner shape
	{
		cHoledPlane *tmpHPlane;

		tmpHPlane = (cHoledPlane *)PoolBottomOut->AddChild (
			new cHoledPlane (
				mDispatcher,
				POOL_SIZE_X, POOL_SIZE_Y,
				50-POOL_IN_PERC_X/2, 50+POOL_IN_PERC_X/2,
				50-POOL_IN_PERC_Y/2, 50+POOL_IN_PERC_Y/2,
				cPlane::PLANE_SOLID, 4,
				"pool top")
			);
		tmpHPlane->MoveBy (cVertex (0.0, 0.0, -POOL_SIZE_Z));
		// bottom out is rotated by 180 degree to get the normals right
		tmpHPlane->RotateBy (180, cVertex (1.0, 0.0, 0.0));

		// create water surface
		mWater = (cWater *)tmpHPlane->AddChild (
			new cWater (
				mDispatcher,
				POOL_IN_SIZE_X, POOL_IN_SIZE_Y,
				MAX_WATER_AMP,
				16,
				"water surface")
			);
		mWater->MoveBy (cVertex (0.0, 0.0, -MAX_WATER_AMP));

		mWaterMaterial = new cMaterial (
			cADSEColor (
				cColor (0.1, 0.1, 0.3, WATER_TRANSP),
				cColor (0.1, 0.1, 0.9, WATER_TRANSP),
				cColor (1.0, 1.0, 1.0, WATER_TRANSP)
			),
			WATER_SHININESS,
			"water mat");

		mWaterEnvTexture = new cEnvTextureMaterial (
			cADSEColor (
				cColor (0.1, 0.1, 0.3, WATER_TRANSP),
				cColor (0.1, 0.1, 0.9, WATER_TRANSP),
				cColor (1.0, 1.0, 1.0, WATER_TRANSP)
			),
			WATER_SHININESS,
			"water envmap");

		CreateEnvTexture (
			mWaterEnvTexture, 
			cColor (0.1, 0.1, 0.9, WATER_TRANSP),
			cColor (1.0, 1.0, 1.0, WATER_TRANSP));

		// default: use smooth shading
		mWater->UseMaterial (mWaterMaterial);
		//mWater->UseMaterial (mWaterEnvTexture);

		cWater::tsOscillationCenter osc1 = 
			{ -POOL_IN_SIZE_X, -POOL_IN_SIZE_Y, 1, 3 };

		cWater::tsOscillationCenter osc2 = 
			{ POOL_IN_SIZE_X, 0, 0.5, 3 };

		cWater::tsOscillationCenter osc3 = 
			{ -POOL_IN_SIZE_X, POOL_IN_SIZE_Y, 0.3, 7 };

		mWater->AddOscillation (osc1);
		mWater->AddOscillation (osc2);
		mWater->AddOscillation (osc3);
	}

	// :POOLIN:
	mPoolInsideMaterial = (cMaterial *)mGrassPlane->AddChild (
		new cMaterial (
			cADSEColor (
				cColor (0.1, 0.1, 0.3),
				cColor (0.1, 0.1, 0.8),
				cColor (1.0, 1.0, 1.0)
			),
			48,
			"pool inside mat")
		);

	// build inner part of pool
	cPlane *PoolBottomIn = (cPlane *)mPoolInsideMaterial->AddChild (
			new cPlane (
				mDispatcher,
				POOL_IN_SIZE_X, POOL_IN_SIZE_Y,
				cPlane::PLANE_SOLID, 4,
				"pool bottom in")
			);
	PoolBottomIn->MoveBy (cVertex (0.0, 0.0, POOL_IN_POS_Z));

	tmpPlane = (cPlane *)PoolBottomIn->AddChild (
			new cPlane (
				mDispatcher,
				POOL_IN_SIZE_X, POOL_IN_SIZE_Z,
				cPlane::PLANE_SOLID, 4,
				"pool front in")
			);
	tmpPlane->MoveBy (cVertex (0.0, POOL_IN_SIZE_Y/2, POOL_IN_SIZE_Z/2));
	tmpPlane->RotateBy (90, cVertex (1.0, 0.0, 0.0));

	tmpPlane = (cPlane *)PoolBottomIn->AddChild (
			new cPlane (
				mDispatcher,
				POOL_IN_SIZE_X, POOL_IN_SIZE_Z,
				cPlane::PLANE_SOLID, 4,
				"pool back out")
			);
	tmpPlane->MoveBy (cVertex (0.0, -POOL_IN_SIZE_Y/2, POOL_IN_SIZE_Z/2));
	tmpPlane->RotateBy (-90, cVertex (1.0, 0.0, 0.0));

	tmpPlane = (cPlane *)PoolBottomIn->AddChild (
			new cPlane (
				mDispatcher,
				POOL_IN_SIZE_Z, POOL_IN_SIZE_Y,
				cPlane::PLANE_SOLID, 4,
				"pool right out")
			);
	tmpPlane->MoveBy (cVertex (-POOL_IN_SIZE_X/2, 0.0, POOL_IN_SIZE_Z/2));
	tmpPlane->RotateBy (90, cVertex (0.0, 1.0, 0.0));

	tmpPlane = (cPlane *)PoolBottomIn->AddChild (
			new cPlane (
				mDispatcher,
				POOL_IN_SIZE_Z, POOL_IN_SIZE_Y,
				cPlane::PLANE_SOLID, 4,
				"pool left out")
			);

	tmpPlane->MoveBy (cVertex (POOL_IN_SIZE_X/2, 0.0, POOL_IN_SIZE_Z/2));
	tmpPlane->RotateBy (-90, cVertex (0.0, 1.0, 0.0));

}

/**
 * main setup function
 */
int cMainProgram::Init (int &argc, char **argv)	// :INIT:
{
	// build the whole scene
	InitScene ();

	// now call cWorld::Init to initialize all objects in the scene
	mWorld->Init (&argc, argv, GLUT_RGB|GLUT_DEPTH|GLUT_DOUBLE|GLUT_ALPHA);

	mWaterMaterial->Init ();
	mWaterEnvTexture->Init ();

	// now we're ready to receive events
	mEventInput = new cEventInput (mDispatcher);

	// build a nifty menu
	InitMenu ();

	mWorld->SetPerspectiveProjection (mFovY, mClipFront, mClipBack);

	// simulate two keystrokes to activate "global move mode" and
	// "walk mode"
    mDispatcher->SendEvent (cKeyEvent (ccEI_KeyPress, 'G', 0, 0));
    mDispatcher->SendEvent (cKeyEvent (ccEI_KeyPress, 'w', 0, 0));

	// select grass plane by default
	mDispatcher->SendEvent (cAddressEvent (ccIO_SelectObject, (void *)mGrassPlane));

	return 0;
}

/** initialize the menu */
void cMainProgram::InitMenu ()
{
	int topmenu = mWorld->GetWorkingMenu ();

	//int qualitymenu = 
	int qualitymenu = mWorld->AddSubMenu ("visual quality");

	mWorld->AddSubMenu ("shading");

	mWorld->AddMenuEntry (
			"Wire frame",
			cEvent (ccW_WireFrameMode));

	mWorld->AddMenuEntry (
			"Shaded",
			cEvent (ccW_ShadingMode));

	mWorld->SetWorkingMenu (qualitymenu);

	mWorld->AddSubMenu ("texture shading/mapping");

	mWorld->AddMenuEntry (
			"high quality",
			cEvent (ccMP_HiQuality));

	mWorld->AddMenuEntry (
			"medium quality",
			cEvent (ccMP_MedQuality));

	mWorld->AddMenuEntry (
			"low quality",
			cEvent (ccMP_LowQuality));

	mWorld->SetWorkingMenu (qualitymenu);

	mWorld->AddSubMenu ("lighting");

	mWorld->AddMenuEntry (
			"enable LOCAL_VIEWER",
			cEvent (ccW_EnableLocalViewer));

	mWorld->AddMenuEntry (
			"disable LOCAL_VIEWER",
			cEvent (ccW_DisableLocalViewer));

	mWorld->SetWorkingMenu (qualitymenu);

	mWorld->AddSubMenu ("water surface");

	mWorld->AddMenuEntry (
			"env mapping",
			cEvent (ccMP_EnvShadeWater));

	mWorld->AddMenuEntry (
			"smooth shading",
			cEvent (ccMP_SmoothShadeWater));

	mWorld->AddMenuEntry (
			"increase detail",
			cEvent (ccWater_IncreaseDetail));

	mWorld->AddMenuEntry (
			"decrease detail",
			cEvent (ccWater_DecreaseDetail));

	mWorld->SetWorkingMenu (qualitymenu);

	mWorld->AddSubMenu ("misc");

	mWorld->AddMenuEntry (
			"enable Z-Buffer",
			cEvent (ccW_EnableZBuffer));

	mWorld->AddMenuEntry (
			"disable Z-Buffer",
			cEvent (ccW_DisableZBuffer));

	mWorld->AddMenuEntry (
			"perspective projection",
			cEvent (ccMP_SetPerspectiveProjection));

	mWorld->AddMenuEntry (
			"orthogonal projection",
			cEvent (ccMP_SetOrthoProjection));

	mWorld->SetWorkingMenu (topmenu);

	mWorld->AddSubMenu ("animation");

	mWorld->AddMenuEntry (
			"enable",
			cEvent (ccW_StartAnimation));

	mWorld->AddMenuEntry (
			"disable",
			cEvent (ccW_StopAnimation));

	mWorld->AddMenuEntry (
			"faster",
			cEvent (ccW_IncrAnimationSpeed));

	mWorld->AddMenuEntry (
			"slower",
			cEvent (ccW_DecrAnimationSpeed));

	mWorld->SetWorkingMenu (topmenu);

	int scenemenu = mWorld->AddSubMenu ("scene graph");

	mWorld->SceneGraphToMenu (scenemenu);

	mWorld->SetWorkingMenu (topmenu);

	mWorld->AddMenuEntry ("quit", cKeyEvent (ccEI_KeyPress, 27));

	mWorld->AttachMenu (GLUT_RIGHT_BUTTON);
}


int cMainProgram::Run ()
{
	glutMainLoop ();
	return 0;
}


// note: alpha value is taken from diffuse color
void cMainProgram::CreateEnvTexture (
	cEnvTextureMaterial *envtex, 
	const cColor &diff_col,
	const cColor &spec_col)
{
	typedef struct sEnvTexel {
		unsigned char r, g, b, a;
	} tsEnvTexel;

	typedef tsEnvTexel taEnvMap[ENVMAP_SIZE][ENVMAP_SIZE];

	taEnvMap *env_map = new tsEnvTexel[1][ENVMAP_SIZE][ENVMAP_SIZE];

	const GLfloat max_dist = sqrt (2.0*(ENVMAP_SIZE*ENVMAP_SIZE)/4.0);
	const GLfloat scale_to_pi = (GLfloat) (M_PI/2.0)/max_dist;

	const unsigned char alpha = (unsigned char)(255.0*diff_col.GetA ());
	const GLfloat diff_red = diff_col.GetR ();
	const GLfloat diff_green = diff_col.GetG ();
	const GLfloat diff_blue = diff_col.GetB ();
	const GLfloat spec_red = spec_col.GetR ();
	const GLfloat spec_green = spec_col.GetG ();
	const GLfloat spec_blue = spec_col.GetB ();

	for (int y = 0; y < ENVMAP_SIZE; y++)
		for (int x = 0; x < ENVMAP_SIZE; x++) 
		{
			tsEnvTexel *texel = &((*env_map)[y][x]);

			const GLfloat dx = x - ENVMAP_SIZE/2.0;
			const GLfloat dy = y - ENVMAP_SIZE/2.0;
			const GLfloat rad = sqrt (dx*dx + dy*dy);

			const GLfloat cosine = cos ((GLdouble) rad*scale_to_pi);
			const GLfloat cosine_spec = pow (cos ((GLdouble) rad*scale_to_pi), WATER_SHININESS);

			GLfloat new_r = (255.0*(diff_red*cosine + spec_red*cosine_spec))+0.5;
			if (new_r > 255.0) new_r = 255.0;
			GLfloat new_g = (255.0*(diff_green*cosine + spec_green*cosine_spec))+0.5;
			if (new_g > 255.0) new_g = 255.0;
			GLfloat new_b = (255.0*(diff_blue*cosine + spec_blue*cosine_spec))+0.5;
			if (new_b > 255.0) new_b = 255.0;

			texel->r = (unsigned char)new_r;
			texel->g = (unsigned char)new_g;
			texel->b = (unsigned char)new_b;
			texel->a = alpha;
		}
	
	envtex->UseTexture (ENVMAP_SIZE, ENVMAP_SIZE, 4, (unsigned char *)env_map);

	delete env_map;
}


int cMainProgram::ReceiveEvent (const cEvent &event)
{
	const string &en = event.GetName ();
	//const cEvent *ev = &event;

	if (en == ccMP_LowQuality)
	{
		mGrassTexture->SetMipmapping (false);
		mGrassTexture->SetTextureParameters (
				GL_NEAREST, GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST,
				GL_REPEAT, GL_REPEAT, GL_DECAL);

		glutPostRedisplay ();
		return 0;
	}

	if (en == ccMP_MedQuality)
	{
		mGrassTexture->SetMipmapping (false);
		mGrassTexture->SetTextureParameters (
				GL_LINEAR, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR,
				GL_REPEAT, GL_REPEAT, GL_MODULATE);

		glutPostRedisplay ();
		return 0;
	}

	if (en == ccMP_HiQuality)
	{
		mGrassTexture->SetMipmapping (true);
		mGrassTexture->SetTextureParameters (
				GL_LINEAR, GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR,
				GL_REPEAT, GL_REPEAT, GL_MODULATE);

		glutPostRedisplay ();
		return 0;
	}


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

	if (en == ccMP_EnvShadeWater)
	{
		mWater->UseMaterial (mWaterEnvTexture);
		glutPostRedisplay ();
		return 0;
	}

	if (en == ccMP_SmoothShadeWater)
	{
		mWater->UseMaterial (mWaterMaterial);
		glutPostRedisplay ();
		return 0;
	}

	if (en == ccEI_KeyPress)
	{
		const cKeyEvent *kpe = (cKeyEvent *)&event;

		unsigned char key = kpe->GetKey ();
		//int special = kpe->GetSpecialKey ();

		switch (key)
		{
			case 'c':
				if (mClipFront > 0.2)
				{
					mClipFront -= 0.1;
					mWorld->SetPerspectiveProjection (45, mClipFront, 50);
					glutPostRedisplay ();
				}
				return 0;
				break;
			case 'C':
				mClipFront += 0.1;
				mWorld->SetPerspectiveProjection (45, mClipFront, 50);
				glutPostRedisplay ();
				return 0;
				break;
			default:
				return -1;
		}

	}

	if (en == ccMP_SetPerspectiveProjection)
	{
		mWorld->SetPerspectiveProjection (mFovY, mClipFront, mClipBack);
		glutPostRedisplay();
		return 0;
	}

	if (en == ccMP_SetOrthoProjection)
	{
		mWorld->SetOrthoProjection (mFovY, mClipFront, mClipBack);
		glutPostRedisplay();
		return 0;
	}

	return -1;
}

// global varibles
cMainProgram *main_program;
cEventDispatcher *event_dispatcher;

// we're a bit stuck since GLUT does not seem to provide any way to
// return from glutMainLoop()
// so, we have bit of a hack here and need to destroy our objects from
// here, though the C++ compiler _might_ do it for us
void cleanup_function ()
{
	delete main_program;	// let destructor do the work
	delete event_dispatcher;
}


int main (int argc, char **argv)
{
	char *program_name = strrchr (argv[0], '/');

	if (program_name == NULL)
	{
		program_name = argv[0];
	}
	else
	{
		program_name++;
	}

	event_dispatcher = new cEventDispatcher ();

	main_program = new cMainProgram (event_dispatcher, program_name);

	if (main_program->Init (argc, argv) != 0)
	{
		delete event_dispatcher;

		return 1;
	}

	atexit (cleanup_function);

	return main_program->Run ();	// will not return, just make gcc happy
}

