#include <stdlib.h>
#include <math.h>
#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/gl.h>
#include <GL/glut.h>
#endif

int recurseLevel=1;
float theta,phi,psi;
float xx,yy,zz;
float center_x=0.5,center_y=0.5,center_z=0.5;
int animation=0;

enum {SEGMENTO,SEGMENTO2,TRIANGOLO,PUNTO,QUADRATO,BICI,CUBO,CUBO2,
      KOCH,FELCE,SIERPINSKI,SIERPINSKI_CUBO, RANDOM, RANDOM3D, BELLO, 
      ITERPIU,ITERMENO,ITER0,ITER1,ITER2,ITER3,ITER4,
      ANIMATION,EXIT,NOP};

int base=SEGMENTO;
int set=KOCH;   //insieme di similitudini

void init(void);
void display(void);
void idle_function(void);
void keyboard(unsigned char key, int x,int y);
void special_keyboard(int key, int x,int y);
void init_light();
void drawA(void);
void scale(float *T);
void drawIter(int n);
void bici(void);
void init_menu(void);
void operation(int);
void random_fractal_init(int tredi,unsigned int seed);

float T1[16]={
  0.85,-0.04,  0, 0, //1° colonna
  0.04, 0.85,  0, 0,
     0,    0,0.1, 0,
     0,  1.6, 0,  1};
float T2[16]={
    0.2,0.23,  0, 0,
  -0.26,0.22,  0, 0,
      0,   0,0.1, 0,
      0, 1.6,  0, 1};
float T3[16]={
  -0.15,0.26,  0, 0,
   0.28,0.24,  0, 0,
      0,   0,0.1, 0,
      0,0.44,  0, 1};
float T4[16]={
      0,   0,  0, 0,    
      0,0.16,  0, 0,
      0,   0,0.1, 0,
      0,   0,  0, 1};

int main(int argc, char *argv[])
{

  glutInit(&argc,argv);
  glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB|GLUT_DEPTH);
  glutInitWindowSize(600,600);
  glutInitWindowPosition(10,10);
  glutCreateWindow("FRATTALI");
  init_menu();
  init();
  operation(KOCH);
  glutDisplayFunc(display);
  glutKeyboardFunc(keyboard);
  glutSpecialFunc(special_keyboard);
  glutIdleFunc(idle_function);

  scale(T1);
  scale(T2);
  scale(T3);
  scale(T4);

  glutMainLoop();
  return 0;
}

void init(void)
{
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);

  glClearColor(1.0f ,1.0f ,1.0f ,0.0f);
  glColor3f(0.0f,0.0f,0.0f);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(4.5f,(GLfloat)250/(GLfloat)250,14.0,25.0);
  glTranslatef(-0.5,-0.5,-16);

  init_light();

  glMatrixMode(GL_MODELVIEW);
}

