curvedinfinity

January 12th, 2009, 12:51 AM

Hey all,

I was experimenting with Python over the last week, and I made this example app for various 3D stuff. I titled it PolarisGP, and I was making it with lax intentions of making a flight racing game. However, in case I lose interest, I'd like to put it out there for posterity.

It includes some examples of pretty cool techniques:

Basic linear algebra

Quaternion rotation

Axis/magnitude angular velocity

Display lists

.obj file loading

Efficiently extracting "up,left,forward" vectors from a matrix

Using an inverse matrix (in this case, from a quaternion) to create a camera system

Perhaps people can dig up some other useful techniques too...

A description of what this app does: In third-person, you fly your spaceship around a cloud of 1728 (12 to the third power) other spaceships. Controls are WADSQE for rotation and IJKLUM for thrust.

It requires python-opengl

Pics:

http://i39.tinypic.com/258cr9u.png

http://i40.tinypic.com/fdfvxc.png

main.py - the main application. To run, call "python main.py"

#!/usr/bin/python

from OpenGL.GL import *

from OpenGL.GLU import *

from OpenGL.GLUT import *

from Drawable import *

from Actor import *

import time

print "== Controls ===================="

print "Heading: w,a,d,s,q,e"

print "Thrust: i,j,l,k,u,m"

print "================================"

window = 0

screenSize = [640,480]

previousTime = time.time()

frameCounter = 0

frameCounterTimer = time.time()

def display():

global previousTime,frameCounter,frameCounterTimer,envlis t

currentTime = time.time()

deltaTime = (currentTime-previousTime)

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

glPushMatrix()

glMultMatrixf(toMatrixQ(inverseQ(actor.drawable.ro tation)))

camera = inverse3(actor.drawable.position)

camera = add3(camera,scale3(actor.drawable.forward,-3))

camera = add3(camera,scale3(actor.drawable.up,-0.6))

glTranslatef(camera[0],camera[1],camera[2])

glLightfv(GL_LIGHT0, GL_POSITION,[-2.0, 3.0, 3.0, 0.0])

glCallList(envlist)

#for flora in scenery:

# flora.draw()

actor.update(deltaTime)

glPopMatrix()

glutSwapBuffers()

previousTime = currentTime

frameCounter += 1

if currentTime-frameCounterTimer > 1:

print "FPS:",frameCounter

frameCounter = 0

frameCounterTimer = currentTime

def resize(width, height):

if(height==0): height = 1

screenSize = [width, height]

glViewport(0, 0, width, height)

glMatrixMode(GL_PROJECTION)

glLoadIdentity()

gluPerspective(90.0, float(width)/float(height), 0.1, 1000.0)

glMatrixMode(GL_MODELVIEW)

def key(*args):

if args[0] == 's': # rotation

actor.spin(1.5,actor.drawable.left)

elif args[0] == 'w':

actor.spin(-1.5,actor.drawable.left)

elif args[0] == 'a':

actor.spin(2.5,actor.drawable.forward)

elif args[0] == 'd':

actor.spin(-2.5,actor.drawable.forward)

elif args[0] == 'e':

actor.spin(1.5,actor.drawable.up)

elif args[0] == 'q':

actor.spin(-1.5,actor.drawable.up)

elif args[0] == 'k': # movement

actor.push(scale3(actor.drawable.forward,10.0))

elif args[0] == 'i':

actor.push(scale3(actor.drawable.forward,-10.0))

elif args[0] == 'l':

actor.push(scale3(actor.drawable.left,3.0))

elif args[0] == 'j':

actor.push(scale3(actor.drawable.left,-3.0))

elif args[0] == 'u':

actor.push(scale3(actor.drawable.up,3.0))

elif args[0] == 'm':

actor.push(scale3(actor.drawable.up,-3.0))

elif args[0] == '\033': #escape

glutDestroyWindow(window)

sys.exit()

glutInit('')

glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH)

glutInitWindowSize(640,480)

window = glutCreateWindow('PolarisGP')

resize(640,480)

glutDisplayFunc(display)

glutIdleFunc(display)

glutReshapeFunc(resize)

glutIgnoreKeyRepeat(1)

glutKeyboardFunc(key)

glClearColor(1,1,1,1)

glClearDepth(1)

glEnable(GL_LIGHTING)

glEnable(GL_LIGHT0)

glLightfv(GL_LIGHT0, GL_AMBIENT,[0.2, 0.1, 0.1, 1.0])

glLightfv(GL_LIGHT0, GL_DIFFUSE,[1.0, 1.0, 1.0, 1.0])

