[657] | 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 | |
---|