void drawA() {
  switch(base){
  case SEGMENTO:
    glColor3f(0,0,0); //NERO
    glBegin(GL_LINES);
    glVertex2f( 0.0f, 0.0f);
    glVertex2f( 1.0f, 0.0f);
    glEnd();
    break;
  case SEGMENTO2:     //VERDE
    glColor3f(0.1,0.8,0.1);
    glBegin(GL_LINES);
    glVertex2f( 0.5f, 0.0f);
    glVertex2f( 0.5f, 0.5f);
    glEnd();
    break;
  case TRIANGOLO:
    glColor3f(0.5,0.5,0);
    glBegin(GL_TRIANGLES);
    glVertex2f( 0.0f, 1.0f);
    glVertex2f( 1.0f, 1.0f);
    glVertex2f( 0.5f, 0.5f);
    glEnd();
    glColor3f(0,0,0);
    glBegin(GL_LINE_LOOP);
    glVertex2f( 0.0f, 1.0f);
    glVertex2f( 1.0f, 1.0f);
    glVertex2f( 0.5f, 0.5f);
    glEnd();
    break;
  case PUNTO:
    glColor3f(0,0,0);                 
    glBegin(GL_POINTS);
    glVertex2f( 0.5f, 0.0f);
    glEnd();
    break;
  case QUADRATO:
    glColor3f(0,0,1);
    glBegin(GL_QUADS);
    glNormal3f(0,0,1);
    glVertex2f( 0.0f, 0.0f);
    glVertex2f( 1.0f, 0.0f);
    glVertex2f( 1.0f, 1.0f);
    glVertex2f( 0.0f, 1.0f);
    glEnd();
    break;
  case BICI:
    bici();
    break;
  case CUBO2:
    {
      const float A=0.1;
      const float B=0.9;
      glColor3f(1.0f,0.0f,0.0f);
      glBegin(GL_QUADS);
      glNormal3f(0,0,1);
      glVertex3f( A, A, B); //Z
      glVertex3f( B, A, B);
      glVertex3f( B, B, B);
      glVertex3f( A, B, B);
      glNormal3f(0,0,-1);
      glVertex3f( A, B, A);
      glVertex3f( B, B, A);
      glVertex3f( B, A, A);
      glVertex3f( A, A, A);
    
      glNormal3f(1,0,0);
      glVertex3f( B, A, B); //X
      glVertex3f( B, A, A);
      glVertex3f( B, B, A);
      glVertex3f( B, B, B);
      glNormal3f(-1,0,0);
      glVertex3f( A, B,B);
      glVertex3f( A, B,A);
      glVertex3f( A, A,A);
      glVertex3f( A, A,B);
    
      glNormal3f(0, 1,0);
      glVertex3f( B, B,B); //Y
      glVertex3f( B, B,A);
      glVertex3f( A, B,A);
      glVertex3f( A, B,B);
      glNormal3f(0,-1,0);
      glVertex3f( A, A,B);
      glVertex3f( A, A,A);
      glVertex3f( B, A,A);
      glVertex3f( B, A,B);                    
      glEnd();
    }
    break;
  case CUBO:
    {
      const float A=-0.05;
      const float B=1.05;
      glColor3f(1.0f,0.0f,0.0f);
      glBegin(GL_QUADS);
      glNormal3f(0,0,1);
      glVertex3f( A, A, B); //Z
      glVertex3f( B, A, B);
      glVertex3f( B, B, B);
      glVertex3f( A, B, B);
      glNormal3f(0,0,-1);
      glVertex3f( A, B, A);
      glVertex3f( B, B, A);
      glVertex3f( B, A, A);
      glVertex3f( A, A, A);
    
      glNormal3f(1,0,0);
      glVertex3f( B, A, B); //X
      glVertex3f( B, A, A);
      glVertex3f( B, B, A);
      glVertex3f( B, B, B);
      glNormal3f(-1,0,0);
      glVertex3f( A, B,B);
      glVertex3f( A, B,A);
      glVertex3f( A, A,A);
      glVertex3f( A, A,B);
    
      glNormal3f(0, 1,0);
      glVertex3f( B, B,B); //Y
      glVertex3f( B, B,A);
      glVertex3f( A, B,A);
      glVertex3f( A, B,B);
      glNormal3f(0,-1,0);
      glVertex3f( A, A,B);
      glVertex3f( A, A,A);
      glVertex3f( B, A,A);
      glVertex3f( B, A,B);                    
      glEnd();
    }
    break;
  };
  
}

void scale(float *T) {  
  float k=0.1;
  float t=0.5;
//contraggo di fattore k 
  T[12]*=k;  
  T[13]*=k;
  T[14]*=k;
//traslo in modo che la felce sia centrata in (0,0.5)
  T[12]+=t;
  T[12]-=T[0]*t;
  T[13]-=T[1]*t;
  T[14]-=T[2]*t;
}

double number() {

  return (rand()%1024)/1024.0;    // dà un numero compreso tra 0 e 1
}

