#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <GL/glut.h>
#include "lens.h"

/*
   Play with nonlinear distortion in OpenGL
*/

/* Flags */
int debug = FALSE;
int currentbutton = -1;
double rotatespeed = 1;
double dtheta = 1;
CAMERA camera;
XYZ origin = {0.0,0.0,0.0};

PIXELA *thetex;

#define NONE     1
#define SINEXY   2
#define SINER    3
#define SQUAREXY 4
#define SQUARER  5
#define ASINXY   6
#define ASINR    7
int distortion = SQUAREXY;

#define MESH   1
#define PULSAR 2
int modeltype = PULSAR;

int main(int argc,char **argv)
{
   int i;
   int mainmenu,modelmenu,distortmenu,aperturemenu;

   /* Parse the command line arguments */
   for (i=1;i<argc;i++) {
      if (strstr(argv[i],"-h") != NULL) 
         GiveUsage(argv[0]);
      if (strstr(argv[i],"-d") != NULL)
         debug = TRUE;
   }

   /* Set things up and go */
   glutInit(&argc,argv);
   glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);

   glutCreateWindow("Lens distortion model");
   camera.screenwidth = 512;
   camera.screenheight = 512;
   glutReshapeWindow(camera.screenwidth,camera.screenheight);
   glutDisplayFunc(HandleDisplay);
   glutReshapeFunc(HandleReshape);
   glutVisibilityFunc(HandleVisibility);
   glutKeyboardFunc(HandleKeyboard);
   glutSpecialFunc(HandleSpecialKeyboard);
   glutMouseFunc(HandleMouse);
   glutMotionFunc(HandleMouseMotion);
   glutSetCursor(GLUT_CURSOR_NONE);
   CreateEnvironment();
   CameraHome(0);

   /* Set up the model menu */
   modelmenu = glutCreateMenu(HandleModelMenu);
   glutAddMenuEntry("Mesh",MESH);
   glutAddMenuEntry("Pulsar",PULSAR);

   /* Set up the distortion menu */
   distortmenu = glutCreateMenu(HandleDistortMenu);
   glutAddMenuEntry("None",NONE);
   glutAddMenuEntry("Sin XY",SINEXY);
   glutAddMenuEntry("Sin R",SINER);
   glutAddMenuEntry("Square XY",SQUAREXY);
   glutAddMenuEntry("Square R",SQUARER);
   glutAddMenuEntry("Asin XY",ASINXY);
   glutAddMenuEntry("Asin R",ASINR);

   /* Set up the aperture menu */
   aperturemenu = glutCreateMenu(HandleApertureMenu);
   glutAddMenuEntry("30",30);
   glutAddMenuEntry("45",45);
   glutAddMenuEntry("50",50);
   glutAddMenuEntry("60",60);
   glutAddMenuEntry("90",90);
   glutAddMenuEntry("120",120);
   glutAddMenuEntry("150",150);
   glutAddMenuEntry("170",170);

   /* Set up the main menu */
   mainmenu = glutCreateMenu(HandleMainMenu);
   glutAddSubMenu("Distortion",distortmenu);
   glutAddSubMenu("Model",modelmenu);
   glutAddSubMenu("Aperture",aperturemenu);
   glutAddMenuEntry("Quit",9);
   glutAttachMenu(GLUT_RIGHT_BUTTON);

   /* Ready to go! */
   glutMainLoop();
   return(0);
}

/*
   This is where global OpenGL/GLUT settings are made, 
   that is, things that will not change in time 
*/
void CreateEnvironment(void)
{
   glEnable(GL_DEPTH_TEST);
   glDisable(GL_LINE_SMOOTH);
   glDisable(GL_POINT_SMOOTH);
   glDisable(GL_POLYGON_SMOOTH); 
   glDisable(GL_DITHER);
   glDisable(GL_CULL_FACE);
   glEnable(GL_BLEND);
   glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

   glLineWidth(1.0);
   glPointSize(1.0);

   glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
   glFrontFace(GL_CW);
   glClearColor(0.0,0.0,0.0,0.0);
   glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);
   glEnable(GL_COLOR_MATERIAL);
   glPixelStorei(GL_UNPACK_ALIGNMENT,1);
}

