eclipse/
lib.rs

1use glam::{Vec2, Vec3};
2
3/// Loose definitions for each type of celestial body. Not being very scientific here.
4pub enum Body {
5    Planet(Mass, Atmosphere),
6    Sun(Mass, Light),
7    Moon(Mass),
8}
9
10/// An object which takes up physical space.
11pub struct Mass {
12    /// A unit vector in object-space, by which this celestial body is tilted.
13    pub axis: Vec3,
14    /// Radius of the celestial body to it's surface, excluding it's atmosphere, in kilometers.
15    pub radius: f32,
16    /// The current position of the mass in space.
17    pub position: Vec3,
18}
19
20impl Default for Mass {
21    fn default() -> Self {
22        // These are rough estimates for earth.
23        Self {
24            axis: Vec3::new(0.0, -0.9175, 0.3978),
25            radius: 6_371.0,
26            position: Vec3::ZERO,
27        }
28    }
29}
30
31impl Mass {
32    /// Map a local (x, z) displacement onto the surface of the mass
33    /// preserving distances along the tangent plane.
34    fn place(&mut self, x: f32, z: f32) -> (Vec3, Vec3) {
35        // Create tangent plane basis
36        let right = self.axis.cross(Vec3::Y).normalize_or_zero();
37        let forward = right.cross(self.axis);
38
39        // Tangent displacement
40        let displacement = right * x + forward * z;
41        let length = displacement.length();
42
43        if length < 1e-6 {
44            // Too close, just return point on sphere
45            return (self.position + self.axis * self.radius, self.axis);
46        }
47
48        // Angle to rotate along the great circle
49        let theta = length / self.radius;
50        let axis = self.axis.cross(displacement).normalize();
51
52        // Rodrigues' rotation formula
53        let up = self.axis * theta.cos()
54            + axis.cross(self.axis) * theta.sin()
55            + axis * axis.dot(self.axis) * (1.0 - theta.cos());
56
57        (self.position + up * self.radius, up)
58    }
59}
60
61pub struct Atmosphere {
62    /// Height of the atmosphere from a celestial body's surface in kilometers.
63    /// Clamped to `max(height, 0)`
64    pub height: f32,
65    /// The Rayleigh scattering component.
66    pub rayleigh: Rayleigh,
67    /// The Mie scattering component.
68    pub mie: Mie,
69    /// The absorption / Ozone component.
70    pub absorption: Absorption,
71    /// The average albedo of the ground used to model light bounced off the planet's surface.
72    pub albedo: Vec3,
73    /// A weight for multiple scattering in the atmosphere.
74    pub multiple_scattering_factor: f32,
75}
76
77impl Atmosphere {
78    fn default(henyey_greenstein_draine: bool) -> Self {
79        const RAYLEIGH_SCALE_HEIGHT: f32 = 8.0;
80        const MIE_SCALE_HEIGHT: f32 = 1.2;
81
82        // These are the rough estimates for earth.
83        Self {
84            height: 100.0, // Kármán line, the conventional definition of the edge of space.
85            rayleigh: Rayleigh {
86                density: -1.0 / RAYLEIGH_SCALE_HEIGHT,
87                scattering: Vec3::new(0.005802, 0.013558, 0.033100),
88            },
89            mie: Mie {
90                density: -1.0 / MIE_SCALE_HEIGHT,
91                scattering: Vec3::new(0.003996, 0.003996, 0.003996),
92                extinction: Vec3::new(0.004440, 0.004440, 0.004440),
93                phase: if henyey_greenstein_draine { 0.8 } else { 3.4 },
94            },
95            absorption: Absorption {
96                low_height: 25.0,
97                low_constant: -2.0 / 3.0,
98                low_linear: 1.0 / 15.0,
99                high_constant: 8.0 / 3.0,
100                high_linear: -1.0 / 15.0,
101                extinction: Vec3::new(0.000650, 0.001881, 0.000085),
102            },
103            albedo: Vec3::new(0.4, 0.4, 0.4),
104            multiple_scattering_factor: 1.0,
105        }
106    }
107}
108
109use atmosphere::*;
110pub mod atmosphere {
111    use glam::Vec3;
112
113    /// Rayleigh scattering parameters.
114    pub struct Rayleigh {
115        /// Rayleigh scattering exponential distribution scale in the atmosphere in `km^-1`.
116        pub density: f32,
117        /// Rayleigh scattering coefficients in `km^-1`.
118        pub scattering: Vec3,
119    }
120
121    /// The mie scattering parameters, the phase function is approximated
122    /// using the Cornette-Shanks phase function.
123    pub struct Mie {
124        /// Mie scattering exponential distribution scale in the atmosphere in `km^-1`.
125        pub density: f32,
126        /// Mie scattering coefficients in `km^-1`.
127        pub scattering: Vec3,
128        /// Mie extinction coefficients in `km^-1`.
129        pub extinction: Vec3,
130        /// Mie phase function parameter.
131        /// For Cornette-Shanks, this is the excentricity, i.e., the asymmetry paraemter of the phase function in range ]-1, 1[.
132        /// For Henyey-Greenstein + Draine, this is the droplet diameter in µm. This should be in range ]2, 20[ (according to the paper, the lower bound for plausible fog particle sizes is 5 µm).
133        /// For Henyey-Greenstein + Draine using a constant droplet diameter, this parameter has no effect.
134        pub phase: f32,
135    }
136
137    /// A medium struct for the atmosphere that only absorbs light with two layers.
138    /// In Earth's atmosphere this is used to model ozone.
139    /// 
140    /// Computed as: `extinction * (linear * h + constant)`
141    /// 
142    /// where `h` is the altitude and `linear` and `constant` are the first or second layer's linear and constant terms.
143    /// If `h` is lower than [`Absorption::low_height`], than the lower layer is used, otherwise the higher one is instead. 
144    pub struct Absorption {
145        /// The height of the first layer of the absorption component in kilometers.
146        pub low_height: f32,
147        /// The constant term of the absorption component's first layer, this is unitless.
148        pub low_constant: f32,
149        /// The linear term of the absorption component's first layer in `km^-1`.
150        pub low_linear: f32,
151        /// The constant term of the absorption component's second layer, this is unitless.
152        pub high_constant: f32,
153        /// The linear term of the absorption component's second layer in `km^-1`.
154        pub high_linear: f32,
155        /// The extinction coefficients of the absorption component in `km^-1`.
156        pub extinction: Vec3,
157    }
158}
159
160pub struct Light {
161    /// The light's illuminance at the horizon.
162    illuminance: Vec3,
163}
164
165pub fn earth(henyey_greenstein_draine: bool) -> Body {
166    Body::Planet(
167        Mass::default(),
168        Atmosphere::default(henyey_greenstein_draine),
169    )
170}
171
172pub fn sun() -> Body {
173    // 127_000.0 lux
174    Body::Sun(
175        Mass {
176            axis: Vec3::Y,
177            radius: 696_340.0,
178            position: Vec3::ZERO,
179        },
180        Light {
181            illuminance: Vec3::ONE,
182        }
183    )
184}