double rnd_s[20];   // vettori che contengono fino a 20 elementi
double rnd_r[20];
double rnd_rr[20];
double rnd_x[20];
double rnd_y[20];
double rnd_z[20];
int rnd_n;         //n: numero di similitudini usate per il frattale
int rnd_3D=0;

void random_fractal_init(int tredi, unsigned int seed) {
  int i;             

  if (seed==0)
    seed=rand();
  srand(seed);
  printf("random with seed=%u\n",seed);
  rnd_3D=tredi;
  rnd_n=1+(number()*5*(1+tredi));
  for (i=0;i<rnd_n;++i) {
    rnd_s[i]=number()*0.7;
    rnd_r[i]=number()*360;
    rnd_rr[i]=number()*360;
    rnd_x[i]=number()*0.5+0.2;
    rnd_y[i]=number()*0.5+0.2;
    rnd_z[i]=number()*0.5+0.2;
  }
};

void drawIter(int n) {
  if (n==0) drawA();
  else {
    switch(set){
    case KOCH:

      glPushMatrix();

      glScalef(1.0/3,1.0/3,1.0/3);
      drawIter(n-1);

      glTranslatef(1,0,0);
      glRotatef(60,0,0,1);
      drawIter(n-1);

      glTranslatef(1,0,0);
      glRotatef(-120,0,0,1);
      drawIter(n-1);

      glTranslatef(1,0,0);
      glRotatef(60,0,0,1);
      drawIter(n-1);

      glPopMatrix();

      break;
    case FELCE:

      glPushMatrix();
      glMultMatrixf(T1);
      drawIter(n-1);
      glPopMatrix();

      glPushMatrix();
      glMultMatrixf(T2);
      drawIter(n-1);
      glPopMatrix();

      glPushMatrix();
      glMultMatrixf(T3);
      drawIter(n-1);
      glPopMatrix();

      glPushMatrix();
      glMultMatrixf(T4);
      drawIter(n-1);
      glPopMatrix();

      break;

    case SIERPINSKI:
      glPushMatrix();

      glScalef(1.0/3,1.0/3,1.0/3);
      drawIter(n-1);

      glTranslatef(1,0,0);
      drawIter(n-1);

      glTranslatef(1,0,0);
      drawIter(n-1);

      glTranslatef(0,1,0);
      drawIter(n-1);

      glTranslatef(0,1,0);
      drawIter(n-1);

      glTranslatef(-1,0,0);
      drawIter(n-1);

      glTranslatef(-1,0,0);
      drawIter(n-1);

      glTranslatef(0,-1,0);
      drawIter(n-1);

      glPopMatrix();
      break;

    case SIERPINSKI_CUBO:
      glPushMatrix();

      glScalef(1.0/3,1.0/3,1.0/3);
      drawIter(n-1);

      glTranslatef(1,0,0);
      drawIter(n-1);

      glTranslatef(1,0,0);
      drawIter(n-1);

      glTranslatef(0,1,0);
      drawIter(n-1);

      glTranslatef(0,1,0);
      drawIter(n-1);

      glTranslatef(-1,0,0);
      drawIter(n-1);

      glTranslatef(-1,0,0);
      drawIter(n-1);

      glTranslatef(0,-1,0); // fino qui: uguale a SIERPINSKI
      drawIter(n-1);

      glTranslatef(0,0,2);
      drawIter(n-1);

      glTranslatef(0,-1,0);
      drawIter(n-1);

      glTranslatef(1,0,0);
      drawIter(n-1);

      glTranslatef(1,0,0);
      drawIter(n-1);

      glTranslatef(0,1,0);
      drawIter(n-1);

      glTranslatef(0,1,0);
      drawIter(n-1);

      glTranslatef(-1,0,0);
      drawIter(n-1);

      glTranslatef(-1,0,0);
      drawIter(n-1);

      glTranslatef(0,0,-1);
      drawIter(n-1);

      glTranslatef(0,-2,0);
      drawIter(n-1);

      glTranslatef(2,0,0);
      drawIter(n-1);

      glTranslatef(0,2,0);
      drawIter(n-1);

      glPopMatrix();
      break;
    case RANDOM:
    case RANDOM3D:
      {
	int i;
	for (i=0;i<rnd_n;++i) {
	  glPushMatrix();
	  glScalef(rnd_s[i],rnd_s[i],rnd_s[i]);
	  if (rnd_3D) 
	    glTranslatef(rnd_x[i],rnd_y[i],rnd_z[i]);
	  else
	    glTranslatef(rnd_x[i],rnd_y[i],0);
	  glRotatef(rnd_r[i],0,0,1);
	  if (rnd_3D) {
	    glRotatef(rnd_rr[i],1,0,0);
	  }
	  drawIter(n-1);
	  glPopMatrix();
	}
      }
      break;
    };
  }
}

