//Space Invaders.cpp
//		Fun with Space Invaders sprites
//
//This code was written with much help from NeHe's tutorials (http://nehe.gamedev.net)
//		and is not intended for commercial use.
//
//		-Jason Blatt

#include <gl\glut.h>
#include <gl\gl.h>
#include <gl\glu.h>
#include <gl\glaux.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <windows.h>


const int WINX = 1280, WINY = 1024;  //Window Dimensions

const int numTextures = 6;  //Texture Maps
GLuint	texture[numTextures];

int frame=0,currTime,timeBase=0;  //Timing
float fps=0;

typedef struct {
	int active;
	int face;
	int path;
	int killed;
	float x;
	float y;
	float z;
	float size;
	float stats[10];
		//0 = Rotation
		//1 = Y-offset
} Alien ;


const int numAliens = 32;
Alien aliens[numAliens];


typedef struct {
	int check;
	int x, y;
} Shot ;

Shot nextShot;


const int numStars = 1000;
float Starfield[7][numStars];
float starRot = 0, cameraRot = 0, starSpeed = 4.0, spinSpeed = 0;
int colored = 0, cameraMove = 1;


typedef struct {
	int life, active, trigger;
	float xyz[3];
	float dXYZ[3];
	float colorRGB[3];
	float alpha;
	float size;
	float rot;
} Particle;

const int partsPerAlien = 50 , numParticles = numAliens * partsPerAlien;
Particle particles[numParticles];  //Number of explosion particles per alien

typedef struct {
	int paths;
	int numAliens[3];
	float startX[3];
	float startY[3];
	float startZ[3];
	int pathFunc[3];
	float stats[10];
} Wave;

Wave waveOne;





////
//Alien Drawing
////

