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/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 3b2afa6..b5214a8 100644 --- a/demo/mod/first_person3d.fnl +++ b/demo/mod/first_person3d.fnl @@ -18,10 +18,6 @@ (local SIM_FLAG_GROUNDED 4) -(local door-spawn-x 860) -(local door-spawn-z 416) -(local door-spawn-yaw 1.5708) - (local sim-cfg (pxl8.sim_config {:move_speed 150 :gravity 600 :jump_velocity 180 @@ -35,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) @@ -44,27 +39,21 @@ (var cam-yaw 0) (var cam-z 416) (var camera nil) -(var ceiling-tex nil) -(var current-bsp-id 1) -(var floor-tex nil) (var land-squash 0) (var last-dt 0.016) (var light-time 0) (var glows nil) (var lights nil) -(var materials-for-chunk 0) +(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) @@ -81,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))) @@ -93,20 +81,6 @@ (let [chunk (world:active_chunk)] (and chunk (chunk:ready))))) -(fn setup-textures [bsp-id] - (if (= bsp-id 2) - (do - (set floor-tex (textures.rough-stone 44442 STONE_FLOOR_COLOR)) - (set wall-tex (textures.ashlar-wall 55552 ASHLAR_COLOR ASHLAR_MOSS)) - (set trim-tex (textures.rough-stone 77772 STONE_TRIM_COLOR)) - (set ceiling-tex (textures.plaster-wall 66662 PLASTER_COLOR))) - (do - (set floor-tex (textures.wood-planks 44444 WOOD_COLOR)) - (set wall-tex (textures.cobble-timber 55555 STONE_WALL_START MOSS_COLOR WOOD_COLOR)) - (set trim-tex (textures.wood-trim 77777 WOOD_COLOR)) - (set ceiling-tex (textures.plaster-wall 66666 PLASTER_COLOR)))) - (set current-bsp-id bsp-id)) - (fn init [] (pxl8.set_relative_mouse_mode true) (pxl8.load_palette "res/palettes/palette.ase") @@ -135,30 +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)) - - (setup-textures 1)) + (set smooth-cam-z cam-z))) (fn setup-materials [] (when (not world) (lua "return")) (when (not network) (lua "return")) - (let [net-chunk (network:chunk_id)] - (when (or (= net-chunk 0) (= materials-for-chunk net-chunk)) (lua "return")) - (when (not= current-bsp-id net-chunk) - (setup-textures net-chunk)) - (when (not floor-tex) (lua "return")) - (when (not wall-tex) (lua "return")) - (let [chunk (world:active_chunk)] - (when (not chunk) (lua "return")) - (when (not (chunk:ready)) (lua "return")) - (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 materials-for-chunk net-chunk))))) + (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 "`") @@ -172,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) @@ -186,47 +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 door-spawn-x 0 door-spawn-z) - (world:init_local_player door-spawn-x 0 door-spawn-z) - (world:set_look door-spawn-yaw 0) - (set cam-x door-spawn-x) - (set cam-y 0) - (set cam-z door-spawn-z) - (set cam-yaw door-spawn-yaw) - (set cam-pitch 0) - (set smooth-cam-x door-spawn-x) - (set smooth-cam-z door-spawn-z) - (setup-textures 2) - (set portal-cooldown 2.0)) - (= current-id 2) - (do - (pxl8.info "Door: BSP 2 -> BSP 1") - (network:enter_scene 1 1 door-spawn-x 0 door-spawn-z) - (world:init_local_player door-spawn-x 0 door-spawn-z) - (world:set_look door-spawn-yaw 0) - (set cam-x door-spawn-x) - (set cam-y 0) - (set cam-z door-spawn-z) - (set cam-yaw door-spawn-yaw) - (set cam-pitch 0) - (set smooth-cam-x door-spawn-x) - (set smooth-cam-z door-spawn-z) - (setup-textures 1) - (set portal-cooldown 2.0))))))) - (when world (let [input-msg (sample-input) player (world:local_player)] @@ -298,8 +237,7 @@ r2 (* 0.04 (math.sin (+ (* real-time 3.2) phase))) light-radius (* 150 (+ 0.95 r1 r2))] (lights:clear) - (when (= current-bsp-id 1) - (lights:add light-x light-y light-z 2 light-intensity light-radius)) + (lights:add light-x light-y light-z 2 light-intensity light-radius) (pxl8.push_target) (pxl8.begin_frame_3d camera lights { @@ -317,10 +255,8 @@ (pxl8.set_wireframe (menu.is-wireframe)) (world:render [smooth-cam-x eye-y smooth-cam-z]) - (when (and chunk (= current-bsp-id 1)) - (entities.render-fireball light-x light-y light-z (menu.is-wireframe))) - - (entities.render-door (menu.is-wireframe) 0) + (when chunk + (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..252494f 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,12 @@ 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, } impl BspBuilder { @@ -182,6 +187,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 +204,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,6 +218,12 @@ 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, }; @@ -227,6 +240,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 bbcb4fb..695045f 100644 --- a/pxl8d/src/main.rs +++ b/pxl8d/src/main.rs @@ -3,10 +3,12 @@ extern crate alloc; +use alloc::vec::Vec; use pxl8d::*; use pxl8d::chunk::ChunkId; use pxl8d::chunk::stream::ClientChunkState; -use pxl8d::procgen::LayoutMode; +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; @@ -74,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(); @@ -94,51 +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), - layout: LayoutMode::Courtyard, - ..ProcgenParams::default() - }), - _ => 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,14 +141,35 @@ 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; @@ -190,7 +179,7 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { } } - let send_limit = if burst { 256 } else { 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); @@ -205,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()); @@ -321,5 +301,17 @@ 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, @@ -120,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) { @@ -142,6 +168,37 @@ 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 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); @@ -179,7 +236,7 @@ fn build_bsp_node_grid(ctx: &mut BspBuildContext, x0: i32, y0: i32, x1: i32, y1: 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), @@ -197,7 +254,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), @@ -242,7 +299,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]; @@ -253,8 +310,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]; @@ -557,7 +614,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; @@ -627,25 +684,163 @@ 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 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 solid_floor_count = 0; + for y in 0..grid.height { + for x in 0..grid.width { + if grid.get(x, y) == 1 { + solid_floor_count += 1; + } + } + } + + let face_count = wall_count * 2 + floor_ceiling_count + doorway_extra + partition_faces + solid_floor_count; let vertex_count = face_count * 4; let total_cells = (grid.width * grid.height) as usize; @@ -668,119 +863,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); @@ -792,7 +986,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; @@ -818,7 +1012,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; @@ -834,7 +1028,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); @@ -846,7 +1040,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; @@ -872,7 +1066,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; @@ -894,8 +1088,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); @@ -909,7 +1103,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; @@ -927,6 +1121,21 @@ 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 fy = origin_z + y as f32 * CELL_SIZE; + let cell_idx = (y * 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, fy), Vec3::new(fx, 0.0, fy + CELL_SIZE), + Vec3::new(fx + CELL_SIZE, 0.0, fy + CELL_SIZE), Vec3::new(fx + CELL_SIZE, 0.0, fy)], + up, 0.0, 7, cell_idx); + } + } + } + bsp.vertices.truncate(vert_idx); bsp.faces.truncate(face_idx); bsp.edges.truncate(edge_idx); @@ -960,8 +1169,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; @@ -976,8 +1185,8 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) { leaf.num_marksurfaces = faces_per_cell[c] as u16; } else { leaf.contents = -1; - leaf.first_marksurface = 0; - leaf.num_marksurfaces = 0; + leaf.first_marksurface = cell_offset[c] as u16; + leaf.num_marksurfaces = faces_per_cell[c] as u16; } leaf.visofs = -1; } @@ -1000,6 +1209,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); @@ -1009,12 +1220,295 @@ 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.005, wz * 0.005, world_seed + 5000, 4) + }; + let blend = edge_blend(wx, wz, world_seed); + corner_heights[vz as usize * vw + vx as usize] = h * 64.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; } + + 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; + + 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); + } + } + + 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 { + for x in 0..grid.width { + let c = (0 * grid.width + x) as usize; + let fz = origin_z - CELL_SIZE; + bsp.leafs[c].mins[2] = fz as i16; + } + } + if border_south { + for x in 0..grid.width { + let c = ((grid.height - 1) * grid.width + x) as usize; + let fz = origin_z + (grid.height + 1) as f32 * CELL_SIZE; + bsp.leafs[c].maxs[2] = fz as i16; + } + } + if border_west { + for y in 0..grid.height { + let c = (y * grid.width + 0) as usize; + let fx = origin_x - CELL_SIZE; + bsp.leafs[c].mins[0] = fx as i16; + } + } + if border_east { + for y in 0..grid.height { + let c = (y * grid.width + grid.width - 1) as usize; + let fx = origin_x + (grid.width + 1) as f32 * CELL_SIZE; + bsp.leafs[c].maxs[0] = fx as i16; + } + } + + 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; +} + +pub fn generate_rooms(params: &ProcgenParams, doorways: &[Doorway], entries: &[ChunkEntry]) -> Bsp { let mut rng = Rng::new(params.seed); let mut grid = RoomGrid::new(params.width, params.height); @@ -1064,43 +1558,52 @@ 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); + let mut bsp = BspBuilder::new(); - grid_to_bsp(&mut bsp, &grid); + grid_to_bsp(&mut bsp, &grid, doorways, entries, &DUNGEON_MATS, params.origin_x, params.origin_z); 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_courtyard(params: &ProcgenParams) -> Bsp { +pub fn generate_courtyard(params: &ProcgenParams, entries: &[ChunkEntry]) -> Bsp { let mut rng = Rng::new(params.seed); let mut grid = RoomGrid::new(params.width, params.height); grid.fill(1); @@ -1196,48 +1699,130 @@ pub fn generate_courtyard(params: &ProcgenParams) -> Bsp { } } + carve_entries(&mut grid, entries); + let mut bsp = BspBuilder::new(); - grid_to_bsp(&mut bsp, &grid); + grid_to_bsp(&mut bsp, &grid, &[], entries, &COURTYARD_MATS, params.origin_x, params.origin_z); 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 room_cx = (room.x as f32 + room.w as f32 / 2.0) * CELL_SIZE; - let room_cz = (room.y as f32 + room.h as f32 / 2.0) * CELL_SIZE; - - let ddx = room_cx - fireball_pos.x; - let ddz = room_cz - fireball_pos.z; - if ddx * ddx + ddz * ddz < fireball_exclusion_radius * fireball_exclusion_radius { - return None; - } + 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 }; - - Some(LightSource { + LightSource { position: Vec3::new(room_cx, light_height, room_cz), intensity, radius, - }) + } }).collect(); - let mut lights = lights; - lights.push(LightSource { - position: Vec3::new(860.0, light_height, 416.0), - intensity: 1.2, - radius: 120.0, - }); - compute_bsp_vertex_lighting(&mut bsp, &lights); bsp.into() } -pub fn generate(params: &ProcgenParams) -> Bsp { - match params.layout { - LayoutMode::Dungeon => generate_rooms(params), - LayoutMode::Courtyard => generate_courtyard(params), +#[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 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, + }]; + compute_bsp_vertex_lighting(&mut bsp, &lights); + + bsp.into() +} + +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 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 }); + } + + match biome { + Biome::Dungeon => generate_rooms(¶ms, &doorways, &entries), + Biome::Courtyard => generate_courtyard(¶ms, &entries), + 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 13bb8c0..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}; +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(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 45be3fc..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" @@ -56,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; @@ -69,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) { @@ -173,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; @@ -298,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; @@ -321,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; } @@ -384,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; @@ -447,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; } @@ -478,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; @@ -553,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; @@ -567,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"); @@ -618,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_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..aedbb63 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,6 +125,12 @@ 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; } pxl8_bsp; 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 3ae384c..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) { @@ -254,11 +239,6 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { 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"); @@ -374,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 83c4122..4e85269 100644 --- a/src/core/pxl8_types.h +++ b/src/core/pxl8_types.h @@ -102,3 +102,4 @@ typedef struct pxl8_viewport { i32 scaled_width, scaled_height; f32 scale; } pxl8_viewport; + diff --git a/src/gfx/pxl8_gfx.c b/src/gfx/pxl8_gfx.c index 9b331e1..8960c43 100644 --- a/src/gfx/pxl8_gfx.c +++ b/src/gfx/pxl8_gfx.c @@ -107,12 +107,7 @@ 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; const u32* pal = pxl8_palette_colors(gfx->palette); @@ -397,11 +392,6 @@ 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; @@ -437,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; @@ -632,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; @@ -735,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; @@ -754,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; @@ -766,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, @@ -774,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, @@ -913,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 51267b7..bbdb2be 100644 --- a/src/gfx/pxl8_gfx.h +++ b/src/gfx/pxl8_gfx.h @@ -55,14 +55,12 @@ pxl8_colormap* pxl8_gfx_colormap(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 31003de..2296bae 100644 --- a/src/gfx/pxl8_render.c +++ b/src/gfx/pxl8_render.c @@ -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), @@ -543,7 +559,7 @@ static void rasterize_triangle( 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, @@ -589,7 +615,7 @@ static void rasterize_triangle( 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/gui/pxl8_gui.c b/src/gui/pxl8_gui.c index 26dd4d8..527578e 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; diff --git a/src/gui/pxl8_gui.h b/src/gui/pxl8_gui.h index 9dcf770..eb123ae 100644 --- a/src/gui/pxl8_gui.h +++ b/src/gui/pxl8_gui.h @@ -34,6 +34,9 @@ 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); #ifdef __cplusplus 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/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 ace4c1f..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; diff --git a/src/script/pxl8_script_ffi.h b/src/script/pxl8_script_ffi.h index 07ec16f..849b1bd 100644 --- a/src/script/pxl8_script_ffi.h +++ b/src/script/pxl8_script_ffi.h @@ -29,7 +29,6 @@ static const char* pxl8_ffi_cdefs = "u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);\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" @@ -451,6 +463,7 @@ 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" @@ -515,7 +528,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" @@ -549,7 +562,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" @@ -588,15 +604,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..9d97a4a 100644 --- a/src/sim/pxl8_sim.c +++ b/src/sim/pxl8_sim.c @@ -17,6 +17,7 @@ static i32 bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) { } bool pxl8_bsp_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos) { + if (!bsp) 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; @@ -50,20 +51,94 @@ pxl8_vec3 pxl8_bsp_trace(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, f32 return result; } +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 bool sim_point_solid(const pxl8_sim_world* world, f32 x, f32 y, f32 z) { + const pxl8_bsp* bsp = sim_bsp_at(world, x, z); + return pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z}); +} + +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 bool sim_point_clear(const pxl8_sim_world* world, f32 x, f32 y, f32 z, f32 radius) { + if (sim_point_solid(world, x, y, z)) return false; + if (sim_point_solid(world, x + radius, y, z)) return false; + if (sim_point_solid(world, x - radius, y, z)) return false; + if (sim_point_solid(world, x, y, z + radius)) return false; + if (sim_point_solid(world, x, y, z - radius)) return false; + return true; +} + +static f32 find_landing_y(const pxl8_sim_world* world, pxl8_vec3 pos, f32 target_y, f32 radius, f32 height) { + f32 hi = pos.y; + f32 lo = target_y; + for (i32 i = 0; i < 8; i++) { + f32 mid = (hi + lo) * 0.5f; + pxl8_vec3 test = {pos.x, mid, pos.z}; + pxl8_vec3 result = pxl8_sim_trace(world, pos, test, radius, height); + if (result.y > mid + 0.01f) { + lo = mid; + } else { + hi = mid; + } + } + return hi; +} + 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); + if (sim_point_clear(world, to.x, to.y, to.z, radius)) { + return to; } - return to; + bool x_ok = sim_point_clear(world, to.x, from.y, from.z, radius); + bool y_ok = sim_point_clear(world, from.x, to.y, from.z, radius); + bool z_ok = sim_point_clear(world, 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; } 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 +194,21 @@ 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; + new_pos.y = find_landing_y(world, new_pos, target.y, cfg->player_radius, cfg->player_height); + } + + 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 +249,21 @@ 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; + new_pos.y = find_landing_y(world, new_pos, target.y, cfg->player_radius, cfg->player_height); + } + + 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 5333269..c1d57e1 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,416 @@ 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++) { + if (bsp->leafs[i].contents == -1) continue; + const pxl8_bsp_leaf* leaf = &bsp->leafs[i]; + + 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; + 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; + i32 leaf = pxl8_bsp_find_leaf(lc->chunk->bsp, camera_pos); + if (leaf >= 0 && (u32)leaf < lc->chunk->bsp->num_leafs && + lc->chunk->bsp->leafs[leaf].contents != -1) { + 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}; + 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) { + 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; + } + + 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) { diff --git a/src/world/pxl8_world.h b/src/world/pxl8_world.h index 90a1609..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); diff --git a/src/world/pxl8_world_chunk_cache.c b/src/world/pxl8_world_chunk_cache.c index 181e807..203ff79 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,32 @@ 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)); + } 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) {