/* Courbes B-Spline et de Bezier                */
/*                                              */
/* Auteur: Nicolas JANEY                        */
/* nicolas.janey@univ-fcomte.fr                 */
/* Mars 2020                                    */

#if defined(WIN32) || defined(WIN64)
  #define _CRTDBG_MAP_ALLOC
  #if defined(_DEBUG)
    #define _AFXDLL
    #include <afx.h>
  #endif
#endif
#include <stdlib.h>
#if defined(WIN32) || defined(WIN64)
  #include <crtdbg.h>
#endif

#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 nbp = 100;
static int aff = 0;

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

static int nbPoints = 37;
static Pos3D *tPos[] = { new Pos3D( 6.5F, 4.9F,-3.00F), new Pos3D( 7.0F, 5.0F,-3.00F), new Pos3D( 7.5F, 4.9F,-2.00F),
                         new Pos3D( 8.0F, 4.6F, 0.00F), new Pos3D( 7.5F, 4.4F, 2.00F), new Pos3D( 7.0F, 4.2F, 3.50F),
                         new Pos3D( 6.0F, 3.8F, 4.00F), new Pos3D( 5.0F, 2.8F, 4.00F), new Pos3D( 4.0F, 1.8F, 4.00F),
                         new Pos3D( 3.0F, 0.8F, 4.00F), new Pos3D( 0.0F, 0.0F, 4.00F), new Pos3D(-1.4F, 0.7F, 3.75F),
                         new Pos3D(-2.0F, 2.0F, 3.50F), new Pos3D(-1.4F, 3.3F, 3.25F), new Pos3D( 0.0F, 4.0F, 3.00F),
                         new Pos3D( 1.4F, 3.3F, 2.75F), new Pos3D( 2.0F, 2.0F, 2.50F), new Pos3D( 1.0F, 0.7F, 2.25F),
                         new Pos3D(-1.0F, 0.0F, 2.00F), new Pos3D(-2.4F, 0.7F, 1.75F), new Pos3D(-3.0F, 2.0F, 1.50F),
                         new Pos3D(-2.4F, 3.3F, 1.25F), new Pos3D(-1.0F, 4.0F, 1.00F), new Pos3D( 0.4F, 3.3F, 0.75F),
                         new Pos3D( 1.0F, 2.0F, 0.50F), new Pos3D( 0.0F, 0.7F, 0.25F), new Pos3D(-2.0F, 0.2F, 0.00F),
                         new Pos3D(-7.0F, 0.0F, 1.50F), new Pos3D(-8.0F, 0.0F,-1.30F), new Pos3D(-7.0F, 0.3F,-1.90F),
                         new Pos3D(-6.0F, 1.8F,-2.40F), new Pos3D(-4.5F, 4.9F,-2.40F), new Pos3D(-3.0F, 3.2F,-2.40F),
                         new Pos3D( 0.5F, 2.4F,-2.80F), new Pos3D( 3.5F, 4.4F,-2.80F), new Pos3D( 4.5F, 4.7F,-2.90F),
                         new Pos3D( 5.5F, 4.8F,-3.00F) };

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

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

/* Modelise une ligne polygonale definie         */
/* par un ensemble de points                     */
/* nbPoints : le nombre de points                */
/* tPos : le tableau des points                  */
/* typePrimitive : le type de primitive OpenGL   */
/*                 a utiliser                    */

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

/* Modelise une courbe de Bezier definie         */
/* par un ensemble de points de controle         */
/* nbPoints : le nombre de points de contrôle    */
/* tPos : le tableau des points de controle      */
/* nb : le nombre de points a calculer           */
/* tRes : le tableau de points resultat          */
/* typePrimitive : le type de primitive OpenGL   */
/*                 a utiliser                    */

static void bezier(int nbPoints,Pos3D **tPos,int n,GLenum typePrimitive) {
  float t,mt;
  float x,y,z,fac;
  long long *cn =(long long *) calloc(nbPoints,sizeof(long long));
  cn[0] = 1;
  cn[1] = nbPoints-1;
  for ( int i = 2 ; i < nbPoints ; i++ )
    cn[i] = (cn[i-1]*(nbPoints-i))/i;
  glBegin(typePrimitive);
  for ( int i = 0 ; i < n ; i++ ) {
    t =(float) i/(n-1);
    mt = 1.0F-t;
    x = y = z = 0.0F;
    for ( int j = 0 ; j < nbPoints ; j++ ) {
      fac = cn[j]*pow(t,j)*pow(mt,nbPoints-1-j);
      x += fac * tPos[j]->x;
      y += fac * tPos[j]->y;
      z += fac * tPos[j]->z; }
    glVertex3f(x,y,z); }
  glEnd();
  free(cn);
}

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

