## MTD v3 ## import bpy import os import random VERSION = b'\x03\x00' # script switches RANDOM_COLORS = 0 # MTD bitflags USE_STP = 1<<0 USE_WIREFRAME = 1<<1 # ignores USE_TEXTURE bit if used USE_TEXTURE = 1<<2 USE_GOURAUD = 1<<3 FLAG = USE_TEXTURE # set MTD bitflags here # 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') # 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 ( (FLAG & USE_WIREFRAME) ^ USE_WIREFRAME ): # FIXME: find out how to use the image used by the active UV map! image_width = (bpy.data.images['desert.png'].size[0]) image_height = (bpy.data.images['desert.png'].size[1]) # 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 # 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(k for k in uv_all)) # write the (optional) UV section. # no need to write the UV section if we're using wireframe! if (FLAG & USE_TEXTURE) and ( (FLAG & USE_WIREFRAME) ^ USE_WIREFRAME ): # write bnum bnum = len(uv_truncated)*2 + 4 f.write(bnum.to_bytes(4, byteorder='little', signed=False)) # write UVs for i in range(len(uv_truncated)): if uv_truncated[i][0] < 0: uv_truncated[i][0] = 0 elif uv_truncated[i][0] > 255: uv_truncated[i][0] = 255 if uv_truncated[i][1] < 0: uv_truncated[i][1] = 0 elif uv_truncated[i][1] > 255: uv_truncated[i][1] = 255 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 # 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: for i, face in enumerate(mesh.tessfaces): 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(RANDOM_COLORS): f.write((random.randint(0, 255)).to_bytes(1, byteorder='little', signed=False)) f.write((random.randint(0, 255)).to_bytes(1, byteorder='little', signed=False)) f.write((random.randint(0, 255)).to_bytes(1, byteorder='little', signed=False)) 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)) 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 UVs if the model is textured 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. if uv_polygon[i][j] == uv_truncated[k]: f.write(k.to_bytes(1, byteorder='little', signed=False)) # write the uv index for this vertex! break # write unused 4th uv index if vert == 3: f.write(b'\x00') # write the NORMAL section if FLAG & USE_GOURAUD: for v in mesh.vertices: x = int(round(v.normal[0]*4096, 0)) y = int(round(v.normal[2]*4096, 0)) z = int(round(v.normal[1]*4096, 0)) f.write(x.to_bytes(4, byteorder='little', signed=True)) f.write(y.to_bytes(4, byteorder='little', signed=True)) f.write(z.to_bytes(4, byteorder='little', signed=True)) print(x) print(y) print(z)