// shadow_transf_demo.cc
// 
// Copyright (C) 2004-2005, 2009 Frank C. Langbein, http://www.langbein.org/
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// Compile with
//
//   c++ -o transf_demo transf_demo.cc -L/usr/X11R6/lib -lm -lX11 \
//       -lXext -lXt -lXmu -lXi -lGL -lGLU -lglut

#include <stdlib.h>
#include <unistd.h>
#include <GL/glut.h>
#include <math.h>

// Degrees to radians conversion factor
const double c = 3.14159 / 180.0;

// Camera following mode
static int cmode = 0;

// Delta angle for rendering
static double da = 2.0;

// Light dot material
static GLfloat diffusel[] = {1.0, 1.0, 1.0, 1.0};
static GLfloat ambientl[] = {1.0, 1.0, 1.0, 1.0};
static GLfloat specularl[] = {0.0, 0.0, 0.0, 1.0};
static GLfloat shinel = 0.5;

// Light source 0
static GLfloat diffuse0[] = {1.0, 1.0, 1.0, 1.0};
static GLfloat ambient0[] = {0.2, 0.2, 0.2, 1.0};
static GLfloat specular0[] = {1.0, 1.0, 1.0, 1.0};
static GLfloat light0_pos[] = {10.0, -5.0, 10.0, 1.0};
static GLfloat attenuation0 = 0.7;

// Sphere material
static GLfloat sphere_diffuse[] = {0.7, 0.0, 0.0, 1.0};
static GLfloat sphere_ambient[] = {0.3, 0.0, 0.0, 1.0};
static GLfloat sphere_specular[] = {1.0, 1.0, 1.0, 1.0};
static GLfloat sphere_shine = 127.0;

// Shadow material
static GLfloat shadow_diffuse[] = {0.0, 0.0, 0.0, 1.0};
static GLfloat shadow_ambient[] = {0.0, 0.0, 0.0, 1.0};
static GLfloat shadow_specular[] = {0.0, 0.0, 0.0, 1.0};
static GLfloat shadow_shine = 1.0;

// Shadow projection matrix
GLfloat shadowm[16];

// Draw a sphere (also for shadow)
void raw_sphere ()
{
  // Quadrilateral strips
  for (double phi = 90.0 - da; phi > -90.0 + da; phi -= da) {
    glBegin (GL_QUAD_STRIP);

    for (double thet = 180.0; thet >= -180.0; thet -= da) {


      glNormal3d (sin (c * thet) * cos (c * phi),
		  cos (c * thet) * cos (c * phi),
		  sin (c * phi)); 

      glVertex3d (sin (c * thet) * cos (c * phi),
		  cos (c * thet) * cos (c * phi),
		  sin (c * phi)); 

      glNormal3d (sin (c * thet) * cos (c * (phi - da)),
		  cos (c * thet) * cos (c * (phi - da)),
		  sin (c * (phi - da)));  

      glVertex3d (sin (c * thet) * cos (c * (phi - da)),
		  cos (c * thet) * cos (c * (phi - da)),
		  sin (c * (phi - da)));

    }

    glEnd();
  }

  // North pole
  glBegin (GL_TRIANGLE_FAN);

  glNormal3d (0.0, 0.0, 1.0);
  glVertex3d (0.0, 0.0, 1.0);
 
  for (double thet = 180.0; thet >= -180.0; thet -= da) {
    glNormal3d (sin (c * thet) * cos (c * (90.0 - da)),
		cos (c * thet) * cos (c * (90.0 - da)),
		sin (c * (90.0 - da)));
    glVertex3d (sin (c * thet) * cos (c * (90.0 - da)),
		cos (c * thet) * cos (c * (90.0 - da)),
		sin (c * (90.0 - da)));
  }

  glEnd();

  // South pole
  glBegin (GL_TRIANGLE_FAN);
  
  glNormal3d (0.0, 0.0, -1.0);
  glVertex3d (0.0, 0.0, -1.0);

  for (double thet = 180.0; thet >= -180.0; thet -= da) {
    glNormal3d (sin (c * thet) * cos (c * (90.0 - da)),
		cos (c * thet) * cos (c * (90.0 - da)),
		sin (c * (da - 90.0)));
    glVertex3d (sin (c * thet) * cos (c * (90.0 - da)),
		cos (c * thet) * cos (c * (90.0 - da)),
		sin (c * (da - 90.0)));
  }

  glEnd();

  return;
}

