#include "vpacsample.h"

/*
   Demonstration Stereographics OpenGL application for 
   the VPAC portable stereograph display hardware.
   The purpose of this software is to illustrate how to
   set up the stereographic settings for frame sequenial
   stereo and dual display stereo. A side purpose is to
   illustrate GLUT calls and handlers.
*/

/* See vpacsample.h */
OPTIONS options;
CAMERA  camera;
INTERFACESTATE interfacestate;

int main(int argc,char **argv)
{
   int i;

   /* The default camera attributes */
   CameraHome(0);
   camera.stereo       = NOSTEREO;
   camera.screenwidth  = 300;
   camera.screenheight = 300;

   /* Default options */
   options.debug        = FALSE;
   options.fullscreen   = FALSE;
   options.windowdump   = FALSE;
   options.record       = FALSE;
   options.targetfps    = 30;

   /* State of the input device, mouse in this case */
   interfacestate.button = -1;
   interfacestate.shift  = FALSE;
   interfacestate.mouseh = 0;
   interfacestate.mousev = 0;

   /* Parse the command line arguments */
   for (i=1;i<argc;i++) {
      if (strcmp(argv[i],"-h") == 0) 
         GiveUsage(argv[0]);
      if (strcmp(argv[i],"-f") == 0)
         options.fullscreen = !options.fullscreen;
      if (strcmp(argv[i],"-s") == 0)
         camera.stereo = ACTIVESTEREO;
      if (strcmp(argv[i],"-ss") == 0) {
         camera.stereo = DUALSTEREO;
         camera.screenwidth *= 2;
      }
      if (strcmp(argv[i],"-d") == 0)
         options.debug = TRUE;
   }

   /* Set things up and go */
   glutInit(&argc,argv);
   if (camera.stereo == ACTIVESTEREO)
      glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_STEREO);
   else
      glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);

   /* Create the window and handlers */
   glutCreateWindow("VPAC Stereo3D Demo");
   glutReshapeWindow(camera.screenwidth,camera.screenheight);
   if (options.fullscreen)
      glutFullScreen();
   glutDisplayFunc(HandleDisplay);
   glutReshapeFunc(HandleReshape);
   CreateMenus();
   glutKeyboardFunc(HandleKeyboard);
   glutSpecialFunc(HandleSpecialKeyboard);
   glutMouseFunc(HandleMouse);
   glutMotionFunc(HandleMouseMotion);
   glutPassiveMotionFunc(HandlePassiveMotion);
   glutVisibilityFunc(HandleVisibility);      
   glutSetCursor(GLUT_CURSOR_NONE);
   glutIdleFunc(HandleIdle);
   CreateEnvironment();

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

/*
   Form the menu structure
   Associate the menus with the right mouse button
*/
void CreateMenus(void)
{
   int mainmenu,cameramenu;

   cameramenu = glutCreateMenu(HandleCameraMenu);
   glutAddMenuEntry("Home   [ h]",1);
   glutAddMenuEntry("Front  [F1]",2);
   glutAddMenuEntry("Back   [F2]",3);
   glutAddMenuEntry("Left   [F3]",4);
   glutAddMenuEntry("Right  [F4]",5);
   glutAddMenuEntry("Top    [F5]",6);
   glutAddMenuEntry("Bottom [F6]",7);

   mainmenu = glutCreateMenu(HandleMainMenu);
   glutAddSubMenu("Camera",cameramenu);
   glutAddMenuEntry("Quit",10);

   glutAttachMenu(GLUT_RIGHT_BUTTON);
}

