/* Courbe B-Spline non rationnelle uniforme     */
/*                                              */
/* Auteur: Nicolas JANEY                        */
/* nicolas.janey@univ-fcomte.fr                 */
/* Mars 2019                                    */

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

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

#include "Pos3D.h"

/* Variables et constantes globales             */

static const float blanc[] = { 1.0F,1.0F,1.0F,1.0F };
static const float gris[]  = { 0.7F,0.7F,0.7F,1.0F };
static float rx = 0.0F;
static float ry = 0.0F;
static float rz = 0.0F;
static int nb = 100;
static int nP = 0;
static int aff = 0;

static float tCouleur[7][3] = { { 1.0F,1.0F,1.0F },
                                { 1.0F,0.0F,1.0F },
                                { 0.0F,1.0F,1.0F },
                                { 1.0F,1.0F,0.0F },
                                { 1.0F,0.0F,0.0F },
                                { 0.0F,1.0F,0.0F },
                                { 0.0F,0.0F,1.0F } };

static int taille = 4;
static int nbPoints = 10;
static Pos3D *tPos[] = { new Pos3D(-6.0,-4.0, 1.0),
                         new Pos3D(-4.5, 3.0, 2.0),
                         new Pos3D( 2.0, 2.0, 3.0),
                         new Pos3D( 5.5,-3.0, 1.0),
                         new Pos3D( 4.5,-2.0,-3.0),
                         new Pos3D( 4.0, 4.0,-4.0),
                         new Pos3D( 0.0, 5.0,-2.0),
                         new Pos3D( 2.0,-2.0,-1.0),
                         new Pos3D(-1.0,-5.0,-2.0),
                         new Pos3D(-3.5,-4.0, 0.0) };

static double NRUBS[4][4]       = { { -1.0/6.0,  3.0/6.0, -3.0/6.0,  1.0/6.0 },
                                    {  3.0/6.0, -6.0/6.0,  3.0/6.0,      0.0 },
                                    { -3.0/6.0,      0.0,  3.0/6.0,      0.0 },
                                    {  1.0/6.0,  4.0/6.0,  1.0/6.0,      0.0 } };

static double CATMULL_ROM[4][4] = { { -1.0/2.0,  3.0/2.0, -3.0/2.0,  1.0/2.0 },
                                    {  2.0/2.0, -5.0/2.0,  4.0/2.0, -1.0/2.0 },
                                    { -1.0/2.0,      0.0,  1.0/2.0,      0.0 },
                                    {      0.0,  2.0/2.0,      0.0,      0.0 } };

static void vertex(Pos3D *p,int couleur,double taille) {
  glPushMatrix();
  glColor3f(tCouleur[couleur%7][0],tCouleur[couleur%7][1],tCouleur[couleur%7][2]);
  glTranslated(p->x,p->y,p->z);
  glutSolidSphere(taille,36,18);
  glPopMatrix();
}

///////////////////////////////////////////////////

/* Calcul la position d'un point sur une courbe  */
/* B-Spline controlee par quatre sommets         */
/* g : le tableau des 4 sommets de controle      */
/* t : la valeur de calcul du point              */
/*     t a prendre dans l'intervalle [0.0,1.0]   */
/* mb : la matrice de base                       */
/* p : le point resultat                         */

static void determinationPositionSurBSpline(Pos3D **g,double t,double mb[4][4],Pos3D *p) {
  double vt[4] = { t*t*t,t*t,t,1.0 };
  double vtmb[4] = { 0.0,0.0,0.0,0.0 };
  for ( int j = 0 ; j < 4 ; j++ ) {
    for ( int k = 0 ; k < 4 ; k++ )
      vtmb[j] += vt[k] * mb[k][j] ; }
  p->x = p->y = p->z = 0.0;
  for ( int j = 0 ; j < 4 ; j++ ) {
    p->x += vtmb[j] * g[j]->x ;
    p->y += vtmb[j] * g[j]->y ;
    p->z += vtmb[j] * g[j]->z ; }
}

