diff --git a/Makefile b/Makefile index 56f8de2..e93b055 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ DEFAULT = header.o arm7/arm7.bin.o triangle.elf: $(DEFAULT) arm9/triangle.bin.o triangle_rotating.elf: $(DEFAULT) arm9/triangle_rotating.bin.o texture.elf: $(DEFAULT) arm9/texture.bin.o +cube.elf: $(DEFAULT) arm9/cube.bin.o TARGET = arm-none-eabi- AARCH = -march=armv4t -mlittle-endian diff --git a/arm9/Makefile b/arm9/Makefile index 0ad5465..02fd1fb 100644 --- a/arm9/Makefile +++ b/arm9/Makefile @@ -22,7 +22,9 @@ triangle_rotating.elf: start.o examples/triangle_rotating.o ../math/cos_table_fp texture.elf: start.o examples/texture.o ../res/hotaru_futaba.data.o ../res/hotaru_futaba.data.pal.o -CFLAGS += -I../include -I../math +cube.elf: start.o examples/cube.o ../math/cos_table_fp12.o ../math/cos.o + +CFLAGS += -I../include -I../ include arm9.mk include ../common.mk diff --git a/arm9/examples/cube.c b/arm9/examples/cube.c new file mode 100644 index 0000000..ca406c8 --- /dev/null +++ b/arm9/examples/cube.c @@ -0,0 +1,193 @@ +#include "io_registers.h" +#include "bits.h" + +#include "models/cube.h" +#include "math/math.h" + +static const uint16_t face_colors[6] = { + COLOR__blue(31), + COLOR__red(31), + COLOR__green(31), + COLOR__red(31) | COLOR__green(31), + COLOR__red(31) | COLOR__blue(31), + COLOR__green(31) | COLOR__blue(31), +}; + +void main() +{ + // power control + io_registers.a.POWCNT = 0 + | POWCNT__lcd_output_destination__a_to_upper__b_to_lower + | POWCNT__geometry_engine__enable + | POWCNT__rendering_engine__enable + | POWCNT__lcd__enable; + + // enable bg0 and 3d graphics + io_registers.a.DISPCNT = 0 + | DISPCNT__display_mode__graphics_display + | DISPCNT__bg0__enable + | DISPCNT__display_selection_for_bg0__3d_graphics + ; + + // disable all 3d effects + io_registers.a.DISP3DCNT = 0 + | DISP3DCNT__clear_image__disable + | DISP3DCNT__fog_master__disable + | DISP3DCNT__edge_marking__disable + | DISP3DCNT__anti_aliasing__disable + | DISP3DCNT__alpha_blending__disable + | DISP3DCNT__alpha_test__disable + | DISP3DCNT__texture_mapping__disable; + + // clear matrix stack status + io_registers.a.GXSTAT |= GXSTAT__matrix_stack_status__overflow_or_underflow; + + // load identity matrices + io_registers.a.MTX_MODE = MTX_MODE__matrix_mode__projection; + io_registers.a.MTX_IDENTITY = 0; + // scale everything by 1/2 + io_registers.a.MTX_SCALE = (1 << 12) / 2; + io_registers.a.MTX_SCALE = (1 << 12) / 2; + io_registers.a.MTX_SCALE = (1 << 12) / 2; + // scale the x-axis by the ratio of the display height by the display width. + io_registers.a.MTX_SCALE = (192 << 12) / 256; + io_registers.a.MTX_SCALE = 1 << 12; + io_registers.a.MTX_SCALE = 1 << 12; + + io_registers.a.MTX_MODE = MTX_MODE__matrix_mode__position; + io_registers.a.MTX_IDENTITY = 0; + + io_registers.a.MTX_MODE = MTX_MODE__matrix_mode__position_and_vector; + io_registers.a.MTX_IDENTITY = 0; + + // set the 3d clear color to a dark red + io_registers.a.CLEAR_COLOR = 0 + | CLEAR_COLOR__clear_polygon_id(31) + | CLEAR_COLOR__alpha_value(31) + | CLEAR_COLOR__blue(1) + | CLEAR_COLOR__green(1) + | CLEAR_COLOR__red(10); + + // set the depth buffer clear value to the maximum value + io_registers.a.CLEAR_DEPTH = CLEAR_DEPTH__value(0x7fff); + + // the following polygons are fully opaque and are not + // backface-culled + io_registers.a.POLYGON_ATTR = 0 + | POLYGON_ATTR__alpha_value(31) + | POLYGON_ATTR__render_front_surface__enable + | POLYGON_ATTR__render_back_surface__enable; + + // the 3d viewport is the entire display area + io_registers.a.VIEWPORT = 0 + | VIEWPORT__y2(191) + | VIEWPORT__x2(255) + | VIEWPORT__y1(0) + | VIEWPORT__x1(0); + + // degrees + int theta = 0; + + while (1) { + // calculate sin/cos for 2d rotation; signed fp20.12 result + int cos = cos_fp12(theta); + int sin = sin_fp12(theta); + + int cos2 = cos_fp12(theta >> 1); + int sin2 = sin_fp12(theta >> 1); + + io_registers.a.MTX_MODE = MTX_MODE__matrix_mode__position; + // reset position matrix + io_registers.a.MTX_IDENTITY = 0; + + // multiply by a z-axis rotation + io_registers.a.MTX_MULT_3X3 = cos; + io_registers.a.MTX_MULT_3X3 = -sin; + io_registers.a.MTX_MULT_3X3 = 0; + + io_registers.a.MTX_MULT_3X3 = sin; + io_registers.a.MTX_MULT_3X3 = cos; + io_registers.a.MTX_MULT_3X3 = 0; + + io_registers.a.MTX_MULT_3X3 = 0; + io_registers.a.MTX_MULT_3X3 = 0; + io_registers.a.MTX_MULT_3X3 = 1 << 12; + + // multiply by a y-axis rotation + io_registers.a.MTX_MULT_3X3 = cos2; + io_registers.a.MTX_MULT_3X3 = 0; + io_registers.a.MTX_MULT_3X3 = sin2; + + io_registers.a.MTX_MULT_3X3 = 0; + io_registers.a.MTX_MULT_3X3 = 1 << 12; + io_registers.a.MTX_MULT_3X3 = 0; + + io_registers.a.MTX_MULT_3X3 = -sin2; + io_registers.a.MTX_MULT_3X3 = 0; + io_registers.a.MTX_MULT_3X3 = cos2; + + // multiply by a x-axis rotation + io_registers.a.MTX_MULT_3X3 = cos2; + io_registers.a.MTX_MULT_3X3 = -sin2; + io_registers.a.MTX_MULT_3X3 = 0; + + io_registers.a.MTX_MULT_3X3 = sin2; + io_registers.a.MTX_MULT_3X3 = cos2; + io_registers.a.MTX_MULT_3X3 = 0; + + io_registers.a.MTX_MULT_3X3 = 0; + io_registers.a.MTX_MULT_3X3 = 0; + io_registers.a.MTX_MULT_3X3 = 1 << 12; + + // the following vertices are a quadrilateral + io_registers.a.BEGIN_VTXS = BEGIN_VTXS__type__quadrilateral; + + // cube faces + for (int i = 0; i < 6; i++) { + io_registers.a.COLOR = face_colors[i]; + + struct vertex_position * a = &cube_positions[cube_faces[i].a.position]; + io_registers.a.VTX_10 = 0 + | VTX_10__z_coordinate(a->z) + | VTX_10__y_coordinate(a->y) + | VTX_10__x_coordinate(a->x); + + struct vertex_position * b = &cube_positions[cube_faces[i].b.position]; + io_registers.a.VTX_10 = 0 + | VTX_10__z_coordinate(b->z) + | VTX_10__y_coordinate(b->y) + | VTX_10__x_coordinate(b->x); + + struct vertex_position * c = &cube_positions[cube_faces[i].c.position]; + io_registers.a.VTX_10 = 0 + | VTX_10__z_coordinate(c->z) + | VTX_10__y_coordinate(c->y) + | VTX_10__x_coordinate(c->x); + + struct vertex_position * d = &cube_positions[cube_faces[i].d.position]; + io_registers.a.VTX_10 = 0 + | VTX_10__z_coordinate(d->z) + | VTX_10__y_coordinate(d->y) + | VTX_10__x_coordinate(d->x); + } + + // end of the quadrilateral + io_registers.a.END_VTXS = 0; + + // wait for the end of the current frame + while (io_registers.a.VCOUNT != 262); + while (io_registers.a.VCOUNT == 262); + + // wait for the geometry engine + while (io_registers.a.GXSTAT & GXSTAT__geometry_engine_busy); + + // swap buffers + io_registers.a.SWAP_BUFFERS = 0; + + // increment theta once per frame + theta += 1; + if (theta >= 360 * 2) { + theta = 0; + } + } +} diff --git a/arm9/examples/texture.c b/arm9/examples/texture.c index 67575b7..d412364 100644 --- a/arm9/examples/texture.c +++ b/arm9/examples/texture.c @@ -1,8 +1,8 @@ #include "io_registers.h" #include "bits.h" -#include "../res/hotaru_futaba.data.h" -#include "../res/hotaru_futaba.data.pal.h" +#include "res/hotaru_futaba.data.h" +#include "res/hotaru_futaba.data.pal.h" static inline uint16_t rgb565(const uint8_t * buf) { diff --git a/arm9/examples/triangle_rotating.c b/arm9/examples/triangle_rotating.c index e625ab6..a9d01f7 100644 --- a/arm9/examples/triangle_rotating.c +++ b/arm9/examples/triangle_rotating.c @@ -1,7 +1,7 @@ #include "io_registers.h" #include "bits.h" -#include "math.h" +#include "math/math.h" void main() { diff --git a/models/cube.h b/models/cube.h new file mode 100644 index 0000000..203435b --- /dev/null +++ b/models/cube.h @@ -0,0 +1,71 @@ +#include "model.h" + +// .6 fixed-point +struct vertex_position cube_positions[] = { + {64, 64, -64}, + {64, -64, -64}, + {64, 64, 64}, + {64, -64, 64}, + {-64, 64, -64}, + {-64, -64, -64}, + {-64, 64, 64}, + {-64, -64, 64}, +}; + +// .15 fixed-point +struct vertex_texture cube_textures[] = { + {32768, 32768}, + {0, 32768}, + {0, 0}, + {32768, 0}, +}; + +// .9 fixed-point +struct vertex_normal cube_normals[] = { + {0, 512, 0}, + {0, 0, 512}, + {-512, 0, 0}, + {0, -512, 0}, + {512, 0, 0}, + {0, 0, -512}, +}; + +struct face cube_faces[] = { + { + {0, 0, 0}, + {4, 1, 0}, + {6, 2, 0}, + {2, 3, 0}, + }, + { + {3, 3, 1}, + {2, 0, 1}, + {6, 1, 1}, + {7, 2, 1}, + }, + { + {7, 2, 2}, + {6, 1, 2}, + {4, 0, 2}, + {5, 3, 2}, + }, + { + {5, 1, 3}, + {1, 0, 3}, + {3, 3, 3}, + {7, 2, 3}, + }, + { + {1, 3, 4}, + {0, 0, 4}, + {2, 1, 4}, + {3, 2, 4}, + }, + { + {5, 2, 5}, + {4, 1, 5}, + {0, 0, 5}, + {1, 3, 5}, + }, +}; + diff --git a/models/cube.obj b/models/cube.obj new file mode 100644 index 0000000..0812d91 --- /dev/null +++ b/models/cube.obj @@ -0,0 +1,28 @@ +# Blender 4.1.1 +# www.blender.org +o Cube +v 1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 1.000000 +v -1.000000 1.000000 -1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 1.000000 +vn -0.0000 1.0000 -0.0000 +vn -0.0000 -0.0000 1.0000 +vn -1.0000 -0.0000 -0.0000 +vn -0.0000 -1.0000 -0.0000 +vn 1.0000 -0.0000 -0.0000 +vn -0.0000 -0.0000 -1.0000 +vt 1.000000 1.000000 +vt 0.000000 1.000000 +vt 0.000000 0.000000 +vt 1.000000 0.000000 +s 0 +f 1/1/1 5/2/1 7/3/1 3/4/1 +f 4/4/2 3/1/2 7/2/2 8/3/2 +f 8/3/3 7/2/3 5/1/3 6/4/3 +f 6/2/4 2/1/4 4/4/4 8/3/4 +f 2/4/5 1/1/5 3/2/5 4/3/5 +f 6/3/6 5/2/6 1/1/6 2/4/6 diff --git a/models/generate.py b/models/generate.py new file mode 120000 index 0000000..936fb4d --- /dev/null +++ b/models/generate.py @@ -0,0 +1 @@ +../registers/generate.py \ No newline at end of file diff --git a/models/model.h b/models/model.h new file mode 100644 index 0000000..07b6a4e --- /dev/null +++ b/models/model.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +struct index_ptn { + uint16_t position; + uint16_t texture; + uint16_t normal; +}; + +struct face { + struct index_ptn a; + struct index_ptn b; + struct index_ptn c; + struct index_ptn d; +}; + +struct vertex_position { // signed 4.6 fixed point + uint16_t x; + uint16_t y; + uint16_t z; +}; + +struct vertex_normal { // s.9 fixed point + uint16_t x; + uint16_t y; + uint16_t z; +}; + +struct vertex_texture { // s.15 fixed point + uint16_t u; + uint16_t v; +}; diff --git a/models/parse_obj_fixed_point.py b/models/parse_obj_fixed_point.py new file mode 100644 index 0000000..103c1a3 --- /dev/null +++ b/models/parse_obj_fixed_point.py @@ -0,0 +1,112 @@ +from collections import defaultdict +from dataclasses import dataclass +import string + +@dataclass +class FixedPoint: + negative: bool + integer: int + fraction: int + point: int + +@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 Face: + a: IndexVTN + b: IndexVTN + c: IndexVTN + d: IndexVTN + +def parse_fixed_point(s): + negative = s.startswith('-') + 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 + return FixedPoint( + negative, + int(integer), + int(fraction), + 10 ** len(fraction) + ) + +def parse_fixed_point_vector(args, n): + split = args.split() + assert len(split) == n, (n, split) + return tuple(map(parse_fixed_point, 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() + assert len(vertices) == 4, vertices + yield Face(*map(parse_vertex_indices, vertices)) + +def parse_obj_line(line): + prefixes = [ + ('v ', parse_vertex_position), + ('vn ', parse_vertex_normal), + ('vt ', parse_vertex_texture), + ('f ', parse_face) + ] + for prefix, parser in prefixes: + if line.startswith(prefix): + args = line.removeprefix(prefix) + yield from parser(args) + +def group_by_type(l): + grouped = defaultdict(list) + for i in l: + grouped[type(i)].append(i) + return dict(grouped) + +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/models/render_obj_fixed_point_c_source.py b/models/render_obj_fixed_point_c_source.py new file mode 100644 index 0000000..1e04471 --- /dev/null +++ b/models/render_obj_fixed_point_c_source.py @@ -0,0 +1,91 @@ +from dataclasses import astuple +import sys +from generate import renderer + +from parse_obj_fixed_point import parse_obj_file +from parse_obj_fixed_point import VertexPosition +from parse_obj_fixed_point import VertexNormal +from parse_obj_fixed_point import VertexTexture +from parse_obj_fixed_point import Face + +vertex_position_fraction_bits = 6 # 4.6 +vertex_normal_fraction_bits = 9 # s.9 +vertex_texture_fraction_bits = 15 # s.15 + +def convert_fixed_point(fp, fraction_bits): + point = 2 ** fraction_bits + sign = -1 if fp.negative else 1 + n0 = sign * (fp.integer * fp.point + fp.fraction) + n1 = n0 * point // fp.point + return n1 + +def render_index_vtn(index_vtn): + s = ", ".join(map(str, index_vtn)) + yield f"{{{s}}}," + +def render_face(face): + yield "{" + for index_vtn in astuple(face): + yield from render_index_vtn(index_vtn) + yield "}," + +def render_faces(prefix, faces): + yield f"struct face {prefix}_faces[] = {{" + for face in faces: + yield from render_face(face) + yield "};" + +def xyz(vec): + return (vec.x, vec.y, vec.z) + +def uv(vec): + return (vec.u, vec.v) + +def render_vertex(vertex_tuple, fraction_bits): + s = ", ".join( + str(convert_fixed_point(fp, fraction_bits)) + for fp in vertex_tuple + ) + yield f"{{{s}}}," + +def render_vertices(prefix, name, vertices, fraction_bits): + yield f"// .{fraction_bits} fixed-point" + yield f"struct vertex_{name} {prefix}_{name}s[] = {{" + for vertex in vertices: + yield from render_vertex(vertex, fraction_bits) + yield "};" + +def render_vertex_positions(prefix, vertex_positions): + yield from render_vertices(prefix, + "position", + map(xyz, vertex_positions), + vertex_position_fraction_bits) + +def render_vertex_normals(prefix, vertex_normals): + yield from render_vertices(prefix, + "normal", + map(xyz, vertex_normals), + vertex_normal_fraction_bits) + +def render_vertex_texture(prefix, vertex_textures): + yield from render_vertices(prefix, + "texture", + map(uv, vertex_textures), + vertex_texture_fraction_bits) + +def render_header(): + yield '#include "model.h"' + yield "" + +with open(sys.argv[1]) as f: + buf = f.read() + +prefix = sys.argv[2] +d = parse_obj_file(buf) +render, out = renderer() +render(render_header()) +render(render_vertex_positions(prefix, d[VertexPosition])) +render(render_vertex_texture(prefix, d[VertexTexture])) +render(render_vertex_normals(prefix, d[VertexNormal])) +render(render_faces(prefix, d[Face])) +sys.stdout.write(out.getvalue())