#include "stdafx.h"
#include "models.h"
#include "ModelMovements.h"

#define M_PI       3.14159265358979323846

#define SCREEN_WIDTH 864
#define SCREEN_HEIGHT 480

#define SCENE_DURATION 250

#define TIME_INTERVAL_MS 15


GLfloat radius = -8.5f;

GLfloat angleX = 0.5f, angleY = 0.1f;
int xOrigin = - 1, yOrigin = -1;
GLfloat deltaAngleX = 0, deltaAngleY = 0;

int current_time = 0;

GLuint textureGround;
GLuint leftRobotTextureIDs[6];
GLuint rightRobotTextureIDs[6];

GLint shadowSize = 512;  

GLfloat light0Pos [] = {4.0f, 6.5f, -11.0f, 1.0f }; 
//GLfloat light0Pos [] = {7.0f, 7.5f, -12.0f, 1.0f }; 


int windowWidth, windowHeight;

#ifdef WIN32
PFNGLACTIVETEXTUREPROC glActiveTexture = nullptr;
#endif


void SetupLight()
{
	
	GLfloat  ambientLight[] = { 0.7f, 0.7f, 0.7f, 1.0f };
	GLfloat  diffuseLight[] = { 1.0f, 1.0f, 1.0f, 1.0f };
	GLfloat  specularLight[] = { 1.0f, 1.0f, 1.0f, 1.0f };
	GLfloat  specref[] = { 1.0f, 1.0f, 1.0f, 1.0f };

	glEnable(GL_LIGHTING);
	glLightfv(GL_LIGHT0, GL_POSITION, light0Pos);
	glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight); 
	glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight); 
	glLightfv(GL_LIGHT0, GL_SPECULAR, specularLight); 

	glEnable(GL_LIGHT0); 	

	glEnable(GL_COLOR_MATERIAL);
	glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);

	glMaterialfv(GL_FRONT, GL_SPECULAR, specref);
	glMateriali(GL_FRONT, GL_SHININESS, 128);

}


GLuint LoadTextureToMemory(char* filePath)
{
	GLubyte *pBytes;
	GLint iWidth, iHeight, iComponents;
	GLenum eFormat;

	GLuint textureID;

	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	pBytes = (GLubyte *)gltLoadTGA(filePath, &iWidth, 
		&iHeight, &iComponents, &eFormat);

	glGenTextures( 1, &textureID );
	glBindTexture(GL_TEXTURE_2D, textureID);	

	glTexImage2D(GL_TEXTURE_2D,0,iComponents,iWidth,iHeight,0,eFormat,GL_UNSIGNED_BYTE,pBytes);

	//gluBuild2DMipmaps( GL_TEXTURE_2D, iComponents, iWidth, iHeight,
	//	eFormat, GL_UNSIGNED_BYTE, pBytes );

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	
	free(pBytes);

	return textureID;
}

void LoadTextures()
{
	glActiveTexture(GL_TEXTURE0);
	textureGround = LoadTextureToMemory("textures//moon//moon2.tga");

	GLuint textureLeftRobot = LoadTextureToMemory("textures//mosaic_5.tga");
	GLuint textuteRightRobot = LoadTextureToMemory("textures//mosaic_40.tga");

	GLuint textureLeftRobot_front = LoadTextureToMemory("textures//gra.tga");

	leftRobotTextureIDs[0] = textureLeftRobot_front;
	leftRobotTextureIDs[1] = -1;
	leftRobotTextureIDs[2] = textureLeftRobot;
	leftRobotTextureIDs[3] = textureLeftRobot_front;
	leftRobotTextureIDs[4] = -1;
	leftRobotTextureIDs[5] = textureLeftRobot;

	rightRobotTextureIDs[0] = -1;
	rightRobotTextureIDs[1] = -1;
	rightRobotTextureIDs[2] = textuteRightRobot;
	rightRobotTextureIDs[3] = -1;
	rightRobotTextureIDs[4] = -1;
	rightRobotTextureIDs[5] = textuteRightRobot;
}



void DrawGround()
{	
	glBindTexture( GL_TEXTURE_2D, textureGround);

	glEnable(GL_TEXTURE_2D);

	glBegin(GL_QUADS);		
	glColor3ub(255, 255, 255);

	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(-10.0f, 0.0f,  10.0f);
	glTexCoord2f(1.0f, 0.0f);
	glVertex3f( 10.0f, 0.0f,  10.0f);
	glTexCoord2f(1.0f, 1.0f);
	glVertex3f( 10.0f, 0.0f,  -10.0f);
	glTexCoord2f(0.0f, 1.0f);
	glVertex3f(-10.0f, 0.0f,  -10.0f);
	glEnd();

	glDisable(GL_TEXTURE_2D);
}

