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}