Here's an MD2 file loader I wrote in Python. I haven't looked at in
years, so I'm sure there's some work that needs to be done on it.
Also, this was written against PyOpenGL (I'm pretty sure pyglet wasn't
around yet), but it shouldn't be too difficult to adapt, as most of it
is just file parsing. I seem to recall loading (more specifically, the
display list compilation) was pretty slow, so you'll probably want to
look at improving that as well.
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
from struct import *
# ID value for MD2 model files
MD2_ID = 844121161
# The states available for MD2 models
IDLE1 = 0
RUN = 1
SHOT_STAND = 2
SHOT_SHOULDER = 3
JUMP = 4
IDLE2 = 5
SHOT_FALLDOWN = 6
IDLE3 = 7
IDLE4 = 8
CROUCH = 9
CROUCH_CRAWL = 10
CROUCH_IDLE = 11
CROUCH_DEATH = 12
DEATH_FALL_BACK = 13
DEATH_FALL_FORWARD = 14
DEATH_FALL_BACK_SLOW = 15
# Frame ranges for the states
IDLE1_START = 0
IDLE1_END = 38
RUN_START = 40
RUN_END = 44
SHOT_STAND_START = 47
SHOT_STAND_END = 60
SHOT_SHOULDER_START = 61
SHOT_SHOULDER_END = 64
JUMP_START = 65
JUMP_END = 73
IDLE2_START = 74
IDLE2_END = 95
SHOT_FALLDOWN_START = 96
SHOT_FALLDOWN_END = 112
IDLE3_START = 113
IDLE3_END = 122
IDLE4_START = 123
IDLE4_END = 135
CROUCH_START = 136
CROUCH_END = 154
CROUCH_CRAWL_START = 155
CROUCH_CRAWL_END = 161
CROUCH_IDLE_START = 162
CROUCH_IDLE_END = 169
CROUCH_DEATH_START = 170
CROUCH_DEATH_END = 177
DEATH_FALL_BACK_START = 178
DEATH_FALL_BACK_END = 185
DEATH_FALL_FORWARD_START = 186
DEATH_FALL_FORWARD_END = 190
DEATH_FALL_BACK_SLOW_START = 191
DEATH_FALL_BACK_SLOW_END = 198
class MD2Vertex:
""" Vertex data """
def __init__(self):
self.vertex = [0.0, 0.0, 0.0]
self.lightNormal = 0
class MD2Frame:
""" Frame data """
def __init__(self):
self.scale = [0.0, 0.0, 0.0]
self.translate = [0.0, 0.0, 0.0]
self.name = ''
self.vertices = []
class MD2Mesh:
""" Mesh data """
def __init__(self):
self.pos = [0.0, 0.0, 0.0]
self.u = 0.0
self.v = 0.0
class MD2Header:
""" Information for an MD2 file header """
def __init__(self):
self.id = 0
self.version = 0
self.skinWidth = 0
self.skinHeight = 0
self.frameSize = 0
self.numSkins = 0
self.numVerts = 0
self.numTexCoords = 0
self.numTriangles = 0
self.numGLCmds = 0
self.numFrames = 0
self.offsetSkins = 0
self.offsetTexCoords = 0
self.offsetTriangles = 0
self.offsetFrames = 0
self.offsetGLCmds = 0
self.offsetEnd = 0
class MD2Model(object):
""" Data for an MD2 model """
def __init__(self):
object.__init__(self)
self._fileName = None
self._numFrames = 0
self._frameSize = 0
self._frames = []
self._vertList = []
self._numGLCmds = 0
self._glCmds = []
self._numTriangles = 0
for i in range(100):
self._vertList.append(MD2Mesh())
self._lists = 0
def load(self, fileName):
""" Load the model data """
f = open(fileName, 'rb')
fileData = f.read()
f.close()
header = MD2Header()
# Read in the header information
header.id = (unpack('i', fileData[0:4]))[0]
if
header.id != MD2_ID:
return False
header.version = (unpack('i', fileData[4:8]))[0]
if header.version != 8:
return False
self._fileName = fileName
header.skinWidth = (unpack('i', fileData[8:12]))[0]
header.skinHeight = (unpack('i', fileData[12:16]))[0]
header.frameSize = (unpack('i', fileData[16:20]))[0]
header.numSkins = (unpack('i', fileData[20:24]))[0]
header.numVerts = (unpack('i', fileData[24:28]))[0]
header.numTexCoords = (unpack('i', fileData[28:32]))[0]
header.numTriangles = (unpack('i', fileData[32:36]))[0]
header.numGLCmds = (unpack('i', fileData[36:40]))[0]
header.numFrames = (unpack('i', fileData[40:44]))[0]
header.offsetSkins = (unpack('i', fileData[44:48]))[0]
header.offsetTexCoords = (unpack('i', fileData[48:52]))[0]
header.offsetTriangles = (unpack('i', fileData[52:56]))[0]
header.offsetFrames = (unpack('i', fileData[56:60]))[0]
header.offsetGLCmds = (unpack('i', fileData[60:64]))[0]
header.offsetEnd = (unpack('i', fileData[64:68]))[0]
# Read in the frame data
for i in range(header.numFrames):
frameInfo = fileData[header.offsetFrames + (i *
header.frameSize):header.offsetFrames + ((i + 1) * header.frameSize)]
frame = MD2Frame()
frame.scale[0] = (unpack('f', frameInfo[0:4]))[0]
frame.scale[1] = (unpack('f', frameInfo[4:8]))[0]
frame.scale[2] = (unpack('f', frameInfo[8:12]))[0]
frame.translate[0] = (unpack('f', frameInfo[12:16]))[0]
frame.translate[1] = (unpack('f', frameInfo[16:20]))[0]
frame.translate[2] = (unpack('f', frameInfo[20:24]))[0]
name = unpack('16B', frameInfo[24:40])
for c in range(16):
frame.name =
frame.name + chr(name[c])
for j in range(40, len(frameInfo), 4):
vertex = MD2Vertex()
vertex.vertex[0] = (unpack('B', frameInfo[j]))[0]
vertex.vertex[1] = (unpack('B', frameInfo[j + 1]))[0]
vertex.vertex[2] = (unpack('B', frameInfo[j + 2]))[0]
vertex.lightNormal = (unpack('B', frameInfo[j + 3]))
[0]
frame.vertices.append(vertex)
self._frames.append(frame)
cmdInfo = fileData[header.offsetGLCmds:header.offsetGLCmds +
(header.numGLCmds * 4)]
i = 0
# Read in the vertex data
while i < len(cmdInfo):
# Number of vertices
numVerts = (unpack('l', cmdInfo[i:i + 4]))[0]
self._glCmds.append(numVerts)
i += 4
if numVerts < 0:
numVerts = -numVerts
# Texture coordinates
for j in range(numVerts):
self._glCmds.append((unpack('f', cmdInfo[i:i + 4]))
[0])
self._glCmds.append((unpack('f', cmdInfo[i + 4:i + 8]))
[0])
# Vertex index
self._glCmds.append((unpack('l', cmdInfo[i + 8:i +
12]))[0])
i += 12
self._numFrames = header.numFrames
self._numGLCmds = header.numGLCmds
self._frameSize = header.frameSize
self._numTriangles = header.numTriangles
self._lists = glGenLists(self._numFrames)
# Compile the model vertex data into display lists
for i in range(self._numFrames):
glNewList(self._lists + i, GL_COMPILE)
self.compile(i)
glEndList()
return True
def compile(self, frame):
""" Compile a frame's data into a display list """
texCoord = [0.0, 0.0]
glDisable(GL_CULL_FACE)
glDisable(GL_BLEND)
glEnable(GL_DEPTH_TEST)
glCmd = 0
while self._glCmds[glCmd] != 0:
if self._glCmds[glCmd] > 0:
numVert = self._glCmds[glCmd]
glCmd += 1
type = 0
else:
numVert = -self._glCmds[glCmd]
glCmd += 1
type = 1
if numVert < 0:
numVert = -numVert
index = 0
for i in range(numVert):
self._vertList[index].u = self._glCmds[glCmd]
glCmd += 1
self._vertList[index].v = self._glCmds[glCmd]
glCmd += 1
vertIndex = self._glCmds[glCmd]
glCmd += 1
self._vertList[index].pos[0] = (self._frames
[frame].vertices[vertIndex].vertex[0] * self._frames[frame].scale[0])
+ self._frames[frame].translate[0]
self._vertList[index].pos[1] = (self._frames
[frame].vertices[vertIndex].vertex[1] * self._frames[frame].scale[1])
+ self._frames[frame].translate[1]
self._vertList[index].pos[2] = (self._frames
[frame].vertices[vertIndex].vertex[2] * self._frames[frame].scale[2])
+ self._frames[frame].translate[2]
index += 1
if type == 0:
glBegin(GL_TRIANGLE_STRIP)
for i in range(index):
vert = self._vertList[i].pos
texCoord[0] = self._vertList[i].u
texCoord[1] = self._vertList[i].v
glTexCoord2f(texCoord[0], texCoord[1])
glVertex3f(vert[0], vert[1], vert[2])
glEnd()
else:
glBegin(GL_TRIANGLE_FAN)
for i in range(index):
vert = self._vertList[i].pos
texCoord[0] = self._vertList[i].u
texCoord[1] = self._vertList[i].v
glTexCoord2f(texCoord[0], texCoord[1])
glVertex3f(vert[0], vert[1], vert[2])
glEnd()
# Draw the current frame
def draw(self, frame):
glCallList(self._lists + frame)