X-Git-Url: http://git.tdb.fi/?p=libs%2Fgl.git;a=blobdiff_plain;f=mesh_export.py;h=41ceb90a7baed794eaf7fa1cf6cc6d0276efb5e2;hp=0158bdbf9189d3cff6e2bedae25b8e040601d02b;hb=bc3c82a86eeadde54be9fe32a8a8a76872ca99c3;hpb=926d77eb54f915a6620eb879a91c1c4bd0f06bbc diff --git a/mesh_export.py b/mesh_export.py index 0158bdbf..41ceb90a 100644 --- a/mesh_export.py +++ b/mesh_export.py @@ -12,22 +12,35 @@ import math import bpy import Blender +def make_edge_key(i1, i2): + return (min(i1, i2), max(i1, i2)) + + class Edge: def __init__(self, me): - self._medge=me + if me.__class__==Edge: + self._medge=me._medge + self.v1=me.v1 + self.v2=me.v2 + self.smooth=me.smooth + else: + self._medge=me + self.smooth=False self.faces=[] - self.smooth=False def __getattr__(self, attr): return getattr(self._medge, attr) + def __cmp__(self, other): + return self is other + def check_smooth(self, limit): if len(self.faces)!=2: return d=Blender.Mathutils.DotVecs(self.faces[0].no, self.faces[1].no) if (d>limit and self.faces[0].smooth and self.faces[1].smooth) or d>0.999: - self.smooth=1 + self.smooth=True def other_face(self, f): if f.index==self.faces[0].index: @@ -43,17 +56,34 @@ class Vertex: def __init__(self, mv): if mv.__class__==Vertex: self._mvert=mv._mvert + self.no=mv.no + self.uv=mv.uv else: self._mvert=mv + self.uv=None + self.orig_index=self._mvert.index + 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) + + def __str__(self): + return ""%(self.index, self.co[0], self.co[1], self.co[2], self.no[0], self.no[1], self.no[2]) + + __repr__=__str__ + class Face: def __init__(self, mf): self._mface=mf - self.smooth_group=None self.edges=[] self.verts=[v for v in mf.verts] self.flag=False @@ -61,87 +91,229 @@ class Face: def __getattr__(self, attr): return getattr(self._mface, attr) - def get_vertices_from(self, *vt): - indices=[u.index for u in vt] - flags=[(v.index in indices) for v in self.verts] - l=len(self.verts) + def __cmp__(self, other): + if other is None: + return 1 + return cmp(self.index, other.index) + + def __str__(self): + return ""%(self.index, " ".join([str(v.index) for v in self.verts])) + + __repr__=__str__ + + def pivot_vertices(self, reverse, *vt): + verts=self.verts[:] + if reverse: + verts.reverse() + flags=[(v in vt) for v in verts] + l=len(verts) for i in range(l): if flags[i] and not flags[(i+l-1)%l]: - return self.verts[i:]+self.verts[:i] + return verts[i:]+verts[: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 SmoothGroup: - def __init__(self, index): - self.index=index - self.faces=[] - self.verts=[] - def find_vertices(self): - vert_map={} +class Line: + def __init__(self, e): + self.edge=e + self.verts=[e.v1, e.v2] + self.flag=False + + def __str__(self): + return ""%(self.verts[0].index, self.verts[1].index) + + __repr__=__str__ + + +class Mesh: + def __init__(self, m): + self._mesh=m + self.verts=[Vertex(v) for v in m.verts] + self.faces=[Face(f) for f in m.faces] + for f in self.faces: for i in range(len(f.verts)): - v=f.verts[i] - if v.index not in vert_map: - vt=Vertex(v) - vt.no=Blender.Mathutils.Vector(f.no) - self.verts.append(vt) - vert_map[v.index]=vt - f.verts[i]=vt - else: - f.verts[i]=vert_map[v.index] - vert_map[v.index].no+=f.no + f.verts[i]=self.verts[f.verts[i].index] + f.verts[i].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.itervalues() if not e.faces] + + smooth_limit=math.cos(m.degr*math.pi/180) + for e in self.edges.itervalues(): + e.v1=self.verts[e.v1.index] + e.v2=self.verts[e.v2.index] + e.check_smooth(smooth_limit) + + def __getattr__(self, attr): + return getattr(self._mesh, attr) + def split_vertices(self, find_group_func, debug): + groups=[] for v in self.verts: - v.no.normalize() + 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)) -class Exporter: - def __init__(self, fn): - self.filename=fn - if fn==None: - self.out_file=sys.stdout - else: - self.out_file=file(fn, "w") - self.use_strips=True - self.use_degen_tris=True - self.debug=False + groups.append(vg) + + for i in range(len(self.verts)): + if len(groups[i])==1: + continue + + if debug: + print "Vertex %s has %d groups"%(self.verts[i], len(groups[i])) + + for g in groups[i][1:]: + v=Vertex(self.verts[i]) + v.index=len(self.verts) + self.verts.append(v) + + if debug: + print " -> %d %s"%(v.index, [f.index for f in g]) + + for f in g: + for j in range(len(f.edges)): + e=f.edges[j] + + if e.v1!=self.verts[i] and e.v2!=self.verts[i]: + continue + + if debug: + print " Splitting edge %s with faces %s"%(e.key, e.faces) + + 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] + + if e.v1==self.verts[i]: + e.v1=v + elif e.v2==self.verts[i]: + e.v2=v + + e.key=make_edge_key(e.v1.index, e.v2.index) + self.edges[e.key]=e + + self.verts[i].faces.remove(f) + f.verts[f.verts.index(self.verts[i])]=v + v.faces.append(f) + + def split_smooth(self, debug=False): + self.split_vertices(self.find_smooth_group, debug) + + def split_uv(self, debug=False): + self.split_vertices(self.find_uv_group, debug) - def find_smooth_group(self, face, sg): - face.smooth_group=sg - sg.faces.append(face) + def find_smooth_group(self, vert, face): + face.flag=True queue=[face] - while queue: - cur=queue.pop(0) - for e in cur.edges: + + 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 vert.faces: + continue + if e.smooth: - other=e.other_face(cur) - if other and not other.smooth_group: - other.smooth_group=sg - sg.faces.append(other) + if not other.flag: + other.flag=True queue.append(other) - def create_strip(self, face): + return queue + + def find_uv_group(self, vert, face): + uv=face.uv[face.verts.index(vert)] + face.flag=True + group=[face] + for f in vert.faces: + if not f.flag and f.uv[f.verts.index(vert)]==uv: + f.flag=True + group.append(f) + return group + + def compute_normals(self): + for v in self.verts: + if v.faces: + v.no=Blender.Mathutils.Vector() + for f in v.faces: + v.no+=f.no + v.no.normalize() + else: + # XXX Should use edges to compute normal + v.no=Blender.Mathutils.Vector(0, 0, 1) + + def compute_uv(self): + for v in self.verts: + if v.faces: + v.uv=v.faces[0].uv[v.faces[0].verts.index(v)] + + def compute_tbn(self): + for v in self.verts: + v.tan=Blender.Mathutils.Vector() + v.bino=Blender.Mathutils.Vector() + for f in v.faces: + fverts=f.pivot_vertices(False, v) + v1=fverts[1] + v2=fverts[-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=fverts[1].co-fverts[0].co + edge2=fverts[-1].co-fverts[0].co + v.tan+=(edge1*dv2-edge2*dv1)/div + v.bino+=(edge2*du1-edge1*du2)/div + v.tan.normalize() + v.bino.normalize() + + def create_strip(self, face, reverse, debug): edge=None for e in face.edges: other=e.other_face(face) - if other and other.smooth_group.index==face.smooth_group.index and not other.flag: + if other and not other.flag: edge=e break if not edge: return None - if self.debug: - print "Starting strip from %s"%[v.index for v in face.verts] + if debug: + print "Starting strip from %s, edge %s, reverse=%s"%([v.index for v in face.verts], (edge.v1.index, edge.v2.index), reverse) - verts=face.get_vertices_from(edge.v1, edge.v2) - result=[verts[-2], verts[-1]] + verts=face.pivot_vertices(reverse, edge.v1, edge.v2) + if len(verts)==3: + result=[verts[-1], verts[0]] + else: + result=[verts[-2], verts[-1]] while 1: - verts=face.get_vertices_from(*result[-2:]) + verts=face.pivot_vertices(reverse, *result[-2:]) k=len(result)%2 - if self.debug: - print " %d %s"%(len(result), [v.index for v in verts]) + if debug: + print " Adding %s"%face face.flag=True if len(verts)==4 and not k: @@ -150,24 +322,59 @@ class Exporter: if len(verts)==4 and k: result.append(verts[3]) - i1=result[-2].index - i2=result[-1].index - ekey=(min(i1, i2), max(i1, i2)) - for e in face.edges: - if e.key==ekey: - edge=e - break + edge=face.get_edge(*result[-2:]) + + if debug: + print " Next edge is %s"%(edge.key, ) next=edge.other_face(face) - if not next or next.smooth_group.index!=face.smooth_group.index or next.flag: + if not next or next.flag: break face=next - if self.debug: + if debug: print " %s"%[v.index for v in result] return result + +class Exporter: + def __init__(self, fn): + self.filename=fn + if fn==None: + self.out_file=sys.stdout + else: + self.out_file=file(fn, "w") + self.use_strips=True + self.use_degen_tris=True + self.optimize_locality=True + self.export_lines=True + self.tbn_vecs=False + self.debug=False + self.strip_debug=False + self.split_debug=False + + def get_locality(self, strip): + total=0 + for i in range(1, len(strip)): + if strip[i].index!=strip[i-1].index: + total+=1.0/(abs(strip[i].index-strip[i-1].index)) + return total/len(strip) + + def get_followers(self, strip): + result={} + for i in range(len(strip)-1): + v=strip[i] + n=strip[i+1] + if v.index!=n.index: + if v.index not in result: + result[v.index]={} + if n.index not in result[v.index]: + result[v.index][n.index]=1 + else: + result[v.index][n.index]+=1 + return result + def export(self): scene=bpy.data.scenes.active @@ -175,78 +382,152 @@ class Exporter: if obj.getType()!="Mesh": raise Exception, "Can only export Mesh data" - mesh=obj.getData(mesh=True) + mesh=Mesh(obj.getData(mesh=True)) - faces=[Face(f) for f in mesh.faces] + if self.debug: + ntris=sum([len(f.verts)-2 for f in mesh.faces]) + print "Starting with %d vertices, %d faces (%d triangles) and %d edges"%(len(mesh.verts), len(mesh.faces), ntris, len(mesh.edges)) - edges=dict([(e.key, Edge(e)) for e in mesh.edges]) - for f in faces: - for e in f.edge_keys: - edges[e].faces.append(f) - f.edges.append(edges[e]) - - smooth_limit=math.cos(mesh.degr*math.pi/180) - for e in edges.itervalues(): - e.check_smooth(smooth_limit) + mesh.split_smooth(self.split_debug) if self.debug: - print "%d faces, %d edges"%(len(faces), len(edges)) + print "After smooth splitting %d vertices and %d edges"%(len(mesh.verts), len(mesh.edges)) - smooth_groups=[] - for f in faces: - if not f.smooth_group: - sg=SmoothGroup(len(smooth_groups)) - smooth_groups.append(sg) - self.find_smooth_group(f, sg) + mesh.compute_normals() - for sg in smooth_groups: - sg.find_vertices() + if mesh.faceUV: + mesh.split_uv(self.split_debug) + if self.debug: + print "After UV splitting %d vertices and %d edges"%(len(mesh.verts), len(mesh.edges)) - if self.debug: - print "%d smooth groups:" - for i in range(len(smooth_groups)): - sg=smooth_groups[i] - print " %d: %d faces, %d vertices"%(i, len(sg.faces), len(sg.verts)) + mesh.compute_uv() + if self.tbn_vecs: + mesh.compute_tbn() strips=[] if self.use_strips: - for sg in smooth_groups: - for f in sg.faces: - if not f.flag: - strip=self.create_strip(f) - if strip: - strips.append(strip) + for f in mesh.faces: + f.flag=False + + while 1: + best=5 + face=None + for f in mesh.faces: + 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 and not mesh.verts[n].flag: + next=mesh.verts[n] + best=flw[n]+0.9/abs(vert.index-n) + + if not next: + for v in mesh.verts: + if not v.flag: + next=v + break + if not next: + break + + vert=next + + mesh.verts=verts2 + + for i in range(len(mesh.verts)): + mesh.verts[i].index=i - self.out_file.write("vertices NORMAL3_VERTEX3\n{\n") + if self.debug: + print "Locality after optimization: "+" ".join(["%.3f"%self.get_locality(s) for s in strips]) + + self.out_file.write("vertices NORMAL3") + if mesh.faceUV: + self.out_file.write("_TEXCOORD2") + if self.tbn_vecs: + self.out_file.write("_ATTRIB33_ATTRIB34") + self.out_file.write("_VERTEX3\n{\n") norm=None - for v in verts: + uv=None + tan=None + bino=None + for v in mesh.verts: if v.no!=norm: self.out_file.write("\tnormal3 %f %f %f;\n"%tuple(v.no)) norm=v.no + if v.uv!=uv: + self.out_file.write("\ttexcoord2 %f %f;\n"%tuple(v.uv)) + uv=v.uv + if v.tan!=tan: + self.out_file.write("\tattrib3 3 %f %f %f;\n"%tuple(v.tan)) + tan=v.tan + if v.bino!=bino: + self.out_file.write("\tattrib3 4 %f %f %f;\n"%tuple(v.bino)) + bino=v.bino self.out_file.write("\tvertex3 %f %f %f;\n"%tuple(v.co)) self.out_file.write("};\n") for s in strips: @@ -260,7 +541,7 @@ class Exporter: self.out_file.write(";\n};\n") first=True - for f in faces: + for f in mesh.faces: if not f.flag: if first: self.out_file.write("batch TRIANGLES\n{\n") @@ -270,15 +551,69 @@ class Exporter: if not first: self.out_file.write("};\n") + if self.export_lines and mesh.lines: + self.out_file.write("batch LINES\n{\n") + for l in mesh.lines: + self.out_file.write("\tindices %u %u;\n"%(l.verts[0].index, l.verts[1].index)) + self.out_file.write("};\n") + class FrontEnd: + def __init__(self): + self.config=Blender.Registry.GetKey('mspgl_export', True) or {} + self.temp_config=Blender.Registry.GetKey('mspgl_export_temp') or {} + def run(self): - #self.export(None) - Blender.Window.FileSelector(self.export, "Export MSP GL mesh", Blender.sys.makename(ext='.mesh')) + self.use_strips=Blender.Draw.Create(self.config.get('use_strips', True)) + self.use_degen_tris=Blender.Draw.Create(self.config.get('use_degen_tris', True)) + self.optimize_locality=Blender.Draw.Create(self.config.get('optimize_locality', True)) + self.export_lines=Blender.Draw.Create(self.config.get('export_lines', False)) + self.tbn_vecs=Blender.Draw.Create(self.config.get('tbn_vecs', False)) + self.debug=Blender.Draw.Create(self.config.get('debug', False)) + self.strip_debug=Blender.Draw.Create(self.config.get('strip_debug', False)) + self.split_debug=Blender.Draw.Create(self.config.get('split_debug', False)) + ret=Blender.Draw.PupBlock("Export MSP GL mesh", + [("Use strips", self.use_strips, "Generage OpenGL triangle strips"), + ("Use degen tris", self.use_degen_tris, "Use degenerate triangles to combine triangle strips"), + ("Optimize locality", self.optimize_locality), + ("Export lines", self.export_lines, "Export lone edges as lines"), + ("Compute T/B vecs", self.tbn_vecs, "Compute tangent/binormal vectors for bumpmapping"), + ("Debugging options"), + ("Debug", self.debug), + ("Debug strips", self.strip_debug), + ("Debug splitting", self.split_debug)]) + if ret: + dirname=self.temp_config.get("dirname", Blender.sys.dirname(Blender.Get("filename"))) + obj=bpy.data.scenes.active.objects.active + Blender.Window.FileSelector(self.export, "Export MSP GL mesh", "%s/%s.mesh"%(dirname, obj.name)) + + def draw(self): + pass def export(self, fn): + self.config['use_strips']=self.use_strips.val + self.config['use_degen_tris']=self.use_degen_tris.val + self.config['optimize_locality']=self.optimize_locality.val + self.config['export_lines']=self.export_lines.val + self.config['tbn_vecs']=self.tbn_vecs.val + self.config['debug']=self.debug.val + self.config['strip_debug']=self.strip_debug.val + self.config['split_debug']=self.split_debug.val + Blender.Registry.SetKey('mspgl_export', self.config, True) + + import os + self.temp_config["dirname"]=os.path.dirname(fn) + Blender.Registry.SetKey('mspgl_export_temp', self.temp_config) + exp=Exporter(fn) - #exp.use_degen_tris=False + exp.use_strips=self.use_strips.val + exp.use_degen_tris=self.use_degen_tris.val + exp.optimize_locality=self.optimize_locality.val + exp.export_lines=self.export_lines.val + exp.tbn_vecs=self.tbn_vecs.val + exp.debug=self.debug.val + exp.strip_debug=self.strip_debug.val + exp.split_debug=self.split_debug.val exp.export()