wip sw renderer...bsp is borked, also lots of other things

This commit is contained in:
asrael 2026-01-21 23:19:50 -06:00
parent 415d424057
commit 71787869e0
58 changed files with 8151 additions and 1261 deletions

View file

@ -6,11 +6,10 @@
(var time 0) (var time 0)
(var active-demo :logo) (var active-demo :logo)
(var particles nil) (var particles nil)
(var particles2 nil)
(var fire-init? false) (var fire-init? false)
(var rain-init? false) (var rain-init? false)
(var snow-init? false) (var snow-init? false)
(var snow-init2? false) (var first_person3d-init? false)
(var use-famicube-palette? false) (var use-famicube-palette? false)
(var logo-x 256) (var logo-x 256)
@ -32,9 +31,7 @@
(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)) (set particles (pxl8.create_particles 1000))
(set particles2 (pxl8.create_particles 500)) (music.init)))
(music.init)
(first_person3d.init)))
(global update (fn [dt] (global update (fn [dt]
(when (pxl8.key_pressed "escape") (when (pxl8.key_pressed "escape")
@ -57,7 +54,8 @@
(set transition-pending nil) (set transition-pending nil)
(when (= active-demo :fire) (set fire-init? false)) (when (= active-demo :fire) (set fire-init? false))
(when (= active-demo :rain) (set rain-init? false)) (when (= active-demo :rain) (set rain-init? false))
(when (= active-demo :snow) (set snow-init? false) (set snow-init2? 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)))
@ -92,12 +90,14 @@
(when (> logo-y 296) (when (> logo-y 296)
(set logo-y 296) (set logo-y 296)
(set logo-dy (- (math.abs logo-dy))))) (set logo-dy (- (math.abs logo-dy)))))
:first_person3d (first_person3d.update dt)) :first_person3d (do
(when (not first_person3d-init?)
(first_person3d.init)
(set first_person3d-init? true))
(first_person3d.update dt)))
(when particles (when particles
(particles:update dt)) (particles:update dt)))
(when particles2
(particles2:update dt)))
(when (menu.is-paused) (when (menu.is-paused)
(menu.update)))) (menu.update))))
@ -174,6 +174,7 @@
:first_person3d (first_person3d.frame) :first_person3d (first_person3d.frame)
_ (pxl8.clear 0)) _ (pxl8.clear 0))
(when transition (when transition

4103
demo/mod/blendtable.fnl Normal file

File diff suppressed because it is too large Load diff

1031
demo/mod/colormap.fnl Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,18 @@
(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 colormap (require :mod.colormap))
(local menu (require :mod.menu))
(local palette (require :mod.palette))
(local sky (require :mod.sky)) (local sky (require :mod.sky))
(local textures (require :mod.textures))
(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 cell-size 64) (local cell-size 64)
(local cursor-sensitivity 0.008) (local cursor-sensitivity 0.010)
(local gravity -800) (local gravity -800)
(local grid-size 64) (local grid-size 64)
(local ground-y 64) (local ground-y 64)
@ -16,7 +21,7 @@
(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 turn-speed 2.0) (local turn-speed 4.0)
(local sim-tick-rate 60) (local sim-tick-rate 60)
(local sim-dt (/ 1.0 sim-tick-rate)) (local sim-dt (/ 1.0 sim-tick-rate))
@ -32,10 +37,10 @@
(var cam-yaw 0) (var cam-yaw 0)
(var cam-z 1000) (var cam-z 1000)
(var camera nil) (var camera nil)
(var cursor-look? true)
(var grounded? true) (var grounded? true)
(var land-squash 0) (var land-squash 0)
(var light-time 0) (var light-time 0)
(var real-time 0)
(var network nil) (var network nil)
(var smooth-cam-x 1000) (var smooth-cam-x 1000)
(var smooth-cam-z 1000) (var smooth-cam-z 1000)
@ -43,16 +48,147 @@
(var world nil) (var world nil)
(var fps-avg 0) (var fps-avg 0)
(var fps-sample-count 0) (var fps-sample-count 0)
(var fireball-mesh nil)
(var last-dt 0.016)
(local FIREBALL_COLOR 184) (local cursor-look? true)
(local FIREBALL_COLOR 218)
(local STONE_FLOOR_START 37)
(local STONE_WALL_START 2)
(local MOSS_COLOR 200)
(fn init-fireball-palette [] (local trail-positions [])
(for [i 0 7] (local TRAIL_LENGTH 8)
(let [t (/ i 7)
r (math.floor (+ 0xFF (* t 0))) (fn create-fireball-mesh []
g (math.floor (+ 0x60 (* t (- 0xE0 0x60)))) (let [verts []
b (math.floor (+ 0x10 (* t (- 0x80 0x10))))] indices []
(pxl8.set_palette_rgb (+ FIREBALL_COLOR i) r g b)))) radius 5
rings 4
segments 6
core-color (+ FIREBALL_COLOR 6)
spike-color (+ FIREBALL_COLOR 2)]
;; top pole
(table.insert verts {:x 0 :y radius :z 0 :nx 0 :ny 1 :nz 0 :color core-color :light 255})
;; sphere rings
(for [ring 1 (- rings 1)]
(let [phi (* (/ ring rings) math.pi)
sin-phi (math.sin phi)
cos-phi (math.cos phi)
y (* radius cos-phi)
ring-radius (* radius sin-phi)]
(for [seg 0 (- segments 1)]
(let [theta (* (/ seg segments) math.pi 2)
x (* ring-radius (math.cos theta))
z (* ring-radius (math.sin theta))
nx (* sin-phi (math.cos theta))
nz (* sin-phi (math.sin theta))]
(table.insert verts {:x x :y y :z z :nx nx :ny cos-phi :nz nz :color core-color :light 255})))))
;; bottom pole
(let [bottom-idx (length verts)]
(table.insert verts {:x 0 :y (- radius) :z 0 :nx 0 :ny -1 :nz 0 :color core-color :light 255})
;; 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 client-tick 0)
(var last-processed-tick 0) (var last-processed-tick 0)
@ -99,10 +235,21 @@
(values new-x new-z)) (values new-x new-z))
(fn init [] (fn init []
(pxl8.set_relative_mouse_mode true)
(pxl8.set_palette palette 256)
(pxl8.set_colormap colormap 16384)
(for [i 0 7]
(let [t (/ i 7)
r 0xFF
g (math.floor (+ 0x60 (* t (- 0xE0 0x60))))
b (math.floor (+ 0x10 (* t (- 0x80 0x10))))]
(pxl8.set_palette_rgb (+ FIREBALL_COLOR i) r g b)))
(sky.update-gradient 1 2 6 6 10 18)
(pxl8.update_palette_deps)
(set camera (pxl8.create_camera_3d)) (set camera (pxl8.create_camera_3d))
(set world (pxl8.create_world)) (set world (pxl8.create_world))
(sky.generate-stars 12345) (sky.generate-stars 12345)
(init-fireball-palette) (create-fireball-mesh)
(set network (net.Net.new {:port 7777})) (set network (net.Net.new {:port 7777}))
(when network (when network
@ -119,16 +266,8 @@
:num_rooms 20})] :num_rooms 20})]
(if (< result 0) (if (< result 0)
(pxl8.error (.. "Failed to generate rooms - result: " result)) (pxl8.error (.. "Failed to generate rooms - result: " result))
(let [floor-tex (pxl8.procgen_tex {:name "floor" (let [floor-tex (textures.mossy-cobblestone 44444 STONE_FLOOR_START MOSS_COLOR)
:seed 11111 wall-tex (textures.ashlar-wall 55555 STONE_WALL_START MOSS_COLOR)
:width 64
:height 64
:base_color 19})
wall-tex (pxl8.procgen_tex {:name "wall"
:seed 12345
:width 64
:height 64
:base_color 4})
sky-tex (pxl8.create_texture [0] 1 1)] sky-tex (pxl8.create_texture [0] 1 1)]
(let [result (world:apply_textures [ (let [result (world:apply_textures [
@ -193,6 +332,7 @@
(store-position t cam-x cam-z hist.yaw)))))))))) (store-position t cam-x cam-z hist.yaw))))))))))
(fn update [dt] (fn update [dt]
(set last-dt dt)
(let [fps (pxl8.get_fps)] (let [fps (pxl8.get_fps)]
(set fps-sample-count (+ fps-sample-count 1)) (set fps-sample-count (+ fps-sample-count 1))
(set fps-avg (+ (* fps-avg (/ (- fps-sample-count 1) fps-sample-count)) (set fps-avg (+ (* fps-avg (/ (- fps-sample-count 1) fps-sample-count))
@ -234,9 +374,9 @@
(when (and (not cursor-look?) (pxl8.key_down "down")) (when (and (not cursor-look?) (pxl8.key_down "down"))
(set cam-pitch (math.max (- max-pitch) (- cam-pitch (* turn-speed dt))))) (set cam-pitch (math.max (- max-pitch) (- cam-pitch (* turn-speed dt)))))
(when (and (not cursor-look?) (pxl8.key_down "left")) (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)))) (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 (when network
(let [(ok err) (pcall (fn [] (let [(ok err) (pcall (fn []
@ -287,7 +427,8 @@
(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))))))
(set light-time (+ light-time (* dt 0.15)))))) (set light-time (+ light-time (* dt 0.5)))
(set real-time (+ real-time dt)))))
(fn frame [] (fn frame []
(pxl8.clear 1) (pxl8.clear 1)
@ -316,77 +457,59 @@
[0 1 0]) [0 1 0])
(camera:set_perspective 1.047 aspect 1.0 4096.0) (camera:set_perspective 1.047 aspect 1.0 4096.0)
(let [light-pulse (+ 0.7 (* 0.3 (math.sin (* light-time 2)))) (let [light-x (+ 1000 (* 50 (math.cos light-time)))
forward-x (- (math.sin cam-yaw)) light-z (+ 940 (* 50 (math.sin light-time)))
forward-z (- (math.cos cam-yaw)) light-y 80
light-x (+ smooth-cam-x (* 150 forward-x) (* 50 (math.cos light-time))) phase (+ (* light-x 0.01) 1.7)
light-z (+ smooth-cam-z (* 150 forward-z) (* 50 (math.sin light-time))) f1 (* 0.08 (math.sin (+ (* real-time 2.5) phase)))
light-y (+ eye-y 30)] f2 (* 0.05 (math.sin (+ (* real-time 4.1) (* phase 0.7))))
f3 (* 0.03 (math.sin (+ (* real-time 7.3) (* phase 1.2))))
flicker (+ 0.92 f1 f2 f3)
light-intensity (math.floor (math.max 0 (math.min 255 (* 255 flicker))))
r1 (* 0.06 (math.sin (+ (* real-time 1.8) (* phase 0.5))))
r2 (* 0.04 (math.sin (+ (* real-time 3.2) phase)))
light-radius (* 150 (+ 0.95 r1 r2))]
(pxl8.begin_frame_3d camera { (pxl8.begin_frame_3d camera {
:ambient 80 :ambient 30
:fog_density 0.0
:celestial_dir [0.5 -0.8 0.3] :celestial_dir [0.5 -0.8 0.3]
:celestial_intensity 0.5 :celestial_intensity 0.5
:lights [{:x light-x :y light-y :z light-z :lights [{:x light-x :y light-y :z light-z
:r 255 :g 200 :b 150 :r 255 :g 200 :b 150
:intensity (* 255 light-pulse) :intensity light-intensity
:radius 400}]}) :radius light-radius}]})
(pxl8.clear_depth) (pxl8.clear_depth)
(sky.update-gradient 1 2 6 6 10 18) (sky.update-gradient 1 2 6 6 10 18)
(sky.render smooth-cam-x eye-y smooth-cam-z) (sky.render smooth-cam-x eye-y smooth-cam-z (menu.is-wireframe))
(pxl8.clear_depth) (pxl8.clear_depth)
(world:set_wireframe (menu.is-wireframe) 15)
(world:render [smooth-cam-x eye-y smooth-cam-z]) (world:render [smooth-cam-x eye-y smooth-cam-z])
(pxl8.end_frame_3d) (when fireball-mesh
(let [wire (menu.is-wireframe)]
(pxl8.draw_mesh fireball-mesh {:x light-x :y light-y :z light-z
:passthrough true
:wireframe wire
:emissive 1.0})))
(let [dx (- light-x smooth-cam-x) (pxl8.end_frame_3d))
dy (- light-y eye-y)
dz (- light-z smooth-cam-z)
dist (math.sqrt (+ (* dx dx) (* dy dy) (* dz dz)))]
(when (> dist 1)
(let [inv-dist (/ 1 dist)
dir-x (* dx inv-dist)
dir-y (* dy inv-dist)
dir-z (* dz inv-dist)
cos-yaw (math.cos cam-yaw)
sin-yaw (math.sin cam-yaw)
cos-pitch (math.cos cam-pitch)
sin-pitch (math.sin cam-pitch)
rx (+ (* dir-x cos-yaw) (* dir-z sin-yaw))
rz (+ (* (- dir-x) sin-yaw) (* dir-z cos-yaw))
ry (- (* dir-y cos-pitch) (* rz sin-pitch))
fz (+ (* dir-y sin-pitch) (* rz cos-pitch))]
(when (> fz 0.01)
(let [width (pxl8.get_width)
height (pxl8.get_height)
fov 1.047
half-fov-tan (math.tan (* fov 0.5))
ndc-x (/ rx (* fz half-fov-tan aspect))
ndc-y (/ ry (* fz half-fov-tan))
sx (math.floor (* (+ 1 ndc-x) 0.5 width))
sy (math.floor (* (- 1 ndc-y) 0.5 height))
screen-size (/ 400 dist)
base-radius (math.max 2 (math.min 12 (math.floor screen-size)))
pulse-int (math.floor (* 180 light-pulse))]
(when (and (>= sx 0) (< sx width) (>= sy 0) (< sy height))
(effects.glows [
{:x sx :y sy :radius (+ base-radius 2) :intensity (/ pulse-int 5) :color (+ FIREBALL_COLOR 1) :shape effects.GLOW_CIRCLE}
{:x sx :y sy :radius base-radius :intensity pulse-int :color (+ FIREBALL_COLOR 5) :shape effects.GLOW_DIAMOND}]))))))))
(sky.render-stars cam-yaw cam-pitch 1.0) (sky.render-stars cam-yaw cam-pitch 1.0 last-dt)
(let [cx (/ (pxl8.get_width) 2) (let [cx (/ (pxl8.get_width) 2)
cy (/ (pxl8.get_height) 2) cy (/ (pxl8.get_height) 2)
crosshair-size 4 crosshair-size 4
red-color 18] crosshair-color 240
(pxl8.line (- cx crosshair-size) cy (+ cx crosshair-size) cy red-color) text-color 251]
(pxl8.line cx (- cy crosshair-size) cx (+ cy crosshair-size) red-color)) (pxl8.line (- cx crosshair-size) cy (+ cx crosshair-size) cy crosshair-color)
(pxl8.line cx (- cy crosshair-size) cx (+ cy crosshair-size) crosshair-color)
(pxl8.text (.. "fps: " (string.format "%.0f" fps-avg)) 5 5 12) (pxl8.text (.. "fps: " (string.format "%.0f" fps-avg)) 5 5 text-color)
(pxl8.text (.. "pos: " (string.format "%.0f" cam-x) "," (pxl8.text (.. "pos: " (string.format "%.0f" cam-x) ","
(string.format "%.0f" cam-y) "," (string.format "%.0f" cam-y) ","
(string.format "%.0f" cam-z)) 5 15 12)))) (string.format "%.0f" cam-z)) 5 15 text-color)))))
{:init init {:init init
:update update :update update

View file

@ -2,6 +2,7 @@
(local music (require :mod.music)) (local music (require :mod.music))
(var paused false) (var paused false)
(var wireframe false)
(var gui nil) (var gui nil)
(fn init [] (fn init []
@ -37,18 +38,22 @@
(when gui (when gui
(gui:begin_frame) (gui:begin_frame)
(pxl8.gui_window 200 100 240 180 "pxl8 demo") (pxl8.gui_window 200 100 240 200 "pxl8 demo")
(when (gui:button 1 215 145 210 32 "Resume") (when (gui:button 1 215 140 210 30 "Resume")
(hide)) (hide))
(let [music-label (if (music.is-playing) "Music: On" "Music: Off")] (let [music-label (if (music.is-playing) "Music: On" "Music: Off")]
(when (gui:button 3 215 185 210 32 music-label) (when (gui:button 3 215 175 210 30 music-label)
(if (music.is-playing) (if (music.is-playing)
(music.stop) (music.stop)
(music.start)))) (music.start))))
(when (gui:button 2 215 225 210 32 "Quit") (let [wire-label (if wireframe "Wireframe: On" "Wireframe: Off")]
(when (gui:button 4 215 210 210 30 wire-label)
(set wireframe (not wireframe))))
(when (gui:button 2 215 245 210 30 "Quit")
(pxl8.quit)) (pxl8.quit))
(if (gui:is_hovering) (if (gui:is_hovering)
@ -58,6 +63,7 @@
(gui:end_frame))) (gui:end_frame)))
{:is-paused (fn [] paused) {:is-paused (fn [] paused)
:is-wireframe (fn [] wireframe)
:toggle toggle :toggle toggle
:show show :show show
:hide hide :hide hide

263
demo/mod/palette.fnl Normal file
View file

@ -0,0 +1,263 @@
(require :pxl8)
(local ffi (require :ffi))
(local data (ffi.new "u32[256]" [
0x080602
0x14120E
0x23211E
0x31302C
0x403E3B
0x4B4946
0x595755
0x676664
0x767573
0x858382
0x939290
0xA2A09F
0xB0AFAE
0xBEBDBC
0xCDCCCC
0xDADAD9
0x594625
0x544023
0x4F3C24
0x4C3A22
0x453821
0x40321F
0x3E2F20
0x382D1D
0x33291E
0x30271F
0x2F251D
0x2D231E
0x28211C
0x251F1D
0x23201A
0x221F1B
0x646269
0x5F5C61
0x5C545A
0x584F55
0x5B514F
0x554A47
0x4B413F
0x423C36
0x463D31
0x3E352A
0x362E25
0x2D2922
0x26221D
0x1C1916
0x151310
0x100F0D
0x8C6F52
0x7E6045
0x73553B
0x715134
0xBA8346
0xA1723B
0x815C2E
0x745226
0xCC8926
0xBB7E22
0x9F6B1F
0x875A1C
0x6E4918
0x553712
0x3B250D
0x24180A
0xA34331
0x9B3728
0x923220
0x882E18
0x842B16
0x772312
0x69200D
0x5A1C06
0x541C04
0x4C1A03
0x411701
0x371000
0x2E0D00
0x250B00
0x1B0600
0x130500
0x7D5741
0x76503A
0x6E4C37
0x684833
0x5D3F2F
0x553A2C
0x4F3628
0x483024
0x4A3126
0x483025
0x432D22
0x3C2C22
0x352922
0x2C241F
0x221C1B
0x1A1916
0x6E4626
0x5F4025
0x523924
0x433322
0x352B1E
0x28231A
0x1A1A14
0x1C1815
0x96544B
0xAC7369
0xB48C86
0xBCA7A4
0xB1BCC2
0x9DB0B9
0x8A9FAA
0x77929F
0x738995
0x5E7C8B
0x4A6C7D
0x345E72
0x1F4C64
0x19445C
0x143C51
0x10384B
0x183748
0x1A3341
0x192F39
0x152B34
0x13262E
0x101E23
0x0E1519
0x0B0E10
0x896463
0x815C5B
0x785352
0x6F4C4D
0x664444
0x5F3C3D
0x573738
0x523233
0x442929
0x392324
0x2D1D1D
0x241414
0x1A0E0E
0x100909
0x070403
0x000000
0x98936F
0x918B68
0x887F60
0x807759
0x797055
0x73684D
0x6B6146
0x63593F
0x5B523A
0x504834
0x423D2D
0x373226
0x2E2B1F
0x222018
0x161511
0x0E0F0A
0x9A554F
0x904D48
0x87453F
0x7D4037
0x743831
0x693329
0x612C24
0x572720
0x4F231A
0x441E16
0x391914
0x2D150F
0x22110D
0x1A0B06
0x0D0403
0x040202
0x7F77C0
0x7770B5
0x6E68A8
0x686099
0x60588C
0x575381
0x4E4C72
0x454263
0x3D3957
0x34324A
0x2C2940
0x242135
0x1E1928
0x16121D
0x0C0A12
0x050306
0x88AF7B
0x81A473
0x7B9A67
0x728E5D
0x6D8553
0x61794A
0x5B7144
0x61734B
0x586A3D
0x4D5E2D
0x465422
0x3F4D17
0x36420E
0x2F3507
0x272804
0x211F02
0x1EF708
0x3CE10D
0x51CC1B
0x64B621
0x6DA12C
0x69882B
0x727F3B
0xE4DDCE
0xEEE6BA
0xEAE290
0xE9E26D
0xE5DE43
0xE3DB20
0xE1CC18
0xDFB911
0xDCA60B
0xE8A306
0xDF9312
0xE17B05
0xC86815
0xBC5908
0xB14805
0xA63D07
0xB6431E
0xAA381A
0x9A2E12
0x8C270F
0x892B17
0x762311
0x5F1F0D
0x491B09
0x3B1809
0xE50F19
0x6A34C4
0xE00B28
0x2B08C8
0x322A33
0x281C0E
0x2F1E15
0xD48067
0xC26B4C
0x974928
0x814123
0xD5B3A9
0xBE9D93
0x9A7B6C
0x7F5F51
0x8E504C
]))
data

View file

@ -7,17 +7,24 @@
(local sky-radius 900) (local sky-radius 900)
(local sky-segments 16) (local sky-segments 16)
(local sky-rings 16) (local sky-rings 16)
(local max-theta (* math.pi 0.55))
(local STAR_COUNT 200) (local NUM_RANDOM_STARS 300)
(local TINY_STAR_COUNT 5000) (local NUM_TINY_STARS 7000)
(local STAR_SILVER_START 160) (local STAR_SEED 0xDEADBEEF)
(local STAR_BLUE_START 168) (local STAR_CYCLE_PERIOD 7200)
(local STAR_RED_START 176)
;; Use existing bright palette colors
;; Silver/white: indices 14-15 (brightest grays)
(local IDX_SILVER 14)
;; Warm/torch: indices 216-218 (bright creams/yellows)
(local IDX_TORCH 216)
;; Blue/magic: indices 176-178 (purples - brightest of the range)
(local IDX_MAGIC 176)
(var sky-mesh nil) (var sky-mesh nil)
(var star-time 0)
(var last-gradient-key nil) (var last-gradient-key nil)
(var stars []) (var random-stars [])
(var tiny-stars []) (var tiny-stars [])
(fn generate-sky-gradient [zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b] (fn generate-sky-gradient [zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b]
@ -33,8 +40,8 @@
indices []] indices []]
(for [i 0 (- sky-rings 1)] (for [i 0 (- sky-rings 1)]
(let [theta0 (* (/ i sky-rings) max-theta) (let [theta0 (* (/ i sky-rings) math.pi 0.5)
theta1 (* (/ (+ i 1) sky-rings) max-theta) theta1 (* (/ (+ i 1) sky-rings) math.pi 0.5)
sin-t0 (math.sin theta0) sin-t0 (math.sin theta0)
cos-t0 (math.cos theta0) cos-t0 (math.cos theta0)
sin-t1 (math.sin theta1) sin-t1 (math.sin theta1)
@ -67,22 +74,17 @@
(if (= i 0) (if (= i 0)
(do (do
;; First ring is degenerate - just a triangle from pole
;; Vertices: v00 (pole, c0), v11 (bottom-right, c1), v10 (bottom-left, c1)
(table.insert verts {:x x00 :y y0 :z z00 :nx nx00 :ny ny00 :nz nz00 :color c0 :light 255}) (table.insert verts {:x x00 :y y0 :z z00 :nx nx00 :ny ny00 :nz nz00 :color c0 :light 255})
(table.insert verts {:x x11 :y y1 :z z11 :nx nx11 :ny ny11 :nz nz11 :color c1 :light 255}) (table.insert verts {:x x11 :y y1 :z z11 :nx nx11 :ny ny11 :nz nz11 :color c1 :light 255})
(table.insert verts {:x x10 :y y1 :z z10 :nx nx10 :ny ny10 :nz nz10 :color c1 :light 255}) (table.insert verts {:x x10 :y y1 :z z10 :nx nx10 :ny ny10 :nz nz10 :color c1 :light 255})
;; Triangle: base, base+2, base+1
(table.insert indices base-idx) (table.insert indices base-idx)
(table.insert indices (+ base-idx 2)) (table.insert indices (+ base-idx 2))
(table.insert indices (+ base-idx 1))) (table.insert indices (+ base-idx 1)))
(do (do
;; Regular quad: v00 (top-left), v01 (top-right), v11 (bottom-right), v10 (bottom-left)
(table.insert verts {:x x00 :y y0 :z z00 :nx nx00 :ny ny00 :nz nz00 :color c0 :light 255}) (table.insert verts {:x x00 :y y0 :z z00 :nx nx00 :ny ny00 :nz nz00 :color c0 :light 255})
(table.insert verts {:x x01 :y y0 :z z01 :nx nx01 :ny ny01 :nz nz01 :color c0 :light 255}) (table.insert verts {:x x01 :y y0 :z z01 :nx nx01 :ny ny01 :nz nz01 :color c0 :light 255})
(table.insert verts {:x x11 :y y1 :z z11 :nx nx11 :ny ny11 :nz nz11 :color c1 :light 255}) (table.insert verts {:x x11 :y y1 :z z11 :nx nx11 :ny ny11 :nz nz11 :color c1 :light 255})
(table.insert verts {:x x10 :y y1 :z z10 :nx nx10 :ny ny10 :nz nz10 :color c1 :light 255}) (table.insert verts {:x x10 :y y1 :z z10 :nx nx10 :ny ny10 :nz nz10 :color c1 :light 255})
;; push_quad(base, base+3, base+2, base+1) = triangles (base,base+3,base+2) and (base,base+2,base+1)
(table.insert indices base-idx) (table.insert indices base-idx)
(table.insert indices (+ base-idx 3)) (table.insert indices (+ base-idx 3))
(table.insert indices (+ base-idx 2)) (table.insert indices (+ base-idx 2))
@ -98,72 +100,78 @@
(generate-sky-gradient zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b) (generate-sky-gradient zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b)
(set last-gradient-key key)))) (set last-gradient-key key))))
(fn palette-ramp [start c0 c1] (fn galactic-band-factor [dx dy dz]
(let [r0 (bit.rshift (bit.band c0 0xFF0000) 16) (let [band-len (math.sqrt (+ (* 0.6 0.6) (* 0.3 0.3) (* 0.742 0.742)))
g0 (bit.rshift (bit.band c0 0x00FF00) 8) bx (/ 0.6 band-len)
b0 (bit.band c0 0x0000FF) by (/ 0.3 band-len)
r1 (bit.rshift (bit.band c1 0xFF0000) 16) bz (/ 0.742 band-len)
g1 (bit.rshift (bit.band c1 0x00FF00) 8) dist-from-band (math.abs (+ (* dx bx) (* dy by) (* dz bz)))
b1 (bit.band c1 0x0000FF)] in-band (- 1 (math.min (* dist-from-band 3) 1))]
(for [i 0 7] (* in-band in-band)))
(let [t (/ i 7)
r (math.floor (+ r0 (* t (- r1 r0))))
g (math.floor (+ g0 (* t (- g1 g0))))
b (math.floor (+ b0 (* t (- b1 b0))))]
(pxl8.set_palette_rgb (+ start i) r g b)))))
(fn init-star-palette [] (fn generate-stars []
(palette-ramp STAR_SILVER_START 0x707888 0xFFFFFF) ;; silver (set random-stars [])
(palette-ramp STAR_BLUE_START 0x5070B0 0xD0E8FF) ;; blue
(palette-ramp STAR_RED_START 0x802020 0xFF9090)) ;; red
(fn generate-stars [seed]
(set stars [])
(set tiny-stars []) (set tiny-stars [])
(init-star-palette)
(pxl8.rng_seed seed)
(for [i 1 STAR_COUNT] ;; Generate random stars - use full upper hemisphere (dy > -0.1)
(let [theta (math.acos (- 1 (* (pxl8.rng_f32) 0.85))) (for [i 0 (- NUM_RANDOM_STARS 1)]
phi (* (pxl8.rng_f32) math.pi 2) (let [h1 (pxl8.hash32 (+ STAR_SEED (* i 5)))
brightness (pxl8.rng_range 1 4) h2 (pxl8.hash32 (+ STAR_SEED (* i 5) 1))
color-type (pxl8.rng_range 0 100) h3 (pxl8.hash32 (+ STAR_SEED (* i 5) 2))
shade (pxl8.rng_range 0 5) h4 (pxl8.hash32 (+ STAR_SEED (* i 5) 3))
color (if (< color-type 3) (+ STAR_RED_START shade) theta (* (/ h1 0xFFFFFFFF) math.pi 2)
(< color-type 15) (+ STAR_BLUE_START shade) phi (math.acos (- 1 (* (/ h2 0xFFFFFFFF) 1.0)))
(+ STAR_SILVER_START shade)) sin-phi (math.sin phi)
sin-t (math.sin theta) cos-phi (math.cos phi)
cos-t (math.cos theta)] dx (* sin-phi (math.cos theta))
(table.insert stars {:dx (* sin-t (math.cos phi)) dy cos-phi
:dy cos-t dz (* sin-phi (math.sin theta))
:dz (* sin-t (math.sin phi)) brightness-raw (/ (% h3 256) 255)
:brightness brightness brightness (math.floor (+ 60 (* brightness-raw brightness-raw 195)))
:color color}))) color-type (% h4 100)
color (if (< color-type 8) (+ IDX_TORCH (% (bit.rshift h4 8) 2))
(< color-type 16) (+ IDX_MAGIC (% (bit.rshift h4 8) 2))
(+ IDX_SILVER (% (bit.rshift h4 8) 2)))]
(pxl8.rng_seed (+ seed 0xCAFEBABE)) (when (> dy -0.1)
(for [i 1 TINY_STAR_COUNT] (table.insert random-stars {:dx dx :dy dy :dz dz
(let [theta (math.acos (- 1 (* (pxl8.rng_f32) 0.95)))
phi (* (pxl8.rng_f32) math.pi 2)
brightness (+ 25 (pxl8.rng_range 0 40))
shade (pxl8.rng_range 0 3)
color-type (pxl8.rng_range 0 100)
color (if (< color-type 15) (+ STAR_BLUE_START shade)
(+ STAR_SILVER_START shade))
sin-t (math.sin theta)
cos-t (math.cos theta)]
(table.insert tiny-stars {:dx (* sin-t (math.cos phi))
:dy cos-t
:dz (* sin-t (math.sin phi))
:brightness brightness :brightness brightness
:color color})))) :color color}))))
(fn project-direction [dir-x dir-y dir-z yaw pitch width height] (let [tiny-seed (+ STAR_SEED 0xCAFEBABE)]
(let [cos-yaw (math.cos yaw) (for [i 0 (- NUM_TINY_STARS 1)]
(let [h1 (pxl8.hash32 (+ tiny-seed (* i 4)))
h2 (pxl8.hash32 (+ tiny-seed (* i 4) 1))
h3 (pxl8.hash32 (+ tiny-seed (* i 4) 2))
h4 (pxl8.hash32 (+ tiny-seed (* i 4) 3))
theta (* (/ h1 0xFFFFFFFF) math.pi 2)
phi (math.acos (- 1 (* (/ h2 0xFFFFFFFF) 1.0)))
sin-phi (math.sin phi)
cos-phi (math.cos phi)
dx (* sin-phi (math.cos theta))
dy cos-phi
dz (* sin-phi (math.sin theta))
band-boost (galactic-band-factor dx dy dz)
base-bright (+ 40 (% h3 50))
brightness (+ base-bright (math.floor (* band-boost 40)))
color-shift (% h4 100)
color (if (< color-shift 3) (+ IDX_TORCH (% (bit.rshift h4 8) 2))
(< color-shift 12) (+ IDX_MAGIC (% (bit.rshift h4 8) 2))
(+ IDX_SILVER (% (bit.rshift h4 8) 2)))]
(when (> dy -0.1)
(table.insert tiny-stars {:dx dx :dy dy :dz dz
:brightness brightness
:color color}))))))
(fn project-direction [dir-x dir-y dir-z yaw pitch cos-rot sin-rot width height]
(let [rot-x (- (* dir-x cos-rot) (* dir-z sin-rot))
rot-z (+ (* dir-x sin-rot) (* dir-z cos-rot))
cos-yaw (math.cos yaw)
sin-yaw (math.sin yaw) sin-yaw (math.sin yaw)
cos-pitch (math.cos pitch) cos-pitch (math.cos pitch)
sin-pitch (math.sin pitch) sin-pitch (math.sin pitch)
rotated-x (+ (* dir-x cos-yaw) (* dir-z sin-yaw)) rotated-x (+ (* rot-x cos-yaw) (* rot-z sin-yaw))
rotated-z (+ (* (- dir-x) sin-yaw) (* dir-z cos-yaw)) rotated-z (+ (* (- rot-x) sin-yaw) (* rot-z cos-yaw))
rotated-y (- (* dir-y cos-pitch) (* rotated-z sin-pitch)) rotated-y (- (* dir-y cos-pitch) (* rotated-z sin-pitch))
final-z (+ (* dir-y sin-pitch) (* rotated-z cos-pitch))] final-z (+ (* dir-y sin-pitch) (* rotated-z cos-pitch))]
(when (> final-z 0.01) (when (> final-z 0.01)
@ -176,17 +184,22 @@
{:x (math.floor (* (+ 1 ndc-x) 0.5 width)) {:x (math.floor (* (+ 1 ndc-x) 0.5 width))
:y (math.floor (* (- 1 ndc-y) 0.5 height))}))))) :y (math.floor (* (- 1 ndc-y) 0.5 height))})))))
(fn render-stars [yaw pitch intensity] (fn render-stars [yaw pitch intensity dt]
(set star-time (+ star-time (or dt 0)))
(when (> intensity 0) (when (> intensity 0)
(let [width (pxl8.get_width) (let [width (pxl8.get_width)
height (pxl8.get_height) height (pxl8.get_height)
glows [] glows []
fade-sq (* intensity intensity)] fade-in (* intensity intensity)
time-factor (/ star-time 60)
star-rotation (/ (* star-time math.pi 2) STAR_CYCLE_PERIOD)
cos-rot (math.cos star-rotation)
sin-rot (math.sin star-rotation)]
(each [_ star (ipairs tiny-stars)] (each [i star (ipairs tiny-stars)]
(let [screen (project-direction star.dx star.dy star.dz yaw pitch width height)] (let [screen (project-direction star.dx star.dy star.dz yaw pitch cos-rot sin-rot width height)]
(when screen (when screen
(let [int (math.floor (* star.brightness fade-sq))] (let [int (math.floor (* star.brightness fade-in))]
(when (> int 8) (when (> int 8)
(table.insert glows {:x screen.x :y screen.y (table.insert glows {:x screen.x :y screen.y
:radius 1 :radius 1
@ -194,53 +207,61 @@
:color star.color :color star.color
:shape effects.GLOW_CIRCLE})))))) :shape effects.GLOW_CIRCLE}))))))
(each [_ star (ipairs stars)] (each [i star (ipairs random-stars)]
(let [screen (project-direction star.dx star.dy star.dz yaw pitch width height)] (let [screen (project-direction star.dx star.dy star.dz yaw pitch cos-rot sin-rot width height)]
(when screen (when screen
(let [base-int (math.floor (* star.brightness 50 fade-sq 1.5))] (let [phase (+ (* i 2.137) (* time-factor 3.0))
(if (>= star.brightness 4) twinkle (+ 0.75 (* 0.25 (math.sin (* phase 6.28))))
(do int (math.floor (* star.brightness fade-in twinkle))]
(table.insert glows {:x screen.x :y screen.y (if (> star.brightness 220)
:radius 4
:intensity (math.floor (/ base-int 4))
:color star.color
:shape effects.GLOW_CIRCLE})
(table.insert glows {:x screen.x :y screen.y
:radius 2
:intensity base-int
:color star.color
:shape effects.GLOW_DIAMOND}))
(>= star.brightness 3)
(do (do
(table.insert glows {:x screen.x :y screen.y (table.insert glows {:x screen.x :y screen.y
:radius 3 :radius 3
:intensity (math.floor (/ base-int 4)) :intensity (math.floor (* int 1.5))
:color star.color
:shape effects.GLOW_CIRCLE})
(table.insert glows {:x screen.x :y screen.y
:radius 2
:intensity base-int
:color star.color
:shape effects.GLOW_DIAMOND}))
(>= star.brightness 2)
(table.insert glows {:x screen.x :y screen.y
:radius 2
:intensity base-int
:color star.color :color star.color
:shape effects.GLOW_DIAMOND}) :shape effects.GLOW_DIAMOND})
(table.insert glows {:x screen.x :y screen.y (table.insert glows {:x screen.x :y screen.y
:radius 1 :radius 5
:intensity (math.floor (* base-int 0.7)) :intensity (math.floor (/ int 2))
:color star.color
:shape effects.GLOW_CIRCLE}))
(> star.brightness 180)
(do
(table.insert glows {:x screen.x :y screen.y
:radius 2
:intensity int
:color star.color
:shape effects.GLOW_DIAMOND})
(table.insert glows {:x screen.x :y screen.y
:radius 4
:intensity (math.floor (/ int 3))
:color star.color
:shape effects.GLOW_CIRCLE}))
(> star.brightness 120)
(do
(table.insert glows {:x screen.x :y screen.y
:radius 2
:intensity (math.floor (* int 0.67))
:color star.color
:shape effects.GLOW_DIAMOND})
(table.insert glows {:x screen.x :y screen.y
:radius 3
:intensity (math.floor (/ int 4))
:color star.color
:shape effects.GLOW_CIRCLE}))
(table.insert glows {:x screen.x :y screen.y
:radius 2
:intensity (math.floor (* int 0.5))
:color star.color :color star.color
:shape effects.GLOW_CIRCLE})))))) :shape effects.GLOW_CIRCLE}))))))
(when (> (length glows) 0) (when (> (length glows) 0)
(effects.glows glows))))) (effects.glows glows)))))
(fn render [cam-x cam-y cam-z] (fn render [cam-x cam-y cam-z wireframe]
(when (not sky-mesh) (create-sky-dome)) (when (not sky-mesh) (create-sky-dome))
(when sky-mesh (when sky-mesh
(pxl8.draw_mesh sky-mesh {:x cam-x :y cam-y :z cam-z :passthrough true}))) (pxl8.draw_mesh sky-mesh {:x cam-x :y cam-y :z cam-z :passthrough true :wireframe wireframe})))
{:render render {:render render
:render-stars render-stars :render-stars render-stars

168
demo/mod/textures.fnl Normal file
View file

@ -0,0 +1,168 @@
(local pxl8 (require :pxl8))
(local procgen (require :pxl8.procgen))
(local textures {})
(fn build-graph [seed builder]
(let [g (pxl8.create_graph 128)
x (g:add_node procgen.OP_INPUT_X 0 0 0 0 0)
y (g:add_node procgen.OP_INPUT_Y 0 0 0 0 0)
ctx {:graph g :x x :y y}]
(g:set_seed seed)
(let [output (builder ctx)]
(g:set_output output)
g)))
(fn const [ctx val]
(ctx.graph:add_node procgen.OP_CONST 0 0 0 0 val))
(fn add [ctx a b]
(ctx.graph:add_node procgen.OP_ADD a b 0 0 0))
(fn sub [ctx a b]
(ctx.graph:add_node procgen.OP_SUB a b 0 0 0))
(fn mul [ctx a b]
(ctx.graph:add_node procgen.OP_MUL a b 0 0 0))
(fn div [ctx a b]
(ctx.graph:add_node procgen.OP_DIV a b 0 0 0))
(fn min-op [ctx a b]
(ctx.graph:add_node procgen.OP_MIN a b 0 0 0))
(fn max-op [ctx a b]
(ctx.graph:add_node procgen.OP_MAX a b 0 0 0))
(fn abs [ctx a]
(ctx.graph:add_node procgen.OP_ABS a 0 0 0 0))
(fn floor [ctx a]
(ctx.graph:add_node procgen.OP_FLOOR a 0 0 0 0))
(fn fract [ctx a]
(ctx.graph:add_node procgen.OP_FRACT a 0 0 0 0))
(fn lerp [ctx a b t]
(ctx.graph:add_node procgen.OP_LERP a b t 0 0))
(fn clamp [ctx val lo hi]
(ctx.graph:add_node procgen.OP_CLAMP val lo hi 0 0))
(fn select [ctx cond a b]
(ctx.graph:add_node procgen.OP_SELECT a b cond 0 0))
(fn smoothstep [ctx edge0 edge1 x]
(ctx.graph:add_node procgen.OP_SMOOTHSTEP edge0 edge1 x 0 0))
(fn noise-value [ctx scale]
(let [s (const ctx scale)]
(ctx.graph:add_node procgen.OP_NOISE_VALUE ctx.x ctx.y s 0 0)))
(fn noise-perlin [ctx scale]
(let [s (const ctx scale)]
(ctx.graph:add_node procgen.OP_NOISE_PERLIN ctx.x ctx.y s 0 0)))
(fn noise-fbm [ctx octaves scale persistence]
(let [s (const ctx scale)
p (const ctx persistence)]
(ctx.graph:add_node procgen.OP_NOISE_FBM ctx.x ctx.y s p octaves)))
(fn noise-ridged [ctx octaves scale persistence]
(let [s (const ctx scale)
p (const ctx persistence)]
(ctx.graph:add_node procgen.OP_NOISE_RIDGED ctx.x ctx.y s p octaves)))
(fn noise-turbulence [ctx octaves scale persistence]
(let [s (const ctx scale)
p (const ctx persistence)]
(ctx.graph:add_node procgen.OP_NOISE_TURBULENCE ctx.x ctx.y s p octaves)))
(fn voronoi-cell [ctx scale]
(let [s (const ctx scale)]
(ctx.graph:add_node procgen.OP_VORONOI_CELL ctx.x ctx.y s 0 0)))
(fn voronoi-edge [ctx scale]
(let [s (const ctx scale)]
(ctx.graph:add_node procgen.OP_VORONOI_EDGE ctx.x ctx.y s 0 0)))
(fn voronoi-id [ctx scale]
(let [s (const ctx scale)]
(ctx.graph:add_node procgen.OP_VORONOI_ID ctx.x ctx.y s 0 0)))
(fn gradient-linear [ctx angle]
(let [a (const ctx angle)]
(ctx.graph:add_node procgen.OP_GRADIENT_LINEAR ctx.x ctx.y a 0 0)))
(fn gradient-radial [ctx cx cy]
(let [center-x (const ctx cx)
center-y (const ctx cy)]
(ctx.graph:add_node procgen.OP_GRADIENT_RADIAL ctx.x ctx.y center-x center-y 0)))
(fn quantize [ctx val base range]
(let [r (const ctx range)]
(ctx.graph:add_node procgen.OP_QUANTIZE val r 0 0 base)))
(fn textures.mossy-cobblestone [seed base-color moss-color]
(let [g (build-graph seed
(fn [ctx]
(let [cell (voronoi-cell ctx 6)
edge (voronoi-edge ctx 6)
mortar-threshold (const ctx 0.05)
is-mortar (sub ctx mortar-threshold edge)
mortar-color (const ctx (- base-color 2))
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 base-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.ashlar-wall [seed base-color moss-color]
(let [g (build-graph seed
(fn [ctx]
(let [cell (voronoi-cell ctx 5)
edge (voronoi-edge ctx 5)
cell-id (voronoi-id ctx 5)
mortar-threshold (const ctx 0.12)
is-mortar (sub ctx mortar-threshold edge)
stone-detail (noise-fbm ctx 3 32 0.5)
stone-tint (mul ctx cell-id (const ctx 0.4))
stone-shade (mul ctx cell (const ctx 0.3))
stone-combined (add ctx stone-detail (add ctx stone-tint stone-shade))
stone-quant (quantize ctx stone-combined base-color 10)
crack-moss (noise-fbm ctx 3 16 0.5)
moss-in-crack (mul ctx crack-moss (sub ctx (const ctx 0.2) edge))
moss-threshold (const ctx 0.06)
has-moss (sub ctx moss-in-crack moss-threshold)
moss-quant (quantize ctx crack-moss moss-color 4)
mortar-color (const ctx (+ base-color 1))
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.gradient-sky [seed zenith-color horizon-color]
(let [g (build-graph seed
(fn [ctx]
(let [grad (gradient-linear ctx (* math.pi 0.5))
zenith (const ctx zenith-color)
horizon (const ctx horizon-color)
range (const ctx (- horizon-color zenith-color))]
(quantize ctx grad zenith-color (- horizon-color zenith-color)))))]
(let [tex-id (g:eval_texture 64 64)]
(g:destroy)
tex-id)))
textures

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

36
pxl8.sh
View file

@ -62,12 +62,17 @@ build_server() {
print_info "Building server ($mode mode)" print_info "Building server ($mode mode)"
cd server cd server
if [[ "$mode" == "release" ]]; then if [[ "$mode" == "release" ]]; then
cargo build --release > /dev/null 2>&1 cargo build --release
else else
cargo build > /dev/null 2>&1 cargo build
fi fi
local status=$?
cd - > /dev/null cd - > /dev/null
if [[ $status -eq 0 ]]; then
print_info "Built server" print_info "Built server"
else
print_error "Server build failed"
fi
fi fi
} }
@ -79,11 +84,16 @@ start_server() {
else else
server_bin="server/target/debug/pxl8-server" server_bin="server/target/debug/pxl8-server"
fi fi
print_info "Server mode: $mode, binary: $server_bin"
if [[ -f "$server_bin" ]]; then if [[ -f "$server_bin" ]]; then
print_info "Starting server" print_info "Starting server..."
./$server_bin & ./$server_bin &
SERVER_PID=$! SERVER_PID=$!
sleep 0.2 print_info "Server started with PID $SERVER_PID"
sleep 0.5
else
print_error "Server binary not found: $server_bin"
print_error "Build the server first with: cd server && cargo build"
fi fi
} }
@ -213,7 +223,7 @@ timestamp() {
update_fennel() { update_fennel() {
print_info "Fetching Fennel" print_info "Fetching Fennel"
local version="1.6.0" local version="1.6.1"
if curl -sL --max-time 5 -o lib/fennel/fennel.lua "https://fennel-lang.org/downloads/fennel-${version}.lua" 2>/dev/null; then if curl -sL --max-time 5 -o lib/fennel/fennel.lua "https://fennel-lang.org/downloads/fennel-${version}.lua" 2>/dev/null; then
if [[ -f "lib/fennel/fennel.lua" ]] && [[ -s "lib/fennel/fennel.lua" ]]; then if [[ -f "lib/fennel/fennel.lua" ]] && [[ -s "lib/fennel/fennel.lua" ]]; then
@ -304,7 +314,8 @@ for arg in "$@"; do
done done
if [ "$MODE" = "release" ]; then if [ "$MODE" = "release" ]; then
CFLAGS="$CFLAGS -O3 -ffast-math -funroll-loops -fno-unwind-tables -fno-asynchronous-unwind-tables" CFLAGS="$CFLAGS -O3 -flto -ffast-math -funroll-loops -fno-unwind-tables -fno-asynchronous-unwind-tables"
LINKER_FLAGS="$LINKER_FLAGS -flto"
BUILDDIR="$BUILDDIR/release" BUILDDIR="$BUILDDIR/release"
BINDIR="$BINDIR/release" BINDIR="$BINDIR/release"
else else
@ -313,7 +324,7 @@ else
BINDIR="$BINDIR/debug" BINDIR="$BINDIR/debug"
fi fi
DEP_CFLAGS="-O3 -ffast-math -funroll-loops" DEP_CFLAGS="-O3 -funroll-loops"
case "$COMMAND" in case "$COMMAND" in
build) build)
@ -360,7 +371,7 @@ case "$COMMAND" in
print_info "Compiler cache: ccache enabled" print_info "Compiler cache: ccache enabled"
fi fi
INCLUDES="-Isrc/core -Isrc/math -Isrc/gfx -Isrc/sfx -Isrc/script -Isrc/hal -Isrc/world -Isrc/asset -Isrc/game -Isrc/net -Ilib -Ilib/luajit/src -Ilib/linenoise -Ilib/miniz" 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"
COMPILE_FLAGS="$CFLAGS $INCLUDES" COMPILE_FLAGS="$CFLAGS $INCLUDES"
DEP_COMPILE_FLAGS="$DEP_CFLAGS $INCLUDES" DEP_COMPILE_FLAGS="$DEP_CFLAGS $INCLUDES"
@ -377,7 +388,6 @@ case "$COMMAND" in
src/math/pxl8_math.c src/math/pxl8_math.c
src/gfx/pxl8_anim.c src/gfx/pxl8_anim.c
src/gfx/pxl8_atlas.c src/gfx/pxl8_atlas.c
src/gfx/pxl8_blend.c
src/gfx/pxl8_blit.c src/gfx/pxl8_blit.c
src/gfx/pxl8_3d_camera.c src/gfx/pxl8_3d_camera.c
src/gfx/pxl8_colormap.c src/gfx/pxl8_colormap.c
@ -385,6 +395,7 @@ case "$COMMAND" in
src/gfx/pxl8_dither.c src/gfx/pxl8_dither.c
src/gfx/pxl8_font.c src/gfx/pxl8_font.c
src/gfx/pxl8_gfx.c src/gfx/pxl8_gfx.c
src/gfx/pxl8_lightmap.c
src/gfx/pxl8_mesh.c src/gfx/pxl8_mesh.c
src/gfx/pxl8_palette.c src/gfx/pxl8_palette.c
src/gfx/pxl8_particles.c src/gfx/pxl8_particles.c
@ -398,11 +409,12 @@ case "$COMMAND" in
src/world/pxl8_bsp.c src/world/pxl8_bsp.c
src/world/pxl8_gen.c src/world/pxl8_gen.c
src/world/pxl8_world.c src/world/pxl8_world.c
src/procgen/pxl8_graph.c
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/game/pxl8_gui.c src/gui/pxl8_gui.c
src/game/pxl8_replay.c src/core/pxl8_replay.c
src/net/pxl8_net.c src/net/pxl8_net.c
src/net/pxl8_protocol.c src/net/pxl8_protocol.c
" "
@ -477,7 +489,7 @@ case "$COMMAND" in
RUN_SERVER=false RUN_SERVER=false
for arg in "$@"; do for arg in "$@"; do
if [[ "$arg" == "--release" ]]; then if [[ "$arg" == "--release" ]]; then
continue MODE="release"
elif [[ "$arg" == "--repl" ]]; then elif [[ "$arg" == "--repl" ]]; then
EXTRA_ARGS="$EXTRA_ARGS --repl" EXTRA_ARGS="$EXTRA_ARGS --repl"
elif [[ "$arg" == "--server" ]]; then elif [[ "$arg" == "--server" ]]; then

View file

@ -3,14 +3,14 @@ use std::path::PathBuf;
fn main() { fn main() {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let client_src = PathBuf::from(&manifest_dir).join("../client/src"); let pxl8_src = PathBuf::from(&manifest_dir).join("../src");
println!("cargo:rerun-if-changed=../client/src/net/pxl8_protocol.h"); println!("cargo:rerun-if-changed=../src/net/pxl8_protocol.h");
println!("cargo:rerun-if-changed=../client/src/core/pxl8_types.h"); println!("cargo:rerun-if-changed=../src/core/pxl8_types.h");
let bindings = bindgen::Builder::default() let bindings = bindgen::Builder::default()
.header(client_src.join("net/pxl8_protocol.h").to_str().unwrap()) .header(pxl8_src.join("net/pxl8_protocol.h").to_str().unwrap())
.clang_arg(format!("-I{}", client_src.join("core").display())) .clang_arg(format!("-I{}", pxl8_src.join("core").display()))
.use_core() .use_core()
.rustified_enum(".*") .rustified_enum(".*")
.generate() .generate()

View file

@ -6,7 +6,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <pthread.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
@ -310,7 +309,7 @@ pxl8_result pxl8_update(pxl8* sys) {
if (pxl8_script_load_module(game->script, "pxl8") != PXL8_OK) { if (pxl8_script_load_module(game->script, "pxl8") != PXL8_OK) {
const char* err_msg = pxl8_script_get_last_error(game->script); const char* err_msg = pxl8_script_get_last_error(game->script);
pxl8_error("failed to setup pxl8 global: %s", err_msg); pxl8_error("failed to load pxl8 global: %s", err_msg);
} }
sys->repl = pxl8_repl_create(); sys->repl = pxl8_repl_create();

View file

@ -56,8 +56,8 @@ void pxl8_3d_camera_lookat(pxl8_3d_camera* cam, pxl8_vec3 eye, pxl8_vec3 target,
pxl8_vec3 forward = pxl8_vec3_normalize(pxl8_vec3_sub(target, eye)); pxl8_vec3 forward = pxl8_vec3_normalize(pxl8_vec3_sub(target, eye));
cam->pitch = asinf(-forward.y); cam->pitch = asinf(forward.y);
cam->yaw = atan2f(forward.x, forward.z); cam->yaw = atan2f(-forward.x, -forward.z);
cam->roll = 0; cam->roll = 0;
(void)up; (void)up;
@ -104,9 +104,9 @@ pxl8_vec3 pxl8_3d_camera_get_forward(const pxl8_3d_camera* cam) {
f32 sy = sinf(cam->yaw); f32 sy = sinf(cam->yaw);
return (pxl8_vec3){ return (pxl8_vec3){
cp * sy, -sy * cp,
-sp, sp,
cp * cy -cy * cp
}; };
} }
@ -183,8 +183,8 @@ void pxl8_3d_camera_follow(pxl8_3d_camera* cam, pxl8_vec3 target, pxl8_vec3 offs
cam->position = pxl8_vec3_lerp(cam->position, desired, t); cam->position = pxl8_vec3_lerp(cam->position, desired, t);
pxl8_vec3 forward = pxl8_vec3_normalize(pxl8_vec3_sub(target, cam->position)); pxl8_vec3 forward = pxl8_vec3_normalize(pxl8_vec3_sub(target, cam->position));
cam->pitch = asinf(-forward.y); cam->pitch = asinf(forward.y);
cam->yaw = atan2f(forward.x, forward.z); cam->yaw = atan2f(-forward.x, -forward.z);
} }
void pxl8_3d_camera_shake(pxl8_3d_camera* cam, f32 intensity, f32 duration) { void pxl8_3d_camera_shake(pxl8_3d_camera* cam, f32 intensity, f32 duration) {
@ -212,3 +212,30 @@ void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt) {
} }
} }
} }
pxl8_projected_point pxl8_3d_camera_world_to_screen(const pxl8_3d_camera* cam, pxl8_vec3 world_pos, u32 screen_width, u32 screen_height) {
pxl8_projected_point result = {0, 0, 0.0f, false};
if (!cam) return result;
pxl8_mat4 view = pxl8_3d_camera_get_view(cam);
pxl8_mat4 proj = pxl8_3d_camera_get_projection(cam);
pxl8_mat4 vp = pxl8_mat4_mul(proj, view);
pxl8_vec4 clip = pxl8_mat4_mul_vec4(vp, (pxl8_vec4){world_pos.x, world_pos.y, world_pos.z, 1.0f});
if (clip.w <= 0.0f) return result;
f32 inv_w = 1.0f / clip.w;
f32 ndc_x = clip.x * inv_w;
f32 ndc_y = clip.y * inv_w;
f32 ndc_z = clip.z * inv_w;
if (ndc_x < -1.0f || ndc_x > 1.0f || ndc_y < -1.0f || ndc_y > 1.0f) return result;
result.x = (i32)((ndc_x + 1.0f) * 0.5f * (f32)screen_width);
result.y = (i32)((1.0f - ndc_y) * 0.5f * (f32)screen_height);
result.depth = ndc_z;
result.visible = true;
return result;
}

View file

@ -32,6 +32,7 @@ pxl8_mat4 pxl8_3d_camera_get_view(const pxl8_3d_camera* cam);
void pxl8_3d_camera_blend(pxl8_3d_camera* dest, const pxl8_3d_camera* a, const pxl8_3d_camera* b, f32 t); void pxl8_3d_camera_blend(pxl8_3d_camera* dest, const pxl8_3d_camera* a, const pxl8_3d_camera* b, f32 t);
void pxl8_3d_camera_follow(pxl8_3d_camera* cam, pxl8_vec3 target, pxl8_vec3 offset, f32 smoothing, f32 dt); void pxl8_3d_camera_follow(pxl8_3d_camera* cam, pxl8_vec3 target, pxl8_vec3 offset, f32 smoothing, f32 dt);
pxl8_projected_point pxl8_3d_camera_world_to_screen(const pxl8_3d_camera* cam, pxl8_vec3 world_pos, u32 screen_width, u32 screen_height);
void pxl8_3d_camera_shake(pxl8_3d_camera* cam, f32 intensity, f32 duration); void pxl8_3d_camera_shake(pxl8_3d_camera* cam, f32 intensity, f32 duration);
void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt); void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt);

View file

@ -27,6 +27,8 @@ typedef struct pxl8_skyline {
struct pxl8_atlas { struct pxl8_atlas {
u32 height, width; u32 height, width;
u8* pixels; u8* pixels;
u8* pixels_tiled;
u32 tiled_capacity, tiled_size;
bool dirty; bool dirty;
@ -195,6 +197,7 @@ void pxl8_atlas_destroy(pxl8_atlas* atlas) {
free(atlas->entries); free(atlas->entries);
free(atlas->free_list); free(atlas->free_list);
free(atlas->pixels); free(atlas->pixels);
free(atlas->pixels_tiled);
free(atlas->skyline.nodes); free(atlas->skyline.nodes);
free(atlas); free(atlas);
} }
@ -209,6 +212,13 @@ void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count) {
atlas->entry_count = preserve_count; atlas->entry_count = preserve_count;
atlas->free_count = 0; atlas->free_count = 0;
if (preserve_count == 0) {
atlas->tiled_size = 0;
} else {
pxl8_atlas_entry* last = &atlas->entries[preserve_count - 1];
atlas->tiled_size = last->tiled_base + (u32)(last->w * last->h);
}
atlas->skyline.nodes[0] = (pxl8_skyline_node){0, 0, (i32)atlas->width}; atlas->skyline.nodes[0] = (pxl8_skyline_node){0, 0, (i32)atlas->width};
atlas->skyline.count = 1; atlas->skyline.count = 1;
@ -334,6 +344,7 @@ u32 pxl8_atlas_add_texture(
entry->y = fit.pos.y; entry->y = fit.pos.y;
entry->w = w; entry->w = w;
entry->h = h; entry->h = h;
entry->log2_w = pxl8_log2(w);
i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode); i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode);
for (u32 y = 0; y < h; y++) { for (u32 y = 0; y < h; y++) {
@ -349,6 +360,30 @@ u32 pxl8_atlas_add_texture(
} }
} }
u32 tiled_tex_size = w * h;
u32 new_tiled_size = atlas->tiled_size + tiled_tex_size;
if (new_tiled_size > atlas->tiled_capacity) {
u32 new_cap = atlas->tiled_capacity ? atlas->tiled_capacity * 2 : 4096;
while (new_cap < new_tiled_size) new_cap *= 2;
u8* new_tiled = (u8*)realloc(atlas->pixels_tiled, new_cap);
if (!new_tiled) {
entry->active = false;
return UINT32_MAX;
}
atlas->pixels_tiled = new_tiled;
atlas->tiled_capacity = new_cap;
}
entry->tiled_base = atlas->tiled_size;
u8* tiled_dst = atlas->pixels_tiled + entry->tiled_base;
for (u32 ty = 0; ty < h; ty++) {
for (u32 tx = 0; tx < w; tx++) {
u32 tiled_offset = pxl8_tile_addr(tx, ty, entry->log2_w);
tiled_dst[tiled_offset] = pixels[ty * w + tx];
}
}
atlas->tiled_size = new_tiled_size;
if (!pxl8_skyline_add_rect(&atlas->skyline, fit.pos, w, h)) { if (!pxl8_skyline_add_rect(&atlas->skyline, fit.pos, w, h)) {
entry->active = false; entry->active = false;
return UINT32_MAX; return UINT32_MAX;
@ -377,6 +412,10 @@ const u8* pxl8_atlas_get_pixels(const pxl8_atlas* atlas) {
return atlas ? atlas->pixels : NULL; return atlas ? atlas->pixels : NULL;
} }
const u8* pxl8_atlas_get_pixels_tiled(const pxl8_atlas* atlas) {
return atlas ? atlas->pixels_tiled : NULL;
}
u32 pxl8_atlas_get_width(const pxl8_atlas* atlas) { u32 pxl8_atlas_get_width(const pxl8_atlas* atlas) {
return atlas ? atlas->width : 0; return atlas ? atlas->width : 0;
} }

View file

@ -8,28 +8,42 @@ typedef struct pxl8_atlas_entry {
bool active; bool active;
u32 texture_id; u32 texture_id;
i32 x, y, w, h; i32 x, y, w, h;
u32 tiled_base;
u8 log2_w;
} pxl8_atlas_entry; } pxl8_atlas_entry;
static inline u32 pxl8_tile_addr(u32 u, u32 v, u8 log2_w) {
u32 tile_y = v >> 3;
u32 tile_x = u >> 3;
u32 local_y = v & 7;
u32 local_x = u & 7;
return (tile_y << (log2_w + 3)) | (tile_x << 6) | (local_y << 3) | local_x;
}
static inline u8 pxl8_log2(u32 v) {
u8 r = 0;
while (v >>= 1) r++;
return r;
}
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h, pxl8_pixel_mode pixel_mode);
void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count);
pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode); pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode);
void pxl8_atlas_destroy(pxl8_atlas* atlas); void pxl8_atlas_destroy(pxl8_atlas* atlas);
bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode);
const pxl8_atlas_entry* pxl8_atlas_get_entry(const pxl8_atlas* atlas, u32 id); const pxl8_atlas_entry* pxl8_atlas_get_entry(const pxl8_atlas* atlas, u32 id);
u32 pxl8_atlas_get_entry_count(const pxl8_atlas* atlas); u32 pxl8_atlas_get_entry_count(const pxl8_atlas* atlas);
u32 pxl8_atlas_get_height(const pxl8_atlas* atlas); u32 pxl8_atlas_get_height(const pxl8_atlas* atlas);
const u8* pxl8_atlas_get_pixels(const pxl8_atlas* atlas); const u8* pxl8_atlas_get_pixels(const pxl8_atlas* atlas);
const u8* pxl8_atlas_get_pixels_tiled(const pxl8_atlas* atlas);
u32 pxl8_atlas_get_width(const pxl8_atlas* atlas); u32 pxl8_atlas_get_width(const pxl8_atlas* atlas);
bool pxl8_atlas_is_dirty(const pxl8_atlas* atlas); bool pxl8_atlas_is_dirty(const pxl8_atlas* atlas);
void pxl8_atlas_mark_clean(pxl8_atlas* atlas); void pxl8_atlas_mark_clean(pxl8_atlas* atlas);
u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h, pxl8_pixel_mode pixel_mode);
void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count);
bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -1,194 +0,0 @@
#include "pxl8_blend.h"
#include "pxl8_colormap.h"
#include <stdlib.h>
struct pxl8_palette_cube {
u8 colors[PXL8_PALETTE_SIZE * 3];
u8 table[PXL8_CUBE_ENTRIES];
u8 stable[PXL8_CUBE_ENTRIES];
};
struct pxl8_additive_table {
u8 table[PXL8_BLEND_TABLE_SIZE];
};
struct pxl8_overbright_table {
u8 table[PXL8_OVERBRIGHT_TABLE_SIZE];
};
static u8 find_closest_stable(const pxl8_palette* pal, u8 r, u8 g, u8 b) {
u8 best_idx = 1;
u32 best_dist = 0xFFFFFFFF;
u8 dynamic_end = PXL8_DYNAMIC_RANGE_START + PXL8_DYNAMIC_RANGE_COUNT;
for (u32 i = 1; i < PXL8_FULLBRIGHT_START; i++) {
if (i >= PXL8_DYNAMIC_RANGE_START && i < dynamic_end) {
continue;
}
u8 pr, pg, pb;
pxl8_palette_get_rgb(pal, (u8)i, &pr, &pg, &pb);
i32 dr = (i32)r - (i32)pr;
i32 dg = (i32)g - (i32)pg;
i32 db = (i32)b - (i32)pb;
u32 dist = (u32)(dr * dr + dg * dg + db * db);
if (dist < best_dist) {
best_dist = dist;
best_idx = (u8)i;
if (dist == 0) break;
}
}
return best_idx;
}
pxl8_palette_cube* pxl8_palette_cube_create(const pxl8_palette* pal) {
pxl8_palette_cube* cube = calloc(1, sizeof(pxl8_palette_cube));
if (!cube) return NULL;
pxl8_palette_cube_rebuild(cube, pal);
return cube;
}
void pxl8_palette_cube_destroy(pxl8_palette_cube* cube) {
free(cube);
}
void pxl8_palette_cube_rebuild(pxl8_palette_cube* cube, const pxl8_palette* pal) {
if (!cube || !pal) return;
for (u32 i = 0; i < PXL8_PALETTE_SIZE; i++) {
u8 r, g, b;
pxl8_palette_get_rgb(pal, (u8)i, &r, &g, &b);
cube->colors[i * 3 + 0] = r;
cube->colors[i * 3 + 1] = g;
cube->colors[i * 3 + 2] = b;
}
for (u32 bi = 0; bi < PXL8_CUBE_SIZE; bi++) {
for (u32 gi = 0; gi < PXL8_CUBE_SIZE; gi++) {
for (u32 ri = 0; ri < PXL8_CUBE_SIZE; ri++) {
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
u8 r8 = (u8)((ri * 255) / (PXL8_CUBE_SIZE - 1));
u8 g8 = (u8)((gi * 255) / (PXL8_CUBE_SIZE - 1));
u8 b8 = (u8)((bi * 255) / (PXL8_CUBE_SIZE - 1));
cube->table[idx] = pxl8_palette_find_closest(pal, r8, g8, b8);
cube->stable[idx] = find_closest_stable(pal, r8, g8, b8);
}
}
}
}
u8 pxl8_palette_cube_lookup(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b) {
u32 ri = (r * (PXL8_CUBE_SIZE - 1)) / 255;
u32 gi = (g * (PXL8_CUBE_SIZE - 1)) / 255;
u32 bi = (b * (PXL8_CUBE_SIZE - 1)) / 255;
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
return cube->table[idx];
}
u8 pxl8_palette_cube_lookup_stable(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b) {
u32 ri = (r * (PXL8_CUBE_SIZE - 1)) / 255;
u32 gi = (g * (PXL8_CUBE_SIZE - 1)) / 255;
u32 bi = (b * (PXL8_CUBE_SIZE - 1)) / 255;
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
return cube->stable[idx];
}
void pxl8_palette_cube_get_rgb(const pxl8_palette_cube* cube, u8 idx, u8* r, u8* g, u8* b) {
*r = cube->colors[idx * 3 + 0];
*g = cube->colors[idx * 3 + 1];
*b = cube->colors[idx * 3 + 2];
}
pxl8_additive_table* pxl8_additive_table_create(const pxl8_palette* pal) {
pxl8_additive_table* table = calloc(1, sizeof(pxl8_additive_table));
if (!table) return NULL;
pxl8_additive_table_rebuild(table, pal);
return table;
}
void pxl8_additive_table_destroy(pxl8_additive_table* table) {
free(table);
}
void pxl8_additive_table_rebuild(pxl8_additive_table* table, const pxl8_palette* pal) {
if (!table || !pal) return;
for (u32 src = 0; src < 256; src++) {
u8 sr, sg, sb;
pxl8_palette_get_rgb(pal, (u8)src, &sr, &sg, &sb);
for (u32 dst = 0; dst < 256; dst++) {
u32 idx = src * 256 + dst;
if (src == PXL8_TRANSPARENT) {
table->table[idx] = (u8)dst;
continue;
}
u8 dr, dg, db;
pxl8_palette_get_rgb(pal, (u8)dst, &dr, &dg, &db);
u16 ar = (u16)sr + (u16)dr;
u16 ag = (u16)sg + (u16)dg;
u16 ab = (u16)sb + (u16)db;
if (ar > 255) ar = 255;
if (ag > 255) ag = 255;
if (ab > 255) ab = 255;
table->table[idx] = find_closest_stable(pal, (u8)ar, (u8)ag, (u8)ab);
}
}
}
u8 pxl8_additive_blend(const pxl8_additive_table* table, u8 src, u8 dst) {
return table->table[(u32)src * 256 + dst];
}
pxl8_overbright_table* pxl8_overbright_table_create(const pxl8_palette* pal) {
pxl8_overbright_table* table = calloc(1, sizeof(pxl8_overbright_table));
if (!table) return NULL;
pxl8_overbright_table_rebuild(table, pal);
return table;
}
void pxl8_overbright_table_destroy(pxl8_overbright_table* table) {
free(table);
}
void pxl8_overbright_table_rebuild(pxl8_overbright_table* table, const pxl8_palette* pal) {
if (!table || !pal) return;
for (u32 level = 0; level < PXL8_OVERBRIGHT_LEVELS; level++) {
f32 overbright = (f32)level / (f32)(PXL8_OVERBRIGHT_LEVELS - 1);
for (u32 pal_idx = 0; pal_idx < 256; pal_idx++) {
u32 idx = level * 256 + pal_idx;
if (pal_idx == PXL8_TRANSPARENT) {
table->table[idx] = PXL8_TRANSPARENT;
continue;
}
u8 r, g, b;
pxl8_palette_get_rgb(pal, (u8)pal_idx, &r, &g, &b);
u8 or = (u8)(r + (255 - r) * overbright);
u8 og = (u8)(g + (255 - g) * overbright);
u8 ob = (u8)(b + (255 - b) * overbright);
table->table[idx] = find_closest_stable(pal, or, og, ob);
}
}
}
u8 pxl8_overbright_lookup(const pxl8_overbright_table* table, u8 pal_idx, f32 emissive) {
u32 level = (u32)(emissive * (PXL8_OVERBRIGHT_LEVELS - 1));
if (level >= PXL8_OVERBRIGHT_LEVELS) level = PXL8_OVERBRIGHT_LEVELS - 1;
return table->table[level * 256 + pal_idx];
}

View file

@ -1,39 +0,0 @@
#pragma once
#include "pxl8_palette.h"
#include "pxl8_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#define PXL8_CUBE_SIZE 32
#define PXL8_CUBE_ENTRIES (PXL8_CUBE_SIZE * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE)
#define PXL8_BLEND_TABLE_SIZE (256 * 256)
#define PXL8_OVERBRIGHT_LEVELS 16
#define PXL8_OVERBRIGHT_TABLE_SIZE (256 * PXL8_OVERBRIGHT_LEVELS)
typedef struct pxl8_additive_table pxl8_additive_table;
typedef struct pxl8_overbright_table pxl8_overbright_table;
typedef struct pxl8_palette_cube pxl8_palette_cube;
pxl8_additive_table* pxl8_additive_table_create(const pxl8_palette* pal);
void pxl8_additive_table_destroy(pxl8_additive_table* table);
void pxl8_additive_table_rebuild(pxl8_additive_table* table, const pxl8_palette* pal);
u8 pxl8_additive_blend(const pxl8_additive_table* table, u8 src, u8 dst);
pxl8_overbright_table* pxl8_overbright_table_create(const pxl8_palette* pal);
void pxl8_overbright_table_destroy(pxl8_overbright_table* table);
void pxl8_overbright_table_rebuild(pxl8_overbright_table* table, const pxl8_palette* pal);
u8 pxl8_overbright_lookup(const pxl8_overbright_table* table, u8 pal_idx, f32 emissive);
pxl8_palette_cube* pxl8_palette_cube_create(const pxl8_palette* pal);
void pxl8_palette_cube_destroy(pxl8_palette_cube* cube);
void pxl8_palette_cube_rebuild(pxl8_palette_cube* cube, const pxl8_palette* pal);
u8 pxl8_palette_cube_lookup(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b);
u8 pxl8_palette_cube_lookup_stable(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b);
void pxl8_palette_cube_get_rgb(const pxl8_palette_cube* cube, u8 idx, u8* r, u8* g, u8* b);
#ifdef __cplusplus
}
#endif

View file

@ -1,42 +1,12 @@
#include "pxl8_colormap.h" #include "pxl8_colormap.h"
#include <string.h> #include <string.h>
static void rgb_to_hsl(u8 r, u8 g, u8 b, i32* h, i32* s, i32* l) {
i32 max = r > g ? (r > b ? r : b) : (g > b ? g : b);
i32 min = r < g ? (r < b ? r : b) : (g < b ? g : b);
i32 chroma = max - min;
*l = (max + min) / 2;
if (chroma == 0) {
*h = 0;
*s = 0;
} else {
i32 denom = 255 - (*l > 127 ? 2 * (*l) - 255 : 255 - 2 * (*l));
if (denom <= 0) denom = 1;
*s = (chroma * 255) / denom;
if (*s > 255) *s = 255;
if (max == (i32)r) {
*h = (((i32)g - (i32)b) * 60) / chroma;
if (*h < 0) *h += 360;
} else if (max == (i32)g) {
*h = 120 + (((i32)b - (i32)r) * 60) / chroma;
} else {
*h = 240 + (((i32)r - (i32)g) * 60) / chroma;
}
}
}
static u8 find_closest_color(const u32* palette, u8 target_r, u8 target_g, u8 target_b) { static u8 find_closest_color(const u32* palette, u8 target_r, u8 target_g, u8 target_b) {
u8 best_idx = 1; u8 best_idx = 1;
u32 best_dist = 0xFFFFFFFF; u32 best_dist = 0xFFFFFFFF;
u8 dynamic_end = PXL8_DYNAMIC_RANGE_START + PXL8_DYNAMIC_RANGE_COUNT; u8 dynamic_end = PXL8_DYNAMIC_RANGE_START + PXL8_DYNAMIC_RANGE_COUNT;
i32 th, ts, tl;
rgb_to_hsl(target_r, target_g, target_b, &th, &ts, &tl);
for (u32 i = 1; i < PXL8_FULLBRIGHT_START; i++) { for (u32 i = 1; i < PXL8_FULLBRIGHT_START; i++) {
if (i >= PXL8_DYNAMIC_RANGE_START && i < dynamic_end) { if (i >= PXL8_DYNAMIC_RANGE_START && i < dynamic_end) {
continue; continue;
@ -47,17 +17,10 @@ static u8 find_closest_color(const u32* palette, u8 target_r, u8 target_g, u8 ta
u8 pg = (c >> 8) & 0xFF; u8 pg = (c >> 8) & 0xFF;
u8 pb = (c >> 16) & 0xFF; u8 pb = (c >> 16) & 0xFF;
i32 ph, ps, pl; i32 dr = (i32)target_r - (i32)pr;
rgb_to_hsl(pr, pg, pb, &ph, &ps, &pl); i32 dg = (i32)target_g - (i32)pg;
i32 db = (i32)target_b - (i32)pb;
i32 dh = th - ph; u32 dist = (u32)(dr * dr + dg * dg + db * db);
if (dh > 180) dh -= 360;
if (dh < -180) dh += 360;
i32 ds = ts - ps;
i32 dl = tl - pl;
u32 dist = (u32)(dh * dh * 4 + ds * ds + dl * dl);
if (dist < best_dist) { if (dist < best_dist) {
best_dist = dist; best_dist = dist;
@ -69,26 +32,19 @@ static u8 find_closest_color(const u32* palette, u8 target_r, u8 target_g, u8 ta
return best_idx; return best_idx;
} }
void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette, const pxl8_level_tint* tint) { void pxl8_set_colormap(pxl8_colormap* cm, const u8* data, u32 size) {
if (!cm || !palette) return; if (!cm || !data || size == 0) return;
u32 copy_size = size > PXL8_COLORMAP_SIZE ? PXL8_COLORMAP_SIZE : size;
u8 dark_r, dark_g, dark_b; memcpy(cm->table, data, copy_size);
if (tint && tint->tint_strength > 0.0f) {
f32 t = tint->tint_strength;
f32 inv = 1.0f - t;
dark_r = (u8)(tint->dark_r * inv + tint->tint_r * t);
dark_g = (u8)(tint->dark_g * inv + tint->tint_g * t);
dark_b = (u8)(tint->dark_b * inv + tint->tint_b * t);
} else if (tint) {
dark_r = tint->dark_r;
dark_g = tint->dark_g;
dark_b = tint->dark_b;
} else {
dark_r = dark_g = dark_b = 0;
} }
for (u32 light = 0; light < PXL8_LIGHT_LEVELS; light++) { static void generate_light_table(pxl8_colormap* cm, const u32* palette, pxl8_light_color light_color) {
f32 brightness = (f32)light / (f32)(PXL8_LIGHT_LEVELS - 1); pxl8_rgb light = pxl8_light_colors[light_color];
u32 base_row = (u32)light_color * PXL8_LIGHT_LEVELS;
for (u32 level = 0; level < PXL8_LIGHT_LEVELS; level++) {
f32 brightness = (f32)level / (f32)(PXL8_LIGHT_LEVELS - 1);
u32 row = base_row + level;
for (u32 pal_idx = 0; pal_idx < 256; pal_idx++) { for (u32 pal_idx = 0; pal_idx < 256; pal_idx++) {
u8 result_idx; u8 result_idx;
@ -103,14 +59,65 @@ void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette, const pxl8_le
u8 g = (c >> 8) & 0xFF; u8 g = (c >> 8) & 0xFF;
u8 b = (c >> 16) & 0xFF; u8 b = (c >> 16) & 0xFF;
u8 target_r = (u8)(dark_r + (r - dark_r) * brightness); f32 lr = (f32)light.r / 255.0f;
u8 target_g = (u8)(dark_g + (g - dark_g) * brightness); f32 lg = (f32)light.g / 255.0f;
u8 target_b = (u8)(dark_b + (b - dark_b) * brightness); f32 lb = (f32)light.b / 255.0f;
u8 target_r = (u8)(r * brightness * lr);
u8 target_g = (u8)(g * brightness * lg);
u8 target_b = (u8)(b * brightness * lb);
result_idx = find_closest_color(palette, target_r, target_g, target_b); result_idx = find_closest_color(palette, target_r, target_g, target_b);
} }
cm->table[light * 256 + pal_idx] = result_idx; cm->table[row * 256 + pal_idx] = result_idx;
} }
} }
} }
static void generate_blend_table(pxl8_colormap* cm, const u32* palette) {
for (u32 src = 0; src < 256; src++) {
u32 row = PXL8_LIGHT_ROWS + src;
u8 sr, sg, sb;
if (src == PXL8_TRANSPARENT) {
sr = sg = sb = 0;
} else {
u32 sc = palette[src];
sr = sc & 0xFF;
sg = (sc >> 8) & 0xFF;
sb = (sc >> 16) & 0xFF;
}
for (u32 dst = 0; dst < 256; dst++) {
u8 result_idx;
if (src == PXL8_TRANSPARENT) {
result_idx = (u8)dst;
} else {
u32 dc = palette[dst];
u8 dr = dc & 0xFF;
u8 dg = (dc >> 8) & 0xFF;
u8 db = (dc >> 16) & 0xFF;
u8 blend_r = (u8)((sr + dr) / 2);
u8 blend_g = (u8)((sg + dg) / 2);
u8 blend_b = (u8)((sb + db) / 2);
result_idx = find_closest_color(palette, blend_r, blend_g, blend_b);
}
cm->table[row * 256 + dst] = result_idx;
}
}
}
void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette) {
if (!cm || !palette) return;
for (u32 light = 0; light < PXL8_LIGHT_COLORS; light++) {
generate_light_table(cm, palette, (pxl8_light_color)light);
}
generate_blend_table(cm, palette);
}

View file

@ -7,35 +7,65 @@
extern "C" { extern "C" {
#endif #endif
#define PXL8_LIGHT_LEVELS 64 #define PXL8_LIGHT_COLORS 8
#define PXL8_COLORMAP_SIZE (256 * PXL8_LIGHT_LEVELS) #define PXL8_LIGHT_LEVELS 8
#define PXL8_LIGHT_ROWS (PXL8_LIGHT_COLORS * PXL8_LIGHT_LEVELS)
#define PXL8_BLEND_ROWS 256
#define PXL8_COLORMAP_ROWS (PXL8_LIGHT_ROWS + PXL8_BLEND_ROWS)
#define PXL8_COLORMAP_SIZE (256 * PXL8_COLORMAP_ROWS)
#define PXL8_FULLBRIGHT_START 240 #define PXL8_FULLBRIGHT_START 240
#define PXL8_TRANSPARENT 0 #define PXL8_TRANSPARENT 0
#define PXL8_DYNAMIC_RANGE_START 144 #define PXL8_DYNAMIC_RANGE_START 144
#define PXL8_DYNAMIC_RANGE_COUNT 16 #define PXL8_DYNAMIC_RANGE_COUNT 16
typedef enum {
PXL8_LIGHT_WHITE = 0,
PXL8_LIGHT_RED = 1,
PXL8_LIGHT_ORANGE = 2,
PXL8_LIGHT_YELLOW = 3,
PXL8_LIGHT_GREEN = 4,
PXL8_LIGHT_CYAN = 5,
PXL8_LIGHT_BLUE = 6,
PXL8_LIGHT_PURPLE = 7,
} pxl8_light_color;
typedef struct {
u8 r, g, b;
} pxl8_rgb;
static const pxl8_rgb pxl8_light_colors[PXL8_LIGHT_COLORS] = {
{255, 255, 255},
{255, 64, 64},
{255, 160, 64},
{255, 255, 64},
{64, 255, 64},
{64, 255, 255},
{64, 64, 255},
{255, 64, 255},
};
typedef struct { typedef struct {
u8 table[PXL8_COLORMAP_SIZE]; u8 table[PXL8_COLORMAP_SIZE];
} pxl8_colormap; } pxl8_colormap;
typedef struct { void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette);
u8 dark_r, dark_g, dark_b; void pxl8_set_colormap(pxl8_colormap* cm, const u8* data, u32 size);
u8 tint_r, tint_g, tint_b;
f32 tint_strength;
} pxl8_level_tint;
void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette, const pxl8_level_tint* tint); static inline u8 pxl8_colormap_lookup(const pxl8_colormap* cm, u8 pal_idx, pxl8_light_color light_color, u8 intensity) {
u32 light_row = ((u32)light_color << 3) + (intensity >> 5);
static inline u8 pxl8_colormap_lookup(const pxl8_colormap* cm, u8 pal_idx, u8 light) { return cm->table[(light_row << 8) + pal_idx];
u32 light_idx = light >> 2;
return cm->table[light_idx * 256 + pal_idx];
} }
static inline u8 pxl8_colormap_lookup_dithered(const pxl8_colormap* cm, u8 pal_idx, u8 light, u32 x, u32 y) { static inline u8 pxl8_colormap_lookup_dithered(const pxl8_colormap* cm, u8 pal_idx, pxl8_light_color light_color, u8 intensity, u32 x, u32 y) {
u8 dithered = pxl8_dither_light(light, x, y); u8 dithered = pxl8_dither_light(intensity, x, y);
u32 light_idx = dithered >> 2; u32 light_row = ((u32)light_color << 3) + (dithered >> 5);
return cm->table[light_idx * 256 + pal_idx]; return cm->table[(light_row << 8) + pal_idx];
}
static inline u8 pxl8_colormap_blend(const pxl8_colormap* cm, u8 src, u8 dst) {
u32 blend_row = PXL8_LIGHT_ROWS + src;
return cm->table[(blend_row << 8) + dst];
} }
#ifdef __cplusplus #ifdef __cplusplus

View file

@ -1,10 +1,9 @@
#include "pxl8_cpu.h" #include "pxl8_cpu.h"
#include <math.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "pxl8_simd.h"
struct pxl8_cpu_render_target { struct pxl8_cpu_render_target {
u8* framebuffer; u8* framebuffer;
u32 height; u32 height;
@ -119,6 +118,7 @@ struct pxl8_cpu_backend {
pxl8_cpu_render_target* target_stack[PXL8_MAX_TARGET_STACK]; pxl8_cpu_render_target* target_stack[PXL8_MAX_TARGET_STACK];
u32 target_stack_depth; u32 target_stack_depth;
const pxl8_colormap* colormap; const pxl8_colormap* colormap;
const pxl8_palette_cube* palette_cube;
const u32* palette; const u32* palette;
pxl8_3d_frame frame; pxl8_3d_frame frame;
pxl8_mat4 mvp; pxl8_mat4 mvp;
@ -231,14 +231,12 @@ void pxl8_cpu_clear(pxl8_cpu_backend* cpu, u8 color) {
void pxl8_cpu_clear_depth(pxl8_cpu_backend* cpu) { void pxl8_cpu_clear_depth(pxl8_cpu_backend* cpu) {
if (!cpu || !cpu->current_target) return; if (!cpu || !cpu->current_target) return;
pxl8_cpu_render_target* render_target = cpu->current_target; pxl8_cpu_render_target* render_target = cpu->current_target;
if (render_target->zbuffer) {
u32 count = render_target->width * render_target->height; u32 count = render_target->width * render_target->height;
for (u32 i = 0; i < count; i++) { if (render_target->zbuffer) {
render_target->zbuffer[i] = 0xFFFF; memset(render_target->zbuffer, 0xFF, count * sizeof(u16));
}
} }
if (render_target->light_accum) { if (render_target->light_accum) {
memset(render_target->light_accum, 0, render_target->width * render_target->height * sizeof(u32)); memset(render_target->light_accum, 0, count * sizeof(u32));
} }
} }
@ -246,6 +244,10 @@ void pxl8_cpu_set_colormap(pxl8_cpu_backend* cpu, const pxl8_colormap* cm) {
if (cpu) cpu->colormap = cm; if (cpu) cpu->colormap = cm;
} }
void pxl8_cpu_set_palette_cube(pxl8_cpu_backend* cpu, const pxl8_palette_cube* cube) {
if (cpu) cpu->palette_cube = cube;
}
void pxl8_cpu_set_palette(pxl8_cpu_backend* cpu, const u32* palette) { void pxl8_cpu_set_palette(pxl8_cpu_backend* cpu, const u32* palette) {
if (cpu) cpu->palette = palette; if (cpu) cpu->palette = palette;
} }
@ -699,6 +701,22 @@ static void rasterize_triangle_opaque(
f32 v_end = (vw + dvw * steps) * pw_end; f32 v_end = (vw + dvw * steps) * pw_end;
f32 l_start = lw * pw_start; f32 l_start = lw * pw_start;
f32 l_end = (lw + d_lw * steps) * pw_end; f32 l_end = (lw + d_lw * steps) * pw_end;
f32 fog_density = cpu->frame.uniforms.fog_density;
if (fog_density > 0.0f) {
f32 z_end_fog = z + dz * steps;
f32 t_start = (z + 1.0f) * 0.5f;
f32 t_end = (z_end_fog + 1.0f) * 0.5f;
if (t_start < 0.0f) t_start = 0.0f;
if (t_end < 0.0f) t_end = 0.0f;
f32 fog_start = t_start * t_start * fog_density;
f32 fog_end = t_end * t_end * fog_density;
if (fog_start > 1.0f) fog_start = 1.0f;
if (fog_end > 1.0f) fog_end = 1.0f;
l_start *= (1.0f - fog_start);
l_end *= (1.0f - fog_end);
}
if (l_start > 255.0f) l_start = 255.0f; if (l_start > 255.0f) l_start = 255.0f;
if (l_end > 255.0f) l_end = 255.0f; if (l_end > 255.0f) l_end = 255.0f;
@ -727,7 +745,7 @@ static void rasterize_triangle_opaque(
if (dither) { if (dither) {
light = pxl8_dither_light(light, (u32)px, (u32)y); light = pxl8_dither_light(light, (u32)px, (u32)y);
} }
u8 pal_idx = cpu->colormap ? pxl8_colormap_lookup(cpu->colormap, tex_idx, light) : tex_idx; u8 pal_idx = cpu->colormap ? pxl8_colormap_lookup(cpu->colormap, tex_idx, PXL8_LIGHT_WHITE, light) : tex_idx;
prow[px] = pal_idx; prow[px] = pal_idx;
zrow[px] = z16; zrow[px] = z16;
if (light_accum) { if (light_accum) {
@ -948,6 +966,22 @@ static void rasterize_triangle_alpha(
f32 v_end = (vw + dvw * steps) * pw_end; f32 v_end = (vw + dvw * steps) * pw_end;
f32 l_start = lw * pw_start; f32 l_start = lw * pw_start;
f32 l_end = (lw + d_lw * steps) * pw_end; f32 l_end = (lw + d_lw * steps) * pw_end;
f32 fog_density = cpu->frame.uniforms.fog_density;
if (fog_density > 0.0f) {
f32 z_end_fog = z + dz * steps;
f32 t_start = (z + 1.0f) * 0.5f;
f32 t_end = (z_end_fog + 1.0f) * 0.5f;
if (t_start < 0.0f) t_start = 0.0f;
if (t_end < 0.0f) t_end = 0.0f;
f32 fog_start = t_start * t_start * fog_density;
f32 fog_end = t_end * t_end * fog_density;
if (fog_start > 1.0f) fog_start = 1.0f;
if (fog_end > 1.0f) fog_end = 1.0f;
l_start *= (1.0f - fog_start);
l_end *= (1.0f - fog_end);
}
if (l_start > 255.0f) l_start = 255.0f; if (l_start > 255.0f) l_start = 255.0f;
if (l_end > 255.0f) l_end = 255.0f; if (l_end > 255.0f) l_end = 255.0f;
@ -974,7 +1008,7 @@ static void rasterize_triangle_alpha(
if (dither) { if (dither) {
light = pxl8_dither_light(light, (u32)px, (u32)y); light = pxl8_dither_light(light, (u32)px, (u32)y);
} }
u8 src_idx = cpu->colormap ? pxl8_colormap_lookup(cpu->colormap, tex_idx, light) : tex_idx; u8 src_idx = cpu->colormap ? pxl8_colormap_lookup(cpu->colormap, tex_idx, PXL8_LIGHT_WHITE, light) : tex_idx;
if (mat_alpha >= 128) { if (mat_alpha >= 128) {
prow[px] = src_idx; prow[px] = src_idx;
@ -1002,11 +1036,43 @@ static void rasterize_triangle_alpha(
} }
} }
static void rasterize_triangle_wireframe(
pxl8_cpu_backend* cpu,
const vertex_output* vo0, const vertex_output* vo1, const vertex_output* vo2,
u8 color, bool double_sided
) {
pxl8_cpu_render_target* render_target = cpu->current_target;
f32 hw = (f32)render_target->width * 0.5f;
f32 hh = (f32)render_target->height * 0.5f;
i32 x0 = (i32)(hw + vo0->clip_pos.x / vo0->clip_pos.w * hw);
i32 y0 = (i32)(hh - vo0->clip_pos.y / vo0->clip_pos.w * hh);
i32 x1 = (i32)(hw + vo1->clip_pos.x / vo1->clip_pos.w * hw);
i32 y1 = (i32)(hh - vo1->clip_pos.y / vo1->clip_pos.w * hh);
i32 x2 = (i32)(hw + vo2->clip_pos.x / vo2->clip_pos.w * hw);
i32 y2 = (i32)(hh - vo2->clip_pos.y / vo2->clip_pos.w * hh);
if (!double_sided) {
i32 cross = (x1 - x0) * (y2 - y0) - (y1 - y0) * (x2 - x0);
if (cross >= 0) return;
}
pxl8_cpu_draw_line_2d(cpu, x0, y0, x1, y1, color);
pxl8_cpu_draw_line_2d(cpu, x1, y1, x2, y2, color);
pxl8_cpu_draw_line_2d(cpu, x2, y2, x0, y0, color);
}
static void dispatch_triangle( static void dispatch_triangle(
pxl8_cpu_backend* cpu, pxl8_cpu_backend* cpu,
const vertex_output* vo0, const vertex_output* vo1, const vertex_output* vo2, const vertex_output* vo0, const vertex_output* vo1, const vertex_output* vo2,
const pxl8_atlas* textures, const pxl8_material* material const pxl8_atlas* textures, const pxl8_gfx_material* material
) { ) {
if (material->wireframe) {
u8 color = (material->texture_id > 0) ? (u8)material->texture_id : 15;
rasterize_triangle_wireframe(cpu, vo0, vo1, vo2, color, material->double_sided);
return;
}
pxl8_cpu_render_target* render_target = cpu->current_target; pxl8_cpu_render_target* render_target = cpu->current_target;
tri_setup setup; tri_setup setup;
if (!setup_triangle(&setup, vo0, vo1, vo2, render_target->width, render_target->height, material->double_sided)) { if (!setup_triangle(&setup, vo0, vo1, vo2, render_target->width, render_target->height, material->double_sided)) {
@ -1029,7 +1095,7 @@ void pxl8_cpu_draw_mesh(
pxl8_cpu_backend* cpu, pxl8_cpu_backend* cpu,
const pxl8_mesh* mesh, const pxl8_mesh* mesh,
const pxl8_mat4* model, const pxl8_mat4* model,
const pxl8_material* material, const pxl8_gfx_material* material,
const pxl8_atlas* textures const pxl8_atlas* textures
) { ) {
if (!cpu || !mesh || !model || !material || mesh->index_count < 3 || !cpu->current_target) return; if (!cpu || !mesh || !model || !material || mesh->index_count < 3 || !cpu->current_target) return;
@ -1237,10 +1303,12 @@ void pxl8_cpu_blit(pxl8_cpu_backend* cpu, pxl8_cpu_render_target* src, i32 x, i3
for (i32 row = 0; row < copy_h; row++) { for (i32 row = 0; row < copy_h; row++) {
u8* src_row = src->framebuffer + (src_y0 + row) * src->width + src_x0; u8* src_row = src->framebuffer + (src_y0 + row) * src->width + src_x0;
u8* dst_row = dst->framebuffer + (dst_y0 + row) * dst->width + dst_x0; u8* dst_row = dst->framebuffer + (dst_y0 + row) * dst->width + dst_x0;
u32* light_row = dst->light_accum ? dst->light_accum + (dst_y0 + row) * dst->width + dst_x0 : NULL;
for (i32 col = 0; col < copy_w; col++) { for (i32 col = 0; col < copy_w; col++) {
u8 pixel = src_row[col]; u8 pixel = src_row[col];
if (pixel != transparent_idx) { if (pixel != transparent_idx) {
dst_row[col] = pixel; dst_row[col] = pixel;
if (light_row) light_row[col] = 0;
} }
} }
} }
@ -1301,12 +1369,8 @@ u32* pxl8_cpu_get_output(pxl8_cpu_backend* cpu) {
void pxl8_cpu_render_glows( void pxl8_cpu_render_glows(
pxl8_cpu_backend* cpu, pxl8_cpu_backend* cpu,
const pxl8_glow_source* glows, const pxl8_glow_source* glows,
u32 glow_count, u32 glow_count
const pxl8_additive_table* additive,
const pxl8_palette_cube* palette_cube,
const pxl8_overbright_table* overbright
) { ) {
(void)overbright;
if (!cpu || cpu->target_stack_depth == 0) return; if (!cpu || cpu->target_stack_depth == 0) return;
pxl8_cpu_render_target* target = cpu->target_stack[cpu->target_stack_depth - 1]; pxl8_cpu_render_target* target = cpu->target_stack[cpu->target_stack_depth - 1];
@ -1392,25 +1456,21 @@ void pxl8_cpu_render_glows(
u8 int_val = intensity < 1.0f ? (u8)(intensity * 255.0f) : 255; u8 int_val = intensity < 1.0f ? (u8)(intensity * 255.0f) : 255;
u8 light_level = pxl8_ordered_dither(int_val, (u32)x, (u32)y); u8 light_level = pxl8_ordered_dither(int_val, (u32)x, (u32)y);
// Skip very dark pixels to create stippled transparency effect
if (light_level < 8) continue; if (light_level < 8) continue;
u8 shaded = pxl8_colormap_lookup(cpu->colormap, base_color, light_level); u8 shaded = pxl8_colormap_lookup(cpu->colormap, base_color, PXL8_LIGHT_WHITE, light_level);
if (intensity > 1.0f && palette_cube) { if (cpu->palette_cube) {
f32 over = intensity - 1.0f; u8 sr, sg, sb, dr, dg, db;
if (over > 1.0f) over = 1.0f; pxl8_palette_cube_get_rgb(cpu->palette_cube, shaded, &sr, &sg, &sb);
u8 r, g, b; pxl8_palette_cube_get_rgb(cpu->palette_cube, pixels[idx], &dr, &dg, &db);
pxl8_palette_cube_get_rgb(palette_cube, shaded, &r, &g, &b); u32 r = (u32)sr + (u32)dr;
u8 or = (u8)(r + (255 - r) * over); u32 g = (u32)sg + (u32)dg;
u8 og = (u8)(g + (255 - g) * over); u32 b = (u32)sb + (u32)db;
u8 ob = (u8)(b + (255 - b) * over); if (r > 255) r = 255;
shaded = pxl8_palette_cube_lookup_stable(palette_cube, or, og, ob); if (g > 255) g = 255;
} if (b > 255) b = 255;
pixels[idx] = pxl8_palette_cube_lookup(cpu->palette_cube, (u8)r, (u8)g, (u8)b);
u8 dst = pixels[idx];
if (additive) {
pixels[idx] = pxl8_additive_blend(additive, shaded, dst);
} else { } else {
pixels[idx] = shaded; pixels[idx] = shaded;
} }

View file

@ -1,12 +1,13 @@
#pragma once #pragma once
#include "pxl8_atlas.h" #include "pxl8_atlas.h"
#include "pxl8_blend.h"
#include "pxl8_colormap.h" #include "pxl8_colormap.h"
#include "pxl8_gfx.h" #include "pxl8_gfx.h"
#include "pxl8_gfx3d.h" #include "pxl8_gfx3d.h"
#include "pxl8_lightmap.h"
#include "pxl8_math.h" #include "pxl8_math.h"
#include "pxl8_mesh.h" #include "pxl8_mesh.h"
#include "pxl8_palette.h"
#include "pxl8_types.h" #include "pxl8_types.h"
#ifdef __cplusplus #ifdef __cplusplus
@ -34,6 +35,10 @@ void pxl8_cpu_clear_depth(pxl8_cpu_backend* cpu);
void pxl8_cpu_set_colormap(pxl8_cpu_backend* cpu, const pxl8_colormap* cm); void pxl8_cpu_set_colormap(pxl8_cpu_backend* cpu, const pxl8_colormap* cm);
void pxl8_cpu_set_palette(pxl8_cpu_backend* cpu, const u32* palette); void pxl8_cpu_set_palette(pxl8_cpu_backend* cpu, const u32* palette);
void pxl8_cpu_set_palette_cube(pxl8_cpu_backend* cpu, const pxl8_palette_cube* cube);
u32 pxl8_cpu_add_lightmap(pxl8_cpu_backend* cpu, pxl8_lightmap* lm);
pxl8_lightmap* pxl8_cpu_get_lightmap(pxl8_cpu_backend* cpu, u32 id);
void pxl8_cpu_draw_pixel(pxl8_cpu_backend* cpu, i32 x, i32 y, u8 color); void pxl8_cpu_draw_pixel(pxl8_cpu_backend* cpu, i32 x, i32 y, u8 color);
u8 pxl8_cpu_get_pixel(pxl8_cpu_backend* cpu, i32 x, i32 y); u8 pxl8_cpu_get_pixel(pxl8_cpu_backend* cpu, i32 x, i32 y);
@ -48,7 +53,7 @@ void pxl8_cpu_draw_mesh(
pxl8_cpu_backend* cpu, pxl8_cpu_backend* cpu,
const pxl8_mesh* mesh, const pxl8_mesh* mesh,
const pxl8_mat4* model, const pxl8_mat4* model,
const pxl8_material* material, const pxl8_gfx_material* material,
const pxl8_atlas* textures const pxl8_atlas* textures
); );
@ -67,10 +72,7 @@ u32 pxl8_cpu_get_width(const pxl8_cpu_backend* cpu);
void pxl8_cpu_render_glows( void pxl8_cpu_render_glows(
pxl8_cpu_backend* cpu, pxl8_cpu_backend* cpu,
const pxl8_glow_source* glows, const pxl8_glow_source* glows,
u32 glow_count, u32 glow_count
const pxl8_additive_table* additive,
const pxl8_palette_cube* palette_cube,
const pxl8_overbright_table* overbright
); );
void pxl8_cpu_resolve(pxl8_cpu_backend* cpu); void pxl8_cpu_resolve(pxl8_cpu_backend* cpu);

View file

@ -6,7 +6,6 @@
#include "pxl8_ase.h" #include "pxl8_ase.h"
#include "pxl8_atlas.h" #include "pxl8_atlas.h"
#include "pxl8_backend.h" #include "pxl8_backend.h"
#include "pxl8_blend.h"
#include "pxl8_blit.h" #include "pxl8_blit.h"
#include "pxl8_color.h" #include "pxl8_color.h"
#include "pxl8_colormap.h" #include "pxl8_colormap.h"
@ -25,7 +24,6 @@ typedef struct pxl8_sprite_cache_entry {
} pxl8_sprite_cache_entry; } pxl8_sprite_cache_entry;
struct pxl8_gfx { struct pxl8_gfx {
pxl8_additive_table* additive_table;
pxl8_atlas* atlas; pxl8_atlas* atlas;
pxl8_gfx_backend backend; pxl8_gfx_backend backend;
pxl8_colormap* colormap; pxl8_colormap* colormap;
@ -35,7 +33,6 @@ struct pxl8_gfx {
pxl8_frustum frustum; pxl8_frustum frustum;
const pxl8_hal* hal; const pxl8_hal* hal;
bool initialized; bool initialized;
pxl8_overbright_table* overbright_table;
pxl8_palette* palette; pxl8_palette* palette;
pxl8_palette_cube* palette_cube; pxl8_palette_cube* palette_cube;
pxl8_pixel_mode pixel_mode; pxl8_pixel_mode pixel_mode;
@ -78,10 +75,14 @@ i32 pxl8_gfx_get_width(const pxl8_gfx* gfx) {
return gfx ? gfx->framebuffer_width : 0; return gfx ? gfx->framebuffer_width : 0;
} }
pxl8_palette* pxl8_gfx_get_palette(pxl8_gfx* gfx) { pxl8_palette* pxl8_gfx_palette(pxl8_gfx* gfx) {
return gfx ? gfx->palette : NULL; return gfx ? gfx->palette : NULL;
} }
pxl8_colormap* pxl8_gfx_colormap(pxl8_gfx* gfx) {
return gfx ? gfx->colormap : NULL;
}
u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color) { u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color) {
if (!gfx || !gfx->palette) return 0; if (!gfx || !gfx->palette) return 0;
if (color <= 0xFFFFFF) color = (color << 8) | 0xFF; if (color <= 0xFFFFFF) color = (color << 8) | 0xFF;
@ -91,37 +92,24 @@ u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color) {
return pxl8_palette_find_closest(gfx->palette, r, g, b); return pxl8_palette_find_closest(gfx->palette, r, g, b);
} }
void pxl8_gfx_set_palette(pxl8_gfx* gfx, pxl8_palette* pal) { void pxl8_gfx_set_palette_colors(pxl8_gfx* gfx, const u32* colors, u16 count) {
if (!gfx) return; if (!gfx || !gfx->palette || !colors) return;
if (gfx->palette) { pxl8_set_palette(gfx->palette, colors, count);
pxl8_palette_destroy(gfx->palette); if (gfx->pixel_mode != PXL8_PIXEL_HICOLOR && gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
} pxl8_cpu_set_palette(gfx->backend.cpu, pxl8_palette_colors(gfx->palette));
gfx->palette = pal;
if (gfx->palette && gfx->pixel_mode != PXL8_PIXEL_HICOLOR) {
u32* colors = pxl8_palette_colors(gfx->palette);
if (gfx->colormap) {
pxl8_colormap_generate(gfx->colormap, colors, NULL);
}
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
pxl8_cpu_set_palette(gfx->backend.cpu, colors);
}
} }
} }
i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) { i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) {
if (!gfx || !filepath) return -1; if (!gfx || !gfx->palette || !filepath) return -1;
pxl8_palette* pal = gfx->palette; pxl8_result result = pxl8_palette_load_ase(gfx->palette, filepath);
if (!pal) return -1;
pxl8_result result = pxl8_palette_load_ase(pal, filepath);
if (result != PXL8_OK) return (i32)result; if (result != PXL8_OK) return (i32)result;
if (gfx->pixel_mode != PXL8_PIXEL_HICOLOR) { if (gfx->pixel_mode != PXL8_PIXEL_HICOLOR) {
u32* colors = pxl8_palette_colors(pal);
if (gfx->colormap) { if (gfx->colormap) {
pxl8_colormap_generate(gfx->colormap, colors, NULL); pxl8_colormap_generate(gfx->colormap, pxl8_palette_colors(gfx->palette));
} }
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) { if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
pxl8_cpu_set_palette(gfx->backend.cpu, colors); pxl8_cpu_set_palette(gfx->backend.cpu, pxl8_palette_colors(gfx->palette));
} }
} }
return 0; return 0;
@ -187,13 +175,11 @@ pxl8_gfx* pxl8_gfx_create(
void pxl8_gfx_destroy(pxl8_gfx* gfx) { void pxl8_gfx_destroy(pxl8_gfx* gfx) {
if (!gfx) return; if (!gfx) return;
pxl8_additive_table_destroy(gfx->additive_table);
pxl8_atlas_destroy(gfx->atlas); pxl8_atlas_destroy(gfx->atlas);
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) { if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
pxl8_cpu_destroy(gfx->backend.cpu); pxl8_cpu_destroy(gfx->backend.cpu);
} }
free(gfx->colormap); free(gfx->colormap);
pxl8_overbright_table_destroy(gfx->overbright_table);
pxl8_palette_cube_destroy(gfx->palette_cube); pxl8_palette_cube_destroy(gfx->palette_cube);
pxl8_palette_destroy(gfx->palette); pxl8_palette_destroy(gfx->palette);
free(gfx->sprite_cache); free(gfx->sprite_cache);
@ -523,6 +509,7 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo
if (!gfx || !gfx->atlas) return; if (!gfx || !gfx->atlas) return;
u8* framebuffer = NULL; u8* framebuffer = NULL;
u32* light_accum = NULL;
i32 fb_width = 0; i32 fb_width = 0;
i32 fb_height = 0; i32 fb_height = 0;
@ -531,6 +518,7 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo
pxl8_cpu_render_target* render_target = pxl8_cpu_get_target(gfx->backend.cpu); pxl8_cpu_render_target* render_target = pxl8_cpu_get_target(gfx->backend.cpu);
if (!render_target) return; if (!render_target) return;
framebuffer = pxl8_cpu_render_target_get_framebuffer(render_target); framebuffer = pxl8_cpu_render_target_get_framebuffer(render_target);
light_accum = pxl8_cpu_render_target_get_light_accum(render_target);
fb_width = (i32)pxl8_cpu_render_target_get_width(render_target); fb_width = (i32)pxl8_cpu_render_target_get_width(render_target);
fb_height = (i32)pxl8_cpu_render_target_get_height(render_target); fb_height = (i32)pxl8_cpu_render_target_get_height(render_target);
break; break;
@ -567,6 +555,11 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo
if (is_1to1_scale && is_unclipped && !is_flipped) { if (is_1to1_scale && is_unclipped && !is_flipped) {
const u8* sprite_data = atlas_pixels + entry->y * atlas_width + entry->x; const u8* sprite_data = atlas_pixels + entry->y * atlas_width + entry->x;
pxl8_blit_indexed(framebuffer, fb_width, sprite_data, atlas_width, x, y, w, h); pxl8_blit_indexed(framebuffer, fb_width, sprite_data, atlas_width, x, y, w, h);
if (light_accum) {
for (i32 py = 0; py < h; py++) {
memset(light_accum + (y + py) * fb_width + x, 0, w * sizeof(u32));
}
}
} else { } else {
for (i32 py = 0; py < draw_height; py++) { for (i32 py = 0; py < draw_height; py++) {
for (i32 px = 0; px < draw_width; px++) { for (i32 px = 0; px < draw_width; px++) {
@ -580,6 +573,7 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo
i32 dest_idx = (dest_y + py) * fb_width + (dest_x + px); i32 dest_idx = (dest_y + py) * fb_width + (dest_x + px);
framebuffer[dest_idx] = pxl8_blend_indexed(atlas_pixels[src_idx], framebuffer[dest_idx]); framebuffer[dest_idx] = pxl8_blend_indexed(atlas_pixels[src_idx], framebuffer[dest_idx]);
if (light_accum) light_accum[dest_idx] = 0;
} }
} }
} }
@ -667,7 +661,7 @@ void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color) {
} }
} }
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_material* material) { void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material) {
if (!gfx || !mesh || !model || !material) return; if (!gfx || !mesh || !model || !material) return;
switch (gfx->backend.type) { switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU: case PXL8_GFX_BACKEND_CPU:
@ -733,31 +727,27 @@ void pxl8_gfx_pop_target(pxl8_gfx* gfx) {
} }
} }
static void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx) { void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx) {
if (!gfx || !gfx->palette) return; if (!gfx || !gfx->palette) return;
if (!gfx->additive_table) {
gfx->additive_table = pxl8_additive_table_create(gfx->palette);
}
if (!gfx->overbright_table) {
gfx->overbright_table = pxl8_overbright_table_create(gfx->palette);
}
if (!gfx->palette_cube) { if (!gfx->palette_cube) {
gfx->palette_cube = pxl8_palette_cube_create(gfx->palette); gfx->palette_cube = pxl8_palette_cube_create(gfx->palette);
if (gfx->backend.cpu) {
pxl8_cpu_set_palette_cube(gfx->backend.cpu, gfx->palette_cube);
}
} }
} }
void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx) { void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx) {
if (!gfx || !gfx->palette) return; if (!gfx || !gfx->palette) return;
if (gfx->additive_table) { pxl8_gfx_ensure_blend_tables(gfx);
pxl8_additive_table_rebuild(gfx->additive_table, gfx->palette);
}
if (gfx->overbright_table) {
pxl8_overbright_table_rebuild(gfx->overbright_table, gfx->palette);
}
if (gfx->palette_cube) { if (gfx->palette_cube) {
pxl8_palette_cube_rebuild(gfx->palette_cube, gfx->palette); pxl8_palette_cube_rebuild(gfx->palette_cube, gfx->palette);
if (gfx->backend.cpu) {
pxl8_cpu_set_palette_cube(gfx->backend.cpu, gfx->palette_cube);
}
} }
} }
@ -765,29 +755,33 @@ void pxl8_gfx_colormap_update(pxl8_gfx* gfx) {
if (!gfx || !gfx->palette || !gfx->colormap) return; if (!gfx || !gfx->palette || !gfx->colormap) return;
u32* colors = pxl8_palette_colors(gfx->palette); u32* colors = pxl8_palette_colors(gfx->palette);
if (colors) { if (colors) {
pxl8_colormap_generate(gfx->colormap, colors, NULL); pxl8_colormap_generate(gfx->colormap, colors);
} }
} }
u8 pxl8_gfx_find_closest_color(pxl8_gfx* gfx, u8 r, u8 g, u8 b) {
if (!gfx || !gfx->palette) return 0;
return pxl8_palette_find_closest(gfx->palette, r, g, b);
}
u8 pxl8_gfx_ui_color(pxl8_gfx* gfx, u8 index) {
if (!gfx || !gfx->palette || index >= PXL8_UI_PALETTE_SIZE) return 0;
u32 abgr = pxl8_ui_palette[index];
u8 r = (abgr >> 0) & 0xFF;
u8 g = (abgr >> 8) & 0xFF;
u8 b = (abgr >> 16) & 0xFF;
return pxl8_palette_find_closest(gfx->palette, r, g, b);
}
void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count) { void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count) {
if (!gfx || !params || count == 0) return; if (!gfx || !params || count == 0) return;
switch (effect) { switch (effect) {
case PXL8_GFX_EFFECT_GLOWS: { case PXL8_GFX_EFFECT_GLOWS: {
pxl8_gfx_ensure_blend_tables(gfx);
if (!gfx->additive_table || !gfx->overbright_table || !gfx->palette_cube) return;
const pxl8_glow_source* glows = (const pxl8_glow_source*)params; const pxl8_glow_source* glows = (const pxl8_glow_source*)params;
switch (gfx->backend.type) { switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU: case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_render_glows( pxl8_cpu_render_glows(gfx->backend.cpu, glows, count);
gfx->backend.cpu,
glows,
count,
gfx->additive_table,
gfx->palette_cube,
gfx->overbright_table
);
break; break;
case PXL8_GFX_BACKEND_GPU: case PXL8_GFX_BACKEND_GPU:
break; break;

View file

@ -3,8 +3,10 @@
#include "pxl8_gfx2d.h" #include "pxl8_gfx2d.h"
#include "pxl8_gfx3d.h" #include "pxl8_gfx3d.h"
#include "pxl8_hal.h" #include "pxl8_hal.h"
#include "pxl8_colormap.h"
#include "pxl8_palette.h" #include "pxl8_palette.h"
#include "pxl8_types.h" #include "pxl8_types.h"
#include "pxl8_gui_palette.h"
typedef struct pxl8_gfx pxl8_gfx; typedef struct pxl8_gfx pxl8_gfx;
@ -76,13 +78,14 @@ 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);
pxl8_palette* pxl8_gfx_get_palette(pxl8_gfx* gfx); pxl8_palette* pxl8_gfx_palette(pxl8_gfx* gfx);
pxl8_colormap* pxl8_gfx_colormap(pxl8_gfx* gfx);
pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx); pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx);
i32 pxl8_gfx_get_width(const pxl8_gfx* gfx); i32 pxl8_gfx_get_width(const pxl8_gfx* gfx);
i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath); i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath);
void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom); void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom);
void pxl8_gfx_set_palette(pxl8_gfx* gfx, pxl8_palette* pal); void pxl8_gfx_set_palette_colors(pxl8_gfx* gfx, const u32* colors, u16 count);
void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp); void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp);
pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height); pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height);
@ -97,6 +100,10 @@ void pxl8_gfx_pop_target(pxl8_gfx* gfx);
void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count); void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count);
void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx); void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx);
void pxl8_gfx_colormap_update(pxl8_gfx* gfx); void pxl8_gfx_colormap_update(pxl8_gfx* gfx);
void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx);
u8 pxl8_gfx_find_closest_color(pxl8_gfx* gfx, u8 r, u8 g, u8 b);
u8 pxl8_gfx_ui_color(pxl8_gfx* gfx, u8 index);
#ifdef __cplusplus #ifdef __cplusplus
} }

View file

@ -59,7 +59,7 @@ void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8
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);
void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color); void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color);
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_material* material); void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material);
void pxl8_3d_draw_mesh_wireframe(pxl8_gfx* gfx, const pxl8_mesh* mesh, pxl8_mat4 model, u8 color); void pxl8_3d_draw_mesh_wireframe(pxl8_gfx* gfx, const pxl8_mesh* mesh, pxl8_mat4 model, u8 color);
void pxl8_3d_end_frame(pxl8_gfx* gfx); void pxl8_3d_end_frame(pxl8_gfx* gfx);
u8* pxl8_3d_get_framebuffer(pxl8_gfx* gfx); u8* pxl8_3d_get_framebuffer(pxl8_gfx* gfx);