/*
   This is the basic display callback routine
   It creates the geometry, lighting, and viewing position
   In this case it rotates the camera around the scene
*/
void HandleDisplay(void)
{
   int i,j;
   XYZ right,focus;
   unsigned int textureid;
   GLfloat white[4] = {1.0,1.0,1.0,1.0};

   /* Determine the focal point */
   Normalise(&camera.vd);
   focus.x = camera.vp.x + camera.focallength * camera.vd.x;
   focus.y = camera.vp.y + camera.focallength * camera.vd.y;
   focus.z = camera.vp.z + camera.focallength * camera.vd.z;

   glShadeModel(GL_SMOOTH);
   glDrawBuffer(GL_BACK_LEFT);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(camera.aperture,camera.screenwidth/(double)camera.screenheight,0.1,10000.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   gluLookAt(camera.vp.x,camera.vp.y,camera.vp.z,
             focus.x,focus.y,focus.z,
             camera.vu.x,camera.vu.y,camera.vu.z);
   switch (modeltype) {
   case MESH:
        MakeMesh();
      break;
   case PULSAR:
      MakePulsar();
      break;
   }
   MakeLighting();
   glFlush();

   /* Copy the image to be used as a texture */
   if ((thetex = malloc(camera.screenwidth*camera.screenheight*sizeof(PIXELA))) == NULL) {
      fprintf(stderr,"Failed to allocate memory for the texture\n");
      return;
   }
   glReadPixels(0,0,camera.screenwidth,camera.screenheight,GL_RGBA,GL_UNSIGNED_BYTE,thetex);
   glGenTextures(1,&textureid);
   glBindTexture(GL_TEXTURE_2D,textureid);
   glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
   glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); 
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
   glTexImage2D(GL_TEXTURE_2D,0,4,
      camera.screenwidth,camera.screenheight,
      0,GL_RGBA,GL_UNSIGNED_BYTE,thetex);
   glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
   glDrawBuffer(GL_BACK_LEFT);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glOrtho(-camera.screenwidth/2,camera.screenwidth/2,
      -camera.screenheight/2,camera.screenheight/2,1.0,10000.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   gluLookAt(0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0);
   glNormal3f(0.0,0.0,1.0);
   glColor3f(1.0,1.0,1.0);
   glDisable(GL_LIGHTING);
   glShadeModel(GL_FLAT);
   glLightModelfv(GL_LIGHT_MODEL_AMBIENT,white);
   glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); 
   glEnable(GL_TEXTURE_2D);
   glBindTexture(GL_TEXTURE_2D,textureid);
   CreateGrid();
   glDisable(GL_TEXTURE_2D);
   glutSwapBuffers();

   glDeleteTextures(1,&textureid);
   free(thetex);
}

/*
   Create the grometry for the mesh
*/
void MakeMesh(void)
{
   int i,j,n=2,w=32;
   COLOUR colour;
   double wmax;

   wmax = sqrt(w*w+(double)w*w);

   for (i=-w;i<=w;i+=n) {
      glBegin(GL_LINES);
      for (j=-w;j<w;j+=n) {
         colour.r = (i + w) / (2.0 * w);
         colour.g = 1;
         colour.b = (w - j) / (2.0 * w);
         glColor3f(colour.r,colour.g,colour.b);
         glVertex3f(0.0,(double)i,(double)j);
         glVertex3f(0.0,(double)i,(double)j+n);
      }
      glEnd();
   }
   for (j=-w;j<=w;j+=n) {
      glBegin(GL_LINES);
      for (i=-w;i<w;i+=n) {
         colour.r = (i + w) / (2.0 * w);
         colour.g = 1;
         colour.b = (w - j) / (2.0 * w);
         glColor3f(colour.r,colour.g,colour.b);
         glVertex3f(0.0,(double)i,(double)j);
         glVertex3f(0.0,(double)i+n,(double)j);
      }
      glEnd();
   }
}

