OpenGL - restricting Arcball rotation

784 Views Asked by At

I have a simple red rectangle rendered in a window, and I created an arcball in order to rotate it in any direction. I was following the code from NeHe's Arcball Rotation tutorial.

The problem is that once the rectangle is rendered and the left mouse button is clicked, it spins like a top in the window. The mouse click-drag-stop update happens every time the mouse is moved, which is the reason why it spins that way. I am not able to figure out a way to restrict the rotation only for the duration of the click-drag-stop. How do I restrict the rotation? I have been trying to debug this for about 4-5 days now with no luck.

I added the original Arcball.h and Arcball.cpp files to my project, with one change to to the header file; I simply created a default constructor for the Arcball_t class with this line -

ArcBall_t() {};

The only other changes from the original project are that I threw the Update() function call into my code:

// ==========================================================================================
// function declarations 

#define GET_PROC_ADDRESS( str ) wglGetProcAddress( str )

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

void DrawOpenGLScene(HDC hDC);
void Update();

HGLRC SetUpOpenGLContext(HWND hWnd);

// ==========================================================================================    
// Trackball declarations 

const float PI2 = 2.0*3.1415926535f;                                // PI Squared

GLUquadricObj *quadratic;   


Point2fT    MousePt;                                                // NEW: Current Mouse Point
bool        isClicked  = false;                                     // NEW: Clicking The Mouse?
bool        isRClicked = false;                                     // NEW: Clicking The Right Mouse Button?
bool        isDragging = false;                                     // NEW: Dragging The Mouse?



Matrix4fT   Transform   = {  1.0f,  0.0f,  0.0f,  0.0f,             // NEW: Final Transform
                             0.0f,  1.0f,  0.0f,  0.0f,
                             0.0f,  0.0f,  1.0f,  0.0f,
                             0.0f,  0.0f,  0.0f,  1.0f };

Matrix3fT   LastRot     = {  1.0f,  0.0f,  0.0f,                    // NEW: Last Rotation
                             0.0f,  1.0f,  0.0f,
                             0.0f,  0.0f,  1.0f };

Matrix3fT   ThisRot     = {  1.0f,  0.0f,  0.0f,                    // NEW: This Rotation
                             0.0f,  1.0f,  0.0f,
                             0.0f,  0.0f,  1.0f };

// ==========================================================================================

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
{
    static char szClassName[] = "Myclass";
    static char szTitle[]="A Simple Win32 API OpenGL Program";
    WNDCLASS wc; 
    MSG      msg;  
    HWND     hWnd;

    wc.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = NULL; 
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)GetStockObject (BLACK_BRUSH);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = szClassName;
    if (!RegisterClass (&wc))
        return 0;

    hWnd = CreateWindow(szClassName, szTitle, 
                        WS_OVERLAPPEDWINDOW |
                            // NEED THESE for OpenGL calls to work!
                WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
                                0, 0, 1024, 256,
                NULL, NULL, hInstance, NULL);

    ArcBall_t    ArcBall(1024.0f, 256.0f);                              // NEW: ArcBall Instance

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow( hWnd );
    while (GetMessage(&msg, NULL, 0, 0)) 
        {
        TranslateMessage( &msg );
        DispatchMessage( &msg );
        }

    return(msg.wParam); 
}

// ==========================================================================================

//*******************************************************
//  This is the brain of the loop
//  Checks for a new key press or mouse movement 
// renders when something is detected 
//*******************************************************


LRESULT CALLBACK WndProc( HWND hWnd, UINT msg,
                     WPARAM wParam, LPARAM lParam )
{
    HDC hDC;
    static HGLRC hRC; // Note this is STATIC!
    PAINTSTRUCT ps;

    switch (msg)
        {
       case WM_CREATE:
            // Select a pixel format and create a rendering context
            hRC = SetUpOpenGLContext(hWnd);
            break;      

        case WM_PAINT:
            // Draw the scene
            // Get a DC, make RC current & associate it with this DC
            hDC = BeginPaint(hWnd, &ps);
            wglMakeCurrent(hDC, hRC);

            DrawOpenGLScene(hDC);  // Draw                  

            // We're done with the RC, so deselect it
            wglMakeCurrent(NULL, NULL);
            EndPaint(hWnd, &ps);
            break;  

        //*NEW* Mouse based messages for arcball

        case WM_LBUTTONUP:
            isClicked   = false;

        break;

        case WM_RBUTTONUP:
            isRClicked  = false;

        break;

        case WM_LBUTTONDOWN:
            isClicked   = true;

        break;

        case WM_RBUTTONDOWN:
            isRClicked  = true;

        break;

        case WM_MOUSEMOVE:
            MousePt.s.X = (GLfloat)LOWORD(lParam);
            MousePt.s.Y = (GLfloat)HIWORD(lParam);
            isClicked   = (LOWORD(wParam) & MK_LBUTTON) ? true : false;
            isRClicked  = (LOWORD(wParam) & MK_RBUTTON) ? true : false;

            Update(); 
            RedrawWindow(hWnd, NULL, NULL, RDW_INTERNALPAINT); 

        break;


        case WM_DESTROY:

            // Clean up and terminate
            wglDeleteContext(hRC);

            PostQuitMessage(0);
            break;

        default:
                return DefWindowProc(hWnd, msg, wParam, lParam);
        }

        return (0);
}

// ==========================================================================================

//*******************************************************
//  SetUpOpenGL sets the pixel format and a rendering
//  context then returns the RC
//*******************************************************

