/*****************************2002*************************************/
/****       skel.c = OpenSkelGlut.c = Noosh97 with CAVE removed    ****/
/****      (C) 1994--2002 Board of Trustees University of Illinois ****/ 
/****      A Model Real-Time Interactive  C/OpenGL/GLUT  Animator  ****/
/****    George Francis, Stuart Levy, Glenn Chappell, Chris Hartman****/
/****    e-mail  gfrancis@math.uiuc.edu : revised 2jan02 by gkf    ****/
/**********************************************************************/

/* A C++ version for CS293 at University of Alaska Fairbanks 
   updated from above by Chris Hartman, 2/2003               */
#include <cstdlib>
#include <cstdio>
#include <GL/glut.h>
#include <sys/timeb.h>     /* sgi  <sys/time.h> */
#define _USE_MATH_DEFINES  // So Windows will define M_PI
#include <cmath>
#include <algorithm>
using namespace std;

#define  CLAMP(x,u,v)     (x<u? u : (x>v ? v: x))
#define  ABS(u)           ((u)<0 ? -(u): (u))
#define  FOR(a,b,c)       for((a)=(b);(a)<(c);(a)++)
#define  DOT(p,q)         ((p)[0]*(q)[0]+(p)[1]*(q)[1]+(p)[2]*(q)[2])
#define  NRM(p)           sqrt(DOT((p),(p)))
#define  DG            M_PI/180
#define  S(u)          sin(u*DG)
#define  C(u)          cos(u*DG)
#define  CLAMP(x,u,v) (x<u? u : (x>v ? v: x))
#define  random        rand        /* library dependent name  */
#define IFCLICK(K,a){static ff=1; if(getbutton(K))ff=1-ff; if(ff){a} }
#define MERIDIANS 32    
#define LATITUDES 32  
#define MANYSTARS 10000
/************************** global variables **************************/
int win=1;                 /* used once to choose window size         */
double gap, gap0=1.;        /* deFault() uses gap0 set by arguments()  */
double lux[3]={1.,2.,3.};   /* world light non unit  vector */
double luxx[3];             /* object space  direction vector          */
double amb, pwr;        /*ambient fraction, pseudo-specular power      */
double mysiz,speed, torq, focal, wfar; /* navigation control variables */
unsigned int PAW,XX,YY,SHIF;  /* used in chaptrack gluttery           */ 
int xwide,yhigh;                /* viewportery width and height       */
int mode,morph,msg,binoc;       /* viewing globals */
int th0, th1, dth, ta0, ta1, dta;  /* torus parameters                */
#define FLYMODE  (0)         /* yellow: turns around head as center   */ 
#define TURNMODE (1)         /* purple: turns around object center    */ 
int ii, jj, kk;  double tmp, temp;          /* saves gray hairs later  */
double aff[16], starmat[16], mat[16];   /* OpenGL placement matrices   */ 
double nose;                       /* to eye distance in console       */
char clefs[128];                  /* which keys were pressed last     */
/*********************** steering *************************************/
void autotymer(int reset){                         /* cheap animator  */
#define  TYME(cnt,max,act) {static cnt; if(first)cnt=max; else\
                            if(cnt?cnt--:0){ act ; goto Break;}}
  static first = 1;              /* the first time autymer is called  */
  if(reset)first=1;              /* or if it is reset to start over   */
  TYME( shrink , 150 , th0++;th1--;ta0++;ta1--)  
  TYME( pause  , 20, 0  )
  TYME( grow   , 150,th0--;th1++;ta0--;ta1++)
  TYME( dwell  , 30, 0  )
  TYME(finish  , 1 , first = 1 )  /* this TYME must be the last one   */
  first = 0;
  Break:   ;                    /* yes Virginia, C has gotos          */
}
/**********************************************************************/
void deFault(void){         /* (Z)ap also restores these assignments  */ 
  th0=5; th1=355;  ta0=5; ta1=355; gap = gap0;    /* torus parameters */ 
  msg=1; binoc=0; nose=.06; mode=TURNMODE;       /* gadget parameters */   
  speed=.02; torq=.02; focal = 2.; wfar=13; mysiz=.01; morph=0; 
  FOR(ii,0,16) starmat[ii]=aff[ii] = (ii/4==ii%4); /* identity matrix */ 
  amb = .3; pwr = 10;                            /* lighting params   */
  tmp=NRM(lux); FOR(ii,0,3)lux[ii] /= tmp;  /* normalize light vector */ 
  aff[12]=0; aff[13]= 0; aff[14]= -4.2; /* place where we can see it  */
  autotymer(1);          /* reset autotymer to start at the beginning */
}
/*************************** scenery **********************************/
void drawvert(int th, int ta){    /* make one properly lighted vertex */
  double  bb,gg,rr; 
  double  lmb,spec,nn[3], dog, cat;
  /* radius of unit sphere is also unit normal to the torus           */
  nn[0] = C(th)*C(ta);
  nn[1] = S(th)*C(ta);
  nn[2] =       S(ta);
  /* illiLight by Ray Idaszak 1989 uses max{amb*lmb, rgb*lmb, spec}   */ 
  lmb = DOT(nn,luxx); lmb =(lmb<0 ? .2 : lmb); lmb = max(amb, lmb); 
  spec = CLAMP((1.1 - pwr+pwr*lmb), 0., 1.);  
  /* illiPaint by Chris Hartman 1993 maps R2(cat,dog)->R3(r,g,b)      */ 
  dog = (ta-ta0)/(double)(ta1-ta0); cat = (th-th0)/(double)(th1-th0);
  rr =  max(spec, lmb*dog);
  gg =  max(spec, lmb*(.25 + ABS(cat -.5)));
  bb =  max(spec, lmb*(1 - cat));
  glColor3f(rr,gg,bb);
  /* torus has unit small diameter and unit big radius                */
  glVertex3f( C(th) + .5*nn[0],
              S(th) + .5*nn[1],
                0   + .5*nn[2]);
} /* end drawvert */
/**********************************************************************/
void drawtor(void){ /* illiTorus with gaps */  
  int th, ta;
  dth = (int)((th1-th0)/MERIDIANS);  /*   this many   meridian strips */ 
  dta = (int)((ta1-ta0)/LATITUDES);  /* and  triangle pairs per strip */
  for(th=th0; th < th1; th += dth){
   glBegin(GL_TRIANGLE_STRIP);
     for(ta = ta0 ; ta < ta1 ; ta += dta ){
         drawvert(th,ta); drawvert(th+gap*dth,ta);}
   glEnd();
  }/* end for.theta loop */
}/* end drawtor */
/**********************************************************************/
void drawcube(void){ /* transfer from skel.c as an exercise  */ }
/**********************************************************************/
void drawall(void){ drawtor(); drawcube();}
/**********************************************************************/
void drawstars(void){  /* replace with SLevy's much prettier stars    */ 
  static double star[MANYSTARS][3]; static int virgin=1;
  if(virgin){             /* first time through set up the stars      */
     FOR(ii,0,MANYSTARS){ /* in a unit cube then  on unit sphere      */
       FOR(jj,0,3)star[ii][jj]  =(double)random()/RAND_MAX - 0.5;
       tmp=NRM(star[ii]); FOR(jj,0,3)star[ii][jj]/=tmp;        
     }
   virgin=0; /* never again */
  }
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();           /* optional insurance or superstition    */ 
    glMultMatrixd(starmat);
    glColor3f(0.8,0.9,1.0);        
    glBegin(GL_POINTS); 
      FOR(ii,0,MANYSTARS)glVertex3dv(star[ii]); 
    glEnd();
  /* glutWireTeapot(1);  if you prefer one on the firmament instead  */ 
  glPopMatrix();            /* optional insurance or superstition    */
  glClear(GL_DEPTH_BUFFER_BIT); /* put the stars at infinity         */ 
}
/************************ steering ***********************************/
void arguments(int argc,char **argv){           /* Pat Hanrahan 1989 */
      while(--argc){++argv; if(argv[0][0]=='-')switch(argv[0][1]){
      case 'w': win =atoi(argv[1]); argv++; argc--; break; 
      case 'g': gap0 =atof(argv[1]); argv++; argc--; break;
      case 'L': lux[0]=atof(argv[1]);
                lux[1]=atof(argv[2]);
                lux[2]=atof(argv[3]); argv +=3; argc -=3; break;
   }}}