113
src/gfx/pxl8_lightmap.c Normal file
View file

@ -0,0 +1,113 @@
#include "pxl8_lightmap.h"
#include <stdlib.h>
#include <string.h>
pxl8_lightmap* pxl8_lightmap_create(u32 width, u32 height, u32 scale) {
pxl8_lightmap* lm = calloc(1, sizeof(pxl8_lightmap));
if (!lm) return NULL;
lm->width = width;
lm->height = height;
lm->scale = scale;
lm->data = calloc(width * height * 3, sizeof(u8));
if (!lm->data) {
free(lm);
return NULL;
}
pxl8_lightmap_clear(lm, PXL8_LIGHTMAP_NEUTRAL, PXL8_LIGHTMAP_NEUTRAL, PXL8_LIGHTMAP_NEUTRAL);
return lm;
}
void pxl8_lightmap_destroy(pxl8_lightmap* lm) {
if (!lm) return;
free(lm->data);
free(lm);
}
void pxl8_lightmap_clear(pxl8_lightmap* lm, u8 r, u8 g, u8 b) {
if (!lm || !lm->data) return;
u32 count = lm->width * lm->height;
for (u32 i = 0; i < count; i++) {
lm->data[i * 3 + 0] = r;
lm->data[i * 3 + 1] = g;
lm->data[i * 3 + 2] = b;
}
}
void pxl8_lightmap_set(pxl8_lightmap* lm, u32 x, u32 y, u8 r, u8 g, u8 b) {
if (!lm || !lm->data || x >= lm->width || y >= lm->height) return;
u32 idx = (y * lm->width + x) * 3;
lm->data[idx + 0] = r;
lm->data[idx + 1] = g;
lm->data[idx + 2] = b;
}
void pxl8_lightmap_get(const pxl8_lightmap* lm, u32 x, u32 y, u8* r, u8* g, u8* b) {
if (!lm || !lm->data || x >= lm->width || y >= lm->height) {
*r = *g = *b = PXL8_LIGHTMAP_NEUTRAL;
return;
}
u32 idx = (y * lm->width + x) * 3;
*r = lm->data[idx + 0];
*g = lm->data[idx + 1];
*b = lm->data[idx + 2];
}
void pxl8_lightmap_add_point(
pxl8_lightmap* lm,
f32 lx, f32 ly,
u8 r, u8 g, u8 b,
f32 radius,
f32 intensity
) {
if (!lm || !lm->data || radius <= 0.0f) return;
f32 radius_sq = radius * radius;
f32 inv_radius_sq = 1.0f / radius_sq;
i32 cx = (i32)(lx * (f32)lm->width);
i32 cy = (i32)(ly * (f32)lm->height);
i32 rad_pixels = (i32)(radius * (f32)lm->width) + 1;
i32 x0 = cx - rad_pixels;
i32 y0 = cy - rad_pixels;
i32 x1 = cx + rad_pixels;
i32 y1 = cy + rad_pixels;
if (x0 < 0) x0 = 0;
if (y0 < 0) y0 = 0;
if (x1 >= (i32)lm->width) x1 = (i32)lm->width - 1;
if (y1 >= (i32)lm->height) y1 = (i32)lm->height - 1;
f32 scale_x = 1.0f / (f32)lm->width;
f32 scale_y = 1.0f / (f32)lm->height;
for (i32 y = y0; y <= y1; y++) {
f32 dy = ((f32)y * scale_y) - ly;
for (i32 x = x0; x <= x1; x++) {
f32 dx = ((f32)x * scale_x) - lx;
f32 dist_sq = dx * dx + dy * dy;
if (dist_sq >= radius_sq) continue;
f32 falloff = 1.0f - dist_sq * inv_radius_sq;
f32 contrib = falloff * falloff * intensity;
u32 idx = ((u32)y * lm->width + (u32)x) * 3;
i32 nr = (i32)lm->data[idx + 0] + (i32)((f32)(r - 128) * contrib);
i32 ng = (i32)lm->data[idx + 1] + (i32)((f32)(g - 128) * contrib);
i32 nb = (i32)lm->data[idx + 2] + (i32)((f32)(b - 128) * contrib);
if (nr < 0) nr = 0; if (nr > 255) nr = 255;
if (ng < 0) ng = 0; if (ng > 255) ng = 255;
if (nb < 0) nb = 0; if (nb > 255) nb = 255;
lm->data[idx + 0] = (u8)nr;
lm->data[idx + 1] = (u8)ng;
lm->data[idx + 2] = (u8)nb;
}
}
}