void display(void)
{
  glLoadIdentity(); // sistema di riferimento iniziale

  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
 
  glTranslatef(xx+center_x,yy+center_y,zz+center_z);
  glRotatef(theta,0,1,0);
  glRotatef(phi,1,0,0);
  glRotatef(psi,0,0,1);
  glTranslatef(-xx-center_x,-yy-center_y,-zz-center_z);
  glTranslatef(xx,yy,zz);

  drawIter(recurseLevel);
  glutSwapBuffers();
}

void operation(int what) {
  switch(what) {
  case KOCH:
    base=SEGMENTO;
    set=KOCH;
    phi=0;
    theta=0;
    psi=0;        
    xx=yy=zz=0;
    center_x=0.5;
    center_y=0.2;
    center_z=0;
    recurseLevel=1;
    animation=0;
    break;
  case FELCE:
    base=SEGMENTO2;
    set=FELCE;
    phi=0;
    theta=0;
    psi=0;        
    xx=yy=zz=0;
    center_x=0.5;
    center_y=0.5;
    center_z=0;
    recurseLevel=1;
    animation=0;
    break;
  case SIERPINSKI:
    base=QUADRATO;
    glDisable(GL_CULL_FACE);
    set=SIERPINSKI;
    phi=0;
    theta=0;
    psi=0;        
    xx=yy=zz=0;
    recurseLevel=1;
    center_x=center_y=0.5;
    center_z=0.0;
    animation=0;
    break;
  case SIERPINSKI_CUBO:
    base=CUBO;
    set=SIERPINSKI_CUBO;
    glEnable(GL_CULL_FACE);
    phi=10;
    theta=10;
    zz=-2;
    center_x=center_y=center_z=0.5;
    recurseLevel=1;
    animation=0;
    break;
 
  case BELLO:         // un particolare frattale RANDOM3D (tasto 0)
    base=TRIANGOLO;
    set=RANDOM3D;
    recurseLevel=8;
    random_fractal_init(1,32055);
    break;

  case RANDOM:
    set=RANDOM;
    random_fractal_init(0,0);
    break;

  case RANDOM3D:
    set=RANDOM3D;
    random_fractal_init(1,0);
    break;

  case SEGMENTO:
    glColor3f(1,1,1);
    base=SEGMENTO;
    break;
  case SEGMENTO2:
    base=SEGMENTO2;
    break;
  case TRIANGOLO:
    base=TRIANGOLO;
    glDisable(GL_CULL_FACE);
    break;
  case PUNTO:
    base=PUNTO;
    break;
  case QUADRATO:
    base=QUADRATO;
    glDisable(GL_CULL_FACE);
    break;
  case BICI:
    base=BICI;
    glDisable(GL_CULL_FACE);
    break;
  case CUBO:
    base=CUBO;
    glEnable(GL_CULL_FACE);
    break;
  case NOP:
    break;
  case EXIT:         //ESC
    exit(0);
    break;
  case ITER0:    
    recurseLevel=0;
    break;
  case ITERPIU:
    recurseLevel++;
    break;
  case ITERMENO:
    if (recurseLevel>0)
      recurseLevel--;
    break;
  case ITER1:    
    recurseLevel=1;
    break;
  case ITER2:    
    recurseLevel=2;
    break;
  case ITER3:    
    recurseLevel=3;
    break;
  case ITER4:    
    recurseLevel=4;
    break;
  case ANIMATION:
    animation=!animation;
    break;
  };
  glutPostRedisplay();
}