/*
   This is where global OpenGL settings are made.
   Since this is not designed to demonstrate how to render
   objects in OpenGL, it's pretty boring. 
*/
void CreateEnvironment(void)
{
   GLfloat white[4] = {1,1,1,1};
   GLfloat shin[1]  = {0.0};

   /* Miscellaneous settings */
   glEnable(GL_DEPTH_TEST);
   glEnable(GL_BLEND);
   glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
   glDisable(GL_DITHER);
   glPixelStorei(GL_UNPACK_ALIGNMENT,1);

   /* These are hardware dependent */
   glDisable(GL_DITHER);
   glDisable(GL_LINE_SMOOTH);
   glDisable(GL_POINT_SMOOTH);
   glDisable(GL_POLYGON_SMOOTH);

   /* Default geometry rendering */
   glLineWidth(1.0);
   glPointSize(1.0);
   glFrontFace(GL_CW);
   glShadeModel(GL_SMOOTH);
   glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);

   /* Default meterials */
   glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,white);
   glMaterialfv(GL_FRONT_AND_BACK,GL_SHININESS,shin);
   glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);
   glEnable(GL_COLOR_MATERIAL);

   /* 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);
   glDisable(GL_LIGHTING);
   glLightModelfv(GL_LIGHT_MODEL_AMBIENT,white);
   glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER,GL_TRUE);
   glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_FALSE);
   glClearColor(0.0,0.0,0.0,0.0);
}

/*
   This is the basic display callback routine
   This is where the real projection work for stereo
   is done, note the viewer is assumed to be located
   centrally to the screen and looking along a vector
   perpendicular to the screen.
*/
void HandleDisplay(void)
{
   XYZ r;
   double ratio,radians,wd2,ndfl;
   double left,right,top,bottom;

   /* Safe stereo */
   if (camera.stereo == ACTIVESTEREO || camera.stereo == DUALSTEREO) 
      camera.near = camera.focallength / 10;
   else
      camera.near = camera.focallength / 100;

   /* Misc stuff needed for the frustum */
   ratio   = camera.screenwidth / (double)camera.screenheight;
   if (camera.stereo == DUALSTEREO)
      ratio /= 2;
   radians = DTOR * camera.aperture / 2;
   wd2     = camera.near * tan(radians);
   ndfl    = camera.near / camera.focallength;
   top     =   wd2;
   bottom  = - wd2;

   /* Determine the right eye vector */
   CROSSPROD(camera.vd,camera.vu,r);
   Normalise(&r);
   r.x *= camera.eyesep / 2.0;
   r.y *= camera.eyesep / 2.0;
   r.z *= camera.eyesep / 2.0;

   if (camera.stereo == ACTIVESTEREO || camera.stereo == DUALSTEREO) {

      if (camera.stereo == DUALSTEREO) {
         glDrawBuffer(GL_BACK);
         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      }

      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();
      left  = - ratio * wd2 - 0.5 * camera.eyesep * ndfl;
      right =   ratio * wd2 - 0.5 * camera.eyesep * ndfl;
      glFrustum(left,right,bottom,top,camera.near,camera.far);
      if (camera.stereo == DUALSTEREO)
         glViewport(camera.screenwidth/2,0,camera.screenwidth/2,camera.screenheight);
      else
         glViewport(0,0,camera.screenwidth,camera.screenheight);
      glMatrixMode(GL_MODELVIEW);
      glDrawBuffer(GL_BACK_RIGHT);
      if (camera.stereo == ACTIVESTEREO)
         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      glLoadIdentity();
      gluLookAt(camera.vp.x + r.x,camera.vp.y + r.y,camera.vp.z + r.z,
                camera.vp.x + r.x + camera.vd.x,
                camera.vp.y + r.y + camera.vd.y,
                camera.vp.z + r.z + camera.vd.z,
                camera.vu.x,camera.vu.y,camera.vu.z);
      CreateGeometry();

      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();
      left  = - ratio * wd2 + 0.5 * camera.eyesep * ndfl;
      right =   ratio * wd2 + 0.5 * camera.eyesep * ndfl;
      glFrustum(left,right,bottom,top,camera.near,camera.far);
      if (camera.stereo == DUALSTEREO)
         glViewport(0,0,camera.screenwidth/2,camera.screenheight);
      else
         glViewport(0,0,camera.screenwidth,camera.screenheight);
      glMatrixMode(GL_MODELVIEW);
      glDrawBuffer(GL_BACK_LEFT);
      if (camera.stereo == ACTIVESTEREO)
         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      glLoadIdentity();
      gluLookAt(camera.vp.x - r.x,camera.vp.y - r.y,camera.vp.z - r.z,
                camera.vp.x - r.x + camera.vd.x,
                camera.vp.y - r.y + camera.vd.y,
                camera.vp.z - r.z + camera.vd.z,
                camera.vu.x,camera.vu.y,camera.vu.z);
      CreateGeometry();

   } else {

      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();
      glViewport(0,0,camera.screenwidth,camera.screenheight); 
      left  = - ratio * wd2;
      right =   ratio * wd2;
      glFrustum(left,right,bottom,top,camera.near,camera.far);
      glMatrixMode(GL_MODELVIEW);
      glDrawBuffer(GL_BACK_LEFT);
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      glLoadIdentity();
      gluLookAt(camera.vp.x,camera.vp.y,camera.vp.z,
                camera.vp.x + camera.vd.x,
                camera.vp.y + camera.vd.y,
                camera.vp.z + camera.vd.z,
                camera.vu.x,camera.vu.y,camera.vu.z);
      CreateGeometry();
   }

   /* Dump the window in compressed TGA format */
   if (options.record || options.windowdump) {
      if (camera.stereo == ACTIVESTEREO)
         Dump2TGA(camera.screenwidth,camera.screenheight,TRUE);
      else
         Dump2TGA(camera.screenwidth,camera.screenheight,FALSE);
      options.windowdump = FALSE;
   }

   /* Swap buffers */
   glutSwapBuffers();
}

