////////////////////////////////////////////////////////
//
// GEM - Graphics Environment for Multimedia
//
// mark@danks.org
//
// Implementation file
//
//    Copyright (c) 1997-1998 Mark Danks.
//    For information on usage and redistribution, and for a DISCLAIMER OF ALL
//    WARRANTIES, see the file, "GEM.LICENSE.TERMS" in this distribution.
//
/////////////////////////////////////////////////////////

#include "partfount.h"

#include <math.h>
#include "papi.h"

CPPEXTERN_NEW(partfount)

/////////////////////////////////////////////////////////
//
// partfount
//
/////////////////////////////////////////////////////////
// Constructor
//
/////////////////////////////////////////////////////////
partfount :: partfount()
		   : m_onOff(0)
{
	const float m_size = .05f;
	static GLdouble n[6][3] =
    {
	{0.0, 0.0, 1.0}, {1.0, 0.0, 0.0}, {0.0, 0.0, -1.0},
	{-1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, -1.0, 0.0}
    };
    static GLdouble v[8][3] =
    {
	{-1.0, -1.0,  1.0}, { 1.0, -1.0,  1.0}, { 1.0, 1.0,  1.0}, {-1.0, 1.0,  1.0},
	{ 1.0, -1.0, -1.0}, {-1.0, -1.0, -1.0}, {-1.0, 1.0, -1.0}, { 1.0, 1.0, -1.0}
    };
    static GLint faces[6][4] =
    {
	{ 0, 1, 2, 3 }, { 1, 4, 7, 2 }, { 4, 5, 6, 7 },
	{ 5, 0, 3, 6 }, { 3, 2, 7, 6 }, { 1, 0, 5, 4 }
    };
    
	m_particle = pGenParticleGroups(1, 300);
	m_dList = glGenLists(1);

	if (m_dList)
	{
		glNewList(m_dList, GL_COMPILE);
	        for (int i = 0; i < 6; i++)
	        {
				glBegin(GL_QUADS);
					glNormal3dv(&n[i][0]);
					glVertex3d(v[faces[i][0]][0] * m_size, v[faces[i][0]][1] * m_size, v[faces[i][0]][2] * m_size);
					glVertex3d(v[faces[i][1]][0] * m_size, v[faces[i][1]][1] * m_size, v[faces[i][1]][2] * m_size);
					glVertex3d(v[faces[i][2]][0] * m_size, v[faces[i][2]][1] * m_size, v[faces[i][2]][2] * m_size);
					glVertex3d(v[faces[i][3]][0] * m_size, v[faces[i][3]][1] * m_size, v[faces[i][3]][2] * m_size);
				glEnd();
 	        }
			
		glEndList();
	}

    // create the rotation inlet
    inlet_new(this->x_obj, &this->x_obj->ob_pd, gensym("list"), gensym("rotlist"));
    // create the bounce inlet
    inlet_new(this->x_obj, &this->x_obj->ob_pd, gensym("list"), gensym("bouncelist"));
    // create the onoff inlet
    inlet_new(this->x_obj, &this->x_obj->ob_pd, gensym("float"), gensym("onoff"));
}

/////////////////////////////////////////////////////////
// Destructor
//
/////////////////////////////////////////////////////////
partfount :: ~partfount()
{
	if (m_particle < 0)
		pDeleteParticleGroups(m_particle, 1);
	if (m_dList)
		glDeleteLists(m_dList, 1);
}

/////////////////////////////////////////////////////////
// onOff
//
/////////////////////////////////////////////////////////
void partfount :: onOff(int state)
{
	m_onOff = state;
}

/////////////////////////////////////////////////////////
// reset
//
/////////////////////////////////////////////////////////
void partfount :: reset()
{
	m_rotMatrix.identity();
	m_bounceMatrix.identity();
}

/////////////////////////////////////////////////////////
// rotationMess
//
/////////////////////////////////////////////////////////
void partfount :: rotationMess(float x, float y, float z)
{
	m_rotMatrix.rotateX(x);
	m_rotMatrix.rotateY(y);
	m_rotMatrix.rotateZ(z);
}

/////////////////////////////////////////////////////////
// bounceMess
//
/////////////////////////////////////////////////////////
void partfount :: bounceMess(float x, float y, float z)
{
	m_bounceMatrix.rotateX(x);
	m_bounceMatrix.rotateY(y);
	m_bounceMatrix.rotateZ(z);
}

