From 415d42405790492652aaf8f17f903afaa506588b Mon Sep 17 00:00:00 2001 From: asrael Date: Sat, 17 Jan 2026 22:52:36 -0600 Subject: [PATCH] add networking, 3d improvements, reorganize src structure --- client/src/core/pxl8_io.h | 126 -- client/src/gfx/pxl8_gfx3d.h | 33 - client/src/lua/pxl8/vfx.lua | 65 -- demo/main.fnl | 21 +- demo/mod/first_person3d.fnl | 393 +++++++ demo/mod/menu.fnl | 11 +- demo/mod/music.fnl | 5 +- demo/mod/sky.fnl | 250 ++++ demo/mod/vfx.fnl | 75 ++ demo/mod/worldgen.fnl | 224 ---- pxl8.sh | 127 +- server/.cargo/config.toml | 2 + server/.gitignore | 1 + server/Cargo.lock | 1090 ++++++++++++++++++ server/Cargo.toml | 34 + server/build.rs | 23 + server/rust-toolchain.toml | 2 + server/src/allocator.rs | 36 + server/src/components.rs | 45 + server/src/lib.rs | 18 + server/src/main.rs | 142 +++ server/src/simulation.rs | 133 +++ server/src/transport.rs | 281 +++++ {client/src => src}/asset/pxl8_ase.c | 0 {client/src => src}/asset/pxl8_ase.h | 0 {client/src => src}/asset/pxl8_cart.c | 0 {client/src => src}/asset/pxl8_cart.h | 0 {client/src => src}/asset/pxl8_embed.h | 41 +- {client/src => src}/asset/pxl8_save.c | 0 {client/src => src}/asset/pxl8_save.h | 0 {client/src => src}/core/pxl8.c | 0 src/core/pxl8_bytes.c | 234 ++++ src/core/pxl8_bytes.h | 251 ++++ {client/src => src}/core/pxl8_io.c | 0 src/core/pxl8_io.h | 38 + {client/src => src}/core/pxl8_log.c | 0 {client/src => src}/core/pxl8_log.h | 0 {client/src => src}/core/pxl8_macros.h | 0 {client/src => src}/core/pxl8_rng.c | 0 {client/src => src}/core/pxl8_rng.h | 0 {client/src => src}/core/pxl8_sys.h | 0 {client/src => src}/core/pxl8_types.h | 5 +- {client/src => src}/game/pxl8_game.h | 0 {client/src => src}/game/pxl8_gui.c | 0 {client/src => src}/game/pxl8_gui.h | 0 {client/src => src}/game/pxl8_replay.c | 0 {client/src => src}/game/pxl8_replay.h | 0 {client/src => src}/gfx/pxl8_3d_camera.c | 0 {client/src => src}/gfx/pxl8_3d_camera.h | 0 {client/src => src}/gfx/pxl8_anim.c | 0 {client/src => src}/gfx/pxl8_anim.h | 0 {client/src => src}/gfx/pxl8_atlas.c | 0 {client/src => src}/gfx/pxl8_atlas.h | 0 {client/src => src}/gfx/pxl8_backend.h | 0 src/gfx/pxl8_blend.c | 194 ++++ src/gfx/pxl8_blend.h | 39 + {client/src => src}/gfx/pxl8_blit.c | 0 {client/src => src}/gfx/pxl8_blit.h | 0 {client/src => src}/gfx/pxl8_color.h | 2 +- {client/src => src}/gfx/pxl8_colormap.c | 57 +- {client/src => src}/gfx/pxl8_colormap.h | 0 {client/src => src}/gfx/pxl8_cpu.c | 475 +++++++- {client/src => src}/gfx/pxl8_cpu.h | 33 +- {client/src => src}/gfx/pxl8_dither.c | 0 {client/src => src}/gfx/pxl8_dither.h | 0 {client/src => src}/gfx/pxl8_font.c | 0 {client/src => src}/gfx/pxl8_font.h | 0 {client/src => src}/gfx/pxl8_gfx.c | 140 ++- {client/src => src}/gfx/pxl8_gfx.h | 55 + {client/src => src}/gfx/pxl8_gfx2d.h | 0 src/gfx/pxl8_gfx3d.h | 72 ++ {client/src => src}/gfx/pxl8_mesh.c | 0 {client/src => src}/gfx/pxl8_mesh.h | 2 +- {client/src => src}/gfx/pxl8_palette.c | 2 +- {client/src => src}/gfx/pxl8_palette.h | 2 +- {client/src => src}/gfx/pxl8_particles.c | 0 {client/src => src}/gfx/pxl8_particles.h | 0 {client/src => src}/gfx/pxl8_tilemap.c | 0 {client/src => src}/gfx/pxl8_tilemap.h | 0 {client/src => src}/gfx/pxl8_tilesheet.c | 0 {client/src => src}/gfx/pxl8_tilesheet.h | 0 {client/src => src}/gfx/pxl8_transition.c | 0 {client/src => src}/gfx/pxl8_transition.h | 0 {client/src => src}/hal/pxl8_hal.h | 4 +- {client/src => src}/hal/pxl8_sdl3.c | 25 +- {client/src => src}/hal/pxl8_sdl3.h | 0 {client/src => src}/lua/pxl8.lua | 136 ++- {client/src => src}/lua/pxl8/anim.lua | 0 src/lua/pxl8/bytes.lua | 44 + {client/src => src}/lua/pxl8/core.lua | 8 + src/lua/pxl8/effects.lua | 32 + {client/src => src}/lua/pxl8/gfx2d.lua | 0 {client/src => src}/lua/pxl8/gfx3d.lua | 61 +- {client/src => src}/lua/pxl8/gui.lua | 0 {client/src => src}/lua/pxl8/input.lua | 0 {client/src => src}/lua/pxl8/math.lua | 0 src/lua/pxl8/net.lua | 182 +++ {client/src => src}/lua/pxl8/particles.lua | 0 {client/src => src}/lua/pxl8/sfx.lua | 4 +- {client/src => src}/lua/pxl8/tilemap.lua | 0 {client/src => src}/lua/pxl8/transition.lua | 0 {client/src => src}/lua/pxl8/world.lua | 0 {client/src => src}/math/pxl8_math.c | 8 + {client/src => src}/math/pxl8_math.h | 10 + {client/src => src}/math/pxl8_simd.h | 8 - src/net/pxl8_net.c | 312 +++++ src/net/pxl8_net.h | 54 + src/net/pxl8_protocol.c | 124 ++ src/net/pxl8_protocol.h | 93 ++ {client/src => src}/script/pxl8_repl.c | 0 {client/src => src}/script/pxl8_repl.h | 0 {client/src => src}/script/pxl8_script.c | 0 {client/src => src}/script/pxl8_script.h | 0 {client/src => src}/script/pxl8_script_ffi.h | 140 ++- {client/src => src}/sfx/pxl8_sfx.c | 0 {client/src => src}/sfx/pxl8_sfx.h | 0 {client/src => src}/world/pxl8_bsp.c | 49 +- {client/src => src}/world/pxl8_bsp.h | 0 {client/src => src}/world/pxl8_gen.c | 0 {client/src => src}/world/pxl8_gen.h | 0 {client/src => src}/world/pxl8_world.c | 6 - {client/src => src}/world/pxl8_world.h | 0 122 files changed, 5358 insertions(+), 721 deletions(-) delete mode 100644 client/src/core/pxl8_io.h delete mode 100644 client/src/gfx/pxl8_gfx3d.h delete mode 100644 client/src/lua/pxl8/vfx.lua create mode 100644 demo/mod/first_person3d.fnl create mode 100644 demo/mod/sky.fnl create mode 100644 demo/mod/vfx.fnl delete mode 100644 demo/mod/worldgen.fnl create mode 100644 server/.cargo/config.toml create mode 100644 server/.gitignore create mode 100644 server/Cargo.lock create mode 100644 server/Cargo.toml create mode 100644 server/build.rs create mode 100644 server/rust-toolchain.toml create mode 100644 server/src/allocator.rs create mode 100644 server/src/components.rs create mode 100644 server/src/lib.rs create mode 100644 server/src/main.rs create mode 100644 server/src/simulation.rs create mode 100644 server/src/transport.rs rename {client/src => src}/asset/pxl8_ase.c (100%) rename {client/src => src}/asset/pxl8_ase.h (100%) rename {client/src => src}/asset/pxl8_cart.c (100%) rename {client/src => src}/asset/pxl8_cart.h (100%) rename {client/src => src}/asset/pxl8_embed.h (70%) rename {client/src => src}/asset/pxl8_save.c (100%) rename {client/src => src}/asset/pxl8_save.h (100%) rename {client/src => src}/core/pxl8.c (100%) create mode 100644 src/core/pxl8_bytes.c create mode 100644 src/core/pxl8_bytes.h rename {client/src => src}/core/pxl8_io.c (100%) create mode 100644 src/core/pxl8_io.h rename {client/src => src}/core/pxl8_log.c (100%) rename {client/src => src}/core/pxl8_log.h (100%) rename {client/src => src}/core/pxl8_macros.h (100%) rename {client/src => src}/core/pxl8_rng.c (100%) rename {client/src => src}/core/pxl8_rng.h (100%) rename {client/src => src}/core/pxl8_sys.h (100%) rename {client/src => src}/core/pxl8_types.h (96%) rename {client/src => src}/game/pxl8_game.h (100%) rename {client/src => src}/game/pxl8_gui.c (100%) rename {client/src => src}/game/pxl8_gui.h (100%) rename {client/src => src}/game/pxl8_replay.c (100%) rename {client/src => src}/game/pxl8_replay.h (100%) rename {client/src => src}/gfx/pxl8_3d_camera.c (100%) rename {client/src => src}/gfx/pxl8_3d_camera.h (100%) rename {client/src => src}/gfx/pxl8_anim.c (100%) rename {client/src => src}/gfx/pxl8_anim.h (100%) rename {client/src => src}/gfx/pxl8_atlas.c (100%) rename {client/src => src}/gfx/pxl8_atlas.h (100%) rename {client/src => src}/gfx/pxl8_backend.h (100%) create mode 100644 src/gfx/pxl8_blend.c create mode 100644 src/gfx/pxl8_blend.h rename {client/src => src}/gfx/pxl8_blit.c (100%) rename {client/src => src}/gfx/pxl8_blit.h (100%) rename {client/src => src}/gfx/pxl8_color.h (97%) rename {client/src => src}/gfx/pxl8_colormap.c (61%) rename {client/src => src}/gfx/pxl8_colormap.h (100%) rename {client/src => src}/gfx/pxl8_cpu.c (70%) rename {client/src => src}/gfx/pxl8_cpu.h (83%) rename {client/src => src}/gfx/pxl8_dither.c (100%) rename {client/src => src}/gfx/pxl8_dither.h (100%) rename {client/src => src}/gfx/pxl8_font.c (100%) rename {client/src => src}/gfx/pxl8_font.h (100%) rename {client/src => src}/gfx/pxl8_gfx.c (83%) rename {client/src => src}/gfx/pxl8_gfx.h (54%) rename {client/src => src}/gfx/pxl8_gfx2d.h (100%) create mode 100644 src/gfx/pxl8_gfx3d.h rename {client/src => src}/gfx/pxl8_mesh.c (100%) rename {client/src => src}/gfx/pxl8_mesh.h (97%) rename {client/src => src}/gfx/pxl8_palette.c (99%) rename {client/src => src}/gfx/pxl8_palette.h (96%) rename {client/src => src}/gfx/pxl8_particles.c (100%) rename {client/src => src}/gfx/pxl8_particles.h (100%) rename {client/src => src}/gfx/pxl8_tilemap.c (100%) rename {client/src => src}/gfx/pxl8_tilemap.h (100%) rename {client/src => src}/gfx/pxl8_tilesheet.c (100%) rename {client/src => src}/gfx/pxl8_tilesheet.h (100%) rename {client/src => src}/gfx/pxl8_transition.c (100%) rename {client/src => src}/gfx/pxl8_transition.h (100%) rename {client/src => src}/hal/pxl8_hal.h (85%) rename {client/src => src}/hal/pxl8_sdl3.c (94%) rename {client/src => src}/hal/pxl8_sdl3.h (100%) rename {client/src => src}/lua/pxl8.lua (67%) rename {client/src => src}/lua/pxl8/anim.lua (100%) create mode 100644 src/lua/pxl8/bytes.lua rename {client/src => src}/lua/pxl8/core.lua (90%) create mode 100644 src/lua/pxl8/effects.lua rename {client/src => src}/lua/pxl8/gfx2d.lua (100%) rename {client/src => src}/lua/pxl8/gfx3d.lua (68%) rename {client/src => src}/lua/pxl8/gui.lua (100%) rename {client/src => src}/lua/pxl8/input.lua (100%) rename {client/src => src}/lua/pxl8/math.lua (100%) create mode 100644 src/lua/pxl8/net.lua rename {client/src => src}/lua/pxl8/particles.lua (100%) rename {client/src => src}/lua/pxl8/sfx.lua (98%) rename {client/src => src}/lua/pxl8/tilemap.lua (100%) rename {client/src => src}/lua/pxl8/transition.lua (100%) rename {client/src => src}/lua/pxl8/world.lua (100%) rename {client/src => src}/math/pxl8_math.c (96%) rename {client/src => src}/math/pxl8_math.h (88%) rename {client/src => src}/math/pxl8_simd.h (97%) create mode 100644 src/net/pxl8_net.c create mode 100644 src/net/pxl8_net.h create mode 100644 src/net/pxl8_protocol.c create mode 100644 src/net/pxl8_protocol.h rename {client/src => src}/script/pxl8_repl.c (100%) rename {client/src => src}/script/pxl8_repl.h (100%) rename {client/src => src}/script/pxl8_script.c (100%) rename {client/src => src}/script/pxl8_script.h (100%) rename {client/src => src}/script/pxl8_script_ffi.h (78%) rename {client/src => src}/sfx/pxl8_sfx.c (100%) rename {client/src => src}/sfx/pxl8_sfx.h (100%) rename {client/src => src}/world/pxl8_bsp.c (92%) rename {client/src => src}/world/pxl8_bsp.h (100%) rename {client/src => src}/world/pxl8_gen.c (100%) rename {client/src => src}/world/pxl8_gen.h (100%) rename {client/src => src}/world/pxl8_world.c (98%) rename {client/src => src}/world/pxl8_world.h (100%) diff --git a/client/src/core/pxl8_io.h b/client/src/core/pxl8_io.h deleted file mode 100644 index f4e73bb..0000000 --- a/client/src/core/pxl8_io.h +++ /dev/null @@ -1,126 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "pxl8_types.h" - -typedef struct { - const u8* bytes; - u32 offset; - u32 size; - bool overflow; -} pxl8_stream; - -static inline pxl8_stream pxl8_stream_create(const u8* bytes, u32 size) { - return (pxl8_stream){ - .bytes = bytes, - .offset = 0, - .size = size, - .overflow = false - }; -} - -static inline bool pxl8_stream_can_read(const pxl8_stream* stream, u32 count) { - return !stream->overflow && stream->offset + count <= stream->size; -} - -static inline bool pxl8_stream_has_overflow(const pxl8_stream* stream) { - return stream->overflow; -} - -static inline void pxl8_stream_seek(pxl8_stream* stream, u32 offset) { - stream->offset = offset; -} - -static inline u32 pxl8_stream_position(const pxl8_stream* stream) { - return stream->offset; -} - -static inline u8 pxl8_read_u8(pxl8_stream* stream) { - if (stream->offset + 1 > stream->size) { stream->overflow = true; return 0; } - return stream->bytes[stream->offset++]; -} - -static inline u16 pxl8_read_u16(pxl8_stream* stream) { - if (stream->offset + 2 > stream->size) { stream->overflow = true; return 0; } - u16 val = (u16)stream->bytes[stream->offset] | ((u16)stream->bytes[stream->offset + 1] << 8); - stream->offset += 2; - return val; -} - -static inline u32 pxl8_read_u32(pxl8_stream* stream) { - if (stream->offset + 4 > stream->size) { stream->overflow = true; return 0; } - u32 val = (u32)stream->bytes[stream->offset] | - ((u32)stream->bytes[stream->offset + 1] << 8) | - ((u32)stream->bytes[stream->offset + 2] << 16) | - ((u32)stream->bytes[stream->offset + 3] << 24); - stream->offset += 4; - return val; -} - -static inline i16 pxl8_read_i16(pxl8_stream* stream) { - return (i16)pxl8_read_u16(stream); -} - -static inline i32 pxl8_read_i32(pxl8_stream* stream) { - return (i32)pxl8_read_u32(stream); -} - -static inline f32 pxl8_read_f32(pxl8_stream* stream) { - u32 val = pxl8_read_u32(stream); - f32 result; - memcpy(&result, &val, sizeof(f32)); - return result; -} - -static inline void pxl8_read_bytes(pxl8_stream* stream, void* dest, u32 count) { - if (stream->offset + count > stream->size) { stream->overflow = true; return; } - for (u32 i = 0; i < count; i++) { - ((u8*)dest)[i] = stream->bytes[stream->offset++]; - } -} - -static inline void pxl8_skip_bytes(pxl8_stream* stream, u32 count) { - if (stream->offset + count > stream->size) { stream->overflow = true; return; } - stream->offset += count; -} - -static inline const u8* pxl8_read_ptr(pxl8_stream* stream, u32 count) { - if (stream->offset + count > stream->size) { stream->overflow = true; return NULL; } - const u8* ptr = &stream->bytes[stream->offset]; - stream->offset += count; - return ptr; -} - -#ifdef __cplusplus -extern "C" { -#endif - -pxl8_result pxl8_io_create_directory(const char* path); -bool pxl8_io_file_exists(const char* path); -void pxl8_io_free_binary_data(u8* data); -void pxl8_io_free_file_content(char* content); -f64 pxl8_io_get_file_modified_time(const char* path); -pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, size_t* size); -pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size); -pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, size_t size); -pxl8_result pxl8_io_write_file(const char* path, const char* content, size_t size); - -bool pxl8_key_down(const pxl8_input_state* input, const char* key_name); -bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name); -bool pxl8_key_released(const pxl8_input_state* input, const char* key_name); - -i32 pxl8_mouse_dx(const pxl8_input_state* input); -i32 pxl8_mouse_dy(const pxl8_input_state* input); -bool pxl8_mouse_pressed(const pxl8_input_state* input, i32 button); -bool pxl8_mouse_released(const pxl8_input_state* input, i32 button); -i32 pxl8_mouse_wheel_x(const pxl8_input_state* input); -i32 pxl8_mouse_wheel_y(const pxl8_input_state* input); -i32 pxl8_mouse_x(const pxl8_input_state* input); -i32 pxl8_mouse_y(const pxl8_input_state* input); - -#ifdef __cplusplus -} -#endif diff --git a/client/src/gfx/pxl8_gfx3d.h b/client/src/gfx/pxl8_gfx3d.h deleted file mode 100644 index 0c4f2df..0000000 --- a/client/src/gfx/pxl8_gfx3d.h +++ /dev/null @@ -1,33 +0,0 @@ -#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/lua/pxl8/vfx.lua b/client/src/lua/pxl8/vfx.lua deleted file mode 100644 index a4372b4..0000000 --- a/client/src/lua/pxl8/vfx.lua +++ /dev/null @@ -1,65 +0,0 @@ -local ffi = require("ffi") -local C = ffi.C -local core = require("pxl8.core") - -local vfx = {} - -function vfx.raster_bars(bars, time) - local c_bars = ffi.new("pxl8_raster_bar[?]", #bars) - for i, bar in ipairs(bars) do - c_bars[i-1].base_y = bar.base_y or 0 - c_bars[i-1].amplitude = bar.amplitude or 10 - c_bars[i-1].height = bar.height or 5 - c_bars[i-1].speed = bar.speed or 1.0 - c_bars[i-1].phase = bar.phase or 0 - c_bars[i-1].color = bar.color or 15 - c_bars[i-1].fade_color = bar.fade_color or bar.color or 15 - end - C.pxl8_vfx_raster_bars(core.gfx, c_bars, #bars, time) -end - -function vfx.plasma(time, scale1, scale2, palette_offset) - C.pxl8_vfx_plasma(core.gfx, time, scale1 or 0.05, scale2 or 0.03, palette_offset or 0) -end - -function vfx.rotozoom(angle, zoom, cx, cy) - local width = C.pxl8_gfx_get_width(core.gfx) - local height = C.pxl8_gfx_get_height(core.gfx) - C.pxl8_vfx_rotozoom(core.gfx, angle, zoom, cx or width/2, cy or height/2) -end - -function vfx.tunnel(time, speed, twist) - C.pxl8_vfx_tunnel(core.gfx, time, speed or 2.0, twist or 0.5) -end - -function vfx.explosion(ps, x, y, color, force) - C.pxl8_vfx_explosion(ps, x, y, color or 15, force or 200.0) -end - -function vfx.fire(ps, x, y, width, palette_start) - C.pxl8_vfx_fire(ps, x, y, width or 50, palette_start or 64) -end - -function vfx.rain(ps, width, wind) - local w = width or C.pxl8_gfx_get_width(core.gfx) - C.pxl8_vfx_rain(ps, w, wind or 0.0) -end - -function vfx.smoke(ps, x, y, color) - C.pxl8_vfx_smoke(ps, x, y, color or 8) -end - -function vfx.snow(ps, width, wind) - local w = width or C.pxl8_gfx_get_width(core.gfx) - C.pxl8_vfx_snow(ps, w, wind or 10.0) -end - -function vfx.sparks(ps, x, y, color) - C.pxl8_vfx_sparks(ps, x, y, color or 15) -end - -function vfx.starfield(ps, speed, spread) - C.pxl8_vfx_starfield(ps, speed or 5.0, spread or 500.0) -end - -return vfx diff --git a/demo/main.fnl b/demo/main.fnl index d078be3..8a63553 100644 --- a/demo/main.fnl +++ b/demo/main.fnl @@ -1,7 +1,7 @@ (local pxl8 (require :pxl8)) (local menu (require :mod.menu)) (local music (require :mod.music)) -(local worldgen (require :mod.worldgen)) +(local first_person3d (require :mod.first_person3d)) (var time 0) (var active-demo :logo) @@ -34,13 +34,12 @@ (set particles (pxl8.create_particles 1000)) (set particles2 (pxl8.create_particles 500)) (music.init) - (music.start) - (worldgen.init))) + (first_person3d.init))) (global update (fn [dt] (when (pxl8.key_pressed "escape") (menu.toggle) - (when (= active-demo :worldgen) + (when (= active-demo :first_person3d) (pxl8.set_relative_mouse_mode (not (menu.is-paused))))) (when (not (menu.is-paused)) @@ -50,9 +49,9 @@ (transition:update dt) (when (transition:is_complete) (when transition-pending - (when (and (= active-demo :worldgen) (not= transition-pending :worldgen)) + (when (and (= active-demo :first_person3d) (not= transition-pending :first_person3d)) (pxl8.set_relative_mouse_mode false)) - (when (and (not= active-demo :worldgen) (= transition-pending :worldgen)) + (when (and (not= active-demo :first_person3d) (= transition-pending :first_person3d)) (pxl8.set_relative_mouse_mode true)) (set active-demo transition-pending) (set transition-pending nil) @@ -69,12 +68,14 @@ (when (pxl8.key_pressed "5") (switch-demo :fire)) (when (pxl8.key_pressed "6") (switch-demo :rain)) (when (pxl8.key_pressed "7") (switch-demo :snow)) - (when (pxl8.key_pressed "8") (switch-demo :worldgen)) + (when (pxl8.key_pressed "8") (switch-demo :first_person3d)) (when (pxl8.key_pressed "=") (set use-famicube-palette? (not use-famicube-palette?)) (local palette-path (if use-famicube-palette? "res/palettes/famicube.ase" "res/sprites/pxl8_logo.ase")) (pxl8.load_palette palette-path)) + (music.update dt) + (case active-demo :logo (do (set logo-x (+ logo-x (* logo-dx dt))) @@ -91,9 +92,7 @@ (when (> logo-y 296) (set logo-y 296) (set logo-dy (- (math.abs logo-dy))))) - :worldgen (worldgen.update dt)) - - (music.update dt) + :first_person3d (first_person3d.update dt)) (when particles (particles:update dt)) @@ -173,7 +172,7 @@ (set snow-init? true)) (particles:render))) - :worldgen (worldgen.frame) + :first_person3d (first_person3d.frame) _ (pxl8.clear 0)) diff --git a/demo/mod/first_person3d.fnl b/demo/mod/first_person3d.fnl new file mode 100644 index 0000000..e45e089 --- /dev/null +++ b/demo/mod/first_person3d.fnl @@ -0,0 +1,393 @@ +(local pxl8 (require :pxl8)) +(local effects (require :pxl8.effects)) +(local net (require :pxl8.net)) +(local sky (require :mod.sky)) + +(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) + +(local sim-tick-rate 60) +(local sim-dt (/ 1.0 sim-tick-rate)) +(local history-size 128) +(local correction-threshold 1.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 light-time 0) +(var network nil) +(var smooth-cam-x 1000) +(var smooth-cam-z 1000) +(var velocity-y 0) +(var world nil) +(var fps-avg 0) +(var fps-sample-count 0) + +(local FIREBALL_COLOR 184) + +(fn init-fireball-palette [] + (for [i 0 7] + (let [t (/ i 7) + r (math.floor (+ 0xFF (* t 0))) + g (math.floor (+ 0x60 (* t (- 0xE0 0x60)))) + b (math.floor (+ 0x10 (* t (- 0x80 0x10))))] + (pxl8.set_palette_rgb (+ FIREBALL_COLOR i) r g b)))) + +(var client-tick 0) +(var last-processed-tick 0) +(var time-accumulator 0) +(var position-history {}) +(var pending-inputs {}) + +(fn history-idx [tick] + (+ 1 (% tick history-size))) + +(fn store-position [tick x z yaw] + (tset position-history (history-idx tick) {:tick tick :x x :z z :yaw yaw})) + +(fn get-position [tick] + (let [entry (. position-history (history-idx tick))] + (when (and entry (= entry.tick tick)) + entry))) + +(fn store-pending-input [tick input] + (tset pending-inputs (history-idx tick) {:tick tick :input input})) + +(fn get-pending-input [tick] + (let [entry (. pending-inputs (history-idx tick))] + (when (and entry (= entry.tick tick)) + entry.input))) + +(fn apply-movement [x z yaw input] + (var new-x x) + (var new-z z) + + (let [move-forward (or input.move_y 0) + move-right (or input.move_x 0)] + (when (or (not= move-forward 0) (not= move-right 0)) + (let [forward-x (- (math.sin yaw)) + forward-z (- (math.cos yaw)) + right-x (math.cos yaw) + right-z (- (math.sin yaw)) + len (math.sqrt (+ (* move-forward move-forward) (* move-right move-right))) + norm-forward (/ move-forward len) + norm-right (/ move-right len) + move-delta (* move-speed sim-dt)] + (set new-x (+ new-x (* move-delta (+ (* forward-x norm-forward) (* right-x norm-right))))) + (set new-z (+ new-z (* move-delta (+ (* forward-z norm-forward) (* right-z norm-right)))))))) + + (values new-x new-z)) +(fn init [] + (set camera (pxl8.create_camera_3d)) + (set world (pxl8.create_world)) + (sky.generate-stars 12345) + (init-fireball-palette) + + (set network (net.Net.new {:port 7777})) + (when network + (network:connect) + (network:spawn cam-x cam-y cam-z cam-yaw cam-pitch)) + + (let [result (world:generate { + :type pxl8.PROCGEN_ROOMS + :width 64 + :height 64 + :seed 42 + :min_room_size 5 + :max_room_size 10 + :num_rooms 20})] + (if (< result 0) + (pxl8.error (.. "Failed to generate rooms - result: " result)) + (let [floor-tex (pxl8.procgen_tex {:name "floor" + :seed 11111 + :width 64 + :height 64 + :base_color 19}) + wall-tex (pxl8.procgen_tex {:name "wall" + :seed 12345 + :width 64 + :height 64 + :base_color 4}) + sky-tex (pxl8.create_texture [0] 1 1)] + + (let [result (world:apply_textures [ + {:name "floor" + :texture_id floor-tex + :rule (fn [normal] (> normal.y 0.7))} + {:name "ceiling" + :texture_id sky-tex + :rule (fn [normal] (< normal.y -0.7))} + {:name "wall" + :texture_id wall-tex + :rule (fn [normal] (and (<= normal.y 0.7) (>= normal.y -0.7)))}])] + (when (< result 0) + (pxl8.error (.. "Failed to apply textures - result: " result)))))))) + +(fn sample-input [] + (var move-forward 0) + (var move-right 0) + + (when (pxl8.key_pressed "`") + (set auto-run? (not auto-run?)) + (when (and auto-run? (pxl8.key_down "w")) + (set auto-run-cancel-key "w"))) + (when (and auto-run? (not auto-run-cancel-key) (or (pxl8.key_down "w") (pxl8.key_down "s"))) + (set auto-run? false) + (when (pxl8.key_down "s") + (set auto-run-cancel-key "s"))) + (when (and auto-run-cancel-key (not (pxl8.key_down auto-run-cancel-key))) + (set auto-run-cancel-key nil)) + + (when (or (pxl8.key_down "w") auto-run?) + (set move-forward (+ move-forward 1))) + (when (and (pxl8.key_down "s") (not= auto-run-cancel-key "s")) + (set move-forward (- move-forward 1))) + (when (pxl8.key_down "a") + (set move-right (- move-right 1))) + (when (pxl8.key_down "d") + (set move-right (+ move-right 1))) + + {:move_x move-right + :move_y move-forward + :look_dx (pxl8.mouse_dx) + :look_dy (pxl8.mouse_dy)}) + +(fn reconcile [server-tick server-x server-z] + (let [predicted (get-position server-tick)] + (when predicted + (let [dx (- predicted.x server-x) + dz (- predicted.z server-z) + error (math.sqrt (+ (* dx dx) (* dz dz)))] + (when (> error correction-threshold) + (set cam-x server-x) + (set cam-z server-z) + + (for [t (+ server-tick 1) client-tick] + (let [input (get-pending-input t) + hist (get-position t)] + (when (and input hist) + (let [(new-x new-z) (apply-movement cam-x cam-z hist.yaw input)] + (set cam-x new-x) + (set cam-z new-z) + (store-position t cam-x cam-z hist.yaw)))))))))) + +(fn update [dt] + (let [fps (pxl8.get_fps)] + (set fps-sample-count (+ fps-sample-count 1)) + (set fps-avg (+ (* fps-avg (/ (- fps-sample-count 1) fps-sample-count)) + (/ fps fps-sample-count))) + (when (>= fps-sample-count 120) + (set fps-sample-count 0) + (set fps-avg 0))) + + (when (world:is_loaded) + (let [input (sample-input) + grid-max (* grid-size cell-size) + movement-yaw cam-yaw] + + (set time-accumulator (+ time-accumulator dt)) + + (while (>= time-accumulator sim-dt) + (set time-accumulator (- time-accumulator sim-dt)) + (set client-tick (+ client-tick 1)) + + (store-pending-input client-tick input) + + (let [(new-x new-z) (apply-movement cam-x cam-z movement-yaw input)] + (when (and (>= new-x 0) (<= new-x grid-max) + (>= new-z 0) (<= new-z grid-max)) + (let [(resolved-x _ 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))) + + (store-position client-tick cam-x cam-z movement-yaw))) + + (when cursor-look? + (set cam-yaw (- cam-yaw (* input.look_dx cursor-sensitivity))) + (set cam-pitch (math.max (- max-pitch) + (math.min max-pitch + (- cam-pitch (* input.look_dy cursor-sensitivity)))))) + + (when (and (not cursor-look?) (pxl8.key_down "up")) + (set cam-pitch (math.min max-pitch (+ cam-pitch (* turn-speed dt))))) + (when (and (not cursor-look?) (pxl8.key_down "down")) + (set cam-pitch (math.max (- max-pitch) (- cam-pitch (* turn-speed dt))))) + (when (and (not cursor-look?) (pxl8.key_down "left")) + (set cam-yaw (+ cam-yaw (* turn-speed dt)))) + (when (and (not cursor-look?) (pxl8.key_down "right")) + (set cam-yaw (- cam-yaw (* turn-speed dt)))) + + (when network + (let [(ok err) (pcall (fn [] + (network:send_input {:move_x input.move_x + :move_y input.move_y + :look_dx input.look_dx + :look_dy input.look_dy + :yaw movement-yaw + :tick client-tick}) + (network:update dt) + (when (network:poll) + (let [snapshot (network:snapshot)] + (when (and snapshot (> snapshot.tick last-processed-tick)) + (set last-processed-tick snapshot.tick) + (let [player-id (network:player_id)] + (when (> player-id 0) + (let [curr (network:entity_userdata player-id)] + (when curr + (let [srv-x (pxl8.unpack_f32_be curr 0) + srv-z (pxl8.unpack_f32_be curr 8)] + (reconcile snapshot.tick srv-x srv-z)))))))))))] + (when (not ok) + (pxl8.error (.. "Network error: " err))))) + + (set smooth-cam-x (+ (* smooth-cam-x (- 1 cam-smoothing)) (* cam-x cam-smoothing))) + (set smooth-cam-z (+ (* smooth-cam-z (- 1 cam-smoothing)) (* cam-z cam-smoothing))) + + (when (and (pxl8.key_pressed "space") grounded?) + (set velocity-y jump-force) + (set grounded? false)) + + (set velocity-y (+ velocity-y (* gravity dt))) + (set cam-y (+ cam-y (* velocity-y dt))) + + (when (<= cam-y ground-y) + (when (not grounded?) + (set land-squash land-squash-amount)) + (set cam-y ground-y) + (set velocity-y 0) + (set grounded? true)) + + (when (< land-squash 0) + (set land-squash (math.min 0 (+ land-squash (* land-recovery-speed dt))))) + + (let [moving (or (not= input.move_x 0) (not= input.move_y 0))] + (if (and moving grounded?) + (set bob-time (+ bob-time (* dt bob-speed))) + (let [target-phase (* (math.floor (/ bob-time math.pi)) math.pi)] + (set bob-time (+ (* bob-time 0.8) (* target-phase 0.2)))))) + + (set light-time (+ light-time (* dt 0.15)))))) + +(fn frame [] + (pxl8.clear 1) + + (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) + aspect (/ (pxl8.get_width) (pxl8.get_height))] + + (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 [light-pulse (+ 0.7 (* 0.3 (math.sin (* light-time 2)))) + forward-x (- (math.sin cam-yaw)) + forward-z (- (math.cos cam-yaw)) + light-x (+ smooth-cam-x (* 150 forward-x) (* 50 (math.cos light-time))) + light-z (+ smooth-cam-z (* 150 forward-z) (* 50 (math.sin light-time))) + light-y (+ eye-y 30)] + (pxl8.begin_frame_3d camera { + :ambient 80 + :celestial_dir [0.5 -0.8 0.3] + :celestial_intensity 0.5 + :lights [{:x light-x :y light-y :z light-z + :r 255 :g 200 :b 150 + :intensity (* 255 light-pulse) + :radius 400}]}) + (pxl8.clear_depth) + + (sky.update-gradient 1 2 6 6 10 18) + (sky.render smooth-cam-x eye-y smooth-cam-z) + (pxl8.clear_depth) + + (world:render [smooth-cam-x eye-y smooth-cam-z]) + + (pxl8.end_frame_3d) + + (let [dx (- light-x smooth-cam-x) + dy (- light-y eye-y) + dz (- light-z smooth-cam-z) + dist (math.sqrt (+ (* dx dx) (* dy dy) (* dz dz)))] + (when (> dist 1) + (let [inv-dist (/ 1 dist) + dir-x (* dx inv-dist) + dir-y (* dy inv-dist) + dir-z (* dz inv-dist) + cos-yaw (math.cos cam-yaw) + sin-yaw (math.sin cam-yaw) + cos-pitch (math.cos cam-pitch) + sin-pitch (math.sin cam-pitch) + rx (+ (* dir-x cos-yaw) (* dir-z sin-yaw)) + rz (+ (* (- dir-x) sin-yaw) (* dir-z cos-yaw)) + ry (- (* dir-y cos-pitch) (* rz sin-pitch)) + fz (+ (* dir-y sin-pitch) (* rz cos-pitch))] + (when (> fz 0.01) + (let [width (pxl8.get_width) + height (pxl8.get_height) + fov 1.047 + half-fov-tan (math.tan (* fov 0.5)) + ndc-x (/ rx (* fz half-fov-tan aspect)) + ndc-y (/ ry (* fz half-fov-tan)) + sx (math.floor (* (+ 1 ndc-x) 0.5 width)) + sy (math.floor (* (- 1 ndc-y) 0.5 height)) + screen-size (/ 400 dist) + base-radius (math.max 2 (math.min 12 (math.floor screen-size))) + pulse-int (math.floor (* 180 light-pulse))] + (when (and (>= sx 0) (< sx width) (>= sy 0) (< sy height)) + (effects.glows [ + {:x sx :y sy :radius (+ base-radius 2) :intensity (/ pulse-int 5) :color (+ FIREBALL_COLOR 1) :shape effects.GLOW_CIRCLE} + {:x sx :y sy :radius base-radius :intensity pulse-int :color (+ FIREBALL_COLOR 5) :shape effects.GLOW_DIAMOND}])))))))) + + (sky.render-stars cam-yaw cam-pitch 1.0) + + (let [cx (/ (pxl8.get_width) 2) + cy (/ (pxl8.get_height) 2) + crosshair-size 4 + red-color 18] + (pxl8.line (- cx crosshair-size) cy (+ cx crosshair-size) cy red-color) + (pxl8.line cx (- cy crosshair-size) cx (+ cy crosshair-size) red-color)) + + (pxl8.text (.. "fps: " (string.format "%.0f" fps-avg)) 5 5 12) + (pxl8.text (.. "pos: " (string.format "%.0f" cam-x) "," + (string.format "%.0f" cam-y) "," + (string.format "%.0f" cam-z)) 5 15 12)))) + +{:init init + :update update + :frame frame} diff --git a/demo/mod/menu.fnl b/demo/mod/menu.fnl index 34ee9e0..d8b9834 100644 --- a/demo/mod/menu.fnl +++ b/demo/mod/menu.fnl @@ -1,4 +1,5 @@ (local pxl8 (require :pxl8)) +(local music (require :mod.music)) (var paused false) (var gui nil) @@ -36,12 +37,18 @@ (when gui (gui:begin_frame) - (pxl8.gui_window 200 100 240 140 "pxl8 demo") + (pxl8.gui_window 200 100 240 180 "pxl8 demo") (when (gui:button 1 215 145 210 32 "Resume") (hide)) - (when (gui:button 2 215 185 210 32 "Quit") + (let [music-label (if (music.is-playing) "Music: On" "Music: Off")] + (when (gui:button 3 215 185 210 32 music-label) + (if (music.is-playing) + (music.stop) + (music.start)))) + + (when (gui:button 2 215 225 210 32 "Quit") (pxl8.quit)) (if (gui:is_hovering) diff --git a/demo/mod/music.fnl b/demo/mod/music.fnl index cdbfc6a..a4e83e4 100644 --- a/demo/mod/music.fnl +++ b/demo/mod/music.fnl @@ -101,7 +101,7 @@ (fn update [dt] (when playing (set time (+ time dt)) - (when (>= time step-duration) + (while (>= time step-duration) (set time (- time step-duration)) (local melody-idx (+ 1 (% step (length melody)))) @@ -125,4 +125,5 @@ :start start :stop stop :update update - :is-playing (fn [] playing)} + :is-playing (fn [] playing) + :get-step (fn [] step)} diff --git a/demo/mod/sky.fnl b/demo/mod/sky.fnl new file mode 100644 index 0000000..49da5a5 --- /dev/null +++ b/demo/mod/sky.fnl @@ -0,0 +1,250 @@ +(local ffi (require :ffi)) +(local pxl8 (require :pxl8)) +(local effects (require :pxl8.effects)) + +(local SKY_GRADIENT_START 144) +(local SKY_GRADIENT_COUNT 16) +(local sky-radius 900) +(local sky-segments 16) +(local sky-rings 16) +(local max-theta (* math.pi 0.55)) + +(local STAR_COUNT 200) +(local TINY_STAR_COUNT 5000) +(local STAR_SILVER_START 160) +(local STAR_BLUE_START 168) +(local STAR_RED_START 176) + +(var sky-mesh nil) +(var last-gradient-key nil) +(var stars []) +(var tiny-stars []) + +(fn generate-sky-gradient [zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b] + (for [i 0 (- SKY_GRADIENT_COUNT 1)] + (let [t (/ i (- SKY_GRADIENT_COUNT 1)) + r (math.floor (+ zenith-r (* t (- horizon-r zenith-r)))) + g (math.floor (+ zenith-g (* t (- horizon-g zenith-g)))) + b (math.floor (+ zenith-b (* t (- horizon-b zenith-b))))] + (pxl8.set_palette_rgb (+ SKY_GRADIENT_START i) r g b)))) + +(fn create-sky-dome [] + (let [verts [] + indices []] + + (for [i 0 (- sky-rings 1)] + (let [theta0 (* (/ i sky-rings) max-theta) + theta1 (* (/ (+ i 1) sky-rings) max-theta) + sin-t0 (math.sin theta0) + cos-t0 (math.cos theta0) + sin-t1 (math.sin theta1) + cos-t1 (math.cos theta1) + y0 (* sky-radius cos-t0) + y1 (* sky-radius cos-t1) + r0 (* sky-radius sin-t0) + r1 (* sky-radius sin-t1) + t0 (/ i sky-rings) + t1 (/ (+ i 1) sky-rings) + c0 (math.floor (+ SKY_GRADIENT_START (* t0 (- SKY_GRADIENT_COUNT 1)) 0.5)) + c1 (math.floor (+ SKY_GRADIENT_START (* t1 (- SKY_GRADIENT_COUNT 1)) 0.5))] + + (for [j 0 (- sky-segments 1)] + (let [phi0 (* (/ j sky-segments) math.pi 2) + phi1 (* (/ (+ j 1) sky-segments) math.pi 2) + cos-p0 (math.cos phi0) + sin-p0 (math.sin phi0) + cos-p1 (math.cos phi1) + sin-p1 (math.sin phi1) + x00 (* r0 cos-p0) z00 (* r0 sin-p0) + x01 (* r0 cos-p1) z01 (* r0 sin-p1) + x10 (* r1 cos-p0) z10 (* r1 sin-p0) + x11 (* r1 cos-p1) z11 (* r1 sin-p1) + nx00 (- (* sin-t0 cos-p0)) ny00 (- cos-t0) nz00 (- (* sin-t0 sin-p0)) + nx01 (- (* sin-t0 cos-p1)) ny01 (- cos-t0) nz01 (- (* sin-t0 sin-p1)) + nx10 (- (* sin-t1 cos-p0)) ny10 (- cos-t1) nz10 (- (* sin-t1 sin-p0)) + nx11 (- (* sin-t1 cos-p1)) ny11 (- cos-t1) nz11 (- (* sin-t1 sin-p1)) + base-idx (# verts)] + + (if (= i 0) + (do + ;; First ring is degenerate - just a triangle from pole + ;; Vertices: v00 (pole, c0), v11 (bottom-right, c1), v10 (bottom-left, c1) + (table.insert verts {:x x00 :y y0 :z z00 :nx nx00 :ny ny00 :nz nz00 :color c0 :light 255}) + (table.insert verts {:x x11 :y y1 :z z11 :nx nx11 :ny ny11 :nz nz11 :color c1 :light 255}) + (table.insert verts {:x x10 :y y1 :z z10 :nx nx10 :ny ny10 :nz nz10 :color c1 :light 255}) + ;; Triangle: base, base+2, base+1 + (table.insert indices base-idx) + (table.insert indices (+ base-idx 2)) + (table.insert indices (+ base-idx 1))) + (do + ;; Regular quad: v00 (top-left), v01 (top-right), v11 (bottom-right), v10 (bottom-left) + (table.insert verts {:x x00 :y y0 :z z00 :nx nx00 :ny ny00 :nz nz00 :color c0 :light 255}) + (table.insert verts {:x x01 :y y0 :z z01 :nx nx01 :ny ny01 :nz nz01 :color c0 :light 255}) + (table.insert verts {:x x11 :y y1 :z z11 :nx nx11 :ny ny11 :nz nz11 :color c1 :light 255}) + (table.insert verts {:x x10 :y y1 :z z10 :nx nx10 :ny ny10 :nz nz10 :color c1 :light 255}) + ;; push_quad(base, base+3, base+2, base+1) = triangles (base,base+3,base+2) and (base,base+2,base+1) + (table.insert indices base-idx) + (table.insert indices (+ base-idx 3)) + (table.insert indices (+ base-idx 2)) + (table.insert indices base-idx) + (table.insert indices (+ base-idx 2)) + (table.insert indices (+ base-idx 1)))))))) + + (set sky-mesh (pxl8.create_mesh verts indices)))) + +(fn update-gradient [zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b] + (let [key (.. zenith-r "," zenith-g "," zenith-b "," horizon-r "," horizon-g "," horizon-b)] + (when (not= key last-gradient-key) + (generate-sky-gradient zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b) + (set last-gradient-key key)))) + +(fn palette-ramp [start c0 c1] + (let [r0 (bit.rshift (bit.band c0 0xFF0000) 16) + g0 (bit.rshift (bit.band c0 0x00FF00) 8) + b0 (bit.band c0 0x0000FF) + r1 (bit.rshift (bit.band c1 0xFF0000) 16) + g1 (bit.rshift (bit.band c1 0x00FF00) 8) + b1 (bit.band c1 0x0000FF)] + (for [i 0 7] + (let [t (/ i 7) + r (math.floor (+ r0 (* t (- r1 r0)))) + g (math.floor (+ g0 (* t (- g1 g0)))) + b (math.floor (+ b0 (* t (- b1 b0))))] + (pxl8.set_palette_rgb (+ start i) r g b))))) + +(fn init-star-palette [] + (palette-ramp STAR_SILVER_START 0x707888 0xFFFFFF) ;; silver + (palette-ramp STAR_BLUE_START 0x5070B0 0xD0E8FF) ;; blue + (palette-ramp STAR_RED_START 0x802020 0xFF9090)) ;; red + +(fn generate-stars [seed] + (set stars []) + (set tiny-stars []) + (init-star-palette) + (pxl8.rng_seed seed) + + (for [i 1 STAR_COUNT] + (let [theta (math.acos (- 1 (* (pxl8.rng_f32) 0.85))) + phi (* (pxl8.rng_f32) math.pi 2) + brightness (pxl8.rng_range 1 4) + color-type (pxl8.rng_range 0 100) + shade (pxl8.rng_range 0 5) + color (if (< color-type 3) (+ STAR_RED_START shade) + (< color-type 15) (+ STAR_BLUE_START shade) + (+ STAR_SILVER_START shade)) + sin-t (math.sin theta) + cos-t (math.cos theta)] + (table.insert stars {:dx (* sin-t (math.cos phi)) + :dy cos-t + :dz (* sin-t (math.sin phi)) + :brightness brightness + :color color}))) + + (pxl8.rng_seed (+ seed 0xCAFEBABE)) + (for [i 1 TINY_STAR_COUNT] + (let [theta (math.acos (- 1 (* (pxl8.rng_f32) 0.95))) + phi (* (pxl8.rng_f32) math.pi 2) + brightness (+ 25 (pxl8.rng_range 0 40)) + shade (pxl8.rng_range 0 3) + color-type (pxl8.rng_range 0 100) + color (if (< color-type 15) (+ STAR_BLUE_START shade) + (+ STAR_SILVER_START shade)) + sin-t (math.sin theta) + cos-t (math.cos theta)] + (table.insert tiny-stars {:dx (* sin-t (math.cos phi)) + :dy cos-t + :dz (* sin-t (math.sin phi)) + :brightness brightness + :color color})))) + +(fn project-direction [dir-x dir-y dir-z yaw pitch width height] + (let [cos-yaw (math.cos yaw) + sin-yaw (math.sin yaw) + cos-pitch (math.cos pitch) + sin-pitch (math.sin pitch) + rotated-x (+ (* dir-x cos-yaw) (* dir-z sin-yaw)) + rotated-z (+ (* (- dir-x) sin-yaw) (* dir-z cos-yaw)) + rotated-y (- (* dir-y cos-pitch) (* rotated-z sin-pitch)) + final-z (+ (* dir-y sin-pitch) (* rotated-z cos-pitch))] + (when (> final-z 0.01) + (let [fov 1.047 + aspect (/ width height) + half-fov-tan (math.tan (* fov 0.5)) + ndc-x (/ rotated-x (* final-z half-fov-tan aspect)) + ndc-y (/ rotated-y (* final-z half-fov-tan))] + (when (and (>= ndc-x -1) (<= ndc-x 1) (>= ndc-y -1) (<= ndc-y 1)) + {:x (math.floor (* (+ 1 ndc-x) 0.5 width)) + :y (math.floor (* (- 1 ndc-y) 0.5 height))}))))) + +(fn render-stars [yaw pitch intensity] + (when (> intensity 0) + (let [width (pxl8.get_width) + height (pxl8.get_height) + glows [] + fade-sq (* intensity intensity)] + + (each [_ star (ipairs tiny-stars)] + (let [screen (project-direction star.dx star.dy star.dz yaw pitch width height)] + (when screen + (let [int (math.floor (* star.brightness fade-sq))] + (when (> int 8) + (table.insert glows {:x screen.x :y screen.y + :radius 1 + :intensity int + :color star.color + :shape effects.GLOW_CIRCLE})))))) + + (each [_ star (ipairs stars)] + (let [screen (project-direction star.dx star.dy star.dz yaw pitch width height)] + (when screen + (let [base-int (math.floor (* star.brightness 50 fade-sq 1.5))] + (if (>= star.brightness 4) + (do + (table.insert glows {:x screen.x :y screen.y + :radius 4 + :intensity (math.floor (/ base-int 4)) + :color star.color + :shape effects.GLOW_CIRCLE}) + (table.insert glows {:x screen.x :y screen.y + :radius 2 + :intensity base-int + :color star.color + :shape effects.GLOW_DIAMOND})) + (>= star.brightness 3) + (do + (table.insert glows {:x screen.x :y screen.y + :radius 3 + :intensity (math.floor (/ base-int 4)) + :color star.color + :shape effects.GLOW_CIRCLE}) + (table.insert glows {:x screen.x :y screen.y + :radius 2 + :intensity base-int + :color star.color + :shape effects.GLOW_DIAMOND})) + (>= star.brightness 2) + (table.insert glows {:x screen.x :y screen.y + :radius 2 + :intensity base-int + :color star.color + :shape effects.GLOW_DIAMOND}) + (table.insert glows {:x screen.x :y screen.y + :radius 1 + :intensity (math.floor (* base-int 0.7)) + :color star.color + :shape effects.GLOW_CIRCLE})))))) + + (when (> (length glows) 0) + (effects.glows glows))))) + +(fn render [cam-x cam-y cam-z] + (when (not sky-mesh) (create-sky-dome)) + (when sky-mesh + (pxl8.draw_mesh sky-mesh {:x cam-x :y cam-y :z cam-z :passthrough true}))) + +{:render render + :render-stars render-stars + :generate-stars generate-stars + :update-gradient update-gradient + :SKY_GRADIENT_START SKY_GRADIENT_START + :SKY_GRADIENT_COUNT SKY_GRADIENT_COUNT} diff --git a/demo/mod/vfx.fnl b/demo/mod/vfx.fnl new file mode 100644 index 0000000..446de1c --- /dev/null +++ b/demo/mod/vfx.fnl @@ -0,0 +1,75 @@ +(local vfx {}) + +(fn vfx.explosion [ps x y ?opts] + (let [opts (or ?opts {}) + color (or opts.color 208) + force (or opts.force 200)] + (ps:set_position x y) + (ps:set_colors color (+ color 15)) + (ps:set_velocity (- force) force (- force) force) + (ps:set_gravity 0 100) + (ps:set_life 0.3 0.8) + (ps:set_size 1 3) + (ps:set_drag 0.98) + (ps:set_spawn_rate 0) + (ps:emit (or opts.count 50)))) + +(fn vfx.fire [ps x y ?opts] + (let [opts (or ?opts {}) + width (or opts.width 50) + color (or opts.color 208)] + (ps:set_position x y) + (ps:set_spread width 5) + (ps:set_colors color (+ color 15)) + (ps:set_velocity -20 20 -80 -40) + (ps:set_gravity 0 -30) + (ps:set_life 0.5 1.5) + (ps:set_size 1 2) + (ps:set_turbulence 30) + (ps:set_drag 0.95) + (ps:set_spawn_rate (or opts.rate 50)))) + +(fn vfx.rain [ps width ?opts] + (let [opts (or ?opts {}) + wind (or opts.wind 0) + color (or opts.color 153)] + (ps:set_position (/ width 2) -10) + (ps:set_spread width 0) + (ps:set_colors color (+ color 3)) + (ps:set_velocity (- wind 10) (+ wind 10) 300 400) + (ps:set_gravity 0 200) + (ps:set_life 1 2) + (ps:set_size 1 1) + (ps:set_drag 1) + (ps:set_spawn_rate (or opts.rate 100)))) + +(fn vfx.smoke [ps x y ?opts] + (let [opts (or ?opts {}) + color (or opts.color 248)] + (ps:set_position x y) + (ps:set_spread 10 5) + (ps:set_colors color (+ color 7)) + (ps:set_velocity -15 15 -30 -10) + (ps:set_gravity 0 -20) + (ps:set_life 1 3) + (ps:set_size 2 4) + (ps:set_turbulence 20) + (ps:set_drag 0.98) + (ps:set_spawn_rate (or opts.rate 20)))) + +(fn vfx.snow [ps width ?opts] + (let [opts (or ?opts {}) + wind (or opts.wind 10) + color (or opts.color 15)] + (ps:set_position (/ width 2) -10) + (ps:set_spread width 0) + (ps:set_colors color color) + (ps:set_velocity (- wind 20) (+ wind 20) 30 60) + (ps:set_gravity 0 10) + (ps:set_life 3 6) + (ps:set_size 1 2) + (ps:set_turbulence 15) + (ps:set_drag 0.99) + (ps:set_spawn_rate (or opts.rate 30)))) + +vfx diff --git a/demo/mod/worldgen.fnl b/demo/mod/worldgen.fnl deleted file mode 100644 index 88e1358..0000000 --- a/demo/mod/worldgen.fnl +++ /dev/null @@ -1,224 +0,0 @@ -(local pxl8 (require :pxl8)) - -(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) -(var velocity-y 0) -(var world nil) - -(fn init [] - (set camera (pxl8.create_camera_3d)) - (set world (pxl8.create_world)) - (let [result (world:generate { - :type pxl8.PROCGEN_ROOMS - :width 64 - :height 64 - :seed 42 - :min_room_size 5 - :max_room_size 10 - :num_rooms 20})] - (if (< result 0) - (pxl8.error (.. "Failed to generate rooms - result: " result)) - (let [floor-tex (pxl8.procgen_tex {:name "floor" - :seed 11111 - :width 64 - :height 64 - :base_color 19}) - ceiling-tex (pxl8.procgen_tex {:name "ceiling" - :seed 22222 - :width 64 - :height 64 - :base_color 1}) - wall-tex (pxl8.procgen_tex {:name "wall" - :seed 12345 - :width 64 - :height 64 - :base_color 4})] - - (let [result (world:apply_textures [ - {:name "floor" - :texture_id floor-tex - :rule (fn [normal] (> normal.y 0.7))} - {:name "ceiling" - :texture_id ceiling-tex - :rule (fn [normal] (< normal.y -0.7))} - {:name "wall" - :texture_id wall-tex - :rule (fn [normal] (and (<= normal.y 0.7) (>= normal.y -0.7)))}])] - (when (< result 0) - (pxl8.error (.. "Failed to apply textures - result: " result)))))))) - -(fn update [dt] - (when (pxl8.key_pressed "`") - (set auto-run? (not auto-run?)) - (when (and auto-run? (pxl8.key_down "w")) - (set auto-run-cancel-key "w"))) - (when (and auto-run? (not auto-run-cancel-key) (or (pxl8.key_down "w") (pxl8.key_down "s"))) - (set auto-run? false) - (when (pxl8.key_down "s") - (set auto-run-cancel-key "s"))) - (when (and auto-run-cancel-key (not (pxl8.key_down auto-run-cancel-key))) - (set auto-run-cancel-key nil)) - - (when (world:is_loaded) - (let [forward-x (- (math.sin cam-yaw)) - forward-z (- (math.cos cam-yaw)) - right-x (math.cos cam-yaw) - right-z (- (math.sin cam-yaw)) - grid-max (* grid-size cell-size) - move-delta (* move-speed dt)] - - (var moving false) - (var move-forward 0) - (var move-right 0) - - (when (or (pxl8.key_down "w") auto-run?) - (set move-forward (+ move-forward 1))) - - (when (and (pxl8.key_down "s") (not= auto-run-cancel-key "s")) - (set move-forward (- move-forward 1))) - - (when (pxl8.key_down "a") - (set move-right (- move-right 1))) - - (when (pxl8.key_down "d") - (set move-right (+ move-right 1))) - - (set moving (or (not= move-forward 0) (not= move-right 0))) - - (var new-x cam-x) - (var new-z cam-z) - - (when moving - (let [len (math.sqrt (+ (* move-forward move-forward) (* move-right move-right))) - norm-forward (/ move-forward len) - norm-right (/ move-right len)] - (set new-x (+ new-x (* move-delta (+ (* forward-x norm-forward) (* right-x norm-right))))) - (set new-z (+ new-z (* move-delta (+ (* forward-z norm-forward) (* right-z norm-right))))))) - - (when (and (>= new-x 0) (<= new-x grid-max) - (>= new-z 0) (<= new-z grid-max)) - (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))) - - (set smooth-cam-x (+ (* smooth-cam-x (- 1 cam-smoothing)) (* cam-x cam-smoothing))) - (set smooth-cam-z (+ (* smooth-cam-z (- 1 cam-smoothing)) (* cam-z cam-smoothing))) - - (when cursor-look? - (let [dx (pxl8.mouse_dx) - dy (pxl8.mouse_dy)] - (set cam-yaw (- cam-yaw (* dx cursor-sensitivity))) - (set cam-pitch (math.max (- max-pitch) - (math.min max-pitch - (- cam-pitch (* dy cursor-sensitivity))))))) - - (when (and (not cursor-look?) (pxl8.key_down "left")) - (set cam-yaw (+ cam-yaw (* turn-speed dt)))) - - (when (and (not cursor-look?) (pxl8.key_down "right")) - (set cam-yaw (- cam-yaw (* turn-speed dt)))) - - (when (and (not cursor-look?) (pxl8.key_down "up")) - (set cam-pitch (math.min max-pitch (+ cam-pitch (* turn-speed dt))))) - - (when (and (not cursor-look?) (pxl8.key_down "down")) - (set cam-pitch (math.max (- max-pitch) (- cam-pitch (* turn-speed dt))))) - - (when (and (pxl8.key_pressed "space") grounded?) - (set velocity-y jump-force) - (set grounded? false)) - - (set velocity-y (+ velocity-y (* gravity dt))) - (set cam-y (+ cam-y (* velocity-y dt))) - - (when (<= cam-y ground-y) - (when (not grounded?) - (set land-squash land-squash-amount)) - (set cam-y ground-y) - (set velocity-y 0) - (set grounded? true)) - - (when (< land-squash 0) - (set land-squash (math.min 0 (+ land-squash (* land-recovery-speed dt))))) - - (if (and moving grounded?) - (set bob-time (+ bob-time (* dt bob-speed))) - (let [target-phase (* (math.floor (/ bob-time math.pi)) math.pi)] - (set bob-time (+ (* bob-time 0.8) (* target-phase 0.2)))))))) - -(fn frame [] - (pxl8.clear 0) - - (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) - aspect (/ (pxl8.get_width) (pxl8.get_height))] - - (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) - - (pxl8.begin_frame_3d camera) - (pxl8.clear_depth) - - (world:render [smooth-cam-x eye-y smooth-cam-z]) - - (pxl8.end_frame_3d) - - (let [cx (/ (pxl8.get_width) 2) - cy (/ (pxl8.get_height) 2) - crosshair-size 4 - red-color 18] - (pxl8.line (- cx crosshair-size) cy (+ cx crosshair-size) cy red-color) - (pxl8.line cx (- cy crosshair-size) cx (+ cy crosshair-size) red-color)) - - (pxl8.text (.. "fps: " (string.format "%.1f" (pxl8.get_fps))) 5 5 12) - (pxl8.text (.. "pos: " (string.format "%.0f" cam-x) "," - (string.format "%.0f" cam-y) "," - (string.format "%.0f" cam-z)) 5 15 12)))) - -{:init init - :update update - :frame frame} diff --git a/pxl8.sh b/pxl8.sh index 140dc7a..93fb8dc 100755 --- a/pxl8.sh +++ b/pxl8.sh @@ -56,6 +56,45 @@ build_luajit() { fi } +build_server() { + local mode="$1" + if [[ -d "server" ]]; then + print_info "Building server ($mode mode)" + cd server + if [[ "$mode" == "release" ]]; then + cargo build --release > /dev/null 2>&1 + else + cargo build > /dev/null 2>&1 + fi + cd - > /dev/null + print_info "Built server" + fi +} + +start_server() { + local mode="$1" + local server_bin + if [[ "$mode" == "release" ]]; then + server_bin="server/target/release/pxl8-server" + else + server_bin="server/target/debug/pxl8-server" + fi + if [[ -f "$server_bin" ]]; then + print_info "Starting server" + ./$server_bin & + SERVER_PID=$! + sleep 0.2 + fi +} + +stop_server() { + if [[ -n "$SERVER_PID" ]]; then + print_info "Stopping server" + kill $SERVER_PID 2>/dev/null || true + wait $SERVER_PID 2>/dev/null || true + fi +} + build_sdl() { if [[ ! -f "lib/SDL/build/libSDL3.so" ]] && [[ ! -f "lib/SDL/build/libSDL3.a" ]] && [[ ! -f "lib/SDL/build/libSDL3.dylib" ]]; then print_info "Building SDL3" @@ -113,6 +152,7 @@ print_usage() { echo " --all Clean both build artifacts and dependencies" echo " --deps Clean only dependencies" echo " --release Build/run/clean in release mode (default: debug)" + echo " --server Start game server before running (for networked games)" } setup_sdl3() { @@ -320,7 +360,7 @@ case "$COMMAND" in print_info "Compiler cache: ccache enabled" fi - 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" + INCLUDES="-Isrc/core -Isrc/math -Isrc/gfx -Isrc/sfx -Isrc/script -Isrc/hal -Isrc/world -Isrc/asset -Isrc/game -Isrc/net -Ilib -Ilib/luajit/src -Ilib/linenoise -Ilib/miniz" COMPILE_FLAGS="$CFLAGS $INCLUDES" DEP_COMPILE_FLAGS="$DEP_CFLAGS $INCLUDES" @@ -329,38 +369,42 @@ case "$COMMAND" in LIB_SOURCE_FILES="lib/linenoise/linenoise.c lib/miniz/miniz.c" PXL8_SOURCE_FILES=" - 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 + src/core/pxl8.c + src/core/pxl8_bytes.c + src/core/pxl8_io.c + src/core/pxl8_log.c + src/core/pxl8_rng.c + src/math/pxl8_math.c + src/gfx/pxl8_anim.c + src/gfx/pxl8_atlas.c + src/gfx/pxl8_blend.c + src/gfx/pxl8_blit.c + src/gfx/pxl8_3d_camera.c + src/gfx/pxl8_colormap.c + src/gfx/pxl8_cpu.c + src/gfx/pxl8_dither.c + src/gfx/pxl8_font.c + src/gfx/pxl8_gfx.c + src/gfx/pxl8_mesh.c + src/gfx/pxl8_palette.c + src/gfx/pxl8_particles.c + src/gfx/pxl8_tilemap.c + src/gfx/pxl8_tilesheet.c + src/gfx/pxl8_transition.c + src/sfx/pxl8_sfx.c + src/script/pxl8_repl.c + src/script/pxl8_script.c + src/hal/pxl8_sdl3.c + src/world/pxl8_bsp.c + src/world/pxl8_gen.c + src/world/pxl8_world.c + src/asset/pxl8_ase.c + src/asset/pxl8_cart.c + src/asset/pxl8_save.c + src/game/pxl8_gui.c + src/game/pxl8_replay.c + src/net/pxl8_net.c + src/net/pxl8_protocol.c " LUAJIT_LIB="lib/luajit/src/libluajit.a" @@ -390,13 +434,13 @@ case "$COMMAND" in NEEDS_REBUILD=false if [[ "$src_file" -nt "$obj_file" ]] || \ - [[ "client/src/core/pxl8_types.h" -nt "$obj_file" ]] || \ - [[ "client/src/core/pxl8_macros.h" -nt "$obj_file" ]]; then + [[ "src/core/pxl8_types.h" -nt "$obj_file" ]] || \ + [[ "src/core/pxl8_macros.h" -nt "$obj_file" ]]; then NEEDS_REBUILD=true fi - 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 [[ "$src_file" == "src/script/pxl8_script.c" ]]; then + for lua_file in src/lua/*.lua src/lua/pxl8/*.lua lib/fennel/fennel.lua; do if [[ -f "$lua_file" ]] && [[ "$lua_file" -nt "$obj_file" ]]; then NEEDS_REBUILD=true break @@ -430,16 +474,25 @@ case "$COMMAND" in CART="" EXTRA_ARGS="" + RUN_SERVER=false for arg in "$@"; do if [[ "$arg" == "--release" ]]; then continue elif [[ "$arg" == "--repl" ]]; then EXTRA_ARGS="$EXTRA_ARGS --repl" + elif [[ "$arg" == "--server" ]]; then + RUN_SERVER=true elif [[ -z "$CART" ]]; then CART="$arg" fi done + if [[ "$RUN_SERVER" == true ]]; then + build_server "$MODE" + start_server "$MODE" + trap stop_server EXIT + fi + if [[ -z "$CART" ]]; then "$BINDIR/pxl8" $EXTRA_ARGS else diff --git a/server/.cargo/config.toml b/server/.cargo/config.toml new file mode 100644 index 0000000..7317206 --- /dev/null +++ b/server/.cargo/config.toml @@ -0,0 +1,2 @@ +[unstable] +build-std = ["core", "alloc"] diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/server/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/server/Cargo.lock b/server/Cargo.lock new file mode 100644 index 0000000..fbbf93d --- /dev/null +++ b/server/Cargo.lock @@ -0,0 +1,1090 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "assert_type_match" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f548ad2c4031f2902e3edc1f29c29e835829437de49562d8eb5dc5584d3a1043" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "bevy_ecs" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24637a7c8643cab493f4085cda6bde4895f0e0816699c59006f18819da2ca0b8" +dependencies = [ + "arrayvec", + "bevy_ecs_macros", + "bevy_platform", + "bevy_ptr", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "bitflags", + "bumpalo", + "concurrent-queue", + "derive_more", + "fixedbitset", + "indexmap", + "log", + "nonmax", + "serde", + "slotmap", + "smallvec", + "thiserror", + "variadics_please", +] + +[[package]] +name = "bevy_ecs_macros" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eb14c18ca71e11c69fbae873c2db129064efac6d52e48d0127d37bfba1acfa8" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bevy_macro_utils" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7272fca0bf30d8ca2571a803598856104b63e5c596d52850f811ed37c5e1e3" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "toml_edit", +] + +[[package]] +name = "bevy_platform" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b29ea749a8e85f98186ab662f607b885b97c804bb14cdb0cdf838164496d474" +dependencies = [ + "critical-section", + "foldhash", + "futures-channel", + "hashbrown", + "js-sys", + "portable-atomic", + "portable-atomic-util", + "serde", + "spin", + "wasm-bindgen", + "wasm-bindgen-futures", +] + +[[package]] +name = "bevy_ptr" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f98cbc6d34bbdb58240b72ed1731931b4991a893b3a3238bb7c42ae054aa676" + +[[package]] +name = "bevy_reflect" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2a977e2b8dba65b6e9c11039c5f9ef108be428f036b3d1cac13ad86ec59f9c" +dependencies = [ + "assert_type_match", + "bevy_platform", + "bevy_ptr", + "bevy_reflect_derive", + "bevy_utils", + "derive_more", + "disqualified", + "downcast-rs", + "erased-serde", + "foldhash", + "glam", + "indexmap", + "serde", + "smallvec", + "smol_str", + "thiserror", + "uuid", + "variadics_please", + "wgpu-types", +] + +[[package]] +name = "bevy_reflect_derive" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "067af30072b1611fda1a577f1cb678b8ea2c9226133068be808dd49aac30cef0" +dependencies = [ + "bevy_macro_utils", + "indexmap", + "proc-macro2", + "quote", + "syn", + "uuid", +] + +[[package]] +name = "bevy_tasks" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990ffedd374dd2c4fe8f0fd4bcefd5617d1ee59164b6c3fcc356a69b48e26e8e" +dependencies = [ + "async-channel", + "async-task", + "atomic-waker", + "bevy_platform", + "crossbeam-queue", + "derive_more", + "futures-lite", + "heapless", + "pin-project", +] + +[[package]] +name = "bevy_utils" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e258c44d869f9c41ac0f88a16815c67f2569eb9fff4716828a40273d127b6f84" +dependencies = [ + "bevy_platform", + "disqualified", + "thread_local", +] + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +dependencies = [ + "serde_core", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", + "portable-atomic", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + +[[package]] +name = "disqualified" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9c272297e804878a2a4b707cfcfc6d2328b5bb936944613b4fdf2b9269afdfd" + +[[package]] +name = "downcast-rs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "glam" +version = "0.30.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fc433e8437a212d1b6f1e68c7824af3aed907da60afa994e7f542d18d12aa9" +dependencies = [ + "serde_core", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "equivalent", + "serde", + "serde_core", +] + +[[package]] +name = "heapless" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed" +dependencies = [ + "hash32", + "portable-atomic", + "stable_deref_trait", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "portable-atomic" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pxl8-server" +version = "0.1.0" +dependencies = [ + "bevy_ecs", + "bindgen", + "libc", + "libm", + "windows-sys", +] + +[[package]] +name = "quote" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.23.10+spec-1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "uuid" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "getrandom", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "variadics_please" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wgpu-types" +version = "27.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afdcf84c395990db737f2dd91628706cb31e86d72e53482320d368e52b5da5eb" +dependencies = [ + "bitflags", + "bytemuck", + "js-sys", + "log", + "serde", + "thiserror", + "web-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" diff --git a/server/Cargo.toml b/server/Cargo.toml new file mode 100644 index 0000000..2ef61a7 --- /dev/null +++ b/server/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "pxl8-server" +version = "0.1.0" +edition = "2024" + +[build-dependencies] +bindgen = "0.72" + +[dependencies] +bevy_ecs = { version = "0.18", default-features = false } +libc = { version = "0.2", default-features = false } +libm = { version = "0.2", default-features = false } + +[target.'cfg(windows)'.dependencies] +windows-sys = { version = "0.61", default-features = false, features = [ + "Win32_System_Memory", + "Win32_System_Performance", + "Win32_System_Threading", + "Win32_Networking_WinSock", +] } + +[[bin]] +name = "pxl8-server" +path = "src/main.rs" + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" + +[features] +default = [] +std = ["bevy_ecs/std"] diff --git a/server/build.rs b/server/build.rs new file mode 100644 index 0000000..e9b3b52 --- /dev/null +++ b/server/build.rs @@ -0,0 +1,23 @@ +use std::env; +use std::path::PathBuf; + +fn main() { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let client_src = PathBuf::from(&manifest_dir).join("../client/src"); + + println!("cargo:rerun-if-changed=../client/src/net/pxl8_protocol.h"); + println!("cargo:rerun-if-changed=../client/src/core/pxl8_types.h"); + + let bindings = bindgen::Builder::default() + .header(client_src.join("net/pxl8_protocol.h").to_str().unwrap()) + .clang_arg(format!("-I{}", client_src.join("core").display())) + .use_core() + .rustified_enum(".*") + .generate() + .expect("Unable to generate bindings"); + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("protocol.rs")) + .expect("Couldn't write bindings"); +} diff --git a/server/rust-toolchain.toml b/server/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/server/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/server/src/allocator.rs b/server/src/allocator.rs new file mode 100644 index 0000000..dada493 --- /dev/null +++ b/server/src/allocator.rs @@ -0,0 +1,36 @@ +use core::alloc::{GlobalAlloc, Layout}; + +pub struct Allocator; + +#[cfg(unix)] +unsafe impl GlobalAlloc for Allocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + unsafe { libc::memalign(layout.align(), layout.size()) as *mut u8 } + } + + unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { + unsafe { libc::free(ptr as *mut libc::c_void) } + } + + unsafe fn realloc(&self, ptr: *mut u8, _layout: Layout, new_size: usize) -> *mut u8 { + unsafe { libc::realloc(ptr as *mut libc::c_void, new_size) as *mut u8 } + } +} + +#[cfg(windows)] +unsafe impl GlobalAlloc for Allocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + use windows_sys::Win32::System::Memory::*; + unsafe { HeapAlloc(GetProcessHeap(), 0, layout.size()) as *mut u8 } + } + + unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { + use windows_sys::Win32::System::Memory::*; + unsafe { HeapFree(GetProcessHeap(), 0, ptr as *mut core::ffi::c_void) }; + } + + unsafe fn realloc(&self, ptr: *mut u8, _layout: Layout, new_size: usize) -> *mut u8 { + use windows_sys::Win32::System::Memory::*; + unsafe { HeapReAlloc(GetProcessHeap(), 0, ptr as *mut core::ffi::c_void, new_size) as *mut u8 } + } +} diff --git a/server/src/components.rs b/server/src/components.rs new file mode 100644 index 0000000..02f3516 --- /dev/null +++ b/server/src/components.rs @@ -0,0 +1,45 @@ +use bevy_ecs::prelude::*; + +#[derive(Component, Clone, Copy, Default)] +pub struct Position { + pub x: f32, + pub y: f32, + pub z: f32, +} + +#[derive(Component, Clone, Copy, Default)] +pub struct Rotation { + pub pitch: f32, + pub yaw: f32, +} + +#[derive(Component, Clone, Copy, Default)] +pub struct Velocity { + pub x: f32, + pub y: f32, + pub z: f32, +} + +#[derive(Component, Clone, Copy)] +pub struct Health { + pub current: f32, + pub max: f32, +} + +impl Health { + pub fn new(max: f32) -> Self { + Self { current: max, max } + } +} + +impl Default for Health { + fn default() -> Self { + Self::new(100.0) + } +} + +#[derive(Component, Clone, Copy, Default)] +pub struct Player; + +#[derive(Component, Clone, Copy)] +pub struct TypeId(pub u16); diff --git a/server/src/lib.rs b/server/src/lib.rs new file mode 100644 index 0000000..a628640 --- /dev/null +++ b/server/src/lib.rs @@ -0,0 +1,18 @@ +#![no_std] + +extern crate alloc; + +pub mod components; +mod simulation; +pub mod transport; + +#[allow(dead_code, non_camel_case_types, non_snake_case, non_upper_case_globals)] +pub mod protocol { + include!(concat!(env!("OUT_DIR"), "/protocol.rs")); +} + +pub use bevy_ecs::prelude::*; +pub use components::*; +pub use protocol::*; +pub use simulation::*; +pub use transport::*; diff --git a/server/src/main.rs b/server/src/main.rs new file mode 100644 index 0000000..165fbbc --- /dev/null +++ b/server/src/main.rs @@ -0,0 +1,142 @@ +#![no_std] +#![no_main] + +extern crate alloc; + +mod allocator; + +use core::panic::PanicInfo; +use pxl8_server::*; + +#[global_allocator] +static ALLOCATOR: allocator::Allocator = allocator::Allocator; + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + loop {} +} + +const TICK_RATE: u64 = 30; +const TICK_NS: u64 = 1_000_000_000 / TICK_RATE; + +#[cfg(unix)] +fn get_time_ns() -> u64 { + let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 }; + unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts) }; + (ts.tv_sec as u64) * 1_000_000_000 + (ts.tv_nsec as u64) +} + +#[cfg(windows)] +fn get_time_ns() -> u64 { + use windows_sys::Win32::System::Performance::*; + static mut FREQ: i64 = 0; + unsafe { + if FREQ == 0 { + QueryPerformanceFrequency(&mut FREQ); + } + let mut count: i64 = 0; + QueryPerformanceCounter(&mut count); + ((count as u128 * 1_000_000_000) / FREQ as u128) as u64 + } +} + +#[cfg(unix)] +fn sleep_ms(ms: u64) { + let ts = libc::timespec { + tv_sec: (ms / 1000) as i64, + tv_nsec: ((ms % 1000) * 1_000_000) as i64, + }; + unsafe { libc::nanosleep(&ts, core::ptr::null_mut()) }; +} + +#[cfg(windows)] +fn sleep_ms(ms: u64) { + use windows_sys::Win32::System::Threading::Sleep; + unsafe { Sleep(ms as u32) }; +} + +fn extract_spawn_position(payload: &[u8]) -> (f32, f32, f32, f32, f32) { + let x = f32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]); + let y = f32::from_be_bytes([payload[4], payload[5], payload[6], payload[7]]); + let z = f32::from_be_bytes([payload[8], payload[9], payload[10], payload[11]]); + let yaw = f32::from_be_bytes([payload[12], payload[13], payload[14], payload[15]]); + let pitch = f32::from_be_bytes([payload[16], payload[17], payload[18], payload[19]]); + (x, y, z, yaw, pitch) +} + +#[unsafe(no_mangle)] +pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + let mut transport = match transport::Transport::bind(transport::DEFAULT_PORT) { + Some(t) => t, + None => return 1, + }; + + let mut sim = Simulation::new(); + let mut player_id: u64 = 0; + let mut last_client_tick: u64 = 0; + + let mut sequence: u32 = 0; + let mut last_tick = get_time_ns(); + let mut entities_buf = [protocol::pxl8_entity_state { + entity_id: 0, + userdata: [0u8; 56], + }; 64]; + let mut inputs_buf: [protocol::pxl8_input_msg; 16] = unsafe { core::mem::zeroed() }; + + loop { + let now = get_time_ns(); + let elapsed = now.saturating_sub(last_tick); + + if elapsed >= TICK_NS { + last_tick = now; + let dt = (elapsed as f32) / 1_000_000_000.0; + + let mut latest_input: Option = None; + while let Some(msg_type) = transport.recv() { + match msg_type { + x if x == protocol::pxl8_msg_type::PXL8_MSG_INPUT as u8 => { + latest_input = Some(transport.get_input()); + } + x if x == protocol::pxl8_msg_type::PXL8_MSG_COMMAND as u8 => { + let cmd = transport.get_command(); + if cmd.cmd_type == protocol::pxl8_cmd_type::PXL8_CMD_SPAWN_ENTITY as u16 { + let (x, y, z, _yaw, _pitch) = extract_spawn_position(&cmd.payload); + let player = sim.spawn_player(x, y, z); + player_id = player.to_bits(); + } + } + _ => {} + } + } + + if let Some(input) = latest_input { + last_client_tick = input.tick; + inputs_buf[0] = input; + sim.step(&inputs_buf[..1], dt); + } else { + sim.step(&[], dt); + } + + let mut count = 0; + sim.generate_snapshot(player_id, |state| { + if count < entities_buf.len() { + entities_buf[count] = *state; + count += 1; + } + }); + + let header = protocol::pxl8_snapshot_header { + entity_count: count as u16, + event_count: 0, + player_id, + tick: last_client_tick, + time: sim.time, + }; + + transport.send_snapshot(&header, &entities_buf[..count], sequence); + sequence = sequence.wrapping_add(1); + } + + sleep_ms(1); + } +} diff --git a/server/src/simulation.rs b/server/src/simulation.rs new file mode 100644 index 0000000..5e9f231 --- /dev/null +++ b/server/src/simulation.rs @@ -0,0 +1,133 @@ +use bevy_ecs::prelude::*; +use libm::{cosf, sinf}; + +use crate::components::*; +use crate::protocol::*; + +#[derive(Resource, Default)] +pub struct SimTime { + pub dt: f32, + pub time: f32, +} + +pub struct Simulation { + pub player: Option, + pub tick: u64, + pub time: f32, + pub world: World, +} + +impl Simulation { + pub fn new() -> Self { + let mut world = World::new(); + world.insert_resource(SimTime::default()); + Self { + player: None, + tick: 0, + time: 0.0, + world, + } + } + + pub fn step(&mut self, inputs: &[pxl8_input_msg], dt: f32) { + self.tick += 1; + self.time += dt; + + if let Some(mut sim_time) = self.world.get_resource_mut::() { + sim_time.dt = dt; + sim_time.time = self.time; + } + + for input in inputs { + self.apply_input(input, dt); + } + } + + fn apply_input(&mut self, input: &pxl8_input_msg, dt: f32) { + let Some(player) = self.player else { return }; + let speed = 200.0; + + let yaw = input.yaw; + let sin_yaw = sinf(yaw); + let cos_yaw = cosf(yaw); + + let move_x = input.move_x; + let move_y = input.move_y; + let len_sq = move_x * move_x + move_y * move_y; + + if len_sq > 0.0 { + let len = libm::sqrtf(len_sq); + let norm_x = move_x / len; + let norm_y = move_y / len; + + let forward_x = -sin_yaw * norm_y * speed * dt; + let forward_z = -cos_yaw * norm_y * speed * dt; + let strafe_x = cos_yaw * norm_x * speed * dt; + let strafe_z = -sin_yaw * norm_x * speed * dt; + + if let Some(mut pos) = self.world.get_mut::(player) { + pos.x += forward_x + strafe_x; + pos.z += forward_z + strafe_z; + } + } + + if let Some(mut rot) = self.world.get_mut::(player) { + rot.yaw = yaw; + rot.pitch = (rot.pitch - input.look_dy * 0.008).clamp(-1.5, 1.5); + } + } + + pub fn spawn_entity(&mut self) -> Entity { + self.world.spawn(( + Position::default(), + Rotation::default(), + Velocity::default(), + )).id() + } + + pub fn spawn_player(&mut self, x: f32, y: f32, z: f32) -> Entity { + let entity = self.world.spawn(( + Player, + Position { x, y, z }, + Rotation::default(), + Velocity::default(), + Health::default(), + )).id(); + self.player = Some(entity); + entity + } + + pub fn generate_snapshot(&mut self, player_id: u64, mut writer: F) + where + F: FnMut(&pxl8_entity_state), + { + let mut query = self.world.query::<(Entity, &Position, &Rotation)>(); + for (entity, pos, rot) in query.iter(&self.world) { + let mut state = pxl8_entity_state { + entity_id: entity.to_bits(), + userdata: [0u8; 56], + }; + + let bytes = &mut state.userdata; + bytes[0..4].copy_from_slice(&pos.x.to_be_bytes()); + bytes[4..8].copy_from_slice(&pos.y.to_be_bytes()); + bytes[8..12].copy_from_slice(&pos.z.to_be_bytes()); + bytes[12..16].copy_from_slice(&rot.yaw.to_be_bytes()); + bytes[16..20].copy_from_slice(&rot.pitch.to_be_bytes()); + + writer(&state); + } + + let _ = player_id; + } + + pub fn entity_count(&self) -> usize { + self.world.entities().len() as usize + } +} + +impl Default for Simulation { + fn default() -> Self { + Self::new() + } +} diff --git a/server/src/transport.rs b/server/src/transport.rs new file mode 100644 index 0000000..2315433 --- /dev/null +++ b/server/src/transport.rs @@ -0,0 +1,281 @@ +use crate::protocol::*; + +pub const DEFAULT_PORT: u16 = 7777; + +#[cfg(unix)] +mod sys { + use libc::{c_int, c_void, sockaddr, sockaddr_in, socklen_t}; + + pub type RawSocket = c_int; + pub const INVALID_SOCKET: RawSocket = -1; + + pub fn socket() -> RawSocket { + unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0) } + } + + pub fn bind(sock: RawSocket, port: u16) -> c_int { + let addr = sockaddr_in { + sin_family: libc::AF_INET as u16, + sin_port: port.to_be(), + sin_addr: libc::in_addr { s_addr: u32::from_be_bytes([127, 0, 0, 1]).to_be() }, + sin_zero: [0; 8], + }; + unsafe { libc::bind(sock, &addr as *const _ as *const sockaddr, core::mem::size_of::() as socklen_t) } + } + + pub fn set_nonblocking(sock: RawSocket) -> c_int { + unsafe { + let flags = libc::fcntl(sock, libc::F_GETFL, 0); + libc::fcntl(sock, libc::F_SETFL, flags | libc::O_NONBLOCK) + } + } + + pub fn recvfrom(sock: RawSocket, buf: &mut [u8], addr: &mut sockaddr_in) -> isize { + let mut addr_len = core::mem::size_of::() as socklen_t; + unsafe { + libc::recvfrom( + sock, + buf.as_mut_ptr() as *mut c_void, + buf.len(), + 0, + addr as *mut _ as *mut sockaddr, + &mut addr_len, + ) + } + } + + pub fn sendto(sock: RawSocket, buf: &[u8], addr: &sockaddr_in) -> isize { + unsafe { + libc::sendto( + sock, + buf.as_ptr() as *const c_void, + buf.len(), + 0, + addr as *const _ as *const sockaddr, + core::mem::size_of::() as socklen_t, + ) + } + } + + pub fn close(sock: RawSocket) { + unsafe { libc::close(sock) }; + } +} + +#[cfg(windows)] +mod sys { + use windows_sys::Win32::Networking::WinSock::*; + + pub type RawSocket = SOCKET; + pub const INVALID_SOCKET_VAL: RawSocket = INVALID_SOCKET; + + pub fn socket() -> RawSocket { + unsafe { socket(AF_INET as i32, SOCK_DGRAM as i32, 0) } + } + + pub fn bind(sock: RawSocket, port: u16) -> i32 { + let addr = SOCKADDR_IN { + sin_family: AF_INET, + sin_port: port.to_be(), + sin_addr: IN_ADDR { S_un: IN_ADDR_0 { S_addr: u32::from_be_bytes([127, 0, 0, 1]).to_be() } }, + sin_zero: [0; 8], + }; + unsafe { bind(sock, &addr as *const _ as *const SOCKADDR, core::mem::size_of::() as i32) } + } + + pub fn set_nonblocking(sock: RawSocket) -> i32 { + let mut nonblocking: u32 = 1; + unsafe { ioctlsocket(sock, FIONBIO as i32, &mut nonblocking) } + } + + pub fn recvfrom(sock: RawSocket, buf: &mut [u8], addr: &mut SOCKADDR_IN) -> i32 { + let mut addr_len = core::mem::size_of::() as i32; + unsafe { + recvfrom( + sock, + buf.as_mut_ptr(), + buf.len() as i32, + 0, + addr as *mut _ as *mut SOCKADDR, + &mut addr_len, + ) + } + } + + pub fn sendto(sock: RawSocket, buf: &[u8], addr: &SOCKADDR_IN) -> i32 { + unsafe { + sendto( + sock, + buf.as_ptr(), + buf.len() as i32, + 0, + addr as *const _ as *const SOCKADDR, + core::mem::size_of::() as i32, + ) + } + } + + pub fn close(sock: RawSocket) { + unsafe { closesocket(sock) }; + } +} + +#[cfg(unix)] +type SockAddr = libc::sockaddr_in; + +#[cfg(windows)] +type SockAddr = windows_sys::Win32::Networking::WinSock::SOCKADDR_IN; + +pub struct Transport { + client_addr: SockAddr, + has_client: bool, + recv_buf: [u8; 4096], + send_buf: [u8; 4096], + socket: sys::RawSocket, +} + +impl Transport { + pub fn bind(port: u16) -> Option { + let sock = sys::socket(); + if sock == sys::INVALID_SOCKET { + return None; + } + + if sys::bind(sock, port) < 0 { + sys::close(sock); + return None; + } + + if sys::set_nonblocking(sock) < 0 { + sys::close(sock); + return None; + } + + Some(Self { + client_addr: unsafe { core::mem::zeroed() }, + has_client: false, + recv_buf: [0u8; 4096], + send_buf: [0u8; 4096], + socket: sock, + }) + } + + pub fn recv(&mut self) -> Option { + let mut addr: SockAddr = unsafe { core::mem::zeroed() }; + let len = sys::recvfrom(self.socket, &mut self.recv_buf, &mut addr); + + if len <= 0 || (len as usize) < size_of::() { + return None; + } + + self.client_addr = addr; + self.has_client = true; + + let header = self.deserialize_header(); + Some(header.type_) + } + + pub fn get_input(&self) -> pxl8_input_msg { + self.deserialize_input() + } + + pub fn get_command(&self) -> pxl8_command_msg { + self.deserialize_command() + } + + pub fn send_snapshot( + &mut self, + header: &pxl8_snapshot_header, + entities: &[pxl8_entity_state], + sequence: u32, + ) { + if !self.has_client { + return; + } + + let mut offset = 0; + + let msg_header = pxl8_msg_header { + sequence, + size: 0, + type_: pxl8_msg_type::PXL8_MSG_SNAPSHOT as u8, + version: PXL8_PROTOCOL_VERSION as u8, + }; + offset += self.serialize_header(&msg_header, offset); + offset += self.serialize_snapshot_header(header, offset); + + for entity in entities { + offset += self.serialize_entity_state(entity, offset); + } + + sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr); + } + + fn serialize_header(&mut self, h: &pxl8_msg_header, offset: usize) -> usize { + let buf = &mut self.send_buf[offset..]; + buf[0..4].copy_from_slice(&h.sequence.to_be_bytes()); + buf[4..6].copy_from_slice(&h.size.to_be_bytes()); + buf[6] = h.type_; + buf[7] = h.version; + 8 + } + + fn serialize_snapshot_header(&mut self, h: &pxl8_snapshot_header, offset: usize) -> usize { + let buf = &mut self.send_buf[offset..]; + buf[0..2].copy_from_slice(&h.entity_count.to_be_bytes()); + buf[2..4].copy_from_slice(&h.event_count.to_be_bytes()); + buf[4..12].copy_from_slice(&h.player_id.to_be_bytes()); + buf[12..20].copy_from_slice(&h.tick.to_be_bytes()); + buf[20..24].copy_from_slice(&h.time.to_be_bytes()); + 24 + } + + fn serialize_entity_state(&mut self, e: &pxl8_entity_state, offset: usize) -> usize { + let buf = &mut self.send_buf[offset..]; + buf[0..8].copy_from_slice(&e.entity_id.to_be_bytes()); + buf[8..64].copy_from_slice(&e.userdata); + 64 + } + + fn deserialize_header(&self) -> pxl8_msg_header { + let buf = &self.recv_buf; + pxl8_msg_header { + sequence: u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]), + size: u16::from_be_bytes([buf[4], buf[5]]), + type_: buf[6], + version: buf[7], + } + } + + fn deserialize_input(&self) -> pxl8_input_msg { + let buf = &self.recv_buf[8..]; + pxl8_input_msg { + buttons: u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]), + look_dx: f32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]), + look_dy: f32::from_be_bytes([buf[8], buf[9], buf[10], buf[11]]), + move_x: f32::from_be_bytes([buf[12], buf[13], buf[14], buf[15]]), + move_y: f32::from_be_bytes([buf[16], buf[17], buf[18], buf[19]]), + yaw: f32::from_be_bytes([buf[20], buf[21], buf[22], buf[23]]), + tick: u64::from_be_bytes([buf[24], buf[25], buf[26], buf[27], buf[28], buf[29], buf[30], buf[31]]), + timestamp: u64::from_be_bytes([buf[32], buf[33], buf[34], buf[35], buf[36], buf[37], buf[38], buf[39]]), + } + } + + fn deserialize_command(&self) -> pxl8_command_msg { + let buf = &self.recv_buf[8..]; + let mut cmd = pxl8_command_msg { + cmd_type: u16::from_be_bytes([buf[0], buf[1]]), + payload: [0u8; 64], + payload_size: u16::from_be_bytes([buf[66], buf[67]]), + tick: u64::from_be_bytes([buf[68], buf[69], buf[70], buf[71], buf[72], buf[73], buf[74], buf[75]]), + }; + cmd.payload.copy_from_slice(&buf[2..66]); + cmd + } +} + +impl Drop for Transport { + fn drop(&mut self) { + sys::close(self.socket); + } +} diff --git a/client/src/asset/pxl8_ase.c b/src/asset/pxl8_ase.c similarity index 100% rename from client/src/asset/pxl8_ase.c rename to src/asset/pxl8_ase.c diff --git a/client/src/asset/pxl8_ase.h b/src/asset/pxl8_ase.h similarity index 100% rename from client/src/asset/pxl8_ase.h rename to src/asset/pxl8_ase.h diff --git a/client/src/asset/pxl8_cart.c b/src/asset/pxl8_cart.c similarity index 100% rename from client/src/asset/pxl8_cart.c rename to src/asset/pxl8_cart.c diff --git a/client/src/asset/pxl8_cart.h b/src/asset/pxl8_cart.h similarity index 100% rename from client/src/asset/pxl8_cart.h rename to src/asset/pxl8_cart.h diff --git a/client/src/asset/pxl8_embed.h b/src/asset/pxl8_embed.h similarity index 70% rename from client/src/asset/pxl8_embed.h rename to src/asset/pxl8_embed.h index 07791e6..a3efa98 100644 --- a/client/src/asset/pxl8_embed.h +++ b/src/asset/pxl8_embed.h @@ -8,55 +8,67 @@ static const char embed_fennel[] = { , 0 }; static const char embed_pxl8[] = { -#embed "client/src/lua/pxl8.lua" +#embed "src/lua/pxl8.lua" , 0 }; static const char embed_pxl8_anim[] = { -#embed "client/src/lua/pxl8/anim.lua" +#embed "src/lua/pxl8/anim.lua" +, 0 }; + +static const char embed_pxl8_bytes[] = { +#embed "src/lua/pxl8/bytes.lua" , 0 }; static const char embed_pxl8_core[] = { -#embed "client/src/lua/pxl8/core.lua" +#embed "src/lua/pxl8/core.lua" +, 0 }; + +static const char embed_pxl8_effects[] = { +#embed "src/lua/pxl8/effects.lua" , 0 }; static const char embed_pxl8_gfx2d[] = { -#embed "client/src/lua/pxl8/gfx2d.lua" +#embed "src/lua/pxl8/gfx2d.lua" , 0 }; static const char embed_pxl8_gfx3d[] = { -#embed "client/src/lua/pxl8/gfx3d.lua" +#embed "src/lua/pxl8/gfx3d.lua" , 0 }; static const char embed_pxl8_gui[] = { -#embed "client/src/lua/pxl8/gui.lua" +#embed "src/lua/pxl8/gui.lua" , 0 }; static const char embed_pxl8_input[] = { -#embed "client/src/lua/pxl8/input.lua" +#embed "src/lua/pxl8/input.lua" , 0 }; static const char embed_pxl8_math[] = { -#embed "client/src/lua/pxl8/math.lua" +#embed "src/lua/pxl8/math.lua" +, 0 }; + +static const char embed_pxl8_net[] = { +#embed "src/lua/pxl8/net.lua" , 0 }; static const char embed_pxl8_particles[] = { -#embed "client/src/lua/pxl8/particles.lua" +#embed "src/lua/pxl8/particles.lua" , 0 }; static const char embed_pxl8_sfx[] = { -#embed "client/src/lua/pxl8/sfx.lua" +#embed "src/lua/pxl8/sfx.lua" , 0 }; static const char embed_pxl8_tilemap[] = { -#embed "client/src/lua/pxl8/tilemap.lua" +#embed "src/lua/pxl8/tilemap.lua" , 0 }; static const char embed_pxl8_transition[] = { -#embed "client/src/lua/pxl8/transition.lua" +#embed "src/lua/pxl8/transition.lua" , 0 }; static const char embed_pxl8_world[] = { -#embed "client/src/lua/pxl8/world.lua" +#embed "src/lua/pxl8/world.lua" , 0 }; #define PXL8_EMBED_ENTRY(var, name) {name, var, sizeof(var) - 1} @@ -67,12 +79,15 @@ static const pxl8_embed pxl8_embeds[] = { PXL8_EMBED_ENTRY(embed_fennel, "fennel"), PXL8_EMBED_ENTRY(embed_pxl8, "pxl8"), PXL8_EMBED_ENTRY(embed_pxl8_anim, "pxl8.anim"), + PXL8_EMBED_ENTRY(embed_pxl8_bytes, "pxl8.bytes"), PXL8_EMBED_ENTRY(embed_pxl8_core, "pxl8.core"), + PXL8_EMBED_ENTRY(embed_pxl8_effects, "pxl8.effects"), PXL8_EMBED_ENTRY(embed_pxl8_gfx2d, "pxl8.gfx2d"), PXL8_EMBED_ENTRY(embed_pxl8_gfx3d, "pxl8.gfx3d"), PXL8_EMBED_ENTRY(embed_pxl8_gui, "pxl8.gui"), PXL8_EMBED_ENTRY(embed_pxl8_input, "pxl8.input"), PXL8_EMBED_ENTRY(embed_pxl8_math, "pxl8.math"), + PXL8_EMBED_ENTRY(embed_pxl8_net, "pxl8.net"), PXL8_EMBED_ENTRY(embed_pxl8_particles, "pxl8.particles"), PXL8_EMBED_ENTRY(embed_pxl8_sfx, "pxl8.sfx"), PXL8_EMBED_ENTRY(embed_pxl8_tilemap, "pxl8.tilemap"), diff --git a/client/src/asset/pxl8_save.c b/src/asset/pxl8_save.c similarity index 100% rename from client/src/asset/pxl8_save.c rename to src/asset/pxl8_save.c diff --git a/client/src/asset/pxl8_save.h b/src/asset/pxl8_save.h similarity index 100% rename from client/src/asset/pxl8_save.h rename to src/asset/pxl8_save.h diff --git a/client/src/core/pxl8.c b/src/core/pxl8.c similarity index 100% rename from client/src/core/pxl8.c rename to src/core/pxl8.c diff --git a/src/core/pxl8_bytes.c b/src/core/pxl8_bytes.c new file mode 100644 index 0000000..1894878 --- /dev/null +++ b/src/core/pxl8_bytes.c @@ -0,0 +1,234 @@ +#include "pxl8_bytes.h" +#include + +void pxl8_pack_u8(u8* buf, size_t offset, u8 val) { + buf[offset] = val; +} + +void pxl8_pack_u16_le(u8* buf, size_t offset, u16 val) { + buf[offset] = (u8)(val); + buf[offset + 1] = (u8)(val >> 8); +} + +void pxl8_pack_u16_be(u8* buf, size_t offset, u16 val) { + buf[offset] = (u8)(val >> 8); + buf[offset + 1] = (u8)(val); +} + +void pxl8_pack_u32_le(u8* buf, size_t offset, u32 val) { + buf[offset] = (u8)(val); + buf[offset + 1] = (u8)(val >> 8); + buf[offset + 2] = (u8)(val >> 16); + buf[offset + 3] = (u8)(val >> 24); +} + +void pxl8_pack_u32_be(u8* buf, size_t offset, u32 val) { + buf[offset] = (u8)(val >> 24); + buf[offset + 1] = (u8)(val >> 16); + buf[offset + 2] = (u8)(val >> 8); + buf[offset + 3] = (u8)(val); +} + +void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val) { + buf[offset] = (u8)(val); + buf[offset + 1] = (u8)(val >> 8); + buf[offset + 2] = (u8)(val >> 16); + buf[offset + 3] = (u8)(val >> 24); + buf[offset + 4] = (u8)(val >> 32); + buf[offset + 5] = (u8)(val >> 40); + buf[offset + 6] = (u8)(val >> 48); + buf[offset + 7] = (u8)(val >> 56); +} + +void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val) { + buf[offset] = (u8)(val >> 56); + buf[offset + 1] = (u8)(val >> 48); + buf[offset + 2] = (u8)(val >> 40); + buf[offset + 3] = (u8)(val >> 32); + buf[offset + 4] = (u8)(val >> 24); + buf[offset + 5] = (u8)(val >> 16); + buf[offset + 6] = (u8)(val >> 8); + buf[offset + 7] = (u8)(val); +} + +void pxl8_pack_i8(u8* buf, size_t offset, i8 val) { + buf[offset] = (u8)val; +} + +void pxl8_pack_i16_le(u8* buf, size_t offset, i16 val) { + pxl8_pack_u16_le(buf, offset, (u16)val); +} + +void pxl8_pack_i16_be(u8* buf, size_t offset, i16 val) { + pxl8_pack_u16_be(buf, offset, (u16)val); +} + +void pxl8_pack_i32_le(u8* buf, size_t offset, i32 val) { + pxl8_pack_u32_le(buf, offset, (u32)val); +} + +void pxl8_pack_i32_be(u8* buf, size_t offset, i32 val) { + pxl8_pack_u32_be(buf, offset, (u32)val); +} + +void pxl8_pack_i64_le(u8* buf, size_t offset, i64 val) { + pxl8_pack_u64_le(buf, offset, (u64)val); +} + +void pxl8_pack_i64_be(u8* buf, size_t offset, i64 val) { + pxl8_pack_u64_be(buf, offset, (u64)val); +} + +void pxl8_pack_f32_le(u8* buf, size_t offset, f32 val) { + u32 bits; + memcpy(&bits, &val, sizeof(bits)); + pxl8_pack_u32_le(buf, offset, bits); +} + +void pxl8_pack_f32_be(u8* buf, size_t offset, f32 val) { + u32 bits; + memcpy(&bits, &val, sizeof(bits)); + pxl8_pack_u32_be(buf, offset, bits); +} + +void pxl8_pack_f64_le(u8* buf, size_t offset, f64 val) { + u64 bits; + memcpy(&bits, &val, sizeof(bits)); + pxl8_pack_u64_le(buf, offset, bits); +} + +void pxl8_pack_f64_be(u8* buf, size_t offset, f64 val) { + u64 bits; + memcpy(&bits, &val, sizeof(bits)); + pxl8_pack_u64_be(buf, offset, bits); +} + +u8 pxl8_unpack_u8(const u8* buf, size_t offset) { + return buf[offset]; +} + +u16 pxl8_unpack_u16_le(const u8* buf, size_t offset) { + return (u16)buf[offset] | ((u16)buf[offset + 1] << 8); +} + +u16 pxl8_unpack_u16_be(const u8* buf, size_t offset) { + return ((u16)buf[offset] << 8) | (u16)buf[offset + 1]; +} + +u32 pxl8_unpack_u32_le(const u8* buf, size_t offset) { + return (u32)buf[offset] | + ((u32)buf[offset + 1] << 8) | + ((u32)buf[offset + 2] << 16) | + ((u32)buf[offset + 3] << 24); +} + +u32 pxl8_unpack_u32_be(const u8* buf, size_t offset) { + return ((u32)buf[offset] << 24) | + ((u32)buf[offset + 1] << 16) | + ((u32)buf[offset + 2] << 8) | + (u32)buf[offset + 3]; +} + +u64 pxl8_unpack_u64_le(const u8* buf, size_t offset) { + return (u64)buf[offset] | + ((u64)buf[offset + 1] << 8) | + ((u64)buf[offset + 2] << 16) | + ((u64)buf[offset + 3] << 24) | + ((u64)buf[offset + 4] << 32) | + ((u64)buf[offset + 5] << 40) | + ((u64)buf[offset + 6] << 48) | + ((u64)buf[offset + 7] << 56); +} + +u64 pxl8_unpack_u64_be(const u8* buf, size_t offset) { + return ((u64)buf[offset] << 56) | + ((u64)buf[offset + 1] << 48) | + ((u64)buf[offset + 2] << 40) | + ((u64)buf[offset + 3] << 32) | + ((u64)buf[offset + 4] << 24) | + ((u64)buf[offset + 5] << 16) | + ((u64)buf[offset + 6] << 8) | + (u64)buf[offset + 7]; +} + +i8 pxl8_unpack_i8(const u8* buf, size_t offset) { + return (i8)buf[offset]; +} + +i16 pxl8_unpack_i16_le(const u8* buf, size_t offset) { + return (i16)pxl8_unpack_u16_le(buf, offset); +} + +i16 pxl8_unpack_i16_be(const u8* buf, size_t offset) { + return (i16)pxl8_unpack_u16_be(buf, offset); +} + +i32 pxl8_unpack_i32_le(const u8* buf, size_t offset) { + return (i32)pxl8_unpack_u32_le(buf, offset); +} + +i32 pxl8_unpack_i32_be(const u8* buf, size_t offset) { + return (i32)pxl8_unpack_u32_be(buf, offset); +} + +i64 pxl8_unpack_i64_le(const u8* buf, size_t offset) { + return (i64)pxl8_unpack_u64_le(buf, offset); +} + +i64 pxl8_unpack_i64_be(const u8* buf, size_t offset) { + return (i64)pxl8_unpack_u64_be(buf, offset); +} + +f32 pxl8_unpack_f32_le(const u8* buf, size_t offset) { + u32 bits = pxl8_unpack_u32_le(buf, offset); + f32 result; + memcpy(&result, &bits, sizeof(result)); + return result; +} + +f32 pxl8_unpack_f32_be(const u8* buf, size_t offset) { + u32 bits = pxl8_unpack_u32_be(buf, offset); + f32 result; + memcpy(&result, &bits, sizeof(result)); + return result; +} + +f64 pxl8_unpack_f64_le(const u8* buf, size_t offset) { + u64 bits = pxl8_unpack_u64_le(buf, offset); + f64 result; + memcpy(&result, &bits, sizeof(result)); + return result; +} + +f64 pxl8_unpack_f64_be(const u8* buf, size_t offset) { + u64 bits = pxl8_unpack_u64_be(buf, offset); + f64 result; + memcpy(&result, &bits, sizeof(result)); + return result; +} + +void pxl8_bit_set(u32* val, u8 bit) { + *val |= (1u << bit); +} + +void pxl8_bit_clear(u32* val, u8 bit) { + *val &= ~(1u << bit); +} + +bool pxl8_bit_test(u32 val, u8 bit) { + return (val & (1u << bit)) != 0; +} + +u32 pxl8_bit_count(u32 val) { +#if defined(__GNUC__) || defined(__clang__) + return (u32)__builtin_popcount(val); +#else + val = val - ((val >> 1) & 0x55555555); + val = (val & 0x33333333) + ((val >> 2) & 0x33333333); + return (((val + (val >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24; +#endif +} + +void pxl8_bit_toggle(u32* val, u8 bit) { + *val ^= (1u << bit); +} diff --git a/src/core/pxl8_bytes.h b/src/core/pxl8_bytes.h new file mode 100644 index 0000000..5f5a5e7 --- /dev/null +++ b/src/core/pxl8_bytes.h @@ -0,0 +1,251 @@ +#pragma once + +#include + +#include "pxl8_types.h" + +void pxl8_bit_clear(u32* val, u8 bit); +u32 pxl8_bit_count(u32 val); +void pxl8_bit_set(u32* val, u8 bit); +bool pxl8_bit_test(u32 val, u8 bit); +void pxl8_bit_toggle(u32* val, u8 bit); + +void pxl8_pack_u8(u8* buf, size_t offset, u8 val); +void pxl8_pack_u16_be(u8* buf, size_t offset, u16 val); +void pxl8_pack_u16_le(u8* buf, size_t offset, u16 val); +void pxl8_pack_u32_be(u8* buf, size_t offset, u32 val); +void pxl8_pack_u32_le(u8* buf, size_t offset, u32 val); +void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val); +void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val); +void pxl8_pack_i8(u8* buf, size_t offset, i8 val); +void pxl8_pack_i16_be(u8* buf, size_t offset, i16 val); +void pxl8_pack_i16_le(u8* buf, size_t offset, i16 val); +void pxl8_pack_i32_be(u8* buf, size_t offset, i32 val); +void pxl8_pack_i32_le(u8* buf, size_t offset, i32 val); +void pxl8_pack_i64_be(u8* buf, size_t offset, i64 val); +void pxl8_pack_i64_le(u8* buf, size_t offset, i64 val); +void pxl8_pack_f32_be(u8* buf, size_t offset, f32 val); +void pxl8_pack_f32_le(u8* buf, size_t offset, f32 val); +void pxl8_pack_f64_be(u8* buf, size_t offset, f64 val); +void pxl8_pack_f64_le(u8* buf, size_t offset, f64 val); + +u8 pxl8_unpack_u8(const u8* buf, size_t offset); +u16 pxl8_unpack_u16_be(const u8* buf, size_t offset); +u16 pxl8_unpack_u16_le(const u8* buf, size_t offset); +u32 pxl8_unpack_u32_be(const u8* buf, size_t offset); +u32 pxl8_unpack_u32_le(const u8* buf, size_t offset); +u64 pxl8_unpack_u64_be(const u8* buf, size_t offset); +u64 pxl8_unpack_u64_le(const u8* buf, size_t offset); +i8 pxl8_unpack_i8(const u8* buf, size_t offset); +i16 pxl8_unpack_i16_be(const u8* buf, size_t offset); +i16 pxl8_unpack_i16_le(const u8* buf, size_t offset); +i32 pxl8_unpack_i32_be(const u8* buf, size_t offset); +i32 pxl8_unpack_i32_le(const u8* buf, size_t offset); +i64 pxl8_unpack_i64_be(const u8* buf, size_t offset); +i64 pxl8_unpack_i64_le(const u8* buf, size_t offset); +f32 pxl8_unpack_f32_be(const u8* buf, size_t offset); +f32 pxl8_unpack_f32_le(const u8* buf, size_t offset); +f64 pxl8_unpack_f64_be(const u8* buf, size_t offset); +f64 pxl8_unpack_f64_le(const u8* buf, size_t offset); + +typedef struct { + const u8* bytes; + u32 offset; + u32 size; + bool overflow; +} pxl8_stream; + +typedef struct { + u8* bytes; + u32 capacity; + u32 offset; + bool overflow; +} pxl8_write_stream; + +static inline pxl8_stream pxl8_stream_create(const u8* bytes, u32 size) { + return (pxl8_stream){ + .bytes = bytes, + .offset = 0, + .size = size, + .overflow = false + }; +} + +static inline pxl8_write_stream pxl8_write_stream_create(u8* bytes, u32 capacity) { + return (pxl8_write_stream){ + .bytes = bytes, + .capacity = capacity, + .offset = 0, + .overflow = false + }; +} + +static inline bool pxl8_stream_can_read(const pxl8_stream* s, u32 count) { + return !s->overflow && s->offset + count <= s->size; +} + +static inline bool pxl8_stream_has_overflow(const pxl8_stream* s) { + return s->overflow; +} + +static inline bool pxl8_write_stream_has_overflow(const pxl8_write_stream* s) { + return s->overflow; +} + +static inline u32 pxl8_stream_position(const pxl8_stream* s) { + return s->offset; +} + +static inline u32 pxl8_write_stream_position(const pxl8_write_stream* s) { + return s->offset; +} + +static inline void pxl8_stream_seek(pxl8_stream* s, u32 offset) { + s->offset = offset; +} + +static inline u8 pxl8_read_u8(pxl8_stream* s) { + if (s->offset + 1 > s->size) { s->overflow = true; return 0; } + return pxl8_unpack_u8(s->bytes, s->offset++); +} + +static inline u16 pxl8_read_u16(pxl8_stream* s) { + if (s->offset + 2 > s->size) { s->overflow = true; return 0; } + u16 val = pxl8_unpack_u16_le(s->bytes, s->offset); + s->offset += 2; + return val; +} + +static inline u16 pxl8_read_u16_be(pxl8_stream* s) { + if (s->offset + 2 > s->size) { s->overflow = true; return 0; } + u16 val = pxl8_unpack_u16_be(s->bytes, s->offset); + s->offset += 2; + return val; +} + +static inline u32 pxl8_read_u32(pxl8_stream* s) { + if (s->offset + 4 > s->size) { s->overflow = true; return 0; } + u32 val = pxl8_unpack_u32_le(s->bytes, s->offset); + s->offset += 4; + return val; +} + +static inline u32 pxl8_read_u32_be(pxl8_stream* s) { + if (s->offset + 4 > s->size) { s->overflow = true; return 0; } + u32 val = pxl8_unpack_u32_be(s->bytes, s->offset); + s->offset += 4; + return val; +} + +static inline u64 pxl8_read_u64(pxl8_stream* s) { + if (s->offset + 8 > s->size) { s->overflow = true; return 0; } + u64 val = pxl8_unpack_u64_le(s->bytes, s->offset); + s->offset += 8; + return val; +} + +static inline u64 pxl8_read_u64_be(pxl8_stream* s) { + if (s->offset + 8 > s->size) { s->overflow = true; return 0; } + u64 val = pxl8_unpack_u64_be(s->bytes, s->offset); + s->offset += 8; + return val; +} + +static inline i16 pxl8_read_i16(pxl8_stream* s) { + return (i16)pxl8_read_u16(s); +} + +static inline i32 pxl8_read_i32(pxl8_stream* s) { + return (i32)pxl8_read_u32(s); +} + +static inline f32 pxl8_read_f32(pxl8_stream* s) { + if (s->offset + 4 > s->size) { s->overflow = true; return 0; } + f32 val = pxl8_unpack_f32_le(s->bytes, s->offset); + s->offset += 4; + return val; +} + +static inline f32 pxl8_read_f32_be(pxl8_stream* s) { + if (s->offset + 4 > s->size) { s->overflow = true; return 0; } + f32 val = pxl8_unpack_f32_be(s->bytes, s->offset); + s->offset += 4; + return val; +} + +static inline void pxl8_read_bytes(pxl8_stream* s, void* dest, u32 count) { + if (s->offset + count > s->size) { s->overflow = true; return; } + memcpy(dest, &s->bytes[s->offset], count); + s->offset += count; +} + +static inline void pxl8_skip_bytes(pxl8_stream* s, u32 count) { + if (s->offset + count > s->size) { s->overflow = true; return; } + s->offset += count; +} + +static inline const u8* pxl8_read_ptr(pxl8_stream* s, u32 count) { + if (s->offset + count > s->size) { s->overflow = true; return NULL; } + const u8* ptr = &s->bytes[s->offset]; + s->offset += count; + return ptr; +} + +static inline void pxl8_write_u8(pxl8_write_stream* s, u8 val) { + if (s->offset + 1 > s->capacity) { s->overflow = true; return; } + pxl8_pack_u8(s->bytes, s->offset++, val); +} + +static inline void pxl8_write_u16(pxl8_write_stream* s, u16 val) { + if (s->offset + 2 > s->capacity) { s->overflow = true; return; } + pxl8_pack_u16_le(s->bytes, s->offset, val); + s->offset += 2; +} + +static inline void pxl8_write_u16_be(pxl8_write_stream* s, u16 val) { + if (s->offset + 2 > s->capacity) { s->overflow = true; return; } + pxl8_pack_u16_be(s->bytes, s->offset, val); + s->offset += 2; +} + +static inline void pxl8_write_u32(pxl8_write_stream* s, u32 val) { + if (s->offset + 4 > s->capacity) { s->overflow = true; return; } + pxl8_pack_u32_le(s->bytes, s->offset, val); + s->offset += 4; +} + +static inline void pxl8_write_u32_be(pxl8_write_stream* s, u32 val) { + if (s->offset + 4 > s->capacity) { s->overflow = true; return; } + pxl8_pack_u32_be(s->bytes, s->offset, val); + s->offset += 4; +} + +static inline void pxl8_write_u64(pxl8_write_stream* s, u64 val) { + if (s->offset + 8 > s->capacity) { s->overflow = true; return; } + pxl8_pack_u64_le(s->bytes, s->offset, val); + s->offset += 8; +} + +static inline void pxl8_write_u64_be(pxl8_write_stream* s, u64 val) { + if (s->offset + 8 > s->capacity) { s->overflow = true; return; } + pxl8_pack_u64_be(s->bytes, s->offset, val); + s->offset += 8; +} + +static inline void pxl8_write_f32(pxl8_write_stream* s, f32 val) { + if (s->offset + 4 > s->capacity) { s->overflow = true; return; } + pxl8_pack_f32_le(s->bytes, s->offset, val); + s->offset += 4; +} + +static inline void pxl8_write_f32_be(pxl8_write_stream* s, f32 val) { + if (s->offset + 4 > s->capacity) { s->overflow = true; return; } + pxl8_pack_f32_be(s->bytes, s->offset, val); + s->offset += 4; +} + +static inline void pxl8_write_bytes(pxl8_write_stream* s, const void* src, u32 count) { + if (s->offset + count > s->capacity) { s->overflow = true; return; } + memcpy(&s->bytes[s->offset], src, count); + s->offset += count; +} diff --git a/client/src/core/pxl8_io.c b/src/core/pxl8_io.c similarity index 100% rename from client/src/core/pxl8_io.c rename to src/core/pxl8_io.c diff --git a/src/core/pxl8_io.h b/src/core/pxl8_io.h new file mode 100644 index 0000000..13daab5 --- /dev/null +++ b/src/core/pxl8_io.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +#include "pxl8_bytes.h" +#include "pxl8_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +pxl8_result pxl8_io_create_directory(const char* path); +bool pxl8_io_file_exists(const char* path); +void pxl8_io_free_binary_data(u8* data); +void pxl8_io_free_file_content(char* content); +f64 pxl8_io_get_file_modified_time(const char* path); +pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, size_t* size); +pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size); +pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, size_t size); +pxl8_result pxl8_io_write_file(const char* path, const char* content, size_t size); + +bool pxl8_key_down(const pxl8_input_state* input, const char* key_name); +bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name); +bool pxl8_key_released(const pxl8_input_state* input, const char* key_name); + +i32 pxl8_mouse_dx(const pxl8_input_state* input); +i32 pxl8_mouse_dy(const pxl8_input_state* input); +bool pxl8_mouse_pressed(const pxl8_input_state* input, i32 button); +bool pxl8_mouse_released(const pxl8_input_state* input, i32 button); +i32 pxl8_mouse_wheel_x(const pxl8_input_state* input); +i32 pxl8_mouse_wheel_y(const pxl8_input_state* input); +i32 pxl8_mouse_x(const pxl8_input_state* input); +i32 pxl8_mouse_y(const pxl8_input_state* input); + +#ifdef __cplusplus +} +#endif diff --git a/client/src/core/pxl8_log.c b/src/core/pxl8_log.c similarity index 100% rename from client/src/core/pxl8_log.c rename to src/core/pxl8_log.c diff --git a/client/src/core/pxl8_log.h b/src/core/pxl8_log.h similarity index 100% rename from client/src/core/pxl8_log.h rename to src/core/pxl8_log.h diff --git a/client/src/core/pxl8_macros.h b/src/core/pxl8_macros.h similarity index 100% rename from client/src/core/pxl8_macros.h rename to src/core/pxl8_macros.h diff --git a/client/src/core/pxl8_rng.c b/src/core/pxl8_rng.c similarity index 100% rename from client/src/core/pxl8_rng.c rename to src/core/pxl8_rng.c diff --git a/client/src/core/pxl8_rng.h b/src/core/pxl8_rng.h similarity index 100% rename from client/src/core/pxl8_rng.h rename to src/core/pxl8_rng.h diff --git a/client/src/core/pxl8_sys.h b/src/core/pxl8_sys.h similarity index 100% rename from client/src/core/pxl8_sys.h rename to src/core/pxl8_sys.h diff --git a/client/src/core/pxl8_types.h b/src/core/pxl8_types.h similarity index 96% rename from client/src/core/pxl8_types.h rename to src/core/pxl8_types.h index 3a90580..e5e1135 100644 --- a/client/src/core/pxl8_types.h +++ b/src/core/pxl8_types.h @@ -29,8 +29,9 @@ typedef __uint128_t u128; #endif typedef enum pxl8_pixel_mode { - PXL8_PIXEL_INDEXED, - PXL8_PIXEL_HICOLOR + PXL8_PIXEL_INDEXED = 1, + PXL8_PIXEL_HICOLOR = 2, + PXL8_PIXEL_RGBA = 4, } pxl8_pixel_mode; typedef enum pxl8_cursor { diff --git a/client/src/game/pxl8_game.h b/src/game/pxl8_game.h similarity index 100% rename from client/src/game/pxl8_game.h rename to src/game/pxl8_game.h diff --git a/client/src/game/pxl8_gui.c b/src/game/pxl8_gui.c similarity index 100% rename from client/src/game/pxl8_gui.c rename to src/game/pxl8_gui.c diff --git a/client/src/game/pxl8_gui.h b/src/game/pxl8_gui.h similarity index 100% rename from client/src/game/pxl8_gui.h rename to src/game/pxl8_gui.h diff --git a/client/src/game/pxl8_replay.c b/src/game/pxl8_replay.c similarity index 100% rename from client/src/game/pxl8_replay.c rename to src/game/pxl8_replay.c diff --git a/client/src/game/pxl8_replay.h b/src/game/pxl8_replay.h similarity index 100% rename from client/src/game/pxl8_replay.h rename to src/game/pxl8_replay.h diff --git a/client/src/gfx/pxl8_3d_camera.c b/src/gfx/pxl8_3d_camera.c similarity index 100% rename from client/src/gfx/pxl8_3d_camera.c rename to src/gfx/pxl8_3d_camera.c diff --git a/client/src/gfx/pxl8_3d_camera.h b/src/gfx/pxl8_3d_camera.h similarity index 100% rename from client/src/gfx/pxl8_3d_camera.h rename to src/gfx/pxl8_3d_camera.h diff --git a/client/src/gfx/pxl8_anim.c b/src/gfx/pxl8_anim.c similarity index 100% rename from client/src/gfx/pxl8_anim.c rename to src/gfx/pxl8_anim.c diff --git a/client/src/gfx/pxl8_anim.h b/src/gfx/pxl8_anim.h similarity index 100% rename from client/src/gfx/pxl8_anim.h rename to src/gfx/pxl8_anim.h diff --git a/client/src/gfx/pxl8_atlas.c b/src/gfx/pxl8_atlas.c similarity index 100% rename from client/src/gfx/pxl8_atlas.c rename to src/gfx/pxl8_atlas.c diff --git a/client/src/gfx/pxl8_atlas.h b/src/gfx/pxl8_atlas.h similarity index 100% rename from client/src/gfx/pxl8_atlas.h rename to src/gfx/pxl8_atlas.h diff --git a/client/src/gfx/pxl8_backend.h b/src/gfx/pxl8_backend.h similarity index 100% rename from client/src/gfx/pxl8_backend.h rename to src/gfx/pxl8_backend.h diff --git a/src/gfx/pxl8_blend.c b/src/gfx/pxl8_blend.c new file mode 100644 index 0000000..74ba68a --- /dev/null +++ b/src/gfx/pxl8_blend.c @@ -0,0 +1,194 @@ +#include "pxl8_blend.h" +#include "pxl8_colormap.h" + +#include + +struct pxl8_palette_cube { + u8 colors[PXL8_PALETTE_SIZE * 3]; + u8 table[PXL8_CUBE_ENTRIES]; + u8 stable[PXL8_CUBE_ENTRIES]; +}; + +struct pxl8_additive_table { + u8 table[PXL8_BLEND_TABLE_SIZE]; +}; + +struct pxl8_overbright_table { + u8 table[PXL8_OVERBRIGHT_TABLE_SIZE]; +}; + +static u8 find_closest_stable(const pxl8_palette* pal, u8 r, u8 g, u8 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; + } + + u8 pr, pg, pb; + pxl8_palette_get_rgb(pal, (u8)i, &pr, &pg, &pb); + + 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; +} + +pxl8_palette_cube* pxl8_palette_cube_create(const pxl8_palette* pal) { + pxl8_palette_cube* cube = calloc(1, sizeof(pxl8_palette_cube)); + if (!cube) return NULL; + pxl8_palette_cube_rebuild(cube, pal); + return cube; +} + +void pxl8_palette_cube_destroy(pxl8_palette_cube* cube) { + free(cube); +} + +void pxl8_palette_cube_rebuild(pxl8_palette_cube* cube, const pxl8_palette* pal) { + if (!cube || !pal) return; + + for (u32 i = 0; i < PXL8_PALETTE_SIZE; i++) { + u8 r, g, b; + pxl8_palette_get_rgb(pal, (u8)i, &r, &g, &b); + cube->colors[i * 3 + 0] = r; + cube->colors[i * 3 + 1] = g; + cube->colors[i * 3 + 2] = b; + } + + for (u32 bi = 0; bi < PXL8_CUBE_SIZE; bi++) { + for (u32 gi = 0; gi < PXL8_CUBE_SIZE; gi++) { + for (u32 ri = 0; ri < PXL8_CUBE_SIZE; ri++) { + u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri; + u8 r8 = (u8)((ri * 255) / (PXL8_CUBE_SIZE - 1)); + u8 g8 = (u8)((gi * 255) / (PXL8_CUBE_SIZE - 1)); + u8 b8 = (u8)((bi * 255) / (PXL8_CUBE_SIZE - 1)); + + cube->table[idx] = pxl8_palette_find_closest(pal, r8, g8, b8); + cube->stable[idx] = find_closest_stable(pal, r8, g8, b8); + } + } + } +} + +u8 pxl8_palette_cube_lookup(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b) { + u32 ri = (r * (PXL8_CUBE_SIZE - 1)) / 255; + u32 gi = (g * (PXL8_CUBE_SIZE - 1)) / 255; + u32 bi = (b * (PXL8_CUBE_SIZE - 1)) / 255; + u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri; + return cube->table[idx]; +} + +u8 pxl8_palette_cube_lookup_stable(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b) { + u32 ri = (r * (PXL8_CUBE_SIZE - 1)) / 255; + u32 gi = (g * (PXL8_CUBE_SIZE - 1)) / 255; + u32 bi = (b * (PXL8_CUBE_SIZE - 1)) / 255; + u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri; + return cube->stable[idx]; +} + +void pxl8_palette_cube_get_rgb(const pxl8_palette_cube* cube, u8 idx, u8* r, u8* g, u8* b) { + *r = cube->colors[idx * 3 + 0]; + *g = cube->colors[idx * 3 + 1]; + *b = cube->colors[idx * 3 + 2]; +} + +pxl8_additive_table* pxl8_additive_table_create(const pxl8_palette* pal) { + pxl8_additive_table* table = calloc(1, sizeof(pxl8_additive_table)); + if (!table) return NULL; + pxl8_additive_table_rebuild(table, pal); + return table; +} + +void pxl8_additive_table_destroy(pxl8_additive_table* table) { + free(table); +} + +void pxl8_additive_table_rebuild(pxl8_additive_table* table, const pxl8_palette* pal) { + if (!table || !pal) return; + + for (u32 src = 0; src < 256; src++) { + u8 sr, sg, sb; + pxl8_palette_get_rgb(pal, (u8)src, &sr, &sg, &sb); + + for (u32 dst = 0; dst < 256; dst++) { + u32 idx = src * 256 + dst; + + if (src == PXL8_TRANSPARENT) { + table->table[idx] = (u8)dst; + continue; + } + + u8 dr, dg, db; + pxl8_palette_get_rgb(pal, (u8)dst, &dr, &dg, &db); + + u16 ar = (u16)sr + (u16)dr; + u16 ag = (u16)sg + (u16)dg; + u16 ab = (u16)sb + (u16)db; + if (ar > 255) ar = 255; + if (ag > 255) ag = 255; + if (ab > 255) ab = 255; + + table->table[idx] = find_closest_stable(pal, (u8)ar, (u8)ag, (u8)ab); + } + } +} + +u8 pxl8_additive_blend(const pxl8_additive_table* table, u8 src, u8 dst) { + return table->table[(u32)src * 256 + dst]; +} + +pxl8_overbright_table* pxl8_overbright_table_create(const pxl8_palette* pal) { + pxl8_overbright_table* table = calloc(1, sizeof(pxl8_overbright_table)); + if (!table) return NULL; + pxl8_overbright_table_rebuild(table, pal); + return table; +} + +void pxl8_overbright_table_destroy(pxl8_overbright_table* table) { + free(table); +} + +void pxl8_overbright_table_rebuild(pxl8_overbright_table* table, const pxl8_palette* pal) { + if (!table || !pal) return; + + for (u32 level = 0; level < PXL8_OVERBRIGHT_LEVELS; level++) { + f32 overbright = (f32)level / (f32)(PXL8_OVERBRIGHT_LEVELS - 1); + + for (u32 pal_idx = 0; pal_idx < 256; pal_idx++) { + u32 idx = level * 256 + pal_idx; + + if (pal_idx == PXL8_TRANSPARENT) { + table->table[idx] = PXL8_TRANSPARENT; + continue; + } + + u8 r, g, b; + pxl8_palette_get_rgb(pal, (u8)pal_idx, &r, &g, &b); + + u8 or = (u8)(r + (255 - r) * overbright); + u8 og = (u8)(g + (255 - g) * overbright); + u8 ob = (u8)(b + (255 - b) * overbright); + + table->table[idx] = find_closest_stable(pal, or, og, ob); + } + } +} + +u8 pxl8_overbright_lookup(const pxl8_overbright_table* table, u8 pal_idx, f32 emissive) { + u32 level = (u32)(emissive * (PXL8_OVERBRIGHT_LEVELS - 1)); + if (level >= PXL8_OVERBRIGHT_LEVELS) level = PXL8_OVERBRIGHT_LEVELS - 1; + return table->table[level * 256 + pal_idx]; +} diff --git a/src/gfx/pxl8_blend.h b/src/gfx/pxl8_blend.h new file mode 100644 index 0000000..94f8a30 --- /dev/null +++ b/src/gfx/pxl8_blend.h @@ -0,0 +1,39 @@ +#pragma once + +#include "pxl8_palette.h" +#include "pxl8_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PXL8_CUBE_SIZE 32 +#define PXL8_CUBE_ENTRIES (PXL8_CUBE_SIZE * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) +#define PXL8_BLEND_TABLE_SIZE (256 * 256) +#define PXL8_OVERBRIGHT_LEVELS 16 +#define PXL8_OVERBRIGHT_TABLE_SIZE (256 * PXL8_OVERBRIGHT_LEVELS) + +typedef struct pxl8_additive_table pxl8_additive_table; +typedef struct pxl8_overbright_table pxl8_overbright_table; +typedef struct pxl8_palette_cube pxl8_palette_cube; + +pxl8_additive_table* pxl8_additive_table_create(const pxl8_palette* pal); +void pxl8_additive_table_destroy(pxl8_additive_table* table); +void pxl8_additive_table_rebuild(pxl8_additive_table* table, const pxl8_palette* pal); +u8 pxl8_additive_blend(const pxl8_additive_table* table, u8 src, u8 dst); + +pxl8_overbright_table* pxl8_overbright_table_create(const pxl8_palette* pal); +void pxl8_overbright_table_destroy(pxl8_overbright_table* table); +void pxl8_overbright_table_rebuild(pxl8_overbright_table* table, const pxl8_palette* pal); +u8 pxl8_overbright_lookup(const pxl8_overbright_table* table, u8 pal_idx, f32 emissive); + +pxl8_palette_cube* pxl8_palette_cube_create(const pxl8_palette* pal); +void pxl8_palette_cube_destroy(pxl8_palette_cube* cube); +void pxl8_palette_cube_rebuild(pxl8_palette_cube* cube, const pxl8_palette* pal); +u8 pxl8_palette_cube_lookup(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b); +u8 pxl8_palette_cube_lookup_stable(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b); +void pxl8_palette_cube_get_rgb(const pxl8_palette_cube* cube, u8 idx, u8* r, u8* g, u8* b); + +#ifdef __cplusplus +} +#endif diff --git a/client/src/gfx/pxl8_blit.c b/src/gfx/pxl8_blit.c similarity index 100% rename from client/src/gfx/pxl8_blit.c rename to src/gfx/pxl8_blit.c diff --git a/client/src/gfx/pxl8_blit.h b/src/gfx/pxl8_blit.h similarity index 100% rename from client/src/gfx/pxl8_blit.h rename to src/gfx/pxl8_blit.h diff --git a/client/src/gfx/pxl8_color.h b/src/gfx/pxl8_color.h similarity index 97% rename from client/src/gfx/pxl8_color.h rename to src/gfx/pxl8_color.h index 6f6eb55..285a147 100644 --- a/client/src/gfx/pxl8_color.h +++ b/src/gfx/pxl8_color.h @@ -3,7 +3,7 @@ #include "pxl8_types.h" static inline i32 pxl8_bytes_per_pixel(pxl8_pixel_mode mode) { - return (mode == PXL8_PIXEL_HICOLOR) ? 2 : 1; + return (i32)mode; } static inline u16 pxl8_rgb565_pack(u8 r, u8 g, u8 b) { diff --git a/client/src/gfx/pxl8_colormap.c b/src/gfx/pxl8_colormap.c similarity index 61% rename from client/src/gfx/pxl8_colormap.c rename to src/gfx/pxl8_colormap.c index f967751..4240ed3 100644 --- a/client/src/gfx/pxl8_colormap.c +++ b/src/gfx/pxl8_colormap.c @@ -1,26 +1,63 @@ #include "pxl8_colormap.h" #include +static void rgb_to_hsl(u8 r, u8 g, u8 b, i32* h, i32* s, i32* l) { + i32 max = r > g ? (r > b ? r : b) : (g > b ? g : b); + i32 min = r < g ? (r < b ? r : b) : (g < b ? g : b); + i32 chroma = max - min; + + *l = (max + min) / 2; + + if (chroma == 0) { + *h = 0; + *s = 0; + } else { + i32 denom = 255 - (*l > 127 ? 2 * (*l) - 255 : 255 - 2 * (*l)); + if (denom <= 0) denom = 1; + *s = (chroma * 255) / denom; + if (*s > 255) *s = 255; + + if (max == (i32)r) { + *h = (((i32)g - (i32)b) * 60) / chroma; + if (*h < 0) *h += 360; + } else if (max == (i32)g) { + *h = 120 + (((i32)b - (i32)r) * 60) / chroma; + } else { + *h = 240 + (((i32)r - (i32)g) * 60) / chroma; + } + } +} + 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; + i32 th, ts, tl; + rgb_to_hsl(target_r, target_g, target_b, &th, &ts, &tl); + 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; + u8 pr = c & 0xFF; + u8 pg = (c >> 8) & 0xFF; + u8 pb = (c >> 16) & 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); + i32 ph, ps, pl; + rgb_to_hsl(pr, pg, pb, &ph, &ps, &pl); + + i32 dh = th - ph; + if (dh > 180) dh -= 360; + if (dh < -180) dh += 360; + + i32 ds = ts - ps; + i32 dl = tl - pl; + + u32 dist = (u32)(dh * dh * 4 + ds * ds + dl * dl); if (dist < best_dist) { best_dist = dist; @@ -62,9 +99,9 @@ void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette, const pxl8_le 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 r = c & 0xFF; + u8 g = (c >> 8) & 0xFF; + u8 b = (c >> 16) & 0xFF; u8 target_r = (u8)(dark_r + (r - dark_r) * brightness); u8 target_g = (u8)(dark_g + (g - dark_g) * brightness); diff --git a/client/src/gfx/pxl8_colormap.h b/src/gfx/pxl8_colormap.h similarity index 100% rename from client/src/gfx/pxl8_colormap.h rename to src/gfx/pxl8_colormap.h diff --git a/client/src/gfx/pxl8_cpu.c b/src/gfx/pxl8_cpu.c similarity index 70% rename from client/src/gfx/pxl8_cpu.c rename to src/gfx/pxl8_cpu.c index 6881c06..4ff5b88 100644 --- a/client/src/gfx/pxl8_cpu.c +++ b/src/gfx/pxl8_cpu.c @@ -9,10 +9,109 @@ struct pxl8_cpu_render_target { u8* framebuffer; u32 height; u32 width; - f32* zbuffer; + u16* zbuffer; u32* light_accum; }; +static inline u16 depth_to_u16(f32 z) { + f32 d = (z + 1.0f) * 32767.5f; + if (d < 0.0f) d = 0.0f; + if (d > 65535.0f) d = 65535.0f; + return (u16)d; +} + +static inline f32 calc_light_intensity(const pxl8_light* light, pxl8_vec3 world_pos, pxl8_vec3 normal) { + pxl8_vec3 to_light = pxl8_vec3_sub(light->position, world_pos); + f32 dist_sq = pxl8_vec3_dot(to_light, to_light); + + if (dist_sq >= light->radius_sq) { + return 0.0f; + } + + f32 intensity_norm = light->intensity * (1.0f / 255.0f); + + if (dist_sq < 0.001f) { + return intensity_norm; + } + + f32 inv_dist = pxl8_fast_inv_sqrt(dist_sq); + pxl8_vec3 light_dir = pxl8_vec3_scale(to_light, inv_dist); + f32 n_dot_l = pxl8_vec3_dot(normal, light_dir); + + if (n_dot_l <= 0.0f) { + return 0.0f; + } + + f32 falloff = 1.0f - dist_sq * light->inv_radius_sq; + return n_dot_l * falloff * intensity_norm; +} + +typedef struct pxl8_light_result { + u8 light; + u32 light_color; +} pxl8_light_result; + +static pxl8_light_result calc_vertex_light( + pxl8_vec3 world_pos, + pxl8_vec3 normal, + const pxl8_3d_frame* frame +) { + f32 intensity = 0.25f + frame->uniforms.ambient * (1.0f / 255.0f); + + f32 accum_r = 0.0f; + f32 accum_g = 0.0f; + f32 accum_b = 0.0f; + f32 total_dynamic = 0.0f; + + f32 celestial_dot = -pxl8_vec3_dot(normal, frame->uniforms.celestial_dir); + if (celestial_dot > 0.0f) { + intensity += celestial_dot * frame->uniforms.celestial_intensity; + } + + f32 sky_factor = normal.y * 0.5f + 0.5f; + if (sky_factor < 0.0f) sky_factor = 0.0f; + intensity += sky_factor * frame->uniforms.celestial_intensity * 0.3f; + + for (u32 i = 0; i < frame->uniforms.num_lights; i++) { + const pxl8_light* light = &frame->uniforms.lights[i]; + f32 contrib = calc_light_intensity(light, world_pos, normal); + if (contrib > 0.0f) { + intensity += contrib; + total_dynamic += contrib; + + accum_r += light->r * contrib; + accum_g += light->g * contrib; + accum_b += light->b * contrib; + } + } + + u32 light_color = 0; + if (total_dynamic > 0.001f) { + f32 inv_total = 1.0f / total_dynamic; + f32 tint_strength = total_dynamic * 1.5f; + if (tint_strength > 1.0f) tint_strength = 1.0f; + + u32 r = (u32)(accum_r * inv_total); + u32 g = (u32)(accum_g * inv_total); + u32 b = (u32)(accum_b * inv_total); + u32 a = (u32)(tint_strength * 255.0f); + + if (r > 255) r = 255; + if (g > 255) g = 255; + if (b > 255) b = 255; + + light_color = r | (g << 8) | (b << 16) | (a << 24); + } + + if (intensity < 0.0f) intensity = 0.0f; + if (intensity > 1.0f) intensity = 1.0f; + + return (pxl8_light_result){ + .light = (u8)(intensity * 255.0f), + .light_color = light_color, + }; +} + #define PXL8_MAX_TARGET_STACK 8 struct pxl8_cpu_backend { @@ -23,6 +122,8 @@ struct pxl8_cpu_backend { const u32* palette; pxl8_3d_frame frame; pxl8_mat4 mvp; + u32* output; + u32 output_size; }; static inline void clip_line_2d(i32* x0, i32* y0, i32* x1, i32* y1, i32 w, i32 h, bool* visible) { @@ -90,6 +191,15 @@ pxl8_cpu_backend* pxl8_cpu_create(u32 width, u32 height) { cpu->target_stack[0] = base_target; cpu->target_stack_depth = 1; cpu->current_target = base_target; + + cpu->output_size = width * height; + cpu->output = calloc(cpu->output_size, sizeof(u32)); + if (!cpu->output) { + pxl8_cpu_destroy_render_target(base_target); + free(cpu); + return NULL; + } + return cpu; } @@ -98,6 +208,7 @@ void pxl8_cpu_destroy(pxl8_cpu_backend* cpu) { for (u32 i = 0; i < cpu->target_stack_depth; i++) { pxl8_cpu_destroy_render_target(cpu->target_stack[i]); } + free(cpu->output); free(cpu); } @@ -121,7 +232,10 @@ 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)); + u32 count = render_target->width * render_target->height; + for (u32 i = 0; i < count; i++) { + render_target->zbuffer[i] = 0xFFFF; + } } if (render_target->light_accum) { memset(render_target->light_accum, 0, render_target->width * render_target->height * sizeof(u32)); @@ -275,8 +389,44 @@ typedef struct { f32 u, v; u8 color; u8 light; + u32 light_color; } vertex_output; +static inline u32 blend_tri_light_color(u32 lc0, u32 lc1, u32 lc2) { + u32 a0 = (lc0 >> 24) & 0xFF; + u32 a1 = (lc1 >> 24) & 0xFF; + u32 a2 = (lc2 >> 24) & 0xFF; + u32 total_a = a0 + a1 + a2; + if (total_a == 0) return 0; + + u32 r = ((lc0 & 0xFF) * a0 + (lc1 & 0xFF) * a1 + (lc2 & 0xFF) * a2) / total_a; + u32 g = (((lc0 >> 8) & 0xFF) * a0 + ((lc1 >> 8) & 0xFF) * a1 + ((lc2 >> 8) & 0xFF) * a2) / total_a; + u32 b = (((lc0 >> 16) & 0xFF) * a0 + ((lc1 >> 16) & 0xFF) * a1 + ((lc2 >> 16) & 0xFF) * a2) / total_a; + u32 a = total_a / 3; + if (a > 255) a = 255; + + return r | (g << 8) | (b << 16) | (a << 24); +} + +static inline u32 lerp_light_color(u32 a, u32 b, f32 t) { + u32 ar = a & 0xFF; + u32 ag = (a >> 8) & 0xFF; + u32 ab = (a >> 16) & 0xFF; + u32 aa = (a >> 24) & 0xFF; + + u32 br = b & 0xFF; + u32 bg = (b >> 8) & 0xFF; + u32 bb = (b >> 16) & 0xFF; + u32 ba = (b >> 24) & 0xFF; + + u32 or = ar + (u32)((i32)(br - ar) * t); + u32 og = ag + (u32)((i32)(bg - ag) * t); + u32 ob = ab + (u32)((i32)(bb - ab) * t); + u32 oa = aa + (u32)((i32)(ba - aa) * t); + + return or | (og << 8) | (ob << 16) | (oa << 24); +} + 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; @@ -293,6 +443,7 @@ static vertex_output lerp_vertex(const vertex_output* a, const vertex_output* b, 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); + out.light_color = lerp_light_color(a->light_color, b->light_color, t); return out; } @@ -348,6 +499,8 @@ typedef struct { 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; + u32 lc0, lc1, lc2; + f32 c0_w, c1_w, c2_w; i32 y_start, y_end, total_height; f32 inv_total; u32 target_width, target_height; @@ -419,6 +572,14 @@ static bool setup_triangle( setup->l1_w = sorted[1]->light * setup->w1_recip; setup->l2_w = sorted[2]->light * setup->w2_recip; + setup->lc0 = sorted[0]->light_color; + setup->lc1 = sorted[1]->light_color; + setup->lc2 = sorted[2]->light_color; + + setup->c0_w = (f32)sorted[0]->color * setup->w0_recip; + setup->c1_w = (f32)sorted[1]->color * setup->w1_recip; + setup->c2_w = (f32)sorted[2]->color * setup->w2_recip; + setup->inv_total = 1.0f / (f32)setup->total_height; setup->target_width = width; setup->target_height = height; @@ -432,6 +593,8 @@ static void rasterize_triangle_opaque( const pxl8_atlas* textures, u32 texture_id, bool dither ) { pxl8_cpu_render_target* render_target = cpu->current_target; + u32* light_accum = render_target->light_accum; + u32 tri_light_color = light_accum ? blend_tri_light_color(setup->lc0, setup->lc1, setup->lc2) : 0; 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; @@ -516,7 +679,7 @@ static void rasterize_triangle_opaque( u32 row_start = (u32)y * render_target->width; u8* prow = render_target->framebuffer + row_start; - f32* zrow = render_target->zbuffer + row_start; + u16* zrow = render_target->zbuffer + row_start; const i32 SUBDIV = 32; i32 x = x_start; @@ -553,7 +716,8 @@ static void rasterize_triangle_opaque( f32 z_a = z; for (i32 px = x; px <= span_end; px++) { - if (z_a <= zrow[px]) { + u16 z16 = depth_to_u16(z_a); + if (z16 < 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; @@ -565,7 +729,10 @@ static void rasterize_triangle_opaque( } u8 pal_idx = cpu->colormap ? pxl8_colormap_lookup(cpu->colormap, tex_idx, light) : tex_idx; prow[px] = pal_idx; - zrow[px] = z_a; + zrow[px] = z16; + if (light_accum) { + light_accum[row_start + (u32)px] = tri_light_color; + } } } @@ -587,11 +754,9 @@ static void rasterize_triangle_opaque( static void rasterize_triangle_passthrough( pxl8_cpu_backend* cpu, - const tri_setup* setup, - const vertex_output* vo0 + const tri_setup* setup ) { 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; @@ -603,20 +768,33 @@ static void rasterize_triangle_passthrough( f32 ax = (f32)setup->x0 + (f32)(setup->x2 - setup->x0) * alpha; f32 az = setup->z0 + (setup->z2 - setup->z0) * alpha; - f32 bx, bz; + f32 a_wr = setup->w0_recip + (setup->w2_recip - setup->w0_recip) * alpha; + f32 a_cw = setup->c0_w + (setup->c2_w - setup->c0_w) * alpha; + + f32 bx, bz, b_wr, b_cw; 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_cw = setup->c1_w + (setup->c2_w - setup->c1_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_cw = setup->c0_w + (setup->c1_w - setup->c0_w) * beta; } - f32 x_start_f, x_end_f, z_start, z_end; + f32 x_start_f, x_end_f, z_start, z_end, wr_start, wr_end, cw_start, cw_end; if (ax <= bx) { - x_start_f = ax; x_end_f = bx; z_start = az; z_end = bz; + x_start_f = ax; x_end_f = bx; + z_start = az; z_end = bz; + wr_start = a_wr; wr_end = b_wr; + cw_start = a_cw; cw_end = b_cw; } else { - x_start_f = bx; x_end_f = ax; z_start = bz; z_end = az; + x_start_f = bx; x_end_f = ax; + z_start = bz; z_end = az; + wr_start = b_wr; wr_end = a_wr; + cw_start = b_cw; cw_end = a_cw; } i32 x_start_orig = (i32)(x_start_f + 0.5f); @@ -630,19 +808,30 @@ static void rasterize_triangle_passthrough( 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 dcw = (cw_end - cw_start) * inv_width; + f32 skip = (f32)(x_start - x_start_orig); f32 z = z_start + dz * skip; + f32 wr = wr_start + dwr * skip; + f32 cw = cw_start + dcw * skip; u32 row_start = (u32)y * render_target->width; u8* prow = render_target->framebuffer + row_start; - f32* zrow = render_target->zbuffer + row_start; + u16* zrow = render_target->zbuffer + row_start; for (i32 px = x_start; px <= x_end; px++) { - if (z <= zrow[px]) { + u16 z16 = depth_to_u16(z); + if (z16 < zrow[px]) { + f32 w = 1.0f / wr; + f32 color_f = cw * w; + u8 color = pxl8_dither_float(color_f, (u32)px, (u32)y); prow[px] = color; - zrow[px] = z; + zrow[px] = z16; } z += dz; + wr += dwr; + cw += dcw; } } } @@ -653,6 +842,8 @@ static void rasterize_triangle_alpha( const pxl8_atlas* textures, u32 texture_id, u8 mat_alpha, bool dither ) { pxl8_cpu_render_target* render_target = cpu->current_target; + u32* light_accum = render_target->light_accum; + u32 tri_light_color = light_accum ? blend_tri_light_color(setup->lc0, setup->lc1, setup->lc2) : 0; 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; @@ -737,7 +928,7 @@ static void rasterize_triangle_alpha( u32 row_start = (u32)y * render_target->width; u8* prow = render_target->framebuffer + row_start; - f32* zrow = render_target->zbuffer + row_start; + u16* zrow = render_target->zbuffer + row_start; const i32 SUBDIV = 32; i32 x = x_start; @@ -787,7 +978,11 @@ static void rasterize_triangle_alpha( if (mat_alpha >= 128) { prow[px] = src_idx; - if (z_a <= zrow[px]) zrow[px] = z_a; + u16 z16 = depth_to_u16(z_a); + if (z16 < zrow[px]) zrow[px] = z16; + if (light_accum) { + light_accum[row_start + (u32)px] = tri_light_color; + } } } @@ -824,7 +1019,7 @@ static void dispatch_triangle( 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); + rasterize_triangle_passthrough(cpu, &setup); } else { rasterize_triangle_opaque(cpu, &setup, textures, material->texture_id, material->dither); } @@ -833,13 +1028,13 @@ static void dispatch_triangle( void pxl8_cpu_draw_mesh( pxl8_cpu_backend* cpu, const pxl8_mesh* mesh, - pxl8_mat4 model, - pxl8_material material, + const pxl8_mat4* model, + const pxl8_material* material, const pxl8_atlas* textures ) { - if (!cpu || !mesh || mesh->index_count < 3 || !cpu->current_target) return; + if (!cpu || !mesh || !model || !material || mesh->index_count < 3 || !cpu->current_target) return; - pxl8_mat4 mv = pxl8_mat4_mul(cpu->frame.view, model); + 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; @@ -863,9 +1058,9 @@ void pxl8_cpu_draw_mesh( 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); + 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}; @@ -882,15 +1077,36 @@ void pxl8_cpu_draw_mesh( 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; + if (material->dynamic_lighting) { + pxl8_vec3 n0 = pxl8_vec3_normalize(pxl8_mat4_mul_vec3(*model, v0->normal)); + pxl8_vec3 n1 = pxl8_vec3_normalize(pxl8_mat4_mul_vec3(*model, v1->normal)); + pxl8_vec3 n2 = pxl8_vec3_normalize(pxl8_mat4_mul_vec3(*model, v2->normal)); + + pxl8_light_result lr0 = calc_vertex_light(vo0.world_pos, n0, &cpu->frame); + pxl8_light_result lr1 = calc_vertex_light(vo1.world_pos, n1, &cpu->frame); + pxl8_light_result lr2 = calc_vertex_light(vo2.world_pos, n2, &cpu->frame); + + vo0.light = lr0.light; + vo1.light = lr1.light; + vo2.light = lr2.light; + + vo0.light_color = lr0.light_color; + vo1.light_color = lr1.light_color; + vo2.light_color = lr2.light_color; + } else { + vo0.light = 255; + vo1.light = 255; + vo2.light = 255; + vo0.light_color = 0; + vo1.light_color = 0; + vo2.light_color = 0; + } 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); + dispatch_triangle(cpu, &clipped[t], &clipped[t+1], &clipped[t+2], textures, material); } } } @@ -964,7 +1180,7 @@ pxl8_cpu_render_target* pxl8_cpu_create_render_target(const pxl8_cpu_render_targ } if (desc->with_depth) { - target->zbuffer = calloc(size, sizeof(f32)); + target->zbuffer = calloc(size, sizeof(u16)); if (!target->zbuffer) { free(target->framebuffer); free(target); @@ -1073,3 +1289,202 @@ 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) { return target ? target->width : 0; } + +u32* pxl8_cpu_render_target_get_light_accum(pxl8_cpu_render_target* target) { + return target ? target->light_accum : NULL; +} + +u32* pxl8_cpu_get_output(pxl8_cpu_backend* cpu) { + return cpu ? cpu->output : NULL; +} + +void pxl8_cpu_render_glows( + pxl8_cpu_backend* cpu, + const pxl8_glow_source* glows, + u32 glow_count, + const pxl8_additive_table* additive, + const pxl8_palette_cube* palette_cube, + const pxl8_overbright_table* overbright +) { + (void)overbright; + if (!cpu || cpu->target_stack_depth == 0) return; + + pxl8_cpu_render_target* target = cpu->target_stack[cpu->target_stack_depth - 1]; + if (!target || !target->framebuffer) return; + + u32 width = target->width; + u32 height = target->height; + u8* pixels = target->framebuffer; + u16* zbuffer = target->zbuffer; + u32* light_accum = target->light_accum; + + for (u32 gi = 0; gi < glow_count; gi++) { + const pxl8_glow_source* glow = &glows[gi]; + i32 cx = glow->x; + i32 cy = glow->y; + i32 radius = glow->radius; + + if (radius <= 0 || glow->intensity == 0) continue; + + i32 x0 = cx - radius; + i32 y0 = cy - radius; + i32 x1 = cx + radius; + i32 y1 = cy + radius; + + if (x0 < 0) x0 = 0; + if (y0 < 0) y0 = 0; + if (x1 >= (i32)width) x1 = (i32)width - 1; + if (y1 >= (i32)height) y1 = (i32)height - 1; + + if (x0 >= x1 || y0 >= y1) continue; + + u16 glow_depth = glow->depth; + u8 base_color = glow->color; + f32 base_intensity = glow->intensity / 255.0f; + f32 radius_f = (f32)radius; + f32 radius_sq = radius_f * radius_f; + f32 inv_radius_sq = 1.0f / radius_sq; + f32 inv_radius = 1.0f / radius_f; + + for (i32 y = y0; y <= y1; y++) { + u32 row = (u32)y * width; + f32 dy = (f32)(y - cy); + + for (i32 x = x0; x <= x1; x++) { + u32 idx = row + (u32)x; + + if (zbuffer && glow_depth > zbuffer[idx]) continue; + + f32 dx = (f32)(x - cx); + f32 intensity = 0.0f; + + switch (glow->shape) { + case PXL8_GLOW_CIRCLE: { + f32 dist_sq = dx * dx + dy * dy; + if (dist_sq >= radius_sq) continue; + f32 falloff = 1.0f - dist_sq * inv_radius_sq; + intensity = base_intensity * falloff * falloff; + break; + } + case PXL8_GLOW_DIAMOND: { + f32 abs_dx = dx < 0 ? -dx : dx; + f32 abs_dy = dy < 0 ? -dy : dy; + f32 manhattan = abs_dx + abs_dy; + if (manhattan >= radius_f) continue; + f32 falloff = 1.0f - manhattan * inv_radius; + intensity = base_intensity * falloff * falloff; + break; + } + case PXL8_GLOW_SHAFT: { + f32 abs_dx = dx < 0 ? -dx : dx; + f32 abs_dy = dy < 0 ? -dy : dy; + f32 height_f = glow->height > 0 ? (f32)glow->height : radius_f; + if (abs_dx >= radius_f || abs_dy >= height_f) continue; + f32 falloff_x = 1.0f - abs_dx * inv_radius; + f32 falloff_y = 1.0f - abs_dy / height_f; + intensity = base_intensity * falloff_x * falloff_x * falloff_y; + break; + } + } + + if (intensity < 0.02f) continue; + + u8 int_val = intensity < 1.0f ? (u8)(intensity * 255.0f) : 255; + u8 light_level = pxl8_ordered_dither(int_val, (u32)x, (u32)y); + + // Skip very dark pixels to create stippled transparency effect + if (light_level < 8) continue; + + u8 shaded = pxl8_colormap_lookup(cpu->colormap, base_color, light_level); + + if (intensity > 1.0f && palette_cube) { + f32 over = intensity - 1.0f; + if (over > 1.0f) over = 1.0f; + u8 r, g, b; + pxl8_palette_cube_get_rgb(palette_cube, shaded, &r, &g, &b); + u8 or = (u8)(r + (255 - r) * over); + u8 og = (u8)(g + (255 - g) * over); + u8 ob = (u8)(b + (255 - b) * over); + shaded = pxl8_palette_cube_lookup_stable(palette_cube, or, og, ob); + } + + u8 dst = pixels[idx]; + if (additive) { + pixels[idx] = pxl8_additive_blend(additive, shaded, dst); + } else { + pixels[idx] = shaded; + } + + if (light_accum) { + light_accum[idx] = 0; + } + } + } + } +} + +void pxl8_cpu_resolve(pxl8_cpu_backend* cpu) { + if (!cpu || !cpu->palette || cpu->target_stack_depth == 0) return; + + pxl8_cpu_render_target* target = cpu->target_stack[0]; + if (!target || !target->framebuffer) return; + + u32 pixel_count = target->width * target->height; + const u8* pixels = target->framebuffer; + const u32* light_accum = target->light_accum; + const u32* palette = cpu->palette; + u32* output = cpu->output; + + for (u32 i = 0; i < pixel_count; i++) { + u32 base = palette[pixels[i]]; + + if (light_accum && light_accum[i] != 0) { + u32 light_color = light_accum[i]; + u32 tint_alpha = (light_color >> 24) & 0xFF; + + if (tint_alpha > 0) { + u32 lr = light_color & 0xFF; + u32 lg = (light_color >> 8) & 0xFF; + u32 lb = (light_color >> 16) & 0xFF; + + u32 br = base & 0xFF; + u32 bg = (base >> 8) & 0xFF; + u32 bb = (base >> 16) & 0xFF; + u32 ba = (base >> 24) & 0xFF; + + u32 t = (tint_alpha * tint_alpha) / 255; + u32 inv_t = 255 - t; + + u32 lr_norm = (lr * 255) / 240; + u32 lg_norm = (lg * 255) / 240; + u32 lb_norm = (lb * 255) / 220; + + u32 or = ((br * inv_t + (br * lr_norm / 255) * t) / 255); + u32 og = ((bg * inv_t + (bg * lg_norm / 255) * t) / 255); + u32 ob = ((bb * inv_t + (bb * lb_norm / 255) * t) / 255); + + if (or > 255) or = 255; + if (og > 255) og = 255; + if (ob > 255) ob = 255; + + output[i] = or | (og << 8) | (ob << 16) | (ba << 24); + continue; + } + } + + output[i] = base; + } + + for (u32 t = 1; t < cpu->target_stack_depth; t++) { + pxl8_cpu_render_target* overlay = cpu->target_stack[t]; + if (!overlay || !overlay->framebuffer) continue; + + const u8* overlay_pixels = overlay->framebuffer; + for (u32 i = 0; i < pixel_count; i++) { + u8 idx = overlay_pixels[i]; + if (idx != 0) { + output[i] = palette[idx]; + } + } + } +} diff --git a/client/src/gfx/pxl8_cpu.h b/src/gfx/pxl8_cpu.h similarity index 83% rename from client/src/gfx/pxl8_cpu.h rename to src/gfx/pxl8_cpu.h index 35f99a0..0f4ab5b 100644 --- a/client/src/gfx/pxl8_cpu.h +++ b/src/gfx/pxl8_cpu.h @@ -1,7 +1,10 @@ #pragma once #include "pxl8_atlas.h" +#include "pxl8_blend.h" #include "pxl8_colormap.h" +#include "pxl8_gfx.h" +#include "pxl8_gfx3d.h" #include "pxl8_math.h" #include "pxl8_mesh.h" #include "pxl8_types.h" @@ -20,19 +23,6 @@ typedef struct pxl8_cpu_render_target_desc { 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); @@ -57,8 +47,8 @@ void pxl8_cpu_draw_line_3d(pxl8_cpu_backend* cpu, pxl8_vec3 v0, pxl8_vec3 v1, u8 void pxl8_cpu_draw_mesh( pxl8_cpu_backend* cpu, const pxl8_mesh* mesh, - pxl8_mat4 model, - pxl8_material material, + const pxl8_mat4* model, + const pxl8_material* material, const pxl8_atlas* textures ); @@ -70,9 +60,21 @@ void pxl8_cpu_draw_mesh_wireframe( ); u8* pxl8_cpu_get_framebuffer(pxl8_cpu_backend* cpu); +u32* pxl8_cpu_get_output(pxl8_cpu_backend* cpu); u32 pxl8_cpu_get_height(const pxl8_cpu_backend* cpu); u32 pxl8_cpu_get_width(const pxl8_cpu_backend* cpu); +void pxl8_cpu_render_glows( + pxl8_cpu_backend* cpu, + const pxl8_glow_source* glows, + u32 glow_count, + const pxl8_additive_table* additive, + const pxl8_palette_cube* palette_cube, + const pxl8_overbright_table* overbright +); + +void pxl8_cpu_resolve(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); @@ -88,6 +90,7 @@ 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); +u32* pxl8_cpu_render_target_get_light_accum(pxl8_cpu_render_target* target); #ifdef __cplusplus } diff --git a/client/src/gfx/pxl8_dither.c b/src/gfx/pxl8_dither.c similarity index 100% rename from client/src/gfx/pxl8_dither.c rename to src/gfx/pxl8_dither.c diff --git a/client/src/gfx/pxl8_dither.h b/src/gfx/pxl8_dither.h similarity index 100% rename from client/src/gfx/pxl8_dither.h rename to src/gfx/pxl8_dither.h diff --git a/client/src/gfx/pxl8_font.c b/src/gfx/pxl8_font.c similarity index 100% rename from client/src/gfx/pxl8_font.c rename to src/gfx/pxl8_font.c diff --git a/client/src/gfx/pxl8_font.h b/src/gfx/pxl8_font.h similarity index 100% rename from client/src/gfx/pxl8_font.h rename to src/gfx/pxl8_font.h diff --git a/client/src/gfx/pxl8_gfx.c b/src/gfx/pxl8_gfx.c similarity index 83% rename from client/src/gfx/pxl8_gfx.c rename to src/gfx/pxl8_gfx.c index 565280b..a0f42e8 100644 --- a/client/src/gfx/pxl8_gfx.c +++ b/src/gfx/pxl8_gfx.c @@ -6,6 +6,7 @@ #include "pxl8_ase.h" #include "pxl8_atlas.h" #include "pxl8_backend.h" +#include "pxl8_blend.h" #include "pxl8_blit.h" #include "pxl8_color.h" #include "pxl8_colormap.h" @@ -24,6 +25,7 @@ typedef struct pxl8_sprite_cache_entry { } pxl8_sprite_cache_entry; struct pxl8_gfx { + pxl8_additive_table* additive_table; pxl8_atlas* atlas; pxl8_gfx_backend backend; pxl8_colormap* colormap; @@ -33,7 +35,9 @@ struct pxl8_gfx { pxl8_frustum frustum; const pxl8_hal* hal; bool initialized; + pxl8_overbright_table* overbright_table; pxl8_palette* palette; + pxl8_palette_cube* palette_cube; pxl8_pixel_mode pixel_mode; void* platform_data; pxl8_sprite_cache_entry* sprite_cache; @@ -183,11 +187,14 @@ pxl8_gfx* pxl8_gfx_create( void pxl8_gfx_destroy(pxl8_gfx* gfx) { if (!gfx) return; + pxl8_additive_table_destroy(gfx->additive_table); pxl8_atlas_destroy(gfx->atlas); if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) { pxl8_cpu_destroy(gfx->backend.cpu); } free(gfx->colormap); + pxl8_overbright_table_destroy(gfx->overbright_table); + pxl8_palette_cube_destroy(gfx->palette_cube); pxl8_palette_destroy(gfx->palette); free(gfx->sprite_cache); @@ -309,15 +316,30 @@ pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx) { 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; + if (gfx->backend.type == PXL8_GFX_BACKEND_CPU && gfx->pixel_mode == PXL8_PIXEL_INDEXED) { + pxl8_cpu_resolve(gfx->backend.cpu); + u32* output = pxl8_cpu_get_output(gfx->backend.cpu); + if (output) { + gfx->hal->upload_texture( + gfx->platform_data, + output, + gfx->framebuffer_width, + gfx->framebuffer_height, + PXL8_PIXEL_RGBA, + NULL + ); + return; + } + } + 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 + gfx->pixel_mode, + colors ); } @@ -438,6 +460,7 @@ void pxl8_2d_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) { if (!gfx || !text) return; u8* framebuffer = NULL; + u32* light_accum = NULL; i32 fb_width = 0; i32 fb_height = 0; @@ -446,6 +469,7 @@ void pxl8_2d_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) { 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); + light_accum = pxl8_cpu_render_target_get_light_accum(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; @@ -483,7 +507,9 @@ void pxl8_2d_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) { } if (pixel_bit) { - framebuffer[py * fb_width + px] = (u8)color; + i32 idx = py * fb_width + px; + framebuffer[idx] = (u8)color; + if (light_accum) light_accum[idx] = 0; } } } @@ -568,30 +594,32 @@ void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt) { } } +pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms) { + pxl8_3d_frame frame = {0}; + if (!camera) return frame; + + frame.view = pxl8_3d_camera_get_view(camera); + frame.projection = pxl8_3d_camera_get_projection(camera); + frame.camera_pos = pxl8_3d_camera_get_position(camera); + frame.camera_dir = pxl8_3d_camera_get_forward(camera); + frame.near_clip = 1.0f; + frame.far_clip = 4096.0f; + + if (uniforms) { + frame.uniforms = *uniforms; + } + + return frame; +} + 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_3d_frame frame = pxl8_3d_frame_from_camera(camera, uniforms); - pxl8_mat4 vp = pxl8_mat4_mul(projection, view); + pxl8_mat4 vp = pxl8_mat4_mul(frame.projection, frame.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); @@ -639,8 +667,8 @@ 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) { - if (!gfx || !mesh) return; +void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_material* material) { + if (!gfx || !mesh || !model || !material) return; switch (gfx->backend.type) { case PXL8_GFX_BACKEND_CPU: pxl8_cpu_draw_mesh(gfx->backend.cpu, mesh, model, material, gfx->atlas); @@ -704,3 +732,67 @@ void pxl8_gfx_pop_target(pxl8_gfx* gfx) { break; } } + +static void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx) { + if (!gfx || !gfx->palette) return; + + if (!gfx->additive_table) { + gfx->additive_table = pxl8_additive_table_create(gfx->palette); + } + if (!gfx->overbright_table) { + gfx->overbright_table = pxl8_overbright_table_create(gfx->palette); + } + if (!gfx->palette_cube) { + gfx->palette_cube = pxl8_palette_cube_create(gfx->palette); + } +} + +void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx) { + if (!gfx || !gfx->palette) return; + + if (gfx->additive_table) { + pxl8_additive_table_rebuild(gfx->additive_table, gfx->palette); + } + if (gfx->overbright_table) { + pxl8_overbright_table_rebuild(gfx->overbright_table, gfx->palette); + } + if (gfx->palette_cube) { + pxl8_palette_cube_rebuild(gfx->palette_cube, gfx->palette); + } +} + +void pxl8_gfx_colormap_update(pxl8_gfx* gfx) { + if (!gfx || !gfx->palette || !gfx->colormap) return; + u32* colors = pxl8_palette_colors(gfx->palette); + if (colors) { + pxl8_colormap_generate(gfx->colormap, colors, NULL); + } +} + +void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count) { + if (!gfx || !params || count == 0) return; + + switch (effect) { + case PXL8_GFX_EFFECT_GLOWS: { + pxl8_gfx_ensure_blend_tables(gfx); + if (!gfx->additive_table || !gfx->overbright_table || !gfx->palette_cube) return; + + const pxl8_glow_source* glows = (const pxl8_glow_source*)params; + switch (gfx->backend.type) { + case PXL8_GFX_BACKEND_CPU: + pxl8_cpu_render_glows( + gfx->backend.cpu, + glows, + count, + gfx->additive_table, + gfx->palette_cube, + gfx->overbright_table + ); + break; + case PXL8_GFX_BACKEND_GPU: + break; + } + break; + } + } +} diff --git a/client/src/gfx/pxl8_gfx.h b/src/gfx/pxl8_gfx.h similarity index 54% rename from client/src/gfx/pxl8_gfx.h rename to src/gfx/pxl8_gfx.h index ac3acd4..11fb2e4 100644 --- a/client/src/gfx/pxl8_gfx.h +++ b/src/gfx/pxl8_gfx.h @@ -8,6 +8,57 @@ typedef struct pxl8_gfx pxl8_gfx; +typedef enum pxl8_gfx_effect { + PXL8_GFX_EFFECT_GLOWS = 0, +} pxl8_gfx_effect; + +#define PXL8_MAX_GLOWS 256 + +typedef enum pxl8_glow_shape { + PXL8_GLOW_CIRCLE = 0, + PXL8_GLOW_DIAMOND = 1, + PXL8_GLOW_SHAFT = 2, +} pxl8_glow_shape; + +typedef struct pxl8_glow_source { + u8 color; + u16 depth; + u8 height; + u16 intensity; + u8 radius; + pxl8_glow_shape shape; + i16 x; + i16 y; +} pxl8_glow_source; + +static inline pxl8_glow_source pxl8_glow_create(i32 x, i32 y, u8 radius, u16 intensity, u8 color) { + return (pxl8_glow_source){ + .color = color, + .depth = 0xFFFF, + .height = 0, + .intensity = intensity, + .radius = radius, + .shape = PXL8_GLOW_CIRCLE, + .x = (i16)x, + .y = (i16)y, + }; +} + +static inline pxl8_glow_source pxl8_glow_with_depth(pxl8_glow_source g, u16 depth) { + g.depth = depth; + return g; +} + +static inline pxl8_glow_source pxl8_glow_with_shape(pxl8_glow_source g, pxl8_glow_shape shape) { + g.shape = shape; + return g; +} + +static inline pxl8_glow_source pxl8_glow_with_height(pxl8_glow_source g, u8 height) { + g.height = height; + return g; +} + #ifdef __cplusplus extern "C" { #endif @@ -43,6 +94,10 @@ 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); +void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count); +void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx); +void pxl8_gfx_colormap_update(pxl8_gfx* gfx); + #ifdef __cplusplus } #endif diff --git a/client/src/gfx/pxl8_gfx2d.h b/src/gfx/pxl8_gfx2d.h similarity index 100% rename from client/src/gfx/pxl8_gfx2d.h rename to src/gfx/pxl8_gfx2d.h diff --git a/src/gfx/pxl8_gfx3d.h b/src/gfx/pxl8_gfx3d.h new file mode 100644 index 0000000..61c5a12 --- /dev/null +++ b/src/gfx/pxl8_gfx3d.h @@ -0,0 +1,72 @@ +#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; + +#define PXL8_MAX_LIGHTS 16 + +typedef struct pxl8_light { + pxl8_vec3 position; + u8 r, g, b; + u8 intensity; + f32 radius; + f32 radius_sq; + f32 inv_radius_sq; +} pxl8_light; + +static inline pxl8_light pxl8_light_create(pxl8_vec3 pos, u8 r, u8 g, u8 b, u8 intensity, f32 radius) { + f32 radius_sq = radius * radius; + return (pxl8_light){ + .position = pos, + .r = r, .g = g, .b = b, + .intensity = intensity, + .radius = radius, + .radius_sq = radius_sq, + .inv_radius_sq = radius_sq > 0.0f ? 1.0f / radius_sq : 0.0f, + }; +} + +typedef struct pxl8_3d_uniforms { + u8 ambient; + pxl8_vec3 celestial_dir; + f32 celestial_intensity; + u8 fog_color; + f32 fog_density; + pxl8_light lights[PXL8_MAX_LIGHTS]; + u32 num_lights; + f32 time; +} pxl8_3d_uniforms; + +typedef struct pxl8_3d_frame { + pxl8_3d_uniforms uniforms; + pxl8_vec3 camera_dir; + pxl8_vec3 camera_pos; + f32 far_clip; + f32 near_clip; + pxl8_mat4 projection; + pxl8_mat4 view; +} pxl8_3d_frame; + +#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, const pxl8_mat4* model, const 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); + +pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms); + +#ifdef __cplusplus +} +#endif diff --git a/client/src/gfx/pxl8_mesh.c b/src/gfx/pxl8_mesh.c similarity index 100% rename from client/src/gfx/pxl8_mesh.c rename to src/gfx/pxl8_mesh.c diff --git a/client/src/gfx/pxl8_mesh.h b/src/gfx/pxl8_mesh.h similarity index 97% rename from client/src/gfx/pxl8_mesh.h rename to src/gfx/pxl8_mesh.h index cbfad5c..78ac881 100644 --- a/client/src/gfx/pxl8_mesh.h +++ b/src/gfx/pxl8_mesh.h @@ -62,7 +62,7 @@ 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) { +static inline pxl8_material pxl8_material_create(u32 texture_id) { return (pxl8_material){ .texture_id = texture_id, .alpha = 255, diff --git a/client/src/gfx/pxl8_palette.c b/src/gfx/pxl8_palette.c similarity index 99% rename from client/src/gfx/pxl8_palette.c rename to src/gfx/pxl8_palette.c index 9d16390..9cdc3d2 100644 --- a/client/src/gfx/pxl8_palette.c +++ b/src/gfx/pxl8_palette.c @@ -449,7 +449,7 @@ 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_create(u8 start, u8 len, u16 period) { pxl8_cycle_range range = { .easing = PXL8_EASE_LINEAR, .interpolate = true, diff --git a/client/src/gfx/pxl8_palette.h b/src/gfx/pxl8_palette.h similarity index 96% rename from client/src/gfx/pxl8_palette.h rename to src/gfx/pxl8_palette.h index 69b38db..9d99eb5 100644 --- a/client/src/gfx/pxl8_palette.h +++ b/src/gfx/pxl8_palette.h @@ -62,7 +62,7 @@ void pxl8_palette_set_cycle_colors(pxl8_palette* pal, u8 slot, const u32* colors 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_create(u8 start, u8 len, u16 period); pxl8_cycle_range pxl8_cycle_range_disabled(void); #ifdef __cplusplus diff --git a/client/src/gfx/pxl8_particles.c b/src/gfx/pxl8_particles.c similarity index 100% rename from client/src/gfx/pxl8_particles.c rename to src/gfx/pxl8_particles.c diff --git a/client/src/gfx/pxl8_particles.h b/src/gfx/pxl8_particles.h similarity index 100% rename from client/src/gfx/pxl8_particles.h rename to src/gfx/pxl8_particles.h diff --git a/client/src/gfx/pxl8_tilemap.c b/src/gfx/pxl8_tilemap.c similarity index 100% rename from client/src/gfx/pxl8_tilemap.c rename to src/gfx/pxl8_tilemap.c diff --git a/client/src/gfx/pxl8_tilemap.h b/src/gfx/pxl8_tilemap.h similarity index 100% rename from client/src/gfx/pxl8_tilemap.h rename to src/gfx/pxl8_tilemap.h diff --git a/client/src/gfx/pxl8_tilesheet.c b/src/gfx/pxl8_tilesheet.c similarity index 100% rename from client/src/gfx/pxl8_tilesheet.c rename to src/gfx/pxl8_tilesheet.c diff --git a/client/src/gfx/pxl8_tilesheet.h b/src/gfx/pxl8_tilesheet.h similarity index 100% rename from client/src/gfx/pxl8_tilesheet.h rename to src/gfx/pxl8_tilesheet.h diff --git a/client/src/gfx/pxl8_transition.c b/src/gfx/pxl8_transition.c similarity index 100% rename from client/src/gfx/pxl8_transition.c rename to src/gfx/pxl8_transition.c diff --git a/client/src/gfx/pxl8_transition.h b/src/gfx/pxl8_transition.h similarity index 100% rename from client/src/gfx/pxl8_transition.h rename to src/gfx/pxl8_transition.h diff --git a/client/src/hal/pxl8_hal.h b/src/hal/pxl8_hal.h similarity index 85% rename from client/src/hal/pxl8_hal.h rename to src/hal/pxl8_hal.h index 282cc90..df504c0 100644 --- a/client/src/hal/pxl8_hal.h +++ b/src/hal/pxl8_hal.h @@ -11,8 +11,8 @@ typedef struct pxl8_hal { void (*present)(void* platform_data); void (*set_cursor)(void* platform_data, u32 cursor); void (*set_relative_mouse_mode)(void* platform_data, bool enabled); - void (*upload_texture)(void* platform_data, const u8* pixels, u32 w, u32 h, - const u32* palette, u32 bpp); + void (*upload_texture)(void* platform_data, const void* pixels, u32 w, u32 h, + u32 bpp, const u32* palette); void* (*audio_create)(i32 sample_rate, i32 channels); void (*audio_destroy)(void* audio_handle); diff --git a/client/src/hal/pxl8_sdl3.c b/src/hal/pxl8_sdl3.c similarity index 94% rename from client/src/hal/pxl8_sdl3.c rename to src/hal/pxl8_sdl3.c index fe93912..3f2f909 100644 --- a/client/src/hal/pxl8_sdl3.c +++ b/src/hal/pxl8_sdl3.c @@ -81,7 +81,6 @@ static u64 sdl3_get_ticks(void) { return SDL_GetTicksNS(); } - static void sdl3_present(void* platform_data) { if (!platform_data) return; @@ -97,28 +96,34 @@ static void sdl3_present(void* platform_data) { SDL_RenderPresent(ctx->renderer); } -static void sdl3_upload_texture(void* platform_data, const u8* pixels, u32 w, u32 h, - const u32* palette, u32 bpp) { +static void sdl3_upload_texture(void* platform_data, const void* pixels, u32 w, u32 h, + u32 bpp, const u32* palette) { if (!platform_data || !pixels) return; pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data; - size_t needed_size = w * h; + size_t pixel_count = w * h; - if (ctx->rgba_buffer_size < needed_size) { - u32* new_buffer = (u32*)SDL_realloc(ctx->rgba_buffer, needed_size * 4); + if (bpp == 4) { + SDL_UpdateTexture(ctx->framebuffer, NULL, pixels, w * 4); + return; + } + + if (ctx->rgba_buffer_size < pixel_count) { + u32* new_buffer = (u32*)SDL_realloc(ctx->rgba_buffer, pixel_count * 4); if (!new_buffer) return; ctx->rgba_buffer = new_buffer; - ctx->rgba_buffer_size = needed_size; + ctx->rgba_buffer_size = pixel_count; } if (bpp == 2) { const u16* pixels16 = (const u16*)pixels; - for (u32 i = 0; i < w * h; i++) { + for (u32 i = 0; i < pixel_count; i++) { ctx->rgba_buffer[i] = pxl8_rgb565_to_rgba32(pixels16[i]); } } else { - for (u32 i = 0; i < w * h; i++) { - ctx->rgba_buffer[i] = palette[pixels[i]]; + const u8* pixels8 = (const u8*)pixels; + for (u32 i = 0; i < pixel_count; i++) { + ctx->rgba_buffer[i] = palette[pixels8[i]]; } } diff --git a/client/src/hal/pxl8_sdl3.h b/src/hal/pxl8_sdl3.h similarity index 100% rename from client/src/hal/pxl8_sdl3.h rename to src/hal/pxl8_sdl3.h diff --git a/client/src/lua/pxl8.lua b/src/lua/pxl8.lua similarity index 67% rename from client/src/lua/pxl8.lua rename to src/lua/pxl8.lua index 5f91f34..0efeff8 100644 --- a/client/src/lua/pxl8.lua +++ b/src/lua/pxl8.lua @@ -1,10 +1,12 @@ local anim = require("pxl8.anim") +local bytes = require("pxl8.bytes") 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 net = require("pxl8.net") local particles = require("pxl8.particles") local sfx = require("pxl8.sfx") local tilemap = require("pxl8.tilemap") @@ -34,6 +36,7 @@ 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.set_palette_rgb = core.set_palette_rgb pxl8.clear = gfx2d.clear pxl8.pixel = gfx2d.pixel @@ -71,87 +74,95 @@ 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.Anim = anim.Anim +pxl8.create_anim = anim.Anim.new +pxl8.create_anim_from_ase = anim.Anim.from_ase -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.bounds = math3d.bounds pxl8.Camera3D = gfx3d.Camera3D -pxl8.create_camera_3d = function() return gfx3d.Camera3D.new() end +pxl8.create_camera_3d = gfx3d.Camera3D.new 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.end_frame_3d = gfx3d.end_frame +pxl8.Mesh = gfx3d.Mesh +pxl8.create_mesh = gfx3d.Mesh.new + +pxl8.Compressor = sfx.Compressor +pxl8.create_compressor = sfx.Compressor.new +pxl8.Delay = sfx.Delay +pxl8.create_delay = sfx.Delay.new +pxl8.Reverb = sfx.Reverb +pxl8.create_reverb = sfx.Reverb.new + +pxl8.Gui = gui.Gui +pxl8.create_gui = gui.Gui.new +pxl8.gui_label = gui.label +pxl8.gui_window = gui.window pxl8.mat4_identity = math3d.mat4_identity +pxl8.mat4_lookat = math3d.mat4_lookat pxl8.mat4_multiply = math3d.mat4_multiply -pxl8.mat4_translate = math3d.mat4_translate +pxl8.mat4_ortho = math3d.mat4_ortho +pxl8.mat4_perspective = math3d.mat4_perspective 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.mat4_translate = math3d.mat4_translate -pxl8.Gui = gui.Gui -pxl8.create_gui = function() return gui.Gui.new() end -pxl8.gui_label = gui.label -pxl8.gui_window = gui.window +pxl8.Net = net.Net +pxl8.create_net = net.Net.new +pxl8.NET_MODE_LOCAL = net.MODE_LOCAL +pxl8.NET_MODE_REMOTE = net.MODE_REMOTE + +pxl8.pack_f32_be = bytes.pack_f32_be +pxl8.pack_f32_le = bytes.pack_f32_le +pxl8.pack_f64_be = bytes.pack_f64_be +pxl8.pack_f64_le = bytes.pack_f64_le +pxl8.pack_i8 = bytes.pack_i8 +pxl8.pack_i16_be = bytes.pack_i16_be +pxl8.pack_i16_le = bytes.pack_i16_le +pxl8.pack_i32_be = bytes.pack_i32_be +pxl8.pack_i32_le = bytes.pack_i32_le +pxl8.pack_i64_be = bytes.pack_i64_be +pxl8.pack_i64_le = bytes.pack_i64_le +pxl8.pack_u8 = bytes.pack_u8 +pxl8.pack_u16_be = bytes.pack_u16_be +pxl8.pack_u16_le = bytes.pack_u16_le +pxl8.pack_u32_be = bytes.pack_u32_be +pxl8.pack_u32_le = bytes.pack_u32_le +pxl8.pack_u64_be = bytes.pack_u64_be +pxl8.pack_u64_le = bytes.pack_u64_le + +pxl8.Particles = particles.Particles +pxl8.create_particles = particles.Particles.new -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.procgen_tex = world.procgen_tex 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.create_sfx_context = sfx.SfxContext.new 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_set_master_volume = sfx.set_master_volume 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 @@ -159,4 +170,39 @@ pxl8.SFX_WAVE_SINE = sfx.WAVE_SINE pxl8.SFX_WAVE_SQUARE = sfx.WAVE_SQUARE pxl8.SFX_WAVE_TRIANGLE = sfx.WAVE_TRIANGLE +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.Tilemap = tilemap.Tilemap +pxl8.create_tilemap = tilemap.Tilemap.new +pxl8.Tilesheet = tilemap.Tilesheet +pxl8.create_tilesheet = tilemap.Tilesheet.new + +pxl8.Transition = transition.Transition +pxl8.create_transition = transition.Transition.new +pxl8.TRANSITION_TYPES = transition.TYPES + +pxl8.unpack_f32_be = bytes.unpack_f32_be +pxl8.unpack_f32_le = bytes.unpack_f32_le +pxl8.unpack_f64_be = bytes.unpack_f64_be +pxl8.unpack_f64_le = bytes.unpack_f64_le +pxl8.unpack_i8 = bytes.unpack_i8 +pxl8.unpack_i16_be = bytes.unpack_i16_be +pxl8.unpack_i16_le = bytes.unpack_i16_le +pxl8.unpack_i32_be = bytes.unpack_i32_be +pxl8.unpack_i32_le = bytes.unpack_i32_le +pxl8.unpack_i64_be = bytes.unpack_i64_be +pxl8.unpack_i64_le = bytes.unpack_i64_le +pxl8.unpack_u8 = bytes.unpack_u8 +pxl8.unpack_u16_be = bytes.unpack_u16_be +pxl8.unpack_u16_le = bytes.unpack_u16_le +pxl8.unpack_u32_be = bytes.unpack_u32_be +pxl8.unpack_u32_le = bytes.unpack_u32_le +pxl8.unpack_u64_be = bytes.unpack_u64_be +pxl8.unpack_u64_le = bytes.unpack_u64_le + +pxl8.World = world.World +pxl8.create_world = world.World.new + return pxl8 diff --git a/client/src/lua/pxl8/anim.lua b/src/lua/pxl8/anim.lua similarity index 100% rename from client/src/lua/pxl8/anim.lua rename to src/lua/pxl8/anim.lua diff --git a/src/lua/pxl8/bytes.lua b/src/lua/pxl8/bytes.lua new file mode 100644 index 0000000..b0ccb15 --- /dev/null +++ b/src/lua/pxl8/bytes.lua @@ -0,0 +1,44 @@ +local ffi = require("ffi") +local C = ffi.C + +local bytes = {} + +bytes.pack_f32_be = C.pxl8_pack_f32_be +bytes.pack_f32_le = C.pxl8_pack_f32_le +bytes.pack_f64_be = C.pxl8_pack_f64_be +bytes.pack_f64_le = C.pxl8_pack_f64_le +bytes.pack_i8 = C.pxl8_pack_i8 +bytes.pack_i16_be = C.pxl8_pack_i16_be +bytes.pack_i16_le = C.pxl8_pack_i16_le +bytes.pack_i32_be = C.pxl8_pack_i32_be +bytes.pack_i32_le = C.pxl8_pack_i32_le +bytes.pack_i64_be = C.pxl8_pack_i64_be +bytes.pack_i64_le = C.pxl8_pack_i64_le +bytes.pack_u8 = C.pxl8_pack_u8 +bytes.pack_u16_be = C.pxl8_pack_u16_be +bytes.pack_u16_le = C.pxl8_pack_u16_le +bytes.pack_u32_be = C.pxl8_pack_u32_be +bytes.pack_u32_le = C.pxl8_pack_u32_le +bytes.pack_u64_be = C.pxl8_pack_u64_be +bytes.pack_u64_le = C.pxl8_pack_u64_le + +bytes.unpack_f32_be = C.pxl8_unpack_f32_be +bytes.unpack_f32_le = C.pxl8_unpack_f32_le +bytes.unpack_f64_be = C.pxl8_unpack_f64_be +bytes.unpack_f64_le = C.pxl8_unpack_f64_le +bytes.unpack_i8 = C.pxl8_unpack_i8 +bytes.unpack_i16_be = C.pxl8_unpack_i16_be +bytes.unpack_i16_le = C.pxl8_unpack_i16_le +bytes.unpack_i32_be = C.pxl8_unpack_i32_be +bytes.unpack_i32_le = C.pxl8_unpack_i32_le +bytes.unpack_i64_be = C.pxl8_unpack_i64_be +bytes.unpack_i64_le = C.pxl8_unpack_i64_le +bytes.unpack_u8 = C.pxl8_unpack_u8 +bytes.unpack_u16_be = C.pxl8_unpack_u16_be +bytes.unpack_u16_le = C.pxl8_unpack_u16_le +bytes.unpack_u32_be = C.pxl8_unpack_u32_be +bytes.unpack_u32_le = C.pxl8_unpack_u32_le +bytes.unpack_u64_be = C.pxl8_unpack_u64_be +bytes.unpack_u64_le = C.pxl8_unpack_u64_le + +return bytes diff --git a/client/src/lua/pxl8/core.lua b/src/lua/pxl8/core.lua similarity index 90% rename from client/src/lua/pxl8/core.lua rename to src/lua/pxl8/core.lua index ecee66d..6b703c8 100644 --- a/client/src/lua/pxl8/core.lua +++ b/src/lua/pxl8/core.lua @@ -31,6 +31,14 @@ function core.palette_index(color) return C.pxl8_palette_index(pal, color) end +function core.set_palette_rgb(index, r, g, b) + local pal = C.pxl8_gfx_get_palette(core.gfx) + if pal == nil then return end + C.pxl8_palette_set_rgb(pal, index, r, g, b) + C.pxl8_gfx_blend_tables_update(core.gfx) + C.pxl8_gfx_colormap_update(core.gfx) +end + function core.ramp_index(position) local pal = C.pxl8_gfx_get_palette(core.gfx) if pal == nil then return 0 end diff --git a/src/lua/pxl8/effects.lua b/src/lua/pxl8/effects.lua new file mode 100644 index 0000000..4db069a --- /dev/null +++ b/src/lua/pxl8/effects.lua @@ -0,0 +1,32 @@ +local ffi = require("ffi") +local C = ffi.C +local core = require("pxl8.core") + +local effects = {} + +effects.GLOW_CIRCLE = 0 +effects.GLOW_DIAMOND = 1 +effects.GLOW_SHAFT = 2 + +function effects.glows(glows) + if not glows or #glows == 0 then return end + + local count = #glows + local glow_array = ffi.new("pxl8_glow_source[?]", count) + + for i, g in ipairs(glows) do + local idx = i - 1 + glow_array[idx].x = g.x or 0 + glow_array[idx].y = g.y or 0 + glow_array[idx].radius = g.radius or 8 + glow_array[idx].intensity = g.intensity or 255 + glow_array[idx].color = g.color or 15 + glow_array[idx].depth = g.depth or 0xFFFF + glow_array[idx].height = g.height or 0 + glow_array[idx].shape = g.shape or 0 + end + + C.pxl8_gfx_apply_effect(core.gfx, C.PXL8_GFX_EFFECT_GLOWS, glow_array, count) +end + +return effects diff --git a/client/src/lua/pxl8/gfx2d.lua b/src/lua/pxl8/gfx2d.lua similarity index 100% rename from client/src/lua/pxl8/gfx2d.lua rename to src/lua/pxl8/gfx2d.lua diff --git a/client/src/lua/pxl8/gfx3d.lua b/src/lua/pxl8/gfx3d.lua similarity index 68% rename from client/src/lua/pxl8/gfx3d.lua rename to src/lua/pxl8/gfx3d.lua index 3772263..dd84914 100644 --- a/client/src/lua/pxl8/gfx3d.lua +++ b/src/lua/pxl8/gfx3d.lua @@ -42,6 +42,14 @@ function Camera3D:get_up() return {v.x, v.y, v.z} end +function Camera3D:get_view() + return C.pxl8_3d_camera_get_view(self._ptr) +end + +function Camera3D:get_projection() + return C.pxl8_3d_camera_get_projection(self._ptr) +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]}) @@ -114,7 +122,14 @@ gfx3d.Mesh = Mesh function gfx3d.draw_mesh(mesh, opts) opts = opts or {} - local model = C.pxl8_mat4_identity() + local model = ffi.new("pxl8_mat4") + model.m[0] = 1 + model.m[5] = 1 + model.m[10] = 1 + model.m[15] = 1 + if opts.x then model.m[12] = opts.x end + if opts.y then model.m[13] = opts.y end + if opts.z then model.m[14] = opts.z end local material = ffi.new("pxl8_material", { texture_id = opts.texture or 0, alpha = opts.alpha or 255, @@ -131,12 +146,44 @@ 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, - }) + local u = ffi.new("pxl8_3d_uniforms") + + u.ambient = uniforms.ambient or 0 + u.fog_color = uniforms.fog_color or 0 + u.fog_density = uniforms.fog_density or 0.0 + u.time = uniforms.time or 0.0 + + if uniforms.celestial_dir then + u.celestial_dir.x = uniforms.celestial_dir[1] or 0 + u.celestial_dir.y = uniforms.celestial_dir[2] or -1 + u.celestial_dir.z = uniforms.celestial_dir[3] or 0 + else + u.celestial_dir.x = 0 + u.celestial_dir.y = -1 + u.celestial_dir.z = 0 + end + u.celestial_intensity = uniforms.celestial_intensity or 0.0 + + u.num_lights = 0 + if uniforms.lights then + for i, light in ipairs(uniforms.lights) do + if i > 16 then break end + local idx = i - 1 + u.lights[idx].position.x = light.x or 0 + u.lights[idx].position.y = light.y or 0 + u.lights[idx].position.z = light.z or 0 + u.lights[idx].r = light.r or 255 + u.lights[idx].g = light.g or 255 + u.lights[idx].b = light.b or 255 + u.lights[idx].intensity = light.intensity or 255 + u.lights[idx].radius = light.radius or 100 + local radius_sq = u.lights[idx].radius * u.lights[idx].radius + u.lights[idx].radius_sq = radius_sq + u.lights[idx].inv_radius_sq = radius_sq > 0 and (1.0 / radius_sq) or 0 + u.num_lights = i + end + end + C.pxl8_3d_begin_frame(core.gfx, camera._ptr, u) end diff --git a/client/src/lua/pxl8/gui.lua b/src/lua/pxl8/gui.lua similarity index 100% rename from client/src/lua/pxl8/gui.lua rename to src/lua/pxl8/gui.lua diff --git a/client/src/lua/pxl8/input.lua b/src/lua/pxl8/input.lua similarity index 100% rename from client/src/lua/pxl8/input.lua rename to src/lua/pxl8/input.lua diff --git a/client/src/lua/pxl8/math.lua b/src/lua/pxl8/math.lua similarity index 100% rename from client/src/lua/pxl8/math.lua rename to src/lua/pxl8/math.lua diff --git a/src/lua/pxl8/net.lua b/src/lua/pxl8/net.lua new file mode 100644 index 0000000..318889f --- /dev/null +++ b/src/lua/pxl8/net.lua @@ -0,0 +1,182 @@ +local ffi = require("ffi") +local C = ffi.C + +local net = {} + +local Net = {} +Net.__index = Net + +net.MODE_LOCAL = C.PXL8_NET_LOCAL +net.MODE_REMOTE = C.PXL8_NET_REMOTE + +function Net.new(config) + config = config or {} + local cfg = ffi.new("pxl8_net_config") + cfg.address = config.address or "127.0.0.1" + cfg.mode = config.mode or C.PXL8_NET_REMOTE + cfg.port = config.port or 7777 + + local n = C.pxl8_net_create(cfg) + if n == nil then + return nil + end + return setmetatable({ _ptr = n }, Net) +end + +function Net:connect() + return C.pxl8_net_connect(self._ptr) == 0 +end + +function Net:connected() + return C.pxl8_net_connected(self._ptr) +end + +function Net:destroy() + if self._ptr then + C.pxl8_net_destroy(self._ptr) + self._ptr = nil + end +end + +function Net:disconnect() + C.pxl8_net_disconnect(self._ptr) +end + +function Net:entities() + local snap = C.pxl8_net_snapshot(self._ptr) + if snap == nil then + return {} + end + local ents = C.pxl8_net_entities(self._ptr) + if ents == nil then + return {} + end + local result = {} + for i = 0, snap.entity_count - 1 do + result[i + 1] = { + entity_id = tonumber(ents[i].entity_id), + userdata = ents[i].userdata + } + end + return result +end + +function Net:entity_prev_userdata(entity_id) + return C.pxl8_net_entity_prev_userdata(self._ptr, entity_id) +end + +function Net:entity_userdata(entity_id) + return C.pxl8_net_entity_userdata(self._ptr, entity_id) +end + +function Net:input_at(tick) + local input = C.pxl8_net_input_at(self._ptr, tick) + if input == nil then return nil end + return { + buttons = input.buttons, + look_dx = input.look_dx, + look_dy = input.look_dy, + move_x = input.move_x, + move_y = input.move_y, + yaw = input.yaw, + tick = tonumber(input.tick), + timestamp = tonumber(input.timestamp) + } +end + +function Net:input_oldest_tick() + return tonumber(C.pxl8_net_input_oldest_tick(self._ptr)) +end + +function Net:input_push(input) + local msg = ffi.new("pxl8_input_msg") + msg.buttons = input.buttons or 0 + msg.look_dx = input.look_dx or 0 + msg.look_dy = input.look_dy or 0 + msg.move_x = input.move_x or 0 + msg.move_y = input.move_y or 0 + msg.yaw = input.yaw or 0 + msg.tick = input.tick or 0 + msg.timestamp = input.timestamp or 0 + C.pxl8_net_input_push(self._ptr, msg) +end + +function Net:lerp_alpha() + return C.pxl8_net_lerp_alpha(self._ptr) +end + +function Net:needs_correction() + return C.pxl8_net_needs_correction(self._ptr) +end + +function Net:player_id() + return tonumber(C.pxl8_net_player_id(self._ptr)) +end + +function Net:poll() + return C.pxl8_net_poll(self._ptr) +end + +function Net:predicted_state() + return C.pxl8_net_predicted_state(self._ptr) +end + +function Net:predicted_tick_set(tick) + C.pxl8_net_predicted_tick_set(self._ptr, tick) +end + +function Net:send_command(cmd) + return C.pxl8_net_send_command(self._ptr, cmd) == 0 +end + +function Net:send_input(input) + local msg = ffi.new("pxl8_input_msg") + msg.buttons = input.buttons or 0 + msg.look_dx = input.look_dx or 0 + msg.look_dy = input.look_dy or 0 + msg.move_x = input.move_x or 0 + msg.move_y = input.move_y or 0 + msg.yaw = input.yaw or 0 + msg.tick = input.tick or 0 + msg.timestamp = input.timestamp or 0 + return C.pxl8_net_send_input(self._ptr, msg) == 0 +end + +function Net:snapshot() + local snap = C.pxl8_net_snapshot(self._ptr) + if snap == nil then + return nil + end + return { + entity_count = snap.entity_count, + event_count = snap.event_count, + player_id = tonumber(snap.player_id), + tick = tonumber(snap.tick), + time = snap.time + } +end + +function Net:spawn(x, y, z, yaw, pitch) + local cmd = ffi.new("pxl8_command_msg") + cmd.cmd_type = C.PXL8_CMD_SPAWN_ENTITY + C.pxl8_pack_f32_be(cmd.payload, 0, x or 0) + C.pxl8_pack_f32_be(cmd.payload, 4, y or 0) + C.pxl8_pack_f32_be(cmd.payload, 8, z or 0) + C.pxl8_pack_f32_be(cmd.payload, 12, yaw or 0) + C.pxl8_pack_f32_be(cmd.payload, 16, pitch or 0) + cmd.payload_size = 20 + cmd.tick = 0 + return self:send_command(cmd) +end + +function Net:tick() + return tonumber(C.pxl8_net_tick(self._ptr)) +end + +function Net:update(dt) + C.pxl8_net_update(self._ptr, dt) +end + +net.Net = Net + +return net diff --git a/client/src/lua/pxl8/particles.lua b/src/lua/pxl8/particles.lua similarity index 100% rename from client/src/lua/pxl8/particles.lua rename to src/lua/pxl8/particles.lua diff --git a/client/src/lua/pxl8/sfx.lua b/src/lua/pxl8/sfx.lua similarity index 98% rename from client/src/lua/pxl8/sfx.lua rename to src/lua/pxl8/sfx.lua index 3a1faf5..ff942a1 100644 --- a/client/src/lua/pxl8/sfx.lua +++ b/src/lua/pxl8/sfx.lua @@ -200,9 +200,7 @@ 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 +sfx.note_to_freq = C.pxl8_sfx_note_to_freq function sfx.set_master_volume(volume) C.pxl8_sfx_mixer_set_master_volume(core.sfx, volume) diff --git a/client/src/lua/pxl8/tilemap.lua b/src/lua/pxl8/tilemap.lua similarity index 100% rename from client/src/lua/pxl8/tilemap.lua rename to src/lua/pxl8/tilemap.lua diff --git a/client/src/lua/pxl8/transition.lua b/src/lua/pxl8/transition.lua similarity index 100% rename from client/src/lua/pxl8/transition.lua rename to src/lua/pxl8/transition.lua diff --git a/client/src/lua/pxl8/world.lua b/src/lua/pxl8/world.lua similarity index 100% rename from client/src/lua/pxl8/world.lua rename to src/lua/pxl8/world.lua diff --git a/client/src/math/pxl8_math.c b/src/math/pxl8_math.c similarity index 96% rename from client/src/math/pxl8_math.c rename to src/math/pxl8_math.c index 92b858f..4b5ce26 100644 --- a/client/src/math/pxl8_math.c +++ b/src/math/pxl8_math.c @@ -126,6 +126,14 @@ pxl8_vec4 pxl8_mat4_mul_vec4(pxl8_mat4 m, pxl8_vec4 v) { }; } +pxl8_vec3 pxl8_mat4_mul_vec3(pxl8_mat4 m, pxl8_vec3 v) { + return (pxl8_vec3){ + .x = m.m[0] * v.x + m.m[4] * v.y + m.m[8] * v.z, + .y = m.m[1] * v.x + m.m[5] * v.y + m.m[9] * v.z, + .z = m.m[2] * v.x + m.m[6] * v.y + m.m[10] * v.z, + }; +} + pxl8_mat4 pxl8_mat4_translate(f32 x, f32 y, f32 z) { pxl8_mat4 mat = pxl8_mat4_identity(); diff --git a/client/src/math/pxl8_math.h b/src/math/pxl8_math.h similarity index 88% rename from client/src/math/pxl8_math.h rename to src/math/pxl8_math.h index 40ea22f..26dcec8 100644 --- a/client/src/math/pxl8_math.h +++ b/src/math/pxl8_math.h @@ -7,6 +7,15 @@ #define PXL8_PI 3.14159265358979323846f #define PXL8_TAU (PXL8_PI * 2.0f) +static inline f32 pxl8_fast_inv_sqrt(f32 x) { + f32 half = 0.5f * x; + i32 i = *(i32*)&x; + i = 0x5f3759df - (i >> 1); + x = *(f32*)&i; + x = x * (1.5f - half * x * x); + return x; +} + typedef struct pxl8_vec2 { f32 x, y; } pxl8_vec2; @@ -55,6 +64,7 @@ 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_mul(pxl8_mat4 a, pxl8_mat4 b); +pxl8_vec3 pxl8_mat4_mul_vec3(pxl8_mat4 m, pxl8_vec3 v); 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); diff --git a/client/src/math/pxl8_simd.h b/src/math/pxl8_simd.h similarity index 97% rename from client/src/math/pxl8_simd.h rename to src/math/pxl8_simd.h index 8202b24..18490fd 100644 --- a/client/src/math/pxl8_simd.h +++ b/src/math/pxl8_simd.h @@ -286,14 +286,6 @@ static inline void pxl8_i32x4_store(pxl8_i32x4 a, i32* out) { #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/net/pxl8_net.c b/src/net/pxl8_net.c new file mode 100644 index 0000000..e5cba62 --- /dev/null +++ b/src/net/pxl8_net.c @@ -0,0 +1,312 @@ +#include "pxl8_net.h" + +#include +#include + +#ifdef _WIN32 + #define WIN32_LEAN_AND_MEAN + #include + #include + typedef SOCKET socket_t; + #define INVALID_SOCK INVALID_SOCKET + #define close_socket closesocket +#else + #include + #include + #include + #include + #include + #include + typedef int socket_t; + #define INVALID_SOCK -1 + #define close_socket close +#endif + +#define PXL8_NET_DEFAULT_PORT 7777 +#define PXL8_NET_TICK_RATE 30.0f + +struct pxl8_net { + char address[256]; + bool connected; + pxl8_entity_state entities[PXL8_MAX_SNAPSHOT_ENTITIES]; + pxl8_event_msg events[PXL8_MAX_SNAPSHOT_EVENTS]; + u64 highest_tick; + pxl8_input_msg input_history[PXL8_NET_INPUT_HISTORY_SIZE]; + u64 input_head; + u64 input_oldest_tick; + f32 interp_time; + pxl8_net_mode mode; + u16 port; + u8 predicted_state[PXL8_NET_USERDATA_SIZE]; + u64 predicted_tick; + pxl8_entity_state prev_entities[PXL8_MAX_SNAPSHOT_ENTITIES]; + pxl8_snapshot_header prev_snapshot; + u8 recv_buf[4096]; + u8 send_buf[4096]; + u32 sequence; + struct sockaddr_in server_addr; + pxl8_snapshot_header snapshot; + socket_t sock; +}; + +static const pxl8_entity_state* find_entity(const pxl8_entity_state* entities, u16 count, u64 id) { + for (u16 i = 0; i < count; i++) { + if (entities[i].entity_id == id) return &entities[i]; + } + return NULL; +} + +pxl8_result pxl8_net_connect(pxl8_net* net) { + if (!net) return PXL8_ERROR_INVALID_ARGUMENT; + if (net->connected) return PXL8_OK; + +#ifdef _WIN32 + WSADATA wsa; + if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { + return PXL8_ERROR_SYSTEM_FAILURE; + } +#endif + + net->sock = socket(AF_INET, SOCK_DGRAM, 0); + if (net->sock == INVALID_SOCK) { + return PXL8_ERROR_SYSTEM_FAILURE; + } + +#ifdef _WIN32 + u_long nonblocking = 1; + ioctlsocket(net->sock, FIONBIO, &nonblocking); +#else + int flags = fcntl(net->sock, F_GETFL, 0); + fcntl(net->sock, F_SETFL, flags | O_NONBLOCK); +#endif + + memset(&net->server_addr, 0, sizeof(net->server_addr)); + net->server_addr.sin_family = AF_INET; + net->server_addr.sin_port = htons(net->port); + inet_pton(AF_INET, net->address, &net->server_addr.sin_addr); + + net->connected = true; + return PXL8_OK; +} + +bool pxl8_net_connected(const pxl8_net* net) { + return net && net->connected; +} + +pxl8_net* pxl8_net_create(const pxl8_net_config* config) { + pxl8_net* net = calloc(1, sizeof(pxl8_net)); + if (!net) return NULL; + + net->mode = config->mode; + net->port = config->port ? config->port : PXL8_NET_DEFAULT_PORT; + net->sock = INVALID_SOCK; + net->connected = false; + net->sequence = 0; + net->highest_tick = 0; + + if (config->address) { + strncpy(net->address, config->address, sizeof(net->address) - 1); + } else { + strncpy(net->address, "127.0.0.1", sizeof(net->address) - 1); + } + + return net; +} + +void pxl8_net_destroy(pxl8_net* net) { + if (!net) return; + pxl8_net_disconnect(net); + free(net); +} + +void pxl8_net_disconnect(pxl8_net* net) { + if (!net) return; + if (net->sock != INVALID_SOCK) { + close_socket(net->sock); + net->sock = INVALID_SOCK; + } +#ifdef _WIN32 + WSACleanup(); +#endif + net->connected = false; +} + +const pxl8_entity_state* pxl8_net_entities(const pxl8_net* net) { + if (!net) return NULL; + return net->entities; +} + +const u8* pxl8_net_entity_prev_userdata(const pxl8_net* net, u64 entity_id) { + if (!net) return NULL; + const pxl8_entity_state* e = find_entity(net->prev_entities, net->prev_snapshot.entity_count, entity_id); + return e ? e->userdata : NULL; +} + +const u8* pxl8_net_entity_userdata(const pxl8_net* net, u64 entity_id) { + if (!net) return NULL; + const pxl8_entity_state* e = find_entity(net->entities, net->snapshot.entity_count, entity_id); + return e ? e->userdata : NULL; +} + +const pxl8_event_msg* pxl8_net_events(const pxl8_net* net) { + if (!net) return NULL; + return net->events; +} + +const pxl8_input_msg* pxl8_net_input_at(const pxl8_net* net, u64 tick) { + if (!net) return NULL; + for (u64 i = 0; i < PXL8_NET_INPUT_HISTORY_SIZE; i++) { + if (net->input_history[i].tick == tick) { + return &net->input_history[i]; + } + } + return NULL; +} + +u64 pxl8_net_input_oldest_tick(const pxl8_net* net) { + if (!net) return 0; + return net->input_oldest_tick; +} + +void pxl8_net_input_push(pxl8_net* net, const pxl8_input_msg* input) { + if (!net || !input) return; + u64 idx = net->input_head % PXL8_NET_INPUT_HISTORY_SIZE; + net->input_history[idx] = *input; + net->input_head++; + if (net->input_oldest_tick == 0 || input->tick < net->input_oldest_tick) { + net->input_oldest_tick = input->tick; + } +} + +f32 pxl8_net_lerp_alpha(const pxl8_net* net) { + if (!net) return 1.0f; + f32 tick_duration = 1.0f / PXL8_NET_TICK_RATE; + f32 alpha = net->interp_time / tick_duration; + return alpha > 1.0f ? 1.0f : alpha; +} + +bool pxl8_net_needs_correction(const pxl8_net* net) { + if (!net) return false; + if (net->snapshot.tick == 0) return false; + if (net->predicted_tick == 0) return false; + if (net->snapshot.tick > net->predicted_tick) return true; + const u8* server = pxl8_net_entity_userdata(net, net->snapshot.player_id); + if (!server) return false; + return memcmp(server, net->predicted_state, PXL8_NET_USERDATA_SIZE) != 0; +} + +u64 pxl8_net_player_id(const pxl8_net* net) { + if (!net) return 0; + return net->snapshot.player_id; +} + +bool pxl8_net_poll(pxl8_net* net) { + if (!net || !net->connected) return false; + + size_t len = pxl8_net_recv(net, net->recv_buf, sizeof(net->recv_buf)); + if (len < sizeof(pxl8_msg_header)) return false; + + pxl8_msg_header hdr; + size_t offset = pxl8_protocol_deserialize_header(net->recv_buf, len, &hdr); + if (hdr.type != PXL8_MSG_SNAPSHOT) return false; + + pxl8_snapshot_header snap; + offset += pxl8_protocol_deserialize_snapshot_header(net->recv_buf + offset, len - offset, &snap); + + if (snap.tick <= net->highest_tick) return false; + + memcpy(net->prev_entities, net->entities, sizeof(net->entities)); + net->prev_snapshot = net->snapshot; + + net->highest_tick = snap.tick; + net->snapshot = snap; + net->interp_time = 0.0f; + + u16 count = snap.entity_count; + if (count > PXL8_MAX_SNAPSHOT_ENTITIES) count = PXL8_MAX_SNAPSHOT_ENTITIES; + + for (u16 i = 0; i < count; i++) { + offset += pxl8_protocol_deserialize_entity_state( + net->recv_buf + offset, len - offset, &net->entities[i]); + } + + return true; +} + +u8* pxl8_net_predicted_state(pxl8_net* net) { + if (!net) return NULL; + return net->predicted_state; +} + +void pxl8_net_predicted_tick_set(pxl8_net* net, u64 tick) { + if (!net) return; + net->predicted_tick = tick; +} + +size_t pxl8_net_recv(pxl8_net* net, u8* buf, size_t len) { + if (!net || !net->connected) return 0; + + struct sockaddr_in from; + socklen_t from_len = sizeof(from); + ssize_t received = recvfrom(net->sock, (char*)buf, len, 0, + (struct sockaddr*)&from, &from_len); + return (received > 0) ? (size_t)received : 0; +} + +pxl8_result pxl8_net_send(pxl8_net* net, const u8* data, size_t len) { + if (!net || !net->connected) return PXL8_ERROR_INVALID_ARGUMENT; + + ssize_t sent = sendto(net->sock, (const char*)data, len, 0, + (struct sockaddr*)&net->server_addr, + sizeof(net->server_addr)); + return (sent > 0) ? PXL8_OK : PXL8_ERROR_SYSTEM_FAILURE; +} + +pxl8_result pxl8_net_send_command(pxl8_net* net, const pxl8_command_msg* cmd) { + if (!net || !net->connected) return PXL8_ERROR_INVALID_ARGUMENT; + + u8 buf[sizeof(pxl8_msg_header) + sizeof(pxl8_command_msg)]; + pxl8_msg_header hdr = { + .type = PXL8_MSG_COMMAND, + .version = PXL8_PROTOCOL_VERSION, + .size = sizeof(pxl8_command_msg), + .sequence = 0 + }; + + size_t offset = pxl8_protocol_serialize_header(&hdr, buf, sizeof(buf)); + offset += pxl8_protocol_serialize_command(cmd, buf + offset, sizeof(buf) - offset); + + return pxl8_net_send(net, buf, offset); +} + +pxl8_result pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input) { + if (!net || !net->connected) return PXL8_ERROR_INVALID_ARGUMENT; + + u8 buf[sizeof(pxl8_msg_header) + sizeof(pxl8_input_msg)]; + pxl8_msg_header hdr = { + .type = PXL8_MSG_INPUT, + .version = PXL8_PROTOCOL_VERSION, + .size = sizeof(pxl8_input_msg), + .sequence = 0 + }; + + size_t offset = pxl8_protocol_serialize_header(&hdr, buf, sizeof(buf)); + offset += pxl8_protocol_serialize_input(input, buf + offset, sizeof(buf) - offset); + + return pxl8_net_send(net, buf, offset); +} + +const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net) { + if (!net) return NULL; + return &net->snapshot; +} + +u64 pxl8_net_tick(const pxl8_net* net) { + if (!net) return 0; + return net->snapshot.tick; +} + +void pxl8_net_update(pxl8_net* net, f32 dt) { + if (!net) return; + net->interp_time += dt; +} diff --git a/src/net/pxl8_net.h b/src/net/pxl8_net.h new file mode 100644 index 0000000..d14ea10 --- /dev/null +++ b/src/net/pxl8_net.h @@ -0,0 +1,54 @@ +#pragma once + +#include "pxl8_protocol.h" +#include "pxl8_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PXL8_NET_INPUT_HISTORY_SIZE 64 +#define PXL8_NET_USERDATA_SIZE 56 + +typedef struct pxl8_net pxl8_net; + +typedef enum pxl8_net_mode { + PXL8_NET_LOCAL = 0, + PXL8_NET_REMOTE +} pxl8_net_mode; + +typedef struct pxl8_net_config { + const char* address; + pxl8_net_mode mode; + u16 port; +} pxl8_net_config; + +pxl8_result pxl8_net_connect(pxl8_net* net); +bool pxl8_net_connected(const pxl8_net* net); +pxl8_net* pxl8_net_create(const pxl8_net_config* config); +void pxl8_net_destroy(pxl8_net* net); +void pxl8_net_disconnect(pxl8_net* net); +const pxl8_entity_state* pxl8_net_entities(const pxl8_net* net); +const u8* pxl8_net_entity_prev_userdata(const pxl8_net* net, u64 entity_id); +const u8* pxl8_net_entity_userdata(const pxl8_net* net, u64 entity_id); +const pxl8_event_msg* pxl8_net_events(const pxl8_net* net); +const pxl8_input_msg* pxl8_net_input_at(const pxl8_net* net, u64 tick); +u64 pxl8_net_input_oldest_tick(const pxl8_net* net); +void pxl8_net_input_push(pxl8_net* net, const pxl8_input_msg* input); +f32 pxl8_net_lerp_alpha(const pxl8_net* net); +bool pxl8_net_needs_correction(const pxl8_net* net); +u64 pxl8_net_player_id(const pxl8_net* net); +bool pxl8_net_poll(pxl8_net* net); +u8* pxl8_net_predicted_state(pxl8_net* net); +void pxl8_net_predicted_tick_set(pxl8_net* net, u64 tick); +size_t pxl8_net_recv(pxl8_net* net, u8* buf, size_t len); +pxl8_result pxl8_net_send(pxl8_net* net, const u8* data, size_t len); +pxl8_result pxl8_net_send_command(pxl8_net* net, const pxl8_command_msg* cmd); +pxl8_result pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input); +const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net); +u64 pxl8_net_tick(const pxl8_net* net); +void pxl8_net_update(pxl8_net* net, f32 dt); + +#ifdef __cplusplus +} +#endif diff --git a/src/net/pxl8_protocol.c b/src/net/pxl8_protocol.c new file mode 100644 index 0000000..3c4a48d --- /dev/null +++ b/src/net/pxl8_protocol.c @@ -0,0 +1,124 @@ +#include "pxl8_protocol.h" +#include "pxl8_bytes.h" + +size_t pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, size_t len) { + if (len < sizeof(pxl8_msg_header)) return 0; + pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len); + pxl8_write_u32_be(&s, msg->sequence); + pxl8_write_u16_be(&s, msg->size); + pxl8_write_u8(&s, msg->type); + pxl8_write_u8(&s, msg->version); + return s.offset; +} + +size_t pxl8_protocol_deserialize_header(const u8* buf, size_t len, pxl8_msg_header* msg) { + if (len < sizeof(pxl8_msg_header)) return 0; + pxl8_stream s = pxl8_stream_create(buf, (u32)len); + msg->sequence = pxl8_read_u32_be(&s); + msg->size = pxl8_read_u16_be(&s); + msg->type = pxl8_read_u8(&s); + msg->version = pxl8_read_u8(&s); + return s.offset; +} + +size_t pxl8_protocol_serialize_input(const pxl8_input_msg* msg, u8* buf, size_t len) { + if (len < sizeof(pxl8_input_msg)) return 0; + pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len); + pxl8_write_u32_be(&s, msg->buttons); + pxl8_write_f32_be(&s, msg->look_dx); + pxl8_write_f32_be(&s, msg->look_dy); + pxl8_write_f32_be(&s, msg->move_x); + pxl8_write_f32_be(&s, msg->move_y); + pxl8_write_f32_be(&s, msg->yaw); + pxl8_write_u64_be(&s, msg->tick); + pxl8_write_u64_be(&s, msg->timestamp); + return s.offset; +} + +size_t pxl8_protocol_deserialize_input(const u8* buf, size_t len, pxl8_input_msg* msg) { + if (len < sizeof(pxl8_input_msg)) return 0; + pxl8_stream s = pxl8_stream_create(buf, (u32)len); + msg->buttons = pxl8_read_u32_be(&s); + msg->look_dx = pxl8_read_f32_be(&s); + msg->look_dy = pxl8_read_f32_be(&s); + msg->move_x = pxl8_read_f32_be(&s); + msg->move_y = pxl8_read_f32_be(&s); + msg->yaw = pxl8_read_f32_be(&s); + msg->tick = pxl8_read_u64_be(&s); + msg->timestamp = pxl8_read_u64_be(&s); + return s.offset; +} + +size_t pxl8_protocol_serialize_command(const pxl8_command_msg* msg, u8* buf, size_t len) { + if (len < sizeof(pxl8_command_msg)) return 0; + pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len); + pxl8_write_u16_be(&s, msg->cmd_type); + pxl8_write_bytes(&s, msg->payload, PXL8_COMMAND_PAYLOAD_SIZE); + pxl8_write_u16_be(&s, msg->payload_size); + pxl8_write_u64_be(&s, msg->tick); + return s.offset; +} + +size_t pxl8_protocol_deserialize_command(const u8* buf, size_t len, pxl8_command_msg* msg) { + if (len < sizeof(pxl8_command_msg)) return 0; + pxl8_stream s = pxl8_stream_create(buf, (u32)len); + msg->cmd_type = pxl8_read_u16_be(&s); + pxl8_read_bytes(&s, msg->payload, PXL8_COMMAND_PAYLOAD_SIZE); + msg->payload_size = pxl8_read_u16_be(&s); + msg->tick = pxl8_read_u64_be(&s); + return s.offset; +} + +size_t pxl8_protocol_serialize_entity_state(const pxl8_entity_state* state, u8* buf, size_t len) { + if (len < sizeof(pxl8_entity_state)) return 0; + pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len); + pxl8_write_u64_be(&s, state->entity_id); + pxl8_write_bytes(&s, state->userdata, 56); + return s.offset; +} + +size_t pxl8_protocol_deserialize_entity_state(const u8* buf, size_t len, pxl8_entity_state* state) { + if (len < sizeof(pxl8_entity_state)) return 0; + pxl8_stream s = pxl8_stream_create(buf, (u32)len); + state->entity_id = pxl8_read_u64_be(&s); + pxl8_read_bytes(&s, state->userdata, 56); + return s.offset; +} + +size_t pxl8_protocol_serialize_event(const pxl8_event_msg* msg, u8* buf, size_t len) { + if (len < sizeof(pxl8_event_msg)) return 0; + pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len); + pxl8_write_u8(&s, msg->event_type); + pxl8_write_bytes(&s, msg->payload, PXL8_EVENT_PAYLOAD_SIZE); + return s.offset; +} + +size_t pxl8_protocol_deserialize_event(const u8* buf, size_t len, pxl8_event_msg* msg) { + if (len < sizeof(pxl8_event_msg)) return 0; + pxl8_stream s = pxl8_stream_create(buf, (u32)len); + msg->event_type = pxl8_read_u8(&s); + pxl8_read_bytes(&s, msg->payload, PXL8_EVENT_PAYLOAD_SIZE); + return s.offset; +} + +size_t pxl8_protocol_serialize_snapshot_header(const pxl8_snapshot_header* hdr, u8* buf, size_t len) { + if (len < sizeof(pxl8_snapshot_header)) return 0; + pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len); + pxl8_write_u16_be(&s, hdr->entity_count); + pxl8_write_u16_be(&s, hdr->event_count); + pxl8_write_u64_be(&s, hdr->player_id); + pxl8_write_u64_be(&s, hdr->tick); + pxl8_write_f32_be(&s, hdr->time); + return s.offset; +} + +size_t pxl8_protocol_deserialize_snapshot_header(const u8* buf, size_t len, pxl8_snapshot_header* hdr) { + if (len < sizeof(pxl8_snapshot_header)) return 0; + pxl8_stream s = pxl8_stream_create(buf, (u32)len); + hdr->entity_count = pxl8_read_u16_be(&s); + hdr->event_count = pxl8_read_u16_be(&s); + hdr->player_id = pxl8_read_u64_be(&s); + hdr->tick = pxl8_read_u64_be(&s); + hdr->time = pxl8_read_f32_be(&s); + return s.offset; +} diff --git a/src/net/pxl8_protocol.h b/src/net/pxl8_protocol.h new file mode 100644 index 0000000..78a4207 --- /dev/null +++ b/src/net/pxl8_protocol.h @@ -0,0 +1,93 @@ +#pragma once + +#include "pxl8_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PXL8_PROTOCOL_VERSION 1 +#define PXL8_MAX_SNAPSHOT_ENTITIES 256 +#define PXL8_MAX_SNAPSHOT_EVENTS 32 +#define PXL8_COMMAND_PAYLOAD_SIZE 64 +#define PXL8_EVENT_PAYLOAD_SIZE 15 + +typedef enum pxl8_msg_type { + PXL8_MSG_NONE = 0, + PXL8_MSG_CONNECT, + PXL8_MSG_DISCONNECT, + PXL8_MSG_INPUT, + PXL8_MSG_COMMAND, + PXL8_MSG_SNAPSHOT, + PXL8_MSG_EVENT +} pxl8_msg_type; + +typedef struct pxl8_msg_header { + u32 sequence; + u16 size; + u8 type; + u8 version; +} pxl8_msg_header; + +typedef enum pxl8_cmd_type { + PXL8_CMD_NONE = 0, + PXL8_CMD_SPAWN_ENTITY, +} pxl8_cmd_type; + +typedef struct pxl8_input_msg { + u32 buttons; + f32 look_dx; + f32 look_dy; + f32 move_x; + f32 move_y; + f32 yaw; + u64 tick; + u64 timestamp; +} pxl8_input_msg; + +typedef struct pxl8_command_msg { + u16 cmd_type; + u8 payload[PXL8_COMMAND_PAYLOAD_SIZE]; + u16 payload_size; + u64 tick; +} pxl8_command_msg; + +typedef struct pxl8_entity_state { + u64 entity_id; + u8 userdata[56]; +} pxl8_entity_state; + +typedef struct pxl8_event_msg { + u8 event_type; + u8 payload[PXL8_EVENT_PAYLOAD_SIZE]; +} pxl8_event_msg; + +typedef struct pxl8_snapshot_header { + u16 entity_count; + u16 event_count; + u64 player_id; + u64 tick; + f32 time; +} pxl8_snapshot_header; + +size_t pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, size_t len); +size_t pxl8_protocol_deserialize_header(const u8* buf, size_t len, pxl8_msg_header* msg); + +size_t pxl8_protocol_serialize_input(const pxl8_input_msg* msg, u8* buf, size_t len); +size_t pxl8_protocol_deserialize_input(const u8* buf, size_t len, pxl8_input_msg* msg); + +size_t pxl8_protocol_serialize_command(const pxl8_command_msg* msg, u8* buf, size_t len); +size_t pxl8_protocol_deserialize_command(const u8* buf, size_t len, pxl8_command_msg* msg); + +size_t pxl8_protocol_serialize_entity_state(const pxl8_entity_state* state, u8* buf, size_t len); +size_t pxl8_protocol_deserialize_entity_state(const u8* buf, size_t len, pxl8_entity_state* state); + +size_t pxl8_protocol_serialize_event(const pxl8_event_msg* msg, u8* buf, size_t len); +size_t pxl8_protocol_deserialize_event(const u8* buf, size_t len, pxl8_event_msg* msg); + +size_t pxl8_protocol_serialize_snapshot_header(const pxl8_snapshot_header* hdr, u8* buf, size_t len); +size_t pxl8_protocol_deserialize_snapshot_header(const u8* buf, size_t len, pxl8_snapshot_header* hdr); + +#ifdef __cplusplus +} +#endif diff --git a/client/src/script/pxl8_repl.c b/src/script/pxl8_repl.c similarity index 100% rename from client/src/script/pxl8_repl.c rename to src/script/pxl8_repl.c diff --git a/client/src/script/pxl8_repl.h b/src/script/pxl8_repl.h similarity index 100% rename from client/src/script/pxl8_repl.h rename to src/script/pxl8_repl.h diff --git a/client/src/script/pxl8_script.c b/src/script/pxl8_script.c similarity index 100% rename from client/src/script/pxl8_script.c rename to src/script/pxl8_script.c diff --git a/client/src/script/pxl8_script.h b/src/script/pxl8_script.h similarity index 100% rename from client/src/script/pxl8_script.h rename to src/script/pxl8_script.h diff --git a/client/src/script/pxl8_script_ffi.h b/src/script/pxl8_script_ffi.h similarity index 78% rename from client/src/script/pxl8_script_ffi.h rename to src/script/pxl8_script_ffi.h index 66e1189..0729bab 100644 --- a/client/src/script/pxl8_script_ffi.h +++ b/src/script/pxl8_script_ffi.h @@ -29,6 +29,7 @@ static const char* pxl8_ffi_cdefs = "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" +"void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b);\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" @@ -189,10 +190,23 @@ static const char* pxl8_ffi_cdefs = "typedef struct { float x, y, z, w; } pxl8_vec4;\n" "typedef struct { float m[16]; } pxl8_mat4;\n" "\n" +"typedef struct pxl8_light {\n" +" pxl8_vec3 position;\n" +" u8 r, g, b;\n" +" u8 intensity;\n" +" f32 radius;\n" +" f32 radius_sq;\n" +" f32 inv_radius_sq;\n" +"} pxl8_light;\n" +"\n" "typedef struct pxl8_3d_uniforms {\n" " u8 ambient;\n" +" pxl8_vec3 celestial_dir;\n" +" f32 celestial_intensity;\n" " u8 fog_color;\n" " f32 fog_density;\n" +" pxl8_light lights[16];\n" +" u32 num_lights;\n" " f32 time;\n" "} pxl8_3d_uniforms;\n" "\n" @@ -211,6 +225,25 @@ static const char* pxl8_ffi_cdefs = "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" +"typedef enum pxl8_gfx_effect { PXL8_GFX_EFFECT_GLOWS = 0 } pxl8_gfx_effect;\n" +"\n" +"typedef enum pxl8_glow_shape { PXL8_GLOW_CIRCLE = 0, PXL8_GLOW_DIAMOND = 1, PXL8_GLOW_SHAFT = 2 } pxl8_glow_shape;\n" +"\n" +"typedef struct pxl8_glow_source {\n" +" u8 color;\n" +" u16 depth;\n" +" u8 height;\n" +" u16 intensity;\n" +" u8 radius;\n" +" pxl8_glow_shape shape;\n" +" i16 x;\n" +" i16 y;\n" +"} pxl8_glow_source;\n" +"\n" +"void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count);\n" +"void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx);\n" +"void pxl8_gfx_colormap_update(pxl8_gfx* gfx);\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" @@ -254,7 +287,7 @@ static const char* pxl8_ffi_cdefs = "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" +"void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const 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" @@ -386,4 +419,107 @@ static const char* pxl8_ffi_cdefs = "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_sfx_stop_voice(pxl8_sfx_context* ctx, u16 voice_id);\n" +"\n" +"typedef struct pxl8_net pxl8_net;\n" +"typedef enum pxl8_net_mode { PXL8_NET_LOCAL = 0, PXL8_NET_REMOTE } pxl8_net_mode;\n" +"typedef struct pxl8_net_config { const char* address; pxl8_net_mode mode; u16 port; } pxl8_net_config;\n" +"typedef enum pxl8_cmd_type { PXL8_CMD_NONE = 0, PXL8_CMD_SPAWN_ENTITY } pxl8_cmd_type;\n" +"\n" +"typedef struct pxl8_command_msg {\n" +" u16 cmd_type;\n" +" u8 payload[64];\n" +" u16 payload_size;\n" +" u64 tick;\n" +"} pxl8_command_msg;\n" +"\n" +"typedef struct pxl8_input_msg {\n" +" u32 buttons;\n" +" f32 look_dx;\n" +" f32 look_dy;\n" +" f32 move_x;\n" +" f32 move_y;\n" +" f32 yaw;\n" +" u64 tick;\n" +" u64 timestamp;\n" +"} pxl8_input_msg;\n" +"\n" +"typedef struct pxl8_entity_state {\n" +" u64 entity_id;\n" +" u8 userdata[56];\n" +"} pxl8_entity_state;\n" +"\n" +"typedef struct pxl8_snapshot_header {\n" +" u16 entity_count;\n" +" u16 event_count;\n" +" u64 player_id;\n" +" u64 tick;\n" +" f32 time;\n" +"} pxl8_snapshot_header;\n" +"\n" +"i32 pxl8_net_connect(pxl8_net* net);\n" +"bool pxl8_net_connected(const pxl8_net* net);\n" +"pxl8_net* pxl8_net_create(const pxl8_net_config* config);\n" +"void pxl8_net_destroy(pxl8_net* net);\n" +"void pxl8_net_disconnect(pxl8_net* net);\n" +"const pxl8_entity_state* pxl8_net_entities(const pxl8_net* net);\n" +"const u8* pxl8_net_entity_prev_userdata(const pxl8_net* net, u64 entity_id);\n" +"const u8* pxl8_net_entity_userdata(const pxl8_net* net, u64 entity_id);\n" +"const pxl8_input_msg* pxl8_net_input_at(const pxl8_net* net, u64 tick);\n" +"u64 pxl8_net_input_oldest_tick(const pxl8_net* net);\n" +"void pxl8_net_input_push(pxl8_net* net, const pxl8_input_msg* input);\n" +"f32 pxl8_net_lerp_alpha(const pxl8_net* net);\n" +"bool pxl8_net_needs_correction(const pxl8_net* net);\n" +"u64 pxl8_net_player_id(const pxl8_net* net);\n" +"bool pxl8_net_poll(pxl8_net* net);\n" +"u8* pxl8_net_predicted_state(pxl8_net* net);\n" +"void pxl8_net_predicted_tick_set(pxl8_net* net, u64 tick);\n" +"i32 pxl8_net_send_command(pxl8_net* net, const pxl8_command_msg* cmd);\n" +"i32 pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input);\n" +"const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net);\n" +"u64 pxl8_net_tick(const pxl8_net* net);\n" +"void pxl8_net_update(pxl8_net* net, f32 dt);\n" +"\n" +"void pxl8_bit_clear(u32* val, u8 bit);\n" +"u32 pxl8_bit_count(u32 val);\n" +"void pxl8_bit_set(u32* val, u8 bit);\n" +"bool pxl8_bit_test(u32 val, u8 bit);\n" +"void pxl8_bit_toggle(u32* val, u8 bit);\n" +"\n" +"void pxl8_pack_u8(u8* buf, size_t offset, u8 val);\n" +"void pxl8_pack_u16_be(u8* buf, size_t offset, u16 val);\n" +"void pxl8_pack_u16_le(u8* buf, size_t offset, u16 val);\n" +"void pxl8_pack_u32_be(u8* buf, size_t offset, u32 val);\n" +"void pxl8_pack_u32_le(u8* buf, size_t offset, u32 val);\n" +"void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val);\n" +"void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val);\n" +"void pxl8_pack_i8(u8* buf, size_t offset, i8 val);\n" +"void pxl8_pack_i16_be(u8* buf, size_t offset, i16 val);\n" +"void pxl8_pack_i16_le(u8* buf, size_t offset, i16 val);\n" +"void pxl8_pack_i32_be(u8* buf, size_t offset, i32 val);\n" +"void pxl8_pack_i32_le(u8* buf, size_t offset, i32 val);\n" +"void pxl8_pack_i64_be(u8* buf, size_t offset, i64 val);\n" +"void pxl8_pack_i64_le(u8* buf, size_t offset, i64 val);\n" +"void pxl8_pack_f32_be(u8* buf, size_t offset, f32 val);\n" +"void pxl8_pack_f32_le(u8* buf, size_t offset, f32 val);\n" +"void pxl8_pack_f64_be(u8* buf, size_t offset, f64 val);\n" +"void pxl8_pack_f64_le(u8* buf, size_t offset, f64 val);\n" +"\n" +"u8 pxl8_unpack_u8(const u8* buf, size_t offset);\n" +"u16 pxl8_unpack_u16_be(const u8* buf, size_t offset);\n" +"u16 pxl8_unpack_u16_le(const u8* buf, size_t offset);\n" +"u32 pxl8_unpack_u32_be(const u8* buf, size_t offset);\n" +"u32 pxl8_unpack_u32_le(const u8* buf, size_t offset);\n" +"u64 pxl8_unpack_u64_be(const u8* buf, size_t offset);\n" +"u64 pxl8_unpack_u64_le(const u8* buf, size_t offset);\n" +"i8 pxl8_unpack_i8(const u8* buf, size_t offset);\n" +"i16 pxl8_unpack_i16_be(const u8* buf, size_t offset);\n" +"i16 pxl8_unpack_i16_le(const u8* buf, size_t offset);\n" +"i32 pxl8_unpack_i32_be(const u8* buf, size_t offset);\n" +"i32 pxl8_unpack_i32_le(const u8* buf, size_t offset);\n" +"i64 pxl8_unpack_i64_be(const u8* buf, size_t offset);\n" +"i64 pxl8_unpack_i64_le(const u8* buf, size_t offset);\n" +"f32 pxl8_unpack_f32_be(const u8* buf, size_t offset);\n" +"f32 pxl8_unpack_f32_le(const u8* buf, size_t offset);\n" +"f64 pxl8_unpack_f64_be(const u8* buf, size_t offset);\n" +"f64 pxl8_unpack_f64_le(const u8* buf, size_t offset);\n"; diff --git a/client/src/sfx/pxl8_sfx.c b/src/sfx/pxl8_sfx.c similarity index 100% rename from client/src/sfx/pxl8_sfx.c rename to src/sfx/pxl8_sfx.c diff --git a/client/src/sfx/pxl8_sfx.h b/src/sfx/pxl8_sfx.h similarity index 100% rename from client/src/sfx/pxl8_sfx.h rename to src/sfx/pxl8_sfx.h diff --git a/client/src/world/pxl8_bsp.c b/src/world/pxl8_bsp.c similarity index 92% rename from client/src/world/pxl8_bsp.c rename to src/world/pxl8_bsp.c index 5beacd2..18b6367 100644 --- a/client/src/world/pxl8_bsp.c +++ b/src/world/pxl8_bsp.c @@ -572,15 +572,12 @@ static void collect_face_to_mesh( u16 base_idx = (u16)mesh->vertex_count; u32 num_verts = 0; - 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)) { - if (debug_this) pxl8_debug("face %u: edge %u failed to get vertex (surfedge_idx=%d)", face_id, i, surfedge_idx); continue; } @@ -609,11 +606,6 @@ static void collect_face_to_mesh( 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; for (u32 i = 1; i < num_verts - 1; i++) { @@ -630,8 +622,9 @@ void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 t 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_mat4 identity = pxl8_mat4_identity(); + pxl8_material mat = pxl8_material_create(texture_id); + pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat); } pxl8_mesh_destroy(mesh); @@ -674,9 +667,6 @@ void pxl8_bsp_render_textured(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 came 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; @@ -692,12 +682,9 @@ void pxl8_bsp_render_textured(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 came if (rendered_faces[face_id]) continue; rendered_faces[face_id] = 1; - 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; @@ -706,39 +693,21 @@ void pxl8_bsp_render_textured(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 came } 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_mat4 identity = pxl8_mat4_identity(); + pxl8_material mat = pxl8_material_with_lighting(pxl8_material_with_double_sided(pxl8_material_create(current_texture))); + pxl8_3d_draw_mesh(gfx, mesh, &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_mat4 identity = pxl8_mat4_identity(); + pxl8_material mat = pxl8_material_with_lighting(pxl8_material_with_double_sided(pxl8_material_create(current_texture))); + pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat); } pxl8_mesh_destroy(mesh); diff --git a/client/src/world/pxl8_bsp.h b/src/world/pxl8_bsp.h similarity index 100% rename from client/src/world/pxl8_bsp.h rename to src/world/pxl8_bsp.h diff --git a/client/src/world/pxl8_gen.c b/src/world/pxl8_gen.c similarity index 100% rename from client/src/world/pxl8_gen.c rename to src/world/pxl8_gen.c diff --git a/client/src/world/pxl8_gen.h b/src/world/pxl8_gen.h similarity index 100% rename from client/src/world/pxl8_gen.h rename to src/world/pxl8_gen.h diff --git a/client/src/world/pxl8_world.c b/src/world/pxl8_world.c similarity index 98% rename from client/src/world/pxl8_world.c rename to src/world/pxl8_world.c index 3f96b74..027ce03 100644 --- a/client/src/world/pxl8_world.c +++ b/src/world/pxl8_world.c @@ -398,12 +398,6 @@ void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) { 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); } else { diff --git a/client/src/world/pxl8_world.h b/src/world/pxl8_world.h similarity index 100% rename from client/src/world/pxl8_world.h rename to src/world/pxl8_world.h