/* Calcul des directions de reflexion           */
/* et de transmission a l'interface             */
/* entre deux milieux                           */
/*                                              */
/* Auteur: Nicolas JANEY                        */
/* nicolas.janey@univ-fcomte.fr                 */
/* Novembre 2012                                */

#include <stdio.h>
#include <math.h>

#include <GL/glut.h>
#include <GL/gl.h>
#include <GL/glu.h>

#include "Pos3D.h"
#include "Dir3D.h"
#include "Rt3D.h"
#include "TG3D.h"

#include "ModuleFleche.h"

/* Variables et constantes globales             */

#ifndef M_PI
#define M_PI 3.1415926535897932384626433832795
#endif

static const float blanc[] = { 1.0F,1.0F,1.0F,1.0F };
static const float rouge[] = { 1.0F,0.0F,0.0F,1.0F };
static const float bleu[] = { 0.0F,0.0F,1.0F,1.0F };
static const float jaune[] = { 1.0F,1.0F,0.0F,1.0F };
static const float magenta[] = { 1.0F,0.0F,1.0F,1.0F };
static const float gris[]  = { 0.7F,0.7F,0.7F,1.0F };
static float rx = -70.0F;
static float ry = 0.0F;
static float rz = -75.0F;

static double az = 0.0;
static double ai = 25.0;

static double ni = 2.4244;
static double nt = 1.3333;

static int val = 0;

/* Fonction executee lors de la frappe          */
/* d'une touche du clavier                      */

static void keyboard(unsigned char key,int x,int y) {
  switch ( key ) {
    case 0x0D :
      val = (val+1)%3;
      glutPostRedisplay();
      break;
    case 0x1B :
      exit(0); 
      break; }
}

/* Fonction executee lors de l'appui            */
/* d'une touche de curseur ou d'une touche      */
/* page up ou page down                         */

static void special(int key,int x,int y) {
  int mod = glutGetModifiers();
  switch(key) {
    case GLUT_KEY_UP :
      switch ( mod ) {
        case GLUT_ACTIVE_SHIFT :
          ai++;
          break;
        case GLUT_ACTIVE_CTRL :
          ni = pow(ni,101.0/100.0);
          break;
        case GLUT_ACTIVE_ALT :
          nt = pow(nt,101.0/100.0);
          break;
        default :
          rx++;
          break; }
      glutPostRedisplay();
      break;
    case GLUT_KEY_DOWN :
      switch ( mod ) {
        case GLUT_ACTIVE_SHIFT :
          ai--;
          break;
        case GLUT_ACTIVE_CTRL :
          ni = pow(ni,100.0/101.0);
          break;
        case GLUT_ACTIVE_ALT :
          nt = pow(nt,100.0/101.0);
          break;
        default :
          rx--;
          break; }
      glutPostRedisplay();
      break;
    case GLUT_KEY_LEFT :
      if ( mod == GLUT_ACTIVE_SHIFT )
        az++;
        else
        ry++;
      glutPostRedisplay();
      break;
    case GLUT_KEY_RIGHT :
      if ( mod == GLUT_ACTIVE_SHIFT )
        az--;
        else
        ry--;
      glutPostRedisplay();
      break;
    case GLUT_KEY_PAGE_UP :
      rz++;
      glutPostRedisplay();
      break;
    case GLUT_KEY_PAGE_DOWN :
      rz--;
      glutPostRedisplay();
      break; }
}

/* Fonction d'initialisation des parametres     */
/* OpenGL ne changeant pas au cours de la vie   */
/* du programme                                 */

static void init(void) {
  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_NORMALIZE);
  glEnable(GL_AUTO_NORMAL);
  glEnable(GL_LIGHT0);
}

/* Scene dessinee                               */

static void traceRayon(Dir3D *rayon,const float *coul) {
  glEnable(GL_LIGHTING);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,coul);
  flecheEnVolume(rayon->x,rayon->y,rayon->z,0.06F,0.3F,0.02F);
  glDisable(GL_LIGHTING);
}

