💿🐜 Antkeeper source code https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

346 lines
12 KiB

  1. /*
  2. * Copyright (C) 2021 Christopher J. Howard
  3. *
  4. * This file is part of Antkeeper source code.
  5. *
  6. * Antkeeper source code is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Antkeeper source code is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include "ecs/systems/astronomy-system.hpp"
  20. #include "astro/apparent-size.hpp"
  21. #include "ecs/components/orbit-component.hpp"
  22. #include "ecs/components/blackbody-component.hpp"
  23. #include "ecs/components/atmosphere-component.hpp"
  24. #include "ecs/components/transform-component.hpp"
  25. #include "geom/intersection.hpp"
  26. #include "color/color.hpp"
  27. #include "physics/orbit/orbit.hpp"
  28. #include "physics/time/ut1.hpp"
  29. #include "physics/light/blackbody.hpp"
  30. #include "physics/light/photometry.hpp"
  31. #include "physics/light/luminosity.hpp"
  32. #include "geom/cartesian.hpp"
  33. #include <iostream>
  34. namespace ecs {
  35. /**
  36. * Calculates the optical depth between two points.
  37. *
  38. * @param start Start point.
  39. * @param end End point.
  40. * @param sample_count Number of samples to take between the start and end points.
  41. * @param scale_height Scale height of the atmospheric particles to measure.
  42. */
  43. template <class T>
  44. T transmittance(math::vector3<T> start, math::vector3<T> end, std::size_t sample_count, T scale_height)
  45. {
  46. const T inverse_scale_height = T(1) / -scale_height;
  47. math::vector3<T> direction = end - start;
  48. T distance = length(direction);
  49. direction /= distance;
  50. // Calculate the distance between each sample point
  51. T step_distance = distance / T(sample_count);
  52. // Sum the atmospheric particle densities at each sample point
  53. T total_density = 0.0;
  54. math::vector3<T> sample_point = start;
  55. for (std::size_t i = 0; i < sample_count; ++i)
  56. {
  57. // Determine altitude of sample point
  58. T altitude = length(sample_point);
  59. // Calculate atmospheric particle density at sample altitude
  60. T density = exp(altitude * inverse_scale_height);
  61. // Add density to the total density
  62. total_density += density;
  63. // Advance to next sample point
  64. sample_point += direction * step_distance;
  65. }
  66. // Scale the total density by the step distance
  67. return total_density * step_distance;
  68. }
  69. astronomy_system::astronomy_system(ecs::registry& registry):
  70. entity_system(registry),
  71. universal_time(0.0),
  72. time_scale(1.0),
  73. reference_body(entt::null),
  74. reference_body_axial_tilt(0.0),
  75. reference_body_axial_rotation(0.0),
  76. sun_light(nullptr),
  77. sky_pass(nullptr)
  78. {}
  79. void astronomy_system::update(double t, double dt)
  80. {
  81. // Add scaled timestep to current time
  82. set_universal_time(universal_time + dt * time_scale);
  83. // Abort if reference body has not been set
  84. if (reference_body == entt::null)
  85. return;
  86. // Abort if reference body has no orbit component
  87. if (!registry.has<ecs::orbit_component>(reference_body))
  88. return;
  89. // Update axial rotation of reference body
  90. reference_body_axial_rotation = physics::time::ut1::era(universal_time);
  91. // Get orbit component of reference body
  92. const auto& reference_orbit = registry.get<ecs::orbit_component>(reference_body);
  93. /// Construct reference frame which transforms coordinates from inertial space to reference body BCBF space
  94. inertial_to_bcbf = physics::orbit::inertial::to_bcbf
  95. (
  96. reference_orbit.state.r,
  97. reference_orbit.elements.i,
  98. reference_body_axial_tilt,
  99. reference_body_axial_rotation
  100. );
  101. /// Construct reference frame which transforms coordinates from inertial space to reference body topocentric space
  102. inertial_to_topocentric = inertial_to_bcbf * bcbf_to_topocentric;
  103. // Set the transform component translations of orbiting bodies to their topocentric positions
  104. registry.view<orbit_component, transform_component>().each(
  105. [&](ecs::entity entity, auto& orbit, auto& transform)
  106. {
  107. // Transform Cartesian position vector (r) from inertial space to topocentric space
  108. const math::vector3<double> r_topocentric = inertial_to_topocentric * orbit.state.r;
  109. // Update local transform
  110. transform.local.translation = math::type_cast<float>(r_topocentric);
  111. });
  112. // Get atmosphere component of reference body, if any
  113. if (registry.has<ecs::atmosphere_component>(reference_body))
  114. {
  115. const ecs::atmosphere_component& atmosphere = registry.get<ecs::atmosphere_component>(reference_body);
  116. }
  117. if (sun_light != nullptr)
  118. {
  119. const math::vector3<double> sun_position_inertial = {0, 0, 0};
  120. const math::vector3<double> sun_forward_inertial = math::normalize(reference_orbit.state.r - sun_position_inertial);
  121. const math::vector3<double> sun_up_inertial = {0, 0, 1};
  122. // Transform sun position, forward, and up vectors into topocentric space
  123. const math::vector3<double> sun_position_topocentric = inertial_to_topocentric * sun_position_inertial;
  124. const math::vector3<double> sun_forward_topocentric = inertial_to_topocentric.rotation * sun_forward_inertial;
  125. const math::vector3<double> sun_up_topocentric = inertial_to_topocentric.rotation * sun_up_inertial;
  126. // Update sun light transform
  127. sun_light->set_translation(math::type_cast<float>(sun_position_topocentric));
  128. sun_light->set_rotation
  129. (
  130. math::look_rotation
  131. (
  132. math::type_cast<float>(sun_forward_topocentric),
  133. math::type_cast<float>(sun_up_topocentric)
  134. )
  135. );
  136. // Convert sun topocentric Cartesian coordinates to spherical coordinates
  137. math::vector3<double> sun_az_el = geom::cartesian::to_spherical(ezs_to_sez * sun_position_topocentric);
  138. sun_az_el.z = math::pi<double> - sun_az_el.z;
  139. //std::cout << "el: " << math::degrees(sun_az_el.y) << "; az: " << math::degrees(sun_az_el.z) << std::endl;
  140. // If the reference body has an atmosphere
  141. if (registry.has<ecs::atmosphere_component>(reference_body))
  142. {
  143. // Get the atmosphere component of the reference body
  144. const auto& atmosphere = registry.get<ecs::atmosphere_component>(reference_body);
  145. const double meters_per_au = 1.496e+11;
  146. const double earth_radius_au = 4.26352e-5;
  147. const double earth_radius_m = earth_radius_au * meters_per_au;
  148. const double observer_altitude_m = (observer_location[0] - earth_radius_au) * meters_per_au;
  149. // Altitude of observer in meters
  150. geom::ray<double> sample_ray;
  151. sample_ray.origin = {0, observer_altitude_m, 0};
  152. sample_ray.direction = math::normalize(sun_position_topocentric);
  153. geom::sphere<double> exosphere;
  154. exosphere.center = {0, -earth_radius_m, 0};
  155. exosphere.radius = atmosphere.exosphere_radius;
  156. auto intersection_result = geom::ray_sphere_intersection(sample_ray, exosphere);
  157. if (std::get<0>(intersection_result))
  158. {
  159. double3 sample_start = sample_ray.origin;
  160. double3 sample_end = sample_ray.extrapolate(std::get<2>(intersection_result));
  161. double transmittance_rayleigh = transmittance(sample_start, sample_end, 16, atmosphere.rayleigh_scale_height);
  162. double transmittance_mie = transmittance(sample_start, sample_end, 16, atmosphere.mie_scale_height);
  163. // Calculate attenuation due to atmospheric scattering
  164. double3 scattering_attenuation =
  165. atmosphere.rayleigh_scattering_coefficients * transmittance_rayleigh +
  166. atmosphere.mie_scattering_coefficients * transmittance_mie;
  167. scattering_attenuation.x = std::exp(-scattering_attenuation.x);
  168. scattering_attenuation.y = std::exp(-scattering_attenuation.y);
  169. scattering_attenuation.z = std::exp(-scattering_attenuation.z);
  170. double scattering_mean = (scattering_attenuation.x + scattering_attenuation.y + scattering_attenuation.z) / 3.0;
  171. const double sun_temperature = 5777.0;
  172. const double sun_radius = 6.957e+8;
  173. const double sun_surface_area = 4.0 * math::pi<double> * sun_radius * sun_radius;
  174. // Calculate distance attenuation
  175. double sun_distance_m = math::length(sun_position_topocentric) * meters_per_au;
  176. double distance_attenuation = 1.0 / (sun_distance_m * sun_distance_m);
  177. double sun_luminous_flux = blackbody_luminous_flux(sun_temperature, sun_radius);
  178. double sun_luminous_intensity = sun_luminous_flux / (4.0 * math::pi<double>);
  179. double sun_illuminance = sun_luminous_intensity / (sun_distance_m * sun_distance_m);
  180. std::cout << "distance atten: " << distance_attenuation << std::endl;
  181. std::cout << "scatter atten: " << scattering_attenuation << std::endl;
  182. std::cout << "luminous flux: " << sun_luminous_flux << std::endl;
  183. std::cout << "luminous intensity: " << sun_luminous_intensity << std::endl;
  184. std::cout << "illuminance: " << sun_illuminance * scattering_mean << std::endl;
  185. // Calculate sun color
  186. double3 color_xyz = color::cct::to_xyz(sun_temperature);
  187. double3 color_acescg = color::xyz::to_acescg(color_xyz);
  188. sun_light->set_color(math::type_cast<float>(color_acescg * scattering_attenuation));
  189. sun_light->set_intensity(sun_illuminance);
  190. }
  191. }
  192. }
  193. if (sky_pass != nullptr)
  194. {
  195. sky_pass->set_topocentric_frame
  196. (
  197. physics::frame<float>
  198. {
  199. math::type_cast<float>(inertial_to_topocentric.translation),
  200. math::type_cast<float>(inertial_to_topocentric.rotation)
  201. }
  202. );
  203. sky_pass->set_sun_object(sun_light);
  204. }
  205. }
  206. void astronomy_system::set_universal_time(double time)
  207. {
  208. universal_time = time;
  209. }
  210. void astronomy_system::set_time_scale(double scale)
  211. {
  212. time_scale = scale;
  213. }
  214. void astronomy_system::set_reference_body(ecs::entity entity)
  215. {
  216. reference_body = entity;
  217. }
  218. void astronomy_system::set_reference_body_axial_tilt(double angle)
  219. {
  220. reference_body_axial_tilt = angle;
  221. }
  222. void astronomy_system::set_observer_location(const double3& location)
  223. {
  224. observer_location = location;
  225. // Construct reference frame which transforms coordinates from SEZ to EZS
  226. sez_to_ezs = physics::frame<double>
  227. {
  228. {0, 0, 0},
  229. math::normalize
  230. (
  231. math::quaternion<double>::rotate_x(-math::half_pi<double>) *
  232. math::quaternion<double>::rotate_z(-math::half_pi<double>)
  233. )
  234. };
  235. // Construct reference frame which transforms coordinates from EZS to SEZ
  236. ezs_to_sez = sez_to_ezs.inverse();
  237. // Construct reference frame which transforms coordinates from BCBF space to topocentric space
  238. bcbf_to_topocentric = physics::orbit::bcbf::to_topocentric
  239. (
  240. observer_location[0], // Radial distance
  241. observer_location[1], // Latitude
  242. observer_location[2] // Longitude
  243. ) * sez_to_ezs;
  244. }
  245. void astronomy_system::set_sun_light(scene::directional_light* light)
  246. {
  247. sun_light = light;
  248. }
  249. void astronomy_system::set_sky_pass(::sky_pass* pass)
  250. {
  251. this->sky_pass = pass;
  252. }
  253. double astronomy_system::blackbody_luminous_flux(double t, double r)
  254. {
  255. // Blackbody spectral power distribution function
  256. auto spd = [t](double x) -> double
  257. {
  258. // Convert nanometers to meters
  259. x *= double(1e-9);
  260. return physics::light::blackbody::spectral_radiance<double>(t, x, physics::constants::speed_of_light<double>);
  261. };
  262. // Luminous efficiency function (photopic)
  263. auto lef = [](double x) -> double
  264. {
  265. return physics::light::luminosity::photopic<double>(x);
  266. };
  267. // Construct range of spectral sample points
  268. std::vector<double> samples(10000);
  269. std::iota(samples.begin(), samples.end(), 10);
  270. // Calculate luminous efficiency
  271. const double efficiency = physics::light::luminous_efficiency<double>(spd, lef, samples.begin(), samples.end());
  272. // Calculate surface area of spherical blackbody
  273. const double a = double(4) * math::pi<double> * r * r;
  274. // Calculate radiant flux
  275. const double radiant_flux = physics::light::blackbody::radiant_flux(t, a);
  276. // Convert radiant flux to luminous flux
  277. const double luminous_flux = physics::light::watts_to_lumens<double>(radiant_flux, efficiency);
  278. return luminous_flux;
  279. }
  280. } // namespace ecs