/* (c) 1999-2000 Tino Schwarze, see COPYING for details */
/**
 * implementation of class cWater
 *
 * -lglut
	*/

// get glut library functions
#include <GL/glut.h>
// get INT_MAX
#include <limits.h>

#include "cWater.hh"
#include "events_cWorld.hh"	// animation event
#include "events_cWater.hh"

#include "common.hh"

/*
:s/^extern const string ccWater_\([^;]*\);/const string ccWater_\1 = string ("\1");/
*/
const string ccWater_DecreaseDetail = string ("DecreaseDetail");
const string ccWater_IncreaseDetail = string ("IncreaseDetail");


/**
 * default constructor w/ optional object name
 	* @param name name of object (optional)
	*/
cWater::cWater (
	cEventDispatcher *disp,
	const char *name)
: 	cInteractiveObject (disp, name),
  	mDisplayList (0),
	mSizeX (1.0),
	mSizeY (1.0),
	mMaxAmp (1.0),
	mResolution(8),
	mTexFromX (0.0), mTexToX (1.0),
	mTexFromY (0.0), mTexToY (1.0)
{
	ENTER_OBJECT_METHOD("cWater::cWater (const char *)");
}


/**
 * constructor w/ optional size and object name
 	* @param size_x x size of plane (default: 1.0)
 	* @param size_y y size of plane (default: 1.0)
 	* @param name name of object (optional)
	*/
cWater::cWater (
	cEventDispatcher *disp,
	GLfloat size_x, 
	GLfloat size_y, 
	const char *name)
:	cInteractiveObject (disp, name),
	mDisplayList (0),
	mSizeX (size_x),
	mSizeY (size_y),
	mMaxAmp (1.0),
	mResolution (8),
	mTexFromX (0.0), mTexToX (1.0),
	mTexFromY (0.0), mTexToY (1.0)
{
	ENTER_OBJECT_METHOD("cWater::cWater (GLfloat, const char *)");
}


/**
 * constructor w/ initialization of size, solid and name
 	* @param size_x x size of plane (default: 1.0)
 	* @param size_y y size of plane (default: 1.0)
	* @param max_amp maximum amplitude
	* @param name name of object (optional)
 	*/
cWater::cWater (
	cEventDispatcher *disp,
	GLfloat size_x, 
	GLfloat size_y, 
	GLfloat max_amp,
	const char *name)
: 	cInteractiveObject (disp, name), 
  	mDisplayList(0),
	mSizeX (size_x),
	mSizeY (size_y),
	mMaxAmp (max_amp),
  	mResolution (8),
	mTexFromX (0.0), mTexToX (1.0),
	mTexFromY (0.0), mTexToY (1.0)
{
	ENTER_OBJECT_METHOD ("cWater::cWater (GLfloat size, GLboolean solid)");
	// this is enough (mDisplayList is aquired during Init()
}

/**
 * constructor w/ initialization of size, solid and name
 	* @param size_x x size of plane (default: 1.0)
 	* @param size_y y size of plane (default: 1.0)
	* @param max_amp maximum amplitude
	* @param resolution subdivisions per side
	* @param name name of object (optional)
 	*/
cWater::cWater (
	cEventDispatcher *disp,
	GLfloat size_x, 
	GLfloat size_y, 
	GLfloat max_amp,
	int resolution,
	const char *name)
: 	cInteractiveObject (disp, name), 
  	mDisplayList(0),
	mSizeX (size_x),
	mSizeY (size_y),
	mMaxAmp (max_amp),
  	mResolution (resolution),
	mTexFromX (0.0), mTexToX (1.0),
	mTexFromY (0.0), mTexToY (1.0)
{
	ENTER_OBJECT_METHOD ("cWater::cWater (GLfloat size, GLboolean solid)");
	// this is enough (mDisplayList is aquired during Init()
}
/**
 * destructor
 	*/
cWater::~cWater ()
{
	ENTER_OBJECT_METHOD ("cWater::~cWater ()");
	if (mDisplayList != 0)
		glDeleteLists (mDisplayList, 1);

	mDispatcher->UnsubscribeFromEvent (cEvent (ccWater_IncreaseDetail), this);
	mDispatcher->UnsubscribeFromEvent (cEvent (ccWater_DecreaseDetail), this);

}

