/************************************/
/* ArcBall.c (c) Ken Shoemake, 1993 */
/* Modified by Tom Bech, 1996       */
/************************************/

#include "config.h"

#include <libgimp/gimp.h>

#include "arcball.h"

/* Gloval variables */
/* ================ */

Quat qOne = { 0, 0, 0, 1 };

static HVect         center;
static double        radius;
static Quat          qNow, qDown, qDrag;
static HVect         vNow, vDown, vFrom, vTo, vrFrom, vrTo;
static HMatrix       mNow, mDown;
static unsigned int  showResult, dragging;
static ConstraintSet sets[NSets];
static int           setSizes[NSets];
static AxisSet       axisSet;
static int           axisIndex;

static HMatrix mId =
{
  { 1, 0, 0, 0 },
  { 0, 1, 0, 0 },
  { 0, 0, 1, 0 },
  { 0, 0, 0, 1 }
};

static double otherAxis[][4] =
{
  {-0.48, 0.80, 0.36, 1}
};

/* Internal methods */
/* ================ */

static void     Qt_ToMatrix(Quat q,HMatrix out);
static Quat     Qt_Conj(Quat q);
static Quat     Qt_Mul(Quat qL, Quat qR);
static Quat     Qt_FromBallPoints(HVect from, HVect to);
static void     Qt_ToBallPoints(Quat q, HVect *arcFrom, HVect *arcTo);

static HVect    V3_(double x, double y, double z);
static double   V3_Norm(HVect v);
static HVect    V3_Unit(HVect v);
static HVect    V3_Scale(HVect v, double s);
static HVect    V3_Negate(HVect v);
/*
static HVect    V3_Add(HVect v1, HVect v2);
*/
static HVect    V3_Sub(HVect v1, HVect v2);
static double   V3_Dot(HVect v1, HVect v2);
/*
static HVect    V3_Cross(HVect v1, HVect v2);
static HVect    V3_Bisect(HVect v0, HVect v1);
*/

static HVect    MouseOnSphere(HVect mouse, HVect ballCenter, double ballRadius);
static HVect    ConstrainToAxis(HVect loose, HVect axis);
static int      NearestConstraintAxis(HVect loose, HVect *axes, int nAxes);

/* Establish reasonable initial values for controller. */
/* =================================================== */

void
ArcBall_Init (void)
{
  int i;

  center = qOne;
  radius = 1.0;
  vDown = vNow = qOne;
  qDown = qNow = qOne;
  for (i=15; i>=0; i--)
    ((double *)mNow)[i] = ((double *)mDown)[i] = ((double *)mId)[i];

  showResult = dragging = FALSE;
  axisSet = NoAxes;
  sets[CameraAxes] = mId[X];
  setSizes[CameraAxes] = 3;
  sets[BodyAxes] = mDown[X];
  setSizes[BodyAxes] = 3;
  sets[OtherAxes] = otherAxis[X];
  setSizes[OtherAxes] = 1;
}

/* Set the center and size of the controller. */
/* ========================================== */

void
ArcBall_Place (HVect  Center,
               double Radius)
{
  center = Center;
  radius = Radius;
}

/* Incorporate new mouse position. */
/* =============================== */

void
ArcBall_Mouse (HVect v_Now)
{
  vNow = v_Now;
}

/* Choose a constraint set, or none. */
/* ================================= */

void
ArcBall_UseSet (AxisSet axis_Set)
{
  if (!dragging) axisSet = axis_Set;
}

/* Using vDown, vNow, dragging, and axisSet, compute rotation etc. */
/* =============================================================== */

void
ArcBall_Update (void)
{
  int setSize = setSizes[axisSet];
  HVect *set = (HVect *)(sets[axisSet]);

  vFrom = MouseOnSphere(vDown, center, radius);
  vTo = MouseOnSphere(vNow, center, radius);
  if (dragging)
    {
      if (axisSet!=NoAxes)
        {
          vFrom = ConstrainToAxis(vFrom, set[axisIndex]);
          vTo = ConstrainToAxis(vTo, set[axisIndex]);
        }
      qDrag = Qt_FromBallPoints(vFrom, vTo);
      qNow = Qt_Mul(qDrag, qDown);
    }
  else
    {
      if (axisSet!=NoAxes) axisIndex = NearestConstraintAxis(vTo, set, setSize);
    }
  Qt_ToBallPoints(qDown, &vrFrom, &vrTo);
  Qt_ToMatrix(Qt_Conj(qNow), mNow); /* Gives transpose for GL. */
}

/* Return rotation matrix defined by controller use. */
/* ================================================= */

void
ArcBall_Value (HMatrix m_Now)
{
  ArcBall_CopyMat (mNow, m_Now);
}

/* Extract rotation angles from matrix */
/* =================================== */

