/* light_dat3.c  user controlled rotating *.dat with lighting */
/*               Utah Graphics ASCII data *.dat of *.det      */
/*               lighted, wire frame and vertex display       */
/*               point selection and output                   */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <GL/glut.h>
#include "datread.h"
#undef  abs
#define abs(x) ((x>=0.0)?x:(-x))
#undef  max
#define max(x,y) ((x)>(y)?(x):(y))

static int num_points;            /* for datread and datwrite */
static int num_polys;
static dpts * data_points;
static int *  data_verts;
static int width = 600; /* user can change */
static int height = 600;
static GLfloat size = 1.0;     /* auto scale */
static GLfloat offs = 1.1;
static char save_filename[100];
static int status = -1;

static int debug = 0;
static GLfloat roll = 0.0;     /* rotate about Z axis 'r' */
static GLfloat pitch = 0.0;    /* rotate about X axis 'p' */
static GLfloat heading = 0.0;  /* rotate about Y axis 'h' */
static GLfloat pos[3] = {0.0, 0.0, 0.0};
static int background = 1;     /* background color 'b' */
static int wire = 0;           /* draw wireframe 'w' */
static int vert = 0;           /* draw vertices 'v' */
static int colorz = 0;         /* color based on z 'c' */
static int trimz = 0;          /* trim farthest z  't' */
static int numvert = 0;        /* number vertices  'n' */
static GLfloat zavg = 0.0;
static GLfloat zmin = 0.0;
static GLfloat zmax = 0.0;
static int iselect = -1;       /* non zero means vertex selected */

static GLfloat amb[4] = { 0.329412, 0.223529, 0.027451, 1.0}; /* brass */
static GLfloat dif[4] = { 0.780392, 0.568627, 0.113725, 1.0}; /* material */
static GLfloat spe[4] = { 0.992157, 0.941176, 0.807843, 1.0};
static GLfloat shiney = 0.21794872;

                                                  /* light source    */
static GLfloat ambient[] = {0.0, 0.0, 0.0, 1.0};  /* no   ambient    */
static GLfloat diffuse[] = {0.4, 0.4, 0.4, 1.0};  /* mid    diffuse  */
static GLfloat specular[] = {0.4, 0.4, 0.4, 1.0}; /* mid    specular */
static GLfloat position[] = {0.0, 0.0, 3.0, 0.0};

static GLdouble mmatrix[16];   /* 4 by 4 model transformation matrix */
static GLdouble pmatrix[16];   /* 4 by 4 perspective transformation matrix */
static GLint viewport[4];
static int win_w, win_h;

/* colorwf.c  color wheel based on parameter  0 < x 1                      */
/*            colorf(X, &R, &G, &B) returns R,G,B in 0.0 to 1.0            */
static void colorf(float X, float *RR, float *GG, float *BB)
{
  float A, AA, AAA; /* temporaries */
  float R, G, B;    /* red, green, blue intensities */

  A = 2.0*X-1.0;    /* -1.0 < A < 1.0 */
  if(A<-1.0) A = -1.0;
  if(A>1.0)  A = 1.0;
  R = 1.0 - abs(A);
  AA = A - 2.0/3.0;
  if(AA < -1.0) AA = 2.0+AA;
  G = 1.0 - abs(AA);
  AA = A + 2.0/3.0;
  if(AA > 1.0) AA = 2.0-AA;
  B = 1.0 - abs(AA);

  /* boost low end and normalize (this brightens colors) */
  R = R+0.2;
  G = G+0.2;
  B = B+0.2;
  AAA = max(R, G);
  AAA = max(AAA, B);
  *RR = R/AAA;
  *GG = G/AAA;
  *BB = B/AAA;
  /*  glColor3f(R, G, B); */
} /* end colorf */