glLightModelfv(GL_LIGHT_MODEL_AMBIENT,[0.2, 0.2, 0.2, 1.0])

glEnable(GL_DEPTH_TEST)

glDepthFunc(GL_LESS)

glShadeModel(GL_FLAT)

glCullFace(GL_BACK);

glEnable(GL_CULL_FACE);

actor = Actor()

b = -6

r = 12

scenery = []

for x in range(b,b+r):

for y in range(b,b+r):

for z in range(b,b+r):

flora = Drawable()

flora.position = (x*20,y*20,z*20)

scenery.append(flora)

envlist = glGenLists(1)

glNewList(envlist,GL_COMPILE)

for flora in scenery:

flora.draw()

glEndList()

glutMainLoop()

Actor.py - This represents a 3D object with some simple newtonian physics

from math3D import *

from Drawable import *

class Actor:

drawable = None

velocity = (0,0,0)

rvAxis = (1,0,0)

rvMagnitude = 0

friction = 3

rFriction = 3

def __init__(self):

self.drawable = Drawable()

def push(self,v):

self.velocity = add3(self.velocity,v)

def spin(self,mag,axis):

newRV = add3(scale3(self.rvAxis,self.rvMagnitude),scale3(a xis,mag))

self.rvMagnitude = length3(newRV)

self.rvAxis = normalize3(newRV)

def update(self,t):

self.drawable.draw()

self.drawable.move(scale3(self.velocity,t))

if lengthSq3(self.rvAxis)==0:

self.rvAxis = (1,0,0)

rvInstant = fromAngleAxisQ(self.rvMagnitude*t,self.rvAxis[0],self.rvAxis[1],self.rvAxis[2])

self.drawable.rotate(rvInstant)

self.velocity = scale3(self.velocity,1.0-t*self.friction)

self.rvMagnitude = self.rvMagnitude*(1.0-t*self.rFriction)

Drawable.py - this represents a 3D object with position and rotation properties

from OpenGL.GL import *

from Model import *

class Drawable:

position = (0,0,-3)

rotation = zeroQ()

matrix = (1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1)

forward = (0,0,1)

up = (0,1,0)

left = (1,0,0)

color = (0.5,0.4,1,1)

model = None

changed = True

def __init__(self):

self.model = Model("LMP1.obj")

def move(self,v):

self.position = add3(self.position,v)

def rotate(self,q):

self.rotation = multiplyQ(self.rotation,q)

self.changed = True

def draw(self):

glPushMatrix()

glTranslatef(self.position[0],self.position[1],self.position[2])

if self.changed:

self.matrix = toMatrixQ(self.rotation)

self.left = (self.matrix[0],self.matrix[1],self.matrix[2])

self.up = (self.matrix[4],self.matrix[5],self.matrix[6])

self.forward = (self.matrix[8],self.matrix[9],self.matrix[10])

self.changed = False

glMultMatrixf(self.matrix)

glMaterialfv(GL_FRONT, GL_AMBIENT,[0.2,0.2,0.2,0])

glMaterialfv(GL_FRONT, GL_DIFFUSE,self.color)

glMaterialfv(GL_FRONT, GL_SPECULAR,[0.7,0.7,0.7,0])

glMaterialf(GL_FRONT, GL_SHININESS, 20)

self.model.draw()

glPopMatrix()

Model.py - this represents a collection of triangles to be drawn

from OpenGL.GL import *

from math3D import *

class Model:

triangles = []

normals = []

listname = 0

def __init__(self,filepath):

self.loadObj(filepath)

self.makeNormals()

self.createList()

def createList(self):

self.listname = glGenLists(1)

glNewList(self.listname,GL_COMPILE)

self.rawDraw()

glEndList()

def loadObj(self,filepath):

modelFile = open(filepath,"r")

triangles = []

vertices = []

for line in modelFile.readlines():

line = line.strip()

if len(line)==0 or line.startswith("#"):

continue

data = line.split(" ")

if data[0]=="v":

vertices.append((float(data[1]),float(data[2]),float(data[3])))

if data[0]=="f":

vertex1 = vertices[int(data[1].split("/")[0])-1]

vertex2 = vertices[int(data[2].split("/")[0])-1]

vertex3 = vertices[int(data[3].split("/")[0])-1]

triangles.append((vertex1,vertex2,vertex3))

self.triangles = triangles

def makeNormals(self):

normals = []

for triangle in self.triangles:

arm1 = sub3(triangle[1],triangle[0])

arm2 = sub3(triangle[2],triangle[0])