48
src/gfx/pxl8_lightmap.h Normal file
View file

@ -0,0 +1,48 @@
#pragma once
#include "pxl8_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#define PXL8_LIGHTMAP_MAX 16
#define PXL8_LIGHTMAP_NEUTRAL 128
typedef struct pxl8_lightmap {
u8* data;
u32 width;
u32 height;
u32 scale;
} pxl8_lightmap;
pxl8_lightmap* pxl8_lightmap_create(u32 width, u32 height, u32 scale);
void pxl8_lightmap_destroy(pxl8_lightmap* lm);
void pxl8_lightmap_clear(pxl8_lightmap* lm, u8 r, u8 g, u8 b);
void pxl8_lightmap_set(pxl8_lightmap* lm, u32 x, u32 y, u8 r, u8 g, u8 b);
void pxl8_lightmap_get(const pxl8_lightmap* lm, u32 x, u32 y, u8* r, u8* g, u8* b);
void pxl8_lightmap_add_point(
pxl8_lightmap* lm,
f32 lx, f32 ly,
u8 r, u8 g, u8 b,
f32 radius,
f32 intensity
);
static inline void pxl8_lightmap_sample(
const pxl8_lightmap* lm,
f32 u, f32 v,
u8* r, u8* g, u8* b
) {
i32 x = (i32)(u * (f32)lm->width) & (i32)(lm->width - 1);
i32 y = (i32)(v * (f32)lm->height) & (i32)(lm->height - 1);
u32 idx = ((u32)y * lm->width + (u32)x) * 3;
*r = lm->data[idx + 0];
*g = lm->data[idx + 1];
*b = lm->data[idx + 2];
}
#ifdef __cplusplus
}
#endif