static void draw_solid_dat(void)
{
  int i, j, k, pts, pt;
  GLfloat v[3][3];
  int kk[3];
  GLfloat ax, ay, az, bx, by, bz, nx, ny, nz, s;
  
  if(debug) printf("draw_solid_dat\n");
  glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
  glLightfv(GL_LIGHT0, GL_POSITION, position);

  glFrontFace(GL_CCW);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_NORMALIZE);
  glEnable(GL_AUTO_NORMAL);
  glEnable(GL_DEPTH_TEST); 
  glLightfv(GL_LIGHT0, GL_POSITION, position);
  glPushMatrix();
    glLoadIdentity();
    glRotatef(pitch, 1.0, 0.0, 0.0);
    glRotatef(heading, 0.0, 1.0, 0.0);
    glRotatef(roll, 0.0, 0.0, 1.0);
    glTranslatef(pos[0], pos[1], pos[2]);
    glGetDoublev(GL_MODELVIEW_MATRIX, mmatrix); /* save transformation */

    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, amb);
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, dif);
    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, spe);
    glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shiney * 64.0);

    /* use pys to do surfaces */
    k = 0;
    for(i=0; i<num_polys; i++)
    {
      pts = data_verts[k++];
      if(debug>2) printf("pts=%d\n", pts);
      glBegin(GL_POLYGON);
        for(j=0; j<3; j++)
        {
          pt = data_verts[k++];
          if(debug>2) printf("j=%d, pt=%d\n", j, pt);
          kk[j] = pt;
          v[j][0] = data_points[pt-1].x;
          v[j][1] = data_points[pt-1].y;
          v[j][2] = data_points[pt-1].z;
        }
        /* compute normals, assuming planar, normalize */
        ax = v[2][0] - v[1][0];
        ay = v[2][1] - v[1][1];
        az = v[2][2] - v[1][2];
        bx = v[1][0] - v[0][0];
        by = v[1][1] - v[0][1];
        bz = v[1][2] - v[0][2];
        nx = ay*bz-az*by;
        ny = az*bx-ax*bz;
        nz = ax*by-ay*bx;
        s = sqrt(nx*nx+ny*ny+nz*nz);
        nx = nx / s;
        ny = ny / s;
        nz = nz / s;
        if(debug>1)
        {
          printf("\nv0x=%f, v0y=%f, v0z=%f\n", v[0][0], v[0][1], v[0][2]);
          printf("v1x=%f, v1y=%f, v1z=%f\n", v[1][0], v[1][1], v[1][2]);
          printf("v2x=%f, v2y=%f, v2z=%f\n", v[2][0], v[2][1], v[2][2]);
          printf("nx=%f, ny=%f, nz=%f\n", nx, ny, nz); 
        }
        for(j=0; j<3; j++)
        {
         glNormal3f(nx, ny, nz);
         glVertex3f(data_points[kk[j]-1].x, data_points[kk[j]-1].y,
                    data_points[kk[j]-1].z);
        }
        for(j=3; j<pts; j++)
        {
          pt = data_verts[k++];
          if(debug>2) printf("j=%d, pt=%d\n", j, pt);
          glNormal3f(nx, ny, nz);
          glVertex3f(data_points[pt-1].x, data_points[pt-1].y,
                     data_points[pt-1].z);
        }
      glEnd();
    } /* end loop on num_polys */
  glPopMatrix();
} /* end draw_solid_dat */