/////////////////////////////////////////////////////////
// render
//
/////////////////////////////////////////////////////////
void partfount :: render(GemState *state)
{
	const float vel1[] = {0.0f, 0.30f, -0.01f};
	const float vel2[] = {0.0f, 0.32f, -0.01f};

	if (m_particle < 0)
		return;

	// draw the extra stuff first
	float dstVel1[3], dstVel2[3];
	m_rotMatrix.transform(vel1[0], vel1[1], vel1[2], &dstVel1[0], &dstVel1[1], &dstVel1[2]);
	m_rotMatrix.transform(vel2[0], vel2[1], vel2[2], &dstVel2[0], &dstVel2[1], &dstVel2[2]);
	
	// blue bounce plane
	const float bon[] = { -5.f, -2.f, -5.f };
	const float dir1[] = { 10.f, 0.f, 0.f };
	const float dir2[] = { 0.f, 0.f, 10.f };
	float dst11[3], dst12[3], dstB1[3];
	m_bounceMatrix.transform(bon[0], bon[1], bon[2], &dstB1[0], &dstB1[1], &dstB1[2]);
	m_bounceMatrix.transform(dir1[0], dir1[1], dir1[2], &dst11[0], &dst11[1], &dst11[2]);
	m_bounceMatrix.transform(dir2[0], dir2[1], dir2[2], &dst12[0], &dst12[1], &dst12[2]);
	
	// red bounce plane
	float dstB2[3], dst21[3], dst22[3];
	Matrix tmpMatrix = m_bounceMatrix;
	tmpMatrix.rotateZ(180.f);
	tmpMatrix.multiply(&m_bounceMatrix);
	tmpMatrix.transform(bon[0], bon[1], bon[2], &dstB2[0], &dstB2[1], &dstB2[2]);
	tmpMatrix.transform(dir1[0], dir1[1], dir1[2], &dst21[0], &dst21[1], &dst21[2]);
	tmpMatrix.transform(dir2[0], dir2[1], dir2[2], &dst22[0], &dst22[1], &dst22[2]);

	// green bounce plane
	float dstB3[3], dst31[3], dst32[3];
	Matrix tmp1Matrix = m_bounceMatrix;
	tmp1Matrix.rotateZ(90.f);
	tmp1Matrix.multiply(&tmpMatrix);
	tmp1Matrix.transform(bon[0], bon[1], bon[2], &dstB3[0], &dstB3[1], &dstB3[2]);
	tmp1Matrix.transform(dir1[0], dir1[1], dir1[2], &dst31[0], &dst31[1], &dst31[2]);
	tmp1Matrix.transform(dir2[0], dir2[1], dir2[2], &dst32[0], &dst32[1], &dst32[2]);

	////////////////////////
	// Particle Stuff
	pCurrentGroup(m_particle);

	pVelocityD(PDCylinder, dstVel1[0], dstVel1[1], dstVel1[2], dstVel2[0], dstVel2[1], dstVel2[2], 0.021f, 0.019f);
	pColorD(1.0, PDLine, 0.6f, 1.0f, 0.7f, 1.0f, 1.0f, 1.0f);
	pSize(1.5f);
	
	pCopyVertexB(false, true);

	if (m_onOff)
	{
		pSource(10, PDLine, 0.0f, 0.401f, 0.0f, 0.0f, 0.405f, 0.0f);
	}
	else
	{
		static int count = 0;
		count++;
		if (count > 10)
		{
			count = 0;
			pSource(2, PDLine, 0.0f, 0.401f, 0.0f, 0.0f, 0.405f, 0.0f);
		}
	}
	pGravity(0.0f, -0.01f, 0.0f);
	
	pKillSlow(0.01f);
	
	pBounce(-0.05f, 0.35f, 0, PDPlane, dstB1[0], dstB1[1], dstB1[2], dst11[0], dst11[1], dst11[2], dst12[0], dst12[1], dst12[2]);
	pBounce(-0.05f, 0.35f, 0, PDPlane, dstB2[0], dstB2[1], dstB2[2], dst21[0], dst21[1], dst21[2], dst22[0], dst22[1], dst22[2]);
	pBounce(-0.05f, 0.35f, 0, PDPlane, dstB3[0], dstB3[1], dstB3[2], dst31[0], dst31[1], dst31[2], dst32[0], dst32[1], dst32[2]);

	pSink(true, PDPlane, 0, -4, 0, 1, 0, 0, 0, 0, 1);

	pMove();

	// draw the fountain
	glDisable(GL_TEXTURE_2D);
	glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);

	pDrawGroupl(m_dList);

	glDisable(GL_CULL_FACE);

	/////////////////////
	// Other drawing items
	/////////////////////
	// draw the line
	glDisable(GL_LIGHTING);
	const float size = 2.f;
	glColor3f(1.f, 1.f, 1.f);
	glLineWidth(5.f);
	glBegin(GL_LINES);
		glVertex3f(0.f, 0.401f, 0.f);
		glVertex3f(dstVel1[0] * size, 0.401f + dstVel1[1] * size, dstVel1[2] * size);
	glEnd();

	// draw the bounce planes
	glDisable(GL_DEPTH_TEST);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_TEXTURE_2D);

	// blue texture
	static double lastTime1 = clock_getsystime();
	static double lastTime2 = clock_getsystime();
	static double lastTime3 = clock_getsystime();
	static double sysToMilli = clock_gettimesince(clock_getsystime() - 1);

	double cur1Time = clock_gettimesince(lastTime1);
	double cur2Time = clock_gettimesince(lastTime2);
	double cur3Time = clock_gettimesince(lastTime3);
	float period = 20.f * 1000.f;
	while (cur1Time > period)
	{
		cur1Time -= period;
		lastTime1 += (period / sysToMilli);
	}

	double amount = cur1Time / period;
	const double TWO_PI = 3.1415926535 * 2.;
	float val1 = (float)sin(amount * TWO_PI);

	// red texture
	period = 18.23f * 1000.f;
	while (cur2Time > period)
	{
		cur2Time -= period;
		lastTime2 += (period / sysToMilli);
	}
	amount = cur2Time / period;
	float val2 = (float)sin(amount * TWO_PI);

	// green texture
	period = 13.64f * 1000.f;
	while (cur3Time > period)
	{
		cur3Time -= period;
		lastTime3 += (period / sysToMilli);
	}
	amount = cur3Time / period;
	float val3 = (float)sin(amount * TWO_PI);

	// blue square
	glColor4f(0.3f, 0.3f, 1.f, .4f);
	glBegin(GL_QUADS);
		glTexCoord2f(val1, val1);
		glVertex3f(dstB1[0], dstB1[1], dstB1[2]);
		glTexCoord2f(1.f + val1, val1);
		glVertex3f(dstB1[0] + dst11[0], dstB1[1] + dst11[1], dstB1[2] + dst11[2]);
		glTexCoord2f(1.f + val1, 1.f + val1);
		glVertex3f(dstB1[0] + dst11[0] + dst12[0], dstB1[1] + dst11[1] + dst12[1], dstB1[2] + dst11[2] + dst12[2]);
		glTexCoord2f(val1, 1.f + val1);
		glVertex3f(dstB1[0] + dst12[0], dstB1[1] + dst12[1], dstB1[2] + dst12[2]);
	glEnd();
	// red square
	glColor4f(1.f, 0.3f, 0.3f, .4f);
	glBegin(GL_QUADS);
		glTexCoord2f(val2, val2);
		glVertex3f(dstB2[0], dstB2[1], dstB2[2]);
		glTexCoord2f(1.f + val2, val2);
		glVertex3f(dstB2[0] + dst21[0], dstB2[1] + dst21[1], dstB2[2] + dst21[2]);
		glTexCoord2f(1.f + val2, 1.f + val2);
		glVertex3f(dstB2[0] + dst21[0] + dst22[0], dstB2[1] + dst21[1] + dst22[1], dstB2[2] + dst21[2] + dst22[2]);
		glTexCoord2f(val2, 1.f + val2);
		glVertex3f(dstB2[0] + dst22[0], dstB2[1] + dst22[1], dstB2[2] + dst22[2]);
	glEnd();
	// green square
	glColor4f(.3f, 1.f, 0.3f, .4f);
	glBegin(GL_QUADS);
		glTexCoord2f(val3, val3);
		glVertex3f(dstB3[0], dstB3[1], dstB3[2]);
		glTexCoord2f(1.f + val3, val3);
		glVertex3f(dstB3[0] + dst31[0], dstB3[1] + dst31[1], dstB3[2] + dst31[2]);
		glTexCoord2f(1.f + val3, 1.f + val3);
		glVertex3f(dstB3[0] + dst31[0] + dst32[0], dstB3[1] + dst31[1] + dst32[1], dstB3[2] + dst31[2] + dst32[2]);
		glTexCoord2f(val1, 1.f + val3);
		glVertex3f(dstB3[0] + dst32[0], dstB3[1] + dst32[1], dstB3[2] + dst32[2]);
	glEnd();
	glDisable(GL_BLEND);
	glEnable(GL_DEPTH_TEST);

	glEnable(GL_LIGHTING);
	
	glEnable(GL_TEXTURE_2D);
}