void drawAlien(int id)
{
	glTranslatef(aliens[id].x,aliens[id].y,aliens[id].z);
	glColor4f(1,1,1,1);
	glBindTexture(GL_TEXTURE_2D, texture[aliens[id].face]);
	glBegin(GL_QUADS);
		glTexCoord2f(0.0f, 0.0f); glVertex3f(-aliens[id].size, -aliens[id].size,  0.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex3f( aliens[id].size , -aliens[id].size,  0.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex3f( aliens[id].size,  aliens[id].size,  0.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex3f(-aliens[id].size,  aliens[id].size,  0.0f);
	glEnd();
	glTranslatef(-aliens[id].x,-aliens[id].y,-aliens[id].z);
}

void updateLocation(int id)
{
	if(aliens[id].path == 0) {
		aliens[id].x += .15;
		aliens[id].y = sin(aliens[id].stats[0]);

		aliens[id].stats[0] += .10;
		if(aliens[id].x >= 12) aliens[id].x = -12;
		
	}

	else if(aliens[id].path == 1) {
		aliens[id].x -= .15;
		aliens[id].y = aliens[id].stats[1] + (sin(aliens[id].stats[0]));

		aliens[id].stats[0] += .10;
		if(aliens[id].x <= -20) aliens[id].x = 20;
		
	}

	

}




////
//Collision Detection
////

void checkShot(void) {

	nextShot.check = 0;
	float newX;
	float newY;

	for(int a = 0 ; a < numAliens ; a++) {
		if(aliens[a].active == 1) {
			newX = (float)nextShot.x - WINX/2;
			newY = (float)-nextShot.y + WINY/2;

			if(aliens[a].path == 0) {
				newX = (newX*12.80)/(float)(WINX/2);
				newY = (newY*10.24)/(float)(WINY/2);
			}
			else if(aliens[a].path == 1) {
				newX = (newX*20)/(float)(WINX/2);
				newY = (newY*16)/(float)(WINY/2);
			}



			if(aliens[a].x - 1 <= newX && newX <= aliens[a].x + 1) {
				if(aliens[a].y - 1 <= newY && newY <= aliens[a].y + 1) {
					aliens[a].killed = 1;
					aliens[a].active = 0;
				}
			}
			




		}

		if(aliens[a].killed == 1) {
			aliens[a].killed = 0;
			int count = partsPerAlien;
			for(int b = 0 ; b < numParticles ; b++) {
				if(count > 0 && particles[b].active != 1) {
					particles[b].trigger = 1;
					particles[b].xyz[0] = newX;
					particles[b].xyz[1] = newY;
					particles[b].xyz[2] = aliens[a].z;
					count--;
				}
			}
		}


	}


}





////
//Particle Drawing
////

void drawParticles(void) {

	for(int c = 0 ; c < numParticles ; c++) { 
		if(particles[c].trigger == 1) {
			particles[c].trigger = 0;
			particles[c].active = 1;
			particles[c].life = (int)(rand())/10;
			particles[c].alpha = .8;
			particles[c].size = 2*(rand() / (float)RAND_MAX);
			particles[c].rot = 3.14*(rand() / (float)RAND_MAX);
			particles[c].colorRGB[0] = rand() / (float)RAND_MAX;
			particles[c].colorRGB[1] = rand() / (float)RAND_MAX;
			particles[c].colorRGB[2] = rand() / (float)RAND_MAX;
			particles[c].dXYZ[0] = 2*(rand() / (float)RAND_MAX) - (rand() / (float)RAND_MAX);
			particles[c].dXYZ[1] = 2*(rand() / (float)RAND_MAX) - (rand() / (float)RAND_MAX);
			particles[c].dXYZ[2] = 3*(rand() / (float)RAND_MAX) - 1.5*(rand() / (float)RAND_MAX);
		}
		

		if(particles[c].active == 1) {
			
			glTranslatef(particles[c].xyz[0], particles[c].xyz[1], particles[c].xyz[2]);
			glColor4f(particles[c].colorRGB[0], particles[c].colorRGB[1], particles[c].colorRGB[2], particles[c].alpha);
			glRotatef(particles[c].rot,0,0,1);

			glBindTexture(GL_TEXTURE_2D, texture[5]);
			glBegin(GL_QUADS);
				glTexCoord2f(0.0f, 0.0f); glVertex3f(-particles[c].size, -particles[c].size,  0.0f);
				glTexCoord2f(1.0f, 0.0f); glVertex3f( particles[c].size, -particles[c].size,  0.0f);
				glTexCoord2f(1.0f, 1.0f); glVertex3f( particles[c].size,  particles[c].size,  0.0f);
				glTexCoord2f(0.0f, 1.0f); glVertex3f(-particles[c].size,  particles[c].size,  0.0f);
			glEnd();	

			if((rand() / (float)RAND_MAX) > .75) {
				glColor4f(1,1,1,1);
				glBegin(GL_QUADS);
					glTexCoord2f(0.0f, 0.0f); glVertex3f(-particles[c].size, -particles[c].size,  0.0f);
					glTexCoord2f(1.0f, 0.0f); glVertex3f( particles[c].size, -particles[c].size,  0.0f);
					glTexCoord2f(1.0f, 1.0f); glVertex3f( particles[c].size,  particles[c].size,  0.0f);
					glTexCoord2f(0.0f, 1.0f); glVertex3f(-particles[c].size,  particles[c].size,  0.0f);
				glEnd();
			}

			glRotatef(-particles[c].rot,0,0,1);
			glTranslatef(-particles[c].xyz[0], -particles[c].xyz[1], -particles[c].xyz[2]);
		
			particles[c].xyz[0] += (particles[c].dXYZ[0] + (rand() / (float)RAND_MAX*.025) - (rand() / (float)RAND_MAX*.05));
			particles[c].xyz[1] += (particles[c].dXYZ[1] + (rand() / (float)RAND_MAX*.025) - (rand() / (float)RAND_MAX*.05));
			particles[c].xyz[2] += (particles[c].dXYZ[2] + (rand() / (float)RAND_MAX*.025) - (rand() / (float)RAND_MAX*.05));
			
			if(particles[c].dXYZ[0] > .2) particles[c].dXYZ[0] -= .2;
			if(particles[c].dXYZ[1] > .2) particles[c].dXYZ[1] -= .2;
			if(particles[c].dXYZ[0] < -.2) particles[c].dXYZ[0] += .2;
			if(particles[c].dXYZ[1] < -.2) particles[c].dXYZ[1] += .2;

			particles[c].alpha -= (rand() / (float)RAND_MAX)/50;
			particles[c].rot -= 10;//32 - 5*(rand() / (float)RAND_MAX);

		}

		particles[c].life-=2;
		if(particles[c].life <= 0) particles[c].active = 0;
		if(particles[c].xyz[2] < -50) particles[c].active = 0;

	}
}







////
//Starfield Drawing
////

void drawStars(void) {

	for(int i = 0 ; i < numStars ; i ++) {
		glLoadIdentity();

		if(cameraMove == 1) glRotatef(cameraRot,0,0,1);
		glTranslatef(Starfield[0][i] , Starfield[1][i] , Starfield[2][i]);
		if(cameraMove == 1) glRotatef(-cameraRot,0,0,1);

		//glRotatef(starRot,0,0,1);

		glColor4f(Starfield[3][i],Starfield[4][i],Starfield[5][i],Starfield[6][i]);

		glBindTexture(GL_TEXTURE_2D, texture[4]);
		glBegin(GL_QUADS);
			glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  0.0f);
			glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  0.0f);
			glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  0.0f);
			glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  0.0f);
		glEnd();

		glTranslatef(-Starfield[0][i] , -Starfield[1][i] , -Starfield[2][i]);

		Starfield[2][i]+= starSpeed;
		if(Starfield[2][i] >= 10) {
			Starfield[2][i] = -1000;
			Starfield[6][i] = 0;
		}
		if(Starfield[6][i] < 1) {
			if(starSpeed < 10) Starfield[6][i]+=(starSpeed/(starSpeed*100));
			else if(starSpeed < 25) Starfield[6][i]+=(starSpeed/(starSpeed*50));
			else Starfield[6][i]+=(starSpeed/(starSpeed*20));
		}
 		starRot-=.002;
	}

	cameraRot += spinSpeed;
	

}