View file

@ -17,8 +17,9 @@ typedef enum pxl8_blend_mode {
PXL8_BLEND_ADDITIVE, PXL8_BLEND_ADDITIVE,
} pxl8_blend_mode; } pxl8_blend_mode;
typedef struct pxl8_material { typedef struct pxl8_gfx_material {
u32 texture_id; u32 texture_id;
u32 lightmap_id;
u8 alpha; u8 alpha;
u8 blend_mode; u8 blend_mode;
bool dither; bool dither;
@ -26,13 +27,15 @@ typedef struct pxl8_material {
bool dynamic_lighting; bool dynamic_lighting;
bool per_pixel; bool per_pixel;
bool vertex_color_passthrough; bool vertex_color_passthrough;
bool wireframe;
f32 emissive_intensity; f32 emissive_intensity;
} pxl8_material; } pxl8_gfx_material;
typedef struct pxl8_vertex { typedef struct pxl8_vertex {
pxl8_vec3 position; pxl8_vec3 position;
pxl8_vec3 normal; pxl8_vec3 normal;
f32 u, v; f32 u, v;
f32 lu, lv;
u8 color; u8 color;
u8 light; u8 light;
u8 _pad[2]; u8 _pad[2];
@ -62,62 +65,6 @@ static inline u32 pxl8_mesh_triangle_count(const pxl8_mesh* mesh) {
return mesh->index_count / 3; return mesh->index_count / 3;
} }
static inline pxl8_material pxl8_material_create(u32 texture_id) {
return (pxl8_material){
.texture_id = texture_id,
.alpha = 255,
.blend_mode = PXL8_BLEND_OPAQUE,
.dither = true,
.double_sided = false,
.dynamic_lighting = false,
.per_pixel = false,
.vertex_color_passthrough = false,
.emissive_intensity = 0.0f,
};
}
static inline pxl8_material pxl8_material_with_alpha(pxl8_material m, u8 alpha) {
m.alpha = alpha;
if (alpha < 255 && m.blend_mode == PXL8_BLEND_OPAQUE) {
m.blend_mode = PXL8_BLEND_ALPHA;
}
return m;
}
static inline pxl8_material pxl8_material_with_blend(pxl8_material m, pxl8_blend_mode mode) {
m.blend_mode = mode;
return m;
}
static inline pxl8_material pxl8_material_with_double_sided(pxl8_material m) {
m.double_sided = true;
return m;
}
static inline pxl8_material pxl8_material_with_emissive(pxl8_material m, f32 intensity) {
m.emissive_intensity = intensity;
return m;
}
static inline pxl8_material pxl8_material_with_lighting(pxl8_material m) {
m.dynamic_lighting = true;
return m;
}
static inline pxl8_material pxl8_material_with_no_dither(pxl8_material m) {
m.dither = false;
return m;
}
static inline pxl8_material pxl8_material_with_passthrough(pxl8_material m) {
m.vertex_color_passthrough = true;
return m;
}
static inline pxl8_material pxl8_material_with_per_pixel(pxl8_material m) {
m.per_pixel = true;
return m;
}
#ifdef __cplusplus #ifdef __cplusplus
} }

