Advertisement
Kitomas

fsm converter v1

Apr 26th, 2024
958
1
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 12.60 KB | None | 1 0
  1. if __name__ != "__main__": exit()
  2. from tkinter import Tk, messagebox as msg
  3. from tkinter.simpledialog import askinteger, askstring
  4. from tkinter.filedialog import askopenfilename, asksaveasfilename
  5. Tk().withdraw()
  6. ''' (method examples so i don't need to look at tkinter documentation again)
  7. askinteger()
  8. askstring()
  9. fileNameI = askopenfilename(title="Choose a .obj to convert to .ttm",initialdir=".")
  10. fileNameO = asksaveasfilename(title="Save As",initialdir=".")
  11. msg.showinfo(title="cool info",message="??")
  12. msg.showerror(title="cool error",message="??")
  13. msg.askyesno(title="?", message="??")
  14. '''
  15. from struct import pack
  16. from math import ceil,log
  17. from os.path import exists
  18. from pprint import pprint
  19. import numpy as np
  20.  
  21.  
  22. def listEval(listI): return [eval(i) for i in listI]
  23. def loadmtl(fileName):
  24.     if not exists(fileName):
  25.         msg.showerror(
  26.           title=".mtl file is missing!",
  27.           message=("Library \"{}\" could not be found!").format(fileName)
  28.         )
  29.         return None
  30.     try:
  31.         mtlFile=open(fileName,"r")
  32.         mtlLines=mtlFile.read().split("\n")
  33.         mtlFile.close()
  34.     except Exception as err:
  35.         msg.showerror(
  36.           title=("An error occurred while trying to open \"{}\"!").format(fileName),
  37.           message=str(err)+"\n(Is this even a .mtl file?)"
  38.         )
  39.          #print(err)
  40.         return None
  41.     try:
  42.         mtl={"_name_":fileName,"_reverseIndex_":[]}
  43.         for line in mtlLines:
  44.             lineSplit=line.split(" ")
  45.             lineCommand=lineSplit[0]; lineArgs=lineSplit[1:]
  46.             #this doesn't handle or account for texture map options like -bm
  47.             if lineCommand == "newmtl":
  48.                 mtlName=lineArgs[0]
  49.                 mtl[mtlName]={}; mtl["_reverseIndex_"].append(mtlName)
  50.             elif lineCommand == "Tr": #(Tr)ansparency; inverse of d (alpha)
  51.                 mtl[mtlName]["d"]=[1.0-float(lineArgs[0])]
  52.             elif lineCommand[0:1] in ["K","N"] or  lineCommand in ["d","Tf","illum"]:
  53.                 mtl[mtlName][lineCommand]=listEval(lineArgs)
  54.             elif lineCommand[0:4] == "map_"  or  lineCommand in ["bump","disp","decal"]:
  55.                 mtl[mtlName][lineCommand]=[lineArgs[0]]
  56.         return mtl
  57.     except Exception as err:
  58.         msg.showerror(
  59.           title="Encountered an error while parsing .mtl!",
  60.           message=("Error parsing \"{}\": \"{}\"\n (Is this even a .mtl?)").format(fileName,err)
  61.         )
  62.         return None
  63. def loadobj(fileName):
  64.     #(relative indexing for faces isn't supported, due to issues with signedness)
  65.     zeroIndexed=True #for face indexes (new_index=original_index-zeroIndexed)
  66.     allowNonTriangles=False
  67.     if not exists(fileName):
  68.         msg.showerror(
  69.           title=".obj file is missing!",
  70.           message=("\"{}\" could not be found!").format(fileName)
  71.         )
  72.         return None
  73.     try:
  74.         objFile=open(fileName,"r")
  75.         objLines=objFile.read().split("\n")
  76.         objFile.close()
  77.     except Exception as err:
  78.         msg.showerror(
  79.           title=("Encountered an error while trying to open \"{}\"!").format(fileName),
  80.           message=("Error: \"{}\"\n (Is this even a .obj?)").format(err)
  81.         )
  82.         return None
  83.     try:
  84.         obj={"_META_":{"_name_":fileName,"_reverseIndex_":[],"mtllib":[], "vMax":0,"vtMax":0,"vnMax":0},
  85.              "v":[],"vt":[],"vn":[]}
  86.         lineCount=1
  87.         for line in objLines:
  88.             lineSplit=line.split(" ")
  89.             lineCommand=lineSplit[0]; lineArgs=lineSplit[1:]
  90.             #group names not supported
  91.             if lineCommand == "o": #(o)bject
  92.                 objectName=lineArgs[0]
  93.                 obj[objectName]={}; obj["_META_"]["_reverseIndex_"].append(objectName)
  94.             elif lineCommand in ["v","vt","vn"]: #(v)ertex coord., (v)ertex (t)exture coord., (v)ertex (n)ormals
  95.                 #put all of the objects' verts in one list, as it seems like they're
  96.                 #treated as global values, which every face in every other object can use
  97.                 #if not obj.get(lineCommand): obj[lineCommand]=[] (redundant)
  98.                 obj[lineCommand].append(listEval(lineArgs))
  99.             elif lineCommand == "f": #(f)ace
  100.                 currentFace=[]
  101.                 for point in lineArgs:
  102.                     p=point.split("/")
  103.                     currentFace+=[[int(i or 0) for i in p]]
  104.                     currentFace[-1]=[i-zeroIndexed for i in currentFace[-1]]
  105.                     #1 at minimum so python won't throw a math domain error by trying log(0,2)
  106.                     obj["_META_"]["vMax" ]=max(1, obj["_META_"]["vMax" ], currentFace[-1][0] )
  107.                     obj["_META_"]["vtMax"]=max(1, obj["_META_"]["vtMax"], currentFace[-1][1] )
  108.                     obj["_META_"]["vnMax"]=max(1, obj["_META_"]["vnMax"], currentFace[-1][2] )
  109.                      #print(currentFace[-1])
  110.                 if not obj[objectName].get("f"): obj[objectName]["f"]=[]
  111.                 obj[objectName]["f"].append(currentFace)
  112.                 if len(currentFace) != 3 and not allowNonTriangles:
  113.                     msg.showerror(
  114.                       title="Non-triangle detected!",
  115.                       message=("A face with {} sides was found at line {}!").format(len(currentFace),lineCount)
  116.                     )
  117.                     return None
  118.             #elif lineCommand == "l": (line elements not supported)
  119.             elif lineCommand == "mtllib":
  120.                 #using a list here, so multiple material libraries can be used
  121.                 #(problems might occur if a path with spaces is used...)
  122.                 obj["_META_"]["mtllib"].append(lineArgs[0])
  123.             elif not lineCommand in ["#",""]:
  124.                 obj[objectName][lineCommand]=[lineArgs[0]]
  125.             lineCount+=1
  126.         obj["_META_"]["vBytes" ]=ceil(log(obj["_META_"]["vMax" ],2)/8)
  127.         obj["_META_"]["vtBytes"]=ceil(log(obj["_META_"]["vtMax"],2)/8)
  128.         obj["_META_"]["vnBytes"]=ceil(log(obj["_META_"]["vnMax"],2)/8)
  129.         return obj
  130.     except Exception as err:
  131.         msg.showerror(
  132.           title="Encountered an error while parsing .obj!",
  133.           message=("Error parsing \"{}\": \"{}\"\n (Is this even a .obj?)").format(fileName,err)
  134.         )
  135.         return None
  136.  
  137.  
  138. ''' some info about obj tags:
  139.       "v" = vertex data
  140.      "vt" = texture coordinates (uv coords)
  141.      "vn" = vertex normals
  142.      "Ka" = ambient color
  143.      "Kd" = diffuse color
  144.      "Ks" = specular color
  145.      "Ns" = specular exponent
  146.       "d" = alpha (opaqueness)
  147.     #"Tr" = transparency, inverse of d (alpha); not used
  148.      "Tf" = transmission filter color
  149.      "Ni" = optical density (or, index of refraction)
  150.   "illum" = illumination model
  151.  "map_Ka" = ambient texture map
  152.  "map_Kd" = diffuse texture map
  153.  "map_Ks" = specular color texture map
  154.  "map_Ns" = specular highlight component
  155.   "map_d" = alpha texture map
  156. "map_bump" = bump map
  157.    "bump" = same as map_bump
  158.    "disp" = displacement map
  159.   "decal" = stencil decal texture
  160.      "Ke" = emissive color (this is a format extension iirc)
  161.       "s" = smooth shading
  162. '''
  163.  
  164. '''
  165. fileNameI = askopenfilename(title="Choose a .obj to convert to .ttm",initialdir=".")
  166. if fileNameI=="": exit()
  167. fileNameO=savettm(loadobj(fileNameI)) #automatically converts linked .mtl(s)
  168. if fileNameO:
  169.    msg.showinfo(
  170.      title="Success!",
  171.      message=("Converted file successfully saved as \"{}\"").format(fileNameO)
  172.    )
  173. '''
  174.  
  175.  
  176. def float2fixed_single(floatValue): return int((floatValue*256)+0.5).to_bytes(4,"little",signed=True)
  177. def float2fixed_list(listI): return [float2fixed_single(i) for i in listI]
  178. def int2bytes_single(intValue,blen=4,s=False): return intValue.to_bytes(blen,"little",signed=s)
  179. def int2bytes_list(listI,blen=4,s=False): return [int2bytes_single(i,blen,s) for i in listI]
  180. def changeFileExtension(name,ext): return ".".join(name.split(".")[:-1]+[ext])
  181. #unused for now
  182. #def bin2byte(b,l=1,s=False): return int(b,2).to_bytes(l,"little",signed=s)
  183.  
  184.  
  185. def getFaceNormal(a, b, c):
  186.     #move points b&c to origin relative to a
  187.     u = np.subtract(b, a)
  188.     v = np.subtract(c, a)
  189.    
  190.     #get surface normal vector via cross product
  191.     n = (
  192.         u[1]*v[2] - u[2]*v[1],
  193.         u[2]*v[0] - u[0]*v[2],
  194.         u[0]*v[1] - u[1]*v[0],
  195.     )
  196.    
  197.     #magnitude of normal vector
  198.     w = (n[0]**2 + n[1]**2 + n[2]**2)**.5 #sqrt(a^2 + b^2 + c^2)
  199.    
  200.     #unit surface normal
  201.     t = np.divide(n,w)
  202.    
  203.     return list(t)
  204.    
  205.    
  206. def getMidpoint(a, b, c):
  207.     return [(a[0]+b[0]+c[0])/3, (a[1]+b[1]+c[1])/3, (a[1]+b[1]+c[1])/3]
  208.  
  209.  
  210.  
  211.  
  212. '''
  213. struct fx_vec3 {
  214.  fx16_8 x,y,z;
  215. };
  216.  
  217. struct Triangle {
  218.  fx_vec3 norm;
  219.  fx_vec3  mid;
  220.  u32    a,b,c;
  221.  Color  color;
  222. };
  223.  
  224. struct HeaderFSM {
  225.  u32      magic; // = "FSM\x00" (0x004D5346)
  226.  fx16_8   scale; //a multiplier for the model's default scale
  227.  u32  verts_len; //number of total vertices
  228.  u32   tris_len; //number of total triangles
  229.  fx_vec3* verts; //are offsets when stored in fsm files; must be
  230.  Triangle* tris;  //+= with the struct's address to use as a pointer
  231. };
  232. '''
  233.  
  234. def createTriangle(faceData, vertices):
  235.     #face index data goes as: vertex, texture coordinate (UV), normal vector
  236.      #the only one we're after is the vertex data, as neither uv coords or vertex
  237.      #normals are used. however, a FACE normal is calculated using the vertex data
  238.     vertIndexes = [ faceData[0][0], faceData[1][0], faceData[2][0] ]
  239.     vertex_a = vertices[vertIndexes[0]]
  240.     vertex_b = vertices[vertIndexes[1]]
  241.     vertex_c = vertices[vertIndexes[2]]
  242.  
  243.     faceNorm = getFaceNormal(vertex_a, vertex_b, vertex_c)
  244.     midpoint = getMidpoint(vertex_a, vertex_b, vertex_c)
  245.    
  246.     faceNormBytes  = float2fixed_list(faceNorm)
  247.     midpointBytes  = float2fixed_list(midpoint)
  248.     vertIndexBytes = int2bytes_list(vertIndexes)
  249.     colorBytes     = [ b'\xCC\xCC\xCC\xCC' ] #tbd: i'll implement this later
  250.     print(faceNormBytes + midpointBytes + vertIndexBytes + colorBytes)
  251.     return faceNormBytes + midpointBytes + vertIndexBytes + colorBytes
  252.  
  253.  
  254.  
  255. def getFsmData(objFileData, scalingFactor = 1.0):
  256.     metaInfo = objFileData["_META_"] #information from the obj that is inferred rather than read
  257.     vBytes   = metaInfo["vBytes" ]   #the minimum number of bytes needed to store vertice indexes
  258.     vMax     = metaInfo["vMax"   ]   #the highest vertice index (its length - 1)
  259.     name     = metaInfo["_reverseIndex_"][0] #get first object in the file data
  260.    
  261.     vertsRaw  = objFileData["v"]       #list of float triplets (which is also a list)
  262.     facesRaw  = objFileData[name]["f"] #list of lists of integer triplets (which is also a list)
  263.     vertsSize = len(vertsRaw) * 4 * 3  #equal to the number of vertices, * sizeof(fx16_8) * 3 axes
  264.    
  265.     #pprint(objFileData) #debug thingy
  266.  
  267.     if vBytes > 4: #if you somehow go higher than this, you're doing something terribly wrong
  268.         msg.showerror(
  269.           title = "Encountered an error while converting obj data to fsm!",
  270.           message = ("Error converting \"{}\": \"{}\"\n").format(fileName, "vBytes > 4")
  271.         ); return None
  272.    
  273.    
  274.     magic       = [ b'FSM\x00'                        ] #magic number (file signature)
  275.     scale       = [ float2fixed_single(scalingFactor) ] #global scale multiplier
  276.     verts_len   = [ int2bytes_single(len(vertsRaw))   ] #number of vertices
  277.     tris_len    = [ int2bytes_single(len(facesRaw))   ] #number of triangles
  278.     verts       = [ int2bytes_single(32, 8)           ] #offset to the start of vertex data
  279.     tris        = [ int2bytes_single(32+vertsSize, 8) ] #offset to the start of triangle data
  280.     verts_bytes = []
  281.     tris_bytes  = []
  282.     for vert in vertsRaw: verts_bytes += float2fixed_list(vert)
  283.     for face in facesRaw: tris_bytes  += createTriangle(face, vertsRaw)
  284.  
  285.     #next destination: concatenation station (LMAOOO)
  286.     return magic + scale + verts_len + tris_len + verts + tris+ verts_bytes + tris_bytes
  287.    
  288.    
  289.  
  290.  
  291. fileNameI = askopenfilename(title="Choose a .obj to convert to .fsm", initialdir=".")
  292. if fileNameI=="": exit()
  293. fileNameO = changeFileExtension(fileNameI,"fsm")
  294.  
  295. dataI = loadobj(fileNameI)
  296. if dataI == None: exit()
  297. dataO = getFsmData(dataI)
  298. if dataO == None: exit()
  299.  
  300. try:
  301.     with open(fileNameO, "wb") as fileO:
  302.         for chunk in dataO:
  303.             fileO.write(chunk)
  304.    
  305.     msg.showinfo(
  306.       title="Success!",
  307.       message=("Converted file successfully saved as \"{}\"").format(fileNameO)
  308.     )
  309.    
  310. except Exception as err:
  311.     msg.showerror(
  312.       title = "Encountered an error while writing fsm data to file!",
  313.       message = ("Error writing to \"{}\": \"{}\"\n").format(fileNameO, str(err))
  314.     )
Advertisement
Comments
  • FastJava
    210 days
    # Bash 0.10 KB | 0 0
    1. thanks for uploading the code, this saves us some time, not every time does gpt give good code advices..
Add Comment
Please, Sign In to add comment
Advertisement