// EXTENDED MESH VIEWER PROGRAM 
// --------------------
// Programming Assignment 2
// Subject: DM6101: Advanced Computer Graphics
// Name: Sornum Kabilen Kadirvelen
// Matriculation No.: G0701666C
//---------------------------------------------------------------

#include <iostream>
#include <GL/glut.h>
#include <cstdlib>
#include <math.h>
#include <algorithm>
#include <stdio.h>

#include "assignment2.h"

using namespace std;



float x_angle    = 0.0; 
float y_angle    = 0.0; 
float x_offset   = 0.0;
float y_offset   = 0.0;
float z_offset   = 0.0;

float scale_size = 1; 

int  obj_mode   = OBJ_SOLID;

bool show_bbox    = false;
bool show_normals = false;
bool show_texImg  = false;

bool drawTextures     = false;
bool drawVertexColor  = false;
bool genTextureCoords = false;

       Object oObject;
static GLuint texObj;


static Point  lightPos               (0.f, 0.f, 5.f, 1.f);

static GLColor4 lightColorAmbient  = {0.1, 0.1, 0.1, 1.f};
static GLColor4 lightColorDiffuse  = {0.6, 0.6, 0.6, 1.f};
static GLColor4 lightColorSpecular = {0.8, 0.0, 0.0, 1.f};

static float    constantAtt        = 1.f;
static float    linearAtt          = 0.4f;
static float    quadraticAtt       = 0.0f;

static GLColor4 matColorAmbient  = {0.9, 0.9, 0.9, 1.f};
static GLColor4 matColorDiffuse  = {0.9, 0.9, 0.9, 1.f};
static GLColor4 matColorSpecular = {0.9, 0.9, 0.9, 1.f};

static float  matShininess       = 60; 

static Point  camPos               (0.f, 0.f, 5.f, 1.f);

GLuint texture;


void updateVertexColor(Matrix mCam, Matrix mModelView)
{
	/* calculate per vertex lighting for a single point light  */
    /* loop over all vertices and update oObject.vertices[i].c */
	
	
	// Read vertices
	for(int i=0;i<(int)oObject.vertices.size();i++)
	{
		Vertex &ver = oObject.vertices[i];

		// Calculate Eye Position E vector
		Point EyePos;
		mult(EyePos, mModelView, ver.v);

		// Calculate the ModelviewNormal matrix
		Matrix mViewNormal;
		invert(mViewNormal, mModelView);
		Matrix mViewNormali;
		transpose(mViewNormali, mViewNormal);
				//dumpMatrix(mViewNormali);

		// Calculate Eye Normal N vector
		Vector EyeNormal;
		mult(EyeNormal, mViewNormali, ver.n);
		normalize(EyeNormal);

		// Calculate Light Direction vector & Light Distance
		Vector LightDir;
		Point t_light;
		mult(t_light, mCam, lightPos); 
		subPnt(LightDir,t_light, EyePos);
		float LightDist = length(LightDir);
		normalize(LightDir);
		
		// Calculate the E vector
		Vector E;
		subPnt(E,EyePos,EyePos);
		
		// Calculate the H vector
		Vector H;
		add(H, LightDir, E);
		normalize(H);

		// Calculate Attenuation
		float attenuation = 1/(constantAtt + linearAtt*LightDist + quadraticAtt*LightDist*LightDist);
		
		// Ambient Component
		Color ambient;
		ambient[R] = matColorAmbient[R] * lightColorAmbient[R];
		ambient[G] = matColorAmbient[G] * lightColorAmbient[G];
		ambient[B] = matColorAmbient[B] * lightColorAmbient[B];

		
		// Diffuse Component
		Color diffuse;
		float maxComponent;
		if (dot(LightDir, EyeNormal) > 0)
			maxComponent = dot(LightDir, EyeNormal);
		else
			maxComponent = 0;
		diffuse[R] = matColorDiffuse[R] * lightColorDiffuse[R] * maxComponent;
		diffuse[G] = matColorDiffuse[G] * lightColorDiffuse[G] * maxComponent;
		diffuse[B] = matColorDiffuse[B] * lightColorDiffuse[B] * maxComponent;


		//Specular Component
		Color specular;
		float maxComponent1;
		if (dot(H, EyeNormal) > 0)
			maxComponent1 = dot(H, EyeNormal);
		else
			maxComponent1 = 0;
		specular[R] = matColorSpecular[R] * lightColorSpecular[R] * powf(maxComponent1, matShininess);
		specular[G] = matColorSpecular[G] * lightColorSpecular[G] * powf(maxComponent1, matShininess);
		specular[B] = matColorSpecular[B] * lightColorSpecular[B] * powf(maxComponent1, matShininess);

		//Combining color components
		Color cResults;
		cResults[R] = attenuation * (ambient[R] + diffuse[R] + specular[R]);
		cResults[G] = attenuation * (ambient[G] + diffuse[G] + specular[G]);
		cResults[B] = attenuation * (ambient[B] + diffuse[B] + specular[B]);
		
		//Passing back the calculated values
		ver.c[R] = cResults[R];
		ver.c[G] = cResults[G];
		ver.c[B] = cResults[B];


	}
}

