| 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 | |
|---|