static void scene(void) {
  Rt3D rtx(rx,1.0,0.0,0.0);
  Rt3D rty(ry,0.0,1.0,0.0);
  Rt3D rtz(rz,0.0,0.0,1.0);
  TG3D m1(&rty,&rtz);
  TG3D m2(&rtx,&m1);
  Pos3D p1( 1.0F, 1.0F, 0.0F);
  Pos3D p2(-1.0F, 1.0F, 0.0F);
  Pos3D p3(-1.0F,-1.0F, 0.0F);
  Pos3D p4( 1.0F,-1.0F, 0.0F);
  p1.transformation(&m2);
  p2.transformation(&m2);
  p3.transformation(&m2);
  p4.transformation(&m2);
  glPushMatrix();
  glColor3fv(blanc);
  glBegin(GL_LINE_LOOP);
  glVertex3d(p1.x,p1.y,p1.z);
  glVertex3d(p2.x,p2.y,p2.z);
  glVertex3d(p3.x,p3.y,p3.z);
  glVertex3d(p4.x,p4.y,p4.z);
  glEnd();
  glPopMatrix();
  Dir3D v1(&p1,&p3);
  Dir3D n(&p1,&p2);
  n.produitVectoriel(&v1);
  n.normalisation();
  Rt3D rtaz0(az,0.0F,0.0F,1.0F);
  TG3D rtaz(&m2,&rtaz0);
  Dir3D d1(1.0F,0.0F,0.0F);
  d1.transformation(&rtaz);
  Rt3D rta(ai,&d1);
  Dir3D i(n);
  i.transformation(&rta);
  Dir3D r;
  Dir3D t;
  traceRayon(&n,magenta);
  traceRayon(&i,jaune);
  printf("ni: %9.4lf, nt:%9.4lf\nthetai: %9.4lf\n",ni,nt,acos(n.produitScalaire(&i))*180.0F/M_PI);
  printf("Normale        : ");
  n.print();
  printf("\n");
  printf("rayon incident : ");
  i.print();
  printf("\n");
  printf("rayon reflechi : ");
  if ( r.reflexion(&i,&n) ) {
    traceRayon(&r,rouge);
    r.print(); }
    else {
    printf("N/A"); }
  printf("\n");
  printf("rayon transmis : ");
  if ( t.transmission(&i,&n,ni/nt) ) {
    traceRayon(&t,bleu);
    t.print();
    printf(" thetat: %9.4lf",acos(-n.produitScalaire(&t))*180.0/M_PI); }
    else {
    printf("N/A"); }
  printf("\n\n");
}

/* Fonction executee lors d'un rafraichissement */
/* de la fenetre de dessin                      */

static void display(void) {
  glClearColor(0.5F,0.5F,0.5F,1.0F);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  switch (val) {
    case 0 :
      ni = 2.4244;
      nt = 1.3333;
      ai = 25.0;
      break;
    case 1 :
      ni = 1.5547;
      nt = 2.7958;
      ai = 53.026449;
      break;
    case 2 :
      ni = 1.8127;
      nt = 1.2844;
      ai = 46.012272;
      break; }
  scene();
  glPopMatrix();
  glFlush();
  glutSwapBuffers();
  int error = glGetError();
  if ( error != GL_NO_ERROR )
    printf("Erreur OpenGL: %d\n",error);
}

/* Fonction executee lors d'un changement       */
/* de la taille de la fenetre OpenGL            */
/* -> Ajustement de la camera de visualisation  */

static void reshape(int x,int y) {
  glViewport(0,0,x,y); 
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if ( x > y )
    glOrtho(-1.1*(double) x/y,1.1*(double) x/y,-1.1,1.1,-2.0,2.0);
    else
    glOrtho(-1.1,1.1,-1.1*(double) y/x,1.1*(double) y/x,-2.0,2.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

/* Fonction principale                          */

int main(int argc,char **argv) {
  glutInit(&argc,argv);
  glutInitDisplayMode(GLUT_RGBA|GLUT_DEPTH|GLUT_DOUBLE);
  glutInitWindowSize(300,300); 
  glutInitWindowPosition(50,50); 
  glutCreateWindow("Reflexion et transmission"); 
  init();
  glutKeyboardFunc(keyboard);
  glutSpecialFunc(special);
  glutReshapeFunc(reshape);
  glutDisplayFunc(display);
  glutMainLoop();
  return(0);
}
