add networking, 3d improvements, reorganize src structure

This commit is contained in:
asrael 2026-01-17 22:52:36 -06:00
parent 39b604b333
commit 415d424057
122 changed files with 5358 additions and 721 deletions

View file

@ -1,7 +1,7 @@
(local pxl8 (require :pxl8))
(local menu (require :mod.menu))
(local music (require :mod.music))
(local worldgen (require :mod.worldgen))
(local first_person3d (require :mod.first_person3d))
(var time 0)
(var active-demo :logo)
@ -34,13 +34,12 @@
(set particles (pxl8.create_particles 1000))
(set particles2 (pxl8.create_particles 500))
(music.init)
(music.start)
(worldgen.init)))
(first_person3d.init)))
(global update (fn [dt]
(when (pxl8.key_pressed "escape")
(menu.toggle)
(when (= active-demo :worldgen)
(when (= active-demo :first_person3d)
(pxl8.set_relative_mouse_mode (not (menu.is-paused)))))
(when (not (menu.is-paused))
@ -50,9 +49,9 @@
(transition:update dt)
(when (transition:is_complete)
(when transition-pending
(when (and (= active-demo :worldgen) (not= transition-pending :worldgen))
(when (and (= active-demo :first_person3d) (not= transition-pending :first_person3d))
(pxl8.set_relative_mouse_mode false))
(when (and (not= active-demo :worldgen) (= transition-pending :worldgen))
(when (and (not= active-demo :first_person3d) (= transition-pending :first_person3d))
(pxl8.set_relative_mouse_mode true))
(set active-demo transition-pending)
(set transition-pending nil)
@ -69,12 +68,14 @@
(when (pxl8.key_pressed "5") (switch-demo :fire))
(when (pxl8.key_pressed "6") (switch-demo :rain))
(when (pxl8.key_pressed "7") (switch-demo :snow))
(when (pxl8.key_pressed "8") (switch-demo :worldgen))
(when (pxl8.key_pressed "8") (switch-demo :first_person3d))
(when (pxl8.key_pressed "=")
(set use-famicube-palette? (not use-famicube-palette?))
(local palette-path (if use-famicube-palette? "res/palettes/famicube.ase" "res/sprites/pxl8_logo.ase"))
(pxl8.load_palette palette-path))
(music.update dt)
(case active-demo
:logo (do
(set logo-x (+ logo-x (* logo-dx dt)))
@ -91,9 +92,7 @@
(when (> logo-y 296)
(set logo-y 296)
(set logo-dy (- (math.abs logo-dy)))))
:worldgen (worldgen.update dt))
(music.update dt)
:first_person3d (first_person3d.update dt))
(when particles
(particles:update dt))
@ -173,7 +172,7 @@
(set snow-init? true))
(particles:render)))
:worldgen (worldgen.frame)
:first_person3d (first_person3d.frame)
_ (pxl8.clear 0))

393
demo/mod/first_person3d.fnl Normal file
View file

@ -0,0 +1,393 @@
(local pxl8 (require :pxl8))
(local effects (require :pxl8.effects))
(local net (require :pxl8.net))
(local sky (require :mod.sky))
(local bob-amount 4.0)
(local bob-speed 8.0)
(local cam-smoothing 0.25)
(local cell-size 64)
(local cursor-sensitivity 0.008)
(local gravity -800)
(local grid-size 64)
(local ground-y 64)
(local jump-force 175)
(local land-recovery-speed 20)
(local land-squash-amount -4)
(local max-pitch 1.5)
(local move-speed 200)
(local turn-speed 2.0)
(local sim-tick-rate 60)
(local sim-dt (/ 1.0 sim-tick-rate))
(local history-size 128)
(local correction-threshold 1.0)
(var auto-run? false)
(var auto-run-cancel-key nil)
(var bob-time 0)
(var cam-pitch 0)
(var cam-x 1000)
(var cam-y 64)
(var cam-yaw 0)
(var cam-z 1000)
(var camera nil)
(var cursor-look? true)
(var grounded? true)
(var land-squash 0)
(var light-time 0)
(var network nil)
(var smooth-cam-x 1000)
(var smooth-cam-z 1000)
(var velocity-y 0)
(var world nil)
(var fps-avg 0)
(var fps-sample-count 0)
(local FIREBALL_COLOR 184)
(fn init-fireball-palette []
(for [i 0 7]
(let [t (/ i 7)
r (math.floor (+ 0xFF (* t 0)))
g (math.floor (+ 0x60 (* t (- 0xE0 0x60))))
b (math.floor (+ 0x10 (* t (- 0x80 0x10))))]
(pxl8.set_palette_rgb (+ FIREBALL_COLOR i) r g b))))
(var client-tick 0)
(var last-processed-tick 0)
(var time-accumulator 0)
(var position-history {})
(var pending-inputs {})
(fn history-idx [tick]
(+ 1 (% tick history-size)))
(fn store-position [tick x z yaw]
(tset position-history (history-idx tick) {:tick tick :x x :z z :yaw yaw}))
(fn get-position [tick]
(let [entry (. position-history (history-idx tick))]
(when (and entry (= entry.tick tick))
entry)))
(fn store-pending-input [tick input]
(tset pending-inputs (history-idx tick) {:tick tick :input input}))
(fn get-pending-input [tick]
(let [entry (. pending-inputs (history-idx tick))]
(when (and entry (= entry.tick tick))
entry.input)))
(fn apply-movement [x z yaw input]
(var new-x x)
(var new-z z)
(let [move-forward (or input.move_y 0)
move-right (or input.move_x 0)]
(when (or (not= move-forward 0) (not= move-right 0))
(let [forward-x (- (math.sin yaw))
forward-z (- (math.cos yaw))
right-x (math.cos yaw)
right-z (- (math.sin yaw))
len (math.sqrt (+ (* move-forward move-forward) (* move-right move-right)))
norm-forward (/ move-forward len)
norm-right (/ move-right len)
move-delta (* move-speed sim-dt)]
(set new-x (+ new-x (* move-delta (+ (* forward-x norm-forward) (* right-x norm-right)))))
(set new-z (+ new-z (* move-delta (+ (* forward-z norm-forward) (* right-z norm-right))))))))
(values new-x new-z))
(fn init []
(set camera (pxl8.create_camera_3d))
(set world (pxl8.create_world))
(sky.generate-stars 12345)
(init-fireball-palette)
(set network (net.Net.new {:port 7777}))
(when network
(network:connect)
(network:spawn cam-x cam-y cam-z cam-yaw cam-pitch))
(let [result (world:generate {
:type pxl8.PROCGEN_ROOMS
:width 64
:height 64
:seed 42
:min_room_size 5
:max_room_size 10
:num_rooms 20})]
(if (< result 0)
(pxl8.error (.. "Failed to generate rooms - result: " result))
(let [floor-tex (pxl8.procgen_tex {:name "floor"
:seed 11111
: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)]
(let [result (world:apply_textures [
{:name "floor"
:texture_id floor-tex
:rule (fn [normal] (> normal.y 0.7))}
{:name "ceiling"
:texture_id sky-tex
:rule (fn [normal] (< normal.y -0.7))}
{:name "wall"
:texture_id wall-tex
:rule (fn [normal] (and (<= normal.y 0.7) (>= normal.y -0.7)))}])]
(when (< result 0)
(pxl8.error (.. "Failed to apply textures - result: " result))))))))
(fn sample-input []
(var move-forward 0)
(var move-right 0)
(when (pxl8.key_pressed "`")
(set auto-run? (not auto-run?))
(when (and auto-run? (pxl8.key_down "w"))
(set auto-run-cancel-key "w")))
(when (and auto-run? (not auto-run-cancel-key) (or (pxl8.key_down "w") (pxl8.key_down "s")))
(set auto-run? false)
(when (pxl8.key_down "s")
(set auto-run-cancel-key "s")))
(when (and auto-run-cancel-key (not (pxl8.key_down auto-run-cancel-key)))
(set auto-run-cancel-key nil))
(when (or (pxl8.key_down "w") auto-run?)
(set move-forward (+ move-forward 1)))
(when (and (pxl8.key_down "s") (not= auto-run-cancel-key "s"))
(set move-forward (- move-forward 1)))
(when (pxl8.key_down "a")
(set move-right (- move-right 1)))
(when (pxl8.key_down "d")
(set move-right (+ move-right 1)))
{:move_x move-right
:move_y move-forward
:look_dx (pxl8.mouse_dx)
:look_dy (pxl8.mouse_dy)})
(fn reconcile [server-tick server-x server-z]
(let [predicted (get-position server-tick)]
(when predicted
(let [dx (- predicted.x server-x)
dz (- predicted.z server-z)
error (math.sqrt (+ (* dx dx) (* dz dz)))]
(when (> error correction-threshold)
(set cam-x server-x)
(set cam-z server-z)
(for [t (+ server-tick 1) client-tick]
(let [input (get-pending-input t)
hist (get-position t)]
(when (and input hist)
(let [(new-x new-z) (apply-movement cam-x cam-z hist.yaw input)]
(set cam-x new-x)
(set cam-z new-z)
(store-position t cam-x cam-z hist.yaw))))))))))
(fn update [dt]
(let [fps (pxl8.get_fps)]
(set fps-sample-count (+ fps-sample-count 1))
(set fps-avg (+ (* fps-avg (/ (- fps-sample-count 1) fps-sample-count))
(/ fps fps-sample-count)))
(when (>= fps-sample-count 120)
(set fps-sample-count 0)
(set fps-avg 0)))
(when (world:is_loaded)
(let [input (sample-input)
grid-max (* grid-size cell-size)
movement-yaw cam-yaw]
(set time-accumulator (+ time-accumulator dt))
(while (>= time-accumulator sim-dt)
(set time-accumulator (- time-accumulator sim-dt))
(set client-tick (+ client-tick 1))
(store-pending-input client-tick input)
(let [(new-x new-z) (apply-movement cam-x cam-z movement-yaw input)]
(when (and (>= new-x 0) (<= new-x grid-max)
(>= new-z 0) (<= new-z grid-max))
(let [(resolved-x _ resolved-z) (world:resolve_collision cam-x cam-y cam-z new-x cam-y new-z 5)]
(set cam-x resolved-x)
(set cam-z resolved-z)))
(store-position client-tick cam-x cam-z movement-yaw)))
(when cursor-look?
(set cam-yaw (- cam-yaw (* input.look_dx cursor-sensitivity)))
(set cam-pitch (math.max (- max-pitch)
(math.min max-pitch
(- cam-pitch (* input.look_dy cursor-sensitivity))))))
(when (and (not cursor-look?) (pxl8.key_down "up"))
(set cam-pitch (math.min max-pitch (+ cam-pitch (* turn-speed dt)))))
(when (and (not cursor-look?) (pxl8.key_down "down"))
(set cam-pitch (math.max (- max-pitch) (- cam-pitch (* turn-speed dt)))))
(when (and (not cursor-look?) (pxl8.key_down "left"))
(set cam-yaw (+ cam-yaw (* turn-speed dt))))
(when (and (not cursor-look?) (pxl8.key_down "right"))
(set cam-yaw (- cam-yaw (* turn-speed dt))))
(when network
(let [(ok err) (pcall (fn []
(network:send_input {:move_x input.move_x
:move_y input.move_y
:look_dx input.look_dx
:look_dy input.look_dy
:yaw movement-yaw
:tick client-tick})
(network:update dt)
(when (network:poll)
(let [snapshot (network:snapshot)]
(when (and snapshot (> snapshot.tick last-processed-tick))
(set last-processed-tick snapshot.tick)
(let [player-id (network:player_id)]
(when (> player-id 0)
(let [curr (network:entity_userdata player-id)]
(when curr
(let [srv-x (pxl8.unpack_f32_be curr 0)
srv-z (pxl8.unpack_f32_be curr 8)]
(reconcile snapshot.tick srv-x srv-z)))))))))))]
(when (not ok)
(pxl8.error (.. "Network error: " err)))))
(set smooth-cam-x (+ (* smooth-cam-x (- 1 cam-smoothing)) (* cam-x cam-smoothing)))
(set smooth-cam-z (+ (* smooth-cam-z (- 1 cam-smoothing)) (* cam-z cam-smoothing)))
(when (and (pxl8.key_pressed "space") grounded?)
(set velocity-y jump-force)
(set grounded? false))
(set velocity-y (+ velocity-y (* gravity dt)))
(set cam-y (+ cam-y (* velocity-y dt)))
(when (<= cam-y ground-y)
(when (not grounded?)
(set land-squash land-squash-amount))
(set cam-y ground-y)
(set velocity-y 0)
(set grounded? true))
(when (< land-squash 0)
(set land-squash (math.min 0 (+ land-squash (* land-recovery-speed dt)))))
(let [moving (or (not= input.move_x 0) (not= input.move_y 0))]
(if (and moving grounded?)
(set bob-time (+ bob-time (* dt bob-speed)))
(let [target-phase (* (math.floor (/ bob-time math.pi)) math.pi)]
(set bob-time (+ (* bob-time 0.8) (* target-phase 0.2))))))
(set light-time (+ light-time (* dt 0.15))))))
(fn frame []
(pxl8.clear 1)
(when (not camera)
(pxl8.error "camera is nil!"))
(when (not world)
(pxl8.error "world is nil!"))
(when (and world (not (world:is_loaded)))
(pxl8.text "World not loaded yet..." 5 30 12))
(when (and camera world (world:is_loaded))
(let [bob-offset (* (math.sin bob-time) bob-amount)
eye-y (+ cam-y bob-offset land-squash)
forward-x (- (math.sin cam-yaw))
forward-z (- (math.cos cam-yaw))
target-x (+ smooth-cam-x forward-x)
target-y (+ eye-y (math.sin cam-pitch))
target-z (+ smooth-cam-z forward-z)
aspect (/ (pxl8.get_width) (pxl8.get_height))]
(camera:lookat [smooth-cam-x eye-y smooth-cam-z]
[target-x target-y target-z]
[0 1 0])
(camera:set_perspective 1.047 aspect 1.0 4096.0)
(let [light-pulse (+ 0.7 (* 0.3 (math.sin (* light-time 2))))
forward-x (- (math.sin cam-yaw))
forward-z (- (math.cos cam-yaw))
light-x (+ smooth-cam-x (* 150 forward-x) (* 50 (math.cos light-time)))
light-z (+ smooth-cam-z (* 150 forward-z) (* 50 (math.sin light-time)))
light-y (+ eye-y 30)]
(pxl8.begin_frame_3d camera {
:ambient 80
:celestial_dir [0.5 -0.8 0.3]
:celestial_intensity 0.5
:lights [{:x light-x :y light-y :z light-z
:r 255 :g 200 :b 150
:intensity (* 255 light-pulse)
:radius 400}]})
(pxl8.clear_depth)
(sky.update-gradient 1 2 6 6 10 18)
(sky.render smooth-cam-x eye-y smooth-cam-z)
(pxl8.clear_depth)
(world:render [smooth-cam-x eye-y smooth-cam-z])
(pxl8.end_frame_3d)
(let [dx (- light-x smooth-cam-x)
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)
(let [cx (/ (pxl8.get_width) 2)
cy (/ (pxl8.get_height) 2)
crosshair-size 4
red-color 18]
(pxl8.line (- cx crosshair-size) cy (+ cx crosshair-size) cy red-color)
(pxl8.line cx (- cy crosshair-size) cx (+ cy crosshair-size) red-color))
(pxl8.text (.. "fps: " (string.format "%.0f" fps-avg)) 5 5 12)
(pxl8.text (.. "pos: " (string.format "%.0f" cam-x) ","
(string.format "%.0f" cam-y) ","
(string.format "%.0f" cam-z)) 5 15 12))))
{:init init
:update update
:frame frame}

View file

@ -1,4 +1,5 @@
(local pxl8 (require :pxl8))
(local music (require :mod.music))
(var paused false)
(var gui nil)
@ -36,12 +37,18 @@
(when gui
(gui:begin_frame)
(pxl8.gui_window 200 100 240 140 "pxl8 demo")
(pxl8.gui_window 200 100 240 180 "pxl8 demo")
(when (gui:button 1 215 145 210 32 "Resume")
(hide))
(when (gui:button 2 215 185 210 32 "Quit")
(let [music-label (if (music.is-playing) "Music: On" "Music: Off")]
(when (gui:button 3 215 185 210 32 music-label)
(if (music.is-playing)
(music.stop)
(music.start))))
(when (gui:button 2 215 225 210 32 "Quit")
(pxl8.quit))
(if (gui:is_hovering)

View file

@ -101,7 +101,7 @@
(fn update [dt]
(when playing
(set time (+ time dt))
(when (>= time step-duration)
(while (>= time step-duration)
(set time (- time step-duration))
(local melody-idx (+ 1 (% step (length melody))))
@ -125,4 +125,5 @@
:start start
:stop stop
:update update
:is-playing (fn [] playing)}
:is-playing (fn [] playing)
:get-step (fn [] step)}

250
demo/mod/sky.fnl Normal file
View file

@ -0,0 +1,250 @@
(local ffi (require :ffi))
(local pxl8 (require :pxl8))
(local effects (require :pxl8.effects))
(local SKY_GRADIENT_START 144)
(local SKY_GRADIENT_COUNT 16)
(local sky-radius 900)
(local sky-segments 16)
(local sky-rings 16)
(local max-theta (* math.pi 0.55))
(local STAR_COUNT 200)
(local TINY_STAR_COUNT 5000)
(local STAR_SILVER_START 160)
(local STAR_BLUE_START 168)
(local STAR_RED_START 176)
(var sky-mesh nil)
(var last-gradient-key nil)
(var stars [])
(var tiny-stars [])
(fn generate-sky-gradient [zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b]
(for [i 0 (- SKY_GRADIENT_COUNT 1)]
(let [t (/ i (- SKY_GRADIENT_COUNT 1))
r (math.floor (+ zenith-r (* t (- horizon-r zenith-r))))
g (math.floor (+ zenith-g (* t (- horizon-g zenith-g))))
b (math.floor (+ zenith-b (* t (- horizon-b zenith-b))))]
(pxl8.set_palette_rgb (+ SKY_GRADIENT_START i) r g b))))
(fn create-sky-dome []
(let [verts []
indices []]
(for [i 0 (- sky-rings 1)]
(let [theta0 (* (/ i sky-rings) max-theta)
theta1 (* (/ (+ i 1) sky-rings) max-theta)
sin-t0 (math.sin theta0)
cos-t0 (math.cos theta0)
sin-t1 (math.sin theta1)
cos-t1 (math.cos theta1)
y0 (* sky-radius cos-t0)
y1 (* sky-radius cos-t1)
r0 (* sky-radius sin-t0)
r1 (* sky-radius sin-t1)
t0 (/ i sky-rings)
t1 (/ (+ i 1) sky-rings)
c0 (math.floor (+ SKY_GRADIENT_START (* t0 (- SKY_GRADIENT_COUNT 1)) 0.5))
c1 (math.floor (+ SKY_GRADIENT_START (* t1 (- SKY_GRADIENT_COUNT 1)) 0.5))]
(for [j 0 (- sky-segments 1)]
(let [phi0 (* (/ j sky-segments) math.pi 2)
phi1 (* (/ (+ j 1) sky-segments) math.pi 2)
cos-p0 (math.cos phi0)
sin-p0 (math.sin phi0)
cos-p1 (math.cos phi1)
sin-p1 (math.sin phi1)
x00 (* r0 cos-p0) z00 (* r0 sin-p0)
x01 (* r0 cos-p1) z01 (* r0 sin-p1)
x10 (* r1 cos-p0) z10 (* r1 sin-p0)
x11 (* r1 cos-p1) z11 (* r1 sin-p1)
nx00 (- (* sin-t0 cos-p0)) ny00 (- cos-t0) nz00 (- (* sin-t0 sin-p0))
nx01 (- (* sin-t0 cos-p1)) ny01 (- cos-t0) nz01 (- (* sin-t0 sin-p1))
nx10 (- (* sin-t1 cos-p0)) ny10 (- cos-t1) nz10 (- (* sin-t1 sin-p0))
nx11 (- (* sin-t1 cos-p1)) ny11 (- cos-t1) nz11 (- (* sin-t1 sin-p1))
base-idx (# verts)]
(if (= i 0)
(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 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})
;; Triangle: base, base+2, base+1
(table.insert indices base-idx)
(table.insert indices (+ base-idx 2))
(table.insert indices (+ base-idx 1)))
(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 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 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 3))
(table.insert indices (+ base-idx 2))
(table.insert indices base-idx)
(table.insert indices (+ base-idx 2))
(table.insert indices (+ base-idx 1))))))))
(set sky-mesh (pxl8.create_mesh verts indices))))
(fn update-gradient [zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b]
(let [key (.. zenith-r "," zenith-g "," zenith-b "," horizon-r "," horizon-g "," horizon-b)]
(when (not= key last-gradient-key)
(generate-sky-gradient zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b)
(set last-gradient-key key))))
(fn palette-ramp [start c0 c1]
(let [r0 (bit.rshift (bit.band c0 0xFF0000) 16)
g0 (bit.rshift (bit.band c0 0x00FF00) 8)
b0 (bit.band c0 0x0000FF)
r1 (bit.rshift (bit.band c1 0xFF0000) 16)
g1 (bit.rshift (bit.band c1 0x00FF00) 8)
b1 (bit.band c1 0x0000FF)]
(for [i 0 7]
(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 []
(palette-ramp STAR_SILVER_START 0x707888 0xFFFFFF) ;; silver
(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 [])
(init-star-palette)
(pxl8.rng_seed seed)
(for [i 1 STAR_COUNT]
(let [theta (math.acos (- 1 (* (pxl8.rng_f32) 0.85)))
phi (* (pxl8.rng_f32) math.pi 2)
brightness (pxl8.rng_range 1 4)
color-type (pxl8.rng_range 0 100)
shade (pxl8.rng_range 0 5)
color (if (< color-type 3) (+ STAR_RED_START shade)
(< color-type 15) (+ STAR_BLUE_START shade)
(+ STAR_SILVER_START shade))
sin-t (math.sin theta)
cos-t (math.cos theta)]
(table.insert stars {:dx (* sin-t (math.cos phi))
:dy cos-t
:dz (* sin-t (math.sin phi))
:brightness brightness
:color color})))
(pxl8.rng_seed (+ seed 0xCAFEBABE))
(for [i 1 TINY_STAR_COUNT]
(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
:color color}))))
(fn project-direction [dir-x dir-y dir-z yaw pitch width height]
(let [cos-yaw (math.cos yaw)
sin-yaw (math.sin yaw)
cos-pitch (math.cos pitch)
sin-pitch (math.sin pitch)
rotated-x (+ (* dir-x cos-yaw) (* dir-z sin-yaw))
rotated-z (+ (* (- dir-x) sin-yaw) (* dir-z cos-yaw))
rotated-y (- (* dir-y cos-pitch) (* rotated-z sin-pitch))
final-z (+ (* dir-y sin-pitch) (* rotated-z cos-pitch))]
(when (> final-z 0.01)
(let [fov 1.047
aspect (/ width height)
half-fov-tan (math.tan (* fov 0.5))
ndc-x (/ rotated-x (* final-z half-fov-tan aspect))
ndc-y (/ rotated-y (* final-z half-fov-tan))]
(when (and (>= ndc-x -1) (<= ndc-x 1) (>= ndc-y -1) (<= ndc-y 1))
{:x (math.floor (* (+ 1 ndc-x) 0.5 width))
:y (math.floor (* (- 1 ndc-y) 0.5 height))})))))
(fn render-stars [yaw pitch intensity]
(when (> intensity 0)
(let [width (pxl8.get_width)
height (pxl8.get_height)
glows []
fade-sq (* intensity intensity)]
(each [_ star (ipairs tiny-stars)]
(let [screen (project-direction star.dx star.dy star.dz yaw pitch width height)]
(when screen
(let [int (math.floor (* star.brightness fade-sq))]
(when (> int 8)
(table.insert glows {:x screen.x :y screen.y
:radius 1
:intensity int
:color star.color
:shape effects.GLOW_CIRCLE}))))))
(each [_ star (ipairs stars)]
(let [screen (project-direction star.dx star.dy star.dz yaw pitch width height)]
(when screen
(let [base-int (math.floor (* star.brightness 50 fade-sq 1.5))]
(if (>= star.brightness 4)
(do
(table.insert glows {:x screen.x :y screen.y
: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
(table.insert glows {:x screen.x :y screen.y
:radius 3
: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 2)
(table.insert glows {:x screen.x :y screen.y
:radius 2
:intensity base-int
:color star.color
:shape effects.GLOW_DIAMOND})
(table.insert glows {:x screen.x :y screen.y
:radius 1
:intensity (math.floor (* base-int 0.7))
:color star.color
:shape effects.GLOW_CIRCLE}))))))
(when (> (length glows) 0)
(effects.glows glows)))))
(fn render [cam-x cam-y cam-z]
(when (not sky-mesh) (create-sky-dome))
(when sky-mesh
(pxl8.draw_mesh sky-mesh {:x cam-x :y cam-y :z cam-z :passthrough true})))
{:render render
:render-stars render-stars
:generate-stars generate-stars
:update-gradient update-gradient
:SKY_GRADIENT_START SKY_GRADIENT_START
:SKY_GRADIENT_COUNT SKY_GRADIENT_COUNT}

75
demo/mod/vfx.fnl Normal file
View file

@ -0,0 +1,75 @@
(local vfx {})
(fn vfx.explosion [ps x y ?opts]
(let [opts (or ?opts {})
color (or opts.color 208)
force (or opts.force 200)]
(ps:set_position x y)
(ps:set_colors color (+ color 15))
(ps:set_velocity (- force) force (- force) force)
(ps:set_gravity 0 100)
(ps:set_life 0.3 0.8)
(ps:set_size 1 3)
(ps:set_drag 0.98)
(ps:set_spawn_rate 0)
(ps:emit (or opts.count 50))))
(fn vfx.fire [ps x y ?opts]
(let [opts (or ?opts {})
width (or opts.width 50)
color (or opts.color 208)]
(ps:set_position x y)
(ps:set_spread width 5)
(ps:set_colors color (+ color 15))
(ps:set_velocity -20 20 -80 -40)
(ps:set_gravity 0 -30)
(ps:set_life 0.5 1.5)
(ps:set_size 1 2)
(ps:set_turbulence 30)
(ps:set_drag 0.95)
(ps:set_spawn_rate (or opts.rate 50))))
(fn vfx.rain [ps width ?opts]
(let [opts (or ?opts {})
wind (or opts.wind 0)
color (or opts.color 153)]
(ps:set_position (/ width 2) -10)
(ps:set_spread width 0)
(ps:set_colors color (+ color 3))
(ps:set_velocity (- wind 10) (+ wind 10) 300 400)
(ps:set_gravity 0 200)
(ps:set_life 1 2)
(ps:set_size 1 1)
(ps:set_drag 1)
(ps:set_spawn_rate (or opts.rate 100))))
(fn vfx.smoke [ps x y ?opts]
(let [opts (or ?opts {})
color (or opts.color 248)]
(ps:set_position x y)
(ps:set_spread 10 5)
(ps:set_colors color (+ color 7))
(ps:set_velocity -15 15 -30 -10)
(ps:set_gravity 0 -20)
(ps:set_life 1 3)
(ps:set_size 2 4)
(ps:set_turbulence 20)
(ps:set_drag 0.98)
(ps:set_spawn_rate (or opts.rate 20))))
(fn vfx.snow [ps width ?opts]
(let [opts (or ?opts {})
wind (or opts.wind 10)
color (or opts.color 15)]
(ps:set_position (/ width 2) -10)
(ps:set_spread width 0)
(ps:set_colors color color)
(ps:set_velocity (- wind 20) (+ wind 20) 30 60)
(ps:set_gravity 0 10)
(ps:set_life 3 6)
(ps:set_size 1 2)
(ps:set_turbulence 15)
(ps:set_drag 0.99)
(ps:set_spawn_rate (or opts.rate 30))))
vfx

View file

@ -1,224 +0,0 @@
(local pxl8 (require :pxl8))
(local bob-amount 4.0)
(local bob-speed 8.0)
(local cam-smoothing 0.25)
(local cell-size 64)
(local cursor-sensitivity 0.008)
(local gravity -800)
(local grid-size 64)
(local ground-y 64)
(local jump-force 175)
(local land-recovery-speed 20)
(local land-squash-amount -4)
(local max-pitch 1.5)
(local move-speed 200)
(local turn-speed 2.0)
(var auto-run? false)
(var auto-run-cancel-key nil)
(var bob-time 0)
(var cam-pitch 0)
(var cam-x 1000)
(var cam-y 64)
(var cam-yaw 0)
(var cam-z 1000)
(var camera nil)
(var cursor-look? true)
(var grounded? true)
(var land-squash 0)
(var smooth-cam-x 1000)
(var smooth-cam-z 1000)
(var velocity-y 0)
(var world nil)
(fn init []
(set camera (pxl8.create_camera_3d))
(set world (pxl8.create_world))
(let [result (world:generate {
:type pxl8.PROCGEN_ROOMS
:width 64
:height 64
:seed 42
:min_room_size 5
:max_room_size 10
:num_rooms 20})]
(if (< result 0)
(pxl8.error (.. "Failed to generate rooms - result: " result))
(let [floor-tex (pxl8.procgen_tex {:name "floor"
:seed 11111
:width 64
:height 64
:base_color 19})
ceiling-tex (pxl8.procgen_tex {:name "ceiling"
:seed 22222
:width 64
:height 64
:base_color 1})
wall-tex (pxl8.procgen_tex {:name "wall"
:seed 12345
:width 64
:height 64
:base_color 4})]
(let [result (world:apply_textures [
{:name "floor"
:texture_id floor-tex
:rule (fn [normal] (> normal.y 0.7))}
{:name "ceiling"
:texture_id ceiling-tex
:rule (fn [normal] (< normal.y -0.7))}
{:name "wall"
:texture_id wall-tex
:rule (fn [normal] (and (<= normal.y 0.7) (>= normal.y -0.7)))}])]
(when (< result 0)
(pxl8.error (.. "Failed to apply textures - result: " result))))))))
(fn update [dt]
(when (pxl8.key_pressed "`")
(set auto-run? (not auto-run?))
(when (and auto-run? (pxl8.key_down "w"))
(set auto-run-cancel-key "w")))
(when (and auto-run? (not auto-run-cancel-key) (or (pxl8.key_down "w") (pxl8.key_down "s")))
(set auto-run? false)
(when (pxl8.key_down "s")
(set auto-run-cancel-key "s")))
(when (and auto-run-cancel-key (not (pxl8.key_down auto-run-cancel-key)))
(set auto-run-cancel-key nil))
(when (world:is_loaded)
(let [forward-x (- (math.sin cam-yaw))
forward-z (- (math.cos cam-yaw))
right-x (math.cos cam-yaw)
right-z (- (math.sin cam-yaw))
grid-max (* grid-size cell-size)
move-delta (* move-speed dt)]
(var moving false)
(var move-forward 0)
(var move-right 0)
(when (or (pxl8.key_down "w") auto-run?)
(set move-forward (+ move-forward 1)))
(when (and (pxl8.key_down "s") (not= auto-run-cancel-key "s"))
(set move-forward (- move-forward 1)))
(when (pxl8.key_down "a")
(set move-right (- move-right 1)))
(when (pxl8.key_down "d")
(set move-right (+ move-right 1)))
(set moving (or (not= move-forward 0) (not= move-right 0)))
(var new-x cam-x)
(var new-z cam-z)
(when moving
(let [len (math.sqrt (+ (* move-forward move-forward) (* move-right move-right)))
norm-forward (/ move-forward len)
norm-right (/ move-right len)]
(set new-x (+ new-x (* move-delta (+ (* forward-x norm-forward) (* right-x norm-right)))))
(set new-z (+ new-z (* move-delta (+ (* forward-z norm-forward) (* right-z norm-right)))))))
(when (and (>= new-x 0) (<= new-x grid-max)
(>= new-z 0) (<= new-z grid-max))
(let [(resolved-x resolved-y resolved-z) (world:resolve_collision cam-x cam-y cam-z new-x cam-y new-z 5)]
(set cam-x resolved-x)
(set cam-z resolved-z)))
(set smooth-cam-x (+ (* smooth-cam-x (- 1 cam-smoothing)) (* cam-x cam-smoothing)))
(set smooth-cam-z (+ (* smooth-cam-z (- 1 cam-smoothing)) (* cam-z cam-smoothing)))
(when cursor-look?
(let [dx (pxl8.mouse_dx)
dy (pxl8.mouse_dy)]
(set cam-yaw (- cam-yaw (* dx cursor-sensitivity)))
(set cam-pitch (math.max (- max-pitch)
(math.min max-pitch
(- cam-pitch (* dy cursor-sensitivity)))))))
(when (and (not cursor-look?) (pxl8.key_down "left"))
(set cam-yaw (+ cam-yaw (* turn-speed dt))))
(when (and (not cursor-look?) (pxl8.key_down "right"))
(set cam-yaw (- cam-yaw (* turn-speed dt))))
(when (and (not cursor-look?) (pxl8.key_down "up"))
(set cam-pitch (math.min max-pitch (+ cam-pitch (* turn-speed dt)))))
(when (and (not cursor-look?) (pxl8.key_down "down"))
(set cam-pitch (math.max (- max-pitch) (- cam-pitch (* turn-speed dt)))))
(when (and (pxl8.key_pressed "space") grounded?)
(set velocity-y jump-force)
(set grounded? false))
(set velocity-y (+ velocity-y (* gravity dt)))
(set cam-y (+ cam-y (* velocity-y dt)))
(when (<= cam-y ground-y)
(when (not grounded?)
(set land-squash land-squash-amount))
(set cam-y ground-y)
(set velocity-y 0)
(set grounded? true))
(when (< land-squash 0)
(set land-squash (math.min 0 (+ land-squash (* land-recovery-speed dt)))))
(if (and moving grounded?)
(set bob-time (+ bob-time (* dt bob-speed)))
(let [target-phase (* (math.floor (/ bob-time math.pi)) math.pi)]
(set bob-time (+ (* bob-time 0.8) (* target-phase 0.2))))))))
(fn frame []
(pxl8.clear 0)
(when (not camera)
(pxl8.error "camera is nil!"))
(when (not world)
(pxl8.error "world is nil!"))
(when (and world (not (world:is_loaded)))
(pxl8.text "World not loaded yet..." 5 30 12))
(when (and camera world (world:is_loaded))
(let [bob-offset (* (math.sin bob-time) bob-amount)
eye-y (+ cam-y bob-offset land-squash)
forward-x (- (math.sin cam-yaw))
forward-z (- (math.cos cam-yaw))
target-x (+ smooth-cam-x forward-x)
target-y (+ eye-y (math.sin cam-pitch))
target-z (+ smooth-cam-z forward-z)
aspect (/ (pxl8.get_width) (pxl8.get_height))]
(camera:lookat [smooth-cam-x eye-y smooth-cam-z]
[target-x target-y target-z]
[0 1 0])
(camera:set_perspective 1.047 aspect 1.0 4096.0)
(pxl8.begin_frame_3d camera)
(pxl8.clear_depth)
(world:render [smooth-cam-x eye-y smooth-cam-z])
(pxl8.end_frame_3d)
(let [cx (/ (pxl8.get_width) 2)
cy (/ (pxl8.get_height) 2)
crosshair-size 4
red-color 18]
(pxl8.line (- cx crosshair-size) cy (+ cx crosshair-size) cy red-color)
(pxl8.line cx (- cy crosshair-size) cx (+ cy crosshair-size) red-color))
(pxl8.text (.. "fps: " (string.format "%.1f" (pxl8.get_fps))) 5 5 12)
(pxl8.text (.. "pos: " (string.format "%.0f" cam-x) ","
(string.format "%.0f" cam-y) ","
(string.format "%.0f" cam-z)) 5 15 12))))
{:init init
:update update
:frame frame}