/* Calcul de diffusion Lambertienne             */
/* sous l'éclairage de lumieres directionnelles */
/* et ponctuelles                               */
/*                                              */
/* Auteur: Nicolas JANEY                        */
/* nicolas.janey@univ-fcomte.fr                 */
/* Novembre 2020                                */

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

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

#include "Couleur.h"
#include "Energie.h"
#include "LumierePonctuelle.h"
#include "LumiereDirectionnelle.h"
#include "Pos3D.h"
#include "Dir3D.h"
#include "Rt3D.h"

/* Variables et constantes globales             */

static const float blanc[] = { 1.0F,1.0F,1.0F,1.0F };
static const float noir[]  = { 0.0F,0.0F,0.0F,1.0F };
static float rx = 0.0F;
static float ry = 0.0F;
static float rz = 0.0F;

static double dist = 5.0;
static int l = 1;
static bool attenuationLp = false;
static bool onLp = true;
static bool onLd = true;
static double iLp = 1.2; 
static double iLd = 1.5;
static float cLp[]  = { 1.0F,0.5F,1.0F,1.0F };
static float cLd[]  = { 1.0F,1.0F,0.5F,1.0F };
static float couleur[]  = { 0.3F,0.6F,0.9F,1.0F };

static Couleur *c;
static LumierePonctuelle *lp;
static LumiereDirectionnelle *ld;

static int w1;
static int w2;

static void postRedisplay(void) {
  glutPostWindowRedisplay(w1);
  glutPostWindowRedisplay(w2);
}

/* Fonction d'affectation de leurs valeurs      */
/* aux lumieres                                 */

static void ajusteMaterielEtLumiere(void) {
  c->r = couleur[0];
  c->v = couleur[1];
  c->b = couleur[2];
  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(&rtz,&rty);
  TG3D m(&m1,&rtx);
  if ( l ) {
    ld->on = onLd;
    ld->c->r = cLd[0];
    ld->c->v = cLd[1];
    ld->c->b = cLd[2];
    ld->intensite = iLd;
    Dir3D d(dist,dist,dist);
    d.transformation(&m);
    ld->d->x = d.x;
    ld->d->y = d.y;
    ld->d->z = d.z; }
    else {
    lp->on = onLp;
    lp->c->r = cLp[0];
    lp->c->v = cLp[1];
    lp->c->b = cLp[2];
    lp->intensite = iLp;
    Pos3D p(-dist,dist,dist);
    p.transformation(&m);
    lp->p->x = p.x;
    lp->p->y = p.y;
    lp->p->z = p.z;
    lp->attenuation = attenuationLp; }
  printf("Coefficients diffusion du materiel: ");
  c->print();
  printf("\n");
  if ( l ) {
    printf("Ld: ");
    ld->print();
    printf("\n"); }
    else {
    printf("Lp: ");
    lp->print();
    printf("\n"); } 
}

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

static void diminueCoefficient(float *c) {
  *c -= 0.01F;
  if ( *c < 0.0F )
    *c = 0.0F;
}

static void augmenteCoefficient(float *c) {
  *c += 0.01F;
  if ( *c > 1.0F )
    *c = 1.0F;
}

