1use serde::Serialize;
4use serde_json::Value;
5
6use crate::{Component, Entity, JsonError, Light, LightKind, Transform, World};
7
8#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
14#[serde(rename_all = "snake_case")]
15pub enum SkyAnchor {
16 Horizontal,
19 TowardsLight { light: Entity, distance: f32 },
24}
25
26impl SkyAnchor {
27 pub fn placed(
31 &self,
32 transform: &Transform,
33 view: [f32; 3],
34 light: Option<&Transform>,
35 ) -> Option<Transform> {
36 match *self {
37 SkyAnchor::Horizontal => {
38 let mut t = *transform;
39 t.cols[3][0] += view[0];
40 t.cols[3][2] += view[2];
41 Some(t)
42 }
43 SkyAnchor::TowardsLight { distance, .. } => {
44 let z = light?.cols[2];
45 let n = z.iter().map(|x| x * x).sum::<f32>().sqrt();
46 if !(n > 0.0 && n.is_finite()) {
47 return None;
48 }
49 let towards = z.map(|x| x / n);
50 let position = std::array::from_fn(|i| view[i] + distance * towards[i]);
51 Transform::facing(position, towards.map(|x| -x))
52 }
53 }
54 }
55}
56
57impl Component for SkyAnchor {
58 const NAME: &'static str = "core.sky_anchor";
59 const GEOMETRY: crate::Geometry = crate::Geometry::None;
60 fn apply_linear(&self, _l: &crate::Linear) -> crate::world::Linearity<Self> {
62 crate::world::Linearity::Invariant
63 }
64 fn to_json(&self) -> Result<Option<Value>, JsonError> {
65 crate::json::to_value(self).map(Some)
66 }
67 fn references(&self, out: &mut Vec<Entity>) {
68 if let SkyAnchor::TowardsLight { light, .. } = self {
69 out.push(*light);
70 }
71 }
72 fn verify(&self, e: Entity, w: &World) -> Result<(), String> {
75 if !w.has::<Transform>(e) {
76 return Err("a sky anchor on an entity without a core.transform".into());
77 }
78 if let SkyAnchor::TowardsLight { light, distance } = *self {
79 if !(distance.is_finite() && distance > 0.0) {
80 return Err(format!(
81 "sky anchor distance {distance} is not finite and above 0"
82 ));
83 }
84 if !matches!(w.get::<Light>(light), Some(l) if l.kind == LightKind::Directional) {
85 return Err(format!(
86 "sky anchor light {light:?} is not a directional core.light"
87 ));
88 }
89 if !w.has::<Transform>(light) {
90 return Err(format!("sky anchor light {light:?} has no core.transform"));
91 }
92 }
93 Ok(())
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 fn close(a: Transform, b: [[f32; 3]; 4]) -> bool {
102 a.cols
103 .iter()
104 .flatten()
105 .zip(b.iter().flatten())
106 .all(|(x, y)| (x - y).abs() < 1e-4)
107 }
108
109 #[test]
115 fn the_sky_of_a_recorded_frame_stands_where_the_game_drew_it() {
116 let view = [-203.3207, -26.2268, 178.8822];
117 let dome = SkyAnchor::Horizontal
118 .placed(&Transform::IDENTITY, view, None)
119 .unwrap();
120 assert!(close(
121 dome,
122 [
123 [1., 0., 0.],
124 [0., 1., 0.],
125 [0., 0., 1.],
126 [-203.3207, 0., 178.8822]
127 ]
128 ));
129 let towards = [0.0, 0.422618, -0.9063079];
131 let light = Transform::facing([0.; 3], towards.map(|x| -x)).unwrap();
132 let mut w = World::new();
133 let moon = SkyAnchor::TowardsLight {
134 light: w.spawn(),
135 distance: 25.0,
136 }
137 .placed(&Transform::IDENTITY, view, Some(&light))
138 .unwrap();
139 assert!(close(
141 moon,
142 [
143 [-1., 0., 0.],
144 [0., 0.9063079, 0.422618],
145 [0., 0.422618, -0.9063079],
146 [-203.3207, -15.66135, 156.2245]
147 ]
148 ));
149 }
150
151 #[test]
152 fn a_light_anchor_needs_a_directional_light_and_a_distance() {
153 let mut w = World::new();
154 let sun = w.spawn();
155 w.insert(sun, Transform::IDENTITY);
156 w.insert(
157 sun,
158 Light {
159 kind: LightKind::Directional,
160 color: [1.; 3],
161 intensity: 1.,
162 range: None,
163 cast_shadows: None,
164 radius: None,
165 specular: None,
166 },
167 );
168 let moon = w.spawn();
169 w.insert(moon, Transform::IDENTITY);
170 let ok = SkyAnchor::TowardsLight {
171 light: sun,
172 distance: 25.,
173 };
174 assert_eq!(ok.verify(moon, &w), Ok(()));
175 let far = SkyAnchor::TowardsLight {
176 light: sun,
177 distance: 0.,
178 };
179 assert!(far.verify(moon, &w).unwrap_err().contains("distance"));
180 let lamp = SkyAnchor::TowardsLight {
181 light: moon,
182 distance: 25.,
183 };
184 assert!(lamp.verify(moon, &w).unwrap_err().contains("not a directional"));
185 let loose = w.spawn();
186 assert!(
187 SkyAnchor::Horizontal
188 .verify(loose, &w)
189 .unwrap_err()
190 .contains("core.transform")
191 );
192 let mut refs = Vec::new();
193 ok.references(&mut refs);
194 assert_eq!(refs, vec![sun]);
195 assert_eq!(
196 ok.to_json().unwrap(),
197 Some(serde_json::json!({"towards_light": {"light": sun, "distance": 25.0}}))
198 );
199 assert_eq!(
200 SkyAnchor::Horizontal.to_json().unwrap(),
201 Some(serde_json::json!("horizontal"))
202 );
203 }
204}