diff --git a/.clang-format b/.clang-format deleted file mode 100644 index 5b275ae..0000000 --- a/.clang-format +++ /dev/null @@ -1,41 +0,0 @@ -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 deleted file mode 100644 index fc7d04a..0000000 --- a/.clangd +++ /dev/null @@ -1,30 +0,0 @@ -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 b2ac494..5a08441 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -*.aseprite-extension -.cache -compile_commands.json +.ccls-cache/ +.ccls **.DS_Store + .pxl8_history +*.aseprite-extension diff --git a/demo/mod/entities.fnl b/demo/mod/entities.fnl index ca00a15..c4077e2 100644 --- a/demo/mod/entities.fnl +++ b/demo/mod/entities.fnl @@ -1,9 +1,36 @@ (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 [] @@ -118,15 +145,40 @@ (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))) + (create-fireball-mesh)) + (when (and (not door-tex) textures) + (set door-tex (textures.door)))) -(fn render-fireball [x y z] +(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] (when fireball-mesh (pxl8.draw_mesh fireball-mesh {:x x :y y :z z - :emissive true}))) + :emissive true + :wireframe wireframe}))) {:FIREBALL_COLOR FIREBALL_COLOR + :get-door-position get-door-position + :get-door-radius get-door-radius :init init - :render-fireball render-fireball} + :render-door render-door + :render-fireball render-fireball + :setup-lighting setup-lighting} diff --git a/demo/mod/first_person3d.fnl b/demo/mod/first_person3d.fnl index 68717d8..3b2afa6 100644 --- a/demo/mod/first_person3d.fnl +++ b/demo/mod/first_person3d.fnl @@ -8,8 +8,8 @@ (local sky (require :mod.sky)) (local textures (require :mod.textures)) -(local bob-amount 3.0) -(local bob-speed 6.0) +(local bob-amount 4.0) +(local bob-speed 8.0) (local cam-smoothing 0.25) (local land-recovery-speed 20) (local land-squash-amount -4) @@ -18,6 +18,10 @@ (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 @@ -31,6 +35,7 @@ (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) @@ -39,21 +44,27 @@ (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-set? false) +(var materials-for-chunk 0) (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 GRASS_COLOR 200) +(local PLASTER_COLOR 16) (local STONE_WALL_START 2) (local WOOD_COLOR 88) @@ -70,7 +81,8 @@ (when (not world) (set world (pxl8.get_world)) (when world - (world:set_sim_config sim-cfg)))) + (world:set_sim_config sim-cfg) + (world:init_local_player cam-x cam-y cam-z)))) (fn is-connected [] (and network (network:connected))) @@ -81,6 +93,20 @@ (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") @@ -109,38 +135,30 @@ (when world (world:init_local_player cam-x cam-y cam-z) (set smooth-cam-x cam-x) - (set smooth-cam-z cam-z))) + (set smooth-cam-z cam-z)) + + (setup-textures 1)) (fn setup-materials [] (when (not world) (lua "return")) (when (not network) (lua "return")) - (when materials-set? (lua "return")) - (when (not (network:has_chunk)) (lua "return")) - (let [chunk (world:active_chunk)] - (when (not chunk) (lua "return")) - (when (not (chunk:ready)) (lua "return")) - (let [dungeon-floor-tex (textures.wood-planks 44444 WOOD_COLOR) - dungeon-wall-tex (textures.cobble-timber 55555 STONE_WALL_START MOSS_COLOR WOOD_COLOR) - dungeon-trim-tex (textures.wood-trim 77777 WOOD_COLOR) - court-floor-tex (textures.rough-stone 44442 STONE_FLOOR_COLOR) - court-wall-tex (textures.ashlar-wall 55552 ASHLAR_COLOR ASHLAR_MOSS) - court-trim-tex (textures.rough-stone 77772 STONE_TRIM_COLOR) - grass-tex (textures.grass-top 44447 GRASS_COLOR) - dungeon-floor-mat (pxl8.create_material {:texture dungeon-floor-tex :lighting true :double_sided true}) - dungeon-wall-mat (pxl8.create_material {:texture dungeon-wall-tex :lighting true :double_sided true}) - dungeon-trim-mat (pxl8.create_material {:texture dungeon-trim-tex :lighting true :double_sided true}) - court-floor-mat (pxl8.create_material {:texture court-floor-tex :lighting true :double_sided true}) - court-wall-mat (pxl8.create_material {:texture court-wall-tex :lighting true :double_sided true}) - court-trim-mat (pxl8.create_material {:texture court-trim-tex :lighting true :double_sided true}) - grass-mat (pxl8.create_material {:texture grass-tex :lighting true :double_sided true})] - (world:set_bsp_material 0 dungeon-floor-mat) - (world:set_bsp_material 1 dungeon-wall-mat) - (world:set_bsp_material 3 dungeon-trim-mat) - (world:set_bsp_material 4 court-floor-mat) - (world:set_bsp_material 5 court-wall-mat) - (world:set_bsp_material 6 court-trim-mat) - (world:set_bsp_material 7 grass-mat) - (set materials-set? true)))) + (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))))) (fn sample-input [] (when (pxl8.key_pressed "`") @@ -154,6 +172,8 @@ (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) @@ -166,6 +186,47 @@ (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)] @@ -237,7 +298,8 @@ r2 (* 0.04 (math.sin (+ (* real-time 3.2) phase))) light-radius (* 150 (+ 0.95 r1 r2))] (lights:clear) - (lights:add light-x light-y light-z 2 light-intensity light-radius) + (when (= current-bsp-id 1) + (lights:add light-x light-y light-z 2 light-intensity light-radius)) (pxl8.push_target) (pxl8.begin_frame_3d camera lights { @@ -255,8 +317,10 @@ (pxl8.set_wireframe (menu.is-wireframe)) (world:render [smooth-cam-x eye-y smooth-cam-z]) - (when chunk - (entities.render-fireball light-x light-y light-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) (pxl8.end_frame_3d) diff --git a/docs/plans/2026-03-02-bsp-chunk-streaming-design.md b/docs/plans/2026-03-02-bsp-chunk-streaming-design.md deleted file mode 100644 index eefb10b..0000000 --- a/docs/plans/2026-03-02-bsp-chunk-streaming-design.md +++ /dev/null @@ -1,80 +0,0 @@ -# BSP Chunk Streaming Design - -Minecraft-style infinite procedural world using BSP geometry. The world is a grid of BSP chunks that generate and stream as the player explores. Both interior (dungeons, courtyards) and exterior (hills, cliffs) terrain use BSP representation. - -## Chunk Addressing - -`ChunkId::Bsp(u32)` becomes `ChunkId::Bsp { cx: i32, cz: i32 }`. Each chunk covers a 16x16 cell region (1024x1024 world units). World-space origin of chunk `(cx, cz)` is `(cx * 1024.0, cz * 1024.0)`. - -The `ChunkMessage` transport header already has `cx/cy/cz` fields (currently unused) — we start populating them. The `id` field becomes `hash(cx, cz)` for fragment assembly deduplication. - -## Procgen — Biome Selection - -The server holds a `world_seed: u64`. For each chunk at `(cx, cz)`: - -1. Sample noise at `(cx * scale, cz * scale)` with the world seed -2. Map noise value to biome: - - **Dungeon** (noise < -0.2): Indoor rooms with corridors, walls, ceiling. Existing `generate_rooms` logic. - - **Courtyard** (noise -0.2 to 0.2): Central open area with wing rooms. Existing `generate_courtyard` logic. - - **Exterior** (noise > 0.2): Open terrain with height variation, no ceiling, cliff walls at steep changes. -3. Per-chunk seed: `hash(world_seed, cx, cz)` for deterministic RNG. -4. All vertex positions emitted in world space: offset by `(cx * 1024.0, cz * 1024.0)`. - -## Procgen — Exterior Terrain - -New `generate_exterior` function: - -- Each cell gets a floor height from fine-grained noise at world-space position -- All cells open (no ceiling) -- Walls emitted between adjacent cells with height difference > 32 units (cliff faces) -- No walls at chunk edges — floor extends to boundary, noise is continuous across chunks -- Single directional light (sunlight) instead of per-room point lights -- Materials 7/8/9 (ground/cliff/grass) - -## Server Streaming - -Track player's chunk coordinate: `chunk_cx = floor(player.x / 1024)`, `chunk_cz = floor(player.z / 1024)`. - -Each tick, compute 3x3 ring around player vs `ClientChunkState.known`: - -- **New chunks in range**: Generate lazily, queue for streaming -- **Chunks leaving range**: No action — client LRU handles eviction -- **Active chunk**: The chunk the player stands in. `CHUNK_ENTER` sent when it changes. - -Streaming priority: player's chunk bursts at 64 fragments/tick, neighbors at 8 fragments/tick. - -## Client — Multi-Chunk Rendering & Collision - -**World struct**: Add `loaded_chunks[9]` array for the 3x3 ring. `active_chunk` remains the player's chunk (collision). - -**Sync**: Iterate chunk cache for the 3x3 ring around player's chunk coordinate. Populate `loaded_chunks`. - -**Rendering**: Iterate `loaded_chunks`, call `pxl8_bsp_render` for each. Vertices are in world space — no transform needed. Frustum-cull entire chunks before BSP rendering (chunk AABB: 1024x128x1024). - -**Collision**: `pxl8_sim_trace` checks `active_chunk` BSP. Swap active chunk when player crosses boundary. Cross-chunk collision is a future refinement. - -**BSP render state**: Store `pxl8_bsp_render_state*` inside each `pxl8_world_chunk`, created on arrival, destroyed on eviction. - -## Wire Protocol - -No new message types. Existing messages gain spatial meaning: - -- `ChunkMessage.cx/cz`: populated with grid coordinates -- `CHUNK_ENTER`: repacked payload as `cx: i32, cz: i32` (8 bytes) -- Fragment assembly keyed by `(cx, cz)` instead of `id` - -## Removals - -- `generate_connected`, `LayoutMode::Connected` — replaced by biome system -- `remap_bsp_materials` — each biome sets its own materials -- `carve_corridor_h_offset` / `carve_corridor_v_offset` — no more combined grids -- `main.rs` single-BSP generation on spawn — replaced by streaming around player position - -## What Stays - -- `generate_rooms` / `generate_courtyard` — used as chunk-level biome generators -- Door system (`pxl8_sim_door`, `door_angle`, `entities.fnl`) — chunk-local feature -- `grid_to_bsp` pipeline — gains `origin_x/origin_z` parameter for world-space offset -- `grid_to_bsp` heights parameter for exterior terrain elevation -- BSP PVS, cell portals — per-chunk visibility still works -- Chunk cache (512 entries), fragment assembly, UDP transport diff --git a/pxl8.sh b/pxl8.sh index 33e265d..2401f38 100755 --- a/pxl8.sh +++ b/pxl8.sh @@ -43,6 +43,10 @@ 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" @@ -163,37 +167,6 @@ 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" @@ -563,13 +536,11 @@ case "$COMMAND" in " if [[ "$HAS_SDL3" -eq 1 ]]; then - PXL8_SOURCE_FILES="$PXL8_SOURCE_FILES src/hal/pxl8_io_sdl3.c src/hal/pxl8_mem_sdl3.c" + PXL8_SOURCE_FILES="$PXL8_SOURCE_FILES 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 9653d62..c414f3b 100644 --- a/pxl8d/src/bsp.rs +++ b/pxl8d/src/bsp.rs @@ -1,3 +1,5 @@ +extern crate alloc; + use alloc::boxed::Box; use alloc::vec::Vec; @@ -144,7 +146,6 @@ pub struct Bsp { pub vertex_lights: Box<[u32]>, pub vertices: Box<[Vertex]>, pub visdata: Box<[u8]>, - pub heightfield: Box<[f32]>, } #[derive(Default)] @@ -160,16 +161,6 @@ pub struct BspBuilder { pub vertex_lights: Vec, pub vertices: Vec, pub visdata: Vec, - pub heightfield: Vec, - pub heightfield_w: u16, - pub heightfield_h: u16, - pub heightfield_ox: f32, - pub heightfield_oz: f32, - pub heightfield_cell_size: f32, - pub bounds_min_x: f32, - pub bounds_min_z: f32, - pub bounds_max_x: f32, - pub bounds_max_z: f32, } impl BspBuilder { @@ -191,7 +182,6 @@ 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 _ }, @@ -208,7 +198,6 @@ 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, @@ -222,17 +211,7 @@ impl From for Bsp { num_surfedges: surfedges.len() as u32, num_vertex_lights: vertex_lights.len() as u32, num_vertices: vertices.len() as u32, - num_heightfield: heightfield.len() as u32, - heightfield_ox: b.heightfield_ox, - heightfield_oz: b.heightfield_oz, - heightfield_cell_size: b.heightfield_cell_size, - heightfield_w: b.heightfield_w, - heightfield_h: b.heightfield_h, visdata_size: visdata.len() as u32, - bounds_min_x: b.bounds_min_x, - bounds_min_z: b.bounds_min_z, - bounds_max_x: b.bounds_max_x, - bounds_max_z: b.bounds_max_z, }; Self { @@ -248,7 +227,6 @@ impl From for Bsp { vertex_lights, vertices, visdata, - heightfield, } } } diff --git a/pxl8d/src/chunk.rs b/pxl8d/src/chunk.rs index 6ecbde1..6283fca 100644 --- a/pxl8d/src/chunk.rs +++ b/pxl8d/src/chunk.rs @@ -1,23 +1,38 @@ +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 { cx: i32, cz: i32 }, + Bsp(u32), } pub enum Chunk { - Bsp { cx: i32, cz: i32, bsp: Bsp, version: u32 }, + Bsp { id: u32, 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 daaa68f..6b5f66e 100644 --- a/pxl8d/src/chunk/stream.rs +++ b/pxl8d/src/chunk/stream.rs @@ -1,3 +1,5 @@ +extern crate alloc; + use alloc::collections::BTreeMap; use alloc::vec::Vec; @@ -26,11 +28,11 @@ impl ClientChunkState { } pub fn next_pending(&mut self) -> Option { - if self.pending.is_empty() { - None - } else { - Some(self.pending.remove(0)) - } + self.pending.pop() + } + + pub fn queue_message(&mut self, msg: ChunkMessage) { + self.pending_messages.push(msg); } pub fn queue_messages(&mut self, msgs: Vec) { @@ -48,6 +50,23 @@ 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 032aa31..b7fbb33 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::generate_chunk; +pub use procgen::{ProcgenParams, generate, generate_rooms}; pub use pxl8::*; pub use sim::*; pub use transport::*; diff --git a/pxl8d/src/main.rs b/pxl8d/src/main.rs index da3d2a9..bbcb4fb 100644 --- a/pxl8d/src/main.rs +++ b/pxl8d/src/main.rs @@ -3,12 +3,10 @@ extern crate alloc; -use alloc::vec::Vec; use pxl8d::*; use pxl8d::chunk::ChunkId; use pxl8d::chunk::stream::ClientChunkState; -use pxl8d::pxl8::pxl8_cmd_type::*; -use pxl8d::pxl8::pxl8_msg_type::*; +use pxl8d::procgen::LayoutMode; const TICK_RATE: u64 = 30; const TICK_NS: u64 = 1_000_000_000 / TICK_RATE; @@ -76,9 +74,6 @@ 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(); @@ -99,14 +94,51 @@ 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 == PXL8_MSG_INPUT as u8 => { + x if x == pxl8d::pxl8_msg_type::PXL8_MSG_INPUT as u8 => { latest_input = Some(transport.get_input()); } - x if x == PXL8_MSG_COMMAND as u8 => { + x if x == pxl8d::pxl8_msg_type::PXL8_MSG_COMMAND as u8 => { let cmd = transport.get_command(); - if cmd.cmd_type == PXL8_CMD_SPAWN_ENTITY as u16 { + if cmd.cmd_type == pxl8d::pxl8_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); } } _ => {} @@ -122,7 +154,7 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { } let mut count = 0; - sim.generate_snapshot(|state| { + sim.generate_snapshot(player_id.unwrap_or(u64::MAX), |state| { if count < entities_buf.len() { entities_buf[count] = *state; count += 1; @@ -141,35 +173,14 @@ 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((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; - } - + if let Some(_player) = sim.get_player_position(pid) { let mut burst = false; while let Some(chunk_id) = client_chunks.next_pending() { match chunk_id { - ChunkId::Bsp { cx, cz } => { + ChunkId::Bsp(id) => { if let Some(chunk) = sim.world.get(&chunk_id) { if let Some(bsp) = chunk.as_bsp() { - let msgs = bsp_to_messages(bsp, cx, cz, chunk.version()); + let msgs = bsp_to_messages(bsp, id, chunk.version()); client_chunks.queue_messages(msgs); client_chunks.mark_sent(chunk_id, chunk.version()); burst = true; @@ -179,7 +190,7 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { } } - let send_limit = if burst { 64 } else { 8 }; + let send_limit = if burst { 256 } else { 8 }; for _ in 0..send_limit { if let Some(msg) = client_chunks.next_message() { transport.send_chunk(&msg, sequence); @@ -194,21 +205,30 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { } } -fn bsp_to_messages(bsp: &bsp::Bsp, cx: i32, cz: i32, version: u32) -> Vec { +fn bsp_to_messages(bsp: &bsp::Bsp, id: u32, version: u32) -> alloc::vec::Vec { + use alloc::vec::Vec; + let mut data = Vec::new(); - 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()); + 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.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()); @@ -301,23 +321,5 @@ fn bsp_to_messages(bsp: &bsp::Bsp, cx: i32, cz: i32, version: u32) -> 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, @@ -144,8 +120,6 @@ 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) { @@ -168,76 +142,6 @@ fn carve_corridor_v(grid: &mut RoomGrid, y1: i32, y2: i32, x: i32) { } } -fn carve_entries(grid: &mut RoomGrid, entries: &[ChunkEntry]) { - for entry in entries { - match entry.edge { - ChunkEdge::West => { - for x in 0..grid.width { - if grid.get(x, entry.cell) == 0 { break; } - grid.set(x, entry.cell, 0); - } - } - ChunkEdge::East => { - for x in (0..grid.width).rev() { - if grid.get(x, entry.cell) == 0 { break; } - grid.set(x, entry.cell, 0); - } - } - ChunkEdge::North => { - for z in 0..grid.height { - if grid.get(entry.cell, z) == 0 { break; } - grid.set(entry.cell, z, 0); - } - } - ChunkEdge::South => { - for z in (0..grid.height).rev() { - if grid.get(entry.cell, z) == 0 { break; } - grid.set(entry.cell, z, 0); - } - } - } - } -} - -fn shrink_grid(grid: &RoomGrid, entries: &[ChunkEntry]) -> (RoomGrid, i32, i32) { - let (mut min_x, mut min_z, mut max_x, mut max_z) = (grid.width, grid.height, 0i32, 0i32); - for z in 0..grid.height { - for x in 0..grid.width { - if grid.get(x, z) == 0 { - if x < min_x { min_x = x; } - if x > max_x { max_x = x; } - if z < min_z { min_z = z; } - if z > max_z { max_z = z; } - } - } - } - - min_x = (min_x - 1).max(0); - min_z = (min_z - 1).max(0); - max_x = (max_x + 1).min(grid.width - 1); - max_z = (max_z + 1).min(grid.height - 1); - - for e in entries { - match e.edge { - ChunkEdge::West => min_x = 0, - ChunkEdge::East => max_x = grid.width - 1, - ChunkEdge::North => min_z = 0, - ChunkEdge::South => max_z = grid.height - 1, - } - } - - let tw = max_x - min_x + 1; - let th = max_z - min_z + 1; - let mut tight = RoomGrid::new(tw, th); - for z in 0..th { - for x in 0..tw { - tight.set(x, z, grid.get(x + min_x, z + min_z)); - } - } - - (tight, min_x, min_z) -} - fn compute_face_aabb(face: &mut Face, verts: &[Vertex], vert_idx: usize) { face.aabb_min = Vec3::new(1e30, 1e30, 1e30); face.aabb_max = Vec3::new(-1e30, -1e30, -1e30); @@ -275,7 +179,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 = ctx.origin_x + mid_x as f32 * CELL_SIZE; + let split_pos = mid_x as f32 * CELL_SIZE; ctx.bsp.planes[plane_idx as usize] = Plane { normal: Vec3::new(1.0, 0.0, 0.0), @@ -293,7 +197,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 = ctx.origin_z + mid_y as f32 * CELL_SIZE; + let split_pos = mid_y as f32 * CELL_SIZE; ctx.bsp.planes[plane_idx as usize] = Plane { normal: Vec3::new(0.0, 0.0, 1.0), @@ -338,7 +242,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, origin_x: f32, origin_z: f32) -> Vec { +fn build_cell_portals(grid: &RoomGrid) -> Vec { let total_cells = (grid.width * grid.height) as usize; let mut portals = vec![CellPortals::default(); total_cells]; @@ -349,8 +253,8 @@ fn build_cell_portals(grid: &RoomGrid, origin_x: f32, origin_z: f32) -> Vec 0 && grid.get(x - 1, y) == 0 { let p = &mut portals[c]; @@ -403,8 +307,6 @@ fn build_cell_portals(grid: &RoomGrid, origin_x: f32, origin_z: f32) -> Vec = (0..n).collect(); - order.sort_by(|&a, &b| { - let pa = &bsp.vertices[a].position; - let pb = &bsp.vertices[b].position; - let ax = (pa.x * 10.0) as i32; - let bx = (pb.x * 10.0) as i32; - let ay = (pa.y * 10.0) as i32; - let by = (pb.y * 10.0) as i32; - let az = (pa.z * 10.0) as i32; - let bz = (pb.z * 10.0) as i32; - ax.cmp(&bx).then(ay.cmp(&by)).then(az.cmp(&bz)) - }); - - let mut i = 0; - while i < n { - let mut j = i + 1; - let pi = &bsp.vertices[order[i]].position; - let ix = (pi.x * 10.0) as i32; - let iy = (pi.y * 10.0) as i32; - let iz = (pi.z * 10.0) as i32; - - while j < n { - let pj = &bsp.vertices[order[j]].position; - if (pj.x * 10.0) as i32 != ix || (pj.y * 10.0) as i32 != iy || (pj.z * 10.0) as i32 != iz { - break; - } - j += 1; - } - - if j - i > 1 { - let count = (j - i) as u32; - let mut sum_direct = 0u32; - let mut sum_ao = 0u32; - for k in i..j { - let l = bsp.vertex_lights[order[k]]; - sum_direct += (l >> 24) & 0xFF; - sum_ao += (l >> 16) & 0xFF; - } - let avg_direct = (sum_direct / count) as u8; - let avg_ao = (sum_ao / count) as u8; - for k in i..j { - let existing = bsp.vertex_lights[order[k]] & 0x0000FFFF; - bsp.vertex_lights[order[k]] = existing | ((avg_direct as u32) << 24) | ((avg_ao as u32) << 16); - } - } - - i = j; - } -} - -fn emit_quad( - bsp: &mut BspBuilder, - face_cell: &mut [u32], - vi: &mut usize, - fi: &mut usize, - ei: &mut usize, - p: [Vec3; 4], - normal: Vec3, - dist: f32, - material_id: u16, - cell_idx: u32, -) { - for i in 0..4 { - bsp.vertices[*vi + i].position = p[i]; - } - bsp.planes[*fi].normal = normal; - bsp.planes[*fi].dist = dist; - bsp.faces[*fi].plane_id = *fi as u16; - bsp.faces[*fi].num_edges = 4; - bsp.faces[*fi].first_edge = *ei as u32; - bsp.faces[*fi].material_id = material_id; - for i in 0..4 { - bsp.edges[*ei + i].vertex[0] = (*vi + i) as u16; - bsp.edges[*ei + i].vertex[1] = (*vi + ((i + 1) % 4)) as u16; - bsp.surfedges[*ei + i] = (*ei + i) as i32; - } - compute_face_aabb(&mut bsp.faces[*fi], &bsp.vertices, *vi); - face_cell[*fi] = cell_idx; - *vi += 4; - *ei += 4; - *fi += 1; -} - -fn is_entry_cell(entries: &[ChunkEntry], edge: ChunkEdge, cell: i32) -> bool { - entries.iter().any(|e| e.edge == edge && e.cell == cell) -} - -struct BspMaterials { - floor: u16, - wall: u16, - trim: u16, -} - -const DUNGEON_MATS: BspMaterials = BspMaterials { floor: 0, wall: 1, trim: 3 }; -const COURTYARD_MATS: BspMaterials = BspMaterials { floor: 4, wall: 5, trim: 6 }; - -fn is_adj_wall(grid: &RoomGrid, x: i32, y: i32) -> bool { - if x < 0 || x >= grid.width || y < 0 || y >= grid.height { return false; } - grid.get(x, y) == 1 && ( - grid.get(x - 1, y) == 0 || grid.get(x + 1, y) == 0 || - grid.get(x, y - 1) == 0 || grid.get(x, y + 1) == 0 - ) -} - -fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid, doorways: &[Doorway], entries: &[ChunkEntry], mats: &BspMaterials, origin_x: f32, origin_z: f32) { +fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) { 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 { - 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; } + 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; } floor_ceiling_count += 1; } } } - let mut filler_floor_count = 0; - let mut ext_wall_count = 0; - let mut wall_top_count = 0; - for y in 0..grid.height { - for x in 0..grid.width { - if grid.get(x, y) == 1 { - let adj_room = grid.get(x-1,y)==0 || grid.get(x+1,y)==0 - || grid.get(x,y-1)==0 || grid.get(x,y+1)==0; - if adj_room { - wall_top_count += 1; - if grid.get(x-1,y) != 0 && !is_adj_wall(grid, x-1, y) { ext_wall_count += 1; } - if grid.get(x+1,y) != 0 && !is_adj_wall(grid, x+1, y) { ext_wall_count += 1; } - if grid.get(x,y-1) != 0 && !is_adj_wall(grid, x, y-1) { ext_wall_count += 1; } - if grid.get(x,y+1) != 0 && !is_adj_wall(grid, x, y+1) { ext_wall_count += 1; } - } else { - filler_floor_count += 1; - } - } - } - } - - let face_count = wall_count * 2 + floor_ceiling_count + doorway_extra + partition_faces - + filler_floor_count + ext_wall_count * 2 + wall_top_count; + let face_count = wall_count * 2 + floor_ceiling_count; let vertex_count = face_count * 4; let total_cells = (grid.width * grid.height) as usize; @@ -925,118 +668,119 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid, doorways: &[Doorway], entr 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 fy = origin_z + y as f32 * CELL_SIZE; + let fx = x as f32 * CELL_SIZE; + let fy = y as f32 * CELL_SIZE; let cell_idx = (y * grid.width + x) as u32; - 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 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); - if left_solid { - let normal = Vec3::new(1.0, 0.0, 0.0); - let dist = fx; + bsp.planes[face_idx].normal = Vec3::new(1.0, 0.0, 0.0); + bsp.planes[face_idx].dist = fx; - 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); + 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; } - } 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); + + 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; } - if right_solid { - let wx = fx + CELL_SIZE; - let normal = Vec3::new(-1.0, 0.0, 0.0); - let dist = -wx; + 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 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); + 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; } + + 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 top_solid { + if grid.get(x, y - 1) == 1 { 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); @@ -1048,7 +792,7 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid, doorways: &[Doorway], entr 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 = mats.wall; + bsp.faces[face_idx].material_id = 1; for i in 0..4 { bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; @@ -1074,7 +818,7 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid, doorways: &[Doorway], entr 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 = mats.trim; + bsp.faces[face_idx].material_id = 3; for i in 0..4 { bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; @@ -1090,7 +834,7 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid, doorways: &[Doorway], entr face_idx += 1; } - if bottom_solid { + if grid.get(x, y + 1) == 1 { 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); @@ -1102,7 +846,7 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid, doorways: &[Doorway], entr 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 = mats.wall; + bsp.faces[face_idx].material_id = 1; for i in 0..4 { bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; @@ -1128,7 +872,7 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid, doorways: &[Doorway], entr 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 = mats.trim; + bsp.faces[face_idx].material_id = 3; for i in 0..4 { bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; @@ -1150,8 +894,8 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid, doorways: &[Doorway], entr 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 fy = origin_z + y as f32 * CELL_SIZE; + let fx = x as f32 * CELL_SIZE; + let fy = 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); @@ -1165,7 +909,7 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid, doorways: &[Doorway], entr 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 = mats.floor; + bsp.faces[face_idx].material_id = 0; for i in 0..4 { bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; @@ -1183,83 +927,6 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid, doorways: &[Doorway], entr } } - let up = Vec3::new(0.0, 1.0, 0.0); - for y in 0..grid.height { - for x in 0..grid.width { - if grid.get(x, y) == 1 { - let fx = origin_x + x as f32 * CELL_SIZE; - let fz = origin_z + y as f32 * CELL_SIZE; - let cell_idx = (y * grid.width + x) as u32; - - let adj_room = grid.get(x-1,y)==0 || grid.get(x+1,y)==0 - || grid.get(x,y-1)==0 || grid.get(x,y+1)==0; - if adj_room { - let room_ci = if grid.get(x-1,y)==0 { ((y)*grid.width+(x-1)) as u32 } - else if grid.get(x+1,y)==0 { ((y)*grid.width+(x+1)) as u32 } - else if grid.get(x,y-1)==0 { ((y-1)*grid.width+(x)) as u32 } - else { ((y+1)*grid.width+(x)) as u32 }; - - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(fx, WALL_HEIGHT, fz), Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fz), - Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fz + CELL_SIZE), Vec3::new(fx, WALL_HEIGHT, fz + CELL_SIZE)], - up, WALL_HEIGHT, mats.wall, room_ci); - - if grid.get(x-1,y) != 0 && !is_adj_wall(grid, x-1, y) { - let n = Vec3::new(-1.0, 0.0, 0.0); - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(fx, TRIM_HEIGHT, fz + CELL_SIZE), Vec3::new(fx, TRIM_HEIGHT, fz), - Vec3::new(fx, WALL_HEIGHT, fz), Vec3::new(fx, WALL_HEIGHT, fz + CELL_SIZE)], - n, -fx, mats.wall, room_ci); - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(fx, 0.0, fz + CELL_SIZE), Vec3::new(fx, 0.0, fz), - Vec3::new(fx, TRIM_HEIGHT, fz), Vec3::new(fx, TRIM_HEIGHT, fz + CELL_SIZE)], - n, -fx, mats.trim, room_ci); - } - if grid.get(x+1,y) != 0 && !is_adj_wall(grid, x+1, y) { - let wx = fx + CELL_SIZE; - let n = Vec3::new(1.0, 0.0, 0.0); - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(wx, TRIM_HEIGHT, fz), Vec3::new(wx, TRIM_HEIGHT, fz + CELL_SIZE), - Vec3::new(wx, WALL_HEIGHT, fz + CELL_SIZE), Vec3::new(wx, WALL_HEIGHT, fz)], - n, wx, mats.wall, room_ci); - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(wx, 0.0, fz), Vec3::new(wx, 0.0, fz + CELL_SIZE), - Vec3::new(wx, TRIM_HEIGHT, fz + CELL_SIZE), Vec3::new(wx, TRIM_HEIGHT, fz)], - n, wx, mats.trim, room_ci); - } - if grid.get(x,y-1) != 0 && !is_adj_wall(grid, x, y-1) { - let n = Vec3::new(0.0, 0.0, -1.0); - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fz), Vec3::new(fx, TRIM_HEIGHT, fz), - Vec3::new(fx, WALL_HEIGHT, fz), Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fz)], - n, -fz, mats.wall, room_ci); - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(fx + CELL_SIZE, 0.0, fz), Vec3::new(fx, 0.0, fz), - Vec3::new(fx, TRIM_HEIGHT, fz), Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fz)], - n, -fz, mats.trim, room_ci); - } - if grid.get(x,y+1) != 0 && !is_adj_wall(grid, x, y+1) { - let wz = fz + CELL_SIZE; - let n = Vec3::new(0.0, 0.0, 1.0); - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(fx, TRIM_HEIGHT, wz), Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, wz), - Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, wz), Vec3::new(fx, WALL_HEIGHT, wz)], - n, wz, mats.wall, room_ci); - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(fx, 0.0, wz), Vec3::new(fx + CELL_SIZE, 0.0, wz), - Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, wz), Vec3::new(fx, TRIM_HEIGHT, wz)], - n, wz, mats.trim, room_ci); - } - } else { - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(fx, 0.0, fz), Vec3::new(fx, 0.0, fz + CELL_SIZE), - Vec3::new(fx + CELL_SIZE, 0.0, fz + CELL_SIZE), Vec3::new(fx + CELL_SIZE, 0.0, fz)], - up, 0.0, 7, cell_idx); - } - } - } - } - bsp.vertices.truncate(vert_idx); bsp.faces.truncate(face_idx); bsp.edges.truncate(edge_idx); @@ -1293,8 +960,8 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid, doorways: &[Doorway], entr 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 fx = x as f32 * CELL_SIZE; + let fz = y as f32 * CELL_SIZE; leaf.mins[0] = fx as i16; leaf.mins[1] = 0; @@ -1305,16 +972,13 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid, doorways: &[Doorway], entr if grid.get(x, y) == 0 { leaf.contents = -2; + leaf.first_marksurface = cell_offset[c] as u16; + leaf.num_marksurfaces = faces_per_cell[c] as u16; } else { - let adjacent_to_room = - grid.get(x - 1, y) == 0 || - grid.get(x + 1, y) == 0 || - grid.get(x, y - 1) == 0 || - grid.get(x, y + 1) == 0; - leaf.contents = if adjacent_to_room { -1 } else { 0 }; + leaf.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; } } @@ -1336,8 +1000,6 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid, doorways: &[Doorway], entr 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); @@ -1347,439 +1009,12 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid, doorways: &[Doorway], entr ctx.bsp.nodes.truncate(node_count); ctx.bsp.planes.truncate(plane_count); - let portals = build_cell_portals(grid, origin_x, origin_z); + let portals = build_cell_portals(grid); build_pvs_data(ctx.bsp, &portals); ctx.bsp.cell_portals = portals; } -fn edge_blend(wx: f32, wz: f32, world_seed: u64) -> f32 { - let chunk_size = 16.0 * CELL_SIZE; - let zone = 3.0 * CELL_SIZE; - let vcx = libm::floorf(wx / chunk_size) as i32; - let vcz = libm::floorf(wz / chunk_size) as i32; - let mut blend = 1.0f32; - - for dz in -1..=1_i32 { - for dx in -1..=1_i32 { - let ncx = vcx + dx; - let ncz = vcz + dz; - if select_biome(ncx, ncz, world_seed) != Biome::Exterior { - let co = ncx as f32 * chunk_size; - let ce = co + chunk_size; - let coz = ncz as f32 * chunk_size; - let cez = coz + chunk_size; - let dist_x = if wx < co { co - wx } else if wx > ce { wx - ce } else { 0.0 }; - let dist_z = if wz < coz { coz - wz } else if wz > cez { wz - cez } else { 0.0 }; - let dist = if dist_x > dist_z { dist_x } else { dist_z }; - if dist < zone { - let t = dist / zone; - blend = blend.min(t * t * (3.0 - 2.0 * t)); - } - } - } - } - - blend -} - -fn grid_to_bsp_exterior(bsp: &mut BspBuilder, grid: &RoomGrid, origin_x: f32, origin_z: f32, world_seed: u64) { - let total_cells = (grid.width * grid.height) as usize; - let chunk_size = 16.0 * CELL_SIZE; - let cx = libm::floorf(origin_x / chunk_size) as i32; - let cz = libm::floorf(origin_z / chunk_size) as i32; - - let border_north = select_biome(cx, cz - 1, world_seed) != Biome::Exterior; - let border_south = select_biome(cx, cz + 1, world_seed) != Biome::Exterior; - let border_west = select_biome(cx - 1, cz, world_seed) != Biome::Exterior; - let border_east = select_biome(cx + 1, cz, world_seed) != Biome::Exterior; - - let vw = (grid.width + 1) as usize; - let vh = (grid.height + 1) as usize; - let mut corner_heights = vec![0.0f32; vw * vh]; - for vz in 0..vh as i32 { - for vx in 0..vw as i32 { - let wx = origin_x + vx as f32 * CELL_SIZE; - let wz = origin_z + vz as f32 * CELL_SIZE; - let h = unsafe { - crate::pxl8::pxl8_fbm(wx * 0.002, wz * 0.002, world_seed + 5000, 4) - }; - let blend = edge_blend(wx, wz, world_seed); - corner_heights[vz as usize * vw + vx as usize] = h * 128.0 * blend; - } - } - - let mut border_face_count = 0i32; - if border_north { border_face_count += grid.width; } - if border_south { border_face_count += grid.width; } - if border_west { border_face_count += grid.height; } - if border_east { border_face_count += grid.height; } - if border_north && border_west { border_face_count += 1; } - if border_north && border_east { border_face_count += 1; } - if border_south && border_west { border_face_count += 1; } - if border_south && border_east { border_face_count += 1; } - - let bg_north = if border_north { generate_building_grid(cx, cz - 1, world_seed) } else { None }; - let bg_south = if border_south { generate_building_grid(cx, cz + 1, world_seed) } else { None }; - let bg_west = if border_west { generate_building_grid(cx - 1, cz, world_seed) } else { None }; - let bg_east = if border_east { generate_building_grid(cx + 1, cz, world_seed) } else { None }; - - let mut building_face_count = 0usize; - for bg in [&bg_north, &bg_south, &bg_west, &bg_east] { - if let Some(g) = bg { - for y in 0..g.height { - for x in 0..g.width { - if g.get(x, y) != 0 && !is_adj_wall(g, x, y) { - building_face_count += 1; - } - } - } - } - } - - let mut face_count = 0; - for y in 0..grid.height { - for x in 0..grid.width { - if grid.get(x, y) == 0 { - face_count += 1; - } - } - } - face_count += border_face_count as usize + building_face_count; - - let vertex_count = face_count * 4; - let max_nodes = 2 * total_cells + 1; - let total_planes = face_count + max_nodes + 1; - - bsp.vertices = vec![Vertex::default(); vertex_count]; - bsp.faces = vec![Face::default(); face_count]; - bsp.planes = vec![Plane::default(); total_planes]; - bsp.edges = vec![Edge::default(); vertex_count]; - bsp.surfedges = vec![0i32; vertex_count]; - bsp.nodes = vec![Node::default(); max_nodes]; - - let mut face_cell = vec![0u32; face_count]; - let mut vert_idx = 0usize; - let mut face_idx = 0usize; - let mut edge_idx = 0usize; - - for y in 0..grid.height { - for x in 0..grid.width { - if grid.get(x, y) == 0 { - let fx = origin_x + x as f32 * CELL_SIZE; - let fz = origin_z + y as f32 * CELL_SIZE; - let cell_idx = (y * grid.width + x) as u32; - - let h00 = corner_heights[y as usize * vw + x as usize]; - let h10 = corner_heights[y as usize * vw + x as usize + 1]; - let h01 = corner_heights[(y as usize + 1) * vw + x as usize]; - let h11 = corner_heights[(y as usize + 1) * vw + x as usize + 1]; - - let nx = -(h10 - h00); - let ny = CELL_SIZE; - let nz = -(h01 - h00); - let len = sqrtf(nx * nx + ny * ny + nz * nz); - let normal = Vec3::new(nx / len, ny / len, nz / len); - let dist = normal.x * fx + normal.y * h00 + normal.z * fz; - - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(fx, h00, fz), Vec3::new(fx + CELL_SIZE, h10, fz), - Vec3::new(fx + CELL_SIZE, h11, fz + CELL_SIZE), Vec3::new(fx, h01, fz + CELL_SIZE)], - normal, dist, 7, cell_idx); - } - } - } - - let up = Vec3::new(0.0, 1.0, 0.0); - if border_north { - for x in 0..grid.width { - let fx = origin_x + x as f32 * CELL_SIZE; - let fz_outer = origin_z - CELL_SIZE; - let fz_inner = origin_z; - let h0 = corner_heights[0 * vw + x as usize]; - let h1 = corner_heights[0 * vw + x as usize + 1]; - let cell_idx = (0 * grid.width + x) as u32; - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(fx, 0.0, fz_outer), Vec3::new(fx + CELL_SIZE, 0.0, fz_outer), - Vec3::new(fx + CELL_SIZE, h1, fz_inner), Vec3::new(fx, h0, fz_inner)], - up, 0.0, 7, cell_idx); - } - } - if border_south { - let last_row = grid.height as usize; - for x in 0..grid.width { - let fx = origin_x + x as f32 * CELL_SIZE; - let fz_inner = origin_z + grid.height as f32 * CELL_SIZE; - let fz_outer = fz_inner + CELL_SIZE; - let h0 = corner_heights[last_row * vw + x as usize]; - let h1 = corner_heights[last_row * vw + x as usize + 1]; - let cell_idx = ((grid.height - 1) * grid.width + x) as u32; - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(fx, h0, fz_inner), Vec3::new(fx + CELL_SIZE, h1, fz_inner), - Vec3::new(fx + CELL_SIZE, 0.0, fz_outer), Vec3::new(fx, 0.0, fz_outer)], - up, 0.0, 7, cell_idx); - } - } - if border_west { - for y in 0..grid.height { - let fx_outer = origin_x - CELL_SIZE; - let fx_inner = origin_x; - let fz = origin_z + y as f32 * CELL_SIZE; - let h0 = corner_heights[y as usize * vw + 0]; - let h1 = corner_heights[(y as usize + 1) * vw + 0]; - let cell_idx = (y * grid.width + 0) as u32; - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(fx_outer, 0.0, fz), Vec3::new(fx_inner, h0, fz), - Vec3::new(fx_inner, h1, fz + CELL_SIZE), Vec3::new(fx_outer, 0.0, fz + CELL_SIZE)], - up, 0.0, 7, cell_idx); - } - } - if border_east { - let last_col = grid.width as usize; - for y in 0..grid.height { - let fx_inner = origin_x + grid.width as f32 * CELL_SIZE; - let fx_outer = fx_inner + CELL_SIZE; - let fz = origin_z + y as f32 * CELL_SIZE; - let h0 = corner_heights[y as usize * vw + last_col]; - let h1 = corner_heights[(y as usize + 1) * vw + last_col]; - let cell_idx = (y * grid.width + grid.width - 1) as u32; - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(fx_inner, h0, fz), Vec3::new(fx_outer, 0.0, fz), - Vec3::new(fx_outer, 0.0, fz + CELL_SIZE), Vec3::new(fx_inner, h1, fz + CELL_SIZE)], - up, 0.0, 7, cell_idx); - } - } - - if border_north && border_west { - let fx_outer = origin_x - CELL_SIZE; - let fx_inner = origin_x; - let fz_outer = origin_z - CELL_SIZE; - let fz_inner = origin_z; - let h = corner_heights[0]; - let cell_idx = 0u32; - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(fx_outer, 0.0, fz_outer), Vec3::new(fx_inner, 0.0, fz_outer), - Vec3::new(fx_inner, h, fz_inner), Vec3::new(fx_outer, 0.0, fz_inner)], - up, 0.0, 7, cell_idx); - } - if border_north && border_east { - let last_col = grid.width as usize; - let fx_inner = origin_x + grid.width as f32 * CELL_SIZE; - let fx_outer = fx_inner + CELL_SIZE; - let fz_outer = origin_z - CELL_SIZE; - let fz_inner = origin_z; - let h = corner_heights[last_col]; - let cell_idx = (grid.width - 1) as u32; - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(fx_inner, 0.0, fz_outer), Vec3::new(fx_outer, 0.0, fz_outer), - Vec3::new(fx_outer, 0.0, fz_inner), Vec3::new(fx_inner, h, fz_inner)], - up, 0.0, 7, cell_idx); - } - if border_south && border_west { - let last_row = grid.height as usize; - let fx_outer = origin_x - CELL_SIZE; - let fx_inner = origin_x; - let fz_inner = origin_z + grid.height as f32 * CELL_SIZE; - let fz_outer = fz_inner + CELL_SIZE; - let h = corner_heights[last_row * vw]; - let cell_idx = ((grid.height - 1) * grid.width) as u32; - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(fx_outer, 0.0, fz_inner), Vec3::new(fx_inner, h, fz_inner), - Vec3::new(fx_inner, 0.0, fz_outer), Vec3::new(fx_outer, 0.0, fz_outer)], - up, 0.0, 7, cell_idx); - } - if border_south && border_east { - let last_row = grid.height as usize; - let last_col = grid.width as usize; - let fx_inner = origin_x + grid.width as f32 * CELL_SIZE; - let fx_outer = fx_inner + CELL_SIZE; - let fz_inner = origin_z + grid.height as f32 * CELL_SIZE; - let fz_outer = fz_inner + CELL_SIZE; - let h = corner_heights[last_row * vw + last_col]; - let cell_idx = ((grid.height - 1) * grid.width + grid.width - 1) as u32; - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(fx_inner, h, fz_inner), Vec3::new(fx_outer, 0.0, fz_inner), - Vec3::new(fx_outer, 0.0, fz_outer), Vec3::new(fx_inner, 0.0, fz_outer)], - up, 0.0, 7, cell_idx); - } - - if let Some(ref bg) = bg_north { - let bg_oz = (cz - 1) as f32 * chunk_size; - for by in 0..bg.height { - for bx in 0..bg.width { - if bg.get(bx, by) != 0 && !is_adj_wall(bg, bx, by) { - let fx = origin_x + bx as f32 * CELL_SIZE; - let fz = bg_oz + by as f32 * CELL_SIZE; - let cell_idx = bx as u32; - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(fx, 0.0, fz), Vec3::new(fx + CELL_SIZE, 0.0, fz), - Vec3::new(fx + CELL_SIZE, 0.0, fz + CELL_SIZE), Vec3::new(fx, 0.0, fz + CELL_SIZE)], - up, 0.0, 7, cell_idx); - } - } - } - } - if let Some(ref bg) = bg_south { - let bg_oz = (cz + 1) as f32 * chunk_size; - for by in 0..bg.height { - for bx in 0..bg.width { - if bg.get(bx, by) != 0 && !is_adj_wall(bg, bx, by) { - let fx = origin_x + bx as f32 * CELL_SIZE; - let fz = bg_oz + by as f32 * CELL_SIZE; - let cell_idx = ((grid.height - 1) * grid.width + bx) as u32; - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(fx, 0.0, fz), Vec3::new(fx + CELL_SIZE, 0.0, fz), - Vec3::new(fx + CELL_SIZE, 0.0, fz + CELL_SIZE), Vec3::new(fx, 0.0, fz + CELL_SIZE)], - up, 0.0, 7, cell_idx); - } - } - } - } - if let Some(ref bg) = bg_west { - let bg_ox = (cx - 1) as f32 * chunk_size; - for by in 0..bg.height { - for bx in 0..bg.width { - if bg.get(bx, by) != 0 && !is_adj_wall(bg, bx, by) { - let fx = bg_ox + bx as f32 * CELL_SIZE; - let fz = origin_z + by as f32 * CELL_SIZE; - let cell_idx = (by * grid.width) as u32; - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(fx, 0.0, fz), Vec3::new(fx + CELL_SIZE, 0.0, fz), - Vec3::new(fx + CELL_SIZE, 0.0, fz + CELL_SIZE), Vec3::new(fx, 0.0, fz + CELL_SIZE)], - up, 0.0, 7, cell_idx); - } - } - } - } - if let Some(ref bg) = bg_east { - let bg_ox = (cx + 1) as f32 * chunk_size; - for by in 0..bg.height { - for bx in 0..bg.width { - if bg.get(bx, by) != 0 && !is_adj_wall(bg, bx, by) { - let fx = bg_ox + bx as f32 * CELL_SIZE; - let fz = origin_z + by as f32 * CELL_SIZE; - let cell_idx = (by * grid.width + grid.width - 1) as u32; - emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx, - [Vec3::new(fx, 0.0, fz), Vec3::new(fx + CELL_SIZE, 0.0, fz), - Vec3::new(fx + CELL_SIZE, 0.0, fz + CELL_SIZE), Vec3::new(fx, 0.0, fz + CELL_SIZE)], - up, 0.0, 7, cell_idx); - } - } - } - } - - bsp.vertices.truncate(vert_idx); - bsp.faces.truncate(face_idx); - bsp.edges.truncate(edge_idx); - bsp.surfedges.truncate(edge_idx); - - for f in &mut bsp.faces[..face_idx] { - compute_face_aabb(f, &bsp.vertices, f.first_edge as usize); - } - - bsp.leafs = vec![Leaf::default(); total_cells]; - bsp.marksurfaces = Vec::new(); - - let mut ms_idx = 0u16; - for y in 0..grid.height { - for x in 0..grid.width { - let c = (y * grid.width + x) as usize; - let leaf = &mut bsp.leafs[c]; - - let fx = origin_x + x as f32 * CELL_SIZE; - let fz = origin_z + y as f32 * CELL_SIZE; - - let h00 = corner_heights[y as usize * vw + x as usize]; - let h10 = corner_heights[y as usize * vw + x as usize + 1]; - let h01 = corner_heights[(y as usize + 1) * vw + x as usize]; - let h11 = corner_heights[(y as usize + 1) * vw + x as usize + 1]; - let h_min = h00.min(h10).min(h01).min(h11); - let h_max = h00.max(h10).max(h01).max(h11); - - leaf.mins[0] = fx as i16; - leaf.mins[1] = (h_min - 64.0) as i16; - leaf.mins[2] = fz as i16; - leaf.maxs[0] = (fx + CELL_SIZE) as i16; - leaf.maxs[1] = (h_max + 64.0) as i16; - leaf.maxs[2] = (fz + CELL_SIZE) as i16; - - leaf.contents = if grid.get(x, y) == 0 { 0 } else { -1 }; - leaf.first_marksurface = ms_idx; - leaf.num_marksurfaces = 0; - - for fi in 0..face_idx { - if face_cell[fi] == c as u32 { - bsp.marksurfaces.push(fi as u16); - leaf.num_marksurfaces += 1; - ms_idx += 1; - } - } - } - } - - if border_north { - let ext = if bg_north.is_some() { chunk_size } else { CELL_SIZE }; - for x in 0..grid.width { - let c = (0 * grid.width + x) as usize; - bsp.leafs[c].mins[2] = (origin_z - ext) as i16; - if bg_north.is_some() { bsp.leafs[c].mins[1] = -64; } - } - } - if border_south { - let ext = if bg_south.is_some() { chunk_size } else { CELL_SIZE }; - for x in 0..grid.width { - let c = ((grid.height - 1) * grid.width + x) as usize; - bsp.leafs[c].maxs[2] = (origin_z + grid.height as f32 * CELL_SIZE + ext) as i16; - if bg_south.is_some() { bsp.leafs[c].mins[1] = -64; } - } - } - if border_west { - let ext = if bg_west.is_some() { chunk_size } else { CELL_SIZE }; - for y in 0..grid.height { - let c = (y * grid.width + 0) as usize; - bsp.leafs[c].mins[0] = (origin_x - ext) as i16; - if bg_west.is_some() { bsp.leafs[c].mins[1] = -64; } - } - } - if border_east { - let ext = if bg_east.is_some() { chunk_size } else { CELL_SIZE }; - for y in 0..grid.height { - let c = (y * grid.width + grid.width - 1) as usize; - bsp.leafs[c].maxs[0] = (origin_x + grid.width as f32 * CELL_SIZE + ext) as i16; - if bg_east.is_some() { bsp.leafs[c].mins[1] = -64; } - } - } - - let face_count_final = face_idx; - - let mut ctx = BspBuildContext { - bsp, - grid, - node_count: 0, - plane_offset: face_count_final as u32, - origin_x, - origin_z, - }; - - build_bsp_node(&mut ctx, 0, 0, grid.width, grid.height, 0, face_count_final as i32); - - let node_count = ctx.node_count as usize; - let plane_count = ctx.plane_offset as usize; - ctx.bsp.nodes.truncate(node_count); - ctx.bsp.planes.truncate(plane_count); - - let portals = build_cell_portals(grid, origin_x, origin_z); - build_pvs_data(ctx.bsp, &portals); - ctx.bsp.cell_portals = portals; - - ctx.bsp.heightfield = corner_heights; - ctx.bsp.heightfield_w = vw as u16; - ctx.bsp.heightfield_h = vh as u16; - ctx.bsp.heightfield_ox = origin_x; - ctx.bsp.heightfield_oz = origin_z; - ctx.bsp.heightfield_cell_size = CELL_SIZE; -} - -fn generate_dungeon_grid(params: &ProcgenParams, doorways: &[Doorway], entries: &[ChunkEntry]) -> (RoomGrid, Vec) { +pub fn generate_rooms(params: &ProcgenParams) -> Bsp { let mut rng = Rng::new(params.seed); let mut grid = RoomGrid::new(params.width, params.height); @@ -1829,93 +1064,43 @@ fn generate_dungeon_grid(params: &ProcgenParams, doorways: &[Doorway], entries: } } - for door in doorways { - let cx_at = ((door.wall_x - params.origin_x) / CELL_SIZE) as i32; - let cz = ((door.wall_z - params.origin_z) / CELL_SIZE) as i32; - if cz < 0 || cz >= grid.height { continue; } - - let cx_left = cx_at - 1; - if cx_left >= 0 && cx_left < grid.width { - grid.set(cx_left, cz, 0); - } - if cx_at >= 0 && cx_at < grid.width { - grid.set(cx_at, cz, 0); - } - } - - carve_entries(&mut grid, entries); - - (grid, rooms) -} - -pub fn generate_rooms(params: &ProcgenParams, doorways: &[Doorway], entries: &[ChunkEntry], world_seed: u64) -> Bsp { - let (grid, rooms) = generate_dungeon_grid(params, doorways, entries); - - let (tight, x_off, z_off) = shrink_grid(&grid, entries); - let tight_origin_x = params.origin_x + x_off as f32 * CELL_SIZE; - let tight_origin_z = params.origin_z + z_off as f32 * CELL_SIZE; - - let tight_entries: Vec = entries.iter().map(|e| { - let cell = match e.edge { - ChunkEdge::West | ChunkEdge::East => e.cell - z_off, - ChunkEdge::North | ChunkEdge::South => e.cell - x_off, - }; - ChunkEntry { edge: e.edge, cell } - }).collect(); - let mut bsp = BspBuilder::new(); - grid_to_bsp(&mut bsp, &tight, doorways, &tight_entries, &DUNGEON_MATS, tight_origin_x, tight_origin_z); - - bsp.bounds_min_x = tight_origin_x; - bsp.bounds_min_z = tight_origin_z; - bsp.bounds_max_x = tight_origin_x + tight.width as f32 * CELL_SIZE; - bsp.bounds_max_z = tight_origin_z + tight.height as f32 * CELL_SIZE; - - let vw = 17usize; - let vh = 17usize; - bsp.heightfield = vec![0.0f32; vw * vh]; - for vz in 0..vh as i32 { - for vx in 0..vw as i32 { - let wx = params.origin_x + vx as f32 * CELL_SIZE; - let wz = params.origin_z + vz as f32 * CELL_SIZE; - let h = unsafe { crate::pxl8::pxl8_fbm(wx * 0.002, wz * 0.002, world_seed + 5000, 4) }; - let blend = edge_blend(wx, wz, world_seed); - bsp.heightfield[vz as usize * vw + vx as usize] = h * 128.0 * blend; - } - } - bsp.heightfield_w = 17; - bsp.heightfield_h = 17; - bsp.heightfield_ox = params.origin_x; - bsp.heightfield_oz = params.origin_z; - bsp.heightfield_cell_size = CELL_SIZE; + grid_to_bsp(&mut bsp, &grid); let light_height = 80.0; + let fireball_pos = Vec3::new(384.0, light_height, 324.0); + let fireball_exclusion_radius = 150.0; - 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 { + 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 { position: Vec3::new(cx, light_height, cz), intensity: 1.5, radius: 160.0, - } + }) }).collect(); - 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, - }); - } + 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() } -fn generate_courtyard_grid(params: &ProcgenParams, entries: &[ChunkEntry]) -> (RoomGrid, Vec) { +pub fn generate_courtyard(params: &ProcgenParams) -> Bsp { let mut rng = Rng::new(params.seed); let mut grid = RoomGrid::new(params.width, params.height); grid.fill(1); @@ -2011,221 +1196,48 @@ fn generate_courtyard_grid(params: &ProcgenParams, entries: &[ChunkEntry]) -> (R } } - carve_entries(&mut grid, entries); - - (grid, rooms) -} - -pub fn generate_courtyard(params: &ProcgenParams, entries: &[ChunkEntry], world_seed: u64) -> Bsp { - let (grid, rooms) = generate_courtyard_grid(params, entries); - - let (tight, x_off, z_off) = shrink_grid(&grid, entries); - let tight_origin_x = params.origin_x + x_off as f32 * CELL_SIZE; - let tight_origin_z = params.origin_z + z_off as f32 * CELL_SIZE; - - let tight_entries: Vec = entries.iter().map(|e| { - let cell = match e.edge { - ChunkEdge::West | ChunkEdge::East => e.cell - z_off, - ChunkEdge::North | ChunkEdge::South => e.cell - x_off, - }; - ChunkEntry { edge: e.edge, cell } - }).collect(); - let mut bsp = BspBuilder::new(); - grid_to_bsp(&mut bsp, &tight, &[], &tight_entries, &COURTYARD_MATS, tight_origin_x, tight_origin_z); - - bsp.bounds_min_x = tight_origin_x; - bsp.bounds_min_z = tight_origin_z; - bsp.bounds_max_x = tight_origin_x + tight.width as f32 * CELL_SIZE; - bsp.bounds_max_z = tight_origin_z + tight.height as f32 * CELL_SIZE; - - let vw = 17usize; - let vh = 17usize; - bsp.heightfield = vec![0.0f32; vw * vh]; - for vz in 0..vh as i32 { - for vx in 0..vw as i32 { - let wx = params.origin_x + vx as f32 * CELL_SIZE; - let wz = params.origin_z + vz as f32 * CELL_SIZE; - let h = unsafe { crate::pxl8::pxl8_fbm(wx * 0.002, wz * 0.002, world_seed + 5000, 4) }; - let blend = edge_blend(wx, wz, world_seed); - bsp.heightfield[vz as usize * vw + vx as usize] = h * 128.0 * blend; - } - } - bsp.heightfield_w = 17; - bsp.heightfield_h = 17; - bsp.heightfield_ox = params.origin_x; - bsp.heightfield_oz = params.origin_z; - bsp.heightfield_cell_size = CELL_SIZE; + grid_to_bsp(&mut bsp, &grid); 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 }; - LightSource { + + Some(LightSource { position: Vec3::new(room_cx, light_height, room_cz), intensity, radius, - } + }) }).collect(); - compute_bsp_vertex_lighting(&mut bsp, &lights); - - bsp.into() -} - -#[derive(Clone, Copy, PartialEq)] -pub enum Biome { - Dungeon, - Courtyard, - Exterior, -} - -pub fn select_biome(cx: i32, cz: i32, seed: u64) -> Biome { - if cx == 0 && cz == 0 { return Biome::Dungeon; } - if cx == 1 && cz == 0 { return Biome::Courtyard; } - - let n = unsafe { crate::pxl8::pxl8_fbm(cx as f32 * 0.1, cz as f32 * 0.1, seed, 3) }; - if n < 0.4 { - Biome::Dungeon - } else { - Biome::Exterior - } -} - -fn chunk_seed(world_seed: u64, cx: i32, cz: i32) -> u32 { - let h = (world_seed as u32) - .wrapping_mul(374761393) - .wrapping_add(cx as u32) - .wrapping_mul(668265263) - .wrapping_add(cz as u32); - h ^ (h >> 16) -} - -pub fn generate_exterior(params: &ProcgenParams, world_seed: u64) -> Bsp { - let mut grid = RoomGrid::new(params.width, params.height); - - for y in 0..params.height { - for x in 0..params.width { - grid.set(x, y, 0); - } - } - - let mut bsp = BspBuilder::new(); - grid_to_bsp_exterior(&mut bsp, &grid, params.origin_x, params.origin_z, world_seed); - - let chunk_size = 16.0 * CELL_SIZE; - let cx = libm::floorf(params.origin_x / chunk_size) as i32; - let cz = libm::floorf(params.origin_z / chunk_size) as i32; - - let mut lights = vec![LightSource { - position: Vec3::new( - params.origin_x + params.width as f32 * CELL_SIZE * 0.5, - 200.0, - params.origin_z + params.height as f32 * CELL_SIZE * 0.5, - ), - intensity: 2.0, - radius: 2000.0, - }]; - - for &(dx, dz) in &[(0, -1), (0, 1), (-1, 0), (1, 0)] { - if select_biome(cx + dx, cz + dz, world_seed) != Biome::Exterior { - lights.push(LightSource { - position: Vec3::new( - (cx + dx) as f32 * chunk_size + chunk_size * 0.5, - 200.0, - (cz + dz) as f32 * chunk_size + chunk_size * 0.5, - ), - intensity: 2.0, - radius: 2000.0, - }); - } - } + 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() } -fn generate_building_grid(cx: i32, cz: i32, world_seed: u64) -> Option { - let biome = select_biome(cx, cz, world_seed); - if biome == Biome::Exterior { return None; } - - let seed = chunk_seed(world_seed, cx, cz); - let origin_x = cx as f32 * 16.0 * CELL_SIZE; - let origin_z = cz as f32 * 16.0 * CELL_SIZE; - let params = ProcgenParams { - seed, - origin_x, - origin_z, - ..Default::default() - }; - - let (doorways, entries) = get_chunk_config(cx, cz); - - let (grid, _) = match biome { - Biome::Dungeon => generate_dungeon_grid(¶ms, &doorways, &entries), - Biome::Courtyard => generate_courtyard_grid(¶ms, &entries), - _ => return None, - }; - - Some(grid) -} - -fn get_chunk_config(cx: i32, cz: i32) -> (Vec, Vec) { - let mut doorways = Vec::new(); - let mut entries = Vec::new(); - - if cx == 0 && cz == 0 { - doorways.push(Doorway { - wall_x: 832.0, - wall_z: 384.0, - z_start: 392.0, - z_end: 440.0, - height: 96.0, - dir: -1, - }); - doorways.push(Doorway { - wall_x: 832.0, - wall_z: 384.0, - z_start: 392.0, - z_end: 440.0, - height: 96.0, - dir: 1, - }); - entries.push(ChunkEntry { edge: ChunkEdge::East, cell: 6 }); - } - - if cx == 1 && cz == 0 { - entries.push(ChunkEntry { edge: ChunkEdge::West, cell: 6 }); - entries.push(ChunkEntry { edge: ChunkEdge::East, cell: 8 }); - entries.push(ChunkEntry { edge: ChunkEdge::North, cell: 8 }); - entries.push(ChunkEntry { edge: ChunkEdge::South, cell: 8 }); - } - - (doorways, entries) -} - -pub fn generate_chunk(cx: i32, cz: i32, world_seed: u64) -> Bsp { - let biome = select_biome(cx, cz, world_seed); - let seed = chunk_seed(world_seed, cx, cz); - let origin_x = cx as f32 * 16.0 * CELL_SIZE; - let origin_z = cz as f32 * 16.0 * CELL_SIZE; - - let params = ProcgenParams { - seed, - origin_x, - origin_z, - ..Default::default() - }; - - let (doorways, entries) = get_chunk_config(cx, cz); - - match biome { - Biome::Dungeon => generate_rooms(¶ms, &doorways, &entries, world_seed), - Biome::Courtyard => generate_courtyard(¶ms, &entries, world_seed), - Biome::Exterior => generate_exterior(¶ms, world_seed), +pub fn generate(params: &ProcgenParams) -> Bsp { + match params.layout { + LayoutMode::Dungeon => generate_rooms(params), + LayoutMode::Courtyard => generate_courtyard(params), } } diff --git a/pxl8d/src/sim.rs b/pxl8d/src/sim.rs index 01caf43..51d8ba9 100644 --- a/pxl8d/src/sim.rs +++ b/pxl8d/src/sim.rs @@ -1,6 +1,7 @@ +extern crate alloc; + use alloc::vec::Vec; -use crate::chunk::ChunkId; use crate::math::Vec3; use crate::pxl8::*; use crate::world::World; @@ -95,6 +96,13 @@ 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; @@ -106,8 +114,22 @@ impl Simulation { self.integrate(dt); } - fn sim_config() -> pxl8_sim_config { - pxl8_sim_config { + 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 { move_speed: 180.0, ground_accel: 10.0, air_accel: 1.0, @@ -118,39 +140,7 @@ 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 { @@ -166,7 +156,18 @@ impl Simulation { } fn move_player(&mut self, input: &pxl8_input_msg, dt: f32) { - let cfg = Self::sim_config(); + 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 Some(id) = self.player else { return }; let ent = &self.entities[id as usize]; if ent.flags & ALIVE == 0 { @@ -180,7 +181,7 @@ impl Simulation { } } - pub fn generate_snapshot(&self, mut writer: F) + pub fn generate_snapshot(&self, _player_id: u64, mut writer: F) where F: FnMut(&pxl8_entity_state), { @@ -210,6 +211,10 @@ 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 654bda4..3d12652 100644 --- a/pxl8d/src/transport.rs +++ b/pxl8d/src/transport.rs @@ -1,18 +1,16 @@ +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, @@ -27,17 +25,16 @@ pub struct ChunkMessage { } impl ChunkMessage { - pub fn from_bsp(data: Vec, cx: i32, cz: i32, version: u32) -> Vec { + pub fn from_bsp(data: Vec, chunk_id: u32, 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, - cx, + id: chunk_id, + cx: 0, cy: 0, - cz, + cz: 0, version, flags: CHUNK_FLAG_FINAL, fragment_idx: 0, @@ -56,10 +53,10 @@ impl ChunkMessage { messages.push(ChunkMessage { chunk_type: CHUNK_TYPE_BSP, - id, - cx, + id: chunk_id, + cx: 0, cy: 0, - cz, + cz: 0, version, flags: if is_final { CHUNK_FLAG_FINAL } else { 0 }, fragment_idx: i as u8, @@ -365,7 +362,7 @@ impl Transport { sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr); } - pub fn send_chunk_enter(&mut self, cx: i32, cz: i32, sequence: u32) { + pub fn send_chunk_enter(&mut self, chunk_id: u32, chunk_type: u8, sequence: u32) { if !self.has_client { return; } @@ -381,13 +378,34 @@ impl Transport { offset += self.serialize_header(&msg_header, offset); let buf = &mut self.send_buf[offset..]; - buf[0..4].copy_from_slice(&cx.to_be_bytes()); - buf[4..8].copy_from_slice(&cz.to_be_bytes()); + buf[0..4].copy_from_slice(&chunk_id.to_be_bytes()); + buf[4] = chunk_type; + buf[5] = 0; + buf[6] = 0; + buf[7] = 0; 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 3d3e48a..13bb8c0 100644 --- a/pxl8d/src/world.rs +++ b/pxl8d/src/world.rs @@ -1,12 +1,15 @@ +extern crate alloc; + use alloc::collections::BTreeMap; use crate::chunk::{Chunk, ChunkId}; -use crate::procgen::generate_chunk; +use crate::math::Vec3; +use crate::procgen::{ProcgenParams, generate}; pub struct World { active: Option, chunks: BTreeMap, - pub seed: u64, + seed: u64, } impl World { @@ -18,26 +21,67 @@ 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 generate_bsp(&mut self, cx: i32, cz: i32) -> &Chunk { - let chunk_id = ChunkId::Bsp { cx, cz }; + 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); if !self.chunks.contains_key(&chunk_id) { - let bsp = generate_chunk(cx, cz, self.seed); - self.chunks.insert(chunk_id, Chunk::Bsp { cx, cz, bsp, version: 1 }); + 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 }); } 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 4ba9cd4..45be3fc 100644 --- a/src/asset/pxl8_cart.c +++ b/src/asset/pxl8_cart.c @@ -1,9 +1,12 @@ #include "pxl8_cart.h" +#include #include +#include #include +#include +#include -#include "pxl8_io.h" #include "pxl8_log.h" #include "pxl8_mem.h" @@ -53,6 +56,11 @@ 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; @@ -61,48 +69,41 @@ 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 (pxl8_io_file_exists(path)) return true; + if (access(path, F_OK) == 0) return true; snprintf(path, sizeof(path), "%s/main.lua", base_path); - 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; + return access(path, F_OK) == 0; } static void collect_files_recursive(const char* dir_path, const char* prefix, char*** paths, u32* count, u32* capacity) { - collect_ctx ctx = { .dir_path = dir_path, .prefix = prefix, - .paths = paths, .count = count, .capacity = capacity }; - pxl8_io_enumerate_directory(dir_path, collect_entry, &ctx); + 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); } static pxl8_cart_file* find_file(const pxl8_cart* cart, const char* path) { @@ -172,8 +173,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 (pxl8_io_is_directory(path)) { - cart->base_path = pxl8_io_get_real_path(path); + if (is_directory(path)) { + cart->base_path = realpath(path, NULL); if (!cart->base_path) { pxl8_error("Failed to resolve cart path: %s", path); return PXL8_ERROR_FILE_NOT_FOUND; @@ -297,8 +298,8 @@ pxl8_result pxl8_cart_mount(pxl8_cart* cart) { } if (cart->is_folder) { - pxl8_original_cwd = pxl8_io_get_cwd(); - if (!pxl8_io_set_cwd(cart->base_path)) { + pxl8_original_cwd = getcwd(NULL, 0); + if (chdir(cart->base_path) != 0) { pxl8_error("Failed to change to cart directory: %s", cart->base_path); pxl8_free(pxl8_original_cwd); pxl8_original_cwd = NULL; @@ -320,7 +321,7 @@ void pxl8_cart_unmount(pxl8_cart* cart) { if (!cart || !cart->is_mounted) return; if (pxl8_original_cwd) { - pxl8_io_set_cwd(pxl8_original_cwd); + chdir(pxl8_original_cwd); pxl8_free(pxl8_original_cwd); pxl8_original_cwd = NULL; } @@ -383,7 +384,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 pxl8_io_file_exists(full_path); + return access(full_path, F_OK) == 0; } return find_file(cart, path) != NULL; @@ -446,7 +447,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 (!pxl8_io_is_directory(folder_path)) { + if (!is_directory(folder_path)) { pxl8_error("Cart folder not found: %s", folder_path); return PXL8_ERROR_FILE_NOT_FOUND; } @@ -477,9 +478,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]); - usize fsize = pxl8_io_get_file_size(full_path); - if (fsize > 0) { - file_sizes[i] = (u32)fsize; + struct stat st; + if (stat(full_path, &st) == 0) { + file_sizes[i] = (u32)st.st_size; total_size += file_sizes[i]; } else { file_sizes[i] = 0; @@ -552,10 +553,9 @@ pxl8_result pxl8_cart_bundle(const char* input_path, const char* output_path, co u32 cart_size = 0; bool free_cart = false; - if (pxl8_io_is_directory(input_path)) { + if (is_directory(input_path)) { char temp_pxc[256]; - static u32 bundle_counter = 0; - snprintf(temp_pxc, sizeof(temp_pxc), "/tmp/pxl8_bundle_%u.pxc", bundle_counter++); + snprintf(temp_pxc, sizeof(temp_pxc), "/tmp/pxl8_bundle_%d.pxc", getpid()); 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); - remove(temp_pxc); + unlink(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); - pxl8_io_set_executable(output_path); + chmod(output_path, 0755); 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 77d3b50..5233869 100644 --- a/src/asset/pxl8_save.c +++ b/src/asset/pxl8_save.c @@ -1,15 +1,23 @@ #include "pxl8_save.h" -#include #include +#include #include +#include +#include -#include "pxl8_io.h" -#include "pxl8_log.h" -#include "pxl8_macros.h" -#include "pxl8_mem.h" - +#ifdef _WIN32 +#include +#include +#define PATH_SEP '\\' +#else +#include +#include #define PATH_SEP '/' +#endif + +#include "pxl8_log.h" +#include "pxl8_mem.h" typedef struct { u32 magic; @@ -41,6 +49,23 @@ 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; @@ -50,17 +75,40 @@ pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) { save->magic = magic; save->version = version; - char* pref_path = pxl8_io_get_pref_path("pxl8", game_name); - if (!pref_path) { + 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) { pxl8_free(save); return NULL; } - pxl8_strncpy(save->directory, pref_path, sizeof(save->directory)); - pxl8_free(pref_path); - usize dir_len = strlen(save->directory); - if (dir_len > 0 && save->directory[dir_len - 1] == '/') { - save->directory[dir_len - 1] = '\0'; + 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; } pxl8_info("Save system initialized: %s", save->directory); @@ -68,7 +116,9 @@ pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) { } void pxl8_save_destroy(pxl8_save* save) { - pxl8_free(save); + if (save) { + pxl8_free(save); + } } pxl8_result pxl8_save_write(pxl8_save* save, u8 slot, const u8* data, u32 size) { @@ -184,7 +234,9 @@ pxl8_result pxl8_save_read(pxl8_save* save, u8 slot, u8** data_out, u32* size_ou } void pxl8_save_free(u8* data) { - pxl8_free(data); + if (data) { + pxl8_free(data); + } } bool pxl8_save_exists(pxl8_save* save, u8 slot) { @@ -196,7 +248,8 @@ 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)); - return pxl8_io_file_exists(path); + struct stat st; + return stat(path, &st) == 0; } 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 27131ff..0a2136d 100644 --- a/src/bsp/pxl8_bsp.c +++ b/src/bsp/pxl8_bsp.c @@ -342,7 +342,6 @@ 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 3fdb06b..71fd615 100644 --- a/src/bsp/pxl8_bsp.h +++ b/src/bsp/pxl8_bsp.h @@ -110,7 +110,6 @@ typedef struct pxl8_bsp { u32* vertex_lights; pxl8_bsp_vertex* vertices; u8* visdata; - f32* heightfield; u32 lightdata_size; u32 num_cell_portals; @@ -125,17 +124,7 @@ typedef struct pxl8_bsp { u32 num_surfedges; u32 num_vertex_lights; u32 num_vertices; - u32 num_heightfield; - f32 heightfield_ox; - f32 heightfield_oz; - f32 heightfield_cell_size; - u16 heightfield_w; - u16 heightfield_h; u32 visdata_size; - f32 bounds_min_x; - f32 bounds_min_z; - f32 bounds_max_x; - f32 bounds_max_z; } pxl8_bsp; #ifdef __cplusplus diff --git a/src/bsp/pxl8_bsp_render.c b/src/bsp/pxl8_bsp_render.c index 08d61ed..8afe551 100644 --- a/src/bsp/pxl8_bsp_render.c +++ b/src/bsp/pxl8_bsp_render.c @@ -7,6 +7,15 @@ #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; @@ -31,6 +40,83 @@ 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]; @@ -142,38 +228,190 @@ void pxl8_bsp_render_state_destroy(pxl8_bsp_render_state* state) { pxl8_free(state); } -void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, - 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; +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; - const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx); - if (!frustum) return; - - pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384); + pxl8_mesh* mesh = pxl8_mesh_create(64, 192); if (!mesh) return; - u8 ambient = pxl8_gfx_get_ambient(gfx); - pxl8_mat4 identity = pxl8_mat4_identity(); + 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) { + if (!gfx || !bsp || !state || bsp->num_faces == 0) return; + if (!state->materials || state->num_materials == 0) 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 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]; - 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); + + 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) { - 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_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}; } } + pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384); + if (!mesh) { + pxl8_free(visited); + pxl8_free(cell_windows); + pxl8_free(queue); + 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; + + 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_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 7002343..3c4cb4e 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, - const pxl8_gfx_draw_opts* opts); + 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); 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 cca8f93..3ae384c 100644 --- a/src/core/pxl8.c +++ b/src/core/pxl8.c @@ -3,8 +3,12 @@ #define PXL8_VERSION "0.1.0" #include +#include #include +#include +#include + #include "pxl8_ase.h" #include "pxl8_game.h" #include "pxl8_hal.h" @@ -146,11 +150,20 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { } if (bundle_mode) { - return pxl8_cart_bundle(pack_input, pack_output, argv[0]); + 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; } if (pack_mode) { - return pxl8_cart_pack(pack_input, pack_output); + pxl8_result result = pxl8_cart_pack(pack_input, pack_output); + return result; } if (remap_palette_mode) { @@ -178,14 +191,14 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { } const char* cart_path = script_arg; - char* original_cwd = pxl8_io_get_cwd(); + char* original_cwd = getcwd(NULL, 0); bool load_embedded = has_embedded && !run_mode; bool load_from_path = false; if (!load_embedded && run_mode) { if (!cart_path) { - if (pxl8_io_file_exists("main.fnl") || pxl8_io_file_exists("main.lua")) { + if (access("main.fnl", F_OK) == 0 || access("main.lua", F_OK) == 0) { cart_path = "."; } else { pxl8_error("no main.fnl or main.lua found in current directory"); @@ -193,7 +206,9 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { return PXL8_ERROR_INITIALIZATION_FAILED; } } - load_from_path = pxl8_io_is_directory(cart_path) || strstr(cart_path, ".pxc"); + struct stat st; + load_from_path = (stat(cart_path, &st) == 0 && S_ISDIR(st.st_mode)) || + strstr(cart_path, ".pxc"); } if (load_embedded || load_from_path) { @@ -239,6 +254,11 @@ 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"); @@ -354,13 +374,18 @@ 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 && !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)) { + 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); } - pxl8_repl_signal_complete(sys->repl); } } diff --git a/src/core/pxl8_io.c b/src/core/pxl8_io.c index 47db00b..1a0206c 100644 --- a/src/core/pxl8_io.c +++ b/src/core/pxl8_io.c @@ -81,9 +81,46 @@ 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) { - pxl8_free(data); + if (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 04bf6bd..7ad1063 100644 --- a/src/core/pxl8_io.h +++ b/src/core/pxl8_io.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "pxl8_bytes.h" #include "pxl8_types.h" @@ -9,20 +10,11 @@ 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); -char* pxl8_io_get_cwd(void); +void pxl8_io_free_file_content(char* content); 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 348c828..e378005 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[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_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_RESET "\033[0m" static pxl8_log* g_log = NULL; diff --git a/src/core/pxl8_queue.h b/src/core/pxl8_queue.h index ae14c44..f2206d5 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 512 +#define PXL8_QUEUE_CAPACITY 256 typedef struct pxl8_queue { void* items[PXL8_QUEUE_CAPACITY]; diff --git a/src/core/pxl8_types.h b/src/core/pxl8_types.h index 4e85269..83c4122 100644 --- a/src/core/pxl8_types.h +++ b/src/core/pxl8_types.h @@ -102,4 +102,3 @@ 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 8960c43..9b331e1 100644 --- a/src/gfx/pxl8_gfx.c +++ b/src/gfx/pxl8_gfx.c @@ -107,7 +107,12 @@ i32 pxl8_gfx_get_height(const pxl8_gfx* gfx) { return gfx ? gfx->framebuffer_height : 0; } -static void pxl8_gfx_resolve(pxl8_gfx* gfx) { +u32* pxl8_gfx_get_output(pxl8_gfx* gfx) { + if (!gfx) return NULL; + return gfx->output; +} + +void pxl8_gfx_resolve(pxl8_gfx* gfx) { if (!gfx || !gfx->initialized) return; const u32* pal = pxl8_palette_colors(gfx->palette); @@ -392,6 +397,11 @@ 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; @@ -427,6 +437,10 @@ 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; @@ -618,7 +632,7 @@ void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt) { } } -static pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_shader_uniforms* uniforms) { +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; @@ -721,11 +735,6 @@ 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; @@ -745,7 +754,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, const pxl8_gfx_draw_opts* opts) { +void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material) { if (!gfx || !mesh || !model || !material) return; if (!pxl8_gfx_handle_valid(gfx->frame_pass)) return; @@ -757,13 +766,6 @@ 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, @@ -772,9 +774,7 @@ void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* mo .alpha_test = false, .alpha_ref = 0, }, - .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 }, + .depth = { .test = true, .write = true, .compare = PXL8_GFX_COMPARE_LESS }, .dither = material->dither, .double_sided = material->double_sided, .emissive = material->emissive, @@ -913,6 +913,11 @@ 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 bbdb2be..51267b7 100644 --- a/src/gfx/pxl8_gfx.h +++ b/src/gfx/pxl8_gfx.h @@ -55,12 +55,14 @@ 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 e462951..d5e2689 100644 --- a/src/gfx/pxl8_gfx3d.h +++ b/src/gfx/pxl8_gfx3d.h @@ -4,7 +4,6 @@ #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" @@ -30,14 +29,16 @@ 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, const pxl8_gfx_draw_opts* opts); +void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material); 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 2296bae..31003de 100644 --- a/src/gfx/pxl8_render.c +++ b/src/gfx/pxl8_render.c @@ -279,7 +279,6 @@ 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, @@ -290,7 +289,6 @@ 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; @@ -298,10 +296,6 @@ 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; @@ -459,7 +453,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 && !stencil_test && !stencil_write) { + if (depth_test && depth_compare == PXL8_GFX_COMPARE_LESS && !blend_enabled) { 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); @@ -510,7 +504,7 @@ static void rasterize_triangle( if (!(mask & (0x8 << (i * 4)))) continue; u8 color = colors[i]; if (!(alpha_test && color <= alpha_ref) && color != 0) { - if (color_write) prow[px + i] = color; + prow[px + i] = color; if (depth_write) zrow[px + i] = (u16)z16_arr[i]; } } @@ -522,21 +516,11 @@ 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), @@ -559,7 +543,7 @@ static void rasterize_triangle( out_color = blend_colors(pipeline, color, prow[px], palette); } - if (color_write) prow[px] = out_color; + prow[px] = out_color; if (depth_write) { zrow[px] = z16; } @@ -578,21 +562,11 @@ 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, @@ -615,7 +589,7 @@ static void rasterize_triangle( out_color = blend_colors(pipeline, color, prow[px], palette); } - if (color_write) prow[px] = out_color; + prow[px] = out_color; if (depth_write) { zrow[px] = z16; } @@ -765,10 +739,6 @@ 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; @@ -797,7 +767,6 @@ 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]; @@ -832,7 +801,6 @@ 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; @@ -848,10 +816,21 @@ 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; @@ -1091,6 +1070,16 @@ 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; @@ -1106,6 +1095,11 @@ 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)); @@ -1408,7 +1402,7 @@ static void execute_draw( } u64 raster_start = pxl8_get_ticks_ns(); - rasterize_triangle(&setup, fb, zb, r->stencil, fb_w, shader, &pip->desc, + rasterize_triangle(&setup, fb, zb, fb_w, shader, &pip->desc, &shader_bindings, &shader_uniforms); r->stats.raster_ns += pxl8_get_ticks_ns() - raster_start; } @@ -1427,7 +1421,6 @@ 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; @@ -1491,12 +1484,6 @@ 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 a66bdcb..d8db971 100644 --- a/src/gfx/pxl8_render.h +++ b/src/gfx/pxl8_render.h @@ -14,6 +14,10 @@ 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); @@ -31,9 +35,13 @@ 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); @@ -53,7 +61,6 @@ 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 7bd6184..6790bc7 100644 --- a/src/gfx/pxl8_render_types.h +++ b/src/gfx/pxl8_render_types.h @@ -31,6 +31,9 @@ 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, @@ -84,6 +87,11 @@ 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; @@ -117,14 +125,6 @@ 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,19 +144,10 @@ 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 dfdeada..26dd4d8 100644 --- a/src/gui/pxl8_gui.c +++ b/src/gui/pxl8_gui.c @@ -173,22 +173,6 @@ 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; @@ -222,63 +206,3 @@ void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y) { if (x) *x = state->cursor_x; if (y) *y = state->cursor_y; } - -i32 pxl8_gui_grid_select(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id, - i32 x, i32 y, i32 cell_w, i32 cell_h, - i32 cols, i32 rows, const u8* colors, i32 selected) { - i32 result = -1; - for (i32 r = 0; r < rows; r++) { - for (i32 c = 0; c < cols; c++) { - i32 cx = x + c * (cell_w + 1); - i32 cy = y + r * (cell_h + 1); - i32 idx = r * cols + c; - u32 id = base_id + (u32)idx; - bool over = is_cursor_over(state, cx, cy, cell_w, cell_h); - if (over) state->hot_id = id; - if (over && state->cursor_down && state->active_id == 0) - state->active_id = id; - bool clicked = (state->active_id == id) && state->cursor_clicked && over; - if (clicked) { result = idx; state->active_id = 0; } - pxl8_2d_rect_fill(gfx, cx, cy, cell_w, cell_h, colors[idx]); - if (idx == selected) { - u8 sel = pxl8_gui_color(gfx, PXL8_UI_FG0); - pxl8_2d_rect(gfx, cx - 1, cy - 1, cell_w + 2, cell_h + 2, sel); - } else if (over) { - u8 hov = pxl8_gui_color(gfx, PXL8_UI_BG3); - pxl8_2d_rect(gfx, cx, cy, cell_w, cell_h, hov); - } - } - } - return result; -} - -void pxl8_gui_image(pxl8_gfx* gfx, u32 texture_id, i32 sx, i32 sy, i32 sw, i32 sh, - i32 dx, i32 dy, i32 dw, i32 dh) { - pxl8_2d_sprite(gfx, texture_id, dx, dy, dw, dh, false, false); - (void)sx; (void)sy; (void)sw; (void)sh; -} - -void pxl8_gui_panel(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h) { - u8 bg = pxl8_gui_color(gfx, PXL8_UI_BG1); - u8 border = pxl8_gui_color(gfx, PXL8_UI_BG3); - pxl8_2d_rect_fill(gfx, x, y, w, h, bg); - pxl8_2d_rect(gfx, x, y, w, h, border); -} - -void pxl8_gui_status_bar(pxl8_gfx* gfx, i32 y, i32 screen_w, i32 h, const char* text) { - u8 bg = pxl8_gui_color(gfx, PXL8_UI_BG1); - u8 fg = pxl8_gui_color(gfx, PXL8_UI_FG0); - pxl8_2d_rect_fill(gfx, 0, y, screen_w, h, bg); - pxl8_2d_text(gfx, text, 4, y + (h / 2) - 5, fg); -} - -bool pxl8_gui_toggle(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, - i32 x, i32 y, i32 w, i32 h, const char* label, bool active) { - bool clicked = pxl8_gui_button(state, gfx, id, x, y, w, h, label); - if (active) { - u8 sel = pxl8_gui_color(gfx, PXL8_UI_FG0); - pxl8_2d_rect(gfx, x, y, w, 2, sel); - pxl8_2d_rect(gfx, x, y + h - 2, w, 2, sel); - } - return clicked ? !active : active; -} diff --git a/src/gui/pxl8_gui.h b/src/gui/pxl8_gui.h index 4d1180f..9dcf770 100644 --- a/src/gui/pxl8_gui.h +++ b/src/gui/pxl8_gui.h @@ -34,25 +34,8 @@ u8 pxl8_gui_color(pxl8_gfx* gfx, u8 index); void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color); bool pxl8_gui_slider(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, f32* value, f32 min_val, f32 max_val); bool pxl8_gui_slider_int(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, i32* value, i32 min_val, i32 max_val); -i32 pxl8_gui_toolbar(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id, - i32 x, i32 y, i32 btn_w, i32 btn_h, - const char** labels, i32 count, i32 selected); void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title); -i32 pxl8_gui_grid_select(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id, - i32 x, i32 y, i32 cell_w, i32 cell_h, - i32 cols, i32 rows, const u8* colors, i32 selected); - -void pxl8_gui_image(pxl8_gfx* gfx, u32 texture_id, i32 sx, i32 sy, i32 sw, i32 sh, - i32 dx, i32 dy, i32 dw, i32 dh); - -void pxl8_gui_panel(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h); - -void pxl8_gui_status_bar(pxl8_gfx* gfx, i32 y, i32 screen_w, i32 h, const char* text); - -bool pxl8_gui_toggle(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, - i32 x, i32 y, i32 w, i32 h, const char* label, bool active); - #ifdef __cplusplus } #endif diff --git a/src/hal/pxl8_io_sdl3.c b/src/hal/pxl8_io_sdl3.c deleted file mode 100644 index d82efc0..0000000 --- a/src/hal/pxl8_io_sdl3.c +++ /dev/null @@ -1,125 +0,0 @@ -#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 079453d..e7cc4dd 100644 --- a/src/lua/pxl8.lua +++ b/src/lua/pxl8.lua @@ -94,7 +94,6 @@ 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 @@ -175,6 +174,8 @@ 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 @@ -233,26 +234,9 @@ 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 b4f9280..64cbf87 100644 --- a/src/lua/pxl8/gfx.lua +++ b/src/lua/pxl8/gfx.lua @@ -230,28 +230,6 @@ 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 @@ -271,21 +249,17 @@ 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 = 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) + 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) end function gfx.begin_frame_3d(camera, lights, uniforms) @@ -327,10 +301,6 @@ 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]}) @@ -357,4 +327,26 @@ 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 36a3807..5d4b17e 100644 --- a/src/lua/pxl8/net.lua +++ b/src/lua/pxl8/net.lua @@ -15,22 +15,18 @@ function net.get() return setmetatable({ _ptr = ptr }, Net) end -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) +function Net:chunk_id() + return C.pxl8_net_chunk_id(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 0d5553a..23f5c3d 100644 --- a/src/math/pxl8_math.h +++ b/src/math/pxl8_math.h @@ -74,10 +74,6 @@ 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 b60ac4c..52681d9 100644 --- a/src/net/pxl8_net.c +++ b/src/net/pxl8_net.c @@ -46,15 +46,17 @@ struct pxl8_net { socket_t sock; pxl8_world_chunk_cache* chunk_cache; - i32 chunk_cx; - i32 chunk_cz; - bool has_chunk; + u32 chunk_id; + u8 chunk_type; 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; @@ -107,9 +109,6 @@ 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); @@ -132,6 +131,9 @@ 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); @@ -177,6 +179,11 @@ 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++) { @@ -224,19 +231,22 @@ u64 pxl8_net_player_id(const pxl8_net* net) { return net->snapshot.player_id; } -static bool dispatch_message(pxl8_net* net, const u8* data, usize len) { +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)); if (len < sizeof(pxl8_msg_header)) return false; pxl8_msg_header hdr; - usize offset = pxl8_protocol_deserialize_header(data, len, &hdr); + usize offset = pxl8_protocol_deserialize_header(net->recv_buf, 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(data + offset, len - offset, &chunk_hdr); + offset += pxl8_protocol_deserialize_chunk_msg_header(net->recv_buf + offset, len - offset, &chunk_hdr); - const u8* payload = data + offset; + const u8* payload = net->recv_buf + offset; usize payload_len = chunk_hdr.payload_size; if (payload_len > len - offset) { payload_len = len - offset; @@ -248,23 +258,23 @@ static bool dispatch_message(pxl8_net* net, const u8* data, usize len) { if (hdr.type == PXL8_MSG_CHUNK_ENTER) { pxl8_chunk_enter_msg chunk_msg; - 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); + 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); return true; } if (hdr.type == PXL8_MSG_CHUNK_EXIT) { - net->has_chunk = false; + 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(data + offset, len - offset, &snap); + offset += pxl8_protocol_deserialize_snapshot_header(net->recv_buf + offset, len - offset, &snap); if (snap.tick <= net->highest_tick) return false; @@ -280,20 +290,10 @@ static bool dispatch_message(pxl8_net* net, const u8* data, usize len) { for (u16 i = 0; i < count; i++) { offset += pxl8_protocol_deserialize_entity_state( - data + offset, len - offset, &net->entities[i]); + net->recv_buf + offset, len - offset, &net->entities[i]); } - 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) { + if (net->world) { pxl8_world_reconcile(net->world, net, 1.0f / 30.0f); } @@ -377,6 +377,7 @@ 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; } @@ -395,19 +396,14 @@ void pxl8_net_set_world(pxl8_net* net, pxl8_world* world) { net->world = world; } -i32 pxl8_net_chunk_cx(const pxl8_net* net) { +u32 pxl8_net_chunk_id(const pxl8_net* net) { if (!net) return 0; - return net->chunk_cx; + return net->chunk_id; } -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; +u8 pxl8_net_chunk_type(const pxl8_net* net) { + if (!net) return PXL8_CHUNK_TYPE_BSP; + return net->chunk_type; } pxl8_result pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch) { @@ -426,6 +422,22 @@ 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) { @@ -492,8 +504,64 @@ void pxl8_net_packet_free(pxl8_packet* pkt) { } bool pxl8_net_process_packet(pxl8_net* net, const pxl8_packet* pkt) { - if (!net || !pkt) return false; - return dispatch_message(net, pkt->data, pkt->len); + 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; } #endif diff --git a/src/net/pxl8_net.h b/src/net/pxl8_net.h index d470441..770c84e 100644 --- a/src/net/pxl8_net.h +++ b/src/net/pxl8_net.h @@ -33,6 +33,7 @@ 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); @@ -53,11 +54,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); -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); +u32 pxl8_net_chunk_id(const pxl8_net* net); +u8 pxl8_net_chunk_type(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 5a29fe8..3a3e4ab 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 < 48) return 0; + if (len < 44) 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, (u32)msg->cx); - pxl8_write_u32_be(&s, (u32)msg->cz); + 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]); 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->cx = (i32)pxl8_read_u32_be(&s); - msg->cz = (i32)pxl8_read_u32_be(&s); + 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); 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 182c846..fd3cc09 100644 --- a/src/net/pxl8_protocol.h +++ b/src/net/pxl8_protocol.h @@ -35,6 +35,7 @@ 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 { @@ -75,6 +76,7 @@ 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 @@ -91,18 +93,17 @@ typedef struct pxl8_chunk_msg_header { } pxl8_chunk_msg_header; typedef struct pxl8_bsp_wire_header { - u32 num_vertices; + u32 num_cell_portals; u32 num_edges; u32 num_faces; - u32 num_planes; - u32 num_nodes; u32 num_leafs; - u32 num_surfedges; u32 num_marksurfaces; - u32 num_cell_portals; - u32 visdata_size; + u32 num_nodes; + u32 num_planes; + u32 num_surfedges; u32 num_vertex_lights; - u32 num_heightfield; + u32 num_vertices; + u32 visdata_size; } pxl8_bsp_wire_header; usize pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, usize len); @@ -129,15 +130,14 @@ 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 { - i32 cx; - i32 cz; + u32 chunk_id; + u8 chunk_type; + u8 reserved[3]; } 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 9acd828..ace4c1f 100644 --- a/src/script/pxl8_script.c +++ b/src/script/pxl8_script.c @@ -4,13 +4,16 @@ #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" @@ -25,7 +28,7 @@ struct pxl8_script { char last_error[PXL8_MAX_ERROR_SIZE]; char main_path[PXL8_MAX_PATH]; char watch_dir[PXL8_MAX_PATH]; - f64 latest_mod_time; + time_t latest_mod_time; int repl_env_ref; bool repl_mode; }; @@ -404,12 +407,8 @@ static pxl8_result pxl8_script_prepare_path(pxl8_script* script, const char* fil char script_dir[PATH_MAX]; char original_cwd[PATH_MAX]; - 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); + if (realpath(filename_copy, script_dir) && getcwd(original_cwd, sizeof(original_cwd))) { + chdir(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'; @@ -417,8 +416,6 @@ 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'; @@ -445,38 +442,51 @@ pxl8_result pxl8_script_run_file(pxl8_script* script, const char* filename) { return result; } -typedef struct { - f64 latest; -} script_mod_ctx; - -static f64 get_latest_script_mod_time(const char* dir_path); - -static bool check_script_mod_time(void* userdata, const char* dirname, const char* name) { - script_mod_ctx* ctx = userdata; - if (name[0] == '.') return true; - - char full_path[512]; - snprintf(full_path, sizeof(full_path), "%s%s", dirname, name); - - 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; - } +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 true; + return 0; } -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; +static time_t get_latest_script_mod_time(const char* dir_path) { + DIR* dir = opendir(dir_path); + if (!dir) return 0; + + time_t latest = 0; + struct dirent* entry; + + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') continue; + + 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; + } + } + } + } + } + + closedir(dir); + return latest; } void pxl8_script_set_cart_path(pxl8_script* script, const char* cart_path, const char* original_cwd) { @@ -1147,8 +1157,8 @@ bool pxl8_script_check_reload(pxl8_script* script) { return false; } - 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) { + time_t current_mod_time = get_latest_script_mod_time(script->watch_dir); + if (current_mod_time > script->latest_mod_time && current_mod_time != 0) { pxl8_info("Script files modified, reloading: %s", script->main_path); script->latest_mod_time = current_mod_time; diff --git a/src/script/pxl8_script_ffi.h b/src/script/pxl8_script_ffi.h index b85e4da..07ec16f 100644 --- a/src/script/pxl8_script_ffi.h +++ b/src/script/pxl8_script_ffi.h @@ -29,6 +29,7 @@ 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" @@ -261,7 +262,6 @@ 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,20 +309,9 @@ 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" -"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" +"void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material);\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" @@ -412,7 +401,6 @@ 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" @@ -463,17 +451,11 @@ static const char* pxl8_ffi_cdefs = "bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label);\n" "bool pxl8_gui_slider(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, float* value, float min_val, float max_val);\n" "bool pxl8_gui_slider_int(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, i32* value, i32 min_val, i32 max_val);\n" -"i32 pxl8_gui_toolbar(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id, i32 x, i32 y, i32 btn_w, i32 btn_h, const char** labels, i32 count, i32 selected);\n" "void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title);\n" "void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color);\n" "u8 pxl8_gui_color(pxl8_gfx* gfx, u8 index);\n" "bool pxl8_gui_is_hovering(const pxl8_gui_state* state);\n" "void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y);\n" -"i32 pxl8_gui_grid_select(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id, i32 x, i32 y, i32 cell_w, i32 cell_h, i32 cols, i32 rows, const u8* colors, i32 selected);\n" -"void pxl8_gui_image(pxl8_gfx* gfx, u32 texture_id, i32 sx, i32 sy, i32 sw, i32 sh, i32 dx, i32 dy, i32 dw, i32 dh);\n" -"void pxl8_gui_panel(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h);\n" -"void pxl8_gui_status_bar(pxl8_gfx* gfx, i32 y, i32 screen_w, i32 h, const char* text);\n" -"bool pxl8_gui_toggle(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label, bool active);\n" "\n" "typedef struct pxl8_save pxl8_save;\n" "pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version);\n" @@ -533,7 +515,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_type;\n" +"typedef enum pxl8_cmd_type { PXL8_CMD_NONE = 0, PXL8_CMD_SPAWN_ENTITY, PXL8_CMD_ENTER_SCENE } pxl8_cmd_type;\n" "\n" "typedef struct pxl8_command_msg {\n" " u16 cmd_type;\n" @@ -567,10 +549,7 @@ static const char* pxl8_ffi_cdefs = "} pxl8_sim_config;\n" "\n" "typedef struct pxl8_sim_world {\n" -" const pxl8_bsp* chunks[9];\n" -" i32 center_cx;\n" -" i32 center_cz;\n" -" f32 chunk_size;\n" +" const pxl8_bsp* bsp;\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" @@ -609,15 +588,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" -"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" +"u8 pxl8_net_chunk_type(const pxl8_net* net);\n" +"u32 pxl8_net_chunk_id(const pxl8_net* net);\n" "bool pxl8_net_poll(pxl8_net* net);\n" "u8* pxl8_net_predicted_state(pxl8_net* net);\n" "void pxl8_net_predicted_tick_set(pxl8_net* net, u64 tick);\n" "i32 pxl8_net_send_command(pxl8_net* net, const pxl8_command_msg* cmd);\n" "i32 pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input);\n" "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 ff19667..9de35e1 100644 --- a/src/sim/pxl8_sim.c +++ b/src/sim/pxl8_sim.c @@ -2,15 +2,6 @@ #include -#define DIST_EPSILON 0.03125f - -typedef struct { - f32 fraction; - pxl8_vec3 normal; - bool all_solid; - bool start_solid; -} trace_result; - static i32 bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) { if (!bsp || bsp->num_nodes == 0) return -1; @@ -25,299 +16,54 @@ static i32 bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) { return -(node_id + 1); } -static i32 bsp_contents_from(const pxl8_bsp* bsp, i32 node_id, pxl8_vec3 pos) { - while (node_id >= 0) { - const pxl8_bsp_node* node = &bsp->nodes[node_id]; - const pxl8_bsp_plane* plane = &bsp->planes[node->plane_id]; - f32 d = pxl8_vec3_dot(pos, plane->normal) - plane->dist; - node_id = node->children[d < 0 ? 1 : 0]; - } - i32 leaf_idx = -(node_id + 1); - if (leaf_idx < 0 || (u32)leaf_idx >= bsp->num_leafs) return -1; - return bsp->leafs[leaf_idx].contents; -} - -static bool bsp_recursive_trace(const pxl8_bsp* bsp, i32 node_id, - f32 p1f, f32 p2f, - pxl8_vec3 p1, pxl8_vec3 p2, - trace_result* tr) { - if (node_id < 0) { - i32 leaf_idx = -(node_id + 1); - if (leaf_idx >= 0 && (u32)leaf_idx < bsp->num_leafs && - bsp->leafs[leaf_idx].contents == -1) { - tr->start_solid = true; - } else { - tr->all_solid = false; - } - return true; - } - - const pxl8_bsp_node* node = &bsp->nodes[node_id]; - const pxl8_bsp_plane* plane = &bsp->planes[node->plane_id]; - - f32 t1 = pxl8_vec3_dot(p1, plane->normal) - plane->dist; - f32 t2 = pxl8_vec3_dot(p2, plane->normal) - plane->dist; - - if (t1 >= 0 && t2 >= 0) - return bsp_recursive_trace(bsp, node->children[0], p1f, p2f, p1, p2, tr); - if (t1 < 0 && t2 < 0) - return bsp_recursive_trace(bsp, node->children[1], p1f, p2f, p1, p2, tr); - - i32 side; - f32 frac; - if (t1 < 0) { - frac = (t1 + DIST_EPSILON) / (t1 - t2); - side = 1; - } else { - frac = (t1 - DIST_EPSILON) / (t1 - t2); - side = 0; - } - if (frac < 0) frac = 0; - if (frac > 1) frac = 1; - - f32 midf = p1f + (p2f - p1f) * frac; - pxl8_vec3 mid = { - p1.x + frac * (p2.x - p1.x), - p1.y + frac * (p2.y - p1.y), - p1.z + frac * (p2.z - p1.z), - }; - - if (!bsp_recursive_trace(bsp, node->children[side], p1f, midf, p1, mid, tr)) - return false; - - if (bsp_contents_from(bsp, node->children[side ^ 1], mid) != -1) - return bsp_recursive_trace(bsp, node->children[side ^ 1], midf, p2f, mid, p2, tr); - - if (tr->all_solid) - return false; - - if (midf < tr->fraction) { - tr->fraction = midf; - if (side == 0) { - tr->normal = plane->normal; - } else { - tr->normal.x = -plane->normal.x; - tr->normal.y = -plane->normal.y; - tr->normal.z = -plane->normal.z; - } - } - return false; -} - -static trace_result bsp_trace_line(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to) { - trace_result tr = { .fraction = 1.0f, .all_solid = true }; - if (!bsp || bsp->num_nodes == 0) { - tr.all_solid = false; - return tr; - } - bsp_recursive_trace(bsp, 0, 0.0f, 1.0f, from, to, &tr); - if (tr.all_solid) { - tr.fraction = 0.0f; - tr.start_solid = true; - } - return tr; -} - bool pxl8_bsp_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos) { - if (!bsp) return false; - if (bsp->bounds_max_x > bsp->bounds_min_x && - (pos.x < bsp->bounds_min_x || pos.x >= bsp->bounds_max_x || - pos.z < bsp->bounds_min_z || pos.z >= bsp->bounds_max_z)) - return false; i32 leaf = bsp_find_leaf(bsp, pos); if (leaf < 0 || (u32)leaf >= bsp->num_leafs) return true; return bsp->leafs[leaf].contents == -1; } -static void trace_offsets(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, - f32 radius, f32* out_frac, pxl8_vec3* out_normal) { - f32 d = radius * 0.7071f; - pxl8_vec3 offsets[9] = { - {0, 0, 0}, - {radius, 0, 0}, {-radius, 0, 0}, - {0, 0, radius}, {0, 0, -radius}, - {d, 0, d}, {d, 0, -d}, - {-d, 0, d}, {-d, 0, -d}, - }; - - *out_frac = 1.0f; - *out_normal = (pxl8_vec3){0, 0, 0}; - - for (i32 i = 0; i < 9; i++) { - pxl8_vec3 s = { from.x + offsets[i].x, from.y + offsets[i].y, from.z + offsets[i].z }; - pxl8_vec3 e = { to.x + offsets[i].x, to.y + offsets[i].y, to.z + offsets[i].z }; - trace_result tr = bsp_trace_line(bsp, s, e); - if (tr.fraction < *out_frac && !tr.start_solid) { - *out_frac = tr.fraction; - *out_normal = tr.normal; - } - } +static bool bsp_point_clear(const pxl8_bsp* bsp, f32 x, f32 y, f32 z, f32 radius) { + if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z})) return false; + if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x + radius, y, z})) return false; + if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x - radius, y, z})) return false; + if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z + radius})) return false; + if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z - radius})) return false; + return true; } pxl8_vec3 pxl8_bsp_trace(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, f32 radius) { if (!bsp || bsp->num_nodes == 0) return to; - f32 frac; - pxl8_vec3 normal; - trace_offsets(bsp, from, to, radius, &frac, &normal); - if (frac >= 1.0f) return to; - - pxl8_vec3 delta = { to.x - from.x, to.y - from.y, to.z - from.z }; - pxl8_vec3 hit = { - from.x + delta.x * frac, - from.y + delta.y * frac, - from.z + delta.z * frac, - }; - - f32 remaining = 1.0f - frac; - if (remaining <= 0.0f) return hit; - - f32 backoff = pxl8_vec3_dot(delta, normal) * remaining; - pxl8_vec3 slide_target = { - hit.x + delta.x * remaining - normal.x * backoff, - hit.y + delta.y * remaining - normal.y * backoff, - hit.z + delta.z * remaining - normal.z * backoff, - }; - - f32 slide_frac; - pxl8_vec3 slide_normal; - trace_offsets(bsp, hit, slide_target, radius, &slide_frac, &slide_normal); - if (slide_frac >= 1.0f) return slide_target; - - pxl8_vec3 slide_delta = { - slide_target.x - hit.x, - slide_target.y - hit.y, - slide_target.z - hit.z, - }; - return (pxl8_vec3){ - hit.x + slide_delta.x * slide_frac, - hit.y + slide_delta.y * slide_frac, - hit.z + slide_delta.z * slide_frac, - }; -} - -static const pxl8_bsp* sim_bsp_at(const pxl8_sim_world* world, f32 x, f32 z) { - i32 cx = (i32)floorf(x / world->chunk_size); - i32 cz = (i32)floorf(z / world->chunk_size); - i32 dx = cx - world->center_cx + 1; - i32 dz = cz - world->center_cz + 1; - if (dx < 0 || dx > 2 || dz < 0 || dz > 2) return NULL; - return world->chunks[dz * 3 + dx]; -} - -static f32 bsp_terrain_height(const pxl8_bsp* bsp, f32 x, f32 z) { - if (!bsp || !bsp->heightfield || bsp->heightfield_cell_size <= 0) return -1e9f; - f32 lx = (x - bsp->heightfield_ox) / bsp->heightfield_cell_size; - f32 lz = (z - bsp->heightfield_oz) / bsp->heightfield_cell_size; - i32 ix = (i32)floorf(lx); - i32 iz = (i32)floorf(lz); - if (ix < 0 || ix >= bsp->heightfield_w - 1 || iz < 0 || iz >= bsp->heightfield_h - 1) return -1e9f; - f32 fx = lx - ix; - f32 fz = lz - iz; - i32 w = bsp->heightfield_w; - f32 h00 = bsp->heightfield[iz * w + ix]; - f32 h10 = bsp->heightfield[iz * w + ix + 1]; - f32 h01 = bsp->heightfield[(iz + 1) * w + ix]; - f32 h11 = bsp->heightfield[(iz + 1) * w + ix + 1]; - f32 h0 = h00 + (h10 - h00) * fx; - f32 h1 = h01 + (h11 - h01) * fx; - return h0 + (h1 - h0) * fz; -} - -static f32 sim_terrain_height(const pxl8_sim_world* world, f32 x, f32 z) { - const pxl8_bsp* bsp = sim_bsp_at(world, x, z); - return bsp_terrain_height(bsp, x, z); -} - -static void sim_trace_offsets(const pxl8_sim_world* world, pxl8_vec3 from, pxl8_vec3 to, - f32 radius, f32* out_frac, pxl8_vec3* out_normal) { - f32 d = radius * 0.7071f; - pxl8_vec3 offsets[9] = { - {0, 0, 0}, - {radius, 0, 0}, {-radius, 0, 0}, - {0, 0, radius}, {0, 0, -radius}, - {d, 0, d}, {d, 0, -d}, - {-d, 0, d}, {-d, 0, -d}, - }; - - *out_frac = 1.0f; - *out_normal = (pxl8_vec3){0, 0, 0}; - - for (i32 i = 0; i < 9; i++) { - pxl8_vec3 s = { from.x + offsets[i].x, from.y + offsets[i].y, from.z + offsets[i].z }; - pxl8_vec3 e = { to.x + offsets[i].x, to.y + offsets[i].y, to.z + offsets[i].z }; - const pxl8_bsp* bsp_s = sim_bsp_at(world, s.x, s.z); - const pxl8_bsp* bsp_e = sim_bsp_at(world, e.x, e.z); - if (bsp_s) { - trace_result tr = bsp_trace_line(bsp_s, s, e); - if (tr.fraction < *out_frac && !tr.start_solid) { - *out_frac = tr.fraction; - *out_normal = tr.normal; - } - } - if (bsp_e && bsp_e != bsp_s) { - bool inside_bounds = !(bsp_e->bounds_max_x > bsp_e->bounds_min_x) || - (s.x >= bsp_e->bounds_min_x && s.x < bsp_e->bounds_max_x && - s.z >= bsp_e->bounds_min_z && s.z < bsp_e->bounds_max_z); - if (inside_bounds) { - trace_result tr = bsp_trace_line(bsp_e, s, e); - if (tr.fraction < *out_frac && !tr.start_solid) { - *out_frac = tr.fraction; - *out_normal = tr.normal; - } - } - } + if (bsp_point_clear(bsp, to.x, to.y, to.z, radius)) { + return to; } + + bool x_ok = bsp_point_clear(bsp, to.x, from.y, from.z, radius); + bool y_ok = bsp_point_clear(bsp, from.x, to.y, from.z, radius); + bool z_ok = bsp_point_clear(bsp, from.x, from.y, to.z, radius); + + pxl8_vec3 result = from; + if (x_ok) result.x = to.x; + if (y_ok) result.y = to.y; + if (z_ok) result.z = to.z; + + return result; } pxl8_vec3 pxl8_sim_trace(const pxl8_sim_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius, f32 height) { (void)height; - if (!world || world->chunk_size <= 0) return to; + if (!world) return to; - f32 frac; - pxl8_vec3 normal; - sim_trace_offsets(world, from, to, radius, &frac, &normal); - if (frac >= 1.0f) return to; + if (world->bsp) { + return pxl8_bsp_trace(world->bsp, from, to, radius); + } - pxl8_vec3 delta = { to.x - from.x, to.y - from.y, to.z - from.z }; - pxl8_vec3 hit = { - from.x + delta.x * frac, - from.y + delta.y * frac, - from.z + delta.z * frac, - }; - - f32 remaining = 1.0f - frac; - if (remaining <= 0.0f) return hit; - - f32 backoff = pxl8_vec3_dot(delta, normal) * remaining; - pxl8_vec3 slide_target = { - hit.x + delta.x * remaining - normal.x * backoff, - hit.y + delta.y * remaining - normal.y * backoff, - hit.z + delta.z * remaining - normal.z * backoff, - }; - - f32 slide_frac; - pxl8_vec3 slide_normal; - sim_trace_offsets(world, hit, slide_target, radius, &slide_frac, &slide_normal); - if (slide_frac >= 1.0f) return slide_target; - - pxl8_vec3 slide_delta = { - slide_target.x - hit.x, - slide_target.y - hit.y, - slide_target.z - hit.z, - }; - return (pxl8_vec3){ - hit.x + slide_delta.x * slide_frac, - hit.y + slide_delta.y * slide_frac, - hit.z + slide_delta.z * slide_frac, - }; + return to; } bool pxl8_sim_check_ground(const pxl8_sim_world* world, pxl8_vec3 pos, f32 radius) { - 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; + if (!world) return true; + if (!world->bsp) 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); @@ -373,17 +119,22 @@ 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); - 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; + 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; } ent->pos = new_pos; @@ -424,17 +175,22 @@ 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); - 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; + 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; } ent->pos = new_pos; diff --git a/src/sim/pxl8_sim.h b/src/sim/pxl8_sim.h index 7d05711..a17c540 100644 --- a/src/sim/pxl8_sim.h +++ b/src/sim/pxl8_sim.h @@ -37,10 +37,7 @@ typedef struct pxl8_sim_entity { } pxl8_sim_entity; typedef struct pxl8_sim_world { - const pxl8_bsp* chunks[9]; - i32 center_cx; - i32 center_cz; - f32 chunk_size; + const pxl8_bsp* bsp; } 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 aa217a3..5333269 100644 --- a/src/world/pxl8_world.c +++ b/src/world/pxl8_world.c @@ -14,31 +14,15 @@ #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; @@ -46,12 +30,6 @@ 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; @@ -94,11 +72,9 @@ 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); } @@ -112,26 +88,27 @@ 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}; - 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; - } + if (world->active_chunk && world->active_chunk->bsp) { + sim.bsp = world->active_chunk->bsp; } - return sim; } @@ -267,452 +244,62 @@ void pxl8_world_update(pxl8_world* world, f32 dt) { pxl8_world_chunk_cache_tick(world->chunk_cache); } -static inline bool vr_valid(pxl8_rect r) { - return r.x0 < r.x1 && r.y0 < r.y1; -} - -static inline pxl8_rect vr_intersect(pxl8_rect a, pxl8_rect b) { - return (pxl8_rect){ - .x0 = a.x0 > b.x0 ? a.x0 : b.x0, - .y0 = a.y0 > b.y0 ? a.y0 : b.y0, - .x1 = a.x1 < b.x1 ? a.x1 : b.x1, - .y1 = a.y1 < b.y1 ? a.y1 : b.y1, - }; -} - -static pxl8_rect project_portal(f32 px0, f32 pz0, f32 px1, f32 pz1, - f32 y_lo, f32 y_hi, const pxl8_mat4* vp) { - pxl8_vec3 corners[4] = { - {px0, y_lo, pz0}, {px1, y_lo, pz1}, - {px1, y_hi, pz1}, {px0, y_hi, pz0}, - }; - - const f32 NEAR_W = 0.001f; - pxl8_vec4 clip[4]; - bool front[4]; - i32 fc = 0; - - for (i32 i = 0; i < 4; i++) { - clip[i] = pxl8_mat4_multiply_vec4(*vp, (pxl8_vec4){ - corners[i].x, corners[i].y, corners[i].z, 1.0f}); - front[i] = clip[i].w > NEAR_W; - if (front[i]) fc++; - } - - if (fc == 0) return (pxl8_rect){0, 0, 0, 0}; - if (fc < 4) return (pxl8_rect){-1.0f, -1.0f, 1.0f, 1.0f}; - - pxl8_rect r = {1e30f, 1e30f, -1e30f, -1e30f}; - - for (i32 i = 0; i < 4; i++) { - f32 iw = 1.0f / clip[i].w; - f32 nx = clip[i].x * iw, ny = clip[i].y * iw; - if (nx < r.x0) r.x0 = nx; if (nx > r.x1) r.x1 = nx; - if (ny < r.y0) r.y0 = ny; if (ny > r.y1) r.y1 = ny; - } - - if (r.x0 < -1.0f) r.x0 = -1.0f; - if (r.y0 < -1.0f) r.y0 = -1.0f; - if (r.x1 > 1.0f) r.x1 = 1.0f; - if (r.y1 > 1.0f) r.y1 = 1.0f; - - return r; -} - -static void compute_edge_leafs(pxl8_loaded_chunk* lc) { - const f32 CHUNK_SIZE = 16.0f * 64.0f; - const pxl8_bsp* bsp = lc->chunk->bsp; - f32 cx0 = lc->cx * CHUNK_SIZE; - f32 cz0 = lc->cz * CHUNK_SIZE; - f32 cx1 = cx0 + CHUNK_SIZE; - f32 cz1 = cz0 + CHUNK_SIZE; - - memset(lc->edges, 0, sizeof(lc->edges)); - - for (u32 i = 0; i < bsp->num_leafs; i++) { - const pxl8_bsp_leaf* leaf = &bsp->leafs[i]; - if (bsp->leafs[i].contents == -1) continue; - - if ((f32)leaf->mins[2] <= (f32)((i16)cz0) + 1.0f && lc->edges[0].count < 16) - lc->edges[0].leafs[lc->edges[0].count++] = (u16)i; - if ((f32)leaf->maxs[2] >= (f32)((i16)cz1) - 1.0f && lc->edges[1].count < 16) - lc->edges[1].leafs[lc->edges[1].count++] = (u16)i; - if ((f32)leaf->mins[0] <= (f32)((i16)cx0) + 1.0f && lc->edges[2].count < 16) - lc->edges[2].leafs[lc->edges[2].count++] = (u16)i; - if ((f32)leaf->maxs[0] >= (f32)((i16)cx1) - 1.0f && lc->edges[3].count < 16) - lc->edges[3].leafs[lc->edges[3].count++] = (u16)i; - } -} - -static i32 world_find_chunk(const pxl8_world* world, i32 cx, i32 cz) { - for (u32 i = 0; i < world->loaded_count; i++) { - if (world->loaded[i].cx == cx && world->loaded[i].cz == cz && - world->loaded[i].chunk && world->loaded[i].chunk->bsp) - return (i32)i; - } - return -1; -} - -static void world_mark_leaf_faces(const pxl8_bsp* bsp, pxl8_bsp_render_state* rs, u32 leaf_idx) { - const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_idx]; - for (u32 i = 0; i < leaf->num_marksurfaces; i++) { - u32 si = leaf->first_marksurface + i; - if (si < bsp->num_marksurfaces) { - u32 fi = bsp->marksurfaces[si]; - if (fi < bsp->num_faces && rs) - rs->render_face_flags[fi] = 1; - } - } -} - -static bool vis_try_enqueue(u8** vis, pxl8_rect** windows, world_vis_node* queue, - u32* tail, u32 max_queue, - u16 ci, u16 li, pxl8_rect nw) { - if (!vis[ci] || !windows[ci]) return false; - - u32 byte = li >> 3; - u32 bit = 1 << (li & 7); - - if (vis[ci][byte] & bit) { - pxl8_rect* ex = &windows[ci][li]; - bool expanded = false; - if (nw.x0 < ex->x0) { ex->x0 = nw.x0; expanded = true; } - if (nw.y0 < ex->y0) { ex->y0 = nw.y0; expanded = true; } - if (nw.x1 > ex->x1) { ex->x1 = nw.x1; expanded = true; } - if (nw.y1 > ex->y1) { ex->y1 = nw.y1; expanded = true; } - if (expanded && *tail < max_queue) - queue[(*tail)++] = (world_vis_node){ci, li, *ex}; - return expanded; - } - - vis[ci][byte] |= bit; - windows[ci][li] = nw; - if (*tail < max_queue) - queue[(*tail)++] = (world_vis_node){ci, li, nw}; - return true; -} - -static void world_compute_visibility(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) { - const f32 CHUNK_SIZE = 16.0f * 64.0f; - const f32 PORTAL_Y_HI = 192.0f; - - const pxl8_mat4* vp = pxl8_3d_get_view_proj(gfx); - - i32 cam_ci = -1, cam_li = -1; - for (u32 i = 0; i < world->loaded_count; i++) { - pxl8_loaded_chunk* lc = &world->loaded[i]; - if (!lc->chunk || !lc->chunk->bsp) continue; - const pxl8_bsp* bsp = lc->chunk->bsp; - f32 cx0 = lc->cx * CHUNK_SIZE; - f32 cz0 = lc->cz * CHUNK_SIZE; - if (camera_pos.x < cx0 || camera_pos.x >= cx0 + CHUNK_SIZE || - camera_pos.z < cz0 || camera_pos.z >= cz0 + CHUNK_SIZE) continue; - if (bsp->bounds_max_x > bsp->bounds_min_x && - (camera_pos.x < bsp->bounds_min_x || camera_pos.x >= bsp->bounds_max_x || - camera_pos.z < bsp->bounds_min_z || camera_pos.z >= bsp->bounds_max_z)) - continue; - i32 leaf = pxl8_bsp_find_leaf(bsp, camera_pos); - if (leaf >= 0 && (u32)leaf < bsp->num_leafs && - bsp->leafs[leaf].contents != -1 && - (!(bsp->bounds_max_x > bsp->bounds_min_x) || bsp->leafs[leaf].contents == -2)) { - cam_ci = (i32)i; - cam_li = leaf; - break; - } - } - - if (cam_ci < 0) { - for (u32 i = 0; i < world->loaded_count; i++) { - pxl8_loaded_chunk* lc = &world->loaded[i]; - if (!lc->chunk || !lc->chunk->bsp) continue; - const pxl8_bsp* bsp = lc->chunk->bsp; - if (bsp->bounds_max_x > bsp->bounds_min_x) continue; - i32 leaf = pxl8_bsp_find_leaf(bsp, camera_pos); - if (leaf < 0 || (u32)leaf >= bsp->num_leafs) continue; - const pxl8_bsp_leaf* l = &bsp->leafs[leaf]; - if (l->contents == -1) continue; - if (camera_pos.x < (f32)l->mins[0] || camera_pos.x > (f32)l->maxs[0] || - camera_pos.z < (f32)l->mins[2] || camera_pos.z > (f32)l->maxs[2]) continue; - cam_ci = (i32)i; - cam_li = leaf; - break; - } - } - - if (cam_ci < 0 || !vp) { - for (u32 i = 0; i < world->loaded_count; i++) { - pxl8_bsp_render_state* rs = world->loaded[i].render_state; - if (!rs) continue; - if (rs->render_face_flags) - memset(rs->render_face_flags, 1, rs->num_faces); - rs->exterior = true; - } - return; - } - - memset(world->vis_bits, 0, sizeof(world->vis_bits)); - memset(world->vis_ptrs, 0, sizeof(world->vis_ptrs)); - memset(world->vis_win_ptrs, 0, sizeof(world->vis_win_ptrs)); - - u32 voff = 0, woff = 0; - for (u32 i = 0; i < world->loaded_count; i++) { - if (world->loaded[i].chunk && world->loaded[i].chunk->bsp) { - u32 nl = world->loaded[i].chunk->bsp->num_leafs; - u32 vbytes = (nl + 7) / 8; - if (voff + vbytes > PXL8_VIS_BYTES || woff + nl > PXL8_VIS_MAX_NODES) - continue; - world->vis_ptrs[i] = world->vis_bits + voff; - voff += vbytes; - world->vis_win_ptrs[i] = world->vis_windows + woff; - woff += nl; - } - } - - if (!world->vis_ptrs[cam_ci] || !world->vis_win_ptrs[cam_ci]) return; - - for (u32 i = 0; i < world->loaded_count; i++) { - pxl8_bsp_render_state* rs = world->loaded[i].render_state; - if (!rs) continue; - if (rs->render_face_flags) - memset(rs->render_face_flags, 0, rs->num_faces); - rs->exterior = false; - } - - u32 head = 0, tail = 0; - - pxl8_rect full_screen = {-1.0f, -1.0f, 1.0f, 1.0f}; - const pxl8_bsp* cam_bsp = world->loaded[cam_ci].chunk->bsp; - bool cam_exterior = cam_bsp->leafs[cam_li].contents == 0 && - !(cam_bsp->bounds_max_x > cam_bsp->bounds_min_x); - - world->vis_ptrs[cam_ci][cam_li >> 3] |= (1 << (cam_li & 7)); - world->vis_win_ptrs[cam_ci][cam_li] = full_screen; - world->vis_queue[tail++] = (world_vis_node){(u16)cam_ci, (u16)cam_li, full_screen}; - - while (head < tail) { - world_vis_node cur = world->vis_queue[head++]; - pxl8_loaded_chunk* lc = &world->loaded[cur.chunk_idx]; - const pxl8_bsp* bsp = lc->chunk->bsp; - - world_mark_leaf_faces(bsp, lc->render_state, cur.leaf_idx); - - if (bsp->cell_portals && cur.leaf_idx < bsp->num_cell_portals) { - bool is_cam_leaf = (cur.chunk_idx == (u16)cam_ci && cur.leaf_idx == (u16)cam_li); - bool is_cam_chunk = (cur.chunk_idx == (u16)cam_ci); - const pxl8_bsp_cell_portals* cp = &bsp->cell_portals[cur.leaf_idx]; - for (u8 pi = 0; pi < cp->num_portals; pi++) { - const pxl8_bsp_portal* p = &cp->portals[pi]; - u32 target = p->target_leaf; - if (target >= bsp->num_leafs) continue; - if (bsp->leafs[target].contents == -1) continue; - if (is_cam_chunk && !pxl8_bsp_is_leaf_visible(bsp, cam_li, (i32)target)) continue; - - if (is_cam_leaf || cam_exterior) { - vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs, - world->vis_queue, &tail, PXL8_VIS_MAX_QUEUE, - cur.chunk_idx, (u16)target, full_screen); - continue; - } - - pxl8_rect psr = project_portal(p->x0, p->z0, p->x1, p->z1, - 0.0f, PORTAL_Y_HI, vp); - if (!vr_valid(psr)) continue; - pxl8_rect nw = vr_intersect(cur.window, psr); - if (!vr_valid(nw)) continue; - - vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs, - world->vis_queue, &tail, PXL8_VIS_MAX_QUEUE, - cur.chunk_idx, (u16)target, nw); - } - } - - const pxl8_bsp_leaf* leaf = &bsp->leafs[cur.leaf_idx]; - f32 chunk_x0 = lc->cx * CHUNK_SIZE; - f32 chunk_z0 = lc->cz * CHUNK_SIZE; - f32 chunk_x1 = chunk_x0 + CHUNK_SIZE; - f32 chunk_z1 = chunk_z0 + CHUNK_SIZE; - - struct { i32 dx, dz; bool at_edge; f32 bnd; } dirs[4] = { - { 0, -1, (f32)leaf->mins[2] <= (f32)((i16)chunk_z0) + 1.0f, chunk_z0 }, - { 0, 1, (f32)leaf->maxs[2] >= (f32)((i16)chunk_z1) - 1.0f, chunk_z1 }, - {-1, 0, (f32)leaf->mins[0] <= (f32)((i16)chunk_x0) + 1.0f, chunk_x0 }, - { 1, 0, (f32)leaf->maxs[0] >= (f32)((i16)chunk_x1) - 1.0f, chunk_x1 }, - }; - - static const u8 opposite_edge[4] = {1, 0, 3, 2}; - - for (u32 d = 0; d < 4; d++) { - if (!dirs[d].at_edge) continue; - i32 nci = world_find_chunk(world, lc->cx + dirs[d].dx, lc->cz + dirs[d].dz); - if (nci < 0) continue; - - const pxl8_bsp* nbsp = world->loaded[nci].chunk->bsp; - const pxl8_edge_leafs* nedge = &world->loaded[nci].edges[opposite_edge[d]]; - - for (u8 k = 0; k < nedge->count; k++) { - u16 nl = nedge->leafs[k]; - const pxl8_bsp_leaf* nleaf =  ->leafs[nl]; - - bool overlaps = false; - if (dirs[d].dx != 0) { - overlaps = leaf->maxs[2] > nleaf->mins[2] && leaf->mins[2] < nleaf->maxs[2]; - } else { - overlaps = leaf->maxs[0] > nleaf->mins[0] && leaf->mins[0] < nleaf->maxs[0]; - } - if (!overlaps) continue; - - f32 bpx0, bpz0, bpx1, bpz1; - if (dirs[d].dx != 0) { - bpx0 = dirs[d].bnd; bpx1 = dirs[d].bnd; - i16 zlo = leaf->mins[2] > nleaf->mins[2] ? leaf->mins[2] : nleaf->mins[2]; - i16 zhi = leaf->maxs[2] < nleaf->maxs[2] ? leaf->maxs[2] : nleaf->maxs[2]; - bpz0 = (f32)zlo; bpz1 = (f32)zhi; - } else { - bpz0 = dirs[d].bnd; bpz1 = dirs[d].bnd; - i16 xlo = leaf->mins[0] > nleaf->mins[0] ? leaf->mins[0] : nleaf->mins[0]; - i16 xhi = leaf->maxs[0] < nleaf->maxs[0] ? leaf->maxs[0] : nleaf->maxs[0]; - bpx0 = (f32)xlo; bpx1 = (f32)xhi; - } - - if (cam_exterior) { - vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs, - world->vis_queue, &tail, PXL8_VIS_MAX_QUEUE, - (u16)nci, (u16)nl, full_screen); - continue; - } - - pxl8_rect psr = project_portal(bpx0, bpz0, bpx1, bpz1, - 0.0f, PORTAL_Y_HI, vp); - if (!vr_valid(psr)) continue; - pxl8_rect nw = vr_intersect(cur.window, psr); - if (!vr_valid(nw)) continue; - - vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs, - world->vis_queue, &tail, PXL8_VIS_MAX_QUEUE, - (u16)nci, (u16)nl, nw); - } - } - } - -} - void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) { if (!world || !gfx) return; - if (world->active_chunk && world->active_chunk->bsp) + if (world->active_chunk && world->active_chunk->bsp) { pxl8_3d_set_bsp(gfx, world->active_chunk->bsp); - else + pxl8_bsp_render(gfx, world->active_chunk->bsp, + world->bsp_render_state, camera_pos); + } 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; - 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; - } + u32 chunk_id = pxl8_net_chunk_id(net); - i32 center_cx = pxl8_net_chunk_cx(net); - i32 center_cz = pxl8_net_chunk_cz(net); + 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); - 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; + if (world->bsp_render_state) { + pxl8_bsp_render_state_destroy(world->bsp_render_state); + world->bsp_render_state = NULL; } - } - - 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; + world->bsp_render_state = pxl8_bsp_render_state_create(chunk->bsp->num_faces); } } - } - - 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); + } 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; } } } +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 || material_id >= 16) return; + if (!world || !material) return; - world->shared_materials[material_id] = *material; - world->shared_material_set[material_id] = true; + ensure_bsp_render_state(world); + if (!world->bsp_render_state) return; - 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); - } - } + pxl8_bsp_set_material(world->bsp_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 55b2d02..90a1609 100644 --- a/src/world/pxl8_world.h +++ b/src/world/pxl8_world.h @@ -2,35 +2,17 @@ #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); @@ -38,6 +20,10 @@ 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 063dcd9..181e807 100644 --- a/src/world/pxl8_world_chunk_cache.c +++ b/src/world/pxl8_world_chunk_cache.c @@ -179,8 +179,13 @@ 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, 48, &wire_hdr); - s.offset = 48; + 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); if (wire_hdr.num_vertices > 0) { bsp->vertices = pxl8_calloc(wire_hdr.num_vertices, sizeof(pxl8_bsp_vertex)); @@ -268,42 +273,21 @@ static pxl8_bsp* assembly_to_bsp(pxl8_world_chunk_assembly* a) { } } - if (wire_hdr.num_heightfield > 0) { - bsp->heightfield = pxl8_calloc(wire_hdr.num_heightfield, sizeof(f32)); - bsp->num_heightfield = wire_hdr.num_heightfield; - for (u32 i = 0; i < wire_hdr.num_heightfield; i++) { - u32 raw = pxl8_read_u32_be(&s); - memcpy(&bsp->heightfield[i], &raw, sizeof(f32)); - } - bsp->heightfield_w = pxl8_read_u16_be(&s); - bsp->heightfield_h = pxl8_read_u16_be(&s); - u32 ox_raw = pxl8_read_u32_be(&s); - memcpy(&bsp->heightfield_ox, &ox_raw, sizeof(f32)); - u32 oz_raw = pxl8_read_u32_be(&s); - memcpy(&bsp->heightfield_oz, &oz_raw, sizeof(f32)); - u32 cs_raw = pxl8_read_u32_be(&s); - memcpy(&bsp->heightfield_cell_size, &cs_raw, sizeof(f32)); - } - - u32 raw; - raw = pxl8_read_u32_be(&s); - memcpy(&bsp->bounds_min_x, &raw, sizeof(f32)); - raw = pxl8_read_u32_be(&s); - memcpy(&bsp->bounds_min_z, &raw, sizeof(f32)); - raw = pxl8_read_u32_be(&s); - memcpy(&bsp->bounds_max_x, &raw, sizeof(f32)); - raw = pxl8_read_u32_be(&s); - memcpy(&bsp->bounds_max_z, &raw, sizeof(f32)); + pxl8_debug("Deserialized BSP: %u verts, %u faces, %u nodes, %u leafs", + bsp->num_vertices, bsp->num_faces, bsp->num_nodes, bsp->num_leafs); 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) {