inline void DrawModels()
{
	glActiveTexture(GL_TEXTURE0);
	
	GLfloat progress = (GLfloat)current_time / (GLfloat)SCENE_DURATION;
	
	glPushMatrix();
		glTranslatef(0.0f, -body_Ysize / 2.0f - leg_Ysize, 4.5f);  // Поверхность
		DrawGround();
	glPopMatrix();

	// Robo left 
	glPushMatrix();
		glTranslatef(-2.8f, 0.0f, 0.0f);  // центр левого робота		
		draw_leftRobot(progress, leftRobotTextureIDs);
	glPopMatrix();

	//-----------------------------------------------------------

	// Robo right
	glPushMatrix();
		glTranslatef(2.8f, 0.0f, 0.0f);  // центр правого робота
		draw_rightRobot(progress, rightRobotTextureIDs);
	glPopMatrix();
	
	glActiveTexture(GL_TEXTURE1);
}

// Called to regenerate the shadow map
void RegenerateShadowMap(void)
{
	GLfloat lightModelview[16], lightProjection[16];

    glMatrixMode(GL_PROJECTION);
    glGetFloatv(GL_PROJECTION_MATRIX, lightProjection);

    // Switch to light's point of view
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(light0Pos[0], light0Pos[1], light0Pos[2], 
			0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
    glGetFloatv(GL_MODELVIEW_MATRIX, lightModelview);

    glViewport(0, 0, shadowSize, shadowSize);

    // Clear the window with current clearing color
    glClear(GL_DEPTH_BUFFER_BIT);

    // All we care about here is resulting depth values
    glShadeModel(GL_FLAT);
    glDisable(GL_LIGHTING);
    glDisable(GL_COLOR_MATERIAL);
    glDisable(GL_NORMALIZE);
    glColorMask(0, 0, 0, 0);

    // Overcome imprecision
    glEnable(GL_POLYGON_OFFSET_FILL);

    // Draw objects in the scene
    DrawModels();

    // Copy depth values into depth texture
    glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 
                     0, 0, shadowSize, shadowSize, 0);

    // Restore normal drawing state
    glShadeModel(GL_SMOOTH);
    glEnable(GL_LIGHTING);
    glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_NORMALIZE);
    glColorMask(1, 1, 1, 1);
    glDisable(GL_POLYGON_OFFSET_FILL);

    // Set up texture matrix for shadow map projection
    glMatrixMode(GL_TEXTURE);
    glLoadIdentity();

    glTranslatef(0.5f, 0.5f, 0.5f);
    glScalef(0.5f, 0.5f, 0.5f);

    glMultMatrixf(lightProjection);
    glMultMatrixf(lightModelview);
}


//*********************************************************
GLfloat sPlane[4] = {1.0f, 0.0f, 0.0f, 0.0f};
GLfloat tPlane[4] = {0.0f, 1.0f, 0.0f, 0.0f};
GLfloat rPlane[4] = {0.0f, 0.0f, 1.0f, 0.0f};
GLfloat qPlane[4] = {0.0f, 0.0f, 0.0f, 1.0f};

GLfloat ambientLight[] = { 0.7f, 0.7f, 0.7f, 1.0f};
GLfloat diffuseLight[] = { 0.4f, 0.4f, 0.4f, 1.0f};

GLfloat lowAmbient[4] = {0.1f, 0.1f, 0.1f, 1.0f};
GLfloat lowDiffuse[4] = {0.1f, 0.1f, 0.1f, 1.0f};
//*********************************************************


void RenderScene(void)
{
	RegenerateShadowMap();

	// Set Viewport to window dimensions
	glViewport(0, 0, windowWidth, windowHeight);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	

	GLfloat eyeX = radius * -sin(angleX + deltaAngleX) * cos(angleY + deltaAngleY);
	GLfloat eyeY = radius * -sin(angleY + deltaAngleY);
	GLfloat eyeZ = -radius * cos(angleX + deltaAngleX) * cos(angleY + deltaAngleY);

	gluLookAt(eyeX, eyeY, eyeZ, 0, 0, 0, 0, 1, 0);

	glPushMatrix();
		glTranslatef(light0Pos[0], light0Pos[1], light0Pos[2]);
		glColor3ub(255, 255, 40);
		gluSphere(gluNewQuadric(), 1.0, 60, 60);
	glPopMatrix();

	//------------------------------------------------------------------

	// Track light position
	glLightfv(GL_LIGHT0, GL_POSITION, light0Pos);
	
	// Because there is no support for an "ambient"
	// shadow compare fail value, we'll have to
	// draw an ambient pass first...
	glLightfv(GL_LIGHT0, GL_AMBIENT, lowAmbient);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, lowDiffuse);

	// Draw objects in the scene
	DrawModels();	

	glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);

	// Set up shadow comparison
	glEnable(GL_TEXTURE_2D);
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);

	// Enable alpha test so that shadowed fragments are discarded
	glAlphaFunc(GL_GREATER, 0.9f);
	glEnable(GL_ALPHA_TEST);	

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	// Set up the eye plane for projecting the shadow map on the scene
	glEnable(GL_TEXTURE_GEN_S);
	glEnable(GL_TEXTURE_GEN_T);
	glEnable(GL_TEXTURE_GEN_R);
	glEnable(GL_TEXTURE_GEN_Q);
	glTexGenfv(GL_S, GL_EYE_PLANE, sPlane);
	glTexGenfv(GL_T, GL_EYE_PLANE, tPlane);
	glTexGenfv(GL_R, GL_EYE_PLANE, rPlane);
	glTexGenfv(GL_Q, GL_EYE_PLANE, qPlane);

	// Draw objects in the scene
	DrawModels();

	glDisable(GL_ALPHA_TEST);
	glDisable(GL_TEXTURE_2D);
	glDisable(GL_TEXTURE_GEN_S);
	glDisable(GL_TEXTURE_GEN_T);
	glDisable(GL_TEXTURE_GEN_R);
	glDisable(GL_TEXTURE_GEN_Q);

	// Flush drawing commands
	glutSwapBuffers();
}