/*
   Create the geometry for the pulsar
*/
void MakePulsar(void)
{
   int i,j,k;
   double cradius = 5.3;         /* Final radius of the cone */
   double clength = 30;            /* Cone length */
   double sradius = 10;            /* Final radius of sphere */
   double r1,r2;                  /* Min and Max radius of field lines */
   double x,y,z;
   XYZ p[4],n[4];
   COLOUR grey = {0.7,0.7,0.7};
   COLOUR white = {1.0,1.0,1.0};
   GLfloat specular[4] = {1.0,1.0,1.0,1.0};
   GLfloat shiny[1] = {5.0};
   char cmd[64];
   static double rotateangle = 0.0;    /* Pulsar Rotation angle */

   glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,specular);
   glMaterialfv(GL_FRONT_AND_BACK,GL_SHININESS,shiny);

   /* Top level rotation  - spin */
   glPushMatrix();
   glRotatef(rotateangle,0.0,1.0,0.0);

   /* Rotation about spin axis */
   glPushMatrix();
   glRotatef(45.0,0.0,0.0,1.0);

   /* Light in center */
   glColor3f(white.r,white.g,white.b);
   glutSolidSphere(5.0,16,8);

   /* Spherical center */
   for (i=0;i<360;i+=5) {
      for (j=-80;j<80;j+=5) {

         p[0].x = sradius * cos(j*DTOR) * cos(i*DTOR);
         p[0].y = sradius * sin(j*DTOR);
         p[0].z = sradius * cos(j*DTOR) * sin(i*DTOR);
         n[0] = p[0];

         p[1].x = sradius * cos((j+5)*DTOR) * cos(i*DTOR);
         p[1].y = sradius * sin((j+5)*DTOR);
         p[1].z = sradius * cos((j+5)*DTOR) * sin(i*DTOR);
         n[1] = p[1];

         p[2].x = sradius * cos((j+5)*DTOR) * cos((i+5)*DTOR);
         p[2].y = sradius * sin((j+5)*DTOR);
         p[2].z = sradius * cos((j+5)*DTOR) * sin((i+5)*DTOR);
         n[2] = p[2];

         p[3].x = sradius * cos(j*DTOR) * cos((i+5)*DTOR);
         p[3].y = sradius * sin(j*DTOR);
         p[3].z = sradius * cos(j*DTOR) * sin((i+5)*DTOR);
         n[3] = p[3];

         glBegin(GL_POLYGON);
         if (i % 20 == 0)
            glColor3f(1.0,0.0,0.0);
         else
            glColor3f(0.5,0.0,0.0);
         for (k=0;k<4;k++) {
            glNormal3f(n[k].x,n[k].y,n[k].z);
            glVertex3f(p[k].x,p[k].y,p[k].z);
         }
         glEnd();
      }
   }

   /* Draw the cones */
   for (j=-1;j<=1;j+=2) {
      for (i=0;i<360;i+=10) {
         
         p[0]   = origin;
         n[0]   = p[0];
         n[0].y = -1;

         p[1].x = cradius * cos(i*DTOR);
         p[1].y = j*clength;
         p[1].z = cradius * sin(i*DTOR);
         n[1]   = p[1];
         n[1].y = 0;

         p[2].x = cradius * cos((i+10)*DTOR);
         p[2].y = j*clength;
         p[2].z = cradius * sin((i+10)*DTOR);
         n[2]   = p[2];
         n[2].y = 0;

         glBegin(GL_POLYGON);
         if (i % 30 == 0)
            glColor3f(0.0,0.2,0.0);
         else
            glColor3f(0.0,0.5,0.0);
         for (k=0;k<3;k++) {
            glNormal3f(n[k].x,n[k].y,n[k].z);
            glVertex3f(p[k].x,p[k].y,p[k].z);
         }
         glEnd();
      }
   }

   /* Draw the field lines */
   r1 = 12;
   r2 = 16;
   for (j=0;j<360;j+=20) {
      glPushMatrix();
      glRotatef((double)j,0.0,1.0,0.0);
      glBegin(GL_LINE_STRIP);
      glColor3f(grey.r,grey.g,grey.b);
      for (i=-140;i<140;i++) {
         x = r1 + r1 * cos(i*DTOR);
         y = r2 * sin(i*DTOR);
         z = 0;
         glVertex3f(x,y,z);   
      }   
      glEnd();
      glPopMatrix();      
   }

   glPopMatrix(); /* Pulsar axis rotation */
   glPopMatrix(); /* Pulsar spin */

   /* Next angle for rotating the pulsar */
   rotateangle += rotatespeed;
}

