💿🐜 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.

529 lines
21 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 "entity/systems/astronomy.hpp"
  20. #include "astro/apparent-size.hpp"
  21. #include "entity/components/blackbody.hpp"
  22. #include "entity/components/transform.hpp"
  23. #include "entity/components/diffuse-reflector.hpp"
  24. #include "geom/intersection.hpp"
  25. #include "geom/cartesian.hpp"
  26. #include "color/color.hpp"
  27. #include "physics/orbit/orbit.hpp"
  28. #include "physics/time/ut1.hpp"
  29. #include "physics/light/photometry.hpp"
  30. #include "physics/light/luminosity.hpp"
  31. #include "physics/light/refraction.hpp"
  32. #include "physics/atmosphere.hpp"
  33. #include "geom/cartesian.hpp"
  34. #include "astro/apparent-size.hpp"
  35. #include "geom/solid-angle.hpp"
  36. #include "math/polynomial.hpp"
  37. #include <iostream>
  38. namespace entity {
  39. namespace system {
  40. template <class T>
  41. math::vector3<T> transmittance(T depth_r, T depth_m, T depth_o, const math::vector3<T>& beta_r, const math::vector3<T>& beta_m)
  42. {
  43. math::vector3<T> transmittance_r = beta_r * depth_r;
  44. math::vector3<T> transmittance_m = beta_m * 1.1 * depth_m;
  45. math::vector3<T> transmittance_o = {0, 0, 0};
  46. math::vector3<T> t = transmittance_r + transmittance_m + transmittance_o;
  47. t.x = std::exp(-t.x);
  48. t.y = std::exp(-t.y);
  49. t.z = std::exp(-t.z);
  50. return t;
  51. }
  52. astronomy::astronomy(entity::registry& registry):
  53. updatable(registry),
  54. time(0.0),
  55. time_scale(1.0),
  56. reference_entity(entt::null),
  57. observer_location{0, 0, 0},
  58. sun_light(nullptr),
  59. sky_light(nullptr),
  60. moon_light(nullptr),
  61. camera(nullptr),
  62. sky_pass(nullptr),
  63. exposure_offset(0.0),
  64. starlight_illuminance(0.0)
  65. {
  66. // Construct transformation which transforms coordinates from ENU to EUS
  67. enu_to_eus = math::transformation::se3<double>
  68. {
  69. {0, 0, 0},
  70. math::quaternion<double>::rotate_x(-math::half_pi<double>)
  71. };
  72. registry.on_construct<entity::component::celestial_body>().connect<&astronomy::on_celestial_body_construct>(this);
  73. registry.on_replace<entity::component::celestial_body>().connect<&astronomy::on_celestial_body_replace>(this);
  74. }
  75. astronomy::~astronomy()
  76. {
  77. registry.on_construct<entity::component::celestial_body>().disconnect<&astronomy::on_celestial_body_construct>(this);
  78. registry.on_replace<entity::component::celestial_body>().disconnect<&astronomy::on_celestial_body_replace>(this);
  79. }
  80. void astronomy::update(double t, double dt)
  81. {
  82. double total_illuminance = 0.0;
  83. double sky_light_illuminance = 0.0;
  84. // Add scaled timestep to current time
  85. set_time(time + dt * time_scale);
  86. // Abort if no reference body
  87. if (reference_entity == entt::null)
  88. return;
  89. // Abort if either reference body or orbit have not been set
  90. if (!registry.has<entity::component::orbit>(reference_entity) || !registry.has<entity::component::celestial_body>(reference_entity))
  91. return;
  92. const entity::component::orbit& reference_orbit = registry.get<entity::component::orbit>(reference_entity);
  93. const entity::component::celestial_body& reference_body = registry.get<entity::component::celestial_body>(reference_entity);
  94. math::transformation::se3<double> icrf_to_bci{{-reference_orbit.position}, math::quaternion<double>::identity};
  95. const double days_from_epoch = time;
  96. const double centuries_from_epoch = days_from_epoch / 36525.0;
  97. // Evaluate reference body orientation polynomials
  98. const double reference_body_pole_ra = math::polynomial::horner(reference_body.pole_ra.begin(), reference_body.pole_ra.end(), centuries_from_epoch);
  99. const double reference_body_pole_dec = math::polynomial::horner(reference_body.pole_dec.begin(), reference_body.pole_dec.end(), centuries_from_epoch);
  100. const double reference_body_prime_meridian = math::polynomial::horner(reference_body.prime_meridian.begin(), reference_body.prime_meridian.end(), days_from_epoch);
  101. // Construct transformation from the ICRF frame to the reference body BCBF frame
  102. icrf_to_bcbf = physics::orbit::frame::bci::to_bcbf
  103. (
  104. reference_body_pole_ra,
  105. reference_body_pole_dec,
  106. reference_body_prime_meridian
  107. );
  108. icrf_to_bcbf.t = icrf_to_bcbf.r * -reference_orbit.position;
  109. icrf_to_enu = icrf_to_bcbf * bcbf_to_enu;
  110. icrf_to_eus = icrf_to_enu * enu_to_eus;
  111. // Set the transform component translations of orbiting bodies to their topocentric positions
  112. registry.view<component::celestial_body, component::orbit, component::transform>().each(
  113. [&](entity::id entity_id, const auto& body, const auto& orbit, auto& transform)
  114. {
  115. // Skip reference entity
  116. if (entity_id == this->reference_entity)
  117. return;
  118. // Transform orbital Cartesian position (r) from the ICRF frame to the EUS frame
  119. const double3 r_eus = icrf_to_eus * orbit.position;
  120. // Evaluate body orientation polynomials
  121. const double body_pole_ra = math::polynomial::horner(body.pole_ra.begin(), body.pole_ra.end(), centuries_from_epoch);
  122. const double body_pole_dec = math::polynomial::horner(body.pole_dec.begin(), body.pole_dec.end(), centuries_from_epoch);
  123. const double body_prime_meridian = math::polynomial::horner(body.prime_meridian.begin(), body.prime_meridian.end(), days_from_epoch);
  124. // Determine body orientation in the ICRF frame
  125. math::quaternion<double> rotation_icrf = physics::orbit::frame::bcbf::to_bci
  126. (
  127. body_pole_ra,
  128. body_pole_dec,
  129. body_prime_meridian
  130. ).r;
  131. // Transform body orientation from the ICRF frame to the EUS frame.
  132. math::quaternion<double> rotation_eus = math::normalize(icrf_to_eus.r * rotation_icrf);
  133. // Update local transform
  134. if (orbit.parent != entt::null)
  135. {
  136. transform.local.translation = math::normalize(math::type_cast<float>(r_eus));
  137. transform.local.rotation = math::type_cast<float>(rotation_eus);
  138. transform.local.scale = {1.0f, 1.0f, 1.0f};
  139. }
  140. /*
  141. if (orbit.parent != entt::null)
  142. {
  143. // RA-DEC
  144. const double3 r_bci = icrf_to_bci * orbit.position;
  145. double3 r_bci_spherical = physics::orbit::frame::bci::spherical(r_bci);
  146. if (r_bci_spherical.z < 0.0)
  147. r_bci_spherical.z += math::two_pi<double>;
  148. const double dec = math::degrees(r_bci_spherical.y);
  149. const double ra = math::degrees(r_bci_spherical.z);
  150. // AZ-EL
  151. const double3 r_enu = icrf_to_enu * orbit.position;
  152. double3 r_enu_spherical = physics::orbit::frame::enu::spherical(r_enu);
  153. if (r_enu_spherical.z < 0.0)
  154. r_enu_spherical.z += math::two_pi<double>;
  155. const double el = math::degrees(r_enu_spherical.y);
  156. const double az = math::degrees(r_enu_spherical.z);
  157. std::cout << "t: " << this->time << "; ra: " << ra << "; dec: " << dec << std::endl;
  158. std::cout << "t: " << this->time << "; az: " << az << "; el: " << el << std::endl;
  159. }
  160. */
  161. });
  162. // Update blackbody lighting
  163. registry.view<component::celestial_body, component::orbit, component::blackbody>().each(
  164. [&](entity::id entity_id, const auto& body, const auto& orbit, const auto& blackbody)
  165. {
  166. // Calculate blackbody inertial basis
  167. //double3 blackbody_forward_icrf = math::normalize(reference_orbit.icrf_position - orbit.icrf_position);
  168. double3 blackbody_up_icrf = {0, 0, 1};
  169. // Transform blackbody ICRF position and basis into EUS frame
  170. double3 blackbody_position_eus = icrf_to_eus * orbit.position;
  171. double3 blackbody_position_enu = icrf_to_enu * orbit.position;
  172. double3 blackbody_forward_eus = math::normalize(-blackbody_position_eus);
  173. double3 blackbody_up_eus = icrf_to_eus.r * blackbody_up_icrf;
  174. // Calculate distance from observer to blackbody
  175. double blackbody_distance = math::length(blackbody_position_eus) - body.radius;
  176. // Calculate blackbody solid angle
  177. const double blackbody_angular_radius = astro::angular_radius(body.radius, blackbody_distance);
  178. const double blackbody_solid_angle = geom::solid_angle::cone(blackbody_angular_radius);
  179. // Calculate blackbody illuminance
  180. const double3 blackbody_illuminance = blackbody.luminance * blackbody_solid_angle;
  181. // Init atmospheric transmittance
  182. double3 atmospheric_transmittance = {1.0, 1.0, 1.0};
  183. // Get atmosphere component of reference body (if any)
  184. if (this->registry.has<entity::component::atmosphere>(reference_entity))
  185. {
  186. const entity::component::atmosphere& reference_atmosphere = registry.get<entity::component::atmosphere>(reference_entity);
  187. // Altitude of observer in meters
  188. geom::ray<double> sample_ray;
  189. sample_ray.origin = {0, reference_body.radius + observer_location[0], 0};
  190. sample_ray.direction = math::normalize(blackbody_position_eus);
  191. geom::sphere<double> exosphere;
  192. exosphere.center = {0, 0, 0};
  193. exosphere.radius = reference_body.radius + reference_atmosphere.exosphere_altitude;
  194. auto intersection_result = geom::ray_sphere_intersection(sample_ray, exosphere);
  195. if (std::get<0>(intersection_result))
  196. {
  197. double3 sample_start = sample_ray.origin;
  198. double3 sample_end = sample_ray.extrapolate(std::get<2>(intersection_result));
  199. double optical_depth_r = physics::atmosphere::optical_depth(sample_start, sample_end, reference_body.radius, reference_atmosphere.rayleigh_scale_height, 32);
  200. double optical_depth_k = physics::atmosphere::optical_depth(sample_start, sample_end, reference_body.radius, reference_atmosphere.mie_scale_height, 32);
  201. double optical_depth_o = 0.0;
  202. atmospheric_transmittance = transmittance(optical_depth_r, optical_depth_k, optical_depth_o, reference_atmosphere.rayleigh_scattering, reference_atmosphere.mie_scattering);
  203. }
  204. // Add airglow to sky light illuminance
  205. sky_light_illuminance += reference_atmosphere.airglow;
  206. }
  207. // Blackbody illuminance transmitted through the atmosphere
  208. const double3 transmitted_blackbody_illuminance = blackbody_illuminance * atmospheric_transmittance;
  209. // Add atmosphere-transmitted blackbody illuminance to total illuminance
  210. total_illuminance += (transmitted_blackbody_illuminance.x + transmitted_blackbody_illuminance.y + transmitted_blackbody_illuminance.z) / 3.0;
  211. // Update sun light
  212. if (sun_light != nullptr)
  213. {
  214. sun_light->set_translation(math::normalize(math::type_cast<float>(blackbody_position_eus)));
  215. sun_light->set_rotation
  216. (
  217. math::look_rotation
  218. (
  219. math::type_cast<float>(blackbody_forward_eus),
  220. math::type_cast<float>(blackbody_up_eus)
  221. )
  222. );
  223. sun_light->set_color(math::type_cast<float>(transmitted_blackbody_illuminance));
  224. }
  225. // Update sky light
  226. if (sky_light != nullptr)
  227. {
  228. // Calculate sky illuminance
  229. double3 blackbody_position_enu_spherical = physics::orbit::frame::enu::spherical(blackbody_position_enu);
  230. const double sky_illuminance = 25000.0 * std::max<double>(0.0, std::sin(blackbody_position_enu_spherical.y));
  231. // Add sky illuminance to sky light illuminance
  232. sky_light_illuminance += sky_illuminance;
  233. // Add starlight illuminance to sky light illuminance
  234. sky_light_illuminance += starlight_illuminance;
  235. // Add sky light illuminance to total illuminance
  236. total_illuminance += sky_light_illuminance;
  237. //std::cout << "sky light illum: " << sky_light_illuminance << std::endl;
  238. // Update sky light
  239. sky_light->set_color(float3{1.0f, 1.0f, 1.0f} * static_cast<float>(sky_light_illuminance));
  240. }
  241. // Upload blackbody params to sky pass
  242. if (this->sky_pass)
  243. {
  244. this->sky_pass->set_sun_position(math::type_cast<float>(blackbody_position_eus));
  245. this->sky_pass->set_sun_luminance(math::type_cast<float>(blackbody.luminance));
  246. this->sky_pass->set_sun_illuminance(math::type_cast<float>(blackbody_illuminance));
  247. this->sky_pass->set_sun_angular_radius(static_cast<float>(blackbody_angular_radius));
  248. }
  249. const double3& blackbody_icrf_position = orbit.position;
  250. // Update diffuse reflectors
  251. this->registry.view<component::celestial_body, component::orbit, component::diffuse_reflector, component::transform>().each(
  252. [&](entity::id entity_id, const auto& reflector_body, const auto& reflector_orbit, const auto& reflector, const auto& transform)
  253. {
  254. // Calculate distance to blackbody and direction of incoming light
  255. double3 blackbody_light_direction_icrf = reflector_orbit.position - blackbody_icrf_position;
  256. double blackbody_distance = math::length(blackbody_light_direction_icrf);
  257. blackbody_light_direction_icrf = blackbody_light_direction_icrf / blackbody_distance;
  258. // Transform blackbody light direction from the ICRF frame to the EUS frame
  259. double3 blackbody_light_direction_eus = icrf_to_eus.r * blackbody_light_direction_icrf;
  260. // Calculate blackbody solid angle
  261. const double blackbody_angular_radius = astro::angular_radius(body.radius, blackbody_distance);
  262. const double blackbody_solid_angle = geom::solid_angle::cone(blackbody_angular_radius);
  263. // Calculate blackbody illuminance
  264. double3 view_direction_icrf = reflector_orbit.position - reference_orbit.position;
  265. const double reflector_distance = math::length(view_direction_icrf);
  266. view_direction_icrf = view_direction_icrf / reflector_distance;
  267. const double3 sunlight_illuminance = blackbody.luminance * blackbody_solid_angle;
  268. const double reflector_angular_radius = astro::angular_radius(reflector_body.radius, reflector_distance);
  269. const double reflector_solid_angle = geom::solid_angle::cone(reflector_angular_radius);
  270. const double reflector_phase_factor = dot(view_direction_icrf, blackbody_light_direction_icrf) * 0.5 + 0.5;
  271. const double3 planet_luminance = (sunlight_illuminance * reference_body.albedo) / math::pi<double>;
  272. const double planet_angular_radius = astro::angular_radius(reference_body.radius, reflector_distance);
  273. const double planet_solid_angle = geom::solid_angle::cone(planet_angular_radius);
  274. const double planet_phase_factor = math::dot(-view_direction_icrf, math::normalize(reference_orbit.position - blackbody_icrf_position)) * 0.5 + 0.5;
  275. const double3 planetlight_illuminance = planet_luminance * planet_solid_angle * planet_phase_factor;
  276. double3 planetlight_direction_eus = math::normalize(icrf_to_eus.r * view_direction_icrf);
  277. const double3 reflected_sunlight_luminance = (sunlight_illuminance * reflector.albedo) / math::pi<double>;
  278. const double3 reflected_sunlight_illuminance = reflected_sunlight_luminance * reflector_solid_angle * reflector_phase_factor;
  279. const double3 reflected_planetlight_luminance = (planetlight_illuminance * reflector.albedo) / math::pi<double>;
  280. const double3 reflected_planetlight_illuminance = reflected_planetlight_luminance * reflector_solid_angle;
  281. /*
  282. std::cout << "reflected sunlight illuminance: " << reflected_sunlight_illuminance << std::endl;
  283. std::cout << "planetlight illuminance: " << planetlight_illuminance << std::endl;
  284. std::cout << "planet luminance: " << planet_luminance << std::endl;
  285. std::cout << "reflected planetlight luminance: " << reflected_planetlight_luminance << std::endl;
  286. std::cout << "reflected planetlight illuminance: " << reflected_planetlight_illuminance << std::endl;
  287. std::cout << "reflector phase: " << reflector_phase_factor << std::endl;
  288. std::cout << "planet phase: " << planet_phase_factor << std::endl;
  289. */
  290. if (this->sky_pass)
  291. {
  292. this->sky_pass->set_moon_position(transform.local.translation);
  293. this->sky_pass->set_moon_rotation(transform.local.rotation);
  294. this->sky_pass->set_moon_angular_radius(static_cast<float>(reflector_angular_radius));
  295. this->sky_pass->set_moon_sunlight_direction(math::type_cast<float>(blackbody_light_direction_eus));
  296. this->sky_pass->set_moon_sunlight_illuminance(math::type_cast<float>(sunlight_illuminance));
  297. this->sky_pass->set_moon_planetlight_direction(math::type_cast<float>(planetlight_direction_eus));
  298. this->sky_pass->set_moon_planetlight_illuminance(math::type_cast<float>(planetlight_illuminance));
  299. }
  300. if (this->moon_light)
  301. {
  302. float3 reflector_up_eus = math::type_cast<float>(icrf_to_eus.r * double3{0, 0, 1});
  303. double3 reflected_illuminance = reflected_sunlight_illuminance + reflected_planetlight_illuminance;
  304. //reflected_illuminance *= std::max<double>(0.0, std::sin(transform.local.translation.y));
  305. total_illuminance += (reflected_illuminance.x + reflected_illuminance.y + reflected_illuminance.z) / 3.0;
  306. this->moon_light->set_color(math::type_cast<float>(reflected_illuminance));
  307. this->moon_light->set_rotation
  308. (
  309. math::look_rotation
  310. (
  311. math::normalize(-transform.local.translation),
  312. reflector_up_eus
  313. )
  314. );
  315. }
  316. /*
  317. std::cout << "moon: sun solid angle: " << blackbody_solid_angle << std::endl;
  318. std::cout << "moon: sun illuminance: " << blackbody_illuminance << std::endl;
  319. std::cout << "moon: moon luminance: " << reflector_luminance << std::endl;
  320. std::cout << "sun brightness: " << sun_brightness << std::endl;
  321. std::cout << "vega brightness: " << vega_brightness << std::endl;
  322. std::cout << "earth: moon distance: " << reflector_distance << std::endl;
  323. std::cout << "earth: moon solid angle: " << reflector_solid_angle << std::endl;
  324. std::cout << "earth: moon phase: " << reflector_phase << std::endl;
  325. std::cout << "earth: moon phase angle: " << math::degrees(reflector_phase_angle) << std::endl;
  326. std::cout << "earth: moon illum %: " << reflector_illumination_factor * 100.0 << std::endl;
  327. std::cout << "earth: moon illuminance: " << reflector_illuminance << std::endl;
  328. std::cout << "earth: moon phase-modulated illuminance: " << reflector_illuminance * reflector_illumination_factor << std::endl;
  329. */
  330. });
  331. });
  332. // Update sky pass topocentric frame
  333. if (sky_pass != nullptr)
  334. {
  335. // Upload topocentric frame to sky pass
  336. sky_pass->set_icrf_to_eus
  337. (
  338. math::transformation::se3<float>
  339. {
  340. math::type_cast<float>(icrf_to_eus.t),
  341. math::type_cast<float>(icrf_to_eus.r)
  342. }
  343. );
  344. // Upload observer altitude to sky pass
  345. sky_pass->set_observer_altitude(observer_location[0]);
  346. // Upload atmosphere params to sky pass
  347. if (registry.has<entity::component::atmosphere>(reference_entity))
  348. {
  349. const entity::component::atmosphere& reference_atmosphere = registry.get<entity::component::atmosphere>(reference_entity);
  350. sky_pass->set_scale_heights(reference_atmosphere.rayleigh_scale_height, reference_atmosphere.mie_scale_height);
  351. sky_pass->set_scattering_coefficients(math::type_cast<float>(reference_atmosphere.rayleigh_scattering), math::type_cast<float>(reference_atmosphere.mie_scattering));
  352. sky_pass->set_mie_anisotropy(reference_atmosphere.mie_anisotropy);
  353. sky_pass->set_atmosphere_radii(reference_body.radius, reference_body.radius + reference_atmosphere.exosphere_altitude);
  354. }
  355. }
  356. // Auto-exposure
  357. if (camera)
  358. {
  359. const double calibration = 250.0;
  360. const double ev100 = std::log2((total_illuminance * 100.0) / calibration);
  361. //std::cout << "LUX: " << total_illuminance << std::endl;
  362. //std::cout << "EV100: " << ev100 << std::endl;
  363. //camera->set_exposure(exposure_offset);
  364. }
  365. }
  366. void astronomy::set_time(double time)
  367. {
  368. this->time = time;
  369. }
  370. void astronomy::set_time_scale(double scale)
  371. {
  372. time_scale = scale;
  373. }
  374. void astronomy::set_reference_body(entity::id entity_id)
  375. {
  376. reference_entity = entity_id;
  377. update_bcbf_to_enu();
  378. }
  379. void astronomy::set_observer_location(const double3& location)
  380. {
  381. observer_location = location;
  382. update_bcbf_to_enu();
  383. }
  384. void astronomy::set_sun_light(scene::directional_light* light)
  385. {
  386. sun_light = light;
  387. }
  388. void astronomy::set_sky_light(scene::ambient_light* light)
  389. {
  390. sky_light = light;
  391. }
  392. void astronomy::set_moon_light(scene::directional_light* light)
  393. {
  394. moon_light = light;
  395. }
  396. void astronomy::set_camera(scene::camera* camera)
  397. {
  398. this->camera = camera;
  399. }
  400. void astronomy::set_exposure_offset(float offset)
  401. {
  402. exposure_offset = offset;
  403. }
  404. void astronomy::set_starlight_illuminance(double illuminance)
  405. {
  406. starlight_illuminance = illuminance;
  407. }
  408. void astronomy::set_sky_pass(::render::sky_pass* pass)
  409. {
  410. this->sky_pass = pass;
  411. }
  412. void astronomy::on_celestial_body_construct(entity::registry& registry, entity::id entity_id, entity::component::celestial_body& celestial_body)
  413. {
  414. if (entity_id == reference_entity)
  415. update_bcbf_to_enu();
  416. }
  417. void astronomy::on_celestial_body_replace(entity::registry& registry, entity::id entity_id, entity::component::celestial_body& celestial_body)
  418. {
  419. if (entity_id == reference_entity)
  420. update_bcbf_to_enu();
  421. }
  422. void astronomy::update_bcbf_to_enu()
  423. {
  424. double radial_distance = observer_location[0];
  425. if (reference_entity)
  426. {
  427. if (registry.has<entity::component::celestial_body>(reference_entity))
  428. radial_distance += registry.get<entity::component::celestial_body>(reference_entity).radius;
  429. }
  430. // Construct reference frame which transforms coordinates from a BCBF frame to a horizontal frame
  431. bcbf_to_enu = physics::orbit::frame::bcbf::to_enu
  432. (
  433. radial_distance,
  434. observer_location[1],
  435. observer_location[2]
  436. );
  437. }
  438. } // namespace system
  439. } // namespace entity