mirror of
https://github.com/pmret/papermario.git
synced 2024-11-09 12:32:38 +01:00
ae66312d8c
* Add Python linter to github actions * wip * Add back splat_ext * Format files * C++ -> C * format 2 files * split workflow into separate file, line length 120, fix excludes * -l 120 in ci * update black locally and apply formatting changes * pyproject.toject --------- Co-authored-by: Ethan Roseman <ethteck@gmail.com>
204 lines
6.3 KiB
Python
Executable File
204 lines
6.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
from dataclasses import dataclass
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any, List
|
|
|
|
|
|
@dataclass
|
|
class Vertex:
|
|
idx: int
|
|
x: int
|
|
y: int
|
|
z: int
|
|
u: int
|
|
v: int
|
|
r: int
|
|
g: int
|
|
b: int
|
|
a: int
|
|
|
|
def toJSON(self):
|
|
return (
|
|
' { "pos": ['
|
|
+ f"{self.x}, {self.y}, {self.z}"
|
|
+ '], "uv": ['
|
|
+ f"{self.u}, {self.v}"
|
|
+ '], "rgba": ['
|
|
+ f"{self.r}, {self.g}, {self.b}, {self.a}"
|
|
+ "] }"
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class Triangle:
|
|
i: int
|
|
j: int
|
|
k: int
|
|
|
|
def toJSON(self):
|
|
return f" [{self.i}, {self.j}, {self.k}]"
|
|
|
|
|
|
@dataclass
|
|
class Anim:
|
|
name: str
|
|
offset: int
|
|
vtx_offset: int
|
|
gfx_offset: int
|
|
vtx_count: int
|
|
gfx_count: int
|
|
keyframes: int
|
|
flags: int
|
|
|
|
frames: List[List[Vertex]]
|
|
triangles: List[Triangle]
|
|
|
|
def toJSON(self):
|
|
framestr = ",\n".join(
|
|
[" [\n" + ",\n".join([v.toJSON() for v in frame]) + "\n ]" for i, frame in enumerate(self.frames)]
|
|
)
|
|
trianglestr = ",\n".join([t.toJSON() for t in self.triangles])
|
|
|
|
ret = "{\n"
|
|
ret += ' "flags": ' + str(self.flags) + ",\n"
|
|
ret += ' "frames": [\n' + framestr + "],\n"
|
|
ret += ' "triangles": [\n' + trianglestr + "]\n"
|
|
ret += "}"
|
|
|
|
return ret
|
|
|
|
@staticmethod
|
|
def fromJSON(name: str, data: Any) -> "Anim":
|
|
flags = data["flags"]
|
|
frames = [
|
|
[Vertex(idx, *vtx["pos"], *vtx["uv"], *vtx["rgba"]) for idx, vtx in enumerate(frame)]
|
|
for frame in data["frames"]
|
|
]
|
|
triangles = [Triangle(*t) for t in data["triangles"]]
|
|
|
|
return Anim(
|
|
name=name,
|
|
offset=0,
|
|
vtx_offset=0,
|
|
gfx_offset=0,
|
|
vtx_count=0,
|
|
gfx_count=0,
|
|
keyframes=len(frames),
|
|
flags=flags,
|
|
frames=frames,
|
|
triangles=triangles,
|
|
)
|
|
|
|
|
|
def build(inputs: List[Path], output: Path):
|
|
with open(output, "w") as f:
|
|
f.write("/* NOTE: This file is autogenerated, do not edit */\n\n")
|
|
f.write('#include "PR/gbi.h"\n')
|
|
f.write('#include "macros.h"\n')
|
|
f.write('#include "imgfx.h"\n\n')
|
|
|
|
for input in inputs:
|
|
with open(input, "r") as fin:
|
|
in_json = json.load(fin)
|
|
|
|
anim = Anim.fromJSON(input.name[:-5], in_json)
|
|
|
|
vtx_name = f"{anim.name}_keyframes"
|
|
gfx_name = f"{anim.name}_gfx"
|
|
|
|
# this has padding before it..maybe a file split?
|
|
if anim.name == "unused_1":
|
|
f.write(f"s32 padding_{anim.name}[] = {{ 0, 0 }};\n\n")
|
|
|
|
# vtx
|
|
f.write(f"ImgFXVtx {vtx_name}[{len(anim.frames)}][{len(anim.frames[0])}] = {{\n")
|
|
for frame in anim.frames:
|
|
f.write(" {\n")
|
|
for vtx in frame:
|
|
f.write(
|
|
f" {{ {{{vtx.x}, {vtx.y}, {vtx.z}}}, {{{vtx.u}, {vtx.v}}}, {{{vtx.r}, {vtx.g}, {vtx.b}}}, {vtx.a} }},\n"
|
|
)
|
|
f.write(" },\n")
|
|
f.write("};\n\n")
|
|
|
|
# gfx
|
|
f.write(f"Gfx {gfx_name}[] = {{\n")
|
|
|
|
# TODO hard-coded
|
|
cur_tri = 0
|
|
just_chunked = False
|
|
chunk_text = ""
|
|
max_t1 = 0
|
|
max_t2 = 0
|
|
min_t = 0
|
|
sub_num = 0
|
|
old_max_t = 0
|
|
while cur_tri < len(anim.triangles):
|
|
# Vertex command every 16 triangles
|
|
t1 = anim.triangles[cur_tri]
|
|
t2 = anim.triangles[cur_tri + 1] if cur_tri + 1 < len(anim.triangles) else None
|
|
|
|
t1x = t1.i - sub_num
|
|
t1y = t1.j - sub_num
|
|
t1z = t1.k - sub_num
|
|
max_t1 = max(max_t1, t1x, t1y, t1z)
|
|
t2x = 0 if t2 is None else t2.i - sub_num
|
|
t2y = 0 if t2 is None else t2.j - sub_num
|
|
t2z = 0 if t2 is None else t2.k - sub_num
|
|
max_t2 = max(max_t2, t2x, t2y, t2z)
|
|
min_t = min(min_t, t1x, t1y, t1z, t2x, t2y, t2z)
|
|
|
|
# We need a new chunk
|
|
if max_t1 >= 32 and not just_chunked:
|
|
chunk_text = (
|
|
f" gsSPVertex((u8*){vtx_name} + 0xC * {sub_num}, {min(32, max_t1 + 1)}, 0),\n" + chunk_text
|
|
)
|
|
just_chunked = True
|
|
f.write(chunk_text)
|
|
chunk_text = ""
|
|
sub_num += old_max_t - min_t + 1
|
|
min_t = 32
|
|
max_t1 = 0
|
|
max_t2 = 0
|
|
continue
|
|
|
|
just_chunked = False
|
|
if max_t2 >= 32 or t2 is None:
|
|
chunk_text += f" gsSP1Triangle({t1x}, {t1y}, {t1z}, 0),\n"
|
|
cur_tri += 1
|
|
old_max_t = max_t1
|
|
else:
|
|
chunk_text += f" gsSP2Triangles({t1x}, {t1y}, {t1z}, 0, {t2x}, {t2y}, {t2z}, 0),\n"
|
|
cur_tri += 2
|
|
old_max_t = max(max_t1, max_t2)
|
|
|
|
# Dump final chunk
|
|
chunk_text = (
|
|
f" gsSPVertex((u8*){vtx_name} + 0xC * {sub_num}, {max(max_t1, max_t2) + 1}, 0),\n" + chunk_text
|
|
)
|
|
f.write(chunk_text)
|
|
f.write(" gsSPEndDisplayList(),\n")
|
|
f.write("};\n\n")
|
|
|
|
# header
|
|
f.write(f"ImgFXAnimHeader {anim.name}_header = {{\n")
|
|
f.write(f" .keyframesOffset = {vtx_name}[0],\n")
|
|
f.write(f" .gfxOffset = {gfx_name},\n")
|
|
f.write(f" .vtxCount = ARRAY_COUNT({vtx_name}[0]),\n")
|
|
f.write(f" .gfxCount = ARRAY_COUNT({gfx_name}),\n")
|
|
f.write(f" .keyframesCount = ARRAY_COUNT({vtx_name}),\n")
|
|
f.write(f" .flags = {anim.flags},\n")
|
|
f.write("};\n\n")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description="ImgFX animation c file builder")
|
|
parser.add_argument("inputs", type=Path, help="json files to build, passed in order", nargs="+")
|
|
parser.add_argument("output", type=Path, help="Output c file path")
|
|
args = parser.parse_args()
|
|
|
|
build(args.inputs, args.output)
|