static void draw_wire_dat(void)
{
  int i, j, k, pts, pt;
  int skipz=0;
  GLdouble winx, winy, winz;

  glDisable(GL_LIGHTING);
  glDisable(GL_LIGHT0);
  glDisable(GL_NORMALIZE);
  glDisable(GL_AUTO_NORMAL);
  glPointSize(3.0);
  glPushMatrix();
    glLoadIdentity();
    glRotatef(pitch, 1.0, 0.0, 0.0);
    glRotatef(heading, 0.0, 1.0, 0.0);
    glRotatef(roll, 0.0, 0.0, 1.0);
    glTranslatef(pos[0], pos[1], pos[2]);
    glGetDoublev(GL_MODELVIEW_MATRIX, mmatrix); /* save transformation */
    glGetDoublev(GL_PROJECTION_MATRIX, pmatrix);
    glGetIntegerv (GL_VIEWPORT, viewport);

    if(trimz || colorz)
    {
      zavg = 0.0;
      for(i=0; i<num_points; i++)
      {
        gluProject(data_points[i].x, data_points[i].y, data_points[i].z,
	           mmatrix, pmatrix, viewport, &winx, &winy, &winz);
        zavg = zavg + winz;
	if(i==0) { zmin = winz; zmax = winz; }
	if(winz < zmin) zmin = winz;
        if(winz > zmax) zmax = winz;		
      }
      zavg = zavg/ (double)num_points;
    }

    /* use num_polys to do wire frame */
    k = 0;
    for(i=0; i<num_polys; i++)
    {
      pts = data_verts[k++];
      if(debug>2) printf("wire pts=%d\n", pts);
      if(background)  glColor3f(0.0, 0.0, 0.0);
      if(!background) glColor3f(1.0, 1.0, 1.0);
      for(j=0; j<pts; j++)
      {
        pt = data_verts[k+j]; /* do not increment, just looking */
	if(pt-1 == iselect) glColor3f(1.0, 0.0, 0.0); /* selected color */
      }
      glBegin(GL_LINE_LOOP);
        for(j=0; j<pts; j++)
        {
          pt = data_verts[k++];
          if(debug>2) printf("j=%d, pt=%d\n", j, pt);
          glVertex3f(data_points[pt-1].x, data_points[pt-1].y,
		             data_points[pt-1].z);
        }
      glEnd();
    } /* end loop on num_polys */
  glPopMatrix();
} /* end draw_wire_dat */

static void draw_vert_dat(void)
{
  int i, j, k, pts, pt;
  GLdouble winx, winy, winz;
  GLfloat zc, R, G, B;

  glDisable(GL_LIGHTING);
  glDisable(GL_LIGHT0);
  glDisable(GL_NORMALIZE);
  glDisable(GL_AUTO_NORMAL);
  glPushMatrix();
    glLoadIdentity();
    glRotatef(pitch, 1.0, 0.0, 0.0);
    glRotatef(heading, 0.0, 1.0, 0.0);
    glRotatef(roll, 0.0, 0.0, 1.0);
    glTranslatef(pos[0], pos[1], pos[2]);
    glGetDoublev(GL_MODELVIEW_MATRIX, mmatrix); /* save transformation */
    glGetDoublev(GL_PROJECTION_MATRIX, pmatrix);
    glGetIntegerv (GL_VIEWPORT, viewport);

    if(trimz || colorz)
    {
      zavg = 0.0;
      for(i=0; i<num_points; i++)
      {
        gluProject(data_points[i].x, data_points[i].y, data_points[i].z,
	               mmatrix, pmatrix, viewport, &winx, &winy, &winz);
	zavg = zavg + winz;
	if(i==0) { zmin = winz; zmax = winz; }
	if(winz < zmin) zmin = winz;
        if(winz > zmax) zmax = winz;		
      }
      zavg = zavg/ (double)num_points;
    }

    /* use num_polys to do vertices */
    k = 0;
    for(i=0; i<num_polys; i++)
    {
      pts = data_verts[k++];
      if(debug>2) printf("vert pts=%d\n", pts);
      for(j=0; j<pts; j++)
      {
        pt = data_verts[k++];
        if(debug) printf("j=%d, wire look pt=%d\n", j, pt);
        if(pt-1 == iselect)
        {
          glPointSize(5.0);
	  glColor3f(1.0, 0.0, 0.0); /* selected color */
	}
	else
	{
          glPointSize(3.0);
          if(colorz)
	  {
            gluProject(data_points[pt-1].x, data_points[pt-1].y,
                       data_points[pt-1].z,
	               mmatrix, pmatrix, viewport, &winx, &winy, &winz);
	    zc =  winz;
            colorf((zc-zmin)/(zmax-zmin), &R, &G, &B);
            glColor3f(R, G, B);
	  }
          else
	  {
            if(background)  glColor3f(0.0, 0.0, 0.0);
            if(!background) glColor3f(1.0, 1.0, 1.0);
	  }
        }
        if(trimz)
	{
          gluProject(data_points[pt-1].x, data_points[pt-1].y,
                     data_points[pt-1].z,  mmatrix, pmatrix,
                     viewport, &winx, &winy, &winz);
          if(winz<zavg) continue;
	}
        glBegin(GL_POINTS);
        glVertex3f(data_points[pt-1].x, data_points[pt-1].y,
		   data_points[pt-1].z);
        glEnd();
      }
    } /* end loop on num_polys */
  glPopMatrix();
} /* end draw_vert_dat */

