#include "Render.h"





Camera<double> Render::getCamera(void) const
{
	return this->camera;
}

void Render::getCamera( Camera<double> &dstCamera) const
{
	dstCamera= this->camera;
}
Projection& Render::getProjection(void)
{
	return this->projection;
}
void Render::setFOV( float FOV)
{
	this->projection.FOV=FOV;
}
void Render::setProjection(float ratio, float zNear, float zFar, float FOV)
{
	this->projection.ratio=ratio;
	this->projection.zNear=zNear;
	this->projection.zFar=zFar;
	this->projection.FOV=FOV;
}
Render::Render( Context &context): AbstractRender(context), camera(0,0,-50,0,0,1,1,0,0)
{
	this->setViewport(0,0, 640, 480 );
	this->setProjection( 640.0/480.0 , 1, 500, 45.0);		// 34.515 golem FOV
}

void Render::beginFrame(const Camera<double>& camera)
{
	this->camera = camera; //save camera parameters
	this->camera.recalculate_up();

	this->beginFrame();
}
void Render::beginFrame(void)
{
	this->bind();
	this->clearDepthbuffer();
	this->clearFramebuffer();
}

Point_3<double> Render::unproject( int screenPos[2] )
{
	this->bind();

	float depthVal;
	glReadPixels( screenPos[0], screenPos[1], 1,1, GL_DEPTH_COMPONENT, GL_FLOAT, &depthVal );

	GLint viewport[4];
	glGetIntegerv(GL_VIEWPORT, viewport);

	GLdouble modelview[16];// Paměť pro modelview matici
	glGetDoublev(GL_MODELVIEW_MATRIX, modelview);// Získání modelview matice

	GLdouble projection[16];// Paměti pro projekční matici
	glGetDoublev(GL_PROJECTION_MATRIX, projection);// Získání projekční matice

	double dstPoint[3];
	gluUnProject( screenPos[0], screenPos[1], depthVal, modelview, projection, viewport, &dstPoint[0], &dstPoint[1], &dstPoint[2] );

	return MAKE_POINT<double>( dstPoint[0], dstPoint[1], dstPoint[2] );
}
Point_3<float> Render::project( Point_3<float> &point3D )
{
	this->bind();


	GLint viewport[4];
	glGetIntegerv(GL_VIEWPORT, viewport);

	GLdouble modelview[16];// Paměť pro modelview matici
	glGetDoublev(GL_MODELVIEW_MATRIX, modelview);// Získání modelview matice

	GLdouble projection[16];// Paměti pro projekční matici
	glGetDoublev(GL_PROJECTION_MATRIX, projection);// Získání projekční matice

	double dstPoint[3];
	gluProject( point3D.x(), point3D.y(), point3D.z(), modelview, projection, viewport, &dstPoint[0], &dstPoint[1], &dstPoint[2] );

	return MAKE_POINT<float>( dstPoint[0], dstPoint[1], dstPoint[2] );
}
void Render::bind(void)
{
	AbstractRender::bind();

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	gluPerspective(this->projection.FOV , this->projection.ratio , this->projection.zNear, this->projection.zFar);	// Calculate The Aspect Ratio Of The Window

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt( this->camera.pos.x(), this->camera.pos.y(), this->camera.pos.z(),
		this->camera.pos.x() + this->camera.look.x(), this->camera.pos.y() + this->camera.look.y(), this->camera.pos.z() + this->camera.look.z(),
		this->camera.get_up().x(), this->camera.get_up().y(), this->camera.get_up().z() );

	/*
	gluLookAt( this->camera.pos.x(), this->camera.pos.y(), this->camera.pos.z(),
		this->camera.pos.x() + this->camera.look.x() * 10000000000.0, this->camera.pos.y() + this->camera.look.y() * 10000000000.0, this->camera.pos.z() + this->camera.look.z() * 10000000000.0,
		this->camera.get_up().x() * 10000000000.0 , this->camera.get_up().y() * 10000000000.0, this->camera.get_up().z() * 10000000000.0 );
	*/

}
void Render::renderModel( Model &model)
{
	this->bind();
	RENDER::renderModel( model, true );
}
void Render::renderModels( vector<Model*> &models)
{
	this->bind();

	for (int i=0; i<models.size(); ++i)
	{
		RENDER::renderModel(*models[i], true );
	}
}
void Render::renderParticles( vector<GraphicParticle*> &particles )
{
	this->bind();

	selectTextureUnit( 0 );
	glEnable( GL_TEXTURE_2D );
	
	glEnable( GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA,GL_ONE);
	glDisable( GL_DEPTH_TEST );
	for ( int i=0; i< particles.size(); ++i)
	{
		if ( particles[i]->isAlive() )
		{
			particles[i]->render( CONVERT_TO_FLOAT(this->camera.right),  CONVERT_TO_FLOAT(this->camera.get_up()) );
		}
	}
	glEnable( GL_DEPTH_TEST );
	glDisable( GL_BLEND );
}
void Render::renderFullscreenImage2D( unsigned int texId )
{
	int leftDown[2]= { this->viewport.fromX, this->viewport.fromY };
	int upRight[2] = { this->viewport.fromX + this->viewport.sizeX, this->viewport.fromY + this->viewport.sizeY };

	this->renderImage2D( leftDown, upRight, texId );
}

