// Bspline1d - Hip - 2009-12-13
// - provlacci B-splajn stupnja D (degree parameter) kroz zadane toccke
// - bijela linija: otvoreni jednoliki B-splajn
// - cijan linija: jednoliki perioddiccki B-splajn
// - crtkana plava linija: Bezierova krivulja

#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

// maksimalni broj toccaka
#define NTMAX 30

// polumjer toccke 
#define TPOLUMJER 0.15

// "degree paramter" kod B-splajna (stupanj polinoma je D - 1)
#define D 4

GLUnurbsObj *krivulja1, *krivulja2;

GLUquadricObj *disk;

int ntocaka = 0; // broj toccaka
int akt = -1; // "aktivna" toccka

float ktocka[NTMAX][3]; // tabela s tocckama
float cvorovi1[D + NTMAX + 1]; // otvoreni jednoliki B-splajnovi
float cvorovi2[D + NTMAX + 1]; // jednoliki periodiccni B-splajnovi

float udaljenost(float t1[], float t2[]) {
  return sqrtf(powf(t1[0] - t2[0], 2.0) +
    powf(t1[1] - t2[1], 2.0) + powf(t1[2] - t2[2], 2.0));
} // udaljenost

void crtajTocku(float t[], double r) {
  glPushMatrix();
    glTranslatef(t[0], t[1], t[2]);
    gluDisk(disk, 0.0, r, 16, 2);
  glPopMatrix();
} // crtajTocku

void iscrtaj(void) {
  int i, j, k;

  glClear(GL_COLOR_BUFFER_BIT);

  if(ntocaka > 1) {
    // crtkane linije koje spajaju toccke
    glLineStipple(1, 0x00FF);
    glEnable(GL_LINE_STIPPLE);
    glColor3f(1.0, 1.0, 0.0);
    glBegin(GL_LINE_STRIP);
      for(i = 0; i < ntocaka; i++) glVertex3fv(&ktocka[i][0]);
    glEnd();
    glDisable(GL_LINE_STIPPLE);
  }

  if(ntocaka >= D) {
    // pripremi ccvorove za otvoreni jednoliki B-splajn
    for(i = 0; i < D; i++) cvorovi1[i] = 0.0;
    for(i = D, k = 1; i < ntocaka; i++) cvorovi1[i] = (float)k++;
    for(i = ntocaka; i < ntocaka + D; i++) cvorovi1[i] = (float)k;
    for(i = 0; i < D + ntocaka; i++) printf("%d %f\n", i, cvorovi1[i]);
    glColor3f(1.0, 1.0, 1.0); // bijela
    gluNurbsProperty(krivulja1, GLU_SAMPLING_TOLERANCE, 1.0);   
    gluBeginCurve(krivulja1);
      gluNurbsCurve(krivulja1, ntocaka + D, cvorovi1,
        3, &ktocka[0][0], D, GL_MAP1_VERTEX_3);
    gluEndCurve(krivulja1);
        
    // pripremi ccvorove za jednoliki periodiccki B-splajn
    for(i = 0, k = 0; i < D + ntocaka; i++) cvorovi2[i] = (float)k++;
    glColor3f(0.0, 1.0, 1.0); // cijan
    gluNurbsProperty(krivulja2, GLU_SAMPLING_TOLERANCE, 1.0); 
    gluBeginCurve(krivulja2);
      gluNurbsCurve(krivulja2, ntocaka + D, cvorovi2,
      3, &ktocka[0][0], D, GL_MAP1_VERTEX_3);
    gluEndCurve(krivulja2);

    // Bezierova krivulja 
    glMap1f(GL_MAP1_VERTEX_3, 0.0, 1.0, 3, ntocaka, &ktocka[0][0]);
    glEnable(GL_MAP1_VERTEX_3);
    glLineStipple(1, 0x00FF);
    glEnable(GL_LINE_STIPPLE); 
    glColor3f(0.0, 0.0, 1.0); // crtkana plava
    glMapGrid1f(ntocaka * 10, 0.0, 1.0);
    glEvalMesh1(GL_LINE, 0, ntocaka * 10);
    glDisable(GL_LINE_STIPPLE);
    glDisable(GL_MAP1_VERTEX_3);
  }

  // crtanje toccaka
  for(i = 0, j = 0; i < ntocaka; i++) { 
    if(i == akt) glColor3f(0.0, 0.0, 1.0);
    else glColor3f(1.0, 0.0, 0.0); 
    crtajTocku(&ktocka[i][0], TPOLUMJER);
  }

   glutSwapBuffers();
} // iscrtaj