static void writeText(GLfloat x, GLfloat y, GLfloat z,
                      GLfloat size, char * msg)
{            /* uses background and wire frame */
  char * p;
  GLfloat wht[] = {1.0, 1.0, 1.0, 1.0};
  GLfloat blk[] = {0.0, 0.0, 0.0, 0.0};

  glPushMatrix();
    glLoadIdentity ();
    if(background)
    {
      if(!wire)
      { 
        glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, blk); 
        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, blk);
      }
      else
      {
        glColor3f(0.0, 0.0, 0.0);
      }
    }
    else
    {
      if(!wire)
      { 
        glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, wht); 
        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, wht);
      }
      else
      {
        glColor3f(1.0, 1.0, 1.0);
      }
    } 
    glEnable(GL_LINE_SMOOTH);
    glEnable(GL_BLEND);
    glTranslatef(x, y, z);
    glScalef(size/1000.0, size/1000.0, 0.0);
    for(p=msg; *p; p++)
      glutStrokeCharacter(GLUT_STROKE_ROMAN, *p);
  glPopMatrix();
} /* end writetext */

static void draw_numvert()
{
  int i, j, k, m, pts, pt;
  GLdouble winx, winy, winz;
  GLfloat zc, R, G, B;
  GLfloat pltx, plty, hw, hh;
  char npt[4] = "   ";

  glDisable(GL_LIGHTING);
  glDisable(GL_LIGHT0);
  glDisable(GL_NORMALIZE);
  glDisable(GL_AUTO_NORMAL);
  glPushMatrix();
    glLoadIdentity();
    glRotatef(pitch, 1.0, 0.0, 0.0);
    glRotatef(heading, 0.0, 1.0, 0.0);
    glRotatef(roll, 0.0, 0.0, 1.0);
    glTranslatef(pos[0], pos[1], pos[2]);
    glGetDoublev(GL_MODELVIEW_MATRIX, mmatrix); /* save transformation */
    glGetDoublev(GL_PROJECTION_MATRIX, pmatrix);
    glGetIntegerv (GL_VIEWPORT, viewport);

    hw = (GLfloat)width/2.0;
    hh = (GLfloat)height/2.0;
    zavg = 0.0; /* crude trim back */
    for(i=0; i<num_points; i++)
    {
      gluProject(data_points[i].x, data_points[i].y, data_points[i].z,
	             mmatrix, pmatrix, viewport, &winx, &winy, &winz);
      zavg = zavg + winz;
      if(i==0) { zmin = winz; zmax = winz; }
      if(winz < zmin) zmin = winz;
      if(winz > zmax) zmax = winz;		
    }
    zavg = zavg/ (double)num_points;

    /* use num_polys to do vertices */
    k = 0;
    for(i=0; i<num_polys; i++)
    {
      pts = data_verts[k++];
      for(j=0; j<pts; j++)
      {
        pt = data_verts[k++];
        gluProject(data_points[pt-1].x, data_points[pt-1].y,
                   data_points[pt-1].z,
	           mmatrix, pmatrix, viewport, &winx, &winy, &winz);
	zc =  winz;
        if(background)  glColor3f(0.0, 0.0, 0.0);
        if(!background) glColor3f(1.0, 1.0, 1.0);
        gluProject(data_points[pt-1].x, data_points[pt-1].y,
                   data_points[pt-1].z,  mmatrix, pmatrix,
                   viewport, &winx, &winy, &winz);
        if(winz<zavg) continue;
        sprintf(npt,"%2d\0",pt);
	printf("npt=%s, datx=%g, daty=%g\n", npt,
               data_points[pt-1].x, data_points[pt-1].y);
        printf("winx=%g, winy=%g\n", winx, winy);
        pltx=offs*(winx-hw)/hw;
        plty=offs*(winy-hh)/hh;
        printf("pltx=%g, plty=%g\n", pltx, plty);
        writeText(pltx, plty, 0.0, size/2.5, npt);
      } /* end points */
    } /* end loop on num_polys */
  glPopMatrix();
} /* end draw_numvert */

