From 39b604b333c1f410c40b63c5d0d748614385fd8e Mon Sep 17 00:00:00 2001 From: asrael Date: Mon, 12 Jan 2026 21:46:31 -0600 Subject: [PATCH] refactor: reorganize pxl8 into client/src/ module structure - core/: main entry, types, logging, I/O, RNG - asset/: ase loader, cart, save, embed - gfx/: graphics, animation, atlas, fonts, tilemap, transitions - sfx/: audio - script/: lua/fennel runtime, REPL - hal/: platform abstraction (SDL3) - world/: BSP, world, procedural gen - math/: math utilities - game/: GUI, replay - lua/: Lua API modules --- {src => client/src/asset}/pxl8_ase.c | 6 +- {src => client/src/asset}/pxl8_ase.h | 0 {src => client/src/asset}/pxl8_cart.c | 0 {src => client/src/asset}/pxl8_cart.h | 0 {src => client/src/asset}/pxl8_embed.h | 31 +- {src => client/src/asset}/pxl8_save.c | 0 {src => client/src/asset}/pxl8_save.h | 0 {src => client/src/core}/pxl8.c | 4 +- {src => client/src/core}/pxl8_io.c | 0 {src => client/src/core}/pxl8_io.h | 0 {src => client/src/core}/pxl8_log.c | 0 {src => client/src/core}/pxl8_log.h | 0 {src => client/src/core}/pxl8_macros.h | 0 {src => client/src/core}/pxl8_rng.c | 0 {src => client/src/core}/pxl8_rng.h | 0 {src => client/src/core}/pxl8_sys.h | 0 {src => client/src/core}/pxl8_types.h | 0 {src => client/src/game}/pxl8_game.h | 0 {src => client/src/game}/pxl8_gui.c | 20 +- {src => client/src/game}/pxl8_gui.h | 0 {src => client/src/game}/pxl8_replay.c | 0 {src => client/src/game}/pxl8_replay.h | 0 client/src/gfx/pxl8_3d_camera.c | 214 +++ client/src/gfx/pxl8_3d_camera.h | 40 + {src => client/src/gfx}/pxl8_anim.c | 3 +- {src => client/src/gfx}/pxl8_anim.h | 0 {src => client/src/gfx}/pxl8_atlas.c | 0 {src => client/src/gfx}/pxl8_atlas.h | 0 client/src/gfx/pxl8_backend.h | 19 + {src => client/src/gfx}/pxl8_blit.c | 0 {src => client/src/gfx}/pxl8_blit.h | 0 {src => client/src/gfx}/pxl8_color.h | 33 +- client/src/gfx/pxl8_colormap.c | 79 ++ client/src/gfx/pxl8_colormap.h | 43 + client/src/gfx/pxl8_cpu.c | 1075 ++++++++++++++ client/src/gfx/pxl8_cpu.h | 94 ++ client/src/gfx/pxl8_dither.c | 8 + client/src/gfx/pxl8_dither.h | 41 + {src => client/src/gfx}/pxl8_font.c | 0 {src => client/src/gfx}/pxl8_font.h | 0 client/src/gfx/pxl8_gfx.c | 706 ++++++++++ client/src/gfx/pxl8_gfx.h | 48 + client/src/gfx/pxl8_gfx2d.h | 24 + client/src/gfx/pxl8_gfx3d.h | 33 + client/src/gfx/pxl8_mesh.c | 122 ++ client/src/gfx/pxl8_mesh.h | 124 ++ client/src/gfx/pxl8_palette.c | 474 +++++++ client/src/gfx/pxl8_palette.h | 70 + client/src/gfx/pxl8_particles.c | 322 +++++ client/src/gfx/pxl8_particles.h | 75 + {src => client/src/gfx}/pxl8_tilemap.c | 0 {src => client/src/gfx}/pxl8_tilemap.h | 0 {src => client/src/gfx}/pxl8_tilesheet.c | 3 +- {src => client/src/gfx}/pxl8_tilesheet.h | 0 {src => client/src/gfx}/pxl8_transition.c | 25 +- {src => client/src/gfx}/pxl8_transition.h | 0 {src => client/src/hal}/pxl8_hal.h | 0 {src => client/src/hal}/pxl8_sdl3.c | 0 {src => client/src/hal}/pxl8_sdl3.h | 0 client/src/lua/pxl8.lua | 162 +++ client/src/lua/pxl8/anim.lua | 126 ++ {src => client/src}/lua/pxl8/core.lua | 22 + {src => client/src}/lua/pxl8/gfx2d.lua | 28 +- client/src/lua/pxl8/gfx3d.lua | 161 +++ client/src/lua/pxl8/gui.lua | 70 + {src => client/src}/lua/pxl8/input.lua | 0 {src => client/src}/lua/pxl8/math.lua | 4 +- client/src/lua/pxl8/particles.lua | 103 ++ client/src/lua/pxl8/sfx.lua | 241 ++++ client/src/lua/pxl8/tilemap.lua | 106 ++ client/src/lua/pxl8/transition.lua | 82 ++ {src => client/src}/lua/pxl8/vfx.lua | 0 {src => client/src}/lua/pxl8/world.lua | 120 +- {src => client/src/math}/pxl8_math.c | 12 +- {src => client/src/math}/pxl8_math.h | 5 +- client/src/math/pxl8_simd.h | 299 ++++ {src => client/src/script}/pxl8_repl.c | 0 {src => client/src/script}/pxl8_repl.h | 0 {src => client/src/script}/pxl8_script.c | 322 +---- {src => client/src/script}/pxl8_script.h | 0 client/src/script/pxl8_script_ffi.h | 389 +++++ {src => client/src/sfx}/pxl8_sfx.c | 0 {src => client/src/sfx}/pxl8_sfx.h | 0 {src => client/src/world}/pxl8_bsp.c | 371 ++++- {src => client/src/world}/pxl8_bsp.h | 58 +- {src => client/src/world}/pxl8_gen.c | 0 {src => client/src/world}/pxl8_gen.h | 0 {src => client/src/world}/pxl8_world.c | 15 +- {src => client/src/world}/pxl8_world.h | 0 demo/main.fnl | 80 +- demo/mod/menu.fnl | 39 +- demo/mod/music.fnl | 18 +- demo/mod/worldgen.fnl | 85 +- pxl8.sh | 68 +- src/lua/pxl8.lua | 248 ---- src/lua/pxl8/anim.lua | 110 -- src/lua/pxl8/gfx3d.lua | 56 - src/lua/pxl8/gui.lua | 59 - src/lua/pxl8/particles.lua | 31 - src/lua/pxl8/sfx.lua | 198 --- src/lua/pxl8/tilemap.lua | 82 -- src/lua/pxl8/transition.lua | 68 - src/pxl8_gfx.c | 1567 --------------------- src/pxl8_gfx.h | 86 -- src/pxl8_vfx.c | 504 ------- src/pxl8_vfx.h | 62 - 106 files changed, 6078 insertions(+), 3715 deletions(-) rename {src => client/src/asset}/pxl8_ase.c (98%) rename {src => client/src/asset}/pxl8_ase.h (100%) rename {src => client/src/asset}/pxl8_cart.c (100%) rename {src => client/src/asset}/pxl8_cart.h (100%) rename {src => client/src/asset}/pxl8_embed.h (78%) rename {src => client/src/asset}/pxl8_save.c (100%) rename {src => client/src/asset}/pxl8_save.h (100%) rename {src => client/src/core}/pxl8.c (99%) rename {src => client/src/core}/pxl8_io.c (100%) rename {src => client/src/core}/pxl8_io.h (100%) rename {src => client/src/core}/pxl8_log.c (100%) rename {src => client/src/core}/pxl8_log.h (100%) rename {src => client/src/core}/pxl8_macros.h (100%) rename {src => client/src/core}/pxl8_rng.c (100%) rename {src => client/src/core}/pxl8_rng.h (100%) rename {src => client/src/core}/pxl8_sys.h (100%) rename {src => client/src/core}/pxl8_types.h (100%) rename {src => client/src/game}/pxl8_game.h (100%) rename {src => client/src/game}/pxl8_gui.c (86%) rename {src => client/src/game}/pxl8_gui.h (100%) rename {src => client/src/game}/pxl8_replay.c (100%) rename {src => client/src/game}/pxl8_replay.h (100%) create mode 100644 client/src/gfx/pxl8_3d_camera.c create mode 100644 client/src/gfx/pxl8_3d_camera.h rename {src => client/src/gfx}/pxl8_anim.c (99%) rename {src => client/src/gfx}/pxl8_anim.h (100%) rename {src => client/src/gfx}/pxl8_atlas.c (100%) rename {src => client/src/gfx}/pxl8_atlas.h (100%) create mode 100644 client/src/gfx/pxl8_backend.h rename {src => client/src/gfx}/pxl8_blit.c (100%) rename {src => client/src/gfx}/pxl8_blit.h (100%) rename {src => client/src/gfx}/pxl8_color.h (51%) create mode 100644 client/src/gfx/pxl8_colormap.c create mode 100644 client/src/gfx/pxl8_colormap.h create mode 100644 client/src/gfx/pxl8_cpu.c create mode 100644 client/src/gfx/pxl8_cpu.h create mode 100644 client/src/gfx/pxl8_dither.c create mode 100644 client/src/gfx/pxl8_dither.h rename {src => client/src/gfx}/pxl8_font.c (100%) rename {src => client/src/gfx}/pxl8_font.h (100%) create mode 100644 client/src/gfx/pxl8_gfx.c create mode 100644 client/src/gfx/pxl8_gfx.h create mode 100644 client/src/gfx/pxl8_gfx2d.h create mode 100644 client/src/gfx/pxl8_gfx3d.h create mode 100644 client/src/gfx/pxl8_mesh.c create mode 100644 client/src/gfx/pxl8_mesh.h create mode 100644 client/src/gfx/pxl8_palette.c create mode 100644 client/src/gfx/pxl8_palette.h create mode 100644 client/src/gfx/pxl8_particles.c create mode 100644 client/src/gfx/pxl8_particles.h rename {src => client/src/gfx}/pxl8_tilemap.c (100%) rename {src => client/src/gfx}/pxl8_tilemap.h (100%) rename {src => client/src/gfx}/pxl8_tilesheet.c (99%) rename {src => client/src/gfx}/pxl8_tilesheet.h (100%) rename {src => client/src/gfx}/pxl8_transition.c (89%) rename {src => client/src/gfx}/pxl8_transition.h (100%) rename {src => client/src/hal}/pxl8_hal.h (100%) rename {src => client/src/hal}/pxl8_sdl3.c (100%) rename {src => client/src/hal}/pxl8_sdl3.h (100%) create mode 100644 client/src/lua/pxl8.lua create mode 100644 client/src/lua/pxl8/anim.lua rename {src => client/src}/lua/pxl8/core.lua (76%) rename {src => client/src}/lua/pxl8/gfx2d.lua (75%) create mode 100644 client/src/lua/pxl8/gfx3d.lua create mode 100644 client/src/lua/pxl8/gui.lua rename {src => client/src}/lua/pxl8/input.lua (100%) rename {src => client/src}/lua/pxl8/math.lua (94%) create mode 100644 client/src/lua/pxl8/particles.lua create mode 100644 client/src/lua/pxl8/sfx.lua create mode 100644 client/src/lua/pxl8/tilemap.lua create mode 100644 client/src/lua/pxl8/transition.lua rename {src => client/src}/lua/pxl8/vfx.lua (100%) rename {src => client/src}/lua/pxl8/world.lua (69%) rename {src => client/src/math}/pxl8_math.c (96%) rename {src => client/src/math}/pxl8_math.h (91%) create mode 100644 client/src/math/pxl8_simd.h rename {src => client/src/script}/pxl8_repl.c (100%) rename {src => client/src/script}/pxl8_repl.h (100%) rename {src => client/src/script}/pxl8_script.c (68%) rename {src => client/src/script}/pxl8_script.h (100%) create mode 100644 client/src/script/pxl8_script_ffi.h rename {src => client/src/sfx}/pxl8_sfx.c (100%) rename {src => client/src/sfx}/pxl8_sfx.h (100%) rename {src => client/src/world}/pxl8_bsp.c (63%) rename {src => client/src/world}/pxl8_bsp.h (61%) rename {src => client/src/world}/pxl8_gen.c (100%) rename {src => client/src/world}/pxl8_gen.h (100%) rename {src => client/src/world}/pxl8_world.c (96%) rename {src => client/src/world}/pxl8_world.h (100%) delete mode 100644 src/lua/pxl8.lua delete mode 100644 src/lua/pxl8/anim.lua delete mode 100644 src/lua/pxl8/gfx3d.lua delete mode 100644 src/lua/pxl8/gui.lua delete mode 100644 src/lua/pxl8/particles.lua delete mode 100644 src/lua/pxl8/sfx.lua delete mode 100644 src/lua/pxl8/tilemap.lua delete mode 100644 src/lua/pxl8/transition.lua delete mode 100644 src/pxl8_gfx.c delete mode 100644 src/pxl8_gfx.h delete mode 100644 src/pxl8_vfx.c delete mode 100644 src/pxl8_vfx.h diff --git a/src/pxl8_ase.c b/client/src/asset/pxl8_ase.c similarity index 98% rename from src/pxl8_ase.c rename to client/src/asset/pxl8_ase.c index 9c3ed34..36f9620 100644 --- a/src/pxl8_ase.c +++ b/client/src/asset/pxl8_ase.c @@ -79,7 +79,7 @@ static pxl8_result parse_old_palette_chunk(pxl8_stream* stream, pxl8_ase_palette u8 r = pxl8_read_u8(stream); u8 g = pxl8_read_u8(stream); u8 b = pxl8_read_u8(stream); - palette->colors[color_index++] = 0xFF000000 | (b << 16) | (g << 8) | r; + palette->colors[color_index++] = 0xFF000000 | ((u32)b << 16) | ((u32)g << 8) | r; } } @@ -134,7 +134,7 @@ static pxl8_result parse_palette_chunk(pxl8_stream* stream, pxl8_ase_palette* pa u8 b = pxl8_read_u8(stream); u8 a = pxl8_read_u8(stream); - palette->colors[i] = (a << 24) | (b << 16) | (g << 8) | r; + palette->colors[i] = ((u32)a << 24) | ((u32)b << 16) | ((u32)g << 8) | r; if (flags & 1) { u16 name_len = pxl8_read_u16(stream); @@ -166,7 +166,7 @@ static pxl8_result parse_user_data_chunk(pxl8_stream* stream, pxl8_ase_user_data u8 g = pxl8_read_u8(stream); u8 b = pxl8_read_u8(stream); u8 a = pxl8_read_u8(stream); - user_data->color = (a << 24) | (b << 16) | (g << 8) | r; + user_data->color = ((u32)a << 24) | ((u32)b << 16) | ((u32)g << 8) | r; } if (flags & 4) { diff --git a/src/pxl8_ase.h b/client/src/asset/pxl8_ase.h similarity index 100% rename from src/pxl8_ase.h rename to client/src/asset/pxl8_ase.h diff --git a/src/pxl8_cart.c b/client/src/asset/pxl8_cart.c similarity index 100% rename from src/pxl8_cart.c rename to client/src/asset/pxl8_cart.c diff --git a/src/pxl8_cart.h b/client/src/asset/pxl8_cart.h similarity index 100% rename from src/pxl8_cart.h rename to client/src/asset/pxl8_cart.h diff --git a/src/pxl8_embed.h b/client/src/asset/pxl8_embed.h similarity index 78% rename from src/pxl8_embed.h rename to client/src/asset/pxl8_embed.h index 02810b9..07791e6 100644 --- a/src/pxl8_embed.h +++ b/client/src/asset/pxl8_embed.h @@ -8,59 +8,55 @@ static const char embed_fennel[] = { , 0 }; static const char embed_pxl8[] = { -#embed "src/lua/pxl8.lua" +#embed "client/src/lua/pxl8.lua" , 0 }; static const char embed_pxl8_anim[] = { -#embed "src/lua/pxl8/anim.lua" +#embed "client/src/lua/pxl8/anim.lua" , 0 }; static const char embed_pxl8_core[] = { -#embed "src/lua/pxl8/core.lua" +#embed "client/src/lua/pxl8/core.lua" , 0 }; static const char embed_pxl8_gfx2d[] = { -#embed "src/lua/pxl8/gfx2d.lua" +#embed "client/src/lua/pxl8/gfx2d.lua" , 0 }; static const char embed_pxl8_gfx3d[] = { -#embed "src/lua/pxl8/gfx3d.lua" +#embed "client/src/lua/pxl8/gfx3d.lua" , 0 }; static const char embed_pxl8_gui[] = { -#embed "src/lua/pxl8/gui.lua" +#embed "client/src/lua/pxl8/gui.lua" , 0 }; static const char embed_pxl8_input[] = { -#embed "src/lua/pxl8/input.lua" +#embed "client/src/lua/pxl8/input.lua" , 0 }; static const char embed_pxl8_math[] = { -#embed "src/lua/pxl8/math.lua" +#embed "client/src/lua/pxl8/math.lua" , 0 }; static const char embed_pxl8_particles[] = { -#embed "src/lua/pxl8/particles.lua" +#embed "client/src/lua/pxl8/particles.lua" , 0 }; static const char embed_pxl8_sfx[] = { -#embed "src/lua/pxl8/sfx.lua" +#embed "client/src/lua/pxl8/sfx.lua" , 0 }; static const char embed_pxl8_tilemap[] = { -#embed "src/lua/pxl8/tilemap.lua" +#embed "client/src/lua/pxl8/tilemap.lua" , 0 }; static const char embed_pxl8_transition[] = { -#embed "src/lua/pxl8/transition.lua" -, 0 }; - -static const char embed_pxl8_vfx[] = { -#embed "src/lua/pxl8/vfx.lua" +#embed "client/src/lua/pxl8/transition.lua" , 0 }; static const char embed_pxl8_world[] = { -#embed "src/lua/pxl8/world.lua" +#embed "client/src/lua/pxl8/world.lua" , 0 }; #define PXL8_EMBED_ENTRY(var, name) {name, var, sizeof(var) - 1} @@ -81,7 +77,6 @@ static const pxl8_embed pxl8_embeds[] = { PXL8_EMBED_ENTRY(embed_pxl8_sfx, "pxl8.sfx"), PXL8_EMBED_ENTRY(embed_pxl8_tilemap, "pxl8.tilemap"), PXL8_EMBED_ENTRY(embed_pxl8_transition, "pxl8.transition"), - PXL8_EMBED_ENTRY(embed_pxl8_vfx, "pxl8.vfx"), PXL8_EMBED_ENTRY(embed_pxl8_world, "pxl8.world"), {0} }; diff --git a/src/pxl8_save.c b/client/src/asset/pxl8_save.c similarity index 100% rename from src/pxl8_save.c rename to client/src/asset/pxl8_save.c diff --git a/src/pxl8_save.h b/client/src/asset/pxl8_save.h similarity index 100% rename from src/pxl8_save.h rename to client/src/asset/pxl8_save.h diff --git a/src/pxl8.c b/client/src/core/pxl8.c similarity index 99% rename from src/pxl8.c rename to client/src/core/pxl8.c index 544ee69..8e29af4 100644 --- a/src/pxl8.c +++ b/client/src/core/pxl8.c @@ -360,7 +360,7 @@ pxl8_result pxl8_frame(pxl8* sys) { pxl8_error("error calling frame: %s", pxl8_script_get_last_error(game->script)); } } else { - pxl8_clear(game->gfx, 32); + pxl8_2d_clear(game->gfx, 32); i32 render_w = pxl8_gfx_get_width(game->gfx); i32 render_h = pxl8_gfx_get_height(game->gfx); @@ -368,7 +368,7 @@ pxl8_result pxl8_frame(pxl8* sys) { for (i32 y = 0; y < render_h; y += 24) { for (i32 x = 0; x < render_w; x += 32) { u32 color = ((x / 32) + (y / 24) + (i32)(game->time * 2)) % 8; - pxl8_rect_fill(game->gfx, x, y, 31, 23, color); + pxl8_2d_rect_fill(game->gfx, x, y, 31, 23, color); } } } diff --git a/src/pxl8_io.c b/client/src/core/pxl8_io.c similarity index 100% rename from src/pxl8_io.c rename to client/src/core/pxl8_io.c diff --git a/src/pxl8_io.h b/client/src/core/pxl8_io.h similarity index 100% rename from src/pxl8_io.h rename to client/src/core/pxl8_io.h diff --git a/src/pxl8_log.c b/client/src/core/pxl8_log.c similarity index 100% rename from src/pxl8_log.c rename to client/src/core/pxl8_log.c diff --git a/src/pxl8_log.h b/client/src/core/pxl8_log.h similarity index 100% rename from src/pxl8_log.h rename to client/src/core/pxl8_log.h diff --git a/src/pxl8_macros.h b/client/src/core/pxl8_macros.h similarity index 100% rename from src/pxl8_macros.h rename to client/src/core/pxl8_macros.h diff --git a/src/pxl8_rng.c b/client/src/core/pxl8_rng.c similarity index 100% rename from src/pxl8_rng.c rename to client/src/core/pxl8_rng.c diff --git a/src/pxl8_rng.h b/client/src/core/pxl8_rng.h similarity index 100% rename from src/pxl8_rng.h rename to client/src/core/pxl8_rng.h diff --git a/src/pxl8_sys.h b/client/src/core/pxl8_sys.h similarity index 100% rename from src/pxl8_sys.h rename to client/src/core/pxl8_sys.h diff --git a/src/pxl8_types.h b/client/src/core/pxl8_types.h similarity index 100% rename from src/pxl8_types.h rename to client/src/core/pxl8_types.h diff --git a/src/pxl8_game.h b/client/src/game/pxl8_game.h similarity index 100% rename from src/pxl8_game.h rename to client/src/game/pxl8_game.h diff --git a/src/pxl8_gui.c b/client/src/game/pxl8_gui.c similarity index 86% rename from src/pxl8_gui.c rename to client/src/game/pxl8_gui.c index fb691a1..6f8bbd3 100644 --- a/src/pxl8_gui.c +++ b/client/src/game/pxl8_gui.c @@ -3,6 +3,8 @@ #include #include +#include "pxl8_gfx.h" + pxl8_gui_state* pxl8_gui_state_create(void) { pxl8_gui_state* state = (pxl8_gui_state*)malloc(sizeof(pxl8_gui_state)); if (!state) return NULL; @@ -90,13 +92,13 @@ bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, border_color = 4; } - pxl8_rect_fill(gfx, x, y, w, h, bg_color); - pxl8_rect(gfx, x, y, w, h, border_color); + pxl8_2d_rect_fill(gfx, x, y, w, h, bg_color); + pxl8_2d_rect(gfx, x, y, w, h, border_color); i32 text_len = (i32)strlen(label); i32 text_x = x + (w / 2) - ((text_len * 8) / 2) + offset_x; i32 text_y = y + (h / 2) - 5 + offset_y; - pxl8_text(gfx, label, text_x, text_y, 6); + pxl8_2d_text(gfx, label, text_x, text_y, 6); return clicked; } @@ -104,19 +106,19 @@ bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title) { if (!gfx || !title) return; - pxl8_rect_fill(gfx, x, y, w, 28, 1); - pxl8_rect_fill(gfx, x, y + 28, w, h - 28, 2); - pxl8_rect(gfx, x, y, w, h, 4); - pxl8_rect_fill(gfx, x, y + 28, w, 1, 4); + pxl8_2d_rect_fill(gfx, x, y, w, 28, 1); + pxl8_2d_rect_fill(gfx, x, y + 28, w, h - 28, 2); + pxl8_2d_rect(gfx, x, y, w, h, 4); + pxl8_2d_rect_fill(gfx, x, y + 28, w, 1, 4); i32 title_x = x + 10; i32 title_y = y + (28 / 2) - 5; - pxl8_text(gfx, title, title_x, title_y, 8); + pxl8_2d_text(gfx, title, title_x, title_y, 8); } void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color) { if (!gfx || !text) return; - pxl8_text(gfx, text, x, y, color); + pxl8_2d_text(gfx, text, x, y, color); } bool pxl8_gui_is_hovering(const pxl8_gui_state* state) { diff --git a/src/pxl8_gui.h b/client/src/game/pxl8_gui.h similarity index 100% rename from src/pxl8_gui.h rename to client/src/game/pxl8_gui.h diff --git a/src/pxl8_replay.c b/client/src/game/pxl8_replay.c similarity index 100% rename from src/pxl8_replay.c rename to client/src/game/pxl8_replay.c diff --git a/src/pxl8_replay.h b/client/src/game/pxl8_replay.h similarity index 100% rename from src/pxl8_replay.h rename to client/src/game/pxl8_replay.h diff --git a/client/src/gfx/pxl8_3d_camera.c b/client/src/gfx/pxl8_3d_camera.c new file mode 100644 index 0000000..3cec31d --- /dev/null +++ b/client/src/gfx/pxl8_3d_camera.c @@ -0,0 +1,214 @@ +#include "pxl8_3d_camera.h" + +#include +#include + +struct pxl8_3d_camera { + pxl8_vec3 position; + f32 pitch; + f32 roll; + f32 yaw; + + pxl8_3d_camera_mode mode; + + f32 aspect; + f32 far; + f32 fov; + f32 near; + + f32 ortho_bottom; + f32 ortho_left; + f32 ortho_right; + f32 ortho_top; + + f32 shake_duration; + f32 shake_intensity; + pxl8_vec3 shake_offset; + f32 shake_timer; +}; + +pxl8_3d_camera* pxl8_3d_camera_create(void) { + pxl8_3d_camera* cam = calloc(1, sizeof(pxl8_3d_camera)); + if (!cam) return NULL; + + cam->position = (pxl8_vec3){0, 0, 0}; + cam->pitch = 0; + cam->yaw = 0; + cam->roll = 0; + + cam->mode = PXL8_3D_CAMERA_PERSPECTIVE; + cam->fov = 1.0f; + cam->aspect = 16.0f / 9.0f; + cam->near = 1.0f; + cam->far = 1000.0f; + + return cam; +} + +void pxl8_3d_camera_destroy(pxl8_3d_camera* cam) { + free(cam); +} + +void pxl8_3d_camera_lookat(pxl8_3d_camera* cam, pxl8_vec3 eye, pxl8_vec3 target, pxl8_vec3 up) { + if (!cam) return; + + cam->position = eye; + + pxl8_vec3 forward = pxl8_vec3_normalize(pxl8_vec3_sub(target, eye)); + + cam->pitch = asinf(-forward.y); + cam->yaw = atan2f(forward.x, forward.z); + cam->roll = 0; + + (void)up; +} + +void pxl8_3d_camera_set_ortho(pxl8_3d_camera* cam, f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far) { + if (!cam) return; + cam->mode = PXL8_3D_CAMERA_ORTHO; + cam->ortho_left = left; + cam->ortho_right = right; + cam->ortho_bottom = bottom; + cam->ortho_top = top; + cam->near = near; + cam->far = far; +} + +void pxl8_3d_camera_set_perspective(pxl8_3d_camera* cam, f32 fov, f32 aspect, f32 near, f32 far) { + if (!cam) return; + cam->mode = PXL8_3D_CAMERA_PERSPECTIVE; + cam->fov = fov; + cam->aspect = aspect; + cam->near = near; + cam->far = far; +} + +void pxl8_3d_camera_set_position(pxl8_3d_camera* cam, pxl8_vec3 pos) { + if (!cam) return; + cam->position = pos; +} + +void pxl8_3d_camera_set_rotation(pxl8_3d_camera* cam, f32 pitch, f32 yaw, f32 roll) { + if (!cam) return; + cam->pitch = pitch; + cam->yaw = yaw; + cam->roll = roll; +} + +pxl8_vec3 pxl8_3d_camera_get_forward(const pxl8_3d_camera* cam) { + if (!cam) return (pxl8_vec3){0, 0, -1}; + + f32 cp = cosf(cam->pitch); + f32 sp = sinf(cam->pitch); + f32 cy = cosf(cam->yaw); + f32 sy = sinf(cam->yaw); + + return (pxl8_vec3){ + cp * sy, + -sp, + cp * cy + }; +} + +pxl8_vec3 pxl8_3d_camera_get_position(const pxl8_3d_camera* cam) { + if (!cam) return (pxl8_vec3){0, 0, 0}; + return pxl8_vec3_add(cam->position, cam->shake_offset); +} + +pxl8_mat4 pxl8_3d_camera_get_projection(const pxl8_3d_camera* cam) { + if (!cam) return pxl8_mat4_identity(); + + if (cam->mode == PXL8_3D_CAMERA_PERSPECTIVE) { + return pxl8_mat4_perspective(cam->fov, cam->aspect, cam->near, cam->far); + } else { + return pxl8_mat4_ortho( + cam->ortho_left, cam->ortho_right, + cam->ortho_bottom, cam->ortho_top, + cam->near, cam->far + ); + } +} + +pxl8_vec3 pxl8_3d_camera_get_right(const pxl8_3d_camera* cam) { + if (!cam) return (pxl8_vec3){1, 0, 0}; + + f32 cy = cosf(cam->yaw); + f32 sy = sinf(cam->yaw); + + return (pxl8_vec3){cy, 0, -sy}; +} + +pxl8_vec3 pxl8_3d_camera_get_up(const pxl8_3d_camera* cam) { + if (!cam) return (pxl8_vec3){0, 1, 0}; + + pxl8_vec3 forward = pxl8_3d_camera_get_forward(cam); + pxl8_vec3 right = pxl8_3d_camera_get_right(cam); + + return pxl8_vec3_cross(forward, right); +} + +pxl8_mat4 pxl8_3d_camera_get_view(const pxl8_3d_camera* cam) { + if (!cam) return pxl8_mat4_identity(); + + pxl8_vec3 pos = pxl8_3d_camera_get_position(cam); + pxl8_vec3 forward = pxl8_3d_camera_get_forward(cam); + pxl8_vec3 target = pxl8_vec3_add(pos, forward); + pxl8_vec3 up = (pxl8_vec3){0, 1, 0}; + + return pxl8_mat4_lookat(pos, target, up); +} + +void pxl8_3d_camera_blend(pxl8_3d_camera* dest, const pxl8_3d_camera* a, const pxl8_3d_camera* b, f32 t) { + if (!dest || !a || !b) return; + + dest->position = pxl8_vec3_lerp(a->position, b->position, t); + dest->pitch = a->pitch + (b->pitch - a->pitch) * t; + dest->yaw = a->yaw + (b->yaw - a->yaw) * t; + dest->roll = a->roll + (b->roll - a->roll) * t; + + dest->fov = a->fov + (b->fov - a->fov) * t; + dest->aspect = a->aspect + (b->aspect - a->aspect) * t; + dest->near = a->near + (b->near - a->near) * t; + dest->far = a->far + (b->far - a->far) * t; + + dest->mode = (t < 0.5f) ? a->mode : b->mode; +} + +void pxl8_3d_camera_follow(pxl8_3d_camera* cam, pxl8_vec3 target, pxl8_vec3 offset, f32 smoothing, f32 dt) { + if (!cam) return; + + pxl8_vec3 desired = pxl8_vec3_add(target, offset); + + f32 t = 1.0f - powf(1.0f - smoothing, dt * 60.0f); + cam->position = pxl8_vec3_lerp(cam->position, desired, t); + + pxl8_vec3 forward = pxl8_vec3_normalize(pxl8_vec3_sub(target, cam->position)); + cam->pitch = asinf(-forward.y); + cam->yaw = atan2f(forward.x, forward.z); +} + +void pxl8_3d_camera_shake(pxl8_3d_camera* cam, f32 intensity, f32 duration) { + if (!cam) return; + cam->shake_intensity = intensity; + cam->shake_duration = duration; + cam->shake_timer = duration; +} + +void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt) { + if (!cam) return; + + if (cam->shake_timer > 0) { + cam->shake_timer -= dt; + + f32 decay = cam->shake_timer / cam->shake_duration; + f32 intensity = cam->shake_intensity * decay; + + cam->shake_offset.x = ((f32)rand() / (f32)RAND_MAX * 2.0f - 1.0f) * intensity; + cam->shake_offset.y = ((f32)rand() / (f32)RAND_MAX * 2.0f - 1.0f) * intensity; + cam->shake_offset.z = ((f32)rand() / (f32)RAND_MAX * 2.0f - 1.0f) * intensity; + + if (cam->shake_timer <= 0) { + cam->shake_offset = (pxl8_vec3){0, 0, 0}; + } + } +} diff --git a/client/src/gfx/pxl8_3d_camera.h b/client/src/gfx/pxl8_3d_camera.h new file mode 100644 index 0000000..24ee8dd --- /dev/null +++ b/client/src/gfx/pxl8_3d_camera.h @@ -0,0 +1,40 @@ +#pragma once + +#include "pxl8_math.h" +#include "pxl8_types.h" + +typedef enum pxl8_3d_camera_mode { + PXL8_3D_CAMERA_ORTHO, + PXL8_3D_CAMERA_PERSPECTIVE +} pxl8_3d_camera_mode; + +typedef struct pxl8_3d_camera pxl8_3d_camera; + +#ifdef __cplusplus +extern "C" { +#endif + +pxl8_3d_camera* pxl8_3d_camera_create(void); +void pxl8_3d_camera_destroy(pxl8_3d_camera* cam); + +void pxl8_3d_camera_lookat(pxl8_3d_camera* cam, pxl8_vec3 eye, pxl8_vec3 target, pxl8_vec3 up); +void pxl8_3d_camera_set_ortho(pxl8_3d_camera* cam, f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far); +void pxl8_3d_camera_set_perspective(pxl8_3d_camera* cam, f32 fov, f32 aspect, f32 near, f32 far); +void pxl8_3d_camera_set_position(pxl8_3d_camera* cam, pxl8_vec3 pos); +void pxl8_3d_camera_set_rotation(pxl8_3d_camera* cam, f32 pitch, f32 yaw, f32 roll); + +pxl8_vec3 pxl8_3d_camera_get_forward(const pxl8_3d_camera* cam); +pxl8_vec3 pxl8_3d_camera_get_position(const pxl8_3d_camera* cam); +pxl8_mat4 pxl8_3d_camera_get_projection(const pxl8_3d_camera* cam); +pxl8_vec3 pxl8_3d_camera_get_right(const pxl8_3d_camera* cam); +pxl8_vec3 pxl8_3d_camera_get_up(const pxl8_3d_camera* cam); +pxl8_mat4 pxl8_3d_camera_get_view(const pxl8_3d_camera* cam); + +void pxl8_3d_camera_blend(pxl8_3d_camera* dest, const pxl8_3d_camera* a, const pxl8_3d_camera* b, f32 t); +void pxl8_3d_camera_follow(pxl8_3d_camera* cam, pxl8_vec3 target, pxl8_vec3 offset, f32 smoothing, f32 dt); +void pxl8_3d_camera_shake(pxl8_3d_camera* cam, f32 intensity, f32 duration); +void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt); + +#ifdef __cplusplus +} +#endif diff --git a/src/pxl8_anim.c b/client/src/gfx/pxl8_anim.c similarity index 99% rename from src/pxl8_anim.c rename to client/src/gfx/pxl8_anim.c index 6dacfdb..9fbffc5 100644 --- a/src/pxl8_anim.c +++ b/client/src/gfx/pxl8_anim.c @@ -5,6 +5,7 @@ #include "pxl8_ase.h" #include "pxl8_atlas.h" +#include "pxl8_gfx.h" #include "pxl8_log.h" #define PXL8_ANIM_MAX_STATES 32 @@ -288,7 +289,7 @@ void pxl8_anim_render_sprite(const pxl8_anim* anim, pxl8_gfx* gfx, i32 x, i32 y, if (!anim || !gfx) return; u32 sprite_id = pxl8_anim_get_current_frame_id(anim); - pxl8_sprite(gfx, sprite_id, x, y, w, h, flip_x, flip_y); + pxl8_2d_sprite(gfx, sprite_id, x, y, w, h, flip_x, flip_y); } void pxl8_anim_reset(pxl8_anim* anim) { diff --git a/src/pxl8_anim.h b/client/src/gfx/pxl8_anim.h similarity index 100% rename from src/pxl8_anim.h rename to client/src/gfx/pxl8_anim.h diff --git a/src/pxl8_atlas.c b/client/src/gfx/pxl8_atlas.c similarity index 100% rename from src/pxl8_atlas.c rename to client/src/gfx/pxl8_atlas.c diff --git a/src/pxl8_atlas.h b/client/src/gfx/pxl8_atlas.h similarity index 100% rename from src/pxl8_atlas.h rename to client/src/gfx/pxl8_atlas.h diff --git a/client/src/gfx/pxl8_backend.h b/client/src/gfx/pxl8_backend.h new file mode 100644 index 0000000..d7741b5 --- /dev/null +++ b/client/src/gfx/pxl8_backend.h @@ -0,0 +1,19 @@ +#ifndef PXL8_BACKEND_H +#define PXL8_BACKEND_H + +#include "pxl8_cpu.h" + +typedef enum { + PXL8_GFX_BACKEND_CPU, + PXL8_GFX_BACKEND_GPU, +} pxl8_gfx_backend_type; + +typedef struct { + pxl8_gfx_backend_type type; + union { + pxl8_cpu_backend* cpu; + void* gpu; + }; +} pxl8_gfx_backend; + +#endif diff --git a/src/pxl8_blit.c b/client/src/gfx/pxl8_blit.c similarity index 100% rename from src/pxl8_blit.c rename to client/src/gfx/pxl8_blit.c diff --git a/src/pxl8_blit.h b/client/src/gfx/pxl8_blit.h similarity index 100% rename from src/pxl8_blit.h rename to client/src/gfx/pxl8_blit.h diff --git a/src/pxl8_color.h b/client/src/gfx/pxl8_color.h similarity index 51% rename from src/pxl8_color.h rename to client/src/gfx/pxl8_color.h index c1fa455..6f6eb55 100644 --- a/src/pxl8_color.h +++ b/client/src/gfx/pxl8_color.h @@ -23,7 +23,7 @@ static inline u16 pxl8_rgba32_to_rgb565(u32 rgba) { static inline u32 pxl8_rgb565_to_rgba32(u16 color) { u8 r, g, b; pxl8_rgb565_unpack(color, &r, &g, &b); - return r | (g << 8) | (b << 16) | 0xFF000000; + return r | ((u32)g << 8) | ((u32)b << 16) | 0xFF000000; } static inline void pxl8_rgba32_unpack(u32 color, u8* r, u8* g, u8* b, u8* a) { @@ -34,9 +34,38 @@ static inline void pxl8_rgba32_unpack(u32 color, u8* r, u8* g, u8* b, u8* a) { } static inline u32 pxl8_rgba32_pack(u8 r, u8 g, u8 b, u8 a) { - return r | (g << 8) | (b << 16) | (a << 24); + return r | ((u32)g << 8) | ((u32)b << 16) | ((u32)a << 24); +} + +static inline u32 pxl8_color_to_rgba(u32 abgr) { + u8 r = abgr & 0xFF; + u8 g = (abgr >> 8) & 0xFF; + u8 b = (abgr >> 16) & 0xFF; + u8 a = (abgr >> 24) & 0xFF; + return ((u32)r << 24) | ((u32)g << 16) | ((u32)b << 8) | a; +} + +static inline u32 pxl8_color_from_rgba(u32 rgba) { + u8 r = (rgba >> 24) & 0xFF; + u8 g = (rgba >> 16) & 0xFF; + u8 b = (rgba >> 8) & 0xFF; + u8 a = rgba & 0xFF; + return r | ((u32)g << 8) | ((u32)b << 16) | ((u32)a << 24); } static inline u8 pxl8_color_lerp_channel(u8 c1, u8 c2, f32 t) { return c1 + (i32)((c2 - c1) * t); } + +static inline u8 pxl8_rgb332_pack(u8 r, u8 g, u8 b) { + return ((r >> 5) << 5) | ((g >> 5) << 2) | (b >> 6); +} + +static inline void pxl8_rgb332_unpack(u8 c, u8* r, u8* g, u8* b) { + u8 ri = (c >> 5) & 0x07; + u8 gi = (c >> 2) & 0x07; + u8 bi = c & 0x03; + *r = (ri << 5) | (ri << 2) | (ri >> 1); + *g = (gi << 5) | (gi << 2) | (gi >> 1); + *b = (bi << 6) | (bi << 4) | (bi << 2) | bi; +} diff --git a/client/src/gfx/pxl8_colormap.c b/client/src/gfx/pxl8_colormap.c new file mode 100644 index 0000000..f967751 --- /dev/null +++ b/client/src/gfx/pxl8_colormap.c @@ -0,0 +1,79 @@ +#include "pxl8_colormap.h" +#include + +static u8 find_closest_color(const u32* palette, u8 target_r, u8 target_g, u8 target_b) { + u8 best_idx = 1; + u32 best_dist = 0xFFFFFFFF; + + u8 dynamic_end = PXL8_DYNAMIC_RANGE_START + PXL8_DYNAMIC_RANGE_COUNT; + + for (u32 i = 1; i < PXL8_FULLBRIGHT_START; i++) { + if (i >= PXL8_DYNAMIC_RANGE_START && i < dynamic_end) { + continue; + } + + u32 c = palette[i]; + u8 pr = (c >> 24) & 0xFF; + u8 pg = (c >> 16) & 0xFF; + u8 pb = (c >> 8) & 0xFF; + + i32 dr = (i32)target_r - (i32)pr; + i32 dg = (i32)target_g - (i32)pg; + i32 db = (i32)target_b - (i32)pb; + u32 dist = (u32)(dr * dr + dg * dg + db * db); + + if (dist < best_dist) { + best_dist = dist; + best_idx = (u8)i; + if (dist == 0) break; + } + } + + return best_idx; +} + +void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette, const pxl8_level_tint* tint) { + if (!cm || !palette) return; + + u8 dark_r, dark_g, dark_b; + if (tint && tint->tint_strength > 0.0f) { + f32 t = tint->tint_strength; + f32 inv = 1.0f - t; + dark_r = (u8)(tint->dark_r * inv + tint->tint_r * t); + dark_g = (u8)(tint->dark_g * inv + tint->tint_g * t); + dark_b = (u8)(tint->dark_b * inv + tint->tint_b * t); + } else if (tint) { + dark_r = tint->dark_r; + dark_g = tint->dark_g; + dark_b = tint->dark_b; + } else { + dark_r = dark_g = dark_b = 0; + } + + for (u32 light = 0; light < PXL8_LIGHT_LEVELS; light++) { + f32 brightness = (f32)light / (f32)(PXL8_LIGHT_LEVELS - 1); + + for (u32 pal_idx = 0; pal_idx < 256; pal_idx++) { + u8 result_idx; + + if (pal_idx == PXL8_TRANSPARENT) { + result_idx = PXL8_TRANSPARENT; + } else if (pal_idx >= PXL8_FULLBRIGHT_START) { + result_idx = (u8)pal_idx; + } else { + u32 c = palette[pal_idx]; + u8 r = (c >> 24) & 0xFF; + u8 g = (c >> 16) & 0xFF; + u8 b = (c >> 8) & 0xFF; + + u8 target_r = (u8)(dark_r + (r - dark_r) * brightness); + u8 target_g = (u8)(dark_g + (g - dark_g) * brightness); + u8 target_b = (u8)(dark_b + (b - dark_b) * brightness); + + result_idx = find_closest_color(palette, target_r, target_g, target_b); + } + + cm->table[light * 256 + pal_idx] = result_idx; + } + } +} diff --git a/client/src/gfx/pxl8_colormap.h b/client/src/gfx/pxl8_colormap.h new file mode 100644 index 0000000..6e090a0 --- /dev/null +++ b/client/src/gfx/pxl8_colormap.h @@ -0,0 +1,43 @@ +#pragma once + +#include "pxl8_dither.h" +#include "pxl8_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PXL8_LIGHT_LEVELS 64 +#define PXL8_COLORMAP_SIZE (256 * PXL8_LIGHT_LEVELS) + +#define PXL8_FULLBRIGHT_START 240 +#define PXL8_TRANSPARENT 0 +#define PXL8_DYNAMIC_RANGE_START 144 +#define PXL8_DYNAMIC_RANGE_COUNT 16 + +typedef struct { + u8 table[PXL8_COLORMAP_SIZE]; +} pxl8_colormap; + +typedef struct { + u8 dark_r, dark_g, dark_b; + u8 tint_r, tint_g, tint_b; + f32 tint_strength; +} pxl8_level_tint; + +void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette, const pxl8_level_tint* tint); + +static inline u8 pxl8_colormap_lookup(const pxl8_colormap* cm, u8 pal_idx, u8 light) { + u32 light_idx = light >> 2; + return cm->table[light_idx * 256 + pal_idx]; +} + +static inline u8 pxl8_colormap_lookup_dithered(const pxl8_colormap* cm, u8 pal_idx, u8 light, u32 x, u32 y) { + u8 dithered = pxl8_dither_light(light, x, y); + u32 light_idx = dithered >> 2; + return cm->table[light_idx * 256 + pal_idx]; +} + +#ifdef __cplusplus +} +#endif diff --git a/client/src/gfx/pxl8_cpu.c b/client/src/gfx/pxl8_cpu.c new file mode 100644 index 0000000..6881c06 --- /dev/null +++ b/client/src/gfx/pxl8_cpu.c @@ -0,0 +1,1075 @@ +#include "pxl8_cpu.h" + +#include +#include + +#include "pxl8_simd.h" + +struct pxl8_cpu_render_target { + u8* framebuffer; + u32 height; + u32 width; + f32* zbuffer; + u32* light_accum; +}; + +#define PXL8_MAX_TARGET_STACK 8 + +struct pxl8_cpu_backend { + pxl8_cpu_render_target* current_target; + pxl8_cpu_render_target* target_stack[PXL8_MAX_TARGET_STACK]; + u32 target_stack_depth; + const pxl8_colormap* colormap; + const u32* palette; + pxl8_3d_frame frame; + pxl8_mat4 mvp; +}; + +static inline void clip_line_2d(i32* x0, i32* y0, i32* x1, i32* y1, i32 w, i32 h, bool* visible) { + const u8 INSIDE = 0, LEFT = 1, RIGHT = 2, BOTTOM = 4, TOP = 8; + + u8 outcode0 = INSIDE, outcode1 = INSIDE; + if (*x0 < 0) outcode0 |= LEFT; + else if (*x0 >= w) outcode0 |= RIGHT; + if (*y0 < 0) outcode0 |= TOP; + else if (*y0 >= h) outcode0 |= BOTTOM; + + if (*x1 < 0) outcode1 |= LEFT; + else if (*x1 >= w) outcode1 |= RIGHT; + if (*y1 < 0) outcode1 |= TOP; + else if (*y1 >= h) outcode1 |= BOTTOM; + + *visible = true; + while (true) { + if ((outcode0 | outcode1) == 0) return; + if ((outcode0 & outcode1) != 0) { *visible = false; return; } + + u8 out = outcode0 ? outcode0 : outcode1; + i32 x, y; + i64 dx = *x1 - *x0, dy = *y1 - *y0; + + if (out & TOP) { x = *x0 + (i32)(dx * (0 - *y0) / dy); y = 0; } + else if (out & BOTTOM) { x = *x0 + (i32)(dx * (h - 1 - *y0) / dy); y = h - 1; } + else if (out & RIGHT) { y = *y0 + (i32)(dy * (w - 1 - *x0) / dx); x = w - 1; } + else { y = *y0 + (i32)(dy * (0 - *x0) / dx); x = 0; } + + if (out == outcode0) { + *x0 = x; *y0 = y; + outcode0 = INSIDE; + if (*x0 < 0) outcode0 |= LEFT; + else if (*x0 >= w) outcode0 |= RIGHT; + if (*y0 < 0) outcode0 |= TOP; + else if (*y0 >= h) outcode0 |= BOTTOM; + } else { + *x1 = x; *y1 = y; + outcode1 = INSIDE; + if (*x1 < 0) outcode1 |= LEFT; + else if (*x1 >= w) outcode1 |= RIGHT; + if (*y1 < 0) outcode1 |= TOP; + else if (*y1 >= h) outcode1 |= BOTTOM; + } + } +} + +pxl8_cpu_backend* pxl8_cpu_create(u32 width, u32 height) { + pxl8_cpu_backend* cpu = calloc(1, sizeof(pxl8_cpu_backend)); + if (!cpu) return NULL; + + pxl8_cpu_render_target_desc desc = { + .width = width, + .height = height, + .with_depth = true, + .with_lighting = true, + }; + pxl8_cpu_render_target* base_target = pxl8_cpu_create_render_target(&desc); + if (!base_target) { + free(cpu); + return NULL; + } + + cpu->target_stack[0] = base_target; + cpu->target_stack_depth = 1; + cpu->current_target = base_target; + return cpu; +} + +void pxl8_cpu_destroy(pxl8_cpu_backend* cpu) { + if (!cpu) return; + for (u32 i = 0; i < cpu->target_stack_depth; i++) { + pxl8_cpu_destroy_render_target(cpu->target_stack[i]); + } + free(cpu); +} + +void pxl8_cpu_begin_frame(pxl8_cpu_backend* cpu, const pxl8_3d_frame* frame) { + if (!cpu || !frame) return; + cpu->frame = *frame; + cpu->mvp = pxl8_mat4_mul(frame->projection, frame->view); +} + +void pxl8_cpu_end_frame(pxl8_cpu_backend* cpu) { + (void)cpu; +} + +void pxl8_cpu_clear(pxl8_cpu_backend* cpu, u8 color) { + if (!cpu || !cpu->current_target || !cpu->current_target->framebuffer) return; + pxl8_cpu_render_target* render_target = cpu->current_target; + memset(render_target->framebuffer, color, render_target->width * render_target->height); +} + +void pxl8_cpu_clear_depth(pxl8_cpu_backend* cpu) { + if (!cpu || !cpu->current_target) return; + pxl8_cpu_render_target* render_target = cpu->current_target; + if (render_target->zbuffer) { + memset(render_target->zbuffer, 0x7F, render_target->width * render_target->height * sizeof(f32)); + } + if (render_target->light_accum) { + memset(render_target->light_accum, 0, render_target->width * render_target->height * sizeof(u32)); + } +} + +void pxl8_cpu_set_colormap(pxl8_cpu_backend* cpu, const pxl8_colormap* cm) { + if (cpu) cpu->colormap = cm; +} + +void pxl8_cpu_set_palette(pxl8_cpu_backend* cpu, const u32* palette) { + if (cpu) cpu->palette = palette; +} + +void pxl8_cpu_draw_pixel(pxl8_cpu_backend* cpu, i32 x, i32 y, u8 color) { + if (!cpu || !cpu->current_target || !cpu->current_target->framebuffer) return; + pxl8_cpu_render_target* render_target = cpu->current_target; + if (x < 0 || x >= (i32)render_target->width || y < 0 || y >= (i32)render_target->height) return; + render_target->framebuffer[y * render_target->width + x] = color; +} + +u8 pxl8_cpu_get_pixel(pxl8_cpu_backend* cpu, i32 x, i32 y) { + if (!cpu || !cpu->current_target || !cpu->current_target->framebuffer) return 0; + pxl8_cpu_render_target* render_target = cpu->current_target; + if (x < 0 || x >= (i32)render_target->width || y < 0 || y >= (i32)render_target->height) return 0; + return render_target->framebuffer[y * render_target->width + x]; +} + +void pxl8_cpu_draw_line_2d(pxl8_cpu_backend* cpu, i32 x0, i32 y0, i32 x1, i32 y1, u8 color) { + if (!cpu || !cpu->current_target || !cpu->current_target->framebuffer) return; + pxl8_cpu_render_target* render_target = cpu->current_target; + + bool visible; + clip_line_2d(&x0, &y0, &x1, &y1, (i32)render_target->width, (i32)render_target->height, &visible); + if (!visible) return; + + i32 dx = x1 - x0; + i32 dy = y1 - y0; + i32 sx = dx > 0 ? 1 : -1; + i32 sy = dy > 0 ? 1 : -1; + dx = dx > 0 ? dx : -dx; + dy = dy > 0 ? dy : -dy; + + i32 err = (dx > dy ? dx : -dy) / 2; + u8* fb = render_target->framebuffer; + u32 w = render_target->width; + + while (true) { + fb[(u32)y0 * w + (u32)x0] = color; + if (x0 == x1 && y0 == y1) break; + i32 e2 = err; + if (e2 > -dx) { err -= dy; x0 += sx; } + if (e2 < dy) { err += dx; y0 += sy; } + } +} + +void pxl8_cpu_draw_rect(pxl8_cpu_backend* cpu, i32 x, i32 y, i32 w, i32 h, u8 color) { + if (!cpu) return; + pxl8_cpu_draw_line_2d(cpu, x, y, x + w - 1, y, color); + pxl8_cpu_draw_line_2d(cpu, x + w - 1, y, x + w - 1, y + h - 1, color); + pxl8_cpu_draw_line_2d(cpu, x + w - 1, y + h - 1, x, y + h - 1, color); + pxl8_cpu_draw_line_2d(cpu, x, y + h - 1, x, y, color); +} + +void pxl8_cpu_draw_rect_fill(pxl8_cpu_backend* cpu, i32 x, i32 y, i32 w, i32 h, u8 color) { + if (!cpu || !cpu->current_target || !cpu->current_target->framebuffer) return; + pxl8_cpu_render_target* render_target = cpu->current_target; + + i32 x0 = (x < 0) ? 0 : x; + i32 y0 = (y < 0) ? 0 : y; + i32 x1 = (x + w > (i32)render_target->width) ? (i32)render_target->width : x + w; + i32 y1 = (y + h > (i32)render_target->height) ? (i32)render_target->height : y + h; + + i32 rect_w = x1 - x0; + if (rect_w <= 0 || y1 <= y0) return; + + for (i32 py = y0; py < y1; py++) { + memset(render_target->framebuffer + py * render_target->width + x0, color, rect_w); + } +} + +void pxl8_cpu_draw_circle(pxl8_cpu_backend* cpu, i32 cx, i32 cy, i32 radius, u8 color) { + if (!cpu) return; + + i32 x = radius; + i32 y = 0; + i32 err = 0; + + while (x >= y) { + pxl8_cpu_draw_pixel(cpu, cx + x, cy + y, color); + pxl8_cpu_draw_pixel(cpu, cx + y, cy + x, color); + pxl8_cpu_draw_pixel(cpu, cx - y, cy + x, color); + pxl8_cpu_draw_pixel(cpu, cx - x, cy + y, color); + pxl8_cpu_draw_pixel(cpu, cx - x, cy - y, color); + pxl8_cpu_draw_pixel(cpu, cx - y, cy - x, color); + pxl8_cpu_draw_pixel(cpu, cx + y, cy - x, color); + pxl8_cpu_draw_pixel(cpu, cx + x, cy - y, color); + + if (err <= 0) { + y += 1; + err += 2 * y + 1; + } else { + x -= 1; + err -= 2 * x + 1; + } + } +} + +void pxl8_cpu_draw_circle_fill(pxl8_cpu_backend* cpu, i32 cx, i32 cy, i32 radius, u8 color) { + if (!cpu || !cpu->current_target || !cpu->current_target->framebuffer) return; + pxl8_cpu_render_target* render_target = cpu->current_target; + + i32 x0 = (cx - radius < 0) ? -cx : -radius; + i32 y0 = (cy - radius < 0) ? -cy : -radius; + i32 x1 = (cx + radius >= (i32)render_target->width) ? (i32)render_target->width - cx - 1 : radius; + i32 y1 = (cy + radius >= (i32)render_target->height) ? (i32)render_target->height - cy - 1 : radius; + + for (i32 dy = y0; dy <= y1; dy++) { + for (i32 dx = x0; dx <= x1; dx++) { + if (dx * dx + dy * dy <= radius * radius) { + render_target->framebuffer[(cy + dy) * render_target->width + (cx + dx)] = color; + } + } + } +} + +void pxl8_cpu_draw_line_3d(pxl8_cpu_backend* cpu, pxl8_vec3 v0, pxl8_vec3 v1, u8 color) { + if (!cpu || !cpu->current_target) return; + pxl8_cpu_render_target* render_target = cpu->current_target; + + pxl8_vec4 c0 = pxl8_mat4_mul_vec4(cpu->mvp, (pxl8_vec4){v0.x, v0.y, v0.z, 1.0f}); + pxl8_vec4 c1 = pxl8_mat4_mul_vec4(cpu->mvp, (pxl8_vec4){v1.x, v1.y, v1.z, 1.0f}); + + if (c0.w <= 0.0f || c1.w <= 0.0f) return; + + f32 hw = (f32)render_target->width * 0.5f; + f32 hh = (f32)render_target->height * 0.5f; + + i32 x0 = (i32)(hw + c0.x / c0.w * hw); + i32 y0 = (i32)(hh - c0.y / c0.w * hh); + i32 x1 = (i32)(hw + c1.x / c1.w * hw); + i32 y1 = (i32)(hh - c1.y / c1.w * hh); + + pxl8_cpu_draw_line_2d(cpu, x0, y0, x1, y1, color); +} + +typedef struct { + pxl8_vec4 clip_pos; + pxl8_vec3 world_pos; + pxl8_vec3 normal; + f32 u, v; + u8 color; + u8 light; +} vertex_output; + +static vertex_output lerp_vertex(const vertex_output* a, const vertex_output* b, f32 t) { + vertex_output out; + out.clip_pos.x = a->clip_pos.x + (b->clip_pos.x - a->clip_pos.x) * t; + out.clip_pos.y = a->clip_pos.y + (b->clip_pos.y - a->clip_pos.y) * t; + out.clip_pos.z = a->clip_pos.z + (b->clip_pos.z - a->clip_pos.z) * t; + out.clip_pos.w = a->clip_pos.w + (b->clip_pos.w - a->clip_pos.w) * t; + out.world_pos.x = a->world_pos.x + (b->world_pos.x - a->world_pos.x) * t; + out.world_pos.y = a->world_pos.y + (b->world_pos.y - a->world_pos.y) * t; + out.world_pos.z = a->world_pos.z + (b->world_pos.z - a->world_pos.z) * t; + out.normal.x = a->normal.x + (b->normal.x - a->normal.x) * t; + out.normal.y = a->normal.y + (b->normal.y - a->normal.y) * t; + out.normal.z = a->normal.z + (b->normal.z - a->normal.z) * t; + out.u = a->u + (b->u - a->u) * t; + out.v = a->v + (b->v - a->v) * t; + out.color = t < 0.5f ? a->color : b->color; + out.light = (u8)(a->light + (b->light - a->light) * t); + return out; +} + +static i32 clip_triangle_near( + const vertex_output* v0, const vertex_output* v1, const vertex_output* v2, + f32 near, vertex_output out[6] +) { + bool in0 = v0->clip_pos.w >= near; + bool in1 = v1->clip_pos.w >= near; + bool in2 = v2->clip_pos.w >= near; + i32 count = in0 + in1 + in2; + + if (count == 0) return 0; + if (count == 3) { + out[0] = *v0; out[1] = *v1; out[2] = *v2; + return 3; + } + + if (count == 1) { + const vertex_output *inside, *out_a, *out_b; + if (in0) { inside = v0; out_a = v1; out_b = v2; } + else if (in1) { inside = v1; out_a = v2; out_b = v0; } + else { inside = v2; out_a = v0; out_b = v1; } + + f32 t_a = (near - out_a->clip_pos.w) / (inside->clip_pos.w - out_a->clip_pos.w); + f32 t_b = (near - out_b->clip_pos.w) / (inside->clip_pos.w - out_b->clip_pos.w); + + out[0] = *inside; + out[1] = lerp_vertex(out_a, inside, t_a); + out[2] = lerp_vertex(out_b, inside, t_b); + return 3; + } + + const vertex_output *outside, *in_a, *in_b; + if (!in0) { outside = v0; in_a = v1; in_b = v2; } + else if (!in1) { outside = v1; in_a = v2; in_b = v0; } + else { outside = v2; in_a = v0; in_b = v1; } + + f32 t_a = (near - outside->clip_pos.w) / (in_a->clip_pos.w - outside->clip_pos.w); + f32 t_b = (near - outside->clip_pos.w) / (in_b->clip_pos.w - outside->clip_pos.w); + + vertex_output new_a = lerp_vertex(outside, in_a, t_a); + vertex_output new_b = lerp_vertex(outside, in_b, t_b); + + out[0] = *in_a; out[1] = *in_b; out[2] = new_b; + out[3] = *in_a; out[4] = new_b; out[5] = new_a; + return 6; +} + +typedef struct { + i32 x0, y0, x1, y1, x2, y2; + f32 z0, z1, z2; + f32 w0_recip, w1_recip, w2_recip; + f32 u0_w, v0_w, u1_w, v1_w, u2_w, v2_w; + f32 l0_w, l1_w, l2_w; + i32 y_start, y_end, total_height; + f32 inv_total; + u32 target_width, target_height; +} tri_setup; + +static bool setup_triangle( + tri_setup* setup, + const vertex_output* vo0, const vertex_output* vo1, const vertex_output* vo2, + u32 width, u32 height, bool double_sided +) { + f32 hw = (f32)width * 0.5f; + f32 hh = (f32)height * 0.5f; + + setup->x0 = (i32)(hw + vo0->clip_pos.x / vo0->clip_pos.w * hw); + setup->y0 = (i32)(hh - vo0->clip_pos.y / vo0->clip_pos.w * hh); + setup->z0 = vo0->clip_pos.z / vo0->clip_pos.w; + + setup->x1 = (i32)(hw + vo1->clip_pos.x / vo1->clip_pos.w * hw); + setup->y1 = (i32)(hh - vo1->clip_pos.y / vo1->clip_pos.w * hh); + setup->z1 = vo1->clip_pos.z / vo1->clip_pos.w; + + setup->x2 = (i32)(hw + vo2->clip_pos.x / vo2->clip_pos.w * hw); + setup->y2 = (i32)(hh - vo2->clip_pos.y / vo2->clip_pos.w * hh); + setup->z2 = vo2->clip_pos.z / vo2->clip_pos.w; + + i32 cross = (setup->x1 - setup->x0) * (setup->y2 - setup->y0) - + (setup->y1 - setup->y0) * (setup->x2 - setup->x0); + if (!double_sided && cross >= 0) return false; + + const vertex_output* sorted[3] = {vo0, vo1, vo2}; + + if (setup->y0 > setup->y1) { + i32 t = setup->x0; setup->x0 = setup->x1; setup->x1 = t; + t = setup->y0; setup->y0 = setup->y1; setup->y1 = t; + f32 tz = setup->z0; setup->z0 = setup->z1; setup->z1 = tz; + const vertex_output* tv = sorted[0]; sorted[0] = sorted[1]; sorted[1] = tv; + } + if (setup->y0 > setup->y2) { + i32 t = setup->x0; setup->x0 = setup->x2; setup->x2 = t; + t = setup->y0; setup->y0 = setup->y2; setup->y2 = t; + f32 tz = setup->z0; setup->z0 = setup->z2; setup->z2 = tz; + const vertex_output* tv = sorted[0]; sorted[0] = sorted[2]; sorted[2] = tv; + } + if (setup->y1 > setup->y2) { + i32 t = setup->x1; setup->x1 = setup->x2; setup->x2 = t; + t = setup->y1; setup->y1 = setup->y2; setup->y2 = t; + f32 tz = setup->z1; setup->z1 = setup->z2; setup->z2 = tz; + const vertex_output* tv = sorted[1]; sorted[1] = sorted[2]; sorted[2] = tv; + } + + setup->total_height = setup->y2 - setup->y0; + if (setup->total_height == 0) return false; + + setup->y_start = setup->y0 < 0 ? 0 : setup->y0; + setup->y_end = setup->y2 >= (i32)height ? (i32)height - 1 : setup->y2; + + setup->w0_recip = 1.0f / sorted[0]->clip_pos.w; + setup->w1_recip = 1.0f / sorted[1]->clip_pos.w; + setup->w2_recip = 1.0f / sorted[2]->clip_pos.w; + + setup->u0_w = sorted[0]->u * setup->w0_recip; + setup->v0_w = sorted[0]->v * setup->w0_recip; + setup->u1_w = sorted[1]->u * setup->w1_recip; + setup->v1_w = sorted[1]->v * setup->w1_recip; + setup->u2_w = sorted[2]->u * setup->w2_recip; + setup->v2_w = sorted[2]->v * setup->w2_recip; + + setup->l0_w = sorted[0]->light * setup->w0_recip; + setup->l1_w = sorted[1]->light * setup->w1_recip; + setup->l2_w = sorted[2]->light * setup->w2_recip; + + setup->inv_total = 1.0f / (f32)setup->total_height; + setup->target_width = width; + setup->target_height = height; + + return true; +} + +static void rasterize_triangle_opaque( + pxl8_cpu_backend* cpu, + const tri_setup* setup, + const pxl8_atlas* textures, u32 texture_id, bool dither +) { + pxl8_cpu_render_target* render_target = cpu->current_target; + const pxl8_atlas_entry* tex_entry = textures ? pxl8_atlas_get_entry(textures, texture_id) : NULL; + u32 atlas_width = textures ? pxl8_atlas_get_width(textures) : 1; + f32 tex_w = tex_entry ? (f32)tex_entry->w : 1.0f; + f32 tex_h = tex_entry ? (f32)tex_entry->h : 1.0f; + i32 tex_stride = (i32)atlas_width; + i32 tex_mask_w = tex_entry ? (i32)tex_entry->w - 1 : 0; + i32 tex_mask_h = tex_entry ? (i32)tex_entry->h - 1 : 0; + u32 tex_base = tex_entry ? (u32)tex_entry->y * atlas_width + (u32)tex_entry->x : 0; + const u8* tex_pixels = textures ? pxl8_atlas_get_pixels(textures) : NULL; + + for (i32 y = setup->y_start; y <= setup->y_end; y++) { + bool second_half = y > setup->y1 || setup->y1 == setup->y0; + i32 segment_height = second_half ? setup->y2 - setup->y1 : setup->y1 - setup->y0; + if (segment_height == 0) continue; + + f32 alpha = (f32)(y - setup->y0) * setup->inv_total; + f32 beta = (f32)(y - (second_half ? setup->y1 : setup->y0)) / (f32)segment_height; + + f32 ax = (f32)setup->x0 + (f32)(setup->x2 - setup->x0) * alpha; + f32 az = setup->z0 + (setup->z2 - setup->z0) * alpha; + f32 a_wr = setup->w0_recip + (setup->w2_recip - setup->w0_recip) * alpha; + f32 a_uw = setup->u0_w + (setup->u2_w - setup->u0_w) * alpha; + f32 a_vw = setup->v0_w + (setup->v2_w - setup->v0_w) * alpha; + f32 a_lw = setup->l0_w + (setup->l2_w - setup->l0_w) * alpha; + + f32 bx, bz, b_wr, b_uw, b_vw, b_lw; + if (second_half) { + bx = (f32)setup->x1 + (f32)(setup->x2 - setup->x1) * beta; + bz = setup->z1 + (setup->z2 - setup->z1) * beta; + b_wr = setup->w1_recip + (setup->w2_recip - setup->w1_recip) * beta; + b_uw = setup->u1_w + (setup->u2_w - setup->u1_w) * beta; + b_vw = setup->v1_w + (setup->v2_w - setup->v1_w) * beta; + b_lw = setup->l1_w + (setup->l2_w - setup->l1_w) * beta; + } else { + bx = (f32)setup->x0 + (f32)(setup->x1 - setup->x0) * beta; + bz = setup->z0 + (setup->z1 - setup->z0) * beta; + b_wr = setup->w0_recip + (setup->w1_recip - setup->w0_recip) * beta; + b_uw = setup->u0_w + (setup->u1_w - setup->u0_w) * beta; + b_vw = setup->v0_w + (setup->v1_w - setup->v0_w) * beta; + b_lw = setup->l0_w + (setup->l1_w - setup->l0_w) * beta; + } + + f32 x_start_f, x_end_f, z_start, z_end; + f32 wr_start, wr_end, uw_start, uw_end, vw_start, vw_end, lw_start, lw_end; + + if (ax <= bx) { + x_start_f = ax; x_end_f = bx; z_start = az; z_end = bz; + wr_start = a_wr; wr_end = b_wr; + uw_start = a_uw; uw_end = b_uw; + vw_start = a_vw; vw_end = b_vw; + lw_start = a_lw; lw_end = b_lw; + } else { + x_start_f = bx; x_end_f = ax; z_start = bz; z_end = az; + wr_start = b_wr; wr_end = a_wr; + uw_start = b_uw; uw_end = a_uw; + vw_start = b_vw; vw_end = a_vw; + lw_start = b_lw; lw_end = a_lw; + } + + i32 x_start_orig = (i32)(x_start_f + 0.5f); + i32 x_end_orig = (i32)(x_end_f + 0.5f) - 1; + i32 x_start = x_start_orig < 0 ? 0 : x_start_orig; + i32 x_end = x_end_orig >= (i32)setup->target_width ? (i32)setup->target_width - 1 : x_end_orig; + if (x_start > x_end) continue; + + i32 width_orig = x_end_orig - x_start_orig; + if (width_orig < 1) width_orig = 1; + f32 inv_width = 1.0f / (f32)width_orig; + + f32 dz = (z_end - z_start) * inv_width; + f32 dwr = (wr_end - wr_start) * inv_width; + f32 duw = (uw_end - uw_start) * inv_width; + f32 dvw = (vw_end - vw_start) * inv_width; + f32 d_lw = (lw_end - lw_start) * inv_width; + + f32 skip = (f32)(x_start - x_start_orig); + f32 z = z_start + dz * skip; + f32 wr = wr_start + dwr * skip; + f32 uw = uw_start + duw * skip; + f32 vw = vw_start + dvw * skip; + f32 lw = lw_start + d_lw * skip; + + u32 row_start = (u32)y * render_target->width; + u8* prow = render_target->framebuffer + row_start; + f32* zrow = render_target->zbuffer + row_start; + + const i32 SUBDIV = 32; + i32 x = x_start; + + while (x <= x_end) { + i32 span_end = x + SUBDIV - 1; + if (span_end > x_end) span_end = x_end; + i32 span_len = span_end - x + 1; + f32 steps = (f32)span_len; + + f32 pw_start = 1.0f / wr; + f32 pw_end = 1.0f / (wr + dwr * steps); + + f32 u_start = uw * pw_start; + f32 v_start = vw * pw_start; + f32 u_end = (uw + duw * steps) * pw_end; + f32 v_end = (vw + dvw * steps) * pw_end; + f32 l_start = lw * pw_start; + f32 l_end = (lw + d_lw * steps) * pw_end; + if (l_start > 255.0f) l_start = 255.0f; + if (l_end > 255.0f) l_end = 255.0f; + + f32 inv_span = span_len > 1 ? 1.0f / (f32)(span_len - 1) : 0.0f; + f32 du = (u_end - u_start) * inv_span; + f32 dv = (v_end - v_start) * inv_span; + f32 dl = (l_end - l_start) * inv_span; + + i32 u_fixed = (i32)(u_start * tex_w * 65536.0f); + i32 v_fixed = (i32)(v_start * tex_h * 65536.0f); + i32 du_fixed = (i32)(du * tex_w * 65536.0f); + i32 dv_fixed = (i32)(dv * tex_h * 65536.0f); + + f32 l_a = l_start; + f32 z_a = z; + + for (i32 px = x; px <= span_end; px++) { + if (z_a <= zrow[px]) { + i32 tx = (u_fixed >> 16) & tex_mask_w; + i32 ty = (v_fixed >> 16) & tex_mask_h; + u8 tex_idx = tex_pixels ? tex_pixels[tex_base + (u32)ty * (u32)tex_stride + (u32)tx] : 1; + + if (tex_idx != 0) { + u8 light = (u8)l_a; + if (dither) { + light = pxl8_dither_light(light, (u32)px, (u32)y); + } + u8 pal_idx = cpu->colormap ? pxl8_colormap_lookup(cpu->colormap, tex_idx, light) : tex_idx; + prow[px] = pal_idx; + zrow[px] = z_a; + } + } + + u_fixed += du_fixed; + v_fixed += dv_fixed; + l_a += dl; + z_a += dz; + } + + wr += dwr * steps; + uw += duw * steps; + vw += dvw * steps; + lw += d_lw * steps; + z += dz * steps; + x = span_end + 1; + } + } +} + +static void rasterize_triangle_passthrough( + pxl8_cpu_backend* cpu, + const tri_setup* setup, + const vertex_output* vo0 +) { + pxl8_cpu_render_target* render_target = cpu->current_target; + u8 color = vo0->color; + + for (i32 y = setup->y_start; y <= setup->y_end; y++) { + bool second_half = y > setup->y1 || setup->y1 == setup->y0; + i32 segment_height = second_half ? setup->y2 - setup->y1 : setup->y1 - setup->y0; + if (segment_height == 0) continue; + + f32 alpha = (f32)(y - setup->y0) * setup->inv_total; + f32 beta = (f32)(y - (second_half ? setup->y1 : setup->y0)) / (f32)segment_height; + + f32 ax = (f32)setup->x0 + (f32)(setup->x2 - setup->x0) * alpha; + f32 az = setup->z0 + (setup->z2 - setup->z0) * alpha; + f32 bx, bz; + if (second_half) { + bx = (f32)setup->x1 + (f32)(setup->x2 - setup->x1) * beta; + bz = setup->z1 + (setup->z2 - setup->z1) * beta; + } else { + bx = (f32)setup->x0 + (f32)(setup->x1 - setup->x0) * beta; + bz = setup->z0 + (setup->z1 - setup->z0) * beta; + } + + f32 x_start_f, x_end_f, z_start, z_end; + if (ax <= bx) { + x_start_f = ax; x_end_f = bx; z_start = az; z_end = bz; + } else { + x_start_f = bx; x_end_f = ax; z_start = bz; z_end = az; + } + + i32 x_start_orig = (i32)(x_start_f + 0.5f); + i32 x_end_orig = (i32)(x_end_f + 0.5f) - 1; + i32 x_start = x_start_orig < 0 ? 0 : x_start_orig; + i32 x_end = x_end_orig >= (i32)setup->target_width ? (i32)setup->target_width - 1 : x_end_orig; + if (x_start > x_end) continue; + + i32 width_orig = x_end_orig - x_start_orig; + if (width_orig < 1) width_orig = 1; + f32 inv_width = 1.0f / (f32)width_orig; + + f32 dz = (z_end - z_start) * inv_width; + f32 skip = (f32)(x_start - x_start_orig); + f32 z = z_start + dz * skip; + + u32 row_start = (u32)y * render_target->width; + u8* prow = render_target->framebuffer + row_start; + f32* zrow = render_target->zbuffer + row_start; + + for (i32 px = x_start; px <= x_end; px++) { + if (z <= zrow[px]) { + prow[px] = color; + zrow[px] = z; + } + z += dz; + } + } +} + +static void rasterize_triangle_alpha( + pxl8_cpu_backend* cpu, + const tri_setup* setup, + const pxl8_atlas* textures, u32 texture_id, u8 mat_alpha, bool dither +) { + pxl8_cpu_render_target* render_target = cpu->current_target; + const pxl8_atlas_entry* tex_entry = textures ? pxl8_atlas_get_entry(textures, texture_id) : NULL; + u32 atlas_width = textures ? pxl8_atlas_get_width(textures) : 1; + f32 tex_w = tex_entry ? (f32)tex_entry->w : 1.0f; + f32 tex_h = tex_entry ? (f32)tex_entry->h : 1.0f; + i32 tex_stride = (i32)atlas_width; + i32 tex_mask_w = tex_entry ? (i32)tex_entry->w - 1 : 0; + i32 tex_mask_h = tex_entry ? (i32)tex_entry->h - 1 : 0; + u32 tex_base = tex_entry ? (u32)tex_entry->y * atlas_width + (u32)tex_entry->x : 0; + const u8* tex_pixels = textures ? pxl8_atlas_get_pixels(textures) : NULL; + + for (i32 y = setup->y_start; y <= setup->y_end; y++) { + bool second_half = y > setup->y1 || setup->y1 == setup->y0; + i32 segment_height = second_half ? setup->y2 - setup->y1 : setup->y1 - setup->y0; + if (segment_height == 0) continue; + + f32 alpha = (f32)(y - setup->y0) * setup->inv_total; + f32 beta = (f32)(y - (second_half ? setup->y1 : setup->y0)) / (f32)segment_height; + + f32 ax = (f32)setup->x0 + (f32)(setup->x2 - setup->x0) * alpha; + f32 az = setup->z0 + (setup->z2 - setup->z0) * alpha; + f32 a_wr = setup->w0_recip + (setup->w2_recip - setup->w0_recip) * alpha; + f32 a_uw = setup->u0_w + (setup->u2_w - setup->u0_w) * alpha; + f32 a_vw = setup->v0_w + (setup->v2_w - setup->v0_w) * alpha; + f32 a_lw = setup->l0_w + (setup->l2_w - setup->l0_w) * alpha; + + f32 bx, bz, b_wr, b_uw, b_vw, b_lw; + if (second_half) { + bx = (f32)setup->x1 + (f32)(setup->x2 - setup->x1) * beta; + bz = setup->z1 + (setup->z2 - setup->z1) * beta; + b_wr = setup->w1_recip + (setup->w2_recip - setup->w1_recip) * beta; + b_uw = setup->u1_w + (setup->u2_w - setup->u1_w) * beta; + b_vw = setup->v1_w + (setup->v2_w - setup->v1_w) * beta; + b_lw = setup->l1_w + (setup->l2_w - setup->l1_w) * beta; + } else { + bx = (f32)setup->x0 + (f32)(setup->x1 - setup->x0) * beta; + bz = setup->z0 + (setup->z1 - setup->z0) * beta; + b_wr = setup->w0_recip + (setup->w1_recip - setup->w0_recip) * beta; + b_uw = setup->u0_w + (setup->u1_w - setup->u0_w) * beta; + b_vw = setup->v0_w + (setup->v1_w - setup->v0_w) * beta; + b_lw = setup->l0_w + (setup->l1_w - setup->l0_w) * beta; + } + + f32 x_start_f, x_end_f, z_start, z_end; + f32 wr_start, wr_end, uw_start, uw_end, vw_start, vw_end, lw_start, lw_end; + + if (ax <= bx) { + x_start_f = ax; x_end_f = bx; z_start = az; z_end = bz; + wr_start = a_wr; wr_end = b_wr; + uw_start = a_uw; uw_end = b_uw; + vw_start = a_vw; vw_end = b_vw; + lw_start = a_lw; lw_end = b_lw; + } else { + x_start_f = bx; x_end_f = ax; z_start = bz; z_end = az; + wr_start = b_wr; wr_end = a_wr; + uw_start = b_uw; uw_end = a_uw; + vw_start = b_vw; vw_end = a_vw; + lw_start = b_lw; lw_end = a_lw; + } + + i32 x_start_orig = (i32)(x_start_f + 0.5f); + i32 x_end_orig = (i32)(x_end_f + 0.5f) - 1; + i32 x_start = x_start_orig < 0 ? 0 : x_start_orig; + i32 x_end = x_end_orig >= (i32)setup->target_width ? (i32)setup->target_width - 1 : x_end_orig; + if (x_start > x_end) continue; + + i32 width_orig = x_end_orig - x_start_orig; + if (width_orig < 1) width_orig = 1; + f32 inv_width = 1.0f / (f32)width_orig; + + f32 dz = (z_end - z_start) * inv_width; + f32 dwr = (wr_end - wr_start) * inv_width; + f32 duw = (uw_end - uw_start) * inv_width; + f32 dvw = (vw_end - vw_start) * inv_width; + f32 d_lw = (lw_end - lw_start) * inv_width; + + f32 skip = (f32)(x_start - x_start_orig); + f32 z = z_start + dz * skip; + f32 wr = wr_start + dwr * skip; + f32 uw = uw_start + duw * skip; + f32 vw = vw_start + dvw * skip; + f32 lw = lw_start + d_lw * skip; + + u32 row_start = (u32)y * render_target->width; + u8* prow = render_target->framebuffer + row_start; + f32* zrow = render_target->zbuffer + row_start; + + const i32 SUBDIV = 32; + i32 x = x_start; + + while (x <= x_end) { + i32 span_end = x + SUBDIV - 1; + if (span_end > x_end) span_end = x_end; + i32 span_len = span_end - x + 1; + f32 steps = (f32)span_len; + + f32 pw_start = 1.0f / wr; + f32 pw_end = 1.0f / (wr + dwr * steps); + + f32 u_start = uw * pw_start; + f32 v_start = vw * pw_start; + f32 u_end = (uw + duw * steps) * pw_end; + f32 v_end = (vw + dvw * steps) * pw_end; + f32 l_start = lw * pw_start; + f32 l_end = (lw + d_lw * steps) * pw_end; + if (l_start > 255.0f) l_start = 255.0f; + if (l_end > 255.0f) l_end = 255.0f; + + f32 inv_span = span_len > 1 ? 1.0f / (f32)(span_len - 1) : 0.0f; + f32 du = (u_end - u_start) * inv_span; + f32 dv = (v_end - v_start) * inv_span; + f32 dl = (l_end - l_start) * inv_span; + + i32 u_fixed = (i32)(u_start * tex_w * 65536.0f); + i32 v_fixed = (i32)(v_start * tex_h * 65536.0f); + i32 du_fixed = (i32)(du * tex_w * 65536.0f); + i32 dv_fixed = (i32)(dv * tex_h * 65536.0f); + + f32 l_a = l_start; + f32 z_a = z; + + for (i32 px = x; px <= span_end; px++) { + i32 tx = (u_fixed >> 16) & tex_mask_w; + i32 ty = (v_fixed >> 16) & tex_mask_h; + u8 tex_idx = tex_pixels ? tex_pixels[tex_base + (u32)ty * (u32)tex_stride + (u32)tx] : 1; + + if (tex_idx != 0) { + u8 light = (u8)l_a; + if (dither) { + light = pxl8_dither_light(light, (u32)px, (u32)y); + } + u8 src_idx = cpu->colormap ? pxl8_colormap_lookup(cpu->colormap, tex_idx, light) : tex_idx; + + if (mat_alpha >= 128) { + prow[px] = src_idx; + if (z_a <= zrow[px]) zrow[px] = z_a; + } + } + + u_fixed += du_fixed; + v_fixed += dv_fixed; + l_a += dl; + z_a += dz; + } + + wr += dwr * steps; + uw += duw * steps; + vw += dvw * steps; + lw += d_lw * steps; + z += dz * steps; + x = span_end + 1; + } + } +} + +static void dispatch_triangle( + pxl8_cpu_backend* cpu, + const vertex_output* vo0, const vertex_output* vo1, const vertex_output* vo2, + const pxl8_atlas* textures, const pxl8_material* material +) { + pxl8_cpu_render_target* render_target = cpu->current_target; + tri_setup setup; + if (!setup_triangle(&setup, vo0, vo1, vo2, render_target->width, render_target->height, material->double_sided)) { + return; + } + + bool alpha_blend = material->alpha < 255; + bool passthrough = material->vertex_color_passthrough; + + if (alpha_blend) { + rasterize_triangle_alpha(cpu, &setup, textures, material->texture_id, material->alpha, material->dither); + } else if (passthrough) { + rasterize_triangle_passthrough(cpu, &setup, vo0); + } else { + rasterize_triangle_opaque(cpu, &setup, textures, material->texture_id, material->dither); + } +} + +void pxl8_cpu_draw_mesh( + pxl8_cpu_backend* cpu, + const pxl8_mesh* mesh, + pxl8_mat4 model, + pxl8_material material, + const pxl8_atlas* textures +) { + if (!cpu || !mesh || mesh->index_count < 3 || !cpu->current_target) return; + + pxl8_mat4 mv = pxl8_mat4_mul(cpu->frame.view, model); + pxl8_mat4 mvp = pxl8_mat4_mul(cpu->frame.projection, mv); + + f32 near = cpu->frame.near_clip > 0.0f ? cpu->frame.near_clip : 0.1f; + + for (u32 i = 0; i < mesh->index_count; i += 3) { + u16 i0 = mesh->indices[i]; + u16 i1 = mesh->indices[i + 1]; + u16 i2 = mesh->indices[i + 2]; + + const pxl8_vertex* v0 = &mesh->vertices[i0]; + const pxl8_vertex* v1 = &mesh->vertices[i1]; + const pxl8_vertex* v2 = &mesh->vertices[i2]; + + vertex_output vo0, vo1, vo2; + + pxl8_vec4 p0 = {v0->position.x, v0->position.y, v0->position.z, 1.0f}; + pxl8_vec4 p1 = {v1->position.x, v1->position.y, v1->position.z, 1.0f}; + pxl8_vec4 p2 = {v2->position.x, v2->position.y, v2->position.z, 1.0f}; + + vo0.clip_pos = pxl8_mat4_mul_vec4(mvp, p0); + vo1.clip_pos = pxl8_mat4_mul_vec4(mvp, p1); + vo2.clip_pos = pxl8_mat4_mul_vec4(mvp, p2); + + pxl8_vec4 w0 = pxl8_mat4_mul_vec4(model, p0); + pxl8_vec4 w1 = pxl8_mat4_mul_vec4(model, p1); + pxl8_vec4 w2 = pxl8_mat4_mul_vec4(model, p2); + vo0.world_pos = (pxl8_vec3){w0.x, w0.y, w0.z}; + vo1.world_pos = (pxl8_vec3){w1.x, w1.y, w1.z}; + vo2.world_pos = (pxl8_vec3){w2.x, w2.y, w2.z}; + + vo0.normal = v0->normal; + vo1.normal = v1->normal; + vo2.normal = v2->normal; + + vo0.u = v0->u; vo0.v = v0->v; + vo1.u = v1->u; vo1.v = v1->v; + vo2.u = v2->u; vo2.v = v2->v; + + vo0.color = v0->color; + vo1.color = v1->color; + vo2.color = v2->color; + + vo0.light = material.dynamic_lighting ? v0->light : 255; + vo1.light = material.dynamic_lighting ? v1->light : 255; + vo2.light = material.dynamic_lighting ? v2->light : 255; + + vertex_output clipped[6]; + i32 clipped_count = clip_triangle_near(&vo0, &vo1, &vo2, near, clipped); + + for (i32 t = 0; t < clipped_count; t += 3) { + dispatch_triangle(cpu, &clipped[t], &clipped[t+1], &clipped[t+2], textures, &material); + } + } +} + +void pxl8_cpu_draw_mesh_wireframe( + pxl8_cpu_backend* cpu, + const pxl8_mesh* mesh, + pxl8_mat4 model, + u8 color +) { + if (!cpu || !cpu->current_target || !mesh || mesh->index_count < 3) return; + pxl8_cpu_render_target* render_target = cpu->current_target; + + pxl8_mat4 mvp = pxl8_mat4_mul(cpu->frame.projection, pxl8_mat4_mul(cpu->frame.view, model)); + + for (u32 i = 0; i < mesh->index_count; i += 3) { + const pxl8_vertex* v0 = &mesh->vertices[mesh->indices[i]]; + const pxl8_vertex* v1 = &mesh->vertices[mesh->indices[i + 1]]; + const pxl8_vertex* v2 = &mesh->vertices[mesh->indices[i + 2]]; + + pxl8_vec4 c0 = pxl8_mat4_mul_vec4(mvp, (pxl8_vec4){v0->position.x, v0->position.y, v0->position.z, 1.0f}); + pxl8_vec4 c1 = pxl8_mat4_mul_vec4(mvp, (pxl8_vec4){v1->position.x, v1->position.y, v1->position.z, 1.0f}); + pxl8_vec4 c2 = pxl8_mat4_mul_vec4(mvp, (pxl8_vec4){v2->position.x, v2->position.y, v2->position.z, 1.0f}); + + if (c0.w <= 0.0f || c1.w <= 0.0f || c2.w <= 0.0f) continue; + + f32 hw = (f32)render_target->width * 0.5f; + f32 hh = (f32)render_target->height * 0.5f; + + i32 x0 = (i32)(hw + c0.x / c0.w * hw); + i32 y0 = (i32)(hh - c0.y / c0.w * hh); + i32 x1 = (i32)(hw + c1.x / c1.w * hw); + i32 y1 = (i32)(hh - c1.y / c1.w * hh); + i32 x2 = (i32)(hw + c2.x / c2.w * hw); + i32 y2 = (i32)(hh - c2.y / c2.w * hh); + + pxl8_cpu_draw_line_2d(cpu, x0, y0, x1, y1, color); + pxl8_cpu_draw_line_2d(cpu, x1, y1, x2, y2, color); + pxl8_cpu_draw_line_2d(cpu, x2, y2, x0, y0, color); + } +} + +u8* pxl8_cpu_get_framebuffer(pxl8_cpu_backend* cpu) { + if (!cpu || cpu->target_stack_depth == 0) return NULL; + return cpu->target_stack[0]->framebuffer; +} + +u32 pxl8_cpu_get_height(const pxl8_cpu_backend* cpu) { + if (!cpu || cpu->target_stack_depth == 0) return 0; + return cpu->target_stack[0]->height; +} + +u32 pxl8_cpu_get_width(const pxl8_cpu_backend* cpu) { + if (!cpu || cpu->target_stack_depth == 0) return 0; + return cpu->target_stack[0]->width; +} + +pxl8_cpu_render_target* pxl8_cpu_create_render_target(const pxl8_cpu_render_target_desc* desc) { + if (!desc) return NULL; + + pxl8_cpu_render_target* target = calloc(1, sizeof(pxl8_cpu_render_target)); + if (!target) return NULL; + + u32 size = desc->width * desc->height; + target->width = desc->width; + target->height = desc->height; + target->framebuffer = calloc(size, sizeof(u8)); + if (!target->framebuffer) { + free(target); + return NULL; + } + + if (desc->with_depth) { + target->zbuffer = calloc(size, sizeof(f32)); + if (!target->zbuffer) { + free(target->framebuffer); + free(target); + return NULL; + } + } + + if (desc->with_lighting) { + target->light_accum = calloc(size, sizeof(u32)); + if (!target->light_accum) { + free(target->zbuffer); + free(target->framebuffer); + free(target); + return NULL; + } + } + + return target; +} + +void pxl8_cpu_destroy_render_target(pxl8_cpu_render_target* target) { + if (!target) return; + free(target->light_accum); + free(target->zbuffer); + free(target->framebuffer); + free(target); +} + +pxl8_cpu_render_target* pxl8_cpu_get_target(pxl8_cpu_backend* cpu) { + return cpu ? cpu->current_target : NULL; +} + +void pxl8_cpu_set_target(pxl8_cpu_backend* cpu, pxl8_cpu_render_target* target) { + if (!cpu || !target) return; + cpu->current_target = target; +} + +void pxl8_cpu_blit(pxl8_cpu_backend* cpu, pxl8_cpu_render_target* src, i32 x, i32 y, u8 transparent_idx) { + if (!cpu || !cpu->current_target || !src || !src->framebuffer) return; + pxl8_cpu_render_target* dst = cpu->current_target; + + i32 src_x0 = 0, src_y0 = 0; + i32 dst_x0 = x, dst_y0 = y; + i32 copy_w = (i32)src->width; + i32 copy_h = (i32)src->height; + + if (dst_x0 < 0) { src_x0 = -dst_x0; copy_w += dst_x0; dst_x0 = 0; } + if (dst_y0 < 0) { src_y0 = -dst_y0; copy_h += dst_y0; dst_y0 = 0; } + if (dst_x0 + copy_w > (i32)dst->width) copy_w = (i32)dst->width - dst_x0; + if (dst_y0 + copy_h > (i32)dst->height) copy_h = (i32)dst->height - dst_y0; + + if (copy_w <= 0 || copy_h <= 0) return; + + for (i32 row = 0; row < copy_h; row++) { + u8* src_row = src->framebuffer + (src_y0 + row) * src->width + src_x0; + u8* dst_row = dst->framebuffer + (dst_y0 + row) * dst->width + dst_x0; + for (i32 col = 0; col < copy_w; col++) { + u8 pixel = src_row[col]; + if (pixel != transparent_idx) { + dst_row[col] = pixel; + } + } + } +} + +bool pxl8_cpu_push_target(pxl8_cpu_backend* cpu) { + if (!cpu || cpu->target_stack_depth >= PXL8_MAX_TARGET_STACK) return false; + + pxl8_cpu_render_target* base = cpu->target_stack[0]; + pxl8_cpu_render_target_desc desc = { + .width = base->width, + .height = base->height, + .with_depth = false, + .with_lighting = false, + }; + pxl8_cpu_render_target* target = pxl8_cpu_create_render_target(&desc); + if (!target) return false; + + cpu->target_stack[cpu->target_stack_depth++] = target; + cpu->current_target = target; + return true; +} + +void pxl8_cpu_pop_target(pxl8_cpu_backend* cpu) { + if (!cpu || cpu->target_stack_depth <= 1) return; + + pxl8_cpu_render_target* popped = cpu->target_stack[--cpu->target_stack_depth]; + cpu->current_target = cpu->target_stack[cpu->target_stack_depth - 1]; + + pxl8_cpu_blit(cpu, popped, 0, 0, 0); + pxl8_cpu_destroy_render_target(popped); +} + +u32 pxl8_cpu_get_target_depth(const pxl8_cpu_backend* cpu) { + return cpu ? cpu->target_stack_depth : 0; +} + +u8* pxl8_cpu_render_target_get_framebuffer(pxl8_cpu_render_target* target) { + return target ? target->framebuffer : NULL; +} + +u32 pxl8_cpu_render_target_get_height(const pxl8_cpu_render_target* target) { + return target ? target->height : 0; +} + +u32 pxl8_cpu_render_target_get_width(const pxl8_cpu_render_target* target) { + return target ? target->width : 0; +} diff --git a/client/src/gfx/pxl8_cpu.h b/client/src/gfx/pxl8_cpu.h new file mode 100644 index 0000000..35f99a0 --- /dev/null +++ b/client/src/gfx/pxl8_cpu.h @@ -0,0 +1,94 @@ +#pragma once + +#include "pxl8_atlas.h" +#include "pxl8_colormap.h" +#include "pxl8_math.h" +#include "pxl8_mesh.h" +#include "pxl8_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct pxl8_cpu_backend pxl8_cpu_backend; +typedef struct pxl8_cpu_render_target pxl8_cpu_render_target; + +typedef struct pxl8_cpu_render_target_desc { + u32 width; + u32 height; + bool with_depth; + bool with_lighting; +} pxl8_cpu_render_target_desc; + +typedef struct pxl8_3d_frame { + u8 ambient_light; + pxl8_vec3 camera_dir; + pxl8_vec3 camera_pos; + f32 far_clip; + u8 fog_color; + f32 fog_density; + f32 near_clip; + pxl8_mat4 projection; + f32 time; + pxl8_mat4 view; +} pxl8_3d_frame; + +pxl8_cpu_backend* pxl8_cpu_create(u32 width, u32 height); +void pxl8_cpu_destroy(pxl8_cpu_backend* cpu); + +void pxl8_cpu_begin_frame(pxl8_cpu_backend* cpu, const pxl8_3d_frame* frame); +void pxl8_cpu_end_frame(pxl8_cpu_backend* cpu); + +void pxl8_cpu_clear(pxl8_cpu_backend* cpu, u8 color); +void pxl8_cpu_clear_depth(pxl8_cpu_backend* cpu); + +void pxl8_cpu_set_colormap(pxl8_cpu_backend* cpu, const pxl8_colormap* cm); +void pxl8_cpu_set_palette(pxl8_cpu_backend* cpu, const u32* palette); + +void pxl8_cpu_draw_pixel(pxl8_cpu_backend* cpu, i32 x, i32 y, u8 color); +u8 pxl8_cpu_get_pixel(pxl8_cpu_backend* cpu, i32 x, i32 y); +void pxl8_cpu_draw_line_2d(pxl8_cpu_backend* cpu, i32 x0, i32 y0, i32 x1, i32 y1, u8 color); +void pxl8_cpu_draw_rect(pxl8_cpu_backend* cpu, i32 x, i32 y, i32 w, i32 h, u8 color); +void pxl8_cpu_draw_rect_fill(pxl8_cpu_backend* cpu, i32 x, i32 y, i32 w, i32 h, u8 color); +void pxl8_cpu_draw_circle(pxl8_cpu_backend* cpu, i32 cx, i32 cy, i32 radius, u8 color); +void pxl8_cpu_draw_circle_fill(pxl8_cpu_backend* cpu, i32 cx, i32 cy, i32 radius, u8 color); +void pxl8_cpu_draw_line_3d(pxl8_cpu_backend* cpu, pxl8_vec3 v0, pxl8_vec3 v1, u8 color); + +void pxl8_cpu_draw_mesh( + pxl8_cpu_backend* cpu, + const pxl8_mesh* mesh, + pxl8_mat4 model, + pxl8_material material, + const pxl8_atlas* textures +); + +void pxl8_cpu_draw_mesh_wireframe( + pxl8_cpu_backend* cpu, + const pxl8_mesh* mesh, + pxl8_mat4 model, + u8 color +); + +u8* pxl8_cpu_get_framebuffer(pxl8_cpu_backend* cpu); +u32 pxl8_cpu_get_height(const pxl8_cpu_backend* cpu); +u32 pxl8_cpu_get_width(const pxl8_cpu_backend* cpu); + +pxl8_cpu_render_target* pxl8_cpu_create_render_target(const pxl8_cpu_render_target_desc* desc); +void pxl8_cpu_destroy_render_target(pxl8_cpu_render_target* target); + +pxl8_cpu_render_target* pxl8_cpu_get_target(pxl8_cpu_backend* cpu); +void pxl8_cpu_set_target(pxl8_cpu_backend* cpu, pxl8_cpu_render_target* target); + +void pxl8_cpu_blit(pxl8_cpu_backend* cpu, pxl8_cpu_render_target* src, i32 x, i32 y, u8 transparent_idx); + +bool pxl8_cpu_push_target(pxl8_cpu_backend* cpu); +void pxl8_cpu_pop_target(pxl8_cpu_backend* cpu); +u32 pxl8_cpu_get_target_depth(const pxl8_cpu_backend* cpu); + +u8* pxl8_cpu_render_target_get_framebuffer(pxl8_cpu_render_target* target); +u32 pxl8_cpu_render_target_get_height(const pxl8_cpu_render_target* target); +u32 pxl8_cpu_render_target_get_width(const pxl8_cpu_render_target* target); + +#ifdef __cplusplus +} +#endif diff --git a/client/src/gfx/pxl8_dither.c b/client/src/gfx/pxl8_dither.c new file mode 100644 index 0000000..0082355 --- /dev/null +++ b/client/src/gfx/pxl8_dither.c @@ -0,0 +1,8 @@ +#include "pxl8_dither.h" + +const u8 PXL8_BAYER_4X4[16] = { + 0, 8, 2, 10, + 12, 4, 14, 6, + 3, 11, 1, 9, + 15, 7, 13, 5, +}; diff --git a/client/src/gfx/pxl8_dither.h b/client/src/gfx/pxl8_dither.h new file mode 100644 index 0000000..3b5416a --- /dev/null +++ b/client/src/gfx/pxl8_dither.h @@ -0,0 +1,41 @@ +#pragma once + +#include "pxl8_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern const u8 PXL8_BAYER_4X4[16]; + +static inline i8 pxl8_bayer_offset(u32 x, u32 y) { + return (i8)(PXL8_BAYER_4X4[(y & 3) * 4 + (x & 3)]) - 8; +} + +static inline u8 pxl8_ordered_dither(u8 value, u32 x, u32 y) { + u8 bayer = PXL8_BAYER_4X4[(y & 3) * 4 + (x & 3)]; + u16 result = (u16)value + (bayer >> 1); + return result > 255 ? 255 : (u8)result; +} + +static inline u8 pxl8_dither_light(u8 light, u32 x, u32 y) { + i8 offset = pxl8_bayer_offset(x, y) >> 1; + i16 result = (i16)light + offset; + if (result < 0) return 0; + if (result > 255) return 255; + return (u8)result; +} + +static inline u8 pxl8_dither_float(f32 value, u32 x, u32 y) { + u8 floor_val = (u8)value; + f32 frac = value - (f32)floor_val; + f32 threshold = (PXL8_BAYER_4X4[(y & 3) * 4 + (x & 3)] + 0.5f) * (1.0f / 16.0f); + if (frac > threshold) { + return floor_val < 255 ? floor_val + 1 : 255; + } + return floor_val; +} + +#ifdef __cplusplus +} +#endif diff --git a/src/pxl8_font.c b/client/src/gfx/pxl8_font.c similarity index 100% rename from src/pxl8_font.c rename to client/src/gfx/pxl8_font.c diff --git a/src/pxl8_font.h b/client/src/gfx/pxl8_font.h similarity index 100% rename from src/pxl8_font.h rename to client/src/gfx/pxl8_font.h diff --git a/client/src/gfx/pxl8_gfx.c b/client/src/gfx/pxl8_gfx.c new file mode 100644 index 0000000..565280b --- /dev/null +++ b/client/src/gfx/pxl8_gfx.c @@ -0,0 +1,706 @@ +#include "pxl8_gfx.h" + +#include +#include + +#include "pxl8_ase.h" +#include "pxl8_atlas.h" +#include "pxl8_backend.h" +#include "pxl8_blit.h" +#include "pxl8_color.h" +#include "pxl8_colormap.h" +#include "pxl8_font.h" +#include "pxl8_hal.h" +#include "pxl8_log.h" +#include "pxl8_macros.h" +#include "pxl8_math.h" +#include "pxl8_sys.h" +#include "pxl8_types.h" + +typedef struct pxl8_sprite_cache_entry { + char path[256]; + u32 sprite_id; + bool active; +} pxl8_sprite_cache_entry; + +struct pxl8_gfx { + pxl8_atlas* atlas; + pxl8_gfx_backend backend; + pxl8_colormap* colormap; + u8* framebuffer; + i32 framebuffer_height; + i32 framebuffer_width; + pxl8_frustum frustum; + const pxl8_hal* hal; + bool initialized; + pxl8_palette* palette; + pxl8_pixel_mode pixel_mode; + void* platform_data; + pxl8_sprite_cache_entry* sprite_cache; + u32 sprite_cache_capacity; + u32 sprite_cache_count; + pxl8_viewport viewport; +}; + +pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) { + pxl8_bounds bounds = {0}; + if (!gfx) { + return bounds; + } + bounds.w = gfx->framebuffer_width; + bounds.h = gfx->framebuffer_height; + return bounds; +} + +pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx) { + return gfx ? gfx->pixel_mode : PXL8_PIXEL_INDEXED; +} + +u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx) { + if (!gfx || gfx->pixel_mode == PXL8_PIXEL_HICOLOR) return NULL; + return gfx->framebuffer; +} + +u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx) { + if (!gfx || gfx->pixel_mode != PXL8_PIXEL_HICOLOR) return NULL; + return (u16*)gfx->framebuffer; +} + +i32 pxl8_gfx_get_height(const pxl8_gfx* gfx) { + return gfx ? gfx->framebuffer_height : 0; +} + +i32 pxl8_gfx_get_width(const pxl8_gfx* gfx) { + return gfx ? gfx->framebuffer_width : 0; +} + +pxl8_palette* pxl8_gfx_get_palette(pxl8_gfx* gfx) { + return gfx ? gfx->palette : NULL; +} + +u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color) { + if (!gfx || !gfx->palette) return 0; + if (color <= 0xFFFFFF) color = (color << 8) | 0xFF; + u8 r = (color >> 24) & 0xFF; + u8 g = (color >> 16) & 0xFF; + u8 b = (color >> 8) & 0xFF; + return pxl8_palette_find_closest(gfx->palette, r, g, b); +} + +void pxl8_gfx_set_palette(pxl8_gfx* gfx, pxl8_palette* pal) { + if (!gfx) return; + if (gfx->palette) { + pxl8_palette_destroy(gfx->palette); + } + gfx->palette = pal; + + if (gfx->palette && gfx->pixel_mode != PXL8_PIXEL_HICOLOR) { + u32* colors = pxl8_palette_colors(gfx->palette); + if (gfx->colormap) { + pxl8_colormap_generate(gfx->colormap, colors, NULL); + } + if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) { + pxl8_cpu_set_palette(gfx->backend.cpu, colors); + } + } +} + +i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) { + if (!gfx || !filepath) return -1; + pxl8_palette* pal = gfx->palette; + if (!pal) return -1; + pxl8_result result = pxl8_palette_load_ase(pal, filepath); + if (result != PXL8_OK) return (i32)result; + if (gfx->pixel_mode != PXL8_PIXEL_HICOLOR) { + u32* colors = pxl8_palette_colors(pal); + if (gfx->colormap) { + pxl8_colormap_generate(gfx->colormap, colors, NULL); + } + if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) { + pxl8_cpu_set_palette(gfx->backend.cpu, colors); + } + } + return 0; +} + +pxl8_gfx* pxl8_gfx_create( + const pxl8_hal* hal, + void* platform_data, + pxl8_pixel_mode mode, + pxl8_resolution resolution +) { + pxl8_gfx* gfx = (pxl8_gfx*)calloc(1, sizeof(pxl8_gfx)); + if (!gfx) { + pxl8_error("Failed to allocate graphics context"); + return NULL; + } + + gfx->hal = hal; + gfx->platform_data = platform_data; + + gfx->pixel_mode = mode; + pxl8_size size = pxl8_get_resolution_dimensions(resolution); + gfx->framebuffer_width = size.w; + gfx->framebuffer_height = size.h; + + if (!gfx->platform_data) { + pxl8_error("Platform data cannot be NULL"); + free(gfx); + return NULL; + } + + if (mode != PXL8_PIXEL_HICOLOR) { + gfx->palette = pxl8_palette_create(); + } + + gfx->backend.type = PXL8_GFX_BACKEND_CPU; + gfx->backend.cpu = pxl8_cpu_create(gfx->framebuffer_width, gfx->framebuffer_height); + if (!gfx->backend.cpu) { + pxl8_error("Failed to create CPU backend"); + pxl8_gfx_destroy(gfx); + return NULL; + } + + gfx->framebuffer = pxl8_cpu_get_framebuffer(gfx->backend.cpu); + + if (mode != PXL8_PIXEL_HICOLOR) { + gfx->colormap = calloc(1, sizeof(pxl8_colormap)); + if (gfx->colormap) { + pxl8_cpu_set_colormap(gfx->backend.cpu, gfx->colormap); + } + } + + gfx->viewport.offset_x = 0; + gfx->viewport.offset_y = 0; + gfx->viewport.scaled_width = gfx->framebuffer_width; + gfx->viewport.scaled_height = gfx->framebuffer_height; + gfx->viewport.scale = 1.0f; + + gfx->initialized = true; + return gfx; +} + +void pxl8_gfx_destroy(pxl8_gfx* gfx) { + if (!gfx) return; + + pxl8_atlas_destroy(gfx->atlas); + if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) { + pxl8_cpu_destroy(gfx->backend.cpu); + } + free(gfx->colormap); + pxl8_palette_destroy(gfx->palette); + free(gfx->sprite_cache); + + free(gfx); +} + +static pxl8_result pxl8_gfx_ensure_atlas(pxl8_gfx* gfx) { + if (gfx->atlas) return PXL8_OK; + gfx->atlas = pxl8_atlas_create(PXL8_DEFAULT_ATLAS_SIZE, PXL8_DEFAULT_ATLAS_SIZE, gfx->pixel_mode); + return gfx->atlas ? PXL8_OK : PXL8_ERROR_OUT_OF_MEMORY; +} + +void pxl8_gfx_clear_textures(pxl8_gfx* gfx) { + if (!gfx) return; + + if (gfx->atlas) { + pxl8_atlas_clear(gfx->atlas, 0); + } + + if (gfx->sprite_cache) { + gfx->sprite_cache_count = 0; + } +} + +pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width, u32 height) { + if (!gfx || !gfx->initialized || !pixels) return PXL8_ERROR_INVALID_ARGUMENT; + + pxl8_result result = pxl8_gfx_ensure_atlas(gfx); + if (result != PXL8_OK) return result; + + u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, pixels, width, height, gfx->pixel_mode); + if (texture_id == UINT32_MAX) { + pxl8_error("Texture doesn't fit in atlas"); + return PXL8_ERROR_INVALID_SIZE; + } + + return texture_id; +} + +pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) { + if (!gfx || !gfx->initialized || !path) return PXL8_ERROR_INVALID_ARGUMENT; + + if (!gfx->sprite_cache) { + gfx->sprite_cache_capacity = PXL8_DEFAULT_SPRITE_CACHE_CAPACITY; + gfx->sprite_cache = (pxl8_sprite_cache_entry*)calloc( + gfx->sprite_cache_capacity, sizeof(pxl8_sprite_cache_entry) + ); + if (!gfx->sprite_cache) return PXL8_ERROR_OUT_OF_MEMORY; + } + + for (u32 i = 0; i < gfx->sprite_cache_count; i++) { + if (gfx->sprite_cache[i].active && strcmp(gfx->sprite_cache[i].path, path) == 0) { + return gfx->sprite_cache[i].sprite_id; + } + } + + pxl8_result result = pxl8_gfx_ensure_atlas(gfx); + if (result != PXL8_OK) return result; + + pxl8_ase_file ase_file; + result = pxl8_ase_load(path, &ase_file); + if (result != PXL8_OK) { + pxl8_error("Failed to load ASE file: %s", path); + return result; + } + + if (ase_file.frame_count == 0) { + pxl8_error("No frames in ASE file"); + pxl8_ase_destroy(&ase_file); + return PXL8_ERROR_INVALID_FORMAT; + } + + u32 sprite_id = pxl8_atlas_add_texture( + gfx->atlas, + ase_file.frames[0].pixels, + ase_file.header.width, + ase_file.header.height, + gfx->pixel_mode + ); + + pxl8_ase_destroy(&ase_file); + + if (sprite_id == UINT32_MAX) { + pxl8_error("Sprite doesn't fit in atlas"); + return PXL8_ERROR_INVALID_SIZE; + } + + if (gfx->sprite_cache_count >= gfx->sprite_cache_capacity) { + u32 new_capacity = gfx->sprite_cache_capacity * 2; + pxl8_sprite_cache_entry* new_cache = (pxl8_sprite_cache_entry*)realloc( + gfx->sprite_cache, + new_capacity * sizeof(pxl8_sprite_cache_entry) + ); + if (!new_cache) return PXL8_ERROR_OUT_OF_MEMORY; + gfx->sprite_cache = new_cache; + gfx->sprite_cache_capacity = new_capacity; + } + + pxl8_sprite_cache_entry* entry = &gfx->sprite_cache[gfx->sprite_cache_count++]; + entry->active = true; + entry->sprite_id = sprite_id; + pxl8_strncpy(entry->path, path, sizeof(entry->path)); + + return sprite_id; +} + +pxl8_atlas* pxl8_gfx_get_atlas(pxl8_gfx* gfx) { + if (!gfx || !gfx->initialized) return NULL; + + if (pxl8_gfx_ensure_atlas(gfx) != PXL8_OK) return NULL; + return gfx->atlas; +} + +pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx) { + (void)gfx; + return PXL8_OK; +} + +void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) { + if (!gfx || !gfx->initialized || !gfx->hal) return; + + u32 bpp = (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) ? 2 : 1; + u32* colors = gfx->palette ? pxl8_palette_colors(gfx->palette) : NULL; + gfx->hal->upload_texture( + gfx->platform_data, + gfx->framebuffer, + gfx->framebuffer_width, + gfx->framebuffer_height, + colors, + bpp + ); +} + +void pxl8_gfx_present(pxl8_gfx* gfx) { + if (!gfx || !gfx->initialized || !gfx->hal) return; + + gfx->hal->present(gfx->platform_data); +} + +pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height) { + pxl8_viewport vp = {0}; + vp.scale = fminf(bounds.w / (f32)width, bounds.h / (f32)height); + vp.scaled_width = (i32)(width * vp.scale); + vp.scaled_height = (i32)(height * vp.scale); + vp.offset_x = (bounds.w - vp.scaled_width) / 2; + vp.offset_y = (bounds.h - vp.scaled_height) / 2; + return vp; +} + +void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp) { + if (!gfx) return; + gfx->viewport = vp; +} + +void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom) { + (void)gfx; (void)left; (void)right; (void)top; (void)bottom; +} + +void pxl8_2d_clear(pxl8_gfx* gfx, u32 color) { + if (!gfx) return; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_clear(gfx->backend.cpu, (u8)color); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } +} + +void pxl8_2d_pixel(pxl8_gfx* gfx, i32 x, i32 y, u32 color) { + if (!gfx) return; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_draw_pixel(gfx->backend.cpu, x, y, (u8)color); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } +} + +u32 pxl8_2d_get_pixel(pxl8_gfx* gfx, i32 x, i32 y) { + if (!gfx) return 0; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + return pxl8_cpu_get_pixel(gfx->backend.cpu, x, y); + case PXL8_GFX_BACKEND_GPU: + return 0; + } + return 0; +} + +void pxl8_2d_line(pxl8_gfx* gfx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color) { + if (!gfx) return; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_draw_line_2d(gfx->backend.cpu, x0, y0, x1, y1, (u8)color); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } +} + +void pxl8_2d_rect(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) { + if (!gfx) return; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_draw_rect(gfx->backend.cpu, x, y, w, h, (u8)color); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } +} + +void pxl8_2d_rect_fill(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) { + if (!gfx) return; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_draw_rect_fill(gfx->backend.cpu, x, y, w, h, (u8)color); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } +} + +void pxl8_2d_circle(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color) { + if (!gfx) return; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_draw_circle(gfx->backend.cpu, cx, cy, radius, (u8)color); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } +} + +void pxl8_2d_circle_fill(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color) { + if (!gfx) return; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_draw_circle_fill(gfx->backend.cpu, cx, cy, radius, (u8)color); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } +} + +void pxl8_2d_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) { + if (!gfx || !text) return; + + u8* framebuffer = NULL; + i32 fb_width = 0; + i32 fb_height = 0; + + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: { + pxl8_cpu_render_target* render_target = pxl8_cpu_get_target(gfx->backend.cpu); + if (!render_target) return; + framebuffer = pxl8_cpu_render_target_get_framebuffer(render_target); + fb_width = (i32)pxl8_cpu_render_target_get_width(render_target); + fb_height = (i32)pxl8_cpu_render_target_get_height(render_target); + break; + } + case PXL8_GFX_BACKEND_GPU: + return; + } + + if (!framebuffer) return; + + const pxl8_font* font = &pxl8_default_font; + i32 cursor_x = x; + i32 cursor_y = y; + + for (const char* c = text; *c; c++) { + const pxl8_glyph* glyph = pxl8_font_find_glyph(font, (u32)*c); + if (!glyph) continue; + + for (i32 gy = 0; gy < glyph->height; gy++) { + for (i32 gx = 0; gx < glyph->width; gx++) { + i32 px = cursor_x + gx; + i32 py = cursor_y + gy; + + if (px < 0 || px >= fb_width || py < 0 || py >= fb_height) continue; + + u8 pixel_bit = 0; + + if (glyph->format == PXL8_FONT_FORMAT_INDEXED) { + u8 pixel_byte = glyph->data.indexed[gy]; + pixel_bit = (pixel_byte >> gx) & 1; + } else { + i32 glyph_idx = gy * 8 + gx; + u32 rgba_pixel = glyph->data.rgba[glyph_idx]; + pixel_bit = ((rgba_pixel >> 24) & 0xFF) > 128 ? 1 : 0; + } + + if (pixel_bit) { + framebuffer[py * fb_width + px] = (u8)color; + } + } + } + + cursor_x += font->default_width; + } +} + + +void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y) { + if (!gfx || !gfx->atlas) return; + + u8* framebuffer = NULL; + i32 fb_width = 0; + i32 fb_height = 0; + + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: { + pxl8_cpu_render_target* render_target = pxl8_cpu_get_target(gfx->backend.cpu); + if (!render_target) return; + framebuffer = pxl8_cpu_render_target_get_framebuffer(render_target); + fb_width = (i32)pxl8_cpu_render_target_get_width(render_target); + fb_height = (i32)pxl8_cpu_render_target_get_height(render_target); + break; + } + case PXL8_GFX_BACKEND_GPU: + return; + } + + if (!framebuffer) return; + + const pxl8_atlas_entry* entry = pxl8_atlas_get_entry(gfx->atlas, sprite_id); + if (!entry || !entry->active) return; + + i32 clip_left = (x < 0) ? -x : 0; + i32 clip_top = (y < 0) ? -y : 0; + i32 clip_right = (x + w > fb_width) ? x + w - fb_width : 0; + i32 clip_bottom = (y + h > fb_height) ? y + h - fb_height : 0; + + i32 draw_width = w - clip_left - clip_right; + i32 draw_height = h - clip_top - clip_bottom; + + if (draw_width <= 0 || draw_height <= 0) return; + + i32 dest_x = x + clip_left; + i32 dest_y = y + clip_top; + + bool is_1to1_scale = (w == entry->w && h == entry->h); + bool is_unclipped = (clip_left == 0 && clip_top == 0 && clip_right == 0 && clip_bottom == 0); + bool is_flipped = flip_x || flip_y; + + u32 atlas_width = pxl8_atlas_get_width(gfx->atlas); + const u8* atlas_pixels = pxl8_atlas_get_pixels(gfx->atlas); + + if (is_1to1_scale && is_unclipped && !is_flipped) { + const u8* sprite_data = atlas_pixels + entry->y * atlas_width + entry->x; + pxl8_blit_indexed(framebuffer, fb_width, sprite_data, atlas_width, x, y, w, h); + } else { + for (i32 py = 0; py < draw_height; py++) { + for (i32 px = 0; px < draw_width; px++) { + i32 local_x = (px + clip_left) * entry->w / w; + i32 local_y = (py + clip_top) * entry->h / h; + + i32 src_x = flip_x ? entry->x + entry->w - 1 - local_x : entry->x + local_x; + i32 src_y = flip_y ? entry->y + entry->h - 1 - local_y : entry->y + local_y; + + i32 src_idx = src_y * atlas_width + src_x; + i32 dest_idx = (dest_y + py) * fb_width + (dest_x + px); + + framebuffer[dest_idx] = pxl8_blend_indexed(atlas_pixels[src_idx], framebuffer[dest_idx]); + } + } + } +} + +void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt) { + if (!gfx) return; + + if (gfx->palette) { + u16 delta_ticks = (u16)(dt * 1000.0f); + pxl8_palette_tick(gfx->palette, delta_ticks); + } +} + +void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms) { + if (!gfx || !camera) return; + + pxl8_mat4 view = pxl8_3d_camera_get_view(camera); + pxl8_mat4 projection = pxl8_3d_camera_get_projection(camera); + pxl8_vec3 position = pxl8_3d_camera_get_position(camera); + pxl8_vec3 forward = pxl8_3d_camera_get_forward(camera); + + pxl8_mat4 vp = pxl8_mat4_mul(projection, view); + gfx->frustum = pxl8_frustum_from_matrix(vp); + + pxl8_3d_frame frame = { + .ambient_light = uniforms ? uniforms->ambient : 0, + .camera_dir = forward, + .camera_pos = position, + .far_clip = 4096.0f, + .fog_color = uniforms ? uniforms->fog_color : 0, + .fog_density = uniforms ? uniforms->fog_density : 0.0f, + .near_clip = 1.0f, + .projection = projection, + .time = uniforms ? uniforms->time : 0.0f, + .view = view, + }; + + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_begin_frame(gfx->backend.cpu, &frame); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } +} + +const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx) { + if (!gfx) return NULL; + return &gfx->frustum; +} + +void pxl8_3d_clear(pxl8_gfx* gfx, u8 color) { + if (!gfx) return; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_clear(gfx->backend.cpu, color); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } +} + +void pxl8_3d_clear_depth(pxl8_gfx* gfx) { + if (!gfx) return; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_clear_depth(gfx->backend.cpu); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } +} + +void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color) { + if (!gfx) return; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_draw_line_3d(gfx->backend.cpu, v0, v1, color); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } +} + +void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, pxl8_mat4 model, pxl8_material material) { + if (!gfx || !mesh) return; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_draw_mesh(gfx->backend.cpu, mesh, model, material, gfx->atlas); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } +} + +void pxl8_3d_draw_mesh_wireframe(pxl8_gfx* gfx, const pxl8_mesh* mesh, pxl8_mat4 model, u8 color) { + if (!gfx || !mesh) return; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_draw_mesh_wireframe(gfx->backend.cpu, mesh, model, color); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } +} + +void pxl8_3d_end_frame(pxl8_gfx* gfx) { + if (!gfx) return; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_end_frame(gfx->backend.cpu); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } +} + +u8* pxl8_3d_get_framebuffer(pxl8_gfx* gfx) { + if (!gfx) return NULL; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + return pxl8_cpu_get_framebuffer(gfx->backend.cpu); + case PXL8_GFX_BACKEND_GPU: + return NULL; + } + return NULL; +} + +bool pxl8_gfx_push_target(pxl8_gfx* gfx) { + if (!gfx) return false; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + return pxl8_cpu_push_target(gfx->backend.cpu); + case PXL8_GFX_BACKEND_GPU: + return false; + } + return false; +} + +void pxl8_gfx_pop_target(pxl8_gfx* gfx) { + if (!gfx) return; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_pop_target(gfx->backend.cpu); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } +} diff --git a/client/src/gfx/pxl8_gfx.h b/client/src/gfx/pxl8_gfx.h new file mode 100644 index 0000000..ac3acd4 --- /dev/null +++ b/client/src/gfx/pxl8_gfx.h @@ -0,0 +1,48 @@ +#pragma once + +#include "pxl8_gfx2d.h" +#include "pxl8_gfx3d.h" +#include "pxl8_hal.h" +#include "pxl8_palette.h" +#include "pxl8_types.h" + +typedef struct pxl8_gfx pxl8_gfx; + +#ifdef __cplusplus +extern "C" { +#endif + +pxl8_gfx* pxl8_gfx_create(const pxl8_hal* hal, void* platform_data, pxl8_pixel_mode mode, pxl8_resolution resolution); +void pxl8_gfx_destroy(pxl8_gfx* gfx); + +void pxl8_gfx_present(pxl8_gfx* gfx); +void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt); +void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx); + +u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color); + +pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx); +u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx); +u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx); +i32 pxl8_gfx_get_height(const pxl8_gfx* gfx); +pxl8_palette* pxl8_gfx_get_palette(pxl8_gfx* gfx); +pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx); +i32 pxl8_gfx_get_width(const pxl8_gfx* gfx); + +i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath); +void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom); +void pxl8_gfx_set_palette(pxl8_gfx* gfx, pxl8_palette* pal); +void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp); +pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height); + +void pxl8_gfx_clear_textures(pxl8_gfx* gfx); +pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width, u32 height); +pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx); +pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path); + +bool pxl8_gfx_push_target(pxl8_gfx* gfx); +void pxl8_gfx_pop_target(pxl8_gfx* gfx); + +#ifdef __cplusplus +} +#endif diff --git a/client/src/gfx/pxl8_gfx2d.h b/client/src/gfx/pxl8_gfx2d.h new file mode 100644 index 0000000..e2d75cd --- /dev/null +++ b/client/src/gfx/pxl8_gfx2d.h @@ -0,0 +1,24 @@ +#pragma once + +#include "pxl8_types.h" + +typedef struct pxl8_gfx pxl8_gfx; + +#ifdef __cplusplus +extern "C" { +#endif + +void pxl8_2d_circle(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color); +void pxl8_2d_circle_fill(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color); +void pxl8_2d_clear(pxl8_gfx* gfx, u32 color); +u32 pxl8_2d_get_pixel(pxl8_gfx* gfx, i32 x, i32 y); +void pxl8_2d_line(pxl8_gfx* gfx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color); +void pxl8_2d_pixel(pxl8_gfx* gfx, i32 x, i32 y, u32 color); +void pxl8_2d_rect(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color); +void pxl8_2d_rect_fill(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color); +void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y); +void pxl8_2d_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color); + +#ifdef __cplusplus +} +#endif diff --git a/client/src/gfx/pxl8_gfx3d.h b/client/src/gfx/pxl8_gfx3d.h new file mode 100644 index 0000000..0c4f2df --- /dev/null +++ b/client/src/gfx/pxl8_gfx3d.h @@ -0,0 +1,33 @@ +#pragma once + +#include "pxl8_3d_camera.h" +#include "pxl8_math.h" +#include "pxl8_mesh.h" +#include "pxl8_types.h" + +typedef struct pxl8_gfx pxl8_gfx; + +typedef struct pxl8_3d_uniforms { + u8 ambient; + u8 fog_color; + f32 fog_density; + f32 time; +} pxl8_3d_uniforms; + +#ifdef __cplusplus +extern "C" { +#endif + +void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms); +void pxl8_3d_clear(pxl8_gfx* gfx, u8 color); +void pxl8_3d_clear_depth(pxl8_gfx* gfx); +void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color); +void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, pxl8_mat4 model, pxl8_material material); +void pxl8_3d_draw_mesh_wireframe(pxl8_gfx* gfx, const pxl8_mesh* mesh, pxl8_mat4 model, u8 color); +void pxl8_3d_end_frame(pxl8_gfx* gfx); +u8* pxl8_3d_get_framebuffer(pxl8_gfx* gfx); +const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx); + +#ifdef __cplusplus +} +#endif diff --git a/client/src/gfx/pxl8_mesh.c b/client/src/gfx/pxl8_mesh.c new file mode 100644 index 0000000..6f8da2c --- /dev/null +++ b/client/src/gfx/pxl8_mesh.c @@ -0,0 +1,122 @@ +#include "pxl8_mesh.h" +#include +#include + +pxl8_mesh* pxl8_mesh_create(u32 vertex_capacity, u32 index_capacity) { + if (vertex_capacity > PXL8_MESH_MAX_VERTICES) vertex_capacity = PXL8_MESH_MAX_VERTICES; + if (index_capacity > PXL8_MESH_MAX_INDICES) index_capacity = PXL8_MESH_MAX_INDICES; + + pxl8_mesh* mesh = calloc(1, sizeof(pxl8_mesh)); + if (!mesh) return NULL; + + mesh->vertices = calloc(vertex_capacity, sizeof(pxl8_vertex)); + mesh->indices = calloc(index_capacity, sizeof(u16)); + + if (!mesh->vertices || !mesh->indices) { + free(mesh->vertices); + free(mesh->indices); + free(mesh); + return NULL; + } + + mesh->vertex_capacity = vertex_capacity; + mesh->index_capacity = index_capacity; + mesh->vertex_count = 0; + mesh->index_count = 0; + + return mesh; +} + +void pxl8_mesh_destroy(pxl8_mesh* mesh) { + if (!mesh) return; + free(mesh->vertices); + free(mesh->indices); + free(mesh); +} + +void pxl8_mesh_clear(pxl8_mesh* mesh) { + if (!mesh) return; + mesh->vertex_count = 0; + mesh->index_count = 0; +} + +u16 pxl8_mesh_push_vertex(pxl8_mesh* mesh, pxl8_vertex v) { + if (!mesh || mesh->vertex_count >= mesh->vertex_capacity) return 0; + u16 idx = (u16)mesh->vertex_count; + mesh->vertices[mesh->vertex_count++] = v; + return idx; +} + +void pxl8_mesh_push_triangle(pxl8_mesh* mesh, u16 i0, u16 i1, u16 i2) { + if (!mesh || mesh->index_count + 3 > mesh->index_capacity) return; + mesh->indices[mesh->index_count++] = i0; + mesh->indices[mesh->index_count++] = i1; + mesh->indices[mesh->index_count++] = i2; +} + +void pxl8_mesh_push_quad(pxl8_mesh* mesh, u16 i0, u16 i1, u16 i2, u16 i3) { + pxl8_mesh_push_triangle(mesh, i0, i1, i2); + pxl8_mesh_push_triangle(mesh, i0, i2, i3); +} + +void pxl8_mesh_push_box(pxl8_mesh* mesh, pxl8_vec3 center, pxl8_vec3 half, u8 color) { + pxl8_mesh_push_box_uv(mesh, center, half, color, 1.0f, 1.0f); +} + +void pxl8_mesh_push_box_uv(pxl8_mesh* mesh, pxl8_vec3 center, pxl8_vec3 half, u8 color, f32 u_scale, f32 v_scale) { + if (!mesh) return; + + pxl8_vec3 corners[8] = { + {center.x - half.x, center.y - half.y, center.z - half.z}, + {center.x + half.x, center.y - half.y, center.z - half.z}, + {center.x + half.x, center.y + half.y, center.z - half.z}, + {center.x - half.x, center.y + half.y, center.z - half.z}, + {center.x - half.x, center.y - half.y, center.z + half.z}, + {center.x + half.x, center.y - half.y, center.z + half.z}, + {center.x + half.x, center.y + half.y, center.z + half.z}, + {center.x - half.x, center.y + half.y, center.z + half.z}, + }; + + pxl8_vec3 normals[6] = { + { 0, 0, -1}, // front + { 0, 0, 1}, // back + {-1, 0, 0}, // left + { 1, 0, 0}, // right + { 0, -1, 0}, // bottom + { 0, 1, 0}, // top + }; + + i32 faces[6][4] = { + {0, 1, 2, 3}, // front + {5, 4, 7, 6}, // back + {4, 0, 3, 7}, // left + {1, 5, 6, 2}, // right + {4, 5, 1, 0}, // bottom + {3, 2, 6, 7}, // top + }; + + f32 uvs[4][2] = { + {0, v_scale}, + {u_scale, v_scale}, + {u_scale, 0}, + {0, 0}, + }; + + for (i32 face = 0; face < 6; face++) { + u16 base = (u16)mesh->vertex_count; + + for (i32 v = 0; v < 4; v++) { + pxl8_vertex vert = { + .position = corners[faces[face][v]], + .normal = normals[face], + .u = uvs[v][0], + .v = uvs[v][1], + .color = color, + .light = 255, + }; + pxl8_mesh_push_vertex(mesh, vert); + } + + pxl8_mesh_push_quad(mesh, base, base + 1, base + 2, base + 3); + } +} diff --git a/client/src/gfx/pxl8_mesh.h b/client/src/gfx/pxl8_mesh.h new file mode 100644 index 0000000..cbfad5c --- /dev/null +++ b/client/src/gfx/pxl8_mesh.h @@ -0,0 +1,124 @@ +#pragma once + +#include "pxl8_math.h" +#include "pxl8_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PXL8_MESH_MAX_VERTICES 65536 +#define PXL8_MESH_MAX_INDICES 65536 + +typedef enum pxl8_blend_mode { + PXL8_BLEND_OPAQUE = 0, + PXL8_BLEND_ALPHA_TEST, + PXL8_BLEND_ALPHA, + PXL8_BLEND_ADDITIVE, +} pxl8_blend_mode; + +typedef struct pxl8_material { + u32 texture_id; + u8 alpha; + u8 blend_mode; + bool dither; + bool double_sided; + bool dynamic_lighting; + bool per_pixel; + bool vertex_color_passthrough; + f32 emissive_intensity; +} pxl8_material; + +typedef struct pxl8_vertex { + pxl8_vec3 position; + pxl8_vec3 normal; + f32 u, v; + u8 color; + u8 light; + u8 _pad[2]; +} pxl8_vertex; + +typedef struct pxl8_mesh { + pxl8_vertex* vertices; + u16* indices; + u32 vertex_count; + u32 index_count; + u32 vertex_capacity; + u32 index_capacity; +} pxl8_mesh; + +pxl8_mesh* pxl8_mesh_create(u32 vertex_capacity, u32 index_capacity); +void pxl8_mesh_destroy(pxl8_mesh* mesh); +void pxl8_mesh_clear(pxl8_mesh* mesh); + +u16 pxl8_mesh_push_vertex(pxl8_mesh* mesh, pxl8_vertex v); +void pxl8_mesh_push_triangle(pxl8_mesh* mesh, u16 i0, u16 i1, u16 i2); +void pxl8_mesh_push_quad(pxl8_mesh* mesh, u16 i0, u16 i1, u16 i2, u16 i3); + +void pxl8_mesh_push_box(pxl8_mesh* mesh, pxl8_vec3 center, pxl8_vec3 half_extents, u8 color); +void pxl8_mesh_push_box_uv(pxl8_mesh* mesh, pxl8_vec3 center, pxl8_vec3 half_extents, u8 color, f32 u_scale, f32 v_scale); + +static inline u32 pxl8_mesh_triangle_count(const pxl8_mesh* mesh) { + return mesh->index_count / 3; +} + +static inline pxl8_material pxl8_material_new(u32 texture_id) { + return (pxl8_material){ + .texture_id = texture_id, + .alpha = 255, + .blend_mode = PXL8_BLEND_OPAQUE, + .dither = true, + .double_sided = false, + .dynamic_lighting = false, + .per_pixel = false, + .vertex_color_passthrough = false, + .emissive_intensity = 0.0f, + }; +} + +static inline pxl8_material pxl8_material_with_alpha(pxl8_material m, u8 alpha) { + m.alpha = alpha; + if (alpha < 255 && m.blend_mode == PXL8_BLEND_OPAQUE) { + m.blend_mode = PXL8_BLEND_ALPHA; + } + return m; +} + +static inline pxl8_material pxl8_material_with_blend(pxl8_material m, pxl8_blend_mode mode) { + m.blend_mode = mode; + return m; +} + +static inline pxl8_material pxl8_material_with_double_sided(pxl8_material m) { + m.double_sided = true; + return m; +} + +static inline pxl8_material pxl8_material_with_emissive(pxl8_material m, f32 intensity) { + m.emissive_intensity = intensity; + return m; +} + +static inline pxl8_material pxl8_material_with_lighting(pxl8_material m) { + m.dynamic_lighting = true; + return m; +} + +static inline pxl8_material pxl8_material_with_no_dither(pxl8_material m) { + m.dither = false; + return m; +} + +static inline pxl8_material pxl8_material_with_passthrough(pxl8_material m) { + m.vertex_color_passthrough = true; + return m; +} + +static inline pxl8_material pxl8_material_with_per_pixel(pxl8_material m) { + m.per_pixel = true; + return m; +} + +#ifdef __cplusplus +} +#endif diff --git a/client/src/gfx/pxl8_palette.c b/client/src/gfx/pxl8_palette.c new file mode 100644 index 0000000..9d16390 --- /dev/null +++ b/client/src/gfx/pxl8_palette.c @@ -0,0 +1,474 @@ +#include "pxl8_palette.h" + +#include +#include + +#include "pxl8_ase.h" +#include "pxl8_color.h" +#include "pxl8_log.h" + +#define PXL8_PALETTE_HASH_SIZE 512 + +typedef struct { + u32 color; + i16 index; +} pxl8_palette_hash_entry; + +struct pxl8_palette { + u32 base_colors[PXL8_MAX_CYCLES][PXL8_MAX_CYCLE_LEN]; + u32 colors[PXL8_PALETTE_SIZE]; + u8 color_ramp[PXL8_PALETTE_SIZE]; + u16 color_count; + pxl8_cycle_range cycles[PXL8_MAX_CYCLES]; + i8 directions[PXL8_MAX_CYCLES]; + f32 phases[PXL8_MAX_CYCLES]; + pxl8_palette_hash_entry hash[PXL8_PALETTE_HASH_SIZE]; +}; + +static inline u32 pxl8_palette_hash(u32 color) { + color ^= color >> 16; + color *= 0x85ebca6b; + color ^= color >> 13; + return color & (PXL8_PALETTE_HASH_SIZE - 1); +} + +static void pxl8_palette_rebuild_hash(pxl8_palette* pal) { + for (u32 i = 0; i < PXL8_PALETTE_HASH_SIZE; i++) { + pal->hash[i].index = -1; + } + for (u32 i = 0; i < PXL8_PALETTE_SIZE; i++) { + u32 color = pal->colors[i]; + u32 slot = pxl8_palette_hash(color); + while (pal->hash[slot].index >= 0) { + slot = (slot + 1) & (PXL8_PALETTE_HASH_SIZE - 1); + } + pal->hash[slot].color = color; + pal->hash[slot].index = (i16)i; + } +} + +static inline u32 pack_rgb(u8 r, u8 g, u8 b) { + return 0xFF000000 | ((u32)b << 16) | ((u32)g << 8) | r; +} + +static inline u32 pack_rgba(u8 r, u8 g, u8 b, u8 a) { + return ((u32)a << 24) | ((u32)b << 16) | ((u32)g << 8) | r; +} + +static inline void unpack_rgba(u32 c, u8* r, u8* g, u8* b, u8* a) { + *r = c & 0xFF; + *g = (c >> 8) & 0xFF; + *b = (c >> 16) & 0xFF; + *a = (c >> 24) & 0xFF; +} + +static f32 apply_easing(pxl8_easing easing, f32 t) { + switch (easing) { + case PXL8_EASE_IN: + return t * t; + case PXL8_EASE_IN_OUT: + return t < 0.5f ? 2.0f * t * t : 1.0f - (-2.0f * t + 2.0f) * (-2.0f * t + 2.0f) * 0.5f; + case PXL8_EASE_LINEAR: + return t; + case PXL8_EASE_OUT: + return 1.0f - (1.0f - t) * (1.0f - t); + default: + return t; + } +} + +static void rgb_to_hsl(u8 r, u8 g, u8 b, f32* h, f32* s, f32* l) { + f32 rf = r / 255.0f; + f32 gf = g / 255.0f; + f32 bf = b / 255.0f; + + f32 max = rf > gf ? (rf > bf ? rf : bf) : (gf > bf ? gf : bf); + f32 min = rf < gf ? (rf < bf ? rf : bf) : (gf < bf ? gf : bf); + f32 delta = max - min; + + *l = (max + min) / 2.0f; + + if (delta < 0.001f) { + *h = 0.0f; + *s = 0.0f; + } else { + *s = *l > 0.5f ? delta / (2.0f - max - min) : delta / (max + min); + + if (max == rf) { + *h = (gf - bf) / delta + (gf < bf ? 6.0f : 0.0f); + } else if (max == gf) { + *h = (bf - rf) / delta + 2.0f; + } else { + *h = (rf - gf) / delta + 4.0f; + } + *h /= 6.0f; + } +} + +typedef struct { + u8 index; + f32 hue; + f32 sat; + f32 lum; +} palette_sort_entry; + +static int palette_sort_cmp(const void* a, const void* b) { + const palette_sort_entry* entry_a = (const palette_sort_entry*)a; + const palette_sort_entry* entry_b = (const palette_sort_entry*)b; + + u8 a_gray = entry_a->sat < 0.1f; + u8 b_gray = entry_b->sat < 0.1f; + if (a_gray != b_gray) return a_gray - b_gray; + + if (a_gray) { + if (entry_a->lum < entry_b->lum) return -1; + if (entry_a->lum > entry_b->lum) return 1; + return (int)entry_a->index - (int)entry_b->index; + } + + if (entry_a->hue < entry_b->hue - 0.02f) return -1; + if (entry_a->hue > entry_b->hue + 0.02f) return 1; + + if (entry_a->lum < entry_b->lum) return -1; + if (entry_a->lum > entry_b->lum) return 1; + + return (int)entry_a->index - (int)entry_b->index; +} + +static void pxl8_palette_sort_colors(pxl8_palette* pal) { + if (!pal || pal->color_count < 2) return; + + pal->color_ramp[0] = 0; + + u8 count = pal->color_count; + palette_sort_entry entries[PXL8_PALETTE_SIZE - 1]; + for (u32 i = 1; i < count; i++) { + u8 r, g, b, a; + unpack_rgba(pal->colors[i], &r, &g, &b, &a); + + entries[i - 1].index = (u8)i; + rgb_to_hsl(r, g, b, &entries[i - 1].hue, &entries[i - 1].sat, &entries[i - 1].lum); + } + + qsort(entries, count - 1, sizeof(palette_sort_entry), palette_sort_cmp); + + for (u32 i = 1; i < count; i++) { + pal->color_ramp[i] = entries[i - 1].index; + } +} + +static u32 lerp_color(u32 c0, u32 c1, f32 t) { + u8 r0, g0, b0, a0, r1, g1, b1, a1; + unpack_rgba(c0, &r0, &g0, &b0, &a0); + unpack_rgba(c1, &r1, &g1, &b1, &a1); + + u8 r = (u8)(r0 + (r1 - r0) * t); + u8 g = (u8)(g0 + (g1 - g0) * t); + u8 b = (u8)(b0 + (b1 - b0) * t); + u8 a = (u8)(a0 + (a1 - a0) * t); + + return pack_rgba(r, g, b, a); +} + +static void update_cycle_colors(pxl8_palette* pal, u8 slot) { + pxl8_cycle_range* cycle = &pal->cycles[slot]; + f32 phase = pal->phases[slot]; + f32 eased = apply_easing(cycle->easing, phase); + u8 len = cycle->len; + u8 start = cycle->start; + + if (cycle->interpolate) { + f32 pos = eased * (len - 1); + u8 idx0 = (u8)pos; + if (idx0 >= len - 1) idx0 = len - 1; + f32 frac = pos - idx0; + + for (u8 i = 0; i < len; i++) { + u8 src0 = (i + idx0) % len; + u8 src1 = (i + idx0 + 1) % len; + u32 c0 = pal->base_colors[slot][src0]; + u32 c1 = pal->base_colors[slot][src1]; + pal->colors[start + i] = lerp_color(c0, c1, frac); + } + } else { + u8 offset = (u8)(eased * len) % len; + for (u8 i = 0; i < len; i++) { + u8 src = (i + offset) % len; + pal->colors[start + i] = pal->base_colors[slot][src]; + } + } +} + +pxl8_palette* pxl8_palette_create(void) { + pxl8_palette* pal = calloc(1, sizeof(pxl8_palette)); + if (!pal) return NULL; + + pal->colors[0] = 0x00000000; + pal->colors[1] = pack_rgb(0x00, 0x00, 0x00); + pal->colors[2] = pack_rgb(0xFF, 0xFF, 0xFF); + pal->color_count = 3; + pal->color_ramp[0] = 0; + pal->color_ramp[1] = 1; + pal->color_ramp[2] = 2; + + for (u8 i = 0; i < PXL8_MAX_CYCLES; i++) { + pal->cycles[i] = pxl8_cycle_range_disabled(); + pal->phases[i] = 0.0f; + pal->directions[i] = 1; + } + + return pal; +} + +void pxl8_palette_destroy(pxl8_palette* pal) { + free(pal); +} + +pxl8_result pxl8_palette_load_ase(pxl8_palette* pal, const char* path) { + if (!pal || !path) return PXL8_ERROR_INVALID_ARGUMENT; + + pxl8_ase_file ase_file; + pxl8_result result = pxl8_ase_load(path, &ase_file); + if (result != PXL8_OK) { + pxl8_error("Failed to load ASE file for palette: %s", path); + return result; + } + + if (ase_file.palette.entry_count == 0 || !ase_file.palette.colors) { + pxl8_error("No palette data in ASE file"); + pxl8_ase_destroy(&ase_file); + return PXL8_ERROR_INVALID_FORMAT; + } + + u32 copy_count = ase_file.palette.entry_count < PXL8_PALETTE_SIZE + ? ase_file.palette.entry_count + : PXL8_PALETTE_SIZE; + + memcpy(pal->colors, ase_file.palette.colors, copy_count * sizeof(u32)); + pal->color_count = (u16)copy_count; + + pxl8_ase_destroy(&ase_file); + pxl8_palette_sort_colors(pal); + pxl8_palette_rebuild_hash(pal); + return PXL8_OK; +} + +u32* pxl8_palette_colors(pxl8_palette* pal) { + return pal ? pal->colors : NULL; +} + +u8* pxl8_palette_color_ramp(pxl8_palette* pal) { + return pal ? pal->color_ramp : NULL; +} + +u16 pxl8_palette_color_count(const pxl8_palette* pal) { + return pal ? pal->color_count : 0; +} + +u8 pxl8_palette_ramp_index(const pxl8_palette* pal, u8 position) { + if (!pal || position >= pal->color_count) return 0; + return pal->color_ramp[position]; +} + +u8 pxl8_palette_ramp_position(const pxl8_palette* pal, u8 index) { + if (!pal) return 0; + for (u32 i = 0; i < pal->color_count; i++) { + if (pal->color_ramp[i] == index) return (u8)i; + } + return 0; +} + +u8 pxl8_palette_find_closest(const pxl8_palette* pal, u8 r, u8 g, u8 b) { + if (!pal || pal->color_count < 2) return 1; + + u8 best_idx = 1; + u32 best_dist = 0xFFFFFFFF; + + for (u32 i = 1; i < pal->color_count; i++) { + u8 pr, pg, pb, pa; + unpack_rgba(pal->colors[i], &pr, &pg, &pb, &pa); + + i32 dr = (i32)r - (i32)pr; + i32 dg = (i32)g - (i32)pg; + i32 db = (i32)b - (i32)pb; + u32 dist = (u32)(dr * dr + dg * dg + db * db); + + if (dist < best_dist) { + best_dist = dist; + best_idx = (u8)i; + if (dist == 0) break; + } + } + + return best_idx; +} + +u32 pxl8_palette_color(const pxl8_palette* pal, u8 idx) { + if (!pal) return 0; + return pxl8_color_to_rgba(pal->colors[idx]); +} + +i32 pxl8_palette_index(const pxl8_palette* pal, u32 rgba) { + if (!pal) return -1; + if (rgba <= 0xFFFFFF) rgba = (rgba << 8) | 0xFF; + u32 abgr = pxl8_color_from_rgba(rgba); + u32 slot = pxl8_palette_hash(abgr); + for (u32 i = 0; i < PXL8_PALETTE_HASH_SIZE; i++) { + u32 idx = (slot + i) & (PXL8_PALETTE_HASH_SIZE - 1); + if (pal->hash[idx].index < 0) return -1; + if (pal->hash[idx].color == abgr) return pal->hash[idx].index; + } + return -1; +} + +void pxl8_palette_get_rgb(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b) { + if (!pal || !r || !g || !b) return; + u8 a; + unpack_rgba(pal->colors[idx], r, g, b, &a); +} + +void pxl8_palette_get_rgba(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b, u8* a) { + if (!pal || !r || !g || !b || !a) return; + unpack_rgba(pal->colors[idx], r, g, b, a); +} + +void pxl8_palette_set(pxl8_palette* pal, u8 idx, u32 color) { + if (pal) pal->colors[idx] = color; +} + +void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b) { + if (pal) pal->colors[idx] = pack_rgb(r, g, b); +} + +void pxl8_palette_set_rgba(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b, u8 a) { + if (pal) pal->colors[idx] = pack_rgba(r, g, b, a); +} + +void pxl8_palette_fill_gradient(pxl8_palette* pal, u8 start, u8 count, u32 from, u32 to) { + if (!pal || count == 0) return; + + u8 r0, g0, b0, a0, r1, g1, b1, a1; + unpack_rgba(from, &r0, &g0, &b0, &a0); + unpack_rgba(to, &r1, &g1, &b1, &a1); + + for (u8 i = 0; i < count; i++) { + f32 t = (count > 1) ? (f32)i / (f32)(count - 1) : 0.0f; + u8 r = (u8)(r0 + (r1 - r0) * t); + u8 g = (u8)(g0 + (g1 - g0) * t); + u8 b = (u8)(b0 + (b1 - b0) * t); + u8 a = (u8)(a0 + (a1 - a0) * t); + pal->colors[start + i] = pack_rgba(r, g, b, a); + } +} + +void pxl8_palette_fill_gradient_rgb(pxl8_palette* pal, u8 start, u8 count, u8 r0, u8 g0, u8 b0, u8 r1, u8 g1, u8 b1) { + pxl8_palette_fill_gradient(pal, start, count, pack_rgb(r0, g0, b0), pack_rgb(r1, g1, b1)); +} + +void pxl8_palette_reset_cycle(pxl8_palette* pal, u8 slot) { + if (!pal || slot >= PXL8_MAX_CYCLES) return; + pal->phases[slot] = 0.0f; + pal->directions[slot] = 1; +} + +void pxl8_palette_set_cycle(pxl8_palette* pal, u8 slot, pxl8_cycle_range range) { + if (!pal || slot >= PXL8_MAX_CYCLES) return; + + pal->cycles[slot] = range; + pal->phases[slot] = 0.0f; + pal->directions[slot] = 1; + + u8 start = range.start; + u8 len = range.len; + if (len > PXL8_MAX_CYCLE_LEN) len = PXL8_MAX_CYCLE_LEN; + + for (u8 i = 0; i < len; i++) { + pal->base_colors[slot][i] = pal->colors[start + i]; + } +} + +void pxl8_palette_set_cycle_colors(pxl8_palette* pal, u8 slot, const u32* colors, u8 count) { + if (!pal || !colors || slot >= PXL8_MAX_CYCLES) return; + + pxl8_cycle_range* cycle = &pal->cycles[slot]; + u8 start = cycle->start; + u8 len = cycle->len; + if (len > count) len = count; + if (len > PXL8_MAX_CYCLE_LEN) len = PXL8_MAX_CYCLE_LEN; + + for (u8 i = 0; i < len; i++) { + pal->base_colors[slot][i] = colors[i]; + pal->colors[start + i] = colors[i]; + } +} + +void pxl8_palette_set_cycle_phase(pxl8_palette* pal, u8 slot, f32 phase) { + if (!pal || slot >= PXL8_MAX_CYCLES) return; + + if (phase < 0.0f) phase = 0.0f; + if (phase > 1.0f) phase = 1.0f; + + pal->phases[slot] = phase; + update_cycle_colors(pal, slot); +} + +void pxl8_palette_tick(pxl8_palette* pal, u16 delta_ticks) { + if (!pal) return; + + for (u8 slot = 0; slot < PXL8_MAX_CYCLES; slot++) { + pxl8_cycle_range* cycle = &pal->cycles[slot]; + if (cycle->period == 0 || cycle->len < 2) continue; + + f32 delta = (f32)delta_ticks / (f32)cycle->period; + f32 dir = (f32)pal->directions[slot]; + pal->phases[slot] += delta * dir; + + switch (cycle->mode) { + case PXL8_CYCLE_LOOP: + while (pal->phases[slot] >= 1.0f) pal->phases[slot] -= 1.0f; + while (pal->phases[slot] < 0.0f) pal->phases[slot] += 1.0f; + break; + + case PXL8_CYCLE_PINGPONG: + if (pal->phases[slot] >= 1.0f) { + pal->phases[slot] = 1.0f - (pal->phases[slot] - 1.0f); + pal->directions[slot] = -1; + } else if (pal->phases[slot] <= 0.0f) { + pal->phases[slot] = -pal->phases[slot]; + pal->directions[slot] = 1; + } + break; + + case PXL8_CYCLE_ONCE: + if (pal->phases[slot] < 0.0f) pal->phases[slot] = 0.0f; + if (pal->phases[slot] > 1.0f) pal->phases[slot] = 1.0f; + break; + } + + update_cycle_colors(pal, slot); + } +} + +pxl8_cycle_range pxl8_cycle_range_new(u8 start, u8 len, u16 period) { + pxl8_cycle_range range = { + .easing = PXL8_EASE_LINEAR, + .interpolate = true, + .len = len, + .mode = PXL8_CYCLE_LOOP, + .period = period, + .start = start, + }; + return range; +} + +pxl8_cycle_range pxl8_cycle_range_disabled(void) { + pxl8_cycle_range range = { + .easing = PXL8_EASE_LINEAR, + .interpolate = false, + .len = 0, + .mode = PXL8_CYCLE_LOOP, + .period = 0, + .start = 0, + }; + return range; +} diff --git a/client/src/gfx/pxl8_palette.h b/client/src/gfx/pxl8_palette.h new file mode 100644 index 0000000..69b38db --- /dev/null +++ b/client/src/gfx/pxl8_palette.h @@ -0,0 +1,70 @@ +#pragma once + +#include "pxl8_types.h" + +#define PXL8_PALETTE_SIZE 256 +#define PXL8_MAX_CYCLES 8 +#define PXL8_MAX_CYCLE_LEN 16 + +typedef struct pxl8_palette pxl8_palette; + +typedef enum pxl8_cycle_mode { + PXL8_CYCLE_LOOP, + PXL8_CYCLE_ONCE, + PXL8_CYCLE_PINGPONG, +} pxl8_cycle_mode; + +typedef enum pxl8_easing { + PXL8_EASE_LINEAR, + PXL8_EASE_IN, + PXL8_EASE_IN_OUT, + PXL8_EASE_OUT, +} pxl8_easing; + +typedef struct pxl8_cycle_range { + pxl8_easing easing; + bool interpolate; + u8 len; + pxl8_cycle_mode mode; + u16 period; + u8 start; +} pxl8_cycle_range; + +#ifdef __cplusplus +extern "C" { +#endif + +pxl8_palette* pxl8_palette_create(void); +void pxl8_palette_destroy(pxl8_palette* pal); + +pxl8_result pxl8_palette_load_ase(pxl8_palette* pal, const char* path); + +u16 pxl8_palette_color_count(const pxl8_palette* pal); +u8* pxl8_palette_color_ramp(pxl8_palette* pal); +u32* pxl8_palette_colors(pxl8_palette* pal); +u8 pxl8_palette_ramp_index(const pxl8_palette* pal, u8 position); +u8 pxl8_palette_ramp_position(const pxl8_palette* pal, u8 index); +u8 pxl8_palette_find_closest(const pxl8_palette* pal, u8 r, u8 g, u8 b); +u32 pxl8_palette_color(const pxl8_palette* pal, u8 idx); +i32 pxl8_palette_index(const pxl8_palette* pal, u32 color); +void pxl8_palette_get_rgb(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b); +void pxl8_palette_get_rgba(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b, u8* a); +void pxl8_palette_set(pxl8_palette* pal, u8 idx, u32 color); +void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b); +void pxl8_palette_set_rgba(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b, u8 a); + +void pxl8_palette_fill_gradient(pxl8_palette* pal, u8 start, u8 count, u32 from, u32 to); +void pxl8_palette_fill_gradient_rgb(pxl8_palette* pal, u8 start, u8 count, u8 r0, u8 g0, u8 b0, u8 r1, u8 g1, u8 b1); + +void pxl8_palette_reset_cycle(pxl8_palette* pal, u8 slot); +void pxl8_palette_set_cycle(pxl8_palette* pal, u8 slot, pxl8_cycle_range range); +void pxl8_palette_set_cycle_colors(pxl8_palette* pal, u8 slot, const u32* colors, u8 count); +void pxl8_palette_set_cycle_phase(pxl8_palette* pal, u8 slot, f32 phase); +void pxl8_palette_tick(pxl8_palette* pal, u16 delta_ticks); + +pxl8_cycle_range pxl8_cycle_range_new(u8 start, u8 len, u16 period); +pxl8_cycle_range pxl8_cycle_range_disabled(void); + +#ifdef __cplusplus +} +#endif diff --git a/client/src/gfx/pxl8_particles.c b/client/src/gfx/pxl8_particles.c new file mode 100644 index 0000000..deb2d83 --- /dev/null +++ b/client/src/gfx/pxl8_particles.c @@ -0,0 +1,322 @@ +#include "pxl8_particles.h" + +#include + +#include "pxl8_gfx.h" +#include "pxl8_gfx2d.h" +#include "pxl8_palette.h" +#include "pxl8_rng.h" + +struct pxl8_particles { + pxl8_particle* particles; + pxl8_palette* palette; + pxl8_rng* rng; + u32 alive_count; + u32 count; + u32 max_count; + + f32 x, y; + f32 spread_x, spread_y; + + f32 drag; + f32 gravity_x, gravity_y; + f32 turbulence; + + f32 spawn_rate; + f32 spawn_timer; + + u8 color_min, color_max; + f32 life_min, life_max; + f32 size_min, size_max; + f32 vx_min, vx_max, vy_min, vy_max; + + pxl8_particle_render_fn render_fn; + pxl8_particle_spawn_fn spawn_fn; + pxl8_particle_update_fn update_fn; + void* userdata; +}; + +pxl8_particles* pxl8_particles_create(u32 max_count, pxl8_rng* rng) { + pxl8_particles* ps = calloc(1, sizeof(pxl8_particles)); + if (!ps) return NULL; + + ps->particles = calloc(max_count, sizeof(pxl8_particle)); + if (!ps->particles) { + free(ps); + return NULL; + } + + ps->rng = rng; + ps->max_count = max_count; + ps->drag = 0.98f; + ps->gravity_y = 100.0f; + ps->spawn_rate = 10.0f; + + ps->color_min = ps->color_max = 15; + ps->life_min = ps->life_max = 1.0f; + ps->size_min = ps->size_max = 1.0f; + + return ps; +} + +void pxl8_particles_destroy(pxl8_particles* ps) { + if (!ps) return; + free(ps->particles); + free(ps); +} + +void pxl8_particles_clear(pxl8_particles* ps) { + if (!ps || !ps->particles) return; + + for (u32 i = 0; i < ps->max_count; i++) { + ps->particles[i].life = 0; + ps->particles[i].flags = 0; + } + ps->alive_count = 0; + ps->spawn_timer = 0; +} + +void pxl8_particles_emit(pxl8_particles* ps, u32 count) { + if (!ps || !ps->particles) return; + + for (u32 i = 0; i < count && ps->alive_count < ps->max_count; i++) { + for (u32 j = 0; j < ps->max_count; j++) { + if (ps->particles[j].life <= 0) { + pxl8_particle* p = &ps->particles[j]; + + f32 life = ps->life_min + pxl8_rng_f32(ps->rng) * (ps->life_max - ps->life_min); + p->life = life; + p->max_life = life; + + p->x = ps->x + (pxl8_rng_f32(ps->rng) - 0.5f) * ps->spread_x; + p->y = ps->y + (pxl8_rng_f32(ps->rng) - 0.5f) * ps->spread_y; + p->z = 0; + + p->vx = ps->vx_min + pxl8_rng_f32(ps->rng) * (ps->vx_max - ps->vx_min); + p->vy = ps->vy_min + pxl8_rng_f32(ps->rng) * (ps->vy_max - ps->vy_min); + p->vz = 0; + + p->ax = ps->gravity_x; + p->ay = ps->gravity_y; + p->az = 0; + + u8 ramp_range = ps->color_max - ps->color_min + 1; + u8 ramp_pos = ps->color_min + (pxl8_rng_next(ps->rng) % ramp_range); + u8 color = ps->palette + ? pxl8_palette_ramp_index(ps->palette, ramp_pos) + : ramp_pos; + p->color = p->start_color = p->end_color = color; + + p->size = ps->size_min + pxl8_rng_f32(ps->rng) * (ps->size_max - ps->size_min); + p->angle = 0; + p->spin = 0; + p->flags = 1; + + if (ps->spawn_fn) { + ps->spawn_fn(ps, p); + } + + ps->alive_count++; + break; + } + } + } +} + +void pxl8_particles_render(pxl8_particles* ps, pxl8_gfx* gfx) { + if (!ps || !ps->particles || !gfx) return; + + for (u32 i = 0; i < ps->max_count; i++) { + pxl8_particle* p = &ps->particles[i]; + if (p->life > 0 && p->flags) { + if (ps->render_fn) { + ps->render_fn(gfx, p, ps->userdata); + } else { + i32 x = (i32)p->x; + i32 y = (i32)p->y; + if (x >= 0 && x < pxl8_gfx_get_width(gfx) && y >= 0 && y < pxl8_gfx_get_height(gfx)) { + pxl8_2d_pixel(gfx, x, y, p->color); + } + } + } + } +} + +void pxl8_particles_update(pxl8_particles* ps, f32 dt) { + if (!ps || !ps->particles) return; + + if (ps->spawn_rate > 0.0f) { + ps->spawn_timer += dt; + f32 spawn_interval = 1.0f / ps->spawn_rate; + u32 max_spawns_per_frame = ps->max_count / 10; + if (max_spawns_per_frame < 1) max_spawns_per_frame = 1; + u32 spawn_count = 0; + while (ps->spawn_timer >= spawn_interval && spawn_count < max_spawns_per_frame) { + pxl8_particles_emit(ps, 1); + ps->spawn_timer -= spawn_interval; + spawn_count++; + } + } + + for (u32 i = 0; i < ps->max_count; i++) { + pxl8_particle* p = &ps->particles[i]; + if (p->life > 0) { + if (ps->update_fn) { + ps->update_fn(p, dt, ps->userdata); + } else { + p->vx += p->ax * dt; + p->vy += p->ay * dt; + p->vz += p->az * dt; + + p->vx *= ps->drag; + p->vy *= ps->drag; + p->vz *= ps->drag; + + p->x += p->vx * dt; + p->y += p->vy * dt; + p->z += p->vz * dt; + + p->angle += p->spin * dt; + } + + p->life -= dt / p->max_life; + if (p->life <= 0) { + p->flags = 0; + ps->alive_count--; + } + } + } +} + +u32 pxl8_particles_count(const pxl8_particles* ps) { + return ps ? ps->alive_count : 0; +} + +u32 pxl8_particles_max_count(const pxl8_particles* ps) { + return ps ? ps->max_count : 0; +} + +pxl8_particle* pxl8_particles_get(pxl8_particles* ps, u32 index) { + if (!ps || index >= ps->max_count) return NULL; + return &ps->particles[index]; +} + +pxl8_rng* pxl8_particles_rng(pxl8_particles* ps) { + return ps ? ps->rng : NULL; +} + +f32 pxl8_particles_get_drag(const pxl8_particles* ps) { + return ps ? ps->drag : 0.0f; +} + +f32 pxl8_particles_get_gravity_x(const pxl8_particles* ps) { + return ps ? ps->gravity_x : 0.0f; +} + +f32 pxl8_particles_get_gravity_y(const pxl8_particles* ps) { + return ps ? ps->gravity_y : 0.0f; +} + +f32 pxl8_particles_get_spawn_rate(const pxl8_particles* ps) { + return ps ? ps->spawn_rate : 0.0f; +} + +f32 pxl8_particles_get_spread_x(const pxl8_particles* ps) { + return ps ? ps->spread_x : 0.0f; +} + +f32 pxl8_particles_get_spread_y(const pxl8_particles* ps) { + return ps ? ps->spread_y : 0.0f; +} + +f32 pxl8_particles_get_turbulence(const pxl8_particles* ps) { + return ps ? ps->turbulence : 0.0f; +} + +void* pxl8_particles_get_userdata(const pxl8_particles* ps) { + return ps ? ps->userdata : NULL; +} + +f32 pxl8_particles_get_x(const pxl8_particles* ps) { + return ps ? ps->x : 0.0f; +} + +f32 pxl8_particles_get_y(const pxl8_particles* ps) { + return ps ? ps->y : 0.0f; +} + +void pxl8_particles_set_colors(pxl8_particles* ps, u8 color_min, u8 color_max) { + if (!ps) return; + ps->color_min = color_min; + ps->color_max = color_max; +} + +void pxl8_particles_set_drag(pxl8_particles* ps, f32 drag) { + if (ps) ps->drag = drag; +} + +void pxl8_particles_set_gravity(pxl8_particles* ps, f32 gx, f32 gy) { + if (!ps) return; + ps->gravity_x = gx; + ps->gravity_y = gy; +} + +void pxl8_particles_set_life(pxl8_particles* ps, f32 life_min, f32 life_max) { + if (!ps) return; + ps->life_min = life_min; + ps->life_max = life_max; +} + +void pxl8_particles_set_palette(pxl8_particles* ps, pxl8_palette* palette) { + if (ps) ps->palette = palette; +} + +void pxl8_particles_set_position(pxl8_particles* ps, f32 x, f32 y) { + if (!ps) return; + ps->x = x; + ps->y = y; +} + +void pxl8_particles_set_render_fn(pxl8_particles* ps, pxl8_particle_render_fn fn) { + if (ps) ps->render_fn = fn; +} + +void pxl8_particles_set_size(pxl8_particles* ps, f32 size_min, f32 size_max) { + if (!ps) return; + ps->size_min = size_min; + ps->size_max = size_max; +} + +void pxl8_particles_set_spawn_fn(pxl8_particles* ps, pxl8_particle_spawn_fn fn) { + if (ps) ps->spawn_fn = fn; +} + +void pxl8_particles_set_spawn_rate(pxl8_particles* ps, f32 rate) { + if (ps) ps->spawn_rate = rate; +} + +void pxl8_particles_set_spread(pxl8_particles* ps, f32 spread_x, f32 spread_y) { + if (!ps) return; + ps->spread_x = spread_x; + ps->spread_y = spread_y; +} + +void pxl8_particles_set_turbulence(pxl8_particles* ps, f32 turbulence) { + if (ps) ps->turbulence = turbulence; +} + +void pxl8_particles_set_update_fn(pxl8_particles* ps, pxl8_particle_update_fn fn) { + if (ps) ps->update_fn = fn; +} + +void pxl8_particles_set_userdata(pxl8_particles* ps, void* userdata) { + if (ps) ps->userdata = userdata; +} + +void pxl8_particles_set_velocity(pxl8_particles* ps, f32 vx_min, f32 vx_max, f32 vy_min, f32 vy_max) { + if (!ps) return; + ps->vx_min = vx_min; + ps->vx_max = vx_max; + ps->vy_min = vy_min; + ps->vy_max = vy_max; +} diff --git a/client/src/gfx/pxl8_particles.h b/client/src/gfx/pxl8_particles.h new file mode 100644 index 0000000..881b277 --- /dev/null +++ b/client/src/gfx/pxl8_particles.h @@ -0,0 +1,75 @@ +#pragma once + +#include "pxl8_types.h" + +typedef struct pxl8_gfx pxl8_gfx; +typedef struct pxl8_palette pxl8_palette; +typedef struct pxl8_particles pxl8_particles; +typedef struct pxl8_rng pxl8_rng; + +typedef struct pxl8_particle { + f32 angle; + f32 ax, ay, az; + u32 color; + u32 end_color; + u8 flags; + f32 life; + f32 max_life; + f32 size; + f32 spin; + u32 start_color; + f32 vx, vy, vz; + f32 x, y, z; +} pxl8_particle; + +typedef void (*pxl8_particle_render_fn)(pxl8_gfx* gfx, pxl8_particle* p, void* userdata); +typedef void (*pxl8_particle_spawn_fn)(pxl8_particles* ps, pxl8_particle* p); +typedef void (*pxl8_particle_update_fn)(pxl8_particle* p, f32 dt, void* userdata); + +#ifdef __cplusplus +extern "C" { +#endif + +pxl8_particles* pxl8_particles_create(u32 max_count, pxl8_rng* rng); +void pxl8_particles_destroy(pxl8_particles* ps); + +void pxl8_particles_clear(pxl8_particles* ps); +void pxl8_particles_emit(pxl8_particles* ps, u32 count); +void pxl8_particles_render(pxl8_particles* ps, pxl8_gfx* gfx); +void pxl8_particles_update(pxl8_particles* ps, f32 dt); + +u32 pxl8_particles_count(const pxl8_particles* ps); +u32 pxl8_particles_max_count(const pxl8_particles* ps); +pxl8_particle* pxl8_particles_get(pxl8_particles* ps, u32 index); +pxl8_rng* pxl8_particles_rng(pxl8_particles* ps); + +f32 pxl8_particles_get_drag(const pxl8_particles* ps); +f32 pxl8_particles_get_gravity_x(const pxl8_particles* ps); +f32 pxl8_particles_get_gravity_y(const pxl8_particles* ps); +f32 pxl8_particles_get_spawn_rate(const pxl8_particles* ps); +f32 pxl8_particles_get_spread_x(const pxl8_particles* ps); +f32 pxl8_particles_get_spread_y(const pxl8_particles* ps); +f32 pxl8_particles_get_turbulence(const pxl8_particles* ps); +void* pxl8_particles_get_userdata(const pxl8_particles* ps); +f32 pxl8_particles_get_x(const pxl8_particles* ps); +f32 pxl8_particles_get_y(const pxl8_particles* ps); + +void pxl8_particles_set_colors(pxl8_particles* ps, u8 color_min, u8 color_max); +void pxl8_particles_set_drag(pxl8_particles* ps, f32 drag); +void pxl8_particles_set_gravity(pxl8_particles* ps, f32 gx, f32 gy); +void pxl8_particles_set_life(pxl8_particles* ps, f32 life_min, f32 life_max); +void pxl8_particles_set_palette(pxl8_particles* ps, pxl8_palette* palette); +void pxl8_particles_set_position(pxl8_particles* ps, f32 x, f32 y); +void pxl8_particles_set_render_fn(pxl8_particles* ps, pxl8_particle_render_fn fn); +void pxl8_particles_set_size(pxl8_particles* ps, f32 size_min, f32 size_max); +void pxl8_particles_set_spawn_fn(pxl8_particles* ps, pxl8_particle_spawn_fn fn); +void pxl8_particles_set_spawn_rate(pxl8_particles* ps, f32 rate); +void pxl8_particles_set_spread(pxl8_particles* ps, f32 spread_x, f32 spread_y); +void pxl8_particles_set_turbulence(pxl8_particles* ps, f32 turbulence); +void pxl8_particles_set_update_fn(pxl8_particles* ps, pxl8_particle_update_fn fn); +void pxl8_particles_set_userdata(pxl8_particles* ps, void* userdata); +void pxl8_particles_set_velocity(pxl8_particles* ps, f32 vx_min, f32 vx_max, f32 vy_min, f32 vy_max); + +#ifdef __cplusplus +} +#endif diff --git a/src/pxl8_tilemap.c b/client/src/gfx/pxl8_tilemap.c similarity index 100% rename from src/pxl8_tilemap.c rename to client/src/gfx/pxl8_tilemap.c diff --git a/src/pxl8_tilemap.h b/client/src/gfx/pxl8_tilemap.h similarity index 100% rename from src/pxl8_tilemap.h rename to client/src/gfx/pxl8_tilemap.h diff --git a/src/pxl8_tilesheet.c b/client/src/gfx/pxl8_tilesheet.c similarity index 99% rename from src/pxl8_tilesheet.c rename to client/src/gfx/pxl8_tilesheet.c index 01fd5bb..3f8dd39 100644 --- a/src/pxl8_tilesheet.c +++ b/client/src/gfx/pxl8_tilesheet.c @@ -5,6 +5,7 @@ #include "pxl8_ase.h" #include "pxl8_color.h" +#include "pxl8_gfx.h" #include "pxl8_log.h" #include "pxl8_tilemap.h" @@ -227,7 +228,7 @@ void pxl8_tilesheet_render_tile(const pxl8_tilesheet* tilesheet, pxl8_gfx* gfx, if (screen_x >= 0 && screen_x < pxl8_gfx_get_width(gfx) && screen_y >= 0 && screen_y < pxl8_gfx_get_height(gfx)) { - pxl8_pixel(gfx, screen_x, screen_y, color_idx); + pxl8_2d_pixel(gfx, screen_x, screen_y, color_idx); } } } diff --git a/src/pxl8_tilesheet.h b/client/src/gfx/pxl8_tilesheet.h similarity index 100% rename from src/pxl8_tilesheet.h rename to client/src/gfx/pxl8_tilesheet.h diff --git a/src/pxl8_transition.c b/client/src/gfx/pxl8_transition.c similarity index 89% rename from src/pxl8_transition.c rename to client/src/gfx/pxl8_transition.c index e648617..f19001f 100644 --- a/src/pxl8_transition.c +++ b/client/src/gfx/pxl8_transition.c @@ -1,8 +1,9 @@ #include "pxl8_transition.h" -#include #include +#include +#include "pxl8_gfx.h" #include "pxl8_log.h" #include "pxl8_math.h" @@ -68,7 +69,7 @@ void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx) { for (i32 y = 0; y < height; y++) { for (i32 x = 0; x < width; x++) { - u32 bg = pxl8_get_pixel(gfx, x, y); + u32 bg = pxl8_2d_get_pixel(gfx, x, y); u32 r_bg = (bg >> 16) & 0xFF; u32 g_bg = (bg >> 8) & 0xFF; u32 b_bg = bg & 0xFF; @@ -81,7 +82,7 @@ void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx) { u32 g = (g_bg * (255 - alpha) + g_fg * alpha) / 255; u32 b = (b_bg * (255 - alpha) + b_fg * alpha) / 255; - pxl8_pixel(gfx, x, y, 0xFF000000 | (r << 16) | (g << 8) | b); + pxl8_2d_pixel(gfx, x, y, 0xFF000000 | (r << 16) | (g << 8) | b); } } break; @@ -89,25 +90,25 @@ void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx) { case PXL8_TRANSITION_WIPE_LEFT: { i32 wipe_x = (i32)(width * progress); - pxl8_rect_fill(gfx, 0, 0, wipe_x, height, transition->color); + pxl8_2d_rect_fill(gfx, 0, 0, wipe_x, height, transition->color); break; } case PXL8_TRANSITION_WIPE_RIGHT: { i32 wipe_x = (i32)(width * (1.0f - progress)); - pxl8_rect_fill(gfx, wipe_x, 0, width - wipe_x, height, transition->color); + pxl8_2d_rect_fill(gfx, wipe_x, 0, width - wipe_x, height, transition->color); break; } case PXL8_TRANSITION_WIPE_UP: { i32 wipe_y = (i32)(height * progress); - pxl8_rect_fill(gfx, 0, 0, width, wipe_y, transition->color); + pxl8_2d_rect_fill(gfx, 0, 0, width, wipe_y, transition->color); break; } case PXL8_TRANSITION_WIPE_DOWN: { i32 wipe_y = (i32)(height * (1.0f - progress)); - pxl8_rect_fill(gfx, 0, wipe_y, width, height - wipe_y, transition->color); + pxl8_2d_rect_fill(gfx, 0, wipe_y, width, height - wipe_y, transition->color); break; } @@ -123,7 +124,7 @@ void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx) { i32 dy = y - center_y; i32 dist = (i32)sqrtf((f32)(dx * dx + dy * dy)); if (dist > radius) { - pxl8_pixel(gfx, x, y, transition->color); + pxl8_2d_pixel(gfx, x, y, transition->color); } } } @@ -142,7 +143,7 @@ void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx) { i32 dy = y - center_y; i32 dist = (i32)sqrtf((f32)(dx * dx + dy * dy)); if (dist < radius) { - pxl8_pixel(gfx, x, y, transition->color); + pxl8_2d_pixel(gfx, x, y, transition->color); } } } @@ -156,7 +157,7 @@ void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx) { seed = seed * 1103515245 + 12345; f32 noise = (f32)((seed / 65536) % 1000) / 1000.0f; if (noise < progress) { - pxl8_pixel(gfx, x, y, transition->color); + pxl8_2d_pixel(gfx, x, y, transition->color); } } } @@ -181,7 +182,7 @@ void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx) { for (i32 by = 0; by < block_size && y + by < height; by++) { for (i32 bx = 0; bx < block_size && x + bx < width; bx++) { - u32 color = pxl8_get_pixel(gfx, x + bx, y + by); + u32 color = pxl8_2d_get_pixel(gfx, x + bx, y + by); color_sum_r += (color >> 16) & 0xFF; color_sum_g += (color >> 8) & 0xFF; color_sum_b += color & 0xFF; @@ -197,7 +198,7 @@ void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx) { for (i32 by = 0; by < block_size && y + by < height; by++) { for (i32 bx = 0; bx < block_size && x + bx < width; bx++) { - pxl8_pixel(gfx, x + bx, y + by, avg_color); + pxl8_2d_pixel(gfx, x + bx, y + by, avg_color); } } } diff --git a/src/pxl8_transition.h b/client/src/gfx/pxl8_transition.h similarity index 100% rename from src/pxl8_transition.h rename to client/src/gfx/pxl8_transition.h diff --git a/src/pxl8_hal.h b/client/src/hal/pxl8_hal.h similarity index 100% rename from src/pxl8_hal.h rename to client/src/hal/pxl8_hal.h diff --git a/src/pxl8_sdl3.c b/client/src/hal/pxl8_sdl3.c similarity index 100% rename from src/pxl8_sdl3.c rename to client/src/hal/pxl8_sdl3.c diff --git a/src/pxl8_sdl3.h b/client/src/hal/pxl8_sdl3.h similarity index 100% rename from src/pxl8_sdl3.h rename to client/src/hal/pxl8_sdl3.h diff --git a/client/src/lua/pxl8.lua b/client/src/lua/pxl8.lua new file mode 100644 index 0000000..5f91f34 --- /dev/null +++ b/client/src/lua/pxl8.lua @@ -0,0 +1,162 @@ +local anim = require("pxl8.anim") +local core = require("pxl8.core") +local gfx2d = require("pxl8.gfx2d") +local gfx3d = require("pxl8.gfx3d") +local gui = require("pxl8.gui") +local input = require("pxl8.input") +local math3d = require("pxl8.math") +local particles = require("pxl8.particles") +local sfx = require("pxl8.sfx") +local tilemap = require("pxl8.tilemap") +local transition = require("pxl8.transition") +local world = require("pxl8.world") +local pxl8 = {} + +core.init(pxl8_gfx, pxl8_input, pxl8_rng, pxl8_sfx, pxl8_sys) + +pxl8.get_fps = core.get_fps +pxl8.get_height = core.get_height +pxl8.get_title = core.get_title +pxl8.get_width = core.get_width +pxl8.info = core.info +pxl8.warn = core.warn +pxl8.error = core.error +pxl8.debug = core.debug +pxl8.trace = core.trace +pxl8.quit = core.quit + +pxl8.rng_seed = core.rng_seed +pxl8.rng_next = core.rng_next +pxl8.rng_f32 = core.rng_f32 +pxl8.rng_range = core.rng_range + +pxl8.find_color = core.find_color +pxl8.palette_color = core.palette_color +pxl8.palette_index = core.palette_index +pxl8.ramp_index = core.ramp_index + +pxl8.clear = gfx2d.clear +pxl8.pixel = gfx2d.pixel +pxl8.line = gfx2d.line +pxl8.rect = gfx2d.rect +pxl8.rect_fill = gfx2d.rect_fill +pxl8.circle = gfx2d.circle +pxl8.circle_fill = gfx2d.circle_fill +pxl8.text = gfx2d.text +pxl8.sprite = gfx2d.sprite +pxl8.load_palette = gfx2d.load_palette +pxl8.load_sprite = gfx2d.load_sprite +pxl8.create_texture = gfx2d.create_texture +pxl8.gfx_color_ramp = gfx2d.color_ramp +pxl8.gfx_fade_palette = gfx2d.fade_palette +pxl8.gfx_cycle_palette = gfx2d.cycle_palette +pxl8.add_palette_cycle = gfx2d.add_palette_cycle +pxl8.remove_palette_cycle = gfx2d.remove_palette_cycle +pxl8.set_palette_cycle_speed = gfx2d.set_palette_cycle_speed +pxl8.clear_palette_cycles = gfx2d.clear_palette_cycles + +pxl8.key_down = input.key_down +pxl8.key_pressed = input.key_pressed +pxl8.key_released = input.key_released +pxl8.mouse_dx = input.mouse_dx +pxl8.mouse_dy = input.mouse_dy +pxl8.mouse_wheel_x = input.mouse_wheel_x +pxl8.mouse_wheel_y = input.mouse_wheel_y +pxl8.mouse_x = input.mouse_x +pxl8.mouse_y = input.mouse_y +pxl8.get_mouse_pos = input.get_mouse_pos +pxl8.mouse_pressed = input.mouse_pressed +pxl8.mouse_released = input.mouse_released +pxl8.center_cursor = input.center_cursor +pxl8.set_cursor = input.set_cursor +pxl8.set_relative_mouse_mode = input.set_relative_mouse_mode + +pxl8.Particles = particles.Particles +pxl8.create_particles = function(max_count) return particles.Particles.new(max_count) end + +pxl8.Tilesheet = tilemap.Tilesheet +pxl8.Tilemap = tilemap.Tilemap +pxl8.create_tilesheet = function(tile_size) return tilemap.Tilesheet.new(tile_size) end +pxl8.create_tilemap = function(w, h, tile_size) return tilemap.Tilemap.new(w, h, tile_size) end +pxl8.TILE_FLIP_X = tilemap.TILE_FLIP_X +pxl8.TILE_FLIP_Y = tilemap.TILE_FLIP_Y +pxl8.TILE_SOLID = tilemap.TILE_SOLID +pxl8.TILE_TRIGGER = tilemap.TILE_TRIGGER + +pxl8.Camera3D = gfx3d.Camera3D +pxl8.create_camera_3d = function() return gfx3d.Camera3D.new() end +pxl8.begin_frame_3d = gfx3d.begin_frame +pxl8.end_frame_3d = gfx3d.end_frame +pxl8.clear_3d = gfx3d.clear +pxl8.clear_depth = gfx3d.clear_depth +pxl8.draw_line_3d = gfx3d.draw_line +pxl8.Mesh = gfx3d.Mesh +pxl8.create_mesh = function(vertices, indices) return gfx3d.Mesh.new(vertices, indices) end +pxl8.draw_mesh = gfx3d.draw_mesh + +pxl8.mat4_identity = math3d.mat4_identity +pxl8.mat4_multiply = math3d.mat4_multiply +pxl8.mat4_translate = math3d.mat4_translate +pxl8.mat4_rotate_x = math3d.mat4_rotate_x +pxl8.mat4_rotate_y = math3d.mat4_rotate_y +pxl8.mat4_rotate_z = math3d.mat4_rotate_z +pxl8.mat4_scale = math3d.mat4_scale +pxl8.mat4_ortho = math3d.mat4_ortho +pxl8.mat4_perspective = math3d.mat4_perspective +pxl8.mat4_lookat = math3d.mat4_lookat +pxl8.bounds = math3d.bounds + +pxl8.Gui = gui.Gui +pxl8.create_gui = function() return gui.Gui.new() end +pxl8.gui_label = gui.label +pxl8.gui_window = gui.window + +pxl8.World = world.World +pxl8.create_world = function() return world.World.new() end +pxl8.procgen_tex = world.procgen_tex +pxl8.PROCGEN_ROOMS = world.PROCGEN_ROOMS +pxl8.PROCGEN_TERRAIN = world.PROCGEN_TERRAIN + +pxl8.Transition = transition.Transition +pxl8.create_transition = function(type_name, duration) return transition.Transition.new(type_name, duration) end +pxl8.TRANSITION_TYPES = transition.TYPES + +pxl8.Anim = anim.Anim +pxl8.create_anim = function(frame_ids, frame_durations) return anim.Anim.new(frame_ids, frame_durations) end +pxl8.create_anim_from_ase = function(filepath) return anim.Anim.from_ase(filepath) end + +pxl8.SfxContext = sfx.SfxContext +pxl8.SfxNode = sfx.SfxNode +pxl8.Compressor = sfx.Compressor +pxl8.Delay = sfx.Delay +pxl8.Reverb = sfx.Reverb +pxl8.create_sfx_context = function() return sfx.SfxContext.new() end +pxl8.create_compressor = function(opts) return sfx.Compressor.new(opts) end +pxl8.create_delay = function(opts) return sfx.Delay.new(opts) end +pxl8.create_reverb = function(opts) return sfx.Reverb.new(opts) end +pxl8.sfx_get_master_volume = sfx.get_master_volume +pxl8.sfx_set_master_volume = sfx.set_master_volume +pxl8.sfx_note_to_freq = sfx.note_to_freq +pxl8.sfx_voice_params = sfx.voice_params + +pxl8.SFX_FILTER_BANDPASS = sfx.FILTER_BANDPASS +pxl8.SFX_FILTER_HIGHPASS = sfx.FILTER_HIGHPASS +pxl8.SFX_FILTER_LOWPASS = sfx.FILTER_LOWPASS +pxl8.SFX_FILTER_NONE = sfx.FILTER_NONE + +pxl8.SFX_LFO_AMPLITUDE = sfx.LFO_AMPLITUDE +pxl8.SFX_LFO_FILTER = sfx.LFO_FILTER +pxl8.SFX_LFO_PITCH = sfx.LFO_PITCH + +pxl8.SFX_NODE_COMPRESSOR = sfx.NODE_COMPRESSOR +pxl8.SFX_NODE_DELAY = sfx.NODE_DELAY +pxl8.SFX_NODE_REVERB = sfx.NODE_REVERB + +pxl8.SFX_WAVE_NOISE = sfx.WAVE_NOISE +pxl8.SFX_WAVE_PULSE = sfx.WAVE_PULSE +pxl8.SFX_WAVE_SAW = sfx.WAVE_SAW +pxl8.SFX_WAVE_SINE = sfx.WAVE_SINE +pxl8.SFX_WAVE_SQUARE = sfx.WAVE_SQUARE +pxl8.SFX_WAVE_TRIANGLE = sfx.WAVE_TRIANGLE + +return pxl8 diff --git a/client/src/lua/pxl8/anim.lua b/client/src/lua/pxl8/anim.lua new file mode 100644 index 0000000..970bc75 --- /dev/null +++ b/client/src/lua/pxl8/anim.lua @@ -0,0 +1,126 @@ +local ffi = require("ffi") +local C = ffi.C +local core = require("pxl8.core") + +local anim = {} + +local Anim = {} +Anim.__index = Anim + +function Anim.new(frame_ids, frame_durations) + local frame_count = #frame_ids + local c_frame_ids = ffi.new("u32[?]", frame_count) + local c_frame_durations = nil + + for i = 1, frame_count do + c_frame_ids[i-1] = frame_ids[i] + end + + if frame_durations then + c_frame_durations = ffi.new("u16[?]", frame_count) + for i = 1, frame_count do + c_frame_durations[i-1] = frame_durations[i] + end + end + + local a = C.pxl8_anim_create(c_frame_ids, c_frame_durations, frame_count) + if a == nil then + return nil + end + return setmetatable({ _ptr = a }, Anim) +end + +function Anim.from_ase(filepath) + local a = C.pxl8_anim_create_from_ase(core.gfx, filepath) + if a == nil then + return nil + end + return setmetatable({ _ptr = a }, Anim) +end + +function Anim:add_state(name, state_anim) + return C.pxl8_anim_add_state(self._ptr, name, state_anim._ptr) +end + +function Anim:destroy() + if self._ptr then + C.pxl8_anim_destroy(self._ptr) + self._ptr = nil + end +end + +function Anim:get_current_frame() + return C.pxl8_anim_get_current_frame(self._ptr) +end + +function Anim:get_current_frame_id() + return C.pxl8_anim_get_current_frame_id(self._ptr) +end + +function Anim:get_state() + local state_name = C.pxl8_anim_get_state(self._ptr) + if state_name ~= nil then + return ffi.string(state_name) + end + return nil +end + +function Anim:has_state_machine() + return C.pxl8_anim_has_state_machine(self._ptr) +end + +function Anim:is_complete() + return C.pxl8_anim_is_complete(self._ptr) +end + +function Anim:is_playing() + return C.pxl8_anim_is_playing(self._ptr) +end + +function Anim:pause() + C.pxl8_anim_pause(self._ptr) +end + +function Anim:play() + C.pxl8_anim_play(self._ptr) +end + +function Anim:render(x, y, w, h, flip_x, flip_y) + C.pxl8_anim_render_sprite(self._ptr, core.gfx, x, y, w, h, flip_x or false, flip_y or false) +end + +function Anim:reset() + C.pxl8_anim_reset(self._ptr) +end + +function Anim:set_frame(frame) + C.pxl8_anim_set_frame(self._ptr, frame) +end + +function Anim:set_loop(loop) + C.pxl8_anim_set_loop(self._ptr, loop) +end + +function Anim:set_reverse(reverse) + C.pxl8_anim_set_reverse(self._ptr, reverse) +end + +function Anim:set_speed(speed) + C.pxl8_anim_set_speed(self._ptr, speed) +end + +function Anim:set_state(name) + return C.pxl8_anim_set_state(self._ptr, name) +end + +function Anim:stop() + C.pxl8_anim_stop(self._ptr) +end + +function Anim:update(dt) + C.pxl8_anim_update(self._ptr, dt) +end + +anim.Anim = Anim + +return anim diff --git a/src/lua/pxl8/core.lua b/client/src/lua/pxl8/core.lua similarity index 76% rename from src/lua/pxl8/core.lua rename to client/src/lua/pxl8/core.lua index 58860f0..ecee66d 100644 --- a/src/lua/pxl8/core.lua +++ b/client/src/lua/pxl8/core.lua @@ -11,10 +11,32 @@ function core.init(gfx, input, rng, sfx, sys) core.sys = sys end +function core.find_color(color) + return C.pxl8_gfx_find_color(core.gfx, color) +end + function core.get_fps() return C.pxl8_get_fps(core.sys) end +function core.palette_color(index) + local pal = C.pxl8_gfx_get_palette(core.gfx) + if pal == nil then return 0 end + return C.pxl8_palette_color(pal, index) +end + +function core.palette_index(color) + local pal = C.pxl8_gfx_get_palette(core.gfx) + if pal == nil then return -1 end + return C.pxl8_palette_index(pal, color) +end + +function core.ramp_index(position) + local pal = C.pxl8_gfx_get_palette(core.gfx) + if pal == nil then return 0 end + return C.pxl8_palette_ramp_index(pal, position) +end + function core.get_width() return C.pxl8_gfx_get_width(core.gfx) end diff --git a/src/lua/pxl8/gfx2d.lua b/client/src/lua/pxl8/gfx2d.lua similarity index 75% rename from src/lua/pxl8/gfx2d.lua rename to client/src/lua/pxl8/gfx2d.lua index 819e9de..3f852d0 100644 --- a/src/lua/pxl8/gfx2d.lua +++ b/client/src/lua/pxl8/gfx2d.lua @@ -5,43 +5,43 @@ local core = require("pxl8.core") local graphics = {} function graphics.clear(color) - C.pxl8_clear(core.gfx, color or 0) + C.pxl8_2d_clear(core.gfx, color or 0) end function graphics.pixel(x, y, color) if color then - C.pxl8_pixel(core.gfx, x, y, color) + C.pxl8_2d_pixel(core.gfx, x, y, color) else - return C.pxl8_get_pixel(core.gfx, x, y) + return C.pxl8_2d_get_pixel(core.gfx, x, y) end end function graphics.line(x0, y0, x1, y1, color) - C.pxl8_line(core.gfx, x0, y0, x1, y1, color) + C.pxl8_2d_line(core.gfx, x0, y0, x1, y1, color) end function graphics.rect(x, y, w, h, color) - C.pxl8_rect(core.gfx, x, y, w, h, color) + C.pxl8_2d_rect(core.gfx, x, y, w, h, color) end function graphics.rect_fill(x, y, w, h, color) - C.pxl8_rect_fill(core.gfx, x, y, w, h, color) + C.pxl8_2d_rect_fill(core.gfx, x, y, w, h, color) end function graphics.circle(x, y, r, color) - C.pxl8_circle(core.gfx, x, y, r, color) + C.pxl8_2d_circle(core.gfx, x, y, r, color) end function graphics.circle_fill(x, y, r, color) - C.pxl8_circle_fill(core.gfx, x, y, r, color) + C.pxl8_2d_circle_fill(core.gfx, x, y, r, color) end function graphics.text(str, x, y, color) - C.pxl8_text(core.gfx, str, x or 0, y or 0, color or 15) + C.pxl8_2d_text(core.gfx, str, x or 0, y or 0, color or 15) end function graphics.sprite(id, x, y, w, h, flip_x, flip_y) - C.pxl8_sprite(core.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_2d_sprite(core.gfx, id or 0, x or 0, y or 0, w or 16, h or 16, flip_x or false, flip_y or false) end function graphics.load_palette(filepath) @@ -98,4 +98,12 @@ function graphics.clear_palette_cycles() C.pxl8_gfx_clear_palette_cycles(core.gfx) end +function graphics.push_target() + return C.pxl8_gfx_push_target(core.gfx) +end + +function graphics.pop_target() + C.pxl8_gfx_pop_target(core.gfx) +end + return graphics diff --git a/client/src/lua/pxl8/gfx3d.lua b/client/src/lua/pxl8/gfx3d.lua new file mode 100644 index 0000000..3772263 --- /dev/null +++ b/client/src/lua/pxl8/gfx3d.lua @@ -0,0 +1,161 @@ +local ffi = require("ffi") +local C = ffi.C +local core = require("pxl8.core") + +local gfx3d = {} + +local Camera3D = {} +Camera3D.__index = Camera3D + +function Camera3D.new() + local cam = C.pxl8_3d_camera_create() + if cam == nil then + return nil + end + return setmetatable({ _ptr = cam }, Camera3D) +end + +function Camera3D:destroy() + if self._ptr then + C.pxl8_3d_camera_destroy(self._ptr) + self._ptr = nil + end +end + +function Camera3D:get_forward() + local v = C.pxl8_3d_camera_get_forward(self._ptr) + return {v.x, v.y, v.z} +end + +function Camera3D:get_position() + local v = C.pxl8_3d_camera_get_position(self._ptr) + return {v.x, v.y, v.z} +end + +function Camera3D:get_right() + local v = C.pxl8_3d_camera_get_right(self._ptr) + return {v.x, v.y, v.z} +end + +function Camera3D:get_up() + local v = C.pxl8_3d_camera_get_up(self._ptr) + return {v.x, v.y, v.z} +end + +function Camera3D:lookat(eye, target, up) + up = up or {0, 1, 0} + local eye_vec = ffi.new("pxl8_vec3", {x = eye[1], y = eye[2], z = eye[3]}) + local target_vec = ffi.new("pxl8_vec3", {x = target[1], y = target[2], z = target[3]}) + local up_vec = ffi.new("pxl8_vec3", {x = up[1], y = up[2], z = up[3]}) + C.pxl8_3d_camera_lookat(self._ptr, eye_vec, target_vec, up_vec) +end + +function Camera3D:set_perspective(fov, aspect, near, far) + C.pxl8_3d_camera_set_perspective(self._ptr, fov, aspect, near, far) +end + +function Camera3D:set_position(x, y, z) + local pos = ffi.new("pxl8_vec3", {x = x, y = y, z = z}) + C.pxl8_3d_camera_set_position(self._ptr, pos) +end + +function Camera3D:set_rotation(pitch, yaw, roll) + C.pxl8_3d_camera_set_rotation(self._ptr, pitch, yaw or 0, roll or 0) +end + +function Camera3D:update(dt) + C.pxl8_3d_camera_update(self._ptr, dt) +end + +gfx3d.Camera3D = Camera3D + +local Mesh = {} +Mesh.__index = Mesh + +function Mesh.new(vertices, indices) + local vertex_count = #vertices + local index_count = #indices + local mesh = C.pxl8_mesh_create(vertex_count, index_count) + if mesh == nil then + return nil + end + local self = setmetatable({ _ptr = mesh }, Mesh) + for _, v in ipairs(vertices) do + local vert = ffi.new("pxl8_vertex", { + position = {x = v.x or 0, y = v.y or 0, z = v.z or 0}, + normal = {x = v.nx or 0, y = v.ny or 0, z = v.nz or 0}, + u = v.u or 0, + v = v.v or 0, + color = v.color or 0, + light = v.light or 255, + }) + C.pxl8_mesh_push_vertex(mesh, vert) + end + for i = 1, #indices, 3 do + C.pxl8_mesh_push_triangle(mesh, indices[i], indices[i+1], indices[i+2]) + end + return self +end + +function Mesh:destroy() + if self._ptr then + C.pxl8_mesh_destroy(self._ptr) + self._ptr = nil + end +end + +function Mesh:clear() + if self._ptr then + C.pxl8_mesh_clear(self._ptr) + end +end + +gfx3d.Mesh = Mesh + +function gfx3d.draw_mesh(mesh, opts) + opts = opts or {} + local model = C.pxl8_mat4_identity() + local material = ffi.new("pxl8_material", { + texture_id = opts.texture or 0, + alpha = opts.alpha or 255, + blend_mode = opts.blend_mode or 0, + dither = opts.dither ~= false, + double_sided = opts.double_sided or false, + dynamic_lighting = opts.lighting or false, + per_pixel = opts.per_pixel or false, + vertex_color_passthrough = opts.passthrough or false, + emissive_intensity = opts.emissive or 0.0, + }) + C.pxl8_3d_draw_mesh(core.gfx, mesh._ptr, model, material) +end + +function gfx3d.begin_frame(camera, uniforms) + uniforms = uniforms or {} + local u = ffi.new("pxl8_3d_uniforms", { + ambient = uniforms.ambient or 0, + fog_color = uniforms.fog_color or 0, + fog_density = uniforms.fog_density or 0.0, + time = uniforms.time or 0.0, + }) + C.pxl8_3d_begin_frame(core.gfx, camera._ptr, u) +end + +function gfx3d.clear(color) + C.pxl8_3d_clear(core.gfx, color or 0) +end + +function gfx3d.clear_depth() + C.pxl8_3d_clear_depth(core.gfx) +end + +function gfx3d.draw_line(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(core.gfx, vec0, vec1, color) +end + +function gfx3d.end_frame() + C.pxl8_3d_end_frame(core.gfx) +end + +return gfx3d diff --git a/client/src/lua/pxl8/gui.lua b/client/src/lua/pxl8/gui.lua new file mode 100644 index 0000000..b8cba94 --- /dev/null +++ b/client/src/lua/pxl8/gui.lua @@ -0,0 +1,70 @@ +local ffi = require("ffi") +local C = ffi.C +local core = require("pxl8.core") + +local gui = {} + +local Gui = {} +Gui.__index = Gui + +function Gui.new() + local s = C.pxl8_gui_state_create() + if s == nil then + return nil + end + return setmetatable({ _ptr = s }, Gui) +end + +function Gui:begin_frame() + C.pxl8_gui_begin_frame(self._ptr) +end + +function Gui:button(id, x, y, w, h, label) + return C.pxl8_gui_button(self._ptr, core.gfx, id, x, y, w, h, label) +end + +function Gui:cursor_down() + C.pxl8_gui_cursor_down(self._ptr) +end + +function Gui:cursor_move(x, y) + C.pxl8_gui_cursor_move(self._ptr, x, y) +end + +function Gui:cursor_up() + C.pxl8_gui_cursor_up(self._ptr) +end + +function Gui:destroy() + if self._ptr then + C.pxl8_gui_state_destroy(self._ptr) + self._ptr = nil + end +end + +function Gui:end_frame() + C.pxl8_gui_end_frame(self._ptr) +end + +function Gui:get_cursor_pos() + local x = ffi.new("i32[1]") + local y = ffi.new("i32[1]") + C.pxl8_gui_get_cursor_pos(self._ptr, x, y) + return x[0], y[0] +end + +function Gui:is_hovering() + return C.pxl8_gui_is_hovering(self._ptr) +end + +gui.Gui = Gui + +function gui.label(x, y, text, color) + C.pxl8_gui_label(core.gfx, x, y, text, color) +end + +function gui.window(x, y, w, h, title) + C.pxl8_gui_window(core.gfx, x, y, w, h, title) +end + +return gui diff --git a/src/lua/pxl8/input.lua b/client/src/lua/pxl8/input.lua similarity index 100% rename from src/lua/pxl8/input.lua rename to client/src/lua/pxl8/input.lua diff --git a/src/lua/pxl8/math.lua b/client/src/lua/pxl8/math.lua similarity index 94% rename from src/lua/pxl8/math.lua rename to client/src/lua/pxl8/math.lua index 3dcbe15..c82ad84 100644 --- a/src/lua/pxl8/math.lua +++ b/client/src/lua/pxl8/math.lua @@ -7,8 +7,8 @@ function math3d.mat4_identity() return C.pxl8_mat4_identity() end -function math3d.mat4_multiply(a, b) - return C.pxl8_mat4_multiply(a, b) +function math3d.mat4_mul(a, b) + return C.pxl8_mat4_mul(a, b) end function math3d.mat4_translate(x, y, z) diff --git a/client/src/lua/pxl8/particles.lua b/client/src/lua/pxl8/particles.lua new file mode 100644 index 0000000..41eacef --- /dev/null +++ b/client/src/lua/pxl8/particles.lua @@ -0,0 +1,103 @@ +local ffi = require("ffi") +local C = ffi.C +local core = require("pxl8.core") + +local particles = {} + +local Particles = {} +Particles.__index = Particles + +function Particles.new(max_count) + local ps = C.pxl8_particles_create(max_count or 1000, core.rng) + if ps == nil then + return nil + end + local pal = C.pxl8_gfx_get_palette(core.gfx) + if pal ~= nil then + C.pxl8_particles_set_palette(ps, pal) + end + return setmetatable({ _ptr = ps }, Particles) +end + +function Particles:clear() + C.pxl8_particles_clear(self._ptr) +end + +function Particles:destroy() + if self._ptr then + C.pxl8_particles_destroy(self._ptr) + self._ptr = nil + end +end + +function Particles:emit(count) + C.pxl8_particles_emit(self._ptr, count or 1) +end + +function Particles:render() + C.pxl8_particles_render(self._ptr, core.gfx) +end + +function Particles:update(dt) + C.pxl8_particles_update(self._ptr, dt) +end + +function Particles:count() + return C.pxl8_particles_count(self._ptr) +end + +function Particles:get(index) + return C.pxl8_particles_get(self._ptr, index) +end + +function Particles:set_position(x, y) + C.pxl8_particles_set_position(self._ptr, x, y) +end + +function Particles:set_gravity(gx, gy) + C.pxl8_particles_set_gravity(self._ptr, gx, gy) +end + +function Particles:set_spread(sx, sy) + C.pxl8_particles_set_spread(self._ptr, sx, sy) +end + +function Particles:set_drag(drag) + C.pxl8_particles_set_drag(self._ptr, drag) +end + +function Particles:set_turbulence(turbulence) + C.pxl8_particles_set_turbulence(self._ptr, turbulence) +end + +function Particles:set_spawn_rate(rate) + C.pxl8_particles_set_spawn_rate(self._ptr, rate) +end + +function Particles:set_colors(ramp_min, ramp_max) + ramp_max = ramp_max or ramp_min + if ramp_min > ramp_max then + ramp_min, ramp_max = ramp_max, ramp_min + end + C.pxl8_particles_set_colors(self._ptr, ramp_min, ramp_max) +end + +function Particles:set_palette(palette) + C.pxl8_particles_set_palette(self._ptr, palette) +end + +function Particles:set_life(life_min, life_max) + C.pxl8_particles_set_life(self._ptr, life_min, life_max or life_min) +end + +function Particles:set_size(size_min, size_max) + C.pxl8_particles_set_size(self._ptr, size_min, size_max or size_min) +end + +function Particles:set_velocity(vx_min, vx_max, vy_min, vy_max) + C.pxl8_particles_set_velocity(self._ptr, vx_min, vx_max, vy_min, vy_max) +end + +particles.Particles = Particles + +return particles diff --git a/client/src/lua/pxl8/sfx.lua b/client/src/lua/pxl8/sfx.lua new file mode 100644 index 0000000..3a1faf5 --- /dev/null +++ b/client/src/lua/pxl8/sfx.lua @@ -0,0 +1,241 @@ +local ffi = require("ffi") +local C = ffi.C +local core = require("pxl8.core") + +local sfx = {} + +sfx.FILTER_BANDPASS = C.PXL8_SFX_FILTER_BANDPASS +sfx.FILTER_HIGHPASS = C.PXL8_SFX_FILTER_HIGHPASS +sfx.FILTER_LOWPASS = C.PXL8_SFX_FILTER_LOWPASS +sfx.FILTER_NONE = C.PXL8_SFX_FILTER_NONE + +sfx.LFO_AMPLITUDE = C.PXL8_SFX_LFO_AMPLITUDE +sfx.LFO_FILTER = C.PXL8_SFX_LFO_FILTER +sfx.LFO_PITCH = C.PXL8_SFX_LFO_PITCH + +sfx.NODE_COMPRESSOR = C.PXL8_SFX_NODE_COMPRESSOR +sfx.NODE_DELAY = C.PXL8_SFX_NODE_DELAY +sfx.NODE_REVERB = C.PXL8_SFX_NODE_REVERB + +sfx.WAVE_NOISE = C.PXL8_SFX_WAVE_NOISE +sfx.WAVE_PULSE = C.PXL8_SFX_WAVE_PULSE +sfx.WAVE_SAW = C.PXL8_SFX_WAVE_SAW +sfx.WAVE_SINE = C.PXL8_SFX_WAVE_SINE +sfx.WAVE_SQUARE = C.PXL8_SFX_WAVE_SQUARE +sfx.WAVE_TRIANGLE = C.PXL8_SFX_WAVE_TRIANGLE + +local SfxNode = {} +SfxNode.__index = SfxNode + +function SfxNode:destroy() + if self._ptr then + C.pxl8_sfx_node_destroy(self._ptr) + self._ptr = nil + end +end + +sfx.SfxNode = SfxNode + +local SfxContext = {} +SfxContext.__index = SfxContext + +function SfxContext.new() + local ctx = C.pxl8_sfx_context_create() + if ctx == nil then + return nil + end + return setmetatable({ _ptr = ctx }, SfxContext) +end + +function SfxContext:append_node(node) + C.pxl8_sfx_context_append_node(self._ptr, node._ptr) +end + +function SfxContext:attach() + C.pxl8_sfx_mixer_attach(core.sfx, self._ptr) +end + +function SfxContext:destroy() + if self._ptr then + C.pxl8_sfx_context_destroy(self._ptr) + self._ptr = nil + end +end + +function SfxContext:detach() + C.pxl8_sfx_mixer_detach(core.sfx, self._ptr) +end + +function SfxContext:get_head() + local ptr = C.pxl8_sfx_context_get_head(self._ptr) + if ptr == nil then return nil end + return setmetatable({ _ptr = ptr }, SfxNode) +end + +function SfxContext:get_volume() + return C.pxl8_sfx_context_get_volume(self._ptr) +end + +function SfxContext:insert_node(after, node) + C.pxl8_sfx_context_insert_node(self._ptr, after._ptr, node._ptr) +end + +function SfxContext:play_note(note, params, volume, duration) + return C.pxl8_sfx_play_note(self._ptr, note, params, volume or 0.8, duration or 0) +end + +function SfxContext:release_voice(voice_id) + C.pxl8_sfx_release_voice(self._ptr, voice_id) +end + +function SfxContext:remove_node(node) + C.pxl8_sfx_context_remove_node(self._ptr, node._ptr) +end + +function SfxContext:set_volume(volume) + C.pxl8_sfx_context_set_volume(self._ptr, volume) +end + +function SfxContext:stop_all() + C.pxl8_sfx_stop_all(self._ptr) +end + +function SfxContext:stop_voice(voice_id) + C.pxl8_sfx_stop_voice(self._ptr, voice_id) +end + +sfx.SfxContext = SfxContext + +local Compressor = setmetatable({}, { __index = SfxNode }) +Compressor.__index = Compressor + +function Compressor.new(opts) + opts = opts or {} + local cfg = ffi.new("pxl8_sfx_compressor_config") + cfg.attack = opts.attack or 10 + cfg.ratio = opts.ratio or 4 + cfg.release = opts.release or 100 + cfg.threshold = opts.threshold or -12 + local ptr = C.pxl8_sfx_compressor_create(cfg) + if ptr == nil then return nil end + return setmetatable({ _ptr = ptr }, Compressor) +end + +function Compressor:set_attack(attack) + C.pxl8_sfx_compressor_set_attack(self._ptr, attack) +end + +function Compressor:set_ratio(ratio) + C.pxl8_sfx_compressor_set_ratio(self._ptr, ratio) +end + +function Compressor:set_release(release) + C.pxl8_sfx_compressor_set_release(self._ptr, release) +end + +function Compressor:set_threshold(threshold) + C.pxl8_sfx_compressor_set_threshold(self._ptr, threshold) +end + +sfx.Compressor = Compressor + +local Delay = setmetatable({}, { __index = SfxNode }) +Delay.__index = Delay + +function Delay.new(opts) + opts = opts or {} + local cfg = ffi.new("pxl8_sfx_delay_config") + cfg.feedback = opts.feedback or 0.4 + cfg.mix = opts.mix or 0.25 + cfg.time_l = opts.time_l or 350 + cfg.time_r = opts.time_r or 500 + local ptr = C.pxl8_sfx_delay_create(cfg) + if ptr == nil then return nil end + return setmetatable({ _ptr = ptr }, Delay) +end + +function Delay:set_feedback(feedback) + C.pxl8_sfx_delay_set_feedback(self._ptr, feedback) +end + +function Delay:set_mix(mix) + C.pxl8_sfx_delay_set_mix(self._ptr, mix) +end + +function Delay:set_time(time_l, time_r) + C.pxl8_sfx_delay_set_time(self._ptr, time_l, time_r) +end + +sfx.Delay = Delay + +local Reverb = setmetatable({}, { __index = SfxNode }) +Reverb.__index = Reverb + +function Reverb.new(opts) + opts = opts or {} + local cfg = ffi.new("pxl8_sfx_reverb_config") + cfg.damping = opts.damping or 0.5 + cfg.mix = opts.mix or 0.3 + cfg.room = opts.room or 0.5 + local ptr = C.pxl8_sfx_reverb_create(cfg) + if ptr == nil then return nil end + return setmetatable({ _ptr = ptr }, Reverb) +end + +function Reverb:set_damping(damping) + C.pxl8_sfx_reverb_set_damping(self._ptr, damping) +end + +function Reverb:set_mix(mix) + C.pxl8_sfx_reverb_set_mix(self._ptr, mix) +end + +function Reverb:set_room(room) + C.pxl8_sfx_reverb_set_room(self._ptr, room) +end + +sfx.Reverb = Reverb + +function sfx.get_master_volume() + return C.pxl8_sfx_mixer_get_master_volume(core.sfx) +end + +function sfx.note_to_freq(note) + return C.pxl8_sfx_note_to_freq(note) +end + +function sfx.set_master_volume(volume) + C.pxl8_sfx_mixer_set_master_volume(core.sfx, volume) +end + +function sfx.voice_params(opts) + opts = opts or {} + local params = ffi.new("pxl8_sfx_voice_params") + + params.amp_env.attack = opts.attack or 0.01 + params.amp_env.decay = opts.decay or 0.1 + params.amp_env.sustain = opts.sustain or 0.5 + params.amp_env.release = opts.release or 0.2 + + params.filter_env.attack = opts.filter_attack or 0.01 + params.filter_env.decay = opts.filter_decay or 0.1 + params.filter_env.sustain = opts.filter_sustain or 0.3 + params.filter_env.release = opts.filter_release or 0.1 + + params.filter_type = opts.filter_type or C.PXL8_SFX_FILTER_NONE + params.lfo_target = opts.lfo_target or C.PXL8_SFX_LFO_PITCH + params.lfo_waveform = opts.lfo_waveform or C.PXL8_SFX_WAVE_SINE + params.waveform = opts.waveform or C.PXL8_SFX_WAVE_SINE + + params.filter_cutoff = opts.filter_cutoff or 4000 + params.filter_env_depth = opts.filter_env_depth or 0 + params.filter_resonance = opts.filter_resonance or 0 + params.fx_send = opts.fx_send or 0 + params.lfo_depth = opts.lfo_depth or 0 + params.lfo_rate = opts.lfo_rate or 0 + params.pulse_width = opts.pulse_width or 0.5 + + return params +end + +return sfx diff --git a/client/src/lua/pxl8/tilemap.lua b/client/src/lua/pxl8/tilemap.lua new file mode 100644 index 0000000..322d0a8 --- /dev/null +++ b/client/src/lua/pxl8/tilemap.lua @@ -0,0 +1,106 @@ +local ffi = require("ffi") +local C = ffi.C +local core = require("pxl8.core") + +local tilemap = {} + +tilemap.TILE_FLIP_X = 1 +tilemap.TILE_FLIP_Y = 2 +tilemap.TILE_SOLID = 4 +tilemap.TILE_TRIGGER = 8 + +local Tilesheet = {} +Tilesheet.__index = Tilesheet + +function Tilesheet.new(tile_size) + local ts = C.pxl8_tilesheet_create(tile_size or 16) + if ts == nil then + return nil + end + return setmetatable({ _ptr = ts }, Tilesheet) +end + +function Tilesheet:destroy() + if self._ptr then + C.pxl8_tilesheet_destroy(self._ptr) + self._ptr = nil + end +end + +function Tilesheet:load(filepath) + return C.pxl8_tilesheet_load(self._ptr, filepath, core.gfx) +end + +tilemap.Tilesheet = Tilesheet + +local Tilemap = {} +Tilemap.__index = Tilemap + +local tile_data = setmetatable({}, {__mode = "k"}) + +function Tilemap.new(width, height, tile_size) + local tm = C.pxl8_tilemap_create(width, height, tile_size or 16) + if tm == nil then + return nil + end + return setmetatable({ _ptr = tm }, Tilemap) +end + +function Tilemap:check_collision(x, y, w, h) + return C.pxl8_tilemap_check_collision(self._ptr, x, y, w, h) +end + +function Tilemap:destroy() + if self._ptr then + C.pxl8_tilemap_destroy(self._ptr) + self._ptr = nil + end +end + +function Tilemap:get_tile_data(tile_id) + if tile_id == 0 then return nil end + if not tile_data[self] then return nil end + return tile_data[self][tile_id] +end + +function Tilemap:get_tile_id(layer, x, y) + return C.pxl8_tilemap_get_tile_id(self._ptr, layer or 0, x, y) +end + +function Tilemap:is_solid(x, y) + return C.pxl8_tilemap_is_solid(self._ptr, x, y) +end + +function Tilemap:load_ase(filepath, layer) + return C.pxl8_tilemap_load_ase(self._ptr, filepath, layer or 0) +end + +function Tilemap:render() + C.pxl8_tilemap_render(self._ptr, core.gfx) +end + +function Tilemap:render_layer(layer) + C.pxl8_tilemap_render_layer(self._ptr, core.gfx, layer) +end + +function Tilemap:set_camera(x, y) + C.pxl8_tilemap_set_camera(self._ptr, x, y) +end + +function Tilemap:set_tile(layer, x, y, tile_id, flags) + C.pxl8_tilemap_set_tile(self._ptr, layer or 0, x, y, tile_id or 0, flags or 0) +end + +function Tilemap:set_tile_data(tile_id, data) + if tile_id == 0 then return end + if not tile_data[self] then tile_data[self] = {} end + tile_data[self][tile_id] = data +end + +function Tilemap:set_tilesheet(tilesheet) + return C.pxl8_tilemap_set_tilesheet(self._ptr, tilesheet._ptr) +end + +tilemap.Tilemap = Tilemap + +return tilemap diff --git a/client/src/lua/pxl8/transition.lua b/client/src/lua/pxl8/transition.lua new file mode 100644 index 0000000..a501bc4 --- /dev/null +++ b/client/src/lua/pxl8/transition.lua @@ -0,0 +1,82 @@ +local ffi = require("ffi") +local C = ffi.C +local core = require("pxl8.core") + +local transition = {} + +local TYPES = { + fade = 0, + wipe_left = 1, + wipe_right = 2, + wipe_up = 3, + wipe_down = 4, + circle_open = 5, + circle_close = 6, + dissolve = 7, + pixelate = 8 +} + +transition.TYPES = TYPES + +local Transition = {} +Transition.__index = Transition + +function Transition.new(type_name, duration) + local type_id = TYPES[type_name] or 0 + local t = C.pxl8_transition_create(type_id, duration or 1.0) + if t == nil then + return nil + end + return setmetatable({ _ptr = t }, Transition) +end + +function Transition:destroy() + if self._ptr then + C.pxl8_transition_destroy(self._ptr) + self._ptr = nil + end +end + +function Transition:get_progress() + return C.pxl8_transition_get_progress(self._ptr) +end + +function Transition:is_active() + return C.pxl8_transition_is_active(self._ptr) +end + +function Transition:is_complete() + return C.pxl8_transition_is_complete(self._ptr) +end + +function Transition:render() + C.pxl8_transition_render(self._ptr, core.gfx) +end + +function Transition:reset() + C.pxl8_transition_reset(self._ptr) +end + +function Transition:set_color(color) + C.pxl8_transition_set_color(self._ptr, color) +end + +function Transition:set_reverse(reverse) + C.pxl8_transition_set_reverse(self._ptr, reverse) +end + +function Transition:start() + C.pxl8_transition_start(self._ptr) +end + +function Transition:stop() + C.pxl8_transition_stop(self._ptr) +end + +function Transition:update(dt) + C.pxl8_transition_update(self._ptr, dt) +end + +transition.Transition = Transition + +return transition diff --git a/src/lua/pxl8/vfx.lua b/client/src/lua/pxl8/vfx.lua similarity index 100% rename from src/lua/pxl8/vfx.lua rename to client/src/lua/pxl8/vfx.lua diff --git a/src/lua/pxl8/world.lua b/client/src/lua/pxl8/world.lua similarity index 69% rename from src/lua/pxl8/world.lua rename to client/src/lua/pxl8/world.lua index c6c9ad9..589917e 100644 --- a/src/lua/pxl8/world.lua +++ b/client/src/lua/pxl8/world.lua @@ -7,32 +7,52 @@ local world = {} world.PROCGEN_ROOMS = C.PXL8_PROCGEN_ROOMS world.PROCGEN_TERRAIN = C.PXL8_PROCGEN_TERRAIN -function world.new() - return C.pxl8_world_create() +local World = {} +World.__index = World + +function World.new() + local w = C.pxl8_world_create() + if w == nil then + return nil + end + return setmetatable({ _ptr = w }, World) end -function world.destroy(w) - C.pxl8_world_destroy(w) +function World:apply_textures(texture_defs) + local count = #texture_defs + local textures = ffi.new("pxl8_world_texture[?]", count) + + for i, def in ipairs(texture_defs) do + local idx = i - 1 + ffi.copy(textures[idx].name, def.name or "", math.min(#(def.name or ""), 15)) + textures[idx].texture_id = def.texture_id or 0 + + if def.rule then + textures[idx].rule = ffi.cast("bool (*)(const pxl8_vec3*, const pxl8_bsp_face*, const pxl8_bsp*)", + function(normal, face, bsp) + return def.rule(normal[0], face, bsp) + end) + else + textures[idx].rule = nil + end + end + + return C.pxl8_world_apply_textures(self._ptr, textures, count) end -function world.load(w, filepath) - return C.pxl8_world_load(w, filepath) +function World:check_collision(x, y, z, radius) + local pos = ffi.new("pxl8_vec3", {x = x, y = y, z = z}) + return C.pxl8_world_check_collision(self._ptr, pos, radius) end -function world.render(w, camera_pos) - local vec = ffi.new("pxl8_vec3", {x = camera_pos[1], y = camera_pos[2], z = camera_pos[3]}) - C.pxl8_world_render(w, core.gfx, vec) +function World:destroy() + if self._ptr then + C.pxl8_world_destroy(self._ptr) + self._ptr = nil + end end -function world.unload(w) - C.pxl8_world_unload(w) -end - -function world.is_loaded(w) - return C.pxl8_world_is_loaded(w) -end - -function world.generate(w, params) +function World:generate(params) local c_params = ffi.new("pxl8_procgen_params") c_params.type = params.type or C.PXL8_PROCGEN_ROOMS c_params.width = params.width or 32 @@ -42,9 +62,35 @@ function world.generate(w, params) c_params.min_room_size = params.min_room_size or 5 c_params.max_room_size = params.max_room_size or 10 c_params.num_rooms = params.num_rooms or 8 - return C.pxl8_world_generate(w, core.gfx, c_params) + return C.pxl8_world_generate(self._ptr, core.gfx, c_params) end +function World:is_loaded() + return C.pxl8_world_is_loaded(self._ptr) +end + +function World:load(filepath) + return C.pxl8_world_load(self._ptr, filepath) +end + +function World:render(camera_pos) + local vec = ffi.new("pxl8_vec3", {x = camera_pos[1], y = camera_pos[2], z = camera_pos[3]}) + C.pxl8_world_render(self._ptr, core.gfx, vec) +end + +function World:resolve_collision(from_x, from_y, from_z, to_x, to_y, to_z, radius) + local from = ffi.new("pxl8_vec3", {x = from_x, y = from_y, z = from_z}) + local to = ffi.new("pxl8_vec3", {x = to_x, y = to_y, z = to_z}) + local result = C.pxl8_world_resolve_collision(self._ptr, from, to, radius) + return result.x, result.y, result.z +end + +function World:unload() + C.pxl8_world_unload(self._ptr) +end + +world.World = World + function world.procgen_tex(params) local width = params.width or 64 local height = params.height or 64 @@ -71,40 +117,4 @@ function world.procgen_tex(params) return tex_id end -function world.apply_textures(w, texture_defs) - local count = #texture_defs - local textures = ffi.new("pxl8_world_texture[?]", count) - - for i, def in ipairs(texture_defs) do - local idx = i - 1 - ffi.copy(textures[idx].name, def.name or "", math.min(#(def.name or ""), 15)) - textures[idx].texture_id = def.texture_id or 0 - - if def.rule then - textures[idx].rule = ffi.cast("bool (*)(const pxl8_vec3*, const pxl8_bsp_face*, const pxl8_bsp*)", - function(normal, face, bsp) - return def.rule(normal[0], face, bsp) - end) - else - textures[idx].rule = nil - end - end - - local result = C.pxl8_world_apply_textures(w, textures, count) - - return result -end - -function world.check_collision(w, x, y, z, radius) - local pos = ffi.new("pxl8_vec3", {x = x, y = y, z = z}) - return C.pxl8_world_check_collision(w, pos, radius) -end - -function world.resolve_collision(w, from_x, from_y, from_z, to_x, to_y, to_z, radius) - local from = ffi.new("pxl8_vec3", {x = from_x, y = from_y, z = from_z}) - local to = ffi.new("pxl8_vec3", {x = to_x, y = to_y, z = to_z}) - local result = C.pxl8_world_resolve_collision(w, from, to, radius) - return result.x, result.y, result.z -end - return world diff --git a/src/pxl8_math.c b/client/src/math/pxl8_math.c similarity index 96% rename from src/pxl8_math.c rename to client/src/math/pxl8_math.c index f26ebd4..92b858f 100644 --- a/src/pxl8_math.c +++ b/client/src/math/pxl8_math.c @@ -77,6 +77,14 @@ f32 pxl8_vec3_length(pxl8_vec3 v) { return sqrtf(pxl8_vec3_dot(v, v)); } +pxl8_vec3 pxl8_vec3_lerp(pxl8_vec3 a, pxl8_vec3 b, f32 t) { + return (pxl8_vec3){ + a.x + (b.x - a.x) * t, + a.y + (b.y - a.y) * t, + a.z + (b.z - a.z) * t + }; +} + pxl8_vec3 pxl8_vec3_normalize(pxl8_vec3 v) { f32 len = pxl8_vec3_length(v); @@ -93,7 +101,7 @@ pxl8_mat4 pxl8_mat4_identity(void) { return mat; } -pxl8_mat4 pxl8_mat4_multiply(pxl8_mat4 a, pxl8_mat4 b) { +pxl8_mat4 pxl8_mat4_mul(pxl8_mat4 a, pxl8_mat4 b) { pxl8_mat4 mat = {0}; for (i32 col = 0; col < 4; col++) { @@ -109,7 +117,7 @@ pxl8_mat4 pxl8_mat4_multiply(pxl8_mat4 a, pxl8_mat4 b) { return mat; } -pxl8_vec4 pxl8_mat4_multiply_vec4(pxl8_mat4 m, pxl8_vec4 v) { +pxl8_vec4 pxl8_mat4_mul_vec4(pxl8_mat4 m, pxl8_vec4 v) { return (pxl8_vec4){ .x = m.m[0] * v.x + m.m[4] * v.y + m.m[8] * v.z + m.m[12] * v.w, .y = m.m[1] * v.x + m.m[5] * v.y + m.m[9] * v.z + m.m[13] * v.w, diff --git a/src/pxl8_math.h b/client/src/math/pxl8_math.h similarity index 91% rename from src/pxl8_math.h rename to client/src/math/pxl8_math.h index 0d941c1..40ea22f 100644 --- a/src/pxl8_math.h +++ b/client/src/math/pxl8_math.h @@ -47,14 +47,15 @@ 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_lerp(pxl8_vec3 a, pxl8_vec3 b, f32 t); 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_mul(pxl8_mat4 a, pxl8_mat4 b); +pxl8_vec4 pxl8_mat4_mul_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); diff --git a/client/src/math/pxl8_simd.h b/client/src/math/pxl8_simd.h new file mode 100644 index 0000000..8202b24 --- /dev/null +++ b/client/src/math/pxl8_simd.h @@ -0,0 +1,299 @@ +#pragma once + +#include "pxl8_types.h" + +#if defined(__x86_64__) || defined(_M_X64) + #define PXL8_SIMD_SSE2 1 + #include +#elif defined(__aarch64__) || defined(_M_ARM64) + #define PXL8_SIMD_NEON 1 + #include +#else + #define PXL8_SIMD_SCALAR 1 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(PXL8_SIMD_SSE2) + +typedef struct { __m128 v; } pxl8_f32x4; +typedef struct { __m128i v; } pxl8_i32x4; +typedef struct { __m128i v; } pxl8_u16x8; + +static inline pxl8_f32x4 pxl8_f32x4_splat(f32 x) { + return (pxl8_f32x4){ _mm_set1_ps(x) }; +} + +static inline pxl8_f32x4 pxl8_f32x4_new(f32 a, f32 b, f32 c, f32 d) { + return (pxl8_f32x4){ _mm_set_ps(d, c, b, a) }; +} + +static inline pxl8_f32x4 pxl8_f32x4_add(pxl8_f32x4 a, pxl8_f32x4 b) { + return (pxl8_f32x4){ _mm_add_ps(a.v, b.v) }; +} + +static inline pxl8_f32x4 pxl8_f32x4_sub(pxl8_f32x4 a, pxl8_f32x4 b) { + return (pxl8_f32x4){ _mm_sub_ps(a.v, b.v) }; +} + +static inline pxl8_f32x4 pxl8_f32x4_mul(pxl8_f32x4 a, pxl8_f32x4 b) { + return (pxl8_f32x4){ _mm_mul_ps(a.v, b.v) }; +} + +static inline pxl8_f32x4 pxl8_f32x4_div(pxl8_f32x4 a, pxl8_f32x4 b) { + return (pxl8_f32x4){ _mm_div_ps(a.v, b.v) }; +} + +static inline pxl8_f32x4 pxl8_f32x4_min(pxl8_f32x4 a, pxl8_f32x4 b) { + return (pxl8_f32x4){ _mm_min_ps(a.v, b.v) }; +} + +static inline pxl8_f32x4 pxl8_f32x4_max(pxl8_f32x4 a, pxl8_f32x4 b) { + return (pxl8_f32x4){ _mm_max_ps(a.v, b.v) }; +} + +static inline pxl8_f32x4 pxl8_f32x4_cmplt(pxl8_f32x4 a, pxl8_f32x4 b) { + return (pxl8_f32x4){ _mm_cmplt_ps(a.v, b.v) }; +} + +static inline i32 pxl8_f32x4_movemask(pxl8_f32x4 a) { + return _mm_movemask_ps(a.v); +} + +static inline pxl8_i32x4 pxl8_f32x4_to_i32x4(pxl8_f32x4 a) { + return (pxl8_i32x4){ _mm_cvttps_epi32(a.v) }; +} + +static inline void pxl8_f32x4_store(pxl8_f32x4 a, f32* out) { + _mm_storeu_ps(out, a.v); +} + +static inline pxl8_i32x4 pxl8_i32x4_splat(i32 x) { + return (pxl8_i32x4){ _mm_set1_epi32(x) }; +} + +static inline pxl8_i32x4 pxl8_i32x4_slli(pxl8_i32x4 a, i32 n) { + return (pxl8_i32x4){ _mm_slli_epi32(a.v, n) }; +} + +static inline pxl8_i32x4 pxl8_i32x4_srai(pxl8_i32x4 a, i32 n) { + return (pxl8_i32x4){ _mm_srai_epi32(a.v, n) }; +} + +static inline pxl8_i32x4 pxl8_i32x4_and(pxl8_i32x4 a, pxl8_i32x4 b) { + return (pxl8_i32x4){ _mm_and_si128(a.v, b.v) }; +} + +static inline pxl8_i32x4 pxl8_i32x4_or(pxl8_i32x4 a, pxl8_i32x4 b) { + return (pxl8_i32x4){ _mm_or_si128(a.v, b.v) }; +} + +static inline void pxl8_i32x4_store(pxl8_i32x4 a, i32* out) { + _mm_storeu_si128((__m128i*)out, a.v); +} + +static inline pxl8_u16x8 pxl8_u16x8_cmplt(pxl8_u16x8 a, pxl8_u16x8 b) { + return (pxl8_u16x8){ _mm_cmplt_epi16(a.v, b.v) }; +} + +static inline pxl8_u16x8 pxl8_u16x8_blend(pxl8_u16x8 a, pxl8_u16x8 b, pxl8_u16x8 mask) { + __m128i not_mask = _mm_andnot_si128(mask.v, a.v); + __m128i and_mask = _mm_and_si128(mask.v, b.v); + return (pxl8_u16x8){ _mm_or_si128(not_mask, and_mask) }; +} + +static inline i32 pxl8_u16x8_movemask(pxl8_u16x8 a) { + return _mm_movemask_epi8(a.v); +} + +#elif defined(PXL8_SIMD_NEON) + +typedef struct { float32x4_t v; } pxl8_f32x4; +typedef struct { int32x4_t v; } pxl8_i32x4; +typedef struct { uint16x8_t v; } pxl8_u16x8; + +static inline pxl8_f32x4 pxl8_f32x4_splat(f32 x) { + return (pxl8_f32x4){ vdupq_n_f32(x) }; +} + +static inline pxl8_f32x4 pxl8_f32x4_new(f32 a, f32 b, f32 c, f32 d) { + f32 arr[4] = {a, b, c, d}; + return (pxl8_f32x4){ vld1q_f32(arr) }; +} + +static inline pxl8_f32x4 pxl8_f32x4_add(pxl8_f32x4 a, pxl8_f32x4 b) { + return (pxl8_f32x4){ vaddq_f32(a.v, b.v) }; +} + +static inline pxl8_f32x4 pxl8_f32x4_sub(pxl8_f32x4 a, pxl8_f32x4 b) { + return (pxl8_f32x4){ vsubq_f32(a.v, b.v) }; +} + +static inline pxl8_f32x4 pxl8_f32x4_mul(pxl8_f32x4 a, pxl8_f32x4 b) { + return (pxl8_f32x4){ vmulq_f32(a.v, b.v) }; +} + +static inline pxl8_f32x4 pxl8_f32x4_div(pxl8_f32x4 a, pxl8_f32x4 b) { + return (pxl8_f32x4){ vdivq_f32(a.v, b.v) }; +} + +static inline pxl8_f32x4 pxl8_f32x4_min(pxl8_f32x4 a, pxl8_f32x4 b) { + return (pxl8_f32x4){ vminq_f32(a.v, b.v) }; +} + +static inline pxl8_f32x4 pxl8_f32x4_max(pxl8_f32x4 a, pxl8_f32x4 b) { + return (pxl8_f32x4){ vmaxq_f32(a.v, b.v) }; +} + +static inline pxl8_f32x4 pxl8_f32x4_cmplt(pxl8_f32x4 a, pxl8_f32x4 b) { + uint32x4_t cmp = vcltq_f32(a.v, b.v); + return (pxl8_f32x4){ vreinterpretq_f32_u32(cmp) }; +} + +static inline i32 pxl8_f32x4_movemask(pxl8_f32x4 a) { + uint32x4_t input = vreinterpretq_u32_f32(a.v); + static const i32 shifts[4] = {0, 1, 2, 3}; + uint32x4_t shifted = vshrq_n_u32(input, 31); + return vgetq_lane_u32(shifted, 0) | (vgetq_lane_u32(shifted, 1) << 1) | + (vgetq_lane_u32(shifted, 2) << 2) | (vgetq_lane_u32(shifted, 3) << 3); +} + +static inline pxl8_i32x4 pxl8_f32x4_to_i32x4(pxl8_f32x4 a) { + return (pxl8_i32x4){ vcvtq_s32_f32(a.v) }; +} + +static inline void pxl8_f32x4_store(pxl8_f32x4 a, f32* out) { + vst1q_f32(out, a.v); +} + +static inline pxl8_i32x4 pxl8_i32x4_splat(i32 x) { + return (pxl8_i32x4){ vdupq_n_s32(x) }; +} + +static inline pxl8_i32x4 pxl8_i32x4_slli(pxl8_i32x4 a, i32 n) { + return (pxl8_i32x4){ vshlq_s32(a.v, vdupq_n_s32(n)) }; +} + +static inline pxl8_i32x4 pxl8_i32x4_srai(pxl8_i32x4 a, i32 n) { + return (pxl8_i32x4){ vshlq_s32(a.v, vdupq_n_s32(-n)) }; +} + +static inline pxl8_i32x4 pxl8_i32x4_and(pxl8_i32x4 a, pxl8_i32x4 b) { + return (pxl8_i32x4){ vandq_s32(a.v, b.v) }; +} + +static inline pxl8_i32x4 pxl8_i32x4_or(pxl8_i32x4 a, pxl8_i32x4 b) { + return (pxl8_i32x4){ vorrq_s32(a.v, b.v) }; +} + +static inline void pxl8_i32x4_store(pxl8_i32x4 a, i32* out) { + vst1q_s32(out, a.v); +} + +#else + +typedef struct { f32 v[4]; } pxl8_f32x4; +typedef struct { i32 v[4]; } pxl8_i32x4; +typedef struct { u16 v[8]; } pxl8_u16x8; + +static inline pxl8_f32x4 pxl8_f32x4_splat(f32 x) { + return (pxl8_f32x4){{ x, x, x, x }}; +} + +static inline pxl8_f32x4 pxl8_f32x4_new(f32 a, f32 b, f32 c, f32 d) { + return (pxl8_f32x4){{ a, b, c, d }}; +} + +static inline pxl8_f32x4 pxl8_f32x4_add(pxl8_f32x4 a, pxl8_f32x4 b) { + return (pxl8_f32x4){{ a.v[0]+b.v[0], a.v[1]+b.v[1], a.v[2]+b.v[2], a.v[3]+b.v[3] }}; +} + +static inline pxl8_f32x4 pxl8_f32x4_sub(pxl8_f32x4 a, pxl8_f32x4 b) { + return (pxl8_f32x4){{ a.v[0]-b.v[0], a.v[1]-b.v[1], a.v[2]-b.v[2], a.v[3]-b.v[3] }}; +} + +static inline pxl8_f32x4 pxl8_f32x4_mul(pxl8_f32x4 a, pxl8_f32x4 b) { + return (pxl8_f32x4){{ a.v[0]*b.v[0], a.v[1]*b.v[1], a.v[2]*b.v[2], a.v[3]*b.v[3] }}; +} + +static inline pxl8_f32x4 pxl8_f32x4_div(pxl8_f32x4 a, pxl8_f32x4 b) { + return (pxl8_f32x4){{ a.v[0]/b.v[0], a.v[1]/b.v[1], a.v[2]/b.v[2], a.v[3]/b.v[3] }}; +} + +static inline pxl8_f32x4 pxl8_f32x4_min(pxl8_f32x4 a, pxl8_f32x4 b) { + return (pxl8_f32x4){{ + a.v[0]b.v[0]?a.v[0]:b.v[0], a.v[1]>b.v[1]?a.v[1]:b.v[1], + a.v[2]>b.v[2]?a.v[2]:b.v[2], a.v[3]>b.v[3]?a.v[3]:b.v[3] + }}; +} + +static inline pxl8_f32x4 pxl8_f32x4_cmplt(pxl8_f32x4 a, pxl8_f32x4 b) { + pxl8_f32x4 r; + u32* rv = (u32*)r.v; + rv[0] = a.v[0] < b.v[0] ? 0xFFFFFFFF : 0; + rv[1] = a.v[1] < b.v[1] ? 0xFFFFFFFF : 0; + rv[2] = a.v[2] < b.v[2] ? 0xFFFFFFFF : 0; + rv[3] = a.v[3] < b.v[3] ? 0xFFFFFFFF : 0; + return r; +} + +static inline i32 pxl8_f32x4_movemask(pxl8_f32x4 a) { + u32* av = (u32*)a.v; + return ((av[0] >> 31) & 1) | ((av[1] >> 31) & 1) << 1 | + ((av[2] >> 31) & 1) << 2 | ((av[3] >> 31) & 1) << 3; +} + +static inline pxl8_i32x4 pxl8_f32x4_to_i32x4(pxl8_f32x4 a) { + return (pxl8_i32x4){{ (i32)a.v[0], (i32)a.v[1], (i32)a.v[2], (i32)a.v[3] }}; +} + +static inline void pxl8_f32x4_store(pxl8_f32x4 a, f32* out) { + out[0] = a.v[0]; out[1] = a.v[1]; out[2] = a.v[2]; out[3] = a.v[3]; +} + +static inline pxl8_i32x4 pxl8_i32x4_splat(i32 x) { + return (pxl8_i32x4){{ x, x, x, x }}; +} + +static inline pxl8_i32x4 pxl8_i32x4_slli(pxl8_i32x4 a, i32 n) { + return (pxl8_i32x4){{ a.v[0]<>n, a.v[1]>>n, a.v[2]>>n, a.v[3]>>n }}; +} + +static inline pxl8_i32x4 pxl8_i32x4_and(pxl8_i32x4 a, pxl8_i32x4 b) { + return (pxl8_i32x4){{ a.v[0]&b.v[0], a.v[1]&b.v[1], a.v[2]&b.v[2], a.v[3]&b.v[3] }}; +} + +static inline pxl8_i32x4 pxl8_i32x4_or(pxl8_i32x4 a, pxl8_i32x4 b) { + return (pxl8_i32x4){{ a.v[0]|b.v[0], a.v[1]|b.v[1], a.v[2]|b.v[2], a.v[3]|b.v[3] }}; +} + +static inline void pxl8_i32x4_store(pxl8_i32x4 a, i32* out) { + out[0] = a.v[0]; out[1] = a.v[1]; out[2] = a.v[2]; out[3] = a.v[3]; +} + +#endif + +static inline f32 pxl8_fast_inv_sqrt(f32 x) { + f32 half = 0.5f * x; + i32 i = *(i32*)&x; + i = 0x5f375a86 - (i >> 1); + f32 y = *(f32*)&i; + return y * (1.5f - half * y * y); +} + +#ifdef __cplusplus +} +#endif diff --git a/src/pxl8_repl.c b/client/src/script/pxl8_repl.c similarity index 100% rename from src/pxl8_repl.c rename to client/src/script/pxl8_repl.c diff --git a/src/pxl8_repl.h b/client/src/script/pxl8_repl.h similarity index 100% rename from src/pxl8_repl.h rename to client/src/script/pxl8_repl.h diff --git a/src/pxl8_script.c b/client/src/script/pxl8_script.c similarity index 68% rename from src/pxl8_script.c rename to client/src/script/pxl8_script.c index 3c18186..481e8ca 100644 --- a/src/pxl8_script.c +++ b/client/src/script/pxl8_script.c @@ -17,6 +17,7 @@ #include "pxl8_gui.h" #include "pxl8_log.h" #include "pxl8_macros.h" +#include "pxl8_script_ffi.h" struct pxl8_script { lua_State* L; @@ -154,327 +155,6 @@ static void pxl8_script_repl_promote_locals(const char* input, char* output, siz output[j] = '\0'; } -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 pxl8;\n" -"typedef struct pxl8_gfx pxl8_gfx;\n" -"typedef struct { int x, y, w, h; } pxl8_bounds;\n" -"typedef struct { int x, y; } pxl8_point;\n" -"typedef struct pxl8_rng { u32 state; } pxl8_rng;\n" -"\n" -"void pxl8_rng_seed(pxl8_rng* rng, u32 seed);\n" -"u32 pxl8_rng_next(pxl8_rng* rng);\n" -"f32 pxl8_rng_f32(pxl8_rng* rng);\n" -"i32 pxl8_rng_range(pxl8_rng* rng, i32 min, i32 max);\n" -"\n" -"f32 pxl8_get_fps(const pxl8* sys);\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_clear(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_cycle_palette(pxl8_gfx* ctx, u8 start, u8 count, i32 step);\n" -"void pxl8_gfx_fade_palette(pxl8_gfx* ctx, u8 start, u8 count, f32 amount, u32 target_color);\n" -"i32 pxl8_gfx_add_palette_cycle(pxl8_gfx* ctx, u8 start_index, u8 end_index, f32 speed);\n" -"void pxl8_gfx_remove_palette_cycle(pxl8_gfx* ctx, i32 cycle_id);\n" -"void pxl8_gfx_set_palette_cycle_speed(pxl8_gfx* ctx, i32 cycle_id, f32 speed);\n" -"void pxl8_gfx_clear_palette_cycles(pxl8_gfx* ctx);\n" -"void pxl8_gfx_update(pxl8_gfx* ctx, f32 dt);\n" -"i32 pxl8_gfx_load_palette(pxl8_gfx* ctx, const char* filepath);\n" -"i32 pxl8_gfx_load_sprite(pxl8_gfx* ctx, const char* filepath, u32* sprite_id);\n" -"i32 pxl8_gfx_create_texture(pxl8_gfx* ctx, const u8* pixels, u32 width, u32 height);\n" -"typedef struct pxl8_input_state pxl8_input_state;\n" -"bool pxl8_key_down(const pxl8_input_state* input, const char* key_name);\n" -"bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name);\n" -"bool pxl8_key_released(const pxl8_input_state* input, const char* key_name);\n" -"bool pxl8_mouse_pressed(const pxl8_input_state* input, i32 button);\n" -"bool pxl8_mouse_released(const pxl8_input_state* input, i32 button);\n" -"int pxl8_mouse_wheel_x(const pxl8_input_state* input);\n" -"int pxl8_mouse_wheel_y(const pxl8_input_state* input);\n" -"int pxl8_mouse_x(const pxl8_input_state* input);\n" -"int pxl8_mouse_y(const pxl8_input_state* input);\n" -"int pxl8_mouse_dx(const pxl8_input_state* input);\n" -"int pxl8_mouse_dy(const pxl8_input_state* input);\n" -"typedef enum { PXL8_CURSOR_ARROW = 0, PXL8_CURSOR_HAND = 1 } pxl8_cursor;\n" -"void pxl8_center_cursor(pxl8* sys);\n" -"void pxl8_set_cursor(pxl8* sys, pxl8_cursor cursor);\n" -"void pxl8_set_relative_mouse_mode(pxl8* sys, bool enabled);\n" -"void pxl8_set_running(pxl8* sys, bool running);\n" -"void pxl8_lua_log(int level, const char* file, int line, const char* msg);\n" -"typedef struct pxl8_cart pxl8_cart;\n" -"pxl8_cart* pxl8_get_cart(void);\n" -"const char* pxl8_cart_get_title(const pxl8_cart* cart);\n" -"typedef u32 pxl8_tile;\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" -"u32 pxl8_tilemap_get_height(const pxl8_tilemap* tilemap);\n" -"pxl8_tile pxl8_tilemap_get_tile(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y);\n" -"u32 pxl8_tilemap_get_tile_size(const pxl8_tilemap* tilemap);\n" -"void* pxl8_tilemap_get_tile_user_data(const pxl8_tilemap* tilemap, u16 tile_id);\n" -"u32 pxl8_tilemap_get_width(const pxl8_tilemap* tilemap);\n" -"void pxl8_tilemap_set_tile_user_data(pxl8_tilemap* tilemap, u16 tile_id, void* user_data);\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" -"i32 pxl8_tilemap_load_ase(pxl8_tilemap* tilemap, const char* filepath, u32 layer);\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, pxl8_rng* rng);\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 pxl8_transition pxl8_transition;\n" -"typedef struct pxl8_anim pxl8_anim;\n" -"\n" -"pxl8_transition* pxl8_transition_create(i32 type, f32 duration);\n" -"void pxl8_transition_destroy(pxl8_transition* transition);\n" -"f32 pxl8_transition_get_progress(const pxl8_transition* transition);\n" -"bool pxl8_transition_is_active(const pxl8_transition* transition);\n" -"bool pxl8_transition_is_complete(const pxl8_transition* transition);\n" -"void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx);\n" -"void pxl8_transition_reset(pxl8_transition* transition);\n" -"void pxl8_transition_set_color(pxl8_transition* transition, u32 color);\n" -"void pxl8_transition_set_reverse(pxl8_transition* transition, bool reverse);\n" -"void pxl8_transition_start(pxl8_transition* transition);\n" -"void pxl8_transition_stop(pxl8_transition* transition);\n" -"void pxl8_transition_update(pxl8_transition* transition, f32 dt);\n" -"\n" -"pxl8_anim* pxl8_anim_create(const u32* frame_ids, const u16* frame_durations, u16 frame_count);\n" -"pxl8_anim* pxl8_anim_create_from_ase(pxl8_gfx* gfx, const char* path);\n" -"void pxl8_anim_destroy(pxl8_anim* anim);\n" -"i32 pxl8_anim_add_state(pxl8_anim* anim, const char* name, pxl8_anim* state_anim);\n" -"u16 pxl8_anim_get_current_frame(const pxl8_anim* anim);\n" -"u32 pxl8_anim_get_current_frame_id(const pxl8_anim* anim);\n" -"const char* pxl8_anim_get_state(const pxl8_anim* anim);\n" -"bool pxl8_anim_has_state_machine(const pxl8_anim* anim);\n" -"bool pxl8_anim_is_complete(const pxl8_anim* anim);\n" -"bool pxl8_anim_is_playing(const pxl8_anim* anim);\n" -"void pxl8_anim_pause(pxl8_anim* anim);\n" -"void pxl8_anim_play(pxl8_anim* anim);\n" -"void pxl8_anim_render_sprite(const pxl8_anim* anim, pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y);\n" -"void pxl8_anim_reset(pxl8_anim* anim);\n" -"void pxl8_anim_set_frame(pxl8_anim* anim, u16 frame);\n" -"void pxl8_anim_set_loop(pxl8_anim* anim, bool loop);\n" -"void pxl8_anim_set_reverse(pxl8_anim* anim, bool reverse);\n" -"void pxl8_anim_set_speed(pxl8_anim* anim, f32 speed);\n" -"i32 pxl8_anim_set_state(pxl8_anim* anim, const char* name);\n" -"void pxl8_anim_stop(pxl8_anim* anim);\n" -"void pxl8_anim_update(pxl8_anim* anim, f32 dt);\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_draw_triangle_textured(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, f32 u0, f32 v0, f32 u1, f32 v1, f32 u2, f32 v2, u32 texture_id);\n" -"void pxl8_3d_set_affine_textures(pxl8_gfx* gfx, bool affine);\n" -"void pxl8_3d_set_backface_culling(pxl8_gfx* gfx, bool culling);\n" -"void pxl8_3d_set_model(pxl8_gfx* gfx, pxl8_mat4 mat);\n" -"void pxl8_3d_set_projection(pxl8_gfx* gfx, pxl8_mat4 mat);\n" -"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" -"\n" -"typedef enum pxl8_procgen_type {\n" -" PXL8_PROCGEN_ROOMS = 0,\n" -" PXL8_PROCGEN_TERRAIN = 1\n" -"} pxl8_procgen_type;\n" -"\n" -"typedef struct pxl8_procgen_params {\n" -" pxl8_procgen_type type;\n" -" int width;\n" -" int height;\n" -" int depth;\n" -" unsigned int seed;\n" -" int min_room_size;\n" -" int max_room_size;\n" -" int num_rooms;\n" -"} pxl8_procgen_params;\n" -"\n" -"typedef struct pxl8_procgen_tex_params {\n" -" char name[16];\n" -" unsigned int seed;\n" -" int width;\n" -" int height;\n" -" float scale;\n" -" float roughness;\n" -" unsigned char base_color;\n" -" unsigned char variation;\n" -"} pxl8_procgen_tex_params;\n" -"\n" -"void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params);\n" -"\n" -"typedef struct pxl8_bsp pxl8_bsp;\n" -"typedef struct pxl8_bsp_face pxl8_bsp_face;\n" -"typedef bool (*pxl8_texture_rule)(const pxl8_vec3* normal, const pxl8_bsp_face* face, const pxl8_bsp* bsp);\n" -"\n" -"typedef struct pxl8_world_texture {\n" -" char name[16];\n" -" unsigned int texture_id;\n" -" pxl8_texture_rule rule;\n" -"} pxl8_world_texture;\n" -"\n" -"typedef struct pxl8_world pxl8_world;\n" -"pxl8_world* pxl8_world_create(void);\n" -"void pxl8_world_destroy(pxl8_world* world);\n" -"int pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params);\n" -"int pxl8_world_load(pxl8_world* world, const char* path);\n" -"void pxl8_world_unload(pxl8_world* world);\n" -"int pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_texture* textures, unsigned int count);\n" -"bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, float radius);\n" -"bool pxl8_world_is_loaded(const pxl8_world* world);\n" -"void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);\n" -"pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, float radius);\n" -"\n" -"typedef struct { i32 cursor_x; i32 cursor_y; bool cursor_down; bool cursor_clicked; u32 hot_id; u32 active_id; } pxl8_gui_state;\n" -"pxl8_gui_state* pxl8_gui_state_create(void);\n" -"void pxl8_gui_state_destroy(pxl8_gui_state* state);\n" -"void pxl8_gui_begin_frame(pxl8_gui_state* state);\n" -"void pxl8_gui_end_frame(pxl8_gui_state* state);\n" -"void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y);\n" -"void pxl8_gui_cursor_down(pxl8_gui_state* state);\n" -"void pxl8_gui_cursor_up(pxl8_gui_state* state);\n" -"bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label);\n" -"void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title);\n" -"void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color);\n" -"bool pxl8_gui_is_hovering(const pxl8_gui_state* state);\n" -"void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y);\n" -"\n" -"typedef struct pxl8_save pxl8_save;\n" -"pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version);\n" -"void pxl8_save_destroy(pxl8_save* save);\n" -"i32 pxl8_save_write(pxl8_save* save, u8 slot, const u8* data, u32 size);\n" -"i32 pxl8_save_read(pxl8_save* save, u8 slot, u8** data_out, u32* size_out);\n" -"void pxl8_save_free(u8* data);\n" -"bool pxl8_save_exists(pxl8_save* save, u8 slot);\n" -"i32 pxl8_save_delete(pxl8_save* save, u8 slot);\n" -"const char* pxl8_save_get_directory(pxl8_save* save);\n" -"\n" -"typedef struct pxl8_sfx_context pxl8_sfx_context;\n" -"typedef struct pxl8_sfx_mixer pxl8_sfx_mixer;\n" -"typedef struct pxl8_sfx_node pxl8_sfx_node;\n" -"typedef enum pxl8_sfx_filter_type { PXL8_SFX_FILTER_BANDPASS = 0, PXL8_SFX_FILTER_HIGHPASS, PXL8_SFX_FILTER_LOWPASS, PXL8_SFX_FILTER_NONE } pxl8_sfx_filter_type;\n" -"typedef enum pxl8_sfx_lfo_target { PXL8_SFX_LFO_AMPLITUDE = 0, PXL8_SFX_LFO_FILTER, PXL8_SFX_LFO_PITCH } pxl8_sfx_lfo_target;\n" -"typedef enum pxl8_sfx_node_type { PXL8_SFX_NODE_COMPRESSOR, PXL8_SFX_NODE_DELAY, PXL8_SFX_NODE_REVERB } pxl8_sfx_node_type;\n" -"typedef enum pxl8_sfx_waveform { PXL8_SFX_WAVE_NOISE = 0, PXL8_SFX_WAVE_PULSE, PXL8_SFX_WAVE_SAW, PXL8_SFX_WAVE_SINE, PXL8_SFX_WAVE_SQUARE, PXL8_SFX_WAVE_TRIANGLE } pxl8_sfx_waveform;\n" -"typedef struct pxl8_sfx_adsr { f32 attack; f32 decay; f32 sustain; f32 release; } pxl8_sfx_adsr;\n" -"typedef struct pxl8_sfx_compressor_config { f32 attack; f32 ratio; f32 release; f32 threshold; } pxl8_sfx_compressor_config;\n" -"typedef struct pxl8_sfx_delay_config { f32 feedback; f32 mix; u32 time_l; u32 time_r; } pxl8_sfx_delay_config;\n" -"typedef struct pxl8_sfx_reverb_config { f32 damping; f32 mix; f32 room; } pxl8_sfx_reverb_config;\n" -"typedef struct pxl8_sfx_voice_params { pxl8_sfx_adsr amp_env; pxl8_sfx_adsr filter_env; pxl8_sfx_filter_type filter_type; pxl8_sfx_lfo_target lfo_target; pxl8_sfx_waveform lfo_waveform; pxl8_sfx_waveform waveform; f32 filter_cutoff; f32 filter_env_depth; f32 filter_resonance; f32 fx_send; f32 lfo_depth; f32 lfo_rate; f32 pulse_width; } pxl8_sfx_voice_params;\n" -"pxl8_sfx_node* pxl8_sfx_compressor_create(pxl8_sfx_compressor_config cfg);\n" -"void pxl8_sfx_compressor_set_attack(pxl8_sfx_node* node, f32 attack);\n" -"void pxl8_sfx_compressor_set_ratio(pxl8_sfx_node* node, f32 ratio);\n" -"void pxl8_sfx_compressor_set_release(pxl8_sfx_node* node, f32 release);\n" -"void pxl8_sfx_compressor_set_threshold(pxl8_sfx_node* node, f32 threshold);\n" -"void pxl8_sfx_context_append_node(pxl8_sfx_context* ctx, pxl8_sfx_node* node);\n" -"pxl8_sfx_context* pxl8_sfx_context_create(void);\n" -"void pxl8_sfx_context_destroy(pxl8_sfx_context* ctx);\n" -"pxl8_sfx_node* pxl8_sfx_context_get_head(pxl8_sfx_context* ctx);\n" -"f32 pxl8_sfx_context_get_volume(const pxl8_sfx_context* ctx);\n" -"void pxl8_sfx_context_insert_node(pxl8_sfx_context* ctx, pxl8_sfx_node* after, pxl8_sfx_node* node);\n" -"void pxl8_sfx_context_remove_node(pxl8_sfx_context* ctx, pxl8_sfx_node* node);\n" -"void pxl8_sfx_context_set_volume(pxl8_sfx_context* ctx, f32 volume);\n" -"pxl8_sfx_node* pxl8_sfx_delay_create(pxl8_sfx_delay_config cfg);\n" -"void pxl8_sfx_delay_set_feedback(pxl8_sfx_node* node, f32 feedback);\n" -"void pxl8_sfx_delay_set_mix(pxl8_sfx_node* node, f32 mix);\n" -"void pxl8_sfx_delay_set_time(pxl8_sfx_node* node, u32 time_l, u32 time_r);\n" -"void pxl8_sfx_mixer_attach(pxl8_sfx_mixer* mixer, pxl8_sfx_context* ctx);\n" -"pxl8_sfx_mixer* pxl8_sfx_mixer_create(void);\n" -"void pxl8_sfx_mixer_destroy(pxl8_sfx_mixer* mixer);\n" -"void pxl8_sfx_mixer_detach(pxl8_sfx_mixer* mixer, pxl8_sfx_context* ctx);\n" -"f32 pxl8_sfx_mixer_get_master_volume(const pxl8_sfx_mixer* mixer);\n" -"void pxl8_sfx_mixer_set_master_volume(pxl8_sfx_mixer* mixer, f32 volume);\n" -"void pxl8_sfx_node_destroy(pxl8_sfx_node* node);\n" -"f32 pxl8_sfx_note_to_freq(u8 note);\n" -"u16 pxl8_sfx_play_note(pxl8_sfx_context* ctx, u8 note, const pxl8_sfx_voice_params* params, f32 volume, f32 duration);\n" -"void pxl8_sfx_release_voice(pxl8_sfx_context* ctx, u16 voice_id);\n" -"pxl8_sfx_node* pxl8_sfx_reverb_create(pxl8_sfx_reverb_config cfg);\n" -"void pxl8_sfx_reverb_set_damping(pxl8_sfx_node* node, f32 damping);\n" -"void pxl8_sfx_reverb_set_mix(pxl8_sfx_node* node, f32 mix);\n" -"void pxl8_sfx_reverb_set_room(pxl8_sfx_node* node, f32 room);\n" -"void pxl8_sfx_stop_all(pxl8_sfx_context* ctx);\n" -"void pxl8_sfx_stop_voice(pxl8_sfx_context* ctx, u16 voice_id);\n"; - void pxl8_lua_log(int level, const char* file, int line, const char* msg) { if (file && (file[0] == '?' || file[0] == '\0')) file = NULL; switch (level) { diff --git a/src/pxl8_script.h b/client/src/script/pxl8_script.h similarity index 100% rename from src/pxl8_script.h rename to client/src/script/pxl8_script.h diff --git a/client/src/script/pxl8_script_ffi.h b/client/src/script/pxl8_script_ffi.h new file mode 100644 index 0000000..66e1189 --- /dev/null +++ b/client/src/script/pxl8_script_ffi.h @@ -0,0 +1,389 @@ +#pragma once + +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 pxl8;\n" +"typedef struct pxl8_gfx pxl8_gfx;\n" +"typedef struct { int x, y, w, h; } pxl8_bounds;\n" +"typedef struct { int x, y; } pxl8_point;\n" +"typedef struct pxl8_rng { u32 state; } pxl8_rng;\n" +"\n" +"void pxl8_rng_seed(pxl8_rng* rng, u32 seed);\n" +"u32 pxl8_rng_next(pxl8_rng* rng);\n" +"f32 pxl8_rng_f32(pxl8_rng* rng);\n" +"i32 pxl8_rng_range(pxl8_rng* rng, i32 min, i32 max);\n" +"\n" +"f32 pxl8_get_fps(const pxl8* sys);\n" +"\n" +"u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);\n" +"i32 pxl8_gfx_get_height(pxl8_gfx* ctx);\n" +"typedef struct pxl8_palette pxl8_palette;\n" +"pxl8_palette* pxl8_gfx_get_palette(pxl8_gfx* gfx);\n" +"u32 pxl8_palette_color(const pxl8_palette* pal, u8 idx);\n" +"i32 pxl8_palette_index(const pxl8_palette* pal, u32 color);\n" +"u8 pxl8_palette_ramp_index(const pxl8_palette* pal, u8 position);\n" +"i32 pxl8_gfx_get_width(pxl8_gfx* ctx);\n" +"void pxl8_2d_circle(pxl8_gfx* ctx, i32 x, i32 y, i32 r, u32 color);\n" +"void pxl8_2d_circle_fill(pxl8_gfx* ctx, i32 x, i32 y, i32 r, u32 color);\n" +"void pxl8_2d_clear(pxl8_gfx* ctx, u32 color);\n" +"u32 pxl8_2d_get_pixel(pxl8_gfx* ctx, i32 x, i32 y);\n" +"void pxl8_2d_line(pxl8_gfx* ctx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color);\n" +"void pxl8_2d_pixel(pxl8_gfx* ctx, i32 x, i32 y, u32 color);\n" +"void pxl8_2d_rect(pxl8_gfx* ctx, i32 x, i32 y, i32 w, i32 h, u32 color);\n" +"void pxl8_2d_rect_fill(pxl8_gfx* ctx, i32 x, i32 y, i32 w, i32 h, u32 color);\n" +"void pxl8_2d_sprite(pxl8_gfx* ctx, u32 id, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y);\n" +"void pxl8_2d_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_cycle_palette(pxl8_gfx* ctx, u8 start, u8 count, i32 step);\n" +"void pxl8_gfx_fade_palette(pxl8_gfx* ctx, u8 start, u8 count, f32 amount, u32 target_color);\n" +"i32 pxl8_gfx_add_palette_cycle(pxl8_gfx* ctx, u8 start_index, u8 end_index, f32 speed);\n" +"void pxl8_gfx_remove_palette_cycle(pxl8_gfx* ctx, i32 cycle_id);\n" +"void pxl8_gfx_set_palette_cycle_speed(pxl8_gfx* ctx, i32 cycle_id, f32 speed);\n" +"void pxl8_gfx_clear_palette_cycles(pxl8_gfx* ctx);\n" +"void pxl8_gfx_update(pxl8_gfx* ctx, f32 dt);\n" +"i32 pxl8_gfx_load_palette(pxl8_gfx* ctx, const char* filepath);\n" +"i32 pxl8_gfx_load_sprite(pxl8_gfx* ctx, const char* filepath, u32* sprite_id);\n" +"i32 pxl8_gfx_create_texture(pxl8_gfx* ctx, const u8* pixels, u32 width, u32 height);\n" +"bool pxl8_gfx_push_target(pxl8_gfx* ctx);\n" +"void pxl8_gfx_pop_target(pxl8_gfx* ctx);\n" +"typedef struct pxl8_input_state pxl8_input_state;\n" +"bool pxl8_key_down(const pxl8_input_state* input, const char* key_name);\n" +"bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name);\n" +"bool pxl8_key_released(const pxl8_input_state* input, const char* key_name);\n" +"bool pxl8_mouse_pressed(const pxl8_input_state* input, i32 button);\n" +"bool pxl8_mouse_released(const pxl8_input_state* input, i32 button);\n" +"int pxl8_mouse_wheel_x(const pxl8_input_state* input);\n" +"int pxl8_mouse_wheel_y(const pxl8_input_state* input);\n" +"int pxl8_mouse_x(const pxl8_input_state* input);\n" +"int pxl8_mouse_y(const pxl8_input_state* input);\n" +"int pxl8_mouse_dx(const pxl8_input_state* input);\n" +"int pxl8_mouse_dy(const pxl8_input_state* input);\n" +"typedef enum { PXL8_CURSOR_ARROW = 0, PXL8_CURSOR_HAND = 1 } pxl8_cursor;\n" +"void pxl8_center_cursor(pxl8* sys);\n" +"void pxl8_set_cursor(pxl8* sys, pxl8_cursor cursor);\n" +"void pxl8_set_relative_mouse_mode(pxl8* sys, bool enabled);\n" +"void pxl8_set_running(pxl8* sys, bool running);\n" +"void pxl8_lua_log(int level, const char* file, int line, const char* msg);\n" +"typedef struct pxl8_cart pxl8_cart;\n" +"pxl8_cart* pxl8_get_cart(void);\n" +"const char* pxl8_cart_get_title(const pxl8_cart* cart);\n" +"typedef u32 pxl8_tile;\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" +"u32 pxl8_tilemap_get_height(const pxl8_tilemap* tilemap);\n" +"pxl8_tile pxl8_tilemap_get_tile(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y);\n" +"u32 pxl8_tilemap_get_tile_size(const pxl8_tilemap* tilemap);\n" +"void* pxl8_tilemap_get_tile_user_data(const pxl8_tilemap* tilemap, u16 tile_id);\n" +"u32 pxl8_tilemap_get_width(const pxl8_tilemap* tilemap);\n" +"void pxl8_tilemap_set_tile_user_data(pxl8_tilemap* tilemap, u16 tile_id, void* user_data);\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" +"i32 pxl8_tilemap_load_ase(pxl8_tilemap* tilemap, const char* filepath, u32 layer);\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 pxl8_particles pxl8_particles;\n" +"pxl8_particles* pxl8_particles_create(u32 max_count, pxl8_rng* rng);\n" +"void pxl8_particles_destroy(pxl8_particles* ps);\n" +"void pxl8_particles_clear(pxl8_particles* ps);\n" +"void pxl8_particles_emit(pxl8_particles* ps, u32 count);\n" +"void pxl8_particles_render(pxl8_particles* ps, pxl8_gfx* gfx);\n" +"void pxl8_particles_update(pxl8_particles* ps, float dt);\n" +"u32 pxl8_particles_count(const pxl8_particles* ps);\n" +"u32 pxl8_particles_max_count(const pxl8_particles* ps);\n" +"pxl8_particle* pxl8_particles_get(pxl8_particles* ps, u32 index);\n" +"pxl8_rng* pxl8_particles_rng(pxl8_particles* ps);\n" +"f32 pxl8_particles_get_drag(const pxl8_particles* ps);\n" +"f32 pxl8_particles_get_gravity_x(const pxl8_particles* ps);\n" +"f32 pxl8_particles_get_gravity_y(const pxl8_particles* ps);\n" +"f32 pxl8_particles_get_spawn_rate(const pxl8_particles* ps);\n" +"f32 pxl8_particles_get_spread_x(const pxl8_particles* ps);\n" +"f32 pxl8_particles_get_spread_y(const pxl8_particles* ps);\n" +"f32 pxl8_particles_get_turbulence(const pxl8_particles* ps);\n" +"f32 pxl8_particles_get_x(const pxl8_particles* ps);\n" +"f32 pxl8_particles_get_y(const pxl8_particles* ps);\n" +"void pxl8_particles_set_colors(pxl8_particles* ps, u8 color_min, u8 color_max);\n" +"void pxl8_particles_set_drag(pxl8_particles* ps, f32 drag);\n" +"void pxl8_particles_set_gravity(pxl8_particles* ps, f32 gx, f32 gy);\n" +"void pxl8_particles_set_life(pxl8_particles* ps, f32 life_min, f32 life_max);\n" +"void pxl8_particles_set_palette(pxl8_particles* ps, pxl8_palette* palette);\n" +"void pxl8_particles_set_position(pxl8_particles* ps, f32 x, f32 y);\n" +"void pxl8_particles_set_size(pxl8_particles* ps, f32 size_min, f32 size_max);\n" +"void pxl8_particles_set_spawn_rate(pxl8_particles* ps, f32 rate);\n" +"void pxl8_particles_set_spread(pxl8_particles* ps, f32 spread_x, f32 spread_y);\n" +"void pxl8_particles_set_turbulence(pxl8_particles* ps, f32 turbulence);\n" +"void pxl8_particles_set_velocity(pxl8_particles* ps, f32 vx_min, f32 vx_max, f32 vy_min, f32 vy_max);\n" +"\n" +"typedef struct pxl8_transition pxl8_transition;\n" +"typedef struct pxl8_anim pxl8_anim;\n" +"\n" +"pxl8_transition* pxl8_transition_create(i32 type, f32 duration);\n" +"void pxl8_transition_destroy(pxl8_transition* transition);\n" +"f32 pxl8_transition_get_progress(const pxl8_transition* transition);\n" +"bool pxl8_transition_is_active(const pxl8_transition* transition);\n" +"bool pxl8_transition_is_complete(const pxl8_transition* transition);\n" +"void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx);\n" +"void pxl8_transition_reset(pxl8_transition* transition);\n" +"void pxl8_transition_set_color(pxl8_transition* transition, u32 color);\n" +"void pxl8_transition_set_reverse(pxl8_transition* transition, bool reverse);\n" +"void pxl8_transition_start(pxl8_transition* transition);\n" +"void pxl8_transition_stop(pxl8_transition* transition);\n" +"void pxl8_transition_update(pxl8_transition* transition, f32 dt);\n" +"\n" +"pxl8_anim* pxl8_anim_create(const u32* frame_ids, const u16* frame_durations, u16 frame_count);\n" +"pxl8_anim* pxl8_anim_create_from_ase(pxl8_gfx* gfx, const char* path);\n" +"void pxl8_anim_destroy(pxl8_anim* anim);\n" +"i32 pxl8_anim_add_state(pxl8_anim* anim, const char* name, pxl8_anim* state_anim);\n" +"u16 pxl8_anim_get_current_frame(const pxl8_anim* anim);\n" +"u32 pxl8_anim_get_current_frame_id(const pxl8_anim* anim);\n" +"const char* pxl8_anim_get_state(const pxl8_anim* anim);\n" +"bool pxl8_anim_has_state_machine(const pxl8_anim* anim);\n" +"bool pxl8_anim_is_complete(const pxl8_anim* anim);\n" +"bool pxl8_anim_is_playing(const pxl8_anim* anim);\n" +"void pxl8_anim_pause(pxl8_anim* anim);\n" +"void pxl8_anim_play(pxl8_anim* anim);\n" +"void pxl8_anim_render_sprite(const pxl8_anim* anim, pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y);\n" +"void pxl8_anim_reset(pxl8_anim* anim);\n" +"void pxl8_anim_set_frame(pxl8_anim* anim, u16 frame);\n" +"void pxl8_anim_set_loop(pxl8_anim* anim, bool loop);\n" +"void pxl8_anim_set_reverse(pxl8_anim* anim, bool reverse);\n" +"void pxl8_anim_set_speed(pxl8_anim* anim, f32 speed);\n" +"i32 pxl8_anim_set_state(pxl8_anim* anim, const char* name);\n" +"void pxl8_anim_stop(pxl8_anim* anim);\n" +"void pxl8_anim_update(pxl8_anim* anim, f32 dt);\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" +"typedef struct pxl8_3d_uniforms {\n" +" u8 ambient;\n" +" u8 fog_color;\n" +" f32 fog_density;\n" +" f32 time;\n" +"} pxl8_3d_uniforms;\n" +"\n" +"typedef struct pxl8_3d_camera pxl8_3d_camera;\n" +"pxl8_3d_camera* pxl8_3d_camera_create(void);\n" +"void pxl8_3d_camera_destroy(pxl8_3d_camera* cam);\n" +"void pxl8_3d_camera_set_perspective(pxl8_3d_camera* cam, f32 fov, f32 aspect, f32 near, f32 far);\n" +"void pxl8_3d_camera_set_position(pxl8_3d_camera* cam, pxl8_vec3 pos);\n" +"void pxl8_3d_camera_set_rotation(pxl8_3d_camera* cam, f32 pitch, f32 yaw, f32 roll);\n" +"void pxl8_3d_camera_lookat(pxl8_3d_camera* cam, pxl8_vec3 eye, pxl8_vec3 target, pxl8_vec3 up);\n" +"pxl8_vec3 pxl8_3d_camera_get_forward(const pxl8_3d_camera* cam);\n" +"pxl8_vec3 pxl8_3d_camera_get_position(const pxl8_3d_camera* cam);\n" +"pxl8_vec3 pxl8_3d_camera_get_right(const pxl8_3d_camera* cam);\n" +"pxl8_vec3 pxl8_3d_camera_get_up(const pxl8_3d_camera* cam);\n" +"pxl8_mat4 pxl8_3d_camera_get_view(const pxl8_3d_camera* cam);\n" +"pxl8_mat4 pxl8_3d_camera_get_projection(const pxl8_3d_camera* cam);\n" +"void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt);\n" +"\n" +"void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms);\n" +"void pxl8_3d_clear(pxl8_gfx* gfx, u8 color);\n" +"void pxl8_3d_clear_depth(pxl8_gfx* gfx);\n" +"void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u8 color);\n" +"void pxl8_3d_end_frame(pxl8_gfx* gfx);\n" +"\n" +"typedef enum pxl8_blend_mode { PXL8_BLEND_OPAQUE = 0, PXL8_BLEND_ALPHA_TEST, PXL8_BLEND_ALPHA, PXL8_BLEND_ADDITIVE } pxl8_blend_mode;\n" +"\n" +"typedef struct pxl8_material {\n" +" u32 texture_id;\n" +" u8 alpha;\n" +" u8 blend_mode;\n" +" bool dither;\n" +" bool double_sided;\n" +" bool dynamic_lighting;\n" +" bool per_pixel;\n" +" bool vertex_color_passthrough;\n" +" f32 emissive_intensity;\n" +"} pxl8_material;\n" +"\n" +"typedef struct pxl8_vertex {\n" +" pxl8_vec3 position;\n" +" pxl8_vec3 normal;\n" +" f32 u, v;\n" +" u8 color;\n" +" u8 light;\n" +" u8 _pad[2];\n" +"} pxl8_vertex;\n" +"\n" +"typedef struct pxl8_mesh {\n" +" pxl8_vertex* vertices;\n" +" u16* indices;\n" +" u32 vertex_count;\n" +" u32 index_count;\n" +" u32 vertex_capacity;\n" +" u32 index_capacity;\n" +"} pxl8_mesh;\n" +"\n" +"pxl8_mesh* pxl8_mesh_create(u32 vertex_capacity, u32 index_capacity);\n" +"void pxl8_mesh_destroy(pxl8_mesh* mesh);\n" +"void pxl8_mesh_clear(pxl8_mesh* mesh);\n" +"u16 pxl8_mesh_push_vertex(pxl8_mesh* mesh, pxl8_vertex v);\n" +"void pxl8_mesh_push_triangle(pxl8_mesh* mesh, u16 i0, u16 i1, u16 i2);\n" +"void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, pxl8_mat4 model, pxl8_material material);\n" +"\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_mul(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" +"\n" +"typedef enum pxl8_procgen_type {\n" +" PXL8_PROCGEN_ROOMS = 0,\n" +" PXL8_PROCGEN_TERRAIN = 1\n" +"} pxl8_procgen_type;\n" +"\n" +"typedef struct pxl8_procgen_params {\n" +" pxl8_procgen_type type;\n" +" int width;\n" +" int height;\n" +" int depth;\n" +" unsigned int seed;\n" +" int min_room_size;\n" +" int max_room_size;\n" +" int num_rooms;\n" +"} pxl8_procgen_params;\n" +"\n" +"typedef struct pxl8_procgen_tex_params {\n" +" char name[16];\n" +" unsigned int seed;\n" +" int width;\n" +" int height;\n" +" float scale;\n" +" float roughness;\n" +" unsigned char base_color;\n" +" unsigned char variation;\n" +"} pxl8_procgen_tex_params;\n" +"\n" +"void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params);\n" +"\n" +"typedef struct pxl8_bsp pxl8_bsp;\n" +"typedef struct pxl8_bsp_face pxl8_bsp_face;\n" +"typedef bool (*pxl8_texture_rule)(const pxl8_vec3* normal, const pxl8_bsp_face* face, const pxl8_bsp* bsp);\n" +"\n" +"typedef struct pxl8_world_texture {\n" +" char name[16];\n" +" unsigned int texture_id;\n" +" pxl8_texture_rule rule;\n" +"} pxl8_world_texture;\n" +"\n" +"typedef struct pxl8_world pxl8_world;\n" +"pxl8_world* pxl8_world_create(void);\n" +"void pxl8_world_destroy(pxl8_world* world);\n" +"int pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params);\n" +"int pxl8_world_load(pxl8_world* world, const char* path);\n" +"void pxl8_world_unload(pxl8_world* world);\n" +"int pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_texture* textures, unsigned int count);\n" +"bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, float radius);\n" +"bool pxl8_world_is_loaded(const pxl8_world* world);\n" +"void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);\n" +"pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, float radius);\n" +"\n" +"typedef struct { i32 cursor_x; i32 cursor_y; bool cursor_down; bool cursor_clicked; u32 hot_id; u32 active_id; } pxl8_gui_state;\n" +"pxl8_gui_state* pxl8_gui_state_create(void);\n" +"void pxl8_gui_state_destroy(pxl8_gui_state* state);\n" +"void pxl8_gui_begin_frame(pxl8_gui_state* state);\n" +"void pxl8_gui_end_frame(pxl8_gui_state* state);\n" +"void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y);\n" +"void pxl8_gui_cursor_down(pxl8_gui_state* state);\n" +"void pxl8_gui_cursor_up(pxl8_gui_state* state);\n" +"bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label);\n" +"void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title);\n" +"void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color);\n" +"bool pxl8_gui_is_hovering(const pxl8_gui_state* state);\n" +"void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y);\n" +"\n" +"typedef struct pxl8_save pxl8_save;\n" +"pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version);\n" +"void pxl8_save_destroy(pxl8_save* save);\n" +"i32 pxl8_save_write(pxl8_save* save, u8 slot, const u8* data, u32 size);\n" +"i32 pxl8_save_read(pxl8_save* save, u8 slot, u8** data_out, u32* size_out);\n" +"void pxl8_save_free(u8* data);\n" +"bool pxl8_save_exists(pxl8_save* save, u8 slot);\n" +"i32 pxl8_save_delete(pxl8_save* save, u8 slot);\n" +"const char* pxl8_save_get_directory(pxl8_save* save);\n" +"\n" +"typedef struct pxl8_sfx_context pxl8_sfx_context;\n" +"typedef struct pxl8_sfx_mixer pxl8_sfx_mixer;\n" +"typedef struct pxl8_sfx_node pxl8_sfx_node;\n" +"typedef enum pxl8_sfx_filter_type { PXL8_SFX_FILTER_BANDPASS = 0, PXL8_SFX_FILTER_HIGHPASS, PXL8_SFX_FILTER_LOWPASS, PXL8_SFX_FILTER_NONE } pxl8_sfx_filter_type;\n" +"typedef enum pxl8_sfx_lfo_target { PXL8_SFX_LFO_AMPLITUDE = 0, PXL8_SFX_LFO_FILTER, PXL8_SFX_LFO_PITCH } pxl8_sfx_lfo_target;\n" +"typedef enum pxl8_sfx_node_type { PXL8_SFX_NODE_COMPRESSOR, PXL8_SFX_NODE_DELAY, PXL8_SFX_NODE_REVERB } pxl8_sfx_node_type;\n" +"typedef enum pxl8_sfx_waveform { PXL8_SFX_WAVE_NOISE = 0, PXL8_SFX_WAVE_PULSE, PXL8_SFX_WAVE_SAW, PXL8_SFX_WAVE_SINE, PXL8_SFX_WAVE_SQUARE, PXL8_SFX_WAVE_TRIANGLE } pxl8_sfx_waveform;\n" +"typedef struct pxl8_sfx_adsr { f32 attack; f32 decay; f32 sustain; f32 release; } pxl8_sfx_adsr;\n" +"typedef struct pxl8_sfx_compressor_config { f32 attack; f32 ratio; f32 release; f32 threshold; } pxl8_sfx_compressor_config;\n" +"typedef struct pxl8_sfx_delay_config { f32 feedback; f32 mix; u32 time_l; u32 time_r; } pxl8_sfx_delay_config;\n" +"typedef struct pxl8_sfx_reverb_config { f32 damping; f32 mix; f32 room; } pxl8_sfx_reverb_config;\n" +"typedef struct pxl8_sfx_voice_params { pxl8_sfx_adsr amp_env; pxl8_sfx_adsr filter_env; pxl8_sfx_filter_type filter_type; pxl8_sfx_lfo_target lfo_target; pxl8_sfx_waveform lfo_waveform; pxl8_sfx_waveform waveform; f32 filter_cutoff; f32 filter_env_depth; f32 filter_resonance; f32 fx_send; f32 lfo_depth; f32 lfo_rate; f32 pulse_width; } pxl8_sfx_voice_params;\n" +"pxl8_sfx_node* pxl8_sfx_compressor_create(pxl8_sfx_compressor_config cfg);\n" +"void pxl8_sfx_compressor_set_attack(pxl8_sfx_node* node, f32 attack);\n" +"void pxl8_sfx_compressor_set_ratio(pxl8_sfx_node* node, f32 ratio);\n" +"void pxl8_sfx_compressor_set_release(pxl8_sfx_node* node, f32 release);\n" +"void pxl8_sfx_compressor_set_threshold(pxl8_sfx_node* node, f32 threshold);\n" +"void pxl8_sfx_context_append_node(pxl8_sfx_context* ctx, pxl8_sfx_node* node);\n" +"pxl8_sfx_context* pxl8_sfx_context_create(void);\n" +"void pxl8_sfx_context_destroy(pxl8_sfx_context* ctx);\n" +"pxl8_sfx_node* pxl8_sfx_context_get_head(pxl8_sfx_context* ctx);\n" +"f32 pxl8_sfx_context_get_volume(const pxl8_sfx_context* ctx);\n" +"void pxl8_sfx_context_insert_node(pxl8_sfx_context* ctx, pxl8_sfx_node* after, pxl8_sfx_node* node);\n" +"void pxl8_sfx_context_remove_node(pxl8_sfx_context* ctx, pxl8_sfx_node* node);\n" +"void pxl8_sfx_context_set_volume(pxl8_sfx_context* ctx, f32 volume);\n" +"pxl8_sfx_node* pxl8_sfx_delay_create(pxl8_sfx_delay_config cfg);\n" +"void pxl8_sfx_delay_set_feedback(pxl8_sfx_node* node, f32 feedback);\n" +"void pxl8_sfx_delay_set_mix(pxl8_sfx_node* node, f32 mix);\n" +"void pxl8_sfx_delay_set_time(pxl8_sfx_node* node, u32 time_l, u32 time_r);\n" +"void pxl8_sfx_mixer_attach(pxl8_sfx_mixer* mixer, pxl8_sfx_context* ctx);\n" +"pxl8_sfx_mixer* pxl8_sfx_mixer_create(void);\n" +"void pxl8_sfx_mixer_destroy(pxl8_sfx_mixer* mixer);\n" +"void pxl8_sfx_mixer_detach(pxl8_sfx_mixer* mixer, pxl8_sfx_context* ctx);\n" +"f32 pxl8_sfx_mixer_get_master_volume(const pxl8_sfx_mixer* mixer);\n" +"void pxl8_sfx_mixer_set_master_volume(pxl8_sfx_mixer* mixer, f32 volume);\n" +"void pxl8_sfx_node_destroy(pxl8_sfx_node* node);\n" +"f32 pxl8_sfx_note_to_freq(u8 note);\n" +"u16 pxl8_sfx_play_note(pxl8_sfx_context* ctx, u8 note, const pxl8_sfx_voice_params* params, f32 volume, f32 duration);\n" +"void pxl8_sfx_release_voice(pxl8_sfx_context* ctx, u16 voice_id);\n" +"pxl8_sfx_node* pxl8_sfx_reverb_create(pxl8_sfx_reverb_config cfg);\n" +"void pxl8_sfx_reverb_set_damping(pxl8_sfx_node* node, f32 damping);\n" +"void pxl8_sfx_reverb_set_mix(pxl8_sfx_node* node, f32 mix);\n" +"void pxl8_sfx_reverb_set_room(pxl8_sfx_node* node, f32 room);\n" +"void pxl8_sfx_stop_all(pxl8_sfx_context* ctx);\n" +"void pxl8_sfx_stop_voice(pxl8_sfx_context* ctx, u16 voice_id);\n"; diff --git a/src/pxl8_sfx.c b/client/src/sfx/pxl8_sfx.c similarity index 100% rename from src/pxl8_sfx.c rename to client/src/sfx/pxl8_sfx.c diff --git a/src/pxl8_sfx.h b/client/src/sfx/pxl8_sfx.h similarity index 100% rename from src/pxl8_sfx.h rename to client/src/sfx/pxl8_sfx.h diff --git a/src/pxl8_bsp.c b/client/src/world/pxl8_bsp.c similarity index 63% rename from src/pxl8_bsp.c rename to client/src/world/pxl8_bsp.c index c73e4c2..5beacd2 100644 --- a/src/pxl8_bsp.c +++ b/client/src/world/pxl8_bsp.c @@ -4,6 +4,7 @@ #include #include +#include "pxl8_color.h" #include "pxl8_gfx.h" #include "pxl8_io.h" #include "pxl8_log.h" @@ -367,18 +368,177 @@ i32 pxl8_bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) { } bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to) { - if (!bsp || !bsp->visdata || leaf_from < 0 || leaf_to < 0) return true; + if (!bsp || !bsp->visdata || bsp->visdata_size == 0) return true; + if (leaf_from < 0 || leaf_to < 0) return true; if ((u32)leaf_from >= bsp->num_leafs || (u32)leaf_to >= bsp->num_leafs) return true; i32 visofs = bsp->leafs[leaf_from].visofs; if (visofs < 0) return true; - i32 byte_idx = leaf_to >> 3; - i32 bit_idx = leaf_to & 7; + u32 target_byte = leaf_to >> 3; + u32 target_bit = leaf_to & 7; + u32 pvs_size = (bsp->num_leafs + 7) / 8; - if ((u32)visofs + byte_idx >= bsp->visdata_size) return true; + u32 pos = (u32)visofs; + u32 current_byte = 0; - return (bsp->visdata[visofs + byte_idx] & (1 << bit_idx)) != 0; + while (current_byte < pvs_size && pos < bsp->visdata_size) { + u8 b = bsp->visdata[pos++]; + + if (b != 0) { + if (current_byte == target_byte) { + return (b & (1 << target_bit)) != 0; + } + current_byte++; + } else { + if (pos >= bsp->visdata_size) return false; + u32 count = bsp->visdata[pos++]; + if (target_byte < current_byte + count) { + return false; + } + current_byte += count; + } + } + + return false; +} + +pxl8_bsp_pvs pxl8_bsp_decompress_pvs(const pxl8_bsp* bsp, i32 leaf) { + pxl8_bsp_pvs pvs = {0}; + + u32 pvs_size = (bsp->num_leafs + 7) / 8; + pvs.data = calloc(pvs_size, 1); + pvs.size = pvs_size; + + if (!pvs.data) return pvs; + + if (!bsp || leaf < 0 || (u32)leaf >= bsp->num_leafs) { + memset(pvs.data, 0xFF, pvs_size); + return pvs; + } + + i32 visofs = bsp->leafs[leaf].visofs; + if (visofs < 0 || !bsp->visdata || bsp->visdata_size == 0) { + memset(pvs.data, 0xFF, pvs_size); + return pvs; + } + + u32 pos = (u32)visofs; + u32 out = 0; + + while (out < pvs_size && pos < bsp->visdata_size) { + u8 b = bsp->visdata[pos++]; + + if (b != 0) { + pvs.data[out++] = b; + } else { + if (pos >= bsp->visdata_size) break; + u32 count = bsp->visdata[pos++]; + out += count; + } + } + + return pvs; +} + +void pxl8_bsp_pvs_destroy(pxl8_bsp_pvs* pvs) { + if (pvs) { + free(pvs->data); + pvs->data = NULL; + pvs->size = 0; + } +} + +bool pxl8_bsp_pvs_is_visible(const pxl8_bsp_pvs* pvs, i32 leaf) { + if (!pvs || !pvs->data || leaf < 0) return false; + u32 byte_idx = leaf >> 3; + u32 bit_idx = leaf & 7; + if (byte_idx >= pvs->size) return false; + return (pvs->data[byte_idx] & (1 << bit_idx)) != 0; +} + +pxl8_bsp_lightmap pxl8_bsp_lightmap_uniform(u8 r, u8 g, u8 b) { + return (pxl8_bsp_lightmap){ + .color = {r, g, b}, + .height = 0, + .offset = 0, + .width = 0, + }; +} + +pxl8_bsp_lightmap pxl8_bsp_lightmap_mapped(u8 width, u8 height, u32 offset) { + return (pxl8_bsp_lightmap){ + .color = {0, 0, 0}, + .height = height, + .offset = offset, + .width = width, + }; +} + +pxl8_bsp_lightmap_sample pxl8_bsp_sample_lightmap(const pxl8_bsp* bsp, u32 face_idx, f32 u, f32 v) { + pxl8_bsp_lightmap_sample white = {255, 255, 255}; + + if (!bsp || !bsp->lightmaps || face_idx >= bsp->num_lightmaps) { + return white; + } + + const pxl8_bsp_lightmap* lm = &bsp->lightmaps[face_idx]; + + if (lm->width == 0) { + return (pxl8_bsp_lightmap_sample){lm->color[2], lm->color[1], lm->color[0]}; + } + + if (!bsp->lightdata || bsp->lightdata_size == 0) { + return white; + } + + f32 w = (f32)lm->width; + f32 h = (f32)lm->height; + f32 fx = u * w; + f32 fy = v * h; + + if (fx < 0) fx = 0; + if (fx > w - 1.001f) fx = w - 1.001f; + if (fy < 0) fy = 0; + if (fy > h - 1.001f) fy = h - 1.001f; + + u32 x0 = (u32)fx; + u32 y0 = (u32)fy; + u32 x1 = x0 + 1; + u32 y1 = y0 + 1; + if (x1 >= lm->width) x1 = lm->width - 1; + if (y1 >= lm->height) y1 = lm->height - 1; + + f32 frac_x = fx - (f32)x0; + f32 frac_y = fy - (f32)y0; + + u32 stride = lm->width; + u32 base = lm->offset; + + u32 idx00 = base + y0 * stride + x0; + u32 idx10 = base + y0 * stride + x1; + u32 idx01 = base + y1 * stride + x0; + u32 idx11 = base + y1 * stride + x1; + + u8 r00, g00, b00, r10, g10, b10, r01, g01, b01, r11, g11, b11; + + if (idx00 < bsp->lightdata_size) pxl8_rgb332_unpack(bsp->lightdata[idx00], &r00, &g00, &b00); + else { r00 = g00 = b00 = 255; } + if (idx10 < bsp->lightdata_size) pxl8_rgb332_unpack(bsp->lightdata[idx10], &r10, &g10, &b10); + else { r10 = g10 = b10 = 255; } + if (idx01 < bsp->lightdata_size) pxl8_rgb332_unpack(bsp->lightdata[idx01], &r01, &g01, &b01); + else { r01 = g01 = b01 = 255; } + if (idx11 < bsp->lightdata_size) pxl8_rgb332_unpack(bsp->lightdata[idx11], &r11, &g11, &b11); + else { r11 = g11 = b11 = 255; } + + f32 inv_x = 1.0f - frac_x; + f32 inv_y = 1.0f - frac_y; + + u8 r = (u8)(r00 * inv_x * inv_y + r10 * frac_x * inv_y + r01 * inv_x * frac_y + r11 * frac_x * frac_y); + u8 g = (u8)(g00 * inv_x * inv_y + g10 * frac_x * inv_y + g01 * inv_x * frac_y + g11 * frac_x * frac_y); + u8 b = (u8)(b00 * inv_x * inv_y + b10 * frac_x * inv_y + b01 * inv_x * frac_y + b11 * frac_x * frac_y); + + return (pxl8_bsp_lightmap_sample){b, g, r}; } static inline bool face_in_frustum(const pxl8_bsp* bsp, u32 face_id, const pxl8_frustum* frustum) { @@ -386,64 +546,114 @@ static inline bool face_in_frustum(const pxl8_bsp* bsp, u32 face_id, const pxl8_ return pxl8_frustum_test_aabb(frustum, face->aabb_min, face->aabb_max); } -void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 texture_id) { - if (!gfx || !bsp || face_id >= bsp->num_faces) return; - +static void collect_face_to_mesh( + const pxl8_bsp* bsp, + u32 face_id, + pxl8_mesh* mesh +) { const pxl8_bsp_face* face = &bsp->faces[face_id]; if (face->num_edges < 3) return; - pxl8_vec3 verts[64]; + pxl8_vec3 normal = {0, 1, 0}; + if (face->plane_id < bsp->num_planes) { + normal = bsp->planes[face->plane_id].normal; + if (face->side) { + normal.x = -normal.x; + normal.y = -normal.y; + normal.z = -normal.z; + } + } + + const pxl8_bsp_texinfo* texinfo = NULL; + f32 tex_scale = 64.0f; + if (face->texinfo_id < bsp->num_texinfo) { + texinfo = &bsp->texinfo[face->texinfo_id]; + } + + u16 base_idx = (u16)mesh->vertex_count; u32 num_verts = 0; - - u32 color = 15; - bool use_texture = (face->texinfo_id < bsp->num_texinfo); - + static int face_debug = 0; + bool debug_this = (face_debug++ < 3); for (u32 i = 0; i < face->num_edges && num_verts < 64; i++) { i32 surfedge_idx = face->first_edge + i; u32 vert_idx; - if (!pxl8_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) continue; + if (!pxl8_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) { + if (debug_this) pxl8_debug("face %u: edge %u failed to get vertex (surfedge_idx=%d)", face_id, i, surfedge_idx); + continue; + } - verts[num_verts++] = bsp->vertices[vert_idx].position; + pxl8_vec3 pos = bsp->vertices[vert_idx].position; + + f32 u = 0.0f, v = 0.0f; + if (texinfo) { + u = (pxl8_vec3_dot(pos, texinfo->u_axis) + texinfo->u_offset) / tex_scale; + v = (pxl8_vec3_dot(pos, texinfo->v_axis) + texinfo->v_offset) / tex_scale; + } + + u8 light = 255; + if (bsp->vertex_lights && vert_idx < bsp->num_vertex_lights) { + light = (bsp->vertex_lights[vert_idx] >> 24) & 0xFF; + } + + pxl8_vertex vtx = { + .position = pos, + .normal = normal, + .u = u, + .v = v, + .color = 15, + .light = light, + }; + pxl8_mesh_push_vertex(mesh, vtx); + num_verts++; + } + + if (debug_this) { + pxl8_debug("face %u: num_edges=%u, collected %u verts, texinfo_id=%u", + face_id, face->num_edges, num_verts, face->texinfo_id); } if (num_verts < 3) return; - if (use_texture && face->texinfo_id < bsp->num_texinfo) { - const pxl8_bsp_texinfo* texinfo = &bsp->texinfo[face->texinfo_id]; - f32 tex_scale = 64.0f; - - for (u32 tri_idx = 1; tri_idx < num_verts - 1; tri_idx++) { - pxl8_vec3 v0 = verts[0]; - pxl8_vec3 v1 = verts[tri_idx]; - pxl8_vec3 v2 = verts[tri_idx + 1]; - - f32 u0 = (pxl8_vec3_dot(v0, texinfo->u_axis) + texinfo->u_offset) / tex_scale; - f32 v0_uv = (pxl8_vec3_dot(v0, texinfo->v_axis) + texinfo->v_offset) / tex_scale; - f32 u1 = (pxl8_vec3_dot(v1, texinfo->u_axis) + texinfo->u_offset) / tex_scale; - f32 v1_uv = (pxl8_vec3_dot(v1, texinfo->v_axis) + texinfo->v_offset) / tex_scale; - f32 u2 = (pxl8_vec3_dot(v2, texinfo->u_axis) + texinfo->u_offset) / tex_scale; - f32 v2_uv = (pxl8_vec3_dot(v2, texinfo->v_axis) + texinfo->v_offset) / tex_scale; - - pxl8_3d_draw_triangle_textured(gfx, v0, v1, v2, u0, v0_uv, u1, v1_uv, u2, v2_uv, texture_id); - } - } else{ - for (u32 i = 1; i < num_verts - 1; i++) { - pxl8_3d_draw_triangle_raw(gfx, verts[0], verts[i], verts[i + 1], color); - } + for (u32 i = 1; i < num_verts - 1; i++) { + pxl8_mesh_push_triangle(mesh, base_idx, base_idx + i, base_idx + i + 1); } } -void pxl8_bsp_render_textured( - pxl8_gfx* gfx, - const pxl8_bsp* bsp, - pxl8_vec3 camera_pos -) { - if (!gfx || !bsp || bsp->num_faces == 0) return; +void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 texture_id) { + if (!gfx || !bsp || face_id >= bsp->num_faces) return; + + pxl8_mesh* mesh = pxl8_mesh_create(64, 192); + if (!mesh) return; + + collect_face_to_mesh(bsp, face_id, mesh); + + if (mesh->index_count > 0) { + pxl8_material mat = pxl8_material_new(texture_id); + pxl8_3d_draw_mesh(gfx, mesh, pxl8_mat4_identity(), mat); + } + + pxl8_mesh_destroy(mesh); +} + +void pxl8_bsp_render_textured(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos) { + static int call_count = 0; + if (!gfx || !bsp || bsp->num_faces == 0) { + if (call_count++ < 5) { + pxl8_debug("bsp_render_textured: early return - gfx=%p, bsp=%p, num_faces=%u", + (void*)gfx, (void*)bsp, bsp ? bsp->num_faces : 0); + } + return; + } const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx); - if (!frustum) return; + if (!frustum) { + if (call_count++ < 5) { + pxl8_debug("bsp_render_textured: frustum is NULL!"); + } + return; + } i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos); @@ -455,11 +665,18 @@ void pxl8_bsp_render_textured( if (!new_buffer) return; rendered_faces = new_buffer; rendered_faces_capacity = bsp->num_faces; - pxl8_debug("Allocated face tracking buffer: %u bytes", bsp->num_faces); } memset(rendered_faces, 0, bsp->num_faces); + pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384); + if (!mesh) return; + + u32 current_texture = 0xFFFFFFFF; + + static int debug_cull = 0; + u32 faces_checked = 0, faces_culled = 0, faces_passed = 0; + for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) { if (camera_leaf >= 0 && !pxl8_bsp_is_leaf_visible(bsp, camera_leaf, leaf_id)) continue; @@ -475,39 +692,66 @@ void pxl8_bsp_render_textured( if (rendered_faces[face_id]) continue; rendered_faces[face_id] = 1; - if (!face_in_frustum(bsp, face_id, frustum)) continue; + faces_checked++; + if (!face_in_frustum(bsp, face_id, frustum)) { + faces_culled++; + continue; + } + faces_passed++; const pxl8_bsp_face* face = &bsp->faces[face_id]; u32 texture_id = 0; if (face->texinfo_id < bsp->num_texinfo) { texture_id = bsp->texinfo[face->texinfo_id].miptex; - } else { - static bool warned = false; - if (!warned) { - pxl8_warn("Face %u has invalid texinfo_id %u (num_texinfo=%u)", - face_id, face->texinfo_id, bsp->num_texinfo); - warned = true; - } } - pxl8_bsp_render_face(gfx, bsp, face_id, texture_id); + if (texture_id != current_texture && mesh->index_count > 0) { + pxl8_material mat = pxl8_material_with_double_sided(pxl8_material_new(current_texture)); + pxl8_3d_draw_mesh(gfx, mesh, pxl8_mat4_identity(), mat); + pxl8_mesh_clear(mesh); + } + + current_texture = texture_id; + u32 before = mesh->index_count; + collect_face_to_mesh(bsp, face_id, mesh); + if (debug_cull < 3 && mesh->index_count > before) { + pxl8_debug("Added face %u: mesh now has %u indices (was %u)", face_id, mesh->index_count, before); + } } } + static int draw_count = 0; + if (debug_cull < 3) { + pxl8_debug("Final mesh: %u indices, %u vertices", mesh->index_count, mesh->vertex_count); + } + if (mesh->index_count > 0) { + pxl8_material mat = pxl8_material_with_double_sided(pxl8_material_new(current_texture)); + pxl8_3d_draw_mesh(gfx, mesh, pxl8_mat4_identity(), mat); + if (draw_count++ < 5) { + pxl8_debug("bsp_render_textured: drew mesh with %u indices, camera_leaf=%d", + mesh->index_count, camera_leaf); + } + } else if (draw_count < 5) { + pxl8_debug("bsp_render_textured: mesh is empty, camera_leaf=%d, num_leafs=%u", + camera_leaf, bsp->num_leafs); + draw_count++; + } + + if (debug_cull++ < 5) { + pxl8_debug("bsp_render: checked=%u, culled=%u, passed=%u", faces_checked, faces_culled, faces_passed); + } + + pxl8_mesh_destroy(mesh); } -void pxl8_bsp_render_wireframe( - pxl8_gfx* gfx, - const pxl8_bsp* bsp, - pxl8_vec3 camera_pos, - u32 color -) { +void pxl8_bsp_render_wireframe(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos, u32 color) { if (!gfx || !bsp) return; const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx); if (!frustum) return; i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos); + u8 line_color = (u8)(color & 0xFF); for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) { if (camera_leaf >= 0 && !pxl8_bsp_is_leaf_visible(bsp, camera_leaf, leaf_id)) continue; @@ -527,19 +771,16 @@ void pxl8_bsp_render_wireframe( for (u32 e = 0; e < face->num_edges; e++) { i32 surfedge_idx = face->first_edge + e; - i32 next_surfedge_idx = face->first_edge + ((e + 1) % face->num_edges); - if (surfedge_idx >= (i32)bsp->num_surfedges || - next_surfedge_idx >= (i32)bsp->num_surfedges) continue; + if (surfedge_idx >= (i32)bsp->num_surfedges) continue; u32 v0_idx, v1_idx; - if (!pxl8_bsp_get_edge_vertices(bsp, surfedge_idx, &v0_idx, &v1_idx)) continue; pxl8_vec3 p0 = bsp->vertices[v0_idx].position; pxl8_vec3 p1 = bsp->vertices[v1_idx].position; - pxl8_3d_draw_line_3d(gfx, p0, p1, color); + pxl8_3d_draw_line(gfx, p0, p1, line_color); } } } diff --git a/src/pxl8_bsp.h b/client/src/world/pxl8_bsp.h similarity index 61% rename from src/pxl8_bsp.h rename to client/src/world/pxl8_bsp.h index 710f480..5a587d4 100644 --- a/src/pxl8_bsp.h +++ b/client/src/world/pxl8_bsp.h @@ -2,6 +2,7 @@ #include "pxl8_gfx.h" #include "pxl8_math.h" +#include "pxl8_mesh.h" #include "pxl8_types.h" typedef struct pxl8_bsp_edge { @@ -77,17 +78,45 @@ typedef struct pxl8_bsp_vertex { pxl8_vec3 position; } pxl8_bsp_vertex; +typedef struct pxl8_bsp_lightmap { + u8 color[3]; + u8 height; + u32 offset; + u8 width; +} pxl8_bsp_lightmap; + +typedef struct pxl8_bsp_lightmap_sample { + u8 b; + u8 g; + u8 r; +} pxl8_bsp_lightmap_sample; + +typedef struct pxl8_bsp_material_batch { + u16* face_indices; + u32 face_count; + u8 material_id; + pxl8_mesh* mesh; +} pxl8_bsp_material_batch; + +typedef struct pxl8_bsp_pvs { + u8* data; + u32 size; +} pxl8_bsp_pvs; + typedef struct pxl8_bsp { pxl8_bsp_edge* edges; pxl8_bsp_face* faces; pxl8_bsp_leaf* leafs; u8* lightdata; + pxl8_bsp_lightmap* lightmaps; u16* marksurfaces; + pxl8_bsp_material_batch* material_batches; pxl8_bsp_model* models; pxl8_bsp_node* nodes; pxl8_bsp_plane* planes; i32* surfedges; pxl8_bsp_texinfo* texinfo; + u32* vertex_lights; pxl8_bsp_vertex* vertices; u8* visdata; @@ -95,12 +124,15 @@ typedef struct pxl8_bsp { u32 num_edges; u32 num_faces; u32 num_leafs; + u32 num_lightmaps; u32 num_marksurfaces; + u32 num_material_batches; u32 num_models; u32 num_nodes; u32 num_planes; u32 num_surfedges; u32 num_texinfo; + u32 num_vertex_lights; u32 num_vertices; u32 visdata_size; } pxl8_bsp; @@ -115,25 +147,17 @@ void pxl8_bsp_destroy(pxl8_bsp* bsp); i32 pxl8_bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos); bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to); -void pxl8_bsp_render_face( - pxl8_gfx* gfx, - const pxl8_bsp* bsp, - u32 face_id, - u32 texture_id -); +pxl8_bsp_pvs pxl8_bsp_decompress_pvs(const pxl8_bsp* bsp, i32 leaf); +void pxl8_bsp_pvs_destroy(pxl8_bsp_pvs* pvs); +bool pxl8_bsp_pvs_is_visible(const pxl8_bsp_pvs* pvs, i32 leaf); -void pxl8_bsp_render_textured( - pxl8_gfx* gfx, - const pxl8_bsp* bsp, - pxl8_vec3 camera_pos -); +pxl8_bsp_lightmap pxl8_bsp_lightmap_uniform(u8 r, u8 g, u8 b); +pxl8_bsp_lightmap pxl8_bsp_lightmap_mapped(u8 width, u8 height, u32 offset); +pxl8_bsp_lightmap_sample pxl8_bsp_sample_lightmap(const pxl8_bsp* bsp, u32 face_idx, f32 u, f32 v); -void pxl8_bsp_render_wireframe( - pxl8_gfx* gfx, - const pxl8_bsp* bsp, - pxl8_vec3 camera_pos, - u32 color -); +void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 texture_id); +void pxl8_bsp_render_textured(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos); +void pxl8_bsp_render_wireframe(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos, u32 color); #ifdef __cplusplus } diff --git a/src/pxl8_gen.c b/client/src/world/pxl8_gen.c similarity index 100% rename from src/pxl8_gen.c rename to client/src/world/pxl8_gen.c diff --git a/src/pxl8_gen.h b/client/src/world/pxl8_gen.h similarity index 100% rename from src/pxl8_gen.h rename to client/src/world/pxl8_gen.h diff --git a/src/pxl8_world.c b/client/src/world/pxl8_world.c similarity index 96% rename from src/pxl8_world.c rename to client/src/world/pxl8_world.c index 36d3975..3f96b74 100644 --- a/src/pxl8_world.c +++ b/client/src/world/pxl8_world.c @@ -389,7 +389,20 @@ pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, } void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) { - if (!world || !gfx || !world->loaded) return; + if (!world || !gfx || !world->loaded) { + static int count = 0; + if (count++ < 10) { + pxl8_debug("world_render: early return - world=%p, gfx=%p, loaded=%d", + (void*)world, (void*)gfx, world ? world->loaded : -1); + } + return; + } + + static int render_count = 0; + if (render_count++ < 5) { + pxl8_debug("world_render: camera_pos=(%.1f, %.1f, %.1f), num_faces=%u", + camera_pos.x, camera_pos.y, camera_pos.z, world->bsp.num_faces); + } if (world->wireframe) { pxl8_bsp_render_wireframe(gfx, &world->bsp, camera_pos, world->wireframe_color); diff --git a/src/pxl8_world.h b/client/src/world/pxl8_world.h similarity index 100% rename from src/pxl8_world.h rename to client/src/world/pxl8_world.h diff --git a/demo/main.fnl b/demo/main.fnl index 47f9ea0..d078be3 100644 --- a/demo/main.fnl +++ b/demo/main.fnl @@ -6,9 +6,11 @@ (var time 0) (var active-demo :logo) (var particles nil) +(var particles2 nil) (var fire-init? false) (var rain-init? false) (var snow-init? false) +(var snow-init2? false) (var use-famicube-palette? false) (var logo-x 256) @@ -22,14 +24,15 @@ (fn switch-demo [new-demo] (when (and (not= active-demo new-demo) (not transition)) (set transition-pending new-demo) - (set transition (pxl8.transition_create :pixelate 0.5)) - (pxl8.transition_set_color transition 0xFF000000) - (pxl8.transition_start transition))) + (set transition (pxl8.create_transition :pixelate 0.5)) + (transition:set_color 0xFF000000) + (transition:start))) (global init (fn [] (pxl8.load_palette "res/sprites/pxl8_logo.ase") (set logo-sprite (pxl8.load_sprite "res/sprites/pxl8_logo.ase")) - (set particles (pxl8.particles_new 1000)) + (set particles (pxl8.create_particles 1000)) + (set particles2 (pxl8.create_particles 500)) (music.init) (music.start) (worldgen.init))) @@ -44,8 +47,8 @@ (set time (+ time dt)) (when transition - (pxl8.transition_update transition dt) - (when (pxl8.transition_is_complete transition) + (transition:update dt) + (when (transition:is_complete) (when transition-pending (when (and (= active-demo :worldgen) (not= transition-pending :worldgen)) (pxl8.set_relative_mouse_mode false)) @@ -55,8 +58,8 @@ (set transition-pending nil) (when (= active-demo :fire) (set fire-init? false)) (when (= active-demo :rain) (set rain-init? false)) - (when (= active-demo :snow) (set snow-init? false))) - (pxl8.transition_destroy transition) + (when (= active-demo :snow) (set snow-init? false) (set snow-init2? false))) + (transition:destroy) (set transition nil))) (when (pxl8.key_pressed "1") (switch-demo :logo)) @@ -93,7 +96,9 @@ (music.update dt) (when particles - (pxl8.particles_update particles dt))) + (particles:update dt)) + (when particles2 + (particles2:update dt))) (when (menu.is-paused) (menu.update)))) @@ -105,50 +110,75 @@ (when logo-sprite (pxl8.sprite logo-sprite logo-x logo-y 128 64 (< logo-dx 0) (< logo-dy 0)))) - :plasma (pxl8.vfx_plasma time 0.10 0.04 1) + :plasma (do + (pxl8.clear 0) + (pxl8.text "Plasma (TODO: Fennel impl)" 200 170 1)) - :tunnel (pxl8.vfx_tunnel time 2.0 0.25) + :tunnel (do + (pxl8.clear 0) + (pxl8.text "Tunnel (TODO: Fennel impl)" 200 170 1)) :raster (do (pxl8.clear 0) - (local bars [{:base_y 60 :amplitude 30 :height 16 :speed 2.0 :phase 0 :color 1 :fade_color 18} - {:base_y 180 :amplitude 35 :height 16 :speed 1.8 :phase 2.0 :color 1 :fade_color 27} - {:base_y 300 :amplitude 25 :height 16 :speed 2.2 :phase 4.0 :color 1 :fade_color 24}]) - (pxl8.vfx_raster_bars bars time)) + (pxl8.text "Raster Bars (TODO: Fennel impl)" 180 170 1)) :fire (do (pxl8.clear 0) (when particles (when (not fire-init?) - (pxl8.particles_clear particles) - (pxl8.vfx_fire particles 160 140 100 12) + (particles:clear) + (particles:set_position 320 360) + (particles:set_spread 320 0) + (particles:set_gravity 0 -80) + (particles:set_drag 0.98) + (particles:set_turbulence 60) + (particles:set_spawn_rate 200) + (particles:set_colors 1 9) + (particles:set_life 2.0 5.0) + (particles:set_velocity -30 30 -120 -60) (set fire-init? true)) - (pxl8.particles_render particles))) + (particles:render))) :rain (do (pxl8.clear 0) (when particles (when (not rain-init?) - (pxl8.particles_clear particles) - (pxl8.vfx_rain particles 320 10.0) + (particles:clear) + (particles:set_position 320 -10) + (particles:set_spread 340 0) + (particles:set_gravity 30 80) + (particles:set_drag 1.0) + (particles:set_turbulence 5) + (particles:set_spawn_rate 500) + (particles:set_colors 20 22) + (particles:set_life 2.0 3.0) + (particles:set_velocity -20 20 400 600) (set rain-init? true)) - (pxl8.particles_render particles))) + (particles:render))) :snow (do (pxl8.clear 0) (when particles (when (not snow-init?) - (pxl8.particles_clear particles) - (pxl8.vfx_snow particles 320 5.0) + (particles:clear) + (particles:set_position 320 -10) + (particles:set_spread 340 0) + (particles:set_gravity 0 25) + (particles:set_drag 1.0) + (particles:set_turbulence 100) + (particles:set_spawn_rate 100) + (particles:set_life 8.0 12.0) + (particles:set_colors 10 15) + (particles:set_velocity -50 50 30 50) (set snow-init? true)) - (pxl8.particles_render particles))) + (particles:render))) :worldgen (worldgen.frame) _ (pxl8.clear 0)) (when transition - (pxl8.transition_render transition)) + (transition:render)) (when (menu.is-paused) (menu.draw)))) diff --git a/demo/mod/menu.fnl b/demo/mod/menu.fnl index 09ea01d..34ee9e0 100644 --- a/demo/mod/menu.fnl +++ b/demo/mod/menu.fnl @@ -1,6 +1,10 @@ (local pxl8 (require :pxl8)) (var paused false) +(var gui nil) + +(fn init [] + (set gui (pxl8.create_gui))) (fn show [] (set paused true) @@ -12,36 +16,39 @@ (pxl8.set_relative_mouse_mode true)) (fn toggle [] + (when (not gui) (init)) (if paused (hide) (show))) (fn update [] - (let [(mx my) (pxl8.get_mouse_pos)] - (pxl8.gui_cursor_move mx my)) + (when gui + (let [(mx my) (pxl8.get_mouse_pos)] + (gui:cursor_move mx my)) - (when (pxl8.mouse_pressed 1) - (pxl8.gui_cursor_down)) + (when (pxl8.mouse_pressed 1) + (gui:cursor_down)) - (when (pxl8.mouse_released 1) - (pxl8.gui_cursor_up))) + (when (pxl8.mouse_released 1) + (gui:cursor_up)))) (fn draw [] - (pxl8.gui_begin_frame) + (when gui + (gui:begin_frame) - (pxl8.gui_window 200 100 240 140 "pxl8 demo") + (pxl8.gui_window 200 100 240 140 "pxl8 demo") - (when (pxl8.gui_button 1 215 145 210 32 "Resume") - (hide)) + (when (gui:button 1 215 145 210 32 "Resume") + (hide)) - (when (pxl8.gui_button 2 215 185 210 32 "Quit") - (pxl8.quit)) + (when (gui:button 2 215 185 210 32 "Quit") + (pxl8.quit)) - (if (pxl8.gui_is_hovering) - (pxl8.set_cursor :hand) - (pxl8.set_cursor :arrow)) + (if (gui:is_hovering) + (pxl8.set_cursor :hand) + (pxl8.set_cursor :arrow)) - (pxl8.gui_end_frame)) + (gui:end_frame))) {:is-paused (fn [] paused) :toggle toggle diff --git a/demo/mod/music.fnl b/demo/mod/music.fnl index 363788b..cdbfc6a 100644 --- a/demo/mod/music.fnl +++ b/demo/mod/music.fnl @@ -64,16 +64,16 @@ (local step-duration sixteenth) (fn init [] - (set ctx (pxl8.sfx_context_create)) + (set ctx (pxl8.create_sfx_context)) - (local reverb (pxl8.sfx_reverb_create + (local reverb (pxl8.create_reverb {:room 0.5 :damping 0.4 :mix 0.35})) - (local compressor (pxl8.sfx_compressor_create + (local compressor (pxl8.create_compressor {:threshold -18 :ratio 6 :attack 3 :release 100})) - (pxl8.sfx_context_append_node ctx reverb) - (pxl8.sfx_context_append_node ctx compressor) + (ctx:append_node reverb) + (ctx:append_node compressor) - (pxl8.sfx_mixer_attach ctx) + (ctx:attach) (set melody-params (pxl8.sfx_voice_params {:waveform pxl8.SFX_WAVE_TRIANGLE @@ -96,7 +96,7 @@ (fn stop [] (set playing false) - (pxl8.sfx_stop_all ctx)) + (ctx:stop_all)) (fn update [dt] (when playing @@ -109,7 +109,7 @@ (local melody-note (. melody-entry 1)) (local melody-dur (. melody-entry 2)) (when (> melody-note 0) - (pxl8.sfx_play_note ctx melody-note melody-params 0.45 melody-dur)) + (ctx:play_note melody-note melody-params 0.45 melody-dur)) (local bass-step (math.floor (/ step 2))) (local bass-idx (+ 1 (% bass-step (length bass)))) @@ -117,7 +117,7 @@ (local bass-note (. bass-entry 1)) (local bass-dur (. bass-entry 2)) (when (= (% step 2) 0) - (pxl8.sfx_play_note ctx bass-note bass-params 0.55 bass-dur)) + (ctx:play_note bass-note bass-params 0.55 bass-dur)) (set step (+ step 1))))) diff --git a/demo/mod/worldgen.fnl b/demo/mod/worldgen.fnl index 3be8784..88e1358 100644 --- a/demo/mod/worldgen.fnl +++ b/demo/mod/worldgen.fnl @@ -1,41 +1,41 @@ (local pxl8 (require :pxl8)) -(var world nil) +(local bob-amount 4.0) +(local bob-speed 8.0) +(local cam-smoothing 0.25) +(local cell-size 64) +(local cursor-sensitivity 0.008) +(local gravity -800) +(local grid-size 64) +(local ground-y 64) +(local jump-force 175) +(local land-recovery-speed 20) +(local land-squash-amount -4) +(local max-pitch 1.5) +(local move-speed 200) +(local turn-speed 2.0) (var auto-run? false) (var auto-run-cancel-key nil) +(var bob-time 0) (var cam-pitch 0) (var cam-x 1000) (var cam-y 64) (var cam-yaw 0) (var cam-z 1000) +(var camera nil) (var cursor-look? true) - +(var grounded? true) +(var land-squash 0) (var smooth-cam-x 1000) (var smooth-cam-z 1000) -(local cam-smoothing 0.25) - -(local bob-amount 4.0) -(local bob-speed 8.0) -(var bob-time 0) -(local cell-size 64) -(local cursor-sensitivity 0.008) -(local gravity -800) -(local grid-size 64) -(var grounded? true) -(local ground-y 64) -(local jump-force 175) -(local land-recovery-speed 20) -(local land-squash-amount -4) -(var land-squash 0) -(local max-pitch 1.5) -(local move-speed 200) -(local turn-speed 2.0) (var velocity-y 0) +(var world nil) (fn init [] - (set world (pxl8.world_new)) - (let [result (pxl8.world_generate world { + (set camera (pxl8.create_camera_3d)) + (set world (pxl8.create_world)) + (let [result (world:generate { :type pxl8.PROCGEN_ROOMS :width 64 :height 64 @@ -61,7 +61,7 @@ :height 64 :base_color 4})] - (let [result (pxl8.world_apply_textures world [ + (let [result (world:apply_textures [ {:name "floor" :texture_id floor-tex :rule (fn [normal] (> normal.y 0.7))} @@ -86,7 +86,7 @@ (when (and auto-run-cancel-key (not (pxl8.key_down auto-run-cancel-key))) (set auto-run-cancel-key nil)) - (when (pxl8.world_is_loaded world) + (when (world:is_loaded) (let [forward-x (- (math.sin cam-yaw)) forward-z (- (math.cos cam-yaw)) right-x (math.cos cam-yaw) @@ -124,7 +124,7 @@ (when (and (>= new-x 0) (<= new-x grid-max) (>= new-z 0) (<= new-z grid-max)) - (let [(resolved-x resolved-y resolved-z) (pxl8.world_resolve_collision world cam-x cam-y cam-z new-x cam-y new-z 5)] + (let [(resolved-x resolved-y resolved-z) (world:resolve_collision cam-x cam-y cam-z new-x cam-y new-z 5)] (set cam-x resolved-x) (set cam-z resolved-z))) @@ -176,31 +176,36 @@ (fn frame [] (pxl8.clear 0) - (when (pxl8.world_is_loaded world) + (when (not camera) + (pxl8.error "camera is nil!")) + + (when (not world) + (pxl8.error "world is nil!")) + + (when (and world (not (world:is_loaded))) + (pxl8.text "World not loaded yet..." 5 30 12)) + + (when (and camera world (world:is_loaded)) (let [bob-offset (* (math.sin bob-time) bob-amount) eye-y (+ cam-y bob-offset land-squash) forward-x (- (math.sin cam-yaw)) forward-z (- (math.cos cam-yaw)) target-x (+ smooth-cam-x forward-x) target-y (+ eye-y (math.sin cam-pitch)) - target-z (+ smooth-cam-z forward-z)] + target-z (+ smooth-cam-z forward-z) + aspect (/ (pxl8.get_width) (pxl8.get_height))] - (pxl8.clear_zbuffer) - (pxl8.set_backface_culling true) - (pxl8.set_wireframe false) + (camera:lookat [smooth-cam-x eye-y smooth-cam-z] + [target-x target-y target-z] + [0 1 0]) + (camera:set_perspective 1.047 aspect 1.0 4096.0) - (let [aspect (/ (pxl8.get_width) (pxl8.get_height)) - fov 1.047] - (pxl8.set_projection (pxl8.mat4_perspective fov aspect 1.0 4096.0))) + (pxl8.begin_frame_3d camera) + (pxl8.clear_depth) - (pxl8.set_view (pxl8.mat4_lookat - [smooth-cam-x eye-y smooth-cam-z] - [target-x target-y target-z] - [0 1 0])) + (world:render [smooth-cam-x eye-y smooth-cam-z]) - (pxl8.set_model (pxl8.mat4_identity)) - - (pxl8.world_render world [smooth-cam-x eye-y smooth-cam-z]) + (pxl8.end_frame_3d) (let [cx (/ (pxl8.get_width) 2) cy (/ (pxl8.get_height) 2) diff --git a/pxl8.sh b/pxl8.sh index 41a46b0..140dc7a 100755 --- a/pxl8.sh +++ b/pxl8.sh @@ -320,7 +320,7 @@ case "$COMMAND" in print_info "Compiler cache: ccache enabled" fi - INCLUDES="-Isrc -Ilib -Ilib/luajit/src -Ilib/linenoise -Ilib/miniz" + INCLUDES="-Iclient/src/core -Iclient/src/math -Iclient/src/gfx -Iclient/src/sfx -Iclient/src/script -Iclient/src/hal -Iclient/src/world -Iclient/src/asset -Iclient/src/game -Ilib -Ilib/luajit/src -Ilib/linenoise -Ilib/miniz" COMPILE_FLAGS="$CFLAGS $INCLUDES" DEP_COMPILE_FLAGS="$DEP_CFLAGS $INCLUDES" @@ -329,32 +329,38 @@ case "$COMMAND" in LIB_SOURCE_FILES="lib/linenoise/linenoise.c lib/miniz/miniz.c" PXL8_SOURCE_FILES=" - src/pxl8.c - src/pxl8_anim.c - src/pxl8_ase.c - src/pxl8_atlas.c - src/pxl8_blit.c - src/pxl8_bsp.c - src/pxl8_cart.c - src/pxl8_font.c - src/pxl8_gen.c - src/pxl8_gfx.c - src/pxl8_gui.c - src/pxl8_io.c - src/pxl8_log.c - src/pxl8_math.c - src/pxl8_repl.c - src/pxl8_replay.c - src/pxl8_rng.c - src/pxl8_save.c - src/pxl8_script.c - src/pxl8_sdl3.c - src/pxl8_sfx.c - src/pxl8_tilemap.c - src/pxl8_tilesheet.c - src/pxl8_transition.c - src/pxl8_vfx.c - src/pxl8_world.c + client/src/core/pxl8.c + client/src/core/pxl8_io.c + client/src/core/pxl8_log.c + client/src/core/pxl8_rng.c + client/src/math/pxl8_math.c + client/src/gfx/pxl8_anim.c + client/src/gfx/pxl8_atlas.c + client/src/gfx/pxl8_blit.c + client/src/gfx/pxl8_3d_camera.c + client/src/gfx/pxl8_colormap.c + client/src/gfx/pxl8_cpu.c + client/src/gfx/pxl8_dither.c + client/src/gfx/pxl8_font.c + client/src/gfx/pxl8_gfx.c + client/src/gfx/pxl8_mesh.c + client/src/gfx/pxl8_palette.c + client/src/gfx/pxl8_particles.c + client/src/gfx/pxl8_tilemap.c + client/src/gfx/pxl8_tilesheet.c + client/src/gfx/pxl8_transition.c + client/src/sfx/pxl8_sfx.c + client/src/script/pxl8_repl.c + client/src/script/pxl8_script.c + client/src/hal/pxl8_sdl3.c + client/src/world/pxl8_bsp.c + client/src/world/pxl8_gen.c + client/src/world/pxl8_world.c + client/src/asset/pxl8_ase.c + client/src/asset/pxl8_cart.c + client/src/asset/pxl8_save.c + client/src/game/pxl8_gui.c + client/src/game/pxl8_replay.c " LUAJIT_LIB="lib/luajit/src/libluajit.a" @@ -384,13 +390,13 @@ case "$COMMAND" in NEEDS_REBUILD=false if [[ "$src_file" -nt "$obj_file" ]] || \ - [[ "src/pxl8_types.h" -nt "$obj_file" ]] || \ - [[ "src/pxl8_macros.h" -nt "$obj_file" ]]; then + [[ "client/src/core/pxl8_types.h" -nt "$obj_file" ]] || \ + [[ "client/src/core/pxl8_macros.h" -nt "$obj_file" ]]; then NEEDS_REBUILD=true fi - if [[ "$src_file" == "src/pxl8_script.c" ]]; then - for lua_file in src/lua/*.lua src/lua/pxl8/*.lua lib/fennel/fennel.lua; do + if [[ "$src_file" == "client/src/script/pxl8_script.c" ]]; then + for lua_file in client/src/lua/*.lua client/src/lua/pxl8/*.lua lib/fennel/fennel.lua; do if [[ -f "$lua_file" ]] && [[ "$lua_file" -nt "$obj_file" ]]; then NEEDS_REBUILD=true break diff --git a/src/lua/pxl8.lua b/src/lua/pxl8.lua deleted file mode 100644 index 2ca1696..0000000 --- a/src/lua/pxl8.lua +++ /dev/null @@ -1,248 +0,0 @@ -local core = require("pxl8.core") -local gfx2d = require("pxl8.gfx2d") -local input = require("pxl8.input") -local vfx = require("pxl8.vfx") -local particles = require("pxl8.particles") -local tilemap = require("pxl8.tilemap") -local gfx3d = require("pxl8.gfx3d") -local math3d = require("pxl8.math") -local gui = require("pxl8.gui") -local world = require("pxl8.world") -local transition = require("pxl8.transition") -local anim = require("pxl8.anim") -local sfx = require("pxl8.sfx") -local pxl8 = {} - -core.init(pxl8_gfx, pxl8_input, pxl8_rng, pxl8_sfx, pxl8_sys) - -pxl8.get_fps = core.get_fps -pxl8.get_height = core.get_height -pxl8.get_title = core.get_title -pxl8.get_width = core.get_width -pxl8.info = core.info -pxl8.warn = core.warn -pxl8.error = core.error -pxl8.debug = core.debug -pxl8.trace = core.trace -pxl8.quit = core.quit - -pxl8.rng_seed = core.rng_seed -pxl8.rng_next = core.rng_next -pxl8.rng_f32 = core.rng_f32 -pxl8.rng_range = core.rng_range - -pxl8.clear = gfx2d.clear -pxl8.pixel = gfx2d.pixel -pxl8.line = gfx2d.line -pxl8.rect = gfx2d.rect -pxl8.rect_fill = gfx2d.rect_fill -pxl8.circle = gfx2d.circle -pxl8.circle_fill = gfx2d.circle_fill -pxl8.text = gfx2d.text -pxl8.sprite = gfx2d.sprite -pxl8.load_palette = gfx2d.load_palette -pxl8.load_sprite = gfx2d.load_sprite -pxl8.create_texture = gfx2d.create_texture -pxl8.gfx_color_ramp = gfx2d.color_ramp -pxl8.gfx_fade_palette = gfx2d.fade_palette -pxl8.gfx_cycle_palette = gfx2d.cycle_palette -pxl8.add_palette_cycle = gfx2d.add_palette_cycle -pxl8.remove_palette_cycle = gfx2d.remove_palette_cycle -pxl8.set_palette_cycle_speed = gfx2d.set_palette_cycle_speed -pxl8.clear_palette_cycles = gfx2d.clear_palette_cycles - -pxl8.key_down = input.key_down -pxl8.key_pressed = input.key_pressed -pxl8.key_released = input.key_released -pxl8.mouse_dx = input.mouse_dx -pxl8.mouse_dy = input.mouse_dy -pxl8.mouse_wheel_x = input.mouse_wheel_x -pxl8.mouse_wheel_y = input.mouse_wheel_y -pxl8.mouse_x = input.mouse_x -pxl8.mouse_y = input.mouse_y -pxl8.get_mouse_pos = input.get_mouse_pos -pxl8.mouse_pressed = input.mouse_pressed -pxl8.mouse_released = input.mouse_released -pxl8.center_cursor = input.center_cursor -pxl8.set_cursor = input.set_cursor -pxl8.set_relative_mouse_mode = input.set_relative_mouse_mode - -pxl8.vfx_raster_bars = vfx.raster_bars -pxl8.vfx_plasma = vfx.plasma -pxl8.vfx_rotozoom = vfx.rotozoom -pxl8.vfx_tunnel = vfx.tunnel -pxl8.vfx_explosion = vfx.explosion -pxl8.vfx_fire = vfx.fire -pxl8.vfx_rain = vfx.rain -pxl8.vfx_smoke = vfx.smoke -pxl8.vfx_snow = vfx.snow -pxl8.vfx_sparks = vfx.sparks -pxl8.vfx_starfield = vfx.starfield - -pxl8.particles_new = particles.new -pxl8.particles_destroy = particles.destroy -pxl8.particles_clear = particles.clear -pxl8.particles_emit = particles.emit -pxl8.particles_update = particles.update -pxl8.particles_render = particles.render - -pxl8.tilesheet_new = tilemap.tilesheet_new -pxl8.tilesheet_destroy = tilemap.tilesheet_destroy -pxl8.tilesheet_load = tilemap.tilesheet_load -pxl8.tilemap_new = tilemap.new -pxl8.tilemap_destroy = tilemap.destroy -pxl8.tilemap_set_tilesheet = tilemap.set_tilesheet -pxl8.tilemap_set_tile = tilemap.set_tile -pxl8.tilemap_get_tile_id = tilemap.get_tile_id -pxl8.tilemap_set_camera = tilemap.set_camera -pxl8.tilemap_render = tilemap.render -pxl8.tilemap_render_layer = tilemap.render_layer -pxl8.tilemap_is_solid = tilemap.is_solid -pxl8.tilemap_check_collision = tilemap.check_collision -pxl8.tilemap_get_tile_data = tilemap.get_tile_data -pxl8.tilemap_load_ase = tilemap.load_ase -pxl8.tilemap_set_tile_data = tilemap.set_tile_data -pxl8.TILE_FLIP_X = tilemap.TILE_FLIP_X -pxl8.TILE_FLIP_Y = tilemap.TILE_FLIP_Y -pxl8.TILE_SOLID = tilemap.TILE_SOLID -pxl8.TILE_TRIGGER = tilemap.TILE_TRIGGER - -pxl8.clear_zbuffer = gfx3d.clear_zbuffer -pxl8.set_model = gfx3d.set_model -pxl8.set_view = gfx3d.set_view -pxl8.set_projection = gfx3d.set_projection -pxl8.set_wireframe = gfx3d.set_wireframe -pxl8.set_affine_textures = gfx3d.set_affine_textures -pxl8.set_backface_culling = gfx3d.set_backface_culling -pxl8.draw_triangle_3d = gfx3d.draw_triangle -pxl8.draw_triangle_3d_textured = gfx3d.draw_triangle_textured -pxl8.draw_line_3d = gfx3d.draw_line - -pxl8.mat4_identity = math3d.mat4_identity -pxl8.mat4_multiply = math3d.mat4_multiply -pxl8.mat4_translate = math3d.mat4_translate -pxl8.mat4_rotate_x = math3d.mat4_rotate_x -pxl8.mat4_rotate_y = math3d.mat4_rotate_y -pxl8.mat4_rotate_z = math3d.mat4_rotate_z -pxl8.mat4_scale = math3d.mat4_scale -pxl8.mat4_ortho = math3d.mat4_ortho -pxl8.mat4_perspective = math3d.mat4_perspective -pxl8.mat4_lookat = math3d.mat4_lookat -pxl8.bounds = math3d.bounds - -pxl8.gui_state_create = gui.state_create -pxl8.gui_state_destroy = gui.state_destroy -pxl8.gui_begin_frame = gui.begin_frame -pxl8.gui_end_frame = gui.end_frame -pxl8.gui_cursor_move = gui.cursor_move -pxl8.gui_cursor_down = gui.cursor_down -pxl8.gui_cursor_up = gui.cursor_up -pxl8.gui_button = gui.button -pxl8.gui_window = gui.window -pxl8.gui_label = gui.label -pxl8.gui_is_hovering = gui.is_hovering -pxl8.gui_get_cursor_pos = gui.get_cursor_pos - -pxl8.world_new = world.new -pxl8.world_destroy = world.destroy -pxl8.world_load = world.load -pxl8.world_render = world.render -pxl8.world_unload = world.unload -pxl8.world_is_loaded = world.is_loaded -pxl8.world_generate = world.generate -pxl8.world_apply_textures = world.apply_textures -pxl8.world_check_collision = world.check_collision -pxl8.world_resolve_collision = world.resolve_collision -pxl8.procgen_tex = world.procgen_tex -pxl8.PROCGEN_ROOMS = world.PROCGEN_ROOMS -pxl8.PROCGEN_TERRAIN = world.PROCGEN_TERRAIN - -pxl8.transition_create = transition.create -pxl8.transition_destroy = transition.destroy -pxl8.transition_get_progress = transition.get_progress -pxl8.transition_is_active = transition.is_active -pxl8.transition_is_complete = transition.is_complete -pxl8.transition_render = transition.render -pxl8.transition_reset = transition.reset -pxl8.transition_set_color = transition.set_color -pxl8.transition_set_reverse = transition.set_reverse -pxl8.transition_start = transition.start -pxl8.transition_stop = transition.stop -pxl8.transition_update = transition.update - -pxl8.anim_create = anim.create -pxl8.anim_create_from_ase = anim.create_from_ase -pxl8.anim_destroy = anim.destroy -pxl8.anim_add_state = anim.add_state -pxl8.anim_get_current_frame = anim.get_current_frame -pxl8.anim_get_current_frame_id = anim.get_current_frame_id -pxl8.anim_get_state = anim.get_state -pxl8.anim_has_state_machine = anim.has_state_machine -pxl8.anim_is_complete = anim.is_complete -pxl8.anim_is_playing = anim.is_playing -pxl8.anim_pause = anim.pause -pxl8.anim_play = anim.play -pxl8.anim_render_sprite = anim.render_sprite -pxl8.anim_reset = anim.reset -pxl8.anim_set_frame = anim.set_frame -pxl8.anim_set_loop = anim.set_loop -pxl8.anim_set_reverse = anim.set_reverse -pxl8.anim_set_speed = anim.set_speed -pxl8.anim_set_state = anim.set_state -pxl8.anim_stop = anim.stop -pxl8.anim_update = anim.update - -pxl8.sfx_compressor_create = sfx.compressor_create -pxl8.sfx_compressor_set_attack = sfx.compressor_set_attack -pxl8.sfx_compressor_set_ratio = sfx.compressor_set_ratio -pxl8.sfx_compressor_set_release = sfx.compressor_set_release -pxl8.sfx_compressor_set_threshold = sfx.compressor_set_threshold -pxl8.sfx_context_append_node = sfx.context_append_node -pxl8.sfx_context_create = sfx.context_create -pxl8.sfx_context_destroy = sfx.context_destroy -pxl8.sfx_context_get_head = sfx.context_get_head -pxl8.sfx_context_get_volume = sfx.context_get_volume -pxl8.sfx_context_insert_node = sfx.context_insert_node -pxl8.sfx_context_remove_node = sfx.context_remove_node -pxl8.sfx_context_set_volume = sfx.context_set_volume -pxl8.sfx_delay_create = sfx.delay_create -pxl8.sfx_delay_set_feedback = sfx.delay_set_feedback -pxl8.sfx_delay_set_mix = sfx.delay_set_mix -pxl8.sfx_delay_set_time = sfx.delay_set_time -pxl8.sfx_get_master_volume = sfx.get_master_volume -pxl8.sfx_mixer_attach = sfx.mixer_attach -pxl8.sfx_mixer_detach = sfx.mixer_detach -pxl8.sfx_node_destroy = sfx.node_destroy -pxl8.sfx_note_to_freq = sfx.note_to_freq -pxl8.sfx_play_note = sfx.play_note -pxl8.sfx_release_voice = sfx.release_voice -pxl8.sfx_reverb_create = sfx.reverb_create -pxl8.sfx_reverb_set_damping = sfx.reverb_set_damping -pxl8.sfx_reverb_set_mix = sfx.reverb_set_mix -pxl8.sfx_reverb_set_room = sfx.reverb_set_room -pxl8.sfx_set_master_volume = sfx.set_master_volume -pxl8.sfx_stop_all = sfx.stop_all -pxl8.sfx_stop_voice = sfx.stop_voice -pxl8.sfx_voice_params = sfx.voice_params - -pxl8.SFX_FILTER_BANDPASS = sfx.FILTER_BANDPASS -pxl8.SFX_FILTER_HIGHPASS = sfx.FILTER_HIGHPASS -pxl8.SFX_FILTER_LOWPASS = sfx.FILTER_LOWPASS -pxl8.SFX_FILTER_NONE = sfx.FILTER_NONE - -pxl8.SFX_LFO_AMPLITUDE = sfx.LFO_AMPLITUDE -pxl8.SFX_LFO_FILTER = sfx.LFO_FILTER -pxl8.SFX_LFO_PITCH = sfx.LFO_PITCH - -pxl8.SFX_NODE_COMPRESSOR = sfx.NODE_COMPRESSOR -pxl8.SFX_NODE_DELAY = sfx.NODE_DELAY -pxl8.SFX_NODE_REVERB = sfx.NODE_REVERB - -pxl8.SFX_WAVE_NOISE = sfx.WAVE_NOISE -pxl8.SFX_WAVE_PULSE = sfx.WAVE_PULSE -pxl8.SFX_WAVE_SAW = sfx.WAVE_SAW -pxl8.SFX_WAVE_SINE = sfx.WAVE_SINE -pxl8.SFX_WAVE_SQUARE = sfx.WAVE_SQUARE -pxl8.SFX_WAVE_TRIANGLE = sfx.WAVE_TRIANGLE - -return pxl8 diff --git a/src/lua/pxl8/anim.lua b/src/lua/pxl8/anim.lua deleted file mode 100644 index 2d349cc..0000000 --- a/src/lua/pxl8/anim.lua +++ /dev/null @@ -1,110 +0,0 @@ -local ffi = require("ffi") -local C = ffi.C -local core = require("pxl8.core") - -local anim = {} - -function anim.create(frame_ids, frame_durations) - local frame_count = #frame_ids - local c_frame_ids = ffi.new("u32[?]", frame_count) - local c_frame_durations = nil - - for i = 1, frame_count do - c_frame_ids[i-1] = frame_ids[i] - end - - if frame_durations then - c_frame_durations = ffi.new("u16[?]", frame_count) - for i = 1, frame_count do - c_frame_durations[i-1] = frame_durations[i] - end - end - - return C.pxl8_anim_create(c_frame_ids, c_frame_durations, frame_count) -end - -function anim.create_from_ase(filepath) - return C.pxl8_anim_create_from_ase(core.gfx, filepath) -end - -function anim.destroy(a) - C.pxl8_anim_destroy(a) -end - -function anim.add_state(a, name, state_anim) - return C.pxl8_anim_add_state(a, name, state_anim) -end - -function anim.get_current_frame(a) - return C.pxl8_anim_get_current_frame(a) -end - -function anim.get_current_frame_id(a) - return C.pxl8_anim_get_current_frame_id(a) -end - -function anim.get_state(a) - local state_name = C.pxl8_anim_get_state(a) - if state_name ~= nil then - return ffi.string(state_name) - end - return nil -end - -function anim.has_state_machine(a) - return C.pxl8_anim_has_state_machine(a) -end - -function anim.is_complete(a) - return C.pxl8_anim_is_complete(a) -end - -function anim.is_playing(a) - return C.pxl8_anim_is_playing(a) -end - -function anim.pause(a) - C.pxl8_anim_pause(a) -end - -function anim.play(a) - C.pxl8_anim_play(a) -end - -function anim.render_sprite(a, x, y, w, h, flip_x, flip_y) - C.pxl8_anim_render_sprite(a, core.gfx, x, y, w, h, flip_x or false, flip_y or false) -end - -function anim.reset(a) - C.pxl8_anim_reset(a) -end - -function anim.set_frame(a, frame) - C.pxl8_anim_set_frame(a, frame) -end - -function anim.set_loop(a, loop) - C.pxl8_anim_set_loop(a, loop) -end - -function anim.set_reverse(a, reverse) - C.pxl8_anim_set_reverse(a, reverse) -end - -function anim.set_speed(a, speed) - C.pxl8_anim_set_speed(a, speed) -end - -function anim.set_state(a, name) - return C.pxl8_anim_set_state(a, name) -end - -function anim.stop(a) - C.pxl8_anim_stop(a) -end - -function anim.update(a, dt) - C.pxl8_anim_update(a, dt) -end - -return anim diff --git a/src/lua/pxl8/gfx3d.lua b/src/lua/pxl8/gfx3d.lua deleted file mode 100644 index 9e63a82..0000000 --- a/src/lua/pxl8/gfx3d.lua +++ /dev/null @@ -1,56 +0,0 @@ -local ffi = require("ffi") -local C = ffi.C -local core = require("pxl8.core") - -local gfx3d = {} - -function gfx3d.clear_zbuffer() - C.pxl8_3d_clear_zbuffer(core.gfx) -end - -function gfx3d.set_model(mat) - C.pxl8_3d_set_model(core.gfx, mat) -end - -function gfx3d.set_view(mat) - C.pxl8_3d_set_view(core.gfx, mat) -end - -function gfx3d.set_projection(mat) - C.pxl8_3d_set_projection(core.gfx, mat) -end - -function gfx3d.set_wireframe(wireframe) - C.pxl8_3d_set_wireframe(core.gfx, wireframe) -end - -function gfx3d.set_affine_textures(affine) - C.pxl8_3d_set_affine_textures(core.gfx, affine) -end - -function gfx3d.set_backface_culling(culling) - C.pxl8_3d_set_backface_culling(core.gfx, culling) -end - -function gfx3d.draw_triangle(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(core.gfx, vec0, vec1, vec2, color) -end - -function gfx3d.draw_triangle_textured(v0, v1, v2, uv0, uv1, uv2, texture_id) - local vec0 = ffi.new("pxl8_vec3", {x = v0[1], y = v0[2], z = v0[3]}) - local vec1 = ffi.new("pxl8_vec3", {x = v1[1], y = v1[2], z = v1[3]}) - local vec2 = ffi.new("pxl8_vec3", {x = v2[1], y = v2[2], z = v2[3]}) - C.pxl8_3d_draw_triangle_textured(core.gfx, vec0, vec1, vec2, - uv0[1], uv0[2], uv1[1], uv1[2], uv2[1], uv2[2], texture_id) -end - -function gfx3d.draw_line(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(core.gfx, vec0, vec1, color) -end - -return gfx3d diff --git a/src/lua/pxl8/gui.lua b/src/lua/pxl8/gui.lua deleted file mode 100644 index 5e1dcb9..0000000 --- a/src/lua/pxl8/gui.lua +++ /dev/null @@ -1,59 +0,0 @@ -local ffi = require("ffi") -local C = ffi.C -local core = require("pxl8.core") - -local gui = {} - -local state = nil - -local function gui_state() - if not state then - state = ffi.gc(C.pxl8_gui_state_create(), C.pxl8_gui_state_destroy) - end - return state -end - -function gui.begin_frame() - C.pxl8_gui_begin_frame(gui_state()) -end - -function gui.end_frame() - C.pxl8_gui_end_frame(gui_state()) -end - -function gui.cursor_move(x, y) - C.pxl8_gui_cursor_move(gui_state(), x, y) -end - -function gui.cursor_down() - C.pxl8_gui_cursor_down(gui_state()) -end - -function gui.cursor_up() - C.pxl8_gui_cursor_up(gui_state()) -end - -function gui.button(id, x, y, w, h, label) - return C.pxl8_gui_button(gui_state(), core.gfx, id, x, y, w, h, label) -end - -function gui.window(x, y, w, h, title) - C.pxl8_gui_window(core.gfx, x, y, w, h, title) -end - -function gui.label(x, y, text, color) - C.pxl8_gui_label(core.gfx, x, y, text, color) -end - -function gui.is_hovering() - return C.pxl8_gui_is_hovering(gui_state()) -end - -function gui.get_cursor_pos() - local x = ffi.new("i32[1]") - local y = ffi.new("i32[1]") - C.pxl8_gui_get_cursor_pos(gui_state(), x, y) - return x[0], y[0] -end - -return gui diff --git a/src/lua/pxl8/particles.lua b/src/lua/pxl8/particles.lua deleted file mode 100644 index 4dc2e91..0000000 --- a/src/lua/pxl8/particles.lua +++ /dev/null @@ -1,31 +0,0 @@ -local ffi = require("ffi") -local C = ffi.C -local core = require("pxl8.core") - -local particles = {} - -function particles.new(max_count) - return C.pxl8_particles_create(max_count or 1000, core.rng) -end - -function particles.destroy(ps) - C.pxl8_particles_destroy(ps) -end - -function particles.clear(ps) - C.pxl8_particles_clear(ps) -end - -function particles.emit(ps, count) - C.pxl8_particles_emit(ps, count or 1) -end - -function particles.update(ps, dt) - C.pxl8_particles_update(ps, dt) -end - -function particles.render(ps) - C.pxl8_particles_render(ps, core.gfx) -end - -return particles diff --git a/src/lua/pxl8/sfx.lua b/src/lua/pxl8/sfx.lua deleted file mode 100644 index 563bc49..0000000 --- a/src/lua/pxl8/sfx.lua +++ /dev/null @@ -1,198 +0,0 @@ -local ffi = require("ffi") -local C = ffi.C -local core = require("pxl8.core") - -local sfx = {} - -sfx.FILTER_BANDPASS = C.PXL8_SFX_FILTER_BANDPASS -sfx.FILTER_HIGHPASS = C.PXL8_SFX_FILTER_HIGHPASS -sfx.FILTER_LOWPASS = C.PXL8_SFX_FILTER_LOWPASS -sfx.FILTER_NONE = C.PXL8_SFX_FILTER_NONE - -sfx.LFO_AMPLITUDE = C.PXL8_SFX_LFO_AMPLITUDE -sfx.LFO_FILTER = C.PXL8_SFX_LFO_FILTER -sfx.LFO_PITCH = C.PXL8_SFX_LFO_PITCH - -sfx.NODE_COMPRESSOR = C.PXL8_SFX_NODE_COMPRESSOR -sfx.NODE_DELAY = C.PXL8_SFX_NODE_DELAY -sfx.NODE_REVERB = C.PXL8_SFX_NODE_REVERB - -sfx.WAVE_NOISE = C.PXL8_SFX_WAVE_NOISE -sfx.WAVE_PULSE = C.PXL8_SFX_WAVE_PULSE -sfx.WAVE_SAW = C.PXL8_SFX_WAVE_SAW -sfx.WAVE_SINE = C.PXL8_SFX_WAVE_SINE -sfx.WAVE_SQUARE = C.PXL8_SFX_WAVE_SQUARE -sfx.WAVE_TRIANGLE = C.PXL8_SFX_WAVE_TRIANGLE - -function sfx.compressor_create(opts) - opts = opts or {} - local cfg = ffi.new("pxl8_sfx_compressor_config") - cfg.attack = opts.attack or 10 - cfg.ratio = opts.ratio or 4 - cfg.release = opts.release or 100 - cfg.threshold = opts.threshold or -12 - return C.pxl8_sfx_compressor_create(cfg) -end - -function sfx.compressor_set_attack(node, attack) - C.pxl8_sfx_compressor_set_attack(node, attack) -end - -function sfx.compressor_set_ratio(node, ratio) - C.pxl8_sfx_compressor_set_ratio(node, ratio) -end - -function sfx.compressor_set_release(node, release) - C.pxl8_sfx_compressor_set_release(node, release) -end - -function sfx.compressor_set_threshold(node, threshold) - C.pxl8_sfx_compressor_set_threshold(node, threshold) -end - -function sfx.context_append_node(ctx, node) - C.pxl8_sfx_context_append_node(ctx, node) -end - -function sfx.context_create() - return C.pxl8_sfx_context_create() -end - -function sfx.context_destroy(ctx) - C.pxl8_sfx_context_destroy(ctx) -end - -function sfx.context_get_head(ctx) - return C.pxl8_sfx_context_get_head(ctx) -end - -function sfx.context_get_volume(ctx) - return C.pxl8_sfx_context_get_volume(ctx) -end - -function sfx.context_insert_node(ctx, after, node) - C.pxl8_sfx_context_insert_node(ctx, after, node) -end - -function sfx.context_remove_node(ctx, node) - C.pxl8_sfx_context_remove_node(ctx, node) -end - -function sfx.context_set_volume(ctx, volume) - C.pxl8_sfx_context_set_volume(ctx, volume) -end - -function sfx.delay_create(opts) - opts = opts or {} - local cfg = ffi.new("pxl8_sfx_delay_config") - cfg.feedback = opts.feedback or 0.4 - cfg.mix = opts.mix or 0.25 - cfg.time_l = opts.time_l or 350 - cfg.time_r = opts.time_r or 500 - return C.pxl8_sfx_delay_create(cfg) -end - -function sfx.delay_set_feedback(node, feedback) - C.pxl8_sfx_delay_set_feedback(node, feedback) -end - -function sfx.delay_set_mix(node, mix) - C.pxl8_sfx_delay_set_mix(node, mix) -end - -function sfx.delay_set_time(node, time_l, time_r) - C.pxl8_sfx_delay_set_time(node, time_l, time_r) -end - -function sfx.get_master_volume() - return C.pxl8_sfx_mixer_get_master_volume(core.sfx) -end - -function sfx.mixer_attach(ctx) - C.pxl8_sfx_mixer_attach(core.sfx, ctx) -end - -function sfx.mixer_detach(ctx) - C.pxl8_sfx_mixer_detach(core.sfx, ctx) -end - -function sfx.node_destroy(node) - C.pxl8_sfx_node_destroy(node) -end - -function sfx.note_to_freq(note) - return C.pxl8_sfx_note_to_freq(note) -end - -function sfx.play_note(ctx, note, params, volume, duration) - return C.pxl8_sfx_play_note(ctx, note, params, volume or 0.8, duration or 0) -end - -function sfx.release_voice(ctx, voice_id) - C.pxl8_sfx_release_voice(ctx, voice_id) -end - -function sfx.reverb_create(opts) - opts = opts or {} - local cfg = ffi.new("pxl8_sfx_reverb_config") - cfg.damping = opts.damping or 0.5 - cfg.mix = opts.mix or 0.3 - cfg.room = opts.room or 0.5 - return C.pxl8_sfx_reverb_create(cfg) -end - -function sfx.reverb_set_damping(node, damping) - C.pxl8_sfx_reverb_set_damping(node, damping) -end - -function sfx.reverb_set_mix(node, mix) - C.pxl8_sfx_reverb_set_mix(node, mix) -end - -function sfx.reverb_set_room(node, room) - C.pxl8_sfx_reverb_set_room(node, room) -end - -function sfx.set_master_volume(volume) - C.pxl8_sfx_mixer_set_master_volume(core.sfx, volume) -end - -function sfx.stop_all(ctx) - C.pxl8_sfx_stop_all(ctx) -end - -function sfx.stop_voice(ctx, voice_id) - C.pxl8_sfx_stop_voice(ctx, voice_id) -end - -function sfx.voice_params(opts) - opts = opts or {} - local params = ffi.new("pxl8_sfx_voice_params") - - params.amp_env.attack = opts.attack or 0.01 - params.amp_env.decay = opts.decay or 0.1 - params.amp_env.sustain = opts.sustain or 0.5 - params.amp_env.release = opts.release or 0.2 - - params.filter_env.attack = opts.filter_attack or 0.01 - params.filter_env.decay = opts.filter_decay or 0.1 - params.filter_env.sustain = opts.filter_sustain or 0.3 - params.filter_env.release = opts.filter_release or 0.1 - - params.filter_type = opts.filter_type or C.PXL8_SFX_FILTER_NONE - params.lfo_target = opts.lfo_target or C.PXL8_SFX_LFO_PITCH - params.lfo_waveform = opts.lfo_waveform or C.PXL8_SFX_WAVE_SINE - params.waveform = opts.waveform or C.PXL8_SFX_WAVE_SINE - - params.filter_cutoff = opts.filter_cutoff or 4000 - params.filter_env_depth = opts.filter_env_depth or 0 - params.filter_resonance = opts.filter_resonance or 0 - params.fx_send = opts.fx_send or 0 - params.lfo_depth = opts.lfo_depth or 0 - params.lfo_rate = opts.lfo_rate or 0 - params.pulse_width = opts.pulse_width or 0.5 - - return params -end - -return sfx diff --git a/src/lua/pxl8/tilemap.lua b/src/lua/pxl8/tilemap.lua deleted file mode 100644 index 5df37b9..0000000 --- a/src/lua/pxl8/tilemap.lua +++ /dev/null @@ -1,82 +0,0 @@ -local ffi = require("ffi") -local C = ffi.C -local core = require("pxl8.core") - -local tilemap = {} - -tilemap.TILE_FLIP_X = 1 -tilemap.TILE_FLIP_Y = 2 -tilemap.TILE_SOLID = 4 -tilemap.TILE_TRIGGER = 8 - -local tile_data = setmetatable({}, {__mode = "k"}) - -function tilemap.tilesheet_new(tile_size) - return C.pxl8_tilesheet_create(tile_size or 16) -end - -function tilemap.tilesheet_destroy(tilesheet) - C.pxl8_tilesheet_destroy(tilesheet) -end - -function tilemap.tilesheet_load(tilesheet, filepath) - return C.pxl8_tilesheet_load(tilesheet, filepath, core.gfx) -end - -function tilemap.new(width, height, tile_size) - return C.pxl8_tilemap_create(width, height, tile_size or 16) -end - -function tilemap.destroy(tm) - C.pxl8_tilemap_destroy(tm) -end - -function tilemap.set_tilesheet(tm, tilesheet) - return C.pxl8_tilemap_set_tilesheet(tm, tilesheet) -end - -function tilemap.set_tile(tm, layer, x, y, tile_id, flags) - C.pxl8_tilemap_set_tile(tm, layer or 0, x, y, tile_id or 0, flags or 0) -end - -function tilemap.get_tile_id(tm, layer, x, y) - return C.pxl8_tilemap_get_tile_id(tm, layer or 0, x, y) -end - -function tilemap.set_camera(tm, x, y) - C.pxl8_tilemap_set_camera(tm, x, y) -end - -function tilemap.render(tm) - C.pxl8_tilemap_render(tm, core.gfx) -end - -function tilemap.render_layer(tm, layer) - C.pxl8_tilemap_render_layer(tm, core.gfx, layer) -end - -function tilemap.is_solid(tm, x, y) - return C.pxl8_tilemap_is_solid(tm, x, y) -end - -function tilemap.check_collision(tm, x, y, w, h) - return C.pxl8_tilemap_check_collision(tm, x, y, w, h) -end - -function tilemap.get_tile_data(tm, tile_id) - if not tm or tile_id == 0 then return nil end - if not tile_data[tm] then return nil end - return tile_data[tm][tile_id] -end - -function tilemap.load_ase(tm, filepath, layer) - return C.pxl8_tilemap_load_ase(tm, filepath, layer or 0) -end - -function tilemap.set_tile_data(tm, tile_id, data) - if not tm or tile_id == 0 then return end - if not tile_data[tm] then tile_data[tm] = {} end - tile_data[tm][tile_id] = data -end - -return tilemap diff --git a/src/lua/pxl8/transition.lua b/src/lua/pxl8/transition.lua deleted file mode 100644 index 4db8399..0000000 --- a/src/lua/pxl8/transition.lua +++ /dev/null @@ -1,68 +0,0 @@ -local ffi = require("ffi") -local C = ffi.C -local core = require("pxl8.core") - -local transition = {} - -local transition_types = { - fade = 0, - wipe_left = 1, - wipe_right = 2, - wipe_up = 3, - wipe_down = 4, - circle_open = 5, - circle_close = 6, - dissolve = 7, - pixelate = 8 -} - -function transition.create(type_name, duration) - local type_id = transition_types[type_name] or 0 - return C.pxl8_transition_create(type_id, duration or 1.0) -end - -function transition.destroy(t) - C.pxl8_transition_destroy(t) -end - -function transition.get_progress(t) - return C.pxl8_transition_get_progress(t) -end - -function transition.is_active(t) - return C.pxl8_transition_is_active(t) -end - -function transition.is_complete(t) - return C.pxl8_transition_is_complete(t) -end - -function transition.render(t) - C.pxl8_transition_render(t, core.gfx) -end - -function transition.reset(t) - C.pxl8_transition_reset(t) -end - -function transition.set_color(t, color) - C.pxl8_transition_set_color(t, color) -end - -function transition.set_reverse(t, reverse) - C.pxl8_transition_set_reverse(t, reverse) -end - -function transition.start(t) - C.pxl8_transition_start(t) -end - -function transition.stop(t) - C.pxl8_transition_stop(t) -end - -function transition.update(t, dt) - C.pxl8_transition_update(t, dt) -end - -return transition diff --git a/src/pxl8_gfx.c b/src/pxl8_gfx.c deleted file mode 100644 index 7e66657..0000000 --- a/src/pxl8_gfx.c +++ /dev/null @@ -1,1567 +0,0 @@ -#include "pxl8_gfx.h" - -#include -#include - -#include "pxl8_ase.h" -#include "pxl8_atlas.h" -#include "pxl8_blit.h" -#include "pxl8_color.h" -#include "pxl8_font.h" -#include "pxl8_hal.h" -#include "pxl8_log.h" -#include "pxl8_macros.h" -#include "pxl8_math.h" -#include "pxl8_sys.h" -#include "pxl8_types.h" - -typedef struct pxl8_palette_cycle { - bool active; - u8 end_index; - f32 speed; - u8 start_index; - f32 timer; -} pxl8_palette_cycle; - -typedef struct pxl8_effects { - pxl8_palette_cycle palette_cycles[8]; - f32 time; -} pxl8_effects; - -typedef struct pxl8_sprite_cache_entry { - char path[256]; - u32 sprite_id; - bool active; -} pxl8_sprite_cache_entry; - -struct pxl8_gfx { - const pxl8_hal* hal; - void* platform_data; - - pxl8_atlas* atlas; - pxl8_sprite_cache_entry* sprite_cache; - u32 sprite_cache_capacity; - u32 sprite_cache_count; - - pxl8_pixel_mode pixel_mode; - u8* framebuffer; - i32 framebuffer_height; - i32 framebuffer_width; - bool initialized; - - u32* palette; - u32 palette_size; - - pxl8_viewport viewport; - - pxl8_effects effects; - - bool backface_culling; - pxl8_mat4 model; - pxl8_mat4 projection; - pxl8_mat4 view; - pxl8_mat4 mvp; - bool mvp_dirty; - pxl8_frustum frustum; - bool wireframe; - f32* zbuffer; - i32 zbuffer_height; - i32 zbuffer_width; - - bool affine_textures; - -}; - -pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) { - pxl8_bounds bounds = {0}; - if (!gfx) { - return bounds; - } - bounds.w = gfx->framebuffer_width; - bounds.h = gfx->framebuffer_height; - return bounds; -} - -pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx) { - return gfx ? gfx->pixel_mode : PXL8_PIXEL_INDEXED; -} - -u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx) { - if (!gfx || gfx->pixel_mode == PXL8_PIXEL_HICOLOR) return NULL; - return gfx->framebuffer; -} - -u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx) { - if (!gfx || gfx->pixel_mode != PXL8_PIXEL_HICOLOR) return NULL; - return (u16*)gfx->framebuffer; -} - -i32 pxl8_gfx_get_height(const pxl8_gfx* gfx) { - return gfx ? gfx->framebuffer_height : 0; -} - -i32 pxl8_gfx_get_width(const pxl8_gfx* gfx) { - return gfx ? gfx->framebuffer_width : 0; -} - -u32 pxl8_gfx_get_palette_size(const pxl8_gfx* gfx) { - return gfx ? gfx->palette_size : 0; -} - -pxl8_gfx* pxl8_gfx_create( - const pxl8_hal* hal, - void* platform_data, - pxl8_pixel_mode mode, - pxl8_resolution resolution -) { - pxl8_gfx* gfx = (pxl8_gfx*)calloc(1, sizeof(pxl8_gfx)); - if (!gfx) { - pxl8_error("Failed to allocate graphics context"); - return NULL; - } - - gfx->hal = hal; - gfx->platform_data = platform_data; - - gfx->pixel_mode = mode; - pxl8_size size = pxl8_get_resolution_dimensions(resolution); - gfx->framebuffer_width = size.w; - gfx->framebuffer_height = size.h; - - if (!gfx->platform_data) { - pxl8_error("Platform data cannot be NULL"); - free(gfx); - return NULL; - } - - i32 bytes_per_pixel = pxl8_bytes_per_pixel(gfx->pixel_mode); - i32 fb_size = gfx->framebuffer_width * gfx->framebuffer_height * bytes_per_pixel; - gfx->framebuffer = (u8*)calloc(1, fb_size); - if (!gfx->framebuffer) { - pxl8_error("Failed to allocate framebuffer"); - pxl8_gfx_destroy(gfx); - return NULL; - } - - gfx->palette_size = (mode == PXL8_PIXEL_HICOLOR) ? 0 : PXL8_MAX_PALETTE_SIZE; - if (gfx->palette_size > 0) { - gfx->palette = (u32*)calloc(gfx->palette_size, sizeof(u32)); - if (!gfx->palette) { - pxl8_error("Failed to allocate palette"); - pxl8_gfx_destroy(gfx); - return NULL; - } - - for (u32 i = 0; i < gfx->palette_size; i++) { - u8 gray = (u8)i; - gfx->palette[i] = 0xFF000000 | (gray << 16) | (gray << 8) | gray; - } - } - - gfx->viewport.offset_x = 0; - gfx->viewport.offset_y = 0; - gfx->viewport.scaled_width = gfx->framebuffer_width; - gfx->viewport.scaled_height = gfx->framebuffer_height; - gfx->viewport.scale = 1.0f; - - gfx->backface_culling = true; - gfx->model = pxl8_mat4_identity(); - gfx->projection = pxl8_mat4_identity(); - gfx->view = pxl8_mat4_identity(); - gfx->mvp_dirty = true; - gfx->wireframe = false; - gfx->zbuffer = NULL; - gfx->zbuffer_height = 0; - gfx->zbuffer_width = 0; - gfx->affine_textures = false; - - gfx->initialized = true; - return gfx; -} - -void pxl8_gfx_destroy(pxl8_gfx* gfx) { - if (!gfx) return; - - pxl8_atlas_destroy(gfx->atlas); - free(gfx->sprite_cache); - free(gfx->framebuffer); - free(gfx->palette); - free(gfx->zbuffer); - - free(gfx); -} - -static pxl8_result pxl8_gfx_ensure_atlas(pxl8_gfx* gfx) { - if (gfx->atlas) return PXL8_OK; - gfx->atlas = pxl8_atlas_create(PXL8_DEFAULT_ATLAS_SIZE, PXL8_DEFAULT_ATLAS_SIZE, gfx->pixel_mode); - return gfx->atlas ? PXL8_OK : PXL8_ERROR_OUT_OF_MEMORY; -} - -void pxl8_gfx_clear_textures(pxl8_gfx* gfx) { - if (!gfx) return; - - if (gfx->atlas) { - pxl8_atlas_clear(gfx->atlas, 0); - } - - if (gfx->sprite_cache) { - gfx->sprite_cache_count = 0; - } -} - -pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width, u32 height) { - if (!gfx || !gfx->initialized || !pixels) return PXL8_ERROR_INVALID_ARGUMENT; - - pxl8_result result = pxl8_gfx_ensure_atlas(gfx); - if (result != PXL8_OK) return result; - - u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, pixels, width, height, gfx->pixel_mode); - if (texture_id == UINT32_MAX) { - pxl8_error("Texture doesn't fit in atlas"); - return PXL8_ERROR_INVALID_SIZE; - } - - return texture_id; -} - -pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) { - if (!gfx || !gfx->initialized || !path) return PXL8_ERROR_INVALID_ARGUMENT; - - if (!gfx->sprite_cache) { - gfx->sprite_cache_capacity = PXL8_DEFAULT_SPRITE_CACHE_CAPACITY; - gfx->sprite_cache = (pxl8_sprite_cache_entry*)calloc( - gfx->sprite_cache_capacity, sizeof(pxl8_sprite_cache_entry) - ); - if (!gfx->sprite_cache) return PXL8_ERROR_OUT_OF_MEMORY; - } - - for (u32 i = 0; i < gfx->sprite_cache_count; i++) { - if (gfx->sprite_cache[i].active && strcmp(gfx->sprite_cache[i].path, path) == 0) { - return gfx->sprite_cache[i].sprite_id; - } - } - - pxl8_result result = pxl8_gfx_ensure_atlas(gfx); - if (result != PXL8_OK) return result; - - pxl8_ase_file ase_file; - result = pxl8_ase_load(path, &ase_file); - if (result != PXL8_OK) { - pxl8_error("Failed to load ASE file: %s", path); - return result; - } - - if (ase_file.frame_count == 0) { - pxl8_error("No frames in ASE file"); - pxl8_ase_destroy(&ase_file); - return PXL8_ERROR_INVALID_FORMAT; - } - - u32 sprite_id = pxl8_atlas_add_texture( - gfx->atlas, - ase_file.frames[0].pixels, - ase_file.header.width, - ase_file.header.height, - gfx->pixel_mode - ); - - pxl8_ase_destroy(&ase_file); - - if (sprite_id == UINT32_MAX) { - pxl8_error("Sprite doesn't fit in atlas"); - return PXL8_ERROR_INVALID_SIZE; - } - - if (gfx->sprite_cache_count >= gfx->sprite_cache_capacity) { - u32 new_capacity = gfx->sprite_cache_capacity * 2; - pxl8_sprite_cache_entry* new_cache = (pxl8_sprite_cache_entry*)realloc( - gfx->sprite_cache, - new_capacity * sizeof(pxl8_sprite_cache_entry) - ); - if (!new_cache) return PXL8_ERROR_OUT_OF_MEMORY; - gfx->sprite_cache = new_cache; - gfx->sprite_cache_capacity = new_capacity; - } - - pxl8_sprite_cache_entry* entry = &gfx->sprite_cache[gfx->sprite_cache_count++]; - entry->active = true; - entry->sprite_id = sprite_id; - pxl8_strncpy(entry->path, path, sizeof(entry->path)); - - return sprite_id; -} - -pxl8_atlas* pxl8_gfx_get_atlas(pxl8_gfx* gfx) { - if (!gfx || !gfx->initialized) return NULL; - - if (pxl8_gfx_ensure_atlas(gfx) != PXL8_OK) return NULL; - return gfx->atlas; -} - -pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path) { - if (!gfx || !gfx->initialized || !path) return PXL8_ERROR_INVALID_ARGUMENT; - if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) return PXL8_OK; - - pxl8_debug("Loading palette from: %s", path); - - pxl8_ase_file ase_file; - pxl8_result result = pxl8_ase_load(path, &ase_file); - if (result != PXL8_OK) { - pxl8_error("Failed to load ASE file for palette: %s", path); - return result; - } - - if (ase_file.palette.entry_count == 0 || !ase_file.palette.colors) { - pxl8_error("No palette data in ASE file"); - pxl8_ase_destroy(&ase_file); - return PXL8_ERROR_INVALID_FORMAT; - } - - u32 copy_size = ase_file.palette.entry_count < gfx->palette_size - ? ase_file.palette.entry_count - : gfx->palette_size; - - memcpy(gfx->palette, ase_file.palette.colors, copy_size * sizeof(u32)); - - u32 last_color = (copy_size > 0) ? gfx->palette[copy_size - 1] : 0xFF000000; - for (u32 i = copy_size; i < gfx->palette_size; i++) { - gfx->palette[i] = last_color; - } - - pxl8_ase_destroy(&ase_file); - pxl8_debug("Loaded palette with %u colors", copy_size); - - return PXL8_OK; -} - -pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx) { - (void)gfx; - return PXL8_OK; -} - -void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) { - if (!gfx || !gfx->initialized || !gfx->hal) return; - - u32 bpp = (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) ? 2 : 1; - gfx->hal->upload_texture( - gfx->platform_data, - gfx->framebuffer, - gfx->framebuffer_width, - gfx->framebuffer_height, - gfx->palette, - bpp - ); -} - -void pxl8_gfx_present(pxl8_gfx* gfx) { - if (!gfx || !gfx->initialized || !gfx->hal) return; - - gfx->hal->present(gfx->platform_data); -} - -pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height) { - pxl8_viewport vp = {0}; - vp.scale = fminf(bounds.w / (f32)width, bounds.h / (f32)height); - vp.scaled_width = (i32)(width * vp.scale); - vp.scaled_height = (i32)(height * vp.scale); - vp.offset_x = (bounds.w - vp.scaled_width) / 2; - vp.offset_y = (bounds.h - vp.scaled_height) / 2; - return vp; -} - -void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp) { - if (!gfx) return; - gfx->viewport = vp; -} - -void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom) { - (void)gfx; (void)left; (void)right; (void)top; (void)bottom; -} - -static inline void pxl8_fb_set(pxl8_gfx* gfx, i32 idx, u32 color) { - if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) - ((u16*)gfx->framebuffer)[idx] = pxl8_rgba32_to_rgb565(color); - else - gfx->framebuffer[idx] = color & 0xFF; -} - -static inline u32 pxl8_fb_get(pxl8_gfx* gfx, i32 idx) { - return (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) - ? pxl8_rgb565_to_rgba32(((u16*)gfx->framebuffer)[idx]) - : gfx->framebuffer[idx]; -} - -void pxl8_clear(pxl8_gfx* gfx, u32 color) { - if (!gfx || !gfx->framebuffer) return; - - i32 size = gfx->framebuffer_width * gfx->framebuffer_height; - - if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) { - u16* fb16 = (u16*)gfx->framebuffer; - u16 color16 = pxl8_rgba32_to_rgb565(color); - u32 pattern = (u32)color16 | ((u32)color16 << 16); - u32* fb32 = (u32*)fb16; - i32 count2 = size / 2; - for (i32 i = 0; i < count2; i++) { - fb32[i] = pattern; - } - if (size & 1) { - fb16[size - 1] = color16; - } - } else { - memset(gfx->framebuffer, color & 0xFF, size); - } -} - -void pxl8_pixel(pxl8_gfx* gfx, i32 x, i32 y, u32 color) { - if (!gfx || !gfx->framebuffer) return; - if (x < 0 || x >= gfx->framebuffer_width || y < 0 || y >= gfx->framebuffer_height) return; - pxl8_fb_set(gfx, y * gfx->framebuffer_width + x, color); -} - -u32 pxl8_get_pixel(pxl8_gfx* gfx, i32 x, i32 y) { - if (!gfx || !gfx->framebuffer) return 0; - if (x < 0 || x >= gfx->framebuffer_width || y < 0 || y >= gfx->framebuffer_height) return 0; - return pxl8_fb_get(gfx, y * gfx->framebuffer_width + x); -} - -void pxl8_line(pxl8_gfx* gfx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color) { - if (!gfx) return; - - i32 dx = abs(x1 - x0); - i32 dy = abs(y1 - y0); - i32 sx = x0 < x1 ? 1 : -1; - i32 sy = y0 < y1 ? 1 : -1; - i32 err = dx - dy; - - while (1) { - pxl8_pixel(gfx, x0, y0, color); - - if (x0 == x1 && y0 == y1) break; - - i32 e2 = 2 * err; - if (e2 > -dy) { - err -= dy; - x0 += sx; - } - if (e2 < dx) { - err += dx; - y0 += sy; - } - } -} - -void pxl8_rect(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) { - if (!gfx) return; - - pxl8_line(gfx, x, y, x + w - 1, y, color); - pxl8_line(gfx, x + w - 1, y, x + w - 1, y + h - 1, color); - pxl8_line(gfx, x + w - 1, y + h - 1, x, y + h - 1, color); - pxl8_line(gfx, x, y + h - 1, x, y, color); -} - -static inline void pxl8_pixel_unchecked(pxl8_gfx* gfx, i32 x, i32 y, u32 color) { - pxl8_fb_set(gfx, y * gfx->framebuffer_width + x, color); -} - -void pxl8_rect_fill(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) { - if (!gfx || !gfx->framebuffer) return; - - i32 x0 = (x < 0) ? 0 : x; - i32 y0 = (y < 0) ? 0 : y; - i32 x1 = (x + w > gfx->framebuffer_width) ? gfx->framebuffer_width : x + w; - i32 y1 = (y + h > gfx->framebuffer_height) ? gfx->framebuffer_height : y + h; - - i32 rect_w = x1 - x0; - if (rect_w <= 0 || y1 <= y0) return; - - if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) { - u16* fb16 = (u16*)gfx->framebuffer; - u16 color16 = pxl8_rgba32_to_rgb565(color); - u32 pattern = (u32)color16 | ((u32)color16 << 16); - - for (i32 py = y0; py < y1; py++) { - u16* row = fb16 + py * gfx->framebuffer_width + x0; - i32 count2 = rect_w / 2; - u32* row32 = (u32*)row; - for (i32 i = 0; i < count2; i++) { - row32[i] = pattern; - } - if (rect_w & 1) { - row[rect_w - 1] = color16; - } - } - } else { - u8 color8 = color & 0xFF; - for (i32 py = y0; py < y1; py++) { - u8* row = gfx->framebuffer + py * gfx->framebuffer_width + x0; - memset(row, color8, rect_w); - } - } -} - -void pxl8_circle(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color) { - if (!gfx) return; - - i32 x = radius; - i32 y = 0; - i32 err = 0; - - while (x >= y) { - pxl8_pixel(gfx, cx + x, cy + y, color); - pxl8_pixel(gfx, cx + y, cy + x, color); - pxl8_pixel(gfx, cx - y, cy + x, color); - pxl8_pixel(gfx, cx - x, cy + y, color); - pxl8_pixel(gfx, cx - x, cy - y, color); - pxl8_pixel(gfx, cx - y, cy - x, color); - pxl8_pixel(gfx, cx + y, cy - x, color); - pxl8_pixel(gfx, cx + x, cy - y, color); - - if (err <= 0) { - y += 1; - err += 2 * y + 1; - } else { - x -= 1; - err -= 2 * x + 1; - } - } -} - -void pxl8_circle_fill(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color) { - if (!gfx || !gfx->framebuffer) return; - - i32 x0 = (cx - radius < 0) ? -cx : -radius; - i32 y0 = (cy - radius < 0) ? -cy : -radius; - i32 x1 = (cx + radius >= gfx->framebuffer_width) ? gfx->framebuffer_width - cx - 1 : radius; - i32 y1 = (cy + radius >= gfx->framebuffer_height) ? gfx->framebuffer_height - cy - 1 : radius; - - for (i32 y = y0; y <= y1; y++) { - for (i32 x = x0; x <= x1; x++) { - if (x * x + y * y <= radius * radius) { - pxl8_pixel_unchecked(gfx, cx + x, cy + y, color); - } - } - } -} - -void pxl8_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) { - if (!gfx || !text || !gfx->framebuffer) return; - - const pxl8_font* font = &pxl8_default_font; - i32 cursor_x = x; - i32 cursor_y = y; - - for (const char* c = text; *c; c++) { - const pxl8_glyph* glyph = pxl8_font_find_glyph(font, (u32)*c); - if (!glyph) continue; - - for (i32 gy = 0; gy < glyph->height; gy++) { - for (i32 gx = 0; gx < glyph->width; gx++) { - i32 px = cursor_x + gx; - i32 py = cursor_y + gy; - - if (px < 0 || px >= gfx->framebuffer_width || - py < 0 || py >= gfx->framebuffer_height) continue; - - u8 pixel_bit = 0; - - if (glyph->format == PXL8_FONT_FORMAT_INDEXED) { - u8 pixel_byte = glyph->data.indexed[gy]; - pixel_bit = (pixel_byte >> gx) & 1; - } else { - i32 glyph_idx = gy * 8 + gx; - u32 rgba_pixel = glyph->data.rgba[glyph_idx]; - pixel_bit = ((rgba_pixel >> 24) & 0xFF) > 128 ? 1 : 0; - } - - if (pixel_bit) { - i32 fb_idx = py * gfx->framebuffer_width + px; - if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) { - ((u16*)gfx->framebuffer)[fb_idx] = pxl8_rgba32_to_rgb565(color); - } else { - gfx->framebuffer[fb_idx] = (u8)color; - } - } - } - } - - cursor_x += font->default_width; - } -} - - -void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y) { - if (!gfx || !gfx->atlas || !gfx->framebuffer) return; - - const pxl8_atlas_entry* entry = pxl8_atlas_get_entry(gfx->atlas, sprite_id); - if (!entry || !entry->active) return; - - i32 clip_left = (x < 0) ? -x : 0; - i32 clip_top = (y < 0) ? -y : 0; - i32 clip_right = (x + w > gfx->framebuffer_width) ? x + w - gfx->framebuffer_width : 0; - i32 clip_bottom = (y + h > gfx->framebuffer_height) ? y + h - gfx->framebuffer_height : 0; - - i32 draw_width = w - clip_left - clip_right; - i32 draw_height = h - clip_top - clip_bottom; - - if (draw_width <= 0 || draw_height <= 0) return; - - i32 dest_x = x + clip_left; - i32 dest_y = y + clip_top; - - bool is_1to1_scale = (w == entry->w && h == entry->h); - bool is_unclipped = (clip_left == 0 && clip_top == 0 && clip_right == 0 && clip_bottom == 0); - bool is_flipped = flip_x || flip_y; - - u32 atlas_width = pxl8_atlas_get_width(gfx->atlas); - const u8* atlas_pixels = pxl8_atlas_get_pixels(gfx->atlas); - - if (is_1to1_scale && is_unclipped && !is_flipped) { - if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) { - const u16* sprite_data = (const u16*)atlas_pixels + entry->y * atlas_width + entry->x; - pxl8_blit_hicolor( - (u16*)gfx->framebuffer, - gfx->framebuffer_width, - sprite_data, - atlas_width, - x, y, w, h - ); - } else { - const u8* sprite_data = atlas_pixels + entry->y * atlas_width + entry->x; - pxl8_blit_indexed( - gfx->framebuffer, - gfx->framebuffer_width, - sprite_data, - atlas_width, - x, y, w, h - ); - } - } else { - for (i32 py = 0; py < draw_height; py++) { - for (i32 px = 0; px < draw_width; px++) { - i32 local_x = (px + clip_left) * entry->w / w; - i32 local_y = (py + clip_top) * entry->h / h; - - i32 src_x = flip_x ? entry->x + entry->w - 1 - local_x : entry->x + local_x; - i32 src_y = flip_y ? entry->y + entry->h - 1 - local_y : entry->y + local_y; - - i32 src_idx = src_y * atlas_width + src_x; - i32 dest_idx = (dest_y + py) * gfx->framebuffer_width + (dest_x + px); - - if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) { - u16* fb16 = (u16*)gfx->framebuffer; - fb16[dest_idx] = pxl8_blend_hicolor(((const u16*)atlas_pixels)[src_idx], fb16[dest_idx]); - } else { - gfx->framebuffer[dest_idx] = pxl8_blend_indexed(atlas_pixels[src_idx], gfx->framebuffer[dest_idx]); - } - } - } - } -} - -void pxl8_gfx_cycle_palette(pxl8_gfx* gfx, u8 start, u8 count, i32 step) { - if (!gfx || !gfx->palette || count == 0) return; - - u32 temp[256]; - for (u8 i = 0; i < count; i++) { - temp[i] = gfx->palette[start + i]; - } - - for (u8 i = 0; i < count; i++) { - u8 src_idx = i; - u8 dst_idx = (i + step) % count; - gfx->palette[start + dst_idx] = temp[src_idx]; - } -} - -void pxl8_gfx_swap_palette(pxl8_gfx* gfx, u8 start, u8 count, u32* new_colors) { - if (!gfx || !gfx->palette || !new_colors) return; - - for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) { - gfx->palette[start + i] = new_colors[i]; - } -} - -void pxl8_gfx_fade_palette(pxl8_gfx* gfx, u8 start, u8 count, f32 amount, u32 target_color) { - if (!gfx || !gfx->palette || count == 0) return; - - if (amount < 0.0f) amount = 0.0f; - if (amount > 1.0f) amount = 1.0f; - - u8 target_r, target_g, target_b, target_a; - pxl8_rgba32_unpack(target_color, &target_r, &target_g, &target_b, &target_a); - - for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) { - u8 cur_r, cur_g, cur_b, cur_a; - pxl8_rgba32_unpack(gfx->palette[start + i], &cur_r, &cur_g, &cur_b, &cur_a); - - u8 new_r = pxl8_color_lerp_channel(cur_r, target_r, amount); - u8 new_g = pxl8_color_lerp_channel(cur_g, target_g, amount); - u8 new_b = pxl8_color_lerp_channel(cur_b, target_b, amount); - u8 new_a = pxl8_color_lerp_channel(cur_a, target_a, amount); - - gfx->palette[start + i] = pxl8_rgba32_pack(new_r, new_g, new_b, new_a); - } -} - -void pxl8_gfx_interpolate_palettes( - pxl8_gfx* gfx, - u32* palette1, - u32* palette2, - u8 start, - u8 count, - f32 t -) { - if (!gfx || !gfx->palette || !palette1 || !palette2 || count == 0) return; - - if (t < 0.0f) t = 0.0f; - if (t > 1.0f) t = 1.0f; - - for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) { - u8 r1, g1, b1, a1, r2, g2, b2, a2; - pxl8_rgba32_unpack(palette1[i], &r1, &g1, &b1, &a1); - pxl8_rgba32_unpack(palette2[i], &r2, &g2, &b2, &a2); - - u8 r = pxl8_color_lerp_channel(r1, r2, t); - u8 g = pxl8_color_lerp_channel(g1, g2, t); - u8 b = pxl8_color_lerp_channel(b1, b2, t); - u8 a = pxl8_color_lerp_channel(a1, a2, t); - - gfx->palette[start + i] = pxl8_rgba32_pack(r, g, b, a); - } -} - -void pxl8_gfx_color_ramp(pxl8_gfx* gfx, u8 start, u8 count, u32 from_color, u32 to_color) { - if (!gfx || !gfx->palette || count <= 1) return; - - u8 from_r, from_g, from_b, from_a; - u8 to_r, to_g, to_b, to_a; - pxl8_rgba32_unpack(from_color, &from_r, &from_g, &from_b, &from_a); - pxl8_rgba32_unpack(to_color, &to_r, &to_g, &to_b, &to_a); - - for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) { - f32 t = (f32)i / (f32)(count - 1); - - u8 r = pxl8_color_lerp_channel(from_r, to_r, t); - u8 g = pxl8_color_lerp_channel(from_g, to_g, t); - u8 b = pxl8_color_lerp_channel(from_b, to_b, t); - u8 a = pxl8_color_lerp_channel(from_a, to_a, t); - - gfx->palette[start + i] = pxl8_rgba32_pack(r, g, b, a); - } -} - -static void pxl8_gfx_process_effects(pxl8_gfx* gfx, pxl8_effects* effects, f32 dt) { - if (!gfx || !effects) return; - - effects->time += dt; - - for (i32 i = 0; i < 8; i++) { - pxl8_palette_cycle* cycle = &effects->palette_cycles[i]; - if (!cycle->active) continue; - - cycle->timer += dt * cycle->speed; - if (cycle->timer >= 1.0f) { - cycle->timer -= 1.0f; - i32 count = cycle->end_index - cycle->start_index + 1; - pxl8_gfx_cycle_palette(gfx, cycle->start_index, count, 1); - } - } -} - -void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt) { - if (!gfx) return; - pxl8_gfx_process_effects(gfx, &gfx->effects, dt); -} - -i32 pxl8_gfx_add_palette_cycle(pxl8_gfx* gfx, u8 start_index, u8 end_index, f32 speed) { - if (!gfx) return -1; - if (start_index >= end_index) return -1; - - for (i32 i = 0; i < 8; i++) { - if (!gfx->effects.palette_cycles[i].active) { - gfx->effects.palette_cycles[i].active = true; - gfx->effects.palette_cycles[i].start_index = start_index; - gfx->effects.palette_cycles[i].end_index = end_index; - gfx->effects.palette_cycles[i].speed = speed; - gfx->effects.palette_cycles[i].timer = 0.0f; - return i; - } - } - return -1; -} - -void pxl8_gfx_remove_palette_cycle(pxl8_gfx* gfx, i32 cycle_id) { - if (!gfx || cycle_id < 0 || cycle_id >= 8) return; - gfx->effects.palette_cycles[cycle_id].active = false; -} - -void pxl8_gfx_set_palette_cycle_speed(pxl8_gfx* gfx, i32 cycle_id, f32 speed) { - if (!gfx || cycle_id < 0 || cycle_id >= 8) return; - gfx->effects.palette_cycles[cycle_id].speed = speed; -} - -void pxl8_gfx_clear_palette_cycles(pxl8_gfx* gfx) { - if (!gfx) return; - for (i32 i = 0; i < 8; i++) { - gfx->effects.palette_cycles[i].active = false; - } -} - -static bool pxl8_3d_init_zbuffer(pxl8_gfx* gfx) { - if (gfx->zbuffer) return true; - - gfx->zbuffer_width = gfx->framebuffer_width; - gfx->zbuffer_height = gfx->framebuffer_height; - - gfx->zbuffer = (f32*)calloc(gfx->zbuffer_width * gfx->zbuffer_height, sizeof(f32)); - if (!gfx->zbuffer) { - return false; - } - - pxl8_3d_clear_zbuffer(gfx); - return true; -} - -void pxl8_3d_clear_zbuffer(pxl8_gfx* gfx) { - if (!gfx || !gfx->zbuffer) return; - - i32 count = gfx->zbuffer_width * gfx->zbuffer_height; - const f32 far_z = 1e30f; - - for (i32 i = 0; i < count; i++) { - gfx->zbuffer[i] = far_z; - } -} - -void pxl8_3d_set_backface_culling(pxl8_gfx* gfx, bool culling) { - if (!gfx) return; - gfx->backface_culling = culling; -} - -void pxl8_3d_set_model(pxl8_gfx* gfx, pxl8_mat4 mat) { - if (!gfx) return; - gfx->model = mat; - gfx->mvp_dirty = true; -} - -void pxl8_3d_set_projection(pxl8_gfx* gfx, pxl8_mat4 mat) { - if (!gfx) return; - gfx->projection = mat; - gfx->mvp_dirty = true; -} - -void pxl8_3d_set_view(pxl8_gfx* gfx, pxl8_mat4 mat) { - if (!gfx) return; - gfx->view = mat; - gfx->mvp_dirty = true; -} - -void pxl8_3d_set_wireframe(pxl8_gfx* gfx, bool wireframe) { - if (!gfx) return; - gfx->wireframe = wireframe; -} - -void pxl8_3d_set_affine_textures(pxl8_gfx* gfx, bool affine) { - if (!gfx) return; - gfx->affine_textures = affine; -} - -static inline void pxl8_update_mvp(pxl8_gfx* gfx) { - if (!gfx->mvp_dirty) return; - - gfx->mvp = pxl8_mat4_multiply(gfx->projection, - pxl8_mat4_multiply(gfx->view, gfx->model)); - - pxl8_mat4 vp = pxl8_mat4_multiply(gfx->projection, gfx->view); - gfx->frustum = pxl8_frustum_from_matrix(vp); - - gfx->mvp_dirty = false; -} - -const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx) { - if (!gfx) return NULL; - pxl8_update_mvp(gfx); - return &gfx->frustum; -} - -static inline pxl8_vec4 pxl8_transform_vertex(pxl8_gfx* gfx, pxl8_vec3 pos) { - pxl8_update_mvp(gfx); - - pxl8_vec4 v = { - .x = pos.x, - .y = pos.y, - .z = pos.z, - .w = 1.0f, - }; - - return pxl8_mat4_multiply_vec4(gfx->mvp, v); -} - -static inline void pxl8_project_to_screen(pxl8_gfx* gfx, pxl8_vec4 clip, i32* x, i32* y, f32* z) { - if (fabsf(clip.w) < 1e-6f) { - *x = *y = 0; - *z = 1e30f; - return; - } - - f32 inv_w = 1.0f / clip.w; - f32 ndc_x = clip.x * inv_w; - f32 ndc_y = clip.y * inv_w; - - *x = (i32)((ndc_x + 1.0f) * 0.5f * gfx->framebuffer_width); - *y = (i32)((1.0f - ndc_y) * 0.5f * gfx->framebuffer_height); - *z = clip.z * inv_w; -} - -void pxl8_3d_draw_line_3d(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u32 color) { - if (!gfx) return; - - pxl8_vec4 v0 = pxl8_transform_vertex(gfx, p0); - pxl8_vec4 v1 = pxl8_transform_vertex(gfx, p1); - - i32 x0, y0, x1, y1; - f32 z0, z1; - pxl8_project_to_screen(gfx, v0, &x0, &y0, &z0); - pxl8_project_to_screen(gfx, v1, &x1, &y1, &z1); - - pxl8_line(gfx, x0, y0, x1, y1, color); -} - -static inline void pxl8_fill_scanline(pxl8_gfx* gfx, i32 y, i32 xs, i32 xe, f32 z0, f32 z1, u32 color) { - if (y < 0 || y >= gfx->framebuffer_height) return; - if (xs > xe) { - i32 tmp = xs; xs = xe; xe = tmp; - f32 tmpz = z0; z0 = z1; z1 = tmpz; - } - - if (xs < 0) xs = 0; - if (xe >= gfx->framebuffer_width) xe = gfx->framebuffer_width - 1; - if (xs > xe) return; - - i32 width = xe - xs; - i32 zbuf_offset = y * gfx->zbuffer_width; - i32 fb_offset = y * gfx->framebuffer_width; - - if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) { - u16* fb = (u16*)gfx->framebuffer; - u16 color16 = pxl8_rgba32_to_rgb565(color); - if (width == 0) { - i32 idx = zbuf_offset + xs; - if (z0 <= gfx->zbuffer[idx]) { - gfx->zbuffer[idx] = z0; - fb[fb_offset + xs] = color16; - } - return; - } - - f32 dz = (z1 - z0) / (f32)width; - f32 z = z0; - - for (i32 x = xs; x <= xe; x++) { - i32 idx = zbuf_offset + x; - if (z <= gfx->zbuffer[idx]) { - gfx->zbuffer[idx] = z; - fb[fb_offset + x] = color16; - } - z += dz; - } - } else { - u8 idx_color = color & 0xFF; - if (width == 0) { - i32 idx = zbuf_offset + xs; - if (z0 <= gfx->zbuffer[idx]) { - gfx->zbuffer[idx] = z0; - gfx->framebuffer[fb_offset + xs] = idx_color; - } - return; - } - - f32 dz = (z1 - z0) / (f32)width; - f32 z = z0; - - for (i32 x = xs; x <= xe; x++) { - i32 idx = zbuf_offset + x; - if (z <= gfx->zbuffer[idx]) { - gfx->zbuffer[idx] = z; - gfx->framebuffer[fb_offset + x] = idx_color; - } - z += dz; - } - } -} - -static inline void pxl8_fill_scanline_textured( - pxl8_gfx* gfx, i32 y, i32 xs, i32 xe, - f32 z0, f32 z1, - f32 u0, f32 v0, f32 w0, - f32 u1, f32 v1, f32 w1, - u32 texture_id -) { - if (y < 0 || y >= gfx->framebuffer_height) return; - if (xs > xe) { - i32 tmp = xs; xs = xe; xe = tmp; - f32 tmpf; - tmpf = z0; z0 = z1; z1 = tmpf; - tmpf = u0; u0 = u1; u1 = tmpf; - tmpf = v0; v0 = v1; v1 = tmpf; - tmpf = w0; w0 = w1; w1 = tmpf; - } - - const pxl8_atlas_entry* entry = pxl8_atlas_get_entry(gfx->atlas, texture_id); - if (!entry || !entry->active) return; - - const u8* atlas_pixels = pxl8_atlas_get_pixels(gfx->atlas); - u32 atlas_width = pxl8_atlas_get_width(gfx->atlas); - i32 tex_w = entry->w; - i32 tex_h = entry->h; - bool is_pow2 = ((tex_w & (tex_w - 1)) == 0) && ((tex_h & (tex_h - 1)) == 0) && (tex_w == tex_h); - i32 tex_mask = tex_w - 1; - i32 atlas_x_base = entry->x; - i32 atlas_y_base = entry->y; - bool is_hicolor = gfx->pixel_mode == PXL8_PIXEL_HICOLOR; - bool affine = gfx->affine_textures; - - i32 span = xe - xs; - if (span <= 0) { - if (xs >= 0 && xs < gfx->framebuffer_width) { - i32 idx = y * gfx->zbuffer_width + xs; - if (z0 <= gfx->zbuffer[idx]) { - f32 tex_u = u0, tex_v = v0; - if (!affine && fabsf(w0) > 1e-6f) { - f32 perspective_w = 1.0f / w0; - tex_u *= perspective_w; - tex_v *= perspective_w; - } - - i32 tu = (i32)(tex_u * tex_w); - i32 tv = (i32)(tex_v * tex_h); - i32 tx, ty; - if (is_pow2) { - tx = tu & tex_mask; - ty = tv & tex_mask; - } else { - tx = tu % tex_w; - if (tx < 0) tx += tex_w; - ty = tv % tex_h; - if (ty < 0) ty += tex_h; - } - - i32 atlas_idx = (atlas_y_base + ty) * atlas_width + (atlas_x_base + tx); - - if (is_hicolor) { - u16 s = ((const u16*)atlas_pixels)[atlas_idx]; - u16 d = ((u16*)gfx->framebuffer)[y * gfx->framebuffer_width + xs]; - u16 m = (u16)(-(s != 0)); - ((u16*)gfx->framebuffer)[y * gfx->framebuffer_width + xs] = (s & m) | (d & ~m); - f32 old_z = gfx->zbuffer[idx]; - u32 zm = -(s != 0); - u32 z0_bits, old_z_bits; - memcpy(&z0_bits, &z0, sizeof(u32)); - memcpy(&old_z_bits, &old_z, sizeof(u32)); - u32 new_z_bits = (z0_bits & zm) | (old_z_bits & ~zm); - memcpy(&gfx->zbuffer[idx], &new_z_bits, sizeof(f32)); - } else { - u8 s = atlas_pixels[atlas_idx]; - u8 d = gfx->framebuffer[y * gfx->framebuffer_width + xs]; - u8 m = (u8)(-(s != 0)); - gfx->framebuffer[y * gfx->framebuffer_width + xs] = (s & m) | (d & ~m); - f32 old_z = gfx->zbuffer[idx]; - u32 zm = -(s != 0); - u32 z0_bits, old_z_bits; - memcpy(&z0_bits, &z0, sizeof(u32)); - memcpy(&old_z_bits, &old_z, sizeof(u32)); - u32 new_z_bits = (z0_bits & zm) | (old_z_bits & ~zm); - memcpy(&gfx->zbuffer[idx], &new_z_bits, sizeof(f32)); - } - } - } - return; - } - - f32 inv_span = 1.0f / (f32)span; - f32 dz = (z1 - z0) * inv_span; - f32 du = (u1 - u0) * inv_span; - f32 dv = (v1 - v0) * inv_span; - f32 dw = (w1 - w0) * inv_span; - - f32 z = z0; - f32 u = u0; - f32 v = v0; - f32 w = w0; - - for (i32 x = xs; x <= xe; x++) { - if (x >= 0 && x < gfx->framebuffer_width) { - i32 idx = y * gfx->zbuffer_width + x; - if (z <= gfx->zbuffer[idx]) { - - f32 tex_u = u, tex_v = v; - if (!affine && fabsf(w) > 1e-6f) { - f32 perspective_w = 1.0f / w; - tex_u *= perspective_w; - tex_v *= perspective_w; - } - - i32 tu = (i32)(tex_u * tex_w); - i32 tv = (i32)(tex_v * tex_h); - i32 tx, ty; - if (is_pow2) { - tx = tu & tex_mask; - ty = tv & tex_mask; - } else { - tx = tu % tex_w; - if (tx < 0) tx += tex_w; - ty = tv % tex_h; - if (ty < 0) ty += tex_h; - } - - i32 atlas_idx = (atlas_y_base + ty) * atlas_width + (atlas_x_base + tx); - - if (is_hicolor) { - u16 s = ((const u16*)atlas_pixels)[atlas_idx]; - u16 d = ((u16*)gfx->framebuffer)[y * gfx->framebuffer_width + x]; - u16 m = (u16)(-(s != 0)); - ((u16*)gfx->framebuffer)[y * gfx->framebuffer_width + x] = (s & m) | (d & ~m); - f32 old_z = gfx->zbuffer[idx]; - u32 zm = -(s != 0); - u32 z_bits, old_z_bits; - memcpy(&z_bits, &z, sizeof(u32)); - memcpy(&old_z_bits, &old_z, sizeof(u32)); - u32 new_z_bits = (z_bits & zm) | (old_z_bits & ~zm); - memcpy(&gfx->zbuffer[idx], &new_z_bits, sizeof(f32)); - } else { - u8 s = atlas_pixels[atlas_idx]; - u8 d = gfx->framebuffer[y * gfx->framebuffer_width + x]; - u8 m = (u8)(-(s != 0)); - gfx->framebuffer[y * gfx->framebuffer_width + x] = (s & m) | (d & ~m); - f32 old_z = gfx->zbuffer[idx]; - u32 zm = -(s != 0); - u32 z_bits, old_z_bits; - memcpy(&z_bits, &z, sizeof(u32)); - memcpy(&old_z_bits, &old_z, sizeof(u32)); - u32 new_z_bits = (z_bits & zm) | (old_z_bits & ~zm); - memcpy(&gfx->zbuffer[idx], &new_z_bits, sizeof(f32)); - } - } - } - z += dz; - u += du; - v += dv; - w += dw; - } -} - -typedef void (*pxl8_scanline_func)(pxl8_gfx*, i32, i32, i32, f32, f32, u32); -typedef void (*pxl8_scanline_textured_func)(pxl8_gfx*, i32, i32, i32, f32, f32, f32, f32, f32, f32, f32, f32, u32); - -static void pxl8_scan_triangle( - pxl8_gfx* gfx, - i32 x0, i32 y0, f32 z0, - i32 x1, i32 y1, f32 z1, - i32 x2, i32 y2, f32 z2, - u32 color, - pxl8_scanline_func fill_scanline, - bool is_bottom -) { - (void)z2; - - i32 y_start, y_end, y_step; - f32 x_start1, x_start2, inv_slope_1, inv_slope_2; - - if (is_bottom) { - if (y1 == y0 || y1 < y0 || y1 - y0 > gfx->framebuffer_height) return; - y_start = y0; - y_end = y1; - y_step = 1; - x_start1 = x_start2 = (f32)x0; - inv_slope_1 = (f32)(x1 - x0) / (f32)(y1 - y0); - inv_slope_2 = (f32)(x2 - x0) / (f32)(y2 - y0); - } else { - if (y2 == y0 || y2 < y0 || y2 - y0 > gfx->framebuffer_height) return; - y_start = y2; - y_end = y0; - y_step = -1; - x_start1 = x_start2 = (f32)x2; - inv_slope_1 = (f32)(x2 - x0) / (f32)(y2 - y0); - inv_slope_2 = (f32)(x2 - x1) / (f32)(y2 - y1); - } - - f32 cur_x1 = x_start1; - f32 cur_x2 = x_start2; - f32 slope_step1 = inv_slope_1 * y_step; - f32 slope_step2 = inv_slope_2 * y_step; - - for (i32 y = y_start; y != y_end + y_step; y += y_step) { - fill_scanline(gfx, y, (i32)cur_x1, (i32)cur_x2, z0, z1, color); - cur_x1 += slope_step1; - cur_x2 += slope_step2; - } -} - -void pxl8_3d_draw_triangle_raw(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, u32 color) { - pxl8_triangle tri; - tri.v[0].position = v0; - tri.v[0].color = color; - tri.v[1].position = v1; - tri.v[1].color = color; - tri.v[2].position = v2; - tri.v[2].color = color; - pxl8_3d_draw_triangle(gfx, tri); -} - -void pxl8_3d_draw_triangle(pxl8_gfx* gfx, pxl8_triangle tri) { - if (!gfx || !pxl8_3d_init_zbuffer(gfx)) return; - - - pxl8_vec4 v0 = pxl8_transform_vertex(gfx, tri.v[0].position); - pxl8_vec4 v1 = pxl8_transform_vertex(gfx, tri.v[1].position); - pxl8_vec4 v2 = pxl8_transform_vertex(gfx, tri.v[2].position); - - pxl8_vec4 tv0 = v0; - pxl8_vec4 tv1 = v1; - pxl8_vec4 tv2 = v2; - u32 tc0 = tri.v[0].color; - - i32 x0, y0, x1, y1, x2, y2; - f32 z0, z1, z2; - pxl8_project_to_screen(gfx, tv0, &x0, &y0, &z0); - pxl8_project_to_screen(gfx, tv1, &x1, &y1, &z1); - pxl8_project_to_screen(gfx, tv2, &x2, &y2, &z2); - - if (gfx->backface_culling) { - i32 cross = (x1 - x0) * (y2 - y0) - (y1 - y0) * (x2 - x0); - if (cross >= 0) return; - } - - if (gfx->wireframe) { - pxl8_line(gfx, x0, y0, x1, y1, tc0); - pxl8_line(gfx, x1, y1, x2, y2, tc0); - pxl8_line(gfx, x2, y2, x0, y0, tc0); - return; - } - - if (y0 > y1) { - i32 tmp_i = x0; x0 = x1; x1 = tmp_i; - tmp_i = y0; y0 = y1; y1 = tmp_i; - f32 tmp_f = z0; z0 = z1; z1 = tmp_f; - } - if (y0 > y2) { - i32 tmp_i = x0; x0 = x2; x2 = tmp_i; - tmp_i = y0; y0 = y2; y2 = tmp_i; - f32 tmp_f = z0; z0 = z2; z2 = tmp_f; - } - if (y1 > y2) { - i32 tmp_i = x1; x1 = x2; x2 = tmp_i; - tmp_i = y1; y1 = y2; y2 = tmp_i; - f32 tmp_f = z1; z1 = z2; z2 = tmp_f; - } - - pxl8_scanline_func fill_scanline = pxl8_fill_scanline; - - if (y1 == y2) { - pxl8_scan_triangle(gfx, x0, y0, z0, x1, y1, z1, x2, y2, z2, tc0, fill_scanline, true); - } else if (y0 == y1) { - pxl8_scan_triangle(gfx, x0, y0, z0, x1, y1, z1, x2, y2, z2, tc0, fill_scanline, false); - } else { - i32 x3 = x0 + (i32)(((f32)(y1 - y0) / (f32)(y2 - y0)) * (x2 - x0)); - i32 y3 = y1; - f32 z3 = z0 + ((f32)(y1 - y0) / (f32)(y2 - y0)) * (z2 - z0); - - pxl8_scan_triangle(gfx, x0, y0, z0, x1, y1, z1, x3, y3, z3, tc0, fill_scanline, true); - pxl8_scan_triangle(gfx, x1, y1, z1, x3, y3, z3, x2, y2, z2, tc0, fill_scanline, false); - } -} - -typedef struct pxl8_textured_vertex { - i32 x, y; - f32 z, u, v, w; -} pxl8_textured_vertex; - -static void pxl8_scan_triangle_textured( - pxl8_gfx* gfx, - pxl8_textured_vertex v0, - pxl8_textured_vertex v1, - pxl8_textured_vertex v2, - u32 texture_id, - pxl8_scanline_textured_func fill_scanline, - bool is_bottom -) { - i32 y_start, y_end, y_step; - f32 x_start1, x_start2; - f32 z_start1, z_start2, u_start1, u_start2, v_start1, v_start2, w_start1, w_start2; - f32 inv_slope_1, inv_slope_2, inv_slope_z1, inv_slope_z2; - f32 inv_slope_u1, inv_slope_u2, inv_slope_v1, inv_slope_v2, inv_slope_w1, inv_slope_w2; - - if (is_bottom) { - if (v1.y == v0.y || v1.y < v0.y) return; - y_start = v0.y; - y_end = v1.y; - y_step = 1; - x_start1 = x_start2 = (f32)v0.x; - z_start1 = z_start2 = v0.z; - u_start1 = u_start2 = v0.u; - v_start1 = v_start2 = v0.v; - w_start1 = w_start2 = v0.w; - inv_slope_1 = (f32)(v1.x - v0.x) / (f32)(v1.y - v0.y); - inv_slope_2 = (f32)(v2.x - v0.x) / (f32)(v2.y - v0.y); - inv_slope_z1 = (v1.z - v0.z) / (f32)(v1.y - v0.y); - inv_slope_z2 = (v2.z - v0.z) / (f32)(v2.y - v0.y); - inv_slope_u1 = (v1.u - v0.u) / (f32)(v1.y - v0.y); - inv_slope_u2 = (v2.u - v0.u) / (f32)(v2.y - v0.y); - inv_slope_v1 = (v1.v - v0.v) / (f32)(v1.y - v0.y); - inv_slope_v2 = (v2.v - v0.v) / (f32)(v2.y - v0.y); - inv_slope_w1 = (v1.w - v0.w) / (f32)(v1.y - v0.y); - inv_slope_w2 = (v2.w - v0.w) / (f32)(v2.y - v0.y); - } else { - if (v2.y == v0.y || v2.y < v0.y) return; - y_start = v2.y; - y_end = v0.y; - y_step = -1; - x_start1 = x_start2 = (f32)v2.x; - z_start1 = z_start2 = v2.z; - u_start1 = u_start2 = v2.u; - v_start1 = v_start2 = v2.v; - w_start1 = w_start2 = v2.w; - inv_slope_1 = (f32)(v2.x - v0.x) / (f32)(v2.y - v0.y); - inv_slope_2 = (f32)(v2.x - v1.x) / (f32)(v2.y - v1.y); - inv_slope_z1 = (v2.z - v0.z) / (f32)(v2.y - v0.y); - inv_slope_z2 = (v2.z - v1.z) / (f32)(v2.y - v1.y); - inv_slope_u1 = (v2.u - v0.u) / (f32)(v2.y - v0.y); - inv_slope_u2 = (v2.u - v1.u) / (f32)(v2.y - v1.y); - inv_slope_v1 = (v2.v - v0.v) / (f32)(v2.y - v0.y); - inv_slope_v2 = (v2.v - v1.v) / (f32)(v2.y - v1.y); - inv_slope_w1 = (v2.w - v0.w) / (f32)(v2.y - v0.y); - inv_slope_w2 = (v2.w - v1.w) / (f32)(v2.y - v1.y); - } - - f32 cur_x1 = x_start1, cur_x2 = x_start2; - f32 cur_z1 = z_start1, cur_z2 = z_start2; - f32 cur_u1 = u_start1, cur_u2 = u_start2; - f32 cur_v1 = v_start1, cur_v2 = v_start2; - f32 cur_w1 = w_start1, cur_w2 = w_start2; - - f32 slope_step_x1 = inv_slope_1 * y_step; - f32 slope_step_x2 = inv_slope_2 * y_step; - f32 slope_step_z1 = inv_slope_z1 * y_step; - f32 slope_step_z2 = inv_slope_z2 * y_step; - f32 slope_step_u1 = inv_slope_u1 * y_step; - f32 slope_step_u2 = inv_slope_u2 * y_step; - f32 slope_step_v1 = inv_slope_v1 * y_step; - f32 slope_step_v2 = inv_slope_v2 * y_step; - f32 slope_step_w1 = inv_slope_w1 * y_step; - f32 slope_step_w2 = inv_slope_w2 * y_step; - - for (i32 y = y_start; y != y_end + y_step; y += y_step) { - fill_scanline(gfx, y, (i32)cur_x1, (i32)cur_x2, - cur_z1, cur_z2, cur_u1, cur_v1, cur_w1, cur_u2, cur_v2, cur_w2, texture_id); - cur_x1 += slope_step_x1; cur_x2 += slope_step_x2; - cur_z1 += slope_step_z1; cur_z2 += slope_step_z2; - cur_u1 += slope_step_u1; cur_u2 += slope_step_u2; - cur_v1 += slope_step_v1; cur_v2 += slope_step_v2; - cur_w1 += slope_step_w1; cur_w2 += slope_step_w2; - } -} - -typedef struct { - pxl8_vec4 clip; - f32 u, v; -} pxl8_clip_vert; - -typedef struct { - i32 x, y; - f32 z, u, v, w; -} pxl8_screen_vert; - -static void pxl8_clip_screen_axis(pxl8_screen_vert* in, i32 in_count, pxl8_screen_vert* out, i32* out_count, i32 plane_pos, bool is_x, bool is_min) { - *out_count = 0; - - for (i32 i = 0; i < in_count; i++) { - pxl8_screen_vert curr = in[i]; - pxl8_screen_vert next = in[(i + 1) % in_count]; - - i32 curr_val = is_x ? curr.x : curr.y; - i32 next_val = is_x ? next.x : next.y; - - bool curr_inside = is_min ? (curr_val >= plane_pos) : (curr_val <= plane_pos); - bool next_inside = is_min ? (next_val >= plane_pos) : (next_val <= plane_pos); - - if (curr_inside) { - out[(*out_count)++] = curr; - } - - if (curr_inside != next_inside) { - i32 denom = next_val - curr_val; - if (denom != 0) { - f32 t = (f32)(plane_pos - curr_val) / (f32)denom; - out[*out_count] = (pxl8_screen_vert){ - .x = curr.x + (i32)(t * (next.x - curr.x)), - .y = curr.y + (i32)(t * (next.y - curr.y)), - .z = curr.z + t * (next.z - curr.z), - .u = curr.u + t * (next.u - curr.u), - .v = curr.v + t * (next.v - curr.v), - .w = curr.w + t * (next.w - curr.w) - }; - (*out_count)++; - } - } - } -} - -static void pxl8_clip_near_plane(pxl8_clip_vert* in, i32 in_count, pxl8_clip_vert* out, i32* out_count, f32 near) { - *out_count = 0; - - for (i32 i = 0; i < in_count; i++) { - pxl8_clip_vert curr = in[i]; - pxl8_clip_vert next = in[(i + 1) % in_count]; - - bool curr_inside = curr.clip.w >= near; - bool next_inside = next.clip.w >= near; - - if (curr_inside) { - out[(*out_count)++] = curr; - } - - if (curr_inside != next_inside) { - f32 t = (near - curr.clip.w) / (next.clip.w - curr.clip.w); - - out[*out_count] = (pxl8_clip_vert){ - .clip = { - curr.clip.x + t * (next.clip.x - curr.clip.x), - curr.clip.y + t * (next.clip.y - curr.clip.y), - curr.clip.z + t * (next.clip.z - curr.clip.z), - near - }, - .u = curr.u + t * (next.u - curr.u), - .v = curr.v + t * (next.v - curr.v) - }; - (*out_count)++; - } - } -} - -void pxl8_3d_draw_triangle_textured( - pxl8_gfx* gfx, - pxl8_vec3 v0, - pxl8_vec3 v1, - pxl8_vec3 v2, - f32 u0, - f32 v0f, - f32 u1, - f32 v1f, - f32 u2, - f32 v2f, - u32 texture_id -) { - if (!gfx || !pxl8_3d_init_zbuffer(gfx)) return; - - - pxl8_vec4 cv0 = pxl8_transform_vertex(gfx, v0); - pxl8_vec4 cv1 = pxl8_transform_vertex(gfx, v1); - pxl8_vec4 cv2 = pxl8_transform_vertex(gfx, v2); - - pxl8_clip_vert input[3] = { - {cv0, u0, v0f}, - {cv1, u1, v1f}, - {cv2, u2, v2f} - }; - - pxl8_clip_vert clipped[8]; - i32 clipped_count = 0; - - f32 near_clip = 0.1f; - pxl8_clip_near_plane(input, 3, clipped, &clipped_count, near_clip); - - if (clipped_count < 3) return; - - i32 guard_band = (gfx->framebuffer_width > gfx->framebuffer_height ? gfx->framebuffer_width : gfx->framebuffer_height) * 2; - - for (i32 tri = 0; tri < clipped_count - 2; tri++) { - pxl8_vec4 c0 = clipped[0].clip; - pxl8_vec4 c1 = clipped[tri + 1].clip; - pxl8_vec4 c2 = clipped[tri + 2].clip; - f32 tu0 = clipped[0].u; - f32 tv0 = clipped[0].v; - f32 tu1 = clipped[tri + 1].u; - f32 tv1 = clipped[tri + 1].v; - f32 tu2 = clipped[tri + 2].u; - f32 tv2 = clipped[tri + 2].v; - - i32 sx0, sy0, sx1, sy1, sx2, sy2; - f32 z0, z1, z2; - pxl8_project_to_screen(gfx, c0, &sx0, &sy0, &z0); - pxl8_project_to_screen(gfx, c1, &sx1, &sy1, &z1); - pxl8_project_to_screen(gfx, c2, &sx2, &sy2, &z2); - - f32 w0_recip = 1.0f / c0.w; - f32 w1_recip = 1.0f / c1.w; - f32 w2_recip = 1.0f / c2.w; - - pxl8_screen_vert screen_tri[3] = { - {sx0, sy0, z0, tu0 * w0_recip, tv0 * w0_recip, w0_recip}, - {sx1, sy1, z1, tu1 * w1_recip, tv1 * w1_recip, w1_recip}, - {sx2, sy2, z2, tu2 * w2_recip, tv2 * w2_recip, w2_recip} - }; - - pxl8_screen_vert temp1[8], temp2[8]; - i32 count1, count2; - - pxl8_clip_screen_axis(screen_tri, 3, temp1, &count1, -guard_band, true, true); - if (count1 < 3) continue; - pxl8_clip_screen_axis(temp1, count1, temp2, &count2, guard_band, true, false); - if (count2 < 3) continue; - pxl8_clip_screen_axis(temp2, count2, temp1, &count1, -guard_band, false, true); - if (count1 < 3) continue; - pxl8_clip_screen_axis(temp1, count1, temp2, &count2, guard_band, false, false); - if (count2 < 3) continue; - - for (i32 i = 1; i < count2 - 1; i++) { - pxl8_screen_vert v0 = temp2[0]; - pxl8_screen_vert v1 = temp2[i]; - pxl8_screen_vert v2 = temp2[i + 1]; - - pxl8_textured_vertex tv[3]; - tv[0].x = v0.x; tv[0].y = v0.y; tv[0].z = v0.z; - tv[0].u = v0.u; tv[0].v = v0.v; tv[0].w = v0.w; - tv[1].x = v1.x; tv[1].y = v1.y; tv[1].z = v1.z; - tv[1].u = v1.u; tv[1].v = v1.v; tv[1].w = v1.w; - tv[2].x = v2.x; tv[2].y = v2.y; tv[2].z = v2.z; - tv[2].u = v2.u; tv[2].v = v2.v; tv[2].w = v2.w; - - if (gfx->backface_culling) { - i32 cross = - (tv[1].x - tv[0].x) * (tv[2].y - tv[0].y) - (tv[1].y - tv[0].y) * (tv[2].x - tv[0].x); - if (cross >= 0) continue; - } - - if (tv[0].y > tv[1].y) { - pxl8_textured_vertex tmp = tv[0]; tv[0] = tv[1]; tv[1] = tmp; - } - if (tv[0].y > tv[2].y) { - pxl8_textured_vertex tmp = tv[0]; tv[0] = tv[2]; tv[2] = tmp; - } - if (tv[1].y > tv[2].y) { - pxl8_textured_vertex tmp = tv[1]; tv[1] = tv[2]; tv[2] = tmp; - } - - pxl8_scanline_textured_func fill_scanline = pxl8_fill_scanline_textured; - - if (tv[1].y == tv[2].y) { - pxl8_scan_triangle_textured(gfx, tv[0], tv[1], tv[2], texture_id, fill_scanline, true); - } else if (tv[0].y == tv[1].y) { - pxl8_scan_triangle_textured(gfx, tv[0], tv[1], tv[2], texture_id, fill_scanline, false); - } else { - f32 t = (f32)(tv[1].y - tv[0].y) / (f32)(tv[2].y - tv[0].y); - pxl8_textured_vertex v3; - v3.x = tv[0].x + (i32)(t * (tv[2].x - tv[0].x)); - v3.y = tv[1].y; - v3.z = tv[0].z + t * (tv[2].z - tv[0].z); - v3.u = tv[0].u + t * (tv[2].u - tv[0].u); - v3.v = tv[0].v + t * (tv[2].v - tv[0].v); - v3.w = tv[0].w + t * (tv[2].w - tv[0].w); - - pxl8_scan_triangle_textured(gfx, tv[0], tv[1], v3, texture_id, fill_scanline, true); - pxl8_scan_triangle_textured(gfx, tv[1], v3, tv[2], texture_id, fill_scanline, false); - } - } - } -} diff --git a/src/pxl8_gfx.h b/src/pxl8_gfx.h deleted file mode 100644 index 38f0797..0000000 --- a/src/pxl8_gfx.h +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once - -#include "pxl8_hal.h" -#include "pxl8_math.h" -#include "pxl8_types.h" - -typedef struct pxl8_gfx pxl8_gfx; - -typedef struct pxl8_vertex { - u32 color; - pxl8_vec3 normal; - pxl8_vec3 position; - f32 u, v; -} pxl8_vertex; - -typedef struct pxl8_triangle { - pxl8_vertex v[3]; - u32 texture_id; -} pxl8_triangle; - -#ifdef __cplusplus -extern "C" { -#endif - -pxl8_gfx* pxl8_gfx_create(const pxl8_hal* hal, void* platform_data, pxl8_pixel_mode mode, pxl8_resolution resolution); -void pxl8_gfx_destroy(pxl8_gfx* gfx); - -void pxl8_gfx_present(pxl8_gfx* gfx); -void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt); -void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx); - -pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx); -u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx); -u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx); -i32 pxl8_gfx_get_height(const pxl8_gfx* gfx); -u32 pxl8_gfx_get_palette_size(const pxl8_gfx* gfx); -pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx); -i32 pxl8_gfx_get_width(const pxl8_gfx* gfx); - -void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom); -void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp); -pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height); - -void pxl8_gfx_clear_textures(pxl8_gfx* gfx); -pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width, u32 height); -pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx); -pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path); -pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path); - -i32 pxl8_gfx_add_palette_cycle(pxl8_gfx* gfx, u8 start_index, u8 end_index, f32 speed); -void pxl8_gfx_clear_palette_cycles(pxl8_gfx* gfx); -void pxl8_gfx_color_ramp(pxl8_gfx* gfx, u8 start, u8 count, u32 from_color, u32 to_color); -void pxl8_gfx_cycle_palette(pxl8_gfx* gfx, u8 start, u8 count, i32 step); -void pxl8_gfx_fade_palette(pxl8_gfx* gfx, u8 start, u8 count, f32 amount, u32 target_color); -void pxl8_gfx_interpolate_palettes(pxl8_gfx* gfx, u32* palette1, u32* palette2, u8 start, u8 count, f32 t); -void pxl8_gfx_remove_palette_cycle(pxl8_gfx* gfx, i32 cycle_id); -void pxl8_gfx_set_palette_cycle_speed(pxl8_gfx* gfx, i32 cycle_id, f32 speed); -void pxl8_gfx_swap_palette(pxl8_gfx* gfx, u8 start, u8 count, u32* new_colors); - -void pxl8_circle(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color); -void pxl8_circle_fill(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color); -void pxl8_clear(pxl8_gfx* gfx, u32 color); -u32 pxl8_get_pixel(pxl8_gfx* gfx, i32 x, i32 y); -void pxl8_line(pxl8_gfx* gfx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color); -void pxl8_pixel(pxl8_gfx* gfx, i32 x, i32 y, u32 color); -void pxl8_rect(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color); -void pxl8_rect_fill(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color); -void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y); -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_draw_triangle_textured(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, f32 u0, f32 v0f, f32 u1, f32 v1f, f32 u2, f32 v2f, u32 texture_id); -const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx); -void pxl8_3d_set_affine_textures(pxl8_gfx* gfx, bool affine); -void pxl8_3d_set_backface_culling(pxl8_gfx* gfx, bool culling); -void pxl8_3d_set_model(pxl8_gfx* gfx, pxl8_mat4 mat); -void pxl8_3d_set_projection(pxl8_gfx* gfx, pxl8_mat4 mat); -void pxl8_3d_set_view(pxl8_gfx* gfx, pxl8_mat4 mat); -void pxl8_3d_set_wireframe(pxl8_gfx* gfx, bool wireframe); - -#ifdef __cplusplus -} -#endif diff --git a/src/pxl8_vfx.c b/src/pxl8_vfx.c deleted file mode 100644 index bfb1761..0000000 --- a/src/pxl8_vfx.c +++ /dev/null @@ -1,504 +0,0 @@ -#include "pxl8_vfx.h" - -#include -#include - -#include "pxl8_math.h" - -struct pxl8_particles { - pxl8_particle* particles; - pxl8_rng* rng; - u32 alive_count; - u32 count; - u32 max_count; - - f32 x, y; - f32 spread_x, spread_y; - - f32 drag; - f32 gravity_x, gravity_y; - f32 turbulence; - - f32 spawn_rate; - f32 spawn_timer; - - void (*render_fn)(pxl8_gfx* gfx, pxl8_particle* p, void* userdata); - void (*spawn_fn)(pxl8_particles* particles, pxl8_particle* p); - void (*update_fn)(pxl8_particle* p, f32 dt, void* userdata); - void* userdata; -}; - -void pxl8_vfx_plasma(pxl8_gfx* gfx, f32 time, f32 scale1, f32 scale2, u8 palette_offset) { - if (!gfx || !pxl8_gfx_get_framebuffer_indexed(gfx)) return; - - for (i32 y = 0; y < pxl8_gfx_get_height(gfx); y++) { - for (i32 x = 0; x < pxl8_gfx_get_width(gfx); x++) { - f32 v1 = sinf(x * scale1 + time); - f32 v2 = sinf(y * scale1 + time * 0.7f); - f32 v3 = sinf((x + y) * scale2 + time * 1.3f); - f32 v4 = sinf(sqrtf(x * x + y * y) * 0.05f + time * 0.5f); - - f32 v = v1 + v2 + v3 + v4; - f32 normalized = (1.0f + v / 4.0f) * 0.5f; - if (normalized < 0.0f) normalized = 0.0f; - if (normalized > 1.0f) normalized = 1.0f; - u8 color = palette_offset + (u8)(15.0f * normalized); - - pxl8_pixel(gfx, x, y, color); - } - } -} - -void pxl8_vfx_raster_bars(pxl8_gfx* gfx, pxl8_raster_bar* bars, u32 bar_count, f32 time) { - if (!gfx || !bars) return; - - for (u32 i = 0; i < bar_count; i++) { - pxl8_raster_bar* bar = &bars[i]; - if (bar->height <= 1) continue; - - f32 y = bar->base_y + bar->amplitude * sinf(time * bar->speed + bar->phase); - i32 y_int = (i32)y; - - for (i32 dy = 0; dy < bar->height; dy++) { - f32 position = (f32)dy / (f32)(bar->height - 1); - f32 distance_from_center = fabsf(position - 0.5f) * 2.0f; - - u8 color_idx; - if (distance_from_center < 0.3f) { - color_idx = bar->fade_color; - } else if (distance_from_center < 0.6f) { - color_idx = bar->fade_color - 1; - } else if (distance_from_center < 0.8f) { - color_idx = bar->color; - } else { - color_idx = bar->color - 1; - } - - pxl8_rect_fill(gfx, 0, y_int + dy, pxl8_gfx_get_width(gfx), 1, color_idx); - } - } -} - -void pxl8_vfx_rotozoom(pxl8_gfx* gfx, f32 angle, f32 zoom, i32 cx, i32 cy) { - if (!gfx || !pxl8_gfx_get_framebuffer_indexed(gfx)) return; - - f32 cos_a = cosf(angle); - f32 sin_a = sinf(angle); - - u8* temp_buffer = (u8*)malloc(pxl8_gfx_get_width(gfx) * pxl8_gfx_get_height(gfx)); - if (!temp_buffer) return; - - memcpy(temp_buffer, pxl8_gfx_get_framebuffer_indexed(gfx), pxl8_gfx_get_width(gfx) * pxl8_gfx_get_height(gfx)); - - for (i32 y = 0; y < pxl8_gfx_get_height(gfx); y++) { - for (i32 x = 0; x < pxl8_gfx_get_width(gfx); x++) { - f32 dx = x - cx; - f32 dy = y - cy; - - f32 src_x = cx + (dx * cos_a - dy * sin_a) / zoom; - f32 src_y = cy + (dx * sin_a + dy * cos_a) / zoom; - - i32 sx = (i32)src_x; - i32 sy = (i32)src_y; - - if (sx >= 0 && sx < pxl8_gfx_get_width(gfx) && sy >= 0 && sy < pxl8_gfx_get_height(gfx)) { - pxl8_gfx_get_framebuffer_indexed(gfx)[y * pxl8_gfx_get_width(gfx) + x] = temp_buffer[sy * pxl8_gfx_get_width(gfx) + sx]; - } - } - } - - free(temp_buffer); -} - -void pxl8_vfx_tunnel(pxl8_gfx* gfx, f32 time, f32 speed, f32 twist) { - if (!gfx || !pxl8_gfx_get_framebuffer_indexed(gfx)) return; - - f32 cx = pxl8_gfx_get_width(gfx) / 2.0f; - f32 cy = pxl8_gfx_get_height(gfx) / 2.0f; - u32 palette_size = pxl8_gfx_get_palette_size(gfx); - if (palette_size == 0) palette_size = 256; - - for (i32 y = 0; y < pxl8_gfx_get_height(gfx); y++) { - for (i32 x = 0; x < pxl8_gfx_get_width(gfx); x++) { - f32 dx = x - cx; - f32 dy = y - cy; - f32 dist = sqrtf(dx * dx + dy * dy); - - if (dist > 1.0f) { - f32 angle = atan2f(dy, dx); - f32 u = angle * 0.159f + twist * sinf(time * 0.5f); - f32 v = 256.0f / dist + time * speed; - - u8 tx = (u8)((i32)(u * 256.0f) & 0xFF); - u8 ty = (u8)((i32)(v * 256.0f) & 0xFF); - u8 pattern = tx ^ ty; - u8 color = (pattern / 8) % palette_size; - - pxl8_pixel(gfx, x, y, color); - } - } - } -} - -void pxl8_vfx_water_ripple(pxl8_gfx* gfx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping) { - if (!gfx || !height_map) return; - - i32 w = pxl8_gfx_get_width(gfx); - i32 h = pxl8_gfx_get_height(gfx); - - static f32* prev_height = NULL; - if (!prev_height) { - prev_height = (f32*)calloc(w * h, sizeof(f32)); - if (!prev_height) return; - } - - if (drop_x >= 0 && drop_x < w && drop_y >= 0 && drop_y < h) { - height_map[drop_y * w + drop_x] = 255.0f; - } - - for (i32 y = 1; y < h - 1; y++) { - for (i32 x = 1; x < w - 1; x++) { - i32 idx = y * w + x; - f32 sum = height_map[idx - w] + height_map[idx + w] + - height_map[idx - 1] + height_map[idx + 1]; - f32 avg = sum / 2.0f; - prev_height[idx] = (avg - prev_height[idx]) * damping; - } - } - - f32* temp = height_map; - height_map = prev_height; - prev_height = temp; -} - -pxl8_particles* pxl8_particles_create(u32 max_count, pxl8_rng* rng) { - pxl8_particles* particles = calloc(1, sizeof(pxl8_particles)); - if (!particles) return NULL; - - particles->particles = calloc(max_count, sizeof(pxl8_particle)); - if (!particles->particles) { - free(particles); - return NULL; - } - - particles->rng = rng; - particles->max_count = max_count; - particles->drag = 0.98f; - particles->gravity_y = 100.0f; - particles->spawn_rate = 10.0f; - - return particles; -} - -void pxl8_particles_destroy(pxl8_particles* particles) { - if (!particles) return; - free(particles->particles); - free(particles); -} - -void pxl8_particles_clear(pxl8_particles* particles) { - if (!particles || !particles->particles) return; - - for (u32 i = 0; i < particles->max_count; i++) { - particles->particles[i].life = 0; - particles->particles[i].flags = 0; - } - particles->alive_count = 0; - particles->spawn_timer = 0; -} - -void pxl8_particles_emit(pxl8_particles* particles, u32 count) { - if (!particles || !particles->particles) return; - - for (u32 i = 0; i < count && particles->alive_count < particles->max_count; i++) { - for (u32 j = 0; j < particles->max_count; j++) { - if (particles->particles[j].life <= 0) { - pxl8_particle* p = &particles->particles[j]; - p->life = 1.0f; - p->max_life = 1.0f; - p->x = particles->x + (pxl8_rng_f32(particles->rng) - 0.5f) * particles->spread_x; - p->y = particles->y + (pxl8_rng_f32(particles->rng) - 0.5f) * particles->spread_y; - p->z = 0; - p->vx = p->vy = p->vz = 0; - p->ax = particles->gravity_x; - p->ay = particles->gravity_y; - p->az = 0; - p->color = p->start_color = p->end_color = 15; - p->size = 1.0f; - p->angle = 0; - p->spin = 0; - p->flags = 1; - - if (particles->spawn_fn) { - particles->spawn_fn(particles, p); - } - - particles->alive_count++; - break; - } - } - } -} - -void pxl8_particles_render(pxl8_particles* particles, pxl8_gfx* gfx) { - if (!particles || !particles->particles || !gfx) return; - - for (u32 i = 0; i < particles->max_count; i++) { - pxl8_particle* p = &particles->particles[i]; - if (p->life > 0 && p->flags) { - if (particles->render_fn) { - particles->render_fn(gfx, p, particles->userdata); - } else { - i32 x = (i32)p->x; - i32 y = (i32)p->y; - if (x >= 0 && x < pxl8_gfx_get_width(gfx) && y >= 0 && y < pxl8_gfx_get_height(gfx)) { - pxl8_pixel(gfx, x, y, p->color); - } - } - } - } -} - -void pxl8_particles_update(pxl8_particles* particles, f32 dt) { - if (!particles || !particles->particles) return; - - if (particles->spawn_rate > 0.0f) { - particles->spawn_timer += dt; - f32 spawn_interval = 1.0f / particles->spawn_rate; - u32 max_spawns_per_frame = particles->max_count / 10; - if (max_spawns_per_frame < 1) max_spawns_per_frame = 1; - u32 spawn_count = 0; - while (particles->spawn_timer >= spawn_interval && spawn_count < max_spawns_per_frame) { - pxl8_particles_emit(particles, 1); - particles->spawn_timer -= spawn_interval; - spawn_count++; - } - } - - for (u32 i = 0; i < particles->max_count; i++) { - pxl8_particle* p = &particles->particles[i]; - if (p->life > 0) { - if (particles->update_fn) { - particles->update_fn(p, dt, particles->userdata); - } else { - p->vx += p->ax * dt; - p->vy += p->ay * dt; - p->vz += p->az * dt; - - p->vx *= particles->drag; - p->vy *= particles->drag; - p->vz *= particles->drag; - - p->x += p->vx * dt; - p->y += p->vy * dt; - p->z += p->vz * dt; - - p->angle += p->spin * dt; - } - - p->life -= dt / p->max_life; - if (p->life <= 0) { - p->flags = 0; - particles->alive_count--; - } - } - } -} - -void pxl8_vfx_explosion(pxl8_particles* particles, i32 x, i32 y, u32 color, f32 force) { - if (!particles) return; - - particles->x = x; - particles->y = y; - particles->spread_x = particles->spread_y = 2.0f; - particles->gravity_x = 0; - particles->gravity_y = 200.0f; - particles->drag = 0.95f; - particles->update_fn = NULL; - - for (u32 i = 0; i < 50 && i < particles->max_count; i++) { - pxl8_particle* p = &particles->particles[i]; - f32 angle = pxl8_rng_f32(particles->rng) * PXL8_TAU; - f32 speed = force * (0.5f + pxl8_rng_f32(particles->rng) * 0.5f); - - p->x = x; - p->y = y; - p->vx = cosf(angle) * speed; - p->vy = sinf(angle) * speed; - p->life = 1.0f; - p->max_life = 1.0f + pxl8_rng_f32(particles->rng); - p->color = color; - p->flags = 1; - } -} - -static void fire_spawn(pxl8_particles* particles, pxl8_particle* p) { - uintptr_t palette_start = (uintptr_t)particles->userdata; - p->start_color = palette_start + 6 + pxl8_rng_range(particles->rng, 0, 3); - p->end_color = palette_start; - p->color = p->start_color; - p->max_life = 1.5f + pxl8_rng_f32(particles->rng) * 1.5f; - p->vy = -80.0f - pxl8_rng_f32(particles->rng) * 120.0f; - p->vx = (pxl8_rng_f32(particles->rng) - 0.5f) * 40.0f; -} - -static void fire_update(pxl8_particle* p, f32 dt, void* userdata) { - (void)userdata; - p->vx += p->ax * dt; - p->vy += p->ay * dt; - p->vz += p->az * dt; - - p->x += p->vx * dt; - p->y += p->vy * dt; - p->z += p->vz * dt; - - f32 life_ratio = 1.0f - (p->life / p->max_life); - if (p->start_color >= p->end_color) { - u8 color_range = p->start_color - p->end_color; - p->color = p->start_color - (u8)(life_ratio * color_range); - } else { - u8 color_range = p->end_color - p->start_color; - p->color = p->start_color + (u8)(life_ratio * color_range); - } -} - -void pxl8_vfx_fire(pxl8_particles* particles, i32 x, i32 y, i32 width, u8 palette_start) { - if (!particles) return; - - particles->x = x; - particles->y = y; - particles->spread_x = width; - particles->spread_y = 4.0f; - particles->gravity_x = 0; - particles->gravity_y = -100.0f; - particles->drag = 0.97f; - particles->spawn_rate = 120.0f; - particles->spawn_fn = fire_spawn; - particles->update_fn = fire_update; - particles->userdata = (void*)(uintptr_t)palette_start; -} - -static void rain_spawn(pxl8_particles* particles, pxl8_particle* p) { - p->start_color = 27 + pxl8_rng_range(particles->rng, 0, 3); - p->end_color = 29; - p->color = p->start_color; - p->max_life = 2.0f; - p->vy = 200.0f + pxl8_rng_f32(particles->rng) * 100.0f; -} - -void pxl8_vfx_rain(pxl8_particles* particles, i32 width, f32 wind) { - if (!particles) return; - - particles->x = width / 2.0f; - particles->y = -10; - particles->spread_x = width; - particles->spread_y = 0; - particles->gravity_x = wind; - particles->gravity_y = 300.0f; - particles->drag = 1.0f; - particles->spawn_rate = 100.0f; - particles->spawn_fn = rain_spawn; - particles->update_fn = NULL; -} - -static void smoke_spawn(pxl8_particles* particles, pxl8_particle* p) { - uintptr_t base_color = (uintptr_t)particles->userdata; - p->start_color = base_color; - p->end_color = base_color + 4; - p->color = p->start_color; - p->max_life = 3.0f + pxl8_rng_f32(particles->rng) * 2.0f; - p->vy = -20.0f - pxl8_rng_f32(particles->rng) * 30.0f; - p->vx = (pxl8_rng_f32(particles->rng) - 0.5f) * 20.0f; - p->size = 1.0f + pxl8_rng_f32(particles->rng) * 2.0f; -} - -void pxl8_vfx_smoke(pxl8_particles* particles, i32 x, i32 y, u8 color) { - if (!particles) return; - - particles->x = x; - particles->y = y; - particles->spread_x = 5.0f; - particles->spread_y = 5.0f; - particles->gravity_x = 0; - particles->gravity_y = -50.0f; - particles->drag = 0.96f; - particles->spawn_rate = 20.0f; - particles->spawn_fn = smoke_spawn; - particles->update_fn = NULL; - particles->userdata = (void*)(uintptr_t)color; -} - -static void snow_spawn(pxl8_particles* particles, pxl8_particle* p) { - p->start_color = 8 + pxl8_rng_range(particles->rng, 0, 3); - p->end_color = 10; - p->color = p->start_color; - p->max_life = 4.0f; - p->vx = (pxl8_rng_f32(particles->rng) - 0.5f) * 20.0f; - p->vy = 30.0f + pxl8_rng_f32(particles->rng) * 20.0f; -} - -void pxl8_vfx_snow(pxl8_particles* particles, i32 width, f32 wind) { - if (!particles) return; - - particles->x = width / 2.0f; - particles->y = -10; - particles->spread_x = width; - particles->spread_y = 0; - particles->gravity_x = wind; - particles->gravity_y = 30.0f; - particles->drag = 1.0f; - particles->spawn_rate = 30.0f; - particles->spawn_fn = snow_spawn; - particles->update_fn = NULL; -} - -static void sparks_spawn(pxl8_particles* particles, pxl8_particle* p) { - uintptr_t base_color = (uintptr_t)particles->userdata; - p->start_color = base_color; - p->end_color = base_color > 2 ? base_color - 2 : 0; - p->color = p->start_color; - p->max_life = 0.5f + pxl8_rng_f32(particles->rng) * 1.0f; - - f32 angle = pxl8_rng_f32(particles->rng) * PXL8_TAU; - f32 speed = 100.0f + pxl8_rng_f32(particles->rng) * 200.0f; - p->vx = cosf(angle) * speed; - p->vy = sinf(angle) * speed - 50.0f; -} - -void pxl8_vfx_sparks(pxl8_particles* particles, i32 x, i32 y, u32 color) { - if (!particles) return; - - particles->x = x; - particles->y = y; - particles->spread_x = 2.0f; - particles->spread_y = 2.0f; - particles->gravity_x = 0; - particles->gravity_y = 100.0f; - particles->drag = 0.97f; - particles->spawn_rate = 40.0f; - particles->spawn_fn = sparks_spawn; - particles->update_fn = NULL; - particles->userdata = (void*)(uintptr_t)color; -} - -void pxl8_vfx_starfield(pxl8_particles* particles, f32 speed, f32 spread) { - if (!particles) return; - - particles->spread_x = particles->spread_y = spread; - particles->gravity_x = particles->gravity_y = 0; - particles->drag = 1.0f; - particles->spawn_rate = 0; - particles->update_fn = NULL; - - for (u32 i = 0; i < particles->max_count; i++) { - pxl8_particle* p = &particles->particles[i]; - p->x = pxl8_rng_f32(particles->rng) * spread * 2.0f - spread; - p->y = pxl8_rng_f32(particles->rng) * spread * 2.0f - spread; - p->z = pxl8_rng_f32(particles->rng) * spread; - p->vz = -speed; - p->life = 1000.0f; - p->max_life = 1000.0f; - p->color = 8 + pxl8_rng_range(particles->rng, 0, 8); - p->flags = 1; - } -} diff --git a/src/pxl8_vfx.h b/src/pxl8_vfx.h deleted file mode 100644 index bee53b6..0000000 --- a/src/pxl8_vfx.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include "pxl8_gfx.h" -#include "pxl8_rng.h" -#include "pxl8_types.h" - -typedef struct pxl8_particles pxl8_particles; - -typedef struct pxl8_particle { - f32 angle; - f32 ax, ay, az; - u32 color; - u32 end_color; - u8 flags; - f32 life; - f32 max_life; - f32 size; - f32 spin; - u32 start_color; - f32 vx, vy, vz; - f32 x, y, z; -} pxl8_particle; - -typedef struct pxl8_raster_bar { - f32 amplitude; - f32 base_y; - u32 color; - u32 fade_color; - i32 height; - f32 phase; - f32 speed; -} pxl8_raster_bar; - -#ifdef __cplusplus -extern "C" { -#endif - -pxl8_particles* pxl8_particles_create(u32 max_count, pxl8_rng* rng); -void pxl8_particles_destroy(pxl8_particles* particles); - -void pxl8_particles_clear(pxl8_particles* particles); -void pxl8_particles_emit(pxl8_particles* particles, u32 count); -void pxl8_particles_render(pxl8_particles* particles, pxl8_gfx* gfx); -void pxl8_particles_update(pxl8_particles* particles, f32 dt); - -void pxl8_vfx_explosion(pxl8_particles* particles, i32 x, i32 y, u32 color, f32 force); -void pxl8_vfx_fire(pxl8_particles* particles, i32 x, i32 y, i32 width, u8 palette_start); -void pxl8_vfx_rain(pxl8_particles* particles, i32 width, f32 wind); -void pxl8_vfx_smoke(pxl8_particles* particles, i32 x, i32 y, u8 color); -void pxl8_vfx_snow(pxl8_particles* particles, i32 width, f32 wind); -void pxl8_vfx_sparks(pxl8_particles* particles, i32 x, i32 y, u32 color); -void pxl8_vfx_starfield(pxl8_particles* particles, f32 speed, f32 spread); - -void pxl8_vfx_plasma(pxl8_gfx* gfx, f32 time, f32 scale1, f32 scale2, u8 palette_offset); -void pxl8_vfx_raster_bars(pxl8_gfx* gfx, pxl8_raster_bar* bars, u32 bar_count, f32 time); -void pxl8_vfx_rotozoom(pxl8_gfx* gfx, f32 angle, f32 zoom, i32 cx, i32 cy); -void pxl8_vfx_tunnel(pxl8_gfx* gfx, f32 time, f32 speed, f32 twist); -void pxl8_vfx_water_ripple(pxl8_gfx* gfx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping); - -#ifdef __cplusplus -} -#endif