#include <GL/glut.h>
#include "mat16fv.hpp"
#include "homoclip.hpp"

// FRUSTUM DEFINITION
float l=-0.25, r=0.25, b=-0.25, t=0.25, n=0.25, f=1; 

// POLYGON TO BE CLIPPED
float OrigPts[] = { 1,1,0,1, -1,1,0,1, -1,-1,0,1, 1,-1,0,1 };
float Pts[4*4];
int NumPts=4;

// MOUSE INTERFACE STUFF
int LeftButtonDown=0;        
int RightButtonDown=0;
int OldX,OldY,NewX,NewY;
float Elevation=0;
float Azimuth=0;

int AnimationOn=1;
int UseNDCspace=0;

void DrawAxes()
{
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
    glBegin(GL_LINES);
      glColor3f(1,0,0);
      glVertex3f(0,0,0); glVertex3f(1,0,0);
      glColor3f(0,1,0);
      glVertex3f(0,0,0); glVertex3f(0,1,0);
      glColor3f(0,0,1);
      glVertex3f(0,0,0); glVertex3f(0,0,1);
    glEnd();
  glPopMatrix();
}

void DrawFrustumInEyeSpace(float l, float r, float b, float t, float n, float f)
{
  float s=f/n;
  float fl=l*s, fr=r*s, fb=b*s, ft=t*s;
  glBegin(GL_LINE_LOOP);
    glVertex3f(r,t,-n);  glVertex3f(l,t,-n);  glVertex3f(l,b,-n);  glVertex3f(r,b,-n);
  glEnd();
  glBegin(GL_LINE_LOOP);
    glVertex3f(fr,ft,-f);  glVertex3f(fl,ft,-f);  glVertex3f(fl,fb,-f);  glVertex3f(fr,fb,-f);
  glEnd();
  glBegin(GL_LINES);
    glVertex3f(r,t,-n); glVertex3f(fr,ft,-f);
    glVertex3f(l,t,-n); glVertex3f(fl,ft,-f);
    glVertex3f(l,b,-n); glVertex3f(fl,fb,-f);
    glVertex3f(r,b,-n); glVertex3f(fr,fb,-f);
  glEnd();
}

void DrawPolygonInEyeSpace(float *Pts, int NumPts)
{
  glBegin(GL_LINE_LOOP);
    for (int i=0; i<NumPts*4; i+=4)
      glVertex4fv(&(Pts[i]));
  glEnd();
}

void DrawPolygonInNDCspace(float *Pts, int NumPts)
{
  float M[16];
  Frustum16fv(M, l,r,b,t,n,f);

  float tPt[4];
  glBegin(GL_LINE_LOOP);
    for (int i=0; i<(NumPts*4); i+=4)
    {
      Mult16fv4fv(tPt,M,&(Pts[i]));
      glVertex4fv(tPt);
    }
  glEnd();
}

void DrawClippedPolygon(float *Pts, int NumPts, int InEyeSpace=0)
{
  int i;
  float M[16];
  Frustum16fv(M, l,r,b,t,n,f);

  float *tPts = new float[NumPts*4];

  for (i=0; i<NumPts*4; i+=4)
    Mult16fv4fv(&(tPts[i]),M,&(Pts[i]));

  float *tPtsClipped;
  int NumClippedPts = ClipPolyToFrustum(tPts,NumPts,tPtsClipped);

  if (InEyeSpace) // HAVE TO XFORM BACK TO EYE SPACE
  {
    invFrustum16fv(M, l,r,b,t,n,f);
    for (i=0; i<NumClippedPts*4; i+=4)
      Mult16fv4fv(&(tPtsClipped[i]),M,&(tPtsClipped[i]));
  }

  glPushAttrib(GL_LINE_BIT);
  glLineWidth(3);
    glBegin(GL_LINE_LOOP);
      for (i=0; i<NumClippedPts*4; i+=4)
        glVertex4fv(&(tPtsClipped[i]));
    glEnd();
  glPopAttrib();

  delete[] tPts;
  delete[] tPtsClipped;
}