/*
   Create the geometry here
   In a real, more efficient, implementation the static items
   would be placed in a display list.
*/
void CreateGeometry(void)
{
   int i;
   XYZ pmin = {-2,-2,-2},pmax = {2,2,2};
   double mu,x,y,z;
   static double theta = 0;

   /* Draw the axes */
   glBegin(GL_LINES);
   glColor3f(1.0,0.0,0.0);
   glVertex3f(0.0,0.0,0.0);
   glVertex3f(1.0,0.0,0.0);
   glColor3f(0.0,1.0,0.0);
   glVertex3f(0.0,0.0,0.0);
   glVertex3f(0.0,1.0,0.0);
   glColor3f(0.0,0.0,1.0);
   glVertex3f(0.0,0.0,0.0);
   glVertex3f(0.0,0.0,1.0);
   glEnd();

   /* Draw a cube centered at the origin */
   glColor3f(0.5,0.5,0.5);
   glBegin(GL_LINE_STRIP);
   glVertex3f(pmin.x,pmin.y,pmin.z);
   glVertex3f(pmax.x,pmin.y,pmin.z);
   glVertex3f(pmax.x,pmin.y,pmax.z);
   glVertex3f(pmin.x,pmin.y,pmax.z);
   glVertex3f(pmin.x,pmin.y,pmin.z);
   glVertex3f(pmin.x,pmax.y,pmin.z);
   glVertex3f(pmax.x,pmax.y,pmin.z);
   glVertex3f(pmax.x,pmax.y,pmax.z);
   glVertex3f(pmin.x,pmax.y,pmax.z);
   glVertex3f(pmin.x,pmax.y,pmin.z);
   glEnd();
   glBegin(GL_LINES);
   glVertex3f(pmax.x,pmin.y,pmin.z); 
   glVertex3f(pmax.x,pmax.y,pmin.z);
   glVertex3f(pmax.x,pmin.y,pmax.z); 
   glVertex3f(pmax.x,pmax.y,pmax.z);
   glVertex3f(pmin.x,pmin.y,pmax.z); 
   glVertex3f(pmin.x,pmax.y,pmax.z);
   glEnd();

   /* Create a rotating knot */
   glPointSize(2.0);
   glColor3f(1.0,0.5,0.5);
   glPushMatrix();
   glRotatef(theta+=1,0.0,0.0,1.0);
   glBegin(GL_POINTS);
   for (i=0;i<=1000;i++) {
      mu = i * TWOPI * 7 / 1000.0;
      x = cos(mu) * (1 + cos(4*mu/7) / 2.0); 
      y = sin(mu) * (1 + cos(4*mu/7) / 2.0);
      z = 2*sin(4*mu/7) / 2.0;
      glVertex3f(x,y,z);
   }
   glEnd();
   glPopMatrix();
   glPointSize(1.0);
}

