// pallina che ruota dentro tubi
//#include <windows.h>
#include <GL/glut.h>
#include <GL/glu.h>
#include <GL/gl.h>

#include <stdlib.h>
#include <stdio.h>
#include <math.h>

typedef GLfloat GLTVector3[3];

GLfloat roty=0.0;
GLfloat rotx=0.0;
int enable_animation=0;
int enable_wireframe=0;
GLfloat sphx, sphy, sphz;
GLfloat angle=0.0;
int selected[4];

#define TORO_PARTE 1

GLfloat gltGetVectorLength(const GLTVector3 vVector){
    return (GLfloat)sqrt((vVector[0]*vVector[0]) +
                (vVector[1]*vVector[1]) + (vVector[2]*vVector[2]) );
}

void gltSubtractVectors(const GLTVector3 vFirst, const GLTVector3 vSecond, GLTVector3 vResult) {
    vResult[0] = vFirst[0] - vSecond[0];
    vResult[1] = vFirst[1] - vSecond[1];
    vResult[2] = vFirst[2] - vSecond[2];
}

void gltScaleVector(GLTVector3 vVector, const GLfloat fScale){
    vVector[0] *= fScale; vVector[1] *= fScale; vVector[2] *= fScale;
    }

void gltVectorCrossProduct(const GLTVector3 vU, const GLTVector3 vV, GLTVector3 vResult)
        {
        vResult[0] = vU[1]*vV[2] - vV[1]*vU[2];
        vResult[1] = -vU[0]*vV[2] + vV[0]*vU[2];
        vResult[2] = vU[0]*vV[1] - vV[0]*vU[1];
        }

void gltNormalizeVector(GLTVector3 vNormal)
    {
    GLfloat fLength = 1.0f / gltGetVectorLength(vNormal);
    gltScaleVector(vNormal, fLength);
    }

// Given three points on a plane in counter clockwise order, calculate the unit normal
void gltGetNormalVector(const GLTVector3 vP1, const GLTVector3 vP2, const GLTVector3 vP3, GLTVector3 vNormal)
    {
    GLTVector3 vV1, vV2;
    gltSubtractVectors(vP2, vP1, vV1);
    gltSubtractVectors(vP3, vP1, vV2);
    gltVectorCrossProduct(vV1, vV2, vNormal);
    gltNormalizeVector(vNormal);
    }



void init_scene (void)
{
 float intervalli_cerchio=30.0;
 float intervalli_corona=30.0;
 float theta=0;
 float phi=0;
 float r=40;
 float d=120;
 GLTVector3 vertice1,vertice2,vertice3;
 GLTVector3 vNormal;

 glNewList(TORO_PARTE,GL_COMPILE);
  // i pezzi di toroide si possono disegnare come tanti quadrati (in un doppio ciclo for) a partire dalle coordinate dei punti sulla superficie
  // le coordinate dei punti si possono calcolare scrivendo l'equazione parametrica (in coordinate omogenee) di una circonferenza di raggio r sul piano z=0, centrata nel punto (d,0,0), (x,y,z)=(r*cos(phi)+d,r*sin(phi),0,1)
  // e poi applicando a tali punti la matrice di rotazione canonica rispetto a y Ry(theta), con parametri 0<phy<2*3.14  e 0<theta<3.14/2
  // Disegno i quadrati in modalità GL_QUAD_STRIP, specifico le normali per poligono (quindi devo calcolare le coordinate di tre vertici per ogni poligono e poi calcolare il vettore normale)
  for (int int_cer = 0; int_cer < intervalli_cerchio; int_cer++) {
    theta=0;
	glBegin(GL_QUAD_STRIP);
     for (int int_cor = 0; int_cor <= intervalli_corona; int_cor++) {
	  vertice1[0]=r*cos(phi)*cos(theta)+d*cos(theta);                                                             vertice1[1]= r*sin(phi);                                vertice1[2]=-r*cos(phi)*sin(theta)-d*sin(theta);
	  vertice2[0]=r*cos(phi-(2*3.14159)/intervalli_cerchio)*cos(theta)+d*cos(theta);                              vertice2[1]= r*sin(phi-(2*3.14159)/intervalli_cerchio); vertice2[2]=-r*cos(phi-(2*3.14159)/intervalli_cerchio)*sin(theta)-d*sin(theta);
	  vertice3[0]=r*cos(phi)*cos(theta+(3.14159/2)/intervalli_corona)+d*cos(theta+(3.14159/2)/intervalli_corona); vertice3[1]= r*sin(phi);                                vertice3[2]=-r*cos(phi)*sin(theta+(3.14159/2)/intervalli_corona)-d*sin(theta+(3.14159/2)/intervalli_corona);
	  gltGetNormalVector(vertice1, vertice2, vertice3, vNormal);
	  glNormal3fv(vNormal);
      glVertex3fv(  vertice1 );
      glVertex3fv(  vertice2 );
	  theta+=((3.14159/2)/intervalli_corona);
	}
    glEnd();
	phi-=((2*3.14159)/intervalli_cerchio);
  }
 glEndList();
}

