#include "camera.h"

Camera::Camera()
{
}

void Camera::ini(int a, int h, Capsa3D capsaMinima,vec4 *tv)
{

    // Calcul del vrp com el centre de la capsa minima contenedora 3D
   // CAL IMPLEMENTAR
    // CODI A MODIFICAR

/*
    vs.vrp[0] = capsaMinima.pmin[0]+(capsaMinima.a/2.0);
    vs.vrp[1] = capsaMinima.pmin[1]+(capsaMinima.h/2.0);
    vs.vrp[2] = capsaMinima.pmin[2]+(capsaMinima.p/2.0);
 */
    vs.vrp[0] = capsaMinima.pmin[0]+((tv->x)/2.0);
    vs.vrp[1] = capsaMinima.pmin[1]+((tv->z)/2.0);
    vs.vrp[2] = capsaMinima.pmin[2]+((tv->y)/2.0);

    vs.angx = 0;
    vs.angy = 0;
    vs.angz = 0;

    vp.a = a;
    vp.h = h;
    vp.pmin[0] = 0;
    vp.pmin[1] = 0;

    piram.d = 64;


    piram.proj = PARALLELA;
//    piram.proj = PERSPECTIVA;
}

void Camera::toGPU(QGLShaderProgram *program)
{
        this->setModelView(program, modView);
        this->setProjection(program, proj);

        vec4 obs(CalculObs(vs.vrp,piram.d,vs.angx,vs.angy),0.0);
        GLuint observador = program->uniformLocation("obs");
        glUniform4fv(observador, 1,obs);

        GLuint vrpLocation = program->uniformLocation("vrp");
        glUniform3fv(vrpLocation, 1, vs.vrp);

        GLuint dirVecLocation = program->uniformLocation("dirVec");
        vec3 dirVec = vec3(vs.vrp.x - obs.x, vs.vrp.y - obs.y, vs.vrp.z - obs.z);
        glUniform3fv(dirVecLocation, 1, dirVec);

}

void Camera::CalculaMatrius()
{

    vec4 obs(CalculObs(vs.vrp,piram.d,vs.angx,vs.angy),0.0);
    vec4 at(vs.vrp,0.0);
    vec4 vup(CalculVup(vs.angx,vs.angy,vs.angz),0.0);

    modView = LookAt(obs,at,vup);
    if (piram.proj == PARALLELA) {

//        proj = Ortho(wd.pmin[0], wd.pmin[0]+wd.a, wd.pmin[1], wd.pmin[1]+wd.h, piram.dant, piram.dpost );
        proj = Ortho(wd.pmin[0], wd.pmin[0]+wd.a, wd.pmin[1], wd.pmin[1]+wd.h, piram.dant, piram.dpost );

    }
    else {
        proj = Frustum(wd.pmin[0], wd.pmin[0]+wd.a, wd.pmin[1], wd.pmin[1]+wd.h, piram.dant, piram.dpost );
    }

}


void Camera::CalculWindow( Capsa3D c)
{


    vec4 vaux[8];
    mat4 MDP, MVIS;
   /* c.a = 144;
    c.p = 144;
    c.h = 213;*/
 //   c.pmin = vec3((256-144)/2,(256-213)/2,(256-144)/2);
    //c.pmin = vec3()
  /*  vs.vrp.x = 144/2;
    vs.vrp.y = 144/2;
    vs.vrp.z = 213/2;*/
   // vs.vrp = vec3((256-144)/2,(256-144)/2,(256-213)/2);
    CreaMatVis(MVIS);
    if (piram.proj == PERSPECTIVA) {
        CreaMatDp(MDP);
        MVIS = MDP*MVIS;
    }

    VertexCapsa3D(c,vaux);
    for(int i = 0; i < 8; i++) {
        vaux[i] = MVIS*vaux[i];
    }
    wd = CapsaMinCont2DXYVert(vaux,8);
  //  wd.pmin.x = (256-144)/-2.0;
  //  wd.pmin.y = (256-144)/-2.0;
 //   std::cout <<" calculwindow:"<<wd.a<<" "<<wd.h <<" "<<wd.pmin.x<<" "<< wd.pmin.y<<std::endl;
 //   std::cout << " capsaWindow: "<<c.a <<" "<<c.h<<" "<< c.p<<" "<<c.pmin.x<<" "<<c.pmin.y <<" "<<c.pmin.z<<std::endl;
 //   std::cout <<" vrp: "<<vs.vrp.x <<" "<<vs.vrp.y<<" "<<vs.vrp.z<<std::endl;
  //  AmpliaWindow(0.2);
    AjustaAspectRatioWd();



}


void Camera::setModelView(QGLShaderProgram *program, mat4 m)
{
    model_view = program->uniformLocation("model_view");
    glUniformMatrix4fv( model_view, 1, GL_TRUE, m);

}
void Camera::setProjection(QGLShaderProgram *program, mat4 p)
{
    projection = program->uniformLocation("projection");
    glUniformMatrix4fv( projection, 1, GL_TRUE, p);

}

