/**********************************************************************

  arcball.cpp


          --------------------------------------------------

  GLUI User Interface Toolkit (LGPL)
  Copyright (c) 1998 Paul Rademacher
     Feb 1998, Paul Rademacher (rademach@cs.unc.edu)
     Oct 2003, Nigel Stewart - GLUI Code Cleaning

  WWW:    http://sourceforge.net/projects/glui/
  Forums: http://sourceforge.net/forum/?group_id=92496

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

**********************************************************************/

#include "arcball.h"

#include <cstdio>


/**************************************** Arcball::Arcball() ****/
/* Default (void) constructor for Arcball                         */

Arcball::Arcball() 
{
    rot_ptr = &rot;
    init();
}

/**************************************** Arcball::Arcball() ****/
/* Takes as argument a mat4 to use instead of the internal rot  */

Arcball::Arcball(mat4 *mtx) 
{
    rot_ptr = mtx;
}


/**************************************** Arcball::Arcball() ****/
/* A constructor that accepts the screen center and arcball radius*/

Arcball::Arcball(const vec2 &_center, float _radius)
{
    rot_ptr = &rot;
    init();
    set_params(_center, _radius);
}


/************************************** Arcball::set_params() ****/

void Arcball::set_params(const vec2 &_center, float _radius)
{
    center      = _center;
    radius      = _radius;
}

/*************************************** Arcball::init() **********/

void Arcball::init()
{
    center.set( 0.0, 0.0 );
    radius         = 1.0;
    q_now          = quat_identity();
    *rot_ptr       = identity3D();
    q_increment    = quat_identity();
    rot_increment  = identity3D();
    is_mouse_down  = false;
    is_spinning    = false;
    damp_factor    = 0.0;
    zero_increment = true;
}

/*********************************** Arcball::mouse_to_sphere() ****/

vec3 Arcball::mouse_to_sphere(const vec2 &p)
{
    float mag;
    vec2  v2 = (p - center) / radius;
    vec3  v3( v2[0], v2[1], 0.0 );

    mag = v2*v2;

    if ( mag > 1.0 ) 
        v3.normalize();
    else 
        v3[VZ] = (float) sqrt( 1.0 - mag );

    /* Now we add constraints - X takes precedence over Y */
    if ( constraint_x ) 
    {
        v3 = constrain_vector( v3, vec3( 1.0, 0.0, 0.0 ));
    } 
    else if ( constraint_y ) 
        {
            v3 = constrain_vector( v3, vec3( 0.0, 1.0, 0.0 ));
        }

    return v3;
}


/************************************ Arcball::constrain_vector() ****/

vec3 Arcball::constrain_vector(const vec3 &vector, const vec3 &axis)
{
    return (vector-(vector*axis)*axis).normalize();
}

/************************************ Arcball::mouse_down() **********/

void Arcball::mouse_down(int x, int y)
{
    down_pt.set( (float)x, (float) y );
    is_mouse_down = true;

    q_increment   = quat_identity();
    rot_increment = identity3D();
    zero_increment = true;
}


/************************************ Arcball::mouse_up() **********/

void Arcball::mouse_up()
{
    q_now = q_drag * q_now;
    is_mouse_down = false;
}


/********************************** Arcball::mouse_motion() **********/

void Arcball::mouse_motion(int x, int y, int shift, int ctrl, int alt)
{
    /* Set the X constraint if CONTROL key is pressed, Y if ALT key */
    set_constraints( ctrl != 0, alt != 0 );

    vec2 new_pt( (float)x, (float) y );
    vec3 v0 = mouse_to_sphere( down_pt );
    vec3 v1 = mouse_to_sphere( new_pt );

    vec3 cross = v0^v1;

    q_drag.set( cross, v0 * v1 );

    //    *rot_ptr = (q_drag * q_now).to_mat4();
    mat4 temp = q_drag.to_mat4();
    *rot_ptr = *rot_ptr * temp;

    down_pt = new_pt;

    /* We keep a copy of the current incremental rotation (= q_drag) */
    q_increment   = q_drag;
    rot_increment = q_increment.to_mat4();

    set_constraints(false, false);

    if ( q_increment.s < .999999 ) 
    {
        is_spinning = true;
        zero_increment = false;
    }
    else 
    {
        is_spinning = false;
        zero_increment = true;
    }
}


/********************************** Arcball::mouse_motion() **********/

void Arcball::mouse_motion(int x, int y)
{
    mouse_motion(x, y, 0, 0, 0);
}


/***************************** Arcball::set_constraints() **********/

void Arcball::set_constraints(bool _constraint_x, bool _constraint_y)
{
    constraint_x = _constraint_x;
    constraint_y = _constraint_y;
}

/***************************** Arcball::idle() *********************/

void Arcball::idle()
{
    if (is_mouse_down) 
    {
        is_spinning = false;
        zero_increment = true;
    }

    if (damp_factor < 1.0f) 
        q_increment.scale_angle(1.0f - damp_factor);

    rot_increment = q_increment.to_mat4();

    if (q_increment.s >= .999999f) 
    {
        is_spinning = false;
        zero_increment = true;
    }
}


/************************ Arcball::set_damping() *********************/

void Arcball::set_damping(float d)
{
    damp_factor = d;
}





