From 7640a163334d306269980274c6b3b1989c6b80d7 Mon Sep 17 00:00:00 2001 From: sfja Date: Tue, 24 Mar 2026 22:47:12 +0100 Subject: [PATCH] init --- .clang-format | 19 ++ .gitignore | 1 + Makefile | 20 ++ compile_flags.txt | 10 + linalg.c | 33 ++++ linalg.h | 59 ++++++ main.c | 454 ++++++++++++++++++++++++++++++++++++++++++++++ texture_map.c | 144 +++++++++++++++ texture_map.h | 14 ++ texture_map.tga | Bin 0 -> 65580 bytes 10 files changed, 754 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 compile_flags.txt create mode 100644 linalg.c create mode 100644 linalg.h create mode 100644 main.c create mode 100644 texture_map.c create mode 100644 texture_map.h create mode 100644 texture_map.tga diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..d04c6be --- /dev/null +++ b/.clang-format @@ -0,0 +1,19 @@ +Language: Cpp +BasedOnStyle: WebKit +IndentWidth: 4 +ColumnLimit: 80 +IndentCaseLabels: true +InsertNewlineAtEOF: true +AllowShortFunctionsOnASingleLine: None + +AlignAfterOpenBracket: AlwaysBreak + +BinPackArguments: false +AllowAllArgumentsOnNextLine: true + +# BinPackParameters: OnePerLine # llvm >= 20 +BinPackParameters: true +AllowAllParametersOfDeclarationOnNextLine: true +PenaltyReturnTypeOnItsOwnLine: 1000 # abitrary, seems to work + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dc22e61 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +game diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e998957 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ + +CFLAGS = -std=c23 +CFLAGS += -fsanitize=address,undefined -g +CFLAGS += -Wall -Wextra -Wpedantic -Wconversion -pedantic -pedantic-errors + +LFLAGS = -lm + +CFLAGS += $(shell pkg-config sdl2 --cflags) +LFLAGS += $(shell pkg-config sdl2 --libs) + +CFILES = $(shell find . -name "*.c") +HFILES = $(shell find . -name "*.h") + +game: $(CFILES) $(HFILES) + gcc -o $@ $(CFILES) $(CFLAGS) $(LFLAGS) + +clean: + rm -rf game + + diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000..7672925 --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1,10 @@ +-xc +-std=c23 +-Wall +-Wextra +-Wpedantic +-Wconversion +-pedantic +-pedantic-errors +-Wno-unused-variable + diff --git a/linalg.c b/linalg.c new file mode 100644 index 0000000..7faab5f --- /dev/null +++ b/linalg.c @@ -0,0 +1,33 @@ +#include "linalg.h" + +f2 f2_from_f3(f3 v) +{ + return (f2) { v.x, v.y }; +} + +i2 i2_from_f2(f2 v) +{ + return (i2) { (int)v.x, (int)v.y }; +} + +f3 add_f3(f3 l, f3 r) +{ + return (f3) { l.x + r.x, l.y + r.y, l.z + r.z }; +} + +f2 mul_f2_f2x2(f2 l, f2x2 r) +{ + return (f2) { + l.x * r.xx + l.y * r.xy, + l.x * r.yx + l.y * r.yy, + }; +} + +f3 mul_f3_f3x3(f3 l, f3x3 r) +{ + return (f3) { + l.x * r.xx + l.y * r.xy + l.z * r.xz, + l.x * r.yx + l.y * r.yy + l.z * r.yz, + l.x * r.zx + l.y * r.zy + l.z * r.zz, + }; +} diff --git a/linalg.h b/linalg.h new file mode 100644 index 0000000..8769133 --- /dev/null +++ b/linalg.h @@ -0,0 +1,59 @@ +#ifndef LINALG_H +#define LINALG_H + +typedef struct { + double x; + double y; + double z; +} f3; + +typedef struct { + double xx; + double xy; + double xz; + double yx; + double yy; + double yz; + double zx; + double zy; + double zz; +} f3x3; + +typedef struct { + double x; + double y; +} f2; + +typedef struct { + double xx; + double xy; + double yx; + double yy; +} f2x2; + +typedef struct { + f2 p0; + f2 p1; + f2 p2; +} f2x3; + +typedef struct { + int x; + int y; +} i2; + +typedef struct { + i2 p0; + i2 p1; + i2 p2; +} i2x3; + +f2 f2_from_f3(f3 v); +i2 i2_from_f2(f2 v); + +f3 add_f3(f3 l, f3 r); + +f2 mul_f2_f2x2(f2 l, f2x2 r); +f3 mul_f3_f3x3(f3 l, f3x3 r); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..3140d45 --- /dev/null +++ b/main.c @@ -0,0 +1,454 @@ +#include "linalg.h" +#include "texture_map.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const f3 verts[] = { + // clang-format off + { 0, 0, 0 }, // 0 front top left + { 1, 0, 0 }, // 1 front top right + { 0, 1, 0 }, // 2 front bottom left + { 1, 1, 0 }, // 3 front bottom right + { 0, 0, 1 }, // 4 back top left + { 1, 0, 1 }, // 5 back top right + { 0, 1, 1 }, // 6 back bottom left + { 1, 1, 1 }, // 7 back bottom right + // clang-format on +}; + +const size_t shapes[][3] = { + // back + { 0, 1, 2 }, + { 3, 2, 1 }, + // bottom + { 4, 5, 0 }, + { 1, 0, 5 }, + // left + { 4, 0, 6 }, + { 2, 6, 0 }, + // front + { 5, 4, 7 }, + { 6, 7, 4 }, + // top + { 7, 6, 3 }, + { 2, 3, 6 }, + // right + { 1, 5, 3 }, + { 7, 3, 5 }, +}; + +constexpr size_t verts_size = sizeof(verts) / sizeof(verts[0]); +constexpr size_t shapes_size = sizeof(shapes) / sizeof(shapes[0]); + +constexpr int screen_width = 1024; +constexpr int screen_height = 1024; + +constexpr size_t colors_size = 32; +uint32_t colors[colors_size] = { 0 }; + +constexpr size_t textures_size = 1; +uint32_t textures[textures_size][64]; + +const f2 verts_uv_map[verts_size] = { + // clang-format off + { 0, 0 }, + { 1, 0 }, + { 0, 1 }, + { 1, 1 }, + { 0, 0 }, + { 1, 0 }, + { 0, 1 }, + { 1, 1 }, + // clang-format on +}; + +const size_t shapes_textures_map[shapes_size] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +#ifndef M_PI +#define M_PI (3.141529653578) +#endif + +double pi(double v) +{ + return v * M_PI; +} + +#define MAX(L, R) ((L) >= (R) ? (L) : (R)) +#define MIN(L, R) ((L) < (R) ? (L) : (R)) + +void set_px(uint32_t* pixels, int x, int y, uint32_t color) +{ + if ((uint32_t)x >= (size_t)screen_width + || (uint32_t)y >= (size_t)screen_height) + return; + + pixels[y * screen_width + x] = color; +} + +void draw_line_impl( + uint32_t* pixels, int x0, int y0, int x1, int y1, uint32_t color, + bool steep) +{ + if (abs(x0 - x1) < abs(y0 - y1)) { + draw_line_impl(pixels, y0, x0, y1, x1, color, true); + return; + } + if (x0 > x1) { + draw_line_impl(pixels, x1, y1, x0, y0, color, steep); + return; + } + int dx = x1 - x0; + int dy = y1 - y0; + int derror2 = abs(dy) * 2; + int error2 = 0; + int y = y0; + for (int x = x0; x <= x1; ++x) { + if (steep) { + set_px(pixels, y, x, color); + } else { + set_px(pixels, x, y, color); + } + error2 += derror2; + if (error2 > dx) { + y += y1 > y0 ? 1 : -1; + error2 -= dx * 2; + } + } +} + +void draw_line(uint32_t* pixels, i2 p0, i2 p1, uint32_t color) +{ + draw_line_impl(pixels, p0.x, p0.y, p1.x, p1.y, color, false); +} + +void draw_triangle_lines(uint32_t* pixels, i2x3 tri, uint32_t color) +{ + draw_line(pixels, tri.p0, tri.p1, color); + draw_line(pixels, tri.p1, tri.p2, color); + draw_line(pixels, tri.p2, tri.p0, color); +} + +double signed_triangle_area(int x0, int y0, int x1, int y1, int x2, int y2) +{ + return 0.5 + * ((y1 - y0) * (x1 + x0) + (y2 - y1) * (x2 + x1) + + (y0 - y2) * (x0 + x2)); +} + +void draw_triangle_filled(uint32_t* pixels, i2x3 tri, uint32_t color) +{ + int x0 = tri.p0.x; + int y0 = tri.p0.y; + int x1 = tri.p1.x; + int y1 = tri.p1.y; + int x2 = tri.p2.x; + int y2 = tri.p2.y; + + int rect_x0 = MIN(MIN(x0, x1), x2); + int rect_y0 = MIN(MIN(y0, y1), y2); + + int rect_x1 = MAX(MAX(x0, x1), x2); + int rect_y1 = MAX(MAX(y0, y1), y2); + + int rect_w = rect_x1 - rect_x0; + int rect_h = rect_y1 - rect_y0; + + if (rect_w == 0 || rect_h == 0) + return; + + double area = signed_triangle_area(x0, y0, x1, y1, x2, y2); + + for (int y = rect_y0; y <= rect_y1; ++y) { + for (int x = rect_x0; x <= rect_x1; ++x) { + double a0 = signed_triangle_area(x, y, x1, y1, x2, y2) / area; + double a1 = signed_triangle_area(x, y, x2, y2, x0, y0) / area; + double a2 = signed_triangle_area(x, y, x0, y0, x1, y1) / area; + + if (a0 < 0 || a1 < 0 || a2 < 0) + continue; + + set_px(pixels, x, y, color); + } + } +} + +f3 rotate_x(f3 v, double angle) +{ + f3x3 rotation_mx = { + // clang-format off + 1, 0, 0, + 0, cos(angle), -sin(angle), + 0, sin(angle), cos(angle), + // clang-format on + }; + return mul_f3_f3x3(v, rotation_mx); +} + +f3 rotate_y(f3 v, double angle) +{ + f3x3 rotation_mx = { + // clang-format off + cos(angle), 0, sin(angle), + 0, 1, 0, + -sin(angle), 0, cos(angle), + // clang-format on + }; + return mul_f3_f3x3(v, rotation_mx); +} + +f3 rotate_z(f3 v, double angle) +{ + f3x3 rotation_mx = { + // clang-format off + cos(angle), -sin(angle), 0, + sin(angle), cos(angle), 0, + 0, 0, 1, + // clang-format on + }; + return mul_f3_f3x3(v, rotation_mx); +} + +f3 translate(f3 v, double x, double y, double z) +{ + return add_f3(v, (f3) { x, y, z }); +} + +f2 f3_project(f3 v, double focal_length) +{ + focal_length *= -1; + return (f2) { + (v.x * focal_length) / (v.z + focal_length), + (-v.y * focal_length) / (v.z + focal_length), + }; +} + +i2 f2_to_screen_i2(f2 v) +{ + return (i2) { + (int)(v.x * (screen_width / 4.0) + screen_width / 2.0), + (int)(v.y * (screen_width / 4.0) + screen_width / 2.0), + }; +} + +f2 f2_to_screen(f2 v) +{ + return (f2) { + v.x * (screen_width / 4.0) + screen_width / 2.0, + v.y * (screen_width / 4.0) + screen_width / 2.0, + }; +} + +void draw_shape(uint32_t* pixels, i2x3 verts, uint32_t color) +{ + double cross_z = (verts.p1.x - verts.p0.x) * (verts.p2.y - verts.p0.y) + - (verts.p1.y - verts.p0.y) * (verts.p2.x - verts.p0.x); + if (cross_z <= 0) + return; + + draw_triangle_filled(pixels, verts, color); + // draw_triangle_lines(pixels, verts, 0xff000000); +} + +double f2x3_signed_area(f2x3 s) +{ + return 0.5 + * ((s.p1.y - s.p0.y) * (s.p1.x + s.p0.x) + + (s.p2.y - s.p1.y) * (s.p2.x + s.p1.x) + + (s.p0.y - s.p2.y) * (s.p0.x + s.p2.x)); +} + +void draw_shape_with_texture(uint32_t* pixels, size_t shape_idx, f2x3 s) +{ + double cross_z = (s.p1.x - s.p0.x) * (s.p2.y - s.p0.y) + - (s.p1.y - s.p0.y) * (s.p2.x - s.p0.x); + if (cross_z <= 0) + return; + + double rect_x0 = MIN(MIN(s.p0.x, s.p1.x), s.p2.x); + double rect_y0 = MIN(MIN(s.p0.y, s.p1.y), s.p2.y); + + double rect_x1 = MAX(MAX(s.p0.x, s.p1.x), s.p2.x); + double rect_y1 = MAX(MAX(s.p0.y, s.p1.y), s.p2.y); + + double rect_w = rect_x1 - rect_x0; + double rect_h = rect_y1 - rect_y0; + + if (rect_w == 0 || rect_h == 0) + return; + + double area = f2x3_signed_area(s); + + for (int y = (int)rect_y0; y <= rect_y1; ++y) { + for (int x = (int)rect_x0; x <= rect_x1; ++x) { + f2 p = { x, y }; + double a0 = f2x3_signed_area((f2x3) { p, s.p1, s.p2 }) / area; + double a1 = f2x3_signed_area((f2x3) { p, s.p2, s.p0 }) / area; + double a2 = f2x3_signed_area((f2x3) { p, s.p0, s.p1 }) / area; + + if (a0 < 0 || a1 < 0 || a2 < 0) + continue; + + f2 uv = { x / rect_x1, y / rect_y1 }; + + // uint32_t color + // = (uint32_t)(0xff * uv.x) << 0 | (uint32_t)(0xff * uv.y) << + // 8; + // uint32_t color = colors[shape_idx]; + uint32_t color + = textures[0][MIN((size_t)(uv.y * 8 * 8 + uv.x * 8), 63)]; + + set_px(pixels, x, y, 0xff000000 | color); + } + } +} + +void render(uint32_t* pixels, uint64_t total_ms) +{ + double time_frac = total_ms % 4000 / 4000.0; + + i2 screen_verts_i2[verts_size]; + f2 screen_verts[verts_size]; + for (size_t i = 0; i < verts_size; ++i) { + f3 v = verts[i]; + + v = translate(v, -0.5, -0.5, -0.5); + + // v = rotate_y(v, pi(2) * time_frac); + + v = rotate_x(v, M_PI * 2 * time_frac); + v = rotate_y(v, M_PI * 2 * time_frac); + v = rotate_z(v, M_PI * 2 * time_frac); + + v = translate(v, -1, -1, 0); + v = translate(v, 10 * time_frac, 10 * time_frac, -20 * time_frac); + + screen_verts_i2[i] = f2_to_screen_i2(f3_project(v, 4.0)); + screen_verts[i] = f2_to_screen(f3_project(v, 4.0)); + } + + for (size_t shape_idx = 0; shape_idx < shapes_size; ++shape_idx) { + + i2x3 shape_verts_i2; + f2x3 shape_verts; + for (size_t vert_idx = 0; vert_idx < 3; ++vert_idx) { + ((i2*)&shape_verts_i2)[vert_idx] + = screen_verts_i2[shapes[shape_idx][vert_idx]]; + ((f2*)&shape_verts)[vert_idx] + = screen_verts[shapes[shape_idx][vert_idx]]; + } + +#ifdef DRAW_TEXTURE + draw_shape_with_texture(pixels, shape_idx, shape_verts); +#elifdef DRAW_LINES + draw_triangle_lines(pixels, shape_verts_i2, 0xffffffff); +#else + draw_shape(pixels, shape_verts_i2, colors[shape_idx]); +#endif + } +} + +int main(void) +{ + srand((unsigned int)time(nullptr)); + + TextureMap* texture_map = texture_map_new("texture_map.tga"); + + for (size_t i = 0; i < textures_size; ++i) { + texture_map_read_texture(texture_map, textures[i], (int)i); + } + + texture_map_free(texture_map); + + for (size_t i = 0; i < colors_size; ++i) { + uint32_t color = (uint32_t)((double)rand() / RAND_MAX * 0xffffff); + colors[i] = 0xffu << 24 | color; + } + + SDL_Init(SDL_INIT_VIDEO); + + SDL_Window* window; + SDL_Renderer* renderer; + + SDL_CreateWindowAndRenderer( + screen_width, screen_height, 0, &window, &renderer); + + SDL_Texture* buffer = SDL_CreateTexture( + renderer, + SDL_PIXELFORMAT_RGBA32, + SDL_TEXTUREACCESS_STREAMING, + screen_width, + screen_height); + + SDL_SetTextureBlendMode(buffer, SDL_BLENDMODE_BLEND); + + uint64_t before = SDL_GetTicks64(); + + while (true) { + uint64_t now = SDL_GetTicks64(); + uint64_t delta = now - before; + (void)delta; + + SDL_Event event; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + goto exit_game; + case SDL_KEYDOWN: { + switch (event.key.keysym.sym) { + case SDLK_ESCAPE: + goto exit_game; + } + } + } + } + + SDL_RenderClear(renderer); + + uint32_t* pixels; + int row_size; + SDL_LockTexture( + buffer, + &(SDL_Rect) { 0, 0, screen_width, screen_height }, + (void**)&pixels, + &row_size); + row_size /= (int)sizeof(uint32_t); + + memset( + pixels, + 0, + sizeof(int) * (size_t)screen_width * (size_t)screen_height); + + render(pixels, now); + + SDL_UnlockTexture(buffer); + + SDL_RenderCopy(renderer, buffer, nullptr, nullptr); + SDL_RenderPresent(renderer); + SDL_Delay(16); + + before = now; + } + +exit_game:; + printf("exitting...\n"); + + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); +} diff --git a/texture_map.c b/texture_map.c new file mode 100644 index 0000000..3b17344 --- /dev/null +++ b/texture_map.c @@ -0,0 +1,144 @@ +#include "texture_map.h" +#include +#include +#include +#include +#include +#include + +struct TextureMap { + uint32_t* data; + size_t width; + size_t height; +}; + +TextureMap* texture_map_new(const char* filename) +{ + TextureMap* result; + + FILE* file = fopen(filename, "rb"); + if (!file) { + fprintf( + stderr, + "error: could not open texture map file \"%s\": %s\n", + filename, + strerror(errno)); + return NULL; + } + + constexpr size_t header_size = 18; + uint8_t header[header_size]; + size_t header_read = fread(header, 1, 18, file); + if (header_read != header_size) { + fprintf( + stderr, + "error: could not read texture map TGA header of file \"%s\"\n", + filename); + + result = nullptr; + goto exit_close_file; + } + + const uint8_t* header_iter = header; + + uint8_t id_length = *header_iter++; + uint8_t color_map_type = *header_iter++; + uint8_t image_type = *header_iter++; + uint8_t cm_spec[5]; + for (size_t i = 0; i < 5; ++i) + cm_spec[i] = *header_iter++; + uint8_t image_spec[10]; + for (size_t i = 0; i < 10; ++i) + image_spec[i] = *header_iter++; + + uint16_t first_entry_index = (uint16_t)(cm_spec[1] << 8 | cm_spec[0]); + uint16_t cm_length = (cm_spec[2] << 8 | cm_spec[3]) & 0xffff; + uint8_t cm_entry_size = cm_spec[4]; + + uint16_t x_origin = (uint16_t)(image_spec[1] << 8 | image_spec[0]); + uint16_t y_origin = (uint16_t)(image_spec[3] << 8 | image_spec[2]); + uint16_t image_width = (uint16_t)(image_spec[5] << 8 | image_spec[4]); + uint16_t image_height = (uint16_t)(image_spec[7] << 8 | image_spec[6]); + uint8_t pixel_depth = image_spec[8]; + uint8_t image_desc = image_spec[9]; + + uint8_t alpha_channel_depth = image_desc & 0b1111; + bool right_to_left_ordering = image_desc >> 4 & 0x1; + bool top_to_bottom_ordering = image_desc >> 5 & 0x1; + + if (!(id_length == 0 && color_map_type == 0 && image_type == 2 + && first_entry_index == 0 && cm_length == 0 && cm_entry_size == 0 + && x_origin == 0 && y_origin == 128 && image_width == 128 + && image_height == 128 && pixel_depth == 32 && image_desc == 40 + && alpha_channel_depth == 8 && right_to_left_ordering == 0 + && top_to_bottom_ordering == 1)) { + fprintf(stderr, "error: invalid texture map \"%s\"\n", filename); + printf("id length: %d\n", id_length); + printf("color map type: %d\n", color_map_type); + printf("image type: %d\n", image_type); + printf("first entry index: %d\n", first_entry_index); + printf("color map length: %d\n", cm_length); + printf("color map entry size: %d\n", cm_entry_size); + printf("x_origin: %d\n", x_origin); + printf("y_origin: %d\n", y_origin); + printf("image_width: %d\n", image_width); + printf("image_height: %d\n", image_height); + printf("pixel_depth: %d\n", pixel_depth); + printf("image_desc: %d\n", image_desc); + printf("alpha_channel_depth: %d\n", alpha_channel_depth); + printf("right_to_left_ordering: %d\n", right_to_left_ordering); + printf("top_to_bottom_ordering: %d\n", top_to_bottom_ordering); + + result = nullptr; + goto exit_close_file; + } + + uint32_t* data = malloc(sizeof(uint32_t) * image_width * image_height); + size_t data_read + = fread(data, sizeof(uint32_t), image_width * image_height, file); + if (data_read != image_width * image_height) { + printf("data_bytes_read: %ld\n", data_read); + printf( + "expected: %ld\n", sizeof(uint32_t) * image_width * image_height); + fprintf( + stderr, + "error: could not read texture map TGA data of file \"%s\"\n", + filename); + + result = nullptr; + goto exit_free_data; + } + + result = malloc(sizeof(TextureMap)); + *result = (TextureMap) { + .data = data, + .width = image_width, + .height = image_height, + }; + + fclose(file); + return result; + +exit_free_data: + free(data); +exit_close_file: + fclose(file); + return result; +} + +void texture_map_free(TextureMap* map) +{ + free(map->data); + free(map); +} + +void texture_map_read_texture(TextureMap* map, uint32_t* data, int id) +{ + for (size_t y = 0; y < 8; ++y) { + for (size_t x = 0; x < 8; ++x) { + size_t oy = (size_t)(id / 8) + y; + size_t ox = (size_t)(id % 8) + x; + data[y * 8 + x] = map->data[oy * map->width + ox]; + } + } +} diff --git a/texture_map.h b/texture_map.h new file mode 100644 index 0000000..4230ae9 --- /dev/null +++ b/texture_map.h @@ -0,0 +1,14 @@ +#ifndef TEXTURE_MAP_H +#define TEXTURE_MAP_H + +#include "linalg.h" +#include + +typedef struct TextureMap TextureMap; + +TextureMap* texture_map_new(const char* filename); +void texture_map_free(TextureMap* map); + +void texture_map_read_texture(TextureMap* map, uint32_t* data, int id); + +#endif diff --git a/texture_map.tga b/texture_map.tga new file mode 100644 index 0000000000000000000000000000000000000000..9e59455c7a0f352f7017e53bf15051c54f1af5d5 GIT binary patch literal 65580 zcmeI!K}y3w6b9hIlel!Fx2O;h4I0H$1bPE+uAQ0(A&bc-i}L168D27>ZQl1!NJ_aY z|JzTgpZkaTJpb;DQ2KJ)ATwqEA?21?%jI{$d@i@KakF3@*-k$k literal 0 HcmV?d00001