/*  this will be my attempt at the graphics project assigned to the computer
 science honours class of 2003
 Ingrid Brandt  */

/*  The specifactions of this project are that I was to create a Fantasy
 Island.  A fly-through of the scene is required.  Interactive control is
 optional, but if it is a feature there must be a default path should the
 user not wish to give input.  */

/*  The following need to be in the project:
 1.  A path to be followed by the camera/viewer using splines
 2.  At least one light, producing specular highlights
 3.  Terrain suitable to the context
 4.  Texture mapping
 5.  A number of objects moving according to a flocking algorithm
 6.  At least one of the following: fog, motion blur, depth of field,
 shadows, environment mapping, transparency
 7.  User of Renderman Shaders   */


#include <iostream>
#include <fstream>
#include <cstring>
#include <string>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <unistd.h>
#include <sys/time.h>

#include <qapplication.h>
#include <qpushbutton.h>
#include <qlayout.h>
#include <qgl.h>

#include "ti.h" 

using namespace std;
static int timer_interval = 4; // our delay between frames (in msec) (4 is 25 fps)
const double level_change = 0.7; 

const double KeepBack = 0.35;
const double MaxVel = 0.040;

#define MAXSTRING 10000
#define NumCoord 3   // number of dimensions in a point.


//a necessary evil
class Point
{
 public:
   double x;
   double y;
   double z;
};


//spline class to conrol automated motion around the island
class Spline
{
 protected:
   // set of control points for the spline.
   Point * points;
   
   // number of points in the spline.
   int numpoints;
   
   // degree of spline.
   int degree;
   
   double Min, Max;
   
 public:
   Spline ()
   {
      Max = 0;
      Min = 0;
      numpoints = 6;
      degree = 5;   
      
      points = new Point [numpoints];
      for (int i = 0; i < numpoints; i++)
      {
	 points[i].x = ((double) ((random () % 1000) - 500)) / 190.0;
	 points[i].y = ((double) ((random () % 1000) - 500)) / 190.0;
	 if (points[i].x > Max) { Max = points[i].x; }
	 if (points[i].x < Min) { Min = points[i].x; }
      }
   }
   
   double getMax()
   {
      return Max;
   }
   double getMin()
   {
      return Min;
   }
   int numberPoints ()
   {
      return numpoints;
   }
   
   int splineDegree ()
   {
      return degree;
   }
   
   void controlPoint (int i, double & x, double & y)
   {
      x = points[i].x;
      y = points[i].y;
   }
   
   void setControlPoint (int i, double x, double y)
   {
      points[i].x = x;
      points[i].y = y;
   }
   
   int fact(int p)
   {
      if (p<=0)
	return 1;
      else
	return (p*fact(p-1));
   }
   
   double B (int i, int p, double t)
   {
      double C = ((fact(p))/(fact(i)*fact(p-i)))*pow(t,i)*pow((1-t),(p-i));
      return C;    
   }
   
   void splineAt (double t, double & x, double & y)
   {
      x = 0.0;
      y = 0.0;
      for (int j = 0; j < numpoints; j++)
      {
	 x += B (j, degree, t) * points[j].x;
	 y += B (j, degree, t) * points[j].y;
      }
   }
};


//another necessary evil
class Vector
{
 public:
   double coord[NumCoord];
   
   friend std::ostream &  operator << (std::ostream & s, Vector & x)
   {
      s << "[" << x.coord[0] << "," << x.coord[1] << "," << x.coord[2] << "]";
      return s;
   }
   
   double length ()
   {
      return sqrt (coord[0] * coord[0] +
		   coord[1] * coord[1] +
		   coord[2] * coord[2]);
   }
   
   void normalize ()
   {
      double l = length ();
      coord[0] /= l;
      coord[1] /= l;
      coord[2] /= l;
   }
   
   friend double dotProduct (Vector & a, Vector & b)
   {
      return a.coord[0] * b.coord[0] +
	a.coord[1] * b.coord[1] +
	a.coord[2] * b.coord[2]; 
   }
   
