/* Source file for the R3 camera class */



/* Include files */

#include "R3Graphics.h"



/* Public variables */

RNLength R3default_camera_xfov(RN_PI / 6.0);
RNLength R3default_camera_yfov(RN_PI / 6.0);
R3Vector R3default_camera_right_vector(1.0, 0.0, 0.0);
R3Vector R3default_camera_towards_vector(0.0, 1.0, 0.0);
R3Vector R3default_camera_up_vector(0.0, 0.0, 1.0);
R3Camera R3default_camera(R3CoordSystem(R3zero_point, 
    R3Triad(R3default_camera_towards_vector, R3default_camera_up_vector)),
    R3default_camera_xfov, R3default_camera_yfov, 0.001, 1000.0);



/* Public functions */

int 
R3InitCamera()
{
    /* Return success */
    return TRUE;
}



void 
R3StopCamera()
{
}



R3Camera::
R3Camera(void)
{
}



R3Camera::
R3Camera(const R3CoordSystem& cs,
	  RNAngle xfov, RNAngle yfov, RNLength neardist, RNLength fardist)
    : cs(cs),
      xfov(xfov),
      yfov(yfov),
      neardist(neardist),
      fardist(fardist)
{
}



R3Camera::
R3Camera(const R3Point& origin, const R3Triad& triad,
		  RNAngle xfov, RNAngle yfov, RNLength neardist, RNLength fardist)
    : cs(origin, triad),
      xfov(xfov),
      yfov(yfov),
      neardist(neardist),
      fardist(fardist)
{
}



R3Camera::
R3Camera(const R3Point& origin, const R3Vector& towards, const R3Vector& up,
		  RNAngle xfov, RNAngle yfov, RNLength neardist, RNLength fardist)
    : cs(origin, R3Triad(towards, up)),
      xfov(xfov),
      yfov(yfov),
      neardist(neardist),
      fardist(fardist)
{
}



R3Camera::
R3Camera(const R3Point& origin, RNAngle pitch, RNAngle yaw, RNAngle roll,
		  RNAngle xfov, RNAngle yfov, RNLength neardist, RNLength fardist)
    : xfov(xfov),
      yfov(yfov),
      neardist(neardist),
      fardist(fardist)
{
    // Clamp yaw (roll is undefined at +-PI/2)
    const RNAngle max_yaw = 0.99 * RN_PI_OVER_TWO;
    if (yaw > max_yaw) yaw = max_yaw;
    if (yaw < -max_yaw) yaw = -max_yaw;

    // Derive  triad from pitch, yaw, and roll
    RNScalar cos_yaw = cos(yaw);
    R3Vector towards = R3Vector(cos_yaw * cos(pitch), cos_yaw * sin(pitch), sin(yaw));
    R3Vector up = R3posz_vector;
    up.Rotate(towards, roll);
    R3Triad triad(towards, up);

    // Set coordinate system
    cs = R3CoordSystem(origin, triad);
}



const RNAngle R3Camera::
Pitch(void) const
{
    // Return pitch angle
    R3Vector v1(Towards());
    RNAngle yaw = Yaw();
    if (RNIsNotZero(yaw)) v1.Rotate(Left(), yaw);
    RNAngle pitch = R3InteriorAngle(R3default_camera_towards_vector, v1);
    if (v1.Dot(R3default_camera_right_vector) > 0.0) pitch = -pitch;
    return pitch;
}



const RNAngle R3Camera::
Yaw(void) const
{
    // Return yaw angle
    RNAngle angle = R3InteriorAngle(R3default_camera_up_vector, Towards()); 
    return RN_PI_OVER_TWO - angle;
}



const RNAngle R3Camera::
Roll(void) const
{
#if FALSE
    // Return roll angle
    R3Vector v2(Right());
    RNAngle yaw = Yaw();
    if (RNIsNotZero(yaw)) v2.Rotate(Left(), yaw);
    RNAngle roll = R3InteriorAngle(R3default_camera_up_vector, v2) - RN_PI_OVER_TWO;
    return roll;
#else
    // Return roll angle
    R3Vector v2 = Towards() % R3default_camera_up_vector; // Right vector if no roll
    RNAngle roll = R3InteriorAngle(Right(), v2);
    if (v2.Dot(Up()) > 0.0) roll = -roll;
    return roll;
#endif
}