View file

@ -5,10 +5,17 @@
#include "pxl8_ase.h" #include "pxl8_ase.h"
#include "pxl8_color.h" #include "pxl8_color.h"
#include "pxl8_colormap.h"
#include "pxl8_log.h" #include "pxl8_log.h"
#define PXL8_PALETTE_HASH_SIZE 512 #define PXL8_PALETTE_HASH_SIZE 512
struct pxl8_palette_cube {
u8 colors[PXL8_PALETTE_SIZE * 3];
u8 table[PXL8_CUBE_ENTRIES];
u8 stable[PXL8_CUBE_ENTRIES];
};
typedef struct { typedef struct {
u32 color; u32 color;
i16 index; i16 index;
@ -332,10 +339,6 @@ void pxl8_palette_get_rgba(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b,
unpack_rgba(pal->colors[idx], r, g, b, a); unpack_rgba(pal->colors[idx], r, g, b, a);
} }
void pxl8_palette_set(pxl8_palette* pal, u8 idx, u32 color) {
if (pal) pal->colors[idx] = color;
}
void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b) { void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b) {
if (pal) pal->colors[idx] = pack_rgb(r, g, b); if (pal) pal->colors[idx] = pack_rgb(r, g, b);
} }
@ -344,6 +347,17 @@ void pxl8_palette_set_rgba(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b, u8 a) {
if (pal) pal->colors[idx] = pack_rgba(r, g, b, a); if (pal) pal->colors[idx] = pack_rgba(r, g, b, a);
} }
void pxl8_set_palette(pxl8_palette* pal, const u32* colors, u16 count) {
if (!pal || !colors) return;
for (u16 i = 0; i < count; i++) {
u32 rgb = colors[i];
u8 r = (rgb >> 16) & 0xFF;
u8 g = (rgb >> 8) & 0xFF;
u8 b = rgb & 0xFF;
pal->colors[i] = pack_rgb(r, g, b);
}
}
void pxl8_palette_fill_gradient(pxl8_palette* pal, u8 start, u8 count, u32 from, u32 to) { void pxl8_palette_fill_gradient(pxl8_palette* pal, u8 start, u8 count, u32 from, u32 to) {
if (!pal || count == 0) return; if (!pal || count == 0) return;
@ -472,3 +486,91 @@ pxl8_cycle_range pxl8_cycle_range_disabled(void) {
}; };
return range; return range;
} }
static u8 find_closest_stable(const pxl8_palette* pal, u8 r, u8 g, u8 b) {
u8 best_idx = 1;
u32 best_dist = 0xFFFFFFFF;
u8 dynamic_end = PXL8_DYNAMIC_RANGE_START + PXL8_DYNAMIC_RANGE_COUNT;
for (u32 i = 1; i < PXL8_FULLBRIGHT_START; i++) {
if (i >= PXL8_DYNAMIC_RANGE_START && i < dynamic_end) {
continue;
}
u8 pr, pg, pb;
pxl8_palette_get_rgb(pal, (u8)i, &pr, &pg, &pb);
i32 dr = (i32)r - (i32)pr;
i32 dg = (i32)g - (i32)pg;
i32 db = (i32)b - (i32)pb;
u32 dist = (u32)(dr * dr + dg * dg + db * db);
if (dist < best_dist) {
best_dist = dist;
best_idx = (u8)i;
if (dist == 0) break;
}
}
return best_idx;
}
pxl8_palette_cube* pxl8_palette_cube_create(const pxl8_palette* pal) {
pxl8_palette_cube* cube = calloc(1, sizeof(pxl8_palette_cube));
if (!cube) return NULL;
pxl8_palette_cube_rebuild(cube, pal);
return cube;
}
void pxl8_palette_cube_destroy(pxl8_palette_cube* cube) {
free(cube);
}
void pxl8_palette_cube_rebuild(pxl8_palette_cube* cube, const pxl8_palette* pal) {
if (!cube || !pal) return;
for (u32 i = 0; i < PXL8_PALETTE_SIZE; i++) {
u8 r, g, b;
pxl8_palette_get_rgb(pal, (u8)i, &r, &g, &b);
cube->colors[i * 3 + 0] = r;
cube->colors[i * 3 + 1] = g;
cube->colors[i * 3 + 2] = b;
}
for (u32 bi = 0; bi < PXL8_CUBE_SIZE; bi++) {
for (u32 gi = 0; gi < PXL8_CUBE_SIZE; gi++) {
for (u32 ri = 0; ri < PXL8_CUBE_SIZE; ri++) {
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
u8 r8 = (u8)((ri * 255) / (PXL8_CUBE_SIZE - 1));
u8 g8 = (u8)((gi * 255) / (PXL8_CUBE_SIZE - 1));
u8 b8 = (u8)((bi * 255) / (PXL8_CUBE_SIZE - 1));
cube->table[idx] = pxl8_palette_find_closest(pal, r8, g8, b8);
cube->stable[idx] = find_closest_stable(pal, r8, g8, b8);
}
}
}
}
u8 pxl8_palette_cube_lookup(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b) {
u32 ri = (r * (PXL8_CUBE_SIZE - 1)) / 255;
u32 gi = (g * (PXL8_CUBE_SIZE - 1)) / 255;
u32 bi = (b * (PXL8_CUBE_SIZE - 1)) / 255;
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
return cube->table[idx];
}
u8 pxl8_palette_cube_lookup_stable(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b) {
u32 ri = (r * (PXL8_CUBE_SIZE - 1)) / 255;
u32 gi = (g * (PXL8_CUBE_SIZE - 1)) / 255;
u32 bi = (b * (PXL8_CUBE_SIZE - 1)) / 255;
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
return cube->stable[idx];
}
void pxl8_palette_cube_get_rgb(const pxl8_palette_cube* cube, u8 idx, u8* r, u8* g, u8* b) {
*r = cube->colors[idx * 3 + 0];
*g = cube->colors[idx * 3 + 1];
*b = cube->colors[idx * 3 + 2];
}

View file

@ -5,8 +5,11 @@
#define PXL8_PALETTE_SIZE 256 #define PXL8_PALETTE_SIZE 256
#define PXL8_MAX_CYCLES 8 #define PXL8_MAX_CYCLES 8
#define PXL8_MAX_CYCLE_LEN 16 #define PXL8_MAX_CYCLE_LEN 16
#define PXL8_CUBE_SIZE 32
#define PXL8_CUBE_ENTRIES (PXL8_CUBE_SIZE * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE)
typedef struct pxl8_palette pxl8_palette; typedef struct pxl8_palette pxl8_palette;
typedef struct pxl8_palette_cube pxl8_palette_cube;
typedef enum pxl8_cycle_mode { typedef enum pxl8_cycle_mode {
PXL8_CYCLE_LOOP, PXL8_CYCLE_LOOP,
@ -49,9 +52,9 @@ u32 pxl8_palette_color(const pxl8_palette* pal, u8 idx);
i32 pxl8_palette_index(const pxl8_palette* pal, u32 color); i32 pxl8_palette_index(const pxl8_palette* pal, u32 color);
void pxl8_palette_get_rgb(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b); void pxl8_palette_get_rgb(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b);
void pxl8_palette_get_rgba(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b, u8* a); void pxl8_palette_get_rgba(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b, u8* a);
void pxl8_palette_set(pxl8_palette* pal, u8 idx, u32 color);
void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b); void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b);
void pxl8_palette_set_rgba(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b, u8 a); void pxl8_palette_set_rgba(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b, u8 a);
void pxl8_set_palette(pxl8_palette* pal, const u32* colors, u16 count);
void pxl8_palette_fill_gradient(pxl8_palette* pal, u8 start, u8 count, u32 from, u32 to); void pxl8_palette_fill_gradient(pxl8_palette* pal, u8 start, u8 count, u32 from, u32 to);
void pxl8_palette_fill_gradient_rgb(pxl8_palette* pal, u8 start, u8 count, u8 r0, u8 g0, u8 b0, u8 r1, u8 g1, u8 b1); void pxl8_palette_fill_gradient_rgb(pxl8_palette* pal, u8 start, u8 count, u8 r0, u8 g0, u8 b0, u8 r1, u8 g1, u8 b1);
@ -65,6 +68,13 @@ void pxl8_palette_tick(pxl8_palette* pal, u16 delta_ticks);
pxl8_cycle_range pxl8_cycle_range_create(u8 start, u8 len, u16 period); pxl8_cycle_range pxl8_cycle_range_create(u8 start, u8 len, u16 period);
pxl8_cycle_range pxl8_cycle_range_disabled(void); pxl8_cycle_range pxl8_cycle_range_disabled(void);
pxl8_palette_cube* pxl8_palette_cube_create(const pxl8_palette* pal);
void pxl8_palette_cube_destroy(pxl8_palette_cube* cube);
void pxl8_palette_cube_rebuild(pxl8_palette_cube* cube, const pxl8_palette* pal);
u8 pxl8_palette_cube_lookup(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b);
u8 pxl8_palette_cube_lookup_stable(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b);
void pxl8_palette_cube_get_rgb(const pxl8_palette_cube* cube, u8 idx, u8* r, u8* g, u8* b);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -18,18 +18,20 @@ void pxl8_gui_state_destroy(pxl8_gui_state* state) {
free(state); free(state);
} }
void pxl8_gui_begin_frame(pxl8_gui_state* state) { void pxl8_gui_begin_frame(pxl8_gui_state* state, pxl8_gfx* gfx) {
if (!state) return; if (!state) return;
state->hot_id = 0; state->hot_id = 0;
if (gfx) pxl8_gfx_push_target(gfx);
} }
void pxl8_gui_end_frame(pxl8_gui_state* state) { void pxl8_gui_end_frame(pxl8_gui_state* state, pxl8_gfx* gfx) {
if (!state) return; if (!state) return;
if (!state->cursor_down) { if (!state->cursor_down) {
state->active_id = 0; state->active_id = 0;
} }
state->cursor_clicked = false; state->cursor_clicked = false;
if (gfx) pxl8_gfx_pop_target(gfx);
} }
void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y) { void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y) {
@ -80,16 +82,16 @@ bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y,
i32 offset_y = 0; i32 offset_y = 0;
if (is_active) { if (is_active) {
bg_color = 4; bg_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG3);
border_color = 3; border_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG2);
offset_x = 1; offset_x = 1;
offset_y = 1; offset_y = 1;
} else if (is_hot || cursor_over) { } else if (is_hot || cursor_over) {
bg_color = 4; bg_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG3);
border_color = 8; border_color = pxl8_gfx_ui_color(gfx, PXL8_UI_FG0);
} else { } else {
bg_color = 3; bg_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG2);
border_color = 4; border_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG3);
} }
pxl8_2d_rect_fill(gfx, x, y, w, h, bg_color); pxl8_2d_rect_fill(gfx, x, y, w, h, bg_color);
@ -98,7 +100,7 @@ bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y,
i32 text_len = (i32)strlen(label); i32 text_len = (i32)strlen(label);
i32 text_x = x + (w / 2) - ((text_len * 8) / 2) + offset_x; i32 text_x = x + (w / 2) - ((text_len * 8) / 2) + offset_x;
i32 text_y = y + (h / 2) - 5 + offset_y; i32 text_y = y + (h / 2) - 5 + offset_y;
pxl8_2d_text(gfx, label, text_x, text_y, 6); pxl8_2d_text(gfx, label, text_x, text_y, pxl8_gfx_ui_color(gfx, PXL8_UI_FG1));
return clicked; return clicked;
} }
@ -106,14 +108,19 @@ bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y,
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;
pxl8_2d_rect_fill(gfx, x, y, w, 28, 1); u8 title_bg = pxl8_gfx_ui_color(gfx, PXL8_UI_BG1);
pxl8_2d_rect_fill(gfx, x, y + 28, w, h - 28, 2); u8 body_bg = pxl8_gfx_ui_color(gfx, PXL8_UI_BG2);
pxl8_2d_rect(gfx, x, y, w, h, 4); u8 border = pxl8_gfx_ui_color(gfx, PXL8_UI_BG3);
pxl8_2d_rect_fill(gfx, x, y + 28, w, 1, 4); u8 title_fg = pxl8_gfx_ui_color(gfx, PXL8_UI_FG0);
pxl8_2d_rect_fill(gfx, x, y, w, 28, title_bg);
pxl8_2d_rect_fill(gfx, x, y + 28, w, h - 28, body_bg);
pxl8_2d_rect(gfx, x, y, w, h, border);
pxl8_2d_rect_fill(gfx, x, y + 28, w, 1, border);
i32 title_x = x + 10; i32 title_x = x + 10;
i32 title_y = y + (28 / 2) - 5; i32 title_y = y + (28 / 2) - 5;
pxl8_2d_text(gfx, title, title_x, title_y, 8); pxl8_2d_text(gfx, title, title_x, title_y, title_fg);
} }
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) {

View file

@ -22,8 +22,8 @@ void pxl8_gui_state_destroy(pxl8_gui_state* state);
void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y); void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y);
bool pxl8_gui_is_hovering(const pxl8_gui_state* state); bool pxl8_gui_is_hovering(const pxl8_gui_state* state);
void pxl8_gui_begin_frame(pxl8_gui_state* state); void pxl8_gui_begin_frame(pxl8_gui_state* state, pxl8_gfx* gfx);
void pxl8_gui_end_frame(pxl8_gui_state* state); void pxl8_gui_end_frame(pxl8_gui_state* state, pxl8_gfx* gfx);
void pxl8_gui_cursor_down(pxl8_gui_state* state); void pxl8_gui_cursor_down(pxl8_gui_state* state);
void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y); void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y);

View file

@ -0,0 +1,41 @@
#pragma once
#include "pxl8_types.h"
#define PXL8_UI_PALETTE_SIZE 16
#define PXL8_UI_BG0 0
#define PXL8_UI_BG1 1
#define PXL8_UI_BG2 2
#define PXL8_UI_BG3 3
#define PXL8_UI_FG0 4
#define PXL8_UI_FG1 5
#define PXL8_UI_FG2 6
#define PXL8_UI_FG3 7
#define PXL8_UI_RED 8
#define PXL8_UI_GREEN 9
#define PXL8_UI_YELLOW 10
#define PXL8_UI_BLUE 11
#define PXL8_UI_PURPLE 12
#define PXL8_UI_AQUA 13
#define PXL8_UI_ORANGE 14
#define PXL8_UI_GRAY 15
static const u32 pxl8_ui_palette[PXL8_UI_PALETTE_SIZE] = {
0xFF282828,
0xFF3c3836,
0xFF504945,
0xFF665c54,
0xFFc7f1fb,
0xFFb2dbeb,
0xFFa1c4d5,
0xFF93aebd,
0xFF3449fb,
0xFF26bbb8,
0xFF2fbdfa,
0xFF98a583,
0xFF9b86d3,
0xFF7cc08e,
0xFF1980fe,
0xFF928374,
};

View file

@ -5,9 +5,10 @@ local gfx2d = require("pxl8.gfx2d")
local gfx3d = require("pxl8.gfx3d") local gfx3d = require("pxl8.gfx3d")
local gui = require("pxl8.gui") local gui = require("pxl8.gui")
local input = require("pxl8.input") local input = require("pxl8.input")
local math3d = require("pxl8.math") local math = require("pxl8.math")
local net = require("pxl8.net") local net = require("pxl8.net")
local particles = require("pxl8.particles") local particles = require("pxl8.particles")
local procgen = require("pxl8.procgen")
local sfx = require("pxl8.sfx") local sfx = require("pxl8.sfx")
local tilemap = require("pxl8.tilemap") local tilemap = require("pxl8.tilemap")
local transition = require("pxl8.transition") local transition = require("pxl8.transition")
@ -27,16 +28,20 @@ pxl8.debug = core.debug
pxl8.trace = core.trace pxl8.trace = core.trace
pxl8.quit = core.quit pxl8.quit = core.quit
pxl8.rng_seed = core.rng_seed pxl8.hash32 = math.hash32
pxl8.rng_next = core.rng_next
pxl8.rng_f32 = core.rng_f32 pxl8.rng_f32 = core.rng_f32
pxl8.rng_next = core.rng_next
pxl8.rng_range = core.rng_range pxl8.rng_range = core.rng_range
pxl8.rng_seed = core.rng_seed
pxl8.find_color = core.find_color pxl8.find_color = core.find_color
pxl8.palette_color = core.palette_color pxl8.palette_color = core.palette_color
pxl8.palette_index = core.palette_index pxl8.palette_index = core.palette_index
pxl8.ramp_index = core.ramp_index pxl8.ramp_index = core.ramp_index
pxl8.set_colormap = core.set_colormap
pxl8.set_palette = core.set_palette
pxl8.set_palette_rgb = core.set_palette_rgb pxl8.set_palette_rgb = core.set_palette_rgb
pxl8.update_palette_deps = core.update_palette_deps
pxl8.clear = gfx2d.clear pxl8.clear = gfx2d.clear
pxl8.pixel = gfx2d.pixel pxl8.pixel = gfx2d.pixel
@ -78,7 +83,7 @@ pxl8.Anim = anim.Anim
pxl8.create_anim = anim.Anim.new pxl8.create_anim = anim.Anim.new
pxl8.create_anim_from_ase = anim.Anim.from_ase pxl8.create_anim_from_ase = anim.Anim.from_ase
pxl8.bounds = math3d.bounds pxl8.bounds = math.bounds
pxl8.Camera3D = gfx3d.Camera3D pxl8.Camera3D = gfx3d.Camera3D
pxl8.create_camera_3d = gfx3d.Camera3D.new pxl8.create_camera_3d = gfx3d.Camera3D.new
@ -103,16 +108,16 @@ pxl8.create_gui = gui.Gui.new
pxl8.gui_label = gui.label pxl8.gui_label = gui.label
pxl8.gui_window = gui.window pxl8.gui_window = gui.window
pxl8.mat4_identity = math3d.mat4_identity pxl8.mat4_identity = math.mat4_identity
pxl8.mat4_lookat = math3d.mat4_lookat pxl8.mat4_lookat = math.mat4_lookat
pxl8.mat4_multiply = math3d.mat4_multiply pxl8.mat4_multiply = math.mat4_multiply
pxl8.mat4_ortho = math3d.mat4_ortho pxl8.mat4_ortho = math.mat4_ortho
pxl8.mat4_perspective = math3d.mat4_perspective pxl8.mat4_perspective = math.mat4_perspective
pxl8.mat4_rotate_x = math3d.mat4_rotate_x pxl8.mat4_rotate_x = math.mat4_rotate_x
pxl8.mat4_rotate_y = math3d.mat4_rotate_y pxl8.mat4_rotate_y = math.mat4_rotate_y
pxl8.mat4_rotate_z = math3d.mat4_rotate_z pxl8.mat4_rotate_z = math.mat4_rotate_z
pxl8.mat4_scale = math3d.mat4_scale pxl8.mat4_scale = math.mat4_scale
pxl8.mat4_translate = math3d.mat4_translate pxl8.mat4_translate = math.mat4_translate
pxl8.Net = net.Net pxl8.Net = net.Net
pxl8.create_net = net.Net.new pxl8.create_net = net.Net.new
@ -141,9 +146,10 @@ pxl8.pack_u64_le = bytes.pack_u64_le
pxl8.Particles = particles.Particles pxl8.Particles = particles.Particles
pxl8.create_particles = particles.Particles.new pxl8.create_particles = particles.Particles.new
pxl8.Graph = procgen.Graph
pxl8.create_graph = procgen.create_graph
pxl8.PROCGEN_ROOMS = world.PROCGEN_ROOMS pxl8.PROCGEN_ROOMS = world.PROCGEN_ROOMS
pxl8.PROCGEN_TERRAIN = world.PROCGEN_TERRAIN pxl8.PROCGEN_TERRAIN = world.PROCGEN_TERRAIN
pxl8.procgen_tex = world.procgen_tex
pxl8.SfxContext = sfx.SfxContext pxl8.SfxContext = sfx.SfxContext
pxl8.SfxNode = sfx.SfxNode pxl8.SfxNode = sfx.SfxNode

View file

