Created
May 15, 2013 12:45
-
-
Save anonymous/5583749 to your computer and use it in GitHub Desktop.
Arcball implementation in C++
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define _USE_MATH_DEFINES | |
#include <math.h> | |
#include <iostream> | |
#include <Eigen/Core> | |
#include <Eigen/Geometry> | |
#include "Arcball.hpp" | |
using namespace std; | |
using namespace Eigen; | |
/** | |
* \ingroup GLVisualization | |
* Default constructor, it sets the ballRadius to 600 | |
**/ | |
Arcball::Arcball() | |
{ this->ballRadius=600; | |
isRotating=false; | |
width=height=0; | |
reset(); | |
} | |
/** | |
* \ingroup GLVisualization | |
* Set width and height of the current windows, it's needed every time you resize the window | |
* \param w Width of the rendering window | |
* \param h Height of the rendering window | |
**/ | |
void Arcball::setWidthHeight(int w, int h) | |
{ width=w; | |
height=h; | |
ballRadius = min((int)(w/2), (int)(h/2)); | |
} | |
/** | |
* \ingroup GLVisualization | |
* Set the radius of the ball (a typical radius for a 1024x768 window is 600 | |
* \param newRadius The radius of the spherical dragging area | |
**/ | |
void Arcball::setRadius(float newRadius) | |
{ ballRadius = newRadius; | |
} | |
/** | |
* \ingroup GLVisualization | |
* Start the rotation. Use this method in association with the left click. | |
* Here you must give directly the coordinates of the mouse as the glut functions extract. This method supposes that the 0,0 is in the upper-left part of the screen | |
* \param _x Horizontal position of the mouse (0,0) = upperleft corner (w,h) = lower right | |
* \param _y Vertical position of the mouse (0,0) = upperleft corner (w,h) = lower right | |
* | |
**/ | |
void Arcball::startRotation(int _x, int _y) | |
{ int x = ( (_x)-(width/2) ); | |
int y = ((height/2)-_y); | |
startRotationVector = convertXY(x,y); | |
startRotationVector.normalize(); | |
currentRotationVector= startRotationVector; | |
isRotating = true; | |
} | |
/** | |
* \ingroup GLVisualization | |
* Update the rotation. Use this method in association with the drag event. | |
* Here you must give directly the coordinates of the mouse as the glut functions extract. This method supposes that the 0,0 is in the upper-left part of the screen | |
* \param _x Horizontal position of the mouse (0,0) = upperleft corner (w,h) = lower right | |
* \param _y Vertical position of the mouse (0,0) = upperleft corner (w,h) = lower right | |
**/ | |
void Arcball::updateRotation(int _x, int _y) | |
{ int x = ( (_x)-(width/2) ); | |
int y = ((height/2)-_y); | |
currentRotationVector = convertXY(x,y); | |
currentRotationVector.normalize(); | |
} | |
/** | |
* \ingroup GLVisualization | |
* Apply the computed rotation matrix | |
* This method must be invoked inside the \code glutDisplayFunc() \endcode | |
* | |
**/ | |
void Arcball::applyRotationMatrix() | |
{ if (isRotating) | |
{ // Do some rotation according to start and current rotation vectors | |
//cerr << currentRotationVector.transpose() << " " << startRotationVector.transpose() << endl; | |
if ( ( currentRotationVector - startRotationVector).norm() > 1E-6 ) | |
{ Vector3d rotationAxis = currentRotationVector.cross(startRotationVector); | |
rotationAxis.normalize(); | |
double val = currentRotationVector.dot(startRotationVector); | |
val > (1-1E-10) ? val=1.0 : val=val ; | |
double rotationAngle = acos(val) * 180.0f/(float)M_PI; | |
// rotate around the current position | |
applyTranslationMatrix(true); | |
glRotatef(rotationAngle * 2, -rotationAxis.x(), -rotationAxis.y(),-rotationAxis.z()); | |
applyTranslationMatrix(false); | |
} | |
} | |
glMultMatrixf(startMatrix); | |
} | |
/** | |
* \ingroup GLVisualization | |
* Stop the current rotation and prepare for a new click-then-drag event | |
* | |
**/ | |
void Arcball::stopRotation() | |
{ | |
glMatrixMode(GL_MODELVIEW); | |
glLoadIdentity(); | |
applyRotationMatrix(); | |
// set the current matrix as the permanent one | |
glGetFloatv(GL_MODELVIEW_MATRIX, startMatrix); | |
isRotating = false; | |
} | |
/** | |
* \ingroup GLVisualization | |
* Apply the translation matrix to the current transformation (zoom factor) | |
**/ | |
void Arcball::applyTranslationMatrix(bool reverse) | |
{ float factor = (reverse?-1.0f:1.0f); | |
float tx = transX + (currentTransX - startTransX)*TRANSLATION_FACTOR; | |
float ty = transY + (currentTransY - startTransY)*TRANSLATION_FACTOR; | |
glTranslatef(factor*tx, factor*(-ty), 0); | |
} | |
/** | |
* \ingroup GLVisualization | |
* Maps the mouse coordinates to points on a sphere, if the points lie outside the sphere, the z is 0, otherwise is \f$ \sqrt(r^2 - (x^2+y^2) ) \f$ where \f$ x,y \f$ | |
* are the window centric coordinates of the mouse | |
* \param x Mouse x coordinate | |
* \param y Mouse y coordinate | |
**/ | |
Vector3d Arcball::convertXY(int x, int y) | |
{ | |
int d = x*x+y*y; | |
float radiusSquared = ballRadius*ballRadius; | |
if (d > radiusSquared) | |
{ return Vector3d((float)x,(float)y, 0 ); | |
} | |
else | |
{ return Vector3d((float)x,(float)y, sqrt(radiusSquared - d)); | |
} | |
} | |
/** | |
* \ingroup GLVisualization | |
* Reset the current transformation to the identity | |
**/ | |
void Arcball::reset() | |
{ fov = INITIAL_FOV; | |
// reset matrix | |
memset(startMatrix, 0, sizeof(startMatrix)); | |
startMatrix[0] = 1; | |
startMatrix[1] =0; | |
startMatrix[2] = 0; | |
startMatrix[3] = 0; | |
startMatrix[4] = 0; | |
startMatrix[5] =1; | |
startMatrix[6] = 0; | |
startMatrix[7] = 0; | |
startMatrix[8] = 0; | |
startMatrix[9] =0; | |
startMatrix[10] = 1; | |
startMatrix[11] = 0; | |
startMatrix[12] = 0; | |
startMatrix[13] =0; | |
startMatrix[14] = 0; | |
startMatrix[15] = 1; | |
transX = transY = 0; | |
startTransX = startTransY = currentTransX = currentTransY = 0; | |
} | |
const float Arcball::INITIAL_FOV = 30; | |
const float Arcball::TRANSLATION_FACTOR = 0.01f; | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef ARCBALL_H | |
#define ARCBALL_H | |
#ifdef __APPLE__ | |
#include <OpenGL/OpenGL.h> | |
#endif | |
#ifdef __linux__ | |
#include <GL/gl.h> | |
#include <GL/glu.h> | |
#endif | |
#ifdef _WIN32 | |
#ifndef NOMINMAX | |
#define NOMINMAX | |
#endif | |
#include <windows.h> | |
#include <gl\gl.h> // Header File For The OpenGL32 Library | |
#include <gl\glu.h> // Header File For The GLu32 Library | |
#endif | |
#include <Eigen/Core> | |
#include <Eigen/Geometry> | |
using namespace Eigen; | |
/* | |
* \class Arcball | |
* \ingroup GLVisualization | |
* \brief Arcball is a method to manipulate and rotate objects in 3D intuitively. | |
* | |
* The motivation behind the trackball (aka arcball) is to provide an intuitive user interface for complex 3D | |
* object rotation via a simple, virtual sphere - the screen space analogy to the familiar input device bearing | |
* the same name. | |
* | |
* The sphere is a good choice for a virtual trackball because it makes a good enclosure for most any object; | |
* and its surface is smooth and continuous, which is important in the generation of smooth rotations in response | |
* to smooth mouse movements. | |
* Any smooth, continuous shape, however, could be used, so long as points on its surface can be generated | |
* in a consistent way. | |
* The algorithm for accomplishing this, needs to perform the following steps (not neseccarily in order). | |
* | |
* | |
* | |
* | |
* \b ArcBall Algorithm made easy | |
* - Detect the left-button of the mouse being depressed. | |
* - Keep track of the last known mouse position. | |
* - Treat the mouse position as the projection of a point on the hemi-sphere down to the image plane (along the z-axis), and determine that point on the hemi-sphere. | |
* - Detect the mouse movement | |
* - Determine the great circle connecting the old mouse-hemi-sphere point to the current mouse-hemi-sphere point. | |
* - Calculate the normal to this plane. This will be the axis about which to rotate. | |
* - Set the OpenGL state to modify the MODELVIEW matrix. | |
* - Read off the current matrix, since we want this operation to be the last transformation, not the first, and OpenGL does things LIFO. | |
* - Reset the model-view matrix to the identity | |
* - Rotate about the axis | |
* - Multiply the resulting matrix by the saved matrix. | |
* - Force a redraw of the scene. | |
* | |
* An example of the code for using successfully this class is the following: | |
* First set the radius of the arcball by using the appropriate method when you call the handle to the GLUT resize method (this is usually accomplished with a | |
* \code | |
* void handleResize(int w, int h) | |
* \endcode | |
* function in GLUT (if you are using Qt or other, please refer to the relative API ). | |
* An example of a \code handleResize(int w, int h) \endcode function is the following: | |
* \code | |
* void handleResize(int w, int h) | |
* { | |
* arcball.setWidthHeight(w, h); | |
* glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
* glViewport(0, 0, w, h); | |
* | |
* glMatrixMode(GL_PROJECTION); | |
* glLoadIdentity(); | |
* gluPerspective(viewAngle, (float)w / (float)w, zNear, zFar ); | |
* | |
* glMatrixMode(GL_MODELVIEW); | |
* } | |
* \endcode | |
* | |
* | |
These are the two functions (GLUT) that control the mouse events: | |
* \code | |
* void mouseFunc(int state, int button, int _x , int _y) | |
* { | |
* if ( button == GLUT_LEFT_BUTTON ) | |
* arcball.startRotation(_x,_y); | |
* else | |
* arcball.stopRotation(); | |
* | |
* | |
* glutPostRedisplay(); | |
* } | |
* | |
* void mouseDrag(int _x, int _y) | |
* { | |
* | |
* arcball.updateRotation(_x,_y); | |
* glutPostRedisplay(); | |
* } | |
* | |
* \endcode | |
* | |
*/ | |
typedef float GLMatrix[16]; | |
class Arcball | |
{ | |
private: | |
float fov; | |
int fovStartY; | |
int fovCurrentY; | |
float transX, transY; | |
float currentTransX, currentTransY; | |
float startTransX, startTransY; | |
GLMatrix startMatrix; | |
GLMatrix currentMatrix; | |
Vector3d startRotationVector; | |
Vector3d currentRotationVector; | |
bool isRotating; | |
float ballRadius; | |
double residualSpin; | |
static const float INITIAL_FOV; | |
static const float MINIMAL_FOV; | |
static const float TRANSLATION_FACTOR; | |
Vector3d convertXY(int x, int y); | |
int width, height; | |
public: | |
Arcball(); | |
void setWidthHeight(int w, int h); | |
void startRotation(int x, int y); | |
void updateRotation(int x, int y); | |
void stopRotation(); | |
void applyTranslationMatrix(bool reverse = false); | |
void applyRotationMatrix(); | |
void setRadius(float newRadius); | |
void reset(); | |
}; | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment