To main page | 3dengine.org

# FPS Spectator code (PyOpenGL)

{ Maybe you were looking for frames-per-second? }

This is how to do FPS (First person shooter) spectator code in PyOpenGL, also known as free look or mouselook using only matrix math.

## How to use it

The Spectator class is defined below ("Working spectator code in Python using matrix math" + "Full Example"). Then use 'w','s','a','d' and mouse look.
```fps = Spectator(w = 640, h = 480, fov = 75)
fps.simple_lights()  # optional
fps.simple_camera_pose() # optional

while fps.loop():
fps.draw_simple_cube() # optional
fps.controls_3d(0,'w','s','a','d')
if fps.keys['q']: break
```

## How does it work (in pseudo code)

Basically (in pseudo code)
```m = glGetDoublev(GL_MODELVIEW_MATRIX)
if mouse_drag:
glTranslate(-camera_center_in_3d)
glRotate(mouse_dx * look_speed, m[1],m[5],m[9]) # [1]
glRotate(mouse_dy * look_speed, m[0],m[4],m[8]) # [1]
# compensate roll - make sure camera's "up" is actually upwards
glRotate(math.atan2(-m[4],m[5]) * 57.29577,m[2],m[6],m[10])
glTranslate(camera_center_in_3d)
if key['w' or 's']:
glTranslate(m[2],m[6],m[10]) # fwd vector [1]
if key['a' or 'd']:
glTranslate(m[0],m[4],m[8]) # right vector [1]
```
[1] Right, up, back vectors in modelview matrix

## Working spectator code in Python using matrix math

You will need PyGame, PyOpenGL and NumPy. (You'll need those anyway if you want to develop OpenGL in Python)

## Full example

This code uses only OpenGL matrices without having to store heading-pitch-roll angles, quaternions or even your position in space - pure matrix math.
```from __future__ import division
import pygame, math, numpy
from OpenGL.GL import *
from OpenGL.GLU import *

class Spectator:
def __init__(self, w=640, h=480, fov=75):
pygame.init()
pygame.display.set_mode((640,480), pygame.OPENGL | \
pygame.DOUBLEBUF)
glMatrixMode(GL_PROJECTION)
aspect = w/h
gluPerspective(fov, aspect, 0.001, 100000.0);
glMatrixMode(GL_MODELVIEW)

def simple_lights(self):
glEnable(GL_LIGHTING)
glEnable(GL_LIGHT0)
glLightfv(GL_LIGHT0, GL_DIFFUSE, (0.9, 0.45, 0.0, 1.0))
glLightfv(GL_LIGHT0, GL_POSITION, (0.0, 10.0, 10.0, 10.0))
glEnable(GL_DEPTH_TEST)
glDepthFunc(GL_LEQUAL)

def simple_camera_pose(self):
""" Pre-position the camera (optional) """
glMatrixMode(GL_MODELVIEW)
0,-0.671,-0.403,0.622,0,-0.649,1.72,-4.05,1]))

def draw_simple_cube(self):
""" Draw a simple object (optional) """
try:
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointerf( self.points )
glEnableClientState(GL_NORMAL_ARRAY);
glNormalPointerf( self.normals )
glDrawElementsui(GL_TRIANGLES, self.indices)
except AttributeError:
# a little hack to initialize points only once
self.points=numpy.array([2.4,0,0,0,0,2,0,0,0,0,0,2,2.4,0,0,\
2.4,0,2,0,0,2,0,-1.66,0,0,0,0,0,-1.66,0,2.4,0,0,0,0,0,\
2.4,0,0,2.4,-1.66,2,2.4,0,2,2.4,-1.66,2,0,0,2,2.4,0,2,0,\
-1.66,0,0,0,2,0,-1.66,2,2.4,0,0,0,-1.66,0,2.4,-1.66,0,\
2.4,-1.66,2,2.4,0,0,2.4,-1.66,0,0,0,2,2.4,-1.66,2,0,\
-1.66,2,2.4,-1.66,2,0,-1.66,0,0,-1.66,2,0,-1.66,0,2.4,
-1.66,2,2.4,-1.66,0],'f').reshape(-1,3)

self.normals=numpy.array([0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,\
1,0,-1,0,0,-1,0,0,-1,0,0,0,0,-1,0,0,-1,0,0,-1,1,0,0,1,\
0,0,1,0,0,0,0,1,0,0,1,0,0,1,-1,0,0,-1,0,0,-1,0,0,0,0,\
-1,0,0,-1,0,0,-1,1,0,0,1,0,0,1,0,0,0,0,1,0,0,1,0,0,1,\
0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0 \
],'f').reshape(-1,3)

self.indices=numpy.array([0,1,2,3,4,5,6,7,8,9,10,11,12,13,\
14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,\
32,33,34,35],'i')

def loop(self):
pygame.display.flip()
pygame.event.pump()
self.keys = dict((chr(i),int(v)) for i,v in \
enumerate(pygame.key.get_pressed()) if i<256)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
return True

def controls_3d(self,mouse_button=1,w_key='w',s_key='s',a_key='a',\
d_key='d'):
""" The actual camera setting cycle """
mouse_dx,mouse_dy = pygame.mouse.get_rel()
if pygame.mouse.get_pressed()[mouse_button]:
look_speed = .2
buffer = glGetDoublev(GL_MODELVIEW_MATRIX)
c = (-1 * numpy.mat(buffer[:3,:3]) * \
numpy.mat(buffer[3,:3]).T).reshape(3,1)
# c is camera center in absolute coordinates,
# we need to move it back to (0,0,0)
# before rotating the camera
glTranslate(c[0],c[1],c[2])
m = buffer.flatten()
glRotate(mouse_dx * look_speed, m[1],m[5],m[9])
glRotate(mouse_dy * look_speed, m[0],m[4],m[8])

# compensate roll
glRotated(-math.atan2(-m[4],m[5]) * \
57.295779513082320876798154814105 ,m[2],m[6],m[10])
glTranslate(-c[0],-c[1],-c[2])

# move forward-back or right-left
# fwd =   .1 if 'w' is pressed;   -0.1 if 's'
fwd = .1 * (self.keys[w_key]-self.keys[s_key])
strafe = .1 * (self.keys[a_key]-self.keys[d_key])
if abs(fwd) or abs(strafe):
m = glGetDoublev(GL_MODELVIEW_MATRIX).flatten()
glTranslate(fwd*m[2],fwd*m[6],fwd*m[10])
glTranslate(strafe*m[0],strafe*m[4],strafe*m[8])

fps = Spectator(w = 640, h = 480, fov = 75)
fps.simple_lights()
fps.simple_camera_pose()

while fps.loop():
fps.draw_simple_cube()
fps.controls_3d(0,'w','s','a','d')
if fps.keys['q']: break
```

## How to find position in space

Just in case you missed this in the code:
```buffer = glGetDoublev(GL_MODELVIEW_MATRIX)
c = (-1 * numpy.mat(buffer[:3,:3]) * \
numpy.mat(buffer[3,:3]).T).reshape(3,1)
```