better lighting

This commit is contained in:
asrael 2026-01-31 09:31:17 -06:00
parent 805a2713a3
commit 6ed4e17065
75 changed files with 6417 additions and 3667 deletions

View file

@ -4,13 +4,8 @@
(local first_person3d (require :mod.first_person3d)) (local first_person3d (require :mod.first_person3d))
(var time 0) (var time 0)
(var active-demo :logo) (var in-world false)
(var particles nil)
(var fire-init? false)
(var rain-init? false)
(var snow-init? false)
(var first_person3d-init? false) (var first_person3d-init? false)
(var use-famicube-palette? false)
(var logo-x 256) (var logo-x 256)
(var logo-y 148) (var logo-y 148)
@ -18,64 +13,64 @@
(var logo-dy 80) (var logo-dy 80)
(var logo-sprite nil) (var logo-sprite nil)
(var transition nil) (var transition nil)
(var transition-pending nil) (var transition-to-world false)
(fn switch-demo [new-demo] (fn start-transition []
(when (and (not= active-demo new-demo) (not transition)) (when (not transition)
(set transition-pending new-demo) (set transition-to-world true)
(set transition (pxl8.create_transition :pixelate 0.5)) (set transition (pxl8.create_transition :pixelate 0.5))
(transition:set_color 0xFF000000) (transition:set_color 0xFF000000)
(transition:start))) (transition:start)))
(fn enter-world []
(when (first_person3d.is-ready)
(start-transition)))
(global init (fn [] (global init (fn []
(pxl8.load_palette "res/sprites/pxl8_logo.ase") (pxl8.load_palette "res/sprites/pxl8_logo.ase")
(set logo-sprite (pxl8.load_sprite "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] (global update (fn [dt]
(when (pxl8.key_pressed "escape") (when (pxl8.key_pressed "escape")
(menu.toggle) (if (menu.is-paused)
(when (= active-demo :first_person3d) (do
(pxl8.set_relative_mouse_mode (not (menu.is-paused))))) (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)) (when (not (menu.is-paused))
(set time (+ time dt)) (set time (+ time dt))
(music.update dt)
(when transition (when transition
(transition:update dt) (transition:update dt)
(when (transition:is_complete) (when (transition:is_complete)
(when transition-pending (when transition-to-world
(when (and (= active-demo :first_person3d) (not= transition-pending :first_person3d)) (set in-world true)
(pxl8.set_relative_mouse_mode false)) (set transition-to-world false)
(when (and (not= active-demo :first_person3d) (= transition-pending :first_person3d))
(pxl8.set_relative_mouse_mode true)) (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)))
(transition:destroy) (transition:destroy)
(set transition nil))) (set transition nil)))
(when (pxl8.key_pressed "1") (switch-demo :logo)) (if in-world
(when (pxl8.key_pressed "2") (switch-demo :plasma)) (do
(when (pxl8.key_pressed "3") (switch-demo :tunnel)) (when (not first_person3d-init?)
(when (pxl8.key_pressed "4") (switch-demo :raster)) (first_person3d.init)
(when (pxl8.key_pressed "5") (switch-demo :fire)) (set first_person3d-init? true))
(when (pxl8.key_pressed "6") (switch-demo :rain)) (first_person3d.update dt))
(when (pxl8.key_pressed "7") (switch-demo :snow)) (do
(when (pxl8.key_pressed "8") (switch-demo :first_person3d)) (when (and (not (menu.is-paused))
(when (pxl8.key_pressed "=") (first_person3d.is-ready)
(set use-famicube-palette? (not use-famicube-palette?)) (or (pxl8.key_pressed "return") (pxl8.key_pressed "space")))
(local palette-path (if use-famicube-palette? "res/palettes/famicube.ase" "res/sprites/pxl8_logo.ase")) (enter-world))
(pxl8.load_palette palette-path))
(music.update dt)
(case active-demo
:logo (do
(set logo-x (+ logo-x (* logo-dx dt))) (set logo-x (+ logo-x (* logo-dx dt)))
(set logo-y (+ logo-y (* logo-dy dt))) (set logo-y (+ logo-y (* logo-dy dt)))
(when (< logo-x 0) (when (< logo-x 0)
@ -89,93 +84,24 @@
(set logo-dy (math.abs logo-dy))) (set logo-dy (math.abs logo-dy)))
(when (> logo-y 296) (when (> logo-y 296)
(set logo-y 296) (set logo-y 296)
(set logo-dy (- (math.abs logo-dy))))) (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)))
(when (menu.is-paused) (when (menu.is-paused)
(menu.update)))) (menu.update))))
(global frame (fn [] (global frame (fn []
(case active-demo (if in-world
:logo (do (first_person3d.frame)
(do
(pxl8.clear 0) (pxl8.clear 0)
(when logo-sprite (when logo-sprite
(pxl8.sprite logo-sprite logo-x logo-y 128 64 (< logo-dx 0) (< logo-dy 0)))) (pxl8.sprite logo-sprite logo-x logo-y 128 64 (< logo-dx 0) (< logo-dy 0)))
(when (not (menu.is-paused))
:plasma (do (if (first_person3d.is-ready)
(pxl8.clear 0) (pxl8.text "Press ENTER to start" 240 320 1)
(pxl8.text "Plasma (TODO: Fennel impl)" 200 170 1)) (if (first_person3d.is-connected)
(pxl8.text "Loading world..." 260 320 1)
:tunnel (do (pxl8.text "Connecting..." 275 320 1))))))
(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))
(when transition (when transition
(transition:render)) (transition:render))

180
demo/mod/entities.fnl Normal file
View file

@ -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}

View file

@ -1,8 +1,10 @@
(local pxl8 (require :pxl8)) (local pxl8 (require :pxl8))
(local effects (require :pxl8.effects)) (local effects (require :pxl8.effects))
(local net (require :pxl8.net)) (local net (require :pxl8.net))
(local shader (require :pxl8.shader))
(local colormap (require :mod.colormap)) (local colormap (require :mod.colormap))
(local entities (require :mod.entities))
(local menu (require :mod.menu)) (local menu (require :mod.menu))
(local palette (require :mod.palette)) (local palette (require :mod.palette))
(local sky (require :mod.sky)) (local sky (require :mod.sky))
@ -11,234 +13,96 @@
(local bob-amount 4.0) (local bob-amount 4.0)
(local bob-speed 8.0) (local bob-speed 8.0)
(local cam-smoothing 0.25) (local cam-smoothing 0.25)
(local ceiling-height 120)
(local chunk-size 64) (local chunk-size 64)
(local cursor-sensitivity 0.010) (local cursor-sensitivity 0.010)
(local gravity -800) (local gravity 600)
(local grid-size 64) (local grid-size 64)
(local ground-y 64) (local jump-velocity 180)
(local jump-force 175)
(local land-recovery-speed 20) (local land-recovery-speed 20)
(local land-squash-amount -4) (local land-squash-amount -4)
(local max-pitch 1.5) (local max-pitch 1.5)
(local move-speed 200) (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 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-cancel-key nil)
(var auto-run? false)
(var bob-time 0) (var bob-time 0)
(var cam-pitch 0) (var cam-pitch 0)
(var cam-x 416) (var cam-x 416)
(var cam-y 64) (var cam-y 0)
(var cam-yaw 0) (var cam-yaw 0)
(var cam-z 416) (var cam-z 416)
(var camera nil) (var camera nil)
(var ceiling-tex nil) (var ceiling-tex nil)
(var fireball-mesh nil)
(var floor-tex nil) (var floor-tex nil)
(var fps-avg 0) (var fps-avg 0)
(var fps-sample-count 0) (var fps-sample-count 0)
(var grounded? true) (var grounded true)
(var land-squash 0) (var land-squash 0)
(var last-dt 0.016) (var last-dt 0.016)
(var light-time 0) (var light-time 0)
(var lights nil) (var lights nil)
(var materials-setup false) (var bsp-materials-setup false)
(var network nil) (var network nil)
(var portal-cooldown 0)
(var real-time 0) (var real-time 0)
(var smooth-cam-x 416) (var smooth-cam-x 416)
(var smooth-cam-z 416) (var smooth-cam-z 416)
(var velocity-y 0) (var vel-y 0)
(var trim-tex nil)
(var wall-tex nil) (var wall-tex nil)
(var world nil) (var world nil)
(local cursor-look? true) (local cursor-look? true)
(local FIREBALL_COLOR 218) (local MOSS_COLOR 200)
(local PLASTER_COLOR 16)
(local STONE_FLOOR_START 37) (local STONE_FLOOR_START 37)
(local STONE_WALL_START 2) (local STONE_WALL_START 2)
(local MOSS_COLOR 200) (local WOOD_COLOR 88)
(local trail-positions []) (fn find-floor [x y z in-bsp]
(local TRAIL_LENGTH 8) (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 [] (fn find-ceiling [x y z max-height]
(let [verts [] (if (not world)
indices [] nil
radius 5 (let [ray (world:ray x (+ y 1) z x (+ y max-height) z)]
rings 4 (if ray.hit
segments 6 (- ray.point.y 1)
core-color (+ FIREBALL_COLOR 6) nil))))
spike-color (+ FIREBALL_COLOR 2)]
;; top pole (fn preload []
(table.insert verts {:x 0 :y radius :z 0 :nx 0 :ny 1 :nz 0 :color core-color :light 255}) (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 (fn is-connected []
(for [ring 1 (- rings 1)] (and network (network:connected)))
(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})))))
;; bottom pole (fn is-ready []
(let [bottom-idx (length verts)] (if (not world)
(table.insert verts {:x 0 :y (- radius) :z 0 :nx 0 :ny -1 :nz 0 :color core-color :light 255}) 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 [] (fn init []
(pxl8.set_relative_mouse_mode true) (pxl8.set_relative_mouse_mode true)
(pxl8.set_palette palette 256) (pxl8.set_palette palette 256)
@ -248,7 +112,9 @@
r 0xFF r 0xFF
g (math.floor (+ 0x60 (* t (- 0xE0 0x60)))) g (math.floor (+ 0x60 (* t (- 0xE0 0x60))))
b (math.floor (+ 0x10 (* t (- 0x80 0x10))))] 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) (sky.update-gradient 1 2 6 6 10 18)
(pxl8.update_palette_deps) (pxl8.update_palette_deps)
@ -256,49 +122,38 @@
(set camera (pxl8.create_camera_3d))) (set camera (pxl8.create_camera_3d)))
(when (not lights) (when (not lights)
(set lights (pxl8.create_lights))) (set lights (pxl8.create_lights)))
(when (not fireball-mesh)
(create-fireball-mesh)) (entities.init textures)
(sky.generate-stars 12345) (sky.generate-stars 12345)
(when (not network) (preload)
(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 (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) (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 [] (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)] (let [chunk (world:active_chunk)]
(when (and chunk (chunk:ready)) (when (and chunk (chunk:ready))
(let [bsp (chunk:bsp)] (let [ceiling-mat (pxl8.create_material {:texture ceiling-tex})
(when bsp floor-mat (pxl8.create_material {:texture floor-tex :lighting true})
(let [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}) wall-mat (pxl8.create_material {:texture wall-tex :lighting true})]
ceiling-mat (pxl8.create_material {:texture ceiling-tex})]
(bsp:set_material 0 floor-mat) (world:set_bsp_material 0 floor-mat)
(bsp:set_material 1 wall-mat) (world:set_bsp_material 1 wall-mat)
(bsp:set_material 2 ceiling-mat) (world:set_bsp_material 2 ceiling-mat)
(world:set_bsp_material 3 trim-mat)
(for [i 0 (- (bsp:face_count) 1)] (set bsp-materials-setup true))))))
(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))))))))
(fn sample-input [] (fn sample-input []
(var move-forward 0) (var move-forward 0)
@ -329,26 +184,6 @@
:look_dx (pxl8.mouse_dx) :look_dx (pxl8.mouse_dx)
:look_dy (pxl8.mouse_dy)}) :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] (fn update [dt]
(set last-dt dt) (set last-dt dt)
(let [fps (pxl8.get_fps)] (let [fps (pxl8.get_fps)]
@ -361,87 +196,111 @@
(setup-materials) (setup-materials)
(let [chunk (world:active_chunk)] (when (> portal-cooldown 0)
(when (and chunk (chunk:ready)) (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) (let [input (sample-input)
grid-max (* grid-size chunk-size) grid-max (if voxel-space 100000 (* grid-size chunk-size))
movement-yaw cam-yaw] 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) (when (and voxel-space grounded)
(set time-accumulator (- time-accumulator sim-dt)) (let [floor-y (find-floor cam-x cam-y cam-z false)
(set client-tick (+ client-tick 1)) height-diff (- floor-y cam-y)]
(if (and (>= height-diff (- step-height)) (<= height-diff step-height))
(store-pending-input client-tick input) (let [lerp-speed 0.3
smooth-y (+ (* cam-y (- 1 lerp-speed)) (* floor-y lerp-speed))]
(let [(new-x new-z) (apply-movement cam-x cam-z movement-yaw input)] (set cam-y smooth-y))
(when (and (>= new-x 0) (<= new-x grid-max) (when (< height-diff (- step-height))
(>= new-z 0) (<= new-z grid-max)) (set grounded false)))))
(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)))))
(set smooth-cam-x (+ (* smooth-cam-x (- 1 cam-smoothing)) (* cam-x cam-smoothing))) (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))) (set smooth-cam-z (+ (* smooth-cam-z (- 1 cam-smoothing)) (* cam-z cam-smoothing)))
(when (and (pxl8.key_pressed "space") grounded?) (when (and (pxl8.key_pressed "space") grounded)
(set velocity-y jump-force) (set vel-y jump-velocity)
(set grounded? false)) (set grounded false))
(set velocity-y (+ velocity-y (* gravity dt))) (when (or (not grounded) (not= vel-y 0))
(set cam-y (+ cam-y (* velocity-y dt))) (set vel-y (- vel-y (* gravity dt)))
(when (<= cam-y ground-y) (if (> vel-y 0)
(when (not grounded?) (let [new-y (+ cam-y (* vel-y dt))
(set land-squash land-squash-amount)) head-y (+ new-y player-height)
(set cam-y ground-y) ceiling-y (if voxel-space
(set velocity-y 0) (find-ceiling cam-x cam-y cam-z 200)
(set grounded? true)) 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) (when (< land-squash 0)
(set land-squash (math.min 0 (+ land-squash (* land-recovery-speed dt))))) (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))] (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))) (set bob-time (+ bob-time (* dt bob-speed)))
(let [target-phase (* (math.floor (/ bob-time math.pi)) math.pi)] (let [target-phase (* (math.floor (/ bob-time math.pi)) math.pi)]
(set bob-time (+ (* bob-time 0.8) (* target-phase 0.2)))))) (set bob-time (+ (* bob-time 0.8) (* target-phase 0.2))))))
@ -458,13 +317,17 @@
(when (not world) (when (not world)
(pxl8.error "world is nil!")) (pxl8.error "world is nil!"))
(let [chunk (when world (world:active_chunk))] (let [chunk (when world (world:active_chunk))
(when (and world (or (not chunk) (not (chunk:ready)))) 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)) (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) (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-x (- (math.sin cam-yaw))
forward-z (- (math.cos cam-yaw)) forward-z (- (math.cos cam-yaw))
target-x (+ smooth-cam-x forward-x) target-x (+ smooth-cam-x forward-x)
@ -490,7 +353,7 @@
r2 (* 0.04 (math.sin (+ (* real-time 3.2) phase))) r2 (* 0.04 (math.sin (+ (* real-time 3.2) phase)))
light-radius (* 150 (+ 0.95 r1 r2))] light-radius (* 150 (+ 0.95 r1 r2))]
(lights:clear) (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 { (pxl8.begin_frame_3d camera lights {
:ambient 30 :ambient 30
:fog_density 0.0 :fog_density 0.0
@ -502,21 +365,22 @@
(sky.render smooth-cam-x eye-y smooth-cam-z (menu.is-wireframe)) (sky.render smooth-cam-x eye-y smooth-cam-z (menu.is-wireframe))
(pxl8.clear_depth) (pxl8.clear_depth)
(when chunk (world:set_wireframe (menu.is-wireframe))
(let [bsp (chunk:bsp)]
(when bsp
(bsp:set_wireframe (menu.is-wireframe)))))
(world:render [smooth-cam-x eye-y smooth-cam-z]) (world:render [smooth-cam-x eye-y smooth-cam-z])
(when fireball-mesh (when chunk
(let [wire (menu.is-wireframe)] (entities.render-fireball light-x light-y light-z (menu.is-wireframe)))
(pxl8.draw_mesh fireball-mesh {:x light-x :y light-y :z light-z
:passthrough true (entities.render-door (menu.is-wireframe) (if voxel-space 128 0))
:wireframe wire
:emissive 1.0})))
(pxl8.end_frame_3d)) (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) (sky.render-stars smooth-cam-x eye-y smooth-cam-z 1.0 last-dt)
(let [cx (/ (pxl8.get_width) 2) (let [cx (/ (pxl8.get_width) 2)
@ -532,6 +396,9 @@
(string.format "%.0f" cam-y) "," (string.format "%.0f" cam-y) ","
(string.format "%.0f" cam-z)) 5 15 text-color)))))) (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 :update update
:frame frame} :frame frame}

View file

@ -1,15 +1,28 @@
(local pxl8 (require :pxl8)) (local pxl8 (require :pxl8))
(local music (require :mod.music)) (local music (require :mod.music))
(local world-mod (require :pxl8.world))
(local net-mod (require :pxl8.net))
(var paused false) (var paused false)
(var wireframe false) (var wireframe false)
(var gui nil) (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 [] (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 [] (fn show []
(set paused true) (set paused true)
(set current-panel :main)
(pxl8.set_relative_mouse_mode false) (pxl8.set_relative_mouse_mode false)
(pxl8.center_cursor)) (pxl8.center_cursor))
@ -23,7 +36,35 @@
(hide) (hide)
(show))) (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 [] (fn update []
(set pending-action nil)
(when gui (when gui
(let [(mx my) (pxl8.get_mouse_pos)] (let [(mx my) (pxl8.get_mouse_pos)]
(gui:cursor_move mx my)) (gui:cursor_move mx my))
@ -32,29 +73,102 @@
(gui:cursor_down)) (gui:cursor_down))
(when (pxl8.mouse_released 1) (when (pxl8.mouse_released 1)
(gui:cursor_up)))) (gui:cursor_up))
(fn draw [] (when (or (pxl8.key_pressed "down")
(when gui (and (pxl8.key_pressed "tab") (not (pxl8.key_down "lshift")) (not (pxl8.key_down "rshift"))))
(gui:begin_frame) (select-next))
(pxl8.gui_window 200 100 240 200 "pxl8 demo") (when (or (pxl8.key_pressed "up")
(and (pxl8.key_pressed "tab") (or (pxl8.key_down "lshift") (pxl8.key_down "rshift"))))
(select-prev))
(when (gui:button 1 215 147 210 30 "Resume") (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)) (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")] (let [music-label (if (music.is-playing) "Music: On" "Music: Off")]
(when (gui:button 3 215 182 210 30 music-label) (when (menu-button 10 215 147 210 30 music-label)
(if (music.is-playing) (if (music.is-playing)
(music.stop) (music.stop)
(music.start)))) (music.start))))
(let [wire-label (if wireframe "Wireframe: On" "Wireframe: Off")] (pxl8.gui_label 215 185 "Volume/Devices: TODO" 15)
(when (gui:button 4 215 217 210 30 wire-label)
(set wireframe (not wireframe))))
(when (gui:button 2 215 252 210 30 "Quit") (when (menu-button 20 215 210 210 30 "Back")
(pxl8.quit)) (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)
(case current-panel
:main (draw-main-menu)
:sfx (draw-sfx-panel)
:gfx (draw-gfx-panel))
(if (gui:is_hovering) (if (gui:is_hovering)
(pxl8.set_cursor :hand) (pxl8.set_cursor :hand)
@ -62,7 +176,8 @@
(gui:end_frame))) (gui:end_frame)))
{:is-paused (fn [] paused) {:init init
:is-paused (fn [] paused)
:is-wireframe (fn [] wireframe) :is-wireframe (fn [] wireframe)
:toggle toggle :toggle toggle
:show show :show show

View file

@ -96,6 +96,9 @@
(set sky-mesh (pxl8.create_mesh verts indices)))) (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] (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)] (let [key (.. zenith-r "," zenith-g "," zenith-b "," horizon-r "," horizon-g "," horizon-b)]
(when (not= key last-gradient-key) (when (not= key last-gradient-key)
@ -249,6 +252,7 @@
{:render render {:render render
:render-stars render-stars :render-stars render-stars
:generate-stars generate-stars :generate-stars generate-stars
:reset-gradient reset-gradient
:update-gradient update-gradient :update-gradient update-gradient
:SKY_GRADIENT_START SKY_GRADIENT_START :SKY_GRADIENT_START SKY_GRADIENT_START
:SKY_GRADIENT_COUNT SKY_GRADIENT_COUNT} :SKY_GRADIENT_COUNT SKY_GRADIENT_COUNT}

View file

@ -59,6 +59,10 @@
(let [s (const ctx scale)] (let [s (const ctx scale)]
(ctx.graph:add_node procgen.OP_NOISE_VALUE ctx.x ctx.y s 0 0))) (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] (fn noise-perlin [ctx scale]
(let [s (const ctx scale)] (let [s (const ctx scale)]
(ctx.graph:add_node procgen.OP_NOISE_PERLIN ctx.x ctx.y s 0 0))) (ctx.graph:add_node procgen.OP_NOISE_PERLIN ctx.x ctx.y s 0 0)))
@ -68,6 +72,11 @@
p (const ctx persistence)] p (const ctx persistence)]
(ctx.graph:add_node procgen.OP_NOISE_FBM ctx.x ctx.y s p octaves))) (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] (fn noise-ridged [ctx octaves scale persistence]
(let [s (const ctx scale) (let [s (const ctx scale)
p (const ctx persistence)] p (const ctx persistence)]
@ -165,4 +174,157 @@
(g:destroy) (g:destroy)
tex-id))) 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 textures

35
pxl8.sh
View file

@ -39,6 +39,7 @@ BOLD='\033[1m'
GREEN='\033[38;2;184;187;38m' GREEN='\033[38;2;184;187;38m'
NC='\033[0m' NC='\033[0m'
RED='\033[38;2;251;73;52m' RED='\033[38;2;251;73;52m'
YELLOW='\033[38;2;250;189;47m'
if [[ "$(uname)" == "Linux" ]]; then if [[ "$(uname)" == "Linux" ]]; then
CFLAGS="$CFLAGS -D_GNU_SOURCE" CFLAGS="$CFLAGS -D_GNU_SOURCE"
@ -126,13 +127,29 @@ build_sdl() {
fi 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() { compile_source_file() {
local src_file="$1" local src_file="$1"
local obj_file="$2" local obj_file="$2"
local compile_flags="$3" local compile_flags="$3"
print_info "Compiling: $src_file" 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" print_error "Compilation failed for $src_file"
exit 1 exit 1
fi fi
@ -382,7 +399,7 @@ case "$COMMAND" in
print_info "Compiler cache: ccache enabled" print_info "Compiler cache: ccache enabled"
fi 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" COMPILE_FLAGS="$CFLAGS $INCLUDES"
DEP_COMPILE_FLAGS="$DEP_CFLAGS $INCLUDES" DEP_COMPILE_FLAGS="$DEP_CFLAGS $INCLUDES"
@ -394,6 +411,8 @@ case "$COMMAND" in
src/asset/pxl8_ase.c src/asset/pxl8_ase.c
src/asset/pxl8_cart.c src/asset/pxl8_cart.c
src/asset/pxl8_save.c src/asset/pxl8_save.c
src/bsp/pxl8_bsp.c
src/bsp/pxl8_bsp_render.c
src/core/pxl8.c src/core/pxl8.c
src/core/pxl8_bytes.c src/core/pxl8_bytes.c
src/core/pxl8_io.c src/core/pxl8_io.c
@ -421,20 +440,22 @@ case "$COMMAND" in
src/gui/pxl8_gui.c src/gui/pxl8_gui.c
src/hal/pxl8_hal_sdl3.c src/hal/pxl8_hal_sdl3.c
src/hal/pxl8_mem_sdl3.c src/hal/pxl8_mem_sdl3.c
src/hal/pxl8_thread_sdl3.c
src/math/pxl8_math.c src/math/pxl8_math.c
src/math/pxl8_noise.c
src/net/pxl8_net.c src/net/pxl8_net.c
src/net/pxl8_protocol.c src/net/pxl8_protocol.c
src/procgen/pxl8_graph.c src/procgen/pxl8_graph.c
src/script/pxl8_repl.c src/script/pxl8_repl.c
src/script/pxl8_script.c src/script/pxl8_script.c
src/sfx/pxl8_sfx.c src/sfx/pxl8_sfx.c
src/world/pxl8_bsp.c src/sim/pxl8_sim.c
src/world/pxl8_chunk.c src/vxl/pxl8_vxl.c
src/world/pxl8_chunk_cache.c src/vxl/pxl8_vxl_render.c
src/world/pxl8_entity.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.c
src/world/pxl8_world_chunk.c
src/world/pxl8_world_chunk_cache.c
" "
LUAJIT_LIB="lib/luajit/src/libluajit.a" LUAJIT_LIB="lib/luajit/src/libluajit.a"

View file

@ -5,21 +5,51 @@ fn main() {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let pxl8_src = PathBuf::from(&manifest_dir).join("../src"); 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.c");
println!("cargo:rerun-if-changed=../src/core/pxl8_log.h"); 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/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/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() cc::Build::new()
.file(pxl8_src.join("core/pxl8_log.c")) .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")) .include(pxl8_src.join("core"))
.define("PXL8_SERVER", None) .include(pxl8_src.join("hal"))
.compile("pxl8_log"); .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() let bindings = bindgen::Builder::default()
.header(pxl8_src.join("core/pxl8_log.h").to_str().unwrap()) .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("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() .use_core()
.rustified_enum(".*") .rustified_enum(".*")
.generate() .generate()
@ -27,6 +57,6 @@ fn main() {
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings bindings
.write_to_file(out_path.join("protocol.rs")) .write_to_file(out_path.join("pxl8.rs"))
.expect("Couldn't write bindings"); .expect("Couldn't write bindings");
} }

View file

@ -1,173 +1,216 @@
extern crate alloc; extern crate alloc;
use alloc::boxed::Box;
use alloc::vec::Vec; use alloc::vec::Vec;
use crate::math::Vec3; use crate::math::{Vec3, VEC3_ZERO};
use crate::pxl8::*;
#[derive(Clone, Copy, Default)] pub type Vertex = pxl8_bsp_vertex;
pub struct BspVertex { pub type Edge = pxl8_bsp_edge;
pub position: Vec3, 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)] impl Default for Face {
pub struct BspEdge { fn default() -> Self {
pub vertex: [u16; 2], 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)] impl Default for Leaf {
pub struct BspFace { fn default() -> Self {
pub first_edge: u32, Self {
pub lightmap_offset: u32, ambient_level: [0; 4],
pub num_edges: u16, contents: 0,
pub plane_id: u16, first_marksurface: 0,
pub side: u16, maxs: [0; 3],
pub styles: [u8; 4], mins: [0; 3],
pub material_id: u16, num_marksurfaces: 0,
pub aabb_min: Vec3, visofs: -1,
pub aabb_max: Vec3, }
}
} }
#[derive(Clone, Copy, Default)] impl Default for Node {
pub struct BspPlane { fn default() -> Self {
pub normal: Vec3, Self {
pub dist: f32, children: [0, 0],
pub plane_type: i32, first_face: 0,
maxs: [0; 3],
mins: [0; 3],
num_faces: 0,
plane_id: 0,
}
}
} }
#[derive(Clone, Copy, Default)] impl Default for Plane {
pub struct BspNode { fn default() -> Self {
pub children: [i32; 2], Self {
pub first_face: u16, dist: 0.0,
pub maxs: [i16; 3], normal: VEC3_ZERO,
pub mins: [i16; 3], type_: 0,
pub num_faces: u16, }
pub plane_id: u32, }
} }
#[derive(Clone, Copy, Default)] impl Default for Vertex {
pub struct BspLeaf { fn default() -> Self {
pub ambient_level: [u8; 4], Self { position: VEC3_ZERO }
pub contents: i32, }
pub first_marksurface: u16,
pub maxs: [i16; 3],
pub mins: [i16; 3],
pub num_marksurfaces: u16,
pub visofs: i32,
} }
#[derive(Clone, Copy, Default)] impl Default for Portal {
pub struct BspPortal { fn default() -> Self {
pub x0: f32, Self {
pub z0: f32, x0: 0.0,
pub x1: f32, z0: 0.0,
pub z1: f32, x1: 0.0,
pub target_leaf: u32, z1: 0.0,
target_leaf: 0,
}
}
} }
#[derive(Clone, Default)] impl Default for CellPortals {
pub struct BspCellPortals { fn default() -> Self {
pub portals: [BspPortal; 4], Self {
pub num_portals: u8, portals: [Portal::default(); 4],
num_portals: 0,
}
}
} }
pub struct Bsp { pub struct Bsp {
pub vertices: Vec<BspVertex>, inner: pxl8_bsp,
pub edges: Vec<BspEdge>, pub cell_portals: Box<[CellPortals]>,
pub surfedges: Vec<i32>, pub edges: Box<[Edge]>,
pub planes: Vec<BspPlane>, pub faces: Box<[Face]>,
pub faces: Vec<BspFace>, pub leafs: Box<[Leaf]>,
pub nodes: Vec<BspNode>, pub marksurfaces: Box<[u16]>,
pub leafs: Vec<BspLeaf>, 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<CellPortals>,
pub edges: Vec<Edge>,
pub faces: Vec<Face>,
pub leafs: Vec<Leaf>,
pub marksurfaces: Vec<u16>, pub marksurfaces: Vec<u16>,
pub cell_portals: Vec<BspCellPortals>, pub nodes: Vec<Node>,
pub visdata: Vec<u8>, pub planes: Vec<Plane>,
pub surfedges: Vec<i32>,
pub vertex_lights: Vec<u32>, pub vertex_lights: Vec<u32>,
pub vertices: Vec<Vertex>,
pub visdata: Vec<u8>,
}
impl BspBuilder {
pub fn new() -> Self {
Self::default()
}
}
impl From<BspBuilder> 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 { impl Bsp {
pub fn new() -> Self { pub fn as_c_bsp(&self) -> &pxl8_bsp {
Self { &self.inner
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 trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 { pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
if self.nodes.is_empty() { unsafe { pxl8_bsp_trace(&self.inner, from, to, radius) }
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
}
} }
} }
impl Default for Bsp { impl Default for Bsp {
fn default() -> Self { fn default() -> Self {
Self::new() BspBuilder::new().into()
} }
} }

View file

@ -4,6 +4,7 @@ pub mod stream;
use crate::bsp::Bsp; use crate::bsp::Bsp;
use crate::math::Vec3; use crate::math::Vec3;
use crate::pxl8::pxl8_vxl_trace;
use crate::voxel::VoxelChunk; use crate::voxel::VoxelChunk;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] #[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 { pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
match self { match self {
Chunk::Bsp { bsp, .. } => bsp.trace(from, to, radius), 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)
},
} }
} }

