/**********************************************************************
* EINFUEHRUNG IN DIE COMPUTERGRAFIK                      BEISPIEL 5.1 *
*                                                                     *
* Kalchmair Franz-Helmut                                     Gruppe 4 *
*                                                                     *
* Date: May/27/2001                                                   *
*                                                                     *
* Fractal mountain mit verschiedenen Farben und                       *
* Maussteuerung                                                       *
**********************************************************************/

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

#include <time.h>

#ifdef WIN32
#define drand48() (((float) rand())/((float) RAND_MAX))
#define srand48(x) (srand((x)))
#endif

#ifndef M_PI
#define M_PI 3.14159265
#endif

#define MAXLEVEL 8
#define MONS 1
#define MONSMAT 2
#define NUMRANDS 191
#define TORAD(x) ((M_PI/180.0)*(x))

/* initial window extent */
#define WIN_SIZE_X 512
#define WIN_SIZE_Y 512

/* current window extent */
int win_width;
int win_height;

/* mouse pointer location */
int mouse_x = WIN_SIZE_X / 2;
int mouse_y = WIN_SIZE_Y / 2;

// Rekursionstiefe
int Level   = 6;

float RandTable[NUMRANDS];
GLfloat Verts[3][3],
        Slopes[3];
int     Pegged[3];

GLfloat DispFactor[MAXLEVEL];
GLfloat DispBias[MAXLEVEL];

float dist = 3.0f;
float elevation = 0.0;
float azimuth = 30.0;
float rotateUp = 0.0;
int moving = 0;
int begin = 0;

void printvert(float v[3])
{
  fprintf(stderr, "(%f, %f, %f)\n", v[0], v[1], v[2]);
}

void normalize(GLfloat v[3])
{
  GLfloat d = sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);

  if (d == 0)
    fprintf(stderr, "Zero length vector in normalize\n");
  else
    v[0] /= d; v[1] /= d; v[2] /= d;
}

void ncrossprod(float v1[3], float v2[3], float cp[3])
{
  cp[0] = v1[1]*v2[2] - v1[2]*v2[1];
  cp[1] = v1[2]*v2[0] - v1[0]*v2[2];
  cp[2] = v1[0]*v2[1] - v1[1]*v2[0];
  normalize(cp);
}

void triagnormal(float v1[3], float v2[3], float v3[3], float norm[3])
{
  float vec1[3], vec2[3];

  vec1[0] = v3[0] - v1[0];  vec2[0] = v2[0] - v1[0];
  vec1[1] = v3[1] - v1[1];  vec2[1] = v2[1] - v1[1];
  vec1[2] = v3[2] - v1[2];  vec2[2] = v2[2] - v1[2];

  ncrossprod(vec2, vec1, norm);
}

float xzlength(float v1[3], float v2[3])
{
  return sqrt((v1[0] - v2[0])*(v1[0] - v2[0]) +
              (v1[2] - v2[2])*(v1[2] - v2[2]));
}

float xzslope(float v1[3], float v2[3])
{
  return ((v1[0] != v2[0]) ? ((v1[2] - v2[2]) / (v1[0] - v2[0]))
	                   : FLT_MAX);
}


void InitRandTable(unsigned int seed)
{
  int i;

  srand48((long) seed);
  for (i = 0; i < NUMRANDS; i++)
    RandTable[i] = drand48() - 0.5;
}


void Midpoint(GLfloat mid[3], GLfloat v1[3], GLfloat v2[3],
	      int edge, int level)
{
  unsigned hash;

  mid[0] = (v1[0] + v2[0]) / 2;
  mid[1] = (v1[1] + v2[1]) / 2;
  mid[2] = (v1[2] + v2[2]) / 2;
  if (!Pegged[edge] || (fabs(xzslope(Verts[edge], mid)
                        - Slopes[edge]) > 0.00001)) {
    srand48((int)((v1[0]+v2[0])*23344));
    hash = drand48() * 7334334;
    srand48((int)((v2[2]+v1[2])*43433));
    hash = (unsigned)(drand48() * 634344 + hash) % NUMRANDS;
    mid[1] += 1.5*((RandTable[hash] + DispBias[level]) * DispFactor[level])-0.02;
	//if (mid[1]>0.75) printf("%f\n", mid[1]);
	if (mid[1]<0) mid[1] = 0.0;
  }
}


