1 | |
---|
2 | from __future__ import nested_scopes |
---|
3 | |
---|
4 | from vector import Vector |
---|
5 | import pgon |
---|
6 | |
---|
7 | import pprint |
---|
8 | |
---|
9 | def sum(seq): |
---|
10 | return reduce(lambda a, b: a + b, seq) |
---|
11 | |
---|
12 | # color property of a vertex |
---|
13 | class ColorProp: |
---|
14 | |
---|
15 | color = None |
---|
16 | uv = None |
---|
17 | |
---|
18 | def __init__(self, data): |
---|
19 | if len(data) == 2: |
---|
20 | self.color = None |
---|
21 | self.uv = data |
---|
22 | else: |
---|
23 | self.color = data |
---|
24 | self.uv = None |
---|
25 | |
---|
26 | def __repr__(self): |
---|
27 | r = [] |
---|
28 | if self.uv: |
---|
29 | u, v = self.uv |
---|
30 | r.append("uv %.3f %.3f" % (u, v)) |
---|
31 | if self.color: |
---|
32 | r, g, b, a = self.color |
---|
33 | r.append("color %.3f %.3f %.3f %.3f" % (r, g, b, a)) |
---|
34 | if r: return " ".join(r) |
---|
35 | else: return "none" |
---|
36 | |
---|
37 | |
---|
38 | # object material |
---|
39 | class Material: |
---|
40 | |
---|
41 | """Material class: |
---|
42 | class members: |
---|
43 | name |
---|
44 | diffuse: diffuse color |
---|
45 | ambient: ambient color |
---|
46 | specular: specular color |
---|
47 | shininess |
---|
48 | opacity |
---|
49 | textures: list of texture layers |
---|
50 | """ |
---|
51 | |
---|
52 | def __init__(self, name): |
---|
53 | self.name = name |
---|
54 | self.diffuse = (1,1,1,1) |
---|
55 | self.ambient = (0,0,0,1) |
---|
56 | self.specular = (0,0,0,1) |
---|
57 | self.shininess = 0 |
---|
58 | self.textures = [] |
---|
59 | |
---|
60 | def __repr__(self): |
---|
61 | return 'Material("' + self.name + '")' |
---|
62 | |
---|
63 | |
---|
64 | class GLVertex: |
---|
65 | |
---|
66 | pos = None |
---|
67 | normal = None |
---|
68 | uv = None |
---|
69 | color = None |
---|
70 | material = None |
---|
71 | |
---|
72 | def __eq__(self, other): |
---|
73 | return self.pos == other.pos \ |
---|
74 | and self.normal == other.normal \ |
---|
75 | and self.uv == other.uv \ |
---|
76 | and self.color == other.color \ |
---|
77 | and self.material == other.material |
---|
78 | |
---|
79 | def __repr__(self): |
---|
80 | seq = [] |
---|
81 | seq.append("pos:" + str(self.pos)) |
---|
82 | seq.append("normal:" + str(self.normal)) |
---|
83 | if self.uv != None: |
---|
84 | u, v = self.uv |
---|
85 | seq.append("uv: (%.3f,%.3f)" % (u, v)) |
---|
86 | if self.color != None: |
---|
87 | r, g, b, a = self.color |
---|
88 | seq.append("color: (%.3f,%.3f,%.3f,%.3f)" % (r, g, b, a)) |
---|
89 | if self.material != None: seq.append("material:" + repr(self.material)) |
---|
90 | return " ".join(seq) |
---|
91 | |
---|
92 | class SubMesh: |
---|
93 | |
---|
94 | """SubMesh class: |
---|
95 | a set of triangles using the same material |
---|
96 | |
---|
97 | submesh data: |
---|
98 | material: material index in parent's materials table |
---|
99 | gltris: vertex indices to parent's glverts table |
---|
100 | """ |
---|
101 | |
---|
102 | def __init__(self): |
---|
103 | self.material = None |
---|
104 | self.mat_data = None |
---|
105 | self.gltris = [] |
---|
106 | self.glverts = [] |
---|
107 | |
---|
108 | # object which describes a mesh |
---|
109 | class Mesh: |
---|
110 | |
---|
111 | """Mesh class |
---|
112 | class members: |
---|
113 | verts: three dimensional coordinates of vertices in this mesh |
---|
114 | faces: sequences containing the vertex indices of the polygons |
---|
115 | hard_edges: contains face index tuples for hard edges |
---|
116 | face_materials: material data for faces |
---|
117 | |
---|
118 | calculated data: |
---|
119 | face_normals: normal vectors of faces |
---|
120 | |
---|
121 | data associated with (face, vertex) tuples: |
---|
122 | face_vert_normals: vertex normals |
---|
123 | face_vert_colors: color or uv information |
---|
124 | |
---|
125 | processed vertex data: |
---|
126 | glverts |
---|
127 | glfaces: faces |
---|
128 | gltris: triangulated glfaces |
---|
129 | """ |
---|
130 | |
---|
131 | def __init__(self): |
---|
132 | self.verts = [] |
---|
133 | self.faces = [] |
---|
134 | self.edges = [] |
---|
135 | self.hard_edges = [] |
---|
136 | self.face_materials = [] |
---|
137 | |
---|
138 | self.materials = [] |
---|
139 | |
---|
140 | self.face_normals = [] |
---|
141 | self.face_vert_normals = {} |
---|
142 | self.face_vert_colors = {} |
---|
143 | |
---|
144 | self.glverts = [] |
---|
145 | self.glfaces = [] |
---|
146 | self.gltris = [] |
---|
147 | self.tri_materials = [] |
---|
148 | |
---|
149 | self.shared_geometry = 0 |
---|
150 | |
---|
151 | def faces_containing_vertex(self, vertex): |
---|
152 | return filter(lambda face: vertex in self.faces[face], |
---|
153 | range(len(self.faces))) |
---|
154 | |
---|
155 | def face_material(self): |
---|
156 | return None |
---|
157 | |
---|
158 | def make_face_normals(self): |
---|
159 | "Calculate face normals" |
---|
160 | |
---|
161 | self.face_normals = [] |
---|
162 | for face in self.faces: |
---|
163 | n = pgon.pgon_normal(map(lambda v: self.verts[v], face)) |
---|
164 | self.face_normals.append(n) |
---|
165 | |
---|
166 | def face_vert_shareable(self, face1, face2, vertex): |
---|
167 | |
---|
168 | # returns true if the vertex has the same gl data for the two vertices |
---|
169 | glvert1 = self.make_gl_vert(face1, vertex) |
---|
170 | glvert2 = self.make_gl_vert(face2, vertex) |
---|
171 | |
---|
172 | return glvert1 == glvert2 |
---|
173 | |
---|
174 | def faces_same_smoothing_simple(self, face1, face2, vertex): |
---|
175 | |
---|
176 | minf, maxf = min(face1, face2), max(face1, face2) |
---|
177 | |
---|
178 | # check if the edge is hard between the faces |
---|
179 | return (minf, maxf) not in self.hard_edges |
---|
180 | |
---|
181 | def faces_same_smoothing_full(self, face1, face2, vertex): |
---|
182 | |
---|
183 | myedges = [] |
---|
184 | for e in self.edges: |
---|
185 | f1, f2, v1, v2 = e |
---|
186 | if vertex in [v1, v2]: |
---|
187 | myedges.append(e) |
---|
188 | |
---|
189 | same_smooth = [] |
---|
190 | buf = [face1] |
---|
191 | while len(buf) > 0: |
---|
192 | face = buf.pop() |
---|
193 | same_smooth.append(face) |
---|
194 | for e in myedges: |
---|
195 | f1, f2, v1, v2 = e |
---|
196 | if face in [f1, f2] and vertex in [v1, v2]: |
---|
197 | if face == f1: |
---|
198 | otherface = f2 |
---|
199 | else: |
---|
200 | otherface = f1 |
---|
201 | if otherface not in same_smooth: |
---|
202 | if (f1, f2) not in self.hard_edges: |
---|
203 | buf.append(otherface) |
---|
204 | #print same_smooth |
---|
205 | |
---|
206 | return face2 in same_smooth |
---|
207 | |
---|
208 | def partition_verts(self, pred): |
---|
209 | "Partition vertices using the given predicate" |
---|
210 | |
---|
211 | result = [] |
---|
212 | for vertex in range(len(self.verts)): |
---|
213 | buckets = [] |
---|
214 | for face in self.faces_containing_vertex(vertex): |
---|
215 | found_bucket = None |
---|
216 | |
---|
217 | # find a bucket for this face |
---|
218 | for bucket in buckets: |
---|
219 | |
---|
220 | # find faces which are compatible with current |
---|
221 | flags = map(lambda f: pred(face, f, vertex), bucket) |
---|
222 | |
---|
223 | # check if this is ok |
---|
224 | if 0 not in flags: |
---|
225 | found_bucket = bucket |
---|
226 | |
---|
227 | # add face to correct bucket or create new bucket |
---|
228 | if found_bucket: |
---|
229 | found_bucket.append(face) |
---|
230 | else: |
---|
231 | buckets.append([face]) |
---|
232 | result.append(buckets) |
---|
233 | |
---|
234 | return result |
---|
235 | |
---|
236 | |
---|
237 | def make_vert_normals(self, full_test): |
---|
238 | |
---|
239 | print "smoothing..." |
---|
240 | if full_test: |
---|
241 | self.faces_same_smoothing = self.faces_same_smoothing_full |
---|
242 | else: |
---|
243 | self.faces_same_smoothing = self.faces_same_smoothing_simple |
---|
244 | |
---|
245 | # find faces which are compatible with current |
---|
246 | all_buckets = self.partition_verts(self.faces_same_smoothing) |
---|
247 | |
---|
248 | #pp = pprint.PrettyPrinter(indent=4,width=78) |
---|
249 | #pp.pprint(self.hard_edges) |
---|
250 | #pp.pprint(all_buckets) |
---|
251 | |
---|
252 | self.face_vert_normals = {} |
---|
253 | for vertex in range(len(self.verts)): |
---|
254 | buckets = all_buckets[vertex] |
---|
255 | |
---|
256 | for bucket in buckets: |
---|
257 | bucket_normals = map(lambda x: self.face_normals[x], bucket) |
---|
258 | try: |
---|
259 | normal = sum(bucket_normals).unit() |
---|
260 | for face in bucket: |
---|
261 | self.face_vert_normals[(face, vertex)] = normal |
---|
262 | except ValueError, x: |
---|
263 | print bucket_normals |
---|
264 | raise x |
---|
265 | |
---|
266 | def make_gl_vert(self, face, vertex): |
---|
267 | |
---|
268 | glvert = GLVertex() |
---|
269 | |
---|
270 | # these are mandatory |
---|
271 | glvert.pos = self.verts[vertex] |
---|
272 | glvert.normal = self.face_vert_normals[(face, vertex)] |
---|
273 | |
---|
274 | # check if there is color data |
---|
275 | if self.face_vert_colors.has_key((face, vertex)): |
---|
276 | data = self.face_vert_colors[(face, vertex)] |
---|
277 | uv = data.uv |
---|
278 | color = data.color |
---|
279 | else: |
---|
280 | uv, color = None, None |
---|
281 | #glvert.uv, glvert.color = uv, color |
---|
282 | # Sinbad: flip v texcoord for 0.13 |
---|
283 | if uv: |
---|
284 | newu, newv = uv |
---|
285 | newv = 1 - newv |
---|
286 | glvert.uv = newu, newv |
---|
287 | else: |
---|
288 | glvert.uv = uv |
---|
289 | glvert.color = color |
---|
290 | # End Sinbad |
---|
291 | glvert.material = self.face_materials[face] |
---|
292 | |
---|
293 | return glvert |
---|
294 | |
---|
295 | def flatten(self): |
---|
296 | "generate gl vertices" |
---|
297 | |
---|
298 | # create buckets for shareable vertices |
---|
299 | all_buckets = self.partition_verts(self.face_vert_shareable) |
---|
300 | |
---|
301 | # calculate number of total verts |
---|
302 | ntotal = sum(map(len, all_buckets)) |
---|
303 | |
---|
304 | # create duplicate vertices and vertex indices |
---|
305 | cur_vert_idx = 0 |
---|
306 | gl_indices = [] |
---|
307 | self.glverts = [] |
---|
308 | for vertex in range(len(self.verts)): |
---|
309 | nsubverts = len(all_buckets[vertex]) |
---|
310 | gl_indices.append(range(cur_vert_idx, cur_vert_idx + nsubverts)) |
---|
311 | for bucket in all_buckets[vertex]: |
---|
312 | face = bucket[0] |
---|
313 | self.glverts.append(self.make_gl_vert(face, vertex)) |
---|
314 | cur_vert_idx += nsubverts |
---|
315 | |
---|
316 | # create reindexed faces |
---|
317 | self.glfaces = [] |
---|
318 | for face in range(len(self.faces)): |
---|
319 | vertices = self.faces[face] |
---|
320 | glface = [] |
---|
321 | for vi in vertices: |
---|
322 | |
---|
323 | def sublistindexelem(seq, val): |
---|
324 | for i in range(len(seq)): |
---|
325 | if val in seq[i]: return i |
---|
326 | return None |
---|
327 | |
---|
328 | group = sublistindexelem(all_buckets[vi], face) |
---|
329 | glface.append(gl_indices[vi][group]) |
---|
330 | self.glfaces.append(glface) |
---|
331 | |
---|
332 | def triangulate(self): |
---|
333 | "triangulate polygons" |
---|
334 | |
---|
335 | print "tesselating..." |
---|
336 | |
---|
337 | self.gltris = [] |
---|
338 | self.tri_materials = [] |
---|
339 | for i in range(len(self.glfaces)): |
---|
340 | face = self.glfaces[i] |
---|
341 | mat = self.face_materials[i] |
---|
342 | if len(face) == 3: |
---|
343 | self.gltris.append(face) |
---|
344 | self.tri_materials.append(mat) |
---|
345 | else: |
---|
346 | verts = map(lambda vindex: Vector(self.glverts[vindex].pos), face) |
---|
347 | |
---|
348 | # triangulate using ear clipping method |
---|
349 | tris = pgon.triangulate(verts) |
---|
350 | |
---|
351 | for tri in tris: |
---|
352 | A, B, C = map(lambda pindex: face[pindex], tri) |
---|
353 | self.gltris.append([A, B, C]) |
---|
354 | self.tri_materials.append(mat) |
---|
355 | |
---|
356 | def submeshize(self): |
---|
357 | "create submeshes" |
---|
358 | |
---|
359 | print "creating submeshes..." |
---|
360 | |
---|
361 | temp = {} |
---|
362 | for t in self.tri_materials: |
---|
363 | temp[t] = 1 |
---|
364 | trimats = temp.keys() |
---|
365 | |
---|
366 | self.subs = [] |
---|
367 | for mat in trimats: |
---|
368 | submesh = SubMesh() |
---|
369 | submesh.material = mat |
---|
370 | submesh.mat_data = self.materials[mat] |
---|
371 | if self.shared_geometry: |
---|
372 | # use shared geometry |
---|
373 | for i in range(len(self.tri_materials)): |
---|
374 | if self.tri_materials[i] == mat: |
---|
375 | submesh.gltris.append(self.gltris[i]) |
---|
376 | else: |
---|
377 | verts = {} |
---|
378 | for i in range(len(self.tri_materials)): |
---|
379 | if self.tri_materials[i] == mat: |
---|
380 | for vert in self.gltris[i]: |
---|
381 | verts[vert] = 1 |
---|
382 | verts = verts.keys() |
---|
383 | verts.sort() |
---|
384 | for i in verts: |
---|
385 | submesh.glverts.append(self.glverts[i]) |
---|
386 | for i in range(len(self.tri_materials)): |
---|
387 | if self.tri_materials[i] == mat: |
---|
388 | tri = [] |
---|
389 | for vert in self.gltris[i]: |
---|
390 | tri.append(verts.index(vert)) |
---|
391 | submesh.gltris.append(tri) |
---|
392 | self.subs.append(submesh) |
---|
393 | |
---|
394 | def dump(self): |
---|
395 | "show data" |
---|
396 | |
---|
397 | print "Mesh '%s':" % self.name |
---|
398 | |
---|
399 | print "%d vertices:" % len(self.glverts) |
---|
400 | for vert in self.glverts: print " ", vert |
---|
401 | |
---|
402 | ntris = sum(map(lambda submesh: len(submesh.gltris), self.subs)) |
---|
403 | print "%d submeshes, %d tris total:" % (len(self.subs), ntris) |
---|
404 | for sub in self.subs: |
---|
405 | print " material %d (%s), %d tris" % (sub.material, sub.mat_data.name, len(sub.gltris)) |
---|
406 | for tri in sub.gltris: |
---|
407 | A, B, C = tri |
---|
408 | print " ", A, B, C |
---|
409 | |
---|
410 | def merge(self, other): |
---|
411 | "add all mesh data from another mesh to self" |
---|
412 | |
---|
413 | nv = len(self.verts) |
---|
414 | nf = len(self.faces) |
---|
415 | |
---|
416 | self.verts += other.verts |
---|
417 | self.faces += map(lambda face: map(lambda x: x + nv, face), other.faces) |
---|
418 | self.hard_edges += map(lambda (x, y): (x + nf, y + nf), other.hard_edges) |
---|
419 | self.face_materials += other.face_materials |
---|
420 | |
---|
421 | for fv in other.face_vert_colors.keys(): |
---|
422 | face, vert = fv |
---|
423 | value = other.face_vert_colors[fv] |
---|
424 | self.face_vert_colors[(face + nf, vert + nv)] = value |
---|
425 | |
---|
426 | for e in other.edges: |
---|
427 | f1, f2, v1, v2 = e |
---|
428 | self.edges.append((f1 + nf, f2 + nf, v1 + nv, v2 + nv)) |
---|
429 | |
---|
430 | def scale(self, value): |
---|
431 | for v in self.glverts: |
---|
432 | v.pos = Vector(v.pos) * value |
---|
433 | |
---|
434 | def find_material(self, mat_name): |
---|
435 | for m in range(len(self.materials)): |
---|
436 | if self.materials[m].name == mat_name: |
---|
437 | return m |
---|
438 | return None |
---|
439 | |
---|
440 | |
---|