/* Calcul les points definissant une courbe      */
/* B-Spline par morceaux definie par un ensemble */
/* de sommets de controle                        */
/* tPos : le tableau des sommets de controle     */
/* n : le nombre de sommets de contrôle          */
/* mb : la matrice de base                       */
/* nb : le nombre de points a calculer           */
/* tRes : le tableau de points resultat          */

static void calculBSpline(Pos3D **tPos,int n,double mb[4][4],int nb,Pos3D **tRes) {
  for ( int i = 0 ; i < nb ; i++ ) {
    double pos = i/(nb-1.0)*(n-3);
    int nb =(int) pos;
    if ( nb == n-3 )
      nb = n-4;
    double t = pos-nb;
    determinationPositionSurBSpline(&tPos[nb],t,mb,tRes[i]); }
}

///////////////////////////////////////////////////

static void determinationPositionSurBSpline(Pos3D **tPos,int n,double pos,double mb[4][4],Pos3D *p) {
  int nb =(int) pos;
  if ( nb == n-3 )
    nb = n-4;
  double t = pos-nb;
  determinationPositionSurBSpline(&tPos[nb],t,mb,p);
}

static Pos3D **allocationPos3D(int n) {
  Pos3D **tP =(Pos3D **) calloc(n,sizeof(Pos3D *));
  for ( int i = 0 ; i < n ; i++ )
    tP[i] = new Pos3D();
  return tP;
}

static void desallocationPos3D(Pos3D **tP,int n) {
  for ( int i = 0 ; i < n ; i++ )
    delete(tP[i]);
  free(tP);
}

static void modelisationBSpline(Pos3D **tPos,int n,int nb,double mb[4][4]) {
  Pos3D **tP = allocationPos3D(nb);
  calculBSpline(tPos,n,mb,nb,tP);
  for ( int i = 0 ; i < nb ; i++ ) {
    double pos = i/(nb-1.0)*(n-3);
    vertex(tP[i],( pos < n-3 ) ? (int) pos : n-4,0.05); }
  desallocationPos3D(tP,nb);
}

static Pos3D **creationTableauPremiereEtDernierePositionsDupliquees(Pos3D **tPos,int n,int nb) {
  Pos3D **tP = allocationPos3D(n+2*nb);
  int p = 0;
  for ( int i = 0 ; i < nb ; i++,p++) {
    tP[p]->x = tPos[0]->x;
    tP[p]->y = tPos[0]->y;
    tP[p]->z = tPos[0]->z; }
  for ( int i = 0 ; i < n ; i++,p++) {
    tP[p]->x = tPos[i]->x;
    tP[p]->y = tPos[i]->y;
    tP[p]->z = tPos[i]->z; }
  for ( int i = 0 ; i < nb ; i++,p++) {
    tP[p]->x = tPos[n-1]->x;
    tP[p]->y = tPos[n-1]->y;
    tP[p]->z = tPos[n-1]->z; }
  return tP;
}

/* 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) {
  switch (key) {
    case GLUT_KEY_UP :
      rx++;
      glutPostRedisplay();
      break;
    case GLUT_KEY_DOWN :
      rx--;
      glutPostRedisplay();
      break;
    case GLUT_KEY_LEFT :
      ry++;
      glutPostRedisplay();
      break;
    case GLUT_KEY_RIGHT :
      ry--;
      glutPostRedisplay();
      break;
    case GLUT_KEY_PAGE_UP :
      switch (glutGetModifiers()) {
        case GLUT_ACTIVE_ALT :
          nb++;
          glutPostRedisplay();
          break;
        case GLUT_ACTIVE_CTRL :
          nP++;
          if ( nP == nb )
            nP = nb-1;
          glutPostRedisplay();
          break;
        default :
          rz++;
          glutPostRedisplay(); }
      break;
    case GLUT_KEY_PAGE_DOWN :
      switch (glutGetModifiers()) {
        case GLUT_ACTIVE_ALT :
          nb--;
          if ( nb == 1 )
            nb = 2;
          if ( nP == nb )
            nP = nb-1;
          glutPostRedisplay(); 
          break;
        case GLUT_ACTIVE_CTRL :
          nP--;
          if ( nP < 0 )
            nP = 0;
          glutPostRedisplay();
          break;
        default :
          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);
}

/* Scene dessinee                               */

