From 5028e8accde81677fc4a50c771b955cf324851b2 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Sun, 30 Jan 2011 09:45:13 +0000 Subject: [PATCH] Add a Blender 2.5 port of the exporter --- blender/io_mesh_mspgl/__init__.py | 36 +++ blender/io_mesh_mspgl/export_mspgl.py | 353 ++++++++++++++++++++++++++ blender/io_mesh_mspgl/mesh.py | 338 ++++++++++++++++++++++++ blender/io_mesh_mspgl/util.py | 15 ++ 4 files changed, 742 insertions(+) create mode 100644 blender/io_mesh_mspgl/__init__.py create mode 100644 blender/io_mesh_mspgl/export_mspgl.py create mode 100644 blender/io_mesh_mspgl/mesh.py create mode 100644 blender/io_mesh_mspgl/util.py diff --git a/blender/io_mesh_mspgl/__init__.py b/blender/io_mesh_mspgl/__init__.py new file mode 100644 index 00000000..1445ff40 --- /dev/null +++ b/blender/io_mesh_mspgl/__init__.py @@ -0,0 +1,36 @@ +import bpy +from io_utils import ExportHelper + +class ExportMspGL(bpy.types.Operator, ExportHelper): + bl_idname = "export.mspgl" + bl_label = "Export Msp GL data" + + filename_ext = ".mesh" + + 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) + tbn_vecs = bpy.props.BoolProperty(name="TBN vectors", description="Compute tangent and binormal vectors for vertices", default=False) + compound = bpy.props.BoolProperty(name="Compound", description="Combine all selected objects into one for exporting", default=False) + object = bpy.props.BoolProperty(name="Object", description="Export an object instead of a mesh", default=False) + material_tex = bpy.props.BoolProperty(name="Material texture", description="Generate a texture based on material colors", default=False) + + def execute(self, context): + from . import export_mspgl + exporter = export_mspgl.Exporter() + for k, v in self.as_keywords().items(): + setattr(exporter, k, v) + exporter.export(context, self.filepath) + return {"FINISHED"} + +def menu_func_export(self, context): + self.layout.operator(ExportMspGL.bl_idname, text="Msp GL") + +def register(): + bpy.types.INFO_MT_file_export.append(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 new file mode 100644 index 00000000..6de38f87 --- /dev/null +++ b/blender/io_mesh_mspgl/export_mspgl.py @@ -0,0 +1,353 @@ +# $Id: mesh_export.py 137 2010-12-05 19:22:35Z tdb $ + +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.tbn_vecs = False + self.compound = False + self.object = False + self.material_tex = False + + 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: + queue = [] + for f in mesh.faces: + if not f.flag: + f.flag = True + queue.append(f) + break + + if not queue: + break + + while queue: + f = queue[0] + del queue[0] + island.append(f) + + for e in f.edges: + other = e.other_face(f) + if other and not other.flag: + other.flag = True + queue.append(other) + + for f in island: + f.flag = False + + best = 5 + face = None + for f in island: + if f.flag: + continue + score = 0 + for e in f.edges: + other = e.other_face(f) + if other and not other.flag: + score += 1 + if score>0 and scorebest_hits: + best = i + best_hits = hits + + s = island_strips[best] + del island_strips[best] + strips.append(s) + + if cache: + cache.fetch_strip(s) + + faces_done += len(island) + if progress: + progress.set_progress(float(faces_done)/len(mesh.faces)) + + loose += [f for f in island if not f.flag] + for f in island: + f.flag = True + + island = [] + island_strips = [] + continue + + strip = mesh.create_strip(face, self.max_strip_len) + if strip: + island_strips.append(strip) + + 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: + 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: + 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.create_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) + mesh.split_smooth() + + mesh.compute_normals() + + if self.material_tex: + mesh.generate_material_uv() + + if mesh.has_uv: + progress.set_task("Splitting UVs", 0.35, 0.65) + mesh.split_uv() + + mesh.compute_uv() + if self.tbn_vecs: + mesh.compute_tbn() + + 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 mesh.has_uv: + fmt += "_TEXCOORD2" + if self.tbn_vecs: + fmt += "_ATTRIB33_ATTRIB34" + fmt += "_VERTEX3" + out_file.begin("vertices", fmt) + normal = None + uv = None + tan = None + bino = None + for v in mesh.vertices: + if v.normal!=normal: + out_file.write("normal3", *v.normal) + normal = v.normal + if v.uv!=uv: + out_file.write("texcoord2", *v.uv) + uv = v.uv + 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 self.material_tex: + out_file.begin("material") + out_file.write("diffuse", 1.0, 1.0, 1.0, 1.0) + out_file.end() + out_file.begin("texunit", 0) + 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() + elif mesh.materials: + m = mesh.materials[0] + out_file.begin("material") + out_file.write("diffuse", m.R, m.G, m.B, 1.0) + out_file.write("ambient", m.R*m.amb, m.G*m.amb, m.B*m.amb, 1.0) + out_file.write("specular", m.specR*m.spec, m.specG*m.spec, m.specB*m.spec, 1.0) + out_file.write("shininess", m.hard); + 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 new file mode 100644 index 00000000..5af85787 --- /dev/null +++ b/blender/io_mesh_mspgl/mesh.py @@ -0,0 +1,338 @@ +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) + if (d>limit and self.faces[0].use_smooth and self.faces[1].use_smooth) or d>0.999: + self.smooth = True + + 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.uv = mv.uv + else: + self._mvert = mv + self.uv = None + self.flag = False + self.faces = [] + self.tan = None + self.bino = None + + 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.uv = None + self.flag = False + self.material = None + + 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,)) + + +class Line: + def __init__(self, e): + self.edge = e + self.vertices = e.vertices[:] + self.flag = False + + +class Mesh: + def __init__(self, m): + self._mesh = m + self.vertices = [Vertex(v) for v in m.vertices] + self.faces = [Face(f) for f in m.faces] + self.materials = m.materials[:] + self.has_uv = False + + uvtex = None + if m.uv_textures: + uvtex = self.uv_textures[0] + self.has_uv = True + + for f in self.faces: + f.vertices = [self.vertices[i] for i in f.vertices] + if uvtex: + f.uv = uvtex.data[f.index].uv + for v in f.vertices: + v.faces.append(f) + + self.edges = dict([(e.key, Edge(e)) for e in m.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 m.use_auto_smooth: + smooth_limit = math.cos(m.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 + f.material = 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 generate_material_uv(self): + for f in self.faces: + f.uv = ([(f.material_index+0.5)/len(self.materials), 0.5],)*len(f.vertices) + self.has_uv = True + + def split_vertices(self, find_group_func, progress = None): + 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)) + + 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: + k = e.faces.index(f) + 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, progress = None): + self.split_vertices(self.find_uv_group, progress) + + 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 not other or other.index not in face_indices: + 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): + uv = face.uv[face.vertices.index(vertex)] + face.flag = True + group = [face] + for f in vertex.faces: + if not f.flag and f.uv[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: + v.normal += f.normal + 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: + v.uv = v.faces[0].uv[v.faces[0].vertices.index(v)] + + def compute_tbn(self): + for v in self.vertices: + v.tan = mathutils.Vector() + v.bino = mathutils.Vector() + for f in v.faces: + fv = f.pivot_vertices(False, v) + v1 = fv[1] + v2 = fv[-1] + du1 = v1.uv[0]-v.uv[0] + du2 = v2.uv[0]-v.uv[0] + dv1 = v1.uv[1]-v.uv[1] + dv2 = v2.uv[1]-v.uv[1] + div = du1*dv2-du2*dv1 + edge1 = fv[1].co-fv[0].co + edge2 = fv[-1].co-fv[0].co + if div: + v.tan += (edge1*dv2-edge2*dv1)/div + v.bino += (edge2*du1-edge1*du2)/div + if v.tan.length: + v.tan.normalize() + if v.bino.length: + v.bino.normalize() + + def create_strip(self, face, max_len): + 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 + + vertices = face.pivot_vertices(*edge.vertices) + if len(vertices)==3: + result = [vertices[-1], vertices[0]] + else: + result = [vertices[-2], vertices[-1]] + + while 1: + vertices = face.pivot_vertices(*result[-2:]) + k = len(result)%2 + + face.flag = True + 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 + + edge = face.get_edge(*result[-2:]) + + next = edge.other_face(face) + if not next or next.flag: + break + face = next + + return result diff --git a/blender/io_mesh_mspgl/util.py b/blender/io_mesh_mspgl/util.py new file mode 100644 index 00000000..936a1c1d --- /dev/null +++ b/blender/io_mesh_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) -- 2.45.2