/**
 * initialization function (aquires display list and compiles it)
 	*/
int cWater::Init ()
{
	ENTER_OBJECT_METHOD ("cWater::Init ()");

	// have at least four quads
	if (mResolution < 2)
		mResolution = 2;

	// have at least one oscillation
	if (mOscillationNo < 1)
	{
		const tsOscillationCenter def_osc = { 0, 0, 1, 1 };

		AddOscillation (def_osc);
	}

	// generate one display list
	mDisplayList = glGenLists (1);

	if (mDisplayList == 0)
		return -1;

	// now fill some data into the list
	CalculateWater ();

	// we want to receive animation events
	mDispatcher->SubscribeToEvent (cEvent (ccW_Animate), this);

	// detail modifications are global... XXX this is a hack
	// detail handling belongs to cVisibleObject and cInteractiveObject!
	mDispatcher->SubscribeToEvent (cEvent (ccWater_IncreaseDetail), this);
	mDispatcher->SubscribeToEvent (cEvent (ccWater_DecreaseDetail), this);


	// call inherited Init (cumpulsory!) after we're set up
	cInteractiveObject::Init ();

	return 0;	// that's all folks - success.
}

void cWater::SetTextureDomain (
	GLfloat x_min, GLfloat x_max,
	GLfloat y_min, GLfloat y_max)
{
	mTexFromX = x_min;
	mTexToX = x_max;
	mTexFromY = y_min;
	mTexToY = y_max;
}


void cWater::AddOscillation (
	const tsOscillationCenter &osc)
{
	mOscillationNo++;

	// this is realloc() by hand...
	tsOscillationCenter *new_osc = new tsOscillationCenter[mOscillationNo];

	if (mOscillationNo > 1)
	{
		// copy old to new oscillations
		memcpy ((void *)new_osc, (void *)mOscillations, (mOscillationNo-1)*sizeof (tsOscillationCenter));

		delete mOscillations;
	}

	new_osc[mOscillationNo-1] = osc;

	mOscillations = new_osc;

	GLfloat amp_sum = 0;

	for (int i = 0; i < mOscillationNo; i++)
	{
		amp_sum += mOscillations[i].amplitude;
	}

	// we need to take care not to exceed mMaxAmp!
	mAmpScale = mMaxAmp/amp_sum;

	mRecalculationNeeded = true;
}


int cWater::ReceiveEvent (const cEvent &event)
{
    const string &en = event.GetName ();

	if (en == ccW_Animate)
	{
		mRecalculationNeeded = true;
		mTimeStamp = ((cIntEvent *)&event)->GetValue ()/1000.0;
		return 0;
	}

	if (en == ccWater_DecreaseDetail)
	{
		if (mResolution >= 2)
		{
			mRecalculationNeeded = true;
			mResolution >>= 1;
		}
		return 0;
	}

	if (en == ccWater_IncreaseDetail)
	{
		if ((mResolution < INT_MAX) && ((mResolution << 1) > mResolution))
		{
			mRecalculationNeeded = true;
			mResolution <<= 1;
		}

		return 0;
	}

	// pass through all other events
	return (cInteractiveObject::ReceiveEvent (event));
}


void cWater::DrawThisObject ()
{
#if 0
	GLint mOldSMode, mOldTMode;
	glGetTexGeniv (GL_S, GL_TEXTURE_GEN_MODE, &mOldSMode);
	glGetTexGeniv (GL_T, GL_TEXTURE_GEN_MODE, &mOldTMode);

	cerr << hex << "old-s: " << mOldSMode << ", old-t: " << mOldTMode;

	glGetTexEnviv (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &mOldSMode);
	cerr << ", old-env: " << mOldSMode << dec << endl;
#endif

	ENTER_OBJECT_METHOD ("cWater::DrawThisObject ()");

	if (mRecalculationNeeded)
	{
		CalculateWater ();
	}

	// now actually draw everything which was calculated earlier
	glCallList (mDisplayList);
}

/**
 * create solid plane
 	*/
