refactor some things...

This commit is contained in:
asrael 2025-10-04 04:13:48 -05:00
parent 3550fad638
commit 1744e689b5
25 changed files with 2396 additions and 1307 deletions

98
demo/cube3d.fnl Normal file
View file

@ -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}

View file

@ -1,4 +1,5 @@
(local pxl8 (require :pxl8)) (local pxl8 (require :pxl8))
(local cube3d (fennel.dofile "cube3d.fnl"))
(var time 0) (var time 0)
(var current-effect 1) (var current-effect 1)
@ -15,8 +16,8 @@
(global init (fn [] (global init (fn []
(pxl8.load_palette "palettes/gruvbox.ase") (pxl8.load_palette "palettes/gruvbox.ase")
(set logo-sprite (pxl8.load_sprite "sprites/pxl8_logo.ase")))) (set logo-sprite (pxl8.load_sprite "sprites/pxl8_logo.ase"))
(set particles (pxl8.particles_new 1000)) (set particles (pxl8.particles_new 1000))))
(global update (fn [dt] (global update (fn [dt]
(set time (+ time dt)) (set time (+ time dt))
@ -38,20 +39,24 @@
(when (pxl8.key_pressed "7") (when (pxl8.key_pressed "7")
(set current-effect 7) (set current-effect 7)
(set snow-init false)) (set snow-init false))
(when (pxl8.key_pressed "8")
(set current-effect 8))
(when (= current-effect 1) (case current-effect
1 (do
(set logo-x (+ logo-x (* logo-dx dt))) (set logo-x (+ logo-x (* logo-dx dt)))
(set logo-y (+ logo-y (* logo-dy dt))) (set logo-y (+ logo-y (* logo-dy dt)))
(when (or (< logo-x 0) (> logo-x 512)) (when (or (< logo-x 0) (> logo-x 512))
(set logo-dx (- logo-dx))) (set logo-dx (- logo-dx)))
(when (or (< logo-y 0) (> logo-y 296)) (when (or (< logo-y 0) (> logo-y 296))
(set logo-dy (- logo-dy)))) (set logo-dy (- logo-dy))))
8 (cube3d.update dt)
_ nil)
(when particles (when particles
(pxl8.particles_update particles dt)))) (pxl8.particles_update particles dt))))
(global draw (fn [] (global frame (fn []
(case current-effect (case current-effect
1 (do 1 (do
(pxl8.clr 0) (pxl8.clr 0)
@ -64,9 +69,9 @@
4 (do 4 (do
(pxl8.clr 0) (pxl8.clr 0)
(local bars [{:base_y 40 :amplitude 20 :height 16 :speed 2.0 :phase 0 :color 14 :fade_color 11} (local bars [{:base_y 60 :amplitude 30 :height 16 :speed 2.0 :phase 0 :color 20 :fade_color 10}
{:base_y 80 :amplitude 15 :height 16 :speed 2.5 :phase 1.5 :color 20 :fade_color 11} {:base_y 180 :amplitude 35 :height 16 :speed 1.8 :phase 2.0 :color 26 :fade_color 10}
{:base_y 120 :amplitude 25 :height 16 :speed 1.8 :phase 3.0 :color 26 :fade_color 11}]) {:base_y 300 :amplitude 25 :height 16 :speed 2.2 :phase 4.0 :color 14 :fade_color 10}])
(pxl8.vfx_raster_bars bars time)) (pxl8.vfx_raster_bars bars time))
5 (do 5 (do
@ -96,4 +101,6 @@
(set snow-init true)) (set snow-init true))
(pxl8.particles_render particles))) (pxl8.particles_render particles)))
8 (cube3d.frame)
_ (pxl8.clr 0)))) _ (pxl8.clr 0))))

View file

@ -388,7 +388,8 @@ case "$COMMAND" in
src/pxl8_font.c src/pxl8_font.c
src/pxl8_gfx.c src/pxl8_gfx.c
src/pxl8_io.c src/pxl8_io.c
src/pxl8_lua.c src/pxl8_math.c
src/pxl8_script.c
src/pxl8_tilemap.c src/pxl8_tilemap.c
src/pxl8_tilesheet.c src/pxl8_tilesheet.c
src/pxl8_vfx.c src/pxl8_vfx.c

View file

@ -1,7 +1,7 @@
local ffi = require("ffi") local ffi = require("ffi")
local C = ffi.C local C = ffi.C
local gfx = _pxl8_gfx_ctx local gfx = _pxl8_gfx
local input = _pxl8_input_ctx local input = _pxl8_input
-- pxl8 lua api -- 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) 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 end
function pxl8.get_screen() function pxl8.get_width()
return C.pxl8_get_screen(gfx) return C.pxl8_gfx_get_width(gfx)
end
function pxl8.get_height()
return C.pxl8_gfx_get_height(gfx)
end end
function pxl8.load_palette(filepath) function pxl8.load_palette(filepath)
@ -119,8 +123,7 @@ function pxl8.vfx_plasma(time, scale1, scale2, palette_offset)
end end
function pxl8.vfx_rotozoom(angle, zoom, cx, cy) function pxl8.vfx_rotozoom(angle, zoom, cx, cy)
local screen = pxl8.get_screen() C.pxl8_vfx_rotozoom(gfx, angle, zoom, cx or pxl8.get_width()/2, cy or pxl8.get_height()/2)
C.pxl8_vfx_rotozoom(gfx, angle, zoom, cx or screen.width/2, cy or screen.height/2)
end end
function pxl8.vfx_tunnel(time, speed, twist) function pxl8.vfx_tunnel(time, speed, twist)
@ -128,29 +131,27 @@ function pxl8.vfx_tunnel(time, speed, twist)
end end
function pxl8.particles_new(max_count) function pxl8.particles_new(max_count)
local ps = ffi.new("pxl8_particle_system") return C.pxl8_particles_create(max_count or 1000)
C.pxl8_vfx_particles_init(ps, max_count or 1000)
return ps
end end
function pxl8.particles_destroy(ps) function pxl8.particles_destroy(ps)
C.pxl8_vfx_particles_destroy(ps) C.pxl8_particles_destroy(ps)
end end
function pxl8.particles_clear(ps) function pxl8.particles_clear(ps)
C.pxl8_vfx_particles_clear(ps) C.pxl8_particles_clear(ps)
end end
function pxl8.particles_emit(ps, count) function pxl8.particles_emit(ps, count)
C.pxl8_vfx_particles_emit(ps, count or 1) C.pxl8_particles_emit(ps, count or 1)
end end
function pxl8.particles_update(ps, dt) function pxl8.particles_update(ps, dt)
C.pxl8_vfx_particles_update(ps, dt) C.pxl8_particles_update(ps, dt)
end end
function pxl8.particles_render(ps) function pxl8.particles_render(ps)
C.pxl8_vfx_particles_render(ps, gfx) C.pxl8_particles_render(ps, gfx)
end end
function pxl8.vfx_explosion(ps, x, y, color, force) function pxl8.vfx_explosion(ps, x, y, color, force)
@ -162,7 +163,7 @@ function pxl8.vfx_fire(ps, x, y, width, palette_start)
end end
function pxl8.vfx_rain(ps, width, wind) 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 end
function pxl8.vfx_smoke(ps, x, y, color) function pxl8.vfx_smoke(ps, x, y, color)
@ -170,7 +171,7 @@ function pxl8.vfx_smoke(ps, x, y, color)
end end
function pxl8.vfx_snow(ps, width, wind) 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 end
function pxl8.vfx_sparks(ps, x, y, color) function pxl8.vfx_sparks(ps, x, y, color)
@ -190,7 +191,7 @@ function pxl8.gfx_fade_palette(start, count, amount, target_color)
end end
function pxl8.tilesheet_new(tile_size) 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 end
function pxl8.tilesheet_destroy(tilesheet) function pxl8.tilesheet_destroy(tilesheet)
@ -202,7 +203,7 @@ function pxl8.tilesheet_load(tilesheet, filepath)
end end
function pxl8.tilemap_new(width, height, tile_size) 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 end
function pxl8.tilemap_destroy(tilemap) function pxl8.tilemap_destroy(tilemap)
@ -246,6 +247,86 @@ pxl8.TILE_FLIP_Y = 2
pxl8.TILE_SOLID = 4 pxl8.TILE_SOLID = 4
pxl8.TILE_TRIGGER = 8 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.gfx = gfx
pxl8.input = input pxl8.input = input