   friend Vector crossProduct (Vector & a, Vector & b)
   {
      Vector result;
      
      result.coord[0] = a.coord[1] * b.coord[2] - a.coord[2] * b.coord[1];
      result.coord[1] = a.coord[2] * b.coord[0] - a.coord[0] * b.coord[2];
      result.coord[2] = a.coord[0] * b.coord[1] - a.coord[1] * b.coord[0];
      return result;
   }
};  


//my class to add textures to my landscapes... or any polygon for that
//matter
class TextureLandscape
{
#define PictureCoords(x,y) (((y) * xsize + (x)) * 3)
   
 protected:
   int x_size;
   int y_size;
   
   GLubyte * colours;
   
   GLuint textureId;
   
 public:
   TextureLandscape ()
   {
      x_size = 0;
      y_size = 0;
   }
   
   ~TextureLandscape ()
   {
      glDeleteTextures (1, &textureId);
      delete [] colours;
   }
   TextureLandscape (char * filename)
   {
      std::cout << "Loading texture: " << filename << "\n";
      
      FILE * file;
      if (strstr (filename, "ppm") == NULL)
      {
	 char line [MAXSTRING];
	 sprintf (line, "giftopnm %s | pnmflip -tb >tmptexture.pnm",
		  filename);
	 system (line);
	 
	 file = fopen ("tmptexture.pnm", "r");
      }
      else
	file = fopen (filename, "r");
      
      if (file == NULL)
      {
	 std::cerr << "Could not open pnm file " << filename << "\n";
	 exit (1);
      }
      
      int c;
      if (fscanf (file, "P6\n%d %d\n%d\n", &x_size, &y_size, &c) != 3)
      {
	 std::cerr << "Unable to read file header of converted " << filename << "\n";
	 exit (1);
      }
      
      colours = new GLubyte [x_size * y_size * 3];
      fread (colours, x_size * 3, y_size, file);
      fclose (file);
      
      // set up OpenGL texturing.
      glGenTextures (1, &textureId);
      glBindTexture (GL_TEXTURE_2D, textureId);
      
      glTexImage2D (GL_TEXTURE_2D, 0, 3, x_size, y_size, 0, GL_RGB,
		    GL_UNSIGNED_BYTE, colours);
      glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
      glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
      glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
      glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
      glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
   }
   // make this texture active.
   void activate ()
   {
      glBindTexture (GL_TEXTURE_2D, textureId);
   }
   int textureID ()
   {
      return textureId;
   }
};


//flocking class, here is where the boids really come together
class flocking
{

  protected:
    TextureLandscape *TLS;

  public:
    typedef struct { double velx, vely, velz, x, y, z; int boidIndex; } Boid; 
    Boid myFlock [10];
    double bound_x[2], bound_y[2], bound_z[2];

    flocking(double x0, double y0, double z0, double x1, double y1, double z1) {
         bound_x[0] = x0; bound_x[1] = x1;
         bound_y[0] = y0; bound_y[1] = y1;
         bound_z[0] = z0; bound_z[1] = z1;
	for (int i = 0; i < 10; i++) {
		add(myFlock[i]);
	}
	
	TLS = new TextureLandscape("blackDragon.gif");

      }

/*    ~flocking() {
	 for(int i = 0; i < 10; i++)
	 {  
	     delete myFlock[i];
	 }  */

    double distance(double x, double y, double z) 
    {
	return sqrt(x*x + y*y + z*z);
    }

  void add(Boid &b) 
  {
	b.x = (((double)random())/((double)RAND_MAX));
        b.y = (((double)random())/((double)RAND_MAX));
        b.z = (((double)random())/((double)RAND_MAX));
        b.velx = ((((double)random())/((double)RAND_MAX))-0.5)/5000;
        b.vely = ((((double)random())/((double)RAND_MAX))-0.5)/5000;
        b.velz = ((((double)random())/((double)RAND_MAX))-0.5)/5000;
  }

