// GL4opruge.C - Hip, 2009-11-16

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

// definicija boja
float bijelo[] = {1.0, 1.0, 1.0, 1.0};
float crno[] = {0.0, 0.0, 0.0, 1.0};
float sivo[] = {0.7, 0.7, 0.7, 1.0};
float crveno[] = {0.8, 0.0, 0.0, 1.0};
float zeleno[] = {0.0, 1.0, 0.0, 1.0};
float zuto[] = {1.0, 0.92, 0.0, 1.0};
float smedje[] = {238.0 / 255.0, 154.0 / 255.0, 73.0 / 255.0};

float sjaj = 100.0; // parametar kod zrcalne refleksije
float sirina = 30.0; // kutna shirina snopa kod svjetla1
int nn = 24; // broj poligona kod iscrtavanja kugle
int otvor = 0; // otvor na kugli

double g = -9.81; // ubrzanje slobodnog pada

class vektor3D {
  double kx, ky, kz; // komponente
public:
  vektor3D(double x, double y, double z) {
    kx = x; ky = y; kz = z;
  } // konstruktor

  double x() { return kx; }
  double y() { return ky; }
  double z() { return kz; }

  double duljina() {
    return sqrt(kx * kx + ky * ky + kz * kz);
  } // duljina

  void normiraj() {
    double d = duljina();
    kx /= d; ky /= d; kz /= d;
  } // normiraj

  void operator+= (vektor3D v) {
    kx += v.kx; ky += v.ky; kz += v.kz;
  } // +=

  double operator* (vektor3D v) {
    return kx * v.kx + ky * v.ky + kz * v.kz;
  } // skalarni produkt

  vektor3D operator+ (vektor3D v) {
    vektor3D r(kx + v.kx, ky + v.ky, kz + v.kz);
    return r;
  } // +

  vektor3D operator- (vektor3D v) {
    vektor3D r(kx - v.kx, ky - v.ky, kz - v.kz);
    return r;
  } // -

  vektor3D operator* (double a) {
    vektor3D r(kx * a, ky * a, kz * a);
    return r;
  } // *

  void vprodukt(vektor3D a, vektor3D b) {
    kx = a.ky * b.kz - a.kz * b.ky;
    ky = a.kz * b.kx - a.kx * b.kz;
    kz = a.kx * b.ky - a.ky * b.kx;
  } // vektorski produkt
}; // vektor3D

// klasa materijalna tocka
class mt {
  double m; // masa
  vektor3D r; // polozaj
  vektor3D v; // brzina

public:
  mt(double masa, double rx, double ry, double rz,
      double vx, double vy, double vz) : r(rx, ry, rz), v(vx, vy, vz) {
    m = masa;
  } // konstruktor

  double masa() { return m; }

  vektor3D pozicija() { return r; } // trenutna pozicija
  vektor3D brzina() { return v; } // trenutna brzina

  void pomakni(double dt, vektor3D F) {
    v += F * (dt / m);
    r += v * dt;
  } // pomakni

  double energija() {
    return 0.5 * m * (v.x() * v.x() + v.y() * v.y() + v.z() * v.z()); 
  }  // energija

  void ppoziciju(double rx, double ry, double rz) {
    r = vektor3D(rx, ry, rz);
  } // postavi poziciju

  void pbrzinu(double vx, double vy, double vz) {
    v = vektor3D(vx, vy, vz);
  } // postavi brzinu
}; // mt

class kugla: public mt {
  double rkugle;
public:
  kugla(double masa, double rx, double ry, double rz, double vx, double vy,
    double vz, double rr) : mt(masa, rx, ry, rz, vx, vy, vz) {
      rkugle = rr;
  } // konstruktor

  double polumjer() { return rkugle; }

  void GLkugla(double r, int nphi, int ntheta);

  void iscrtaj(int nphi, int ntheta) {
    glPushMatrix();
      glTranslated(pozicija().x(), pozicija().y(), pozicija().z());
      GLkugla(rkugle, nphi, ntheta);
    glPopMatrix();
  } // iscrtaj
}; // kugla