////
//Text Drawing Utility Functions
////

void drawString(float x, float y, float z, void *font, char *string) {  
  char *c;
  glRasterPos3f(x,y,z);
  for (c=string; *c != '\0'; c++) {
    glutBitmapCharacter(font, *c);
  }
}

void setOrthographicProjection() {

	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	gluOrtho2D(0, WINX, 0, WINY);
	glScalef(1, -1, 1);
	glTranslatef(0, -WINY, 0);
	glMatrixMode(GL_MODELVIEW);
}

void resetPerpectiveProjection() {
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
}








////
//Main Draw
////

void draw(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	
	glPushMatrix();
	glLoadIdentity();									
	
	srand((unsigned int)time((time_t *)NULL));

	//Draw Stars
	drawStars();
	
	//Check For Kill
	if(nextShot.check == 1) checkShot();

	//Draw Aliens
	glLoadIdentity();
	glTranslatef(0.0f,0.0f,-20.0f);
	for(int i = 0 ; i < numAliens ; i++) {
		if(aliens[i].active == 1) drawAlien(i);
	}

	//Draw Particles
	glLoadIdentity();
	glTranslatef(0.0f,0.0f,-20.0f);
	drawParticles();

	//Update Locations
	for(int j = 0 ; j < numAliens ; j++) {
		if(aliens[i].active == 1) updateLocation(j);
	}

	

	



	glPopMatrix();
	glutSwapBuffers();
}









////
//Texture Mapping Functions
////

AUX_RGBImageRec *LoadBMP(char *Filename)				
{
	FILE *File=NULL;									

	if (!Filename)										
	{
		return NULL;									
	}

	File=fopen(Filename,"r");							

	if (File)											
	{
		fclose(File);									
		return auxDIBImageLoad(Filename);				
	}

	return NULL;										
}

int LoadGLTextures()								
{
	GLuint loop;
	int Status=FALSE;							
	AUX_RGBImageRec *TextureImage[numTextures];					
	memset(TextureImage,0,sizeof(void *)*numTextures);				

	if ((TextureImage[0]=LoadBMP("Data/alien1.bmp")) &&			
	    (TextureImage[1]=LoadBMP("Data/alien2.bmp")) &&			
	    (TextureImage[2]=LoadBMP("Data/alien3.bmp")) &&			
	    (TextureImage[3]=LoadBMP("Data/alien4.bmp")) &&
		(TextureImage[4]=LoadBMP("Data/star1.bmp")) &&
		(TextureImage[5]=LoadBMP("Data/star2.bmp")))		
	{
		Status=TRUE;							
		glGenTextures(numTextures, &texture[0]);					

		for (loop=0; loop<numTextures; loop++)					
		{
			glBindTexture(GL_TEXTURE_2D, texture[loop]);
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
			glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY,
				0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
		}
	}
	for (loop=0; loop<numTextures; loop++)						
	{
		if (TextureImage[loop])						
		{
			if (TextureImage[loop]->data)				
			{
				free(TextureImage[loop]->data);			
			}
			free(TextureImage[loop]);				
		}
	}
	return Status;								
}





////
//Resize window function
////

void changeSize(int w, int h)  
{
	if(h == 0) h = 1;

	double ratio = 1.0* w / h;

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	
	glViewport(0, 0, w, h);

	gluPerspective(45,ratio,1,1000);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(0.0,0.0,5.0, 
		      0.0,0.0,-1.0,
			  0.0f,1.0f,0.0f);
}







////
//Process keys
////

void processNormalKeys(unsigned char key, int x, int y)
{
	if (key == 27) exit(0);
}

void processSpecialKeys(int key, int x, int y)
{	

	if(key == GLUT_KEY_UP) {
		starSpeed+=.25;
	}
	if(key == GLUT_KEY_DOWN) {
		//if(starSpeed > .5) starSpeed-=.25;
		starSpeed-=.25;
	}

	if(key == GLUT_KEY_LEFT) {
		spinSpeed+=.05;
	}
	if(key == GLUT_KEY_RIGHT) {
		spinSpeed-=.05;
	}

}






////
//Process Mouse
////

void processMouse(int button, int state, int x, int y) {

	if(state == GLUT_DOWN) {
		nextShot.x = x;
		nextShot.y = y;
		nextShot.check = 1;
	}

}






////
//Get Next Alien
////

int nextAlien(void) {
	int e = 0;
	while(e < numAliens) {
		if(aliens[e].active == 0) return e;
		e++;
	}
	return -1;
}
	





 

////
//Init
////

void main(int argc, char **argv)
{
    glutInit(&argc, argv);
	glutInitWindowPosition(0,0);
    glutInitWindowSize(WINX,WINY);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
    int mainwin = glutCreateWindow("Space Invaders");

    glutDisplayFunc(draw);
	glutIdleFunc(draw);
	glutReshapeFunc(changeSize);
	glutKeyboardFunc(processNormalKeys);
	glutSpecialFunc(processSpecialKeys);
	glutMouseFunc(processMouse);

	glBlendFunc(GL_SRC_ALPHA,GL_ONE);
	glEnable(GL_BLEND);
	//glEnable(GL_DEPTH_TEST);

	glEnable(GL_TEXTURE_2D);							
	glShadeModel(GL_SMOOTH);
	LoadGLTextures();

	glutFullScreen();

	currTime = glutGet(GLUT_ELAPSED_TIME);

	waveOne.paths = 3;
	waveOne.numAliens[0] = 12;
	waveOne.numAliens[1] = 8;
	waveOne.numAliens[2] = 12;
	waveOne.startX[0] = 20;
	waveOne.startX[1] = -12;
	waveOne.startX[2] = 20;
	waveOne.startY[0] = 7.5;
	waveOne.startY[1] = 0;
	waveOne.startY[2] = -7.5;
	waveOne.startZ[0] = -13.33333;
	waveOne.startZ[1] = 0;
	waveOne.startZ[2] = -13.33333;
	waveOne.pathFunc[0] = 1;
	waveOne.pathFunc[1] = 0;
	waveOne.pathFunc[2] = 1;
	waveOne.stats[0] = 0;


	float thisRot = waveOne.stats[0];
	for(int d = 0 ; d < waveOne.paths ; d++) {
		float thisStartX = waveOne.startX[d];
		for(int k = 0 ; k < waveOne.numAliens[d] ; k++) {
			int tempNum = nextAlien();
			aliens[tempNum].active = 1;
			aliens[tempNum].face = k%4;
			aliens[tempNum].path=waveOne.pathFunc[d];
			aliens[tempNum].killed=0;
			aliens[tempNum].x = thisStartX;
			aliens[tempNum].y = waveOne.startY[d];
			aliens[tempNum].z = waveOne.startZ[d];
			aliens[tempNum].size = 1;
			aliens[tempNum].stats[0] = thisRot;
			aliens[tempNum].stats[1] = waveOne.startY[d];

			if(waveOne.startX[d] < 0) thisStartX += 3;
			else if(waveOne.startX[d] > 0) thisStartX -= 3.333333;
			thisRot += 5;
		}
	}

 
	

	nextShot.check = 0;

	//Init Starfield
	srand((unsigned int)time((time_t *)NULL));
	for(int j = 0 ; j < numStars ; j++) {
		Starfield[0][j] = 250 - (rand() % 501);
		Starfield[1][j] = 250 - (rand() % 501);
		Starfield[2][j] = -(rand() % 1000);
		if(colored == 1) {
			Starfield[3][j] = rand()/(float)RAND_MAX;
			Starfield[4][j] = rand()/(float)RAND_MAX;
			Starfield[5][j] = rand()/(float)RAND_MAX;
		}
		else {
			Starfield[3][j] = 1;
			Starfield[4][j] = 1;
			Starfield[5][j] = 1;
		}
		Starfield[6][j] = 1;
	}




    glutMainLoop();
}