diff --git a/demo/cube3d.fnl b/demo/cube3d.fnl new file mode 100644 index 0000000..859e8f7 --- /dev/null +++ b/demo/cube3d.fnl @@ -0,0 +1,98 @@ +(local pxl8 (require :pxl8)) + +(var angle-x 0) +(var angle-y 0) +(var angle-z 0) +(var auto-rotate true) +(var orthographic true) +(var wireframe true) +(var time 0) +(var zoom 5.0) + +(fn make-cube-vertices [] + [[-1 -1 -1] [1 -1 -1] [1 1 -1] [-1 1 -1] + [-1 -1 1] [1 -1 1] [1 1 1] [-1 1 1]]) + +(fn make-cube-faces [] + [[0 1 2] [0 2 3] + [1 5 6] [1 6 2] + [5 4 7] [5 7 6] + [4 0 3] [4 3 7] + [3 2 6] [3 6 7] + [4 5 1] [4 1 0]]) + +(fn get-face-color [face-idx] + (let [colors [12 22 30 16 28 20]] + (. colors (+ 1 (% face-idx 6))))) + +(fn cube-update [dt] + (set time (+ time dt)) + + (when (pxl8.key_down "w") + (set angle-x (- angle-x (* dt 2.0)))) + (when (pxl8.key_down "s") + (set angle-x (+ angle-x (* dt 2.0)))) + (when (pxl8.key_down "a") + (set angle-y (- angle-y (* dt 2.0)))) + (when (pxl8.key_down "d") + (set angle-y (+ angle-y (* dt 2.0)))) + (when (pxl8.key_down "q") + (set angle-z (- angle-z (* dt 2.0)))) + (when (pxl8.key_down "e") + (set angle-z (+ angle-z (* dt 2.0)))) + + (when (pxl8.key_pressed " ") + (set wireframe (not wireframe))) + (when (pxl8.key_pressed "r") + (set auto-rotate (not auto-rotate))) + (when (pxl8.key_pressed "p") + (set orthographic (not orthographic))) + + (when (pxl8.key_down "=") + (set zoom (- zoom (* dt 2.0)))) + (when (pxl8.key_down "-") + (set zoom (+ zoom (* dt 2.0)))) + (set zoom (math.max 1.0 (math.min zoom 20.0))) + + (when auto-rotate + (set angle-x (+ angle-x (* dt 0.7))) + (set angle-y (+ angle-y (* dt 0.5))) + (set angle-z (+ angle-z (* dt 0.3))))) + +(fn cube-frame [] + (pxl8.clr 0) + + (pxl8.clear_zbuffer) + (pxl8.set_wireframe wireframe) + (pxl8.set_backface_culling true) + + (if orthographic + (let [size (* 2.5 (/ zoom 5.0)) + aspect (/ (pxl8.get_width) (pxl8.get_height)) + w (* size aspect) + h size] + (pxl8.set_projection (pxl8.mat4_ortho (- w) w (- h) h 1.0 50.0))) + (let [aspect (/ (pxl8.get_width) (pxl8.get_height)) + fov (/ 3.14159 (+ 2.0 (/ zoom 5.0)))] + (pxl8.set_projection (pxl8.mat4_perspective fov aspect 1.0 50.0)))) + + (pxl8.set_view (pxl8.mat4_lookat [0 0 zoom] [0 0 0] [0 1 0])) + + (let [model (-> (pxl8.mat4_identity) + (pxl8.mat4_multiply (pxl8.mat4_rotate_x angle-x)) + (pxl8.mat4_multiply (pxl8.mat4_rotate_y angle-y)) + (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))))) + +{:update cube-update + :frame cube-frame} diff --git a/demo/main.fnl b/demo/main.fnl index 3290ac1..29119bd 100644 --- a/demo/main.fnl +++ b/demo/main.fnl @@ -1,4 +1,5 @@ (local pxl8 (require :pxl8)) +(local cube3d (fennel.dofile "cube3d.fnl")) (var time 0) (var current-effect 1) @@ -15,8 +16,8 @@ (global init (fn [] (pxl8.load_palette "palettes/gruvbox.ase") - (set logo-sprite (pxl8.load_sprite "sprites/pxl8_logo.ase")))) - (set particles (pxl8.particles_new 1000)) + (set logo-sprite (pxl8.load_sprite "sprites/pxl8_logo.ase")) + (set particles (pxl8.particles_new 1000)))) (global update (fn [dt] (set time (+ time dt)) @@ -38,20 +39,24 @@ (when (pxl8.key_pressed "7") (set current-effect 7) (set snow-init false)) + (when (pxl8.key_pressed "8") + (set current-effect 8)) - (when (= current-effect 1) - (set logo-x (+ logo-x (* logo-dx dt))) - (set logo-y (+ logo-y (* logo-dy dt))) - - (when (or (< logo-x 0) (> logo-x 512)) - (set logo-dx (- logo-dx))) - (when (or (< logo-y 0) (> logo-y 296)) - (set logo-dy (- logo-dy)))) + (case current-effect + 1 (do + (set logo-x (+ logo-x (* logo-dx dt))) + (set logo-y (+ logo-y (* logo-dy dt))) + (when (or (< logo-x 0) (> logo-x 512)) + (set logo-dx (- logo-dx))) + (when (or (< logo-y 0) (> logo-y 296)) + (set logo-dy (- logo-dy)))) + 8 (cube3d.update dt) + _ nil) (when particles (pxl8.particles_update particles dt)))) -(global draw (fn [] +(global frame (fn [] (case current-effect 1 (do (pxl8.clr 0) @@ -64,9 +69,9 @@ 4 (do (pxl8.clr 0) - (local bars [{:base_y 40 :amplitude 20 :height 16 :speed 2.0 :phase 0 :color 14 :fade_color 11} - {:base_y 80 :amplitude 15 :height 16 :speed 2.5 :phase 1.5 :color 20 :fade_color 11} - {:base_y 120 :amplitude 25 :height 16 :speed 1.8 :phase 3.0 :color 26 :fade_color 11}]) + (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}]) (pxl8.vfx_raster_bars bars time)) 5 (do @@ -95,5 +100,7 @@ (pxl8.vfx_snow particles 320 5.0) (set snow-init true)) (pxl8.particles_render particles))) - + + 8 (cube3d.frame) + _ (pxl8.clr 0)))) diff --git a/pxl8.sh b/pxl8.sh index c798a79..fb99eeb 100755 --- a/pxl8.sh +++ b/pxl8.sh @@ -388,7 +388,8 @@ case "$COMMAND" in src/pxl8_font.c src/pxl8_gfx.c src/pxl8_io.c - src/pxl8_lua.c + src/pxl8_math.c + src/pxl8_script.c src/pxl8_tilemap.c src/pxl8_tilesheet.c src/pxl8_vfx.c diff --git a/src/lua/pxl8.lua b/src/lua/pxl8.lua index 553403b..1f4dbcc 100644 --- a/src/lua/pxl8.lua +++ b/src/lua/pxl8.lua @@ -1,7 +1,7 @@ local ffi = require("ffi") local C = ffi.C -local gfx = _pxl8_gfx_ctx -local input = _pxl8_input_ctx +local gfx = _pxl8_gfx +local input = _pxl8_input -- pxl8 lua api -- @@ -47,8 +47,12 @@ function pxl8.sprite(id, x, y, w, h, flip_x, flip_y) C.pxl8_sprite(gfx, id or 0, x or 0, y or 0, w or 16, h or 16, flip_x or false, flip_y or false) end -function pxl8.get_screen() - return C.pxl8_get_screen(gfx) +function pxl8.get_width() + return C.pxl8_gfx_get_width(gfx) +end + +function pxl8.get_height() + return C.pxl8_gfx_get_height(gfx) end function pxl8.load_palette(filepath) @@ -119,8 +123,7 @@ function pxl8.vfx_plasma(time, scale1, scale2, palette_offset) end function pxl8.vfx_rotozoom(angle, zoom, cx, cy) - local screen = pxl8.get_screen() - C.pxl8_vfx_rotozoom(gfx, angle, zoom, cx or screen.width/2, cy or screen.height/2) + C.pxl8_vfx_rotozoom(gfx, angle, zoom, cx or pxl8.get_width()/2, cy or pxl8.get_height()/2) end function pxl8.vfx_tunnel(time, speed, twist) @@ -128,29 +131,27 @@ function pxl8.vfx_tunnel(time, speed, twist) end function pxl8.particles_new(max_count) - local ps = ffi.new("pxl8_particle_system") - C.pxl8_vfx_particles_init(ps, max_count or 1000) - return ps + return C.pxl8_particles_create(max_count or 1000) end function pxl8.particles_destroy(ps) - C.pxl8_vfx_particles_destroy(ps) + C.pxl8_particles_destroy(ps) end function pxl8.particles_clear(ps) - C.pxl8_vfx_particles_clear(ps) + C.pxl8_particles_clear(ps) end function pxl8.particles_emit(ps, count) - C.pxl8_vfx_particles_emit(ps, count or 1) + C.pxl8_particles_emit(ps, count or 1) end function pxl8.particles_update(ps, dt) - C.pxl8_vfx_particles_update(ps, dt) + C.pxl8_particles_update(ps, dt) end function pxl8.particles_render(ps) - C.pxl8_vfx_particles_render(ps, gfx) + C.pxl8_particles_render(ps, gfx) end function pxl8.vfx_explosion(ps, x, y, color, force) @@ -162,7 +163,7 @@ function pxl8.vfx_fire(ps, x, y, width, palette_start) end function pxl8.vfx_rain(ps, width, wind) - C.pxl8_vfx_rain(ps, width or pxl8.get_screen().width, wind or 0.0) + C.pxl8_vfx_rain(ps, width or pxl8.get_width(), wind or 0.0) end function pxl8.vfx_smoke(ps, x, y, color) @@ -170,7 +171,7 @@ function pxl8.vfx_smoke(ps, x, y, color) end function pxl8.vfx_snow(ps, width, wind) - C.pxl8_vfx_snow(ps, width or pxl8.get_screen().width, wind or 10.0) + C.pxl8_vfx_snow(ps, width or pxl8.get_width(), wind or 10.0) end function pxl8.vfx_sparks(ps, x, y, color) @@ -190,7 +191,7 @@ function pxl8.gfx_fade_palette(start, count, amount, target_color) end function pxl8.tilesheet_new(tile_size) - return C.pxl8_tilesheet_new(tile_size or 16) + return C.pxl8_tilesheet_create(tile_size or 16) end function pxl8.tilesheet_destroy(tilesheet) @@ -202,7 +203,7 @@ function pxl8.tilesheet_load(tilesheet, filepath) end function pxl8.tilemap_new(width, height, tile_size) - return C.pxl8_tilemap_new(width, height, tile_size or 16) + return C.pxl8_tilemap_create(width, height, tile_size or 16) end function pxl8.tilemap_destroy(tilemap) @@ -246,6 +247,86 @@ pxl8.TILE_FLIP_Y = 2 pxl8.TILE_SOLID = 4 pxl8.TILE_TRIGGER = 8 +function pxl8.clear_zbuffer() + C.pxl8_3d_clear_zbuffer(gfx) +end + +function pxl8.set_model(mat) + C.pxl8_3d_set_model(gfx, mat) +end + +function pxl8.set_view(mat) + C.pxl8_3d_set_view(gfx, mat) +end + +function pxl8.set_projection(mat) + C.pxl8_3d_set_projection(gfx, mat) +end + +function pxl8.set_wireframe(wireframe) + C.pxl8_3d_set_wireframe(gfx, wireframe) +end + +function pxl8.set_backface_culling(culling) + C.pxl8_3d_set_backface_culling(gfx, culling) +end + +function pxl8.draw_triangle_3d(v0, v1, v2, color) + 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_raw(gfx, vec0, vec1, vec2, color) +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]}) + C.pxl8_3d_draw_line_3d(gfx, vec0, vec1, color) +end + +function pxl8.mat4_identity() + return C.pxl8_mat4_identity() +end + +function pxl8.mat4_multiply(a, b) + return C.pxl8_mat4_multiply(a, b) +end + +function pxl8.mat4_translate(x, y, z) + return C.pxl8_mat4_translate(x, y, z) +end + +function pxl8.mat4_rotate_x(angle) + return C.pxl8_mat4_rotate_x(angle) +end + +function pxl8.mat4_rotate_y(angle) + return C.pxl8_mat4_rotate_y(angle) +end + +function pxl8.mat4_rotate_z(angle) + return C.pxl8_mat4_rotate_z(angle) +end + +function pxl8.mat4_scale(x, y, z) + return C.pxl8_mat4_scale(x, y, z) +end + +function pxl8.mat4_ortho(left, right, bottom, top, near, far) + return C.pxl8_mat4_ortho(left, right, bottom, top, near, far) +end + +function pxl8.mat4_perspective(fov, aspect, near, far) + return C.pxl8_mat4_perspective(fov, aspect, near, far) +end + +function pxl8.mat4_lookat(eye, center, up) + local eye_vec = ffi.new("pxl8_vec3", {x = eye[1], y = eye[2], z = eye[3]}) + local center_vec = ffi.new("pxl8_vec3", {x = center[1], y = center[2], z = center[3]}) + local up_vec = ffi.new("pxl8_vec3", {x = up[1], y = up[2], z = up[3]}) + return C.pxl8_mat4_lookat(eye_vec, center_vec, up_vec) +end + pxl8.gfx = gfx pxl8.input = input diff --git a/src/pxl8.c b/src/pxl8.c index 846fc0e..ffad11f 100644 --- a/src/pxl8.c +++ b/src/pxl8.c @@ -9,13 +9,14 @@ #include #include + #define SDL_MAIN_USE_CALLBACKS #include #include #include "pxl8_cart.h" -#include "pxl8_lua.h" #include "pxl8_macros.h" +#include "pxl8_script.h" #include "pxl8_types.h" #define PXL8_MAX_REPL_COMMANDS 4096 @@ -36,10 +37,10 @@ typedef struct pxl8_repl_state { typedef struct pxl8_state { pxl8_cart* cart; pxl8_color_mode color_mode; - pxl8_gfx_ctx gfx; - lua_State* lua; + pxl8_gfx* gfx; pxl8_repl_state repl; pxl8_resolution resolution; + pxl8_script* script; f32 fps_timer; i32 frame_count; @@ -50,8 +51,7 @@ typedef struct pxl8_state { bool running; bool script_loaded; char script_path[256]; - time_t script_mod_time; - + pxl8_input_state input; } pxl8_state; @@ -184,54 +184,6 @@ static pxl8_repl_command* pxl8_repl_pop_command(pxl8_repl_state* repl) { return cmd; } -static void load_script(pxl8_state* app) { - const char* ext = strrchr(app->script_path, '.'); - - if (ext && strcmp(ext, ".fnl") == 0) { - pxl8_result result = pxl8_lua_run_fennel_file(app->lua, app->script_path); - if (result == PXL8_OK) { - pxl8_info("Loaded script: %s", app->script_path); - app->script_loaded = true; - lua_getglobal(app->lua, "init"); - if (lua_isfunction(app->lua, -1)) { - lua_pcall(app->lua, 0, 0, 0); - } else { - pxl8_warn("No init function found (type: %s)", lua_typename(app->lua, lua_type(app->lua, -1))); - lua_pop(app->lua, 1); - } - } else { - pxl8_warn("Failed to load script: %s", app->script_path); - app->script_loaded = false; - } - } else if (ext && strcmp(ext, ".lua") == 0) { - pxl8_result result = pxl8_lua_run_file(app->lua, app->script_path); - if (result == PXL8_OK) { - pxl8_info("Loaded script: %s", app->script_path); - app->script_loaded = true; - lua_getglobal(app->lua, "init"); - if (lua_isfunction(app->lua, -1)) { - lua_pcall(app->lua, 0, 0, 0); - } else { - pxl8_warn("No init function found (type: %s)", lua_typename(app->lua, lua_type(app->lua, -1))); - lua_pop(app->lua, 1); - } - } else { - pxl8_warn("Failed to load script: %s", app->script_path); - app->script_loaded = false; - } - } else { - pxl8_warn("Unknown script type for: %s (expected .fnl or .lua)", app->script_path); - app->script_loaded = false; - } -} - -static time_t get_file_mod_time(const char* path) { - struct stat file_stat; - if (stat(path, &file_stat) == 0) { - return file_stat.st_mtime; - } - return 0; -} SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { static pxl8_state app = {0}; @@ -249,7 +201,7 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { const char* pack_input = NULL; const char* pack_output = NULL; - for (int i = 1; i < argc; i++) { + for (i32 i = 1; i < argc; i++) { if (strcmp(argv[i], "--repl") == 0) { app.repl_mode = true; } else if (strcmp(argv[i], "--pack") == 0) { @@ -277,26 +229,27 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { } pxl8_info("Starting up"); - - if (pxl8_gfx_init(&app.gfx, app.color_mode, app.resolution, "pxl8", 1280, 720) != PXL8_OK) { - pxl8_error("Failed to initialize graphics context"); + + app.gfx = pxl8_gfx_create(app.color_mode, app.resolution, "pxl8", 1280, 720); + if (!app.gfx) { + pxl8_error("Failed to create graphics context"); return SDL_APP_FAILURE; } - - if (pxl8_gfx_load_font_atlas(&app.gfx) != PXL8_OK) { + + if (pxl8_gfx_load_font_atlas(app.gfx) != PXL8_OK) { pxl8_error("Failed to load font atlas"); return SDL_APP_FAILURE; } - - if (pxl8_gfx_init_atlas(&app.gfx, 1024, 1024) != PXL8_OK) { + + if (pxl8_gfx_init_atlas(app.gfx, 1024, 1024) != PXL8_OK) { pxl8_error("Failed to initialize sprite atlas"); return SDL_APP_FAILURE; } - - pxl8_result lua_result = pxl8_lua_init(&app.lua); - if (lua_result != PXL8_OK) { - pxl8_error("Failed to initialize Lua scripting!"); - pxl8_gfx_shutdown(&app.gfx); + + app.script = pxl8_script_create(); + if (!app.script) { + pxl8_error("Failed to initialize scripting: %s", pxl8_script_get_last_error(app.script)); + pxl8_gfx_destroy(app.gfx); return SDL_APP_FAILURE; } @@ -308,16 +261,16 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { if (is_cart) { char* original_cwd = getcwd(NULL, 0); - app.cart = calloc(1, sizeof(pxl8_cart)); + app.cart = pxl8_cart_create(); if (!app.cart) { - pxl8_error("Failed to allocate memory for cart"); + pxl8_error("Failed to create cart"); return false; } if (pxl8_cart_load(app.cart, cart_path) == PXL8_OK) { - pxl8_lua_setup_cart_path(app.lua, app.cart->base_path, original_cwd); + pxl8_script_set_cart_path(app.script, pxl8_cart_get_base_path(app.cart), original_cwd); pxl8_cart_mount(app.cart); strcpy(app.script_path, "main.fnl"); - pxl8_info("Loaded cart: %s", app.cart->name); + pxl8_info("Loaded cart: %s", pxl8_cart_get_name(app.cart)); } else { pxl8_error("Failed to load cart: %s", cart_path); return SDL_APP_FAILURE; @@ -328,25 +281,20 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { app.script_path[sizeof(app.script_path) - 1] = '\0'; } - pxl8_lua_setup_contexts(app.lua, &app.gfx, &app.input); + pxl8_script_set_gfx(app.script, app.gfx); + pxl8_script_set_input(app.script, &app.input); if (app.script_path[0] != '\0') { - app.script_mod_time = get_file_mod_time(app.script_path); - load_script(&app); + pxl8_result result = pxl8_script_load_main(app.script, app.script_path); + app.script_loaded = (result == PXL8_OK); } if (app.repl_mode) { pxl8_repl_init(&app.repl); fprintf(stderr, "\033[38;2;184;187;38m[pxl8 REPL]\033[0m Fennel %s - Tab for completions, Ctrl-C to exit\n", "1.5.1"); - - lua_getglobal(app.lua, "require"); - lua_pushstring(app.lua, "pxl8"); - if (lua_pcall(app.lua, 1, 1, 0) == 0) { - lua_setglobal(app.lua, "pxl8"); - } else { - const char* error_msg = lua_tostring(app.lua, -1); - fprintf(stderr, "Warning: Failed to setup pxl8 global: %s\n", error_msg); - lua_pop(app.lua, 1); + + if (pxl8_script_load_module(app.script, "pxl8") != PXL8_OK) { + fprintf(stderr, "Warning: Failed to setup pxl8 global: %s\n", pxl8_script_get_last_error(app.script)); } } @@ -359,110 +307,72 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) { SDL_AppResult SDL_AppIterate(void* appstate) { pxl8_state* app = (pxl8_state*)appstate; - int width, height; + pxl8_bounds bounds = pxl8_gfx_get_bounds(app->gfx); - SDL_GetWindowSize(app->gfx.window, &width, &height); - u64 current_time = SDL_GetTicksNS(); - float dt = (float)(current_time - app->last_time) / 1000000000.0f; + f32 dt = (f32)(current_time - app->last_time) / 1000000000.0f; app->frame_count++; app->fps_timer += dt; app->last_time = current_time; app->time += dt; - + if (app->fps_timer >= 3.0f) { if (!app->repl_mode) { - float avg_fps = app->frame_count / app->fps_timer; + f32 avg_fps = app->frame_count / app->fps_timer; pxl8_info("FPS: %.1f", avg_fps); } app->frame_count = 0; app->fps_timer = 0.0f; } - time_t current_mod_time = get_file_mod_time(app->script_path); - if (current_mod_time != app->script_mod_time && current_mod_time != 0) { - pxl8_info("Script modified, reloading: %s", app->script_path); - app->script_mod_time = current_mod_time; - load_script(app); - } + pxl8_script_check_reload(app->script); if (app->repl_mode) { pxl8_repl_command* cmd = pxl8_repl_pop_command(&app->repl); if (cmd) { - pxl8_result result = pxl8_lua_eval_fennel(app->lua, cmd->buffer); - if (result == PXL8_OK) { - if (lua_gettop(app->lua) > 0 && !lua_isnil(app->lua, -1)) { - const char* result_str = lua_tostring(app->lua, -1); - if (result_str) { - fprintf(stdout, "%s\n", result_str); - } else if (lua_isuserdata(app->lua, -1)) { - fprintf(stdout, "\n"); - } else if (lua_iscfunction(app->lua, -1)) { - fprintf(stdout, "\n"); - } else if (lua_istable(app->lua, -1)) { - fprintf(stdout, "\n"); - } else { - fprintf(stdout, "<%s>\n", lua_typename(app->lua, lua_type(app->lua, -1))); - } - lua_pop(app->lua, 1); - } - } else { - const char* error_msg = lua_tostring(app->lua, -1); - pxl8_error("%s", error_msg ? error_msg : "Unknown error"); - lua_pop(app->lua, 1); + pxl8_result result = pxl8_script_eval(app->script, cmd->buffer); + if (result != PXL8_OK) { + pxl8_error("%s", pxl8_script_get_last_error(app->script)); } SDL_free(cmd); } } - + if (app->script_loaded) { - lua_getglobal(app->lua, "update"); - if (lua_isfunction(app->lua, -1)) { - lua_pushnumber(app->lua, dt); - lua_pcall(app->lua, 1, 0, 0); - } else { - lua_pop(app->lua, 1); - } - - lua_getglobal(app->lua, "draw"); - if (lua_isfunction(app->lua, -1)) { - int result = lua_pcall(app->lua, 0, 0, 0); - if (result != 0) { - pxl8_error("Error calling draw: %s", lua_tostring(app->lua, -1)); - lua_pop(app->lua, 1); - } - } else { - pxl8_warn("draw is not a function, type: %s", lua_typename(app->lua, lua_type(app->lua, -1))); - lua_pop(app->lua, 1); + pxl8_script_call_function_f32(app->script, "update", dt); + + pxl8_result frame_result = pxl8_script_call_function(app->script, "frame"); + if (frame_result == PXL8_ERROR_SCRIPT_ERROR) { + pxl8_error("Error calling frame: %s", pxl8_script_get_last_error(app->script)); } } else { - pxl8_clr(&app->gfx, 32); + pxl8_clr(app->gfx, 32); i32 render_width, render_height; pxl8_gfx_get_resolution_dimensions(app->resolution, &render_width, &render_height); - for (int y = 0; y < render_height; y += 24) { - for (int x = 0; x < render_width; x += 32) { - u32 color = ((x / 32) + (y / 24) + (int)(app->time * 2)) % 8; - pxl8_rect_fill(&app->gfx, x, y, 31, 23, color); + for (i32 y = 0; y < render_height; y += 24) { + for (i32 x = 0; x < render_width; x += 32) { + u32 color = ((x / 32) + (y / 24) + (i32)(app->time * 2)) % 8; + pxl8_rect_fill(app->gfx, x, y, 31, 23, color); } } } i32 render_width, render_height; pxl8_gfx_get_resolution_dimensions(app->resolution, &render_width, &render_height); + + f32 scale = fminf(bounds.w / (f32)render_width, bounds.h / (f32)render_height); + i32 scaled_width = (i32)(render_width * scale); + i32 scaled_height = (i32)(render_height * scale); + i32 offset_x = (bounds.w - scaled_width) / 2; + i32 offset_y = (bounds.h - scaled_height) / 2; - float scale = fminf(width / (float)render_width, height / (float)render_height); - int scaled_width = (int)(render_width * scale); - int scaled_height = (int)(render_height * scale); - int offset_x = (width - scaled_width) / 2; - int offset_y = (height - scaled_height) / 2; - - pxl8_gfx_viewport(&app->gfx, offset_x, offset_y, scaled_width, scaled_height); - pxl8_gfx_upload_framebuffer(&app->gfx); - pxl8_gfx_upload_atlas(&app->gfx); - pxl8_gfx_present(&app->gfx); + pxl8_gfx_viewport(app->gfx, offset_x, offset_y, scaled_width, scaled_height); + pxl8_gfx_upload_framebuffer(app->gfx); + pxl8_gfx_upload_atlas(app->gfx); + pxl8_gfx_present(app->gfx); SDL_memset(app->input.keys_pressed, 0, sizeof(app->input.keys_pressed)); @@ -520,9 +430,9 @@ void SDL_AppQuit(void* appstate, SDL_AppResult result) { free(app->cart); app->cart = NULL; } - pxl8_lua_shutdown(app->lua); - pxl8_gfx_shutdown(&app->gfx); + pxl8_script_destroy(app->script); + pxl8_gfx_destroy(app->gfx); } - + SDL_Quit(); } diff --git a/src/pxl8_ase.c b/src/pxl8_ase.c index c1fb9c0..ad89582 100644 --- a/src/pxl8_ase.c +++ b/src/pxl8_ase.c @@ -178,7 +178,7 @@ static pxl8_result parse_cel_chunk(const u8* data, u32 chunk_size, pxl8_ase_cel* } mz_ulong dest_len = pixel_data_size; - int result = mz_uncompress(cel->pixel_data, &dest_len, data + 20, compressed_data_size); + i32 result = mz_uncompress(cel->pixel_data, &dest_len, data + 20, compressed_data_size); if (result != MZ_OK) { pxl8_error("Failed to decompress cel data: miniz error %d", result); SDL_free(cel->pixel_data); @@ -358,13 +358,13 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) { pxl8_io_free_binary_data(file_data); if (result != PXL8_OK) { - pxl8_ase_free(ase_file); + pxl8_ase_destroy(ase_file); } return result; } -void pxl8_ase_free(pxl8_ase_file* ase_file) { +void pxl8_ase_destroy(pxl8_ase_file* ase_file) { if (!ase_file) return; if (ase_file->frames) { diff --git a/src/pxl8_ase.h b/src/pxl8_ase.h index 2ff3460..947b87a 100644 --- a/src/pxl8_ase.h +++ b/src/pxl8_ase.h @@ -91,7 +91,7 @@ extern "C" { #endif pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file); -void pxl8_ase_free(pxl8_ase_file* ase_file); +void pxl8_ase_destroy(pxl8_ase_file* ase_file); #ifdef __cplusplus } diff --git a/src/pxl8_cart.c b/src/pxl8_cart.c index 9fefeea..fd451fb 100644 --- a/src/pxl8_cart.c +++ b/src/pxl8_cart.c @@ -10,6 +10,14 @@ #include "pxl8_cart.h" #include "pxl8_macros.h" +struct pxl8_cart { + void* archive_data; + size_t archive_size; + char* base_path; + bool is_folder; + bool is_mounted; + char* name; +}; static pxl8_cart* __pxl8_current_cart = NULL; static char* __pxl8_original_cwd = NULL; @@ -73,10 +81,29 @@ static bool is_pxc_file(const char* path) { return len > 4 && strcmp(path + len - 4, ".pxc") == 0; } +pxl8_cart* pxl8_cart_create(void) { + pxl8_cart* cart = calloc(1, sizeof(pxl8_cart)); + return cart; +} + pxl8_cart* pxl8_cart_current(void) { return __pxl8_current_cart; } +void pxl8_cart_destroy(pxl8_cart* cart) { + if (!cart) return; + pxl8_cart_unload(cart); + free(cart); +} + +const char* pxl8_cart_get_base_path(const pxl8_cart* cart) { + return cart ? cart->base_path : NULL; +} + +const char* pxl8_cart_get_name(const pxl8_cart* cart) { + return cart ? cart->name : NULL; +} + pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) { if (!cart || !path) return PXL8_ERROR_NULL_POINTER; @@ -146,8 +173,8 @@ pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) { return PXL8_ERROR_INVALID_FORMAT; } - int num_files = mz_zip_reader_get_num_files(&zip); - for (int i = 0; i < num_files; i++) { + i32 num_files = mz_zip_reader_get_num_files(&zip); + for (i32 i = 0; i < num_files; i++) { mz_zip_archive_file_stat file_stat; if (!mz_zip_reader_file_stat(&zip, i, &file_stat)) continue; diff --git a/src/pxl8_cart.h b/src/pxl8_cart.h index 52b101c..016224b 100644 --- a/src/pxl8_cart.h +++ b/src/pxl8_cart.h @@ -2,21 +2,18 @@ #include "pxl8_types.h" -typedef struct pxl8_cart { - void* archive_data; - size_t archive_size; - char* base_path; - bool is_folder; - bool is_mounted; - char* name; -} pxl8_cart; +typedef struct pxl8_cart pxl8_cart; #ifdef __cplusplus extern "C" { #endif +pxl8_cart* pxl8_cart_create(void); pxl8_cart* pxl8_cart_current(void); +void pxl8_cart_destroy(pxl8_cart* cart); bool pxl8_cart_file_exists(const pxl8_cart* cart, const char* path); +const char* pxl8_cart_get_base_path(const pxl8_cart* cart); +const char* pxl8_cart_get_name(const pxl8_cart* cart); pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path); pxl8_result pxl8_cart_mount(pxl8_cart* cart); pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path); diff --git a/src/pxl8_gfx.c b/src/pxl8_gfx.c index 9c3b522..16b56bf 100644 --- a/src/pxl8_gfx.c +++ b/src/pxl8_gfx.c @@ -1,11 +1,64 @@ +#include #include +#include + +#include #include "pxl8_ase.h" #include "pxl8_blit.h" #include "pxl8_gfx.h" #include "pxl8_macros.h" +#include "pxl8_math.h" #include "pxl8_types.h" +typedef struct pxl8_atlas_entry { + char path[256]; + u32 sprite_id; + i32 x, y, w, h; +} pxl8_atlas_entry; + +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_color_mode color_mode; + u8* framebuffer; + i32 framebuffer_height; + i32 framebuffer_width; + bool initialized; + + 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; + + i32 viewport_height; + i32 viewport_width; + i32 viewport_x; + i32 viewport_y; + + bool backface_culling; + pxl8_mat4 model; + pxl8_mat4 projection; + pxl8_mat4 view; + bool wireframe; + f32* zbuffer; + i32 zbuffer_height; + i32 zbuffer_width; +}; + static u32 pxl8_get_palette_size(pxl8_color_mode mode) { switch (mode) { case PXL8_COLOR_MODE_HICOLOR: return 0; @@ -38,172 +91,206 @@ void pxl8_gfx_get_resolution_dimensions(pxl8_resolution resolution, i32* width, } } -pxl8_result pxl8_gfx_init( - pxl8_gfx_ctx* ctx, - pxl8_color_mode mode, - pxl8_resolution resolution, - const char* title, - i32 window_width, - i32 window_height -) { - memset(ctx, 0, sizeof(pxl8_gfx_ctx)); - - ctx->color_mode = mode; - pxl8_gfx_get_resolution_dimensions(resolution, &ctx->framebuffer_width, &ctx->framebuffer_height); - - ctx->window = SDL_CreateWindow( +pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) { + pxl8_bounds bounds = {0}; + if (!gfx || !gfx->window) { + return bounds; + } + SDL_GetWindowPosition(gfx->window, &bounds.x, &bounds.y); + SDL_GetWindowSize(gfx->window, &bounds.w, &bounds.h); + return bounds; +} + +pxl8_color_mode pxl8_gfx_get_color_mode(pxl8_gfx* gfx) { + return gfx ? gfx->color_mode : PXL8_COLOR_MODE_FAMI; +} + +u8* pxl8_gfx_get_framebuffer(pxl8_gfx* gfx) { + return gfx ? gfx->framebuffer : NULL; +} + +i32 pxl8_gfx_get_height(const pxl8_gfx* gfx) { + return gfx ? gfx->framebuffer_height : 0; +} + +i32 pxl8_gfx_get_width(const pxl8_gfx* gfx) { + return gfx ? gfx->framebuffer_width : 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) { + pxl8_error("Failed to allocate graphics context"); + return NULL; + } + + gfx->color_mode = mode; + pxl8_gfx_get_resolution_dimensions(resolution, &gfx->framebuffer_width, &gfx->framebuffer_height); + + gfx->window = SDL_CreateWindow( title, window_width, window_height, SDL_WINDOW_RESIZABLE ); - - if (!ctx->window) { + + if (!gfx->window) { pxl8_error("Failed to create window: %s", SDL_GetError()); - return PXL8_ERROR_SYSTEM_FAILURE; + pxl8_gfx_destroy(gfx); + return NULL; } - - ctx->renderer = SDL_CreateRenderer(ctx->window, NULL); - if (!ctx->renderer) { + + gfx->renderer = SDL_CreateRenderer(gfx->window, NULL); + if (!gfx->renderer) { pxl8_error("Failed to create renderer: %s", SDL_GetError()); - SDL_DestroyWindow(ctx->window); - return PXL8_ERROR_SYSTEM_FAILURE; + pxl8_gfx_destroy(gfx); + return NULL; } - - SDL_SetRenderLogicalPresentation(ctx->renderer, - ctx->framebuffer_width, - ctx->framebuffer_height, + + SDL_SetRenderLogicalPresentation(gfx->renderer, + gfx->framebuffer_width, + gfx->framebuffer_height, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE); - - i32 bytes_per_pixel = (ctx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; - i32 fb_size = ctx->framebuffer_width * ctx->framebuffer_height * bytes_per_pixel; - ctx->framebuffer = (u8*)SDL_calloc(1, fb_size); - if (!ctx->framebuffer) { + + i32 bytes_per_pixel = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; + i32 fb_size = gfx->framebuffer_width * gfx->framebuffer_height * bytes_per_pixel; + gfx->framebuffer = (u8*)SDL_calloc(1, fb_size); + if (!gfx->framebuffer) { pxl8_error("Failed to allocate framebuffer"); - pxl8_gfx_shutdown(ctx); - return PXL8_ERROR_OUT_OF_MEMORY; + pxl8_gfx_destroy(gfx); + return NULL; } - - ctx->framebuffer_texture = SDL_CreateTexture( - ctx->renderer, + + gfx->framebuffer_texture = SDL_CreateTexture( + gfx->renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, - ctx->framebuffer_width, - ctx->framebuffer_height + gfx->framebuffer_width, + gfx->framebuffer_height ); - - SDL_SetTextureScaleMode(ctx->framebuffer_texture, SDL_SCALEMODE_NEAREST); - - if (!ctx->framebuffer_texture) { + + SDL_SetTextureScaleMode(gfx->framebuffer_texture, SDL_SCALEMODE_NEAREST); + + if (!gfx->framebuffer_texture) { pxl8_error("Failed to create framebuffer texture: %s", SDL_GetError()); - pxl8_gfx_shutdown(ctx); - return PXL8_ERROR_SYSTEM_FAILURE; + pxl8_gfx_destroy(gfx); + return NULL; } - - ctx->palette_size = pxl8_get_palette_size(mode); - if (ctx->palette_size > 0) { - ctx->palette = (u32*)SDL_calloc(ctx->palette_size, sizeof(u32)); - if (!ctx->palette) { + + gfx->palette_size = pxl8_get_palette_size(mode); + if (gfx->palette_size > 0) { + gfx->palette = (u32*)SDL_calloc(gfx->palette_size, sizeof(u32)); + if (!gfx->palette) { pxl8_error("Failed to allocate palette"); - pxl8_gfx_shutdown(ctx); - return PXL8_ERROR_OUT_OF_MEMORY; + pxl8_gfx_destroy(gfx); + return NULL; } - - for (u32 i = 0; i < 256 && i < ctx->palette_size; i++) { + + for (u32 i = 0; i < 256 && i < gfx->palette_size; i++) { u8 gray = (u8)(i * 255 / 255); - ctx->palette[i] = 0xFF000000 | (gray << 16) | (gray << 8) | gray; + gfx->palette[i] = 0xFF000000 | (gray << 16) | (gray << 8) | gray; } } - - ctx->viewport_x = 0; - ctx->viewport_y = 0; - ctx->viewport_width = ctx->framebuffer_width; - ctx->viewport_height = ctx->framebuffer_height; - - ctx->initialized = true; - return PXL8_OK; + + gfx->viewport_x = 0; + gfx->viewport_y = 0; + gfx->viewport_width = gfx->framebuffer_width; + gfx->viewport_height = gfx->framebuffer_height; + + gfx->backface_culling = true; + gfx->model = pxl8_mat4_identity(); + gfx->projection = pxl8_mat4_identity(); + gfx->view = pxl8_mat4_identity(); + gfx->wireframe = false; + gfx->zbuffer = NULL; + gfx->zbuffer_height = 0; + gfx->zbuffer_width = 0; + + gfx->initialized = true; + return gfx; } -void pxl8_gfx_shutdown(pxl8_gfx_ctx* ctx) { - if (!ctx) return; +void pxl8_gfx_destroy(pxl8_gfx* gfx) { + if (!gfx) return; - if (ctx->framebuffer_texture) { - SDL_DestroyTexture(ctx->framebuffer_texture); - ctx->framebuffer_texture = NULL; + if (gfx->framebuffer_texture) { + SDL_DestroyTexture(gfx->framebuffer_texture); + gfx->framebuffer_texture = NULL; } - if (ctx->sprite_atlas_texture) { - SDL_DestroyTexture(ctx->sprite_atlas_texture); - ctx->sprite_atlas_texture = NULL; + if (gfx->sprite_atlas_texture) { + SDL_DestroyTexture(gfx->sprite_atlas_texture); + gfx->sprite_atlas_texture = NULL; } - if (ctx->renderer) { - SDL_DestroyRenderer(ctx->renderer); - ctx->renderer = NULL; + if (gfx->renderer) { + SDL_DestroyRenderer(gfx->renderer); + gfx->renderer = NULL; } - if (ctx->window) { - SDL_DestroyWindow(ctx->window); - ctx->window = NULL; + if (gfx->window) { + SDL_DestroyWindow(gfx->window); + gfx->window = NULL; } - SDL_free(ctx->framebuffer); - SDL_free(ctx->palette); - SDL_free(ctx->atlas); - SDL_free(ctx->atlas_entries); - - ctx->initialized = false; + SDL_free(gfx->framebuffer); + SDL_free(gfx->palette); + SDL_free(gfx->atlas); + SDL_free(gfx->atlas_entries); + SDL_free(gfx->zbuffer); + + SDL_free(gfx); } // resource loading -pxl8_result pxl8_gfx_init_atlas(pxl8_gfx_ctx* ctx, u32 width, u32 height) { - if (!ctx || !ctx->initialized) return PXL8_ERROR_INVALID_ARGUMENT; +pxl8_result pxl8_gfx_init_atlas(pxl8_gfx* gfx, u32 width, u32 height) { + if (!gfx || !gfx->initialized) return PXL8_ERROR_INVALID_ARGUMENT; - ctx->sprite_atlas_width = width; - ctx->sprite_atlas_height = height; + gfx->sprite_atlas_width = width; + gfx->sprite_atlas_height = height; - i32 bytes_per_pixel = (ctx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; - ctx->atlas = (u8*)SDL_calloc(width * height, bytes_per_pixel); - if (!ctx->atlas) { + i32 bytes_per_pixel = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; + gfx->atlas = (u8*)SDL_calloc(width * height, bytes_per_pixel); + if (!gfx->atlas) { return PXL8_ERROR_OUT_OF_MEMORY; } - ctx->sprite_atlas_texture = SDL_CreateTexture( - ctx->renderer, + gfx->sprite_atlas_texture = SDL_CreateTexture( + gfx->renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, width, height ); - if (!ctx->sprite_atlas_texture) { - SDL_free(ctx->atlas); - ctx->atlas = NULL; + if (!gfx->sprite_atlas_texture) { + SDL_free(gfx->atlas); + gfx->atlas = NULL; return PXL8_ERROR_SYSTEM_FAILURE; } - ctx->atlas_entries_cap = 256; - ctx->atlas_entries = (pxl8_atlas_entry*)SDL_calloc(ctx->atlas_entries_cap, sizeof(pxl8_atlas_entry)); - if (!ctx->atlas_entries) { - SDL_DestroyTexture(ctx->sprite_atlas_texture); - SDL_free(ctx->atlas); + 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; } - ctx->sprite_frames_per_row = width / 128; - if (ctx->sprite_frames_per_row == 0) ctx->sprite_frames_per_row = 1; + gfx->sprite_frames_per_row = width / 128; + if (gfx->sprite_frames_per_row == 0) gfx->sprite_frames_per_row = 1; return PXL8_OK; } -pxl8_result pxl8_gfx_load_sprite(pxl8_gfx_ctx* ctx, const char* path) { - if (!ctx || !ctx->initialized || !path) return PXL8_ERROR_INVALID_ARGUMENT; - if (!ctx->atlas) { +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; } - for (u32 i = 0; i < ctx->atlas_entries_len; i++) { - if (strcmp(ctx->atlas_entries[i].path, path) == 0) { + for (u32 i = 0; i < gfx->atlas_entries_len; i++) { + if (strcmp(gfx->atlas_entries[i].path, path) == 0) { return PXL8_OK; } } @@ -217,78 +304,78 @@ pxl8_result pxl8_gfx_load_sprite(pxl8_gfx_ctx* ctx, const char* path) { if (ase_file.frame_count == 0) { pxl8_error("No frames in ASE file"); - pxl8_ase_free(&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 (ctx->sprite_frame_width == 0 || ctx->sprite_frame_height == 0) { - ctx->sprite_frame_width = sprite_w; - ctx->sprite_frame_height = sprite_h; + 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 = ctx->atlas_entries_len; - u32 frames_per_row = ctx->sprite_frames_per_row; - u32 atlas_x = (id % frames_per_row) * ctx->sprite_frame_width; - u32 atlas_y = (id / frames_per_row) * ctx->sprite_frame_height; + 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 > ctx->sprite_atlas_width || - atlas_y + sprite_h > ctx->sprite_atlas_height) { + if (atlas_x + sprite_w > gfx->sprite_atlas_width || + atlas_y + sprite_h > gfx->sprite_atlas_height) { pxl8_error("Sprite doesn't fit in atlas"); - pxl8_ase_free(&ase_file); + pxl8_ase_destroy(&ase_file); return PXL8_ERROR_INVALID_SIZE; } - i32 bytes_per_pixel = (ctx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; + 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) * ctx->sprite_atlas_width + (atlas_x + x); + u32 atlas_idx = (atlas_y + y) * gfx->sprite_atlas_width + (atlas_x + x); if (bytes_per_pixel == 4) { - ((u32*)ctx->atlas)[atlas_idx] = ((u32*)ase_file.frames[0].pixels)[frame_idx]; + ((u32*)gfx->atlas)[atlas_idx] = ((u32*)ase_file.frames[0].pixels)[frame_idx]; } else { - ctx->atlas[atlas_idx] = ase_file.frames[0].pixels[frame_idx]; + gfx->atlas[atlas_idx] = ase_file.frames[0].pixels[frame_idx]; } } } - if (ctx->atlas_entries_len >= ctx->atlas_entries_cap) { - ctx->atlas_entries_cap *= 2; + if (gfx->atlas_entries_len >= gfx->atlas_entries_cap) { + gfx->atlas_entries_cap *= 2; pxl8_atlas_entry* new_entries = (pxl8_atlas_entry*)SDL_realloc( - ctx->atlas_entries, - ctx->atlas_entries_cap * sizeof(pxl8_atlas_entry) + gfx->atlas_entries, + gfx->atlas_entries_cap * sizeof(pxl8_atlas_entry) ); if (!new_entries) { - pxl8_ase_free(&ase_file); + pxl8_ase_destroy(&ase_file); return PXL8_ERROR_OUT_OF_MEMORY; } - ctx->atlas_entries = new_entries; + gfx->atlas_entries = new_entries; } - ctx->atlas_entries[id].x = atlas_x; - ctx->atlas_entries[id].y = atlas_y; - ctx->atlas_entries[id].w = sprite_w; - ctx->atlas_entries[id].h = sprite_h; - ctx->atlas_entries[id].sprite_id = id; - strncpy(ctx->atlas_entries[id].path, path, sizeof(ctx->atlas_entries[id].path) - 1); - ctx->atlas_entries[id].path[sizeof(ctx->atlas_entries[id].path) - 1] = '\0'; - ctx->atlas_entries_len++; + 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++; - ctx->atlas_dirty = true; + gfx->atlas_dirty = true; - pxl8_ase_free(&ase_file); + 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_result pxl8_gfx_load_palette(pxl8_gfx_ctx* ctx, const char* path) { - if (!ctx || !ctx->initialized || !path) return PXL8_ERROR_INVALID_ARGUMENT; - if (ctx->color_mode == PXL8_COLOR_MODE_HICOLOR) return PXL8_OK; +pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path) { + if (!gfx || !gfx->initialized || !path) return PXL8_ERROR_INVALID_ARGUMENT; + if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) return PXL8_OK; pxl8_debug("Loading palette from: %s", path); @@ -301,46 +388,46 @@ pxl8_result pxl8_gfx_load_palette(pxl8_gfx_ctx* ctx, const char* path) { if (ase_file.palette.entry_count == 0) { pxl8_error("No palette data in ASE file"); - pxl8_ase_free(&ase_file); + pxl8_ase_destroy(&ase_file); return PXL8_ERROR_INVALID_FORMAT; } - u32 copy_size = (ase_file.palette.entry_count < ctx->palette_size) ? ase_file.palette.entry_count : ctx->palette_size; - memcpy(ctx->palette, ase_file.palette.colors, copy_size * sizeof(u32)); + 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 (ctx->color_mode != PXL8_COLOR_MODE_HICOLOR && ctx->framebuffer_texture) { + if (gfx->color_mode != PXL8_COLOR_MODE_HICOLOR && gfx->framebuffer_texture) { SDL_Color colors[256]; - for (u32 i = 0; i < 256 && i < ctx->palette_size; i++) { - colors[i].r = (ctx->palette[i] >> 16) & 0xFF; - colors[i].g = (ctx->palette[i] >> 8) & 0xFF; - colors[i].b = ctx->palette[i] & 0xFF; - colors[i].a = (ctx->palette[i] >> 24) & 0xFF; + 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); } - pxl8_ase_free(&ase_file); + pxl8_ase_destroy(&ase_file); pxl8_debug("Loaded palette with %u colors", copy_size); return PXL8_OK; } -pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx_ctx* ctx) { - (void)ctx; +pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx) { + (void)gfx; return PXL8_OK; } // rendering pipeline -void pxl8_gfx_upload_framebuffer(pxl8_gfx_ctx* ctx) { - if (!ctx || !ctx->initialized || !ctx->framebuffer_texture) return; +void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) { + if (!gfx || !gfx->initialized || !gfx->framebuffer_texture) return; - if (ctx->color_mode == PXL8_COLOR_MODE_HICOLOR) { - SDL_UpdateTexture(ctx->framebuffer_texture, NULL, ctx->framebuffer, - ctx->framebuffer_width * 4); + if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) { + SDL_UpdateTexture(gfx->framebuffer_texture, NULL, gfx->framebuffer, + gfx->framebuffer_width * 4); } else { static u32* rgba_buffer = NULL; static size_t buffer_size = 0; - size_t needed_size = ctx->framebuffer_width * ctx->framebuffer_height; + size_t needed_size = gfx->framebuffer_width * gfx->framebuffer_height; if (buffer_size < needed_size) { rgba_buffer = (u32*)SDL_realloc(rgba_buffer, needed_size * 4); @@ -349,108 +436,108 @@ void pxl8_gfx_upload_framebuffer(pxl8_gfx_ctx* ctx) { if (!rgba_buffer) return; - for (i32 i = 0; i < ctx->framebuffer_width * ctx->framebuffer_height; i++) { - u8 index = ctx->framebuffer[i]; - rgba_buffer[i] = (index < ctx->palette_size) ? ctx->palette[index] : 0xFF000000; + for (i32 i = 0; i < gfx->framebuffer_width * gfx->framebuffer_height; i++) { + u8 index = gfx->framebuffer[i]; + rgba_buffer[i] = (index < gfx->palette_size) ? gfx->palette[index] : 0xFF000000; } - SDL_UpdateTexture(ctx->framebuffer_texture, NULL, rgba_buffer, - ctx->framebuffer_width * 4); + SDL_UpdateTexture(gfx->framebuffer_texture, NULL, rgba_buffer, + gfx->framebuffer_width * 4); } } -void pxl8_gfx_upload_atlas(pxl8_gfx_ctx* ctx) { - if (!ctx || !ctx->initialized || !ctx->sprite_atlas_texture || !ctx->atlas_dirty) return; +void pxl8_gfx_upload_atlas(pxl8_gfx* gfx) { + if (!gfx || !gfx->initialized || !gfx->sprite_atlas_texture || !gfx->atlas_dirty) return; - if (ctx->color_mode == PXL8_COLOR_MODE_HICOLOR) { - SDL_UpdateTexture(ctx->sprite_atlas_texture, NULL, ctx->atlas, - ctx->sprite_atlas_width * 4); + if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) { + SDL_UpdateTexture(gfx->sprite_atlas_texture, NULL, gfx->atlas, + gfx->sprite_atlas_width * 4); } else { - u32* rgba_buffer = (u32*)SDL_malloc(ctx->sprite_atlas_width * ctx->sprite_atlas_height * 4); + u32* rgba_buffer = (u32*)SDL_malloc(gfx->sprite_atlas_width * gfx->sprite_atlas_height * 4); if (!rgba_buffer) return; - for (u32 i = 0; i < ctx->sprite_atlas_width * ctx->sprite_atlas_height; i++) { - u8 index = ctx->atlas[i]; - rgba_buffer[i] = (index < ctx->palette_size) ? ctx->palette[index] : 0x00000000; + for (u32 i = 0; i < gfx->sprite_atlas_width * gfx->sprite_atlas_height; i++) { + u8 index = gfx->atlas[i]; + rgba_buffer[i] = (index < gfx->palette_size) ? gfx->palette[index] : 0x00000000; } - SDL_UpdateTexture(ctx->sprite_atlas_texture, NULL, rgba_buffer, - ctx->sprite_atlas_width * 4); + SDL_UpdateTexture(gfx->sprite_atlas_texture, NULL, rgba_buffer, + gfx->sprite_atlas_width * 4); SDL_free(rgba_buffer); } - ctx->atlas_dirty = false; + gfx->atlas_dirty = false; pxl8_debug("Atlas uploaded to GPU"); } -void pxl8_gfx_present(pxl8_gfx_ctx* ctx) { - if (!ctx || !ctx->initialized) return; +void pxl8_gfx_present(pxl8_gfx* gfx) { + if (!gfx || !gfx->initialized) return; - SDL_SetRenderDrawColor(ctx->renderer, 0, 0, 0, 255); - SDL_RenderClear(ctx->renderer); + SDL_SetRenderDrawColor(gfx->renderer, 0, 0, 0, 255); + SDL_RenderClear(gfx->renderer); - if (ctx->framebuffer_texture) { - SDL_RenderTexture(ctx->renderer, ctx->framebuffer_texture, NULL, NULL); + if (gfx->framebuffer_texture) { + SDL_RenderTexture(gfx->renderer, gfx->framebuffer_texture, NULL, NULL); } - SDL_RenderPresent(ctx->renderer); + SDL_RenderPresent(gfx->renderer); } -void pxl8_gfx_viewport(pxl8_gfx_ctx* ctx, i32 x, i32 y, i32 width, i32 height) { - if (!ctx) return; - ctx->viewport_x = x; - ctx->viewport_y = y; - ctx->viewport_width = width; - ctx->viewport_height = height; +void pxl8_gfx_viewport(pxl8_gfx* gfx, i32 x, i32 y, i32 width, i32 height) { + if (!gfx) return; + gfx->viewport_x = x; + gfx->viewport_y = y; + gfx->viewport_width = width; + gfx->viewport_height = height; } -void pxl8_gfx_project(pxl8_gfx_ctx* ctx, f32 left, f32 right, f32 top, f32 bottom) { - (void)ctx; (void)left; (void)right; (void)top; (void)bottom; +void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom) { + (void)gfx; (void)left; (void)right; (void)top; (void)bottom; } // drawing primitives -void pxl8_clr(pxl8_gfx_ctx* ctx, u32 color) { - if (!ctx || !ctx->framebuffer) return; +void pxl8_clr(pxl8_gfx* gfx, u32 color) { + if (!gfx || !gfx->framebuffer) return; - i32 bytes_per_pixel = (ctx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; - i32 size = ctx->framebuffer_width * ctx->framebuffer_height; + i32 bytes_per_pixel = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; + i32 size = gfx->framebuffer_width * gfx->framebuffer_height; if (bytes_per_pixel == 4) { - u32* fb32 = (u32*)ctx->framebuffer; + u32* fb32 = (u32*)gfx->framebuffer; for (i32 i = 0; i < size; i++) { fb32[i] = color; } } else { - memset(ctx->framebuffer, color & 0xFF, size); + memset(gfx->framebuffer, color & 0xFF, size); } } -void pxl8_pixel(pxl8_gfx_ctx* ctx, i32 x, i32 y, u32 color) { - if (!ctx || !ctx->framebuffer) return; - if (x < 0 || x >= ctx->framebuffer_width || y < 0 || y >= ctx->framebuffer_height) return; +void pxl8_pixel(pxl8_gfx* gfx, i32 x, i32 y, u32 color) { + if (!gfx || !gfx->framebuffer) return; + if (x < 0 || x >= gfx->framebuffer_width || y < 0 || y >= gfx->framebuffer_height) return; - i32 idx = y * ctx->framebuffer_width + x; - if (ctx->color_mode == PXL8_COLOR_MODE_HICOLOR) { - ((u32*)ctx->framebuffer)[idx] = color; + i32 idx = y * gfx->framebuffer_width + x; + if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) { + ((u32*)gfx->framebuffer)[idx] = color; } else { - ctx->framebuffer[idx] = color & 0xFF; + gfx->framebuffer[idx] = color & 0xFF; } } -u32 pxl8_get_pixel(pxl8_gfx_ctx* ctx, i32 x, i32 y) { - if (!ctx || !ctx->framebuffer) return 0; - if (x < 0 || x >= ctx->framebuffer_width || y < 0 || y >= ctx->framebuffer_height) return 0; +u32 pxl8_get_pixel(pxl8_gfx* gfx, i32 x, i32 y) { + if (!gfx || !gfx->framebuffer) return 0; + if (x < 0 || x >= gfx->framebuffer_width || y < 0 || y >= gfx->framebuffer_height) return 0; - i32 idx = y * ctx->framebuffer_width + x; - if (ctx->color_mode == PXL8_COLOR_MODE_HICOLOR) { - return ((u32*)ctx->framebuffer)[idx]; + i32 idx = y * gfx->framebuffer_width + x; + if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) { + return ((u32*)gfx->framebuffer)[idx]; } else { - return ctx->framebuffer[idx]; + return gfx->framebuffer[idx]; } } -void pxl8_line(pxl8_gfx_ctx* ctx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color) { - if (!ctx) return; +void pxl8_line(pxl8_gfx* gfx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color) { + if (!gfx) return; i32 dx = abs(x1 - x0); i32 dy = abs(y1 - y0); @@ -459,7 +546,7 @@ void pxl8_line(pxl8_gfx_ctx* ctx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color) { i32 err = dx - dy; while (1) { - pxl8_pixel(ctx, x0, y0, color); + pxl8_pixel(gfx, x0, y0, color); if (x0 == x1 && y0 == y1) break; @@ -475,41 +562,41 @@ void pxl8_line(pxl8_gfx_ctx* ctx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color) { } } -void pxl8_rect(pxl8_gfx_ctx* ctx, i32 x, i32 y, i32 w, i32 h, u32 color) { - if (!ctx) return; +void pxl8_rect(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) { + if (!gfx) return; - pxl8_line(ctx, x, y, x + w - 1, y, color); - pxl8_line(ctx, x + w - 1, y, x + w - 1, y + h - 1, color); - pxl8_line(ctx, x + w - 1, y + h - 1, x, y + h - 1, color); - pxl8_line(ctx, x, y + h - 1, x, y, color); + pxl8_line(gfx, x, y, x + w - 1, y, color); + pxl8_line(gfx, x + w - 1, y, x + w - 1, y + h - 1, color); + pxl8_line(gfx, x + w - 1, y + h - 1, x, y + h - 1, color); + pxl8_line(gfx, x, y + h - 1, x, y, color); } -void pxl8_rect_fill(pxl8_gfx_ctx* ctx, i32 x, i32 y, i32 w, i32 h, u32 color) { - if (!ctx) return; +void pxl8_rect_fill(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) { + if (!gfx) return; for (i32 py = y; py < y + h; py++) { for (i32 px = x; px < x + w; px++) { - pxl8_pixel(ctx, px, py, color); + pxl8_pixel(gfx, px, py, color); } } } -void pxl8_circle(pxl8_gfx_ctx* ctx, i32 cx, i32 cy, i32 radius, u32 color) { - if (!ctx) return; +void pxl8_circle(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color) { + if (!gfx) return; i32 x = radius; i32 y = 0; i32 err = 0; while (x >= y) { - pxl8_pixel(ctx, cx + x, cy + y, color); - pxl8_pixel(ctx, cx + y, cy + x, color); - pxl8_pixel(ctx, cx - y, cy + x, color); - pxl8_pixel(ctx, cx - x, cy + y, color); - pxl8_pixel(ctx, cx - x, cy - y, color); - pxl8_pixel(ctx, cx - y, cy - x, color); - pxl8_pixel(ctx, cx + y, cy - x, color); - pxl8_pixel(ctx, cx + x, cy - y, color); + pxl8_pixel(gfx, cx + x, cy + y, color); + pxl8_pixel(gfx, cx + y, cy + x, color); + pxl8_pixel(gfx, cx - y, cy + x, color); + pxl8_pixel(gfx, cx - x, cy + y, color); + pxl8_pixel(gfx, cx - x, cy - y, color); + pxl8_pixel(gfx, cx - y, cy - x, color); + pxl8_pixel(gfx, cx + y, cy - x, color); + pxl8_pixel(gfx, cx + x, cy - y, color); if (err <= 0) { y += 1; @@ -521,33 +608,33 @@ void pxl8_circle(pxl8_gfx_ctx* ctx, i32 cx, i32 cy, i32 radius, u32 color) { } } -void pxl8_circle_fill(pxl8_gfx_ctx* ctx, i32 cx, i32 cy, i32 radius, u32 color) { - if (!ctx) return; +void pxl8_circle_fill(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color) { + if (!gfx) return; for (i32 y = -radius; y <= radius; y++) { for (i32 x = -radius; x <= radius; x++) { if (x * x + y * y <= radius * radius) { - pxl8_pixel(ctx, cx + x, cy + y, color); + pxl8_pixel(gfx, cx + x, cy + y, color); } } } } -void pxl8_text(pxl8_gfx_ctx* ctx, const char* text, i32 x, i32 y, u32 color) { - if (!ctx || !text) return; +void pxl8_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) { + if (!gfx || !text) return; (void)x; (void)y; (void)color; } -void pxl8_sprite(pxl8_gfx_ctx* ctx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) { - if (!ctx || !ctx->atlas || !ctx->framebuffer || sprite_id >= ctx->atlas_entries_len) return; +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 = &ctx->atlas_entries[sprite_id]; + pxl8_atlas_entry* entry = &gfx->atlas_entries[sprite_id]; i32 clip_left = (x < 0) ? -x : 0; i32 clip_top = (y < 0) ? -y : 0; - i32 clip_right = (x + w > ctx->framebuffer_width) ? x + w - ctx->framebuffer_width : 0; - i32 clip_bottom = (y + h > ctx->framebuffer_height) ? y + h - ctx->framebuffer_height : 0; + i32 clip_right = (x + w > gfx->framebuffer_width) ? x + w - gfx->framebuffer_width : 0; + i32 clip_bottom = (y + h > gfx->framebuffer_height) ? y + h - gfx->framebuffer_height : 0; i32 draw_width = w - clip_left - clip_right; i32 draw_height = h - clip_top - clip_bottom; @@ -561,32 +648,32 @@ void pxl8_sprite(pxl8_gfx_ctx* ctx, 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 = ctx->atlas + entry->y * ctx->sprite_atlas_width + entry->x; + const u8* sprite_data = gfx->atlas + entry->y * gfx->sprite_atlas_width + entry->x; - if (ctx->color_mode == PXL8_COLOR_MODE_HICOLOR) { - pxl8_blit_simd_hicolor((u32*)ctx->framebuffer, ctx->framebuffer_width, - (const u32*)sprite_data, ctx->sprite_atlas_width, x, y, w, h); + 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); } else { - pxl8_blit_simd_indexed(ctx->framebuffer, ctx->framebuffer_width, - sprite_data, ctx->sprite_atlas_width, x, y, w, h); + pxl8_blit_simd_indexed(gfx->framebuffer, gfx->framebuffer_width, + sprite_data, gfx->sprite_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 * ctx->sprite_atlas_width + src_x; - i32 dest_idx = (dest_y + py) * ctx->framebuffer_width + (dest_x + px); + i32 src_idx = src_y * gfx->sprite_atlas_width + src_x; + i32 dest_idx = (dest_y + py) * gfx->framebuffer_width + (dest_x + px); - if (ctx->color_mode == PXL8_COLOR_MODE_HICOLOR) { - u32 pixel = ((u32*)ctx->atlas)[src_idx]; + if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) { + u32 pixel = ((u32*)gfx->atlas)[src_idx]; if (pixel & 0xFF000000) { - ((u32*)ctx->framebuffer)[dest_idx] = pixel; + ((u32*)gfx->framebuffer)[dest_idx] = pixel; } } else { - u8 pixel = ctx->atlas[src_idx]; + u8 pixel = gfx->atlas[src_idx]; if (pixel != 0) { - ctx->framebuffer[dest_idx] = pixel; + gfx->framebuffer[dest_idx] = pixel; } } } @@ -595,31 +682,31 @@ void pxl8_sprite(pxl8_gfx_ctx* ctx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) { } // palette effects -void pxl8_gfx_cycle_palette(pxl8_gfx_ctx* ctx, u8 start, u8 count, i32 step) { - if (!ctx || !ctx->palette || count == 0) return; +void pxl8_gfx_cycle_palette(pxl8_gfx* gfx, u8 start, u8 count, i32 step) { + if (!gfx || !gfx->palette || count == 0) return; u32 temp[256]; for (u8 i = 0; i < count; i++) { - temp[i] = ctx->palette[start + i]; + temp[i] = gfx->palette[start + i]; } for (u8 i = 0; i < count; i++) { u8 src_idx = i; u8 dst_idx = (i + step) % count; - ctx->palette[start + dst_idx] = temp[src_idx]; + gfx->palette[start + dst_idx] = temp[src_idx]; } } -void pxl8_gfx_swap_palette(pxl8_gfx_ctx* ctx, u8 start, u8 count, u32* new_colors) { - if (!ctx || !ctx->palette || !new_colors) return; +void pxl8_gfx_swap_palette(pxl8_gfx* gfx, u8 start, u8 count, u32* new_colors) { + if (!gfx || !gfx->palette || !new_colors) return; - for (u8 i = 0; i < count && (start + i) < ctx->palette_size; i++) { - ctx->palette[start + i] = new_colors[i]; + for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) { + gfx->palette[start + i] = new_colors[i]; } } -void pxl8_gfx_fade_palette(pxl8_gfx_ctx* ctx, u8 start, u8 count, f32 amount, u32 target_color) { - if (!ctx || !ctx->palette || count == 0) return; +void pxl8_gfx_fade_palette(pxl8_gfx* gfx, u8 start, u8 count, f32 amount, u32 target_color) { + if (!gfx || !gfx->palette || count == 0) return; if (amount < 0.0f) amount = 0.0f; if (amount > 1.0f) amount = 1.0f; @@ -629,8 +716,8 @@ void pxl8_gfx_fade_palette(pxl8_gfx_ctx* ctx, u8 start, u8 count, f32 amount, u3 u8 target_b = (target_color >> 16) & 0xFF; u8 target_a = (target_color >> 24) & 0xFF; - for (u8 i = 0; i < count && (start + i) < ctx->palette_size; i++) { - u32 current = ctx->palette[start + i]; + for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) { + u32 current = gfx->palette[start + i]; u8 cur_r = current & 0xFF; u8 cur_g = (current >> 8) & 0xFF; u8 cur_b = (current >> 16) & 0xFF; @@ -641,17 +728,17 @@ void pxl8_gfx_fade_palette(pxl8_gfx_ctx* ctx, u8 start, u8 count, f32 amount, u3 u8 new_b = cur_b + (i32)((target_b - cur_b) * amount); u8 new_a = cur_a + (i32)((target_a - cur_a) * amount); - ctx->palette[start + i] = new_r | (new_g << 8) | (new_b << 16) | (new_a << 24); + gfx->palette[start + i] = new_r | (new_g << 8) | (new_b << 16) | (new_a << 24); } } -void pxl8_gfx_interpolate_palettes(pxl8_gfx_ctx* ctx, u32* palette1, u32* palette2, u8 start, u8 count, f32 t) { - if (!ctx || !ctx->palette || !palette1 || !palette2 || count == 0) return; +void pxl8_gfx_interpolate_palettes(pxl8_gfx* gfx, u32* palette1, u32* palette2, u8 start, u8 count, f32 t) { + if (!gfx || !gfx->palette || !palette1 || !palette2 || count == 0) return; if (t < 0.0f) t = 0.0f; if (t > 1.0f) t = 1.0f; - for (u8 i = 0; i < count && (start + i) < ctx->palette_size; i++) { + for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) { u32 col1 = palette1[i]; u32 col2 = palette2[i]; @@ -670,12 +757,12 @@ void pxl8_gfx_interpolate_palettes(pxl8_gfx_ctx* ctx, u32* palette1, u32* palett u8 b = b1 + (i32)((b2 - b1) * t); u8 a = a1 + (i32)((a2 - a1) * t); - ctx->palette[start + i] = r | (g << 8) | (b << 16) | (a << 24); + gfx->palette[start + i] = r | (g << 8) | (b << 16) | (a << 24); } } -void pxl8_gfx_color_ramp(pxl8_gfx_ctx* ctx, u8 start, u8 count, u32 from_color, u32 to_color) { - if (!ctx || !ctx->palette || count <= 1) return; +void pxl8_gfx_color_ramp(pxl8_gfx* gfx, u8 start, u8 count, u32 from_color, u32 to_color) { + if (!gfx || !gfx->palette || count <= 1) return; u8 from_r = from_color & 0xFF; u8 from_g = (from_color >> 8) & 0xFF; @@ -687,7 +774,7 @@ void pxl8_gfx_color_ramp(pxl8_gfx_ctx* ctx, u8 start, u8 count, u32 from_color, u8 to_b = (to_color >> 16) & 0xFF; u8 to_a = (to_color >> 24) & 0xFF; - for (u8 i = 0; i < count && (start + i) < ctx->palette_size; i++) { + for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) { f32 t = (f32)i / (f32)(count - 1); u8 r = from_r + (i32)((to_r - from_r) * t); @@ -695,24 +782,274 @@ void pxl8_gfx_color_ramp(pxl8_gfx_ctx* ctx, u8 start, u8 count, u32 from_color, u8 b = from_b + (i32)((to_b - from_b) * t); u8 a = from_a + (i32)((to_a - from_a) * t); - ctx->palette[start + i] = r | (g << 8) | (b << 16) | (a << 24); + gfx->palette[start + i] = r | (g << 8) | (b << 16) | (a << 24); } } -void pxl8_gfx_process_effects(pxl8_gfx_ctx* ctx, pxl8_effects* effects, f32 dt) { - if (!ctx || !effects) return; - +void pxl8_gfx_process_effects(pxl8_gfx* gfx, pxl8_effects* effects, f32 dt) { + if (!gfx || !effects) return; + effects->time += dt; - + for (i32 i = 0; i < 8; i++) { pxl8_palette_cycle* cycle = &effects->palette_cycles[i]; if (!cycle->active) continue; - + cycle->timer += dt * cycle->speed; if (cycle->timer >= 1.0f) { cycle->timer -= 1.0f; i32 count = cycle->end_index - cycle->start_index + 1; - pxl8_gfx_cycle_palette(ctx, cycle->start_index, count, 1); + pxl8_gfx_cycle_palette(gfx, cycle->start_index, count, 1); } } } + +static bool pxl8_3d_init_zbuffer(pxl8_gfx* gfx) { + if (gfx->zbuffer) return true; + + gfx->zbuffer_width = gfx->framebuffer_width; + gfx->zbuffer_height = gfx->framebuffer_height; + + gfx->zbuffer = (f32*)SDL_calloc(gfx->zbuffer_width * gfx->zbuffer_height, sizeof(f32)); + if (!gfx->zbuffer) { + return false; + } + + pxl8_3d_clear_zbuffer(gfx); + return true; +} + +void pxl8_3d_clear_zbuffer(pxl8_gfx* gfx) { + if (!gfx || !gfx->zbuffer) return; + for (i32 i = 0; i < gfx->zbuffer_width * gfx->zbuffer_height; i++) { + gfx->zbuffer[i] = 1e30f; + } +} + +void pxl8_3d_set_backface_culling(pxl8_gfx* gfx, bool culling) { + if (!gfx) return; + gfx->backface_culling = culling; +} + +void pxl8_3d_set_model(pxl8_gfx* gfx, pxl8_mat4 mat) { + if (!gfx) return; + gfx->model = mat; +} + +void pxl8_3d_set_projection(pxl8_gfx* gfx, pxl8_mat4 mat) { + if (!gfx) return; + gfx->projection = mat; +} + +void pxl8_3d_set_view(pxl8_gfx* gfx, pxl8_mat4 mat) { + if (!gfx) return; + gfx->view = mat; +} + +void pxl8_3d_set_wireframe(pxl8_gfx* gfx, bool wireframe) { + if (!gfx) return; + gfx->wireframe = wireframe; +} + +static inline pxl8_vec4 pxl8_transform_vertex(pxl8_gfx* gfx, pxl8_vec3 pos) { + pxl8_vec4 v = { + .x = pos.x, + .y = pos.y, + .z = pos.z, + .w = 1.0f, + }; + + pxl8_mat4 mvp = pxl8_mat4_multiply(gfx->projection, + pxl8_mat4_multiply(gfx->view, gfx->model)); + + return pxl8_mat4_multiply_vec4(mvp, v); +} + +static inline void pxl8_project_to_screen(pxl8_gfx* gfx, pxl8_vec4 clip, i32* x, i32* y, f32* z) { + if (fabsf(clip.w) < 1e-6f) { + *x = *y = 0; + *z = 1e30f; + return; + } + + f32 inv_w = 1.0f / clip.w; + f32 ndc_x = clip.x * inv_w; + f32 ndc_y = clip.y * inv_w; + + *x = (i32)((ndc_x + 1.0f) * 0.5f * gfx->framebuffer_width); + *y = (i32)((1.0f - ndc_y) * 0.5f * gfx->framebuffer_height); + *z = clip.z * inv_w; +} + +void pxl8_3d_draw_line_3d(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u32 color) { + if (!gfx) return; + + pxl8_vec4 v0 = pxl8_transform_vertex(gfx, p0); + pxl8_vec4 v1 = pxl8_transform_vertex(gfx, p1); + + i32 x0, y0, x1, y1; + f32 z0, z1; + pxl8_project_to_screen(gfx, v0, &x0, &y0, &z0); + pxl8_project_to_screen(gfx, v1, &x1, &y1, &z1); + + pxl8_line(gfx, x0, y0, x1, y1, color); +} + +static void pxl8_draw_flat_bottom_triangle( + pxl8_gfx* gfx, + i32 x0, i32 y0, f32 z0, + i32 x1, i32 y1, f32 z1, + i32 x2, i32 y2, f32 z2, + u32 color +) { + (void)z2; + if (y1 == y0) return; + + f32 inv_slope_1 = (f32)(x1 - x0) / (f32)(y1 - y0); + f32 inv_slope_2 = (f32)(x2 - x0) / (f32)(y2 - y0); + + f32 cur_x1 = (f32)x0; + f32 cur_x2 = (f32)x0; + + for (i32 y = y0; y <= y1; y++) { + if (y >= 0 && y < gfx->framebuffer_height) { + i32 xs = (i32)cur_x1; + i32 xe = (i32)cur_x2; + + if (xs > xe) { + i32 tmp = xs; xs = xe; xe = tmp; + } + + 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]) { + gfx->zbuffer[idx] = z; + pxl8_pixel(gfx, x, y, color); + } + } + } + } + + cur_x1 += inv_slope_1; + cur_x2 += inv_slope_2; + } +} + +static void pxl8_draw_flat_top_triangle( + pxl8_gfx* gfx, + i32 x0, i32 y0, f32 z0, + i32 x1, i32 y1, f32 z1, + i32 x2, i32 y2, f32 z2, + u32 color +) { + (void)z2; + if (y2 == y0) return; + + f32 inv_slope_1 = (f32)(x2 - x0) / (f32)(y2 - y0); + f32 inv_slope_2 = (f32)(x2 - x1) / (f32)(y2 - y1); + + f32 cur_x1 = (f32)x2; + f32 cur_x2 = (f32)x2; + + for (i32 y = y2; y > y0; y--) { + if (y >= 0 && y < gfx->framebuffer_height) { + i32 xs = (i32)cur_x1; + i32 xe = (i32)cur_x2; + + if (xs > xe) { + i32 tmp = xs; xs = xe; xe = tmp; + } + + 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]) { + gfx->zbuffer[idx] = z; + pxl8_pixel(gfx, x, y, color); + } + } + } + } + + cur_x1 -= inv_slope_1; + cur_x2 -= inv_slope_2; + } +} + +void pxl8_3d_draw_triangle_raw(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, u32 color) { + pxl8_triangle tri; + tri.v[0].position = v0; + tri.v[0].color = color; + tri.v[1].position = v1; + tri.v[1].color = color; + tri.v[2].position = v2; + tri.v[2].color = color; + pxl8_3d_draw_triangle(gfx, tri); +} + +void pxl8_3d_draw_triangle(pxl8_gfx* gfx, pxl8_triangle tri) { + if (!gfx || !pxl8_3d_init_zbuffer(gfx)) return; + + pxl8_vec4 v0 = pxl8_transform_vertex(gfx, tri.v[0].position); + pxl8_vec4 v1 = pxl8_transform_vertex(gfx, tri.v[1].position); + pxl8_vec4 v2 = pxl8_transform_vertex(gfx, tri.v[2].position); + + if (v0.w <= 0 || v1.w <= 0 || v2.w <= 0) return; + + i32 x0, y0, x1, y1, x2, y2; + f32 z0, z1, z2; + pxl8_project_to_screen(gfx, v0, &x0, &y0, &z0); + pxl8_project_to_screen(gfx, v1, &x1, &y1, &z1); + pxl8_project_to_screen(gfx, v2, &x2, &y2, &z2); + + if (gfx->backface_culling) { + i32 cross = (x1 - x0) * (y2 - y0) - (y1 - y0) * (x2 - x0); + if (cross >= 0) return; + } + + if (gfx->wireframe) { + u32 color = tri.v[0].color; + pxl8_line(gfx, x0, y0, x1, y1, color); + pxl8_line(gfx, x1, y1, x2, y2, color); + pxl8_line(gfx, x2, y2, x0, y0, color); + return; + } + + if (y0 > y1) { + i32 tmp_i = x0; x0 = x1; x1 = tmp_i; + tmp_i = y0; y0 = y1; y1 = tmp_i; + f32 tmp_f = z0; z0 = z1; z1 = tmp_f; + } + if (y0 > y2) { + i32 tmp_i = x0; x0 = x2; x2 = tmp_i; + tmp_i = y0; y0 = y2; y2 = tmp_i; + f32 tmp_f = z0; z0 = z2; z2 = tmp_f; + } + if (y1 > y2) { + i32 tmp_i = x1; x1 = x2; x2 = tmp_i; + tmp_i = y1; y1 = y2; y2 = tmp_i; + f32 tmp_f = z1; z1 = z2; z2 = tmp_f; + } + + u32 color = tri.v[0].color; + + if (y1 == y2) { + pxl8_draw_flat_bottom_triangle(gfx, x0, y0, z0, x1, y1, z1, x2, y2, z2, color); + } else if (y0 == y1) { + pxl8_draw_flat_top_triangle(gfx, x0, y0, z0, x1, y1, z1, x2, y2, z2, color); + } else { + i32 x3 = x0 + (i32)(((f32)(y1 - y0) / (f32)(y2 - y0)) * (x2 - x0)); + i32 y3 = y1; + f32 z3 = z0 + ((f32)(y1 - y0) / (f32)(y2 - y0)) * (z2 - z0); + + pxl8_draw_flat_bottom_triangle(gfx, x0, y0, z0, x1, y1, z1, x3, y3, z3, color); + pxl8_draw_flat_top_triangle(gfx, x1, y1, z1, x3, y3, z3, x2, y2, z2, color); + } +} diff --git a/src/pxl8_gfx.h b/src/pxl8_gfx.h index 2fe9ac0..9263a56 100644 --- a/src/pxl8_gfx.h +++ b/src/pxl8_gfx.h @@ -1,143 +1,105 @@ #pragma once -#include - -#include - -#include "pxl8_blit.h" +#include "pxl8_math.h" #include "pxl8_types.h" -typedef struct pxl8_atlas_entry { - char path[256]; - u32 sprite_id; - i32 x, y, w, h; -} pxl8_atlas_entry; - -typedef struct pxl8_gfx_ctx { - SDL_Renderer* renderer; - SDL_Texture* framebuffer_texture; - SDL_Texture* sprite_atlas_texture; - SDL_Window* window; - - u8* framebuffer; - bool initialized; - u32* palette; - u32 palette_size; - - i32 framebuffer_width; - i32 framebuffer_height; - pxl8_color_mode color_mode; - - u8* atlas; - bool atlas_dirty; - pxl8_atlas_entry* atlas_entries; - u32 atlas_entries_len; - u32 atlas_entries_cap; - - u32 sprite_atlas_width; - u32 sprite_atlas_height; - u32 sprite_frame_width; - u32 sprite_frame_height; - u32 sprite_frames_per_row; - - i32 viewport_x, viewport_y; - i32 viewport_width, viewport_height; -} pxl8_gfx_ctx; +typedef struct pxl8_gfx pxl8_gfx; typedef enum pxl8_blend_mode { - PXL8_BLEND_NONE, - PXL8_BLEND_ALPHA, PXL8_BLEND_ADD, - PXL8_BLEND_MULTIPLY + PXL8_BLEND_ALPHA, + PXL8_BLEND_MULTIPLY, + PXL8_BLEND_NONE } pxl8_blend_mode; typedef struct pxl8_mode7_params { - f32 horizon; - f32 scale_x, scale_y; - f32 rotation; - f32 offset_x, offset_y; bool active; + f32 horizon; + f32 offset_x, offset_y; + f32 rotation; + f32 scale_x, scale_y; } pxl8_mode7_params; typedef struct pxl8_palette_cycle { - u8 start_index; + bool active; u8 end_index; f32 speed; + u8 start_index; f32 timer; - bool active; } pxl8_palette_cycle; typedef struct pxl8_scanline_effect { - void (*process)(pxl8_gfx_ctx* ctx, i32 line, f32 time); bool active; + void (*process)(pxl8_gfx* gfx, i32 line, f32 time); } pxl8_scanline_effect; +typedef struct pxl8_vertex { + u32 color; + pxl8_vec3 normal; + pxl8_vec3 position; + f32 u, v; +} pxl8_vertex; + typedef struct pxl8_effects { pxl8_palette_cycle palette_cycles[8]; pxl8_scanline_effect scanline_effects[4]; f32 time; } pxl8_effects; +typedef struct pxl8_triangle { + pxl8_vertex v[3]; +} pxl8_triangle; + #ifdef __cplusplus extern "C" { #endif +pxl8_gfx* pxl8_gfx_create(pxl8_color_mode mode, pxl8_resolution resolution, const char* title, i32 window_width, i32 window_height); +void pxl8_gfx_destroy(pxl8_gfx* gfx); + +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); void pxl8_gfx_get_resolution_dimensions(pxl8_resolution resolution, i32* width, i32* height); -pxl8_result pxl8_gfx_init( - pxl8_gfx_ctx* ctx, - pxl8_color_mode mode, - pxl8_resolution resolution, - const char* title, - i32 window_width, - i32 window_height -); -pxl8_result pxl8_gfx_init_atlas(pxl8_gfx_ctx* ctx, u32 width, u32 height); -pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx_ctx* ctx); -pxl8_result pxl8_gfx_load_palette(pxl8_gfx_ctx* ctx, const char* path); -pxl8_result pxl8_gfx_load_sprite(pxl8_gfx_ctx* ctx, const char* path); -void pxl8_gfx_present(pxl8_gfx_ctx* ctx); -void pxl8_gfx_project(pxl8_gfx_ctx* ctx, f32 left, f32 right, f32 top, f32 bottom); -void pxl8_gfx_shutdown(pxl8_gfx_ctx* ctx); -void pxl8_gfx_upload_atlas(pxl8_gfx_ctx* ctx); -void pxl8_gfx_upload_framebuffer(pxl8_gfx_ctx* ctx); -void pxl8_gfx_viewport(pxl8_gfx_ctx* ctx, i32 x, i32 y, 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); +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); +void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx); +void pxl8_gfx_viewport(pxl8_gfx* gfx, i32 x, i32 y, i32 width, i32 height); -void pxl8_gfx_color_ramp( - pxl8_gfx_ctx* ctx, - u8 start, - u8 count, - u32 from_color, - u32 to_color -); -void pxl8_gfx_cycle_palette(pxl8_gfx_ctx* ctx, u8 start, u8 count, i32 step); -void pxl8_gfx_fade_palette( - pxl8_gfx_ctx* ctx, - u8 start, - u8 count, - f32 amount, - u32 target_color -); -void pxl8_gfx_interpolate_palettes( - pxl8_gfx_ctx* ctx, - u32* palette1, - u32* palette2, - u8 start, - u8 count, - f32 t -); -void pxl8_gfx_process_effects(pxl8_gfx_ctx* ctx, pxl8_effects* effects, f32 dt); -void pxl8_gfx_swap_palette(pxl8_gfx_ctx* ctx, u8 start, u8 count, u32* new_colors); +void pxl8_gfx_color_ramp(pxl8_gfx* gfx, u8 start, u8 count, u32 from_color, u32 to_color); +void pxl8_gfx_cycle_palette(pxl8_gfx* gfx, u8 start, u8 count, i32 step); +void pxl8_gfx_fade_palette(pxl8_gfx* gfx, u8 start, u8 count, f32 amount, u32 target_color); +void pxl8_gfx_interpolate_palettes(pxl8_gfx* gfx, u32* palette1, u32* palette2, u8 start, u8 count, f32 t); +void pxl8_gfx_process_effects(pxl8_gfx* gfx, pxl8_effects* effects, f32 dt); +void pxl8_gfx_swap_palette(pxl8_gfx* gfx, u8 start, u8 count, u32* new_colors); -void pxl8_circle(pxl8_gfx_ctx* ctx, i32 cx, i32 cy, i32 radius, u32 color); -void pxl8_circle_fill(pxl8_gfx_ctx* ctx, i32 cx, i32 cy, i32 radius, u32 color); -void pxl8_clr(pxl8_gfx_ctx* ctx, u32 color); -u32 pxl8_get_pixel(pxl8_gfx_ctx* ctx, i32 x, i32 y); -void pxl8_line(pxl8_gfx_ctx* ctx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color); -void pxl8_pixel(pxl8_gfx_ctx* ctx, i32 x, i32 y, u32 color); -void pxl8_rect(pxl8_gfx_ctx* ctx, i32 x, i32 y, i32 w, i32 h, u32 color); -void pxl8_rect_fill(pxl8_gfx_ctx* ctx, i32 x, i32 y, i32 w, i32 h, u32 color); -void pxl8_sprite(pxl8_gfx_ctx* ctx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h); -void pxl8_text(pxl8_gfx_ctx* ctx, const char* text, i32 x, i32 y, u32 color); +void pxl8_circle(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color); +void pxl8_circle_fill(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color); +void pxl8_clr(pxl8_gfx* gfx, u32 color); +u32 pxl8_get_pixel(pxl8_gfx* gfx, i32 x, i32 y); +void pxl8_line(pxl8_gfx* gfx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color); +void pxl8_pixel(pxl8_gfx* gfx, i32 x, i32 y, u32 color); +void pxl8_rect(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color); +void pxl8_rect_fill(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color); +void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h); +void pxl8_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color); + +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_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); +void pxl8_3d_set_view(pxl8_gfx* gfx, pxl8_mat4 mat); +void pxl8_3d_set_wireframe(pxl8_gfx* gfx, bool wireframe); #ifdef __cplusplus } diff --git a/src/pxl8_lua.c b/src/pxl8_lua.c deleted file mode 100644 index c99de63..0000000 --- a/src/pxl8_lua.c +++ /dev/null @@ -1,330 +0,0 @@ -#include - -#include "pxl8_lua.h" -#include "pxl8_macros.h" -#include "pxl8_vfx.h" - -static const char* pxl8_ffi_cdefs = -"typedef uint8_t u8;\n" -"typedef uint16_t u16;\n" -"typedef uint32_t u32;\n" -"typedef uint64_t u64;\n" -"typedef int8_t i8;\n" -"typedef int16_t i16;\n" -"typedef int32_t i32;\n" -"typedef int64_t i64;\n" -"typedef float f32;\n" -"typedef double f64;\n" -"typedef struct pxl8_gfx_ctx pxl8_gfx_ctx;\n" -"typedef struct { int x, y, w, h; } pxl8_rect;\n" -"typedef struct { int x, y; } pxl8_point;\n" -"typedef struct {\n" -" int width, height;\n" -" int color_mode;\n" -" unsigned char* pixels;\n" -"} pxl8_screen;\n" -"\n" -"void pxl8_clr(pxl8_gfx_ctx* ctx, u32 color);\n" -"void pxl8_pixel(pxl8_gfx_ctx* ctx, i32 x, i32 y, u32 color);\n" -"u32 pxl8_get_pixel(pxl8_gfx_ctx* ctx, i32 x, i32 y);\n" -"void pxl8_line(pxl8_gfx_ctx* ctx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color);\n" -"void pxl8_rect(pxl8_gfx_ctx* ctx, i32 x, i32 y, i32 w, i32 h, u32 color);\n" -"void pxl8_rect_fill(pxl8_gfx_ctx* ctx, i32 x, i32 y, i32 w, i32 h, u32 color);\n" -"void pxl8_circle(pxl8_gfx_ctx* ctx, i32 x, i32 y, i32 r, u32 color);\n" -"void pxl8_circle_fill(pxl8_gfx_ctx* ctx, i32 x, i32 y, i32 r, u32 color);\n" -"void pxl8_text(pxl8_gfx_ctx* ctx, const char* str, i32 x, i32 y, u32 color);\n" -"void pxl8_sprite(pxl8_gfx_ctx* ctx, i32 id, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y);\n" -"pxl8_screen* pxl8_get_screen(pxl8_gfx_ctx* ctx);\n" -"i32 pxl8_gfx_load_palette(pxl8_gfx_ctx* ctx, const char* filepath);\n" -"i32 pxl8_gfx_load_sprite(pxl8_gfx_ctx* ctx, const char* filepath, u32* sprite_id);\n" -"void pxl8_gfx_color_ramp(pxl8_gfx_ctx* ctx, u8 start, u8 count, u32 from_color, u32 to_color);\n" -"void pxl8_gfx_fade_palette(pxl8_gfx_ctx* ctx, u8 start, u8 count, f32 amount, u32 target_color);\n" -"void pxl8_lua_info(const char* msg);\n" -"void pxl8_lua_warn(const char* msg);\n" -"void pxl8_lua_error(const char* msg);\n" -"void pxl8_lua_debug(const char* msg);\n" -"void pxl8_lua_trace(const char* msg);\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" -"\n" -"typedef struct pxl8_tilesheet pxl8_tilesheet;\n" -"typedef struct pxl8_tilemap pxl8_tilemap;\n" -"pxl8_tilesheet* pxl8_tilesheet_new(u32 tile_size);\n" -"void pxl8_tilesheet_destroy(pxl8_tilesheet* tilesheet);\n" -"i32 pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, pxl8_gfx_ctx* gfx);\n" -"pxl8_tilemap* pxl8_tilemap_new(u32 width, u32 height, u32 tile_size);\n" -"void pxl8_tilemap_destroy(pxl8_tilemap* tilemap);\n" -"i32 pxl8_tilemap_set_tilesheet(pxl8_tilemap* tilemap, pxl8_tilesheet* tilesheet);\n" -"void pxl8_tilemap_set_tile(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u16 tile_id, u8 flags);\n" -"u16 pxl8_tilemap_get_tile_id(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y);\n" -"void pxl8_tilemap_set_camera(pxl8_tilemap* tilemap, i32 x, i32 y);\n" -"void pxl8_tilemap_render(const pxl8_tilemap* tilemap, pxl8_gfx_ctx* gfx);\n" -"void pxl8_tilemap_render_layer(const pxl8_tilemap* tilemap, pxl8_gfx_ctx* gfx, u32 layer);\n" -"bool pxl8_tilemap_is_solid(const pxl8_tilemap* tilemap, u32 x, u32 y);\n" -"bool pxl8_tilemap_check_collision(const pxl8_tilemap* tilemap, i32 x, i32 y, i32 w, i32 h);\n" -"\n" -"typedef struct {\n" -" float x, y, z;\n" -" float vx, vy, vz;\n" -" float ax, ay, az;\n" -" float life;\n" -" float max_life;\n" -" unsigned int color;\n" -" unsigned int start_color;\n" -" unsigned int end_color;\n" -" float size;\n" -" float angle;\n" -" float spin;\n" -" unsigned char flags;\n" -"} pxl8_particle;\n" -"\n" -"typedef struct {\n" -" pxl8_particle* particles;\n" -" unsigned int count;\n" -" unsigned int max_count;\n" -" unsigned int alive_count;\n" -" float spawn_rate;\n" -" float spawn_timer;\n" -" float x, y;\n" -" float spread_x, spread_y;\n" -" float gravity_x, gravity_y;\n" -" float drag;\n" -" float turbulence;\n" -" void* spawn_fn;\n" -" void* update_fn;\n" -" void* render_fn;\n" -" void* userdata;\n" -"} pxl8_particle_system;\n" -"\n" -"typedef struct {\n" -" float base_y;\n" -" float amplitude;\n" -" int height;\n" -" float speed;\n" -" float phase;\n" -" unsigned int color;\n" -" unsigned int fade_color;\n" -"} pxl8_raster_bar;\n" -"\n" -"void pxl8_vfx_raster_bars(pxl8_gfx_ctx* ctx, pxl8_raster_bar* bars, u32 bar_count, f32 time);\n" -"void pxl8_vfx_plasma(pxl8_gfx_ctx* ctx, f32 time, f32 scale1, f32 scale2, u8 palette_offset);\n" -"void pxl8_vfx_rotozoom(pxl8_gfx_ctx* ctx, f32 angle, f32 zoom, i32 cx, i32 cy);\n" -"void pxl8_vfx_tunnel(pxl8_gfx_ctx* ctx, f32 time, f32 speed, f32 twist);\n" -"void pxl8_vfx_water_ripple(pxl8_gfx_ctx* ctx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping);\n" -"\n" -"void pxl8_vfx_particles_clear(pxl8_particle_system* sys);\n" -"void pxl8_vfx_particles_destroy(pxl8_particle_system* sys);\n" -"void pxl8_vfx_particles_emit(pxl8_particle_system* sys, u32 count);\n" -"void pxl8_vfx_particles_init(pxl8_particle_system* sys, unsigned int max_count);\n" -"void pxl8_vfx_particles_render(pxl8_particle_system* sys, pxl8_gfx_ctx* ctx);\n" -"void pxl8_vfx_particles_update(pxl8_particle_system* sys, float dt);\n" -"\n" -"void pxl8_vfx_explosion(pxl8_particle_system* sys, int x, int y, unsigned int color, float force);\n" -"void pxl8_vfx_fire(pxl8_particle_system* sys, int x, int y, int width, unsigned char palette_start);\n" -"void pxl8_vfx_rain(pxl8_particle_system* sys, int width, float wind);\n" -"void pxl8_vfx_smoke(pxl8_particle_system* sys, int x, int y, unsigned char color);\n" -"void pxl8_vfx_snow(pxl8_particle_system* sys, int width, float wind);\n" -"void pxl8_vfx_sparks(pxl8_particle_system* sys, int x, int y, unsigned int color);\n" -"void pxl8_vfx_starfield(pxl8_particle_system* sys, float speed, float spread);\n"; - -typedef struct { - int width, height; - int color_mode; - unsigned char* pixels; -} pxl8_screen; - -pxl8_screen* pxl8_get_screen(pxl8_gfx_ctx* ctx) { - if (!ctx) return NULL; - - static pxl8_screen screen = {0}; - screen.width = ctx->framebuffer_width; - screen.height = ctx->framebuffer_height; - screen.color_mode = ctx->color_mode; - screen.pixels = ctx->framebuffer; - - return &screen; -} - -void pxl8_lua_info(const char* msg) { - pxl8_info("%s", msg); -} - -void pxl8_lua_warn(const char* msg) { - pxl8_warn("%s", msg); -} - -void pxl8_lua_error(const char* msg) { - pxl8_error("%s", msg); -} - -void pxl8_lua_debug(const char* msg) { - pxl8_debug("%s", msg); -} - -void pxl8_lua_trace(const char* msg) { - pxl8_trace("%s", msg); -} - -pxl8_result pxl8_lua_init(lua_State** lua_state) { - if (!lua_state) return PXL8_ERROR_NULL_POINTER; - - *lua_state = luaL_newstate(); - if (!*lua_state) { - return PXL8_ERROR_OUT_OF_MEMORY; - } - - luaL_openlibs(*lua_state); - - lua_getglobal(*lua_state, "require"); - lua_pushstring(*lua_state, "ffi"); - if (lua_pcall(*lua_state, 1, 1, 0) != 0) { - lua_close(*lua_state); - *lua_state = NULL; - return PXL8_ERROR_SYSTEM_FAILURE; - } - - lua_getfield(*lua_state, -1, "cdef"); - lua_pushstring(*lua_state, pxl8_ffi_cdefs); - if (lua_pcall(*lua_state, 1, 0, 0) != 0) { - lua_close(*lua_state); - *lua_state = NULL; - return PXL8_ERROR_SYSTEM_FAILURE; - } - - lua_pop(*lua_state, 1); - - lua_getglobal(*lua_state, "package"); - lua_getfield(*lua_state, -1, "path"); - const char* current_path = lua_tostring(*lua_state, -1); - lua_pop(*lua_state, 1); - - lua_pushfstring(*lua_state, "%s;src/lua/?.lua", current_path); - lua_setfield(*lua_state, -2, "path"); - lua_pop(*lua_state, 1); - - if (luaL_dofile(*lua_state, "lib/fennel/fennel.lua") == 0) { - lua_setglobal(*lua_state, "fennel"); - } - - return PXL8_OK; -} - -void pxl8_lua_shutdown(lua_State* lua_state) { - if (lua_state) { - lua_close(lua_state); - } -} - -pxl8_result pxl8_lua_setup_contexts(lua_State* lua_state, pxl8_gfx_ctx* gfx_ctx, pxl8_input_state* input) { - if (!lua_state || !gfx_ctx || !input) return PXL8_ERROR_NULL_POINTER; - - lua_pushlightuserdata(lua_state, gfx_ctx); - lua_setglobal(lua_state, "_pxl8_gfx_ctx"); - - lua_pushlightuserdata(lua_state, input); - lua_setglobal(lua_state, "_pxl8_input_ctx"); - - return PXL8_OK; -} - -pxl8_result pxl8_lua_run_file(lua_State* lua_state, const char* filename) { - if (!lua_state || !filename) return PXL8_ERROR_NULL_POINTER; - - if (luaL_dofile(lua_state, filename) != 0) { - printf("Lua error: %s\n", lua_tostring(lua_state, -1)); - lua_pop(lua_state, 1); - return PXL8_ERROR_SCRIPT_ERROR; - } - - return PXL8_OK; -} - -pxl8_result pxl8_lua_run_string(lua_State* lua_state, const char* code) { - if (!lua_state || !code) return PXL8_ERROR_NULL_POINTER; - - if (luaL_dostring(lua_state, code) != 0) { - printf("Lua error: %s\n", lua_tostring(lua_state, -1)); - lua_pop(lua_state, 1); - return PXL8_ERROR_SCRIPT_ERROR; - } - - return PXL8_OK; -} - -void pxl8_lua_setup_cart_path(lua_State* lua_state, const char* cart_path, const char* original_cwd) { - if (!lua_state || !cart_path || !original_cwd) return; - - lua_getglobal(lua_state, "package"); - lua_getfield(lua_state, -1, "path"); - const char* current_path = lua_tostring(lua_state, -1); - - char new_path[2048]; - snprintf(new_path, sizeof(new_path), - "%s/?.lua;%s/?/init.lua;%s/src/?.lua;%s/src/?/init.lua;%s/src/lua/?.lua;%s", - cart_path, cart_path, cart_path, cart_path, original_cwd, - current_path ? current_path : ""); - - lua_pushstring(lua_state, new_path); - lua_setfield(lua_state, -3, "path"); - lua_pop(lua_state, 2); - - lua_getglobal(lua_state, "fennel"); - if (!lua_isnil(lua_state, -1)) { - lua_getfield(lua_state, -1, "path"); - const char* fennel_path = lua_tostring(lua_state, -1); - - snprintf(new_path, sizeof(new_path), - "%s/?.fnl;%s/?/init.fnl;%s/src/?.fnl;%s/src/?/init.fnl;%s", - cart_path, cart_path, cart_path, cart_path, - fennel_path ? fennel_path : ""); - - lua_pushstring(lua_state, new_path); - lua_setfield(lua_state, -3, "path"); - lua_pop(lua_state, 1); - } - lua_pop(lua_state, 1); -} - -pxl8_result pxl8_lua_run_fennel_file(lua_State* lua_state, const char* filename) { - if (!lua_state || !filename) return PXL8_ERROR_NULL_POINTER; - - lua_getglobal(lua_state, "fennel"); - if (lua_isnil(lua_state, -1)) { - lua_pop(lua_state, 1); - return PXL8_ERROR_SCRIPT_ERROR; - } - - lua_getfield(lua_state, -1, "dofile"); - lua_pushstring(lua_state, filename); - - if (lua_pcall(lua_state, 1, 0, 0) != 0) { - printf("Fennel error: %s\n", lua_tostring(lua_state, -1)); - lua_pop(lua_state, 1); - return PXL8_ERROR_SCRIPT_ERROR; - } - - lua_pop(lua_state, 1); - return PXL8_OK; -} - -pxl8_result pxl8_lua_eval_fennel(lua_State* lua_state, const char* code) { - if (!lua_state || !code) return PXL8_ERROR_NULL_POINTER; - - lua_getglobal(lua_state, "fennel"); - if (lua_isnil(lua_state, -1)) { - lua_pop(lua_state, 1); - return PXL8_ERROR_SCRIPT_ERROR; - } - - lua_getfield(lua_state, -1, "eval"); - lua_pushstring(lua_state, code); - - if (lua_pcall(lua_state, 1, 1, 0) != 0) { - lua_remove(lua_state, -2); - return PXL8_ERROR_SCRIPT_ERROR; - } - - lua_remove(lua_state, -2); - return PXL8_OK; -} diff --git a/src/pxl8_lua.h b/src/pxl8_lua.h deleted file mode 100644 index f9ca315..0000000 --- a/src/pxl8_lua.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "pxl8_types.h" -#include "pxl8_gfx.h" - -#ifdef __cplusplus -extern "C" { -#endif - -pxl8_result pxl8_lua_init(lua_State** lua_state); -void pxl8_lua_shutdown(lua_State* lua_state); -pxl8_result pxl8_lua_setup_contexts(lua_State* lua_state, pxl8_gfx_ctx* gfx_ctx, pxl8_input_state* input); -void pxl8_lua_setup_cart_path(lua_State* lua_state, const char* cart_path, const char* original_cwd); - -pxl8_result pxl8_lua_eval_fennel(lua_State* lua_state, const char* code); -pxl8_result pxl8_lua_run_fennel_file(lua_State* lua_state, const char* filename); -pxl8_result pxl8_lua_run_file(lua_State* lua_state, const char* filename); -pxl8_result pxl8_lua_run_string(lua_State* lua_state, const char* code); - -#ifdef __cplusplus -} -#endif diff --git a/src/pxl8_math.c b/src/pxl8_math.c new file mode 100644 index 0000000..dad4f92 --- /dev/null +++ b/src/pxl8_math.c @@ -0,0 +1,235 @@ +#include + +#include "pxl8_math.h" +#include "pxl8_simd.h" + +pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b) { + 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) { + .x = a.x - b.x, + .y = a.y - b.y, + }; +} + +pxl8_vec2 pxl8_vec2_scale(pxl8_vec2 v, f32 s) { + return (pxl8_vec2) { + .x = v.x * s, + .y = v.y * s, + }; +} + +f32 pxl8_vec2_dot(pxl8_vec2 a, pxl8_vec2 b) { + return a.x * b.x + a.y * b.y; +} + +f32 pxl8_vec2_length(pxl8_vec2 v) { + return sqrtf(v.x * v.x + v.y * v.y); +} + +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); +} + +pxl8_vec3 pxl8_vec3_add(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); + pxl8_simd_vec_f32 result = pxl8_simd_add_f32(va, vb); + + return (pxl8_vec3) { + .x = result.f32_array[0], + .y = result.f32_array[1], + .z = result.f32_array[2], + }; +} + +pxl8_vec3 pxl8_vec3_sub(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); + pxl8_simd_vec_f32 result = pxl8_simd_sub_f32(va, vb); + + return (pxl8_vec3) { + .x = result.f32_array[0], + .y = result.f32_array[1], + .z = result.f32_array[2], + }; +} + +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) { + .x = result.f32_array[0], + .y = result.f32_array[1], + .z = result.f32_array[2], + }; +} + +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) { + .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, + }; +} + +f32 pxl8_vec3_length(pxl8_vec3 v) { + return sqrtf(pxl8_vec3_dot(v, 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}; + for (i32 i = 0; i < 4; i++) { + for (i32 j = 0; j < 4; j++) { + pxl8_simd_vec_f32 row = pxl8_simd_set_f32( + a.m[i * 4 + 0], a.m[i * 4 + 1], a.m[i * 4 + 2], a.m[i * 4 + 3] + ); + 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); + } + } + return result; +} + +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]); + + 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; +} + +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; +} + +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; +} + +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; +} + +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; +} + +pxl8_mat4 pxl8_mat4_ortho(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far) { + pxl8_mat4 mat = {0}; + + mat.m[0] = 2.0f / (right - left); + mat.m[5] = 2.0f / (top - bottom); + mat.m[10] = -2.0f / (far - near); + mat.m[3] = -(right + left) / (right - left); + mat.m[7] = -(top + bottom) / (top - bottom); + mat.m[11] = -(far + near) / (far - near); + mat.m[15] = 1.0f; + + return mat; +} + +pxl8_mat4 pxl8_mat4_perspective(f32 fov, f32 aspect, f32 near, f32 far) { + pxl8_mat4 mat = {0}; + f32 tan_half_fov = tanf(fov / 2.0f); + + mat.m[0] = 1.0f / (aspect * tan_half_fov); + mat.m[5] = 1.0f / tan_half_fov; + mat.m[10] = -(far + near) / (far - near); + mat.m[11] = -(2.0f * far * near) / (far - near); + mat.m[14] = -1.0f; + + return mat; +} + +pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up) { + 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; + mat.m[4] = u.x; + mat.m[5] = u.y; + mat.m[6] = u.z; + mat.m[8] = -f.x; + mat.m[9] = -f.y; + mat.m[10] = -f.z; + mat.m[3] = -pxl8_vec3_dot(s, eye); + mat.m[7] = -pxl8_vec3_dot(u, eye); + mat.m[11] = pxl8_vec3_dot(f, eye); + + return mat; +} diff --git a/src/pxl8_math.h b/src/pxl8_math.h new file mode 100644 index 0000000..e10f17d --- /dev/null +++ b/src/pxl8_math.h @@ -0,0 +1,54 @@ +#pragma once + +#include "pxl8_types.h" + +typedef struct pxl8_vec2 { + f32 x, y; +} pxl8_vec2; + +typedef struct pxl8_vec3 { + f32 x, y, z; +} pxl8_vec3; + +typedef struct pxl8_vec4 { + f32 x, y, z, w; +} pxl8_vec4; + +typedef struct pxl8_mat4 { + f32 m[16]; +} pxl8_mat4; + +#ifdef __cplusplus +extern "C" { +#endif + +pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b); +f32 pxl8_vec2_dot(pxl8_vec2 a, pxl8_vec2 b); +f32 pxl8_vec2_length(pxl8_vec2 v); +pxl8_vec2 pxl8_vec2_normalize(pxl8_vec2 v); +pxl8_vec2 pxl8_vec2_scale(pxl8_vec2 v, f32 s); +pxl8_vec2 pxl8_vec2_sub(pxl8_vec2 a, pxl8_vec2 b); + +pxl8_vec3 pxl8_vec3_add(pxl8_vec3 a, pxl8_vec3 b); +pxl8_vec3 pxl8_vec3_cross(pxl8_vec3 a, pxl8_vec3 b); +f32 pxl8_vec3_dot(pxl8_vec3 a, pxl8_vec3 b); +f32 pxl8_vec3_length(pxl8_vec3 v); +pxl8_vec3 pxl8_vec3_normalize(pxl8_vec3 v); +pxl8_vec3 pxl8_vec3_scale(pxl8_vec3 v, f32 s); +pxl8_vec3 pxl8_vec3_sub(pxl8_vec3 a, pxl8_vec3 b); + +pxl8_mat4 pxl8_mat4_identity(void); +pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up); +pxl8_mat4 pxl8_mat4_multiply(pxl8_mat4 a, pxl8_mat4 b); +pxl8_vec4 pxl8_mat4_multiply_vec4(pxl8_mat4 m, pxl8_vec4 v); +pxl8_mat4 pxl8_mat4_ortho(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far); +pxl8_mat4 pxl8_mat4_perspective(f32 fov, f32 aspect, f32 near, f32 far); +pxl8_mat4 pxl8_mat4_rotate_x(f32 angle); +pxl8_mat4 pxl8_mat4_rotate_y(f32 angle); +pxl8_mat4 pxl8_mat4_rotate_z(f32 angle); +pxl8_mat4 pxl8_mat4_scale(f32 x, f32 y, f32 z); +pxl8_mat4 pxl8_mat4_translate(f32 x, f32 y, f32 z); + +#ifdef __cplusplus +} +#endif diff --git a/src/pxl8_script.c b/src/pxl8_script.c new file mode 100644 index 0000000..6d3a5ea --- /dev/null +++ b/src/pxl8_script.c @@ -0,0 +1,543 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "pxl8_macros.h" +#include "pxl8_script.h" +#include "pxl8_vfx.h" + +struct pxl8_script { + lua_State* L; + pxl8_gfx* gfx; + pxl8_input_state* input; + char last_error[1024]; + char main_path[256]; + char watch_dir[256]; + time_t latest_mod_time; +}; + +static const char* pxl8_ffi_cdefs = +"typedef uint8_t u8;\n" +"typedef uint16_t u16;\n" +"typedef uint32_t u32;\n" +"typedef uint64_t u64;\n" +"typedef int8_t i8;\n" +"typedef int16_t i16;\n" +"typedef int32_t i32;\n" +"typedef int64_t i64;\n" +"typedef float f32;\n" +"typedef double f64;\n" +"typedef struct pxl8_gfx pxl8_gfx;\n" +"typedef struct { int x, y, w, h; } pxl8_rect;\n" +"typedef struct { int x, y; } pxl8_point;\n" +"\n" +"i32 pxl8_gfx_get_height(pxl8_gfx* ctx);\n" +"i32 pxl8_gfx_get_width(pxl8_gfx* ctx);\n" +"void pxl8_circle(pxl8_gfx* ctx, i32 x, i32 y, i32 r, u32 color);\n" +"void pxl8_circle_fill(pxl8_gfx* ctx, i32 x, i32 y, i32 r, u32 color);\n" +"void pxl8_clr(pxl8_gfx* ctx, u32 color);\n" +"u32 pxl8_get_pixel(pxl8_gfx* ctx, i32 x, i32 y);\n" +"void pxl8_line(pxl8_gfx* ctx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color);\n" +"void pxl8_pixel(pxl8_gfx* ctx, i32 x, i32 y, u32 color);\n" +"void pxl8_rect(pxl8_gfx* ctx, i32 x, i32 y, i32 w, i32 h, u32 color);\n" +"void pxl8_rect_fill(pxl8_gfx* ctx, i32 x, i32 y, i32 w, i32 h, u32 color);\n" +"void pxl8_sprite(pxl8_gfx* ctx, i32 id, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y);\n" +"void pxl8_text(pxl8_gfx* ctx, const char* str, i32 x, i32 y, u32 color);\n" +"void pxl8_gfx_color_ramp(pxl8_gfx* ctx, u8 start, u8 count, u32 from_color, u32 to_color);\n" +"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" +"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" +"void pxl8_lua_debug(const char* msg);\n" +"void pxl8_lua_error(const char* msg);\n" +"void pxl8_lua_info(const char* msg);\n" +"void pxl8_lua_trace(const char* msg);\n" +"void pxl8_lua_warn(const char* msg);\n" +"\n" +"typedef struct pxl8_tilemap pxl8_tilemap;\n" +"typedef struct pxl8_tilesheet pxl8_tilesheet;\n" +"pxl8_tilemap* pxl8_tilemap_create(u32 width, u32 height, u32 tile_size);\n" +"void pxl8_tilemap_destroy(pxl8_tilemap* tilemap);\n" +"bool pxl8_tilemap_check_collision(const pxl8_tilemap* tilemap, i32 x, i32 y, i32 w, i32 h);\n" +"u16 pxl8_tilemap_get_tile_id(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y);\n" +"bool pxl8_tilemap_is_solid(const pxl8_tilemap* tilemap, u32 x, u32 y);\n" +"void pxl8_tilemap_render(const pxl8_tilemap* tilemap, pxl8_gfx* gfx);\n" +"void pxl8_tilemap_render_layer(const pxl8_tilemap* tilemap, pxl8_gfx* gfx, u32 layer);\n" +"void pxl8_tilemap_set_camera(pxl8_tilemap* tilemap, i32 x, i32 y);\n" +"void pxl8_tilemap_set_tile(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u16 tile_id, u8 flags);\n" +"i32 pxl8_tilemap_set_tilesheet(pxl8_tilemap* tilemap, pxl8_tilesheet* tilesheet);\n" +"pxl8_tilesheet* pxl8_tilesheet_create(u32 tile_size);\n" +"void pxl8_tilesheet_destroy(pxl8_tilesheet* tilesheet);\n" +"i32 pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, pxl8_gfx* gfx);\n" +"\n" +"typedef struct {\n" +" float angle;\n" +" float ax, ay, az;\n" +" unsigned int color;\n" +" unsigned int end_color;\n" +" unsigned char flags;\n" +" float life;\n" +" float max_life;\n" +" float size;\n" +" float spin;\n" +" unsigned int start_color;\n" +" float vx, vy, vz;\n" +" float x, y, z;\n" +"} pxl8_particle;\n" +"\n" +"typedef struct {\n" +" float amplitude;\n" +" float base_y;\n" +" unsigned int color;\n" +" unsigned int fade_color;\n" +" int height;\n" +" float phase;\n" +" float speed;\n" +"} pxl8_raster_bar;\n" +"\n" +"typedef struct pxl8_particles pxl8_particles;\n" +"pxl8_particles* pxl8_particles_create(u32 max_count);\n" +"void pxl8_particles_destroy(pxl8_particles* particles);\n" +"void pxl8_particles_clear(pxl8_particles* particles);\n" +"void pxl8_particles_emit(pxl8_particles* particles, u32 count);\n" +"void pxl8_particles_render(pxl8_particles* particles, pxl8_gfx* gfx);\n" +"void pxl8_particles_update(pxl8_particles* particles, float dt);\n" +"void pxl8_vfx_explosion(pxl8_particles* particles, int x, int y, unsigned int color, float force);\n" +"void pxl8_vfx_fire(pxl8_particles* particles, int x, int y, int width, unsigned char palette_start);\n" +"void pxl8_vfx_plasma(pxl8_gfx* ctx, f32 time, f32 scale1, f32 scale2, u8 palette_offset);\n" +"void pxl8_vfx_rain(pxl8_particles* particles, int width, float wind);\n" +"void pxl8_vfx_raster_bars(pxl8_gfx* ctx, pxl8_raster_bar* bars, u32 bar_count, f32 time);\n" +"void pxl8_vfx_rotozoom(pxl8_gfx* ctx, f32 angle, f32 zoom, i32 cx, i32 cy);\n" +"void pxl8_vfx_smoke(pxl8_particles* particles, int x, int y, unsigned char color);\n" +"void pxl8_vfx_snow(pxl8_particles* particles, int width, float wind);\n" +"void pxl8_vfx_sparks(pxl8_particles* particles, int x, int y, unsigned int color);\n" +"void pxl8_vfx_starfield(pxl8_particles* particles, float speed, float spread);\n" +"void pxl8_vfx_tunnel(pxl8_gfx* ctx, f32 time, f32 speed, f32 twist);\n" +"void pxl8_vfx_water_ripple(pxl8_gfx* ctx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping);\n" +"\n" +"typedef struct { float x, y, z; } pxl8_vec3;\n" +"typedef struct { float x, y, z, w; } pxl8_vec4;\n" +"typedef struct { float m[16]; } pxl8_mat4;\n" +"\n" +"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_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" +"void pxl8_3d_set_view(pxl8_gfx* gfx, pxl8_mat4 mat);\n" +"void pxl8_3d_set_wireframe(pxl8_gfx* gfx, bool wireframe);\n" +"pxl8_mat4 pxl8_mat4_identity(void);\n" +"pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up);\n" +"pxl8_mat4 pxl8_mat4_multiply(pxl8_mat4 a, pxl8_mat4 b);\n" +"pxl8_mat4 pxl8_mat4_ortho(float left, float right, float bottom, float top, float near, float far);\n" +"pxl8_mat4 pxl8_mat4_perspective(float fov, float aspect, float near, float far);\n" +"pxl8_mat4 pxl8_mat4_rotate_x(float angle);\n" +"pxl8_mat4 pxl8_mat4_rotate_y(float angle);\n" +"pxl8_mat4 pxl8_mat4_rotate_z(float angle);\n" +"pxl8_mat4 pxl8_mat4_scale(float x, float y, float z);\n" +"pxl8_mat4 pxl8_mat4_translate(float x, float y, float z);\n"; + +void pxl8_lua_info(const char* msg) { + pxl8_info("%s", msg); +} + +void pxl8_lua_warn(const char* msg) { + pxl8_warn("%s", msg); +} + +void pxl8_lua_error(const char* msg) { + pxl8_error("%s", msg); +} + +void pxl8_lua_debug(const char* msg) { + pxl8_debug("%s", msg); +} + +void pxl8_lua_trace(const char* msg) { + pxl8_trace("%s", msg); +} + +static void pxl8_script_set_error(pxl8_script* script, const char* error) { + if (!script) return; + snprintf(script->last_error, sizeof(script->last_error), "%s", error ? error : "Unknown error"); +} + +const char* pxl8_script_get_last_error(pxl8_script* script) { + return script ? script->last_error : "Invalid script context"; +} + +pxl8_script* pxl8_script_create(void) { + pxl8_script* script = SDL_calloc(1, sizeof(pxl8_script)); + if (!script) return NULL; + + script->L = luaL_newstate(); + if (!script->L) { + SDL_free(script); + return NULL; + } + + luaL_openlibs(script->L); + + lua_getglobal(script->L, "require"); + lua_pushstring(script->L, "ffi"); + if (lua_pcall(script->L, 1, 1, 0) != 0) { + pxl8_script_set_error(script, lua_tostring(script->L, -1)); + pxl8_script_destroy(script); + return NULL; + } + + lua_getfield(script->L, -1, "cdef"); + lua_pushstring(script->L, pxl8_ffi_cdefs); + if (lua_pcall(script->L, 1, 0, 0) != 0) { + pxl8_script_set_error(script, lua_tostring(script->L, -1)); + pxl8_script_destroy(script); + return NULL; + } + + lua_pop(script->L, 1); + + lua_getglobal(script->L, "package"); + lua_getfield(script->L, -1, "path"); + const char* current_path = lua_tostring(script->L, -1); + lua_pop(script->L, 1); + + lua_pushfstring(script->L, "%s;src/lua/?.lua", current_path); + lua_setfield(script->L, -2, "path"); + lua_pop(script->L, 1); + + if (luaL_dofile(script->L, "lib/fennel/fennel.lua") == 0) { + lua_setglobal(script->L, "fennel"); + } + + script->last_error[0] = '\0'; + return script; +} + +void pxl8_script_destroy(pxl8_script* script) { + if (!script) return; + if (script->L) { + lua_close(script->L); + } + SDL_free(script); +} + +void pxl8_script_set_gfx(pxl8_script* script, pxl8_gfx* gfx) { + if (!script) return; + script->gfx = gfx; + if (script->L && gfx) { + lua_pushlightuserdata(script->L, gfx); + lua_setglobal(script->L, "_pxl8_gfx"); + } +} + +void pxl8_script_set_input(pxl8_script* script, pxl8_input_state* input) { + if (!script) return; + script->input = input; + if (script->L && input) { + lua_pushlightuserdata(script->L, input); + lua_setglobal(script->L, "_pxl8_input"); + } +} + +pxl8_result pxl8_script_run_file(pxl8_script* script, const char* filename) { + if (!script || !script->L || !filename) return PXL8_ERROR_NULL_POINTER; + + char* filename_copy = strdup(filename); + char* last_slash = strrchr(filename_copy, '/'); + const char* basename = filename; + + if (last_slash) { + *last_slash = '\0'; + char* script_dir = realpath(filename_copy, NULL); + char* original_cwd = getcwd(NULL, 0); + if (script_dir && original_cwd) { + chdir(script_dir); + pxl8_script_set_cart_path(script, script_dir, original_cwd); + basename = last_slash + 1; + } + free(script_dir); + free(original_cwd); + } + + pxl8_result result = PXL8_OK; + if (luaL_dofile(script->L, basename) != 0) { + pxl8_script_set_error(script, lua_tostring(script->L, -1)); + lua_pop(script->L, 1); + result = PXL8_ERROR_SCRIPT_ERROR; + } else { + script->last_error[0] = '\0'; + } + + free(filename_copy); + + return result; +} + +void pxl8_script_set_cart_path(pxl8_script* script, const char* cart_path, const char* original_cwd) { + if (!script || !script->L || !cart_path || !original_cwd) return; + + lua_getglobal(script->L, "package"); + lua_getfield(script->L, -1, "path"); + const char* current_path = lua_tostring(script->L, -1); + + char new_path[2048]; + snprintf(new_path, sizeof(new_path), + "%s/?.lua;%s/?/init.lua;%s/src/?.lua;%s/src/?/init.lua;%s/src/lua/?.lua;%s", + cart_path, cart_path, cart_path, cart_path, original_cwd, + current_path ? current_path : ""); + + lua_pushstring(script->L, new_path); + lua_setfield(script->L, -3, "path"); + lua_pop(script->L, 2); + + lua_getglobal(script->L, "fennel"); + if (!lua_isnil(script->L, -1)) { + lua_getfield(script->L, -1, "path"); + const char* fennel_path = lua_tostring(script->L, -1); + + snprintf(new_path, sizeof(new_path), + "%s/?.fnl;%s/?/init.fnl;%s/src/?.fnl;%s/src/?/init.fnl;%s", + cart_path, cart_path, cart_path, cart_path, + fennel_path ? fennel_path : ""); + + lua_pushstring(script->L, new_path); + lua_setfield(script->L, -3, "path"); + lua_pop(script->L, 1); + } + lua_pop(script->L, 1); +} + +pxl8_result pxl8_script_run_fennel_file(pxl8_script* script, const char* filename) { + if (!script || !script->L || !filename) return PXL8_ERROR_NULL_POINTER; + + char* filename_copy = strdup(filename); + char* last_slash = strrchr(filename_copy, '/'); + const char* basename = filename; + + if (last_slash) { + *last_slash = '\0'; + char* script_dir = realpath(filename_copy, NULL); + char* original_cwd = getcwd(NULL, 0); + if (script_dir && original_cwd) { + chdir(script_dir); + pxl8_script_set_cart_path(script, script_dir, original_cwd); + basename = last_slash + 1; + } + free(script_dir); + free(original_cwd); + } + + lua_getglobal(script->L, "fennel"); + if (lua_isnil(script->L, -1)) { + pxl8_script_set_error(script, "Fennel not loaded"); + lua_pop(script->L, 1); + free(filename_copy); + return PXL8_ERROR_SCRIPT_ERROR; + } + + lua_getfield(script->L, -1, "dofile"); + lua_pushstring(script->L, basename); + + pxl8_result result = PXL8_OK; + if (lua_pcall(script->L, 1, 0, 0) != 0) { + pxl8_script_set_error(script, lua_tostring(script->L, -1)); + lua_pop(script->L, 1); + result = PXL8_ERROR_SCRIPT_ERROR; + } else { + script->last_error[0] = '\0'; + } + + lua_pop(script->L, 1); + free(filename_copy); + + return result; +} + +pxl8_result pxl8_script_eval(pxl8_script* script, const char* code) { + if (!script || !script->L || !code) return PXL8_ERROR_NULL_POINTER; + + lua_getglobal(script->L, "fennel"); + if (lua_isnil(script->L, -1)) { + pxl8_script_set_error(script, "Fennel not loaded"); + lua_pop(script->L, 1); + return PXL8_ERROR_SCRIPT_ERROR; + } + + lua_getfield(script->L, -1, "eval"); + lua_pushstring(script->L, code); + + if (lua_pcall(script->L, 1, 1, 0) != 0) { + pxl8_script_set_error(script, lua_tostring(script->L, -1)); + lua_remove(script->L, -2); + return PXL8_ERROR_SCRIPT_ERROR; + } + + lua_remove(script->L, -2); + script->last_error[0] = '\0'; + return PXL8_OK; +} + +pxl8_result pxl8_script_load_module(pxl8_script* script, const char* module_name) { + if (!script || !script->L || !module_name) return PXL8_ERROR_NULL_POINTER; + + lua_getglobal(script->L, "require"); + lua_pushstring(script->L, module_name); + if (lua_pcall(script->L, 1, 1, 0) != 0) { + pxl8_script_set_error(script, lua_tostring(script->L, -1)); + lua_pop(script->L, 1); + return PXL8_ERROR_SCRIPT_ERROR; + } + + lua_setglobal(script->L, module_name); + script->last_error[0] = '\0'; + return PXL8_OK; +} + +pxl8_result pxl8_script_call_function(pxl8_script* script, const char* name) { + if (!script || !script->L || !name) return PXL8_ERROR_NULL_POINTER; + + lua_getglobal(script->L, name); + if (!lua_isfunction(script->L, -1)) { + lua_pop(script->L, 1); + return PXL8_OK; + } + + if (lua_pcall(script->L, 0, 0, 0) != 0) { + pxl8_script_set_error(script, lua_tostring(script->L, -1)); + lua_pop(script->L, 1); + return PXL8_ERROR_SCRIPT_ERROR; + } + + script->last_error[0] = '\0'; + return PXL8_OK; +} + +pxl8_result pxl8_script_call_function_f32(pxl8_script* script, const char* name, f32 arg) { + if (!script || !script->L || !name) return PXL8_ERROR_NULL_POINTER; + + lua_getglobal(script->L, name); + if (!lua_isfunction(script->L, -1)) { + lua_pop(script->L, 1); + return PXL8_OK; + } + + lua_pushnumber(script->L, arg); + if (lua_pcall(script->L, 1, 0, 0) != 0) { + pxl8_script_set_error(script, lua_tostring(script->L, -1)); + lua_pop(script->L, 1); + return PXL8_ERROR_SCRIPT_ERROR; + } + + script->last_error[0] = '\0'; + return PXL8_OK; +} + +static time_t get_file_mod_time(const char* path) { + struct stat file_stat; + if (stat(path, &file_stat) == 0) { + return file_stat.st_mtime; + } + return 0; +} + +static time_t get_latest_script_mod_time(const char* dir_path) { + DIR* dir = opendir(dir_path); + if (!dir) return 0; + + time_t latest = 0; + struct dirent* entry; + + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') continue; + + size_t len = strlen(entry->d_name); + bool is_script = (len > 4 && strcmp(entry->d_name + len - 4, ".fnl") == 0) || + (len > 4 && strcmp(entry->d_name + len - 4, ".lua") == 0); + + if (is_script) { + char full_path[512]; + snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name); + time_t mod_time = get_file_mod_time(full_path); + if (mod_time > latest) { + latest = mod_time; + } + } + } + + closedir(dir); + return latest; +} + +pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path) { + if (!script || !path) return PXL8_ERROR_NULL_POINTER; + + strncpy(script->main_path, path, sizeof(script->main_path) - 1); + script->main_path[sizeof(script->main_path) - 1] = '\0'; + + char* last_slash = strrchr(script->main_path, '/'); + if (last_slash) { + size_t dir_len = last_slash - script->main_path; + strncpy(script->watch_dir, script->main_path, dir_len); + script->watch_dir[dir_len] = '\0'; + } else { + strcpy(script->watch_dir, "."); + } + + script->latest_mod_time = get_latest_script_mod_time(script->watch_dir); + + const char* ext = strrchr(path, '.'); + pxl8_result result = PXL8_ERROR_INVALID_FORMAT; + + if (ext && strcmp(ext, ".fnl") == 0) { + result = pxl8_script_run_fennel_file(script, path); + } else if (ext && strcmp(ext, ".lua") == 0) { + result = pxl8_script_run_file(script, path); + } else { + pxl8_script_set_error(script, "Unknown script type (expected .fnl or .lua)"); + return PXL8_ERROR_INVALID_FORMAT; + } + + if (result == PXL8_OK) { + pxl8_info("Loaded script: %s", path); + pxl8_script_call_function(script, "init"); + } else { + pxl8_warn("Failed to load script: %s", script->last_error); + } + + return result; +} + +bool pxl8_script_check_reload(pxl8_script* script) { + if (!script || script->main_path[0] == '\0') return false; + + time_t current_mod_time = get_latest_script_mod_time(script->watch_dir); + if (current_mod_time > script->latest_mod_time && current_mod_time != 0) { + pxl8_info("Script files modified, reloading: %s", script->main_path); + script->latest_mod_time = current_mod_time; + + const char* ext = strrchr(script->main_path, '.'); + if (ext && strcmp(ext, ".fnl") == 0) { + if (pxl8_script_run_fennel_file(script, script->main_path) == PXL8_OK) { + pxl8_script_call_function(script, "init"); + return true; + } + } else if (ext && strcmp(ext, ".lua") == 0) { + if (pxl8_script_run_file(script, script->main_path) == PXL8_OK) { + pxl8_script_call_function(script, "init"); + return true; + } + } + } + + return false; +} diff --git a/src/pxl8_script.h b/src/pxl8_script.h new file mode 100644 index 0000000..e6a5fe8 --- /dev/null +++ b/src/pxl8_script.h @@ -0,0 +1,32 @@ +#pragma once + +#include "pxl8_gfx.h" +#include "pxl8_types.h" + +typedef struct pxl8_script pxl8_script; + +#ifdef __cplusplus +extern "C" { +#endif + +pxl8_script* pxl8_script_create(void); +void pxl8_script_destroy(pxl8_script* script); + +const char* pxl8_script_get_last_error(pxl8_script* script); +void pxl8_script_set_cart_path(pxl8_script* script, const char* cart_path, const char* original_cwd); +void pxl8_script_set_gfx(pxl8_script* script, pxl8_gfx* gfx); +void pxl8_script_set_input(pxl8_script* script, pxl8_input_state* input); + +pxl8_result pxl8_script_call_function(pxl8_script* script, const char* name); +pxl8_result pxl8_script_call_function_f32(pxl8_script* script, const char* name, f32 arg); +pxl8_result pxl8_script_eval(pxl8_script* script, const char* code); +pxl8_result pxl8_script_load_module(pxl8_script* script, const char* module_name); +pxl8_result pxl8_script_run_fennel_file(pxl8_script* script, const char* filename); +pxl8_result pxl8_script_run_file(pxl8_script* script, const char* filename); + +pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path); +bool pxl8_script_check_reload(pxl8_script* script); + +#ifdef __cplusplus +} +#endif diff --git a/src/pxl8_simd.h b/src/pxl8_simd.h index f794e89..35eaa73 100644 --- a/src/pxl8_simd.h +++ b/src/pxl8_simd.h @@ -187,3 +187,130 @@ static inline pxl8_simd_vec pxl8_simd_blendv_u32(pxl8_simd_vec src, pxl8_simd_ve #endif return result; } + +typedef union { +#if defined(PXL8_SIMD_AVX2) + __m256 avx2; + __m128 sse; +#elif defined(PXL8_SIMD_SSE2) || defined(__SSE__) + __m128 sse; +#elif defined(PXL8_SIMD_NEON) + float32x4_t neon; +#endif + f32 f32_array[8]; +} pxl8_simd_vec_f32; + +static inline pxl8_simd_vec_f32 pxl8_simd_set_f32(f32 x, f32 y, f32 z, f32 w) { + pxl8_simd_vec_f32 result; +#if defined(PXL8_SIMD_AVX2) + result.avx2 = _mm256_set_ps(0, 0, 0, 0, w, z, y, x); +#elif defined(PXL8_SIMD_SSE2) || defined(__SSE__) + result.sse = _mm_set_ps(w, z, y, x); +#elif defined(PXL8_SIMD_NEON) + f32 data[4] = {x, y, z, w}; + result.neon = vld1q_f32(data); +#else + result.f32_array[0] = x; + result.f32_array[1] = y; + result.f32_array[2] = z; + result.f32_array[3] = w; +#endif + return result; +} + +static inline pxl8_simd_vec_f32 pxl8_simd_add_f32(pxl8_simd_vec_f32 a, pxl8_simd_vec_f32 b) { + pxl8_simd_vec_f32 result; +#if defined(PXL8_SIMD_AVX2) + result.avx2 = _mm256_add_ps(a.avx2, b.avx2); +#elif defined(PXL8_SIMD_SSE2) || defined(__SSE__) + result.sse = _mm_add_ps(a.sse, b.sse); +#elif defined(PXL8_SIMD_NEON) + result.neon = vaddq_f32(a.neon, b.neon); +#else + for (i32 i = 0; i < 4; i++) result.f32_array[i] = a.f32_array[i] + b.f32_array[i]; +#endif + return result; +} + +static inline pxl8_simd_vec_f32 pxl8_simd_sub_f32(pxl8_simd_vec_f32 a, pxl8_simd_vec_f32 b) { + pxl8_simd_vec_f32 result; +#if defined(PXL8_SIMD_AVX2) + result.avx2 = _mm256_sub_ps(a.avx2, b.avx2); +#elif defined(PXL8_SIMD_SSE2) || defined(__SSE__) + result.sse = _mm_sub_ps(a.sse, b.sse); +#elif defined(PXL8_SIMD_NEON) + result.neon = vsubq_f32(a.neon, b.neon); +#else + for (i32 i = 0; i < 4; i++) result.f32_array[i] = a.f32_array[i] - b.f32_array[i]; +#endif + return result; +} + +static inline pxl8_simd_vec_f32 pxl8_simd_mul_f32(pxl8_simd_vec_f32 a, pxl8_simd_vec_f32 b) { + pxl8_simd_vec_f32 result; +#if defined(PXL8_SIMD_AVX2) + result.avx2 = _mm256_mul_ps(a.avx2, b.avx2); +#elif defined(PXL8_SIMD_SSE2) || defined(__SSE__) + result.sse = _mm_mul_ps(a.sse, b.sse); +#elif defined(PXL8_SIMD_NEON) + result.neon = vmulq_f32(a.neon, b.neon); +#else + for (i32 i = 0; i < 4; i++) result.f32_array[i] = a.f32_array[i] * b.f32_array[i]; +#endif + return result; +} + +static inline pxl8_simd_vec_f32 pxl8_simd_scale_f32(pxl8_simd_vec_f32 v, f32 s) { + pxl8_simd_vec_f32 result; +#if defined(PXL8_SIMD_AVX2) + result.avx2 = _mm256_mul_ps(v.avx2, _mm256_set1_ps(s)); +#elif defined(PXL8_SIMD_SSE2) || defined(__SSE__) + result.sse = _mm_mul_ps(v.sse, _mm_set1_ps(s)); +#elif defined(PXL8_SIMD_NEON) + result.neon = vmulq_n_f32(v.neon, s); +#else + for (i32 i = 0; i < 4; i++) result.f32_array[i] = v.f32_array[i] * s; +#endif + return result; +} + +static inline f32 pxl8_simd_dot3_f32(pxl8_simd_vec_f32 a, pxl8_simd_vec_f32 b) { +#if defined(PXL8_SIMD_SSE2) || defined(__SSE__) + __m128 mul = _mm_mul_ps(a.sse, b.sse); + __m128 shuf = _mm_shuffle_ps(mul, mul, _MM_SHUFFLE(2, 1, 0, 3)); + __m128 sums = _mm_add_ps(mul, shuf); + shuf = _mm_movehl_ps(shuf, sums); + sums = _mm_add_ss(sums, shuf); + return _mm_cvtss_f32(sums); +#elif defined(PXL8_SIMD_NEON) + float32x4_t mul = vmulq_f32(a.neon, b.neon); + float32x2_t sum = vpadd_f32(vget_low_f32(mul), vget_high_f32(mul)); + sum = vpadd_f32(sum, sum); + return vget_lane_f32(sum, 0); +#else + return a.f32_array[0] * b.f32_array[0] + + a.f32_array[1] * b.f32_array[1] + + a.f32_array[2] * b.f32_array[2]; +#endif +} + +static inline f32 pxl8_simd_dot4_f32(pxl8_simd_vec_f32 a, pxl8_simd_vec_f32 b) { +#if defined(PXL8_SIMD_SSE2) || defined(__SSE__) + __m128 mul = _mm_mul_ps(a.sse, b.sse); + __m128 shuf = _mm_shuffle_ps(mul, mul, _MM_SHUFFLE(2, 3, 0, 1)); + __m128 sums = _mm_add_ps(mul, shuf); + shuf = _mm_movehl_ps(shuf, sums); + sums = _mm_add_ss(sums, shuf); + return _mm_cvtss_f32(sums); +#elif defined(PXL8_SIMD_NEON) + float32x4_t mul = vmulq_f32(a.neon, b.neon); + float32x2_t sum = vpadd_f32(vget_low_f32(mul), vget_high_f32(mul)); + sum = vpadd_f32(sum, sum); + return vget_lane_f32(sum, 0); +#else + return a.f32_array[0] * b.f32_array[0] + + a.f32_array[1] * b.f32_array[1] + + a.f32_array[2] * b.f32_array[2] + + a.f32_array[3] * b.f32_array[3]; +#endif +} diff --git a/src/pxl8_tilemap.c b/src/pxl8_tilemap.c index 89323bd..f811612 100644 --- a/src/pxl8_tilemap.c +++ b/src/pxl8_tilemap.c @@ -5,6 +5,27 @@ #include "pxl8_tilemap.h" #include "pxl8_tilesheet.h" +struct pxl8_tilemap_layer { + u32 allocated_chunks; + u32 chunk_count; + pxl8_tile_chunk** chunks; + u32 chunks_high; + u32 chunks_wide; + u8 opacity; + bool visible; +}; + +struct pxl8_tilemap { + u32 active_layers; + i32 camera_x; + i32 camera_y; + u32 height; + pxl8_tilemap_layer layers[PXL8_MAX_TILE_LAYERS]; + u32 tile_size; + pxl8_tilesheet* tilesheet; + u32 width; +}; + static inline u32 pxl8_chunk_index(u32 x, u32 y, u32 chunks_wide) { return (y >> 4) * chunks_wide + (x >> 4); } @@ -29,13 +50,14 @@ static pxl8_tile_chunk* pxl8_get_or_create_chunk(pxl8_tilemap_layer* layer, u32 return layer->chunks[idx]; } -pxl8_result pxl8_tilemap_init(pxl8_tilemap* tilemap, u32 width, u32 height, u32 tile_size) { - if (!tilemap) return PXL8_ERROR_NULL_POINTER; +pxl8_tilemap* pxl8_tilemap_create(u32 width, u32 height, u32 tile_size) { if (width > PXL8_MAX_TILEMAP_WIDTH || height > PXL8_MAX_TILEMAP_HEIGHT) { - return PXL8_ERROR_INVALID_SIZE; + return NULL; } - memset(tilemap, 0, sizeof(pxl8_tilemap)); + pxl8_tilemap* tilemap = calloc(1, sizeof(pxl8_tilemap)); + if (!tilemap) return NULL; + tilemap->width = width; tilemap->height = height; tilemap->tile_size = tile_size ? tile_size : PXL8_TILE_SIZE; @@ -57,14 +79,15 @@ pxl8_result pxl8_tilemap_init(pxl8_tilemap* tilemap, u32 width, u32 height, u32 for (u32 j = 0; j < i; j++) { free(tilemap->layers[j].chunks); } - return PXL8_ERROR_OUT_OF_MEMORY; + free(tilemap); + return NULL; } } - return PXL8_OK; + return tilemap; } -void pxl8_tilemap_free(pxl8_tilemap* tilemap) { +void pxl8_tilemap_destroy(pxl8_tilemap* tilemap) { if (!tilemap) return; for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) { @@ -76,14 +99,14 @@ void pxl8_tilemap_free(pxl8_tilemap* tilemap) { } } free(layer->chunks); - layer->chunks = NULL; } } if (tilemap->tilesheet) { pxl8_tilesheet_unref(tilemap->tilesheet); - tilemap->tilesheet = NULL; } + + free(tilemap); } pxl8_result pxl8_tilemap_set_tilesheet(pxl8_tilemap* tilemap, pxl8_tilesheet* tilesheet) { @@ -96,10 +119,11 @@ pxl8_result pxl8_tilemap_set_tilesheet(pxl8_tilemap* tilemap, pxl8_tilesheet* ti tilemap->tilesheet = tilesheet; pxl8_tilesheet_ref(tilesheet); - if (tilesheet->tile_size != tilemap->tile_size) { + u32 tilesheet_size = pxl8_tilesheet_get_tile_size(tilesheet); + if (tilesheet_size != tilemap->tile_size) { pxl8_warn("Tilesheet tile size (%d) differs from tilemap tile size (%d)", - tilesheet->tile_size, tilemap->tile_size); - tilemap->tile_size = tilesheet->tile_size; + tilesheet_size, tilemap->tile_size); + tilemap->tile_size = tilesheet_size; } return PXL8_OK; @@ -155,13 +179,13 @@ void pxl8_tilemap_set_camera(pxl8_tilemap* tilemap, i32 x, i32 y) { tilemap->camera_y = y; } -void pxl8_tilemap_get_view(const pxl8_tilemap* tilemap, const pxl8_gfx_ctx* gfx, pxl8_tilemap_view* view) { +void pxl8_tilemap_get_view(const pxl8_tilemap* tilemap, const pxl8_gfx* gfx, pxl8_tilemap_view* view) { if (!tilemap || !gfx || !view) return; view->x = -tilemap->camera_x; view->y = -tilemap->camera_y; - view->width = gfx->framebuffer_width; - view->height = gfx->framebuffer_height; + view->width = pxl8_gfx_get_width(gfx); + view->height = pxl8_gfx_get_height(gfx); view->tile_start_x = pxl8_max(0, tilemap->camera_x / (i32)tilemap->tile_size); view->tile_start_y = pxl8_max(0, tilemap->camera_y / (i32)tilemap->tile_size); @@ -171,12 +195,12 @@ void pxl8_tilemap_get_view(const pxl8_tilemap* tilemap, const pxl8_gfx_ctx* gfx, (tilemap->camera_y + view->height) / (i32)tilemap->tile_size + 1); } -void pxl8_tilemap_render_tile(const pxl8_tilemap* tilemap, pxl8_gfx_ctx* gfx, u16 tile_id, i32 x, i32 y, u8 flags) { +void pxl8_tilemap_render_tile(const pxl8_tilemap* tilemap, pxl8_gfx* gfx, u16 tile_id, i32 x, i32 y, u8 flags) { if (!tilemap || !gfx || !tilemap->tilesheet) return; pxl8_tilesheet_render_tile(tilemap->tilesheet, gfx, tile_id, x, y, flags); } -void pxl8_tilemap_render_layer(const pxl8_tilemap* tilemap, pxl8_gfx_ctx* gfx, u32 layer) { +void pxl8_tilemap_render_layer(const pxl8_tilemap* tilemap, pxl8_gfx* gfx, u32 layer) { if (!tilemap || !gfx || layer >= tilemap->active_layers) return; if (!tilemap->tilesheet) { pxl8_warn("No tilesheet set for tilemap"); @@ -188,8 +212,8 @@ void pxl8_tilemap_render_layer(const pxl8_tilemap* tilemap, pxl8_gfx_ctx* gfx, u i32 view_left = tilemap->camera_x / tilemap->tile_size; i32 view_top = tilemap->camera_y / tilemap->tile_size; - i32 view_right = (tilemap->camera_x + gfx->framebuffer_width) / tilemap->tile_size + 1; - i32 view_bottom = (tilemap->camera_y + gfx->framebuffer_height) / tilemap->tile_size + 1; + i32 view_right = (tilemap->camera_x + pxl8_gfx_get_width(gfx)) / tilemap->tile_size + 1; + i32 view_bottom = (tilemap->camera_y + pxl8_gfx_get_height(gfx)) / tilemap->tile_size + 1; u32 chunk_left = pxl8_max(0, view_left >> 4); u32 chunk_top = pxl8_max(0, view_top >> 4); @@ -233,7 +257,7 @@ void pxl8_tilemap_render_layer(const pxl8_tilemap* tilemap, pxl8_gfx_ctx* gfx, u } } -void pxl8_tilemap_render(const pxl8_tilemap* tilemap, pxl8_gfx_ctx* gfx) { +void pxl8_tilemap_render(const pxl8_tilemap* tilemap, pxl8_gfx* gfx) { if (!tilemap || !gfx) return; for (u32 layer = 0; layer < tilemap->active_layers; layer++) { diff --git a/src/pxl8_tilemap.h b/src/pxl8_tilemap.h index fea4659..699113b 100644 --- a/src/pxl8_tilemap.h +++ b/src/pxl8_tilemap.h @@ -46,20 +46,20 @@ static inline u8 pxl8_tile_get_palette(pxl8_tile tile) { } typedef struct pxl8_tile_animation { - u16* frames; - u16 frame_count; u16 current_frame; f32 frame_duration; + u16 frame_count; + u16* frames; f32 time_accumulator; } pxl8_tile_animation; typedef struct pxl8_tile_properties { - void* user_data; - u32 property_flags; i16 collision_offset_x; i16 collision_offset_y; - u16 collision_width; u16 collision_height; + u16 collision_width; + u32 property_flags; + void* user_data; } pxl8_tile_properties; typedef struct pxl8_autotile_rule { @@ -68,64 +68,46 @@ typedef struct pxl8_autotile_rule { } pxl8_autotile_rule; typedef struct pxl8_tile_chunk { - pxl8_tile tiles[PXL8_CHUNK_SIZE * PXL8_CHUNK_SIZE]; u32 chunk_x; u32 chunk_y; bool empty; + pxl8_tile tiles[PXL8_CHUNK_SIZE * PXL8_CHUNK_SIZE]; } pxl8_tile_chunk; -typedef struct pxl8_tilemap_layer { - pxl8_tile_chunk** chunks; - u32 chunks_wide; - u32 chunks_high; - u32 chunk_count; - u32 allocated_chunks; - bool visible; - u8 opacity; -} pxl8_tilemap_layer; - -typedef struct pxl8_tilemap { - pxl8_tilemap_layer layers[PXL8_MAX_TILE_LAYERS]; - u32 width; - u32 height; - u32 tile_size; - u32 active_layers; - - pxl8_tilesheet* tilesheet; - - i32 camera_x; - i32 camera_y; -} pxl8_tilemap; +typedef struct pxl8_tilemap_layer pxl8_tilemap_layer; +typedef struct pxl8_tilemap pxl8_tilemap; typedef struct pxl8_tilemap_view { - i32 x, y; - i32 width, height; - i32 tile_start_x, tile_start_y; + i32 height; i32 tile_end_x, tile_end_y; + i32 tile_start_x, tile_start_y; + i32 width; + i32 x, y; } pxl8_tilemap_view; #ifdef __cplusplus extern "C" { #endif +pxl8_tilemap* pxl8_tilemap_create(u32 width, u32 height, u32 tile_size); +void pxl8_tilemap_destroy(pxl8_tilemap* tilemap); + bool pxl8_tilemap_check_collision(const pxl8_tilemap* tilemap, i32 x, i32 y, i32 w, i32 h); void pxl8_tilemap_compress(pxl8_tilemap* tilemap); -void pxl8_tilemap_free(pxl8_tilemap* tilemap); u32 pxl8_tilemap_get_memory_usage(const pxl8_tilemap* tilemap); pxl8_tile pxl8_tilemap_get_tile(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y); u16 pxl8_tilemap_get_tile_id(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y); void pxl8_tilemap_get_view( const pxl8_tilemap* tilemap, - const pxl8_gfx_ctx* gfx, + const pxl8_gfx* gfx, pxl8_tilemap_view* view ); -pxl8_result pxl8_tilemap_init(pxl8_tilemap* tilemap, u32 width, u32 height, u32 tile_size); bool pxl8_tilemap_is_solid(const pxl8_tilemap* tilemap, u32 x, u32 y); -void pxl8_tilemap_render(const pxl8_tilemap* tilemap, pxl8_gfx_ctx* gfx); -void pxl8_tilemap_render_layer(const pxl8_tilemap* tilemap, pxl8_gfx_ctx* gfx, u32 layer); +void pxl8_tilemap_render(const pxl8_tilemap* tilemap, pxl8_gfx* gfx); +void pxl8_tilemap_render_layer(const pxl8_tilemap* tilemap, pxl8_gfx* gfx, u32 layer); void pxl8_tilemap_render_tile( const pxl8_tilemap* tilemap, - pxl8_gfx_ctx* gfx, + pxl8_gfx* gfx, u16 tile_id, i32 x, i32 y, diff --git a/src/pxl8_tilesheet.c b/src/pxl8_tilesheet.c index 0b11e30..cfc6748 100644 --- a/src/pxl8_tilesheet.c +++ b/src/pxl8_tilesheet.c @@ -6,17 +6,47 @@ #include "pxl8_tilesheet.h" #include "pxl8_tilemap.h" -void pxl8_tilesheet_free(pxl8_tilesheet* tilesheet) { +struct pxl8_tilesheet { + u8* data; + bool* tile_valid; + + u32 height; + u32 tile_size; + u32 tiles_per_row; + u32 total_tiles; + u32 width; + + pxl8_color_mode color_mode; + u32 ref_count; + + pxl8_tile_animation* animations; + u32 animation_count; + + pxl8_tile_properties* properties; + + pxl8_autotile_rule** autotile_rules; + u32* autotile_rule_counts; +}; + +pxl8_tilesheet* pxl8_tilesheet_create(u32 tile_size) { + pxl8_tilesheet* tilesheet = calloc(1, sizeof(pxl8_tilesheet)); + if (!tilesheet) return NULL; + + tilesheet->tile_size = tile_size; + tilesheet->ref_count = 1; + + return tilesheet; +} + +void pxl8_tilesheet_destroy(pxl8_tilesheet* tilesheet) { if (!tilesheet) return; if (tilesheet->data) { free(tilesheet->data); - tilesheet->data = NULL; } if (tilesheet->tile_valid) { free(tilesheet->tile_valid); - tilesheet->tile_valid = NULL; } if (tilesheet->animations) { @@ -26,12 +56,10 @@ void pxl8_tilesheet_free(pxl8_tilesheet* tilesheet) { } } free(tilesheet->animations); - tilesheet->animations = NULL; } if (tilesheet->properties) { free(tilesheet->properties); - tilesheet->properties = NULL; } if (tilesheet->autotile_rules) { @@ -42,12 +70,12 @@ void pxl8_tilesheet_free(pxl8_tilesheet* tilesheet) { } free(tilesheet->autotile_rules); free(tilesheet->autotile_rule_counts); - tilesheet->autotile_rules = NULL; - tilesheet->autotile_rule_counts = NULL; } + + free(tilesheet); } -pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, pxl8_gfx_ctx* gfx) { +pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, pxl8_gfx* gfx) { if (!tilesheet || !filepath || !gfx) return PXL8_ERROR_NULL_POINTER; pxl8_ase_file ase_file; @@ -74,16 +102,16 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, tilesheet->height = height; tilesheet->tiles_per_row = width / tilesheet->tile_size; tilesheet->total_tiles = (width / tilesheet->tile_size) * (height / tilesheet->tile_size); - tilesheet->color_mode = gfx->color_mode; + tilesheet->color_mode = pxl8_gfx_get_color_mode(gfx); size_t data_size = width * height; - if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) { + if (pxl8_gfx_get_color_mode(gfx) == PXL8_COLOR_MODE_HICOLOR) { data_size *= 4; } tilesheet->data = malloc(data_size); if (!tilesheet->data) { - pxl8_ase_free(&ase_file); + pxl8_ase_destroy(&ase_file); return PXL8_ERROR_OUT_OF_MEMORY; } @@ -95,7 +123,7 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, if (!tilesheet->tile_valid) { free(tilesheet->data); tilesheet->data = NULL; - pxl8_ase_free(&ase_file); + pxl8_ase_destroy(&ase_file); return PXL8_ERROR_OUT_OF_MEMORY; } @@ -135,11 +163,11 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, filepath, width, height, valid_tiles, tilesheet->total_tiles, tilesheet->tile_size, tilesheet->tile_size); - pxl8_ase_free(&ase_file); + pxl8_ase_destroy(&ase_file); return PXL8_OK; } -void pxl8_tilesheet_render_tile(const pxl8_tilesheet* tilesheet, pxl8_gfx_ctx* gfx, +void pxl8_tilesheet_render_tile(const pxl8_tilesheet* tilesheet, pxl8_gfx* gfx, u16 tile_id, i32 x, i32 y, u8 flags) { if (!tilesheet || !gfx || !tilesheet->data) return; if (tile_id == 0 || tile_id > tilesheet->total_tiles) return; @@ -163,8 +191,8 @@ void pxl8_tilesheet_render_tile(const pxl8_tilesheet* tilesheet, pxl8_gfx_ctx* g i32 screen_x = x + px; i32 screen_y = y + py; - if (screen_x >= 0 && screen_x < gfx->framebuffer_width && - screen_y >= 0 && screen_y < gfx->framebuffer_height) { + if (screen_x >= 0 && screen_x < pxl8_gfx_get_width(gfx) && + screen_y >= 0 && screen_y < pxl8_gfx_get_height(gfx)) { pxl8_pixel(gfx, screen_x, screen_y, color_idx); } } @@ -186,7 +214,7 @@ void pxl8_tilesheet_unref(pxl8_tilesheet* tilesheet) { if (!tilesheet) return; if (--tilesheet->ref_count == 0) { - pxl8_tilesheet_free(tilesheet); + pxl8_tilesheet_destroy(tilesheet); } } @@ -261,6 +289,10 @@ const pxl8_tile_properties* pxl8_tilesheet_get_tile_property(const pxl8_tileshee return &tilesheet->properties[tile_id]; } +u32 pxl8_tilesheet_get_tile_size(const pxl8_tilesheet* tilesheet) { + return tilesheet ? tilesheet->tile_size : 0; +} + pxl8_result pxl8_tilesheet_add_autotile_rule(pxl8_tilesheet* tilesheet, u16 base_tile_id, u8 neighbor_mask, u16 result_tile_id) { if (!tilesheet || base_tile_id == 0 || base_tile_id > tilesheet->total_tiles) { diff --git a/src/pxl8_tilesheet.h b/src/pxl8_tilesheet.h index fd94bf2..1cdb9b4 100644 --- a/src/pxl8_tilesheet.h +++ b/src/pxl8_tilesheet.h @@ -6,31 +6,15 @@ typedef struct pxl8_tile_animation pxl8_tile_animation; typedef struct pxl8_tile_properties pxl8_tile_properties; typedef struct pxl8_autotile_rule pxl8_autotile_rule; - -typedef struct pxl8_tilesheet { - u8* data; - bool* tile_valid; - u32 width; - u32 height; - u32 tile_size; - u32 tiles_per_row; - u32 total_tiles; - pxl8_color_mode color_mode; - u32 ref_count; - - pxl8_tile_animation* animations; - u32 animation_count; - - pxl8_tile_properties* properties; - - pxl8_autotile_rule** autotile_rules; - u32* autotile_rule_counts; -} pxl8_tilesheet; +typedef struct pxl8_tilesheet pxl8_tilesheet; #ifdef __cplusplus extern "C" { #endif +pxl8_tilesheet* pxl8_tilesheet_create(u32 tile_size); +void pxl8_tilesheet_destroy(pxl8_tilesheet* tilesheet); + pxl8_result pxl8_tilesheet_add_animation( pxl8_tilesheet* tilesheet, u16 base_tile_id, @@ -45,18 +29,18 @@ pxl8_result pxl8_tilesheet_add_autotile_rule( u16 result_tile_id ); u16 pxl8_tilesheet_apply_autotile(const pxl8_tilesheet* tilesheet, u16 base_tile_id, u8 neighbors); -void pxl8_tilesheet_free(pxl8_tilesheet* tilesheet); u16 pxl8_tilesheet_get_animated_frame(const pxl8_tilesheet* tilesheet, u16 tile_id); const pxl8_tile_properties* pxl8_tilesheet_get_tile_property( const pxl8_tilesheet* tilesheet, u16 tile_id ); +u32 pxl8_tilesheet_get_tile_size(const pxl8_tilesheet* tilesheet); bool pxl8_tilesheet_is_tile_valid(const pxl8_tilesheet* tilesheet, u16 tile_id); -pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, pxl8_gfx_ctx* gfx); +pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, pxl8_gfx* gfx); void pxl8_tilesheet_ref(pxl8_tilesheet* tilesheet); void pxl8_tilesheet_render_tile( const pxl8_tilesheet* tilesheet, - pxl8_gfx_ctx* gfx, + pxl8_gfx* gfx, u16 tile_id, i32 x, i32 y, diff --git a/src/pxl8_types.h b/src/pxl8_types.h index 41dbf58..1ce0cf8 100644 --- a/src/pxl8_types.h +++ b/src/pxl8_types.h @@ -4,28 +4,28 @@ #include #include -typedef uint8_t u8; -typedef uint16_t u16; -typedef uint32_t u32; -typedef uint64_t u64; +typedef float f32; +typedef double f64; typedef int8_t i8; typedef int16_t i16; typedef int32_t i32; typedef int64_t i64; -typedef float f32; -typedef double f64; +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; #if defined(__SIZEOF_INT128__) -typedef __uint128_t u128; typedef __int128_t i128; +typedef __uint128_t u128; #endif typedef enum pxl8_color_mode { PXL8_COLOR_MODE_FAMI, - PXL8_COLOR_MODE_MEGA, PXL8_COLOR_MODE_GBA, - PXL8_COLOR_MODE_SUPERFAMI, PXL8_COLOR_MODE_HICOLOR, + PXL8_COLOR_MODE_MEGA, + PXL8_COLOR_MODE_SUPERFAMI } pxl8_color_mode; typedef enum pxl8_resolution { @@ -35,9 +35,34 @@ typedef enum pxl8_resolution { PXL8_RESOLUTION_640x360, PXL8_RESOLUTION_640x480, PXL8_RESOLUTION_800x600, - PXL8_RESOLUTION_960x540, + PXL8_RESOLUTION_960x540 } pxl8_resolution; +typedef enum pxl8_result { + PXL8_OK = 0, + PXL8_ERROR_ASE_INVALID_FRAME_MAGIC, + PXL8_ERROR_ASE_INVALID_MAGIC, + PXL8_ERROR_ASE_MALFORMED_CHUNK, + PXL8_ERROR_ASE_TRUNCATED_FILE, + PXL8_ERROR_FILE_NOT_FOUND, + PXL8_ERROR_INITIALIZATION_FAILED, + PXL8_ERROR_INVALID_ARGUMENT, + PXL8_ERROR_INVALID_COORDINATE, + PXL8_ERROR_INVALID_FORMAT, + PXL8_ERROR_INVALID_SIZE, + PXL8_ERROR_NOT_INITIALIZED, + PXL8_ERROR_NULL_POINTER, + PXL8_ERROR_OUT_OF_MEMORY, + PXL8_ERROR_SCRIPT_ERROR, + PXL8_ERROR_SYSTEM_FAILURE +} pxl8_result; + +typedef struct pxl8_bounds { + i32 x, y; + i32 w; + i32 h; +} pxl8_bounds; + typedef struct pxl8_input_state { bool keys[256]; bool keys_pressed[256]; @@ -46,27 +71,3 @@ typedef struct pxl8_input_state { typedef struct pxl8_point { i32 x, y; } pxl8_point; - -typedef struct pxl8_rectangle { - i32 x, y; - i32 width, height; -} pxl8_rectangle; - -typedef enum pxl8_result { - PXL8_OK = 0, - PXL8_ERROR_NULL_POINTER, - PXL8_ERROR_INVALID_ARGUMENT, - PXL8_ERROR_OUT_OF_MEMORY, - PXL8_ERROR_FILE_NOT_FOUND, - PXL8_ERROR_INVALID_FORMAT, - PXL8_ERROR_SYSTEM_FAILURE, - PXL8_ERROR_INVALID_COORDINATE, - PXL8_ERROR_INVALID_SIZE, - PXL8_ERROR_INITIALIZATION_FAILED, - PXL8_ERROR_NOT_INITIALIZED, - PXL8_ERROR_SCRIPT_ERROR, - PXL8_ERROR_ASE_INVALID_MAGIC, - PXL8_ERROR_ASE_INVALID_FRAME_MAGIC, - PXL8_ERROR_ASE_TRUNCATED_FILE, - PXL8_ERROR_ASE_MALFORMED_CHUNK, -} pxl8_result; diff --git a/src/pxl8_vfx.c b/src/pxl8_vfx.c index 31cfc6f..90a5059 100644 --- a/src/pxl8_vfx.c +++ b/src/pxl8_vfx.c @@ -6,11 +6,33 @@ #include "pxl8_macros.h" #include "pxl8_vfx.h" -void pxl8_vfx_plasma(pxl8_gfx_ctx* ctx, f32 time, f32 scale1, f32 scale2, u8 palette_offset) { - if (!ctx || !ctx->framebuffer) return; +struct pxl8_particles { + pxl8_particle* particles; + u32 alive_count; + u32 count; + u32 max_count; + + f32 x, y; + f32 spread_x, spread_y; + + f32 drag; + f32 gravity_x, gravity_y; + f32 turbulence; + + f32 spawn_rate; + f32 spawn_timer; + + void (*render_fn)(pxl8_gfx* gfx, pxl8_particle* p, void* userdata); + void (*spawn_fn)(pxl8_particle* p, void* userdata); + void (*update_fn)(pxl8_particle* p, f32 dt, void* userdata); + 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 < ctx->framebuffer_height; y++) { - for (i32 x = 0; x < ctx->framebuffer_width; x++) { + for (i32 y = 0; y < pxl8_gfx_get_height(ctx); y++) { + for (i32 x = 0; x < pxl8_gfx_get_width(ctx); x++) { f32 v1 = sinf(x * scale1 + time); f32 v2 = sinf(y * scale1 + time * 0.7f); f32 v3 = sinf((x + y) * scale2 + time * 1.3f); @@ -27,44 +49,47 @@ void pxl8_vfx_plasma(pxl8_gfx_ctx* ctx, f32 time, f32 scale1, f32 scale2, u8 pal } } -void pxl8_vfx_raster_bars(pxl8_gfx_ctx* ctx, pxl8_raster_bar* bars, u32 bar_count, f32 time) { +void pxl8_vfx_raster_bars(pxl8_gfx* ctx, pxl8_raster_bar* bars, u32 bar_count, f32 time) { if (!ctx || !bars) return; - + for (u32 i = 0; i < bar_count; i++) { pxl8_raster_bar* bar = &bars[i]; f32 y = bar->base_y + bar->amplitude * sinf(time * bar->speed + bar->phase); i32 y_int = (i32)y; - - for (i32 dy = 0; dy <= bar->height; dy++) { - f32 position = (f32)dy / (f32)bar->height; - f32 gradient = 1.0f - 2.0f * fabsf(position - 0.5f); - + + for (i32 dy = 0; dy < bar->height; dy++) { + f32 position = (f32)dy / (f32)(bar->height - 1); + f32 distance_from_center = fabsf(position - 0.5f) * 2.0f; + u8 color_idx; - if (gradient > 0.8f) { + if (distance_from_center < 0.3f) { color_idx = bar->fade_color; + } else if (distance_from_center < 0.6f) { + color_idx = bar->fade_color - 1; + } else if (distance_from_center < 0.8f) { + color_idx = bar->color; } else { - u8 range = bar->fade_color - bar->color; - color_idx = bar->color + (u8)(gradient * range); + color_idx = bar->color - 1; } - - pxl8_rect_fill(ctx, 0, y_int + dy, ctx->framebuffer_width, 1, color_idx); + + pxl8_rect_fill(ctx, 0, y_int + dy, pxl8_gfx_get_width(ctx), 1, color_idx); } } } -void pxl8_vfx_rotozoom(pxl8_gfx_ctx* ctx, f32 angle, f32 zoom, i32 cx, i32 cy) { - if (!ctx || !ctx->framebuffer) return; +void pxl8_vfx_rotozoom(pxl8_gfx* ctx, f32 angle, f32 zoom, i32 cx, i32 cy) { + if (!ctx || !pxl8_gfx_get_framebuffer(ctx)) return; f32 cos_a = cosf(angle); f32 sin_a = sinf(angle); - u8* temp_buffer = (u8*)SDL_malloc(ctx->framebuffer_width * ctx->framebuffer_height); + u8* temp_buffer = (u8*)SDL_malloc(pxl8_gfx_get_width(ctx) * pxl8_gfx_get_height(ctx)); if (!temp_buffer) return; - SDL_memcpy(temp_buffer, ctx->framebuffer, ctx->framebuffer_width * ctx->framebuffer_height); + SDL_memcpy(temp_buffer, pxl8_gfx_get_framebuffer(ctx), pxl8_gfx_get_width(ctx) * pxl8_gfx_get_height(ctx)); - for (i32 y = 0; y < ctx->framebuffer_height; y++) { - for (i32 x = 0; x < ctx->framebuffer_width; x++) { + for (i32 y = 0; y < pxl8_gfx_get_height(ctx); y++) { + for (i32 x = 0; x < pxl8_gfx_get_width(ctx); x++) { f32 dx = x - cx; f32 dy = y - cy; @@ -74,8 +99,8 @@ void pxl8_vfx_rotozoom(pxl8_gfx_ctx* ctx, f32 angle, f32 zoom, i32 cx, i32 cy) { i32 sx = (i32)src_x; i32 sy = (i32)src_y; - if (sx >= 0 && sx < ctx->framebuffer_width && sy >= 0 && sy < ctx->framebuffer_height) { - ctx->framebuffer[y * ctx->framebuffer_width + x] = temp_buffer[sy * ctx->framebuffer_width + sx]; + 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]; } } } @@ -83,14 +108,14 @@ void pxl8_vfx_rotozoom(pxl8_gfx_ctx* ctx, f32 angle, f32 zoom, i32 cx, i32 cy) { SDL_free(temp_buffer); } -void pxl8_vfx_tunnel(pxl8_gfx_ctx* ctx, f32 time, f32 speed, f32 twist) { - if (!ctx || !ctx->framebuffer) return; +void pxl8_vfx_tunnel(pxl8_gfx* ctx, f32 time, f32 speed, f32 twist) { + if (!ctx || !pxl8_gfx_get_framebuffer(ctx)) return; - f32 cx = ctx->framebuffer_width / 2.0f; - f32 cy = ctx->framebuffer_height / 2.0f; + f32 cx = pxl8_gfx_get_width(ctx) / 2.0f; + f32 cy = pxl8_gfx_get_height(ctx) / 2.0f; - for (i32 y = 0; y < ctx->framebuffer_height; y++) { - for (i32 x = 0; x < ctx->framebuffer_width; x++) { + for (i32 y = 0; y < pxl8_gfx_get_height(ctx); y++) { + for (i32 x = 0; x < pxl8_gfx_get_width(ctx); x++) { f32 dx = x - cx; f32 dy = y - cy; f32 dist = sqrtf(dx * dx + dy * dy); @@ -110,11 +135,11 @@ void pxl8_vfx_tunnel(pxl8_gfx_ctx* ctx, f32 time, f32 speed, f32 twist) { } } -void pxl8_vfx_water_ripple(pxl8_gfx_ctx* ctx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping) { +void pxl8_vfx_water_ripple(pxl8_gfx* ctx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping) { if (!ctx || !height_map) return; - i32 w = ctx->framebuffer_width; - i32 h = ctx->framebuffer_height; + i32 w = pxl8_gfx_get_width(ctx); + i32 h = pxl8_gfx_get_height(ctx); static f32* prev_height = NULL; if (!prev_height) { @@ -141,44 +166,56 @@ void pxl8_vfx_water_ripple(pxl8_gfx_ctx* ctx, f32* height_map, i32 drop_x, i32 d prev_height = temp; } -void pxl8_vfx_particles_clear(pxl8_particle_system* sys) { - if (!sys || !sys->particles) return; +pxl8_particles* pxl8_particles_create(u32 max_count) { + pxl8_particles* particles = SDL_calloc(1, sizeof(pxl8_particles)); + if (!particles) return NULL; - for (u32 i = 0; i < sys->max_count; i++) { - sys->particles[i].life = 0; - sys->particles[i].flags = 0; + particles->particles = SDL_calloc(max_count, sizeof(pxl8_particle)); + if (!particles->particles) { + SDL_free(particles); + return NULL; } - sys->alive_count = 0; - sys->spawn_timer = 0; + + particles->max_count = max_count; + particles->drag = 0.98f; + particles->gravity_y = 100.0f; + particles->spawn_rate = 10.0f; + + return particles; } -void pxl8_vfx_particles_free(pxl8_particle_system* sys) { - if (!sys) return; - - if (sys->particles) { - SDL_free(sys->particles); - sys->particles = NULL; - } - sys->count = 0; - sys->max_count = 0; - sys->alive_count = 0; +void pxl8_particles_destroy(pxl8_particles* particles) { + if (!particles) return; + SDL_free(particles->particles); + SDL_free(particles); } -void pxl8_vfx_particles_emit(pxl8_particle_system* sys, u32 count) { - if (!sys || !sys->particles) return; +void pxl8_particles_clear(pxl8_particles* particles) { + if (!particles || !particles->particles) return; + + for (u32 i = 0; i < particles->max_count; i++) { + particles->particles[i].life = 0; + particles->particles[i].flags = 0; + } + particles->alive_count = 0; + particles->spawn_timer = 0; +} + +void pxl8_particles_emit(pxl8_particles* particles, u32 count) { + if (!particles || !particles->particles) return; - for (u32 i = 0; i < count && sys->alive_count < sys->max_count; i++) { - for (u32 j = 0; j < sys->max_count; j++) { - if (sys->particles[j].life <= 0) { - pxl8_particle* p = &sys->particles[j]; + for (u32 i = 0; i < count && particles->alive_count < particles->max_count; i++) { + for (u32 j = 0; j < particles->max_count; j++) { + if (particles->particles[j].life <= 0) { + pxl8_particle* p = &particles->particles[j]; p->life = 1.0f; p->max_life = 1.0f; - p->x = sys->x + (((f32)rand() / RAND_MAX) - 0.5f) * sys->spread_x; - p->y = sys->y + (((f32)rand() / RAND_MAX) - 0.5f) * sys->spread_y; + p->x = particles->x + (((f32)rand() / RAND_MAX) - 0.5f) * particles->spread_x; + p->y = particles->y + (((f32)rand() / RAND_MAX) - 0.5f) * particles->spread_y; p->z = 0; p->vx = p->vy = p->vz = 0; - p->ax = sys->gravity_x; - p->ay = sys->gravity_y; + p->ax = particles->gravity_x; + p->ay = particles->gravity_y; p->az = 0; p->color = p->start_color = p->end_color = 15; p->size = 1.0f; @@ -186,76 +223,65 @@ void pxl8_vfx_particles_emit(pxl8_particle_system* sys, u32 count) { p->spin = 0; p->flags = 1; - if (sys->spawn_fn) { - sys->spawn_fn(p, sys->userdata); + if (particles->spawn_fn) { + particles->spawn_fn(p, particles->userdata); } - sys->alive_count++; + particles->alive_count++; break; } } } } -void pxl8_vfx_particles_init(pxl8_particle_system* sys, u32 max_count) { - if (!sys) return; - - SDL_memset(sys, 0, sizeof(pxl8_particle_system)); - - sys->particles = (pxl8_particle*)SDL_calloc(max_count, sizeof(pxl8_particle)); - if (!sys->particles) return; - - sys->max_count = max_count; - sys->count = 0; - sys->alive_count = 0; - sys->spawn_rate = 10.0f; - sys->spawn_timer = 0; - sys->drag = 0.98f; - sys->gravity_y = 100.0f; -} +void pxl8_particles_render(pxl8_particles* particles, pxl8_gfx* gfx) { + if (!particles || !particles->particles || !gfx) return; -void pxl8_vfx_particles_render(pxl8_particle_system* sys, pxl8_gfx_ctx* ctx) { - if (!sys || !sys->particles || !ctx) return; - - for (u32 i = 0; i < sys->max_count; i++) { - pxl8_particle* p = &sys->particles[i]; + for (u32 i = 0; i < particles->max_count; i++) { + pxl8_particle* p = &particles->particles[i]; if (p->life > 0 && p->flags) { - if (sys->render_fn) { - sys->render_fn(ctx, p, sys->userdata); + if (particles->render_fn) { + particles->render_fn(gfx, p, particles->userdata); } else { i32 x = (i32)p->x; i32 y = (i32)p->y; - if (x >= 0 && x < ctx->framebuffer_width && y >= 0 && y < ctx->framebuffer_height) { - pxl8_pixel(ctx, x, y, p->color); + if (x >= 0 && x < pxl8_gfx_get_width(gfx) && y >= 0 && y < pxl8_gfx_get_height(gfx)) { + pxl8_pixel(gfx, x, y, p->color); } } } } } -void pxl8_vfx_particles_update(pxl8_particle_system* sys, f32 dt) { - if (!sys || !sys->particles) return; - - sys->spawn_timer += dt; - f32 spawn_interval = 1.0f / sys->spawn_rate; - while (sys->spawn_timer >= spawn_interval) { - pxl8_vfx_particles_emit(sys, 1); - sys->spawn_timer -= spawn_interval; +void pxl8_particles_update(pxl8_particles* particles, f32 dt) { + if (!particles || !particles->particles) return; + + if (particles->spawn_rate > 0.0f) { + particles->spawn_timer += dt; + f32 spawn_interval = 1.0f / particles->spawn_rate; + u32 max_spawns_per_frame = particles->max_count / 10; + if (max_spawns_per_frame < 1) max_spawns_per_frame = 1; + u32 spawn_count = 0; + while (particles->spawn_timer >= spawn_interval && spawn_count < max_spawns_per_frame) { + pxl8_particles_emit(particles, 1); + particles->spawn_timer -= spawn_interval; + spawn_count++; + } } - for (u32 i = 0; i < sys->max_count; i++) { - pxl8_particle* p = &sys->particles[i]; + for (u32 i = 0; i < particles->max_count; i++) { + pxl8_particle* p = &particles->particles[i]; if (p->life > 0) { - if (sys->update_fn) { - sys->update_fn(p, dt, sys->userdata); + if (particles->update_fn) { + particles->update_fn(p, dt, particles->userdata); } else { p->vx += p->ax * dt; p->vy += p->ay * dt; p->vz += p->az * dt; - p->vx *= sys->drag; - p->vy *= sys->drag; - p->vz *= sys->drag; + p->vx *= particles->drag; + p->vy *= particles->drag; + p->vz *= particles->drag; p->x += p->vx * dt; p->y += p->vy * dt; @@ -267,25 +293,25 @@ void pxl8_vfx_particles_update(pxl8_particle_system* sys, f32 dt) { p->life -= dt / p->max_life; if (p->life <= 0) { p->flags = 0; - sys->alive_count--; + particles->alive_count--; } } } } -void pxl8_vfx_explosion(pxl8_particle_system* sys, i32 x, i32 y, u32 color, f32 force) { - if (!sys) return; +void pxl8_vfx_explosion(pxl8_particles* particles, i32 x, i32 y, u32 color, f32 force) { + if (!particles) return; - sys->x = x; - sys->y = y; - sys->spread_x = sys->spread_y = 2.0f; - sys->gravity_x = 0; - sys->gravity_y = 200.0f; - sys->drag = 0.95f; - sys->update_fn = NULL; + particles->x = x; + particles->y = y; + particles->spread_x = particles->spread_y = 2.0f; + particles->gravity_x = 0; + particles->gravity_y = 200.0f; + particles->drag = 0.95f; + particles->update_fn = NULL; - for (u32 i = 0; i < 50 && i < sys->max_count; i++) { - pxl8_particle* p = &sys->particles[i]; + for (u32 i = 0; i < 50 && i < particles->max_count; i++) { + pxl8_particle* p = &particles->particles[i]; f32 angle = ((f32)rand() / RAND_MAX) * 6.28f; f32 speed = force * (0.5f + ((f32)rand() / RAND_MAX) * 0.5f); @@ -330,20 +356,20 @@ static void fire_update(pxl8_particle* p, f32 dt, void* userdata) { } } -void pxl8_vfx_fire(pxl8_particle_system* sys, i32 x, i32 y, i32 width, u8 palette_start) { - if (!sys) return; +void pxl8_vfx_fire(pxl8_particles* particles, i32 x, i32 y, i32 width, u8 palette_start) { + if (!particles) return; - sys->x = x; - sys->y = y; - sys->spread_x = width; - sys->spread_y = 4.0f; - sys->gravity_x = 0; - sys->gravity_y = -100.0f; - sys->drag = 0.97f; - sys->spawn_rate = 120.0f; - sys->spawn_fn = fire_spawn; - sys->update_fn = fire_update; - sys->userdata = (void*)(uintptr_t)palette_start; + particles->x = x; + particles->y = y; + particles->spread_x = width; + particles->spread_y = 4.0f; + particles->gravity_x = 0; + particles->gravity_y = -100.0f; + particles->drag = 0.97f; + particles->spawn_rate = 120.0f; + particles->spawn_fn = fire_spawn; + particles->update_fn = fire_update; + particles->userdata = (void*)(uintptr_t)palette_start; } static void rain_spawn(pxl8_particle* p, void* userdata) { @@ -355,19 +381,19 @@ static void rain_spawn(pxl8_particle* p, void* userdata) { p->vy = 200.0f + ((f32)rand() / RAND_MAX) * 100.0f; } -void pxl8_vfx_rain(pxl8_particle_system* sys, i32 width, f32 wind) { - if (!sys) return; +void pxl8_vfx_rain(pxl8_particles* particles, i32 width, f32 wind) { + if (!particles) return; - sys->x = width / 2.0f; - sys->y = -10; - sys->spread_x = width; - sys->spread_y = 0; - sys->gravity_x = wind; - sys->gravity_y = 300.0f; - sys->drag = 1.0f; - sys->spawn_rate = 100.0f; - sys->spawn_fn = rain_spawn; - sys->update_fn = NULL; + particles->x = width / 2.0f; + particles->y = -10; + particles->spread_x = width; + particles->spread_y = 0; + particles->gravity_x = wind; + particles->gravity_y = 300.0f; + particles->drag = 1.0f; + particles->spawn_rate = 100.0f; + particles->spawn_fn = rain_spawn; + particles->update_fn = NULL; } static void smoke_spawn(pxl8_particle* p, void* userdata) { @@ -381,20 +407,20 @@ static void smoke_spawn(pxl8_particle* p, void* userdata) { p->size = 1.0f + ((f32)rand() / RAND_MAX) * 2.0f; } -void pxl8_vfx_smoke(pxl8_particle_system* sys, i32 x, i32 y, u8 color) { - if (!sys) return; +void pxl8_vfx_smoke(pxl8_particles* particles, i32 x, i32 y, u8 color) { + if (!particles) return; - sys->x = x; - sys->y = y; - sys->spread_x = 5.0f; - sys->spread_y = 5.0f; - sys->gravity_x = 0; - sys->gravity_y = -50.0f; - sys->drag = 0.96f; - sys->spawn_rate = 20.0f; - sys->spawn_fn = smoke_spawn; - sys->update_fn = NULL; - sys->userdata = (void*)(uintptr_t)color; + particles->x = x; + particles->y = y; + particles->spread_x = 5.0f; + particles->spread_y = 5.0f; + particles->gravity_x = 0; + particles->gravity_y = -50.0f; + particles->drag = 0.96f; + particles->spawn_rate = 20.0f; + particles->spawn_fn = smoke_spawn; + particles->update_fn = NULL; + particles->userdata = (void*)(uintptr_t)color; } static void snow_spawn(pxl8_particle* p, void* userdata) { @@ -407,19 +433,19 @@ static void snow_spawn(pxl8_particle* p, void* userdata) { p->vy = 30.0f + ((f32)rand() / RAND_MAX) * 20.0f; } -void pxl8_vfx_snow(pxl8_particle_system* sys, i32 width, f32 wind) { - if (!sys) return; +void pxl8_vfx_snow(pxl8_particles* particles, i32 width, f32 wind) { + if (!particles) return; - sys->x = width / 2.0f; - sys->y = -10; - sys->spread_x = width; - sys->spread_y = 0; - sys->gravity_x = wind; - sys->gravity_y = 30.0f; - sys->drag = 1.0f; - sys->spawn_rate = 30.0f; - sys->spawn_fn = snow_spawn; - sys->update_fn = NULL; + particles->x = width / 2.0f; + particles->y = -10; + particles->spread_x = width; + particles->spread_y = 0; + particles->gravity_x = wind; + particles->gravity_y = 30.0f; + particles->drag = 1.0f; + particles->spawn_rate = 30.0f; + particles->spawn_fn = snow_spawn; + particles->update_fn = NULL; } static void sparks_spawn(pxl8_particle* p, void* userdata) { @@ -435,33 +461,33 @@ static void sparks_spawn(pxl8_particle* p, void* userdata) { p->vy = sinf(angle) * speed - 50.0f; } -void pxl8_vfx_sparks(pxl8_particle_system* sys, i32 x, i32 y, u32 color) { - if (!sys) return; +void pxl8_vfx_sparks(pxl8_particles* particles, i32 x, i32 y, u32 color) { + if (!particles) return; - sys->x = x; - sys->y = y; - sys->spread_x = 2.0f; - sys->spread_y = 2.0f; - sys->gravity_x = 0; - sys->gravity_y = 100.0f; - sys->drag = 0.97f; - sys->spawn_rate = 40.0f; - sys->spawn_fn = sparks_spawn; - sys->update_fn = NULL; - sys->userdata = (void*)(uintptr_t)color; + particles->x = x; + particles->y = y; + particles->spread_x = 2.0f; + particles->spread_y = 2.0f; + particles->gravity_x = 0; + particles->gravity_y = 100.0f; + particles->drag = 0.97f; + particles->spawn_rate = 40.0f; + particles->spawn_fn = sparks_spawn; + particles->update_fn = NULL; + particles->userdata = (void*)(uintptr_t)color; } -void pxl8_vfx_starfield(pxl8_particle_system* sys, f32 speed, f32 spread) { - if (!sys) return; +void pxl8_vfx_starfield(pxl8_particles* particles, f32 speed, f32 spread) { + if (!particles) return; - sys->spread_x = sys->spread_y = spread; - sys->gravity_x = sys->gravity_y = 0; - sys->drag = 1.0f; - sys->spawn_rate = 0; - sys->update_fn = NULL; + particles->spread_x = particles->spread_y = spread; + particles->gravity_x = particles->gravity_y = 0; + particles->drag = 1.0f; + particles->spawn_rate = 0; + particles->update_fn = NULL; - for (u32 i = 0; i < sys->max_count; i++) { - pxl8_particle* p = &sys->particles[i]; + for (u32 i = 0; i < particles->max_count; i++) { + pxl8_particle* p = &particles->particles[i]; p->x = ((f32)rand() / RAND_MAX) * spread * 2.0f - spread; p->y = ((f32)rand() / RAND_MAX) * spread * 2.0f - spread; p->z = ((f32)rand() / RAND_MAX) * spread; diff --git a/src/pxl8_vfx.h b/src/pxl8_vfx.h index 79c0061..856cb1b 100644 --- a/src/pxl8_vfx.h +++ b/src/pxl8_vfx.h @@ -3,66 +3,51 @@ #include "pxl8_gfx.h" #include "pxl8_types.h" +typedef struct pxl8_particles pxl8_particles; + typedef struct pxl8_particle { - f32 x, y, z; - f32 vx, vy, vz; + f32 angle; f32 ax, ay, az; + u32 color; + u32 end_color; + u8 flags; f32 life; f32 max_life; - u32 color; - u32 start_color; - u32 end_color; f32 size; - f32 angle; f32 spin; - u8 flags; + u32 start_color; + f32 vx, vy, vz; + f32 x, y, z; } pxl8_particle; -typedef struct pxl8_particle_system { - pxl8_particle* particles; - u32 count; - u32 max_count; - u32 alive_count; - f32 spawn_rate; - f32 spawn_timer; - f32 x, y; - f32 spread_x, spread_y; - f32 gravity_x, gravity_y; - f32 drag; - f32 turbulence; - void (*spawn_fn)(pxl8_particle* p, void* userdata); - void (*update_fn)(pxl8_particle* p, f32 dt, void* userdata); - void (*render_fn)(pxl8_gfx_ctx* ctx, pxl8_particle* p, void* userdata); - void* userdata; -} pxl8_particle_system; - typedef struct pxl8_raster_bar { - f32 base_y; f32 amplitude; - i32 height; - f32 speed; - f32 phase; + f32 base_y; u32 color; u32 fade_color; + i32 height; + f32 phase; + f32 speed; } pxl8_raster_bar; -void pxl8_vfx_particles_clear(pxl8_particle_system* sys); -void pxl8_vfx_particles_emit(pxl8_particle_system* sys, u32 count); -void pxl8_vfx_particles_free(pxl8_particle_system* sys); -void pxl8_vfx_particles_init(pxl8_particle_system* sys, u32 max_count); -void pxl8_vfx_particles_render(pxl8_particle_system* sys, pxl8_gfx_ctx* ctx); -void pxl8_vfx_particles_update(pxl8_particle_system* sys, f32 dt); +pxl8_particles* pxl8_particles_create(u32 max_count); +void pxl8_particles_destroy(pxl8_particles* particles); -void pxl8_vfx_explosion(pxl8_particle_system* sys, i32 x, i32 y, u32 color, f32 force); -void pxl8_vfx_fire(pxl8_particle_system* sys, i32 x, i32 y, i32 width, u8 palette_start); -void pxl8_vfx_rain(pxl8_particle_system* sys, i32 width, f32 wind); -void pxl8_vfx_smoke(pxl8_particle_system* sys, i32 x, i32 y, u8 color); -void pxl8_vfx_snow(pxl8_particle_system* sys, i32 width, f32 wind); -void pxl8_vfx_sparks(pxl8_particle_system* sys, i32 x, i32 y, u32 color); -void pxl8_vfx_starfield(pxl8_particle_system* sys, f32 speed, f32 spread); +void pxl8_particles_clear(pxl8_particles* particles); +void pxl8_particles_emit(pxl8_particles* particles, u32 count); +void pxl8_particles_render(pxl8_particles* particles, pxl8_gfx* gfx); +void pxl8_particles_update(pxl8_particles* particles, f32 dt); -void pxl8_vfx_plasma(pxl8_gfx_ctx* ctx, f32 time, f32 scale1, f32 scale2, u8 palette_offset); -void pxl8_vfx_raster_bars(pxl8_gfx_ctx* ctx, pxl8_raster_bar* bars, u32 bar_count, f32 time); -void pxl8_vfx_rotozoom(pxl8_gfx_ctx* ctx, f32 angle, f32 zoom, i32 cx, i32 cy); -void pxl8_vfx_tunnel(pxl8_gfx_ctx* ctx, f32 time, f32 speed, f32 twist); -void pxl8_vfx_water_ripple(pxl8_gfx_ctx* ctx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping); +void pxl8_vfx_explosion(pxl8_particles* particles, i32 x, i32 y, u32 color, f32 force); +void pxl8_vfx_fire(pxl8_particles* particles, i32 x, i32 y, i32 width, u8 palette_start); +void pxl8_vfx_rain(pxl8_particles* particles, i32 width, f32 wind); +void pxl8_vfx_smoke(pxl8_particles* particles, i32 x, i32 y, u8 color); +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);