View file

@ -9,13 +9,14 @@
#include <unistd.h> #include <unistd.h>
#include <linenoise.h> #include <linenoise.h>
#define SDL_MAIN_USE_CALLBACKS #define SDL_MAIN_USE_CALLBACKS
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <SDL3/SDL_main.h> #include <SDL3/SDL_main.h>
#include "pxl8_cart.h" #include "pxl8_cart.h"
#include "pxl8_lua.h"
#include "pxl8_macros.h" #include "pxl8_macros.h"
#include "pxl8_script.h"
#include "pxl8_types.h" #include "pxl8_types.h"
#define PXL8_MAX_REPL_COMMANDS 4096 #define PXL8_MAX_REPL_COMMANDS 4096
@ -36,10 +37,10 @@ typedef struct pxl8_repl_state {
typedef struct pxl8_state { typedef struct pxl8_state {
pxl8_cart* cart; pxl8_cart* cart;
pxl8_color_mode color_mode; pxl8_color_mode color_mode;
pxl8_gfx_ctx gfx; pxl8_gfx* gfx;
lua_State* lua;
pxl8_repl_state repl; pxl8_repl_state repl;
pxl8_resolution resolution; pxl8_resolution resolution;
pxl8_script* script;
f32 fps_timer; f32 fps_timer;
i32 frame_count; i32 frame_count;
@ -50,7 +51,6 @@ typedef struct pxl8_state {
bool running; bool running;
bool script_loaded; bool script_loaded;
char script_path[256]; char script_path[256];
time_t script_mod_time;
pxl8_input_state input; pxl8_input_state input;
} pxl8_state; } pxl8_state;
@ -184,54 +184,6 @@ static pxl8_repl_command* pxl8_repl_pop_command(pxl8_repl_state* repl) {
return cmd; 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[]) { SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
static pxl8_state app = {0}; 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_input = NULL;
const char* pack_output = 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) { if (strcmp(argv[i], "--repl") == 0) {
app.repl_mode = true; app.repl_mode = true;
} else if (strcmp(argv[i], "--pack") == 0) { } else if (strcmp(argv[i], "--pack") == 0) {
@ -278,25 +230,26 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
pxl8_info("Starting up"); pxl8_info("Starting up");
if (pxl8_gfx_init(&app.gfx, app.color_mode, app.resolution, "pxl8", 1280, 720) != PXL8_OK) { app.gfx = pxl8_gfx_create(app.color_mode, app.resolution, "pxl8", 1280, 720);
pxl8_error("Failed to initialize graphics context"); if (!app.gfx) {
pxl8_error("Failed to create graphics context");
return SDL_APP_FAILURE; 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"); pxl8_error("Failed to load font atlas");
return SDL_APP_FAILURE; 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"); pxl8_error("Failed to initialize sprite atlas");
return SDL_APP_FAILURE; return SDL_APP_FAILURE;
} }
pxl8_result lua_result = pxl8_lua_init(&app.lua); app.script = pxl8_script_create();
if (lua_result != PXL8_OK) { if (!app.script) {
pxl8_error("Failed to initialize Lua scripting!"); pxl8_error("Failed to initialize scripting: %s", pxl8_script_get_last_error(app.script));
pxl8_gfx_shutdown(&app.gfx); pxl8_gfx_destroy(app.gfx);
return SDL_APP_FAILURE; return SDL_APP_FAILURE;
} }
@ -308,16 +261,16 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
if (is_cart) { if (is_cart) {
char* original_cwd = getcwd(NULL, 0); char* original_cwd = getcwd(NULL, 0);
app.cart = calloc(1, sizeof(pxl8_cart)); app.cart = pxl8_cart_create();
if (!app.cart) { if (!app.cart) {
pxl8_error("Failed to allocate memory for cart"); pxl8_error("Failed to create cart");
return false; return false;
} }
if (pxl8_cart_load(app.cart, cart_path) == PXL8_OK) { 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); pxl8_cart_mount(app.cart);
strcpy(app.script_path, "main.fnl"); 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 { } else {
pxl8_error("Failed to load cart: %s", cart_path); pxl8_error("Failed to load cart: %s", cart_path);
return SDL_APP_FAILURE; 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'; 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') { if (app.script_path[0] != '\0') {
app.script_mod_time = get_file_mod_time(app.script_path); pxl8_result result = pxl8_script_load_main(app.script, app.script_path);
load_script(&app); app.script_loaded = (result == PXL8_OK);
} }
if (app.repl_mode) { if (app.repl_mode) {
pxl8_repl_init(&app.repl); 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"); 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"); if (pxl8_script_load_module(app.script, "pxl8") != PXL8_OK) {
lua_pushstring(app.lua, "pxl8"); fprintf(stderr, "Warning: Failed to setup pxl8 global: %s\n", pxl8_script_get_last_error(app.script));
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);
} }
} }
@ -359,12 +307,10 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
SDL_AppResult SDL_AppIterate(void* appstate) { SDL_AppResult SDL_AppIterate(void* appstate) {
pxl8_state* app = (pxl8_state*)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(); 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->frame_count++;
app->fps_timer += dt; app->fps_timer += dt;
@ -373,79 +319,43 @@ SDL_AppResult SDL_AppIterate(void* appstate) {
if (app->fps_timer >= 3.0f) { if (app->fps_timer >= 3.0f) {
if (!app->repl_mode) { 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); pxl8_info("FPS: %.1f", avg_fps);
} }
app->frame_count = 0; app->frame_count = 0;
app->fps_timer = 0.0f; app->fps_timer = 0.0f;
} }
time_t current_mod_time = get_file_mod_time(app->script_path); pxl8_script_check_reload(app->script);
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);
}
if (app->repl_mode) { if (app->repl_mode) {
pxl8_repl_command* cmd = pxl8_repl_pop_command(&app->repl); pxl8_repl_command* cmd = pxl8_repl_pop_command(&app->repl);
if (cmd) { if (cmd) {
pxl8_result result = pxl8_lua_eval_fennel(app->lua, cmd->buffer); pxl8_result result = pxl8_script_eval(app->script, cmd->buffer);
if (result == PXL8_OK) { if (result != PXL8_OK) {
if (lua_gettop(app->lua) > 0 && !lua_isnil(app->lua, -1)) { pxl8_error("%s", pxl8_script_get_last_error(app->script));
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, "<userdata>\n");
} else if (lua_iscfunction(app->lua, -1)) {
fprintf(stdout, "<function>\n");
} else if (lua_istable(app->lua, -1)) {
fprintf(stdout, "<table>\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);
} }
SDL_free(cmd); SDL_free(cmd);
} }
} }
if (app->script_loaded) { if (app->script_loaded) {
lua_getglobal(app->lua, "update"); pxl8_script_call_function_f32(app->script, "update", dt);
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"); pxl8_result frame_result = pxl8_script_call_function(app->script, "frame");
if (lua_isfunction(app->lua, -1)) { if (frame_result == PXL8_ERROR_SCRIPT_ERROR) {
int result = lua_pcall(app->lua, 0, 0, 0); pxl8_error("Error calling frame: %s", pxl8_script_get_last_error(app->script));
if (result != 0) {
pxl8_error("Error calling draw: %s", lua_tostring(app->lua, -1));
lua_pop(app->lua, 1);
} }
} else { } else {
pxl8_warn("draw is not a function, type: %s", lua_typename(app->lua, lua_type(app->lua, -1))); pxl8_clr(app->gfx, 32);
lua_pop(app->lua, 1);
}
} else {
pxl8_clr(&app->gfx, 32);
i32 render_width, render_height; i32 render_width, render_height;
pxl8_gfx_get_resolution_dimensions(app->resolution, &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 (i32 y = 0; y < render_height; y += 24) {
for (int x = 0; x < render_width; x += 32) { for (i32 x = 0; x < render_width; x += 32) {
u32 color = ((x / 32) + (y / 24) + (int)(app->time * 2)) % 8; u32 color = ((x / 32) + (y / 24) + (i32)(app->time * 2)) % 8;
pxl8_rect_fill(&app->gfx, x, y, 31, 23, color); pxl8_rect_fill(app->gfx, x, y, 31, 23, color);
} }
} }
} }
@ -453,16 +363,16 @@ SDL_AppResult SDL_AppIterate(void* appstate) {
i32 render_width, render_height; i32 render_width, render_height;
pxl8_gfx_get_resolution_dimensions(app->resolution, &render_width, &render_height); pxl8_gfx_get_resolution_dimensions(app->resolution, &render_width, &render_height);
float scale = fminf(width / (float)render_width, height / (float)render_height); f32 scale = fminf(bounds.w / (f32)render_width, bounds.h / (f32)render_height);
int scaled_width = (int)(render_width * scale); i32 scaled_width = (i32)(render_width * scale);
int scaled_height = (int)(render_height * scale); i32 scaled_height = (i32)(render_height * scale);
int offset_x = (width - scaled_width) / 2; i32 offset_x = (bounds.w - scaled_width) / 2;
int offset_y = (height - scaled_height) / 2; i32 offset_y = (bounds.h - scaled_height) / 2;
pxl8_gfx_viewport(&app->gfx, offset_x, offset_y, scaled_width, scaled_height); pxl8_gfx_viewport(app->gfx, offset_x, offset_y, scaled_width, scaled_height);
pxl8_gfx_upload_framebuffer(&app->gfx); pxl8_gfx_upload_framebuffer(app->gfx);
pxl8_gfx_upload_atlas(&app->gfx); pxl8_gfx_upload_atlas(app->gfx);
pxl8_gfx_present(&app->gfx); pxl8_gfx_present(app->gfx);
SDL_memset(app->input.keys_pressed, 0, sizeof(app->input.keys_pressed)); SDL_memset(app->input.keys_pressed, 0, sizeof(app->input.keys_pressed));
@ -520,8 +430,8 @@ void SDL_AppQuit(void* appstate, SDL_AppResult result) {
free(app->cart); free(app->cart);
app->cart = NULL; app->cart = NULL;
} }
pxl8_lua_shutdown(app->lua); pxl8_script_destroy(app->script);
pxl8_gfx_shutdown(&app->gfx); pxl8_gfx_destroy(app->gfx);
} }
SDL_Quit(); SDL_Quit();

View file

@ -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; 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) { if (result != MZ_OK) {
pxl8_error("Failed to decompress cel data: miniz error %d", result); pxl8_error("Failed to decompress cel data: miniz error %d", result);
SDL_free(cel->pixel_data); 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); pxl8_io_free_binary_data(file_data);
if (result != PXL8_OK) { if (result != PXL8_OK) {
pxl8_ase_free(ase_file); pxl8_ase_destroy(ase_file);
} }
return result; 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) return;
if (ase_file->frames) { if (ase_file->frames) {

View file

@ -91,7 +91,7 @@ extern "C" {
#endif #endif
pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file); 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 #ifdef __cplusplus
} }