// Draw a sphere with shadow
void sphere (GLfloat rz, GLfloat dx, GLfloat dy, GLfloat dz)
{
  glEnable (GL_NORMALIZE);

  // Material
  glMaterialfv (GL_FRONT, GL_AMBIENT, sphere_ambient);
  glMaterialfv (GL_FRONT, GL_DIFFUSE, sphere_diffuse);
  glMaterialfv (GL_FRONT, GL_SPECULAR, sphere_specular);
  glMaterialfv (GL_FRONT, GL_SHININESS, &sphere_shine);

  // Draw sphere
  raw_sphere ();

  // Shadow material
  glMaterialfv (GL_FRONT, GL_AMBIENT, shadow_ambient);
  glMaterialfv (GL_FRONT, GL_DIFFUSE, shadow_diffuse);
  glMaterialfv (GL_FRONT, GL_SPECULAR, shadow_specular);
  glMaterialfv (GL_FRONT, GL_SHININESS, &shadow_shine);

  // Draw shadow
  glMatrixMode (GL_MODELVIEW);
  glPushMatrix ();

  // Shadow projection

  // Translate Back (accounting for sphere displacement by movement)
  glRotatef (-rz, 0.0, 0.0, 1.0);
  glTranslatef (light0_pos[0] - dx, light0_pos[1] - dy, light0_pos[2] - dz + .01);
  // Project shadow
  glMultMatrixf (shadowm);
  // Move light to origin (accounting for sphere displacement by movement)
  glTranslatef (-light0_pos[0] + dx, -light0_pos[1] + dy,
		-light0_pos[2] + dz);
  glRotatef (rz, 0.0, 0.0, 1.0);

  raw_sphere ();
  
  glPopMatrix ();


  glDisable (GL_NORMALIZE);
  
  return;
}

// Cone material
static GLfloat cone_diffuse[] = {0.0, 0.6, 0.0, 1.0};
static GLfloat cone_ambient[] = {0.0, 0.1, 0.0, 1.0};
static GLfloat cone_specular[] = {0.1, 0.1, 0.1, 1.0};
static GLfloat cone_shine = 1.0;

// Draw a cone (also for shadow)
void raw_cone ()
{
  // Cone
  glBegin (GL_TRIANGLE_FAN);

  glNormal3d (0.0, 0.0, 0.0);
  glVertex3d (0.0, 0.0, -1.0);
 
  for (double thet = 180.0; thet >= -180.0; thet -= da) {
    glNormal3d (sin (c * thet),
		cos (c * thet),
		1.0);
    glVertex3d (sin (c * thet),
		cos (c * thet),
		1.0);
  }

  glEnd();

  // Top
  glBegin (GL_TRIANGLE_FAN);
  
  glNormal3d (0.0, 0.0, 1.0);
  glVertex3d (0.0, 0.0, 1.0);

  for (double thet = 180.0; thet >= -180.0; thet -= da) {
    glNormal3d (sin (c * thet),
		cos (c * thet),
		1.0);
    glVertex3d (sin (c * thet),
		cos (c * thet),
		1.0);
  }

  glEnd();
  
  return;
}

// Draw a cone with shadow
void cone (GLfloat rz, GLfloat dx, GLfloat dy, GLfloat dz)
{
  // Material
  glMaterialfv (GL_FRONT, GL_AMBIENT, cone_ambient);
  glMaterialfv (GL_FRONT, GL_DIFFUSE, cone_diffuse);
  glMaterialfv (GL_FRONT, GL_SPECULAR, cone_specular);
  glMaterialfv (GL_FRONT, GL_SHININESS, &cone_shine);

  // Draw sphere
  raw_cone ();

  // Shadow material
  glMaterialfv (GL_FRONT, GL_AMBIENT, shadow_ambient);
  glMaterialfv (GL_FRONT, GL_DIFFUSE, shadow_diffuse);
  glMaterialfv (GL_FRONT, GL_SPECULAR, shadow_specular);
  glMaterialfv (GL_FRONT, GL_SHININESS, &shadow_shine);

  // Draw shadow
  glMatrixMode (GL_MODELVIEW);
  glPushMatrix ();

  // Shadow projection

  // Translate Back (accounting for sphere displacement by movement)
  glRotatef (-rz, 0.0, 0.0, 1.0);
  glTranslatef (light0_pos[0] - dx, light0_pos[1] - dy, light0_pos[2] - dz + .01);
  // Project shadow
  glMultMatrixf (shadowm);
  // Move light to origin (accounting for sphere displacement by movement)
  glTranslatef (-light0_pos[0] + dx, -light0_pos[1] + dy,
		-light0_pos[2] + dz);
  glRotatef (rz, 0.0, 0.0, 1.0);

  raw_cone ();
  
  glPopMatrix ();
  
  glDisable (GL_NORMALIZE);
  
  return;
}

// Floor material

