Search code examples
pythonopenglpygamepyopenglopengl-compat

How create a camera on PyOpenGL that can do "perspective rotations" on mouse movements?


I am creating a first-person view RPG and I want to rotate the camera in PyOpenGL when I move the mouse (just like some other games like Minecraft). What function can I use to do this and how?

I tried to use gluLookAt() but I don't understand how it works although I went through different sources. I don't even know if it can help.

import sys,pygame
from OpenGL.GL import *
from OpenGL.GLU import *
cmddown = False
#...
keypress = pygame.key.get_pressed()#Move using WASD
    if keypress[pygame.K_w]:
        glTranslatef(0,0,0.1)
    if keypress[pygame.K_s]:
        glTranslatef(0,0,-0.1)
    if keypress[pygame.K_d]:
        glTranslatef(-0.1,0,0)
    if keypress[pygame.K_a]:
        glTranslatef(0.1,0,0)
    mouse_movement = pygame.mouse.get_rel()#Get mouse event
    #This is where the "look around" should be happen
    pygame.display.flip()

Solution

  • You can use glRotate to rotate around an axis, by an amount which is given by the relative mouse movement (pygame.mouse.get_rel()):

    mouseMove = pygame.mouse.get_rel()
    glRotatef(mouseMove[0]*0.1, 0.0, 1.0, 0.0)
    

    But that won't satisfy you, because the solution won't work any more, if the mouse leaves the window.
    You've to center the mouse in the middle of the screen by pygame.mouse.set_pos() in every frame. Get the mouse movement by the pygame.MOUSEMOTION event. e.g.:

    # init mouse movement and center mouse on screen
    displayCenter = [scree.get_size()[i] // 2 for i in range(2)]
    mouseMove = [0, 0]
    pygame.mouse.set_pos(displayCenter)
    
    paused = False
    run = True
    while run:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE or event.key == pygame.K_RETURN:
                    run = False
                if event.key == pygame.K_PAUSE or event.key == pygame.K_p:
                    paused = not paused
                    pygame.mouse.set_pos(displayCenter)  
            if event.type == pygame.MOUSEMOTION:
                mouseMove = [event.pos[i] - displayCenter[i] for i in range(2)]
                if not paused:
                    pygame.mouse.set_pos(displayCenter)  
    

    Note, that operations like glRotate and glTranslate set a matrix and multiply the current matrix by the new matrix.

    currentMatrix = currentMatrix * newMatrix
    

    That is perfect for model animations and transformations, but it is the wrong way for a first person movement, where the camera position and point of view has to be changed.

    viewMatrix = viewTransformMatrix * viewMatrix
    

    To do operations like that glGetFloatv(GL_MODELVIEW_MATRIX) and glMultMatrixf.
    Initialize the view matrix by gluLookAt and load the view matrix to a variable (viewMatrix) by glGetFloatv(GL_MODELVIEW_MATRIX), before the main loop.

    In the main loop for each frame:

    • load the Identity matrix (glLoadIdentity)
    • do the new transformations to the the view (glRotatef, glTranslatef)
    • multiply the current view matrix by viewMatrix (glMultMatrixf)
    • load the new view matrix to viewMatrix for the next frame
    glMatrixMode(GL_MODELVIEW)
    gluLookAt(0, -8, 0, 0, 0, 0, 0, 0, 1)
    viewMatrix = glGetFloatv(GL_MODELVIEW_MATRIX)
    glLoadIdentity()
    
    # [...]
    
    run = True
    while run:
    
        # [...]
    
        # init the view matrix
        glLoadIdentity()
    
        # apply the movment 
        if keypress[pygame.K_w]:
            glTranslatef(0,0,0.1)
        if keypress[pygame.K_s]:
            glTranslatef(0,0,-0.1)
        if keypress[pygame.K_d]:
            glTranslatef(-0.1,0,0)
        if keypress[pygame.K_a]:
            glTranslatef(0.1,0,0)
    
        # apply the roation
        glRotatef(mouseMove[0]*0.1, 0.0, 1.0, 0.0)
    
        # multiply the current matrix by the get the new view matrix and store the final vie matrix 
        glMultMatrixf(viewMatrix)
        viewMatrix = glGetFloatv(GL_MODELVIEW_MATRIX)
    

    For the look up und down, you've to apply a final rotation around the x axis. The pivot of the rotation depends on the point of view. The angle has to be summed up, and the rotation must be applied after the view matrix was else the movement would change the ("height") level dependent on the angle.

    viewMatrix = viewTransformMatrix * viewMatrix
    finlalMatrix = lookUpDownMatrix * viewMatrix  
    

    To do so, you've to ally the up and down rotation matrix and multiply it by viewMatrix

    up_down_angle = 0.0
    run = True
    while run:
    
        # [...]
    
        # init model view matrix
        glLoadIdentity()
    
        # apply the look up and down
        up_down_angle += mouseMove[1]*0.1
        glRotatef(up_down_angle, 1.0, 0.0, 0.0)
        
        # init the view matrix
        glPushMatrix()
        glLoadIdentity()
    
        # calculate new `viewMatrix` 
        # [...]
    
        # apply view matrix
        glPopMatrix()
        glMultMatrixf(viewMatrix)
    

    See the following example program, which demonstrates the process.
    Note, the program keeps the mouse in the center of the window, so you can't "move" the mouse any more. Therefore the application can be stopped by ESC or return.
    The application can be paused by pause or p. When the application is paused, the mouse is not centered to the window.

    import pygame
    from pygame.locals import *
    
    from OpenGL.GL import *
    from OpenGL.GLU import *
    
    import math
    
    pygame.init()
    display = (400, 300)
    scree = pygame.display.set_mode(display, DOUBLEBUF | OPENGL)
    
    glEnable(GL_DEPTH_TEST)
    glEnable(GL_LIGHTING)
    glShadeModel(GL_SMOOTH)
    glEnable(GL_COLOR_MATERIAL)
    glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
    
    glEnable(GL_LIGHT0)
    glLightfv(GL_LIGHT0, GL_AMBIENT, [0.5, 0.5, 0.5, 1])
    glLightfv(GL_LIGHT0, GL_DIFFUSE, [1.0, 1.0, 1.0, 1])
    
    sphere = gluNewQuadric() 
    
    glMatrixMode(GL_PROJECTION)
    gluPerspective(45, (display[0]/display[1]), 0.1, 50.0)
    
    glMatrixMode(GL_MODELVIEW)
    gluLookAt(0, -8, 0, 0, 0, 0, 0, 0, 1)
    viewMatrix = glGetFloatv(GL_MODELVIEW_MATRIX)
    glLoadIdentity()
    
    # init mouse movement and center mouse on screen
    displayCenter = [scree.get_size()[i] // 2 for i in range(2)]
    mouseMove = [0, 0]
    pygame.mouse.set_pos(displayCenter)
    
    up_down_angle = 0.0
    paused = False
    run = True
    while run:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                run = False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE or event.key == pygame.K_RETURN:
                    run = False
                if event.key == pygame.K_PAUSE or event.key == pygame.K_p:
                    paused = not paused
                    pygame.mouse.set_pos(displayCenter) 
            if not paused: 
                if event.type == pygame.MOUSEMOTION:
                    mouseMove = [event.pos[i] - displayCenter[i] for i in range(2)]
                pygame.mouse.set_pos(displayCenter)    
    
        if not paused:
            # get keys
            keypress = pygame.key.get_pressed()
            #mouseMove = pygame.mouse.get_rel()
        
            # init model view matrix
            glLoadIdentity()
    
            # apply the look up and down
            up_down_angle += mouseMove[1]*0.1
            glRotatef(up_down_angle, 1.0, 0.0, 0.0)
    
            # init the view matrix
            glPushMatrix()
            glLoadIdentity()
    
            # apply the movment 
            if keypress[pygame.K_w]:
                glTranslatef(0,0,0.1)
            if keypress[pygame.K_s]:
                glTranslatef(0,0,-0.1)
            if keypress[pygame.K_d]:
                glTranslatef(-0.1,0,0)
            if keypress[pygame.K_a]:
                glTranslatef(0.1,0,0)
    
            # apply the left and right rotation
            glRotatef(mouseMove[0]*0.1, 0.0, 1.0, 0.0)
    
            # multiply the current matrix by the get the new view matrix and store the final vie matrix 
            glMultMatrixf(viewMatrix)
            viewMatrix = glGetFloatv(GL_MODELVIEW_MATRIX)
    
            # apply view matrix
            glPopMatrix()
            glMultMatrixf(viewMatrix)
    
            glLightfv(GL_LIGHT0, GL_POSITION, [1, -1, 1, 0])
    
            glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
    
            glPushMatrix()
    
            glColor4f(0.5, 0.5, 0.5, 1)
            glBegin(GL_QUADS)
            glVertex3f(-10, -10, -2)
            glVertex3f(10, -10, -2)
            glVertex3f(10, 10, -2)
            glVertex3f(-10, 10, -2)
            glEnd()
    
            glTranslatef(-1.5, 0, 0)
            glColor4f(0.5, 0.2, 0.2, 1)
            gluSphere(sphere, 1.0, 32, 16) 
    
            glTranslatef(3, 0, 0)
            glColor4f(0.2, 0.2, 0.5, 1)
            gluSphere(sphere, 1.0, 32, 16) 
    
            glPopMatrix()
    
            pygame.display.flip()
            pygame.time.wait(10)
    
    pygame.quit()