/***************************************************************************
                          camera.cpp  -  description
                             -------------------
    begin                : Sat May 10 2003
    copyright            : (C) 2003 by Roland Schwarz
    email                : roland.schwarz@chello.at
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "camera.h"

const sgVec3 Camera::Xaxis = {1.0,0.0,0.0};
const sgVec3 Camera::Yaxis = {0.0,1.0,0.0};
const sgVec3 Camera::Zaxis = {0.0,0.0,1.0};

Camera::Camera(){
  ssgSetFOV     ( 60.0f, 0.0f ) ;
  ssgSetNearFar ( 1.0f, 90000.0f ) ; /* nb. skydome is scalable ... so far can be reduced */
  sgCoord campos ;
  sgSetCoord ( & campos, 0.0f, -50.0, 0.0f, 0.0f, 0.0f, 0.0f ) ;
  ssgSetCamera ( & campos ) ;
  POI[0] = -7;
  POI[1] = -7;
  POI[2] = 4.0;
}

Camera::~Camera(){
}

void Camera::setFOV(const float fovy){
  float aspect;
  float hfov,vfov;
  frustum->getFOV(&hfov,&vfov);
  // calc the current aspect from hfov and vfov
  aspect = tan(SG_DEGREES_TO_RADIANS*hfov/2.0)/tan(SG_DEGREES_TO_RADIANS*vfov/2.0);
  // calc new hfov, vfov
  if (1.0 < aspect){
    hfov = 2.0*SG_RADIANS_TO_DEGREES*atan(tan(SG_DEGREES_TO_RADIANS*fovy/2.0)*aspect);
    vfov = fovy;
  }
  else {
    hfov = fovy;
    vfov = 2.0*SG_RADIANS_TO_DEGREES*atan(tan(SG_DEGREES_TO_RADIANS*fovy/2.0)/aspect);
  }
  ssgContext::setFOV(hfov, vfov);
}

void Camera::setAspect(const float aspect){
  float fovy;
  float hfov,vfov;
  frustum->getFOV(&hfov,&vfov);
  // calc the current fovy from hfov and vfov
  if (1.0 < tan(SG_DEGREES_TO_RADIANS*hfov/2.0)/tan(SG_DEGREES_TO_RADIANS*vfov/2.0))
    fovy = vfov;
  else
    fovy = hfov;
  // calc new hfov, vfov
  if (1.0 < aspect){
    hfov = 2.0*SG_RADIANS_TO_DEGREES*atan(tan(SG_DEGREES_TO_RADIANS*fovy/2.0)*aspect);
    vfov = fovy;
  }
  else {
    hfov = fovy;
    vfov = 2.0*SG_RADIANS_TO_DEGREES*atan(tan(SG_DEGREES_TO_RADIANS*fovy/2.0)/aspect);
  }
  ssgContext::setFOV(hfov, vfov);
}

void Camera::setPOI(const int x, const int y){
  GLfloat z;
  GLint vp[4];
  GLfloat dr[2];
  sgVec3 w, g;

  // we get the missing z-coordinate from the z-buffer
  glGetIntegerv(GL_VIEWPORT, vp);
  glGetFloatv(GL_DEPTH_RANGE, dr);
  glReadBuffer(GL_FRONT);
  glPixelStorei(GL_PACK_ALIGNMENT, 1);
  glReadPixels(x, vp[3]-y-1,1,1,GL_DEPTH_COMPONENT, GL_FLOAT, &z);

  if (z != dr[1]) { // we do nothing when infinity is hit
    w[0] = (2.0*x)/vp[2] -1.0;
    w[1] = 1.0 - (2.0*y)/vp[3];
    w[2] = (2.0*z-(dr[0]+dr[1]))/(dr[1]-dr[0]);
      unproject(&g,w);
      setPOI(g);
    }
}
  
void Camera::setPOI(const sgVec3 p){
  sgCopyVec3(POI, p);
}

void Camera::getPOI(sgVec3* point)
{
  sgCopyVec3( *point, POI);
}

void Camera::getEyePoint(sgVec3* eye){
  (*eye)[0] = -cameraMatrix[0][0]*cameraMatrix[3][0]
    - cameraMatrix[0][1]*cameraMatrix[3][1] - cameraMatrix[0][2]*cameraMatrix[3][2];
  (*eye)[1] = -cameraMatrix[1][0]*cameraMatrix[3][0]
    - cameraMatrix[1][1]*cameraMatrix[3][1] - cameraMatrix[1][2]*cameraMatrix[3][2];
  (*eye)[2] = -cameraMatrix[2][0]*cameraMatrix[3][0]
    - cameraMatrix[2][1]*cameraMatrix[3][1] - cameraMatrix[2][2]*cameraMatrix[3][2];
}
            
