/* light_dat.c  Rotating *.dat with lighting */
/*             Utah Graphics ASCII data *.dat of *.det */

/* Demonstration of use of homogeneous coordinate */
/* transformations and simple data structure for representing */
/* lighting model, needs normal vectors */
/* Mouse buttons control direction of rotation */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <GL/glut.h>

static FILE * infile;
static char file_name[127];
static char input_line[255];
static int num_pts;
static int num_pys;
typedef struct {GLfloat x; GLfloat y; GLfloat z;
                GLfloat nx; GLfloat ny; GLfloat nz;} dpts;
static dpts * data_points; /* malloc'd space for vertices */
static int * polys;        /* malloc'd space for polygon index to vertices */
static int ipoly = 0;      /* index to polys: count, indices, ... */
static int npoly = 0;
static int debug = 0;
static GLuint dataList;
static GLfloat size = 1.0; /* default, may get larger */
static int background = 0;

static GLfloat theta[3] = {0.0, 0.0, 0.0};
static GLfloat dtheta = 2.0;
static GLint axis = 2;

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};
static GLfloat spe[4] = { 0.992157, 0.941176, 0.807843, 1.0};
static GLfloat shiney = 0.21794872;

static GLfloat position[] = {0.0, 3.0, 4.0, 0.0};
/* Initialize depth buffer, projection matrix, */
/* light source, and lighting model.           */
/* Do not specify a material property here.    */

void init(void)
{
  int i, j, k, pts;
                                             /* light source    */
  GLfloat ambient[] = {0.0, 0.0, 0.0, 1.0};  /* no ambient    */
  GLfloat diffuse[] = {1.0, 1.0, 1.0, 1.0};  /* strong diffuse  */
  GLfloat specular[] = {1.0, 1.0, 1.0, 1.0}; /* strong specular */

  GLfloat lmodel_ambient[] = {0.2, 0.2, 0.2, 1.0};
  GLfloat local_view[] = {0.0};

  glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
  glLightfv(GL_LIGHT0, GL_POSITION, position);
  glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
  glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view);

  glFrontFace(GL_CCW);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_AUTO_NORMAL);
  glEnable(GL_NORMALIZE);
  glEnable(GL_DEPTH_TEST); 
  glClearColor(1.0, 1.0, 1.0, 1.0); 
  
  /*  be efficient--make data display list  */
  dataList = glGenLists(1);
  glNewList(dataList, GL_COMPILE);
    /* use pys to do surfaces */
    ipoly = 0;
    for(i=0; i<num_pys; i++)
    {
      pts = polys[ipoly++];
      if(debug) printf("pts=%d\n", pts);
      glBegin(GL_POLYGON);
        for(j=0; j<pts; j++)
        {
          k = polys[ipoly++];
          if(debug) printf("j=%d, k=%d\n", j, k);
          glNormal3f(data_points[k-1].nx, data_points[k-1].ny, data_points[k-1].nz);
          glVertex3f(data_points[k-1].x,  data_points[k-1].y,  data_points[k-1].z);
          if(debug) printf("k=%d, x=%g, y=%g, z=%g, nx=%g, ny=%g, nz=%g\n", k,
                  data_points[k-1].x,  data_points[k-1].y,  data_points[k-1].z,
                  data_points[k-1].nx, data_points[k-1].ny, data_points[k-1].nz);
        }
      glEnd();
    }
  glEndList();
}

static writeText(GLfloat x, GLfloat y, GLfloat z, GLfloat size, char * msg)
{
  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)
    {
      glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, blk); 
      glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, blk);
    }
    else
    {
      glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, wht); 
      glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, wht);
    } 
    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();
}

void display(void)
{
  /* display callback, clear frame buffer and z buffer, */
  /* rotate Dat and draw, swap buffers */
  if(background) glClearColor(1.0, 1.0, 1.0, 1.0); 
  else           glClearColor(0.0, 0.0, 0.0, 1.0); 
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glLoadIdentity();
  glLightfv(GL_LIGHT0, GL_POSITION, position);
  writeText(-size+0.25, -size+0.25+size/16.0, 2.0*size-1.0, size/2.0,
            "keys: s=slower, S=faster, w=white background, or b=black"); 
  writeText(-size+0.25, -size+0.25, 2.0*size-1.0, size/2.0,
            "x, y, z axis of rotation"); 
  writeText(-size+0.25, size-size/16.0, 2.0*size-1.0, size/2.0, file_name); 
  glPushMatrix();
    glLoadIdentity();
    glRotatef(theta[0], 1.0, 0.0, 0.0);
    glRotatef(theta[1], 0.0, 1.0, 0.0);
    glRotatef(theta[2], 0.0, 0.0, 1.0);

    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 * 128.0);
    glCallList(dataList);
  glPopMatrix();
  glFlush();
  glutSwapBuffers();
}

void spinDat(void)
{
  /* Idle callback, spin Dat 2 degrees about selected axis */
  theta[axis] = theta[axis] + dtheta;
  if(theta[axis] > 360.0) theta[axis] = theta[axis] - 360.0;
  glutPostRedisplay();
}

void mouse(int btn, int state, int x, int y)
{
  /* mouse callback, selects an axis about which to rotate */
  if(btn==GLUT_LEFT_BUTTON && state == GLUT_DOWN) axis = 0;
  if(btn==GLUT_MIDDLE_BUTTON && state == GLUT_DOWN) axis = 1;
  if(btn==GLUT_RIGHT_BUTTON && state == GLUT_DOWN) axis = 2;
}

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

