diff --git a/punyverse/_model.pyx b/punyverse/_model.pyx deleted file mode 100644 index bdf536a..0000000 --- a/punyverse/_model.pyx +++ /dev/null @@ -1,362 +0,0 @@ -from uuid import uuid4 -import os -import gzip -import bz2 -import zipfile - -from libc.string cimport strcmp, strlen -cimport cython - -include "_cyopengl.pxi" -from punyverse.texture import load_texture - - -def zip_open(file): - zip = zipfile.ZipFile(file) - return zip.open(zip.namelist()[0]) - -openers = { - 'gz': gzip.open, - 'bz2': bz2.BZ2File, - 'zip': zip_open, -} - - -cdef enum: - FACE_TRIANGLES - FACE_QUADS - -cdef class Face(object): - cdef public int type - cdef public list verts, norms, texs, vertices, normals, textures - - def __init__(self, int type, list verts, list norms, list texs, - list vertices, list normals, list textures): - self.type = type - self.verts = verts - self.norms = norms - self.texs = texs - self.vertices = vertices - self.normals = normals - self.textures = textures - -cdef class Material(object): - cdef public unicode name, texture - cdef public tuple Ka, Kd, Ks - cdef public double shininess - - def __init__(self, unicode name, unicode texture=None, tuple Ka=(0, 0, 0), - tuple Kd=(0, 0, 0), tuple Ks=(0, 0, 0), double shininess=0.0): - self.name = name - self.texture = texture - self.Ka = Ka - self.Kd = Kd - self.Ks = Ks - self.shininess = shininess - -cdef class Group(object): - cdef public unicode name - cdef public tuple min - cdef public Material material - cdef public list faces, indices, vertices, normals, textures - cdef public int idx_count - - def __init__(self, unicode name=None): - if name is None: - self.name = unicode(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) - -cdef class WavefrontObject(object): - cdef unicode root - cdef public list vertices, normals, textures, groups - cdef public dict materials - cdef unicode path - cdef Material current_material - cdef Group current_group - def __init__(self, unicode 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) - - cdef void new_material(self, list words): - name = words[1].decode('utf-8') - material = Material(name) - self.materials[name] = material - self.current_material = material - - cdef void Ka(self, list words): - self.current_material.Ka = (float(words[1]), float(words[2]), float(words[3])) - - cdef void Kd(self, list words): - self.current_material.Kd = (float(words[1]), float(words[2]), float(words[3])) - - cdef void Ks(self, list words): - self.current_material.Ks = (float(words[1]), float(words[2]), float(words[3])) - - cdef void material_shininess(self, list words): - self.current_material.shininess = min(float(words[1]), 125) - - cdef void material_texture(self, list words): - self.current_material.texture = words[-1].decode('utf-8') - - @cython.nonecheck(False) - cdef void vertex(self, list words): - self.vertices.append((float(words[1]), float(words[2]), float(words[3]))) - - @cython.nonecheck(False) - cdef void normal(self, list words): - self.normals.append((float(words[1]), float(words[2]), float(words[3]))) - - cdef void texture(self, list words): - cdef int l = len(words) - cdef object x = 0, y = 0, z = 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)) - - cdef void face(self, list words): - cdef int l = len(words) - cdef int type = -1 - cdef int vertex_count = l - 1 - cdef list face_vertices = [], face_normals = [], face_textures = [] - - if vertex_count == 3: - type = FACE_TRIANGLES - else: - type = FACE_QUADS - - cdef int current_value = -1, texture_len = len(self.textures) - cdef list raw_faces, vindices = [], nindices = [], tindices = [] - - for i in xrange(1, vertex_count + 1): - raw_faces = words[i].split(b'/') - 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]) - - cdef Group group - 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)) - - cdef bint material(self, list words) except False: - return self.perform_io(os.path.join(self.root, words[1].decode('utf-8'))) - - cdef void use_material(self, list words): - mat = words[1].decode('utf-8') - 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" - - cdef void group(self, list words): - name = words[1].decode('utf-8') - group = Group(name) - - if self.groups: - self.current_group.pack() - self.groups.append(group) - self.current_group = group - - cdef inline bint perform_io(self, unicode file) except False: - cdef const char *type - cdef list words - cdef int hash, length - - ext = os.path.splitext(file)[1].lstrip('.') - reader = openers.get(ext, lambda x: open(x, 'rb'))(file) - with reader: - for buf in reader: - if not buf or buf.startswith((b'\r', b'\n', b'#')): - continue # Empty or comment - words = buf.split() - type = words[0] - - length = strlen(type) - if not length: - continue - elif length < 3: - hash = type[0] << 8 | type[1] - if hash == 0x7600: # v\0 - self.vertex(words) - elif hash == 0x766e: # vn - self.normal(words) - elif hash == 0x7674: # vt - self.texture(words) - elif hash == 0x6600: # f - self.face(words) - elif hash == 0x6700: # g - self.group(words) - elif hash == 0x6f00: # o - self.group(words) - elif hash == 0x4b61: # Ka - self.Ka(words) - elif hash == 0x4b64: # Kd - self.Kd(words) - elif hash == 0x4b73: # Ks - self.Ks(words) - elif hash == 0x4e73: # Ns - self.material_shininess(words) - elif strcmp(type, b'mtllib') == 0: - self.material(words) - elif strcmp(type, b'usemtl') == 0: - self.use_material(words) - elif strcmp(type, b'newmtl') == 0: - self.new_material(words) - elif strcmp(type, b'map_Kd') == 0: - self.material_texture(words) - return True - -model_base = None - - -def load_model(path): - global model_base - if model_base is None: - model_base = os.path.join(os.path.dirname(__file__), 'assets', 'models') - if not os.path.isabs(path): - path = os.path.join(model_base, path) - if not isinstance(path, unicode): - path = path.decode('mbcs' if os.name == 'nt' else 'utf8') - return WavefrontObject(path) - - -@cython.nonecheck(False) -cdef inline void point(Face f, WavefrontObject m, int tex_id, float sx, float sy, float sz, int n): - cdef float x, y, z - cdef tuple normal, texture - if f.norms: - normal = m.normals[f.norms[n]] - glNormal3f(normal[0], normal[1], normal[2]) - if tex_id: - texture = m.textures[f.texs[n]] - glTexCoord2f(texture[0], texture[1]) - - x, y, z = m.vertices[f.verts[n]] - glVertex3f(x * sx, y * sy, z * sz) - - -cpdef int model_list(WavefrontObject model, float sx=1, float sy=1, float sz=1, object rotation=(0, 0, 0)): - for m, text in model.materials.iteritems(): - if text.texture: - load_texture(os.path.join(model.root, text.texture)) - - cdef int display = glGenLists(1) - - glNewList(display, GL_COMPILE) - glPushMatrix() - glPushAttrib(GL_CURRENT_BIT) - - cdef float pitch, yaw, roll - cdef float kx, ky, kz - - pitch, yaw, roll = rotation - glPushAttrib(GL_TRANSFORM_BIT) - glRotatef(pitch, 1, 0, 0) - glRotatef(yaw, 0, 1, 0) - glRotatef(roll, 0, 0, 1) - glPopAttrib() - - cdef Face f - cdef Group g - cdef int tex_id - - for g in model.groups: - tex_id = load_texture(os.path.join(model.root, g.material.texture)) if (g.material and g.material.texture) else 0 - - if tex_id: - glEnable(GL_TEXTURE_2D) - glBindTexture(GL_TEXTURE_2D, tex_id) - else: - glBindTexture(GL_TEXTURE_2D, 0) - glDisable(GL_TEXTURE_2D) - - if g.material is not None: - if g.material.Ka: - kx, ky, kz = g.material.Ka - glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, [kx, ky, kz, 1]) - if g.material.Kd: - kx, ky, kz = g.material.Kd - glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, [kx, ky, kz, 1]) - if g.material.Ks: - kx, ky, kz = g.material.Ks - glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, [kx, ky, kz, 1]) - glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, g.material.shininess) - - glBegin(GL_TRIANGLES) - for f in g.faces: - point(f, model, tex_id, sx, sy, sz, 0) - point(f, model, tex_id, sx, sy, sz, 1) - point(f, model, tex_id, sx, sy, sz, 2) - - if f.type == FACE_QUADS: - point(f, model, tex_id, sx, sy, sz, 2) - point(f, model, tex_id, sx, sy, sz, 3) - point(f, model, tex_id, sx, sy, sz, 0) - glEnd() - - if tex_id: - glBindTexture(GL_TEXTURE_2D, 0) - glDisable(GL_TEXTURE_2D) - - glPopAttrib() - glPopMatrix() - - glEndList() - return display diff --git a/setup.py b/setup.py index 78e91cd..1e9b315 100644 --- a/setup.py +++ b/setup.py @@ -124,7 +124,6 @@ setup( }, ext_modules=cythonize([ Extension('punyverse._glgeom', sources=[pyx_path('punyverse/_glgeom.pyx')], libraries=gl_libs), - Extension('punyverse._model', sources=[pyx_path('punyverse/_model.pyx')], libraries=gl_libs), ]) + extra_libs, cmdclass={'build_ext': build_ext},