Skip to main content

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}