source_map_gen/light/
time.rs1use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Utc};
2use rgb::RGB;
3use spa::{SolarPos, SpaError};
4
5use crate::{
6 source::{ColorBrightness}, light::{GlobalLighting, pitch_to_rgb}, map::Angles,
8};
9
10impl Angles {
33 pub(crate) fn from_solar_pos(pos: SolarPos) -> Self {
36 let pitch = pos.zenith_angle - 90.0;
37 let yaw = (270.0 - pos.azimuth).rem_euclid(360.0);
40 let roll = 0.0;
41 Angles { pitch, yaw, roll }
42 }
43}
44
45pub trait DateTimeUtcExt {
46 fn from_longitude(longitude_east: f64, datetime: NaiveDateTime) -> Option<DateTime<Utc>> {
49 let offset = lon_to_offset(longitude_east)?;
50 let datetime_fixed = DateTime::<FixedOffset>::from_local(datetime, offset);
51 Some(datetime_fixed.into())
52 }
53}
54
55impl DateTimeUtcExt for DateTime<Utc> {}
56
57pub trait NaiveDateTimeExt {
58 fn from_season(season: Season, time: NaiveTime) -> Option<NaiveDateTime> {
59 let year = 2023;
60 let (month, day) = match season {
61 Season::Spring => (3, 24), Season::Summer => (6, 25), Season::Fall => (9, 25), Season::Winter => (12, 24), };
66 Some(NaiveDate::from_ymd_opt(year, month, day)?.and_time(time))
67 }
68}
69
70impl NaiveDateTimeExt for NaiveDateTime {}
71
72#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
73pub enum Season {
74 Spring,
75 Summer,
76 Fall,
77 Winter,
78}
79
80pub(crate) fn lon_to_offset(longitude_east: f64) -> Option<FixedOffset> {
83 let tz_hour = longitude_east / 15.0;
84 let tz_secs = (tz_hour * 3600.0).round() as i32;
85 FixedOffset::east_opt(tz_secs)
87}
88
89pub fn calc_solar_position_local(
92 lat: f64,
93 lon: f64,
94 datetime: NaiveDateTime,
95) -> Result<SolarPos, SpaError> {
96 let utc = DateTime::from_longitude(lon, datetime).ok_or(SpaError::BadParam)?;
97 spa::calc_solar_position(utc, lat, lon)
98}
99pub fn loc_time_to_sun(
102 lat: f64,
103 lon: f64,
104 datetime: NaiveDateTime,
105) -> Result<GlobalLighting, SpaError> {
106 let solar_pos = calc_solar_position_local(lat, lon, datetime)?;
107 let mut sun_dir = Angles::from_solar_pos(solar_pos);
108 dbg!(sun_dir.pitch);
109 let RGB { r, g, b } = pitch_to_rgb(-sun_dir.pitch);
110 sun_dir.pitch = -sun_dir.pitch.abs();
111
112 Ok(GlobalLighting {
113 sun_color: ColorBrightness::new(r, g, b, 255), sun_dir: sun_dir.clone(),
115 amb_color: ColorBrightness::new(171, 206, 220, 50), amb_dir: sun_dir,
117 dir_lights: Vec::new(),
118 })
119}
120
121#[cfg(test)]
134mod tests {
135 use approx::assert_relative_eq;
136
137 use super::*;
138
139 #[test]
140 fn loc_time() {
141 let datetime =
142 NaiveDate::from_ymd_opt(2023, 4, 21).unwrap().and_hms_opt(14, 42, 0).unwrap();
143 println!("datetime: {}", datetime);
145 let lighting = loc_time_to_sun(36.188110, -115.176468, datetime).unwrap();
147 let dir = &lighting.sun_dir;
148 dbg!(dir);
149 assert_relative_eq!(-46.0, dir.pitch, epsilon = 3.0);
151 assert_relative_eq!(22.0, dir.yaw, epsilon = 3.0);
152 assert_relative_eq!(0.0, dir.roll);
153 }
154}