normals.append(normalize3(cross3(arm1,arm2)))

self.normals = normals

def draw(self):

glCallList(self.listname)

def rawDraw(self):

glBegin(GL_TRIANGLES)

i = 0

for triangle in self.triangles:

glNormal3f(self.normals[i][0],self.normals[i][1],self.normals[i][2])

glVertex3f(triangle[0][0],triangle[0][1],triangle[0][2])

glVertex3f(triangle[1][0],triangle[1][1],triangle[1][2])

glVertex3f(triangle[2][0],triangle[2][1],triangle[2][2])

i+=1

glEnd()

math3D.py - 3-vector and quaternion math functions

from math import *

def zero3():

return (0.0,0.0,0.0)

def copy3(v):

return (v[0],v[1],v[2])

def inverse3(v):

return (-v[0],-v[1],-v[2])

def add3(v1,v2):

return (v1[0]+v2[0],v1[1]+v2[1],v1[2]+v2[2])

def sub3(v1,v2):

return (v1[0]-v2[0],v1[1]-v2[1],v1[2]-v2[2])

def scale3(v,s):

return (v[0]*s,v[1]*s,v[2]*s)

def lengthSq3(v):

return v[0]*v[0]+v[1]*v[1]+v[2]*v[2]

def length3(v):

return sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2])

def normalize3(v):

l = length3(v)

if l == 0:

return (0.0,0.0,0.0)

return (v[0]/l,v[1]/l,v[2]/l)

def dot3(v1,v2):

return v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2]

def cross3(v1,v2):

return (v1[1]*v2[2]-v1[2]*v2[1],v1[2]*v2[0]-v1[0]*v2[2],v1[0]*v2[1]-v1[1]*v2[0])

def perpendicular3(v):

if v[1]==0 and v[2]==0:

return cross3(v,add3(v,(0,1,0)))

return cross3(v,add3(v,(1,0,0)))

def zeroQ():

return (1.0,0.0,0.0,0.0)

def copyQ(q):

return (q[0],q[1],q[2],q[3])

def addQ(q1,q2):

return (q1[0]+q2[0],q1[1]+q2[1],q1[2]+q2[2],q1[3]+q2[3])

def subQ(q1,q2):

return (q1[0]-q2[0],q1[1]-q2[1],q1[2]-q2[2],q1[3]-q2[3])

def scaleQ(q,s):

return (q[0]*s,q[1]*s,q[2]*s,q[3]*s)

def magnitudeSqQ(q):

return q[0]*q[0]+q[1]*q[1]+q[2]*q[2]+q[3]*q[3]

def magnitudeQ(q):

return sqrt(q[0]*q[0]+q[1]*q[1]+q[2]*q[2]+q[3]*q[3])

def conjugateQ(q):

return (q[0],-q[1],-q[2],-q[3])

def multiplyQ(q1,q2):

w1,x1,y1,z1 = q1[0],q1[1],q1[2],q1[3]

w2,x2,y2,z2 = q2[0],q2[1],q2[2],q2[3]

return (w1*w2 - x1*x2 - y1*y2 - z1*z2,

w1*x2 + x1*w2 + y1*z2 - z1*y2,

w1*y2 + y1*w2 + z1*x2 - x1*z2,

w1*z2 + z1*w2 + x1*y2 - y1*x2)

def normalizeQ(q):

m = magnitudeQ(q)

if m==0:

return (1.0,0.0,0.0,0.0)

return (q[0]/m,q[1]/m,q[2]/m,q[3]/m)

def inverseQ(q):

m2 = magnitudeSqQ(q)

return (q[0]/m2,-q[1]/m2,-q[2]/m2,-q[3]/m2)

def dotQ(q1,q2):

return q1[0]*q2[0] + q1[1]*q2[1] + q1[2]*q2[2] + q1[3]*q2[3]

def fromAngleAxisQ(radians,x,y,z):

radians/=2.0

s = sin(radians)/sqrt(x*x+y*y+z*z)

return normalizeQ((cos(radians),x*s,y*s,z*s))

def toMatrixQ(q):

w,x,y,z = q[0], q[1], q[2], q[3]

xx = 2.0*x*x

yy = 2.0*y*y

zz = 2.0*z*z

xy = 2.0*x*y

zw = 2.0*z*w

xz = 2.0*x*z

yw = 2.0*y*w

yz = 2.0*y*z

xw = 2.0*x*w

return (1.0-yy-zz, xy-zw, xz+yw, 0.0,

xy+zw, 1.0-xx-zz, yz-xw, 0.0,

xz-yw, yz+xw, 1.0-xx-yy, 0.0,

0.0, 0.0, 0.0, 1.0)