static GLfloat floor1_diffuse[] = {0.0, 0.0, 0.7, 1.0};
static GLfloat floor1_ambient[] = {0.1, 0.1, 0.1, 1.0};
static GLfloat floor1_specular[] = {0.1, 0.1, 0.1, 1.0};
static GLfloat floor1_shine = 1.0;

static GLfloat floor2_diffuse[] = {0.7, 0.7, 0.7, 1.0};
static GLfloat floor2_ambient[] = {0.1, 0.1, 0.1, 1.0};
static GLfloat floor2_specular[] = {0.1, 0.1, 0.1, 1.0};
static GLfloat floor2_shine = 1.0;

// Draw floor
void floor ()
{
  float d = 2.0;

  // Cone
  glNormal3d (0.0, 0.0, 1.0);

  for (int l = -15; l < 16;  ++l) {
    for (int k = -15; k < 16;  ++k) {

      // Material
      if ( (l + k) % 2 == 0) {
	glMaterialfv (GL_FRONT, GL_AMBIENT, floor1_ambient);
	glMaterialfv (GL_FRONT, GL_DIFFUSE, floor1_diffuse);
	glMaterialfv (GL_FRONT, GL_SPECULAR, floor1_specular);
	glMaterialfv (GL_FRONT, GL_SHININESS, &floor1_shine);
      } else {
	glMaterialfv (GL_FRONT, GL_AMBIENT, floor2_ambient);
	glMaterialfv (GL_FRONT, GL_DIFFUSE, floor2_diffuse);
	glMaterialfv (GL_FRONT, GL_SPECULAR, floor2_specular);
	glMaterialfv (GL_FRONT, GL_SHININESS, &floor2_shine);
      }

      glBegin (GL_QUAD_STRIP);

      glVertex3f (l * d, k * d, 0.0);
      glVertex3f ((l + 1) * d, k * d, 0.0);
      glVertex3f (l * d, (k + 1) * d, 0.0);
      glVertex3f ((l + 1) * d, (k + 1) * d, 0.0);

      glEnd ();
    }
  }
  
  return;
}

// Time
static unsigned long T = 0UL;

// Bouncing ball
int loop = 45;
float h = loop * loop / 4;

