From 2d1ae9578b037a9bdb911c3dc713fed6890e206a Mon Sep 17 00:00:00 2001 From: asrael Date: Sun, 5 Oct 2025 16:25:17 -0500 Subject: [PATCH] refactor atlas implementation --- demo/cube3d.fnl | 77 ++++- demo/main.fnl | 17 +- demo/palettes/nes.ase | Bin 0 -> 554 bytes src/lua/pxl8.lua | 28 ++ src/pxl8.c | 5 - src/pxl8_ase.c | 27 +- src/pxl8_gfx.c | 783 +++++++++++++++++++++++++++++++++--------- src/pxl8_gfx.h | 7 +- src/pxl8_math.c | 57 +-- src/pxl8_script.c | 4 + src/pxl8_vfx.c | 71 ++-- src/pxl8_vfx.h | 10 +- 12 files changed, 825 insertions(+), 261 deletions(-) create mode 100644 demo/palettes/nes.ase diff --git a/demo/cube3d.fnl b/demo/cube3d.fnl index 859e8f7..e9d5650 100644 --- a/demo/cube3d.fnl +++ b/demo/cube3d.fnl @@ -8,6 +8,17 @@ (var wireframe true) (var time 0) (var zoom 5.0) +(var texture-id nil) +(var use-texture false) +(var affine false) +(var texture-initialized false) + +(fn init-texture [] + (when (not texture-initialized) + (pxl8.load_palette "sprites/pxl8_logo.ase") + (set texture-id (pxl8.load_sprite "sprites/pxl8_logo.ase")) + (pxl8.upload_atlas) + (set texture-initialized true))) (fn make-cube-vertices [] [[-1 -1 -1] [1 -1 -1] [1 1 -1] [-1 1 -1] @@ -21,6 +32,20 @@ [3 2 6] [3 6 7] [4 5 1] [4 1 0]]) +(fn make-cube-faces-with-uvs [] + [{:tri [0 1 2] :uvs [[0 0] [1 0] [1 1]]} + {:tri [0 2 3] :uvs [[0 0] [1 1] [0 1]]} + {:tri [1 5 6] :uvs [[0 0] [1 0] [1 1]]} + {:tri [1 6 2] :uvs [[0 0] [1 1] [0 1]]} + {:tri [5 4 7] :uvs [[0 0] [1 0] [1 1]]} + {:tri [5 7 6] :uvs [[0 0] [1 1] [0 1]]} + {:tri [4 0 3] :uvs [[0 0] [1 0] [1 1]]} + {:tri [4 3 7] :uvs [[0 0] [1 1] [0 1]]} + {:tri [3 2 6] :uvs [[0 0] [1 0] [1 1]]} + {:tri [3 6 7] :uvs [[0 0] [1 1] [0 1]]} + {:tri [4 5 1] :uvs [[0 0] [1 0] [1 1]]} + {:tri [4 1 0] :uvs [[0 0] [1 1] [0 1]]}]) + (fn get-face-color [face-idx] (let [colors [12 22 30 16 28 20]] (. colors (+ 1 (% face-idx 6))))) @@ -43,10 +68,16 @@ (when (pxl8.key_pressed " ") (set wireframe (not wireframe))) - (when (pxl8.key_pressed "r") - (set auto-rotate (not auto-rotate))) + (when (pxl8.key_pressed "f") + (set affine (not affine))) (when (pxl8.key_pressed "p") (set orthographic (not orthographic))) + (when (pxl8.key_pressed "r") + (set auto-rotate (not auto-rotate))) + (when (pxl8.key_pressed "t") + (set use-texture (not use-texture)) + (when use-texture + (init-texture))) (when (pxl8.key_down "=") (set zoom (- zoom (* dt 2.0)))) @@ -63,8 +94,9 @@ (pxl8.clr 0) (pxl8.clear_zbuffer) - (pxl8.set_wireframe wireframe) + (pxl8.set_affine_textures affine) (pxl8.set_backface_culling true) + (pxl8.set_wireframe wireframe) (if orthographic (let [size (* 2.5 (/ zoom 5.0)) @@ -84,15 +116,36 @@ (pxl8.mat4_multiply (pxl8.mat4_rotate_z angle-z)))] (pxl8.set_model model)) - (let [vertices (make-cube-vertices) - faces (make-cube-faces)] - (each [i face (ipairs faces)] - (let [[i0 i1 i2] face - v0 (. vertices (+ 1 i0)) - v1 (. vertices (+ 1 i1)) - v2 (. vertices (+ 1 i2)) - color (get-face-color (math.floor (/ (- i 1) 2)))] - (pxl8.draw_triangle_3d v0 v1 v2 color))))) + (let [vertices (make-cube-vertices)] + (if (and use-texture texture-id) + (let [faces (make-cube-faces-with-uvs)] + (each [i face-data (ipairs faces)] + (let [tri-indices face-data.tri + tri-uvs face-data.uvs + v0 (. vertices (+ 1 (. tri-indices 1))) + v1 (. vertices (+ 1 (. tri-indices 2))) + v2 (. vertices (+ 1 (. tri-indices 3))) + uv0 (. tri-uvs 1) + uv1 (. tri-uvs 2) + uv2 (. tri-uvs 3)] + (pxl8.draw_triangle_3d_textured + v0 v1 v2 + uv0 uv1 uv2 + texture-id)))) + (let [faces (make-cube-faces)] + (each [i face (ipairs faces)] + (let [[i0 i1 i2] face + v0 (. vertices (+ 1 i0)) + v1 (. vertices (+ 1 i1)) + v2 (. vertices (+ 1 i2)) + color (get-face-color (math.floor (/ (- i 1) 2)))] + (pxl8.draw_triangle_3d v0 v1 v2 color)))))) + + (pxl8.text "WASD/QE: Rotate | +/-: Zoom | Space: Wire | R: Auto | P: Proj" 5 5 15) + (pxl8.text (.. "T: Texture | F: Affine | Mode: " + (if wireframe "Wire" "Fill") + " | Texture: " (if use-texture "On" "Off") + " | Mapping: " (if affine "Affine" "Persp")) 5 15 15)) {:update cube-update :frame cube-frame} diff --git a/demo/main.fnl b/demo/main.fnl index 29119bd..d002ac7 100644 --- a/demo/main.fnl +++ b/demo/main.fnl @@ -7,6 +7,7 @@ (var fire-init false) (var rain-init false) (var snow-init false) +(var use-nes-palette false) (var logo-x 256) (var logo-y 148) @@ -15,7 +16,7 @@ (var logo-sprite nil) (global init (fn [] - (pxl8.load_palette "palettes/gruvbox.ase") + (pxl8.load_palette "sprites/pxl8_logo.ase") (set logo-sprite (pxl8.load_sprite "sprites/pxl8_logo.ase")) (set particles (pxl8.particles_new 1000)))) @@ -41,6 +42,12 @@ (set snow-init false)) (when (pxl8.key_pressed "8") (set current-effect 8)) + (when (pxl8.key_pressed "9") + (set use-nes-palette (not use-nes-palette)) + (local palette-path (if use-nes-palette "palettes/nes.ase" "sprites/pxl8_logo.ase")) + (print (.. "Switching to palette: " palette-path)) + (pxl8.load_palette palette-path) + (print "Palette loaded")) (case current-effect 1 (do @@ -63,15 +70,15 @@ (when logo-sprite (pxl8.sprite logo-sprite logo-x logo-y 128 64))) - 2 (pxl8.vfx_plasma time 0.10 0.04 0) + 2 (pxl8.vfx_plasma time 0.10 0.04 1) 3 (pxl8.vfx_tunnel time 2.0 0.25) 4 (do (pxl8.clr 0) - (local bars [{:base_y 60 :amplitude 30 :height 16 :speed 2.0 :phase 0 :color 20 :fade_color 10} - {:base_y 180 :amplitude 35 :height 16 :speed 1.8 :phase 2.0 :color 26 :fade_color 10} - {:base_y 300 :amplitude 25 :height 16 :speed 2.2 :phase 4.0 :color 14 :fade_color 10}]) + (local bars [{:base_y 60 :amplitude 30 :height 16 :speed 2.0 :phase 0 :color 1 :fade_color 18} + {:base_y 180 :amplitude 35 :height 16 :speed 1.8 :phase 2.0 :color 1 :fade_color 27} + {:base_y 300 :amplitude 25 :height 16 :speed 2.2 :phase 4.0 :color 1 :fade_color 24}]) (pxl8.vfx_raster_bars bars time)) 5 (do diff --git a/demo/palettes/nes.ase b/demo/palettes/nes.ase new file mode 100644 index 0000000000000000000000000000000000000000..76de1cadadacf57ad31933048cc113692ef3f403 GIT binary patch literal 554 zcmcJ~p-#j=7zE%c3R!^wcaorx2ofs#00_hiB%+V7q7u;uKr|@CU9ur3POhln0T9TQ zCn}KOHl#_J`8iHw*d|{mvwwE8X9vJ`RiNZ|2(3HwmkLFpto{F3&piF>bDnjpE_x&< zi)we()$YIl&6f6h@0r`SrNc1LoHL!KiPooT9&k^aT1~<$?Y(BQnWAMgV8MQb)2{2~ z@`Z*H$`_{B?#J7Pc1LQ)+mcRCwcG}JeXf4p+I%#VX9KT{#(>T*wGPHNswdoS--1HBsdI*u}54(gYqY&sc#UY)ed^(VgpNw%PQ literal 0 HcmV?d00001 diff --git a/src/lua/pxl8.lua b/src/lua/pxl8.lua index 289ad4d..786f711 100644 --- a/src/lua/pxl8.lua +++ b/src/lua/pxl8.lua @@ -69,6 +69,22 @@ function pxl8.load_sprite(filepath) end end +function pxl8.create_texture(pixels, width, height) + local pixel_data = ffi.new("u8[?]", width * height) + for i = 0, width * height - 1 do + pixel_data[i] = pixels[i + 1] or 0 + end + local result = C.pxl8_gfx_create_texture(gfx, pixel_data, width, height) + if result < 0 then + return nil + end + return result +end + +function pxl8.upload_atlas() + C.pxl8_gfx_upload_atlas(gfx) +end + -- log function pxl8.info(msg) C.pxl8_lua_info(msg) @@ -267,6 +283,10 @@ function pxl8.set_wireframe(wireframe) C.pxl8_3d_set_wireframe(gfx, wireframe) end +function pxl8.set_affine_textures(affine) + C.pxl8_3d_set_affine_textures(gfx, affine) +end + function pxl8.set_backface_culling(culling) C.pxl8_3d_set_backface_culling(gfx, culling) end @@ -278,6 +298,14 @@ function pxl8.draw_triangle_3d(v0, v1, v2, color) C.pxl8_3d_draw_triangle_raw(gfx, vec0, vec1, vec2, color) end +function pxl8.draw_triangle_3d_textured(v0, v1, v2, uv0, uv1, uv2, texture_id) + local vec0 = ffi.new("pxl8_vec3", {x = v0[1], y = v0[2], z = v0[3]}) + local vec1 = ffi.new("pxl8_vec3", {x = v1[1], y = v1[2], z = v1[3]}) + local vec2 = ffi.new("pxl8_vec3", {x = v2[1], y = v2[2], z = v2[3]}) + C.pxl8_3d_draw_triangle_textured(gfx, vec0, vec1, vec2, + uv0[1], uv0[2], uv1[1], uv1[2], uv2[1], uv2[2], texture_id) +end + function pxl8.draw_line_3d(p0, p1, color) local vec0 = ffi.new("pxl8_vec3", {x = p0[1], y = p0[2], z = p0[3]}) local vec1 = ffi.new("pxl8_vec3", {x = p1[1], y = p1[2], z = p1[3]}) diff --git a/src/pxl8.c b/src/pxl8.c index 565cf87..34f69da 100644 --- a/src/pxl8.c +++ b/src/pxl8.c @@ -245,11 +245,6 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { return SDL_APP_FAILURE; } - if (pxl8_gfx_init_atlas(app.gfx, 1024, 1024) != PXL8_OK) { - pxl8_error("Failed to initialize sprite atlas"); - return SDL_APP_FAILURE; - } - app.ui = pxl8_ui_create(app.gfx); if (!app.ui) { pxl8_error("Failed to create UI"); diff --git a/src/pxl8_ase.c b/src/pxl8_ase.c index ad89582..c1aa236 100644 --- a/src/pxl8_ase.c +++ b/src/pxl8_ase.c @@ -260,16 +260,11 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) { chunk_header.chunk_type = read_u16_le(chunk_data + 4); const u8* chunk_payload = chunk_data + 6; - - pxl8_debug("Found chunk: type=0x%04X, size=%d", chunk_header.chunk_type, chunk_header.chunk_size); + switch (chunk_header.chunk_type) { case PXL8_ASE_CHUNK_OLD_PALETTE: // 0x0004 if (!ase_file->palette.colors) { result = parse_old_palette_chunk(chunk_payload, &ase_file->palette); - pxl8_debug("Parsed old palette: %d colors, indices %d-%d", - ase_file->palette.entry_count, ase_file->palette.first_color, ase_file->palette.last_color); - } else { - pxl8_debug("Ignoring old palette (0x0004) - new palette (0x2019) already loaded"); } break; @@ -284,11 +279,6 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) { result = parse_layer_chunk(chunk_payload, &ase_file->layers[ase_file->layer_count]); if (result == PXL8_OK) { - pxl8_debug("Parsed layer %d: '%s', blend_mode=%d, opacity=%d", - ase_file->layer_count, - ase_file->layers[ase_file->layer_count].name ? ase_file->layers[ase_file->layer_count].name : "(unnamed)", - ase_file->layers[ase_file->layer_count].blend_mode, - ase_file->layers[ase_file->layer_count].opacity); ase_file->layer_count++; } break; @@ -296,14 +286,11 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) { case PXL8_ASE_CHUNK_CEL: { // 0x2005 pxl8_ase_cel cel = {0}; - pxl8_debug("Found CEL chunk: size=%d", chunk_header.chunk_size - 6); result = parse_cel_chunk(chunk_payload, chunk_header.chunk_size - 6, &cel); if (result == PXL8_OK && cel.pixel_data) { - pxl8_debug("CEL data loaded: %dx%d, type=%d, first pixel = %d", cel.width, cel.height, cel.cel_type, cel.pixel_data[0]); u32 copy_width = (cel.width < frame->width) ? cel.width : frame->width; u32 copy_height = (cel.height < frame->height) ? cel.height : frame->height; - - pxl8_debug("Copying cel to frame: cel_pos=(%d,%d), copy_size=%dx%d", cel.x, cel.y, copy_width, copy_height); + for (u32 y = 0; y < copy_height; y++) { u32 src_offset = y * cel.width; u32 dst_offset = (y + cel.y) * frame->width + cel.x; @@ -311,20 +298,16 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) { for (u32 x = 0; x < copy_width; x++) { u8 src_pixel = cel.pixel_data[src_offset + x]; bool is_transparent = false; - + if (src_pixel < ase_file->palette.entry_count && ase_file->palette.colors) { u32 color = ase_file->palette.colors[src_pixel]; is_transparent = ((color >> 24) & 0xFF) == 0; } - + if (!is_transparent) { frame->pixels[dst_offset + x] = src_pixel; } } - if (y < 3) { - pxl8_debug("Row %d: cel[%d]=%d, frame[%d]=%d", - y, src_offset, cel.pixel_data[src_offset], dst_offset, frame->pixels[dst_offset]); - } } } SDL_free(cel.pixel_data); @@ -337,8 +320,6 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) { SDL_free(ase_file->palette.colors); } result = parse_palette_chunk(chunk_payload, &ase_file->palette); - pxl8_debug("Parsed new palette: %d colors, indices %d-%d", - ase_file->palette.entry_count, ase_file->palette.first_color, ase_file->palette.last_color); break; default: diff --git a/src/pxl8_gfx.c b/src/pxl8_gfx.c index 9d6ba8c..758ccda 100644 --- a/src/pxl8_gfx.c +++ b/src/pxl8_gfx.c @@ -38,22 +38,46 @@ pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height) { } typedef struct pxl8_atlas_entry { + bool active; char path[256]; - u32 sprite_id; + u32 texture_id; + i32 x, y, w, h; } pxl8_atlas_entry; +typedef struct pxl8_skyline_node { + i32 x, y, width; +} pxl8_skyline_node; + +typedef struct pxl8_skyline_fit { + bool found; + u32 node_idx; + pxl8_point pos; +} pxl8_skyline_fit; + +struct pxl8_atlas { + u32 height, width; + u8* pixels; + SDL_Texture* texture; + + bool dirty; + + u32 entry_capacity, entry_count; + pxl8_atlas_entry* entries; + + u32 free_capacity, free_count; + u32* free_list; + + u32 skyline_capacity, skyline_count; + pxl8_skyline_node* skyline; +}; + struct pxl8_gfx { SDL_Renderer* renderer; SDL_Texture* framebuffer_texture; - SDL_Texture* sprite_atlas_texture; SDL_Window* window; - u8* atlas; - bool atlas_dirty; - pxl8_atlas_entry* atlas_entries; - u32 atlas_entries_cap; - u32 atlas_entries_len; + pxl8_atlas* atlas; pxl8_color_mode color_mode; u8* framebuffer; @@ -64,12 +88,6 @@ struct pxl8_gfx { u32* palette; u32 palette_size; - u32 sprite_atlas_height; - u32 sprite_atlas_width; - u32 sprite_frame_height; - u32 sprite_frame_width; - u32 sprite_frames_per_row; - pxl8_viewport viewport; bool backface_culling; @@ -80,8 +98,305 @@ struct pxl8_gfx { f32* zbuffer; i32 zbuffer_height; i32 zbuffer_width; + + bool affine_textures; }; +static pxl8_skyline_fit pxl8_skyline_find_position(pxl8_skyline_node* skyline, u32 skyline_count, u32 atlas_w, u32 atlas_h, u32 rect_w, u32 rect_h) { + pxl8_skyline_fit result = {.found = false}; + i32 best_y = INT32_MAX; + i32 best_x = 0; + u32 best_idx = 0; + + for (u32 i = 0; i < skyline_count; i++) { + i32 x = skyline[i].x; + i32 y = skyline[i].y; + + if (x + (i32)rect_w > (i32)atlas_w) continue; + + i32 max_y = y; + for (u32 j = i; j < skyline_count && skyline[j].x < x + (i32)rect_w; j++) { + if (skyline[j].y > max_y) { + max_y = skyline[j].y; + } + } + + if (max_y + (i32)rect_h > (i32)atlas_h) continue; + + if (max_y < best_y || (max_y == best_y && x < best_x)) { + best_y = max_y; + best_x = x; + best_idx = i; + } + } + + if (best_y != INT32_MAX) { + result.found = true; + result.pos.x = best_x; + result.pos.y = best_y; + result.node_idx = best_idx; + } + + return result; +} + +static void pxl8_skyline_add_rect(pxl8_skyline_node** skyline, u32* skyline_count, u32* skyline_capacity, pxl8_point pos, u32 w, u32 h) { + u32 node_idx = 0; + for (u32 i = 0; i < *skyline_count; i++) { + if ((*skyline)[i].x == pos.x) { + node_idx = i; + break; + } + } + + pxl8_skyline_node new_node = {pos.x, pos.y + (i32)h, (i32)w}; + + u32 nodes_to_remove = 0; + for (u32 i = node_idx; i < *skyline_count; i++) { + if ((*skyline)[i].x < pos.x + (i32)w) { + nodes_to_remove++; + } else { + break; + } + } + + if (*skyline_count - nodes_to_remove + 1 > *skyline_capacity) { + *skyline_capacity = (*skyline_count - nodes_to_remove + 1) * 2; + *skyline = (pxl8_skyline_node*)SDL_realloc(*skyline, *skyline_capacity * sizeof(pxl8_skyline_node)); + } + + if (nodes_to_remove > 0) { + SDL_memmove(&(*skyline)[node_idx + 1], &(*skyline)[node_idx + nodes_to_remove], + (*skyline_count - node_idx - nodes_to_remove) * sizeof(pxl8_skyline_node)); + } + + (*skyline)[node_idx] = new_node; + *skyline_count = *skyline_count - nodes_to_remove + 1; +} + +static void pxl8_skyline_compact(pxl8_skyline_node* skyline, u32* skyline_count) { + for (u32 i = 0; i < *skyline_count - 1; ) { + if (skyline[i].y == skyline[i + 1].y) { + skyline[i].width += skyline[i + 1].width; + SDL_memmove(&skyline[i + 1], &skyline[i + 2], + (*skyline_count - i - 2) * sizeof(pxl8_skyline_node)); + (*skyline_count)--; + } else { + i++; + } + } +} + +static pxl8_atlas* pxl8_atlas_create(SDL_Renderer* renderer, u32 width, u32 height, pxl8_color_mode color_mode) { + pxl8_atlas* atlas = (pxl8_atlas*)SDL_calloc(1, sizeof(pxl8_atlas)); + if (!atlas) return NULL; + + atlas->height = height; + atlas->width = width; + + i32 bytes_per_pixel = (color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; + atlas->pixels = (u8*)SDL_calloc(width * height, bytes_per_pixel); + if (!atlas->pixels) { + SDL_free(atlas); + return NULL; + } + + atlas->texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, + SDL_TEXTUREACCESS_STREAMING, width, height); + if (!atlas->texture) { + SDL_free(atlas->pixels); + SDL_free(atlas); + return NULL; + } + + atlas->entry_capacity = 64; + atlas->entries = (pxl8_atlas_entry*)SDL_calloc(atlas->entry_capacity, sizeof(pxl8_atlas_entry)); + if (!atlas->entries) { + SDL_DestroyTexture(atlas->texture); + SDL_free(atlas->pixels); + SDL_free(atlas); + return NULL; + } + + atlas->free_capacity = 16; + atlas->free_list = (u32*)SDL_calloc(atlas->free_capacity, sizeof(u32)); + if (!atlas->free_list) { + SDL_free(atlas->entries); + SDL_DestroyTexture(atlas->texture); + SDL_free(atlas->pixels); + SDL_free(atlas); + return NULL; + } + + atlas->skyline_capacity = 16; + atlas->skyline = (pxl8_skyline_node*)SDL_calloc(atlas->skyline_capacity, sizeof(pxl8_skyline_node)); + if (!atlas->skyline) { + SDL_free(atlas->free_list); + SDL_free(atlas->entries); + SDL_DestroyTexture(atlas->texture); + SDL_free(atlas->pixels); + SDL_free(atlas); + return NULL; + } + + atlas->skyline[0] = (pxl8_skyline_node){0, 0, (i32)width}; + atlas->skyline_count = 1; + + return atlas; +} + +static void pxl8_atlas_destroy(pxl8_atlas* atlas) { + if (!atlas) return; + + SDL_free(atlas->free_list); + SDL_free(atlas->entries); + SDL_free(atlas->skyline); + if (atlas->texture) SDL_DestroyTexture(atlas->texture); + SDL_free(atlas->pixels); + SDL_free(atlas); +} + + +static u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h, const char* path, pxl8_color_mode color_mode); +static bool pxl8_atlas_expand(pxl8_atlas* atlas, SDL_Renderer* renderer, pxl8_color_mode color_mode); + +static u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h, const char* path, pxl8_color_mode color_mode) { + if (!atlas || !pixels) return UINT32_MAX; + + pxl8_skyline_fit fit = pxl8_skyline_find_position(atlas->skyline, atlas->skyline_count, atlas->width, atlas->height, w, h); + if (!fit.found) { + if (!pxl8_atlas_expand(atlas, atlas->texture ? SDL_GetRendererFromTexture(atlas->texture) : NULL, color_mode)) { + return UINT32_MAX; + } + fit = pxl8_skyline_find_position(atlas->skyline, atlas->skyline_count, atlas->width, atlas->height, w, h); + if (!fit.found) return UINT32_MAX; + } + + u32 texture_id; + if (atlas->free_count > 0) { + texture_id = atlas->free_list[--atlas->free_count]; + } else { + if (atlas->entry_count >= atlas->entry_capacity) { + atlas->entry_capacity *= 2; + atlas->entries = (pxl8_atlas_entry*)SDL_realloc(atlas->entries, + atlas->entry_capacity * sizeof(pxl8_atlas_entry)); + } + texture_id = atlas->entry_count++; + } + + pxl8_atlas_entry* entry = &atlas->entries[texture_id]; + entry->active = true; + entry->texture_id = texture_id; + entry->x = fit.pos.x; + entry->y = fit.pos.y; + entry->w = w; + entry->h = h; + + if (path) { + strncpy(entry->path, path, sizeof(entry->path) - 1); + entry->path[sizeof(entry->path) - 1] = '\0'; + } else { + snprintf(entry->path, sizeof(entry->path), "procgen_%u", texture_id); + } + + i32 bytes_per_pixel = (color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; + for (u32 y = 0; y < h; y++) { + for (u32 x = 0; x < w; x++) { + u32 src_idx = y * w + x; + u32 dst_idx = (fit.pos.y + y) * atlas->width + (fit.pos.x + x); + + if (bytes_per_pixel == 4) { + ((u32*)atlas->pixels)[dst_idx] = ((const u32*)pixels)[src_idx]; + } else { + atlas->pixels[dst_idx] = pixels[src_idx]; + } + } + } + + pxl8_skyline_add_rect(&atlas->skyline, &atlas->skyline_count, &atlas->skyline_capacity, fit.pos, w, h); + pxl8_skyline_compact(atlas->skyline, &atlas->skyline_count); + + atlas->dirty = true; + + return texture_id; +} + +static bool pxl8_atlas_expand(pxl8_atlas* atlas, SDL_Renderer* renderer, pxl8_color_mode color_mode) { + if (!atlas || atlas->width >= 4096) return false; + + u32 new_size = atlas->width * 2; + u32 old_width = atlas->width; + i32 bytes_per_pixel = (color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; + + u8* new_pixels = (u8*)SDL_calloc(new_size * new_size, bytes_per_pixel); + if (!new_pixels) return false; + + SDL_Texture* new_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, + SDL_TEXTUREACCESS_STREAMING, new_size, new_size); + if (!new_texture) { + SDL_free(new_pixels); + return false; + } + + pxl8_skyline_node* new_skyline = (pxl8_skyline_node*)SDL_calloc(16, sizeof(pxl8_skyline_node)); + if (!new_skyline) { + SDL_DestroyTexture(new_texture); + SDL_free(new_pixels); + return false; + } + new_skyline[0] = (pxl8_skyline_node){0, 0, (i32)new_size}; + u32 new_skyline_count = 1; + u32 new_skyline_capacity = 16; + + for (u32 i = 0; i < atlas->entry_count; i++) { + if (!atlas->entries[i].active) continue; + + pxl8_skyline_fit fit = pxl8_skyline_find_position(new_skyline, new_skyline_count, new_size, new_size, + atlas->entries[i].w, atlas->entries[i].h); + if (!fit.found) { + SDL_free(new_skyline); + SDL_DestroyTexture(new_texture); + SDL_free(new_pixels); + return false; + } + + for (u32 y = 0; y < (u32)atlas->entries[i].h; y++) { + for (u32 x = 0; x < (u32)atlas->entries[i].w; x++) { + u32 src_idx = (atlas->entries[i].y + y) * old_width + (atlas->entries[i].x + x); + u32 dst_idx = (fit.pos.y + y) * new_size + (fit.pos.x + x); + if (bytes_per_pixel == 4) { + ((u32*)new_pixels)[dst_idx] = ((u32*)atlas->pixels)[src_idx]; + } else { + new_pixels[dst_idx] = atlas->pixels[src_idx]; + } + } + } + + atlas->entries[i].x = fit.pos.x; + atlas->entries[i].y = fit.pos.y; + + pxl8_skyline_add_rect(&new_skyline, &new_skyline_count, &new_skyline_capacity, fit.pos, + atlas->entries[i].w, atlas->entries[i].h); + pxl8_skyline_compact(new_skyline, &new_skyline_count); + } + + SDL_DestroyTexture(atlas->texture); + SDL_free(atlas->pixels); + SDL_free(atlas->skyline); + + atlas->pixels = new_pixels; + atlas->texture = new_texture; + atlas->skyline = new_skyline; + atlas->skyline_count = new_skyline_count; + atlas->skyline_capacity = new_skyline_capacity; + atlas->width = new_size; + atlas->height = new_size; + atlas->dirty = true; + + pxl8_debug("Atlas expanded to %ux%u", atlas->width, atlas->height); + return true; +} + static u32 pxl8_get_palette_size(pxl8_color_mode mode) { switch (mode) { case PXL8_COLOR_MODE_HICOLOR: return 0; @@ -140,6 +455,10 @@ i32 pxl8_gfx_get_width(const pxl8_gfx* gfx) { return gfx ? gfx->framebuffer_width : 0; } +u32 pxl8_gfx_get_palette_size(const pxl8_gfx* gfx) { + return gfx ? gfx->palette_size : 0; +} + pxl8_gfx* pxl8_gfx_create(pxl8_color_mode mode, pxl8_resolution resolution, const char* title, i32 window_width, i32 window_height) { pxl8_gfx* gfx = (pxl8_gfx*)SDL_calloc(1, sizeof(*gfx)); if (!gfx) { @@ -228,6 +547,7 @@ pxl8_gfx* pxl8_gfx_create(pxl8_color_mode mode, pxl8_resolution resolution, cons gfx->zbuffer = NULL; gfx->zbuffer_height = 0; gfx->zbuffer_width = 0; + gfx->affine_textures = false; gfx->initialized = true; return gfx; @@ -235,165 +555,99 @@ pxl8_gfx* pxl8_gfx_create(pxl8_color_mode mode, pxl8_resolution resolution, cons void pxl8_gfx_destroy(pxl8_gfx* gfx) { if (!gfx) return; - + + pxl8_atlas_destroy(gfx->atlas); + if (gfx->framebuffer_texture) { SDL_DestroyTexture(gfx->framebuffer_texture); gfx->framebuffer_texture = NULL; } - - if (gfx->sprite_atlas_texture) { - SDL_DestroyTexture(gfx->sprite_atlas_texture); - gfx->sprite_atlas_texture = NULL; - } - + if (gfx->renderer) { SDL_DestroyRenderer(gfx->renderer); gfx->renderer = NULL; } - + if (gfx->window) { SDL_DestroyWindow(gfx->window); gfx->window = NULL; } - + SDL_free(gfx->framebuffer); SDL_free(gfx->palette); - SDL_free(gfx->atlas); - SDL_free(gfx->atlas_entries); SDL_free(gfx->zbuffer); SDL_free(gfx); } -pxl8_result pxl8_gfx_init_atlas(pxl8_gfx* gfx, u32 width, u32 height) { - if (!gfx || !gfx->initialized) return PXL8_ERROR_INVALID_ARGUMENT; - - gfx->sprite_atlas_width = width; - gfx->sprite_atlas_height = height; - - i32 bytes_per_pixel = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; - gfx->atlas = (u8*)SDL_calloc(width * height, bytes_per_pixel); + +pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width, u32 height) { + if (!gfx || !gfx->initialized || !pixels) return PXL8_ERROR_INVALID_ARGUMENT; + if (!gfx->atlas) { - return PXL8_ERROR_OUT_OF_MEMORY; + gfx->atlas = pxl8_atlas_create(gfx->renderer, 1024, 1024, gfx->color_mode); + if (!gfx->atlas) return PXL8_ERROR_OUT_OF_MEMORY; } - - gfx->sprite_atlas_texture = SDL_CreateTexture( - gfx->renderer, - SDL_PIXELFORMAT_RGBA32, - SDL_TEXTUREACCESS_STREAMING, - width, - height - ); - - if (!gfx->sprite_atlas_texture) { - SDL_free(gfx->atlas); - gfx->atlas = NULL; - return PXL8_ERROR_SYSTEM_FAILURE; + + u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, pixels, width, height, NULL, gfx->color_mode); + if (texture_id == UINT32_MAX) { + pxl8_error("Texture doesn't fit in atlas"); + return PXL8_ERROR_INVALID_SIZE; } - - gfx->atlas_entries_cap = 256; - gfx->atlas_entries = (pxl8_atlas_entry*)SDL_calloc(gfx->atlas_entries_cap, sizeof(pxl8_atlas_entry)); - if (!gfx->atlas_entries) { - SDL_DestroyTexture(gfx->sprite_atlas_texture); - SDL_free(gfx->atlas); - return PXL8_ERROR_OUT_OF_MEMORY; - } - - gfx->sprite_frames_per_row = width / 128; - if (gfx->sprite_frames_per_row == 0) gfx->sprite_frames_per_row = 1; - - return PXL8_OK; + + pxl8_debug("Created texture %u: %ux%u at (%d,%d)", + texture_id, width, height, + gfx->atlas->entries[texture_id].x, + gfx->atlas->entries[texture_id].y); + + return texture_id; } pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) { if (!gfx || !gfx->initialized || !path) return PXL8_ERROR_INVALID_ARGUMENT; + if (!gfx->atlas) { - pxl8_error("Atlas not initialized"); - return PXL8_ERROR_NOT_INITIALIZED; + gfx->atlas = pxl8_atlas_create(gfx->renderer, 1024, 1024, gfx->color_mode); + if (!gfx->atlas) return PXL8_ERROR_OUT_OF_MEMORY; } - - for (u32 i = 0; i < gfx->atlas_entries_len; i++) { - if (strcmp(gfx->atlas_entries[i].path, path) == 0) { - return PXL8_OK; + + for (u32 i = 0; i < gfx->atlas->entry_count; i++) { + if (gfx->atlas->entries[i].active && strcmp(gfx->atlas->entries[i].path, path) == 0) { + return gfx->atlas->entries[i].texture_id; } } - + pxl8_ase_file ase_file; pxl8_result result = pxl8_ase_load(path, &ase_file); if (result != PXL8_OK) { pxl8_error("Failed to load ASE file: %s", path); return result; } - + if (ase_file.frame_count == 0) { pxl8_error("No frames in ASE file"); pxl8_ase_destroy(&ase_file); return PXL8_ERROR_INVALID_FORMAT; } - + u32 sprite_w = ase_file.header.width; u32 sprite_h = ase_file.header.height; - - if (gfx->sprite_frame_width == 0 || gfx->sprite_frame_height == 0) { - gfx->sprite_frame_width = sprite_w; - gfx->sprite_frame_height = sprite_h; - } - - u32 id = gfx->atlas_entries_len; - u32 frames_per_row = gfx->sprite_frames_per_row; - u32 atlas_x = (id % frames_per_row) * gfx->sprite_frame_width; - u32 atlas_y = (id / frames_per_row) * gfx->sprite_frame_height; - - if (atlas_x + sprite_w > gfx->sprite_atlas_width || - atlas_y + sprite_h > gfx->sprite_atlas_height) { + + u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, ase_file.frames[0].pixels, sprite_w, sprite_h, path, gfx->color_mode); + + pxl8_ase_destroy(&ase_file); + + if (texture_id == UINT32_MAX) { pxl8_error("Sprite doesn't fit in atlas"); - pxl8_ase_destroy(&ase_file); return PXL8_ERROR_INVALID_SIZE; } - - i32 bytes_per_pixel = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; - - for (u32 y = 0; y < sprite_h; y++) { - for (u32 x = 0; x < sprite_w; x++) { - u32 frame_idx = y * sprite_w + x; - u32 atlas_idx = (atlas_y + y) * gfx->sprite_atlas_width + (atlas_x + x); - - if (bytes_per_pixel == 4) { - ((u32*)gfx->atlas)[atlas_idx] = ((u32*)ase_file.frames[0].pixels)[frame_idx]; - } else { - gfx->atlas[atlas_idx] = ase_file.frames[0].pixels[frame_idx]; - } - } - } - - if (gfx->atlas_entries_len >= gfx->atlas_entries_cap) { - gfx->atlas_entries_cap *= 2; - pxl8_atlas_entry* new_entries = (pxl8_atlas_entry*)SDL_realloc( - gfx->atlas_entries, - gfx->atlas_entries_cap * sizeof(pxl8_atlas_entry) - ); - if (!new_entries) { - pxl8_ase_destroy(&ase_file); - return PXL8_ERROR_OUT_OF_MEMORY; - } - gfx->atlas_entries = new_entries; - } - - gfx->atlas_entries[id].x = atlas_x; - gfx->atlas_entries[id].y = atlas_y; - gfx->atlas_entries[id].w = sprite_w; - gfx->atlas_entries[id].h = sprite_h; - gfx->atlas_entries[id].sprite_id = id; - strncpy(gfx->atlas_entries[id].path, path, sizeof(gfx->atlas_entries[id].path) - 1); - gfx->atlas_entries[id].path[sizeof(gfx->atlas_entries[id].path) - 1] = '\0'; - gfx->atlas_entries_len++; - - gfx->atlas_dirty = true; - - pxl8_ase_destroy(&ase_file); - pxl8_debug("Loaded sprite %u: %ux%u at (%u,%u)", id, sprite_w, sprite_h, atlas_x, atlas_y); - - return id; + + pxl8_debug("Loaded sprite %u: %ux%u at (%d,%d)", + texture_id, sprite_w, sprite_h, + gfx->atlas->entries[texture_id].x, + gfx->atlas->entries[texture_id].y); + + return texture_id; } pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path) { @@ -417,18 +671,12 @@ pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path) { u32 copy_size = (ase_file.palette.entry_count < gfx->palette_size) ? ase_file.palette.entry_count : gfx->palette_size; memcpy(gfx->palette, ase_file.palette.colors, copy_size * sizeof(u32)); - - if (gfx->color_mode != PXL8_COLOR_MODE_HICOLOR && gfx->framebuffer_texture) { - SDL_Color colors[256]; - for (u32 i = 0; i < 256 && i < gfx->palette_size; i++) { - colors[i].r = (gfx->palette[i] >> 16) & 0xFF; - colors[i].g = (gfx->palette[i] >> 8) & 0xFF; - colors[i].b = gfx->palette[i] & 0xFF; - colors[i].a = (gfx->palette[i] >> 24) & 0xFF; - } - SDL_SetPaletteColors(SDL_CreateSurfacePalette(NULL), colors, 0, 256); + + u32 last_color = (copy_size > 0) ? gfx->palette[copy_size - 1] : 0xFF000000; + for (u32 i = copy_size; i < gfx->palette_size; i++) { + gfx->palette[i] = last_color; } - + pxl8_ase_destroy(&ase_file); pxl8_debug("Loaded palette with %u colors", copy_size); @@ -475,19 +723,19 @@ void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) { } void pxl8_gfx_upload_atlas(pxl8_gfx* gfx) { - if (!gfx || !gfx->initialized || !gfx->sprite_atlas_texture || !gfx->atlas_dirty) return; + if (!gfx || !gfx->initialized || !gfx->atlas || !gfx->atlas->texture || !gfx->atlas->dirty) return; if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) { - SDL_UpdateTexture(gfx->sprite_atlas_texture, NULL, gfx->atlas, - gfx->sprite_atlas_width * 4); + SDL_UpdateTexture(gfx->atlas->texture, NULL, gfx->atlas->pixels, + gfx->atlas->width * 4); } else { - pxl8_upload_indexed_texture(gfx->sprite_atlas_texture, gfx->atlas, + pxl8_upload_indexed_texture(gfx->atlas->texture, gfx->atlas->pixels, gfx->palette, gfx->palette_size, - gfx->sprite_atlas_width, gfx->sprite_atlas_height, + gfx->atlas->width, gfx->atlas->height, 0x00000000); } - gfx->atlas_dirty = false; + gfx->atlas->dirty = false; pxl8_debug("Atlas uploaded to GPU"); } @@ -703,9 +951,10 @@ void pxl8_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) { void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) { - if (!gfx || !gfx->atlas || !gfx->framebuffer || sprite_id >= gfx->atlas_entries_len) return; - - pxl8_atlas_entry* entry = &gfx->atlas_entries[sprite_id]; + if (!gfx || !gfx->atlas || !gfx->framebuffer || sprite_id >= gfx->atlas->entry_count) return; + if (!gfx->atlas->entries[sprite_id].active) return; + + pxl8_atlas_entry* entry = &gfx->atlas->entries[sprite_id]; i32 clip_left = (x < 0) ? -x : 0; i32 clip_top = (y < 0) ? -y : 0; @@ -724,30 +973,30 @@ void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) { bool is_unclipped = (clip_left == 0 && clip_top == 0 && clip_right == 0 && clip_bottom == 0); if (is_1to1_scale && is_unclipped && pxl8_is_simd_aligned(w)) { - const u8* sprite_data = gfx->atlas + entry->y * gfx->sprite_atlas_width + entry->x; - + const u8* sprite_data = gfx->atlas->pixels + entry->y * gfx->atlas->width + entry->x; + if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) { - pxl8_blit_simd_hicolor((u32*)gfx->framebuffer, gfx->framebuffer_width, - (const u32*)sprite_data, gfx->sprite_atlas_width, x, y, w, h); + pxl8_blit_simd_hicolor((u32*)gfx->framebuffer, gfx->framebuffer_width, + (const u32*)sprite_data, gfx->atlas->width, x, y, w, h); } else { - pxl8_blit_simd_indexed(gfx->framebuffer, gfx->framebuffer_width, - sprite_data, gfx->sprite_atlas_width, x, y, w, h); + pxl8_blit_simd_indexed(gfx->framebuffer, gfx->framebuffer_width, + sprite_data, gfx->atlas->width, x, y, w, h); } } else { for (i32 py = 0; py < draw_height; py++) { for (i32 px = 0; px < draw_width; px++) { i32 src_x = entry->x + ((px + clip_left) * entry->w) / w; i32 src_y = entry->y + ((py + clip_top) * entry->h) / h; - i32 src_idx = src_y * gfx->sprite_atlas_width + src_x; + i32 src_idx = src_y * gfx->atlas->width + src_x; i32 dest_idx = (dest_y + py) * gfx->framebuffer_width + (dest_x + px); - + if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) { - u32 pixel = ((u32*)gfx->atlas)[src_idx]; + u32 pixel = ((u32*)gfx->atlas->pixels)[src_idx]; if (pixel & 0xFF000000) { ((u32*)gfx->framebuffer)[dest_idx] = pixel; } } else { - u8 pixel = gfx->atlas[src_idx]; + u8 pixel = gfx->atlas->pixels[src_idx]; if (pixel != 0) { gfx->framebuffer[dest_idx] = pixel; } @@ -907,6 +1156,11 @@ void pxl8_3d_set_wireframe(pxl8_gfx* gfx, bool wireframe) { gfx->wireframe = wireframe; } +void pxl8_3d_set_affine_textures(pxl8_gfx* gfx, bool affine) { + if (!gfx) return; + gfx->affine_textures = affine; +} + static inline pxl8_vec4 pxl8_transform_vertex(pxl8_gfx* gfx, pxl8_vec3 pos) { pxl8_vec4 v = { .x = pos.x, @@ -971,6 +1225,81 @@ static inline void pxl8_fill_scanline(pxl8_gfx* gfx, i32 y, i32 xs, i32 xe, f32 } } +static inline u32 pxl8_sample_texture(pxl8_gfx* gfx, u32 texture_id, f32 u, f32 v) { + if (!gfx->atlas || texture_id >= gfx->atlas->entry_count) return 0; + if (!gfx->atlas->entries[texture_id].active) return 0; + + pxl8_atlas_entry* entry = &gfx->atlas->entries[texture_id]; + + u = u - floorf(u); + v = v - floorf(v); + + i32 tx = (i32)(u * entry->w) % entry->w; + i32 ty = (i32)(v * entry->h) % entry->h; + + i32 atlas_x = entry->x + tx; + i32 atlas_y = entry->y + ty; + i32 idx = atlas_y * gfx->atlas->width + atlas_x; + + if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) { + return ((u32*)gfx->atlas->pixels)[idx]; + } else { + return gfx->atlas->pixels[idx]; + } +} + +static inline void pxl8_fill_scanline_textured( + pxl8_gfx* gfx, i32 y, i32 xs, i32 xe, + f32 z0, f32 z1, + f32 u0, f32 v0, f32 w0, + f32 u1, f32 v1, f32 w1, + u32 texture_id +) { + if (y < 0 || y >= gfx->framebuffer_height) return; + if (xs > xe) { + i32 tmp = xs; xs = xe; xe = tmp; + f32 tmpf; + tmpf = z0; z0 = z1; z1 = tmpf; + tmpf = u0; u0 = u1; u1 = tmpf; + tmpf = v0; v0 = v1; v1 = tmpf; + tmpf = w0; w0 = w1; w1 = tmpf; + } + + for (i32 x = xs; x <= xe; x++) { + if (x >= 0 && x < gfx->framebuffer_width) { + f32 t = (xe == xs) ? 0.0f : (f32)(x - xs) / (f32)(xe - xs); + f32 z = z0 + t * (z1 - z0); + + i32 idx = y * gfx->zbuffer_width + x; + if (z <= gfx->zbuffer[idx]) { + f32 u = u0 + t * (u1 - u0); + f32 v = v0 + t * (v1 - v0); + + if (!gfx->affine_textures) { + f32 w = w0 + t * (w1 - w0); + if (fabsf(w) > 1e-6f) { + u /= w; + v /= w; + } + } + + u32 color = pxl8_sample_texture(gfx, texture_id, u, v); + if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) { + if (color & 0xFF000000) { + gfx->zbuffer[idx] = z; + pxl8_pixel(gfx, x, y, color); + } + } else { + if (color != 0) { + gfx->zbuffer[idx] = z; + pxl8_pixel(gfx, x, y, color); + } + } + } + } + } +} + static void pxl8_draw_flat_bottom_triangle( pxl8_gfx* gfx, i32 x0, i32 y0, f32 z0, @@ -1087,3 +1416,145 @@ void pxl8_3d_draw_triangle(pxl8_gfx* gfx, pxl8_triangle tri) { pxl8_draw_flat_top_triangle(gfx, x1, y1, z1, x3, y3, z3, x2, y2, z2, color); } } + +typedef struct pxl8_textured_vertex { + i32 x, y; + f32 z, u, v, w; +} pxl8_textured_vertex; + +static void pxl8_draw_flat_bottom_triangle_textured( + pxl8_gfx* gfx, + pxl8_textured_vertex v0, + pxl8_textured_vertex v1, + pxl8_textured_vertex v2, + u32 texture_id +) { + if (v1.y == v0.y) return; + + f32 inv_slope_1 = (f32)(v1.x - v0.x) / (f32)(v1.y - v0.y); + f32 inv_slope_2 = (f32)(v2.x - v0.x) / (f32)(v2.y - v0.y); + f32 inv_slope_z1 = (v1.z - v0.z) / (f32)(v1.y - v0.y); + f32 inv_slope_z2 = (v2.z - v0.z) / (f32)(v2.y - v0.y); + f32 inv_slope_u1 = (v1.u - v0.u) / (f32)(v1.y - v0.y); + f32 inv_slope_u2 = (v2.u - v0.u) / (f32)(v2.y - v0.y); + f32 inv_slope_v1 = (v1.v - v0.v) / (f32)(v1.y - v0.y); + f32 inv_slope_v2 = (v2.v - v0.v) / (f32)(v2.y - v0.y); + f32 inv_slope_w1 = (v1.w - v0.w) / (f32)(v1.y - v0.y); + f32 inv_slope_w2 = (v2.w - v0.w) / (f32)(v2.y - v0.y); + + f32 cur_x1 = (f32)v0.x, cur_x2 = (f32)v0.x; + f32 cur_z1 = v0.z, cur_z2 = v0.z; + f32 cur_u1 = v0.u, cur_u2 = v0.u; + f32 cur_v1 = v0.v, cur_v2 = v0.v; + f32 cur_w1 = v0.w, cur_w2 = v0.w; + + for (i32 y = v0.y; y <= v1.y; y++) { + pxl8_fill_scanline_textured(gfx, y, (i32)cur_x1, (i32)cur_x2, + cur_z1, cur_z2, cur_u1, cur_v1, cur_w1, cur_u2, cur_v2, cur_w2, texture_id); + cur_x1 += inv_slope_1; cur_x2 += inv_slope_2; + cur_z1 += inv_slope_z1; cur_z2 += inv_slope_z2; + cur_u1 += inv_slope_u1; cur_u2 += inv_slope_u2; + cur_v1 += inv_slope_v1; cur_v2 += inv_slope_v2; + cur_w1 += inv_slope_w1; cur_w2 += inv_slope_w2; + } +} + +static void pxl8_draw_flat_top_triangle_textured( + pxl8_gfx* gfx, + pxl8_textured_vertex v0, + pxl8_textured_vertex v1, + pxl8_textured_vertex v2, + u32 texture_id +) { + if (v2.y == v0.y) return; + + f32 inv_slope_1 = (f32)(v2.x - v0.x) / (f32)(v2.y - v0.y); + f32 inv_slope_2 = (f32)(v2.x - v1.x) / (f32)(v2.y - v1.y); + f32 inv_slope_z1 = (v2.z - v0.z) / (f32)(v2.y - v0.y); + f32 inv_slope_z2 = (v2.z - v1.z) / (f32)(v2.y - v1.y); + f32 inv_slope_u1 = (v2.u - v0.u) / (f32)(v2.y - v0.y); + f32 inv_slope_u2 = (v2.u - v1.u) / (f32)(v2.y - v1.y); + f32 inv_slope_v1 = (v2.v - v0.v) / (f32)(v2.y - v0.y); + f32 inv_slope_v2 = (v2.v - v1.v) / (f32)(v2.y - v1.y); + f32 inv_slope_w1 = (v2.w - v0.w) / (f32)(v2.y - v0.y); + f32 inv_slope_w2 = (v2.w - v1.w) / (f32)(v2.y - v1.y); + + f32 cur_x1 = (f32)v2.x, cur_x2 = (f32)v2.x; + f32 cur_z1 = v2.z, cur_z2 = v2.z; + f32 cur_u1 = v2.u, cur_u2 = v2.u; + f32 cur_v1 = v2.v, cur_v2 = v2.v; + f32 cur_w1 = v2.w, cur_w2 = v2.w; + + for (i32 y = v2.y; y > v0.y; y--) { + pxl8_fill_scanline_textured(gfx, y, (i32)cur_x1, (i32)cur_x2, + cur_z1, cur_z2, cur_u1, cur_v1, cur_w1, cur_u2, cur_v2, cur_w2, texture_id); + cur_x1 -= inv_slope_1; cur_x2 -= inv_slope_2; + cur_z1 -= inv_slope_z1; cur_z2 -= inv_slope_z2; + cur_u1 -= inv_slope_u1; cur_u2 -= inv_slope_u2; + cur_v1 -= inv_slope_v1; cur_v2 -= inv_slope_v2; + cur_w1 -= inv_slope_w1; cur_w2 -= inv_slope_w2; + } +} + +void pxl8_3d_draw_triangle_textured(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, f32 u0, f32 v0f, f32 u1, f32 v1f, f32 u2, f32 v2f, u32 texture_id) { + if (!gfx || !pxl8_3d_init_zbuffer(gfx)) return; + + pxl8_vec4 cv0 = pxl8_transform_vertex(gfx, v0); + pxl8_vec4 cv1 = pxl8_transform_vertex(gfx, v1); + pxl8_vec4 cv2 = pxl8_transform_vertex(gfx, v2); + + if (cv0.w <= 0 || cv1.w <= 0 || cv2.w <= 0) return; + + pxl8_textured_vertex tv[3]; + f32 z_tmp; + pxl8_project_to_screen(gfx, cv0, &tv[0].x, &tv[0].y, &z_tmp); + tv[0].z = z_tmp; + tv[0].u = gfx->affine_textures ? u0 : u0 * cv0.w; + tv[0].v = gfx->affine_textures ? v0f : v0f * cv0.w; + tv[0].w = cv0.w; + + pxl8_project_to_screen(gfx, cv1, &tv[1].x, &tv[1].y, &z_tmp); + tv[1].z = z_tmp; + tv[1].u = gfx->affine_textures ? u1 : u1 * cv1.w; + tv[1].v = gfx->affine_textures ? v1f : v1f * cv1.w; + tv[1].w = cv1.w; + + pxl8_project_to_screen(gfx, cv2, &tv[2].x, &tv[2].y, &z_tmp); + tv[2].z = z_tmp; + tv[2].u = gfx->affine_textures ? u2 : u2 * cv2.w; + tv[2].v = gfx->affine_textures ? v2f : v2f * cv2.w; + tv[2].w = cv2.w; + + if (gfx->backface_culling) { + i32 cross = (tv[1].x - tv[0].x) * (tv[2].y - tv[0].y) - (tv[1].y - tv[0].y) * (tv[2].x - tv[0].x); + if (cross >= 0) return; + } + + if (tv[0].y > tv[1].y) { + pxl8_textured_vertex tmp = tv[0]; tv[0] = tv[1]; tv[1] = tmp; + } + if (tv[0].y > tv[2].y) { + pxl8_textured_vertex tmp = tv[0]; tv[0] = tv[2]; tv[2] = tmp; + } + if (tv[1].y > tv[2].y) { + pxl8_textured_vertex tmp = tv[1]; tv[1] = tv[2]; tv[2] = tmp; + } + + if (tv[1].y == tv[2].y) { + pxl8_draw_flat_bottom_triangle_textured(gfx, tv[0], tv[1], tv[2], texture_id); + } else if (tv[0].y == tv[1].y) { + pxl8_draw_flat_top_triangle_textured(gfx, tv[0], tv[1], tv[2], texture_id); + } else { + f32 t = (f32)(tv[1].y - tv[0].y) / (f32)(tv[2].y - tv[0].y); + pxl8_textured_vertex v3; + v3.x = tv[0].x + (i32)(t * (tv[2].x - tv[0].x)); + v3.y = tv[1].y; + v3.z = tv[0].z + t * (tv[2].z - tv[0].z); + v3.u = tv[0].u + t * (tv[2].u - tv[0].u); + v3.v = tv[0].v + t * (tv[2].v - tv[0].v); + v3.w = tv[0].w + t * (tv[2].w - tv[0].w); + + pxl8_draw_flat_bottom_triangle_textured(gfx, tv[0], tv[1], v3, texture_id); + pxl8_draw_flat_top_triangle_textured(gfx, tv[1], v3, tv[2], texture_id); + } +} diff --git a/src/pxl8_gfx.h b/src/pxl8_gfx.h index b6108db..e40ebad 100644 --- a/src/pxl8_gfx.h +++ b/src/pxl8_gfx.h @@ -3,6 +3,7 @@ #include "pxl8_math.h" #include "pxl8_types.h" +typedef struct pxl8_atlas pxl8_atlas; typedef struct pxl8_gfx pxl8_gfx; typedef enum pxl8_blend_mode { @@ -48,6 +49,7 @@ typedef struct pxl8_effects { typedef struct pxl8_triangle { pxl8_vertex v[3]; + u32 texture_id; } pxl8_triangle; #ifdef __cplusplus @@ -61,12 +63,13 @@ pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx); pxl8_color_mode pxl8_gfx_get_color_mode(pxl8_gfx* gfx); u8* pxl8_gfx_get_framebuffer(pxl8_gfx* gfx); i32 pxl8_gfx_get_height(const pxl8_gfx* gfx); +u32 pxl8_gfx_get_palette_size(const pxl8_gfx* gfx); void pxl8_gfx_get_resolution_dimensions(pxl8_resolution resolution, i32* width, i32* height); i32 pxl8_gfx_get_width(const pxl8_gfx* gfx); -pxl8_result pxl8_gfx_init_atlas(pxl8_gfx* gfx, u32 width, u32 height); pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx); pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path); pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path); +pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width, u32 height); void pxl8_gfx_present(pxl8_gfx* gfx); void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom); void pxl8_gfx_upload_atlas(pxl8_gfx* gfx); @@ -96,6 +99,8 @@ void pxl8_3d_clear_zbuffer(pxl8_gfx* gfx); void pxl8_3d_draw_line_3d(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u32 color); void pxl8_3d_draw_triangle(pxl8_gfx* gfx, pxl8_triangle tri); void pxl8_3d_draw_triangle_raw(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, u32 color); +void pxl8_3d_draw_triangle_textured(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, f32 u0, f32 v0f, f32 u1, f32 v1f, f32 u2, f32 v2f, u32 texture_id); +void pxl8_3d_set_affine_textures(pxl8_gfx* gfx, bool affine); void pxl8_3d_set_backface_culling(pxl8_gfx* gfx, bool culling); void pxl8_3d_set_model(pxl8_gfx* gfx, pxl8_mat4 mat); void pxl8_3d_set_projection(pxl8_gfx* gfx, pxl8_mat4 mat); diff --git a/src/pxl8_math.c b/src/pxl8_math.c index dad4f92..60016e2 100644 --- a/src/pxl8_math.c +++ b/src/pxl8_math.c @@ -4,21 +4,21 @@ #include "pxl8_simd.h" pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b) { - return (pxl8_vec2) { + return (pxl8_vec2){ .x = a.x + b.x, .y = a.y + b.y, }; } pxl8_vec2 pxl8_vec2_sub(pxl8_vec2 a, pxl8_vec2 b) { - return (pxl8_vec2) { + return (pxl8_vec2){ .x = a.x - b.x, .y = a.y - b.y, }; } pxl8_vec2 pxl8_vec2_scale(pxl8_vec2 v, f32 s) { - return (pxl8_vec2) { + return (pxl8_vec2){ .x = v.x * s, .y = v.y * s, }; @@ -34,7 +34,9 @@ f32 pxl8_vec2_length(pxl8_vec2 v) { pxl8_vec2 pxl8_vec2_normalize(pxl8_vec2 v) { f32 len = pxl8_vec2_length(v); + if (len < 1e-6f) return (pxl8_vec2){0}; + return pxl8_vec2_scale(v, 1.0f / len); } @@ -43,7 +45,7 @@ pxl8_vec3 pxl8_vec3_add(pxl8_vec3 a, pxl8_vec3 b) { pxl8_simd_vec_f32 vb = pxl8_simd_set_f32(b.x, b.y, b.z, 0); pxl8_simd_vec_f32 result = pxl8_simd_add_f32(va, vb); - return (pxl8_vec3) { + return (pxl8_vec3){ .x = result.f32_array[0], .y = result.f32_array[1], .z = result.f32_array[2], @@ -55,7 +57,7 @@ pxl8_vec3 pxl8_vec3_sub(pxl8_vec3 a, pxl8_vec3 b) { pxl8_simd_vec_f32 vb = pxl8_simd_set_f32(b.x, b.y, b.z, 0); pxl8_simd_vec_f32 result = pxl8_simd_sub_f32(va, vb); - return (pxl8_vec3) { + return (pxl8_vec3){ .x = result.f32_array[0], .y = result.f32_array[1], .z = result.f32_array[2], @@ -66,7 +68,7 @@ pxl8_vec3 pxl8_vec3_scale(pxl8_vec3 v, f32 s) { pxl8_simd_vec_f32 vv = pxl8_simd_set_f32(v.x, v.y, v.z, 0); pxl8_simd_vec_f32 result = pxl8_simd_scale_f32(vv, s); - return (pxl8_vec3) { + return (pxl8_vec3){ .x = result.f32_array[0], .y = result.f32_array[1], .z = result.f32_array[2], @@ -76,11 +78,12 @@ pxl8_vec3 pxl8_vec3_scale(pxl8_vec3 v, f32 s) { f32 pxl8_vec3_dot(pxl8_vec3 a, pxl8_vec3 b) { pxl8_simd_vec_f32 va = pxl8_simd_set_f32(a.x, a.y, a.z, 0); pxl8_simd_vec_f32 vb = pxl8_simd_set_f32(b.x, b.y, b.z, 0); + return pxl8_simd_dot3_f32(va, vb); } pxl8_vec3 pxl8_vec3_cross(pxl8_vec3 a, pxl8_vec3 b) { - return (pxl8_vec3) { + return (pxl8_vec3){ .x = a.y * b.z - a.z * b.y, .y = a.z * b.x - a.x * b.z, .z = a.x * b.y - a.y * b.x, @@ -93,18 +96,23 @@ f32 pxl8_vec3_length(pxl8_vec3 v) { pxl8_vec3 pxl8_vec3_normalize(pxl8_vec3 v) { f32 len = pxl8_vec3_length(v); + if (len < 1e-6f) return (pxl8_vec3){0}; + return pxl8_vec3_scale(v, 1.0f / len); } pxl8_mat4 pxl8_mat4_identity(void) { pxl8_mat4 mat = {0}; + mat.m[0] = mat.m[5] = mat.m[10] = mat.m[15] = 1.0f; + return mat; } pxl8_mat4 pxl8_mat4_multiply(pxl8_mat4 a, pxl8_mat4 b) { - pxl8_mat4 result = {0}; + pxl8_mat4 mat = {0}; + for (i32 i = 0; i < 4; i++) { for (i32 j = 0; j < 4; j++) { pxl8_simd_vec_f32 row = pxl8_simd_set_f32( @@ -113,34 +121,35 @@ pxl8_mat4 pxl8_mat4_multiply(pxl8_mat4 a, pxl8_mat4 b) { pxl8_simd_vec_f32 col = pxl8_simd_set_f32( b.m[0 * 4 + j], b.m[1 * 4 + j], b.m[2 * 4 + j], b.m[3 * 4 + j] ); - result.m[i * 4 + j] = pxl8_simd_dot4_f32(row, col); + mat.m[i * 4 + j] = pxl8_simd_dot4_f32(row, col); } } - return result; + + return mat; } pxl8_vec4 pxl8_mat4_multiply_vec4(pxl8_mat4 m, pxl8_vec4 v) { - pxl8_simd_vec_f32 vec = pxl8_simd_set_f32(v.x, v.y, v.z, v.w); - pxl8_vec4 result; - pxl8_simd_vec_f32 row0 = pxl8_simd_set_f32(m.m[0], m.m[1], m.m[2], m.m[3]); pxl8_simd_vec_f32 row1 = pxl8_simd_set_f32(m.m[4], m.m[5], m.m[6], m.m[7]); pxl8_simd_vec_f32 row2 = pxl8_simd_set_f32(m.m[8], m.m[9], m.m[10], m.m[11]); pxl8_simd_vec_f32 row3 = pxl8_simd_set_f32(m.m[12], m.m[13], m.m[14], m.m[15]); + pxl8_simd_vec_f32 vec = pxl8_simd_set_f32(v.x, v.y, v.z, v.w); - result.x = pxl8_simd_dot4_f32(row0, vec); - result.y = pxl8_simd_dot4_f32(row1, vec); - result.z = pxl8_simd_dot4_f32(row2, vec); - result.w = pxl8_simd_dot4_f32(row3, vec); - - return result; + return (pxl8_vec4){ + .x = pxl8_simd_dot4_f32(row0, vec), + .y = pxl8_simd_dot4_f32(row1, vec), + .z = pxl8_simd_dot4_f32(row2, vec), + .w = pxl8_simd_dot4_f32(row3, vec), + }; } pxl8_mat4 pxl8_mat4_translate(f32 x, f32 y, f32 z) { pxl8_mat4 mat = pxl8_mat4_identity(); + mat.m[3] = x; mat.m[7] = y; mat.m[11] = z; + return mat; } @@ -148,10 +157,12 @@ pxl8_mat4 pxl8_mat4_rotate_x(f32 angle) { pxl8_mat4 mat = pxl8_mat4_identity(); f32 c = cosf(angle); f32 s = sinf(angle); + mat.m[5] = c; mat.m[6] = -s; mat.m[9] = s; mat.m[10] = c; + return mat; } @@ -159,10 +170,12 @@ pxl8_mat4 pxl8_mat4_rotate_y(f32 angle) { pxl8_mat4 mat = pxl8_mat4_identity(); f32 c = cosf(angle); f32 s = sinf(angle); + mat.m[0] = c; mat.m[2] = s; mat.m[8] = -s; mat.m[10] = c; + return mat; } @@ -170,18 +183,22 @@ pxl8_mat4 pxl8_mat4_rotate_z(f32 angle) { pxl8_mat4 mat = pxl8_mat4_identity(); f32 c = cosf(angle); f32 s = sinf(angle); + mat.m[0] = c; mat.m[1] = -s; mat.m[4] = s; mat.m[5] = c; + return mat; } pxl8_mat4 pxl8_mat4_scale(f32 x, f32 y, f32 z) { pxl8_mat4 mat = pxl8_mat4_identity(); + mat.m[0] = x; mat.m[5] = y; mat.m[10] = z; + return mat; } @@ -213,11 +230,11 @@ pxl8_mat4 pxl8_mat4_perspective(f32 fov, f32 aspect, f32 near, f32 far) { } pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up) { + pxl8_mat4 mat = pxl8_mat4_identity(); pxl8_vec3 f = pxl8_vec3_normalize(pxl8_vec3_sub(center, eye)); pxl8_vec3 s = pxl8_vec3_normalize(pxl8_vec3_cross(f, up)); pxl8_vec3 u = pxl8_vec3_cross(s, f); - pxl8_mat4 mat = pxl8_mat4_identity(); mat.m[0] = s.x; mat.m[1] = s.y; mat.m[2] = s.z; diff --git a/src/pxl8_script.c b/src/pxl8_script.c index ce90121..ba62dfb 100644 --- a/src/pxl8_script.c +++ b/src/pxl8_script.c @@ -56,6 +56,8 @@ static const char* pxl8_ffi_cdefs = "void pxl8_gfx_fade_palette(pxl8_gfx* ctx, u8 start, u8 count, f32 amount, u32 target_color);\n" "i32 pxl8_gfx_load_palette(pxl8_gfx* ctx, const char* filepath);\n" "i32 pxl8_gfx_load_sprite(pxl8_gfx* ctx, const char* filepath, u32* sprite_id);\n" +"i32 pxl8_gfx_create_texture(pxl8_gfx* ctx, const u8* pixels, u32 width, u32 height);\n" +"void pxl8_gfx_upload_atlas(pxl8_gfx* ctx);\n" "typedef struct pxl8_input_state pxl8_input_state;\n" "bool pxl8_key_down(const pxl8_input_state* input, i32 key);\n" "bool pxl8_key_pressed(const pxl8_input_state* input, i32 key);\n" @@ -133,6 +135,8 @@ static const char* pxl8_ffi_cdefs = "void pxl8_3d_clear_zbuffer(pxl8_gfx* gfx);\n" "void pxl8_3d_draw_line_3d(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u32 color);\n" "void pxl8_3d_draw_triangle_raw(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, u32 color);\n" +"void pxl8_3d_draw_triangle_textured(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, f32 u0, f32 v0, f32 u1, f32 v1, f32 u2, f32 v2, u32 texture_id);\n" +"void pxl8_3d_set_affine_textures(pxl8_gfx* gfx, bool affine);\n" "void pxl8_3d_set_backface_culling(pxl8_gfx* gfx, bool culling);\n" "void pxl8_3d_set_model(pxl8_gfx* gfx, pxl8_mat4 mat);\n" "void pxl8_3d_set_projection(pxl8_gfx* gfx, pxl8_mat4 mat);\n" diff --git a/src/pxl8_vfx.c b/src/pxl8_vfx.c index 90a5059..2d9e30b 100644 --- a/src/pxl8_vfx.c +++ b/src/pxl8_vfx.c @@ -28,11 +28,11 @@ struct pxl8_particles { void* userdata; }; -void pxl8_vfx_plasma(pxl8_gfx* ctx, f32 time, f32 scale1, f32 scale2, u8 palette_offset) { - if (!ctx || !pxl8_gfx_get_framebuffer(ctx)) return; - - for (i32 y = 0; y < pxl8_gfx_get_height(ctx); y++) { - for (i32 x = 0; x < pxl8_gfx_get_width(ctx); x++) { +void pxl8_vfx_plasma(pxl8_gfx* gfx, f32 time, f32 scale1, f32 scale2, u8 palette_offset) { + if (!gfx || !pxl8_gfx_get_framebuffer(gfx)) return; + + for (i32 y = 0; y < pxl8_gfx_get_height(gfx); y++) { + for (i32 x = 0; x < pxl8_gfx_get_width(gfx); x++) { f32 v1 = sinf(x * scale1 + time); f32 v2 = sinf(y * scale1 + time * 0.7f); f32 v3 = sinf((x + y) * scale2 + time * 1.3f); @@ -44,13 +44,13 @@ void pxl8_vfx_plasma(pxl8_gfx* ctx, f32 time, f32 scale1, f32 scale2, u8 palette if (normalized > 1.0f) normalized = 1.0f; u8 color = palette_offset + (u8)(15.0f * normalized); - pxl8_pixel(ctx, x, y, color); + pxl8_pixel(gfx, x, y, color); } } } -void pxl8_vfx_raster_bars(pxl8_gfx* ctx, pxl8_raster_bar* bars, u32 bar_count, f32 time) { - if (!ctx || !bars) return; +void pxl8_vfx_raster_bars(pxl8_gfx* gfx, pxl8_raster_bar* bars, u32 bar_count, f32 time) { + if (!gfx || !bars) return; for (u32 i = 0; i < bar_count; i++) { pxl8_raster_bar* bar = &bars[i]; @@ -72,24 +72,24 @@ void pxl8_vfx_raster_bars(pxl8_gfx* ctx, pxl8_raster_bar* bars, u32 bar_count, f color_idx = bar->color - 1; } - pxl8_rect_fill(ctx, 0, y_int + dy, pxl8_gfx_get_width(ctx), 1, color_idx); + pxl8_rect_fill(gfx, 0, y_int + dy, pxl8_gfx_get_width(gfx), 1, color_idx); } } } -void pxl8_vfx_rotozoom(pxl8_gfx* ctx, f32 angle, f32 zoom, i32 cx, i32 cy) { - if (!ctx || !pxl8_gfx_get_framebuffer(ctx)) return; +void pxl8_vfx_rotozoom(pxl8_gfx* gfx, f32 angle, f32 zoom, i32 cx, i32 cy) { + if (!gfx || !pxl8_gfx_get_framebuffer(gfx)) return; f32 cos_a = cosf(angle); f32 sin_a = sinf(angle); - u8* temp_buffer = (u8*)SDL_malloc(pxl8_gfx_get_width(ctx) * pxl8_gfx_get_height(ctx)); + u8* temp_buffer = (u8*)SDL_malloc(pxl8_gfx_get_width(gfx) * pxl8_gfx_get_height(gfx)); if (!temp_buffer) return; - SDL_memcpy(temp_buffer, pxl8_gfx_get_framebuffer(ctx), pxl8_gfx_get_width(ctx) * pxl8_gfx_get_height(ctx)); + SDL_memcpy(temp_buffer, pxl8_gfx_get_framebuffer(gfx), pxl8_gfx_get_width(gfx) * pxl8_gfx_get_height(gfx)); - for (i32 y = 0; y < pxl8_gfx_get_height(ctx); y++) { - for (i32 x = 0; x < pxl8_gfx_get_width(ctx); x++) { + for (i32 y = 0; y < pxl8_gfx_get_height(gfx); y++) { + for (i32 x = 0; x < pxl8_gfx_get_width(gfx); x++) { f32 dx = x - cx; f32 dy = y - cy; @@ -99,8 +99,8 @@ void pxl8_vfx_rotozoom(pxl8_gfx* ctx, f32 angle, f32 zoom, i32 cx, i32 cy) { i32 sx = (i32)src_x; i32 sy = (i32)src_y; - if (sx >= 0 && sx < pxl8_gfx_get_width(ctx) && sy >= 0 && sy < pxl8_gfx_get_height(ctx)) { - pxl8_gfx_get_framebuffer(ctx)[y * pxl8_gfx_get_width(ctx) + x] = temp_buffer[sy * pxl8_gfx_get_width(ctx) + sx]; + if (sx >= 0 && sx < pxl8_gfx_get_width(gfx) && sy >= 0 && sy < pxl8_gfx_get_height(gfx)) { + pxl8_gfx_get_framebuffer(gfx)[y * pxl8_gfx_get_width(gfx) + x] = temp_buffer[sy * pxl8_gfx_get_width(gfx) + sx]; } } } @@ -108,38 +108,41 @@ void pxl8_vfx_rotozoom(pxl8_gfx* ctx, f32 angle, f32 zoom, i32 cx, i32 cy) { SDL_free(temp_buffer); } -void pxl8_vfx_tunnel(pxl8_gfx* ctx, f32 time, f32 speed, f32 twist) { - if (!ctx || !pxl8_gfx_get_framebuffer(ctx)) return; - - f32 cx = pxl8_gfx_get_width(ctx) / 2.0f; - f32 cy = pxl8_gfx_get_height(ctx) / 2.0f; - - for (i32 y = 0; y < pxl8_gfx_get_height(ctx); y++) { - for (i32 x = 0; x < pxl8_gfx_get_width(ctx); x++) { +void pxl8_vfx_tunnel(pxl8_gfx* gfx, f32 time, f32 speed, f32 twist) { + if (!gfx || !pxl8_gfx_get_framebuffer(gfx)) return; + + f32 cx = pxl8_gfx_get_width(gfx) / 2.0f; + f32 cy = pxl8_gfx_get_height(gfx) / 2.0f; + u32 palette_size = pxl8_gfx_get_palette_size(gfx); + if (palette_size == 0) palette_size = 256; + + for (i32 y = 0; y < pxl8_gfx_get_height(gfx); y++) { + for (i32 x = 0; x < pxl8_gfx_get_width(gfx); x++) { f32 dx = x - cx; f32 dy = y - cy; f32 dist = sqrtf(dx * dx + dy * dy); - + if (dist > 1.0f) { f32 angle = atan2f(dy, dx); f32 u = angle * 0.159f + twist * sinf(time * 0.5f); f32 v = 256.0f / dist + time * speed; - + u8 tx = (u8)((i32)(u * 256.0f) & 0xFF); u8 ty = (u8)((i32)(v * 256.0f) & 0xFF); - u8 color = tx ^ ty; - - pxl8_pixel(ctx, x, y, color); + u8 pattern = tx ^ ty; + u8 color = (pattern / 8) % palette_size; + + pxl8_pixel(gfx, x, y, color); } } } } -void pxl8_vfx_water_ripple(pxl8_gfx* ctx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping) { - if (!ctx || !height_map) return; +void pxl8_vfx_water_ripple(pxl8_gfx* gfx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping) { + if (!gfx || !height_map) return; - i32 w = pxl8_gfx_get_width(ctx); - i32 h = pxl8_gfx_get_height(ctx); + i32 w = pxl8_gfx_get_width(gfx); + i32 h = pxl8_gfx_get_height(gfx); static f32* prev_height = NULL; if (!prev_height) { diff --git a/src/pxl8_vfx.h b/src/pxl8_vfx.h index 856cb1b..cbfe98b 100644 --- a/src/pxl8_vfx.h +++ b/src/pxl8_vfx.h @@ -46,8 +46,8 @@ void pxl8_vfx_snow(pxl8_particles* particles, i32 width, f32 wind); void pxl8_vfx_sparks(pxl8_particles* particles, i32 x, i32 y, u32 color); void pxl8_vfx_starfield(pxl8_particles* particles, f32 speed, f32 spread); -void pxl8_vfx_plasma(pxl8_gfx* ctx, f32 time, f32 scale1, f32 scale2, u8 palette_offset); -void pxl8_vfx_raster_bars(pxl8_gfx* ctx, pxl8_raster_bar* bars, u32 bar_count, f32 time); -void pxl8_vfx_rotozoom(pxl8_gfx* ctx, f32 angle, f32 zoom, i32 cx, i32 cy); -void pxl8_vfx_tunnel(pxl8_gfx* ctx, f32 time, f32 speed, f32 twist); -void pxl8_vfx_water_ripple(pxl8_gfx* ctx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping); +void pxl8_vfx_plasma(pxl8_gfx* gfx, f32 time, f32 scale1, f32 scale2, u8 palette_offset); +void pxl8_vfx_raster_bars(pxl8_gfx* gfx, pxl8_raster_bar* bars, u32 bar_count, f32 time); +void pxl8_vfx_rotozoom(pxl8_gfx* gfx, f32 angle, f32 zoom, i32 cx, i32 cy); +void pxl8_vfx_tunnel(pxl8_gfx* gfx, f32 time, f32 speed, f32 twist); +void pxl8_vfx_water_ripple(pxl8_gfx* gfx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping);