/* curv_surf.c  demonstrate some neat curves and surfaces */

#include <stdio.h>
#include <math.h>
#include <time.h>

#ifndef M_PI
#define M_PI 3.14159265
#endif
#include <GL/glut.h>

typedef GLfloat pts3[3];
static pts3 points[500];
static int max_pts = 500;
static int n_points = 0;
static int n = 0;
static int width=600;
static int height=650;
static int drawpts = 0;
static int dynamic = 0;
static int dyn_pt = 0;
static int dyn_pt_max = 498;
static float dyn_val = 0.0;
static float dyn_val_max = 1.0;
static float dyn_val_inc = 0.01;
static float param1;
static float param2;
static double now, next, tinc;

#define CYCLOID     20
#define SPIRAL      21
#define LISSAJOUS   22
#define ROSE        23
#define HELIX3D     30
#define TORSPIRAL3D 31
#define QUIT         1
#define DRAWPOINTS   2
#define DYNAMIC      3
#define BULLSEYE     4
#define TARGET       5
#define TRAIL        6
#define SLOWER       7
#define FASTER       8
#define P1UP         9
#define P1DOWN      10
#define P2UP        11
#define P2DOWN      12
static int curve_type = LISSAJOUS;
static int target_type = TRAIL;
GLUquadricObj *obj;

/* function prototypes for curves */
void compute_curve(void);
void cycloid(float ab_ratio, float cycles, float xmin, float xmax,
             float ymin, float ymax, int npts, pts3 points[]);
void spiral(float gap, float rmax, int npts, pts3 points[]);
void lissajous(float a, float b, float rmax, int npts, pts3 points[]);
void rose(float m, float rmax, int npts, pts3 points[]);
void helix(float rmax, float gap, int npts, pts3 points[]);
void toroidal_spiral(float inrad, float outrad, float cycles, int npts, pts3 points[]);

void display(void)
{
  char text[]="arrows change parameters, 'd' toggle dynamic, 'p' toggle point";
  char text2[]="mouse selects menu, then drag to curve type or options";
  char *p;
  int i, j;
  pts3 p1, p2;

  /* clear window */
  glClear(GL_COLOR_BUFFER_BIT); 
  glLoadIdentity ();
  glColor3f(0.0, 0.0, 0.0);
  
  /* draw rectangle  -1.0 to 1.0 */
  glBegin(GL_LINE_LOOP);
    glVertex2f(-1.0,  1.0);
    glVertex2f(-1.0, -1.0);
    glVertex2f( 1.0, -1.0);
    glVertex2f( 1.0,  1.0);
  glEnd();

  /* draw text, in its own context */
  glPushMatrix();
    glLoadIdentity ();
    glColor3f(0.0, 0.0, 0.0);
    glEnable(GL_LINE_SMOOTH);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);
    glPushMatrix();
      glTranslatef(-1.0, 1.05, 0.0);
      glScalef(0.0005, 0.0005, 0.0);
      for(p=text; *p; p++)
        glutStrokeCharacter(GLUT_STROKE_ROMAN, *p);
    glPopMatrix();
      glTranslatef(-1.0, 1.15, 0.0);
      glScalef(0.0005, 0.0005, 0.0);
      for(p=text2; *p; p++)
        glutStrokeCharacter(GLUT_STROKE_ROMAN, *p);
  glPopMatrix();
  
  /* Draw the points */
  if(drawpts)
  {
    glColor3f(1.0, 0.0, 0.0);
    glPointSize(4.0);
    for(i=0; i<n; i++)
    {
      glBegin(GL_POINTS);
        glVertex3fv(points[i]);
      glEnd();
    }
  }

  /* draw lines */
  if(!dynamic)
  {
    glColor3f(0.0, 0.0, 1.0);
    for(j=0; j<n_points-1; j++)
    {
      glBegin(GL_LINES);
        glVertex3fv(points[j]);
        glVertex3fv(points[j+1]);
      glEnd();
    }
  }
  
  /* draw target */
  if(dynamic && target_type==TRAIL)
  {
    glColor3f(0.0, 0.0, 1.0);
    glLineWidth(3.0);
    glBegin(GL_LINES);
      glVertex3fv(points[dyn_pt]);
      glVertex3fv(points[dyn_pt+1]);
    glEnd();
    glLineWidth(2.0);
    if(dyn_pt>0)
    {
      glBegin(GL_LINES);
        glVertex3fv(points[dyn_pt-1]);
        glVertex3fv(points[dyn_pt]);
      glEnd();
    }
    glLineWidth(1.0);
    if(dyn_pt>1)
    {
      glBegin(GL_LINES);
        glVertex3fv(points[dyn_pt-2]);
        glVertex3fv(points[dyn_pt-1]);
      glEnd();
    }
  }
  else if(dynamic && target_type==TARGET)
  {
    p1[0] = points[dyn_pt][0]+0.04;
    p1[1] = points[dyn_pt][1];
    p1[2] = points[dyn_pt][2];
    p2[0] = points[dyn_pt][0]-0.04;
    p2[1] = points[dyn_pt][1];
    p2[2] = points[dyn_pt][2];
    glColor3f(0.0, 0.0, 1.0);
    glLineWidth(2.0);
    glBegin(GL_LINES);
      glVertex3fv(p1);
      glVertex3fv(p2);
      p1[0] = p1[0]-0.04;
      p2[0] = p2[0]+0.04;
      p1[1] = p1[1]+0.04;
      p2[1] = p2[1]-0.04;
      glVertex3fv(p1);
      glVertex3fv(p2);
    glEnd();
    glPushMatrix();
      glLoadIdentity();
      glTranslatef(points[dyn_pt][0],points[dyn_pt][1],points[dyn_pt][2]);
      gluDisk(obj, 0.03, 0.04, 12, 12);
    glPopMatrix();
  }
  else if(dynamic && target_type==BULLSEYE)
  {
    glPointSize(4.0);
    glColor3f(1.0, 0.0, 0.0);
    glBegin(GL_POINTS);
        glVertex3fv(points[dyn_pt]);
    glEnd();
    glPushMatrix();
      glLoadIdentity();
      glTranslatef(points[dyn_pt][0],points[dyn_pt][1],points[dyn_pt][2]);
      gluDisk(obj, 0.02, 0.03, 12, 12);
      gluDisk(obj, 0.04, 0.05, 12, 12);
    glPopMatrix();
  }
  
  glFlush();
  glutSwapBuffers();
}

void moveit(void)
{
  int i;
  
  /* Idle callback, show curve in motion */
  dyn_pt++;
  if(dyn_pt>dyn_pt_max) dyn_pt = 0;
  dyn_val = dyn_val+dyn_val_inc;
  if(dyn_val>dyn_val_max) dyn_val = 0.0;
  glutPostRedisplay();

  for(i=0; i<1;) /* at least 'tinc' second between updates */
  {
    now = (double)clock()/(double)CLOCKS_PER_SEC;
    if(now>next) break;
  }
  next = now+tinc;
}


/* This routine handels mouse events */
static void mouse(int button, int state, int x, int y)
{
  glutPostRedisplay();
}

/* This routine handles window resizes */
void reshape(int w, int h)
{
  width = w;
  height = h;
  /* Set the transformations */
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(-1.1, 1.1, -1.1, 1.3, -1.1, 1.1);
  glMatrixMode(GL_MODELVIEW);
  glViewport(0, 0, w, h);
}

void init()
{
  curve_type = LISSAJOUS;
  param1 = 3.0;
  param2 = 5.0;
  n = max_pts;  /* for drawing points*/
  n_points = n; /* for drawing lines */
  
  /* set clear color to white */
  glClearColor (1.0, 1.0, 1.0, 0.0);
  /* set fill  color to black */
  glColor3f(0.0, 0.0, 0.0);
  now = (double)clock()/(double)CLOCKS_PER_SEC;
  tinc = 0.1; /* 1/10 second */
  next = now+tinc;
  compute_curve(); 
} /* end init */

static void keyboard(unsigned char key, int x, int y)
{
  switch (key)
  {
    case 'p':
      drawpts = 1 - drawpts;
      break;
    case 'd':
      dynamic = 1 - dynamic;
      if(dynamic) glutIdleFunc(moveit);
      else        glutIdleFunc(NULL);
      break;
  }
  glutPostRedisplay();
} /* end keyboard */

static void special(int k, int x, int y)
{
  switch(k)
  {
    case GLUT_KEY_LEFT:
      param2 = 0.75 * param2;
      break;
    case GLUT_KEY_RIGHT:
      param2 = 1.25 * param2;
      break;
    case GLUT_KEY_DOWN:
      param1 = 0.75 * param1;
      break;
    case GLUT_KEY_UP:
      param1 = 1.25 * param1;
      break;
  }
  compute_curve();
}

