From: Mikko Rasa Date: Sun, 12 Aug 2012 16:06:29 +0000 (+0300) Subject: Restructure the exporter to make room for new stuff X-Git-Url: http://git.tdb.fi/?p=libs%2Fgl.git;a=commitdiff_plain;h=fcdc70624618488c514676874006f5eddc4e63df Restructure the exporter to make room for new stuff --- diff --git a/blender/io_mesh_mspgl/__init__.py b/blender/io_mesh_mspgl/__init__.py deleted file mode 100644 index e7fa07ce..00000000 --- a/blender/io_mesh_mspgl/__init__.py +++ /dev/null @@ -1,114 +0,0 @@ -bl_info = { - "name": "Msp GL format", - "author": "Mikko Rasa", - "location": "File > Export", - "description": "Export Msp GL meshes and objects", - "category": "Import-Export" } - -if "bpy" in locals(): - import imp - for sub in "export_mspgl", "mesh", "util": - if sub in locals(): - imp.reload(locals()[sub]) - -import bpy -from bpy_extras.io_utils import ExportHelper - -class ExportMspGLBase(ExportHelper): - use_strips = bpy.props.BoolProperty(name="Use strips", description="Combine faces into triangle strips", default=True) - use_degen_tris = bpy.props.BoolProperty(name="Use degen tris", description="Concatenate triangle strips with degenerate triangles", default=False) - max_strip_len = bpy.props.IntProperty(name="Max strip length", description="Maximum length for a triangle strip", default=1024, min=4, max=16384) - optimize_cache = bpy.props.BoolProperty(name="Optimize cache", description="Optimize element order for vertex cache", default=True) - cache_size = bpy.props.IntProperty(name="Cache size", description="Simulated vertex cache size used in optimization", default=64, min=8, max=1024) - export_lines = bpy.props.BoolProperty(name="Export lines", description="Export edges without faces as lines", default=False) - export_uv = bpy.props.EnumProperty(name="Export UV", description="Export UV coordinates", default="UNIT0", - items=(("NONE", "None", "No UV coordinates are exported"), - ("UNIT0", "Unit 0", "UV coordinates for unit 0 are exported"), - ("ALL", "All", "All UV coordinates are exported"))) - tbn_vecs = bpy.props.BoolProperty(name="TBN vectors", description="Compute tangent and binormal vectors for vertices", default=False) - tbn_uvtex = bpy.props.StringProperty(name="TBN UV layer", description="UV layer to use as basis for TBN vectors", default="") - compound = bpy.props.BoolProperty(name="Compound", description="Combine all selected objects into one for exporting", default=False) - smoothing = bpy.props.EnumProperty(name="Smoothing", description="Smoothing method to use", default="MSPGL", - items=(("NONE", "None", "No smoothing"), - ("BLENDER", "Blender", "Use Blender's vertex normals"), - ("MSPGL", "MspGL", "Compute vertex normals internally"))) - - def execute(self, context): - from . import export_mspgl - exporter = export_mspgl.Exporter() - self.prepare_exporter(exporter) - exporter.export(context, self.filepath) - return {"FINISHED"} - - def prepare_exporter(self, exporter): - for k, v in self.as_keywords().items(): - setattr(exporter, k, v) - - def draw(self, context): - col = self.layout.column() - col.prop(self, "export_lines") - col.prop(self, "compound") - col.prop(self, "smoothing") - self.layout.separator() - col = self.layout.column() - col.label("Triangle strips") - col.prop(self, "use_strips") - col.prop(self, "use_degen_tris") - col.prop(self, "max_strip_len") - self.layout.separator() - col = self.layout.column() - col.label("Vertex cache") - col.prop(self, "optimize_cache") - col.prop(self, "cache_size") - self.layout.separator() - col = self.layout.column() - col.label("TBN vectors") - col.prop(self, "tbn_vecs") - col.prop(self, "tbn_uvtex") - -class ExportMspGLMesh(bpy.types.Operator, ExportMspGLBase): - bl_idname = "export_mesh.mspgl_mesh" - bl_label = "Export Msp GL mesh" - - filename_ext = ".mesh" - -class ExportMspGLObject(bpy.types.Operator, ExportMspGLBase): - bl_idname = "export_mesh.mspgl_object" - bl_label = "Export Msp GL object" - - filename_ext = ".object" - - textures = bpy.props.EnumProperty(name="Textures", description="Export textures", default="REF", - items=(("NONE", "None", "Ignore textures"), - ("REF", "Referenced", "Reference external data"), - ("INLINE", "Inline", "Embed textures in the object"))) - material_tex = bpy.props.BoolProperty(name="Material texture", description="Generate a texture based on material colors", default=False) - - def prepare_exporter(self, exporter): - super().prepare_exporter(exporter) - exporter.object = True - - def draw(self, context): - super().draw(context) - self.layout.separator() - col = self.layout.column() - col.label("Texturing") - col.prop(self, "textures") - col.prop(self, "material_tex") - -def menu_func_export(self, context): - self.layout.operator(ExportMspGLMesh.bl_idname, text="Msp GL mesh") - self.layout.operator(ExportMspGLObject.bl_idname, text="Msp GL object") - -def register(): - bpy.utils.register_module(__name__) - - bpy.types.INFO_MT_file_export.append(menu_func_export) - -def unregister(): - bpy.utils.unregister_module(__name__) - - bpy.types.INFO_MT_file_export.remove(menu_func_export) - -if __name__=="__main__": - register() diff --git a/blender/io_mesh_mspgl/export_mspgl.py b/blender/io_mesh_mspgl/export_mspgl.py deleted file mode 100644 index c0fd96e9..00000000 --- a/blender/io_mesh_mspgl/export_mspgl.py +++ /dev/null @@ -1,439 +0,0 @@ -import bpy - -class VertexCache: - def __init__(self, size): - self.size = size - self.slots = [-1]*self.size - - def fetch(self, v): - hit = v.index in self.slots - if hit: - self.slots.remove(v.index) - self.slots.append(v.index) - if not hit: - del self.slots[0] - return hit - - def fetch_strip(self, strip): - hits = 0 - for v in strip: - if self.fetch(v): - hits += 1 - return hits - - def test_strip(self, strip): - hits = 0 - for i in range(len(strip)): - if i>=self.size: - break - if strip[i].index in self.slots[i:]: - hits += 1 - return hits - - -class OutFile: - def __init__(self, fn): - if fn==None: - self.file = sys.stdout - else: - self.file = open(fn, "w") - self.indent = 0 - - def make(self, kwd, *params): - pstr = "" - for p in params: - if type(p)==float: - pstr += " %.6g"%p - else: - pstr += " %s"%p - return "%s%s"%(kwd, pstr) - - def write(self, kwd, *params): - self.file.write("%s%s;\n"%('\t'*self.indent, self.make(kwd, *params))) - - def begin(self, kwd, *params): - i = '\t'*self.indent - self.file.write("%s%s\n%s{\n"%(i, self.make(kwd, *params), i)) - self.indent += 1 - - def end(self): - self.indent -= 1 - self.file.write("%s};\n"%('\t'*self.indent)) - - -class Exporter: - def __init__(self): - self.use_strips = True - self.use_degen_tris = True - self.max_strip_len = 1024 - self.optimize_cache = False - self.cache_size = 64 - self.export_lines = True - self.export_uv = "UNIT0" - self.tbn_vecs = False - self.tbn_uvtex = "" - self.compound = False - self.object = False - self.material_tex = False - self.textures = "REF" - self.smoothing = "MSPGL" - - def stripify(self, mesh, progress = None): - for f in mesh.faces: - f.flag = False - - faces_done = 0 - strips = [] - loose = [] - - cache = None - if self.optimize_cache: - cache = VertexCache(self.cache_size) - - island = [] - island_strips = [] - while 1: - if not island: - # No current island; find any unused face to start from - queue = [] - for f in mesh.faces: - if not f.flag: - f.flag = True - queue.append(f) - break - - if not queue: - break - - # Find all faces connected to the first one - while queue: - face = queue.pop(0) - island.append(face) - - for n in f.get_neighbors(): - if not n.flag: - n.flag = True - queue.append(n) - - # Unflag the island for the next phase - for f in island: - f.flag = False - - # Find an unused face with as few unused neighbors as possible, but - # at least one. This heuristic gives a preference to faces in corners - # or along borders of a non-closed island. - best = 5 - face = None - for f in island: - if f.flag: - continue - - score = sum(not n.flag for n in f.get_neighbors()) - if score>0 and scorebest_hits: - best = i - best_hits = hits - - strip = island_strips.pop(best) - strips.append(strip) - - if cache: - cache.fetch_strip(strip) - - faces_done += len(island) - if progress: - progress.set_progress(float(faces_done)/len(mesh.faces)) - - # Collect any faces that weren't used in strips - loose += [f for f in island if not f.flag] - for f in island: - f.flag = True - - island = [] - island_strips = [] - - if cache: - cache = VertexCache(self.cache_size) - total_hits = 0 - - if self.use_degen_tris and strips: - big_strip = [] - - for s in strips: - if big_strip: - # Generate glue elements, ensuring that the next strip begins at - # an even position - glue = [big_strip[-1], s[0]] - if len(big_strip)%2: - glue += [s[0]] - - big_strip += glue - if cache: - total_hits += cache.fetch_strip(glue) - - big_strip += s - if cache: - total_hits += cache.fetch_strip(s) - - for f in loose: - # Add loose faces to the end. This wastes space, using five - # elements per triangle and six elements per quad. - if len(big_strip)%2: - order = (-1, -2, 0, 1) - else: - order = (0, 1, -1, -2) - vertices = [f.vertices[i] for i in order[:len(f.vertices)]] - - if big_strip: - glue = [big_strip[-1], vertices[0]] - big_strip += glue - if cache: - total_hits += cache.fetch_strip(glue) - - big_strip += vertices - if cache: - total_hits += cache.fetch_strip(vertices) - - strips = [big_strip] - loose = [] - - return strips, loose - - def export(self, context, fn): - if self.compound: - objs = context.selected_objects - else: - objs = [context.active_object] - - if not objs: - raise Exception("Nothing to export") - for o in objs: - if o.type!="MESH": - raise Exception("Can only export Mesh data") - - from .mesh import Mesh - from .util import Progress - - progress = Progress() - progress.set_task("Preparing", 0.0, 0.0) - - mesh = None - bmeshes = [] - for o in objs: - bmesh = o.to_mesh(context.scene, True, "PREVIEW") - bmeshes.append(bmesh) - if not mesh: - mesh = Mesh(bmesh) - else: - mesh.splice(Mesh(bmesh)) - - progress.set_task("Smoothing", 0.05, 0.35) - if self.smoothing=="NONE": - mesh.flatten_faces() - mesh.split_smooth(progress) - - if self.smoothing!="BLENDER": - mesh.compute_normals() - - if self.material_tex and mesh.materials: - mesh.generate_material_uv() - - texunits = [] - if mesh.uv_layers and self.export_uv!="NONE": - # Figure out which UV layers to export - if self.export_uv=="UNIT0": - if mesh.uv_layers[0].unit==0: - texunits = [0] - else: - texunits = range(len(mesh.uv_layers)) - texunits = [(i, mesh.uv_layers[i]) for i in texunits] - texunits = [u for u in texunits if not u[1].hidden] - - if self.tbn_vecs: - # TBN coordinates must be generated before vertices are split by any other layer - uv_names = [u.name for i, u in texunits] - if self.tbn_uvtex in uv_names: - tbn_index = uv_names.index(self.tbn_uvtex) - unit = texunits[tbn_index] - del texunits[tbn_index] - texunits.insert(0, unit) - - for i, u in texunits: - progress.set_task("Splitting UVs", 0.35+0.3*i/len(texunits), 0.35+0.3*(i+1)/len(texunits)) - mesh.split_uv(i, progress) - if self.tbn_vecs and u.name==self.tbn_uvtex: - mesh.compute_uv() - mesh.compute_tbn(i) - - mesh.compute_uv() - - strips = [] - loose = mesh.faces - if self.use_strips: - progress.set_task("Creating strips", 0.65, 0.95) - strips, loose = self.stripify(mesh, progress) - - progress.set_task("Writing file", 0.95, 1.0) - - out_file = OutFile(fn) - if self.object: - out_file.begin("mesh") - - fmt = "NORMAL3" - if texunits: - for i, u in texunits: - if u.unit==0: - fmt += "_TEXCOORD2" - else: - fmt += "_TEXCOORD2%d"%u.unit - if self.tbn_vecs: - fmt += "_ATTRIB33_ATTRIB34" - fmt += "_VERTEX3" - out_file.begin("vertices", fmt) - normal = None - uvs = [None]*len(texunits) - tan = None - bino = None - for v in mesh.vertices: - if v.normal!=normal: - out_file.write("normal3", *v.normal) - normal = v.normal - for i, u in texunits: - if v.uvs[i]!=uvs[i]: - if u.unit==0: - out_file.write("texcoord2", *v.uvs[i]) - else: - out_file.write("multitexcoord2", u.unit, *v.uvs[i]) - uvs[i] = v.uvs[i] - if self.tbn_vecs: - if v.tan!=tan: - out_file.write("attrib3", 3, *v.tan) - tan = v.tan - if v.bino!=bino: - out_file.write("attrib3", 4, *v.bino) - bino = v.bino - out_file.write("vertex3", *v.co) - out_file.end() - for s in strips: - out_file.begin("batch", "TRIANGLE_STRIP") - indices = [] - n = 0 - for v in s: - indices.append(v.index) - if len(indices)>=32: - out_file.write("indices", *indices) - indices = [] - if indices: - out_file.write("indices", *indices) - out_file.end() - - if loose: - out_file.begin("batch", "TRIANGLES") - for f in loose: - for i in range(2, len(f.vertices)): - out_file.write("indices", f.vertices[0].index, f.vertices[i-1].index, f.vertices[i].index) - out_file.end() - - if self.export_lines and mesh.lines: - out_file.write("batch", "LINES") - for l in mesh.lines: - out_file.write("indices", l.vertices[0].index, l.vertices[1].index) - out_file.end() - - if self.object: - out_file.end() - out_file.begin("technique") - out_file.begin("pass", '""') - if mesh.materials: - if self.material_tex: - out_file.begin("material") - out_file.write("diffuse", 1.0, 1.0, 1.0, 1.0) - out_file.end() - index = 0 - for u in mesh.uv_layers: - if u.name=="material_tex": - index = u.unit - out_file.begin("texunit", index) - out_file.begin("texture2d") - out_file.write("min_filter", "NEAREST") - out_file.write("mag_filter", "NEAREST") - out_file.write("storage", "RGB", len(mesh.materials), 1) - texdata = '"' - for m in mesh.materials: - color = [int(c*255) for c in m.diffuse_color] - texdata += "\\x%02X\\x%02X\\x%02X"%tuple(color) - texdata += '"' - out_file.write("raw_data", texdata) - out_file.end() - out_file.end() - else: - mat = mesh.materials[0] - out_file.begin("material") - diff = mat.diffuse_color - out_file.write("diffuse", diff.r, diff.g, diff.b, 1.0) - amb = diff*mat.ambient - out_file.write("ambient", amb.r, amb.g, amb.b, 1.0) - spec = mat.specular_color*mat.specular_intensity - out_file.write("specular", spec.r, spec.g, spec.b, 1.0) - out_file.write("shininess", mat.specular_hardness); - out_file.end() - - if self.textures!="NONE": - for slot in mesh.materials[0].texture_slots: - if not slot: - continue - - tex = slot.texture - if tex.type!="IMAGE": - continue - - if slot.uv_layer: - for u in mesh.uv_layers: - if u.name==slot.uv_layer: - index = u.unit - else: - index = mesh.uv_layers[0].unit - - out_file.begin("texunit", index) - if self.textures=="INLINE": - out_file.begin("texture2d") - out_file.write("min_filter", "LINEAR") - out_file.write("storage", "RGBA", tex.image.size[0], tex.image.size[1]) - texdata = '"' - for p in tex.image.pixels: - texdata += "\\x%02X"%int(p*255) - texdata += '"' - out_file.write("raw_data", texdata) - out_file.end() - else: - out_file.write("texture", '"%s"'%tex.image.name) - out_file.end() - - out_file.end() - out_file.end() - - progress.set_task("Done", 1.0, 1.0) - - for m in bmeshes: - bpy.data.meshes.remove(m) diff --git a/blender/io_mesh_mspgl/mesh.py b/blender/io_mesh_mspgl/mesh.py deleted file mode 100644 index aa8df8d2..00000000 --- a/blender/io_mesh_mspgl/mesh.py +++ /dev/null @@ -1,407 +0,0 @@ -import math -import mathutils - -def make_edge_key(i1, i2): - return (min(i1, i2), max(i1, i2)) - -class Edge: - def __init__(self, me): - if me.__class__==Edge: - self._medge = me._medge - self.vertices = me.vertices[:] - self.smooth = me.smooth - else: - self._medge = me - self.smooth = False - self.faces = [] - - def __getattr__(self, attr): - return getattr(self._medge, attr) - - def check_smooth(self, limit): - if len(self.faces)!=2: - return - - d = self.faces[0].normal.dot(self.faces[1].normal) - self.smooth = ((d>limit and self.faces[0].use_smooth and self.faces[1].use_smooth) or d>0.99995) - - def other_face(self, f): - if f.index==self.faces[0].index: - if len(self.faces)>=2: - return self.faces[1] - else: - return None - else: - return self.faces[0] - - -class Vertex: - def __init__(self, mv): - if mv.__class__==Vertex: - self._mvert = mv._mvert - self.normal = mv.normal - self.uvs = mv.uvs[:] - self.tan = mv.tan - self.bino = mv.bino - else: - self._mvert = mv - self.uvs = [] - self.tan = None - self.bino = None - self.flag = False - self.faces = [] - - def __getattr__(self, attr): - return getattr(self._mvert, attr) - - def __cmp__(self, other): - if other is None: - return 1 - return cmp(self.index, other.index) - - -class Face: - def __init__(self, mf): - self._mface = mf - self.edges = [] - self.vertices = mf.vertices[:] - self.uvs = [] - self.flag = False - - def __getattr__(self, attr): - return getattr(self._mface, attr) - - def __cmp__(self, other): - if other is None: - return 1 - return cmp(self.index, other.index) - - def pivot_vertices(self, *vt): - flags = [(v in vt) for v in self.vertices] - l = len(self.vertices) - for i in range(l): - if flags[i] and not flags[(i+l-1)%l]: - return self.vertices[i:]+self.vertices[:i] - - def get_edge(self, v1, v2): - key = make_edge_key(v1.index, v2.index) - for e in self.edges: - if e.key==key: - return e - raise KeyError("No edge %s"%(key,)) - - def get_neighbors(self): - neighbors = [e.other_face(self) for e in self.edges] - return list(filter(bool, neighbors)) - - -class Line: - def __init__(self, e): - self.edge = e - self.vertices = e.vertices[:] - self.flag = False - - -class UvLayer: - def __init__(self, l, t): - self._layer = l - self.uvtex = t - self.name = self.uvtex.name - self.unit = None - self.hidden = False - dot = self.name.find('.') - if dot>=0: - ext = self.name[dot:] - if ext.startswith(".unit") and ext[5:].isdigit(): - self.unit = int(ext[5:]) - elif ext==".hidden": - self.hidden = True - - def __getattr__(self, attr): - return getattr(self._layer, attr) - -class FakeUvLayer: - def __init__(self, n): - self.uvtex = None - self.name = n - self.unit = None - self.hidden = False - -class Mesh: - def __init__(self, m): - self._mesh = m - - self.vertices = [Vertex(v) for v in self.vertices] - self.faces = [Face(f) for f in self.polygons] - - self.materials = self.materials[:] - - self.uv_layers = [UvLayer(self.uv_layers[i], self.uv_textures[i]) for i in range(len(self.uv_layers))] - self.assign_texture_units() - - for f in self.faces: - f.vertices = [self.vertices[i] for i in f.vertices] - for v in f.vertices: - v.faces.append(f) - for u in self.uv_layers: - f.uvs.append([u.data[f.loop_indices[i]].uv for i in range(len(f.vertices))]) - - self.edges = dict([(e.key, Edge(e)) for e in self.edges]) - for f in self.faces: - for k in f.edge_keys: - e = self.edges[k] - e.faces.append(self.faces[f.index]) - f.edges.append(e) - - self.lines = [Line(e) for e in self.edges.values() if not e.faces] - - if self.use_auto_smooth: - smooth_limit = math.cos(self.auto_smooth_angle*math.pi/180) - else: - smooth_limit = -1 - - for e in self.edges.values(): - e.vertices = [self.vertices[i] for i in e.vertices] - e.check_smooth(smooth_limit) - - def __getattr__(self, attr): - return getattr(self._mesh, attr) - - def splice(self, other): - material_map = [] - for m in other.materials: - if m in self.materials: - material_map.append(self.materials.index(m)) - else: - material_map.append(len(self.materials)) - self.materials.append(m) - - offset = len(self.vertices) - for v in other.vertices: - v.index += offset - self.vertices.append(v) - - offset = len(self.faces) - for f in other.faces: - f.index += offset - if other.materials: - f.material_index = material_map[f.material_index] - self.faces.append(f) - - for e in other.edges.values(): - e.key = make_edge_key(e.vertices[0].index, e.vertices[1].index) - self.edges[e.key] = e - - self.lines += other.lines - - def flatten_faces(self): - for f in self.faces: - f.use_smooth = False - - for e in self.edges.values(): - e.check_smooth(1) - - def assign_texture_units(self): - # Assign texture units for any non-hidden UV layers that lack one - units = [u.unit for u in self.uv_layers if u.unit is not None] - if units: - free_unit = max(units)+1 - else: - free_unit = 0 - for u in self.uv_layers: - if u.unit is None: - if not u.hidden: - u.unit = free_unit - free_unit += 1 - - def generate_material_uv(self): - self.uv_layers.append(FakeUvLayer("material_tex")) - self.assign_texture_units() - for f in self.faces: - f.uvs.append([((f.material_index+0.5)/len(self.materials), 0.5)]*len(f.vertices)) - - def split_vertices(self, find_group_func, progress, *args): - groups = [] - for i in range(len(self.vertices)): - v = self.vertices[i] - for f in v.faces: - f.flag = False - - vg = [] - for f in v.faces: - if not f.flag: - vg.append(find_group_func(v, f, *args)) - - groups.append(vg) - - if progress: - progress.set_progress(i*0.5/len(self.vertices)) - - for i in range(len(self.vertices)): - if len(groups[i])==1: - continue - - for g in groups[i][1:]: - v = Vertex(self.vertices[i]) - v.index = len(self.vertices) - self.vertices.append(v) - - for f in g: - for j in range(len(f.edges)): - e = f.edges[j] - - if self.vertices[i] not in e.vertices: - continue - - if e.other_face(f) not in g and len(e.faces)>=2: - e.faces.remove(f) - e = Edge(e) - f.edges[j] = e - e.faces.append(f) - else: - del self.edges[e.key] - - e.vertices[e.vertices.index(self.vertices[i])] = v - - e.key = make_edge_key(e.vertices[0].index, e.vertices[1].index) - self.edges[e.key] = e - - self.vertices[i].faces.remove(f) - f.vertices[f.vertices.index(self.vertices[i])] = v - v.faces.append(f) - - if progress: - progress.set_progress(0.5+i*0.5/len(self.vertices)) - - def split_smooth(self, progress = None): - self.split_vertices(self.find_smooth_group, progress) - - def split_uv(self, index, progress = None): - self.split_vertices(self.find_uv_group, progress, index) - - def find_smooth_group(self, vertex, face): - face.flag = True - queue = [face] - - for f in queue: - for e in f.edges: - other = e.other_face(f) - if other not in vertex.faces: - continue - - if e.smooth: - if not other.flag: - other.flag = True - queue.append(other) - - return queue - - def find_uv_group(self, vertex, face, index): - uv = face.uvs[index][face.vertices.index(vertex)] - face.flag = True - group = [face] - for f in vertex.faces: - if not f.flag and f.uvs[index][f.vertices.index(vertex)]==uv: - f.flag = True - group.append(f) - return group - - def compute_normals(self): - for v in self.vertices: - if v.faces: - v.normal = mathutils.Vector() - for f in v.faces: - fv = f.pivot_vertices(v) - edge1 = fv[1].co-fv[0].co - edge2 = fv[-1].co-fv[0].co - weight = 1 - if len(f.get_edge(fv[0], fv[1]).faces)==1: - weight += 1 - if len(f.get_edge(fv[0], fv[-1]).faces)==1: - weight += 1 - v.normal += f.normal*edge1.angle(edge2)*weight - v.normal.normalize() - else: - # XXX Should use edges to compute normal - v.normal = mathutils.Vector(0, 0, 1) - - def compute_uv(self): - for v in self.vertices: - if v.faces: - f = v.faces[0] - i = f.vertices.index(v) - v.uvs = [u[i] for u in f.uvs] - - def compute_tbn(self, index): - if not self.uv_layers: - return - - for v in self.vertices: - v.tan = mathutils.Vector() - v.bino = mathutils.Vector() - for f in v.faces: - fv = f.pivot_vertices(v) - uv0 = fv[0].uvs[index] - uv1 = fv[1].uvs[index] - uv2 = fv[-1].uvs[index] - du1 = uv1[0]-uv0[0] - du2 = uv2[0]-uv0[0] - dv1 = uv1[1]-uv0[1] - dv2 = uv2[1]-uv0[1] - edge1 = fv[1].co-fv[0].co - edge2 = fv[-1].co-fv[0].co - div = (du1*dv2-du2*dv1) - if div: - mul = edge1.angle(edge2)/div - v.tan += (edge1*dv2-edge2*dv1)*mul - v.bino += (edge2*du1-edge1*du2)*mul - - if v.tan.length: - v.tan.normalize() - if v.bino.length: - v.bino.normalize() - - def create_strip(self, face, max_len): - # Find an edge with another unused face next to it - edge = None - for e in face.edges: - other = e.other_face(face) - if other and not other.flag: - edge = e - break - - if not edge: - return None - - # Add initial vertices so that we'll complete the edge on the first - # iteration - vertices = face.pivot_vertices(*edge.vertices) - if len(vertices)==3: - result = [vertices[-1], vertices[0]] - else: - result = [vertices[-2], vertices[-1]] - - while 1: - face.flag = True - - vertices = face.pivot_vertices(*result[-2:]) - k = len(result)%2 - - # Quads need special handling because the winding of every other - # triangle in the strip is reversed - if len(vertices)==4 and not k: - result.append(vertices[3]) - result.append(vertices[2]) - if len(vertices)==4 and k: - result.append(vertices[3]) - - if len(result)>=max_len: - break - - # Hop over the last edge - edge = face.get_edge(*result[-2:]) - face = edge.other_face(face) - if not face or face.flag: - break - - return result diff --git a/blender/io_mesh_mspgl/util.py b/blender/io_mesh_mspgl/util.py deleted file mode 100644 index 936a1c1d..00000000 --- a/blender/io_mesh_mspgl/util.py +++ /dev/null @@ -1,15 +0,0 @@ -class Progress: - def __init__(self): - self.task = "" - self.start = 0.0 - self.delta = 1.0 - - def set_task(self, task, low, high): - self.task = task - self.start = low - self.delta = high-low - self.set_progress(0.0) - - def set_progress(self, value): - pass - #Blender.Window.DrawProgressBar(self.start+self.delta*value, self.task) diff --git a/blender/io_mspgl/__init__.py b/blender/io_mspgl/__init__.py new file mode 100644 index 00000000..2ac3bd19 --- /dev/null +++ b/blender/io_mspgl/__init__.py @@ -0,0 +1,114 @@ +bl_info = { + "name": "Msp GL datafiles", + "author": "Mikko Rasa", + "location": "File > Export", + "description": "Export Msp GL data", + "category": "Import-Export" } + +if "bpy" in locals(): + import imp + for sub in "export_mesh", "mesh", "outfile", "util": + if sub in locals(): + imp.reload(locals()[sub]) + +import bpy +from bpy_extras.io_utils import ExportHelper + +class ExportMspGLMeshBase(ExportHelper): + use_strips = bpy.props.BoolProperty(name="Use strips", description="Combine faces into triangle strips", default=True) + use_degen_tris = bpy.props.BoolProperty(name="Use degen tris", description="Concatenate triangle strips with degenerate triangles", default=False) + max_strip_len = bpy.props.IntProperty(name="Max strip length", description="Maximum length for a triangle strip", default=1024, min=4, max=16384) + optimize_cache = bpy.props.BoolProperty(name="Optimize cache", description="Optimize element order for vertex cache", default=True) + cache_size = bpy.props.IntProperty(name="Cache size", description="Simulated vertex cache size used in optimization", default=64, min=8, max=1024) + export_lines = bpy.props.BoolProperty(name="Export lines", description="Export edges without faces as lines", default=False) + export_uv = bpy.props.EnumProperty(name="Export UV", description="Export UV coordinates", default="UNIT0", + items=(("NONE", "None", "No UV coordinates are exported"), + ("UNIT0", "Unit 0", "UV coordinates for unit 0 are exported"), + ("ALL", "All", "All UV coordinates are exported"))) + tbn_vecs = bpy.props.BoolProperty(name="TBN vectors", description="Compute tangent and binormal vectors for vertices", default=False) + tbn_uvtex = bpy.props.StringProperty(name="TBN UV layer", description="UV layer to use as basis for TBN vectors", default="") + compound = bpy.props.BoolProperty(name="Compound", description="Combine all selected objects into one for exporting", default=False) + smoothing = bpy.props.EnumProperty(name="Smoothing", description="Smoothing method to use", default="MSPGL", + items=(("NONE", "None", "No smoothing"), + ("BLENDER", "Blender", "Use Blender's vertex normals"), + ("MSPGL", "MspGL", "Compute vertex normals internally"))) + + def execute(self, context): + from .export_mesh import MeshExporter + exporter = MeshExporter() + self.prepare_exporter(exporter) + exporter.export(context, self.filepath) + return {"FINISHED"} + + def prepare_exporter(self, exporter): + for k, v in self.as_keywords().items(): + setattr(exporter, k, v) + + def draw(self, context): + col = self.layout.column() + col.prop(self, "export_lines") + col.prop(self, "compound") + col.prop(self, "smoothing") + self.layout.separator() + col = self.layout.column() + col.label("Triangle strips") + col.prop(self, "use_strips") + col.prop(self, "use_degen_tris") + col.prop(self, "max_strip_len") + self.layout.separator() + col = self.layout.column() + col.label("Vertex cache") + col.prop(self, "optimize_cache") + col.prop(self, "cache_size") + self.layout.separator() + col = self.layout.column() + col.label("TBN vectors") + col.prop(self, "tbn_vecs") + col.prop(self, "tbn_uvtex") + +class ExportMspGLMesh(bpy.types.Operator, ExportMspGLMeshBase): + bl_idname = "export_mesh.mspgl_mesh" + bl_label = "Export Msp GL mesh" + + filename_ext = ".mesh" + +class ExportMspGLObject(bpy.types.Operator, ExportMspGLMeshBase): + bl_idname = "export_mesh.mspgl_object" + bl_label = "Export Msp GL object" + + filename_ext = ".object" + + textures = bpy.props.EnumProperty(name="Textures", description="Export textures", default="REF", + items=(("NONE", "None", "Ignore textures"), + ("REF", "Referenced", "Reference external data"), + ("INLINE", "Inline", "Embed textures in the object"))) + material_tex = bpy.props.BoolProperty(name="Material texture", description="Generate a texture based on material colors", default=False) + + def prepare_exporter(self, exporter): + super().prepare_exporter(exporter) + exporter.object = True + + def draw(self, context): + super().draw(context) + self.layout.separator() + col = self.layout.column() + col.label("Texturing") + col.prop(self, "textures") + col.prop(self, "material_tex") + +def menu_func_export(self, context): + self.layout.operator(ExportMspGLMesh.bl_idname, text="Msp GL mesh") + self.layout.operator(ExportMspGLObject.bl_idname, text="Msp GL object") + +def register(): + bpy.utils.register_module(__name__) + + bpy.types.INFO_MT_file_export.append(menu_func_export) + +def unregister(): + bpy.utils.unregister_module(__name__) + + bpy.types.INFO_MT_file_export.remove(menu_func_export) + +if __name__=="__main__": + register() diff --git a/blender/io_mspgl/export_mesh.py b/blender/io_mspgl/export_mesh.py new file mode 100644 index 00000000..3f0771fe --- /dev/null +++ b/blender/io_mspgl/export_mesh.py @@ -0,0 +1,410 @@ +import bpy +from .outfile import OutFile + +class VertexCache: + def __init__(self, size): + self.size = size + self.slots = [-1]*self.size + + def fetch(self, v): + hit = v.index in self.slots + if hit: + self.slots.remove(v.index) + self.slots.append(v.index) + if not hit: + del self.slots[0] + return hit + + def fetch_strip(self, strip): + hits = 0 + for v in strip: + if self.fetch(v): + hits += 1 + return hits + + def test_strip(self, strip): + hits = 0 + for i in range(len(strip)): + if i>=self.size: + break + if strip[i].index in self.slots[i:]: + hits += 1 + return hits + + +class MeshExporter: + def __init__(self): + self.use_strips = True + self.use_degen_tris = True + self.max_strip_len = 1024 + self.optimize_cache = False + self.cache_size = 64 + self.export_lines = True + self.export_uv = "UNIT0" + self.tbn_vecs = False + self.tbn_uvtex = "" + self.compound = False + self.object = False + self.material_tex = False + self.textures = "REF" + self.smoothing = "MSPGL" + + def stripify(self, mesh, progress = None): + for f in mesh.faces: + f.flag = False + + faces_done = 0 + strips = [] + loose = [] + + cache = None + if self.optimize_cache: + cache = VertexCache(self.cache_size) + + island = [] + island_strips = [] + while 1: + if not island: + # No current island; find any unused face to start from + queue = [] + for f in mesh.faces: + if not f.flag: + f.flag = True + queue.append(f) + break + + if not queue: + break + + # Find all faces connected to the first one + while queue: + face = queue.pop(0) + island.append(face) + + for n in f.get_neighbors(): + if not n.flag: + n.flag = True + queue.append(n) + + # Unflag the island for the next phase + for f in island: + f.flag = False + + # Find an unused face with as few unused neighbors as possible, but + # at least one. This heuristic gives a preference to faces in corners + # or along borders of a non-closed island. + best = 5 + face = None + for f in island: + if f.flag: + continue + + score = sum(not n.flag for n in f.get_neighbors()) + if score>0 and scorebest_hits: + best = i + best_hits = hits + + strip = island_strips.pop(best) + strips.append(strip) + + if cache: + cache.fetch_strip(strip) + + faces_done += len(island) + if progress: + progress.set_progress(float(faces_done)/len(mesh.faces)) + + # Collect any faces that weren't used in strips + loose += [f for f in island if not f.flag] + for f in island: + f.flag = True + + island = [] + island_strips = [] + + if cache: + cache = VertexCache(self.cache_size) + total_hits = 0 + + if self.use_degen_tris and strips: + big_strip = [] + + for s in strips: + if big_strip: + # Generate glue elements, ensuring that the next strip begins at + # an even position + glue = [big_strip[-1], s[0]] + if len(big_strip)%2: + glue += [s[0]] + + big_strip += glue + if cache: + total_hits += cache.fetch_strip(glue) + + big_strip += s + if cache: + total_hits += cache.fetch_strip(s) + + for f in loose: + # Add loose faces to the end. This wastes space, using five + # elements per triangle and six elements per quad. + if len(big_strip)%2: + order = (-1, -2, 0, 1) + else: + order = (0, 1, -1, -2) + vertices = [f.vertices[i] for i in order[:len(f.vertices)]] + + if big_strip: + glue = [big_strip[-1], vertices[0]] + big_strip += glue + if cache: + total_hits += cache.fetch_strip(glue) + + big_strip += vertices + if cache: + total_hits += cache.fetch_strip(vertices) + + strips = [big_strip] + loose = [] + + return strips, loose + + def export(self, context, fn): + if self.compound: + objs = context.selected_objects + else: + objs = [context.active_object] + + if not objs: + raise Exception("Nothing to export") + for o in objs: + if o.type!="MESH": + raise Exception("Can only export Mesh data") + + from .mesh import Mesh + from .util import Progress + + progress = Progress() + progress.set_task("Preparing", 0.0, 0.0) + + mesh = None + bmeshes = [] + for o in objs: + bmesh = o.to_mesh(context.scene, True, "PREVIEW") + bmeshes.append(bmesh) + if not mesh: + mesh = Mesh(bmesh) + else: + mesh.splice(Mesh(bmesh)) + + progress.set_task("Smoothing", 0.05, 0.35) + if self.smoothing=="NONE": + mesh.flatten_faces() + mesh.split_smooth(progress) + + if self.smoothing!="BLENDER": + mesh.compute_normals() + + if self.material_tex and mesh.materials: + mesh.generate_material_uv() + + texunits = [] + if mesh.uv_layers and self.export_uv!="NONE": + # Figure out which UV layers to export + if self.export_uv=="UNIT0": + if mesh.uv_layers[0].unit==0: + texunits = [0] + else: + texunits = range(len(mesh.uv_layers)) + texunits = [(i, mesh.uv_layers[i]) for i in texunits] + texunits = [u for u in texunits if not u[1].hidden] + + if self.tbn_vecs: + # TBN coordinates must be generated before vertices are split by any other layer + uv_names = [u.name for i, u in texunits] + if self.tbn_uvtex in uv_names: + tbn_index = uv_names.index(self.tbn_uvtex) + unit = texunits[tbn_index] + del texunits[tbn_index] + texunits.insert(0, unit) + + for i, u in texunits: + progress.set_task("Splitting UVs", 0.35+0.3*i/len(texunits), 0.35+0.3*(i+1)/len(texunits)) + mesh.split_uv(i, progress) + if self.tbn_vecs and u.name==self.tbn_uvtex: + mesh.compute_uv() + mesh.compute_tbn(i) + + mesh.compute_uv() + + strips = [] + loose = mesh.faces + if self.use_strips: + progress.set_task("Creating strips", 0.65, 0.95) + strips, loose = self.stripify(mesh, progress) + + progress.set_task("Writing file", 0.95, 1.0) + + out_file = OutFile(fn) + if self.object: + out_file.begin("mesh") + + fmt = "NORMAL3" + if texunits: + for i, u in texunits: + if u.unit==0: + fmt += "_TEXCOORD2" + else: + fmt += "_TEXCOORD2%d"%u.unit + if self.tbn_vecs: + fmt += "_ATTRIB33_ATTRIB34" + fmt += "_VERTEX3" + out_file.begin("vertices", fmt) + normal = None + uvs = [None]*len(texunits) + tan = None + bino = None + for v in mesh.vertices: + if v.normal!=normal: + out_file.write("normal3", *v.normal) + normal = v.normal + for i, u in texunits: + if v.uvs[i]!=uvs[i]: + if u.unit==0: + out_file.write("texcoord2", *v.uvs[i]) + else: + out_file.write("multitexcoord2", u.unit, *v.uvs[i]) + uvs[i] = v.uvs[i] + if self.tbn_vecs: + if v.tan!=tan: + out_file.write("attrib3", 3, *v.tan) + tan = v.tan + if v.bino!=bino: + out_file.write("attrib3", 4, *v.bino) + bino = v.bino + out_file.write("vertex3", *v.co) + out_file.end() + for s in strips: + out_file.begin("batch", "TRIANGLE_STRIP") + indices = [] + n = 0 + for v in s: + indices.append(v.index) + if len(indices)>=32: + out_file.write("indices", *indices) + indices = [] + if indices: + out_file.write("indices", *indices) + out_file.end() + + if loose: + out_file.begin("batch", "TRIANGLES") + for f in loose: + for i in range(2, len(f.vertices)): + out_file.write("indices", f.vertices[0].index, f.vertices[i-1].index, f.vertices[i].index) + out_file.end() + + if self.export_lines and mesh.lines: + out_file.write("batch", "LINES") + for l in mesh.lines: + out_file.write("indices", l.vertices[0].index, l.vertices[1].index) + out_file.end() + + if self.object: + out_file.end() + out_file.begin("technique") + out_file.begin("pass", '""') + if mesh.materials: + if self.material_tex: + out_file.begin("material") + out_file.write("diffuse", 1.0, 1.0, 1.0, 1.0) + out_file.end() + index = 0 + for u in mesh.uv_layers: + if u.name=="material_tex": + index = u.unit + out_file.begin("texunit", index) + out_file.begin("texture2d") + out_file.write("min_filter", "NEAREST") + out_file.write("mag_filter", "NEAREST") + out_file.write("storage", "RGB", len(mesh.materials), 1) + texdata = '"' + for m in mesh.materials: + color = [int(c*255) for c in m.diffuse_color] + texdata += "\\x%02X\\x%02X\\x%02X"%tuple(color) + texdata += '"' + out_file.write("raw_data", texdata) + out_file.end() + out_file.end() + else: + mat = mesh.materials[0] + out_file.begin("material") + diff = mat.diffuse_color + out_file.write("diffuse", diff.r, diff.g, diff.b, 1.0) + amb = diff*mat.ambient + out_file.write("ambient", amb.r, amb.g, amb.b, 1.0) + spec = mat.specular_color*mat.specular_intensity + out_file.write("specular", spec.r, spec.g, spec.b, 1.0) + out_file.write("shininess", mat.specular_hardness); + out_file.end() + + if self.textures!="NONE": + for slot in mesh.materials[0].texture_slots: + if not slot: + continue + + tex = slot.texture + if tex.type!="IMAGE": + continue + + if slot.uv_layer: + for u in mesh.uv_layers: + if u.name==slot.uv_layer: + index = u.unit + else: + index = mesh.uv_layers[0].unit + + out_file.begin("texunit", index) + if self.textures=="INLINE": + out_file.begin("texture2d") + out_file.write("min_filter", "LINEAR") + out_file.write("storage", "RGBA", tex.image.size[0], tex.image.size[1]) + texdata = '"' + for p in tex.image.pixels: + texdata += "\\x%02X"%int(p*255) + texdata += '"' + out_file.write("raw_data", texdata) + out_file.end() + else: + out_file.write("texture", '"%s"'%tex.image.name) + out_file.end() + + out_file.end() + out_file.end() + + progress.set_task("Done", 1.0, 1.0) + + for m in bmeshes: + bpy.data.meshes.remove(m) diff --git a/blender/io_mspgl/mesh.py b/blender/io_mspgl/mesh.py new file mode 100644 index 00000000..aa8df8d2 --- /dev/null +++ b/blender/io_mspgl/mesh.py @@ -0,0 +1,407 @@ +import math +import mathutils + +def make_edge_key(i1, i2): + return (min(i1, i2), max(i1, i2)) + +class Edge: + def __init__(self, me): + if me.__class__==Edge: + self._medge = me._medge + self.vertices = me.vertices[:] + self.smooth = me.smooth + else: + self._medge = me + self.smooth = False + self.faces = [] + + def __getattr__(self, attr): + return getattr(self._medge, attr) + + def check_smooth(self, limit): + if len(self.faces)!=2: + return + + d = self.faces[0].normal.dot(self.faces[1].normal) + self.smooth = ((d>limit and self.faces[0].use_smooth and self.faces[1].use_smooth) or d>0.99995) + + def other_face(self, f): + if f.index==self.faces[0].index: + if len(self.faces)>=2: + return self.faces[1] + else: + return None + else: + return self.faces[0] + + +class Vertex: + def __init__(self, mv): + if mv.__class__==Vertex: + self._mvert = mv._mvert + self.normal = mv.normal + self.uvs = mv.uvs[:] + self.tan = mv.tan + self.bino = mv.bino + else: + self._mvert = mv + self.uvs = [] + self.tan = None + self.bino = None + self.flag = False + self.faces = [] + + def __getattr__(self, attr): + return getattr(self._mvert, attr) + + def __cmp__(self, other): + if other is None: + return 1 + return cmp(self.index, other.index) + + +class Face: + def __init__(self, mf): + self._mface = mf + self.edges = [] + self.vertices = mf.vertices[:] + self.uvs = [] + self.flag = False + + def __getattr__(self, attr): + return getattr(self._mface, attr) + + def __cmp__(self, other): + if other is None: + return 1 + return cmp(self.index, other.index) + + def pivot_vertices(self, *vt): + flags = [(v in vt) for v in self.vertices] + l = len(self.vertices) + for i in range(l): + if flags[i] and not flags[(i+l-1)%l]: + return self.vertices[i:]+self.vertices[:i] + + def get_edge(self, v1, v2): + key = make_edge_key(v1.index, v2.index) + for e in self.edges: + if e.key==key: + return e + raise KeyError("No edge %s"%(key,)) + + def get_neighbors(self): + neighbors = [e.other_face(self) for e in self.edges] + return list(filter(bool, neighbors)) + + +class Line: + def __init__(self, e): + self.edge = e + self.vertices = e.vertices[:] + self.flag = False + + +class UvLayer: + def __init__(self, l, t): + self._layer = l + self.uvtex = t + self.name = self.uvtex.name + self.unit = None + self.hidden = False + dot = self.name.find('.') + if dot>=0: + ext = self.name[dot:] + if ext.startswith(".unit") and ext[5:].isdigit(): + self.unit = int(ext[5:]) + elif ext==".hidden": + self.hidden = True + + def __getattr__(self, attr): + return getattr(self._layer, attr) + +class FakeUvLayer: + def __init__(self, n): + self.uvtex = None + self.name = n + self.unit = None + self.hidden = False + +class Mesh: + def __init__(self, m): + self._mesh = m + + self.vertices = [Vertex(v) for v in self.vertices] + self.faces = [Face(f) for f in self.polygons] + + self.materials = self.materials[:] + + self.uv_layers = [UvLayer(self.uv_layers[i], self.uv_textures[i]) for i in range(len(self.uv_layers))] + self.assign_texture_units() + + for f in self.faces: + f.vertices = [self.vertices[i] for i in f.vertices] + for v in f.vertices: + v.faces.append(f) + for u in self.uv_layers: + f.uvs.append([u.data[f.loop_indices[i]].uv for i in range(len(f.vertices))]) + + self.edges = dict([(e.key, Edge(e)) for e in self.edges]) + for f in self.faces: + for k in f.edge_keys: + e = self.edges[k] + e.faces.append(self.faces[f.index]) + f.edges.append(e) + + self.lines = [Line(e) for e in self.edges.values() if not e.faces] + + if self.use_auto_smooth: + smooth_limit = math.cos(self.auto_smooth_angle*math.pi/180) + else: + smooth_limit = -1 + + for e in self.edges.values(): + e.vertices = [self.vertices[i] for i in e.vertices] + e.check_smooth(smooth_limit) + + def __getattr__(self, attr): + return getattr(self._mesh, attr) + + def splice(self, other): + material_map = [] + for m in other.materials: + if m in self.materials: + material_map.append(self.materials.index(m)) + else: + material_map.append(len(self.materials)) + self.materials.append(m) + + offset = len(self.vertices) + for v in other.vertices: + v.index += offset + self.vertices.append(v) + + offset = len(self.faces) + for f in other.faces: + f.index += offset + if other.materials: + f.material_index = material_map[f.material_index] + self.faces.append(f) + + for e in other.edges.values(): + e.key = make_edge_key(e.vertices[0].index, e.vertices[1].index) + self.edges[e.key] = e + + self.lines += other.lines + + def flatten_faces(self): + for f in self.faces: + f.use_smooth = False + + for e in self.edges.values(): + e.check_smooth(1) + + def assign_texture_units(self): + # Assign texture units for any non-hidden UV layers that lack one + units = [u.unit for u in self.uv_layers if u.unit is not None] + if units: + free_unit = max(units)+1 + else: + free_unit = 0 + for u in self.uv_layers: + if u.unit is None: + if not u.hidden: + u.unit = free_unit + free_unit += 1 + + def generate_material_uv(self): + self.uv_layers.append(FakeUvLayer("material_tex")) + self.assign_texture_units() + for f in self.faces: + f.uvs.append([((f.material_index+0.5)/len(self.materials), 0.5)]*len(f.vertices)) + + def split_vertices(self, find_group_func, progress, *args): + groups = [] + for i in range(len(self.vertices)): + v = self.vertices[i] + for f in v.faces: + f.flag = False + + vg = [] + for f in v.faces: + if not f.flag: + vg.append(find_group_func(v, f, *args)) + + groups.append(vg) + + if progress: + progress.set_progress(i*0.5/len(self.vertices)) + + for i in range(len(self.vertices)): + if len(groups[i])==1: + continue + + for g in groups[i][1:]: + v = Vertex(self.vertices[i]) + v.index = len(self.vertices) + self.vertices.append(v) + + for f in g: + for j in range(len(f.edges)): + e = f.edges[j] + + if self.vertices[i] not in e.vertices: + continue + + if e.other_face(f) not in g and len(e.faces)>=2: + e.faces.remove(f) + e = Edge(e) + f.edges[j] = e + e.faces.append(f) + else: + del self.edges[e.key] + + e.vertices[e.vertices.index(self.vertices[i])] = v + + e.key = make_edge_key(e.vertices[0].index, e.vertices[1].index) + self.edges[e.key] = e + + self.vertices[i].faces.remove(f) + f.vertices[f.vertices.index(self.vertices[i])] = v + v.faces.append(f) + + if progress: + progress.set_progress(0.5+i*0.5/len(self.vertices)) + + def split_smooth(self, progress = None): + self.split_vertices(self.find_smooth_group, progress) + + def split_uv(self, index, progress = None): + self.split_vertices(self.find_uv_group, progress, index) + + def find_smooth_group(self, vertex, face): + face.flag = True + queue = [face] + + for f in queue: + for e in f.edges: + other = e.other_face(f) + if other not in vertex.faces: + continue + + if e.smooth: + if not other.flag: + other.flag = True + queue.append(other) + + return queue + + def find_uv_group(self, vertex, face, index): + uv = face.uvs[index][face.vertices.index(vertex)] + face.flag = True + group = [face] + for f in vertex.faces: + if not f.flag and f.uvs[index][f.vertices.index(vertex)]==uv: + f.flag = True + group.append(f) + return group + + def compute_normals(self): + for v in self.vertices: + if v.faces: + v.normal = mathutils.Vector() + for f in v.faces: + fv = f.pivot_vertices(v) + edge1 = fv[1].co-fv[0].co + edge2 = fv[-1].co-fv[0].co + weight = 1 + if len(f.get_edge(fv[0], fv[1]).faces)==1: + weight += 1 + if len(f.get_edge(fv[0], fv[-1]).faces)==1: + weight += 1 + v.normal += f.normal*edge1.angle(edge2)*weight + v.normal.normalize() + else: + # XXX Should use edges to compute normal + v.normal = mathutils.Vector(0, 0, 1) + + def compute_uv(self): + for v in self.vertices: + if v.faces: + f = v.faces[0] + i = f.vertices.index(v) + v.uvs = [u[i] for u in f.uvs] + + def compute_tbn(self, index): + if not self.uv_layers: + return + + for v in self.vertices: + v.tan = mathutils.Vector() + v.bino = mathutils.Vector() + for f in v.faces: + fv = f.pivot_vertices(v) + uv0 = fv[0].uvs[index] + uv1 = fv[1].uvs[index] + uv2 = fv[-1].uvs[index] + du1 = uv1[0]-uv0[0] + du2 = uv2[0]-uv0[0] + dv1 = uv1[1]-uv0[1] + dv2 = uv2[1]-uv0[1] + edge1 = fv[1].co-fv[0].co + edge2 = fv[-1].co-fv[0].co + div = (du1*dv2-du2*dv1) + if div: + mul = edge1.angle(edge2)/div + v.tan += (edge1*dv2-edge2*dv1)*mul + v.bino += (edge2*du1-edge1*du2)*mul + + if v.tan.length: + v.tan.normalize() + if v.bino.length: + v.bino.normalize() + + def create_strip(self, face, max_len): + # Find an edge with another unused face next to it + edge = None + for e in face.edges: + other = e.other_face(face) + if other and not other.flag: + edge = e + break + + if not edge: + return None + + # Add initial vertices so that we'll complete the edge on the first + # iteration + vertices = face.pivot_vertices(*edge.vertices) + if len(vertices)==3: + result = [vertices[-1], vertices[0]] + else: + result = [vertices[-2], vertices[-1]] + + while 1: + face.flag = True + + vertices = face.pivot_vertices(*result[-2:]) + k = len(result)%2 + + # Quads need special handling because the winding of every other + # triangle in the strip is reversed + if len(vertices)==4 and not k: + result.append(vertices[3]) + result.append(vertices[2]) + if len(vertices)==4 and k: + result.append(vertices[3]) + + if len(result)>=max_len: + break + + # Hop over the last edge + edge = face.get_edge(*result[-2:]) + face = edge.other_face(face) + if not face or face.flag: + break + + return result diff --git a/blender/io_mspgl/outfile.py b/blender/io_mspgl/outfile.py new file mode 100644 index 00000000..ca7abcf9 --- /dev/null +++ b/blender/io_mspgl/outfile.py @@ -0,0 +1,30 @@ +import sys + +class OutFile: + def __init__(self, fn): + if fn==None: + self.file = sys.stdout + else: + self.file = open(fn, "w") + self.indent = 0 + + def make(self, kwd, *params): + pstr = "" + for p in params: + if type(p)==float: + pstr += " %.6g"%p + else: + pstr += " %s"%p + return "%s%s"%(kwd, pstr) + + def write(self, kwd, *params): + self.file.write("%s%s;\n"%('\t'*self.indent, self.make(kwd, *params))) + + def begin(self, kwd, *params): + i = '\t'*self.indent + self.file.write("%s%s\n%s{\n"%(i, self.make(kwd, *params), i)) + self.indent += 1 + + def end(self): + self.indent -= 1 + self.file.write("%s};\n"%('\t'*self.indent)) diff --git a/blender/io_mspgl/util.py b/blender/io_mspgl/util.py new file mode 100644 index 00000000..936a1c1d --- /dev/null +++ b/blender/io_mspgl/util.py @@ -0,0 +1,15 @@ +class Progress: + def __init__(self): + self.task = "" + self.start = 0.0 + self.delta = 1.0 + + def set_task(self, task, low, high): + self.task = task + self.start = low + self.delta = high-low + self.set_progress(0.0) + + def set_progress(self, value): + pass + #Blender.Window.DrawProgressBar(self.start+self.delta*value, self.task)