@ -20,27 +20,40 @@ function core.get_fps()
end end
function core.palette_color(index) function core.palette_color(index)
local pal = C.pxl8_gfx_get_palette(core.gfx) local pal = C.pxl8_gfx_palette(core.gfx)
if pal == nil then return 0 end if pal == nil then return 0 end
return C.pxl8_palette_color(pal, index) return C.pxl8_palette_color(pal, index)
end end
function core.palette_index(color) function core.palette_index(color)
local pal = C.pxl8_gfx_get_palette(core.gfx) local pal = C.pxl8_gfx_palette(core.gfx)
if pal == nil then return -1 end if pal == nil then return -1 end
return C.pxl8_palette_index(pal, color) return C.pxl8_palette_index(pal, color)
end end
function core.set_palette_rgb(index, r, g, b) function core.set_palette_rgb(index, r, g, b)
local pal = C.pxl8_gfx_get_palette(core.gfx) local pal = C.pxl8_gfx_palette(core.gfx)
if pal == nil then return end if pal == nil then return end
C.pxl8_palette_set_rgb(pal, index, r, g, b) C.pxl8_palette_set_rgb(pal, index, r, g, b)
end
function core.set_palette(colors, count)
C.pxl8_gfx_set_palette_colors(core.gfx, colors, count)
end
function core.set_colormap(data, size)
local cm = C.pxl8_gfx_colormap(core.gfx)
if cm == nil then return end
C.pxl8_set_colormap(cm, data, size)
end
function core.update_palette_deps()
C.pxl8_gfx_blend_tables_update(core.gfx) C.pxl8_gfx_blend_tables_update(core.gfx)
C.pxl8_gfx_colormap_update(core.gfx) C.pxl8_gfx_colormap_update(core.gfx)
end end
function core.ramp_index(position) function core.ramp_index(position)
local pal = C.pxl8_gfx_get_palette(core.gfx) local pal = C.pxl8_gfx_palette(core.gfx)
if pal == nil then return 0 end if pal == nil then return 0 end
return C.pxl8_palette_ramp_index(pal, position) return C.pxl8_palette_ramp_index(pal, position)
end end

View file

@ -75,6 +75,15 @@ function Camera3D:update(dt)
C.pxl8_3d_camera_update(self._ptr, dt) C.pxl8_3d_camera_update(self._ptr, dt)
end end
function Camera3D:world_to_screen(x, y, z, width, height)
local pos = ffi.new("pxl8_vec3", {x = x, y = y, z = z})
local result = C.pxl8_3d_camera_world_to_screen(self._ptr, pos, width, height)
if result.visible then
return {x = result.x, y = result.y, depth = result.depth}
end
return nil
end
gfx3d.Camera3D = Camera3D gfx3d.Camera3D = Camera3D
local Mesh = {} local Mesh = {}
@ -123,14 +132,15 @@ gfx3d.Mesh = Mesh
function gfx3d.draw_mesh(mesh, opts) function gfx3d.draw_mesh(mesh, opts)
opts = opts or {} opts = opts or {}
local model = ffi.new("pxl8_mat4") local model = ffi.new("pxl8_mat4")
model.m[0] = 1 local s = opts.scale or 1
model.m[5] = 1 model.m[0] = s
model.m[10] = 1 model.m[5] = s
model.m[10] = s
model.m[15] = 1 model.m[15] = 1
if opts.x then model.m[12] = opts.x end if opts.x then model.m[12] = opts.x end
if opts.y then model.m[13] = opts.y end if opts.y then model.m[13] = opts.y end
if opts.z then model.m[14] = opts.z end if opts.z then model.m[14] = opts.z end
local material = ffi.new("pxl8_material", { local material = ffi.new("pxl8_gfx_material", {
texture_id = opts.texture or 0, texture_id = opts.texture or 0,
alpha = opts.alpha or 255, alpha = opts.alpha or 255,
blend_mode = opts.blend_mode or 0, blend_mode = opts.blend_mode or 0,
@ -139,6 +149,7 @@ function gfx3d.draw_mesh(mesh, opts)
dynamic_lighting = opts.lighting or false, dynamic_lighting = opts.lighting or false,
per_pixel = opts.per_pixel or false, per_pixel = opts.per_pixel or false,
vertex_color_passthrough = opts.passthrough or false, vertex_color_passthrough = opts.passthrough or false,
wireframe = opts.wireframe or false,
emissive_intensity = opts.emissive or 0.0, emissive_intensity = opts.emissive or 0.0,
}) })
C.pxl8_3d_draw_mesh(core.gfx, mesh._ptr, model, material) C.pxl8_3d_draw_mesh(core.gfx, mesh._ptr, model, material)

View file

@ -16,7 +16,7 @@ function Gui.new()
end end
function Gui:begin_frame() function Gui:begin_frame()
C.pxl8_gui_begin_frame(self._ptr) C.pxl8_gui_begin_frame(self._ptr, core.gfx)
end end
function Gui:button(id, x, y, w, h, label) function Gui:button(id, x, y, w, h, label)
@ -43,7 +43,7 @@ function Gui:destroy()
end end
function Gui:end_frame() function Gui:end_frame()
C.pxl8_gui_end_frame(self._ptr) C.pxl8_gui_end_frame(self._ptr, core.gfx)
end end
function Gui:get_cursor_pos() function Gui:get_cursor_pos()

View file

@ -1,53 +1,57 @@
local ffi = require("ffi") local ffi = require("ffi")
local C = ffi.C local C = ffi.C
local math3d = {} local math = {}
function math3d.mat4_identity() function math.hash32(x)
return C.pxl8_hash32(x)
end
function math.mat4_identity()
return C.pxl8_mat4_identity() return C.pxl8_mat4_identity()
end end
function math3d.mat4_mul(a, b) function math.mat4_mul(a, b)
return C.pxl8_mat4_mul(a, b) return C.pxl8_mat4_mul(a, b)
end end
function math3d.mat4_translate(x, y, z) function math.mat4_translate(x, y, z)
return C.pxl8_mat4_translate(x, y, z) return C.pxl8_mat4_translate(x, y, z)
end end
function math3d.mat4_rotate_x(angle) function math.mat4_rotate_x(angle)
return C.pxl8_mat4_rotate_x(angle) return C.pxl8_mat4_rotate_x(angle)
end end
function math3d.mat4_rotate_y(angle) function math.mat4_rotate_y(angle)
return C.pxl8_mat4_rotate_y(angle) return C.pxl8_mat4_rotate_y(angle)
end end
function math3d.mat4_rotate_z(angle) function math.mat4_rotate_z(angle)
return C.pxl8_mat4_rotate_z(angle) return C.pxl8_mat4_rotate_z(angle)
end end
function math3d.mat4_scale(x, y, z) function math.mat4_scale(x, y, z)
return C.pxl8_mat4_scale(x, y, z) return C.pxl8_mat4_scale(x, y, z)
end end
function math3d.mat4_ortho(left, right, bottom, top, near, far) function math.mat4_ortho(left, right, bottom, top, near, far)
return C.pxl8_mat4_ortho(left, right, bottom, top, near, far) return C.pxl8_mat4_ortho(left, right, bottom, top, near, far)
end end
function math3d.mat4_perspective(fov, aspect, near, far) function math.mat4_perspective(fov, aspect, near, far)
return C.pxl8_mat4_perspective(fov, aspect, near, far) return C.pxl8_mat4_perspective(fov, aspect, near, far)
end end
function math3d.mat4_lookat(eye, center, up) function math.mat4_lookat(eye, center, up)
local eye_vec = ffi.new("pxl8_vec3", {x = eye[1], y = eye[2], z = eye[3]}) local eye_vec = ffi.new("pxl8_vec3", {x = eye[1], y = eye[2], z = eye[3]})
local center_vec = ffi.new("pxl8_vec3", {x = center[1], y = center[2], z = center[3]}) local center_vec = ffi.new("pxl8_vec3", {x = center[1], y = center[2], z = center[3]})
local up_vec = ffi.new("pxl8_vec3", {x = up[1], y = up[2], z = up[3]}) local up_vec = ffi.new("pxl8_vec3", {x = up[1], y = up[2], z = up[3]})
return C.pxl8_mat4_lookat(eye_vec, center_vec, up_vec) return C.pxl8_mat4_lookat(eye_vec, center_vec, up_vec)
end end
function math3d.bounds(x, y, w, h) function math.bounds(x, y, w, h)
return ffi.new("pxl8_bounds", {x = x, y = y, w = w, h = h}) return ffi.new("pxl8_bounds", {x = x, y = y, w = w, h = h})
end end
return math3d return math

View file

@ -12,7 +12,7 @@ function Particles.new(max_count)
if ps == nil then if ps == nil then
return nil return nil
end end
local pal = C.pxl8_gfx_get_palette(core.gfx) local pal = C.pxl8_gfx_palette(core.gfx)
if pal ~= nil then if pal ~= nil then
C.pxl8_particles_set_palette(ps, pal) C.pxl8_particles_set_palette(ps, pal)
end end

99
src/lua/pxl8/procgen.lua Normal file
View file

@ -0,0 +1,99 @@
local ffi = require("ffi")
local C = ffi.C
local core = require("pxl8.core")
local procgen = {}
procgen.OP_CONST = C.PXL8_OP_CONST
procgen.OP_INPUT_AGE = C.PXL8_OP_INPUT_AGE
procgen.OP_INPUT_SEED = C.PXL8_OP_INPUT_SEED
procgen.OP_INPUT_TIME = C.PXL8_OP_INPUT_TIME
procgen.OP_INPUT_X = C.PXL8_OP_INPUT_X
procgen.OP_INPUT_Y = C.PXL8_OP_INPUT_Y
procgen.OP_ABS = C.PXL8_OP_ABS
procgen.OP_ADD = C.PXL8_OP_ADD
procgen.OP_CEIL = C.PXL8_OP_CEIL
procgen.OP_CLAMP = C.PXL8_OP_CLAMP
procgen.OP_COS = C.PXL8_OP_COS
procgen.OP_DIV = C.PXL8_OP_DIV
procgen.OP_FLOOR = C.PXL8_OP_FLOOR
procgen.OP_FRACT = C.PXL8_OP_FRACT
procgen.OP_GRADIENT_LINEAR = C.PXL8_OP_GRADIENT_LINEAR
procgen.OP_GRADIENT_RADIAL = C.PXL8_OP_GRADIENT_RADIAL
procgen.OP_LERP = C.PXL8_OP_LERP
procgen.OP_MAX = C.PXL8_OP_MAX
procgen.OP_MIN = C.PXL8_OP_MIN
procgen.OP_MOD = C.PXL8_OP_MOD
procgen.OP_MUL = C.PXL8_OP_MUL
procgen.OP_NEGATE = C.PXL8_OP_NEGATE
procgen.OP_NOISE_FBM = C.PXL8_OP_NOISE_FBM
procgen.OP_NOISE_PERLIN = C.PXL8_OP_NOISE_PERLIN
procgen.OP_NOISE_RIDGED = C.PXL8_OP_NOISE_RIDGED
procgen.OP_NOISE_TURBULENCE = C.PXL8_OP_NOISE_TURBULENCE
procgen.OP_NOISE_VALUE = C.PXL8_OP_NOISE_VALUE
procgen.OP_POW = C.PXL8_OP_POW
procgen.OP_QUANTIZE = C.PXL8_OP_QUANTIZE
procgen.OP_SELECT = C.PXL8_OP_SELECT
procgen.OP_SIN = C.PXL8_OP_SIN
procgen.OP_SMOOTHSTEP = C.PXL8_OP_SMOOTHSTEP
procgen.OP_SQRT = C.PXL8_OP_SQRT
procgen.OP_SUB = C.PXL8_OP_SUB
procgen.OP_VORONOI_CELL = C.PXL8_OP_VORONOI_CELL
procgen.OP_VORONOI_EDGE = C.PXL8_OP_VORONOI_EDGE
procgen.OP_VORONOI_ID = C.PXL8_OP_VORONOI_ID
local Graph = {}
Graph.__index = Graph
function Graph.new(capacity)
capacity = capacity or 64
local ptr = C.pxl8_graph_create(capacity)
if ptr == nil then
return nil
end
return setmetatable({ _ptr = ptr }, Graph)
end
function Graph:destroy()
if self._ptr then
C.pxl8_graph_destroy(self._ptr)
self._ptr = nil
end
end
function Graph:clear()
C.pxl8_graph_clear(self._ptr)
end
function Graph:add_node(op, in0, in1, in2, in3, param)
return C.pxl8_graph_add_node(self._ptr, op, in0 or 0, in1 or 0, in2 or 0, in3 or 0, param or 0)
end
function Graph:set_output(reg)
C.pxl8_graph_set_output(self._ptr, reg)
end
function Graph:set_seed(seed)
C.pxl8_graph_set_seed(self._ptr, seed)
end
function Graph:eval_texture(width, height)
width = width or 64
height = height or 64
local buffer = ffi.new("u8[?]", width * height)
C.pxl8_graph_eval_texture(self._ptr, buffer, width, height)
local tex_id = C.pxl8_gfx_create_texture(core.gfx, buffer, width, height)
if tex_id < 0 then
return nil
end
return tex_id
end
procgen.Graph = Graph
function procgen.create_graph(capacity)
return Graph.new(capacity)
end
return procgen

View file

@ -85,36 +85,14 @@ function World:resolve_collision(from_x, from_y, from_z, to_x, to_y, to_z, radiu
return result.x, result.y, result.z return result.x, result.y, result.z
end end
function World:set_wireframe(enabled, color)
C.pxl8_world_set_wireframe(self._ptr, enabled, color or 15)
end
function World:unload() function World:unload()
C.pxl8_world_unload(self._ptr) C.pxl8_world_unload(self._ptr)
end end
world.World = World world.World = World
function world.procgen_tex(params)
local width = params.width or 64
local height = params.height or 64
local buffer = ffi.new("u8[?]", width * height)
local tex_params = ffi.new("pxl8_procgen_tex_params")
local name = params.name or ""
ffi.copy(tex_params.name, name, math.min(#name, 15))
tex_params.seed = params.seed or 0
tex_params.width = width
tex_params.height = height
tex_params.scale = params.scale or 1.0
tex_params.roughness = params.roughness or 0.0
tex_params.base_color = params.base_color or 0
tex_params.variation = params.variation or 0
C.pxl8_procgen_tex(buffer, tex_params)
local tex_id = C.pxl8_gfx_create_texture(core.gfx, buffer, width, height)
if tex_id < 0 then
return nil
end
return tex_id
end
return world return world

View file

@ -1,5 +1,14 @@
#include "pxl8_math.h" #include "pxl8_math.h"
u32 pxl8_hash32(u32 x) {
x ^= x >> 16;
x *= 0x85EBCA6Bu;
x ^= x >> 13;
x *= 0xC2B2AE35u;
x ^= x >> 16;
return x;
}
pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b) { pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b) {
return (pxl8_vec2){ return (pxl8_vec2){
.x = a.x + b.x, .x = a.x + b.x,

View file

@ -4,16 +4,49 @@
#include "pxl8_types.h" #include "pxl8_types.h"
#if defined(__x86_64__) || defined(_M_X64)
#include <xmmintrin.h>
#elif defined(__aarch64__) || defined(_M_ARM64)
#include <arm_neon.h>
#endif
#define PXL8_PI 3.14159265358979323846f #define PXL8_PI 3.14159265358979323846f
#define PXL8_TAU (PXL8_PI * 2.0f) #define PXL8_TAU (PXL8_PI * 2.0f)
static inline f32 pxl8_fast_inv_sqrt(f32 x) { static inline f32 pxl8_fast_inv_sqrt(f32 x) {
#if defined(__x86_64__) || defined(_M_X64)
__m128 v = _mm_set_ss(x);
v = _mm_rsqrt_ss(v);
return _mm_cvtss_f32(v);
#elif defined(__aarch64__) || defined(_M_ARM64)
float32x2_t v = vdup_n_f32(x);
float32x2_t est = vrsqrte_f32(v);
est = vmul_f32(est, vrsqrts_f32(vmul_f32(v, est), est));
return vget_lane_f32(est, 0);
#else
f32 half = 0.5f * x; f32 half = 0.5f * x;
i32 i = *(i32*)&x; i32 i = *(i32*)&x;
i = 0x5f3759df - (i >> 1); i = 0x5f3759df - (i >> 1);
x = *(f32*)&i; x = *(f32*)&i;
x = x * (1.5f - half * x * x); x = x * (1.5f - half * x * x);
return x; return x;
#endif
}
static inline f32 pxl8_fast_rcp(f32 x) {
#if defined(__x86_64__) || defined(_M_X64)
__m128 v = _mm_set_ss(x);
__m128 rcp = _mm_rcp_ss(v);
rcp = _mm_add_ss(rcp, _mm_mul_ss(rcp, _mm_sub_ss(_mm_set_ss(1.0f), _mm_mul_ss(v, rcp))));
return _mm_cvtss_f32(rcp);
#elif defined(__aarch64__) || defined(_M_ARM64)
float32x2_t v = vdup_n_f32(x);
float32x2_t est = vrecpe_f32(v);
est = vmul_f32(est, vrecps_f32(v, est));
return vget_lane_f32(est, 0);
#else
return 1.0f / x;
#endif
} }
typedef struct pxl8_vec2 { typedef struct pxl8_vec2 {
@ -33,18 +66,27 @@ typedef struct pxl8_mat4 {
} pxl8_mat4; } pxl8_mat4;
typedef struct pxl8_plane { typedef struct pxl8_plane {
pxl8_vec3 normal;
f32 distance; f32 distance;
pxl8_vec3 normal;
} pxl8_plane; } pxl8_plane;
typedef struct pxl8_frustum { typedef struct pxl8_frustum {
pxl8_plane planes[6]; pxl8_plane planes[6];
} pxl8_frustum; } pxl8_frustum;
typedef struct pxl8_projected_point {
f32 depth;
i32 x;
i32 y;
bool visible;
} pxl8_projected_point;
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
u32 pxl8_hash32(u32 x);
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);
f32 pxl8_vec2_length(pxl8_vec2 v); f32 pxl8_vec2_length(pxl8_vec2 v);

View file

@ -1,291 +0,0 @@
#pragma once
#include "pxl8_types.h"
#if defined(__x86_64__) || defined(_M_X64)
#define PXL8_SIMD_SSE2 1
#include <emmintrin.h>
#elif defined(__aarch64__) || defined(_M_ARM64)
#define PXL8_SIMD_NEON 1
#include <arm_neon.h>
#else
#define PXL8_SIMD_SCALAR 1
#endif
#ifdef __cplusplus
extern "C" {
#endif
#if defined(PXL8_SIMD_SSE2)
typedef struct { __m128 v; } pxl8_f32x4;
typedef struct { __m128i v; } pxl8_i32x4;
typedef struct { __m128i v; } pxl8_u16x8;
static inline pxl8_f32x4 pxl8_f32x4_splat(f32 x) {
return (pxl8_f32x4){ _mm_set1_ps(x) };
}
static inline pxl8_f32x4 pxl8_f32x4_new(f32 a, f32 b, f32 c, f32 d) {
return (pxl8_f32x4){ _mm_set_ps(d, c, b, a) };
}
static inline pxl8_f32x4 pxl8_f32x4_add(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_add_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_sub(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_sub_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_mul(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_mul_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_div(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_div_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_min(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_min_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_max(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_max_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_cmplt(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_cmplt_ps(a.v, b.v) };
}
static inline i32 pxl8_f32x4_movemask(pxl8_f32x4 a) {
return _mm_movemask_ps(a.v);
}
static inline pxl8_i32x4 pxl8_f32x4_to_i32x4(pxl8_f32x4 a) {
return (pxl8_i32x4){ _mm_cvttps_epi32(a.v) };
}
static inline void pxl8_f32x4_store(pxl8_f32x4 a, f32* out) {
_mm_storeu_ps(out, a.v);
}
static inline pxl8_i32x4 pxl8_i32x4_splat(i32 x) {
return (pxl8_i32x4){ _mm_set1_epi32(x) };
}
static inline pxl8_i32x4 pxl8_i32x4_slli(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){ _mm_slli_epi32(a.v, n) };
}
static inline pxl8_i32x4 pxl8_i32x4_srai(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){ _mm_srai_epi32(a.v, n) };
}
static inline pxl8_i32x4 pxl8_i32x4_and(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){ _mm_and_si128(a.v, b.v) };
}
static inline pxl8_i32x4 pxl8_i32x4_or(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){ _mm_or_si128(a.v, b.v) };
}
static inline void pxl8_i32x4_store(pxl8_i32x4 a, i32* out) {
_mm_storeu_si128((__m128i*)out, a.v);
}
static inline pxl8_u16x8 pxl8_u16x8_cmplt(pxl8_u16x8 a, pxl8_u16x8 b) {
return (pxl8_u16x8){ _mm_cmplt_epi16(a.v, b.v) };
}
static inline pxl8_u16x8 pxl8_u16x8_blend(pxl8_u16x8 a, pxl8_u16x8 b, pxl8_u16x8 mask) {
__m128i not_mask = _mm_andnot_si128(mask.v, a.v);
__m128i and_mask = _mm_and_si128(mask.v, b.v);
return (pxl8_u16x8){ _mm_or_si128(not_mask, and_mask) };
}
static inline i32 pxl8_u16x8_movemask(pxl8_u16x8 a) {
return _mm_movemask_epi8(a.v);
}
#elif defined(PXL8_SIMD_NEON)
typedef struct { float32x4_t v; } pxl8_f32x4;
typedef struct { int32x4_t v; } pxl8_i32x4;
typedef struct { uint16x8_t v; } pxl8_u16x8;
static inline pxl8_f32x4 pxl8_f32x4_splat(f32 x) {
return (pxl8_f32x4){ vdupq_n_f32(x) };
}
static inline pxl8_f32x4 pxl8_f32x4_new(f32 a, f32 b, f32 c, f32 d) {
f32 arr[4] = {a, b, c, d};
return (pxl8_f32x4){ vld1q_f32(arr) };
}
static inline pxl8_f32x4 pxl8_f32x4_add(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vaddq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_sub(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vsubq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_mul(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vmulq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_div(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vdivq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_min(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vminq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_max(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vmaxq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_cmplt(pxl8_f32x4 a, pxl8_f32x4 b) {
uint32x4_t cmp = vcltq_f32(a.v, b.v);
return (pxl8_f32x4){ vreinterpretq_f32_u32(cmp) };
}
static inline i32 pxl8_f32x4_movemask(pxl8_f32x4 a) {
uint32x4_t input = vreinterpretq_u32_f32(a.v);
static const i32 shifts[4] = {0, 1, 2, 3};
uint32x4_t shifted = vshrq_n_u32(input, 31);
return vgetq_lane_u32(shifted, 0) | (vgetq_lane_u32(shifted, 1) << 1) |
(vgetq_lane_u32(shifted, 2) << 2) | (vgetq_lane_u32(shifted, 3) << 3);
}
static inline pxl8_i32x4 pxl8_f32x4_to_i32x4(pxl8_f32x4 a) {
return (pxl8_i32x4){ vcvtq_s32_f32(a.v) };
}
static inline void pxl8_f32x4_store(pxl8_f32x4 a, f32* out) {
vst1q_f32(out, a.v);
}
static inline pxl8_i32x4 pxl8_i32x4_splat(i32 x) {
return (pxl8_i32x4){ vdupq_n_s32(x) };
}
static inline pxl8_i32x4 pxl8_i32x4_slli(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){ vshlq_s32(a.v, vdupq_n_s32(n)) };
}
static inline pxl8_i32x4 pxl8_i32x4_srai(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){ vshlq_s32(a.v, vdupq_n_s32(-n)) };
}
static inline pxl8_i32x4 pxl8_i32x4_and(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){ vandq_s32(a.v, b.v) };
}
static inline pxl8_i32x4 pxl8_i32x4_or(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){ vorrq_s32(a.v, b.v) };
}
static inline void pxl8_i32x4_store(pxl8_i32x4 a, i32* out) {
vst1q_s32(out, a.v);
}
#else
typedef struct { f32 v[4]; } pxl8_f32x4;
typedef struct { i32 v[4]; } pxl8_i32x4;
typedef struct { u16 v[8]; } pxl8_u16x8;
static inline pxl8_f32x4 pxl8_f32x4_splat(f32 x) {
return (pxl8_f32x4){{ x, x, x, x }};
}
static inline pxl8_f32x4 pxl8_f32x4_new(f32 a, f32 b, f32 c, f32 d) {
return (pxl8_f32x4){{ a, b, c, d }};
}
static inline pxl8_f32x4 pxl8_f32x4_add(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{ a.v[0]+b.v[0], a.v[1]+b.v[1], a.v[2]+b.v[2], a.v[3]+b.v[3] }};
}
static inline pxl8_f32x4 pxl8_f32x4_sub(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{ a.v[0]-b.v[0], a.v[1]-b.v[1], a.v[2]-b.v[2], a.v[3]-b.v[3] }};
}
static inline pxl8_f32x4 pxl8_f32x4_mul(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{ a.v[0]*b.v[0], a.v[1]*b.v[1], a.v[2]*b.v[2], a.v[3]*b.v[3] }};
}
static inline pxl8_f32x4 pxl8_f32x4_div(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{ a.v[0]/b.v[0], a.v[1]/b.v[1], a.v[2]/b.v[2], a.v[3]/b.v[3] }};
}
static inline pxl8_f32x4 pxl8_f32x4_min(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{
a.v[0]<b.v[0]?a.v[0]:b.v[0], a.v[1]<b.v[1]?a.v[1]:b.v[1],
a.v[2]<b.v[2]?a.v[2]:b.v[2], a.v[3]<b.v[3]?a.v[3]:b.v[3]
}};
}
static inline pxl8_f32x4 pxl8_f32x4_max(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{
a.v[0]>b.v[0]?a.v[0]:b.v[0], a.v[1]>b.v[1]?a.v[1]:b.v[1],
a.v[2]>b.v[2]?a.v[2]:b.v[2], a.v[3]>b.v[3]?a.v[3]:b.v[3]
}};
}
static inline pxl8_f32x4 pxl8_f32x4_cmplt(pxl8_f32x4 a, pxl8_f32x4 b) {
pxl8_f32x4 r;
u32* rv = (u32*)r.v;
rv[0] = a.v[0] < b.v[0] ? 0xFFFFFFFF : 0;
rv[1] = a.v[1] < b.v[1] ? 0xFFFFFFFF : 0;
rv[2] = a.v[2] < b.v[2] ? 0xFFFFFFFF : 0;
rv[3] = a.v[3] < b.v[3] ? 0xFFFFFFFF : 0;
return r;
}
static inline i32 pxl8_f32x4_movemask(pxl8_f32x4 a) {
u32* av = (u32*)a.v;
return ((av[0] >> 31) & 1) | ((av[1] >> 31) & 1) << 1 |
((av[2] >> 31) & 1) << 2 | ((av[3] >> 31) & 1) << 3;
}
static inline pxl8_i32x4 pxl8_f32x4_to_i32x4(pxl8_f32x4 a) {
return (pxl8_i32x4){{ (i32)a.v[0], (i32)a.v[1], (i32)a.v[2], (i32)a.v[3] }};
}
static inline void pxl8_f32x4_store(pxl8_f32x4 a, f32* out) {
out[0] = a.v[0]; out[1] = a.v[1]; out[2] = a.v[2]; out[3] = a.v[3];
}
static inline pxl8_i32x4 pxl8_i32x4_splat(i32 x) {
return (pxl8_i32x4){{ x, x, x, x }};
}
static inline pxl8_i32x4 pxl8_i32x4_slli(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){{ a.v[0]<<n, a.v[1]<<n, a.v[2]<<n, a.v[3]<<n }};
}
static inline pxl8_i32x4 pxl8_i32x4_srai(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){{ a.v[0]>>n, a.v[1]>>n, a.v[2]>>n, a.v[3]>>n }};
}
static inline pxl8_i32x4 pxl8_i32x4_and(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){{ a.v[0]&b.v[0], a.v[1]&b.v[1], a.v[2]&b.v[2], a.v[3]&b.v[3] }};
}
static inline pxl8_i32x4 pxl8_i32x4_or(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){{ a.v[0]|b.v[0], a.v[1]|b.v[1], a.v[2]|b.v[2], a.v[3]|b.v[3] }};
}
static inline void pxl8_i32x4_store(pxl8_i32x4 a, i32* out) {
out[0] = a.v[0]; out[1] = a.v[1]; out[2] = a.v[2]; out[3] = a.v[3];
}
#endif
#ifdef __cplusplus
}
#endif

331
src/procgen/pxl8_graph.c Normal file
View file