static void dessinerOpenGL(Pos3D **tP,int n) {
  glBegin(GL_LINE_STRIP);
  for ( int i = 0 ; i < n ; i++ ) {
    glVertex3d(tP[i]->x,tP[i]->y,tP[i]->z); }
  glEnd();
}

static void scene(void) {
  switch (aff) {
    case 0 :
      glColor3f(0.75F,0.75F,0.75F);
      dessinerOpenGL(tPos,taille);
      break;
    case 1 : {
      glColor3f(0.75F,0.75F,0.75F);
      dessinerOpenGL(tPos,taille);
      int nbSplines = taille-3;
      double pos =(double) nP/(nb-1)*nbSplines;
      Pos3D p;
      determinationPositionSurBSpline(tPos,taille,pos,NRUBS,&p);
      vertex(&p,0,0.2); }
      break;
    case 2 : {
      glColor3f(0.75F,0.75F,0.75F);
      dessinerOpenGL(tPos,taille);
      modelisationBSpline(tPos,taille,nb,NRUBS); }
      break;
    case 3 : {
      glColor3f(0.75F,0.75F,0.75F);
      dessinerOpenGL(tPos,taille);
      Pos3D **ntPos = creationTableauPremiereEtDernierePositionsDupliquees(tPos,taille,2);
      modelisationBSpline(ntPos,taille+4,nb,NRUBS);
      desallocationPos3D(ntPos,taille+4); }
      break;
    case 4 : {
      glColor3f(0.75F,0.75F,0.75F);
      dessinerOpenGL(tPos,taille);
      modelisationBSpline(tPos,taille,nb,CATMULL_ROM); }
      break;
    case 5 : {
      glColor3f(0.75F,0.75F,0.75F);
      dessinerOpenGL(tPos,taille);
      Pos3D **ntPos = creationTableauPremiereEtDernierePositionsDupliquees(tPos,taille,1);
      modelisationBSpline(ntPos,taille+2,nb,CATMULL_ROM);
      desallocationPos3D(ntPos,taille+2); }
      break; }
}

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

static void display(void) {
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glRotatef(rx,1.0F,0.0F,0.0F);
  glRotatef(ry,0.0F,1.0F,0.0F);
  glRotatef(rz,0.0F,0.0F,1.0F);
  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 tx,int ty) {
  glViewport(0,0,tx,ty); 
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  double ratio =(double) tx/ty;
  if ( ratio >= 1.0 )
    glOrtho(-6.0*ratio,6.0*ratio,-6.0,6.0,-10.0,10.0);
    else
    glOrtho(-6.0,6.0,-6.0/ratio,6.0/ratio,-10.0,10.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

/* 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 :
      taille = ( taille == 10 ) ? 4 : 10;
      glutPostRedisplay(); 
      break;
    case 0x20 :
      aff = (aff+1)%6;
      glutPostRedisplay();
      break;
    case 0x1B :
      exit(0); 
      break; }
}

static void clean(void) {
  for ( int i = 0 ; i < nbPoints ; i++ ) {
    delete(tPos[i]); }
}

/* Fonction principale                          */

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

  atexit(clean);

  glutInit(&argc,argv);
  glutInitDisplayMode(GLUT_RGBA|GLUT_DEPTH|GLUT_DOUBLE);
  glutInitWindowSize(450,300); 
  glutInitWindowPosition(50,50); 
  glutCreateWindow("Une courbe B-Spline"); 
  init();
  glutKeyboardFunc(keyboard);
  glutSpecialFunc(special);
  glutReshapeFunc(reshape);
  glutKeyboardFunc(keyboard);
  glutDisplayFunc(display);
  glutMainLoop();
  return(0);
}