def rotateVectorQ(q,v):

qw, qx, qy, qz = q[0], q[1], q[2], q[3]

x, y, z = v[0], v[1], v[2]

ww = qw*qw

xx = qx*qx

yy = qy*qy

zz = qz*qz

wx = qw*qx

wy = qw*qy

wz = qw*qz

xy = qx*qy

xz = qx*qz

yz = qy*qz

return (ww*x + xx*x - yy*x - zz*x + 2*((xy-wz)*y + (xz+wy)*z),

ww*y - xx*y + yy*y - zz*y + 2*((xy+wz)*x + (yz-wx)*z),

ww*z - xx*z - yy*z + zz*z + 2*((xz-wy)*x + (yz+wx)*y))

def interpolateQ(q1, q2, s, shortest=True):

ca = dotQ(q1,q2)

if shortest and ca<0:

ca = -ca

neg_q2 = True

else:

neg_q2 = False

o = acos(ca)

so = sin(o)

if (abs(so)<=1E-12):

return copyQ(q1)

a = sin(o*(1.0-s)) / so

b = sin(o*s) / so

if neg_q2:

return subQ(scaleQ(q1,a),scaleQ(q2,b))

else:

return addQ(scaleQ(q1,a),scaleQ(q2,b))

LMP1.obj - a simple OBJ-format spaceship model

# Wavefront OBJ file

# Exported by Misfit Model 3D 1.3.6

# Fri Jan 9 22:59:21 2009

# 13 Vertices

v 0.985767 -0.379487 1.043507

v -0.0 0.050998 -1.606737

v -0.0 0.07388 -2.5526

v -0.985767 -0.379487 1.043507

v -0.0 -0.091687 0.61302

v 0.306791 -0.009609 1.272661

v -0.306791 -0.009609 1.272661

v -0.0 0.194027 1.785505

v 0.153395 0.092209 1.529083

v -0.0 0.379487 1.145188

v 0.14346 0.281515 0.886805

v -0.153395 0.092209 1.529083

v -0.14346 0.281515 0.886805

# 60 Texture Coordinates

vt 1.0 0.0

vt 0.0 0.0

vt 0.0 1.0

vt 1.0 0.0

vt 0.0 0.0

vt 0.0 1.0

vt 1.0 0.0

vt 0.0 0.0

vt 0.0 1.0

vt 1.0 0.0

vt 0.0 0.0

vt 0.0 1.0

vt 0.0 1.0

vt 0.0 0.0

vt 1.0 0.0

vt 0.0 1.0

vt 0.0 0.0

vt 1.0 0.0

vt 0.0 1.0

vt 0.0 0.0

vt 1.0 0.0

vt 0.0 1.0

vt 0.0 0.0

vt 1.0 0.0

vt 0.0 1.0

vt 0.0 0.0

vt 1.0 0.0

vt 0.0 1.0

vt 0.0 0.0

vt 1.0 0.0

vt 0.0 1.0

vt 0.0 0.0

vt 1.0 0.0

vt 0.0 1.0

vt 0.0 0.0

vt 1.0 0.0

vt 1.0 0.0

vt 0.0 0.0

vt 0.0 1.0

vt 1.0 0.0

vt 0.0 0.0

vt 0.0 1.0

vt 1.0 0.0

vt 0.0 0.0

vt 0.0 1.0

vt 1.0 0.0

vt 0.0 0.0

vt 0.0 1.0

vt 1.0 0.0

vt 0.0 0.0

vt 0.0 1.0

vt 1.0 0.0

vt 0.0 0.0

vt 0.0 1.0

vt 0.0 1.0

vt 0.0 0.0

vt 1.0 0.0

vt 1.0 0.0

vt 0.0 0.0

vt 0.0 1.0

# ungrouped, 20 grouped triangles

o ungrouped

g ungrouped

f 1/1 2/2 3/3

f 2/4 4/5 3/6

f 5/7 6/8 7/9

f 6/10 8/11 7/12

f 3/13 6/14 1/15

f 11/16 9/17 6/18

f 7/19 2/20 5/21

f 7/22 4/23 2/24

f 10/25 12/26 8/27

f 3/28 4/29 7/30

f 2/31 6/32 5/33

f 2/34 1/35 6/36

f 9/37 10/38 8/39

f 10/40 11/41 3/42

f 11/43 10/44 9/45

f 12/46 13/47 7/48

f 13/49 10/50 3/51

f 10/52 13/53 12/54

f 3/55 7/56 13/57

