/* Implementações do modulo ARCBALL
 *
 * Autor: Marcelo Gattass
 * Data: 15 de novembro de 2005
 *
 * O modulo contem duas implementacoes quase equivalentes: 
 * (a) uma com base na matriz de rotacao em torno de um eixo qualquer;
 * (b) outra com base de quaternios.
 *
 * A unica diferenca e' qua a de quaternios roda o dobro para o mesmo 
 * movimento de mouse.
 * 
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include <windows.h>                /* inclui as definicoes do windows para o OpenGL */
#include <gl/gl.h>                  /* prototypes do OpenGL */
#include <gl/glu.h>                 /* prototypes do OpenGL */

#include "arcball.h"
#include "algebra.h"


struct _arcball {
  /* Centro do arcball (em coordenadas do canvas) */
  double center_x, center_y; 

  /* Raio do arcball  (em coordenadas do canvas) */
  double radius;

  /* screen x y and z Vectors in object space */
  Vector ex,ey,ez;
};

Arcball* arcballInit(int xc, int yc, int radius, Vector ex, Vector ey, Vector ez )
{
   Arcball* arc=(Arcball*) malloc(sizeof(Arcball));
   arc->center_x=(double) xc;
   arc->center_y=(double) yc;
   arc->radius=(double) radius;
   arc->ex=ex;
   arc->ey=ey;
   arc->ez=ez;
   return arc;
}



void arcballDestroi(Arcball* arc) {
   if (arc) free(arc);
}


static Quat screenToArcsphere(Arcball *arc, int x, int y) 
{
 double vx, vy, vz, r;
 double s;

 vx = ( x - arc->center_x) / arc->radius;
 vy = ( y - arc->center_y) / arc->radius;

 r = vx * vx + vy * vy;

 if (r > 1) {	
		s = 1/sqrt(r);
      vx *= s;
      vy *= s;
      vz = 0.0;
    }
    else
       vz = sqrt(1 - r);

 return algQuat(0., vx, vy, vz );
}

static Vector screenToSphere(Arcball* arc, int x, int y)
{
 double vx, vy, vz, r;
 double s;

 vx = ( x - arc->center_x) / arc->radius;
 vy = ( y - arc->center_y) / arc->radius;

 r = vx * vx + vy * vy;

 if (r > 1) {	
		s = 1/sqrt(r);
      vx *= s;
      vy *= s;
      vz = 0.0;
    }
    else
       vz = sqrt(1 - r);

 return algVector( vx, vy, vz ,1.);
}

Matrix arcballRotMatrix(Arcball* arc, int x0, int y0, int x1, int y1)
{
   Vector v0   = screenToSphere(arc, x0, y0);
   Vector v1   = screenToSphere(arc, x1, y1);
   Vector axis = algCross(v0,v1);
   double cos  = algDot(v0,v1);
   double sin  = algNorm(axis);
   Matrix R,Rt;

   axis  = algScale(1./sin, axis);
   if (1)  /* se tiver que fazer uma correcao de eixos */
   {
      double a1=axis.x, a2=axis.y, a3=axis.z;
      axis = algLinComb(3,a1,arc->ex,a2,arc->ey,a3,arc->ez);
   }
   R=algMatrixRotateCS(cos,sin,axis.x,axis.y,axis.z);
   Rt=algTransp(R);
   arc->ex=algTransf(Rt,arc->ex);
   arc->ey=algTransf(Rt,arc->ey);
   arc->ez=algTransf(Rt,arc->ez);

   return R;
}

 
Quat arcballRotQuat(Arcball *arc, int x0, int y0, int x1, int y1) 
{
  Quat q0 = screenToArcsphere(arc, x0, y0);
  Quat q1 = screenToArcsphere(arc, x1, y1);
  Quat q  = algQuatMult(q1,algQuatConj(q0));
  Matrix  Rt;

  if (1)   /* se tiver que fazer uma correcao de eixos */
  {
   Vector axis=algQuatRotAxis(q);
   double ang=algQuatRotAngle(q);
   double a1=axis.x, a2=axis.y, a3=axis.z;
   axis = algLinComb(3,a1,arc->ex,a2,arc->ey,a3,arc->ez);
   q = algQuatFromRot(ang,axis);
  }
  Rt=algTransp(algQuatToMatrix(q));
  arc->ex=algTransf(Rt,arc->ex);
  arc->ey=algTransf(Rt,arc->ey);
  arc->ez=algTransf(Rt,arc->ez);

  return q;
} 



static void DrawCircle(double xc, double yc,double r)
{
  double x, y;
  double dt = 2*3.1416/32.0;
  double t;
  int i;
  glColor3d(0.5,0.5,0.5);
  glBegin(GL_LINE_LOOP);
  for(i=0, t=0.; i < 32; i++, t += dt) {
    x = xc + r * cos(t);
    y = yc + r * sin(t);
    glVertex2d(x,y);
  }
  glEnd();
}

void arcballDraw(Arcball* arc, int width, int height)
{
   glMatrixMode(GL_MODELVIEW);
   glPushMatrix();
   glLoadIdentity();
 
   glMatrixMode(GL_PROJECTION);
   glPushMatrix();
   glLoadIdentity();
   gluOrtho2D(0.,width,0.,height);

   DrawCircle(arc->center_x,arc->center_y,arc->radius);

   glPopMatrix();
   
   glMatrixMode(GL_MODELVIEW);
   glPopMatrix();
}
