// update current arcball rotation
void arcball_move(int mx, int my)
{
  if(ab_planar)
  {
    ab_curr = planar_coords((GLdouble)mx,(GLdouble)my);
    if(ab_curr.equals(ab_start)) return;
    
    // d is motion since the last position
    vec d = ab_curr - ab_start;
    
    GLfloat angle = d.length() * 0.5;
    GLfloat cosa = cos( angle );
    GLfloat sina = sin( angle );
    // p is perpendicular to d
    vec p = ((ab_out*d.x)-(ab_up*d.y)).unit() * sina;

    quaternion(ab_next,p.x,p.y,p.z,cosa);
    quatnext(ab_quat,ab_last,ab_next);
    // planar style only ever relates to the last point
    quatcopy(ab_last,ab_quat);
    ab_start = ab_curr;
    
  } else {

    ab_curr = sphere_coords((GLdouble)mx,(GLdouble)my);
    if(ab_curr.equals(ab_start))
    { // avoid potential rare divide by tiny
      quatcopy(ab_quat,ab_last);
      return;
    }

    // use a dot product to get the angle between them
    // use a cross product to get the vector to rotate around
    GLfloat cos2a = ab_start*ab_curr;
    GLfloat sina = sqrt((1.0 - cos2a)*0.5);
    GLfloat cosa = sqrt((1.0 + cos2a)*0.5);
    vec cross = (ab_start^ab_curr).unit() * sina;
    quaternion(ab_next,cross.x,cross.y,cross.z,cosa);

    // update the rotation matrix
    quatnext(ab_quat,ab_last,ab_next);
  }
}