(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 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 :update-gradient update-gradient :SKY_GRADIENT_START SKY_GRADIENT_START :SKY_GRADIENT_COUNT SKY_GRADIENT_COUNT}