void calcPlaneMapping(void)
{
	/* calculate plane mapping                                 */
	/* loop over all vertices and update oObject.vertices[i].t */
	
	// using projection along z-plane
		
	float tS, tT, XMIN, XMAX, YMIN, YMAX;

	// defining variables for object's externs
	XMIN = oObject.bBox.minP[X];
	XMAX = oObject.bBox.maxP[X];
	YMIN = oObject.bBox.minP[Y];
	YMAX = oObject.bBox.maxP[Y];

	// Loop over vertices to calculate S & T and update it
	for(int i=0;i<(int)oObject.vertices.size();i++)
	{
		Vertex &vr = oObject.vertices[i];
		// calculate S
		tS = (vr.v[X] - XMIN) / (XMAX - XMIN);
		// calculate T
		tT = (vr.v[Y] - YMIN) / (YMAX - YMIN);
		// update texture cordinates
		vr.t[S] = tS;
		vr.t[T] = tT;
	}
	


}

void calcCylindricalMapping(void)
{
	/* calculate cylindrical mapping                           */
	/* loop over all vertices and update oObject.vertices[i].t */
	
	float x, y, z, cx, cy, cz, u, v, lon;
	
	cx = (oObject.bBox.maxP[X] + oObject.bBox.minP[X])*0.5 ;
	cy = (oObject.bBox.maxP[Y] + oObject.bBox.minP[Y])*0.5 ;
	cz = (oObject.bBox.maxP[Z] + oObject.bBox.minP[Z])*0.5 ;
		

	for(int i=0;i<(int)oObject.vertices.size();i++)
	{
		x = oObject.vertices[i].v[X] - cx;
		y = oObject.vertices[i].v[Y] - cy;
		z = oObject.vertices[i].v[Z] - cz;

		Vector V;

		V[X] = x;
		V[Y] = y;
		V[Z] = z;

		normalize(V);
				
		lon = atan2(-V[X],-V[Y]);
        v = -V[Z] + 0.5;
        lon = 1.0 - lon / (2*PI);
        u = lon - floor(lon);
		
		oObject.vertices[i].t[S] = u;
		oObject.vertices[i].t[T] = v;

	}

}