View file

@ -10,6 +10,14 @@
#include "pxl8_cart.h" #include "pxl8_cart.h"
#include "pxl8_macros.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 pxl8_cart* __pxl8_current_cart = NULL;
static char* __pxl8_original_cwd = 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; 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) { pxl8_cart* pxl8_cart_current(void) {
return __pxl8_current_cart; 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) { pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) {
if (!cart || !path) return PXL8_ERROR_NULL_POINTER; 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; return PXL8_ERROR_INVALID_FORMAT;
} }
int num_files = mz_zip_reader_get_num_files(&zip); i32 num_files = mz_zip_reader_get_num_files(&zip);
for (int i = 0; i < num_files; i++) { for (i32 i = 0; i < num_files; i++) {
mz_zip_archive_file_stat file_stat; mz_zip_archive_file_stat file_stat;
if (!mz_zip_reader_file_stat(&zip, i, &file_stat)) continue; if (!mz_zip_reader_file_stat(&zip, i, &file_stat)) continue;

View file

@ -2,21 +2,18 @@
#include "pxl8_types.h" #include "pxl8_types.h"
typedef struct pxl8_cart { typedef struct pxl8_cart pxl8_cart;
void* archive_data;
size_t archive_size;
char* base_path;
bool is_folder;
bool is_mounted;
char* name;
} pxl8_cart;
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
pxl8_cart* pxl8_cart_create(void);
pxl8_cart* pxl8_cart_current(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); 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_load(pxl8_cart* cart, const char* path);
pxl8_result pxl8_cart_mount(pxl8_cart* cart); pxl8_result pxl8_cart_mount(pxl8_cart* cart);
pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path); pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path);

File diff suppressed because it is too large Load diff

View file