f 11/58 6/59 3/60

I was experimenting with Python over the last week, and I made this example app for various 3D stuff. I titled it PolarisGP, and I was making it with lax intentions of making a flight racing game. However, in case I lose interest, I'd like to put it out there for posterity.

It includes some examples of pretty cool techniques:

Basic linear algebra

Quaternion rotation

Axis/magnitude angular velocity

Display lists

.obj file loading

Efficiently extracting "up,left,forward" vectors from a matrix

Using an inverse matrix (in this case, from a quaternion) to create a camera system

Perhaps people can dig up some other useful techniques too...

A description of what this app does: In third-person, you fly your spaceship around a cloud of 1728 (12 to the third power) other spaceships. Controls are WADSQE for rotation and IJKLUM for thrust.

It requires python-opengl

Pics:

http://i39.tinypic.com/258cr9u.png

http://i40.tinypic.com/fdfvxc.png

main.py - the main application. To run, call "python main.py"

#!/usr/bin/python

from OpenGL.GL import *

from OpenGL.GLU import *

from OpenGL.GLUT import *

from Drawable import *

from Actor import *

import time

print "== Controls ===================="

print "Heading: w,a,d,s,q,e"

print "Thrust: i,j,l,k,u,m"

print "================================"

window = 0

screenSize = [640,480]

previousTime = time.time()

frameCounter = 0

frameCounterTimer = time.time()

def display():

global previousTime,frameCounter,frameCounterTimer,envlis t

currentTime = time.time()

deltaTime = (currentTime-previousTime)

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

glPushMatrix()

glMultMatrixf(toMatrixQ(inverseQ(actor.drawable.ro tation)))

camera = inverse3(actor.drawable.position)

camera = add3(camera,scale3(actor.drawable.forward,-3))

camera = add3(camera,scale3(actor.drawable.up,-0.6))

glTranslatef(camera[0],camera[1],camera[2])

glLightfv(GL_LIGHT0, GL_POSITION,[-2.0, 3.0, 3.0, 0.0])

glCallList(envlist)

#for flora in scenery:

# flora.draw()

actor.update(deltaTime)

glPopMatrix()

glutSwapBuffers()

previousTime = currentTime

frameCounter += 1

if currentTime-frameCounterTimer > 1:

print "FPS:",frameCounter

frameCounter = 0

frameCounterTimer = currentTime

def resize(width, height):

if(height==0): height = 1

screenSize = [width, height]

glViewport(0, 0, width, height)

glMatrixMode(GL_PROJECTION)

glLoadIdentity()

gluPerspective(90.0, float(width)/float(height), 0.1, 1000.0)

glMatrixMode(GL_MODELVIEW)

def key(*args):

if args[0] == 's': # rotation

actor.spin(1.5,actor.drawable.left)

elif args[0] == 'w':

actor.spin(-1.5,actor.drawable.left)

elif args[0] == 'a':

actor.spin(2.5,actor.drawable.forward)

elif args[0] == 'd':

actor.spin(-2.5,actor.drawable.forward)

elif args[0] == 'e':

actor.spin(1.5,actor.drawable.up)

elif args[0] == 'q':

actor.spin(-1.5,actor.drawable.up)

elif args[0] == 'k': # movement

actor.push(scale3(actor.drawable.forward,10.0))

elif args[0] == 'i':

actor.push(scale3(actor.drawable.forward,-10.0))

elif args[0] == 'l':

actor.push(scale3(actor.drawable.left,3.0))

elif args[0] == 'j':

actor.push(scale3(actor.drawable.left,-3.0))

elif args[0] == 'u':

actor.push(scale3(actor.drawable.up,3.0))

elif args[0] == 'm':

actor.push(scale3(actor.drawable.up,-3.0))

elif args[0] == '\033': #escape

glutDestroyWindow(window)

sys.exit()

glutInit('')

glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH)

glutInitWindowSize(640,480)

window = glutCreateWindow('PolarisGP')

resize(640,480)

glutDisplayFunc(display)

glutIdleFunc(display)

glutReshapeFunc(resize)

glutIgnoreKeyRepeat(1)

glutKeyboardFunc(key)

glClearColor(1,1,1,1)

glClearDepth(1)

glEnable(GL_LIGHTING)

glEnable(GL_LIGHT0)

glLightfv(GL_LIGHT0, GL_AMBIENT,[0.2, 0.1, 0.1, 1.0])

glLightfv(GL_LIGHT0, GL_DIFFUSE,[1.0, 1.0, 1.0, 1.0])

