/*****************************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 <GLUT/glut.h>
#include <sys/timeb.h>     /* sgi  <sys/time.h> */
#define _USE_MATH_DEFINES  // So Windows will define M_PI
#include <cmath>
#include <algorithm>
#include <iostream>
using std::cout;
using std::endl;
using std::max;

#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 int ff=1; if(getbutton(K))ff=1-ff; if(ff){a} }
#define MERIDIANS 12   
#define LATITUDES 12  
#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     */
double ww;			/*change distance between torii*/
int bumps;			/*change number of bumps on torii*/
double rot;			/*change amount of rotation of second torii*/
int numtor;
/*********************** steering *************************************/
void autotymer(int reset){                         /* cheap animator  */
#define  TYME(cnt,max,act) {static int cnt; if(first)cnt=max; else\
                            if(cnt?cnt--:0){ act ; goto Break;}}
  static int first = 1;              /* the first time autymer is called  */
  if(reset)first=1;              /* or if it is reset to start over   */
  TYME(expand,180,ww+=2)
  TYME(changebumps,1,bumps--)
  TYME(expand,180,ww+=2)
  TYME(changebumps,1,bumps--)
  TYME(expand,180,ww+=2)
  TYME(changebumps,1,bumps--)
  TYME(expand,180,ww+=2)
  TYME(changebumps,1,bumps--)
  TYME(expand,180,ww+=2)
  TYME(changebumps,1,bumps--)
  TYME(expand,180,ww+=2)
  TYME(changenumtor,1,numtor--)
  TYME(expand,180,ww+=2)
  TYME(changenumtor,1,numtor--)
  TYME(expand,180,ww+=2)
  TYME(changenumtor,1,numtor++)
  TYME(expand,180,ww+=2)
  TYME(changenumtor,1,numtor++)
  TYME(expand,180,ww+=2)
  TYME(changenumtor,1,numtor++)
  TYME(expand,180,ww+=2)
  TYME(changenumtor,1,numtor--)
  TYME(expand,180,ww+=2)
  TYME(changebumps,1,bumps++)
  TYME(expand,180,ww+=2)
  TYME(changebumps,1,bumps++)
  TYME(expand,180,ww+=2)
  TYME(changebumps,1,bumps++)
  TYME(expand,180,ww+=2)
  TYME(changebumps,1,bumps++)
  TYME(expand,180,ww+=2)
  TYME(changebumps,1,bumps++)
  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=0; th1=360;  ta0=0; ta1=360; 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; ww = 1;           /* lighting params   */
  rot = 36; bumps = 5;
  numtor = 3;
  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(double th, double 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);
  double oo = .1 + C(20*S(ta))/10 + C(bumps*th)/10;
  /* 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);
  glVertex3f(C(th) + oo*nn[0],
             S(th) + oo*nn[1],
                0  + oo*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 */
/**********************************************************************/
double rad(double x){
return .5*(S(x)) + 1.1;
}
/**********************************************************************/
double siz(double y){
return C(y);
}
/**********************************************************************/
void fixlightvector(void){
double tempmat[16];
glGetDoublev(GL_MODELVIEW_MATRIX,tempmat);
   FOR(ii,0,3){luxx[ii]=0; FOR(jj,0,3)luxx[ii] +=tempmat[ii*4+jj]*lux[jj];}
}
/**********************************************************************/
void drawtorii(void){
for(int torang = 0; torang < 360; torang += 360/numtor)
    {
    glPushMatrix();
    glTranslated(0,0,siz(ww + torang));
    glScaled(rad(ww + torang),rad(ww + torang),rad(ww + torang));
    glRotated(rot,0,0,1);
//    fixlightvector();
    drawtor();
    glPopMatrix();
    }
}
/**********************************************************************/
void drawsideone(void){
    {
    glScaled(.2,.2,.2);
    drawtorii();
    }
    {
    glPushMatrix();
    glRotated(-45,0,1,0);
    glTranslated(4,0,-1);
    drawtorii();
    glPopMatrix();
    }
    {
    glPushMatrix();
    glRotated(45,1,0,0);
    glTranslated(0,4,-1);
    drawtorii();
    glPopMatrix();
    }
    {
    glPushMatrix();
    glRotated(45,0,1,0);
    glTranslated(-4,0,-1);
    drawtorii();
    glPopMatrix();
    }
    {
    glPushMatrix();
    glRotated(-45,1,0,0);
    glTranslated(0,-4,-1);
    drawtorii();
    glPopMatrix();
    }
}
/**********************************************************************/
void drawsidetwo(void){
    {
    glPushMatrix();
    glTranslated(.7,-.7,0);
    glRotated(-90,1,1,0);
    glScaled(.2,.2,.2);
    drawtorii();
    glPopMatrix();
    }
    {
    glPushMatrix();
    glTranslated(.7,.7,0);
    glRotated(90,1,-1,0);
    glScaled(.2,.2,.2);
    drawtorii();
    glPopMatrix();
    }
    {
    glPushMatrix();
    glTranslated(-.7,.7,0);
    glRotated(90,1,1,0);
    glScaled(.2,.2,.2);
    drawtorii();
    glPopMatrix();
    }
    {
    glPushMatrix();
    glTranslated(-.7,-.7,0);
    glRotated(-90,1,-1,0);
    glScaled(.2,.2,.2);
    drawtorii();
    glPopMatrix();
    }
}
/**********************************************************************/
void drawall(void){
    {
    glPushMatrix();
    glTranslated(0,0,-1);
    drawsideone();
    glPopMatrix();
    }
    {
    glPushMatrix();
    glRotated(180,1,0,0);
    glTranslated(0,0,-1);
    drawsideone();
    glPopMatrix();
    }
    {
    glPushMatrix();
    drawsidetwo();
    glPopMatrix();
    }
}
/**********************************************************************/
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, "%lf", 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('h',morph);                            /* autotymer on/off  */
   PRESS('z', deFault(), deFault());             /* zap changes       */
   PRESS('g',gap /= .9, gap *= .9);             /* gap parameter      */
   PRESS('b',bumps += 1, bumps -= 1);		/* # of bumps on torii*/
   PRESS('n',numtor -= 1, numtor += 1);		/*changes numtor*/
   if (numtor == 0) numtor = 1;			/*prevents program from freezing up 						when numtor reaches zero*/
/********** 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();
}
/**********************************************************************/
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,"o%s",""); /* place a bullseye dead center */
      if(msg==2)return;
      LABEL(80,2840,"(ESC)ape %s","");
      LABEL(10,10,"illiSkel-2002 by Francis etc., modified by McCaleb (cs293) 2003 %s","");
      LABEL(80,2770,"(Z)ap %s","");
      LABEL(80,2700,"(G)ap %.2g",gap);
      LABEL(80,2630,"(B)umps on torii %d" ,bumps);
      LABEL(80,2560,"(N)umber of torii %d",numtor);
    glPopMatrix();
    glMatrixMode(GL_PROJECTION); glPopMatrix();
} 
/************************ navigation **********************************/
void chaptrack(long int paw,long int xx,long int yy,long int shif){/* Glenn Chappell 1992 */
   long dx,dy; 
   dx = (long int) (xx -.5*xwide); dx = abs(dx)>5?dx:0;        /* 5 pixel latency  */
   dy = (long int) (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("Dance of the Torii 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();
}