void calcSphereMapping(void)
{
	/* calculate spherical mapping                             */
	/* loop over all vertices and update oObject.vertices[i].t */

  // using projection along z-plane
	
	Vector Vcent;

	Vcent[X] = (oObject.bBox.minP[X] + oObject.bBox.maxP[X])*0.5;
	Vcent[Y] = (oObject.bBox.minP[Y] + oObject.bBox.maxP[Y])*0.5;
	Vcent[Z] = (oObject.bBox.minP[Z] + oObject.bBox.maxP[Z])*0.5;

	

	for(int i=0;i<(int)oObject.vertices.size();i++)
	{
		

		Vector V;

		V[X] = oObject.vertices[i].v[X] - Vcent[X];
		V[Y] = oObject.vertices[i].v[Y] - Vcent[Y];
		V[Z] = oObject.vertices[i].v[Z] - Vcent[Z];

		normalize(V);

		oObject.vertices[i].t[S] = asinf(V[X])/PI + 0.5;
		oObject.vertices[i].t[T] = asinf(V[Y])/PI + 0.5;

		
	}


}




void drawNormals(void)
{
    // Set color of normal
	glColor3f(1.0f, 1.0f, 1.0f);
	
	// Read vertices and normals
	for(int i=0;i<(int)oObject.faces.size();i++)
	{
	
		Vertex &vv1 = oObject.vertices[oObject.faces[i].v1];
		Vertex &vv2 = oObject.vertices[oObject.faces[i].v2];
		Vertex &vv3 = oObject.vertices[oObject.faces[i].v3];
		
		//Draw the 3 normals for each face
		glBegin(GL_LINES);
		{
			glScalef(0.2f, 0.2f, 0.2f);
			
			// Length of normal is shortened to be displayed more clearly (divided by 50)
			glVertex3f(vv1.v[X], vv1.v[Y], vv1.v[Z]);
			glVertex3f(vv1.v[X]+vv1.n[X]/50, vv1.v[Y]+vv1.n[Y]/50, vv1.v[Z]+vv1.n[Z]/50);

			glVertex3f(vv2.v[X], vv2.v[Y], vv2.v[Z]);
			glVertex3f(vv2.v[X]+vv2.n[X]/50, vv2.v[Y]+vv2.n[Y]/50, vv2.v[Z]+vv2.n[Z]/50);
			
			glVertex3f(vv3.v[X], vv3.v[Y], vv3.v[Z]);
			glVertex3f(vv3.v[X]+vv3.n[X]/50, vv3.v[Y]+vv3.n[Y]/50, vv3.v[Z]+vv3.n[Z]/50);		

		}
		glEnd();
	}	
}




// function to read a RAW file texture.
GLuint LoadTexture( const char * filename, int width, int height )
{
	GLuint texture;
	unsigned char * data;
	FILE * file;
	
	// reading the RAW file
	file = fopen( filename, "rb" );
	if ( file == NULL ) 
		return 0;
	data = (unsigned char *)malloc( width * height * 3 );
	fread( data, width * height * 3, 1, file );
	fclose( file );

	// generate the texture with the loaded data
	glGenTextures( 1, &texture ); 

	// bind the texture
	glBindTexture( GL_TEXTURE_2D, texture ); 

	// set texture environment	
	glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );

	// set the quality for zoom and filtering
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

	// setting the wrapping to REPEAT type 
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );

	// generate the texture from the read data 
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
	
	// clear the dynamic allocation used previously by malloc function
	free( data );
	return texture; 
	
}
void initTexture(void)
{
    // load the texture file
	texture = LoadTexture("texture2.raw", 256, 256);
	

} 

void setupTexture(void)
{
	/* enable texturing and bind texture */
	glEnable(GL_TEXTURE_2D);
	glBindTexture( GL_TEXTURE_2D, texture );
		
}

void finishTexture(void)
{
	/* disable texturing and unbind texture */
	//glDeleteTextures( 1, &texture ); 
	glDisable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, 0);
}

void setupTexGen(void)
{
	/* setup and enable texture coordinate generation */

	// set the mapping to be object linear. i.e texture locked to object
	glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); 
    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
	//enable texture coordinate generation
	glEnable(GL_TEXTURE_GEN_S); 
    glEnable(GL_TEXTURE_GEN_T);
}