/////////////////////////////////////////////////////////
// static member functions
//
/////////////////////////////////////////////////////////
void partfount :: obj_setupCallback(t_class *classPtr)
{
    class_addmethod(classPtr, (t_method)&partfount::rotationMessCallback,
    	    gensym("rotlist"), A_FLOAT, A_FLOAT, A_FLOAT, A_NULL);
    class_addmethod(classPtr, (t_method)&partfount::bounceMessCallback,
    	    gensym("bouncelist"), A_FLOAT, A_FLOAT, A_FLOAT, A_NULL);
    class_addmethod(classPtr, (t_method)&partfount::resetCallback,
    	    gensym("reset"), A_NULL);
    class_addmethod(classPtr, (t_method)&partfount::onOffCallback,
    	    gensym("onoff"), A_FLOAT, A_NULL);
}
void partfount :: rotationMessCallback(void *data, t_floatarg x, t_floatarg y, t_floatarg z)
{
    GetMyClass(data)->rotationMess((float)x, (float)y, (float)z);
}
void partfount :: bounceMessCallback(void *data, t_floatarg x, t_floatarg y, t_floatarg z)
{
    GetMyClass(data)->bounceMess((float)x, (float)y, (float)z);
}
void partfount :: resetCallback(void *data)
{
	GetMyClass(data)->reset();
}
void partfount :: onOffCallback(void *data, t_floatarg state)
{
	GetMyClass(data)->onOff((int)state);
}
