Search code examples
pythonopenglpygamepyopenglopengl-compat

OpenGL python and pygame translation after rotation not working for mouselook and movement


I'm trying to make a simple mouse look with standard arrow key movement, and I've got the mouselook working but the translations from the rotated points seem to move along an orthogonal basis, but not one that is aligned with the rotation of the mouselook. I can't tell if my math is off or if opengl is doing something extra to transform the points and I need to adjust. I looked at the modelview matrix and it appears to be following the same order of rotations but I'm just stuck here, I'm not sure if it has something to do with the perspective or what is happening really. I'm not the best with linear algebra so it's got me stuck a bit.

import pygame
import pygameMenu as pgm
from pygame.locals import *

from OpenGL.GL import *
from OpenGL.GLU import *
import numpy as np    
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)
    )

class OGl():
    def three_func(a,b,func):
        return (func(a[0],b[0]),func(a[1],b[1]),func(a[2],b[2]))

class GLCamera():
    def __init__(self):
        self.pos = [0.0,0.0,10.0]
        self.rot = [0.0,0.0,0.0]
        self.rotating = False
        self.mouse_pos = [0,0]

    def add_to_scene(self):
        #buffer = glGetDouble( GL_MODELVIEW_MATRIX )
        #print(buffer)
        glRotatef(self.rot[2], 0, 0, 1);  # roll
        glRotatef(self.rot[1], 0, 1, 0); # heading
        glRotatef(self.rot[0], 1, 0, 0);  # pitch
        glTranslatef(-self.pos[0],-self.pos[1],-self.pos[2]);

    def change_of_basis(self):
        #skip roll for now
        c=np.cos(self.rot[1]*(np.pi/180))
        s=np.sin(self.rot[1]*(np.pi/180))
        m1=np.array([[c,0,s],[0,1,0],[-s,0,c]])
        c=np.cos(self.rot[0]*(np.pi/180))
        s=np.sin(self.rot[0]*(np.pi/180))
        m2=np.array([[1,0,0],[0,c,-s],[0,s,c]])
        m=m1.dot(m2)
        return m

    def handle_camera_events(self,event):
        if event.type == pygame.KEYDOWN:
            cb = self.change_of_basis()
            if event.key == pygame.K_f:
                m=cb.dot(np.array([0,0,-0.5]))
                self.pos=OGl.three_func(self.pos,m, lambda x,y : x+y )
            if event.key == pygame.K_g:
                m=cb.dot(np.array([0,0,0.5]))
                self.pos=OGl.three_func(self.pos,m, lambda x,y : x+y )
            if event.key == pygame.K_LEFT:
                m=cb.dot(np.array([-0.5,0,0]))
                self.pos=OGl.three_func(self.pos,m, lambda x,y : x+y )
            if event.key == pygame.K_RIGHT:
                m=cb.dot(np.array([0.5,0,0]))
                self.pos=OGl.three_func(self.pos,m, lambda x,y : x+y )
            if event.key == pygame.K_DOWN:
                m=cb.dot(np.array([0,-0.5,0]))
                self.pos=OGl.three_func(self.pos,m, lambda x,y : x+y )
            if event.key == pygame.K_UP:
                m=cb.dot(np.array([0,0.5,0]))
                self.pos=OGl.three_func(self.pos,m, lambda x,y : x+y )

        if event.type == pygame.MOUSEMOTION and self.rotating:
            tmp_pos = pygame.mouse.get_pos()
            x,y = self.mouse_pos[0] - tmp_pos[0], self.mouse_pos[1] - tmp_pos[1]
            if x != 0 or y != 0:
                self.rot[1] = (self.rot[1] + x)
                self.rot[0] = (self.rot[0] + y)
                self.mouse_pos = tmp_pos

        if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
            if self.rotating == False:
                self.rotating = True
                self.mouse_pos = pygame.mouse.get_pos()

        if event.type == pygame.MOUSEBUTTONUP and event.button == 1:
            self.rotating = False

def Cube():
    glBegin(GL_LINES)
    for edge in edges:
        for vertex in edge:
            glVertex3fv(verticies[vertex])
    glEnd()

def main():
    pygame.init()
    display = (800,600)
    pygame.display.set_mode(display, DOUBLEBUF|OPENGL)
    camera = GLCamera()

    while True:
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        gluPerspective(45, (display[0]/display[1]), 0.1, 50.0)
        camera.add_to_scene()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()
            camera.handle_camera_events(event)     

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

main()

Solution

  • You are close, but the computation change_of_basis is not correct. The view matrix is defined by:

    glRotatef(self.rot[2], 0, 0, 1);  # roll
    glRotatef(self.rot[1], 0, 1, 0); # heading
    glRotatef(self.rot[0], 1, 0, 0);  # pitch
    glTranslatef(-self.pos[0],-self.pos[1],-self.pos[2])
    

    If you want to move the object relative to the view space, then you have to transform the movement vectors in view space to world space. Then change the world space position of the object.
    Since the view matrix transforms from world space to view space, the vectors have to be transformed by the inverse view matrix.

    Reconstruct the orientation of the view matrix and compute the inverse matrix of the view matrix. Using NumPy, the inverse matrix can be computed by numpy.linalg.inv(a). The rotation matrices matrices for the x, y and z axis can be concatenated by either numpy.matmul or an operator. Note, for array, * means element-wise multiplication, while @ means matrix multiplication. See array.

    Change change_of_basis to solve the issue:

    class GLCamera():
    
        # [...]
    
        def change_of_basis(self):
            #skip roll for now
            rx, ry, rz = [self.rot[i]*np.pi/180 for i in range(3)] 
            s, c = np.sin(rx), np.cos(rx)
            mx = np.array([[1,0,0],[0,c,-s],[0,s,c]])
            s, c = np.sin(ry), np.cos(ry)
            my = np.array([[c,0,s],[0,1,0],[-s,0,c]])
            s, c = np.sin(rz), np.cos(rz)
            mz = np.array([[c,-s,0],[s,c,0],[0,0,1]])
            m = my @ mx @ mz
            inv_m = np.linalg.inv(m)
            return inv_m