/*
   Set up the lighing environment
*/
void MakeLighting(void)
{
   GLfloat fullambient[4] = {1.0,1.0,1.0,1.0};
   GLfloat position[4] = {0.0,0.0,0.0,0.0};
   GLfloat ambient[4]  = {0.2,0.2,0.2,1.0};
   GLfloat diffuse[4]  = {1.0,1.0,1.0,1.0};
   GLfloat specular[4] = {0.0,0.0,0.0,1.0};

   /* Turn off all the lights */
   glDisable(GL_LIGHT0);
   glDisable(GL_LIGHT1);
   glDisable(GL_LIGHT2);
   glDisable(GL_LIGHT3);
   glDisable(GL_LIGHT4);
   glDisable(GL_LIGHT5);
   glDisable(GL_LIGHT6);
   glDisable(GL_LIGHT7);
   glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER,GL_TRUE);
   glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_FALSE);

   /* Turn on the appropriate lights */
   glLightModelfv(GL_LIGHT_MODEL_AMBIENT,fullambient);
   glLightfv(GL_LIGHT0,GL_POSITION,position);
   glLightfv(GL_LIGHT0,GL_AMBIENT,ambient);
   glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuse);
   glLightfv(GL_LIGHT0,GL_SPECULAR,specular);
   glEnable(GL_LIGHT0);

   /* Sort out the shading algorithm */
   glShadeModel(GL_SMOOTH);

   /* Turn lighting on */
   glEnable(GL_LIGHTING);
}

/*
   Deal with plain key strokes
*/
void HandleKeyboard(unsigned char key,int x, int y)
{
   switch (key) {
   case ESC:                            /* Quit */
   case 'Q':
   case 'q': 
      exit(0); 
      break;
   case 'h':                           /* Go home     */
   case 'H':
      CameraHome(0);
      break;
   case '[':                           /* Roll anti clockwise */
      RotateCamera(0,0,-1);
      break;
   case ']':                           /* Roll clockwise */
      RotateCamera(0,0,1);
      break;
   case 'i':                           /* Translate camera up */
   case 'I':
      TranslateCamera(0,1);
      break;
   case 'k':                           /* Translate camera down */
   case 'K':
      TranslateCamera(0,-1);
      break;
   case 'j':                           /* Translate camera left */
   case 'J':
      TranslateCamera(-1,0);
      break;
   case 'l':                           /* Translate camera right */
   case 'L':
      TranslateCamera(1,0);
      break;
   }
}

/*
   Deal with special key strokes
*/
void HandleSpecialKeyboard(int key,int x, int y)
{
   switch (key) {
   case GLUT_KEY_LEFT:
      RotateCamera(-1,0,0);
      break;
   case GLUT_KEY_RIGHT:
      RotateCamera(1,0,0);
      break;
   case GLUT_KEY_UP:
      RotateCamera(0,1,0);
      break;
   case GLUT_KEY_DOWN:
      RotateCamera(0,-1,0);
      break;
   }
}

/*
   Rotate (ix,iy) or roll (iz) the camera about the focal point
   ix,iy,iz are flags, 0 do nothing, +- 1 rotates in opposite directions
   Correctly updating all camera attributes
*/
void RotateCamera(int ix,int iy,int iz)
{
   XYZ vp,vu,vd;
   XYZ right;
   XYZ newvp,newr;
   double radius,dd,radians;
   double dx,dy,dz;

   vu = camera.vu;
   Normalise(&vu);
   vp = camera.vp;
   vd = camera.vd;
   Normalise(&vd);
   CROSSPROD(vd,vu,right);
   Normalise(&right);
   radians = dtheta * PI / 180.0;

   /* Handle the roll */
   if (iz != 0) {
      camera.vu.x += iz * right.x * radians;
      camera.vu.y += iz * right.y * radians;
      camera.vu.z += iz * right.z * radians;
      Normalise(&camera.vu);
      return;
   }

   /* Distance from the rotate point */
   dx = camera.vp.x - camera.pr.x;
   dy = camera.vp.y - camera.pr.y;
   dz = camera.vp.z - camera.pr.z;
   radius = sqrt(dx*dx + dy*dy + dz*dz);

   /* Determine the new view point */
   dd = radius * radians;
   newvp.x = vp.x + dd * ix * right.x + dd * iy * vu.x - camera.pr.x;
   newvp.y = vp.y + dd * ix * right.y + dd * iy * vu.y - camera.pr.y;
   newvp.z = vp.z + dd * ix * right.z + dd * iy * vu.z - camera.pr.z;
   Normalise(&newvp);
   camera.vp.x = camera.pr.x + radius * newvp.x;
   camera.vp.y = camera.pr.y + radius * newvp.y;
   camera.vp.z = camera.pr.z + radius * newvp.z;

   /* Determine the new right vector */
   newr.x = camera.vp.x + right.x - camera.pr.x;
   newr.y = camera.vp.y + right.y - camera.pr.y;
   newr.z = camera.vp.z + right.z - camera.pr.z;
   Normalise(&newr);
   newr.x = camera.pr.x + radius * newr.x - camera.vp.x;
   newr.y = camera.pr.y + radius * newr.y - camera.vp.y;
   newr.z = camera.pr.z + radius * newr.z - camera.vp.z;

   camera.vd.x = camera.pr.x - camera.vp.x;
   camera.vd.y = camera.pr.y - camera.vp.y;
   camera.vd.z = camera.pr.z - camera.vp.z;
   Normalise(&camera.vd);

   /* Determine the new up vector */
   CROSSPROD(newr,camera.vd,camera.vu);
   Normalise(&camera.vu);

   if (debug)
      fprintf(stderr,"Camera position: (%g,%g,%g)\n",
         camera.vp.x,camera.vp.y,camera.vp.z);
}