glLightModelfv(GL_LIGHT_MODEL_AMBIENT,[0.2, 0.2, 0.2, 1.0])

glEnable(GL_DEPTH_TEST)

glDepthFunc(GL_LESS)

glShadeModel(GL_FLAT)

glCullFace(GL_BACK);

glEnable(GL_CULL_FACE);

actor = Actor()

b = -6

r = 12

scenery = []

for x in range(b,b+r):

for y in range(b,b+r):

for z in range(b,b+r):

flora = Drawable()

flora.position = (x*20,y*20,z*20)

scenery.append(flora)

envlist = glGenLists(1)

glNewList(envlist,GL_COMPILE)

for flora in scenery:

flora.draw()

glEndList()

glutMainLoop()

Actor.py - This represents a 3D object with some simple newtonian physics

from math3D import *

from Drawable import *

class Actor:

drawable = None

velocity = (0,0,0)

rvAxis = (1,0,0)

rvMagnitude = 0

friction = 3

rFriction = 3

def __init__(self):

self.drawable = Drawable()

def push(self,v):

self.velocity = add3(self.velocity,v)

def spin(self,mag,axis):

newRV = add3(scale3(self.rvAxis,self.rvMagnitude),scale3(a xis,mag))

self.rvMagnitude = length3(newRV)

self.rvAxis = normalize3(newRV)

def update(self,t):

self.drawable.draw()

self.drawable.move(scale3(self.velocity,t))

if lengthSq3(self.rvAxis)==0:

self.rvAxis = (1,0,0)

rvInstant = fromAngleAxisQ(self.rvMagnitude*t,self.rvAxis[0],self.rvAxis[1],self.rvAxis[2])

self.drawable.rotate(rvInstant)

self.velocity = scale3(self.velocity,1.0-t*self.friction)

self.rvMagnitude = self.rvMagnitude*(1.0-t*self.rFriction)

Drawable.py - this represents a 3D object with position and rotation properties

from OpenGL.GL import *

from Model import *

class Drawable:

position = (0,0,-3)

rotation = zeroQ()

matrix = (1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1)

forward = (0,0,1)

up = (0,1,0)

left = (1,0,0)

color = (0.5,0.4,1,1)

model = None

changed = True

def __init__(self):

self.model = Model("LMP1.obj")

def move(self,v):

self.position = add3(self.position,v)

def rotate(self,q):

self.rotation = multiplyQ(self.rotation,q)

self.changed = True

def draw(self):

glPushMatrix()

glTranslatef(self.position[0],self.position[1],self.position[2])

if self.changed:

self.matrix = toMatrixQ(self.rotation)

self.left = (self.matrix[0],self.matrix[1],self.matrix[2])

self.up = (self.matrix[4],self.matrix[5],self.matrix[6])

self.forward = (self.matrix[8],self.matrix[9],self.matrix[10])

self.changed = False

glMultMatrixf(self.matrix)

glMaterialfv(GL_FRONT, GL_AMBIENT,[0.2,0.2,0.2,0])

glMaterialfv(GL_FRONT, GL_DIFFUSE,self.color)

glMaterialfv(GL_FRONT, GL_SPECULAR,[0.7,0.7,0.7,0])

glMaterialf(GL_FRONT, GL_SHININESS, 20)

self.model.draw()

glPopMatrix()

Model.py - this represents a collection of triangles to be drawn

from OpenGL.GL import *

from math3D import *

class Model:

triangles = []

normals = []

listname = 0

def __init__(self,filepath):

self.loadObj(filepath)

self.makeNormals()

self.createList()

def createList(self):

self.listname = glGenLists(1)

glNewList(self.listname,GL_COMPILE)

self.rawDraw()

glEndList()

def loadObj(self,filepath):

modelFile = open(filepath,"r")

triangles = []

vertices = []

for line in modelFile.readlines():

line = line.strip()

if len(line)==0 or line.startswith("#"):

continue

data = line.split(" ")

if data[0]=="v":

vertices.append((float(data[1]),float(data[2]),float(data[3])))

if data[0]=="f":

vertex1 = vertices[int(data[1].split("/")[0])-1]

vertex2 = vertices[int(data[2].split("/")[0])-1]

vertex3 = vertices[int(data[3].split("/")[0])-1]

triangles.append((vertex1,vertex2,vertex3))

self.triangles = triangles

def makeNormals(self):

normals = []

for triangle in self.triangles:

arm1 = sub3(triangle[1],triangle[0])

arm2 = sub3(triangle[2],triangle[0])

normals.append(normalize3(cross3(arm1,arm2)))