void Camera::tilt(const SGfloat a, const sgVec3 axis){
  sgVec4 v;
  sgMat4 M;
  
  v[0] = axis[0]; v[1] = axis[1]; v[2] = axis[2]; v[3]=0.0f;
  sgInvertMat4(M, cameraMatrix);
  sgXformPnt4(v, M);
  
  translate(POI);
  rotate(a, v);
  sgCopyVec3(v, POI);
  sgNegateVec3(v);
  translate(v);    
}

void Camera::pan(const int x, const int y){
  // move camera parallel to viewplane, so that POI appears at x,y.
  GLint vp[4];
  sgVec3 a,b,n,w;
  glGetIntegerv(GL_VIEWPORT, vp);
  w[0] = (2.0*x)/vp[2] - 1.0;
  w[1] = 1.0 - (2.0*y)/vp[3];
  w[2] = -1.0;
  unproject(&a,w);
  w[2] = 1.0;
  unproject(&b,w);
  sgSubVec3(b,a);
  sgSubVec3(a,POI);
  n[0] = cameraMatrix[0][2];
  n[1] = cameraMatrix[1][2];
  n[2] = cameraMatrix[2][2];
  sgAddScaledVec3(a, b,  -sgScalarProductVec3(n,a)/sgScalarProductVec3(n,b));
  translate(a);
}

void Camera::walk(const SGfloat d){
  sgVec3 w;
  getEyePoint(&w);
  sgSubVec3(w, POI, w);
  sgNormaliseVec3(w);
  sgScaleVec3(w,d);
  translate(w);
}
  
void Camera::unproject(sgVec3* upt, const sgVec3 pt){
  sgMat4 M, P;
  sgVec4 a;
  frustum->getMat4(P);
  sgMultMat4(M, P,cameraMatrix);
  sgInvertMat4(M, M);
  a[0] = pt[0];
  a[1] = pt[1];
  a[2] = pt[2];
  a[3] = 1.0;
  sgXformPnt4(a, M);
  (*upt)[0] = a[0]/a[3];
  (*upt)[1] = a[1]/a[3];
  (*upt)[2] = a[2]/a[3];
  
}

void Camera::translate(const sgVec3 dist){
  sgMat4 A;
  sgMakeIdentMat4(A);
  A[3][0] = dist[0]; A[3][1] = dist[1]; A[3][2] = dist[2];
  sgPreMultMat4(cameraMatrix, A);
}
              
void Camera::rotate(const SGfloat a, const sgVec3 axis){
  sgMat4 A,B,C,D;
  sgVec3 av;
  SGfloat sina, cosa;
  int n,m;
  
  sgCopyVec3(av, axis);
  sgNormaliseVec3(av);
  sina = sin(SG_DEGREES_TO_RADIANS*a);
  cosa = cos(SG_DEGREES_TO_RADIANS*a);
  
  A[0][0] = av[0]*av[0];   A[1][0] = av[0]*av[1];   A[2][0] = av[0]*av[2];   A[3][0] = 0.0;
  A[0][1] = av[1]*av[0];   A[1][1] = av[1]*av[1];   A[2][1] = av[1]*av[2];   A[3][1] = 0.0;
  A[0][2] = av[2]*av[0];   A[1][2] = av[2]*av[1];   A[2][2] = av[2]*av[2];   A[3][2] = 0.0;
  A[0][3] =         0.0;   A[1][3] =         0.0;   A[2][3] =         0.0;   A[3][3] = 1.0;

  B[0][0] = 1.0 - A[0][0]; B[1][0] =     - A[1][0]; B[2][0] =     - A[2][0]; B[3][0] = 0.0;
  B[0][1] =     - A[0][1]; B[1][1] = 1.0 - A[1][1]; B[2][1] =     - A[2][1]; B[3][1] = 0.0;
  B[0][2] =     - A[0][2]; B[1][2] =     - A[1][2]; B[2][2] = 1.0 - A[2][2]; B[3][2] = 0.0;
  B[0][3] =           0.0; B[1][3] =           0.0; B[2][3] =           0.0; B[3][3] = 0.0;

  C[0][0] =     0.0;       C[1][0] = -av[2];        C[2][0] =  av[1];        C[3][0] = 0.0;
  C[0][1] =   av[2];       C[1][1] =    0.0;        C[2][1] = -av[0];        C[3][1] = 0.0;
  C[0][2] = - av[1];       C[1][2] =  av[0];        C[2][2] =    0.0;        C[3][2] = 0.0;
  C[0][3] =     0.0;       C[1][3] =    0.0;        C[2][3] =    0.0;        C[3][3] = 0.0;

  for(n=0; n<4; ++n) for(m=0; m<4; ++m) D[n][m] = A[n][m]+cosa*B[n][m]+sina*C[n][m];

  sgPreMultMat4(cameraMatrix, D);    
}