static void ModeMenu(int entry)
{
  if(entry==CYCLOID)
  {
    curve_type = CYCLOID;
    param1 = 2.0;  /* abratio */
    param2 = 10.0; /* cycles */
  }
  else if(entry==SPIRAL) 
  {
    curve_type = SPIRAL;
    param1 = 0.1;  /* gap  */
    param2 = 1.0;  /* rmax */
  }
  else if(entry==LISSAJOUS) 
  {
    curve_type = LISSAJOUS;
    param1 = 5.0;  /* xcycles  */
    param2 = 7.0;  /* ycycles  */
  }
  else if(entry==ROSE) 
  {
    curve_type = ROSE;
    param1 = 4.0;  /* cycles/2 */
    param2 = 0.95; /* radius   */
  }
  else if(entry==HELIX3D) 
  {
    curve_type = HELIX3D;
    param1 = 0.5;  /* inner radius */
    param2 = 10.0;  /* cycles       */
  }
  else if(entry==TORSPIRAL3D) 
  {
    curve_type = TORSPIRAL3D;
    param1 = 0.2;  /* inner radius */
    param2 = 0.8;  /* outer radius */
                   /* gap later    */
  }
   
  else if(entry==DRAWPOINTS) 
  {
    drawpts = 1 - drawpts;
  }
  else if(entry==DYNAMIC) 
  {
    dynamic = 1 - dynamic;
    if(dynamic) glutIdleFunc(moveit);
    else        glutIdleFunc(NULL);
  }
  else if(entry==BULLSEYE) 
  {
    target_type = BULLSEYE;
  }
  else if(entry==TARGET) 
  {
    target_type = TARGET;
  }
  else if(entry==TRAIL)
  {
    target_type = TRAIL;
  }
  else if(entry==SLOWER)
  {
    tinc = 2.0 * tinc;
  }
  else if(entry==FASTER)
  {
    tinc = 0.5 * tinc;
  }
  else if(entry==P1DOWN)
  {
    param1 = 0.75 * param1;
  }
  else if(entry==P1UP)
  {
    param1 = 1.25 * param1;
  }
  else if(entry==P2DOWN)
  {
    param2 = 0.75 * param2;
  }
  else if(entry==P2UP)
  {
    param2 = 1.25 * param2;
  }
  else if(entry==QUIT)
  {
    exit(0);
  }
  compute_curve();
} /* end ModeMenu */

void compute_curve()
{
  if(curve_type==CYCLOID) cycloid(param1, param2, -1.0, 1.0, -1.0, 1.0, max_pts, points); 
  if(curve_type==SPIRAL)  spiral(param1, param2, max_pts, points); 
  if(curve_type==LISSAJOUS) lissajous(param1, param2, 0.95, max_pts, points);
  if(curve_type==ROSE) rose(param1, param2, max_pts, points);
  if(curve_type==HELIX3D) helix(param1, param2, max_pts, points);
  if(curve_type==TORSPIRAL3D) toroidal_spiral(param1, param2, 20.0, max_pts, points);
  
  glutPostRedisplay();
}

int main(int argc, char* argv[])
{
  glutInit(&argc,argv);
  glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);  
  glutInitWindowSize(width, height);
  glutInitWindowPosition(100,10); 
  
  glutCreateWindow(argv[0]); 
  glutDisplayFunc(display);
  glutReshapeFunc(reshape);
  glutMouseFunc(mouse);
  glutIdleFunc(NULL);
  glutKeyboardFunc(keyboard);
  glutSpecialFunc(special);
  glutCreateMenu(ModeMenu);
    glutAddMenuEntry("Cycloid", CYCLOID);
    glutAddMenuEntry("Spiral", SPIRAL);
    glutAddMenuEntry("Lissajous", LISSAJOUS);
    glutAddMenuEntry("Rose", ROSE);
    glutAddMenuEntry("3D Helix", HELIX3D);
    glutAddMenuEntry("3D Toroidal Spiral", TORSPIRAL3D);
    glutAddMenuEntry("Toggle draw points", DRAWPOINTS);
    glutAddMenuEntry("Toggle dynamic", DYNAMIC);
    glutAddMenuEntry("Dynamic bulls eye", BULLSEYE);
    glutAddMenuEntry("Dynamic target", TARGET);
    glutAddMenuEntry("Dynamic vapor trail", TRAIL);
    glutAddMenuEntry("Move slower", SLOWER);
    glutAddMenuEntry("Move faster", FASTER);
    glutAddMenuEntry("Parameter1 larger", P1UP);
    glutAddMenuEntry("Parameter1 smaller", P1DOWN);
    glutAddMenuEntry("Parameter2 larger", P2UP);
    glutAddMenuEntry("Parameter2 smaller", P2DOWN);
    glutAddMenuEntry("Quit", QUIT);
    glutAttachMenu(GLUT_LEFT_BUTTON);
    glutAttachMenu(GLUT_RIGHT_BUTTON); /* try both */
  obj = gluNewQuadric();
  gluQuadricDrawStyle(obj, GLU_FILL);

  init();
  glutMainLoop();
  return 0;
}

