/* tenseg2gl.c  structures in three dimensions with tension members */
/* This tensegrety structure has six thin cylinders of length 1
 * Label them  A through F with ends A1, A2, through F1, F2
 * There are 24 strings labeled 1 through 24 with ends 1-1, 1-2, 2-1 etc.
 * 8 strings of length 1/2, 16 strings of length 0.6125
 *
 * The connections are four string ends to each cylinder end:
 *                            length
 * A1   1-1   4-2   9-2  17-2   1/2 .6125
 * B1   1-2   2-1  13-2  18-2   1/2 .6125
 * C1   2-2   3-1  14-2  21-2   1/2 .6125
 * D1   3-2   4-1  10-2  22-2   1/2 .6125
 * E1   9-1  10-1  11-1  12-1   .6125
 * F1  17-1  18-1  19-1  20-1   .6125
 * A2   5-1   8-2   9-2  19-2   1/2 .6125
 * B2   5-2   6-1  15-2  20-2   1/2 .6125
 * C2   6-2   7-1  16-2  23-2   1/2 .6125
 * D2   7-2   8-1  11-2  24-2   1/2 .6125
 * E2  13-1  14-1  15-1  16-1   .6125
 * F2  21-1  22-1  23-1  24-1   .6125
 *
 * In order to "anchor" the 3D structure:
 * Place A1 at    0,    0,    0
 * Place B1 at  1/2,    0,    0
 * Place C1 at  1/2,  1/2,    0
 * Place D1 at    0,  1/2,    0
 * Place A2 at    0,    0,    1
 * Place B2 at  1/2,    0,    1
 * Place C2 at  1/2,  1/2,    1
 * Place D2 at    0,  1/2,    1
 * Place E1 at -1/4,  1/4,  1/2
 * Place E2 at  3/4,  1/4,  1/2
 * Place F1 at  1/4, -1/4,  1/2
 * Place F2 At  1/4,  3/4,  1/2
 */
/* Mouse buttons control axis of rotation               */
/* "O" and "o" make centroid offset bigger and smaller  */
/* "C" and "c" make color distinct or same              */
/* "F" and "f" make rotation faster or slower           */
/* "S" and "s" scale larger or smaller                  */

#include <GL/glut.h>
#include <stdlib.h>
#include <string.h>

#define sumcent(A) {centroid[0]+=A[0];centroid[1]+=A[1];centroid[2]+=A[2];}

static GLfloat A1[3] = {  0.00,   0.00,  0.00};  
static GLfloat A2[3] = {  0.00,   0.00,  1.00}; 
static GLfloat B1[3] = {  0.50,   0.00,  0.00}; 
static GLfloat B2[3] = {  0.50,   0.00,  1.00}; 
static GLfloat C1[3] = {  0.50,   0.50,  0.00}; 
static GLfloat C2[3] = {  0.50,   0.50,  1.00}; 
static GLfloat D1[3] = {  0.00,   0.50,  0.00};  
static GLfloat D2[3] = {  0.00,   0.50,  1.00}; 
static GLfloat E1[3] = { -0.25,   0.25,  0.50}; 
static GLfloat E2[3] = {  0.75,   0.25,  0.50}; 
static GLfloat F1[3] = {  0.25,  -0.25,  0.50}; 
static GLfloat F2[3] = {  0.25,   0.75,  0.50}; 

GLfloat colors[][3] = {{1.0,0.0,0.0},
       {1.0,1.0,0.0}, {0.0,1.0,0.0}, {0.0,0.0,1.0},
       {1.0,0.0,1.0}, {0.0,1.0,1.0}, {0.5,0.5,0.5}};
static GLfloat theta[] = {0.0,0.0,0.0};
static GLint axis = 2;
static int colorset = 1;                      /* 'C' or 'c' */
static double rotation = 1.0;                 /* 'F' or 'f' */
static GLfloat myScale = 0.5;                 /* 'S' or 's' */
static GLfloat centroid[3]= { 0.0, 0.0, 0.0}; /* 'O' or 'o' */
static int winWidth  = 500;
static int winHeight = 500;

