Thursday, April 13, 2017

Using Mouse for Object Zoom In/Zoom Out/Rotation in Python + OpenGL

OpenGL is supported in multiple languages including Python. A common task performed in OpenGL is to zoom in, zoom out and rotate the 3D object. In this blog, we provide an example of how to carry out these tasks. We utilize a Python package name pyname in our code.

In our code, when one rolls up the wheel, the object will be zoomed in; when one rolls down the wheel, the object will be zoomed out. Zoom in/out is implemented by the function of glScaled. When the scale is larger than 1, the object will be enlarged; when the scale is smaller than 1, the object will be shrink. The code snippet for zoom function is:


    if event.type == pygame.MOUSEBUTTONDOWN and event.button == 4: # wheel rolled up
        glScaled(1.05, 1.05, 1.05);
    elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 5: # wheel rolled down
        glScaled(0.95, 0.95, 0.95);


Rotation function is designed in this way: when you move the mouse  to a direction while pressing the left button, the object rotates to that direction. Since the movement of mouse in the monitor is 2D, the first step is to calculate the mouse movement in the x-axis and y-axis directions. pygame allows to record the 2D location of the mouse. The delta between the current location and the previous location gives dx and dy, which indicates the horizontal and vertical directions of movement:
        x, y = event.pos;
        dx = x - lastPosX;
        dy = y - lastPosY;

As the next step of implementing rotation, model view matrix needs to be retrieved. Since the 3D object keeps rotating, model view matrix tells the current orientation.

            modelView = (GLfloat * 16)()
            mvm = glGetFloatv(GL_MODELVIEW_MATRIX, modelView)

The model view matrix is a 4x4 matrix. The first 3 rows by 3 columns are the rotation matrix which is also a unitary matrix. This rotation matrix, Q, tells the orientation of the object. In order to achieve the desired rotation effect, elements of the rotation matrix need to be multiplied with dx and dy. Assuming Q = [q0|q1|q2], since dy represents the rotation around x-axis and dx represents the rotation around y-axis, q0*dy+q1*dx is combined rotation vector. sqrt(dx^2+dy^2) is the magnitude of rotation. The code is as below:

            temp = (GLfloat * 3)();
            temp[0] = modelView[0]*dy + modelView[1]*dx;
            temp[1] = modelView[4]*dy + modelView[5]*dx;
            temp[2] = modelView[8]*dy + modelView[9]*dx;
            norm_xy = math.sqrt(temp[0]*temp[0] + temp[1]*temp[1] + temp[2]*temp[2]);
            glRotatef(math.sqrt(dx*dx+dy*dy), temp[0]/norm_xy, temp[1]/norm_xy, temp[2]/norm_xy);


For reference, an example Python program is provided below. To run this program, the user needs to install pygame Python package.

import pygame
from pygame.locals import *
import math

from OpenGL.GL import *
from OpenGL.GLU import *

lastPosX = 0;
lastPosY = 0;
zoomScale = 1.0;
dataL = 0;
xRot = 0;
yRot = 0;
zRot = 0;

verticies = (
    (1, -1, -1),
    (1, 1, -1),
    (-1, 1, -1),
    (-1, -1, -1),
    (1, -1, 1),
    (1, 1, 1),
    (-1, -1, 1),
    (-1, 1, 1)
    )

edges = (
    (0,1),
    (0,3),
    (0,4),
    (2,1),
    (2,3),
    (2,7),
    (6,3),
    (6,4),
    (6,7),
    (5,1),
    (5,4),
    (5,7)
    )


def Cube():
    glBegin(GL_LINES)
    for edge in edges:
        for vertex in edge:
            glVertex3fv(verticies[vertex])
    glEnd()
    #print(repr(verticies[0][0])+','+repr(verticies[0][1]));
 
    glBegin( GL_POINTS );
    glColor3f(1,1,1); 
    for i in range(0,8):
         glVertex3f(verticies[i][0], verticies[i][1], verticies[i][2]);
    glEnd();
 
def mouseMove(event):
    global lastPosX, lastPosY, zoomScale, xRot, yRot, zRot;
 
    if event.type == pygame.MOUSEBUTTONDOWN and event.button == 4: # wheel rolled up
        glScaled(1.05, 1.05, 1.05);
    elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 5: # wheel rolled down
        glScaled(0.95, 0.95, 0.95);
 
    if event.type == pygame.MOUSEMOTION:
        x, y = event.pos;
        dx = x - lastPosX;
        dy = y - lastPosY;
        
        mouseState = pygame.mouse.get_pressed();
        if mouseState[0]:

            modelView = (GLfloat * 16)()
            mvm = glGetFloatv(GL_MODELVIEW_MATRIX, modelView)
   
   # To combine x-axis and y-axis rotation
            temp = (GLfloat * 3)();
            temp[0] = modelView[0]*dy + modelView[1]*dx;
            temp[1] = modelView[4]*dy + modelView[5]*dx;
            temp[2] = modelView[8]*dy + modelView[9]*dx;
            norm_xy = math.sqrt(temp[0]*temp[0] + temp[1]*temp[1] + temp[2]*temp[2]);
            glRotatef(math.sqrt(dx*dx+dy*dy), temp[0]/norm_xy, temp[1]/norm_xy, temp[2]/norm_xy);

        lastPosX = x;
        lastPosY = y;
        


def main():
    pygame.init()
 
    display = (1000,750)
    pygame.display.set_mode(display, DOUBLEBUF|OPENGL, RESIZABLE)

    gluPerspective(45, (1.0*display[0]/display[1]), 0.1, 50.0)
    glTranslatef(0.0,0.0, -5)
 

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()
            mouseMove(event);

        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
        Cube()
        pygame.display.flip()
        pygame.time.wait(10)


main()

1 comment:

  1. very nice. What if I want to work with 2D items; i.e. "line drawings" (top/front/side of your cube, not all of it) - I only need to zoom, move, and rotate around one (1) axis, or just a "spin". How would I do that?

    ReplyDelete