  void centreOfMass(Boid &b) //rule 1
  {
	double cx = 0;
	double cy = 0;
	double cz = 0;

	for (int i = 0; i < 10; i++)
	{
	  if (i!=b.boidIndex) {
	    cx += myFlock[i].x;
	    cy += myFlock[i].y;
	    cz += myFlock[i].z;	 
	  }
	}
	cx /= 9;
	cy /= 9;
	cz /= 9;

	b.velx += (cx-b.x)/1000;
	b.vely += (cy-b.y)/1000;
	b.velz += (cz-b.z)/1000;
  }//centre of mass, rule 1

  void stayAway(Boid &b) //rule 2
  {
	double vx = 0;
	double vy = 0;
	double vz = 0;

	for (int i = 0; i < 10; i++)
	{
	  if (i!=b.boidIndex) {
	     if ( distance(myFlock[i].x - b.x, myFlock[i].y - b.y, myFlock[i].z - b.z) < KeepBack) {
       	        vx = vx - (myFlock[i].x - b.x)/40;
	        vy = vy - (myFlock[i].y - b.y)/40;
	        vz = vz - (myFlock[i].z - b.z)/40;
	     }
	  }
	}
	b.velx += vx;
	b.vely += vy;
	b.velz += vz;
  } //keeping from bumping into each other, rule 2
   
  void velMatch(Boid &b) //rule 3
  {
	double vx = 0;
   	double vy = 0;
   	double vz = 0;
	
	for (int i = 0; i < 10; i++)
	{
	   if (i!=b.boidIndex) {
	     vx += myFlock[i].velx;
	     vy += myFlock[i].vely;
	     vz += myFlock[i].velz;
	   }
	}
	vx /= 9;
	vy /= 9;
	vz /= 9;

	b.velx += (vx-b.velx)/50;
        b.vely += (vy-b.vely)/50;
	b.velz += (vz-b.velz)/50;

  }//velocity matching, rule 3

  void boundingOfPos(Boid &b)
  {
	if (b.x < bound_x[0]) {   
	      b.velx += 0.01;
	} else if (b.x > bound_x[1]) {
	      b.velx -= 0.01;
	}

	if (b.y < bound_y[0]) {
	    b.vely += 0.01;
	} else if (b.y > bound_y[1]) {
	    b.vely -= 0.01;
	}

	if (b.z < bound_z[0]) {
	    b.velz += 0.01;
	} else if (b.z > bound_z[1]) {
	    b.velz -= 0.01;
	}
  }  
 
  void boundingVel(Boid &b)
  {
	if (abs(b.velx) > MaxVel) b.velx *= 0.9;
	if (abs(b.vely) > MaxVel) b.vely *= 0.9;
	if (abs(b.velz) > MaxVel) b.velz *= 0.9;
  }

  void draw(Boid &b)
  {
	int textureid = TLS->textureID();
      
	glEnable (GL_TEXTURE_2D);
	glBindTexture (GL_TEXTURE_2D, textureid);

	double boidsize = 1.5;	

	tiPrimitiveBegin (TYPE_POLYGON);
          tiPrimitiveNormal (sin(b.x * 0.1), sin(b.y * 0.1), sin(b.z * 0.1));
	  tiPrimitiveTextureCoordinate(0.0, 0.0);
	  tiPrimitiveVertex (b.x, b.y, b.z);
	  tiPrimitiveTextureCoordinate(0.0, 1.0);
          tiPrimitiveVertex (b.x + boidsize, b.y, b.z);
	  tiPrimitiveTextureCoordinate(1.0, 1.0);
 	  tiPrimitiveVertex (b.x + boidsize, b.y + boidsize, b.z);
	  tiPrimitiveTextureCoordinate(1.0, 0.0);
	  tiPrimitiveVertex (b.x, b.y + boidsize, b.z);
        tiPrimitiveEnd ();
	
	glDisable (GL_TEXTURE_2D);
  }	


  void updateFlock(Boid &b)
  {
 	boundingOfPos(b);
        boundingVel(b);

	centreOfMass(b);  //calling rule 1
	stayAway(b); //calling rule 2
	velMatch(b); //calling rule 3

	b.x+=b.velx;
	b.y+=b.vely;
	b.z+=b.velz;

	draw(b);
  }

}; //flocking


//putting it all together, the centre of attention
class GLWorld : public QGLWidget
{
   Q_OBJECT
     
 protected:

   Spline * spline;
   
   double whirl1;
   
   int frame;
   
   int light1;
   int light2;
   int light3;
   
   double scale;
   
   int framecount;
   
   double vx, vy, vz, rx, ry, rz, m_vx, m_vy, m_vz, m_rx, m_ry, m_rz;
   
   int tiles;
   double tilesize;
   
   double level [100][100];
   
   double delta;
   double actualpos;

   TextureLandscape *TLS;

   flocking *f; // descriptive
   
 public slots:
   
 public:
   GLWorld (Spline * s, char * obj, QWidget *parent, const char *name) : QGLWidget (parent, name)
   {
      setMinimumSize (400, 400);
      this->show ();
      
      spline = s;

      whirl1 = 0.0;
      frame = 0;
      
      light1 = 1;
      light2 = 0;
      light3 = 0;
      
      vy = -4; // Syrian version of VX

      TLS = new TextureLandscape("land_53.gif");
      
      scale = 0.01;
      framecount = 0;
      startTimer (50);
      
      delta = 0.01;
      actualpos = spline->getMin();

      tiles = 20;
      tilesize = 2.0;
      
      level [0][0] = 0.0;
      for (int i = 0; i< tiles; i++)
      {
	 for (int j = 0; j< tiles; j++)
	 {
	    if (j == 0 || j == tiles-1 || i == 0 || i == tiles-1) level[j][i] = 0.0;
	    else {
	       level[j][i] = rand()%100 > 50 && level[j-1][i]-level_change > 0 ? level[j-1][i] - level_change : level[j-1][i] + level_change;
	       level[j][i] = (level[j][i-1]+level[j][i])/2.0;
	    }//else
	    
	 }//middle for
	 
      }//first for, setting up random heights of the terrain
      f = new flocking(-10.0, 2.0, -10.0, 10.0, 6.5, 10.0);
   }
   