/**********************************************************************/
int number, hasnumber, decimal, sign;  /* globals for SLevy's gadgets */
   /* these are assigned in keyboard() but used by these factor fcns  */  
double getnumber(double dflt){    /*  from keyboard, factor of bump()   */ 
     double v = (sign ? -number : number);  /* positive or negative nr */
     if(!hasnumber) return dflt;       /* if no new nr use old on     */ 
     return decimal>0 ? v/(double)decimal : v;
}
void bump(double *val, double incr){          /* SLevy 98 */
  double by = fabs(incr);   /* wizard speak ... best not mess with it  */ 
  char fmt[8], code[32];
  int digits = 1;
  if(hasnumber) {
    *val = getnumber(0);
    return;
  }
  if(by <= .003) digits = 3;
  else if(by <= .03) digits = 2;
  sprintf(fmt, "%%.%de", digits);
  sprintf(code, fmt, *val * (1 + incr));
  sscanf(code, "%f", val);
}
/********************from SLevy 2jan02 ********************************/
int getbutton(char key) { 
    int uu = clefs[key & 127]; clefs[key & 127]=0; return uu;
}
/**********************************************************************/
void keyboard(unsigned char key, int x, int y){
   clefs[key&127]=1;  /* globalize the keys that were pressed         */ 
#define  IF(K)            if(key==K)  
#define  PRESS(K,A,b)     IF(K){b;} IF(K-32){A;}  /* catch upper case */
#define  TOGGLE(K,flg)    IF(K){(flg) = 1-(flg); }
#define  CYCLE(K,f,m)   PRESS((K),(f)=(((f)+(m)-1)%(m)),(f)=(++(f)%(m)))
#define  SLIDI(K,f,m,M)   PRESS(K,(--f<m?m:f), (++f>M?M:f)) 
#define  SLIDF(K,f,m,M,d) PRESS(K,((f -= d)<m?m:f), ((f += d)>M?M:f))
/* Only 127 ASCII chars are processed in this GLUT callback function  */ 
/* Use the specialkeybo function for the special keys                 */ 
   IF(27) { exit(0); }                           /* ESC exit          */
   TOGGLE('v',binoc);                            /* cross-eyed STEREO */
   TOGGLE(' ',mode);                             /* space key         */
   TOGGLE('h',morph);                            /* autotymer on/off  */
   CYCLE('w',msg,3);           /* writing on/off/speedometer+bullseye */
   PRESS('n', nose -= .001 , nose += .001 );     /* for binoculars    */
   PRESS('s', bump(&speed,.02), bump(&speed,-.02));/* flying speed    */ 
   PRESS('q', bump(&torq, .02), bump(&torq, -.02)); /* turning speed  */
   PRESS('o', focal *= 1.1 , focal /= 1.1)       /* telephoto         */
   PRESS('i', mysiz /= 1.1, mysiz *= 1.1)        /* rescale the world */
   PRESS('p', wfar *= 1.01 , wfar   /= 1.01)   /* rear clipping plane */
   PRESS('z', deFault(), deFault());             /* zap changes       */
   PRESS('g',gap /= .9, gap *= .9);             /* gap parameter      */
   PRESS('a',amb /= .9, amb *= .9);             /* ambient fraction   */
   PRESS('r',pwr /= .9, pwr *= .9);             /* pseudo-spec power  */
/********** SLevy's parser creates the input decimal ******************/
   if(key >= '0' && key <= '9'){         /* if key is a digit numeral */
          hasnumber = 1; number = number*10+key-'0'; decimal *= 10; } 
   else if(key == '.') { decimal = 1; }  /* it's a decimal !          */
   else if(key == '-') { sign = -1; }    /* it's negative  !          */
   else { hasnumber = number = decimal = sign = 0;} /* erase mess     */
   glutPostRedisplay();                 /* in case window was resized */
}
/**********************************************************************/
void specialkeybo(int key, int x, int y){
  clefs[0]= key ;
  switch(key){    /*  HOME END PAGE_DOWN RIGHT F1 etc  see glut.h    */
  case GLUT_KEY_F1:  th0++; th1--; break;
  case GLUT_KEY_F2:  th0--; th1++; break;
  /* default: fprintf(stderr,"non-ASCII char [%d] pressed.\n", key); */
  }
  hasnumber=number=decimal=0; glutPostRedisplay();
}
/**********************************************************************/
double speedometer(void){                      /* this one is for win32*/ 
  double dbl; static double rate; static int ii=0;
  static struct _timeb lnow, lthen;
    if(++ii % 8 == 0){                 /* 8 times around measure time */
	   _ftime(&lnow);
		dbl =  (double)(lnow.time - lthen.time)
			 +(double)(lnow.millitm - lthen.millitm)/1000;
		lthen = lnow;  rate = 8/dbl;
      }
    return((double)rate);
}
/**********************************************************************/
void char2wall(double x,double y,double z, char buf[]){
     char *p; glRasterPos3f(x,y,z);
     for(p = buf;*p;p++) glutBitmapCharacter(GLUT_BITMAP_9_BY_15,*p);
}
/**********************************************************************/
void messages(void){char buf[256]; 
  /* console messages are done differently from cave */
#define  LABEL(x,y,W,u) {sprintf(buf,(W),(u));char2wall(x,y,0.,buf);}
  glMatrixMode(GL_PROJECTION); glPushMatrix();  /* new projection matrix */
  glLoadIdentity(); gluOrtho2D(0,3000,0,3000);  /* new 2D coordinates */
  glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity();
      if(mode==TURNMODE) glColor3f(1.,0.,1.); else glColor3f(1.,1.,0.);
      LABEL(1500,1500,"%s","o"); /* place a bullseye dead center */
      LABEL(80,80,"%4.1f fps",speedometer());
      if(msg==2)return;  //try this
      LABEL(80,2840,\
      "(ESC)ape (V)Binoc (MAUS2)Fore (BAR)%s (H)omotopy (W)riting",
             mode?"TURNMODE":"FLYMODE");
      LABEL(10,10,"illiSkel-2002 by Francis, Levy, Bourd, Hartman,\
& Chappell, U Illinois, 1995..2002 %s","");
      LABEL(80,2770,"(N)ose   %0.3f",nose);
      LABEL(80,2700,"[S]peed  %0.4f",speed);
      LABEL(80,2630," tor[Q] %0.4f",torq);
      LABEL(80,2560,"near clipper %g", mysiz*focal);
      LABEL(80,2490,"f(O)cal factor %g",focal);
      LABEL(80,2420,"my s(I)ze %.2g",mysiz);
      LABEL(80,2350,"far cli(P)per= %.2g",wfar); 
      LABEL(80,2280,"(Z)ap %s","");
      LABEL(80,2210,"(G)ap %.2g",gap);
      LABEL(80,2140,"(A)mb %.2g",amb);
      LABEL(80,2070,"pw(R) %.2g",pwr);
    glPopMatrix();
    glMatrixMode(GL_PROJECTION); glPopMatrix();
} 
/************************ navigation **********************************/
void chaptrack(int paw,int xx,int yy,int shif){/* Glenn Chappell 1992 */
   long dx,dy; 
   dx = xx -.5*xwide; dx = abs(dx)>5?dx:0;        /* 5 pixel latency  */
   dy = yy -.5*yhigh; dy = abs(dy)>5?dy:0; 
   glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity();
   if(mode==TURNMODE) glTranslatef(aff[12],aff[13],aff[14]);
   glRotatef(dx*torq,0.,1.,0.); glRotatef(dy*torq,1.,0.,0.);
   if(paw&(1<<GLUT_RIGHT_BUTTON ))glRotatef(shif?-10:-1,0.,0.,1.);
   if(paw&(1<<GLUT_LEFT_BUTTON  ))glRotatef(shif?10:1,0.,0.,1.);
   if(mode==FLYMODE){
      glPushMatrix();
      glMultMatrixd(starmat);
      glGetDoublev(GL_MODELVIEW_MATRIX,starmat);
      glPopMatrix(); }
   if(paw&(1<<GLUT_MIDDLE_BUTTON))glTranslatef(0.,0.,shif?-speed:speed);
   if(clefs[0]==GLUT_KEY_UP) glTranslatef(0.,0., speed);
   if(clefs[0]==GLUT_KEY_DOWN) glTranslatef(0.,0., -speed);
   if(clefs[0]==GLUT_KEY_LEFT) glTranslatef(-speed,0.,0.);
   if(clefs[0]==GLUT_KEY_RIGHT) glTranslatef(speed,0.,0.);
   if(clefs[0]==GLUT_KEY_PAGE_UP) glTranslatef(0., speed,0.);
   if(clefs[0]==GLUT_KEY_PAGE_DOWN) glTranslatef(0.,-speed,0.);
   if(mode==TURNMODE) glTranslatef(-aff[12],-aff[13],-aff[14]);
   glMultMatrixd(aff); 
   glGetDoublev(GL_MODELVIEW_MATRIX,aff);
   FOR(ii,0,3){luxx[ii]=0; FOR(jj,0,3)luxx[ii] +=aff[ii*4+jj]*lux[jj];}
   glPopMatrix();
}
/************************* scenery ************************************/
void reshaped(int xx, int yy){xwide=xx ; yhigh=yy;} /*win width,height*/
/**********************************************************************/
void drawcons(void){ double asp =(double)xwide/yhigh; /* aspect ratio   */
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 
  glClearColor(0,0,0,0);   /* base color, try (.1,.2,.3,0.)           */  
  if(binoc) glViewport(0,yhigh/4,xwide/2,yhigh/2);
  glMatrixMode(GL_PROJECTION); glLoadIdentity();
  glFrustum(-mysiz*asp,mysiz*asp,-mysiz,mysiz,mysiz*focal,wfar); 
  glMatrixMode(GL_MODELVIEW); glLoadIdentity();
  drawstars();
  glTranslatef(-binoc*nose,0.0,0.0);
  glMultMatrixd(aff);
  drawall();
  if(binoc){
      glViewport(xwide/2,yhigh/4,xwide/2,yhigh/2); 
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
      drawstars();
      glTranslatef(binoc*nose,0.0,0.0);
      glMultMatrixd(aff);
      drawall();
    }
  glViewport(0,0,xwide,yhigh);
  if(msg) messages(); 
  glutSwapBuffers();
}
/************************** steering **********************************/
void idle(void){            /* do this when nothing else is happening */
   if(morph) autotymer(0);                       /* advance autotymer */ 
   glutPostRedisplay();                          /* redraw the window */
   IFCLICK('=',chaptrack(PAW,XX,YY,SHIF);)       /* bypass navigation */
   glDisable(GL_DEPTH_TEST);                   /* bypass depth buffer */ 
   IFCLICK('-',glEnable(GL_DEPTH_TEST); )      /* bypass depth buffer */
}
/**********************************************************************/
void mousepushed(int but,int stat,int x,int y){
  if(stat==GLUT_DOWN) PAW |= (1<<but); /*key came down and called back*/
  else PAW &= (-1 ^ (1<<but));              /* on the wayup erase flag*/ 
  XX=x; YY=y;        /* position in window coordinates (pos integers) */ 
  SHIF=(glutGetModifiers()==GLUT_ACTIVE_SHIFT)?1:0;  /* shift down too*/
}
/**********************************************************************/
void mousemoved(int x,int y){ XX=x; YY=y; }
/***************** one ring to rule the all ***************************/
int main(int argc, char **argv){  
   arguments(argc,argv);                      /* from the commandline */
   deFault();                         /* values of control parameters */
       glutInit(&argc, argv);  
       glutInitDisplayMode(GLUT_DOUBLE|GLUT_DEPTH);
       switch(win){ 
           case 0: break;                   /* manage your own window */
           case 1: glutInitWindowSize(640, 480);
                   glutInitWindowPosition(100,100); break;
           case 2: glutInitWindowPosition(0,0); break;
         }
       glutCreateWindow("<* illiSkel 2002 in C/OpenGL/GLUT *>");
       if(win==2) glutFullScreen();
       glEnable(GL_DEPTH_TEST);                    /* enable z-buffer */
       glutDisplayFunc(drawcons); 
               /* the following are optional for interactive control  */ 
       glutKeyboardFunc(keyboard);
       glutSpecialFunc(specialkeybo);
       glutMouseFunc(mousepushed);
       glutMotionFunc(mousemoved);       
       glutPassiveMotionFunc(mousemoved); 
                                      /*  beyond here all are needed  */
       glutReshapeFunc(reshaped);
       glutIdleFunc(idle);             
       glutMainLoop();
}
/**********************************************************************/
 
