From 35d77c2bc04aeff93d91532c6fd808b53e2a2677 Mon Sep 17 00:00:00 2001 From: Quantum Date: Sat, 23 Nov 2013 12:36:27 -0500 Subject: [PATCH] Backported all of Cython model loader to Python. Signed-off-by: Xiaomao Chen --- punyverse/_model.pyx | 2 - punyverse/model.py | 442 +++++++++++++++++++++++-------------------- 2 files changed, 241 insertions(+), 203 deletions(-) diff --git a/punyverse/_model.pyx b/punyverse/_model.pyx index 7d9d462..163dcb7 100644 --- a/punyverse/_model.pyx +++ b/punyverse/_model.pyx @@ -328,8 +328,6 @@ cpdef int model_list(WavefrontObject model, float sx=1, float sy=1, float sz=1, glDisable(GL_TEXTURE_2D) if g.material is not None: - if g.material.texture is not None: - tex_id = load_texture(os.path.join(model.root, g.material.texture)) if g.material.Ka: kx, ky, kz = g.material.Ka glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [kx, ky, kz, 1]) diff --git a/punyverse/model.py b/punyverse/model.py index fcaacee..7753bcb 100644 --- a/punyverse/model.py +++ b/punyverse/model.py @@ -1,14 +1,246 @@ -from time import clock -import os.path +from punyverse.texture import load_texture from pyglet.gl import * +from uuid import uuid4 +import os +import gzip +import bz2 +import zipfile + + +def zip_open(file): + zip = zipfile.ZipFile(file) + return zip.open(zip.namelist()[0]) + +openers = { + 'gz': gzip.open, + 'bz2': bz2.BZ2File, + 'zip': zip_open, +} -from punyverse.texture import load_texture FACE_TRIANGLES = 0 FACE_QUADS = 1 +class Face(object): + def __init__(self, type, verts, norms, texs, vertices, normals, textures): + self.type = type + self.verts = verts + self.norms = norms + self.texs = texs + self.vertices = vertices + self.normals = normals + self.textures = textures + + +class Material(object): + def __init__(self, name, texture=None, Ka=(0, 0, 0), Kd=(0, 0, 0), Ks=(0, 0, 0), shininess=0.0): + self.name = name + self.texture = texture + self.Ka = Ka + self.Kd = Kd + self.Ks = Ks + self.shininess = shininess + + +class Group(object): + def __init__(self, name=None): + if name is None: + self.name = str(uuid4()) + else: + self.name = name + self.min = () + self.material = None + self.faces = [] + self.indices = [] + self.vertices = [] + self.normals = [] + self.textures = [] + self.idx_count = 0 + + def pack(self): + min_x, min_y, min_z = 0, 0, 0 + for face in self.faces: + for x, y, z in face.vertices: + min_x = max(min_x, abs(x)) + min_y = max(min_y, abs(y)) + min_z = max(min_x, abs(z)) + self.min = (min_x, min_y, min_z) + + +class WavefrontObject(object): + def __init__(self, path): + self.path = path + self.root = os.path.abspath(os.path.dirname(path)) + self.vertices = [] + self.normals = [] + self.textures = [] + self.groups = [] + self.materials = {} + + self.perform_io(self.path) + + def new_material(self, words): + material = Material(words[1]) + self.materials[words[1]] = material + self.current_material = material + + def Ka(self, words): + self.current_material.Ka = (float(words[1]), float(words[2]), float(words[3])) + + def Kd(self, words): + self.current_material.Kd = (float(words[1]), float(words[2]), float(words[3])) + + def Ks(self, words): + self.current_material.Ks = (float(words[1]), float(words[2]), float(words[3])) + + def material_shininess(self, words): + self.current_material.shininess = min(float(words[1]), 125) + + def material_texture(self, words): + self.current_material.texture = words[-1] + + def vertex(self, words): + self.vertices.append((float(words[1]), float(words[2]), float(words[3]))) + + def normal(self, words): + self.normals.append((float(words[1]), float(words[2]), float(words[3]))) + + def texture(self, words): + l = len(words) + x, y, z = 0, 0, 0 + if l >= 2: + x = float(words[1]) + if l >= 3: + # OBJ origin is at upper left, OpenGL origin is at lower left + y = 1 - float(words[2]) + if l >= 4: + z = float(words[3]) + self.textures.append((x, y, z)) + + def face(self, words): + l = len(words) + type = -1 + vertex_count = l - 1 + face_vertices = [] + face_normals = [] + face_textures = [] + + if vertex_count == 3: + type = FACE_TRIANGLES + else: + type = FACE_QUADS + + current_value = -1 + texture_len = len(self.textures) + vindices = [] + nindices = [] + tindices = [] + + for i in xrange(1, vertex_count + 1): + raw_faces = words[i].split('/') + l = len(raw_faces) + + current_value = int(raw_faces[0]) + vindices.append(current_value - 1) + face_vertices.append(self.vertices[current_value - 1]) + + if l == 1: + continue + if l >= 2 and raw_faces[1]: + current_value = int(raw_faces[1]) + if current_value <= texture_len: + tindices.append(current_value - 1) + face_textures.append(self.textures[current_value - 1]) + if l >= 3 and raw_faces[2]: + current_value = int(raw_faces[2]) + nindices.append(current_value - 1) + face_normals.append(self.normals[current_value - 1]) + + if self.current_group is None: + self.current_group = group = Group() + self.groups.append(group) + else: + group = self.current_group + + group.vertices += face_vertices + group.normals += face_normals + group.textures += face_textures + idx_count = group.idx_count + group.indices += (idx_count + 1, idx_count + 2, idx_count + 3) + group.idx_count += 3 + + group.faces.append(Face(type, vindices, nindices, tindices, face_vertices, face_normals, face_textures)) + + def material(self, words): + self.perform_io(os.path.join(self.root, words[1])) + + def use_material(self, words): + mat = words[1] + try: + self.current_group.material = self.materials[mat] + except KeyError: + print "Warning: material %s undefined, only %s defined." % (mat, self.materials) + except AttributeError: + print "Warning: no group" + + def group(self, words): + name = words[1] + group = Group(name) + + if self.groups: + self.current_group.pack() + self.groups.append(group) + self.current_group = group + + def perform_io(self, file): + ext = os.path.splitext(file)[1].lstrip('.') + reader = openers.get(ext, open)(file) + + dispatcher = { + 'v': self.vertex, + 'vn': self.normal, + 'vt': self.texture, + 'f': self.face, + 'mtllib': self.material, + 'usemtl': self.use_material, + 'g': self.group, + 'o': self.group, + 'newmtl': self.new_material, + 'Ka': self.Ka, + 'Kd': self.Kd, + 'Ks': self.Ks, + 'Ns': self.material_shininess, + 'map_Kd': self.material_texture, + } + default = lambda words: None + + with reader: + for buf in reader: + if not buf or buf.startswith(('\r', '\n', '#')): + continue # Empty or comment + words = buf.split() + type = words[0] + + dispatcher.get(type, default)(words) + return True + +import sys +if hasattr(sys, 'frozen'): + model_base = os.path.dirname(sys.executable) +else: + model_base = os.path.join(os.path.dirname(__file__), 'assets', 'models') + + +def load_model(path): + if not os.path.isabs(path): + path = os.path.join(model_base, path) + if not isinstance(path, unicode): + path = path.decode('mbcs') + return WavefrontObject(path) + + def model_list(model, sx=1, sy=1, sz=1, rotation=(0, 0, 0)): for m, text in model.materials.iteritems(): if text.texture: @@ -83,206 +315,14 @@ def model_list(model, sx=1, sy=1, sz=1, rotation=(0, 0, 0)): point(f, vertices, normals, textures, 2) point(f, vertices, normals, textures, 3) point(f, vertices, normals, textures, 0) - glEnd() + glEnd() + + if tex_id: + glBindTexture(GL_TEXTURE_2D, 0) + glDisable(GL_TEXTURE_2D) glPopAttrib() glPopMatrix() glEndList() - return display - -def load_model(path): - path = os.path.join(os.path.dirname(__file__), "assets", "models", path) - root = os.path.dirname(path) - - vertices = [] - normals = [] - textures = [] - groups = [] - materials = {} - - # Allez au diable, Python - current_group = [None] - current_material = [None] - - def dispatch(p, commands): - with open(p, 'r') as file: - for line in file: - line = line.strip() - if not line or line[0] == '#': - continue # Empty or comment - words = line.split() - type = words[0] - if type in commands: - commands[type](words) - - def newmtl(words): - material = Material(words[1]) - materials[words[1]] = material - current_material[0] = material - - def Ka(words): - current_material[0].Ka = (float(words[1]), float(words[2]), float(words[3])) - - def Kd(words): - current_material[0].Kd = (float(words[1]), float(words[2]), float(words[3])) - - def Ks(words): - current_material[0].Ks = (float(words[1]), float(words[2]), float(words[3])) - - def Ns(words): - current_material[0].shininess = min(float(words[1]), 125) # Seems to sometimes be > 125. TODO: find out why - - def map_Kd(words): - current_material[0].texture = words[-1] - - def v(words): - vertices.append((float(words[1]), float(words[2]), float(words[3]))) - - def vn(words): - normals.append((float(words[1]), float(words[2]), float(words[3]))) - - def vt(words): - l = len(words) - x, y, z = 0, 0, 0 - if l >= 2: - x = float(words[1]) - if l >= 3: - # OBJ origin is at upper left, OpenGL origin is at lower left - y = 1 - float(words[2]) - if l >= 4: - z = float(words[3]) - textures.append((x, y, z)) - - def f(words): - l = len(words) - type = -1 - face_vertices = [] - face_normals = [] - face_textures = [] - - vertex_count = l - 1 - if vertex_count == 3: - type = FACE_TRIANGLES - else: - type = FACE_QUADS - - raw_faces = [] - current_value = -1 - vindices = [] - nindices = [] - tindices = [] - - for i in xrange(1, vertex_count + 1): - raw_faces = words[i].split('/') - l = len(raw_faces) - - current_value = int(raw_faces[0]) - - vindices.append(current_value - 1) - face_vertices.append(vertices[current_value - 1]) - - if l == 1: - continue - - if l >= 2 and raw_faces[1]: - current_value = int(raw_faces[1]) - if current_value <= len(textures): - tindices.append(current_value - 1) - face_textures.append(textures[current_value - 1]) - if l >= 3 and raw_faces[2]: - current_value = int(raw_faces[2]) - nindices.append(current_value - 1) - face_normals.append(normals[current_value - 1]) - - if not groups: - group = Group() - groups.append(group) - else: - group = groups[-1] - group.vertices += face_vertices - group.normals += face_normals - group.textures += face_textures - - idx_count = group.idx_count - group.indices += (idx_count + 1, idx_count + 2, idx_count + 3) - group.idx_count += 3 - - group.faces.append(Face(type, vindices, nindices, tindices, face_vertices, face_normals, face_textures)) - - mtllib = lambda words: dispatch(os.path.join(root, words[1]), obj) - - def usemtl(words): - mat = words[1] - if mat in materials: - groups[-1].material = materials[mat] - else: - print "Warning: material %s undefined." % mat - - g = lambda words: groups.append(Group(words[1])) - - obj = { - 'v': v, - 'vn': vn, - 'vt': vt, - 'f': f, - 'mtllib': mtllib, - 'usemtl': usemtl, - 'g': g, - 'o': g, # TODO: o is not really g - 'newmtl': newmtl, - 'Ka': Ka, - 'Kd': Kd, - 'Ks': Ks, - 'Ns': Ns, - 'map_Kd': map_Kd - } - - dispatch(path, obj) - - return WavefrontObject(root, vertices, normals, textures, groups, materials) - - -class WavefrontObject(object): - def __init__(self, root, vertices=None, normals=None, textures=None, groups=None, materials=None): - self.root = root - self.vertices = vertices or [] - self.normals = normals or [] - self.textures = textures or [] - self.groups = groups or [] - self.materials = materials or [] - - -class Group(object): - def __init__(self, name=None): - if not name: - name = clock() - self.name = name - self.material = None - self.faces = [] - self.indices = [] - self.vertices = [] - self.normals = [] - self.textures = [] - self.idx_count = 0 - - -class Face(object): - def __init__(self, type, verts, norms, texs, vertices, normals, textures): - self.type = type - self.verts = verts - self.norms = norms - self.texs = texs - self.vertices = vertices - self.normals = normals - self.textures = textures - - -class Material(object): - def __init__(self, name, texture=None, Ka=0, Kd=0, Ks=0, shininess=0.0): - self.name = name - self.texture = texture - self.Ka = Ka - self.Kd = Kd - self.Ks = Ks - self.shininess = shininess \ No newline at end of file + return display \ No newline at end of file