Trackball mouse Rotation in OpenGL

1k views Asked by At

I'm trying to implement a basic solution for trackball with openGL.

There are 2 rotations : - around x axis (right direction) , linked with up/down mouse movement. - around y axis (up direction) , linked with right/left mouse movement. The cube is centered on 0, camera is on z axis. Axis are drawn : x in blue, y in green, z in red.

2 different groups of rotations:

  1. 90° around x by moving the mouse down. Z toward down, x right, y in front. Then another rotation: 90° around z (z as displayed), mouse movement on the right. z towards the right direction, x up and y in front.

  2. I process again, position start from scratch. 90° around y. y : up , z: right , x: in front Another rotation of 90° around z. x: up , z: right : y : in front .

I identify a different behaviour between the second rotations.

For coherence, I was waiting that for the first operation, the second rotation was around z Or for the second operation, the second rotation was around x axis. Why ?

For me, the second operation has the goood rotations, that's the behaviour i would like to have. Thank you for your help.

Below the code

cube.cpp :

    #include <SDL/SDL.h>
    #include <GL/gl.h>
    #include <GL/glu.h>
    #include <cstdlib>
    #include <iostream>

    #include "sdlglutils.h"
    #include "trackballcamera.h"

    using namespace std;

    void Dessiner();
    double angleZ = 0;
    double angleX = 0;
    double angleY = 0;

    TrackBallCamera * camera;

    int main(int argc, char *argv[])
    {
    cout<<"in main cube"<<endl;
    freopen("CON", "w", stdout);
    freopen("CON", "r", stdin);
    freopen("CON", "w", stderr);


    SDL_Event event;

    SDL_Init(SDL_INIT_VIDEO);
    atexit(SDL_Quit);
    SDL_WM_SetCaption("SDL GL Application", NULL);
    SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);

    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    gluPerspective(70,(double)640/480,1,1000);

    glEnable(GL_DEPTH_TEST);

    camera = new TrackBallCamera();
    camera->setScrollSensivity(10);

    Dessiner();

    Uint32 last_time = SDL_GetTicks();
    Uint32 current_time,ellapsed_time;
    Uint32 start_time;

    GLUquadric* params;
    params = gluNewQuadric();

    for (;;)
    {
        start_time = SDL_GetTicks();
        while (SDL_PollEvent(&event))
        {

            switch(event.type)
            {
                case SDL_QUIT:
                exit(0);
                break;

                case SDL_MOUSEBUTTONUP:
                cout<<"SDL_MOUSEBUTTONUP"<<endl;
                camera->OnMouseButton(event.button); 
                break;

                case SDL_MOUSEBUTTONDOWN:
                cout<<"SDL_MOUSEBUTTONDOWN"<<endl;
                camera->OnMouseButton(event.button); 
                break;

                case SDL_MOUSEMOTION:
                camera->OnMouseMotion(event.motion);
                break;


            }
        }

        current_time = SDL_GetTicks();
        ellapsed_time = current_time - last_time;
        last_time = current_time;

        //angleZ += 0.05 * ellapsed_time;
        //angleX += 0.05 * ellapsed_time;

        Dessiner();

        ellapsed_time = SDL_GetTicks() - start_time;
        if (ellapsed_time < 10)
        {
            SDL_Delay(10 - ellapsed_time);
        }

    }

    return 0;
}

void dessinerRepere(unsigned int echelle = 10)
{
    //glPushMatrix();
    glScalef(echelle,echelle,echelle);
    glBegin(GL_LINES);
    glColor3ub(0,0,255);
    glVertex3i(0,0,0);
    glVertex3i(1,0,0);
    glColor3ub(0,255,0);
    glVertex3i(0,0,0);
    glVertex3i(0,1,0);
    glColor3ub(255,0,0);
    glVertex3i(0,0,0);
    glVertex3i(0,0,1);
    glEnd();
    //glPopMatrix();
}


