// include file for 'Camera.h'

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

//  << Support Classes for Camera >>
//@@@@@@@@@@@@@@@@@@ Point3 class @@@@@@@@@@@@@@@@@
class Point3{ 
public: 
    float x,y,z;
    void set(float dx, float dy, float dz){x = dx; y = dy; z = dz;}
    void set(Point3& p){x = p.x; y = p.y; z = p.z;}
    Point3(float xx, float yy, float zz){x = xx; y = yy; z = zz;}
    Point3(){x = y = z = 0;}
    void build4tuple(float v[])
    {// load 4-tuple with this point: v[3] = 1 for homogeneous
            v[0] = x; v[1] = y; v[2] = z; v[3] = 1.0f;
    }
};

//@@@@@@@@@@@@@@@@@@ Vector3 class @@@@@@@@@@@@@@@@
class Vector3{ 
public: 
    float x,y,z;
    void set(float dx, float dy, float dz){ x = dx; y = dy; z = dz;} 
    void set(Vector3& v){ x = v.x; y = v.y; z = v.z;}
    void flip(){x = -x; y = -y; z = -z;} // reverse this vector
    void setDiff(Point3& a, Point3& b)//set to difference a - b
    { x = a.x - b.x; y = a.y - b.y; z = a.z - b.z;}
    void normalize()//adjust this vector to unit length
    {
        double sizeSq = x * x + y * y + z * z;
        if(sizeSq < 0.0000001)
        {
            cerr << "\nnormalize() sees vector (0,0,0)!";
            return; // does nothing to zero vectors;
        }
        float scaleFactor = 1.0/(float)sqrt(sizeSq);
        x *= scaleFactor; y *= scaleFactor; z *= scaleFactor;
    }
    Vector3(float xx, float yy, float zz){x = xx; y = yy; z = zz;}
    Vector3(Vector3& v){x = v.x; y = v.y; z = v.z;}
    Vector3(){x = y = z = 0;} //default constructor
    Vector3 cross(Vector3 b) //return this cross b
    {
       Vector3 c(y*b.z - z*b.y, z*b.x - x*b.z, x*b.y - y*b.x);
       return c;
    }
    float dot(Vector3 b) // return this dotted with b
   {return x * b.x + y * b.y + z * b.z;}
};

class Camera{
  private:
    Point3 eye;
    Vector3 u,v,n;
    double viewAngle, aspect, nearDist, farDist; // view volume shape
    void setModelViewMatrix(); // tell OpenGL where the Camera is

  public:
    Camera(){};
    //Camera(){u.set(1, 0, 0); v.set(0, 1, 0); n.set(0, 0, 1);};
    // default constructor
    void set(Point3 eye, Point3 look, Vector3 up); // like gluLookAt()
    void roll(float angle); // roll it
    void pitch(float angle); // increase pitch
    void yaw(float angle); // yaw it
    void slide(float delU, float delV, float delN); // slide it
    void setShape(float vAng, float asp, float nearD, float farD);
};

void Camera :: setModelViewMatrix(void)
{ // load modelview matrix with existing Camera values
    float m[16];
    Vector3 eVec(eye.x, eye.y, eye.z); // a vector version of eye 
    m[0] =  u.x; m[4] =  u.y; m[8]  =  u.z;  m[12] = -eVec.dot(u);
    m[1] =  v.x; m[5] =  v.y; m[9]  =  v.z;  m[13] = -eVec.dot(v);
    m[2] =  n.x; m[6] =  n.y; m[10] =  n.z;  m[14] = -eVec.dot(n);
    m[3] =  0;   m[7] =  0;   m[11] =  0;    m[15] = 1.0;
    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixf(m);  // load OpenGL's modelview matrix
}

void Camera :: set(Point3 Eye, Point3 look, Vector3 up)
{	// create a modelview matrix and send it to OpenGL
Vector3 t;
    eye.set(Eye); // store the given eye position
    n.set(eye.x - look.x, eye.y - look.y, eye.z - look.z); // make n
    //t.set(up.y*n.z - up.z*n.y, up.z*n.x - up.x*n.z, up.x*n.y - up.y*n.x);
    u.set(up.y*n.z - up.z*n.y, up.z*n.x - up.x*n.z, up.x*n.y - up.y*n.x);
    //Vector3 t(up.cross(n));      
    //u.set(t);                     // make u = up X n
    //u.set(up.cross(n));           // make u = up X n
    n.normalize(); u.normalize(); // make them unit length
    v.set(n.y*u.z - n.z*u.y, n.z*u.x - n.x*u.z, n.x*u.y - n.y*u.x);
    //t = up.cross(n);      
    //v.set(t);                     // make v =  n X u
    //v.set(n.cross(u));            // make v =  n X u
    setModelViewMatrix();         // tell OpenGL 
}

void Camera :: setShape(float vAng, float asp, float nearD, float farD)
{ // set shape of the view volume and set up the projection matrix
viewAngle = vAng; aspect = asp; nearDist = nearD; farDist = farD;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(viewAngle, aspect, nearDist, farDist);
}

void Camera :: slide(float delU, float delV, float delN)
{
    eye.x += delU * u.x + delV * v.x + delN * n.x;
    eye.y += delU * u.y + delV * v.y + delN * n.y;
    eye.z += delU * u.z + delV * v.z + delN * n.z;
    setModelViewMatrix();
}

void Camera :: roll(float angle)
{ // roll the camera through angle degrees
float cs = cos( angle / 180 * 3.14159265 );
float sn = sin( angle / 180 * 3.14159265 );
Vector3 t = u;   // remember old u
u.set(cs*t.x - sn*v.x, cs*t.y - sn*v.y, cs*t.z - sn*v.z );
v.set(sn*t.x + cs*v.x, sn*t.y + cs*v.y, sn*t.z + cs*v.z );
setModelViewMatrix();
}

void Camera :: pitch(float angle)
{ // pitch the camera through angle degrees
float cs = cos( angle / 180 * 3.14159265 );
float sn = sin( angle / 180 * 3.14159265 );
Vector3 t = n;   // remember old n
n.set(cs*t.x + sn*v.x, cs*t.y + sn*v.y, cs*t.z + sn*v.z );
v.set(-sn*t.x + cs*v.x, -sn*t.y + cs*v.y, -sn*t.z + cs*v.z );
setModelViewMatrix();
}

void Camera :: yaw(float angle)
{ // yaw the camera through angle degrees
float cs = cos( angle / 180 * 3.14159265 );
float sn = sin( angle / 180 * 3.14159265 );
Vector3 t = u;   // remember old n
u.set(cs*t.x + sn*n.x, cs*t.y + sn*n.y, cs*t.z + sn*n.z );
n.set(-sn*t.x + cs*n.x, -sn*t.y + cs*n.y, -sn*t.z + cs*n.z );
setModelViewMatrix();
}