void
ArcBall_Values (double *alpha,
                double *beta,
                double *gamma)
{
  if ((*beta=asin(-mNow[0][2]))!=0.0)
    {
      *gamma=atan2(mNow[1][2],mNow[2][2]);
      *alpha=atan2(mNow[0][1],mNow[0][0]);
    }
  else
    {
      *gamma=atan2(mNow[1][0],mNow[1][1]);
      *alpha=0.0;
    }
}

/* Begin drag sequence. */
/* ==================== */

void
ArcBall_BeginDrag (void)
{
  dragging = TRUE;
  vDown = vNow;
}

/* Stop drag sequence. */
/* =================== */

void
ArcBall_EndDrag (void)
{
  dragging = FALSE;
  qDown = qNow;

  ArcBall_CopyMat (mNow, mDown);
}

/*===================*/
/***** BallAux.c *****/
/*===================*/

/* Return quaternion product qL * qR.  Note: order is important! */
/* To combine rotations, use the product Mul(qSecond, qFirst),   */
/* which gives the effect of rotating by qFirst then qSecond.    */
/* ============================================================= */

static Quat
Qt_Mul (Quat qL,
        Quat qR)
{
  Quat qq;
  qq.w = qL.w*qR.w - qL.x*qR.x - qL.y*qR.y - qL.z*qR.z;
  qq.x = qL.w*qR.x + qL.x*qR.w + qL.y*qR.z - qL.z*qR.y;
  qq.y = qL.w*qR.y + qL.y*qR.w + qL.z*qR.x - qL.x*qR.z;
  qq.z = qL.w*qR.z + qL.z*qR.w + qL.x*qR.y - qL.y*qR.x;
  return (qq);
}

/* Construct rotation matrix from (possibly non-unit) quaternion. */
/* Assumes matrix is used to multiply column vector on the left:  */
/* vnew = mat vold.  Works correctly for right-handed coordinate  */
/* system and right-handed rotations.                             */
/* ============================================================== */

static void
Qt_ToMatrix (Quat    q,
             HMatrix out)
{
  double Nq = q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w;
  double s = (Nq > 0.0) ? (2.0 / Nq) : 0.0;
  double xs = q.x*s,              ys = q.y*s,          zs = q.z*s;
  double wx = q.w*xs,              wy = q.w*ys,          wz = q.w*zs;
  double xx = q.x*xs,              xy = q.x*ys,          xz = q.x*zs;
  double yy = q.y*ys,              yz = q.y*zs,          zz = q.z*zs;
  out[X][X] = 1.0 - (yy + zz); out[Y][X] = xy + wz; out[Z][X] = xz - wy;
  out[X][Y] = xy - wz; out[Y][Y] = 1.0 - (xx + zz); out[Z][Y] = yz + wx;
  out[X][Z] = xz + wy; out[Y][Z] = yz - wx; out[Z][Z] = 1.0 - (xx + yy);
  out[X][W] = out[Y][W] = out[Z][W] = out[W][X] = out[W][Y] = out[W][Z] = 0.0;
  out[W][W] = 1.0;
}

/* Return conjugate of quaternion. */
/* =============================== */

static Quat
Qt_Conj (Quat q)
{
  Quat qq;
  qq.x = -q.x; qq.y = -q.y; qq.z = -q.z; qq.w = q.w;
  return (qq);
}

/* Return vector formed from components */
/* ==================================== */

static HVect
V3_ (double x,
     double y,
     double z)
{
  HVect v;
  v.x = x; v.y = y; v.z = z; v.w = 0;
  return (v);
}

/* Return norm of v, defined as sum of squares of components */
/* ========================================================= */

static double
V3_Norm (HVect v)
{
  return ( v.x*v.x + v.y*v.y + v.z*v.z );
}

/* Return unit magnitude vector in direction of v */
/* ============================================== */

static HVect
V3_Unit (HVect v)
{
  static HVect u = {0, 0, 0, 0};
  double vlen = sqrt(V3_Norm(v));

  if (vlen != 0.0) u.x = v.x/vlen; u.y = v.y/vlen; u.z = v.z/vlen;
  return (u);
}

/* Return version of v scaled by s */
/* =============================== */

static HVect
V3_Scale (HVect v,
          double s)
{
  HVect u;
  u.x = s*v.x; u.y = s*v.y; u.z = s*v.z; u.w = v.w;
  return (u);
}

/* Return negative of v */
/* ==================== */

static HVect
V3_Negate (HVect v)
{
  static HVect u = {0, 0, 0, 0};
  u.x = -v.x; u.y = -v.y; u.z = -v.z;
  return (u);
}

/* Return sum of v1 and v2 */
/* ======================= */
/*
static HVect
V3_Add (HVect v1,
        HVect v2)
{
  static HVect v = {0, 0, 0, 0};
  v.x = v1.x+v2.x; v.y = v1.y+v2.y; v.z = v1.z+v2.z;
  return (v);
}
*/
/* Return difference of v1 minus v2 */
/* ================================ */

static HVect
V3_Sub (HVect v1,
        HVect v2)
{
  static HVect v = {0, 0, 0, 0};
  v.x = v1.x-v2.x; v.y = v1.y-v2.y; v.z = v1.z-v2.z;
  return (v);
}

