]> git.tdb.fi Git - libs/gl.git/blob - blender/io_mspgl/export_mesh.py
Support for exporting armatures and vertex groups
[libs/gl.git] / blender / io_mspgl / export_mesh.py
1 import itertools
2 import bpy
3 from .outfile import OutFile
4
5 class VertexCache:
6         def __init__(self, size):
7                 self.size = size
8                 self.slots = [-1]*self.size
9
10         def fetch(self, v):
11                 hit = v.index in self.slots
12                 if hit:
13                         self.slots.remove(v.index)
14                 self.slots.append(v.index)
15                 if not hit:
16                         del self.slots[0]
17                 return hit
18
19         def fetch_strip(self, strip):
20                 hits = 0
21                 for v in strip:
22                         if self.fetch(v):
23                                 hits += 1
24                 return hits
25
26         def test_strip(self, strip):
27                 hits = 0
28                 for i in range(len(strip)):
29                         if i>=self.size:
30                                 break
31                         if strip[i].index in self.slots[i:]:
32                                 hits += 1
33                 return hits
34
35
36 class MeshExporter:
37         def __init__(self):
38                 self.use_strips = True
39                 self.use_degen_tris = True
40                 self.max_strip_len = 1024
41                 self.optimize_cache = False
42                 self.cache_size = 64
43                 self.export_lines = True
44                 self.export_uv = "UNIT0"
45                 self.tbn_vecs = False
46                 self.tbn_uvtex = ""
47                 self.compound = False
48                 self.object = False
49                 self.material_tex = False
50                 self.textures = "REF"
51                 self.smoothing = "MSPGL"
52                 self.export_groups = False
53                 self.max_groups = 2
54
55         def stripify(self, mesh, progress = None):
56                 for f in mesh.faces:
57                         f.flag = False
58
59                 faces_done = 0
60                 strips = []
61                 loose = []
62
63                 cache = None
64                 if self.optimize_cache:
65                         cache = VertexCache(self.cache_size)
66
67                 island = []
68                 island_strips = []
69                 while 1:
70                         if not island:
71                                 # No current island; find any unused face to start from
72                                 queue = []
73                                 for f in mesh.faces:
74                                         if not f.flag:
75                                                 f.flag = True
76                                                 queue.append(f)
77                                                 break
78
79                                 if not queue:
80                                         break
81
82                                 # Find all faces connected to the first one
83                                 while queue:
84                                         face = queue.pop(0)
85                                         island.append(face)
86
87                                         for n in f.get_neighbors():
88                                                 if not n.flag:
89                                                         n.flag = True
90                                                         queue.append(n)
91
92                                 # Unflag the island for the next phase
93                                 for f in island:
94                                         f.flag = False
95
96                         # Find an unused face with as few unused neighbors as possible, but
97                         # at least one.  This heuristic gives a preference to faces in corners
98                         # or along borders of a non-closed island.
99                         best = 5
100                         face = None
101                         for f in island:
102                                 if f.flag:
103                                         continue
104
105                                 score = sum(not n.flag for n in f.get_neighbors())
106                                 if score>0 and score<best:
107                                         face = f
108                                         best = score
109
110                         if face:
111                                 # Create a strip starting from the face.  This will flag the faces.
112                                 strip = mesh.create_strip(face, self.max_strip_len)
113                                 if strip:
114                                         island_strips.append(strip)
115                                 else:
116                                         face.flag = True
117                         else:
118                                 # Couldn't find a candidate face for starting a strip, so we're
119                                 # done with this island
120                                 while island_strips:
121                                         best = 0
122                                         if cache:
123                                                 # Find the strip that benefits the most from the current
124                                                 # contents of the vertex cache
125                                                 best_hits = 0
126                                                 for i in range(len(island_strips)):
127                                                         hits = cache.test_strip(island_strips[i])
128                                                         if hits>best_hits:
129                                                                 best = i
130                                                                 best_hits = hits
131
132                                         strip = island_strips.pop(best)
133                                         strips.append(strip)
134
135                                         if cache:
136                                                 cache.fetch_strip(strip)
137
138                                 faces_done += len(island)
139                                 if progress:
140                                         progress.set_progress(float(faces_done)/len(mesh.faces))
141
142                                 # Collect any faces that weren't used in strips
143                                 loose += [f for f in island if not f.flag]
144                                 for f in island:
145                                         f.flag = True
146
147                                 island = []
148                                 island_strips = []
149
150                 if cache:
151                         cache = VertexCache(self.cache_size)
152                         total_hits = 0
153
154                 if self.use_degen_tris and strips:
155                         big_strip = []
156
157                         for s in strips:
158                                 if big_strip:
159                                         # Generate glue elements, ensuring that the next strip begins at
160                                         # an even position
161                                         glue = [big_strip[-1], s[0]]
162                                         if len(big_strip)%2:
163                                                 glue += [s[0]]
164
165                                         big_strip += glue
166                                         if cache:
167                                                 total_hits += cache.fetch_strip(glue)
168
169                                 big_strip += s
170                                 if cache:
171                                         total_hits += cache.fetch_strip(s)
172
173                         for f in loose:
174                                 # Add loose faces to the end.  This wastes space, using five
175                                 # elements per triangle and six elements per quad.
176                                 if len(big_strip)%2:
177                                         order = (-1, -2, 0, 1)
178                                 else:
179                                         order = (0, 1, -1, -2)
180                                 vertices = [f.vertices[i] for i in order[:len(f.vertices)]]
181
182                                 if big_strip:
183                                         glue = [big_strip[-1], vertices[0]]
184                                         big_strip += glue
185                                         if cache:
186                                                 total_hits += cache.fetch_strip(glue)
187
188                                 big_strip += vertices
189                                 if cache:
190                                         total_hits += cache.fetch_strip(vertices)
191
192                         strips = [big_strip]
193                         loose = []
194
195                 return strips, loose
196
197         def export(self, context, fn):
198                 if self.compound:
199                         objs = context.selected_objects
200                 else:
201                         objs = [context.active_object]
202
203                 if not objs:
204                         raise Exception("Nothing to export")
205                 for o in objs:
206                         if o.type!="MESH":
207                                 raise Exception("Can only export Mesh data")
208
209                 from .mesh import Mesh
210                 from .util import Progress
211
212                 progress = Progress()
213                 progress.set_task("Preparing", 0.0, 0.0)
214
215                 mesh = None
216                 bmeshes = []
217                 for o in objs:
218                         bmesh = o.to_mesh(context.scene, True, "PREVIEW")
219                         bmeshes.append(bmesh)
220                         if not mesh:
221                                 mesh = Mesh(bmesh)
222                         else:
223                                 mesh.splice(Mesh(bmesh))
224
225                 progress.set_task("Smoothing", 0.05, 0.35)
226                 if self.smoothing=="NONE":
227                         mesh.flatten_faces()
228                 mesh.split_smooth(progress)
229
230                 if self.smoothing!="BLENDER":
231                         mesh.compute_normals()
232
233                 if self.export_groups:
234                         mesh.sort_vertex_groups(self.max_groups)
235
236                         # Create a mapping from vertex group indices to bone indices
237                         group_index_map = dict((i, i) for i in range(len(objs[0].vertex_groups)))
238                         if objs[0].parent and objs[0].parent.type=="ARMATURE":
239                                 armature = objs[0].parent.data
240                                 bone_indices = dict((armature.bones[i].name, i) for i in range(len(armature.bones)))
241                                 for g in objs[0].vertex_groups:
242                                         if g.name in bone_indices:
243                                                 group_index_map[g.index] = bone_indices[g.name]
244
245                 if self.material_tex and mesh.materials:
246                         mesh.generate_material_uv()
247
248                 texunits = []
249                 if mesh.uv_layers and self.export_uv!="NONE":
250                         # Figure out which UV layers to export
251                         if self.export_uv=="UNIT0":
252                                 if mesh.uv_layers[0].unit==0:
253                                         texunits = [0]
254                         else:
255                                 texunits = range(len(mesh.uv_layers))
256                         texunits = [(i, mesh.uv_layers[i]) for i in texunits]
257                         texunits = [u for u in texunits if not u[1].hidden]
258
259                         if self.tbn_vecs:
260                                 # TBN coordinates must be generated before vertices are split by any other layer
261                                 uv_names = [u.name for i, u in texunits]
262                                 if self.tbn_uvtex in uv_names:
263                                         tbn_index = uv_names.index(self.tbn_uvtex)
264                                         unit = texunits[tbn_index]
265                                         del texunits[tbn_index]
266                                         texunits.insert(0, unit)
267
268                         for i, u in texunits:
269                                 progress.set_task("Splitting UVs", 0.35+0.3*i/len(texunits), 0.35+0.3*(i+1)/len(texunits))
270                                 mesh.split_uv(i, progress)
271                                 if self.tbn_vecs and u.name==self.tbn_uvtex:
272                                         mesh.compute_uv()
273                                         mesh.compute_tbn(i)
274
275                         mesh.compute_uv()
276
277                 strips = []
278                 loose = mesh.faces
279                 if self.use_strips:
280                         progress.set_task("Creating strips", 0.65, 0.95)
281                         strips, loose = self.stripify(mesh, progress)
282
283                 progress.set_task("Writing file", 0.95, 1.0)
284
285                 out_file = OutFile(fn)
286                 if self.object:
287                         out_file.begin("mesh")
288
289                 fmt = "NORMAL3"
290                 if texunits:
291                         for i, u in texunits:
292                                 if u.unit==0:
293                                         fmt += "_TEXCOORD2"
294                                 else:
295                                         fmt += "_TEXCOORD2%d"%u.unit
296                         if self.tbn_vecs:
297                                 fmt += "_ATTRIB33_ATTRIB34"
298                 if self.export_groups:
299                         fmt += "_ATTRIB%d5"%(self.max_groups*2)
300                 fmt += "_VERTEX3"
301                 out_file.begin("vertices", fmt)
302                 normal = None
303                 uvs = [None]*len(texunits)
304                 tan = None
305                 bino = None
306                 group = None
307                 for v in mesh.vertices:
308                         if v.normal!=normal:
309                                 out_file.write("normal3", *v.normal)
310                                 normal = v.normal
311                         for i, u in texunits:
312                                 if v.uvs[i]!=uvs[i]:
313                                         if u.unit==0:
314                                                 out_file.write("texcoord2", *v.uvs[i])
315                                         else:
316                                                 out_file.write("multitexcoord2", u.unit, *v.uvs[i])
317                                         uvs[i] = v.uvs[i]
318                         if self.tbn_vecs:
319                                 if v.tan!=tan:
320                                         out_file.write("attrib3", 3, *v.tan)
321                                         tan = v.tan
322                                 if v.bino!=bino:
323                                         out_file.write("attrib3", 4, *v.bino)
324                                         bino = v.bino
325                         if self.export_groups:
326                                 group_attr = [(group_index_map[g.group], g.weight*v.group_weight_scale) for g in v.groups[:self.max_groups]]
327                                 while len(group_attr)<self.max_groups:
328                                         group_attr.append((0, 0.0))
329                                 group_attr = list(itertools.chain(*group_attr))
330                                 if group_attr!=group:
331                                         out_file.write("attrib%d"%len(group_attr), 5, *group_attr)
332                                         group = group_attr
333                         out_file.write("vertex3", *v.co)
334                 out_file.end()
335                 for s in strips:
336                         out_file.begin("batch", "TRIANGLE_STRIP")
337                         indices = []
338                         n = 0
339                         for v in s:
340                                 indices.append(v.index)
341                                 if len(indices)>=32:
342                                         out_file.write("indices", *indices)
343                                         indices = []
344                         if indices:
345                                 out_file.write("indices", *indices)
346                         out_file.end()
347
348                 if loose:
349                         out_file.begin("batch", "TRIANGLES")
350                         for f in loose:
351                                 for i in range(2, len(f.vertices)):
352                                         out_file.write("indices", f.vertices[0].index, f.vertices[i-1].index, f.vertices[i].index)
353                         out_file.end()
354
355                 if self.export_lines and mesh.lines:
356                         out_file.write("batch", "LINES")
357                         for l in mesh.lines:
358                                 out_file.write("indices", l.vertices[0].index, l.vertices[1].index)
359                         out_file.end()
360
361                 if self.object:
362                         out_file.end()
363                         out_file.begin("technique")
364                         out_file.begin("pass", '""')
365                         if mesh.materials:
366                                 if self.material_tex:
367                                         out_file.begin("material")
368                                         out_file.write("diffuse", 1.0, 1.0, 1.0, 1.0)
369                                         out_file.end()
370                                         index = 0
371                                         for u in mesh.uv_layers:
372                                                 if u.name=="material_tex":
373                                                         index = u.unit
374                                         out_file.begin("texunit", index)
375                                         out_file.begin("texture2d")
376                                         out_file.write("min_filter", "NEAREST")
377                                         out_file.write("mag_filter", "NEAREST")
378                                         out_file.write("storage", "RGB", len(mesh.materials), 1)
379                                         texdata = '"'
380                                         for m in mesh.materials:
381                                                 color = [int(c*255) for c in m.diffuse_color]
382                                                 texdata += "\\x%02X\\x%02X\\x%02X"%tuple(color)
383                                         texdata += '"'
384                                         out_file.write("raw_data", texdata)
385                                         out_file.end()
386                                         out_file.end()
387                                 else:
388                                         mat = mesh.materials[0]
389                                         out_file.begin("material")
390                                         diff = mat.diffuse_color
391                                         out_file.write("diffuse", diff.r, diff.g, diff.b, 1.0)
392                                         amb = diff*mat.ambient
393                                         out_file.write("ambient", amb.r, amb.g, amb.b, 1.0)
394                                         spec = mat.specular_color*mat.specular_intensity
395                                         out_file.write("specular", spec.r, spec.g, spec.b, 1.0)
396                                         out_file.write("shininess", mat.specular_hardness);
397                                         out_file.end()
398
399                                 if self.textures!="NONE":
400                                         for slot in mesh.materials[0].texture_slots:
401                                                 if not slot:
402                                                         continue
403
404                                                 tex = slot.texture
405                                                 if tex.type!="IMAGE":
406                                                         continue
407
408                                                 if slot.uv_layer:
409                                                         for u in mesh.uv_layers:
410                                                                 if u.name==slot.uv_layer:
411                                                                         index = u.unit
412                                                 else:
413                                                         index = mesh.uv_layers[0].unit
414
415                                                 out_file.begin("texunit", index)
416                                                 if self.textures=="INLINE":
417                                                         out_file.begin("texture2d")
418                                                         out_file.write("min_filter", "LINEAR")
419                                                         out_file.write("storage", "RGBA", tex.image.size[0], tex.image.size[1])
420                                                         texdata = '"'
421                                                         for p in tex.image.pixels:
422                                                                 texdata += "\\x%02X"%int(p*255)
423                                                         texdata += '"'
424                                                         out_file.write("raw_data", texdata)
425                                                         out_file.end()
426                                                 else:
427                                                         out_file.write("texture", '"%s"'%tex.image.name)
428                                                 out_file.end()
429
430                         out_file.end()
431                         out_file.end()
432
433                 progress.set_task("Done", 1.0, 1.0)
434
435                 for m in bmeshes:
436                         bpy.data.meshes.remove(m)