self.normals = normals

def draw(self):

glCallList(self.listname)

def rawDraw(self):

glBegin(GL_TRIANGLES)

i = 0

for triangle in self.triangles:

glNormal3f(self.normals[i][0],self.normals[i][1],self.normals[i][2])

glVertex3f(triangle[0][0],triangle[0][1],triangle[0][2])

glVertex3f(triangle[1][0],triangle[1][1],triangle[1][2])

glVertex3f(triangle[2][0],triangle[2][1],triangle[2][2])

i+=1

glEnd()

math3D.py - 3-vector and quaternion math functions

from math import *

def zero3():

return (0.0,0.0,0.0)

def copy3(v):

return (v[0],v[1],v[2])

def inverse3(v):

return (-v[0],-v[1],-v[2])

def add3(v1,v2):

return (v1[0]+v2[0],v1[1]+v2[1],v1[2]+v2[2])

def sub3(v1,v2):

return (v1[0]-v2[0],v1[1]-v2[1],v1[2]-v2[2])

def scale3(v,s):

return (v[0]*s,v[1]*s,v[2]*s)

def lengthSq3(v):

return v[0]*v[0]+v[1]*v[1]+v[2]*v[2]

def length3(v):

return sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2])

def normalize3(v):

l = length3(v)

if l == 0:

return (0.0,0.0,0.0)

return (v[0]/l,v[1]/l,v[2]/l)

def dot3(v1,v2):

return v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2]

def cross3(v1,v2):

return (v1[1]*v2[2]-v1[2]*v2[1],v1[2]*v2[0]-v1[0]*v2[2],v1[0]*v2[1]-v1[1]*v2[0])

def perpendicular3(v):

if v[1]==0 and v[2]==0:

return cross3(v,add3(v,(0,1,0)))

return cross3(v,add3(v,(1,0,0)))

def zeroQ():

return (1.0,0.0,0.0,0.0)

def copyQ(q):

return (q[0],q[1],q[2],q[3])

def addQ(q1,q2):

return (q1[0]+q2[0],q1[1]+q2[1],q1[2]+q2[2],q1[3]+q2[3])

def subQ(q1,q2):

return (q1[0]-q2[0],q1[1]-q2[1],q1[2]-q2[2],q1[3]-q2[3])

def scaleQ(q,s):

return (q[0]*s,q[1]*s,q[2]*s,q[3]*s)

def magnitudeSqQ(q):

return q[0]*q[0]+q[1]*q[1]+q[2]*q[2]+q[3]*q[3]

def magnitudeQ(q):

return sqrt(q[0]*q[0]+q[1]*q[1]+q[2]*q[2]+q[3]*q[3])

def conjugateQ(q):

return (q[0],-q[1],-q[2],-q[3])

def multiplyQ(q1,q2):

w1,x1,y1,z1 = q1[0],q1[1],q1[2],q1[3]

w2,x2,y2,z2 = q2[0],q2[1],q2[2],q2[3]

return (w1*w2 - x1*x2 - y1*y2 - z1*z2,

w1*x2 + x1*w2 + y1*z2 - z1*y2,

w1*y2 + y1*w2 + z1*x2 - x1*z2,

w1*z2 + z1*w2 + x1*y2 - y1*x2)

def normalizeQ(q):

m = magnitudeQ(q)

if m==0:

return (1.0,0.0,0.0,0.0)

return (q[0]/m,q[1]/m,q[2]/m,q[3]/m)

def inverseQ(q):

m2 = magnitudeSqQ(q)

return (q[0]/m2,-q[1]/m2,-q[2]/m2,-q[3]/m2)

def dotQ(q1,q2):

return q1[0]*q2[0] + q1[1]*q2[1] + q1[2]*q2[2] + q1[3]*q2[3]

def fromAngleAxisQ(radians,x,y,z):

radians/=2.0

s = sin(radians)/sqrt(x*x+y*y+z*z)

return normalizeQ((cos(radians),x*s,y*s,z*s))

def toMatrixQ(q):

w,x,y,z = q[0], q[1], q[2], q[3]

xx = 2.0*x*x

yy = 2.0*y*y

zz = 2.0*z*z

xy = 2.0*x*y

zw = 2.0*z*w

xz = 2.0*x*z

yw = 2.0*y*w

yz = 2.0*y*z

xw = 2.0*x*w

return (1.0-yy-zz, xy-zw, xz+yw, 0.0,

xy+zw, 1.0-xx-zz, yz-xw, 0.0,

xz-yw, yz+xw, 1.0-xx-yy, 0.0,

0.0, 0.0, 0.0, 1.0)

