#include<GL/gl.h>
#include<GL/glu.h>
#include<GL/glut.h>
#include<stdlib.h>
#include<iostream.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#define maxVertices 1000

const int screenWidth = 640;
const int screenHeight = 480;

/*  POINT CLASSES  */

// a 2D point with integer coords
class GLintPoint{
public:
  GLint x,y;
};

// a 2D point with float coords
class GLfloatPoint{
public:
  GLfloat x,y;
};


/*  VECTOR CLASSES  */

// a two-vector
class Vector2 {
public:
  Vector2() { x = y = 0.0f; origin = 0; }
  Vector2(float xx, float yy) { x = xx; y = yy; }
  void set(float xx, float yy) { x = xx; y = yy; }
  void setX(float xx) { x = xx; }
  void setY(float yy) { y = yy; }
  float getX() { return x; }
  float getY() { return y; }
  Vector2 scale(float k) {
    Vector2 result;
    result.set(k * x, k * y);
    return result;
  }
  Vector2 perp() {
    Vector2 result;
    result.set(- y, x);
    return result;
  }
  // not tested yet!
  float perpdot(Vector2 v) {
    return ( x * v.getY() - y * v.getX() );
  }
  Vector2 normalize() {
    Vector2 result;
    double vectorLength = x*x + y*y;
    if (vectorLength < 0.000000001)
      {
	cerr << "\nnormalize() in vector.cxx is about to divide by zero\n";
      }
    float scaleFactor = 1.0/(float)sqrt(vectorLength);
    result.set(x * scaleFactor, y * scaleFactor);
    return result;
  }
private:
	float x, y, origin;
};

/////////////
// Vector3
/////////////
class Vector3 {
public:
  Vector3() { x = y = z = 0.0f; origin = 0; }
  Vector3(float xx, float yy, float zz) { x = xx; y = yy; z = zz; }
  void set(float xx, float yy, float zz) { x = xx; y = yy; z = zz; }
  float getX() { return x; }
  float getY() { return y; }
  float getZ() { return z; }
  Vector3 scale(float k) {
    Vector3 result;
    result.set(k * x, k * y, k * z);
    return result;
  }
  Vector3 perp() {
    Vector3 result;
    result.set(- y, x, z);
    return result;
  }
  Vector3 normalize() {
    Vector3 result;
    double vectorLength = x*x + y*y + z*z;
    if (vectorLength < 0.000000001)
      {
	cerr << "\nnormalize() in vector.cxx is about to divide by zero\n";
      }
    float scaleFactor = 1.0/(float)sqrt(vectorLength);
    result.set(x * scaleFactor, y * scaleFactor, z * scaleFactor);
    return result;
  }
private:
  float x, y, z;
  int origin;

};

//--------------------------------------------
// functions that do useful stuff with vectors
//--------------------------------------------

Vector2 addVector2s (Vector2 a, Vector2 b)
{
  Vector2 result;
  result.set(a.getX() + b. getX(), a.getY() + b. getY());
  return result;
}

Vector3 addVector3s (Vector3 a, Vector3 b)
{
   Vector3 result;
   result.set(a.getX() + b. getX(), 
	      a.getY() + b. getY(), 
	      a.getZ() + b. getZ());
   return result;
}

float dotVector3s (Vector3 a, Vector3 b)
{
  return ( (a.getX() * b.getX()) +
	   (a.getY() * b.getY()) +
	   (a.getZ() * b.getZ()) );
}

float dotVector2s (Vector2 a, Vector2 b)
{
  return ( (a.getX() * b.getX()) +
	   (a.getY() * b.getY()));
}

Vector3 crossVector3s (Vector3 a, Vector3 b)
{
  Vector3 result;
  result.set(a.getY()*b.getZ() - b.getY()*a.getZ(),
	     b.getX()*a.getZ() - a.getX()*b.getZ(),
	     a.getX()*b.getY() - b.getX()*a.getY());
  return result;
}

/*
2D TRANSFORMATION MATRIX CLASS
*/

class TwoDXformMatrix {
public:
  TwoDXformMatrix() {
    for (int i=0; i<3; i++)
      {
	for (int j=0; j<3; j++)
	  {
	    XformArray[i][j]=0.0;
	  }
      }
  }
  TwoDXformMatrix(float m11, float m12, float m13,
		float m21, float m22, float m23)
  {
    XformArray[0][0] = m11;
    XformArray[0][1] = m12;
    XformArray[0][2] = m13;
    XformArray[1][0] = m21;
    XformArray[1][1] = m22;
    XformArray[1][2] = m23;
    XformArray[2][0] = XformArray[2][1] = 0;  
    XformArray[2][2] = 1;
  }
  void setElement(int row, int col, float val) { XformArray[row][col] = val; }
  Vector3 getRow(int row) 
  { 
    Vector3 result;
    result.set ( XformArray[row][0], XformArray[row][1], XformArray[row][2] );
    return result; 
  }
  Vector3 getCol(int col) 
  { 
    Vector3 result;
    result.set ( XformArray[0][col], XformArray[1][col], XformArray[2][col] );
    return result; 
  }
  float getElement(int i, int j)  { return XformArray[i][j]; }

private:
  float XformArray[3][3];
};