const R3Halfspace R3Camera::
Halfspace(RNDirection dir, RNDimension dim) const
{
    R3Vector normal;
    switch (dim) {
    case RN_X:
        normal = cs.Axes().Axis(RN_X); 
	if (dir == RN_LO) normal.Rotate(cs.Axes().Axis(RN_Y), xfov);
	else normal.Rotate(cs.Axes().Axis(RN_Y), RN_PI - xfov);
	return R3Halfspace(Origin(), normal);
        break;

    case RN_Y:
        normal = cs.Axes().Axis(RN_Y); 
	if (dir == RN_LO) normal.Rotate(cs.Axes().Axis(RN_X), -yfov);
	else normal.Rotate(cs.Axes().Axis(RN_X), yfov - RN_PI);
	return R3Halfspace(Origin(), normal);
        break;

    case RN_Z:
        if (dir == RN_LO) return R3Halfspace(Origin() - cs.Axes().Axis(RN_Z) * neardist, -cs.Axes().Axis(RN_Z));
	else return R3Halfspace(Origin() - cs.Axes().Axis(RN_Z) * fardist, cs.Axes().Axis(RN_Z));
        break;
    }

    // Should never get here
    RNAbort("Invalid dim in camera halfspace query");
    return R3null_halfspace;
}



const R4Matrix R3Camera::
Perspective(void) const
{
#if FALSE
    // Return 4x4 perspective matrix
    double m[4][4];
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    gluPerspective(2.0 * RN_RAD2DEG(yfov), tan(xfov) / tan(yfov), neardist, fardist);
    glGetDoublev(GL_PROJECTION_MATRIX, m);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    return R4Matrix(m);
#else 
    // Return 4x4 perspective matrix -- derived from OpenGL Programming Guide
    RNScalar A = 1.0 / tan(xfov);
    RNScalar B = 1.0 / tan(yfov);
    RNScalar C = -(fardist + neardist) / (fardist - neardist);
    RNScalar D = -2.0 * neardist * fardist / (fardist - neardist);
    return R4Matrix(  A, 0.0,  0.0, 0.0,
		    0.0,   B,  0.0, 0.0,
		    0.0, 0.0,    C,   D,
		    0.0, 0.0, -1.0, 0.0);
#endif
}



const RNBoolean R3Camera::
operator==(const R3Camera& camera) const
{
    // Return whether camera is equal
    return (cs == camera.cs) && 
           (xfov == camera.xfov) &&
           (yfov == camera.yfov) &&
           (neardist == camera.neardist) &&
          (fardist == camera.fardist);
}



void R3Camera::
Reset(const R3Point& origin, const R3Vector& towards, const R3Vector& up)
{
    // Set coordinate system
    cs.SetOrigin(origin);
    cs.SetAxes(R3Triad(towards, up));
}



void R3Camera::
Reposition(const R3Point& origin)
{
    // Set position
    cs.SetOrigin(origin);
}



void R3Camera::
Reorient(const R3Vector& towards, const R3Vector& up)
{
    // Set orientation
    cs.SetAxes(R3Triad(towards, up));
}



void R3Camera::
Reorient(RNAngle pitch, RNAngle yaw, RNAngle roll)
{
    // Set orientation
    SetPitch(pitch);
    SetYaw(yaw);
    SetRoll(roll);
}



void R3Camera::
Mirror(const R3Plane& plane)
{
    // Mirror coordinate system over plane
    cs.Mirror(plane);
}



void R3Camera::
SetCoordSystem(const R3CoordSystem& cs)
{
    // Set coordinate system
    this->cs.SetOrigin(cs.Origin());;
    this->cs.SetAxes(cs.Axes());;
}



void R3Camera::
SetOrigin(const R3Point& origin)
{
    // Set origin 
    cs.SetOrigin(origin);
}



void R3Camera::
SetTowards(const R3Vector& towards)
{
    // Set towards vector
    cs.SetAxes(R3Triad(towards, cs.Axes().Axis(RN_Y)));
}



void R3Camera::
SetUp(const R3Vector& up)
{
    // Set up vector
    cs.SetAxes(R3Triad(Towards(), up));
}



void R3Camera::
SetRight(const R3Vector& right)
{
    // Set right vector
    cs.SetAxes(R3Triad(Towards(), right % Towards()));
}