void Render::renderImage2D( int leftDown[2], unsigned int texId )
{
	GLint width, height;
	glBindTexture(GL_TEXTURE_2D, texId);

	glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
	glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);

	int upRight[2] = { leftDown[0] + width, leftDown[1] + height  };
	this->renderImage2D( leftDown, upRight, texId );
}


void Render::renderImage2D( int leftDown[2], int upRight[2], unsigned int texId )
{
	AbstractRender::bind();

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D( this->viewport.fromX, this->viewport.fromX + this->viewport.sizeX, this->viewport.fromY, this->viewport.fromY + this->viewport.sizeY );	// Calculate The Aspect Ratio Of The Window

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	selectTextureUnit(0);
	glEnable( GL_TEXTURE_2D);

	glBindTexture(GL_TEXTURE_2D, texId);

	glBegin(GL_QUADS);
		glMultiTexCoord2f( GL_TEXTURE0, 0, 0); glVertex2f( leftDown[0], leftDown[1] );
		glMultiTexCoord2f( GL_TEXTURE0, 1, 0); glVertex2f( upRight[0], leftDown[1] );
		glMultiTexCoord2f( GL_TEXTURE0, 1, 1); glVertex2f( upRight[0], upRight[1] );
		glMultiTexCoord2f( GL_TEXTURE0, 0, 1); glVertex2f( leftDown[0], upRight[1] );
	glEnd();

}
void Render::renderText( Point_3<float> pos, const string &text, const Font &font )
{
	const float nextCharOffset = 0.0625f;
	selectTextureUnit(0);
	glEnable( GL_TEXTURE_2D );
	glBindTexture(GL_TEXTURE_2D, font.texID );

	//set pos param as center of the text
	pos = CONVERT_TO_FLOAT( CONVERT_TO_DOUBLE(pos) -  this->camera.right * font.size * text.size() / 2);

	glColor4f( font.color[0], font.color[1], font.color[2], font.alpha );

	glPushMatrix();

		//version with translatef is much more numeric stable, which is the reason why i use it
		glTranslatef( pos.x(), pos.y(), pos.z() );
		pos.set_zero();

		glBegin(GL_QUADS);						

		for ( int i=0; i < text.size(); ++i)					
		{
			//cout<<"keyCode "<<char(text[i])<<endl;

			float cx=float(char(text[i]/*-32*/)%16)/16.0;					// X Position Of Current Character
			float cy=float(char(text[i]/*-32*/)/16)/16.0;					// Y Position Of Current Character

			//cy = cy + 0.5;
			//cout<<cx<<" "<<cy<<endl;
		
			glTexCoord2f(cx,1-cy);						// Texture Coord (Top left)
			glVertex3f( pos.x(), pos.y(), pos.z() );			// Vertex Coord (Top left)


			Point_3<float> tmpPos = pos;
			tmpPos -=  CONVERT_TO_FLOAT(this->camera.get_up()) * font.size;

			glTexCoord2f(cx,1-cy-nextCharOffset);				// Texture Coord (Bottom Left)
			glVertex3f( tmpPos.x(), tmpPos.y(), tmpPos.z() );	// Vertex Coord (Bottom Left)


			tmpPos +=  CONVERT_TO_FLOAT(this->camera.right) * font.size;
			glTexCoord2f(cx+nextCharOffset,1-cy-nextCharOffset);				// Texture Coord (Bottom Right)
			glVertex3f( tmpPos.x(), tmpPos.y(), tmpPos.z() );	// Vertex Coord (Bottom Right)

			tmpPos = pos;
			tmpPos +=  CONVERT_TO_FLOAT(this->camera.right) * font.size;

			glTexCoord2f(cx+nextCharOffset,1-cy);						// Texture Coord (Top Right)
			glVertex3f( tmpPos.x(), tmpPos.y(), tmpPos.z() );			// Vertex Coord (Top Right)

			pos +=  CONVERT_TO_FLOAT(this->camera.right) * font.size;
		}		

		glEnd();		
	glPopMatrix();
}
void Render::renderBillboard( const Billboard &billboard )
{
	this->bind();

	glColor4f( billboard.color.col.r, billboard.color.col.g, billboard.color.col.b, billboard.color.w );

	if ( billboard.texID!= 0 )
	{
		selectTextureUnit( 0 );
		glEnable( GL_TEXTURE_2D);
		glBindTexture( GL_TEXTURE_2D, billboard.texID );
	}
	else
	{
		glDisable( GL_TEXTURE_2D);
	}

	Vector_3<float> offsetVec;
	Point_3<float> vertexPos;


	glBegin( GL_QUADS);

		offsetVec=  CONVERT_TO_FLOAT(this->camera.right*billboard.size[0] + this->camera.get_up() * billboard.size[1]);
		vertexPos = billboard.position + offsetVec;
		glTexCoord2f( 0,0); glVertex3fv( vertexPos.getArrPtr() );

		offsetVec=  CONVERT_TO_FLOAT(-this->camera.right*billboard.size[0] + this->camera.get_up() * billboard.size[1]);
		vertexPos = billboard.position + offsetVec;
		glTexCoord2f( 1,0); glVertex3fv( vertexPos.getArrPtr() );

		offsetVec=  CONVERT_TO_FLOAT(-this->camera.right*billboard.size[0] - this->camera.get_up() * billboard.size[1]);
		vertexPos = billboard.position + offsetVec;
		glTexCoord2f( 1,1); glVertex3fv( vertexPos.getArrPtr() );

		offsetVec=  CONVERT_TO_FLOAT(this->camera.right*billboard.size[0] - this->camera.get_up() * billboard.size[1] );
		vertexPos = billboard.position + offsetVec;
		glTexCoord2f( 0,1); glVertex3fv( vertexPos.getArrPtr() );

	glEnd();
}



