From 7e1c251a2234561a87705e04e3a77256c86d5cfb Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Sun, 15 Sep 2024 19:14:56 -0500 Subject: [PATCH] gen: move to separate repository --- cartridge.lds | 2 + gen/binary_image_palette.py | 78 -------------- gen/color_convert.py | 92 ----------------- gen/fixed_point.py | 77 -------------- gen/generate.py | 35 ------- gen/parse_material.py | 47 --------- gen/parse_obj.py | 162 ----------------------------- gen/path.py | 9 -- gen/profiles.py | 34 ------- gen/render_material_textures.py | 119 ---------------------- gen/render_model.py | 174 -------------------------------- header.s | 2 +- model_generator | 1 + 13 files changed, 4 insertions(+), 828 deletions(-) delete mode 100644 gen/binary_image_palette.py delete mode 100644 gen/color_convert.py delete mode 100644 gen/fixed_point.py delete mode 100644 gen/generate.py delete mode 100644 gen/parse_material.py delete mode 100644 gen/parse_obj.py delete mode 100644 gen/path.py delete mode 100644 gen/profiles.py delete mode 100644 gen/render_material_textures.py delete mode 100644 gen/render_model.py create mode 120000 model_generator diff --git a/cartridge.lds b/cartridge.lds index 9d13b4a..bad5f74 100644 --- a/cartridge.lds +++ b/cartridge.lds @@ -22,6 +22,7 @@ SECTIONS .text.arm9 ALIGN(4) : { KEEP(*(.data.arm9*.bin)) + . = ALIGN(32); } AT>rom . = 0x02000000 + 0x390000; @@ -31,6 +32,7 @@ SECTIONS .text.arm7 ALIGN(4) : { KEEP(*(.data.arm7*.bin)) + . = ALIGN(32); } AT>rom /DISCARD/ : diff --git a/gen/binary_image_palette.py b/gen/binary_image_palette.py deleted file mode 100644 index bc967e7..0000000 --- a/gen/binary_image_palette.py +++ /dev/null @@ -1,78 +0,0 @@ -import struct -import sys - -from PIL import Image - -def round_up_palette_size(palette_size): - assert palette_size != 0, (name, palette_size) - if palette_size <= 4: - return 4 - elif palette_size <= 16: - return 16 - elif palette_size <= 256: - return 256 - else: - assert False, palette_size - -def pixels_per_byte(palette_size): - if palette_size == 4: - return 4 - elif palette_size == 16: - return 2 - elif palette_size == 256: - return 1 - else: - assert False, palette_size - -def pack_one_byte(pixels, index, colors, palette_size): - color_count = len(colors) - num = pixels_per_byte(palette_size) - shift = 8 // num - byte = 0 - i = 0 - while num > 0: - px, alpha = pixels[index] - if alpha == 0 and color_count < palette_size: - px = color_count - assert px < palette_size - byte |= px << (shift * i) - index += 1 - i += 1 - num -= 1 - return [byte], index - -def pack_one_texel(pixels, index, colors, palette_size): - px, alpha = pixels[index] - return - -def pack_pixels(pixels, width, height, colors, palette_size): - index = 0 - with open(sys.argv[2], 'wb') as f: - while index < (width * height): - byte_list, index = pack_texel(pixels, index, colors, palette_size) - f.write(bytes(byte)) - -def pack_palette(colors, palette_size): - with open(sys.argv[2], 'wb') as f: - for color in colors: - out = argb1555(255, *color) - f.write(struct.pack('> 3) & 31 - g6 = (g >> 3) & 31 - g6_l = (g >> 2) & 1 - b5 = (b >> 3) & 31 - return (g6_l << 15) | (b5 << 10) | (g6 << 5) | (r5 << 0) - - def argb4444(r, g, b, a): - a4 = (a >> 4) & 15 - r4 = (r >> 4) & 15 - g4 = (g >> 4) & 15 - b4 = (b >> 4) & 15 - return (a4 << 12) | (r4 << 8) | (g4 << 4) | (b4 << 0) - - def argb1555(r, g, b, a): - a1 = (a >> 7) & 1 - r5 = (r >> 3) & 31 - g5 = (g >> 3) & 31 - b5 = (b >> 3) & 31 - return (a1 << 15) | (r5 << 10) | (g5 << 5) | (b5 << 0) - - def rgb565(r, g, b, a): - r5 = (r >> 3) & 31 - g6 = (g >> 2) & 63 - b5 = (b >> 3) & 31 - return (r5 << 11) | (g5 << 5) | (b5 << 0) - - def from_string(s): - return dict([ - ("gbgr1555", color_format.gbgr1555), - ("argb4444", color_format.argb4444), - ("argb1555", color_format.argb1555), - ("rgb565", color_format.rgb565), - ])[s] - -in_file = sys.argv[1] -format = sys.argv[2] -out_file = sys.argv[3] - -palette = None - -with Image.open(in_file) as im: - width, height = im.size - if not im.palette: - pixels = list(im.convert("RGBA").getdata()) - else: - pixels = list(im.convert("P").getdata()) - palette = list(im.palette.colors) - -has_alpha = False -convert = color_format.from_string(format) - -def convert_colors(f, colors): - for color in colors: - value = convert(*color) - f.write(struct.pack("= integer_point: - raise FixedPointOverflow((integer, integer_point)) - return FixedPoint( - fp.sign, - value, - point - ) - - def to_int(fp): - return fp.sign * fp.value - - def to_float(fp): - return fp.sign * fp.value / fp.point - -def from_float(n): - if n == 0.0: - sign = 1 - value = 0 - else: - sign = -1 if n < 0 else 1 - value = abs(round(n * (2 ** 32))) - point = 2 ** 32 - return FixedPoint(sign, value, point) - -assert from_float(0.5).to_float() == 0.5 -assert from_float(1.5).to_fixed_point(16, 16).value == 98304 - -def parse(s): - sign = -1 if s.startswith('-') else 1 - s = s.removeprefix('-') - integer, fraction = s.split('.') - assert all(c in string.digits for c in integer), integer - assert all(c in string.digits for c in fraction), fraction - assert len(integer) > 0 and len(fraction) > 0, s - point = 10 ** len(fraction) - value = int(integer) * point + int(fraction) - return FixedPoint( - sign, - value, - point - ) - -def equal(a, b): - epsilon = 0.00001 - return (a - b) < epsilon - -def assert_raises(e, f): - try: - f() - except e: - return - raise AssertionError(f"expected {str(e)}") - -assert parse("1.234").value == 1234 -assert equal(parse("1.234").to_float(), 1.234) -assert parse("1.234").to_fixed_point(16, 16).value == 80871 -assert_raises(FixedPointOverflow, - lambda: parse("2.234").to_fixed_point(1, 16)) -assert parse("2.234").to_fixed_point(2, 16).value == 146407 diff --git a/gen/generate.py b/gen/generate.py deleted file mode 100644 index 2871189..0000000 --- a/gen/generate.py +++ /dev/null @@ -1,35 +0,0 @@ -import io - -def should_autonewline(line): - return ( - "static_assert" not in line - and "extern" not in line - and (len(line.split()) < 2 or line.split()[1] != '=') # hacky; meh - ) - -def _render(out, lines): - indent = " " - level = 0 - for l in lines: - if l and (l[0] == "}" or l[0] == ")"): - level -= 2 - assert level >= 0, out.getvalue() - - if len(l) == 0: - out.write("\n") - else: - out.write(indent * level + l + "\n") - - if l and (l[-1] == "{" or l[-1] == "("): - level += 2 - - if level == 0 and l and l[-1] == ";": - if should_autonewline(l): - out.write("\n") - return out - -def renderer(): - out = io.StringIO() - def render(lines): - return _render(out, lines) - return render, out diff --git a/gen/parse_material.py b/gen/parse_material.py deleted file mode 100644 index 04d0793..0000000 --- a/gen/parse_material.py +++ /dev/null @@ -1,47 +0,0 @@ -from dataclasses import dataclass - -@dataclass -class NewMtl: - name: str - -@dataclass -class MapKd: - name: str - -def parse_material_newmtl(args): - name, = args.split() - yield NewMtl(name.replace('-', '_').replace('.', '_')) - -def parse_material_mapkd(args): - name, = args.split() - yield MapKd(name) - -def parse_mtl_line(line): - prefixes = [ - ('newmtl ', parse_material_newmtl), - ('map_Kd ', parse_material_mapkd), - ] - for prefix, parser in prefixes: - if line.startswith(prefix): - args = line.removeprefix(prefix) - yield from parser(args) - -def group_by_material(l): - current_material = None - for i in l: - if type(i) is NewMtl: - current_material = i - elif type(i) is MapKd: - assert current_material is not None - yield (current_material, i) - current_material = None - else: - assert False, type(i) - -def parse_mtl_file(buf): - return list(group_by_material(( - parsed - for line in buf.strip().split('\n') - for parsed in parse_mtl_line(line) - if not line.startswith('#') - ))) diff --git a/gen/parse_obj.py b/gen/parse_obj.py deleted file mode 100644 index f004ffc..0000000 --- a/gen/parse_obj.py +++ /dev/null @@ -1,162 +0,0 @@ -from collections import defaultdict -from dataclasses import dataclass -import string -from fixed_point import FixedPoint -import fixed_point - -@dataclass -class VertexPosition: - x: FixedPoint - y: FixedPoint - z: FixedPoint - -@dataclass -class VertexNormal: - x: FixedPoint - y: FixedPoint - z: FixedPoint - -@dataclass -class VertexTexture: - u: FixedPoint - v: FixedPoint - -@dataclass -class IndexVTN: - vertex_position: int - vertex_texture: int - vertex_normal: int - -@dataclass -class Triangle: - a: IndexVTN - b: IndexVTN - c: IndexVTN - -@dataclass -class Quadrilateral: - a: IndexVTN - b: IndexVTN - c: IndexVTN - d: IndexVTN - -@dataclass -class Object: - name: str - -@dataclass -class Material: - lib: str - name: str - -@dataclass -class MtlLib: - name: str - -def parse_fixed_point_vector(args, n): - split = args.split() - assert len(split) == n, (n, split) - return tuple(map(fixed_point.parse, split)) - -def parse_vertex_position(args): - coordinates = parse_fixed_point_vector(args, 3) - yield VertexPosition(*coordinates) - -def parse_vertex_normal(args): - coordinates = parse_fixed_point_vector(args, 3) - yield VertexNormal(*coordinates) - -def parse_vertex_texture(args): - coordinates = parse_fixed_point_vector(args, 2) - yield VertexTexture(*coordinates) - -def int_minus_one(s): - n = int(s) - 1 - assert n >= 0 - return n - -def _parse_vertex_indices(args): - indices = args.split('/') - assert len(indices) == 3, indices - return IndexVTN(*map(int_minus_one, indices)) - -def parse_face(args): - vertices = args.split() - if len(vertices) == 3: - yield Triangle(*map(_parse_vertex_indices, vertices)) - elif len(vertices) == 4: - yield Quadrilateral(*map(_parse_vertex_indices, vertices)) - else: - assert False, (len(vertices), args) - -def safe(s): - return s.replace('-', '_').replace('.', '_').replace(':', '_') - -def parse_object(args): - name, = args.split() - yield Object(safe(name)) - -def parse_material(args): - name, = args.split() - yield Material(None, safe(name)) - -def parse_mtllib(args): - name, = args.split() - yield MtlLib(name) - -def parse_obj_line(line): - prefixes = [ - ('v ', parse_vertex_position), - ('vn ', parse_vertex_normal), - ('vt ', parse_vertex_texture), - ('f ', parse_face), - ('o ', parse_object), - ('usemtl ', parse_material), - ('mtllib ', parse_mtllib), - ] - for prefix, parser in prefixes: - if line.startswith(prefix): - args = line.removeprefix(prefix) - yield from parser(args) - -def group_by_type(l): - vertices = defaultdict(list) - current_object = None - faces = defaultdict(lambda: defaultdict(list)) - materials = dict() - current_mtllib = None - multi_material_index = 0 - for i in l: - if type(i) in {VertexPosition, VertexTexture, VertexNormal}: - vertices[type(i)].append(i) - elif type(i) in {Triangle, Quadrilateral}: - assert current_object is not None - faces[current_object.name][type(i)].append(i) - elif type(i) is Material: - assert current_object is not None - assert current_mtllib is not None - i.lib = current_mtllib.name - if current_object.name in materials: - if multi_material_index != 0: - current_object.name = current_object.name[:-4] - current_object.name += f"_{multi_material_index:03}" - multi_material_index += 1 - assert current_object.name not in materials - materials[current_object.name] = i - elif type(i) is Object: - multi_material_index = 0 - current_object = i - elif type(i) is MtlLib: - current_mtllib = i - else: - assert False, type(i) - - return dict(vertices), dict((k, dict(v)) for k, v in faces.items()), materials - -def parse_obj_file(buf): - return group_by_type(( - parsed - for line in buf.strip().split('\n') - for parsed in parse_obj_line(line) - if not line.startswith('#') - )) diff --git a/gen/path.py b/gen/path.py deleted file mode 100644 index 2c3511a..0000000 --- a/gen/path.py +++ /dev/null @@ -1,9 +0,0 @@ -from os import path - -def texture_path(s): - #return path.join('..', 'texture', s) - return s - -def model_path(s): - #return path.join('..', 'model', s) - return s diff --git a/gen/profiles.py b/gen/profiles.py deleted file mode 100644 index 92f9734..0000000 --- a/gen/profiles.py +++ /dev/null @@ -1,34 +0,0 @@ -from dataclasses import dataclass - -@dataclass -class Profile: - position: tuple[int, int] - texture: tuple[int, int] - normal: tuple[int, int] - -@dataclass -class FixedPointBits: - integer: int - fraction: int - - def to_str(self): - return f"{self.integer}.{self.fraction} fixed-point" - -@dataclass -class FloatingPoint: - def to_str(self): - return f"floating-point" - -profiles = {} - -profiles["nds"] = Profile( - position = FixedPointBits(3, 12), # 3.12 - normal = FixedPointBits(0, 9), # 0.9 - texture = FixedPointBits(1, 14), # 1.14 -) - -profiles["dreamcast"] = Profile( - position = FloatingPoint(), - normal = FloatingPoint(), - texture = FloatingPoint(), -) diff --git a/gen/render_material_textures.py b/gen/render_material_textures.py deleted file mode 100644 index f7ae804..0000000 --- a/gen/render_material_textures.py +++ /dev/null @@ -1,119 +0,0 @@ -from dataclasses import dataclass -from generate import renderer -from math import log -from path import texture_path -import sys - -from PIL import Image - -from parse_material import parse_mtl_file - -material_filenames = sys.argv[1:] - -def render_material_enum(newmtl_mapkd): - yield f"enum material {{" - for newmtl, mapkd in newmtl_mapkd: - yield f"{newmtl.name},"; - yield "};" - -def render_pixel_descriptor(offset, mapkd, dimensions): - name, _ext = mapkd.name.rsplit('.', maxsplit=1) - pixel_name = f"{name}_data" - width, height = dimensions - yield ".pixel = {" - yield f".start = (uint8_t *)&_binary_{pixel_name}_start," - yield f".size = (int)&_binary_{pixel_name}_size," - yield f".vram_offset = {offset.pixel}," - yield f".width = {width}," - yield f".height = {height}," - yield "}," - -def render_palette_descriptor(offset, mapkd, palette_size): - name, _ext = mapkd.name.rsplit('.', maxsplit=1) - palette_name = f"{name}_data_pal" - yield ".palette = {" - yield f".start = (uint8_t *)&_binary_{palette_name}_start," - yield f".size = (int)&_binary_{palette_name}_size," - yield f".vram_offset = {offset.palette}," - yield f".palette_size = {palette_size}," - yield "}," - -@dataclass -class Offset: - pixel: int - palette: int - -def round_up_colors(name, colors): - assert colors != 0, (name, colors) - if colors <= 4: - return 4 - if colors <= 16: - return 16 - elif colors <= 256: - return 256 - else: - assert False, (name, colors) - -def image_metadata(mapkd): - path = texture_path(mapkd.name) - with Image.open(path) as im: - dimensions = im.size - colors = len(im.palette.colors) - return dimensions, colors - -def round_up_n(x, multiple): - return ((x + multiple - 1) // multiple) * multiple - -def bytes_per_pixel(palette_size): - bits_per_pixel = int(log(palette_size)/log(2)) - return bits_per_pixel / 8 - -def render_material(offset, mapkd): - dimensions, colors = image_metadata(mapkd) - palette_size = round_up_colors(mapkd.name, colors) - - # pixel descriptor - yield from render_pixel_descriptor(offset, mapkd, dimensions) - pixel_size = bytes_per_pixel(palette_size) * dimensions[0] * dimensions[1] - #pixel_size = 2 * dimensions[0] * dimensions[1] - assert int(pixel_size) == pixel_size - offset.pixel += round_up_n(int(pixel_size), 8) - - # palette descriptor - yield from render_palette_descriptor(offset, mapkd, palette_size) - offset.palette += round_up_n(colors * 2, 16) - -def render_materials(newmtl_mapkd): - yield "struct material_descriptor material[] = {" - offset = Offset(0, 0) - for newmtl, mapkd in newmtl_mapkd: - yield f"[{newmtl.name}] = {{" - yield from render_material(offset, mapkd) - yield "}," - yield "};" - -def render_header(): - yield "#pragma once" - yield "" - yield "#include " - yield "" - yield '#include "model/material.h"' - yield "" - -if __name__ == "__main__": - material_filenames = sys.argv[1:] - assert material_filenames - newmtl_mapkd = [] - for material_filename in material_filenames: - with open(material_filename) as f: - buf = f.read() - newmtl_mapkd.extend([ - (newmtl, mapkd) - for (newmtl, mapkd) in parse_mtl_file(buf) - ]) - - render, out = renderer() - render(render_header()) - render(render_material_enum(newmtl_mapkd)) - render(render_materials(newmtl_mapkd)) - sys.stdout.write(out.getvalue()) diff --git a/gen/render_model.py b/gen/render_model.py deleted file mode 100644 index 6ac1851..0000000 --- a/gen/render_model.py +++ /dev/null @@ -1,174 +0,0 @@ -from dataclasses import astuple -import sys -from generate import renderer -import math - -import fixed_point - -from parse_obj import parse_obj_file -from parse_obj import VertexPosition -from parse_obj import VertexNormal -from parse_obj import VertexTexture -from parse_obj import Triangle -from parse_obj import Quadrilateral - -import profiles - -def render_index_vtn(index_vtn): - s = ", ".join(map(str, index_vtn)) - yield f"{{{s}}}," - -def render_face(face): - yield "{ .v = {" - for index_vtn in astuple(face): - yield from render_index_vtn(index_vtn) - yield "}}," - -def render_faces(prefix, name, faces): - yield f"union {name} {prefix}_{name}[] = {{" - for face in faces: - yield from render_face(face) - yield "};" - -def render_triangles(prefix, faces): - yield from render_faces(prefix, "triangle", faces) - -def render_quadrilateral(prefix, faces): - yield from render_faces(prefix, "quadrilateral", faces) - -def unit_vector(vec): - x = vec.x.to_float() - y = vec.y.to_float() - z = vec.z.to_float() - norm = math.sqrt(x ** 2 + y ** 2 + z ** 2) - return type(vec)( - fixed_point.parse(str(x / norm)), - fixed_point.parse(str(y / norm)), - fixed_point.parse(str(z / norm)) - ) - -def xyz(vec): - return (vec.x, vec.y, vec.z) - -def uv(vec): - return (vec.u, vec.v) - -def render_vertex(profile_item, vertex_tuple): - def _profile_item(profile_item, fp): - if type(profile_item) == profiles.FixedPointBits: - return fp.to_fixed_point(profile_item.integer, profile_item.fraction).to_int() - elif type(profile_item) == profiles.FloatingPoint: - return fp.to_float() - else: - assert False, type(profile_item) - - s = ", ".join( - str(_profile_item(profile_item, fp)) - for fp in vertex_tuple - ) - yield f"{{{s}}}," - -def render_vertices(profile_item, prefix, name, vertices): - yield f"// {profile_item.to_str()}" - yield f"vertex_{name} {prefix}_{name}[] = {{" - for i, vertex in enumerate(vertices): - yield from render_vertex(profile_item, vertex) - yield "};" - -def render_vertex_positions(profile, prefix, vertex_positions): - yield from render_vertices(profile.position, - prefix, - "position", - map(xyz, vertex_positions)) - -def render_vertex_normals(profile, prefix, vertex_normals): - yield from render_vertices(profile.normal, - prefix, - "normal", - map(xyz, map(unit_vector, vertex_normals))) - -def render_vertex_texture(profile, prefix, vertex_textures): - yield from render_vertices(profile.texture, - prefix, - "texture", - map(uv, vertex_textures)) - -def render_object(prefix, object_name, d, material): - yield f"struct object {prefix}_{object_name} = {{" - - triangle_count = len(d[Triangle]) if Triangle in d else 0 - quadrilateral_count = len(d[Quadrilateral]) if Quadrilateral in d else 0 - - if triangle_count > 0: - yield f".triangle = &{prefix}_{object_name}_triangle[0]," - else: - yield f".triangle = NULL," - - if quadrilateral_count > 0: - yield f".quadrilateral = &{prefix}_{object_name}_quadrilateral[0]," - else: - yield f".quadrilateral = NULL," - - yield f".triangle_count = {triangle_count}," - yield f".quadrilateral_count = {quadrilateral_count}," - - if material is None: - yield f".material = -1,", - else: - yield f".material = {material.name}," - - yield "};" - -def render_object_list(prefix, object_names): - yield f"struct object * {prefix}_object_list[] = {{" - for object_name in object_names: - yield f"&{prefix}_{object_name}," - yield "};" - -def render_model(prefix, object_count): - yield f"struct model {prefix}_model = {{" - yield f".position = &{prefix}_position[0]," - yield f".texture = &{prefix}_texture[0]," - yield f".normal = &{prefix}_normal[0]," - yield f".object = &{prefix}_object_list[0]," - yield f".object_count = {object_count}," - yield "};" - -def render_header(): - yield "#pragma once" - yield "" - yield "#include " - yield "" - yield '#include "../model.h"' - yield "" - -obj_filename = sys.argv[1] -prefix = sys.argv[2] -profile_name = sys.argv[3] - -profile = profiles.profiles[profile_name] - -with open(obj_filename) as f: - buf = f.read() - -vertices, faces, materials = parse_obj_file(buf) -render, out = renderer() -render(render_header()) -render(render_vertex_positions(profile, prefix, vertices[VertexPosition])) -render(render_vertex_texture(profile, prefix, vertices[VertexTexture])) -render(render_vertex_normals(profile, prefix, vertices[VertexNormal])) - -for object_name, d in faces.items(): - object_prefix = '_'.join((prefix, object_name)) - - if Triangle in d: - render(render_triangles(object_prefix, d[Triangle])) - if Quadrilateral in d: - render(render_quadrilateral(object_prefix, d[Quadrilateral])) - - render(render_object(prefix, object_name, d, materials.get(object_name))); - -render(render_object_list(prefix, faces.keys())) -render(render_model(prefix, len(faces))) - -sys.stdout.write(out.getvalue()) diff --git a/header.s b/header.s index dd29223..4d9637a 100644 --- a/header.s +++ b/header.s @@ -5,7 +5,7 @@ .ascii "00" /* Maker Code */ .byte 0x0 /* Unit Code */ .byte 0x0 /* Device type */ - .byte 0x0 /* Device capacity */ + .byte 0x1 /* Device capacity */ .fill 7,1,0x0 /* Reserved */ .byte 0x0 /* Reserved */ .byte 0x0 /* NDS region */ diff --git a/model_generator b/model_generator new file mode 120000 index 0000000..9fa5375 --- /dev/null +++ b/model_generator @@ -0,0 +1 @@ +../model_generator/ \ No newline at end of file