void display(void)
{
  if(background)  glClearColor(1.0, 1.0, 1.0, 1.0); 
  if(!background) glClearColor(0.0, 0.0, 0.0, 1.0); 
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  if(vert)      draw_vert_dat();
  else if(wire) draw_wire_dat();
  else          draw_solid_dat();
  if(numvert)   draw_numvert();
  writeText(-size*offs, -size*offs+size/8.0, 2.0*size-1.0, size/2.5,
            "Key t,T for trim back vertices, c,C change color"); 
  writeText(-size*offs, -size*offs+size/16.0, 2.0*size-1.0, size/2.5,
            "Key r,R roll, p,P pitch, h,H heading,  b toggles background"); 
  writeText(-size*offs, -size*offs, 2.0*size-1.0, size/2.5,
            "Key x,X y,Y z,Z to position, w for wireframe, v for vertices, n for vertex numbers");  
  glFlush();
  glutSwapBuffers();
} /* end display */


static GLdouble dist(GLdouble a1x, GLdouble a1y, GLdouble a1z, /* line p1 */
                     GLdouble a2x, GLdouble a2y, GLdouble a2z, /* line p2 */
                     GLdouble a3x, GLdouble a3y, GLdouble a3z) /* point   */
{
  GLdouble d, ax, ay, az, bx, by, bz, cx, cy, cz;
  
  ax = a2x-a1x;      ay = a2y-a1y;      az = a2z-a1z;
  bx = a1x-a3x;      by = a1y-a3y;      bz = a1z-a3z;
  cx = ay*bz-az*by;  cy = ax*bz-az*bx;  cz = ax*by-ay*bx;
  d =  sqrt(cx*cx+cy*cy+cz*cz)/sqrt(ax*ax+ay*ay+az*az);
  return d; /* distance from point to line */
} /* end dist */

