gists/pot3d.py

140 lines
4.1 KiB
Python
Raw Normal View History

2018-08-21 19:17:05 -07:00
# 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("<ihhi", len(rgbs) * 4 + 14, 0, 0, 14 + 40))
f.write(struct.pack("<iiihhiiiiii", 40, tw, th, 1, 32, 0, 0, 0, 0, 0, 0))
for rgb in reversed(rgbs):
r, g, b = rgb
a = 0xFF
f.write(struct.pack("<BBBB", b, g, r, a))