void skaliraj(int w, int h) {
  float a = 5.0, b;

  glViewport(0, 0, w, h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if(w > h) {
    b = a * (float)w / (float)h;
    glOrtho(-b, b, -a, a, -a, a);
  } else {
    b = a * (float)h / (float)w;
    glOrtho(-a, a, -b, b, -a, a);
  }

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
} // skaliraj

void mish(int tipka, int stanje, int x, int yy) {
  GLint viewport[4];
  GLdouble mvmatrix[16], projmatrix[16];
  int y, i, j;
  double wx, wy, wz;
  float w[3], d;

  if(tipka == GLUT_LEFT_BUTTON) {
    if(stanje == GLUT_DOWN) {
      glGetIntegerv(GL_VIEWPORT, viewport);
      glGetDoublev(GL_MODELVIEW_MATRIX, mvmatrix);
      glGetDoublev(GL_PROJECTION_MATRIX, projmatrix);

      // viewport[2] - shirina prozora; viewport[3] - visina prozora
      y = viewport[3] - yy - 1; // korekcija ishodishta
      gluUnProject((GLdouble) x, (GLdouble) y, 0.0, 
        mvmatrix, projmatrix, viewport, &wx, &wy, &wz);
      w[0] = wx; w[1] = wy; w[2] = 0.0;

      akt = -1;
      for(i = 0; i < ntocaka; i++) {
        d = udaljenost(ktocka[i], w); // printf("%d udaljenost: %lf\n", i, d);
        if(d <= TPOLUMJER) akt = i;
      }
      if((akt == -1) && (ntocaka < NTMAX)) {
        ktocka[ntocaka][0] = wx;
        ktocka[ntocaka][1] = wy;
        ktocka[ntocaka][2] = 0.0;
        ntocaka++;
      }
      glutPostRedisplay();
    }

    if(stanje == GLUT_UP) {
      glGetIntegerv(GL_VIEWPORT, viewport);
      glGetDoublev(GL_MODELVIEW_MATRIX, mvmatrix);
      glGetDoublev(GL_PROJECTION_MATRIX, projmatrix);

      // viewport[2] - shirina prozora; viewport[3] - visina prozora
      y = viewport[3] - yy - 1; // korekcija ishodishta
      gluUnProject ((GLdouble) x, (GLdouble) y, 0.0, 
        mvmatrix, projmatrix, viewport, &wx, &wy, &wz);

      //printf ("(%4d, %4d) -> (%f, %f)\n", x, y, wx, wy);
      if(akt >= 0) {
        ktocka[akt][0] = wx; ktocka[akt][1] = wy; ktocka[akt][2] = 0.0;
        akt = -1;
      }
      glutPostRedisplay();
    }
  }

  if((tipka == GLUT_MIDDLE_BUTTON) && (stanje == GLUT_UP)) {
    // ispis toccaka
    for(i = 0, j = 0; i < ntocaka; i++)
      printf(" %6.2f %6.2f\n", ktocka[i][0], ktocka[i][1]);

    exit(0);
  }
} // mish

int main(int argc, char** argv) {
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
  glutInitWindowSize(500, 500);
  glutInitWindowPosition(0, 0);
  glutCreateWindow(argv[0]);

  glClearColor(0.0, 0.0, 0.0, 0.0);
  disk = gluNewQuadric();

  krivulja1 = gluNewNurbsRenderer();
  krivulja2 = gluNewNurbsRenderer();

  glutDisplayFunc(iscrtaj);
  glutReshapeFunc(skaliraj);
  glutMouseFunc(mish);

  glutMainLoop();
  return 0;
} // main