/* Halve arc between unit vectors v0 and v1. */
/* ========================================= */
/*
static HVect
V3_Bisect (HVect v0,
           HVect v1)
{
  HVect v = {0, 0, 0, 0};
  double Nv;

  v = V3_Add(v0, v1);
  Nv = V3_Norm(v);
  if (Nv < 1.0e-5) v = V3_(0, 0, 1);
  else v = V3_Scale(v, 1/sqrt(Nv));
  return (v);
}
*/

/* Return dot product of v1 and v2 */
/* =============================== */

static double
V3_Dot (HVect v1,
        HVect v2)
{
  return (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z);
}


/* Return cross product, v1 x v2 */
/* ============================= */
/*
static HVect
V3_Cross (HVect v1,
          HVect v2)
{
  static HVect v = {0, 0, 0, 0};
  v.x = v1.y*v2.z-v1.z*v2.y;
  v.y = v1.z*v2.x-v1.x*v2.z;
  v.z = v1.x*v2.y-v1.y*v2.x;
  return (v);
}
*/

void
ArcBall_CopyMat (HMatrix inm,
                 HMatrix outm)
{
  int x=0,y=0;

  for (x=0;x<4;x++)
    {
      for (y=0;y<4;y++)
        {
          outm[y][x]=inm[y][x];
        }
    }
}

/*=====================================================*/
/**** BallMath.c - Essential routines for ArcBall.  ****/
/*=====================================================*/

/* Convert window coordinates to sphere coordinates. */
/* ================================================= */

static HVect
MouseOnSphere (HVect  mouse,
               HVect  ballCenter,
               double ballRadius)
{
  HVect ballMouse;
  register double mag;

  ballMouse.x = (mouse.x - ballCenter.x) / ballRadius;
  ballMouse.y = (mouse.y - ballCenter.y) / ballRadius;
  mag = ballMouse.x*ballMouse.x + ballMouse.y*ballMouse.y;
  if (mag > 1.0)
    {
      register double scale = 1.0/sqrt(mag);
      ballMouse.x *= scale; ballMouse.y *= scale;
      ballMouse.z = 0.0;
    }
  else ballMouse.z = sqrt(1 - mag);
  ballMouse.w = 0.0;
  return (ballMouse);
}

/* Construct a unit quaternion from two points on unit sphere */
/* ========================================================== */

static Quat
Qt_FromBallPoints (HVect from,
                   HVect to)
{
  Quat qu;
  qu.x = from.y*to.z - from.z*to.y;
  qu.y = from.z*to.x - from.x*to.z;
  qu.z = from.x*to.y - from.y*to.x;
  qu.w = from.x*to.x + from.y*to.y + from.z*to.z;
  return (qu);
}

/* Convert a unit quaternion to two points on unit sphere */
/* ====================================================== */

static void
Qt_ToBallPoints (Quat   q,
                 HVect *arcFrom,
                 HVect *arcTo)
{
  double s;

  s = sqrt(q.x*q.x + q.y*q.y);
  if (s == 0.0) *arcFrom = V3_(0.0, 1.0, 0.0);
  else          *arcFrom = V3_(-q.y/s, q.x/s, 0.0);
  arcTo->x = q.w*arcFrom->x - q.z*arcFrom->y;
  arcTo->y = q.w*arcFrom->y + q.z*arcFrom->x;
  arcTo->z = q.x*arcFrom->y - q.y*arcFrom->x;
  if (q.w < 0.0) *arcFrom = V3_(-arcFrom->x, -arcFrom->y, 0.0);
}

/* Force sphere point to be perpendicular to axis. */
/* =============================================== */

static HVect
ConstrainToAxis (HVect loose,
                 HVect axis)
{
  HVect onPlane;
  register double norm;

  onPlane = V3_Sub(loose, V3_Scale(axis, V3_Dot(axis, loose)));
  norm = V3_Norm(onPlane);
  if (norm > 0.0)
    {
      if (onPlane.z < 0.0) onPlane = V3_Negate(onPlane);
      return ( V3_Scale(onPlane, 1/sqrt(norm)) );
    }
  /* else drop through */
  /* ================= */

  if (axis.z == 1) onPlane = V3_(1.0, 0.0, 0.0);
  else             onPlane = V3_Unit(V3_(-axis.y, axis.x, 0.0));
  return (onPlane);
}

/* Find the index of nearest arc of axis set. */
/* ========================================== */

static int
NearestConstraintAxis (HVect  loose,
                       HVect *axes,
                       int    nAxes)
{
  HVect onPlane;
  register double max, dot;
  register int i, nearest;
  max = -1; nearest = 0;

  for (i=0; i<nAxes; i++)
    {
      onPlane = ConstrainToAxis(loose, axes[i]);
      dot = V3_Dot(onPlane, loose);
      if (dot>max)
        {
          max = dot; nearest = i;
        }
    }
  return (nearest);
}