@ -0,0 +1,331 @@
#include "pxl8_graph.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "pxl8_math.h"
static inline u32 hash2d(i32 x, i32 y, u32 seed) {
return pxl8_hash32((u32)x ^ ((u32)y * 2654435769u) ^ seed);
}
static inline f32 hash2d_f(i32 x, i32 y, u32 seed) {
return (f32)hash2d(x, y, seed) / (f32)0xFFFFFFFF;
}
static f32 gradient2d(i32 ix, i32 iy, f32 fx, f32 fy, u32 seed) {
u32 h = hash2d(ix, iy, seed);
f32 angle = (f32)h / (f32)0xFFFFFFFF * 6.28318530718f;
f32 gx = cosf(angle);
f32 gy = sinf(angle);
return gx * fx + gy * fy;
}
static inline f32 smoothstep(f32 t) {
return t * t * (3.0f - 2.0f * t);
}
static inline f32 lerp(f32 a, f32 b, f32 t) {
return a + t * (b - a);
}
static f32 noise_value(f32 x, f32 y, f32 scale, u32 seed) {
f32 sx = x * scale;
f32 sy = y * scale;
i32 ix = (i32)floorf(sx);
i32 iy = (i32)floorf(sy);
f32 fx = sx - (f32)ix;
f32 fy = sy - (f32)iy;
f32 u = smoothstep(fx);
f32 v = smoothstep(fy);
f32 n00 = hash2d_f(ix, iy, seed);
f32 n10 = hash2d_f(ix + 1, iy, seed);
f32 n01 = hash2d_f(ix, iy + 1, seed);
f32 n11 = hash2d_f(ix + 1, iy + 1, seed);
return lerp(lerp(n00, n10, u), lerp(n01, n11, u), v);
}
static f32 noise_perlin(f32 x, f32 y, f32 scale, u32 seed) {
f32 sx = x * scale;
f32 sy = y * scale;
i32 ix = (i32)floorf(sx);
i32 iy = (i32)floorf(sy);
f32 fx = sx - (f32)ix;
f32 fy = sy - (f32)iy;
f32 u = smoothstep(fx);
f32 v = smoothstep(fy);
f32 n00 = gradient2d(ix, iy, fx, fy, seed);
f32 n10 = gradient2d(ix + 1, iy, fx - 1.0f, fy, seed);
f32 n01 = gradient2d(ix, iy + 1, fx, fy - 1.0f, seed);
f32 n11 = gradient2d(ix + 1, iy + 1, fx - 1.0f, fy - 1.0f, seed);
f32 result = lerp(lerp(n00, n10, u), lerp(n01, n11, u), v);
return result * 0.5f + 0.5f;
}
static f32 noise_fbm(f32 x, f32 y, i32 octaves, f32 scale, f32 persistence, u32 seed) {
f32 value = 0.0f;
f32 amplitude = 1.0f;
f32 frequency = scale;
f32 max_value = 0.0f;
for (i32 i = 0; i < octaves; i++) {
value += amplitude * noise_perlin(x, y, frequency, seed + (u32)i * 1337);
max_value += amplitude;
amplitude *= persistence;
frequency *= 2.0f;
}
return value / max_value;
}
static f32 noise_ridged(f32 x, f32 y, i32 octaves, f32 scale, f32 persistence, u32 seed) {
f32 value = 0.0f;
f32 amplitude = 1.0f;
f32 frequency = scale;
f32 max_value = 0.0f;
for (i32 i = 0; i < octaves; i++) {
f32 n = noise_perlin(x, y, frequency, seed + (u32)i * 1337);
n = 1.0f - fabsf(n * 2.0f - 1.0f);
value += amplitude * n;
max_value += amplitude;
amplitude *= persistence;
frequency *= 2.0f;
}
return value / max_value;
}
static f32 noise_turbulence(f32 x, f32 y, i32 octaves, f32 scale, f32 persistence, u32 seed) {
f32 value = 0.0f;
f32 amplitude = 1.0f;
f32 frequency = scale;
f32 max_value = 0.0f;
for (i32 i = 0; i < octaves; i++) {
f32 n = noise_perlin(x, y, frequency, seed + (u32)i * 1337);
value += amplitude * fabsf(n * 2.0f - 1.0f);
max_value += amplitude;
amplitude *= persistence;
frequency *= 2.0f;
}
return value / max_value;
}
static void voronoi(f32 x, f32 y, f32 scale, u32 seed, f32* cell_dist, f32* edge_dist, i32* cell_id) {
f32 sx = x * scale;
f32 sy = y * scale;
i32 cx = (i32)floorf(sx);
i32 cy = (i32)floorf(sy);
f32 fx = sx - (f32)cx;
f32 fy = sy - (f32)cy;
f32 min_dist = 1e30f;
f32 second_dist = 1e30f;
i32 closest_id = 0;
for (i32 dy = -1; dy <= 1; dy++) {
for (i32 dx = -1; dx <= 1; dx++) {
i32 nx = cx + dx;
i32 ny = cy + dy;
u32 h = hash2d(nx, ny, seed);
f32 px = (f32)dx + (f32)(h & 0xFF) / 255.0f - 0.5f - fx;
f32 py = (f32)dy + (f32)((h >> 8) & 0xFF) / 255.0f - 0.5f - fy;
f32 dist = px * px + py * py;
if (dist < min_dist) {
second_dist = min_dist;
min_dist = dist;
closest_id = (i32)h;
} else if (dist < second_dist) {
second_dist = dist;
}
}
}
*cell_dist = sqrtf(min_dist);
*edge_dist = sqrtf(second_dist) - sqrtf(min_dist);
*cell_id = closest_id;
}
static f32 gradient_linear(f32 x, f32 y, f32 angle) {
f32 dx = cosf(angle);
f32 dy = sinf(angle);
f32 result = x * dx + y * dy;
return fmaxf(0.0f, fminf(1.0f, result));
}
static f32 gradient_radial(f32 x, f32 y, f32 cx, f32 cy) {
f32 dx = x - cx;
f32 dy = y - cy;
return fmaxf(0.0f, fminf(1.0f, sqrtf(dx * dx + dy * dy)));
}
pxl8_graph* pxl8_graph_create(u32 capacity) {
pxl8_graph* graph = calloc(1, sizeof(pxl8_graph));
if (!graph) return NULL;
graph->nodes = calloc(capacity, sizeof(pxl8_node));
if (!graph->nodes) {
free(graph);
return NULL;
}
graph->capacity = capacity;
graph->count = 0;
graph->seed = 0;
graph->output_reg = 0;
return graph;
}
void pxl8_graph_destroy(pxl8_graph* graph) {
if (!graph) return;
free(graph->nodes);
free(graph);
}
void pxl8_graph_clear(pxl8_graph* graph) {
if (!graph) return;
graph->count = 0;
graph->output_reg = 0;
}
u8 pxl8_graph_add_node(pxl8_graph* graph, pxl8_graph_op op, u8 in0, u8 in1, u8 in2, u8 in3, f32 param) {
if (!graph || graph->count >= graph->capacity) return 0;
u8 out = (u8)(graph->count + 4);
pxl8_node* node = &graph->nodes[graph->count++];
node->op = (u8)op;
node->out = out;
node->in[0] = in0;
node->in[1] = in1;
node->in[2] = in2;
node->in[3] = in3;
node->param = param;
return out;
}
void pxl8_graph_set_output(pxl8_graph* graph, u8 reg) {
if (graph) graph->output_reg = reg;
}
void pxl8_graph_set_seed(pxl8_graph* graph, u32 seed) {
if (graph) graph->seed = seed;
}
f32 pxl8_graph_eval(const pxl8_graph* graph, pxl8_graph_context* ctx) {
if (!graph || !ctx) return 0.0f;
for (u32 i = 0; i < graph->count; i++) {
const pxl8_node* n = &graph->nodes[i];
f32 a = ctx->regs[n->in[0]];
f32 b = ctx->regs[n->in[1]];
f32 c = ctx->regs[n->in[2]];
f32 d = ctx->regs[n->in[3]];
f32 result = 0.0f;
switch (n->op) {
case PXL8_OP_CONST: result = n->param; break;
case PXL8_OP_INPUT_AGE: result = ctx->regs[3]; break;
case PXL8_OP_INPUT_SEED: result = (f32)ctx->seed; break;
case PXL8_OP_INPUT_TIME: result = ctx->regs[2]; break;
case PXL8_OP_INPUT_X: result = ctx->regs[0]; break;
case PXL8_OP_INPUT_Y: result = ctx->regs[1]; break;
case PXL8_OP_ABS: result = fabsf(a); break;
case PXL8_OP_CEIL: result = ceilf(a); break;
case PXL8_OP_COS: result = cosf(a); break;
case PXL8_OP_FLOOR: result = floorf(a); break;
case PXL8_OP_FRACT: result = a - floorf(a); break;
case PXL8_OP_NEGATE: result = -a; break;
case PXL8_OP_SIN: result = sinf(a); break;
case PXL8_OP_SQRT: result = sqrtf(a); break;
case PXL8_OP_ADD: result = a + b; break;
case PXL8_OP_DIV: result = b != 0.0f ? a / b : 0.0f; break;
case PXL8_OP_MAX: result = fmaxf(a, b); break;
case PXL8_OP_MIN: result = fminf(a, b); break;
case PXL8_OP_MOD: result = b != 0.0f ? fmodf(a, b) : 0.0f; break;
case PXL8_OP_MUL: result = a * b; break;
case PXL8_OP_POW: result = powf(a, b); break;
case PXL8_OP_SUB: result = a - b; break;
case PXL8_OP_CLAMP: result = fmaxf(b, fminf(c, a)); break;
case PXL8_OP_LERP: result = a + c * (b - a); break;
case PXL8_OP_SELECT: result = c > 0.0f ? a : b; break;
case PXL8_OP_SMOOTHSTEP: {
f32 t = fmaxf(0.0f, fminf(1.0f, (c - a) / (b - a)));
result = t * t * (3.0f - 2.0f * t);
break;
}
case PXL8_OP_NOISE_FBM: result = noise_fbm(a, b, (i32)n->param, c, d, ctx->seed); break;
case PXL8_OP_NOISE_PERLIN: result = noise_perlin(a, b, c, ctx->seed); break;
case PXL8_OP_NOISE_RIDGED: result = noise_ridged(a, b, (i32)n->param, c, d, ctx->seed); break;
case PXL8_OP_NOISE_TURBULENCE:result = noise_turbulence(a, b, (i32)n->param, c, d, ctx->seed); break;
case PXL8_OP_NOISE_VALUE: result = noise_value(a, b, c, ctx->seed); break;
case PXL8_OP_VORONOI_CELL: {
f32 cell, edge; i32 id;
voronoi(a, b, c, ctx->seed, &cell, &edge, &id);
result = cell;
break;
}
case PXL8_OP_VORONOI_EDGE: {
f32 cell, edge; i32 id;
voronoi(a, b, c, ctx->seed, &cell, &edge, &id);
result = edge;
break;
}
case PXL8_OP_VORONOI_ID: {
f32 cell, edge; i32 id;
voronoi(a, b, c, ctx->seed, &cell, &edge, &id);
result = (f32)(id & 0xFF) / 255.0f;
break;
}
case PXL8_OP_GRADIENT_LINEAR: result = gradient_linear(a, b, c); break;
case PXL8_OP_GRADIENT_RADIAL: result = gradient_radial(a, b, c, d); break;
case PXL8_OP_QUANTIZE: {
u8 base = (u8)n->param;
f32 range = b;
f32 clamped = fmaxf(0.0f, fminf(1.0f, a));
result = (f32)(base + (u8)fminf(clamped * range, range - 1.0f));
break;
}
default: break;
}
ctx->regs[n->out] = result;
}
return ctx->regs[graph->output_reg];
}
void pxl8_graph_eval_texture(const pxl8_graph* graph, u8* buffer, i32 width, i32 height) {
if (!graph || !buffer) return;
pxl8_graph_context ctx = {0};
ctx.seed = graph->seed;
for (i32 y = 0; y < height; y++) {
for (i32 x = 0; x < width; x++) {
ctx.regs[0] = (f32)x / (f32)width;
ctx.regs[1] = (f32)y / (f32)height;
ctx.regs[2] = 0.0f;
ctx.regs[3] = 0.0f;
f32 result = pxl8_graph_eval(graph, &ctx);
buffer[y * width + x] = (u8)fmaxf(0.0f, fminf(255.0f, result));
}
}
}

90
src/procgen/pxl8_graph.h Normal file
View file

@ -0,0 +1,90 @@
#pragma once
#include "pxl8_types.h"
typedef enum pxl8_graph_op {
PXL8_OP_CONST, // param -> out
PXL8_OP_INPUT_AGE, // particle normalized age -> out
PXL8_OP_INPUT_SEED, // seed -> out
PXL8_OP_INPUT_TIME, // time -> out
PXL8_OP_INPUT_X, // x coord -> out
PXL8_OP_INPUT_Y, // y coord -> out
PXL8_OP_ABS, // |a| -> out
PXL8_OP_CEIL, // ceil(a) -> out
PXL8_OP_COS, // cos(a) -> out
PXL8_OP_FLOOR, // floor(a) -> out
PXL8_OP_FRACT, // fract(a) -> out
PXL8_OP_NEGATE, // -a -> out
PXL8_OP_SIN, // sin(a) -> out
PXL8_OP_SQRT, // sqrt(a) -> out
PXL8_OP_ADD, // a + b -> out
PXL8_OP_DIV, // a / b -> out
PXL8_OP_MAX, // max(a, b) -> out
PXL8_OP_MIN, // min(a, b) -> out
PXL8_OP_MOD, // fmod(a, b) -> out
PXL8_OP_MUL, // a * b -> out
PXL8_OP_POW, // pow(a, b) -> out
PXL8_OP_SUB, // a - b -> out
PXL8_OP_CLAMP, // clamp(a, min, max) -> out
PXL8_OP_LERP, // lerp(a, b, t) -> out
PXL8_OP_SELECT, // t > 0 ? a : b -> out
PXL8_OP_SMOOTHSTEP, // smoothstep(edge0, edge1, x) -> out
PXL8_OP_NOISE_FBM, // fbm(x, y, octaves, scale, persistence) -> out
PXL8_OP_NOISE_PERLIN, // perlin noise(x, y, scale) -> out
PXL8_OP_NOISE_RIDGED, // ridged(x, y, octaves, scale, persistence) -> out
PXL8_OP_NOISE_TURBULENCE,// turbulence(x, y, octaves, scale, persistence) -> out
PXL8_OP_NOISE_VALUE, // value noise(x, y, scale) -> out
PXL8_OP_VORONOI_CELL, // voronoi cell distance(x, y, scale) -> out
PXL8_OP_VORONOI_EDGE, // voronoi edge distance(x, y, scale) -> out
PXL8_OP_VORONOI_ID, // voronoi cell id(x, y, scale) -> out
PXL8_OP_GRADIENT_LINEAR, // linear gradient(x, y, angle) -> out
PXL8_OP_GRADIENT_RADIAL, // radial gradient(x, y, cx, cy) -> out
PXL8_OP_QUANTIZE, // quantize to palette: base + floor(a * range) -> out
PXL8_OP_COUNT
} pxl8_graph_op;
typedef struct pxl8_node {
u8 in[4];
u8 op;
u8 out;
f32 param;
} pxl8_node;
typedef struct pxl8_graph {
u32 capacity;
u32 count;
pxl8_node* nodes;
u8 output_reg;
u32 seed;
} pxl8_graph;
typedef struct pxl8_graph_context {
f32 regs[256];
u32 seed;
} pxl8_graph_context;
#ifdef __cplusplus
extern "C" {
#endif
f32 pxl8_graph_eval(const pxl8_graph* graph, pxl8_graph_context* ctx);
void pxl8_graph_eval_texture(const pxl8_graph* graph, u8* buffer, i32 width, i32 height);
u8 pxl8_graph_add_node(pxl8_graph* graph, pxl8_graph_op op, u8 in0, u8 in1, u8 in2, u8 in3, f32 param);
void pxl8_graph_clear(pxl8_graph* graph);
pxl8_graph* pxl8_graph_create(u32 capacity);
void pxl8_graph_destroy(pxl8_graph* graph);
void pxl8_graph_set_output(pxl8_graph* graph, u8 reg);
void pxl8_graph_set_seed(pxl8_graph* graph, u32 seed);
#ifdef __cplusplus
}
#endif

View file

