diff --git a/pot3d.py b/pot3d.py new file mode 100644 index 0000000..3fa000b --- /dev/null +++ b/pot3d.py @@ -0,0 +1,139 @@ +# hi this is super hacky and only converts a tiny segment of display lists. +# it's really just to get a feel for how the formats work. +# after extracting/decompressing OoT, run this script on object_tsubo. +# the top of the pot is missing, i know. see the comment above. + +import array +import io +import struct +import sys + +name = "pot" +tw, th = 32, 64 # hardcoded texture width and height +viscale = 256 # inverted scale, this is arbitrary +v_base = 0x1838 # offset to first G_VTX command + +with open(sys.argv[1], "rb") as f: + data = f.read() + +f = io.BytesIO(data) + +# hardcoded for a rga5a1 texture at the start of the file: +pix = array.array('H', f.read(tw * th * 2)) +pix.byteswap() +rgbs = [] +for p in pix: + r = (p & 0xF800) >> 11 + g = (p & 0x07C0) >> 6 + b = (p & 0x003E) >> 1 + a = p & 1 + # TODO: round color calculations. or not? i don't imagine the N64 bothers. + rgb = (r * 255 // 31, g * 255 // 31, b * 255 // 31) + rgbs.append(rgb) + +verts = [] +texes = [] +norms = [] # unimplemented +polys = [] + +vi = 0 # vertex index to offset by (incremented after each chunk) + +f.seek(v_base) +opcode = ord(f.read(1)) + +while opcode == 0x01: # G_VTX + counts = f.read(3) + numv = ((counts[0] & 0xF) << 4) | ((counts[1] & 0xF0) >> 4) + vbidx = counts[2] // 2 - numv + vaddr = struct.unpack(">i", f.read(4))[0] + + back = f.tell() + f.seek(vaddr & 0xFFFFFF) + + for i in range(numv): + if 0: + # colored vertices + vertex = struct.unpack(">hhhHhhBBBB", f.read(16)) + x, y, z, w, tx, ty, r, g, b, a = vertex + else: + # lit vertices + vertex = struct.unpack(">hhhHhhbbbB", f.read(16)) + x, y, z, w, tx, ty, n, p, q, a = vertex + + pos = (x / viscale, y / viscale, z / viscale) + # FIXME: texture coordinates are slightly off + tpos = ((tx / 32 / tw), 1 - (ty / 32 / th)) + verts.append(pos) + texes.append(tpos) + + f.seek(back) + + while 1: + opcode = ord(f.read(1)) + if opcode not in (6, 5): + break + if opcode == 5: + indices = struct.unpack('>bbbbbbb', f.read(7)) + a0, a1, a2, _, _, _, _ = indices + atri = a0 // 2 + 1 + vi, a1 // 2 + 1 + vi, a2 // 2 + 1 + vi + polys.append(atri) + elif opcode == 6: + indices = struct.unpack('>bbbbbbb', f.read(7)) + a0, a1, a2, _, b0, b1, b2 = indices + # TODO: assert all indices are in range(32) + atri = a0 // 2 + 1 + vi, a1 // 2 + 1 + vi, a2 // 2 + 1 + vi + btri = b0 // 2 + 1 + vi, b1 // 2 + 1 + vi, b2 // 2 + 1 + vi + polys.append(atri) + polys.append(btri) + + vi = len(verts) + +# write the model file +with open("{}.obj".format(name), "w") as f: + fprint = lambda *args, **kwargs: print(*args, file=f, **kwargs) + + fprint("mtllib {}.mtl".format(name)) + + fprint("o {}".format(name)) + + for vert in verts: + fprint("v", *("{:.8f}".format(v) for v in vert)) + + for tex in texes: + fprint("vt", *("{:.8f}".format(v) for v in tex)) + + #fprint("g {}".format(name)) + fprint("usemtl {}".format(name)) + fprint("s off") + + for poly in polys: + fprint("f", *("{}/{}".format(i, i) for i in poly)) + +# write the material file +with open("{}.mtl".format(name), "w") as f: + fprint = lambda *args, **kwargs: print(*args, file=f, **kwargs) + + fprint("newmtl {}".format(name)) + fprint("Ns 0.0") + # i don't know what any of these do but they all look terrible + fprint("Ka 1.0 1.0 1.0") + fprint("Kd 0.8 0.8 0.8") + fprint("Ks 0.0 0.0 0.0") + fprint("Ke 0.0 0.0 0.0") + fprint("d 1.0") + fprint("illum 0") + + fprint("map_Kd {}.bmp".format(name)) + #fprint("map_Ka {}.bmp".format(name)) + +# write the texture file +with open("{}.bmp".format(name), "wb") as f: + f.write(b'BM') + # format: 32-bit BGRA + # everything else: sane default + f.write(struct.pack("