/*
   Deal with plain key strokes
*/
void HandleKeyboard(unsigned char key,int x, int y)
{
   if (glutGetModifiers() == GLUT_ACTIVE_SHIFT)
      interfacestate.shift = TRUE;
   else
      interfacestate.shift = FALSE;

   /* Handle keyboard commands */
   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,0.0,-0.1);
      break;
   case ']':                           /* Roll clockwise */
      RotateCamera(0.0,0.0,0.1);
      break;
   case 'w':                           /* Write the image to disk */
   case 'W':
      options.windowdump = !options.windowdump;
      break;
   case 'r':                           /* Frame recording */
   case 'R':
      options.record = !options.record;
      break;
   case '<':                           /* Move forward */
   case ',':
      TranslateCamera(0.0,0.0,camera.focallength/50);
      break;
   case '>':                           /* Move backward */
   case '.':
      TranslateCamera(0.0,0.0,-camera.focallength/50);
      break;
   case '-':                           /* Zoom in */
   case '_':
      if (camera.aperture < 90)
         camera.aperture *= 1.05;
      break;
   case '+':                           /* Zoom out */
   case '=':
      if (camera.aperture > 5)
         camera.aperture /= 1.05;
      break;
   }
}

/*
   Deal with special key strokes
*/
void HandleSpecialKeyboard(int key,int x, int y)
{
   if (glutGetModifiers() == GLUT_ACTIVE_SHIFT)
      interfacestate.shift = TRUE;
   else
      interfacestate.shift = FALSE;

   switch (key) {
   case GLUT_KEY_LEFT:  
      if (interfacestate.shift)
         TranslateCamera(-1.0,0.0,0.0);
      else
         RotateCamera(-1.0,0.0,0.0); 
      break;
   case GLUT_KEY_RIGHT: 
      if (interfacestate.shift)
         TranslateCamera(1.0,0.0,0.0);
      else
         RotateCamera(1.0,0.0,0.0);  
      break;
   case GLUT_KEY_UP:    
      if (interfacestate.shift)
         TranslateCamera(0.0,1.0,0.0);
      else
         RotateCamera(0.0,1.0,0.0);  
      break;
   case GLUT_KEY_DOWN:  
      if (interfacestate.shift)
         TranslateCamera(0.0,-1.0,0.0);
      else
         RotateCamera(0.0,-1.0,0.0); 
      break;
   case GLUT_KEY_F1:
      CameraHome(1);
      break;
   case GLUT_KEY_F2:
      CameraHome(2);
      break;
   case GLUT_KEY_F3:
      CameraHome(3);
      break;
   case GLUT_KEY_F4:
      CameraHome(4);
      break;
   case GLUT_KEY_F5:
      CameraHome(5);
      break;
   case GLUT_KEY_F6:
      CameraHome(6);
      break;
   }
}

/*
   Rotate (ix,iy) or roll (iz) the camera about the focal point
*/
void RotateCamera(double ix,double iy,double iz)
{
   XYZ vp,vu,vd,right;
   XYZ newvp,newr;
   double dx,dy,dz,radius;

   vu = camera.vu;
   Normalise(&vu);
   vp = camera.vp;
   vd = camera.vd;
   Normalise(&vd);
   CROSSPROD(vd,vu,right);
   Normalise(&right);

   /* Handle the roll */
   if (iz != 0) {
      camera.vu.x += iz * right.x;
      camera.vu.y += iz * right.y;
      camera.vu.z += iz * right.z;
      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 */
   newvp.x = vp.x + ix * right.x + iy * vu.x - camera.pr.x;
   newvp.y = vp.y + ix * right.y + iy * vu.y - camera.pr.y;
   newvp.z = vp.z + ix * right.z + 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);
}

/*
   Translate (pan) the camera view point
*/
void TranslateCamera(double ix,double iy,double iz)
{
   XYZ right;

   CROSSPROD(camera.vd,camera.vu,right);
   Normalise(&right);

   camera.vp.x += iy*camera.vu.x + ix*right.x + iz*camera.vd.x;
   camera.vp.y += iy*camera.vu.y + ix*right.y + iz*camera.vd.y;
   camera.vp.z += iy*camera.vu.z + ix*right.z + iz*camera.vd.z;

   camera.pr.x += iy*camera.vu.x + ix*right.x + iz*camera.vd.x;
   camera.pr.y += iy*camera.vu.y + ix*right.y + iz*camera.vd.y;
   camera.pr.z += iy*camera.vu.z + ix*right.z + iz*camera.vd.z;
}

/*
   Handle mouse events.
   Do nothing special, just remember the state.
*/
void HandleMouse(int button,int state,int x,int y)
{
   if (glutGetModifiers() == GLUT_ACTIVE_SHIFT)
      interfacestate.shift = TRUE;
   else
      interfacestate.shift = FALSE;

   if (state == GLUT_DOWN) {
      if (button == GLUT_LEFT_BUTTON) {
         interfacestate.button = GLUT_LEFT_BUTTON;
      } else if (button == GLUT_MIDDLE_BUTTON) {
         interfacestate.button = GLUT_MIDDLE_BUTTON;
      } else {
         /* Assigned to menus */
      }
   }
   interfacestate.mouseh = x;
   interfacestate.mousev = y;
}