class opruga {
  double k; // konstanta opruge
  vektor3D r0; // gdje je opruga priccvrshchena
  double d0; // duljina opruge
  double rr; // polumjer opruge (kod crtanja)
  int nnavoja; // broj navoja (kod crtanja)
public:
  opruga(double konstanta, double rx, double ry, double rz, double d,
    double polumjer, int n) : r0(rx, ry, rz) {
      k = konstanta; d0 = d; rr = polumjer; nnavoja = n;
  } // konstruktor

  vektor3D sila(mt mt1) {
    vektor3D dvekt = mt1.pozicija() - r0;
    double d = dvekt.duljina();
    return dvekt * (-k * (1.0 - d0 / d));
  } // sila

  double energija(vektor3D r) {
    double d = (r - r0).duljina();
    return 0.5 * k * (d - d0) * (d - d0);
  } // energija

  void iscrtajDo(kugla kugla1) {
    vektor3D dvekt = kugla1.pozicija() - r0;
    vektor3D ivekt(1.0, 0.0, 0.0), os(0.0, 0.0, 1.0);
    double d = dvekt.duljina();
    double f = 2.0 * M_PI * nnavoja / (d - kugla1.polumjer());
    double x, phi;

    glMaterialfv(GL_FRONT, GL_DIFFUSE, crno);
    glMaterialfv(GL_FRONT, GL_EMISSION, sivo);

    glPushMatrix();
    glTranslated(r0.x(), r0.y(), r0.z());

    phi = acos((ivekt * dvekt) / d) * 180.0 / M_PI;
    os.vprodukt(ivekt, dvekt);
    glRotated(phi, os.x(), os.y(), os.z());

    glBegin(GL_LINE_STRIP);
    for(x = 0; x < d; x += 0.01) {
      glVertex3d(x, rr * cos(f * x), rr * sin(f * x));
    }
    glEnd();
    glPopMatrix();

    glMaterialfv(GL_FRONT, GL_EMISSION, crno);
  } // iscrtaj
}; // opruga

void kugla::GLkugla(double R, int nphi, int ntheta) {
  double t, r, z;
  double theta, step_theta = M_PI / ntheta;
  double r1, r2, z1, z2;
  int i;

  // blizha kapica
  theta = M_PI / 2.0 - step_theta;
  r = R * cos(theta);
  z = R * sin(theta);
  glBegin(GL_TRIANGLE_FAN);
    glNormal3d(0.0, 0.0, 1.0);
    glVertex3d(0.0, 0.0, R);
    t = 0.0;
    for(i = 0; i <= nphi; i++) {
      glNormal3d(r * cos(t) / R, r * sin(t) / R, z / R);      
      glVertex3d(r * cos(t), r * sin(t), z);
      t += 2.0 * M_PI / nphi;
    }
  glEnd();

  // plasht
  for(theta = M_PI / 2.0 - 2.0 * step_theta; theta > -M_PI / 2.0;
      theta -= step_theta) {
    r1 = R * cos(theta); r2 = R * cos(theta + step_theta);
    z1 = R * sin(theta); z2 = R * sin(theta + step_theta);  
 
    glBegin(GL_QUAD_STRIP);
      t = 0.0; 
      for(i = 0; i <= nphi - otvor; i++) {
        glNormal3d(r2 * cos(t) / R, r2 * sin(t) / R, z2 / R);
        glVertex3d(r2 * cos(t), r2 * sin(t), z2);
        glNormal3d(r1 * cos(t) / R, r1 * sin(t) / R, z1 / R);
        glVertex3d(r1 * cos(t), r1 * sin(t), z1);
        t += 2.0 * M_PI / nphi;
      }
    glEnd();
  }

  // dalja kapica
  theta = -M_PI / 2.0 + step_theta;
  r = R * cos(theta);
  z = R * sin(theta);
  glBegin(GL_TRIANGLE_FAN);
    glNormal3d(0.0, 0.0, -1.0);
    glVertex3d(0.0, 0.0, -R);
    t = 0.0;
    for(i = 0; i <= nphi; i++) {
      glNormal3d(r * cos(t) / R, r * sin(t) / R, z / R);
      glVertex3d(r * cos(t), r * sin(t), z);
      t -= 2.0 * M_PI / nphi;
    }
  glEnd();
} // GLkugla

