1#!BPY 2# Version 9: Modified "top", so upper edges means all on the upper side of the 3# mesh, not any edges facing up. Took as long as all the rest of this 4# script. 5# 6# Friction is now sum of R, G, B components of material color. 7# Version 8: added the "top" property, and an error message 8# Version 7: "foreground" meshes and objects now possible 9# friction can now be set for materials (using color components) 10# Version 6: merge object types, round numbers to integer 11# Version 5: objects 12# Version 4: read edge_height property 13# Version 3: process geometry into clockwise triangles 14# Version 2: materials 15# Version 1: only mesh geometry 16 17""" 18Name: 'Allegro Demo Game Level (.txt)...' 19Blender: 249 20Group: 'Export' 21Tooltip: 'Export Allegro Demo Game Level (.txt)' 22""" 23 24__bpydoc__ = """\ 25All mesh objects are exported as triangles. Coordinates are scaled by a factor 26of 10, so something at Blender coordinate x=10 will be x=100 in the game. And the 27y axis is flipped, since in the game, positive y is downwards. 28 29Every mesh must have a material assigned for each of its faces. You can do this 30by just giving a material to the mesh object, or also by applying per-face 31materials. 32 33A material must have texture slot 0 and 1 filled in and set to Image textures, 34where the first texture's image is the base texture, and the second the edge 35texture. 36The sum of the R,G,B components of the material color is used as friction. 37All other material settings are ignored. 38 39Mesh object understand the following properties: 40 41"edge_height" - The edge height for all faces in the mesh. 42"foreground" - All edges are marked foreground. 43"background" - No edge is marked collidable. 44"top" - Only upper edges of the mesh are marked as edges. Selected are all those 45edges part of the outer contour of the mesh, which are lying between the leftmost 46and rightmost vertex of that contour, in clockwise order. 47 48Objects are inserted as Blender "Empties". Each object has the following 49properties: 50 51"bitmap" - Name of the bitmap to use. 52"sound" - Name of the sound to play. 53"bonus" - If 1, this object does not count towards the number of needed fruits. 54 (So if a level has 30 fruits, and 10 are marked "bonus", only 20 need 55 to be collected. It doesn't matter at all which out of the 30 though.) 56"foreground" - This is a foreground object. 57"deco" - This object is not collidable. 58 59There are some reserved names. Empties whose name starts with "player" or 60"Player" mark the player starting position. Empties whose name starts with 61"exit" or "Exit" mark the level exit position. 62 63All other objects (cameras, lamps, ...) are ignored. 64 65Currently, there is no sanity checking done, so be sure to: 66- Assign materials to meshes/faces. 67- Make only 2D meshes. Blender will happily twist your meshes around, but the 68 exported level simply will crash. Only a planar mesh is allowed. 69- Don't forget the Empties for the player and exit positions, and set at least 70 one other collectible. 71""" 72 73import Blender, meshtools, sys, time 74 75# scaling factor when exporting vertex coordinates from blender 76SCALE_X = 10 77SCALE_Y = -10 78 79COMMENTS = False # Set to True to enable additional comments in the output 80 81# can be modified on a per-mesh basis via the "edge_height" property 82DEFAULT_EDGE_HEIGHT = 2 83 84message_string = "?" 85 86def message_draw(): 87 # clearing screen 88 Blender.BGL.glClearColor(0.5, 0.5, 0.5, 1) 89 Blender.BGL.glColor3f(1.,1.,1.) 90 Blender.BGL.glClear(Blender.BGL.GL_COLOR_BUFFER_BIT) 91 92 # Buttons 93 Blender.Draw.Button("Oh oh", 1, 10, 40, 100, 25) 94 95 #Text 96 Blender.BGL.glColor3f(1, 1, 1) 97 Blender.BGL.glRasterPos2d(10, 310) 98 Blender.Draw.Text(message_string) 99 100def message_event(evt, val): 101 if evt == Blender.Draw.ESCKEY and not val: Blender.Draw.Exit() 102 103def message_bevent(evt): 104 105 if evt == 1: Blender.Draw.Exit() 106 107class MessageException(Exception): pass 108 109def message(str): 110 global message_string 111 message_string = str 112 Blender.Draw.Register(message_draw, message_event, message_bevent) 113 raise MessageException 114 115def transform(matrix, vector): 116 """ 117 Given a matrix and a vector, return the result of transforming the vector by 118 the matrix. 119 matrix = [[0, 0, 0, 0], [0, 0, 0, 0]] 120 vector = [0, 0, 0, 0] 121 return = [0, 0] 122 """ 123 vout = [] 124 for j in range(2): 125 x = 0 126 for i in range(4): 127 x += matrix[i][j] * vector[i] 128 vout += [x] 129 return vout 130 131 132def direction(tri): 133 """ 134 Given a triangle, determine if it is counterclockwise or not. 135 tri = [i, i, i], 136 where i is a global vertex index. 137 """ 138 v1 = (V[tri[1]][0] - V[tri[0]][0], V[tri[1]][1] - V[tri[0]][1]) 139 v2 = (V[tri[2]][0] - V[tri[1]][0], V[tri[2]][1] - V[tri[1]][1]) 140 v = v1[0] * v2[1] - v1[1] * v2[0] 141 return v 142 143def upper(i1, i2): 144 """ 145 Given two vertex ids, determine if the edge is an upper face. 146 """ 147 v = V[i2][0] - V[i1][0] 148 return v 149 150def get_prop(ob, name, val): 151 try: 152 prop = ob.getProperty(name) 153 v = prop.getData() 154 except (AttributeError, RuntimeError): 155 v = val 156 return v 157 158def make_contour(ob, base_i): 159 """ 160 ob # the Blender mesh object 161 162 Returns a pair (edges, upper_contour). 163 edges = {v: n} # maps vertex to sucessor 164 upper_contour = {v: 1} # set of vertices in the upper mesh outline 165 """ 166 mesh = ob.getData() 167 # count how often each edge is present in the mesh 168 boundaries = {} 169 for f in mesh.faces: 170 prev = f.v[-1] 171 for v in f.v: 172 vs = [prev.index, v.index] 173 vs.sort() 174 vs = tuple(vs) 175 if vs in boundaries: 176 boundaries[vs] += 1 177 else: 178 boundaries[vs] = 1 179 prev = v 180 181 # map each vertex to its connected vertex 182 topmost = None 183 edges = {} 184 for f in mesh.faces: 185 prev = f.v[-1] 186 for v in f.v: 187 vs = [prev.index, v.index] 188 vs.sort() 189 vs = tuple(vs) 190 if vs in boundaries and boundaries[vs] == 1: 191 if not base_i + prev.index in edges: edges[base_i + prev.index] = {} 192 if not base_i + v.index in edges: edges[base_i + v.index] = {} 193 edges[base_i + prev.index][base_i + v.index] = 1 194 edges[base_i + v.index][base_i + prev.index] = 1 195 if not topmost or V[base_i + prev.index][1] > V[topmost][1]: 196 topmost = base_i + prev.index 197 prev = v 198 199 # Build the contour list. We start by picking the topmost vertex, then 200 # finding its contour neighbour so we are facing a clockwise direction. 201 cv = topmost # pick topmost contour vertex 202 next = list(edges[cv])[0] # next one 203 ok = False 204 # find the face this edge is part of 205 edge = (cv, next) 206 for f in mesh.faces: 207 for i in range(len(f.v)): 208 if (base_i + f.v[i - 1].index, base_i + f.v[i].index) == edge or\ 209 (base_i + f.v[i].index, base_i + f.v[i - 1].index) == edge: 210 j = base_i + f.v[(i + 1) % len(f.v)].index 211 if direction([cv, next, j]) < 0: 212 ok = True 213 break 214 if ok: break 215 if not ok: 216 next = list(edges[cv])[1] 217 218 full_contour = [cv] # a list of vertex indices, representing the mesh outline 219 prev = cv 220 while next != cv: 221 full_contour += [next] 222 v = list(edges[next])[0] 223 if v == prev: v = list(edges[next])[1] 224 prev = next 225 next = v 226 227 leftmost = None 228 rightmost = None 229 if COMMENTS: print "\t# contour:", 230 for vi in range(len(full_contour)): 231 v = full_contour[vi] 232 if COMMENTS: print "%d" % v, 233 if leftmost == None or V[v][0] < V[full_contour[leftmost]][0]: 234 leftmost = vi 235 if rightmost == None or V[v][0] > V[full_contour[rightmost]][0]: 236 rightmost = vi 237 if COMMENTS: print 238 239 if COMMENTS: print "\t# left: %d right: %d" % (full_contour[leftmost], 240 full_contour[rightmost]) 241 242 upper_contour = {} 243 for v in range(len(full_contour)): 244 if rightmost > leftmost: 245 if v >= leftmost and v < rightmost: 246 upper_contour[full_contour[v]] = 1 247 else: 248 if v >= leftmost or v < rightmost: 249 upper_contour[full_contour[v]] = 1 250 return (edges, upper_contour) 251 252def write(filename): 253 global V 254 file = open(filename, "wb") 255 stdout = sys.stdout 256 sys.stdout=file 257 258 print "# Exported from Blender %s" % Blender.Get("version") 259 print "# on %s (%s)" % (time.ctime(), time.tzname[0]) 260 print "# do not edit, edit %s instead" % Blender.Get("filename") 261 print 262 263 # Retrieve a list of all mesh objects in Blender. 264 meshobs = [ob for ob in Blender.Object.Get() if ob.getType() == "Mesh"] 265 266 # Export all current Blender materials. 267 mnum = 0 268 materials = {} 269 print "# Materials" 270 print "{" 271 for m in Blender.Material.Get(): 272 t = [] 273 for tex in m.getTextures()[:2]: 274 if tex and tex.tex.getImage(): 275 t += [tex.tex.getImage().name.split(".")[0]] 276 else: 277 t += [""] 278 print """\t{"%s", "%s", %f} # %d \"%s\"""" % (t[0], t[1], 279 m.R + m.G + m.B, mnum, m.getName()) 280 materials[m.name] = mnum 281 mnum += 1 282 print "}" 283 print 284 285 # Export all vertices of all mesh objects. 286 V = {} 287 print "# Vertices" 288 print "{" 289 i = 0 290 for ob in meshobs: 291 print "\t# Mesh \"%s\"" % ob.name 292 matrix = ob.matrix 293 mesh = ob.getData() 294 for v in mesh.verts: 295 pos = transform(matrix, [v.co.x, v.co.y, v.co.z, 1]) 296 V[i] = pos 297 print "\t{%d, %d} # %d" % (round(pos[0] * SCALE_X), 298 round(pos[1] * SCALE_Y), i) 299 i += 1 300 print "}" 301 print 302 303 # Export all faces of all mesh objects (split into triangles). 304 print "# Triangles" 305 print "{" 306 307 base_i = 0 308 for ob in meshobs: 309 print "\t# Mesh \"%s\"" % ob.name 310 311 mesh = ob.getData() 312 313 meshmaterials = mesh.getMaterials(1) 314 315 edge_height = get_prop(ob, "edge_height", DEFAULT_EDGE_HEIGHT) 316 foreground = get_prop(ob, "foreground", 0) 317 background = get_prop(ob, "background", 0) 318 top = get_prop(ob, "top", 0) 319 320 edges, upper_contour = make_contour(ob, base_i) 321 322 for f in mesh.faces: 323 # First triangle. 324 tri1 = [base_i + f.v[2].index, base_i + f.v[1].index, base_i + 325 f.v[0].index] 326 if direction(tri1) > 0: 327 tri1.reverse() 328 tris = [tri1] 329 330 # If the face has 4 vertices, add another triangle. 331 if len(f.v) > 3: 332 tri2 = [tri1[2], base_i + f.v[3].index, tri1[0]] 333 if direction(tri2) > 0: 334 tri2.reverse() 335 tris += [tri2] 336 337 for tri in tris: 338 # Handle one triangle. 339 print "\t{" 340 for i in range(3): 341 v = tri[i] 342 if i == 2: 343 next = tri[0] 344 else: 345 next = tri[i + 1] 346 flags = "" 347 edge_ok = False 348 if v in edges and next in edges[v]: edge_ok = True 349 if top and not v in upper_contour: edge_ok = False 350 if edge_ok: 351 flags += " \"edge\"" 352 if background == 0 and foreground == 0: 353 flags += " \"collidable\"" 354 if foreground != 0: 355 flags += " \"foreground\"" 356 if flags != "": 357 flags = ", " + flags 358 print "\t\t{%d, %d%s}," % (v, edge_height, flags) 359 360 try: 361 print "\t\t%d" % materials[meshmaterials[f.mat].name] 362 except IndexError: 363 message("You forgot to assign a material to mesh %s (or \ 364one of its faces)" % ob.name) 365 print "\t}" 366 base_i += len(mesh.verts) 367 print "}" 368 print 369 370 # Retrieve a list of all empty objects in Blender. 371 gameobs = [ob for ob in Blender.Object.Get() if ob.getType() == "Empty"] 372 373 obtypes = {} 374 n = 0 375 376 print "# Object types" 377 print "{" 378 for ob in gameobs: 379 if ob.name.lower().startswith("player"): 380 continue 381 if ob.name.lower().startswith("exit"): 382 continue 383 bitmap = get_prop(ob, "bitmap", "") 384 sound = get_prop(ob, "sound", "") 385 386 if not (bitmap, sound) in obtypes: 387 print "\t{ \"%s\", \"%s\" } # %d" % (bitmap, sound, n) 388 obtypes[(bitmap, sound)] = n 389 n += 1 390 print "}" 391 print 392 393 collectibles_count = 0 394 print "# Objects" 395 print "{" 396 for ob in gameobs: 397 if ob.name.lower().startswith("player"): 398 continue 399 x, y, z = ob.getLocation() 400 anglex, angley, anglez = ob.getEuler() 401 deco = get_prop(ob, "deco", 0) 402 if ob.name.lower().startswith("exit"): 403 type = -1 404 else: 405 bitmap = get_prop(ob, "bitmap", "") 406 sound = get_prop(ob, "sound", "") 407 type = obtypes[(bitmap, sound)] 408 bonus = get_prop(ob, "bonus", 0) 409 if bonus == 0 and deco == 0: collectibles_count += 1 410 foreground = get_prop(ob, "foreground", 0) 411 flags = "" 412 if deco == 0: flags += "\"collidable\"" 413 if foreground != 0: flags += "\"foreground\"" 414 if flags != "": 415 flags = ", " + flags 416 417 print "\t{ %d, %d, %d, %d%s } # \"%s\"" % (round(x * SCALE_X), round(y * SCALE_Y), 418 round(anglez), type, flags, ob.name) 419 print "}" 420 print 421 422 print "# Level" 423 print "{" 424 for ob in gameobs: 425 if ob.name.lower().startswith("player"): 426 x, y, z = ob.getLocation() 427 print "\t%d, %d, %d" % (round(x * SCALE_X), round(y * SCALE_Y), 428 collectibles_count) 429 break 430 print "}" 431 432 sys.stdout = stdout 433 434def fs_callback(filename): 435 # Mesh access is better done outside edit mode I believe. 436 editmode = Blender.Window.EditMode() 437 if editmode: Blender.Window.EditMode(0) 438 try: 439 write(filename) 440 except MessageException: 441 pass 442 if editmode: Blender.Window.EditMode(1) 443 444Blender.Window.FileSelector(fs_callback, "Export Allegro Demo Game Level", 445 "mylevel.txt") 446