void Dessiner()
{
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity( );

    //gluLookAt(20,20,20,0,0,0,0,0,1);

    camera->look(0,0,camera->getDistance()
                 ,0,0,0
                 ,0,1,0);

    //glRotated(angleZ,0,0,1);
    //glRotated(angleX,1,0,0);

    glBegin(GL_QUADS);

    glColor3ub(255,0,0); //face rouge
    glVertex3d(1,1,1);
    glVertex3d(1,1,-1);
    glVertex3d(-1,1,-1);
    glVertex3d(-1,1,1);

    glColor3ub(0,255,0); //face verte
    glVertex3d(1,-1,1);
    glVertex3d(1,-1,-1);
    glVertex3d(1,1,-1);
    glVertex3d(1,1,1);

    glColor3ub(0,0,255); //face bleue
    glVertex3d(-1,-1,1);
    glVertex3d(-1,-1,-1);
    glVertex3d(1,-1,-1);
    glVertex3d(1,-1,1);

    glColor3ub(255,255,0); //face jaune
    glVertex3d(-1,1,1);
    glVertex3d(-1,1,-1);
    glVertex3d(-1,-1,-1);
    glVertex3d(-1,-1,1);

    glColor3ub(0,255,255); //face cyan
    glVertex3d(1,1,-1);
    glVertex3d(1,-1,-1);
    glVertex3d(-1,-1,-1);
    glVertex3d(-1,1,-1);

    glColor3ub(255,0,255); //face magenta
    glVertex3d(1,-1,1);
    glVertex3d(1,1,1);
    glVertex3d(-1,1,1);
    glVertex3d(-1,-1,1);

    glEnd();
    //glLoadIdentity( );
    dessinerRepere();

    glFlush();
    SDL_GL_SwapBuffers();
}

trackballcamera.cpp :

#include "trackballcamera.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <cmath>
#include "trackballcamera.h"
#include "sdlglutils.h"

void TrackBallCamera::setDistance(const double & newDistance)
{
    _distance = newDistance;
}

const double & TrackBallCamera::getDistance() const
{
    return _distance;
}

TrackBallCamera::TrackBallCamera()
{
    const char *hand1[] =
        {
            /* width height num_colors chars_per_pixel */
            " 16 16 3 1 ",
            /* colors */
            "X c #000000",
            ". c #ffffff",
            "  c None",
            /* pixels */
            "       XX       ",
            "   XX X..XXX    ",
            "  X..XX..X..X   ",
            "  X..XX..X..X X ",
            "   X..X..X..XX.X",
            "   X..X..X..X..X",
            " XX X.......X..X",
            "X..XX..........X",
            "X...X.........X ",
            " X............X ",
            "  X...........X ",
            "  X..........X  ",
            "   X.........X  ",
            "    X.......X   ",
            "     X......X   ",
            "     X......X   ",
            "0,0"
        };

    const char *hand2[] =
        {
            /* width height num_colors chars_per_pixel */
            " 16 16 3 1 ",
            /* colors */
            "X c #000000",
            ". c #ffffff",
            "  c None",
            /* pixels */
            "                ",
            "                ",
            "                ",
            "                ",
            "    XX XX XX    ",
            "   X..X..X..XX  ",
            "   X........X.X ",
            "    X.........X ",
            "   XX.........X ",
            "  X...........X ",
            "  X...........X ",
            "  X..........X  ",
            "   X.........X  ",
            "    X.......X   ",
            "     X......X   ",
            "     X......X   ",
            "0,0"
        };
    _hand1 = cursorFromXPM(hand1);
    _hand2 = cursorFromXPM(hand2);
    SDL_SetCursor(_hand1);
    _holdRotation = false;
    _holdTranslation = false;
    _angleX = 0;
    _angleY = 0;
    _angleZ = 0;
    _transX = 0;
    _transY = 0;
    _transZ = 0;
    _distance = 50;
    _motionSensivity = 0.3;
    //_scrollSensivity = 1;
    _scrollSensivity = 10;
}