View file

@ -77,6 +77,18 @@ impl ClientChunkState {
self.pending.clear(); self.pending.clear();
self.pending_messages.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 { impl Default for ClientChunkState {

View file

@ -24,8 +24,8 @@ fn panic(_info: &PanicInfo) -> ! {
} }
#[allow(dead_code, non_camel_case_types, non_snake_case, non_upper_case_globals)] #[allow(dead_code, non_camel_case_types, non_snake_case, non_upper_case_globals)]
pub mod protocol { pub mod pxl8 {
include!(concat!(env!("OUT_DIR"), "/protocol.rs")); include!(concat!(env!("OUT_DIR"), "/pxl8.rs"));
} }
pub use bsp::*; pub use bsp::*;
@ -33,7 +33,7 @@ pub use chunk::*;
pub use chunk::stream::*; pub use chunk::stream::*;
pub use math::*; pub use math::*;
pub use procgen::{ProcgenParams, generate, generate_rooms}; pub use procgen::{ProcgenParams, generate, generate_rooms};
pub use protocol::*; pub use pxl8::*;
pub use sim::*; pub use sim::*;
pub use transport::*; pub use transport::*;
pub use voxel::*; pub use voxel::*;

View file

@ -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; use core::ffi::c_char;
static mut G_LOG: pxl8_log = pxl8_log { static mut G_LOG: pxl8_log = pxl8_log {
handler: None, 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() { pub fn init() {
@ -20,7 +20,7 @@ macro_rules! pxl8_debug {
let _ = ::core::write!(&mut buf, $($arg)*); let _ = ::core::write!(&mut buf, $($arg)*);
buf.push(0); buf.push(0);
unsafe { unsafe {
$crate::protocol::pxl8_log_write_debug( $crate::pxl8::pxl8_log_write_debug(
concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char, concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char,
line!() as ::core::ffi::c_int, line!() as ::core::ffi::c_int,
c"%s".as_ptr() as *const ::core::ffi::c_char, c"%s".as_ptr() as *const ::core::ffi::c_char,
@ -38,7 +38,7 @@ macro_rules! pxl8_error {
let _ = ::core::write!(&mut buf, $($arg)*); let _ = ::core::write!(&mut buf, $($arg)*);
buf.push(0); buf.push(0);
unsafe { unsafe {
$crate::protocol::pxl8_log_write_error( $crate::pxl8::pxl8_log_write_error(
concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char, concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char,
line!() as ::core::ffi::c_int, line!() as ::core::ffi::c_int,
c"%s".as_ptr() as *const ::core::ffi::c_char, c"%s".as_ptr() as *const ::core::ffi::c_char,
@ -56,7 +56,7 @@ macro_rules! pxl8_info {
let _ = ::core::write!(&mut buf, $($arg)*); let _ = ::core::write!(&mut buf, $($arg)*);
buf.push(0); buf.push(0);
unsafe { unsafe {
$crate::protocol::pxl8_log_write_info( $crate::pxl8::pxl8_log_write_info(
c"%s".as_ptr() as *const ::core::ffi::c_char, c"%s".as_ptr() as *const ::core::ffi::c_char,
buf.as_ptr(), buf.as_ptr(),
); );
@ -72,7 +72,7 @@ macro_rules! pxl8_trace {
let _ = ::core::write!(&mut buf, $($arg)*); let _ = ::core::write!(&mut buf, $($arg)*);
buf.push(0); buf.push(0);
unsafe { unsafe {
$crate::protocol::pxl8_log_write_trace( $crate::pxl8::pxl8_log_write_trace(
concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char, concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char,
line!() as ::core::ffi::c_int, line!() as ::core::ffi::c_int,
c"%s".as_ptr() as *const ::core::ffi::c_char, c"%s".as_ptr() as *const ::core::ffi::c_char,
@ -90,7 +90,7 @@ macro_rules! pxl8_warn {
let _ = ::core::write!(&mut buf, $($arg)*); let _ = ::core::write!(&mut buf, $($arg)*);
buf.push(0); buf.push(0);
unsafe { unsafe {
$crate::protocol::pxl8_log_write_warn( $crate::pxl8::pxl8_log_write_warn(
concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char, concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char,
line!() as ::core::ffi::c_int, line!() as ::core::ffi::c_int,
c"%s".as_ptr() as *const ::core::ffi::c_char, c"%s".as_ptr() as *const ::core::ffi::c_char,

View file

@ -6,6 +6,7 @@ extern crate alloc;
use pxl8d::*; use pxl8d::*;
use pxl8d::chunk::ChunkId; use pxl8d::chunk::ChunkId;
use pxl8d::chunk::stream::ClientChunkState; use pxl8d::chunk::stream::ClientChunkState;
use pxl8d::math::Vec3;
const TICK_RATE: u64 = 30; const TICK_RATE: u64 = 30;
const TICK_NS: u64 = 1_000_000_000 / TICK_RATE; 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<u64> = None; let mut player_id: Option<u64> = None;
let mut last_client_tick: u64 = 0; let mut last_client_tick: u64 = 0;
let mut client_chunks = ClientChunkState::new(); let mut client_chunks = ClientChunkState::new();
let mut client_stream_radius: i32 = 3;
let mut sequence: u32 = 0; let mut sequence: u32 = 0;
let mut last_tick = get_time_ns(); 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, entity_id: 0,
userdata: [0u8; 56], userdata: [0u8; 56],
}; 64]; }; 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"); pxl8_debug!("[SERVER] Entering main loop");
loop { loop {
@ -91,15 +93,15 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
last_tick = now; last_tick = now;
let dt = (elapsed as f32) / 1_000_000_000.0; let dt = (elapsed as f32) / 1_000_000_000.0;
let mut latest_input: Option<protocol::pxl8_input_msg> = None; let mut latest_input: Option<pxl8d::pxl8_input_msg> = None;
while let Some(msg_type) = transport.recv() { while let Some(msg_type) = transport.recv() {
match msg_type { 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()); 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(); 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); let (x, y, z, _yaw, _pitch) = extract_spawn_position(&cmd.payload);
player_id = Some(sim.spawn_player(x, y, z) as u64); 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"); pxl8_debug!("[SERVER] Sending CHUNK_ENTER for BSP id=1");
transport.send_chunk_enter(1, transport::CHUNK_TYPE_BSP, sequence); transport.send_chunk_enter(1, transport::CHUNK_TYPE_BSP, sequence);
sequence = sequence.wrapping_add(1); 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, entity_count: count as u16,
event_count: 0, event_count: 0,
player_id: player_id.unwrap_or(0), player_id: player_id.unwrap_or(0),
@ -147,15 +189,15 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
if let Some(player) = sim.get_player_position(pid) { if let Some(player) = sim.get_player_position(pid) {
let pos = Vec3 { x: player.0, y: player.1, z: player.2 }; let pos = Vec3 { x: player.0, y: player.1, z: player.2 };
if sim.world.active().is_some() { 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() { while let Some(chunk_id) = client_chunks.next_pending() {
match chunk_id { match chunk_id {
ChunkId::Bsp(id) => { ChunkId::Bsp(id) => {
pxl8_debug!("[SERVER] Processing pending BSP chunk");
if let Some(chunk) = sim.world.get(&chunk_id) { if let Some(chunk) = sim.world.get(&chunk_id) {
if let Some(bsp) = chunk.as_bsp() { if let Some(bsp) = chunk.as_bsp() {
let msgs = bsp_to_messages(bsp, id, chunk.version()); let msgs = bsp_to_messages(bsp, id, chunk.version());
pxl8_debug!("[SERVER] BSP serialized, queueing messages");
client_chunks.queue_messages(msgs); client_chunks.queue_messages(msgs);
client_chunks.mark_sent(chunk_id, chunk.version()); client_chunks.mark_sent(chunk_id, chunk.version());
} }
@ -171,30 +213,12 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
} }
} }
for _ in 0..4 { for _ in 0..8 {
if let Some(msg) = client_chunks.next_message() { if let Some(msg) = client_chunks.next_message() {
transport.send_chunk(&msg, sequence); transport.send_chunk(&msg, sequence);
sequence = sequence.wrapping_add(1); 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);
}
}
}
}
}
} }
} }
} }
@ -250,7 +274,7 @@ fn bsp_to_messages(bsp: &bsp::Bsp, id: u32, version: u32) -> alloc::vec::Vec<tra
data.extend_from_slice(&p.normal.y.to_be_bytes()); data.extend_from_slice(&p.normal.y.to_be_bytes());
data.extend_from_slice(&p.normal.z.to_be_bytes()); data.extend_from_slice(&p.normal.z.to_be_bytes());
data.extend_from_slice(&p.dist.to_be_bytes()); data.extend_from_slice(&p.dist.to_be_bytes());
data.extend_from_slice(&p.plane_type.to_be_bytes()); data.extend_from_slice(&p.type_.to_be_bytes());
} }
for f in &bsp.faces { for f in &bsp.faces {

View file

@ -1,42 +1,62 @@
use core::ops::{Add, Mul, Sub}; use core::ops::{Add, Mul, Sub};
#[derive(Clone, Copy, Default)] use crate::pxl8::pxl8_vec3;
pub struct Vec3 {
pub x: f32, pub type Vec3 = pxl8_vec3;
pub y: f32,
pub z: f32, pub const VEC3_ZERO: Vec3 = Vec3 { x: 0.0, y: 0.0, z: 0.0 };
pub const VEC3_Y: Vec3 = Vec3 { x: 0.0, y: 1.0, z: 0.0 };
pub trait Vec3Ext {
fn new(x: f32, y: f32, z: f32) -> Self;
fn dot(self, rhs: Self) -> f32;
} }
impl Vec3 { impl Vec3Ext for pxl8_vec3 {
pub const ZERO: Self = Self { x: 0.0, y: 0.0, z: 0.0 }; fn new(x: f32, y: f32, z: f32) -> Self {
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 {
Self { x, y, z } 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 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; type Output = Self;
fn add(self, rhs: Self) -> 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; type Output = Self;
fn sub(self, rhs: Self) -> 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<f32> for Vec3 { impl Mul<f32> for pxl8_vec3 {
type Output = Self; type Output = Self;
fn mul(self, rhs: f32) -> 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,
}
} }
} }

View file

@ -4,11 +4,12 @@ use alloc::vec;
use alloc::vec::Vec; use alloc::vec::Vec;
use libm::sqrtf; use libm::sqrtf;
use crate::bsp::{Bsp, BspVertex, BspEdge, BspFace, BspPlane, BspNode, BspLeaf, BspPortal, BspCellPortals}; use crate::bsp::{Bsp, BspBuilder, CellPortals, Edge, Face, Leaf, Node, Plane, Portal, Vertex};
use crate::math::Vec3; use crate::math::{Vec3, Vec3Ext};
pub const CELL_SIZE: f32 = 64.0; pub const CELL_SIZE: f32 = 64.0;
pub const WALL_HEIGHT: f32 = 128.0; pub const WALL_HEIGHT: f32 = 128.0;
pub const TRIM_HEIGHT: f32 = 12.0;
pub const PVS_MAX_DEPTH: u32 = 64; pub const PVS_MAX_DEPTH: u32 = 64;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@ -107,7 +108,7 @@ impl Bounds {
} }
struct BspBuildContext<'a> { struct BspBuildContext<'a> {
bsp: &'a mut Bsp, bsp: &'a mut BspBuilder,
grid: &'a RoomGrid, grid: &'a RoomGrid,
node_count: u32, node_count: u32,
plane_offset: 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_min = Vec3::new(1e30, 1e30, 1e30);
face.aabb_max = 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 { if x1 - x0 == 1 && y1 - y0 == 1 {
let leaf_idx = y0 * ctx.grid.width + x0; let leaf_idx = y0 * ctx.grid.width + x0;
return -(leaf_idx + 1); 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 mid_x = (x0 + x1) / 2;
let split_pos = mid_x as f32 * CELL_SIZE; 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), normal: Vec3::new(1.0, 0.0, 0.0),
dist: split_pos, dist: split_pos,
plane_type: 0, type_: 0,
}; };
let child0 = build_bsp_node(ctx, mid_x, y0, x1, y1, depth + 1); let child0 = build_bsp_node_grid(ctx, mid_x, y0, x1, y1, depth + 1);
let child1 = build_bsp_node(ctx, x0, y0, mid_x, 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, plane_id: plane_idx,
children: [child0, child1], children: [child0, child1],
..Default::default() ..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 mid_y = (y0 + y1) / 2;
let split_pos = mid_y as f32 * CELL_SIZE; 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), normal: Vec3::new(0.0, 0.0, 1.0),
dist: split_pos, dist: split_pos,
plane_type: 0, type_: 0,
}; };
let child0 = build_bsp_node(ctx, x0, mid_y, x1, y1, depth + 1); let child0 = build_bsp_node_grid(ctx, x0, mid_y, x1, y1, depth + 1);
let child1 = build_bsp_node(ctx, x0, y0, x1, mid_y, 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, plane_id: plane_idx,
children: [child0, child1], children: [child0, child1],
..Default::default() ..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 node_idx as i32
} }
fn build_cell_portals(grid: &RoomGrid) -> Vec<BspCellPortals> { 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<CellPortals> {
let total_cells = (grid.width * grid.height) as usize; 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 y in 0..grid.height {
for x in 0..grid.width { for x in 0..grid.width {
@ -218,7 +243,7 @@ fn build_cell_portals(grid: &RoomGrid) -> Vec<BspCellPortals> {
if x > 0 && grid.get(x - 1, y) == 0 { if x > 0 && grid.get(x - 1, y) == 0 {
let p = &mut portals[c]; let p = &mut portals[c];
let idx = p.num_portals as usize; let idx = p.num_portals as usize;
p.portals[idx] = BspPortal { p.portals[idx] = Portal {
x0: cx, x0: cx,
z0: cz, z0: cz,
x1: cx, x1: cx,
@ -230,7 +255,7 @@ fn build_cell_portals(grid: &RoomGrid) -> Vec<BspCellPortals> {
if x < grid.width - 1 && grid.get(x + 1, y) == 0 { if x < grid.width - 1 && grid.get(x + 1, y) == 0 {
let p = &mut portals[c]; let p = &mut portals[c];
let idx = p.num_portals as usize; let idx = p.num_portals as usize;
p.portals[idx] = BspPortal { p.portals[idx] = Portal {
x0: cx + CELL_SIZE, x0: cx + CELL_SIZE,
z0: cz + CELL_SIZE, z0: cz + CELL_SIZE,
x1: cx + CELL_SIZE, x1: cx + CELL_SIZE,
@ -242,7 +267,7 @@ fn build_cell_portals(grid: &RoomGrid) -> Vec<BspCellPortals> {
if y > 0 && grid.get(x, y - 1) == 0 { if y > 0 && grid.get(x, y - 1) == 0 {
let p = &mut portals[c]; let p = &mut portals[c];
let idx = p.num_portals as usize; let idx = p.num_portals as usize;
p.portals[idx] = BspPortal { p.portals[idx] = Portal {
x0: cx + CELL_SIZE, x0: cx + CELL_SIZE,
z0: cz, z0: cz,
x1: cx, x1: cx,
@ -254,7 +279,7 @@ fn build_cell_portals(grid: &RoomGrid) -> Vec<BspCellPortals> {
if y < grid.height - 1 && grid.get(x, y + 1) == 0 { if y < grid.height - 1 && grid.get(x, y + 1) == 0 {
let p = &mut portals[c]; let p = &mut portals[c];
let idx = p.num_portals as usize; let idx = p.num_portals as usize;
p.portals[idx] = BspPortal { p.portals[idx] = Portal {
x0: cx, x0: cx,
z0: cz + CELL_SIZE, z0: cz + CELL_SIZE,
x1: cx + CELL_SIZE, x1: cx + CELL_SIZE,
@ -277,8 +302,8 @@ struct FloodEntry {
fn portal_flood_bfs( fn portal_flood_bfs(
start_leaf: u32, start_leaf: u32,
portals: &[BspCellPortals], portals: &[CellPortals],
leafs: &[BspLeaf], leafs: &[Leaf],
pvs: &mut [u8], pvs: &mut [u8],
num_leafs: u32, num_leafs: u32,
) { ) {
@ -325,8 +350,8 @@ fn portal_flood_bfs(
fn compute_leaf_pvs( fn compute_leaf_pvs(
start_leaf: u32, start_leaf: u32,
portals: &[BspCellPortals], portals: &[CellPortals],
leafs: &[BspLeaf], leafs: &[Leaf],
num_leafs: u32, num_leafs: u32,
) -> Vec<u8> { ) -> Vec<u8> {
let pvs_bytes = ((num_leafs + 7) / 8) as usize; let pvs_bytes = ((num_leafs + 7) / 8) as usize;
@ -357,7 +382,7 @@ fn rle_compress_pvs(pvs: &[u8]) -> Vec<u8> {
out 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 num_leafs = bsp.leafs.len() as u32;
let mut visdata = Vec::new(); let mut visdata = Vec::new();
@ -417,7 +442,7 @@ fn compute_vertex_light(
total.min(1.0) 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() { if bsp.vertices.is_empty() {
return; 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) { fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) {
let mut face_count = 0; let mut wall_count = 0;
let mut floor_ceiling_count = 0; let mut floor_ceiling_count = 0;
for y in 0..grid.height { for y in 0..grid.height {
for x in 0..grid.width { for x in 0..grid.width {
if grid.get(x, y) == 0 { if grid.get(x, y) == 0 {
if grid.get(x - 1, y) == 1 { face_count += 1; } if grid.get(x - 1, y) == 1 { wall_count += 1; }
if grid.get(x + 1, y) == 1 { face_count += 1; } if grid.get(x + 1, y) == 1 { wall_count += 1; }
if grid.get(x, y - 1) == 1 { face_count += 1; } if grid.get(x, y - 1) == 1 { wall_count += 1; }
if grid.get(x, y + 1) == 1 { face_count += 1; } if grid.get(x, y + 1) == 1 { wall_count += 1; }
floor_ceiling_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 vertex_count = face_count * 4;
let total_cells = (grid.width * grid.height) as usize; let total_cells = (grid.width * grid.height) as usize;
let max_nodes = 2 * total_cells; let max_nodes = 2 * total_cells + 1;
let total_planes = face_count + max_nodes; let total_planes = face_count + max_nodes + 1;
bsp.vertices = vec![BspVertex::default(); vertex_count]; bsp.vertices = vec![Vertex::default(); vertex_count];
bsp.faces = vec![BspFace::default(); face_count]; bsp.faces = vec![Face::default(); face_count];
bsp.planes = vec![BspPlane::default(); total_planes]; bsp.planes = vec![Plane::default(); total_planes];
bsp.edges = vec![BspEdge::default(); vertex_count]; bsp.edges = vec![Edge::default(); vertex_count];
bsp.surfedges = vec![0i32; 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]; 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; let cell_idx = (y * grid.width + x) as u32;
if grid.get(x - 1, y) == 1 { 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 + 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 + 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.vertices[vert_idx + 3].position = Vec3::new(fx, 0.0, fy + CELL_SIZE);
bsp.planes[face_idx].normal = Vec3::new(1.0, 0.0, 0.0); bsp.planes[face_idx].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].plane_id = face_idx as u16;
bsp.faces[face_idx].num_edges = 4; bsp.faces[face_idx].num_edges = 4;
bsp.faces[face_idx].first_edge = edge_idx as u32; 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 { for i in 0..4 {
bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; 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 { 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 + 0].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy);
bsp.vertices[vert_idx + 1].position = Vec3::new(fx + CELL_SIZE, 0.0, fy + CELL_SIZE); 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 + 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); 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].plane_id = face_idx as u16;
bsp.faces[face_idx].num_edges = 4; bsp.faces[face_idx].num_edges = 4;
bsp.faces[face_idx].first_edge = edge_idx as u32; 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 { for i in 0..4 {
bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; 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 { if grid.get(x, y - 1) == 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 + CELL_SIZE, 0.0, 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 + 2].position = Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fy);
bsp.vertices[vert_idx + 3].position = Vec3::new(fx, 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].plane_id = face_idx as u16;
bsp.faces[face_idx].num_edges = 4; bsp.faces[face_idx].num_edges = 4;
bsp.faces[face_idx].first_edge = edge_idx as u32; 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 { for i in 0..4 {
bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; 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 { 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 + 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 + 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.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); 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].plane_id = face_idx as u16;
bsp.faces[face_idx].num_edges = 4; bsp.faces[face_idx].num_edges = 4;
bsp.faces[face_idx].first_edge = edge_idx as u32; 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 { for i in 0..4 {
bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; 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.edges.truncate(edge_idx);
bsp.surfedges.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]; bsp.marksurfaces = vec![0u16; face_idx];
let mut faces_per_cell = vec![0u32; total_cells]; 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 face_count_final = face_idx;
let mut ctx = BspBuildContext { let mut ctx = BspBuildContext {
bsp, bsp,
@ -720,7 +860,7 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) {
plane_offset: face_count_final as u32, 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 node_count = ctx.node_count as usize;
let plane_count = ctx.plane_offset 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); grid_to_bsp(&mut bsp, &grid);
let light_height = 80.0; 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); compute_bsp_vertex_lighting(&mut bsp, &lights, 0.1);
bsp bsp.into()
} }
pub fn generate(params: &ProcgenParams) -> Bsp { pub fn generate(params: &ProcgenParams) -> Bsp {

View file

@ -1,38 +1,29 @@
extern crate alloc; extern crate alloc;
use alloc::vec::Vec; use alloc::vec::Vec;
use libm::{cosf, sinf, sqrtf};
use crate::math::Vec3; use crate::math::{Vec3, Vec3Ext, VEC3_ZERO};
use crate::protocol::*; use crate::pxl8::*;
use crate::voxel::VoxelWorld; use crate::voxel::VoxelWorld;
use crate::world::World; use crate::world::World;
const ALIVE: u32 = 1 << 0; pub type Entity = pxl8_sim_entity;
const PLAYER: u32 = 1 << 1;
const GROUNDED: u32 = 1 << 2; const ALIVE: u32 = PXL8_SIM_FLAG_ALIVE;
const PLAYER: u32 = PXL8_SIM_FLAG_PLAYER;
const MAX_ENTITIES: usize = 1024; 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 { impl Default for Entity {
fn default() -> Self { fn default() -> Self {
Self { Self {
flags: 0, pos: VEC3_ZERO,
kind: 0, vel: VEC3_ZERO,
pos: Vec3::ZERO,
vel: Vec3::ZERO,
yaw: 0.0, yaw: 0.0,
pitch: 0.0, pitch: 0.0,
flags: 0,
kind: 0,
_pad: 0,
} }
} }
} }
@ -96,6 +87,13 @@ impl Simulation {
id 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) { pub fn step(&mut self, inputs: &[pxl8_input_msg], dt: f32) {
self.tick += 1; self.tick += 1;
self.time += dt; self.time += dt;
@ -107,120 +105,68 @@ impl Simulation {
self.integrate(dt); self.integrate(dt);
} }
fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 { fn make_sim_world(&self, pos: Vec3) -> pxl8_sim_world {
if self.world.active().is_some() { if let Some(chunk) = self.world.active() {
return self.world.trace(from, to, radius); 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) { 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() { for i in 0..self.entities.len() {
let ent = &self.entities[i]; let ent = &self.entities[i];
if ent.flags & ALIVE == 0 || ent.flags & PLAYER != 0 { if ent.flags & ALIVE == 0 || ent.flags & PLAYER != 0 {
continue; continue;
} }
let mut vel = ent.vel; let world = self.make_sim_world(ent.pos);
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 ent = &mut self.entities[i]; let ent = &mut self.entities[i];
ent.vel = vel; unsafe {
ent.pos = new_pos; pxl8_sim_integrate(ent, &world, dt);
}
} }
} }
fn move_player(&mut self, input: &pxl8_input_msg, dt: f32) { fn move_player(&mut self, input: &pxl8_input_msg, dt: f32) {
let Some(id) = self.player else { return }; 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 { if ent.flags & ALIVE == 0 {
return; return;
} }
const MOVE_SPEED: f32 = 320.0; let world = self.make_sim_world(ent.pos);
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 ent = &mut self.entities[id as usize]; let ent = &mut self.entities[id as usize];
ent.pos = new_pos; unsafe {
pxl8_sim_move_player(ent, input, &world, dt);
if ground_check.y < new_pos.y - 1.0 {
ent.flags &= !GROUNDED;
} else {
ent.flags |= GROUNDED;
} }
} }

View file

@ -2,8 +2,8 @@ extern crate alloc;
use alloc::vec; use alloc::vec;
use alloc::vec::Vec; use alloc::vec::Vec;
use crate::protocol::*; use crate::pxl8::*;
use crate::protocol::pxl8_msg_type::*; use crate::pxl8::pxl8_msg_type::*;
use crate::voxel::VoxelChunk; use crate::voxel::VoxelChunk;
pub const DEFAULT_PORT: u16 = 7777; pub const DEFAULT_PORT: u16 = 7777;
@ -374,6 +374,24 @@ impl Transport {
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr); 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 { fn serialize_chunk_msg_header(&mut self, msg: &ChunkMessage, offset: usize) -> usize {
let buf = &mut self.send_buf[offset..]; let buf = &mut self.send_buf[offset..];
buf[0] = msg.chunk_type; buf[0] = msg.chunk_type;

View file

@ -1,21 +1,22 @@
extern crate alloc; extern crate alloc;
use alloc::vec;
use alloc::vec::Vec; use alloc::vec::Vec;
use libm::floorf; use core::ptr;
use crate::math::Vec3; use crate::math::Vec3;
use crate::pxl8::*;
pub const CHUNK_SIZE: usize = 32; const CHUNK_SIZE: i32 = PXL8_VXL_CHUNK_SIZE as i32;
pub const CHUNK_VOLUME: usize = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE; 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 struct VoxelChunk {
pub blocks: [u8; CHUNK_VOLUME], pub chunk: *mut pxl8_vxl_chunk,
pub cx: i32, pub cx: i32,
pub cy: i32, pub cy: i32,
pub cz: i32, pub cz: i32,
@ -23,79 +24,57 @@ pub struct VoxelChunk {
impl VoxelChunk { impl VoxelChunk {
pub fn new(cx: i32, cy: i32, cz: i32) -> Self { pub fn new(cx: i32, cy: i32, cz: i32) -> Self {
Self { let chunk = unsafe { pxl8_vxl_chunk_create() };
blocks: [AIR; CHUNK_VOLUME], Self { chunk, cx, cy, cz }
cx,
cy,
cz,
}
} }
pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 { pub fn get(&self, x: i32, y: i32, z: i32) -> u8 {
if !self.is_solid_radius(to.x, to.y, to.z, radius) { if self.chunk.is_null() {
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 {
return AIR; 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) { pub fn set(&mut self, x: i32, y: i32, z: i32, block: u8) {
if x < CHUNK_SIZE && y < CHUNK_SIZE && z < CHUNK_SIZE { if self.chunk.is_null() {
self.blocks[Self::index(x, y, z)] = block; 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<u8> { pub fn rle_encode(&self) -> Vec<u8> {
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 result = Vec::new();
let mut i = 0; let mut i = 0;
while i < CHUNK_VOLUME { while i < CHUNK_VOLUME {
let block = self.blocks[i]; let block = linear[i];
let mut run_len = 1usize; let mut run_len = 1usize;
while i + run_len < CHUNK_VOLUME while i + run_len < CHUNK_VOLUME
&& self.blocks[i + run_len] == block && linear[i + run_len] == block
&& run_len < 256 && run_len < 256
{ {
run_len += 1; 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 struct VoxelWorld {
pub chunks: Vec<VoxelChunk>, pub chunks: Vec<VoxelChunk>,
pub seed: u64, pub seed: u64,
@ -141,61 +148,7 @@ impl VoxelWorld {
} }
pub fn world_to_chunk(x: f32) -> i32 { pub fn world_to_chunk(x: f32) -> i32 {
floorf(x / CHUNK_SIZE as f32) as i32 libm::floorf(x / WORLD_CHUNK_SIZE) 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
} }
pub fn chunks_near(&self, pos: Vec3, radius: i32) -> Vec<(i32, i32, 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); 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 { fn hash(mut x: u64) -> u64 {
@ -231,11 +215,6 @@ fn hash(mut x: u64) -> u64 {
x 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 { fn smoothstep(t: f32) -> f32 {
t * t * (3.0 - 2.0 * t) t * t * (3.0 - 2.0 * t)
} }
@ -244,33 +223,84 @@ fn lerp(a: f32, b: f32, t: f32) -> f32 {
a + (b - a) * t a + (b - a) * t
} }
fn value_noise(x: f32, z: f32, seed: u64) -> f32 { fn value_noise_3d(x: f32, y: f32, z: f32, seed: u64) -> f32 {
let x0 = floorf(x) as i32; let x0 = libm::floorf(x) as i32;
let z0 = floorf(z) as i32; let y0 = libm::floorf(y) as i32;
let z0 = libm::floorf(z) as i32;
let x1 = x0 + 1; let x1 = x0 + 1;
let y1 = y0 + 1;
let z1 = z0 + 1; let z1 = z0 + 1;
let tx = smoothstep(x - x0 as f32); let tx = smoothstep(x - x0 as f32);
let ty = smoothstep(y - y0 as f32);
let tz = smoothstep(z - z0 as f32); let tz = smoothstep(z - z0 as f32);
let c00 = noise2d(x0, z0, seed); let c000 = noise3d(x0, y0, z0, seed);
let c10 = noise2d(x1, z0, seed); let c100 = noise3d(x1, y0, z0, seed);
let c01 = noise2d(x0, z1, seed); let c010 = noise3d(x0, y1, z0, seed);
let c11 = noise2d(x1, z1, 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 a00 = lerp(c000, c100, tx);
let b = lerp(c01, c11, tx); let a10 = lerp(c010, c110, tx);
lerp(a, b, tz) 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 value = 0.0;
let mut amplitude = 1.0; let mut amplitude = 1.0;
let mut frequency = 1.0; let mut frequency = 1.0;
let mut max_value = 0.0; let mut max_value = 0.0;
for i in 0..octaves { 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; max_value += amplitude;
amplitude *= 0.5; amplitude *= 0.5;
frequency *= 2.0; 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) { fn generate_chunk(chunk: &mut VoxelChunk, seed: u64) {
let world_x = chunk.cx * CHUNK_SIZE as i32; let world_x = chunk.cx * CHUNK_SIZE;
let world_y = chunk.cy * CHUNK_SIZE as i32; let world_y = chunk.cy * CHUNK_SIZE;
let world_z = chunk.cz * CHUNK_SIZE as i32; let world_z = chunk.cz * CHUNK_SIZE;
for lz in 0..CHUNK_SIZE { let mut height_cache = [[0i32; 32]; 32];
for lx in 0..CHUNK_SIZE { for lz in 0..32 {
for lx in 0..32 {
let wx = (world_x + lx as i32) as f32; let wx = (world_x + lx as i32) as f32;
let wz = (world_z + lz 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 mut density = [[[0.0f32; 33]; 33]; 33];
let height = height as i32; 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;
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 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 ly in 0..CHUNK_SIZE {
let wy = world_y + ly as i32; for lx in 0..CHUNK_SIZE {
let d = density[lz as usize][ly as usize][lx as usize];
if d <= 0.0 {
continue;
}
let block = if wy > height { let d_above = density[lz as usize][(ly + 1) as usize][lx as usize];
AIR let is_surface = d_above <= 0.0;
} else if wy == height {
let block = if is_surface {
GRASS GRASS
} else if wy > height - 4 { } else {
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 DIRT
} else { } else {
STONE STONE
}
}; };
chunk.set(lx, ly, lz, block); chunk.set(lx, ly, lz, block);

View file

@ -38,10 +38,6 @@ impl World {
self.chunks.remove(id) self.chunks.remove(id)
} }
pub fn set_active(&mut self, id: ChunkId) {
self.active = Some(id);
}
pub fn active(&self) -> Option<&Chunk> { pub fn active(&self) -> Option<&Chunk> {
self.active.as_ref().and_then(|id| self.chunks.get(id)) self.active.as_ref().and_then(|id| self.chunks.get(id))
} }
@ -50,6 +46,14 @@ impl World {
self.active 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 { pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
if let Some(chunk) = self.active() { if let Some(chunk) = self.active() {
return chunk.trace(from, to, radius); return chunk.trace(from, to, radius);

View file

@ -7,11 +7,11 @@
#define MINIZ_NO_TIME #define MINIZ_NO_TIME
#define MINIZ_NO_ARCHIVE_APIS #define MINIZ_NO_ARCHIVE_APIS
#define MINIZ_NO_ARCHIVE_WRITING_APIS #define MINIZ_NO_ARCHIVE_WRITING_APIS
#define MINIZ_NO_DEFLATE_APIS
#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES #define MINIZ_NO_ZLIB_COMPATIBLE_NAMES
#include <miniz.h> #include <miniz.h>
#include "pxl8_color.h"
#include "pxl8_io.h" #include "pxl8_io.h"
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_mem.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)); 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;
}

View file

@ -141,12 +141,20 @@ typedef struct pxl8_ase_file {
pxl8_ase_tileset* tilesets; pxl8_ase_tileset* tilesets;
} pxl8_ase_file; } pxl8_ase_file;
typedef struct pxl8_ase_remap_config {
const u32* palette;
u32 palette_count;
f32 hue_tolerance;
} pxl8_ase_remap_config;
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file); 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); 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 #ifdef __cplusplus
} }

View file

@ -1,11 +1,8 @@
#include "pxl8_bsp.h" #include "pxl8_bsp.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include "pxl8_color.h" #include "pxl8_color.h"
#include "pxl8_gfx.h"
#include "pxl8_io.h" #include "pxl8_io.h"
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_mem.h" #include "pxl8_mem.h"
@ -41,15 +38,6 @@ typedef struct {
pxl8_bsp_chunk chunks[CHUNK_COUNT]; pxl8_bsp_chunk chunks[CHUNK_COUNT];
} pxl8_bsp_header; } 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) { static inline pxl8_vec3 read_vec3(pxl8_stream* stream) {
f32 x = pxl8_read_f32(stream); f32 x = pxl8_read_f32(stream);
f32 y = 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]; chunk = &header.chunks[CHUNK_FACES];
if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup; if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup;
bsp->num_faces = chunk->size / 20; bsp->num_faces = chunk->size / 20;
@ -364,12 +333,11 @@ void pxl8_bsp_destroy(pxl8_bsp* bsp) {
pxl8_free(bsp->faces); pxl8_free(bsp->faces);
pxl8_free(bsp->leafs); pxl8_free(bsp->leafs);
pxl8_free(bsp->lightdata); pxl8_free(bsp->lightdata);
pxl8_free(bsp->lightmaps);
pxl8_free(bsp->marksurfaces); pxl8_free(bsp->marksurfaces);
pxl8_free(bsp->materials);
pxl8_free(bsp->models); pxl8_free(bsp->models);
pxl8_free(bsp->nodes); pxl8_free(bsp->nodes);
pxl8_free(bsp->planes); pxl8_free(bsp->planes);
pxl8_free(bsp->render_face_flags);
pxl8_free(bsp->surfedges); pxl8_free(bsp->surfedges);
pxl8_free(bsp->vertex_lights); pxl8_free(bsp->vertex_lights);
pxl8_free(bsp->vertices); pxl8_free(bsp->vertices);
@ -394,47 +362,6 @@ i32 pxl8_bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) {
return -(node_id + 1); 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) { 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; 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}; 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) { u32 pxl8_bsp_face_count(const pxl8_bsp* bsp) {
if (!bsp) return 0; if (!bsp) return 0;
return bsp->num_faces; 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; if (!bsp || face_id >= bsp->num_faces) return;
bsp->faces[face_id].material_id = material_id; 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;
}
}

View file

@ -1,8 +1,6 @@
#pragma once #pragma once
#include "pxl8_gfx.h"
#include "pxl8_math.h" #include "pxl8_math.h"
#include "pxl8_mesh.h"
#include "pxl8_types.h" #include "pxl8_types.h"
typedef struct pxl8_bsp_edge { typedef struct pxl8_bsp_edge {
@ -105,11 +103,9 @@ typedef struct pxl8_bsp {
u8* lightdata; u8* lightdata;
pxl8_bsp_lightmap* lightmaps; pxl8_bsp_lightmap* lightmaps;
u16* marksurfaces; u16* marksurfaces;
pxl8_gfx_material* materials;
pxl8_bsp_model* models; pxl8_bsp_model* models;
pxl8_bsp_node* nodes; pxl8_bsp_node* nodes;
pxl8_bsp_plane* planes; pxl8_bsp_plane* planes;
u8* render_face_flags;
i32* surfedges; i32* surfedges;
u32* vertex_lights; u32* vertex_lights;
pxl8_bsp_vertex* vertices; pxl8_bsp_vertex* vertices;
@ -122,7 +118,6 @@ typedef struct pxl8_bsp {
u32 num_leafs; u32 num_leafs;
u32 num_lightmaps; u32 num_lightmaps;
u32 num_marksurfaces; u32 num_marksurfaces;
u32 num_materials;
u32 num_models; u32 num_models;
u32 num_nodes; u32 num_nodes;
u32 num_planes; 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); 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); 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_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_mapped(u8 width, u8 height, u32 offset);
pxl8_bsp_lightmap pxl8_bsp_lightmap_uniform(u8 r, u8 g, u8 b); pxl8_bsp_lightmap pxl8_bsp_lightmap_uniform(u8 r, u8 g, u8 b);
pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp); pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp);
void pxl8_bsp_pvs_destroy(pxl8_bsp_pvs* pvs); void pxl8_bsp_pvs_destroy(pxl8_bsp_pvs* pvs);
bool pxl8_bsp_pvs_is_visible(const pxl8_bsp_pvs* pvs, i32 leaf); 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); 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 #ifdef __cplusplus
} }

429
src/bsp/pxl8_bsp_render.c Normal file
View file

@ -0,0 +1,429 @@
#include "pxl8_bsp_render.h"
#include <string.h>
#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;
}
}

30
src/bsp/pxl8_bsp_render.h Normal file
View file

@ -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

View file

@ -9,6 +9,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include "pxl8_ase.h"
#include "pxl8_game.h" #include "pxl8_game.h"
#include "pxl8_hal.h" #include "pxl8_hal.h"
#include "pxl8_log.h" #include "pxl8_log.h"
@ -19,6 +20,7 @@
#include "pxl8_script.h" #include "pxl8_script.h"
#include "pxl8_sfx.h" #include "pxl8_sfx.h"
#include "pxl8_sys.h" #include "pxl8_sys.h"
#include "pxl8_world.h"
struct pxl8 { struct pxl8 {
pxl8_cart* cart; pxl8_cart* cart;
@ -83,6 +85,7 @@ static void pxl8_print_help(void) {
printf("Other commands:\n"); printf("Other commands:\n");
printf(" pxl8 pack <folder> <out.pxc> Pack folder into cart file\n"); printf(" pxl8 pack <folder> <out.pxc> Pack folder into cart file\n");
printf(" pxl8 bundle <in> <out> Bundle cart into executable\n"); printf(" pxl8 bundle <in> <out> Bundle cart into executable\n");
printf(" pxl8 remap-ase <in> <out> <palette> Remap ASE to palette by hue/lum\n");
printf(" pxl8 help Show this help\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; const char* script_arg = NULL;
bool bundle_mode = false; bool bundle_mode = false;
bool pack_mode = false; bool pack_mode = false;
bool remap_palette_mode = false;
bool run_mode = false; bool run_mode = false;
const char* pack_input = NULL; const char* pack_input = NULL;
const char* pack_output = NULL; const char* pack_output = NULL;
const char* remap_palette = NULL;
bool has_embedded = pxl8_cart_has_embedded(argv[0]); 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 <folder> <output.pxc>"); pxl8_error("pack requires <folder> <output.pxc>");
return PXL8_ERROR_INVALID_ARGUMENT; 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 <input.ase> <output.ase> <palette.ase>");
return PXL8_ERROR_INVALID_ARGUMENT;
}
} else if (!script_arg) { } else if (!script_arg) {
script_arg = argv[i]; script_arg = argv[i];
} }
} }
if (!run_mode && !bundle_mode && !pack_mode) { if (!run_mode && !bundle_mode && !pack_mode && !remap_palette_mode) {
run_mode = true; run_mode = true;
} }
@ -151,6 +166,22 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
return result; 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"); pxl8_info("Starting up");
game->script = pxl8_script_create(game->repl_mode); 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 }; pxl8_net_config net_cfg = { .address = "127.0.0.1", .port = 7777 };
game->net = pxl8_net_create(&net_cfg); game->net = pxl8_net_create(&net_cfg);
if (game->net) { 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); 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->last_time = sys->hal->get_ticks();
game->running = true; 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; 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) { if (game->net) {
while (pxl8_net_poll(game->net)) {} while (pxl8_net_poll(game->net)) {}
pxl8_net_update(game->net, dt); pxl8_net_update(game->net, dt);
pxl8_world_sync(game->world, game->net); 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_gfx_update(game->gfx, dt);
pxl8_sfx_mixer_process(game->mixer); pxl8_sfx_mixer_process(game->mixer);
@ -430,6 +485,15 @@ void pxl8_quit(pxl8* sys) {
pxl8_info("Shutting down"); 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) { if (sys->cart) {
pxl8_cart_unmount(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); sys->hal->set_relative_mouse_mode(sys->platform_data, enabled);
if (sys->game) { if (sys->game) {
sys->game->input.mouse_relative_mode = enabled; sys->game->input.mouse_relative_mode = enabled;
#ifdef PXL8_ASYNC_THREADS
pxl8_world_pause_sim(sys->game->world, !enabled);
#endif
} }
} }

53
src/core/pxl8_queue.h Normal file
View file

@ -0,0 +1,53 @@
#pragma once
#include <stdatomic.h>
#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);
}

View file

@ -64,6 +64,7 @@ typedef enum pxl8_result {
PXL8_ERROR_INVALID_COORDINATE, PXL8_ERROR_INVALID_COORDINATE,
PXL8_ERROR_INVALID_FORMAT, PXL8_ERROR_INVALID_FORMAT,
PXL8_ERROR_INVALID_SIZE, PXL8_ERROR_INVALID_SIZE,
PXL8_ERROR_NOT_CONNECTED,
PXL8_ERROR_NOT_INITIALIZED, PXL8_ERROR_NOT_INITIALIZED,
PXL8_ERROR_NULL_POINTER, PXL8_ERROR_NULL_POINTER,
PXL8_ERROR_OUT_OF_MEMORY, PXL8_ERROR_OUT_OF_MEMORY,

View file

@ -6,45 +6,6 @@ static inline i32 pxl8_bytes_per_pixel(pxl8_pixel_mode mode) {
return (i32)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) { static inline u32 pxl8_color_from_rgba(u32 rgba) {
u8 r = (rgba >> 24) & 0xFF; u8 r = (rgba >> 24) & 0xFF;
u8 g = (rgba >> 16) & 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); 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) { static inline u8 pxl8_rgb332_pack(u8 r, u8 g, u8 b) {
return ((r >> 5) << 5) | ((g >> 5) << 2) | (b >> 6); 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); *g = (gi << 5) | (gi << 2) | (gi >> 1);
*b = (bi << 6) | (bi << 4) | (bi << 2) | bi; *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;
}

File diff suppressed because it is too large Load diff

View file

@ -58,7 +58,9 @@ void pxl8_cpu_draw_mesh(
); );
u8* pxl8_cpu_get_framebuffer(pxl8_cpu_backend* cpu); 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); 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_height(const pxl8_cpu_backend* cpu);
u32 pxl8_cpu_get_width(const pxl8_cpu_backend* cpu); u32 pxl8_cpu_get_width(const pxl8_cpu_backend* cpu);

View file

@ -27,6 +27,7 @@ typedef struct pxl8_sprite_cache_entry {
struct pxl8_gfx { struct pxl8_gfx {
pxl8_atlas* atlas; pxl8_atlas* atlas;
pxl8_gfx_backend backend; pxl8_gfx_backend backend;
const pxl8_bsp* bsp;
pxl8_colormap* colormap; pxl8_colormap* colormap;
u8* framebuffer; u8* framebuffer;
i32 framebuffer_height; i32 framebuffer_height;
@ -38,6 +39,7 @@ struct pxl8_gfx {
pxl8_palette_cube* palette_cube; pxl8_palette_cube* palette_cube;
pxl8_pixel_mode pixel_mode; pxl8_pixel_mode pixel_mode;
void* platform_data; void* platform_data;
const pxl8_sdf* sdf;
pxl8_sprite_cache_entry* sprite_cache; pxl8_sprite_cache_entry* sprite_cache;
u32 sprite_cache_capacity; u32 sprite_cache_capacity;
u32 sprite_cache_count; 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) { u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx) {
if (!gfx || gfx->pixel_mode == PXL8_PIXEL_HICOLOR) return NULL; 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; return gfx->framebuffer;
} }
@ -73,6 +78,27 @@ i32 pxl8_gfx_get_height(const pxl8_gfx* gfx) {
return gfx ? gfx->framebuffer_height : 0; 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) { i32 pxl8_gfx_get_width(const pxl8_gfx* gfx) {
return gfx ? gfx->framebuffer_width : 0; 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; 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) { 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; if (!gfx || !camera) return;
pxl8_3d_frame frame = pxl8_3d_frame_from_camera(camera, uniforms); 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 = lights ? pxl8_lights_data(lights) : NULL;
frame.lights_count = lights ? pxl8_lights_count(lights) : 0; frame.lights_count = lights ? pxl8_lights_count(lights) : 0;
frame.sdf = gfx->sdf;
pxl8_mat4 vp = pxl8_mat4_multiply(frame.projection, frame.view); pxl8_mat4 vp = pxl8_mat4_multiply(frame.projection, frame.view);

View file

@ -32,6 +32,9 @@ pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx);
u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx); u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx);
u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx); u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx);
i32 pxl8_gfx_get_height(const 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_palette* pxl8_gfx_palette(pxl8_gfx* gfx);
pxl8_colormap* pxl8_gfx_colormap(pxl8_gfx* gfx); pxl8_colormap* pxl8_gfx_colormap(pxl8_gfx* gfx);
pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx); pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx);

View file

@ -6,6 +6,7 @@
#include "pxl8_mesh.h" #include "pxl8_mesh.h"
#include "pxl8_types.h" #include "pxl8_types.h"
typedef struct pxl8_bsp pxl8_bsp;
typedef struct pxl8_gfx pxl8_gfx; typedef struct pxl8_gfx pxl8_gfx;
typedef struct pxl8_3d_uniforms { typedef struct pxl8_3d_uniforms {
@ -17,7 +18,16 @@ typedef struct pxl8_3d_uniforms {
f32 time; f32 time;
} pxl8_3d_uniforms; } 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 { typedef struct pxl8_3d_frame {
const pxl8_bsp* bsp;
pxl8_vec3 camera_dir; pxl8_vec3 camera_dir;
pxl8_vec3 camera_pos; pxl8_vec3 camera_pos;
f32 far_clip; f32 far_clip;
@ -25,6 +35,7 @@ typedef struct pxl8_3d_frame {
u32 lights_count; u32 lights_count;
f32 near_clip; f32 near_clip;
pxl8_mat4 projection; pxl8_mat4 projection;
const pxl8_sdf* sdf;
pxl8_3d_uniforms uniforms; pxl8_3d_uniforms uniforms;
pxl8_mat4 view; pxl8_mat4 view;
} pxl8_3d_frame; } pxl8_3d_frame;
@ -33,6 +44,8 @@ typedef struct pxl8_3d_frame {
extern "C" { extern "C" {
#endif #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_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(pxl8_gfx* gfx, u8 color);
void pxl8_3d_clear_depth(pxl8_gfx* gfx); void pxl8_3d_clear_depth(pxl8_gfx* gfx);

View file

@ -106,6 +106,63 @@ bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y,
return clicked; 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) { void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title) {
if (!gfx || !title) return; if (!gfx || !title) return;

View file

@ -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); 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_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_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); void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title);

View file

@ -2,6 +2,34 @@
#include "pxl8_types.h" #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 { typedef struct pxl8_hal {
void* (*create)(i32 render_w, i32 render_h, void* (*create)(i32 render_w, i32 render_h,
const char* title, i32 win_w, i32 win_h); const char* title, i32 win_w, i32 win_h);

View file

@ -83,6 +83,14 @@ static u64 sdl3_get_ticks(void) {
return SDL_GetTicksNS(); 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) { static void sdl3_present(void* platform_data) {
if (!platform_data) return; if (!platform_data) return;

20
src/hal/pxl8_mem.c Normal file
View file

@ -0,0 +1,20 @@
#include "pxl8_mem.h"
#include <stdlib.h>
#include <string.h>
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);
}

110
src/hal/pxl8_thread_sdl3.c Normal file
View file

@ -0,0 +1,110 @@
#include "pxl8_hal.h"
#include <SDL3/SDL.h>
#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);
}

View file

@ -56,6 +56,14 @@ function Lights.new(capacity)
end end
function Lights:add(x, y, z, r, g, b, intensity, radius) 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) 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 end

View file

@ -49,10 +49,9 @@ function graphics.load_palette(filepath)
end end
function graphics.load_sprite(filepath) function graphics.load_sprite(filepath)
local sprite_id = ffi.new("unsigned int[1]") local result = C.pxl8_gfx_load_sprite(core.gfx, filepath)
local result = C.pxl8_gfx_load_sprite(core.gfx, filepath, sprite_id) if result >= 0 and result < 100 then
if result == 0 then return result
return sprite_id[0]
else else
return nil, result return nil, result
end end

View file

@ -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) return C.pxl8_gui_button(self._ptr, core.gfx, id, x, y, w, h, label)
end 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() function Gui:cursor_down()
C.pxl8_gui_cursor_down(self._ptr) C.pxl8_gui_cursor_down(self._ptr)
end end

View file

@ -15,91 +15,20 @@ function net.get()
return setmetatable({ _ptr = ptr }, Net) return setmetatable({ _ptr = ptr }, Net)
end end
function Net:chunk_id()
return C.pxl8_net_chunk_id(self._ptr)
end
function Net:connected() function Net:connected()
return C.pxl8_net_connected(self._ptr) return C.pxl8_net_connected(self._ptr)
end end
function Net:entities() function Net:enter_chunk(chunk_id)
local snap = C.pxl8_net_snapshot(self._ptr) return C.pxl8_net_enter_chunk(self._ptr, chunk_id or 1) == 0
if snap == nil then
return {}
end
local ents = C.pxl8_net_entities(self._ptr)
if ents == nil then
return {}
end
local result = {}
for i = 0, snap.entity_count - 1 do
result[i + 1] = {
entity_id = tonumber(ents[i].entity_id),
userdata = ents[i].userdata
}
end
return result
end end
function Net:entity_prev_userdata(entity_id) function Net:exit_chunk(x, y, z)
return C.pxl8_net_entity_prev_userdata(self._ptr, entity_id) return C.pxl8_net_exit_chunk(self._ptr, x or 0, y or 0, z or 0) == 0
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
end end
function Net:send_input(input) function Net:send_input(input)
@ -115,35 +44,12 @@ function Net:send_input(input)
return C.pxl8_net_send_input(self._ptr, msg) == 0 return C.pxl8_net_send_input(self._ptr, msg) == 0
end end
function Net:snapshot() function Net:set_chunk_settings(render_distance, sim_distance)
local snap = C.pxl8_net_snapshot(self._ptr) return C.pxl8_net_send_chunk_settings(self._ptr, render_distance or 3, sim_distance or 4) == 0
if snap == nil then
return nil
end
return {
entity_count = snap.entity_count,
event_count = snap.event_count,
player_id = tonumber(snap.player_id),
tick = tonumber(snap.tick),
time = snap.time
}
end end
function Net:spawn(x, y, z, yaw, pitch) function Net:spawn(x, y, z, yaw, pitch)
local cmd = ffi.new("pxl8_command_msg") return C.pxl8_net_spawn(self._ptr, x or 0, y or 0, z or 0, yaw or 0, pitch or 0) == 0
cmd.cmd_type = C.PXL8_CMD_SPAWN_ENTITY
C.pxl8_pack_f32_be(cmd.payload, 0, x or 0)
C.pxl8_pack_f32_be(cmd.payload, 4, y or 0)
C.pxl8_pack_f32_be(cmd.payload, 8, z or 0)
C.pxl8_pack_f32_be(cmd.payload, 12, yaw or 0)
C.pxl8_pack_f32_be(cmd.payload, 16, pitch or 0)
cmd.payload_size = 20
cmd.tick = 0
return self:send_command(cmd)
end
function Net:tick()
return tonumber(C.pxl8_net_tick(self._ptr))
end end
return net return net

281
src/lua/pxl8/shader.lua Normal file
View file

@ -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

View file

@ -6,8 +6,6 @@ local world = {}
world.CHUNK_VXL = 0 world.CHUNK_VXL = 0
world.CHUNK_BSP = 1 world.CHUNK_BSP = 1
world.PROCGEN_ROOMS = C.PXL8_PROCGEN_ROOMS
world.PROCGEN_TERRAIN = C.PXL8_PROCGEN_TERRAIN
local Bsp = {} local Bsp = {}
Bsp.__index = 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) C.pxl8_bsp_face_set_material(self._ptr, face_id, material_id)
end 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 world.Bsp = Bsp
local Chunk = {} local Chunk = {}
@ -45,21 +35,26 @@ function Chunk:bsp()
return setmetatable({ _ptr = ptr }, Bsp) return setmetatable({ _ptr = ptr }, Bsp)
end end
function Chunk:type()
if self._ptr == nil then return nil end
return self._ptr.type
end
function Chunk:ready() function Chunk:ready()
if self._ptr == nil then return false end if self._ptr == nil then return false end
if self._ptr.type == world.CHUNK_BSP then if self._ptr.type == world.CHUNK_BSP then
return self._ptr.bsp ~= nil return self._ptr.bsp ~= nil
elseif self._ptr.type == world.CHUNK_VXL then elseif self._ptr.type == world.CHUNK_VXL then
return self._ptr.voxel ~= nil return self._ptr.voxels ~= nil
end end
return false return false
end 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 world.Chunk = Chunk
local World = {} local World = {}
@ -77,9 +72,32 @@ function World:active_chunk()
return setmetatable({ _ptr = ptr }, Chunk) return setmetatable({ _ptr = ptr }, Chunk)
end end
function World:check_collision(x, y, z, radius) function World:get_render_distance()
local pos = ffi.new("pxl8_vec3", {x = x, y = y, z = z}) return C.pxl8_world_get_render_distance(self._ptr)
return C.pxl8_world_check_collision(self._ptr, pos, radius) 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 end
function World:render(camera_pos) function World:render(camera_pos)
@ -87,11 +105,26 @@ function World:render(camera_pos)
C.pxl8_world_render(self._ptr, core.gfx, vec) C.pxl8_world_render(self._ptr, core.gfx, vec)
end 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 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 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 C.pxl8_world_sweep(self._ptr, from, to, radius)
return result.x, result.y, result.z
end end
world.World = World world.World = World

View file

@ -9,6 +9,23 @@ u32 pxl8_hash32(u32 x) {
return 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) { pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b) {
return (pxl8_vec2){ return (pxl8_vec2){
.x = a.x + b.x, .x = a.x + b.x,

View file

@ -81,11 +81,34 @@ typedef struct pxl8_projected_point {
bool visible; bool visible;
} pxl8_projected_point; } 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 #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
u32 pxl8_hash32(u32 x); 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); pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b);
f32 pxl8_vec2_dot(pxl8_vec2 a, pxl8_vec2 b); f32 pxl8_vec2_dot(pxl8_vec2 a, pxl8_vec2 b);

94
src/math/pxl8_noise.c Normal file
View file

@ -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;
}

18
src/math/pxl8_noise.h Normal file
View file

@ -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

View file

@ -1,11 +1,21 @@
#include "pxl8_net.h" #include "pxl8_net.h"
#include "pxl8_chunk_cache.h"
#include "pxl8_log.h"
#include "pxl8_mem.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "pxl8_hal.h"
#ifdef PXL8_ASYNC_THREADS
#include <stdatomic.h>
#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 #ifdef _WIN32
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <winsock2.h> #include <winsock2.h>
@ -30,28 +40,42 @@
struct pxl8_net { struct pxl8_net {
char address[256]; char address[256];
bool connected;
u16 port;
struct sockaddr_in server_addr;
socket_t sock;
pxl8_world_chunk_cache* chunk_cache;
u32 chunk_id; u32 chunk_id;
u8 chunk_type; u8 chunk_type;
pxl8_chunk_cache* chunk_cache; pxl8_world* world;
bool connected;
f32 dt;
u64 highest_tick;
f32 interp_time;
u32 sequence;
pxl8_entity_state entities[PXL8_MAX_SNAPSHOT_ENTITIES]; pxl8_entity_state entities[PXL8_MAX_SNAPSHOT_ENTITIES];
pxl8_event_msg events[PXL8_MAX_SNAPSHOT_EVENTS]; 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_entity_state prev_entities[PXL8_MAX_SNAPSHOT_ENTITIES];
pxl8_snapshot_header prev_snapshot; 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 recv_buf[4096];
u8 send_buf[4096]; u8 send_buf[4096];
u32 sequence;
struct sockaddr_in server_addr; #ifdef PXL8_ASYNC_THREADS
pxl8_snapshot_header snapshot; pxl8_thread* recv_thread;
socket_t sock; atomic_bool running;
pxl8_queue recv_queue;
#endif
}; };
static const pxl8_entity_state* find_entity(const pxl8_entity_state* entities, u16 count, u64 id) { 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; 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; return true;
} }
@ -238,6 +262,12 @@ bool pxl8_net_poll(pxl8_net* net) {
return true; 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; if (hdr.type != PXL8_MSG_SNAPSHOT) return false;
pxl8_snapshot_header snap; pxl8_snapshot_header snap;
@ -260,6 +290,10 @@ bool pxl8_net_poll(pxl8_net* net) {
net->recv_buf + offset, len - offset, &net->entities[i]); net->recv_buf + offset, len - offset, &net->entities[i]);
} }
if (net->world) {
pxl8_world_reconcile(net->world, net, 1.0f / 30.0f);
}
return true; 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) { pxl8_result pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input) {
if (!net || !net->connected) return PXL8_ERROR_INVALID_ARGUMENT; 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)]; u8 buf[sizeof(pxl8_msg_header) + sizeof(pxl8_input_msg)];
pxl8_msg_header hdr = { pxl8_msg_header hdr = {
.type = PXL8_MSG_INPUT, .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) { void pxl8_net_update(pxl8_net* net, f32 dt) {
if (!net) return; if (!net) return;
net->dt = dt;
net->interp_time += 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; if (!net) return;
net->chunk_cache = cache; 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; if (!net) return NULL;
return net->chunk_cache; 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) { u32 pxl8_net_chunk_id(const pxl8_net* net) {
if (!net) return 0; if (!net) return 0;
return net->chunk_id; return net->chunk_id;
@ -360,3 +402,186 @@ u8 pxl8_net_chunk_type(const pxl8_net* net) {
if (!net) return PXL8_CHUNK_TYPE_VXL; if (!net) return PXL8_CHUNK_TYPE_VXL;
return net->chunk_type; 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

View file

@ -9,9 +9,16 @@ extern "C" {
#define PXL8_NET_INPUT_HISTORY_SIZE 64 #define PXL8_NET_INPUT_HISTORY_SIZE 64
#define PXL8_NET_USERDATA_SIZE 56 #define PXL8_NET_USERDATA_SIZE 56
#define PXL8_NET_PACKET_MAX_SIZE 2048
typedef struct pxl8_net pxl8_net; 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 { typedef struct pxl8_net_config {
const char* address; 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); const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net);
u64 pxl8_net_tick(const pxl8_net* net); u64 pxl8_net_tick(const pxl8_net* net);
void pxl8_net_update(pxl8_net* net, f32 dt); void pxl8_net_update(pxl8_net* net, f32 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);
pxl8_chunk_cache* pxl8_net_chunk_cache(pxl8_net* net); 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); u32 pxl8_net_chunk_id(const pxl8_net* net);
u8 pxl8_net_chunk_type(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 #ifdef __cplusplus
} }
#endif #endif

View file

@ -16,6 +16,7 @@ typedef enum pxl8_msg_type {
PXL8_MSG_NONE = 0, PXL8_MSG_NONE = 0,
PXL8_MSG_CHUNK, PXL8_MSG_CHUNK,
PXL8_MSG_CHUNK_ENTER, PXL8_MSG_CHUNK_ENTER,
PXL8_MSG_CHUNK_EXIT,
PXL8_MSG_COMMAND, PXL8_MSG_COMMAND,
PXL8_MSG_CONNECT, PXL8_MSG_CONNECT,
PXL8_MSG_DISCONNECT, PXL8_MSG_DISCONNECT,
@ -34,6 +35,9 @@ typedef struct pxl8_msg_header {
typedef enum pxl8_cmd_type { typedef enum pxl8_cmd_type {
PXL8_CMD_NONE = 0, PXL8_CMD_NONE = 0,
PXL8_CMD_SPAWN_ENTITY, PXL8_CMD_SPAWN_ENTITY,
PXL8_CMD_EXIT_CHUNK,
PXL8_CMD_ENTER_CHUNK,
PXL8_CMD_SET_CHUNK_SETTINGS,
} pxl8_cmd_type; } pxl8_cmd_type;
typedef struct pxl8_input_msg { typedef struct pxl8_input_msg {
@ -92,17 +96,17 @@ typedef struct pxl8_chunk_msg_header {
} pxl8_chunk_msg_header; } pxl8_chunk_msg_header;
typedef struct pxl8_bsp_wire_header { typedef struct pxl8_bsp_wire_header {
u32 num_vertices; u32 num_cell_portals;
u32 num_edges; u32 num_edges;
u32 num_faces; u32 num_faces;
u32 num_planes;
u32 num_nodes;
u32 num_leafs; u32 num_leafs;
u32 num_surfedges;
u32 num_marksurfaces; u32 num_marksurfaces;
u32 num_cell_portals; u32 num_nodes;
u32 visdata_size; u32 num_planes;
u32 num_surfedges;
u32 num_vertex_lights; u32 num_vertex_lights;
u32 num_vertices;
u32 visdata_size;
} pxl8_bsp_wire_header; } pxl8_bsp_wire_header;
usize pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, usize len); usize pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, usize len);

View file

@ -260,7 +260,7 @@ pxl8_script* pxl8_script_create(bool repl_mode) {
lua_getglobal(script->L, "require"); lua_getglobal(script->L, "require");
lua_pushstring(script->L, "ffi"); lua_pushstring(script->L, "ffi");
if (lua_pcall(script->L, 1, 1, 0) != 0) { 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); pxl8_script_destroy(script);
return NULL; return NULL;
} }
@ -268,7 +268,7 @@ pxl8_script* pxl8_script_create(bool repl_mode) {
lua_getfield(script->L, -1, "cdef"); lua_getfield(script->L, -1, "cdef");
lua_pushstring(script->L, pxl8_ffi_cdefs); lua_pushstring(script->L, pxl8_ffi_cdefs);
if (lua_pcall(script->L, 1, 0, 0) != 0) { 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); pxl8_script_destroy(script);
return NULL; return NULL;
} }

View file

@ -27,7 +27,11 @@ static const char* pxl8_ffi_cdefs =
"f32 pxl8_get_fps(const pxl8* sys);\n" "f32 pxl8_get_fps(const pxl8* sys);\n"
"\n" "\n"
"u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);\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" "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" "typedef struct pxl8_palette pxl8_palette;\n"
"pxl8_palette* pxl8_gfx_palette(pxl8_gfx* gfx);\n" "pxl8_palette* pxl8_gfx_palette(pxl8_gfx* gfx);\n"
"u32 pxl8_palette_color(const pxl8_palette* pal, u8 idx);\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_clear_palette_cycles(pxl8_gfx* ctx);\n"
"void pxl8_gfx_update(pxl8_gfx* ctx, f32 dt);\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_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" "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" "bool pxl8_gfx_push_target(pxl8_gfx* ctx);\n"
"void pxl8_gfx_pop_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_scale(float x, float y, float z);\n"
"pxl8_mat4 pxl8_mat4_translate(float x, float y, float z);\n" "pxl8_mat4 pxl8_mat4_translate(float x, float y, float z);\n"
"\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" "typedef enum pxl8_graph_op {\n"
" PXL8_OP_CONST = 0,\n" " PXL8_OP_CONST = 0,\n"
" PXL8_OP_INPUT_AGE,\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" "void pxl8_graph_eval_texture(const pxl8_graph* graph, u8* buffer, i32 width, i32 height);\n"
"\n" "\n"
"typedef struct pxl8_bsp pxl8_bsp;\n" "typedef struct pxl8_bsp pxl8_bsp;\n"
"typedef struct pxl8_vxl_chunk pxl8_vxl_chunk;\n"
"\n" "\n"
"u32 pxl8_bsp_face_count(const pxl8_bsp* bsp);\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" "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" "\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" "\n"
"typedef struct pxl8_chunk {\n" "typedef struct pxl8_world_chunk {\n"
" pxl8_chunk_type type;\n" " pxl8_world_chunk_type type;\n"
" u32 id;\n" " u32 id;\n"
" u32 version;\n" " u32 version;\n"
" i32 cx, cy, cz;\n" " i32 cx, cy, cz;\n"
" union {\n" " union {\n"
" void* voxel;\n"
" pxl8_bsp* bsp;\n" " pxl8_bsp* bsp;\n"
" pxl8_vxl_chunk* voxels;\n"
" };\n" " };\n"
"} pxl8_chunk;\n" "} pxl8_world_chunk;\n"
"\n" "\n"
"typedef struct pxl8_world pxl8_world;\n" "typedef struct pxl8_world pxl8_world;\n"
"\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_world* pxl8_get_world(pxl8* sys);\n"
"pxl8_chunk* pxl8_world_active_chunk(pxl8_world* world);\n" "pxl8_world_chunk* pxl8_world_active_chunk(pxl8_world* world);\n"
"bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, float radius);\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" "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" "\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" "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" "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_down(pxl8_gui_state* state);\n"
"void pxl8_gui_cursor_up(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_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_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" "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" "bool pxl8_gui_is_hovering(const pxl8_gui_state* state);\n"
@ -524,7 +539,7 @@ static const char* pxl8_ffi_cdefs =
"\n" "\n"
"typedef struct pxl8_net pxl8_net;\n" "typedef struct pxl8_net pxl8_net;\n"
"typedef struct pxl8_net_config { const char* address; u16 port; } pxl8_net_config;\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" "\n"
"typedef struct pxl8_command_msg {\n" "typedef struct pxl8_command_msg {\n"
" u16 cmd_type;\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" "f32 pxl8_net_lerp_alpha(const pxl8_net* net);\n"
"bool pxl8_net_needs_correction(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" "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" "bool pxl8_net_poll(pxl8_net* net);\n"
"u8* pxl8_net_predicted_state(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" "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_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_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" "const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net);\n"
"u64 pxl8_net_tick(const pxl8_net* net);\n" "u64 pxl8_net_tick(const pxl8_net* net);\n"
"void pxl8_net_update(pxl8_net* net, f32 dt);\n" "void pxl8_net_update(pxl8_net* net, f32 dt);\n"

224
src/sim/pxl8_sim.c Normal file
View file

@ -0,0 +1,224 @@
#include "pxl8_sim.h"
#include <math.h>
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;
}
}

56
src/sim/pxl8_sim.h Normal file
View file

@ -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

319
src/vxl/pxl8_vxl.c Normal file
View file

@ -0,0 +1,319 @@
#include "pxl8_vxl.h"
#include <string.h>
#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 = &registry->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;
}

78
src/vxl/pxl8_vxl.h Normal file
View file

@ -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

576
src/vxl/pxl8_vxl_render.c Normal file
View file

@ -0,0 +1,576 @@
#include "pxl8_vxl_render.h"
#include <string.h>
#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;
}

48
src/vxl/pxl8_vxl_render.h Normal file
View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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

View file

@ -1,898 +0,0 @@
#include "pxl8_gen.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
#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;
}
}

View file

@ -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

View file

@ -1,406 +0,0 @@
#include "pxl8_voxel.h"
#include <string.h>
#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 = &registry->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;
}

View file

@ -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

View file

@ -1,28 +1,66 @@
#include "pxl8_world.h" #include "pxl8_world.h"
#include <stdio.h> #include <string.h>
#include "pxl8_hal.h"
#ifdef PXL8_ASYNC_THREADS
#include <stdatomic.h>
#include "pxl8_queue.h"
#endif
#include "pxl8_bsp_render.h"
#include "pxl8_io.h"
#include "pxl8_gfx3d.h"
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_mem.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 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 { struct pxl8_world {
pxl8_chunk* active_chunk; pxl8_world_chunk* active_chunk;
pxl8_block_registry* block_registry; pxl8_vxl_block_registry* block_registry;
pxl8_chunk_cache* chunk_cache; pxl8_world_chunk_cache* chunk_cache;
pxl8_entity_pool* entities; 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* pxl8_world_create(void) {
pxl8_world* world = pxl8_calloc(1, sizeof(pxl8_world)); pxl8_world* world = pxl8_calloc(1, sizeof(pxl8_world));
if (!world) return NULL; if (!world) return NULL;
world->block_registry = pxl8_block_registry_create(); world->block_registry = pxl8_vxl_block_registry_create();
world->chunk_cache = pxl8_chunk_cache_create(); world->chunk_cache = pxl8_world_chunk_cache_create();
world->entities = pxl8_entity_pool_create(PXL8_WORLD_ENTITY_CAPACITY); 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); pxl8_world_destroy(world);
return NULL; return NULL;
} }
@ -33,28 +71,30 @@ pxl8_world* pxl8_world_create(void) {
void pxl8_world_destroy(pxl8_world* world) { void pxl8_world_destroy(pxl8_world* world) {
if (!world) return; if (!world) return;
pxl8_block_registry_destroy(world->block_registry); pxl8_vxl_block_registry_destroy(world->block_registry);
pxl8_chunk_cache_destroy(world->chunk_cache); pxl8_world_chunk_cache_destroy(world->chunk_cache);
pxl8_entity_pool_destroy(world->entities); 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_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; if (!world) return NULL;
return world->chunk_cache; 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; if (!world) return NULL;
return world->active_chunk; 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; if (!world) return;
world->active_chunk = chunk; 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; if (!world) return NULL;
return world->block_registry; return world->block_registry;
} }
@ -69,40 +109,362 @@ pxl8_entity pxl8_world_spawn(pxl8_world* world) {
return pxl8_entity_spawn(world->entities); return pxl8_entity_spawn(world->entities);
} }
bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, f32 radius) { static bool voxel_point_solid(pxl8_world_chunk_cache* cache, f32 x, f32 y, f32 z) {
(void)radius; 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) { i32 lx = (i32)floorf(local_x);
return pxl8_bsp_point_solid(world->active_chunk->bsp, pos); 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; return false;
} }
pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius) { pxl8_ray pxl8_world_ray(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to) {
if (!world || !world->active_chunk) return to; pxl8_ray result = {0};
if (world->active_chunk->type == PXL8_CHUNK_BSP && world->active_chunk->bsp) { if (!world) return result;
return pxl8_bsp_trace(world->active_chunk->bsp, from, to, radius);
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 to; return result;
}
traveled += step_size;
}
return result;
} }
void pxl8_world_update(pxl8_world* world, f32 dt) { pxl8_ray pxl8_world_sweep(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
(void)dt; 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; 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) { 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) { update_sdf(world, camera_pos, PXL8_SDF_CELL);
pxl8_bsp_render(gfx, world->active_chunk->bsp, camera_pos); 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); u32 chunk_id = pxl8_net_chunk_id(net);
if (chunk_type == PXL8_CHUNK_TYPE_BSP && chunk_id != 0) { 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 (chunk && chunk->bsp) {
if (world->active_chunk != chunk) { if (world->active_chunk != chunk) {
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); 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

View file

@ -1,12 +1,13 @@
#pragma once #pragma once
#include "pxl8_chunk.h"
#include "pxl8_chunk_cache.h"
#include "pxl8_entity.h" #include "pxl8_entity.h"
#include "pxl8_gfx.h" #include "pxl8_gfx.h"
#include "pxl8_math.h" #include "pxl8_math.h"
#include "pxl8_net.h" #include "pxl8_net.h"
#include "pxl8_sim.h"
#include "pxl8_types.h" #include "pxl8_types.h"
#include "pxl8_world_chunk.h"
#include "pxl8_world_chunk_cache.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@ -17,21 +18,44 @@ typedef struct pxl8_world pxl8_world;
pxl8_world* pxl8_world_create(void); pxl8_world* pxl8_world_create(void);
void pxl8_world_destroy(pxl8_world* world); void pxl8_world_destroy(pxl8_world* world);
pxl8_chunk_cache* pxl8_world_chunk_cache(pxl8_world* world); pxl8_world_chunk_cache* pxl8_world_get_chunk_cache(pxl8_world* world);
pxl8_chunk* pxl8_world_active_chunk(pxl8_world* world); pxl8_world_chunk* pxl8_world_active_chunk(pxl8_world* world);
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);
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_pool* pxl8_world_entities(pxl8_world* world);
pxl8_entity pxl8_world_spawn(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); bool pxl8_world_point_solid(const pxl8_world* world, f32 x, f32 y, f32 z);
pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius); 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_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);
void pxl8_world_sync(pxl8_world* world, pxl8_net* net); 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 #ifdef __cplusplus
} }
#endif #endif

View file

@ -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);
}

View file

@ -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

View file

@ -1,4 +1,4 @@
#include "pxl8_chunk_cache.h" #include "pxl8_world_chunk_cache.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -7,12 +7,12 @@
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_mem.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++) { for (u32 i = 0; i < cache->entry_count; i++) {
pxl8_chunk_cache_entry* e = &cache->entries[i]; pxl8_world_chunk_cache_entry* e = &cache->entries[i];
if (e->valid && e->chunk && e->chunk->type == PXL8_CHUNK_VXL && if (e->valid && e->chunk && e->chunk->type == PXL8_WORLD_CHUNK_VXL &&
e->chunk->cx == cx && e->chunk->cy == cy && e->chunk->cz == cz) { e->chunk->cx == cx && e->chunk->cy == cy && e->chunk->cz == cz) {
return e; return e;
} }
@ -20,10 +20,10 @@ static pxl8_chunk_cache_entry* find_entry_vxl(pxl8_chunk_cache* cache, i32 cx, i
return NULL; 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++) { for (u32 i = 0; i < cache->entry_count; i++) {
pxl8_chunk_cache_entry* e = &cache->entries[i]; pxl8_world_chunk_cache_entry* e = &cache->entries[i];
if (e->valid && e->chunk && e->chunk->type == PXL8_CHUNK_BSP && if (e->valid && e->chunk && e->chunk->type == PXL8_WORLD_CHUNK_BSP &&
e->chunk->id == id) { e->chunk->id == id) {
return e; return e;
} }
@ -31,16 +31,16 @@ static pxl8_chunk_cache_entry* find_entry_bsp(pxl8_chunk_cache* cache, u32 id) {
return NULL; return NULL;
} }
static pxl8_chunk_cache_entry* alloc_entry(pxl8_chunk_cache* cache) { static pxl8_world_chunk_cache_entry* alloc_entry(pxl8_world_chunk_cache* cache) {
if (cache->entry_count < PXL8_CHUNK_CACHE_SIZE) { if (cache->entry_count < PXL8_WORLD_CHUNK_CACHE_SIZE) {
pxl8_chunk_cache_entry* e = &cache->entries[cache->entry_count++]; pxl8_world_chunk_cache_entry* e = &cache->entries[cache->entry_count++];
memset(e, 0, sizeof(*e)); memset(e, 0, sizeof(*e));
return 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) { 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)); memset(e, 0, sizeof(*e));
return 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; u64 oldest = cache->entries[0].last_used;
u32 slot = 0; 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) { if (cache->entries[i].last_used < oldest) {
oldest = cache->entries[i].last_used; oldest = cache->entries[i].last_used;
slot = i; slot = i;
} }
} }
pxl8_chunk_cache_entry* e = &cache->entries[slot]; pxl8_world_chunk_cache_entry* e = &cache->entries[slot];
if (e->chunk) pxl8_chunk_destroy(e->chunk); if (e->chunk) pxl8_world_chunk_destroy(e->chunk);
if (e->mesh) pxl8_mesh_destroy(e->mesh); if (e->mesh) pxl8_mesh_destroy(e->mesh);
memset(e, 0, sizeof(*e)); memset(e, 0, sizeof(*e));
return e; return e;
} }
static void assembly_reset(pxl8_chunk_assembly* a) { static void assembly_reset(pxl8_world_chunk_assembly* a) {
a->type = PXL8_CHUNK_VXL; a->type = PXL8_WORLD_CHUNK_VXL;
a->id = 0; a->id = 0;
a->cx = 0; a->cx = 0;
a->cy = 0; a->cy = 0;
@ -76,9 +76,9 @@ static void assembly_reset(pxl8_chunk_assembly* a) {
a->complete = false; 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); 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->id = hdr->id;
a->cx = hdr->cx; a->cx = hdr->cx;
a->cy = hdr->cy; 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 src_pos = 0;
usize dst_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 block = src[src_pos++];
u8 run_minus_one = src[src_pos++]; u8 run_minus_one = src[src_pos++];
usize run = (usize)run_minus_one + 1; usize run = (usize)run_minus_one + 1;
for (usize i = 0; i < run && dst_pos < PXL8_VOXEL_CHUNK_VOLUME; i++) { for (usize i = 0; i < run && dst_pos < PXL8_VXL_CHUNK_VOLUME; i++) {
i32 x = (i32)(dst_pos % PXL8_VOXEL_CHUNK_SIZE); linear[dst_pos++] = block;
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++;
} }
} }
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) { 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; return PXL8_OK;
} }
static pxl8_bsp* assembly_to_bsp(pxl8_chunk_assembly* a) { static pxl8_bsp* assembly_to_bsp(pxl8_world_chunk_assembly* a) {
if (!a->complete || a->data_size < 44) { if (!a->complete || a->data_size < 48) {
return NULL; return NULL;
} }
@ -315,11 +321,11 @@ static pxl8_bsp* assembly_to_bsp(pxl8_chunk_assembly* a) {
return bsp; return bsp;
} }
static pxl8_result assemble_vxl(pxl8_chunk_cache* cache, pxl8_chunk_assembly* a) { static pxl8_result assemble_vxl(pxl8_world_chunk_cache* cache, pxl8_world_chunk_assembly* a) {
pxl8_chunk_cache_entry* entry = find_entry_vxl(cache, a->cx, a->cy, a->cz); pxl8_world_chunk_cache_entry* entry = find_entry_vxl(cache, a->cx, a->cy, a->cz);
if (!entry) { if (!entry) {
entry = alloc_entry(cache); 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; entry->valid = true;
} }
@ -332,9 +338,10 @@ static pxl8_result assemble_vxl(pxl8_chunk_cache* cache, pxl8_chunk_assembly* a)
entry->mesh = NULL; 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; 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; 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_debug("[CLIENT] assemble_bsp: id=%u data_size=%zu", a->id, a->data_size);
pxl8_bsp* bsp = assembly_to_bsp(a); pxl8_bsp* bsp = assembly_to_bsp(a);
if (!bsp) { 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_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) {
if (entry->chunk && entry->chunk->bsp) { if (entry->chunk && entry->chunk->bsp) {
pxl8_bsp_destroy(entry->chunk->bsp); pxl8_bsp_destroy(entry->chunk->bsp);
} }
} else { } else {
entry = alloc_entry(cache); entry = alloc_entry(cache);
entry->chunk = pxl8_chunk_create_bsp(a->id); entry->chunk = pxl8_world_chunk_create_bsp(a->id);
entry->valid = true; entry->valid = true;
} }
@ -371,19 +378,19 @@ static pxl8_result assemble_bsp(pxl8_chunk_cache* cache, pxl8_chunk_assembly* a)
return PXL8_OK; return PXL8_OK;
} }
pxl8_chunk_cache* pxl8_chunk_cache_create(void) { pxl8_world_chunk_cache* pxl8_world_chunk_cache_create(void) {
pxl8_chunk_cache* cache = pxl8_calloc(1, sizeof(pxl8_chunk_cache)); pxl8_world_chunk_cache* cache = pxl8_calloc(1, sizeof(pxl8_world_chunk_cache));
if (!cache) return NULL; if (!cache) return NULL;
assembly_reset(&cache->assembly); assembly_reset(&cache->assembly);
return cache; 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; if (!cache) return;
for (u32 i = 0; i < cache->entry_count; i++) { for (u32 i = 0; i < cache->entry_count; i++) {
pxl8_chunk_cache_entry* e = &cache->entries[i]; pxl8_world_chunk_cache_entry* e = &cache->entries[i];
if (e->chunk) pxl8_chunk_destroy(e->chunk); if (e->chunk) pxl8_world_chunk_destroy(e->chunk);
if (e->mesh) pxl8_mesh_destroy(e->mesh); 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_free(cache);
} }
pxl8_result pxl8_chunk_cache_receive(pxl8_chunk_cache* cache, pxl8_result pxl8_world_chunk_cache_receive(pxl8_world_chunk_cache* cache,
const pxl8_chunk_msg_header* hdr, const pxl8_chunk_msg_header* hdr,
const u8* payload, usize len) { const u8* payload, usize len) {
if (!cache || !hdr || !payload) return PXL8_ERROR_INVALID_ARGUMENT; 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 || bool new_assembly = !a->active ||
(hdr->chunk_type == PXL8_CHUNK_TYPE_BSP && a->id != hdr->id) || (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); 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; 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) { if (hdr->flags & PXL8_CHUNK_FLAG_FINAL) {
a->complete = true; a->complete = true;
pxl8_debug("[CLIENT] Final fragment received, assembling type=%d data_size=%zu", a->type, a->data_size); if (a->type == PXL8_WORLD_CHUNK_BSP) {
if (a->type == PXL8_CHUNK_BSP) {
return assemble_bsp(cache, a); return assemble_bsp(cache, a);
} else { } else {
return assemble_vxl(cache, a); return assemble_vxl(cache, a);
@ -442,9 +447,9 @@ pxl8_result pxl8_chunk_cache_receive(pxl8_chunk_cache* cache,
return PXL8_OK; 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; 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) { if (e) {
e->last_used = cache->frame_counter; e->last_used = cache->frame_counter;
return e->chunk; 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; 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; 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) { if (e) {
e->last_used = cache->frame_counter; e->last_used = cache->frame_counter;
return e->chunk; return e->chunk;
@ -462,65 +467,72 @@ pxl8_chunk* pxl8_chunk_cache_get_bsp(pxl8_chunk_cache* cache, u32 id) {
return NULL; return NULL;
} }
pxl8_mesh* pxl8_chunk_cache_get_mesh(pxl8_chunk_cache* cache, pxl8_mesh* pxl8_world_chunk_cache_get_mesh(pxl8_world_chunk_cache* cache,
i32 cx, i32 cy, i32 cz, i32 cx, i32 cy, i32 cz,
const pxl8_block_registry* registry, const pxl8_vxl_block_registry* registry,
const pxl8_voxel_mesh_config* config) { const pxl8_vxl_mesh_config* config) {
if (!cache || !registry) return NULL; if (!cache || !registry) return NULL;
pxl8_chunk_cache_entry* entry = find_entry_vxl(cache, cx, cy, cz); pxl8_world_chunk_cache_entry* entry = find_entry_vxl(cache, cx, cy, cz);
if (!entry || !entry->chunk || !entry->chunk->voxel) return NULL; 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) { if (entry->mesh && !entry->mesh_dirty) {
if (entry->has_all_neighbors == all_neighbors) {
return entry->mesh; return entry->mesh;
} }
}
if (entry->mesh) { if (entry->mesh) {
pxl8_mesh_destroy(entry->mesh); pxl8_mesh_destroy(entry->mesh);
entry->mesh = NULL; entry->mesh = NULL;
} }
pxl8_chunk* nx = pxl8_chunk_cache_get_vxl(cache, cx - 1, cy, cz); const pxl8_vxl_chunk* neighbors[6] = {
pxl8_chunk* px = pxl8_chunk_cache_get_vxl(cache, cx + 1, cy, cz); nx ? nx->voxels : NULL,
pxl8_chunk* ny = pxl8_chunk_cache_get_vxl(cache, cx, cy - 1, cz); px ? px->voxels : NULL,
pxl8_chunk* py = pxl8_chunk_cache_get_vxl(cache, cx, cy + 1, cz); ny ? ny->voxels : NULL,
pxl8_chunk* nz = pxl8_chunk_cache_get_vxl(cache, cx, cy, cz - 1); py ? py->voxels : NULL,
pxl8_chunk* pz = pxl8_chunk_cache_get_vxl(cache, cx, cy, cz + 1); nz ? nz->voxels : NULL,
pz ? pz->voxels : NULL
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
}; };
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->mesh_dirty = false;
entry->has_all_neighbors = all_neighbors;
return entry->mesh; 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; if (!cache) return;
cache->frame_counter++; cache->frame_counter++;
} }
void pxl8_chunk_cache_evict_distant(pxl8_chunk_cache* cache, void pxl8_world_chunk_cache_evict_distant(pxl8_world_chunk_cache* cache,
i32 cx, i32 cy, i32 cz, i32 radius) { i32 cx, i32 cy, i32 cz, i32 radius) {
if (!cache) return; if (!cache) return;
for (u32 i = 0; i < cache->entry_count; i++) { for (u32 i = 0; i < cache->entry_count; i++) {
pxl8_chunk_cache_entry* e = &cache->entries[i]; pxl8_world_chunk_cache_entry* e = &cache->entries[i];
if (!e->valid || !e->chunk || e->chunk->type != PXL8_CHUNK_VXL) continue; if (!e->valid || !e->chunk || e->chunk->type != PXL8_WORLD_CHUNK_VXL) continue;
i32 dx = e->chunk->cx - cx; i32 dx = e->chunk->cx - cx;
i32 dy = e->chunk->cy - cy; i32 dy = e->chunk->cy - cy;
i32 dz = e->chunk->cz - cz; i32 dz = e->chunk->cz - cz;
if (abs(dx) > radius || abs(dy) > radius || abs(dz) > radius) { 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); if (e->mesh) pxl8_mesh_destroy(e->mesh);
e->chunk = NULL; e->chunk = NULL;
e->mesh = 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; if (!cache) return;
for (u32 i = 0; i < cache->entry_count; i++) { for (u32 i = 0; i < cache->entry_count; i++) {

View file

@ -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