   void drawFloor ()
   {
      int textureid = TLS->textureID();
      
      glEnable (GL_TEXTURE_2D);
      glBindTexture (GL_TEXTURE_2D, textureid);
      
      for (int i = 0; i < tiles; i++)
      {
	 for (int j = 0; j < tiles; j++)
	 {
	    double x = tilesize * (i - tiles / 2);
	    double z = tilesize * (j - tiles / 2);
	    if (((i + j) / 10) % 2)
	    {
	       GLfloat material_ambient[] = { 0.0, 0.6, 0.3, 1.0 };
	       GLfloat material_diffuse [] = { 0.0, 0.7, 0.2, 1.0 };
	       GLfloat material_specular [] = { 0.0, 0.4, 0.2, 0.8 };
	       GLfloat material_emission [] = { 0.01, 0.3, 0.1, 0.0 };
	       
	       glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT, material_ambient);
	       glMaterialfv (GL_FRONT_AND_BACK, GL_DIFFUSE, material_diffuse);
	       glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,
			     material_specular);
	       glMaterialfv (GL_FRONT_AND_BACK, GL_EMISSION,
			     material_emission);
	       glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, 2.0);
	    }
	    else
	    {
	       GLfloat material_ambient[] = { 0.0, 0.6, 0.2, 0.0 };
	       GLfloat material_diffuse [] = { 0.0, 0.9, 0.2, 0.0 };
	       GLfloat material_specular [] = { 0.0, 0.7, 0.2, 0.0 };
	       GLfloat material_emission [] = { 0.01, 0.3, 0.1, 0.0 };
	       
	       glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT, material_ambient);
	       glMaterialfv (GL_FRONT_AND_BACK, GL_DIFFUSE, material_diffuse);
	       glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR,
			     material_specular);
	       glMaterialfv (GL_FRONT_AND_BACK, GL_EMISSION,
			     material_emission);
	       glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, 4.0);
	    }
	    
	    tiPrimitiveBegin (TYPE_POLYGON);
	    //tiPrimitiveNormal (sin(x * 0.1), 1.0, sin(z * 0.1));
	    tiPrimitiveTextureCoordinate(0.0, 0.0);
	    tiPrimitiveVertex (x, level[i][j], z);
	    tiPrimitiveTextureCoordinate(0.0, 1.0);
	    tiPrimitiveVertex (x + tilesize, level[i+1][j], z);
	    tiPrimitiveTextureCoordinate(1.0, 1.0);
	    tiPrimitiveVertex (x + tilesize, level[i+1][j+1], z + tilesize);
	    tiPrimitiveTextureCoordinate(1.0, 0.0);
	    tiPrimitiveVertex (x, level[i][j+1], z + tilesize);
	    tiPrimitiveEnd ();
	 }
      }
      glDisable (GL_TEXTURE_2D);
   }
   
   void paintGL ()
   {
      tiStartFrame (framecount);
      
      tiSetLight (0, 0.0, 0.6, 0.1, 1.0);
      tiSetLight (1, 0.0, 1.0, 0.2, 0.0);
      tiSetLight (2, 0.0, 0.4, 1.0, 0.0);
      
      tiSetProjection (0.5, 100.0, 20.0);
      
      tiStartWorldTransformations ();
      tiTransformIdentity ();

      if (actualpos >= spline->getMax()) { delta = -0.01; }
      if (actualpos <= spline->getMin()) { delta = 0.01; }

      actualpos += delta;
      
      //camera moving via Spline, calls to the splineAt function in the
      //spline class
      spline->splineAt(actualpos, vx, vz);
      
      vx += 15; // but WMD--!!
      vz -= 30; // related to VX, equally bad; also get outta here.

      // view transformations.
      tiTransformTranslate(m_vx + vx, m_vy + vy, m_vz + vz); // this sets the camera back a bit
      tiTransformRotate(m_rx+15, 1.0, 1.0, 0.0); // user direction
      tiTransformRotate(m_ry, 0.0, 1.0, 0.0); // user direction
      tiTransformRotate(m_rz, 0.0, 0.0, 1.0); // user direction

      //tiTransformRotate(whirl1, 0.0, 1.0, 0.0); // and this gives it a whirl
      
      // fixed light, relative to floor.
      GLfloat light2pos [] = { -2.0, 30.0, 30.0, 1.0 };
      GLfloat light2dir [] = { 2.0, -30.0, -30.0, 0.0};
      glLightfv (GL_LIGHT2, GL_POSITION, light2pos);
      glLightfv (GL_LIGHT2, GL_SPOT_DIRECTION, light2dir);
      
      //call boid class and draw them
      for (int i = 0; i < 10; i++)
      {
	  f->myFlock[i].boidIndex = i;
	  f->updateFlock(f->myFlock[i]);
      } 

      // draw a floor plane.
      drawFloor ();
 
      whirl1 += 0.2;
      frame = frame + 1;
      
      tiStopFrame ();
      framecount++;
   }
   
   void keyPressEvent(QKeyEvent* e) {
      switch (e->key()) {
       case Key_A: m_vy-=0.05; break;
       case Key_Z: m_vy+=0.05; break;
       case Key_X: m_vx+=0.05; break;
       case Key_C: m_vx-=0.05; break;
       case Key_K: m_vz+=0.05; break;
       case Key_M: m_vz-=0.05; break;
       case Key_T: m_rx+=1.0; break;
       case Key_Y: m_rx-=1.0; break;
       case Key_G: m_ry+=1.0; break;
       case Key_H: m_ry-=1.0; break;
       case Key_B: m_rz+=1.0; break;
       case Key_N: m_rz-=1.0; break;
      }
   }
   
   void resizeGL (int w, int h)
   {
      glViewport (0, 0, (GLint)w, (GLint)h);
   }
   void initializeGL ()
   {
   }
   void timerEvent(QTimerEvent*) {
      updateGL();
   }
   
};


//yet another necessary evil ;-)
int main (int argc, char * argv[])
{
   QApplication a (argc, argv);
   
   char * name = "dribble";
   if (argc > 1)
     name = argv[1];

   Spline s;   
   GLWorld w (&s, name, NULL, "Coaster");
   a.setMainWidget(&w); //make w our main widget
   w.show();
   
   a.exec ();
}