/*
   Handle mouse motion
*/
void HandleMouseMotion(int x,int y)
{
   double dx,dy;

   dx = x - interfacestate.mouseh;
   dy = y - interfacestate.mousev;

   if (interfacestate.button == GLUT_LEFT_BUTTON) {
      if (interfacestate.shift)
         TranslateCamera(-dx/25,dy/25,0.0);
      else
         RotateCamera(-dx/25,dy/25,0.0);
   } else if (interfacestate.button == GLUT_MIDDLE_BUTTON) {
      if (interfacestate.shift)
         TranslateCamera(0.0,0.0,camera.focallength*dx/50);
      else
         RotateCamera(0.0,0.0,dx/50);
   }

   interfacestate.mouseh = x;
   interfacestate.mousev = y;
}

/*
   What to do on moviing nouse, the button isn't down.
*/
void HandlePassiveMotion(int x,int y)
{
   interfacestate.mouseh = x;
   interfacestate.mousev = y;
}


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

/*
   Handle the camera menu
*/
void HandleCameraMenu(int whichone)
{
   CameraHome(whichone-1);
}

/*
   How to handle visibility
   If the window is not visible then don't draw it,
   allows layered windows without a processor load blowout.
*/
void HandleVisibility(int visible)
{
   if (visible == GLUT_VISIBLE)
      glutIdleFunc(HandleIdle);
   else
      glutIdleFunc(NULL);
}

/*
   What to do on an idle event.
   Maintain a constant frame rate.
*/
void HandleIdle(void)
{
   static double tstart = -1;
   double tstop;

   if (tstart < 0)
      tstart = GetRunTime();
   tstop = GetRunTime();
   if (tstop - tstart > 1.0/options.targetfps) {
      glutPostRedisplay();
      tstart = tstop;
   }
}

/*
   Handle a window reshape/resize
*/
void HandleReshape(int w,int h)
{
   camera.screenwidth = w;
   camera.screenheight = h;
}

/*
   Display the program usage information
*/
void GiveUsage(char *cmd)
{
   fprintf(stderr,"Usage: %s [command line options]\n",cmd);
   fprintf(stderr,"Command line options\n");
   fprintf(stderr,"            -h   this text\n");
   fprintf(stderr,"            -f   full screen\n");
   fprintf(stderr,"            -s   active stereo\n");
   fprintf(stderr,"           -ss   dual screen stereo\n");
   fprintf(stderr,"Mouse buttons\n");
   fprintf(stderr,"          left   camera rotate\n");
   fprintf(stderr,"    shift left   camera pan\n");
   fprintf(stderr,"        middle   camera roll\n");
   fprintf(stderr,"  shift middle   camera forward, reverse\n");
   fprintf(stderr,"Key Strokes\n");
   fprintf(stderr,"    arrow keys   camera rotate\n");
   fprintf(stderr,"           <,>   camera forward, reverse\n");
   fprintf(stderr,"           +,-   camera zoom in, out\n");
   fprintf(stderr,"           [,]   camera roll\n");
   fprintf(stderr,"             r   toggle window recording\n");
   fprintf(stderr,"             w   capture window\n");
   fprintf(stderr,"         ESC,q   quit\n");
   exit(-1);
}