void finishTexGen(void)
{
	/* disable texture coordinate generation */
	glDisable(GL_TEXTURE_GEN_S);
	glDisable(GL_TEXTURE_GEN_T);

}

void disp(void)
{
    // Just clean the screen
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    // setup the perspective projection
    glMatrixMode  (GL_PROJECTION ); 
    glLoadIdentity(              ); 
    gluPerspective(60, 1, .1, 100); 

    glMatrixMode  (GL_MODELVIEW); 
    glLoadIdentity(            ); 

    gluLookAt(camPos[X], 
              camPos[Y],
              camPos[Z], 
              0, 0, 0,
              0, 1, 0); 

    Matrix mCam;

    if(drawVertexColor == true)
    {
        glGetFloatv(GL_MODELVIEW_MATRIX, mCam);
    }

    glLightfv(GL_LIGHT0, GL_POSITION, lightPos.gldata());

    glLightfv(GL_LIGHT0, GL_AMBIENT,               lightColorAmbient );
    glLightfv(GL_LIGHT0, GL_DIFFUSE,               lightColorDiffuse );
    glLightfv(GL_LIGHT0, GL_SPECULAR,              lightColorSpecular);

	glLightf (GL_LIGHT0, GL_CONSTANT_ATTENUATION,  constantAtt );
	glLightf (GL_LIGHT0, GL_LINEAR_ATTENUATION,    linearAtt   );
	glLightf (GL_LIGHT0, GL_QUADRATIC_ATTENUATION, quadraticAtt);

	if(show_texImg)
	{
		setupTexture();

		glBegin(GL_QUADS);
        {
			glTexCoord2d(0.0,0.0); glVertex2d(-0.5,-0.5);
			glTexCoord2d(1.0,0.0); glVertex2d( 0.5,-0.5);
			glTexCoord2d(1.0,1.0); glVertex2d( 0.5, 0.5);
			glTexCoord2d(0.0,1.0); glVertex2d(-0.5, 0.5);
        }
		glEnd();

		finishTexture();
	}

    // rotate and scale the object
	glTranslatef(x_offset, y_offset, z_offset);
    glRotatef   (x_angle,  0, 1, 0); 
    glRotatef   (y_angle,  1, 0, 0); 
    glScalef    (scale_size, scale_size, scale_size); 

    if(show_bbox == true)
    {
        drawBoundingBox();
    }

	if(show_normals == true)
    {
		drawNormals();
    }

    if(drawVertexColor == false)
    {
        glEnable(GL_LIGHTING);
        glEnable(GL_LIGHT0);
    }
    else
    {
        Matrix mModelView;
        
        glGetFloatv(GL_MODELVIEW_MATRIX, mModelView);

        updateVertexColor(mCam, mModelView);
    }

    if(drawTextures == true)
    {	
		setupTexture();

		if(genTextureCoords == true)
		{
			setupTexGen();
		}
	}

	if(obj_mode == OBJ_WIREFRAME)
    {
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    }
    else if(obj_mode == OBJ_SOLID || obj_mode == OBJ_EDGE)
    {
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    }

    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT,   matColorAmbient );
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE,   matColorDiffuse );
    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR,  matColorSpecular);
    glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, matShininess    );

    drawObject(drawVertexColor);

    if(obj_mode == OBJ_EDGE)
    {
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        glDisable(GL_LIGHTING);
        glDisable(GL_LIGHT0);

        glPolygonOffset(0, 10);
        glEnable(GL_POLYGON_OFFSET_LINE);

        glColor3f(0.0f, 0.0, 1.0f);

        drawObject(false);

        glDisable(GL_POLYGON_OFFSET_LINE);
        glPolygonOffset(0, 0);
    }


    if(drawTextures == true)
    {
		finishTexture();

		if(genTextureCoords == true)
		{
			finishTexGen();
		}
	}

    glDisable(GL_LIGHTING);
    glDisable(GL_LIGHT0);

  
    // swap the buffers
    glutSwapBuffers(); 
}