void keyboard(unsigned char key, int x,int y) {
  switch(key) {
  case '+':
    operation(ITERPIU);
    break;
  case '-':
    operation(ITERMENO);
    break;
  case 'k':
    operation(KOCH);
    break;
  case 'f':
    operation(FELCE);
    break;
  case 's':
    operation(SIERPINSKI);
    break;
  case 'd':
    operation(SIERPINSKI_CUBO);
    break;
  case 'l':
    operation(SEGMENTO);
    break;
  case 'y':
    operation(SEGMENTO2);
    break;
  case 't':
    operation(TRIANGOLO);
    break;
  case 'p':
    operation(PUNTO);
    break;
  case 'q':
    operation(QUADRATO);
    break;
  case 'b':
    operation(BICI);
    break;
  case 'c':
    operation(CUBO);
    break;
  case 27:         //ESC
    operation(EXIT);
    break;
  case 'v':  //rotazione intorno all'asse z
    psi+=10;
    break;
  case 'g':  //rotazione intorno all'asse z
    psi-=10;
    break;
  case 'h':
    xx-=0.2;
    break;
  case 'j':
    xx+=0.2;
    break;
  case 'u':
    yy+=0.2;
    break;
  case 'n':
    yy-=0.2;
    break;
  case 'z':
    zz+=0.2;
    break;
  case 'x':
    zz-=0.2;
    break;
  case 'a':
    operation(ANIMATION);
    break;
  case '?':
    operation(RANDOM);
    break;
  case '!':
    operation(RANDOM3D);
    break;
  case '0':
    operation(BELLO);
    break;
  };
  display();
}

void special_keyboard(int key,int x,int y){
  switch(key){
  case GLUT_KEY_LEFT: 
    theta-=10;
    break;
  case GLUT_KEY_RIGHT:    
    theta+=10;
    break;
  case GLUT_KEY_UP:
    phi-=10;
    break;
  case GLUT_KEY_DOWN:
    phi+=10;
    break;
  };
  display();
}

void idle_function() {
  if (animation) {
    theta+=1*(1<<recurseLevel);
    display();
  }
};

void init_light(){
  const float ambient[]={0.3, 0.3, 0.3, 1.0};
  const float diffuse[]={0.7, 0.7, 0.7, 1.0};
  const float position[]={0.0, 2.0, 3, 1.0};
  const float specular[]={0.2,0.2,0.2,1};

  glEnable(GL_LIGHTING);
  glEnable(GL_NORMALIZE);

  glLightfv(GL_LIGHT0,GL_AMBIENT,ambient);
  glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuse);
  glLightfv(GL_LIGHT0,GL_POSITION,position);

  glEnable(GL_LIGHT0);

  glEnable(GL_COLOR_MATERIAL);
  glColorMaterial(GL_FRONT,GL_AMBIENT_AND_DIFFUSE);
}