static void keyboard(unsigned char key,int x,int y) {
  switch ( key ) {
    case 0x20 :
      l = (l+1)%2;
      ajusteMaterielEtLumiere();
      postRedisplay(); 
      break;
    case 'o' :
      if ( l ) {
        onLd = !onLd; }
        else {
        onLp = !onLp; }
      ajusteMaterielEtLumiere();
      postRedisplay();
      break;
    case 'a' :
      attenuationLp = !attenuationLp;
      ajusteMaterielEtLumiere();
      postRedisplay();
      break;
    case 'D' :
      dist += 0.01;
      ajusteMaterielEtLumiere();
      postRedisplay();
      break;
    case 'd' :
      dist -= 0.01;
      ajusteMaterielEtLumiere();
      postRedisplay();
      break;
    case 'i' :
      if ( l ) {
        iLd -= 0.01;
        if ( iLd < 0.0 )
          iLd = 0.0; }
        else {
        iLp -= 0.01;
        if ( iLp < 0.0 )
          iLp = 0.0; }
      ajusteMaterielEtLumiere();
      postRedisplay();
      break;
    case 'I' :
      if ( l ) {
        iLd += 0.01; }
        else {
        iLp += 0.01; }
      ajusteMaterielEtLumiere();
      postRedisplay();
      break;
    case 'j' :
      if ( l ) {
        iLd -= 0.1;
        if ( iLd < 0.0 )
          iLd = 0.0; }
        else {
        iLp -= 0.1;
        if ( iLp < 0.0 )
          iLp = 0.0; }
      ajusteMaterielEtLumiere();
      postRedisplay();
      break;
    case 'J' :
      if ( l ) {
        iLd += 0.1; }
        else {
        iLp += 0.1; }
      ajusteMaterielEtLumiere();
      postRedisplay();
      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 gestionDiminution(int modifier,float *c,float *cLd,float *cLp,float *a) {
  switch ( glutGetModifiers() ) {
    case GLUT_ACTIVE_CTRL :
      diminueCoefficient(c);
      break;
    case GLUT_ACTIVE_SHIFT :
      if ( l )
        diminueCoefficient(cLd);
        else
        diminueCoefficient(cLp);
      break;
    default :
      (*a)++;
      break; }
}

static void gestionAugmentation(int modifier,float *c,float *cLd,float *cLp,float *a) {
  switch ( glutGetModifiers() ) {
    case GLUT_ACTIVE_CTRL :
      augmenteCoefficient(c);
      break;
    case GLUT_ACTIVE_SHIFT :
      if ( l )
        augmenteCoefficient(cLd);
        else
        augmenteCoefficient(cLp);
      break;
    default :
      (*a)--;
      break; }
}

static void special(int key,int x,int y) {
  switch(key) {
    case GLUT_KEY_UP :
      gestionAugmentation(glutGetModifiers(),&couleur[0],&cLd[0],&cLp[0],&rx);
      ajusteMaterielEtLumiere();
      postRedisplay();
      break;
    case GLUT_KEY_DOWN :
      gestionDiminution(glutGetModifiers(),&couleur[0],&cLd[0],&cLp[0],&rx);
      ajusteMaterielEtLumiere();
      postRedisplay();
      break;
    case GLUT_KEY_LEFT :
      gestionAugmentation(glutGetModifiers(),&couleur[1],&cLd[1],&cLp[1],&ry);
      ajusteMaterielEtLumiere();
      postRedisplay();
      break;
    case GLUT_KEY_RIGHT :
      gestionDiminution(glutGetModifiers(),&couleur[1],&cLd[1],&cLp[1],&ry);
      ajusteMaterielEtLumiere();
      postRedisplay();
      break;
    case GLUT_KEY_PAGE_UP :
      gestionAugmentation(glutGetModifiers(),&couleur[2],&cLd[2],&cLp[2],&rz);
      ajusteMaterielEtLumiere();
      postRedisplay();
      break;
    case GLUT_KEY_PAGE_DOWN :
      gestionDiminution(glutGetModifiers(),&couleur[2],&cLd[2],&cLp[2],&rz);
      ajusteMaterielEtLumiere();
      postRedisplay();
      break; }
}

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

static void init1(void) {
  glEnable(GL_LIGHTING);
  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_NORMALIZE);
  glEnable(GL_AUTO_NORMAL);
//  glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
}

static void init2(void) {
  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
//  glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
}

#ifndef M_PI
#define M_PI 3.14159F
#endif

void solidSphere(double r,int nl,int ns,Couleur *couleur,Lumiere *lumiere) {
  Energie *e = new Energie();
  Pos3D *p = new Pos3D();
  Dir3D *n = new Dir3D();
  for ( int i = 0 ; i < ns ; i++ ) {
    float a1 = -M_PI/2.0F + i*M_PI/ns ;
    float a2 = a1 + M_PI/ns ;
    float cs1 = -cos(a1);
    float cs2 = -cos(a2);
    float sn1 = sin(a1);
    float sn2 = sin(a2);
    glBegin(GL_QUAD_STRIP);
    for ( int j = 0 ; j <= nl ; j++ ) {
      float a = j*2*M_PI/nl;
      float x1 = cs1*sin(a);
      float y1 = cs1*cos(a);
      float x2 = cs2*sin(a);
      float y2 = cs2*cos(a);
      p->x = r*x1;
      p->y = r*y1;
      p->z = r*sn1;
      n->x = x1;
      n->y = y1;
      n->z = sn1;
      lumiere->energieDiffusee(e,p,n,couleur);
      glColor3f(e->r,e->v,e->b);
      glVertex3d(p->x,p->y,p->z);
      p->x = r*x2;
      p->y = r*y2;
      p->z = r*sn2;
      n->x = x2;
      n->y = y2;
      n->z = sn2;
      lumiere->energieDiffusee(e,p,n,couleur);
      glColor3f(e->r,e->v,e->b);
      glVertex3f(p->x,p->y,p->z); }
    glEnd(); }
  delete(n);
  delete(p);
  delete(e);
}

static void scene2(void) {
  glPushMatrix();
  if ( l ) {
    solidSphere(4.0,90,90,c,ld); }
    else {
    solidSphere(4.0,90,90,c,lp); }
  glPopMatrix();
}

static void clean(void) {
  delete(c);
  delete(ld);
  delete(lp);
}

/* Scene dessinee                               */

static void scene1(void) {
  const GLfloat mat_shininess[] = { 50.0 };
  float ldif[4];
  float lpos[4];
  if ( l ) {
    if ( ld->on )
      glEnable(GL_LIGHT0);
      else
      glDisable(GL_LIGHT0);
    ldif[0] = ld->intensite*ld->c->r;
    ldif[1] = ld->intensite*ld->c->v;
    ldif[2] = ld->intensite*ld->c->b;
    ldif[3] = 1.0F;
    lpos[0] = ld->d->x;
    lpos[1] = ld->d->y;
    lpos[2] = ld->d->z;
    lpos[3] = 0.0F; }
    else {
    if ( lp->on )
      glEnable(GL_LIGHT0);
      else
      glDisable(GL_LIGHT0);
    ldif[0] = lp->intensite*lp->c->r;
    ldif[1] = lp->intensite*lp->c->v;
    ldif[2] = lp->intensite*lp->c->b;
    ldif[3] = 1.0F;
    lpos[0] = lp->p->x;
    lpos[1] = lp->p->y;
    lpos[2] = lp->p->z;
    lpos[3] = 1.0F; }
  glLightfv(GL_LIGHT0,GL_DIFFUSE,ldif);
  glLightfv(GL_LIGHT0,GL_POSITION,lpos);
  if ( attenuationLp ) {
    glLightf(GL_LIGHT0,GL_QUADRATIC_ATTENUATION,1.0F);
    glLightf(GL_LIGHT0,GL_CONSTANT_ATTENUATION,0.0F); }
    else {
    glLightf(GL_LIGHT0,GL_QUADRATIC_ATTENUATION,0.0F);
    glLightf(GL_LIGHT0,GL_CONSTANT_ATTENUATION,1.0F); }
  glMaterialfv(GL_FRONT,GL_DIFFUSE,couleur);
  glMaterialfv(GL_FRONT,GL_SPECULAR,noir);
  glMaterialfv(GL_FRONT,GL_AMBIENT,noir);
  glMaterialfv(GL_FRONT,GL_SHININESS,mat_shininess);
  glPushMatrix();
  glutSolidSphere(4.0,90,90);
  glPopMatrix();
}

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

static void display1(void) {
  glClearColor(0.5F,0.5F,0.5F,1.0F);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  scene1();
  glPopMatrix();
  glFlush();
  glutSwapBuffers();
  int error = glGetError();
  if ( error != GL_NO_ERROR )
    printf("Erreur OpenGL: %d\n",error);
}

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

static void display2(void) {
  glClearColor(0.5F,0.5F,0.5F,1.0F);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  scene2();
  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(-6.0*(double) x/y,6.0*(double) x/y,-6.0,6.0,-6.0,6.0);
    else
    glOrtho(-6.0,6.0,-6.0*(double) y/x,6.0*(double) y/x,-6.0,6.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

/* Fonction principale                          */

void select(int selection) {
  switch (selection) {
    case 0 :
    case 1 :
      l = selection;
      ajusteMaterielEtLumiere();
      postRedisplay(); 
      break;
    case -1 :
      exit(0);
      break; } 
  glutPostRedisplay();
}

static void makeMenu(void) {
  int menu = glutCreateMenu(select);
  glutAddMenuEntry("Lumiere ponctuelle",0);
  glutAddMenuEntry("Lumiere directionnelle",1);
  glutAddMenuEntry("Quitter",-1);
  glutAttachMenu(GLUT_RIGHT_BUTTON);
}

int main(int argc,char **argv) {
  atexit(clean);
  c = new Couleur();
  lp = new LumierePonctuelle();
  ld = new LumiereDirectionnelle();
  ajusteMaterielEtLumiere();
  glutInit(&argc,argv);
  glutInitDisplayMode(GLUT_RGBA|GLUT_DEPTH|GLUT_DOUBLE);
  glutInitWindowSize(300,300);
  glutInitWindowPosition(50,50); 
  w1 = glutCreateWindow("Eclairages OpenGL");
  makeMenu();
  init1();
  glutKeyboardFunc(keyboard);
  glutSpecialFunc(special);
  glutReshapeFunc(reshape);
  glutDisplayFunc(display1);
  glutInitWindowSize(300,300); 
  glutInitWindowPosition(50,400); 
  w2 = glutCreateWindow("Eclairages \"maison\""); 
  makeMenu();
  init2();
  glutKeyboardFunc(keyboard);
  glutSpecialFunc(special);
  glutReshapeFunc(reshape);
  glutDisplayFunc(display2);
  glutMainLoop();
  return(0);
}