/*
   Translate (pan) the camera view point
   In response to i,j,k,l keys
   Also move the camera rotate location in parallel
*/
void TranslateCamera(int ix,int iy)
{
   XYZ vp,vu,vd;
   XYZ right;
   XYZ newvp,newr;
   double radians,delta;

   vu = camera.vu;
   Normalise(&vu);
   vp = camera.vp;
   vd = camera.vd;
   Normalise(&vd);
   CROSSPROD(vd,vu,right);
   Normalise(&right);
   radians = dtheta * PI / 180.0;
   delta = dtheta * camera.focallength / 90.0;

   camera.vp.x += iy * vu.x * delta;
   camera.vp.y += iy * vu.y * delta;
   camera.vp.z += iy * vu.z * delta;
   camera.pr.x += iy * vu.x * delta;
   camera.pr.y += iy * vu.y * delta;
   camera.pr.z += iy * vu.z * delta;

   camera.vp.x += ix * right.x * delta;
   camera.vp.y += ix * right.y * delta;
   camera.vp.z += ix * right.z * delta;
   camera.pr.x += ix * right.x * delta;
   camera.pr.y += ix * right.y * delta;
   camera.pr.z += ix * right.z * delta;
}

/*
   Handle mouse events
   Right button events are passed to menu handlers
*/
void HandleMouse(int button,int state,int x,int y)
{
   if (state == GLUT_DOWN) {
      if (button == GLUT_LEFT_BUTTON) {
         currentbutton = GLUT_LEFT_BUTTON;
      } else if (button == GLUT_MIDDLE_BUTTON) {
         currentbutton = GLUT_MIDDLE_BUTTON;
      } 
   }
}

/*
   Handle the main menu
*/
void HandleMainMenu(int whichone)
{
   switch (whichone) {
   case 9: 
      exit(0); 
      break;
   }
}

/*
   Handle the distortion menu
*/
void HandleDistortMenu(int whichone)
{
   distortion = whichone;
}

/*
   Handle the aperture menu
*/
void HandleApertureMenu(int whichone)
{
   camera.aperture = whichone;
}

/*
   Handle the model menu
*/
void HandleModelMenu(int whichone)
{
   modeltype = whichone; 
}

/*
   How to handle visibility
*/
void HandleVisibility(int visible)
{
   if (visible == GLUT_VISIBLE)
      glutIdleFunc(HandleIdle);
   else
      glutIdleFunc(NULL);
}

/*
   What to do on an idle event
*/
void HandleIdle(void)
{
   glutPostRedisplay();
}

/*
   Handle a window reshape/resize
   Keep it a power of 2 for the textures
   Keep it square
*/
void HandleReshape(int w,int h)
{
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   if (w < 300) 
      w = 256;
   else if (w < 700) 
      w = 512;
   else 
      w = 1024;
   if (h < 300) 
      h = 256;
   else if (h < 700) 
      h = 512;
   else 
      h = 1024;
   if (h != w)
      h = w;
   glutReshapeWindow(w,h);
   glViewport(0,0,(GLsizei)w,(GLsizei)h);
   camera.screenwidth = w;
   camera.screenheight = h;
}

/*
   Display the program usage information
*/
void GiveUsage(char *cmd)
{
   fprintf(stderr,"Usage: %s [-h] [-f] [-s] [-c] [-q n]\n",cmd);
   fprintf(stderr,"          -h   this text\n");
   fprintf(stderr,"Key Strokes\n");
   fprintf(stderr,"  arrow keys   rotate left/right/up/down\n");
   fprintf(stderr,"  left mouse   rotate\n");
   fprintf(stderr,"middle mouse   roll\n");
   fprintf(stderr,"           i   translate up\n");
   fprintf(stderr,"           k   translate down\n");
   fprintf(stderr,"           j   translate left\n");
   fprintf(stderr,"           l   translate right\n");
   fprintf(stderr,"           [   roll clockwise\n");
   fprintf(stderr,"           ]   roll anti clockwise\n");
   fprintf(stderr,"           q   quit\n");
   exit(-1);
}

