concinnity_core/components/sky_rotation.rs
1// Celestial-sphere rotation schema.
2
3use crate::ecs::asset_id::AssetId;
4
5/// Turns the whole celestial sphere: the sky, the image-based lighting it
6/// casts, every [DirectionalLight](#directionallight), and any
7/// [Prop](#prop) hung on it.
8///
9/// One per world. The rotation at elapsed time `t` is `angle_deg +
10/// degrees_per_second * t` about `axis`, taken in the sense a planet's own
11/// spin gives the sky: with the default axis a body rises from `+Z`, passes
12/// overhead through `+Y`, and sets toward `-Z`.
13///
14/// The component's own entity carries that rotation as its transform, so a
15/// `Prop` naming this asset as its `parent` orbits with the sky. Reflection
16/// probes are baked once and do not turn.
17///
18/// ```rust
19/// # use concinnity_core::components::SkyRotation;
20/// SkyRotation {
21/// axis: [1.0, 0.0, 0.0],
22/// degrees_per_second: 3.0,
23/// ..Default::default()
24/// };
25/// ```
26#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
27#[serde(default)]
28pub struct SkyRotation {
29 /// Asset identity; injected via `inject_name`. Not part of `args`.
30 #[serde(skip)]
31 pub asset_id: AssetId,
32 /// The celestial pole in world space: the axis the sphere turns about.
33 /// Does not need to be normalised.
34 pub axis: [f32; 3],
35 /// Turn rate in degrees per second. Negative runs the sky backwards.
36 pub degrees_per_second: f32,
37 /// The angle the sky starts at, in degrees.
38 pub angle_deg: f32,
39}
40
41impl Default for SkyRotation {
42 fn default() -> Self {
43 Self {
44 asset_id: AssetId::default(),
45 axis: [1.0, 0.0, 0.0],
46 degrees_per_second: 1.0,
47 angle_deg: 0.0,
48 }
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 #[test]
57 fn the_default_pole_is_horizontal_so_bodies_rise_and_set() {
58 // A pole along Y would only spin the sky about the zenith, which no
59 // observer on the ground can see; a horizontal pole is what makes a
60 // body cross the sky.
61 let s = SkyRotation::default();
62 assert_eq!(s.axis, [1.0, 0.0, 0.0]);
63 assert_eq!(s.degrees_per_second, 1.0);
64 assert_eq!(s.angle_deg, 0.0);
65 }
66
67 #[test]
68 fn an_authored_rotation_parses_and_round_trips_through_postcard() {
69 let s: SkyRotation =
70 serde_json::from_str(r#"{"axis":[0,0,1],"degrees_per_second":6,"angle_deg":45}"#)
71 .unwrap();
72 assert_eq!(s.axis, [0.0, 0.0, 1.0]);
73 assert_eq!(s.degrees_per_second, 6.0);
74 assert_eq!(s.angle_deg, 45.0);
75
76 let bytes = postcard::to_allocvec(&s).unwrap();
77 let back: SkyRotation = postcard::from_bytes(&bytes).unwrap();
78 assert_eq!(back.axis, [0.0, 0.0, 1.0]);
79 assert_eq!(back.degrees_per_second, 6.0);
80 assert_eq!(back.angle_deg, 45.0);
81 }
82}