void FMR(GLfloat v1[3], GLfloat v2[3], GLfloat v3[3], int level)
{
  //GLfloat mtn_ambuse0[] =   { 0.1, 0.3, 0.3, 1.0 };
  GLfloat mtn_ambuse0[] =   { 0.0, 0.0, 0.3, 1.0 };  // blau
  GLfloat mtn_ambuse1[] =   { 0.4, 0.4, 0.03, 1.0 }; // gelb
  GLfloat mtn_ambuse2[] =   { 0.2, 0.8, 0.2, 1.0 };  // hellgrün
  GLfloat mtn_ambuse3[] =   { 0.0, 0.3, 0.0, 1.0 };  // dunkelgrün
  GLfloat mtn_ambuse4[] =   { 0.3, 0.3, 0.1, 1.0 };  // braun
  GLfloat mtn_ambuse5[] =   { 0.2, 0.2, 0.2, 1.0 };  // grau
  GLfloat mtn_ambuse6[] =   { 1.0, 1.0, 1.0, 1.0 };  // weiß
  float h;

  if (level == Level) {
    GLfloat norm[3];

    triagnormal(v1, v2, v3, norm);
    glNormal3fv(norm);
	h = (v1[1]+v2[1]+v3[1])/3.0;
    //printf("%f\n", h);
	// Höhenentsprechungen: h*5000
	// h = 0.02 ........ 100m
	// h = 0.20 ....... 1000m
	// h = 0.40 ....... 2000m
	// h = 0.50 ....... 2500m
	// h = 0.56 ....... 2800m
	// h = 1.00 ....... 5000m
	if (h==0.0) glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mtn_ambuse0);
	else if (h<=0.02) glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mtn_ambuse1);
	else if (h<=0.2) glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mtn_ambuse2);
	else if (h<=0.4) glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mtn_ambuse3);
	else if (h<=0.5) glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mtn_ambuse4);
	else if (h<=0.56) glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mtn_ambuse5);
	else glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mtn_ambuse6);
	glVertex3fv(v1);
    glVertex3fv(v2);
    glVertex3fv(v3);

  } else {
    GLfloat m1[3], m2[3], m3[3];

    Midpoint(m1, v1, v2, 0, level);
    Midpoint(m2, v2, v3, 1, level);
    Midpoint(m3, v3, v1, 2, level);

    FMR(v1, m1, m3, level + 1);
    FMR(m1, v2, m2, level + 1);
    FMR(m3, m2, v3, level + 1);
    FMR(m1, m2, m3, level + 1);
  }
}


void FractalMountain(GLfloat v1[3], GLfloat v2[3], GLfloat v3[3],
                     int pegged[3])
{
  GLfloat lengths[MAXLEVEL];
  GLfloat fraction[8] = { 0.3, 0.3, 0.4, 0.2, 0.3, 0.2, 0.4, 0.4  };
  GLfloat bias[8]     = { 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1  };
  int i;
  float avglen = (xzlength(v1, v2) +
                  xzlength(v2, v3) +
		  xzlength(v3, v1) / 3);

  for (i = 0; i < 3; i++) {
    Verts[0][i] = v1[i];
    Verts[1][i] = v2[i];
    Verts[2][i] = v3[i];
    Pegged[i] = pegged[i];
  }

  Slopes[0] = xzslope(Verts[0], Verts[1]);
  Slopes[1] = xzslope(Verts[1], Verts[2]);
  Slopes[2] = xzslope(Verts[2], Verts[0]);

  lengths[0] = avglen;
  for (i = 1; i < Level; i++) {
    lengths[i] = lengths[i-1]/2;
  }

  for (i = 0; i < Level; i++) {
    DispFactor[i] = (lengths[i] * ((i <= 7) ? fraction[i] : fraction[7]));
    DispBias[i]   = ((i <= 7) ? bias[i] : bias[7]);
  }

  glBegin(GL_TRIANGLES);
    FMR(v1, v2, v3, 0);
  glEnd();
}

void CreateMountain(void)
{
  GLfloat v1[3] = { 0, 0, -1 }, v2[3] = { -1, 0, 1 }, v3[3] = { 1, 0, 1 };
  int pegged[3] = { 1, 1, 1 };

  glNewList(MONS, GL_COMPILE);
  glPushAttrib(GL_LIGHTING_BIT);
    //glCallList(MONSMAT);
    FractalMountain(v1, v2, v3, pegged);
  glPopAttrib();
  glEndList();
}


void KeyPressed(int key, int x, int y)
{
  switch(key)
  {
    case GLUT_KEY_LEFT:
      if (azimuth==0.0f) azimuth=355.0f; else azimuth -= 5.0f;
      break;
    case GLUT_KEY_RIGHT:
      if (azimuth==355.0f) azimuth=0.0f; else azimuth += 5.0f;
      break;
    case GLUT_KEY_DOWN:
      if (elevation>-85.0f) elevation -= 5.0f;
      break;
    case GLUT_KEY_UP:
      if (elevation<85.0f) elevation += 5.0f;
      break;
    case GLUT_KEY_PAGE_DOWN:
      if (dist<12.0f) dist += 0.2f;
      break;
    case GLUT_KEY_PAGE_UP:
      if (dist>0.6f) dist -= 0.2f;
      break;
    default:;
  }
  glutPostRedisplay();
}


void mouseButton (int button, int state, int x, int y) {
  if (state == GLUT_DOWN) {
    if (button == GLUT_LEFT_BUTTON) {
      //printf ("you pressed the left mousebutton at (%i/%i)\n", x, y);
	  moving = 1;
	  begin = x;
	  printf("%f\n", rotateUp);
	}
	else
	  if  (button == GLUT_RIGHT_BUTTON) {
        printf ("you pressed the right mousebutton at (%i/%i)\n", x, y);
		if (mouse_y<WIN_SIZE_Y/2)
		{
          if (dist<12.0f) dist += 0.2f;
		}
		else
		{
          if (dist>0.6f) dist -= 0.2f;
		}
	  }
  } else
    printf ("you released the mousebutton\n");

  glutPostRedisplay ();
}

void mouseMotion (int x, int y) {
  printf ("current position: (%i/%i)\n", x, y);
  mouse_x = x;
  mouse_y = y;
  if (moving) azimuth += (x-begin)/20.0;
  glutPostRedisplay ();
}


void myGLInit(void)
{
  GLfloat light_ambient[] = { 1.0, 1.0, 1.0, 1.0 };
  GLfloat lmodel_ambient[] = { 1.0, 1.0, 1.0, 1.0 };
  GLfloat mtn_ambuse[] =   { 0.1, 0.3, 0.3, 1.0 };

  glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
  glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);

  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);

  glDepthFunc(GL_LEQUAL);
  glEnable(GL_DEPTH_TEST);

  glEnable(GL_NORMALIZE);
  glShadeModel(GL_SMOOTH);

  glNewList(MONSMAT, GL_COMPILE);
    glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mtn_ambuse);
  glEndList();

  glFlush();
}


void reshape(GLsizei w, GLsizei h)
{
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(60.0, (GLdouble)w/h, 0.01, 100);
  glPushMatrix();
  glMatrixMode(GL_MODELVIEW);
  glFlush();
}

void display(void)
{

  //double x,y,z, pi=3.14;
  //int radius = 1;

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glFlush();

  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  glPushMatrix();

  glTranslatef(0, 0, -dist);
  glRotatef(elevation, 1, 0, 0);
  glRotatef(azimuth, 0, 1, 0);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  // Punkt (x,y,z) auf der Kugel berechnen (für Kameraposition)
  //x= radius * cos( (elevation*M_PI)/180 );

  //x= x * cos ( ((azimuth+90)*M_PI)/180 );
  //y= radius * sin( (elevation*M_PI)/180 );
  //z = x * sin ( ((azimuth+90)*M_PI)/180 );

  // Kameraposition und Blickrichtung setzen
  gluLookAt(0, 0, 1,   /* Kameraposition */
    0.0, 0.0, 0.0,     /* Look */
    0.0, 1.0, 0.0);    /* Up / Richtung */

  CreateMountain();

  glCallList(MONS);

  glutSwapBuffers();
  glFlush();
}


int main(int argc, char** argv)
{
  glutInit(&argc, argv);
  glutInitWindowSize(WIN_SIZE_X, WIN_SIZE_Y);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_MULTISAMPLE);
  glutCreateWindow("Fractal Mountains 2");

  glutReshapeFunc(reshape);
  glutDisplayFunc(display);

  InitRandTable(time(NULL));
  myGLInit();
  glutSpecialFunc(KeyPressed);
  glutMouseFunc (mouseButton);
  glutMotionFunc (mouseMotion);

  glutMainLoop();
  return 0;
}