/*
   Move the camera to the home position 
*/
void CameraHome(int mode)
{
   camera.aperture = 50;
   camera.focallength = 70;
   camera.eyesep = camera.focallength / 20;
   camera.pr = origin;

   camera.vp.x = camera.focallength; 
   camera.vp.y = 0; 
   camera.vp.z = 0;
   camera.vd.x = -1;
   camera.vd.y = 0;
   camera.vd.z = 0;

   camera.vu.x = 0;  
   camera.vu.y = 1; 
   camera.vu.z = 0;
}

/*
   Handle mouse motion
*/
void HandleMouseMotion(int x,int y)
{
   static int xlast=-1,ylast=-1;
   int dx,dy;

   dx = x - xlast;
   dy = y - ylast;
   if (dx < 0)      dx = -1;
   else if (dx > 0) dx =  1;
   if (dy < 0)      dy = -1;
   else if (dy > 0) dy =  1;

   if (currentbutton == GLUT_LEFT_BUTTON)
      RotateCamera(-dx,dy,0);
   else if (currentbutton == GLUT_MIDDLE_BUTTON)
      RotateCamera(0,0,dx);

   xlast = x;
   ylast = y;
}

/*
   Form the grid with distorted texture coordinates
*/
void CreateGrid(void)
{
   int i,j,n=5;
   double x,y;

   glBegin(GL_QUADS);
   for (i=0;i<camera.screenwidth;i+=n) {
      for (j=0;j<camera.screenheight;j+=n) {      
         Transform(i,j,&x,&y);
         glTexCoord2f(x,y);
         glVertex3f(i-camera.screenwidth/2.0,j-camera.screenheight/2.0,0.0);
         Transform(i+n,j,&x,&y);
         glTexCoord2f(x,y);
         glVertex3f((i+n)-camera.screenwidth/2.0,j-camera.screenheight/2.0,0.0);
         Transform(i+n,j+n,&x,&y);
         glTexCoord2f(x,y);
         glVertex3f((i+n)-camera.screenwidth/2.0,(j+n)-camera.screenheight/2.0,0.0);
         Transform(i,j+n,&x,&y);
         glTexCoord2f(x,y);
         glVertex3f(i-camera.screenwidth/2.0,(j+n)-camera.screenheight/2.0,0.0);
      }
   } 
   glEnd();
}

/*
   Calculate the distorted grid coordinates
*/
void Transform(int i,int j,double *ix,double *iy)
{
   double x,y,xnew,ynew;
   double r,theta,rnew,thetanew;

   x = i / (camera.screenwidth/2.0) - 1;
   y = j / (camera.screenwidth/2.0) - 1;
   r = sqrt(x*x+y*y);
   theta = atan2(y,x);
   switch (distortion) {
   case NONE:
      xnew = x;
      ynew = y;
      break;
   case SINEXY:
      xnew = sin(PID2*x);
      ynew = sin(PID2*y);
      break;
   case SINER:
      rnew = sin(PID2*r);
      thetanew = theta;
      xnew = rnew * cos(thetanew);
      ynew = rnew * sin(thetanew);
      break;
   case SQUAREXY:
      xnew = x*x*SIGN(x);
      ynew = y*y*SIGN(y);
      break;
   case SQUARER:
      rnew = r*r;
      thetanew = theta;
      xnew = rnew * cos(thetanew);
      ynew = rnew * sin(thetanew);
      break;
   case ASINXY:
      xnew = asin(x) / PID2;
      ynew = asin(y) / PID2;
      break;
   case ASINR:
      rnew = asin(r) / PID2;
      thetanew = theta;
      xnew = rnew * cos(thetanew);
      ynew = rnew * sin(thetanew);
      break;
   }
   *ix = (xnew + 1) / 2.0;
   *iy = (ynew + 1) / 2.0;
}

/*-------------------------------------------------------------------------
   Normalise a vector
*/
void Normalise(XYZ *p)
{
   double length;

   length = sqrt(p->x * p->x + p->y * p->y + p->z * p->z);
   if (length != 0) {
      p->x /= length;
      p->y /= length;
      p->z /= length;
   } else {
      p->x = 0;
      p->y = 0;
      p->z = 0;
   }
}




