1use glam::Vec3;
5use serde::{Deserialize, Serialize};
6
7#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
9#[serde(default)]
10pub struct SkySettings {
11 pub procedural: bool,
13 pub time: f32,
15 pub speed: f32,
17 pub clouds: f32,
19 pub cloud_speed: f32,
21 pub stars: f32,
23 pub haze: f32,
25 pub moon_phase: f32,
27 pub aurora: f32,
29 pub milky_way: f32,
31}
32
33impl Default for SkySettings {
34 fn default() -> Self {
35 Self {
36 procedural: false,
37 time: 14.0,
38 speed: 0.0,
39 clouds: 0.45,
40 cloud_speed: 0.02,
41 stars: 0.8,
42 haze: 0.3,
43 moon_phase: 0.5,
44 aurora: 0.0,
45 milky_way: 0.7,
46 }
47 }
48}
49
50pub fn sun_direction(hour: f32) -> Vec3 {
52 let a = (hour - 6.0) / 24.0 * std::f32::consts::TAU;
53 Vec3::new(a.cos(), a.sin(), 0.35).normalize()
54}
55
56fn smoothstep(a: f32, b: f32, x: f32) -> f32 {
57 let t = ((x - a) / (b - a)).clamp(0.0, 1.0);
58 t * t * (3.0 - 2.0 * t)
59}
60
61fn mix3(a: [f32; 3], b: [f32; 3], t: f32) -> [f32; 3] {
62 [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t]
63}
64
65pub fn moon_illumination(phase: f32) -> f32 {
67 0.5 - 0.5 * (std::f32::consts::TAU * phase).cos()
68}
69
70pub fn main_light(sky: &SkySettings) -> (Vec3, [f32; 3], f32) {
73 let sun = sun_direction(sky.time);
74 let e = sun.y;
75 let day = smoothstep(-0.06, 0.18, e);
76 let warm = mix3([1.0, 0.5, 0.25], [1.0, 0.96, 0.9], smoothstep(0.02, 0.45, e));
77 let cloud_dim = 1.0 - 0.55 * sky.clouds.clamp(0.0, 1.0);
78 if day > 0.001 {
79 (sun, warm, 1.35 * day * cloud_dim)
80 } else {
81 (-sun, [0.55, 0.65, 1.0], 0.03 + 0.15 * moon_illumination(sky.moon_phase))
82 }
83}
84
85pub fn ambient(sky: &SkySettings) -> [f32; 3] {
87 let e = sun_direction(sky.time).y;
88 let day = smoothstep(-0.1, 0.25, e);
89 let dusk = (1.0 - (e * 4.0).abs().min(1.0)) * 0.5;
90 let base = mix3([0.025, 0.035, 0.08], [0.3, 0.34, 0.42], day);
91 mix3(base, [0.4, 0.28, 0.25], dusk * day.max(0.3))
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn sun_follows_the_day() {
100 assert!(sun_direction(12.0).y > 0.9);
101 assert!(sun_direction(0.0).y < -0.9);
102 assert!(sun_direction(6.0).y.abs() < 0.05);
103 assert!(sun_direction(6.0).x > 0.9 && sun_direction(18.0).x < -0.9);
104 }
105
106 #[test]
107 fn moon_phase_dims_night_light() {
108 let full = main_light(&SkySettings { time: 0.0, moon_phase: 0.5, ..Default::default() }).2;
109 let new = main_light(&SkySettings { time: 0.0, moon_phase: 0.0, ..Default::default() }).2;
110 assert!(full > new * 3.0 && moon_illumination(0.5) > 0.99 && moon_illumination(0.0) < 0.01);
111 }
112
113 #[test]
114 fn light_is_bright_at_noon_and_dim_at_night() {
115 let noon = main_light(&SkySettings { time: 12.0, ..Default::default() });
116 let night = main_light(&SkySettings { time: 0.0, ..Default::default() });
117 assert!(noon.2 > 1.0 && night.2 < 0.3);
118 assert!(noon.0.y > 0.0 && night.0.y > 0.0, "the moon also shines from above");
119 assert!(
120 ambient(&SkySettings { time: 12.0, ..Default::default() })[0]
121 > ambient(&SkySettings { time: 0.0, ..Default::default() })[0] * 4.0
122 );
123 }
124}