## MTD v5 ## import bpy import os import random VERSION = b'\x05\x00' # script switches RANDOM_COLORS = 0 BAKED_LIGHTING = 0 # must have USE_PNTLITE disabled # MTD bitflags USE_STP = 1<<0 USE_WIREFRAME = 1<<1 # ignores USE_TEXTURE bit if used USE_TEXTURE = 1<<2 USE_LIGHTING = 1<<3 USE_GOURAUD = 1<<4 USE_PNTLITE = 1<<5 USE_FILECULL = 1<<6 USE_BACKFACE_CULL = 1<<7 FLAG = USE_FILECULL|USE_PNTLITE|USE_LIGHTING|USE_GOURAUD|USE_TEXTURE # NOTE: any references to a mesh's data (e.g. UVs) # cannot be accessed in edit mode, so we must switch # to object mode. More info here: # https://docs.blender.org/api/blender_python_api_2_78_release/info_gotcha.html bpy.ops.object.mode_set(mode='OBJECT') # get object context obj = bpy.context.active_object mesh = obj.to_mesh(bpy.context.scene, False, 'PREVIEW') def clamp(n, minval, maxval): n = max(min(n, maxval), minval) return n def intizize(n, factor): n = int(n*factor) return n # FIXME: use relative file path name with open('C:\\Documents and Settings\\sveeion\\Desktop\\a.mtd', 'wb') as f: # header section ID = b'\x00\x00\x00\x00' nvert = (len(mesh.vertices)).to_bytes(2, byteorder='little', signed=False) if(FLAG & USE_WIREFRAME): npoly = (len(mesh.edges)).to_bytes(2, byteorder='little', signed=False) else: npoly = (len(mesh.polygons)).to_bytes(2, byteorder='little', signed=False) # write the ID block f.write(ID) # write the version and flag f.write(VERSION) f.write(FLAG.to_bytes(2, byteorder='little', signed=False)) # write the nvert and npoly block f.write(nvert + npoly) # write the VERTEX section for v in mesh.vertices: for axis in range(3): # FIXME: not the best way to do this, but whatever if axis == 0: num = -int( round(v.co[0] * 4096, 0) ) elif axis == 1: # z in blender is y on psx num = -int( round(v.co[2] * 4096, 0) ) elif axis == 2: # y in blender in z on psx num = -int( round(v.co[1] * 4096, 0) ) # fixed-point rounding if num > 0: r = num%4096 if r >= 2048: # round to greater positive magnitude num += 4096 - r else: num -= r elif num < 0: r = num%-4096 if r <= -2048: # round to greater negative magnitude num += -4096 - r else: num -= r f.write( num.to_bytes(4, byteorder='little', signed=True) ) # uv_all lists ALL uv values from all polygons. UV coords are guaranteed # to repeat on different polygons on complex models uv_all = [] # each element of uv_polygon is a tuple storing that polygon's UV coords. uv_polygon = [] # uv_truncated does NOT correspond to the order of the polygons # but instead keeps a non-repeating list of the UVs. This list is what # will get written to the UV section if USE_TEXTURE == 1. Both uv and # uv_truncated will be used later when writing the POLYGON section. uv_truncated = [] if (FLAG & USE_TEXTURE) and not (FLAG & USE_WIREFRAME): # FIXME: find out how to use the image used by the active UV map! image_width = (bpy.data.images['mtn.png'].size[0]) image_height = (bpy.data.images['mtn.png'].size[1]) if image_width == 0 or image_height == 0: print('image dimensions: %dx%d\n' % (image_width, image_height)) raise Exception('Invalid image dimensions!') # record UV section for i, polygon in enumerate(obj.data.polygons): reset = [] for j, loopindex in enumerate(polygon.loop_indices): meshuvloop = obj.data.uv_layers.active.data[loopindex] # the UV coords are normalized by default, so convert # them back to pixel coords. u = int(round(meshuvloop.uv[0] * image_width, 0)) v = int(round(meshuvloop.uv[1] * image_height, 0)) # flip the v coord v = image_height - v u = clamp(u, 0, 255) v = clamp(v, 0, 255) # store the UV contents into a tuple and add to list tuple = (u, v) reset.append(tuple) uv_all.append(tuple) uv_polygon.append(reset) # remove duplicate UVs uv_truncated = list(set(uv_all)) # write the (optional) UV section. # no need to write the UV section if we're using wireframe! if (FLAG & USE_TEXTURE) and not (FLAG & USE_WIREFRAME): # write bnum # FIXME: bnum does not account for pad bnum = len(uv_truncated)*2 + 4 f.write(bnum.to_bytes(4, byteorder='little', signed=False)) print('UV bnum: %d\n' % (bnum)) # write UVs for i in range(len(uv_truncated)): f.write(uv_truncated[i][0].to_bytes(1, byteorder='little', signed=False)) # u f.write(uv_truncated[i][1].to_bytes(1, byteorder='little', signed=False)) # v if (len(uv_truncated)*2)%4: f.write(b'\x00\x00') # pad norm_all = [] norm_truncated = [] # record NORMAL section (for both GOURAUD and FLAT shading) if FLAG & USE_LIGHTING: if FLAG & USE_GOURAUD: for i, vert in enumerate(mesh.vertices): #x = intizize(vert.normal[0],4096) #y = intizize(vert.normal[2],4096) #z = intizize(vert.normal[1],4096) x = intizize(vert.normal[0],4096) y = intizize(vert.normal[2],4096) z = intizize(vert.normal[1],4096) tuple = (x, y, z) norm_all.append(tuple) else: for i, face in enumerate(mesh.tessfaces): x = intizize(face.normal[0],4096) y = intizize(face.normal[2],4096) z = intizize(face.normal[1],4096) tuple = (x, y, z) norm_all.append(tuple) norm_truncated = list(set(norm_all)) # write the (optional) NORMAL section bnum = len(norm_truncated)*12 + 4 f.write(bnum.to_bytes(4, byteorder='little', signed=False)) for i in range(len(norm_truncated)): x = norm_truncated[i][0].to_bytes(4, byteorder='little', signed=True) y = norm_truncated[i][1].to_bytes(4, byteorder='little', signed=True) z = norm_truncated[i][2].to_bytes(4, byteorder='little', signed=True) f.write(x+y+z) # record RGB section rgb_all= [] rgb_truncated = [] if FLAG & USE_GOURAUD: if BAKED_LIGHTING: for i, polygon in enumerate(mesh.vertex_colors[0].data): reset = [] r = int(polygon.color[0]*255) g = int(polygon.color[1]*255) b = int(polygon.color[2]*255) tuple = (r, g, b) rgb_all.append(tuple) rgb_truncated = list(set(rgb_all)) else: tuple = (128, 128, 128) rgb_all.append(tuple) rgb_truncated = list(set(rgb_all)) # write the RGB section bnum = len(rgb_truncated)*4 + 4 print('rgb bnum: %d' % (bnum)) f.write(bnum.to_bytes(4, byteorder='little', signed=False)) for i in range(len(rgb_truncated)): r = rgb_truncated[i][0].to_bytes(1, byteorder='little', signed=False) g = rgb_truncated[i][1].to_bytes(1, byteorder='little', signed=False) b = rgb_truncated[i][2].to_bytes(1, byteorder='little', signed=False) f.write(r+g+b+ b'\x00') # pad # write the POLYGON section if FLAG & USE_WIREFRAME: for i, edge in enumerate(mesh.edges): f.write(b'\x00') # 'type' is ignored in wireframe mode f.write(b'\x80\x80\x80') # set RGBs to 128 f.write(edge.vertices[0].to_bytes(2, byteorder='little', signed=False)) f.write(edge.vertices[1].to_bytes(2, byteorder='little', signed=False)) else: rgb_all_idx = 0 norm_all_idx = 0 for i, poly in enumerate(mesh.polygons): face = mesh.tessfaces[i] r = 0 g = 0 b = 0 vert = len(face.vertices) if vert == 3: p_type = b'\x00' elif vert == 4: p_type = b'\x01' else: p_type = b'\x255' # write polygon's type f.write(p_type) # write RGB if not FLAG & USE_GOURAUD: if(RANDOM_COLORS): r = random.randint(0, 255) g = random.randint(0, 255) b = random.randint(0, 255) else: r = obj.material_slots[face.material_index].material.node_tree.nodes['Diffuse BSDF'].inputs[0].default_value[0] * 255 g = obj.material_slots[face.material_index].material.node_tree.nodes['Diffuse BSDF'].inputs[0].default_value[1] * 255 b = obj.material_slots[face.material_index].material.node_tree.nodes['Diffuse BSDF'].inputs[0].default_value[2] * 255 r = int(round(r, 0)) g = int(round(g, 0)) b = int(round(b, 0)) f.write(r.to_bytes(1, byteorder='little', signed=False)) f.write(g.to_bytes(1, byteorder='little', signed=False)) f.write(b.to_bytes(1, byteorder='little', signed=False)) else: f.write(b'\x00\x00\x00') # pad for j in range(vert): f.write( (face.vertices[j]).to_bytes(2, byteorder='little', signed=False) ) # write unused 4th vertex index if vert == 3: f.write(b'\x00\x00') # write UV indices if needed if FLAG & USE_TEXTURE: # each vertex has a UV coord for j in range(vert): for k in range(len(uv_truncated)): # UVs can repeat, so ensure they don't # by only writing an index to the UV section # (uv_truncated) which has no repeating UVs. # uv_truncated is written after the POLYGON # section. # 'i' represents the polygon index, 'k' represents # an index into the non-repeating UV section. # this idea is repeated with the normal and rgb indices. if uv_polygon[i][j] == uv_truncated[k]: f.write(k.to_bytes(2, byteorder='little', signed=False)) # write the uv index for this vertex! break # write unused 4th uv index if vert == 3: f.write(b'\x00\x00') # write normal indices if needed if FLAG & USE_LIGHTING: if FLAG & USE_GOURAUD: for j in range(vert): for k in range(len(norm_all)): if poly.vertices[j] == k: for w in range(len(norm_truncated)): if norm_all[k] == norm_truncated[w]: f.write(w.to_bytes(2, byteorder='little', signed=False)) # write unused 4th normal index if vert == 3: f.write(b'\x00\x00') else: skip = 0 for j in range(len(norm_all)): if face.index == j: for k in range(len(norm_truncated)): if norm_all[j] == norm_truncated[k]: skip = 1 f.write(k.to_bytes(2, byteorder='little', signed=False)) f.write(b'\x00\x00') f.write(b'\x00\x00') f.write(b'\x00\x00') break if skip: break # write rgb indices if needed if FLAG & USE_GOURAUD: for j in range(vert): for k in range(len(rgb_truncated)): if FLAG & USE_LIGHTING: f.write(b'\x00\x00') break else: if rgb_all[rgb_all_idx+j] == rgb_truncated[k]: f.write(k.to_bytes(2, byteorder='little', signed=False)) break # write unused 4th rgb index if vert == 3: f.write(b'\x00\x00') rgb_all_idx += vert