void Keyboard(unsigned char key, int x, int y)
{
   switch (key) {
      case 'w':
         rotx+=1.0;
         break;
	  case 's':
         rotx-=1.0;
		 break;
	  case 'a':
         roty-=1.0;
         break;
	  case 'd':
         roty+=1.0;
         break;
	  case 'c':
         enable_animation=!enable_animation;
		 for (int j=0; j<4; j++)
			 selected[j]=0;
		 break;
      case 'z':
         enable_wireframe=!enable_wireframe;
         break;
	  case '1':
         selected[0]=!selected[0];
         break;
	  case '2':
         selected[1]=!selected[1];
		 break;
	  case '3':
         selected[2]=!selected[2];
         break;
	  case '4':
         selected[3]=!selected[3];
         break;
   }
   glutPostRedisplay();
}


GLfloat light0_position[] = { 400.0,200.0,100.0, 1.0 };
void init (void)
{
   GLfloat light0_ambient[] = { 0.5f, 0.5f, 0.5f, 1.0f };
   GLfloat light0_diffuse[] = { 0.4f, 0.4f, 0.4f, 1.0f };
   GLfloat light0_specular[] = { 1.0, 1.0, 1.0, 1.0 };
   GLfloat specref[] = { 0.5f, 0.5f, 0.5f, 1.0f };
   GLfloat  ambientLightON[] = { 0.4f, 0.4f, 0.4f, 1.0f};
  
   glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambientLightON);

   glLightfv (GL_LIGHT0, GL_AMBIENT, light0_ambient);
   glLightfv (GL_LIGHT0, GL_DIFFUSE, light0_diffuse);
   glLightfv (GL_LIGHT0, GL_SPECULAR, light0_specular);
   glLightfv (GL_LIGHT0, GL_POSITION, light0_position);
   glEnable(GL_LIGHT0);

   glEnable(GL_COLOR_MATERIAL);
   glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
   glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, light0_specular);
   glMateriali(GL_FRONT_AND_BACK, GL_SHININESS, 128);

   glEnable (GL_LIGHTING);
   glEnable(GL_CULL_FACE);
   glEnable(GL_DEPTH_TEST);
   glShadeModel(GL_SMOOTH);
   glEnable(GL_NORMALIZE);

   glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
   glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
   glClearColor(1.0, 1.0, 1.0, 0.0);

   init_scene();
}

void display (void)
{
   glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
   glInitNames();
   glPushName(0);

   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();

   glPushMatrix ();
   glTranslatef (0.0, 0.0, -750.0); 
   glRotatef(rotx,1.0,0.0,0.0);
   glRotatef(roty,0.0,1.0,0.0);
   
   glColor3f(0.4,0.0,0.0);
   if (enable_wireframe)
	glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
   //disegno i pezzi di toroide dopo aver disabilitato il back-face culling perchè devo visualizzare la parte interna (back-face) dei pezzi del toroide
   glDisable(GL_CULL_FACE);
   glPushMatrix();
       if (!enable_animation)
        glTranslatef (80.0, 0.0, -80.0); 
	    glLoadName(1);
		if (!selected[0])
	     glCallList(TORO_PARTE);
   glPopMatrix();
   glPushMatrix();
       if (!enable_animation)
        glTranslatef (-80.0, 0.0, -80.0); 
       glRotatef(180,0.0,0.0,1.0);
	    glLoadName(2);
       if (!selected[1])
	   glCallList(TORO_PARTE);
   glPopMatrix();
   glPushMatrix();
       if (!enable_animation)
        glTranslatef (80.0, 0.0, 80.0); 
       glRotatef(180,1.0,0.0,0.0);
	    glLoadName(3);
       if (!selected[2])
	   glCallList(TORO_PARTE);
   glPopMatrix();
   glPushMatrix();
       if (!enable_animation)
        glTranslatef (-80.0, 0.0, 80.0); 
       glRotatef(180,1.0,0.0,1.0);
	    glLoadName(4);
	   if (!selected[3])
	   glCallList(TORO_PARTE);
   glPopMatrix();
   glEnable(GL_CULL_FACE);
   glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
	
   if (enable_animation)
   {
   glColor3f(0.0,0.0,0.4);
   glPushMatrix();
    glTranslatef (120.0*cos(angle), 0.0, 120.0*sin(angle)); 
    glutSolidSphere( 20, 40, 40);
   glPopMatrix();
   }
   glPopMatrix();
   glutSwapBuffers();
}

void reshape(int w, int h)
{
   // Prevent a divide by zero
	if(h == 0)
		h = 1;

	// Set Viewport to window dimensions
    glViewport(0, 0, w, h);

	// Calculate aspect ratio of the window
	float fAspect = (GLfloat)w/(GLfloat)h;

	// Set the perspective coordinate system
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	gluPerspective(45.0f, fAspect, 1.0, 1480);

    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity ();

}

void IdleFunc()
{
  if (enable_animation)
   angle+=0.01;
   
  glutPostRedisplay(); 
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
   glutInitWindowSize (800, 800);
   glutCreateWindow (argv[0]);
   init();
   glutReshapeFunc (reshape);
   glutDisplayFunc(display);
   glutIdleFunc(IdleFunc);
   glutKeyboardFunc(Keyboard);
   glutMainLoop();
   return 0; 
}
