I have a field of cubes created in OpenGL and I have walking around working as expected, and part of rotating the "camera" is working until I try to look up or down.
I have a snippet of code that kind of works:
if pressed[pygame.K_UP] or pressed[pygame.K_DOWN]:
rotx = cos(rot/radian)
rotz = sin(rot/radian)
if pressed[pygame.K_UP]:
glRotatef(speed / 2, -rotx, 0, rotz)
if pressed[pygame.K_DOWN]:
glRotatef(speed / 2, rotx, 0, -rotz)
but it only works when rot is 0. so when I first run the program I can look up and down if I only move side to side, and dont look left or right, or move forwards and backwards.
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(tX, tY, tZ):
glBegin(GL_LINES)
for edge in edges:
for vertex in edge:
glVertex3f(verticies[vertex][0] + tX, verticies[vertex][1] + tY, verticies[vertex][2] + tZ)
glEnd()
def main():
pygame.init()
screenSize = (1500, 800)
pygame.display.set_mode(screenSize, DOUBLEBUF|OPENGL)
gluPerspective(45, (screenSize[0]/screenSize[1]), 0.1, 50.0)
rot = 0
speed = 3
radian = 57.2958
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
pressed = pygame.key.get_pressed()
#==# Rotation with arrow keys #==#
if pressed[pygame.K_LEFT]:
glRotatef(speed / 2, 0, -1, 0)
rot += 1
if pressed[pygame.K_RIGHT]:
glRotatef(speed / 2, 0, 1, 0)
rot -= 1
if pressed[pygame.K_UP] or pressed[pygame.K_DOWN]:
rotx = cos(rot/radian)
rotz = sin(rot/radian)
if pressed[pygame.K_UP]:
glRotatef(speed / 2, -rotx, 0, rotz)
if pressed[pygame.K_DOWN]:
glRotatef(speed / 2, rotx, 0, -rotz)
#==# Walking with WASD #==#
if pressed[pygame.K_w]:
glTranslate(sin(rot/radian) / speed, 0, cos(rot/radian) / speed)
if pressed[pygame.K_s]:
glTranslate(-sin(rot/radian) / speed, 0, -cos(rot/radian) / speed)
if pressed[pygame.K_a]:
glTranslate(sin((rot + 90)/radian) / speed, 0, cos((rot + 90)/radian) / speed)
if pressed[pygame.K_d]:
glTranslate(-sin((rot + 90)/radian) / speed, 0, -cos((rot + 90)/radian) / speed)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
for i in range(8):
for j in range(8):
Cube(-i*2.5, -4, -j*2.5)
pygame.display.flip()
pygame.time.wait(10)
main()
I thought this would work as the movement and camera in an FPS game, but it doesn't.
It's all a matter of the order. OpenGL is a state engine. Each operation changes a state. When you do a operation like glTranslatef
or glRotatef
then the current matrix on the matrix stack is changed.
In OpenGL there are different matrices, like the model view matrix and the projection matrix. The first thing you've to do is to separate the projection matrix and model vie matrix. THis can be done by setting the matrix mode (See glMatrixMode
):
glMatrixMode(GL_PROJECTION)
gluPerspective(45, (screenSize[0]/screenSize[1]), 0.1, 50.0)
glMatrixMode(GL_MODELVIEW)
# [...]
When the a matrix operation is applied to the matrix stack, then the current matrix is multiplied by the new (additional) matrix, which is defined by the operation. This means glRotatef
followed by glTranslatef
dies:
current_matrix = current_matrix * rotation_matrix * translation_matrix
The issue is that if you want to apply a new translation or rotation in a first person view, the the new transformation has to be applied on the current view (current model view matrix). This mean the operations have to be performed in the opposite order, than the matrix operation does:
current_matrix = rotation_matrix * translation_matrix * current_matrix
You've tried to compensate this, by considering the current direction of view, which you've calculated by trigonometric functions. But there is alternative solution:
glGetFloatv(GL_MODELVIEW_MATRIX, ...)
glLoadIdentity
glTranslatef
/ glRotatef
)glMultMatrix
current_mv_mat = (GLfloat * 16)()
glGetFloatv(GL_MODELVIEW_MATRIX, current_mv_mat)
glLoadIdentity()
# [...] glTranslatef, glRotatef
glMultMatrixf(mv)
Even more trick is the up and down rotation which has to be performed on the current view, but sadly it should not be stated in on in the current matrix, since the movement and respectively left and right rotation should not depend on the up and down view:
current_matrix = rotation_matrix * translation_matrix * current_matrix
mdel_view_matrix = roate_updown * current_matrix
Fortunately the current matrix is manged on a stack and can be pushed and popped by glPushMatrix
/ glPopMatrix
. The up ad down rotation has to be summed up and finally applied to the view:
glPushMatrix()
current_mv_mat = (GLfloat * 16)()
glGetFloatv(GL_MODELVIEW_MATRIX, current_mv_mat)
glLoadIdentity()
glRotatef(sum_rot_updown, 1, 0, 0)
glMultMatrixf(mv)
# [...] draw all the objects of the scene
glPopMatrix()
See the example, where I applied the suggestions to your original code:
def main():
pygame.init()
screenSize = (1500, 800)
pygame.display.set_mode(screenSize, DOUBLEBUF|OPENGL)
glMatrixMode(GL_PROJECTION)
gluPerspective(45, (screenSize[0]/screenSize[1]), 0.1, 50.0)
glMatrixMode(GL_MODELVIEW)
rot = 0
speed = 3
radian = 57.2958
sum_rot_updown = 0
current_mv_mat = (GLfloat * 16)()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
pressed = pygame.key.get_pressed()
glGetFloatv(GL_MODELVIEW_MATRIX, current_mv_mat)
glLoadIdentity()
#==# Rotation left and right with arrow keys #==#
if pressed[pygame.K_LEFT]:
glRotatef(speed / 2, 0, -1, 0)
rot += 1
if pressed[pygame.K_RIGHT]:
glRotatef(speed / 2, 0, 1, 0)
rot -= 1
#==# Walking with WASD #==#
if pressed[pygame.K_w]:
glTranslate(0, 0, 1/speed)
if pressed[pygame.K_s]:
glTranslate(0, 0, -1/speed)
if pressed[pygame.K_a]:
glTranslate(1/speed, 0, 0)
if pressed[pygame.K_d]:
glTranslate(-1/speed, 0, 0)
glMultMatrixf(current_mv_mat)
#==# Rotation up and down with arrow keys #==#
if pressed[pygame.K_UP]:
sum_rot_updown -= speed / 2
if pressed[pygame.K_DOWN]:
sum_rot_updown += speed / 2
glPushMatrix()
glGetFloatv(GL_MODELVIEW_MATRIX, current_mv_mat)
glLoadIdentity()
glRotatef(sum_rot_updown, 1, 0, 0)
glMultMatrixf(current_mv_mat)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
for i in range(8):
for j in range(8):
Cube(-i*2.5, -4, -j*2.5)
glPopMatrix()
pygame.display.flip()
pygame.time.wait(10)
main()