/* internal functions */
static void myInit(void);
static void drawString(int color, GLfloat *A, GLfloat *B);
static void drawCylinder(int color, GLfloat *A, GLfloat *B);
static void draw_tenseg(void);
static void display(void);
static void drawText(void);
static void spin_tenseg(void);
static void mouse(int btn, int state, int x, int y);
static void keyboard(unsigned char key, int x, int y);
static void myReshape(int w, int h);
static GLUquadricObj *c1, *c2, *c3; /* for cylinder */

static void myInit(void)
{
  c1 = gluNewQuadric(); /* for cylinder */
  c2 = gluNewQuadric();
  c3 = gluNewQuadric();
  
  /* compute centroid, average of vertices */
  centroid[0] = 0.5;
  centroid[1] = 0.5;
  centroid[2] = 0.5;
}

static void drawString(int color, GLfloat *A, GLfloat *B)
{
  GLfloat Ac[3], Bc[3];
  int i, lcolor;

  lcolor=color;
  if(colorset==0) lcolor=1;
  for(i=0; i<3; i++)
  {
    Ac[i] = A[i]-centroid[i];
    Bc[i] = B[i]-centroid[i];
  }
  glLineWidth(1.0/myScale);
  glBegin(GL_LINES);
    glColor3fv(colors[lcolor]);
    glVertex3fv(Ac);
    glVertex3fv(Bc);
  glEnd();
}

static void drawCylinder(int color, GLfloat *A, GLfloat *B)
{
  GLfloat Ac[3], Bc[3];
  int i, lcolor;
    
  lcolor=color;
  if(colorset==0) lcolor=1;
  for(i=0; i<3; i++)
  {
    Ac[i] = A[i]-centroid[i];
    Bc[i] = B[i]-centroid[i];
  }

  /* draw a cylinder (just a line)*/
  glLineWidth(6.0/myScale);
  glBegin(GL_LINES);
    glColor3fv(colors[lcolor]);
    glVertex3fv(Ac);
    glVertex3fv(Bc);
  glEnd();
  
/*  glPushMatrix();
    glLoadIdentity();
    glRotatef(-90.0, 0.0, 0.0, 0.0);
    gluCylinder(c1, 0.03, 0.03, 0.5, 12, 12);
  glPopMatrix();
  glPushMatrix();
    glLoadIdentity();
    glRotatef(0.0, 60.0, 0.0, 0.0);
    gluCylinder(c2, 0.04, 0.04, 0.6, 12, 12);
  glPopMatrix();
  glPushMatrix();
    glLoadIdentity();
    glRotatef(0.0, 0.0, 0.0, 0.0);
    gluCylinder(c3, 0.08, 0.08, 0.4, 12, 12);
*/    
  glPopMatrix();
}

static void draw_tenseg(void)
{
  /* 24 strings and 6 cylinders */
  drawString(0, A1, B1);
  drawString(0, B1, C1);
  drawString(0, C1, D1);
  drawString(0, D1, A1);
  drawString(0, A2, B2);
  drawString(0, B2, C2);
  drawString(0, C2, D2);
  drawString(0, D2, A2);
  drawString(1, E1, A1);
  drawString(1, E1, D1);
  drawString(1, E1, A2);
  drawString(1, E1, D2);
  drawString(2, E2, B1);
  drawString(2, E2, C1);
  drawString(2, E2, B2);
  drawString(2, E2, C2);
  drawString(4, F1, A1);
  drawString(4, F1, B1);
  drawString(4, F1, A2);
  drawString(4, F1, B2);
  drawString(5, F2, C1);
  drawString(5, F2, D1);
  drawString(5, F2, C2);
  drawString(5, F2, D2);
  
  drawCylinder(3, A1, A2);
  drawCylinder(3, B1, B2);
  drawCylinder(3, C1, C2);
  drawCylinder(3, D1, D2);
  drawCylinder(3, E1, E2);
  drawCylinder(3, F1, F2);
}

