better lighting
This commit is contained in:
parent
805a2713a3
commit
6ed4e17065
75 changed files with 6417 additions and 3667 deletions
202
demo/main.fnl
202
demo/main.fnl
|
|
@ -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,164 +13,95 @@
|
||||||
(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))
|
(set logo-x (+ logo-x (* logo-dx dt)))
|
||||||
|
(set logo-y (+ logo-y (* logo-dy dt)))
|
||||||
(music.update dt)
|
(when (< logo-x 0)
|
||||||
|
(set logo-x 0)
|
||||||
(case active-demo
|
(set logo-dx (math.abs logo-dx)))
|
||||||
:logo (do
|
(when (> logo-x 512)
|
||||||
(set logo-x (+ logo-x (* logo-dx dt)))
|
(set logo-x 512)
|
||||||
(set logo-y (+ logo-y (* logo-dy dt)))
|
(set logo-dx (- (math.abs logo-dx))))
|
||||||
(when (< logo-x 0)
|
(when (< logo-y 0)
|
||||||
(set logo-x 0)
|
(set logo-y 0)
|
||||||
(set logo-dx (math.abs logo-dx)))
|
(set logo-dy (math.abs logo-dy)))
|
||||||
(when (> logo-x 512)
|
(when (> logo-y 296)
|
||||||
(set logo-x 512)
|
(set logo-y 296)
|
||||||
(set logo-dx (- (math.abs logo-dx))))
|
(set logo-dy (- (math.abs logo-dy)))))))
|
||||||
(when (< logo-y 0)
|
|
||||||
(set logo-y 0)
|
|
||||||
(set logo-dy (math.abs logo-dy)))
|
|
||||||
(when (> logo-y 296)
|
|
||||||
(set logo-y 296)
|
|
||||||
(set logo-dy (- (math.abs logo-dy)))))
|
|
||||||
:first_person3d (do
|
|
||||||
(when (not first_person3d-init?)
|
|
||||||
(first_person3d.init)
|
|
||||||
(set first_person3d-init? true))
|
|
||||||
(first_person3d.update dt)))
|
|
||||||
|
|
||||||
(when particles
|
|
||||||
(particles:update dt)))
|
|
||||||
|
|
||||||
(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)
|
||||||
(pxl8.clear 0)
|
(do
|
||||||
(when logo-sprite
|
(pxl8.clear 0)
|
||||||
(pxl8.sprite logo-sprite logo-x logo-y 128 64 (< logo-dx 0) (< logo-dy 0))))
|
(when logo-sprite
|
||||||
|
(pxl8.sprite logo-sprite logo-x logo-y 128 64 (< logo-dx 0) (< logo-dy 0)))
|
||||||
:plasma (do
|
(when (not (menu.is-paused))
|
||||||
(pxl8.clear 0)
|
(if (first_person3d.is-ready)
|
||||||
(pxl8.text "Plasma (TODO: Fennel impl)" 200 170 1))
|
(pxl8.text "Press ENTER to start" 240 320 1)
|
||||||
|
(if (first_person3d.is-connected)
|
||||||
:tunnel (do
|
(pxl8.text "Loading world..." 260 320 1)
|
||||||
(pxl8.clear 0)
|
(pxl8.text "Connecting..." 275 320 1))))))
|
||||||
(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
180
demo/mod/entities.fnl
Normal 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}
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
||||||
|
(when (or (pxl8.key_pressed "down")
|
||||||
|
(and (pxl8.key_pressed "tab") (not (pxl8.key_down "lshift")) (not (pxl8.key_down "rshift"))))
|
||||||
|
(select-next))
|
||||||
|
|
||||||
|
(when (or (pxl8.key_pressed "up")
|
||||||
|
(and (pxl8.key_pressed "tab") (or (pxl8.key_down "lshift") (pxl8.key_down "rshift"))))
|
||||||
|
(select-prev))
|
||||||
|
|
||||||
|
(when (or (pxl8.key_pressed "return") (pxl8.key_pressed "space"))
|
||||||
|
(when selected-item
|
||||||
|
(set pending-action selected-item)))))
|
||||||
|
|
||||||
|
(fn menu-button [id x y w h label]
|
||||||
|
(table.insert current-items label)
|
||||||
|
(when (= selected-item nil)
|
||||||
|
(set selected-item label))
|
||||||
|
(let [is-selected (= selected-item label)]
|
||||||
|
(when is-selected
|
||||||
|
(pxl8.rect (- x 3) (- y 3) (+ w 6) (+ h 6) 15))
|
||||||
|
(let [clicked (gui:button id x y w h label)]
|
||||||
|
(when clicked
|
||||||
|
(set selected-item label))
|
||||||
|
(or clicked (and is-selected (= pending-action label))))))
|
||||||
|
|
||||||
|
(fn draw-main-menu []
|
||||||
|
(pxl8.gui_window 200 80 240 235 "pxl8 demo")
|
||||||
|
|
||||||
|
(when (menu-button 1 215 127 210 30 "Resume")
|
||||||
|
(hide))
|
||||||
|
|
||||||
|
(let [wire-label (if wireframe "Wireframe: On" "Wireframe: Off")]
|
||||||
|
(when (menu-button 4 215 162 210 30 wire-label)
|
||||||
|
(set wireframe (not wireframe))))
|
||||||
|
|
||||||
|
(when (menu-button 5 215 197 210 30 "GFX")
|
||||||
|
(set current-panel :gfx)
|
||||||
|
(set selected-item nil))
|
||||||
|
|
||||||
|
(when (menu-button 6 215 232 210 30 "SFX")
|
||||||
|
(set current-panel :sfx)
|
||||||
|
(set selected-item nil))
|
||||||
|
|
||||||
|
(when (menu-button 2 215 267 210 30 "Quit")
|
||||||
|
(pxl8.quit)))
|
||||||
|
|
||||||
|
(fn draw-sfx-panel []
|
||||||
|
(pxl8.gui_window 200 100 240 145 "SFX")
|
||||||
|
|
||||||
|
(let [music-label (if (music.is-playing) "Music: On" "Music: Off")]
|
||||||
|
(when (menu-button 10 215 147 210 30 music-label)
|
||||||
|
(if (music.is-playing)
|
||||||
|
(music.stop)
|
||||||
|
(music.start))))
|
||||||
|
|
||||||
|
(pxl8.gui_label 215 185 "Volume/Devices: TODO" 15)
|
||||||
|
|
||||||
|
(when (menu-button 20 215 210 210 30 "Back")
|
||||||
|
(set current-panel :main)
|
||||||
|
(set selected-item nil)))
|
||||||
|
|
||||||
|
(fn draw-gfx-panel []
|
||||||
|
(pxl8.gui_window 200 100 240 180 "GFX")
|
||||||
|
|
||||||
|
(pxl8.gui_label 215 150 (.. "Render: " render-distance) 15)
|
||||||
|
(let [(changed new-val) (gui:slider_int 30 215 165 210 16 render-distance 1 8)]
|
||||||
|
(when changed
|
||||||
|
(set render-distance new-val)
|
||||||
|
(let [w (world-mod.World.get)
|
||||||
|
n (net-mod.get)]
|
||||||
|
(when w (w:set_render_distance new-val))
|
||||||
|
(when n (n:set_chunk_settings new-val sim-distance)))))
|
||||||
|
|
||||||
|
(pxl8.gui_label 215 190 (.. "Sim: " sim-distance) 15)
|
||||||
|
(let [(changed new-val) (gui:slider_int 31 215 205 210 16 sim-distance 1 8)]
|
||||||
|
(when changed
|
||||||
|
(set sim-distance new-val)
|
||||||
|
(let [w (world-mod.World.get)
|
||||||
|
n (net-mod.get)]
|
||||||
|
(when w (w:set_sim_distance new-val))
|
||||||
|
(when n (n:set_chunk_settings render-distance new-val)))))
|
||||||
|
|
||||||
|
(when (menu-button 32 215 240 210 30 "Back")
|
||||||
|
(set current-panel :main)
|
||||||
|
(set selected-item nil)))
|
||||||
|
|
||||||
(fn draw []
|
(fn draw []
|
||||||
|
(set current-items [])
|
||||||
(when gui
|
(when gui
|
||||||
(gui:begin_frame)
|
(gui:begin_frame)
|
||||||
|
|
||||||
(pxl8.gui_window 200 100 240 200 "pxl8 demo")
|
(case current-panel
|
||||||
|
:main (draw-main-menu)
|
||||||
(when (gui:button 1 215 147 210 30 "Resume")
|
:sfx (draw-sfx-panel)
|
||||||
(hide))
|
:gfx (draw-gfx-panel))
|
||||||
|
|
||||||
(let [music-label (if (music.is-playing) "Music: On" "Music: Off")]
|
|
||||||
(when (gui:button 3 215 182 210 30 music-label)
|
|
||||||
(if (music.is-playing)
|
|
||||||
(music.stop)
|
|
||||||
(music.start))))
|
|
||||||
|
|
||||||
(let [wire-label (if wireframe "Wireframe: On" "Wireframe: Off")]
|
|
||||||
(when (gui:button 4 215 217 210 30 wire-label)
|
|
||||||
(set wireframe (not wireframe))))
|
|
||||||
|
|
||||||
(when (gui:button 2 215 252 210 30 "Quit")
|
|
||||||
(pxl8.quit))
|
|
||||||
|
|
||||||
(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
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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
35
pxl8.sh
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
315
pxl8d/src/bsp.rs
315
pxl8d/src/bsp.rs
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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::*;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,54 +189,36 @@ 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);
|
||||||
while let Some(chunk_id) = client_chunks.next_pending() {
|
client_chunks.request_vxl_radius(pos, client_stream_radius, &sim.voxels);
|
||||||
match chunk_id {
|
|
||||||
ChunkId::Bsp(id) => {
|
while let Some(chunk_id) = client_chunks.next_pending() {
|
||||||
pxl8_debug!("[SERVER] Processing pending BSP chunk");
|
match chunk_id {
|
||||||
if let Some(chunk) = sim.world.get(&chunk_id) {
|
ChunkId::Bsp(id) => {
|
||||||
if let Some(bsp) = chunk.as_bsp() {
|
if let Some(chunk) = sim.world.get(&chunk_id) {
|
||||||
let msgs = bsp_to_messages(bsp, id, chunk.version());
|
if let Some(bsp) = chunk.as_bsp() {
|
||||||
pxl8_debug!("[SERVER] BSP serialized, queueing messages");
|
let msgs = bsp_to_messages(bsp, id, chunk.version());
|
||||||
client_chunks.queue_messages(msgs);
|
|
||||||
client_chunks.mark_sent(chunk_id, chunk.version());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ChunkId::Vxl(cx, cy, cz) => {
|
|
||||||
if let Some(chunk) = sim.voxels.get_chunk(cx, cy, cz) {
|
|
||||||
let msgs = transport::ChunkMessage::from_voxel(chunk, 1);
|
|
||||||
client_chunks.queue_messages(msgs);
|
client_chunks.queue_messages(msgs);
|
||||||
client_chunks.mark_sent(chunk_id, 1);
|
client_chunks.mark_sent(chunk_id, chunk.version());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
ChunkId::Vxl(cx, cy, cz) => {
|
||||||
|
if let Some(chunk) = sim.voxels.get_chunk(cx, cy, cz) {
|
||||||
for _ in 0..4 {
|
let msgs = transport::ChunkMessage::from_voxel(chunk, 1);
|
||||||
if let Some(msg) = client_chunks.next_message() {
|
client_chunks.queue_messages(msgs);
|
||||||
transport.send_chunk(&msg, sequence);
|
client_chunks.mark_sent(chunk_id, 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _ in 0..8 {
|
||||||
|
if let Some(msg) = client_chunks.next_message() {
|
||||||
|
transport.send_chunk(&msg, sequence);
|
||||||
|
sequence = sequence.wrapping_add(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -250,7 +274,7 @@ fn bsp_to_messages(bsp: &bsp::Bsp, id: u32, version: u32) -> alloc::vec::Vec<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 {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
172
pxl8d/src/sim.rs
172
pxl8d/src/sim.rs
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
for ly in 0..CHUNK_SIZE {
|
let hx = lx.min(31);
|
||||||
let wy = world_y + ly as i32;
|
let hz = lz.min(31);
|
||||||
|
let base_height = height_cache[hz][hx] as f32;
|
||||||
|
let height_bias = (base_height - wy) * 0.1;
|
||||||
|
|
||||||
let block = if wy > height {
|
let cave_noise = fbm_3d(wx * 0.05, wy * 0.05, wz * 0.05, seed.wrapping_add(1000), 2);
|
||||||
AIR
|
density[lz][ly][lx] = height_bias + (cave_noise - 0.55);
|
||||||
} else if wy == height {
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for lz in 0..CHUNK_SIZE {
|
||||||
|
for ly in 0..CHUNK_SIZE {
|
||||||
|
for lx in 0..CHUNK_SIZE {
|
||||||
|
let d = density[lz as usize][ly as usize][lx as usize];
|
||||||
|
if d <= 0.0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let d_above = density[lz as usize][(ly + 1) as usize][lx as usize];
|
||||||
|
let is_surface = d_above <= 0.0;
|
||||||
|
|
||||||
|
let block = if is_surface {
|
||||||
GRASS
|
GRASS
|
||||||
} else if wy > height - 4 {
|
|
||||||
DIRT
|
|
||||||
} else {
|
} else {
|
||||||
STONE
|
let wy = world_y + ly;
|
||||||
|
let base_height = height_cache[lz as usize][lx as usize];
|
||||||
|
let depth = base_height - wy;
|
||||||
|
if depth < 4 {
|
||||||
|
DIRT
|
||||||
|
} else {
|
||||||
|
STONE
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
chunk.set(lx, ly, lz, block);
|
chunk.set(lx, ly, lz, block);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
429
src/bsp/pxl8_bsp_render.c
Normal 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
30
src/bsp/pxl8_bsp_render.h
Normal 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
|
||||||
|
|
@ -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
53
src/core/pxl8_queue.h
Normal 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);
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
20
src/hal/pxl8_mem.c
Normal 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
110
src/hal/pxl8_thread_sdl3.c
Normal 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);
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
281
src/lua/pxl8/shader.lua
Normal 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
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
94
src/math/pxl8_noise.c
Normal 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
18
src/math/pxl8_noise.h
Normal 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
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
224
src/sim/pxl8_sim.c
Normal 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
56
src/sim/pxl8_sim.h
Normal 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
319
src/vxl/pxl8_vxl.c
Normal 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 = ®istry->blocks[id];
|
||||||
|
strncpy(def->name, name ? name : "", sizeof(def->name) - 1);
|
||||||
|
def->texture_top = texture_top;
|
||||||
|
def->texture_side = texture_side;
|
||||||
|
def->texture_bottom = texture_bottom;
|
||||||
|
def->geometry = geo;
|
||||||
|
def->registered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* pxl8_vxl_block_registry_name(const pxl8_vxl_block_registry* registry, u8 id) {
|
||||||
|
if (!registry || !registry->blocks[id].registered) return NULL;
|
||||||
|
return registry->blocks[id].name;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_vxl_geometry pxl8_vxl_block_registry_geometry(const pxl8_vxl_block_registry* registry, u8 id) {
|
||||||
|
if (!registry || !registry->blocks[id].registered) return PXL8_VXL_GEOMETRY_CUBE;
|
||||||
|
return registry->blocks[id].geometry;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 pxl8_vxl_block_registry_texture(const pxl8_vxl_block_registry* registry, u8 id) {
|
||||||
|
if (!registry || !registry->blocks[id].registered) return 0;
|
||||||
|
return registry->blocks[id].texture_top;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 pxl8_vxl_block_registry_texture_for_face(const pxl8_vxl_block_registry* registry, u8 id, i32 face) {
|
||||||
|
if (!registry || !registry->blocks[id].registered) return 0;
|
||||||
|
if (face == 3) return registry->blocks[id].texture_top;
|
||||||
|
if (face == 2) return registry->blocks[id].texture_bottom;
|
||||||
|
return registry->blocks[id].texture_side;
|
||||||
|
}
|
||||||
78
src/vxl/pxl8_vxl.h
Normal file
78
src/vxl/pxl8_vxl.h
Normal 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
576
src/vxl/pxl8_vxl_render.c
Normal 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
48
src/vxl/pxl8_vxl_render.h
Normal 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
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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 = ®istry->blocks[id];
|
|
||||||
strncpy(def->name, name ? name : "", sizeof(def->name) - 1);
|
|
||||||
def->texture_id = texture_id;
|
|
||||||
def->geometry = geo;
|
|
||||||
def->registered = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* pxl8_block_registry_name(const pxl8_block_registry* registry, pxl8_block id) {
|
|
||||||
if (!registry || !registry->blocks[id].registered) return NULL;
|
|
||||||
return registry->blocks[id].name;
|
|
||||||
}
|
|
||||||
|
|
||||||
pxl8_voxel_geometry pxl8_block_registry_geometry(const pxl8_block_registry* registry, pxl8_block id) {
|
|
||||||
if (!registry || !registry->blocks[id].registered) return PXL8_VOXEL_GEOMETRY_CUBE;
|
|
||||||
return registry->blocks[id].geometry;
|
|
||||||
}
|
|
||||||
|
|
||||||
u8 pxl8_block_registry_texture(const pxl8_block_registry* registry, pxl8_block id) {
|
|
||||||
if (!registry || !registry->blocks[id].registered) return 0;
|
|
||||||
return registry->blocks[id].texture_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool block_is_opaque(pxl8_block block) {
|
|
||||||
return block != PXL8_BLOCK_AIR;
|
|
||||||
}
|
|
||||||
|
|
||||||
static pxl8_block get_block_or_neighbor(const pxl8_voxel_chunk* chunk, const pxl8_voxel_chunk** neighbors, i32 x, i32 y, i32 z) {
|
|
||||||
if (voxel_in_bounds(x, y, z)) {
|
|
||||||
return chunk->blocks[voxel_index(x, y, z)];
|
|
||||||
}
|
|
||||||
|
|
||||||
i32 nx = x, ny = y, nz = z;
|
|
||||||
i32 neighbor_idx = -1;
|
|
||||||
|
|
||||||
if (x < 0) { neighbor_idx = 0; nx = x + PXL8_VOXEL_CHUNK_SIZE; }
|
|
||||||
else if (x >= PXL8_VOXEL_CHUNK_SIZE) { neighbor_idx = 1; nx = x - PXL8_VOXEL_CHUNK_SIZE; }
|
|
||||||
else if (y < 0) { neighbor_idx = 2; ny = y + PXL8_VOXEL_CHUNK_SIZE; }
|
|
||||||
else if (y >= PXL8_VOXEL_CHUNK_SIZE) { neighbor_idx = 3; ny = y - PXL8_VOXEL_CHUNK_SIZE; }
|
|
||||||
else if (z < 0) { neighbor_idx = 4; nz = z + PXL8_VOXEL_CHUNK_SIZE; }
|
|
||||||
else if (z >= PXL8_VOXEL_CHUNK_SIZE) { neighbor_idx = 5; nz = z - PXL8_VOXEL_CHUNK_SIZE; }
|
|
||||||
|
|
||||||
if (neighbor_idx >= 0 && neighbors && neighbors[neighbor_idx]) {
|
|
||||||
return pxl8_voxel_get(neighbors[neighbor_idx], nx, ny, nz);
|
|
||||||
}
|
|
||||||
|
|
||||||
return PXL8_BLOCK_AIR;
|
|
||||||
}
|
|
||||||
|
|
||||||
static f32 compute_ao(const pxl8_voxel_chunk* chunk, const pxl8_voxel_chunk** neighbors,
|
|
||||||
i32 x, i32 y, i32 z, i32 dx1, i32 dy1, i32 dz1, i32 dx2, i32 dy2, i32 dz2) {
|
|
||||||
bool side1 = block_is_opaque(get_block_or_neighbor(chunk, neighbors, x + dx1, y + dy1, z + dz1));
|
|
||||||
bool side2 = block_is_opaque(get_block_or_neighbor(chunk, neighbors, x + dx2, y + dy2, z + dz2));
|
|
||||||
bool corner = block_is_opaque(get_block_or_neighbor(chunk, neighbors, x + dx1 + dx2, y + dy1 + dy2, z + dz1 + dz2));
|
|
||||||
|
|
||||||
if (side1 && side2) return 0.0f;
|
|
||||||
return (3.0f - (f32)side1 - (f32)side2 - (f32)corner) / 3.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void add_face_vertices(pxl8_mesh* mesh, const pxl8_voxel_mesh_config* config,
|
|
||||||
pxl8_vec3 pos, i32 face, u8 texture_id,
|
|
||||||
f32 ao0, f32 ao1, f32 ao2, f32 ao3) {
|
|
||||||
static const pxl8_vec3 face_normals[6] = {
|
|
||||||
{-1, 0, 0}, { 1, 0, 0},
|
|
||||||
{ 0, -1, 0}, { 0, 1, 0},
|
|
||||||
{ 0, 0, -1}, { 0, 0, 1}
|
|
||||||
};
|
|
||||||
|
|
||||||
static const i32 face_vertices[6][4][3] = {
|
|
||||||
{{0,0,1}, {0,0,0}, {0,1,0}, {0,1,1}},
|
|
||||||
{{1,0,0}, {1,0,1}, {1,1,1}, {1,1,0}},
|
|
||||||
{{0,0,0}, {1,0,0}, {1,0,1}, {0,0,1}},
|
|
||||||
{{0,1,1}, {1,1,1}, {1,1,0}, {0,1,0}},
|
|
||||||
{{0,0,0}, {0,1,0}, {1,1,0}, {1,0,0}},
|
|
||||||
{{1,0,1}, {1,1,1}, {0,1,1}, {0,0,1}}
|
|
||||||
};
|
|
||||||
|
|
||||||
static const f32 face_uvs[4][2] = {
|
|
||||||
{0, 1}, {1, 1}, {1, 0}, {0, 0}
|
|
||||||
};
|
|
||||||
|
|
||||||
f32 ao_values[4] = {ao0, ao1, ao2, ao3};
|
|
||||||
f32 ao_strength = config->ambient_occlusion ? config->ao_strength : 0.0f;
|
|
||||||
f32 tex_scale = config->texture_scale;
|
|
||||||
|
|
||||||
pxl8_vec3 normal = face_normals[face];
|
|
||||||
u16 indices[4];
|
|
||||||
|
|
||||||
for (i32 i = 0; i < 4; i++) {
|
|
||||||
pxl8_vertex v = {0};
|
|
||||||
v.position.x = pos.x + (f32)face_vertices[face][i][0];
|
|
||||||
v.position.y = pos.y + (f32)face_vertices[face][i][1];
|
|
||||||
v.position.z = pos.z + (f32)face_vertices[face][i][2];
|
|
||||||
v.normal = normal;
|
|
||||||
v.u = face_uvs[i][0] * tex_scale;
|
|
||||||
v.v = face_uvs[i][1] * tex_scale;
|
|
||||||
v.color = texture_id;
|
|
||||||
v.light = (u8)(255.0f * (1.0f - ao_strength * (1.0f - ao_values[i])));
|
|
||||||
|
|
||||||
indices[i] = pxl8_mesh_push_vertex(mesh, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ao0 + ao2 > ao1 + ao3) {
|
|
||||||
pxl8_mesh_push_quad(mesh, indices[0], indices[1], indices[2], indices[3]);
|
|
||||||
} else {
|
|
||||||
pxl8_mesh_push_triangle(mesh, indices[0], indices[1], indices[2]);
|
|
||||||
pxl8_mesh_push_triangle(mesh, indices[0], indices[2], indices[3]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void add_cube_faces(pxl8_mesh* mesh, const pxl8_voxel_chunk* chunk,
|
|
||||||
const pxl8_voxel_chunk** neighbors, const pxl8_block_registry* registry,
|
|
||||||
const pxl8_voxel_mesh_config* config,
|
|
||||||
i32 x, i32 y, i32 z, pxl8_block block) {
|
|
||||||
u8 texture_id = pxl8_block_registry_texture(registry, block);
|
|
||||||
pxl8_vec3 pos = {(f32)x, (f32)y, (f32)z};
|
|
||||||
|
|
||||||
static const i32 face_dirs[6][3] = {
|
|
||||||
{-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (i32 face = 0; face < 6; face++) {
|
|
||||||
i32 nx = x + face_dirs[face][0];
|
|
||||||
i32 ny = y + face_dirs[face][1];
|
|
||||||
i32 nz = z + face_dirs[face][2];
|
|
||||||
|
|
||||||
pxl8_block neighbor = get_block_or_neighbor(chunk, neighbors, nx, ny, nz);
|
|
||||||
if (block_is_opaque(neighbor)) continue;
|
|
||||||
|
|
||||||
f32 ao[4] = {1.0f, 1.0f, 1.0f, 1.0f};
|
|
||||||
|
|
||||||
if (config->ambient_occlusion) {
|
|
||||||
switch (face) {
|
|
||||||
case 0:
|
|
||||||
ao[0] = compute_ao(chunk, neighbors, x-1, y, z, 0, -1, 0, 0, 0, 1);
|
|
||||||
ao[1] = compute_ao(chunk, neighbors, x-1, y, z, 0, -1, 0, 0, 0, -1);
|
|
||||||
ao[2] = compute_ao(chunk, neighbors, x-1, y, z, 0, 1, 0, 0, 0, -1);
|
|
||||||
ao[3] = compute_ao(chunk, neighbors, x-1, y, z, 0, 1, 0, 0, 0, 1);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
ao[0] = compute_ao(chunk, neighbors, x+1, y, z, 0, -1, 0, 0, 0, -1);
|
|
||||||
ao[1] = compute_ao(chunk, neighbors, x+1, y, z, 0, -1, 0, 0, 0, 1);
|
|
||||||
ao[2] = compute_ao(chunk, neighbors, x+1, y, z, 0, 1, 0, 0, 0, 1);
|
|
||||||
ao[3] = compute_ao(chunk, neighbors, x+1, y, z, 0, 1, 0, 0, 0, -1);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
ao[0] = compute_ao(chunk, neighbors, x, y-1, z, -1, 0, 0, 0, 0, -1);
|
|
||||||
ao[1] = compute_ao(chunk, neighbors, x, y-1, z, 1, 0, 0, 0, 0, -1);
|
|
||||||
ao[2] = compute_ao(chunk, neighbors, x, y-1, z, 1, 0, 0, 0, 0, 1);
|
|
||||||
ao[3] = compute_ao(chunk, neighbors, x, y-1, z, -1, 0, 0, 0, 0, 1);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
ao[0] = compute_ao(chunk, neighbors, x, y+1, z, -1, 0, 0, 0, 0, 1);
|
|
||||||
ao[1] = compute_ao(chunk, neighbors, x, y+1, z, 1, 0, 0, 0, 0, 1);
|
|
||||||
ao[2] = compute_ao(chunk, neighbors, x, y+1, z, 1, 0, 0, 0, 0, -1);
|
|
||||||
ao[3] = compute_ao(chunk, neighbors, x, y+1, z, -1, 0, 0, 0, 0, -1);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
ao[0] = compute_ao(chunk, neighbors, x, y, z-1, -1, 0, 0, 0, 1, 0);
|
|
||||||
ao[1] = compute_ao(chunk, neighbors, x, y, z-1, -1, 0, 0, 0, -1, 0);
|
|
||||||
ao[2] = compute_ao(chunk, neighbors, x, y, z-1, 1, 0, 0, 0, -1, 0);
|
|
||||||
ao[3] = compute_ao(chunk, neighbors, x, y, z-1, 1, 0, 0, 0, 1, 0);
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
ao[0] = compute_ao(chunk, neighbors, x, y, z+1, 1, 0, 0, 0, -1, 0);
|
|
||||||
ao[1] = compute_ao(chunk, neighbors, x, y, z+1, 1, 0, 0, 0, 1, 0);
|
|
||||||
ao[2] = compute_ao(chunk, neighbors, x, y, z+1, -1, 0, 0, 0, 1, 0);
|
|
||||||
ao[3] = compute_ao(chunk, neighbors, x, y, z+1, -1, 0, 0, 0, -1, 0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
add_face_vertices(mesh, config, pos, face, texture_id, ao[0], ao[1], ao[2], ao[3]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void add_slab_faces(pxl8_mesh* mesh, const pxl8_voxel_chunk* chunk,
|
|
||||||
const pxl8_voxel_chunk** neighbors, const pxl8_block_registry* registry,
|
|
||||||
const pxl8_voxel_mesh_config* config,
|
|
||||||
i32 x, i32 y, i32 z, pxl8_block block, bool top) {
|
|
||||||
u8 texture_id = pxl8_block_registry_texture(registry, block);
|
|
||||||
f32 y_offset = top ? 0.5f : 0.0f;
|
|
||||||
pxl8_vec3 pos = {(f32)x, (f32)y + y_offset, (f32)z};
|
|
||||||
|
|
||||||
static const i32 horiz_faces[4] = {0, 1, 4, 5};
|
|
||||||
static const i32 face_dirs[6][3] = {
|
|
||||||
{-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (i32 i = 0; i < 4; i++) {
|
|
||||||
i32 face = horiz_faces[i];
|
|
||||||
i32 nx = x + face_dirs[face][0];
|
|
||||||
i32 nz = z + face_dirs[face][2];
|
|
||||||
|
|
||||||
pxl8_block neighbor = get_block_or_neighbor(chunk, neighbors, nx, y, nz);
|
|
||||||
if (block_is_opaque(neighbor)) continue;
|
|
||||||
|
|
||||||
add_face_vertices(mesh, config, pos, face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
i32 top_face = 3;
|
|
||||||
i32 bot_face = 2;
|
|
||||||
|
|
||||||
if (top) {
|
|
||||||
pxl8_block above = get_block_or_neighbor(chunk, neighbors, x, y + 1, z);
|
|
||||||
if (!block_is_opaque(above)) {
|
|
||||||
add_face_vertices(mesh, config, pos, top_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
|
|
||||||
}
|
|
||||||
add_face_vertices(mesh, config, (pxl8_vec3){(f32)x, (f32)y + 0.5f, (f32)z}, bot_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
|
|
||||||
} else {
|
|
||||||
add_face_vertices(mesh, config, pos, top_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
|
|
||||||
pxl8_block below = get_block_or_neighbor(chunk, neighbors, x, y - 1, z);
|
|
||||||
if (!block_is_opaque(below)) {
|
|
||||||
add_face_vertices(mesh, config, pos, bot_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void add_slope_faces(pxl8_mesh* mesh, const pxl8_voxel_chunk* chunk,
|
|
||||||
const pxl8_voxel_chunk** neighbors, const pxl8_block_registry* registry,
|
|
||||||
const pxl8_voxel_mesh_config* config,
|
|
||||||
i32 x, i32 y, i32 z, pxl8_block block, i32 direction) {
|
|
||||||
u8 texture_id = pxl8_block_registry_texture(registry, block);
|
|
||||||
pxl8_vec3 pos = {(f32)x, (f32)y, (f32)z};
|
|
||||||
|
|
||||||
pxl8_block below = get_block_or_neighbor(chunk, neighbors, x, y - 1, z);
|
|
||||||
if (!block_is_opaque(below)) {
|
|
||||||
add_face_vertices(mesh, config, pos, 2, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const i32 dir_to_back_face[4] = {5, 4, 1, 0};
|
|
||||||
i32 back_face = dir_to_back_face[direction];
|
|
||||||
|
|
||||||
static const i32 face_dirs[6][3] = {
|
|
||||||
{-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}
|
|
||||||
};
|
|
||||||
|
|
||||||
i32 bx = x + face_dirs[back_face][0];
|
|
||||||
i32 bz = z + face_dirs[back_face][2];
|
|
||||||
pxl8_block back_neighbor = get_block_or_neighbor(chunk, neighbors, bx, y, bz);
|
|
||||||
if (!block_is_opaque(back_neighbor)) {
|
|
||||||
add_face_vertices(mesh, config, pos, back_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
pxl8_vertex verts[4];
|
|
||||||
memset(verts, 0, sizeof(verts));
|
|
||||||
|
|
||||||
static const f32 slope_verts[4][4][3] = {
|
|
||||||
{{0,0,0}, {1,0,0}, {1,1,1}, {0,1,1}},
|
|
||||||
{{0,0,1}, {1,0,1}, {1,1,0}, {0,1,0}},
|
|
||||||
{{1,0,0}, {1,0,1}, {0,1,1}, {0,1,0}},
|
|
||||||
{{0,0,0}, {0,0,1}, {1,1,1}, {1,1,0}}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (i32 i = 0; i < 4; i++) {
|
|
||||||
verts[i].position.x = pos.x + slope_verts[direction][i][0];
|
|
||||||
verts[i].position.y = pos.y + slope_verts[direction][i][1];
|
|
||||||
verts[i].position.z = pos.z + slope_verts[direction][i][2];
|
|
||||||
verts[i].color = texture_id;
|
|
||||||
verts[i].light = 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const pxl8_vec3 slope_normals[4] = {
|
|
||||||
{0, 0.707f, -0.707f}, {0, 0.707f, 0.707f},
|
|
||||||
{-0.707f, 0.707f, 0}, {0.707f, 0.707f, 0}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (i32 i = 0; i < 4; i++) {
|
|
||||||
verts[i].normal = slope_normals[direction];
|
|
||||||
}
|
|
||||||
|
|
||||||
u16 i0 = pxl8_mesh_push_vertex(mesh, verts[0]);
|
|
||||||
u16 i1 = pxl8_mesh_push_vertex(mesh, verts[1]);
|
|
||||||
u16 i2 = pxl8_mesh_push_vertex(mesh, verts[2]);
|
|
||||||
u16 i3 = pxl8_mesh_push_vertex(mesh, verts[3]);
|
|
||||||
pxl8_mesh_push_quad(mesh, i0, i1, i2, i3);
|
|
||||||
}
|
|
||||||
|
|
||||||
pxl8_mesh* pxl8_voxel_build_mesh(const pxl8_voxel_chunk* chunk,
|
|
||||||
const pxl8_voxel_chunk** neighbors,
|
|
||||||
const pxl8_block_registry* registry,
|
|
||||||
const pxl8_voxel_mesh_config* config) {
|
|
||||||
if (!chunk || !registry) return NULL;
|
|
||||||
|
|
||||||
pxl8_voxel_mesh_config cfg = config ? *config : PXL8_VOXEL_MESH_CONFIG_DEFAULT;
|
|
||||||
|
|
||||||
u32 max_faces = PXL8_VOXEL_CHUNK_VOLUME * 6;
|
|
||||||
pxl8_mesh* mesh = pxl8_mesh_create(max_faces * 4, max_faces * 6);
|
|
||||||
if (!mesh) return NULL;
|
|
||||||
|
|
||||||
for (i32 z = 0; z < PXL8_VOXEL_CHUNK_SIZE; z++) {
|
|
||||||
for (i32 y = 0; y < PXL8_VOXEL_CHUNK_SIZE; y++) {
|
|
||||||
for (i32 x = 0; x < PXL8_VOXEL_CHUNK_SIZE; x++) {
|
|
||||||
pxl8_block block = chunk->blocks[voxel_index(x, y, z)];
|
|
||||||
if (block == PXL8_BLOCK_AIR) continue;
|
|
||||||
|
|
||||||
pxl8_voxel_geometry geo = pxl8_block_registry_geometry(registry, block);
|
|
||||||
|
|
||||||
switch (geo) {
|
|
||||||
case PXL8_VOXEL_GEOMETRY_CUBE:
|
|
||||||
add_cube_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block);
|
|
||||||
break;
|
|
||||||
case PXL8_VOXEL_GEOMETRY_SLAB_BOTTOM:
|
|
||||||
add_slab_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, false);
|
|
||||||
break;
|
|
||||||
case PXL8_VOXEL_GEOMETRY_SLAB_TOP:
|
|
||||||
add_slab_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, true);
|
|
||||||
break;
|
|
||||||
case PXL8_VOXEL_GEOMETRY_SLOPE_NORTH:
|
|
||||||
add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 0);
|
|
||||||
break;
|
|
||||||
case PXL8_VOXEL_GEOMETRY_SLOPE_SOUTH:
|
|
||||||
add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 1);
|
|
||||||
break;
|
|
||||||
case PXL8_VOXEL_GEOMETRY_SLOPE_EAST:
|
|
||||||
add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 2);
|
|
||||||
break;
|
|
||||||
case PXL8_VOXEL_GEOMETRY_SLOPE_WEST:
|
|
||||||
add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 3);
|
|
||||||
break;
|
|
||||||
case PXL8_VOXEL_GEOMETRY_STAIRS_NORTH:
|
|
||||||
case PXL8_VOXEL_GEOMETRY_STAIRS_SOUTH:
|
|
||||||
case PXL8_VOXEL_GEOMETRY_STAIRS_EAST:
|
|
||||||
case PXL8_VOXEL_GEOMETRY_STAIRS_WEST:
|
|
||||||
add_cube_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mesh;
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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 result;
|
||||||
|
}
|
||||||
|
|
||||||
|
traveled += step_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
return to;
|
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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
46
src/world/pxl8_world_chunk.c
Normal file
46
src/world/pxl8_world_chunk.c
Normal 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);
|
||||||
|
}
|
||||||
33
src/world/pxl8_world_chunk.h
Normal file
33
src/world/pxl8_world_chunk.h
Normal 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
|
||||||
|
|
@ -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,17 +467,28 @@ 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) {
|
||||||
return entry->mesh;
|
if (entry->has_all_neighbors == all_neighbors) {
|
||||||
|
return entry->mesh;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry->mesh) {
|
if (entry->mesh) {
|
||||||
|
|
@ -480,47 +496,43 @@ pxl8_mesh* pxl8_chunk_cache_get_mesh(pxl8_chunk_cache* cache,
|
||||||
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++) {
|
||||||
69
src/world/pxl8_world_chunk_cache.h
Normal file
69
src/world/pxl8_world_chunk_cache.h
Normal 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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue