#include <GL/glut.h>
#include <stdlib.h>
#include <vector>
#include <math.h>
#include <stdio.h>

#define PI 3.1415926535

using namespace std;

struct Point {
	float x;
	float y;
	float z;
}; // end Point

float xRot=0, yRot=0, zRot=0; // rotation for viewing
vector<vector<Point> > surface; // points on the surface
char* filename = NULL;

// returns a psuedo random between -1 and 1
float getRand()
{
	return (float)rand() / (float)RAND_MAX * 2.0f - 1.0f;
}

void saveSurface(char* filename)
{
	// check that we have a surface
	if (surface.size() <= 0)
	{
		printf("No surface to save\n");
		return;
	} // end if

	FILE *fp;
	if ((fp = fopen(filename, "w"))==NULL)
	{
		printf("Cannot open file %s for writing.\n",filename);
		return;
	} // end if

	// first write the dimensions of the surface
	fprintf(fp,"%i %i\n",surface.size(),surface[0].size());

	// then write the surface points
	for (int i=0; i<surface.size(); i++)
	{
		for (int j=0; j<surface.size(); j++)
			fprintf(fp, "%1.5f %1.5f %1.5f ",surface[i][j].x, surface[i][j].y, surface[i][j].z);
		fprintf(fp, "\n");
	} // end for i

	// close the file
	fclose(fp);

	printf("File %s was saved.\n",filename);
} // end save surface

void loadSurface(char* filename)
{
	// test for bad filename
	if (filename == NULL)
		return;

	// remove the current surface
	surface.clear();

	FILE *fp;
	if ((fp = fopen(filename, "r")) == NULL)
	{
		printf("Cannot open file %s for reading.\n",filename);
		return;
	} // end if

	// first get the dimensions of the surface
	int width=0, height=0;
	fscanf(fp,"%i %i\n",&width,&height);

	// now read in the surface data (and resize surface)
	surface.resize(width);
	for (int i=0; i<width; i++)
	{
		surface[i].resize(height);
		for (int j=0; j<height; j++)
			fscanf(fp,"%f %f %f",&(surface[i][j].x), &(surface[i][j].y), &(surface[i][j].z));
		fscanf(fp,"\n");
	} // end for i

	fclose(fp);
}

// draw function
void drawGL()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glLoadIdentity();

  glColor3f(1,0,0);

  // set up the camera
  gluLookAt(0,0,-15,0,0,0,0,1,0);
  glRotatef(xRot,1,0,0);
  glRotatef(yRot,0,1,0);
  glRotatef(zRot,0,0,1);

  // render the surface as a point cloud
  glBegin(GL_POINTS);
  for (int i=0; i<surface.size(); i++)
	  for (int j=0; j<surface.size(); j++)
		  glVertex3f(surface[i][j].x, surface[i][j].y, surface[i][j].z);
  glEnd();

  glFlush();
  glutSwapBuffers();
}

// OpenGL initialization
void initializeGL()
{
  glClearColor( 0.0, 0.0, 0.0, 0.0 ); // Let OpenGL clear to black
  glEnable( GL_DEPTH_TEST ); 
  glShadeModel( GL_SMOOTH ); // we want smooth shading
}

// resets the projection when window resized
void resizeGL( int w, int h )
{
  glViewport(0, 0, (GLint)w, (GLint)h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  // this preserves aspect ratio
  if(w>h)
	glOrtho(-30.0f * (float)w/(float)h, 30.0f * (float)w/(float)h, -30, 30, -1000, 1000);
  else
    glOrtho(-30, 30, -30.0f * (float)w/(float)h, 30.0f * (float)w/(float)h, -1000, 1000);

  glMatrixMode( GL_MODELVIEW );
}

// Creates a torus
// u parameter moves around circumfrence of torus, v around torus cross section
void generateTorus(int numUPoints, int numVPoints, bool randHeights)
{
	// get rid of any existing surface
	surface.clear();

	// r1 is radius of torus, r2 is radius of cross section
	const float r1 = 10.0f;
	const float r2 = 3.0f;
	const float roughness = 1.5f;

	surface.resize(numUPoints);
	for (int i=0; i<numUPoints; i++)
	{
		surface[i].resize(numVPoints);
		float u = (float)i / (float)(numUPoints) * 2.0f * PI;
		for (int j=0; j<numVPoints; j++)
		{
			float v = (float)j / (float)(numVPoints) * 2.0f * PI;
			Point p;
			float r = 0.0f;
			if (randHeights)
				r = getRand() * roughness;
			p.x = (r1 + (r2 + r) * cos(v)) * cos(u);
			p.y = (r2 + r) * sin(v);
			p.z = (r1 + (r2 + r)* cos(v)) * sin(u);
			surface[i][j] = p;
		} // end for j
	} // end for i

	filename = "torus";
} // end generate taurus

// Creates a plane
// u parameter controls x direction, v the y direction
void generatePlane(int numUPoints, int numVPoints, bool randHeights)
{
	// get rid of any existing surface
	surface.clear();

	// size of plane in u and v directions
	const float uExtent = 20.0f;
	const float vExtent = 20.0f;
	const float roughness = 2.0f;

	surface.resize(numUPoints);
	for (int i=0; i<numUPoints; i++)
	{
		surface[i].resize(numVPoints);
		float u = (float)i / (float)(numUPoints-1);
		for (int j=0; j<numVPoints; j++)
		{
			float v = (float)j / (float)(numVPoints-1);

			Point p;
			p.x = u * uExtent - .5 * uExtent;
			// if random heights we will modify y coord in plane
			if (randHeights)
				p.y = getRand() * roughness;
			else
				p.y = 0;
			p.z = v * vExtent - .5 * vExtent;
			surface[i][j] = p;
		} // end for j
	} // end for i

	filename = "plane";
} // end generate plane

// keyboard key mapping
void keyPress(unsigned char key, int x, int y)
{
	const float speed = 3.3f;

	switch (key) {
		case 't':
			generateTorus(10,10,false);
			printf("Flat Torus Generated\n");
			break;
		case 'y':
			generateTorus(10,10,true);
			printf("Rough Torus Generated\n");
			break;
		case 'p':
			generatePlane(10,10,false);
			printf("Flat Plane Generated\n");
			break;
		case 'o':
			generatePlane(10,10,true);
			printf("Rough Plane Generated\n");
			break;
		case 'v':
			// save the current surface
			if (filename != NULL)
				saveSurface(filename);
			break;
		case 'c':
			// load the surface (looks for torus if current surface is a torus & same for plane)
			if (filename != NULL)
				loadSurface(filename);
			break;
		case 'w':
			xRot += speed;
			break;
		case 's':
			xRot -= speed;
			break;
		case 'a':
			yRot -= speed;
			break;
		case 'd':
			yRot += speed;
			break;
		case 'q':
			zRot -= speed;
			break;
		case 'e':
			zRot += speed;
			break;
		default:
		break;
  } // end switch
  glutPostRedisplay();
}

// main function that makes things happen
int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
   glutInitWindowSize(500, 500);
   glutCreateWindow("Simple Surfaces"); // set the window's title
   initializeGL();
   glutDisplayFunc(drawGL);
   glutReshapeFunc(resizeGL);
   glutKeyboardFunc(keyPress);
   glutMainLoop();
   return 0;
}   

