258 lines
11 KiB
Fennel
258 lines
11 KiB
Fennel
(local pxl8 (require :pxl8))
|
|
|
|
(local SKY_GRADIENT_START 144)
|
|
(local SKY_GRADIENT_COUNT 16)
|
|
(local sky-radius 900)
|
|
(local sky-segments 16)
|
|
(local sky-rings 16)
|
|
|
|
(local NUM_RANDOM_STARS 300)
|
|
(local NUM_TINY_STARS 7000)
|
|
(local STAR_CYCLE_PERIOD 86400)
|
|
(local STAR_SEED 0xDEADBEEF)
|
|
|
|
;; 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 last-gradient-key nil)
|
|
(var random-stars [])
|
|
(var sky-mesh nil)
|
|
(var star-count 0)
|
|
(var star-directions nil)
|
|
(var star-glows nil)
|
|
(var star-projected nil)
|
|
(var star-time 0)
|
|
(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) math.pi 0.5)
|
|
theta1 (* (/ (+ i 1) sky-rings) math.pi 0.5)
|
|
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
|
|
(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})
|
|
(table.insert indices base-idx)
|
|
(table.insert indices (+ base-idx 2))
|
|
(table.insert indices (+ base-idx 1)))
|
|
(do
|
|
(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})
|
|
(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 reset-gradient []
|
|
(set last-gradient-key nil))
|
|
|
|
(fn update-gradient [zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b]
|
|
(let [key (.. zenith-r "," zenith-g "," zenith-b "," horizon-r "," horizon-g "," horizon-b)]
|
|
(when (not= key last-gradient-key)
|
|
(generate-sky-gradient zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b)
|
|
(set last-gradient-key key))))
|
|
|
|
(fn band-factor [dx dy dz bx by bz width]
|
|
(let [dist (math.abs (+ (* dx bx) (* dy by) (* dz bz)))
|
|
in-band (- 1 (math.min (* dist width) 1))]
|
|
(* in-band in-band)))
|
|
|
|
(fn galactic-band-factor [dx dy dz]
|
|
(let [;; Main galactic band - crosses zenith
|
|
band-len (math.sqrt (+ (* 0.6 0.6) (* 0.3 0.3) (* 0.742 0.742)))
|
|
b1 (band-factor dx dy dz (/ 0.6 band-len) (/ 0.3 band-len) (/ 0.742 band-len) 3)
|
|
;; Secondary band - lower angle, different orientation
|
|
b2 (band-factor dx dy dz 0.8 0.15 0.58 3.5)
|
|
;; Tertiary band - opposite side
|
|
b3 (band-factor dx dy dz -0.7 0.2 0.69 4)]
|
|
(math.max b1 b2 b3)))
|
|
|
|
(fn generate-stars []
|
|
(set random-stars [])
|
|
(set tiny-stars [])
|
|
|
|
;; Generate random stars - use full upper hemisphere (dy > -0.1)
|
|
(for [i 0 (- NUM_RANDOM_STARS 1)]
|
|
(let [h1 (pxl8.hash32 (+ STAR_SEED (* i 5)))
|
|
h2 (pxl8.hash32 (+ STAR_SEED (* i 5) 1))
|
|
h3 (pxl8.hash32 (+ STAR_SEED (* i 5) 2))
|
|
h4 (pxl8.hash32 (+ STAR_SEED (* i 5) 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))
|
|
brightness-raw (/ (% h3 256) 255)
|
|
brightness (math.floor (+ 60 (* brightness-raw brightness-raw 195)))
|
|
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)))]
|
|
|
|
(when (> dy -0.1)
|
|
(table.insert random-stars {:dx dx :dy dy :dz dz
|
|
:brightness brightness
|
|
:color color}))))
|
|
|
|
(let [tiny-seed (+ STAR_SEED 0xCAFEBABE)]
|
|
(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})))))
|
|
|
|
(set star-count (+ (length tiny-stars) (length random-stars)))
|
|
(set star-directions (pxl8.create_vec3_array star-count))
|
|
(set star-glows (pxl8.create_glows 10000))
|
|
(set star-projected (pxl8.create_vec3_array star-count))
|
|
|
|
(var idx 0)
|
|
(each [_ star (ipairs tiny-stars)]
|
|
(let [dir (. star-directions idx)]
|
|
(set dir.x star.dx)
|
|
(set dir.y star.dy)
|
|
(set dir.z star.dz))
|
|
(set idx (+ idx 1)))
|
|
(each [_ star (ipairs random-stars)]
|
|
(let [dir (. star-directions idx)]
|
|
(set dir.x star.dx)
|
|
(set dir.y star.dy)
|
|
(set dir.z star.dz))
|
|
(set idx (+ idx 1))))
|
|
|
|
(fn render-stars [cam-x cam-y cam-z intensity dt]
|
|
(set star-time (+ star-time (or dt 0)))
|
|
(when (and (> intensity 0) (> star-count 0) star-glows)
|
|
(let [fade-in (* intensity intensity)
|
|
time-factor (/ star-time 60)
|
|
star-rotation (/ (* star-time math.pi 2) STAR_CYCLE_PERIOD)
|
|
t (pxl8.mat4_translate cam-x cam-y cam-z)
|
|
r (pxl8.mat4_rotate_y star-rotation)
|
|
s (pxl8.mat4_scale sky-radius sky-radius sky-radius)
|
|
transform (pxl8.mat4_multiply t (pxl8.mat4_multiply r s))
|
|
tiny-count (length tiny-stars)]
|
|
|
|
(star-glows:clear)
|
|
(pxl8.project_points star-directions star-projected star-count transform)
|
|
|
|
(for [i 0 (- tiny-count 1)]
|
|
(let [screen (. star-projected i)]
|
|
(when (> screen.z 0)
|
|
(let [star (. tiny-stars (+ i 1))
|
|
int (math.floor (* star.brightness fade-in))]
|
|
(when (> int 8)
|
|
(let [px (math.floor (+ screen.x 0.5))
|
|
py (math.floor (+ screen.y 0.5))]
|
|
(star-glows:add px py 1 int star.color pxl8.GLOW_CIRCLE)))))))
|
|
|
|
(for [i 0 (- (length random-stars) 1)]
|
|
(let [screen (. star-projected (+ tiny-count i))]
|
|
(when (> screen.z 0)
|
|
(let [star (. random-stars (+ i 1))
|
|
phase (+ (* (+ i 1) 2.137) (* time-factor 3.0))
|
|
twinkle (+ 0.75 (* 0.25 (math.sin (* phase 6.28))))
|
|
int (math.floor (* star.brightness fade-in twinkle))
|
|
sx (math.floor (+ screen.x 0.5))
|
|
sy (math.floor (+ screen.y 0.5))]
|
|
(if (> star.brightness 220)
|
|
(do
|
|
(star-glows:add sx sy 3 (math.floor (* int 1.5)) star.color pxl8.GLOW_DIAMOND)
|
|
(star-glows:add sx sy 5 (math.floor (/ int 2)) star.color pxl8.GLOW_CIRCLE))
|
|
(> star.brightness 180)
|
|
(do
|
|
(star-glows:add sx sy 2 int star.color pxl8.GLOW_DIAMOND)
|
|
(star-glows:add sx sy 4 (math.floor (/ int 3)) star.color pxl8.GLOW_CIRCLE))
|
|
(> star.brightness 120)
|
|
(do
|
|
(star-glows:add sx sy 2 (math.floor (* int 0.67)) star.color pxl8.GLOW_DIAMOND)
|
|
(star-glows:add sx sy 3 (math.floor (/ int 4)) star.color pxl8.GLOW_CIRCLE))
|
|
(star-glows:add sx sy 2 (math.floor (* int 0.5)) star.color pxl8.GLOW_CIRCLE))))))
|
|
|
|
(when (> (star-glows:count) 0)
|
|
(star-glows:render)))))
|
|
|
|
(fn render [cam-x cam-y cam-z wireframe]
|
|
(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 :wireframe wireframe})))
|
|
|
|
{:render render
|
|
:render-stars render-stars
|
|
:generate-stars generate-stars
|
|
:reset-gradient reset-gradient
|
|
:update-gradient update-gradient
|
|
:SKY_GRADIENT_START SKY_GRADIENT_START
|
|
:SKY_GRADIENT_COUNT SKY_GRADIENT_COUNT}
|