From 8d491612ab45a7b96dbe5df709329941e35c5cdc Mon Sep 17 00:00:00 2001 From: asrael Date: Fri, 27 Feb 2026 06:50:49 -0600 Subject: [PATCH] feat(gui): add toolbar widget feat(gui): add grid_select, toggle, panel, status_bar, image widgets fix(bsp): fill in exterior cells --- .clang-format | 41 + .clangd | 30 + .gitignore | 9 +- demo/cart.fnl | 1 - demo/mod/entities.fnl | 60 +- demo/mod/first_person3d.fnl | 114 +-- pxl8.sh | 39 +- pxl8d/src/bsp.rs | 26 +- pxl8d/src/chunk.rs | 19 +- pxl8d/src/chunk/stream.rs | 29 +- pxl8d/src/lib.rs | 2 +- pxl8d/src/main.rs | 131 +-- pxl8d/src/procgen.rs | 1466 +++++++++++++++++++++++++--- pxl8d/src/sim.rs | 79 +- pxl8d/src/transport.rs | 50 +- pxl8d/src/world.rs | 56 +- src/asset/pxl8_cart.c | 116 +-- src/asset/pxl8_cart.h | 2 - src/asset/pxl8_save.c | 85 +- src/bsp/pxl8_bsp.c | 1 + src/bsp/pxl8_bsp.h | 11 + src/bsp/pxl8_bsp_render.c | 276 +----- src/bsp/pxl8_bsp_render.h | 6 +- src/core/pxl8.c | 50 +- src/core/pxl8_io.c | 39 +- src/core/pxl8_io.h | 12 +- src/core/pxl8_log.c | 10 +- src/core/pxl8_queue.h | 2 +- src/core/pxl8_types.h | 7 +- src/gfx/pxl8_atlas.c | 32 +- src/gfx/pxl8_atlas.h | 6 +- src/gfx/pxl8_blit.c | 41 +- src/gfx/pxl8_blit.h | 14 +- src/gfx/pxl8_color.h | 24 - src/gfx/pxl8_gfx.c | 108 +- src/gfx/pxl8_gfx.h | 8 +- src/gfx/pxl8_gfx3d.h | 7 +- src/gfx/pxl8_render.c | 83 +- src/gfx/pxl8_render.h | 9 +- src/gfx/pxl8_render_types.h | 27 +- src/gfx/pxl8_tilemap.c | 2 - src/gfx/pxl8_tilesheet.c | 58 +- src/gfx/pxl8_transition.c | 6 +- src/gui/pxl8_gui.c | 76 ++ src/gui/pxl8_gui.h | 17 + src/hal/pxl8_hal_sdl3.c | 13 +- src/hal/pxl8_io_sdl3.c | 125 +++ src/lua/pxl8.lua | 22 +- src/lua/pxl8/gfx.lua | 74 +- src/lua/pxl8/net.lua | 16 +- src/lua/pxl8/world.lua | 4 + src/math/pxl8_math.h | 4 + src/net/pxl8_net.c | 150 +-- src/net/pxl8_net.h | 7 +- src/net/pxl8_protocol.c | 22 +- src/net/pxl8_protocol.h | 22 +- src/script/pxl8_script.c | 96 +- src/script/pxl8_script_ffi.h | 38 +- src/sim/pxl8_sim.c | 354 +++++-- src/sim/pxl8_sim.h | 5 +- src/world/pxl8_world.c | 534 +++++++++- src/world/pxl8_world.h | 23 +- src/world/pxl8_world_chunk_cache.c | 40 +- 63 files changed, 3150 insertions(+), 1686 deletions(-) create mode 100644 .clang-format create mode 100644 .clangd create mode 100644 src/hal/pxl8_io_sdl3.c diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..5b275ae --- /dev/null +++ b/.clang-format @@ -0,0 +1,41 @@ +BasedOnStyle: LLVM + +Language: C +Standard: Latest + +IndentWidth: 4 +TabWidth: 4 +UseTab: Never +ColumnLimit: 120 + +PointerAlignment: Left +ReferenceAlignment: Left +SpaceBeforeParens: ControlStatements + +BreakBeforeBraces: Attach +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLoopsOnASingleLine: false +AllowShortBlocksOnASingleLine: Empty + +BinPackArguments: true +BinPackParameters: true +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: Consecutive +AlignEscapedNewlines: Left +AlignOperands: Align + +IncludeBlocks: Preserve +SortIncludes: Never + +SpaceAfterCStyleCast: false +SpacesInParens: Never + +IndentCaseLabels: false +IndentPPDirectives: None + +MaxEmptyLinesToKeep: 1 +KeepEmptyLines: + AtEndOfFile: false + AtStartOfBlock: false + AtStartOfFile: false diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..fc7d04a --- /dev/null +++ b/.clangd @@ -0,0 +1,30 @@ +CompileFlags: + CompilationDatabase: . + +Diagnostics: + UnusedIncludes: None + MissingIncludes: None + ClangTidy: + Add: + - bugprone-sizeof-expression + - bugprone-suspicious-memset-usage + - bugprone-integer-division + - bugprone-macro-parentheses + - bugprone-bool-pointer-implicit-conversion + - bugprone-multiple-statement-macro + - performance-type-promotion-in-math-fn + Remove: + - bugprone-easily-swappable-parameters + +InlayHints: + Enabled: Yes + ParameterNames: Yes + DeducedTypes: Yes + Designators: Yes + BlockEnd: No + +Hover: + ShowAKA: Yes + +Completion: + AllScopes: Yes diff --git a/.gitignore b/.gitignore index 5a08441..b2ac494 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ -.ccls-cache/ -.ccls -**.DS_Store - -.pxl8_history *.aseprite-extension +.cache +compile_commands.json +**.DS_Store +.pxl8_history diff --git a/demo/cart.fnl b/demo/cart.fnl index 235cffe..1c3d431 100644 --- a/demo/cart.fnl +++ b/demo/cart.fnl @@ -1,4 +1,3 @@ {:title "pxl8 demo" - :pixel-mode "indexed" :resolution "640x360" :window-size [1280 720]} diff --git a/demo/mod/entities.fnl b/demo/mod/entities.fnl index c4077e2..ca00a15 100644 --- a/demo/mod/entities.fnl +++ b/demo/mod/entities.fnl @@ -1,36 +1,9 @@ (local pxl8 (require :pxl8)) -(local DOOR_HEIGHT 96) -(local DOOR_WIDTH 48) -(local DOOR_X 892) -(local DOOR_Z 416) (local FIREBALL_COLOR 218) -(var door-mesh nil) -(var door-tex nil) (var fireball-mesh nil) -(fn create-door-mesh [bsp ambient] - (let [verts [] - indices [] - hw (/ DOOR_WIDTH 2) - y0 0 - y1 DOOR_HEIGHT - x DOOR_X - sample (fn [vx vy vz] - (if bsp (bsp:light_at vx vy vz (or ambient 0)) 200))] - (table.insert verts {:x x :y y0 :z (- DOOR_Z hw) :u 0 :v 1 :nx -1 :ny 0 :nz 0 :color 80 :light (sample x y0 (- DOOR_Z hw))}) - (table.insert verts {:x x :y y0 :z (+ DOOR_Z hw) :u 1 :v 1 :nx -1 :ny 0 :nz 0 :color 80 :light (sample x y0 (+ DOOR_Z hw))}) - (table.insert verts {:x x :y y1 :z (+ DOOR_Z hw) :u 1 :v 0 :nx -1 :ny 0 :nz 0 :color 80 :light (sample x y1 (+ DOOR_Z hw))}) - (table.insert verts {:x x :y y1 :z (- DOOR_Z hw) :u 0 :v 0 :nx -1 :ny 0 :nz 0 :color 80 :light (sample x y1 (- DOOR_Z hw))}) - (table.insert indices 0) - (table.insert indices 1) - (table.insert indices 2) - (table.insert indices 0) - (table.insert indices 2) - (table.insert indices 3) - (set door-mesh (pxl8.create_mesh verts indices)))) - (fn create-fireball-mesh [] (let [verts [] indices [] @@ -145,40 +118,15 @@ (set fireball-mesh (pxl8.create_mesh verts indices)))) -(fn get-door-position [] - (values DOOR_X DOOR_Z)) - -(fn get-door-radius [] - 20) - (fn init [textures] (when (not fireball-mesh) - (create-fireball-mesh)) - (when (and (not door-tex) textures) - (set door-tex (textures.door)))) + (create-fireball-mesh))) -(fn setup-lighting [bsp ambient] - (when (and (not door-mesh) bsp) - (create-door-mesh bsp ambient))) - -(fn render-door [wireframe floor-y] - (when (and door-mesh door-tex) - (pxl8.draw_mesh door-mesh {:x 0 :y (or floor-y 0) :z 0 - :texture door-tex - :lighting true - :double_sided true - :wireframe wireframe}))) - -(fn render-fireball [x y z wireframe] +(fn render-fireball [x y z] (when fireball-mesh (pxl8.draw_mesh fireball-mesh {:x x :y y :z z - :emissive true - :wireframe wireframe}))) + :emissive true}))) {:FIREBALL_COLOR FIREBALL_COLOR - :get-door-position get-door-position - :get-door-radius get-door-radius :init init - :render-door render-door - :render-fireball render-fireball - :setup-lighting setup-lighting} + :render-fireball render-fireball} diff --git a/demo/mod/first_person3d.fnl b/demo/mod/first_person3d.fnl index 20c1240..68717d8 100644 --- a/demo/mod/first_person3d.fnl +++ b/demo/mod/first_person3d.fnl @@ -8,8 +8,8 @@ (local sky (require :mod.sky)) (local textures (require :mod.textures)) -(local bob-amount 4.0) -(local bob-speed 8.0) +(local bob-amount 3.0) +(local bob-speed 6.0) (local cam-smoothing 0.25) (local land-recovery-speed 20) (local land-squash-amount -4) @@ -31,7 +31,6 @@ (var auto-run-cancel-key nil) (var auto-run? false) -(var was-auto-running false) (var bob-time 0) (var cam-pitch 0) (var day-time 0) @@ -40,29 +39,29 @@ (var cam-yaw 0) (var cam-z 416) (var camera nil) -(var ceiling-tex nil) -(var floor-tex nil) (var land-squash 0) (var last-dt 0.016) (var light-time 0) (var glows nil) (var lights nil) -(var bsp-materials-setup false) +(var materials-set? false) (var network nil) -(var portal-cooldown 0) (var real-time 0) (var smooth-cam-x 416) (var smooth-cam-z 416) (var was-grounded true) -(var trim-tex nil) -(var wall-tex nil) (var world nil) (local MOSS_COLOR 200) -(local PLASTER_COLOR 16) +(local GRASS_COLOR 200) (local STONE_WALL_START 2) (local WOOD_COLOR 88) +(local ASHLAR_COLOR 64) +(local ASHLAR_MOSS 68) +(local STONE_FLOOR_COLOR 72) +(local STONE_TRIM_COLOR 72) + (fn preload [] (when (not network) (set network (net.get)) @@ -71,8 +70,7 @@ (when (not world) (set world (pxl8.get_world)) (when world - (world:set_sim_config sim-cfg) - (world:init_local_player cam-x cam-y cam-z)))) + (world:set_sim_config sim-cfg)))) (fn is-connected [] (and network (network:connected))) @@ -111,29 +109,38 @@ (when world (world:init_local_player cam-x cam-y cam-z) (set smooth-cam-x cam-x) - (set smooth-cam-z cam-z)) - - (when (not ceiling-tex) - (set ceiling-tex (textures.plaster-wall 66666 PLASTER_COLOR))) - (when (not floor-tex) - (set floor-tex (textures.wood-planks 44444 WOOD_COLOR))) - (when (not trim-tex) - (set trim-tex (textures.wood-trim 77777 WOOD_COLOR))) - (when (not wall-tex) - (set wall-tex (textures.cobble-timber 55555 STONE_WALL_START MOSS_COLOR WOOD_COLOR)))) + (set smooth-cam-z cam-z))) (fn setup-materials [] - (when (and world (not bsp-materials-setup) floor-tex trim-tex wall-tex) - (let [chunk (world:active_chunk)] - (when (and chunk (chunk:ready)) - (let [floor-mat (pxl8.create_material {:texture floor-tex :lighting true :double_sided true}) - trim-mat (pxl8.create_material {:texture trim-tex :lighting true :double_sided true}) - wall-mat (pxl8.create_material {:texture wall-tex :lighting true :double_sided true})] - (world:set_bsp_material 0 floor-mat) - (world:set_bsp_material 1 wall-mat) - (world:set_bsp_material 3 trim-mat) - (entities.setup-lighting (chunk:bsp) 2) - (set bsp-materials-setup true)))))) + (when (not world) (lua "return")) + (when (not network) (lua "return")) + (when materials-set? (lua "return")) + (when (not (network:has_chunk)) (lua "return")) + (let [chunk (world:active_chunk)] + (when (not chunk) (lua "return")) + (when (not (chunk:ready)) (lua "return")) + (let [dungeon-floor-tex (textures.wood-planks 44444 WOOD_COLOR) + dungeon-wall-tex (textures.cobble-timber 55555 STONE_WALL_START MOSS_COLOR WOOD_COLOR) + dungeon-trim-tex (textures.wood-trim 77777 WOOD_COLOR) + court-floor-tex (textures.rough-stone 44442 STONE_FLOOR_COLOR) + court-wall-tex (textures.ashlar-wall 55552 ASHLAR_COLOR ASHLAR_MOSS) + court-trim-tex (textures.rough-stone 77772 STONE_TRIM_COLOR) + grass-tex (textures.grass-top 44447 GRASS_COLOR) + dungeon-floor-mat (pxl8.create_material {:texture dungeon-floor-tex :lighting true :double_sided true}) + dungeon-wall-mat (pxl8.create_material {:texture dungeon-wall-tex :lighting true :double_sided true}) + dungeon-trim-mat (pxl8.create_material {:texture dungeon-trim-tex :lighting true :double_sided true}) + court-floor-mat (pxl8.create_material {:texture court-floor-tex :lighting true :double_sided true}) + court-wall-mat (pxl8.create_material {:texture court-wall-tex :lighting true :double_sided true}) + court-trim-mat (pxl8.create_material {:texture court-trim-tex :lighting true :double_sided true}) + grass-mat (pxl8.create_material {:texture grass-tex :lighting true :double_sided true})] + (world:set_bsp_material 0 dungeon-floor-mat) + (world:set_bsp_material 1 dungeon-wall-mat) + (world:set_bsp_material 3 dungeon-trim-mat) + (world:set_bsp_material 4 court-floor-mat) + (world:set_bsp_material 5 court-wall-mat) + (world:set_bsp_material 6 court-trim-mat) + (world:set_bsp_material 7 grass-mat) + (set materials-set? true)))) (fn sample-input [] (when (pxl8.key_pressed "`") @@ -147,8 +154,6 @@ (when (and auto-run-cancel-key (not (pxl8.key_down auto-run-cancel-key))) (set auto-run-cancel-key nil)) - (set was-auto-running auto-run?) - (pxl8.make_input_msg {:move_x (+ (if (pxl8.key_down "d") 1 0) (if (pxl8.key_down "a") -1 0)) :move_y (+ (if (or (pxl8.key_down "w") auto-run?) 1 0) @@ -161,41 +166,6 @@ (set last-dt dt) (setup-materials) - (when (> portal-cooldown 0) - (set portal-cooldown (- portal-cooldown dt))) - - (when (and world network (<= portal-cooldown 0)) - (let [(door-x door-z) (entities.get-door-position) - door-radius (entities.get-door-radius) - dist-to-door (math.sqrt (+ (* (- cam-x door-x) (- cam-x door-x)) - (* (- cam-z door-z) (- cam-z door-z))))] - (when (< dist-to-door door-radius) - (let [current-id (network:chunk_id)] - (if (= current-id 1) - (do - (pxl8.info "Door: BSP 1 -> BSP 2") - (network:enter_scene 1 2 416 0 416) - (world:init_local_player 416 0 416) - (set cam-x 416) - (set cam-y 0) - (set cam-z 416) - (set smooth-cam-x 416) - (set smooth-cam-z 416) - (set bsp-materials-setup false) - (set portal-cooldown 2.0)) - (= current-id 2) - (do - (pxl8.info "Door: BSP 2 -> BSP 1") - (network:enter_scene 1 1 416 0 416) - (world:init_local_player 416 0 416) - (set cam-x 416) - (set cam-y 0) - (set cam-z 416) - (set smooth-cam-x 416) - (set smooth-cam-z 416) - (set bsp-materials-setup false) - (set portal-cooldown 2.0))))))) - (when world (let [input-msg (sample-input) player (world:local_player)] @@ -286,9 +256,7 @@ (world:render [smooth-cam-x eye-y smooth-cam-z]) (when chunk - (entities.render-fireball light-x light-y light-z (menu.is-wireframe))) - - (entities.render-door (menu.is-wireframe) 0) + (entities.render-fireball light-x light-y light-z)) (pxl8.end_frame_3d) diff --git a/pxl8.sh b/pxl8.sh index 2401f38..33e265d 100755 --- a/pxl8.sh +++ b/pxl8.sh @@ -43,10 +43,6 @@ NC='\033[0m' RED='\033[38;2;251;73;52m' YELLOW='\033[38;2;250;189;47m' -if [[ "$(uname)" == "Linux" ]]; then - CFLAGS="$CFLAGS -D_GNU_SOURCE" -fi - build_luajit() { if [[ ! -f "lib/luajit/src/libluajit.a" ]]; then print_info "Building LuaJIT" @@ -167,6 +163,37 @@ compile_source_file() { fi } +generate_compile_commands() { + local project_dir + project_dir="$(pwd)" + local tmpfile + tmpfile=$(mktemp) + local first=true + + echo "[" > "$tmpfile" + + for src_file in $LIB_SOURCE_FILES; do + src_file="${src_file## }" + src_file="${src_file%% }" + [[ -z "$src_file" ]] && continue + if $first; then first=false; else printf ",\n" >> "$tmpfile"; fi + printf ' {\n "directory": "%s",\n "command": "clang -c %s %s",\n "file": "%s"\n }' \ + "$project_dir" "$DEP_COMPILE_FLAGS" "$src_file" "$src_file" >> "$tmpfile" + done + + for src_file in $PXL8_SOURCE_FILES; do + src_file="${src_file## }" + src_file="${src_file%% }" + [[ -z "$src_file" ]] && continue + if $first; then first=false; else printf ",\n" >> "$tmpfile"; fi + printf ' {\n "directory": "%s",\n "command": "clang -c %s %s",\n "file": "%s"\n }' \ + "$project_dir" "$COMPILE_FLAGS" "$src_file" "$src_file" >> "$tmpfile" + done + + printf "\n]\n" >> "$tmpfile" + mv "$tmpfile" "compile_commands.json" +} + compile_shaders() { local build_mode="$1" local shader_dir="src/gfx/shaders/cpu" @@ -536,11 +563,13 @@ case "$COMMAND" in " if [[ "$HAS_SDL3" -eq 1 ]]; then - PXL8_SOURCE_FILES="$PXL8_SOURCE_FILES src/hal/pxl8_mem_sdl3.c" + PXL8_SOURCE_FILES="$PXL8_SOURCE_FILES src/hal/pxl8_io_sdl3.c src/hal/pxl8_mem_sdl3.c" else PXL8_SOURCE_FILES="$PXL8_SOURCE_FILES src/hal/pxl8_mem.c" fi + generate_compile_commands + LUAJIT_LIB="lib/luajit/src/libluajit.a" OBJECT_DIR="$BUILDDIR/obj" mkdir -p "$OBJECT_DIR" diff --git a/pxl8d/src/bsp.rs b/pxl8d/src/bsp.rs index c414f3b..9653d62 100644 --- a/pxl8d/src/bsp.rs +++ b/pxl8d/src/bsp.rs @@ -1,5 +1,3 @@ -extern crate alloc; - use alloc::boxed::Box; use alloc::vec::Vec; @@ -146,6 +144,7 @@ pub struct Bsp { pub vertex_lights: Box<[u32]>, pub vertices: Box<[Vertex]>, pub visdata: Box<[u8]>, + pub heightfield: Box<[f32]>, } #[derive(Default)] @@ -161,6 +160,16 @@ pub struct BspBuilder { pub vertex_lights: Vec, pub vertices: Vec, pub visdata: Vec, + pub heightfield: Vec, + pub heightfield_w: u16, + pub heightfield_h: u16, + pub heightfield_ox: f32, + pub heightfield_oz: f32, + pub heightfield_cell_size: f32, + pub bounds_min_x: f32, + pub bounds_min_z: f32, + pub bounds_max_x: f32, + pub bounds_max_z: f32, } impl BspBuilder { @@ -182,6 +191,7 @@ impl From for Bsp { let vertex_lights = b.vertex_lights.into_boxed_slice(); let vertices = b.vertices.into_boxed_slice(); let visdata = b.visdata.into_boxed_slice(); + let heightfield = b.heightfield.into_boxed_slice(); let inner = pxl8_bsp { cell_portals: if cell_portals.is_empty() { core::ptr::null_mut() } else { cell_portals.as_ptr() as *mut _ }, @@ -198,6 +208,7 @@ impl From for Bsp { vertex_lights: if vertex_lights.is_empty() { core::ptr::null_mut() } else { vertex_lights.as_ptr() as *mut _ }, vertices: if vertices.is_empty() { core::ptr::null_mut() } else { vertices.as_ptr() as *mut _ }, visdata: if visdata.is_empty() { core::ptr::null_mut() } else { visdata.as_ptr() as *mut _ }, + heightfield: if heightfield.is_empty() { core::ptr::null_mut() } else { heightfield.as_ptr() as *mut _ }, lightdata_size: 0, num_cell_portals: cell_portals.len() as u32, num_edges: edges.len() as u32, @@ -211,7 +222,17 @@ impl From for Bsp { num_surfedges: surfedges.len() as u32, num_vertex_lights: vertex_lights.len() as u32, num_vertices: vertices.len() as u32, + num_heightfield: heightfield.len() as u32, + heightfield_ox: b.heightfield_ox, + heightfield_oz: b.heightfield_oz, + heightfield_cell_size: b.heightfield_cell_size, + heightfield_w: b.heightfield_w, + heightfield_h: b.heightfield_h, visdata_size: visdata.len() as u32, + bounds_min_x: b.bounds_min_x, + bounds_min_z: b.bounds_min_z, + bounds_max_x: b.bounds_max_x, + bounds_max_z: b.bounds_max_z, }; Self { @@ -227,6 +248,7 @@ impl From for Bsp { vertex_lights, vertices, visdata, + heightfield, } } } diff --git a/pxl8d/src/chunk.rs b/pxl8d/src/chunk.rs index 6283fca..6ecbde1 100644 --- a/pxl8d/src/chunk.rs +++ b/pxl8d/src/chunk.rs @@ -1,38 +1,23 @@ -extern crate alloc; - pub mod stream; use crate::bsp::Bsp; -use crate::math::Vec3; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] pub enum ChunkId { - Bsp(u32), + Bsp { cx: i32, cz: i32 }, } pub enum Chunk { - Bsp { id: u32, bsp: Bsp, version: u32 }, + Bsp { cx: i32, cz: i32, bsp: Bsp, version: u32 }, } impl Chunk { - pub fn id(&self) -> ChunkId { - match self { - Chunk::Bsp { id, .. } => ChunkId::Bsp(*id), - } - } - pub fn version(&self) -> u32 { match self { Chunk::Bsp { version, .. } => *version, } } - pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 { - match self { - Chunk::Bsp { bsp, .. } => bsp.trace(from, to, radius), - } - } - pub fn as_bsp(&self) -> Option<&Bsp> { match self { Chunk::Bsp { bsp, .. } => Some(bsp), diff --git a/pxl8d/src/chunk/stream.rs b/pxl8d/src/chunk/stream.rs index 6b5f66e..daaa68f 100644 --- a/pxl8d/src/chunk/stream.rs +++ b/pxl8d/src/chunk/stream.rs @@ -1,5 +1,3 @@ -extern crate alloc; - use alloc::collections::BTreeMap; use alloc::vec::Vec; @@ -28,11 +26,11 @@ impl ClientChunkState { } pub fn next_pending(&mut self) -> Option { - self.pending.pop() - } - - pub fn queue_message(&mut self, msg: ChunkMessage) { - self.pending_messages.push(msg); + if self.pending.is_empty() { + None + } else { + Some(self.pending.remove(0)) + } } pub fn queue_messages(&mut self, msgs: Vec) { @@ -50,23 +48,6 @@ impl ClientChunkState { pub fn mark_sent(&mut self, id: ChunkId, version: u32) { self.known.insert(id, version); } - - pub fn has_pending(&self) -> bool { - !self.pending.is_empty() || !self.pending_messages.is_empty() - } - - pub fn clear_pending(&mut self) { - self.pending.clear(); - self.pending_messages.clear(); - } - - pub fn clear_known_bsp(&mut self) { - self.known.retain(|id, _| !matches!(id, ChunkId::Bsp(_))); - } - - pub fn forget(&mut self, id: &ChunkId) { - self.known.remove(id); - } } impl Default for ClientChunkState { diff --git a/pxl8d/src/lib.rs b/pxl8d/src/lib.rs index b7fbb33..032aa31 100644 --- a/pxl8d/src/lib.rs +++ b/pxl8d/src/lib.rs @@ -31,7 +31,7 @@ pub use bsp::*; pub use chunk::*; pub use chunk::stream::*; pub use math::*; -pub use procgen::{ProcgenParams, generate, generate_rooms}; +pub use procgen::generate_chunk; pub use pxl8::*; pub use sim::*; pub use transport::*; diff --git a/pxl8d/src/main.rs b/pxl8d/src/main.rs index e67ec8a..da3d2a9 100644 --- a/pxl8d/src/main.rs +++ b/pxl8d/src/main.rs @@ -3,9 +3,12 @@ extern crate alloc; +use alloc::vec::Vec; use pxl8d::*; use pxl8d::chunk::ChunkId; use pxl8d::chunk::stream::ClientChunkState; +use pxl8d::pxl8::pxl8_cmd_type::*; +use pxl8d::pxl8::pxl8_msg_type::*; const TICK_RATE: u64 = 30; const TICK_NS: u64 = 1_000_000_000 / TICK_RATE; @@ -73,6 +76,9 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { let mut player_id: Option = None; let mut last_client_tick: u64 = 0; let mut client_chunks = ClientChunkState::new(); + let mut player_cx: i32 = 0; + let mut player_cz: i32 = 0; + let mut has_sent_initial_enter = false; let mut sequence: u32 = 0; let mut last_tick = get_time_ns(); @@ -93,52 +99,14 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { let mut latest_input: Option = None; while let Some(msg_type) = transport.recv() { match msg_type { - x if x == pxl8d::pxl8_msg_type::PXL8_MSG_INPUT as u8 => { + x if x == PXL8_MSG_INPUT as u8 => { latest_input = Some(transport.get_input()); } - x if x == pxl8d::pxl8_msg_type::PXL8_MSG_COMMAND as u8 => { + x if x == PXL8_MSG_COMMAND as u8 => { let cmd = transport.get_command(); - if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_SPAWN_ENTITY as u16 { + if cmd.cmd_type == PXL8_CMD_SPAWN_ENTITY as u16 { let (x, y, z, _yaw, _pitch) = extract_spawn_position(&cmd.payload); player_id = Some(sim.spawn_player(x, y, z) as u64); - - sim.world.generate_bsp(1, None); - sim.world.set_active(ChunkId::Bsp(1)); - client_chunks.request(ChunkId::Bsp(1)); - transport.send_chunk_enter(1, transport::CHUNK_TYPE_BSP, sequence); - sequence = sequence.wrapping_add(1); - } else if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_ENTER_SCENE as u16 { - let chunk_id = u32::from_be_bytes([ - cmd.payload[0], cmd.payload[1], cmd.payload[2], cmd.payload[3] - ]); - let pos_x = f32::from_be_bytes([cmd.payload[8], cmd.payload[9], cmd.payload[10], cmd.payload[11]]); - let pos_y = f32::from_be_bytes([cmd.payload[12], cmd.payload[13], cmd.payload[14], cmd.payload[15]]); - let pos_z = f32::from_be_bytes([cmd.payload[16], cmd.payload[17], cmd.payload[18], cmd.payload[19]]); - - sim.world.clear_active(); - client_chunks.clear_pending(); - client_chunks.clear_known_bsp(); - transport.send_chunk_exit(sequence); - sequence = sequence.wrapping_add(1); - - let params = match chunk_id { - 2 => Some(ProcgenParams { - width: 20, - height: 20, - seed: 12345u32.wrapping_add(2), - min_room_size: 14, - max_room_size: 16, - num_rooms: 1, - }), - _ => None, - }; - sim.world.generate_bsp(chunk_id, params.as_ref()); - sim.world.set_active(ChunkId::Bsp(chunk_id)); - client_chunks.request(ChunkId::Bsp(chunk_id)); - transport.send_chunk_enter(chunk_id, transport::CHUNK_TYPE_BSP, sequence); - sequence = sequence.wrapping_add(1); - - sim.teleport_player(pos_x, pos_y, pos_z); } } _ => {} @@ -154,7 +122,7 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { } let mut count = 0; - sim.generate_snapshot(player_id.unwrap_or(u64::MAX), |state| { + sim.generate_snapshot(|state| { if count < entities_buf.len() { entities_buf[count] = *state; count += 1; @@ -173,22 +141,46 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { sequence = sequence.wrapping_add(1); if let Some(pid) = player_id { - if let Some(_player) = sim.get_player_position(pid) { + if let Some((px, _py, pz)) = sim.get_player_position(pid) { + let new_cx = libm::floorf(px / 1024.0) as i32; + let new_cz = libm::floorf(pz / 1024.0) as i32; + + for dz in -2..=2_i32 { + for dx in -2..=2_i32 { + let cx = new_cx + dx; + let cz = new_cz + dz; + sim.world.generate_bsp(cx, cz); + client_chunks.request(ChunkId::Bsp { cx, cz }); + } + } + + if !has_sent_initial_enter || player_cx != new_cx || player_cz != new_cz { + player_cx = new_cx; + player_cz = new_cz; + sim.world.set_active(ChunkId::Bsp { cx: new_cx, cz: new_cz }); + transport.send_chunk_enter(new_cx, new_cz, sequence); + sequence = sequence.wrapping_add(1); + has_sent_initial_enter = true; + } + + let mut burst = false; while let Some(chunk_id) = client_chunks.next_pending() { match chunk_id { - ChunkId::Bsp(id) => { + ChunkId::Bsp { cx, cz } => { if let Some(chunk) = sim.world.get(&chunk_id) { if let Some(bsp) = chunk.as_bsp() { - let msgs = bsp_to_messages(bsp, id, chunk.version()); + let msgs = bsp_to_messages(bsp, cx, cz, chunk.version()); client_chunks.queue_messages(msgs); client_chunks.mark_sent(chunk_id, chunk.version()); + burst = true; } } } } } - for _ in 0..8 { + let send_limit = if burst { 64 } else { 8 }; + for _ in 0..send_limit { if let Some(msg) = client_chunks.next_message() { transport.send_chunk(&msg, sequence); sequence = sequence.wrapping_add(1); @@ -202,30 +194,21 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { } } -fn bsp_to_messages(bsp: &bsp::Bsp, id: u32, version: u32) -> alloc::vec::Vec { - use alloc::vec::Vec; - +fn bsp_to_messages(bsp: &bsp::Bsp, cx: i32, cz: i32, version: u32) -> Vec { let mut data = Vec::new(); - let num_verts = bsp.vertices.len(); - let num_edges = bsp.edges.len(); - let num_faces = bsp.faces.len(); - let num_planes = bsp.planes.len(); - let num_nodes = bsp.nodes.len(); - let num_leafs = bsp.leafs.len(); - let num_surfedges = bsp.surfedges.len(); - - data.extend_from_slice(&(num_verts as u32).to_be_bytes()); - data.extend_from_slice(&(num_edges as u32).to_be_bytes()); - data.extend_from_slice(&(num_faces as u32).to_be_bytes()); - data.extend_from_slice(&(num_planes as u32).to_be_bytes()); - data.extend_from_slice(&(num_nodes as u32).to_be_bytes()); - data.extend_from_slice(&(num_leafs as u32).to_be_bytes()); - data.extend_from_slice(&(num_surfedges as u32).to_be_bytes()); + data.extend_from_slice(&(bsp.vertices.len() as u32).to_be_bytes()); + data.extend_from_slice(&(bsp.edges.len() as u32).to_be_bytes()); + data.extend_from_slice(&(bsp.faces.len() as u32).to_be_bytes()); + data.extend_from_slice(&(bsp.planes.len() as u32).to_be_bytes()); + data.extend_from_slice(&(bsp.nodes.len() as u32).to_be_bytes()); + data.extend_from_slice(&(bsp.leafs.len() as u32).to_be_bytes()); + data.extend_from_slice(&(bsp.surfedges.len() as u32).to_be_bytes()); data.extend_from_slice(&(bsp.marksurfaces.len() as u32).to_be_bytes()); data.extend_from_slice(&(bsp.cell_portals.len() as u32).to_be_bytes()); data.extend_from_slice(&(bsp.visdata.len() as u32).to_be_bytes()); data.extend_from_slice(&(bsp.vertex_lights.len() as u32).to_be_bytes()); + data.extend_from_slice(&(bsp.heightfield.len() as u32).to_be_bytes()); for v in &bsp.vertices { data.extend_from_slice(&v.position.x.to_be_bytes()); @@ -318,5 +301,23 @@ fn bsp_to_messages(bsp: &bsp::Bsp, id: u32, version: u32) -> alloc::vec::Vec Option { + doorways.iter().find(|d| { + d.dir == dir && (d.wall_x - wall_x).abs() < 0.1 && (d.wall_z - wall_z).abs() < 0.1 + }).copied() +} + struct RoomGrid { cells: Vec, width: i32, @@ -112,6 +144,8 @@ struct BspBuildContext<'a> { grid: &'a RoomGrid, node_count: u32, plane_offset: u32, + origin_x: f32, + origin_z: f32, } fn carve_corridor_h(grid: &mut RoomGrid, x1: i32, x2: i32, y: i32) { @@ -134,6 +168,76 @@ fn carve_corridor_v(grid: &mut RoomGrid, y1: i32, y2: i32, x: i32) { } } +fn carve_entries(grid: &mut RoomGrid, entries: &[ChunkEntry]) { + for entry in entries { + match entry.edge { + ChunkEdge::West => { + for x in 0..grid.width { + if grid.get(x, entry.cell) == 0 { break; } + grid.set(x, entry.cell, 0); + } + } + ChunkEdge::East => { + for x in (0..grid.width).rev() { + if grid.get(x, entry.cell) == 0 { break; } + grid.set(x, entry.cell, 0); + } + } + ChunkEdge::North => { + for z in 0..grid.height { + if grid.get(entry.cell, z) == 0 { break; } + grid.set(entry.cell, z, 0); + } + } + ChunkEdge::South => { + for z in (0..grid.height).rev() { + if grid.get(entry.cell, z) == 0 { break; } + grid.set(entry.cell, z, 0); + } + } + } + } +} + +fn shrink_grid(grid: &RoomGrid, entries: &[ChunkEntry]) -> (RoomGrid, i32, i32) { + let (mut min_x, mut min_z, mut max_x, mut max_z) = (grid.width, grid.height, 0i32, 0i32); + for z in 0..grid.height { + for x in 0..grid.width { + if grid.get(x, z) == 0 { + if x < min_x { min_x = x; } + if x > max_x { max_x = x; } + if z < min_z { min_z = z; } + if z > max_z { max_z = z; } + } + } + } + + min_x = (min_x - 1).max(0); + min_z = (min_z - 1).max(0); + max_x = (max_x + 1).min(grid.width - 1); + max_z = (max_z + 1).min(grid.height - 1); + + for e in entries { + match e.edge { + ChunkEdge::West => min_x = 0, + ChunkEdge::East => max_x = grid.width - 1, + ChunkEdge::North => min_z = 0, + ChunkEdge::South => max_z = grid.height - 1, + } + } + + let tw = max_x - min_x + 1; + let th = max_z - min_z + 1; + let mut tight = RoomGrid::new(tw, th); + for z in 0..th { + for x in 0..tw { + tight.set(x, z, grid.get(x + min_x, z + min_z)); + } + } + + (tight, min_x, min_z) +} + fn compute_face_aabb(face: &mut Face, verts: &[Vertex], vert_idx: usize) { face.aabb_min = Vec3::new(1e30, 1e30, 1e30); face.aabb_max = Vec3::new(-1e30, -1e30, -1e30); @@ -161,9 +265,17 @@ fn build_bsp_node_grid(ctx: &mut BspBuildContext, x0: i32, y0: i32, x1: i32, y1: let plane_idx = ctx.plane_offset; ctx.plane_offset += 1; - if depth % 2 == 0 { + let split_x = if x1 - x0 <= 1 { + false + } else if y1 - y0 <= 1 { + true + } else { + depth % 2 == 0 + }; + + if split_x { let mid_x = (x0 + x1) / 2; - let split_pos = mid_x as f32 * CELL_SIZE; + let split_pos = ctx.origin_x + mid_x as f32 * CELL_SIZE; ctx.bsp.planes[plane_idx as usize] = Plane { normal: Vec3::new(1.0, 0.0, 0.0), @@ -181,7 +293,7 @@ fn build_bsp_node_grid(ctx: &mut BspBuildContext, x0: i32, y0: i32, x1: i32, y1: }; } else { let mid_y = (y0 + y1) / 2; - let split_pos = mid_y as f32 * CELL_SIZE; + let split_pos = ctx.origin_z + mid_y as f32 * CELL_SIZE; ctx.bsp.planes[plane_idx as usize] = Plane { normal: Vec3::new(0.0, 0.0, 1.0), @@ -226,7 +338,7 @@ fn build_bsp_node(ctx: &mut BspBuildContext, x0: i32, y0: i32, x1: i32, y1: i32, node_idx as i32 } -fn build_cell_portals(grid: &RoomGrid) -> Vec { +fn build_cell_portals(grid: &RoomGrid, origin_x: f32, origin_z: f32) -> Vec { let total_cells = (grid.width * grid.height) as usize; let mut portals = vec![CellPortals::default(); total_cells]; @@ -237,8 +349,8 @@ fn build_cell_portals(grid: &RoomGrid) -> Vec { } let c = (y * grid.width + x) as usize; - let cx = x as f32 * CELL_SIZE; - let cz = y as f32 * CELL_SIZE; + let cx = origin_x + x as f32 * CELL_SIZE; + let cz = origin_z + y as f32 * CELL_SIZE; if x > 0 && grid.get(x - 1, y) == 0 { let p = &mut portals[c]; @@ -291,6 +403,8 @@ fn build_cell_portals(grid: &RoomGrid) -> Vec { } } + + portals } @@ -471,6 +585,16 @@ fn compute_vertex_ao(bsp: &BspBuilder, pos: Vec3, normal: Vec3) -> f32 { continue; } + let cx = offset_pos.x.max(face.aabb_min.x).min(face.aabb_max.x); + let cy = offset_pos.y.max(face.aabb_min.y).min(face.aabb_max.y); + let cz = offset_pos.z.max(face.aabb_min.z).min(face.aabb_max.z); + let dx = offset_pos.x - cx; + let dy = offset_pos.y - cy; + let dz = offset_pos.z - cz; + if dx * dx + dy * dy + dz * dz > AO_RAY_LENGTH * AO_RAY_LENGTH { + continue; + } + let mut verts = [Vec3::new(0.0, 0.0, 0.0); 4]; let mut num_verts = 0usize; @@ -531,7 +655,7 @@ fn compute_vertex_light( } let light_dir = to_light.normalize(); - let ndotl = normal.dot(light_dir).max(0.0); + let ndotl = normal.dot(light_dir).abs(); let dist_sq = to_light.dot(to_light); let radius_sq = light.radius * light.radius; @@ -601,25 +725,184 @@ fn compute_bsp_vertex_lighting(bsp: &mut BspBuilder, lights: &[LightSource]) { bsp.vertex_lights[vert_idx] = (bsp.vertex_lights[vert_idx] & 0xFF00FFFF) | ((ao_byte as u32) << 16); } + + smooth_shared_vertex_lights(bsp); } -fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) { +fn smooth_shared_vertex_lights(bsp: &mut BspBuilder) { + let n = bsp.vertices.len(); + if n == 0 || bsp.vertex_lights.len() != n { + return; + } + + let mut order: Vec = (0..n).collect(); + order.sort_by(|&a, &b| { + let pa = &bsp.vertices[a].position; + let pb = &bsp.vertices[b].position; + let ax = (pa.x * 10.0) as i32; + let bx = (pb.x * 10.0) as i32; + let ay = (pa.y * 10.0) as i32; + let by = (pb.y * 10.0) as i32; + let az = (pa.z * 10.0) as i32; + let bz = (pb.z * 10.0) as i32; + ax.cmp(&bx).then(ay.cmp(&by)).then(az.cmp(&bz)) + }); + + let mut i = 0; + while i < n { + let mut j = i + 1; + let pi = &bsp.vertices[order[i]].position; + let ix = (pi.x * 10.0) as i32; + let iy = (pi.y * 10.0) as i32; + let iz = (pi.z * 10.0) as i32; + + while j < n { + let pj = &bsp.vertices[order[j]].position; + if (pj.x * 10.0) as i32 != ix || (pj.y * 10.0) as i32 != iy || (pj.z * 10.0) as i32 != iz { + break; + } + j += 1; + } + + if j - i > 1 { + let count = (j - i) as u32; + let mut sum_direct = 0u32; + let mut sum_ao = 0u32; + for k in i..j { + let l = bsp.vertex_lights[order[k]]; + sum_direct += (l >> 24) & 0xFF; + sum_ao += (l >> 16) & 0xFF; + } + let avg_direct = (sum_direct / count) as u8; + let avg_ao = (sum_ao / count) as u8; + for k in i..j { + let existing = bsp.vertex_lights[order[k]] & 0x0000FFFF; + bsp.vertex_lights[order[k]] = existing | ((avg_direct as u32) << 24) | ((avg_ao as u32) << 16); + } + } + + i = j; + } +} + +fn emit_quad( + bsp: &mut BspBuilder, + face_cell: &mut [u32], + vi: &mut usize, + fi: &mut usize, + ei: &mut usize, + p: [Vec3; 4], + normal: Vec3, + dist: f32, + material_id: u16, + cell_idx: u32, +) { + for i in 0..4 { + bsp.vertices[*vi + i].position = p[i]; + } + bsp.planes[*fi].normal = normal; + bsp.planes[*fi].dist = dist; + bsp.faces[*fi].plane_id = *fi as u16; + bsp.faces[*fi].num_edges = 4; + bsp.faces[*fi].first_edge = *ei as u32; + bsp.faces[*fi].material_id = material_id; + for i in 0..4 { + bsp.edges[*ei + i].vertex[0] = (*vi + i) as u16; + bsp.edges[*ei + i].vertex[1] = (*vi + ((i + 1) % 4)) as u16; + bsp.surfedges[*ei + i] = (*ei + i) as i32; + } + compute_face_aabb(&mut bsp.faces[*fi], &bsp.vertices, *vi); + face_cell[*fi] = cell_idx; + *vi += 4; + *ei += 4; + *fi += 1; +} + +fn is_entry_cell(entries: &[ChunkEntry], edge: ChunkEdge, cell: i32) -> bool { + entries.iter().any(|e| e.edge == edge && e.cell == cell) +} + +struct BspMaterials { + floor: u16, + wall: u16, + trim: u16, +} + +const DUNGEON_MATS: BspMaterials = BspMaterials { floor: 0, wall: 1, trim: 3 }; +const COURTYARD_MATS: BspMaterials = BspMaterials { floor: 4, wall: 5, trim: 6 }; + +fn is_adj_wall(grid: &RoomGrid, x: i32, y: i32) -> bool { + if x < 0 || x >= grid.width || y < 0 || y >= grid.height { return false; } + grid.get(x, y) == 1 && ( + grid.get(x - 1, y) == 0 || grid.get(x + 1, y) == 0 || + grid.get(x, y - 1) == 0 || grid.get(x, y + 1) == 0 + ) +} + +fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid, doorways: &[Doorway], entries: &[ChunkEntry], mats: &BspMaterials, origin_x: f32, origin_z: f32) { let mut wall_count = 0; let mut floor_ceiling_count = 0; + let mut doorway_extra = 0; + let mut partition_faces = 0; for y in 0..grid.height { for x in 0..grid.width { if grid.get(x, y) == 0 { - if grid.get(x - 1, y) == 1 { wall_count += 1; } - if grid.get(x + 1, y) == 1 { wall_count += 1; } - if grid.get(x, y - 1) == 1 { wall_count += 1; } - if grid.get(x, y + 1) == 1 { wall_count += 1; } + let fx = origin_x + x as f32 * CELL_SIZE; + let fy = origin_z + y as f32 * CELL_SIZE; + let left_solid = grid.get(x - 1, y) == 1 + && !(x == 0 && is_entry_cell(entries, ChunkEdge::West, y)); + let right_solid = grid.get(x + 1, y) == 1 + && !(x == grid.width - 1 && is_entry_cell(entries, ChunkEdge::East, y)); + let top_solid = grid.get(x, y - 1) == 1 + && !(y == 0 && is_entry_cell(entries, ChunkEdge::North, x)); + let bottom_solid = grid.get(x, y + 1) == 1 + && !(y == grid.height - 1 && is_entry_cell(entries, ChunkEdge::South, x)); + + if left_solid { + wall_count += 1; + if find_doorway(doorways, fx, fy, -1).is_some() { + doorway_extra += 3; + } + } else if find_doorway(doorways, fx, fy, -1).is_some() { + partition_faces += 5; + } + if right_solid { + wall_count += 1; + if find_doorway(doorways, fx + CELL_SIZE, fy, 1).is_some() { + doorway_extra += 3; + } + } + if top_solid { wall_count += 1; } + if bottom_solid { wall_count += 1; } floor_ceiling_count += 1; } } } - let face_count = wall_count * 2 + floor_ceiling_count; + let mut filler_floor_count = 0; + let mut ext_wall_count = 0; + let mut wall_top_count = 0; + for y in 0..grid.height { + for x in 0..grid.width { + if grid.get(x, y) == 1 { + let adj_room = grid.get(x-1,y)==0 || grid.get(x+1,y)==0 + || grid.get(x,y-1)==0 || grid.get(x,y+1)==0; + if adj_room { + wall_top_count += 1; + if grid.get(x-1,y) != 0 && !is_adj_wall(grid, x-1, y) { ext_wall_count += 1; } + if grid.get(x+1,y) != 0 && !is_adj_wall(grid, x+1, y) { ext_wall_count += 1; } + if grid.get(x,y-1) != 0 && !is_adj_wall(grid, x, y-1) { ext_wall_count += 1; } + if grid.get(x,y+1) != 0 && !is_adj_wall(grid, x, y+1) { ext_wall_count += 1; } + } else { + filler_floor_count += 1; + } + } + } + } + + let face_count = wall_count * 2 + floor_ceiling_count + doorway_extra + partition_faces + + filler_floor_count + ext_wall_count * 2 + wall_top_count; let vertex_count = face_count * 4; let total_cells = (grid.width * grid.height) as usize; @@ -642,119 +925,118 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) { for y in 0..grid.height { for x in 0..grid.width { if grid.get(x, y) == 0 { - let fx = x as f32 * CELL_SIZE; - let fy = y as f32 * CELL_SIZE; + let fx = origin_x + x as f32 * CELL_SIZE; + let fy = origin_z + y as f32 * CELL_SIZE; let cell_idx = (y * grid.width + x) as u32; - if grid.get(x - 1, y) == 1 { - bsp.vertices[vert_idx + 0].position = Vec3::new(fx, TRIM_HEIGHT, fy); - bsp.vertices[vert_idx + 1].position = Vec3::new(fx, WALL_HEIGHT, fy); - bsp.vertices[vert_idx + 2].position = Vec3::new(fx, WALL_HEIGHT, fy + CELL_SIZE); - bsp.vertices[vert_idx + 3].position = Vec3::new(fx, TRIM_HEIGHT, fy + CELL_SIZE); + let left_solid = grid.get(x - 1, y) == 1 + && !(x == 0 && is_entry_cell(entries, ChunkEdge::West, y)); + let right_solid = grid.get(x + 1, y) == 1 + && !(x == grid.width - 1 && is_entry_cell(entries, ChunkEdge::East, y)); + let top_solid = grid.get(x, y - 1) == 1 + && !(y == 0 && is_entry_cell(entries, ChunkEdge::North, x)); + let bottom_solid = grid.get(x, y + 1) == 1 + && !(y == grid.height - 1 && is_entry_cell(entries, ChunkEdge::South, x)); - bsp.planes[face_idx].normal = Vec3::new(1.0, 0.0, 0.0); - bsp.planes[face_idx].dist = fx; + if left_solid { + let normal = Vec3::new(1.0, 0.0, 0.0); + let dist = fx; - bsp.faces[face_idx].plane_id = face_idx as u16; - bsp.faces[face_idx].num_edges = 4; - bsp.faces[face_idx].first_edge = edge_idx as u32; - bsp.faces[face_idx].material_id = 1; - - for i in 0..4 { - bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; - bsp.edges[edge_idx + i].vertex[1] = (vert_idx + ((i + 1) % 4)) as u16; - bsp.surfedges[edge_idx + i] = (edge_idx + i) as i32; + if let Some(door) = find_doorway(doorways, fx, fy, -1) { + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, door.height, fy), Vec3::new(fx, WALL_HEIGHT, fy), + Vec3::new(fx, WALL_HEIGHT, fy + CELL_SIZE), Vec3::new(fx, door.height, fy + CELL_SIZE)], + normal, dist, mats.wall, cell_idx); + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, TRIM_HEIGHT, fy), Vec3::new(fx, door.height, fy), + Vec3::new(fx, door.height, door.z_start), Vec3::new(fx, TRIM_HEIGHT, door.z_start)], + normal, dist, mats.wall, cell_idx); + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, TRIM_HEIGHT, door.z_end), Vec3::new(fx, door.height, door.z_end), + Vec3::new(fx, door.height, fy + CELL_SIZE), Vec3::new(fx, TRIM_HEIGHT, fy + CELL_SIZE)], + normal, dist, mats.wall, cell_idx); + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, 0.0, fy), Vec3::new(fx, TRIM_HEIGHT, fy), + Vec3::new(fx, TRIM_HEIGHT, door.z_start), Vec3::new(fx, 0.0, door.z_start)], + normal, dist, mats.trim, cell_idx); + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, 0.0, door.z_end), Vec3::new(fx, TRIM_HEIGHT, door.z_end), + Vec3::new(fx, TRIM_HEIGHT, fy + CELL_SIZE), Vec3::new(fx, 0.0, fy + CELL_SIZE)], + normal, dist, mats.trim, cell_idx); + } else { + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, TRIM_HEIGHT, fy), Vec3::new(fx, WALL_HEIGHT, fy), + Vec3::new(fx, WALL_HEIGHT, fy + CELL_SIZE), Vec3::new(fx, TRIM_HEIGHT, fy + CELL_SIZE)], + normal, dist, mats.wall, cell_idx); + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, 0.0, fy), Vec3::new(fx, TRIM_HEIGHT, fy), + Vec3::new(fx, TRIM_HEIGHT, fy + CELL_SIZE), Vec3::new(fx, 0.0, fy + CELL_SIZE)], + normal, dist, mats.trim, cell_idx); } - - compute_face_aabb(&mut bsp.faces[face_idx], &bsp.vertices, vert_idx); - face_cell[face_idx] = cell_idx; - - vert_idx += 4; - edge_idx += 4; - face_idx += 1; - - bsp.vertices[vert_idx + 0].position = Vec3::new(fx, 0.0, fy); - bsp.vertices[vert_idx + 1].position = Vec3::new(fx, TRIM_HEIGHT, fy); - bsp.vertices[vert_idx + 2].position = Vec3::new(fx, TRIM_HEIGHT, fy + CELL_SIZE); - bsp.vertices[vert_idx + 3].position = Vec3::new(fx, 0.0, fy + CELL_SIZE); - - bsp.planes[face_idx].normal = Vec3::new(1.0, 0.0, 0.0); - bsp.planes[face_idx].dist = fx; - - bsp.faces[face_idx].plane_id = face_idx as u16; - bsp.faces[face_idx].num_edges = 4; - bsp.faces[face_idx].first_edge = edge_idx as u32; - bsp.faces[face_idx].material_id = 3; - - for i in 0..4 { - bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; - bsp.edges[edge_idx + i].vertex[1] = (vert_idx + ((i + 1) % 4)) as u16; - bsp.surfedges[edge_idx + i] = (edge_idx + i) as i32; - } - - compute_face_aabb(&mut bsp.faces[face_idx], &bsp.vertices, vert_idx); - face_cell[face_idx] = cell_idx; - - vert_idx += 4; - edge_idx += 4; - face_idx += 1; + } else if let Some(door) = find_doorway(doorways, fx, fy, -1) { + let normal = Vec3::new(1.0, 0.0, 0.0); + let dist = fx; + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, door.height, fy), Vec3::new(fx, WALL_HEIGHT, fy), + Vec3::new(fx, WALL_HEIGHT, fy + CELL_SIZE), Vec3::new(fx, door.height, fy + CELL_SIZE)], + normal, dist, mats.wall, cell_idx); + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, TRIM_HEIGHT, fy), Vec3::new(fx, door.height, fy), + Vec3::new(fx, door.height, door.z_start), Vec3::new(fx, TRIM_HEIGHT, door.z_start)], + normal, dist, mats.wall, cell_idx); + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, TRIM_HEIGHT, door.z_end), Vec3::new(fx, door.height, door.z_end), + Vec3::new(fx, door.height, fy + CELL_SIZE), Vec3::new(fx, TRIM_HEIGHT, fy + CELL_SIZE)], + normal, dist, mats.wall, cell_idx); + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, 0.0, fy), Vec3::new(fx, TRIM_HEIGHT, fy), + Vec3::new(fx, TRIM_HEIGHT, door.z_start), Vec3::new(fx, 0.0, door.z_start)], + normal, dist, mats.trim, cell_idx); + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, 0.0, door.z_end), Vec3::new(fx, TRIM_HEIGHT, door.z_end), + Vec3::new(fx, TRIM_HEIGHT, fy + CELL_SIZE), Vec3::new(fx, 0.0, fy + CELL_SIZE)], + normal, dist, mats.trim, cell_idx); } - if grid.get(x + 1, y) == 1 { - bsp.vertices[vert_idx + 0].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy); - bsp.vertices[vert_idx + 1].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy + CELL_SIZE); - bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fy + CELL_SIZE); - bsp.vertices[vert_idx + 3].position = Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fy); + if right_solid { + let wx = fx + CELL_SIZE; + let normal = Vec3::new(-1.0, 0.0, 0.0); + let dist = -wx; - bsp.planes[face_idx].normal = Vec3::new(-1.0, 0.0, 0.0); - bsp.planes[face_idx].dist = -(fx + CELL_SIZE); - - bsp.faces[face_idx].plane_id = face_idx as u16; - bsp.faces[face_idx].num_edges = 4; - bsp.faces[face_idx].first_edge = edge_idx as u32; - bsp.faces[face_idx].material_id = 1; - - for i in 0..4 { - bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; - bsp.edges[edge_idx + i].vertex[1] = (vert_idx + ((i + 1) % 4)) as u16; - bsp.surfedges[edge_idx + i] = (edge_idx + i) as i32; + if let Some(door) = find_doorway(doorways, wx, fy, 1) { + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(wx, door.height, fy), Vec3::new(wx, door.height, fy + CELL_SIZE), + Vec3::new(wx, WALL_HEIGHT, fy + CELL_SIZE), Vec3::new(wx, WALL_HEIGHT, fy)], + normal, dist, mats.wall, cell_idx); + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(wx, TRIM_HEIGHT, fy), Vec3::new(wx, TRIM_HEIGHT, door.z_start), + Vec3::new(wx, door.height, door.z_start), Vec3::new(wx, door.height, fy)], + normal, dist, mats.wall, cell_idx); + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(wx, TRIM_HEIGHT, door.z_end), Vec3::new(wx, TRIM_HEIGHT, fy + CELL_SIZE), + Vec3::new(wx, door.height, fy + CELL_SIZE), Vec3::new(wx, door.height, door.z_end)], + normal, dist, mats.wall, cell_idx); + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(wx, 0.0, fy), Vec3::new(wx, 0.0, door.z_start), + Vec3::new(wx, TRIM_HEIGHT, door.z_start), Vec3::new(wx, TRIM_HEIGHT, fy)], + normal, dist, mats.trim, cell_idx); + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(wx, 0.0, door.z_end), Vec3::new(wx, 0.0, fy + CELL_SIZE), + Vec3::new(wx, TRIM_HEIGHT, fy + CELL_SIZE), Vec3::new(wx, TRIM_HEIGHT, door.z_end)], + normal, dist, mats.trim, cell_idx); + } else { + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(wx, TRIM_HEIGHT, fy), Vec3::new(wx, TRIM_HEIGHT, fy + CELL_SIZE), + Vec3::new(wx, WALL_HEIGHT, fy + CELL_SIZE), Vec3::new(wx, WALL_HEIGHT, fy)], + normal, dist, mats.wall, cell_idx); + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(wx, 0.0, fy), Vec3::new(wx, 0.0, fy + CELL_SIZE), + Vec3::new(wx, TRIM_HEIGHT, fy + CELL_SIZE), Vec3::new(wx, TRIM_HEIGHT, fy)], + normal, dist, mats.trim, cell_idx); } - - compute_face_aabb(&mut bsp.faces[face_idx], &bsp.vertices, vert_idx); - face_cell[face_idx] = cell_idx; - - vert_idx += 4; - edge_idx += 4; - face_idx += 1; - - bsp.vertices[vert_idx + 0].position = Vec3::new(fx + CELL_SIZE, 0.0, fy); - bsp.vertices[vert_idx + 1].position = Vec3::new(fx + CELL_SIZE, 0.0, fy + CELL_SIZE); - bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy + CELL_SIZE); - bsp.vertices[vert_idx + 3].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy); - - bsp.planes[face_idx].normal = Vec3::new(-1.0, 0.0, 0.0); - bsp.planes[face_idx].dist = -(fx + CELL_SIZE); - - bsp.faces[face_idx].plane_id = face_idx as u16; - bsp.faces[face_idx].num_edges = 4; - bsp.faces[face_idx].first_edge = edge_idx as u32; - bsp.faces[face_idx].material_id = 3; - - for i in 0..4 { - bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; - bsp.edges[edge_idx + i].vertex[1] = (vert_idx + ((i + 1) % 4)) as u16; - bsp.surfedges[edge_idx + i] = (edge_idx + i) as i32; - } - - compute_face_aabb(&mut bsp.faces[face_idx], &bsp.vertices, vert_idx); - face_cell[face_idx] = cell_idx; - - vert_idx += 4; - edge_idx += 4; - face_idx += 1; } - if grid.get(x, y - 1) == 1 { + if top_solid { bsp.vertices[vert_idx + 0].position = Vec3::new(fx, TRIM_HEIGHT, fy); bsp.vertices[vert_idx + 1].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy); bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fy); @@ -766,7 +1048,7 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) { bsp.faces[face_idx].plane_id = face_idx as u16; bsp.faces[face_idx].num_edges = 4; bsp.faces[face_idx].first_edge = edge_idx as u32; - bsp.faces[face_idx].material_id = 1; + bsp.faces[face_idx].material_id = mats.wall; for i in 0..4 { bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; @@ -792,7 +1074,7 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) { bsp.faces[face_idx].plane_id = face_idx as u16; bsp.faces[face_idx].num_edges = 4; bsp.faces[face_idx].first_edge = edge_idx as u32; - bsp.faces[face_idx].material_id = 3; + bsp.faces[face_idx].material_id = mats.trim; for i in 0..4 { bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; @@ -808,7 +1090,7 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) { face_idx += 1; } - if grid.get(x, y + 1) == 1 { + if bottom_solid { bsp.vertices[vert_idx + 0].position = Vec3::new(fx, TRIM_HEIGHT, fy + CELL_SIZE); bsp.vertices[vert_idx + 1].position = Vec3::new(fx, WALL_HEIGHT, fy + CELL_SIZE); bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fy + CELL_SIZE); @@ -820,7 +1102,7 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) { bsp.faces[face_idx].plane_id = face_idx as u16; bsp.faces[face_idx].num_edges = 4; bsp.faces[face_idx].first_edge = edge_idx as u32; - bsp.faces[face_idx].material_id = 1; + bsp.faces[face_idx].material_id = mats.wall; for i in 0..4 { bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; @@ -846,7 +1128,7 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) { bsp.faces[face_idx].plane_id = face_idx as u16; bsp.faces[face_idx].num_edges = 4; bsp.faces[face_idx].first_edge = edge_idx as u32; - bsp.faces[face_idx].material_id = 3; + bsp.faces[face_idx].material_id = mats.trim; for i in 0..4 { bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; @@ -868,8 +1150,8 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) { for y in 0..grid.height { for x in 0..grid.width { if grid.get(x, y) == 0 { - let fx = x as f32 * CELL_SIZE; - let fy = y as f32 * CELL_SIZE; + let fx = origin_x + x as f32 * CELL_SIZE; + let fy = origin_z + y as f32 * CELL_SIZE; let cell_idx = (y * grid.width + x) as u32; bsp.vertices[vert_idx + 0].position = Vec3::new(fx, 0.0, fy); @@ -883,7 +1165,7 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) { bsp.faces[face_idx].plane_id = face_idx as u16; bsp.faces[face_idx].num_edges = 4; bsp.faces[face_idx].first_edge = edge_idx as u32; - bsp.faces[face_idx].material_id = 0; + bsp.faces[face_idx].material_id = mats.floor; for i in 0..4 { bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; @@ -901,6 +1183,83 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) { } } + let up = Vec3::new(0.0, 1.0, 0.0); + for y in 0..grid.height { + for x in 0..grid.width { + if grid.get(x, y) == 1 { + let fx = origin_x + x as f32 * CELL_SIZE; + let fz = origin_z + y as f32 * CELL_SIZE; + let cell_idx = (y * grid.width + x) as u32; + + let adj_room = grid.get(x-1,y)==0 || grid.get(x+1,y)==0 + || grid.get(x,y-1)==0 || grid.get(x,y+1)==0; + if adj_room { + let room_ci = if grid.get(x-1,y)==0 { ((y)*grid.width+(x-1)) as u32 } + else if grid.get(x+1,y)==0 { ((y)*grid.width+(x+1)) as u32 } + else if grid.get(x,y-1)==0 { ((y-1)*grid.width+(x)) as u32 } + else { ((y+1)*grid.width+(x)) as u32 }; + + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, WALL_HEIGHT, fz), Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fz), + Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fz + CELL_SIZE), Vec3::new(fx, WALL_HEIGHT, fz + CELL_SIZE)], + up, WALL_HEIGHT, mats.wall, room_ci); + + if grid.get(x-1,y) != 0 && !is_adj_wall(grid, x-1, y) { + let n = Vec3::new(-1.0, 0.0, 0.0); + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, TRIM_HEIGHT, fz + CELL_SIZE), Vec3::new(fx, TRIM_HEIGHT, fz), + Vec3::new(fx, WALL_HEIGHT, fz), Vec3::new(fx, WALL_HEIGHT, fz + CELL_SIZE)], + n, -fx, mats.wall, room_ci); + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, 0.0, fz + CELL_SIZE), Vec3::new(fx, 0.0, fz), + Vec3::new(fx, TRIM_HEIGHT, fz), Vec3::new(fx, TRIM_HEIGHT, fz + CELL_SIZE)], + n, -fx, mats.trim, room_ci); + } + if grid.get(x+1,y) != 0 && !is_adj_wall(grid, x+1, y) { + let wx = fx + CELL_SIZE; + let n = Vec3::new(1.0, 0.0, 0.0); + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(wx, TRIM_HEIGHT, fz), Vec3::new(wx, TRIM_HEIGHT, fz + CELL_SIZE), + Vec3::new(wx, WALL_HEIGHT, fz + CELL_SIZE), Vec3::new(wx, WALL_HEIGHT, fz)], + n, wx, mats.wall, room_ci); + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(wx, 0.0, fz), Vec3::new(wx, 0.0, fz + CELL_SIZE), + Vec3::new(wx, TRIM_HEIGHT, fz + CELL_SIZE), Vec3::new(wx, TRIM_HEIGHT, fz)], + n, wx, mats.trim, room_ci); + } + if grid.get(x,y-1) != 0 && !is_adj_wall(grid, x, y-1) { + let n = Vec3::new(0.0, 0.0, -1.0); + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fz), Vec3::new(fx, TRIM_HEIGHT, fz), + Vec3::new(fx, WALL_HEIGHT, fz), Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fz)], + n, -fz, mats.wall, room_ci); + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx + CELL_SIZE, 0.0, fz), Vec3::new(fx, 0.0, fz), + Vec3::new(fx, TRIM_HEIGHT, fz), Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fz)], + n, -fz, mats.trim, room_ci); + } + if grid.get(x,y+1) != 0 && !is_adj_wall(grid, x, y+1) { + let wz = fz + CELL_SIZE; + let n = Vec3::new(0.0, 0.0, 1.0); + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, TRIM_HEIGHT, wz), Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, wz), + Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, wz), Vec3::new(fx, WALL_HEIGHT, wz)], + n, wz, mats.wall, room_ci); + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, 0.0, wz), Vec3::new(fx + CELL_SIZE, 0.0, wz), + Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, wz), Vec3::new(fx, TRIM_HEIGHT, wz)], + n, wz, mats.trim, room_ci); + } + } else { + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, 0.0, fz), Vec3::new(fx, 0.0, fz + CELL_SIZE), + Vec3::new(fx + CELL_SIZE, 0.0, fz + CELL_SIZE), Vec3::new(fx + CELL_SIZE, 0.0, fz)], + up, 0.0, 7, cell_idx); + } + } + } + } + bsp.vertices.truncate(vert_idx); bsp.faces.truncate(face_idx); bsp.edges.truncate(edge_idx); @@ -934,8 +1293,8 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) { let c = (y * grid.width + x) as usize; let leaf = &mut bsp.leafs[c]; - let fx = x as f32 * CELL_SIZE; - let fz = y as f32 * CELL_SIZE; + let fx = origin_x + x as f32 * CELL_SIZE; + let fz = origin_z + y as f32 * CELL_SIZE; leaf.mins[0] = fx as i16; leaf.mins[1] = 0; @@ -946,13 +1305,16 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) { if grid.get(x, y) == 0 { leaf.contents = -2; - leaf.first_marksurface = cell_offset[c] as u16; - leaf.num_marksurfaces = faces_per_cell[c] as u16; } else { - leaf.contents = -1; - leaf.first_marksurface = 0; - leaf.num_marksurfaces = 0; + let adjacent_to_room = + grid.get(x - 1, y) == 0 || + grid.get(x + 1, y) == 0 || + grid.get(x, y - 1) == 0 || + grid.get(x, y + 1) == 0; + leaf.contents = if adjacent_to_room { -1 } else { 0 }; } + leaf.first_marksurface = cell_offset[c] as u16; + leaf.num_marksurfaces = faces_per_cell[c] as u16; leaf.visofs = -1; } } @@ -974,6 +1336,8 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) { grid, node_count: 0, plane_offset: face_count_final as u32, + origin_x, + origin_z, }; build_bsp_node(&mut ctx, 0, 0, grid.width, grid.height, 0, floor_leaf_idx); @@ -983,12 +1347,439 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) { ctx.bsp.nodes.truncate(node_count); ctx.bsp.planes.truncate(plane_count); - let portals = build_cell_portals(grid); + let portals = build_cell_portals(grid, origin_x, origin_z); build_pvs_data(ctx.bsp, &portals); ctx.bsp.cell_portals = portals; } -pub fn generate_rooms(params: &ProcgenParams) -> Bsp { +fn edge_blend(wx: f32, wz: f32, world_seed: u64) -> f32 { + let chunk_size = 16.0 * CELL_SIZE; + let zone = 3.0 * CELL_SIZE; + let vcx = libm::floorf(wx / chunk_size) as i32; + let vcz = libm::floorf(wz / chunk_size) as i32; + let mut blend = 1.0f32; + + for dz in -1..=1_i32 { + for dx in -1..=1_i32 { + let ncx = vcx + dx; + let ncz = vcz + dz; + if select_biome(ncx, ncz, world_seed) != Biome::Exterior { + let co = ncx as f32 * chunk_size; + let ce = co + chunk_size; + let coz = ncz as f32 * chunk_size; + let cez = coz + chunk_size; + let dist_x = if wx < co { co - wx } else if wx > ce { wx - ce } else { 0.0 }; + let dist_z = if wz < coz { coz - wz } else if wz > cez { wz - cez } else { 0.0 }; + let dist = if dist_x > dist_z { dist_x } else { dist_z }; + if dist < zone { + let t = dist / zone; + blend = blend.min(t * t * (3.0 - 2.0 * t)); + } + } + } + } + + blend +} + +fn grid_to_bsp_exterior(bsp: &mut BspBuilder, grid: &RoomGrid, origin_x: f32, origin_z: f32, world_seed: u64) { + let total_cells = (grid.width * grid.height) as usize; + let chunk_size = 16.0 * CELL_SIZE; + let cx = libm::floorf(origin_x / chunk_size) as i32; + let cz = libm::floorf(origin_z / chunk_size) as i32; + + let border_north = select_biome(cx, cz - 1, world_seed) != Biome::Exterior; + let border_south = select_biome(cx, cz + 1, world_seed) != Biome::Exterior; + let border_west = select_biome(cx - 1, cz, world_seed) != Biome::Exterior; + let border_east = select_biome(cx + 1, cz, world_seed) != Biome::Exterior; + + let vw = (grid.width + 1) as usize; + let vh = (grid.height + 1) as usize; + let mut corner_heights = vec![0.0f32; vw * vh]; + for vz in 0..vh as i32 { + for vx in 0..vw as i32 { + let wx = origin_x + vx as f32 * CELL_SIZE; + let wz = origin_z + vz as f32 * CELL_SIZE; + let h = unsafe { + crate::pxl8::pxl8_fbm(wx * 0.002, wz * 0.002, world_seed + 5000, 4) + }; + let blend = edge_blend(wx, wz, world_seed); + corner_heights[vz as usize * vw + vx as usize] = h * 128.0 * blend; + } + } + + let mut border_face_count = 0i32; + if border_north { border_face_count += grid.width; } + if border_south { border_face_count += grid.width; } + if border_west { border_face_count += grid.height; } + if border_east { border_face_count += grid.height; } + if border_north && border_west { border_face_count += 1; } + if border_north && border_east { border_face_count += 1; } + if border_south && border_west { border_face_count += 1; } + if border_south && border_east { border_face_count += 1; } + + let bg_north = if border_north { generate_building_grid(cx, cz - 1, world_seed) } else { None }; + let bg_south = if border_south { generate_building_grid(cx, cz + 1, world_seed) } else { None }; + let bg_west = if border_west { generate_building_grid(cx - 1, cz, world_seed) } else { None }; + let bg_east = if border_east { generate_building_grid(cx + 1, cz, world_seed) } else { None }; + + let mut building_face_count = 0usize; + for bg in [&bg_north, &bg_south, &bg_west, &bg_east] { + if let Some(g) = bg { + for y in 0..g.height { + for x in 0..g.width { + if g.get(x, y) != 0 && !is_adj_wall(g, x, y) { + building_face_count += 1; + } + } + } + } + } + + let mut face_count = 0; + for y in 0..grid.height { + for x in 0..grid.width { + if grid.get(x, y) == 0 { + face_count += 1; + } + } + } + face_count += border_face_count as usize + building_face_count; + + let vertex_count = face_count * 4; + let max_nodes = 2 * total_cells + 1; + let total_planes = face_count + max_nodes + 1; + + bsp.vertices = vec![Vertex::default(); vertex_count]; + bsp.faces = vec![Face::default(); face_count]; + bsp.planes = vec![Plane::default(); total_planes]; + bsp.edges = vec![Edge::default(); vertex_count]; + bsp.surfedges = vec![0i32; vertex_count]; + bsp.nodes = vec![Node::default(); max_nodes]; + + let mut face_cell = vec![0u32; face_count]; + let mut vert_idx = 0usize; + let mut face_idx = 0usize; + let mut edge_idx = 0usize; + + for y in 0..grid.height { + for x in 0..grid.width { + if grid.get(x, y) == 0 { + let fx = origin_x + x as f32 * CELL_SIZE; + let fz = origin_z + y as f32 * CELL_SIZE; + let cell_idx = (y * grid.width + x) as u32; + + let h00 = corner_heights[y as usize * vw + x as usize]; + let h10 = corner_heights[y as usize * vw + x as usize + 1]; + let h01 = corner_heights[(y as usize + 1) * vw + x as usize]; + let h11 = corner_heights[(y as usize + 1) * vw + x as usize + 1]; + + let nx = -(h10 - h00); + let ny = CELL_SIZE; + let nz = -(h01 - h00); + let len = sqrtf(nx * nx + ny * ny + nz * nz); + let normal = Vec3::new(nx / len, ny / len, nz / len); + let dist = normal.x * fx + normal.y * h00 + normal.z * fz; + + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, h00, fz), Vec3::new(fx + CELL_SIZE, h10, fz), + Vec3::new(fx + CELL_SIZE, h11, fz + CELL_SIZE), Vec3::new(fx, h01, fz + CELL_SIZE)], + normal, dist, 7, cell_idx); + } + } + } + + let up = Vec3::new(0.0, 1.0, 0.0); + if border_north { + for x in 0..grid.width { + let fx = origin_x + x as f32 * CELL_SIZE; + let fz_outer = origin_z - CELL_SIZE; + let fz_inner = origin_z; + let h0 = corner_heights[0 * vw + x as usize]; + let h1 = corner_heights[0 * vw + x as usize + 1]; + let cell_idx = (0 * grid.width + x) as u32; + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, 0.0, fz_outer), Vec3::new(fx + CELL_SIZE, 0.0, fz_outer), + Vec3::new(fx + CELL_SIZE, h1, fz_inner), Vec3::new(fx, h0, fz_inner)], + up, 0.0, 7, cell_idx); + } + } + if border_south { + let last_row = grid.height as usize; + for x in 0..grid.width { + let fx = origin_x + x as f32 * CELL_SIZE; + let fz_inner = origin_z + grid.height as f32 * CELL_SIZE; + let fz_outer = fz_inner + CELL_SIZE; + let h0 = corner_heights[last_row * vw + x as usize]; + let h1 = corner_heights[last_row * vw + x as usize + 1]; + let cell_idx = ((grid.height - 1) * grid.width + x) as u32; + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, h0, fz_inner), Vec3::new(fx + CELL_SIZE, h1, fz_inner), + Vec3::new(fx + CELL_SIZE, 0.0, fz_outer), Vec3::new(fx, 0.0, fz_outer)], + up, 0.0, 7, cell_idx); + } + } + if border_west { + for y in 0..grid.height { + let fx_outer = origin_x - CELL_SIZE; + let fx_inner = origin_x; + let fz = origin_z + y as f32 * CELL_SIZE; + let h0 = corner_heights[y as usize * vw + 0]; + let h1 = corner_heights[(y as usize + 1) * vw + 0]; + let cell_idx = (y * grid.width + 0) as u32; + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx_outer, 0.0, fz), Vec3::new(fx_inner, h0, fz), + Vec3::new(fx_inner, h1, fz + CELL_SIZE), Vec3::new(fx_outer, 0.0, fz + CELL_SIZE)], + up, 0.0, 7, cell_idx); + } + } + if border_east { + let last_col = grid.width as usize; + for y in 0..grid.height { + let fx_inner = origin_x + grid.width as f32 * CELL_SIZE; + let fx_outer = fx_inner + CELL_SIZE; + let fz = origin_z + y as f32 * CELL_SIZE; + let h0 = corner_heights[y as usize * vw + last_col]; + let h1 = corner_heights[(y as usize + 1) * vw + last_col]; + let cell_idx = (y * grid.width + grid.width - 1) as u32; + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx_inner, h0, fz), Vec3::new(fx_outer, 0.0, fz), + Vec3::new(fx_outer, 0.0, fz + CELL_SIZE), Vec3::new(fx_inner, h1, fz + CELL_SIZE)], + up, 0.0, 7, cell_idx); + } + } + + if border_north && border_west { + let fx_outer = origin_x - CELL_SIZE; + let fx_inner = origin_x; + let fz_outer = origin_z - CELL_SIZE; + let fz_inner = origin_z; + let h = corner_heights[0]; + let cell_idx = 0u32; + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx_outer, 0.0, fz_outer), Vec3::new(fx_inner, 0.0, fz_outer), + Vec3::new(fx_inner, h, fz_inner), Vec3::new(fx_outer, 0.0, fz_inner)], + up, 0.0, 7, cell_idx); + } + if border_north && border_east { + let last_col = grid.width as usize; + let fx_inner = origin_x + grid.width as f32 * CELL_SIZE; + let fx_outer = fx_inner + CELL_SIZE; + let fz_outer = origin_z - CELL_SIZE; + let fz_inner = origin_z; + let h = corner_heights[last_col]; + let cell_idx = (grid.width - 1) as u32; + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx_inner, 0.0, fz_outer), Vec3::new(fx_outer, 0.0, fz_outer), + Vec3::new(fx_outer, 0.0, fz_inner), Vec3::new(fx_inner, h, fz_inner)], + up, 0.0, 7, cell_idx); + } + if border_south && border_west { + let last_row = grid.height as usize; + let fx_outer = origin_x - CELL_SIZE; + let fx_inner = origin_x; + let fz_inner = origin_z + grid.height as f32 * CELL_SIZE; + let fz_outer = fz_inner + CELL_SIZE; + let h = corner_heights[last_row * vw]; + let cell_idx = ((grid.height - 1) * grid.width) as u32; + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx_outer, 0.0, fz_inner), Vec3::new(fx_inner, h, fz_inner), + Vec3::new(fx_inner, 0.0, fz_outer), Vec3::new(fx_outer, 0.0, fz_outer)], + up, 0.0, 7, cell_idx); + } + if border_south && border_east { + let last_row = grid.height as usize; + let last_col = grid.width as usize; + let fx_inner = origin_x + grid.width as f32 * CELL_SIZE; + let fx_outer = fx_inner + CELL_SIZE; + let fz_inner = origin_z + grid.height as f32 * CELL_SIZE; + let fz_outer = fz_inner + CELL_SIZE; + let h = corner_heights[last_row * vw + last_col]; + let cell_idx = ((grid.height - 1) * grid.width + grid.width - 1) as u32; + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx_inner, h, fz_inner), Vec3::new(fx_outer, 0.0, fz_inner), + Vec3::new(fx_outer, 0.0, fz_outer), Vec3::new(fx_inner, 0.0, fz_outer)], + up, 0.0, 7, cell_idx); + } + + if let Some(ref bg) = bg_north { + let bg_oz = (cz - 1) as f32 * chunk_size; + for by in 0..bg.height { + for bx in 0..bg.width { + if bg.get(bx, by) != 0 && !is_adj_wall(bg, bx, by) { + let fx = origin_x + bx as f32 * CELL_SIZE; + let fz = bg_oz + by as f32 * CELL_SIZE; + let cell_idx = bx as u32; + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, 0.0, fz), Vec3::new(fx + CELL_SIZE, 0.0, fz), + Vec3::new(fx + CELL_SIZE, 0.0, fz + CELL_SIZE), Vec3::new(fx, 0.0, fz + CELL_SIZE)], + up, 0.0, 7, cell_idx); + } + } + } + } + if let Some(ref bg) = bg_south { + let bg_oz = (cz + 1) as f32 * chunk_size; + for by in 0..bg.height { + for bx in 0..bg.width { + if bg.get(bx, by) != 0 && !is_adj_wall(bg, bx, by) { + let fx = origin_x + bx as f32 * CELL_SIZE; + let fz = bg_oz + by as f32 * CELL_SIZE; + let cell_idx = ((grid.height - 1) * grid.width + bx) as u32; + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, 0.0, fz), Vec3::new(fx + CELL_SIZE, 0.0, fz), + Vec3::new(fx + CELL_SIZE, 0.0, fz + CELL_SIZE), Vec3::new(fx, 0.0, fz + CELL_SIZE)], + up, 0.0, 7, cell_idx); + } + } + } + } + if let Some(ref bg) = bg_west { + let bg_ox = (cx - 1) as f32 * chunk_size; + for by in 0..bg.height { + for bx in 0..bg.width { + if bg.get(bx, by) != 0 && !is_adj_wall(bg, bx, by) { + let fx = bg_ox + bx as f32 * CELL_SIZE; + let fz = origin_z + by as f32 * CELL_SIZE; + let cell_idx = (by * grid.width) as u32; + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, 0.0, fz), Vec3::new(fx + CELL_SIZE, 0.0, fz), + Vec3::new(fx + CELL_SIZE, 0.0, fz + CELL_SIZE), Vec3::new(fx, 0.0, fz + CELL_SIZE)], + up, 0.0, 7, cell_idx); + } + } + } + } + if let Some(ref bg) = bg_east { + let bg_ox = (cx + 1) as f32 * chunk_size; + for by in 0..bg.height { + for bx in 0..bg.width { + if bg.get(bx, by) != 0 && !is_adj_wall(bg, bx, by) { + let fx = bg_ox + bx as f32 * CELL_SIZE; + let fz = origin_z + by as f32 * CELL_SIZE; + let cell_idx = (by * grid.width + grid.width - 1) as u32; + emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, + [Vec3::new(fx, 0.0, fz), Vec3::new(fx + CELL_SIZE, 0.0, fz), + Vec3::new(fx + CELL_SIZE, 0.0, fz + CELL_SIZE), Vec3::new(fx, 0.0, fz + CELL_SIZE)], + up, 0.0, 7, cell_idx); + } + } + } + } + + bsp.vertices.truncate(vert_idx); + bsp.faces.truncate(face_idx); + bsp.edges.truncate(edge_idx); + bsp.surfedges.truncate(edge_idx); + + for f in &mut bsp.faces[..face_idx] { + compute_face_aabb(f, &bsp.vertices, f.first_edge as usize); + } + + bsp.leafs = vec![Leaf::default(); total_cells]; + bsp.marksurfaces = Vec::new(); + + let mut ms_idx = 0u16; + for y in 0..grid.height { + for x in 0..grid.width { + let c = (y * grid.width + x) as usize; + let leaf = &mut bsp.leafs[c]; + + let fx = origin_x + x as f32 * CELL_SIZE; + let fz = origin_z + y as f32 * CELL_SIZE; + + let h00 = corner_heights[y as usize * vw + x as usize]; + let h10 = corner_heights[y as usize * vw + x as usize + 1]; + let h01 = corner_heights[(y as usize + 1) * vw + x as usize]; + let h11 = corner_heights[(y as usize + 1) * vw + x as usize + 1]; + let h_min = h00.min(h10).min(h01).min(h11); + let h_max = h00.max(h10).max(h01).max(h11); + + leaf.mins[0] = fx as i16; + leaf.mins[1] = (h_min - 64.0) as i16; + leaf.mins[2] = fz as i16; + leaf.maxs[0] = (fx + CELL_SIZE) as i16; + leaf.maxs[1] = (h_max + 64.0) as i16; + leaf.maxs[2] = (fz + CELL_SIZE) as i16; + + leaf.contents = if grid.get(x, y) == 0 { 0 } else { -1 }; + leaf.first_marksurface = ms_idx; + leaf.num_marksurfaces = 0; + + for fi in 0..face_idx { + if face_cell[fi] == c as u32 { + bsp.marksurfaces.push(fi as u16); + leaf.num_marksurfaces += 1; + ms_idx += 1; + } + } + } + } + + if border_north { + let ext = if bg_north.is_some() { chunk_size } else { CELL_SIZE }; + for x in 0..grid.width { + let c = (0 * grid.width + x) as usize; + bsp.leafs[c].mins[2] = (origin_z - ext) as i16; + if bg_north.is_some() { bsp.leafs[c].mins[1] = -64; } + } + } + if border_south { + let ext = if bg_south.is_some() { chunk_size } else { CELL_SIZE }; + for x in 0..grid.width { + let c = ((grid.height - 1) * grid.width + x) as usize; + bsp.leafs[c].maxs[2] = (origin_z + grid.height as f32 * CELL_SIZE + ext) as i16; + if bg_south.is_some() { bsp.leafs[c].mins[1] = -64; } + } + } + if border_west { + let ext = if bg_west.is_some() { chunk_size } else { CELL_SIZE }; + for y in 0..grid.height { + let c = (y * grid.width + 0) as usize; + bsp.leafs[c].mins[0] = (origin_x - ext) as i16; + if bg_west.is_some() { bsp.leafs[c].mins[1] = -64; } + } + } + if border_east { + let ext = if bg_east.is_some() { chunk_size } else { CELL_SIZE }; + for y in 0..grid.height { + let c = (y * grid.width + grid.width - 1) as usize; + bsp.leafs[c].maxs[0] = (origin_x + grid.width as f32 * CELL_SIZE + ext) as i16; + if bg_east.is_some() { bsp.leafs[c].mins[1] = -64; } + } + } + + let face_count_final = face_idx; + + let mut ctx = BspBuildContext { + bsp, + grid, + node_count: 0, + plane_offset: face_count_final as u32, + origin_x, + origin_z, + }; + + build_bsp_node(&mut ctx, 0, 0, grid.width, grid.height, 0, face_count_final as i32); + + let node_count = ctx.node_count as usize; + let plane_count = ctx.plane_offset as usize; + ctx.bsp.nodes.truncate(node_count); + ctx.bsp.planes.truncate(plane_count); + + let portals = build_cell_portals(grid, origin_x, origin_z); + build_pvs_data(ctx.bsp, &portals); + ctx.bsp.cell_portals = portals; + + ctx.bsp.heightfield = corner_heights; + ctx.bsp.heightfield_w = vw as u16; + ctx.bsp.heightfield_h = vh as u16; + ctx.bsp.heightfield_ox = origin_x; + ctx.bsp.heightfield_oz = origin_z; + ctx.bsp.heightfield_cell_size = CELL_SIZE; +} + +fn generate_dungeon_grid(params: &ProcgenParams, doorways: &[Doorway], entries: &[ChunkEntry]) -> (RoomGrid, Vec) { let mut rng = Rng::new(params.seed); let mut grid = RoomGrid::new(params.width, params.height); @@ -1038,42 +1829,403 @@ pub fn generate_rooms(params: &ProcgenParams) -> Bsp { } } + for door in doorways { + let cx_at = ((door.wall_x - params.origin_x) / CELL_SIZE) as i32; + let cz = ((door.wall_z - params.origin_z) / CELL_SIZE) as i32; + if cz < 0 || cz >= grid.height { continue; } + + let cx_left = cx_at - 1; + if cx_left >= 0 && cx_left < grid.width { + grid.set(cx_left, cz, 0); + } + if cx_at >= 0 && cx_at < grid.width { + grid.set(cx_at, cz, 0); + } + } + + carve_entries(&mut grid, entries); + + (grid, rooms) +} + +pub fn generate_rooms(params: &ProcgenParams, doorways: &[Doorway], entries: &[ChunkEntry], world_seed: u64) -> Bsp { + let (grid, rooms) = generate_dungeon_grid(params, doorways, entries); + + let (tight, x_off, z_off) = shrink_grid(&grid, entries); + let tight_origin_x = params.origin_x + x_off as f32 * CELL_SIZE; + let tight_origin_z = params.origin_z + z_off as f32 * CELL_SIZE; + + let tight_entries: Vec = entries.iter().map(|e| { + let cell = match e.edge { + ChunkEdge::West | ChunkEdge::East => e.cell - z_off, + ChunkEdge::North | ChunkEdge::South => e.cell - x_off, + }; + ChunkEntry { edge: e.edge, cell } + }).collect(); + let mut bsp = BspBuilder::new(); - grid_to_bsp(&mut bsp, &grid); + grid_to_bsp(&mut bsp, &tight, doorways, &tight_entries, &DUNGEON_MATS, tight_origin_x, tight_origin_z); + + bsp.bounds_min_x = tight_origin_x; + bsp.bounds_min_z = tight_origin_z; + bsp.bounds_max_x = tight_origin_x + tight.width as f32 * CELL_SIZE; + bsp.bounds_max_z = tight_origin_z + tight.height as f32 * CELL_SIZE; + + let vw = 17usize; + let vh = 17usize; + bsp.heightfield = vec![0.0f32; vw * vh]; + for vz in 0..vh as i32 { + for vx in 0..vw as i32 { + let wx = params.origin_x + vx as f32 * CELL_SIZE; + let wz = params.origin_z + vz as f32 * CELL_SIZE; + let h = unsafe { crate::pxl8::pxl8_fbm(wx * 0.002, wz * 0.002, world_seed + 5000, 4) }; + let blend = edge_blend(wx, wz, world_seed); + bsp.heightfield[vz as usize * vw + vx as usize] = h * 128.0 * blend; + } + } + bsp.heightfield_w = 17; + bsp.heightfield_h = 17; + bsp.heightfield_ox = params.origin_x; + bsp.heightfield_oz = params.origin_z; + bsp.heightfield_cell_size = CELL_SIZE; let light_height = 80.0; - let fireball_pos = Vec3::new(384.0, light_height, 324.0); - let fireball_exclusion_radius = 150.0; - let lights: Vec = rooms.iter().filter_map(|room| { - let cx = (room.x as f32 + room.w as f32 / 2.0) * CELL_SIZE; - let cz = (room.y as f32 + room.h as f32 / 2.0) * CELL_SIZE; - - let dx = cx - fireball_pos.x; - let dz = cz - fireball_pos.z; - if dx * dx + dz * dz < fireball_exclusion_radius * fireball_exclusion_radius { - return None; - } - - Some(LightSource { + let mut lights: Vec = rooms.iter().map(|room| { + let cx = params.origin_x + (room.x as f32 + room.w as f32 / 2.0) * CELL_SIZE; + let cz = params.origin_z + (room.y as f32 + room.h as f32 / 2.0) * CELL_SIZE; + LightSource { position: Vec3::new(cx, light_height, cz), intensity: 1.5, radius: 160.0, - }) + } }).collect(); - let mut lights = lights; - lights.push(LightSource { - position: Vec3::new(860.0, light_height, 416.0), - intensity: 1.2, - radius: 120.0, - }); + for door in doorways { + let dz = (door.z_start + door.z_end) * 0.5; + lights.push(LightSource { + position: Vec3::new(door.wall_x, light_height, dz), + intensity: 1.2, + radius: 120.0, + }); + } compute_bsp_vertex_lighting(&mut bsp, &lights); bsp.into() } -pub fn generate(params: &ProcgenParams) -> Bsp { - generate_rooms(params) +fn generate_courtyard_grid(params: &ProcgenParams, entries: &[ChunkEntry]) -> (RoomGrid, Vec) { + let mut rng = Rng::new(params.seed); + let mut grid = RoomGrid::new(params.width, params.height); + grid.fill(1); + + let mut rooms: Vec = Vec::new(); + + let cx = params.width / 2; + let cy = params.height / 2; + let court_half = 4; + let court_x = cx - court_half; + let court_y = cy - court_half; + let court_w = court_half * 2; + let court_h = court_half * 2; + + for ry in court_y..(court_y + court_h) { + for rx in court_x..(court_x + court_w) { + grid.set(rx, ry, 0); + } + } + rooms.push(Bounds { x: court_x, y: court_y, w: court_w, h: court_h }); + + let wing_dirs: [(i32, i32); 4] = [(0, -1), (0, 1), (-1, 0), (1, 0)]; + + for &(dx, dy) in &wing_dirs { + let room_w = 3 + (rng.next() % 3) as i32; + let room_h = 3 + (rng.next() % 3) as i32; + + let (rx, ry) = if dx != 0 { + let rx = if dx > 0 { court_x + court_w + 2 } else { court_x - room_w - 2 }; + let ry = cy - room_h / 2; + (rx, ry) + } else { + let rx = cx - room_w / 2; + let ry = if dy > 0 { court_y + court_h + 2 } else { court_y - room_h - 2 }; + (rx, ry) + }; + + let rx = rx.max(1).min(params.width - room_w - 1); + let ry = ry.max(1).min(params.height - room_h - 1); + + for y in ry..(ry + room_h) { + for x in rx..(rx + room_w) { + grid.set(x, y, 0); + } + } + + let room_cx = rx + room_w / 2; + let room_cy = ry + room_h / 2; + if dx != 0 { + carve_corridor_h(&mut grid, cx, room_cx, room_cy); + } else { + carve_corridor_v(&mut grid, cy, room_cy, room_cx); + } + + let primary = Bounds { x: rx, y: ry, w: room_w, h: room_h }; + rooms.push(primary); + + let extra_count = 1 + (rng.next() % 2) as i32; + for _ in 0..extra_count { + let ew = 2 + (rng.next() % 3) as i32; + let eh = 2 + (rng.next() % 3) as i32; + + let (ex, ey) = if dx != 0 { + let ex = if dx > 0 { rx + room_w + 1 } else { rx - ew - 1 }; + let ey = ry + (rng.next() % room_h.max(1) as u32) as i32 - eh / 2; + (ex, ey) + } else { + let ex = rx + (rng.next() % room_w.max(1) as u32) as i32 - ew / 2; + let ey = if dy > 0 { ry + room_h + 1 } else { ry - eh - 1 }; + (ex, ey) + }; + + let ex = ex.max(1).min(params.width - ew - 1); + let ey = ey.max(1).min(params.height - eh - 1); + + for y in ey..(ey + eh) { + for x in ex..(ex + ew) { + grid.set(x, y, 0); + } + } + + let ecx = ex + ew / 2; + let ecy = ey + eh / 2; + if rng.next() % 2 == 0 { + carve_corridor_h(&mut grid, room_cx, ecx, room_cy); + carve_corridor_v(&mut grid, room_cy, ecy, ecx); + } else { + carve_corridor_v(&mut grid, room_cy, ecy, room_cx); + carve_corridor_h(&mut grid, room_cx, ecx, ecy); + } + + rooms.push(Bounds { x: ex, y: ey, w: ew, h: eh }); + } + } + + carve_entries(&mut grid, entries); + + (grid, rooms) +} + +pub fn generate_courtyard(params: &ProcgenParams, entries: &[ChunkEntry], world_seed: u64) -> Bsp { + let (grid, rooms) = generate_courtyard_grid(params, entries); + + let (tight, x_off, z_off) = shrink_grid(&grid, entries); + let tight_origin_x = params.origin_x + x_off as f32 * CELL_SIZE; + let tight_origin_z = params.origin_z + z_off as f32 * CELL_SIZE; + + let tight_entries: Vec = entries.iter().map(|e| { + let cell = match e.edge { + ChunkEdge::West | ChunkEdge::East => e.cell - z_off, + ChunkEdge::North | ChunkEdge::South => e.cell - x_off, + }; + ChunkEntry { edge: e.edge, cell } + }).collect(); + + let mut bsp = BspBuilder::new(); + grid_to_bsp(&mut bsp, &tight, &[], &tight_entries, &COURTYARD_MATS, tight_origin_x, tight_origin_z); + + bsp.bounds_min_x = tight_origin_x; + bsp.bounds_min_z = tight_origin_z; + bsp.bounds_max_x = tight_origin_x + tight.width as f32 * CELL_SIZE; + bsp.bounds_max_z = tight_origin_z + tight.height as f32 * CELL_SIZE; + + let vw = 17usize; + let vh = 17usize; + bsp.heightfield = vec![0.0f32; vw * vh]; + for vz in 0..vh as i32 { + for vx in 0..vw as i32 { + let wx = params.origin_x + vx as f32 * CELL_SIZE; + let wz = params.origin_z + vz as f32 * CELL_SIZE; + let h = unsafe { crate::pxl8::pxl8_fbm(wx * 0.002, wz * 0.002, world_seed + 5000, 4) }; + let blend = edge_blend(wx, wz, world_seed); + bsp.heightfield[vz as usize * vw + vx as usize] = h * 128.0 * blend; + } + } + bsp.heightfield_w = 17; + bsp.heightfield_h = 17; + bsp.heightfield_ox = params.origin_x; + bsp.heightfield_oz = params.origin_z; + bsp.heightfield_cell_size = CELL_SIZE; + + let light_height = 80.0; + + let lights: Vec = rooms.iter().map(|room| { + let room_cx = params.origin_x + (room.x as f32 + room.w as f32 / 2.0) * CELL_SIZE; + let room_cz = params.origin_z + (room.y as f32 + room.h as f32 / 2.0) * CELL_SIZE; + let intensity = if room.w >= 6 && room.h >= 6 { 2.0 } else { 1.5 }; + let radius = if room.w >= 6 && room.h >= 6 { 250.0 } else { 160.0 }; + LightSource { + position: Vec3::new(room_cx, light_height, room_cz), + intensity, + radius, + } + }).collect(); + + compute_bsp_vertex_lighting(&mut bsp, &lights); + + bsp.into() +} + +#[derive(Clone, Copy, PartialEq)] +pub enum Biome { + Dungeon, + Courtyard, + Exterior, +} + +pub fn select_biome(cx: i32, cz: i32, seed: u64) -> Biome { + if cx == 0 && cz == 0 { return Biome::Dungeon; } + if cx == 1 && cz == 0 { return Biome::Courtyard; } + + let n = unsafe { crate::pxl8::pxl8_fbm(cx as f32 * 0.1, cz as f32 * 0.1, seed, 3) }; + if n < 0.4 { + Biome::Dungeon + } else { + Biome::Exterior + } +} + +fn chunk_seed(world_seed: u64, cx: i32, cz: i32) -> u32 { + let h = (world_seed as u32) + .wrapping_mul(374761393) + .wrapping_add(cx as u32) + .wrapping_mul(668265263) + .wrapping_add(cz as u32); + h ^ (h >> 16) +} + +pub fn generate_exterior(params: &ProcgenParams, world_seed: u64) -> Bsp { + let mut grid = RoomGrid::new(params.width, params.height); + + for y in 0..params.height { + for x in 0..params.width { + grid.set(x, y, 0); + } + } + + let mut bsp = BspBuilder::new(); + grid_to_bsp_exterior(&mut bsp, &grid, params.origin_x, params.origin_z, world_seed); + + let chunk_size = 16.0 * CELL_SIZE; + let cx = libm::floorf(params.origin_x / chunk_size) as i32; + let cz = libm::floorf(params.origin_z / chunk_size) as i32; + + let mut lights = vec![LightSource { + position: Vec3::new( + params.origin_x + params.width as f32 * CELL_SIZE * 0.5, + 200.0, + params.origin_z + params.height as f32 * CELL_SIZE * 0.5, + ), + intensity: 2.0, + radius: 2000.0, + }]; + + for &(dx, dz) in &[(0, -1), (0, 1), (-1, 0), (1, 0)] { + if select_biome(cx + dx, cz + dz, world_seed) != Biome::Exterior { + lights.push(LightSource { + position: Vec3::new( + (cx + dx) as f32 * chunk_size + chunk_size * 0.5, + 200.0, + (cz + dz) as f32 * chunk_size + chunk_size * 0.5, + ), + intensity: 2.0, + radius: 2000.0, + }); + } + } + + compute_bsp_vertex_lighting(&mut bsp, &lights); + + bsp.into() +} + +fn generate_building_grid(cx: i32, cz: i32, world_seed: u64) -> Option { + let biome = select_biome(cx, cz, world_seed); + if biome == Biome::Exterior { return None; } + + let seed = chunk_seed(world_seed, cx, cz); + let origin_x = cx as f32 * 16.0 * CELL_SIZE; + let origin_z = cz as f32 * 16.0 * CELL_SIZE; + let params = ProcgenParams { + seed, + origin_x, + origin_z, + ..Default::default() + }; + + let (doorways, entries) = get_chunk_config(cx, cz); + + let (grid, _) = match biome { + Biome::Dungeon => generate_dungeon_grid(¶ms, &doorways, &entries), + Biome::Courtyard => generate_courtyard_grid(¶ms, &entries), + _ => return None, + }; + + Some(grid) +} + +fn get_chunk_config(cx: i32, cz: i32) -> (Vec, Vec) { + let mut doorways = Vec::new(); + let mut entries = Vec::new(); + + if cx == 0 && cz == 0 { + doorways.push(Doorway { + wall_x: 832.0, + wall_z: 384.0, + z_start: 392.0, + z_end: 440.0, + height: 96.0, + dir: -1, + }); + doorways.push(Doorway { + wall_x: 832.0, + wall_z: 384.0, + z_start: 392.0, + z_end: 440.0, + height: 96.0, + dir: 1, + }); + entries.push(ChunkEntry { edge: ChunkEdge::East, cell: 6 }); + } + + if cx == 1 && cz == 0 { + entries.push(ChunkEntry { edge: ChunkEdge::West, cell: 6 }); + entries.push(ChunkEntry { edge: ChunkEdge::East, cell: 8 }); + entries.push(ChunkEntry { edge: ChunkEdge::North, cell: 8 }); + entries.push(ChunkEntry { edge: ChunkEdge::South, cell: 8 }); + } + + (doorways, entries) +} + +pub fn generate_chunk(cx: i32, cz: i32, world_seed: u64) -> Bsp { + let biome = select_biome(cx, cz, world_seed); + let seed = chunk_seed(world_seed, cx, cz); + let origin_x = cx as f32 * 16.0 * CELL_SIZE; + let origin_z = cz as f32 * 16.0 * CELL_SIZE; + + let params = ProcgenParams { + seed, + origin_x, + origin_z, + ..Default::default() + }; + + let (doorways, entries) = get_chunk_config(cx, cz); + + match biome { + Biome::Dungeon => generate_rooms(¶ms, &doorways, &entries, world_seed), + Biome::Courtyard => generate_courtyard(¶ms, &entries, world_seed), + Biome::Exterior => generate_exterior(¶ms, world_seed), + } } diff --git a/pxl8d/src/sim.rs b/pxl8d/src/sim.rs index 51d8ba9..01caf43 100644 --- a/pxl8d/src/sim.rs +++ b/pxl8d/src/sim.rs @@ -1,7 +1,6 @@ -extern crate alloc; - use alloc::vec::Vec; +use crate::chunk::ChunkId; use crate::math::Vec3; use crate::pxl8::*; use crate::world::World; @@ -96,13 +95,6 @@ impl Simulation { id } - pub fn teleport_player(&mut self, x: f32, y: f32, z: f32) { - if let Some(id) = self.player { - let ent = &mut self.entities[id as usize]; - ent.pos = Vec3::new(x, y, z); - } - } - pub fn step(&mut self, inputs: &[pxl8_input_msg], dt: f32) { self.tick += 1; self.time += dt; @@ -114,22 +106,8 @@ impl Simulation { self.integrate(dt); } - fn make_sim_world(&self, _pos: Vec3) -> pxl8_sim_world { - if let Some(chunk) = self.world.active() { - if let Some(bsp) = chunk.as_bsp() { - return pxl8_sim_world { - bsp: bsp.as_c_bsp(), - }; - } - } - - pxl8_sim_world { - bsp: core::ptr::null(), - } - } - - fn integrate(&mut self, dt: f32) { - let cfg = pxl8_sim_config { + fn sim_config() -> pxl8_sim_config { + pxl8_sim_config { move_speed: 180.0, ground_accel: 10.0, air_accel: 1.0, @@ -140,7 +118,39 @@ impl Simulation { player_radius: 16.0, player_height: 72.0, max_pitch: 1.5, + } + } + + fn make_sim_world(&self, player_pos: Vec3) -> pxl8_sim_world { + let chunk_size: f32 = 16.0 * 64.0; + let center_cx = libm::floorf(player_pos.x / chunk_size) as i32; + let center_cz = libm::floorf(player_pos.z / chunk_size) as i32; + + let mut sim = pxl8_sim_world { + chunks: [core::ptr::null(); 9], + center_cx, + center_cz, + chunk_size, }; + + for dz in -1..=1i32 { + for dx in -1..=1i32 { + let cx = center_cx + dx; + let cz = center_cz + dz; + let id = ChunkId::Bsp { cx, cz }; + if let Some(chunk) = self.world.get(&id) { + if let Some(bsp) = chunk.as_bsp() { + let idx = ((dz + 1) * 3 + (dx + 1)) as usize; + sim.chunks[idx] = bsp.as_c_bsp(); + } + } + } + } + sim + } + + fn integrate(&mut self, dt: f32) { + let cfg = Self::sim_config(); for i in 0..self.entities.len() { let ent = &self.entities[i]; if ent.flags & ALIVE == 0 || ent.flags & PLAYER != 0 { @@ -156,18 +166,7 @@ impl Simulation { } fn move_player(&mut self, input: &pxl8_input_msg, dt: f32) { - let cfg = pxl8_sim_config { - move_speed: 180.0, - ground_accel: 10.0, - air_accel: 1.0, - stop_speed: 100.0, - friction: 6.0, - gravity: 800.0, - jump_velocity: 200.0, - player_radius: 16.0, - player_height: 72.0, - max_pitch: 1.5, - }; + let cfg = Self::sim_config(); let Some(id) = self.player else { return }; let ent = &self.entities[id as usize]; if ent.flags & ALIVE == 0 { @@ -181,7 +180,7 @@ impl Simulation { } } - pub fn generate_snapshot(&self, _player_id: u64, mut writer: F) + pub fn generate_snapshot(&self, mut writer: F) where F: FnMut(&pxl8_entity_state), { @@ -211,10 +210,6 @@ impl Simulation { } } - pub fn entity_count(&self) -> usize { - self.entities.iter().filter(|e| e.flags & ALIVE != 0).count() - } - pub fn get_player_position(&self, player_id: u64) -> Option<(f32, f32, f32)> { let idx = player_id as usize; if idx < self.entities.len() { diff --git a/pxl8d/src/transport.rs b/pxl8d/src/transport.rs index 3d12652..654bda4 100644 --- a/pxl8d/src/transport.rs +++ b/pxl8d/src/transport.rs @@ -1,16 +1,18 @@ -extern crate alloc; - use alloc::vec; use alloc::vec::Vec; use crate::pxl8::*; use crate::pxl8::pxl8_msg_type::*; pub const DEFAULT_PORT: u16 = 7777; pub const CHUNK_MAX_PAYLOAD: usize = 1400; -pub const CHUNK_FLAG_RLE: u8 = 0x01; pub const CHUNK_FLAG_FINAL: u8 = 0x04; pub const CHUNK_TYPE_BSP: u8 = 1; +pub fn chunk_hash(cx: i32, cz: i32) -> u32 { + let h = (cx as u32).wrapping_mul(374761393).wrapping_add((cz as u32).wrapping_mul(668265263)); + h ^ (h >> 16) +} + pub struct ChunkMessage { pub chunk_type: u8, pub id: u32, @@ -25,16 +27,17 @@ pub struct ChunkMessage { } impl ChunkMessage { - pub fn from_bsp(data: Vec, chunk_id: u32, version: u32) -> Vec { + pub fn from_bsp(data: Vec, cx: i32, cz: i32, version: u32) -> Vec { let total_size = data.len(); + let id = chunk_hash(cx, cz); if total_size <= CHUNK_MAX_PAYLOAD { return vec![ChunkMessage { chunk_type: CHUNK_TYPE_BSP, - id: chunk_id, - cx: 0, + id, + cx, cy: 0, - cz: 0, + cz, version, flags: CHUNK_FLAG_FINAL, fragment_idx: 0, @@ -53,10 +56,10 @@ impl ChunkMessage { messages.push(ChunkMessage { chunk_type: CHUNK_TYPE_BSP, - id: chunk_id, - cx: 0, + id, + cx, cy: 0, - cz: 0, + cz, version, flags: if is_final { CHUNK_FLAG_FINAL } else { 0 }, fragment_idx: i as u8, @@ -362,7 +365,7 @@ impl Transport { sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr); } - pub fn send_chunk_enter(&mut self, chunk_id: u32, chunk_type: u8, sequence: u32) { + pub fn send_chunk_enter(&mut self, cx: i32, cz: i32, sequence: u32) { if !self.has_client { return; } @@ -378,34 +381,13 @@ impl Transport { offset += self.serialize_header(&msg_header, offset); let buf = &mut self.send_buf[offset..]; - buf[0..4].copy_from_slice(&chunk_id.to_be_bytes()); - buf[4] = chunk_type; - buf[5] = 0; - buf[6] = 0; - buf[7] = 0; + buf[0..4].copy_from_slice(&cx.to_be_bytes()); + buf[4..8].copy_from_slice(&cz.to_be_bytes()); offset += 8; sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr); } - pub fn send_chunk_exit(&mut self, sequence: u32) { - if !self.has_client { - return; - } - - let mut offset = 0; - - let msg_header = pxl8_msg_header { - sequence, - size: 0, - type_: PXL8_MSG_CHUNK_EXIT as u8, - version: PXL8_PROTOCOL_VERSION as u8, - }; - offset += self.serialize_header(&msg_header, offset); - - sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr); - } - fn serialize_chunk_msg_header(&mut self, msg: &ChunkMessage, offset: usize) -> usize { let buf = &mut self.send_buf[offset..]; buf[0] = msg.chunk_type; diff --git a/pxl8d/src/world.rs b/pxl8d/src/world.rs index 087ce7c..3d3e48a 100644 --- a/pxl8d/src/world.rs +++ b/pxl8d/src/world.rs @@ -1,15 +1,12 @@ -extern crate alloc; - use alloc::collections::BTreeMap; use crate::chunk::{Chunk, ChunkId}; -use crate::math::Vec3; -use crate::procgen::{ProcgenParams, generate_rooms}; +use crate::procgen::generate_chunk; pub struct World { active: Option, chunks: BTreeMap, - seed: u64, + pub seed: u64, } impl World { @@ -21,67 +18,26 @@ impl World { } } - pub fn insert(&mut self, chunk: Chunk) { - let id = chunk.id(); - self.chunks.insert(id, chunk); - } - pub fn get(&self, id: &ChunkId) -> Option<&Chunk> { self.chunks.get(id) } - pub fn get_mut(&mut self, id: &ChunkId) -> Option<&mut Chunk> { - self.chunks.get_mut(id) - } - - pub fn remove(&mut self, id: &ChunkId) -> Option { - self.chunks.remove(id) - } - pub fn active(&self) -> Option<&Chunk> { self.active.as_ref().and_then(|id| self.chunks.get(id)) } - pub fn active_id(&self) -> Option { - self.active - } - pub fn set_active(&mut self, id: ChunkId) { self.active = Some(id); } - pub fn clear_active(&mut self) { - self.active = None; - } - - pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 { - if let Some(chunk) = self.active() { - return chunk.trace(from, to, radius); - } - to - } - - pub fn generate_bsp(&mut self, id: u32, params: Option<&ProcgenParams>) -> &Chunk { - let chunk_id = ChunkId::Bsp(id); + pub fn generate_bsp(&mut self, cx: i32, cz: i32) -> &Chunk { + let chunk_id = ChunkId::Bsp { cx, cz }; if !self.chunks.contains_key(&chunk_id) { - let default_params = ProcgenParams { - seed: (self.seed as u32).wrapping_add(id), - ..Default::default() - }; - let p = params.unwrap_or(&default_params); - let bsp = generate_rooms(p); - self.chunks.insert(chunk_id, Chunk::Bsp { id, bsp, version: 1 }); + let bsp = generate_chunk(cx, cz, self.seed); + self.chunks.insert(chunk_id, Chunk::Bsp { cx, cz, bsp, version: 1 }); } self.chunks.get(&chunk_id).unwrap() } - - pub fn contains(&self, id: &ChunkId) -> bool { - self.chunks.contains_key(id) - } - - pub fn iter(&self) -> impl Iterator { - self.chunks.iter() - } } impl Default for World { diff --git a/src/asset/pxl8_cart.c b/src/asset/pxl8_cart.c index 9f4131e..4ba9cd4 100644 --- a/src/asset/pxl8_cart.c +++ b/src/asset/pxl8_cart.c @@ -1,12 +1,9 @@ #include "pxl8_cart.h" -#include #include -#include #include -#include -#include +#include "pxl8_io.h" #include "pxl8_log.h" #include "pxl8_mem.h" @@ -49,7 +46,6 @@ struct pxl8_cart { char* title; pxl8_resolution resolution; pxl8_size window_size; - pxl8_pixel_mode pixel_mode; bool is_folder; bool is_mounted; }; @@ -57,11 +53,6 @@ struct pxl8_cart { static pxl8_cart* pxl8_current_cart = NULL; static char* pxl8_original_cwd = NULL; -static bool is_directory(const char* path) { - struct stat st; - return stat(path, &st) == 0 && S_ISDIR(st.st_mode); -} - static bool is_pxc_file(const char* path) { usize len = strlen(path); return len > 4 && strcmp(path + len - 4, ".pxc") == 0; @@ -70,41 +61,48 @@ static bool is_pxc_file(const char* path) { static bool has_main_script(const char* base_path) { char path[512]; snprintf(path, sizeof(path), "%s/main.fnl", base_path); - if (access(path, F_OK) == 0) return true; + if (pxl8_io_file_exists(path)) return true; snprintf(path, sizeof(path), "%s/main.lua", base_path); - return access(path, F_OK) == 0; + return pxl8_io_file_exists(path); +} + +typedef struct { + const char* dir_path; + const char* prefix; + char*** paths; + u32* count; + u32* capacity; +} collect_ctx; + +static bool collect_entry(void* userdata, const char* dir_path, const char* name) { + collect_ctx* ctx = userdata; + + char full_path[1024]; + char rel_path[1024]; + snprintf(full_path, sizeof(full_path), "%s%s", dir_path, name); + snprintf(rel_path, sizeof(rel_path), "%s%s", ctx->prefix, name); + + if (pxl8_io_is_directory(full_path)) { + char new_prefix[1025]; + snprintf(new_prefix, sizeof(new_prefix), "%s/", rel_path); + collect_ctx sub = { .dir_path = full_path, .prefix = new_prefix, + .paths = ctx->paths, .count = ctx->count, .capacity = ctx->capacity }; + pxl8_io_enumerate_directory(full_path, collect_entry, &sub); + } else { + if (*ctx->count >= *ctx->capacity) { + *ctx->capacity = (*ctx->capacity == 0) ? 64 : (*ctx->capacity * 2); + *ctx->paths = pxl8_realloc(*ctx->paths, *ctx->capacity * sizeof(char*)); + } + (*ctx->paths)[(*ctx->count)++] = strdup(rel_path); + } + return true; } static void collect_files_recursive(const char* dir_path, const char* prefix, char*** paths, u32* count, u32* capacity) { - DIR* dir = opendir(dir_path); - if (!dir) return; - - struct dirent* entry; - while ((entry = readdir(dir)) != NULL) { - if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; - - char full_path[1024]; - char rel_path[1024]; - snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name); - snprintf(rel_path, sizeof(rel_path), "%s%s", prefix, entry->d_name); - - struct stat st; - if (stat(full_path, &st) == 0) { - if (S_ISDIR(st.st_mode)) { - char new_prefix[1025]; - snprintf(new_prefix, sizeof(new_prefix), "%s/", rel_path); - collect_files_recursive(full_path, new_prefix, paths, count, capacity); - } else { - if (*count >= *capacity) { - *capacity = (*capacity == 0) ? 64 : (*capacity * 2); - *paths = pxl8_realloc(*paths, *capacity * sizeof(char*)); - } - (*paths)[(*count)++] = strdup(rel_path); - } - } - } - closedir(dir); + collect_ctx ctx = { .dir_path = dir_path, .prefix = prefix, + .paths = paths, .count = count, .capacity = capacity }; + pxl8_io_enumerate_directory(dir_path, collect_entry, &ctx); } static pxl8_cart_file* find_file(const pxl8_cart* cart, const char* path) { @@ -156,7 +154,6 @@ pxl8_cart* pxl8_cart_create(void) { if (cart) { cart->resolution = PXL8_RESOLUTION_640x360; cart->window_size = (pxl8_size){1280, 720}; - cart->pixel_mode = PXL8_PIXEL_INDEXED; } return cart; } @@ -175,8 +172,8 @@ pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) { if (!cart || !path) return PXL8_ERROR_NULL_POINTER; pxl8_cart_unload(cart); - if (is_directory(path)) { - cart->base_path = realpath(path, NULL); + if (pxl8_io_is_directory(path)) { + cart->base_path = pxl8_io_get_real_path(path); if (!cart->base_path) { pxl8_error("Failed to resolve cart path: %s", path); return PXL8_ERROR_FILE_NOT_FOUND; @@ -300,8 +297,8 @@ pxl8_result pxl8_cart_mount(pxl8_cart* cart) { } if (cart->is_folder) { - pxl8_original_cwd = getcwd(NULL, 0); - if (chdir(cart->base_path) != 0) { + pxl8_original_cwd = pxl8_io_get_cwd(); + if (!pxl8_io_set_cwd(cart->base_path)) { pxl8_error("Failed to change to cart directory: %s", cart->base_path); pxl8_free(pxl8_original_cwd); pxl8_original_cwd = NULL; @@ -323,7 +320,7 @@ void pxl8_cart_unmount(pxl8_cart* cart) { if (!cart || !cart->is_mounted) return; if (pxl8_original_cwd) { - chdir(pxl8_original_cwd); + pxl8_io_set_cwd(pxl8_original_cwd); pxl8_free(pxl8_original_cwd); pxl8_original_cwd = NULL; } @@ -364,14 +361,6 @@ void pxl8_cart_set_window_size(pxl8_cart* cart, pxl8_size size) { if (cart) cart->window_size = size; } -pxl8_pixel_mode pxl8_cart_get_pixel_mode(const pxl8_cart* cart) { - return cart ? cart->pixel_mode : PXL8_PIXEL_INDEXED; -} - -void pxl8_cart_set_pixel_mode(pxl8_cart* cart, pxl8_pixel_mode mode) { - if (cart) cart->pixel_mode = mode; -} - bool pxl8_cart_is_packed(const pxl8_cart* cart) { return cart && !cart->is_folder; } @@ -394,7 +383,7 @@ bool pxl8_cart_file_exists(const pxl8_cart* cart, const char* path) { if (cart->is_folder) { char full_path[512]; if (!pxl8_cart_resolve_path(cart, path, full_path, sizeof(full_path))) return false; - return access(full_path, F_OK) == 0; + return pxl8_io_file_exists(full_path); } return find_file(cart, path) != NULL; @@ -457,7 +446,7 @@ void pxl8_cart_free_file(u8* data) { pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) { if (!folder_path || !output_path) return PXL8_ERROR_NULL_POINTER; - if (!is_directory(folder_path)) { + if (!pxl8_io_is_directory(folder_path)) { pxl8_error("Cart folder not found: %s", folder_path); return PXL8_ERROR_FILE_NOT_FOUND; } @@ -488,9 +477,9 @@ pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) { for (u32 i = 0; i < count; i++) { char full_path[1024]; snprintf(full_path, sizeof(full_path), "%s/%s", folder_path, paths[i]); - struct stat st; - if (stat(full_path, &st) == 0) { - file_sizes[i] = (u32)st.st_size; + usize fsize = pxl8_io_get_file_size(full_path); + if (fsize > 0) { + file_sizes[i] = (u32)fsize; total_size += file_sizes[i]; } else { file_sizes[i] = 0; @@ -563,9 +552,10 @@ pxl8_result pxl8_cart_bundle(const char* input_path, const char* output_path, co u32 cart_size = 0; bool free_cart = false; - if (is_directory(input_path)) { + if (pxl8_io_is_directory(input_path)) { char temp_pxc[256]; - snprintf(temp_pxc, sizeof(temp_pxc), "/tmp/pxl8_bundle_%d.pxc", getpid()); + static u32 bundle_counter = 0; + snprintf(temp_pxc, sizeof(temp_pxc), "/tmp/pxl8_bundle_%u.pxc", bundle_counter++); pxl8_result result = pxl8_cart_pack(input_path, temp_pxc); if (result != PXL8_OK) return result; @@ -577,7 +567,7 @@ pxl8_result pxl8_cart_bundle(const char* input_path, const char* output_path, co cart_data = pxl8_malloc(cart_size); fread(cart_data, 1, cart_size, f); fclose(f); - unlink(temp_pxc); + remove(temp_pxc); free_cart = true; } else if (is_pxc_file(input_path)) { FILE* f = fopen(input_path, "rb"); @@ -628,7 +618,7 @@ pxl8_result pxl8_cart_bundle(const char* input_path, const char* output_path, co fwrite(&trailer, sizeof(trailer), 1, out); fclose(out); - chmod(output_path, 0755); + pxl8_io_set_executable(output_path); pxl8_info("Bundle created: %s", output_path); return PXL8_OK; diff --git a/src/asset/pxl8_cart.h b/src/asset/pxl8_cart.h index 9384192..e81b406 100644 --- a/src/asset/pxl8_cart.h +++ b/src/asset/pxl8_cart.h @@ -25,11 +25,9 @@ const char* pxl8_cart_get_base_path(const pxl8_cart* cart); const char* pxl8_cart_get_title(const pxl8_cart* cart); pxl8_resolution pxl8_cart_get_resolution(const pxl8_cart* cart); pxl8_size pxl8_cart_get_window_size(const pxl8_cart* cart); -pxl8_pixel_mode pxl8_cart_get_pixel_mode(const pxl8_cart* cart); void pxl8_cart_set_title(pxl8_cart* cart, const char* title); void pxl8_cart_set_resolution(pxl8_cart* cart, pxl8_resolution resolution); void pxl8_cart_set_window_size(pxl8_cart* cart, pxl8_size size); -void pxl8_cart_set_pixel_mode(pxl8_cart* cart, pxl8_pixel_mode mode); bool pxl8_cart_is_packed(const pxl8_cart* cart); bool pxl8_cart_has_embedded(const char* exe_path); diff --git a/src/asset/pxl8_save.c b/src/asset/pxl8_save.c index 5233869..77d3b50 100644 --- a/src/asset/pxl8_save.c +++ b/src/asset/pxl8_save.c @@ -1,24 +1,16 @@ #include "pxl8_save.h" -#include -#include -#include -#include #include +#include +#include -#ifdef _WIN32 -#include -#include -#define PATH_SEP '\\' -#else -#include -#include -#define PATH_SEP '/' -#endif - +#include "pxl8_io.h" #include "pxl8_log.h" +#include "pxl8_macros.h" #include "pxl8_mem.h" +#define PATH_SEP '/' + typedef struct { u32 magic; u32 version; @@ -49,23 +41,6 @@ static void pxl8_save_get_slot_path(pxl8_save* save, u8 slot, char* path, usize } } -static pxl8_result pxl8_save_ensure_directory(const char* path) { - struct stat st; - if (stat(path, &st) == 0) { - return S_ISDIR(st.st_mode) ? PXL8_OK : PXL8_ERROR_SYSTEM_FAILURE; - } - -#ifdef _WIN32 - if (_mkdir(path) != 0 && errno != EEXIST) { -#else - if (mkdir(path, 0755) != 0 && errno != EEXIST) { -#endif - return PXL8_ERROR_SYSTEM_FAILURE; - } - - return PXL8_OK; -} - pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) { if (!game_name) return NULL; @@ -75,40 +50,17 @@ pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) { save->magic = magic; save->version = version; - char base_dir[PXL8_SAVE_MAX_PATH]; - -#ifdef _WIN32 - if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, base_dir))) { - snprintf(save->directory, sizeof(save->directory), - "%s%cpxl8%c%s", base_dir, PATH_SEP, PATH_SEP, game_name); - } else { - pxl8_free(save); - return NULL; - } -#else - const char* home = getenv("HOME"); - if (!home) { - struct passwd* pw = getpwuid(getuid()); - if (pw) home = pw->pw_dir; - } - if (!home) { + char* pref_path = pxl8_io_get_pref_path("pxl8", game_name); + if (!pref_path) { pxl8_free(save); return NULL; } + pxl8_strncpy(save->directory, pref_path, sizeof(save->directory)); + pxl8_free(pref_path); - snprintf(base_dir, sizeof(base_dir), "%s/.local/share", home); - pxl8_save_ensure_directory(base_dir); - - snprintf(base_dir, sizeof(base_dir), "%s/.local/share/pxl8", home); - pxl8_save_ensure_directory(base_dir); - - snprintf(save->directory, sizeof(save->directory), - "%s/.local/share/pxl8/%s", home, game_name); -#endif - - if (pxl8_save_ensure_directory(save->directory) != PXL8_OK) { - pxl8_free(save); - return NULL; + usize dir_len = strlen(save->directory); + if (dir_len > 0 && save->directory[dir_len - 1] == '/') { + save->directory[dir_len - 1] = '\0'; } pxl8_info("Save system initialized: %s", save->directory); @@ -116,9 +68,7 @@ pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) { } void pxl8_save_destroy(pxl8_save* save) { - if (save) { - pxl8_free(save); - } + pxl8_free(save); } pxl8_result pxl8_save_write(pxl8_save* save, u8 slot, const u8* data, u32 size) { @@ -234,9 +184,7 @@ pxl8_result pxl8_save_read(pxl8_save* save, u8 slot, u8** data_out, u32* size_ou } void pxl8_save_free(u8* data) { - if (data) { - pxl8_free(data); - } + pxl8_free(data); } bool pxl8_save_exists(pxl8_save* save, u8 slot) { @@ -248,8 +196,7 @@ bool pxl8_save_exists(pxl8_save* save, u8 slot) { char path[PXL8_SAVE_MAX_PATH]; pxl8_save_get_slot_path(save, slot, path, sizeof(path)); - struct stat st; - return stat(path, &st) == 0; + return pxl8_io_file_exists(path); } pxl8_result pxl8_save_delete(pxl8_save* save, u8 slot) { diff --git a/src/bsp/pxl8_bsp.c b/src/bsp/pxl8_bsp.c index 0a2136d..27131ff 100644 --- a/src/bsp/pxl8_bsp.c +++ b/src/bsp/pxl8_bsp.c @@ -342,6 +342,7 @@ void pxl8_bsp_destroy(pxl8_bsp* bsp) { pxl8_free(bsp->vertex_lights); pxl8_free(bsp->vertices); pxl8_free(bsp->visdata); + pxl8_free(bsp->heightfield); memset(bsp, 0, sizeof(*bsp)); } diff --git a/src/bsp/pxl8_bsp.h b/src/bsp/pxl8_bsp.h index 71fd615..3fdb06b 100644 --- a/src/bsp/pxl8_bsp.h +++ b/src/bsp/pxl8_bsp.h @@ -110,6 +110,7 @@ typedef struct pxl8_bsp { u32* vertex_lights; pxl8_bsp_vertex* vertices; u8* visdata; + f32* heightfield; u32 lightdata_size; u32 num_cell_portals; @@ -124,7 +125,17 @@ typedef struct pxl8_bsp { u32 num_surfedges; u32 num_vertex_lights; u32 num_vertices; + u32 num_heightfield; + f32 heightfield_ox; + f32 heightfield_oz; + f32 heightfield_cell_size; + u16 heightfield_w; + u16 heightfield_h; u32 visdata_size; + f32 bounds_min_x; + f32 bounds_min_z; + f32 bounds_max_x; + f32 bounds_max_z; } pxl8_bsp; #ifdef __cplusplus diff --git a/src/bsp/pxl8_bsp_render.c b/src/bsp/pxl8_bsp_render.c index 8afe551..08d61ed 100644 --- a/src/bsp/pxl8_bsp_render.c +++ b/src/bsp/pxl8_bsp_render.c @@ -7,15 +7,6 @@ #include "pxl8_mem.h" #include "pxl8_mesh.h" -typedef struct { - f32 x0, y0, x1, y1; -} screen_rect; - -typedef struct { - u32 leaf; - screen_rect window; -} portal_queue_entry; - static inline bool pxl8_bsp_get_edge_vertex(const pxl8_bsp* bsp, i32 surfedge_idx, u32* out_vert_idx) { if (surfedge_idx >= (i32)bsp->num_surfedges) return false; @@ -40,83 +31,6 @@ static inline bool face_in_frustum(const pxl8_bsp* bsp, u32 face_id, const pxl8_ return pxl8_frustum_test_aabb(frustum, face->aabb_min, face->aabb_max); } -static inline bool screen_rect_valid(screen_rect r) { - return r.x0 < r.x1 && r.y0 < r.y1; -} - -static inline screen_rect screen_rect_intersect(screen_rect a, screen_rect b) { - return (screen_rect){ - .x0 = (a.x0 > b.x0) ? a.x0 : b.x0, - .y0 = (a.y0 > b.y0) ? a.y0 : b.y0, - .x1 = (a.x1 < b.x1) ? a.x1 : b.x1, - .y1 = (a.y1 < b.y1) ? a.y1 : b.y1, - }; -} - -static inline void expand_rect_with_ndc(screen_rect* r, f32 nx, f32 ny) { - if (nx < r->x0) r->x0 = nx; - if (nx > r->x1) r->x1 = nx; - if (ny < r->y0) r->y0 = ny; - if (ny > r->y1) r->y1 = ny; -} - -static screen_rect project_portal_to_screen(const pxl8_bsp_portal* portal, const pxl8_mat4* vp, f32 wall_height) { - pxl8_vec3 world_corners[4] = { - {portal->x0, 0, portal->z0}, - {portal->x1, 0, portal->z1}, - {portal->x1, wall_height, portal->z1}, - {portal->x0, wall_height, portal->z0}, - }; - - pxl8_vec4 clip[4]; - bool in_front[4]; - i32 front_count = 0; - - const f32 NEAR_W = 0.001f; - - for (i32 i = 0; i < 4; i++) { - clip[i] = pxl8_mat4_multiply_vec4(*vp, (pxl8_vec4){world_corners[i].x, world_corners[i].y, world_corners[i].z, 1.0f}); - in_front[i] = clip[i].w > NEAR_W; - if (in_front[i]) front_count++; - } - - if (front_count == 0) return (screen_rect){0, 0, 0, 0}; - - screen_rect result = {1e30f, 1e30f, -1e30f, -1e30f}; - - for (i32 i = 0; i < 4; i++) { - i32 j = (i + 1) % 4; - pxl8_vec4 a = clip[i]; - pxl8_vec4 b = clip[j]; - bool a_in = in_front[i]; - bool b_in = in_front[j]; - - if (a_in) { - f32 inv_w = 1.0f / a.w; - expand_rect_with_ndc(&result, a.x * inv_w, a.y * inv_w); - } - - if (a_in != b_in) { - f32 t = (NEAR_W - a.w) / (b.w - a.w); - pxl8_vec4 intersection = { - a.x + t * (b.x - a.x), - a.y + t * (b.y - a.y), - a.z + t * (b.z - a.z), - NEAR_W - }; - f32 inv_w = 1.0f / intersection.w; - expand_rect_with_ndc(&result, intersection.x * inv_w, intersection.y * inv_w); - } - } - - if (result.x0 < -1.0f) result.x0 = -1.0f; - if (result.y0 < -1.0f) result.y0 = -1.0f; - if (result.x1 > 1.0f) result.x1 = 1.0f; - if (result.y1 > 1.0f) result.y1 = 1.0f; - - return result; -} - static void collect_face_to_mesh(const pxl8_bsp* bsp, const pxl8_bsp_render_state* state, u32 face_id, pxl8_mesh* mesh, u8 ambient) { const pxl8_bsp_face* face = &bsp->faces[face_id]; @@ -228,190 +142,38 @@ void pxl8_bsp_render_state_destroy(pxl8_bsp_render_state* state) { pxl8_free(state); } -void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_gfx_material* material) { - if (!gfx || !bsp || face_id >= bsp->num_faces || !material) return; - - pxl8_mesh* mesh = pxl8_mesh_create(64, 192); - if (!mesh) return; - - collect_face_to_mesh(bsp, NULL, face_id, mesh, pxl8_gfx_get_ambient(gfx)); - - if (mesh->index_count > 0) { - pxl8_mat4 identity = pxl8_mat4_identity(); - pxl8_3d_draw_mesh(gfx, mesh, &identity, material); - } - - pxl8_mesh_destroy(mesh); -} - void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, - pxl8_bsp_render_state* state, pxl8_vec3 camera_pos) { + pxl8_bsp_render_state* state, + const pxl8_gfx_draw_opts* opts) { if (!gfx || !bsp || !state || bsp->num_faces == 0) return; if (!state->materials || state->num_materials == 0) return; + if (!state->render_face_flags) return; const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx); - const pxl8_mat4* vp = pxl8_3d_get_view_proj(gfx); - if (!frustum || !vp) return; - - i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos); - if (camera_leaf < 0 || (u32)camera_leaf >= bsp->num_leafs) return; - - if (!bsp->cell_portals || bsp->num_cell_portals == 0) { - pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384); - if (!mesh) return; - - u32 current_material = 0xFFFFFFFF; - - for (u32 face_id = 0; face_id < bsp->num_faces; face_id++) { - if (!face_in_frustum(bsp, face_id, frustum)) continue; - - const pxl8_bsp_face* face = &bsp->faces[face_id]; - u32 material_id = face->material_id; - if (material_id >= state->num_materials) continue; - - if (material_id != current_material && mesh->index_count > 0 && current_material < state->num_materials) { - pxl8_mat4 identity = pxl8_mat4_identity(); - pxl8_3d_draw_mesh(gfx, mesh, &identity, &state->materials[current_material]); - pxl8_mesh_clear(mesh); - } - - current_material = material_id; - collect_face_to_mesh(bsp, state, face_id, mesh, pxl8_gfx_get_ambient(gfx)); - } - - if (mesh->index_count > 0 && current_material < state->num_materials) { - pxl8_mat4 identity = pxl8_mat4_identity(); - pxl8_3d_draw_mesh(gfx, mesh, &identity, &state->materials[current_material]); - } - - pxl8_mesh_destroy(mesh); - return; - } - - if (!state->render_face_flags && state->num_faces > 0) { - state->render_face_flags = pxl8_calloc(state->num_faces, 1); - if (!state->render_face_flags) return; - } - memset(state->render_face_flags, 0, state->num_faces); - - pxl8_bsp_pvs pvs = pxl8_bsp_decompress_pvs(bsp, camera_leaf); - - u32 visited_bytes = (bsp->num_leafs + 7) / 8; - u8* visited = pxl8_calloc(visited_bytes, 1); - screen_rect* cell_windows = pxl8_calloc(bsp->num_leafs, sizeof(screen_rect)); - portal_queue_entry* queue = pxl8_malloc(bsp->num_leafs * 4 * sizeof(portal_queue_entry)); - if (!visited || !cell_windows || !queue) { - pxl8_free(visited); - pxl8_free(cell_windows); - pxl8_free(queue); - pxl8_bsp_pvs_destroy(&pvs); - return; - } - - u32 head = 0, tail = 0; - screen_rect full_screen = {-1.0f, -1.0f, 1.0f, 1.0f}; - - visited[camera_leaf >> 3] |= (1 << (camera_leaf & 7)); - cell_windows[camera_leaf] = full_screen; - queue[tail++] = (portal_queue_entry){camera_leaf, full_screen}; - - f32 wall_height = 128.0f; - - while (head < tail) { - portal_queue_entry entry = queue[head++]; - u32 leaf_id = entry.leaf; - screen_rect window = entry.window; - - if (leaf_id >= bsp->num_cell_portals) continue; - const pxl8_bsp_cell_portals* cp = &bsp->cell_portals[leaf_id]; - - for (u8 i = 0; i < cp->num_portals; i++) { - const pxl8_bsp_portal* portal = &cp->portals[i]; - u32 target = portal->target_leaf; - - if (target >= bsp->num_leafs) continue; - if (bsp->leafs[target].contents == -1) continue; - if (!pxl8_bsp_pvs_is_visible(&pvs, target)) continue; - - screen_rect portal_rect = project_portal_to_screen(portal, vp, wall_height); - if (!screen_rect_valid(portal_rect)) continue; - - screen_rect new_window = screen_rect_intersect(window, portal_rect); - if (!screen_rect_valid(new_window)) continue; - - u32 byte = target >> 3; - u32 bit = target & 7; - if (visited[byte] & (1 << bit)) { - screen_rect existing = cell_windows[target]; - bool expanded = false; - if (new_window.x0 < existing.x0) { cell_windows[target].x0 = new_window.x0; expanded = true; } - if (new_window.y0 < existing.y0) { cell_windows[target].y0 = new_window.y0; expanded = true; } - if (new_window.x1 > existing.x1) { cell_windows[target].x1 = new_window.x1; expanded = true; } - if (new_window.y1 > existing.y1) { cell_windows[target].y1 = new_window.y1; expanded = true; } - if (expanded && tail < bsp->num_leafs * 4) { - queue[tail++] = (portal_queue_entry){target, cell_windows[target]}; - } - continue; - } - - visited[byte] |= (1 << bit); - cell_windows[target] = new_window; - queue[tail++] = (portal_queue_entry){target, new_window}; - } - } + if (!frustum) return; pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384); - if (!mesh) { - pxl8_free(visited); - pxl8_free(cell_windows); - pxl8_free(queue); - return; - } + if (!mesh) return; - u32 current_material = 0xFFFFFFFF; - - for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) { - if (!(visited[leaf_id >> 3] & (1 << (leaf_id & 7)))) continue; - if (bsp->leafs[leaf_id].contents == -1) continue; - - const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id]; - - for (u32 i = 0; i < leaf->num_marksurfaces; i++) { - u32 surf_idx = leaf->first_marksurface + i; - if (surf_idx >= bsp->num_marksurfaces) continue; - - u32 face_id = bsp->marksurfaces[surf_idx]; - if (face_id >= bsp->num_faces) continue; - - if (state->render_face_flags[face_id]) continue; - state->render_face_flags[face_id] = 1; - - if (!face_in_frustum(bsp, face_id, frustum)) continue; + u8 ambient = pxl8_gfx_get_ambient(gfx); + pxl8_mat4 identity = pxl8_mat4_identity(); + for (u32 mat = 0; mat < state->num_materials; mat++) { + for (u32 face_id = 0; face_id < bsp->num_faces; face_id++) { + if (!state->render_face_flags[face_id]) continue; const pxl8_bsp_face* face = &bsp->faces[face_id]; - u32 material_id = face->material_id; - if (material_id >= state->num_materials) continue; - - if (material_id != current_material && mesh->index_count > 0 && current_material < state->num_materials) { - pxl8_mat4 identity = pxl8_mat4_identity(); - pxl8_3d_draw_mesh(gfx, mesh, &identity, &state->materials[current_material]); - pxl8_mesh_clear(mesh); - } - - current_material = material_id; - collect_face_to_mesh(bsp, state, face_id, mesh, pxl8_gfx_get_ambient(gfx)); + if (face->material_id != mat) continue; + if (!face_in_frustum(bsp, face_id, frustum)) continue; + collect_face_to_mesh(bsp, state, face_id, mesh, ambient); + } + if (mesh->index_count > 0) { + pxl8_gfx_material mat_copy = state->materials[mat]; + if (state->exterior) mat_copy.double_sided = true; + pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat_copy, opts); + pxl8_mesh_clear(mesh); } } - if (mesh->index_count > 0 && current_material < state->num_materials) { - pxl8_mat4 identity = pxl8_mat4_identity(); - pxl8_3d_draw_mesh(gfx, mesh, &identity, &state->materials[current_material]); - } - - pxl8_bsp_pvs_destroy(&pvs); - pxl8_free(visited); - pxl8_free(cell_windows); - pxl8_free(queue); pxl8_mesh_destroy(mesh); } diff --git a/src/bsp/pxl8_bsp_render.h b/src/bsp/pxl8_bsp_render.h index 3c4cb4e..7002343 100644 --- a/src/bsp/pxl8_bsp_render.h +++ b/src/bsp/pxl8_bsp_render.h @@ -12,15 +12,15 @@ typedef struct pxl8_bsp_render_state { u8* render_face_flags; u32 num_materials; u32 num_faces; + bool exterior; } pxl8_bsp_render_state; pxl8_bsp_render_state* pxl8_bsp_render_state_create(u32 num_faces); void pxl8_bsp_render_state_destroy(pxl8_bsp_render_state* state); void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, - pxl8_bsp_render_state* state, pxl8_vec3 camera_pos); -void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, - const pxl8_gfx_material* material); + pxl8_bsp_render_state* state, + const pxl8_gfx_draw_opts* opts); void pxl8_bsp_set_material(pxl8_bsp_render_state* state, u16 material_id, const pxl8_gfx_material* material); diff --git a/src/core/pxl8.c b/src/core/pxl8.c index 5d9d794..cca8f93 100644 --- a/src/core/pxl8.c +++ b/src/core/pxl8.c @@ -3,12 +3,8 @@ #define PXL8_VERSION "0.1.0" #include -#include #include -#include -#include - #include "pxl8_ase.h" #include "pxl8_game.h" #include "pxl8_hal.h" @@ -150,20 +146,11 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { } if (bundle_mode) { - char exe_path[1024]; - ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1); - if (len == -1) { - pxl8_error("failed to resolve executable path"); - return PXL8_ERROR_SYSTEM_FAILURE; - } - exe_path[len] = '\0'; - pxl8_result result = pxl8_cart_bundle(pack_input, pack_output, exe_path); - return result; + return pxl8_cart_bundle(pack_input, pack_output, argv[0]); } if (pack_mode) { - pxl8_result result = pxl8_cart_pack(pack_input, pack_output); - return result; + return pxl8_cart_pack(pack_input, pack_output); } if (remap_palette_mode) { @@ -191,14 +178,14 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { } const char* cart_path = script_arg; - char* original_cwd = getcwd(NULL, 0); + char* original_cwd = pxl8_io_get_cwd(); bool load_embedded = has_embedded && !run_mode; bool load_from_path = false; if (!load_embedded && run_mode) { if (!cart_path) { - if (access("main.fnl", F_OK) == 0 || access("main.lua", F_OK) == 0) { + if (pxl8_io_file_exists("main.fnl") || pxl8_io_file_exists("main.lua")) { cart_path = "."; } else { pxl8_error("no main.fnl or main.lua found in current directory"); @@ -206,9 +193,7 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { return PXL8_ERROR_INITIALIZATION_FAILED; } } - struct stat st; - load_from_path = (stat(cart_path, &st) == 0 && S_ISDIR(st.st_mode)) || - strstr(cart_path, ".pxc"); + load_from_path = pxl8_io_is_directory(cart_path) || strstr(cart_path, ".pxc"); } if (load_embedded || load_from_path) { @@ -239,7 +224,6 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { const char* window_title = pxl8_cart_get_title(sys->cart); if (!window_title) window_title = "pxl8"; pxl8_resolution resolution = pxl8_cart_get_resolution(sys->cart); - pxl8_pixel_mode pixel_mode = pxl8_cart_get_pixel_mode(sys->cart); pxl8_size window_size = pxl8_cart_get_window_size(sys->cart); pxl8_size render_size = pxl8_get_resolution_dimensions(resolution); @@ -249,17 +233,12 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { return PXL8_ERROR_INITIALIZATION_FAILED; } - game->gfx = pxl8_gfx_create(sys->hal, sys->platform_data, pixel_mode, resolution); + game->gfx = pxl8_gfx_create(sys->hal, sys->platform_data, resolution); if (!game->gfx) { pxl8_error("failed to create graphics context"); return PXL8_ERROR_INITIALIZATION_FAILED; } - if (pxl8_gfx_load_font_atlas(game->gfx) != PXL8_OK) { - pxl8_error("failed to load font atlas"); - return PXL8_ERROR_INITIALIZATION_FAILED; - } - game->mixer = pxl8_sfx_mixer_create(sys->hal); if (!game->mixer) { pxl8_error("failed to create audio mixer"); @@ -375,18 +354,13 @@ pxl8_result pxl8_update(pxl8* sys) { pxl8_repl_command* cmd = pxl8_repl_pop_command(sys->repl); if (cmd) { pxl8_result result = pxl8_script_eval_repl(game->script, pxl8_repl_command_buffer(cmd)); - if (result != PXL8_OK) { - if (pxl8_script_is_incomplete_input(game->script)) { - pxl8_repl_signal_complete(sys->repl); - } else { - pxl8_error("%s", pxl8_script_get_last_error(game->script)); - pxl8_repl_clear_accumulator(sys->repl); - pxl8_repl_signal_complete(sys->repl); - } - } else { - pxl8_repl_clear_accumulator(sys->repl); - pxl8_repl_signal_complete(sys->repl); + if (result != PXL8_OK && !pxl8_script_is_incomplete_input(game->script)) { + pxl8_error("%s", pxl8_script_get_last_error(game->script)); } + if (result == PXL8_OK || !pxl8_script_is_incomplete_input(game->script)) { + pxl8_repl_clear_accumulator(sys->repl); + } + pxl8_repl_signal_complete(sys->repl); } } diff --git a/src/core/pxl8_io.c b/src/core/pxl8_io.c index 1a0206c..47db00b 100644 --- a/src/core/pxl8_io.c +++ b/src/core/pxl8_io.c @@ -81,46 +81,9 @@ pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, usize si return pxl8_io_write_file(path, (const char*)data, size); } -bool pxl8_io_file_exists(const char* path) { - if (!path) return false; - - struct stat st; - return stat(path, &st) == 0; -} - -f64 pxl8_io_get_file_modified_time(const char* path) { - if (!path) return 0.0; - - struct stat st; - if (stat(path, &st) == 0) { - return st.st_mtime; - } - return 0.0; -} - -pxl8_result pxl8_io_create_directory(const char* path) { - if (!path) return PXL8_ERROR_NULL_POINTER; - -#ifdef _WIN32 - if (mkdir(path) != 0) { -#else - if (mkdir(path, 0755) != 0) { -#endif - return PXL8_ERROR_SYSTEM_FAILURE; - } - return PXL8_OK; -} - -void pxl8_io_free_file_content(char* content) { - if (content) { - pxl8_free(content); - } -} void pxl8_io_free_binary_data(u8* data) { - if (data) { - pxl8_free(data); - } + pxl8_free(data); } static i32 pxl8_key_code(const char* key_name) { diff --git a/src/core/pxl8_io.h b/src/core/pxl8_io.h index 7ad1063..04bf6bd 100644 --- a/src/core/pxl8_io.h +++ b/src/core/pxl8_io.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include "pxl8_bytes.h" #include "pxl8_types.h" @@ -10,11 +9,20 @@ extern "C" { #endif +typedef bool (*pxl8_io_dir_callback)(void* userdata, const char* dir_path, const char* name); + pxl8_result pxl8_io_create_directory(const char* path); +bool pxl8_io_enumerate_directory(const char* path, pxl8_io_dir_callback callback, void* userdata); bool pxl8_io_file_exists(const char* path); void pxl8_io_free_binary_data(u8* data); -void pxl8_io_free_file_content(char* content); +char* pxl8_io_get_cwd(void); f64 pxl8_io_get_file_modified_time(const char* path); +usize pxl8_io_get_file_size(const char* path); +char* pxl8_io_get_pref_path(const char* org, const char* app); +char* pxl8_io_get_real_path(const char* path); +bool pxl8_io_is_directory(const char* path); +bool pxl8_io_set_cwd(const char* path); +void pxl8_io_set_executable(const char* path); pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, usize* size); pxl8_result pxl8_io_read_file(const char* path, char** content, usize* size); pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, usize size); diff --git a/src/core/pxl8_log.c b/src/core/pxl8_log.c index e378005..348c828 100644 --- a/src/core/pxl8_log.c +++ b/src/core/pxl8_log.c @@ -7,11 +7,11 @@ #include #include -#define PXL8_LOG_COLOR_ERROR "\033[38;2;251;73;52m" -#define PXL8_LOG_COLOR_WARN "\033[38;2;250;189;47m" -#define PXL8_LOG_COLOR_INFO "\033[38;2;184;187;38m" -#define PXL8_LOG_COLOR_DEBUG "\033[38;2;131;165;152m" -#define PXL8_LOG_COLOR_TRACE "\033[38;2;211;134;155m" +#define PXL8_LOG_COLOR_ERROR "\033[1;38;2;251;73;52m" +#define PXL8_LOG_COLOR_WARN "\033[1;38;2;250;189;47m" +#define PXL8_LOG_COLOR_INFO "\033[1;38;2;184;187;38m" +#define PXL8_LOG_COLOR_DEBUG "\033[1;38;2;131;165;152m" +#define PXL8_LOG_COLOR_TRACE "\033[1;38;2;211;134;155m" #define PXL8_LOG_COLOR_RESET "\033[0m" static pxl8_log* g_log = NULL; diff --git a/src/core/pxl8_queue.h b/src/core/pxl8_queue.h index f2206d5..ae14c44 100644 --- a/src/core/pxl8_queue.h +++ b/src/core/pxl8_queue.h @@ -4,7 +4,7 @@ #include "pxl8_types.h" -#define PXL8_QUEUE_CAPACITY 256 +#define PXL8_QUEUE_CAPACITY 512 typedef struct pxl8_queue { void* items[PXL8_QUEUE_CAPACITY]; diff --git a/src/core/pxl8_types.h b/src/core/pxl8_types.h index 761afea..4e85269 100644 --- a/src/core/pxl8_types.h +++ b/src/core/pxl8_types.h @@ -31,12 +31,6 @@ typedef __int128_t i128; typedef __uint128_t u128; #endif -typedef enum pxl8_pixel_mode { - PXL8_PIXEL_INDEXED = 1, - PXL8_PIXEL_HICOLOR = 2, - PXL8_PIXEL_RGBA = 4, -} pxl8_pixel_mode; - typedef enum pxl8_cursor { PXL8_CURSOR_ARROW, PXL8_CURSOR_HAND @@ -108,3 +102,4 @@ typedef struct pxl8_viewport { i32 scaled_width, scaled_height; f32 scale; } pxl8_viewport; + diff --git a/src/gfx/pxl8_atlas.c b/src/gfx/pxl8_atlas.c index 910dba6..3811699 100644 --- a/src/gfx/pxl8_atlas.c +++ b/src/gfx/pxl8_atlas.c @@ -1,11 +1,8 @@ #include "pxl8_atlas.h" #include -#include -#include #include -#include "pxl8_color.h" #include "pxl8_log.h" #include "pxl8_mem.h" @@ -144,15 +141,14 @@ static void pxl8_skyline_compact(pxl8_skyline* skyline) { } } -pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode) { +pxl8_atlas* pxl8_atlas_create(u32 width, u32 height) { pxl8_atlas* atlas = (pxl8_atlas*)pxl8_calloc(1, sizeof(pxl8_atlas)); if (!atlas) return NULL; atlas->height = height; atlas->width = width; - i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode); - atlas->pixels = (u8*)pxl8_calloc(width * height, bytes_per_pixel); + atlas->pixels = (u8*)pxl8_calloc(width * height, 1); if (!atlas->pixels) { pxl8_free(atlas); return NULL; @@ -226,14 +222,13 @@ void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count) { atlas->dirty = true; } -bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode) { +bool pxl8_atlas_expand(pxl8_atlas* atlas) { if (!atlas || atlas->width >= 4096) return false; - i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode); u32 new_size = atlas->width * 2; u32 old_width = atlas->width; - u8* new_pixels = (u8*)pxl8_calloc(new_size * new_size, bytes_per_pixel); + u8* new_pixels = (u8*)pxl8_calloc(new_size * new_size, 1); if (!new_pixels) return false; pxl8_skyline new_skyline; @@ -268,11 +263,7 @@ bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode) { for (u32 x = 0; x < (u32)atlas->entries[i].w; x++) { u32 src_idx = (atlas->entries[i].y + y) * old_width + (atlas->entries[i].x + x); u32 dst_idx = (fit.pos.y + y) * new_size + (fit.pos.x + x); - if (bytes_per_pixel == 2) { - ((u16*)new_pixels)[dst_idx] = ((u16*)atlas->pixels)[src_idx]; - } else { - new_pixels[dst_idx] = atlas->pixels[src_idx]; - } + new_pixels[dst_idx] = atlas->pixels[src_idx]; } } @@ -304,15 +295,14 @@ u32 pxl8_atlas_add_texture( pxl8_atlas* atlas, const u8* pixels, u32 w, - u32 h, - pxl8_pixel_mode pixel_mode + u32 h ) { if (!atlas || !pixels) return UINT32_MAX; pxl8_skyline_fit fit = pxl8_skyline_find_position(&atlas->skyline, atlas->width, atlas->height, w, h); if (!fit.found) { - if (!pxl8_atlas_expand(atlas, pixel_mode)) { + if (!pxl8_atlas_expand(atlas)) { return UINT32_MAX; } @@ -347,17 +337,11 @@ u32 pxl8_atlas_add_texture( entry->h = h; entry->log2_w = pxl8_log2(w); - i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode); for (u32 y = 0; y < h; y++) { for (u32 x = 0; x < w; x++) { u32 src_idx = y * w + x; u32 dst_idx = (fit.pos.y + y) * atlas->width + (fit.pos.x + x); - - if (bytes_per_pixel == 2) { - ((u16*)atlas->pixels)[dst_idx] = ((const u16*)pixels)[src_idx]; - } else { - atlas->pixels[dst_idx] = pixels[src_idx]; - } + atlas->pixels[dst_idx] = pixels[src_idx]; } } diff --git a/src/gfx/pxl8_atlas.h b/src/gfx/pxl8_atlas.h index 94e0a95..46d46e2 100644 --- a/src/gfx/pxl8_atlas.h +++ b/src/gfx/pxl8_atlas.h @@ -30,11 +30,11 @@ static inline u8 pxl8_log2(u32 v) { extern "C" { #endif -u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h, pxl8_pixel_mode pixel_mode); +u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h); void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count); -pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode); +pxl8_atlas* pxl8_atlas_create(u32 width, u32 height); void pxl8_atlas_destroy(pxl8_atlas* atlas); -bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode); +bool pxl8_atlas_expand(pxl8_atlas* atlas); const pxl8_atlas_entry* pxl8_atlas_get_entry(const pxl8_atlas* atlas, u32 id); u32 pxl8_atlas_get_entry_count(const pxl8_atlas* atlas); u32 pxl8_atlas_get_height(const pxl8_atlas* atlas); diff --git a/src/gfx/pxl8_blit.c b/src/gfx/pxl8_blit.c index 7409066..8fa3192 100644 --- a/src/gfx/pxl8_blit.c +++ b/src/gfx/pxl8_blit.c @@ -1,34 +1,7 @@ #include "pxl8_blit.h" -void pxl8_blit_hicolor(u16* fb, u32 fb_width, const u16* sprite, u32 atlas_width, - i32 x, i32 y, u32 w, u32 h) { - u16* dest_base = fb + y * fb_width + x; - const u16* src_base = sprite; - - for (u32 row = 0; row < h; row++) { - u16* dest_row = dest_base + row * fb_width; - const u16* src_row = src_base + row * atlas_width; - - u32 col = 0; - u32 count2 = w / 2; - for (u32 i = 0; i < count2; i++) { - u32 pixels = ((const u32*)src_row)[i]; - if (pixels == 0) { - col += 2; - continue; - } - dest_row[col] = pxl8_blend_hicolor((u16)(pixels), dest_row[col]); - dest_row[col + 1] = pxl8_blend_hicolor((u16)(pixels >> 16), dest_row[col + 1]); - col += 2; - } - if (w & 1) { - dest_row[col] = pxl8_blend_hicolor(src_row[col], dest_row[col]); - } - } -} - -void pxl8_blit_indexed(u8* fb, u32 fb_width, const u8* sprite, u32 atlas_width, - i32 x, i32 y, u32 w, u32 h) { +void pxl8_blit(u8* fb, u32 fb_width, const u8* sprite, u32 atlas_width, + i32 x, i32 y, u32 w, u32 h) { u8* dest_base = fb + y * fb_width + x; const u8* src_base = sprite; @@ -44,14 +17,14 @@ void pxl8_blit_indexed(u8* fb, u32 fb_width, const u8* sprite, u32 atlas_width, col += 4; continue; } - dest_row[col] = pxl8_blend_indexed((u8)(pixels), dest_row[col]); - dest_row[col + 1] = pxl8_blend_indexed((u8)(pixels >> 8), dest_row[col + 1]); - dest_row[col + 2] = pxl8_blend_indexed((u8)(pixels >> 16), dest_row[col + 2]); - dest_row[col + 3] = pxl8_blend_indexed((u8)(pixels >> 24), dest_row[col + 3]); + dest_row[col] = pxl8_blit_mask((u8)(pixels), dest_row[col]); + dest_row[col + 1] = pxl8_blit_mask((u8)(pixels >> 8), dest_row[col + 1]); + dest_row[col + 2] = pxl8_blit_mask((u8)(pixels >> 16), dest_row[col + 2]); + dest_row[col + 3] = pxl8_blit_mask((u8)(pixels >> 24), dest_row[col + 3]); col += 4; } for (; col < w; col++) { - dest_row[col] = pxl8_blend_indexed(src_row[col], dest_row[col]); + dest_row[col] = pxl8_blit_mask(src_row[col], dest_row[col]); } } } diff --git a/src/gfx/pxl8_blit.h b/src/gfx/pxl8_blit.h index 0ab5bd8..63a65c6 100644 --- a/src/gfx/pxl8_blit.h +++ b/src/gfx/pxl8_blit.h @@ -6,22 +6,12 @@ extern "C" { #endif -static inline u8 pxl8_blend_indexed(u8 src, u8 dst) { +static inline u8 pxl8_blit_mask(u8 src, u8 dst) { u8 m = (u8)(-(src != 0)); return (src & m) | (dst & ~m); } -static inline u16 pxl8_blend_hicolor(u16 src, u16 dst) { - u16 m = (u16)(-(src != 0)); - return (src & m) | (dst & ~m); -} - -void pxl8_blit_hicolor( - u16* fb, u32 fb_width, - const u16* sprite, u32 atlas_width, - i32 x, i32 y, u32 w, u32 h -); -void pxl8_blit_indexed( +void pxl8_blit( u8* fb, u32 fb_width, const u8* sprite, u32 atlas_width, i32 x, i32 y, u32 w, u32 h diff --git a/src/gfx/pxl8_color.h b/src/gfx/pxl8_color.h index f159724..8915454 100644 --- a/src/gfx/pxl8_color.h +++ b/src/gfx/pxl8_color.h @@ -2,10 +2,6 @@ #include "pxl8_types.h" -static inline i32 pxl8_bytes_per_pixel(pxl8_pixel_mode mode) { - return (i32)mode; -} - static inline u32 pxl8_color_from_rgba(u32 rgba) { u8 r = (rgba >> 24) & 0xFF; u8 g = (rgba >> 16) & 0xFF; @@ -76,30 +72,10 @@ static inline void pxl8_rgb332_unpack(u8 c, u8* r, u8* g, u8* b) { *b = (bi << 6) | (bi << 4) | (bi << 2) | bi; } -static inline u16 pxl8_rgb565_pack(u8 r, u8 g, u8 b) { - return ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); -} - -static inline void pxl8_rgb565_unpack(u16 color, u8* r, u8* g, u8* b) { - *r = (color >> 11) << 3; - *g = ((color >> 5) & 0x3F) << 2; - *b = (color & 0x1F) << 3; -} - -static inline u32 pxl8_rgb565_to_rgba32(u16 color) { - u8 r, g, b; - pxl8_rgb565_unpack(color, &r, &g, &b); - return r | ((u32)g << 8) | ((u32)b << 16) | 0xFF000000; -} - static inline u32 pxl8_rgba32_pack(u8 r, u8 g, u8 b, u8 a) { return r | ((u32)g << 8) | ((u32)b << 16) | ((u32)a << 24); } -static inline u16 pxl8_rgba32_to_rgb565(u32 rgba) { - return pxl8_rgb565_pack(rgba & 0xFF, (rgba >> 8) & 0xFF, (rgba >> 16) & 0xFF); -} - static inline void pxl8_rgba32_unpack(u32 color, u8* r, u8* g, u8* b, u8* a) { *r = color & 0xFF; *g = (color >> 8) & 0xFF; diff --git a/src/gfx/pxl8_gfx.c b/src/gfx/pxl8_gfx.c index de4ad2c..8960c43 100644 --- a/src/gfx/pxl8_gfx.c +++ b/src/gfx/pxl8_gfx.c @@ -6,7 +6,6 @@ #include "pxl8_ase.h" #include "pxl8_atlas.h" #include "pxl8_blit.h" -#include "pxl8_color.h" #include "pxl8_colormap.h" #include "pxl8_font.h" #include "pxl8_glows.h" @@ -72,7 +71,6 @@ struct pxl8_gfx { pxl8_palette_cube* palette_cube; pxl8_gfx_pass frame_pass; pxl8_frame_resources frame_res; - pxl8_pixel_mode pixel_mode; void* platform_data; pxl8_sprite_cache_entry* sprite_cache; u32 sprite_cache_capacity; @@ -100,34 +98,19 @@ pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) { return bounds; } -pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx) { - return gfx ? gfx->pixel_mode : PXL8_PIXEL_INDEXED; -} - -u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx) { - if (!gfx || gfx->pixel_mode == PXL8_PIXEL_HICOLOR) return NULL; +u8* pxl8_gfx_framebuffer(pxl8_gfx* gfx) { + if (!gfx) return NULL; return (u8*)pxl8_texture_get_data(gfx->renderer, gfx->color_target); } -u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx) { - if (!gfx || gfx->pixel_mode != PXL8_PIXEL_HICOLOR) return NULL; - return (u16*)pxl8_texture_get_data(gfx->renderer, gfx->color_target); -} - i32 pxl8_gfx_get_height(const pxl8_gfx* gfx) { return gfx ? gfx->framebuffer_height : 0; } -u32* pxl8_gfx_get_output(pxl8_gfx* gfx) { - if (!gfx) return NULL; - return gfx->output; -} - -void pxl8_gfx_resolve(pxl8_gfx* gfx) { +static void pxl8_gfx_resolve(pxl8_gfx* gfx) { if (!gfx || !gfx->initialized) return; - if (gfx->pixel_mode != PXL8_PIXEL_INDEXED) return; - const u32* pal = gfx->palette ? pxl8_palette_colors(gfx->palette) : NULL; + const u32* pal = pxl8_palette_colors(gfx->palette); if (!pal) { pxl8_error("resolve: no palette!"); return; @@ -180,10 +163,8 @@ i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) { if (!gfx || !gfx->palette || !filepath) return -1; pxl8_result result = pxl8_palette_load_ase(gfx->palette, filepath); if (result != PXL8_OK) return (i32)result; - if (gfx->pixel_mode != PXL8_PIXEL_HICOLOR) { - if (gfx->colormap) { - pxl8_colormap_generate(gfx->colormap, pxl8_palette_colors(gfx->palette)); - } + if (gfx->colormap) { + pxl8_colormap_generate(gfx->colormap, pxl8_palette_colors(gfx->palette)); } return 0; } @@ -191,7 +172,6 @@ i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) { pxl8_gfx* pxl8_gfx_create( const pxl8_hal* hal, void* platform_data, - pxl8_pixel_mode mode, pxl8_resolution resolution ) { pxl8_shader_registry_init(); @@ -205,7 +185,6 @@ pxl8_gfx* pxl8_gfx_create( gfx->hal = hal; gfx->platform_data = platform_data; - gfx->pixel_mode = mode; pxl8_size size = pxl8_get_resolution_dimensions(resolution); gfx->framebuffer_width = size.w; gfx->framebuffer_height = size.h; @@ -216,9 +195,7 @@ pxl8_gfx* pxl8_gfx_create( return NULL; } - if (mode != PXL8_PIXEL_HICOLOR) { - gfx->palette = pxl8_palette_create(); - } + gfx->palette = pxl8_palette_create(); gfx->renderer = pxl8_renderer_create(gfx->framebuffer_width, gfx->framebuffer_height); if (!gfx->renderer) { @@ -258,9 +235,7 @@ pxl8_gfx* pxl8_gfx_create( return NULL; } - if (mode != PXL8_PIXEL_HICOLOR) { - gfx->colormap = pxl8_calloc(1, sizeof(pxl8_colormap)); - } + gfx->colormap = pxl8_calloc(1, sizeof(pxl8_colormap)); gfx->target_stack[0] = (pxl8_target_entry){ .color = gfx->color_target, @@ -313,7 +288,7 @@ void pxl8_gfx_destroy(pxl8_gfx* gfx) { static pxl8_result pxl8_gfx_ensure_atlas(pxl8_gfx* gfx) { if (gfx->atlas) return PXL8_OK; - gfx->atlas = pxl8_atlas_create(PXL8_DEFAULT_ATLAS_SIZE, PXL8_DEFAULT_ATLAS_SIZE, gfx->pixel_mode); + gfx->atlas = pxl8_atlas_create(PXL8_DEFAULT_ATLAS_SIZE, PXL8_DEFAULT_ATLAS_SIZE); return gfx->atlas ? PXL8_OK : PXL8_ERROR_OUT_OF_MEMORY; } @@ -335,7 +310,7 @@ pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width, pxl8_result result = pxl8_gfx_ensure_atlas(gfx); if (result != PXL8_OK) return result; - u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, pixels, width, height, gfx->pixel_mode); + u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, pixels, width, height); if (texture_id == UINT32_MAX) { pxl8_error("Texture doesn't fit in atlas"); return PXL8_ERROR_INVALID_SIZE; @@ -381,8 +356,7 @@ pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) { gfx->atlas, ase_file.frames[0].pixels, ase_file.header.width, - ase_file.header.height, - gfx->pixel_mode + ase_file.header.height ); pxl8_ase_destroy(&ase_file); @@ -418,36 +392,17 @@ pxl8_atlas* pxl8_gfx_get_atlas(pxl8_gfx* gfx) { return gfx->atlas; } -pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx) { - (void)gfx; - return PXL8_OK; -} - void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) { if (!gfx || !gfx->initialized || !gfx->hal) return; - if (gfx->pixel_mode == PXL8_PIXEL_INDEXED) { - pxl8_gfx_resolve(gfx); - gfx->hal->upload_texture( - gfx->platform_data, - gfx->output, - gfx->framebuffer_width, - gfx->framebuffer_height, - PXL8_PIXEL_RGBA, - NULL - ); - return; - } - - u32* colors = gfx->palette ? pxl8_palette_colors(gfx->palette) : NULL; - u8* framebuffer = (u8*)pxl8_texture_get_data(gfx->renderer, gfx->color_target); + pxl8_gfx_resolve(gfx); gfx->hal->upload_texture( gfx->platform_data, - framebuffer, + gfx->output, gfx->framebuffer_width, gfx->framebuffer_height, - gfx->pixel_mode, - colors + 4, + NULL ); } @@ -472,10 +427,6 @@ void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp) { gfx->viewport = vp; } -void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom) { - (void)gfx; (void)left; (void)right; (void)top; (void)bottom; -} - static pxl8_gfx_texture gfx_current_color(pxl8_gfx* gfx) { if (gfx->target_stack_depth == 0) return gfx->color_target; return gfx->target_stack[gfx->target_stack_depth - 1].color; @@ -639,7 +590,7 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo if (is_1to1_scale && is_unclipped && !is_flipped) { const u8* sprite_data = atlas_pixels + entry->y * atlas_width + entry->x; - pxl8_blit_indexed(framebuffer, fb_width, sprite_data, atlas_width, x, y, w, h); + pxl8_blit(framebuffer, fb_width, sprite_data, atlas_width, x, y, w, h); } else { for (i32 py = 0; py < draw_height; py++) { for (i32 px = 0; px < draw_width; px++) { @@ -652,7 +603,7 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo i32 src_idx = src_y * atlas_width + src_x; i32 dest_idx = (dest_y + py) * fb_width + (dest_x + px); - framebuffer[dest_idx] = pxl8_blend_indexed(atlas_pixels[src_idx], framebuffer[dest_idx]); + framebuffer[dest_idx] = pxl8_blit_mask(atlas_pixels[src_idx], framebuffer[dest_idx]); } } } @@ -667,7 +618,7 @@ void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt) { } } -pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_shader_uniforms* uniforms) { +static pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_shader_uniforms* uniforms) { pxl8_3d_frame frame = {0}; if (!camera) return frame; @@ -770,6 +721,11 @@ void pxl8_3d_clear_depth(pxl8_gfx* gfx) { pxl8_cmdbuf_clear_depth(gfx->cmdbuf, gfx_current_depth(gfx)); } +void pxl8_3d_clear_stencil(pxl8_gfx* gfx, u8 value) { + if (!gfx) return; + pxl8_clear_stencil(gfx->renderer, value); +} + void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color) { if (!gfx) return; @@ -789,7 +745,7 @@ void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color) { pxl8_draw_line(gfx->renderer, gfx_current_color(gfx), x0, y0, x1, y1, color); } -void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material) { +void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material, const pxl8_gfx_draw_opts* opts) { if (!gfx || !mesh || !model || !material) return; if (!pxl8_gfx_handle_valid(gfx->frame_pass)) return; @@ -801,6 +757,13 @@ void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* mo shader_name = "unlit"; } + static const pxl8_gfx_draw_opts default_opts = { + .color_write = true, + .depth_compare = PXL8_GFX_COMPARE_LESS, + .depth_write = true, + }; + if (!opts) opts = &default_opts; + pxl8_gfx_pipeline_desc pipe_desc = { .blend = { .enabled = false, @@ -809,7 +772,9 @@ void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* mo .alpha_test = false, .alpha_ref = 0, }, - .depth = { .test = true, .write = true, .compare = PXL8_GFX_COMPARE_LESS }, + .color_write = opts->color_write, + .depth = { .test = true, .write = opts->depth_write, .compare = opts->depth_compare }, + .stencil = { .test = opts->stencil_test, .write = opts->stencil_write, .compare = opts->stencil_compare, .ref = opts->stencil_ref }, .dither = material->dither, .double_sided = material->double_sided, .emissive = material->emissive, @@ -948,11 +913,6 @@ void pxl8_3d_end_frame(pxl8_gfx* gfx) { res->texture_count = 0; } -u8* pxl8_3d_get_framebuffer(pxl8_gfx* gfx) { - if (!gfx) return NULL; - return (u8*)pxl8_texture_get_data(gfx->renderer, gfx_current_color(gfx)); -} - bool pxl8_gfx_push_target(pxl8_gfx* gfx) { if (!gfx || gfx->target_stack_depth >= PXL8_MAX_TARGET_STACK) return false; diff --git a/src/gfx/pxl8_gfx.h b/src/gfx/pxl8_gfx.h index ad13479..bbdb2be 100644 --- a/src/gfx/pxl8_gfx.h +++ b/src/gfx/pxl8_gfx.h @@ -36,7 +36,7 @@ typedef enum pxl8_gfx_effect { extern "C" { #endif -pxl8_gfx* pxl8_gfx_create(const pxl8_hal* hal, void* platform_data, pxl8_pixel_mode mode, pxl8_resolution resolution); +pxl8_gfx* pxl8_gfx_create(const pxl8_hal* hal, void* platform_data, pxl8_resolution resolution); void pxl8_gfx_destroy(pxl8_gfx* gfx); void pxl8_gfx_present(pxl8_gfx* gfx); @@ -46,25 +46,21 @@ void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx); u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color); pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx); -u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx); -u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx); +u8* pxl8_gfx_framebuffer(pxl8_gfx* gfx); i32 pxl8_gfx_get_height(const pxl8_gfx* gfx); const u32* pxl8_gfx_palette_colors(pxl8_gfx* gfx); u16* pxl8_gfx_get_zbuffer(pxl8_gfx* gfx); pxl8_palette* pxl8_gfx_palette(pxl8_gfx* gfx); pxl8_colormap* pxl8_gfx_colormap(pxl8_gfx* gfx); -pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx); i32 pxl8_gfx_get_width(const pxl8_gfx* gfx); i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath); -void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom); void pxl8_gfx_set_palette_colors(pxl8_gfx* gfx, const u32* colors, u16 count); void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp); pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height); void pxl8_gfx_clear_textures(pxl8_gfx* gfx); pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width, u32 height); -pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx); pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path); bool pxl8_gfx_push_target(pxl8_gfx* gfx); diff --git a/src/gfx/pxl8_gfx3d.h b/src/gfx/pxl8_gfx3d.h index d5e2689..e462951 100644 --- a/src/gfx/pxl8_gfx3d.h +++ b/src/gfx/pxl8_gfx3d.h @@ -4,6 +4,7 @@ #include "pxl8_lights.h" #include "pxl8_math.h" #include "pxl8_mesh.h" +#include "pxl8_render_types.h" #include "pxl8_shader.h" #include "pxl8_types.h" @@ -29,16 +30,14 @@ void pxl8_3d_set_bsp(pxl8_gfx* gfx, const pxl8_bsp* bsp); void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_shader_uniforms* uniforms); void pxl8_3d_clear(pxl8_gfx* gfx, u8 color); void pxl8_3d_clear_depth(pxl8_gfx* gfx); +void pxl8_3d_clear_stencil(pxl8_gfx* gfx, u8 value); 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_gfx_material* material); +void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material, const pxl8_gfx_draw_opts* opts); 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); const pxl8_mat4* pxl8_3d_get_view_proj(pxl8_gfx* gfx); u32 pxl8_3d_project_points(pxl8_gfx* gfx, const pxl8_vec3* in, pxl8_vec3* out, u32 count, const pxl8_mat4* transform); -pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_shader_uniforms* uniforms); - #ifdef __cplusplus } #endif diff --git a/src/gfx/pxl8_render.c b/src/gfx/pxl8_render.c index 00c3362..2296bae 100644 --- a/src/gfx/pxl8_render.c +++ b/src/gfx/pxl8_render.c @@ -83,7 +83,7 @@ static u8 palette_find_closest(const u32* palette, u8 r, u8 g, u8 b) { return best_idx; } -static u8 blend_indexed( +static u8 blend_colors( const pxl8_gfx_pipeline_desc* pipeline, u8 src, u8 dst, @@ -279,6 +279,7 @@ static void rasterize_triangle( const tri_setup* setup, u8* fb, u16* zb, + u8* sb, u32 fb_width, pxl8_shader_fn shader, const pxl8_gfx_pipeline_desc* pipeline, @@ -289,6 +290,7 @@ static void rasterize_triangle( if (setup->y_start > setup->y_end) return; + bool color_write = !pipeline || pipeline->color_write; bool depth_test = pipeline && pipeline->depth.test; bool depth_write = pipeline && pipeline->depth.write; pxl8_gfx_compare_func depth_compare = pipeline ? pipeline->depth.compare : PXL8_GFX_COMPARE_ALWAYS; @@ -296,6 +298,10 @@ static void rasterize_triangle( u8 alpha_ref = pipeline ? pipeline->blend.alpha_ref : 0; bool blend_enabled = pipeline && pipeline->blend.enabled; const u32* palette = bindings ? bindings->palette : NULL; + bool stencil_test = pipeline && pipeline->stencil.test && sb; + bool stencil_write = pipeline && pipeline->stencil.write && sb; + pxl8_gfx_compare_func stencil_compare = pipeline ? pipeline->stencil.compare : PXL8_GFX_COMPARE_ALWAYS; + u8 stencil_ref = pipeline ? pipeline->stencil.ref : 0; for (i32 y = setup->y_start; y <= setup->y_end; y++) { f32 yf = (f32)y + 0.5f; @@ -453,7 +459,7 @@ static void rasterize_triangle( i32 px = x; #if defined(PXL8_SIMD_SSE) || defined(PXL8_SIMD_NEON) - if (depth_test && depth_compare == PXL8_GFX_COMPARE_LESS && !blend_enabled) { + if (depth_test && depth_compare == PXL8_GFX_COMPARE_LESS && !blend_enabled && !stencil_test && !stencil_write) { pxl8_f32_simd dz4_simd = pxl8_f32_simd_set(dz * 4.0f); pxl8_f32_simd half = pxl8_f32_simd_set(0.5f); pxl8_f32_simd one = pxl8_f32_simd_set(1.0f); @@ -504,7 +510,7 @@ static void rasterize_triangle( if (!(mask & (0x8 << (i * 4)))) continue; u8 color = colors[i]; if (!(alpha_test && color <= alpha_ref) && color != 0) { - prow[px + i] = color; + if (color_write) prow[px + i] = color; if (depth_write) zrow[px + i] = (u16)z16_arr[i]; } } @@ -516,11 +522,21 @@ static void rasterize_triangle( } } for (; px <= span_end; px++) { + u32 pixel = row_start + (u32)px; + + if (stencil_test && !depth_test_pass(stencil_compare, stencil_ref, sb[pixel])) { + u_a += du; v_a += dv; l_a += dl; c_a += dc; + z_a += dz; wx_a += dwx; wy_a += dwy; wz_a += dwz; + continue; + } + f32 depth_norm = pxl8_clamp((z_a + 1.0f) * 0.5f, 0.0f, 1.0f); u16 z16 = (u16)(depth_norm * 65535.0f); bool depth_pass = !depth_test || depth_test_pass(depth_compare, z16, zrow[px]); if (depth_pass) { + if (stencil_write) sb[pixel] = stencil_ref; + pxl8_shader_ctx frag_ctx = { .color_count = 1, .x = pxl8_i32_simd_set(px), @@ -540,10 +556,10 @@ static void rasterize_triangle( if (color != 0) { u8 out_color = color; if (blend_enabled) { - out_color = blend_indexed(pipeline, color, prow[px], palette); + out_color = blend_colors(pipeline, color, prow[px], palette); } - prow[px] = out_color; + if (color_write) prow[px] = out_color; if (depth_write) { zrow[px] = z16; } @@ -562,11 +578,21 @@ static void rasterize_triangle( } #else for (; px <= span_end; px++) { + u32 pixel = row_start + (u32)px; + + if (stencil_test && !depth_test_pass(stencil_compare, stencil_ref, sb[pixel])) { + u_a += du; v_a += dv; l_a += dl; c_a += dc; + z_a += dz; wx_a += dwx; wy_a += dwy; wz_a += dwz; + continue; + } + f32 depth_norm = pxl8_clamp((z_a + 1.0f) * 0.5f, 0.0f, 1.0f); u16 z16 = (u16)(depth_norm * 65535.0f); bool depth_pass = !depth_test || depth_test_pass(depth_compare, z16, zrow[px]); if (depth_pass) { + if (stencil_write) sb[pixel] = stencil_ref; + pxl8_shader_ctx frag_ctx = { .color_count = 1, .x = px, @@ -586,10 +612,10 @@ static void rasterize_triangle( if (color != 0) { u8 out_color = color; if (blend_enabled) { - out_color = blend_indexed(pipeline, color, prow[px], palette); + out_color = blend_colors(pipeline, color, prow[px], palette); } - prow[px] = out_color; + if (color_write) prow[px] = out_color; if (depth_write) { zrow[px] = z16; } @@ -739,6 +765,10 @@ static u32 pipeline_desc_hash(const pxl8_gfx_pipeline_desc* d) { h = (h ^ (u32)d->dither) * 16777619u; h = (h ^ (u32)d->double_sided) * 16777619u; h = (h ^ (u32)d->emissive) * 16777619u; + h = (h ^ (u32)d->stencil.test) * 16777619u; + h = (h ^ (u32)d->stencil.write) * 16777619u; + h = (h ^ (u32)d->stencil.compare) * 16777619u; + h = (h ^ (u32)d->stencil.ref) * 16777619u; h = (h ^ (u32)d->rasterizer.cull) * 16777619u; h = (h ^ (u32)d->rasterizer.fill) * 16777619u; u64 s = (u64)(uintptr_t)d->shader; @@ -767,6 +797,7 @@ typedef struct { struct pxl8_renderer { u32 width; u32 height; + u8* stencil; texture_slot textures[PXL8_GFX_MAX_TEXTURES]; buffer_slot buffers[PXL8_GFX_MAX_BUFFERS]; @@ -801,6 +832,7 @@ pxl8_renderer* pxl8_renderer_create(u32 width, u32 height) { pxl8_renderer* r = pxl8_calloc(1, sizeof(pxl8_renderer)); r->width = width; r->height = height; + r->stencil = pxl8_calloc(width * height, 1); r->viewport_w = width; r->viewport_h = height; r->scissor_w = width; @@ -816,21 +848,10 @@ void pxl8_renderer_destroy(pxl8_renderer* r) { for (u32 i = 0; i < PXL8_GFX_MAX_BUFFERS; i++) { if (r->buffers[i].data) pxl8_free(r->buffers[i].data); } + pxl8_free(r->stencil); pxl8_free(r); } -u32 pxl8_renderer_get_width(const pxl8_renderer* r) { - return r ? r->width : 0; -} - -u32 pxl8_renderer_get_height(const pxl8_renderer* r) { - return r ? r->height : 0; -} - -void pxl8_renderer_set_shader(pxl8_renderer* r, pxl8_shader_fn fn) { - if (r) r->shader = fn; -} - void pxl8_renderer_update_stats(pxl8_renderer* r, f32 dt) { if (!r) return; @@ -1070,16 +1091,6 @@ void pxl8_update_texture(pxl8_renderer* r, pxl8_gfx_texture tex, const pxl8_gfx_ } } -void* pxl8_buffer_ptr(pxl8_renderer* r, pxl8_gfx_buffer buf) { - if (!VALID_BUF(r, buf)) return NULL; - return r->buffers[SLOT_INDEX(buf.id)].data; -} - -u32 pxl8_buffer_size(pxl8_renderer* r, pxl8_gfx_buffer buf) { - if (!VALID_BUF(r, buf)) return 0; - return r->buffers[SLOT_INDEX(buf.id)].size; -} - void* pxl8_texture_get_data(pxl8_renderer* r, pxl8_gfx_texture tex) { if (!VALID_TEX(r, tex)) return NULL; return r->textures[SLOT_INDEX(tex.id)].data; @@ -1095,11 +1106,6 @@ u32 pxl8_texture_get_height(pxl8_renderer* r, pxl8_gfx_texture tex) { return r->textures[SLOT_INDEX(tex.id)].height; } -pxl8_gfx_texture_format pxl8_texture_get_format(pxl8_renderer* r, pxl8_gfx_texture tex) { - if (!VALID_TEX(r, tex)) return PXL8_GFX_FORMAT_INDEXED8; - return r->textures[SLOT_INDEX(tex.id)].format; -} - pxl8_gfx_cmdbuf* pxl8_cmdbuf_create(u32 capacity) { pxl8_gfx_cmdbuf* cb = pxl8_malloc(sizeof(pxl8_gfx_cmdbuf)); cb->commands = pxl8_malloc(capacity * sizeof(pxl8_gfx_cmd)); @@ -1402,7 +1408,7 @@ static void execute_draw( } u64 raster_start = pxl8_get_ticks_ns(); - rasterize_triangle(&setup, fb, zb, fb_w, shader, &pip->desc, + rasterize_triangle(&setup, fb, zb, r->stencil, fb_w, shader, &pip->desc, &shader_bindings, &shader_uniforms); r->stats.raster_ns += pxl8_get_ticks_ns() - raster_start; } @@ -1421,6 +1427,7 @@ void pxl8_gfx_submit(pxl8_renderer* r, pxl8_gfx_cmdbuf* cb) { if (p->desc.color.load == PXL8_GFX_LOAD_CLEAR) { pxl8_clear(r, p->desc.color.texture, p->desc.color.clear_value); pxl8_clear_depth(r, p->desc.depth.texture); + if (r->stencil) memset(r->stencil, 0, r->width * r->height); } } break; @@ -1484,6 +1491,12 @@ void pxl8_clear_depth(pxl8_renderer* r, pxl8_gfx_texture target) { } } +void pxl8_clear_stencil(pxl8_renderer* r, u8 value) { + if (!r || !r->stencil) return; + memset(r->stencil, value, r->width * r->height); +} + + void pxl8_draw_pixel(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y, u8 color) { if (!VALID_TEX(r, target)) return; texture_slot* s = &r->textures[SLOT_INDEX(target.id)]; diff --git a/src/gfx/pxl8_render.h b/src/gfx/pxl8_render.h index d8db971..a66bdcb 100644 --- a/src/gfx/pxl8_render.h +++ b/src/gfx/pxl8_render.h @@ -14,10 +14,6 @@ typedef struct pxl8_gfx_cmdbuf pxl8_gfx_cmdbuf; pxl8_renderer* pxl8_renderer_create(u32 width, u32 height); void pxl8_renderer_destroy(pxl8_renderer* r); -void pxl8_renderer_set_shader(pxl8_renderer* r, pxl8_shader_fn fn); - -u32 pxl8_renderer_get_width(const pxl8_renderer* r); -u32 pxl8_renderer_get_height(const pxl8_renderer* r); pxl8_gfx_bindings pxl8_create_bindings(pxl8_renderer* r, const pxl8_gfx_bindings_desc* desc); pxl8_gfx_buffer pxl8_create_buffer(pxl8_renderer* r, const pxl8_gfx_buffer_desc* desc); @@ -35,13 +31,9 @@ void pxl8_update_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf, const pxl8_gfx_ra i32 pxl8_append_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf, const pxl8_gfx_range* data); void pxl8_update_texture(pxl8_renderer* r, pxl8_gfx_texture tex, const pxl8_gfx_range* data, u32 x, u32 y, u32 w, u32 h); -void* pxl8_buffer_ptr(pxl8_renderer* r, pxl8_gfx_buffer buf); -u32 pxl8_buffer_size(pxl8_renderer* r, pxl8_gfx_buffer buf); - void* pxl8_texture_get_data(pxl8_renderer* r, pxl8_gfx_texture tex); u32 pxl8_texture_get_width(pxl8_renderer* r, pxl8_gfx_texture tex); u32 pxl8_texture_get_height(pxl8_renderer* r, pxl8_gfx_texture tex); -pxl8_gfx_texture_format pxl8_texture_get_format(pxl8_renderer* r, pxl8_gfx_texture tex); pxl8_gfx_cmdbuf* pxl8_cmdbuf_create(u32 capacity); void pxl8_cmdbuf_destroy(pxl8_gfx_cmdbuf* cb); @@ -61,6 +53,7 @@ void pxl8_gfx_submit(pxl8_renderer* r, pxl8_gfx_cmdbuf* cb); void pxl8_clear(pxl8_renderer* r, pxl8_gfx_texture target, u8 color); void pxl8_clear_depth(pxl8_renderer* r, pxl8_gfx_texture target); +void pxl8_clear_stencil(pxl8_renderer* r, u8 value); void pxl8_draw_pixel(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y, u8 color); u8 pxl8_get_pixel(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y); diff --git a/src/gfx/pxl8_render_types.h b/src/gfx/pxl8_render_types.h index 6790bc7..7bd6184 100644 --- a/src/gfx/pxl8_render_types.h +++ b/src/gfx/pxl8_render_types.h @@ -31,9 +31,6 @@ typedef struct pxl8_gfx_range { u32 size; } pxl8_gfx_range; -#define PXL8_GFX_RANGE(x) ((pxl8_gfx_range){ .ptr = &(x), .size = sizeof(x) }) -#define PXL8_GFX_RANGE_REF(p, sz) ((pxl8_gfx_range){ .ptr = (p), .size = (sz) }) - typedef enum pxl8_gfx_buffer_type { PXL8_GFX_BUFFER_VERTEX, PXL8_GFX_BUFFER_INDEX, @@ -87,11 +84,6 @@ typedef enum pxl8_gfx_load_op { PXL8_GFX_LOAD_DONT_CARE, } pxl8_gfx_load_op; -typedef enum pxl8_gfx_store_op { - PXL8_GFX_STORE_STORE, - PXL8_GFX_STORE_DONT_CARE, -} pxl8_gfx_store_op; - typedef struct pxl8_gfx_buffer_desc { pxl8_gfx_range data; u32 capacity; @@ -125,6 +117,14 @@ typedef struct pxl8_gfx_pipeline_desc { pxl8_gfx_compare_func compare; } depth; + struct { + bool test; + bool write; + pxl8_gfx_compare_func compare; + u8 ref; + } stencil; + + bool color_write; bool dither; bool double_sided; bool emissive; @@ -144,10 +144,19 @@ typedef struct pxl8_gfx_bindings_desc { u32 texture_id; } pxl8_gfx_bindings_desc; +typedef struct pxl8_gfx_draw_opts { + bool color_write; + pxl8_gfx_compare_func depth_compare; + bool depth_write; + bool stencil_test; + bool stencil_write; + u8 stencil_compare; + u8 stencil_ref; +} pxl8_gfx_draw_opts; + typedef struct pxl8_gfx_pass_color_attachment { pxl8_gfx_texture texture; pxl8_gfx_load_op load; - pxl8_gfx_store_op store; u8 clear_value; } pxl8_gfx_pass_color_attachment; diff --git a/src/gfx/pxl8_tilemap.c b/src/gfx/pxl8_tilemap.c index 263b65c..33ceef2 100644 --- a/src/gfx/pxl8_tilemap.c +++ b/src/gfx/pxl8_tilemap.c @@ -17,7 +17,6 @@ struct pxl8_tilesheet { u32 tiles_per_row; u32 total_tiles; u32 width; - pxl8_pixel_mode pixel_mode; u32 ref_count; pxl8_tile_animation* animations; u32 animation_count; @@ -547,7 +546,6 @@ pxl8_result pxl8_tilemap_load_ase(pxl8_tilemap* tilemap, const char* filepath, u tilemap->tilesheet->height = tilesheet_height; tilemap->tilesheet->tiles_per_row = tiles_per_row; tilemap->tilesheet->total_tiles = tileset->tile_count; - tilemap->tilesheet->pixel_mode = PXL8_PIXEL_INDEXED; if (tilemap->tilesheet->tile_valid) pxl8_free(tilemap->tilesheet->tile_valid); tilemap->tilesheet->tile_valid = pxl8_calloc(tileset->tile_count + 1, sizeof(bool)); diff --git a/src/gfx/pxl8_tilesheet.c b/src/gfx/pxl8_tilesheet.c index 9f41cd3..1782220 100644 --- a/src/gfx/pxl8_tilesheet.c +++ b/src/gfx/pxl8_tilesheet.c @@ -4,7 +4,6 @@ #include #include "pxl8_ase.h" -#include "pxl8_color.h" #include "pxl8_gfx.h" #include "pxl8_log.h" #include "pxl8_mem.h" @@ -20,7 +19,6 @@ struct pxl8_tilesheet { u32 total_tiles; u32 width; - pxl8_pixel_mode pixel_mode; u32 ref_count; pxl8_tile_animation* animations; @@ -106,14 +104,10 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, tilesheet->height = height; tilesheet->tiles_per_row = width / tilesheet->tile_size; tilesheet->total_tiles = (width / tilesheet->tile_size) * (height / tilesheet->tile_size); - tilesheet->pixel_mode = pxl8_gfx_get_pixel_mode(gfx); - u32 pixel_count = width * height; u16 ase_depth = ase_file.header.color_depth; - bool gfx_hicolor = (tilesheet->pixel_mode == PXL8_PIXEL_HICOLOR); - usize data_size = pixel_count * pxl8_bytes_per_pixel(tilesheet->pixel_mode); - tilesheet->data = pxl8_malloc(data_size); + tilesheet->data = pxl8_malloc(pixel_count); if (!tilesheet->data) { pxl8_ase_destroy(&ase_file); return PXL8_ERROR_OUT_OF_MEMORY; @@ -122,34 +116,10 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, if (ase_file.frame_count > 0 && ase_file.frames[0].pixels) { const u8* src = ase_file.frames[0].pixels; - if (ase_depth == 8 && !gfx_hicolor) { - memcpy(tilesheet->data, src, pixel_count); - } else if (ase_depth == 32 && gfx_hicolor) { - u16* dst = (u16*)tilesheet->data; - const u32* rgba = (const u32*)src; - for (u32 i = 0; i < pixel_count; i++) { - u32 c = rgba[i]; - u8 a = (c >> 24) & 0xFF; - if (a == 0) { - dst[i] = 0; - } else { - dst[i] = pxl8_rgba32_to_rgb565(c); - } - } - } else if (ase_depth == 8 && gfx_hicolor) { - pxl8_warn("Indexed ASE with hicolor gfx - storing as indexed"); - tilesheet->pixel_mode = PXL8_PIXEL_INDEXED; - u8* new_data = pxl8_realloc(tilesheet->data, pixel_count); - if (!new_data) { - pxl8_free(tilesheet->data); - tilesheet->data = NULL; - pxl8_ase_destroy(&ase_file); - return PXL8_ERROR_OUT_OF_MEMORY; - } - tilesheet->data = new_data; + if (ase_depth == 8) { memcpy(tilesheet->data, src, pixel_count); } else { - pxl8_error("Unsupported ASE color depth %d for gfx mode", ase_depth); + pxl8_error("Unsupported ASE color depth %d (expected indexed 8-bit)", ase_depth); pxl8_free(tilesheet->data); tilesheet->data = NULL; pxl8_ase_destroy(&ase_file); @@ -166,7 +136,6 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, } u32 valid_tiles = 0; - bool is_hicolor = (tilesheet->pixel_mode == PXL8_PIXEL_HICOLOR); for (u32 tile_id = 1; tile_id <= tilesheet->total_tiles; tile_id++) { u32 tile_x = ((tile_id - 1) % tilesheet->tiles_per_row) * tilesheet->tile_size; @@ -176,16 +145,9 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, for (u32 py = 0; py < tilesheet->tile_size && !has_content; py++) { for (u32 px = 0; px < tilesheet->tile_size; px++) { u32 idx = (tile_y + py) * width + (tile_x + px); - if (is_hicolor) { - if (((u16*)tilesheet->data)[idx] != 0) { - has_content = true; - break; - } - } else { - if (tilesheet->data[idx] != 0) { - has_content = true; - break; - } + if (tilesheet->data[idx] != 0) { + has_content = true; + break; } } } @@ -312,7 +274,6 @@ pxl8_result pxl8_tilesheet_set_tile_pixels(pxl8_tilesheet* tilesheet, u16 tile_i u32 tile_x = (tile_id - 1) % tilesheet->tiles_per_row; u32 tile_y = (tile_id - 1) / tilesheet->tiles_per_row; - u32 bytes_per_pixel = pxl8_bytes_per_pixel(tilesheet->pixel_mode); for (u32 py = 0; py < tilesheet->tile_size; py++) { for (u32 px = 0; px < tilesheet->tile_size; px++) { @@ -320,12 +281,7 @@ pxl8_result pxl8_tilesheet_set_tile_pixels(pxl8_tilesheet* tilesheet, u16 tile_i u32 dst_x = tile_x * tilesheet->tile_size + px; u32 dst_y = tile_y * tilesheet->tile_size + py; u32 dst_idx = dst_y * tilesheet->width + dst_x; - - if (bytes_per_pixel == 2) { - ((u16*)tilesheet->data)[dst_idx] = ((const u16*)pixels)[src_idx]; - } else { - tilesheet->data[dst_idx] = pixels[src_idx]; - } + tilesheet->data[dst_idx] = pixels[src_idx]; } } diff --git a/src/gfx/pxl8_transition.c b/src/gfx/pxl8_transition.c index ba76e1d..5c16266 100644 --- a/src/gfx/pxl8_transition.c +++ b/src/gfx/pxl8_transition.c @@ -170,11 +170,7 @@ void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx) { i32 block_size = (i32)(max_block_size * progress); if (block_size < 1) block_size = 1; - pxl8_pixel_mode mode = pxl8_gfx_get_pixel_mode(gfx); - bool has_fb = (mode == PXL8_PIXEL_HICOLOR) - ? (pxl8_gfx_get_framebuffer_hicolor(gfx) != NULL) - : (pxl8_gfx_get_framebuffer_indexed(gfx) != NULL); - if (!has_fb) break; + if (!pxl8_gfx_framebuffer(gfx)) break; for (i32 y = 0; y < height; y += block_size) { for (i32 x = 0; x < width; x += block_size) { diff --git a/src/gui/pxl8_gui.c b/src/gui/pxl8_gui.c index 26dd4d8..dfdeada 100644 --- a/src/gui/pxl8_gui.c +++ b/src/gui/pxl8_gui.c @@ -173,6 +173,22 @@ bool pxl8_gui_slider_int(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i3 return changed; } +i32 pxl8_gui_toolbar(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id, + i32 x, i32 y, i32 btn_w, i32 btn_h, + const char** labels, i32 count, i32 selected) { + i32 result = -1; + for (i32 i = 0; i < count; i++) { + i32 bx = x + i * (btn_w + 2); + bool clicked = pxl8_gui_button(state, gfx, base_id + (u32)i, bx, y, btn_w, btn_h, labels[i]); + if (clicked) result = i; + if (i == selected) { + u8 sel_color = pxl8_gui_color(gfx, PXL8_UI_FG0); + pxl8_2d_rect(gfx, bx, y + btn_h - 2, btn_w, 2, sel_color); + } + } + return result; +} + void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title) { if (!gfx || !title) return; @@ -206,3 +222,63 @@ void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y) { if (x) *x = state->cursor_x; if (y) *y = state->cursor_y; } + +i32 pxl8_gui_grid_select(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id, + i32 x, i32 y, i32 cell_w, i32 cell_h, + i32 cols, i32 rows, const u8* colors, i32 selected) { + i32 result = -1; + for (i32 r = 0; r < rows; r++) { + for (i32 c = 0; c < cols; c++) { + i32 cx = x + c * (cell_w + 1); + i32 cy = y + r * (cell_h + 1); + i32 idx = r * cols + c; + u32 id = base_id + (u32)idx; + bool over = is_cursor_over(state, cx, cy, cell_w, cell_h); + if (over) state->hot_id = id; + if (over && state->cursor_down && state->active_id == 0) + state->active_id = id; + bool clicked = (state->active_id == id) && state->cursor_clicked && over; + if (clicked) { result = idx; state->active_id = 0; } + pxl8_2d_rect_fill(gfx, cx, cy, cell_w, cell_h, colors[idx]); + if (idx == selected) { + u8 sel = pxl8_gui_color(gfx, PXL8_UI_FG0); + pxl8_2d_rect(gfx, cx - 1, cy - 1, cell_w + 2, cell_h + 2, sel); + } else if (over) { + u8 hov = pxl8_gui_color(gfx, PXL8_UI_BG3); + pxl8_2d_rect(gfx, cx, cy, cell_w, cell_h, hov); + } + } + } + return result; +} + +void pxl8_gui_image(pxl8_gfx* gfx, u32 texture_id, i32 sx, i32 sy, i32 sw, i32 sh, + i32 dx, i32 dy, i32 dw, i32 dh) { + pxl8_2d_sprite(gfx, texture_id, dx, dy, dw, dh, false, false); + (void)sx; (void)sy; (void)sw; (void)sh; +} + +void pxl8_gui_panel(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h) { + u8 bg = pxl8_gui_color(gfx, PXL8_UI_BG1); + u8 border = pxl8_gui_color(gfx, PXL8_UI_BG3); + pxl8_2d_rect_fill(gfx, x, y, w, h, bg); + pxl8_2d_rect(gfx, x, y, w, h, border); +} + +void pxl8_gui_status_bar(pxl8_gfx* gfx, i32 y, i32 screen_w, i32 h, const char* text) { + u8 bg = pxl8_gui_color(gfx, PXL8_UI_BG1); + u8 fg = pxl8_gui_color(gfx, PXL8_UI_FG0); + pxl8_2d_rect_fill(gfx, 0, y, screen_w, h, bg); + pxl8_2d_text(gfx, text, 4, y + (h / 2) - 5, fg); +} + +bool pxl8_gui_toggle(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, + i32 x, i32 y, i32 w, i32 h, const char* label, bool active) { + bool clicked = pxl8_gui_button(state, gfx, id, x, y, w, h, label); + if (active) { + u8 sel = pxl8_gui_color(gfx, PXL8_UI_FG0); + pxl8_2d_rect(gfx, x, y, w, 2, sel); + pxl8_2d_rect(gfx, x, y + h - 2, w, 2, sel); + } + return clicked ? !active : active; +} diff --git a/src/gui/pxl8_gui.h b/src/gui/pxl8_gui.h index 9dcf770..4d1180f 100644 --- a/src/gui/pxl8_gui.h +++ b/src/gui/pxl8_gui.h @@ -34,8 +34,25 @@ u8 pxl8_gui_color(pxl8_gfx* gfx, u8 index); void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color); bool pxl8_gui_slider(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, f32* value, f32 min_val, f32 max_val); bool pxl8_gui_slider_int(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, i32* value, i32 min_val, i32 max_val); +i32 pxl8_gui_toolbar(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id, + i32 x, i32 y, i32 btn_w, i32 btn_h, + const char** labels, i32 count, i32 selected); void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title); +i32 pxl8_gui_grid_select(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id, + i32 x, i32 y, i32 cell_w, i32 cell_h, + i32 cols, i32 rows, const u8* colors, i32 selected); + +void pxl8_gui_image(pxl8_gfx* gfx, u32 texture_id, i32 sx, i32 sy, i32 sw, i32 sh, + i32 dx, i32 dy, i32 dw, i32 dh); + +void pxl8_gui_panel(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h); + +void pxl8_gui_status_bar(pxl8_gfx* gfx, i32 y, i32 screen_w, i32 h, const char* text); + +bool pxl8_gui_toggle(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, + i32 x, i32 y, i32 w, i32 h, const char* label, bool active); + #ifdef __cplusplus } #endif diff --git a/src/hal/pxl8_hal_sdl3.c b/src/hal/pxl8_hal_sdl3.c index 581b257..58b8d33 100644 --- a/src/hal/pxl8_hal_sdl3.c +++ b/src/hal/pxl8_hal_sdl3.c @@ -125,16 +125,9 @@ static void sdl3_upload_texture(void* platform_data, const void* pixels, u32 w, ctx->rgba_buffer_size = pixel_count; } - if (bpp == 2) { - const u16* pixels16 = (const u16*)pixels; - for (u32 i = 0; i < pixel_count; i++) { - ctx->rgba_buffer[i] = pxl8_rgb565_to_rgba32(pixels16[i]); - } - } else { - const u8* pixels8 = (const u8*)pixels; - for (u32 i = 0; i < pixel_count; i++) { - ctx->rgba_buffer[i] = palette[pixels8[i]]; - } + const u8* pixels8 = (const u8*)pixels; + for (u32 i = 0; i < pixel_count; i++) { + ctx->rgba_buffer[i] = palette[pixels8[i]]; } SDL_UpdateTexture(ctx->framebuffer, NULL, ctx->rgba_buffer, w * 4); diff --git a/src/hal/pxl8_io_sdl3.c b/src/hal/pxl8_io_sdl3.c new file mode 100644 index 0000000..d82efc0 --- /dev/null +++ b/src/hal/pxl8_io_sdl3.c @@ -0,0 +1,125 @@ +#include "pxl8_io.h" +#include "pxl8_mem.h" + +#include + +#ifndef _WIN32 +#include +#include +#else +#include +#endif + +#include + +pxl8_result pxl8_io_create_directory(const char* path) { + if (!path) return PXL8_ERROR_NULL_POINTER; + return SDL_CreateDirectory(path) ? PXL8_OK : PXL8_ERROR_SYSTEM_FAILURE; +} + +typedef struct { + pxl8_io_dir_callback callback; + void* userdata; +} pxl8_io_enum_ctx; + +static SDL_EnumerationResult pxl8_io_enum_adapter(void* userdata, const char* dirname, const char* fname) { + pxl8_io_enum_ctx* ctx = userdata; + if (ctx->callback(ctx->userdata, dirname, fname)) { + return SDL_ENUM_CONTINUE; + } + return SDL_ENUM_SUCCESS; +} + +bool pxl8_io_enumerate_directory(const char* path, pxl8_io_dir_callback callback, void* userdata) { + if (!path || !callback) return false; + pxl8_io_enum_ctx ctx = { .callback = callback, .userdata = userdata }; + return SDL_EnumerateDirectory(path, pxl8_io_enum_adapter, &ctx); +} + +bool pxl8_io_file_exists(const char* path) { + if (!path) return false; + return SDL_GetPathInfo(path, NULL); +} + +char* pxl8_io_get_cwd(void) { + char* sdl_cwd = SDL_GetCurrentDirectory(); + if (!sdl_cwd) return NULL; + char* cwd = pxl8_malloc(strlen(sdl_cwd) + 1); + if (cwd) strcpy(cwd, sdl_cwd); + SDL_free(sdl_cwd); + return cwd; +} + +f64 pxl8_io_get_file_modified_time(const char* path) { + if (!path) return 0.0; + SDL_PathInfo info; + if (SDL_GetPathInfo(path, &info)) { + return (f64)info.modify_time / 1000000000.0; + } + return 0.0; +} + +usize pxl8_io_get_file_size(const char* path) { + if (!path) return 0; + SDL_PathInfo info; + if (SDL_GetPathInfo(path, &info)) return (usize)info.size; + return 0; +} + +char* pxl8_io_get_pref_path(const char* org, const char* app) { + char* sdl_path = SDL_GetPrefPath(org, app); + if (!sdl_path) return NULL; + char* path = pxl8_malloc(strlen(sdl_path) + 1); + if (path) strcpy(path, sdl_path); + SDL_free(sdl_path); + return path; +} + +char* pxl8_io_get_real_path(const char* path) { + if (!path) return NULL; + + if (path[0] == '/') { + char* result = pxl8_malloc(strlen(path) + 1); + if (result) strcpy(result, path); + return result; + } + + char* cwd = SDL_GetCurrentDirectory(); + if (!cwd) return NULL; + + usize cwd_len = strlen(cwd); + usize path_len = strlen(path); + char* result = pxl8_malloc(cwd_len + path_len + 2); + if (result) { + strcpy(result, cwd); + if (cwd_len > 0 && cwd[cwd_len - 1] != '/') { + strcat(result, "/"); + } + strcat(result, path); + } + + SDL_free(cwd); + return result; +} + +bool pxl8_io_set_cwd(const char* path) { + if (!path) return false; +#ifdef _WIN32 + return _chdir(path) == 0; +#else + return chdir(path) == 0; +#endif +} + +void pxl8_io_set_executable(const char* path) { + if (!path) return; +#ifndef _WIN32 + chmod(path, 0755); +#endif +} + +bool pxl8_io_is_directory(const char* path) { + if (!path) return false; + SDL_PathInfo info; + return SDL_GetPathInfo(path, &info) && info.type == SDL_PATHTYPE_DIRECTORY; +} diff --git a/src/lua/pxl8.lua b/src/lua/pxl8.lua index e7cc4dd..079453d 100644 --- a/src/lua/pxl8.lua +++ b/src/lua/pxl8.lua @@ -94,6 +94,7 @@ pxl8.Mesh = gfx.Mesh pxl8.begin_frame_3d = gfx.begin_frame_3d pxl8.clear_3d = gfx.clear_3d pxl8.clear_depth = gfx.clear_depth +pxl8.clear_stencil = gfx.clear_stencil pxl8.create_camera_3d = gfx.Camera3D.new pxl8.create_material = gfx.create_material pxl8.create_mesh = gfx.Mesh.new @@ -174,8 +175,6 @@ pxl8.create_particles = particles.Particles.new pxl8.Graph = procgen.Graph pxl8.create_graph = procgen.create_graph -pxl8.PROCGEN_ROOMS = world.PROCGEN_ROOMS -pxl8.PROCGEN_TERRAIN = world.PROCGEN_TERRAIN pxl8.SfxContext = sfx.SfxContext pxl8.SfxNode = sfx.SfxNode @@ -234,9 +233,26 @@ 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.DEPTH_NEVER = 0 +pxl8.DEPTH_LESS = 1 +pxl8.DEPTH_EQUAL = 2 +pxl8.DEPTH_LEQUAL = 3 +pxl8.DEPTH_GREATER = 4 +pxl8.DEPTH_NOTEQUAL = 5 +pxl8.DEPTH_GEQUAL = 6 +pxl8.DEPTH_ALWAYS = 7 + +pxl8.STENCIL_NEVER = 0 +pxl8.STENCIL_LESS = 1 +pxl8.STENCIL_EQUAL = 2 +pxl8.STENCIL_LEQUAL = 3 +pxl8.STENCIL_GREATER = 4 +pxl8.STENCIL_NOTEQUAL = 5 +pxl8.STENCIL_GEQUAL = 6 +pxl8.STENCIL_ALWAYS = 7 + pxl8.Bsp = world.Bsp pxl8.Chunk = world.Chunk -pxl8.CHUNK_BSP = world.CHUNK_BSP pxl8.World = world.World pxl8.get_world = world.World.get pxl8.sim_config = world.sim_config diff --git a/src/lua/pxl8/gfx.lua b/src/lua/pxl8/gfx.lua index 64cbf87..b4f9280 100644 --- a/src/lua/pxl8/gfx.lua +++ b/src/lua/pxl8/gfx.lua @@ -230,6 +230,28 @@ gfx.Camera3D = Camera3D gfx.Mesh = Mesh +local Material = {} +Material.__index = Material + +function Material.new(opts) + opts = opts or {} + local mat = ffi.new("pxl8_gfx_material", { + alpha = opts.alpha or 255, + blend_mode = opts.blend_mode or 0, + dither = opts.dither ~= false, + double_sided = opts.double_sided or false, + dynamic_lighting = opts.lighting or false, + emissive = opts.emissive or false, + per_pixel = opts.per_pixel or false, + texture_id = opts.texture or 0xFFFFFFFF, + }) + return setmetatable({ _ptr = mat }, Material) +end + +gfx.Material = Material + +gfx.create_material = Material.new + function gfx.draw_mesh(mesh, opts) if not mesh or not mesh._ptr then return @@ -249,17 +271,21 @@ function gfx.draw_mesh(mesh, opts) if opts.y then model.m[13] = opts.y end if opts.z then model.m[14] = opts.z end end - local material = ffi.new("pxl8_gfx_material", { - alpha = opts.alpha or 255, - blend_mode = opts.blend_mode or 0, - dither = opts.dither ~= false, - double_sided = opts.double_sided or false, - dynamic_lighting = opts.lighting or false, - emissive = opts.emissive or false, - per_pixel = opts.per_pixel or false, - texture_id = opts.texture or 0xFFFFFFFF, - }) - C.pxl8_3d_draw_mesh(core.gfx, mesh._ptr, model, material) + local material = Material.new(opts)._ptr + local draw_opts = nil + if opts.color_write ~= nil or opts.depth_compare or opts.depth_write ~= nil + or opts.stencil_test or opts.stencil_write then + draw_opts = ffi.new("pxl8_gfx_draw_opts", { + color_write = opts.color_write ~= false, + depth_compare = opts.depth_compare or 1, + depth_write = opts.depth_write ~= false, + stencil_test = opts.stencil_test or false, + stencil_write = opts.stencil_write or false, + stencil_compare = opts.stencil_compare or 0, + stencil_ref = opts.stencil_ref or 0, + }) + end + C.pxl8_3d_draw_mesh(core.gfx, mesh._ptr, model, material, draw_opts) end function gfx.begin_frame_3d(camera, lights, uniforms) @@ -301,6 +327,10 @@ function gfx.clear_depth() C.pxl8_3d_clear_depth(core.gfx) end +function gfx.clear_stencil(value) + C.pxl8_3d_clear_stencil(core.gfx, value or 0) +end + function gfx.draw_line_3d(p0, p1, color) local vec0 = ffi.new("pxl8_vec3", {x = p0[1], y = p0[2], z = p0[3]}) local vec1 = ffi.new("pxl8_vec3", {x = p1[1], y = p1[2], z = p1[3]}) @@ -327,26 +357,4 @@ function gfx.get_wireframe() return C.pxl8_gfx_get_wireframe(core.gfx) end -local Material = {} -Material.__index = Material - -function Material.new(opts) - opts = opts or {} - local mat = ffi.new("pxl8_gfx_material", { - alpha = opts.alpha or 255, - blend_mode = opts.blend_mode or 0, - dither = opts.dither ~= false, - double_sided = opts.double_sided or false, - dynamic_lighting = opts.lighting or false, - emissive = opts.emissive or false, - per_pixel = opts.per_pixel or false, - texture_id = opts.texture or 0xFFFFFFFF, - }) - return setmetatable({ _ptr = mat }, Material) -end - -gfx.Material = Material - -gfx.create_material = Material.new - return gfx diff --git a/src/lua/pxl8/net.lua b/src/lua/pxl8/net.lua index 5d4b17e..36a3807 100644 --- a/src/lua/pxl8/net.lua +++ b/src/lua/pxl8/net.lua @@ -15,18 +15,22 @@ function net.get() return setmetatable({ _ptr = ptr }, Net) end -function Net:chunk_id() - return C.pxl8_net_chunk_id(self._ptr) +function Net:chunk_cx() + return C.pxl8_net_chunk_cx(self._ptr) +end + +function Net:chunk_cz() + return C.pxl8_net_chunk_cz(self._ptr) +end + +function Net:has_chunk() + return C.pxl8_net_has_chunk(self._ptr) end function Net:connected() return C.pxl8_net_connected(self._ptr) end -function Net:enter_scene(chunk_type, chunk_id, x, y, z) - return C.pxl8_net_enter_scene(self._ptr, chunk_type or 0, chunk_id or 0, x or 0, y or 0, z or 0) == 0 -end - function Net:send_input(input) local msg = ffi.new("pxl8_input_msg") msg.buttons = input.buttons or 0 diff --git a/src/lua/pxl8/world.lua b/src/lua/pxl8/world.lua index 152ef84..f2eab50 100644 --- a/src/lua/pxl8/world.lua +++ b/src/lua/pxl8/world.lua @@ -64,6 +64,10 @@ function World:init_local_player(x, y, z) C.pxl8_world_init_local_player(self._ptr, x, y, z) end +function World:set_look(yaw, pitch) + C.pxl8_world_set_look(self._ptr, yaw, pitch or 0) +end + function World:local_player() local ptr = C.pxl8_world_local_player(self._ptr) if ptr == nil then return nil end diff --git a/src/math/pxl8_math.h b/src/math/pxl8_math.h index 23f5c3d..0d5553a 100644 --- a/src/math/pxl8_math.h +++ b/src/math/pxl8_math.h @@ -74,6 +74,10 @@ typedef struct pxl8_plane { pxl8_vec3 normal; } pxl8_plane; +typedef struct pxl8_rect { + f32 x0, y0, x1, y1; +} pxl8_rect; + typedef struct pxl8_frustum { pxl8_plane planes[6]; } pxl8_frustum; diff --git a/src/net/pxl8_net.c b/src/net/pxl8_net.c index 52681d9..b60ac4c 100644 --- a/src/net/pxl8_net.c +++ b/src/net/pxl8_net.c @@ -46,17 +46,15 @@ struct pxl8_net { socket_t sock; pxl8_world_chunk_cache* chunk_cache; - u32 chunk_id; - u8 chunk_type; + i32 chunk_cx; + i32 chunk_cz; + bool has_chunk; pxl8_world* world; - f32 dt; u64 highest_tick; f32 interp_time; - u32 sequence; pxl8_entity_state entities[PXL8_MAX_SNAPSHOT_ENTITIES]; - pxl8_event_msg events[PXL8_MAX_SNAPSHOT_EVENTS]; pxl8_entity_state prev_entities[PXL8_MAX_SNAPSHOT_ENTITIES]; pxl8_snapshot_header prev_snapshot; pxl8_snapshot_header snapshot; @@ -109,6 +107,9 @@ pxl8_result pxl8_net_connect(pxl8_net* net) { fcntl(net->sock, F_SETFL, flags | O_NONBLOCK); #endif + int rcvbuf = 1024 * 1024; + setsockopt(net->sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)); + memset(&net->server_addr, 0, sizeof(net->server_addr)); #ifdef __APPLE__ net->server_addr.sin_len = sizeof(net->server_addr); @@ -131,9 +132,6 @@ pxl8_net* pxl8_net_create(const pxl8_net_config* config) { 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); @@ -179,11 +177,6 @@ const u8* pxl8_net_entity_userdata(const pxl8_net* net, u64 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++) { @@ -231,22 +224,19 @@ u64 pxl8_net_player_id(const pxl8_net* net) { return net->snapshot.player_id; } -bool pxl8_net_poll(pxl8_net* net) { - if (!net || !net->connected) return false; - - usize len = pxl8_net_recv(net, net->recv_buf, sizeof(net->recv_buf)); +static bool dispatch_message(pxl8_net* net, const u8* data, usize len) { if (len < sizeof(pxl8_msg_header)) return false; pxl8_msg_header hdr; - usize offset = pxl8_protocol_deserialize_header(net->recv_buf, len, &hdr); + usize offset = pxl8_protocol_deserialize_header(data, len, &hdr); if (hdr.type == PXL8_MSG_CHUNK) { if (!net->chunk_cache) return false; pxl8_chunk_msg_header chunk_hdr; - offset += pxl8_protocol_deserialize_chunk_msg_header(net->recv_buf + offset, len - offset, &chunk_hdr); + offset += pxl8_protocol_deserialize_chunk_msg_header(data + offset, len - offset, &chunk_hdr); - const u8* payload = net->recv_buf + offset; + const u8* payload = data + offset; usize payload_len = chunk_hdr.payload_size; if (payload_len > len - offset) { payload_len = len - offset; @@ -258,23 +248,23 @@ bool pxl8_net_poll(pxl8_net* net) { if (hdr.type == PXL8_MSG_CHUNK_ENTER) { pxl8_chunk_enter_msg chunk_msg; - pxl8_protocol_deserialize_chunk_enter(net->recv_buf + offset, len - offset, &chunk_msg); - net->chunk_id = chunk_msg.chunk_id; - net->chunk_type = chunk_msg.chunk_type; - pxl8_debug("[CLIENT] Received CHUNK_ENTER type=%u id=%u", chunk_msg.chunk_type, chunk_msg.chunk_id); + pxl8_protocol_deserialize_chunk_enter(data + offset, len - offset, &chunk_msg); + net->chunk_cx = chunk_msg.cx; + net->chunk_cz = chunk_msg.cz; + net->has_chunk = true; + pxl8_debug("[CLIENT] Received CHUNK_ENTER cx=%d cz=%d", chunk_msg.cx, chunk_msg.cz); return true; } if (hdr.type == PXL8_MSG_CHUNK_EXIT) { - net->chunk_id = 0; - net->chunk_type = 0; + net->has_chunk = false; return true; } 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); + offset += pxl8_protocol_deserialize_snapshot_header(data + offset, len - offset, &snap); if (snap.tick <= net->highest_tick) return false; @@ -290,10 +280,20 @@ bool pxl8_net_poll(pxl8_net* net) { for (u16 i = 0; i < count; i++) { offset += pxl8_protocol_deserialize_entity_state( - net->recv_buf + offset, len - offset, &net->entities[i]); + data + offset, len - offset, &net->entities[i]); } - if (net->world) { + return true; +} + +bool pxl8_net_poll(pxl8_net* net) { + if (!net || !net->connected) return false; + + usize len = pxl8_net_recv(net, net->recv_buf, sizeof(net->recv_buf)); + u64 prev_tick = net->highest_tick; + if (!dispatch_message(net, net->recv_buf, len)) return false; + + if (net->highest_tick > prev_tick && net->world) { pxl8_world_reconcile(net->world, net, 1.0f / 30.0f); } @@ -377,7 +377,6 @@ u64 pxl8_net_tick(const pxl8_net* net) { void pxl8_net_update(pxl8_net* net, f32 dt) { if (!net) return; - net->dt = dt; net->interp_time += dt; } @@ -396,14 +395,19 @@ void pxl8_net_set_world(pxl8_net* net, pxl8_world* world) { net->world = world; } -u32 pxl8_net_chunk_id(const pxl8_net* net) { +i32 pxl8_net_chunk_cx(const pxl8_net* net) { if (!net) return 0; - return net->chunk_id; + return net->chunk_cx; } -u8 pxl8_net_chunk_type(const pxl8_net* net) { - if (!net) return PXL8_CHUNK_TYPE_BSP; - return net->chunk_type; +i32 pxl8_net_chunk_cz(const pxl8_net* net) { + if (!net) return 0; + return net->chunk_cz; +} + +bool pxl8_net_has_chunk(const pxl8_net* net) { + if (!net) return false; + return net->has_chunk; } pxl8_result pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch) { @@ -422,22 +426,6 @@ pxl8_result pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitc return pxl8_net_send_command(net, &cmd); } -pxl8_result pxl8_net_enter_scene(pxl8_net* net, u8 chunk_type, u32 chunk_id, f32 x, f32 y, f32 z) { - if (!net) return PXL8_ERROR_NULL_POINTER; - if (!net->connected) return PXL8_ERROR_NOT_CONNECTED; - - pxl8_command_msg cmd = {0}; - cmd.cmd_type = PXL8_CMD_ENTER_SCENE; - pxl8_pack_u32_be(cmd.payload, 0, chunk_id); - cmd.payload[4] = chunk_type; - pxl8_pack_f32_be(cmd.payload, 8, x); - pxl8_pack_f32_be(cmd.payload, 12, y); - pxl8_pack_f32_be(cmd.payload, 16, z); - cmd.payload_size = 20; - - return pxl8_net_send_command(net, &cmd); -} - #ifdef PXL8_ASYNC_THREADS static int pxl8_net_recv_thread(void* data) { @@ -504,64 +492,8 @@ void pxl8_net_packet_free(pxl8_packet* pkt) { } bool pxl8_net_process_packet(pxl8_net* net, const pxl8_packet* pkt) { - if (!net || !pkt || pkt->len < sizeof(pxl8_msg_header)) return false; - - pxl8_msg_header hdr; - usize offset = pxl8_protocol_deserialize_header(pkt->data, pkt->len, &hdr); - - if (hdr.type == PXL8_MSG_CHUNK) { - if (!net->chunk_cache) return false; - - pxl8_chunk_msg_header chunk_hdr; - offset += pxl8_protocol_deserialize_chunk_msg_header(pkt->data + offset, pkt->len - offset, &chunk_hdr); - - const u8* payload = pkt->data + offset; - usize payload_len = chunk_hdr.payload_size; - if (payload_len > pkt->len - offset) { - payload_len = pkt->len - offset; - } - - pxl8_world_chunk_cache_receive(net->chunk_cache, &chunk_hdr, payload, payload_len); - return true; - } - - if (hdr.type == PXL8_MSG_CHUNK_ENTER) { - pxl8_chunk_enter_msg chunk_msg; - pxl8_protocol_deserialize_chunk_enter(pkt->data + offset, pkt->len - offset, &chunk_msg); - net->chunk_id = chunk_msg.chunk_id; - net->chunk_type = chunk_msg.chunk_type; - return true; - } - - if (hdr.type == PXL8_MSG_CHUNK_EXIT) { - net->chunk_id = 0; - net->chunk_type = 0; - return true; - } - - if (hdr.type != PXL8_MSG_SNAPSHOT) return false; - - pxl8_snapshot_header snap; - offset += pxl8_protocol_deserialize_snapshot_header(pkt->data + offset, pkt->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( - pkt->data + offset, pkt->len - offset, &net->entities[i]); - } - - return true; + if (!net || !pkt) return false; + return dispatch_message(net, pkt->data, pkt->len); } #endif diff --git a/src/net/pxl8_net.h b/src/net/pxl8_net.h index 770c84e..d470441 100644 --- a/src/net/pxl8_net.h +++ b/src/net/pxl8_net.h @@ -33,7 +33,6 @@ 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); @@ -54,11 +53,11 @@ void pxl8_net_set_chunk_cache(pxl8_net* net, pxl8_world_chunk_cache* cache); pxl8_world_chunk_cache* pxl8_net_chunk_cache(pxl8_net* net); void pxl8_net_set_world(pxl8_net* net, pxl8_world* world); -u32 pxl8_net_chunk_id(const pxl8_net* net); -u8 pxl8_net_chunk_type(const pxl8_net* net); +i32 pxl8_net_chunk_cx(const pxl8_net* net); +i32 pxl8_net_chunk_cz(const pxl8_net* net); +bool pxl8_net_has_chunk(const pxl8_net* net); pxl8_result pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch); -pxl8_result pxl8_net_enter_scene(pxl8_net* net, u8 chunk_type, u32 chunk_id, f32 x, f32 y, f32 z); #ifdef PXL8_ASYNC_THREADS void pxl8_net_start_thread(pxl8_net* net); diff --git a/src/net/pxl8_protocol.c b/src/net/pxl8_protocol.c index 3a3e4ab..5a29fe8 100644 --- a/src/net/pxl8_protocol.c +++ b/src/net/pxl8_protocol.c @@ -158,7 +158,7 @@ usize pxl8_protocol_deserialize_chunk_msg_header(const u8* buf, usize len, pxl8_ } usize pxl8_protocol_deserialize_bsp_wire_header(const u8* buf, usize len, pxl8_bsp_wire_header* hdr) { - if (len < 44) return 0; + if (len < 48) return 0; pxl8_stream s = pxl8_stream_create(buf, (u32)len); hdr->num_vertices = pxl8_read_u32_be(&s); hdr->num_edges = pxl8_read_u32_be(&s); @@ -171,27 +171,27 @@ usize pxl8_protocol_deserialize_bsp_wire_header(const u8* buf, usize len, pxl8_b hdr->num_cell_portals = pxl8_read_u32_be(&s); hdr->visdata_size = pxl8_read_u32_be(&s); hdr->num_vertex_lights = pxl8_read_u32_be(&s); + hdr->num_heightfield = pxl8_read_u32_be(&s); return s.offset; } usize pxl8_protocol_serialize_chunk_enter(const pxl8_chunk_enter_msg* msg, u8* buf, usize len) { if (len < 8) return 0; pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len); - pxl8_write_u32_be(&s, msg->chunk_id); - pxl8_write_u8(&s, msg->chunk_type); - pxl8_write_u8(&s, msg->reserved[0]); - pxl8_write_u8(&s, msg->reserved[1]); - pxl8_write_u8(&s, msg->reserved[2]); + pxl8_write_u32_be(&s, (u32)msg->cx); + pxl8_write_u32_be(&s, (u32)msg->cz); return s.offset; } usize pxl8_protocol_deserialize_chunk_enter(const u8* buf, usize len, pxl8_chunk_enter_msg* msg) { if (len < 8) return 0; pxl8_stream s = pxl8_stream_create(buf, (u32)len); - msg->chunk_id = pxl8_read_u32_be(&s); - msg->chunk_type = pxl8_read_u8(&s); - msg->reserved[0] = pxl8_read_u8(&s); - msg->reserved[1] = pxl8_read_u8(&s); - msg->reserved[2] = pxl8_read_u8(&s); + msg->cx = (i32)pxl8_read_u32_be(&s); + msg->cz = (i32)pxl8_read_u32_be(&s); return s.offset; } + +u32 pxl8_chunk_hash(i32 cx, i32 cz) { + u32 h = (u32)cx * 374761393u + (u32)cz * 668265263u; + return h ^ (h >> 16); +} diff --git a/src/net/pxl8_protocol.h b/src/net/pxl8_protocol.h index fd3cc09..182c846 100644 --- a/src/net/pxl8_protocol.h +++ b/src/net/pxl8_protocol.h @@ -35,7 +35,6 @@ typedef struct pxl8_msg_header { typedef enum pxl8_cmd_type { PXL8_CMD_NONE = 0, PXL8_CMD_SPAWN_ENTITY, - PXL8_CMD_ENTER_SCENE, } pxl8_cmd_type; typedef struct pxl8_input_msg { @@ -76,7 +75,6 @@ typedef struct pxl8_snapshot_header { #define PXL8_CHUNK_TYPE_BSP 1 -#define PXL8_CHUNK_FLAG_RLE 0x01 #define PXL8_CHUNK_FLAG_FINAL 0x04 #define PXL8_CHUNK_MAX_PAYLOAD 1400 @@ -93,17 +91,18 @@ typedef struct pxl8_chunk_msg_header { } pxl8_chunk_msg_header; typedef struct pxl8_bsp_wire_header { - u32 num_cell_portals; + u32 num_vertices; u32 num_edges; u32 num_faces; - u32 num_leafs; - u32 num_marksurfaces; - u32 num_nodes; u32 num_planes; + u32 num_nodes; + u32 num_leafs; u32 num_surfedges; - u32 num_vertex_lights; - u32 num_vertices; + u32 num_marksurfaces; + u32 num_cell_portals; u32 visdata_size; + u32 num_vertex_lights; + u32 num_heightfield; } pxl8_bsp_wire_header; usize pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, usize len); @@ -130,14 +129,15 @@ usize pxl8_protocol_deserialize_chunk_msg_header(const u8* buf, usize len, pxl8_ usize pxl8_protocol_deserialize_bsp_wire_header(const u8* buf, usize len, pxl8_bsp_wire_header* hdr); typedef struct pxl8_chunk_enter_msg { - u32 chunk_id; - u8 chunk_type; - u8 reserved[3]; + i32 cx; + i32 cz; } pxl8_chunk_enter_msg; usize pxl8_protocol_serialize_chunk_enter(const pxl8_chunk_enter_msg* msg, u8* buf, usize len); usize pxl8_protocol_deserialize_chunk_enter(const u8* buf, usize len, pxl8_chunk_enter_msg* msg); +u32 pxl8_chunk_hash(i32 cx, i32 cz); + #ifdef __cplusplus } #endif diff --git a/src/script/pxl8_script.c b/src/script/pxl8_script.c index 3685650..9acd828 100644 --- a/src/script/pxl8_script.c +++ b/src/script/pxl8_script.c @@ -4,16 +4,13 @@ #include #include -#include -#include -#include - #include #include #include #include "pxl8_cart.h" #include "pxl8_embed.h" +#include "pxl8_io.h" #include "pxl8_gui.h" #include "pxl8_log.h" #include "pxl8_macros.h" @@ -28,7 +25,7 @@ struct pxl8_script { char last_error[PXL8_MAX_ERROR_SIZE]; char main_path[PXL8_MAX_PATH]; char watch_dir[PXL8_MAX_PATH]; - time_t latest_mod_time; + f64 latest_mod_time; int repl_env_ref; bool repl_mode; }; @@ -407,8 +404,12 @@ static pxl8_result pxl8_script_prepare_path(pxl8_script* script, const char* fil char script_dir[PATH_MAX]; char original_cwd[PATH_MAX]; - if (realpath(filename_copy, script_dir) && getcwd(original_cwd, sizeof(original_cwd))) { - chdir(script_dir); + char* resolved = pxl8_io_get_real_path(filename_copy); + char* cwd = pxl8_io_get_cwd(); + if (resolved && cwd) { + pxl8_strncpy(script_dir, resolved, sizeof(script_dir)); + pxl8_strncpy(original_cwd, cwd, sizeof(original_cwd)); + pxl8_io_set_cwd(script_dir); pxl8_script_set_cart_path(script, script_dir, original_cwd); strncpy(out_basename, last_slash + 1, basename_size - 1); out_basename[basename_size - 1] = '\0'; @@ -416,6 +417,8 @@ static pxl8_result pxl8_script_prepare_path(pxl8_script* script, const char* fil strncpy(out_basename, filename, basename_size - 1); out_basename[basename_size - 1] = '\0'; } + pxl8_free(resolved); + pxl8_free(cwd); } else { strncpy(out_basename, filename, basename_size - 1); out_basename[basename_size - 1] = '\0'; @@ -442,51 +445,38 @@ pxl8_result pxl8_script_run_file(pxl8_script* script, const char* filename) { return result; } -static time_t get_file_mod_time(const char* path) { - struct stat file_stat; - if (stat(path, &file_stat) == 0) { - return file_stat.st_mtime; - } - return 0; -} +typedef struct { + f64 latest; +} script_mod_ctx; -static time_t get_latest_script_mod_time(const char* dir_path) { - DIR* dir = opendir(dir_path); - if (!dir) return 0; +static f64 get_latest_script_mod_time(const char* dir_path); - time_t latest = 0; - struct dirent* entry; +static bool check_script_mod_time(void* userdata, const char* dirname, const char* name) { + script_mod_ctx* ctx = userdata; + if (name[0] == '.') return true; - while ((entry = readdir(dir)) != NULL) { - if (entry->d_name[0] == '.') continue; + char full_path[512]; + snprintf(full_path, sizeof(full_path), "%s%s", dirname, name); - char full_path[512]; - snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name); - - struct stat st; - if (stat(full_path, &st) == 0) { - if (S_ISDIR(st.st_mode)) { - time_t subdir_time = get_latest_script_mod_time(full_path); - if (subdir_time > latest) { - latest = subdir_time; - } - } else { - usize len = strlen(entry->d_name); - bool is_script = (len > 4 && strcmp(entry->d_name + len - 4, ".fnl") == 0) || - (len > 4 && strcmp(entry->d_name + len - 4, ".lua") == 0); - - if (is_script) { - time_t mod_time = get_file_mod_time(full_path); - if (mod_time > latest) { - latest = mod_time; - } - } - } + if (pxl8_io_is_directory(full_path)) { + f64 subdir_time = get_latest_script_mod_time(full_path); + if (subdir_time > ctx->latest) ctx->latest = subdir_time; + } else { + usize len = strlen(name); + bool is_script = (len > 4 && strcmp(name + len - 4, ".fnl") == 0) || + (len > 4 && strcmp(name + len - 4, ".lua") == 0); + if (is_script) { + f64 mod_time = pxl8_io_get_file_modified_time(full_path); + if (mod_time > ctx->latest) ctx->latest = mod_time; } } + return true; +} - closedir(dir); - return latest; +static f64 get_latest_script_mod_time(const char* dir_path) { + script_mod_ctx ctx = { .latest = 0.0 }; + pxl8_io_enumerate_directory(dir_path, check_script_mod_time, &ctx); + return ctx.latest; } void pxl8_script_set_cart_path(pxl8_script* script, const char* cart_path, const char* original_cwd) { @@ -1157,8 +1147,8 @@ bool pxl8_script_check_reload(pxl8_script* script) { return false; } - time_t current_mod_time = get_latest_script_mod_time(script->watch_dir); - if (current_mod_time > script->latest_mod_time && current_mod_time != 0) { + f64 current_mod_time = get_latest_script_mod_time(script->watch_dir); + if (current_mod_time > script->latest_mod_time && current_mod_time != 0.0) { pxl8_info("Script files modified, reloading: %s", script->main_path); script->latest_mod_time = current_mod_time; @@ -1214,12 +1204,6 @@ static pxl8_resolution parse_resolution(const char* str) { return PXL8_RESOLUTION_640x360; } -static pxl8_pixel_mode parse_pixel_mode(const char* str) { - if (strcmp(str, "indexed") == 0) return PXL8_PIXEL_INDEXED; - if (strcmp(str, "hicolor") == 0) return PXL8_PIXEL_HICOLOR; - return PXL8_PIXEL_INDEXED; -} - pxl8_result pxl8_script_load_cart_manifest(pxl8_script* script, pxl8_cart* cart) { if (!script || !script->L || !cart) return PXL8_ERROR_NULL_POINTER; @@ -1267,12 +1251,6 @@ pxl8_result pxl8_script_load_cart_manifest(pxl8_script* script, pxl8_cart* cart) } lua_pop(script->L, 1); - lua_getfield(script->L, -1, "pixel-mode"); - if (lua_isstring(script->L, -1)) { - pxl8_cart_set_pixel_mode(cart, parse_pixel_mode(lua_tostring(script->L, -1))); - } - lua_pop(script->L, 1); - lua_getfield(script->L, -1, "window-size"); if (lua_istable(script->L, -1)) { lua_rawgeti(script->L, -1, 1); diff --git a/src/script/pxl8_script_ffi.h b/src/script/pxl8_script_ffi.h index f3e27f9..b85e4da 100644 --- a/src/script/pxl8_script_ffi.h +++ b/src/script/pxl8_script_ffi.h @@ -27,9 +27,8 @@ static const char* pxl8_ffi_cdefs = "f32 pxl8_get_fps(const pxl8* sys);\n" "\n" "u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);\n" -"u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx);\n" +"u8* pxl8_gfx_framebuffer(pxl8_gfx* gfx);\n" "i32 pxl8_gfx_get_height(pxl8_gfx* ctx);\n" -"u32* pxl8_gfx_get_light_accum(pxl8_gfx* gfx);\n" "const u32* pxl8_gfx_palette_colors(pxl8_gfx* gfx);\n" "u16* pxl8_gfx_get_zbuffer(pxl8_gfx* gfx);\n" "typedef struct pxl8_palette pxl8_palette;\n" @@ -262,6 +261,7 @@ static const char* pxl8_ffi_cdefs = "void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_3d_uniforms* uniforms);\n" "void pxl8_3d_clear(pxl8_gfx* gfx, u8 color);\n" "void pxl8_3d_clear_depth(pxl8_gfx* gfx);\n" +"void pxl8_3d_clear_stencil(pxl8_gfx* gfx, uint8_t value);\n" "void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u8 color);\n" "void pxl8_3d_end_frame(pxl8_gfx* gfx);\n" "u32 pxl8_3d_project_points(pxl8_gfx* gfx, const pxl8_vec3* in, pxl8_vec3* out, u32 count, const pxl8_mat4* transform);\n" @@ -309,9 +309,20 @@ 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, const pxl8_mat4* model, const pxl8_gfx_material* material);\n" +"typedef struct pxl8_gfx_draw_opts {\n" +" bool color_write;\n" +" int depth_compare;\n" +" bool depth_write;\n" +" bool stencil_test;\n" +" bool stencil_write;\n" +" uint8_t stencil_compare;\n" +" uint8_t stencil_ref;\n" +"} pxl8_gfx_draw_opts;\n" +"\n" +"void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material, const pxl8_gfx_draw_opts* opts);\n" "\n" "u32 pxl8_hash32(u32 x);\n" +"u32 pxl8_chunk_hash(i32 cx, i32 cz);\n" "\n" "pxl8_mat4 pxl8_mat4_identity(void);\n" "pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up);\n" @@ -401,6 +412,7 @@ static const char* pxl8_ffi_cdefs = "\n" "u32 pxl8_bsp_face_count(const pxl8_bsp* bsp);\n" "pxl8_vec3 pxl8_bsp_face_normal(const pxl8_bsp* bsp, u32 face_id);\n" +"void pxl8_bsp_face_set_material(pxl8_bsp* bsp, u32 face_id, u16 material_id);\n" "u8 pxl8_bsp_light_at(const pxl8_bsp* bsp, f32 x, f32 y, f32 z, u8 ambient);\n" "\n" "typedef struct pxl8_world_chunk {\n" @@ -437,6 +449,7 @@ static const char* pxl8_ffi_cdefs = "} pxl8_sim_entity;\n" "\n" "void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z);\n" +"void pxl8_world_set_look(pxl8_world* world, f32 yaw, f32 pitch);\n" "pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world);\n" "\n" "typedef struct { i32 cursor_x; i32 cursor_y; bool cursor_down; bool cursor_clicked; u32 hot_id; u32 active_id; } pxl8_gui_state;\n" @@ -450,11 +463,17 @@ static const char* pxl8_ffi_cdefs = "bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label);\n" "bool pxl8_gui_slider(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, float* value, float min_val, float max_val);\n" "bool pxl8_gui_slider_int(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, i32* value, i32 min_val, i32 max_val);\n" +"i32 pxl8_gui_toolbar(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id, i32 x, i32 y, i32 btn_w, i32 btn_h, const char** labels, i32 count, i32 selected);\n" "void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title);\n" "void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color);\n" "u8 pxl8_gui_color(pxl8_gfx* gfx, u8 index);\n" "bool pxl8_gui_is_hovering(const pxl8_gui_state* state);\n" "void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y);\n" +"i32 pxl8_gui_grid_select(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id, i32 x, i32 y, i32 cell_w, i32 cell_h, i32 cols, i32 rows, const u8* colors, i32 selected);\n" +"void pxl8_gui_image(pxl8_gfx* gfx, u32 texture_id, i32 sx, i32 sy, i32 sw, i32 sh, i32 dx, i32 dy, i32 dw, i32 dh);\n" +"void pxl8_gui_panel(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h);\n" +"void pxl8_gui_status_bar(pxl8_gfx* gfx, i32 y, i32 screen_w, i32 h, const char* text);\n" +"bool pxl8_gui_toggle(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label, bool active);\n" "\n" "typedef struct pxl8_save pxl8_save;\n" "pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version);\n" @@ -514,7 +533,7 @@ static const char* pxl8_ffi_cdefs = "\n" "typedef struct pxl8_net pxl8_net;\n" "typedef struct pxl8_net_config { const char* address; u16 port; } pxl8_net_config;\n" -"typedef enum pxl8_cmd_type { PXL8_CMD_NONE = 0, PXL8_CMD_SPAWN_ENTITY, PXL8_CMD_ENTER_SCENE } pxl8_cmd_type;\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" @@ -548,7 +567,10 @@ static const char* pxl8_ffi_cdefs = "} pxl8_sim_config;\n" "\n" "typedef struct pxl8_sim_world {\n" -" const pxl8_bsp* bsp;\n" +" const pxl8_bsp* chunks[9];\n" +" i32 center_cx;\n" +" i32 center_cz;\n" +" f32 chunk_size;\n" "} pxl8_sim_world;\n" "\n" "void pxl8_sim_move_player(pxl8_sim_entity* ent, const pxl8_input_msg* input, const pxl8_sim_world* world, const pxl8_sim_config* cfg, f32 dt);\n" @@ -587,15 +609,15 @@ static const char* pxl8_ffi_cdefs = "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" -"u8 pxl8_net_chunk_type(const pxl8_net* net);\n" -"u32 pxl8_net_chunk_id(const pxl8_net* net);\n" +"i32 pxl8_net_chunk_cx(const pxl8_net* net);\n" +"i32 pxl8_net_chunk_cz(const pxl8_net* net);\n" +"bool pxl8_net_has_chunk(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" "i32 pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch);\n" -"i32 pxl8_net_enter_scene(pxl8_net* net, u8 chunk_type, u32 chunk_id, f32 x, f32 y, f32 z);\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" diff --git a/src/sim/pxl8_sim.c b/src/sim/pxl8_sim.c index 9de35e1..ff19667 100644 --- a/src/sim/pxl8_sim.c +++ b/src/sim/pxl8_sim.c @@ -2,6 +2,15 @@ #include +#define DIST_EPSILON 0.03125f + +typedef struct { + f32 fraction; + pxl8_vec3 normal; + bool all_solid; + bool start_solid; +} trace_result; + static i32 bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) { if (!bsp || bsp->num_nodes == 0) return -1; @@ -16,54 +25,299 @@ static i32 bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) { return -(node_id + 1); } +static i32 bsp_contents_from(const pxl8_bsp* bsp, i32 node_id, pxl8_vec3 pos) { + while (node_id >= 0) { + const pxl8_bsp_node* node = &bsp->nodes[node_id]; + const pxl8_bsp_plane* plane = &bsp->planes[node->plane_id]; + f32 d = pxl8_vec3_dot(pos, plane->normal) - plane->dist; + node_id = node->children[d < 0 ? 1 : 0]; + } + i32 leaf_idx = -(node_id + 1); + if (leaf_idx < 0 || (u32)leaf_idx >= bsp->num_leafs) return -1; + return bsp->leafs[leaf_idx].contents; +} + +static bool bsp_recursive_trace(const pxl8_bsp* bsp, i32 node_id, + f32 p1f, f32 p2f, + pxl8_vec3 p1, pxl8_vec3 p2, + trace_result* tr) { + if (node_id < 0) { + i32 leaf_idx = -(node_id + 1); + if (leaf_idx >= 0 && (u32)leaf_idx < bsp->num_leafs && + bsp->leafs[leaf_idx].contents == -1) { + tr->start_solid = true; + } else { + tr->all_solid = false; + } + return true; + } + + const pxl8_bsp_node* node = &bsp->nodes[node_id]; + const pxl8_bsp_plane* plane = &bsp->planes[node->plane_id]; + + f32 t1 = pxl8_vec3_dot(p1, plane->normal) - plane->dist; + f32 t2 = pxl8_vec3_dot(p2, plane->normal) - plane->dist; + + if (t1 >= 0 && t2 >= 0) + return bsp_recursive_trace(bsp, node->children[0], p1f, p2f, p1, p2, tr); + if (t1 < 0 && t2 < 0) + return bsp_recursive_trace(bsp, node->children[1], p1f, p2f, p1, p2, tr); + + i32 side; + f32 frac; + if (t1 < 0) { + frac = (t1 + DIST_EPSILON) / (t1 - t2); + side = 1; + } else { + frac = (t1 - DIST_EPSILON) / (t1 - t2); + side = 0; + } + if (frac < 0) frac = 0; + if (frac > 1) frac = 1; + + f32 midf = p1f + (p2f - p1f) * frac; + pxl8_vec3 mid = { + p1.x + frac * (p2.x - p1.x), + p1.y + frac * (p2.y - p1.y), + p1.z + frac * (p2.z - p1.z), + }; + + if (!bsp_recursive_trace(bsp, node->children[side], p1f, midf, p1, mid, tr)) + return false; + + if (bsp_contents_from(bsp, node->children[side ^ 1], mid) != -1) + return bsp_recursive_trace(bsp, node->children[side ^ 1], midf, p2f, mid, p2, tr); + + if (tr->all_solid) + return false; + + if (midf < tr->fraction) { + tr->fraction = midf; + if (side == 0) { + tr->normal = plane->normal; + } else { + tr->normal.x = -plane->normal.x; + tr->normal.y = -plane->normal.y; + tr->normal.z = -plane->normal.z; + } + } + return false; +} + +static trace_result bsp_trace_line(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to) { + trace_result tr = { .fraction = 1.0f, .all_solid = true }; + if (!bsp || bsp->num_nodes == 0) { + tr.all_solid = false; + return tr; + } + bsp_recursive_trace(bsp, 0, 0.0f, 1.0f, from, to, &tr); + if (tr.all_solid) { + tr.fraction = 0.0f; + tr.start_solid = true; + } + return tr; +} + bool pxl8_bsp_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos) { + if (!bsp) return false; + if (bsp->bounds_max_x > bsp->bounds_min_x && + (pos.x < bsp->bounds_min_x || pos.x >= bsp->bounds_max_x || + pos.z < bsp->bounds_min_z || pos.z >= bsp->bounds_max_z)) + return false; i32 leaf = bsp_find_leaf(bsp, pos); if (leaf < 0 || (u32)leaf >= bsp->num_leafs) return true; return bsp->leafs[leaf].contents == -1; } -static bool bsp_point_clear(const pxl8_bsp* bsp, f32 x, f32 y, f32 z, f32 radius) { - if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z})) return false; - if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x + radius, y, z})) return false; - if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x - radius, y, z})) return false; - if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z + radius})) return false; - if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z - radius})) return false; - return true; +static void trace_offsets(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, + f32 radius, f32* out_frac, pxl8_vec3* out_normal) { + f32 d = radius * 0.7071f; + pxl8_vec3 offsets[9] = { + {0, 0, 0}, + {radius, 0, 0}, {-radius, 0, 0}, + {0, 0, radius}, {0, 0, -radius}, + {d, 0, d}, {d, 0, -d}, + {-d, 0, d}, {-d, 0, -d}, + }; + + *out_frac = 1.0f; + *out_normal = (pxl8_vec3){0, 0, 0}; + + for (i32 i = 0; i < 9; i++) { + pxl8_vec3 s = { from.x + offsets[i].x, from.y + offsets[i].y, from.z + offsets[i].z }; + pxl8_vec3 e = { to.x + offsets[i].x, to.y + offsets[i].y, to.z + offsets[i].z }; + trace_result tr = bsp_trace_line(bsp, s, e); + if (tr.fraction < *out_frac && !tr.start_solid) { + *out_frac = tr.fraction; + *out_normal = tr.normal; + } + } } pxl8_vec3 pxl8_bsp_trace(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, f32 radius) { if (!bsp || bsp->num_nodes == 0) return to; - if (bsp_point_clear(bsp, to.x, to.y, to.z, radius)) { - return to; + f32 frac; + pxl8_vec3 normal; + trace_offsets(bsp, from, to, radius, &frac, &normal); + if (frac >= 1.0f) return to; + + pxl8_vec3 delta = { to.x - from.x, to.y - from.y, to.z - from.z }; + pxl8_vec3 hit = { + from.x + delta.x * frac, + from.y + delta.y * frac, + from.z + delta.z * frac, + }; + + f32 remaining = 1.0f - frac; + if (remaining <= 0.0f) return hit; + + f32 backoff = pxl8_vec3_dot(delta, normal) * remaining; + pxl8_vec3 slide_target = { + hit.x + delta.x * remaining - normal.x * backoff, + hit.y + delta.y * remaining - normal.y * backoff, + hit.z + delta.z * remaining - normal.z * backoff, + }; + + f32 slide_frac; + pxl8_vec3 slide_normal; + trace_offsets(bsp, hit, slide_target, radius, &slide_frac, &slide_normal); + if (slide_frac >= 1.0f) return slide_target; + + pxl8_vec3 slide_delta = { + slide_target.x - hit.x, + slide_target.y - hit.y, + slide_target.z - hit.z, + }; + return (pxl8_vec3){ + hit.x + slide_delta.x * slide_frac, + hit.y + slide_delta.y * slide_frac, + hit.z + slide_delta.z * slide_frac, + }; +} + +static const pxl8_bsp* sim_bsp_at(const pxl8_sim_world* world, f32 x, f32 z) { + i32 cx = (i32)floorf(x / world->chunk_size); + i32 cz = (i32)floorf(z / world->chunk_size); + i32 dx = cx - world->center_cx + 1; + i32 dz = cz - world->center_cz + 1; + if (dx < 0 || dx > 2 || dz < 0 || dz > 2) return NULL; + return world->chunks[dz * 3 + dx]; +} + +static f32 bsp_terrain_height(const pxl8_bsp* bsp, f32 x, f32 z) { + if (!bsp || !bsp->heightfield || bsp->heightfield_cell_size <= 0) return -1e9f; + f32 lx = (x - bsp->heightfield_ox) / bsp->heightfield_cell_size; + f32 lz = (z - bsp->heightfield_oz) / bsp->heightfield_cell_size; + i32 ix = (i32)floorf(lx); + i32 iz = (i32)floorf(lz); + if (ix < 0 || ix >= bsp->heightfield_w - 1 || iz < 0 || iz >= bsp->heightfield_h - 1) return -1e9f; + f32 fx = lx - ix; + f32 fz = lz - iz; + i32 w = bsp->heightfield_w; + f32 h00 = bsp->heightfield[iz * w + ix]; + f32 h10 = bsp->heightfield[iz * w + ix + 1]; + f32 h01 = bsp->heightfield[(iz + 1) * w + ix]; + f32 h11 = bsp->heightfield[(iz + 1) * w + ix + 1]; + f32 h0 = h00 + (h10 - h00) * fx; + f32 h1 = h01 + (h11 - h01) * fx; + return h0 + (h1 - h0) * fz; +} + +static f32 sim_terrain_height(const pxl8_sim_world* world, f32 x, f32 z) { + const pxl8_bsp* bsp = sim_bsp_at(world, x, z); + return bsp_terrain_height(bsp, x, z); +} + +static void sim_trace_offsets(const pxl8_sim_world* world, pxl8_vec3 from, pxl8_vec3 to, + f32 radius, f32* out_frac, pxl8_vec3* out_normal) { + f32 d = radius * 0.7071f; + pxl8_vec3 offsets[9] = { + {0, 0, 0}, + {radius, 0, 0}, {-radius, 0, 0}, + {0, 0, radius}, {0, 0, -radius}, + {d, 0, d}, {d, 0, -d}, + {-d, 0, d}, {-d, 0, -d}, + }; + + *out_frac = 1.0f; + *out_normal = (pxl8_vec3){0, 0, 0}; + + for (i32 i = 0; i < 9; i++) { + pxl8_vec3 s = { from.x + offsets[i].x, from.y + offsets[i].y, from.z + offsets[i].z }; + pxl8_vec3 e = { to.x + offsets[i].x, to.y + offsets[i].y, to.z + offsets[i].z }; + const pxl8_bsp* bsp_s = sim_bsp_at(world, s.x, s.z); + const pxl8_bsp* bsp_e = sim_bsp_at(world, e.x, e.z); + if (bsp_s) { + trace_result tr = bsp_trace_line(bsp_s, s, e); + if (tr.fraction < *out_frac && !tr.start_solid) { + *out_frac = tr.fraction; + *out_normal = tr.normal; + } + } + if (bsp_e && bsp_e != bsp_s) { + bool inside_bounds = !(bsp_e->bounds_max_x > bsp_e->bounds_min_x) || + (s.x >= bsp_e->bounds_min_x && s.x < bsp_e->bounds_max_x && + s.z >= bsp_e->bounds_min_z && s.z < bsp_e->bounds_max_z); + if (inside_bounds) { + trace_result tr = bsp_trace_line(bsp_e, s, e); + if (tr.fraction < *out_frac && !tr.start_solid) { + *out_frac = tr.fraction; + *out_normal = tr.normal; + } + } + } } - - bool x_ok = bsp_point_clear(bsp, to.x, from.y, from.z, radius); - bool y_ok = bsp_point_clear(bsp, from.x, to.y, from.z, radius); - bool z_ok = bsp_point_clear(bsp, from.x, from.y, to.z, radius); - - pxl8_vec3 result = from; - if (x_ok) result.x = to.x; - if (y_ok) result.y = to.y; - if (z_ok) result.z = to.z; - - return result; } pxl8_vec3 pxl8_sim_trace(const pxl8_sim_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius, f32 height) { (void)height; - if (!world) return to; + if (!world || world->chunk_size <= 0) return to; - if (world->bsp) { - return pxl8_bsp_trace(world->bsp, from, to, radius); - } + f32 frac; + pxl8_vec3 normal; + sim_trace_offsets(world, from, to, radius, &frac, &normal); + if (frac >= 1.0f) return to; - return to; + pxl8_vec3 delta = { to.x - from.x, to.y - from.y, to.z - from.z }; + pxl8_vec3 hit = { + from.x + delta.x * frac, + from.y + delta.y * frac, + from.z + delta.z * frac, + }; + + f32 remaining = 1.0f - frac; + if (remaining <= 0.0f) return hit; + + f32 backoff = pxl8_vec3_dot(delta, normal) * remaining; + pxl8_vec3 slide_target = { + hit.x + delta.x * remaining - normal.x * backoff, + hit.y + delta.y * remaining - normal.y * backoff, + hit.z + delta.z * remaining - normal.z * backoff, + }; + + f32 slide_frac; + pxl8_vec3 slide_normal; + sim_trace_offsets(world, hit, slide_target, radius, &slide_frac, &slide_normal); + if (slide_frac >= 1.0f) return slide_target; + + pxl8_vec3 slide_delta = { + slide_target.x - hit.x, + slide_target.y - hit.y, + slide_target.z - hit.z, + }; + return (pxl8_vec3){ + hit.x + slide_delta.x * slide_frac, + hit.y + slide_delta.y * slide_frac, + hit.z + slide_delta.z * slide_frac, + }; } bool pxl8_sim_check_ground(const pxl8_sim_world* world, pxl8_vec3 pos, f32 radius) { - if (!world) return true; - if (!world->bsp) return true; + if (!world || world->chunk_size <= 0) return true; + + f32 th = sim_terrain_height(world, pos.x, pos.z); + if (th > -1e8f && pos.y - th < 2.0f) return true; pxl8_vec3 down = {pos.x, pos.y - 2.0f, pos.z}; pxl8_vec3 result = pxl8_sim_trace(world, pos, down, radius, 0.0f); @@ -119,22 +373,17 @@ void pxl8_sim_move_player(pxl8_sim_entity* ent, const pxl8_input_msg* input, ent->pos.z + ent->vel.z * dt }; + if (grounded) { + f32 th_dest = sim_terrain_height(world, target.x, target.z); + if (th_dest > -1e8f) target.y = th_dest; + } + pxl8_vec3 new_pos = pxl8_sim_trace(world, ent->pos, target, cfg->player_radius, cfg->player_height); - if (ent->vel.y < 0 && new_pos.y > target.y + 0.01f) { - f32 hi = new_pos.y; - f32 lo = target.y; - for (i32 i = 0; i < 8; i++) { - f32 mid = (hi + lo) * 0.5f; - pxl8_vec3 test = {new_pos.x, mid, new_pos.z}; - pxl8_vec3 result = pxl8_sim_trace(world, new_pos, test, cfg->player_radius, cfg->player_height); - if (result.y > mid + 0.01f) { - lo = mid; - } else { - hi = mid; - } - } - new_pos.y = hi; + f32 th = sim_terrain_height(world, new_pos.x, new_pos.z); + if (th > -1e8f && new_pos.y < th) { + new_pos.y = th; + if (ent->vel.y < 0) ent->vel.y = 0; } ent->pos = new_pos; @@ -175,22 +424,17 @@ void pxl8_sim_integrate(pxl8_sim_entity* ent, const pxl8_sim_world* world, ent->pos.z + ent->vel.z * dt }; + if (grounded) { + f32 th_dest = sim_terrain_height(world, target.x, target.z); + if (th_dest > -1e8f) target.y = th_dest; + } + pxl8_vec3 new_pos = pxl8_sim_trace(world, ent->pos, target, cfg->player_radius, cfg->player_height); - if (ent->vel.y < 0 && new_pos.y > target.y + 0.01f) { - f32 hi = new_pos.y; - f32 lo = target.y; - for (i32 i = 0; i < 8; i++) { - f32 mid = (hi + lo) * 0.5f; - pxl8_vec3 test = {new_pos.x, mid, new_pos.z}; - pxl8_vec3 result = pxl8_sim_trace(world, new_pos, test, cfg->player_radius, cfg->player_height); - if (result.y > mid + 0.01f) { - lo = mid; - } else { - hi = mid; - } - } - new_pos.y = hi; + f32 th2 = sim_terrain_height(world, new_pos.x, new_pos.z); + if (th2 > -1e8f && new_pos.y < th2) { + new_pos.y = th2; + if (ent->vel.y < 0.0f) ent->vel.y = 0.0f; } ent->pos = new_pos; diff --git a/src/sim/pxl8_sim.h b/src/sim/pxl8_sim.h index a17c540..7d05711 100644 --- a/src/sim/pxl8_sim.h +++ b/src/sim/pxl8_sim.h @@ -37,7 +37,10 @@ typedef struct pxl8_sim_entity { } pxl8_sim_entity; typedef struct pxl8_sim_world { - const pxl8_bsp* bsp; + const pxl8_bsp* chunks[9]; + i32 center_cx; + i32 center_cz; + f32 chunk_size; } pxl8_sim_world; bool pxl8_bsp_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos); diff --git a/src/world/pxl8_world.c b/src/world/pxl8_world.c index 854d7f3..aa217a3 100644 --- a/src/world/pxl8_world.c +++ b/src/world/pxl8_world.c @@ -14,15 +14,31 @@ #include "pxl8_gfx3d.h" #include "pxl8_log.h" #include "pxl8_mem.h" +#include "pxl8_protocol.h" #include "pxl8_sim.h" +#define PXL8_VIS_MAX_NODES (PXL8_WORLD_MAX_LOADED_CHUNKS * 512) +#define PXL8_VIS_MAX_QUEUE (PXL8_VIS_MAX_NODES * 4) +#define PXL8_VIS_BYTES ((PXL8_VIS_MAX_NODES + 7) / 8) #define PXL8_WORLD_ENTITY_CAPACITY 256 +typedef struct { + u16 chunk_idx; + u16 leaf_idx; + pxl8_rect window; +} world_vis_node; + struct pxl8_world { + pxl8_loaded_chunk loaded[PXL8_WORLD_MAX_LOADED_CHUNKS]; + u32 loaded_count; pxl8_world_chunk* active_chunk; + pxl8_bsp_render_state* active_render_state; + + pxl8_gfx_material shared_materials[16]; + bool shared_material_set[16]; + pxl8_world_chunk_cache* chunk_cache; pxl8_entity_pool* entities; - pxl8_bsp_render_state* bsp_render_state; pxl8_sim_entity local_player; u64 client_tick; @@ -30,6 +46,12 @@ struct pxl8_world { pxl8_vec2 pointer_motion; pxl8_sim_config sim_config; + u8 vis_bits[PXL8_VIS_BYTES]; + u8* vis_ptrs[PXL8_WORLD_MAX_LOADED_CHUNKS]; + pxl8_rect vis_windows[PXL8_VIS_MAX_NODES]; + pxl8_rect* vis_win_ptrs[PXL8_WORLD_MAX_LOADED_CHUNKS]; + world_vis_node vis_queue[PXL8_VIS_MAX_QUEUE]; + #ifdef PXL8_ASYNC_THREADS pxl8_sim_entity render_state[2]; atomic_uint active_buffer; @@ -72,9 +94,11 @@ pxl8_world* pxl8_world_create(void) { void pxl8_world_destroy(pxl8_world* world) { if (!world) return; + for (u32 i = 0; i < world->loaded_count; i++) { + pxl8_bsp_render_state_destroy(world->loaded[i].render_state); + } pxl8_world_chunk_cache_destroy(world->chunk_cache); pxl8_entity_pool_destroy(world->entities); - pxl8_bsp_render_state_destroy(world->bsp_render_state); pxl8_free(world); } @@ -88,27 +112,26 @@ pxl8_world_chunk* pxl8_world_active_chunk(pxl8_world* world) { return world->active_chunk; } -void pxl8_world_set_active_chunk(pxl8_world* world, pxl8_world_chunk* chunk) { - if (!world) return; - world->active_chunk = chunk; -} - -pxl8_entity_pool* pxl8_world_entities(pxl8_world* world) { - if (!world) return NULL; - return world->entities; -} - -pxl8_entity pxl8_world_spawn(pxl8_world* world) { - if (!world || !world->entities) return PXL8_ENTITY_INVALID; - return pxl8_entity_spawn(world->entities); -} - pxl8_sim_world pxl8_world_sim_world(const pxl8_world* world, pxl8_vec3 pos) { - (void)pos; pxl8_sim_world sim = {0}; - if (world->active_chunk && world->active_chunk->bsp) { - sim.bsp = world->active_chunk->bsp; + const f32 chunk_size = 16.0f * 64.0f; + sim.chunk_size = chunk_size; + + i32 pcx = (i32)floorf(pos.x / chunk_size); + i32 pcz = (i32)floorf(pos.z / chunk_size); + sim.center_cx = pcx; + sim.center_cz = pcz; + + for (u32 i = 0; i < world->loaded_count; i++) { + const pxl8_loaded_chunk* lc = &world->loaded[i]; + if (!lc->chunk || !lc->chunk->bsp) continue; + i32 dx = lc->cx - pcx + 1; + i32 dz = lc->cz - pcz + 1; + if (dx >= 0 && dx <= 2 && dz >= 0 && dz <= 2) { + sim.chunks[dz * 3 + dx] = lc->chunk->bsp; + } } + return sim; } @@ -244,62 +267,452 @@ void pxl8_world_update(pxl8_world* world, f32 dt) { pxl8_world_chunk_cache_tick(world->chunk_cache); } +static inline bool vr_valid(pxl8_rect r) { + return r.x0 < r.x1 && r.y0 < r.y1; +} + +static inline pxl8_rect vr_intersect(pxl8_rect a, pxl8_rect b) { + return (pxl8_rect){ + .x0 = a.x0 > b.x0 ? a.x0 : b.x0, + .y0 = a.y0 > b.y0 ? a.y0 : b.y0, + .x1 = a.x1 < b.x1 ? a.x1 : b.x1, + .y1 = a.y1 < b.y1 ? a.y1 : b.y1, + }; +} + +static pxl8_rect project_portal(f32 px0, f32 pz0, f32 px1, f32 pz1, + f32 y_lo, f32 y_hi, const pxl8_mat4* vp) { + pxl8_vec3 corners[4] = { + {px0, y_lo, pz0}, {px1, y_lo, pz1}, + {px1, y_hi, pz1}, {px0, y_hi, pz0}, + }; + + const f32 NEAR_W = 0.001f; + pxl8_vec4 clip[4]; + bool front[4]; + i32 fc = 0; + + for (i32 i = 0; i < 4; i++) { + clip[i] = pxl8_mat4_multiply_vec4(*vp, (pxl8_vec4){ + corners[i].x, corners[i].y, corners[i].z, 1.0f}); + front[i] = clip[i].w > NEAR_W; + if (front[i]) fc++; + } + + if (fc == 0) return (pxl8_rect){0, 0, 0, 0}; + if (fc < 4) return (pxl8_rect){-1.0f, -1.0f, 1.0f, 1.0f}; + + pxl8_rect r = {1e30f, 1e30f, -1e30f, -1e30f}; + + for (i32 i = 0; i < 4; i++) { + f32 iw = 1.0f / clip[i].w; + f32 nx = clip[i].x * iw, ny = clip[i].y * iw; + if (nx < r.x0) r.x0 = nx; if (nx > r.x1) r.x1 = nx; + if (ny < r.y0) r.y0 = ny; if (ny > r.y1) r.y1 = ny; + } + + if (r.x0 < -1.0f) r.x0 = -1.0f; + if (r.y0 < -1.0f) r.y0 = -1.0f; + if (r.x1 > 1.0f) r.x1 = 1.0f; + if (r.y1 > 1.0f) r.y1 = 1.0f; + + return r; +} + +static void compute_edge_leafs(pxl8_loaded_chunk* lc) { + const f32 CHUNK_SIZE = 16.0f * 64.0f; + const pxl8_bsp* bsp = lc->chunk->bsp; + f32 cx0 = lc->cx * CHUNK_SIZE; + f32 cz0 = lc->cz * CHUNK_SIZE; + f32 cx1 = cx0 + CHUNK_SIZE; + f32 cz1 = cz0 + CHUNK_SIZE; + + memset(lc->edges, 0, sizeof(lc->edges)); + + for (u32 i = 0; i < bsp->num_leafs; i++) { + const pxl8_bsp_leaf* leaf = &bsp->leafs[i]; + if (bsp->leafs[i].contents == -1) continue; + + if ((f32)leaf->mins[2] <= (f32)((i16)cz0) + 1.0f && lc->edges[0].count < 16) + lc->edges[0].leafs[lc->edges[0].count++] = (u16)i; + if ((f32)leaf->maxs[2] >= (f32)((i16)cz1) - 1.0f && lc->edges[1].count < 16) + lc->edges[1].leafs[lc->edges[1].count++] = (u16)i; + if ((f32)leaf->mins[0] <= (f32)((i16)cx0) + 1.0f && lc->edges[2].count < 16) + lc->edges[2].leafs[lc->edges[2].count++] = (u16)i; + if ((f32)leaf->maxs[0] >= (f32)((i16)cx1) - 1.0f && lc->edges[3].count < 16) + lc->edges[3].leafs[lc->edges[3].count++] = (u16)i; + } +} + +static i32 world_find_chunk(const pxl8_world* world, i32 cx, i32 cz) { + for (u32 i = 0; i < world->loaded_count; i++) { + if (world->loaded[i].cx == cx && world->loaded[i].cz == cz && + world->loaded[i].chunk && world->loaded[i].chunk->bsp) + return (i32)i; + } + return -1; +} + +static void world_mark_leaf_faces(const pxl8_bsp* bsp, pxl8_bsp_render_state* rs, u32 leaf_idx) { + const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_idx]; + for (u32 i = 0; i < leaf->num_marksurfaces; i++) { + u32 si = leaf->first_marksurface + i; + if (si < bsp->num_marksurfaces) { + u32 fi = bsp->marksurfaces[si]; + if (fi < bsp->num_faces && rs) + rs->render_face_flags[fi] = 1; + } + } +} + +static bool vis_try_enqueue(u8** vis, pxl8_rect** windows, world_vis_node* queue, + u32* tail, u32 max_queue, + u16 ci, u16 li, pxl8_rect nw) { + if (!vis[ci] || !windows[ci]) return false; + + u32 byte = li >> 3; + u32 bit = 1 << (li & 7); + + if (vis[ci][byte] & bit) { + pxl8_rect* ex = &windows[ci][li]; + bool expanded = false; + if (nw.x0 < ex->x0) { ex->x0 = nw.x0; expanded = true; } + if (nw.y0 < ex->y0) { ex->y0 = nw.y0; expanded = true; } + if (nw.x1 > ex->x1) { ex->x1 = nw.x1; expanded = true; } + if (nw.y1 > ex->y1) { ex->y1 = nw.y1; expanded = true; } + if (expanded && *tail < max_queue) + queue[(*tail)++] = (world_vis_node){ci, li, *ex}; + return expanded; + } + + vis[ci][byte] |= bit; + windows[ci][li] = nw; + if (*tail < max_queue) + queue[(*tail)++] = (world_vis_node){ci, li, nw}; + return true; +} + +static void world_compute_visibility(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) { + const f32 CHUNK_SIZE = 16.0f * 64.0f; + const f32 PORTAL_Y_HI = 192.0f; + + const pxl8_mat4* vp = pxl8_3d_get_view_proj(gfx); + + i32 cam_ci = -1, cam_li = -1; + for (u32 i = 0; i < world->loaded_count; i++) { + pxl8_loaded_chunk* lc = &world->loaded[i]; + if (!lc->chunk || !lc->chunk->bsp) continue; + const pxl8_bsp* bsp = lc->chunk->bsp; + f32 cx0 = lc->cx * CHUNK_SIZE; + f32 cz0 = lc->cz * CHUNK_SIZE; + if (camera_pos.x < cx0 || camera_pos.x >= cx0 + CHUNK_SIZE || + camera_pos.z < cz0 || camera_pos.z >= cz0 + CHUNK_SIZE) continue; + if (bsp->bounds_max_x > bsp->bounds_min_x && + (camera_pos.x < bsp->bounds_min_x || camera_pos.x >= bsp->bounds_max_x || + camera_pos.z < bsp->bounds_min_z || camera_pos.z >= bsp->bounds_max_z)) + continue; + i32 leaf = pxl8_bsp_find_leaf(bsp, camera_pos); + if (leaf >= 0 && (u32)leaf < bsp->num_leafs && + bsp->leafs[leaf].contents != -1 && + (!(bsp->bounds_max_x > bsp->bounds_min_x) || bsp->leafs[leaf].contents == -2)) { + cam_ci = (i32)i; + cam_li = leaf; + break; + } + } + + if (cam_ci < 0) { + for (u32 i = 0; i < world->loaded_count; i++) { + pxl8_loaded_chunk* lc = &world->loaded[i]; + if (!lc->chunk || !lc->chunk->bsp) continue; + const pxl8_bsp* bsp = lc->chunk->bsp; + if (bsp->bounds_max_x > bsp->bounds_min_x) continue; + i32 leaf = pxl8_bsp_find_leaf(bsp, camera_pos); + if (leaf < 0 || (u32)leaf >= bsp->num_leafs) continue; + const pxl8_bsp_leaf* l = &bsp->leafs[leaf]; + if (l->contents == -1) continue; + if (camera_pos.x < (f32)l->mins[0] || camera_pos.x > (f32)l->maxs[0] || + camera_pos.z < (f32)l->mins[2] || camera_pos.z > (f32)l->maxs[2]) continue; + cam_ci = (i32)i; + cam_li = leaf; + break; + } + } + + if (cam_ci < 0 || !vp) { + for (u32 i = 0; i < world->loaded_count; i++) { + pxl8_bsp_render_state* rs = world->loaded[i].render_state; + if (!rs) continue; + if (rs->render_face_flags) + memset(rs->render_face_flags, 1, rs->num_faces); + rs->exterior = true; + } + return; + } + + memset(world->vis_bits, 0, sizeof(world->vis_bits)); + memset(world->vis_ptrs, 0, sizeof(world->vis_ptrs)); + memset(world->vis_win_ptrs, 0, sizeof(world->vis_win_ptrs)); + + u32 voff = 0, woff = 0; + for (u32 i = 0; i < world->loaded_count; i++) { + if (world->loaded[i].chunk && world->loaded[i].chunk->bsp) { + u32 nl = world->loaded[i].chunk->bsp->num_leafs; + u32 vbytes = (nl + 7) / 8; + if (voff + vbytes > PXL8_VIS_BYTES || woff + nl > PXL8_VIS_MAX_NODES) + continue; + world->vis_ptrs[i] = world->vis_bits + voff; + voff += vbytes; + world->vis_win_ptrs[i] = world->vis_windows + woff; + woff += nl; + } + } + + if (!world->vis_ptrs[cam_ci] || !world->vis_win_ptrs[cam_ci]) return; + + for (u32 i = 0; i < world->loaded_count; i++) { + pxl8_bsp_render_state* rs = world->loaded[i].render_state; + if (!rs) continue; + if (rs->render_face_flags) + memset(rs->render_face_flags, 0, rs->num_faces); + rs->exterior = false; + } + + u32 head = 0, tail = 0; + + pxl8_rect full_screen = {-1.0f, -1.0f, 1.0f, 1.0f}; + const pxl8_bsp* cam_bsp = world->loaded[cam_ci].chunk->bsp; + bool cam_exterior = cam_bsp->leafs[cam_li].contents == 0 && + !(cam_bsp->bounds_max_x > cam_bsp->bounds_min_x); + + world->vis_ptrs[cam_ci][cam_li >> 3] |= (1 << (cam_li & 7)); + world->vis_win_ptrs[cam_ci][cam_li] = full_screen; + world->vis_queue[tail++] = (world_vis_node){(u16)cam_ci, (u16)cam_li, full_screen}; + + while (head < tail) { + world_vis_node cur = world->vis_queue[head++]; + pxl8_loaded_chunk* lc = &world->loaded[cur.chunk_idx]; + const pxl8_bsp* bsp = lc->chunk->bsp; + + world_mark_leaf_faces(bsp, lc->render_state, cur.leaf_idx); + + if (bsp->cell_portals && cur.leaf_idx < bsp->num_cell_portals) { + bool is_cam_leaf = (cur.chunk_idx == (u16)cam_ci && cur.leaf_idx == (u16)cam_li); + bool is_cam_chunk = (cur.chunk_idx == (u16)cam_ci); + const pxl8_bsp_cell_portals* cp = &bsp->cell_portals[cur.leaf_idx]; + for (u8 pi = 0; pi < cp->num_portals; pi++) { + const pxl8_bsp_portal* p = &cp->portals[pi]; + u32 target = p->target_leaf; + if (target >= bsp->num_leafs) continue; + if (bsp->leafs[target].contents == -1) continue; + if (is_cam_chunk && !pxl8_bsp_is_leaf_visible(bsp, cam_li, (i32)target)) continue; + + if (is_cam_leaf || cam_exterior) { + vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs, + world->vis_queue, &tail, PXL8_VIS_MAX_QUEUE, + cur.chunk_idx, (u16)target, full_screen); + continue; + } + + pxl8_rect psr = project_portal(p->x0, p->z0, p->x1, p->z1, + 0.0f, PORTAL_Y_HI, vp); + if (!vr_valid(psr)) continue; + pxl8_rect nw = vr_intersect(cur.window, psr); + if (!vr_valid(nw)) continue; + + vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs, + world->vis_queue, &tail, PXL8_VIS_MAX_QUEUE, + cur.chunk_idx, (u16)target, nw); + } + } + + const pxl8_bsp_leaf* leaf = &bsp->leafs[cur.leaf_idx]; + f32 chunk_x0 = lc->cx * CHUNK_SIZE; + f32 chunk_z0 = lc->cz * CHUNK_SIZE; + f32 chunk_x1 = chunk_x0 + CHUNK_SIZE; + f32 chunk_z1 = chunk_z0 + CHUNK_SIZE; + + struct { i32 dx, dz; bool at_edge; f32 bnd; } dirs[4] = { + { 0, -1, (f32)leaf->mins[2] <= (f32)((i16)chunk_z0) + 1.0f, chunk_z0 }, + { 0, 1, (f32)leaf->maxs[2] >= (f32)((i16)chunk_z1) - 1.0f, chunk_z1 }, + {-1, 0, (f32)leaf->mins[0] <= (f32)((i16)chunk_x0) + 1.0f, chunk_x0 }, + { 1, 0, (f32)leaf->maxs[0] >= (f32)((i16)chunk_x1) - 1.0f, chunk_x1 }, + }; + + static const u8 opposite_edge[4] = {1, 0, 3, 2}; + + for (u32 d = 0; d < 4; d++) { + if (!dirs[d].at_edge) continue; + i32 nci = world_find_chunk(world, lc->cx + dirs[d].dx, lc->cz + dirs[d].dz); + if (nci < 0) continue; + + const pxl8_bsp* nbsp = world->loaded[nci].chunk->bsp; + const pxl8_edge_leafs* nedge = &world->loaded[nci].edges[opposite_edge[d]]; + + for (u8 k = 0; k < nedge->count; k++) { + u16 nl = nedge->leafs[k]; + const pxl8_bsp_leaf* nleaf =  ->leafs[nl]; + + bool overlaps = false; + if (dirs[d].dx != 0) { + overlaps = leaf->maxs[2] > nleaf->mins[2] && leaf->mins[2] < nleaf->maxs[2]; + } else { + overlaps = leaf->maxs[0] > nleaf->mins[0] && leaf->mins[0] < nleaf->maxs[0]; + } + if (!overlaps) continue; + + f32 bpx0, bpz0, bpx1, bpz1; + if (dirs[d].dx != 0) { + bpx0 = dirs[d].bnd; bpx1 = dirs[d].bnd; + i16 zlo = leaf->mins[2] > nleaf->mins[2] ? leaf->mins[2] : nleaf->mins[2]; + i16 zhi = leaf->maxs[2] < nleaf->maxs[2] ? leaf->maxs[2] : nleaf->maxs[2]; + bpz0 = (f32)zlo; bpz1 = (f32)zhi; + } else { + bpz0 = dirs[d].bnd; bpz1 = dirs[d].bnd; + i16 xlo = leaf->mins[0] > nleaf->mins[0] ? leaf->mins[0] : nleaf->mins[0]; + i16 xhi = leaf->maxs[0] < nleaf->maxs[0] ? leaf->maxs[0] : nleaf->maxs[0]; + bpx0 = (f32)xlo; bpx1 = (f32)xhi; + } + + if (cam_exterior) { + vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs, + world->vis_queue, &tail, PXL8_VIS_MAX_QUEUE, + (u16)nci, (u16)nl, full_screen); + continue; + } + + pxl8_rect psr = project_portal(bpx0, bpz0, bpx1, bpz1, + 0.0f, PORTAL_Y_HI, vp); + if (!vr_valid(psr)) continue; + pxl8_rect nw = vr_intersect(cur.window, psr); + if (!vr_valid(nw)) continue; + + vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs, + world->vis_queue, &tail, PXL8_VIS_MAX_QUEUE, + (u16)nci, (u16)nl, nw); + } + } + } + +} + void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) { if (!world || !gfx) return; - if (world->active_chunk && world->active_chunk->bsp) { + if (world->active_chunk && world->active_chunk->bsp) pxl8_3d_set_bsp(gfx, world->active_chunk->bsp); - pxl8_bsp_render(gfx, world->active_chunk->bsp, - world->bsp_render_state, camera_pos); - } else { + else pxl8_3d_set_bsp(gfx, NULL); + + world_compute_visibility(world, gfx, camera_pos); + + for (u32 i = 0; i < world->loaded_count; i++) { + pxl8_loaded_chunk* lc = &world->loaded[i]; + if (!lc->chunk || !lc->chunk->bsp || !lc->render_state) continue; + pxl8_bsp_render(gfx, lc->chunk->bsp, lc->render_state, NULL); + } +} + +static void apply_shared_materials(pxl8_world* world, pxl8_bsp_render_state* rs) { + for (u16 i = 0; i < 16; i++) { + if (world->shared_material_set[i]) { + pxl8_bsp_set_material(rs, i, &world->shared_materials[i]); + } } } void pxl8_world_sync(pxl8_world* world, pxl8_net* net) { if (!world || !net) return; - u32 chunk_id = pxl8_net_chunk_id(net); + if (!pxl8_net_has_chunk(net)) { + u32 old_count = world->loaded_count; + world->loaded_count = 0; + world->active_chunk = NULL; + world->active_render_state = NULL; + for (u32 i = 0; i < old_count; i++) { + pxl8_bsp_render_state_destroy(world->loaded[i].render_state); + } + return; + } - if (chunk_id != 0) { - pxl8_world_chunk* chunk = pxl8_world_chunk_cache_get_bsp(world->chunk_cache, chunk_id); - if (chunk && chunk->bsp) { - if (world->active_chunk != chunk) { - world->active_chunk = chunk; - pxl8_debug("[CLIENT] Synced BSP chunk id=%u as active (verts=%u faces=%u)", - chunk_id, chunk->bsp->num_vertices, chunk->bsp->num_faces); + i32 center_cx = pxl8_net_chunk_cx(net); + i32 center_cz = pxl8_net_chunk_cz(net); - if (world->bsp_render_state) { - pxl8_bsp_render_state_destroy(world->bsp_render_state); - world->bsp_render_state = NULL; + pxl8_loaded_chunk old_loaded[PXL8_WORLD_MAX_LOADED_CHUNKS]; + u32 old_count = world->loaded_count; + memcpy(old_loaded, world->loaded, sizeof(old_loaded)); + + pxl8_loaded_chunk new_loaded[PXL8_WORLD_MAX_LOADED_CHUNKS]; + u32 new_count = 0; + pxl8_world_chunk* new_active_chunk = NULL; + pxl8_bsp_render_state* new_active_rs = NULL; + + for (i32 dz = -2; dz <= 2; dz++) { + for (i32 dx = -2; dx <= 2; dx++) { + i32 cx = center_cx + dx; + i32 cz = center_cz + dz; + u32 id = pxl8_chunk_hash(cx, cz); + + pxl8_world_chunk* chunk = pxl8_world_chunk_cache_get_bsp(world->chunk_cache, id); + if (!chunk || !chunk->bsp) continue; + + pxl8_bsp_render_state* rs = NULL; + for (u32 j = 0; j < old_count; j++) { + if (old_loaded[j].active && old_loaded[j].cx == cx && old_loaded[j].cz == cz && + old_loaded[j].chunk == chunk) { + rs = old_loaded[j].render_state; + old_loaded[j].active = false; + break; } - world->bsp_render_state = pxl8_bsp_render_state_create(chunk->bsp->num_faces); + } + + if (!rs) { + rs = pxl8_bsp_render_state_create(chunk->bsp->num_faces); + if (rs && rs->render_face_flags) + memset(rs->render_face_flags, 1, rs->num_faces); + apply_shared_materials(world, rs); + } + + u32 idx = new_count++; + new_loaded[idx] = (pxl8_loaded_chunk){ + .chunk = chunk, + .render_state = rs, + .cx = cx, + .cz = cz, + .active = true, + }; + compute_edge_leafs(&new_loaded[idx]); + + if (dx == 0 && dz == 0) { + new_active_chunk = chunk; + new_active_rs = rs; } } - } else if (world->active_chunk != NULL) { - world->active_chunk = NULL; - if (world->bsp_render_state) { - pxl8_bsp_render_state_destroy(world->bsp_render_state); - world->bsp_render_state = NULL; + } + + memcpy(world->loaded, new_loaded, sizeof(new_loaded)); + world->active_chunk = new_active_chunk; + world->active_render_state = new_active_rs; + world->loaded_count = new_count; + + for (u32 j = 0; j < old_count; j++) { + if (old_loaded[j].active) { + pxl8_bsp_render_state_destroy(old_loaded[j].render_state); } } } -static void ensure_bsp_render_state(pxl8_world* world) { - if (!world || world->bsp_render_state) return; - if (!world->active_chunk) return; - if (!world->active_chunk->bsp) return; - - world->bsp_render_state = pxl8_bsp_render_state_create(world->active_chunk->bsp->num_faces); -} - void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_gfx_material* material) { - if (!world || !material) return; + if (!world || !material || material_id >= 16) return; - ensure_bsp_render_state(world); - if (!world->bsp_render_state) return; + world->shared_materials[material_id] = *material; + world->shared_material_set[material_id] = true; - pxl8_bsp_set_material(world->bsp_render_state, material_id, material); + for (u32 i = 0; i < world->loaded_count; i++) { + if (world->loaded[i].render_state) { + pxl8_bsp_set_material(world->loaded[i].render_state, material_id, material); + } + } } void pxl8_world_set_sim_config(pxl8_world* world, const pxl8_sim_config* config) { @@ -316,6 +729,7 @@ void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z) { world->local_player.flags = PXL8_SIM_FLAG_ALIVE | PXL8_SIM_FLAG_PLAYER | PXL8_SIM_FLAG_GROUNDED; world->local_player.kind = 0; world->client_tick = 0; + world->pointer_motion = (pxl8_vec2){0}; #ifdef PXL8_ASYNC_THREADS world->render_state[0] = world->local_player; @@ -323,6 +737,20 @@ void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z) { #endif } +void pxl8_world_set_look(pxl8_world* world, f32 yaw, f32 pitch) { + if (!world) return; + world->local_player.yaw = yaw; + world->local_player.pitch = pitch; + world->pointer_motion = (pxl8_vec2){.yaw = yaw, .pitch = pitch}; + +#ifdef PXL8_ASYNC_THREADS + world->render_state[0].yaw = yaw; + world->render_state[0].pitch = pitch; + world->render_state[1].yaw = yaw; + world->render_state[1].pitch = pitch; +#endif +} + pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world) { if (!world) return NULL; #ifdef PXL8_ASYNC_THREADS diff --git a/src/world/pxl8_world.h b/src/world/pxl8_world.h index bb09a41..55b2d02 100644 --- a/src/world/pxl8_world.h +++ b/src/world/pxl8_world.h @@ -2,17 +2,35 @@ #include "pxl8_entity.h" #include "pxl8_gfx.h" +#include "pxl8_gfx3d.h" #include "pxl8_math.h" #include "pxl8_net.h" #include "pxl8_sim.h" #include "pxl8_types.h" #include "pxl8_world_chunk.h" #include "pxl8_world_chunk_cache.h" +#include "pxl8_bsp_render.h" #ifdef __cplusplus extern "C" { #endif +#define PXL8_WORLD_MAX_LOADED_CHUNKS 25 + +typedef struct { + u16 leafs[16]; + u8 count; +} pxl8_edge_leafs; + +typedef struct pxl8_loaded_chunk { + pxl8_world_chunk* chunk; + pxl8_bsp_render_state* render_state; + pxl8_edge_leafs edges[4]; + i32 cx; + i32 cz; + bool active; +} pxl8_loaded_chunk; + typedef struct pxl8_world pxl8_world; pxl8_world* pxl8_world_create(void); @@ -20,10 +38,6 @@ void pxl8_world_destroy(pxl8_world* world); pxl8_world_chunk_cache* pxl8_world_get_chunk_cache(pxl8_world* world); pxl8_world_chunk* pxl8_world_active_chunk(pxl8_world* world); -void pxl8_world_set_active_chunk(pxl8_world* world, pxl8_world_chunk* chunk); - -pxl8_entity_pool* pxl8_world_entities(pxl8_world* world); -pxl8_entity pxl8_world_spawn(pxl8_world* world); bool pxl8_world_point_solid(const pxl8_world* world, f32 x, f32 y, f32 z); pxl8_ray pxl8_world_ray(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to); @@ -37,6 +51,7 @@ void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_ void pxl8_world_set_sim_config(pxl8_world* world, const pxl8_sim_config* config); void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z); +void pxl8_world_set_look(pxl8_world* world, f32 yaw, f32 pitch); pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world); pxl8_sim_world pxl8_world_sim_world(const pxl8_world* world, pxl8_vec3 pos); void pxl8_world_predict(pxl8_world* world, pxl8_net* net, const pxl8_input_msg* input, f32 dt); diff --git a/src/world/pxl8_world_chunk_cache.c b/src/world/pxl8_world_chunk_cache.c index 181e807..063dcd9 100644 --- a/src/world/pxl8_world_chunk_cache.c +++ b/src/world/pxl8_world_chunk_cache.c @@ -179,13 +179,8 @@ static pxl8_bsp* assembly_to_bsp(pxl8_world_chunk_assembly* a) { pxl8_stream s = pxl8_stream_create(a->data, (u32)a->data_size); pxl8_bsp_wire_header wire_hdr; - pxl8_protocol_deserialize_bsp_wire_header(a->data, 44, &wire_hdr); - s.offset = 44; - - pxl8_debug("[CLIENT] Wire header: verts=%u edges=%u faces=%u planes=%u nodes=%u leafs=%u surfedges=%u visdata=%u", - wire_hdr.num_vertices, wire_hdr.num_edges, wire_hdr.num_faces, - wire_hdr.num_planes, wire_hdr.num_nodes, wire_hdr.num_leafs, - wire_hdr.num_surfedges, wire_hdr.visdata_size); + pxl8_protocol_deserialize_bsp_wire_header(a->data, 48, &wire_hdr); + s.offset = 48; if (wire_hdr.num_vertices > 0) { bsp->vertices = pxl8_calloc(wire_hdr.num_vertices, sizeof(pxl8_bsp_vertex)); @@ -273,21 +268,42 @@ static pxl8_bsp* assembly_to_bsp(pxl8_world_chunk_assembly* a) { } } - pxl8_debug("Deserialized BSP: %u verts, %u faces, %u nodes, %u leafs", - bsp->num_vertices, bsp->num_faces, bsp->num_nodes, bsp->num_leafs); + if (wire_hdr.num_heightfield > 0) { + bsp->heightfield = pxl8_calloc(wire_hdr.num_heightfield, sizeof(f32)); + bsp->num_heightfield = wire_hdr.num_heightfield; + for (u32 i = 0; i < wire_hdr.num_heightfield; i++) { + u32 raw = pxl8_read_u32_be(&s); + memcpy(&bsp->heightfield[i], &raw, sizeof(f32)); + } + bsp->heightfield_w = pxl8_read_u16_be(&s); + bsp->heightfield_h = pxl8_read_u16_be(&s); + u32 ox_raw = pxl8_read_u32_be(&s); + memcpy(&bsp->heightfield_ox, &ox_raw, sizeof(f32)); + u32 oz_raw = pxl8_read_u32_be(&s); + memcpy(&bsp->heightfield_oz, &oz_raw, sizeof(f32)); + u32 cs_raw = pxl8_read_u32_be(&s); + memcpy(&bsp->heightfield_cell_size, &cs_raw, sizeof(f32)); + } + + u32 raw; + raw = pxl8_read_u32_be(&s); + memcpy(&bsp->bounds_min_x, &raw, sizeof(f32)); + raw = pxl8_read_u32_be(&s); + memcpy(&bsp->bounds_min_z, &raw, sizeof(f32)); + raw = pxl8_read_u32_be(&s); + memcpy(&bsp->bounds_max_x, &raw, sizeof(f32)); + raw = pxl8_read_u32_be(&s); + memcpy(&bsp->bounds_max_z, &raw, sizeof(f32)); return bsp; } static pxl8_result assemble_bsp(pxl8_world_chunk_cache* cache, pxl8_world_chunk_assembly* a) { - pxl8_debug("[CLIENT] assemble_bsp: id=%u data_size=%zu", a->id, a->data_size); pxl8_bsp* bsp = assembly_to_bsp(a); if (!bsp) { - pxl8_debug("[CLIENT] assemble_bsp: assembly_to_bsp returned NULL!"); assembly_reset(a); return PXL8_ERROR_INVALID_ARGUMENT; } - pxl8_debug("[CLIENT] assemble_bsp: BSP created with %u verts %u faces", bsp->num_vertices, bsp->num_faces); pxl8_world_chunk_cache_entry* entry = find_entry_bsp(cache, a->id); if (entry) {