From 6ed4e17065241562141c755a6c13261abf4a97c5 Mon Sep 17 00:00:00 2001 From: asrael Date: Sat, 31 Jan 2026 09:31:17 -0600 Subject: [PATCH] better lighting --- demo/main.fnl | 202 ++-- demo/mod/entities.fnl | 180 ++++ demo/mod/first_person3d.fnl | 511 ++++------ demo/mod/menu.fnl | 155 ++- demo/mod/sky.fnl | 4 + demo/mod/textures.fnl | 162 ++++ pxl8.sh | 35 +- pxl8d/build.rs | 38 +- pxl8d/src/bsp.rs | 315 +++--- pxl8d/src/chunk.rs | 5 +- pxl8d/src/chunk/stream.rs | 12 + pxl8d/src/lib.rs | 6 +- pxl8d/src/log.rs | 14 +- pxl8d/src/main.rs | 120 ++- pxl8d/src/math.rs | 54 +- pxl8d/src/procgen.rs | 250 +++-- pxl8d/src/sim.rs | 172 ++-- pxl8d/src/transport.rs | 22 +- pxl8d/src/voxel.rs | 367 ++++--- pxl8d/src/world.rs | 12 +- src/asset/pxl8_ase.c | 301 +++++- src/asset/pxl8_ase.h | 8 + src/{world => bsp}/pxl8_bsp.c | 415 +------- src/{world => bsp}/pxl8_bsp.h | 11 - src/bsp/pxl8_bsp_render.c | 429 +++++++++ src/bsp/pxl8_bsp_render.h | 30 + src/core/pxl8.c | 73 +- src/core/pxl8_queue.h | 53 ++ src/core/pxl8_types.h | 1 + src/gfx/pxl8_color.h | 115 ++- src/gfx/pxl8_cpu.c | 771 +++++++++------ src/gfx/pxl8_cpu.h | 2 + src/gfx/pxl8_gfx.c | 38 + src/gfx/pxl8_gfx.h | 3 + src/gfx/pxl8_gfx3d.h | 13 + src/gui/pxl8_gui.c | 57 ++ src/gui/pxl8_gui.h | 2 + src/hal/pxl8_hal.h | 28 + src/hal/pxl8_hal_sdl3.c | 8 + src/hal/pxl8_mem.c | 20 + src/hal/pxl8_thread_sdl3.c | 110 +++ src/lua/pxl8/effects.lua | 8 + src/lua/pxl8/gfx2d.lua | 7 +- src/lua/pxl8/gui.lua | 12 + src/lua/pxl8/net.lua | 116 +-- src/lua/pxl8/shader.lua | 281 ++++++ src/lua/pxl8/world.lua | 77 +- src/math/pxl8_math.c | 17 + src/math/pxl8_math.h | 23 + src/math/pxl8_noise.c | 94 ++ src/math/pxl8_noise.h | 18 + src/net/pxl8_net.c | 265 +++++- src/net/pxl8_net.h | 28 +- src/net/pxl8_protocol.h | 16 +- src/script/pxl8_script.c | 4 +- src/script/pxl8_script_ffi.h | 79 +- src/sim/pxl8_sim.c | 224 +++++ src/sim/pxl8_sim.h | 56 ++ src/vxl/pxl8_vxl.c | 319 +++++++ src/vxl/pxl8_vxl.h | 78 ++ src/vxl/pxl8_vxl_render.c | 576 +++++++++++ src/vxl/pxl8_vxl_render.h | 48 + src/world/pxl8_chunk.c | 44 - src/world/pxl8_chunk.h | 33 - src/world/pxl8_chunk_cache.h | 67 -- src/world/pxl8_gen.c | 898 ------------------ src/world/pxl8_gen.h | 32 - src/world/pxl8_voxel.c | 406 -------- src/world/pxl8_voxel.h | 67 -- src/world/pxl8_world.c | 695 +++++++++++++- src/world/pxl8_world.h | 42 +- src/world/pxl8_world_chunk.c | 46 + src/world/pxl8_world_chunk.h | 33 + ...chunk_cache.c => pxl8_world_chunk_cache.c} | 182 ++-- src/world/pxl8_world_chunk_cache.h | 69 ++ 75 files changed, 6417 insertions(+), 3667 deletions(-) create mode 100644 demo/mod/entities.fnl rename src/{world => bsp}/pxl8_bsp.c (56%) rename src/{world => bsp}/pxl8_bsp.h (83%) create mode 100644 src/bsp/pxl8_bsp_render.c create mode 100644 src/bsp/pxl8_bsp_render.h create mode 100644 src/core/pxl8_queue.h create mode 100644 src/hal/pxl8_mem.c create mode 100644 src/hal/pxl8_thread_sdl3.c create mode 100644 src/lua/pxl8/shader.lua create mode 100644 src/math/pxl8_noise.c create mode 100644 src/math/pxl8_noise.h create mode 100644 src/sim/pxl8_sim.c create mode 100644 src/sim/pxl8_sim.h create mode 100644 src/vxl/pxl8_vxl.c create mode 100644 src/vxl/pxl8_vxl.h create mode 100644 src/vxl/pxl8_vxl_render.c create mode 100644 src/vxl/pxl8_vxl_render.h delete mode 100644 src/world/pxl8_chunk.c delete mode 100644 src/world/pxl8_chunk.h delete mode 100644 src/world/pxl8_chunk_cache.h delete mode 100644 src/world/pxl8_gen.c delete mode 100644 src/world/pxl8_gen.h delete mode 100644 src/world/pxl8_voxel.c delete mode 100644 src/world/pxl8_voxel.h create mode 100644 src/world/pxl8_world_chunk.c create mode 100644 src/world/pxl8_world_chunk.h rename src/world/{pxl8_chunk_cache.c => pxl8_world_chunk_cache.c} (68%) create mode 100644 src/world/pxl8_world_chunk_cache.h diff --git a/demo/main.fnl b/demo/main.fnl index 6b9367d..e4cc30c 100644 --- a/demo/main.fnl +++ b/demo/main.fnl @@ -4,13 +4,8 @@ (local first_person3d (require :mod.first_person3d)) (var time 0) -(var active-demo :logo) -(var particles nil) -(var fire-init? false) -(var rain-init? false) -(var snow-init? false) +(var in-world false) (var first_person3d-init? false) -(var use-famicube-palette? false) (var logo-x 256) (var logo-y 148) @@ -18,164 +13,95 @@ (var logo-dy 80) (var logo-sprite nil) (var transition nil) -(var transition-pending nil) +(var transition-to-world false) -(fn switch-demo [new-demo] - (when (and (not= active-demo new-demo) (not transition)) - (set transition-pending new-demo) +(fn start-transition [] + (when (not transition) + (set transition-to-world true) (set transition (pxl8.create_transition :pixelate 0.5)) (transition:set_color 0xFF000000) (transition:start))) +(fn enter-world [] + (when (first_person3d.is-ready) + (start-transition))) + (global init (fn [] (pxl8.load_palette "res/sprites/pxl8_logo.ase") (set logo-sprite (pxl8.load_sprite "res/sprites/pxl8_logo.ase")) - (set particles (pxl8.create_particles 1000)) - (music.init))) + (music.init) + (menu.init) + (first_person3d.preload))) (global update (fn [dt] (when (pxl8.key_pressed "escape") - (menu.toggle) - (when (= active-demo :first_person3d) - (pxl8.set_relative_mouse_mode (not (menu.is-paused))))) + (if (menu.is-paused) + (do + (menu.hide) + (when in-world + (pxl8.set_relative_mouse_mode true))) + (if in-world + (do + (menu.show) + (pxl8.set_relative_mouse_mode false)) + (menu.toggle)))) (when (not (menu.is-paused)) (set time (+ time dt)) + (music.update dt) (when transition (transition:update dt) (when (transition:is_complete) - (when transition-pending - (when (and (= active-demo :first_person3d) (not= transition-pending :first_person3d)) - (pxl8.set_relative_mouse_mode false)) - (when (and (not= active-demo :first_person3d) (= transition-pending :first_person3d)) - (pxl8.set_relative_mouse_mode true)) - (set active-demo transition-pending) - (set transition-pending nil) - (when (= active-demo :fire) (set fire-init? false)) - (when (= active-demo :rain) (set rain-init? false)) - (when (= active-demo :snow) (set snow-init? false)) - (when (= active-demo :first_person3d) (set first_person3d-init? false))) + (when transition-to-world + (set in-world true) + (set transition-to-world false) + (pxl8.set_relative_mouse_mode true)) (transition:destroy) (set transition nil))) - (when (pxl8.key_pressed "1") (switch-demo :logo)) - (when (pxl8.key_pressed "2") (switch-demo :plasma)) - (when (pxl8.key_pressed "3") (switch-demo :tunnel)) - (when (pxl8.key_pressed "4") (switch-demo :raster)) - (when (pxl8.key_pressed "5") (switch-demo :fire)) - (when (pxl8.key_pressed "6") (switch-demo :rain)) - (when (pxl8.key_pressed "7") (switch-demo :snow)) - (when (pxl8.key_pressed "8") (switch-demo :first_person3d)) - (when (pxl8.key_pressed "=") - (set use-famicube-palette? (not use-famicube-palette?)) - (local palette-path (if use-famicube-palette? "res/palettes/famicube.ase" "res/sprites/pxl8_logo.ase")) - (pxl8.load_palette palette-path)) - - (music.update dt) - - (case active-demo - :logo (do - (set logo-x (+ logo-x (* logo-dx dt))) - (set logo-y (+ logo-y (* logo-dy dt))) - (when (< logo-x 0) - (set logo-x 0) - (set logo-dx (math.abs logo-dx))) - (when (> logo-x 512) - (set logo-x 512) - (set logo-dx (- (math.abs logo-dx)))) - (when (< logo-y 0) - (set logo-y 0) - (set logo-dy (math.abs logo-dy))) - (when (> logo-y 296) - (set logo-y 296) - (set logo-dy (- (math.abs logo-dy))))) - :first_person3d (do - (when (not first_person3d-init?) - (first_person3d.init) - (set first_person3d-init? true)) - (first_person3d.update dt))) - - (when particles - (particles:update dt))) + (if in-world + (do + (when (not first_person3d-init?) + (first_person3d.init) + (set first_person3d-init? true)) + (first_person3d.update dt)) + (do + (when (and (not (menu.is-paused)) + (first_person3d.is-ready) + (or (pxl8.key_pressed "return") (pxl8.key_pressed "space"))) + (enter-world)) + (set logo-x (+ logo-x (* logo-dx dt))) + (set logo-y (+ logo-y (* logo-dy dt))) + (when (< logo-x 0) + (set logo-x 0) + (set logo-dx (math.abs logo-dx))) + (when (> logo-x 512) + (set logo-x 512) + (set logo-dx (- (math.abs logo-dx)))) + (when (< logo-y 0) + (set logo-y 0) + (set logo-dy (math.abs logo-dy))) + (when (> logo-y 296) + (set logo-y 296) + (set logo-dy (- (math.abs logo-dy))))))) (when (menu.is-paused) (menu.update)))) (global frame (fn [] - (case active-demo - :logo (do - (pxl8.clear 0) - (when logo-sprite - (pxl8.sprite logo-sprite logo-x logo-y 128 64 (< logo-dx 0) (< logo-dy 0)))) - - :plasma (do - (pxl8.clear 0) - (pxl8.text "Plasma (TODO: Fennel impl)" 200 170 1)) - - :tunnel (do - (pxl8.clear 0) - (pxl8.text "Tunnel (TODO: Fennel impl)" 200 170 1)) - - :raster (do - (pxl8.clear 0) - (pxl8.text "Raster Bars (TODO: Fennel impl)" 180 170 1)) - - :fire (do - (pxl8.clear 0) - (when particles - (when (not fire-init?) - (particles:clear) - (particles:set_position 320 360) - (particles:set_spread 320 0) - (particles:set_gravity 0 -80) - (particles:set_drag 0.98) - (particles:set_turbulence 60) - (particles:set_spawn_rate 200) - (particles:set_colors 1 9) - (particles:set_life 2.0 5.0) - (particles:set_velocity -30 30 -120 -60) - (set fire-init? true)) - (particles:render))) - - :rain (do - (pxl8.clear 0) - (when particles - (when (not rain-init?) - (particles:clear) - (particles:set_position 320 -10) - (particles:set_spread 340 0) - (particles:set_gravity 30 80) - (particles:set_drag 1.0) - (particles:set_turbulence 5) - (particles:set_spawn_rate 500) - (particles:set_colors 20 22) - (particles:set_life 2.0 3.0) - (particles:set_velocity -20 20 400 600) - (set rain-init? true)) - (particles:render))) - - :snow (do - (pxl8.clear 0) - (when particles - (when (not snow-init?) - (particles:clear) - (particles:set_position 320 -10) - (particles:set_spread 340 0) - (particles:set_gravity 0 25) - (particles:set_drag 1.0) - (particles:set_turbulence 100) - (particles:set_spawn_rate 100) - (particles:set_life 8.0 12.0) - (particles:set_colors 10 15) - (particles:set_velocity -50 50 30 50) - (set snow-init? true)) - (particles:render))) - - :first_person3d (first_person3d.frame) - - - _ (pxl8.clear 0)) + (if in-world + (first_person3d.frame) + (do + (pxl8.clear 0) + (when logo-sprite + (pxl8.sprite logo-sprite logo-x logo-y 128 64 (< logo-dx 0) (< logo-dy 0))) + (when (not (menu.is-paused)) + (if (first_person3d.is-ready) + (pxl8.text "Press ENTER to start" 240 320 1) + (if (first_person3d.is-connected) + (pxl8.text "Loading world..." 260 320 1) + (pxl8.text "Connecting..." 275 320 1)))))) (when transition (transition:render)) diff --git a/demo/mod/entities.fnl b/demo/mod/entities.fnl new file mode 100644 index 0000000..829957b --- /dev/null +++ b/demo/mod/entities.fnl @@ -0,0 +1,180 @@ +(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 [] + (let [verts [] + indices [] + hw (/ DOOR_WIDTH 2) + y0 0 + y1 DOOR_HEIGHT + x DOOR_X] + (table.insert verts {:x x :y y0 :z (- DOOR_Z hw) :u 0 :v 1 :nx -1 :ny 0 :nz 0 :color 80 :light 200}) + (table.insert verts {:x x :y y0 :z (+ DOOR_Z hw) :u 1 :v 1 :nx -1 :ny 0 :nz 0 :color 80 :light 200}) + (table.insert verts {:x x :y y1 :z (+ DOOR_Z hw) :u 1 :v 0 :nx -1 :ny 0 :nz 0 :color 80 :light 200}) + (table.insert verts {:x x :y y1 :z (- DOOR_Z hw) :u 0 :v 0 :nx -1 :ny 0 :nz 0 :color 80 :light 200}) + (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 [] + radius 5 + rings 4 + segments 6 + core-color (+ FIREBALL_COLOR 6) + spike-color (- FIREBALL_COLOR 1)] + + (table.insert verts {:x 0 :y radius :z 0 :nx 0 :ny 1 :nz 0 :color core-color :light 255}) + + (for [ring 1 (- rings 1)] + (let [phi (* (/ ring rings) math.pi) + sin-phi (math.sin phi) + cos-phi (math.cos phi) + y (* radius cos-phi) + ring-radius (* radius sin-phi)] + (for [seg 0 (- segments 1)] + (let [theta (* (/ seg segments) math.pi 2) + x (* ring-radius (math.cos theta)) + z (* ring-radius (math.sin theta)) + nx (* sin-phi (math.cos theta)) + nz (* sin-phi (math.sin theta))] + (table.insert verts {:x x :y y :z z :nx nx :ny cos-phi :nz nz :color core-color :light 255}))))) + + (let [bottom-idx (length verts)] + (table.insert verts {:x 0 :y (- radius) :z 0 :nx 0 :ny -1 :nz 0 :color core-color :light 255}) + + (for [seg 0 (- segments 1)] + (let [next-seg (% (+ seg 1) segments)] + (table.insert indices 0) + (table.insert indices (+ 1 next-seg)) + (table.insert indices (+ 1 seg)))) + + (for [ring 0 (- rings 3)] + (for [seg 0 (- segments 1)] + (let [next-seg (% (+ seg 1) segments) + curr-row (+ 1 (* ring segments)) + next-row (+ 1 (* (+ ring 1) segments))] + (table.insert indices (+ curr-row seg)) + (table.insert indices (+ curr-row next-seg)) + (table.insert indices (+ next-row seg)) + (table.insert indices (+ curr-row next-seg)) + (table.insert indices (+ next-row next-seg)) + (table.insert indices (+ next-row seg))))) + + (let [last-ring-start (+ 1 (* (- rings 2) segments))] + (for [seg 0 (- segments 1)] + (let [next-seg (% (+ seg 1) segments)] + (table.insert indices bottom-idx) + (table.insert indices (+ last-ring-start seg)) + (table.insert indices (+ last-ring-start next-seg)))))) + + (let [num-spikes 12 + spike-len 8 + base-size 1.2 + golden-ratio (/ (+ 1 (math.sqrt 5)) 2)] + (for [i 0 (- num-spikes 1)] + (let [y (- 1 (* (/ i (- num-spikes 1)) 2)) + r-at-y (math.sqrt (- 1 (* y y))) + theta (* math.pi 2 i golden-ratio) + nx (* r-at-y (math.cos theta)) + ny y + nz (* r-at-y (math.sin theta)) + tx (if (> (math.abs ny) 0.9) 1 0) + ty (if (> (math.abs ny) 0.9) 0 1) + tz 0 + px (- (* ty nz) (* tz ny)) + py (- (* tz nx) (* tx nz)) + pz (- (* tx ny) (* ty nx)) + pl (math.sqrt (+ (* px px) (* py py) (* pz pz))) + px (/ px pl) py (/ py pl) pz (/ pz pl) + qx (- (* ny pz) (* nz py)) + qy (- (* nz px) (* nx pz)) + qz (- (* nx py) (* ny px)) + bx (* radius 0.8 nx) + by (* radius 0.8 ny) + bz (* radius 0.8 nz) + sx (* (+ radius spike-len) nx) + sy (* (+ radius spike-len) ny) + sz (* (+ radius spike-len) nz) + base-idx (length verts)] + (table.insert verts {:x (+ bx (* base-size px) (* base-size qx)) + :y (+ by (* base-size py) (* base-size qy)) + :z (+ bz (* base-size pz) (* base-size qz)) + :nx nx :ny ny :nz nz :color core-color :light 255}) + (table.insert verts {:x (+ bx (* base-size px) (* (- base-size) qx)) + :y (+ by (* base-size py) (* (- base-size) qy)) + :z (+ bz (* base-size pz) (* (- base-size) qz)) + :nx nx :ny ny :nz nz :color core-color :light 255}) + (table.insert verts {:x (+ bx (* (- base-size) px) (* (- base-size) qx)) + :y (+ by (* (- base-size) py) (* (- base-size) qy)) + :z (+ bz (* (- base-size) pz) (* (- base-size) qz)) + :nx nx :ny ny :nz nz :color core-color :light 255}) + (table.insert verts {:x (+ bx (* (- base-size) px) (* base-size qx)) + :y (+ by (* (- base-size) py) (* base-size qy)) + :z (+ bz (* (- base-size) pz) (* base-size qz)) + :nx nx :ny ny :nz nz :color core-color :light 255}) + (table.insert verts {:x sx :y sy :z sz :nx nx :ny ny :nz nz :color spike-color :light 255}) + (table.insert indices base-idx) + (table.insert indices (+ base-idx 1)) + (table.insert indices (+ base-idx 4)) + (table.insert indices (+ base-idx 1)) + (table.insert indices (+ base-idx 2)) + (table.insert indices (+ base-idx 4)) + (table.insert indices (+ base-idx 2)) + (table.insert indices (+ base-idx 3)) + (table.insert indices (+ base-idx 4)) + (table.insert indices (+ base-idx 3)) + (table.insert indices base-idx) + (table.insert indices (+ base-idx 4))))) + + (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 door-mesh) + (create-door-mesh)) + (when (not fireball-mesh) + (create-fireball-mesh)) + (when (and (not door-tex) textures) + (set door-tex (textures.door)))) + +(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 + :passthrough true + :wireframe wireframe + :emissive 1.0}))) + +{:FIREBALL_COLOR FIREBALL_COLOR + :get-door-position get-door-position + :get-door-radius get-door-radius + :init init + :render-door render-door + :render-fireball render-fireball} diff --git a/demo/mod/first_person3d.fnl b/demo/mod/first_person3d.fnl index c66aa46..c231e0b 100644 --- a/demo/mod/first_person3d.fnl +++ b/demo/mod/first_person3d.fnl @@ -1,8 +1,10 @@ (local pxl8 (require :pxl8)) (local effects (require :pxl8.effects)) (local net (require :pxl8.net)) +(local shader (require :pxl8.shader)) (local colormap (require :mod.colormap)) +(local entities (require :mod.entities)) (local menu (require :mod.menu)) (local palette (require :mod.palette)) (local sky (require :mod.sky)) @@ -11,234 +13,96 @@ (local bob-amount 4.0) (local bob-speed 8.0) (local cam-smoothing 0.25) +(local ceiling-height 120) (local chunk-size 64) (local cursor-sensitivity 0.010) -(local gravity -800) +(local gravity 600) (local grid-size 64) -(local ground-y 64) -(local jump-force 175) +(local jump-velocity 180) (local land-recovery-speed 20) (local land-squash-amount -4) (local max-pitch 1.5) (local move-speed 200) +(local player-eye-height 64) +(local player-height 72) +(local player-radius 12) +(local step-height 24) (local turn-speed 4.0) -(local sim-tick-rate 60) -(local sim-dt (/ 1.0 sim-tick-rate)) -(local history-size 128) -(local correction-threshold 1.0) - -(var auto-run? false) (var auto-run-cancel-key nil) +(var auto-run? false) (var bob-time 0) (var cam-pitch 0) (var cam-x 416) -(var cam-y 64) +(var cam-y 0) (var cam-yaw 0) (var cam-z 416) (var camera nil) (var ceiling-tex nil) -(var fireball-mesh nil) (var floor-tex nil) (var fps-avg 0) (var fps-sample-count 0) -(var grounded? true) +(var grounded true) (var land-squash 0) (var last-dt 0.016) (var light-time 0) (var lights nil) -(var materials-setup false) +(var bsp-materials-setup false) (var network nil) +(var portal-cooldown 0) (var real-time 0) (var smooth-cam-x 416) (var smooth-cam-z 416) -(var velocity-y 0) +(var vel-y 0) +(var trim-tex nil) (var wall-tex nil) (var world nil) (local cursor-look? true) -(local FIREBALL_COLOR 218) +(local MOSS_COLOR 200) +(local PLASTER_COLOR 16) (local STONE_FLOOR_START 37) (local STONE_WALL_START 2) -(local MOSS_COLOR 200) +(local WOOD_COLOR 88) -(local trail-positions []) -(local TRAIL_LENGTH 8) +(fn find-floor [x y z in-bsp] + (if (not world) + 0 + (if in-bsp + 0 + (let [start-y (math.max (+ y 64) 256) + ray (world:ray x start-y z x (- y 1000) z)] + (if ray.hit + (+ ray.point.y 1) + 128))))) -(fn create-fireball-mesh [] - (let [verts [] - indices [] - radius 5 - rings 4 - segments 6 - core-color (+ FIREBALL_COLOR 6) - spike-color (+ FIREBALL_COLOR 2)] +(fn find-ceiling [x y z max-height] + (if (not world) + nil + (let [ray (world:ray x (+ y 1) z x (+ y max-height) z)] + (if ray.hit + (- ray.point.y 1) + nil)))) - ;; top pole - (table.insert verts {:x 0 :y radius :z 0 :nx 0 :ny 1 :nz 0 :color core-color :light 255}) +(fn preload [] + (when (not network) + (set network (net.get)) + (when network + (network:spawn cam-x cam-y cam-z cam-yaw cam-pitch))) + (when (not world) + (set world (pxl8.get_world)) + (when world + (world:init_local_player cam-x cam-y cam-z)))) - ;; sphere rings - (for [ring 1 (- rings 1)] - (let [phi (* (/ ring rings) math.pi) - sin-phi (math.sin phi) - cos-phi (math.cos phi) - y (* radius cos-phi) - ring-radius (* radius sin-phi)] - (for [seg 0 (- segments 1)] - (let [theta (* (/ seg segments) math.pi 2) - x (* ring-radius (math.cos theta)) - z (* ring-radius (math.sin theta)) - nx (* sin-phi (math.cos theta)) - nz (* sin-phi (math.sin theta))] - (table.insert verts {:x x :y y :z z :nx nx :ny cos-phi :nz nz :color core-color :light 255}))))) +(fn is-connected [] + (and network (network:connected))) - ;; bottom pole - (let [bottom-idx (length verts)] - (table.insert verts {:x 0 :y (- radius) :z 0 :nx 0 :ny -1 :nz 0 :color core-color :light 255}) +(fn is-ready [] + (if (not world) + false + (let [chunk (world:active_chunk)] + (and chunk (chunk:ready))))) - ;; top cap triangles - (for [seg 0 (- segments 1)] - (let [next-seg (% (+ seg 1) segments)] - (table.insert indices 0) - (table.insert indices (+ 1 next-seg)) - (table.insert indices (+ 1 seg)))) - - ;; middle quads - (for [ring 0 (- rings 3)] - (for [seg 0 (- segments 1)] - (let [next-seg (% (+ seg 1) segments) - curr-row (+ 1 (* ring segments)) - next-row (+ 1 (* (+ ring 1) segments))] - (table.insert indices (+ curr-row seg)) - (table.insert indices (+ curr-row next-seg)) - (table.insert indices (+ next-row seg)) - (table.insert indices (+ curr-row next-seg)) - (table.insert indices (+ next-row next-seg)) - (table.insert indices (+ next-row seg))))) - - ;; bottom cap triangles - (let [last-ring-start (+ 1 (* (- rings 2) segments))] - (for [seg 0 (- segments 1)] - (let [next-seg (% (+ seg 1) segments)] - (table.insert indices bottom-idx) - (table.insert indices (+ last-ring-start seg)) - (table.insert indices (+ last-ring-start next-seg)))))) - - ;; add spikes - evenly distributed using golden ratio - (let [num-spikes 12 - spike-len 8 - base-size 1.2 - golden-ratio (/ (+ 1 (math.sqrt 5)) 2)] - (for [i 0 (- num-spikes 1)] - (let [;; fibonacci sphere distribution - y (- 1 (* (/ i (- num-spikes 1)) 2)) - r-at-y (math.sqrt (- 1 (* y y))) - theta (* math.pi 2 i golden-ratio) - nx (* r-at-y (math.cos theta)) - ny y - nz (* r-at-y (math.sin theta)) - ;; tangent vectors for base - tx (if (> (math.abs ny) 0.9) 1 0) - ty (if (> (math.abs ny) 0.9) 0 1) - tz 0 - ;; cross product for perpendicular - px (- (* ty nz) (* tz ny)) - py (- (* tz nx) (* tx nz)) - pz (- (* tx ny) (* ty nx)) - pl (math.sqrt (+ (* px px) (* py py) (* pz pz))) - px (/ px pl) py (/ py pl) pz (/ pz pl) - ;; second perpendicular - qx (- (* ny pz) (* nz py)) - qy (- (* nz px) (* nx pz)) - qz (- (* nx py) (* ny px)) - ;; base center inside sphere - bx (* radius 0.8 nx) - by (* radius 0.8 ny) - bz (* radius 0.8 nz) - ;; spike tip - sx (* (+ radius spike-len) nx) - sy (* (+ radius spike-len) ny) - sz (* (+ radius spike-len) nz) - base-idx (length verts)] - ;; 4 base vertices forming a square - (table.insert verts {:x (+ bx (* base-size px) (* base-size qx)) - :y (+ by (* base-size py) (* base-size qy)) - :z (+ bz (* base-size pz) (* base-size qz)) - :nx nx :ny ny :nz nz :color core-color :light 255}) - (table.insert verts {:x (+ bx (* base-size px) (* (- base-size) qx)) - :y (+ by (* base-size py) (* (- base-size) qy)) - :z (+ bz (* base-size pz) (* (- base-size) qz)) - :nx nx :ny ny :nz nz :color core-color :light 255}) - (table.insert verts {:x (+ bx (* (- base-size) px) (* (- base-size) qx)) - :y (+ by (* (- base-size) py) (* (- base-size) qy)) - :z (+ bz (* (- base-size) pz) (* (- base-size) qz)) - :nx nx :ny ny :nz nz :color core-color :light 255}) - (table.insert verts {:x (+ bx (* (- base-size) px) (* base-size qx)) - :y (+ by (* (- base-size) py) (* base-size qy)) - :z (+ bz (* (- base-size) pz) (* base-size qz)) - :nx nx :ny ny :nz nz :color core-color :light 255}) - ;; spike tip - (table.insert verts {:x sx :y sy :z sz :nx nx :ny ny :nz nz :color spike-color :light 255}) - ;; 4 triangular faces of pyramid - (table.insert indices base-idx) - (table.insert indices (+ base-idx 1)) - (table.insert indices (+ base-idx 4)) - (table.insert indices (+ base-idx 1)) - (table.insert indices (+ base-idx 2)) - (table.insert indices (+ base-idx 4)) - (table.insert indices (+ base-idx 2)) - (table.insert indices (+ base-idx 3)) - (table.insert indices (+ base-idx 4)) - (table.insert indices (+ base-idx 3)) - (table.insert indices base-idx) - (table.insert indices (+ base-idx 4))))) - - (set fireball-mesh (pxl8.create_mesh verts indices)))) - -(var client-tick 0) -(var last-processed-tick 0) -(var time-accumulator 0) -(var position-history {}) -(var pending-inputs {}) - -(fn history-idx [tick] - (+ 1 (% tick history-size))) - -(fn store-position [tick x z yaw] - (tset position-history (history-idx tick) {:tick tick :x x :z z :yaw yaw})) - -(fn get-position [tick] - (let [entry (. position-history (history-idx tick))] - (when (and entry (= entry.tick tick)) - entry))) - -(fn store-pending-input [tick input] - (tset pending-inputs (history-idx tick) {:tick tick :input input})) - -(fn get-pending-input [tick] - (let [entry (. pending-inputs (history-idx tick))] - (when (and entry (= entry.tick tick)) - entry.input))) - -(fn apply-movement [x z yaw input] - (var new-x x) - (var new-z z) - - (let [move-forward (or input.move_y 0) - move-right (or input.move_x 0)] - (when (or (not= move-forward 0) (not= move-right 0)) - (let [forward-x (- (math.sin yaw)) - forward-z (- (math.cos yaw)) - right-x (math.cos yaw) - right-z (- (math.sin yaw)) - len (math.sqrt (+ (* move-forward move-forward) (* move-right move-right))) - norm-forward (/ move-forward len) - norm-right (/ move-right len) - move-delta (* move-speed sim-dt)] - (set new-x (+ new-x (* move-delta (+ (* forward-x norm-forward) (* right-x norm-right))))) - (set new-z (+ new-z (* move-delta (+ (* forward-z norm-forward) (* right-z norm-right)))))))) - - (values new-x new-z)) (fn init [] (pxl8.set_relative_mouse_mode true) (pxl8.set_palette palette 256) @@ -248,7 +112,9 @@ r 0xFF g (math.floor (+ 0x60 (* t (- 0xE0 0x60)))) b (math.floor (+ 0x10 (* t (- 0x80 0x10))))] - (pxl8.set_palette_rgb (+ FIREBALL_COLOR i) r g b))) + (pxl8.set_palette_rgb (+ entities.FIREBALL_COLOR i) r g b))) + (pxl8.set_palette_rgb (- entities.FIREBALL_COLOR 1) 0xFF 0x20 0x10) + (sky.reset-gradient) (sky.update-gradient 1 2 6 6 10 18) (pxl8.update_palette_deps) @@ -256,49 +122,38 @@ (set camera (pxl8.create_camera_3d))) (when (not lights) (set lights (pxl8.create_lights))) - (when (not fireball-mesh) - (create-fireball-mesh)) + + (entities.init textures) (sky.generate-stars 12345) - (when (not network) - (set network (net.get)) - (when network - (network:spawn cam-x cam-y cam-z cam-yaw cam-pitch))) + (preload) - (when (not world) - (set world (pxl8.get_world))) - - (when (not floor-tex) - (set floor-tex (textures.mossy-cobblestone 44444 STONE_FLOOR_START MOSS_COLOR))) - (when (not wall-tex) - (set wall-tex (textures.ashlar-wall 55555 STONE_WALL_START MOSS_COLOR))) (when (not ceiling-tex) - (set ceiling-tex (pxl8.create_texture [0] 1 1))) + (set ceiling-tex (textures.plaster-wall 66666 PLASTER_COLOR))) + (when (not floor-tex) + (set floor-tex (textures.wood-planks 44444 WOOD_COLOR))) + (when (not trim-tex) + (set trim-tex (textures.wood-trim 77777 WOOD_COLOR))) + (when (not wall-tex) + (set wall-tex (textures.cobble-timber 55555 STONE_WALL_START MOSS_COLOR WOOD_COLOR))) ) (fn setup-materials [] - (when (and world (not materials-setup)) + (when (and world (not bsp-materials-setup) ceiling-tex floor-tex trim-tex wall-tex) (let [chunk (world:active_chunk)] (when (and chunk (chunk:ready)) - (let [bsp (chunk:bsp)] - (when bsp - (let [floor-mat (pxl8.create_material {:texture floor-tex :lighting true}) - wall-mat (pxl8.create_material {:texture wall-tex :lighting true}) - ceiling-mat (pxl8.create_material {:texture ceiling-tex})] + (let [ceiling-mat (pxl8.create_material {:texture ceiling-tex}) + floor-mat (pxl8.create_material {:texture floor-tex :lighting true}) + trim-mat (pxl8.create_material {:texture trim-tex :lighting true}) + wall-mat (pxl8.create_material {:texture wall-tex :lighting true})] - (bsp:set_material 0 floor-mat) - (bsp:set_material 1 wall-mat) - (bsp:set_material 2 ceiling-mat) - - (for [i 0 (- (bsp:face_count) 1)] - (let [n (bsp:face_normal i)] - (bsp:face_set_material i - (if (> n.y 0.7) 0 - (< n.y -0.7) 2 - 1)))) - (set materials-setup true)))))))) + (world:set_bsp_material 0 floor-mat) + (world:set_bsp_material 1 wall-mat) + (world:set_bsp_material 2 ceiling-mat) + (world:set_bsp_material 3 trim-mat) + (set bsp-materials-setup true)))))) (fn sample-input [] (var move-forward 0) @@ -329,26 +184,6 @@ :look_dx (pxl8.mouse_dx) :look_dy (pxl8.mouse_dy)}) -(fn reconcile [server-tick server-x server-z] - (let [predicted (get-position server-tick)] - (when predicted - (let [dx (- predicted.x server-x) - dz (- predicted.z server-z) - error (math.sqrt (+ (* dx dx) (* dz dz)))] - (when (> error correction-threshold) - (set cam-x server-x) - (set cam-z server-z) - - (for [t (+ server-tick 1) client-tick] - (let [input (get-pending-input t) - hist (get-position t)] - (when (and input hist) - (let [(new-x new-z) (apply-movement cam-x cam-z hist.yaw input) - (resolved-x _ resolved-z) (world:resolve_collision cam-x cam-y cam-z new-x cam-y new-z 5)] - (set cam-x resolved-x) - (set cam-z resolved-z) - (store-position t cam-x cam-z hist.yaw)))))))))) - (fn update [dt] (set last-dt dt) (let [fps (pxl8.get_fps)] @@ -361,87 +196,111 @@ (setup-materials) - (let [chunk (world:active_chunk)] - (when (and chunk (chunk:ready)) + (when (> portal-cooldown 0) + (set portal-cooldown (- portal-cooldown dt))) + + (when (and network (<= portal-cooldown 0)) + (let [chunk (world:active_chunk) + in-bsp (not= chunk nil) + (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) + (if in-bsp + (do + (pxl8.info "Exiting through door...") + (let [exit-x (+ door-x 50) + exit-z door-z + exit-y 200] + (network:exit_chunk exit-x exit-y exit-z) + (set cam-x exit-x) + (set cam-z exit-z) + (set cam-y exit-y)) + (set vel-y 0) + (set grounded false) + (set smooth-cam-x cam-x) + (set smooth-cam-z cam-z) + (set bsp-materials-setup false) + (set portal-cooldown 2.0)) + (do + (pxl8.info "Entering through door...") + (network:enter_chunk 1) + (set cam-x 416) + (set cam-z 416) + (set cam-y 0) + (set vel-y 0) + (set grounded true) + (set smooth-cam-x 416) + (set smooth-cam-z 416) + (set portal-cooldown 2.0)))))) + + (let [chunk (world:active_chunk) + chunk-id (if network (network:chunk_id) -1) + expecting-bsp (> chunk-id 0) + voxel-space (and (not chunk) (= chunk-id 0)) + ready (or voxel-space (and chunk (chunk:ready)))] + (when ready (let [input (sample-input) - grid-max (* grid-size chunk-size) + grid-max (if voxel-space 100000 (* grid-size chunk-size)) movement-yaw cam-yaw] - (set time-accumulator (+ time-accumulator dt)) + (let [player (world:local_player)] + (when player + (set cam-x player.pos.x) + (set cam-y player.pos.y) + (set cam-z player.pos.z) + (set cam-yaw player.yaw) + (set cam-pitch player.pitch))) - (while (>= time-accumulator sim-dt) - (set time-accumulator (- time-accumulator sim-dt)) - (set client-tick (+ client-tick 1)) - - (store-pending-input client-tick input) - - (let [(new-x new-z) (apply-movement cam-x cam-z movement-yaw input)] - (when (and (>= new-x 0) (<= new-x grid-max) - (>= new-z 0) (<= new-z grid-max)) - (let [(resolved-x _ resolved-z) (world:resolve_collision cam-x cam-y cam-z new-x cam-y new-z 5)] - (set cam-x resolved-x) - (set cam-z resolved-z))) - - (store-position client-tick cam-x cam-z movement-yaw))) - - (when cursor-look? - (set cam-yaw (- cam-yaw (* input.look_dx cursor-sensitivity))) - (set cam-pitch (math.max (- max-pitch) - (math.min max-pitch - (- cam-pitch (* input.look_dy cursor-sensitivity)))))) - - (when (and (not cursor-look?) (pxl8.key_down "up")) - (set cam-pitch (math.min max-pitch (+ cam-pitch (* turn-speed dt))))) - (when (and (not cursor-look?) (pxl8.key_down "down")) - (set cam-pitch (math.max (- max-pitch) (- cam-pitch (* turn-speed dt))))) - (when (and (not cursor-look?) (pxl8.key_down "left")) - (set cam-yaw (- cam-yaw (* turn-speed dt)))) - (when (and (not cursor-look?) (pxl8.key_down "right")) - (set cam-yaw (+ cam-yaw (* turn-speed dt)))) - - (when network - (let [(ok err) (pcall (fn [] - (network:send_input {:move_x input.move_x - :move_y input.move_y - :look_dx input.look_dx - :look_dy input.look_dy - :yaw movement-yaw - :tick client-tick}) - (let [snapshot (network:snapshot)] - (when (and snapshot (> snapshot.tick last-processed-tick)) - (set last-processed-tick snapshot.tick) - (let [player-id (network:player_id)] - (when (> player-id 0) - (let [curr (network:entity_userdata player-id)] - (when curr - (let [srv-x (pxl8.unpack_f32_be curr 0) - srv-z (pxl8.unpack_f32_be curr 8)] - (reconcile snapshot.tick srv-x srv-z))))))))))] - (when (not ok) - (pxl8.error (.. "Network error: " err))))) + (when (and voxel-space grounded) + (let [floor-y (find-floor cam-x cam-y cam-z false) + height-diff (- floor-y cam-y)] + (if (and (>= height-diff (- step-height)) (<= height-diff step-height)) + (let [lerp-speed 0.3 + smooth-y (+ (* cam-y (- 1 lerp-speed)) (* floor-y lerp-speed))] + (set cam-y smooth-y)) + (when (< height-diff (- step-height)) + (set grounded false))))) (set smooth-cam-x (+ (* smooth-cam-x (- 1 cam-smoothing)) (* cam-x cam-smoothing))) (set smooth-cam-z (+ (* smooth-cam-z (- 1 cam-smoothing)) (* cam-z cam-smoothing))) - (when (and (pxl8.key_pressed "space") grounded?) - (set velocity-y jump-force) - (set grounded? false)) + (when (and (pxl8.key_pressed "space") grounded) + (set vel-y jump-velocity) + (set grounded false)) - (set velocity-y (+ velocity-y (* gravity dt))) - (set cam-y (+ cam-y (* velocity-y dt))) + (when (or (not grounded) (not= vel-y 0)) + (set vel-y (- vel-y (* gravity dt))) - (when (<= cam-y ground-y) - (when (not grounded?) - (set land-squash land-squash-amount)) - (set cam-y ground-y) - (set velocity-y 0) - (set grounded? true)) + (if (> vel-y 0) + (let [new-y (+ cam-y (* vel-y dt)) + head-y (+ new-y player-height) + ceiling-y (if voxel-space + (find-ceiling cam-x cam-y cam-z 200) + ceiling-height)] + (if (and ceiling-y (>= head-y ceiling-y)) + (do + (set cam-y (- ceiling-y player-height)) + (set vel-y 0)) + (set cam-y new-y))) + (let [new-y (+ cam-y (* vel-y dt)) + floor-y (find-floor cam-x cam-y cam-z (not voxel-space))] + (if (<= new-y floor-y) + (do + (set cam-y floor-y) + (set land-squash land-squash-amount) + (set vel-y 0) + (set grounded true)) + (do + (set cam-y new-y) + (set grounded false)))))) (when (< land-squash 0) (set land-squash (math.min 0 (+ land-squash (* land-recovery-speed dt))))) (let [moving (or (not= input.move_x 0) (not= input.move_y 0))] - (if (and moving grounded?) + (if (and moving grounded) (set bob-time (+ bob-time (* dt bob-speed))) (let [target-phase (* (math.floor (/ bob-time math.pi)) math.pi)] (set bob-time (+ (* bob-time 0.8) (* target-phase 0.2)))))) @@ -458,13 +317,17 @@ (when (not world) (pxl8.error "world is nil!")) - (let [chunk (when world (world:active_chunk))] - (when (and world (or (not chunk) (not (chunk:ready)))) + (let [chunk (when world (world:active_chunk)) + expecting-bsp (and network (> (network:chunk_id) 0)) + voxel-space (and world (not chunk) (not expecting-bsp)) + ready (or voxel-space (and chunk (chunk:ready)))] + + (when (and world (not ready)) (pxl8.text "Waiting for world data..." 5 30 12)) - (when (and camera world chunk (chunk:ready)) + (when (and camera world ready) (let [bob-offset (* (math.sin bob-time) bob-amount) - eye-y (+ cam-y bob-offset land-squash) + eye-y (+ cam-y player-eye-height bob-offset land-squash) forward-x (- (math.sin cam-yaw)) forward-z (- (math.cos cam-yaw)) target-x (+ smooth-cam-x forward-x) @@ -490,7 +353,7 @@ 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 255 200 150 light-intensity light-radius) + (lights:add light-x light-y light-z 0xFF8C32 light-intensity light-radius) (pxl8.begin_frame_3d camera lights { :ambient 30 :fog_density 0.0 @@ -502,21 +365,22 @@ (sky.render smooth-cam-x eye-y smooth-cam-z (menu.is-wireframe)) (pxl8.clear_depth) - (when chunk - (let [bsp (chunk:bsp)] - (when bsp - (bsp:set_wireframe (menu.is-wireframe))))) + (world:set_wireframe (menu.is-wireframe)) (world:render [smooth-cam-x eye-y smooth-cam-z]) - (when fireball-mesh - (let [wire (menu.is-wireframe)] - (pxl8.draw_mesh fireball-mesh {:x light-x :y light-y :z light-z - :passthrough true - :wireframe wire - :emissive 1.0}))) + (when chunk + (entities.render-fireball light-x light-y light-z (menu.is-wireframe))) + + (entities.render-door (menu.is-wireframe) (if voxel-space 128 0)) (pxl8.end_frame_3d)) + ;; TODO: shader needs to run at present time, not mid-frame + ;; (shader.begin_frame) + ;; (shader.run shader.presets.light_with_fog + ;; {:fog_r 6 :fog_g 6 :fog_b 12 + ;; :fog_start 0.1 :fog_end 0.85}) + (sky.render-stars smooth-cam-x eye-y smooth-cam-z 1.0 last-dt) (let [cx (/ (pxl8.get_width) 2) @@ -532,6 +396,9 @@ (string.format "%.0f" cam-y) "," (string.format "%.0f" cam-z)) 5 15 text-color)))))) -{:init init +{:preload preload + :is-connected is-connected + :is-ready is-ready + :init init :update update :frame frame} diff --git a/demo/mod/menu.fnl b/demo/mod/menu.fnl index 689fc49..5bee354 100644 --- a/demo/mod/menu.fnl +++ b/demo/mod/menu.fnl @@ -1,15 +1,28 @@ (local pxl8 (require :pxl8)) (local music (require :mod.music)) +(local world-mod (require :pxl8.world)) +(local net-mod (require :pxl8.net)) (var paused false) (var wireframe false) (var gui nil) +(var render-distance 3) +(var sim-distance 4) +(var current-panel :main) +(var selected-item nil) +(var current-items []) +(var pending-action nil) (fn init [] - (set gui (pxl8.create_gui))) + (set gui (pxl8.create_gui)) + (let [w (world-mod.World.get)] + (when w + (set render-distance (w:get_render_distance)) + (set sim-distance (w:get_sim_distance))))) (fn show [] (set paused true) + (set current-panel :main) (pxl8.set_relative_mouse_mode false) (pxl8.center_cursor)) @@ -23,7 +36,35 @@ (hide) (show))) +(fn select-next [] + (when (> (length current-items) 0) + (var found-idx nil) + (for [i 1 (length current-items)] + (when (= (. current-items i) selected-item) + (set found-idx i))) + (if found-idx + (let [next-idx (+ found-idx 1)] + (if (<= next-idx (length current-items)) + (set selected-item (. current-items next-idx)) + (set selected-item (. current-items 1)))) + (set selected-item (. current-items 1))))) + +(fn select-prev [] + (when (> (length current-items) 0) + (var found-idx nil) + (for [i 1 (length current-items)] + (when (= (. current-items i) selected-item) + (set found-idx i))) + (if found-idx + (let [prev-idx (- found-idx 1)] + (if (>= prev-idx 1) + (set selected-item (. current-items prev-idx)) + (set selected-item (. current-items (length current-items))))) + (set selected-item (. current-items (length current-items)))))) + (fn update [] + (set pending-action nil) + (when gui (let [(mx my) (pxl8.get_mouse_pos)] (gui:cursor_move mx my)) @@ -32,29 +73,102 @@ (gui:cursor_down)) (when (pxl8.mouse_released 1) - (gui:cursor_up)))) + (gui:cursor_up)) + + (when (or (pxl8.key_pressed "down") + (and (pxl8.key_pressed "tab") (not (pxl8.key_down "lshift")) (not (pxl8.key_down "rshift")))) + (select-next)) + + (when (or (pxl8.key_pressed "up") + (and (pxl8.key_pressed "tab") (or (pxl8.key_down "lshift") (pxl8.key_down "rshift")))) + (select-prev)) + + (when (or (pxl8.key_pressed "return") (pxl8.key_pressed "space")) + (when selected-item + (set pending-action selected-item))))) + +(fn menu-button [id x y w h label] + (table.insert current-items label) + (when (= selected-item nil) + (set selected-item label)) + (let [is-selected (= selected-item label)] + (when is-selected + (pxl8.rect (- x 3) (- y 3) (+ w 6) (+ h 6) 15)) + (let [clicked (gui:button id x y w h label)] + (when clicked + (set selected-item label)) + (or clicked (and is-selected (= pending-action label)))))) + +(fn draw-main-menu [] + (pxl8.gui_window 200 80 240 235 "pxl8 demo") + + (when (menu-button 1 215 127 210 30 "Resume") + (hide)) + + (let [wire-label (if wireframe "Wireframe: On" "Wireframe: Off")] + (when (menu-button 4 215 162 210 30 wire-label) + (set wireframe (not wireframe)))) + + (when (menu-button 5 215 197 210 30 "GFX") + (set current-panel :gfx) + (set selected-item nil)) + + (when (menu-button 6 215 232 210 30 "SFX") + (set current-panel :sfx) + (set selected-item nil)) + + (when (menu-button 2 215 267 210 30 "Quit") + (pxl8.quit))) + +(fn draw-sfx-panel [] + (pxl8.gui_window 200 100 240 145 "SFX") + + (let [music-label (if (music.is-playing) "Music: On" "Music: Off")] + (when (menu-button 10 215 147 210 30 music-label) + (if (music.is-playing) + (music.stop) + (music.start)))) + + (pxl8.gui_label 215 185 "Volume/Devices: TODO" 15) + + (when (menu-button 20 215 210 210 30 "Back") + (set current-panel :main) + (set selected-item nil))) + +(fn draw-gfx-panel [] + (pxl8.gui_window 200 100 240 180 "GFX") + + (pxl8.gui_label 215 150 (.. "Render: " render-distance) 15) + (let [(changed new-val) (gui:slider_int 30 215 165 210 16 render-distance 1 8)] + (when changed + (set render-distance new-val) + (let [w (world-mod.World.get) + n (net-mod.get)] + (when w (w:set_render_distance new-val)) + (when n (n:set_chunk_settings new-val sim-distance))))) + + (pxl8.gui_label 215 190 (.. "Sim: " sim-distance) 15) + (let [(changed new-val) (gui:slider_int 31 215 205 210 16 sim-distance 1 8)] + (when changed + (set sim-distance new-val) + (let [w (world-mod.World.get) + n (net-mod.get)] + (when w (w:set_sim_distance new-val)) + (when n (n:set_chunk_settings render-distance new-val))))) + + (when (menu-button 32 215 240 210 30 "Back") + (set current-panel :main) + (set selected-item nil))) (fn draw [] + (set current-items []) (when gui (gui:begin_frame) - (pxl8.gui_window 200 100 240 200 "pxl8 demo") - - (when (gui:button 1 215 147 210 30 "Resume") - (hide)) - - (let [music-label (if (music.is-playing) "Music: On" "Music: Off")] - (when (gui:button 3 215 182 210 30 music-label) - (if (music.is-playing) - (music.stop) - (music.start)))) - - (let [wire-label (if wireframe "Wireframe: On" "Wireframe: Off")] - (when (gui:button 4 215 217 210 30 wire-label) - (set wireframe (not wireframe)))) - - (when (gui:button 2 215 252 210 30 "Quit") - (pxl8.quit)) + (case current-panel + :main (draw-main-menu) + :sfx (draw-sfx-panel) + :gfx (draw-gfx-panel)) (if (gui:is_hovering) (pxl8.set_cursor :hand) @@ -62,7 +176,8 @@ (gui:end_frame))) -{:is-paused (fn [] paused) +{:init init + :is-paused (fn [] paused) :is-wireframe (fn [] wireframe) :toggle toggle :show show diff --git a/demo/mod/sky.fnl b/demo/mod/sky.fnl index 6661455..f1727fe 100644 --- a/demo/mod/sky.fnl +++ b/demo/mod/sky.fnl @@ -96,6 +96,9 @@ (set sky-mesh (pxl8.create_mesh verts indices)))) +(fn reset-gradient [] + (set last-gradient-key nil)) + (fn update-gradient [zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b] (let [key (.. zenith-r "," zenith-g "," zenith-b "," horizon-r "," horizon-g "," horizon-b)] (when (not= key last-gradient-key) @@ -249,6 +252,7 @@ {:render render :render-stars render-stars :generate-stars generate-stars + :reset-gradient reset-gradient :update-gradient update-gradient :SKY_GRADIENT_START SKY_GRADIENT_START :SKY_GRADIENT_COUNT SKY_GRADIENT_COUNT} diff --git a/demo/mod/textures.fnl b/demo/mod/textures.fnl index c4966ef..6225235 100644 --- a/demo/mod/textures.fnl +++ b/demo/mod/textures.fnl @@ -59,6 +59,10 @@ (let [s (const ctx scale)] (ctx.graph:add_node procgen.OP_NOISE_VALUE ctx.x ctx.y s 0 0))) +(fn noise-value-at [ctx x y scale] + (let [s (const ctx scale)] + (ctx.graph:add_node procgen.OP_NOISE_VALUE x y s 0 0))) + (fn noise-perlin [ctx scale] (let [s (const ctx scale)] (ctx.graph:add_node procgen.OP_NOISE_PERLIN ctx.x ctx.y s 0 0))) @@ -68,6 +72,11 @@ p (const ctx persistence)] (ctx.graph:add_node procgen.OP_NOISE_FBM ctx.x ctx.y s p octaves))) +(fn noise-fbm-at [ctx x y octaves scale persistence] + (let [s (const ctx scale) + p (const ctx persistence)] + (ctx.graph:add_node procgen.OP_NOISE_FBM x y s p octaves))) + (fn noise-ridged [ctx octaves scale persistence] (let [s (const ctx scale) p (const ctx persistence)] @@ -165,4 +174,157 @@ (g:destroy) tex-id))) +(fn textures.wood-planks [seed base-color] + (let [g (build-graph seed + (fn [ctx] + (let [tex-size 64 + plank-count 2 + pixel-x (floor ctx (mul ctx ctx.x (const ctx tex-size))) + plank-x (div ctx pixel-x (const ctx (/ tex-size plank-count))) + plank-fract (fract ctx plank-x) + edge-dist (min-op ctx plank-fract (sub ctx (const ctx 1.0) plank-fract)) + gap-threshold (const ctx 0.04) + is-gap (sub ctx gap-threshold edge-dist) + plank-tint (mul ctx (noise-value ctx 6) (const ctx 0.25)) + grain-x (mul ctx ctx.x (const ctx 12)) + grain-y (mul ctx ctx.y (const ctx 2)) + grain-base (noise-fbm-at ctx grain-x grain-y 3 4 0.6) + grain-fine-x (mul ctx ctx.x (const ctx 48)) + grain-fine-y (mul ctx ctx.y (const ctx 6)) + grain-fine (noise-value-at ctx grain-fine-x grain-fine-y 1) + grain (add ctx (mul ctx grain-base (const ctx 0.6)) + (mul ctx grain-fine (const ctx 0.4))) + wood-val (add ctx grain plank-tint) + wood-quant (quantize ctx wood-val base-color 6) + gap-shade (sub ctx wood-val (const ctx 0.15)) + gap-quant (quantize ctx gap-shade base-color 6)] + (select ctx is-gap gap-quant wood-quant))))] + (let [tex-id (g:eval_texture 64 64)] + (g:destroy) + tex-id))) + +(fn textures.diagonal-planks [seed base-color] + (let [g (build-graph seed + (fn [ctx] + (let [tex-size 64 + plank-count 4 + diag (add ctx ctx.x ctx.y) + pixel-diag (floor ctx (mul ctx diag (const ctx tex-size))) + plank-diag (div ctx pixel-diag (const ctx (/ tex-size plank-count))) + plank-fract (fract ctx plank-diag) + edge-dist (min-op ctx plank-fract (sub ctx (const ctx 1.0) plank-fract)) + gap-threshold (const ctx 0.06) + is-gap (sub ctx gap-threshold edge-dist) + plank-id (floor ctx plank-diag) + plank-tint (mul ctx (noise-value-at ctx plank-id (const ctx 0) 8) (const ctx 0.2)) + grain-diag (mul ctx diag (const ctx 8)) + grain-perp (sub ctx ctx.x ctx.y) + grain-base (noise-fbm-at ctx grain-diag grain-perp 3 6 0.5) + grain-fine (noise-value-at ctx (mul ctx grain-diag (const ctx 4)) grain-perp 1) + grain (add ctx (mul ctx grain-base (const ctx 0.5)) + (mul ctx grain-fine (const ctx 0.3))) + wood-val (add ctx grain plank-tint) + wood-quant (quantize ctx wood-val base-color 5) + gap-shade (sub ctx wood-val (const ctx 0.2)) + gap-quant (quantize ctx gap-shade base-color 5)] + (select ctx is-gap gap-quant wood-quant))))] + (let [tex-id (g:eval_texture 64 64)] + (g:destroy) + tex-id))) + +(fn textures.cobble-timber [seed stone-color moss-color wood-color] + (let [g (build-graph seed + (fn [ctx] + (let [warp-x (noise-fbm ctx 2 3 0.5) + warp-y (noise-value ctx 4) + warped-x (add ctx ctx.x (mul ctx warp-x (const ctx 0.15))) + warped-y (add ctx ctx.y (mul ctx warp-y (const ctx 0.15))) + cell (ctx.graph:add_node procgen.OP_VORONOI_CELL warped-x warped-y (const ctx 5) 0 0) + edge (ctx.graph:add_node procgen.OP_VORONOI_EDGE warped-x warped-y (const ctx 5) 0 0) + mortar-threshold (const ctx 0.08) + is-mortar (sub ctx mortar-threshold edge) + mortar-color (const ctx 79) + stone-detail (noise-value ctx 48) + stone-base (mul ctx cell (const ctx 0.6)) + stone-combined (add ctx stone-base (mul ctx stone-detail (const ctx 0.4))) + stone-quant (quantize ctx stone-combined stone-color 8) + moss-pattern (noise-fbm ctx 4 10 0.5) + moss-detail (noise-value ctx 64) + moss-var (add ctx (mul ctx moss-pattern (const ctx 0.7)) + (mul ctx moss-detail (const ctx 0.3))) + moss-threshold (const ctx 0.55) + has-moss (sub ctx moss-pattern moss-threshold) + moss-quant (quantize ctx moss-var moss-color 6) + stone-or-moss (select ctx has-moss moss-quant stone-quant)] + (select ctx is-mortar mortar-color stone-or-moss))))] + (let [tex-id (g:eval_texture 64 64)] + (g:destroy) + tex-id))) + +(fn textures.plaster-wall [seed base-color] + (let [g (build-graph seed + (fn [ctx] + (let [plaster-base (noise-fbm ctx 3 24 0.4) + plaster-detail (noise-value ctx 64) + plaster-rough (noise-turbulence ctx 2 32 0.5) + combined (add ctx (mul ctx plaster-base (const ctx 0.5)) + (add ctx (mul ctx plaster-detail (const ctx 0.3)) + (mul ctx plaster-rough (const ctx 0.2)))) + crack-noise (noise-ridged ctx 2 8 0.6) + crack-threshold (const ctx 0.75) + has-crack (sub ctx crack-noise crack-threshold) + crack-color (const ctx (- base-color 2)) + plaster-quant (quantize ctx combined base-color 4)] + (select ctx has-crack crack-color plaster-quant))))] + (let [tex-id (g:eval_texture 64 64)] + (g:destroy) + tex-id))) + +(fn textures.timber-frame [seed wood-color plaster-color] + (let [g (build-graph seed + (fn [ctx] + (let [h-beam-count 2 + v-beam-count 1.5 + beam-half-width 0.08 + scaled-x (mul ctx ctx.x (const ctx h-beam-count)) + scaled-y (mul ctx ctx.y (const ctx v-beam-count)) + dist-to-h-beam (abs ctx (sub ctx (fract ctx scaled-y) (const ctx 0.5))) + dist-to-v-beam (abs ctx (sub ctx (fract ctx scaled-x) (const ctx 0.5))) + is-h-timber (sub ctx beam-half-width dist-to-h-beam) + is-v-timber (sub ctx beam-half-width dist-to-v-beam) + wood-grain (noise-fbm ctx 2 48 0.5) + wood-quant (quantize ctx wood-grain wood-color 4) + plaster-noise (noise-fbm ctx 3 16 0.4) + plaster-quant (quantize ctx plaster-noise plaster-color 3) + timber-or-plaster (select ctx is-h-timber wood-quant + (select ctx is-v-timber wood-quant plaster-quant))] + timber-or-plaster)))] + (let [tex-id (g:eval_texture 64 64)] + (g:destroy) + tex-id))) + +(fn textures.door [] + (let [(tex err) (pxl8.load_sprite "res/textures/door.ase")] + (if tex + tex + (do (pxl8.error (.. "Failed to load res/textures/door.ase, error: " (tostring err))) nil)))) + +(fn textures.wood-trim [seed base-color] + (let [g (build-graph seed + (fn [ctx] + (let [plank-tint (mul ctx (noise-value ctx 6) (const ctx 0.25)) + grain-x (mul ctx ctx.x (const ctx 12)) + grain-y (mul ctx ctx.y (const ctx 2)) + grain-base (noise-fbm-at ctx grain-x grain-y 3 4 0.6) + grain-fine-x (mul ctx ctx.x (const ctx 48)) + grain-fine-y (mul ctx ctx.y (const ctx 6)) + grain-fine (noise-value-at ctx grain-fine-x grain-fine-y 1) + grain (add ctx (mul ctx grain-base (const ctx 0.6)) + (mul ctx grain-fine (const ctx 0.4))) + wood-val (add ctx grain plank-tint)] + (quantize ctx wood-val base-color 6))))] + (let [tex-id (g:eval_texture 64 16)] + (g:destroy) + tex-id))) + textures diff --git a/pxl8.sh b/pxl8.sh index bbe5e4f..ef62ee3 100755 --- a/pxl8.sh +++ b/pxl8.sh @@ -39,6 +39,7 @@ BOLD='\033[1m' GREEN='\033[38;2;184;187;38m' 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" @@ -126,13 +127,29 @@ build_sdl() { fi } +prefix_output() { + while IFS= read -r line; do + if [[ "$line" == *": warning:"* ]] || [[ "$line" == *": note:"* ]]; then + echo -e "${YELLOW}${BOLD}[$(timestamp) WARN]${NC} $line" >&2 + else + echo -e "${RED}${BOLD}[$(timestamp) ERROR]${NC} $line" >&2 + fi + done +} + compile_source_file() { local src_file="$1" local obj_file="$2" local compile_flags="$3" print_info "Compiling: $src_file" - if ! $CC -c $compile_flags "$src_file" -o "$obj_file"; then + local output + local exit_code + output=$($CC -c $compile_flags "$src_file" -o "$obj_file" 2>&1) || exit_code=$? + if [[ -n "$output" ]]; then + echo "$output" | prefix_output + fi + if [[ -n "$exit_code" ]]; then print_error "Compilation failed for $src_file" exit 1 fi @@ -382,7 +399,7 @@ case "$COMMAND" in print_info "Compiler cache: ccache enabled" fi - INCLUDES="-Isrc/asset -Isrc/core -Isrc/gfx -Isrc/gui -Isrc/hal -Isrc/math -Isrc/net -Isrc/procgen -Isrc/script -Isrc/sfx -Isrc/world -Ilib/linenoise -Ilib/luajit/src -Ilib/miniz" + INCLUDES="-Isrc/asset -Isrc/bsp -Isrc/core -Isrc/gfx -Isrc/gui -Isrc/hal -Isrc/math -Isrc/net -Isrc/procgen -Isrc/script -Isrc/sfx -Isrc/sim -Isrc/vxl -Isrc/world -Ilib/linenoise -Ilib/luajit/src -Ilib/miniz" COMPILE_FLAGS="$CFLAGS $INCLUDES" DEP_COMPILE_FLAGS="$DEP_CFLAGS $INCLUDES" @@ -394,6 +411,8 @@ case "$COMMAND" in src/asset/pxl8_ase.c src/asset/pxl8_cart.c src/asset/pxl8_save.c + src/bsp/pxl8_bsp.c + src/bsp/pxl8_bsp_render.c src/core/pxl8.c src/core/pxl8_bytes.c src/core/pxl8_io.c @@ -421,20 +440,22 @@ case "$COMMAND" in src/gui/pxl8_gui.c src/hal/pxl8_hal_sdl3.c src/hal/pxl8_mem_sdl3.c + src/hal/pxl8_thread_sdl3.c src/math/pxl8_math.c + src/math/pxl8_noise.c src/net/pxl8_net.c src/net/pxl8_protocol.c src/procgen/pxl8_graph.c src/script/pxl8_repl.c src/script/pxl8_script.c src/sfx/pxl8_sfx.c - src/world/pxl8_bsp.c - src/world/pxl8_chunk.c - src/world/pxl8_chunk_cache.c + src/sim/pxl8_sim.c + src/vxl/pxl8_vxl.c + src/vxl/pxl8_vxl_render.c src/world/pxl8_entity.c - src/world/pxl8_gen.c - src/world/pxl8_voxel.c src/world/pxl8_world.c + src/world/pxl8_world_chunk.c + src/world/pxl8_world_chunk_cache.c " LUAJIT_LIB="lib/luajit/src/libluajit.a" diff --git a/pxl8d/build.rs b/pxl8d/build.rs index 4b2a940..b588811 100644 --- a/pxl8d/build.rs +++ b/pxl8d/build.rs @@ -5,21 +5,51 @@ fn main() { let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); let pxl8_src = PathBuf::from(&manifest_dir).join("../src"); + println!("cargo:rerun-if-changed=../src/bsp/pxl8_bsp.h"); println!("cargo:rerun-if-changed=../src/core/pxl8_log.c"); println!("cargo:rerun-if-changed=../src/core/pxl8_log.h"); println!("cargo:rerun-if-changed=../src/core/pxl8_types.h"); + println!("cargo:rerun-if-changed=../src/math/pxl8_math.c"); + println!("cargo:rerun-if-changed=../src/math/pxl8_math.h"); + println!("cargo:rerun-if-changed=../src/net/pxl8_protocol.c"); println!("cargo:rerun-if-changed=../src/net/pxl8_protocol.h"); + println!("cargo:rerun-if-changed=../src/sim/pxl8_sim.c"); + println!("cargo:rerun-if-changed=../src/sim/pxl8_sim.h"); + println!("cargo:rerun-if-changed=../src/vxl/pxl8_vxl.c"); + println!("cargo:rerun-if-changed=../src/vxl/pxl8_vxl.h"); cc::Build::new() .file(pxl8_src.join("core/pxl8_log.c")) + .file(pxl8_src.join("hal/pxl8_mem.c")) + .file(pxl8_src.join("math/pxl8_math.c")) + .file(pxl8_src.join("math/pxl8_noise.c")) + .file(pxl8_src.join("sim/pxl8_sim.c")) + .file(pxl8_src.join("vxl/pxl8_vxl.c")) + .include(pxl8_src.join("bsp")) .include(pxl8_src.join("core")) - .define("PXL8_SERVER", None) - .compile("pxl8_log"); + .include(pxl8_src.join("hal")) + .include(pxl8_src.join("math")) + .include(pxl8_src.join("net")) + .include(pxl8_src.join("sim")) + .include(pxl8_src.join("vxl")) + .compile("pxl8"); let bindings = bindgen::Builder::default() .header(pxl8_src.join("core/pxl8_log.h").to_str().unwrap()) - .header(pxl8_src.join("net/pxl8_protocol.h").to_str().unwrap()) + .header(pxl8_src.join("sim/pxl8_sim.h").to_str().unwrap()) + .header(pxl8_src.join("vxl/pxl8_vxl.h").to_str().unwrap()) + .header(pxl8_src.join("math/pxl8_noise.h").to_str().unwrap()) + .clang_arg(format!("-I{}", pxl8_src.join("bsp").display())) .clang_arg(format!("-I{}", pxl8_src.join("core").display())) + .clang_arg(format!("-I{}", pxl8_src.join("math").display())) + .clang_arg(format!("-I{}", pxl8_src.join("net").display())) + .clang_arg(format!("-I{}", pxl8_src.join("sim").display())) + .clang_arg(format!("-I{}", pxl8_src.join("vxl").display())) + .blocklist_item("FP_NAN") + .blocklist_item("FP_INFINITE") + .blocklist_item("FP_ZERO") + .blocklist_item("FP_SUBNORMAL") + .blocklist_item("FP_NORMAL") .use_core() .rustified_enum(".*") .generate() @@ -27,6 +57,6 @@ fn main() { let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); bindings - .write_to_file(out_path.join("protocol.rs")) + .write_to_file(out_path.join("pxl8.rs")) .expect("Couldn't write bindings"); } diff --git a/pxl8d/src/bsp.rs b/pxl8d/src/bsp.rs index 841d63f..0ef5fa0 100644 --- a/pxl8d/src/bsp.rs +++ b/pxl8d/src/bsp.rs @@ -1,173 +1,216 @@ extern crate alloc; +use alloc::boxed::Box; use alloc::vec::Vec; -use crate::math::Vec3; +use crate::math::{Vec3, VEC3_ZERO}; +use crate::pxl8::*; -#[derive(Clone, Copy, Default)] -pub struct BspVertex { - pub position: Vec3, +pub type Vertex = pxl8_bsp_vertex; +pub type Edge = pxl8_bsp_edge; +pub type Face = pxl8_bsp_face; +pub type Plane = pxl8_bsp_plane; +pub type Node = pxl8_bsp_node; +pub type Leaf = pxl8_bsp_leaf; +pub type Portal = pxl8_bsp_portal; +pub type CellPortals = pxl8_bsp_cell_portals; + +impl Default for Edge { + fn default() -> Self { + Self { vertex: [0, 0] } + } } -#[derive(Clone, Copy, Default)] -pub struct BspEdge { - pub vertex: [u16; 2], +impl Default for Face { + fn default() -> Self { + Self { + first_edge: 0, + lightmap_offset: 0, + num_edges: 0, + plane_id: 0, + side: 0, + styles: [0; 4], + material_id: 0, + aabb_min: VEC3_ZERO, + aabb_max: VEC3_ZERO, + } + } } -#[derive(Clone, Copy, Default)] -pub struct BspFace { - pub first_edge: u32, - pub lightmap_offset: u32, - pub num_edges: u16, - pub plane_id: u16, - pub side: u16, - pub styles: [u8; 4], - pub material_id: u16, - pub aabb_min: Vec3, - pub aabb_max: Vec3, +impl Default for Leaf { + fn default() -> Self { + Self { + ambient_level: [0; 4], + contents: 0, + first_marksurface: 0, + maxs: [0; 3], + mins: [0; 3], + num_marksurfaces: 0, + visofs: -1, + } + } } -#[derive(Clone, Copy, Default)] -pub struct BspPlane { - pub normal: Vec3, - pub dist: f32, - pub plane_type: i32, +impl Default for Node { + fn default() -> Self { + Self { + children: [0, 0], + first_face: 0, + maxs: [0; 3], + mins: [0; 3], + num_faces: 0, + plane_id: 0, + } + } } -#[derive(Clone, Copy, Default)] -pub struct BspNode { - pub children: [i32; 2], - pub first_face: u16, - pub maxs: [i16; 3], - pub mins: [i16; 3], - pub num_faces: u16, - pub plane_id: u32, +impl Default for Plane { + fn default() -> Self { + Self { + dist: 0.0, + normal: VEC3_ZERO, + type_: 0, + } + } } -#[derive(Clone, Copy, Default)] -pub struct BspLeaf { - pub ambient_level: [u8; 4], - pub contents: i32, - pub first_marksurface: u16, - pub maxs: [i16; 3], - pub mins: [i16; 3], - pub num_marksurfaces: u16, - pub visofs: i32, +impl Default for Vertex { + fn default() -> Self { + Self { position: VEC3_ZERO } + } } -#[derive(Clone, Copy, Default)] -pub struct BspPortal { - pub x0: f32, - pub z0: f32, - pub x1: f32, - pub z1: f32, - pub target_leaf: u32, +impl Default for Portal { + fn default() -> Self { + Self { + x0: 0.0, + z0: 0.0, + x1: 0.0, + z1: 0.0, + target_leaf: 0, + } + } } -#[derive(Clone, Default)] -pub struct BspCellPortals { - pub portals: [BspPortal; 4], - pub num_portals: u8, +impl Default for CellPortals { + fn default() -> Self { + Self { + portals: [Portal::default(); 4], + num_portals: 0, + } + } } pub struct Bsp { - pub vertices: Vec, - pub edges: Vec, - pub surfedges: Vec, - pub planes: Vec, - pub faces: Vec, - pub nodes: Vec, - pub leafs: Vec, + inner: pxl8_bsp, + pub cell_portals: Box<[CellPortals]>, + pub edges: Box<[Edge]>, + pub faces: Box<[Face]>, + pub leafs: Box<[Leaf]>, + pub marksurfaces: Box<[u16]>, + pub nodes: Box<[Node]>, + pub planes: Box<[Plane]>, + pub surfedges: Box<[i32]>, + pub vertex_lights: Box<[u32]>, + pub vertices: Box<[Vertex]>, + pub visdata: Box<[u8]>, +} + +#[derive(Default)] +pub struct BspBuilder { + pub cell_portals: Vec, + pub edges: Vec, + pub faces: Vec, + pub leafs: Vec, pub marksurfaces: Vec, - pub cell_portals: Vec, - pub visdata: Vec, + pub nodes: Vec, + pub planes: Vec, + pub surfedges: Vec, pub vertex_lights: Vec, + pub vertices: Vec, + pub visdata: Vec, +} + +impl BspBuilder { + pub fn new() -> Self { + Self::default() + } +} + +impl From for Bsp { + fn from(b: BspBuilder) -> Self { + let cell_portals = b.cell_portals.into_boxed_slice(); + let edges = b.edges.into_boxed_slice(); + let faces = b.faces.into_boxed_slice(); + let leafs = b.leafs.into_boxed_slice(); + let marksurfaces = b.marksurfaces.into_boxed_slice(); + let nodes = b.nodes.into_boxed_slice(); + let planes = b.planes.into_boxed_slice(); + let surfedges = b.surfedges.into_boxed_slice(); + let vertex_lights = b.vertex_lights.into_boxed_slice(); + let vertices = b.vertices.into_boxed_slice(); + let visdata = b.visdata.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 _ }, + edges: if edges.is_empty() { core::ptr::null_mut() } else { edges.as_ptr() as *mut _ }, + faces: if faces.is_empty() { core::ptr::null_mut() } else { faces.as_ptr() as *mut _ }, + leafs: if leafs.is_empty() { core::ptr::null_mut() } else { leafs.as_ptr() as *mut _ }, + lightdata: core::ptr::null_mut(), + lightmaps: core::ptr::null_mut(), + marksurfaces: if marksurfaces.is_empty() { core::ptr::null_mut() } else { marksurfaces.as_ptr() as *mut _ }, + models: core::ptr::null_mut(), + nodes: if nodes.is_empty() { core::ptr::null_mut() } else { nodes.as_ptr() as *mut _ }, + planes: if planes.is_empty() { core::ptr::null_mut() } else { planes.as_ptr() as *mut _ }, + surfedges: if surfedges.is_empty() { core::ptr::null_mut() } else { surfedges.as_ptr() as *mut _ }, + 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 _ }, + lightdata_size: 0, + num_cell_portals: cell_portals.len() as u32, + num_edges: edges.len() as u32, + num_faces: faces.len() as u32, + num_leafs: leafs.len() as u32, + num_lightmaps: 0, + num_marksurfaces: marksurfaces.len() as u32, + num_models: 0, + num_nodes: nodes.len() as u32, + num_planes: planes.len() as u32, + num_surfedges: surfedges.len() as u32, + num_vertex_lights: vertex_lights.len() as u32, + num_vertices: vertices.len() as u32, + visdata_size: visdata.len() as u32, + }; + + Self { + inner, + cell_portals, + edges, + faces, + leafs, + marksurfaces, + nodes, + planes, + surfedges, + vertex_lights, + vertices, + visdata, + } + } } impl Bsp { - pub fn new() -> Self { - Self { - vertices: Vec::new(), - edges: Vec::new(), - surfedges: Vec::new(), - planes: Vec::new(), - faces: Vec::new(), - nodes: Vec::new(), - leafs: Vec::new(), - marksurfaces: Vec::new(), - cell_portals: Vec::new(), - visdata: Vec::new(), - vertex_lights: Vec::new(), - } - } - - pub fn find_leaf(&self, pos: Vec3) -> i32 { - if self.nodes.is_empty() { - return -1; - } - - let mut node_id: i32 = 0; - - while node_id >= 0 { - let node = &self.nodes[node_id as usize]; - let plane = &self.planes[node.plane_id as usize]; - - let dist = pos.dot(plane.normal) - plane.dist; - node_id = node.children[if dist < 0.0 { 1 } else { 0 }]; - } - - -(node_id + 1) - } - - pub fn point_solid(&self, pos: Vec3) -> bool { - let leaf = self.find_leaf(pos); - if leaf < 0 || leaf as usize >= self.leafs.len() { - return true; - } - self.leafs[leaf as usize].contents == -1 - } - - fn point_clear(&self, x: f32, y: f32, z: f32, radius: f32) -> bool { - if self.point_solid(Vec3::new(x, y, z)) { return false; } - if self.point_solid(Vec3::new(x + radius, y, z)) { return false; } - if self.point_solid(Vec3::new(x - radius, y, z)) { return false; } - if self.point_solid(Vec3::new(x, y, z + radius)) { return false; } - if self.point_solid(Vec3::new(x, y, z - radius)) { return false; } - true + pub fn as_c_bsp(&self) -> &pxl8_bsp { + &self.inner } pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 { - if self.nodes.is_empty() { - return to; - } - - if self.point_clear(to.x, to.y, to.z, radius) { - return to; - } - - let x_ok = self.point_clear(to.x, from.y, from.z, radius); - let z_ok = self.point_clear(from.x, from.y, to.z, radius); - - if x_ok && z_ok { - let dx = to.x - from.x; - let dz = to.z - from.z; - if dx * dx > dz * dz { - Vec3::new(to.x, from.y, from.z) - } else { - Vec3::new(from.x, from.y, to.z) - } - } else if x_ok { - Vec3::new(to.x, from.y, from.z) - } else if z_ok { - Vec3::new(from.x, from.y, to.z) - } else { - from - } + unsafe { pxl8_bsp_trace(&self.inner, from, to, radius) } } } impl Default for Bsp { fn default() -> Self { - Self::new() + BspBuilder::new().into() } } diff --git a/pxl8d/src/chunk.rs b/pxl8d/src/chunk.rs index c58e48c..ec1c5f3 100644 --- a/pxl8d/src/chunk.rs +++ b/pxl8d/src/chunk.rs @@ -4,6 +4,7 @@ pub mod stream; use crate::bsp::Bsp; use crate::math::Vec3; +use crate::pxl8::pxl8_vxl_trace; use crate::voxel::VoxelChunk; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] @@ -35,7 +36,9 @@ impl Chunk { pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 { match self { Chunk::Bsp { bsp, .. } => bsp.trace(from, to, radius), - Chunk::Vxl { data, .. } => data.trace(from, to, radius), + Chunk::Vxl { cx, cy, cz, data, .. } => unsafe { + pxl8_vxl_trace(data.chunk, *cx, *cy, *cz, from, to, radius) + }, } } diff --git a/pxl8d/src/chunk/stream.rs b/pxl8d/src/chunk/stream.rs index 0afe9cf..9ca9744 100644 --- a/pxl8d/src/chunk/stream.rs +++ b/pxl8d/src/chunk/stream.rs @@ -77,6 +77,18 @@ impl ClientChunkState { self.pending.clear(); self.pending_messages.clear(); } + + pub fn clear_known_vxl(&mut self) { + self.known.retain(|id, _| !matches!(id, ChunkId::Vxl(_, _, _))); + } + + 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 18d106f..4834756 100644 --- a/pxl8d/src/lib.rs +++ b/pxl8d/src/lib.rs @@ -24,8 +24,8 @@ fn panic(_info: &PanicInfo) -> ! { } #[allow(dead_code, non_camel_case_types, non_snake_case, non_upper_case_globals)] -pub mod protocol { - include!(concat!(env!("OUT_DIR"), "/protocol.rs")); +pub mod pxl8 { + include!(concat!(env!("OUT_DIR"), "/pxl8.rs")); } pub use bsp::*; @@ -33,7 +33,7 @@ pub use chunk::*; pub use chunk::stream::*; pub use math::*; pub use procgen::{ProcgenParams, generate, generate_rooms}; -pub use protocol::*; +pub use pxl8::*; pub use sim::*; pub use transport::*; pub use voxel::*; diff --git a/pxl8d/src/log.rs b/pxl8d/src/log.rs index e8390a8..f77ed0c 100644 --- a/pxl8d/src/log.rs +++ b/pxl8d/src/log.rs @@ -1,9 +1,9 @@ -use crate::protocol::{pxl8_log, pxl8_log_init}; +use crate::pxl8::{pxl8_log, pxl8_log_init}; use core::ffi::c_char; static mut G_LOG: pxl8_log = pxl8_log { handler: None, - level: crate::protocol::pxl8_log_level::PXL8_LOG_LEVEL_DEBUG, + level: crate::pxl8::pxl8_log_level::PXL8_LOG_LEVEL_DEBUG, }; pub fn init() { @@ -20,7 +20,7 @@ macro_rules! pxl8_debug { let _ = ::core::write!(&mut buf, $($arg)*); buf.push(0); unsafe { - $crate::protocol::pxl8_log_write_debug( + $crate::pxl8::pxl8_log_write_debug( concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char, line!() as ::core::ffi::c_int, c"%s".as_ptr() as *const ::core::ffi::c_char, @@ -38,7 +38,7 @@ macro_rules! pxl8_error { let _ = ::core::write!(&mut buf, $($arg)*); buf.push(0); unsafe { - $crate::protocol::pxl8_log_write_error( + $crate::pxl8::pxl8_log_write_error( concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char, line!() as ::core::ffi::c_int, c"%s".as_ptr() as *const ::core::ffi::c_char, @@ -56,7 +56,7 @@ macro_rules! pxl8_info { let _ = ::core::write!(&mut buf, $($arg)*); buf.push(0); unsafe { - $crate::protocol::pxl8_log_write_info( + $crate::pxl8::pxl8_log_write_info( c"%s".as_ptr() as *const ::core::ffi::c_char, buf.as_ptr(), ); @@ -72,7 +72,7 @@ macro_rules! pxl8_trace { let _ = ::core::write!(&mut buf, $($arg)*); buf.push(0); unsafe { - $crate::protocol::pxl8_log_write_trace( + $crate::pxl8::pxl8_log_write_trace( concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char, line!() as ::core::ffi::c_int, c"%s".as_ptr() as *const ::core::ffi::c_char, @@ -90,7 +90,7 @@ macro_rules! pxl8_warn { let _ = ::core::write!(&mut buf, $($arg)*); buf.push(0); unsafe { - $crate::protocol::pxl8_log_write_warn( + $crate::pxl8::pxl8_log_write_warn( concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char, line!() as ::core::ffi::c_int, c"%s".as_ptr() as *const ::core::ffi::c_char, diff --git a/pxl8d/src/main.rs b/pxl8d/src/main.rs index e51e269..760b91e 100644 --- a/pxl8d/src/main.rs +++ b/pxl8d/src/main.rs @@ -6,6 +6,7 @@ extern crate alloc; use pxl8d::*; use pxl8d::chunk::ChunkId; use pxl8d::chunk::stream::ClientChunkState; +use pxl8d::math::Vec3; const TICK_RATE: u64 = 30; const TICK_NS: u64 = 1_000_000_000 / TICK_RATE; @@ -73,14 +74,15 @@ 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 client_stream_radius: i32 = 3; let mut sequence: u32 = 0; let mut last_tick = get_time_ns(); - let mut entities_buf = [protocol::pxl8_entity_state { + let mut entities_buf = [pxl8d::pxl8_entity_state { entity_id: 0, userdata: [0u8; 56], }; 64]; - let mut inputs_buf: [protocol::pxl8_input_msg; 16] = unsafe { core::mem::zeroed() }; + let mut inputs_buf: [pxl8d::pxl8_input_msg; 16] = unsafe { core::mem::zeroed() }; pxl8_debug!("[SERVER] Entering main loop"); loop { @@ -91,15 +93,15 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { last_tick = now; let dt = (elapsed as f32) / 1_000_000_000.0; - let mut latest_input: Option = None; + let mut latest_input: Option = None; while let Some(msg_type) = transport.recv() { match msg_type { - x if x == protocol::pxl8_msg_type::PXL8_MSG_INPUT as u8 => { + x if x == pxl8d::pxl8_msg_type::PXL8_MSG_INPUT as u8 => { latest_input = Some(transport.get_input()); } - x if x == protocol::pxl8_msg_type::PXL8_MSG_COMMAND as u8 => { + x if x == pxl8d::pxl8_msg_type::PXL8_MSG_COMMAND as u8 => { let cmd = transport.get_command(); - if cmd.cmd_type == protocol::pxl8_cmd_type::PXL8_CMD_SPAWN_ENTITY as u16 { + 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); @@ -110,6 +112,46 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { pxl8_debug!("[SERVER] Sending CHUNK_ENTER for BSP id=1"); transport.send_chunk_enter(1, transport::CHUNK_TYPE_BSP, sequence); sequence = sequence.wrapping_add(1); + + pxl8_debug!("[SERVER] Preloading voxel chunks around spawn and door"); + let spawn_pos = Vec3 { x, y, z }; + let door_y = sim.voxels.find_surface_y(942.0, 416.0); + let door_pos = Vec3 { x: 942.0, y: door_y, z: 416.0 }; + pxl8_debug!("[SERVER] Door placed at surface y={}", door_y); + sim.voxels.load_chunks_around(spawn_pos, client_stream_radius); + sim.voxels.load_chunks_around(door_pos, client_stream_radius); + client_chunks.request_vxl_radius(spawn_pos, client_stream_radius, &sim.voxels); + client_chunks.request_vxl_radius(door_pos, client_stream_radius, &sim.voxels); + } else if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_EXIT_CHUNK as u16 { + sim.world.clear_active(); + client_chunks.clear_pending(); + client_chunks.clear_known_vxl(); + client_chunks.clear_known_bsp(); + transport.send_chunk_exit(sequence); + sequence = sequence.wrapping_add(1); + let exit_x = f32::from_be_bytes([cmd.payload[0], cmd.payload[1], cmd.payload[2], cmd.payload[3]]); + let exit_y = f32::from_be_bytes([cmd.payload[4], cmd.payload[5], cmd.payload[6], cmd.payload[7]]); + let exit_z = f32::from_be_bytes([cmd.payload[8], cmd.payload[9], cmd.payload[10], cmd.payload[11]]); + let exit_pos = Vec3 { x: exit_x, y: exit_y, z: exit_z }; + sim.teleport_player(exit_x, exit_y, exit_z); + sim.voxels.load_chunks_around(exit_pos, client_stream_radius); + client_chunks.request_vxl_radius(exit_pos, client_stream_radius, &sim.voxels); + } else if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_ENTER_CHUNK as u16 { + let chunk_id = u32::from_be_bytes([ + cmd.payload[0], cmd.payload[1], cmd.payload[2], cmd.payload[3] + ]); + pxl8_debug!("[SERVER] Enter chunk command - entering BSP {}", chunk_id); + if sim.world.contains(&ChunkId::Bsp(chunk_id)) { + 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); + } + } else if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_SET_CHUNK_SETTINGS as u16 { + let render_dist = i32::from_be_bytes([ + cmd.payload[0], cmd.payload[1], cmd.payload[2], cmd.payload[3] + ]); + client_stream_radius = render_dist.clamp(1, 8); } } _ => {} @@ -132,7 +174,7 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { } }); - let header = protocol::pxl8_snapshot_header { + let header = pxl8d::pxl8_snapshot_header { entity_count: count as u16, event_count: 0, player_id: player_id.unwrap_or(0), @@ -147,54 +189,36 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { if let Some(player) = sim.get_player_position(pid) { let pos = Vec3 { x: player.0, y: player.1, z: player.2 }; - if sim.world.active().is_some() { - while let Some(chunk_id) = client_chunks.next_pending() { - match chunk_id { - ChunkId::Bsp(id) => { - pxl8_debug!("[SERVER] Processing pending BSP chunk"); - if let Some(chunk) = sim.world.get(&chunk_id) { - if let Some(bsp) = chunk.as_bsp() { - let msgs = bsp_to_messages(bsp, id, chunk.version()); - pxl8_debug!("[SERVER] BSP serialized, queueing messages"); - client_chunks.queue_messages(msgs); - client_chunks.mark_sent(chunk_id, chunk.version()); - } - } - } - ChunkId::Vxl(cx, cy, cz) => { - if let Some(chunk) = sim.voxels.get_chunk(cx, cy, cz) { - let msgs = transport::ChunkMessage::from_voxel(chunk, 1); + sim.voxels.load_chunks_around(pos, client_stream_radius); + client_chunks.request_vxl_radius(pos, client_stream_radius, &sim.voxels); + + while let Some(chunk_id) = client_chunks.next_pending() { + match chunk_id { + 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, id, chunk.version()); client_chunks.queue_messages(msgs); - client_chunks.mark_sent(chunk_id, 1); + client_chunks.mark_sent(chunk_id, chunk.version()); } } } - } - - for _ in 0..4 { - if let Some(msg) = client_chunks.next_message() { - transport.send_chunk(&msg, sequence); - sequence = sequence.wrapping_add(1); - } - } - } else { - client_chunks.request_vxl_radius(pos, 2, &sim.voxels); - - for _ in 0..2 { - if let Some(chunk_id) = client_chunks.next_pending() { - if let ChunkId::Vxl(cx, cy, cz) = chunk_id { - if let Some(chunk) = sim.voxels.get_chunk(cx, cy, cz) { - let msgs = transport::ChunkMessage::from_voxel(chunk, 1); - for msg in &msgs { - transport.send_chunk(msg, sequence); - sequence = sequence.wrapping_add(1); - } - client_chunks.mark_sent(chunk_id, 1); - } + ChunkId::Vxl(cx, cy, cz) => { + if let Some(chunk) = sim.voxels.get_chunk(cx, cy, cz) { + let msgs = transport::ChunkMessage::from_voxel(chunk, 1); + client_chunks.queue_messages(msgs); + client_chunks.mark_sent(chunk_id, 1); } } } } + + for _ in 0..8 { + if let Some(msg) = client_chunks.next_message() { + transport.send_chunk(&msg, sequence); + sequence = sequence.wrapping_add(1); + } + } } } } @@ -250,7 +274,7 @@ fn bsp_to_messages(bsp: &bsp::Bsp, id: u32, version: u32) -> alloc::vec::Vec Self; + fn dot(self, rhs: Self) -> f32; } -impl Vec3 { - pub const ZERO: Self = Self { x: 0.0, y: 0.0, z: 0.0 }; - pub const Y: Self = Self { x: 0.0, y: 1.0, z: 0.0 }; - - pub const fn new(x: f32, y: f32, z: f32) -> Self { +impl Vec3Ext for pxl8_vec3 { + fn new(x: f32, y: f32, z: f32) -> Self { Self { x, y, z } } - pub fn dot(self, rhs: Self) -> f32 { + fn dot(self, rhs: Self) -> f32 { self.x * rhs.x + self.y * rhs.y + self.z * rhs.z } } -impl Add for Vec3 { +impl Default for pxl8_vec3 { + fn default() -> Self { + VEC3_ZERO + } +} + +impl Add for pxl8_vec3 { type Output = Self; fn add(self, rhs: Self) -> Self { - Self::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z) + Self { + x: self.x + rhs.x, + y: self.y + rhs.y, + z: self.z + rhs.z, + } } } -impl Sub for Vec3 { +impl Sub for pxl8_vec3 { type Output = Self; fn sub(self, rhs: Self) -> Self { - Self::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) + Self { + x: self.x - rhs.x, + y: self.y - rhs.y, + z: self.z - rhs.z, + } } } -impl Mul for Vec3 { +impl Mul for pxl8_vec3 { type Output = Self; fn mul(self, rhs: f32) -> Self { - Self::new(self.x * rhs, self.y * rhs, self.z * rhs) + Self { + x: self.x * rhs, + y: self.y * rhs, + z: self.z * rhs, + } } } diff --git a/pxl8d/src/procgen.rs b/pxl8d/src/procgen.rs index 99b3c3a..0da79f0 100644 --- a/pxl8d/src/procgen.rs +++ b/pxl8d/src/procgen.rs @@ -4,11 +4,12 @@ use alloc::vec; use alloc::vec::Vec; use libm::sqrtf; -use crate::bsp::{Bsp, BspVertex, BspEdge, BspFace, BspPlane, BspNode, BspLeaf, BspPortal, BspCellPortals}; -use crate::math::Vec3; +use crate::bsp::{Bsp, BspBuilder, CellPortals, Edge, Face, Leaf, Node, Plane, Portal, Vertex}; +use crate::math::{Vec3, Vec3Ext}; pub const CELL_SIZE: f32 = 64.0; pub const WALL_HEIGHT: f32 = 128.0; +pub const TRIM_HEIGHT: f32 = 12.0; pub const PVS_MAX_DEPTH: u32 = 64; #[derive(Clone, Copy)] @@ -107,7 +108,7 @@ impl Bounds { } struct BspBuildContext<'a> { - bsp: &'a mut Bsp, + bsp: &'a mut BspBuilder, grid: &'a RoomGrid, node_count: u32, plane_offset: u32, @@ -133,7 +134,7 @@ fn carve_corridor_v(grid: &mut RoomGrid, y1: i32, y2: i32, x: i32) { } } -fn compute_face_aabb(face: &mut BspFace, verts: &[BspVertex], vert_idx: usize) { +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); @@ -148,7 +149,7 @@ fn compute_face_aabb(face: &mut BspFace, verts: &[BspVertex], vert_idx: usize) { } } -fn build_bsp_node(ctx: &mut BspBuildContext, x0: i32, y0: i32, x1: i32, y1: i32, depth: i32) -> i32 { +fn build_bsp_node_grid(ctx: &mut BspBuildContext, x0: i32, y0: i32, x1: i32, y1: i32, depth: i32) -> i32 { if x1 - x0 == 1 && y1 - y0 == 1 { let leaf_idx = y0 * ctx.grid.width + x0; return -(leaf_idx + 1); @@ -164,16 +165,16 @@ fn build_bsp_node(ctx: &mut BspBuildContext, x0: i32, y0: i32, x1: i32, y1: i32, let mid_x = (x0 + x1) / 2; let split_pos = mid_x as f32 * CELL_SIZE; - ctx.bsp.planes[plane_idx as usize] = BspPlane { + ctx.bsp.planes[plane_idx as usize] = Plane { normal: Vec3::new(1.0, 0.0, 0.0), dist: split_pos, - plane_type: 0, + type_: 0, }; - let child0 = build_bsp_node(ctx, mid_x, y0, x1, y1, depth + 1); - let child1 = build_bsp_node(ctx, x0, y0, mid_x, y1, depth + 1); + let child0 = build_bsp_node_grid(ctx, mid_x, y0, x1, y1, depth + 1); + let child1 = build_bsp_node_grid(ctx, x0, y0, mid_x, y1, depth + 1); - ctx.bsp.nodes[node_idx as usize] = BspNode { + ctx.bsp.nodes[node_idx as usize] = Node { plane_id: plane_idx, children: [child0, child1], ..Default::default() @@ -182,16 +183,16 @@ fn build_bsp_node(ctx: &mut BspBuildContext, x0: i32, y0: i32, x1: i32, y1: i32, let mid_y = (y0 + y1) / 2; let split_pos = mid_y as f32 * CELL_SIZE; - ctx.bsp.planes[plane_idx as usize] = BspPlane { + ctx.bsp.planes[plane_idx as usize] = Plane { normal: Vec3::new(0.0, 0.0, 1.0), dist: split_pos, - plane_type: 0, + type_: 0, }; - let child0 = build_bsp_node(ctx, x0, mid_y, x1, y1, depth + 1); - let child1 = build_bsp_node(ctx, x0, y0, x1, mid_y, depth + 1); + let child0 = build_bsp_node_grid(ctx, x0, mid_y, x1, y1, depth + 1); + let child1 = build_bsp_node_grid(ctx, x0, y0, x1, mid_y, depth + 1); - ctx.bsp.nodes[node_idx as usize] = BspNode { + ctx.bsp.nodes[node_idx as usize] = Node { plane_id: plane_idx, children: [child0, child1], ..Default::default() @@ -201,9 +202,33 @@ fn build_bsp_node(ctx: &mut BspBuildContext, x0: i32, y0: i32, x1: i32, y1: i32, node_idx as i32 } -fn build_cell_portals(grid: &RoomGrid) -> Vec { +fn build_bsp_node(ctx: &mut BspBuildContext, x0: i32, y0: i32, x1: i32, y1: i32, depth: i32, floor_leaf_idx: i32) -> i32 { + let node_idx = ctx.node_count; + ctx.node_count += 1; + + let plane_idx = ctx.plane_offset; + ctx.plane_offset += 1; + + ctx.bsp.planes[plane_idx as usize] = Plane { + normal: Vec3::new(0.0, 1.0, 0.0), + dist: 0.0, + type_: 1, + }; + + let above_floor = build_bsp_node_grid(ctx, x0, y0, x1, y1, depth); + + ctx.bsp.nodes[node_idx as usize] = Node { + plane_id: plane_idx, + children: [above_floor, -(floor_leaf_idx + 1)], + ..Default::default() + }; + + node_idx as i32 +} + +fn build_cell_portals(grid: &RoomGrid) -> Vec { let total_cells = (grid.width * grid.height) as usize; - let mut portals = vec![BspCellPortals::default(); total_cells]; + let mut portals = vec![CellPortals::default(); total_cells]; for y in 0..grid.height { for x in 0..grid.width { @@ -218,7 +243,7 @@ fn build_cell_portals(grid: &RoomGrid) -> Vec { if x > 0 && grid.get(x - 1, y) == 0 { let p = &mut portals[c]; let idx = p.num_portals as usize; - p.portals[idx] = BspPortal { + p.portals[idx] = Portal { x0: cx, z0: cz, x1: cx, @@ -230,7 +255,7 @@ fn build_cell_portals(grid: &RoomGrid) -> Vec { if x < grid.width - 1 && grid.get(x + 1, y) == 0 { let p = &mut portals[c]; let idx = p.num_portals as usize; - p.portals[idx] = BspPortal { + p.portals[idx] = Portal { x0: cx + CELL_SIZE, z0: cz + CELL_SIZE, x1: cx + CELL_SIZE, @@ -242,7 +267,7 @@ fn build_cell_portals(grid: &RoomGrid) -> Vec { if y > 0 && grid.get(x, y - 1) == 0 { let p = &mut portals[c]; let idx = p.num_portals as usize; - p.portals[idx] = BspPortal { + p.portals[idx] = Portal { x0: cx + CELL_SIZE, z0: cz, x1: cx, @@ -254,7 +279,7 @@ fn build_cell_portals(grid: &RoomGrid) -> Vec { if y < grid.height - 1 && grid.get(x, y + 1) == 0 { let p = &mut portals[c]; let idx = p.num_portals as usize; - p.portals[idx] = BspPortal { + p.portals[idx] = Portal { x0: cx, z0: cz + CELL_SIZE, x1: cx + CELL_SIZE, @@ -277,8 +302,8 @@ struct FloodEntry { fn portal_flood_bfs( start_leaf: u32, - portals: &[BspCellPortals], - leafs: &[BspLeaf], + portals: &[CellPortals], + leafs: &[Leaf], pvs: &mut [u8], num_leafs: u32, ) { @@ -325,8 +350,8 @@ fn portal_flood_bfs( fn compute_leaf_pvs( start_leaf: u32, - portals: &[BspCellPortals], - leafs: &[BspLeaf], + portals: &[CellPortals], + leafs: &[Leaf], num_leafs: u32, ) -> Vec { let pvs_bytes = ((num_leafs + 7) / 8) as usize; @@ -357,7 +382,7 @@ fn rle_compress_pvs(pvs: &[u8]) -> Vec { out } -fn build_pvs_data(bsp: &mut Bsp, portals: &[BspCellPortals]) { +fn build_pvs_data(bsp: &mut BspBuilder, portals: &[CellPortals]) { let num_leafs = bsp.leafs.len() as u32; let mut visdata = Vec::new(); @@ -417,7 +442,7 @@ fn compute_vertex_light( total.min(1.0) } -fn compute_bsp_vertex_lighting(bsp: &mut Bsp, lights: &[LightSource], ambient: f32) { +fn compute_bsp_vertex_lighting(bsp: &mut BspBuilder, lights: &[LightSource], ambient: f32) { if bsp.vertices.is_empty() { return; } @@ -462,35 +487,35 @@ fn compute_bsp_vertex_lighting(bsp: &mut Bsp, lights: &[LightSource], ambient: f } } -fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) { - let mut face_count = 0; +fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) { + let mut wall_count = 0; let mut floor_ceiling_count = 0; for y in 0..grid.height { for x in 0..grid.width { if grid.get(x, y) == 0 { - if grid.get(x - 1, y) == 1 { face_count += 1; } - if grid.get(x + 1, y) == 1 { face_count += 1; } - if grid.get(x, y - 1) == 1 { face_count += 1; } - if grid.get(x, y + 1) == 1 { face_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; } } } - face_count += floor_ceiling_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; - let max_nodes = 2 * total_cells; - let total_planes = face_count + max_nodes; + let max_nodes = 2 * total_cells + 1; + let total_planes = face_count + max_nodes + 1; - bsp.vertices = vec![BspVertex::default(); vertex_count]; - bsp.faces = vec![BspFace::default(); face_count]; - bsp.planes = vec![BspPlane::default(); total_planes]; - bsp.edges = vec![BspEdge::default(); vertex_count]; + 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![BspNode::default(); max_nodes]; + bsp.nodes = vec![Node::default(); max_nodes]; let mut face_cell = vec![0u32; face_count]; @@ -506,9 +531,35 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) { let cell_idx = (y * grid.width + x) as u32; if grid.get(x - 1, y) == 1 { - bsp.vertices[vert_idx + 0].position = Vec3::new(fx, 0.0, fy); + 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); + + 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 = 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, 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); @@ -517,7 +568,7 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) { bsp.faces[face_idx].plane_id = face_idx as u16; bsp.faces[face_idx].num_edges = 4; bsp.faces[face_idx].first_edge = edge_idx as u32; - bsp.faces[face_idx].material_id = 0; + bsp.faces[face_idx].material_id = 3; for i in 0..4 { bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; @@ -534,8 +585,8 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) { } if grid.get(x + 1, y) == 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 + 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); @@ -545,7 +596,33 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) { bsp.faces[face_idx].plane_id = face_idx as u16; bsp.faces[face_idx].num_edges = 4; bsp.faces[face_idx].first_edge = edge_idx as u32; - bsp.faces[face_idx].material_id = 0; + bsp.faces[face_idx].material_id = 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; @@ -562,8 +639,8 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) { } if grid.get(x, y - 1) == 1 { - bsp.vertices[vert_idx + 0].position = Vec3::new(fx, 0.0, fy); - bsp.vertices[vert_idx + 1].position = Vec3::new(fx + CELL_SIZE, 0.0, fy); + 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); bsp.vertices[vert_idx + 3].position = Vec3::new(fx, WALL_HEIGHT, fy); @@ -573,7 +650,33 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) { bsp.faces[face_idx].plane_id = face_idx as u16; bsp.faces[face_idx].num_edges = 4; bsp.faces[face_idx].first_edge = edge_idx as u32; - bsp.faces[face_idx].material_id = 0; + bsp.faces[face_idx].material_id = 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, 0.0, fy); + bsp.vertices[vert_idx + 1].position = Vec3::new(fx + CELL_SIZE, 0.0, fy); + bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy); + bsp.vertices[vert_idx + 3].position = Vec3::new(fx, TRIM_HEIGHT, fy); + + bsp.planes[face_idx].normal = Vec3::new(0.0, 0.0, 1.0); + bsp.planes[face_idx].dist = fy; + + 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; @@ -590,9 +693,35 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) { } if grid.get(x, y + 1) == 1 { - bsp.vertices[vert_idx + 0].position = Vec3::new(fx, 0.0, fy + CELL_SIZE); + 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); + bsp.vertices[vert_idx + 3].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy + CELL_SIZE); + + bsp.planes[face_idx].normal = Vec3::new(0.0, 0.0, -1.0); + bsp.planes[face_idx].dist = -(fy + 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, 0.0, fy + CELL_SIZE); + bsp.vertices[vert_idx + 1].position = Vec3::new(fx, TRIM_HEIGHT, 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, 0.0, fy + CELL_SIZE); bsp.planes[face_idx].normal = Vec3::new(0.0, 0.0, -1.0); @@ -601,7 +730,7 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) { bsp.faces[face_idx].plane_id = face_idx as u16; bsp.faces[face_idx].num_edges = 4; bsp.faces[face_idx].first_edge = edge_idx as u32; - bsp.faces[face_idx].material_id = 0; + bsp.faces[face_idx].material_id = 3; for i in 0..4 { bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; @@ -661,7 +790,7 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) { bsp.edges.truncate(edge_idx); bsp.surfedges.truncate(edge_idx); - bsp.leafs = vec![BspLeaf::default(); total_cells]; + bsp.leafs = vec![Leaf::default(); total_cells + 1]; bsp.marksurfaces = vec![0u16; face_idx]; let mut faces_per_cell = vec![0u32; total_cells]; @@ -712,6 +841,17 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) { } } + let floor_leaf_idx = total_cells as i32; + let floor_leaf = &mut bsp.leafs[total_cells]; + floor_leaf.contents = -1; + floor_leaf.mins = [0, i16::MIN, 0]; + floor_leaf.maxs = [ + (grid.width as f32 * CELL_SIZE) as i16, + 0, + (grid.height as f32 * CELL_SIZE) as i16, + ]; + floor_leaf.visofs = -1; + let face_count_final = face_idx; let mut ctx = BspBuildContext { bsp, @@ -720,7 +860,7 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) { plane_offset: face_count_final as u32, }; - build_bsp_node(&mut ctx, 0, 0, grid.width, grid.height, 0); + build_bsp_node(&mut ctx, 0, 0, grid.width, grid.height, 0, floor_leaf_idx); let node_count = ctx.node_count as usize; let plane_count = ctx.plane_offset as usize; @@ -782,7 +922,7 @@ pub fn generate_rooms(params: &ProcgenParams) -> Bsp { } } - let mut bsp = Bsp::new(); + let mut bsp = BspBuilder::new(); grid_to_bsp(&mut bsp, &grid); let light_height = 80.0; @@ -798,7 +938,7 @@ pub fn generate_rooms(params: &ProcgenParams) -> Bsp { compute_bsp_vertex_lighting(&mut bsp, &lights, 0.1); - bsp + bsp.into() } pub fn generate(params: &ProcgenParams) -> Bsp { diff --git a/pxl8d/src/sim.rs b/pxl8d/src/sim.rs index 8ddef47..e3dc5c1 100644 --- a/pxl8d/src/sim.rs +++ b/pxl8d/src/sim.rs @@ -1,38 +1,29 @@ extern crate alloc; use alloc::vec::Vec; -use libm::{cosf, sinf, sqrtf}; -use crate::math::Vec3; -use crate::protocol::*; +use crate::math::{Vec3, Vec3Ext, VEC3_ZERO}; +use crate::pxl8::*; use crate::voxel::VoxelWorld; use crate::world::World; -const ALIVE: u32 = 1 << 0; -const PLAYER: u32 = 1 << 1; -const GROUNDED: u32 = 1 << 2; +pub type Entity = pxl8_sim_entity; + +const ALIVE: u32 = PXL8_SIM_FLAG_ALIVE; +const PLAYER: u32 = PXL8_SIM_FLAG_PLAYER; const MAX_ENTITIES: usize = 1024; -#[derive(Clone, Copy)] -pub struct Entity { - pub flags: u32, - pub kind: u16, - pub pos: Vec3, - pub vel: Vec3, - pub yaw: f32, - pub pitch: f32, -} - impl Default for Entity { fn default() -> Self { Self { - flags: 0, - kind: 0, - pos: Vec3::ZERO, - vel: Vec3::ZERO, + pos: VEC3_ZERO, + vel: VEC3_ZERO, yaw: 0.0, pitch: 0.0, + flags: 0, + kind: 0, + _pad: 0, } } } @@ -96,6 +87,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; @@ -107,120 +105,68 @@ impl Simulation { self.integrate(dt); } - fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 { - if self.world.active().is_some() { - return self.world.trace(from, to, radius); + 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(), + vxl: core::ptr::null(), + vxl_cx: 0, + vxl_cy: 0, + vxl_cz: 0, + }; + } + } + + let cx = VoxelWorld::world_to_chunk(pos.x); + let cy = VoxelWorld::world_to_chunk(pos.y); + let cz = VoxelWorld::world_to_chunk(pos.z); + + if let Some(chunk) = self.voxels.get_chunk(cx, cy, cz) { + pxl8_sim_world { + bsp: core::ptr::null(), + vxl: chunk.chunk as *const _, + vxl_cx: cx, + vxl_cy: cy, + vxl_cz: cz, + } + } else { + pxl8_sim_world { + bsp: core::ptr::null(), + vxl: core::ptr::null(), + vxl_cx: 0, + vxl_cy: 0, + vxl_cz: 0, + } } - self.voxels.trace(from, to, radius) } fn integrate(&mut self, dt: f32) { - const GRAVITY: f32 = 800.0; - const FRICTION: f32 = 6.0; - const RADIUS: f32 = 16.0; - for i in 0..self.entities.len() { let ent = &self.entities[i]; if ent.flags & ALIVE == 0 || ent.flags & PLAYER != 0 { continue; } - let mut vel = ent.vel; - let pos = ent.pos; - let flags = ent.flags; - - vel.y = vel.y - GRAVITY * dt; - - if flags & GROUNDED != 0 { - let speed = sqrtf(vel.x * vel.x + vel.z * vel.z); - if speed > 0.0 { - let drop = speed * FRICTION * dt; - let scale = (speed - drop).max(0.0) / speed; - vel.x = vel.x * scale; - vel.z = vel.z * scale; - } - } - - let target = pos + vel * dt; - let new_pos = self.trace(pos, target, RADIUS); - + let world = self.make_sim_world(ent.pos); let ent = &mut self.entities[i]; - ent.vel = vel; - ent.pos = new_pos; + unsafe { + pxl8_sim_integrate(ent, &world, dt); + } } } fn move_player(&mut self, input: &pxl8_input_msg, dt: f32) { let Some(id) = self.player else { return }; - let ent = &mut self.entities[id as usize]; + let ent = &self.entities[id as usize]; if ent.flags & ALIVE == 0 { return; } - const MOVE_SPEED: f32 = 320.0; - const GROUND_ACCEL: f32 = 10.0; - const AIR_ACCEL: f32 = 1.0; - const STOP_SPEED: f32 = 100.0; - const FRICTION: f32 = 6.0; - const RADIUS: f32 = 16.0; - - let sin_yaw = sinf(input.yaw); - let cos_yaw = cosf(input.yaw); - - let input_len = sqrtf(input.move_x * input.move_x + input.move_y * input.move_y); - let (move_dir, target_speed) = if input_len > 0.0 { - let nx = input.move_x / input_len; - let ny = input.move_y / input_len; - let dir = Vec3::new( - cos_yaw * nx - sin_yaw * ny, - 0.0, - -sin_yaw * nx - cos_yaw * ny, - ); - (dir, MOVE_SPEED) - } else { - (Vec3::ZERO, 0.0) - }; - - let grounded = ent.flags & GROUNDED != 0; - - if grounded { - let speed = sqrtf(ent.vel.x * ent.vel.x + ent.vel.z * ent.vel.z); - if speed > 0.0 { - let control = speed.max(STOP_SPEED); - let drop = control * FRICTION * dt; - let scale = (speed - drop).max(0.0) / speed; - ent.vel.x *= scale; - ent.vel.z *= scale; - } - } - - if target_speed > 0.0 { - let accel = if grounded { GROUND_ACCEL } else { AIR_ACCEL }; - let current = ent.vel.x * move_dir.x + ent.vel.z * move_dir.z; - let add = target_speed - current; - if add > 0.0 { - let amount = (accel * target_speed * dt).min(add); - ent.vel.x += move_dir.x * amount; - ent.vel.z += move_dir.z * amount; - } - } - - ent.yaw = input.yaw; - ent.pitch = (ent.pitch - input.look_dy * 0.008).clamp(-1.5, 1.5); - - let pos = ent.pos; - let vel = ent.vel; - let target = pos + vel * dt; - let new_pos = self.trace(pos, target, RADIUS); - let ground_check = self.trace(new_pos, new_pos - Vec3::Y * 2.0, RADIUS); - + let world = self.make_sim_world(ent.pos); let ent = &mut self.entities[id as usize]; - ent.pos = new_pos; - - if ground_check.y < new_pos.y - 1.0 { - ent.flags &= !GROUNDED; - } else { - ent.flags |= GROUNDED; + unsafe { + pxl8_sim_move_player(ent, input, &world, dt); } } diff --git a/pxl8d/src/transport.rs b/pxl8d/src/transport.rs index 3aa1381..61217c3 100644 --- a/pxl8d/src/transport.rs +++ b/pxl8d/src/transport.rs @@ -2,8 +2,8 @@ extern crate alloc; use alloc::vec; use alloc::vec::Vec; -use crate::protocol::*; -use crate::protocol::pxl8_msg_type::*; +use crate::pxl8::*; +use crate::pxl8::pxl8_msg_type::*; use crate::voxel::VoxelChunk; pub const DEFAULT_PORT: u16 = 7777; @@ -374,6 +374,24 @@ impl Transport { 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/voxel.rs b/pxl8d/src/voxel.rs index 879363e..5c46dc5 100644 --- a/pxl8d/src/voxel.rs +++ b/pxl8d/src/voxel.rs @@ -1,21 +1,22 @@ extern crate alloc; +use alloc::vec; use alloc::vec::Vec; -use libm::floorf; +use core::ptr; use crate::math::Vec3; +use crate::pxl8::*; -pub const CHUNK_SIZE: usize = 32; -pub const CHUNK_VOLUME: usize = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE; +const CHUNK_SIZE: i32 = PXL8_VXL_CHUNK_SIZE as i32; +const CHUNK_VOLUME: usize = PXL8_VXL_CHUNK_VOLUME as usize; +const WORLD_CHUNK_SIZE: f32 = PXL8_VXL_WORLD_CHUNK_SIZE as f32; +const AIR: u8 = PXL8_VXL_BLOCK_AIR as u8; +const GRASS: u8 = 3; +const DIRT: u8 = 2; +const STONE: u8 = 1; -pub const AIR: u8 = 0; -pub const STONE: u8 = 1; -pub const DIRT: u8 = 2; -pub const GRASS: u8 = 3; - -#[derive(Clone)] pub struct VoxelChunk { - pub blocks: [u8; CHUNK_VOLUME], + pub chunk: *mut pxl8_vxl_chunk, pub cx: i32, pub cy: i32, pub cz: i32, @@ -23,79 +24,57 @@ pub struct VoxelChunk { impl VoxelChunk { pub fn new(cx: i32, cy: i32, cz: i32) -> Self { - Self { - blocks: [AIR; CHUNK_VOLUME], - cx, - cy, - cz, - } + let chunk = unsafe { pxl8_vxl_chunk_create() }; + Self { chunk, cx, cy, cz } } - pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 { - if !self.is_solid_radius(to.x, to.y, to.z, radius) { - return to; - } - - let x_ok = !self.is_solid_radius(to.x, from.y, from.z, radius); - let y_ok = !self.is_solid_radius(from.x, to.y, from.z, radius); - let z_ok = !self.is_solid_radius(from.x, from.y, to.z, radius); - - let mut result = from; - if x_ok { result.x = to.x; } - if y_ok { result.y = to.y; } - if z_ok { result.z = to.z; } - result - } - - fn world_to_local(x: f32, chunk_coord: i32) -> usize { - let chunk_base = chunk_coord as f32 * CHUNK_SIZE as f32; - let local = (x - chunk_base) as usize; - local.min(CHUNK_SIZE - 1) - } - - fn is_solid_at(&self, x: f32, y: f32, z: f32) -> bool { - let lx = Self::world_to_local(x, self.cx); - let ly = Self::world_to_local(y, self.cy); - let lz = Self::world_to_local(z, self.cz); - self.get(lx, ly, lz) != AIR - } - - fn is_solid_radius(&self, x: f32, y: f32, z: f32, radius: f32) -> bool { - self.is_solid_at(x - radius, y, z) || - self.is_solid_at(x + radius, y, z) || - self.is_solid_at(x, y - radius, z) || - self.is_solid_at(x, y + radius, z) || - self.is_solid_at(x, y, z - radius) || - self.is_solid_at(x, y, z + radius) - } - - pub fn index(x: usize, y: usize, z: usize) -> usize { - x + y * CHUNK_SIZE + z * CHUNK_SIZE * CHUNK_SIZE - } - - pub fn get(&self, x: usize, y: usize, z: usize) -> u8 { - if x >= CHUNK_SIZE || y >= CHUNK_SIZE || z >= CHUNK_SIZE { + pub fn get(&self, x: i32, y: i32, z: i32) -> u8 { + if self.chunk.is_null() { return AIR; } - self.blocks[Self::index(x, y, z)] + unsafe { pxl8_vxl_block_get(self.chunk, x, y, z) } } - pub fn set(&mut self, x: usize, y: usize, z: usize, block: u8) { - if x < CHUNK_SIZE && y < CHUNK_SIZE && z < CHUNK_SIZE { - self.blocks[Self::index(x, y, z)] = block; + pub fn set(&mut self, x: i32, y: i32, z: i32, block: u8) { + if self.chunk.is_null() { + return; } + unsafe { pxl8_vxl_block_set(self.chunk, x, y, z, block) } + } + + pub fn fill(&mut self, block: u8) { + if self.chunk.is_null() { + return; + } + unsafe { pxl8_vxl_block_fill(self.chunk, block) } + } + + pub fn is_uniform(&self) -> bool { + if self.chunk.is_null() { + return true; + } + unsafe { pxl8_vxl_chunk_is_uniform(self.chunk) } } pub fn rle_encode(&self) -> Vec { + if self.chunk.is_null() { + return Vec::new(); + } + + let mut linear = vec![0u8; CHUNK_VOLUME]; + unsafe { + pxl8_vxl_chunk_linearize(self.chunk, linear.as_mut_ptr()); + } + let mut result = Vec::new(); let mut i = 0; while i < CHUNK_VOLUME { - let block = self.blocks[i]; + let block = linear[i]; let mut run_len = 1usize; while i + run_len < CHUNK_VOLUME - && self.blocks[i + run_len] == block + && linear[i + run_len] == block && run_len < 256 { run_len += 1; @@ -110,6 +89,34 @@ impl VoxelChunk { } } +impl Drop for VoxelChunk { + fn drop(&mut self) { + if !self.chunk.is_null() { + unsafe { pxl8_vxl_chunk_destroy(self.chunk) }; + self.chunk = ptr::null_mut(); + } + } +} + +impl Clone for VoxelChunk { + fn clone(&self) -> Self { + let new_chunk = Self::new(self.cx, self.cy, self.cz); + for z in 0..CHUNK_SIZE { + for y in 0..CHUNK_SIZE { + for x in 0..CHUNK_SIZE { + let block = self.get(x, y, z); + if block != AIR { + unsafe { + pxl8_vxl_block_set(new_chunk.chunk, x, y, z, block); + } + } + } + } + } + new_chunk + } +} + pub struct VoxelWorld { pub chunks: Vec, pub seed: u64, @@ -141,61 +148,7 @@ impl VoxelWorld { } pub fn world_to_chunk(x: f32) -> i32 { - floorf(x / CHUNK_SIZE as f32) as i32 - } - - pub fn world_to_local(x: f32) -> usize { - let chunk = floorf(x / CHUNK_SIZE as f32); - let local = (x - chunk * CHUNK_SIZE as f32) as usize; - local.min(CHUNK_SIZE - 1) - } - - pub fn is_solid(&self, x: f32, y: f32, z: f32) -> bool { - let cx = Self::world_to_chunk(x); - let cy = Self::world_to_chunk(y); - let cz = Self::world_to_chunk(z); - - let lx = Self::world_to_local(x); - let ly = Self::world_to_local(y); - let lz = Self::world_to_local(z); - - match self.get_chunk(cx, cy, cz) { - Some(chunk) => chunk.get(lx, ly, lz) != AIR, - None => false, - } - } - - pub fn is_solid_radius(&self, x: f32, y: f32, z: f32, radius: f32) -> bool { - self.is_solid(x - radius, y, z) || - self.is_solid(x + radius, y, z) || - self.is_solid(x, y - radius, z) || - self.is_solid(x, y + radius, z) || - self.is_solid(x, y, z - radius) || - self.is_solid(x, y, z + radius) - } - - pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 { - if !self.is_solid_radius(to.x, to.y, to.z, radius) { - return to; - } - - let x_ok = !self.is_solid_radius(to.x, from.y, from.z, radius); - let y_ok = !self.is_solid_radius(from.x, to.y, from.z, radius); - let z_ok = !self.is_solid_radius(from.x, from.y, to.z, radius); - - let mut result = from; - - if x_ok { - result.x = to.x; - } - if y_ok { - result.y = to.y; - } - if z_ok { - result.z = to.z; - } - - result + libm::floorf(x / WORLD_CHUNK_SIZE) as i32 } pub fn chunks_near(&self, pos: Vec3, radius: i32) -> Vec<(i32, i32, i32)> { @@ -220,6 +173,37 @@ impl VoxelWorld { self.ensure_chunk(cx, cy, cz); } } + + pub fn find_surface_y(&mut self, world_x: f32, world_z: f32) -> f32 { + let scale = PXL8_VXL_SCALE as f32; + let block_x = libm::floorf(world_x / scale) as i32; + let block_z = libm::floorf(world_z / scale) as i32; + + let cx = libm::floorf(block_x as f32 / CHUNK_SIZE as f32) as i32; + let cz = libm::floorf(block_z as f32 / CHUNK_SIZE as f32) as i32; + let lx = ((block_x % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE; + let lz = ((block_z % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE; + + for cy in (-2..=2).rev() { + self.ensure_chunk(cx, cy, cz); + if let Some(chunk) = self.get_chunk(cx, cy, cz) { + for ly in (0..CHUNK_SIZE).rev() { + let block = chunk.get(lx, ly, lz); + if block == GRASS { + let world_y = (cy * CHUNK_SIZE + ly + 1) as f32 * scale; + return world_y; + } + } + } + } + + 0.0 + } +} + +fn noise3d(x: i32, y: i32, z: i32, seed: u64) -> f32 { + let h = hash(seed ^ (x as u64) ^ ((y as u64) << 21) ^ ((z as u64) << 42)); + (h & 0xFFFF) as f32 / 65535.0 } fn hash(mut x: u64) -> u64 { @@ -231,11 +215,6 @@ fn hash(mut x: u64) -> u64 { x } -fn noise2d(x: i32, z: i32, seed: u64) -> f32 { - let h = hash(seed ^ (x as u64) ^ ((z as u64) << 32)); - (h & 0xFFFF) as f32 / 65535.0 -} - fn smoothstep(t: f32) -> f32 { t * t * (3.0 - 2.0 * t) } @@ -244,33 +223,84 @@ fn lerp(a: f32, b: f32, t: f32) -> f32 { a + (b - a) * t } -fn value_noise(x: f32, z: f32, seed: u64) -> f32 { - let x0 = floorf(x) as i32; - let z0 = floorf(z) as i32; +fn value_noise_3d(x: f32, y: f32, z: f32, seed: u64) -> f32 { + let x0 = libm::floorf(x) as i32; + let y0 = libm::floorf(y) as i32; + let z0 = libm::floorf(z) as i32; let x1 = x0 + 1; + let y1 = y0 + 1; let z1 = z0 + 1; let tx = smoothstep(x - x0 as f32); + let ty = smoothstep(y - y0 as f32); let tz = smoothstep(z - z0 as f32); - let c00 = noise2d(x0, z0, seed); - let c10 = noise2d(x1, z0, seed); - let c01 = noise2d(x0, z1, seed); - let c11 = noise2d(x1, z1, seed); + let c000 = noise3d(x0, y0, z0, seed); + let c100 = noise3d(x1, y0, z0, seed); + let c010 = noise3d(x0, y1, z0, seed); + let c110 = noise3d(x1, y1, z0, seed); + let c001 = noise3d(x0, y0, z1, seed); + let c101 = noise3d(x1, y0, z1, seed); + let c011 = noise3d(x0, y1, z1, seed); + let c111 = noise3d(x1, y1, z1, seed); - let a = lerp(c00, c10, tx); - let b = lerp(c01, c11, tx); - lerp(a, b, tz) + let a00 = lerp(c000, c100, tx); + let a10 = lerp(c010, c110, tx); + let a01 = lerp(c001, c101, tx); + let a11 = lerp(c011, c111, tx); + + let b0 = lerp(a00, a10, ty); + let b1 = lerp(a01, a11, ty); + + lerp(b0, b1, tz) } -fn fbm(x: f32, z: f32, seed: u64, octaves: u32) -> f32 { +fn fbm_3d(x: f32, y: f32, z: f32, seed: u64, octaves: u32) -> f32 { let mut value = 0.0; let mut amplitude = 1.0; let mut frequency = 1.0; let mut max_value = 0.0; for i in 0..octaves { - value += amplitude * value_noise(x * frequency, z * frequency, seed.wrapping_add(i as u64 * 1000)); + value += amplitude * value_noise_3d( + x * frequency, + y * frequency, + z * frequency, + seed.wrapping_add(i as u64 * 1000) + ); + max_value += amplitude; + amplitude *= 0.5; + frequency *= 2.0; + } + + value / max_value +} + +fn fbm_2d(x: f32, z: f32, seed: u64, octaves: u32) -> f32 { + let mut value = 0.0; + let mut amplitude = 1.0; + let mut frequency = 1.0; + let mut max_value = 0.0; + + for i in 0..octaves { + let x0 = libm::floorf(x * frequency) as i32; + let z0 = libm::floorf(z * frequency) as i32; + let x1 = x0 + 1; + let z1 = z0 + 1; + + let tx = smoothstep(x * frequency - x0 as f32); + let tz = smoothstep(z * frequency - z0 as f32); + + let offset_seed = seed.wrapping_add(i as u64 * 1000); + let c00 = (hash(offset_seed ^ (x0 as u64) ^ ((z0 as u64) << 32)) & 0xFFFF) as f32 / 65535.0; + let c10 = (hash(offset_seed ^ (x1 as u64) ^ ((z0 as u64) << 32)) & 0xFFFF) as f32 / 65535.0; + let c01 = (hash(offset_seed ^ (x0 as u64) ^ ((z1 as u64) << 32)) & 0xFFFF) as f32 / 65535.0; + let c11 = (hash(offset_seed ^ (x1 as u64) ^ ((z1 as u64) << 32)) & 0xFFFF) as f32 / 65535.0; + + let a = lerp(c00, c10, tx); + let b = lerp(c01, c11, tx); + value += amplitude * lerp(a, b, tz); + max_value += amplitude; amplitude *= 0.5; frequency *= 2.0; @@ -280,29 +310,60 @@ fn fbm(x: f32, z: f32, seed: u64, octaves: u32) -> f32 { } fn generate_chunk(chunk: &mut VoxelChunk, seed: u64) { - let world_x = chunk.cx * CHUNK_SIZE as i32; - let world_y = chunk.cy * CHUNK_SIZE as i32; - let world_z = chunk.cz * CHUNK_SIZE as i32; + let world_x = chunk.cx * CHUNK_SIZE; + let world_y = chunk.cy * CHUNK_SIZE; + let world_z = chunk.cz * CHUNK_SIZE; - for lz in 0..CHUNK_SIZE { - for lx in 0..CHUNK_SIZE { + let mut height_cache = [[0i32; 32]; 32]; + for lz in 0..32 { + for lx in 0..32 { let wx = (world_x + lx as i32) as f32; let wz = (world_z + lz as i32) as f32; + height_cache[lz][lx] = (fbm_2d(wx * 0.02, wz * 0.02, seed, 4) * 32.0) as i32; + } + } - let height = fbm(wx * 0.02, wz * 0.02, seed, 4) * 32.0 + 16.0; - let height = height as i32; + let mut density = [[[0.0f32; 33]; 33]; 33]; + for lz in 0..33 { + for ly in 0..33 { + for lx in 0..33 { + let wx = (world_x + lx as i32) as f32; + let wy = (world_y + ly as i32) as f32; + let wz = (world_z + lz as i32) as f32; - for ly in 0..CHUNK_SIZE { - let wy = world_y + ly as i32; + let hx = lx.min(31); + let hz = lz.min(31); + let base_height = height_cache[hz][hx] as f32; + let height_bias = (base_height - wy) * 0.1; - let block = if wy > height { - AIR - } else if wy == height { + let cave_noise = fbm_3d(wx * 0.05, wy * 0.05, wz * 0.05, seed.wrapping_add(1000), 2); + density[lz][ly][lx] = height_bias + (cave_noise - 0.55); + } + } + } + + for lz in 0..CHUNK_SIZE { + for ly in 0..CHUNK_SIZE { + for lx in 0..CHUNK_SIZE { + let d = density[lz as usize][ly as usize][lx as usize]; + if d <= 0.0 { + continue; + } + + let d_above = density[lz as usize][(ly + 1) as usize][lx as usize]; + let is_surface = d_above <= 0.0; + + let block = if is_surface { GRASS - } else if wy > height - 4 { - DIRT } else { - STONE + let wy = world_y + ly; + let base_height = height_cache[lz as usize][lx as usize]; + let depth = base_height - wy; + if depth < 4 { + DIRT + } else { + STONE + } }; chunk.set(lx, ly, lz, block); diff --git a/pxl8d/src/world.rs b/pxl8d/src/world.rs index cf4cc99..087ce7c 100644 --- a/pxl8d/src/world.rs +++ b/pxl8d/src/world.rs @@ -38,10 +38,6 @@ impl World { self.chunks.remove(id) } - pub fn set_active(&mut self, id: ChunkId) { - self.active = Some(id); - } - pub fn active(&self) -> Option<&Chunk> { self.active.as_ref().and_then(|id| self.chunks.get(id)) } @@ -50,6 +46,14 @@ impl World { self.active } + pub fn set_active(&mut self, id: ChunkId) { + self.active = Some(id); + } + + pub fn clear_active(&mut self) { + self.active = None; + } + pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 { if let Some(chunk) = self.active() { return chunk.trace(from, to, radius); diff --git a/src/asset/pxl8_ase.c b/src/asset/pxl8_ase.c index df78459..dc01cf4 100644 --- a/src/asset/pxl8_ase.c +++ b/src/asset/pxl8_ase.c @@ -7,11 +7,11 @@ #define MINIZ_NO_TIME #define MINIZ_NO_ARCHIVE_APIS #define MINIZ_NO_ARCHIVE_WRITING_APIS -#define MINIZ_NO_DEFLATE_APIS #define MINIZ_NO_ZLIB_COMPATIBLE_NAMES #include +#include "pxl8_color.h" #include "pxl8_io.h" #include "pxl8_log.h" #include "pxl8_mem.h" @@ -635,3 +635,302 @@ void pxl8_ase_destroy(pxl8_ase_file* ase_file) { memset(ase_file, 0, sizeof(pxl8_ase_file)); } + +pxl8_result pxl8_ase_load_palette(const char* filepath, u32* colors, u32* count) { + if (!filepath || !colors || !count) { + return PXL8_ERROR_NULL_POINTER; + } + + pxl8_ase_file ase; + pxl8_result result = pxl8_ase_load(filepath, &ase); + if (result != PXL8_OK) { + return result; + } + + u32 n = ase.palette.entry_count; + if (n > 256) n = 256; + + for (u32 i = 0; i < n; i++) { + colors[i] = ase.palette.colors[i]; + } + *count = n; + + pxl8_ase_destroy(&ase); + return PXL8_OK; +} + +pxl8_result pxl8_ase_remap(const char* input_path, const char* output_path, const pxl8_ase_remap_config* config) { + if (!input_path || !output_path || !config) { + return PXL8_ERROR_NULL_POINTER; + } + + if (!config->palette || config->palette_count == 0) { + return PXL8_ERROR_INVALID_ARGUMENT; + } + + f32 hue_tol = config->hue_tolerance > 0.0f ? config->hue_tolerance : 0.08f; + + u8* file_data; + usize file_size; + pxl8_result result = pxl8_io_read_binary_file(input_path, &file_data, &file_size); + if (result != PXL8_OK) { + return result; + } + + if (file_size < 128) { + pxl8_io_free_binary_data(file_data); + return PXL8_ERROR_ASE_TRUNCATED_FILE; + } + + u8* output_data = (u8*)pxl8_malloc(file_size + 65536); + if (!output_data) { + pxl8_io_free_binary_data(file_data); + return PXL8_ERROR_OUT_OF_MEMORY; + } + memcpy(output_data, file_data, file_size); + + pxl8_stream stream = pxl8_stream_create(file_data, (u32)file_size); + pxl8_stream_seek(&stream, 128); + + u32 frame_start = 128; + u32 frame_size = pxl8_read_u32(&stream); + pxl8_skip_bytes(&stream, 2); + u16 old_chunks = pxl8_read_u16(&stream); + pxl8_skip_bytes(&stream, 2); + u16 num_chunks = old_chunks; + if (old_chunks == 0xFFFF || old_chunks == 0xFFFE) { + num_chunks = (u16)pxl8_read_u32(&stream); + } else { + pxl8_skip_bytes(&stream, 4); + } + + u32 palette_chunk_offset = 0; + u32 palette_entry_start = 0; + u32 orig_colors[256] = {0}; + + u32 chunk_offset = frame_start + 16; + for (u16 c = 0; c < num_chunks; c++) { + pxl8_stream_seek(&stream, chunk_offset); + u32 chunk_size = pxl8_read_u32(&stream); + u16 chunk_type = pxl8_read_u16(&stream); + + if (chunk_type == PXL8_ASE_CHUNK_PALETTE) { + palette_chunk_offset = chunk_offset; + pxl8_skip_bytes(&stream, 4); + u32 first_color = pxl8_read_u32(&stream); + u32 last_color = pxl8_read_u32(&stream); + pxl8_skip_bytes(&stream, 8); + palette_entry_start = pxl8_stream_position(&stream); + + for (u32 i = first_color; i <= last_color && i < 256; i++) { + u16 flags = pxl8_read_u16(&stream); + u8 r = pxl8_read_u8(&stream); + u8 g = pxl8_read_u8(&stream); + u8 b = pxl8_read_u8(&stream); + pxl8_skip_bytes(&stream, 1); + orig_colors[i] = r | (g << 8) | (b << 16); + if (flags & 1) { + u16 name_len = pxl8_read_u16(&stream); + pxl8_skip_bytes(&stream, name_len); + } + } + } + chunk_offset += chunk_size; + } + + if (palette_entry_start == 0) { + pxl8_free(output_data); + pxl8_io_free_binary_data(file_data); + return PXL8_ERROR_ASE_MALFORMED_CHUNK; + } + + u8 remap[256]; + bool used[256] = {0}; + for (u32 i = 0; i < 256; i++) { + u32 src = orig_colors[i]; + f32 src_hue = pxl8_color_hue(src); + f32 src_sat = pxl8_color_saturation(src); + f32 src_lum = pxl8_color_luminance(src); + + u32 best_idx = 0; + f32 best_score = 999999.0f; + + for (u32 j = 0; j < config->palette_count; j++) { + u32 tgt = config->palette[j]; + f32 tgt_hue = pxl8_color_hue(tgt); + f32 tgt_sat = pxl8_color_saturation(tgt); + f32 tgt_lum = pxl8_color_luminance(tgt); + + f32 hue_diff = pxl8_color_hue_diff(src_hue, tgt_hue); + f32 lum_diff = src_lum > tgt_lum ? src_lum - tgt_lum : tgt_lum - src_lum; + f32 sat_diff = src_sat > tgt_sat ? src_sat - tgt_sat : tgt_sat - src_sat; + + f32 score; + if (src_sat < 0.1f) { + score = lum_diff + sat_diff * 100.0f; + } else if (hue_diff <= hue_tol) { + score = lum_diff + sat_diff * 50.0f; + } else { + score = hue_diff * 1000.0f + lum_diff; + } + + if (score < best_score) { + best_score = score; + best_idx = j; + } + } + remap[i] = (u8)best_idx; + used[best_idx] = true; + } + + u8 compact[256]; + u32 compact_colors[256]; + u32 compact_count = 0; + for (u32 i = 0; i < config->palette_count; i++) { + if (used[i]) { + compact[i] = (u8)compact_count; + compact_colors[compact_count] = config->palette[i]; + compact_count++; + } + } + + for (u32 i = 0; i < 256; i++) { + remap[i] = compact[remap[i]]; + } + + for (u32 i = 0; i < compact_count; i++) { + u32 offset = palette_entry_start + i * 6; + u32 color = compact_colors[i]; + output_data[offset + 0] = 0; + output_data[offset + 1] = 0; + output_data[offset + 2] = color & 0xFF; + output_data[offset + 3] = (color >> 8) & 0xFF; + output_data[offset + 4] = (color >> 16) & 0xFF; + output_data[offset + 5] = 0xFF; + } + + u32 new_last_color = compact_count > 0 ? compact_count - 1 : 0; + output_data[palette_chunk_offset + 6] = compact_count & 0xFF; + output_data[palette_chunk_offset + 7] = (compact_count >> 8) & 0xFF; + output_data[palette_chunk_offset + 8] = (compact_count >> 16) & 0xFF; + output_data[palette_chunk_offset + 9] = (compact_count >> 24) & 0xFF; + output_data[palette_chunk_offset + 14] = new_last_color & 0xFF; + output_data[palette_chunk_offset + 15] = (new_last_color >> 8) & 0xFF; + output_data[palette_chunk_offset + 16] = (new_last_color >> 16) & 0xFF; + output_data[palette_chunk_offset + 17] = (new_last_color >> 24) & 0xFF; + + chunk_offset = frame_start + 16; + usize output_size = file_size; + + for (u16 c = 0; c < num_chunks; c++) { + pxl8_stream_seek(&stream, chunk_offset); + u32 chunk_size = pxl8_read_u32(&stream); + u16 chunk_type = pxl8_read_u16(&stream); + + if (chunk_type == PXL8_ASE_CHUNK_CEL) { + pxl8_skip_bytes(&stream, 7); + u16 cel_type = pxl8_read_u16(&stream); + + if (cel_type == 2) { + pxl8_skip_bytes(&stream, 7); + u16 width = pxl8_read_u16(&stream); + u16 height = pxl8_read_u16(&stream); + u32 pixels_size = width * height; + u32 compressed_start = pxl8_stream_position(&stream); + u32 compressed_size = chunk_size - (compressed_start - chunk_offset); + + u8* pixels = (u8*)pxl8_malloc(pixels_size); + if (!pixels) { + pxl8_free(output_data); + pxl8_io_free_binary_data(file_data); + return PXL8_ERROR_OUT_OF_MEMORY; + } + + const u8* compressed_data = file_data + compressed_start; + mz_ulong dest_len = pixels_size; + i32 mz_result = mz_uncompress(pixels, &dest_len, compressed_data, compressed_size); + if (mz_result != MZ_OK) { + pxl8_free(pixels); + pxl8_free(output_data); + pxl8_io_free_binary_data(file_data); + return PXL8_ERROR_ASE_MALFORMED_CHUNK; + } + + for (u32 i = 0; i < pixels_size; i++) { + pixels[i] = remap[pixels[i]]; + } + + mz_ulong new_compressed_size = mz_compressBound(pixels_size); + u8* new_compressed = (u8*)pxl8_malloc(new_compressed_size); + if (!new_compressed) { + pxl8_free(pixels); + pxl8_free(output_data); + pxl8_io_free_binary_data(file_data); + return PXL8_ERROR_OUT_OF_MEMORY; + } + + mz_result = mz_compress2(new_compressed, &new_compressed_size, pixels, pixels_size, 6); + pxl8_free(pixels); + + if (mz_result != MZ_OK) { + pxl8_free(new_compressed); + pxl8_free(output_data); + pxl8_io_free_binary_data(file_data); + return PXL8_ERROR_ASE_MALFORMED_CHUNK; + } + + i32 size_diff = (i32)new_compressed_size - (i32)compressed_size; + + u8* new_output = (u8*)pxl8_malloc(output_size + size_diff + 65536); + if (!new_output) { + pxl8_free(new_compressed); + pxl8_free(output_data); + pxl8_io_free_binary_data(file_data); + return PXL8_ERROR_OUT_OF_MEMORY; + } + + memcpy(new_output, output_data, compressed_start); + memcpy(new_output + compressed_start, new_compressed, new_compressed_size); + memcpy(new_output + compressed_start + new_compressed_size, + output_data + compressed_start + compressed_size, + output_size - compressed_start - compressed_size); + + u32 new_chunk_size = chunk_size + size_diff; + new_output[chunk_offset + 0] = new_chunk_size & 0xFF; + new_output[chunk_offset + 1] = (new_chunk_size >> 8) & 0xFF; + new_output[chunk_offset + 2] = (new_chunk_size >> 16) & 0xFF; + new_output[chunk_offset + 3] = (new_chunk_size >> 24) & 0xFF; + + u32 new_frame_size = frame_size + size_diff; + new_output[frame_start + 0] = new_frame_size & 0xFF; + new_output[frame_start + 1] = (new_frame_size >> 8) & 0xFF; + new_output[frame_start + 2] = (new_frame_size >> 16) & 0xFF; + new_output[frame_start + 3] = (new_frame_size >> 24) & 0xFF; + + output_size += size_diff; + new_output[0] = output_size & 0xFF; + new_output[1] = (output_size >> 8) & 0xFF; + new_output[2] = (output_size >> 16) & 0xFF; + new_output[3] = (output_size >> 24) & 0xFF; + + pxl8_free(new_compressed); + pxl8_free(output_data); + output_data = new_output; + + break; + } + } + chunk_offset += chunk_size; + } + + pxl8_io_free_binary_data(file_data); + + result = pxl8_io_write_binary_file(output_path, output_data, output_size); + pxl8_free(output_data); + + if (result == PXL8_OK) { + pxl8_info("Remapped %s -> %s", input_path, output_path); + } + + return result; +} diff --git a/src/asset/pxl8_ase.h b/src/asset/pxl8_ase.h index f85a251..1a59034 100644 --- a/src/asset/pxl8_ase.h +++ b/src/asset/pxl8_ase.h @@ -141,12 +141,20 @@ typedef struct pxl8_ase_file { pxl8_ase_tileset* tilesets; } pxl8_ase_file; +typedef struct pxl8_ase_remap_config { + const u32* palette; + u32 palette_count; + f32 hue_tolerance; +} pxl8_ase_remap_config; + #ifdef __cplusplus extern "C" { #endif pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file); +pxl8_result pxl8_ase_load_palette(const char* filepath, u32* colors, u32* count); void pxl8_ase_destroy(pxl8_ase_file* ase_file); +pxl8_result pxl8_ase_remap(const char* input_path, const char* output_path, const pxl8_ase_remap_config* config); #ifdef __cplusplus } diff --git a/src/world/pxl8_bsp.c b/src/bsp/pxl8_bsp.c similarity index 56% rename from src/world/pxl8_bsp.c rename to src/bsp/pxl8_bsp.c index 0d73d7e..b3d2a8e 100644 --- a/src/world/pxl8_bsp.c +++ b/src/bsp/pxl8_bsp.c @@ -1,11 +1,8 @@ #include "pxl8_bsp.h" -#include -#include #include #include "pxl8_color.h" -#include "pxl8_gfx.h" #include "pxl8_io.h" #include "pxl8_log.h" #include "pxl8_mem.h" @@ -41,15 +38,6 @@ typedef struct { pxl8_bsp_chunk chunks[CHUNK_COUNT]; } pxl8_bsp_header; -typedef struct { - f32 x0, y0, x1, y1; -} screen_rect; - -typedef struct { - u32 leaf; - screen_rect window; -} portal_queue_entry; - static inline pxl8_vec3 read_vec3(pxl8_stream* stream) { f32 x = pxl8_read_f32(stream); f32 y = pxl8_read_f32(stream); @@ -167,25 +155,6 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { } } - chunk = &header.chunks[CHUNK_TEXINFO]; - if (!validate_chunk(chunk, 40, file_size)) goto error_cleanup; - bsp->num_materials = chunk->size / 40; - if (bsp->num_materials > 0) { - bsp->materials = pxl8_calloc(bsp->num_materials, sizeof(pxl8_gfx_material)); - pxl8_stream_seek(&stream, chunk->offset); - for (u32 i = 0; i < bsp->num_materials; i++) { - bsp->materials[i].u_axis = read_vec3(&stream); - bsp->materials[i].u_offset = pxl8_read_f32(&stream); - bsp->materials[i].v_axis = read_vec3(&stream); - bsp->materials[i].v_offset = pxl8_read_f32(&stream); - bsp->materials[i].texture_id = pxl8_read_u32(&stream); - bsp->materials[i].alpha = 255; - bsp->materials[i].dither = true; - bsp->materials[i].dynamic_lighting = true; - bsp->materials[i].double_sided = true; - } - } - chunk = &header.chunks[CHUNK_FACES]; if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup; bsp->num_faces = chunk->size / 20; @@ -364,12 +333,11 @@ void pxl8_bsp_destroy(pxl8_bsp* bsp) { pxl8_free(bsp->faces); pxl8_free(bsp->leafs); pxl8_free(bsp->lightdata); + pxl8_free(bsp->lightmaps); pxl8_free(bsp->marksurfaces); - pxl8_free(bsp->materials); pxl8_free(bsp->models); pxl8_free(bsp->nodes); pxl8_free(bsp->planes); - pxl8_free(bsp->render_face_flags); pxl8_free(bsp->surfedges); pxl8_free(bsp->vertex_lights); pxl8_free(bsp->vertices); @@ -394,47 +362,6 @@ i32 pxl8_bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) { return -(node_id + 1); } -bool pxl8_bsp_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos) { - i32 leaf = pxl8_bsp_find_leaf(bsp, pos); - if (leaf < 0 || (u32)leaf >= bsp->num_leafs) return true; - return bsp->leafs[leaf].contents == -1; -} - -static bool 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; - - if (point_clear(bsp, to.x, to.y, to.z, radius)) { - return to; - } - - bool x_ok = point_clear(bsp, to.x, from.y, from.z, radius); - bool z_ok = point_clear(bsp, from.x, from.y, to.z, radius); - - if (x_ok && z_ok) { - f32 dx = to.x - from.x; - f32 dz = to.z - from.z; - if (dx * dx > dz * dz) { - return (pxl8_vec3){to.x, from.y, from.z}; - } else { - return (pxl8_vec3){from.x, from.y, to.z}; - } - } else if (x_ok) { - return (pxl8_vec3){to.x, from.y, from.z}; - } else if (z_ok) { - return (pxl8_vec3){from.x, from.y, to.z}; - } - - return from; -} bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to) { if (!bsp || !bsp->visdata || bsp->visdata_size == 0) return true; @@ -613,320 +540,6 @@ pxl8_bsp_lightmap_sample pxl8_bsp_sample_lightmap(const pxl8_bsp* bsp, u32 face_ return (pxl8_bsp_lightmap_sample){b, g, r}; } -static inline bool face_in_frustum(const pxl8_bsp* bsp, u32 face_id, const pxl8_frustum* frustum) { - const pxl8_bsp_face* face = &bsp->faces[face_id]; - 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, - u32 face_id, - pxl8_mesh* mesh -) { - const pxl8_bsp_face* face = &bsp->faces[face_id]; - if (face->num_edges < 3) return; - - pxl8_vec3 normal = {0, 1, 0}; - if (face->plane_id < bsp->num_planes) { - normal = bsp->planes[face->plane_id].normal; - if (face->side) { - normal.x = -normal.x; - normal.y = -normal.y; - normal.z = -normal.z; - } - } - - const pxl8_gfx_material* material = NULL; - f32 tex_scale = 64.0f; - if (face->material_id < bsp->num_materials) { - material = &bsp->materials[face->material_id]; - } - - u16 base_idx = (u16)mesh->vertex_count; - u32 num_verts = 0; - - for (u32 i = 0; i < face->num_edges && num_verts < 64; i++) { - u32 edge_i = face->side ? (face->num_edges - 1 - i) : i; - i32 surfedge_idx = face->first_edge + edge_i; - u32 vert_idx; - - if (!pxl8_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) { - continue; - } - - pxl8_vec3 pos = bsp->vertices[vert_idx].position; - - f32 u = 0.0f, v = 0.0f; - if (material) { - u = (pxl8_vec3_dot(pos, material->u_axis) + material->u_offset) / tex_scale; - v = (pxl8_vec3_dot(pos, material->v_axis) + material->v_offset) / tex_scale; - } - - u8 light = 255; - if (bsp->vertex_lights && vert_idx < bsp->num_vertex_lights) { - light = (bsp->vertex_lights[vert_idx] >> 24) & 0xFF; - } - - pxl8_vertex vtx = { - .position = pos, - .normal = normal, - .u = u, - .v = v, - .color = 15, - .light = light, - }; - pxl8_mesh_push_vertex(mesh, vtx); - num_verts++; - } - - if (num_verts < 3) return; - - for (u32 i = 1; i < num_verts - 1; i++) { - pxl8_mesh_push_triangle(mesh, base_idx, base_idx + i, base_idx + i + 1); - } -} - -void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_gfx_material* material) { - if (!gfx || !bsp || face_id >= bsp->num_faces || !material) return; - - pxl8_mesh* mesh = pxl8_mesh_create(64, 192); - if (!mesh) return; - - collect_face_to_mesh(bsp, face_id, mesh); - - 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_vec3 camera_pos) { - if (!gfx || !bsp || bsp->num_faces == 0) { - return; - } - if (!bsp->cell_portals || bsp->num_cell_portals == 0) { - pxl8_debug("[BSP] render: no cell_portals (ptr=%p count=%u)", (void*)bsp->cell_portals, bsp->num_cell_portals); - return; - } - if (!bsp->materials || bsp->num_materials == 0) { - pxl8_debug("[BSP] render: no materials (ptr=%p count=%u)", (void*)bsp->materials, bsp->num_materials); - 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; - - pxl8_bsp* bsp_mut = (pxl8_bsp*)bsp; - if (!bsp_mut->render_face_flags) { - bsp_mut->render_face_flags = pxl8_calloc(bsp->num_faces, 1); - if (!bsp_mut->render_face_flags) return; - } - memset(bsp_mut->render_face_flags, 0, bsp->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 (bsp_mut->render_face_flags[face_id]) continue; - bsp_mut->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 >= bsp->num_materials) continue; - - if (material_id != current_material && mesh->index_count > 0 && current_material < bsp->num_materials) { - pxl8_mat4 identity = pxl8_mat4_identity(); - pxl8_3d_draw_mesh(gfx, mesh, &identity, &bsp->materials[current_material]); - pxl8_mesh_clear(mesh); - } - - current_material = material_id; - collect_face_to_mesh(bsp, face_id, mesh); - } - } - - if (mesh->index_count > 0 && current_material < bsp->num_materials) { - pxl8_mat4 identity = pxl8_mat4_identity(); - pxl8_3d_draw_mesh(gfx, mesh, &identity, &bsp->materials[current_material]); - } - - pxl8_bsp_pvs_destroy(&pvs); - pxl8_free(visited); - pxl8_free(cell_windows); - pxl8_free(queue); - pxl8_mesh_destroy(mesh); -} - u32 pxl8_bsp_face_count(const pxl8_bsp* bsp) { if (!bsp) return 0; return bsp->num_faces; @@ -952,29 +565,3 @@ void pxl8_bsp_face_set_material(pxl8_bsp* bsp, u32 face_id, u16 material_id) { if (!bsp || face_id >= bsp->num_faces) return; bsp->faces[face_id].material_id = material_id; } - -void pxl8_bsp_set_material(pxl8_bsp* bsp, u16 material_id, const pxl8_gfx_material* material) { - if (!bsp || !material) return; - - if (material_id >= bsp->num_materials) { - u32 new_count = material_id + 1; - pxl8_gfx_material* new_materials = pxl8_realloc(bsp->materials, new_count * sizeof(pxl8_gfx_material)); - if (!new_materials) return; - - for (u32 i = bsp->num_materials; i < new_count; i++) { - memset(&new_materials[i], 0, sizeof(pxl8_gfx_material)); - } - - bsp->materials = new_materials; - bsp->num_materials = new_count; - } - - bsp->materials[material_id] = *material; -} - -void pxl8_bsp_set_wireframe(pxl8_bsp* bsp, bool wireframe) { - if (!bsp || !bsp->materials) return; - for (u32 i = 0; i < bsp->num_materials; i++) { - bsp->materials[i].wireframe = wireframe; - } -} diff --git a/src/world/pxl8_bsp.h b/src/bsp/pxl8_bsp.h similarity index 83% rename from src/world/pxl8_bsp.h rename to src/bsp/pxl8_bsp.h index 74aafa8..eead2a2 100644 --- a/src/world/pxl8_bsp.h +++ b/src/bsp/pxl8_bsp.h @@ -1,8 +1,6 @@ #pragma once -#include "pxl8_gfx.h" #include "pxl8_math.h" -#include "pxl8_mesh.h" #include "pxl8_types.h" typedef struct pxl8_bsp_edge { @@ -105,11 +103,9 @@ typedef struct pxl8_bsp { u8* lightdata; pxl8_bsp_lightmap* lightmaps; u16* marksurfaces; - pxl8_gfx_material* materials; pxl8_bsp_model* models; pxl8_bsp_node* nodes; pxl8_bsp_plane* planes; - u8* render_face_flags; i32* surfedges; u32* vertex_lights; pxl8_bsp_vertex* vertices; @@ -122,7 +118,6 @@ typedef struct pxl8_bsp { u32 num_leafs; u32 num_lightmaps; u32 num_marksurfaces; - u32 num_materials; u32 num_models; u32 num_nodes; u32 num_planes; @@ -143,18 +138,12 @@ pxl8_vec3 pxl8_bsp_face_normal(const pxl8_bsp* bsp, u32 face_id); void pxl8_bsp_face_set_material(pxl8_bsp* bsp, u32 face_id, u16 material_id); i32 pxl8_bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos); bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to); -bool pxl8_bsp_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos); -pxl8_vec3 pxl8_bsp_trace(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, f32 radius); pxl8_bsp_lightmap pxl8_bsp_lightmap_mapped(u8 width, u8 height, u32 offset); pxl8_bsp_lightmap pxl8_bsp_lightmap_uniform(u8 r, u8 g, u8 b); pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp); void pxl8_bsp_pvs_destroy(pxl8_bsp_pvs* pvs); bool pxl8_bsp_pvs_is_visible(const pxl8_bsp_pvs* pvs, i32 leaf); -void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos); -void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_gfx_material* material); pxl8_bsp_lightmap_sample pxl8_bsp_sample_lightmap(const pxl8_bsp* bsp, u32 face_idx, f32 u, f32 v); -void pxl8_bsp_set_material(pxl8_bsp* bsp, u16 material_id, const pxl8_gfx_material* material); -void pxl8_bsp_set_wireframe(pxl8_bsp* bsp, bool wireframe); #ifdef __cplusplus } diff --git a/src/bsp/pxl8_bsp_render.c b/src/bsp/pxl8_bsp_render.c new file mode 100644 index 0000000..5f85a65 --- /dev/null +++ b/src/bsp/pxl8_bsp_render.c @@ -0,0 +1,429 @@ +#include "pxl8_bsp_render.h" + +#include + +#include "pxl8_gfx3d.h" +#include "pxl8_log.h" +#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; + + i32 edge_idx = bsp->surfedges[surfedge_idx]; + u32 vertex_index; + + if (edge_idx >= 0) { + if ((u32)edge_idx >= bsp->num_edges) return false; + vertex_index = 0; + } else { + edge_idx = -edge_idx; + if ((u32)edge_idx >= bsp->num_edges) return false; + vertex_index = 1; + } + + *out_vert_idx = bsp->edges[edge_idx].vertex[vertex_index]; + return *out_vert_idx < bsp->num_vertices; +} + +static inline bool face_in_frustum(const pxl8_bsp* bsp, u32 face_id, const pxl8_frustum* frustum) { + const pxl8_bsp_face* face = &bsp->faces[face_id]; + 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) { + const pxl8_bsp_face* face = &bsp->faces[face_id]; + if (face->num_edges < 3) return; + + pxl8_vec3 normal = {0, 1, 0}; + if (face->plane_id < bsp->num_planes) { + normal = bsp->planes[face->plane_id].normal; + if (face->side) { + normal.x = -normal.x; + normal.y = -normal.y; + normal.z = -normal.z; + } + } + + const pxl8_gfx_material* material = NULL; + f32 tex_scale = 64.0f; + if (state && face->material_id < state->num_materials) { + material = &state->materials[face->material_id]; + } + + pxl8_vec3 u_axis = {1.0f, 0.0f, 0.0f}; + pxl8_vec3 v_axis = {0.0f, 0.0f, 1.0f}; + f32 u_offset = 0.0f, v_offset = 0.0f; + if (material) { + u_offset = material->u_offset; + v_offset = material->v_offset; + } + f32 abs_ny = normal.y < 0 ? -normal.y : normal.y; + if (abs_ny > 0.7f) { + u_axis = (pxl8_vec3){1.0f, 0.0f, 0.0f}; + v_axis = (pxl8_vec3){0.0f, 0.0f, 1.0f}; + } else { + f32 abs_nx = normal.x < 0 ? -normal.x : normal.x; + f32 abs_nz = normal.z < 0 ? -normal.z : normal.z; + if (abs_nx > abs_nz) { + u_axis = (pxl8_vec3){0.0f, 0.0f, 1.0f}; + } else { + u_axis = (pxl8_vec3){1.0f, 0.0f, 0.0f}; + } + v_axis = (pxl8_vec3){0.0f, 1.0f, 0.0f}; + } + + u16 base_idx = (u16)mesh->vertex_count; + u32 num_verts = 0; + + for (u32 i = 0; i < face->num_edges && num_verts < 64; i++) { + u32 edge_i = face->side ? (face->num_edges - 1 - i) : i; + i32 surfedge_idx = face->first_edge + edge_i; + u32 vert_idx; + + if (!pxl8_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) { + continue; + } + + pxl8_vec3 pos = bsp->vertices[vert_idx].position; + + f32 u = (pxl8_vec3_dot(pos, u_axis) + u_offset) / tex_scale; + f32 v = (pxl8_vec3_dot(pos, v_axis) + v_offset) / tex_scale; + + u8 light = 255; + if (bsp->vertex_lights && vert_idx < bsp->num_vertex_lights) { + light = (bsp->vertex_lights[vert_idx] >> 24) & 0xFF; + } + + pxl8_vertex vtx = { + .position = pos, + .normal = normal, + .u = u, + .v = v, + .color = 15, + .light = light, + }; + pxl8_mesh_push_vertex(mesh, vtx); + num_verts++; + } + + if (num_verts < 3) return; + + for (u32 i = 1; i < num_verts - 1; i++) { + pxl8_mesh_push_triangle(mesh, base_idx, base_idx + i, base_idx + i + 1); + } +} + +pxl8_bsp_render_state* pxl8_bsp_render_state_create(u32 num_faces) { + pxl8_bsp_render_state* state = pxl8_calloc(1, sizeof(pxl8_bsp_render_state)); + if (!state) return NULL; + + state->num_faces = num_faces; + if (num_faces > 0) { + state->render_face_flags = pxl8_calloc(num_faces, 1); + if (!state->render_face_flags) { + pxl8_free(state); + return NULL; + } + } + + return state; +} + +void pxl8_bsp_render_state_destroy(pxl8_bsp_render_state* state) { + if (!state) return; + pxl8_free(state->materials); + pxl8_free(state->render_face_flags); + pxl8_free(state); +} + +void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_gfx_material* material) { + if (!gfx || !bsp || face_id >= bsp->num_faces || !material) return; + + pxl8_mesh* mesh = pxl8_mesh_create(64, 192); + if (!mesh) return; + + collect_face_to_mesh(bsp, NULL, face_id, mesh); + + 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 (!bsp->cell_portals || bsp->num_cell_portals == 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 (!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); + } + } + + 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); +} + +void pxl8_bsp_set_material(pxl8_bsp_render_state* state, u16 material_id, const pxl8_gfx_material* material) { + if (!state || !material) return; + + if (material_id >= state->num_materials) { + u32 new_count = material_id + 1; + pxl8_gfx_material* new_materials = pxl8_realloc(state->materials, new_count * sizeof(pxl8_gfx_material)); + if (!new_materials) return; + + for (u32 i = state->num_materials; i < new_count; i++) { + memset(&new_materials[i], 0, sizeof(pxl8_gfx_material)); + new_materials[i].u_axis = (pxl8_vec3){1.0f, 0.0f, 0.0f}; + if (i == 0 || i == 2) { + new_materials[i].v_axis = (pxl8_vec3){0.0f, 0.0f, 1.0f}; + } else { + new_materials[i].v_axis = (pxl8_vec3){0.0f, 1.0f, 0.0f}; + } + } + + state->materials = new_materials; + state->num_materials = new_count; + } + + pxl8_vec3 u_axis = state->materials[material_id].u_axis; + pxl8_vec3 v_axis = state->materials[material_id].v_axis; + f32 u_offset = state->materials[material_id].u_offset; + f32 v_offset = state->materials[material_id].v_offset; + + state->materials[material_id] = *material; + + state->materials[material_id].u_axis = u_axis; + state->materials[material_id].v_axis = v_axis; + state->materials[material_id].u_offset = u_offset; + state->materials[material_id].v_offset = v_offset; +} + +void pxl8_bsp_set_wireframe(pxl8_bsp_render_state* state, bool wireframe) { + if (!state || !state->materials) return; + for (u32 i = 0; i < state->num_materials; i++) { + state->materials[i].wireframe = wireframe; + } +} diff --git a/src/bsp/pxl8_bsp_render.h b/src/bsp/pxl8_bsp_render.h new file mode 100644 index 0000000..04b1cb3 --- /dev/null +++ b/src/bsp/pxl8_bsp_render.h @@ -0,0 +1,30 @@ +#pragma once + +#include "pxl8_bsp.h" +#include "pxl8_gfx.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct pxl8_bsp_render_state { + pxl8_gfx_material* materials; + u8* render_face_flags; + u32 num_materials; + u32 num_faces; +} pxl8_bsp_render_state; + +pxl8_bsp_render_state* pxl8_bsp_render_state_create(u32 num_faces); +void pxl8_bsp_render_state_destroy(pxl8_bsp_render_state* state); + +void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, + pxl8_bsp_render_state* state, pxl8_vec3 camera_pos); +void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, + const pxl8_gfx_material* material); +void pxl8_bsp_set_material(pxl8_bsp_render_state* state, u16 material_id, + const pxl8_gfx_material* material); +void pxl8_bsp_set_wireframe(pxl8_bsp_render_state* state, bool wireframe); + +#ifdef __cplusplus +} +#endif diff --git a/src/core/pxl8.c b/src/core/pxl8.c index d89cff1..642bc0e 100644 --- a/src/core/pxl8.c +++ b/src/core/pxl8.c @@ -9,6 +9,7 @@ #include #include +#include "pxl8_ase.h" #include "pxl8_game.h" #include "pxl8_hal.h" #include "pxl8_log.h" @@ -19,6 +20,7 @@ #include "pxl8_script.h" #include "pxl8_sfx.h" #include "pxl8_sys.h" +#include "pxl8_world.h" struct pxl8 { pxl8_cart* cart; @@ -83,6 +85,7 @@ static void pxl8_print_help(void) { printf("Other commands:\n"); printf(" pxl8 pack Pack folder into cart file\n"); printf(" pxl8 bundle Bundle cart into executable\n"); + printf(" pxl8 remap-ase Remap ASE to palette by hue/lum\n"); printf(" pxl8 help Show this help\n"); } @@ -93,9 +96,11 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { const char* script_arg = NULL; bool bundle_mode = false; bool pack_mode = false; + bool remap_palette_mode = false; bool run_mode = false; const char* pack_input = NULL; const char* pack_output = NULL; + const char* remap_palette = NULL; bool has_embedded = pxl8_cart_has_embedded(argv[0]); @@ -125,12 +130,22 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { pxl8_error("pack requires "); return PXL8_ERROR_INVALID_ARGUMENT; } + } else if (strcmp(argv[i], "remap-ase") == 0) { + remap_palette_mode = true; + if (i + 3 < argc) { + pack_input = argv[++i]; + pack_output = argv[++i]; + remap_palette = argv[++i]; + } else { + pxl8_error("remap-ase requires "); + return PXL8_ERROR_INVALID_ARGUMENT; + } } else if (!script_arg) { script_arg = argv[i]; } } - if (!run_mode && !bundle_mode && !pack_mode) { + if (!run_mode && !bundle_mode && !pack_mode && !remap_palette_mode) { run_mode = true; } @@ -151,6 +166,22 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { return result; } + if (remap_palette_mode) { + u32 palette[256]; + u32 palette_count = 0; + pxl8_result result = pxl8_ase_load_palette(remap_palette, palette, &palette_count); + if (result != PXL8_OK) { + pxl8_error("failed to load palette: %s", remap_palette); + return result; + } + pxl8_ase_remap_config config = { + .palette = palette, + .palette_count = palette_count, + .hue_tolerance = 0.08f + }; + return pxl8_ase_remap(pack_input, pack_output, &config); + } + pxl8_info("Starting up"); game->script = pxl8_script_create(game->repl_mode); @@ -246,7 +277,8 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { pxl8_net_config net_cfg = { .address = "127.0.0.1", .port = 7777 }; game->net = pxl8_net_create(&net_cfg); if (game->net) { - pxl8_net_set_chunk_cache(game->net, pxl8_world_chunk_cache(game->world)); + pxl8_net_set_chunk_cache(game->net, pxl8_world_get_chunk_cache(game->world)); + pxl8_net_set_world(game->net, game->world); pxl8_net_connect(game->net); } @@ -280,6 +312,15 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { game->last_time = sys->hal->get_ticks(); game->running = true; +#ifdef PXL8_ASYNC_THREADS + if (game->net) { + pxl8_net_start_thread(game->net); + } + if (game->world) { + pxl8_world_start_sim_thread(game->world, game->net); + } +#endif + return PXL8_OK; } @@ -351,13 +392,27 @@ pxl8_result pxl8_update(pxl8* sys) { } } +#ifdef PXL8_ASYNC_THREADS + if (game->world && (pxl8_world_local_player(game->world))) { + pxl8_input_msg msg = {0}; + msg.move_x = (pxl8_key_down(&game->input, "d") ? 1.0f : 0.0f) - (pxl8_key_down(&game->input, "a") ? 1.0f : 0.0f); + msg.move_y = (pxl8_key_down(&game->input, "w") ? 1.0f : 0.0f) - (pxl8_key_down(&game->input, "s") ? 1.0f : 0.0f); + msg.look_dx = (f32)pxl8_mouse_dx(&game->input); + msg.look_dy = (f32)pxl8_mouse_dy(&game->input); + msg.buttons = pxl8_key_down(&game->input, "space") ? 1 : 0; + pxl8_world_push_input(game->world, &msg); + } + pxl8_net_update(game->net, dt); +#else if (game->net) { while (pxl8_net_poll(game->net)) {} pxl8_net_update(game->net, dt); pxl8_world_sync(game->world, game->net); } - pxl8_world_update(game->world, dt); + pxl8_world_update(game->world, &game->input, dt); +#endif + pxl8_gfx_update(game->gfx, dt); pxl8_sfx_mixer_process(game->mixer); @@ -430,6 +485,15 @@ void pxl8_quit(pxl8* sys) { pxl8_info("Shutting down"); +#ifdef PXL8_ASYNC_THREADS + if (game->world) { + pxl8_world_stop_sim_thread(game->world); + } + if (game->net) { + pxl8_net_stop_thread(game->net); + } +#endif + if (sys->cart) { pxl8_cart_unmount(sys->cart); } @@ -499,6 +563,9 @@ void pxl8_set_relative_mouse_mode(pxl8* sys, bool enabled) { sys->hal->set_relative_mouse_mode(sys->platform_data, enabled); if (sys->game) { sys->game->input.mouse_relative_mode = enabled; +#ifdef PXL8_ASYNC_THREADS + pxl8_world_pause_sim(sys->game->world, !enabled); +#endif } } diff --git a/src/core/pxl8_queue.h b/src/core/pxl8_queue.h new file mode 100644 index 0000000..f2206d5 --- /dev/null +++ b/src/core/pxl8_queue.h @@ -0,0 +1,53 @@ +#pragma once + +#include + +#include "pxl8_types.h" + +#define PXL8_QUEUE_CAPACITY 256 + +typedef struct pxl8_queue { + void* items[PXL8_QUEUE_CAPACITY]; + atomic_uint write_idx; + atomic_uint read_idx; +} pxl8_queue; + +static inline void pxl8_queue_init(pxl8_queue* q) { + atomic_store(&q->write_idx, 0); + atomic_store(&q->read_idx, 0); + for (u32 i = 0; i < PXL8_QUEUE_CAPACITY; i++) { + q->items[i] = NULL; + } +} + +static inline bool pxl8_queue_push(pxl8_queue* q, void* item) { + u32 w = atomic_load_explicit(&q->write_idx, memory_order_relaxed); + u32 next = (w + 1) % PXL8_QUEUE_CAPACITY; + if (next == atomic_load_explicit(&q->read_idx, memory_order_acquire)) { + return false; + } + q->items[w] = item; + atomic_store_explicit(&q->write_idx, next, memory_order_release); + return true; +} + +static inline void* pxl8_queue_pop(pxl8_queue* q) { + u32 r = atomic_load_explicit(&q->read_idx, memory_order_relaxed); + if (r == atomic_load_explicit(&q->write_idx, memory_order_acquire)) { + return NULL; + } + void* item = q->items[r]; + atomic_store_explicit(&q->read_idx, (r + 1) % PXL8_QUEUE_CAPACITY, memory_order_release); + return item; +} + +static inline bool pxl8_queue_empty(const pxl8_queue* q) { + return atomic_load_explicit(&((pxl8_queue*)q)->read_idx, memory_order_acquire) == + atomic_load_explicit(&((pxl8_queue*)q)->write_idx, memory_order_acquire); +} + +static inline u32 pxl8_queue_count(const pxl8_queue* q) { + u32 w = atomic_load_explicit(&((pxl8_queue*)q)->write_idx, memory_order_acquire); + u32 r = atomic_load_explicit(&((pxl8_queue*)q)->read_idx, memory_order_acquire); + return (w >= r) ? (w - r) : (PXL8_QUEUE_CAPACITY - r + w); +} diff --git a/src/core/pxl8_types.h b/src/core/pxl8_types.h index 437ebce..761afea 100644 --- a/src/core/pxl8_types.h +++ b/src/core/pxl8_types.h @@ -64,6 +64,7 @@ typedef enum pxl8_result { PXL8_ERROR_INVALID_COORDINATE, PXL8_ERROR_INVALID_FORMAT, PXL8_ERROR_INVALID_SIZE, + PXL8_ERROR_NOT_CONNECTED, PXL8_ERROR_NOT_INITIALIZED, PXL8_ERROR_NULL_POINTER, PXL8_ERROR_OUT_OF_MEMORY, diff --git a/src/gfx/pxl8_color.h b/src/gfx/pxl8_color.h index 285a147..f159724 100644 --- a/src/gfx/pxl8_color.h +++ b/src/gfx/pxl8_color.h @@ -6,45 +6,6 @@ static inline i32 pxl8_bytes_per_pixel(pxl8_pixel_mode mode) { return (i32)mode; } -static inline u16 pxl8_rgb565_pack(u8 r, u8 g, u8 b) { - return ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); -} - -static inline void pxl8_rgb565_unpack(u16 color, u8* r, u8* g, u8* b) { - *r = (color >> 11) << 3; - *g = ((color >> 5) & 0x3F) << 2; - *b = (color & 0x1F) << 3; -} - -static inline u16 pxl8_rgba32_to_rgb565(u32 rgba) { - return pxl8_rgb565_pack(rgba & 0xFF, (rgba >> 8) & 0xFF, (rgba >> 16) & 0xFF); -} - -static inline u32 pxl8_rgb565_to_rgba32(u16 color) { - u8 r, g, b; - pxl8_rgb565_unpack(color, &r, &g, &b); - return r | ((u32)g << 8) | ((u32)b << 16) | 0xFF000000; -} - -static inline void pxl8_rgba32_unpack(u32 color, u8* r, u8* g, u8* b, u8* a) { - *r = color & 0xFF; - *g = (color >> 8) & 0xFF; - *b = (color >> 16) & 0xFF; - *a = (color >> 24) & 0xFF; -} - -static inline u32 pxl8_rgba32_pack(u8 r, u8 g, u8 b, u8 a) { - return r | ((u32)g << 8) | ((u32)b << 16) | ((u32)a << 24); -} - -static inline u32 pxl8_color_to_rgba(u32 abgr) { - u8 r = abgr & 0xFF; - u8 g = (abgr >> 8) & 0xFF; - u8 b = (abgr >> 16) & 0xFF; - u8 a = (abgr >> 24) & 0xFF; - return ((u32)r << 24) | ((u32)g << 16) | ((u32)b << 8) | a; -} - static inline u32 pxl8_color_from_rgba(u32 rgba) { u8 r = (rgba >> 24) & 0xFF; u8 g = (rgba >> 16) & 0xFF; @@ -57,6 +18,51 @@ static inline u8 pxl8_color_lerp_channel(u8 c1, u8 c2, f32 t) { return c1 + (i32)((c2 - c1) * t); } +static inline f32 pxl8_color_hue(u32 color) { + u8 r = color & 0xFF; + u8 g = (color >> 8) & 0xFF; + u8 b = (color >> 16) & 0xFF; + u8 max = r > g ? (r > b ? r : b) : (g > b ? g : b); + u8 min = r < g ? (r < b ? r : b) : (g < b ? g : b); + if (max == min) return 0.0f; + f32 d = (f32)(max - min); + f32 h; + if (max == r) h = (f32)(g - b) / d + (g < b ? 6.0f : 0.0f); + else if (max == g) h = (f32)(b - r) / d + 2.0f; + else h = (f32)(r - g) / d + 4.0f; + return h / 6.0f; +} + +static inline f32 pxl8_color_hue_diff(f32 h1, f32 h2) { + f32 d = h1 > h2 ? h1 - h2 : h2 - h1; + return d > 0.5f ? 1.0f - d : d; +} + +static inline f32 pxl8_color_luminance(u32 color) { + u8 r = color & 0xFF; + u8 g = (color >> 8) & 0xFF; + u8 b = (color >> 16) & 0xFF; + return 0.299f * r + 0.587f * g + 0.114f * b; +} + +static inline f32 pxl8_color_saturation(u32 color) { + u8 r = color & 0xFF; + u8 g = (color >> 8) & 0xFF; + u8 b = (color >> 16) & 0xFF; + u8 max = r > g ? (r > b ? r : b) : (g > b ? g : b); + u8 min = r < g ? (r < b ? r : b) : (g < b ? g : b); + if (max == 0) return 0.0f; + return (f32)(max - min) / (f32)max; +} + +static inline u32 pxl8_color_to_rgba(u32 abgr) { + u8 r = abgr & 0xFF; + u8 g = (abgr >> 8) & 0xFF; + u8 b = (abgr >> 16) & 0xFF; + u8 a = (abgr >> 24) & 0xFF; + return ((u32)r << 24) | ((u32)g << 16) | ((u32)b << 8) | a; +} + static inline u8 pxl8_rgb332_pack(u8 r, u8 g, u8 b) { return ((r >> 5) << 5) | ((g >> 5) << 2) | (b >> 6); } @@ -69,3 +75,34 @@ static inline void pxl8_rgb332_unpack(u8 c, u8* r, u8* g, u8* b) { *g = (gi << 5) | (gi << 2) | (gi >> 1); *b = (bi << 6) | (bi << 4) | (bi << 2) | bi; } + +static inline u16 pxl8_rgb565_pack(u8 r, u8 g, u8 b) { + return ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); +} + +static inline void pxl8_rgb565_unpack(u16 color, u8* r, u8* g, u8* b) { + *r = (color >> 11) << 3; + *g = ((color >> 5) & 0x3F) << 2; + *b = (color & 0x1F) << 3; +} + +static inline u32 pxl8_rgb565_to_rgba32(u16 color) { + u8 r, g, b; + pxl8_rgb565_unpack(color, &r, &g, &b); + return r | ((u32)g << 8) | ((u32)b << 16) | 0xFF000000; +} + +static inline u32 pxl8_rgba32_pack(u8 r, u8 g, u8 b, u8 a) { + return r | ((u32)g << 8) | ((u32)b << 16) | ((u32)a << 24); +} + +static inline u16 pxl8_rgba32_to_rgb565(u32 rgba) { + return pxl8_rgb565_pack(rgba & 0xFF, (rgba >> 8) & 0xFF, (rgba >> 16) & 0xFF); +} + +static inline void pxl8_rgba32_unpack(u32 color, u8* r, u8* g, u8* b, u8* a) { + *r = color & 0xFF; + *g = (color >> 8) & 0xFF; + *b = (color >> 16) & 0xFF; + *a = (color >> 24) & 0xFF; +} diff --git a/src/gfx/pxl8_cpu.c b/src/gfx/pxl8_cpu.c index a43d494..2989d42 100644 --- a/src/gfx/pxl8_cpu.c +++ b/src/gfx/pxl8_cpu.c @@ -1,4 +1,7 @@ +#include "pxl8_atlas.h" +#include "pxl8_bsp.h" #include "pxl8_cpu.h" +#include "pxl8_log.h" #include "pxl8_mem.h" #include @@ -13,13 +16,6 @@ struct pxl8_cpu_render_target { u32* light_accum; }; -static inline u16 depth_to_u16(f32 z) { - f32 d = (z + 1.0f) * 32767.5f; - if (d < 0.0f) d = 0.0f; - if (d > 65535.0f) d = 65535.0f; - return (u16)d; -} - static inline f32 calc_light_intensity(const pxl8_light* light, pxl8_vec3 world_pos, pxl8_vec3 normal) { pxl8_vec3 to_light = pxl8_vec3_sub(light->position, world_pos); f32 dist_sq = pxl8_vec3_dot(to_light, to_light); @@ -29,9 +25,10 @@ static inline f32 calc_light_intensity(const pxl8_light* light, pxl8_vec3 world_ } f32 intensity_norm = light->intensity * (1.0f / 255.0f); + f32 falloff = 1.0f - dist_sq * light->inv_radius_sq; if (dist_sq < 0.001f) { - return intensity_norm; + return falloff * intensity_norm; } f32 inv_dist = pxl8_fast_inv_sqrt(dist_sq); @@ -42,7 +39,6 @@ static inline f32 calc_light_intensity(const pxl8_light* light, pxl8_vec3 world_ return 0.0f; } - f32 falloff = 1.0f - dist_sq * light->inv_radius_sq; return n_dot_l * falloff * intensity_norm; } @@ -125,6 +121,9 @@ struct pxl8_cpu_backend { pxl8_mat4 mvp; u32* output; u32 output_size; + u32 light_tint; + f32 light_depth; + i32 light_leaf; }; static inline void clip_line_2d(i32* x0, i32* y0, i32* x1, i32* y1, i32 w, i32 h, bool* visible) { @@ -217,6 +216,23 @@ void pxl8_cpu_begin_frame(pxl8_cpu_backend* cpu, const pxl8_3d_frame* frame) { if (!cpu || !frame) return; cpu->frame = *frame; cpu->mvp = pxl8_mat4_multiply(frame->projection, frame->view); + + cpu->light_tint = 0; + cpu->light_depth = 1.0f; + cpu->light_leaf = -1; + if (frame->lights_count > 0) { + const pxl8_light* light = &frame->lights[0]; + cpu->light_tint = light->r | (light->g << 8) | (light->b << 16); + + pxl8_vec4 light_clip = pxl8_mat4_multiply_vec4(cpu->mvp, (pxl8_vec4){light->position.x, light->position.y, light->position.z, 1.0f}); + if (light_clip.w > 0.001f) { + cpu->light_depth = light_clip.z / light_clip.w; + } + + if (frame->bsp) { + cpu->light_leaf = pxl8_bsp_find_leaf(frame->bsp, light->position); + } + } } void pxl8_cpu_end_frame(pxl8_cpu_backend* cpu) { @@ -392,44 +408,8 @@ typedef struct { f32 u, v; u8 color; u8 light; - u32 light_color; } vertex_output; -static inline u32 blend_tri_light_color(u32 lc0, u32 lc1, u32 lc2) { - u32 a0 = (lc0 >> 24) & 0xFF; - u32 a1 = (lc1 >> 24) & 0xFF; - u32 a2 = (lc2 >> 24) & 0xFF; - u32 total_a = a0 + a1 + a2; - if (total_a == 0) return 0; - - u32 r = ((lc0 & 0xFF) * a0 + (lc1 & 0xFF) * a1 + (lc2 & 0xFF) * a2) / total_a; - u32 g = (((lc0 >> 8) & 0xFF) * a0 + ((lc1 >> 8) & 0xFF) * a1 + ((lc2 >> 8) & 0xFF) * a2) / total_a; - u32 b = (((lc0 >> 16) & 0xFF) * a0 + ((lc1 >> 16) & 0xFF) * a1 + ((lc2 >> 16) & 0xFF) * a2) / total_a; - u32 a = total_a / 3; - if (a > 255) a = 255; - - return r | (g << 8) | (b << 16) | (a << 24); -} - -static inline u32 lerp_light_color(u32 a, u32 b, f32 t) { - u32 ar = a & 0xFF; - u32 ag = (a >> 8) & 0xFF; - u32 ab = (a >> 16) & 0xFF; - u32 aa = (a >> 24) & 0xFF; - - u32 br = b & 0xFF; - u32 bg = (b >> 8) & 0xFF; - u32 bb = (b >> 16) & 0xFF; - u32 ba = (b >> 24) & 0xFF; - - u32 or = ar + (u32)((i32)(br - ar) * t); - u32 og = ag + (u32)((i32)(bg - ag) * t); - u32 ob = ab + (u32)((i32)(bb - ab) * t); - u32 oa = aa + (u32)((i32)(ba - aa) * t); - - return or | (og << 8) | (ob << 16) | (oa << 24); -} - static vertex_output lerp_vertex(const vertex_output* a, const vertex_output* b, f32 t) { vertex_output out; out.clip_pos.x = a->clip_pos.x + (b->clip_pos.x - a->clip_pos.x) * t; @@ -446,7 +426,6 @@ static vertex_output lerp_vertex(const vertex_output* a, const vertex_output* b, out.v = a->v + (b->v - a->v) * t; out.color = t < 0.5f ? a->color : b->color; out.light = (u8)(a->light + (b->light - a->light) * t); - out.light_color = lerp_light_color(a->light_color, b->light_color, t); return out; } @@ -497,14 +476,14 @@ static i32 clip_triangle_near( } typedef struct { - i32 x0, y0, x1, y1, x2, y2; - f32 z0, z1, z2; - f32 w0_recip, w1_recip, w2_recip; - f32 u0_w, v0_w, u1_w, v1_w, u2_w, v2_w; - f32 l0_w, l1_w, l2_w; - u32 lc0, lc1, lc2; - f32 c0_w, c1_w, c2_w; - i32 y_start, y_end, total_height; + pxl8_vec3 p0, p1, p2; + pxl8_vec3 w_recip; + pxl8_vec3 u_w, v_w; + pxl8_vec3 l_w; + pxl8_vec3 c_w; + pxl8_vec3 world0_w, world1_w, world2_w; + pxl8_vec3 normal; + i32 y_start, y_end; f32 inv_total; u32 target_width, target_height; } tri_setup; @@ -517,73 +496,71 @@ static bool setup_triangle( f32 hw = (f32)width * 0.5f; f32 hh = (f32)height * 0.5f; - setup->x0 = (i32)(hw + vo0->clip_pos.x / vo0->clip_pos.w * hw); - setup->y0 = (i32)(hh - vo0->clip_pos.y / vo0->clip_pos.w * hh); - setup->z0 = vo0->clip_pos.z / vo0->clip_pos.w; + setup->p0.x = hw + vo0->clip_pos.x / vo0->clip_pos.w * hw; + setup->p0.y = hh - vo0->clip_pos.y / vo0->clip_pos.w * hh; + setup->p0.z = vo0->clip_pos.z / vo0->clip_pos.w; - setup->x1 = (i32)(hw + vo1->clip_pos.x / vo1->clip_pos.w * hw); - setup->y1 = (i32)(hh - vo1->clip_pos.y / vo1->clip_pos.w * hh); - setup->z1 = vo1->clip_pos.z / vo1->clip_pos.w; + setup->p1.x = hw + vo1->clip_pos.x / vo1->clip_pos.w * hw; + setup->p1.y = hh - vo1->clip_pos.y / vo1->clip_pos.w * hh; + setup->p1.z = vo1->clip_pos.z / vo1->clip_pos.w; - setup->x2 = (i32)(hw + vo2->clip_pos.x / vo2->clip_pos.w * hw); - setup->y2 = (i32)(hh - vo2->clip_pos.y / vo2->clip_pos.w * hh); - setup->z2 = vo2->clip_pos.z / vo2->clip_pos.w; + setup->p2.x = hw + vo2->clip_pos.x / vo2->clip_pos.w * hw; + setup->p2.y = hh - vo2->clip_pos.y / vo2->clip_pos.w * hh; + setup->p2.z = vo2->clip_pos.z / vo2->clip_pos.w; - i32 cross = (setup->x1 - setup->x0) * (setup->y2 - setup->y0) - - (setup->y1 - setup->y0) * (setup->x2 - setup->x0); - if (!double_sided && cross >= 0) return false; + f32 cross = (setup->p1.x - setup->p0.x) * (setup->p2.y - setup->p0.y) - + (setup->p1.y - setup->p0.y) * (setup->p2.x - setup->p0.x); + if (!double_sided && cross >= 0.0f) return false; const vertex_output* sorted[3] = {vo0, vo1, vo2}; - if (setup->y0 > setup->y1) { - i32 t = setup->x0; setup->x0 = setup->x1; setup->x1 = t; - t = setup->y0; setup->y0 = setup->y1; setup->y1 = t; - f32 tz = setup->z0; setup->z0 = setup->z1; setup->z1 = tz; + if (setup->p0.y > setup->p1.y) { + pxl8_vec3 t = setup->p0; setup->p0 = setup->p1; setup->p1 = t; const vertex_output* tv = sorted[0]; sorted[0] = sorted[1]; sorted[1] = tv; } - if (setup->y0 > setup->y2) { - i32 t = setup->x0; setup->x0 = setup->x2; setup->x2 = t; - t = setup->y0; setup->y0 = setup->y2; setup->y2 = t; - f32 tz = setup->z0; setup->z0 = setup->z2; setup->z2 = tz; + if (setup->p0.y > setup->p2.y) { + pxl8_vec3 t = setup->p0; setup->p0 = setup->p2; setup->p2 = t; const vertex_output* tv = sorted[0]; sorted[0] = sorted[2]; sorted[2] = tv; } - if (setup->y1 > setup->y2) { - i32 t = setup->x1; setup->x1 = setup->x2; setup->x2 = t; - t = setup->y1; setup->y1 = setup->y2; setup->y2 = t; - f32 tz = setup->z1; setup->z1 = setup->z2; setup->z2 = tz; + if (setup->p1.y > setup->p2.y) { + pxl8_vec3 t = setup->p1; setup->p1 = setup->p2; setup->p2 = t; const vertex_output* tv = sorted[1]; sorted[1] = sorted[2]; sorted[2] = tv; } - setup->total_height = setup->y2 - setup->y0; - if (setup->total_height == 0) return false; + f32 total_height = setup->p2.y - setup->p0.y; + if (total_height < 1.0f) return false; - setup->y_start = setup->y0 < 0 ? 0 : setup->y0; - setup->y_end = setup->y2 >= (i32)height ? (i32)height - 1 : setup->y2; + i32 y0_int = (i32)floorf(setup->p0.y); + i32 y2_int = (i32)ceilf(setup->p2.y) - 1; + setup->y_start = y0_int < 0 ? 0 : y0_int; + setup->y_end = y2_int >= (i32)height ? (i32)height - 1 : y2_int; - setup->w0_recip = 1.0f / sorted[0]->clip_pos.w; - setup->w1_recip = 1.0f / sorted[1]->clip_pos.w; - setup->w2_recip = 1.0f / sorted[2]->clip_pos.w; + setup->w_recip.x = 1.0f / sorted[0]->clip_pos.w; + setup->w_recip.y = 1.0f / sorted[1]->clip_pos.w; + setup->w_recip.z = 1.0f / sorted[2]->clip_pos.w; - setup->u0_w = sorted[0]->u * setup->w0_recip; - setup->v0_w = sorted[0]->v * setup->w0_recip; - setup->u1_w = sorted[1]->u * setup->w1_recip; - setup->v1_w = sorted[1]->v * setup->w1_recip; - setup->u2_w = sorted[2]->u * setup->w2_recip; - setup->v2_w = sorted[2]->v * setup->w2_recip; + setup->u_w.x = sorted[0]->u * setup->w_recip.x; + setup->v_w.x = sorted[0]->v * setup->w_recip.x; + setup->u_w.y = sorted[1]->u * setup->w_recip.y; + setup->v_w.y = sorted[1]->v * setup->w_recip.y; + setup->u_w.z = sorted[2]->u * setup->w_recip.z; + setup->v_w.z = sorted[2]->v * setup->w_recip.z; - setup->l0_w = sorted[0]->light * setup->w0_recip; - setup->l1_w = sorted[1]->light * setup->w1_recip; - setup->l2_w = sorted[2]->light * setup->w2_recip; + setup->l_w.x = sorted[0]->light * setup->w_recip.x; + setup->l_w.y = sorted[1]->light * setup->w_recip.y; + setup->l_w.z = sorted[2]->light * setup->w_recip.z; - setup->lc0 = sorted[0]->light_color; - setup->lc1 = sorted[1]->light_color; - setup->lc2 = sorted[2]->light_color; + setup->c_w.x = (f32)sorted[0]->color * setup->w_recip.x; + setup->c_w.y = (f32)sorted[1]->color * setup->w_recip.y; + setup->c_w.z = (f32)sorted[2]->color * setup->w_recip.z; - setup->c0_w = (f32)sorted[0]->color * setup->w0_recip; - setup->c1_w = (f32)sorted[1]->color * setup->w1_recip; - setup->c2_w = (f32)sorted[2]->color * setup->w2_recip; + setup->world0_w = pxl8_vec3_scale(sorted[0]->world_pos, setup->w_recip.x); + setup->world1_w = pxl8_vec3_scale(sorted[1]->world_pos, setup->w_recip.y); + setup->world2_w = pxl8_vec3_scale(sorted[2]->world_pos, setup->w_recip.z); - setup->inv_total = 1.0f / (f32)setup->total_height; + setup->normal = sorted[0]->normal; + + setup->inv_total = 1.0f / total_height; setup->target_width = width; setup->target_height = height; @@ -597,149 +574,224 @@ static void rasterize_triangle_opaque( ) { pxl8_cpu_render_target* render_target = cpu->current_target; u32* light_accum = render_target->light_accum; - u32 tri_light_color = light_accum ? blend_tri_light_color(setup->lc0, setup->lc1, setup->lc2) : 0; + + bool facing_light = false; + f32 light_radius_sq = 0.0f; + f32 light_inv_radius_sq = 0.0f; + pxl8_vec3 light_pos = {0}; + if (cpu->frame.lights_count > 0) { + const pxl8_light* light = &cpu->frame.lights[0]; + light_pos = light->position; + light_radius_sq = light->radius_sq; + light_inv_radius_sq = light->inv_radius_sq; + + f32 avg_w = (setup->w_recip.x + setup->w_recip.y + setup->w_recip.z) / 3.0f; + f32 inv_w = (avg_w > 0.0001f) ? 1.0f / avg_w : 1.0f; + f32 cx = (setup->world0_w.x + setup->world1_w.x + setup->world2_w.x) / 3.0f * inv_w; + f32 cy = (setup->world0_w.y + setup->world1_w.y + setup->world2_w.y) / 3.0f * inv_w; + f32 cz = (setup->world0_w.z + setup->world1_w.z + setup->world2_w.z) / 3.0f * inv_w; + f32 to_light_x = light->position.x - cx; + f32 to_light_y = light->position.y - cy; + f32 to_light_z = light->position.z - cz; + f32 n_dot_l = setup->normal.x * to_light_x + setup->normal.y * to_light_y + setup->normal.z * to_light_z; + facing_light = n_dot_l > 0.0f; + + if (facing_light && setup->normal.y > 0.9f && cpu->frame.bsp && cpu->light_leaf >= 0) { + pxl8_vec3 centroid = {cx, cy, cz}; + i32 tri_leaf = pxl8_bsp_find_leaf(cpu->frame.bsp, centroid); + if (tri_leaf >= 0 && tri_leaf != cpu->light_leaf && + !pxl8_bsp_is_leaf_visible(cpu->frame.bsp, cpu->light_leaf, tri_leaf)) { + facing_light = false; + } + } + } + + f32 base_light = 0.25f + cpu->frame.uniforms.ambient * (1.0f / 255.0f); + base_light += cpu->frame.uniforms.celestial_intensity * 0.5f; + const pxl8_atlas_entry* tex_entry = textures ? pxl8_atlas_get_entry(textures, texture_id) : NULL; - u32 atlas_width = textures ? pxl8_atlas_get_width(textures) : 1; - f32 tex_w = tex_entry ? (f32)tex_entry->w : 1.0f; - f32 tex_h = tex_entry ? (f32)tex_entry->h : 1.0f; - i32 tex_stride = (i32)atlas_width; + i32 tex_w = tex_entry ? (i32)tex_entry->w : 1; + i32 tex_h = tex_entry ? (i32)tex_entry->h : 1; i32 tex_mask_w = tex_entry ? (i32)tex_entry->w - 1 : 0; i32 tex_mask_h = tex_entry ? (i32)tex_entry->h - 1 : 0; - u32 tex_base = tex_entry ? (u32)tex_entry->y * atlas_width + (u32)tex_entry->x : 0; - const u8* tex_pixels = textures ? pxl8_atlas_get_pixels(textures) : NULL; + u32 tex_base = tex_entry ? tex_entry->tiled_base : 0; + u8 tex_log2_w = tex_entry ? tex_entry->log2_w : 0; + const u8* tex_pixels = textures ? pxl8_atlas_get_pixels_tiled(textures) : NULL; for (i32 y = setup->y_start; y <= setup->y_end; y++) { - bool second_half = y > setup->y1 || setup->y1 == setup->y0; - i32 segment_height = second_half ? setup->y2 - setup->y1 : setup->y1 - setup->y0; - if (segment_height == 0) continue; + f32 yf = (f32)y + 0.5f; + f32 alpha = (yf - setup->p0.y) * setup->inv_total; - f32 alpha = (f32)(y - setup->y0) * setup->inv_total; - f32 beta = (f32)(y - (second_half ? setup->y1 : setup->y0)) / (f32)segment_height; + f32 ax = setup->p0.x + (setup->p2.x - setup->p0.x) * alpha; + f32 az = setup->p0.z + (setup->p2.z - setup->p0.z) * alpha; + f32 a_wr = setup->w_recip.x + (setup->w_recip.z - setup->w_recip.x) * alpha; + f32 a_uw = setup->u_w.x + (setup->u_w.z - setup->u_w.x) * alpha; + f32 a_vw = setup->v_w.x + (setup->v_w.z - setup->v_w.x) * alpha; + f32 a_wxw = setup->world0_w.x + (setup->world2_w.x - setup->world0_w.x) * alpha; + f32 a_wyw = setup->world0_w.y + (setup->world2_w.y - setup->world0_w.y) * alpha; + f32 a_wzw = setup->world0_w.z + (setup->world2_w.z - setup->world0_w.z) * alpha; - f32 ax = (f32)setup->x0 + (f32)(setup->x2 - setup->x0) * alpha; - f32 az = setup->z0 + (setup->z2 - setup->z0) * alpha; - f32 a_wr = setup->w0_recip + (setup->w2_recip - setup->w0_recip) * alpha; - f32 a_uw = setup->u0_w + (setup->u2_w - setup->u0_w) * alpha; - f32 a_vw = setup->v0_w + (setup->v2_w - setup->v0_w) * alpha; - f32 a_lw = setup->l0_w + (setup->l2_w - setup->l0_w) * alpha; + f32 bx, bz, b_wr, b_uw, b_vw, b_wxw, b_wyw, b_wzw; + bool second_half = yf > setup->p1.y || setup->p1.y == setup->p0.y; + f32 segment_height = second_half ? (setup->p2.y - setup->p1.y) : (setup->p1.y - setup->p0.y); + if (segment_height < 0.001f) segment_height = 0.001f; + + f32 beta = (yf - (second_half ? setup->p1.y : setup->p0.y)) / segment_height; + if (beta < 0.0f) beta = 0.0f; + if (beta > 1.0f) beta = 1.0f; - f32 bx, bz, b_wr, b_uw, b_vw, b_lw; if (second_half) { - bx = (f32)setup->x1 + (f32)(setup->x2 - setup->x1) * beta; - bz = setup->z1 + (setup->z2 - setup->z1) * beta; - b_wr = setup->w1_recip + (setup->w2_recip - setup->w1_recip) * beta; - b_uw = setup->u1_w + (setup->u2_w - setup->u1_w) * beta; - b_vw = setup->v1_w + (setup->v2_w - setup->v1_w) * beta; - b_lw = setup->l1_w + (setup->l2_w - setup->l1_w) * beta; + bx = setup->p1.x + (setup->p2.x - setup->p1.x) * beta; + bz = setup->p1.z + (setup->p2.z - setup->p1.z) * beta; + b_wr = setup->w_recip.y + (setup->w_recip.z - setup->w_recip.y) * beta; + b_uw = setup->u_w.y + (setup->u_w.z - setup->u_w.y) * beta; + b_vw = setup->v_w.y + (setup->v_w.z - setup->v_w.y) * beta; + b_wxw = setup->world1_w.x + (setup->world2_w.x - setup->world1_w.x) * beta; + b_wyw = setup->world1_w.y + (setup->world2_w.y - setup->world1_w.y) * beta; + b_wzw = setup->world1_w.z + (setup->world2_w.z - setup->world1_w.z) * beta; } else { - bx = (f32)setup->x0 + (f32)(setup->x1 - setup->x0) * beta; - bz = setup->z0 + (setup->z1 - setup->z0) * beta; - b_wr = setup->w0_recip + (setup->w1_recip - setup->w0_recip) * beta; - b_uw = setup->u0_w + (setup->u1_w - setup->u0_w) * beta; - b_vw = setup->v0_w + (setup->v1_w - setup->v0_w) * beta; - b_lw = setup->l0_w + (setup->l1_w - setup->l0_w) * beta; + bx = setup->p0.x + (setup->p1.x - setup->p0.x) * beta; + bz = setup->p0.z + (setup->p1.z - setup->p0.z) * beta; + b_wr = setup->w_recip.x + (setup->w_recip.y - setup->w_recip.x) * beta; + b_uw = setup->u_w.x + (setup->u_w.y - setup->u_w.x) * beta; + b_vw = setup->v_w.x + (setup->v_w.y - setup->v_w.x) * beta; + b_wxw = setup->world0_w.x + (setup->world1_w.x - setup->world0_w.x) * beta; + b_wyw = setup->world0_w.y + (setup->world1_w.y - setup->world0_w.y) * beta; + b_wzw = setup->world0_w.z + (setup->world1_w.z - setup->world0_w.z) * beta; } - f32 x_start_f, x_end_f, z_start, z_end; - f32 wr_start, wr_end, uw_start, uw_end, vw_start, vw_end, lw_start, lw_end; + f32 x_start_fp, x_end_fp, z_start, z_end; + f32 wr_start, wr_end, uw_start, uw_end, vw_start, vw_end; + f32 wxw_start, wxw_end, wyw_start, wyw_end, wzw_start, wzw_end; if (ax <= bx) { - x_start_f = ax; x_end_f = bx; z_start = az; z_end = bz; + x_start_fp = ax; x_end_fp = bx; z_start = az; z_end = bz; wr_start = a_wr; wr_end = b_wr; uw_start = a_uw; uw_end = b_uw; vw_start = a_vw; vw_end = b_vw; - lw_start = a_lw; lw_end = b_lw; + wxw_start = a_wxw; wxw_end = b_wxw; + wyw_start = a_wyw; wyw_end = b_wyw; + wzw_start = a_wzw; wzw_end = b_wzw; } else { - x_start_f = bx; x_end_f = ax; z_start = bz; z_end = az; + x_start_fp = bx; x_end_fp = ax; z_start = bz; z_end = az; wr_start = b_wr; wr_end = a_wr; uw_start = b_uw; uw_end = a_uw; vw_start = b_vw; vw_end = a_vw; - lw_start = b_lw; lw_end = a_lw; + wxw_start = b_wxw; wxw_end = a_wxw; + wyw_start = b_wyw; wyw_end = a_wyw; + wzw_start = b_wzw; wzw_end = a_wzw; } - i32 x_start_orig = (i32)(x_start_f + 0.5f); - i32 x_end_orig = (i32)(x_end_f + 0.5f) - 1; - i32 x_start = x_start_orig < 0 ? 0 : x_start_orig; - i32 x_end = x_end_orig >= (i32)setup->target_width ? (i32)setup->target_width - 1 : x_end_orig; + i32 x_start = (i32)floorf(x_start_fp); + i32 x_end = (i32)ceilf(x_end_fp) - 1; + if (x_start < 0) x_start = 0; + if (x_end >= (i32)setup->target_width) x_end = (i32)setup->target_width - 1; if (x_start > x_end) continue; - i32 width_orig = x_end_orig - x_start_orig; - if (width_orig < 1) width_orig = 1; - f32 inv_width = 1.0f / (f32)width_orig; + f32 span_width = x_end_fp - x_start_fp; + if (span_width < 1.0f) span_width = 1.0f; + f32 inv_width = 1.0f / span_width; f32 dz = (z_end - z_start) * inv_width; f32 dwr = (wr_end - wr_start) * inv_width; f32 duw = (uw_end - uw_start) * inv_width; f32 dvw = (vw_end - vw_start) * inv_width; - f32 d_lw = (lw_end - lw_start) * inv_width; + f32 dwxw = (wxw_end - wxw_start) * inv_width; + f32 dwyw = (wyw_end - wyw_start) * inv_width; + f32 dwzw = (wzw_end - wzw_start) * inv_width; - f32 skip = (f32)(x_start - x_start_orig); + f32 skip = (f32)x_start + 0.5f - x_start_fp; f32 z = z_start + dz * skip; f32 wr = wr_start + dwr * skip; f32 uw = uw_start + duw * skip; f32 vw = vw_start + dvw * skip; - f32 lw = lw_start + d_lw * skip; + f32 wxw = wxw_start + dwxw * skip; + f32 wyw = wyw_start + dwyw * skip; + f32 wzw = wzw_start + dwzw * skip; u32 row_start = (u32)y * render_target->width; u8* prow = render_target->framebuffer + row_start; u16* zrow = render_target->zbuffer + row_start; - const i32 SUBDIV = 32; + const i32 SUBDIV = 16; i32 x = x_start; while (x <= x_end) { i32 span_end = x + SUBDIV - 1; if (span_end > x_end) span_end = x_end; i32 span_len = span_end - x + 1; - f32 steps = (f32)span_len; f32 pw_start = 1.0f / wr; - f32 pw_end = 1.0f / (wr + dwr * steps); + f32 pw_end = 1.0f / (wr + dwr * (f32)span_len); f32 u_start = uw * pw_start; f32 v_start = vw * pw_start; - f32 u_end = (uw + duw * steps) * pw_end; - f32 v_end = (vw + dvw * steps) * pw_end; - f32 l_start = lw * pw_start; - f32 l_end = (lw + d_lw * steps) * pw_end; + f32 u_end = (uw + duw * (f32)span_len) * pw_end; + f32 v_end = (vw + dvw * (f32)span_len) * pw_end; - f32 fog_density = cpu->frame.uniforms.fog_density; - if (fog_density > 0.0f) { - f32 z_end_fog = z + dz * steps; - f32 t_start = (z + 1.0f) * 0.5f; - f32 t_end = (z_end_fog + 1.0f) * 0.5f; - if (t_start < 0.0f) t_start = 0.0f; - if (t_end < 0.0f) t_end = 0.0f; - f32 fog_start = t_start * t_start * fog_density; - f32 fog_end = t_end * t_end * fog_density; - if (fog_start > 1.0f) fog_start = 1.0f; - if (fog_end > 1.0f) fog_end = 1.0f; - l_start *= (1.0f - fog_start); - l_end *= (1.0f - fog_end); + f32 wx_start = wxw * pw_start; + f32 wy_start = wyw * pw_start; + f32 wz_start = wzw * pw_start; + f32 wx_end = (wxw + dwxw * (f32)span_len) * pw_end; + f32 wy_end = (wyw + dwyw * (f32)span_len) * pw_end; + f32 wz_end = (wzw + dwzw * (f32)span_len) * pw_end; + + f32 l_start = base_light; + f32 l_end = base_light; + f32 tint_start = 0.0f; + f32 tint_end = 0.0f; + + if (facing_light && cpu->frame.lights_count > 0) { + f32 dx_s = light_pos.x - wx_start; + f32 dy_s = light_pos.y - wy_start; + f32 dz_s = light_pos.z - wz_start; + f32 dist_sq_s = dx_s * dx_s + dy_s * dy_s + dz_s * dz_s; + if (dist_sq_s < light_radius_sq) { + f32 falloff = 1.0f - dist_sq_s * light_inv_radius_sq; + l_start += falloff; + tint_start = falloff; + } + + f32 dx_e = light_pos.x - wx_end; + f32 dy_e = light_pos.y - wy_end; + f32 dz_e = light_pos.z - wz_end; + f32 dist_sq_e = dx_e * dx_e + dy_e * dy_e + dz_e * dz_e; + if (dist_sq_e < light_radius_sq) { + f32 falloff = 1.0f - dist_sq_e * light_inv_radius_sq; + l_end += falloff; + tint_end = falloff; + } } - if (l_start > 255.0f) l_start = 255.0f; - if (l_end > 255.0f) l_end = 255.0f; + if (l_start > 1.0f) l_start = 1.0f; + if (l_end > 1.0f) l_end = 1.0f; + l_start *= 255.0f; + l_end *= 255.0f; f32 inv_span = span_len > 1 ? 1.0f / (f32)(span_len - 1) : 0.0f; f32 du = (u_end - u_start) * inv_span; f32 dv = (v_end - v_start) * inv_span; f32 dl = (l_end - l_start) * inv_span; + f32 dt = (tint_end - tint_start) * inv_span; - i32 u_fixed = (i32)(u_start * tex_w * 65536.0f); - i32 v_fixed = (i32)(v_start * tex_h * 65536.0f); - i32 du_fixed = (i32)(du * tex_w * 65536.0f); - i32 dv_fixed = (i32)(dv * tex_h * 65536.0f); + i32 u_fixed = (i32)(u_start * (f32)tex_w * 65536.0f); + i32 v_fixed = (i32)(v_start * (f32)tex_h * 65536.0f); + i32 du_fixed = (i32)(du * (f32)tex_w * 65536.0f); + i32 dv_fixed = (i32)(dv * (f32)tex_h * 65536.0f); f32 l_a = l_start; + f32 t_a = tint_start; f32 z_a = z; for (i32 px = x; px <= span_end; px++) { - u16 z16 = depth_to_u16(z_a); + f32 depth_norm = (z_a + 1.0f) * 0.5f; + if (depth_norm < 0.0f) depth_norm = 0.0f; + if (depth_norm > 1.0f) depth_norm = 1.0f; + u16 z16 = (u16)(depth_norm * 65535.0f); if (z16 < zrow[px]) { i32 tx = (u_fixed >> 16) & tex_mask_w; i32 ty = (v_fixed >> 16) & tex_mask_h; - u8 tex_idx = tex_pixels ? tex_pixels[tex_base + (u32)ty * (u32)tex_stride + (u32)tx] : 1; + u8 tex_idx = tex_pixels ? tex_pixels[tex_base + pxl8_tile_addr((u32)tx, (u32)ty, tex_log2_w)] : 1; if (tex_idx != 0) { u8 light = (u8)l_a; @@ -749,8 +801,11 @@ static void rasterize_triangle_opaque( u8 pal_idx = cpu->colormap ? pxl8_colormap_lookup(cpu->colormap, tex_idx, PXL8_LIGHT_WHITE, light) : tex_idx; prow[px] = pal_idx; zrow[px] = z16; - if (light_accum) { - light_accum[row_start + (u32)px] = tri_light_color; + if (light_accum && t_a > 0.01f) { + u32 alpha = (u32)(t_a * 255.0f); + if (alpha > 255) alpha = 255; + u32 tint = (cpu->light_tint & 0x00FFFFFF) | (alpha << 24); + light_accum[row_start + (u32)px] = tint; } } } @@ -758,14 +813,17 @@ static void rasterize_triangle_opaque( u_fixed += du_fixed; v_fixed += dv_fixed; l_a += dl; + t_a += dt; z_a += dz; } - wr += dwr * steps; - uw += duw * steps; - vw += dvw * steps; - lw += d_lw * steps; - z += dz * steps; + wr += dwr * (f32)span_len; + uw += duw * (f32)span_len; + vw += dvw * (f32)span_len; + wxw += dwxw * (f32)span_len; + wyw += dwyw * (f32)span_len; + wzw += dwzw * (f32)span_len; + z += dz * (f32)span_len; x = span_end + 1; } } @@ -778,59 +836,63 @@ static void rasterize_triangle_passthrough( pxl8_cpu_render_target* render_target = cpu->current_target; for (i32 y = setup->y_start; y <= setup->y_end; y++) { - bool second_half = y > setup->y1 || setup->y1 == setup->y0; - i32 segment_height = second_half ? setup->y2 - setup->y1 : setup->y1 - setup->y0; - if (segment_height == 0) continue; + f32 yf = (f32)y + 0.5f; + f32 alpha = (yf - setup->p0.y) * setup->inv_total; - f32 alpha = (f32)(y - setup->y0) * setup->inv_total; - f32 beta = (f32)(y - (second_half ? setup->y1 : setup->y0)) / (f32)segment_height; - - f32 ax = (f32)setup->x0 + (f32)(setup->x2 - setup->x0) * alpha; - f32 az = setup->z0 + (setup->z2 - setup->z0) * alpha; - f32 a_wr = setup->w0_recip + (setup->w2_recip - setup->w0_recip) * alpha; - f32 a_cw = setup->c0_w + (setup->c2_w - setup->c0_w) * alpha; + f32 ax = setup->p0.x + (setup->p2.x - setup->p0.x) * alpha; + f32 az = setup->p0.z + (setup->p2.z - setup->p0.z) * alpha; + f32 a_wr = setup->w_recip.x + (setup->w_recip.z - setup->w_recip.x) * alpha; + f32 a_cw = setup->c_w.x + (setup->c_w.z - setup->c_w.x) * alpha; f32 bx, bz, b_wr, b_cw; + bool second_half = yf > setup->p1.y || setup->p1.y == setup->p0.y; + f32 segment_height = second_half ? (setup->p2.y - setup->p1.y) : (setup->p1.y - setup->p0.y); + if (segment_height < 0.001f) segment_height = 0.001f; + + f32 beta = (yf - (second_half ? setup->p1.y : setup->p0.y)) / segment_height; + if (beta < 0.0f) beta = 0.0f; + if (beta > 1.0f) beta = 1.0f; + if (second_half) { - bx = (f32)setup->x1 + (f32)(setup->x2 - setup->x1) * beta; - bz = setup->z1 + (setup->z2 - setup->z1) * beta; - b_wr = setup->w1_recip + (setup->w2_recip - setup->w1_recip) * beta; - b_cw = setup->c1_w + (setup->c2_w - setup->c1_w) * beta; + bx = setup->p1.x + (setup->p2.x - setup->p1.x) * beta; + bz = setup->p1.z + (setup->p2.z - setup->p1.z) * beta; + b_wr = setup->w_recip.y + (setup->w_recip.z - setup->w_recip.y) * beta; + b_cw = setup->c_w.y + (setup->c_w.z - setup->c_w.y) * beta; } else { - bx = (f32)setup->x0 + (f32)(setup->x1 - setup->x0) * beta; - bz = setup->z0 + (setup->z1 - setup->z0) * beta; - b_wr = setup->w0_recip + (setup->w1_recip - setup->w0_recip) * beta; - b_cw = setup->c0_w + (setup->c1_w - setup->c0_w) * beta; + bx = setup->p0.x + (setup->p1.x - setup->p0.x) * beta; + bz = setup->p0.z + (setup->p1.z - setup->p0.z) * beta; + b_wr = setup->w_recip.x + (setup->w_recip.y - setup->w_recip.x) * beta; + b_cw = setup->c_w.x + (setup->c_w.y - setup->c_w.x) * beta; } - f32 x_start_f, x_end_f, z_start, z_end, wr_start, wr_end, cw_start, cw_end; + f32 x_start_fp, x_end_fp, z_start, z_end, wr_start, wr_end, cw_start, cw_end; if (ax <= bx) { - x_start_f = ax; x_end_f = bx; + x_start_fp = ax; x_end_fp = bx; z_start = az; z_end = bz; wr_start = a_wr; wr_end = b_wr; cw_start = a_cw; cw_end = b_cw; } else { - x_start_f = bx; x_end_f = ax; + x_start_fp = bx; x_end_fp = ax; z_start = bz; z_end = az; wr_start = b_wr; wr_end = a_wr; cw_start = b_cw; cw_end = a_cw; } - i32 x_start_orig = (i32)(x_start_f + 0.5f); - i32 x_end_orig = (i32)(x_end_f + 0.5f) - 1; - i32 x_start = x_start_orig < 0 ? 0 : x_start_orig; - i32 x_end = x_end_orig >= (i32)setup->target_width ? (i32)setup->target_width - 1 : x_end_orig; + i32 x_start = (i32)floorf(x_start_fp); + i32 x_end = (i32)ceilf(x_end_fp) - 1; + if (x_start < 0) x_start = 0; + if (x_end >= (i32)setup->target_width) x_end = (i32)setup->target_width - 1; if (x_start > x_end) continue; - i32 width_orig = x_end_orig - x_start_orig; - if (width_orig < 1) width_orig = 1; - f32 inv_width = 1.0f / (f32)width_orig; + f32 span_width = x_end_fp - x_start_fp; + if (span_width < 1.0f) span_width = 1.0f; + f32 inv_width = 1.0f / span_width; f32 dz = (z_end - z_start) * inv_width; f32 dwr = (wr_end - wr_start) * inv_width; f32 dcw = (cw_end - cw_start) * inv_width; - f32 skip = (f32)(x_start - x_start_orig); + f32 skip = (f32)x_start + 0.5f - x_start_fp; f32 z = z_start + dz * skip; f32 wr = wr_start + dwr * skip; f32 cw = cw_start + dcw * skip; @@ -840,11 +902,14 @@ static void rasterize_triangle_passthrough( u16* zrow = render_target->zbuffer + row_start; for (i32 px = x_start; px <= x_end; px++) { - u16 z16 = depth_to_u16(z); + f32 depth_norm = (z + 1.0f) * 0.5f; + if (depth_norm < 0.0f) depth_norm = 0.0f; + if (depth_norm > 1.0f) depth_norm = 1.0f; + u16 z16 = (u16)(depth_norm * 65535.0f); if (z16 < zrow[px]) { f32 w = 1.0f / wr; - f32 color_f = cw * w; - u8 color = pxl8_dither_float(color_f, (u32)px, (u32)y); + f32 color_fp = cw * w; + u8 color = pxl8_dither_float(color_fp, (u32)px, (u32)y); prow[px] = color; zrow[px] = z16; } @@ -862,11 +927,45 @@ static void rasterize_triangle_alpha( ) { pxl8_cpu_render_target* render_target = cpu->current_target; u32* light_accum = render_target->light_accum; - u32 tri_light_color = light_accum ? blend_tri_light_color(setup->lc0, setup->lc1, setup->lc2) : 0; + + bool facing_light = false; + f32 light_radius_sq = 0.0f; + f32 light_inv_radius_sq = 0.0f; + pxl8_vec3 light_pos = {0}; + if (cpu->frame.lights_count > 0) { + const pxl8_light* light = &cpu->frame.lights[0]; + light_pos = light->position; + light_radius_sq = light->radius_sq; + light_inv_radius_sq = light->inv_radius_sq; + + f32 avg_w = (setup->w_recip.x + setup->w_recip.y + setup->w_recip.z) / 3.0f; + f32 inv_w = (avg_w > 0.0001f) ? 1.0f / avg_w : 1.0f; + f32 cx = (setup->world0_w.x + setup->world1_w.x + setup->world2_w.x) / 3.0f * inv_w; + f32 cy = (setup->world0_w.y + setup->world1_w.y + setup->world2_w.y) / 3.0f * inv_w; + f32 cz = (setup->world0_w.z + setup->world1_w.z + setup->world2_w.z) / 3.0f * inv_w; + f32 to_light_x = light->position.x - cx; + f32 to_light_y = light->position.y - cy; + f32 to_light_z = light->position.z - cz; + f32 n_dot_l = setup->normal.x * to_light_x + setup->normal.y * to_light_y + setup->normal.z * to_light_z; + facing_light = n_dot_l > 0.0f; + + if (facing_light && setup->normal.y > 0.9f && cpu->frame.bsp && cpu->light_leaf >= 0) { + pxl8_vec3 centroid = {cx, cy, cz}; + i32 tri_leaf = pxl8_bsp_find_leaf(cpu->frame.bsp, centroid); + if (tri_leaf >= 0 && tri_leaf != cpu->light_leaf && + !pxl8_bsp_is_leaf_visible(cpu->frame.bsp, cpu->light_leaf, tri_leaf)) { + facing_light = false; + } + } + } + + f32 base_light = 0.25f + cpu->frame.uniforms.ambient * (1.0f / 255.0f); + base_light += cpu->frame.uniforms.celestial_intensity * 0.5f; + const pxl8_atlas_entry* tex_entry = textures ? pxl8_atlas_get_entry(textures, texture_id) : NULL; u32 atlas_width = textures ? pxl8_atlas_get_width(textures) : 1; - f32 tex_w = tex_entry ? (f32)tex_entry->w : 1.0f; - f32 tex_h = tex_entry ? (f32)tex_entry->h : 1.0f; + i32 tex_w = tex_entry ? (i32)tex_entry->w : 1; + i32 tex_h = tex_entry ? (i32)tex_entry->h : 1; i32 tex_stride = (i32)atlas_width; i32 tex_mask_w = tex_entry ? (i32)tex_entry->w - 1 : 0; i32 tex_mask_h = tex_entry ? (i32)tex_entry->h - 1 : 0; @@ -874,129 +973,168 @@ static void rasterize_triangle_alpha( const u8* tex_pixels = textures ? pxl8_atlas_get_pixels(textures) : NULL; for (i32 y = setup->y_start; y <= setup->y_end; y++) { - bool second_half = y > setup->y1 || setup->y1 == setup->y0; - i32 segment_height = second_half ? setup->y2 - setup->y1 : setup->y1 - setup->y0; - if (segment_height == 0) continue; + f32 yf = (f32)y + 0.5f; + f32 alpha = (yf - setup->p0.y) * setup->inv_total; - f32 alpha = (f32)(y - setup->y0) * setup->inv_total; - f32 beta = (f32)(y - (second_half ? setup->y1 : setup->y0)) / (f32)segment_height; + f32 ax = setup->p0.x + (setup->p2.x - setup->p0.x) * alpha; + f32 az = setup->p0.z + (setup->p2.z - setup->p0.z) * alpha; + f32 a_wr = setup->w_recip.x + (setup->w_recip.z - setup->w_recip.x) * alpha; + f32 a_uw = setup->u_w.x + (setup->u_w.z - setup->u_w.x) * alpha; + f32 a_vw = setup->v_w.x + (setup->v_w.z - setup->v_w.x) * alpha; + f32 a_wxw = setup->world0_w.x + (setup->world2_w.x - setup->world0_w.x) * alpha; + f32 a_wyw = setup->world0_w.y + (setup->world2_w.y - setup->world0_w.y) * alpha; + f32 a_wzw = setup->world0_w.z + (setup->world2_w.z - setup->world0_w.z) * alpha; - f32 ax = (f32)setup->x0 + (f32)(setup->x2 - setup->x0) * alpha; - f32 az = setup->z0 + (setup->z2 - setup->z0) * alpha; - f32 a_wr = setup->w0_recip + (setup->w2_recip - setup->w0_recip) * alpha; - f32 a_uw = setup->u0_w + (setup->u2_w - setup->u0_w) * alpha; - f32 a_vw = setup->v0_w + (setup->v2_w - setup->v0_w) * alpha; - f32 a_lw = setup->l0_w + (setup->l2_w - setup->l0_w) * alpha; + f32 bx, bz, b_wr, b_uw, b_vw, b_wxw, b_wyw, b_wzw; + bool second_half = yf > setup->p1.y || setup->p1.y == setup->p0.y; + f32 segment_height = second_half ? (setup->p2.y - setup->p1.y) : (setup->p1.y - setup->p0.y); + if (segment_height < 0.001f) segment_height = 0.001f; + + f32 beta = (yf - (second_half ? setup->p1.y : setup->p0.y)) / segment_height; + if (beta < 0.0f) beta = 0.0f; + if (beta > 1.0f) beta = 1.0f; - f32 bx, bz, b_wr, b_uw, b_vw, b_lw; if (second_half) { - bx = (f32)setup->x1 + (f32)(setup->x2 - setup->x1) * beta; - bz = setup->z1 + (setup->z2 - setup->z1) * beta; - b_wr = setup->w1_recip + (setup->w2_recip - setup->w1_recip) * beta; - b_uw = setup->u1_w + (setup->u2_w - setup->u1_w) * beta; - b_vw = setup->v1_w + (setup->v2_w - setup->v1_w) * beta; - b_lw = setup->l1_w + (setup->l2_w - setup->l1_w) * beta; + bx = setup->p1.x + (setup->p2.x - setup->p1.x) * beta; + bz = setup->p1.z + (setup->p2.z - setup->p1.z) * beta; + b_wr = setup->w_recip.y + (setup->w_recip.z - setup->w_recip.y) * beta; + b_uw = setup->u_w.y + (setup->u_w.z - setup->u_w.y) * beta; + b_vw = setup->v_w.y + (setup->v_w.z - setup->v_w.y) * beta; + b_wxw = setup->world1_w.x + (setup->world2_w.x - setup->world1_w.x) * beta; + b_wyw = setup->world1_w.y + (setup->world2_w.y - setup->world1_w.y) * beta; + b_wzw = setup->world1_w.z + (setup->world2_w.z - setup->world1_w.z) * beta; } else { - bx = (f32)setup->x0 + (f32)(setup->x1 - setup->x0) * beta; - bz = setup->z0 + (setup->z1 - setup->z0) * beta; - b_wr = setup->w0_recip + (setup->w1_recip - setup->w0_recip) * beta; - b_uw = setup->u0_w + (setup->u1_w - setup->u0_w) * beta; - b_vw = setup->v0_w + (setup->v1_w - setup->v0_w) * beta; - b_lw = setup->l0_w + (setup->l1_w - setup->l0_w) * beta; + bx = setup->p0.x + (setup->p1.x - setup->p0.x) * beta; + bz = setup->p0.z + (setup->p1.z - setup->p0.z) * beta; + b_wr = setup->w_recip.x + (setup->w_recip.y - setup->w_recip.x) * beta; + b_uw = setup->u_w.x + (setup->u_w.y - setup->u_w.x) * beta; + b_vw = setup->v_w.x + (setup->v_w.y - setup->v_w.x) * beta; + b_wxw = setup->world0_w.x + (setup->world1_w.x - setup->world0_w.x) * beta; + b_wyw = setup->world0_w.y + (setup->world1_w.y - setup->world0_w.y) * beta; + b_wzw = setup->world0_w.z + (setup->world1_w.z - setup->world0_w.z) * beta; } - f32 x_start_f, x_end_f, z_start, z_end; - f32 wr_start, wr_end, uw_start, uw_end, vw_start, vw_end, lw_start, lw_end; + f32 x_start_fp, x_end_fp, z_start, z_end; + f32 wr_start, wr_end, uw_start, uw_end, vw_start, vw_end; + f32 wxw_start, wxw_end, wyw_start, wyw_end, wzw_start, wzw_end; if (ax <= bx) { - x_start_f = ax; x_end_f = bx; z_start = az; z_end = bz; + x_start_fp = ax; x_end_fp = bx; z_start = az; z_end = bz; wr_start = a_wr; wr_end = b_wr; uw_start = a_uw; uw_end = b_uw; vw_start = a_vw; vw_end = b_vw; - lw_start = a_lw; lw_end = b_lw; + wxw_start = a_wxw; wxw_end = b_wxw; + wyw_start = a_wyw; wyw_end = b_wyw; + wzw_start = a_wzw; wzw_end = b_wzw; } else { - x_start_f = bx; x_end_f = ax; z_start = bz; z_end = az; + x_start_fp = bx; x_end_fp = ax; z_start = bz; z_end = az; wr_start = b_wr; wr_end = a_wr; uw_start = b_uw; uw_end = a_uw; vw_start = b_vw; vw_end = a_vw; - lw_start = b_lw; lw_end = a_lw; + wxw_start = b_wxw; wxw_end = a_wxw; + wyw_start = b_wyw; wyw_end = a_wyw; + wzw_start = b_wzw; wzw_end = a_wzw; } - i32 x_start_orig = (i32)(x_start_f + 0.5f); - i32 x_end_orig = (i32)(x_end_f + 0.5f) - 1; - i32 x_start = x_start_orig < 0 ? 0 : x_start_orig; - i32 x_end = x_end_orig >= (i32)setup->target_width ? (i32)setup->target_width - 1 : x_end_orig; + i32 x_start = (i32)floorf(x_start_fp); + i32 x_end = (i32)ceilf(x_end_fp) - 1; + if (x_start < 0) x_start = 0; + if (x_end >= (i32)setup->target_width) x_end = (i32)setup->target_width - 1; if (x_start > x_end) continue; - i32 width_orig = x_end_orig - x_start_orig; - if (width_orig < 1) width_orig = 1; - f32 inv_width = 1.0f / (f32)width_orig; + f32 span_width = x_end_fp - x_start_fp; + if (span_width < 1.0f) span_width = 1.0f; + f32 inv_width = 1.0f / span_width; f32 dz = (z_end - z_start) * inv_width; f32 dwr = (wr_end - wr_start) * inv_width; f32 duw = (uw_end - uw_start) * inv_width; f32 dvw = (vw_end - vw_start) * inv_width; - f32 d_lw = (lw_end - lw_start) * inv_width; + f32 dwxw = (wxw_end - wxw_start) * inv_width; + f32 dwyw = (wyw_end - wyw_start) * inv_width; + f32 dwzw = (wzw_end - wzw_start) * inv_width; - f32 skip = (f32)(x_start - x_start_orig); + f32 skip = (f32)x_start + 0.5f - x_start_fp; f32 z = z_start + dz * skip; f32 wr = wr_start + dwr * skip; f32 uw = uw_start + duw * skip; f32 vw = vw_start + dvw * skip; - f32 lw = lw_start + d_lw * skip; + f32 wxw = wxw_start + dwxw * skip; + f32 wyw = wyw_start + dwyw * skip; + f32 wzw = wzw_start + dwzw * skip; u32 row_start = (u32)y * render_target->width; u8* prow = render_target->framebuffer + row_start; u16* zrow = render_target->zbuffer + row_start; - const i32 SUBDIV = 32; + const i32 SUBDIV = 16; i32 x = x_start; while (x <= x_end) { i32 span_end = x + SUBDIV - 1; if (span_end > x_end) span_end = x_end; i32 span_len = span_end - x + 1; - f32 steps = (f32)span_len; f32 pw_start = 1.0f / wr; - f32 pw_end = 1.0f / (wr + dwr * steps); + f32 pw_end = 1.0f / (wr + dwr * (f32)span_len); f32 u_start = uw * pw_start; f32 v_start = vw * pw_start; - f32 u_end = (uw + duw * steps) * pw_end; - f32 v_end = (vw + dvw * steps) * pw_end; - f32 l_start = lw * pw_start; - f32 l_end = (lw + d_lw * steps) * pw_end; + f32 u_end = (uw + duw * (f32)span_len) * pw_end; + f32 v_end = (vw + dvw * (f32)span_len) * pw_end; - f32 fog_density = cpu->frame.uniforms.fog_density; - if (fog_density > 0.0f) { - f32 z_end_fog = z + dz * steps; - f32 t_start = (z + 1.0f) * 0.5f; - f32 t_end = (z_end_fog + 1.0f) * 0.5f; - if (t_start < 0.0f) t_start = 0.0f; - if (t_end < 0.0f) t_end = 0.0f; - f32 fog_start = t_start * t_start * fog_density; - f32 fog_end = t_end * t_end * fog_density; - if (fog_start > 1.0f) fog_start = 1.0f; - if (fog_end > 1.0f) fog_end = 1.0f; - l_start *= (1.0f - fog_start); - l_end *= (1.0f - fog_end); + f32 wx_start = wxw * pw_start; + f32 wy_start = wyw * pw_start; + f32 wz_start = wzw * pw_start; + f32 wx_end = (wxw + dwxw * (f32)span_len) * pw_end; + f32 wy_end = (wyw + dwyw * (f32)span_len) * pw_end; + f32 wz_end = (wzw + dwzw * (f32)span_len) * pw_end; + + f32 l_start = base_light; + f32 l_end = base_light; + f32 tint_start = 0.0f; + f32 tint_end = 0.0f; + + if (facing_light && cpu->frame.lights_count > 0) { + f32 dx_s = light_pos.x - wx_start; + f32 dy_s = light_pos.y - wy_start; + f32 dz_s = light_pos.z - wz_start; + f32 dist_sq_s = dx_s * dx_s + dy_s * dy_s + dz_s * dz_s; + if (dist_sq_s < light_radius_sq) { + f32 falloff = 1.0f - dist_sq_s * light_inv_radius_sq; + l_start += falloff; + tint_start = falloff; + } + + f32 dx_e = light_pos.x - wx_end; + f32 dy_e = light_pos.y - wy_end; + f32 dz_e = light_pos.z - wz_end; + f32 dist_sq_e = dx_e * dx_e + dy_e * dy_e + dz_e * dz_e; + if (dist_sq_e < light_radius_sq) { + f32 falloff = 1.0f - dist_sq_e * light_inv_radius_sq; + l_end += falloff; + tint_end = falloff; + } } - if (l_start > 255.0f) l_start = 255.0f; - if (l_end > 255.0f) l_end = 255.0f; + if (l_start > 1.0f) l_start = 1.0f; + if (l_end > 1.0f) l_end = 1.0f; + l_start *= 255.0f; + l_end *= 255.0f; f32 inv_span = span_len > 1 ? 1.0f / (f32)(span_len - 1) : 0.0f; f32 du = (u_end - u_start) * inv_span; f32 dv = (v_end - v_start) * inv_span; f32 dl = (l_end - l_start) * inv_span; + f32 dt = (tint_end - tint_start) * inv_span; - i32 u_fixed = (i32)(u_start * tex_w * 65536.0f); - i32 v_fixed = (i32)(v_start * tex_h * 65536.0f); - i32 du_fixed = (i32)(du * tex_w * 65536.0f); - i32 dv_fixed = (i32)(dv * tex_h * 65536.0f); + i32 u_fixed = (i32)(u_start * (f32)tex_w * 65536.0f); + i32 v_fixed = (i32)(v_start * (f32)tex_h * 65536.0f); + i32 du_fixed = (i32)(du * (f32)tex_w * 65536.0f); + i32 dv_fixed = (i32)(dv * (f32)tex_h * 65536.0f); f32 l_a = l_start; + f32 t_a = tint_start; f32 z_a = z; for (i32 px = x; px <= span_end; px++) { @@ -1013,10 +1151,16 @@ static void rasterize_triangle_alpha( if (mat_alpha >= 128) { prow[px] = src_idx; - u16 z16 = depth_to_u16(z_a); + f32 depth_norm = (z_a + 1.0f) * 0.5f; + if (depth_norm < 0.0f) depth_norm = 0.0f; + if (depth_norm > 1.0f) depth_norm = 1.0f; + u16 z16 = (u16)(depth_norm * 65535.0f); if (z16 < zrow[px]) zrow[px] = z16; - if (light_accum) { - light_accum[row_start + (u32)px] = tri_light_color; + if (light_accum && t_a > 0.01f) { + u32 alpha = (u32)(t_a * 255.0f); + if (alpha > 255) alpha = 255; + u32 tint = (cpu->light_tint & 0x00FFFFFF) | (alpha << 24); + light_accum[row_start + (u32)px] = tint; } } } @@ -1024,14 +1168,17 @@ static void rasterize_triangle_alpha( u_fixed += du_fixed; v_fixed += dv_fixed; l_a += dl; + t_a += dt; z_a += dz; } - wr += dwr * steps; - uw += duw * steps; - vw += dvw * steps; - lw += d_lw * steps; - z += dz * steps; + wr += dwr * (f32)span_len; + uw += duw * (f32)span_len; + vw += dvw * (f32)span_len; + wxw += dwxw * (f32)span_len; + wyw += dwyw * (f32)span_len; + wzw += dwzw * (f32)span_len; + z += dz * (f32)span_len; x = span_end + 1; } } @@ -1155,17 +1302,10 @@ void pxl8_cpu_draw_mesh( vo0.light = lr0.light; vo1.light = lr1.light; vo2.light = lr2.light; - - vo0.light_color = lr0.light_color; - vo1.light_color = lr1.light_color; - vo2.light_color = lr2.light_color; } else { vo0.light = 255; vo1.light = 255; vo2.light = 255; - vo0.light_color = 0; - vo1.light_color = 0; - vo2.light_color = 0; } vertex_output clipped[6]; @@ -1182,6 +1322,16 @@ u8* pxl8_cpu_get_framebuffer(pxl8_cpu_backend* cpu) { return cpu->target_stack[0]->framebuffer; } +u32* pxl8_cpu_get_light_accum(pxl8_cpu_backend* cpu) { + if (!cpu || cpu->target_stack_depth == 0) return NULL; + return cpu->target_stack[0]->light_accum; +} + +u16* pxl8_cpu_get_zbuffer(pxl8_cpu_backend* cpu) { + if (!cpu || cpu->target_stack_depth == 0) return NULL; + return cpu->target_stack[0]->zbuffer; +} + u32 pxl8_cpu_get_height(const pxl8_cpu_backend* cpu) { if (!cpu || cpu->target_stack_depth == 0) return 0; return cpu->target_stack[0]->height; @@ -1464,6 +1614,11 @@ void pxl8_cpu_resolve(pxl8_cpu_backend* cpu) { u32 light_color = light_accum[i]; u32 tint_alpha = (light_color >> 24) & 0xFF; + if (tint_alpha == 255) { + output[i] = light_color; + continue; + } + if (tint_alpha > 0) { u32 lr = light_color & 0xFF; u32 lg = (light_color >> 8) & 0xFF; @@ -1474,16 +1629,18 @@ void pxl8_cpu_resolve(pxl8_cpu_backend* cpu) { u32 bb = (base >> 16) & 0xFF; u32 ba = (base >> 24) & 0xFF; - u32 t = (tint_alpha * tint_alpha) / 255; - u32 inv_t = 255 - t; + f32 t = (f32)tint_alpha / 255.0f; + f32 tint_r = (f32)lr / 255.0f; + f32 tint_g = (f32)lg / 255.0f; + f32 tint_b = (f32)lb / 255.0f; - u32 lr_norm = (lr * 255) / 240; - u32 lg_norm = (lg * 255) / 240; - u32 lb_norm = (lb * 255) / 220; + f32 or_f = (f32)br * (1.0f + t * tint_r); + f32 og_f = (f32)bg * (1.0f + t * tint_g * 0.6f); + f32 ob_f = (f32)bb * (1.0f + t * tint_b * 0.3f); - u32 or = ((br * inv_t + (br * lr_norm / 255) * t) / 255); - u32 og = ((bg * inv_t + (bg * lg_norm / 255) * t) / 255); - u32 ob = ((bb * inv_t + (bb * lb_norm / 255) * t) / 255); + u32 or = (u32)or_f; + u32 og = (u32)og_f; + u32 ob = (u32)ob_f; if (or > 255) or = 255; if (og > 255) og = 255; diff --git a/src/gfx/pxl8_cpu.h b/src/gfx/pxl8_cpu.h index bfde3f1..81f3852 100644 --- a/src/gfx/pxl8_cpu.h +++ b/src/gfx/pxl8_cpu.h @@ -58,7 +58,9 @@ void pxl8_cpu_draw_mesh( ); u8* pxl8_cpu_get_framebuffer(pxl8_cpu_backend* cpu); +u32* pxl8_cpu_get_light_accum(pxl8_cpu_backend* cpu); u32* pxl8_cpu_get_output(pxl8_cpu_backend* cpu); +u16* pxl8_cpu_get_zbuffer(pxl8_cpu_backend* cpu); u32 pxl8_cpu_get_height(const pxl8_cpu_backend* cpu); u32 pxl8_cpu_get_width(const pxl8_cpu_backend* cpu); diff --git a/src/gfx/pxl8_gfx.c b/src/gfx/pxl8_gfx.c index 00e2826..4d2e364 100644 --- a/src/gfx/pxl8_gfx.c +++ b/src/gfx/pxl8_gfx.c @@ -27,6 +27,7 @@ typedef struct pxl8_sprite_cache_entry { struct pxl8_gfx { pxl8_atlas* atlas; pxl8_gfx_backend backend; + const pxl8_bsp* bsp; pxl8_colormap* colormap; u8* framebuffer; i32 framebuffer_height; @@ -38,6 +39,7 @@ struct pxl8_gfx { pxl8_palette_cube* palette_cube; pxl8_pixel_mode pixel_mode; void* platform_data; + const pxl8_sdf* sdf; pxl8_sprite_cache_entry* sprite_cache; u32 sprite_cache_capacity; u32 sprite_cache_count; @@ -61,6 +63,9 @@ pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx) { u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx) { if (!gfx || gfx->pixel_mode == PXL8_PIXEL_HICOLOR) return NULL; + if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) { + return pxl8_cpu_get_framebuffer(gfx->backend.cpu); + } return gfx->framebuffer; } @@ -73,6 +78,27 @@ i32 pxl8_gfx_get_height(const pxl8_gfx* gfx) { return gfx ? gfx->framebuffer_height : 0; } +u32* pxl8_gfx_get_light_accum(pxl8_gfx* gfx) { + if (!gfx) return NULL; + if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) { + return pxl8_cpu_get_light_accum(gfx->backend.cpu); + } + return NULL; +} + +const u32* pxl8_gfx_palette_colors(pxl8_gfx* gfx) { + if (!gfx || !gfx->palette) return NULL; + return pxl8_palette_colors(gfx->palette); +} + +u16* pxl8_gfx_get_zbuffer(pxl8_gfx* gfx) { + if (!gfx) return NULL; + if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) { + return pxl8_cpu_get_zbuffer(gfx->backend.cpu); + } + return NULL; +} + i32 pxl8_gfx_get_width(const pxl8_gfx* gfx) { return gfx ? gfx->framebuffer_width : 0; } @@ -608,12 +634,24 @@ pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8 return frame; } +void pxl8_3d_set_bsp(pxl8_gfx* gfx, const pxl8_bsp* bsp) { + if (!gfx) return; + gfx->bsp = bsp; +} + +void pxl8_3d_set_sdf(pxl8_gfx* gfx, const pxl8_sdf* sdf) { + if (!gfx) return; + gfx->sdf = sdf; +} + void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_3d_uniforms* uniforms) { if (!gfx || !camera) return; pxl8_3d_frame frame = pxl8_3d_frame_from_camera(camera, uniforms); + frame.bsp = gfx->bsp; frame.lights = lights ? pxl8_lights_data(lights) : NULL; frame.lights_count = lights ? pxl8_lights_count(lights) : 0; + frame.sdf = gfx->sdf; pxl8_mat4 vp = pxl8_mat4_multiply(frame.projection, frame.view); diff --git a/src/gfx/pxl8_gfx.h b/src/gfx/pxl8_gfx.h index ec03ed4..cd53b5c 100644 --- a/src/gfx/pxl8_gfx.h +++ b/src/gfx/pxl8_gfx.h @@ -32,6 +32,9 @@ pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx); u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx); u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx); i32 pxl8_gfx_get_height(const pxl8_gfx* gfx); +u32* pxl8_gfx_get_light_accum(pxl8_gfx* gfx); +const u32* pxl8_gfx_palette_colors(pxl8_gfx* gfx); +u16* pxl8_gfx_get_zbuffer(pxl8_gfx* gfx); pxl8_palette* pxl8_gfx_palette(pxl8_gfx* gfx); pxl8_colormap* pxl8_gfx_colormap(pxl8_gfx* gfx); pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx); diff --git a/src/gfx/pxl8_gfx3d.h b/src/gfx/pxl8_gfx3d.h index b5cf0e9..bf964c2 100644 --- a/src/gfx/pxl8_gfx3d.h +++ b/src/gfx/pxl8_gfx3d.h @@ -6,6 +6,7 @@ #include "pxl8_mesh.h" #include "pxl8_types.h" +typedef struct pxl8_bsp pxl8_bsp; typedef struct pxl8_gfx pxl8_gfx; typedef struct pxl8_3d_uniforms { @@ -17,7 +18,16 @@ typedef struct pxl8_3d_uniforms { f32 time; } pxl8_3d_uniforms; +typedef struct pxl8_3d_frame_desc { + const pxl8_bsp* bsp; + const pxl8_3d_camera* camera; + const pxl8_lights* lights; + const pxl8_sdf* sdf; + pxl8_3d_uniforms uniforms; +} pxl8_3d_frame_desc; + typedef struct pxl8_3d_frame { + const pxl8_bsp* bsp; pxl8_vec3 camera_dir; pxl8_vec3 camera_pos; f32 far_clip; @@ -25,6 +35,7 @@ typedef struct pxl8_3d_frame { u32 lights_count; f32 near_clip; pxl8_mat4 projection; + const pxl8_sdf* sdf; pxl8_3d_uniforms uniforms; pxl8_mat4 view; } pxl8_3d_frame; @@ -33,6 +44,8 @@ typedef struct pxl8_3d_frame { extern "C" { #endif +void pxl8_3d_set_bsp(pxl8_gfx* gfx, const pxl8_bsp* bsp); +void pxl8_3d_set_sdf(pxl8_gfx* gfx, const pxl8_sdf* sdf); void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_3d_uniforms* uniforms); void pxl8_3d_clear(pxl8_gfx* gfx, u8 color); void pxl8_3d_clear_depth(pxl8_gfx* gfx); diff --git a/src/gui/pxl8_gui.c b/src/gui/pxl8_gui.c index f81a910..e3f667a 100644 --- a/src/gui/pxl8_gui.c +++ b/src/gui/pxl8_gui.c @@ -106,6 +106,63 @@ bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, return clicked; } +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) { + if (!state || !gfx || !value) return false; + + bool cursor_over = is_cursor_over(state, x, y, w, h); + bool is_active = (state->active_id == id); + bool changed = false; + + if (cursor_over) { + state->hot_id = id; + } + + if (cursor_over && state->cursor_down && state->active_id == 0) { + state->active_id = id; + } + + if (is_active && state->cursor_down) { + f32 t = (f32)(state->cursor_x - x) / (f32)w; + if (t < 0.0f) t = 0.0f; + if (t > 1.0f) t = 1.0f; + f32 new_val = min_val + t * (max_val - min_val); + if (new_val != *value) { + *value = new_val; + changed = true; + } + } + + u8 bg_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG1); + u8 fill_color = pxl8_gfx_ui_color(gfx, is_active ? PXL8_UI_FG0 : PXL8_UI_BG3); + u8 handle_color = pxl8_gfx_ui_color(gfx, PXL8_UI_FG1); + + pxl8_2d_rect_fill(gfx, x, y, w, h, bg_color); + + f32 t = (*value - min_val) / (max_val - min_val); + i32 fill_w = (i32)(t * (f32)w); + if (fill_w > 0) { + pxl8_2d_rect_fill(gfx, x, y, fill_w, h, fill_color); + } + + i32 handle_x = x + fill_w - 2; + if (handle_x < x) handle_x = x; + if (handle_x > x + w - 4) handle_x = x + w - 4; + pxl8_2d_rect_fill(gfx, handle_x, y, 4, h, handle_color); + + return changed; +} + +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) { + if (!state || !gfx || !value) return false; + + f32 fval = (f32)*value; + bool changed = pxl8_gui_slider(state, gfx, id, x, y, w, h, &fval, (f32)min_val, (f32)max_val); + if (changed) { + *value = (i32)(fval + 0.5f); + } + return changed; +} + void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title) { if (!gfx || !title) return; diff --git a/src/gui/pxl8_gui.h b/src/gui/pxl8_gui.h index 1d7646a..982a3ff 100644 --- a/src/gui/pxl8_gui.h +++ b/src/gui/pxl8_gui.h @@ -30,6 +30,8 @@ void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y); void pxl8_gui_cursor_up(pxl8_gui_state* state); bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label); +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); void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color); void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title); diff --git a/src/hal/pxl8_hal.h b/src/hal/pxl8_hal.h index df504c0..94c934b 100644 --- a/src/hal/pxl8_hal.h +++ b/src/hal/pxl8_hal.h @@ -2,6 +2,34 @@ #include "pxl8_types.h" +#if defined(_WIN32) || defined(__APPLE__) || defined(__linux__) +#define PXL8_ASYNC_THREADS +#endif + +typedef struct pxl8_thread pxl8_thread; +typedef struct pxl8_mutex pxl8_mutex; +typedef struct pxl8_cond pxl8_cond; +typedef int (*pxl8_thread_fn)(void* data); + +pxl8_thread* pxl8_thread_create(pxl8_thread_fn fn, const char* name, void* data); +void pxl8_thread_wait(pxl8_thread* thread, int* status); +void pxl8_thread_detach(pxl8_thread* thread); + +pxl8_mutex* pxl8_mutex_create(void); +void pxl8_mutex_destroy(pxl8_mutex* mutex); +void pxl8_mutex_lock(pxl8_mutex* mutex); +void pxl8_mutex_unlock(pxl8_mutex* mutex); +bool pxl8_mutex_trylock(pxl8_mutex* mutex); + +pxl8_cond* pxl8_cond_create(void); +void pxl8_cond_destroy(pxl8_cond* cond); +void pxl8_cond_signal(pxl8_cond* cond); +void pxl8_cond_broadcast(pxl8_cond* cond); +void pxl8_cond_wait(pxl8_cond* cond, pxl8_mutex* mutex); + +u64 pxl8_get_ticks_ns(void); +void pxl8_sleep_ms(u32 ms); + typedef struct pxl8_hal { void* (*create)(i32 render_w, i32 render_h, const char* title, i32 win_w, i32 win_h); diff --git a/src/hal/pxl8_hal_sdl3.c b/src/hal/pxl8_hal_sdl3.c index 66c4f0b..581b257 100644 --- a/src/hal/pxl8_hal_sdl3.c +++ b/src/hal/pxl8_hal_sdl3.c @@ -83,6 +83,14 @@ static u64 sdl3_get_ticks(void) { return SDL_GetTicksNS(); } +void pxl8_sleep_ms(u32 ms) { + SDL_Delay(ms); +} + +u64 pxl8_get_ticks_ns(void) { + return SDL_GetTicksNS(); +} + static void sdl3_present(void* platform_data) { if (!platform_data) return; diff --git a/src/hal/pxl8_mem.c b/src/hal/pxl8_mem.c new file mode 100644 index 0000000..15d39d8 --- /dev/null +++ b/src/hal/pxl8_mem.c @@ -0,0 +1,20 @@ +#include "pxl8_mem.h" + +#include +#include + +void* pxl8_malloc(usize size) { + return malloc(size); +} + +void* pxl8_calloc(usize count, usize size) { + return calloc(count, size); +} + +void* pxl8_realloc(void* ptr, usize size) { + return realloc(ptr, size); +} + +void pxl8_free(void* ptr) { + free(ptr); +} diff --git a/src/hal/pxl8_thread_sdl3.c b/src/hal/pxl8_thread_sdl3.c new file mode 100644 index 0000000..4eccdfb --- /dev/null +++ b/src/hal/pxl8_thread_sdl3.c @@ -0,0 +1,110 @@ +#include "pxl8_hal.h" + +#include + +#include "pxl8_mem.h" + +struct pxl8_thread { + SDL_Thread* handle; +}; + +struct pxl8_mutex { + SDL_Mutex* handle; +}; + +struct pxl8_cond { + SDL_Condition* handle; +}; + +pxl8_thread* pxl8_thread_create(pxl8_thread_fn fn, const char* name, void* data) { + pxl8_thread* t = pxl8_calloc(1, sizeof(pxl8_thread)); + if (!t) return NULL; + + t->handle = SDL_CreateThread((SDL_ThreadFunction)fn, name, data); + if (!t->handle) { + pxl8_free(t); + return NULL; + } + + return t; +} + +void pxl8_thread_wait(pxl8_thread* thread, int* status) { + if (!thread || !thread->handle) return; + SDL_WaitThread(thread->handle, status); + pxl8_free(thread); +} + +void pxl8_thread_detach(pxl8_thread* thread) { + if (!thread || !thread->handle) return; + SDL_DetachThread(thread->handle); + pxl8_free(thread); +} + +pxl8_mutex* pxl8_mutex_create(void) { + pxl8_mutex* m = pxl8_calloc(1, sizeof(pxl8_mutex)); + if (!m) return NULL; + + m->handle = SDL_CreateMutex(); + if (!m->handle) { + pxl8_free(m); + return NULL; + } + + return m; +} + +void pxl8_mutex_destroy(pxl8_mutex* mutex) { + if (!mutex) return; + if (mutex->handle) SDL_DestroyMutex(mutex->handle); + pxl8_free(mutex); +} + +void pxl8_mutex_lock(pxl8_mutex* mutex) { + if (!mutex || !mutex->handle) return; + SDL_LockMutex(mutex->handle); +} + +void pxl8_mutex_unlock(pxl8_mutex* mutex) { + if (!mutex || !mutex->handle) return; + SDL_UnlockMutex(mutex->handle); +} + +bool pxl8_mutex_trylock(pxl8_mutex* mutex) { + if (!mutex || !mutex->handle) return false; + return SDL_TryLockMutex(mutex->handle); +} + +pxl8_cond* pxl8_cond_create(void) { + pxl8_cond* c = pxl8_calloc(1, sizeof(pxl8_cond)); + if (!c) return NULL; + + c->handle = SDL_CreateCondition(); + if (!c->handle) { + pxl8_free(c); + return NULL; + } + + return c; +} + +void pxl8_cond_destroy(pxl8_cond* cond) { + if (!cond) return; + if (cond->handle) SDL_DestroyCondition(cond->handle); + pxl8_free(cond); +} + +void pxl8_cond_signal(pxl8_cond* cond) { + if (!cond || !cond->handle) return; + SDL_SignalCondition(cond->handle); +} + +void pxl8_cond_broadcast(pxl8_cond* cond) { + if (!cond || !cond->handle) return; + SDL_BroadcastCondition(cond->handle); +} + +void pxl8_cond_wait(pxl8_cond* cond, pxl8_mutex* mutex) { + if (!cond || !cond->handle || !mutex || !mutex->handle) return; + SDL_WaitCondition(cond->handle, mutex->handle); +} diff --git a/src/lua/pxl8/effects.lua b/src/lua/pxl8/effects.lua index 7edc0a2..a6221b5 100644 --- a/src/lua/pxl8/effects.lua +++ b/src/lua/pxl8/effects.lua @@ -56,6 +56,14 @@ function Lights.new(capacity) end function Lights:add(x, y, z, r, g, b, intensity, radius) + if r and r > 255 then + local rgb = r + intensity = g + radius = b + r = bit.band(bit.rshift(rgb, 16), 0xFF) + g = bit.band(bit.rshift(rgb, 8), 0xFF) + b = bit.band(rgb, 0xFF) + end C.pxl8_lights_add(self._ptr, x, y, z, r or 255, g or 255, b or 255, intensity or 255, radius or 10) end diff --git a/src/lua/pxl8/gfx2d.lua b/src/lua/pxl8/gfx2d.lua index 3f852d0..16ae850 100644 --- a/src/lua/pxl8/gfx2d.lua +++ b/src/lua/pxl8/gfx2d.lua @@ -49,10 +49,9 @@ function graphics.load_palette(filepath) end function graphics.load_sprite(filepath) - local sprite_id = ffi.new("unsigned int[1]") - local result = C.pxl8_gfx_load_sprite(core.gfx, filepath, sprite_id) - if result == 0 then - return sprite_id[0] + local result = C.pxl8_gfx_load_sprite(core.gfx, filepath) + if result >= 0 and result < 100 then + return result else return nil, result end diff --git a/src/lua/pxl8/gui.lua b/src/lua/pxl8/gui.lua index b1f379c..395f584 100644 --- a/src/lua/pxl8/gui.lua +++ b/src/lua/pxl8/gui.lua @@ -23,6 +23,18 @@ function Gui:button(id, x, y, w, h, label) return C.pxl8_gui_button(self._ptr, core.gfx, id, x, y, w, h, label) end +function Gui:slider(id, x, y, w, h, value, min_val, max_val) + local val = ffi.new("float[1]", value) + local changed = C.pxl8_gui_slider(self._ptr, core.gfx, id, x, y, w, h, val, min_val, max_val) + return changed, val[0] +end + +function Gui:slider_int(id, x, y, w, h, value, min_val, max_val) + local val = ffi.new("i32[1]", value) + local changed = C.pxl8_gui_slider_int(self._ptr, core.gfx, id, x, y, w, h, val, min_val, max_val) + return changed, val[0] +end + function Gui:cursor_down() C.pxl8_gui_cursor_down(self._ptr) end diff --git a/src/lua/pxl8/net.lua b/src/lua/pxl8/net.lua index 33f30cb..ef59901 100644 --- a/src/lua/pxl8/net.lua +++ b/src/lua/pxl8/net.lua @@ -15,91 +15,20 @@ function net.get() return setmetatable({ _ptr = ptr }, Net) end +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:entities() - local snap = C.pxl8_net_snapshot(self._ptr) - if snap == nil then - return {} - end - local ents = C.pxl8_net_entities(self._ptr) - if ents == nil then - return {} - end - local result = {} - for i = 0, snap.entity_count - 1 do - result[i + 1] = { - entity_id = tonumber(ents[i].entity_id), - userdata = ents[i].userdata - } - end - return result +function Net:enter_chunk(chunk_id) + return C.pxl8_net_enter_chunk(self._ptr, chunk_id or 1) == 0 end -function Net:entity_prev_userdata(entity_id) - return C.pxl8_net_entity_prev_userdata(self._ptr, entity_id) -end - -function Net:entity_userdata(entity_id) - return C.pxl8_net_entity_userdata(self._ptr, entity_id) -end - -function Net:input_at(tick) - local input = C.pxl8_net_input_at(self._ptr, tick) - if input == nil then return nil end - return { - buttons = input.buttons, - look_dx = input.look_dx, - look_dy = input.look_dy, - move_x = input.move_x, - move_y = input.move_y, - yaw = input.yaw, - tick = tonumber(input.tick), - timestamp = tonumber(input.timestamp) - } -end - -function Net:input_oldest_tick() - return tonumber(C.pxl8_net_input_oldest_tick(self._ptr)) -end - -function Net:input_push(input) - local msg = ffi.new("pxl8_input_msg") - msg.buttons = input.buttons or 0 - msg.look_dx = input.look_dx or 0 - msg.look_dy = input.look_dy or 0 - msg.move_x = input.move_x or 0 - msg.move_y = input.move_y or 0 - msg.yaw = input.yaw or 0 - msg.tick = input.tick or 0 - msg.timestamp = input.timestamp or 0 - C.pxl8_net_input_push(self._ptr, msg) -end - -function Net:lerp_alpha() - return C.pxl8_net_lerp_alpha(self._ptr) -end - -function Net:needs_correction() - return C.pxl8_net_needs_correction(self._ptr) -end - -function Net:player_id() - return tonumber(C.pxl8_net_player_id(self._ptr)) -end - -function Net:predicted_state() - return C.pxl8_net_predicted_state(self._ptr) -end - -function Net:predicted_tick_set(tick) - C.pxl8_net_predicted_tick_set(self._ptr, tick) -end - -function Net:send_command(cmd) - return C.pxl8_net_send_command(self._ptr, cmd) == 0 +function Net:exit_chunk(x, y, z) + return C.pxl8_net_exit_chunk(self._ptr, x or 0, y or 0, z or 0) == 0 end function Net:send_input(input) @@ -115,35 +44,12 @@ function Net:send_input(input) return C.pxl8_net_send_input(self._ptr, msg) == 0 end -function Net:snapshot() - local snap = C.pxl8_net_snapshot(self._ptr) - if snap == nil then - return nil - end - return { - entity_count = snap.entity_count, - event_count = snap.event_count, - player_id = tonumber(snap.player_id), - tick = tonumber(snap.tick), - time = snap.time - } +function Net:set_chunk_settings(render_distance, sim_distance) + return C.pxl8_net_send_chunk_settings(self._ptr, render_distance or 3, sim_distance or 4) == 0 end function Net:spawn(x, y, z, yaw, pitch) - local cmd = ffi.new("pxl8_command_msg") - cmd.cmd_type = C.PXL8_CMD_SPAWN_ENTITY - C.pxl8_pack_f32_be(cmd.payload, 0, x or 0) - C.pxl8_pack_f32_be(cmd.payload, 4, y or 0) - C.pxl8_pack_f32_be(cmd.payload, 8, z or 0) - C.pxl8_pack_f32_be(cmd.payload, 12, yaw or 0) - C.pxl8_pack_f32_be(cmd.payload, 16, pitch or 0) - cmd.payload_size = 20 - cmd.tick = 0 - return self:send_command(cmd) -end - -function Net:tick() - return tonumber(C.pxl8_net_tick(self._ptr)) + return C.pxl8_net_spawn(self._ptr, x or 0, y or 0, z or 0, yaw or 0, pitch or 0) == 0 end return net diff --git a/src/lua/pxl8/shader.lua b/src/lua/pxl8/shader.lua new file mode 100644 index 0000000..2dab54b --- /dev/null +++ b/src/lua/pxl8/shader.lua @@ -0,0 +1,281 @@ +local ffi = require("ffi") +local bit = require("bit") +local core = require("pxl8.core") + +ffi.cdef[[ + u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx); + u32* pxl8_gfx_get_light_accum(pxl8_gfx* gfx); + const u32* pxl8_gfx_palette_colors(pxl8_gfx* gfx); + u16* pxl8_gfx_get_zbuffer(pxl8_gfx* gfx); +]] + +local C = ffi.C +local band, bor, rshift, lshift = bit.band, bit.bor, bit.rshift, bit.lshift +local floor, sqrt, max, min, abs = math.floor, math.sqrt, math.max, math.min, math.abs +local sin, cos, fmod = math.sin, math.cos, math.fmod + +local shader = {} + +local fb = ffi.new("u8*") +local light = ffi.new("u32*") +local pal = ffi.new("const u32*") +local zbuf = ffi.new("u16*") +local w, h, count = 0, 0, 0 + +function shader.begin_frame() + fb = C.pxl8_gfx_get_framebuffer_indexed(core.gfx) + light = C.pxl8_gfx_get_light_accum(core.gfx) + pal = C.pxl8_gfx_palette_colors(core.gfx) + zbuf = C.pxl8_gfx_get_zbuffer(core.gfx) + w = C.pxl8_gfx_get_width(core.gfx) + h = C.pxl8_gfx_get_height(core.gfx) + count = w * h +end + +function shader.get_buffers() + return fb, light, pal, zbuf, w, h +end + +local function clamp(x, lo, hi) + if x < lo then return lo end + if x > hi then return hi end + return x +end + +shader.resolve_tint = function() + if fb == nil or light == nil or pal == nil then return end + local fb_l, light_l, pal_l = fb, light, pal + local count_l = count + local band_l, rshift_l, bor_l, lshift_l = band, rshift, bor, lshift + local floor_l = floor + + for i = 0, count_l - 1 do + local lv = light_l[i] + if lv ~= 0 then + local a = rshift_l(lv, 24) + if a > 0 then + local base = pal_l[fb_l[i]] + local br = band_l(base, 0xFF) + local bg = band_l(rshift_l(base, 8), 0xFF) + local bb = band_l(rshift_l(base, 16), 0xFF) + + local lr = band_l(lv, 0xFF) + local lg = band_l(rshift_l(lv, 8), 0xFF) + local lb = band_l(rshift_l(lv, 16), 0xFF) + + local t = a * 0.00392156862 + local r = floor_l(br + (lr - 128) * t * 2) + local g = floor_l(bg + (lg - 128) * t * 2) + local b = floor_l(bb + (lb - 128) * t * 2) + + if r < 0 then r = 0 elseif r > 255 then r = 255 end + if g < 0 then g = 0 elseif g > 255 then g = 255 end + if b < 0 then b = 0 elseif b > 255 then b = 255 end + + light_l[i] = bor_l(r, lshift_l(g, 8), lshift_l(b, 16), 0xFF000000) + end + end + end +end + +shader.compile = function(source) + local header = [[ +local ffi = require("ffi") +local bit = require("bit") +local band, bor, rshift, lshift = bit.band, bit.bor, bit.rshift, bit.lshift +local floor, sqrt, max, min, abs = math.floor, math.sqrt, math.max, math.min, math.abs +local sin, cos, fmod = math.sin, math.cos, math.fmod +local clamp = function(x, lo, hi) if x < lo then return lo elseif x > hi then return hi else return x end end +local mix = function(a, b, t) return a + (b - a) * t end +local smoothstep = function(e0, e1, x) local t = clamp((x - e0) / (e1 - e0), 0, 1); return t * t * (3 - 2 * t) end +local saturate = function(x) if x < 0 then return 0 elseif x > 1 then return 1 else return x end end +local length2 = function(x, y) return sqrt(x*x + y*y) end +local length3 = function(x, y, z) return sqrt(x*x + y*y + z*z) end +local dot2 = function(ax, ay, bx, by) return ax*bx + ay*by end +local dot3 = function(ax, ay, az, bx, by, bz) return ax*bx + ay*by + az*bz end +local fract = function(x) return x - floor(x) end + +return function(fb, light, pal, zbuf, w, h, uniforms) + uniforms = uniforms or {} + local count = w * h + local time = uniforms.time or 0 + local band_l, rshift_l, bor_l, lshift_l = band, rshift, bor, lshift + local floor_l, sqrt_l, max_l, min_l = floor, sqrt, max, min + + for i = 0, count - 1 do + local x = i % w + local y = floor_l(i / w) + local uv_x = x / w + local uv_y = y / h + + local idx = fb[i] + local base = pal[idx] + local br = band_l(base, 0xFF) + local bg = band_l(rshift_l(base, 8), 0xFF) + local bb = band_l(rshift_l(base, 16), 0xFF) + + local lv = light[i] + local lr = band_l(lv, 0xFF) + local lg = band_l(rshift_l(lv, 8), 0xFF) + local lb = band_l(rshift_l(lv, 16), 0xFF) + local la = rshift_l(lv, 24) + + local depth = zbuf and zbuf[i] or 0 + local depth_n = depth / 65535.0 + + local r, g, b = br, bg, bb + +]] + + local footer = [[ + + r = floor_l(clamp(r, 0, 255)) + g = floor_l(clamp(g, 0, 255)) + b = floor_l(clamp(b, 0, 255)) + light[i] = bor_l(r, lshift_l(g, 8), lshift_l(b, 16), 0xFF000000) + end +end +]] + + local code = header .. source .. footer + local fn, err = loadstring(code) + if not fn then + error("Shader compile error: " .. tostring(err)) + end + return fn() +end + +shader.run = function(compiled_shader, uniforms) + if fb == nil or light == nil or pal == nil then return end + compiled_shader(fb, light, pal, zbuf, w, h, uniforms) +end + +shader.presets = {} + +shader.presets.passthrough = shader.compile([[ + -- passthrough: just use base color +]]) + +shader.presets.light_tint = shader.compile([[ + if la > 0 then + local t = la / 255.0 + r = br + (lr - 128) * t * 2 + g = bg + (lg - 128) * t * 2 + b = bb + (lb - 128) * t * 2 + end +]]) + +shader.presets.vignette = shader.compile([[ + local cx = uv_x - 0.5 + local cy = uv_y - 0.5 + local dist = sqrt_l(cx*cx + cy*cy) + local vig = 1.0 - saturate(dist * 1.5) + vig = vig * vig + r = br * vig + g = bg * vig + b = bb * vig +]]) + +shader.presets.scanlines = shader.compile([[ + local scan = 0.8 + 0.2 * (y % 2) + r = br * scan + g = bg * scan + b = bb * scan +]]) + +shader.presets.crt = shader.compile([[ + -- Scanlines + local scan = 0.85 + 0.15 * (y % 2) + -- Vignette + local cx = uv_x - 0.5 + local cy = uv_y - 0.5 + local dist = sqrt_l(cx*cx + cy*cy) + local vig = 1.0 - saturate(dist * 1.2) + -- RGB shift based on x position + local shift = (uv_x - 0.5) * 0.02 + local mult = scan * vig + r = br * mult * (1.0 + shift) + g = bg * mult + b = bb * mult * (1.0 - shift) +]]) + +shader.presets.dither_fade = shader.compile([[ + local threshold = fract(sin(x * 12.9898 + y * 78.233) * 43758.5453) + local fade = uniforms.fade or 0.5 + if threshold > fade then + r, g, b = 0, 0, 0 + else + r, g, b = br, bg, bb + end +]]) + +shader.presets.fog = shader.compile([[ + local fog_color_r = uniforms.fog_r or 32 + local fog_color_g = uniforms.fog_g or 32 + local fog_color_b = uniforms.fog_b or 48 + local fog_start = uniforms.fog_start or 0.3 + local fog_end = uniforms.fog_end or 0.9 + local fog_t = saturate((depth_n - fog_start) / (fog_end - fog_start)) + fog_t = fog_t * fog_t + r = mix(br, fog_color_r, fog_t) + g = mix(bg, fog_color_g, fog_t) + b = mix(bb, fog_color_b, fog_t) +]]) + +shader.presets.light_with_fog = shader.compile([[ + -- Apply light tint first + if la > 0 then + local t = la / 255.0 + r = br + (lr - 128) * t * 2 + g = bg + (lg - 128) * t * 2 + b = bb + (lb - 128) * t * 2 + else + r, g, b = br, bg, bb + end + -- Then fog + local fog_r = uniforms.fog_r or 16 + local fog_g = uniforms.fog_g or 16 + local fog_b = uniforms.fog_b or 24 + local fog_start = uniforms.fog_start or 0.2 + local fog_end = uniforms.fog_end or 0.95 + local fog_t = saturate((depth_n - fog_start) / (fog_end - fog_start)) + fog_t = fog_t * fog_t + r = mix(r, fog_r, fog_t) + g = mix(g, fog_g, fog_t) + b = mix(b, fog_b, fog_t) +]]) + +shader.presets.posterize = shader.compile([[ + local levels = uniforms.levels or 4 + local step = 255 / levels + r = floor_l(br / step) * step + g = floor_l(bg / step) * step + b = floor_l(bb / step) * step +]]) + +shader.presets.chromatic = shader.compile([=[ + local amount = uniforms.amount or 2 + local ox = floor_l(amount * (uv_x - 0.5)) + local r_i = clamp(i - ox, 0, count - 1) + local b_i = clamp(i + ox, 0, count - 1) + local r_base = pal[fb[r_i]] + local b_base = pal[fb[b_i]] + r = band_l(r_base, 0xFF) + g = bg + b = band_l(rshift_l(b_base, 16), 0xFF) +]=]) + +shader.clear_light = function() + if light == nil then return end + ffi.fill(light, count * 4, 0) +end + +shader.fill_output = function(r, g, b) + if light == nil then return end + local color = bor(r, lshift(g, 8), lshift(b, 16), 0xFF000000) + for i = 0, count - 1 do + light[i] = color + end +end + +return shader diff --git a/src/lua/pxl8/world.lua b/src/lua/pxl8/world.lua index eebcf38..bb93c62 100644 --- a/src/lua/pxl8/world.lua +++ b/src/lua/pxl8/world.lua @@ -6,8 +6,6 @@ local world = {} world.CHUNK_VXL = 0 world.CHUNK_BSP = 1 -world.PROCGEN_ROOMS = C.PXL8_PROCGEN_ROOMS -world.PROCGEN_TERRAIN = C.PXL8_PROCGEN_TERRAIN local Bsp = {} Bsp.__index = Bsp @@ -24,14 +22,6 @@ function Bsp:face_set_material(face_id, material_id) C.pxl8_bsp_face_set_material(self._ptr, face_id, material_id) end -function Bsp:set_material(material_id, material) - C.pxl8_bsp_set_material(self._ptr, material_id, material._ptr) -end - -function Bsp:set_wireframe(wireframe) - C.pxl8_bsp_set_wireframe(self._ptr, wireframe) -end - world.Bsp = Bsp local Chunk = {} @@ -45,21 +35,26 @@ function Chunk:bsp() return setmetatable({ _ptr = ptr }, Bsp) end -function Chunk:type() - if self._ptr == nil then return nil end - return self._ptr.type -end - function Chunk:ready() if self._ptr == nil then return false end if self._ptr.type == world.CHUNK_BSP then return self._ptr.bsp ~= nil elseif self._ptr.type == world.CHUNK_VXL then - return self._ptr.voxel ~= nil + return self._ptr.voxels ~= nil end return false end +function Chunk:type() + if self._ptr == nil then return nil end + return self._ptr.type +end + +function Chunk:version() + if self._ptr == nil then return 0 end + return self._ptr.version +end + world.Chunk = Chunk local World = {} @@ -77,9 +72,32 @@ function World:active_chunk() return setmetatable({ _ptr = ptr }, Chunk) end -function World:check_collision(x, y, z, radius) - local pos = ffi.new("pxl8_vec3", {x = x, y = y, z = z}) - return C.pxl8_world_check_collision(self._ptr, pos, radius) +function World:get_render_distance() + return C.pxl8_world_get_render_distance(self._ptr) +end + +function World:get_sim_distance() + return C.pxl8_world_get_sim_distance(self._ptr) +end + +function World:init_local_player(x, y, z) + C.pxl8_world_init_local_player(self._ptr, x, y, z) +end + +function World:local_player() + local ptr = C.pxl8_world_local_player(self._ptr) + if ptr == nil then return nil end + return ptr +end + +function World:point_solid(x, y, z) + return C.pxl8_world_point_solid(self._ptr, x, y, z) +end + +function World:ray(from_x, from_y, from_z, to_x, to_y, to_z) + local from = ffi.new("pxl8_vec3", {x = from_x, y = from_y, z = from_z}) + local to = ffi.new("pxl8_vec3", {x = to_x, y = to_y, z = to_z}) + return C.pxl8_world_ray(self._ptr, from, to) end function World:render(camera_pos) @@ -87,11 +105,26 @@ function World:render(camera_pos) C.pxl8_world_render(self._ptr, core.gfx, vec) end -function World:resolve_collision(from_x, from_y, from_z, to_x, to_y, to_z, radius) +function World:set_bsp_material(material_id, material) + C.pxl8_world_set_bsp_material(self._ptr, material_id, material._ptr) +end + +function World:set_render_distance(distance) + C.pxl8_world_set_render_distance(self._ptr, distance) +end + +function World:set_sim_distance(distance) + C.pxl8_world_set_sim_distance(self._ptr, distance) +end + +function World:set_wireframe(enabled) + C.pxl8_world_set_wireframe(self._ptr, enabled) +end + +function World:sweep(from_x, from_y, from_z, to_x, to_y, to_z, radius) local from = ffi.new("pxl8_vec3", {x = from_x, y = from_y, z = from_z}) local to = ffi.new("pxl8_vec3", {x = to_x, y = to_y, z = to_z}) - local result = C.pxl8_world_resolve_collision(self._ptr, from, to, radius) - return result.x, result.y, result.z + return C.pxl8_world_sweep(self._ptr, from, to, radius) end world.World = World diff --git a/src/math/pxl8_math.c b/src/math/pxl8_math.c index bd85905..2efb8f5 100644 --- a/src/math/pxl8_math.c +++ b/src/math/pxl8_math.c @@ -9,6 +9,23 @@ u32 pxl8_hash32(u32 x) { return x; } +u64 pxl8_hash64(u64 x) { + x ^= x >> 33; + x *= 0xff51afd7ed558ccdULL; + x ^= x >> 33; + x *= 0xc4ceb9fe1a85ec53ULL; + x ^= x >> 33; + return x; +} + +f32 pxl8_lerp_f32(f32 a, f32 b, f32 t) { + return a + (b - a) * t; +} + +f32 pxl8_smoothstep(f32 t) { + return t * t * (3.0f - 2.0f * t); +} + pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b) { return (pxl8_vec2){ .x = a.x + b.x, diff --git a/src/math/pxl8_math.h b/src/math/pxl8_math.h index df4ff1b..aa63044 100644 --- a/src/math/pxl8_math.h +++ b/src/math/pxl8_math.h @@ -81,11 +81,34 @@ typedef struct pxl8_projected_point { bool visible; } pxl8_projected_point; +typedef struct pxl8_ray { + pxl8_vec3 normal; + pxl8_vec3 point; + f32 fraction; + bool hit; +} pxl8_ray; + +#define PXL8_SDF_X 32 +#define PXL8_SDF_Y 16 +#define PXL8_SDF_Z 32 +#define PXL8_SDF_SIZE (PXL8_SDF_X * PXL8_SDF_Y * PXL8_SDF_Z) +#define PXL8_SDF_CELL 16.0f + +typedef struct pxl8_sdf { + i8 data[PXL8_SDF_SIZE]; + pxl8_vec3 origin; + f32 cell; +} pxl8_sdf; + #ifdef __cplusplus extern "C" { #endif u32 pxl8_hash32(u32 x); +u64 pxl8_hash64(u64 x); + +f32 pxl8_lerp_f32(f32 a, f32 b, f32 t); +f32 pxl8_smoothstep(f32 t); pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b); f32 pxl8_vec2_dot(pxl8_vec2 a, pxl8_vec2 b); diff --git a/src/math/pxl8_noise.c b/src/math/pxl8_noise.c new file mode 100644 index 0000000..df3fa1f --- /dev/null +++ b/src/math/pxl8_noise.c @@ -0,0 +1,94 @@ +#include "pxl8_noise.h" + +f32 pxl8_noise2d(i32 x, i32 z, u64 seed) { + u64 h = pxl8_hash64(seed ^ (u64)x ^ ((u64)z << 32)); + return (f32)(h & 0xFFFF) / 65535.0f; +} + +f32 pxl8_noise3d(i32 x, i32 y, i32 z, u64 seed) { + u64 h = pxl8_hash64(seed ^ (u64)x ^ ((u64)y << 21) ^ ((u64)z << 42)); + return (f32)(h & 0xFFFF) / 65535.0f; +} + +f32 pxl8_value_noise(f32 x, f32 z, u64 seed) { + i32 x0 = (i32)floorf(x); + i32 z0 = (i32)floorf(z); + i32 x1 = x0 + 1; + i32 z1 = z0 + 1; + + f32 tx = pxl8_smoothstep(x - (f32)x0); + f32 tz = pxl8_smoothstep(z - (f32)z0); + + f32 c00 = pxl8_noise2d(x0, z0, seed); + f32 c10 = pxl8_noise2d(x1, z0, seed); + f32 c01 = pxl8_noise2d(x0, z1, seed); + f32 c11 = pxl8_noise2d(x1, z1, seed); + + f32 a = pxl8_lerp_f32(c00, c10, tx); + f32 b = pxl8_lerp_f32(c01, c11, tx); + return pxl8_lerp_f32(a, b, tz); +} + +f32 pxl8_value_noise3d(f32 x, f32 y, f32 z, u64 seed) { + i32 x0 = (i32)floorf(x); + i32 y0 = (i32)floorf(y); + i32 z0 = (i32)floorf(z); + i32 x1 = x0 + 1; + i32 y1 = y0 + 1; + i32 z1 = z0 + 1; + + f32 tx = pxl8_smoothstep(x - (f32)x0); + f32 ty = pxl8_smoothstep(y - (f32)y0); + f32 tz = pxl8_smoothstep(z - (f32)z0); + + f32 c000 = pxl8_noise3d(x0, y0, z0, seed); + f32 c100 = pxl8_noise3d(x1, y0, z0, seed); + f32 c010 = pxl8_noise3d(x0, y1, z0, seed); + f32 c110 = pxl8_noise3d(x1, y1, z0, seed); + f32 c001 = pxl8_noise3d(x0, y0, z1, seed); + f32 c101 = pxl8_noise3d(x1, y0, z1, seed); + f32 c011 = pxl8_noise3d(x0, y1, z1, seed); + f32 c111 = pxl8_noise3d(x1, y1, z1, seed); + + f32 a00 = pxl8_lerp_f32(c000, c100, tx); + f32 a10 = pxl8_lerp_f32(c010, c110, tx); + f32 a01 = pxl8_lerp_f32(c001, c101, tx); + f32 a11 = pxl8_lerp_f32(c011, c111, tx); + + f32 b0 = pxl8_lerp_f32(a00, a10, ty); + f32 b1 = pxl8_lerp_f32(a01, a11, ty); + + return pxl8_lerp_f32(b0, b1, tz); +} + +f32 pxl8_fbm(f32 x, f32 z, u64 seed, u32 octaves) { + f32 value = 0.0f; + f32 amplitude = 1.0f; + f32 frequency = 1.0f; + f32 max_value = 0.0f; + + for (u32 i = 0; i < octaves; i++) { + value += amplitude * pxl8_value_noise(x * frequency, z * frequency, seed + (u64)i * 1000); + max_value += amplitude; + amplitude *= 0.5f; + frequency *= 2.0f; + } + + return value / max_value; +} + +f32 pxl8_fbm3d(f32 x, f32 y, f32 z, u64 seed, u32 octaves) { + f32 value = 0.0f; + f32 amplitude = 1.0f; + f32 frequency = 1.0f; + f32 max_value = 0.0f; + + for (u32 i = 0; i < octaves; i++) { + value += amplitude * pxl8_value_noise3d(x * frequency, y * frequency, z * frequency, seed + (u64)i * 1000); + max_value += amplitude; + amplitude *= 0.5f; + frequency *= 2.0f; + } + + return value / max_value; +} diff --git a/src/math/pxl8_noise.h b/src/math/pxl8_noise.h new file mode 100644 index 0000000..b6cb53c --- /dev/null +++ b/src/math/pxl8_noise.h @@ -0,0 +1,18 @@ +#pragma once + +#include "pxl8_math.h" + +#ifdef __cplusplus +extern "C" { +#endif + +f32 pxl8_noise2d(i32 x, i32 z, u64 seed); +f32 pxl8_noise3d(i32 x, i32 y, i32 z, u64 seed); +f32 pxl8_value_noise(f32 x, f32 z, u64 seed); +f32 pxl8_value_noise3d(f32 x, f32 y, f32 z, u64 seed); +f32 pxl8_fbm(f32 x, f32 z, u64 seed, u32 octaves); +f32 pxl8_fbm3d(f32 x, f32 y, f32 z, u64 seed, u32 octaves); + +#ifdef __cplusplus +} +#endif diff --git a/src/net/pxl8_net.c b/src/net/pxl8_net.c index 18eb45f..f2f886b 100644 --- a/src/net/pxl8_net.c +++ b/src/net/pxl8_net.c @@ -1,11 +1,21 @@ #include "pxl8_net.h" -#include "pxl8_chunk_cache.h" -#include "pxl8_log.h" -#include "pxl8_mem.h" #include #include +#include "pxl8_hal.h" + +#ifdef PXL8_ASYNC_THREADS +#include +#include "pxl8_queue.h" +#endif + +#include "pxl8_bytes.h" +#include "pxl8_log.h" +#include "pxl8_mem.h" +#include "pxl8_world.h" +#include "pxl8_world_chunk_cache.h" + #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include @@ -30,28 +40,42 @@ struct pxl8_net { char address[256]; + bool connected; + u16 port; + struct sockaddr_in server_addr; + socket_t sock; + + pxl8_world_chunk_cache* chunk_cache; u32 chunk_id; u8 chunk_type; - pxl8_chunk_cache* chunk_cache; - bool connected; + 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]; - u64 highest_tick; - pxl8_input_msg input_history[PXL8_NET_INPUT_HISTORY_SIZE]; - u64 input_head; - u64 input_oldest_tick; - f32 interp_time; - u16 port; - u8 predicted_state[PXL8_NET_USERDATA_SIZE]; - u64 predicted_tick; pxl8_entity_state prev_entities[PXL8_MAX_SNAPSHOT_ENTITIES]; pxl8_snapshot_header prev_snapshot; + pxl8_snapshot_header snapshot; + + u64 input_head; + pxl8_input_msg input_history[PXL8_NET_INPUT_HISTORY_SIZE]; + u64 input_oldest_tick; + + u8 predicted_state[PXL8_NET_USERDATA_SIZE]; + u64 predicted_tick; + u8 recv_buf[4096]; u8 send_buf[4096]; - u32 sequence; - struct sockaddr_in server_addr; - pxl8_snapshot_header snapshot; - socket_t sock; + +#ifdef PXL8_ASYNC_THREADS + pxl8_thread* recv_thread; + atomic_bool running; + pxl8_queue recv_queue; +#endif }; static const pxl8_entity_state* find_entity(const pxl8_entity_state* entities, u16 count, u64 id) { @@ -225,7 +249,7 @@ bool pxl8_net_poll(pxl8_net* net) { payload_len = len - offset; } - pxl8_chunk_cache_receive(net->chunk_cache, &chunk_hdr, payload, payload_len); + pxl8_world_chunk_cache_receive(net->chunk_cache, &chunk_hdr, payload, payload_len); return true; } @@ -238,6 +262,12 @@ bool pxl8_net_poll(pxl8_net* net) { 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; @@ -260,6 +290,10 @@ bool pxl8_net_poll(pxl8_net* net) { net->recv_buf + offset, len - offset, &net->entities[i]); } + if (net->world) { + pxl8_world_reconcile(net->world, net, 1.0f / 30.0f); + } + return true; } @@ -312,6 +346,8 @@ pxl8_result pxl8_net_send_command(pxl8_net* net, const pxl8_command_msg* cmd) { pxl8_result pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input) { if (!net || !net->connected) return PXL8_ERROR_INVALID_ARGUMENT; + pxl8_net_input_push(net, input); + u8 buf[sizeof(pxl8_msg_header) + sizeof(pxl8_input_msg)]; pxl8_msg_header hdr = { .type = PXL8_MSG_INPUT, @@ -338,19 +374,25 @@ 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; } -void pxl8_net_set_chunk_cache(pxl8_net* net, pxl8_chunk_cache* cache) { +void pxl8_net_set_chunk_cache(pxl8_net* net, pxl8_world_chunk_cache* cache) { if (!net) return; net->chunk_cache = cache; } -pxl8_chunk_cache* pxl8_net_chunk_cache(pxl8_net* net) { +pxl8_world_chunk_cache* pxl8_net_chunk_cache(pxl8_net* net) { if (!net) return NULL; return net->chunk_cache; } +void pxl8_net_set_world(pxl8_net* net, pxl8_world* world) { + if (!net) return; + net->world = world; +} + u32 pxl8_net_chunk_id(const pxl8_net* net) { if (!net) return 0; return net->chunk_id; @@ -360,3 +402,186 @@ u8 pxl8_net_chunk_type(const pxl8_net* net) { if (!net) return PXL8_CHUNK_TYPE_VXL; return net->chunk_type; } + +pxl8_result pxl8_net_send_chunk_settings(pxl8_net* net, i32 render_distance, i32 sim_distance) { + 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_SET_CHUNK_SETTINGS; + pxl8_pack_i32_be(cmd.payload, 0, render_distance); + pxl8_pack_i32_be(cmd.payload, 4, sim_distance); + cmd.payload_size = 8; + + return pxl8_net_send_command(net, &cmd); +} + +pxl8_result pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch) { + 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_SPAWN_ENTITY; + pxl8_pack_f32_be(cmd.payload, 0, x); + pxl8_pack_f32_be(cmd.payload, 4, y); + pxl8_pack_f32_be(cmd.payload, 8, z); + pxl8_pack_f32_be(cmd.payload, 12, yaw); + pxl8_pack_f32_be(cmd.payload, 16, pitch); + cmd.payload_size = 20; + + return pxl8_net_send_command(net, &cmd); +} + +pxl8_result pxl8_net_exit_chunk(pxl8_net* net, 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_EXIT_CHUNK; + pxl8_pack_f32_be(cmd.payload, 0, x); + pxl8_pack_f32_be(cmd.payload, 4, y); + pxl8_pack_f32_be(cmd.payload, 8, z); + cmd.payload_size = 12; + + return pxl8_net_send_command(net, &cmd); +} + +pxl8_result pxl8_net_enter_chunk(pxl8_net* net, u32 chunk_id) { + 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_CHUNK; + pxl8_pack_u32_be(cmd.payload, 0, chunk_id); + cmd.payload_size = 4; + + return pxl8_net_send_command(net, &cmd); +} + +#ifdef PXL8_ASYNC_THREADS + +static int pxl8_net_recv_thread(void* data) { + pxl8_net* net = (pxl8_net*)data; + u8 buf[PXL8_NET_PACKET_MAX_SIZE]; + + while (atomic_load(&net->running)) { + if (!net->connected) { + pxl8_sleep_ms(10); + continue; + } + + struct sockaddr_in from; + socklen_t from_len = sizeof(from); + ssize_t received = recvfrom(net->sock, (char*)buf, sizeof(buf), 0, + (struct sockaddr*)&from, &from_len); + + if (received > 0) { + pxl8_packet* pkt = pxl8_malloc(sizeof(pxl8_packet)); + if (pkt) { + memcpy(pkt->data, buf, (usize)received); + pkt->len = (usize)received; + + if (!pxl8_queue_push(&net->recv_queue, pkt)) { + pxl8_free(pkt); + } + } + } else { + pxl8_sleep_ms(1); + } + } + + return 0; +} + +void pxl8_net_start_thread(pxl8_net* net) { + if (!net || net->recv_thread) return; + + pxl8_queue_init(&net->recv_queue); + atomic_store(&net->running, true); + net->recv_thread = pxl8_thread_create(pxl8_net_recv_thread, "pxl8_net_recv", net); +} + +void pxl8_net_stop_thread(pxl8_net* net) { + if (!net || !net->recv_thread) return; + + atomic_store(&net->running, false); + pxl8_thread_wait(net->recv_thread, NULL); + net->recv_thread = NULL; + + pxl8_packet* pkt; + while ((pkt = pxl8_queue_pop(&net->recv_queue))) { + pxl8_free(pkt); + } +} + +pxl8_packet* pxl8_net_pop_packet(pxl8_net* net) { + if (!net) return NULL; + return (pxl8_packet*)pxl8_queue_pop(&net->recv_queue); +} + +void pxl8_net_packet_free(pxl8_packet* pkt) { + pxl8_free(pkt); +} + +bool pxl8_net_process_packet(pxl8_net* net, const pxl8_packet* pkt) { + if (!net || !pkt || pkt->len < sizeof(pxl8_msg_header)) return false; + + pxl8_msg_header hdr; + usize offset = pxl8_protocol_deserialize_header(pkt->data, pkt->len, &hdr); + + if (hdr.type == PXL8_MSG_CHUNK) { + if (!net->chunk_cache) return false; + + pxl8_chunk_msg_header chunk_hdr; + offset += pxl8_protocol_deserialize_chunk_msg_header(pkt->data + offset, pkt->len - offset, &chunk_hdr); + + const u8* payload = pkt->data + offset; + usize payload_len = chunk_hdr.payload_size; + if (payload_len > pkt->len - offset) { + payload_len = pkt->len - offset; + } + + pxl8_world_chunk_cache_receive(net->chunk_cache, &chunk_hdr, payload, payload_len); + return true; + } + + if (hdr.type == PXL8_MSG_CHUNK_ENTER) { + pxl8_chunk_enter_msg chunk_msg; + pxl8_protocol_deserialize_chunk_enter(pkt->data + offset, pkt->len - offset, &chunk_msg); + net->chunk_id = chunk_msg.chunk_id; + net->chunk_type = chunk_msg.chunk_type; + return true; + } + + if (hdr.type == PXL8_MSG_CHUNK_EXIT) { + net->chunk_id = 0; + net->chunk_type = 0; + return true; + } + + if (hdr.type != PXL8_MSG_SNAPSHOT) return false; + + pxl8_snapshot_header snap; + offset += pxl8_protocol_deserialize_snapshot_header(pkt->data + offset, pkt->len - offset, &snap); + + if (snap.tick <= net->highest_tick) return false; + + memcpy(net->prev_entities, net->entities, sizeof(net->entities)); + net->prev_snapshot = net->snapshot; + + net->highest_tick = snap.tick; + net->snapshot = snap; + net->interp_time = 0.0f; + + u16 count = snap.entity_count; + if (count > PXL8_MAX_SNAPSHOT_ENTITIES) count = PXL8_MAX_SNAPSHOT_ENTITIES; + + for (u16 i = 0; i < count; i++) { + offset += pxl8_protocol_deserialize_entity_state( + pkt->data + offset, pkt->len - offset, &net->entities[i]); + } + + return true; +} + +#endif diff --git a/src/net/pxl8_net.h b/src/net/pxl8_net.h index 5516355..0fdbae8 100644 --- a/src/net/pxl8_net.h +++ b/src/net/pxl8_net.h @@ -9,9 +9,16 @@ extern "C" { #define PXL8_NET_INPUT_HISTORY_SIZE 64 #define PXL8_NET_USERDATA_SIZE 56 +#define PXL8_NET_PACKET_MAX_SIZE 2048 typedef struct pxl8_net pxl8_net; -typedef struct pxl8_chunk_cache pxl8_chunk_cache; +typedef struct pxl8_world pxl8_world; +typedef struct pxl8_world_chunk_cache pxl8_world_chunk_cache; + +typedef struct pxl8_packet { + u8 data[PXL8_NET_PACKET_MAX_SIZE]; + usize len; +} pxl8_packet; typedef struct pxl8_net_config { const char* address; @@ -43,12 +50,27 @@ pxl8_result pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input); const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net); u64 pxl8_net_tick(const pxl8_net* net); void pxl8_net_update(pxl8_net* net, f32 dt); -void pxl8_net_set_chunk_cache(pxl8_net* net, pxl8_chunk_cache* cache); -pxl8_chunk_cache* pxl8_net_chunk_cache(pxl8_net* net); +void pxl8_net_set_chunk_cache(pxl8_net* net, pxl8_world_chunk_cache* cache); +pxl8_world_chunk_cache* pxl8_net_chunk_cache(pxl8_net* net); +void pxl8_net_set_world(pxl8_net* net, pxl8_world* world); u32 pxl8_net_chunk_id(const pxl8_net* net); u8 pxl8_net_chunk_type(const pxl8_net* net); +pxl8_result pxl8_net_send_chunk_settings(pxl8_net* net, i32 render_distance, i32 sim_distance); + +pxl8_result pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch); +pxl8_result pxl8_net_exit_chunk(pxl8_net* net, f32 x, f32 y, f32 z); +pxl8_result pxl8_net_enter_chunk(pxl8_net* net, u32 chunk_id); + +#ifdef PXL8_ASYNC_THREADS +void pxl8_net_start_thread(pxl8_net* net); +void pxl8_net_stop_thread(pxl8_net* net); +pxl8_packet* pxl8_net_pop_packet(pxl8_net* net); +void pxl8_net_packet_free(pxl8_packet* pkt); +bool pxl8_net_process_packet(pxl8_net* net, const pxl8_packet* pkt); +#endif + #ifdef __cplusplus } #endif diff --git a/src/net/pxl8_protocol.h b/src/net/pxl8_protocol.h index 3660987..f3bd88a 100644 --- a/src/net/pxl8_protocol.h +++ b/src/net/pxl8_protocol.h @@ -16,6 +16,7 @@ typedef enum pxl8_msg_type { PXL8_MSG_NONE = 0, PXL8_MSG_CHUNK, PXL8_MSG_CHUNK_ENTER, + PXL8_MSG_CHUNK_EXIT, PXL8_MSG_COMMAND, PXL8_MSG_CONNECT, PXL8_MSG_DISCONNECT, @@ -34,6 +35,9 @@ typedef struct pxl8_msg_header { typedef enum pxl8_cmd_type { PXL8_CMD_NONE = 0, PXL8_CMD_SPAWN_ENTITY, + PXL8_CMD_EXIT_CHUNK, + PXL8_CMD_ENTER_CHUNK, + PXL8_CMD_SET_CHUNK_SETTINGS, } pxl8_cmd_type; typedef struct pxl8_input_msg { @@ -92,17 +96,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_vertices; + u32 visdata_size; } pxl8_bsp_wire_header; usize pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, usize len); diff --git a/src/script/pxl8_script.c b/src/script/pxl8_script.c index dda3c5f..4df3301 100644 --- a/src/script/pxl8_script.c +++ b/src/script/pxl8_script.c @@ -260,7 +260,7 @@ pxl8_script* pxl8_script_create(bool repl_mode) { lua_getglobal(script->L, "require"); lua_pushstring(script->L, "ffi"); if (lua_pcall(script->L, 1, 1, 0) != 0) { - pxl8_script_set_error(script, lua_tostring(script->L, -1)); + pxl8_error("FFI require failed: %s", lua_tostring(script->L, -1)); pxl8_script_destroy(script); return NULL; } @@ -268,7 +268,7 @@ pxl8_script* pxl8_script_create(bool repl_mode) { lua_getfield(script->L, -1, "cdef"); lua_pushstring(script->L, pxl8_ffi_cdefs); if (lua_pcall(script->L, 1, 0, 0) != 0) { - pxl8_script_set_error(script, lua_tostring(script->L, -1)); + pxl8_error("FFI cdef failed: %s", lua_tostring(script->L, -1)); pxl8_script_destroy(script); return NULL; } diff --git a/src/script/pxl8_script_ffi.h b/src/script/pxl8_script_ffi.h index 40a9618..7ec6877 100644 --- a/src/script/pxl8_script_ffi.h +++ b/src/script/pxl8_script_ffi.h @@ -27,7 +27,11 @@ static const char* pxl8_ffi_cdefs = "f32 pxl8_get_fps(const pxl8* sys);\n" "\n" "u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);\n" +"u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx);\n" "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" "pxl8_palette* pxl8_gfx_palette(pxl8_gfx* gfx);\n" "u32 pxl8_palette_color(const pxl8_palette* pal, u8 idx);\n" @@ -59,7 +63,7 @@ static const char* pxl8_ffi_cdefs = "void pxl8_gfx_clear_palette_cycles(pxl8_gfx* ctx);\n" "void pxl8_gfx_update(pxl8_gfx* ctx, f32 dt);\n" "i32 pxl8_gfx_load_palette(pxl8_gfx* ctx, const char* filepath);\n" -"i32 pxl8_gfx_load_sprite(pxl8_gfx* ctx, const char* filepath, u32* sprite_id);\n" +"i32 pxl8_gfx_load_sprite(pxl8_gfx* ctx, const char* filepath);\n" "i32 pxl8_gfx_create_texture(pxl8_gfx* ctx, const u8* pixels, u32 width, u32 height);\n" "bool pxl8_gfx_push_target(pxl8_gfx* ctx);\n" "void pxl8_gfx_pop_target(pxl8_gfx* ctx);\n" @@ -336,22 +340,6 @@ static const char* pxl8_ffi_cdefs = "pxl8_mat4 pxl8_mat4_scale(float x, float y, float z);\n" "pxl8_mat4 pxl8_mat4_translate(float x, float y, float z);\n" "\n" -"typedef enum pxl8_procgen_type {\n" -" PXL8_PROCGEN_ROOMS = 0,\n" -" PXL8_PROCGEN_TERRAIN = 1\n" -"} pxl8_procgen_type;\n" -"\n" -"typedef struct pxl8_procgen_params {\n" -" pxl8_procgen_type type;\n" -" int width;\n" -" int height;\n" -" int depth;\n" -" unsigned int seed;\n" -" int min_room_size;\n" -" int max_room_size;\n" -" int num_rooms;\n" -"} pxl8_procgen_params;\n" -"\n" "typedef enum pxl8_graph_op {\n" " PXL8_OP_CONST = 0,\n" " PXL8_OP_INPUT_AGE,\n" @@ -424,33 +412,58 @@ static const char* pxl8_ffi_cdefs = "void pxl8_graph_eval_texture(const pxl8_graph* graph, u8* buffer, i32 width, i32 height);\n" "\n" "typedef struct pxl8_bsp pxl8_bsp;\n" +"typedef struct pxl8_vxl_chunk pxl8_vxl_chunk;\n" "\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" -"void pxl8_bsp_set_material(pxl8_bsp* bsp, u16 material_id, const pxl8_gfx_material* material);\n" -"void pxl8_bsp_set_wireframe(pxl8_bsp* bsp, bool wireframe);\n" "\n" -"typedef enum { PXL8_CHUNK_VXL = 0, PXL8_CHUNK_BSP = 1 } pxl8_chunk_type;\n" +"typedef enum { PXL8_WORLD_CHUNK_VXL = 0, PXL8_WORLD_CHUNK_BSP = 1 } pxl8_world_chunk_type;\n" "\n" -"typedef struct pxl8_chunk {\n" -" pxl8_chunk_type type;\n" +"typedef struct pxl8_world_chunk {\n" +" pxl8_world_chunk_type type;\n" " u32 id;\n" " u32 version;\n" " i32 cx, cy, cz;\n" " union {\n" -" void* voxel;\n" " pxl8_bsp* bsp;\n" +" pxl8_vxl_chunk* voxels;\n" " };\n" -"} pxl8_chunk;\n" +"} pxl8_world_chunk;\n" "\n" "typedef struct pxl8_world pxl8_world;\n" "\n" +"typedef struct pxl8_ray {\n" +" pxl8_vec3 normal;\n" +" pxl8_vec3 point;\n" +" float fraction;\n" +" bool hit;\n" +"} pxl8_ray;\n" +"\n" "pxl8_world* pxl8_get_world(pxl8* sys);\n" -"pxl8_chunk* pxl8_world_active_chunk(pxl8_world* world);\n" -"bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, float radius);\n" +"pxl8_world_chunk* pxl8_world_active_chunk(pxl8_world* world);\n" +"bool pxl8_world_point_solid(const pxl8_world* world, float x, float y, float z);\n" +"pxl8_ray pxl8_world_ray(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to);\n" +"pxl8_ray pxl8_world_sweep(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, float radius);\n" "void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);\n" -"pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, float radius);\n" +"void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_gfx_material* material);\n" +"void pxl8_world_set_wireframe(pxl8_world* world, bool enabled);\n" +"i32 pxl8_world_get_render_distance(const pxl8_world* world);\n" +"void pxl8_world_set_render_distance(pxl8_world* world, i32 distance);\n" +"i32 pxl8_world_get_sim_distance(const pxl8_world* world);\n" +"void pxl8_world_set_sim_distance(pxl8_world* world, i32 distance);\n" +"\n" +"typedef struct pxl8_sim_entity {\n" +" pxl8_vec3 pos;\n" +" pxl8_vec3 vel;\n" +" f32 yaw;\n" +" f32 pitch;\n" +" u32 flags;\n" +" u16 kind;\n" +" u16 _pad;\n" +"} pxl8_sim_entity;\n" +"\n" +"void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z);\n" +"pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world);\n" "\n" "typedef struct { i32 cursor_x; i32 cursor_y; bool cursor_down; bool cursor_clicked; u32 hot_id; u32 active_id; } pxl8_gui_state;\n" "pxl8_gui_state* pxl8_gui_state_create(void);\n" @@ -461,6 +474,8 @@ static const char* pxl8_ffi_cdefs = "void pxl8_gui_cursor_down(pxl8_gui_state* state);\n" "void pxl8_gui_cursor_up(pxl8_gui_state* state);\n" "bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label);\n" +"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" "void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title);\n" "void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color);\n" "bool pxl8_gui_is_hovering(const pxl8_gui_state* state);\n" @@ -524,7 +539,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_EXIT_CHUNK, PXL8_CMD_ENTER_CHUNK } pxl8_cmd_type;\n" "\n" "typedef struct pxl8_command_msg {\n" " u16 cmd_type;\n" @@ -571,11 +586,17 @@ static const char* pxl8_ffi_cdefs = "f32 pxl8_net_lerp_alpha(const pxl8_net* net);\n" "bool pxl8_net_needs_correction(const pxl8_net* net);\n" "u64 pxl8_net_player_id(const pxl8_net* net);\n" +"u8 pxl8_net_chunk_type(const pxl8_net* net);\n" +"u32 pxl8_net_chunk_id(const pxl8_net* net);\n" "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_send_chunk_settings(pxl8_net* net, i32 render_distance, i32 sim_distance);\n" +"i32 pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch);\n" +"i32 pxl8_net_exit_chunk(pxl8_net* net, f32 x, f32 y, f32 z);\n" +"i32 pxl8_net_enter_chunk(pxl8_net* net, u32 chunk_id);\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 new file mode 100644 index 0000000..d6334ac --- /dev/null +++ b/src/sim/pxl8_sim.c @@ -0,0 +1,224 @@ +#include "pxl8_sim.h" + +#include + +static usize vxl_world_to_local(f32 x, i32 chunk_coord) { + f32 chunk_base = chunk_coord * PXL8_VXL_WORLD_CHUNK_SIZE; + f32 local_world = x - chunk_base; + i32 local_voxel = (i32)floorf(local_world / PXL8_VXL_SCALE); + if (local_voxel < 0) return 0; + if (local_voxel >= PXL8_VXL_CHUNK_SIZE) return PXL8_VXL_CHUNK_SIZE - 1; + return (usize)local_voxel; +} + +static i32 bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) { + if (!bsp || bsp->num_nodes == 0) return -1; + + i32 node_id = 0; + while (node_id >= 0) { + const pxl8_bsp_node* node = &bsp->nodes[node_id]; + const pxl8_bsp_plane* plane = &bsp->planes[node->plane_id]; + f32 dist = pxl8_vec3_dot(pos, plane->normal) - plane->dist; + node_id = node->children[dist < 0 ? 1 : 0]; + } + + return -(node_id + 1); +} + +bool pxl8_bsp_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos) { + i32 leaf = bsp_find_leaf(bsp, pos); + if (leaf < 0 || (u32)leaf >= bsp->num_leafs) return true; + return bsp->leafs[leaf].contents == -1; +} + +static bool bsp_point_clear(const pxl8_bsp* bsp, f32 x, f32 y, f32 z, f32 radius) { + if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z})) return false; + if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x + radius, y, z})) return false; + if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x - radius, y, z})) return false; + if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z + radius})) return false; + if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z - radius})) return false; + return true; +} + +pxl8_vec3 pxl8_bsp_trace(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, f32 radius) { + if (!bsp || bsp->num_nodes == 0) return to; + + if (bsp_point_clear(bsp, to.x, to.y, to.z, radius)) { + return to; + } + + 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; +} + +bool pxl8_vxl_point_solid(const pxl8_vxl_chunk* chunk, i32 cx, i32 cy, i32 cz, + f32 x, f32 y, f32 z) { + if (!chunk) return false; + + usize lx = vxl_world_to_local(x, cx); + usize ly = vxl_world_to_local(y, cy); + usize lz = vxl_world_to_local(z, cz); + + return pxl8_vxl_block_get(chunk, (i32)lx, (i32)ly, (i32)lz) != PXL8_VXL_BLOCK_AIR; +} + +static bool vxl_point_clear(const pxl8_vxl_chunk* chunk, i32 cx, i32 cy, i32 cz, + f32 x, f32 y, f32 z, f32 radius) { + if (pxl8_vxl_point_solid(chunk, cx, cy, cz, x - radius, y, z)) return false; + if (pxl8_vxl_point_solid(chunk, cx, cy, cz, x + radius, y, z)) return false; + if (pxl8_vxl_point_solid(chunk, cx, cy, cz, x, y - radius, z)) return false; + if (pxl8_vxl_point_solid(chunk, cx, cy, cz, x, y + radius, z)) return false; + if (pxl8_vxl_point_solid(chunk, cx, cy, cz, x, y, z - radius)) return false; + if (pxl8_vxl_point_solid(chunk, cx, cy, cz, x, y, z + radius)) return false; + return true; +} + +pxl8_vec3 pxl8_vxl_trace(const pxl8_vxl_chunk* chunk, i32 cx, i32 cy, i32 cz, + pxl8_vec3 from, pxl8_vec3 to, f32 radius) { + if (!chunk) return to; + + if (vxl_point_clear(chunk, cx, cy, cz, to.x, to.y, to.z, radius)) { + return to; + } + + bool x_ok = vxl_point_clear(chunk, cx, cy, cz, to.x, from.y, from.z, radius); + bool y_ok = vxl_point_clear(chunk, cx, cy, cz, from.x, to.y, from.z, radius); + bool z_ok = vxl_point_clear(chunk, cx, cy, cz, 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) { + if (!world) return to; + + if (world->bsp) { + return pxl8_bsp_trace(world->bsp, from, to, radius); + } + + if (world->vxl) { + return pxl8_vxl_trace(world->vxl, world->vxl_cx, world->vxl_cy, world->vxl_cz, + from, to, radius); + } + + return to; +} + +bool pxl8_sim_check_ground(const pxl8_sim_world* world, pxl8_vec3 pos, f32 radius) { + if (!world) return true; + if (!world->bsp && !world->vxl) return true; + + pxl8_vec3 down = {pos.x, pos.y - 2.0f, pos.z}; + pxl8_vec3 result = pxl8_sim_trace(world, pos, down, radius); + + return result.y > down.y; +} + +void pxl8_sim_move_player(pxl8_sim_entity* ent, const pxl8_input_msg* input, + const pxl8_sim_world* world, f32 dt) { + if (!ent || !input) return; + if (!(ent->flags & PXL8_SIM_FLAG_ALIVE)) return; + + ent->yaw -= input->look_dx * 0.008f; + f32 new_pitch = ent->pitch - input->look_dy * 0.008f; + if (new_pitch > PXL8_SIM_MAX_PITCH) new_pitch = PXL8_SIM_MAX_PITCH; + if (new_pitch < -PXL8_SIM_MAX_PITCH) new_pitch = -PXL8_SIM_MAX_PITCH; + ent->pitch = new_pitch; + + f32 sin_yaw = sinf(ent->yaw); + f32 cos_yaw = cosf(ent->yaw); + + f32 input_len = sqrtf(input->move_x * input->move_x + input->move_y * input->move_y); + + pxl8_vec3 move_dir = {0, 0, 0}; + f32 target_speed = 0.0f; + + if (input_len > 0.0f) { + f32 nx = input->move_x / input_len; + f32 ny = input->move_y / input_len; + move_dir.x = cos_yaw * nx - sin_yaw * ny; + move_dir.z = -sin_yaw * nx - cos_yaw * ny; + target_speed = PXL8_SIM_MOVE_SPEED; + } + + ent->vel.x = move_dir.x * target_speed; + ent->vel.z = move_dir.z * target_speed; + + bool grounded = (ent->flags & PXL8_SIM_FLAG_GROUNDED) != 0; + + if (grounded && (input->buttons & 1)) { + ent->vel.y = PXL8_SIM_JUMP_VELOCITY; + ent->flags &= ~PXL8_SIM_FLAG_GROUNDED; + grounded = false; + } + + if (!grounded) { + ent->vel.y -= PXL8_SIM_GRAVITY * dt; + } + + pxl8_vec3 target = { + ent->pos.x + ent->vel.x * dt, + ent->pos.y + ent->vel.y * dt, + ent->pos.z + ent->vel.z * dt + }; + + pxl8_vec3 new_pos = pxl8_sim_trace(world, ent->pos, target, PXL8_SIM_PLAYER_RADIUS); + ent->pos = new_pos; + + if (pxl8_sim_check_ground(world, ent->pos, PXL8_SIM_PLAYER_RADIUS)) { + ent->flags |= PXL8_SIM_FLAG_GROUNDED; + if (ent->vel.y < 0) ent->vel.y = 0; + } else { + ent->flags &= ~PXL8_SIM_FLAG_GROUNDED; + } +} + +void pxl8_sim_integrate(pxl8_sim_entity* ent, const pxl8_sim_world* world, f32 dt) { + if (!ent) return; + if (!(ent->flags & PXL8_SIM_FLAG_ALIVE)) return; + if (ent->flags & PXL8_SIM_FLAG_PLAYER) return; + + bool grounded = (ent->flags & PXL8_SIM_FLAG_GROUNDED) != 0; + + ent->vel.y -= PXL8_SIM_GRAVITY * dt; + + if (grounded) { + f32 speed = sqrtf(ent->vel.x * ent->vel.x + ent->vel.z * ent->vel.z); + if (speed > 0.0f) { + f32 drop = speed * PXL8_SIM_FRICTION * dt; + f32 new_speed = speed - drop; + if (new_speed < 0.0f) new_speed = 0.0f; + f32 scale = new_speed / speed; + ent->vel.x *= scale; + ent->vel.z *= scale; + } + } + + pxl8_vec3 target = { + ent->pos.x + ent->vel.x * dt, + ent->pos.y + ent->vel.y * dt, + ent->pos.z + ent->vel.z * dt + }; + + pxl8_vec3 new_pos = pxl8_sim_trace(world, ent->pos, target, PXL8_SIM_PLAYER_RADIUS); + ent->pos = new_pos; + + if (pxl8_sim_check_ground(world, ent->pos, PXL8_SIM_PLAYER_RADIUS)) { + ent->flags |= PXL8_SIM_FLAG_GROUNDED; + if (ent->vel.y < 0.0f) ent->vel.y = 0.0f; + } else { + ent->flags &= ~PXL8_SIM_FLAG_GROUNDED; + } +} diff --git a/src/sim/pxl8_sim.h b/src/sim/pxl8_sim.h new file mode 100644 index 0000000..94cb67c --- /dev/null +++ b/src/sim/pxl8_sim.h @@ -0,0 +1,56 @@ +#pragma once + +#include "pxl8_bsp.h" +#include "pxl8_math.h" +#include "pxl8_protocol.h" +#include "pxl8_types.h" +#include "pxl8_vxl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PXL8_SIM_FLAG_ALIVE (1 << 0) +#define PXL8_SIM_FLAG_PLAYER (1 << 1) +#define PXL8_SIM_FLAG_GROUNDED (1 << 2) + +#define PXL8_SIM_MOVE_SPEED 180.0f +#define PXL8_SIM_GROUND_ACCEL 10.0f +#define PXL8_SIM_AIR_ACCEL 1.0f +#define PXL8_SIM_STOP_SPEED 100.0f +#define PXL8_SIM_FRICTION 6.0f +#define PXL8_SIM_GRAVITY 800.0f +#define PXL8_SIM_JUMP_VELOCITY 200.0f +#define PXL8_SIM_PLAYER_RADIUS 16.0f +#define PXL8_SIM_MAX_PITCH 1.5f + +typedef struct pxl8_sim_entity { + pxl8_vec3 pos; + pxl8_vec3 vel; + f32 yaw; + f32 pitch; + u32 flags; + u16 kind; + u16 _pad; +} pxl8_sim_entity; + +typedef struct pxl8_sim_world { + const pxl8_bsp* bsp; + const pxl8_vxl_chunk* vxl; + i32 vxl_cx, vxl_cy, vxl_cz; +} pxl8_sim_world; + +bool pxl8_bsp_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos); +pxl8_vec3 pxl8_bsp_trace(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, f32 radius); + +bool pxl8_vxl_point_solid(const pxl8_vxl_chunk* chunk, i32 cx, i32 cy, i32 cz, f32 x, f32 y, f32 z); +pxl8_vec3 pxl8_vxl_trace(const pxl8_vxl_chunk* chunk, i32 cx, i32 cy, i32 cz, pxl8_vec3 from, pxl8_vec3 to, f32 radius); + +pxl8_vec3 pxl8_sim_trace(const pxl8_sim_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius); +bool pxl8_sim_check_ground(const pxl8_sim_world* world, pxl8_vec3 pos, f32 radius); +void pxl8_sim_move_player(pxl8_sim_entity* ent, const pxl8_input_msg* input, const pxl8_sim_world* world, f32 dt); +void pxl8_sim_integrate(pxl8_sim_entity* ent, const pxl8_sim_world* world, f32 dt); + +#ifdef __cplusplus +} +#endif diff --git a/src/vxl/pxl8_vxl.c b/src/vxl/pxl8_vxl.c new file mode 100644 index 0000000..9e54a45 --- /dev/null +++ b/src/vxl/pxl8_vxl.c @@ -0,0 +1,319 @@ +#include "pxl8_vxl.h" + +#include + +#include "pxl8_mem.h" + +typedef struct pxl8_vxl_block_def { + char name[32]; + u8 texture_top; + u8 texture_side; + u8 texture_bottom; + pxl8_vxl_geometry geometry; + bool registered; +} pxl8_vxl_block_def; + +struct pxl8_vxl_block_registry { + pxl8_vxl_block_def blocks[PXL8_VXL_BLOCK_COUNT]; +}; + +static inline bool vxl_in_bounds(i32 x, i32 y, i32 z) { + return x >= 0 && x < PXL8_VXL_CHUNK_SIZE && + y >= 0 && y < PXL8_VXL_CHUNK_SIZE && + z >= 0 && z < PXL8_VXL_CHUNK_SIZE; +} + +static inline u32 vxl_index(i32 x, i32 y, i32 z) { + return (u32)(x + y * PXL8_VXL_CHUNK_SIZE + z * PXL8_VXL_CHUNK_SIZE * PXL8_VXL_CHUNK_SIZE); +} + +static pxl8_vxl_octree_node* octree_alloc_children(void) { + pxl8_vxl_octree_node* children = pxl8_calloc(8, sizeof(pxl8_vxl_octree_node)); + for (i32 i = 0; i < 8; i++) { + children[i].type = PXL8_VXL_OCTREE_UNIFORM; + children[i].block = PXL8_VXL_BLOCK_AIR; + } + return children; +} + +static void octree_free_node(pxl8_vxl_octree_node* node) { + if (node->type == PXL8_VXL_OCTREE_BRANCH && node->children) { + for (i32 i = 0; i < 8; i++) { + octree_free_node(&node->children[i]); + } + pxl8_free(node->children); + node->children = NULL; + } + node->type = PXL8_VXL_OCTREE_UNIFORM; + node->block = PXL8_VXL_BLOCK_AIR; +} + +static u8 octree_get(const pxl8_vxl_octree_node* node, i32 depth, i32 x, i32 y, i32 z) { + if (node->type == PXL8_VXL_OCTREE_UNIFORM) { + return node->block; + } + + if (depth == 0 || !node->children) { + return PXL8_VXL_BLOCK_AIR; + } + + i32 half = 1 << (depth - 1); + i32 idx = ((x >= half) ? 1 : 0) | + ((y >= half) ? 2 : 0) | + ((z >= half) ? 4 : 0); + + return octree_get(&node->children[idx], depth - 1, + x & (half - 1), y & (half - 1), z & (half - 1)); +} + +static void octree_subdivide(pxl8_vxl_octree_node* node) { + if (node->type == PXL8_VXL_OCTREE_BRANCH) return; + + u8 block = node->block; + node->type = PXL8_VXL_OCTREE_BRANCH; + node->children = octree_alloc_children(); + + for (i32 i = 0; i < 8; i++) { + node->children[i].type = PXL8_VXL_OCTREE_UNIFORM; + node->children[i].block = block; + } +} + +static bool octree_try_simplify(pxl8_vxl_octree_node* node) { + if (node->type != PXL8_VXL_OCTREE_BRANCH || !node->children) { + return false; + } + + for (i32 i = 0; i < 8; i++) { + if (node->children[i].type != PXL8_VXL_OCTREE_UNIFORM) { + return false; + } + } + + u8 first_block = node->children[0].block; + for (i32 i = 1; i < 8; i++) { + if (node->children[i].block != first_block) { + return false; + } + } + + pxl8_free(node->children); + node->children = NULL; + node->type = PXL8_VXL_OCTREE_UNIFORM; + node->block = first_block; + return true; +} + +static void octree_set(pxl8_vxl_octree_node* node, i32 depth, i32 x, i32 y, i32 z, u8 block) { + if (depth == 0) { + if (node->type == PXL8_VXL_OCTREE_BRANCH) { + octree_free_node(node); + } + node->type = PXL8_VXL_OCTREE_UNIFORM; + node->block = block; + return; + } + + if (node->type == PXL8_VXL_OCTREE_UNIFORM) { + if (node->block == block) { + return; + } + octree_subdivide(node); + } + + i32 half = 1 << (depth - 1); + i32 idx = ((x >= half) ? 1 : 0) | + ((y >= half) ? 2 : 0) | + ((z >= half) ? 4 : 0); + + octree_set(&node->children[idx], depth - 1, + x & (half - 1), y & (half - 1), z & (half - 1), block); + + octree_try_simplify(node); +} + +static void octree_linearize_recursive(const pxl8_vxl_octree_node* node, i32 depth, + i32 ox, i32 oy, i32 oz, u8* out) { + i32 size = 1 << depth; + + if (node->type == PXL8_VXL_OCTREE_UNIFORM) { + for (i32 z = 0; z < size; z++) { + for (i32 y = 0; y < size; y++) { + for (i32 x = 0; x < size; x++) { + out[vxl_index(ox + x, oy + y, oz + z)] = node->block; + } + } + } + return; + } + + if (depth == 0 || !node->children) return; + + i32 half = size / 2; + for (i32 i = 0; i < 8; i++) { + i32 cx = ox + ((i & 1) ? half : 0); + i32 cy = oy + ((i & 2) ? half : 0); + i32 cz = oz + ((i & 4) ? half : 0); + octree_linearize_recursive(&node->children[i], depth - 1, cx, cy, cz, out); + } +} + +static void octree_build_recursive(pxl8_vxl_octree_node* node, i32 depth, + i32 ox, i32 oy, i32 oz, const u8* data) { + i32 size = 1 << depth; + + if (depth == 0) { + node->type = PXL8_VXL_OCTREE_UNIFORM; + node->block = data[vxl_index(ox, oy, oz)]; + return; + } + + u8 first_block = data[vxl_index(ox, oy, oz)]; + bool all_same = true; + + for (i32 z = 0; z < size && all_same; z++) { + for (i32 y = 0; y < size && all_same; y++) { + for (i32 x = 0; x < size && all_same; x++) { + if (data[vxl_index(ox + x, oy + y, oz + z)] != first_block) { + all_same = false; + } + } + } + } + + if (all_same) { + node->type = PXL8_VXL_OCTREE_UNIFORM; + node->block = first_block; + return; + } + + node->type = PXL8_VXL_OCTREE_BRANCH; + node->children = octree_alloc_children(); + + i32 half = size / 2; + for (i32 i = 0; i < 8; i++) { + i32 cx = ox + ((i & 1) ? half : 0); + i32 cy = oy + ((i & 2) ? half : 0); + i32 cz = oz + ((i & 4) ? half : 0); + octree_build_recursive(&node->children[i], depth - 1, cx, cy, cz, data); + } + + octree_try_simplify(node); +} + +pxl8_vxl_chunk* pxl8_vxl_chunk_create(void) { + pxl8_vxl_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_vxl_chunk)); + if (!chunk) return NULL; + chunk->root.type = PXL8_VXL_OCTREE_UNIFORM; + chunk->root.block = PXL8_VXL_BLOCK_AIR; + return chunk; +} + +void pxl8_vxl_chunk_destroy(pxl8_vxl_chunk* chunk) { + if (!chunk) return; + octree_free_node(&chunk->root); + pxl8_free(chunk); +} + +u8 pxl8_vxl_block_get(const pxl8_vxl_chunk* chunk, i32 x, i32 y, i32 z) { + if (!chunk || !vxl_in_bounds(x, y, z)) return PXL8_VXL_BLOCK_AIR; + return octree_get(&chunk->root, PXL8_VXL_OCTREE_DEPTH, x, y, z); +} + +void pxl8_vxl_block_set(pxl8_vxl_chunk* chunk, i32 x, i32 y, i32 z, u8 block) { + if (!chunk || !vxl_in_bounds(x, y, z)) return; + octree_set(&chunk->root, PXL8_VXL_OCTREE_DEPTH, x, y, z, block); +} + +void pxl8_vxl_block_fill(pxl8_vxl_chunk* chunk, u8 block) { + if (!chunk) return; + octree_free_node(&chunk->root); + chunk->root.type = PXL8_VXL_OCTREE_UNIFORM; + chunk->root.block = block; +} + +void pxl8_vxl_block_clear(pxl8_vxl_chunk* chunk) { + pxl8_vxl_block_fill(chunk, PXL8_VXL_BLOCK_AIR); +} + +void pxl8_vxl_chunk_linearize(const pxl8_vxl_chunk* chunk, u8* out) { + if (!chunk || !out) return; + octree_linearize_recursive(&chunk->root, PXL8_VXL_OCTREE_DEPTH, 0, 0, 0, out); +} + +void pxl8_vxl_chunk_from_linear(pxl8_vxl_chunk* chunk, const u8* data) { + if (!chunk || !data) return; + octree_free_node(&chunk->root); + octree_build_recursive(&chunk->root, PXL8_VXL_OCTREE_DEPTH, 0, 0, 0, data); +} + +bool pxl8_vxl_chunk_is_uniform(const pxl8_vxl_chunk* chunk) { + if (!chunk) return true; + return chunk->root.type == PXL8_VXL_OCTREE_UNIFORM; +} + +u8 pxl8_vxl_chunk_uniform_block(const pxl8_vxl_chunk* chunk) { + if (!chunk) return PXL8_VXL_BLOCK_AIR; + if (chunk->root.type == PXL8_VXL_OCTREE_UNIFORM) { + return chunk->root.block; + } + return PXL8_VXL_BLOCK_AIR; +} + +pxl8_vxl_block_registry* pxl8_vxl_block_registry_create(void) { + pxl8_vxl_block_registry* registry = pxl8_calloc(1, sizeof(pxl8_vxl_block_registry)); + if (!registry) return NULL; + + pxl8_vxl_block_registry_register(registry, 1, "stone", 8, PXL8_VXL_GEOMETRY_CUBE); + pxl8_vxl_block_registry_register(registry, 2, "dirt", 52, PXL8_VXL_GEOMETRY_CUBE); + pxl8_vxl_block_registry_register_ex(registry, 3, "grass", 200, 52, 52, PXL8_VXL_GEOMETRY_CUBE); + pxl8_vxl_block_registry_register_ex(registry, 4, "grass_slope_n", 200, 52, 52, PXL8_VXL_GEOMETRY_SLOPE_NORTH); + pxl8_vxl_block_registry_register_ex(registry, 5, "grass_slope_s", 200, 52, 52, PXL8_VXL_GEOMETRY_SLOPE_SOUTH); + pxl8_vxl_block_registry_register_ex(registry, 6, "grass_slope_e", 200, 52, 52, PXL8_VXL_GEOMETRY_SLOPE_EAST); + pxl8_vxl_block_registry_register_ex(registry, 7, "grass_slope_w", 200, 52, 52, PXL8_VXL_GEOMETRY_SLOPE_WEST); + + return registry; +} + +void pxl8_vxl_block_registry_destroy(pxl8_vxl_block_registry* registry) { + pxl8_free(registry); +} + +void pxl8_vxl_block_registry_register(pxl8_vxl_block_registry* registry, u8 id, const char* name, u8 texture_id, pxl8_vxl_geometry geo) { + pxl8_vxl_block_registry_register_ex(registry, id, name, texture_id, texture_id, texture_id, geo); +} + +void pxl8_vxl_block_registry_register_ex(pxl8_vxl_block_registry* registry, u8 id, const char* name, + u8 texture_top, u8 texture_side, u8 texture_bottom, pxl8_vxl_geometry geo) { + if (!registry || id == PXL8_VXL_BLOCK_AIR) return; + + pxl8_vxl_block_def* def = ®istry->blocks[id]; + strncpy(def->name, name ? name : "", sizeof(def->name) - 1); + def->texture_top = texture_top; + def->texture_side = texture_side; + def->texture_bottom = texture_bottom; + def->geometry = geo; + def->registered = true; +} + +const char* pxl8_vxl_block_registry_name(const pxl8_vxl_block_registry* registry, u8 id) { + if (!registry || !registry->blocks[id].registered) return NULL; + return registry->blocks[id].name; +} + +pxl8_vxl_geometry pxl8_vxl_block_registry_geometry(const pxl8_vxl_block_registry* registry, u8 id) { + if (!registry || !registry->blocks[id].registered) return PXL8_VXL_GEOMETRY_CUBE; + return registry->blocks[id].geometry; +} + +u8 pxl8_vxl_block_registry_texture(const pxl8_vxl_block_registry* registry, u8 id) { + if (!registry || !registry->blocks[id].registered) return 0; + return registry->blocks[id].texture_top; +} + +u8 pxl8_vxl_block_registry_texture_for_face(const pxl8_vxl_block_registry* registry, u8 id, i32 face) { + if (!registry || !registry->blocks[id].registered) return 0; + if (face == 3) return registry->blocks[id].texture_top; + if (face == 2) return registry->blocks[id].texture_bottom; + return registry->blocks[id].texture_side; +} diff --git a/src/vxl/pxl8_vxl.h b/src/vxl/pxl8_vxl.h new file mode 100644 index 0000000..f1ec7c9 --- /dev/null +++ b/src/vxl/pxl8_vxl.h @@ -0,0 +1,78 @@ +#pragma once + +#include "pxl8_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PXL8_VXL_BLOCK_COUNT 256 +#define PXL8_VXL_CHUNK_SIZE 32 +#define PXL8_VXL_CHUNK_VOLUME (PXL8_VXL_CHUNK_SIZE * PXL8_VXL_CHUNK_SIZE * PXL8_VXL_CHUNK_SIZE) +#define PXL8_VXL_SCALE 16.0f +#define PXL8_VXL_WORLD_CHUNK_SIZE (PXL8_VXL_CHUNK_SIZE * PXL8_VXL_SCALE) +#define PXL8_VXL_OCTREE_DEPTH 5 + +#define PXL8_VXL_BLOCK_AIR 0 +#define PXL8_VXL_BLOCK_UNKNOWN 255 + +typedef struct pxl8_vxl_block_registry pxl8_vxl_block_registry; + +typedef enum pxl8_vxl_octree_type { + PXL8_VXL_OCTREE_UNIFORM = 0, + PXL8_VXL_OCTREE_BRANCH = 1 +} pxl8_vxl_octree_type; + +typedef struct pxl8_vxl_octree_node { + u8 type; + union { + u8 block; + struct pxl8_vxl_octree_node* children; + }; +} pxl8_vxl_octree_node; + +typedef struct pxl8_vxl_chunk { + pxl8_vxl_octree_node root; +} pxl8_vxl_chunk; + +typedef enum pxl8_vxl_geometry { + PXL8_VXL_GEOMETRY_CUBE, + PXL8_VXL_GEOMETRY_SLAB_BOTTOM, + PXL8_VXL_GEOMETRY_SLAB_TOP, + PXL8_VXL_GEOMETRY_SLOPE_NORTH, + PXL8_VXL_GEOMETRY_SLOPE_SOUTH, + PXL8_VXL_GEOMETRY_SLOPE_EAST, + PXL8_VXL_GEOMETRY_SLOPE_WEST, + PXL8_VXL_GEOMETRY_STAIRS_NORTH, + PXL8_VXL_GEOMETRY_STAIRS_SOUTH, + PXL8_VXL_GEOMETRY_STAIRS_EAST, + PXL8_VXL_GEOMETRY_STAIRS_WEST +} pxl8_vxl_geometry; + +pxl8_vxl_chunk* pxl8_vxl_chunk_create(void); +void pxl8_vxl_chunk_destroy(pxl8_vxl_chunk* chunk); + +u8 pxl8_vxl_block_get(const pxl8_vxl_chunk* chunk, i32 x, i32 y, i32 z); +void pxl8_vxl_block_set(pxl8_vxl_chunk* chunk, i32 x, i32 y, i32 z, u8 block); +void pxl8_vxl_block_fill(pxl8_vxl_chunk* chunk, u8 block); +void pxl8_vxl_block_clear(pxl8_vxl_chunk* chunk); + +void pxl8_vxl_chunk_linearize(const pxl8_vxl_chunk* chunk, u8* out); +void pxl8_vxl_chunk_from_linear(pxl8_vxl_chunk* chunk, const u8* data); + +bool pxl8_vxl_chunk_is_uniform(const pxl8_vxl_chunk* chunk); +u8 pxl8_vxl_chunk_uniform_block(const pxl8_vxl_chunk* chunk); + +pxl8_vxl_block_registry* pxl8_vxl_block_registry_create(void); +void pxl8_vxl_block_registry_destroy(pxl8_vxl_block_registry* registry); +void pxl8_vxl_block_registry_register(pxl8_vxl_block_registry* registry, u8 id, const char* name, u8 texture_id, pxl8_vxl_geometry geo); +void pxl8_vxl_block_registry_register_ex(pxl8_vxl_block_registry* registry, u8 id, const char* name, + u8 texture_top, u8 texture_side, u8 texture_bottom, pxl8_vxl_geometry geo); +const char* pxl8_vxl_block_registry_name(const pxl8_vxl_block_registry* registry, u8 id); +pxl8_vxl_geometry pxl8_vxl_block_registry_geometry(const pxl8_vxl_block_registry* registry, u8 id); +u8 pxl8_vxl_block_registry_texture(const pxl8_vxl_block_registry* registry, u8 id); +u8 pxl8_vxl_block_registry_texture_for_face(const pxl8_vxl_block_registry* registry, u8 id, i32 face); + +#ifdef __cplusplus +} +#endif diff --git a/src/vxl/pxl8_vxl_render.c b/src/vxl/pxl8_vxl_render.c new file mode 100644 index 0000000..a4ebbdc --- /dev/null +++ b/src/vxl/pxl8_vxl_render.c @@ -0,0 +1,576 @@ +#include "pxl8_vxl_render.h" + +#include + +#include "pxl8_mem.h" +#include "pxl8_noise.h" +#include "pxl8_vxl.h" + +typedef struct pxl8_vxl_greedy_mask { + u8 block[PXL8_VXL_CHUNK_SIZE][PXL8_VXL_CHUNK_SIZE]; + bool done[PXL8_VXL_CHUNK_SIZE][PXL8_VXL_CHUNK_SIZE]; +} pxl8_vxl_greedy_mask; + +static const pxl8_vec3 face_normals[6] = { + {-1, 0, 0}, { 1, 0, 0}, + { 0, -1, 0}, { 0, 1, 0}, + { 0, 0, -1}, { 0, 0, 1} +}; + +static const i32 face_dirs[6][3] = { + {-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1} +}; + +static inline bool vxl_in_bounds(i32 x, i32 y, i32 z) { + return x >= 0 && x < PXL8_VXL_CHUNK_SIZE && + y >= 0 && y < PXL8_VXL_CHUNK_SIZE && + z >= 0 && z < PXL8_VXL_CHUNK_SIZE; +} + +static bool block_is_opaque(u8 block) { + return block != PXL8_VXL_BLOCK_AIR && block != PXL8_VXL_BLOCK_UNKNOWN; +} + +static bool block_is_full_cube(u8 block, const pxl8_vxl_block_registry* registry) { + if (block == PXL8_VXL_BLOCK_AIR) return false; + pxl8_vxl_geometry geo = pxl8_vxl_block_registry_geometry(registry, block); + return geo == PXL8_VXL_GEOMETRY_CUBE; +} + +static u8 get_block_or_neighbor(const pxl8_vxl_chunk* chunk, const pxl8_vxl_chunk** neighbors, i32 x, i32 y, i32 z) { + if (vxl_in_bounds(x, y, z)) { + return pxl8_vxl_block_get(chunk, x, y, z); + } + + i32 nx = x, ny = y, nz = z; + i32 neighbor_idx = -1; + + if (x < 0) { neighbor_idx = 0; nx = x + PXL8_VXL_CHUNK_SIZE; } + else if (x >= PXL8_VXL_CHUNK_SIZE) { neighbor_idx = 1; nx = x - PXL8_VXL_CHUNK_SIZE; } + else if (y < 0) { neighbor_idx = 2; ny = y + PXL8_VXL_CHUNK_SIZE; } + else if (y >= PXL8_VXL_CHUNK_SIZE) { neighbor_idx = 3; ny = y - PXL8_VXL_CHUNK_SIZE; } + else if (z < 0) { neighbor_idx = 4; nz = z + PXL8_VXL_CHUNK_SIZE; } + else if (z >= PXL8_VXL_CHUNK_SIZE) { neighbor_idx = 5; nz = z - PXL8_VXL_CHUNK_SIZE; } + + if (neighbor_idx >= 0 && neighbors && neighbors[neighbor_idx]) { + return pxl8_vxl_block_get(neighbors[neighbor_idx], nx, ny, nz); + } + + return PXL8_VXL_BLOCK_UNKNOWN; +} + +static f32 compute_ao(const pxl8_vxl_chunk* chunk, const pxl8_vxl_chunk** neighbors, + i32 x, i32 y, i32 z, i32 dx1, i32 dy1, i32 dz1, i32 dx2, i32 dy2, i32 dz2) { + u8 s1 = get_block_or_neighbor(chunk, neighbors, x + dx1, y + dy1, z + dz1); + u8 s2 = get_block_or_neighbor(chunk, neighbors, x + dx2, y + dy2, z + dz2); + u8 corner = get_block_or_neighbor(chunk, neighbors, x + dx1 + dx2, y + dy1 + dy2, z + dz1 + dz2); + + bool side1 = block_is_opaque(s1); + bool side2 = block_is_opaque(s2); + bool has_corner = block_is_opaque(corner); + + if (side1 && side2) return 0.0f; + return (3.0f - (f32)side1 - (f32)side2 - (f32)has_corner) / 3.0f; +} + +static f32 sample_corner_displacement(const pxl8_vxl_chunk* chunk, const pxl8_vxl_chunk** neighbors, + i32 cx, i32 cz, i32 base_y) { + f32 sum = 0.0f; + i32 count = 0; + + for (i32 dz = -1; dz <= 0; dz++) { + for (i32 dx = -1; dx <= 0; dx++) { + i32 x = cx + dx; + i32 z = cz + dz; + + u8 block = get_block_or_neighbor(chunk, neighbors, x, base_y, z); + u8 above = get_block_or_neighbor(chunk, neighbors, x, base_y + 1, z); + + if (block_is_opaque(block) && !block_is_opaque(above)) { + sum += 1.0f; + } else if (!block_is_opaque(block)) { + sum -= 1.0f; + } + count++; + } + } + + return count > 0 ? (sum / (f32)count) * 0.15f : 0.0f; +} + +static void slice_to_world(i32 face, i32 slice, i32 u, i32 v, i32* x, i32* y, i32* z) { + switch (face) { + case 0: *x = slice; *y = v; *z = u; break; + case 1: *x = slice; *y = v; *z = u; break; + case 2: *x = u; *y = slice; *z = v; break; + case 3: *x = u; *y = slice; *z = v; break; + case 4: *x = u; *y = v; *z = slice; break; + case 5: *x = u; *y = v; *z = slice; break; + } +} + +static f32 terrain_noise(f32 wx, f32 wz, u64 seed) { + f32 noise = pxl8_fbm(wx * 0.08f, wz * 0.08f, seed, 3); + return (noise - 0.5f) * 0.4f; +} + +static void emit_greedy_quad(pxl8_mesh* mesh, const pxl8_vxl_chunk* chunk, + const pxl8_vxl_chunk** neighbors, + const pxl8_vxl_mesh_config* config, + i32 face, i32 slice, + i32 u, i32 v, i32 width, i32 height, + u8 texture_id, f32 ao_avg, f32 ao_strength) { + pxl8_vec3 normal = face_normals[face]; + u8 light = (u8)(255.0f * (1.0f - ao_strength * (1.0f - ao_avg))); + + f32 p[4][3]; + + switch (face) { + case 0: + p[0][0] = (f32)slice; p[0][1] = (f32)v; p[0][2] = (f32)(u + width); + p[1][0] = (f32)slice; p[1][1] = (f32)v; p[1][2] = (f32)u; + p[2][0] = (f32)slice; p[2][1] = (f32)(v + height); p[2][2] = (f32)u; + p[3][0] = (f32)slice; p[3][1] = (f32)(v + height); p[3][2] = (f32)(u + width); + break; + case 1: + p[0][0] = (f32)(slice + 1); p[0][1] = (f32)v; p[0][2] = (f32)u; + p[1][0] = (f32)(slice + 1); p[1][1] = (f32)v; p[1][2] = (f32)(u + width); + p[2][0] = (f32)(slice + 1); p[2][1] = (f32)(v + height); p[2][2] = (f32)(u + width); + p[3][0] = (f32)(slice + 1); p[3][1] = (f32)(v + height); p[3][2] = (f32)u; + break; + case 2: + p[0][0] = (f32)u; p[0][1] = (f32)slice; p[0][2] = (f32)v; + p[1][0] = (f32)(u + width); p[1][1] = (f32)slice; p[1][2] = (f32)v; + p[2][0] = (f32)(u + width); p[2][1] = (f32)slice; p[2][2] = (f32)(v + height); + p[3][0] = (f32)u; p[3][1] = (f32)slice; p[3][2] = (f32)(v + height); + break; + case 3: { + f32 base_y = (f32)(slice + 1); + f32 wx0 = (f32)(config->chunk_x * PXL8_VXL_CHUNK_SIZE + u); + f32 wz0 = (f32)(config->chunk_z * PXL8_VXL_CHUNK_SIZE + v); + + f32 d0 = sample_corner_displacement(chunk, neighbors, u, v + height, slice); + f32 d1 = sample_corner_displacement(chunk, neighbors, u + width, v + height, slice); + f32 d2 = sample_corner_displacement(chunk, neighbors, u + width, v, slice); + f32 d3 = sample_corner_displacement(chunk, neighbors, u, v, slice); + + f32 n0 = terrain_noise(wx0, wz0 + (f32)height, config->seed); + f32 n1 = terrain_noise(wx0 + (f32)width, wz0 + (f32)height, config->seed); + f32 n2 = terrain_noise(wx0 + (f32)width, wz0, config->seed); + f32 n3 = terrain_noise(wx0, wz0, config->seed); + + p[0][0] = (f32)u; p[0][1] = base_y + d0 + n0; p[0][2] = (f32)(v + height); + p[1][0] = (f32)(u + width); p[1][1] = base_y + d1 + n1; p[1][2] = (f32)(v + height); + p[2][0] = (f32)(u + width); p[2][1] = base_y + d2 + n2; p[2][2] = (f32)v; + p[3][0] = (f32)u; p[3][1] = base_y + d3 + n3; p[3][2] = (f32)v; + + pxl8_vec3 e1 = {p[1][0] - p[0][0], p[1][1] - p[0][1], p[1][2] - p[0][2]}; + pxl8_vec3 e2 = {p[3][0] - p[0][0], p[3][1] - p[0][1], p[3][2] - p[0][2]}; + normal = (pxl8_vec3){ + e1.y * e2.z - e1.z * e2.y, + e1.z * e2.x - e1.x * e2.z, + e1.x * e2.y - e1.y * e2.x + }; + f32 len_sq = normal.x * normal.x + normal.y * normal.y + normal.z * normal.z; + if (len_sq > 0.0001f) { + f32 inv_len = pxl8_fast_inv_sqrt(len_sq); + normal.x *= inv_len; normal.y *= inv_len; normal.z *= inv_len; + } else { + normal = (pxl8_vec3){0, 1, 0}; + } + break; + } + case 4: + p[0][0] = (f32)u; p[0][1] = (f32)v; p[0][2] = (f32)slice; + p[1][0] = (f32)u; p[1][1] = (f32)(v + height); p[1][2] = (f32)slice; + p[2][0] = (f32)(u + width); p[2][1] = (f32)(v + height); p[2][2] = (f32)slice; + p[3][0] = (f32)(u + width); p[3][1] = (f32)v; p[3][2] = (f32)slice; + break; + case 5: + p[0][0] = (f32)(u + width); p[0][1] = (f32)v; p[0][2] = (f32)(slice + 1); + p[1][0] = (f32)(u + width); p[1][1] = (f32)(v + height); p[1][2] = (f32)(slice + 1); + p[2][0] = (f32)u; p[2][1] = (f32)(v + height); p[2][2] = (f32)(slice + 1); + p[3][0] = (f32)u; p[3][1] = (f32)v; p[3][2] = (f32)(slice + 1); + break; + } + + f32 tex_u = (f32)width; + f32 tex_v = (f32)height; + + pxl8_vertex verts[4]; + for (i32 i = 0; i < 4; i++) { + verts[i].position.x = p[i][0]; + verts[i].position.y = p[i][1]; + verts[i].position.z = p[i][2]; + verts[i].normal = normal; + verts[i].color = texture_id; + verts[i].light = light; + } + + verts[0].u = 0; verts[0].v = tex_v; + verts[1].u = tex_u; verts[1].v = tex_v; + verts[2].u = tex_u; verts[2].v = 0; + verts[3].u = 0; verts[3].v = 0; + + u16 i0 = pxl8_mesh_push_vertex(mesh, verts[0]); + u16 i1 = pxl8_mesh_push_vertex(mesh, verts[1]); + u16 i2 = pxl8_mesh_push_vertex(mesh, verts[2]); + u16 i3 = pxl8_mesh_push_vertex(mesh, verts[3]); + + pxl8_mesh_push_quad(mesh, i0, i1, i2, i3); +} + +static void build_greedy_slice(const pxl8_vxl_chunk* chunk, + const pxl8_vxl_chunk** neighbors, + const pxl8_vxl_block_registry* registry, + const pxl8_vxl_mesh_config* config, + i32 face, i32 slice, + pxl8_mesh* mesh) { + pxl8_vxl_greedy_mask mask; + memset(&mask, 0, sizeof(mask)); + + i32 dx = face_dirs[face][0]; + i32 dy = face_dirs[face][1]; + i32 dz = face_dirs[face][2]; + + for (i32 v = 0; v < PXL8_VXL_CHUNK_SIZE; v++) { + for (i32 u = 0; u < PXL8_VXL_CHUNK_SIZE; u++) { + i32 x, y, z; + slice_to_world(face, slice, u, v, &x, &y, &z); + + u8 block = pxl8_vxl_block_get(chunk, x, y, z); + if (block == PXL8_VXL_BLOCK_AIR) continue; + + pxl8_vxl_geometry geo = pxl8_vxl_block_registry_geometry(registry, block); + if (geo != PXL8_VXL_GEOMETRY_CUBE) continue; + + i32 nx = x + dx; + i32 ny = y + dy; + i32 nz = z + dz; + + u8 neighbor = get_block_or_neighbor(chunk, neighbors, nx, ny, nz); + if (neighbor == PXL8_VXL_BLOCK_UNKNOWN) continue; + if (block_is_full_cube(neighbor, registry)) continue; + + mask.block[u][v] = block; + } + } + + f32 ao_strength = config->ambient_occlusion ? config->ao_strength : 0.0f; + + for (i32 v = 0; v < PXL8_VXL_CHUNK_SIZE; v++) { + for (i32 u = 0; u < PXL8_VXL_CHUNK_SIZE; u++) { + if (mask.done[u][v] || mask.block[u][v] == 0) continue; + + u8 block = mask.block[u][v]; + u8 texture_id = pxl8_vxl_block_registry_texture_for_face(registry, block, face); + + i32 width = 1; + while (u + width < PXL8_VXL_CHUNK_SIZE && + !mask.done[u + width][v] && + mask.block[u + width][v] == block) { + width++; + } + + i32 height = 1; + bool can_expand = true; + while (can_expand && v + height < PXL8_VXL_CHUNK_SIZE) { + for (i32 wu = 0; wu < width; wu++) { + if (mask.done[u + wu][v + height] || + mask.block[u + wu][v + height] != block) { + can_expand = false; + break; + } + } + if (can_expand) height++; + } + + for (i32 dv = 0; dv < height; dv++) { + for (i32 du = 0; du < width; du++) { + mask.done[u + du][v + dv] = true; + } + } + + f32 ao_sum = 0.0f; + i32 ao_count = 0; + if (config->ambient_occlusion) { + i32 cx, cy, cz; + slice_to_world(face, slice, u, v, &cx, &cy, &cz); + i32 fx = cx + dx; + i32 fy = cy + dy; + i32 fz = cz + dz; + + static const i32 ao_offsets[6][4][2][3] = { + [0] = {{{0,-1,0}, {0,0,1}}, {{0,-1,0}, {0,0,-1}}, {{0,1,0}, {0,0,-1}}, {{0,1,0}, {0,0,1}}}, + [1] = {{{0,-1,0}, {0,0,-1}}, {{0,-1,0}, {0,0,1}}, {{0,1,0}, {0,0,1}}, {{0,1,0}, {0,0,-1}}}, + [2] = {{{-1,0,0}, {0,0,-1}}, {{1,0,0}, {0,0,-1}}, {{1,0,0}, {0,0,1}}, {{-1,0,0}, {0,0,1}}}, + [3] = {{{-1,0,0}, {0,0,1}}, {{1,0,0}, {0,0,1}}, {{1,0,0}, {0,0,-1}}, {{-1,0,0}, {0,0,-1}}}, + [4] = {{{-1,0,0}, {0,1,0}}, {{-1,0,0}, {0,-1,0}}, {{1,0,0}, {0,-1,0}}, {{1,0,0}, {0,1,0}}}, + [5] = {{{1,0,0}, {0,-1,0}}, {{1,0,0}, {0,1,0}}, {{-1,0,0}, {0,1,0}}, {{-1,0,0}, {0,-1,0}}} + }; + + for (i32 corner = 0; corner < 4; corner++) { + f32 ao = compute_ao(chunk, neighbors, fx, fy, fz, + ao_offsets[face][corner][0][0], + ao_offsets[face][corner][0][1], + ao_offsets[face][corner][0][2], + ao_offsets[face][corner][1][0], + ao_offsets[face][corner][1][1], + ao_offsets[face][corner][1][2]); + ao_sum += ao; + ao_count++; + } + } + + f32 ao_avg = ao_count > 0 ? ao_sum / (f32)ao_count : 1.0f; + + emit_greedy_quad(mesh, chunk, neighbors, config, face, slice, u, v, width, height, + texture_id, ao_avg, ao_strength); + } + } +} + +static void add_face_vertices(pxl8_mesh* mesh, const pxl8_vxl_mesh_config* config, + pxl8_vec3 pos, i32 face, u8 texture_id, + f32 ao0, f32 ao1, f32 ao2, f32 ao3) { + static const i32 face_vertices[6][4][3] = { + {{0,0,1}, {0,0,0}, {0,1,0}, {0,1,1}}, + {{1,0,0}, {1,0,1}, {1,1,1}, {1,1,0}}, + {{0,0,0}, {1,0,0}, {1,0,1}, {0,0,1}}, + {{0,1,1}, {1,1,1}, {1,1,0}, {0,1,0}}, + {{0,0,0}, {0,1,0}, {1,1,0}, {1,0,0}}, + {{1,0,1}, {1,1,1}, {0,1,1}, {0,0,1}} + }; + + static const f32 face_uvs[4][2] = { + {0, 1}, {1, 1}, {1, 0}, {0, 0} + }; + + f32 ao_values[4] = {ao0, ao1, ao2, ao3}; + f32 ao_strength = config->ambient_occlusion ? config->ao_strength : 0.0f; + f32 tex_scale = config->texture_scale; + + pxl8_vec3 normal = face_normals[face]; + u16 indices[4]; + + for (i32 i = 0; i < 4; i++) { + pxl8_vertex v = {0}; + v.position.x = pos.x + (f32)face_vertices[face][i][0]; + v.position.y = pos.y + (f32)face_vertices[face][i][1]; + v.position.z = pos.z + (f32)face_vertices[face][i][2]; + v.normal = normal; + v.u = face_uvs[i][0] * tex_scale; + v.v = face_uvs[i][1] * tex_scale; + v.color = texture_id; + v.light = (u8)(255.0f * (1.0f - ao_strength * (1.0f - ao_values[i]))); + + indices[i] = pxl8_mesh_push_vertex(mesh, v); + } + + if (ao0 + ao2 > ao1 + ao3) { + pxl8_mesh_push_quad(mesh, indices[0], indices[1], indices[2], indices[3]); + } else { + pxl8_mesh_push_triangle(mesh, indices[0], indices[1], indices[2]); + pxl8_mesh_push_triangle(mesh, indices[0], indices[2], indices[3]); + } +} + +static void add_slab_faces(pxl8_mesh* mesh, const pxl8_vxl_chunk* chunk, + const pxl8_vxl_chunk** neighbors, const pxl8_vxl_block_registry* registry, + const pxl8_vxl_mesh_config* config, + i32 x, i32 y, i32 z, u8 block, bool top) { + u8 texture_id = pxl8_vxl_block_registry_texture(registry, block); + f32 y_offset = top ? 0.5f : 0.0f; + pxl8_vec3 pos = {(f32)x, (f32)y + y_offset, (f32)z}; + + static const i32 horiz_faces[4] = {0, 1, 4, 5}; + + for (i32 i = 0; i < 4; i++) { + i32 face = horiz_faces[i]; + i32 nx = x + face_dirs[face][0]; + i32 nz = z + face_dirs[face][2]; + + u8 neighbor = get_block_or_neighbor(chunk, neighbors, nx, y, nz); + if (neighbor == PXL8_VXL_BLOCK_UNKNOWN) continue; + if (block_is_full_cube(neighbor, registry)) continue; + + add_face_vertices(mesh, config, pos, face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f); + } + + i32 top_face = 3; + i32 bot_face = 2; + + if (top) { + u8 above = get_block_or_neighbor(chunk, neighbors, x, y + 1, z); + if (above != PXL8_VXL_BLOCK_UNKNOWN && !block_is_full_cube(above, registry)) { + add_face_vertices(mesh, config, pos, top_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f); + } + add_face_vertices(mesh, config, (pxl8_vec3){(f32)x, (f32)y + 0.5f, (f32)z}, bot_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f); + } else { + add_face_vertices(mesh, config, pos, top_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f); + u8 below = get_block_or_neighbor(chunk, neighbors, x, y - 1, z); + if (below != PXL8_VXL_BLOCK_UNKNOWN && !block_is_full_cube(below, registry)) { + add_face_vertices(mesh, config, pos, bot_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f); + } + } +} + +static void add_slope_faces(pxl8_mesh* mesh, const pxl8_vxl_chunk* chunk, + const pxl8_vxl_chunk** neighbors, const pxl8_vxl_block_registry* registry, + const pxl8_vxl_mesh_config* config, + i32 x, i32 y, i32 z, u8 block, i32 direction) { + u8 texture_top = pxl8_vxl_block_registry_texture_for_face(registry, block, 3); + u8 texture_side = pxl8_vxl_block_registry_texture_for_face(registry, block, 0); + pxl8_vec3 pos = {(f32)x, (f32)y, (f32)z}; + + u8 below = get_block_or_neighbor(chunk, neighbors, x, y - 1, z); + if (below != PXL8_VXL_BLOCK_UNKNOWN && !block_is_full_cube(below, registry)) { + add_face_vertices(mesh, config, pos, 2, texture_side, 1.0f, 1.0f, 1.0f, 1.0f); + } + + static const i32 dir_to_back_face[4] = {5, 4, 1, 0}; + i32 back_face = dir_to_back_face[direction]; + + i32 bx = x + face_dirs[back_face][0]; + i32 bz = z + face_dirs[back_face][2]; + u8 back_neighbor = get_block_or_neighbor(chunk, neighbors, bx, y, bz); + if (back_neighbor != PXL8_VXL_BLOCK_UNKNOWN && !block_is_full_cube(back_neighbor, registry)) { + add_face_vertices(mesh, config, pos, back_face, texture_side, 1.0f, 1.0f, 1.0f, 1.0f); + } + + static const f32 slope_verts[4][4][3] = { + {{0,0,0}, {1,0,0}, {1,1,1}, {0,1,1}}, + {{0,0,1}, {1,0,1}, {1,1,0}, {0,1,0}}, + {{1,0,0}, {1,0,1}, {0,1,1}, {0,1,0}}, + {{0,0,0}, {0,0,1}, {1,1,1}, {1,1,0}} + }; + + static const pxl8_vec3 slope_normals[4] = { + {0, 0.707f, -0.707f}, {0, 0.707f, 0.707f}, + {-0.707f, 0.707f, 0}, {0.707f, 0.707f, 0} + }; + + pxl8_vertex verts[4]; + memset(verts, 0, sizeof(verts)); + for (i32 i = 0; i < 4; i++) { + verts[i].position.x = pos.x + slope_verts[direction][i][0]; + verts[i].position.y = pos.y + slope_verts[direction][i][1]; + verts[i].position.z = pos.z + slope_verts[direction][i][2]; + verts[i].normal = slope_normals[direction]; + verts[i].color = texture_top; + verts[i].light = 255; + } + + u16 i0 = pxl8_mesh_push_vertex(mesh, verts[0]); + u16 i1 = pxl8_mesh_push_vertex(mesh, verts[1]); + u16 i2 = pxl8_mesh_push_vertex(mesh, verts[2]); + u16 i3 = pxl8_mesh_push_vertex(mesh, verts[3]); + pxl8_mesh_push_quad(mesh, i0, i1, i2, i3); + + static const i32 side_faces[4][2] = {{0, 1}, {0, 1}, {4, 5}, {4, 5}}; + static const f32 side_tris[4][2][3][3] = { + {{{0,0,0}, {0,1,1}, {0,0,1}}, {{1,0,0}, {1,0,1}, {1,1,1}}}, + {{{0,0,0}, {0,0,1}, {0,1,0}}, {{1,0,0}, {1,1,0}, {1,0,1}}}, + {{{0,0,0}, {0,0,1}, {0,1,1}}, {{1,0,0}, {1,1,0}, {1,0,1}}}, + {{{0,0,0}, {0,1,0}, {0,0,1}}, {{1,0,0}, {1,0,1}, {1,1,1}}} + }; + static const pxl8_vec3 side_normals[6] = { + {-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1} + }; + + for (i32 s = 0; s < 2; s++) { + i32 side_face = side_faces[direction][s]; + i32 snx = x + face_dirs[side_face][0]; + i32 snz = z + face_dirs[side_face][2]; + u8 side_neighbor = get_block_or_neighbor(chunk, neighbors, snx, y, snz); + if (side_neighbor != PXL8_VXL_BLOCK_UNKNOWN && !block_is_full_cube(side_neighbor, registry)) { + pxl8_vertex tv[3]; + memset(tv, 0, sizeof(tv)); + for (i32 vi = 0; vi < 3; vi++) { + tv[vi].position.x = pos.x + side_tris[direction][s][vi][0]; + tv[vi].position.y = pos.y + side_tris[direction][s][vi][1]; + tv[vi].position.z = pos.z + side_tris[direction][s][vi][2]; + tv[vi].normal = side_normals[side_face]; + tv[vi].color = texture_side; + tv[vi].light = 255; + } + u16 ti0 = pxl8_mesh_push_vertex(mesh, tv[0]); + u16 ti1 = pxl8_mesh_push_vertex(mesh, tv[1]); + u16 ti2 = pxl8_mesh_push_vertex(mesh, tv[2]); + pxl8_mesh_push_triangle(mesh, ti0, ti1, ti2); + } + } +} + +pxl8_mesh* pxl8_vxl_build_mesh(const pxl8_vxl_chunk* chunk, + const pxl8_vxl_chunk** neighbors, + const pxl8_vxl_block_registry* registry, + const pxl8_vxl_mesh_config* config) { + if (!chunk || !registry) return NULL; + + pxl8_vxl_mesh_config cfg = config ? *config : PXL8_VXL_MESH_CONFIG_DEFAULT; + + u32 max_faces = PXL8_VXL_CHUNK_VOLUME * 6; + pxl8_mesh* mesh = pxl8_mesh_create(max_faces * 4, max_faces * 6); + if (!mesh) return NULL; + + for (i32 face = 0; face < 6; face++) { + for (i32 slice = 0; slice < PXL8_VXL_CHUNK_SIZE; slice++) { + build_greedy_slice(chunk, neighbors, registry, &cfg, face, slice, mesh); + } + } + + for (i32 z = 0; z < PXL8_VXL_CHUNK_SIZE; z++) { + for (i32 y = 0; y < PXL8_VXL_CHUNK_SIZE; y++) { + for (i32 x = 0; x < PXL8_VXL_CHUNK_SIZE; x++) { + u8 block = pxl8_vxl_block_get(chunk, x, y, z); + if (block == PXL8_VXL_BLOCK_AIR) continue; + + pxl8_vxl_geometry geo = pxl8_vxl_block_registry_geometry(registry, block); + + switch (geo) { + case PXL8_VXL_GEOMETRY_CUBE: + break; + case PXL8_VXL_GEOMETRY_SLAB_BOTTOM: + add_slab_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, false); + break; + case PXL8_VXL_GEOMETRY_SLAB_TOP: + add_slab_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, true); + break; + case PXL8_VXL_GEOMETRY_SLOPE_NORTH: + add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 0); + break; + case PXL8_VXL_GEOMETRY_SLOPE_SOUTH: + add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 1); + break; + case PXL8_VXL_GEOMETRY_SLOPE_EAST: + add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 2); + break; + case PXL8_VXL_GEOMETRY_SLOPE_WEST: + add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 3); + break; + case PXL8_VXL_GEOMETRY_STAIRS_NORTH: + case PXL8_VXL_GEOMETRY_STAIRS_SOUTH: + case PXL8_VXL_GEOMETRY_STAIRS_EAST: + case PXL8_VXL_GEOMETRY_STAIRS_WEST: + break; + } + } + } + } + + return mesh; +} + +pxl8_vxl_render_state* pxl8_vxl_render_state_create(void) { + return pxl8_calloc(1, sizeof(pxl8_vxl_render_state)); +} + +void pxl8_vxl_render_state_destroy(pxl8_vxl_render_state* state) { + pxl8_free(state); +} + +void pxl8_vxl_set_wireframe(pxl8_vxl_render_state* state, bool wireframe) { + if (!state) return; + state->wireframe = wireframe; +} diff --git a/src/vxl/pxl8_vxl_render.h b/src/vxl/pxl8_vxl_render.h new file mode 100644 index 0000000..2762c46 --- /dev/null +++ b/src/vxl/pxl8_vxl_render.h @@ -0,0 +1,48 @@ +#pragma once + +#include "pxl8_mesh.h" +#include "pxl8_types.h" +#include "pxl8_vxl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct pxl8_vxl_render_state { + bool wireframe; +} pxl8_vxl_render_state; + +pxl8_vxl_render_state* pxl8_vxl_render_state_create(void); +void pxl8_vxl_render_state_destroy(pxl8_vxl_render_state* state); +void pxl8_vxl_set_wireframe(pxl8_vxl_render_state* state, bool wireframe); + +typedef struct pxl8_vxl_mesh_config { + bool ambient_occlusion; + bool vertex_heights; + f32 ao_strength; + f32 texture_scale; + i32 chunk_x; + i32 chunk_y; + i32 chunk_z; + u64 seed; +} pxl8_vxl_mesh_config; + +#define PXL8_VXL_MESH_CONFIG_DEFAULT ((pxl8_vxl_mesh_config){ \ + .ambient_occlusion = true, \ + .vertex_heights = true, \ + .ao_strength = 0.2f, \ + .texture_scale = 1.0f, \ + .chunk_x = 0, \ + .chunk_y = 0, \ + .chunk_z = 0, \ + .seed = 12345 \ +}) + +pxl8_mesh* pxl8_vxl_build_mesh(const pxl8_vxl_chunk* chunk, + const pxl8_vxl_chunk** neighbors, + const pxl8_vxl_block_registry* registry, + const pxl8_vxl_mesh_config* config); + +#ifdef __cplusplus +} +#endif diff --git a/src/world/pxl8_chunk.c b/src/world/pxl8_chunk.c deleted file mode 100644 index d7bcbd5..0000000 --- a/src/world/pxl8_chunk.c +++ /dev/null @@ -1,44 +0,0 @@ -#include "pxl8_chunk.h" - -#include "pxl8_mem.h" - -pxl8_chunk* pxl8_chunk_create_vxl(i32 cx, i32 cy, i32 cz) { - pxl8_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_chunk)); - if (!chunk) return NULL; - - chunk->type = PXL8_CHUNK_VXL; - chunk->cx = cx; - chunk->cy = cy; - chunk->cz = cz; - chunk->voxel = pxl8_voxel_chunk_create(); - - if (!chunk->voxel) { - pxl8_free(chunk); - return NULL; - } - - return chunk; -} - -pxl8_chunk* pxl8_chunk_create_bsp(u32 id) { - pxl8_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_chunk)); - if (!chunk) return NULL; - - chunk->type = PXL8_CHUNK_BSP; - chunk->id = id; - chunk->bsp = NULL; - - return chunk; -} - -void pxl8_chunk_destroy(pxl8_chunk* chunk) { - if (!chunk) return; - - if (chunk->type == PXL8_CHUNK_VXL && chunk->voxel) { - pxl8_voxel_chunk_destroy(chunk->voxel); - } else if (chunk->type == PXL8_CHUNK_BSP && chunk->bsp) { - pxl8_bsp_destroy(chunk->bsp); - } - - pxl8_free(chunk); -} diff --git a/src/world/pxl8_chunk.h b/src/world/pxl8_chunk.h deleted file mode 100644 index b531d32..0000000 --- a/src/world/pxl8_chunk.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include "pxl8_bsp.h" -#include "pxl8_types.h" -#include "pxl8_voxel.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum pxl8_chunk_type { - PXL8_CHUNK_VXL, - PXL8_CHUNK_BSP -} pxl8_chunk_type; - -typedef struct pxl8_chunk { - pxl8_chunk_type type; - u32 id; - u32 version; - i32 cx, cy, cz; - union { - pxl8_voxel_chunk* voxel; - pxl8_bsp* bsp; - }; -} pxl8_chunk; - -pxl8_chunk* pxl8_chunk_create_vxl(i32 cx, i32 cy, i32 cz); -pxl8_chunk* pxl8_chunk_create_bsp(u32 id); -void pxl8_chunk_destroy(pxl8_chunk* chunk); - -#ifdef __cplusplus -} -#endif diff --git a/src/world/pxl8_chunk_cache.h b/src/world/pxl8_chunk_cache.h deleted file mode 100644 index 0a2a49b..0000000 --- a/src/world/pxl8_chunk_cache.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include "pxl8_chunk.h" -#include "pxl8_mesh.h" -#include "pxl8_protocol.h" -#include "pxl8_types.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define PXL8_CHUNK_CACHE_SIZE 64 -#define PXL8_CHUNK_MAX_FRAGMENTS 64 -#define PXL8_CHUNK_MAX_DATA_SIZE 131072 - -typedef struct pxl8_chunk_cache_entry { - pxl8_chunk* chunk; - pxl8_mesh* mesh; - u64 last_used; - bool mesh_dirty; - bool valid; -} pxl8_chunk_cache_entry; - -typedef struct pxl8_chunk_assembly { - pxl8_chunk_type type; - u32 id; - i32 cx, cy, cz; - u32 version; - u8 fragment_count; - u8 fragments_received; - u8* data; - u32 data_size; - u32 data_capacity; - bool active; - bool complete; -} pxl8_chunk_assembly; - -typedef struct pxl8_chunk_cache { - pxl8_chunk_cache_entry entries[PXL8_CHUNK_CACHE_SIZE]; - pxl8_chunk_assembly assembly; - u32 entry_count; - u64 frame_counter; -} pxl8_chunk_cache; - -pxl8_chunk_cache* pxl8_chunk_cache_create(void); -void pxl8_chunk_cache_destroy(pxl8_chunk_cache* cache); - -pxl8_result pxl8_chunk_cache_receive(pxl8_chunk_cache* cache, - const pxl8_chunk_msg_header* hdr, - const u8* payload, usize len); - -pxl8_chunk* pxl8_chunk_cache_get_vxl(pxl8_chunk_cache* cache, i32 cx, i32 cy, i32 cz); -pxl8_chunk* pxl8_chunk_cache_get_bsp(pxl8_chunk_cache* cache, u32 id); - -pxl8_mesh* pxl8_chunk_cache_get_mesh(pxl8_chunk_cache* cache, - i32 cx, i32 cy, i32 cz, - const pxl8_block_registry* registry, - const pxl8_voxel_mesh_config* config); - -void pxl8_chunk_cache_tick(pxl8_chunk_cache* cache); -void pxl8_chunk_cache_evict_distant(pxl8_chunk_cache* cache, - i32 cx, i32 cy, i32 cz, i32 radius); -void pxl8_chunk_cache_invalidate_meshes(pxl8_chunk_cache* cache); - -#ifdef __cplusplus -} -#endif diff --git a/src/world/pxl8_gen.c b/src/world/pxl8_gen.c deleted file mode 100644 index eb2982e..0000000 --- a/src/world/pxl8_gen.c +++ /dev/null @@ -1,898 +0,0 @@ -#include "pxl8_gen.h" - -#include -#include -#include - -#include "pxl8_log.h" -#include "pxl8_mem.h" -#include "pxl8_rng.h" - -#define CELL_SIZE 64.0f -#define PVS_MAX_DEPTH 64 -#define WALL_HEIGHT 128.0f - -typedef struct room_grid { - u8* cells; - i32 width; - i32 height; -} room_grid; - -typedef struct bsp_build_context { - pxl8_bsp* bsp; - const room_grid* grid; - u32 node_count; - u32 plane_offset; -} bsp_build_context; - -typedef struct light_source { - pxl8_vec3 position; - f32 intensity; - f32 radius; -} light_source; - -static bool room_grid_init(room_grid* grid, i32 width, i32 height) { - grid->width = width; - grid->height = height; - grid->cells = pxl8_calloc(width * height, sizeof(u8)); - - return grid->cells != NULL; -} - -static u8 room_grid_get(const room_grid* grid, i32 x, i32 y) { - if (x < 0 || x >= grid->width || y < 0 || y >= grid->height) { - return 1; - } - return grid->cells[y * grid->width + x]; -} - -static void room_grid_set(room_grid* grid, i32 x, i32 y, u8 value) { - if (x < 0 || x >= grid->width || y < 0 || y >= grid->height) { - return; - } - grid->cells[y * grid->width + x] = value; -} - -static inline void compute_face_aabb(pxl8_bsp_face* face, const pxl8_bsp_vertex* verts, u32 vert_idx) { - face->aabb_min = (pxl8_vec3){1e30f, 1e30f, 1e30f}; - face->aabb_max = (pxl8_vec3){-1e30f, -1e30f, -1e30f}; - - for (u32 i = 0; i < 4; i++) { - pxl8_vec3 v = verts[vert_idx + i].position; - if (v.x < face->aabb_min.x) face->aabb_min.x = v.x; - if (v.x > face->aabb_max.x) face->aabb_max.x = v.x; - if (v.y < face->aabb_min.y) face->aabb_min.y = v.y; - if (v.y > face->aabb_max.y) face->aabb_max.y = v.y; - if (v.z < face->aabb_min.z) face->aabb_min.z = v.z; - if (v.z > face->aabb_max.z) face->aabb_max.z = v.z; - } -} - -static void room_grid_fill(room_grid* grid, u8 value) { - for (i32 y = 0; y < grid->height; y++) { - for (i32 x = 0; x < grid->width; x++) { - room_grid_set(grid, x, y, value); - } - } -} - -static f32 compute_vertex_light( - pxl8_vec3 pos, - pxl8_vec3 normal, - const light_source* lights, - u32 num_lights, - f32 ambient -) { - f32 total = ambient; - - for (u32 i = 0; i < num_lights; i++) { - pxl8_vec3 to_light = { - lights[i].position.x - pos.x, - lights[i].position.y - pos.y, - lights[i].position.z - pos.z - }; - - f32 dist_sq = to_light.x * to_light.x + to_light.y * to_light.y + to_light.z * to_light.z; - f32 dist = sqrtf(dist_sq); - - if (dist > lights[i].radius) continue; - if (dist < 1.0f) dist = 1.0f; - - f32 inv_dist = 1.0f / dist; - pxl8_vec3 light_dir = { - to_light.x * inv_dist, - to_light.y * inv_dist, - to_light.z * inv_dist - }; - - f32 ndotl = normal.x * light_dir.x + normal.y * light_dir.y + normal.z * light_dir.z; - if (ndotl < 0) ndotl = 0; - - f32 attenuation = 1.0f - (dist / lights[i].radius); - if (attenuation < 0) attenuation = 0; - attenuation *= attenuation; - - total += lights[i].intensity * ndotl * attenuation; - } - - if (total > 1.0f) total = 1.0f; - return total; -} - -static void compute_bsp_vertex_lighting( - pxl8_bsp* bsp, - const light_source* lights, - u32 num_lights, - f32 ambient -) { - if (!bsp || bsp->num_vertices == 0) return; - - bsp->vertex_lights = pxl8_calloc(bsp->num_vertices, sizeof(u32)); - if (!bsp->vertex_lights) return; - bsp->num_vertex_lights = bsp->num_vertices; - - for (u32 f = 0; f < bsp->num_faces; f++) { - const pxl8_bsp_face* face = &bsp->faces[f]; - pxl8_vec3 normal = {0, 1, 0}; - - if (face->plane_id < bsp->num_planes) { - normal = bsp->planes[face->plane_id].normal; - } - - for (u32 e = 0; e < face->num_edges; e++) { - i32 surfedge_idx = face->first_edge + e; - if (surfedge_idx >= (i32)bsp->num_surfedges) continue; - - i32 edge_idx = bsp->surfedges[surfedge_idx]; - u32 vert_idx; - - if (edge_idx >= 0) { - if ((u32)edge_idx >= bsp->num_edges) continue; - vert_idx = bsp->edges[edge_idx].vertex[0]; - } else { - edge_idx = -edge_idx; - if ((u32)edge_idx >= bsp->num_edges) continue; - vert_idx = bsp->edges[edge_idx].vertex[1]; - } - - if (vert_idx >= bsp->num_vertices) continue; - - pxl8_vec3 pos = bsp->vertices[vert_idx].position; - f32 light = compute_vertex_light(pos, normal, lights, num_lights, ambient); - - u8 light_byte = (u8)(light * 255.0f); - bsp->vertex_lights[vert_idx] = ((u32)light_byte << 24) | 0x00FFFFFF; - } - } - - pxl8_debug("Computed vertex lighting: %u vertices, %u lights", bsp->num_vertices, num_lights); -} - -static pxl8_bsp_cell_portals* build_pxl8_bsp_cell_portals(const room_grid* grid, f32 cell_size) { - i32 total_cells = grid->width * grid->height; - pxl8_bsp_cell_portals* portals = pxl8_calloc(total_cells, sizeof(pxl8_bsp_cell_portals)); - if (!portals) return NULL; - - for (i32 y = 0; y < grid->height; y++) { - for (i32 x = 0; x < grid->width; x++) { - if (room_grid_get(grid, x, y) != 0) continue; - - i32 c = y * grid->width + x; - f32 cx = x * cell_size; - f32 cz = y * cell_size; - - if (x > 0 && room_grid_get(grid, x - 1, y) == 0) { - pxl8_bsp_portal* p = &portals[c].portals[portals[c].num_portals++]; - p->x0 = cx; - p->z0 = cz; - p->x1 = cx; - p->z1 = cz + cell_size; - p->target_leaf = y * grid->width + (x - 1); - } - if (x < grid->width - 1 && room_grid_get(grid, x + 1, y) == 0) { - pxl8_bsp_portal* p = &portals[c].portals[portals[c].num_portals++]; - p->x0 = cx + cell_size; - p->z0 = cz + cell_size; - p->x1 = cx + cell_size; - p->z1 = cz; - p->target_leaf = y * grid->width + (x + 1); - } - if (y > 0 && room_grid_get(grid, x, y - 1) == 0) { - pxl8_bsp_portal* p = &portals[c].portals[portals[c].num_portals++]; - p->x0 = cx + cell_size; - p->z0 = cz; - p->x1 = cx; - p->z1 = cz; - p->target_leaf = (y - 1) * grid->width + x; - } - if (y < grid->height - 1 && room_grid_get(grid, x, y + 1) == 0) { - pxl8_bsp_portal* p = &portals[c].portals[portals[c].num_portals++]; - p->x0 = cx; - p->z0 = cz + cell_size; - p->x1 = cx + cell_size; - p->z1 = cz + cell_size; - p->target_leaf = (y + 1) * grid->width + x; - } - } - } - return portals; -} - -typedef struct flood_entry { - u32 leaf; - u32 depth; -} flood_entry; - -static void portal_flood_bfs( - u32 start_leaf, - const pxl8_bsp_cell_portals* portals, - const pxl8_bsp_leaf* leafs, - u8* pvs, - u32 num_leafs, - f32 cell_size, - i32 grid_width -) { - (void)cell_size; - (void)grid_width; - - u32 pvs_bytes = (num_leafs + 7) / 8; - u8* visited = pxl8_calloc(pvs_bytes, 1); - flood_entry* queue = pxl8_malloc(num_leafs * sizeof(flood_entry)); - if (!visited || !queue) { - pxl8_free(visited); - pxl8_free(queue); - return; - } - - u32 head = 0, tail = 0; - - pvs[start_leaf >> 3] |= (1 << (start_leaf & 7)); - visited[start_leaf >> 3] |= (1 << (start_leaf & 7)); - queue[tail++] = (flood_entry){start_leaf, 0}; - - while (head < tail) { - flood_entry e = queue[head++]; - - if (e.depth >= PVS_MAX_DEPTH) continue; - if (leafs[e.leaf].contents == -1) continue; - - const pxl8_bsp_cell_portals* cp = &portals[e.leaf]; - for (u8 i = 0; i < cp->num_portals; i++) { - u32 target = cp->portals[i].target_leaf; - u32 byte = target >> 3; - u32 bit = target & 7; - - if (visited[byte] & (1 << bit)) continue; - visited[byte] |= (1 << bit); - - if (leafs[target].contents == -1) continue; - - pvs[byte] |= (1 << bit); - queue[tail++] = (flood_entry){target, e.depth + 1}; - } - } - - pxl8_free(visited); - pxl8_free(queue); -} - -static u8* compute_leaf_pvs(u32 start_leaf, const pxl8_bsp_cell_portals* portals, - u32 num_leafs, const pxl8_bsp_leaf* leafs, - const room_grid* grid, f32 cell_size) { - u32 pvs_bytes = (num_leafs + 7) / 8; - u8* pvs = pxl8_calloc(pvs_bytes, 1); - if (!pvs) return NULL; - - portal_flood_bfs(start_leaf, portals, leafs, pvs, num_leafs, cell_size, grid->width); - - return pvs; -} - -static u32 rle_compress_pvs(const u8* pvs, u32 pvs_bytes, u8* out) { - u32 out_pos = 0; - u32 i = 0; - - while (i < pvs_bytes) { - if (pvs[i] != 0) { - out[out_pos++] = pvs[i++]; - } else { - u32 count = 0; - while (i < pvs_bytes && pvs[i] == 0 && count < 255) { - count++; - i++; - } - out[out_pos++] = 0; - out[out_pos++] = (u8)count; - } - } - return out_pos; -} - -static pxl8_result build_pvs_data(pxl8_bsp* bsp, const pxl8_bsp_cell_portals* portals, - const room_grid* grid, f32 cell_size) { - u32 num_leafs = bsp->num_leafs; - u32 pvs_bytes = (num_leafs + 7) / 8; - - u32 max_visdata = num_leafs * pvs_bytes * 2; - u8* visdata = pxl8_malloc(max_visdata); - if (!visdata) return PXL8_ERROR_OUT_OF_MEMORY; - - u32 visdata_pos = 0; - - u8* compressed = pxl8_malloc(pvs_bytes * 2); - if (!compressed) { - pxl8_free(visdata); - return PXL8_ERROR_OUT_OF_MEMORY; - } - - u32 debug_count = 0; - for (u32 leaf = 0; leaf < num_leafs; leaf++) { - if (bsp->leafs[leaf].contents == -1) { - bsp->leafs[leaf].visofs = -1; - continue; - } - - u8* pvs = compute_leaf_pvs(leaf, portals, num_leafs, bsp->leafs, grid, cell_size); - if (!pvs) { - pxl8_free(compressed); - pxl8_free(visdata); - return PXL8_ERROR_OUT_OF_MEMORY; - } - - if (debug_count < 3) { - u32 visible = 0; - for (u32 b = 0; b < pvs_bytes; b++) { - for (u32 i = 0; i < 8; i++) { - if (pvs[b] & (1 << i)) visible++; - } - } - pxl8_debug("Leaf %u PVS: %u cells visible (portals: %u)", leaf, visible, portals[leaf].num_portals); - debug_count++; - } - - u32 compressed_size = rle_compress_pvs(pvs, pvs_bytes, compressed); - - bsp->leafs[leaf].visofs = visdata_pos; - memcpy(visdata + visdata_pos, compressed, compressed_size); - visdata_pos += compressed_size; - - pxl8_free(pvs); - } - - pxl8_free(compressed); - bsp->visdata = pxl8_realloc(visdata, visdata_pos > 0 ? visdata_pos : 1); - bsp->visdata_size = visdata_pos; - - pxl8_debug("Built PVS: %u leafs, %u bytes visdata", num_leafs, visdata_pos); - return PXL8_OK; -} - -static i32 build_bsp_node(bsp_build_context* ctx, i32 x0, i32 y0, i32 x1, i32 y1, i32 depth) { - if (x1 - x0 == 1 && y1 - y0 == 1) { - i32 leaf_idx = y0 * ctx->grid->width + x0; - return -(leaf_idx + 1); - } - - i32 node_idx = ctx->node_count++; - pxl8_bsp_node* node = &ctx->bsp->nodes[node_idx]; - - i32 plane_idx = ctx->plane_offset++; - pxl8_bsp_plane* plane = &ctx->bsp->planes[plane_idx]; - node->plane_id = plane_idx; - - if (depth % 2 == 0) { - i32 mid_x = (x0 + x1) / 2; - f32 split_pos = mid_x * CELL_SIZE; - - plane->normal = (pxl8_vec3){1, 0, 0}; - plane->dist = split_pos; - - node->children[0] = build_bsp_node(ctx, mid_x, y0, x1, y1, depth + 1); - node->children[1] = build_bsp_node(ctx, x0, y0, mid_x, y1, depth + 1); - } else { - i32 mid_y = (y0 + y1) / 2; - f32 split_pos = mid_y * CELL_SIZE; - - plane->normal = (pxl8_vec3){0, 0, 1}; - plane->dist = split_pos; - - node->children[0] = build_bsp_node(ctx, x0, mid_y, x1, y1, depth + 1); - node->children[1] = build_bsp_node(ctx, x0, y0, x1, mid_y, depth + 1); - } - - return node_idx; -} - -static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) { - i32 vertex_count = 0; - i32 face_count = 0; - i32 floor_ceiling_count = 0; - - for (i32 y = 0; y < grid->height; y++) { - for (i32 x = 0; x < grid->width; x++) { - if (room_grid_get(grid, x, y) == 0) { - if (room_grid_get(grid, x - 1, y) == 1) face_count++; - if (room_grid_get(grid, x + 1, y) == 1) face_count++; - if (room_grid_get(grid, x, y - 1) == 1) face_count++; - if (room_grid_get(grid, x, y + 1) == 1) face_count++; - floor_ceiling_count++; - } - } - } - - face_count += floor_ceiling_count; - vertex_count = face_count * 4; - - pxl8_debug("Level generation: %dx%d grid -> %d faces, %d vertices", - grid->width, grid->height, face_count, vertex_count); - - i32 total_cells = grid->width * grid->height; - u32 max_nodes = 2 * total_cells; - u32 total_planes = face_count + max_nodes; - - bsp->vertices = pxl8_calloc(vertex_count, sizeof(pxl8_bsp_vertex)); - bsp->faces = pxl8_calloc(face_count, sizeof(pxl8_bsp_face)); - bsp->planes = pxl8_calloc(total_planes, sizeof(pxl8_bsp_plane)); - bsp->edges = pxl8_calloc(vertex_count, sizeof(pxl8_bsp_edge)); - bsp->surfedges = pxl8_calloc(vertex_count, sizeof(i32)); - bsp->nodes = pxl8_calloc(max_nodes, sizeof(pxl8_bsp_node)); - - u32* face_cell = pxl8_calloc(face_count, sizeof(u32)); - - if (!bsp->vertices || !bsp->faces || !bsp->planes || !bsp->edges || - !bsp->surfedges || !bsp->nodes || !face_cell) { - pxl8_free(face_cell); - return PXL8_ERROR_OUT_OF_MEMORY; - } - - bsp->materials = NULL; - bsp->num_materials = 0; - - i32 vert_idx = 0; - i32 face_idx = 0; - i32 edge_idx = 0; - - const f32 cell_size = CELL_SIZE; - const f32 wall_height = WALL_HEIGHT; - - for (i32 y = 0; y < grid->height; y++) { - for (i32 x = 0; x < grid->width; x++) { - if (room_grid_get(grid, x, y) == 0) { - f32 fx = (f32)x * cell_size; - f32 fy = (f32)y * cell_size; - i32 cell_idx = y * grid->width + x; - - if (room_grid_get(grid, x - 1, y) == 1) { - bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy}; - bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx, wall_height, fy}; - bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx, wall_height, fy + cell_size}; - bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, 0, fy + cell_size}; - - bsp->planes[face_idx].normal = (pxl8_vec3){1, 0, 0}; - bsp->planes[face_idx].dist = fx; - - bsp->faces[face_idx].plane_id = face_idx; - bsp->faces[face_idx].num_edges = 4; - bsp->faces[face_idx].first_edge = edge_idx; - bsp->faces[face_idx].material_id = 0; - - for (i32 i = 0; i < 4; i++) { - bsp->edges[edge_idx + i].vertex[0] = vert_idx + i; - bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4); - bsp->surfedges[edge_idx + i] = edge_idx + i; - } - - compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx); - face_cell[face_idx] = cell_idx; - - vert_idx += 4; - edge_idx += 4; - face_idx++; - } - - if (room_grid_get(grid, x + 1, y) == 1) { - bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx + cell_size, 0, fy}; - bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx + cell_size, 0, fy + cell_size}; - bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size}; - bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx + cell_size, wall_height, fy}; - - bsp->planes[face_idx].normal = (pxl8_vec3){-1, 0, 0}; - bsp->planes[face_idx].dist = -(fx + cell_size); - - bsp->faces[face_idx].plane_id = face_idx; - bsp->faces[face_idx].num_edges = 4; - bsp->faces[face_idx].first_edge = edge_idx; - bsp->faces[face_idx].material_id = 0; - - for (i32 i = 0; i < 4; i++) { - bsp->edges[edge_idx + i].vertex[0] = vert_idx + i; - bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4); - bsp->surfedges[edge_idx + i] = edge_idx + i; - } - - compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx); - face_cell[face_idx] = cell_idx; - - vert_idx += 4; - edge_idx += 4; - face_idx++; - } - - if (room_grid_get(grid, x, y - 1) == 1) { - bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy}; - bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx + cell_size, 0, fy}; - bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy}; - bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, wall_height, fy}; - - bsp->planes[face_idx].normal = (pxl8_vec3){0, 0, 1}; - bsp->planes[face_idx].dist = fy; - - bsp->faces[face_idx].plane_id = face_idx; - bsp->faces[face_idx].num_edges = 4; - bsp->faces[face_idx].first_edge = edge_idx; - bsp->faces[face_idx].material_id = 0; - - for (i32 i = 0; i < 4; i++) { - bsp->edges[edge_idx + i].vertex[0] = vert_idx + i; - bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4); - bsp->surfedges[edge_idx + i] = edge_idx + i; - } - - compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx); - face_cell[face_idx] = cell_idx; - - vert_idx += 4; - edge_idx += 4; - face_idx++; - } - - if (room_grid_get(grid, x, y + 1) == 1) { - bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy + cell_size}; - bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx, wall_height, fy + cell_size}; - bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size}; - bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx + cell_size, 0, fy + cell_size}; - - bsp->planes[face_idx].normal = (pxl8_vec3){0, 0, -1}; - bsp->planes[face_idx].dist = -(fy + cell_size); - - bsp->faces[face_idx].plane_id = face_idx; - bsp->faces[face_idx].num_edges = 4; - bsp->faces[face_idx].first_edge = edge_idx; - bsp->faces[face_idx].material_id = 0; - - for (i32 i = 0; i < 4; i++) { - bsp->edges[edge_idx + i].vertex[0] = vert_idx + i; - bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4); - bsp->surfedges[edge_idx + i] = edge_idx + i; - } - - compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx); - face_cell[face_idx] = cell_idx; - - vert_idx += 4; - edge_idx += 4; - face_idx++; - } - } - } - } - - for (i32 y = 0; y < grid->height; y++) { - for (i32 x = 0; x < grid->width; x++) { - if (room_grid_get(grid, x, y) == 0) { - f32 fx = (f32)x * cell_size; - f32 fy = (f32)y * cell_size; - i32 cell_idx = y * grid->width + x; - - bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy}; - bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx, 0, fy + cell_size}; - bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, 0, fy + cell_size}; - bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx + cell_size, 0, fy}; - - bsp->planes[face_idx].normal = (pxl8_vec3){0, 1, 0}; - bsp->planes[face_idx].dist = 0; - - bsp->faces[face_idx].plane_id = face_idx; - bsp->faces[face_idx].num_edges = 4; - bsp->faces[face_idx].first_edge = edge_idx; - bsp->faces[face_idx].material_id = 0; - - for (i32 i = 0; i < 4; i++) { - bsp->edges[edge_idx + i].vertex[0] = vert_idx + i; - bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4); - bsp->surfedges[edge_idx + i] = edge_idx + i; - } - - compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx); - face_cell[face_idx] = cell_idx; - - vert_idx += 4; - edge_idx += 4; - face_idx++; - } - } - } - - bsp->num_vertices = vertex_count; - bsp->num_faces = face_count; - bsp->num_edges = vertex_count; - bsp->num_surfedges = vertex_count; - - bsp->leafs = pxl8_calloc(total_cells, sizeof(pxl8_bsp_leaf)); - bsp->marksurfaces = pxl8_calloc(face_count, sizeof(u16)); - - if (!bsp->leafs || !bsp->marksurfaces) { - pxl8_free(face_cell); - return PXL8_ERROR_OUT_OF_MEMORY; - } - - bsp->num_leafs = total_cells; - bsp->num_marksurfaces = face_count; - - u32* faces_per_cell = pxl8_calloc(total_cells, sizeof(u32)); - u32* cell_offset = pxl8_calloc(total_cells, sizeof(u32)); - u32* cell_cursor = pxl8_calloc(total_cells, sizeof(u32)); - - if (!faces_per_cell || !cell_offset || !cell_cursor) { - pxl8_free(faces_per_cell); - pxl8_free(cell_offset); - pxl8_free(cell_cursor); - pxl8_free(face_cell); - return PXL8_ERROR_OUT_OF_MEMORY; - } - - for (i32 i = 0; i < face_count; i++) { - faces_per_cell[face_cell[i]]++; - } - - u32 offset = 0; - for (i32 c = 0; c < total_cells; c++) { - cell_offset[c] = offset; - offset += faces_per_cell[c]; - } - - for (i32 i = 0; i < face_count; i++) { - u32 c = face_cell[i]; - bsp->marksurfaces[cell_offset[c] + cell_cursor[c]++] = i; - } - - for (i32 y = 0; y < grid->height; y++) { - for (i32 x = 0; x < grid->width; x++) { - i32 c = y * grid->width + x; - pxl8_bsp_leaf* leaf = &bsp->leafs[c]; - - f32 fx = (f32)x * cell_size; - f32 fz = (f32)y * cell_size; - - leaf->mins[0] = (i16)fx; - leaf->mins[1] = 0; - leaf->mins[2] = (i16)fz; - leaf->maxs[0] = (i16)(fx + cell_size); - leaf->maxs[1] = (i16)wall_height; - leaf->maxs[2] = (i16)(fz + cell_size); - - if (room_grid_get(grid, x, y) == 0) { - leaf->contents = -2; - leaf->first_marksurface = cell_offset[c]; - leaf->num_marksurfaces = faces_per_cell[c]; - } else { - leaf->contents = -1; - leaf->first_marksurface = 0; - leaf->num_marksurfaces = 0; - } - leaf->visofs = -1; - } - } - - pxl8_free(faces_per_cell); - pxl8_free(cell_offset); - pxl8_free(cell_cursor); - pxl8_free(face_cell); - - bsp_build_context ctx = { - .bsp = bsp, - .grid = grid, - .node_count = 0, - .plane_offset = face_count, - }; - - build_bsp_node(&ctx, 0, 0, grid->width, grid->height, 0); - bsp->num_nodes = ctx.node_count; - bsp->num_planes = ctx.plane_offset; - - pxl8_debug("Built BSP tree: %u nodes, %u leafs, %u planes", - bsp->num_nodes, bsp->num_leafs, bsp->num_planes); - - pxl8_bsp_cell_portals* portals = build_pxl8_bsp_cell_portals(grid, cell_size); - if (!portals) { - return PXL8_ERROR_OUT_OF_MEMORY; - } - - u32 walkable_cells = 0; - u32 total_portals = 0; - i32 first_walkable = -1; - for (i32 c = 0; c < total_cells; c++) { - if (bsp->leafs[c].contents == -2) { - walkable_cells++; - if (first_walkable < 0) first_walkable = c; - } - total_portals += portals[c].num_portals; - } - pxl8_debug("Portal stats: %u walkable cells, %u total portals (avg %.1f per cell)", - walkable_cells, total_portals, (f32)total_portals / walkable_cells); - - if (first_walkable >= 0) { - u32 pvs_bytes = (total_cells + 7) / 8; - u8* visited = pxl8_calloc(pvs_bytes, 1); - u8* queue = pxl8_malloc(total_cells * sizeof(u32)); - u32 head = 0, tail = 0; - ((u32*)queue)[tail++] = first_walkable; - visited[first_walkable >> 3] |= (1 << (first_walkable & 7)); - u32 reachable = 0; - while (head < tail) { - u32 c = ((u32*)queue)[head++]; - reachable++; - for (u8 i = 0; i < portals[c].num_portals; i++) { - u32 n = portals[c].portals[i].target_leaf; - u32 nb = n >> 3, ni = n & 7; - if (!(visited[nb] & (1 << ni))) { - visited[nb] |= (1 << ni); - ((u32*)queue)[tail++] = n; - } - } - } - pxl8_debug("Connectivity: %u/%u walkable cells reachable from leaf %d", - reachable, walkable_cells, first_walkable); - pxl8_free(visited); - pxl8_free(queue); - } - - pxl8_result pvs_result = build_pvs_data(bsp, portals, grid, cell_size); - if (pvs_result != PXL8_OK) { - pxl8_free(portals); - return pvs_result; - } - - bsp->cell_portals = portals; - bsp->num_cell_portals = total_cells; - - return PXL8_OK; -} - -static bool bounds_intersects(const pxl8_bounds* a, const pxl8_bounds* b) { - return !(a->x + a->w <= b->x || b->x + b->w <= a->x || - a->y + a->h <= b->y || b->y + b->h <= a->y); -} - -static void carve_corridor_h(room_grid* grid, i32 x1, i32 x2, i32 y) { - i32 start = (x1 < x2) ? x1 : x2; - i32 end = (x1 > x2) ? x1 : x2; - for (i32 x = start; x <= end; x++) { - room_grid_set(grid, x, y, 0); - room_grid_set(grid, x, y - 1, 0); - room_grid_set(grid, x, y + 1, 0); - } -} - -static void carve_corridor_v(room_grid* grid, i32 y1, i32 y2, i32 x) { - i32 start = (y1 < y2) ? y1 : y2; - i32 end = (y1 > y2) ? y1 : y2; - for (i32 y = start; y <= end; y++) { - room_grid_set(grid, x, y, 0); - room_grid_set(grid, x - 1, y, 0); - room_grid_set(grid, x + 1, y, 0); - } -} - -static pxl8_result procgen_rooms(pxl8_bsp* bsp, const pxl8_procgen_params* params) { - pxl8_debug("procgen_rooms called: %dx%d, seed=%u, min=%d, max=%d, num=%d", - params->width, params->height, params->seed, - params->min_room_size, params->max_room_size, params->num_rooms); - - pxl8_rng rng; - pxl8_rng_seed(&rng, params->seed); - - room_grid grid; - if (!room_grid_init(&grid, params->width, params->height)) { - pxl8_error("Failed to allocate room grid"); - return PXL8_ERROR_OUT_OF_MEMORY; - } - - room_grid_fill(&grid, 1); - - pxl8_bounds rooms[256]; - i32 room_count = 0; - i32 max_attempts = params->num_rooms * 10; - - const f32 cell_size = CELL_SIZE; - const f32 light_height = 80.0f; - - for (i32 attempt = 0; attempt < max_attempts && room_count < params->num_rooms && room_count < 256; attempt++) { - i32 w = params->min_room_size + (pxl8_rng_next(&rng) % (params->max_room_size - params->min_room_size + 1)); - i32 h = params->min_room_size + (pxl8_rng_next(&rng) % (params->max_room_size - params->min_room_size + 1)); - i32 x = 1 + (pxl8_rng_next(&rng) % (params->width - w - 2)); - i32 y = 1 + (pxl8_rng_next(&rng) % (params->height - h - 2)); - - pxl8_bounds new_room = {x, y, w, h}; - - bool overlaps = false; - for (i32 i = 0; i < room_count; i++) { - if (bounds_intersects(&new_room, &rooms[i])) { - overlaps = true; - break; - } - } - - if (!overlaps) { - for (i32 ry = y; ry < y + h; ry++) { - for (i32 rx = x; rx < x + w; rx++) { - room_grid_set(&grid, rx, ry, 0); - } - } - - if (room_count > 0) { - i32 new_cx = x + w / 2; - i32 new_cy = y + h / 2; - i32 prev_cx = rooms[room_count - 1].x + rooms[room_count - 1].w / 2; - i32 prev_cy = rooms[room_count - 1].y + rooms[room_count - 1].h / 2; - - if (pxl8_rng_next(&rng) % 2 == 0) { - carve_corridor_h(&grid, prev_cx, new_cx, prev_cy); - carve_corridor_v(&grid, prev_cy, new_cy, new_cx); - } else { - carve_corridor_v(&grid, prev_cy, new_cy, prev_cx); - carve_corridor_h(&grid, prev_cx, new_cx, new_cy); - } - } - - rooms[room_count++] = new_room; - } - } - - pxl8_debug("Room generation: %dx%d grid -> %d rooms created", - params->width, params->height, room_count); - - pxl8_result result = grid_to_bsp(bsp, &grid); - pxl8_free(grid.cells); - - if (result != PXL8_OK) { - return result; - } - - light_source lights[256]; - u32 num_lights = 0; - - for (i32 i = 0; i < room_count && num_lights < 256; i++) { - f32 cx = (rooms[i].x + rooms[i].w / 2.0f) * cell_size; - f32 cy = (rooms[i].y + rooms[i].h / 2.0f) * cell_size; - - lights[num_lights++] = (light_source){ - .position = {cx, light_height, cy}, - .intensity = 0.8f, - .radius = 300.0f, - }; - } - - compute_bsp_vertex_lighting(bsp, lights, num_lights, 0.1f); - - return result; -} - -pxl8_result pxl8_procgen(pxl8_bsp* bsp, const pxl8_procgen_params* params) { - if (!bsp || !params) { - return PXL8_ERROR_NULL_POINTER; - } - - switch (params->type) { - case PXL8_PROCGEN_ROOMS: - return procgen_rooms(bsp, params); - - case PXL8_PROCGEN_TERRAIN: - pxl8_error("Terrain generation not yet implemented"); - return PXL8_ERROR_NOT_INITIALIZED; - - default: - pxl8_error("Unknown procgen type: %d", params->type); - return PXL8_ERROR_INVALID_ARGUMENT; - } -} diff --git a/src/world/pxl8_gen.h b/src/world/pxl8_gen.h deleted file mode 100644 index bef83b0..0000000 --- a/src/world/pxl8_gen.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include "pxl8_bsp.h" -#include "pxl8_types.h" - -typedef enum pxl8_procgen_type { - PXL8_PROCGEN_ROOMS, - PXL8_PROCGEN_TERRAIN -} pxl8_procgen_type; - -typedef struct pxl8_procgen_params { - pxl8_procgen_type type; - - i32 width; - i32 height; - i32 depth; - u32 seed; - - i32 min_room_size; - i32 max_room_size; - i32 num_rooms; -} pxl8_procgen_params; - -#ifdef __cplusplus -extern "C" { -#endif - -pxl8_result pxl8_procgen(pxl8_bsp* bsp, const pxl8_procgen_params* params); - -#ifdef __cplusplus -} -#endif diff --git a/src/world/pxl8_voxel.c b/src/world/pxl8_voxel.c deleted file mode 100644 index 20fb38b..0000000 --- a/src/world/pxl8_voxel.c +++ /dev/null @@ -1,406 +0,0 @@ -#include "pxl8_voxel.h" - -#include - -#include "pxl8_mem.h" - -#define PXL8_VOXEL_CHUNK_VOLUME (PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE) - -typedef struct pxl8_block_def { - char name[32]; - u8 texture_id; - pxl8_voxel_geometry geometry; - bool registered; -} pxl8_block_def; - -struct pxl8_block_registry { - pxl8_block_def blocks[PXL8_BLOCK_COUNT]; -}; - -struct pxl8_voxel_chunk { - pxl8_block blocks[PXL8_VOXEL_CHUNK_VOLUME]; -}; - -static inline u32 voxel_index(i32 x, i32 y, i32 z) { - return (u32)(x + y * PXL8_VOXEL_CHUNK_SIZE + z * PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE); -} - -static inline bool voxel_in_bounds(i32 x, i32 y, i32 z) { - return x >= 0 && x < PXL8_VOXEL_CHUNK_SIZE && - y >= 0 && y < PXL8_VOXEL_CHUNK_SIZE && - z >= 0 && z < PXL8_VOXEL_CHUNK_SIZE; -} - -pxl8_voxel_chunk* pxl8_voxel_chunk_create(void) { - pxl8_voxel_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_voxel_chunk)); - return chunk; -} - -void pxl8_voxel_chunk_clear(pxl8_voxel_chunk* chunk) { - if (!chunk) return; - memset(chunk->blocks, 0, sizeof(chunk->blocks)); -} - -void pxl8_voxel_chunk_destroy(pxl8_voxel_chunk* chunk) { - pxl8_free(chunk); -} - -pxl8_block pxl8_voxel_get(const pxl8_voxel_chunk* chunk, i32 x, i32 y, i32 z) { - if (!chunk || !voxel_in_bounds(x, y, z)) return PXL8_BLOCK_AIR; - return chunk->blocks[voxel_index(x, y, z)]; -} - -void pxl8_voxel_set(pxl8_voxel_chunk* chunk, i32 x, i32 y, i32 z, pxl8_block block) { - if (!chunk || !voxel_in_bounds(x, y, z)) return; - chunk->blocks[voxel_index(x, y, z)] = block; -} - -void pxl8_voxel_fill(pxl8_voxel_chunk* chunk, pxl8_block block) { - if (!chunk) return; - memset(chunk->blocks, block, sizeof(chunk->blocks)); -} - -pxl8_block_registry* pxl8_block_registry_create(void) { - pxl8_block_registry* registry = pxl8_calloc(1, sizeof(pxl8_block_registry)); - return registry; -} - -void pxl8_block_registry_destroy(pxl8_block_registry* registry) { - pxl8_free(registry); -} - -void pxl8_block_registry_register(pxl8_block_registry* registry, pxl8_block id, const char* name, u8 texture_id, pxl8_voxel_geometry geo) { - if (!registry || id == PXL8_BLOCK_AIR) return; - - pxl8_block_def* def = ®istry->blocks[id]; - strncpy(def->name, name ? name : "", sizeof(def->name) - 1); - def->texture_id = texture_id; - def->geometry = geo; - def->registered = true; -} - -const char* pxl8_block_registry_name(const pxl8_block_registry* registry, pxl8_block id) { - if (!registry || !registry->blocks[id].registered) return NULL; - return registry->blocks[id].name; -} - -pxl8_voxel_geometry pxl8_block_registry_geometry(const pxl8_block_registry* registry, pxl8_block id) { - if (!registry || !registry->blocks[id].registered) return PXL8_VOXEL_GEOMETRY_CUBE; - return registry->blocks[id].geometry; -} - -u8 pxl8_block_registry_texture(const pxl8_block_registry* registry, pxl8_block id) { - if (!registry || !registry->blocks[id].registered) return 0; - return registry->blocks[id].texture_id; -} - -static bool block_is_opaque(pxl8_block block) { - return block != PXL8_BLOCK_AIR; -} - -static pxl8_block get_block_or_neighbor(const pxl8_voxel_chunk* chunk, const pxl8_voxel_chunk** neighbors, i32 x, i32 y, i32 z) { - if (voxel_in_bounds(x, y, z)) { - return chunk->blocks[voxel_index(x, y, z)]; - } - - i32 nx = x, ny = y, nz = z; - i32 neighbor_idx = -1; - - if (x < 0) { neighbor_idx = 0; nx = x + PXL8_VOXEL_CHUNK_SIZE; } - else if (x >= PXL8_VOXEL_CHUNK_SIZE) { neighbor_idx = 1; nx = x - PXL8_VOXEL_CHUNK_SIZE; } - else if (y < 0) { neighbor_idx = 2; ny = y + PXL8_VOXEL_CHUNK_SIZE; } - else if (y >= PXL8_VOXEL_CHUNK_SIZE) { neighbor_idx = 3; ny = y - PXL8_VOXEL_CHUNK_SIZE; } - else if (z < 0) { neighbor_idx = 4; nz = z + PXL8_VOXEL_CHUNK_SIZE; } - else if (z >= PXL8_VOXEL_CHUNK_SIZE) { neighbor_idx = 5; nz = z - PXL8_VOXEL_CHUNK_SIZE; } - - if (neighbor_idx >= 0 && neighbors && neighbors[neighbor_idx]) { - return pxl8_voxel_get(neighbors[neighbor_idx], nx, ny, nz); - } - - return PXL8_BLOCK_AIR; -} - -static f32 compute_ao(const pxl8_voxel_chunk* chunk, const pxl8_voxel_chunk** neighbors, - i32 x, i32 y, i32 z, i32 dx1, i32 dy1, i32 dz1, i32 dx2, i32 dy2, i32 dz2) { - bool side1 = block_is_opaque(get_block_or_neighbor(chunk, neighbors, x + dx1, y + dy1, z + dz1)); - bool side2 = block_is_opaque(get_block_or_neighbor(chunk, neighbors, x + dx2, y + dy2, z + dz2)); - bool corner = block_is_opaque(get_block_or_neighbor(chunk, neighbors, x + dx1 + dx2, y + dy1 + dy2, z + dz1 + dz2)); - - if (side1 && side2) return 0.0f; - return (3.0f - (f32)side1 - (f32)side2 - (f32)corner) / 3.0f; -} - -static void add_face_vertices(pxl8_mesh* mesh, const pxl8_voxel_mesh_config* config, - pxl8_vec3 pos, i32 face, u8 texture_id, - f32 ao0, f32 ao1, f32 ao2, f32 ao3) { - static const pxl8_vec3 face_normals[6] = { - {-1, 0, 0}, { 1, 0, 0}, - { 0, -1, 0}, { 0, 1, 0}, - { 0, 0, -1}, { 0, 0, 1} - }; - - static const i32 face_vertices[6][4][3] = { - {{0,0,1}, {0,0,0}, {0,1,0}, {0,1,1}}, - {{1,0,0}, {1,0,1}, {1,1,1}, {1,1,0}}, - {{0,0,0}, {1,0,0}, {1,0,1}, {0,0,1}}, - {{0,1,1}, {1,1,1}, {1,1,0}, {0,1,0}}, - {{0,0,0}, {0,1,0}, {1,1,0}, {1,0,0}}, - {{1,0,1}, {1,1,1}, {0,1,1}, {0,0,1}} - }; - - static const f32 face_uvs[4][2] = { - {0, 1}, {1, 1}, {1, 0}, {0, 0} - }; - - f32 ao_values[4] = {ao0, ao1, ao2, ao3}; - f32 ao_strength = config->ambient_occlusion ? config->ao_strength : 0.0f; - f32 tex_scale = config->texture_scale; - - pxl8_vec3 normal = face_normals[face]; - u16 indices[4]; - - for (i32 i = 0; i < 4; i++) { - pxl8_vertex v = {0}; - v.position.x = pos.x + (f32)face_vertices[face][i][0]; - v.position.y = pos.y + (f32)face_vertices[face][i][1]; - v.position.z = pos.z + (f32)face_vertices[face][i][2]; - v.normal = normal; - v.u = face_uvs[i][0] * tex_scale; - v.v = face_uvs[i][1] * tex_scale; - v.color = texture_id; - v.light = (u8)(255.0f * (1.0f - ao_strength * (1.0f - ao_values[i]))); - - indices[i] = pxl8_mesh_push_vertex(mesh, v); - } - - if (ao0 + ao2 > ao1 + ao3) { - pxl8_mesh_push_quad(mesh, indices[0], indices[1], indices[2], indices[3]); - } else { - pxl8_mesh_push_triangle(mesh, indices[0], indices[1], indices[2]); - pxl8_mesh_push_triangle(mesh, indices[0], indices[2], indices[3]); - } -} - -static void add_cube_faces(pxl8_mesh* mesh, const pxl8_voxel_chunk* chunk, - const pxl8_voxel_chunk** neighbors, const pxl8_block_registry* registry, - const pxl8_voxel_mesh_config* config, - i32 x, i32 y, i32 z, pxl8_block block) { - u8 texture_id = pxl8_block_registry_texture(registry, block); - pxl8_vec3 pos = {(f32)x, (f32)y, (f32)z}; - - static const i32 face_dirs[6][3] = { - {-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1} - }; - - for (i32 face = 0; face < 6; face++) { - i32 nx = x + face_dirs[face][0]; - i32 ny = y + face_dirs[face][1]; - i32 nz = z + face_dirs[face][2]; - - pxl8_block neighbor = get_block_or_neighbor(chunk, neighbors, nx, ny, nz); - if (block_is_opaque(neighbor)) continue; - - f32 ao[4] = {1.0f, 1.0f, 1.0f, 1.0f}; - - if (config->ambient_occlusion) { - switch (face) { - case 0: - ao[0] = compute_ao(chunk, neighbors, x-1, y, z, 0, -1, 0, 0, 0, 1); - ao[1] = compute_ao(chunk, neighbors, x-1, y, z, 0, -1, 0, 0, 0, -1); - ao[2] = compute_ao(chunk, neighbors, x-1, y, z, 0, 1, 0, 0, 0, -1); - ao[3] = compute_ao(chunk, neighbors, x-1, y, z, 0, 1, 0, 0, 0, 1); - break; - case 1: - ao[0] = compute_ao(chunk, neighbors, x+1, y, z, 0, -1, 0, 0, 0, -1); - ao[1] = compute_ao(chunk, neighbors, x+1, y, z, 0, -1, 0, 0, 0, 1); - ao[2] = compute_ao(chunk, neighbors, x+1, y, z, 0, 1, 0, 0, 0, 1); - ao[3] = compute_ao(chunk, neighbors, x+1, y, z, 0, 1, 0, 0, 0, -1); - break; - case 2: - ao[0] = compute_ao(chunk, neighbors, x, y-1, z, -1, 0, 0, 0, 0, -1); - ao[1] = compute_ao(chunk, neighbors, x, y-1, z, 1, 0, 0, 0, 0, -1); - ao[2] = compute_ao(chunk, neighbors, x, y-1, z, 1, 0, 0, 0, 0, 1); - ao[3] = compute_ao(chunk, neighbors, x, y-1, z, -1, 0, 0, 0, 0, 1); - break; - case 3: - ao[0] = compute_ao(chunk, neighbors, x, y+1, z, -1, 0, 0, 0, 0, 1); - ao[1] = compute_ao(chunk, neighbors, x, y+1, z, 1, 0, 0, 0, 0, 1); - ao[2] = compute_ao(chunk, neighbors, x, y+1, z, 1, 0, 0, 0, 0, -1); - ao[3] = compute_ao(chunk, neighbors, x, y+1, z, -1, 0, 0, 0, 0, -1); - break; - case 4: - ao[0] = compute_ao(chunk, neighbors, x, y, z-1, -1, 0, 0, 0, 1, 0); - ao[1] = compute_ao(chunk, neighbors, x, y, z-1, -1, 0, 0, 0, -1, 0); - ao[2] = compute_ao(chunk, neighbors, x, y, z-1, 1, 0, 0, 0, -1, 0); - ao[3] = compute_ao(chunk, neighbors, x, y, z-1, 1, 0, 0, 0, 1, 0); - break; - case 5: - ao[0] = compute_ao(chunk, neighbors, x, y, z+1, 1, 0, 0, 0, -1, 0); - ao[1] = compute_ao(chunk, neighbors, x, y, z+1, 1, 0, 0, 0, 1, 0); - ao[2] = compute_ao(chunk, neighbors, x, y, z+1, -1, 0, 0, 0, 1, 0); - ao[3] = compute_ao(chunk, neighbors, x, y, z+1, -1, 0, 0, 0, -1, 0); - break; - } - } - - add_face_vertices(mesh, config, pos, face, texture_id, ao[0], ao[1], ao[2], ao[3]); - } -} - -static void add_slab_faces(pxl8_mesh* mesh, const pxl8_voxel_chunk* chunk, - const pxl8_voxel_chunk** neighbors, const pxl8_block_registry* registry, - const pxl8_voxel_mesh_config* config, - i32 x, i32 y, i32 z, pxl8_block block, bool top) { - u8 texture_id = pxl8_block_registry_texture(registry, block); - f32 y_offset = top ? 0.5f : 0.0f; - pxl8_vec3 pos = {(f32)x, (f32)y + y_offset, (f32)z}; - - static const i32 horiz_faces[4] = {0, 1, 4, 5}; - static const i32 face_dirs[6][3] = { - {-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1} - }; - - for (i32 i = 0; i < 4; i++) { - i32 face = horiz_faces[i]; - i32 nx = x + face_dirs[face][0]; - i32 nz = z + face_dirs[face][2]; - - pxl8_block neighbor = get_block_or_neighbor(chunk, neighbors, nx, y, nz); - if (block_is_opaque(neighbor)) continue; - - add_face_vertices(mesh, config, pos, face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f); - } - - i32 top_face = 3; - i32 bot_face = 2; - - if (top) { - pxl8_block above = get_block_or_neighbor(chunk, neighbors, x, y + 1, z); - if (!block_is_opaque(above)) { - add_face_vertices(mesh, config, pos, top_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f); - } - add_face_vertices(mesh, config, (pxl8_vec3){(f32)x, (f32)y + 0.5f, (f32)z}, bot_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f); - } else { - add_face_vertices(mesh, config, pos, top_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f); - pxl8_block below = get_block_or_neighbor(chunk, neighbors, x, y - 1, z); - if (!block_is_opaque(below)) { - add_face_vertices(mesh, config, pos, bot_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f); - } - } -} - -static void add_slope_faces(pxl8_mesh* mesh, const pxl8_voxel_chunk* chunk, - const pxl8_voxel_chunk** neighbors, const pxl8_block_registry* registry, - const pxl8_voxel_mesh_config* config, - i32 x, i32 y, i32 z, pxl8_block block, i32 direction) { - u8 texture_id = pxl8_block_registry_texture(registry, block); - pxl8_vec3 pos = {(f32)x, (f32)y, (f32)z}; - - pxl8_block below = get_block_or_neighbor(chunk, neighbors, x, y - 1, z); - if (!block_is_opaque(below)) { - add_face_vertices(mesh, config, pos, 2, texture_id, 1.0f, 1.0f, 1.0f, 1.0f); - } - - static const i32 dir_to_back_face[4] = {5, 4, 1, 0}; - i32 back_face = dir_to_back_face[direction]; - - static const i32 face_dirs[6][3] = { - {-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1} - }; - - i32 bx = x + face_dirs[back_face][0]; - i32 bz = z + face_dirs[back_face][2]; - pxl8_block back_neighbor = get_block_or_neighbor(chunk, neighbors, bx, y, bz); - if (!block_is_opaque(back_neighbor)) { - add_face_vertices(mesh, config, pos, back_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f); - } - - pxl8_vertex verts[4]; - memset(verts, 0, sizeof(verts)); - - static const f32 slope_verts[4][4][3] = { - {{0,0,0}, {1,0,0}, {1,1,1}, {0,1,1}}, - {{0,0,1}, {1,0,1}, {1,1,0}, {0,1,0}}, - {{1,0,0}, {1,0,1}, {0,1,1}, {0,1,0}}, - {{0,0,0}, {0,0,1}, {1,1,1}, {1,1,0}} - }; - - for (i32 i = 0; i < 4; i++) { - verts[i].position.x = pos.x + slope_verts[direction][i][0]; - verts[i].position.y = pos.y + slope_verts[direction][i][1]; - verts[i].position.z = pos.z + slope_verts[direction][i][2]; - verts[i].color = texture_id; - verts[i].light = 255; - } - - static const pxl8_vec3 slope_normals[4] = { - {0, 0.707f, -0.707f}, {0, 0.707f, 0.707f}, - {-0.707f, 0.707f, 0}, {0.707f, 0.707f, 0} - }; - - for (i32 i = 0; i < 4; i++) { - verts[i].normal = slope_normals[direction]; - } - - u16 i0 = pxl8_mesh_push_vertex(mesh, verts[0]); - u16 i1 = pxl8_mesh_push_vertex(mesh, verts[1]); - u16 i2 = pxl8_mesh_push_vertex(mesh, verts[2]); - u16 i3 = pxl8_mesh_push_vertex(mesh, verts[3]); - pxl8_mesh_push_quad(mesh, i0, i1, i2, i3); -} - -pxl8_mesh* pxl8_voxel_build_mesh(const pxl8_voxel_chunk* chunk, - const pxl8_voxel_chunk** neighbors, - const pxl8_block_registry* registry, - const pxl8_voxel_mesh_config* config) { - if (!chunk || !registry) return NULL; - - pxl8_voxel_mesh_config cfg = config ? *config : PXL8_VOXEL_MESH_CONFIG_DEFAULT; - - u32 max_faces = PXL8_VOXEL_CHUNK_VOLUME * 6; - pxl8_mesh* mesh = pxl8_mesh_create(max_faces * 4, max_faces * 6); - if (!mesh) return NULL; - - for (i32 z = 0; z < PXL8_VOXEL_CHUNK_SIZE; z++) { - for (i32 y = 0; y < PXL8_VOXEL_CHUNK_SIZE; y++) { - for (i32 x = 0; x < PXL8_VOXEL_CHUNK_SIZE; x++) { - pxl8_block block = chunk->blocks[voxel_index(x, y, z)]; - if (block == PXL8_BLOCK_AIR) continue; - - pxl8_voxel_geometry geo = pxl8_block_registry_geometry(registry, block); - - switch (geo) { - case PXL8_VOXEL_GEOMETRY_CUBE: - add_cube_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block); - break; - case PXL8_VOXEL_GEOMETRY_SLAB_BOTTOM: - add_slab_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, false); - break; - case PXL8_VOXEL_GEOMETRY_SLAB_TOP: - add_slab_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, true); - break; - case PXL8_VOXEL_GEOMETRY_SLOPE_NORTH: - add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 0); - break; - case PXL8_VOXEL_GEOMETRY_SLOPE_SOUTH: - add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 1); - break; - case PXL8_VOXEL_GEOMETRY_SLOPE_EAST: - add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 2); - break; - case PXL8_VOXEL_GEOMETRY_SLOPE_WEST: - add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 3); - break; - case PXL8_VOXEL_GEOMETRY_STAIRS_NORTH: - case PXL8_VOXEL_GEOMETRY_STAIRS_SOUTH: - case PXL8_VOXEL_GEOMETRY_STAIRS_EAST: - case PXL8_VOXEL_GEOMETRY_STAIRS_WEST: - add_cube_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block); - break; - } - } - } - } - - return mesh; -} diff --git a/src/world/pxl8_voxel.h b/src/world/pxl8_voxel.h deleted file mode 100644 index e4feef7..0000000 --- a/src/world/pxl8_voxel.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include "pxl8_mesh.h" -#include "pxl8_types.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define PXL8_VOXEL_CHUNK_SIZE 32 -#define PXL8_BLOCK_COUNT 256 - -typedef struct pxl8_voxel_chunk pxl8_voxel_chunk; -typedef struct pxl8_block_registry pxl8_block_registry; -typedef u8 pxl8_block; - -#define PXL8_BLOCK_AIR 0 - -typedef enum pxl8_voxel_geometry { - PXL8_VOXEL_GEOMETRY_CUBE, - PXL8_VOXEL_GEOMETRY_SLAB_BOTTOM, - PXL8_VOXEL_GEOMETRY_SLAB_TOP, - PXL8_VOXEL_GEOMETRY_SLOPE_NORTH, - PXL8_VOXEL_GEOMETRY_SLOPE_SOUTH, - PXL8_VOXEL_GEOMETRY_SLOPE_EAST, - PXL8_VOXEL_GEOMETRY_SLOPE_WEST, - PXL8_VOXEL_GEOMETRY_STAIRS_NORTH, - PXL8_VOXEL_GEOMETRY_STAIRS_SOUTH, - PXL8_VOXEL_GEOMETRY_STAIRS_EAST, - PXL8_VOXEL_GEOMETRY_STAIRS_WEST -} pxl8_voxel_geometry; - -typedef struct pxl8_voxel_mesh_config { - bool ambient_occlusion; - f32 ao_strength; - f32 texture_scale; -} pxl8_voxel_mesh_config; - -#define PXL8_VOXEL_MESH_CONFIG_DEFAULT ((pxl8_voxel_mesh_config){ \ - .ambient_occlusion = true, \ - .ao_strength = 0.2f, \ - .texture_scale = 1.0f \ -}) - -pxl8_voxel_chunk* pxl8_voxel_chunk_create(void); -void pxl8_voxel_chunk_clear(pxl8_voxel_chunk* chunk); -void pxl8_voxel_chunk_destroy(pxl8_voxel_chunk* chunk); - -pxl8_block pxl8_voxel_get(const pxl8_voxel_chunk* chunk, i32 x, i32 y, i32 z); -void pxl8_voxel_set(pxl8_voxel_chunk* chunk, i32 x, i32 y, i32 z, pxl8_block block); -void pxl8_voxel_fill(pxl8_voxel_chunk* chunk, pxl8_block block); - -pxl8_block_registry* pxl8_block_registry_create(void); -void pxl8_block_registry_destroy(pxl8_block_registry* registry); -void pxl8_block_registry_register(pxl8_block_registry* registry, pxl8_block id, const char* name, u8 texture_id, pxl8_voxel_geometry geo); -const char* pxl8_block_registry_name(const pxl8_block_registry* registry, pxl8_block id); -pxl8_voxel_geometry pxl8_block_registry_geometry(const pxl8_block_registry* registry, pxl8_block id); -u8 pxl8_block_registry_texture(const pxl8_block_registry* registry, pxl8_block id); - -pxl8_mesh* pxl8_voxel_build_mesh(const pxl8_voxel_chunk* chunk, - const pxl8_voxel_chunk** neighbors, - const pxl8_block_registry* registry, - const pxl8_voxel_mesh_config* config); - -#ifdef __cplusplus -} -#endif diff --git a/src/world/pxl8_world.c b/src/world/pxl8_world.c index b14d9c1..353b8b0 100644 --- a/src/world/pxl8_world.c +++ b/src/world/pxl8_world.c @@ -1,28 +1,66 @@ #include "pxl8_world.h" -#include +#include +#include "pxl8_hal.h" + +#ifdef PXL8_ASYNC_THREADS +#include +#include "pxl8_queue.h" +#endif + +#include "pxl8_bsp_render.h" +#include "pxl8_io.h" +#include "pxl8_gfx3d.h" #include "pxl8_log.h" #include "pxl8_mem.h" +#include "pxl8_sim.h" +#include "pxl8_vxl.h" +#include "pxl8_vxl_render.h" #define PXL8_WORLD_ENTITY_CAPACITY 256 +#define VOXEL_SCALE 16.0f +#define VOXEL_CHUNK_SIZE 32.0f +#define WORLD_CHUNK_SIZE (VOXEL_CHUNK_SIZE * VOXEL_SCALE) struct pxl8_world { - pxl8_chunk* active_chunk; - pxl8_block_registry* block_registry; - pxl8_chunk_cache* chunk_cache; + pxl8_world_chunk* active_chunk; + pxl8_vxl_block_registry* block_registry; + pxl8_world_chunk_cache* chunk_cache; pxl8_entity_pool* entities; + pxl8_bsp_render_state* bsp_render_state; + pxl8_vxl_render_state* vxl_render_state; + pxl8_sdf sdf; + i32 render_distance; + i32 sim_distance; + + pxl8_sim_entity local_player; + u64 client_tick; + +#ifdef PXL8_ASYNC_THREADS + pxl8_sim_entity render_state[2]; + atomic_uint active_buffer; + pxl8_thread* sim_thread; + atomic_bool sim_running; + atomic_bool sim_paused; + pxl8_net* net; + pxl8_queue input_queue; + f32 sim_accumulator; +#endif }; pxl8_world* pxl8_world_create(void) { pxl8_world* world = pxl8_calloc(1, sizeof(pxl8_world)); if (!world) return NULL; - world->block_registry = pxl8_block_registry_create(); - world->chunk_cache = pxl8_chunk_cache_create(); + world->block_registry = pxl8_vxl_block_registry_create(); + world->chunk_cache = pxl8_world_chunk_cache_create(); world->entities = pxl8_entity_pool_create(PXL8_WORLD_ENTITY_CAPACITY); + world->vxl_render_state = pxl8_vxl_render_state_create(); + world->render_distance = 3; + world->sim_distance = 4; - if (!world->block_registry || !world->chunk_cache || !world->entities) { + if (!world->block_registry || !world->chunk_cache || !world->entities || !world->vxl_render_state) { pxl8_world_destroy(world); return NULL; } @@ -33,28 +71,30 @@ pxl8_world* pxl8_world_create(void) { void pxl8_world_destroy(pxl8_world* world) { if (!world) return; - pxl8_block_registry_destroy(world->block_registry); - pxl8_chunk_cache_destroy(world->chunk_cache); + pxl8_vxl_block_registry_destroy(world->block_registry); + pxl8_world_chunk_cache_destroy(world->chunk_cache); pxl8_entity_pool_destroy(world->entities); + pxl8_bsp_render_state_destroy(world->bsp_render_state); + pxl8_vxl_render_state_destroy(world->vxl_render_state); pxl8_free(world); } -pxl8_chunk_cache* pxl8_world_chunk_cache(pxl8_world* world) { +pxl8_world_chunk_cache* pxl8_world_get_chunk_cache(pxl8_world* world) { if (!world) return NULL; return world->chunk_cache; } -pxl8_chunk* pxl8_world_active_chunk(pxl8_world* world) { +pxl8_world_chunk* pxl8_world_active_chunk(pxl8_world* world) { if (!world) return NULL; return world->active_chunk; } -void pxl8_world_set_active_chunk(pxl8_world* world, pxl8_chunk* chunk) { +void pxl8_world_set_active_chunk(pxl8_world* world, pxl8_world_chunk* chunk) { if (!world) return; world->active_chunk = chunk; } -pxl8_block_registry* pxl8_world_block_registry(pxl8_world* world) { +pxl8_vxl_block_registry* pxl8_world_block_registry(pxl8_world* world) { if (!world) return NULL; return world->block_registry; } @@ -69,40 +109,362 @@ pxl8_entity pxl8_world_spawn(pxl8_world* world) { return pxl8_entity_spawn(world->entities); } -bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, f32 radius) { - (void)radius; +static bool voxel_point_solid(pxl8_world_chunk_cache* cache, f32 x, f32 y, f32 z) { + i32 cx = (i32)floorf(x / WORLD_CHUNK_SIZE); + i32 cy = (i32)floorf(y / WORLD_CHUNK_SIZE); + i32 cz = (i32)floorf(z / WORLD_CHUNK_SIZE); - if (!world || !world->active_chunk) return false; + f32 local_x = (x - cx * WORLD_CHUNK_SIZE) / VOXEL_SCALE; + f32 local_y = (y - cy * WORLD_CHUNK_SIZE) / VOXEL_SCALE; + f32 local_z = (z - cz * WORLD_CHUNK_SIZE) / VOXEL_SCALE; - if (world->active_chunk->type == PXL8_CHUNK_BSP && world->active_chunk->bsp) { - return pxl8_bsp_point_solid(world->active_chunk->bsp, pos); + i32 lx = (i32)floorf(local_x); + i32 ly = (i32)floorf(local_y); + i32 lz = (i32)floorf(local_z); + + lx = lx < 0 ? 0 : (lx >= 32 ? 31 : lx); + ly = ly < 0 ? 0 : (ly >= 32 ? 31 : ly); + lz = lz < 0 ? 0 : (lz >= 32 ? 31 : lz); + + pxl8_world_chunk* chunk = pxl8_world_chunk_cache_get_vxl(cache, cx, cy, cz); + if (!chunk || !chunk->voxels) { + return ly < 8; + } + + return pxl8_vxl_block_get(chunk->voxels, lx, ly, lz) != PXL8_VXL_BLOCK_AIR; +} + +static pxl8_sim_world make_sim_world(const pxl8_world* world, pxl8_vec3 pos) { + pxl8_sim_world sim = {0}; + + if (world->active_chunk && world->active_chunk->type == PXL8_WORLD_CHUNK_BSP) { + sim.bsp = world->active_chunk->bsp; + return sim; + } + + if (world->chunk_cache) { + i32 cx = (i32)floorf(pos.x / WORLD_CHUNK_SIZE); + i32 cy = (i32)floorf(pos.y / WORLD_CHUNK_SIZE); + i32 cz = (i32)floorf(pos.z / WORLD_CHUNK_SIZE); + + pxl8_world_chunk* chunk = pxl8_world_chunk_cache_get_vxl(world->chunk_cache, cx, cy, cz); + if (chunk && chunk->voxels) { + sim.vxl = chunk->voxels; + sim.vxl_cx = cx; + sim.vxl_cy = cy; + sim.vxl_cz = cz; + } + } + + return sim; +} + +static void entity_to_userdata(const pxl8_sim_entity* ent, u8* userdata) { + u8* p = userdata; + memcpy(p, &ent->pos.x, 4); p += 4; + memcpy(p, &ent->pos.y, 4); p += 4; + memcpy(p, &ent->pos.z, 4); p += 4; + memcpy(p, &ent->yaw, 4); p += 4; + memcpy(p, &ent->pitch, 4); p += 4; + memcpy(p, &ent->vel.x, 4); p += 4; + memcpy(p, &ent->vel.y, 4); p += 4; + memcpy(p, &ent->vel.z, 4); p += 4; + memcpy(p, &ent->flags, 4); p += 4; + memcpy(p, &ent->kind, 2); +} + +static void userdata_to_entity(const u8* userdata, pxl8_sim_entity* ent) { + const u8* p = userdata; + memcpy(&ent->pos.x, p, 4); p += 4; + memcpy(&ent->pos.y, p, 4); p += 4; + memcpy(&ent->pos.z, p, 4); p += 4; + memcpy(&ent->yaw, p, 4); p += 4; + memcpy(&ent->pitch, p, 4); p += 4; + memcpy(&ent->vel.x, p, 4); p += 4; + memcpy(&ent->vel.y, p, 4); p += 4; + memcpy(&ent->vel.z, p, 4); p += 4; + memcpy(&ent->flags, p, 4); p += 4; + memcpy(&ent->kind, p, 2); +} + +static void update_sdf(pxl8_world* world, pxl8_vec3 center, f32 cell_size) { + if (!world) return; + + world->sdf.cell = cell_size; + world->sdf.origin.x = center.x - (PXL8_SDF_X / 2) * cell_size; + world->sdf.origin.y = center.y - (PXL8_SDF_Y / 2) * cell_size; + world->sdf.origin.z = center.z - (PXL8_SDF_Z / 2) * cell_size; + + i16 seed_x[PXL8_SDF_SIZE]; + i16 seed_y[PXL8_SDF_SIZE]; + i16 seed_z[PXL8_SDF_SIZE]; + + for (i32 iy = 0; iy < PXL8_SDF_Y; iy++) { + f32 wy = world->sdf.origin.y + (iy + 0.5f) * cell_size; + for (i32 iz = 0; iz < PXL8_SDF_Z; iz++) { + f32 wz = world->sdf.origin.z + (iz + 0.5f) * cell_size; + for (i32 ix = 0; ix < PXL8_SDF_X; ix++) { + f32 wx = world->sdf.origin.x + (ix + 0.5f) * cell_size; + i32 idx = iy * PXL8_SDF_Z * PXL8_SDF_X + iz * PXL8_SDF_X + ix; + + if (pxl8_world_point_solid(world, wx, wy, wz)) { + seed_x[idx] = (i16)ix; + seed_y[idx] = (i16)iy; + seed_z[idx] = (i16)iz; + } else { + seed_x[idx] = -1; + seed_y[idx] = -1; + seed_z[idx] = -1; + } + } + } + } + + for (i32 step = 16; step >= 1; step /= 2) { + for (i32 iy = 0; iy < PXL8_SDF_Y; iy++) { + for (i32 iz = 0; iz < PXL8_SDF_Z; iz++) { + for (i32 ix = 0; ix < PXL8_SDF_X; ix++) { + i32 idx = iy * PXL8_SDF_Z * PXL8_SDF_X + iz * PXL8_SDF_X + ix; + i32 best_dist_sq = (seed_x[idx] >= 0) + ? (ix - seed_x[idx]) * (ix - seed_x[idx]) + + (iy - seed_y[idx]) * (iy - seed_y[idx]) + + (iz - seed_z[idx]) * (iz - seed_z[idx]) + : 0x7FFFFFFF; + + for (i32 dy = -step; dy <= step; dy += step) { + i32 ny = iy + dy; + if (ny < 0 || ny >= PXL8_SDF_Y) continue; + for (i32 dz = -step; dz <= step; dz += step) { + i32 nz = iz + dz; + if (nz < 0 || nz >= PXL8_SDF_Z) continue; + for (i32 dx = -step; dx <= step; dx += step) { + if (dx == 0 && dy == 0 && dz == 0) continue; + i32 nx = ix + dx; + if (nx < 0 || nx >= PXL8_SDF_X) continue; + + i32 nidx = ny * PXL8_SDF_Z * PXL8_SDF_X + nz * PXL8_SDF_X + nx; + if (seed_x[nidx] < 0) continue; + + i32 dist_sq = (ix - seed_x[nidx]) * (ix - seed_x[nidx]) + + (iy - seed_y[nidx]) * (iy - seed_y[nidx]) + + (iz - seed_z[nidx]) * (iz - seed_z[nidx]); + + if (dist_sq < best_dist_sq) { + best_dist_sq = dist_sq; + seed_x[idx] = seed_x[nidx]; + seed_y[idx] = seed_y[nidx]; + seed_z[idx] = seed_z[nidx]; + } + } + } + } + } + } + } + } + + for (i32 i = 0; i < PXL8_SDF_SIZE; i++) { + if (seed_x[i] < 0) { + world->sdf.data[i] = 127; + } else { + i32 idx_x = i % PXL8_SDF_X; + i32 idx_z = (i / PXL8_SDF_X) % PXL8_SDF_Z; + i32 idx_y = i / (PXL8_SDF_X * PXL8_SDF_Z); + i32 dx = idx_x - seed_x[i]; + i32 dy = idx_y - seed_y[i]; + i32 dz = idx_z - seed_z[i]; + f32 dist = sqrtf((f32)(dx * dx + dy * dy + dz * dz)); + i32 d = (i32)(dist * cell_size); + if (d > 127) d = 127; + world->sdf.data[i] = (i8)d; + } + } +} + +bool pxl8_world_point_solid(const pxl8_world* world, f32 x, f32 y, f32 z) { + if (!world) return false; + + if (world->active_chunk) { + if (world->active_chunk->type == PXL8_WORLD_CHUNK_BSP && world->active_chunk->bsp) { + return pxl8_bsp_point_solid(world->active_chunk->bsp, (pxl8_vec3){x, y, z}); + } + } else if (world->chunk_cache) { + return voxel_point_solid(world->chunk_cache, x, y, z); } return false; } -pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius) { - if (!world || !world->active_chunk) return to; +pxl8_ray pxl8_world_ray(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to) { + pxl8_ray result = {0}; - if (world->active_chunk->type == PXL8_CHUNK_BSP && world->active_chunk->bsp) { - return pxl8_bsp_trace(world->active_chunk->bsp, from, to, radius); + if (!world) return result; + + pxl8_vec3 dir = pxl8_vec3_sub(to, from); + f32 length = pxl8_vec3_length(dir); + if (length < 0.001f) return result; + + f32 step_size = 1.0f; + f32 traveled = 0.0f; + + while (traveled < length) { + f32 t = traveled / length; + pxl8_vec3 pos = { + from.x + dir.x * t, + from.y + dir.y * t, + from.z + dir.z * t + }; + + if (pxl8_world_point_solid(world, pos.x, pos.y, pos.z)) { + result.hit = true; + result.fraction = t; + result.point = pos; + + f32 eps = 0.1f; + bool sx_neg = pxl8_world_point_solid(world, pos.x - eps, pos.y, pos.z); + bool sx_pos = pxl8_world_point_solid(world, pos.x + eps, pos.y, pos.z); + bool sy_neg = pxl8_world_point_solid(world, pos.x, pos.y - eps, pos.z); + bool sy_pos = pxl8_world_point_solid(world, pos.x, pos.y + eps, pos.z); + bool sz_neg = pxl8_world_point_solid(world, pos.x, pos.y, pos.z - eps); + bool sz_pos = pxl8_world_point_solid(world, pos.x, pos.y, pos.z + eps); + + result.normal = (pxl8_vec3){ + (sx_neg && !sx_pos) ? 1.0f : (!sx_neg && sx_pos) ? -1.0f : 0.0f, + (sy_neg && !sy_pos) ? 1.0f : (!sy_neg && sy_pos) ? -1.0f : 0.0f, + (sz_neg && !sz_pos) ? 1.0f : (!sz_neg && sz_pos) ? -1.0f : 0.0f + }; + + f32 nl = pxl8_vec3_length(result.normal); + if (nl > 0.001f) { + result.normal = pxl8_vec3_scale(result.normal, 1.0f / nl); + } else { + result.normal = (pxl8_vec3){0, 1, 0}; + } + + return result; + } + + traveled += step_size; } - return to; + return result; } -void pxl8_world_update(pxl8_world* world, f32 dt) { - (void)dt; +pxl8_ray pxl8_world_sweep(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius) { + pxl8_ray result = {0}; + + if (!world) return result; + + f32 diag = radius * 0.707f; + + bool dest_blocked = pxl8_world_point_solid(world, to.x, to.y, to.z) || + pxl8_world_point_solid(world, to.x + radius, to.y, to.z) || + pxl8_world_point_solid(world, to.x - radius, to.y, to.z) || + pxl8_world_point_solid(world, to.x, to.y, to.z + radius) || + pxl8_world_point_solid(world, to.x, to.y, to.z - radius) || + pxl8_world_point_solid(world, to.x + diag, to.y, to.z + diag) || + pxl8_world_point_solid(world, to.x + diag, to.y, to.z - diag) || + pxl8_world_point_solid(world, to.x - diag, to.y, to.z + diag) || + pxl8_world_point_solid(world, to.x - diag, to.y, to.z - diag); + + if (dest_blocked) { + result.hit = true; + result.fraction = 0; + result.point = from; + pxl8_vec3 dir = pxl8_vec3_sub(to, from); + f32 length = pxl8_vec3_length(dir); + if (length > 0.001f) { + result.normal = pxl8_vec3_scale(dir, -1.0f / length); + } else { + result.normal = (pxl8_vec3){0, 1, 0}; + } + } + + return result; +} + +void pxl8_world_update(pxl8_world* world, const pxl8_input_state* input, f32 dt) { if (!world) return; - pxl8_chunk_cache_tick(world->chunk_cache); + pxl8_world_chunk_cache_tick(world->chunk_cache); + + if (input && (world->local_player.flags & PXL8_SIM_FLAG_ALIVE)) { + pxl8_input_msg msg = {0}; + msg.move_x = (pxl8_key_down(input, "d") ? 1.0f : 0.0f) - (pxl8_key_down(input, "a") ? 1.0f : 0.0f); + msg.move_y = (pxl8_key_down(input, "w") ? 1.0f : 0.0f) - (pxl8_key_down(input, "s") ? 1.0f : 0.0f); + msg.look_dx = (f32)pxl8_mouse_dx(input); + msg.look_dy = (f32)pxl8_mouse_dy(input); + msg.buttons = pxl8_key_down(input, "space") ? 1 : 0; + + pxl8_sim_world sim = make_sim_world(world, world->local_player.pos); + pxl8_sim_move_player(&world->local_player, &msg, &sim, dt); + } } void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) { - if (!world || !gfx || !world->active_chunk) return; + if (!world || !gfx) return; - if (world->active_chunk->type == PXL8_CHUNK_BSP && world->active_chunk->bsp) { - pxl8_bsp_render(gfx, world->active_chunk->bsp, camera_pos); + update_sdf(world, camera_pos, PXL8_SDF_CELL); + pxl8_3d_set_sdf(gfx, &world->sdf); + + if (world->active_chunk) { + if (world->active_chunk->type == PXL8_WORLD_CHUNK_BSP && world->active_chunk->bsp) { + pxl8_3d_set_bsp(gfx, world->active_chunk->bsp); + pxl8_bsp_render(gfx, world->active_chunk->bsp, + world->bsp_render_state, camera_pos); + } + } else { + pxl8_3d_set_bsp(gfx, NULL); + i32 cx = (i32)floorf(camera_pos.x / WORLD_CHUNK_SIZE); + i32 cy = (i32)floorf(camera_pos.y / WORLD_CHUNK_SIZE); + i32 cz = (i32)floorf(camera_pos.z / WORLD_CHUNK_SIZE); + + const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx); + + bool wireframe = world->vxl_render_state && world->vxl_render_state->wireframe; + pxl8_gfx_material mat = { + .texture_id = 0, + .dynamic_lighting = true, + .vertex_color_passthrough = true, + .alpha = 255, + .wireframe = wireframe, + }; + + i32 r = world->render_distance; + for (i32 dy = -r; dy <= r; dy++) { + for (i32 dz = -r; dz <= r; dz++) { + for (i32 dx = -r; dx <= r; dx++) { + i32 chunk_cx = cx + dx; + i32 chunk_cy = cy + dy; + i32 chunk_cz = cz + dz; + f32 chunk_x = chunk_cx * WORLD_CHUNK_SIZE; + f32 chunk_y = chunk_cy * WORLD_CHUNK_SIZE; + f32 chunk_z = chunk_cz * WORLD_CHUNK_SIZE; + + if (frustum) { + pxl8_vec3 aabb_min = {chunk_x, chunk_y, chunk_z}; + pxl8_vec3 aabb_max = {chunk_x + WORLD_CHUNK_SIZE, chunk_y + WORLD_CHUNK_SIZE, chunk_z + WORLD_CHUNK_SIZE}; + if (!pxl8_frustum_test_aabb(frustum, aabb_min, aabb_max)) continue; + } + + pxl8_vxl_mesh_config config = PXL8_VXL_MESH_CONFIG_DEFAULT; + config.chunk_x = chunk_cx; + config.chunk_y = chunk_cy; + config.chunk_z = chunk_cz; + + pxl8_mesh* mesh = pxl8_world_chunk_cache_get_mesh( + world->chunk_cache, + chunk_cx, chunk_cy, chunk_cz, + world->block_registry, &config); + if (mesh && mesh->index_count > 0) { + pxl8_mat4 scale = pxl8_mat4_scale(VOXEL_SCALE, VOXEL_SCALE, VOXEL_SCALE); + pxl8_mat4 translate = pxl8_mat4_translate(chunk_x, chunk_y, chunk_z); + pxl8_mat4 model = pxl8_mat4_multiply(translate, scale); + pxl8_3d_draw_mesh(gfx, mesh, &model, &mat); + } + } + } + } } } @@ -113,7 +475,7 @@ void pxl8_world_sync(pxl8_world* world, pxl8_net* net) { u32 chunk_id = pxl8_net_chunk_id(net); if (chunk_type == PXL8_CHUNK_TYPE_BSP && chunk_id != 0) { - pxl8_chunk* chunk = pxl8_chunk_cache_get_bsp(world->chunk_cache, chunk_id); + 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; @@ -121,5 +483,278 @@ void pxl8_world_sync(pxl8_world* world, pxl8_net* net) { chunk_id, chunk->bsp->num_vertices, chunk->bsp->num_faces); } } + } else if (chunk_id == 0 && world->active_chunk != NULL) { + world->active_chunk = NULL; } } + +static void ensure_bsp_render_state(pxl8_world* world) { + if (!world || world->bsp_render_state) return; + if (!world->active_chunk || world->active_chunk->type != PXL8_WORLD_CHUNK_BSP) return; + if (!world->active_chunk->bsp) return; + + world->bsp_render_state = pxl8_bsp_render_state_create(world->active_chunk->bsp->num_faces); +} + +void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_gfx_material* material) { + if (!world || !material) return; + + ensure_bsp_render_state(world); + if (!world->bsp_render_state) return; + + pxl8_bsp_set_material(world->bsp_render_state, material_id, material); +} + +void pxl8_world_set_wireframe(pxl8_world* world, bool enabled) { + if (!world) return; + + ensure_bsp_render_state(world); + if (world->bsp_render_state) { + pxl8_bsp_set_wireframe(world->bsp_render_state, enabled); + } + if (world->vxl_render_state) { + pxl8_vxl_set_wireframe(world->vxl_render_state, enabled); + } +} + +i32 pxl8_world_get_render_distance(const pxl8_world* world) { + if (!world) return 3; + return world->render_distance; +} + +void pxl8_world_set_render_distance(pxl8_world* world, i32 distance) { + if (!world) return; + if (distance < 1) distance = 1; + if (distance > 8) distance = 8; + world->render_distance = distance; +} + +i32 pxl8_world_get_sim_distance(const pxl8_world* world) { + if (!world) return 4; + return world->sim_distance; +} + +void pxl8_world_set_sim_distance(pxl8_world* world, i32 distance) { + if (!world) return; + if (distance < 1) distance = 1; + if (distance > 8) distance = 8; + world->sim_distance = distance; +} + +void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z) { + if (!world) return; + world->local_player.pos = (pxl8_vec3){x, y, z}; + world->local_player.vel = (pxl8_vec3){0, 0, 0}; + world->local_player.yaw = 0; + world->local_player.pitch = 0; + world->local_player.flags = PXL8_SIM_FLAG_ALIVE | PXL8_SIM_FLAG_PLAYER | PXL8_SIM_FLAG_GROUNDED; + world->local_player.kind = 0; + world->client_tick = 0; + +#ifdef PXL8_ASYNC_THREADS + world->render_state[0] = world->local_player; + world->render_state[1] = world->local_player; +#endif +} + +pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world) { + if (!world) return NULL; + if (!(world->local_player.flags & PXL8_SIM_FLAG_ALIVE)) return NULL; + return &world->local_player; +} + +void pxl8_world_predict(pxl8_world* world, pxl8_net* net, const pxl8_input_msg* input, f32 dt) { + if (!world || !net || !input) return; + if (!(world->local_player.flags & PXL8_SIM_FLAG_ALIVE)) return; + + pxl8_sim_world sim = make_sim_world(world, world->local_player.pos); + pxl8_sim_move_player(&world->local_player, input, &sim, dt); + + world->client_tick++; + + entity_to_userdata(&world->local_player, pxl8_net_predicted_state(net)); + pxl8_net_predicted_tick_set(net, world->client_tick); +} + +void pxl8_world_reconcile(pxl8_world* world, pxl8_net* net, f32 dt) { + if (!world || !net) return; + if (!(world->local_player.flags & PXL8_SIM_FLAG_ALIVE)) return; + if (!pxl8_net_needs_correction(net)) return; + + u64 player_id = pxl8_net_player_id(net); + const u8* server_state = pxl8_net_entity_userdata(net, player_id); + if (!server_state) return; + + userdata_to_entity(server_state, &world->local_player); + + const pxl8_snapshot_header* snap = pxl8_net_snapshot(net); + u64 server_tick = snap ? snap->tick : 0; + + for (u64 tick = server_tick + 1; tick <= world->client_tick; tick++) { + const pxl8_input_msg* input = pxl8_net_input_at(net, tick); + if (!input) continue; + + pxl8_sim_world sim = make_sim_world(world, world->local_player.pos); + pxl8_sim_move_player(&world->local_player, input, &sim, dt); + } + + entity_to_userdata(&world->local_player, pxl8_net_predicted_state(net)); +} + +#ifdef PXL8_ASYNC_THREADS + +#define SIM_TIMESTEP (1.0f / 60.0f) + +static void pxl8_world_sim_tick(pxl8_world* world, f32 dt) { + if (!(world->local_player.flags & PXL8_SIM_FLAG_ALIVE)) return; + + pxl8_input_msg merged = {0}; + pxl8_input_msg* input = NULL; + bool has_input = false; + + while ((input = pxl8_queue_pop(&world->input_queue))) { + merged.look_dx += input->look_dx; + merged.look_dy += input->look_dy; + merged.move_x = input->move_x; + merged.move_y = input->move_y; + merged.buttons |= input->buttons; + has_input = true; + pxl8_free(input); + } + + const f32 MAX_LOOK_DELTA = 100.0f; + if (merged.look_dx > MAX_LOOK_DELTA) merged.look_dx = MAX_LOOK_DELTA; + if (merged.look_dx < -MAX_LOOK_DELTA) merged.look_dx = -MAX_LOOK_DELTA; + if (merged.look_dy > MAX_LOOK_DELTA) merged.look_dy = MAX_LOOK_DELTA; + if (merged.look_dy < -MAX_LOOK_DELTA) merged.look_dy = -MAX_LOOK_DELTA; + + pxl8_sim_world sim = make_sim_world(world, world->local_player.pos); + pxl8_sim_move_player(&world->local_player, &merged, &sim, dt); + + if (world->net) { + entity_to_userdata(&world->local_player, pxl8_net_predicted_state(world->net)); + pxl8_net_predicted_tick_set(world->net, world->client_tick); + } + + world->client_tick++; + (void)has_input; +} + +static void pxl8_world_swap_buffers(pxl8_world* world) { + u32 back = atomic_load(&world->active_buffer) ^ 1; + world->render_state[back] = world->local_player; + atomic_store(&world->active_buffer, back); +} + +static int pxl8_world_sim_thread(void* data) { + pxl8_world* world = (pxl8_world*)data; + u64 last_time = pxl8_get_ticks_ns(); + + while (atomic_load(&world->sim_running)) { + if (atomic_load(&world->sim_paused)) { + last_time = pxl8_get_ticks_ns(); + pxl8_sleep_ms(1); + continue; + } + + u64 now = pxl8_get_ticks_ns(); + f32 dt = (f32)(now - last_time) / 1e9f; + last_time = now; + + if (dt > 0.1f) dt = 0.1f; + if (dt < 0.0001f) dt = 0.0001f; + + world->sim_accumulator += dt; + + while (world->sim_accumulator >= SIM_TIMESTEP) { + pxl8_world_chunk_cache_tick(world->chunk_cache); + + if (world->net) { + pxl8_packet* pkt; + while ((pkt = pxl8_net_pop_packet(world->net))) { + pxl8_net_process_packet(world->net, pkt); + pxl8_net_packet_free(pkt); + } + + pxl8_world_sync(world, world->net); + pxl8_world_reconcile(world, world->net, SIM_TIMESTEP); + } + + pxl8_world_sim_tick(world, SIM_TIMESTEP); + world->sim_accumulator -= SIM_TIMESTEP; + } + + pxl8_world_swap_buffers(world); + pxl8_sleep_ms(1); + } + + return 0; +} + +void pxl8_world_start_sim_thread(pxl8_world* world, pxl8_net* net) { + if (!world || world->sim_thread) return; + + world->net = net; + pxl8_queue_init(&world->input_queue); + atomic_store(&world->active_buffer, 0); + atomic_store(&world->sim_running, true); + world->sim_accumulator = 0.0f; + + world->render_state[0] = world->local_player; + world->render_state[1] = world->local_player; + + world->sim_thread = pxl8_thread_create(pxl8_world_sim_thread, "pxl8_sim", world); +} + +void pxl8_world_stop_sim_thread(pxl8_world* world) { + if (!world || !world->sim_thread) return; + + atomic_store(&world->sim_running, false); + pxl8_thread_wait(world->sim_thread, NULL); + world->sim_thread = NULL; + + pxl8_input_msg* input; + while ((input = pxl8_queue_pop(&world->input_queue))) { + pxl8_free(input); + } +} + +void pxl8_world_pause_sim(pxl8_world* world, bool paused) { + if (!world) return; + + if (paused) { + atomic_store(&world->sim_paused, true); + } else { + pxl8_input_msg* input; + while ((input = pxl8_queue_pop(&world->input_queue))) { + pxl8_free(input); + } + world->sim_accumulator = 0.0f; + atomic_store(&world->sim_paused, false); + } +} + +void pxl8_world_push_input(pxl8_world* world, const pxl8_input_msg* input) { + if (!world || !input) return; + + pxl8_input_msg* copy = pxl8_malloc(sizeof(pxl8_input_msg)); + if (copy) { + *copy = *input; + if (!pxl8_queue_push(&world->input_queue, copy)) { + pxl8_free(copy); + } + } +} + +const pxl8_sim_entity* pxl8_world_get_render_state(const pxl8_world* world) { + if (!world) return NULL; + u32 front = atomic_load(&((pxl8_world*)world)->active_buffer); + return &world->render_state[front]; +} + +f32 pxl8_world_get_interp_alpha(const pxl8_world* world) { + if (!world) return 1.0f; + return world->sim_accumulator / SIM_TIMESTEP; +} + +#endif diff --git a/src/world/pxl8_world.h b/src/world/pxl8_world.h index 1346cd2..a9024d9 100644 --- a/src/world/pxl8_world.h +++ b/src/world/pxl8_world.h @@ -1,12 +1,13 @@ #pragma once -#include "pxl8_chunk.h" -#include "pxl8_chunk_cache.h" #include "pxl8_entity.h" #include "pxl8_gfx.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" #ifdef __cplusplus extern "C" { @@ -17,21 +18,44 @@ typedef struct pxl8_world pxl8_world; pxl8_world* pxl8_world_create(void); void pxl8_world_destroy(pxl8_world* world); -pxl8_chunk_cache* pxl8_world_chunk_cache(pxl8_world* world); -pxl8_chunk* pxl8_world_active_chunk(pxl8_world* world); -void pxl8_world_set_active_chunk(pxl8_world* world, pxl8_chunk* chunk); +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_block_registry* pxl8_world_block_registry(pxl8_world* world); +pxl8_vxl_block_registry* pxl8_world_block_registry(pxl8_world* world); pxl8_entity_pool* pxl8_world_entities(pxl8_world* world); pxl8_entity pxl8_world_spawn(pxl8_world* world); -bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, f32 radius); -pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius); +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); +pxl8_ray pxl8_world_sweep(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius); -void pxl8_world_update(pxl8_world* world, f32 dt); +void pxl8_world_update(pxl8_world* world, const pxl8_input_state* input, f32 dt); void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos); void pxl8_world_sync(pxl8_world* world, pxl8_net* net); +void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_gfx_material* material); +void pxl8_world_set_wireframe(pxl8_world* world, bool enabled); + +i32 pxl8_world_get_render_distance(const pxl8_world* world); +void pxl8_world_set_render_distance(pxl8_world* world, i32 distance); +i32 pxl8_world_get_sim_distance(const pxl8_world* world); +void pxl8_world_set_sim_distance(pxl8_world* world, i32 distance); + +void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z); +pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world); +void pxl8_world_predict(pxl8_world* world, pxl8_net* net, const pxl8_input_msg* input, f32 dt); +void pxl8_world_reconcile(pxl8_world* world, pxl8_net* net, f32 dt); + +#ifdef PXL8_ASYNC_THREADS +void pxl8_world_start_sim_thread(pxl8_world* world, pxl8_net* net); +void pxl8_world_stop_sim_thread(pxl8_world* world); +void pxl8_world_pause_sim(pxl8_world* world, bool paused); +void pxl8_world_push_input(pxl8_world* world, const pxl8_input_msg* input); +const pxl8_sim_entity* pxl8_world_get_render_state(const pxl8_world* world); +f32 pxl8_world_get_interp_alpha(const pxl8_world* world); +#endif + #ifdef __cplusplus } #endif diff --git a/src/world/pxl8_world_chunk.c b/src/world/pxl8_world_chunk.c new file mode 100644 index 0000000..9ffe42f --- /dev/null +++ b/src/world/pxl8_world_chunk.c @@ -0,0 +1,46 @@ +#include "pxl8_world_chunk.h" + +#include "pxl8_bsp.h" +#include "pxl8_mem.h" +#include "pxl8_vxl.h" + +pxl8_world_chunk* pxl8_world_chunk_create_vxl(i32 cx, i32 cy, i32 cz) { + pxl8_world_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_world_chunk)); + if (!chunk) return NULL; + + chunk->type = PXL8_WORLD_CHUNK_VXL; + chunk->cx = cx; + chunk->cy = cy; + chunk->cz = cz; + chunk->voxels = pxl8_vxl_chunk_create(); + + if (!chunk->voxels) { + pxl8_free(chunk); + return NULL; + } + + return chunk; +} + +pxl8_world_chunk* pxl8_world_chunk_create_bsp(u32 id) { + pxl8_world_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_world_chunk)); + if (!chunk) return NULL; + + chunk->type = PXL8_WORLD_CHUNK_BSP; + chunk->id = id; + chunk->bsp = NULL; + + return chunk; +} + +void pxl8_world_chunk_destroy(pxl8_world_chunk* chunk) { + if (!chunk) return; + + if (chunk->type == PXL8_WORLD_CHUNK_VXL && chunk->voxels) { + pxl8_vxl_chunk_destroy(chunk->voxels); + } else if (chunk->type == PXL8_WORLD_CHUNK_BSP && chunk->bsp) { + pxl8_bsp_destroy(chunk->bsp); + } + + pxl8_free(chunk); +} diff --git a/src/world/pxl8_world_chunk.h b/src/world/pxl8_world_chunk.h new file mode 100644 index 0000000..15f2551 --- /dev/null +++ b/src/world/pxl8_world_chunk.h @@ -0,0 +1,33 @@ +#pragma once + +#include "pxl8_bsp.h" +#include "pxl8_types.h" +#include "pxl8_vxl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum pxl8_world_chunk_type { + PXL8_WORLD_CHUNK_VXL, + PXL8_WORLD_CHUNK_BSP +} pxl8_world_chunk_type; + +typedef struct pxl8_world_chunk { + pxl8_world_chunk_type type; + u32 id; + u32 version; + i32 cx, cy, cz; + union { + pxl8_bsp* bsp; + pxl8_vxl_chunk* voxels; + }; +} pxl8_world_chunk; + +pxl8_world_chunk* pxl8_world_chunk_create_vxl(i32 cx, i32 cy, i32 cz); +pxl8_world_chunk* pxl8_world_chunk_create_bsp(u32 id); +void pxl8_world_chunk_destroy(pxl8_world_chunk* chunk); + +#ifdef __cplusplus +} +#endif diff --git a/src/world/pxl8_chunk_cache.c b/src/world/pxl8_world_chunk_cache.c similarity index 68% rename from src/world/pxl8_chunk_cache.c rename to src/world/pxl8_world_chunk_cache.c index 2d9b34b..3fd1177 100644 --- a/src/world/pxl8_chunk_cache.c +++ b/src/world/pxl8_world_chunk_cache.c @@ -1,4 +1,4 @@ -#include "pxl8_chunk_cache.h" +#include "pxl8_world_chunk_cache.h" #include #include @@ -7,12 +7,12 @@ #include "pxl8_log.h" #include "pxl8_mem.h" -#define PXL8_VOXEL_CHUNK_VOLUME (PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE) +#define PXL8_VXL_CHUNK_VOLUME (PXL8_VXL_CHUNK_SIZE * PXL8_VXL_CHUNK_SIZE * PXL8_VXL_CHUNK_SIZE) -static pxl8_chunk_cache_entry* find_entry_vxl(pxl8_chunk_cache* cache, i32 cx, i32 cy, i32 cz) { +static pxl8_world_chunk_cache_entry* find_entry_vxl(pxl8_world_chunk_cache* cache, i32 cx, i32 cy, i32 cz) { for (u32 i = 0; i < cache->entry_count; i++) { - pxl8_chunk_cache_entry* e = &cache->entries[i]; - if (e->valid && e->chunk && e->chunk->type == PXL8_CHUNK_VXL && + pxl8_world_chunk_cache_entry* e = &cache->entries[i]; + if (e->valid && e->chunk && e->chunk->type == PXL8_WORLD_CHUNK_VXL && e->chunk->cx == cx && e->chunk->cy == cy && e->chunk->cz == cz) { return e; } @@ -20,10 +20,10 @@ static pxl8_chunk_cache_entry* find_entry_vxl(pxl8_chunk_cache* cache, i32 cx, i return NULL; } -static pxl8_chunk_cache_entry* find_entry_bsp(pxl8_chunk_cache* cache, u32 id) { +static pxl8_world_chunk_cache_entry* find_entry_bsp(pxl8_world_chunk_cache* cache, u32 id) { for (u32 i = 0; i < cache->entry_count; i++) { - pxl8_chunk_cache_entry* e = &cache->entries[i]; - if (e->valid && e->chunk && e->chunk->type == PXL8_CHUNK_BSP && + pxl8_world_chunk_cache_entry* e = &cache->entries[i]; + if (e->valid && e->chunk && e->chunk->type == PXL8_WORLD_CHUNK_BSP && e->chunk->id == id) { return e; } @@ -31,16 +31,16 @@ static pxl8_chunk_cache_entry* find_entry_bsp(pxl8_chunk_cache* cache, u32 id) { return NULL; } -static pxl8_chunk_cache_entry* alloc_entry(pxl8_chunk_cache* cache) { - if (cache->entry_count < PXL8_CHUNK_CACHE_SIZE) { - pxl8_chunk_cache_entry* e = &cache->entries[cache->entry_count++]; +static pxl8_world_chunk_cache_entry* alloc_entry(pxl8_world_chunk_cache* cache) { + if (cache->entry_count < PXL8_WORLD_CHUNK_CACHE_SIZE) { + pxl8_world_chunk_cache_entry* e = &cache->entries[cache->entry_count++]; memset(e, 0, sizeof(*e)); return e; } - for (u32 i = 0; i < PXL8_CHUNK_CACHE_SIZE; i++) { + for (u32 i = 0; i < PXL8_WORLD_CHUNK_CACHE_SIZE; i++) { if (!cache->entries[i].valid) { - pxl8_chunk_cache_entry* e = &cache->entries[i]; + pxl8_world_chunk_cache_entry* e = &cache->entries[i]; memset(e, 0, sizeof(*e)); return e; } @@ -48,22 +48,22 @@ static pxl8_chunk_cache_entry* alloc_entry(pxl8_chunk_cache* cache) { u64 oldest = cache->entries[0].last_used; u32 slot = 0; - for (u32 i = 1; i < PXL8_CHUNK_CACHE_SIZE; i++) { + for (u32 i = 1; i < PXL8_WORLD_CHUNK_CACHE_SIZE; i++) { if (cache->entries[i].last_used < oldest) { oldest = cache->entries[i].last_used; slot = i; } } - pxl8_chunk_cache_entry* e = &cache->entries[slot]; - if (e->chunk) pxl8_chunk_destroy(e->chunk); + pxl8_world_chunk_cache_entry* e = &cache->entries[slot]; + if (e->chunk) pxl8_world_chunk_destroy(e->chunk); if (e->mesh) pxl8_mesh_destroy(e->mesh); memset(e, 0, sizeof(*e)); return e; } -static void assembly_reset(pxl8_chunk_assembly* a) { - a->type = PXL8_CHUNK_VXL; +static void assembly_reset(pxl8_world_chunk_assembly* a) { + a->type = PXL8_WORLD_CHUNK_VXL; a->id = 0; a->cx = 0; a->cy = 0; @@ -76,9 +76,9 @@ static void assembly_reset(pxl8_chunk_assembly* a) { a->complete = false; } -static void assembly_init(pxl8_chunk_assembly* a, const pxl8_chunk_msg_header* hdr) { +static void assembly_init(pxl8_world_chunk_assembly* a, const pxl8_chunk_msg_header* hdr) { assembly_reset(a); - a->type = hdr->chunk_type == PXL8_CHUNK_TYPE_BSP ? PXL8_CHUNK_BSP : PXL8_CHUNK_VXL; + a->type = hdr->chunk_type == PXL8_CHUNK_TYPE_BSP ? PXL8_WORLD_CHUNK_BSP : PXL8_WORLD_CHUNK_VXL; a->id = hdr->id; a->cx = hdr->cx; a->cy = hdr->cy; @@ -94,25 +94,31 @@ static void assembly_init(pxl8_chunk_assembly* a, const pxl8_chunk_msg_header* h } } -static bool rle_decode_voxel(const u8* src, usize src_len, pxl8_voxel_chunk* chunk) { +static bool rle_decode_voxel(const u8* src, usize src_len, pxl8_vxl_chunk* chunk) { + u8* linear = pxl8_malloc(PXL8_VXL_CHUNK_VOLUME); + if (!linear) return false; + usize src_pos = 0; usize dst_pos = 0; - while (src_pos + 1 < src_len && dst_pos < PXL8_VOXEL_CHUNK_VOLUME) { + while (src_pos + 1 < src_len && dst_pos < PXL8_VXL_CHUNK_VOLUME) { u8 block = src[src_pos++]; u8 run_minus_one = src[src_pos++]; usize run = (usize)run_minus_one + 1; - for (usize i = 0; i < run && dst_pos < PXL8_VOXEL_CHUNK_VOLUME; i++) { - i32 x = (i32)(dst_pos % PXL8_VOXEL_CHUNK_SIZE); - i32 y = (i32)((dst_pos / PXL8_VOXEL_CHUNK_SIZE) % PXL8_VOXEL_CHUNK_SIZE); - i32 z = (i32)(dst_pos / (PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE)); - pxl8_voxel_set(chunk, x, y, z, block); - dst_pos++; + for (usize i = 0; i < run && dst_pos < PXL8_VXL_CHUNK_VOLUME; i++) { + linear[dst_pos++] = block; } } - return dst_pos == PXL8_VOXEL_CHUNK_VOLUME; + if (dst_pos != PXL8_VXL_CHUNK_VOLUME) { + pxl8_free(linear); + return false; + } + + pxl8_vxl_chunk_from_linear(chunk, linear); + pxl8_free(linear); + return true; } static pxl8_result deserialize_vertex(pxl8_stream* s, pxl8_bsp_vertex* v) { @@ -204,8 +210,8 @@ static pxl8_result deserialize_cell_portals(pxl8_stream* s, pxl8_bsp_cell_portal return PXL8_OK; } -static pxl8_bsp* assembly_to_bsp(pxl8_chunk_assembly* a) { - if (!a->complete || a->data_size < 44) { +static pxl8_bsp* assembly_to_bsp(pxl8_world_chunk_assembly* a) { + if (!a->complete || a->data_size < 48) { return NULL; } @@ -315,11 +321,11 @@ static pxl8_bsp* assembly_to_bsp(pxl8_chunk_assembly* a) { return bsp; } -static pxl8_result assemble_vxl(pxl8_chunk_cache* cache, pxl8_chunk_assembly* a) { - pxl8_chunk_cache_entry* entry = find_entry_vxl(cache, a->cx, a->cy, a->cz); +static pxl8_result assemble_vxl(pxl8_world_chunk_cache* cache, pxl8_world_chunk_assembly* a) { + pxl8_world_chunk_cache_entry* entry = find_entry_vxl(cache, a->cx, a->cy, a->cz); if (!entry) { entry = alloc_entry(cache); - entry->chunk = pxl8_chunk_create_vxl(a->cx, a->cy, a->cz); + entry->chunk = pxl8_world_chunk_create_vxl(a->cx, a->cy, a->cz); entry->valid = true; } @@ -332,9 +338,10 @@ static pxl8_result assemble_vxl(pxl8_chunk_cache* cache, pxl8_chunk_assembly* a) entry->mesh = NULL; } - pxl8_voxel_chunk_clear(entry->chunk->voxel); + pxl8_vxl_block_clear(entry->chunk->voxels); - if (!rle_decode_voxel(a->data, a->data_size, entry->chunk->voxel)) { + if (!rle_decode_voxel(a->data, a->data_size, entry->chunk->voxels)) { + pxl8_error("[CLIENT] RLE decode failed for chunk (%d,%d,%d)", a->cx, a->cy, a->cz); return PXL8_ERROR_INVALID_ARGUMENT; } @@ -342,7 +349,7 @@ static pxl8_result assemble_vxl(pxl8_chunk_cache* cache, pxl8_chunk_assembly* a) return PXL8_OK; } -static pxl8_result assemble_bsp(pxl8_chunk_cache* cache, pxl8_chunk_assembly* a) { +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) { @@ -352,14 +359,14 @@ static pxl8_result assemble_bsp(pxl8_chunk_cache* cache, pxl8_chunk_assembly* a) } pxl8_debug("[CLIENT] assemble_bsp: BSP created with %u verts %u faces", bsp->num_vertices, bsp->num_faces); - pxl8_chunk_cache_entry* entry = find_entry_bsp(cache, a->id); + pxl8_world_chunk_cache_entry* entry = find_entry_bsp(cache, a->id); if (entry) { if (entry->chunk && entry->chunk->bsp) { pxl8_bsp_destroy(entry->chunk->bsp); } } else { entry = alloc_entry(cache); - entry->chunk = pxl8_chunk_create_bsp(a->id); + entry->chunk = pxl8_world_chunk_create_bsp(a->id); entry->valid = true; } @@ -371,19 +378,19 @@ static pxl8_result assemble_bsp(pxl8_chunk_cache* cache, pxl8_chunk_assembly* a) return PXL8_OK; } -pxl8_chunk_cache* pxl8_chunk_cache_create(void) { - pxl8_chunk_cache* cache = pxl8_calloc(1, sizeof(pxl8_chunk_cache)); +pxl8_world_chunk_cache* pxl8_world_chunk_cache_create(void) { + pxl8_world_chunk_cache* cache = pxl8_calloc(1, sizeof(pxl8_world_chunk_cache)); if (!cache) return NULL; assembly_reset(&cache->assembly); return cache; } -void pxl8_chunk_cache_destroy(pxl8_chunk_cache* cache) { +void pxl8_world_chunk_cache_destroy(pxl8_world_chunk_cache* cache) { if (!cache) return; for (u32 i = 0; i < cache->entry_count; i++) { - pxl8_chunk_cache_entry* e = &cache->entries[i]; - if (e->chunk) pxl8_chunk_destroy(e->chunk); + pxl8_world_chunk_cache_entry* e = &cache->entries[i]; + if (e->chunk) pxl8_world_chunk_destroy(e->chunk); if (e->mesh) pxl8_mesh_destroy(e->mesh); } @@ -391,12 +398,12 @@ void pxl8_chunk_cache_destroy(pxl8_chunk_cache* cache) { pxl8_free(cache); } -pxl8_result pxl8_chunk_cache_receive(pxl8_chunk_cache* cache, - const pxl8_chunk_msg_header* hdr, - const u8* payload, usize len) { +pxl8_result pxl8_world_chunk_cache_receive(pxl8_world_chunk_cache* cache, + const pxl8_chunk_msg_header* hdr, + const u8* payload, usize len) { if (!cache || !hdr || !payload) return PXL8_ERROR_INVALID_ARGUMENT; - pxl8_chunk_assembly* a = &cache->assembly; + pxl8_world_chunk_assembly* a = &cache->assembly; bool new_assembly = !a->active || (hdr->chunk_type == PXL8_CHUNK_TYPE_BSP && a->id != hdr->id) || @@ -409,7 +416,7 @@ pxl8_result pxl8_chunk_cache_receive(pxl8_chunk_cache* cache, assembly_init(a, hdr); } - if (hdr->fragment_idx >= PXL8_CHUNK_MAX_FRAGMENTS) { + if (hdr->fragment_idx >= PXL8_WORLD_CHUNK_MAX_FRAGMENTS) { return PXL8_ERROR_INVALID_ARGUMENT; } @@ -430,9 +437,7 @@ pxl8_result pxl8_chunk_cache_receive(pxl8_chunk_cache* cache, if (hdr->flags & PXL8_CHUNK_FLAG_FINAL) { a->complete = true; - pxl8_debug("[CLIENT] Final fragment received, assembling type=%d data_size=%zu", a->type, a->data_size); - - if (a->type == PXL8_CHUNK_BSP) { + if (a->type == PXL8_WORLD_CHUNK_BSP) { return assemble_bsp(cache, a); } else { return assemble_vxl(cache, a); @@ -442,9 +447,9 @@ pxl8_result pxl8_chunk_cache_receive(pxl8_chunk_cache* cache, return PXL8_OK; } -pxl8_chunk* pxl8_chunk_cache_get_vxl(pxl8_chunk_cache* cache, i32 cx, i32 cy, i32 cz) { +pxl8_world_chunk* pxl8_world_chunk_cache_get_vxl(pxl8_world_chunk_cache* cache, i32 cx, i32 cy, i32 cz) { if (!cache) return NULL; - pxl8_chunk_cache_entry* e = find_entry_vxl(cache, cx, cy, cz); + pxl8_world_chunk_cache_entry* e = find_entry_vxl(cache, cx, cy, cz); if (e) { e->last_used = cache->frame_counter; return e->chunk; @@ -452,9 +457,9 @@ pxl8_chunk* pxl8_chunk_cache_get_vxl(pxl8_chunk_cache* cache, i32 cx, i32 cy, i3 return NULL; } -pxl8_chunk* pxl8_chunk_cache_get_bsp(pxl8_chunk_cache* cache, u32 id) { +pxl8_world_chunk* pxl8_world_chunk_cache_get_bsp(pxl8_world_chunk_cache* cache, u32 id) { if (!cache) return NULL; - pxl8_chunk_cache_entry* e = find_entry_bsp(cache, id); + pxl8_world_chunk_cache_entry* e = find_entry_bsp(cache, id); if (e) { e->last_used = cache->frame_counter; return e->chunk; @@ -462,17 +467,28 @@ pxl8_chunk* pxl8_chunk_cache_get_bsp(pxl8_chunk_cache* cache, u32 id) { return NULL; } -pxl8_mesh* pxl8_chunk_cache_get_mesh(pxl8_chunk_cache* cache, - i32 cx, i32 cy, i32 cz, - const pxl8_block_registry* registry, - const pxl8_voxel_mesh_config* config) { +pxl8_mesh* pxl8_world_chunk_cache_get_mesh(pxl8_world_chunk_cache* cache, + i32 cx, i32 cy, i32 cz, + const pxl8_vxl_block_registry* registry, + const pxl8_vxl_mesh_config* config) { if (!cache || !registry) return NULL; - pxl8_chunk_cache_entry* entry = find_entry_vxl(cache, cx, cy, cz); - if (!entry || !entry->chunk || !entry->chunk->voxel) return NULL; + pxl8_world_chunk_cache_entry* entry = find_entry_vxl(cache, cx, cy, cz); + if (!entry || !entry->chunk || !entry->chunk->voxels) return NULL; + + pxl8_world_chunk* nx = pxl8_world_chunk_cache_get_vxl(cache, cx - 1, cy, cz); + pxl8_world_chunk* px = pxl8_world_chunk_cache_get_vxl(cache, cx + 1, cy, cz); + pxl8_world_chunk* ny = pxl8_world_chunk_cache_get_vxl(cache, cx, cy - 1, cz); + pxl8_world_chunk* py = pxl8_world_chunk_cache_get_vxl(cache, cx, cy + 1, cz); + pxl8_world_chunk* nz = pxl8_world_chunk_cache_get_vxl(cache, cx, cy, cz - 1); + pxl8_world_chunk* pz = pxl8_world_chunk_cache_get_vxl(cache, cx, cy, cz + 1); + + bool all_neighbors = nx && px && ny && py && nz && pz; if (entry->mesh && !entry->mesh_dirty) { - return entry->mesh; + if (entry->has_all_neighbors == all_neighbors) { + return entry->mesh; + } } if (entry->mesh) { @@ -480,47 +496,43 @@ pxl8_mesh* pxl8_chunk_cache_get_mesh(pxl8_chunk_cache* cache, entry->mesh = NULL; } - pxl8_chunk* nx = pxl8_chunk_cache_get_vxl(cache, cx - 1, cy, cz); - pxl8_chunk* px = pxl8_chunk_cache_get_vxl(cache, cx + 1, cy, cz); - pxl8_chunk* ny = pxl8_chunk_cache_get_vxl(cache, cx, cy - 1, cz); - pxl8_chunk* py = pxl8_chunk_cache_get_vxl(cache, cx, cy + 1, cz); - pxl8_chunk* nz = pxl8_chunk_cache_get_vxl(cache, cx, cy, cz - 1); - pxl8_chunk* pz = pxl8_chunk_cache_get_vxl(cache, cx, cy, cz + 1); - - const pxl8_voxel_chunk* neighbors[6] = { - nx ? nx->voxel : NULL, - px ? px->voxel : NULL, - ny ? ny->voxel : NULL, - py ? py->voxel : NULL, - nz ? nz->voxel : NULL, - pz ? pz->voxel : NULL + const pxl8_vxl_chunk* neighbors[6] = { + nx ? nx->voxels : NULL, + px ? px->voxels : NULL, + ny ? ny->voxels : NULL, + py ? py->voxels : NULL, + nz ? nz->voxels : NULL, + pz ? pz->voxels : NULL }; - entry->mesh = pxl8_voxel_build_mesh(entry->chunk->voxel, neighbors, registry, config); + pxl8_vxl_mesh_config local_config = config ? *config : PXL8_VXL_MESH_CONFIG_DEFAULT; + + entry->mesh = pxl8_vxl_build_mesh(entry->chunk->voxels, neighbors, registry, &local_config); entry->mesh_dirty = false; + entry->has_all_neighbors = all_neighbors; return entry->mesh; } -void pxl8_chunk_cache_tick(pxl8_chunk_cache* cache) { +void pxl8_world_chunk_cache_tick(pxl8_world_chunk_cache* cache) { if (!cache) return; cache->frame_counter++; } -void pxl8_chunk_cache_evict_distant(pxl8_chunk_cache* cache, - i32 cx, i32 cy, i32 cz, i32 radius) { +void pxl8_world_chunk_cache_evict_distant(pxl8_world_chunk_cache* cache, + i32 cx, i32 cy, i32 cz, i32 radius) { if (!cache) return; for (u32 i = 0; i < cache->entry_count; i++) { - pxl8_chunk_cache_entry* e = &cache->entries[i]; - if (!e->valid || !e->chunk || e->chunk->type != PXL8_CHUNK_VXL) continue; + pxl8_world_chunk_cache_entry* e = &cache->entries[i]; + if (!e->valid || !e->chunk || e->chunk->type != PXL8_WORLD_CHUNK_VXL) continue; i32 dx = e->chunk->cx - cx; i32 dy = e->chunk->cy - cy; i32 dz = e->chunk->cz - cz; if (abs(dx) > radius || abs(dy) > radius || abs(dz) > radius) { - pxl8_chunk_destroy(e->chunk); + pxl8_world_chunk_destroy(e->chunk); if (e->mesh) pxl8_mesh_destroy(e->mesh); e->chunk = NULL; e->mesh = NULL; @@ -529,7 +541,7 @@ void pxl8_chunk_cache_evict_distant(pxl8_chunk_cache* cache, } } -void pxl8_chunk_cache_invalidate_meshes(pxl8_chunk_cache* cache) { +void pxl8_world_chunk_cache_invalidate_meshes(pxl8_world_chunk_cache* cache) { if (!cache) return; for (u32 i = 0; i < cache->entry_count; i++) { diff --git a/src/world/pxl8_world_chunk_cache.h b/src/world/pxl8_world_chunk_cache.h new file mode 100644 index 0000000..ed40b01 --- /dev/null +++ b/src/world/pxl8_world_chunk_cache.h @@ -0,0 +1,69 @@ +#pragma once + +#include "pxl8_mesh.h" +#include "pxl8_protocol.h" +#include "pxl8_types.h" +#include "pxl8_vxl_render.h" +#include "pxl8_world_chunk.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PXL8_WORLD_CHUNK_CACHE_SIZE 512 +#define PXL8_WORLD_CHUNK_MAX_FRAGMENTS 64 +#define PXL8_WORLD_CHUNK_MAX_DATA_SIZE 131072 + +typedef struct pxl8_world_chunk_cache_entry { + pxl8_world_chunk* chunk; + pxl8_mesh* mesh; + u64 last_used; + bool mesh_dirty; + bool valid; + bool has_all_neighbors; +} pxl8_world_chunk_cache_entry; + +typedef struct pxl8_world_chunk_assembly { + pxl8_world_chunk_type type; + u32 id; + i32 cx, cy, cz; + u32 version; + u8 fragment_count; + u8 fragments_received; + u8* data; + u32 data_size; + u32 data_capacity; + bool active; + bool complete; +} pxl8_world_chunk_assembly; + +typedef struct pxl8_world_chunk_cache { + pxl8_world_chunk_cache_entry entries[PXL8_WORLD_CHUNK_CACHE_SIZE]; + pxl8_world_chunk_assembly assembly; + u32 entry_count; + u64 frame_counter; +} pxl8_world_chunk_cache; + +pxl8_world_chunk_cache* pxl8_world_chunk_cache_create(void); +void pxl8_world_chunk_cache_destroy(pxl8_world_chunk_cache* cache); + +pxl8_result pxl8_world_chunk_cache_receive(pxl8_world_chunk_cache* cache, + const pxl8_chunk_msg_header* hdr, + const u8* payload, usize len); + +pxl8_world_chunk* pxl8_world_chunk_cache_get_vxl(pxl8_world_chunk_cache* cache, i32 cx, i32 cy, i32 cz); +pxl8_world_chunk* pxl8_world_chunk_cache_get_bsp(pxl8_world_chunk_cache* cache, u32 id); + +pxl8_mesh* pxl8_world_chunk_cache_get_mesh(pxl8_world_chunk_cache* cache, + i32 cx, i32 cy, i32 cz, + const pxl8_vxl_block_registry* registry, + const pxl8_vxl_mesh_config* config); + +void pxl8_world_chunk_cache_tick(pxl8_world_chunk_cache* cache); +void pxl8_world_chunk_cache_evict_distant(pxl8_world_chunk_cache* cache, + i32 cx, i32 cy, i32 cz, i32 radius); +void pxl8_world_chunk_cache_invalidate_meshes(pxl8_world_chunk_cache* cache); + +#ifdef __cplusplus +} +#endif