void AnimatePts(int UpdateOn=1)
{
  static float T[3]={0,0,-0.75f};  // TRANSLATION
  static float R[3]={0,0,0};      // EULER ANGLES
  static float dT[3]={0.01f,0.003f,0.007f};
  static float dR[3]={1,1,1};

  if (UpdateOn)
  {
    T[0]+=dT[0]; if (T[0]<l || T[0]>r) { dT[0]=-dT[0]; T[0]+=dT[0]; }
    T[1]+=dT[1]; if (T[1]<b || T[1]>t) { dT[1]=-dT[1]; T[1]+=dT[1]; }
    T[2]+=dT[2]; if (T[2]<-f || T[2]>-n) { dT[2]=-dT[2]; T[2]+=dT[2]; }
    for (int j=0; j<3; j++) { R[j]+=dR[j]; if (R[j]>360) R[j]-=360; }
  }

  float M[16], t[16], X[]={1,0,0}, Y[]={0,1,0}, Z[]={0,0,1};
  Translate16fv(M,T[0],T[1],T[2]);
  Rotate16fv(t,R[2],Z);
  Mult16fv(M,M,t);
  Rotate16fv(t,R[1],Y);
  Mult16fv(M,M,t);
  Rotate16fv(t,R[0],X);
  Mult16fv(M,M,t);
  for (int i=0; i<NumPts*4; i+=4)
    Mult16fv4fv(&(Pts[i]),M,&(OrigPts[i]));
}

void myDisplay()
{
  AnimatePts(AnimationOn);

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt(0,0,8, 0,0,0, 0,1,0);
  glRotatef(Elevation,1,0,0);
  glRotatef(Azimuth,0,1,0);

  if (!UseNDCspace) DrawAxes();

  if (UseNDCspace)
  {
    glPushMatrix();
    glScalef(1,1,-1);
      glColor3f(1,0,0);
      DrawPolygonInNDCspace(Pts,NumPts);
      glColor3f(0,0,1);
      DrawClippedPolygon(Pts,NumPts,0);
    glPopMatrix();
  }
  else 
  {
    glColor3f(1,0,0);
    DrawPolygonInEyeSpace(Pts,NumPts);
    glColor3f(0,0,1);
    DrawClippedPolygon(Pts,NumPts,1);
  }

  if (UseNDCspace) glFrustum(l,r,b,t,n,f);
  glColor3f(0,1,0);
  DrawFrustumInEyeSpace(l,r,b,t,n,f);

  glutSwapBuffers();
}

void myReshape(int w, int h)
{
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(30,(float)w/(float)h,0.1,100);
}

void myIdle()
{
  glutPostRedisplay();
}

void myMouseFunc(int button, int state, int x, int y)
{
  NewX=x; NewY=y;
  if (button==GLUT_LEFT_BUTTON)
    LeftButtonDown=!LeftButtonDown;
}

void myMotionFunc(int x, int y)
{
  OldX=NewX; OldY=NewY;
  NewX=x;    NewY=y;
  float RelX = (NewX-OldX) / (float)glutGet((GLenum)GLUT_WINDOW_WIDTH);
  float RelY = (NewY-OldY) / (float)glutGet((GLenum)GLUT_WINDOW_HEIGHT);
  if (LeftButtonDown) 
  { 
    Azimuth += (RelX*180);
    Elevation += (RelY*180);
  }
}

void myKeyboard(unsigned char Key, int x, int y)
{
  switch(Key)
  {
    case ' ': UseNDCspace=!UseNDCspace; break;
    case '\\': AnimationOn=!AnimationOn; break;
    case 27: exit(0);
  };
}

void main(void)
{
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutInitWindowSize(256,256);
  glutInitWindowPosition(180,100);
  glutCreateWindow("Polygon Clipping In Homogeneous Coordinates");

  glEnable(GL_DEPTH_TEST);

  glutDisplayFunc(myDisplay);
  glutReshapeFunc(myReshape);
  glutIdleFunc(myIdle);

  glutMouseFunc(myMouseFunc);
  glutMotionFunc(myMotionFunc);
  glutKeyboardFunc(myKeyboard);

  glutMainLoop();
}
