251 lines
11 KiB
Text
251 lines
11 KiB
Text
|
|
(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}
|