def rotateVectorQ(q,v):

qw, qx, qy, qz = q[0], q[1], q[2], q[3]

x, y, z = v[0], v[1], v[2]

ww = qw*qw

xx = qx*qx

yy = qy*qy

zz = qz*qz

wx = qw*qx

wy = qw*qy

wz = qw*qz

xy = qx*qy

xz = qx*qz

yz = qy*qz

return (ww*x + xx*x - yy*x - zz*x + 2*((xy-wz)*y + (xz+wy)*z),

ww*y - xx*y + yy*y - zz*y + 2*((xy+wz)*x + (yz-wx)*z),

ww*z - xx*z - yy*z + zz*z + 2*((xz-wy)*x + (yz+wx)*y))

def interpolateQ(q1, q2, s, shortest=True):

ca = dotQ(q1,q2)

if shortest and ca<0:

ca = -ca

neg_q2 = True

else:

neg_q2 = False

o = acos(ca)

so = sin(o)

if (abs(so)<=1E-12):

return copyQ(q1)

a = sin(o*(1.0-s)) / so

b = sin(o*s) / so

if neg_q2:

return subQ(scaleQ(q1,a),scaleQ(q2,b))

else:

return addQ(scaleQ(q1,a),scaleQ(q2,b))

LMP1.obj - a simple OBJ-format spaceship model

# Wavefront OBJ file

# Exported by Misfit Model 3D 1.3.6

# Fri Jan 9 22:59:21 2009

# 13 Vertices

v 0.985767 -0.379487 1.043507

v -0.0 0.050998 -1.606737

v -0.0 0.07388 -2.5526

v -0.985767 -0.379487 1.043507

v -0.0 -0.091687 0.61302

v 0.306791 -0.009609 1.272661

v -0.306791 -0.009609 1.272661

v -0.0 0.194027 1.785505

v 0.153395 0.092209 1.529083

v -0.0 0.379487 1.145188

v 0.14346 0.281515 0.886805

v -0.153395 0.092209 1.529083

v -0.14346 0.281515 0.886805

# 60 Texture Coordinates

vt 1.0 0.0

vt 0.0 0.0

vt 0.0 1.0

vt 1.0 0.0

vt 0.0 0.0

vt 0.0 1.0

vt 1.0 0.0

vt 0.0 0.0

vt 0.0 1.0

vt 1.0 0.0

vt 0.0 0.0

vt 0.0 1.0

vt 0.0 1.0

vt 0.0 0.0

vt 1.0 0.0

vt 0.0 1.0

vt 0.0 0.0

vt 1.0 0.0

vt 0.0 1.0

vt 0.0 0.0

vt 1.0 0.0

vt 0.0 1.0

vt 0.0 0.0

vt 1.0 0.0

vt 0.0 1.0

vt 0.0 0.0

vt 1.0 0.0

vt 0.0 1.0

vt 0.0 0.0

vt 1.0 0.0

vt 0.0 1.0

vt 0.0 0.0

vt 1.0 0.0

vt 0.0 1.0

vt 0.0 0.0

vt 1.0 0.0

vt 1.0 0.0

vt 0.0 0.0

vt 0.0 1.0

vt 1.0 0.0

vt 0.0 0.0

vt 0.0 1.0

vt 1.0 0.0

vt 0.0 0.0

vt 0.0 1.0

vt 1.0 0.0

vt 0.0 0.0

vt 0.0 1.0

vt 1.0 0.0

vt 0.0 0.0

vt 0.0 1.0

vt 1.0 0.0

vt 0.0 0.0

vt 0.0 1.0

vt 0.0 1.0

vt 0.0 0.0

vt 1.0 0.0

vt 1.0 0.0

vt 0.0 0.0

vt 0.0 1.0

# ungrouped, 20 grouped triangles

o ungrouped

g ungrouped

f 1/1 2/2 3/3

f 2/4 4/5 3/6

f 5/7 6/8 7/9

f 6/10 8/11 7/12

f 3/13 6/14 1/15

f 11/16 9/17 6/18

f 7/19 2/20 5/21

f 7/22 4/23 2/24

f 10/25 12/26 8/27

f 3/28 4/29 7/30

f 2/31 6/32 5/33

f 2/34 1/35 6/36

f 9/37 10/38 8/39

f 10/40 11/41 3/42

f 11/43 10/44 9/45

f 12/46 13/47 7/48

f 13/49 10/50 3/51

f 10/52 13/53 12/54

f 3/55 7/56 13/57

f 11/58 6/59 3/60