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