void kvadar(double ax, double ay, double az, float *boja, char c) {
  if(c == 'e') {
    glMaterialfv(GL_FRONT, GL_DIFFUSE, crno);
    glMaterialfv(GL_FRONT, GL_EMISSION, boja);
  } else glMaterialfv(GL_FRONT, GL_DIFFUSE, boja);
  glBegin(GL_QUADS);
    glNormal3d(0.0, 0.0, -1.0); glVertex3d(0.0, 0.0, 0.0); glVertex3d(0.0, ay, 0.0);
    glVertex3d(ax, ay, 0.0); glVertex3d(ax, 0.0, 0.0);
    glNormal3d(0.0, 0.0, 1.0); glVertex3d(0.0, 0.0, az); glVertex3d(ax, 0.0, az);
    glVertex3d(ax, ay, az); glVertex3d(0.0, ay, az);
  glEnd();
  glBegin(GL_QUAD_STRIP);
    glNormal3d(0.0, -1.0, 0.0);
    glVertex3d(0.0, 0.0, az); glVertex3d(0.0, 0.0, 0.0);
    glVertex3d(ax, 0.0, az); glVertex3d(ax, 0.0, 0.0);
    glNormal3d(1.0, 0.0, 0.0); glVertex3d(ax, ay, az); glVertex3d(ax, ay, 0.0);
    glNormal3d(0.0, 1.0, 0.0); glVertex3d(0.0, ay, az); glVertex3d(0.0, ay, 0.0);
    glNormal3d(-1.0, 0.0, 0.0); glVertex3d(0.0, 0.0, az); glVertex3d(0.0, 0.0, 0.0);
  glEnd();
  if(c == 'e') glMaterialfv(GL_FRONT, GL_EMISSION, crno);
} // kvadar

void svjetlo0() {
  float pozicija[] = {0.0, 0.0, 0.0, 1.0};

  glLightfv(GL_LIGHT0, GL_DIFFUSE, bijelo);
  glLightfv(GL_LIGHT0, GL_SPECULAR, bijelo);
  glLightfv(GL_LIGHT0, GL_POSITION, pozicija);
} // svjetlo0

void svjetlo1() {
  float pozicija[] = {0.0, 0.0, 0.0, 1.0};
  float smjer_snopa[] = {1.0, -1.0, -1.0};

  glLightfv(GL_LIGHT1, GL_DIFFUSE, crveno);
  glLightfv(GL_LIGHT1, GL_SPECULAR, crveno);
  glLightfv(GL_LIGHT1, GL_POSITION, pozicija);

  glLightfv(GL_LIGHT1, GL_SPOT_DIRECTION, smjer_snopa);
  glLightf(GL_LIGHT1, GL_SPOT_CUTOFF, sirina);
} // svjetlo1

kugla kugla1(1.0, 5.0, 1.5, 0.0, 0.0, 0.0, 0.0, 2.0);

double okvirx = 12.0, okviry = 8.0, okvird = 2.0;

opruga opruga1(3.0,     0.0,  okviry, 0.0, okviry, 1.0, 10),
       opruga2(1.5, -okvirx,     0.0, 0.0, okvirx, 0.7, 10),
       opruga3(3.0,     0.0, -okviry, 0.0, okviry, 1.0, 10),
       opruga4(1.5,  okvirx,     0.0, 0.0, okvirx, 0.7, 10);

void slikaj(void) {
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glColor3d(1.0, 1.0, 0.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  // postavljanje kamere
  gluLookAt(0.0, 5.0, 35.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

  // postavljanje svjetla0
  glPushMatrix();
    glTranslated(7.0, 10.0, 13.0);
    svjetlo0();
    kvadar(0.5, 0.5, 0.5, bijelo, 'e');
  glPopMatrix();

  // postavljanje svjetla1
  glPushMatrix();
    glTranslated(-10.0, 10.0, 10.0);
    svjetlo1();
    kvadar(0.5, 0.5, 0.5, crveno, 'e');
  glPopMatrix();

  // crtanje "drvenog" okvira
  glPushMatrix();
    glTranslated(-okvirx - okvird, -okviry, -okvird / 2.0);    
    kvadar(okvird, 2.0 * okviry, okvird, smedje, 'd');
  glPopMatrix();
  glPushMatrix();
    glTranslated(okvirx, -okviry, -okvird / 2.0);    
    kvadar(okvird, 2.0 * okviry, okvird, smedje, 'd');
  glPopMatrix();
  glPushMatrix();
    glTranslated(-okvirx - okvird, -okviry - okvird, -okvird / 2.0);    
    kvadar(2.0 * (okvirx + okvird), okvird, okvird, smedje, 'd');
  glPopMatrix();
  glPushMatrix();
    glTranslated(-okvirx - okvird, okviry, -okvird / 2.0);    
    kvadar(2.0 * (okvirx + okvird), okvird, okvird, smedje, 'd');
  glPopMatrix();

  // crtanje opruga
  opruga1.iscrtajDo(kugla1);
  opruga2.iscrtajDo(kugla1);
  opruga3.iscrtajDo(kugla1);
  opruga4.iscrtajDo(kugla1);

  // crtanje kugle
  glMaterialfv(GL_FRONT, GL_DIFFUSE, zuto);
  glMaterialfv(GL_FRONT, GL_SPECULAR, bijelo);
  glMaterialf(GL_FRONT, GL_SHININESS, sjaj);
  glMaterialfv(GL_BACK, GL_DIFFUSE, crveno);
  kugla1.iscrtaj(nn, nn);

  glutSwapBuffers();
} // slikaj

void skaliraj(int w, int h) {
  double xmin = -10.0, xmax = 10.0;
  double xrange = xmax - xmin;
  double yrange = h * xrange / w;

  glViewport(0, 0, w, h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glFrustum(xmin, xmax, -yrange / 2.0, yrange / 2.0, 20.0, 50.0);
} // skaliraj

void tipka(unsigned char c, int x, int y) {
  if(c == 'q') exit(0);
  if(c == 's') glShadeModel(GL_SMOOTH);
  if(c == 'f') glShadeModel(GL_FLAT);
  if(c == 'D') glEnable(GL_DEPTH_TEST);
  if(c == 'd') glDisable(GL_DEPTH_TEST);
  if(c == 'C') glEnable(GL_CULL_FACE);
  if(c == 'c') glDisable(GL_CULL_FACE);
  if(c == 'B') glCullFace(GL_BACK);
  if(c == 'b') glCullFace(GL_FRONT);
  if(c == 'N') { if(++nn > 50) nn = 50; }
  if(c == 'n') { if(--nn < 3) nn = 3; if(otvor > nn - 1) otvor--; }
  if(c == 'O') { if(++otvor > nn - 1) otvor = nn - 1; }
  if(c == 'o') { if(--otvor < 0) otvor = 0; }
  if(c == 'j') { if(sjaj > 1.0) sjaj -= 1.0; }
  if(c == 'J') { if(sjaj < 128.0) sjaj += 1.0; }
  if(c == 'w') { if(sirina > 0.0) sirina -= 1.0; }
  if(c == 'W') { if(sirina < 90.0) sirina += 1.0; }
  if(c == '0') glEnable(GL_LIGHT0);
  if(c == '=') glDisable(GL_LIGHT0);
  if(c == '1') glEnable(GL_LIGHT1);
  if(c == '!') glDisable(GL_LIGHT1);
  if(c == 'g') g = 0.0;
  if(c == 'G') g = -9.81;
} // tipka

void simuliraj(void) {
  vektor3D gvekt(0.0, g, 0.0); // ubrzanje slobodnog pada
 
  vektor3D F(0.0, 0.0, 0.0); // vektor sile
  F += opruga1.sila(kugla1);
  F += opruga2.sila(kugla1);
  F += opruga3.sila(kugla1);
  F += opruga4.sila(kugla1);
  F += gvekt * kugla1.masa();

  kugla1.pomakni(0.02, F);

  glutPostRedisplay();
} // simuliraj

int main(int argc, char** argv) {
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB);

  glutInitWindowSize(640, 480);
  glutCreateWindow("kugla");

  glClearColor(0.0, 0.0, 0.0, 0.0);

  glEnable(GL_DEPTH_TEST);
  glEnable(GL_LIGHTING);
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
  glEnable(GL_LIGHT0);
  glEnable(GL_LIGHT1);

  glutDisplayFunc(slikaj);
  glutReshapeFunc(skaliraj);
  glutKeyboardFunc(tipka);
  glutIdleFunc(simuliraj);

  glutMainLoop();
  return 0;
} // main