void keyboardFunc(int key, int x, int y) 
{
	switch (key)
	{

	case GLUT_KEY_LEFT:	// left arrow
		angleX -= 0.03f;
		break;

	case GLUT_KEY_RIGHT:	// right arrow
		angleX += 0.03f;
		break;

	case GLUT_KEY_UP:	// up arrow
		angleY += 0.03f;
		break;

	case GLUT_KEY_DOWN:	// down arrow
		angleY -= 0.03f;
		break;

	default:
		break;
	}
}

void keyboardFunc(unsigned char key, int x, int y) 
{
	switch (key)
	{
	case 27:     // Esc
		exit(0);
		break;

	case '+':
		radius += 0.1f;
		break;

	case '-':
		radius -= 0.1f;
		break;

	default:
		break;
	}
}


void mouseButton(int button, int state, int x, int y) 
{
	// only start motion if the left button is pressed
	if (button == GLUT_LEFT_BUTTON)
	{
		// when the button is released
		if (state == GLUT_UP) 
		{
			angleX += deltaAngleX;
			angleY += deltaAngleY;

			deltaAngleX = 0;
			deltaAngleY = 0;

			xOrigin = -1;
			yOrigin = -1;
		}
		else  
		{
			xOrigin = x;
			yOrigin = y;
		}
	}
}

void mouseMove(int x, int y)
{
	if (xOrigin >= 0) 
	{
		deltaAngleX = -(x - xOrigin) * 0.005f;
		deltaAngleY = -(y - yOrigin) * 0.005f;
	}
}

void mouseWheel(int button, int dir, int x, int y)
{
	if (dir > 0)	
		radius += 0.2f;	
	else	
		radius -= 0.2f;	
}


void reshapeFunc(int w, int h)
{
	GLint i;
    
    windowWidth = w;
    windowHeight = h;

	glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

	windowHeight = (windowHeight == 0) ? 1 : windowHeight;
	GLdouble aspect = static_cast<GLdouble>(windowWidth)/static_cast<GLdouble>(windowHeight);
	gluPerspective(35.0f, aspect, 0.01, 1900.0);
    
    // Find the largest power of two that will fit in window
    if (windowWidth > windowHeight)
        shadowSize = windowHeight;
    else
        shadowSize = windowWidth;
	
    // Try each size until we get one that's too big
    i = 0;
    while ((1 << i) <= shadowSize)
        i++;
    shadowSize = (1 << (i-1));
}

void timerFunc(int i) 
{
	static int time_delta = 1;

	current_time = i;

	if (i == SCENE_DURATION || i == -1) 
	{
		time_delta = -time_delta;
		//i = -1;
	}

	glutPostRedisplay();
	glutTimerFunc(TIME_INTERVAL_MS, timerFunc, i + time_delta);
}



void initGL()
{
	glActiveTexture = (PFNGLACTIVETEXTUREPROC)gltGetExtensionPointer("glActiveTexture");
	
	// Black background
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f );

    // Hidden surface removal
	glClearDepth(1.0f);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glPolygonOffset(4.0f, 0.0f);
	//glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); 

    // Set up some lighting state that never changes
    glShadeModel(GL_SMOOTH);
    glEnable(GL_LIGHTING);
    glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_NORMALIZE);
    glEnable(GL_LIGHT0);
	glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);

	GLuint shadowTextureID;

    // Set up some texture state that never changes
	glActiveTexture(GL_TEXTURE1);
    glGenTextures(1, &shadowTextureID);
    glBindTexture(GL_TEXTURE_2D, shadowTextureID);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY);

    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
   
	
	glutFullScreen();

	LoadTextures();
}


int main(int argc, char **argv) 
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_MULTISAMPLE);

	glutInitWindowSize(SCREEN_WIDTH, SCREEN_HEIGHT);
	glutCreateWindow("Fighting robots");

	glutInitWindowPosition(0, 0);
	glutDisplayFunc(RenderScene);
	glutReshapeFunc(reshapeFunc);
	glutKeyboardFunc(keyboardFunc);
	glutSpecialFunc(keyboardFunc);
	
	glutMouseFunc(mouseButton);
	glutMotionFunc(mouseMove);
	glutMouseWheelFunc(mouseWheel);

	initGL();
	glutTimerFunc(TIME_INTERVAL_MS, timerFunc, 0);
		
	glutMainLoop();
	return 0;
}