void TrackBallCamera::OnMouseMotion(const SDL_MouseMotionEvent & event)
{
    printf("OnMouseMotion; _holdRotation:%d; _holdTranslation:%d\n", _holdRotation, _holdTranslation);
    if (_holdRotation)
    {
        _angleY += event.xrel*_motionSensivity;
        _angleX += event.yrel*_motionSensivity;
        printf("_angleX:%lf; _angleY:%lf\n",_angleX,_angleY);
//        if (_angleY > 90)
//            _angleY = 90;
//        else if (_angleY < -90)
//            _angleY = -90;
    }

    if (_holdTranslation)
    {
        _transX += event.xrel*_motionSensivity;
        _transY += event.yrel*_motionSensivity;
        printf("_transZ:%lf, _transY:%lf\n",_transZ, _transY);
    }

}

void TrackBallCamera::OnMouseButton(const SDL_MouseButtonEvent & event)
{
    printf("OnMouseButton\n");

    if (event.button == SDL_BUTTON_LEFT)
    {
        if ((_holdRotation)&&(event.type == SDL_MOUSEBUTTONUP))
        {
            _holdRotation = false;
            SDL_SetCursor(_hand1);
        }
        else if ((!_holdRotation)&&(event.type == SDL_MOUSEBUTTONDOWN))
        {
            _holdRotation = true;
            SDL_SetCursor(_hand2);
        }
        printf("_holdRotation:%d\n",_holdRotation);
    }

    else if (event.button == SDL_BUTTON_RIGHT)
    {
        if ((_holdTranslation)&&(event.type == SDL_MOUSEBUTTONUP))
        {
            _holdTranslation = false;
            SDL_SetCursor(_hand1);
        }
        else if ((!_holdTranslation)&&(event.type == SDL_MOUSEBUTTONDOWN))
        {
            _holdTranslation = true;
            SDL_SetCursor(_hand2);
        }
    }

    else if ((event.button == SDL_BUTTON_WHEELUP)&&(event.type == SDL_MOUSEBUTTONDOWN))
    {
        printf("OK WHEELUP, _scrollSensivity:%lf; _distance:%lf\n", _scrollSensivity,_distance);
        _distance -= _scrollSensivity;
        //if (_distance < 0.1)
            //_distance = 0.1;
        printf("APRES WHEELUP, _distance:%lf\n", _distance);
    }
    else if ((event.button == SDL_BUTTON_WHEELDOWN)&&(event.type == SDL_MOUSEBUTTONDOWN))
    {
            printf("OK WHEELDOWN, _distance:%lf\n",_distance);
            _distance += _scrollSensivity;
    }
}

void TrackBallCamera::OnKeyboard(const SDL_KeyboardEvent & event)
{
    if ((event.type == SDL_KEYDOWN)&&(event.keysym.sym == SDLK_HOME))
    {
        _angleY = 0;
        _angleX = 0;
    }
}

void TrackBallCamera::setMotionSensivity(double sensivity)
{
    _motionSensivity = sensivity;
}

void TrackBallCamera::setScrollSensivity(double sensivity)
{
    _scrollSensivity = sensivity;
}

TrackBallCamera::~TrackBallCamera()
{
    SDL_FreeCursor(_hand1);
    SDL_FreeCursor(_hand2);
    SDL_SetCursor(NULL);
}

//camera->look(cloud.pointPosition[lastElement*3],0,75,cloud.pointPosition[lastElement*3],0,0,0,1,0);
void TrackBallCamera::look(const GLfloat& xPointOfView
                            ,const GLfloat& yPointOfView
                            ,const GLfloat& zPointOfView
                            ,const GLfloat& xCenter
                            ,const GLfloat& yCenter
                            ,const GLfloat& zCenter
                            ,const GLfloat& xVerticalVector
                            ,const GLfloat& yVerticalVector
                            ,const GLfloat& zVerticalVector
                            )

//void TrackBallCamera::look()
{
    //gluLookAt(_distance,0,0,
              //0,0,0,
              //0,0,1);
    //printf("look, xPointOfView:%lf\n",xPointOfView);
    gluLookAt(xPointOfView,yPointOfView,zPointOfView,
              xCenter,yCenter,zCenter,
              xVerticalVector,yVerticalVector,zVerticalVector);

    glRotated(_angleX,1,0,0);
    glRotated(_angleY,0,1,0);
    //glRotated(_angleZ,0,0,1);
    //glTranslated(_transX,0,0);
    //glTranslated(0,_transY,0);
    //glTranslated(0,0,_transZ);
}