void Render::renderLine( const Point_3<float> &from, const Point_3<float> &to, const CRgbw &color, float width )
{
	this->bind();

	GLboolean useBlending;
	glGetBooleanv( GL_BLEND, &useBlending );

	glEnable(GL_BLEND);
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	glDisable(GL_TEXTURE_2D);
	glColor4f( color.col.r, color.col.g, color.col.b, color.w );

	glLineWidth( width );

	glBegin( GL_LINES );
		glVertex3fv( from.getArrPtr() );
		glVertex3fv( to.getArrPtr() );
	glEnd();

	if ( useBlending ) glEnable( GL_BLEND ); else glDisable( GL_BLEND );
}

void Render::renderCircle( const Point_3<float> &center, const Vector_3<float> &normalizeAxis, const CRgbw &color, float radius, int segmentCount, float width )
{
	this->bind();

	GLboolean useBlending;
	glGetBooleanv( GL_BLEND, &useBlending );

	glEnable(GL_BLEND);
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	glDisable(GL_TEXTURE_2D);
	glColor4f( color.col.r, color.col.g, color.col.b, color.w );

	glLineWidth( width );

	float angleOffset = 360.0 / segmentCount;

	Vector_3<float> vec = normalizeAxis.getPerpendicularVector();
	if ( vec!= Vector_3<float>(0,0,0) )
	{
		vec.normalize();
	}

	Point_3<double> point1 = CONVERT_TO_DOUBLE( /*center*/ Point_3<float>(0,0,0) ) +  CONVERT_TO_DOUBLE( vec * radius );
	Point_3<double> point2 = CONVERT_TO_DOUBLE( point1 );

	/// this solution is much more stable for high center values
	glPushMatrix();
		glTranslatef( center.x(), center.y(), center.z() );
		glBegin( GL_LINE_LOOP );
			for ( int i=0; i< segmentCount-1; ++i)
			{
				point1 = point2;
				point2.rotate_point( CONVERT_TO_DOUBLE( /*center*/ Point_3<float>(0,0,0) ), CONVERT_TO_DOUBLE(normalizeAxis), angleOffset );

				glVertex3f( point1.x(), point1.y(), point1.z() );
				glVertex3f( point2.x(), point2.y(), point2.z() );
			}
		glEnd();
	glPopMatrix();

	if ( useBlending ) glEnable( GL_BLEND ); else glDisable( GL_BLEND );
}