/* various curves, some with scaling */
void cycloid(float ab_ratio, float cycles, float xmin, float xmax,
             float ymin, float ymax, int npts, pts3 points[])
{
  /* ab_ratio of 1.0 tracks rim of rotating wheel, smaller inside */
  float cx, cy, dt, t;
  float cxmin, cxmax, cymin, cymax, cxoff, cxsca, cyoff, cysca;
  float a;
  float b;
  int i;
  
  t = 4.0*M_PI*xmin;
  dt = 3.51*cycles*(xmax-xmin)/(float)npts;
  a = 1.0/(4.0*M_PI);
  b = ab_ratio * a;
  
  for(i=0; i<npts; i++)
  {
    cx = a*t - b*sin(t);
    cy = a   - b*cos(t);
    points[i][0] = cx;
    points[i][1] = cy;
    points[i][2] = 0.0; /* no z */
    t = t + dt;
    if(i==0)
    {
      cxmin = points[i][0]; /* for scaling */
      cxmax = cxmin;
      cymin = points[i][1];
      cymax = cymin;
    }
    if(points[i][0]<cxmin) cxmin = points[i][0];
    if(points[i][0]>cxmax) cxmax = points[i][0];
    if(points[i][1]<cymin) cymin = points[i][1];
    if(points[i][1]>cymax) cymax = points[i][1];
  }
  cxsca = (xmax-xmin)/(cxmax-cxmin);
  cysca = (ymax-ymin)/(cymax-cymin);
  cxoff = xmin-cxmin*cxsca;
  cyoff = ymin-cymin*cysca;
  for(i=0; i<npts; i++)
  {
    points[i][0] = points[i][0]*cxsca+cxoff;
    points[i][1] = points[i][1]*cysca+cyoff;
  }
} /* end cycloid */

void spiral(float gap, float rmax, int npts, pts3 points[])
{
  float r, t, dt, cx, cy, c;
  int i;
  
  c = gap/(2.0*M_PI);
  t = 0.0;
  dt = (rmax/c)/(float)npts;
  
  for(i=0; i<npts; i++)
  {
    r = c*t;
    cx = r*cos(t);
    cy = r*sin(t);
    points[i][0] = cx;
    points[i][1] = cy;
    points[i][2] = 0.0; /* no z */
    t = t + dt;
  }
}

void lissajous(float a, float b, float rmax, int npts, pts3 points[])
{
  float t, dt, cx, cy;
  int i;

  t = 0.0;
  dt = 2.0*M_PI/(float)npts;
  
  for(i=0; i<npts; i++)
  {
    cx = rmax*sin(a*t);
    cy = rmax*cos(b*t);
    points[i][0] = cx;
    points[i][1] = cy;
    points[i][2] = 0.0; /* no z */
    t = t + dt;
  }
}

void rose(float m, float rmax, int npts, pts3 points[])
{
  float t, dt, cx, cy, r;
  int i;

  t = 0.0;
  dt = 2.0*M_PI/(float)npts;
  
  for(i=0; i<npts; i++)
  {
    r = cos(m*t);
    cx = rmax*r*cos(t);
    cy = rmax*r*sin(t);
    points[i][0] = cx;
    points[i][1] = cy;
    points[i][2] = 0.0; /* no z */
    t = t + dt;
  }
}

/* 3D curves */
void helix(float rmax, float cycles, int npts, pts3 points[])
{
  float t, dt, cx, cy, cz;
  int i;
  
  t = 0.0;
  dt = cycles*2.0*M_PI/(float)npts;
  
  for(i=0; i<npts; i++)
  {
    cx = rmax*sin(t);
    cz = rmax*cos(t);
    cy = t/(cycles*M_PI)-1.0;
    points[i][0] = cx;
    points[i][1] = cy;
    points[i][2] = cz;
    t = t + dt;
  }
}

void toroidal_spiral(float inrad, float outrad, float cycles, int npts, pts3 points[])
{
  float t, dt, cx, cy, cz;
  int i;
  
  t = 0.0;
  dt = 2.0*M_PI/(float)npts;
  
  for(i=0; i<npts; i++)
  {
    cx = (inrad*sin(cycles*t)+outrad)*cos(t);
    cy = (inrad*sin(cycles*t)+outrad)*sin(t);
    cz = inrad*cos(cycles*t);
    points[i][0] = cx;
    points[i][1] = cy;
    points[i][2] = cz;
    t = t + dt;
  }
}

/* 3D surfaces */
/* this is getting too big, see new file  surf3d.c  coming soon */