void cWater::CalculateWater ()
{
	// we need to update the display list
	glNewList (mDisplayList, GL_COMPILE);

	const GLfloat step_x = (mSizeX/(mResolution));
	const GLfloat step_y = (mSizeY/(mResolution));
	const GLfloat step_tx = (mTexToX - mTexFromX)/(mResolution);
	const GLfloat step_ty = (mTexToY - mTexFromY)/(mResolution);

	const GLfloat start_x = -mSizeX/2.0;

//	typedef cVertex tVertex_row[];
	typedef cVertex *pVertex_row;

	// we need to cache four rows of coordinates since we want to
	// calculate normal vectors (calculated vectors are in row
	// 1 and 2; rows 0 and 3 are used for normal calculation only)
	// avoid copying whole rows around
	pVertex_row row_cache_ptr[4];

	for (int i = 0; i < 4; i++)
	{
		row_cache_ptr[i] = new cVertex[mResolution+3];
	}

	GLfloat cur_x, cur_tx;
	GLfloat cur_y = -mSizeY/2.0 - step_y, cur_ty = mTexFromY - step_ty;

	// we need 3 extra passes to fill the row cache
	for (int y = -3; y < mResolution; y++)
	{
		const GLfloat next_y = cur_y + step_y;
		const GLfloat next_ty = cur_ty + step_ty;

		cur_x = start_x - step_x;

		for (int x = -1; x <= mResolution+1; x++)
		{
			GLfloat z_sum = 0;

			for (int c = 0; c < mOscillationNo; c++)
			{
				const tsOscillationCenter *osc = &mOscillations[c];

				const GLfloat dx = (cur_x - osc->pos_x);
				const GLfloat dy = (cur_y - osc->pos_y);
				const GLfloat r = sqrt (dx*dx + dy*dy);

				z_sum += osc->amplitude*sin ((mTimeStamp+r)*osc->frequency);
			}

			row_cache_ptr[3][x+1].Assign (cur_x, cur_y, mAmpScale*z_sum);

			cur_x += step_x;
		}

		// skip rows while filling cache
		if (y >= 0)
		{
			pVertex_row top_row = row_cache_ptr[1];
			pVertex_row dwn_row = row_cache_ptr[2];

			glBegin (GL_TRIANGLE_STRIP);

			cur_tx = mTexFromX;

			for (int x = 0; x <= mResolution; x++)
			{
				cVertex normal, a, b, tmp;

				tmp = dwn_row[x+1];
				normal.SetZero ();

#define ADD_NORMAL(dy1,dx1,dy2,dx2) \
				a = tmp - row_cache_ptr[BASE_ROW+dy1][x+1+dx1]; \
				b = tmp - row_cache_ptr[BASE_ROW+dy2][x+1+dx2]; \
				normal += a.CrossProd (b);
#define BASE_ROW 2

				ADD_NORMAL(0,-1,-1,0);
				ADD_NORMAL(-1,0,0,+1);
				ADD_NORMAL(0,+1,+1,0);
				ADD_NORMAL(0,+1,0,-1);
				normal.Normalize ();
				glNormal3fv (normal);

				glTexCoord2f (cur_tx, next_ty);
				glVertex3fv (tmp);

				tmp = top_row[x+1];
				normal.SetZero ();
#undef BASE_ROW
#define BASE_ROW 1

				ADD_NORMAL(0,-1,-1,0);
				ADD_NORMAL(-1,0,0,+1);
				ADD_NORMAL(0,+1,+1,0);
				ADD_NORMAL(0,+1,0,-1);
				normal.Normalize ();
				glNormal3fv (normal);

				glTexCoord2f (cur_tx, cur_ty);
				glVertex3fv (tmp);

				cur_tx += step_tx;
			}

			glEnd ();
		}

		const pVertex_row first_row_tmp = row_cache_ptr[0];

		row_cache_ptr[0] = row_cache_ptr[1];
		row_cache_ptr[1] = row_cache_ptr[2];
		row_cache_ptr[2] = row_cache_ptr[3];
		row_cache_ptr[3] = first_row_tmp;

		cur_y = next_y;
		cur_ty = next_ty;
	}

	// free row cache
	for (int i = 0; i < 4; i++)
	{
		delete row_cache_ptr[i];
	}

	mRecalculationNeeded = false;

	glEndList ();	// complete display list
}