void bici(void)
{
  float angolo;
  glColor3f(0.2,0.2,0.2);
  //  RUOTA  SX
  glBegin(GL_LINE_LOOP);
  for(angolo = 0; angolo < 2.0*M_PI; angolo+=2.0*M_PI/20.0)
    {
      glVertex2f(sin(angolo)/6+1.0/6, cos(angolo)/6+1.0/6);
    }
  glEnd();
  //  RUOTA DX
  glBegin(GL_LINE_LOOP);
  for(angolo = 0; angolo < 2.0*M_PI; angolo+=2.0*M_PI/20.0)
    {
      glVertex2f(sin(angolo)/6+5.0/6, cos(angolo)/6+1.0/6);
    }
  glEnd();
  glColor3f(0,0,1);
  //TELAIO TRIANGOLO RUOTA DX
  glBegin(GL_LINE_LOOP);
  glVertex2f( 5.0/6, 1.0/6);
  glVertex2f(3.0/6,1.0/6);
  glVertex2f( 4.0/6,3.0/6);
  glEnd();
  //TELAIO TRIANGOLO TORTO
  glBegin(GL_LINE_LOOP);
  glVertex2f( 1.0/6, 3.0/6);
  glVertex2f(3.0/6,1.0/6);
  glVertex2f( 4.0/6,3.0/6);
  glEnd();
  //TELAIO SOTTO MANUBRIO 
  glBegin(GL_LINE_LOOP);
  glVertex2f( 1.0/6, 3.0/6);
  glVertex2f( 1.0/6,1.0/6);
  glEnd();
  //SOTTO MANUBRIO E SOTTO SELLINO
  glBegin(GL_LINES);
  glVertex2f( 1.0/6, 3.0/6);
  glVertex2f( 1.0/6,3.0/6+1.0/12);
  glVertex2f( 4.0/6, 3.0/6);
  glVertex2f( 4.0/6,3.0/6+1.0/12);
  glEnd();
  glColor3f(0,0,0);   // nero
 //MANUBRIO
  glBegin(GL_LINES);
  glVertex3f(1.0/6+0.01,3.0/6+1.0/12+0.02,-1.0/12);
  glVertex3f(1.0/6-0.01,3.0/6+1.0/12-0.02,1.0/12);
  glEnd();
  //SELLINO
  glBegin(GL_TRIANGLES);
  glVertex3f(4.0/6+1.0/24+0.01,3.0/6+1.0/12+0.01,-1.0/24);
  glVertex3f(4.0/6+1.0/24-0.01,3.0/6+1.0/12-0.01,1.0/24);
  glVertex3f(4.0/6-1.0/12,3.0/6+1.0/12,0);
  glEnd();
}

void menu_callback(int v) {
  operation(v);
}

void init_menu() {
  int frattale,base,iter;
 
  frattale=glutCreateMenu(menu_callback);
  glutAddMenuEntry("Koch",KOCH);
  glutAddMenuEntry("Felce",FELCE);
  glutAddMenuEntry("Sierpinski",SIERPINSKI);
  glutAddMenuEntry("Sierpinski 3D",SIERPINSKI_CUBO);
  glutAddMenuEntry("Random",RANDOM);
  glutAddMenuEntry("Random3D",RANDOM3D);

  base=glutCreateMenu(menu_callback);
  glutAddMenuEntry("segmento",SEGMENTO);
  glutAddMenuEntry("segmento2",SEGMENTO2);
  glutAddMenuEntry("triangolo",TRIANGOLO);
  glutAddMenuEntry("punto",PUNTO);
  glutAddMenuEntry("quadrato",QUADRATO);
  glutAddMenuEntry("cubo",CUBO);
  glutAddMenuEntry("bicicletta",BICI);

  iter=glutCreateMenu(menu_callback);
  glutAddMenuEntry("+ iter",ITERPIU);
  glutAddMenuEntry("- iter",ITERMENO);
  glutAddMenuEntry("0 iter",ITER0);
  glutAddMenuEntry("1 iter",ITER1);
  glutAddMenuEntry("2 iter",ITER2);
  glutAddMenuEntry("3 iter",ITER3);
  glutAddMenuEntry("4 iter",ITER4);
  
  glutCreateMenu(menu_callback);
  glutAttachMenu(GLUT_LEFT_BUTTON);
  glutAddSubMenu("frattale",frattale);
  glutAddSubMenu("base",base);
  glutAddSubMenu("iter",iter);
  glutAddMenuEntry("animazione",ANIMATION);
  glutAddMenuEntry("quit",EXIT);  
};