static void display(void)
{
  /* display callback, clear frame buffer and z buffer, */
  /* rotate tenseg and draw, swap buffers */
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  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);
  draw_tenseg();
  drawText();
  glFlush();
  glutSwapBuffers();
}

static void drawText(void)
{
  char  msg1[] = "'S' or 's' for size,    'F' or 'f' for rotation speed";
  char  msg2[] = "'O' or 'o' to offset,   'C' or 'c' for color";
  char  msg3[] = "mouse button for rotation axis change";
  int   i, len;

  /* Draw text */
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
    glLoadIdentity();
    glOrtho(0, winWidth, 0, winHeight, -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
      glLoadIdentity();
      glColor3f(1.0f, 1.0f, 0.0f);
      glRasterPos2i(10, 10);
      len = strlen(msg1);
      for (i=0; i<len; i++)
        glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, msg1[i]);
      glRasterPos2i(10, 25);
      len = strlen(msg2);
      for (i=0; i<len; i++)
        glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, msg2[i]);
      glRasterPos2i(10, 40);
      len = strlen(msg3);
      for (i=0; i<len; i++)
        glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, msg3[i]);
      glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
  glPopMatrix();
}

static void spin_tenseg(void)
{
  /* Idle callback, spin tenseg 'rotation' degrees about selected axis */
  theta[axis] = theta[axis] + rotation;
  if(theta[axis] > 360.0) theta[axis] = theta[axis] - 360.0;
  /* display(); */
  glutPostRedisplay();
}

static 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;
}

static void keyboard(unsigned char key, int x, int y)
{
  switch (key)
  {
    case 'O':
      centroid[0] *= 1.25; /* increase by 25% each press */
      centroid[1] *= 1.25;
      centroid[2] *= 1.25;
      break;
    case 'o':
      centroid[0] /= 1.25;
      centroid[1] /= 1.25;
      centroid[2] /= 1.25;
      break;
    case 'C':
      colorset=1;
      break;
    case 'c':
      colorset=0;
      break;
    case 'F':
      rotation=rotation+0.25;
      if(rotation>5.0) rotation=5.0;
      break;
    case 'f':
      rotation=rotation-0.25;
      if(rotation<0.0) rotation=0.0;
      break;
    case 's':
      myScale *= 1.25; /* decrease size by 25% */
      myReshape(winWidth, winHeight);
      break;
    case 'S':
      myScale /= 1.25; /* increase size by 25% */
      myReshape(winWidth, winHeight);
      break;
   }
}

static void myReshape(int w, int h)
{
  winWidth  = w;
  winHeight = h;
  glViewport(0, 0, w, h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if(w <= h)
      glOrtho(-2.0*myScale, 2.0*myScale,
              -2.0*myScale * (GLfloat)h / (GLfloat)w,
               2.0*myScale * (GLfloat)h / (GLfloat)w,
              -10.0*myScale, 10.0*myScale);
  else
      glOrtho(-2.0*myScale * (GLfloat)w / (GLfloat)h,
              2.0*myScale * (GLfloat)w / (GLfloat)h, -2.0*myScale, 2.0*myScale,
              -10.0, 10.0);
  glMatrixMode(GL_MODELVIEW);
}


int main(int argc, char *argv[])
{
  glutInit(&argc, argv);
  /* need both double buffering and z buffer */
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutInitWindowSize(winWidth, winHeight);
  glutCreateWindow(argv[0]);
  glutReshapeFunc(myReshape);
  glutDisplayFunc(display);        /* draws screen */
  glutIdleFunc(spin_tenseg);       /* "spin_tenseg" setup */
  glutMouseFunc(mouse);            /* "mouse" setup    */
  glutKeyboardFunc(keyboard);      /* "keyboard" setup    */
  glEnable(GL_DEPTH_TEST);         /* Enable hidden--surface--removal */
  myInit();
  glutMainLoop();
  return 0;
}