HGLRC SetUpOpenGLContext(HWND hWnd)
{
    static PIXELFORMATDESCRIPTOR pfd = {
        sizeof (PIXELFORMATDESCRIPTOR), // strcut size 
        1,                              // Version number
        PFD_DRAW_TO_WINDOW |     // Flags, draw to a window,
            PFD_SUPPORT_OPENGL | // use OpenGL
            PFD_DOUBLEBUFFER,   // Use a double buffer 
        PFD_TYPE_RGBA,          // RGBA pixel values
        32,                     // 24-bit color
        0, 0, 0,                // RGB bits & shift sizes.
        0, 0, 0,                // Don't care about them
        0, 0,                   // No alpha buffer info
        0, 0, 0, 0, 0,          // No accumulation buffer
        32,                     // 32-bit depth buffer
        8,                      // No stencil buffer
        0,                      // No auxiliary buffers
        PFD_MAIN_PLANE,         // Layer type
        0,                      // Reserved (must be 0)
        0,                      // No layer mask
        0,                      // No visible mask
        0                       // No damage mask
    };

    int nMyPixelFormatID;
    HDC hDC;
    HGLRC hRC;

    hDC = GetDC(hWnd);
    nMyPixelFormatID = ChoosePixelFormat(hDC, &pfd);
    SetPixelFormat(hDC, nMyPixelFormatID, &pfd);
    hRC = wglCreateContext(hDC);
    ReleaseDC(hWnd, hDC);

    quadratic=gluNewQuadric();                                      // Create A Pointer To The Quadric Object
    gluQuadricNormals(quadratic, GLU_SMOOTH);                       // Create Smooth Normals
    gluQuadricTexture(quadratic, GL_TRUE);                          // Create Texture Coords

    return hRC;
}

// ==========================================================================================
// simple test code - rectangle/triangle 

void DrawOpenGLScene(HDC hDC)
{


    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode( GL_MODELVIEW ); 

    glTranslatef(0.0f, 0.0f, 0.0f); 

    glColor3f(1.0, 0.0, 0.0);  // drawing color
    glBegin(GL_POLYGON);     // define the rectangle
            glVertex2f(-0.5,-0.5);
            glVertex2f(-0.5,0.5);
            glVertex2f(0.5,0.5);
            glVertex2f(0.5,-0.5);
    glEnd();

    glMultMatrixf(Transform.M);

    glFlush();   // force execution
    SwapBuffers(hDC);
}

// ==========================================================================================

void Update ()                                  // Perform Motion Updates Here
{   

    ArcBall_t    ArcBall;

    if (isRClicked)                                                 // If Right Mouse Clicked, Reset All Rotations
    {
        Matrix3fSetIdentity(&LastRot);                              // Reset Rotation
        Matrix3fSetIdentity(&ThisRot);                              // Reset Rotation
        Matrix4fSetRotationFromMatrix3f(&Transform, &ThisRot);      // Reset Rotation
    }

    if (!isDragging)                                                // Not Dragging
    {
        if (isClicked)                                              // First Click
        {
            isDragging = true;                                      // Prepare For Dragging
            LastRot = ThisRot;                                      // Set Last Static Rotation To Last Dynamic One
            ArcBall.click(&MousePt);                                // Update Start Vector And Prepare For Dragging
        }

    }
    else
    {
        if (isClicked)                                              // Still Clicked, So Still Dragging
        {
            Quat4fT     ThisQuat;

            ArcBall.drag(&MousePt, &ThisQuat);                      // Update End Vector And Get Rotation As Quaternion
            Matrix3fSetRotationFromQuat4f(&ThisRot, &ThisQuat);     // Convert Quaternion Into Matrix3fT
            Matrix3fMulMatrix3f(&ThisRot, &LastRot);                // Accumulate Last Rotation Into This One
            Matrix4fSetRotationFromMatrix3f(&Transform, &ThisRot);  // Set Our Final Transform's Rotation From This One
        }
        else                                                        // No Longer Dragging
            isDragging = false;
    }


}

EDIT: I fixed the issue with the uncontrollable scene rotation by inserting a check within the case WM_MOUSEMOVE: handler of the WndProc() routine. The check was: if(isClicked == true) { //code }, and that seemed to do the trick. It no longer spins like a top. However, I still do not have fine control over the rotation; it still spins like a top within the click-drag-release duration.

1

There are 1 best solutions below

4
On

Assuming your arcball.drag and arcball.click functions are correct, the only problem with your code is that you are applying the rotation after you draw and your need to do it before. Your Code:

glTranslatef(0.0f, 0.0f, 0.0f); 

glColor3f(1.0, 0.0, 0.0);  // drawing color
glBegin(GL_POLYGON);     // define the rectangle
        glVertex2f(-0.5,-0.5);
        glVertex2f(-0.5,0.5);
        glVertex2f(0.5,0.5);
        glVertex2f(0.5,-0.5);
glEnd();

glMultMatrixf(Transform.M);

Try changing it to:

glTranslatef(0.0f, 0.0f, 0.0f); 
glMultMatrixf(Transform.M);
glColor3f(1.0, 0.0, 0.0);  // drawing color
glBegin(GL_POLYGON);     // define the rectangle
        glVertex2f(-0.5,-0.5);
        glVertex2f(-0.5,0.5);
        glVertex2f(0.5,0.5);
        glVertex2f(0.5,-0.5);
glEnd();

Although, based on the code you have provided, I dont think your image should rotate at all BECAUSE you are applying the rotation after. I think more code might be needed to find your issue.