// display callback, clear frame buffer and z buffer,
// rotate cube and draw, swap buffers
void display ()
{
  // Clear
  glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  // Model view matrix mode
  glMatrixMode (GL_MODELVIEW);
  // Set to matrix to identity
  glLoadIdentity ();

  float a, b, x, y, ax, ay, ht;

  // Camera Position (first matrix, i.e. last transformation)
  switch (cmode) {
  case 0:
    // static
    gluLookAt (0.0, -10.0, 8.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
    break;
  case 1:
    // follow cylinder
    a = - static_cast<float> (T % 360) / 180.0 * M_PI;
    b = a + 60.0 / 180.0 * M_PI;
    x = sin (b) * 5.0;
    y = cos (b) * 5.0;
    ax = sin (a) * 5.0;
    ay = cos (a) * 5.0;
    gluLookAt (x, y, 3.0, ax, ay, 3.0, 0.0, 0.0, 1.0);
    break;
  case 2:
    // follow ball
    a = - static_cast<float> (T % 360) / 180.0 * M_PI;
    b = a + 60.0 / 180.0 * M_PI;
    x = sin (b) * 5.0;
    y = cos (b) * 5.0;
    ax = sin (a) * 5.0;
    ay = cos (a) * 5.0;
    ht = (T % loop);
    ht = 4.0 / h * ht * (loop - ht);
    gluLookAt (x, y, 3.0 + ht, ax, ay, 3.0 + ht, 0.0, 0.0, 1.0);
    break;
  case 3:
    // follow cone, look at ball
    a = - static_cast<float> (T % 360) / 180.0 * M_PI;
    b = a + 60.0 / 180.0 * M_PI;
    x = sin (b) * 5.0;
    y = cos (b) * 5.0;
    ax = sin (a) * 5.0;
    ay = cos (a) * 5.0;
    ht = (T % loop);
    ht = 4.0 / h * ht * (loop - ht);
    gluLookAt (x, y, 3.0, ax, ay, 3.0 + ht, 0.0, 0.0, 1.0);
    break;
  }

  // Transformations here woudl rotate the camera with the
  // light source

  // Everything specified here stays fixed with respect to 
  // camera position

  // Light rotates with object (fixed light source position)
  glLightfv (GL_LIGHT0, GL_POSITION, light0_pos);

  // Point indicating position of light source (fixed)
  glMaterialfv (GL_FRONT, GL_AMBIENT, ambientl);
  glMaterialfv (GL_FRONT, GL_DIFFUSE, diffusel);
  glMaterialfv (GL_FRONT, GL_SPECULAR, specularl);
  glMaterialfv (GL_FRONT, GL_SHININESS, &shinel);
  glPointSize (10.0);
  glBegin (GL_POINTS);
  glVertex4fv (light0_pos);
  glEnd ();

  // Floor
  floor ();

  // Cone
  glTranslatef (0.0, 0.0, 1.0);
  glRotatef (static_cast<float> (T % 360), 0.0, 0.0, 1.0);
  glTranslatef (0.0, 5.0, 0.0);
  cone (static_cast<float> (T % 360), 0.0, 5.0, 1.0);

  // Sphere
  float t = (T % loop);
  float H = 4.0 / h * t * (loop - t);
  glTranslatef (0.0, 0, 2.0 + H);
  sphere (static_cast<float> (T % 360), 0.0, 5.0, 1.0 + 2.0 + H);

  // Flush
  glFlush ();

  // Swap buffers (double buffering for smooth animation)
  glutSwapBuffers ();
}

// Idle callback, spin cube about selected axis
void spin ()
{
  T += 2;
  glutPostRedisplay ();
  return;
}

// Fog colour, etc.
static GLfloat fogColor[4] = {0.5, 0.5, 0.5, 1.0};
static bool Fog = false;

// Turn fog on/off
void fog ()
{
  Fog = !Fog;
  if (Fog) {
    glEnable (GL_FOG);
    glFogi (GL_FOG_MODE, GL_EXP);
    glFogfv (GL_FOG_COLOR, fogColor);
    glFogf (GL_FOG_DENSITY, 0.2);
    glHint (GL_FOG_HINT, GL_NICEST);
    glClearColor (fogColor[0], fogColor[1], fogColor[2], fogColor[3]);
  } else {
    glDisable (GL_FOG);
    glClearColor (0.0, 0.0, 0.0, 1.0);
  }
  return;
}

// mouse callback, selects an axis about which to rotate
void mouse (int btn, int state, int x, int y)
{
  if (btn == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
    cmode = (cmode + 1) % 4;
  if (btn == GLUT_RIGHT_BUTTON && state == GLUT_DOWN)
    fog ();
  glutPostRedisplay ();
  return;
}

// Window reshape callback
void myReshape (int w, int h)
{
  glViewport (0, 0, w, h);

  // Projection matrix mode
  glMatrixMode (GL_PROJECTION);

  // Set to identity
  glLoadIdentity ();
 
  // Set perspective frustum
  gluPerspective (100.0, h/w, 1.5, 50.0);

  // Set different projection mode here

  return;
}

static GLfloat gambient[] = {0.0, 0.0, 0.0, 1.0};

int main(int argc, char **argv)
{
  // Shadow projection matrix
  //
  // Have a look at http://www.gaffga.de//shadows/proj/index.php
  // for the general case.
  for (int i = 0; i < 15; ++i)
    shadowm[i] = 0.0;
  shadowm[0] = shadowm[5] = shadowm[10] = 1.0;
  shadowm[11] = -1.0/light0_pos[2];

  glutInit (&argc, argv);

  // need both double buffering and z buffer
  glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);

  glutInitWindowSize (500, 500);
  glutCreateWindow ("cube");

  glutReshapeFunc (myReshape);
  glutDisplayFunc (display);
  glutIdleFunc (spin);
  glutMouseFunc (mouse);

  // Clear color
  glClearColor (0.0, 0.0, 0.0, 1.0);

  // Enable hidden-surface-removal
  glDepthFunc (GL_LEQUAL);
  glClearDepth (1.0);
  glEnable (GL_DEPTH_TEST); 
  
  // Enable lighting and one light source
  glEnable (GL_LIGHTING);
  glEnable (GL_LIGHT0);
  glLightfv (GL_LIGHT0, GL_AMBIENT, ambient0);
  glLightfv (GL_LIGHT0, GL_DIFFUSE, diffuse0);
  glLightfv (GL_LIGHT0, GL_SPECULAR, specular0);
  glLightfv (GL_LIGHT0, GL_CONSTANT_ATTENUATION, &attenuation0);

  // No two-sided polygons
  glLightModeli (GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);

  // Local viewer for specular light?
  glLightModeli (GL_LIGHT_MODEL_LOCAL_VIEWER, GL_FALSE);

  // Ambient light
  glLightModelfv (GL_LIGHT_MODEL_AMBIENT, gambient);

  // Enable smoooth shading (multi-coloured polygons)
  glShadeModel (GL_SMOOTH);

  // Fill polygons
  glPolygonMode(GL_FRONT, GL_FILL);

  display ();

  // Initiate main loop
  glutMainLoop();

  return 0;
}

