MD2 (and other format) 3D model file parsers

275 views
Skip to first unread message

Parsha Pourkhomami

unread,
Apr 14, 2009, 5:31:05 PM4/14/09
to pyglet-users
Hello,

I am looking for a MD2 parser for use in a Pyglet project. Although at
this point I will be happy with even a generic MD2 parser written in
Python that I can adapt for use in Pyglet.

I am also interested in looking at parsers written for other 3D model
formats. I was able to find a Wavefront OBJ parser in /contrib/model/
in the Pyglet SVN (http://code.google.com/p/pyglet/source/browse/trunk/
contrib/model/model/obj.py) but have not been able to find parsers for
any other formats. I am particularly looking for formats with
animation support.

If anyone has written their own parser or has any insightful direction
I would love to hear it before I go down the path of writing my own
parser. If it comes down to it, I imagine it would not be the end of
the world to adapt the OBJ parser to parse MD2 files.

Derick

unread,
Apr 16, 2009, 1:39:09 PM4/16/09
to pyglet-users

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)

Parsha Pourkhomami

unread,
Apr 18, 2009, 5:37:25 PM4/18/09
to pyglet-users
Thank you, Derick. This looks like it may be very useful if I go the
route of MD2.
Reply all
Reply to author
Forward
0 new messages