//
// drawBox()
//
// Draw a small box around a point
//
void drawBox(int x, int y)
{
	glColor3f(1.0, 0.0, 0.0);
	glBegin(GL_LINE_LOOP);
		glVertex2i(x-1, screenHeight - (y+1));
		glVertex2i(x-1, screenHeight - (y-1));
		glVertex2i(x+1, screenHeight - (y-1));
		glVertex2i(x+1, screenHeight - (y+1));
		glVertex2i(x-1, screenHeight - (y+1));
	glEnd();
	glFlush();
}

//
// drawPolygon()
//
// Given a set of vertices, draw lines between them.
//
void drawPolygon(int numVertices, GLintPoint vertices[])
{
	glColor3f(0.0, 0.0, 1.0);
	glBegin(GL_LINE_LOOP);
	  for(int i = 0; i < numVertices; i++) 
		glVertex2i(vertices[i].x, vertices[i].y);
	glEnd();
	glFlush();
}


//
// transform2D()
//
// Take a vertex, apply a transform to it, and return it.
//
GLintPoint transform2D(GLintPoint vertex, TwoDXformMatrix Frob)
{
	GLintPoint frobbedVertex;

	//
	// Hardcoded matrix-multiply.  
	// We use rint() to round, rather than just casting.
	//
	frobbedVertex.x = (int)rint(Frob.getRow(0).getX()*vertex.x) +
	                  (int)rint(Frob.getRow(0).getY()*vertex.y) +
	                  (int)rint(Frob.getRow(0).getZ());
	frobbedVertex.y = (int)rint(Frob.getRow(1).getX()*vertex.x) +
	                  (int)rint(Frob.getRow(1).getY()*vertex.y) +
	                  (int)rint(Frob.getRow(1).getZ());

	return frobbedVertex;
}


//
// drawTransformedPolygon()
//
// Given a set of vertices and a TwoDXformMatrix, draw the
// transformed polygon
//
void drawTransformedPolygon(int numVertices, GLintPoint vertices[],
			    TwoDXformMatrix Frob)
{
	GLintPoint frobbedVertices[maxVertices];
	for(int i = 0; i < numVertices; i++) { 
		frobbedVertices[i] = transform2D(vertices[i], Frob); 
	}
	glColor3f(1.0, 0.0, 0.0);
	glBegin(GL_LINE_LOOP);
		for(int i = 0; i < numVertices; i++) 
			glVertex2i(frobbedVertices[i].x, frobbedVertices[i].y);
	glEnd();
	glFlush();
}

//
// myMouse()
//
// Mouse event handler.  Capture left-button keypresses up to and until
// a right-button event happens; then draw them as a polygon: both
// in their original form and transformed by the matrix Frob
//
void myMouse(int button, int state, int x, int y)
{
	static GLintPoint vertices[maxVertices];
	static int numVertices = 0;

	// declare and initialize transform mtx 
        // this one shrinks both coords of each vertex by a factor of 3
	TwoDXformMatrix Frob(0.33, 0, 0, 0, 0.33, 0);

	if ((button == GLUT_LEFT_BUTTON) && (state == GLUT_DOWN)) {
	  printf("vertex entered: %d,%d \n", x, screenHeight - y); 
	  fflush(stdout);
	  vertices[numVertices].x = x;
	  vertices[numVertices].y = screenHeight - y;
	  numVertices++;
	  drawBox(x, y);
	} 
	else if ((button == GLUT_RIGHT_BUTTON) && (state == GLUT_DOWN)) {
	  drawPolygon(numVertices, vertices);
	  drawTransformedPolygon(numVertices, vertices, Frob);
	  numVertices = 0;
	}
}

void display(void)
{
	glClear(GL_COLOR_BUFFER_BIT);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(0.0, (GLfloat)screenWidth, 0.0, (GLfloat)screenHeight);

}

int main(int argc, char** argv)
{
        glutInit(&argc,argv);
	glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);  
	glutInitWindowSize(screenWidth, screenHeight);
	glutInitWindowPosition(0, 0);
	glutCreateWindow("transformations");
        glClearColor(1.0, 1.0, 1.0, 0.0);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(0.0, (GLfloat)screenWidth, 0.0,  (GLfloat) screenHeight);
	glutDisplayFunc(display);
	glutMouseFunc(myMouse);
	glutMainLoop();
}

