Source code for wasabisg.loaders.objloader

import os.path

from ..model import Model, Mesh, AnimatedModel, DEFAULT_FRAMERATE, Material

from OpenGL.GL import GL_TRIANGLES, GL_QUADS


ANIMDIR = 'assets/mesh-animations/'


def optimise_model(model):
    """Combine meshes with identical materials.

    This significantly improves drawing performance.

    """
    from array import array
    materials = {}
    meshes_by_mat = {}
    for m in model.meshes:
        matid = id(m.material)
        mode = m.mode
        materials[matid] = m.material
        meshes_by_mat.setdefault((mode, matid), []).append(m)

    out = []
    for (mode, matid), meshes in meshes_by_mat.items():
        vs = array('f')
        ns = array('f')
        uvs = array('f')
        indices = array('L')
        for m in meshes:
            offset = len(vs) // 3
            vs.extend(m.vertices)
            ns.extend(m.normals)
            uvs.extend(m.texcoords)
            indices.extend(i + offset for i in m.indices)
        out.append(Mesh(
            mode=mode,
            vertices=vs,
            normals=ns,
            texcoords=uvs,
            indices=indices,
            material=materials[matid]
        ))
    model.meshes = out


[docs]class ObjFileLoader(object): """Load models from Wavefront .obj files.""" def __init__(self): self.mtl_loader = MtlFileLoader()
[docs] def load_obj(self, filename, swapyz=False): """Load a Wavefront OBJ file and return a Model.""" mode = GL_TRIANGLES # These list hold defined vectors vertices = [] normals = [] texcoords = [] # This will hold indexes into the vectors above faces = [] # (faces, material) tuples facegroups = [] material = None name = None for line in open(filename, "r"): if line.startswith('#'): continue values = line.split() if not values: continue if values[0] == 'v': v = map(float, values[1:4]) if swapyz: v = v[0], v[2], v[1] vertices.append(v) elif values[0] == 'vn': v = map(float, values[1:4]) if swapyz: v = v[0], v[2], v[1] normals.append(v) elif values[0] == 'vt': texcoords.append(map(float, values[1:3])) elif values[0] in ('usemtl', 'usemat'): if faces and material: facegroups.append((faces, material, name)) faces = [] material = self.mtl_loader.get_material(values[1]) elif values[0] == 'mtllib': self.mtl_loader.load_materials( os.path.join(os.path.dirname(filename), values[1]) ) elif values[0] == 'f': vs = [] uvs = [] ns = [] for v in values[1:]: w = v.split('/') vs.append(int(w[0])) if len(w) >= 2 and len(w[1]) > 0: uvs.append(int(w[1])) else: uvs.append(0) if len(w) >= 3 and len(w[2]) > 0: ns.append(int(w[2])) else: ns.append(0) if len(vs) == 4: mode = GL_QUADS faces.append((vs, uvs, ns)) elif values[0] == 'o': name = values[1] if faces and material: facegroups.append((faces, material, name)) meshes = [] for faces, material, name in facegroups: count = 0 outvs = [] outns = [] outuvs = [] indices = [] seen = {} for face in faces: vs, uvs, ns = face for v, n, uv in zip(vs, ns, uvs): vdata = (v, n, uv) try: i = seen[vdata] except KeyError: i = count count += 1 seen[vdata] = i outvs.extend(vertices[v - 1]) if normals: outns.extend(normals[n - 1]) if texcoords: outuvs.extend(texcoords[uv - 1]) indices.append(i) meshes.append(Mesh( name=name, mode=mode, vertices=outvs, normals=outns, texcoords=outuvs, indices=indices, material=material, )) model = Model(name=filename, meshes=meshes) optimise_model(model) return model
def load_model(self, name): return self.load_obj(os.path.join(ANIMDIR, name + '.obj')) def _get_frames(self, basename): for f in os.listdir(ANIMDIR): if f.startswith(basename + '_') and f.endswith('.obj'): yield os.path.join(ANIMDIR, f) def load_animation(self, name, sequences={}, default=None, next={}, framerate=DEFAULT_FRAMERATE): frames = sorted(self._get_frames(name)) if not frames: raise ValueError( "No .obj files found in '%s' matching %s_*.obj" % ( ANIMDIR, name ) ) models = [self.load_obj(f) for f in frames] return AnimatedModel( models, sequences=sequences, default=default, next=next, framerate=framerate )
class MtlFileLoader(object): def __init__(self): self.mtllibs = set() self.materials = {} def get_material(self, name): """Get a loaded material.""" return self.materials[name] def _read_mtl(self, filename): """Read material definitions from a mtl file.""" mtl = None with open(filename, 'r') as f: for line in f: # skip comments if line.startswith('#'): continue values = line.split() # skip empty lines if not values: continue cmd = values.pop(0) if cmd == 'newmtl': if mtl: yield mtl mtl = Material(name=values[0]) elif mtl is None: raise ValueError( "mtl file doesn't start with newmtl stmt" ) elif cmd.startswith('map_'): # map definition mtl[cmd] = values[0] else: mtl[cmd] = map(float, values) if mtl: yield mtl def load_materials(self, filename): """Load a material library as a dict.""" if filename in self.mtllibs: return self.mtllibs.add(filename) for mtl in self._read_mtl(filename): name = mtl['name'] mtl['relpath'] = os.path.dirname(filename) self.materials[name] = mtl