@ -1,143 +1,105 @@
#pragma once #pragma once
#include <string.h> #include "pxl8_math.h"
#include <SDL3/SDL.h>
#include "pxl8_blit.h"
#include "pxl8_types.h" #include "pxl8_types.h"
typedef struct pxl8_atlas_entry { typedef struct pxl8_gfx pxl8_gfx;
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 enum pxl8_blend_mode { typedef enum pxl8_blend_mode {
PXL8_BLEND_NONE,
PXL8_BLEND_ALPHA,
PXL8_BLEND_ADD, PXL8_BLEND_ADD,
PXL8_BLEND_MULTIPLY PXL8_BLEND_ALPHA,
PXL8_BLEND_MULTIPLY,
PXL8_BLEND_NONE
} pxl8_blend_mode; } pxl8_blend_mode;
typedef struct pxl8_mode7_params { typedef struct pxl8_mode7_params {
f32 horizon;
f32 scale_x, scale_y;
f32 rotation;
f32 offset_x, offset_y;
bool active; bool active;
f32 horizon;
f32 offset_x, offset_y;
f32 rotation;
f32 scale_x, scale_y;
} pxl8_mode7_params; } pxl8_mode7_params;
typedef struct pxl8_palette_cycle { typedef struct pxl8_palette_cycle {
u8 start_index; bool active;
u8 end_index; u8 end_index;
f32 speed; f32 speed;
u8 start_index;
f32 timer; f32 timer;
bool active;
} pxl8_palette_cycle; } pxl8_palette_cycle;
typedef struct pxl8_scanline_effect { typedef struct pxl8_scanline_effect {
void (*process)(pxl8_gfx_ctx* ctx, i32 line, f32 time);
bool active; bool active;
void (*process)(pxl8_gfx* gfx, i32 line, f32 time);
} pxl8_scanline_effect; } pxl8_scanline_effect;
typedef struct pxl8_vertex {
u32 color;
pxl8_vec3 normal;
pxl8_vec3 position;
f32 u, v;
} pxl8_vertex;
typedef struct pxl8_effects { typedef struct pxl8_effects {
pxl8_palette_cycle palette_cycles[8]; pxl8_palette_cycle palette_cycles[8];
pxl8_scanline_effect scanline_effects[4]; pxl8_scanline_effect scanline_effects[4];
f32 time; f32 time;
} pxl8_effects; } pxl8_effects;
typedef struct pxl8_triangle {
pxl8_vertex v[3];
} pxl8_triangle;
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #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); void pxl8_gfx_get_resolution_dimensions(pxl8_resolution resolution, i32* width, i32* height);
pxl8_result pxl8_gfx_init( i32 pxl8_gfx_get_width(const pxl8_gfx* gfx);
pxl8_gfx_ctx* ctx, pxl8_result pxl8_gfx_init_atlas(pxl8_gfx* gfx, u32 width, u32 height);
pxl8_color_mode mode, pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx);
pxl8_resolution resolution, pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path);
const char* title, pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path);
i32 window_width, void pxl8_gfx_present(pxl8_gfx* gfx);
i32 window_height void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom);
); void pxl8_gfx_upload_atlas(pxl8_gfx* gfx);
pxl8_result pxl8_gfx_init_atlas(pxl8_gfx_ctx* ctx, u32 width, u32 height); void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx);
pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx_ctx* ctx); void pxl8_gfx_viewport(pxl8_gfx* gfx, i32 x, i32 y, i32 width, i32 height);
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);
void pxl8_gfx_color_ramp( void pxl8_gfx_color_ramp(pxl8_gfx* gfx, u8 start, u8 count, u32 from_color, u32 to_color);
pxl8_gfx_ctx* ctx, void pxl8_gfx_cycle_palette(pxl8_gfx* gfx, u8 start, u8 count, i32 step);
u8 start, void pxl8_gfx_fade_palette(pxl8_gfx* gfx, u8 start, u8 count, f32 amount, u32 target_color);
u8 count, void pxl8_gfx_interpolate_palettes(pxl8_gfx* gfx, u32* palette1, u32* palette2, u8 start, u8 count, f32 t);
u32 from_color, void pxl8_gfx_process_effects(pxl8_gfx* gfx, pxl8_effects* effects, f32 dt);
u32 to_color void pxl8_gfx_swap_palette(pxl8_gfx* gfx, u8 start, u8 count, u32* new_colors);
);
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_circle(pxl8_gfx_ctx* ctx, i32 cx, i32 cy, i32 radius, u32 color); void pxl8_circle(pxl8_gfx* gfx, 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_circle_fill(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color);
void pxl8_clr(pxl8_gfx_ctx* ctx, u32 color); void pxl8_clr(pxl8_gfx* gfx, u32 color);
u32 pxl8_get_pixel(pxl8_gfx_ctx* ctx, i32 x, i32 y); u32 pxl8_get_pixel(pxl8_gfx* gfx, i32 x, i32 y);
void pxl8_line(pxl8_gfx_ctx* ctx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color); void pxl8_line(pxl8_gfx* gfx, 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_pixel(pxl8_gfx* gfx, 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(pxl8_gfx* gfx, 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_rect_fill(pxl8_gfx* gfx, 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_sprite(pxl8_gfx* gfx, 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_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 #ifdef __cplusplus
} }

View file

@ -1,330 +0,0 @@
#include <unistd.h>
#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;
}

View file

@ -1,26 +0,0 @@
#pragma once
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#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

235
src/pxl8_math.c Normal file
View file

@ -0,0 +1,235 @@
#include <math.h>
#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;
}

54
src/pxl8_math.h Normal file
View file

@ -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

543
src/pxl8_script.c Normal file
View file

@ -0,0 +1,543 @@
#include <dirent.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
#include <SDL3/SDL.h>
#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;
}

32
src/pxl8_script.h Normal file
View file

@ -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

View file

@ -187,3 +187,130 @@ static inline pxl8_simd_vec pxl8_simd_blendv_u32(pxl8_simd_vec src, pxl8_simd_ve
#endif #endif
return result; 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
}

View file

@ -5,6 +5,27 @@
#include "pxl8_tilemap.h" #include "pxl8_tilemap.h"
#include "pxl8_tilesheet.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) { static inline u32 pxl8_chunk_index(u32 x, u32 y, u32 chunks_wide) {
return (y >> 4) * chunks_wide + (x >> 4); 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]; return layer->chunks[idx];
} }
pxl8_result pxl8_tilemap_init(pxl8_tilemap* tilemap, u32 width, u32 height, u32 tile_size) { pxl8_tilemap* pxl8_tilemap_create(u32 width, u32 height, u32 tile_size) {
if (!tilemap) return PXL8_ERROR_NULL_POINTER;
if (width > PXL8_MAX_TILEMAP_WIDTH || height > PXL8_MAX_TILEMAP_HEIGHT) { 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->width = width;
tilemap->height = height; tilemap->height = height;
tilemap->tile_size = tile_size ? tile_size : PXL8_TILE_SIZE; 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++) { for (u32 j = 0; j < i; j++) {
free(tilemap->layers[j].chunks); 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; if (!tilemap) return;
for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) { for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) {
@ -76,14 +99,14 @@ void pxl8_tilemap_free(pxl8_tilemap* tilemap) {
} }
} }
free(layer->chunks); free(layer->chunks);
layer->chunks = NULL;
} }
} }
if (tilemap->tilesheet) { if (tilemap->tilesheet) {
pxl8_tilesheet_unref(tilemap->tilesheet); pxl8_tilesheet_unref(tilemap->tilesheet);
tilemap->tilesheet = NULL;
} }
free(tilemap);
} }
pxl8_result pxl8_tilemap_set_tilesheet(pxl8_tilemap* tilemap, pxl8_tilesheet* tilesheet) { 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; tilemap->tilesheet = tilesheet;
pxl8_tilesheet_ref(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)", pxl8_warn("Tilesheet tile size (%d) differs from tilemap tile size (%d)",
tilesheet->tile_size, tilemap->tile_size); tilesheet_size, tilemap->tile_size);
tilemap->tile_size = tilesheet->tile_size; tilemap->tile_size = tilesheet_size;
} }
return PXL8_OK; return PXL8_OK;
@ -155,13 +179,13 @@ void pxl8_tilemap_set_camera(pxl8_tilemap* tilemap, i32 x, i32 y) {
tilemap->camera_y = 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; if (!tilemap || !gfx || !view) return;
view->x = -tilemap->camera_x; view->x = -tilemap->camera_x;
view->y = -tilemap->camera_y; view->y = -tilemap->camera_y;
view->width = gfx->framebuffer_width; view->width = pxl8_gfx_get_width(gfx);
view->height = gfx->framebuffer_height; view->height = pxl8_gfx_get_height(gfx);
view->tile_start_x = pxl8_max(0, tilemap->camera_x / (i32)tilemap->tile_size); 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); 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); (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; if (!tilemap || !gfx || !tilemap->tilesheet) return;
pxl8_tilesheet_render_tile(tilemap->tilesheet, gfx, tile_id, x, y, flags); 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 || !gfx || layer >= tilemap->active_layers) return;
if (!tilemap->tilesheet) { if (!tilemap->tilesheet) {
pxl8_warn("No tilesheet set for tilemap"); 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_left = tilemap->camera_x / tilemap->tile_size;
i32 view_top = tilemap->camera_y / 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_right = (tilemap->camera_x + pxl8_gfx_get_width(gfx)) / tilemap->tile_size + 1;
i32 view_bottom = (tilemap->camera_y + gfx->framebuffer_height) / 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_left = pxl8_max(0, view_left >> 4);
u32 chunk_top = pxl8_max(0, view_top >> 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; if (!tilemap || !gfx) return;
for (u32 layer = 0; layer < tilemap->active_layers; layer++) { for (u32 layer = 0; layer < tilemap->active_layers; layer++) {

View file

@ -46,20 +46,20 @@ static inline u8 pxl8_tile_get_palette(pxl8_tile tile) {
} }
typedef struct pxl8_tile_animation { typedef struct pxl8_tile_animation {
u16* frames;
u16 frame_count;
u16 current_frame; u16 current_frame;
f32 frame_duration; f32 frame_duration;
u16 frame_count;
u16* frames;
f32 time_accumulator; f32 time_accumulator;
} pxl8_tile_animation; } pxl8_tile_animation;
typedef struct pxl8_tile_properties { typedef struct pxl8_tile_properties {
void* user_data;
u32 property_flags;
i16 collision_offset_x; i16 collision_offset_x;
i16 collision_offset_y; i16 collision_offset_y;
u16 collision_width;
u16 collision_height; u16 collision_height;
u16 collision_width;
u32 property_flags;
void* user_data;
} pxl8_tile_properties; } pxl8_tile_properties;
typedef struct pxl8_autotile_rule { typedef struct pxl8_autotile_rule {
@ -68,64 +68,46 @@ typedef struct pxl8_autotile_rule {
} pxl8_autotile_rule; } pxl8_autotile_rule;
typedef struct pxl8_tile_chunk { typedef struct pxl8_tile_chunk {
pxl8_tile tiles[PXL8_CHUNK_SIZE * PXL8_CHUNK_SIZE];
u32 chunk_x; u32 chunk_x;
u32 chunk_y; u32 chunk_y;
bool empty; bool empty;
pxl8_tile tiles[PXL8_CHUNK_SIZE * PXL8_CHUNK_SIZE];
} pxl8_tile_chunk; } pxl8_tile_chunk;
typedef struct pxl8_tilemap_layer { typedef struct pxl8_tilemap_layer pxl8_tilemap_layer;
pxl8_tile_chunk** chunks; typedef struct pxl8_tilemap pxl8_tilemap;
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_view { typedef struct pxl8_tilemap_view {
i32 x, y; i32 height;
i32 width, height;
i32 tile_start_x, tile_start_y;
i32 tile_end_x, tile_end_y; i32 tile_end_x, tile_end_y;
i32 tile_start_x, tile_start_y;
i32 width;
i32 x, y;
} pxl8_tilemap_view; } pxl8_tilemap_view;
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #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); 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_compress(pxl8_tilemap* tilemap);
void pxl8_tilemap_free(pxl8_tilemap* tilemap);
u32 pxl8_tilemap_get_memory_usage(const 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); 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); u16 pxl8_tilemap_get_tile_id(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y);
void pxl8_tilemap_get_view( void pxl8_tilemap_get_view(
const pxl8_tilemap* tilemap, const pxl8_tilemap* tilemap,
const pxl8_gfx_ctx* gfx, const pxl8_gfx* gfx,
pxl8_tilemap_view* view 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); 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(const pxl8_tilemap* tilemap, pxl8_gfx* gfx);
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);
void pxl8_tilemap_render_tile( void pxl8_tilemap_render_tile(
const pxl8_tilemap* tilemap, const pxl8_tilemap* tilemap,
pxl8_gfx_ctx* gfx, pxl8_gfx* gfx,
u16 tile_id, u16 tile_id,
i32 x, i32 x,
i32 y, i32 y,

View file

@ -6,17 +6,47 @@
#include "pxl8_tilesheet.h" #include "pxl8_tilesheet.h"
#include "pxl8_tilemap.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) return;
if (tilesheet->data) { if (tilesheet->data) {
free(tilesheet->data); free(tilesheet->data);
tilesheet->data = NULL;
} }
if (tilesheet->tile_valid) { if (tilesheet->tile_valid) {
free(tilesheet->tile_valid); free(tilesheet->tile_valid);
tilesheet->tile_valid = NULL;
} }
if (tilesheet->animations) { if (tilesheet->animations) {
@ -26,12 +56,10 @@ void pxl8_tilesheet_free(pxl8_tilesheet* tilesheet) {
} }
} }
free(tilesheet->animations); free(tilesheet->animations);
tilesheet->animations = NULL;
} }
if (tilesheet->properties) { if (tilesheet->properties) {
free(tilesheet->properties); free(tilesheet->properties);
tilesheet->properties = NULL;
} }
if (tilesheet->autotile_rules) { if (tilesheet->autotile_rules) {
@ -42,12 +70,12 @@ void pxl8_tilesheet_free(pxl8_tilesheet* tilesheet) {
} }
free(tilesheet->autotile_rules); free(tilesheet->autotile_rules);
free(tilesheet->autotile_rule_counts); 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; if (!tilesheet || !filepath || !gfx) return PXL8_ERROR_NULL_POINTER;
pxl8_ase_file ase_file; pxl8_ase_file ase_file;
@ -74,16 +102,16 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
tilesheet->height = height; tilesheet->height = height;
tilesheet->tiles_per_row = width / tilesheet->tile_size; tilesheet->tiles_per_row = width / tilesheet->tile_size;
tilesheet->total_tiles = (width / tilesheet->tile_size) * (height / 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; 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; data_size *= 4;
} }
tilesheet->data = malloc(data_size); tilesheet->data = malloc(data_size);
if (!tilesheet->data) { if (!tilesheet->data) {
pxl8_ase_free(&ase_file); pxl8_ase_destroy(&ase_file);
return PXL8_ERROR_OUT_OF_MEMORY; 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) { if (!tilesheet->tile_valid) {
free(tilesheet->data); free(tilesheet->data);
tilesheet->data = NULL; tilesheet->data = NULL;
pxl8_ase_free(&ase_file); pxl8_ase_destroy(&ase_file);
return PXL8_ERROR_OUT_OF_MEMORY; 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, filepath, width, height, valid_tiles, tilesheet->total_tiles,
tilesheet->tile_size, tilesheet->tile_size); tilesheet->tile_size, tilesheet->tile_size);
pxl8_ase_free(&ase_file); pxl8_ase_destroy(&ase_file);
return PXL8_OK; 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) { u16 tile_id, i32 x, i32 y, u8 flags) {
if (!tilesheet || !gfx || !tilesheet->data) return; if (!tilesheet || !gfx || !tilesheet->data) return;
if (tile_id == 0 || tile_id > tilesheet->total_tiles) 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_x = x + px;
i32 screen_y = y + py; i32 screen_y = y + py;
if (screen_x >= 0 && screen_x < gfx->framebuffer_width && if (screen_x >= 0 && screen_x < pxl8_gfx_get_width(gfx) &&
screen_y >= 0 && screen_y < gfx->framebuffer_height) { screen_y >= 0 && screen_y < pxl8_gfx_get_height(gfx)) {
pxl8_pixel(gfx, screen_x, screen_y, color_idx); 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) return;
if (--tilesheet->ref_count == 0) { 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]; 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, pxl8_result pxl8_tilesheet_add_autotile_rule(pxl8_tilesheet* tilesheet, u16 base_tile_id,
u8 neighbor_mask, u16 result_tile_id) { u8 neighbor_mask, u16 result_tile_id) {
if (!tilesheet || base_tile_id == 0 || base_tile_id > tilesheet->total_tiles) { if (!tilesheet || base_tile_id == 0 || base_tile_id > tilesheet->total_tiles) {

View file

@ -6,31 +6,15 @@
typedef struct pxl8_tile_animation pxl8_tile_animation; typedef struct pxl8_tile_animation pxl8_tile_animation;
typedef struct pxl8_tile_properties pxl8_tile_properties; typedef struct pxl8_tile_properties pxl8_tile_properties;
typedef struct pxl8_autotile_rule pxl8_autotile_rule; typedef struct pxl8_autotile_rule pxl8_autotile_rule;
typedef struct pxl8_tilesheet pxl8_tilesheet;
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;
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
pxl8_tilesheet* pxl8_tilesheet_create(u32 tile_size);
void pxl8_tilesheet_destroy(pxl8_tilesheet* tilesheet);
pxl8_result pxl8_tilesheet_add_animation( pxl8_result pxl8_tilesheet_add_animation(
pxl8_tilesheet* tilesheet, pxl8_tilesheet* tilesheet,
u16 base_tile_id, u16 base_tile_id,
@ -45,18 +29,18 @@ pxl8_result pxl8_tilesheet_add_autotile_rule(
u16 result_tile_id u16 result_tile_id
); );
u16 pxl8_tilesheet_apply_autotile(const pxl8_tilesheet* tilesheet, u16 base_tile_id, u8 neighbors); 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); u16 pxl8_tilesheet_get_animated_frame(const pxl8_tilesheet* tilesheet, u16 tile_id);
const pxl8_tile_properties* pxl8_tilesheet_get_tile_property( const pxl8_tile_properties* pxl8_tilesheet_get_tile_property(
const pxl8_tilesheet* tilesheet, const pxl8_tilesheet* tilesheet,
u16 tile_id 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); 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_ref(pxl8_tilesheet* tilesheet);
void pxl8_tilesheet_render_tile( void pxl8_tilesheet_render_tile(
const pxl8_tilesheet* tilesheet, const pxl8_tilesheet* tilesheet,
pxl8_gfx_ctx* gfx, pxl8_gfx* gfx,
u16 tile_id, u16 tile_id,
i32 x, i32 x,
i32 y, i32 y,

View file

@ -4,28 +4,28 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
typedef uint8_t u8; typedef float f32;
typedef uint16_t u16; typedef double f64;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int8_t i8; typedef int8_t i8;
typedef int16_t i16; typedef int16_t i16;
typedef int32_t i32; typedef int32_t i32;
typedef int64_t i64; typedef int64_t i64;
typedef float f32; typedef uint8_t u8;
typedef double f64; typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
#if defined(__SIZEOF_INT128__) #if defined(__SIZEOF_INT128__)
typedef __uint128_t u128;
typedef __int128_t i128; typedef __int128_t i128;
typedef __uint128_t u128;
#endif #endif
typedef enum pxl8_color_mode { typedef enum pxl8_color_mode {
PXL8_COLOR_MODE_FAMI, PXL8_COLOR_MODE_FAMI,
PXL8_COLOR_MODE_MEGA,
PXL8_COLOR_MODE_GBA, PXL8_COLOR_MODE_GBA,
PXL8_COLOR_MODE_SUPERFAMI,
PXL8_COLOR_MODE_HICOLOR, PXL8_COLOR_MODE_HICOLOR,
PXL8_COLOR_MODE_MEGA,
PXL8_COLOR_MODE_SUPERFAMI
} pxl8_color_mode; } pxl8_color_mode;
typedef enum pxl8_resolution { typedef enum pxl8_resolution {
@ -35,9 +35,34 @@ typedef enum pxl8_resolution {
PXL8_RESOLUTION_640x360, PXL8_RESOLUTION_640x360,
PXL8_RESOLUTION_640x480, PXL8_RESOLUTION_640x480,
PXL8_RESOLUTION_800x600, PXL8_RESOLUTION_800x600,
PXL8_RESOLUTION_960x540, PXL8_RESOLUTION_960x540
} pxl8_resolution; } 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 { typedef struct pxl8_input_state {
bool keys[256]; bool keys[256];
bool keys_pressed[256]; bool keys_pressed[256];
@ -46,27 +71,3 @@ typedef struct pxl8_input_state {
typedef struct pxl8_point { typedef struct pxl8_point {
i32 x, y; i32 x, y;
} pxl8_point; } 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;

View file

@ -6,11 +6,33 @@
#include "pxl8_macros.h" #include "pxl8_macros.h"
#include "pxl8_vfx.h" #include "pxl8_vfx.h"
void pxl8_vfx_plasma(pxl8_gfx_ctx* ctx, f32 time, f32 scale1, f32 scale2, u8 palette_offset) { struct pxl8_particles {
if (!ctx || !ctx->framebuffer) return; pxl8_particle* particles;
u32 alive_count;
u32 count;
u32 max_count;
for (i32 y = 0; y < ctx->framebuffer_height; y++) { f32 x, y;
for (i32 x = 0; x < ctx->framebuffer_width; x++) { 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 < pxl8_gfx_get_height(ctx); y++) {
for (i32 x = 0; x < pxl8_gfx_get_width(ctx); x++) {
f32 v1 = sinf(x * scale1 + time); f32 v1 = sinf(x * scale1 + time);
f32 v2 = sinf(y * scale1 + time * 0.7f); f32 v2 = sinf(y * scale1 + time * 0.7f);
f32 v3 = sinf((x + y) * scale2 + time * 1.3f); f32 v3 = sinf((x + y) * scale2 + time * 1.3f);
@ -27,7 +49,7 @@ 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; if (!ctx || !bars) return;
for (u32 i = 0; i < bar_count; i++) { for (u32 i = 0; i < bar_count; i++) {
@ -35,36 +57,39 @@ void pxl8_vfx_raster_bars(pxl8_gfx_ctx* ctx, pxl8_raster_bar* bars, u32 bar_coun
f32 y = bar->base_y + bar->amplitude * sinf(time * bar->speed + bar->phase); f32 y = bar->base_y + bar->amplitude * sinf(time * bar->speed + bar->phase);
i32 y_int = (i32)y; i32 y_int = (i32)y;
for (i32 dy = 0; dy <= bar->height; dy++) { for (i32 dy = 0; dy < bar->height; dy++) {
f32 position = (f32)dy / (f32)bar->height; f32 position = (f32)dy / (f32)(bar->height - 1);
f32 gradient = 1.0f - 2.0f * fabsf(position - 0.5f); f32 distance_from_center = fabsf(position - 0.5f) * 2.0f;
u8 color_idx; u8 color_idx;
if (gradient > 0.8f) { if (distance_from_center < 0.3f) {
color_idx = bar->fade_color; 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 { } else {
u8 range = bar->fade_color - bar->color; color_idx = bar->color - 1;
color_idx = bar->color + (u8)(gradient * range);
} }
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) { void pxl8_vfx_rotozoom(pxl8_gfx* ctx, f32 angle, f32 zoom, i32 cx, i32 cy) {
if (!ctx || !ctx->framebuffer) return; if (!ctx || !pxl8_gfx_get_framebuffer(ctx)) return;
f32 cos_a = cosf(angle); f32 cos_a = cosf(angle);
f32 sin_a = sinf(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; 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 y = 0; y < pxl8_gfx_get_height(ctx); y++) {
for (i32 x = 0; x < ctx->framebuffer_width; x++) { for (i32 x = 0; x < pxl8_gfx_get_width(ctx); x++) {
f32 dx = x - cx; f32 dx = x - cx;
f32 dy = y - cy; 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 sx = (i32)src_x;
i32 sy = (i32)src_y; i32 sy = (i32)src_y;
if (sx >= 0 && sx < ctx->framebuffer_width && sy >= 0 && sy < ctx->framebuffer_height) { if (sx >= 0 && sx < pxl8_gfx_get_width(ctx) && sy >= 0 && sy < pxl8_gfx_get_height(ctx)) {
ctx->framebuffer[y * ctx->framebuffer_width + x] = temp_buffer[sy * ctx->framebuffer_width + sx]; 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); SDL_free(temp_buffer);
} }
void pxl8_vfx_tunnel(pxl8_gfx_ctx* ctx, f32 time, f32 speed, f32 twist) { void pxl8_vfx_tunnel(pxl8_gfx* ctx, f32 time, f32 speed, f32 twist) {
if (!ctx || !ctx->framebuffer) return; if (!ctx || !pxl8_gfx_get_framebuffer(ctx)) return;
f32 cx = ctx->framebuffer_width / 2.0f; f32 cx = pxl8_gfx_get_width(ctx) / 2.0f;
f32 cy = ctx->framebuffer_height / 2.0f; f32 cy = pxl8_gfx_get_height(ctx) / 2.0f;
for (i32 y = 0; y < ctx->framebuffer_height; y++) { for (i32 y = 0; y < pxl8_gfx_get_height(ctx); y++) {
for (i32 x = 0; x < ctx->framebuffer_width; x++) { for (i32 x = 0; x < pxl8_gfx_get_width(ctx); x++) {
f32 dx = x - cx; f32 dx = x - cx;
f32 dy = y - cy; f32 dy = y - cy;
f32 dist = sqrtf(dx * dx + dy * dy); 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; if (!ctx || !height_map) return;
i32 w = ctx->framebuffer_width; i32 w = pxl8_gfx_get_width(ctx);
i32 h = ctx->framebuffer_height; i32 h = pxl8_gfx_get_height(ctx);
static f32* prev_height = NULL; static f32* prev_height = NULL;
if (!prev_height) { 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; prev_height = temp;
} }
void pxl8_vfx_particles_clear(pxl8_particle_system* sys) { pxl8_particles* pxl8_particles_create(u32 max_count) {
if (!sys || !sys->particles) return; pxl8_particles* particles = SDL_calloc(1, sizeof(pxl8_particles));
if (!particles) return NULL;
for (u32 i = 0; i < sys->max_count; i++) { particles->particles = SDL_calloc(max_count, sizeof(pxl8_particle));
sys->particles[i].life = 0; if (!particles->particles) {
sys->particles[i].flags = 0; 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) { void pxl8_particles_destroy(pxl8_particles* particles) {
if (!sys) return; if (!particles) return;
SDL_free(particles->particles);
if (sys->particles) { SDL_free(particles);
SDL_free(sys->particles);
sys->particles = NULL;
}
sys->count = 0;
sys->max_count = 0;
sys->alive_count = 0;
} }
void pxl8_vfx_particles_emit(pxl8_particle_system* sys, u32 count) { void pxl8_particles_clear(pxl8_particles* particles) {
if (!sys || !sys->particles) return; if (!particles || !particles->particles) return;
for (u32 i = 0; i < count && sys->alive_count < sys->max_count; i++) { for (u32 i = 0; i < particles->max_count; i++) {
for (u32 j = 0; j < sys->max_count; j++) { particles->particles[i].life = 0;
if (sys->particles[j].life <= 0) { particles->particles[i].flags = 0;
pxl8_particle* p = &sys->particles[j]; }
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 && 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->life = 1.0f;
p->max_life = 1.0f; p->max_life = 1.0f;
p->x = sys->x + (((f32)rand() / RAND_MAX) - 0.5f) * sys->spread_x; p->x = particles->x + (((f32)rand() / RAND_MAX) - 0.5f) * particles->spread_x;
p->y = sys->y + (((f32)rand() / RAND_MAX) - 0.5f) * sys->spread_y; p->y = particles->y + (((f32)rand() / RAND_MAX) - 0.5f) * particles->spread_y;
p->z = 0; p->z = 0;
p->vx = p->vy = p->vz = 0; p->vx = p->vy = p->vz = 0;
p->ax = sys->gravity_x; p->ax = particles->gravity_x;
p->ay = sys->gravity_y; p->ay = particles->gravity_y;
p->az = 0; p->az = 0;
p->color = p->start_color = p->end_color = 15; p->color = p->start_color = p->end_color = 15;
p->size = 1.0f; p->size = 1.0f;
@ -186,76 +223,65 @@ void pxl8_vfx_particles_emit(pxl8_particle_system* sys, u32 count) {
p->spin = 0; p->spin = 0;
p->flags = 1; p->flags = 1;
if (sys->spawn_fn) { if (particles->spawn_fn) {
sys->spawn_fn(p, sys->userdata); particles->spawn_fn(p, particles->userdata);
} }
sys->alive_count++; particles->alive_count++;
break; break;
} }
} }
} }
} }
void pxl8_vfx_particles_init(pxl8_particle_system* sys, u32 max_count) { void pxl8_particles_render(pxl8_particles* particles, pxl8_gfx* gfx) {
if (!sys) return; if (!particles || !particles->particles || !gfx) return;
SDL_memset(sys, 0, sizeof(pxl8_particle_system)); for (u32 i = 0; i < particles->max_count; i++) {
pxl8_particle* p = &particles->particles[i];
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_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];
if (p->life > 0 && p->flags) { if (p->life > 0 && p->flags) {
if (sys->render_fn) { if (particles->render_fn) {
sys->render_fn(ctx, p, sys->userdata); particles->render_fn(gfx, p, particles->userdata);
} else { } else {
i32 x = (i32)p->x; i32 x = (i32)p->x;
i32 y = (i32)p->y; i32 y = (i32)p->y;
if (x >= 0 && x < ctx->framebuffer_width && y >= 0 && y < ctx->framebuffer_height) { if (x >= 0 && x < pxl8_gfx_get_width(gfx) && y >= 0 && y < pxl8_gfx_get_height(gfx)) {
pxl8_pixel(ctx, x, y, p->color); pxl8_pixel(gfx, x, y, p->color);
} }
} }
} }
} }
} }
void pxl8_vfx_particles_update(pxl8_particle_system* sys, f32 dt) { void pxl8_particles_update(pxl8_particles* particles, f32 dt) {
if (!sys || !sys->particles) return; if (!particles || !particles->particles) return;
sys->spawn_timer += dt; if (particles->spawn_rate > 0.0f) {
f32 spawn_interval = 1.0f / sys->spawn_rate; particles->spawn_timer += dt;
while (sys->spawn_timer >= spawn_interval) { f32 spawn_interval = 1.0f / particles->spawn_rate;
pxl8_vfx_particles_emit(sys, 1); u32 max_spawns_per_frame = particles->max_count / 10;
sys->spawn_timer -= spawn_interval; 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++) { for (u32 i = 0; i < particles->max_count; i++) {
pxl8_particle* p = &sys->particles[i]; pxl8_particle* p = &particles->particles[i];
if (p->life > 0) { if (p->life > 0) {
if (sys->update_fn) { if (particles->update_fn) {
sys->update_fn(p, dt, sys->userdata); particles->update_fn(p, dt, particles->userdata);
} else { } else {
p->vx += p->ax * dt; p->vx += p->ax * dt;
p->vy += p->ay * dt; p->vy += p->ay * dt;
p->vz += p->az * dt; p->vz += p->az * dt;
p->vx *= sys->drag; p->vx *= particles->drag;
p->vy *= sys->drag; p->vy *= particles->drag;
p->vz *= sys->drag; p->vz *= particles->drag;
p->x += p->vx * dt; p->x += p->vx * dt;
p->y += p->vy * 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; p->life -= dt / p->max_life;
if (p->life <= 0) { if (p->life <= 0) {
p->flags = 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) { void pxl8_vfx_explosion(pxl8_particles* particles, i32 x, i32 y, u32 color, f32 force) {
if (!sys) return; if (!particles) return;
sys->x = x; particles->x = x;
sys->y = y; particles->y = y;
sys->spread_x = sys->spread_y = 2.0f; particles->spread_x = particles->spread_y = 2.0f;
sys->gravity_x = 0; particles->gravity_x = 0;
sys->gravity_y = 200.0f; particles->gravity_y = 200.0f;
sys->drag = 0.95f; particles->drag = 0.95f;
sys->update_fn = NULL; particles->update_fn = NULL;
for (u32 i = 0; i < 50 && i < sys->max_count; i++) { for (u32 i = 0; i < 50 && i < particles->max_count; i++) {
pxl8_particle* p = &sys->particles[i]; pxl8_particle* p = &particles->particles[i];
f32 angle = ((f32)rand() / RAND_MAX) * 6.28f; f32 angle = ((f32)rand() / RAND_MAX) * 6.28f;
f32 speed = force * (0.5f + ((f32)rand() / RAND_MAX) * 0.5f); 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) { void pxl8_vfx_fire(pxl8_particles* particles, i32 x, i32 y, i32 width, u8 palette_start) {
if (!sys) return; if (!particles) return;
sys->x = x; particles->x = x;
sys->y = y; particles->y = y;
sys->spread_x = width; particles->spread_x = width;
sys->spread_y = 4.0f; particles->spread_y = 4.0f;
sys->gravity_x = 0; particles->gravity_x = 0;
sys->gravity_y = -100.0f; particles->gravity_y = -100.0f;
sys->drag = 0.97f; particles->drag = 0.97f;
sys->spawn_rate = 120.0f; particles->spawn_rate = 120.0f;
sys->spawn_fn = fire_spawn; particles->spawn_fn = fire_spawn;
sys->update_fn = fire_update; particles->update_fn = fire_update;
sys->userdata = (void*)(uintptr_t)palette_start; particles->userdata = (void*)(uintptr_t)palette_start;
} }
static void rain_spawn(pxl8_particle* p, void* userdata) { 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; p->vy = 200.0f + ((f32)rand() / RAND_MAX) * 100.0f;
} }
void pxl8_vfx_rain(pxl8_particle_system* sys, i32 width, f32 wind) { void pxl8_vfx_rain(pxl8_particles* particles, i32 width, f32 wind) {
if (!sys) return; if (!particles) return;
sys->x = width / 2.0f; particles->x = width / 2.0f;
sys->y = -10; particles->y = -10;
sys->spread_x = width; particles->spread_x = width;
sys->spread_y = 0; particles->spread_y = 0;
sys->gravity_x = wind; particles->gravity_x = wind;
sys->gravity_y = 300.0f; particles->gravity_y = 300.0f;
sys->drag = 1.0f; particles->drag = 1.0f;
sys->spawn_rate = 100.0f; particles->spawn_rate = 100.0f;
sys->spawn_fn = rain_spawn; particles->spawn_fn = rain_spawn;
sys->update_fn = NULL; particles->update_fn = NULL;
} }
static void smoke_spawn(pxl8_particle* p, void* userdata) { 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; p->size = 1.0f + ((f32)rand() / RAND_MAX) * 2.0f;
} }
void pxl8_vfx_smoke(pxl8_particle_system* sys, i32 x, i32 y, u8 color) { void pxl8_vfx_smoke(pxl8_particles* particles, i32 x, i32 y, u8 color) {
if (!sys) return; if (!particles) return;
sys->x = x; particles->x = x;
sys->y = y; particles->y = y;
sys->spread_x = 5.0f; particles->spread_x = 5.0f;
sys->spread_y = 5.0f; particles->spread_y = 5.0f;
sys->gravity_x = 0; particles->gravity_x = 0;
sys->gravity_y = -50.0f; particles->gravity_y = -50.0f;
sys->drag = 0.96f; particles->drag = 0.96f;
sys->spawn_rate = 20.0f; particles->spawn_rate = 20.0f;
sys->spawn_fn = smoke_spawn; particles->spawn_fn = smoke_spawn;
sys->update_fn = NULL; particles->update_fn = NULL;
sys->userdata = (void*)(uintptr_t)color; particles->userdata = (void*)(uintptr_t)color;
} }
static void snow_spawn(pxl8_particle* p, void* userdata) { 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; p->vy = 30.0f + ((f32)rand() / RAND_MAX) * 20.0f;
} }
void pxl8_vfx_snow(pxl8_particle_system* sys, i32 width, f32 wind) { void pxl8_vfx_snow(pxl8_particles* particles, i32 width, f32 wind) {
if (!sys) return; if (!particles) return;
sys->x = width / 2.0f; particles->x = width / 2.0f;
sys->y = -10; particles->y = -10;
sys->spread_x = width; particles->spread_x = width;
sys->spread_y = 0; particles->spread_y = 0;
sys->gravity_x = wind; particles->gravity_x = wind;
sys->gravity_y = 30.0f; particles->gravity_y = 30.0f;
sys->drag = 1.0f; particles->drag = 1.0f;
sys->spawn_rate = 30.0f; particles->spawn_rate = 30.0f;
sys->spawn_fn = snow_spawn; particles->spawn_fn = snow_spawn;
sys->update_fn = NULL; particles->update_fn = NULL;
} }
static void sparks_spawn(pxl8_particle* p, void* userdata) { 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; p->vy = sinf(angle) * speed - 50.0f;
} }
void pxl8_vfx_sparks(pxl8_particle_system* sys, i32 x, i32 y, u32 color) { void pxl8_vfx_sparks(pxl8_particles* particles, i32 x, i32 y, u32 color) {
if (!sys) return; if (!particles) return;
sys->x = x; particles->x = x;
sys->y = y; particles->y = y;
sys->spread_x = 2.0f; particles->spread_x = 2.0f;
sys->spread_y = 2.0f; particles->spread_y = 2.0f;
sys->gravity_x = 0; particles->gravity_x = 0;
sys->gravity_y = 100.0f; particles->gravity_y = 100.0f;
sys->drag = 0.97f; particles->drag = 0.97f;
sys->spawn_rate = 40.0f; particles->spawn_rate = 40.0f;
sys->spawn_fn = sparks_spawn; particles->spawn_fn = sparks_spawn;
sys->update_fn = NULL; particles->update_fn = NULL;
sys->userdata = (void*)(uintptr_t)color; particles->userdata = (void*)(uintptr_t)color;
} }
void pxl8_vfx_starfield(pxl8_particle_system* sys, f32 speed, f32 spread) { void pxl8_vfx_starfield(pxl8_particles* particles, f32 speed, f32 spread) {
if (!sys) return; if (!particles) return;
sys->spread_x = sys->spread_y = spread; particles->spread_x = particles->spread_y = spread;
sys->gravity_x = sys->gravity_y = 0; particles->gravity_x = particles->gravity_y = 0;
sys->drag = 1.0f; particles->drag = 1.0f;
sys->spawn_rate = 0; particles->spawn_rate = 0;
sys->update_fn = NULL; particles->update_fn = NULL;
for (u32 i = 0; i < sys->max_count; i++) { for (u32 i = 0; i < particles->max_count; i++) {
pxl8_particle* p = &sys->particles[i]; pxl8_particle* p = &particles->particles[i];
p->x = ((f32)rand() / RAND_MAX) * spread * 2.0f - spread; p->x = ((f32)rand() / RAND_MAX) * spread * 2.0f - spread;
p->y = ((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; p->z = ((f32)rand() / RAND_MAX) * spread;

View file

@ -3,66 +3,51 @@
#include "pxl8_gfx.h" #include "pxl8_gfx.h"
#include "pxl8_types.h" #include "pxl8_types.h"
typedef struct pxl8_particles pxl8_particles;
typedef struct pxl8_particle { typedef struct pxl8_particle {
f32 x, y, z; f32 angle;
f32 vx, vy, vz;
f32 ax, ay, az; f32 ax, ay, az;
u32 color;
u32 end_color;
u8 flags;
f32 life; f32 life;
f32 max_life; f32 max_life;
u32 color;
u32 start_color;
u32 end_color;
f32 size; f32 size;
f32 angle;
f32 spin; f32 spin;
u8 flags; u32 start_color;
f32 vx, vy, vz;
f32 x, y, z;
} pxl8_particle; } 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 { typedef struct pxl8_raster_bar {
f32 base_y;
f32 amplitude; f32 amplitude;
i32 height; f32 base_y;
f32 speed;
f32 phase;
u32 color; u32 color;
u32 fade_color; u32 fade_color;
i32 height;
f32 phase;
f32 speed;
} pxl8_raster_bar; } pxl8_raster_bar;
void pxl8_vfx_particles_clear(pxl8_particle_system* sys); pxl8_particles* pxl8_particles_create(u32 max_count);
void pxl8_vfx_particles_emit(pxl8_particle_system* sys, u32 count); void pxl8_particles_destroy(pxl8_particles* particles);
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);
void pxl8_vfx_explosion(pxl8_particle_system* sys, i32 x, i32 y, u32 color, f32 force); void pxl8_particles_clear(pxl8_particles* particles);
void pxl8_vfx_fire(pxl8_particle_system* sys, i32 x, i32 y, i32 width, u8 palette_start); void pxl8_particles_emit(pxl8_particles* particles, u32 count);
void pxl8_vfx_rain(pxl8_particle_system* sys, i32 width, f32 wind); void pxl8_particles_render(pxl8_particles* particles, pxl8_gfx* gfx);
void pxl8_vfx_smoke(pxl8_particle_system* sys, i32 x, i32 y, u8 color); void pxl8_particles_update(pxl8_particles* particles, f32 dt);
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_vfx_plasma(pxl8_gfx_ctx* ctx, f32 time, f32 scale1, f32 scale2, u8 palette_offset); void pxl8_vfx_explosion(pxl8_particles* particles, i32 x, i32 y, u32 color, f32 force);
void pxl8_vfx_raster_bars(pxl8_gfx_ctx* ctx, pxl8_raster_bar* bars, u32 bar_count, f32 time); void pxl8_vfx_fire(pxl8_particles* particles, i32 x, i32 y, i32 width, u8 palette_start);
void pxl8_vfx_rotozoom(pxl8_gfx_ctx* ctx, f32 angle, f32 zoom, i32 cx, i32 cy); void pxl8_vfx_rain(pxl8_particles* particles, i32 width, f32 wind);
void pxl8_vfx_tunnel(pxl8_gfx_ctx* ctx, f32 time, f32 speed, f32 twist); void pxl8_vfx_smoke(pxl8_particles* particles, i32 x, i32 y, u8 color);
void pxl8_vfx_water_ripple(pxl8_gfx_ctx* ctx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping); 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);