void Render::renderSphere( const Point_3<float> &center, const CRgbw &color, float radius, int segmentCount, unsigned int texId )
{
	this->bind();
	
	GLboolean useBlending;
	glGetBooleanv( GL_BLEND, &useBlending );

	glEnable(GL_BLEND);
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	glColor4f( color.col.r, color.col.g, color.col.b, color.w );

	if ( texId == 0 )
	{
		glDisable( GL_TEXTURE_2D );
	}
	else
	{
		glEnable(GL_TEXTURE_2D);
		glBindTexture( GL_TEXTURE_2D, texId );
	}

	glPushMatrix();
		glTranslatef( center.x(), center.y(), center.z() );
		glRotatef( 90.0, 1, 0, 0);
		GLUquadricObj *quadratic = gluNewQuadric();
			gluQuadricTexture(quadratic, GL_TRUE);
			gluSphere( quadratic, radius, segmentCount, segmentCount );
		gluDeleteQuadric( quadratic );
	glPopMatrix();

	if ( useBlending ) glEnable( GL_BLEND ); else glDisable( GL_BLEND );
}

void Render::renderQuad( const Point_3<float> &point1, const Point_3<float> &point2, const Point_3<float> &point3, const Point_3<float> &point4, const CRgbw &color, unsigned int texId )
{
	this->bind();

	GLboolean useBlending;
	glGetBooleanv( GL_BLEND, &useBlending );

	glEnable(GL_BLEND);
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	selectTextureUnit( 0 );

	if ( texId == 0 )
	{
		glDisable( GL_TEXTURE_2D );
	}
	else
	{
		glEnable(GL_TEXTURE_2D);
		glBindTexture( GL_TEXTURE_2D, texId );
	}

	glColor4f( color.col.r, color.col.g, color.col.b, color.w );


	glBegin( GL_QUADS );
		glTexCoord2f(0.0, 0.0);		glVertex3fv( point1.getArrPtr() );
		glTexCoord2f(1.0, 0.0);		glVertex3fv( point2.getArrPtr() );
		glTexCoord2f(1.0, 1.0);		glVertex3fv( point3.getArrPtr() );
		glTexCoord2f(0.0, 1.0);		glVertex3fv( point4.getArrPtr() );
	glEnd();

	if ( useBlending ) glEnable( GL_BLEND ); else glDisable( GL_BLEND );
}


//===========================