static void positionSurBSpline(Pos3D **tPos,float t,float mb[4][4],Pos3D *point) {
  float vt[4] = { t*t*t,t*t,t,1.0F };
  float vtmb[4] = { 0.0F,0.0F,0.0F,0.0F };
  for ( int j = 0 ; j < 4 ; j++ ) {
    for ( int k = 0 ; k < 4 ; k++ )
      vtmb[j] += vt[k] * mb[k][j] ; }
  point->x = point->y = point->z = 0.0;
  for ( int j = 0 ; j < 4 ; j++ ) {
    point->x += vtmb[j] * tPos[j]->x ;
    point->y += vtmb[j] * tPos[j]->y ;
    point->z += vtmb[j] * tPos[j]->z ; }
}

/* Modelise une courbe B-Spline par morceaux     */
/* definie par un ensemble de points de controle */
/* nbPoints : le nombre de points de contrôle    */
/* tPos : le tableau des points de controle      */
/* mb : la matrice de base                       */
/* n : le nombre de points a calculer            */
/* typePrimitive : le type de primitive OpenGL   */
/*                 a utiliser                    */

static void BSpline(int nbPoints,Pos3D **tPos,float mb[4][4],int n,GLenum typePrimitive) {
  glBegin(typePrimitive);
  for ( int i = 0 ; i < n ; i++ ) {
    float t = i/(n-1.0)*(nbPoints-3);
    int nb =(int) t;
    if ( nb == nbPoints-3 )
      nb = nbPoints-4;
    Pos3D point;
    positionSurBSpline(&tPos[nb],t-nb,mb,&point);
    glVertex3f(point.x,point.y,point.z); }
  glEnd();
}

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

/* 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 :
      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);
}

/* Scene dessinee                                */

static void scene(void) {
  switch (aff) {
    case 0 :
      glColor3f(0.75F,0.75F,0.75F);
      dessinerOpenGL(nbPoints,tPos,GL_LINE_STRIP);
      break;
    case 1 : {
      glColor3f(0.75F,0.75F,0.75F);
      glPointSize(3.0);
      dessinerOpenGL(nbPoints,tPos,GL_LINE_STRIP);
      glColor3f(1.0F,1.0F,1.0F);
      bezier(nbPoints,tPos,nbp,GL_POINTS); }
      break;
    case 2 : {
      glColor3f(0.75F,0.75F,0.75F);
      dessinerOpenGL(nbPoints,tPos,GL_LINE_STRIP);
      glPointSize(3.0);
      glColor3f(1.0F,1.0F,1.0F);
      BSpline(nbPoints,tPos,NRUBS,nbp,GL_POINTS); }
      break;
    case 3 : {
      glColor3f(0.75F,0.75F,0.75F);
      glPointSize(3.0);
      dessinerOpenGL(nbPoints,tPos,GL_LINE_STRIP);
      glColor3f(1.0F,1.0F,1.0F);
      BSpline(nbPoints,tPos,CATMULL_ROM,nbp,GL_POINTS); }
      break;
    case 4 : {
      glLineWidth(2.0F);
      glColor3f(0.75F,0.75F,0.75F);
      dessinerOpenGL(nbPoints,tPos,GL_LINE_STRIP);
      glLineWidth(1.0F);
      glColor3f(0.0F,1.0F,1.0F);
      BSpline(nbPoints,tPos,NRUBS,nbp,GL_LINE_STRIP);
      glColor3f(1.0F,0.75F,0.75F);
      BSpline(nbPoints,tPos,CATMULL_ROM,nbp,GL_LINE_STRIP);
      glColor3f(0.0F,1.0F,0.0F);
      bezier(nbPoints,tPos,nbp,GL_LINE_STRIP); }
      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);
  glTranslatef(0.0F,-2.5F,0.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 nbPoints 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(-3.5*ratio,3.5*ratio,-3.5,3.5,-10.0,10.0);
    else
    glOrtho(-3.5,3.5,-3.5/ratio,3.5/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 45 :
      nbp--;
      if ( nbp == 1 )
        nbp = 2;
      glutPostRedisplay(); 
      break;
    case 43 :
      nbp++;
      glutPostRedisplay(); 
      break;
    case 0x20 :
      aff = (aff+1)%5;
      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) {

#if defined(WIN32) || defined(WIN64)
  #if defined(_DEBUG)
    _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
    //_crtBreakAlloc = 119;
  #endif
#endif

  atexit(clean);

  glutInit(&argc,argv);
  glutInitDisplayMode(GLUT_RGBA|GLUT_DEPTH|GLUT_DOUBLE);
  glutInitWindowSize(600,250); 
  glutInitWindowPosition(50,50); 
  glutCreateWindow("Courbes B-Spline et de Bézier"); 
  init();
  glutKeyboardFunc(keyboard);
  glutSpecialFunc(special);
  glutReshapeFunc(reshape);
  glutKeyboardFunc(keyboard);
  glutDisplayFunc(display);
  glutMainLoop();
  return(0);
}
