/* surf3d.c                                          */
/* plane at x, X, y, Y, z, Z */

/* We use the surf3d view function in the display callback to point
   the object, whose position can be altered by the
   x,X,y,Y,z and Z keys for object position
   r,R,p,P,h and H keys for rool, pitch and heading
   The perspective view is set in the reshape callback */
   
#include <stdlib.h>
#include <GL/glut.h>
#include <math.h>

static GLfloat roll=0.0;
static GLfloat pitch=90.0;
static GLfloat heading=0.0;
static GLfloat scale=4.0;

/* initial object location */
static GLdouble object[]= {0.0, -2.0, 0.0}; 

void cyl_spiral(float a, float b, float c, float n)
{
  struct point { float x, y, z; };
  struct point raw[63][15];
  struct tri_poly { int p1, p2, p3; };
  int i, j;
  double x, y, z;
  struct point p1, p2, p3;
  
  int nx = 63;     /* last == first */
  int ny = 15;
  double pi = 3.141592653589793238462643383279502884197;

  double u = 0.0; /* parametric in u and v */
  double du = 2.0*pi/(double)ny;
  double v = 0.0;
  double dv = 2.0*pi/(double)(nx-1);

  for(i=0; i<nx; i++)
  {
    u = 0.0;
    for(j=0; j<ny; j++)
    {
      raw[i][j].x = a*cos(n*v)*(1.0+cos(u))+c*cos(n*v);
      raw[i][j].y = a*sin(n*v)*(1.0+cos(u))+c*sin(n*v);
      raw[i][j].z = b*v/(2.0*pi) + a*sin(u);
      u = u + du;
    }
    v = v + dv;
  }

  /* draw as triangles */
  for(i=0; i<(nx-1); i++)
  {
    for(j=0; j<ny; j++)
    {
      p1 = raw[i][j];
      p2 = raw[i][(j+1)%ny];
      p3 = raw[(i+1)%nx][(j+1)%ny];
      glBegin(GL_LINE_LOOP);
        glVertex3f(p1.x, p1.y, p1.z);
        glVertex3f(p2.x, p2.y, p2.z);
        glVertex3f(p3.x, p3.y, p3.z);
      glEnd();
      p1 = raw[i][j];
      p2 = raw[(i+1)%nx][(j+1)%ny];
      p3 = raw[(i+1)%nx][j];
      glBegin(GL_LINE_LOOP);
        glVertex3f(p1.x, p1.y, p1.z);
        glVertex3f(p2.x, p2.y, p2.z);
        glVertex3f(p3.x, p3.y, p3.z);
      glEnd();
    }
  }
} /* end cyl_spiral */


void surf3dView()
{
  glScalef(scale, scale, scale);
  glRotatef(roll, 0.0, 0.0, 1.0);
  glRotatef(pitch, 1.0, 0.0, 0.0);
  glRotatef(heading, 0.0, 1.0, 0.0);
  glTranslatef(object[0], object[1], object[2]);
}

static void printstring(void *font, char msg[])
{
   int len, i;

   len = (int)strlen(msg);
   for(i=0; i<len; i++)
      glutBitmapCharacter(font, msg[i]);
} /* end printstring */

void display(void)
{
  char text[]="move object using x, X, y, Y, z, Z";
  char text2[]="roll, pitch, heading  r, R, p, P, h, H";
  char *p;
  
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  /* Update object position in modelview matrix */
  glPushMatrix();
    glLoadIdentity();
    /* from object to center of scene */
    surf3dView();
    /* draw object*/
    glColor3f(0.5, 0.5, 0.5); /* gray */
    cyl_spiral(0.1, 1.0, 0.5, 3.0); /* a, b, c, n of P306 */
  glPopMatrix();
  
  glPushMatrix();
    glLoadIdentity();
    glColor3f(1.0, 1.0, 0.0);
    glTranslatef(0.0, 0.0, -1.1);
    glRasterPos2f(-1.8, -1.75);
    printstring(GLUT_BITMAP_TIMES_ROMAN_24, text);
    glRasterPos2f(-1.8, -1.95);
    printstring(GLUT_BITMAP_TIMES_ROMAN_24, text2);
  glPopMatrix();

  glFlush();
  glutSwapBuffers();
}

void keys(unsigned char key, int x, int y)
{
  /* Use x, X, y, Y, z, and Z keys to move object */
  if(key == 'x') object[0] -= 1.0;
  if(key == 'X') object[0] += 1.0;
  if(key == 'y') object[1] -= 0.5; /* could clamp at zero */
  if(key == 'Y') object[1] += 0.5;
  if(key == 'z') object[2] -= 1.0;
  if(key == 'Z') object[2] += 1.0;
  /* Use r, R, p, P, h, and H keys to move roll, pitch, heading */
  if(key == 'r') roll    -= 2.0;
  if(key == 'R') roll    += 2.0;
  if(key == 'p') pitch   -= 2.0;
  if(key == 'P') pitch   += 2.0;
  if(key == 'h') heading -= 2.0;
  if(key == 'H') heading += 2.0;
  display();
}

void myReshape(int w, int h)
{
  glViewport(0, 0, w, h);
  /* Use a perspective view */
  glMatrixMode(GL_PROJECTION); 
  glLoadIdentity();
    glFrustum(-2.0, 2.0, -2.0, 2.0, 1.0, 20.0);
           /* xmin xmax  ymin ynax  near  far */
  glMatrixMode(GL_MODELVIEW);
}

int main(int argc, char *argv[])
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutInitWindowSize(500, 500);
  glutCreateWindow(argv[0]);
  glutReshapeFunc(myReshape);
  glutDisplayFunc(display);
  glutKeyboardFunc(keys);
  glEnable(GL_DEPTH_TEST);
  glutMainLoop();
  return 0;
}