void mouse(int button, int state, int x, int y) 
{
   GLint realy;  /*  OpenGL y coordinate position  */
   GLdouble wx0, wy0, wz0;  /*  returned world x, y, z coords at near */
   GLdouble wx1, wy1, wz1;  /*  returned world x, y, z coords at far */
   GLdouble d, dmin;
   int i;

   if(button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
   {
     glGetIntegerv (GL_VIEWPORT, viewport);
     glGetDoublev(GL_PROJECTION_MATRIX, pmatrix);
     /*  note viewport[3] is height of window in pixels  */
     realy = viewport[3] - (GLint) y - 1;
     gluUnProject((GLdouble) x, (GLdouble) realy, 0.0, /* near */
                  mmatrix, pmatrix, viewport, &wx0, &wy0, &wz0); 
     gluUnProject((GLdouble) x, (GLdouble) realy, 1.0, /* far */
                  mmatrix, pmatrix, viewport, &wx1, &wy1, &wz1); 
     iselect = 0;
     for(i=0; i<num_points; i++)
     {
       d = dist(wx0, wy0, wz0, wx1, wy1, wz1,
                data_points[i].x, data_points[i].y, data_points[i].z);
       if(i==0) dmin = d;
       if(debug) printf("distance, d to point %d is %g\n", i, d);
       if(d < dmin) { dmin = d; iselect = i; }
     }
     printf("%g close to data_points[%d]\n", dmin, iselect);
  }
  if(button==GLUT_MIDDLE_BUTTON && state == GLUT_DOWN) iselect = 0; /* commit */
  if(button==GLUT_RIGHT_BUTTON && state == GLUT_DOWN) iselect = 0;  /* drag */
  glutPostRedisplay();
} /* end mouse */

void myReshape(int w, int h)
{
  GLfloat wsize = offs*size;
  width = w;
  height = h;
  glViewport(0, 0, w, h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if(w <= h)
      glOrtho(-wsize, wsize, -wsize * (GLfloat)h / (GLfloat)w,
              wsize * (GLfloat)h / (GLfloat)w, -5.0*wsize, 5.0*wsize);
  else
      glOrtho(-wsize * (GLfloat)w / (GLfloat)h,
              wsize * (GLfloat)w / (GLfloat)h, -wsize, wsize,
              -5.0*wsize, 5.0*wsize);
  glMatrixMode(GL_MODELVIEW);
} /* end myReshape */

/* x,X,y,Y,z and Z keys for moving position
   r,R,p,P,h and H keys for rool, pitch and heading
   w toggle wire frame, v toggle vertices
   b toggle background, c toggle color in z */
void keyboard (unsigned char key, int x, int y)
{
  if(key == 'x') pos[0] -= 0.125; /* position object */
  if(key == 'X') pos[0] += 0.125;
  if(key == 'y') pos[1] -= 0.125;
  if(key == 'Y') pos[1] += 0.125;
  if(key == 'z') pos[2] -= 0.125;
  if(key == 'Z') pos[2] += 0.125;
  if(key == 'r') roll    -= 5.0;  /* rotate object */
  if(key == 'R') roll    += 5.0;
  if(key == 'p') pitch   -= 5.0;
  if(key == 'P') pitch   += 5.0;
  if(key == 'h') heading -= 5.0;
  if(key == 'H') heading += 5.0;
  if(key == 'b' || key == 'B') background = 1-background;
  if(key == 'w' || key == 'W') wire = 1-wire;
  if(key == 'v' || key == 'V') vert = 1-vert;
  if(key == 'c' || key == 'C') colorz = 1-colorz;
  if(key == 't' || key == 'T') trimz = 1-trimz;
  if(key == 'n' || key == 'N') numvert = 1-numvert;
  if(key == 's' || key == 'S') /* save object in .dat format */
  {
    if(strlen(save_filename)>3)
      datwrite(save_filename, data_points, num_points,
               data_verts, num_polys);
  }
  glutPostRedisplay();
} /* end keyboard */


int main(int argc, char *argv[])
{
  glutInit(&argc, argv);
  /* need both double buffering and z buffer */
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutInitWindowSize(width, height);
  glutCreateWindow(argv[0]);
  glutReshapeFunc(myReshape);
  glutDisplayFunc(display);
  glutMouseFunc(mouse);       /* "mouse" setup    */
  glutKeyboardFunc(keyboard); /* "keyboard" setup */
  glEnable(GL_DEPTH_TEST);    /* Enable hidden--surface--removal */
  /* read a .dat file */
  if(argc>1)
  {
    status = datread(argv[1], &data_points, &num_points,
                              &data_verts, &num_polys, &size);
  }
  else
  {
    printf("a filename of a .dat file is required.\n");
    return 0;
  }
  if(status!=0)
  {
    printf("%s file not read as a .dat file\n", argv[1]);
    return 0;
  }
  save_filename[0] = '\0';
  if(argc>2) strcpy(save_filename, argv[2]);
  if(debug) printf("starting MainLoop num_points=%d, num_polys=%d, size=%g\n",
                    num_points, num_polys, size);
  if(size<1.0) size=1.0;
  glutMainLoop();
  return 0;
} /* end main of light_dat3.c */