@ -1,14 +1,13 @@
#include "pxl8_repl.h" #include "pxl8_repl.h"
#include <poll.h> #include <poll.h>
#include <pthread.h>
#include <stdatomic.h> #include <stdatomic.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h>
#include <unistd.h> #include <unistd.h>
#include <SDL3/SDL.h>
#include <linenoise.h> #include <linenoise.h>
#define PXL8_MAX_REPL_COMMAND_SIZE 4096 #define PXL8_MAX_REPL_COMMAND_SIZE 4096
@ -29,7 +28,7 @@ struct pxl8_repl {
atomic_uint log_read_idx; atomic_uint log_read_idx;
atomic_bool should_quit; atomic_bool should_quit;
pthread_t thread; SDL_Thread* thread;
char accumulator[PXL8_MAX_REPL_COMMAND_SIZE]; char accumulator[PXL8_MAX_REPL_COMMAND_SIZE];
pxl8_repl_command command; pxl8_repl_command command;
}; };
@ -105,7 +104,7 @@ static void pxl8_repl_flush_logs(pxl8_repl* repl) {
fflush(stdout); fflush(stdout);
} }
static void* pxl8_repl_thread(void* arg) { static int pxl8_repl_thread(void* arg) {
pxl8_repl* repl = (pxl8_repl*)arg; pxl8_repl* repl = (pxl8_repl*)arg;
printf("[pxl8 REPL] Fennel 1.6.0 - Tab for completion, Ctrl-D to exit\n"); printf("[pxl8 REPL] Fennel 1.6.0 - Tab for completion, Ctrl-D to exit\n");
@ -204,8 +203,7 @@ static void* pxl8_repl_thread(void* arg) {
lw = atomic_load(&repl->log_write_idx); lw = atomic_load(&repl->log_write_idx);
} }
fflush(stdout); fflush(stdout);
struct timespec ts = {.tv_sec = 0, .tv_nsec = 1000000}; SDL_Delay(1);
nanosleep(&ts, NULL);
} }
atomic_store(&repl->cmd_complete, false); atomic_store(&repl->cmd_complete, false);
} }
@ -214,7 +212,7 @@ static void* pxl8_repl_thread(void* arg) {
pxl8_repl_flush_logs(repl); pxl8_repl_flush_logs(repl);
return NULL; return 0;
} }
pxl8_repl* pxl8_repl_create(void) { pxl8_repl* pxl8_repl_create(void) {
@ -237,7 +235,8 @@ pxl8_repl* pxl8_repl_create(void) {
g_repl = repl; g_repl = repl;
if (pthread_create(&repl->thread, NULL, pxl8_repl_thread, repl) != 0) { repl->thread = SDL_CreateThread(pxl8_repl_thread, "pxl8-repl", repl);
if (!repl->thread) {
free(repl); free(repl);
g_repl = NULL; g_repl = NULL;
return NULL; return NULL;
@ -251,13 +250,12 @@ void pxl8_repl_destroy(pxl8_repl* repl) {
atomic_store(&repl->should_quit, true); atomic_store(&repl->should_quit, true);
struct timespec ts = {.tv_sec = 0, .tv_nsec = 2000000}; SDL_Delay(2);
nanosleep(&ts, NULL);
printf("\r\033[K"); printf("\r\033[K");
fflush(stdout); fflush(stdout);
pthread_join(repl->thread, NULL); SDL_WaitThread(repl->thread, NULL);
pxl8_repl_flush_logs(repl); pxl8_repl_flush_logs(repl);
g_repl = NULL; g_repl = NULL;

View file

@ -27,11 +27,16 @@ static const char* pxl8_ffi_cdefs =
"u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);\n" "u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);\n"
"i32 pxl8_gfx_get_height(pxl8_gfx* ctx);\n" "i32 pxl8_gfx_get_height(pxl8_gfx* ctx);\n"
"typedef struct pxl8_palette pxl8_palette;\n" "typedef struct pxl8_palette pxl8_palette;\n"
"pxl8_palette* pxl8_gfx_get_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"
"void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b);\n" "void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b);\n"
"void pxl8_gfx_set_palette_colors(pxl8_gfx* gfx, const u32* colors, u16 count);\n"
"i32 pxl8_palette_index(const pxl8_palette* pal, u32 color);\n" "i32 pxl8_palette_index(const pxl8_palette* pal, u32 color);\n"
"u8 pxl8_palette_ramp_index(const pxl8_palette* pal, u8 position);\n" "u8 pxl8_palette_ramp_index(const pxl8_palette* pal, u8 position);\n"
"typedef struct pxl8_colormap pxl8_colormap;\n"
"pxl8_colormap* pxl8_gfx_colormap(pxl8_gfx* gfx);\n"
"void pxl8_set_colormap(pxl8_colormap* cm, const u8* data, u32 size);\n"
"void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx);\n"
"i32 pxl8_gfx_get_width(pxl8_gfx* ctx);\n" "i32 pxl8_gfx_get_width(pxl8_gfx* ctx);\n"
"void pxl8_2d_circle(pxl8_gfx* ctx, i32 x, i32 y, i32 r, u32 color);\n" "void pxl8_2d_circle(pxl8_gfx* ctx, i32 x, i32 y, i32 r, u32 color);\n"
"void pxl8_2d_circle_fill(pxl8_gfx* ctx, i32 x, i32 y, i32 r, u32 color);\n" "void pxl8_2d_circle_fill(pxl8_gfx* ctx, i32 x, i32 y, i32 r, u32 color);\n"
@ -224,6 +229,8 @@ static const char* pxl8_ffi_cdefs =
"pxl8_mat4 pxl8_3d_camera_get_view(const pxl8_3d_camera* cam);\n" "pxl8_mat4 pxl8_3d_camera_get_view(const pxl8_3d_camera* cam);\n"
"pxl8_mat4 pxl8_3d_camera_get_projection(const pxl8_3d_camera* cam);\n" "pxl8_mat4 pxl8_3d_camera_get_projection(const pxl8_3d_camera* cam);\n"
"void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt);\n" "void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt);\n"
"typedef struct pxl8_projected_point { i32 x; i32 y; f32 depth; bool visible; } pxl8_projected_point;\n"
"pxl8_projected_point pxl8_3d_camera_world_to_screen(const pxl8_3d_camera* cam, pxl8_vec3 world_pos, u32 screen_width, u32 screen_height);\n"
"\n" "\n"
"typedef enum pxl8_gfx_effect { PXL8_GFX_EFFECT_GLOWS = 0 } pxl8_gfx_effect;\n" "typedef enum pxl8_gfx_effect { PXL8_GFX_EFFECT_GLOWS = 0 } pxl8_gfx_effect;\n"
"\n" "\n"
@ -252,8 +259,9 @@ static const char* pxl8_ffi_cdefs =
"\n" "\n"
"typedef enum pxl8_blend_mode { PXL8_BLEND_OPAQUE = 0, PXL8_BLEND_ALPHA_TEST, PXL8_BLEND_ALPHA, PXL8_BLEND_ADDITIVE } pxl8_blend_mode;\n" "typedef enum pxl8_blend_mode { PXL8_BLEND_OPAQUE = 0, PXL8_BLEND_ALPHA_TEST, PXL8_BLEND_ALPHA, PXL8_BLEND_ADDITIVE } pxl8_blend_mode;\n"
"\n" "\n"
"typedef struct pxl8_material {\n" "typedef struct pxl8_gfx_material {\n"
" u32 texture_id;\n" " u32 texture_id;\n"
" u32 lightmap_id;\n"
" u8 alpha;\n" " u8 alpha;\n"
" u8 blend_mode;\n" " u8 blend_mode;\n"
" bool dither;\n" " bool dither;\n"
@ -261,13 +269,15 @@ static const char* pxl8_ffi_cdefs =
" bool dynamic_lighting;\n" " bool dynamic_lighting;\n"
" bool per_pixel;\n" " bool per_pixel;\n"
" bool vertex_color_passthrough;\n" " bool vertex_color_passthrough;\n"
" bool wireframe;\n"
" f32 emissive_intensity;\n" " f32 emissive_intensity;\n"
"} pxl8_material;\n" "} pxl8_gfx_material;\n"
"\n" "\n"
"typedef struct pxl8_vertex {\n" "typedef struct pxl8_vertex {\n"
" pxl8_vec3 position;\n" " pxl8_vec3 position;\n"
" pxl8_vec3 normal;\n" " pxl8_vec3 normal;\n"
" f32 u, v;\n" " f32 u, v;\n"
" f32 lu, lv;\n"
" u8 color;\n" " u8 color;\n"
" u8 light;\n" " u8 light;\n"
" u8 _pad[2];\n" " u8 _pad[2];\n"
@ -287,7 +297,10 @@ static const char* pxl8_ffi_cdefs =
"void pxl8_mesh_clear(pxl8_mesh* mesh);\n" "void pxl8_mesh_clear(pxl8_mesh* mesh);\n"
"u16 pxl8_mesh_push_vertex(pxl8_mesh* mesh, pxl8_vertex v);\n" "u16 pxl8_mesh_push_vertex(pxl8_mesh* mesh, pxl8_vertex v);\n"
"void pxl8_mesh_push_triangle(pxl8_mesh* mesh, u16 i0, u16 i1, u16 i2);\n" "void pxl8_mesh_push_triangle(pxl8_mesh* mesh, u16 i0, u16 i1, u16 i2);\n"
"void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_material* material);\n" "void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material);\n"
"void pxl8_3d_draw_mesh_wireframe(pxl8_gfx* gfx, const pxl8_mesh* mesh, pxl8_mat4 model, u8 color);\n"
"\n"
"u32 pxl8_hash32(u32 x);\n"
"\n" "\n"
"pxl8_mat4 pxl8_mat4_identity(void);\n" "pxl8_mat4 pxl8_mat4_identity(void);\n"
"pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up);\n" "pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up);\n"
@ -316,18 +329,76 @@ static const char* pxl8_ffi_cdefs =
" int num_rooms;\n" " int num_rooms;\n"
"} pxl8_procgen_params;\n" "} pxl8_procgen_params;\n"
"\n" "\n"
"typedef struct pxl8_procgen_tex_params {\n" "typedef enum pxl8_graph_op {\n"
" char name[16];\n" " PXL8_OP_CONST = 0,\n"
" unsigned int seed;\n" " PXL8_OP_INPUT_AGE,\n"
" int width;\n" " PXL8_OP_INPUT_SEED,\n"
" int height;\n" " PXL8_OP_INPUT_TIME,\n"
" float scale;\n" " PXL8_OP_INPUT_X,\n"
" float roughness;\n" " PXL8_OP_INPUT_Y,\n"
" unsigned char base_color;\n"
" unsigned char variation;\n"
"} pxl8_procgen_tex_params;\n"
"\n" "\n"
"void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params);\n" " PXL8_OP_ABS,\n"
" PXL8_OP_CEIL,\n"
" PXL8_OP_COS,\n"
" PXL8_OP_FLOOR,\n"
" PXL8_OP_FRACT,\n"
" PXL8_OP_NEGATE,\n"
" PXL8_OP_SIN,\n"
" PXL8_OP_SQRT,\n"
"\n"
" PXL8_OP_ADD,\n"
" PXL8_OP_DIV,\n"
" PXL8_OP_MAX,\n"
" PXL8_OP_MIN,\n"
" PXL8_OP_MOD,\n"
" PXL8_OP_MUL,\n"
" PXL8_OP_POW,\n"
" PXL8_OP_SUB,\n"
"\n"
" PXL8_OP_CLAMP,\n"
" PXL8_OP_LERP,\n"
" PXL8_OP_SELECT,\n"
" PXL8_OP_SMOOTHSTEP,\n"
"\n"
" PXL8_OP_NOISE_FBM,\n"
" PXL8_OP_NOISE_PERLIN,\n"
" PXL8_OP_NOISE_RIDGED,\n"
" PXL8_OP_NOISE_TURBULENCE,\n"
" PXL8_OP_NOISE_VALUE,\n"
"\n"
" PXL8_OP_VORONOI_CELL,\n"
" PXL8_OP_VORONOI_EDGE,\n"
" PXL8_OP_VORONOI_ID,\n"
"\n"
" PXL8_OP_GRADIENT_LINEAR,\n"
" PXL8_OP_GRADIENT_RADIAL,\n"
"\n"
" PXL8_OP_QUANTIZE,\n"
" PXL8_OP_COUNT\n"
"} pxl8_graph_op;\n"
"\n"
"typedef struct pxl8_node {\n"
" u8 in[4];\n"
" u8 op;\n"
" u8 out;\n"
" f32 param;\n"
"} pxl8_node;\n"
"\n"
"typedef struct pxl8_graph {\n"
" u32 capacity;\n"
" u32 count;\n"
" pxl8_node* nodes;\n"
" u8 output_reg;\n"
" u32 seed;\n"
"} pxl8_graph;\n"
"\n"
"pxl8_graph* pxl8_graph_create(u32 capacity);\n"
"void pxl8_graph_destroy(pxl8_graph* graph);\n"
"void pxl8_graph_clear(pxl8_graph* graph);\n"
"u8 pxl8_graph_add_node(pxl8_graph* graph, pxl8_graph_op op, u8 in0, u8 in1, u8 in2, u8 in3, f32 param);\n"
"void pxl8_graph_set_output(pxl8_graph* graph, u8 reg);\n"
"void pxl8_graph_set_seed(pxl8_graph* graph, u32 seed);\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_bsp_face pxl8_bsp_face;\n" "typedef struct pxl8_bsp_face pxl8_bsp_face;\n"
@ -350,12 +421,13 @@ static const char* pxl8_ffi_cdefs =
"bool pxl8_world_is_loaded(const pxl8_world* world);\n" "bool pxl8_world_is_loaded(const pxl8_world* world);\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" "pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, float radius);\n"
"void pxl8_world_set_wireframe(pxl8_world* world, bool enabled, u8 color);\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"
"void pxl8_gui_state_destroy(pxl8_gui_state* state);\n" "void pxl8_gui_state_destroy(pxl8_gui_state* state);\n"
"void pxl8_gui_begin_frame(pxl8_gui_state* state);\n" "void pxl8_gui_begin_frame(pxl8_gui_state* state, pxl8_gfx* gfx);\n"
"void pxl8_gui_end_frame(pxl8_gui_state* state);\n" "void pxl8_gui_end_frame(pxl8_gui_state* state, pxl8_gfx* gfx);\n"
"void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y);\n" "void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y);\n"
"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"

View file

@ -9,6 +9,11 @@
#define VOICE_SCALE 0.15f #define VOICE_SCALE 0.15f
static inline f32 sanitize_audio(f32 x) {
union { f32 f; u32 u; } conv = { .f = x };
return ((conv.u & 0x7F800000) == 0x7F800000) ? 0.0f : x;
}
typedef enum envelope_state { typedef enum envelope_state {
ENV_ATTACK = 0, ENV_ATTACK = 0,
ENV_DECAY, ENV_DECAY,
@ -716,8 +721,8 @@ void pxl8_sfx_mixer_process(pxl8_sfx_mixer* mixer) {
left = fmaxf(-1.0f, fminf(1.0f, left)); left = fmaxf(-1.0f, fminf(1.0f, left));
right = fmaxf(-1.0f, fminf(1.0f, right)); right = fmaxf(-1.0f, fminf(1.0f, right));
if (!isfinite(left)) left = 0.0f; left = sanitize_audio(left);
if (!isfinite(right)) right = 0.0f; right = sanitize_audio(right);
mixer->output_buffer[i * 2] = left; mixer->output_buffer[i * 2] = left;
mixer->output_buffer[i * 2 + 1] = right; mixer->output_buffer[i * 2 + 1] = right;

View file

@ -345,6 +345,7 @@ void pxl8_bsp_destroy(pxl8_bsp* bsp) {
free(bsp->planes); free(bsp->planes);
free(bsp->surfedges); free(bsp->surfedges);
free(bsp->texinfo); free(bsp->texinfo);
free(bsp->vertex_lights);
free(bsp->vertices); free(bsp->vertices);
free(bsp->visdata); free(bsp->visdata);
@ -377,26 +378,24 @@ bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to) {
u32 target_byte = leaf_to >> 3; u32 target_byte = leaf_to >> 3;
u32 target_bit = leaf_to & 7; u32 target_bit = leaf_to & 7;
u32 pvs_size = (bsp->num_leafs + 7) / 8;
u32 pos = (u32)visofs; u32 pos = (u32)visofs;
u32 current_byte = 0; u32 out_pos = 0;
while (current_byte < pvs_size && pos < bsp->visdata_size) { while (out_pos <= target_byte && pos < bsp->visdata_size) {
u8 b = bsp->visdata[pos++]; u8 b = bsp->visdata[pos++];
if (b != 0) { if (b != 0) {
if (current_byte == target_byte) { if (out_pos == target_byte) {
return (b & (1 << target_bit)) != 0; return (b & (1 << target_bit)) != 0;
} }
current_byte++; out_pos++;
} else { } else {
if (pos >= bsp->visdata_size) return false; if (pos >= bsp->visdata_size) break;
u32 count = bsp->visdata[pos++]; u32 count = bsp->visdata[pos++];
if (target_byte < current_byte + count) { if (out_pos + count > target_byte) {
return false; return false;
} }
current_byte += count; out_pos += count;
} }
} }
@ -546,6 +545,12 @@ static inline bool face_in_frustum(const pxl8_bsp* bsp, u32 face_id, const pxl8_
return pxl8_frustum_test_aabb(frustum, face->aabb_min, face->aabb_max); return pxl8_frustum_test_aabb(frustum, face->aabb_min, face->aabb_max);
} }
static inline bool leaf_in_frustum(const pxl8_bsp_leaf* leaf, const pxl8_frustum* frustum) {
pxl8_vec3 mins = {(f32)leaf->mins[0], (f32)leaf->mins[1], (f32)leaf->mins[2]};
pxl8_vec3 maxs = {(f32)leaf->maxs[0], (f32)leaf->maxs[1], (f32)leaf->maxs[2]};
return pxl8_frustum_test_aabb(frustum, mins, maxs);
}
static void collect_face_to_mesh( static void collect_face_to_mesh(
const pxl8_bsp* bsp, const pxl8_bsp* bsp,
u32 face_id, u32 face_id,
@ -623,7 +628,12 @@ void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 t
if (mesh->index_count > 0) { if (mesh->index_count > 0) {
pxl8_mat4 identity = pxl8_mat4_identity(); pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_material mat = pxl8_material_create(texture_id); pxl8_gfx_material mat = {
.texture_id = texture_id,
.alpha = 255,
.dither = true,
.dynamic_lighting = true,
};
pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat); pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat);
} }
@ -650,6 +660,32 @@ void pxl8_bsp_render_textured(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 came
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos); i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
static u32 debug_counter = 0;
bool do_debug = (debug_counter++ % 300 == 0);
u32 visible_leafs = 0;
u32 visible_faces = 0;
u32 total_faces_in_visible_leafs = 0;
static bool dumped_camera_leaf = false;
if (do_debug) {
bool self_visible = (camera_leaf < 0) || pxl8_bsp_is_leaf_visible(bsp, camera_leaf, camera_leaf);
if (!self_visible) {
pxl8_debug("WARNING: Camera leaf %d is NOT visible from itself!", camera_leaf);
}
if (!dumped_camera_leaf && camera_leaf >= 0) {
const pxl8_bsp_leaf* cl = &bsp->leafs[camera_leaf];
pxl8_debug("Camera leaf %d: contents=%d, marksurfaces=%u-%u (%u faces)",
camera_leaf, cl->contents, cl->first_marksurface,
cl->first_marksurface + cl->num_marksurfaces, cl->num_marksurfaces);
dumped_camera_leaf = true;
}
for (u32 i = 0; i < bsp->num_leafs; i++) {
if (camera_leaf < 0 || pxl8_bsp_is_leaf_visible(bsp, camera_leaf, i)) {
visible_leafs++;
total_faces_in_visible_leafs += bsp->leafs[i].num_marksurfaces;
}
}
}
static u8* rendered_faces = NULL; static u8* rendered_faces = NULL;
static u32 rendered_faces_capacity = 0; static u32 rendered_faces_capacity = 0;
@ -672,6 +708,20 @@ void pxl8_bsp_render_textured(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 came
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id]; const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id];
bool is_camera_leaf = ((i32)leaf_id == camera_leaf);
f32 cx = ((f32)leaf->mins[0] + (f32)leaf->maxs[0]) * 0.5f;
f32 cy = ((f32)leaf->mins[1] + (f32)leaf->maxs[1]) * 0.5f;
f32 cz = ((f32)leaf->mins[2] + (f32)leaf->maxs[2]) * 0.5f;
f32 dx = camera_pos.x - cx;
f32 dy = camera_pos.y - cy;
f32 dz = camera_pos.z - cz;
f32 dist_sq = dx*dx + dy*dy + dz*dz;
bool camera_near_leaf = dist_sq < (512.0f * 512.0f);
bool skip_frustum = is_camera_leaf || camera_near_leaf;
if (!skip_frustum && !leaf_in_frustum(leaf, frustum)) continue;
for (u32 i = 0; i < leaf->num_marksurfaces; i++) { for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
u32 surf_idx = leaf->first_marksurface + i; u32 surf_idx = leaf->first_marksurface + i;
if (surf_idx >= bsp->num_marksurfaces) continue; if (surf_idx >= bsp->num_marksurfaces) continue;
@ -681,8 +731,9 @@ void pxl8_bsp_render_textured(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 came
if (rendered_faces[face_id]) continue; if (rendered_faces[face_id]) continue;
rendered_faces[face_id] = 1; rendered_faces[face_id] = 1;
if (do_debug) visible_faces++;
if (!face_in_frustum(bsp, face_id, frustum)) { if (!skip_frustum && !face_in_frustum(bsp, face_id, frustum)) {
continue; continue;
} }
@ -694,7 +745,12 @@ void pxl8_bsp_render_textured(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 came
if (texture_id != current_texture && mesh->index_count > 0) { if (texture_id != current_texture && mesh->index_count > 0) {
pxl8_mat4 identity = pxl8_mat4_identity(); pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_material mat = pxl8_material_with_lighting(pxl8_material_with_double_sided(pxl8_material_create(current_texture))); pxl8_gfx_material mat = {
.texture_id = current_texture,
.alpha = 255,
.dither = true,
.dynamic_lighting = true,
};
pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat); pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat);
pxl8_mesh_clear(mesh); pxl8_mesh_clear(mesh);
} }
@ -706,10 +762,22 @@ void pxl8_bsp_render_textured(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 came
if (mesh->index_count > 0) { if (mesh->index_count > 0) {
pxl8_mat4 identity = pxl8_mat4_identity(); pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_material mat = pxl8_material_with_lighting(pxl8_material_with_double_sided(pxl8_material_create(current_texture))); pxl8_gfx_material mat = {
.texture_id = current_texture,
.alpha = 255,
.dither = true,
.dynamic_lighting = true,
};
pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat); pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat);
} }
if (do_debug) {
pxl8_debug("Camera at (%.1f, %.1f, %.1f) -> leaf %d, %u/%u leafs, %u/%u faces (expected %u)",
camera_pos.x, camera_pos.y, camera_pos.z,
camera_leaf, visible_leafs, bsp->num_leafs,
visible_faces, bsp->num_faces, total_faces_in_visible_leafs);
}
pxl8_mesh_destroy(mesh); pxl8_mesh_destroy(mesh);
} }

View file

@ -1,17 +1,51 @@
#include "pxl8_gen.h" #include "pxl8_gen.h"
#include <math.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_rng.h" #include "pxl8_rng.h"
#define CELL_SIZE 64.0f
#define PVS_MAX_DEPTH 40
#define WALL_HEIGHT 128.0f
typedef struct room_grid { typedef struct room_grid {
u8* cells; u8* cells;
i32 width; i32 width;
i32 height; i32 height;
} room_grid; } 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 portal {
f32 x0, z0;
f32 x1, z1;
u32 target_leaf;
} portal;
typedef struct cell_portals {
portal portals[4];
u8 num_portals;
} cell_portals;
typedef struct vis_window {
f32 left_x, left_z;
f32 right_x, right_z;
} vis_window;
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) { static bool room_grid_init(room_grid* grid, i32 width, i32 height) {
grid->width = width; grid->width = width;
grid->height = height; grid->height = height;
@ -57,6 +91,444 @@ static void room_grid_fill(room_grid* grid, u8 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 = 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 cell_portals* build_cell_portals(const room_grid* grid, f32 cell_size) {
i32 total_cells = grid->width * grid->height;
cell_portals* portals = calloc(total_cells, sizeof(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) {
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) {
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) {
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) {
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;
}
static inline f32 cross_2d(f32 ax, f32 az, f32 bx, f32 bz) {
return ax * bz - az * bx;
}
static bool clip_window_through_portal(
f32 src_x, f32 src_z,
const vis_window* window,
const portal* p,
vis_window* clipped
) {
f32 wl_x = window->left_x - src_x;
f32 wl_z = window->left_z - src_z;
f32 wr_x = window->right_x - src_x;
f32 wr_z = window->right_z - src_z;
f32 p0_x = p->x0 - src_x;
f32 p0_z = p->z0 - src_z;
f32 p1_x = p->x1 - src_x;
f32 p1_z = p->z1 - src_z;
f32 p0_vs_wl = cross_2d(p0_x, p0_z, wl_x, wl_z);
f32 p1_vs_wl = cross_2d(p1_x, p1_z, wl_x, wl_z);
f32 p0_vs_wr = cross_2d(p0_x, p0_z, wr_x, wr_z);
f32 p1_vs_wr = cross_2d(p1_x, p1_z, wr_x, wr_z);
if (p1_vs_wl <= 0.0f || p0_vs_wr >= 0.0f) {
return false;
}
if (p0_vs_wl > 0.0f) {
clipped->left_x = p->x0;
clipped->left_z = p->z0;
} else {
clipped->left_x = window->left_x;
clipped->left_z = window->left_z;
}
if (p1_vs_wr < 0.0f) {
clipped->right_x = p->x1;
clipped->right_z = p->z1;
} else {
clipped->right_x = window->right_x;
clipped->right_z = window->right_z;
}
f32 cl_x = clipped->left_x - src_x;
f32 cl_z = clipped->left_z - src_z;
f32 cr_x = clipped->right_x - src_x;
f32 cr_z = clipped->right_z - src_z;
if (cross_2d(cl_x, cl_z, cr_x, cr_z) >= -0.0001f) {
return false;
}
return true;
}
typedef struct portal_vis_context {
u8* pvs;
u8* visited;
const cell_portals* portals;
const pxl8_bsp_leaf* leafs;
f32 src_x;
f32 src_z;
u32 num_leafs;
u32 max_depth;
} portal_vis_context;
static void portal_flood_recursive(
portal_vis_context* ctx,
u32 current_leaf,
const vis_window* window,
u32 depth
) {
if (depth > ctx->max_depth) return;
u32 byte = current_leaf >> 3;
u32 bit = current_leaf & 7;
if (ctx->visited[byte] & (1 << bit)) return;
ctx->visited[byte] |= (1 << bit);
if (ctx->leafs[current_leaf].contents == -1) return;
ctx->pvs[byte] |= (1 << bit);
const cell_portals* cp = &ctx->portals[current_leaf];
for (u8 i = 0; i < cp->num_portals; i++) {
const portal* p = &cp->portals[i];
u32 target_byte = p->target_leaf >> 3;
u32 target_bit = p->target_leaf & 7;
if (ctx->visited[target_byte] & (1 << target_bit)) continue;
vis_window clipped;
if (clip_window_through_portal(ctx->src_x, ctx->src_z, window, p, &clipped)) {
portal_flood_recursive(ctx, p->target_leaf, &clipped, depth + 1);
}
}
}
typedef struct vis_entry {
u32 leaf;
vis_window window;
} vis_entry;
static void portal_flood_bfs(
u32 start_leaf,
const cell_portals* portals,
const pxl8_bsp_leaf* leafs,
u8* pvs,
u32 num_leafs,
f32 cell_size,
i32 grid_width
) {
u32 pvs_bytes = (num_leafs + 7) / 8;
u8* visited = calloc(pvs_bytes, 1);
vis_entry* queue = malloc(num_leafs * 4 * sizeof(vis_entry));
if (!visited || !queue) {
free(visited);
free(queue);
return;
}
i32 start_x = start_leaf % grid_width;
i32 start_y = start_leaf / grid_width;
f32 src_x = (start_x + 0.5f) * cell_size;
f32 src_z = (start_y + 0.5f) * cell_size;
u32 head = 0, tail = 0;
pvs[start_leaf >> 3] |= (1 << (start_leaf & 7));
visited[start_leaf >> 3] |= (1 << (start_leaf & 7));
const cell_portals* start_cp = &portals[start_leaf];
for (u8 i = 0; i < start_cp->num_portals; i++) {
const portal* p = &start_cp->portals[i];
vis_window initial = {p->x0, p->z0, p->x1, p->z1};
queue[tail++] = (vis_entry){p->target_leaf, initial};
}
while (head < tail) {
vis_entry e = queue[head++];
if (leafs[e.leaf].contents == -1) continue;
u32 byte = e.leaf >> 3;
u32 bit = e.leaf & 7;
pvs[byte] |= (1 << bit);
if (visited[byte] & (1 << bit)) continue;
visited[byte] |= (1 << bit);
const cell_portals* cp = &portals[e.leaf];
for (u8 i = 0; i < cp->num_portals; i++) {
const portal* p = &cp->portals[i];
vis_window clipped;
if (clip_window_through_portal(src_x, src_z, &e.window, p, &clipped)) {
queue[tail++] = (vis_entry){p->target_leaf, clipped};
}
}
}
free(visited);
free(queue);
}
static u8* compute_leaf_pvs(u32 start_leaf, const 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 = 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 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 = malloc(max_visdata);
if (!visdata) return PXL8_ERROR_OUT_OF_MEMORY;
u32 visdata_pos = 0;
u8* compressed = malloc(pvs_bytes * 2);
if (!compressed) {
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) {
free(compressed);
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;
free(pvs);
}
free(compressed);
bsp->visdata = 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) { static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
i32 vertex_count = 0; i32 vertex_count = 0;
i32 face_count = 0; i32 face_count = 0;
@ -74,19 +546,28 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
} }
} }
face_count += floor_ceiling_count * 2; face_count += floor_ceiling_count;
vertex_count = face_count * 4; vertex_count = face_count * 4;
pxl8_debug("Cave generation: %dx%d grid -> %d faces, %d vertices", pxl8_debug("Level generation: %dx%d grid -> %d faces, %d vertices",
grid->width, grid->height, face_count, vertex_count); 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 = calloc(vertex_count, sizeof(pxl8_bsp_vertex)); bsp->vertices = calloc(vertex_count, sizeof(pxl8_bsp_vertex));
bsp->faces = calloc(face_count, sizeof(pxl8_bsp_face)); bsp->faces = calloc(face_count, sizeof(pxl8_bsp_face));
bsp->planes = calloc(face_count, sizeof(pxl8_bsp_plane)); bsp->planes = calloc(total_planes, sizeof(pxl8_bsp_plane));
bsp->edges = calloc(vertex_count, sizeof(pxl8_bsp_edge)); bsp->edges = calloc(vertex_count, sizeof(pxl8_bsp_edge));
bsp->surfedges = calloc(vertex_count, sizeof(i32)); bsp->surfedges = calloc(vertex_count, sizeof(i32));
bsp->nodes = calloc(max_nodes, sizeof(pxl8_bsp_node));
if (!bsp->vertices || !bsp->faces || !bsp->planes || !bsp->edges || !bsp->surfedges) { u32* face_cell = calloc(face_count, sizeof(u32));
if (!bsp->vertices || !bsp->faces || !bsp->planes || !bsp->edges ||
!bsp->surfedges || !bsp->nodes || !face_cell) {
free(face_cell);
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }
@ -97,14 +578,15 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
i32 face_idx = 0; i32 face_idx = 0;
i32 edge_idx = 0; i32 edge_idx = 0;
const f32 cell_size = 64.0f; const f32 cell_size = CELL_SIZE;
const f32 wall_height = 128.0f; const f32 wall_height = WALL_HEIGHT;
for (i32 y = 0; y < grid->height; y++) { for (i32 y = 0; y < grid->height; y++) {
for (i32 x = 0; x < grid->width; x++) { for (i32 x = 0; x < grid->width; x++) {
if (room_grid_get(grid, x, y) == 0) { if (room_grid_get(grid, x, y) == 0) {
f32 fx = (f32)x * cell_size; f32 fx = (f32)x * cell_size;
f32 fy = (f32)y * cell_size; f32 fy = (f32)y * cell_size;
i32 cell_idx = y * grid->width + x;
if (room_grid_get(grid, x - 1, y) == 1) { if (room_grid_get(grid, x - 1, y) == 1) {
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy}; bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy};
@ -112,8 +594,8 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx, wall_height, fy + cell_size}; 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->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].normal = (pxl8_vec3){1, 0, 0};
bsp->planes[face_idx].dist = -fx; bsp->planes[face_idx].dist = fx;
bsp->faces[face_idx].plane_id = face_idx; bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4; bsp->faces[face_idx].num_edges = 4;
@ -127,6 +609,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
} }
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx); compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
face_cell[face_idx] = cell_idx;
vert_idx += 4; vert_idx += 4;
edge_idx += 4; edge_idx += 4;
@ -139,8 +622,8 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, 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, wall_height, fy}; 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].normal = (pxl8_vec3){-1, 0, 0};
bsp->planes[face_idx].dist = fx + cell_size; bsp->planes[face_idx].dist = -(fx + cell_size);
bsp->faces[face_idx].plane_id = face_idx; bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4; bsp->faces[face_idx].num_edges = 4;
@ -154,6 +637,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
} }
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx); compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
face_cell[face_idx] = cell_idx;
vert_idx += 4; vert_idx += 4;
edge_idx += 4; edge_idx += 4;
@ -166,8 +650,8 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, 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->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].normal = (pxl8_vec3){0, 0, 1};
bsp->planes[face_idx].dist = -fy; bsp->planes[face_idx].dist = fy;
bsp->faces[face_idx].plane_id = face_idx; bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4; bsp->faces[face_idx].num_edges = 4;
@ -181,6 +665,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
} }
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx); compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
face_cell[face_idx] = cell_idx;
vert_idx += 4; vert_idx += 4;
edge_idx += 4; edge_idx += 4;
@ -193,8 +678,8 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, 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->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].normal = (pxl8_vec3){0, 0, -1};
bsp->planes[face_idx].dist = fy + cell_size; bsp->planes[face_idx].dist = -(fy + cell_size);
bsp->faces[face_idx].plane_id = face_idx; bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4; bsp->faces[face_idx].num_edges = 4;
@ -208,6 +693,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
} }
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx); compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
face_cell[face_idx] = cell_idx;
vert_idx += 4; vert_idx += 4;
edge_idx += 4; edge_idx += 4;
@ -222,6 +708,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
if (room_grid_get(grid, x, y) == 0) { if (room_grid_get(grid, x, y) == 0) {
f32 fx = (f32)x * cell_size; f32 fx = (f32)x * cell_size;
f32 fy = (f32)y * 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 + 0].position = (pxl8_vec3){fx, 0, fy};
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx, 0, fy + cell_size}; bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx, 0, fy + cell_size};
@ -243,31 +730,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
} }
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx); 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->vertices[vert_idx + 0].position = (pxl8_vec3){fx, wall_height, fy};
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx + cell_size, wall_height, fy};
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, wall_height, fy + cell_size};
bsp->planes[face_idx].normal = (pxl8_vec3){0, -1, 0};
bsp->planes[face_idx].dist = -wall_height;
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].texinfo_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);
vert_idx += 4; vert_idx += 4;
edge_idx += 4; edge_idx += 4;
@ -278,26 +741,143 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
bsp->num_vertices = vertex_count; bsp->num_vertices = vertex_count;
bsp->num_faces = face_count; bsp->num_faces = face_count;
bsp->num_planes = face_count;
bsp->num_edges = vertex_count; bsp->num_edges = vertex_count;
bsp->num_surfedges = vertex_count; bsp->num_surfedges = vertex_count;
bsp->leafs = calloc(1, sizeof(pxl8_bsp_leaf)); bsp->leafs = calloc(total_cells, sizeof(pxl8_bsp_leaf));
bsp->marksurfaces = calloc(face_count, sizeof(u16)); bsp->marksurfaces = calloc(face_count, sizeof(u16));
if (!bsp->leafs || !bsp->marksurfaces) { if (!bsp->leafs || !bsp->marksurfaces) {
free(face_cell);
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }
bsp->num_leafs = 1; bsp->num_leafs = total_cells;
bsp->num_marksurfaces = face_count; bsp->num_marksurfaces = face_count;
bsp->leafs[0].first_marksurface = 0; u32* faces_per_cell = calloc(total_cells, sizeof(u32));
bsp->leafs[0].num_marksurfaces = face_count; u32* cell_offset = calloc(total_cells, sizeof(u32));
bsp->leafs[0].contents = -2; u32* cell_cursor = calloc(total_cells, sizeof(u32));
if (!faces_per_cell || !cell_offset || !cell_cursor) {
free(faces_per_cell);
free(cell_offset);
free(cell_cursor);
free(face_cell);
return PXL8_ERROR_OUT_OF_MEMORY;
}
for (i32 i = 0; i < face_count; i++) { for (i32 i = 0; i < face_count; i++) {
bsp->marksurfaces[i] = 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;
}
}
free(faces_per_cell);
free(cell_offset);
free(cell_cursor);
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);
cell_portals* portals = build_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 = calloc(pvs_bytes, 1);
u8* queue = 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);
free(visited);
free(queue);
}
pxl8_result pvs_result = build_pvs_data(bsp, portals, grid, cell_size);
free(portals);
if (pvs_result != PXL8_OK) {
return pvs_result;
} }
return PXL8_OK; return PXL8_OK;
@ -348,6 +928,9 @@ static pxl8_result procgen_rooms(pxl8_bsp* bsp, const pxl8_procgen_params* param
i32 room_count = 0; i32 room_count = 0;
i32 max_attempts = params->num_rooms * 10; 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++) { 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 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 h = params->min_room_size + (pxl8_rng_next(&rng) % (params->max_room_size - params->min_room_size + 1));
@ -396,6 +979,26 @@ static pxl8_result procgen_rooms(pxl8_bsp* bsp, const pxl8_procgen_params* param
pxl8_result result = grid_to_bsp(bsp, &grid); pxl8_result result = grid_to_bsp(bsp, &grid);
free(grid.cells); 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; return result;
} }
@ -417,88 +1020,3 @@ pxl8_result pxl8_procgen(pxl8_bsp* bsp, const pxl8_procgen_params* params) {
return PXL8_ERROR_INVALID_ARGUMENT; return PXL8_ERROR_INVALID_ARGUMENT;
} }
} }
static u32 hash2d(i32 x, i32 y) {
u32 h = ((u32)x * 374761393u) + ((u32)y * 668265263u);
h ^= h >> 13;
h ^= h << 17;
h ^= h >> 5;
return h;
}
void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params) {
if (!buffer || !params) return;
for (i32 y = 0; y < params->height; y++) {
for (i32 x = 0; x < params->width; x++) {
f32 u = (f32)x / (f32)params->width;
f32 v = (f32)y / (f32)params->height;
u8 color = params->base_color;
// Tile-based pattern (floor style)
if (params->seed == 11111) {
i32 tile_x = (i32)floorf(u * 8.0f);
i32 tile_y = (i32)floorf(v * 8.0f);
u32 h = hash2d(tile_x, tile_y);
f32 pattern = (f32)(h & 0xFF) / 255.0f;
i32 quantized = (pattern < 0.3f) ? 0 : (pattern < 0.7f) ? 1 : 2;
color = params->base_color + quantized;
// Checkerboard dither
if (((tile_x + tile_y) & 1) == 0 && (h & 0x100)) {
color = (color < 255) ? color + 1 : color;
}
}
// Large tile pattern (ceiling style)
else if (params->seed == 22222) {
i32 coarse_x = (i32)floorf(u * 2.0f);
i32 coarse_y = (i32)floorf(v * 2.0f);
u32 coarse_h = hash2d(coarse_x, coarse_y);
i32 subdivision = (coarse_h >> 8) & 0x3;
i32 tile_x, tile_y;
switch (subdivision) {
case 0: tile_x = (i32)floorf(u * 3.0f); tile_y = (i32)floorf(v * 3.0f); break;
case 1: tile_x = (i32)floorf(u * 5.0f); tile_y = (i32)floorf(v * 5.0f); break;
case 2: tile_x = (i32)floorf(u * 2.0f); tile_y = (i32)floorf(v * 4.0f); break;
default: tile_x = (i32)floorf(u * 4.0f); tile_y = (i32)floorf(v * 2.0f); break;
}
u32 h = hash2d(tile_x, tile_y);
f32 pattern = (f32)(h & 0xFF) / 255.0f;
if (pattern < 0.25f) color = params->base_color;
else if (pattern < 0.50f) color = params->base_color + 1;
else if (pattern < 0.75f) color = params->base_color + 2;
else color = params->base_color + 3;
}
// Brick pattern (wall style)
else {
f32 brick_y = floorf(v * 4.0f);
f32 offset = ((i32)brick_y & 1) ? 0.5f : 0.0f;
i32 brick_x = (i32)floorf(u * 4.0f + offset);
brick_y = (i32)brick_y;
f32 brick_u = fabsf((u * 4.0f + offset) - floorf(u * 4.0f + offset) - 0.5f);
f32 brick_v = fabsf((v * 4.0f) - floorf(v * 4.0f) - 0.5f);
u32 h = hash2d(brick_x, (i32)brick_y);
f32 noise = (f32)(h & 0xFF) / 255.0f;
// Mortar lines
if (brick_u > 0.47f || brick_v > 0.47f) {
color = params->base_color - 2;
} else {
i32 shade = (i32)(noise * 3.0f);
color = params->base_color + shade;
}
}
buffer[y * params->width + x] = color;
}
}
}

View file

@ -21,24 +21,11 @@ typedef struct pxl8_procgen_params {
i32 num_rooms; i32 num_rooms;
} pxl8_procgen_params; } pxl8_procgen_params;
typedef struct pxl8_procgen_tex_params {
char name[16];
u32 seed;
i32 width;
i32 height;
f32 scale;
f32 roughness;
u8 base_color;
u8 variation;
u8 max_color;
} pxl8_procgen_tex_params;
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
pxl8_result pxl8_procgen(pxl8_bsp* bsp, const pxl8_procgen_params* params); pxl8_result pxl8_procgen(pxl8_bsp* bsp, const pxl8_procgen_params* params);
void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params);
#ifdef __cplusplus #ifdef __cplusplus
} }

View file

@ -404,3 +404,9 @@ void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
pxl8_bsp_render_textured(gfx, &world->bsp, camera_pos); pxl8_bsp_render_textured(gfx, &world->bsp, camera_pos);
} }
} }
void pxl8_world_set_wireframe(pxl8_world* world, bool enabled, u8 color) {
if (!world) return;
world->wireframe = enabled;
world->wireframe_color = color;
}

View file

@ -32,6 +32,7 @@ bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, f32 radi
bool pxl8_world_is_loaded(const pxl8_world* world); bool pxl8_world_is_loaded(const pxl8_world* world);
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);
pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius); pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius);
void pxl8_world_set_wireframe(pxl8_world* world, bool enabled, u8 color);
#ifdef __cplusplus #ifdef __cplusplus
} }