trackballcamera.h :

#ifndef TRACKBALLCAMERA_H
#define TRACKBALLCAMERA_H

#include <SDL/SDL.h>
#include <GL/gl.h>

class TrackBallCamera
{
public:
    TrackBallCamera();

    virtual void OnMouseMotion(const SDL_MouseMotionEvent & event);
    virtual void OnMouseButton(const SDL_MouseButtonEvent & event);
    virtual void OnKeyboard(const SDL_KeyboardEvent & event);

    //virtual void look();
    virtual void look(const GLfloat& xPointOfView
                            ,const GLfloat& yPointOfView
                            ,const GLfloat& zPointOfView
                            ,const GLfloat& xCenter
                            ,const GLfloat& yCenter
                            ,const GLfloat& zCenter
                            ,const GLfloat& xVerticalVector
                            ,const GLfloat& yVerticalVector
                            ,const GLfloat& zVerticalVector
                            );
    virtual void setMotionSensivity(double sensivity);
    virtual void setScrollSensivity(double sensivity);

    void setDistance(const double & newDistance);
    const double & getDistance() const ;

    virtual ~TrackBallCamera();

protected:
    double _motionSensivity;
    double _scrollSensivity;
    //bool _hold;
    bool _holdRotation;
    bool _holdTranslation;
    double _distance;
    double _angleX;
    double _angleY;
    double _angleZ;
    double _transX;
    double _transY;
    double _transZ;
    SDL_Cursor * _hand1;
    SDL_Cursor * _hand2;
};

#endif //TRACKBALLCAMERA_H

sdlglutils.cpp :

#include "sdlglutils.h"
#include <SDL/SDL.h>
//#include <SDL/SDL_image.h>
#include <GL/glu.h>

#include <cstring>
#include <cstdlib>



SDL_Cursor * cursorFromXPM(const char * xpm[])
{
    int i, row, col;
    int width, height;
    Uint8 * data;
    Uint8 * mask;
    int hot_x, hot_y;
    SDL_Cursor * cursor = NULL;

    sscanf(xpm[0], "%d %d", &width, &height);
    data = (Uint8*)calloc(width/8*height,sizeof(Uint8));
    mask = (Uint8*)calloc(width/8*height,sizeof(Uint8));

    i = -1;
    for ( row=0; row<height; ++row )
    {
        for ( col=0; col<width; ++col )
        {
            if ( col % 8 )
            {
                data[i] <<= 1;
                mask[i] <<= 1;
            }
            else
            {
                ++i;
                data[i] = mask[i] = 0;
            }
            switch (xpm[4+row][col])
            {
                case 'X':
                data[i] |= 0x01;
                mask[i] |= 0x01;
                break;
                case '.':
                mask[i] |= 0x01;
                break;
                case ' ':
                break;
            }
        }
    }
    sscanf(xpm[4+row], "%d,%d", &hot_x, &hot_y);
    //printf("data :%" PRIu8 "; mask:%" PRIu8 ";width:%d; height:%d; hot_x:%d; hot_y:%d\n", *data, *mask, width, height, hot_x, hot_y);
    cursor = SDL_CreateCursor(data, mask, width, height, hot_x, hot_y);
    free(data);
    free(mask);
    return cursor;
}

sdlglutils.h :

#ifndef SDLGLUTILS_H
#define SDLGLUTILS_H

#include <GL/gl.h>
#include <SDL/SDL.h>

SDL_Cursor * cursorFromXPM(const char * xpm[]);

#endif //SDLGLUTILS_H
1

There are 1 answers

2
dognotdog On

You seem to be running into a classic problem with Euler Angles, the order of incremental rotations matters.

Furthermore for an intuitive Trackball behavior for 3D rotation, you need to consider that the model and global rotation axes are not aligned, and thus when you apply the trackball deltas to the global axes, the rotation might be in an unexpected direction. To remedy this, you need to do a coordinate transformation of the user input into global space, or compute a rotation matrix in user space, and transform that rotation matrix from model space into global space, so that the rendering does the right thing.