void Camera::setViewport(int x, int y, int a, int h)
{
    vp.pmin[0] = x;
    vp.pmin[1] = y;
    vp.a = a;
    vp.h = h;

}

void Camera::AmpliaWindow(double r)
{
  // Pre r = 1.5 => amplia a 150%
  double na, da;

  na  = wd.a * (1.0 + r);
  da = na - wd.a;
  wd.pmin[0] = wd.pmin[0] - 0.5*da;
  wd.a  = na;
  na  = wd.h * (1.0 + r);
  da = na - wd.h;
  wd.pmin[1] = wd.pmin[1] - 0.5*da;
  wd.h  = na;
}

/*************************************************************/
/* procediment que calcula el semi angle d'obertura vertical */
/* a partir de l'alcada total del window (h) i la distancia  */
/* a l'observador                                            */
/*************************************************************/

void Camera::CalculAngleOberturaVertical()
{
  piram.alfav =  180.0 * atan2(wd.h/2.0, piram.d)/PI;

}
/****************************************************************/
/* procediment que calcula el semi angle d'obertura horitzontal */
/* a partir de l'amplada total del window (a) i la distancia    */
/* a l'observador                                               */
/****************************************************************/

void Camera::CalculAngleOberturaHoritzontal()
{

  piram.alfah =  180.0 * atan2(wd.a/2.0, piram.d)/PI;

}

Capsa2D  Camera::CalculWindowAmbRetallat()
{

  Capsa2D c;

  if (piram.proj == PARALLELA) {
    /* Projeccio paral.lela:
         el window ve donat: amplada o alcada o tots dos. En el darrer cas
         si el window no s'ajusta a l'aspect ratio, pitjor per l'usuari */

    if( fabs(wd.h) <EPS ) {
      c.a = wd.a;
      c.h = ((float)(vp.h)/ (float)(vp.a) ) * wd.a;
    }
    else {
      c.h = wd.h;
      c.a = ((float)(vp.a)/ (float)(vp.h) ) * wd.h;
    }
  }
  else {

    /* Projeccio perspectiva:
         el window ve donat pels angles d'obertura de la camera: el
         vertical, l'horitzontal o tots dos. En el darrer cas
         si el window no s'ajusta a l'aspect ratio, pitjor per l'usuari */

    if( fabs(piram.alfav) <EPS ) {
      c.a = 2.0  * piram.d * tan( PI*piram.alfah/180.);
      c.h = ((float)(vp.h)/ (float)(vp.a) ) * c.a;
    }
    else {
      if (fabs(piram.alfah) <EPS ) {
        c.h = 2.0  * piram.d * tan( PI*piram.alfav/180.);
        c.a = ((float)(vp.a)/ (float)(vp.h) ) * c.h;
      }
      else {
        c.a = 2.0  * piram.d * tan( PI*piram.alfah/180.);
        c.h = 2.0  * piram.d * tan( PI*piram.alfav/180.);
      }
    }
  }
  c.pmin[0] = -0.5 * c.a;
  c.pmin[1] = -0.5 * c.h;


  return(c);

}


/*                                                                       */
/* Donat un window i un viewport, amplia el window per tal que el seu    */
/* aspect ratio sigui igual al del viewport                              */
/*                                                                       */
void    Camera::AjustaAspectRatioWd()
{

    double arvp, arwd;

    arvp = ((double) vp.h)/((double)(vp.a));
    arwd = wd.h/wd.a;
    if(arwd > arvp) {
        wd.a = wd.h/arvp;
        wd.pmin[0] = -0.5*wd.a;
    }
    else if (arwd <arvp) {
        wd.h = wd.a *arvp;
        wd.pmin[1] = -0.5*wd.h;
    }
}


/*                                                                       */
/* Accio que crea la matriu de visualitzacio 3D                          */
/*                                                                       */
 void Camera::CreaMatVis(mat4 &MVIS)
{
  // BLOQUEJAR ROTACIO
    //printf("ENTRE A CREAMATVIS\n");
  MVIS = RotateZ(-vs.angz)*RotateX(-vs.angx)*RotateY(-vs.angy)*Translate(-vs.vrp);
  //MVIS = RotateY(-vs.angx)*Translate(-vs.vrp);

}

/*                                                                       */
/* Accio que crea la matriu del proces invers de  visualitzacio 3D       */
/*                                                                       */
 void Camera::CreaMatSiv(mat4 &MSIV)
{
     //printf("ENTRA A CREAMAT-SIV\n");
     MSIV = Translate(vs.vrp)*RotateY(vs.angy)*RotateX(vs.angx)*RotateZ(vs.angz);
     //MSIV = Translate(vs.vrp)*RotateY(vs.angx);
}

/*                                                                       */
/* Accio que crea la matriu de deformacio perspectiva                    */
/*                                                                       */
void Camera::CreaMatDp(mat4 &MDP)
{
  MDP[3][2] = -1/piram.d;
}



Capsa2D  Camera::CapsaMinCont2DXYVert( vec4 *v, int nv)
{
    Capsa2D c;
    vec2    pmin, pmax;
    int     i;

    pmin[0] = v[0][0];       pmin[1] = v[0][1];
    pmax[0] = v[0][0];       pmax[1] = v[0][1];
    for(i=1; i<nv; i++) {
      if(v[i][0] <pmin[0])
        pmin[0] = v[i][0];
      if(v[i][1] <pmin[1])
        pmin[1] = v[i][1];
      if(v[i][0] >pmax[0])
        pmax[0] = v[i][0];
      if(v[i][1] >pmax[1])
        pmax[1] = v[i][1];
    }
    c.pmin = pmin;
    c.a = pmax[0]-pmin[0];
    c.h = pmax[1]-pmin[1];
    return c;
}



/***********************************************************************/
/*                                                                     */
/*             Calcul del observador:                                  */
/*   es el vrp translladat en la direccio del vector de visio  d'una   */
/*   distancia d = distancia (obs, vrp) si la projeccio es perspectiva */
/*   i d'una distancia prou gran si la projeccio es paral.lela         */
/*                                                                     */
/***********************************************************************/

vec3 Camera::CalculObs(vec3 vrp, double d, double angx, double angy)
{
 vec3 obs;
 vec3 v;
 double norma;

 //printf("ENTRE A CALCULOBS\n");
 /* Calcul del vector de visio a partir dels angles */

// v[0] = sin (PI*angy/180.) * cos (PI*angx/180.);
// v[2] = cos (PI*angy/180.) * cos (PI*angx/180.);
// v[1]= -sin (PI*angx/180.);

 v[0] = sin (PI*angy/180.) * cos (PI*angx/180.);
 v[2] = cos (PI*angy/180.) * cos (PI*angx/180.);
 v[1]= -sin (PI*angx/180.);


 norma = sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);

 v[0] = v[0]/norma;
 v[1] = v[1]/norma;
 v[2] = v[2]/norma;


 obs[0] = vrp[0] + v[0] *d;
 obs[1] = vrp[1] + v[1] *d;
 obs[2] = vrp[2] + v[2] *d;
 return(obs);

}


vec3 Camera::CalculVup(double angx, double angy, double angz)
{
  vec3 v;
  int   x, y;

  x = 1.0;
  y = 1.0;
  //printf("ENTRE A CALCULVUP\n");

  if (cos(PI*angx/180.)<0.0) y = -1.0;

  if (cos(PI*angy/180.)<0.0) x = -1.0;


  v[0] = x*sin (-PI*angz/180.);
  v[1] = y*cos( -PI*angz/180.);
  v[2] = 0.0;

  return(v);

}



void Camera::VertexCapsa3D(Capsa3D capsaMinima, vec4 vaux[8])
{
    vec3 ptfi;

    ptfi[0] = capsaMinima.pmin[0]+capsaMinima.a;
    ptfi[1] = capsaMinima.pmin[1]+capsaMinima.h;
    ptfi[2] = capsaMinima.pmin[2]+capsaMinima.p;

    vaux[0][0] = capsaMinima.pmin[0]; vaux[0][1] = capsaMinima.pmin[1]; vaux[0][2] = capsaMinima.pmin[2]; vaux[0][3] = 1;
    vaux[1][0] = capsaMinima.pmin[0]; vaux[1][1] = capsaMinima.pmin[1]; vaux[1][2] = ptfi[2]; vaux[1][3] = 1;
    vaux[2][0] = capsaMinima.pmin[0]; vaux[2][1] = ptfi[1]; vaux[2][2] = capsaMinima.pmin[2];vaux[2][3] = 1;
    vaux[3][0] = capsaMinima.pmin[0]; vaux[3][1] = ptfi[1]; vaux[3][2] = ptfi[2];vaux[3][3] = 1;
    vaux[4][0] = ptfi[0]; vaux[4][1] = capsaMinima.pmin[1]; vaux[4][2] = capsaMinima.pmin[2];vaux[4][3] = 1;
    vaux[5][0] = ptfi[0]; vaux[5][1] = capsaMinima.pmin[1]; vaux[5][2] = ptfi[2];vaux[5][3] = 1;
    vaux[6][0] = ptfi[0]; vaux[6][1] = ptfi[1]; vaux[6][2] = capsaMinima.pmin[2];vaux[6][3] = 1;
    vaux[7][0] = ptfi[0]; vaux[7][1] = ptfi[1]; vaux[7][2] = ptfi[2];vaux[7][3] = 1;
}