void R3Camera::
SetPitch(RNAngle pitch)
{
    // Set pitch angle
    RotatePitch(pitch - Pitch());
}



void R3Camera::
SetYaw(RNAngle yaw)
{
    // Set yaw angle
    RotateYaw(yaw - Yaw());
}



void R3Camera::
SetRoll(RNAngle roll)
{
    // Set roll angle
    RotateRoll(roll - Roll());
}



void R3Camera::
SetFOV(RNAngle xfov, RNAngle yfov)
{
    // Set field of view
    this->xfov = xfov;
    this->yfov = yfov;
}



void R3Camera::
SetXFOV(RNAngle xfov)
{
    // Set x field of view
    this->xfov = xfov;
}



void R3Camera::
SetYFOV(RNAngle yfov)
{
    // Set y field of view
    this->yfov = yfov;
}



void R3Camera::
SetNear(RNLength neardist)
{
    // Set near plane distance
    this->neardist = neardist;
}



void R3Camera::
SetFar(RNLength fardist)
{
    // Set far plane distance
    this->fardist = fardist;
}



void R3Camera::
Translate(const R3Vector& translation)
{
    // Translate coordinate system
    cs.Translate(translation);
}



void R3Camera::
Rotate(const R3Vector& axis, RNAngle dtheta)
{
    // Rotate coordinate system
    cs.Rotate(axis, dtheta);
}



void R3Camera::
Transform(const R3Transformation& transformation)
{
    // Transform near and far distances
    R3Vector zaxis(cs.Axes().Axis(RN_Z));
    zaxis.Transform(transformation);
    RNLength scale = zaxis.Length();
    neardist *= scale;
    fardist *= scale;
    
    // Transform coordinate system
    cs.Transform(transformation);
}



void R3Camera::
Load(RNBoolean select_mode) const
{
    // Set the perspective projection
#if (RN_3D_GRFX == RN_IRISGL)
    perspective(20.0 * RN_RAD2DEG(yfov), tan(xfov) / tan(yfov), neardist, fardist);
#elif (RN_3D_GRFX == RN_OPENGL)
    if (!select_mode) {
      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();
    }
    gluPerspective(2.0 * RN_RAD2DEG(yfov), tan(xfov) / tan(yfov), neardist, fardist);
    glMatrixMode(GL_MODELVIEW);
#elif ((RN_3D_GRFX == RN_3DR) && (RN_MATH_PRECISION == RN_FLOAT_PRECISION))
    PointFW_t loc;
    PointF_t dir, up;
    loc.x = Origin().X();   loc.y = Origin().Y();   loc.z = Origin().Z();   loc.w = 1.0;
    dir.x = Towards().X();  dir.y = Towards().Y();  dir.z = Towards().Z();
    up.x = Up().X();        up.y = Up().Y();        up.z = Up().Z();
    G3dSetCameraPosition(R3dr_gc, &loc, &dir, &up);
#else
    RNAbort("Not Implemented");
#endif

    // Load the rotation matrix
    cs.InverseMatrix().Load();
}



void R3Camera::
Draw(void) const
{
#if TRUE
    // Draw pyramid to near plane
    // static float pyramid_color[3] = { 1.0, 0.0, 0.0 };
    // R3LoadRgb(pyramid_color);
    RNScalar scale = 1.0;
    R3Point org = Origin() + Towards() * scale;
    R3Vector dx = Right() * scale * tan(xfov);
    R3Vector dy = Up() * scale * tan(yfov);
    R3Point ur = org + dx + dy;
    R3Point lr = org + dx - dy;
    R3Point ul = org - dx + dy;
    R3Point ll = org - dx - dy;
    R3BeginLine();
    R3LoadPoint(ur);
    R3LoadPoint(ul);
    R3LoadPoint(ll);
    R3LoadPoint(lr);
    R3LoadPoint(ur);
    R3LoadPoint(Origin());
    R3LoadPoint(lr);
    R3LoadPoint(ll);
    R3LoadPoint(Origin());
    R3LoadPoint(ul);
    R3EndLine();
#endif

#if FALSE
    // Draw coordinate system 
    // static float cs_color[3] = { 0.0, 1.0, 0.0 };
    // R3LoadRgb(cs_color);
    cs.Draw();
#endif
}