void keyboard (unsigned char key, int x, int y)
{
  switch (key) {
    case 'S':
      dtheta = dtheta + 0.25;
      glutPostRedisplay();
      break;
    case 's':
      dtheta = dtheta - 0.25;
      glutPostRedisplay();
      break;
    case 'x':
      axis = 0; /* same as left mouse button */
      glutPostRedisplay();
      break;
    case 'y':
      axis = 1; /* same as middle mouse button */
      glutPostRedisplay();
      break;
    case 'z':
      axis = 2; /* same as right mouse button */
      glutPostRedisplay();
      break;
    case 'W': /* also arrow key */
      background = 1;
      break;
    case 'w':
      background = 1;
      break;
    case 'B':
      background = 0;
      break;
    case 'b':
      background = 0;
      break;
    case 27:
      exit(0);
      break;
    default:
      break;
   }
}

#undef abs
#define abs(x) (((x)<0.0)?(-(x)):(x))
int main(int argc, char *argv[])
{
  int i, j, k, pts;
  GLfloat v[3][3];
  int kk[3];
  GLfloat ax, ay, az, bx, by, bz, nx, ny, nz, s;
  
  /* get file name of data to render */
  if(argc<2)
  {
    printf("supply a file name of a Utah data file\n");
    exit(0);
  }
  else
  {
    printf("open %s for reading\n", argv[1]);
    infile = fopen(argv[1], "r");
    if(infile == NULL)
    {
      printf("could not open %s for reading\n", argv[1]);
      exit(0);
    }
    strcpy(file_name, argv[1]);
    fgets(input_line, 254, infile);
    printf("first line %s \n", input_line);
    if(!strncmp("data", input_line, 4))
    {
      sscanf(input_line, "data%d %d", &num_pts, &num_pys);
    }
    else
    {
      sscanf(input_line, "%d %d", &num_pts, &num_pys);
    }
    printf("num_pts=%d, num_pys=%d\n", num_pts, num_pys);
    data_points = (dpts *)malloc(sizeof(dpts)*num_pts);
    if(data_points == NULL)
    {
      printf("can not allocate enough space for points\n");
      exit(0);
    }
  }
  for(i=0; i<num_pts; i++)
  {
    fscanf(infile, " %f %f %f", &data_points[i].x,
           &data_points[i].y, &data_points[i].z);
    data_points[i].nx = 0.0; /* normals averaged */
    data_points[i].ny = 0.0;
    data_points[i].nz = 0.0;
    if(debug) printf("i=%d, x=%f, y=%f, z=%f\n", i+1, data_points[i].x,
                     data_points[i].y, data_points[i].z);
    if(abs(data_points[i].x)>size) size = abs(data_points[i].x);
    if(abs(data_points[i].y)>size) size = abs(data_points[i].y);
    if(abs(data_points[i].z)>size) size = abs(data_points[i].z);
  }
  printf("scaling to size=%g\n", size);
  size = size * 2.0; /* for spinning */
  position[2] = 2.0*size;
  
  /* now bring in polygon vertex indices */
  polys = (int *)malloc(sizeof(int)*10*num_pys);
  if(polys == NULL)
  {
    printf("can not allocate enough space for polygon indices\n");
    exit(0);
  }
  npoly = 0;
  for(i=0; i<num_pys; i++)
  {
    fscanf(infile, "%d", &pts);
    polys[npoly++] = pts;
    if(debug) printf("pts=%d\n", pts);
    if(pts<3)
    {
      printf("bad polygon vertices at i=%d, pts=%d\n", i, pts);
      exit(0);
    }
    for(j=0; j<3; j++)
    {
      fscanf(infile, "%d", &k);
      polys[npoly++] = k;
      if(debug) printf("j=%d, k=%d\n", j, k);
      kk[j] = k;
      v[j][0] = data_points[k-1].x;
      v[j][1] = data_points[k-1].y;
      v[j][2] = data_points[k-1].z;
    }
    /* compute, normalize and average normals */
    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;

    data_points[kk[0]-1].nx += nx; /* sum normals */
    data_points[kk[0]-1].ny += ny;
    data_points[kk[0]-1].nz += nz;
    data_points[kk[1]-1].nx += nx;
    data_points[kk[1]-1].ny += ny;
    data_points[kk[1]-1].nz += nz;
    data_points[kk[2]-1].nx += nx;
    data_points[kk[2]-1].ny += ny;
    data_points[kk[2]-1].nz += nz;
        
    for(j=3; j<pts; j++)
    {
      fscanf(infile, "%d", &k);
      polys[npoly++] = k;
      if(debug) printf("j=%d, k=%d\n", j, k);
      data_points[k-1].nx += nx;
      data_points[k-1].ny += ny;
      data_points[k-1].nz += nz;
    }
  } /* finished inputting .dat file */
  if(debug)printf("%d polys count and indices stored\n", npoly);

  glutInit(&argc, argv);
  /* need both double buffering and z buffer */
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutInitWindowSize(500, 500);
  glutCreateWindow(argv[0]);
  glutReshapeFunc(myReshape);
  glutDisplayFunc(display);
  glutIdleFunc(spinDat);      /* "spinDat" setup  */
  glutMouseFunc(mouse);       /* "mouse" setup    */
  glutKeyboardFunc(keyboard); /* "keyboard" setup */
  glEnable(GL_DEPTH_TEST);    /* Enable hidden--surface--removal */
  init();
  glutMainLoop();
  return 0;
}