/*
   Move the camera to the home position 
*/
void CameraHome(int position)
{
   XYZ origin = {0,0,0};
   XYZ up = {0.0,0.0,1.0},x = {1.0,0.0,0.0};

   switch (position) {
   case 0:
      camera.aperture = 60;
      camera.focallength = 6;
      camera.eyesep = camera.focallength / 25;
      camera.near = camera.focallength / 10;
      camera.far = camera.focallength * 10;
      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 = up;
      break;
   case 1: /* Front */
      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 = up;
      break;
   case 2: /* Back */
      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 = up;
      break;
   case 3: /* Left */
      camera.vp.x = 0;
      camera.vp.y = camera.focallength;
      camera.vp.z = 0;
      camera.vd.x = 0;
      camera.vd.y = -1;
      camera.vd.z = 0;
      camera.vu = up;
      break;
   case 4: /* Right */
      camera.vp.x = 0;
      camera.vp.y = -camera.focallength;
      camera.vp.z = 0;
      camera.vd.x = 0;
      camera.vd.y = 1;
      camera.vd.z = 0;
      camera.vu = up;
      break;
   case 5: /* Top */
      camera.vp.x = 0;
      camera.vp.y = 0;
      camera.vp.z = camera.focallength;
      camera.vd.x = 0;
      camera.vd.y = 0;
      camera.vd.z = -1;
      camera.vu = x;
      break;
   case 6: /* Bottom */
      camera.vp.x = 0;
      camera.vp.y = 0;
      camera.vp.z = -camera.focallength;
      camera.vd.x = 0;
      camera.vd.y = 0;
      camera.vd.z = 1;
      camera.vu = x;
      break;
   }

   camera.pr = origin;
   Normalise(&camera.vd);
   Normalise(&camera.vu);
}

/*
   Calculate the length of a vector
*/
double Modulus(XYZ p)
{
    return(sqrt(p.x * p.x + p.y * p.y + p.z * p.z));
}

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

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

/*
   Time scale at microsecond resolution but returned as seconds
*/
double GetRunTime(void)
{
   double sec = 0;
   struct timeval tp;

   gettimeofday(&tp,NULL);
   sec = tp.tv_sec + tp.tv_usec / 1000000.0;

   return(sec);
}

/*
   Write the current view to an image file
   Do the right thing for stereo, ie: two images
*/
int Dump2TGA(int width,int height,int stereo)
{
   FILE *fptr;
   static int counter = 0;
   char fname[32];
   unsigned char *image;

   /* Allocate our buffer for the image */
   if ((image = malloc(width*height*4)) == NULL) {
      fprintf(stderr,"Failed to allocate memory for image\n");
      return(FALSE);
   }
   glFinish();
   if (stereo)
      sprintf(fname,"L_%04d.tga",counter);
   else
      sprintf(fname,"C_%04d.tga",counter);
   if ((fptr = fopen(fname,"w")) == NULL) {
      fprintf(stderr,"WindowDump - Failed to open file for window dump\n");
      return(FALSE);
   }

   /* Copy the image into our buffer */
   glReadBuffer(GL_BACK_LEFT);
   glReadPixels(0,0,width,height,GL_RGBA,GL_UNSIGNED_BYTE,image);

   /* Write the file */
   WriteTGA(fptr,image,width,height);
   fclose(fptr);

   if (stereo) {

      /* Open the file */
      sprintf(fname,"R_%04d.tga",counter);
      if ((fptr = fopen(fname,"w")) == NULL) {
         fprintf(stderr,"Failed to open file for window dump\n");
         return(FALSE);
      }

      /* Copy the image into our buffer */
      glReadBuffer(GL_BACK_RIGHT);
      glReadPixels(0,0,width,height,GL_RGBA,GL_UNSIGNED_BYTE,image);

      /* Write the file */
      WriteTGA(fptr,image,width,height);
      fclose(fptr);
   }

   free(image);
   counter++;
   return(TRUE);
}

void WriteTGA(FILE *fptr,unsigned char *image,int nx,int ny)
{
   int i,j;
   long index=0;

   putc(0,fptr);  /* Length of ID */
   putc(0,fptr);  /* No colour map */
   putc(2,fptr);  /* Uncompressed */
   putc(0,fptr);    /* Index of colour map entry */
   putc(0,fptr);
   putc(0,fptr);  /* Colour map length */
   putc(0,fptr);
   putc(0,fptr);  /* Colour map size */
   putc(0,fptr);    /* X origin */
   putc(0,fptr);
   putc(0,fptr);  /* Y origin */
   putc(0,fptr);
   putc((nx & 0x00ff),fptr); /* X width */
   putc((nx & 0xff00) / 256,fptr);
   putc((ny & 0x00ff),fptr); /* Y width */
   putc((ny & 0xff00) / 256,fptr);
   putc(24,fptr); /* 24 bit */
   putc(0x00,fptr);
   for (j=0;j<ny;j++) {
      for (i=0;i<nx;i++) {
         putc(image[index+2],fptr);
         putc(image[index+1],fptr);
         putc(image[index+0],fptr);
         index += 4;
      }
   }
}

