From 4a43fe08c0479aa4e66b30711eade7ba144eec89 Mon Sep 17 00:00:00 2001 From: "Pedro J. Garcia" Date: Sun, 7 Jul 2024 16:27:55 -0300 Subject: [PATCH 01/15] Update rotation models from the 2015 IAU report --- src/celephem/customrotation.cpp | 427 +++++++++++++----------------- src/celephem/customrotation.gperf | 1 - 2 files changed, 185 insertions(+), 243 deletions(-) diff --git a/src/celephem/customrotation.cpp b/src/celephem/customrotation.cpp index 4c7455f2bd..a7e83dfb61 100644 --- a/src/celephem/customrotation.cpp +++ b/src/celephem/customrotation.cpp @@ -246,6 +246,100 @@ class IAUPrecessingRotationModel : public IAURotationModel }; +class IAUMercuryRotationModel : public IAURotationModel +{ +public: + IAUMercuryRotationModel() : IAURotationModel(360.0 / 6.1385108) {} + + void pole(double d, double& ra, double &dec) const override + { + double T = d / 36525.0; + clamp_centuries(T); + ra = 281.0103 - 0.0328 * T; + dec = 61.4155 - 0.0049 * T; + } + + double meridian(double d) const override + { + double M1 = math::degToRad(174.7910857 + 4.092335 * d); + double M2 = math::degToRad(349.5821714 + 8.184670 * d); + double M3 = math::degToRad(164.3732571 + 12.277005 * d); + double M4 = math::degToRad(339.1643429 + 16.369340 * d); + double M5 = math::degToRad(153.9554286 + 20.461675 * d); + return (329.5988 + 6.1385108 * d + + 0.01067257 * sin(M1) + - 0.00112309 * sin(M2) + - 0.00011040 * sin(M3) + - 0.00002539 * sin(M4) + - 0.00000571 * sin(M5)); + } +}; + + +class IAUMarsRotationModel : public IAURotationModel +{ +public: + IAUMarsRotationModel() : IAURotationModel(360.0 / 350.891982443297) {} + + void pole(double d, double& ra, double &dec) const override + { + double T = d / 36525.0; + clamp_centuries(T); + ra = 317.269202 - 0.10927547 * T + + 0.000068 * sin(math::degToRad(198.991226 + 19139.4819985 * T)) + + 0.000238 * sin(math::degToRad(226.292679 + 38280.8511281 * T)) + + 0.000052 * sin(math::degToRad(249.663391 + 57420.7251593 * T)) + + 0.000009 * sin(math::degToRad(266.183510 + 76560.6367950 * T)) + + 0.419057 * sin(math::degToRad(79.398797 + 0.5042615 * T)); + dec = 54.432516 - 0.05827105 * T + + 0.000051 * cos(math::degToRad(122.433576 + 19139.9407476 * T)) + + 0.000141 * cos(math::degToRad(43.058401 + 38280.8753272 * T)) + + 0.000031 * cos(math::degToRad(57.663379 + 57420.7517205 * T)) + + 0.000005 * cos(math::degToRad(79.476401 + 76560.6495004 * T)) + + 1.591274 * cos(math::degToRad(166.325722 + 0.5042615 * T)); + } + + double meridian(double d) const override + { + double T = d / 36525.0; + return (176.049863 + 350.891982443297 * d + + 0.000145 * sin(math::degToRad(129.071773 + 19140.0328244 * T)) + + 0.000157 * sin(math::degToRad(36.352167 + 38281.0473591 * T)) + + 0.000040 * sin(math::degToRad(56.668646 + 57420.9295360 * T)) + + 0.000001 * sin(math::degToRad(67.364003 + 76560.2552215 * T)) + + 0.000001 * sin(math::degToRad(104.792680 + 95700.4387578 * T)) + + 0.584542 * sin(math::degToRad(95.391654 + 0.5042615 * T))); + } +}; + + +class IAUJupiterRotationModel : public IAURotationModel +{ +public: + IAUJupiterRotationModel() : IAURotationModel(360.0 / 870.5360000) {} + + void pole(double d, double& ra, double &dec) const override + { + double T = d / 36525.0; + double Ja = math::degToRad(99.360714 + 4850.4046 * T); + double Jb = math::degToRad(175.895369 + 1191.9605 * T); + double Jc = math::degToRad(300.323162 + 262.5475 * T); + double Jd = math::degToRad(114.012305 + 6070.2476 * T); + double Je = math::degToRad(49.511251 + 64.3000 * T); + clamp_centuries(T); + ra = 268.056595 - 0.006499 * T + 0.000117 * sin(Ja) + 0.000938 * sin(Jb) + + 0.001432 * sin(Jc) + 0.000030 * sin(Jd) + 0.002150 * sin(Je); + dec = 64.495303 + 0.002413 * T + 0.000050 * cos(Ja) + 0.000404 * cos(Jb) + + 0.000617 * cos(Jc) - 0.000013 * cos(Jd) + 0.000926 * cos(Je); + } + + double meridian(double d) const override + { + return 284.95 + 870.5360000 * d; + } +}; + + class IAUNeptuneRotationModel : public IAURotationModel { public: @@ -355,33 +449,44 @@ class IAULunarRotationModel : public IAURotationModel }; -// Rotations of Martian, Jovian, and Uranian satellites from IAU/IAG Working group -// on Cartographic Coordinates and Rotational Elements (Corrected for known errata -// as of 17 Feb 2006) -// See: http://astrogeology.usgs.gov/Projects/WGCCRE/constants/iau2000_table2.html +// Rotations of Martian, Jovian, Saturnian, and Uranian satellites from IAU Working Group +// on Cartographic Coordinates and Rotational Elements (2015 report) +// See: https://astropedia.astrogeology.usgs.gov/download/Docs/WGCCRE/WGCCRE2015reprint.pdf class IAUPhobosRotationModel : public IAURotationModel { public: - IAUPhobosRotationModel() : IAURotationModel(360.0 / 1128.8445850) {} + IAUPhobosRotationModel() : IAURotationModel(360.0 / 1128.84475928) {} void pole(double t, double& ra, double& dec) const override { double T = t / 36525.0; - double M1 = math::degToRad(169.51 - 0.04357640 * t); + double M1 = math::degToRad(190.72646643 + 15917.10818695 * T); + double M2 = math::degToRad(21.46892470 + 31834.27934054 * T); + double M3 = math::degToRad(332.86082793 + 19139.89694742 * T); + double M4 = math::degToRad(394.93256437 + 38280.79631835 * T); clamp_centuries(T); - ra = 317.68 - 0.108 * T + 1.79 * std::sin(M1); - dec = 52.90 - 0.061 * T - 1.08 * std::cos(M1); + ra = 317.67071657 - 0.10844326 * T + - 1.78428399 * std::sin(M1) + 0.02212824 * std::sin(M2) + - 0.01028251 * std::sin(M3) - 0.00475595 * std::sin(M4); + dec = 52.88627266 - 0.06134706 * T + - 1.07516537 * std::cos(M1) + 0.00668626 * std::cos(M2) + - 0.00648740 * std::cos(M3) + 0.00281576 * std::cos(M4); } + // From correction to the 2015 report: https://ui.adsabs.harvard.edu/abs/2019CeMDA.131...61A/abstract double meridian(double t) const override { - // Note: negative coefficient of T^2 term for meridian angle indicates faster - // rotation as Phobos's orbit evolves inward toward Mars double T = t / 36525.0; - double M1 = math::degToRad(169.51 - 0.04357640 * t); - double M2 = math::degToRad(192.93 + 1128.4096700 * t + 8.864 * T * T); - return 35.06 + 1128.8445850 * t + 8.864 * T * T - 1.42 * std::sin(M1) - 0.78 * std::sin(M2); + double M1 = math::degToRad(190.72646643 + 15917.10818695 * T); + double M2 = math::degToRad(21.46892470 + 31834.27934054 * T); + double M3 = math::degToRad(332.86082793 + 19139.89694742 * T); + double M4 = math::degToRad(394.93256437 + 38280.79631835 * T); + double M5 = math::degToRad(189.63271560 + 41215158.18420050 * T + 12.71192322 * T * T); + return (35.18774440 + 1128.84475928 * t + 12.72192797 * T * T + + 1.42421769 * std::sin(M1) - 0.02273783 * std::sin(M2) + + 0.00410711 * std::sin(M3) + 0.00631964 * std::sin(M4) + - 1.143 * std::sin(M5)); } }; @@ -389,24 +494,39 @@ class IAUPhobosRotationModel : public IAURotationModel class IAUDeimosRotationModel : public IAURotationModel { public: - IAUDeimosRotationModel() : IAURotationModel(360.0 / 285.1618970) {} + IAUDeimosRotationModel() : IAURotationModel(360.0 / 285.16188899) {} void pole(double t, double& ra, double& dec) const override { double T = t / 36525.0; - double M3 = math::degToRad(53.47 - 0.0181510 * t); + double M6 = math::degToRad(121.46893664 + 660.22803474 * T); + double M7 = math::degToRad(231.05028581 + 660.99123540 * T); + double M8 = math::degToRad(251.37314025 + 1320.50145245 * T); + double M9 = math::degToRad(217.98635955 + 38279.96125550 * T); + double M10 = math::degToRad(196.19729402 + 19139.83628608 * T); clamp_centuries(T); - ra = 316.65 - 0.108 * T + 2.98 * std::sin(M3); - dec = 53.52 - 0.061 * T - 1.78 * std::cos(M3); + ra = 316.65705808 - 0.10518014 * T + + 3.09217726 * std::sin(M6) + 0.22980637 * std::sin(M7) + + 0.06418655 * std::sin(M8) + 0.02533537 * std::sin(M9) + + 0.00778695 * std::sin(M10); + dec = 53.50992033 - 0.05979094 * T + + 1.83936004 * std::cos(M6) + 0.14325320 * std::cos(M7) + + 0.01911409 * std::cos(M8) - 0.01482590 * std::cos(M9) + + 0.00192430 * std::cos(M10); } double meridian(double t) const override { - // Note: positive coefficient of T^2 term for meridian angle indicates slowing - // rotation as Deimos's orbit evolves outward from Mars double T = t / 36525.0; - double M3 = math::degToRad(53.47 - 0.0181510 * t); - return 79.41 + 285.1618970 * t + 0.520 * T * T - 2.58 * std::sin(M3) + 0.19 * std::cos(M3); + double M6 = math::degToRad(121.46893664 + 660.22803474 * T); + double M7 = math::degToRad(231.05028581 + 660.99123540 * T); + double M8 = math::degToRad(251.37314025 + 1320.50145245 * T); + double M9 = math::degToRad(217.98635955 + 38279.96125550 * T); + double M10 = math::degToRad(196.19729402 + 19139.83628608 * T); + return (79.39932954 + 285.16188899 * t + - 2.73954829 * std::sin(M6) - 0.39968606 * std::sin(M7) + - 0.06563259 * std::sin(M8) - 0.02912940 * std::sin(M9) + + 0.01699160 * std::sin(M10)); } }; @@ -567,20 +687,7 @@ class IAUCallistoRotationModel : public IAURotationModel }; -/* -S1 = 353.32 + 75706.7 * T -S2 = 28.72 + 75706.7 * T -S3 = 177.40 - 36505.5 * T -S4 = 300.00 - 7225.9 * T -S5 = 53.59 - 8968.6 * T -S6 = 143.38 - 10553.5 * T -S7 = 345.20 - 1016.3 * T -S8 = 29.80 - 52.1 * T -S9 = 316.45 + 506.2 * T -*/ - -// Rotations of Saturnian satellites from Seidelmann, _Explanatory Supplement to the -// Astronomical Almanac_ (1992). +/****** Satellites of Saturn ******/ class IAUMimasRotationModel : public IAURotationModel { @@ -600,28 +707,8 @@ class IAUMimasRotationModel : public IAURotationModel { double T = t / 36525.0; double S3 = math::degToRad(177.40 - 36505.5 * T); - double S9 = math::degToRad(316.45 + 506.2 * T); - return 337.46 + 381.9945550 * t - 13.48 * std::sin(S3) - 44.85 * std::sin(S9); - } -}; - - -class IAUEnceladusRotationModel : public IAURotationModel -{ -public: - IAUEnceladusRotationModel() : IAURotationModel(360.0 / 262.7318996) {} - - void pole(double t, double& ra, double& dec) const override - { - double T = t / 36525.0; - clamp_centuries(T); - ra = 40.66 - 0.036 * T; - dec = 83.52 - 0.004 * T; - } - - double meridian(double t) const override - { - return 2.82 + 262.7318996 * t; + double S5 = math::degToRad(316.45 + 506.2 * T); + return 333.46 + 381.9945550 * t - 13.48 * std::sin(S3) - 44.85 * std::sin(S5); } }; @@ -636,7 +723,7 @@ class IAUTethysRotationModel : public IAURotationModel double T = t / 36525.0; double S4 = math::degToRad(300.00 - 7225.9 * T); clamp_centuries(T); - ra = 40.66 - 0.036 * T - 9.66 * sin(S4); + ra = 40.66 - 0.036 * T + 9.66 * sin(S4); dec = 83.52 - 0.004 * T - 1.09 * cos(S4); } @@ -644,94 +731,8 @@ class IAUTethysRotationModel : public IAURotationModel { double T = t / 36525.0; double S4 = math::degToRad(300.00 - 7225.9 * T); - double S9 = math::degToRad(316.45 + 506.2 * T); - return 10.45 + 190.6979085 * t - 9.60 * std::sin(S4) + 2.23 * std::sin(S9); - } -}; - - -class IAUTelestoRotationModel : public IAURotationModel -{ -public: - IAUTelestoRotationModel() : IAURotationModel(360.0 / 190.6979330) {} - - void pole(double t, double& ra, double& dec) const override - { - double T = t / 36525.0; - clamp_centuries(T); - ra = 50.50 - 0.036 * T; - dec = 84.06 - 0.004 * T; - } - - double meridian(double t) const override - { - return 56.88 + 190.6979330 * t; - } -}; - - -class IAUCalypsoRotationModel : public IAURotationModel -{ -public: - IAUCalypsoRotationModel() : IAURotationModel(360.0 / 190.6742373) {} - - void pole(double t, double& ra, double& dec) const override - { - double T = t / 36525.0; - double S5 = math::degToRad(53.59 - 8968.6 * T); - clamp_centuries(T); - ra = 40.58 - 0.036 * T - 13.943 * std::sin(S5) - 1.686 * std::sin(2.0 * S5); - dec = 83.43 - 0.004 * T - 1.572 * std::cos(S5) + 0.095 * std::cos(2.0 * S5); - } - - double meridian(double t) const override - { - double T = t / 36525.0; - double S5 = math::degToRad(53.59 - 8968.6 * T); - return 149.36 + 190.6742373 * t - 13.849 * std::sin(S5) + 1.685 * std::sin(2.0 * S5); - } -}; - - -class IAUDioneRotationModel : public IAURotationModel -{ -public: - IAUDioneRotationModel() : IAURotationModel(360.0 / 131.5349316) {} - - void pole(double t, double& ra, double& dec) const override - { - double T = t / 36525.0; - clamp_centuries(T); - ra = 40.66 - 0.036 * T; - dec = 83.52 - 0.004 * T; - } - - double meridian(double t) const override - { - return 357.00 + 131.5349316 * t; - } -}; - - -class IAUHeleneRotationModel : public IAURotationModel -{ -public: - IAUHeleneRotationModel() : IAURotationModel(360.0 / 131.6174056) {} - - void pole(double t, double& ra, double& dec) const override - { - double T = t / 36525.0; - double S6 = math::degToRad(143.38 - 10553.5 * T); - clamp_centuries(T); - ra = 40.58 - 0.036 * T + 1.662 * std::sin(S6) + 0.024 * std::sin(2.0 * S6); - dec = 83.52 - 0.004 * T - 0.187 * std::cos(S6) + 0.095 * std::cos(2.0 * S6); - } - - double meridian(double t) const override - { - double T = t / 36525.0; - double S6 = math::degToRad(143.38 - 10553.5 * T); - return 245.39 + 131.6174056 * t - 1.651 * sin(S6) + 0.024 * sin(2.0 * S6); + double S5 = math::degToRad(316.45 + 506.2 * T); + return 8.95 + 190.6979085 * t - 9.60 * std::sin(S4) + 2.23 * std::sin(S5); } }; @@ -744,80 +745,17 @@ class IAURheaRotationModel : public IAURotationModel void pole(double t, double& ra, double& dec) const override { double T = t / 36525.0; - double S7 = math::degToRad(345.20 - 1016.3 * T); - clamp_centuries(T); - ra = 40.38 - 0.036 * T + 3.10 * sin(S7); - dec = 83.55 - 0.004 * T - 0.35 * cos(S7); - } - - double meridian(double t) const override - { - double T = t / 36525.0; - double S7 = math::degToRad(345.20 - 1016.3 * T); - return 235.16 + 79.6900478 * t - 1.651 - 3.08 * sin(S7); - } -}; - - -class IAUTitanRotationModel : public IAURotationModel -{ -public: - IAUTitanRotationModel() : IAURotationModel(360.0 / 22.5769768) {} - - void pole(double t, double& ra, double& dec) const override - { - double T = t / 36525.0; - double S8 = math::degToRad(29.80 - 52.1 * T); - clamp_centuries(T); - ra = 36.41 - 0.036 * T + 2.66 * sin(S8); - dec = 83.94 - 0.004 * T - 0.30 * cos(S8); - } - - double meridian(double t) const override - { - double T = t / 36525.0; - double S8 = math::degToRad(29.80 - 52.1 * T); - return 189.64 + 22.5769768 * t - 2.64 * sin(S8); - } -}; - - -class IAUIapetusRotationModel : public IAURotationModel -{ -public: - IAUIapetusRotationModel() : IAURotationModel(360.0 / 4.5379572) {} - - void pole(double t, double& ra, double& dec) const override - { - double T = t / 36525.0; + double S6 = math::degToRad(345.20 - 1016.3 * T); clamp_centuries(T); - ra = 318.16 - 3.949 * T; - dec = 75.03 - 1.142 * T; + ra = 40.38 - 0.036 * T + 3.10 * sin(S6); + dec = 83.55 - 0.004 * T - 0.35 * cos(S6); } double meridian(double t) const override - { - return 350.20 + 4.5379572 * t; - } -}; - - -class IAUPhoebeRotationModel : public IAURotationModel -{ -public: - IAUPhoebeRotationModel() : IAURotationModel(360.0 / 22.5769768) {} - - void pole(double t, double& ra, double& dec) const override { double T = t / 36525.0; - clamp_centuries(T); - ra = 355.16; - dec = 68.70 - 1.143 * T; - } - - double meridian(double t) const override - { - return 304.70 + 930.8338720 * t; + double S6 = math::degToRad(345.20 - 1016.3 * T); + return 235.16 + 79.6900478 * t - 3.08 * sin(S6); } }; @@ -941,7 +879,6 @@ enum class CustomRotationModelType EarthP03lp = 0, IAUMercury, IAUVenus, - IAUEarth, IAUMars, IAUJupiter, IAUSaturn, @@ -1023,25 +960,15 @@ CustomRotationsManager::createModel(CustomRotationModelType type) // IAU rotation elements for the planets case CustomRotationModelType::IAUMercury: - return std::make_shared(281.01, -0.033, - 61.45, -0.005, - 329.548, 6.1385025); + return std::make_shared(); case CustomRotationModelType::IAUVenus: return std::make_shared(272.76, 0.0, 67.16, 0.0, 160.20, -1.4813688); - case CustomRotationModelType::IAUEarth: - return std::make_shared(0.0, -0.641, - 90.0, -0.557, - 190.147, 360.9856235); case CustomRotationModelType::IAUMars: - return std::make_shared(317.68143, -0.1061, - 52.88650, -0.0609, - 176.630, 350.89198226); + return std::make_shared(); case CustomRotationModelType::IAUJupiter: - return std::make_shared(268.05, -0.009, - 64.49, -0.003, - 284.95, 870.5366420); + return std::make_shared(); case CustomRotationModelType::IAUSaturn: return std::make_shared(40.589, -0.036, 83.537, -0.004, @@ -1095,39 +1022,55 @@ CustomRotationsManager::createModel(CustomRotationModelType type) 83.5, -0.004, 48.8, 626.0440000); case CustomRotationModelType::IAUAtlas: - return std::make_shared(40.6, -0.036, - 83.5, -0.004, + return std::make_shared(40.58, -0.036, + 83.53, -0.004, 137.88, 598.3060000); case CustomRotationModelType::IAUPrometheus: - return std::make_shared(40.6, -0.036, - 83.5, -0.004, + return std::make_shared(40.58, -0.036, + 83.53, -0.004, 296.14, 587.289000); case CustomRotationModelType::IAUPandora: - return std::make_shared(40.6, -0.036, - 83.5, -0.004, + return std::make_shared(40.58, -0.036, + 83.53, -0.004, 162.92, 572.7891000); case CustomRotationModelType::IAUMimas: return std::make_shared(); case CustomRotationModelType::IAUEnceladus: - return std::make_shared(); + return std::make_shared(40.66, -0.036, + 83.52, -0.004, + 6.32, 262.7318996); case CustomRotationModelType::IAUTethys: return std::make_shared(); case CustomRotationModelType::IAUTelesto: - return std::make_shared(); + return std::make_shared(50.51, -0.036, + 84.06, -0.004, + 56.88, 190.6979332); case CustomRotationModelType::IAUCalypso: - return std::make_shared(); + return std::make_shared(36.41, -0.036, + 85.04, -0.004, + 153.51, 190.6742373); case CustomRotationModelType::IAUDione: - return std::make_shared(); + return std::make_shared(40.66, -0.036, + 83.52, -0.004, + 357.6, 131.5349316); case CustomRotationModelType::IAUHelene: - return std::make_shared(); + return std::make_shared(40.85, -0.036, + 83.34, -0.004, + 245.12, 131.6174056); case CustomRotationModelType::IAURhea: return std::make_shared(); case CustomRotationModelType::IAUTitan: - return std::make_shared(); + return std::make_shared(39.4827, 0.0, + 83.4279, 0.0, + 186.5855, 22.5769768); case CustomRotationModelType::IAUIapetus: - return std::make_shared(); + return std::make_shared(318.16, -3.949, + 75.03, -1.143, + 355.2, 4.5379572); case CustomRotationModelType::IAUPhoebe: - return std::make_shared(); + return std::make_shared(356.90, 0.0, + 77.80, 0.0, + 178.58, 931.639); // IAU rotation elements for satellites of Uranus case CustomRotationModelType::IAUMiranda: diff --git a/src/celephem/customrotation.gperf b/src/celephem/customrotation.gperf index 03e66259a6..22cdf583ba 100644 --- a/src/celephem/customrotation.gperf +++ b/src/celephem/customrotation.gperf @@ -10,7 +10,6 @@ struct CustomRotationEntry { const char* name; CustomRotationModelType modelType "earth-p03lp", CustomRotationModelType::EarthP03lp "iau-mercury", CustomRotationModelType::IAUMercury "iau-venus", CustomRotationModelType::IAUVenus -"iau-earth", CustomRotationModelType::IAUEarth "iau-mars", CustomRotationModelType::IAUMars "iau-jupiter", CustomRotationModelType::IAUJupiter "iau-saturn", CustomRotationModelType::IAUSaturn From 4e25ba2974337a75aaca02f454fff2208ccf96d4 Mon Sep 17 00:00:00 2001 From: "Pedro J. Garcia" Date: Sun, 7 Jul 2024 16:47:12 -0300 Subject: [PATCH 02/15] Small fixes to lunar rotation model --- src/celephem/customrotation.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/celephem/customrotation.cpp b/src/celephem/customrotation.cpp index a7e83dfb61..697b0a1add 100644 --- a/src/celephem/customrotation.cpp +++ b/src/celephem/customrotation.cpp @@ -363,8 +363,8 @@ class IAUNeptuneRotationModel : public IAURotationModel /*! IAU rotation model for the Moon. - * From the IAU/IAG Working Group on Cartographic Coordinates and Rotational Elements: - * http://astrogeology.usgs.gov/Projects/WGCCRE/constants/iau2000_table2.html + * From the 2009 report of the IAU Working Group on Cartographic Coordinates and Rotational Elements: + * https://astropedia.astrogeology.usgs.gov/alfresco/d/d/workspace/SpacesStore/28fd9e81-1964-44d6-a58b-fbbf61e64e15/WGCCRE2009reprint.pdf */ class IAULunarRotationModel : public IAURotationModel { @@ -375,9 +375,9 @@ class IAULunarRotationModel : public IAURotationModel { E[1] = math::degToRad(125.045 - 0.0529921 * d); E[2] = math::degToRad(250.089 - 0.1059842 * d); - E[3] = math::degToRad(260.008 + 13.012009 * d); + E[3] = math::degToRad(260.008 + 13.0120009 * d); E[4] = math::degToRad(176.625 + 13.3407154 * d); - E[5] = math::degToRad(357.529 + 0.9856993 * d); + E[5] = math::degToRad(357.529 + 0.9856003 * d); E[6] = math::degToRad(311.589 + 26.4057084 * d); E[7] = math::degToRad(134.963 + 13.0649930 * d); E[8] = math::degToRad(276.617 + 0.3287146 * d); @@ -397,7 +397,7 @@ class IAULunarRotationModel : public IAURotationModel calcArgs(d, E); ra = 269.9949 - + 0.0013*T + + 0.0031 * T - 3.8787 * std::sin(E[1]) - 0.1204 * std::sin(E[2]) + 0.0700 * std::sin(E[3]) From dc40f0cb947b150be256be9dd1052c8fd093e300 Mon Sep 17 00:00:00 2001 From: "Pedro J. Garcia" Date: Sun, 7 Jul 2024 19:51:52 -0300 Subject: [PATCH 03/15] Fixes to Jovian and Uranian moon rotation models --- src/celephem/customrotation.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/celephem/customrotation.cpp b/src/celephem/customrotation.cpp index 697b0a1add..9a8db6c9f8 100644 --- a/src/celephem/customrotation.cpp +++ b/src/celephem/customrotation.cpp @@ -591,7 +591,7 @@ class IAUIoRotationModel : public IAURotationModel double J4 = math::degToRad(355.80 + 1191.3 * T); clamp_centuries(T); ra = 268.05 - 0.009 * T + 0.094 * std::sin(J3) + 0.024 * std::sin(J4); - dec = 64.49 + 0.003 * T + 0.040 * std::cos(J3) + 0.011 * std::cos(J4); + dec = 64.50 + 0.003 * T + 0.040 * std::cos(J3) + 0.011 * std::cos(J4); } double meridian(double t) const override @@ -615,10 +615,10 @@ class IAUEuropaRotationModel : public IAURotationModel double J4 = math::degToRad(355.80 + 1191.3 * T); double J5 = math::degToRad(119.90 + 262.1 * T); double J6 = math::degToRad(229.80 + 64.3 * T); - double J7 = math::degToRad(352.35 + 2382.6 * T); + double J7 = math::degToRad(352.25 + 2382.6 * T); clamp_centuries(T); - ra = 268.05 - 0.009 * T + 1.086 * std::sin(J4) + 0.060 * std::sin(J5) + 0.015 * std::sin(J6) + 0.009 * std::sin(J7); - dec = 64.49 + 0.003 * T + 0.486 * std::cos(J4) + 0.026 * std::cos(J5) + 0.007 * std::cos(J6) + 0.002 * std::cos(J7); + ra = 268.08 - 0.009 * T + 1.086 * std::sin(J4) + 0.060 * std::sin(J5) + 0.015 * std::sin(J6) + 0.009 * std::sin(J7); + dec = 64.51 + 0.003 * T + 0.468 * std::cos(J4) + 0.026 * std::cos(J5) + 0.007 * std::cos(J6) + 0.002 * std::cos(J7); } double meridian(double t) const override @@ -627,7 +627,7 @@ class IAUEuropaRotationModel : public IAURotationModel double J4 = math::degToRad(355.80 + 1191.3 * T); double J5 = math::degToRad(119.90 + 262.1 * T); double J6 = math::degToRad(229.80 + 64.3 * T); - double J7 = math::degToRad(352.35 + 2382.6 * T); + double J7 = math::degToRad(352.25 + 2382.6 * T); return 36.022 + 101.3747235 * t - 0.980 * std::sin(J4) - 0.054 * std::sin(J5) - 0.014 * std::sin(J6) - 0.008 * std::sin(J7); } }; @@ -645,8 +645,8 @@ class IAUGanymedeRotationModel : public IAURotationModel double J5 = math::degToRad(119.90 + 262.1 * T); double J6 = math::degToRad(229.80 + 64.3 * T); clamp_centuries(T); - ra = 268.05 - 0.009 * T - 0.037 * std::sin(J4) + 0.431 * std::sin(J5) + 0.091 * std::sin(J6); - dec = 64.49 + 0.003 * T - 0.016 * std::cos(J4) + 0.186 * std::cos(J5) + 0.039 * std::cos(J6); + ra = 268.20 - 0.009 * T - 0.037 * std::sin(J4) + 0.431 * std::sin(J5) + 0.091 * std::sin(J6); + dec = 64.57 + 0.003 * T - 0.016 * std::cos(J4) + 0.186 * std::cos(J5) + 0.039 * std::cos(J6); } double meridian(double t) const override @@ -672,8 +672,8 @@ class IAUCallistoRotationModel : public IAURotationModel double J6 = math::degToRad(229.80 + 64.3 * T); double J8 = math::degToRad(113.35 + 6070.0 * T); clamp_centuries(T); - ra = 268.05 - 0.009 * T - 0.068 * std::sin(J5) + 0.590 * std::sin(J6) + 0.010 * std::sin(J8); - dec = 64.49 + 0.003 * T - 0.029 * std::cos(J5) + 0.254 * std::cos(J6) - 0.004 * std::cos(J8); + ra = 268.72 - 0.009 * T - 0.068 * std::sin(J5) + 0.590 * std::sin(J6) + 0.010 * std::sin(J8); + dec = 64.83 + 0.003 * T - 0.029 * std::cos(J5) + 0.254 * std::cos(J6) - 0.004 * std::cos(J8); } double meridian(double t) const override @@ -848,7 +848,7 @@ class IAUTitaniaRotationModel : public IAURotationModel { double T = t / 36525.0; double U15 = math::degToRad(340.82 - 75.32 * T); - return 77.74 - 41.351431 * t + 0.08 * std::sin(U15); + return 77.74 - 41.3514316 * t + 0.08 * std::sin(U15); } }; From ee9cb61d82a3bac5207d2867dda5cbf139fe5055 Mon Sep 17 00:00:00 2001 From: Andrew Tribick Date: Thu, 11 Jul 2024 01:03:32 +0200 Subject: [PATCH 04/15] Use smart pointers in octree classes - Use unique_ptr to hold DSOs - Use default growth strategy for DSO vector, as binary DSO files were never implemented --- src/celengine/dsodb.cpp | 124 +++----- src/celengine/dsodb.h | 16 +- src/celengine/dsooctree.cpp | 134 +++++---- src/celengine/dsooctree.h | 40 ++- src/celengine/dsorenderer.cpp | 15 +- src/celengine/dsorenderer.h | 5 +- src/celengine/glmarker.cpp | 2 +- src/celengine/objectrenderer.h | 21 +- src/celengine/octree.h | 432 +++++++++++----------------- src/celengine/pointstarrenderer.cpp | 6 +- src/celengine/pointstarrenderer.h | 14 +- src/celengine/stardb.h | 2 +- src/celengine/stardbbuilder.cpp | 6 +- src/celengine/staroctree.cpp | 128 ++++----- src/celengine/staroctree.h | 38 ++- src/celengine/universe.cpp | 12 +- 16 files changed, 455 insertions(+), 540 deletions(-) diff --git a/src/celengine/dsodb.cpp b/src/celengine/dsodb.cpp index 791f0f622a..3702190a8c 100644 --- a/src/celengine/dsodb.cpp +++ b/src/celengine/dsodb.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -42,24 +43,20 @@ constexpr const float DSO_OCTREE_MAGNITUDE = 8.0f; } // end unnamed namespace -DSODatabase::~DSODatabase() -{ - delete [] DSOs; - delete [] catalogNumberIndex; -} - DeepSkyObject* DSODatabase::find(const AstroCatalog::IndexNumber catalogNumber) const { - DeepSkyObject** dso = std::lower_bound(catalogNumberIndex, - catalogNumberIndex + nDSOs, - catalogNumber, - [](const DeepSkyObject* const& dso, AstroCatalog::IndexNumber catNum) { return dso->getIndex() < catNum; }); - - if (dso != catalogNumberIndex + nDSOs && (*dso)->getIndex() == catalogNumber) - return *dso; - else - return nullptr; + auto it = std::lower_bound(catalogNumberIndex.begin(), + catalogNumberIndex.end(), + catalogNumber, + [this](std::uint32_t idx, AstroCatalog::IndexNumber catNum) + { + return DSOs[idx]->getIndex() < catNum; + }); + + return (it != catalogNumberIndex.end() && DSOs[*it]->getIndex() == catalogNumber) + ? DSOs[*it].get() + : nullptr; } DeepSkyObject* @@ -238,46 +235,22 @@ DSODatabase::load(std::istream& in, const fs::path& resourcePath) return false; } - DeepSkyObject* obj = nullptr; + std::unique_ptr obj; if (compareIgnoringCase(objType, "Galaxy") == 0) - obj = new Galaxy(); + obj = std::make_unique(); else if (compareIgnoringCase(objType, "Globular") == 0) - obj = new Globular(); + obj = std::make_unique(); else if (compareIgnoringCase(objType, "Nebula") == 0) - obj = new Nebula(); + obj = std::make_unique(); else if (compareIgnoringCase(objType, "OpenCluster") == 0) - obj = new OpenCluster(); + obj = std::make_unique(); if (obj != nullptr && obj->load(objParams, resourcePath)) { - UserCategory::loadCategories(obj, *objParams, DataDisposition::Add, resourcePath.string()); - - // Ensure that the DSO array is large enough - if (nDSOs == capacity) - { - // Grow the array by 5%--this may be too little, but the - // assumption here is that there will be small numbers of - // DSOs in text files added to a big collection loaded from - // a binary file. - capacity = static_cast(capacity * 1.05); - - // 100 DSOs seems like a reasonable minimum - if (capacity < 100) - capacity = 100; - - DeepSkyObject** newDSOs = new DeepSkyObject*[capacity]; - - if (DSOs != nullptr) - { - std::copy(DSOs, DSOs + nDSOs, newDSOs); - delete[] DSOs; - } - DSOs = newDSOs; - } - - DSOs[nDSOs++] = obj; + UserCategory::loadCategories(obj.get(), *objParams, DataDisposition::Add, resourcePath.string()); obj->setIndex(objCatalogNumber); + DSOs.emplace_back(std::move(obj)); if (namesDB != nullptr && !objName.empty()) { @@ -310,6 +283,7 @@ DSODatabase::load(std::istream& in, const fs::path& resourcePath) return false; } } + return true; } @@ -319,15 +293,8 @@ DSODatabase::finish() buildOctree(); buildIndexes(); calcAvgAbsMag(); - /* - // Put AbsMag = avgAbsMag for Add-ons without AbsMag entry - for (int i = 0; i < nDSOs; ++i) - { - if(DSOs[i]->getAbsoluteMagnitude() == DSO_DEFAULT_ABS_MAGNITUDE) - DSOs[i]->setAbsoluteMagnitude((float)avgAbsMag); - } - */ - GetLogger()->info(_("Loaded {} deep space objects\n"), nDSOs); + + GetLogger()->info(_("Loaded {} deep space objects\n"), DSOs.size()); } void @@ -339,46 +306,41 @@ DSODatabase::buildOctree() // TODO: investigate using a different center--it's possible that more // objects end up straddling the base level nodes when the center of the // octree is at the origin. - DynamicDSOOctree* root = new DynamicDSOOctree(Eigen::Vector3d::Zero(), absMag); - for (int i = 0; i < nDSOs; ++i) - { - root->insertObject(DSOs[i], DSO_OCTREE_ROOT_SIZE); - } + auto root = std::make_unique(Eigen::Vector3d::Zero(), absMag); + for (auto& dso : DSOs) + root->insertObject(dso, DSO_OCTREE_ROOT_SIZE); GetLogger()->debug("Spatially sorting DSOs for improved locality of reference . . .\n"); - DeepSkyObject** sortedDSOs = new DeepSkyObject*[nDSOs]; - DeepSkyObject** firstDSO = sortedDSOs; + std::vector> sortedDSOs; + sortedDSOs.resize(DSOs.size()); + std::unique_ptr* firstDSO = sortedDSOs.data(); // The spatial sorting part is useless for DSOs since we // are storing pointers to objects and not the objects themselves: - root->rebuildAndSort(octreeRoot, firstDSO); + octreeRoot = root->rebuildAndSort(firstDSO); GetLogger()->debug("{} DSOs total.\nOctree has {} nodes and {} DSOs.\n", - static_cast(firstDSO - sortedDSOs), - 1 + octreeRoot->countChildren(), + firstDSO - sortedDSOs.data(), + UINT32_C(1) + octreeRoot->countChildren(), octreeRoot->countObjects()); - // Clean up . . . - delete[] DSOs; - delete root; - - DSOs = sortedDSOs; + DSOs = std::move(sortedDSOs); } void DSODatabase::calcAvgAbsMag() { - uint32_t nDSOeff = size(); - for (int i = 0; i < nDSOs; ++i) + std::uint32_t nDSOeff = size(); + for (const auto& dso : DSOs) { - float DSOmag = DSOs[i]->getAbsoluteMagnitude(); + float DSOmag = dso->getAbsoluteMagnitude(); // take only DSO's with realistic AbsMag entry // (> DSO_DEFAULT_ABS_MAGNITUDE) into account if (DSOmag > DSO_DEFAULT_ABS_MAGNITUDE) avgAbsMag += DSOmag; else if (nDSOeff > 1) - nDSOeff--; + --nDSOeff; } avgAbsMag /= static_cast(nDSOeff); } @@ -391,13 +353,15 @@ DSODatabase::buildIndexes() GetLogger()->debug("Building catalog number indexes . . .\n"); - catalogNumberIndex = new DeepSkyObject*[nDSOs]; - for (int i = 0; i < nDSOs; ++i) - catalogNumberIndex[i] = DSOs[i]; + catalogNumberIndex.resize(DSOs.size()); + std::iota(catalogNumberIndex.begin(), catalogNumberIndex.end(), UINT32_C(0)); - std::sort(catalogNumberIndex, - catalogNumberIndex + nDSOs, - [](const DeepSkyObject* dso0, const DeepSkyObject* dso1) { return dso0->getIndex() < dso1->getIndex(); }); + std::sort(catalogNumberIndex.begin(), + catalogNumberIndex.end(), + [this](std::uint32_t idx0, std::uint32_t idx1) + { + return DSOs[idx0]->getIndex() < DSOs[idx1]->getIndex(); + }); } float diff --git a/src/celengine/dsodb.h b/src/celengine/dsodb.h index 5a8949429a..d3c715a2d8 100644 --- a/src/celengine/dsodb.h +++ b/src/celengine/dsodb.h @@ -36,7 +36,7 @@ class DSODatabase { public: DSODatabase() = default; - ~DSODatabase(); + ~DSODatabase() = default; DeepSkyObject* getDSO(const std::uint32_t) const; std::uint32_t size() const; @@ -73,25 +73,23 @@ class DSODatabase void buildOctree(); void calcAvgAbsMag(); - int nDSOs{ 0 }; - int capacity{ 0 }; - DeepSkyObject** DSOs{ nullptr }; + std::vector> DSOs; std::unique_ptr namesDB; - DeepSkyObject** catalogNumberIndex{ nullptr }; - DSOOctree* octreeRoot{ nullptr }; + std::vector catalogNumberIndex; + std::unique_ptr octreeRoot; AstroCatalog::IndexNumber nextAutoCatalogNumber{ 0xfffffffe }; - float avgAbsMag{ 0.0f }; + float avgAbsMag{ 0.0f }; }; inline DeepSkyObject* DSODatabase::getDSO(const std::uint32_t n) const { - return *(DSOs + n); + return DSOs[n].get(); } inline std::uint32_t DSODatabase::size() const { - return nDSOs; + return static_cast(DSOs.size()); } diff --git a/src/celengine/dsooctree.cpp b/src/celengine/dsooctree.cpp index 4c8dec7e1c..0e155d1287 100644 --- a/src/celengine/dsooctree.cpp +++ b/src/celengine/dsooctree.cpp @@ -10,11 +10,13 @@ // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. -#include +#include "dsooctree.h" -using namespace Eigen; +#include +#include namespace astro = celestia::astro; +namespace numbers = celestia::numbers; // The octree node into which a dso is placed is dependent on two properties: // its obsPosition and its luminosity--the fainter the dso, the deeper the node @@ -22,57 +24,54 @@ namespace astro = celestia::astro; // of the node is allowed contain a dso brighter than this value, making it // possible to determine quickly whether or not to cull subtrees. -bool dsoAbsoluteMagnitudePredicate(DeepSkyObject* const & _dso, const float absMag) +template<> +bool +DynamicDSOOctree::exceedsBrightnessThreshold(const std::unique_ptr& dso, //NOSONAR + float absMag) { - return _dso->getAbsoluteMagnitude() <= absMag; + return dso->getAbsoluteMagnitude() <= absMag; } - -bool dsoStraddlesNodesPredicate(const Vector3d& cellCenterPos, DeepSkyObject* const & _dso, const float /*unused*/) +template<> +bool +DynamicDSOOctree::isStraddling(const Eigen::Vector3d& cellCenterPos, + const std::unique_ptr& dso) //NOSONAR { //checks if this dso's radius straddles child nodes - float dsoRadius = _dso->getBoundingSphereRadius(); - - return (_dso->getPosition() - cellCenterPos).cwiseAbs().minCoeff() < dsoRadius; + float dsoRadius = dso->getBoundingSphereRadius(); + return (dso->getPosition() - cellCenterPos).cwiseAbs().minCoeff() < dsoRadius; } - -double dsoAbsoluteMagnitudeDecayFunction(const double excludingFactor) +template<> +float +DynamicDSOOctree::applyDecay(float excludingFactor) { return excludingFactor + 0.5f; } - -template <> -DynamicDSOOctree* DynamicDSOOctree::getChild(DeepSkyObject* const & _obj, const PointType& cellCenterPos) +template<> +DynamicDSOOctree* +DynamicDSOOctree::getChild(const std::unique_ptr& obj, //NOSONAR + const PointType& cellCenterPos) const { - PointType objPos = _obj->getPosition(); + PointType objPos = obj->getPosition(); int child = 0; - child |= objPos.x() < cellCenterPos.x() ? 0 : XPos; - child |= objPos.y() < cellCenterPos.y() ? 0 : YPos; - child |= objPos.z() < cellCenterPos.z() ? 0 : ZPos; + child |= objPos.x() < cellCenterPos.x() ? 0 : XPos; + child |= objPos.y() < cellCenterPos.y() ? 0 : YPos; + child |= objPos.z() < cellCenterPos.z() ? 0 : ZPos; - return _children[child]; + return (*m_children)[child].get(); } - -template<> unsigned int DynamicDSOOctree::SPLIT_THRESHOLD = 10; -template<> DynamicDSOOctree::LimitingFactorPredicate* - DynamicDSOOctree::limitingFactorPredicate = dsoAbsoluteMagnitudePredicate; -template<> DynamicDSOOctree::StraddlingPredicate* - DynamicDSOOctree::straddlingPredicate = dsoStraddlesNodesPredicate; -template<> DynamicDSOOctree::ExclusionFactorDecayFunction* - DynamicDSOOctree::decayFunction = dsoAbsoluteMagnitudeDecayFunction; - - // total specialization of the StaticOctree template process*() methods for DSOs: template<> -void DSOOctree::processVisibleObjects(DSOHandler& processor, - const PointType& obsPosition, - const Hyperplane* frustumPlanes, - float limitingFactor, - double scale) const +void +DSOOctree::processVisibleObjects(DSOHandler& processor, + const PointType& obsPosition, + const PlaneType* frustumPlanes, + float limitingFactor, + double scale) const { // See if this node lies within the view frustum @@ -80,63 +79,61 @@ void DSOOctree::processVisibleObjects(DSOHandler& processor, // planes that define the infinite view frustum. for (unsigned int i = 0; i < 5; ++i) { - const Hyperplane& plane = frustumPlanes[i]; + const PlaneType& plane = frustumPlanes[i]; double r = scale * plane.normal().cwiseAbs().sum(); - if (plane.signedDistance(cellCenterPos) < -r) + if (plane.signedDistance(m_cellCenterPos) < -r) return; } // Compute the distance to node; this is equal to the distance to // the cellCenterPos of the node minus the boundingRadius of the node, scale * SQRT3. - double minDistance = (obsPosition - cellCenterPos).norm() - scale * DSOOctree::SQRT3; + double minDistance = (obsPosition - m_cellCenterPos).norm() - scale * numbers::sqrt3; // Process the objects in this node double dimmest = minDistance > 0.0 ? astro::appToAbsMag((double) limitingFactor, minDistance) : 1000.0; - for (unsigned int i=0; igetAbsoluteMagnitude(); + const auto& obj = m_firstObject[i]; + float absMag = obj->getAbsoluteMagnitude(); if (absMag < dimmest) { - double distance = (obsPosition - _obj->getPosition()).norm() - _obj->getBoundingSphereRadius(); - float appMag = (float) ((distance >= 32.6167) ? astro::absToAppMag((double) absMag, distance) : absMag); + double distance = (obsPosition - obj->getPosition()).norm() - obj->getBoundingSphereRadius(); + auto appMag = static_cast((distance >= 32.6167) ? astro::absToAppMag(static_cast(absMag), distance) : absMag); if (appMag < limitingFactor) - processor.process(_obj, distance, absMag); + processor.process(obj, distance, absMag); } } // See if any of the objects in child nodes are potentially included // that we need to recurse deeper. - if (minDistance <= 0.0 || astro::absToAppMag((double) exclusionFactor, minDistance) <= limitingFactor) + if (m_children != nullptr && + (minDistance <= 0.0 || astro::absToAppMag(static_cast(m_exclusionFactor), minDistance) <= limitingFactor)) { // Recurse into the child nodes - if (_children != nullptr) + for (int i = 0; i < 8; ++i) { - for (int i = 0; i < 8; ++i) - { - _children[i]->processVisibleObjects(processor, + (*m_children)[i]->processVisibleObjects(processor, obsPosition, frustumPlanes, limitingFactor, scale * 0.5f); - } } } } - template<> -void DSOOctree::processCloseObjects(DSOHandler& processor, - const PointType& obsPosition, - double boundingRadius, - double scale) const +void +DSOOctree::processCloseObjects(DSOHandler& processor, + const PointType& obsPosition, + double boundingRadius, + double scale) const { // Compute the distance to node; this is equal to the distance to // the cellCenterPos of the node minus the boundingRadius of the node, scale * SQRT3. - double nodeDistance = (obsPosition - cellCenterPos).norm() - scale * DSOOctree::SQRT3; // + double nodeDistance = (obsPosition - m_cellCenterPos).norm() - scale * numbers::sqrt3; if (nodeDistance > boundingRadius) return; @@ -146,31 +143,30 @@ void DSOOctree::processCloseObjects(DSOHandler& processor, // Compute distance squared to avoid having to sqrt for distance // comparison. - double radiusSquared = boundingRadius * boundingRadius; // + double radiusSquared = boundingRadius * boundingRadius; // Check all the objects in the node. - for (unsigned int i=0; igetPosition()).squaredNorm() < radiusSquared) // + const auto& obj = m_firstObject[i]; + PointType offset = obsPosition - obj->getPosition(); + if (offset.squaredNorm() < radiusSquared) { - float absMag = _obj->getAbsoluteMagnitude(); - double distance = (obsPosition - _obj->getPosition()).norm() - _obj->getBoundingSphereRadius(); - - processor.process(_obj, distance, absMag); + float absMag = obj->getAbsoluteMagnitude(); + double distance = offset.norm() - obj->getBoundingSphereRadius(); + processor.process(obj, distance, absMag); } } // Recurse into the child nodes - if (_children != nullptr) + if (m_children != nullptr) { for (int i = 0; i < 8; ++i) { - _children[i]->processCloseObjects(processor, - obsPosition, - boundingRadius, - scale * 0.5f); + (*m_children)[i]->processCloseObjects(processor, + obsPosition, + boundingRadius, + scale * 0.5f); } } } diff --git a/src/celengine/dsooctree.h b/src/celengine/dsooctree.h index 183cd82d5a..db9033854a 100644 --- a/src/celengine/dsooctree.h +++ b/src/celengine/dsooctree.h @@ -12,10 +12,40 @@ #pragma once -#include -#include +#include +#include +#include "deepskyobj.h" +#include "octree.h" -using DynamicDSOOctree = DynamicOctree; -using DSOOctree = StaticOctree; -using DSOHandler = OctreeProcessor; +using DynamicDSOOctree = DynamicOctree, double>; +using DSOOctree = StaticOctree, double>; +using DSOHandler = OctreeProcessor, double>; + +template<> +const inline std::uint32_t DynamicDSOOctree::SPLIT_THRESHOLD = 10; + +template<> +bool DynamicDSOOctree::exceedsBrightnessThreshold(const std::unique_ptr&, float); //NOSONAR + +template<> +bool DynamicDSOOctree::isStraddling(const Eigen::Vector3d&, const std::unique_ptr&); //NOSONAR + +template<> +float DynamicDSOOctree::applyDecay(float); + +template<> +DynamicDSOOctree* DynamicDSOOctree::getChild(const std::unique_ptr&, const PointType&) const; //NOSONAR + +template<> +void DSOOctree::processVisibleObjects(DSOHandler&, + const PointType&, + const PlaneType*, + float, + double) const; + +template<> +void DSOOctree::processCloseObjects(DSOHandler&, + const PointType&, + double, + double) const; diff --git a/src/celengine/dsorenderer.cpp b/src/celengine/dsorenderer.cpp index 52c12678c5..692c881b42 100644 --- a/src/celengine/dsorenderer.cpp +++ b/src/celengine/dsorenderer.cpp @@ -55,12 +55,11 @@ brightness(float avgAbsMag, float absMag, float appMag, float brightnessCorr, fl } // anonymous namespace -DSORenderer::DSORenderer() : - ObjectRenderer(DSO_OCTREE_ROOT_SIZE) +DSORenderer::DSORenderer() : ObjectRenderer(DSO_OCTREE_ROOT_SIZE) { } -void DSORenderer::process(DeepSkyObject* const &dso, +void DSORenderer::process(const std::unique_ptr& dso, //NOSONAR double distanceToDSO, float absMag) { @@ -111,20 +110,20 @@ void DSORenderer::process(DeepSkyObject* const &dso, case DeepSkyObjectType::Galaxy: // -19.04f == average over 10937 galaxies in galaxies.dsc. b = brightness(-19.04f, absMag, appMag, b, faintestMag); - galaxyRenderer->add(static_cast(dso), relPos, b, nearZ, farZ); + galaxyRenderer->add(static_cast(dso.get()), relPos, b, nearZ, farZ); break; case DeepSkyObjectType::Globular: // -6.86f == average over 150 globulars in globulars.dsc. b = brightness(-6.86f, absMag, appMag, b, faintestMag); - globularRenderer->add(static_cast(dso), relPos, b, nearZ, farZ); + globularRenderer->add(static_cast(dso.get()), relPos, b, nearZ, farZ); break; case DeepSkyObjectType::Nebula: b = brightness(avgAbsMag, absMag, appMag, b, faintestMag); - nebulaRenderer->add(static_cast(dso), relPos, b, nearZ, farZ); + nebulaRenderer->add(static_cast(dso.get()), relPos, b, nearZ, farZ); break; case DeepSkyObjectType::OpenCluster: b = brightness(avgAbsMag, absMag, appMag, b, faintestMag); - openClusterRenderer->add(static_cast(dso), relPos, b, nearZ, farZ); + openClusterRenderer->add(static_cast(dso.get()), relPos, b, nearZ, farZ); break; default: // Unsupported DSO @@ -188,7 +187,7 @@ void DSORenderer::process(DeepSkyObject* const &dso, labelColor.alpha(distr * labelColor.alpha()); renderer->addBackgroundAnnotation(rep, - dsoDB->getDSOName(dso, true), + dsoDB->getDSOName(dso.get(), true), labelColor, relPos, Renderer::LabelHorizontalAlignment::Start, diff --git a/src/celengine/dsorenderer.h b/src/celengine/dsorenderer.h index a2d35ebc7f..4a44180b74 100644 --- a/src/celengine/dsorenderer.h +++ b/src/celengine/dsorenderer.h @@ -11,6 +11,7 @@ #pragma once #include +#include #include @@ -23,12 +24,12 @@ class DeepSkyObject; class DSODatabase; -class DSORenderer : public ObjectRenderer +class DSORenderer : public ObjectRenderer, double> { public: DSORenderer(); - void process(DeepSkyObject *const &, double, float) override; + void process(const std::unique_ptr&, double, float) override; //NOSONAR celestia::math::InfiniteFrustum frustum{ celestia::math::degToRad(celestia::engine::standardFOV), 1.0f, diff --git a/src/celengine/glmarker.cpp b/src/celengine/glmarker.cpp index d989be4f57..82e0933aa0 100644 --- a/src/celengine/glmarker.cpp +++ b/src/celengine/glmarker.cpp @@ -19,9 +19,9 @@ #include #include #include "marker.h" +#include "observer.h" #include "render.h" - using namespace celestia; using celestia::render::LineRenderer; diff --git a/src/celengine/objectrenderer.h b/src/celengine/objectrenderer.h index 4d6e8c554e..420c9c28ef 100644 --- a/src/celengine/objectrenderer.h +++ b/src/celengine/objectrenderer.h @@ -10,20 +10,17 @@ #pragma once -#include +#include + #include "octree.h" class Observer; class Renderer; -template class ObjectRenderer : public OctreeProcessor +template +class ObjectRenderer : public OctreeProcessor { - public: - ObjectRenderer(PREC _distanceLimit) : - distanceLimit((float) _distanceLimit) - { - }; - +public: const Observer* observer { nullptr }; Renderer* renderer { nullptr }; @@ -34,6 +31,12 @@ template class ObjectRenderer : public OctreeProcessor(_distanceLimit)) + { + } }; diff --git a/src/celengine/octree.h b/src/celengine/octree.h index cd5a054e49..ec413f77da 100644 --- a/src/celengine/octree.h +++ b/src/celengine/octree.h @@ -12,10 +12,13 @@ #pragma once +#include +#include +#include +#include + #include #include -#include -#include // The DynamicOctree and StaticOctree template arguments are: // OBJ: object hanging from the node, @@ -24,92 +27,31 @@ // OBJ's limiting property defined by the octree particular specialization: ie. we use [absolute magnitude] for star octrees, etc. // For details, see notes below. -template class OctreeProcessor +template +class OctreeProcessor { - public: - OctreeProcessor() {}; - virtual ~OctreeProcessor() {}; - +public: + virtual ~OctreeProcessor() = default; virtual void process(const OBJ& obj, PREC distance, float appMag) = 0; -}; - - -struct OctreeLevelStatistics -{ - unsigned int nodeCount; - unsigned int objectCount; - double size; +protected: + OctreeProcessor() = default; }; +template +class DynamicOctree; -template class StaticOctree; -template class DynamicOctree +template +class StaticOctree { public: - typedef Eigen::Matrix PointType; + using PointType = Eigen::Matrix; + using PlaneType = Eigen::Hyperplane; -private: - typedef std::vector ObjectList; - - - typedef bool (LimitingFactorPredicate) (const OBJ&, const float); - typedef bool (StraddlingPredicate) (const Eigen::Matrix&, const OBJ&, const float); - typedef PREC (ExclusionFactorDecayFunction)(const PREC); - - public: - DynamicOctree(const Eigen::Matrix& cellCenterPos, - const float exclusionFactor); - ~DynamicOctree(); - - void insertObject (const OBJ&, const PREC); - void rebuildAndSort(StaticOctree*&, OBJ*&); - - private: - static unsigned int SPLIT_THRESHOLD; - - static LimitingFactorPredicate* limitingFactorPredicate; - static StraddlingPredicate* straddlingPredicate; - static ExclusionFactorDecayFunction* decayFunction; - - private: - void add (const OBJ&); - void split(const PREC); - void sortIntoChildNodes(); - DynamicOctree* getChild(const OBJ&, const Eigen::Matrix&); - - DynamicOctree** _children; - Eigen::Matrix cellCenterPos; - PREC exclusionFactor; - ObjectList* _objects; -}; - -// make clang happy -#ifndef _MSC_VER -template<> DynamicOctree::ExclusionFactorDecayFunction* DynamicOctree::decayFunction; -template<> DynamicOctree::LimitingFactorPredicate* DynamicOctree::limitingFactorPredicate; -template<> DynamicOctree::StraddlingPredicate* DynamicOctree::straddlingPredicate; -template<> unsigned int DynamicOctree::SPLIT_THRESHOLD; - -template<> DynamicOctree::ExclusionFactorDecayFunction* DynamicOctree::decayFunction; -template<> DynamicOctree::LimitingFactorPredicate* DynamicOctree::limitingFactorPredicate; -template<> DynamicOctree::StraddlingPredicate* DynamicOctree::straddlingPredicate; -template<> unsigned int DynamicOctree::SPLIT_THRESHOLD; -#endif - -template class StaticOctree -{ - friend class DynamicOctree; - - public: - typedef Eigen::Matrix PointType; - - public: - StaticOctree(const PointType& cellCenterPos, - const float exclusionFactor, - OBJ* _firstObject, - unsigned int nObjects); - ~StaticOctree(); + StaticOctree(const PointType& cellCenterPos, + const float exclusionFactor, + OBJ* _firstObject, + std::uint32_t nObjects); // These methods are only declared at the template level; we'll implement them as // full specializations, allowing for different traversal strategies depending on the @@ -122,39 +64,71 @@ template class StaticOctree // objects that are outside the view frustum may be. Frustum tests are performed // only at the node level to optimize the octree traversal, so an exact test // (if one is required) is the responsibility of the callback method. - void processVisibleObjects(OctreeProcessor& processor, - const PointType& obsPosition, - const Eigen::Hyperplane* frustumPlanes, - float limitingFactor, - PREC scale) const; - - void processCloseObjects(OctreeProcessor& processor, - const PointType& obsPosition, - PREC boundingRadius, - PREC scale) const; - - int countChildren() const; - int countObjects() const; - - void computeStatistics(std::vector& stats, unsigned int level = 0); - - private: - static const PREC SQRT3; - - private: - StaticOctree** _children; - Eigen::Matrix cellCenterPos; - float exclusionFactor; - OBJ* _firstObject; - unsigned int nObjects; + void processVisibleObjects(OctreeProcessor& processor, + const PointType& obsPosition, + const PlaneType* frustumPlanes, + float limitingFactor, + PREC scale) const; + + void processCloseObjects(OctreeProcessor& processor, + const PointType& obsPosition, + PREC boundingRadius, + PREC scale) const; + + std::uint32_t countChildren() const; + std::uint32_t countObjects() const; + +private: + using ChildrenType = std::array, 8>; + + std::unique_ptr m_children; + PointType m_cellCenterPos; + float m_exclusionFactor; + std::uint32_t m_nObjects; + OBJ* m_firstObject; + + friend class DynamicOctree; }; +template +StaticOctree::StaticOctree(const PointType& cellCenterPos, + float exclusionFactor, + OBJ* firstObject, + std::uint32_t nObjects): + m_cellCenterPos(cellCenterPos), + m_exclusionFactor(exclusionFactor), + m_firstObject(firstObject), + m_nObjects(nObjects) +{ +} + +template +std::uint32_t +StaticOctree::countChildren() const +{ + if (m_children == nullptr) + return 0; + std::uint32_t count = 0; + for (int i = 0; i < 8; ++i) + count += UINT32_C(1) + (*m_children)[i]->countChildren(); + return count; +} +template +std::uint32_t +StaticOctree::countObjects() const +{ + std::uint32_t count = m_nObjects; + if (m_children != nullptr) + for (int i = 0; i < 8; ++i) + count += (*m_children)[i]->countObjects(); + return count; +} // There are two classes implemented in this module: StaticOctree and // DynamicOctree. The DynamicOctree is built first by inserting @@ -170,42 +144,59 @@ enum ZPos = 4, }; +template +class DynamicOctree +{ +public: + using PointType = Eigen::Matrix; + + DynamicOctree(const PointType& cellCenterPos, + float exclusionFactor); + + void insertObject(OBJ&, const PREC); + std::unique_ptr> rebuildAndSort(OBJ*&); + +private: + using ObjectList = std::vector; + + static const unsigned int SPLIT_THRESHOLD; + + static bool exceedsBrightnessThreshold(const OBJ&, float); + static bool isStraddling(const PointType&, const OBJ&); + static float applyDecay(float); + +private: + using ChildrenType = std::array, 8>; + + void add(OBJ&); + void split(const PREC); + void sortIntoChildNodes(); + DynamicOctree* getChild(const OBJ&, const PointType&) const; + + std::unique_ptr m_children; + PointType m_cellCenterPos; + float m_exclusionFactor; + std::unique_ptr m_objects; +}; + // The SPLIT_THRESHOLD is the number of objects a node must contain before its // children are generated. Increasing this number will decrease the number of // octree nodes in the tree, which will use less memory but make culling less // efficient. -template -inline DynamicOctree::DynamicOctree(const Eigen::Matrix& cellCenterPos, - const float exclusionFactor): - _children (nullptr), - cellCenterPos (cellCenterPos), - exclusionFactor(exclusionFactor), - _objects (nullptr) -{ -} - - -template -inline DynamicOctree::~DynamicOctree() +template +DynamicOctree::DynamicOctree(const PointType& cellCenterPos, + float exclusionFactor): + m_cellCenterPos(cellCenterPos), + m_exclusionFactor(exclusionFactor) { - if (_children != nullptr) - { - for (int i = 0; i < 8; ++i) - { - delete _children[i]; - } - - delete[] _children; - } - delete _objects; } - -template -inline void DynamicOctree::insertObject(const OBJ& obj, const PREC scale) +template +void +DynamicOctree::insertObject(OBJ& obj, const PREC scale) { // If the object can't be placed into this node's children, then put it here: - if (limitingFactorPredicate(obj, exclusionFactor) || straddlingPredicate(cellCenterPos, obj, exclusionFactor)) + if (exceedsBrightnessThreshold(obj, m_exclusionFactor) || isStraddling(m_cellCenterPos, obj)) { add(obj); return; @@ -218,9 +209,9 @@ inline void DynamicOctree::insertObject(const OBJ& obj, const PREC sc // object into a child node. This is done in order // to avoid having the octree degenerate into one object // per node. - if (_children == nullptr) + if (m_children == nullptr) { - if (_objects == nullptr || _objects->size() < DynamicOctree::SPLIT_THRESHOLD) + if (m_objects == nullptr || m_objects->size() < SPLIT_THRESHOLD) { add(obj); return; @@ -231,176 +222,89 @@ inline void DynamicOctree::insertObject(const OBJ& obj, const PREC sc // We've already allocated child nodes; place the object // into the appropriate one. - this->getChild(obj, cellCenterPos)->insertObject(obj, scale * (PREC) 0.5); + getChild(obj, m_cellCenterPos)->insertObject(obj, scale * (PREC) 0.5); } - -template -inline void DynamicOctree::add(const OBJ& obj) +template +void +DynamicOctree::add(OBJ& obj) { - if (_objects == nullptr) - _objects = new ObjectList; + if (m_objects == nullptr) + m_objects = std::make_unique(); - _objects->push_back(&obj); + m_objects->push_back(&obj); } - -template -inline void DynamicOctree::split(const PREC scale) +template +void +DynamicOctree::split(const PREC scale) { - _children = new DynamicOctree*[8]; - + m_children = std::make_unique(); for (int i = 0; i < 8; ++i) { - Eigen::Matrix centerPos = cellCenterPos; - - centerPos += Eigen::Matrix(((i & XPos) != 0) ? scale : -scale, - ((i & YPos) != 0) ? scale : -scale, - ((i & ZPos) != 0) ? scale : -scale); + PointType centerPos = m_cellCenterPos + + PointType(((i & XPos) != 0) ? scale : -scale, + ((i & YPos) != 0) ? scale : -scale, + ((i & ZPos) != 0) ? scale : -scale); -#if 0 - centerPos.x += ((i & XPos) != 0) ? scale : -scale; - centerPos.y += ((i & YPos) != 0) ? scale : -scale; - centerPos.z += ((i & ZPos) != 0) ? scale : -scale; -#endif - - _children[i] = new DynamicOctree(centerPos, - decayFunction(exclusionFactor)); + (*m_children)[i] = std::make_unique(centerPos, applyDecay(m_exclusionFactor)); } + sortIntoChildNodes(); } - // Sort this node's objects into objects that can remain here, // and objects that should be placed into one of the eight // child nodes. -template -inline void DynamicOctree::sortIntoChildNodes() +template +void +DynamicOctree::sortIntoChildNodes() { - unsigned int nKeptInParent = 0; + auto writeIt = m_objects->begin(); + auto endIt = m_objects->end(); - for (unsigned int i=0; i<_objects->size(); ++i) + for (auto readIt = writeIt; readIt != endIt; ++readIt) { - const OBJ& obj = *(*_objects)[i]; - - if (limitingFactorPredicate(obj, exclusionFactor) || - straddlingPredicate(cellCenterPos, obj, exclusionFactor) ) + OBJ& obj = **readIt; + if (exceedsBrightnessThreshold(obj, m_exclusionFactor) || isStraddling(m_cellCenterPos, obj)) { - (*_objects)[nKeptInParent++] = (*_objects)[i]; + *writeIt = *readIt; + ++writeIt; } else { - this->getChild(obj, cellCenterPos)->add(obj); + getChild(obj, m_cellCenterPos)->add(obj); } } - _objects->resize(nKeptInParent); + m_objects->erase(writeIt, endIt); } - -template -inline void DynamicOctree::rebuildAndSort(StaticOctree*& _staticNode, OBJ*& _sortedObjects) +template +std::unique_ptr> +DynamicOctree::rebuildAndSort(OBJ*& sortedObjects) { - OBJ* _firstObject = _sortedObjects; + OBJ* firstObject = sortedObjects; - if (_objects != nullptr) - for (typename ObjectList::const_iterator iter = _objects->begin(); iter != _objects->end(); ++iter) + if (m_objects != nullptr) + { + for (OBJ* obj : *m_objects) { - *_sortedObjects++ = **iter; + *sortedObjects = std::move(*obj); + ++sortedObjects; } - - unsigned int nObjects = (unsigned int) (_sortedObjects - _firstObject); - _staticNode = new StaticOctree(cellCenterPos, exclusionFactor, _firstObject, nObjects); - - if (_children != nullptr) - { - _staticNode->_children = new StaticOctree*[8]; - - for (int i=0; i<8; ++i) - _children[i]->rebuildAndSort(_staticNode->_children[i], _sortedObjects); } -} - - -//MS VC++ wants this to be placed here: -template -const PREC StaticOctree::SQRT3 = (PREC) 1.732050807568877; - - -template -inline StaticOctree::StaticOctree(const Eigen::Matrix& cellCenterPos, - const float exclusionFactor, - OBJ* _firstObject, - unsigned int nObjects): - _children (nullptr), - cellCenterPos (cellCenterPos), - exclusionFactor(exclusionFactor), - _firstObject (_firstObject), - nObjects (nObjects) -{ -} + auto nObjects = static_cast(sortedObjects - firstObject); + auto staticNode = std::make_unique>(m_cellCenterPos, m_exclusionFactor, firstObject, nObjects); -template -inline StaticOctree::~StaticOctree() -{ - if (_children != nullptr) + if (m_children != nullptr) { - for (int i = 0; i < 8; ++i) - delete _children[i]; - - delete[] _children; - } -} - - -template -inline int StaticOctree::countChildren() const -{ - int count = 0; - - for (int i = 0; i < 8; ++i) - count += _children != nullptr ? 1 + _children[i]->countChildren() : 0; - - return count; -} - + staticNode->m_children = std::make_unique::ChildrenType>(); -template -inline int StaticOctree::countObjects() const -{ - int count = nObjects; - - if (_children != nullptr) for (int i = 0; i < 8; ++i) - count += _children[i]->countObjects(); - - return count; -} - - -template -void StaticOctree::computeStatistics(std::vector& stats, unsigned int level) -{ - if (level >= stats.size()) - { - while (level >= stats.size()) - { - OctreeLevelStatistics levelStats; - levelStats.nodeCount = 0; - levelStats.objectCount = 0; - levelStats.size = 0.0; - stats.push_back(levelStats); - } + (*staticNode->m_children)[i] = (*m_children)[i]->rebuildAndSort(sortedObjects); } - stats[level].nodeCount++; - stats[level].objectCount += nObjects; - stats[level].size = 0.0; - - if (_children != nullptr) - { - for (int i = 0; i < 8; i++) - _children[i]->computeStatistics(stats, level + 1); - } + return staticNode; } diff --git a/src/celengine/pointstarrenderer.cpp b/src/celengine/pointstarrenderer.cpp index 1c4dfa66a6..fa64e23b28 100644 --- a/src/celengine/pointstarrenderer.cpp +++ b/src/celengine/pointstarrenderer.cpp @@ -8,12 +8,14 @@ // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. +#include "pointstarrenderer.h" + #include #include #include +#include "observer.h" #include "pointstarvertexbuffer.h" #include "render.h" -#include "pointstarrenderer.h" using namespace std; using namespace Eigen; @@ -30,7 +32,7 @@ static Vector3d astrocentricPosition(const UniversalCoord& pos, } PointStarRenderer::PointStarRenderer() : - ObjectRenderer(StarDistanceLimit) + ObjectRenderer(StarDistanceLimit) { } diff --git a/src/celengine/pointstarrenderer.h b/src/celengine/pointstarrenderer.h index be23f7b3a1..44b649c784 100644 --- a/src/celengine/pointstarrenderer.h +++ b/src/celengine/pointstarrenderer.h @@ -10,8 +10,10 @@ #pragma once -#include #include + +#include + #include "objectrenderer.h" #include "renderlistentry.h" @@ -30,15 +32,7 @@ constexpr inline float GlareOpacity = 0.65f; class PointStarRenderer : public ObjectRenderer { - public: -#if 0 - static constexpr const float StarDistanceLimit = 1.0e6f; - // Star disc size in pixels - static constexpr const float BaseStarDiscSize = 5.0f; - static constexpr const float MaxScaledDiscStarSize = 8.0f; - static constexpr const float GlareOpacity = 0.65f; -#endif - +public: PointStarRenderer(); void process(const Star &star, float distance, float appMag) override; diff --git a/src/celengine/stardb.h b/src/celengine/stardb.h index 7d20b9d5bf..444f0983d9 100644 --- a/src/celengine/stardb.h +++ b/src/celengine/stardb.h @@ -77,7 +77,7 @@ class StarDatabase std::unique_ptr stars; //NOSONAR std::unique_ptr namesDB; std::vector catalogNumberIndex; - StarOctree* octreeRoot; + std::unique_ptr octreeRoot; friend class StarDatabaseBuilder; }; diff --git a/src/celengine/stardbbuilder.cpp b/src/celengine/stardbbuilder.cpp index 4b6e7e9682..3b8f3527ca 100644 --- a/src/celengine/stardbbuilder.cpp +++ b/src/celengine/stardbbuilder.cpp @@ -1018,13 +1018,13 @@ StarDatabaseBuilder::buildOctree() StarDatabase::STAR_OCTREE_ROOT_SIZE * celestia::numbers::sqrt3_v); auto root = std::make_unique(Eigen::Vector3f(1000.0f, 1000.0f, 1000.0f), absMag); - for (const Star& star : unsortedStars) + for (Star& star : unsortedStars) root->insertObject(star, StarDatabase::STAR_OCTREE_ROOT_SIZE); GetLogger()->debug("Spatially sorting stars for improved locality of reference . . .\n"); - auto sortedStars = std::make_unique(unsortedStars.size()); + auto sortedStars = std::make_unique(unsortedStars.size()); //NOSONAR Star* firstStar = sortedStars.get(); - root->rebuildAndSort(starDB->octreeRoot, firstStar); + starDB->octreeRoot = root->rebuildAndSort(firstStar); GetLogger()->debug("{} stars total\nOctree has {} nodes and {} stars.\n", firstStar - sortedStars.get(), diff --git a/src/celengine/staroctree.cpp b/src/celengine/staroctree.cpp index 49df095bc4..767daaaba2 100644 --- a/src/celengine/staroctree.cpp +++ b/src/celengine/staroctree.cpp @@ -10,11 +10,16 @@ // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. -#include +#include "staroctree.h" -using namespace Eigen; +#include +#include namespace astro = celestia::astro; +namespace numbers = celestia::numbers; + +namespace +{ // Maximum permitted orbital radius for stars, in light years. Orbital // radii larger than this value are not guaranteed to give correct @@ -24,74 +29,64 @@ namespace astro = celestia::astro; // star is very faint, this estimate may not work when the star is // far from the barycenter. Thus, the star octree traversal will always // render stars with orbits that are closer than MAX_STAR_ORBIT_RADIUS. -static const float MAX_STAR_ORBIT_RADIUS = 1.0f; +constexpr float MAX_STAR_ORBIT_RADIUS = 1.0f; +} // end unnamed namespace // The octree node into which a star is placed is dependent on two properties: // its obsPosition and its luminosity--the fainter the star, the deeper the node // in which it will reside. Each node stores an absolute magnitude; no child // of the node is allowed contain a star brighter than this value, making it // possible to determine quickly whether or not to cull subtrees. - -bool starAbsoluteMagnitudePredicate(const Star& star, const float absMag) +template<> +bool +DynamicStarOctree::exceedsBrightnessThreshold(const Star& star, float absMag) { return star.getAbsoluteMagnitude() <= absMag; } - -bool starOrbitStraddlesNodesPredicate(const Vector3f& cellCenterPos, const Star& star, const float /*unused*/) +template<> +bool +DynamicStarOctree::isStraddling(const Eigen::Vector3f& cellCenterPos, const Star& star) { //checks if this star's orbit straddles child nodes float orbitalRadius = star.getOrbitalRadius(); if (orbitalRadius == 0.0f) return false; - Vector3f starPos = star.getPosition(); - + Eigen::Vector3f starPos = star.getPosition(); return (starPos - cellCenterPos).cwiseAbs().minCoeff() < orbitalRadius; } - -float starAbsoluteMagnitudeDecayFunction(const float excludingFactor) +template<> +float +DynamicStarOctree::applyDecay(float excludingFactor) { return astro::lumToAbsMag(astro::absMagToLum(excludingFactor) / 4.0f); } - template<> -DynamicStarOctree* DynamicStarOctree::getChild(const Star& obj, - const Vector3f& cellCenterPos) +DynamicStarOctree* +DynamicStarOctree::getChild(const Star& obj, const Eigen::Vector3f& cellCenterPos) const { - Vector3f objPos = obj.getPosition(); + Eigen::Vector3f objPos = obj.getPosition(); int child = 0; - child |= objPos.x() < cellCenterPos.x() ? 0 : XPos; - child |= objPos.y() < cellCenterPos.y() ? 0 : YPos; - child |= objPos.z() < cellCenterPos.z() ? 0 : ZPos; + child |= objPos.x() < cellCenterPos.x() ? 0 : XPos; + child |= objPos.y() < cellCenterPos.y() ? 0 : YPos; + child |= objPos.z() < cellCenterPos.z() ? 0 : ZPos; - return _children[child]; + return (*m_children)[child].get(); } - -// In testing, changing SPLIT_THRESHOLD from 100 to 50 nearly -// doubled the number of nodes in the tree, but provided only between a -// 0 to 5 percent frame rate improvement. -template<> unsigned int DynamicStarOctree::SPLIT_THRESHOLD = 75; -template<> DynamicStarOctree::LimitingFactorPredicate* - DynamicStarOctree::limitingFactorPredicate = starAbsoluteMagnitudePredicate; -template<> DynamicStarOctree::StraddlingPredicate* - DynamicStarOctree::straddlingPredicate = starOrbitStraddlesNodesPredicate; -template<> DynamicStarOctree::ExclusionFactorDecayFunction* - DynamicStarOctree::decayFunction = starAbsoluteMagnitudeDecayFunction; - - // total specialization of the StaticOctree template process*() methods for stars: template<> -void StarOctree::processVisibleObjects(StarHandler& processor, - const Vector3f& obsPosition, - const Hyperplane* frustumPlanes, - float limitingFactor, - float scale) const +void +StarOctree::processVisibleObjects(StarHandler& processor, + const PointType& obsPosition, + const PlaneType* frustumPlanes, + float limitingFactor, + float scale) const { // See if this node lies within the view frustum @@ -99,27 +94,26 @@ void StarOctree::processVisibleObjects(StarHandler& processor, // planes that define the infinite view frustum. for (unsigned int i = 0; i < 5; ++i) { - const Hyperplane& plane = frustumPlanes[i]; + const PlaneType& plane = frustumPlanes[i]; float r = scale * plane.normal().cwiseAbs().sum(); - if (plane.signedDistance(cellCenterPos) < -r) + if (plane.signedDistance(m_cellCenterPos) < -r) return; } // Compute the distance to node; this is equal to the distance to // the cellCenterPos of the node minus the boundingRadius of the node, scale * SQRT3. - float minDistance = (obsPosition - cellCenterPos).norm() - scale * StarOctree::SQRT3; + float minDistance = (obsPosition - m_cellCenterPos).norm() - scale * numbers::sqrt3_v; // Process the objects in this node float dimmest = minDistance > 0 ? astro::appToAbsMag(limitingFactor, minDistance) : 1000; - for (unsigned int i=0; iprocessVisibleObjects(processor, + (*m_children)[i]->processVisibleObjects(processor, obsPosition, frustumPlanes, limitingFactor, scale * 0.5f); - } } } } - template<> -void StarOctree::processCloseObjects(StarHandler& processor, - const Vector3f& obsPosition, - float boundingRadius, - float scale) const +void +StarOctree::processCloseObjects(StarHandler& processor, + const PointType& obsPosition, + float boundingRadius, + float scale) const { // Compute the distance to node; this is equal to the distance to // the cellCenterPos of the node minus the boundingRadius of the node, scale * SQRT3. - float nodeDistance = (obsPosition - cellCenterPos).norm() - scale * StarOctree::SQRT3; + float nodeDistance = (obsPosition - m_cellCenterPos).norm() - scale * numbers::sqrt3_v; if (nodeDistance > boundingRadius) return; @@ -167,28 +159,28 @@ void StarOctree::processCloseObjects(StarHandler& processor, float radiusSquared = boundingRadius * boundingRadius; // Check all the objects in the node. - for (unsigned int i = 0; i < nObjects; ++i) + for (std::uint32_t i = 0; i < m_nObjects; ++i) { - Star& obj = _firstObject[i]; - - if ((obsPosition - obj.getPosition()).squaredNorm() < radiusSquared) + const Star& obj = m_firstObject[i]; + PointType offset = obsPosition - obj.getPosition(); + if (offset.squaredNorm() < radiusSquared) { - float distance = (obsPosition - obj.getPosition()).norm(); - float appMag = obj.getApparentMagnitude(distance); + float distance = offset.norm(); + float appMag = obj.getApparentMagnitude(distance); processor.process(obj, distance, appMag); } } // Recurse into the child nodes - if (_children != nullptr) + if (m_children != nullptr) { for (int i = 0; i < 8; ++i) { - _children[i]->processCloseObjects(processor, - obsPosition, - boundingRadius, - scale * 0.5f); + (*m_children)[i]->processCloseObjects(processor, + obsPosition, + boundingRadius, + scale * 0.5f); } } } diff --git a/src/celengine/staroctree.h b/src/celengine/staroctree.h index 3d8c67268c..6e745bbb5c 100644 --- a/src/celengine/staroctree.h +++ b/src/celengine/staroctree.h @@ -12,10 +12,42 @@ #pragma once +#include + #include #include +using DynamicStarOctree = DynamicOctree; +using StarOctree = StaticOctree; +using StarHandler = OctreeProcessor; + +// In testing, changing SPLIT_THRESHOLD from 100 to 50 nearly +// doubled the number of nodes in the tree, but provided only between a +// 0 to 5 percent frame rate improvement. +template<> +const inline std::uint32_t DynamicStarOctree::SPLIT_THRESHOLD = 75; + +template<> +bool DynamicStarOctree::exceedsBrightnessThreshold(const Star&, float); + +template<> +bool DynamicStarOctree::isStraddling(const Eigen::Vector3f&, const Star&); + +template<> +float DynamicStarOctree::applyDecay(float excludingFactor); + +template<> +DynamicStarOctree* DynamicStarOctree::getChild(const Star&, const Eigen::Vector3f&) const; + +template<> +void StarOctree::processVisibleObjects(StarHandler&, + const PointType&, + const PlaneType*, + float, + float) const; -typedef DynamicOctree DynamicStarOctree; -typedef StaticOctree StarOctree; -typedef OctreeProcessor StarHandler; +template<> +void StarOctree::processCloseObjects(StarHandler&, + const PointType&, + float, + float) const; diff --git a/src/celengine/universe.cpp b/src/celengine/universe.cpp index 8d09cdb4bc..0a3f5acfc6 100644 --- a/src/celengine/universe.cpp +++ b/src/celengine/universe.cpp @@ -424,7 +424,7 @@ class DSOPicker : public DSOHandler float angle); ~DSOPicker() = default; - void process(DeepSkyObject* const &, double, float) override; + void process(const std::unique_ptr&, double, float) override; //NOSONAR public: Eigen::Vector3d pickOrigin; @@ -450,7 +450,7 @@ DSOPicker::DSOPicker(const Eigen::Vector3d& pickOrigin, void -DSOPicker::process(DeepSkyObject* const & dso, double /*unused*/, float /*unused*/) +DSOPicker::process(const std::unique_ptr& dso, double, float) //NOSONAR { if (!(dso->getRenderMask() & renderFlags) || !dso->isVisible() || !dso->isClickable()) return; @@ -473,7 +473,7 @@ DSOPicker::process(DeepSkyObject* const & dso, double /*unused*/, float /*unused if (sinAngle2 <= sinAngle2Closest) { sinAngle2Closest = std::max(sinAngle2, ANGULAR_RES); - pickedDSO = dso; + pickedDSO = dso.get(); } } @@ -488,7 +488,7 @@ class CloseDSOPicker : public DSOHandler float); ~CloseDSOPicker() = default; - void process(DeepSkyObject* const & dso, double distance, float appMag); + void process(const std::unique_ptr& dso, double distance, float appMag); //NOSONAR public: Eigen::Vector3d pickOrigin; @@ -517,7 +517,7 @@ CloseDSOPicker::CloseDSOPicker(const Eigen::Vector3d& pos, void -CloseDSOPicker::process(DeepSkyObject* const & dso, +CloseDSOPicker::process(const std::unique_ptr& dso, //NOSONAR double distance, float /*unused*/) { @@ -532,7 +532,7 @@ CloseDSOPicker::process(DeepSkyObject* const & dso, if ((pickOrigin - dso->getPosition()).norm() > dso->getRadius() && cosAngleToBoundCenter > largestCosAngle) { - closestDSO = dso; + closestDSO = dso.get(); largestCosAngle = cosAngleToBoundCenter; } } From 26db10ead8479a7ed0bc185b6b157a9a65942256 Mon Sep 17 00:00:00 2001 From: Andrew Tribick Date: Sun, 14 Jul 2024 17:10:37 +0200 Subject: [PATCH 05/15] Split DSODatabaseBuilder and DynamicOctree into separate files --- src/celengine/CMakeLists.txt | 3 + src/celengine/dsodb.cpp | 300 +++++--------------------------- src/celengine/dsodb.h | 65 +++---- src/celengine/dsodbbuilder.cpp | 297 +++++++++++++++++++++++++++++++ src/celengine/dsodbbuilder.h | 40 +++++ src/celengine/dsooctree.cpp | 46 ----- src/celengine/dsooctree.h | 16 -- src/celengine/octree.h | 184 +------------------- src/celengine/octreebuilder.h | 198 +++++++++++++++++++++ src/celengine/stardbbuilder.cpp | 55 ++++++ src/celengine/staroctree.cpp | 46 ----- src/celengine/staroctree.h | 19 -- src/celestia/loaddso.cpp | 9 +- 13 files changed, 679 insertions(+), 599 deletions(-) create mode 100644 src/celengine/dsodbbuilder.cpp create mode 100644 src/celengine/dsodbbuilder.h create mode 100644 src/celengine/octreebuilder.h diff --git a/src/celengine/CMakeLists.txt b/src/celengine/CMakeLists.txt index 3a7a1d40a9..a2a9cbceab 100644 --- a/src/celengine/CMakeLists.txt +++ b/src/celengine/CMakeLists.txt @@ -21,6 +21,8 @@ set(CELENGINE_SOURCES deepskyobj.h dsodb.cpp dsodb.h + dsodbbuilder.cpp + dsodbbuilder.h dsooctree.cpp dsooctree.h dsorenderer.cpp @@ -70,6 +72,7 @@ set(CELENGINE_SOURCES observer.cpp observer.h octree.h + octreebuilder.h opencluster.cpp opencluster.h orbitsampler.h diff --git a/src/celengine/dsodb.cpp b/src/celengine/dsodb.cpp index 3702190a8c..c26c057695 100644 --- a/src/celengine/dsodb.cpp +++ b/src/celengine/dsodb.cpp @@ -1,61 +1,54 @@ +// dsodb.cpp // -// C++ Implementation: dsodb -// -// Description: -// +// Copyright (C) 2005-2024, the Celestia Development Team // +// Original version: // Author: Toti , (C) 2005 // -// Copyright: See COPYING file that comes with this distribution -// -// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + +#include "dsodb.h" #include -#include -#include #include -#include #include #include -#include -#include "category.h" -#include "galaxy.h" -#include "globular.h" -#include "parser.h" -#include "dsodb.h" -#include "nebula.h" -#include "opencluster.h" -#include "value.h" +#include "name.h" using celestia::util::GetLogger; -namespace astro = celestia::astro; +DSODatabase::~DSODatabase() = default; -namespace +DSODatabase::DSODatabase(std::vector>&& DSOs, + std::unique_ptr&& octreeRoot, + std::unique_ptr&& namesDB, + std::vector&& catalogNumberIndex, + float avgAbsMag) : + m_DSOs(std::move(DSOs)), + m_octreeRoot(std::move(octreeRoot)), + m_namesDB(std::move(namesDB)), + m_catalogNumberIndex(std::move(catalogNumberIndex)), + m_avgAbsMag(avgAbsMag) { - -constexpr const float DSO_OCTREE_MAGNITUDE = 8.0f; -//constexpr const float DSO_EXTRA_ROOM = 0.01f; // Reserve 1% capacity for extra DSOs - // (useful as a complement of binary loaded DSOs) - -//constexpr char FILE_HEADER[] = "CEL_DSOs"; - -} // end unnamed namespace +} DeepSkyObject* DSODatabase::find(const AstroCatalog::IndexNumber catalogNumber) const { - auto it = std::lower_bound(catalogNumberIndex.begin(), - catalogNumberIndex.end(), + auto it = std::lower_bound(m_catalogNumberIndex.begin(), + m_catalogNumberIndex.end(), catalogNumber, [this](std::uint32_t idx, AstroCatalog::IndexNumber catNum) { - return DSOs[idx]->getIndex() < catNum; + return m_DSOs[idx]->getIndex() < catNum; }); - return (it != catalogNumberIndex.end() && DSOs[*it]->getIndex() == catalogNumber) - ? DSOs[*it].get() + return (it != m_catalogNumberIndex.end() && m_DSOs[*it]->getIndex() == catalogNumber) + ? m_DSOs[*it].get() : nullptr; } @@ -65,34 +58,27 @@ DSODatabase::find(std::string_view name, bool i18n) const if (name.empty()) return nullptr; - if (namesDB != nullptr) - { - AstroCatalog::IndexNumber catalogNumber = namesDB->getCatalogNumberByName(name, i18n); - if (catalogNumber != AstroCatalog::InvalidIndex) - return find(catalogNumber); - } - - return nullptr; + AstroCatalog::IndexNumber catalogNumber = m_namesDB->getCatalogNumberByName(name, i18n); + return catalogNumber == AstroCatalog::InvalidIndex + ? nullptr + : find(catalogNumber); } void DSODatabase::getCompletion(std::vector& completion, std::string_view name) const { // only named DSOs are supported by completion. - if (!name.empty() && namesDB != nullptr) - namesDB->getCompletion(completion, name); + if (!name.empty()) + m_namesDB->getCompletion(completion, name); } std::string DSODatabase::getDSOName(const DeepSkyObject* dso, [[maybe_unused]] bool i18n) const { - if (namesDB == nullptr) - return {}; - AstroCatalog::IndexNumber catalogNumber = dso->getIndex(); - auto iter = namesDB->getFirstNameIter(catalogNumber); - if (iter == namesDB->getFinalNameIter()) + auto iter = m_namesDB->getFirstNameIter(catalogNumber); + if (iter == m_namesDB->getFinalNameIter()) return {}; #ifdef ENABLE_NLS @@ -113,10 +99,10 @@ DSODatabase::getDSONameList(const DeepSkyObject* dso, const unsigned int maxName std::string dsoNames; auto catalogNumber = dso->getIndex(); - auto iter = namesDB->getFirstNameIter(catalogNumber); + auto iter = m_namesDB->getFirstNameIter(catalogNumber); unsigned int count = 0; - while (iter != namesDB->getFinalNameIter() && iter->first == catalogNumber && count < maxNames) + while (iter != m_namesDB->getFinalNameIter() && iter->first == catalogNumber && count < maxNames) { if (count != 0) dsoNames.append(" / "); @@ -158,11 +144,11 @@ DSODatabase::findVisibleDSOs(DSOHandler& dsoHandler, frustumPlanes[i] = Eigen::Hyperplane(planeNormals[i], obsPos); } - octreeRoot->processVisibleObjects(dsoHandler, - obsPos, - frustumPlanes, - limitingMag, - DSO_OCTREE_ROOT_SIZE); + m_octreeRoot->processVisibleObjects(dsoHandler, + obsPos, + frustumPlanes, + limitingMag, + DSO_OCTREE_ROOT_SIZE); } void @@ -170,202 +156,8 @@ DSODatabase::findCloseDSOs(DSOHandler& dsoHandler, const Eigen::Vector3d& obsPos, float radius) const { - octreeRoot->processCloseObjects(dsoHandler, - obsPos, - radius, - DSO_OCTREE_ROOT_SIZE); -} - -NameDatabase* -DSODatabase::getNameDatabase() const -{ - return namesDB.get(); -} - -void -DSODatabase::setNameDatabase(std::unique_ptr&& _namesDB) -{ - namesDB = std::move(_namesDB); -} - -bool -DSODatabase::load(std::istream& in, const fs::path& resourcePath) -{ - Tokenizer tokenizer(&in); - Parser parser(&tokenizer); - -#ifdef ENABLE_NLS - std::string s = resourcePath.string(); - const char *d = s.c_str(); - bindtextdomain(d, d); // domain name is the same as resource path -#endif - - while (tokenizer.nextToken() != Tokenizer::TokenEnd) - { - std::string objType; - if (auto tokenValue = tokenizer.getNameValue(); tokenValue.has_value()) - { - objType = *tokenValue; - } - else - { - GetLogger()->error("Error parsing deep sky catalog file.\n"); - return false; - } - - AstroCatalog::IndexNumber objCatalogNumber = nextAutoCatalogNumber--; - - tokenizer.nextToken(); - std::string objName; - if (auto tokenValue = tokenizer.getStringValue(); tokenValue.has_value()) - { - objName = *tokenValue; - } - else - { - GetLogger()->error("Error parsing deep sky catalog file: bad name.\n"); - return false; - } - - const Value objParamsValue = parser.readValue(); - const Hash* objParams = objParamsValue.getHash(); - if (objParams == nullptr) - { - GetLogger()->error("Error parsing deep sky catalog entry {}\n", objName.c_str()); - return false; - } - - std::unique_ptr obj; - if (compareIgnoringCase(objType, "Galaxy") == 0) - obj = std::make_unique(); - else if (compareIgnoringCase(objType, "Globular") == 0) - obj = std::make_unique(); - else if (compareIgnoringCase(objType, "Nebula") == 0) - obj = std::make_unique(); - else if (compareIgnoringCase(objType, "OpenCluster") == 0) - obj = std::make_unique(); - - if (obj != nullptr && obj->load(objParams, resourcePath)) - { - UserCategory::loadCategories(obj.get(), *objParams, DataDisposition::Add, resourcePath.string()); - - obj->setIndex(objCatalogNumber); - DSOs.emplace_back(std::move(obj)); - - if (namesDB != nullptr && !objName.empty()) - { - // List of names will replace any that already exist for - // this DSO. - namesDB->erase(objCatalogNumber); - - // Iterate through the string for names delimited - // by ':', and insert them into the DSO database. - // Note that db->add() will skip empty names. - std::string::size_type startPos = 0; - while (startPos != std::string::npos) - { - std::string::size_type next = objName.find(':', startPos); - std::string::size_type length = std::string::npos; - if (next != std::string::npos) - { - length = next - startPos; - ++next; - } - std::string DSOName = objName.substr(startPos, length); - namesDB->add(objCatalogNumber, DSOName); - startPos = next; - } - } - } - else - { - GetLogger()->warn("Bad Deep Sky Object definition--will continue parsing file.\n"); - return false; - } - } - - return true; -} - -void -DSODatabase::finish() -{ - buildOctree(); - buildIndexes(); - calcAvgAbsMag(); - - GetLogger()->info(_("Loaded {} deep space objects\n"), DSOs.size()); -} - -void -DSODatabase::buildOctree() -{ - GetLogger()->debug("Sorting DSOs into octree . . .\n"); - float absMag = astro::appToAbsMag(DSO_OCTREE_MAGNITUDE, DSO_OCTREE_ROOT_SIZE * celestia::numbers::sqrt3_v); - - // TODO: investigate using a different center--it's possible that more - // objects end up straddling the base level nodes when the center of the - // octree is at the origin. - auto root = std::make_unique(Eigen::Vector3d::Zero(), absMag); - for (auto& dso : DSOs) - root->insertObject(dso, DSO_OCTREE_ROOT_SIZE); - - GetLogger()->debug("Spatially sorting DSOs for improved locality of reference . . .\n"); - std::vector> sortedDSOs; - sortedDSOs.resize(DSOs.size()); - std::unique_ptr* firstDSO = sortedDSOs.data(); - - // The spatial sorting part is useless for DSOs since we - // are storing pointers to objects and not the objects themselves: - octreeRoot = root->rebuildAndSort(firstDSO); - - GetLogger()->debug("{} DSOs total.\nOctree has {} nodes and {} DSOs.\n", - firstDSO - sortedDSOs.data(), - UINT32_C(1) + octreeRoot->countChildren(), - octreeRoot->countObjects()); - - DSOs = std::move(sortedDSOs); -} - -void -DSODatabase::calcAvgAbsMag() -{ - std::uint32_t nDSOeff = size(); - for (const auto& dso : DSOs) - { - float DSOmag = dso->getAbsoluteMagnitude(); - - // take only DSO's with realistic AbsMag entry - // (> DSO_DEFAULT_ABS_MAGNITUDE) into account - if (DSOmag > DSO_DEFAULT_ABS_MAGNITUDE) - avgAbsMag += DSOmag; - else if (nDSOeff > 1) - --nDSOeff; - } - avgAbsMag /= static_cast(nDSOeff); -} - -void -DSODatabase::buildIndexes() -{ - // This should only be called once for the database - // assert(catalogNumberIndexes[0] == nullptr); - - GetLogger()->debug("Building catalog number indexes . . .\n"); - - catalogNumberIndex.resize(DSOs.size()); - std::iota(catalogNumberIndex.begin(), catalogNumberIndex.end(), UINT32_C(0)); - - std::sort(catalogNumberIndex.begin(), - catalogNumberIndex.end(), - [this](std::uint32_t idx0, std::uint32_t idx1) - { - return DSOs[idx0]->getIndex() < DSOs[idx1]->getIndex(); - }); -} - -float -DSODatabase::getAverageAbsoluteMagnitude() const -{ - return avgAbsMag; + m_octreeRoot->processCloseObjects(dsoHandler, + obsPos, + radius, + DSO_OCTREE_ROOT_SIZE); } diff --git a/src/celengine/dsodb.h b/src/celengine/dsodb.h index d3c715a2d8..94439cdf01 100644 --- a/src/celengine/dsodb.h +++ b/src/celengine/dsodb.h @@ -1,19 +1,18 @@ +// dsodb.h // -// C++ Interface: dsodb -// -// Description: -// +// Copyright (C) 2005-2024, the Celestia Development Team // +// Original version: // Author: Toti , (C) 2005 // -// Copyright: See COPYING file that comes with this distribution -// -// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. #pragma once #include -#include #include #include #include @@ -22,9 +21,11 @@ #include #include -#include -#include -#include +#include "dsooctree.h" + +class DeepSkyObject; +class DSODatabaseBuilder; +class NameDatabase; constexpr inline unsigned int MAX_DSO_NAMES = 10; @@ -34,9 +35,14 @@ constexpr inline float DSO_OCTREE_ROOT_SIZE = 1.0e11f; //NOTE: this one and starDatabase should be derived from a common base class since they share lots of code and functionality. class DSODatabase { - public: - DSODatabase() = default; - ~DSODatabase() = default; +public: + DSODatabase(std::vector>&&, + std::unique_ptr&&, + std::unique_ptr&&, + std::vector&&, + float); + + ~DSODatabase(); DeepSkyObject* getDSO(const std::uint32_t) const; std::uint32_t size() const; @@ -60,36 +66,33 @@ class DSODatabase std::string getDSOName (const DeepSkyObject*, bool i18n = false) const; std::string getDSONameList(const DeepSkyObject*, const unsigned int maxNames = MAX_DSO_NAMES) const; - NameDatabase* getNameDatabase() const; - void setNameDatabase(std::unique_ptr&&); - - bool load(std::istream&, const fs::path& resourcePath = fs::path()); - void finish(); - float getAverageAbsoluteMagnitude() const; private: - void buildIndexes(); - void buildOctree(); - void calcAvgAbsMag(); + std::vector> m_DSOs; + std::unique_ptr m_octreeRoot; + std::unique_ptr m_namesDB; + std::vector m_catalogNumberIndex; - std::vector> DSOs; - std::unique_ptr namesDB; - std::vector catalogNumberIndex; - std::unique_ptr octreeRoot; - AstroCatalog::IndexNumber nextAutoCatalogNumber{ 0xfffffffe }; + float m_avgAbsMag{ 0.0f }; - float avgAbsMag{ 0.0f }; + friend class DSODatabaseBuilder; }; inline DeepSkyObject* DSODatabase::getDSO(const std::uint32_t n) const { - return DSOs[n].get(); + return m_DSOs[n].get(); } inline std::uint32_t DSODatabase::size() const { - return static_cast(DSOs.size()); + return static_cast(m_DSOs.size()); +} + +inline float +DSODatabase::getAverageAbsoluteMagnitude() const +{ + return m_avgAbsMag; } diff --git a/src/celengine/dsodbbuilder.cpp b/src/celengine/dsodbbuilder.cpp new file mode 100644 index 0000000000..0ae417cd64 --- /dev/null +++ b/src/celengine/dsodbbuilder.cpp @@ -0,0 +1,297 @@ +// dsodbbuilder.cpp +// +// Copyright (C) 2005-2024, the Celestia Development Team +// +// Split from dsodb.cpp - original version: +// Author: Toti , (C) 2005 +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + +#include "dsodbbuilder.h" + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include "category.h" +#include "deepskyobj.h" +#include "dsodb.h" +#include "dsooctree.h" +#include "galaxy.h" +#include "globular.h" +#include "hash.h" +#include "nebula.h" +#include "octreebuilder.h" +#include "opencluster.h" +#include "parser.h" +#include "value.h" + +namespace astro = celestia::astro; + +using DynamicDSOOctree = DynamicOctree, double>; + +using celestia::util::GetLogger; + +template<> +const inline std::uint32_t DynamicDSOOctree::SPLIT_THRESHOLD = 10; + +// The octree node into which a dso is placed is dependent on two properties: +// its obsPosition and its luminosity--the fainter the dso, the deeper the node +// in which it will reside. Each node stores an absolute magnitude; no child +// of the node is allowed contain a dso brighter than this value, making it +// possible to determine quickly whether or not to cull subtrees. + +template<> +bool +DynamicDSOOctree::exceedsBrightnessThreshold(const std::unique_ptr& dso, //NOSONAR + float absMag) +{ + return dso->getAbsoluteMagnitude() <= absMag; +} + +template<> +bool +DynamicDSOOctree::isStraddling(const Eigen::Vector3d& cellCenterPos, + const std::unique_ptr& dso) //NOSONAR +{ + //checks if this dso's radius straddles child nodes + float dsoRadius = dso->getBoundingSphereRadius(); + return (dso->getPosition() - cellCenterPos).cwiseAbs().minCoeff() < dsoRadius; +} + +template<> +float +DynamicDSOOctree::applyDecay(float excludingFactor) +{ + return excludingFactor + 0.5f; +} + +template<> +DynamicDSOOctree* +DynamicDSOOctree::getChild(const std::unique_ptr& obj, //NOSONAR + const PointType& cellCenterPos) const +{ + PointType objPos = obj->getPosition(); + + unsigned int child = 0U; + child |= objPos.x() < cellCenterPos.x() ? 0U : OctreeXPos; + child |= objPos.y() < cellCenterPos.y() ? 0U : OctreeYPos; + child |= objPos.z() < cellCenterPos.z() ? 0U : OctreeZPos; + + return (*m_children)[child].get(); +} + +namespace +{ + +constexpr float DSO_OCTREE_MAGNITUDE = 8.0f; + +std::unique_ptr +createDSO(std::string_view objType) +{ + if (compareIgnoringCase(objType, "Galaxy") == 0) + return std::make_unique(); + if (compareIgnoringCase(objType, "Globular") == 0) + return std::make_unique(); + if (compareIgnoringCase(objType, "Nebula") == 0) + return std::make_unique(); + if (compareIgnoringCase(objType, "OpenCluster") == 0) + return std::make_unique(); + return nullptr; +} + +float +calcAvgAbsMag(const std::vector>& DSOs) +{ + auto nDSOeff = DSOs.size(); + float avgAbsMag = 0.0f; + for (const auto& dso : DSOs) + { + float DSOmag = dso->getAbsoluteMagnitude(); + + // take only DSO's with realistic AbsMag entry + // (> DSO_DEFAULT_ABS_MAGNITUDE) into account + if (DSOmag > DSO_DEFAULT_ABS_MAGNITUDE) + avgAbsMag += DSOmag; + else if (nDSOeff > 1) + --nDSOeff; + } + + return avgAbsMag / static_cast(nDSOeff); +} + +void +addName(NameDatabase* namesDB, AstroCatalog::IndexNumber objCatalogNumber, std::string_view objName) +{ + if (objName.empty()) + return; + + // List of names will replace any that already exist for + // this DSO. + namesDB->erase(objCatalogNumber); + + // Iterate through the string for names delimited + // by ':', and insert them into the DSO database. + // Note that db->add() will skip empty names. + while (!objName.empty()) + { + auto pos = objName.find(':'); + namesDB->add(objCatalogNumber, objName.substr(0, pos)); + if (pos == std::string_view::npos) + break; + + objName = objName.substr(pos + 1); + } +} + +std::unique_ptr +buildOctree(std::vector>& DSOs) +{ + GetLogger()->debug("Sorting DSOs into octree . . .\n"); + float absMag = astro::appToAbsMag(DSO_OCTREE_MAGNITUDE, DSO_OCTREE_ROOT_SIZE * celestia::numbers::sqrt3_v); + + auto root = std::make_unique(Eigen::Vector3d::Zero(), absMag); + for (auto& dso : DSOs) + root->insertObject(dso, DSO_OCTREE_ROOT_SIZE); + + GetLogger()->debug("Spatially sorting DSOs for improved locality of reference . . .\n"); + std::vector> sortedDSOs; + sortedDSOs.resize(DSOs.size()); + std::unique_ptr* firstDSO = sortedDSOs.data(); + + // The spatial sorting part is useless for DSOs since we + // are storing pointers to objects and not the objects themselves: + auto octreeRoot = root->rebuildAndSort(firstDSO); + + GetLogger()->debug("{} DSOs total.\nOctree has {} nodes and {} DSOs.\n", + firstDSO - sortedDSOs.data(), + UINT32_C(1) + octreeRoot->countChildren(), + octreeRoot->countObjects()); + + DSOs = std::move(sortedDSOs); + + return octreeRoot; +} + +std::vector +buildCatalogNumberIndex(const std::vector>& DSOs) +{ + GetLogger()->debug("Building catalog number indexes . . .\n"); + + std::vector catalogNumberIndex(DSOs.size(), UINT32_C(0)); + std::iota(catalogNumberIndex.begin(), catalogNumberIndex.end(), UINT32_C(0)); + + std::sort(catalogNumberIndex.begin(), + catalogNumberIndex.end(), + [&DSOs](std::uint32_t idx0, std::uint32_t idx1) + { + return DSOs[idx0]->getIndex() < DSOs[idx1]->getIndex(); + }); + + return catalogNumberIndex; +} + +} // end unnamed namespace + +DSODatabaseBuilder::~DSODatabaseBuilder() = default; + +bool +DSODatabaseBuilder::load(std::istream& in, const fs::path& resourcePath) +{ + Tokenizer tokenizer(&in); + Parser parser(&tokenizer); + +#ifdef ENABLE_NLS + std::string s = resourcePath.string(); + const char *d = s.c_str(); + bindtextdomain(d, d); // domain name is the same as resource path +#endif + + while (tokenizer.nextToken() != Tokenizer::TokenEnd) + { + std::string objType; + if (auto tokenValue = tokenizer.getNameValue(); tokenValue.has_value()) + { + objType = *tokenValue; + } + else + { + GetLogger()->error("Error parsing deep sky catalog file.\n"); + return false; + } + + tokenizer.nextToken(); + std::string objName; + if (auto tokenValue = tokenizer.getStringValue(); tokenValue.has_value()) + { + objName = *tokenValue; + } + else + { + GetLogger()->error("Error parsing deep sky catalog file: bad name.\n"); + return false; + } + + const Value objParamsValue = parser.readValue(); + const Hash* objParams = objParamsValue.getHash(); + if (objParams == nullptr) + { + GetLogger()->error("Error parsing deep sky catalog entry {}\n", objName); + return false; + } + + std::unique_ptr obj = createDSO(objType); + + if (obj == nullptr || !obj->load(objParams, resourcePath)) + { + GetLogger()->warn("Bad Deep Sky Object definition--will continue parsing file.\n"); + continue; + } + + UserCategory::loadCategories(obj.get(), *objParams, DataDisposition::Add, resourcePath.string()); + + if (nextAutoCatalogNumber == AstroCatalog::InvalidIndex) + { + GetLogger()->error("Exceeded maximum DSO count.\n"); + break; + } + + AstroCatalog::IndexNumber objCatalogNumber = nextAutoCatalogNumber; + ++nextAutoCatalogNumber; + + obj->setIndex(objCatalogNumber); + DSOs.emplace_back(std::move(obj)); + + addName(namesDB.get(), objCatalogNumber, objName); + } + + return true; +} + +std::unique_ptr +DSODatabaseBuilder::finish() +{ + auto octreeRoot = buildOctree(DSOs); + auto catalogNumberIndex = buildCatalogNumberIndex(DSOs); + float avgAbsMag = calcAvgAbsMag(DSOs); + + GetLogger()->info(_("Loaded {} deep space objects\n"), DSOs.size()); + + return std::make_unique(std::move(DSOs), + std::move(octreeRoot), + std::move(namesDB), + std::move(catalogNumberIndex), + avgAbsMag); +} diff --git a/src/celengine/dsodbbuilder.h b/src/celengine/dsodbbuilder.h new file mode 100644 index 0000000000..4aeb7d73a3 --- /dev/null +++ b/src/celengine/dsodbbuilder.h @@ -0,0 +1,40 @@ +// dsodbbuilder.h +// +// Copyright (C) 2005-2024, the Celestia Development Team +// +// Split from dsodb.h - original version: +// Author: Toti , (C) 2005 +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + +#pragma once + +#include +#include +#include + +#include +#include "astroobj.h" +#include "name.h" + +class DeepSkyObject; +class DSODatabase; +class NameDatabase; + +class DSODatabaseBuilder +{ +public: + DSODatabaseBuilder() = default; + ~DSODatabaseBuilder(); + + bool load(std::istream&, const fs::path& resourcePath = fs::path()); + std::unique_ptr finish(); + +private: + std::vector> DSOs; + std::unique_ptr namesDB{ std::make_unique() }; + AstroCatalog::IndexNumber nextAutoCatalogNumber{ 0 }; +}; diff --git a/src/celengine/dsooctree.cpp b/src/celengine/dsooctree.cpp index 0e155d1287..123791745a 100644 --- a/src/celengine/dsooctree.cpp +++ b/src/celengine/dsooctree.cpp @@ -18,52 +18,6 @@ namespace astro = celestia::astro; namespace numbers = celestia::numbers; -// The octree node into which a dso is placed is dependent on two properties: -// its obsPosition and its luminosity--the fainter the dso, the deeper the node -// in which it will reside. Each node stores an absolute magnitude; no child -// of the node is allowed contain a dso brighter than this value, making it -// possible to determine quickly whether or not to cull subtrees. - -template<> -bool -DynamicDSOOctree::exceedsBrightnessThreshold(const std::unique_ptr& dso, //NOSONAR - float absMag) -{ - return dso->getAbsoluteMagnitude() <= absMag; -} - -template<> -bool -DynamicDSOOctree::isStraddling(const Eigen::Vector3d& cellCenterPos, - const std::unique_ptr& dso) //NOSONAR -{ - //checks if this dso's radius straddles child nodes - float dsoRadius = dso->getBoundingSphereRadius(); - return (dso->getPosition() - cellCenterPos).cwiseAbs().minCoeff() < dsoRadius; -} - -template<> -float -DynamicDSOOctree::applyDecay(float excludingFactor) -{ - return excludingFactor + 0.5f; -} - -template<> -DynamicDSOOctree* -DynamicDSOOctree::getChild(const std::unique_ptr& obj, //NOSONAR - const PointType& cellCenterPos) const -{ - PointType objPos = obj->getPosition(); - - int child = 0; - child |= objPos.x() < cellCenterPos.x() ? 0 : XPos; - child |= objPos.y() < cellCenterPos.y() ? 0 : YPos; - child |= objPos.z() < cellCenterPos.z() ? 0 : ZPos; - - return (*m_children)[child].get(); -} - // total specialization of the StaticOctree template process*() methods for DSOs: template<> void diff --git a/src/celengine/dsooctree.h b/src/celengine/dsooctree.h index db9033854a..b7ef947e1b 100644 --- a/src/celengine/dsooctree.h +++ b/src/celengine/dsooctree.h @@ -18,25 +18,9 @@ #include "deepskyobj.h" #include "octree.h" -using DynamicDSOOctree = DynamicOctree, double>; using DSOOctree = StaticOctree, double>; using DSOHandler = OctreeProcessor, double>; -template<> -const inline std::uint32_t DynamicDSOOctree::SPLIT_THRESHOLD = 10; - -template<> -bool DynamicDSOOctree::exceedsBrightnessThreshold(const std::unique_ptr&, float); //NOSONAR - -template<> -bool DynamicDSOOctree::isStraddling(const Eigen::Vector3d&, const std::unique_ptr&); //NOSONAR - -template<> -float DynamicDSOOctree::applyDecay(float); - -template<> -DynamicDSOOctree* DynamicDSOOctree::getChild(const std::unique_ptr&, const PointType&) const; //NOSONAR - template<> void DSOOctree::processVisibleObjects(DSOHandler&, const PointType&, diff --git a/src/celengine/octree.h b/src/celengine/octree.h index ec413f77da..0d81a270a1 100644 --- a/src/celengine/octree.h +++ b/src/celengine/octree.h @@ -2,7 +2,7 @@ // // Octree-based visibility determination for objects. // -// Copyright (C) 2001-2009, Celestia Development Team +// Copyright (C) 2001-2024, Celestia Development Team // Original version by Chris Laurel // // This program is free software; you can redistribute it and/or @@ -15,12 +15,11 @@ #include #include #include -#include #include #include -// The DynamicOctree and StaticOctree template arguments are: +// The StaticOctree template arguments are: // OBJ: object hanging from the node, // PREC: floating point precision of the culling operations at node level. // The hierarchy of octree nodes is built using a single precision value (excludingFactor), which relates to an @@ -129,182 +128,3 @@ StaticOctree::countObjects() const return count; } - -// There are two classes implemented in this module: StaticOctree and -// DynamicOctree. The DynamicOctree is built first by inserting -// objects from a database or catalog and is then 'compiled' into a StaticOctree. -// In the process of building the StaticOctree, the original object database is -// reorganized, with objects in the same octree node all placed adjacent to each -// other. This spatial sorting of the objects dramatically improves the -// performance of octree operations through much more coherent memory access. -enum -{ - XPos = 1, - YPos = 2, - ZPos = 4, -}; - -template -class DynamicOctree -{ -public: - using PointType = Eigen::Matrix; - - DynamicOctree(const PointType& cellCenterPos, - float exclusionFactor); - - void insertObject(OBJ&, const PREC); - std::unique_ptr> rebuildAndSort(OBJ*&); - -private: - using ObjectList = std::vector; - - static const unsigned int SPLIT_THRESHOLD; - - static bool exceedsBrightnessThreshold(const OBJ&, float); - static bool isStraddling(const PointType&, const OBJ&); - static float applyDecay(float); - -private: - using ChildrenType = std::array, 8>; - - void add(OBJ&); - void split(const PREC); - void sortIntoChildNodes(); - DynamicOctree* getChild(const OBJ&, const PointType&) const; - - std::unique_ptr m_children; - PointType m_cellCenterPos; - float m_exclusionFactor; - std::unique_ptr m_objects; -}; - -// The SPLIT_THRESHOLD is the number of objects a node must contain before its -// children are generated. Increasing this number will decrease the number of -// octree nodes in the tree, which will use less memory but make culling less -// efficient. -template -DynamicOctree::DynamicOctree(const PointType& cellCenterPos, - float exclusionFactor): - m_cellCenterPos(cellCenterPos), - m_exclusionFactor(exclusionFactor) -{ -} - -template -void -DynamicOctree::insertObject(OBJ& obj, const PREC scale) -{ - // If the object can't be placed into this node's children, then put it here: - if (exceedsBrightnessThreshold(obj, m_exclusionFactor) || isStraddling(m_cellCenterPos, obj)) - { - add(obj); - return; - } - - // If we haven't allocated child nodes yet, try to fit - // the object in this node, even though it could be put - // in a child. Only if there are more than SPLIT_THRESHOLD - // objects in the node will we attempt to place the - // object into a child node. This is done in order - // to avoid having the octree degenerate into one object - // per node. - if (m_children == nullptr) - { - if (m_objects == nullptr || m_objects->size() < SPLIT_THRESHOLD) - { - add(obj); - return; - } - - split(scale * 0.5f); - } - - // We've already allocated child nodes; place the object - // into the appropriate one. - getChild(obj, m_cellCenterPos)->insertObject(obj, scale * (PREC) 0.5); -} - -template -void -DynamicOctree::add(OBJ& obj) -{ - if (m_objects == nullptr) - m_objects = std::make_unique(); - - m_objects->push_back(&obj); -} - -template -void -DynamicOctree::split(const PREC scale) -{ - m_children = std::make_unique(); - for (int i = 0; i < 8; ++i) - { - PointType centerPos = m_cellCenterPos - + PointType(((i & XPos) != 0) ? scale : -scale, - ((i & YPos) != 0) ? scale : -scale, - ((i & ZPos) != 0) ? scale : -scale); - - (*m_children)[i] = std::make_unique(centerPos, applyDecay(m_exclusionFactor)); - } - - sortIntoChildNodes(); -} - -// Sort this node's objects into objects that can remain here, -// and objects that should be placed into one of the eight -// child nodes. -template -void -DynamicOctree::sortIntoChildNodes() -{ - auto writeIt = m_objects->begin(); - auto endIt = m_objects->end(); - - for (auto readIt = writeIt; readIt != endIt; ++readIt) - { - OBJ& obj = **readIt; - if (exceedsBrightnessThreshold(obj, m_exclusionFactor) || isStraddling(m_cellCenterPos, obj)) - { - *writeIt = *readIt; - ++writeIt; - } - else - { - getChild(obj, m_cellCenterPos)->add(obj); - } - } - - m_objects->erase(writeIt, endIt); -} - -template -std::unique_ptr> -DynamicOctree::rebuildAndSort(OBJ*& sortedObjects) -{ - OBJ* firstObject = sortedObjects; - - if (m_objects != nullptr) - { - for (OBJ* obj : *m_objects) - { - *sortedObjects = std::move(*obj); - ++sortedObjects; - } - } - - auto nObjects = static_cast(sortedObjects - firstObject); - auto staticNode = std::make_unique>(m_cellCenterPos, m_exclusionFactor, firstObject, nObjects); - - if (m_children != nullptr) - { - staticNode->m_children = std::make_unique::ChildrenType>(); - - for (int i = 0; i < 8; ++i) - (*staticNode->m_children)[i] = (*m_children)[i]->rebuildAndSort(sortedObjects); - } - - return staticNode; -} diff --git a/src/celengine/octreebuilder.h b/src/celengine/octreebuilder.h new file mode 100644 index 0000000000..e248fea2b4 --- /dev/null +++ b/src/celengine/octreebuilder.h @@ -0,0 +1,198 @@ +// octreebuilder.h +// +// Octree-based visibility determination for objects. +// +// Copyright (C) 2001-2024, Celestia Development Team +// +// Split from octree.h: +// Original version by Chris Laurel +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. + +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include "octree.h" + +constexpr inline unsigned int OctreeXPos = 1; +constexpr inline unsigned int OctreeYPos = 2; +constexpr inline unsigned int OctreeZPos = 4; + +// The DynamicOctree is built first by inserting objects from a database or +// catalog and is then 'compiled' into a StaticOctree. In the process of +// building the StaticOctree, the original object database is reorganized, +// with objects in the same octree node all placed adjacent to each other. +// This spatial sorting of the objects dramatically improves the performance +// of octree operations through much more coherent memory access. +template +class DynamicOctree +{ +public: + using PointType = Eigen::Matrix; + + DynamicOctree(const PointType& cellCenterPos, + float exclusionFactor); + + void insertObject(OBJ&, const PREC); + std::unique_ptr> rebuildAndSort(OBJ*&); + +private: + using ObjectList = std::vector; + using ChildrenType = std::array, 8>; + + static const unsigned int SPLIT_THRESHOLD; + + static bool exceedsBrightnessThreshold(const OBJ&, float); + static bool isStraddling(const PointType&, const OBJ&); + static float applyDecay(float); + + void add(OBJ&); + void split(const PREC); + void sortIntoChildNodes(); + DynamicOctree* getChild(const OBJ&, const PointType&) const; + + std::unique_ptr m_children; + PointType m_cellCenterPos; + float m_exclusionFactor; + std::unique_ptr m_objects; +}; + +// The SPLIT_THRESHOLD is the number of objects a node must contain before its +// children are generated. Increasing this number will decrease the number of +// octree nodes in the tree, which will use less memory but make culling less +// efficient. +template +DynamicOctree::DynamicOctree(const PointType& cellCenterPos, + float exclusionFactor): + m_cellCenterPos(cellCenterPos), + m_exclusionFactor(exclusionFactor) +{ +} + +template +void +DynamicOctree::insertObject(OBJ& obj, const PREC scale) +{ + // If the object can't be placed into this node's children, then put it here: + if (exceedsBrightnessThreshold(obj, m_exclusionFactor) || isStraddling(m_cellCenterPos, obj)) + { + add(obj); + return; + } + + // If we haven't allocated child nodes yet, try to fit + // the object in this node, even though it could be put + // in a child. Only if there are more than SPLIT_THRESHOLD + // objects in the node will we attempt to place the + // object into a child node. This is done in order + // to avoid having the octree degenerate into one object + // per node. + if (m_children == nullptr) + { + if (m_objects == nullptr || m_objects->size() < SPLIT_THRESHOLD) + { + add(obj); + return; + } + + split(scale * 0.5f); + } + + // We've already allocated child nodes; place the object + // into the appropriate one. + getChild(obj, m_cellCenterPos)->insertObject(obj, scale * PREC(0.5)); +} + +template +void +DynamicOctree::add(OBJ& obj) +{ + if (m_objects == nullptr) + m_objects = std::make_unique(); + + m_objects->push_back(&obj); +} + +template +void +DynamicOctree::split(const PREC scale) +{ + m_children = std::make_unique(); + for (unsigned int i = 0U; i < 8U; ++i) + { + PointType centerPos = m_cellCenterPos + + PointType(((i & OctreeXPos) != 0U) ? scale : -scale, + ((i & OctreeYPos) != 0U) ? scale : -scale, + ((i & OctreeZPos) != 0U) ? scale : -scale); + + (*m_children)[i] = std::make_unique(centerPos, applyDecay(m_exclusionFactor)); + } + + sortIntoChildNodes(); +} + +// Sort this node's objects into objects that can remain here, +// and objects that should be placed into one of the eight +// child nodes. +template +void +DynamicOctree::sortIntoChildNodes() +{ + auto writeIt = m_objects->begin(); + auto endIt = m_objects->end(); + + for (auto readIt = writeIt; readIt != endIt; ++readIt) + { + OBJ& obj = **readIt; + if (exceedsBrightnessThreshold(obj, m_exclusionFactor) || isStraddling(m_cellCenterPos, obj)) + { + *writeIt = *readIt; + ++writeIt; + } + else + { + getChild(obj, m_cellCenterPos)->add(obj); + } + } + + m_objects->erase(writeIt, endIt); +} + +template +std::unique_ptr> +DynamicOctree::rebuildAndSort(OBJ*& sortedObjects) +{ + OBJ* firstObject = sortedObjects; + + if (m_objects != nullptr) + { + for (OBJ* obj : *m_objects) + { + *sortedObjects = std::move(*obj); + ++sortedObjects; + } + } + + auto nObjects = static_cast(sortedObjects - firstObject); + auto staticNode = std::make_unique>(m_cellCenterPos, m_exclusionFactor, firstObject, nObjects); + + if (m_children != nullptr) + { + staticNode->m_children = std::make_unique::ChildrenType>(); + + for (unsigned int i = 0U; i < 8U; ++i) + (*staticNode->m_children)[i] = (*m_children)[i]->rebuildAndSort(sortedObjects); + } + + return staticNode; +} diff --git a/src/celengine/stardbbuilder.cpp b/src/celengine/stardbbuilder.cpp index 3b8f3527ca..e0db1ea3b0 100644 --- a/src/celengine/stardbbuilder.cpp +++ b/src/celengine/stardbbuilder.cpp @@ -38,6 +38,7 @@ #include #include "hash.h" #include "meshmanager.h" +#include "octreebuilder.h" #include "parser.h" #include "stardb.h" #include "stellarclass.h" @@ -51,8 +52,62 @@ namespace ephem = celestia::ephem; namespace math = celestia::math; namespace util = celestia::util; +using DynamicStarOctree = DynamicOctree; + using util::GetLogger; +// In testing, changing SPLIT_THRESHOLD from 100 to 50 nearly +// doubled the number of nodes in the tree, but provided only between a +// 0 to 5 percent frame rate improvement. +template<> +const inline std::uint32_t DynamicStarOctree::SPLIT_THRESHOLD = 75; + +// The octree node into which a star is placed is dependent on two properties: +// its obsPosition and its luminosity--the fainter the star, the deeper the node +// in which it will reside. Each node stores an absolute magnitude; no child +// of the node is allowed contain a star brighter than this value, making it +// possible to determine quickly whether or not to cull subtrees. +template<> +bool +DynamicStarOctree::exceedsBrightnessThreshold(const Star& star, float absMag) +{ + return star.getAbsoluteMagnitude() <= absMag; +} + +template<> +bool +DynamicStarOctree::isStraddling(const Eigen::Vector3f& cellCenterPos, const Star& star) +{ + //checks if this star's orbit straddles child nodes + float orbitalRadius = star.getOrbitalRadius(); + if (orbitalRadius == 0.0f) + return false; + + Eigen::Vector3f starPos = star.getPosition(); + return (starPos - cellCenterPos).cwiseAbs().minCoeff() < orbitalRadius; +} + +template<> +float +DynamicStarOctree::applyDecay(float excludingFactor) +{ + return astro::lumToAbsMag(astro::absMagToLum(excludingFactor) / 4.0f); +} + +template<> +DynamicStarOctree* +DynamicStarOctree::getChild(const Star& obj, const Eigen::Vector3f& cellCenterPos) const +{ + Eigen::Vector3f objPos = obj.getPosition(); + + unsigned int child = 0U; + child |= objPos.x() < cellCenterPos.x() ? 0U : OctreeXPos; + child |= objPos.y() < cellCenterPos.y() ? 0U : OctreeYPos; + child |= objPos.z() < cellCenterPos.z() ? 0U : OctreeZPos; + + return (*m_children)[child].get(); +} + struct StarDatabaseBuilder::StcHeader { explicit StcHeader(const fs::path&); diff --git a/src/celengine/staroctree.cpp b/src/celengine/staroctree.cpp index 767daaaba2..45efe5c921 100644 --- a/src/celengine/staroctree.cpp +++ b/src/celengine/staroctree.cpp @@ -33,52 +33,6 @@ constexpr float MAX_STAR_ORBIT_RADIUS = 1.0f; } // end unnamed namespace -// The octree node into which a star is placed is dependent on two properties: -// its obsPosition and its luminosity--the fainter the star, the deeper the node -// in which it will reside. Each node stores an absolute magnitude; no child -// of the node is allowed contain a star brighter than this value, making it -// possible to determine quickly whether or not to cull subtrees. -template<> -bool -DynamicStarOctree::exceedsBrightnessThreshold(const Star& star, float absMag) -{ - return star.getAbsoluteMagnitude() <= absMag; -} - -template<> -bool -DynamicStarOctree::isStraddling(const Eigen::Vector3f& cellCenterPos, const Star& star) -{ - //checks if this star's orbit straddles child nodes - float orbitalRadius = star.getOrbitalRadius(); - if (orbitalRadius == 0.0f) - return false; - - Eigen::Vector3f starPos = star.getPosition(); - return (starPos - cellCenterPos).cwiseAbs().minCoeff() < orbitalRadius; -} - -template<> -float -DynamicStarOctree::applyDecay(float excludingFactor) -{ - return astro::lumToAbsMag(astro::absMagToLum(excludingFactor) / 4.0f); -} - -template<> -DynamicStarOctree* -DynamicStarOctree::getChild(const Star& obj, const Eigen::Vector3f& cellCenterPos) const -{ - Eigen::Vector3f objPos = obj.getPosition(); - - int child = 0; - child |= objPos.x() < cellCenterPos.x() ? 0 : XPos; - child |= objPos.y() < cellCenterPos.y() ? 0 : YPos; - child |= objPos.z() < cellCenterPos.z() ? 0 : ZPos; - - return (*m_children)[child].get(); -} - // total specialization of the StaticOctree template process*() methods for stars: template<> void diff --git a/src/celengine/staroctree.h b/src/celengine/staroctree.h index 6e745bbb5c..fb28dd60aa 100644 --- a/src/celengine/staroctree.h +++ b/src/celengine/staroctree.h @@ -17,28 +17,9 @@ #include #include -using DynamicStarOctree = DynamicOctree; using StarOctree = StaticOctree; using StarHandler = OctreeProcessor; -// In testing, changing SPLIT_THRESHOLD from 100 to 50 nearly -// doubled the number of nodes in the tree, but provided only between a -// 0 to 5 percent frame rate improvement. -template<> -const inline std::uint32_t DynamicStarOctree::SPLIT_THRESHOLD = 75; - -template<> -bool DynamicStarOctree::exceedsBrightnessThreshold(const Star&, float); - -template<> -bool DynamicStarOctree::isStraddling(const Eigen::Vector3f&, const Star&); - -template<> -float DynamicStarOctree::applyDecay(float excludingFactor); - -template<> -DynamicStarOctree* DynamicStarOctree::getChild(const Star&, const Eigen::Vector3f&) const; - template<> void StarOctree::processVisibleObjects(StarHandler&, const PointType&, diff --git a/src/celestia/loaddso.cpp b/src/celestia/loaddso.cpp index 691f4bcc6c..deef696836 100644 --- a/src/celestia/loaddso.cpp +++ b/src/celestia/loaddso.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -20,13 +21,12 @@ namespace celestia { -using DeepSkyLoader = CatalogLoader; +using DeepSkyLoader = CatalogLoader; std::unique_ptr loadDSO(const CelestiaConfig &config, ProgressNotifier *progressNotifier) { - auto dsoDB = std::make_unique(); - dsoDB->setNameDatabase(std::make_unique()); + auto dsoDB = std::make_unique(); // TRANSLATORS: this is a part of phrases "Loading {} catalog", "Skipping {} catalog" const char *typeDesc = C_("catalog", "deep sky"); @@ -46,8 +46,7 @@ loadDSO(const CelestiaConfig &config, ProgressNotifier *progressNotifier) // Next, read all the deep sky files in the extras directories loader.loadExtras(config.paths.extrasDirs); - dsoDB->finish(); - return dsoDB; + return dsoDB->finish(); } } // namespace celestia From 3bb3d12640b144157a6f4c40ac7e5abc4c71c9bc Mon Sep 17 00:00:00 2001 From: Andrew Tribick Date: Sun, 14 Jul 2024 21:08:00 +0200 Subject: [PATCH 06/15] Updates to build and instructions - Use vcpkg manifest mode to automatically install dependencies - Default all front-ends to OFF - Disable Qt5 on Windows - Update build instructions --- .github/workflows/ci.yml | 59 +-------- CMakeLists.txt | 29 ++++- INSTALL.md | 159 ++++++++++++++--------- src/celestia/qt5/CMakeLists.txt | 4 + vcpkg-triplets/x86-windows-release.cmake | 4 + vcpkg.json | 50 +++++++ 6 files changed, 184 insertions(+), 121 deletions(-) create mode 100644 vcpkg-triplets/x86-windows-release.cmake create mode 100644 vcpkg.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e158fd0275..d2b417e3f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,25 +27,6 @@ jobs: platform: [ x64, x86 ] steps: - - name: Update vcpkg - shell: pwsh - run: | - $vcpkgCommit = 'f7423ee180c4b7f40d43402c2feb3859161ef625' - pushd "$env:VCPKG_INSTALLATION_ROOT" - git cat-file -e "${vcpkgCommit}^{commit}" 2> $null - if (!$?) { - git pull - } - git -c advice.detachedHead=false checkout $vcpkgCommit - if (!$?) { - throw "Could not check out commit $vcpkgCommit" - } - ./bootstrap-vcpkg.bat - Get-ChildItem -Path 'triplets\*-windows*.cmake' | ForEach-Object { - Add-Content -Path $_ -Value 'set(VCPKG_BUILD_TYPE "release")' - } - popd - - name: Setup NuGet Credentials shell: pwsh run: | @@ -58,40 +39,6 @@ jobs: -Username 'CelestiaProject' ` -Password '${{secrets.GITHUB_TOKEN}}' - - name: Install dependencies - shell: pwsh - run: | - $packages = @( - 'boost-container', - 'boost-smart-ptr', - 'cspice', - 'eigen3', - 'ffmpeg[core,avcodec,avformat,gpl,swscale,x264]', - 'fmt', - 'freetype', - 'gettext[tools]', - 'gperf', - 'libepoxy', - 'libjpeg-turbo', - 'libpng', - 'luajit', - 'qtbase[core,opengl,widgets,freetype,harfbuzz,jpeg,png]' - ) - - # We treat x86 builds as native, other builds keep host as x64-windows. - # Currently this is equivalent to the platform triplet, but this would - # differ if we enable arm64-windows builds in future - - if ( '${{matrix.platform}}' -eq 'x86' ) { - $hostTriplet = 'x86-windows' - } else { - $hostTriplet = 'x64-windows' - } - - vcpkg --triplet=${{matrix.platform}}-windows ` - --host-triplet=$hostTriplet ` - install --recurse @packages - - name: Checkout source code uses: actions/checkout@v4 with: @@ -102,13 +49,17 @@ jobs: run: | if ( '${{matrix.platform}}' -eq 'x86' ) { $GeneratorPlatform = 'Win32' + $hostTriplet = 'x86-windows-release' } else { $GeneratorPlatform = '${{matrix.platform}}' + $hostTriplet = 'x64-windows-release' } cmake -B '${{github.workspace}}/build' ` -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" ` - -DVCPKG_TARGET_TRIPLET=${{matrix.platform}}-windows ` + -DVCPKG_OVERLAY_TRIPLETS="${{github.workspace}}/vcpkg-triplets" ` + -DVCPKG_HOST_TRIPLET="$hostTriplet" ` + -DVCPKG_TARGET_TRIPLET="$hostTriplet" ` -DCMAKE_GENERATOR_PLATFORM="$GeneratorPlatform" ` -DCMAKE_INSTALL_PREFIX="${{github.workspace}}/install" ` -DENABLE_SPICE=ON ` diff --git a/CMakeLists.txt b/CMakeLists.txt index b88eb09e8a..a00fdfea42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,19 +47,14 @@ if(POLICY CMP0004) cmake_policy(SET CMP0004 NEW) endif() -project(celestia VERSION 1.7.0 LANGUAGES C CXX) -set(DISPLAY_NAME "Celestia") -# -# -# option(ENABLE_CELX "Enable celx scripting, requires Lua library? (Default: on)" ON) option(ENABLE_SPICE "Use spice library? (Default: off)" OFF) option(ENABLE_NLS "Enable interface translation? (Default: on)" ON) option(ENABLE_GTK "Build GTK2 frontend (Unix only)? (Default: off)" OFF) -option(ENABLE_QT5 "Build Qt frontend? (Default: on)" ON) +option(ENABLE_QT5 "Build Qt frontend? (Default: off)" OFF) option(ENABLE_QT6 "Build Qt6 frontend (Default: off)" OFF) option(ENABLE_SDL "Build SDL frontend? (Default: off)" OFF) -option(ENABLE_WIN "Build Windows native frontend? (Default: on)" ON) +option(ENABLE_WIN "Build Windows native frontend? (Default: off)" OFF) option(ENABLE_FFMPEG "Support video capture using FFMPEG (Default: off)" OFF) option(ENABLE_MINIAUDIO "Support audio playback using miniaudio (Default: off)" OFF) option(ENABLE_TOOLS "Build different tools? (Default: off)" OFF) @@ -74,6 +69,26 @@ option(USE_GLSL_STRUCTS "Use structs in GLSL (Default: off)" OFF) option(USE_ICU "Use ICU for UTF8 decoding for text rendering (Default: off)" OFF) option(USE_WIN_ICU "Use Windows SDK's ICU implementation (Default: off)" OFF) option(USE_WEFFCPP "Use the -Weffc++ option when compiling with GCC (Default: off)" OFF) +option(USE_LOCAL_QT "Use local Qt installation instead of vcpkg (Default: off)" OFF) + +if(ENABLE_SPICE) + list(APPEND VCPKG_MANIFEST_FEATURES spice) +endif() + +if(ENABLE_FFMPEG) + list(APPEND VCPKG_MANIFEST_FEATURES ffmpeg) +endif() + +if(ENABLE_QT6 AND (NOT USE_LOCAL_QT)) + list(APPEND VCPKG_MANIFEST_FEATURES qt) +endif() + +if(USE_ICU AND (NOT USE_WIN_ICU)) + list(APPEND VCPKG_MANIFEST_FEATURES icu) +endif() + +project(celestia VERSION 1.7.0 LANGUAGES C CXX) +set(DISPLAY_NAME "Celestia") # Qt requires -fPIC, so build all code with it set(CMAKE_POSITION_INDEPENDENT_CODE ON) diff --git a/INSTALL.md b/INSTALL.md index 365f3f3848..92188c6d84 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -178,65 +178,102 @@ in /usr/local/share/celestia, but you may specify a new location with the following option to cmake: -DCMAKE_INSTALL_PREFIX=/another/path. -## Celestia Install instructions for Windows (MSVC) +## Celestia install instructions for Windows (MSVC) -Currently to build on Windows you need Visual Studio 2015 or later, CMake -and vcpkg (*). +You will need the following to build on Windows: -Install required packages: +* Visual Studio 2022 or Visual Studio Build Tools 2022 +* CMake (minimum version 3.19) +* vcpkg (install instructions at https://github.com/Microsoft/vcpkg) -``` -vcpkg --triplet=TRIPLET install --recurse boost-container boost-smart-ptr libpng libjpeg-turbo gettext gperf luajit fmt libepoxy eigen3 freetype -``` +In the following instructions, we assume that vcpkg has been installed to +`C:\vcpkg` - this is not necessary but to avoid build issues, avoid installing +it in locations where the full path contains spaces. -Install optional packages: +### Visual Studio Code -``` -vcpkg --triplet=TRIPLET install --recurse qt5-base ffmpeg[x264] cspice libavif +Extensions: + +* C/C++ (https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) +* CMake Tools (https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools) + +To build with Visual Studio Code, add the following `cmake.configureSettings` +to the workspace `settings.json` file: + +```json +{ + "cmake.configureSettings": { + "CMAKE_TOOLCHAIN_FILE": "C:\\vcpkg\\scripts\\buildsystems\\vcpkg.cmake", + "ENABLE_QT6": true + } +} ``` -Replace TRIPLET with `x86-windows` to build 32-bit versions or `x64-windows` -for 64-bit versions. +In order to run multiple build jobs in parallel for a faster build, it is also +possible to set the `cmake.parallelJobs` setting. -Instead of `luajit` `lua` can be used. +The above setup will build the Qt6 interface. Additional variables can be +added to this section, see the section "Supported CMake parameters" below. -Use `vcpkg list` to ensure that all packages have actually been installed. -If not, try installing them one at a time. +Once the settings are in place, ensure that the CMake extension is set to +build the "INSTALL" target, then run using "CMake: Build" from +the command palette. On first run, this will automatically download and build +the required dependencies: this may take a long time. -Configure and build 32-bit version: +Visual Studio Code defaults the `CMAKE_INSTALL_PREFIX` to the subdirectory +`out\install` - this will contain the result in a layout enabling it to be +run. Note that this does not include the content files, these have to be +copied separately. -``` -md build32 -cd build32 -cmake -DCMAKE_GENERATOR_PLATFORM=Win32 -DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x86-windows .. -cmake --build . -- /maxcpucount:N /nologo -``` +### Command line -Configure and build 64-bit version: +In the below commands, replace `` with the full path to a +directory where the output should be installed. Replace `` with the +number of CPU cores to use during the build. -``` -md build64 -cd build64 -cmake -DCMAKE_GENERATOR_PLATFORM=x64 -DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows .. -cmake --build . -- /maxcpucount:N /nologo +For a 64-bit build: + +```bat +mkdir build +cd build +cmake -DCMAKE_GENERATOR_PLATFORM=x64 ^ + -DCMAKE_TOOLCHAIN_FILE=c:\vpckg\scripts\buildsystems\vcpkg.cmake ^ + -DVCPKG_TARGET_TRIPLET=x64-windows ^ + -DENABLE_QT6=ON ^ + -DCMAKE_INSTALL_PREFIX= ^ + .. + +cmake --build . + +cmake --install . -- /maxcpucount: /nologo ``` -Instead of N in /maxcpucount pass the number of CPU cores you want to use during -the build. +For a 32-bit build: -This example assumes that `vcpkg` is installed into `c:/tools/vcpkg`. Update -the path to `vcpkg.cmake` according to your installation. +```bat +mkdir build +cd build +cmake -DCMAKE_GENERATOR_PLATFORM=Win32 ^ + -DCMAKE_TOOLCHAIN_FILE=c:\vpckg\scripts\buildsystems\vcpkg.cmake ^ + -DVCPKG_TARGET_TRIPLET=x86-windows ^ + -DVCPKG_HOST_TRIPLET=x86-windows ^ + -DENABLE_QT6=ON ^ + -DCMAKE_INSTALL_PREFIX= ^ + .. -If you have Qt5 installed using official Qt installer, then pass parameter -CMAKE_PREFIX_PATH to cmake call used to configure Celestia, e.g. +cmake --build . -- /maxcpucount: /nologo +cmake --install . ``` -cmake -DCMAKE_PREFIX_PATH=C:\Qt\5.10.1\msvc2015 .. -``` -Not supported yet: -- automatic installation using cmake -- using Ninja instead of MSBuild +On first run, this will automatically download and build the required +dependencies: this may take a long time. If you have the Qt libraries +installed from Qt's installer, you can pass `-DUSE_LOCAL_QT=ON` to avoid +building Qt with vcpkg. + +Once this is done, the project files will end up in the +`` directory. This does not process the content files: +these will need to be manually copied to the destination. Notes: * vcpkg installation instructions are located on @@ -351,29 +388,31 @@ the following option to cmake: `-DCMAKE_INSTALL_PREFIX=/another/path`. List of supported parameters (passed as `-DPARAMETER=VALUE`): - Parameter | TYPE | Default | Description -----------------------| ------|---------|-------------------------------------- -| CMAKE_INSTALL_PREFIX | path | \* | Prefix where to install Celestia -| CMAKE_PREFIX_PATH | path | | Additional path to look for libraries + Parameter | TYPE | Default | Description +-----------------------|------|-----------|-------------------------------------- +| CMAKE_INSTALL_PREFIX | path | \* | Prefix where to install Celestia +| CMAKE_PREFIX_PATH | path | | Additional path to look for libraries | LEGACY_OPENGL_LIBS | bool | \*\*OFF | Use OpenGL libraries not GLvnd -| ENABLE_CELX | bool | ON | Enable Lua scripting support -| ENABLE_SPICE | bool | OFF | Enable NAIF kernels support -| ENABLE_NLS | bool | ON | Enable interface translation +| ENABLE_CELX | bool | ON | Enable Lua scripting support +| ENABLE_SPICE | bool | OFF | Enable NAIF kernels support +| ENABLE_NLS | bool | ON | Enable interface translation | ENABLE_GTK | bool | \*\*OFF | Build legacy GTK2 frontend -| ENABLE_QT5 | bool | ON | Build Qt5 frontend -| ENABLE_QT6 | bool | ON | Build Qt6 frontend -| ENABLE_SDL | bool | OFF | Build SDL frontend -| ENABLE_WIN | bool | \*\*\*ON | Build Windows native frontend -| ENABLE_FFMPEG | bool | OFF | Support video capture using ffmpeg -| ENABLE_LIBAVIF | bool | OFF | Support AVIF texture using libavif -| ENABLE_MINIAUDIO | bool | OFF | Support audio playback using miniaudio -| ENABLE_TOOLS | bool | OFF | Build tools for Celestia data files -| ENABLE_GLES | bool | OFF | Use OpenGL ES 2.0 in rendering code -| USE_GTKGLEXT | bool | ON | Use libgtkglext1 in GTK2 frontend -| USE_QT6 | bool | OFF | Use Qt6 in Qt frontend -| USE_GTK3 | bool | OFF | Use Gtk3 instead of Gtk2 in GTK2 frontend -| USE_GLSL_STRUCTS | bool | OFF | Use structs in GLSL -| USE_ICU | bool | OFF | Use ICU for UTF8 decoding for text rendering +| ENABLE_QT5 | bool | \*\*OFF | Build Qt5 frontend +| ENABLE_QT6 | bool | OFF | Build Qt6 frontend +| ENABLE_SDL | bool | OFF | Build SDL frontend +| ENABLE_WIN | bool | \*\*\*OFF | Build Windows native frontend +| ENABLE_FFMPEG | bool | OFF | Support video capture using ffmpeg +| ENABLE_LIBAVIF | bool | OFF | Support AVIF texture using libavif +| ENABLE_MINIAUDIO | bool | OFF | Support audio playback using miniaudio +| ENABLE_TOOLS | bool | OFF | Build tools for Celestia data files +| ENABLE_GLES | bool | OFF | Use OpenGL ES 2.0 in rendering code +| USE_GTKGLEXT | bool | ON | Use libgtkglext1 in GTK2 frontend +| USE_QT6 | bool | OFF | Use Qt6 in Qt frontend +| USE_GTK3 | bool | OFF | Use Gtk3 instead of Gtk2 in GTK2 frontend +| USE_GLSL_STRUCTS | bool | OFF | Use structs in GLSL +| USE_ICU | bool | OFF | Use ICU for UTF8 decoding for text rendering +| USE_WIN_ICU | bool | \*\*\*OFF | Use built-in Windows ICU +| USE_LOCAL_QT | bool | \*\*\*OFF | Don't install Qt from vcpkg Notes: \* /usr/local on Unix-like systems, c:\Program Files or c:\Program Files (x86) diff --git a/src/celestia/qt5/CMakeLists.txt b/src/celestia/qt5/CMakeLists.txt index 9413450b62..d126018cf7 100644 --- a/src/celestia/qt5/CMakeLists.txt +++ b/src/celestia/qt5/CMakeLists.txt @@ -3,6 +3,10 @@ if(NOT ENABLE_QT5) return() endif() +if(WIN32) + message(FATAL_ERROR "Qt5 frontend is not supported on Windows") +endif() + include(../qt/CelestiaQtCommon.cmake) if(APPLE AND EXISTS /usr/local/opt/qt5) diff --git a/vcpkg-triplets/x86-windows-release.cmake b/vcpkg-triplets/x86-windows-release.cmake new file mode 100644 index 0000000000..0a277bdb77 --- /dev/null +++ b/vcpkg-triplets/x86-windows-release.cmake @@ -0,0 +1,4 @@ +set(VCPKG_TARGET_ARCHITECTURE x86) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE dynamic) +set(VCPKG_BUILD_TYPE release) diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 0000000000..3be1b357bb --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", + "builtin-baseline": "f7423ee180c4b7f40d43402c2feb3859161ef625", + "dependencies": [ + "boost-container", + "boost-smart-ptr", + "eigen3", + "fmt", + "freetype", + { + "name": "gettext", + "features": ["tools"] + }, + "gperf", + "libepoxy", + "libjpeg-turbo", + "libpng", + "luajit" + ], + "features": { + "ffmpeg": { + "description": "FFMPEG support", + "dependencies": [ + { + "name": "ffmpeg", + "default-features": false, + "features": ["avcodec", "avformat", "gpl", "swscale", "x264"] + } + ] + }, + "icu": { + "description": "Use separate ICU library", + "dependencies": ["icu"] + }, + "qt": { + "description": "Qt front end", + "dependencies": [ + { + "name": "qtbase", + "default-features": false, + "features": ["opengl", "widgets", "freetype", "harfbuzz", "jpeg", "png"] + } + ] + }, + "spice": { + "description": "SPICE orbits/rotations support", + "dependencies": ["cspice"] + } + } +} From 303a1aafe60cec6daad293fb9d981867cf150deb Mon Sep 17 00:00:00 2001 From: ajtribick Date: Wed, 24 Jul 2024 20:24:14 +0200 Subject: [PATCH 07/15] Remove unnecessary semicolons from selection.h --- src/celengine/selection.h | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/celengine/selection.h b/src/celengine/selection.h index 90239bf924..19ea24497c 100644 --- a/src/celengine/selection.h +++ b/src/celengine/selection.h @@ -31,12 +31,12 @@ enum class SelectionType class Selection { - public: +public: Selection() = default; - Selection(Star* star) : type(SelectionType::Star), obj(star) { checkNull(); }; - Selection(Body* body) : type(SelectionType::Body), obj(body) { checkNull(); }; - Selection(DeepSkyObject* deepsky) : type(SelectionType::DeepSky), obj(deepsky) {checkNull(); }; - Selection(Location* location) : type(SelectionType::Location), obj(location) { checkNull(); }; + Selection(Star* star) : type(SelectionType::Star), obj(star) { checkNull(); } + Selection(Body* body) : type(SelectionType::Body), obj(body) { checkNull(); } + Selection(DeepSkyObject* deepsky) : type(SelectionType::DeepSky), obj(deepsky) {checkNull(); } + Selection(Location* location) : type(SelectionType::Location), obj(location) { checkNull(); } ~Selection() = default; Selection(const Selection&) = default; @@ -74,7 +74,7 @@ class Selection inline SelectionType getType() const { return type; } - private: +private: SelectionType type { SelectionType::None }; void* obj { nullptr }; @@ -85,7 +85,6 @@ class Selection friend struct std::hash; }; - inline bool operator==(const Selection& s0, const Selection& s1) { return s0.type == s1.type && s0.obj == s1.obj; From af9c3fa01ce7b2c14e5aa0047e6136179c5d63cd Mon Sep 17 00:00:00 2001 From: Andrew Tribick Date: Thu, 25 Jul 2024 12:05:22 +0200 Subject: [PATCH 08/15] Revert "Updates to build and instructions" This reverts commit 3bb3d12640b144157a6f4c40ac7e5abc4c71c9bc. --- .github/workflows/ci.yml | 59 ++++++++- CMakeLists.txt | 29 +---- INSTALL.md | 159 +++++++++-------------- src/celestia/qt5/CMakeLists.txt | 4 - vcpkg-triplets/x86-windows-release.cmake | 4 - vcpkg.json | 50 ------- 6 files changed, 121 insertions(+), 184 deletions(-) delete mode 100644 vcpkg-triplets/x86-windows-release.cmake delete mode 100644 vcpkg.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d2b417e3f2..e158fd0275 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,25 @@ jobs: platform: [ x64, x86 ] steps: + - name: Update vcpkg + shell: pwsh + run: | + $vcpkgCommit = 'f7423ee180c4b7f40d43402c2feb3859161ef625' + pushd "$env:VCPKG_INSTALLATION_ROOT" + git cat-file -e "${vcpkgCommit}^{commit}" 2> $null + if (!$?) { + git pull + } + git -c advice.detachedHead=false checkout $vcpkgCommit + if (!$?) { + throw "Could not check out commit $vcpkgCommit" + } + ./bootstrap-vcpkg.bat + Get-ChildItem -Path 'triplets\*-windows*.cmake' | ForEach-Object { + Add-Content -Path $_ -Value 'set(VCPKG_BUILD_TYPE "release")' + } + popd + - name: Setup NuGet Credentials shell: pwsh run: | @@ -39,6 +58,40 @@ jobs: -Username 'CelestiaProject' ` -Password '${{secrets.GITHUB_TOKEN}}' + - name: Install dependencies + shell: pwsh + run: | + $packages = @( + 'boost-container', + 'boost-smart-ptr', + 'cspice', + 'eigen3', + 'ffmpeg[core,avcodec,avformat,gpl,swscale,x264]', + 'fmt', + 'freetype', + 'gettext[tools]', + 'gperf', + 'libepoxy', + 'libjpeg-turbo', + 'libpng', + 'luajit', + 'qtbase[core,opengl,widgets,freetype,harfbuzz,jpeg,png]' + ) + + # We treat x86 builds as native, other builds keep host as x64-windows. + # Currently this is equivalent to the platform triplet, but this would + # differ if we enable arm64-windows builds in future + + if ( '${{matrix.platform}}' -eq 'x86' ) { + $hostTriplet = 'x86-windows' + } else { + $hostTriplet = 'x64-windows' + } + + vcpkg --triplet=${{matrix.platform}}-windows ` + --host-triplet=$hostTriplet ` + install --recurse @packages + - name: Checkout source code uses: actions/checkout@v4 with: @@ -49,17 +102,13 @@ jobs: run: | if ( '${{matrix.platform}}' -eq 'x86' ) { $GeneratorPlatform = 'Win32' - $hostTriplet = 'x86-windows-release' } else { $GeneratorPlatform = '${{matrix.platform}}' - $hostTriplet = 'x64-windows-release' } cmake -B '${{github.workspace}}/build' ` -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" ` - -DVCPKG_OVERLAY_TRIPLETS="${{github.workspace}}/vcpkg-triplets" ` - -DVCPKG_HOST_TRIPLET="$hostTriplet" ` - -DVCPKG_TARGET_TRIPLET="$hostTriplet" ` + -DVCPKG_TARGET_TRIPLET=${{matrix.platform}}-windows ` -DCMAKE_GENERATOR_PLATFORM="$GeneratorPlatform" ` -DCMAKE_INSTALL_PREFIX="${{github.workspace}}/install" ` -DENABLE_SPICE=ON ` diff --git a/CMakeLists.txt b/CMakeLists.txt index a00fdfea42..b88eb09e8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,14 +47,19 @@ if(POLICY CMP0004) cmake_policy(SET CMP0004 NEW) endif() +project(celestia VERSION 1.7.0 LANGUAGES C CXX) +set(DISPLAY_NAME "Celestia") +# +# +# option(ENABLE_CELX "Enable celx scripting, requires Lua library? (Default: on)" ON) option(ENABLE_SPICE "Use spice library? (Default: off)" OFF) option(ENABLE_NLS "Enable interface translation? (Default: on)" ON) option(ENABLE_GTK "Build GTK2 frontend (Unix only)? (Default: off)" OFF) -option(ENABLE_QT5 "Build Qt frontend? (Default: off)" OFF) +option(ENABLE_QT5 "Build Qt frontend? (Default: on)" ON) option(ENABLE_QT6 "Build Qt6 frontend (Default: off)" OFF) option(ENABLE_SDL "Build SDL frontend? (Default: off)" OFF) -option(ENABLE_WIN "Build Windows native frontend? (Default: off)" OFF) +option(ENABLE_WIN "Build Windows native frontend? (Default: on)" ON) option(ENABLE_FFMPEG "Support video capture using FFMPEG (Default: off)" OFF) option(ENABLE_MINIAUDIO "Support audio playback using miniaudio (Default: off)" OFF) option(ENABLE_TOOLS "Build different tools? (Default: off)" OFF) @@ -69,26 +74,6 @@ option(USE_GLSL_STRUCTS "Use structs in GLSL (Default: off)" OFF) option(USE_ICU "Use ICU for UTF8 decoding for text rendering (Default: off)" OFF) option(USE_WIN_ICU "Use Windows SDK's ICU implementation (Default: off)" OFF) option(USE_WEFFCPP "Use the -Weffc++ option when compiling with GCC (Default: off)" OFF) -option(USE_LOCAL_QT "Use local Qt installation instead of vcpkg (Default: off)" OFF) - -if(ENABLE_SPICE) - list(APPEND VCPKG_MANIFEST_FEATURES spice) -endif() - -if(ENABLE_FFMPEG) - list(APPEND VCPKG_MANIFEST_FEATURES ffmpeg) -endif() - -if(ENABLE_QT6 AND (NOT USE_LOCAL_QT)) - list(APPEND VCPKG_MANIFEST_FEATURES qt) -endif() - -if(USE_ICU AND (NOT USE_WIN_ICU)) - list(APPEND VCPKG_MANIFEST_FEATURES icu) -endif() - -project(celestia VERSION 1.7.0 LANGUAGES C CXX) -set(DISPLAY_NAME "Celestia") # Qt requires -fPIC, so build all code with it set(CMAKE_POSITION_INDEPENDENT_CODE ON) diff --git a/INSTALL.md b/INSTALL.md index 92188c6d84..365f3f3848 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -178,102 +178,65 @@ in /usr/local/share/celestia, but you may specify a new location with the following option to cmake: -DCMAKE_INSTALL_PREFIX=/another/path. -## Celestia install instructions for Windows (MSVC) +## Celestia Install instructions for Windows (MSVC) -You will need the following to build on Windows: +Currently to build on Windows you need Visual Studio 2015 or later, CMake +and vcpkg (*). -* Visual Studio 2022 or Visual Studio Build Tools 2022 -* CMake (minimum version 3.19) -* vcpkg (install instructions at https://github.com/Microsoft/vcpkg) - -In the following instructions, we assume that vcpkg has been installed to -`C:\vcpkg` - this is not necessary but to avoid build issues, avoid installing -it in locations where the full path contains spaces. - -### Visual Studio Code - -Extensions: - -* C/C++ (https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) -* CMake Tools (https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools) - -To build with Visual Studio Code, add the following `cmake.configureSettings` -to the workspace `settings.json` file: +Install required packages: -```json -{ - "cmake.configureSettings": { - "CMAKE_TOOLCHAIN_FILE": "C:\\vcpkg\\scripts\\buildsystems\\vcpkg.cmake", - "ENABLE_QT6": true - } -} +``` +vcpkg --triplet=TRIPLET install --recurse boost-container boost-smart-ptr libpng libjpeg-turbo gettext gperf luajit fmt libepoxy eigen3 freetype ``` -In order to run multiple build jobs in parallel for a faster build, it is also -possible to set the `cmake.parallelJobs` setting. - -The above setup will build the Qt6 interface. Additional variables can be -added to this section, see the section "Supported CMake parameters" below. +Install optional packages: -Once the settings are in place, ensure that the CMake extension is set to -build the "INSTALL" target, then run using "CMake: Build" from -the command palette. On first run, this will automatically download and build -the required dependencies: this may take a long time. +``` +vcpkg --triplet=TRIPLET install --recurse qt5-base ffmpeg[x264] cspice libavif +``` -Visual Studio Code defaults the `CMAKE_INSTALL_PREFIX` to the subdirectory -`out\install` - this will contain the result in a layout enabling it to be -run. Note that this does not include the content files, these have to be -copied separately. +Replace TRIPLET with `x86-windows` to build 32-bit versions or `x64-windows` +for 64-bit versions. -### Command line +Instead of `luajit` `lua` can be used. -In the below commands, replace `` with the full path to a -directory where the output should be installed. Replace `` with the -number of CPU cores to use during the build. +Use `vcpkg list` to ensure that all packages have actually been installed. +If not, try installing them one at a time. -For a 64-bit build: +Configure and build 32-bit version: -```bat -mkdir build -cd build -cmake -DCMAKE_GENERATOR_PLATFORM=x64 ^ - -DCMAKE_TOOLCHAIN_FILE=c:\vpckg\scripts\buildsystems\vcpkg.cmake ^ - -DVCPKG_TARGET_TRIPLET=x64-windows ^ - -DENABLE_QT6=ON ^ - -DCMAKE_INSTALL_PREFIX= ^ - .. +``` +md build32 +cd build32 +cmake -DCMAKE_GENERATOR_PLATFORM=Win32 -DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x86-windows .. +cmake --build . -- /maxcpucount:N /nologo +``` -cmake --build . +Configure and build 64-bit version: -cmake --install . -- /maxcpucount: /nologo +``` +md build64 +cd build64 +cmake -DCMAKE_GENERATOR_PLATFORM=x64 -DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows .. +cmake --build . -- /maxcpucount:N /nologo ``` -For a 32-bit build: +Instead of N in /maxcpucount pass the number of CPU cores you want to use during +the build. -```bat -mkdir build -cd build -cmake -DCMAKE_GENERATOR_PLATFORM=Win32 ^ - -DCMAKE_TOOLCHAIN_FILE=c:\vpckg\scripts\buildsystems\vcpkg.cmake ^ - -DVCPKG_TARGET_TRIPLET=x86-windows ^ - -DVCPKG_HOST_TRIPLET=x86-windows ^ - -DENABLE_QT6=ON ^ - -DCMAKE_INSTALL_PREFIX= ^ - .. +This example assumes that `vcpkg` is installed into `c:/tools/vcpkg`. Update +the path to `vcpkg.cmake` according to your installation. -cmake --build . -- /maxcpucount: /nologo +If you have Qt5 installed using official Qt installer, then pass parameter +CMAKE_PREFIX_PATH to cmake call used to configure Celestia, e.g. -cmake --install . +``` +cmake -DCMAKE_PREFIX_PATH=C:\Qt\5.10.1\msvc2015 .. ``` -On first run, this will automatically download and build the required -dependencies: this may take a long time. If you have the Qt libraries -installed from Qt's installer, you can pass `-DUSE_LOCAL_QT=ON` to avoid -building Qt with vcpkg. - -Once this is done, the project files will end up in the -`` directory. This does not process the content files: -these will need to be manually copied to the destination. +Not supported yet: +- automatic installation using cmake +- using Ninja instead of MSBuild Notes: * vcpkg installation instructions are located on @@ -388,31 +351,29 @@ the following option to cmake: `-DCMAKE_INSTALL_PREFIX=/another/path`. List of supported parameters (passed as `-DPARAMETER=VALUE`): - Parameter | TYPE | Default | Description ------------------------|------|-----------|-------------------------------------- -| CMAKE_INSTALL_PREFIX | path | \* | Prefix where to install Celestia -| CMAKE_PREFIX_PATH | path | | Additional path to look for libraries + Parameter | TYPE | Default | Description +----------------------| ------|---------|-------------------------------------- +| CMAKE_INSTALL_PREFIX | path | \* | Prefix where to install Celestia +| CMAKE_PREFIX_PATH | path | | Additional path to look for libraries | LEGACY_OPENGL_LIBS | bool | \*\*OFF | Use OpenGL libraries not GLvnd -| ENABLE_CELX | bool | ON | Enable Lua scripting support -| ENABLE_SPICE | bool | OFF | Enable NAIF kernels support -| ENABLE_NLS | bool | ON | Enable interface translation +| ENABLE_CELX | bool | ON | Enable Lua scripting support +| ENABLE_SPICE | bool | OFF | Enable NAIF kernels support +| ENABLE_NLS | bool | ON | Enable interface translation | ENABLE_GTK | bool | \*\*OFF | Build legacy GTK2 frontend -| ENABLE_QT5 | bool | \*\*OFF | Build Qt5 frontend -| ENABLE_QT6 | bool | OFF | Build Qt6 frontend -| ENABLE_SDL | bool | OFF | Build SDL frontend -| ENABLE_WIN | bool | \*\*\*OFF | Build Windows native frontend -| ENABLE_FFMPEG | bool | OFF | Support video capture using ffmpeg -| ENABLE_LIBAVIF | bool | OFF | Support AVIF texture using libavif -| ENABLE_MINIAUDIO | bool | OFF | Support audio playback using miniaudio -| ENABLE_TOOLS | bool | OFF | Build tools for Celestia data files -| ENABLE_GLES | bool | OFF | Use OpenGL ES 2.0 in rendering code -| USE_GTKGLEXT | bool | ON | Use libgtkglext1 in GTK2 frontend -| USE_QT6 | bool | OFF | Use Qt6 in Qt frontend -| USE_GTK3 | bool | OFF | Use Gtk3 instead of Gtk2 in GTK2 frontend -| USE_GLSL_STRUCTS | bool | OFF | Use structs in GLSL -| USE_ICU | bool | OFF | Use ICU for UTF8 decoding for text rendering -| USE_WIN_ICU | bool | \*\*\*OFF | Use built-in Windows ICU -| USE_LOCAL_QT | bool | \*\*\*OFF | Don't install Qt from vcpkg +| ENABLE_QT5 | bool | ON | Build Qt5 frontend +| ENABLE_QT6 | bool | ON | Build Qt6 frontend +| ENABLE_SDL | bool | OFF | Build SDL frontend +| ENABLE_WIN | bool | \*\*\*ON | Build Windows native frontend +| ENABLE_FFMPEG | bool | OFF | Support video capture using ffmpeg +| ENABLE_LIBAVIF | bool | OFF | Support AVIF texture using libavif +| ENABLE_MINIAUDIO | bool | OFF | Support audio playback using miniaudio +| ENABLE_TOOLS | bool | OFF | Build tools for Celestia data files +| ENABLE_GLES | bool | OFF | Use OpenGL ES 2.0 in rendering code +| USE_GTKGLEXT | bool | ON | Use libgtkglext1 in GTK2 frontend +| USE_QT6 | bool | OFF | Use Qt6 in Qt frontend +| USE_GTK3 | bool | OFF | Use Gtk3 instead of Gtk2 in GTK2 frontend +| USE_GLSL_STRUCTS | bool | OFF | Use structs in GLSL +| USE_ICU | bool | OFF | Use ICU for UTF8 decoding for text rendering Notes: \* /usr/local on Unix-like systems, c:\Program Files or c:\Program Files (x86) diff --git a/src/celestia/qt5/CMakeLists.txt b/src/celestia/qt5/CMakeLists.txt index d126018cf7..9413450b62 100644 --- a/src/celestia/qt5/CMakeLists.txt +++ b/src/celestia/qt5/CMakeLists.txt @@ -3,10 +3,6 @@ if(NOT ENABLE_QT5) return() endif() -if(WIN32) - message(FATAL_ERROR "Qt5 frontend is not supported on Windows") -endif() - include(../qt/CelestiaQtCommon.cmake) if(APPLE AND EXISTS /usr/local/opt/qt5) diff --git a/vcpkg-triplets/x86-windows-release.cmake b/vcpkg-triplets/x86-windows-release.cmake deleted file mode 100644 index 0a277bdb77..0000000000 --- a/vcpkg-triplets/x86-windows-release.cmake +++ /dev/null @@ -1,4 +0,0 @@ -set(VCPKG_TARGET_ARCHITECTURE x86) -set(VCPKG_CRT_LINKAGE dynamic) -set(VCPKG_LIBRARY_LINKAGE dynamic) -set(VCPKG_BUILD_TYPE release) diff --git a/vcpkg.json b/vcpkg.json deleted file mode 100644 index 3be1b357bb..0000000000 --- a/vcpkg.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", - "builtin-baseline": "f7423ee180c4b7f40d43402c2feb3859161ef625", - "dependencies": [ - "boost-container", - "boost-smart-ptr", - "eigen3", - "fmt", - "freetype", - { - "name": "gettext", - "features": ["tools"] - }, - "gperf", - "libepoxy", - "libjpeg-turbo", - "libpng", - "luajit" - ], - "features": { - "ffmpeg": { - "description": "FFMPEG support", - "dependencies": [ - { - "name": "ffmpeg", - "default-features": false, - "features": ["avcodec", "avformat", "gpl", "swscale", "x264"] - } - ] - }, - "icu": { - "description": "Use separate ICU library", - "dependencies": ["icu"] - }, - "qt": { - "description": "Qt front end", - "dependencies": [ - { - "name": "qtbase", - "default-features": false, - "features": ["opengl", "widgets", "freetype", "harfbuzz", "jpeg", "png"] - } - ] - }, - "spice": { - "description": "SPICE orbits/rotations support", - "dependencies": ["cspice"] - } - } -} From abfe86eab093a7b98734994a7db308d2876d5e32 Mon Sep 17 00:00:00 2001 From: Andrew Tribick Date: Sat, 27 Jul 2024 22:34:52 +0200 Subject: [PATCH 09/15] Build system updates - Update to latest vcpkg - Default all front-ends to OFF - Use the OpenGL_GL_PREFERENCE variable instead of switching CMP0072 - Set CMP0167 to NEW (use Boost-provided find module) - Make use of meshoptimizer configurable instead of relying on find result --- .github/workflows/ci.yml | 2 +- CMakeLists.txt | 76 ++++++++++++++++++++-------------------- INSTALL.md | 43 ++++++++++++----------- 3 files changed, 61 insertions(+), 60 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e158fd0275..4e027d2ad1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: - name: Update vcpkg shell: pwsh run: | - $vcpkgCommit = 'f7423ee180c4b7f40d43402c2feb3859161ef625' + $vcpkgCommit = 'cacf5994341f27e9a14a7b8724b0634b138ecb30' pushd "$env:VCPKG_INSTALLATION_ROOT" git cat-file -e "${vcpkgCommit}^{commit}" 2> $null if (!$?) { diff --git a/CMakeLists.txt b/CMakeLists.txt index b88eb09e8a..55e7bbb2ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,8 +5,6 @@ else() cmake_minimum_required(VERSION 3.9) endif() -option(LEGACY_OPENGL_LIBS "Use legacy OpenGL libraries instead of glvnd library (Default: off)" OFF) - # Plain and keyword target_link_libraries() signatures cannot be mixed if (POLICY CMP0023) cmake_policy(SET CMP0023 NEW) @@ -33,47 +31,52 @@ if(POLICY CMP0071) cmake_policy(SET CMP0071 OLD) endif() -# Prefer GLVND or "legacy" OpenGL library (libOpenGL.so vs libGL.so) -if(POLICY CMP0072) - if(LEGACY_OPENGL_LIBS) - cmake_policy(SET CMP0072 OLD) - else() - cmake_policy(SET CMP0072 NEW) - endif() -endif() - # Remove leading and trailing whitespace from libraries linked if(POLICY CMP0004) cmake_policy(SET CMP0004 NEW) endif() +# Use Boost-provided FindBoost module instead of CMake-provided one +if(POLICY CMP0167) + cmake_policy(SET CMP0167 NEW) +endif() + project(celestia VERSION 1.7.0 LANGUAGES C CXX) set(DISPLAY_NAME "Celestia") # # # -option(ENABLE_CELX "Enable celx scripting, requires Lua library? (Default: on)" ON) -option(ENABLE_SPICE "Use spice library? (Default: off)" OFF) -option(ENABLE_NLS "Enable interface translation? (Default: on)" ON) -option(ENABLE_GTK "Build GTK2 frontend (Unix only)? (Default: off)" OFF) -option(ENABLE_QT5 "Build Qt frontend? (Default: on)" ON) -option(ENABLE_QT6 "Build Qt6 frontend (Default: off)" OFF) -option(ENABLE_SDL "Build SDL frontend? (Default: off)" OFF) -option(ENABLE_WIN "Build Windows native frontend? (Default: on)" ON) -option(ENABLE_FFMPEG "Support video capture using FFMPEG (Default: off)" OFF) -option(ENABLE_MINIAUDIO "Support audio playback using miniaudio (Default: off)" OFF) -option(ENABLE_TOOLS "Build different tools? (Default: off)" OFF) -option(ENABLE_FAST_MATH "Build with unsafe fast-math compiller option (Default: off)" OFF) -option(ENABLE_TESTS "Enable unit tests? (Default: off)" OFF) -option(ENABLE_GLES "Build for OpenGL ES 2.0 instead of OpenGL 2.1 (Default: off)" OFF) -option(ENABLE_LTO "Enable link time optimizations (Default: off)" OFF) -option(USE_GTKGLEXT "Use libgtkglext1 for GTK2 frontend (Default: on)" ON) -option(USE_GTK3 "Use Gtk3 in GTK2 frontend (Default: off)" OFF) -option(USE_WAYLAND "Use Wayland in Qt frontend (Default: off)" OFF) -option(USE_GLSL_STRUCTS "Use structs in GLSL (Default: off)" OFF) -option(USE_ICU "Use ICU for UTF8 decoding for text rendering (Default: off)" OFF) -option(USE_WIN_ICU "Use Windows SDK's ICU implementation (Default: off)" OFF) -option(USE_WEFFCPP "Use the -Weffc++ option when compiling with GCC (Default: off)" OFF) +option(ENABLE_CELX "Enable celx scripting, requires Lua library? (Default: on)" ON) +option(ENABLE_SPICE "Use spice library? (Default: off)" OFF) +option(ENABLE_NLS "Enable interface translation? (Default: on)" ON) +option(ENABLE_GTK "Build GTK2 frontend (Unix only)? (Default: off)" OFF) +option(ENABLE_QT5 "Build Qt frontend? (Default: off)" OFF) +option(ENABLE_QT6 "Build Qt6 frontend (Default: off)" OFF) +option(ENABLE_SDL "Build SDL frontend? (Default: off)" OFF) +option(ENABLE_WIN "Build Windows native frontend? (Default: on)" ON) +option(ENABLE_FFMPEG "Support video capture using FFMPEG (Default: off)" OFF) +option(ENABLE_MINIAUDIO "Support audio playback using miniaudio (Default: off)" OFF) +option(ENABLE_TOOLS "Build different tools? (Default: off)" OFF) +option(ENABLE_FAST_MATH "Build with unsafe fast-math compiller option (Default: off)" OFF) +option(ENABLE_TESTS "Enable unit tests? (Default: off)" OFF) +option(ENABLE_GLES "Build for OpenGL ES 2.0 instead of OpenGL 2.1 (Default: off)" OFF) +option(ENABLE_LTO "Enable link time optimizations (Default: off)" OFF) +option(USE_GTKGLEXT "Use libgtkglext1 for GTK2 frontend (Default: on)" ON) +option(USE_GTK3 "Use Gtk3 in GTK2 frontend (Default: off)" OFF) +option(USE_WAYLAND "Use Wayland in Qt frontend (Default: off)" OFF) +option(USE_GLSL_STRUCTS "Use structs in GLSL (Default: off)" OFF) +option(USE_ICU "Use ICU for UTF8 decoding for text rendering (Default: off)" OFF) +option(USE_WIN_ICU "Use Windows SDK's ICU implementation (Default: off)" OFF) +option(USE_MESHOPTIMIZER "Use meshoptimizer (Default: off)" OFF) +option(USE_WEFFCPP "Use the -Weffc++ option when compiling with GCC (Default: off)" OFF) +option(LEGACY_OPENGL_LIBS "Use legacy OpenGL libraries instead of glvnd library (Default: off)" OFF) + +# Prefer GLVND or "legacy" OpenGL library (libOpenGL.so vs libGL.so) +if(LEGACY_OPENGL_LIBS) + set(OpenGL_GL_PREFERENCE LEGACY) +else() + set(OpenGL_GL_PREFERENCE GLVND) +endif() # Qt requires -fPIC, so build all code with it set(CMAKE_POSITION_INDEPENDENT_CODE ON) @@ -304,12 +307,9 @@ endif() find_package(Freetype REQUIRED) link_libraries(Freetype::Freetype) -find_package(meshoptimizer CONFIG QUIET) -if(meshoptimizer_FOUND) - message(STATUS "Found meshoptimizer library") +if(USE_MESHOPTIMIZER) + find_package(meshoptimizer CONFIG REQUIRED) set(HAVE_MESHOPTIMIZER 1) -else() - message(STATUS "meshoptimizer library is missing") endif() if(USE_WAYLAND) diff --git a/INSTALL.md b/INSTALL.md index 365f3f3848..1809da75ef 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -351,29 +351,30 @@ the following option to cmake: `-DCMAKE_INSTALL_PREFIX=/another/path`. List of supported parameters (passed as `-DPARAMETER=VALUE`): - Parameter | TYPE | Default | Description -----------------------| ------|---------|-------------------------------------- -| CMAKE_INSTALL_PREFIX | path | \* | Prefix where to install Celestia -| CMAKE_PREFIX_PATH | path | | Additional path to look for libraries + Parameter | TYPE | Default | Description +-----------------------|------|-----------|-------------------------------------- +| CMAKE_INSTALL_PREFIX | path | \* | Prefix where to install Celestia +| CMAKE_PREFIX_PATH | path | | Additional path to look for libraries | LEGACY_OPENGL_LIBS | bool | \*\*OFF | Use OpenGL libraries not GLvnd -| ENABLE_CELX | bool | ON | Enable Lua scripting support -| ENABLE_SPICE | bool | OFF | Enable NAIF kernels support -| ENABLE_NLS | bool | ON | Enable interface translation +| ENABLE_CELX | bool | ON | Enable Lua scripting support +| ENABLE_SPICE | bool | OFF | Enable NAIF kernels support +| ENABLE_NLS | bool | ON | Enable interface translation | ENABLE_GTK | bool | \*\*OFF | Build legacy GTK2 frontend -| ENABLE_QT5 | bool | ON | Build Qt5 frontend -| ENABLE_QT6 | bool | ON | Build Qt6 frontend -| ENABLE_SDL | bool | OFF | Build SDL frontend -| ENABLE_WIN | bool | \*\*\*ON | Build Windows native frontend -| ENABLE_FFMPEG | bool | OFF | Support video capture using ffmpeg -| ENABLE_LIBAVIF | bool | OFF | Support AVIF texture using libavif -| ENABLE_MINIAUDIO | bool | OFF | Support audio playback using miniaudio -| ENABLE_TOOLS | bool | OFF | Build tools for Celestia data files -| ENABLE_GLES | bool | OFF | Use OpenGL ES 2.0 in rendering code -| USE_GTKGLEXT | bool | ON | Use libgtkglext1 in GTK2 frontend -| USE_QT6 | bool | OFF | Use Qt6 in Qt frontend -| USE_GTK3 | bool | OFF | Use Gtk3 instead of Gtk2 in GTK2 frontend -| USE_GLSL_STRUCTS | bool | OFF | Use structs in GLSL -| USE_ICU | bool | OFF | Use ICU for UTF8 decoding for text rendering +| ENABLE_QT5 | bool | OFF | Build Qt5 frontend +| ENABLE_QT6 | bool | OFF | Build Qt6 frontend +| ENABLE_SDL | bool | OFF | Build SDL frontend +| ENABLE_WIN | bool | \*\*\*ON | Build Windows native frontend +| ENABLE_FFMPEG | bool | OFF | Support video capture using ffmpeg +| ENABLE_LIBAVIF | bool | OFF | Support AVIF texture using libavif +| ENABLE_MINIAUDIO | bool | OFF | Support audio playback using miniaudio +| ENABLE_TOOLS | bool | OFF | Build tools for Celestia data files +| ENABLE_GLES | bool | OFF | Use OpenGL ES 2.0 in rendering code +| USE_GTKGLEXT | bool | ON | Use libgtkglext1 in GTK2 frontend +| USE_QT6 | bool | OFF | Use Qt6 in Qt frontend +| USE_GTK3 | bool | OFF | Use Gtk3 instead of Gtk2 in GTK2 frontend +| USE_GLSL_STRUCTS | bool | OFF | Use structs in GLSL +| USE_ICU | bool | OFF | Use ICU for UTF8 decoding for text rendering +| USE_MESHOPTIMIZER | bool | OFF | Use meshoptimizer when loading models Notes: \* /usr/local on Unix-like systems, c:\Program Files or c:\Program Files (x86) From 8aab61b38f99910d7f0446075c05d4265ad8641b Mon Sep 17 00:00:00 2001 From: Andrew Tribick Date: Wed, 24 Jul 2024 20:22:12 +0200 Subject: [PATCH 10/15] Move octrees into celestia::engine namespace --- src/celengine/dsodb.cpp | 8 +++-- src/celengine/dsodb.h | 8 ++--- src/celengine/dsodbbuilder.cpp | 5 +-- src/celengine/dsooctree.cpp | 6 ++-- src/celengine/dsooctree.h | 5 +++ src/celengine/objectrenderer.h | 2 +- src/celengine/octree.h | 5 +++ src/celengine/octreebuilder.h | 5 +++ src/celengine/stardb.cpp | 5 +-- src/celengine/stardb.h | 12 +++---- src/celengine/stardbbuilder.cpp | 2 +- src/celengine/staroctree.cpp | 6 ++-- src/celengine/staroctree.h | 5 +++ src/celengine/universe.cpp | 57 ++++----------------------------- 14 files changed, 57 insertions(+), 74 deletions(-) diff --git a/src/celengine/dsodb.cpp b/src/celengine/dsodb.cpp index c26c057695..7454abef40 100644 --- a/src/celengine/dsodb.cpp +++ b/src/celengine/dsodb.cpp @@ -19,12 +19,14 @@ #include #include "name.h" +namespace engine = celestia::engine; + using celestia::util::GetLogger; DSODatabase::~DSODatabase() = default; DSODatabase::DSODatabase(std::vector>&& DSOs, - std::unique_ptr&& octreeRoot, + std::unique_ptr&& octreeRoot, std::unique_ptr&& namesDB, std::vector&& catalogNumberIndex, float avgAbsMag) : @@ -116,7 +118,7 @@ DSODatabase::getDSONameList(const DeepSkyObject* dso, const unsigned int maxName } void -DSODatabase::findVisibleDSOs(DSOHandler& dsoHandler, +DSODatabase::findVisibleDSOs(engine::DSOHandler& dsoHandler, const Eigen::Vector3d& obsPos, const Eigen::Quaternionf& obsOrient, float fovY, @@ -152,7 +154,7 @@ DSODatabase::findVisibleDSOs(DSOHandler& dsoHandler, } void -DSODatabase::findCloseDSOs(DSOHandler& dsoHandler, +DSODatabase::findCloseDSOs(engine::DSOHandler& dsoHandler, const Eigen::Vector3d& obsPos, float radius) const { diff --git a/src/celengine/dsodb.h b/src/celengine/dsodb.h index 94439cdf01..05d2bcf52a 100644 --- a/src/celengine/dsodb.h +++ b/src/celengine/dsodb.h @@ -37,7 +37,7 @@ class DSODatabase { public: DSODatabase(std::vector>&&, - std::unique_ptr&&, + std::unique_ptr&&, std::unique_ptr&&, std::vector&&, float); @@ -52,14 +52,14 @@ class DSODatabase void getCompletion(std::vector&, std::string_view) const; - void findVisibleDSOs(DSOHandler& dsoHandler, + void findVisibleDSOs(celestia::engine::DSOHandler& dsoHandler, const Eigen::Vector3d& obsPosition, const Eigen::Quaternionf& obsOrientation, float fovY, float aspectRatio, float limitingMag) const; - void findCloseDSOs(DSOHandler& dsoHandler, + void findCloseDSOs(celestia::engine::DSOHandler& dsoHandler, const Eigen::Vector3d& obsPosition, float radius) const; @@ -70,7 +70,7 @@ class DSODatabase private: std::vector> m_DSOs; - std::unique_ptr m_octreeRoot; + std::unique_ptr m_octreeRoot; std::unique_ptr m_namesDB; std::vector m_catalogNumberIndex; diff --git a/src/celengine/dsodbbuilder.cpp b/src/celengine/dsodbbuilder.cpp index 0ae417cd64..b22e3eddc7 100644 --- a/src/celengine/dsodbbuilder.cpp +++ b/src/celengine/dsodbbuilder.cpp @@ -39,8 +39,9 @@ #include "value.h" namespace astro = celestia::astro; +namespace engine = celestia::engine; -using DynamicDSOOctree = DynamicOctree, double>; +using DynamicDSOOctree = engine::DynamicOctree, double>; using celestia::util::GetLogger; @@ -156,7 +157,7 @@ addName(NameDatabase* namesDB, AstroCatalog::IndexNumber objCatalogNumber, std:: } } -std::unique_ptr +std::unique_ptr buildOctree(std::vector>& DSOs) { GetLogger()->debug("Sorting DSOs into octree . . .\n"); diff --git a/src/celengine/dsooctree.cpp b/src/celengine/dsooctree.cpp index 123791745a..0acbb13e3d 100644 --- a/src/celengine/dsooctree.cpp +++ b/src/celengine/dsooctree.cpp @@ -15,8 +15,8 @@ #include #include -namespace astro = celestia::astro; -namespace numbers = celestia::numbers; +namespace celestia::engine +{ // total specialization of the StaticOctree template process*() methods for DSOs: template<> @@ -124,3 +124,5 @@ DSOOctree::processCloseObjects(DSOHandler& processor, } } } + +} // end namespace celestia::engine diff --git a/src/celengine/dsooctree.h b/src/celengine/dsooctree.h index b7ef947e1b..fb56cc8113 100644 --- a/src/celengine/dsooctree.h +++ b/src/celengine/dsooctree.h @@ -18,6 +18,9 @@ #include "deepskyobj.h" #include "octree.h" +namespace celestia::engine +{ + using DSOOctree = StaticOctree, double>; using DSOHandler = OctreeProcessor, double>; @@ -33,3 +36,5 @@ void DSOOctree::processCloseObjects(DSOHandler&, const PointType&, double, double) const; + +} // end namespace celestia::engine diff --git a/src/celengine/objectrenderer.h b/src/celengine/objectrenderer.h index 420c9c28ef..15c4d2606c 100644 --- a/src/celengine/objectrenderer.h +++ b/src/celengine/objectrenderer.h @@ -18,7 +18,7 @@ class Observer; class Renderer; template -class ObjectRenderer : public OctreeProcessor +class ObjectRenderer : public celestia::engine::OctreeProcessor { public: const Observer* observer { nullptr }; diff --git a/src/celengine/octree.h b/src/celengine/octree.h index 0d81a270a1..d24f14bed0 100644 --- a/src/celengine/octree.h +++ b/src/celengine/octree.h @@ -19,6 +19,9 @@ #include #include +namespace celestia::engine +{ + // The StaticOctree template arguments are: // OBJ: object hanging from the node, // PREC: floating point precision of the culling operations at node level. @@ -128,3 +131,5 @@ StaticOctree::countObjects() const return count; } + +} // end namespace celestia::engine diff --git a/src/celengine/octreebuilder.h b/src/celengine/octreebuilder.h index e248fea2b4..a75f08bae3 100644 --- a/src/celengine/octreebuilder.h +++ b/src/celengine/octreebuilder.h @@ -24,6 +24,9 @@ #include "octree.h" +namespace celestia::engine +{ + constexpr inline unsigned int OctreeXPos = 1; constexpr inline unsigned int OctreeYPos = 2; constexpr inline unsigned int OctreeZPos = 4; @@ -196,3 +199,5 @@ DynamicOctree::rebuildAndSort(OBJ*& sortedObjects) return staticNode; } + +} // end namespace celestia::engine diff --git a/src/celengine/stardb.cpp b/src/celengine/stardb.cpp index 71962ccbd1..fedacf08a7 100644 --- a/src/celengine/stardb.cpp +++ b/src/celengine/stardb.cpp @@ -21,6 +21,7 @@ using namespace std::string_view_literals; namespace compat = celestia::compat; +namespace engine = celestia::engine; namespace { @@ -197,7 +198,7 @@ StarDatabase::getStarNameList(const Star& star, unsigned int maxNames) const } void -StarDatabase::findVisibleStars(StarHandler& starHandler, +StarDatabase::findVisibleStars(engine::StarHandler& starHandler, const Eigen::Vector3f& position, const Eigen::Quaternionf& orientation, float fovY, @@ -232,7 +233,7 @@ StarDatabase::findVisibleStars(StarHandler& starHandler, } void -StarDatabase::findCloseStars(StarHandler& starHandler, +StarDatabase::findCloseStars(engine::StarHandler& starHandler, const Eigen::Vector3f& position, float radius) const { diff --git a/src/celengine/stardb.h b/src/celengine/stardb.h index 444f0983d9..f753b12747 100644 --- a/src/celengine/stardb.h +++ b/src/celengine/stardb.h @@ -54,14 +54,14 @@ class StarDatabase void getCompletion(std::vector&, std::string_view) const; - void findVisibleStars(StarHandler& starHandler, + void findVisibleStars(celestia::engine::StarHandler& starHandler, const Eigen::Vector3f& obsPosition, const Eigen::Quaternionf& obsOrientation, float fovY, float aspectRatio, float limitingMag) const; - void findCloseStars(StarHandler& starHandler, + void findCloseStars(celestia::engine::StarHandler& starHandler, const Eigen::Vector3f& obsPosition, float radius) const; @@ -74,10 +74,10 @@ class StarDatabase Star* searchCrossIndex(StarCatalog, AstroCatalog::IndexNumber number) const; std::uint32_t nStars{ 0 }; - std::unique_ptr stars; //NOSONAR - std::unique_ptr namesDB; - std::vector catalogNumberIndex; - std::unique_ptr octreeRoot; + std::unique_ptr stars; //NOSONAR + std::unique_ptr namesDB; + std::vector catalogNumberIndex; + std::unique_ptr octreeRoot; friend class StarDatabaseBuilder; }; diff --git a/src/celengine/stardbbuilder.cpp b/src/celengine/stardbbuilder.cpp index e0db1ea3b0..99ad09dce9 100644 --- a/src/celengine/stardbbuilder.cpp +++ b/src/celengine/stardbbuilder.cpp @@ -52,7 +52,7 @@ namespace ephem = celestia::ephem; namespace math = celestia::math; namespace util = celestia::util; -using DynamicStarOctree = DynamicOctree; +using DynamicStarOctree = engine::DynamicOctree; using util::GetLogger; diff --git a/src/celengine/staroctree.cpp b/src/celengine/staroctree.cpp index 45efe5c921..8b50fe3d67 100644 --- a/src/celengine/staroctree.cpp +++ b/src/celengine/staroctree.cpp @@ -15,8 +15,8 @@ #include #include -namespace astro = celestia::astro; -namespace numbers = celestia::numbers; +namespace celestia::engine +{ namespace { @@ -138,3 +138,5 @@ StarOctree::processCloseObjects(StarHandler& processor, } } } + +} // end namespace celestia::engine diff --git a/src/celengine/staroctree.h b/src/celengine/staroctree.h index fb28dd60aa..8a88c38a31 100644 --- a/src/celengine/staroctree.h +++ b/src/celengine/staroctree.h @@ -17,6 +17,9 @@ #include #include +namespace celestia::engine +{ + using StarOctree = StaticOctree; using StarHandler = OctreeProcessor; @@ -32,3 +35,5 @@ void StarOctree::processCloseObjects(StarHandler&, const PointType&, float, float) const; + +} // end namespace celestia::engine diff --git a/src/celengine/universe.cpp b/src/celengine/universe.cpp index 0a3f5acfc6..5ff159cc40 100644 --- a/src/celengine/universe.cpp +++ b/src/celengine/universe.cpp @@ -41,8 +41,7 @@ namespace constexpr double ANGULAR_RES = 3.5e-6; - -class ClosestStarFinder : public StarHandler +class ClosestStarFinder : public engine::StarHandler { public: ClosestStarFinder(float _maxDistance, const Universe* _universe); @@ -80,8 +79,7 @@ ClosestStarFinder::process(const Star& star, float distance, float /*unused*/) } } - -class NearStarFinder : public StarHandler +class NearStarFinder : public engine::StarHandler { public: NearStarFinder(float _maxDistance, std::vector& nearStars); @@ -107,8 +105,6 @@ NearStarFinder::process(const Star& star, float distance, float /*unused*/) nearStars.push_back(&star); } - - struct PlanetPickInfo { double sinAngle2Closest; @@ -120,7 +116,6 @@ struct PlanetPickInfo float atanTolerance; }; - bool ApproxPlanetPickTraversal(Body* body, PlanetPickInfo& pickInfo) { @@ -153,7 +148,6 @@ ApproxPlanetPickTraversal(Body* body, PlanetPickInfo& pickInfo) return true; } - // Perform an intersection test between the pick ray and a body bool ExactPlanetPickTraversal(Body* body, PlanetPickInfo& pickInfo) @@ -259,9 +253,8 @@ traverseFrameTree(const FrameTree* frameTree, return true; } - // StarPicker is a callback class for StarDatabase::findVisibleStars -class StarPicker : public StarHandler +class StarPicker : public engine::StarHandler { public: StarPicker(const Eigen::Vector3f&, const Eigen::Vector3f&, double, float); @@ -329,8 +322,7 @@ StarPicker::process(const Star& star, float /*unused*/, float /*unused*/) } } - -class CloseStarPicker : public StarHandler +class CloseStarPicker : public engine::StarHandler { public: CloseStarPicker(const UniversalCoord& pos, @@ -351,7 +343,6 @@ class CloseStarPicker : public StarHandler double sinAngle2Closest; }; - CloseStarPicker::CloseStarPicker(const UniversalCoord& pos, const Eigen::Vector3f& dir, double t, @@ -414,8 +405,7 @@ CloseStarPicker::process(const Star& star, } } - -class DSOPicker : public DSOHandler +class DSOPicker : public engine::DSOHandler { public: DSOPicker(const Eigen::Vector3d& pickOrigin, @@ -435,7 +425,6 @@ class DSOPicker : public DSOHandler double sinAngle2Closest; }; - DSOPicker::DSOPicker(const Eigen::Vector3d& pickOrigin, const Eigen::Vector3d& pickDir, std::uint64_t renderFlags, @@ -448,7 +437,6 @@ DSOPicker::DSOPicker(const Eigen::Vector3d& pickOrigin, { } - void DSOPicker::process(const std::unique_ptr& dso, double, float) //NOSONAR { @@ -477,8 +465,7 @@ DSOPicker::process(const std::unique_ptr& dso, double, float) //N } } - -class CloseDSOPicker : public DSOHandler +class CloseDSOPicker : public engine::DSOHandler { public: CloseDSOPicker(const Eigen::Vector3d& pos, @@ -500,7 +487,6 @@ class CloseDSOPicker : public DSOHandler double largestCosAngle; }; - CloseDSOPicker::CloseDSOPicker(const Eigen::Vector3d& pos, const Eigen::Vector3d& dir, std::uint64_t renderFlags, @@ -515,7 +501,6 @@ CloseDSOPicker::CloseDSOPicker(const Eigen::Vector3d& pos, { } - void CloseDSOPicker::process(const std::unique_ptr& dso, //NOSONAR double distance, @@ -565,25 +550,21 @@ getLocationsCompletion(std::vector& completion, } // end unnamed namespace - // Needs definition of ConstellationBoundaries Universe::~Universe() = default; - StarDatabase* Universe::getStarCatalog() const { return starCatalog.get(); } - void Universe::setStarCatalog(std::unique_ptr&& catalog) { starCatalog = std::move(catalog); } - SolarSystemCatalog* Universe::getSolarSystemCatalog() const { @@ -596,49 +577,42 @@ Universe::setSolarSystemCatalog(std::unique_ptr&& catalog) solarSystemCatalog = std::move(catalog); } - DSODatabase* Universe::getDSOCatalog() const { return dsoCatalog.get(); } - void Universe::setDSOCatalog(std::unique_ptr&& catalog) { dsoCatalog = std::move(catalog); } - AsterismList* Universe::getAsterisms() const { return asterisms.get(); } - void Universe::setAsterisms(std::unique_ptr&& _asterisms) { asterisms = std::move(_asterisms); } - ConstellationBoundaries* Universe::getBoundaries() const { return boundaries.get(); } - void Universe::setBoundaries(std::unique_ptr&& _boundaries) { boundaries = std::move(_boundaries); } - // Return the planetary system of a star, or nullptr if it has no planets. SolarSystem* Universe::getSolarSystem(const Star* star) const @@ -653,7 +627,6 @@ Universe::getSolarSystem(const Star* star) const : iter->second.get(); } - // A more general version of the method above--return the solar system // that contains an object, or nullptr if there is no solar sytstem. SolarSystem* @@ -686,7 +659,6 @@ Universe::getSolarSystem(const Selection& sel) const } } - // Create a new solar system for a star and return a pointer to it; if it // already has a solar system, just return a pointer to the existing one. SolarSystem* @@ -701,14 +673,12 @@ Universe::getOrCreateSolarSystem(Star* star) const return iter->second.get(); } - const celestia::MarkerList& Universe::getMarkers() const { return markers; } - void Universe::markObject(const Selection& sel, const celestia::MarkerRepresentation& rep, @@ -735,7 +705,6 @@ Universe::markObject(const Selection& sel, marker.setSizing(sizing); } - void Universe::unmarkObject(const Selection& sel, int priority) { @@ -745,14 +714,12 @@ Universe::unmarkObject(const Selection& sel, int priority) markers.erase(iter); } - void Universe::unmarkAll() { markers.clear(); } - bool Universe::isMarked(const Selection& sel, int priority) const { @@ -761,7 +728,6 @@ Universe::isMarked(const Selection& sel, int priority) const return iter != markers.end() && iter->priority() >= priority; } - Selection Universe::pickPlanet(const SolarSystem& solarSystem, const UniversalCoord& origin, @@ -828,7 +794,6 @@ Universe::pickPlanet(const SolarSystem& solarSystem, return Selection(); } - Selection Universe::pickStar(const UniversalCoord& origin, const Eigen::Vector3f& direction, @@ -867,7 +832,6 @@ Universe::pickStar(const UniversalCoord& origin, return Selection(); } - Selection Universe::pickDeepSkyObject(const UniversalCoord& origin, const Eigen::Vector3f& direction, @@ -902,7 +866,6 @@ Universe::pickDeepSkyObject(const UniversalCoord& origin, return Selection(); } - Selection Universe::pick(const UniversalCoord& origin, const Eigen::Vector3f& direction, @@ -946,7 +909,6 @@ Universe::pick(const UniversalCoord& origin, return sel; } - // Search by name for an immediate child of the specified object. Selection Universe::findChildObject(const Selection& sel, @@ -989,7 +951,6 @@ Universe::findChildObject(const Selection& sel, return Selection(); } - // Search for a name within an object's context. For stars, planets (bodies), // and locations, the context includes all bodies in the associated solar // system. For locations and planets, the context additionally includes @@ -1036,7 +997,6 @@ Universe::findObjectInContext(const Selection& sel, return Selection(); } - // Select an object by name, with the following priority: // 1. Try to look up the name in the star catalog // 2. Search the deep sky catalog for a matching name. @@ -1078,7 +1038,6 @@ Universe::find(std::string_view s, return Selection(); } - // Find an object from a path, for example Sol/Earth/Moon or Upsilon And/b // Currently, 'absolute' paths starting with a / are not supported nor are // paths that contain galaxies. The caller may pass in a list of solar systems @@ -1117,7 +1076,6 @@ Universe::findPath(std::string_view s, return sel; } - void Universe::getCompletion(std::vector& completion, std::string_view s, @@ -1150,7 +1108,6 @@ Universe::getCompletion(std::vector& completion, starCatalog->getCompletion(completion, s); } - void Universe::getCompletionPath(std::vector& completion, std::string_view s, @@ -1202,7 +1159,6 @@ Universe::getCompletionPath(std::vector& completion, } } - // Return the closest solar system to position, or nullptr if there are no planets // with in one light year. SolarSystem* @@ -1215,7 +1171,6 @@ Universe::getNearestSolarSystem(const UniversalCoord& position) const return getSolarSystem(closestFinder.closestStar); } - void Universe::getNearStars(const UniversalCoord& position, float maxDistance, From bdca26400ec872872458456aabbdebb134c8a1c0 Mon Sep 17 00:00:00 2001 From: Andrew Tribick Date: Fri, 26 Jul 2024 00:13:27 +0200 Subject: [PATCH 11/15] Linearize StaticOctree --- src/celastro/astro.h | 12 +- src/celengine/dsodb.cpp | 55 +++++---- src/celengine/dsodb.h | 8 +- src/celengine/dsodbbuilder.cpp | 45 ++++--- src/celengine/dsooctree.cpp | 145 ++++++++++------------- src/celengine/dsooctree.h | 60 ++++++++-- src/celengine/octree.h | 170 +++++++++++++++----------- src/celengine/octreebuilder.h | 204 +++++++++++++++++++++----------- src/celengine/stardb.cpp | 25 ++-- src/celengine/stardb.h | 6 +- src/celengine/stardbbuilder.cpp | 37 +++--- src/celengine/staroctree.cpp | 144 ++++++++++------------ src/celengine/staroctree.h | 59 +++++++-- 13 files changed, 559 insertions(+), 411 deletions(-) diff --git a/src/celastro/astro.h b/src/celastro/astro.h index 7531ddc92e..ab85e4b24b 100644 --- a/src/celastro/astro.h +++ b/src/celastro/astro.h @@ -74,12 +74,20 @@ float lumToAppMag(float lum, float lyrs); float absMagToLum(float mag); float appMagToLum(float mag, float lyrs); +template +CELESTIA_CMATH_CONSTEXPR T +distanceModulus(T lyrs) +{ + using std::log10; + return T(5) * log10(lyrs / LY_PER_PARSEC) - T(5); +} + template CELESTIA_CMATH_CONSTEXPR T absToAppMag(T absMag, T lyrs) { using std::log10; - return absMag - T(5) + T(5) * log10(lyrs / LY_PER_PARSEC); + return absMag + distanceModulus(lyrs); } template @@ -87,7 +95,7 @@ CELESTIA_CMATH_CONSTEXPR T appToAbsMag(T appMag, T lyrs) { using std::log10; - return appMag + T(5) - T(5) * log10(lyrs / LY_PER_PARSEC); + return appMag - distanceModulus(lyrs); } // Distance conversions diff --git a/src/celengine/dsodb.cpp b/src/celengine/dsodb.cpp index 7454abef40..95662b965e 100644 --- a/src/celengine/dsodb.cpp +++ b/src/celengine/dsodb.cpp @@ -13,6 +13,7 @@ #include "dsodb.h" #include +#include #include #include @@ -25,12 +26,10 @@ using celestia::util::GetLogger; DSODatabase::~DSODatabase() = default; -DSODatabase::DSODatabase(std::vector>&& DSOs, - std::unique_ptr&& octreeRoot, +DSODatabase::DSODatabase(std::unique_ptr&& octreeRoot, std::unique_ptr&& namesDB, std::vector&& catalogNumberIndex, float avgAbsMag) : - m_DSOs(std::move(DSOs)), m_octreeRoot(std::move(octreeRoot)), m_namesDB(std::move(namesDB)), m_catalogNumberIndex(std::move(catalogNumberIndex)), @@ -46,12 +45,14 @@ DSODatabase::find(const AstroCatalog::IndexNumber catalogNumber) const catalogNumber, [this](std::uint32_t idx, AstroCatalog::IndexNumber catNum) { - return m_DSOs[idx]->getIndex() < catNum; + return (*m_octreeRoot)[idx]->getIndex() < catNum; }); - return (it != m_catalogNumberIndex.end() && m_DSOs[*it]->getIndex() == catalogNumber) - ? m_DSOs[*it].get() - : nullptr; + if (it == m_catalogNumberIndex.end()) + return nullptr; + + DeepSkyObject* dso = (*m_octreeRoot)[*it].get(); + return dso->getIndex() == catalogNumber ? dso : nullptr; } DeepSkyObject* @@ -126,31 +127,34 @@ DSODatabase::findVisibleDSOs(engine::DSOHandler& dsoHandler, float limitingMag) const { // Compute the bounding planes of an infinite view frustum - Eigen::Hyperplane frustumPlanes[5]; - Eigen::Vector3d planeNormals[5]; + std::array, 5> frustumPlanes; Eigen::Quaterniond obsOrientd = obsOrient.cast(); Eigen::Matrix3d rot = obsOrientd.toRotationMatrix().transpose(); double h = std::tan(fovY / 2); double w = h * aspectRatio; - planeNormals[0] = Eigen::Vector3d( 0, 1, -h); - planeNormals[1] = Eigen::Vector3d( 0, -1, -h); - planeNormals[2] = Eigen::Vector3d( 1, 0, -w); - planeNormals[3] = Eigen::Vector3d(-1, 0, -w); - planeNormals[4] = Eigen::Vector3d( 0, 0, -1); + std::array planeNormals + { + Eigen::Vector3d( 0, 1, -h), + Eigen::Vector3d( 0, -1, -h), + Eigen::Vector3d( 1, 0, -w), + Eigen::Vector3d(-1, 0, -w), + Eigen::Vector3d( 0, 0, -1), + }; for (int i = 0; i < 5; ++i) { - planeNormals[i] = rot * planeNormals[i].normalized(); - frustumPlanes[i] = Eigen::Hyperplane(planeNormals[i], obsPos); + planeNormals[i] = rot * planeNormals[i].normalized(); + frustumPlanes[i] = Eigen::Hyperplane(planeNormals[i], obsPos); } - m_octreeRoot->processVisibleObjects(dsoHandler, - obsPos, - frustumPlanes, - limitingMag, - DSO_OCTREE_ROOT_SIZE); + engine::DSOOctreeVisibleObjectsProcessor processor(&dsoHandler, + obsPos, + frustumPlanes, + limitingMag); + + m_octreeRoot->processDepthFirst(processor); } void @@ -158,8 +162,9 @@ DSODatabase::findCloseDSOs(engine::DSOHandler& dsoHandler, const Eigen::Vector3d& obsPos, float radius) const { - m_octreeRoot->processCloseObjects(dsoHandler, - obsPos, - radius, - DSO_OCTREE_ROOT_SIZE); + engine::DSOOctreeCloseObjectsProcessor processor(&dsoHandler, + obsPos, + radius); + + m_octreeRoot->processDepthFirst(processor); } diff --git a/src/celengine/dsodb.h b/src/celengine/dsodb.h index 05d2bcf52a..c9a05ed8c3 100644 --- a/src/celengine/dsodb.h +++ b/src/celengine/dsodb.h @@ -36,8 +36,7 @@ constexpr inline float DSO_OCTREE_ROOT_SIZE = 1.0e11f; class DSODatabase { public: - DSODatabase(std::vector>&&, - std::unique_ptr&&, + DSODatabase(std::unique_ptr&&, std::unique_ptr&&, std::vector&&, float); @@ -69,7 +68,6 @@ class DSODatabase float getAverageAbsoluteMagnitude() const; private: - std::vector> m_DSOs; std::unique_ptr m_octreeRoot; std::unique_ptr m_namesDB; std::vector m_catalogNumberIndex; @@ -82,13 +80,13 @@ class DSODatabase inline DeepSkyObject* DSODatabase::getDSO(const std::uint32_t n) const { - return m_DSOs[n].get(); + return (*m_octreeRoot)[n].get(); } inline std::uint32_t DSODatabase::size() const { - return static_cast(m_DSOs.size()); + return m_octreeRoot->size(); } inline float diff --git a/src/celengine/dsodbbuilder.cpp b/src/celengine/dsodbbuilder.cpp index b22e3eddc7..4ac541f7ac 100644 --- a/src/celengine/dsodbbuilder.cpp +++ b/src/celengine/dsodbbuilder.cpp @@ -55,11 +55,10 @@ const inline std::uint32_t DynamicDSOOctree::SPLIT_THRESHOLD = 10; // possible to determine quickly whether or not to cull subtrees. template<> -bool -DynamicDSOOctree::exceedsBrightnessThreshold(const std::unique_ptr& dso, //NOSONAR - float absMag) +float +DynamicDSOOctree::getMagnitude(const std::unique_ptr& dso) //NOSONAR { - return dso->getAbsoluteMagnitude() <= absMag; + return dso->getAbsoluteMagnitude(); } template<> @@ -80,9 +79,9 @@ DynamicDSOOctree::applyDecay(float excludingFactor) } template<> -DynamicDSOOctree* -DynamicDSOOctree::getChild(const std::unique_ptr& obj, //NOSONAR - const PointType& cellCenterPos) const +unsigned int +DynamicDSOOctree::getChildIndex(const std::unique_ptr& obj, //NOSONAR + const PointType& cellCenterPos) { PointType objPos = obj->getPosition(); @@ -91,7 +90,7 @@ DynamicDSOOctree::getChild(const std::unique_ptr& obj, //NOSONAR child |= objPos.y() < cellCenterPos.y() ? 0U : OctreeYPos; child |= objPos.z() < cellCenterPos.z() ? 0U : OctreeZPos; - return (*m_children)[child].get(); + return child; } namespace @@ -114,13 +113,13 @@ createDSO(std::string_view objType) } float -calcAvgAbsMag(const std::vector>& DSOs) +calcAvgAbsMag(const engine::DSOOctree& DSOs) { auto nDSOeff = DSOs.size(); float avgAbsMag = 0.0f; - for (const auto& dso : DSOs) + for (engine::OctreeObjectIndex i = 0, end = nDSOeff; i < end; ++i) { - float DSOmag = dso->getAbsoluteMagnitude(); + float DSOmag = DSOs[i]->getAbsoluteMagnitude(); // take only DSO's with realistic AbsMag entry // (> DSO_DEFAULT_ABS_MAGNITUDE) into account @@ -163,31 +162,28 @@ buildOctree(std::vector>& DSOs) GetLogger()->debug("Sorting DSOs into octree . . .\n"); float absMag = astro::appToAbsMag(DSO_OCTREE_MAGNITUDE, DSO_OCTREE_ROOT_SIZE * celestia::numbers::sqrt3_v); + auto dsoCount = static_cast(DSOs.size()); + auto root = std::make_unique(Eigen::Vector3d::Zero(), absMag); for (auto& dso : DSOs) root->insertObject(dso, DSO_OCTREE_ROOT_SIZE); GetLogger()->debug("Spatially sorting DSOs for improved locality of reference . . .\n"); - std::vector> sortedDSOs; - sortedDSOs.resize(DSOs.size()); - std::unique_ptr* firstDSO = sortedDSOs.data(); // The spatial sorting part is useless for DSOs since we // are storing pointers to objects and not the objects themselves: - auto octreeRoot = root->rebuildAndSort(firstDSO); + auto octreeRoot = root->rebuildAndSort(DSO_OCTREE_ROOT_SIZE, dsoCount); GetLogger()->debug("{} DSOs total.\nOctree has {} nodes and {} DSOs.\n", - firstDSO - sortedDSOs.data(), - UINT32_C(1) + octreeRoot->countChildren(), - octreeRoot->countObjects()); - - DSOs = std::move(sortedDSOs); + dsoCount, + octreeRoot->nodeCount(), + octreeRoot->size()); return octreeRoot; } std::vector -buildCatalogNumberIndex(const std::vector>& DSOs) +buildCatalogNumberIndex(const engine::DSOOctree& DSOs) { GetLogger()->debug("Building catalog number indexes . . .\n"); @@ -285,13 +281,12 @@ std::unique_ptr DSODatabaseBuilder::finish() { auto octreeRoot = buildOctree(DSOs); - auto catalogNumberIndex = buildCatalogNumberIndex(DSOs); - float avgAbsMag = calcAvgAbsMag(DSOs); + auto catalogNumberIndex = buildCatalogNumberIndex(*octreeRoot); + float avgAbsMag = calcAvgAbsMag(*octreeRoot); GetLogger()->info(_("Loaded {} deep space objects\n"), DSOs.size()); - return std::make_unique(std::move(DSOs), - std::move(octreeRoot), + return std::make_unique(std::move(octreeRoot), std::move(namesDB), std::move(catalogNumberIndex), avgAbsMag); diff --git a/src/celengine/dsooctree.cpp b/src/celengine/dsooctree.cpp index 0acbb13e3d..84fd21615b 100644 --- a/src/celengine/dsooctree.cpp +++ b/src/celengine/dsooctree.cpp @@ -14,114 +14,99 @@ #include #include +#include namespace celestia::engine { -// total specialization of the StaticOctree template process*() methods for DSOs: -template<> -void -DSOOctree::processVisibleObjects(DSOHandler& processor, - const PointType& obsPosition, - const PlaneType* frustumPlanes, - float limitingFactor, - double scale) const +// The version of cppcheck used by Codacy doesn't seem to detect the field initializer + +DSOOctreeVisibleObjectsProcessor::DSOOctreeVisibleObjectsProcessor(DSOHandler* dsoHandler, // cppcheck-suppress uninitMemberVar + const DSOOctree::PointType& obsPosition, + util::array_view frustumPlanes, + float limitingFactor) : + m_dsoHandler(dsoHandler), + m_obsPosition(obsPosition), + m_frustumPlanes(frustumPlanes), + m_limitingFactor(limitingFactor) { - // See if this node lies within the view frustum +} +bool +DSOOctreeVisibleObjectsProcessor::checkNode(const DSOOctree::PointType& center, + double size, + float factor) +{ // Test the cubic octree node against each one of the five // planes that define the infinite view frustum. for (unsigned int i = 0; i < 5; ++i) { - const PlaneType& plane = frustumPlanes[i]; + const PlaneType& plane = m_frustumPlanes[i]; - double r = scale * plane.normal().cwiseAbs().sum(); - if (plane.signedDistance(m_cellCenterPos) < -r) - return; + double r = size * plane.normal().cwiseAbs().sum(); + if (plane.signedDistance(center) < -r) + return false; } // Compute the distance to node; this is equal to the distance to // the cellCenterPos of the node minus the boundingRadius of the node, scale * SQRT3. - double minDistance = (obsPosition - m_cellCenterPos).norm() - scale * numbers::sqrt3; + double minDistance = (m_obsPosition - center).norm() - size * numbers::sqrt3; - // Process the objects in this node - double dimmest = minDistance > 0.0 ? astro::appToAbsMag((double) limitingFactor, minDistance) : 1000.0; + // Check whether the brightest object in this node is bright enough to render + auto distanceModulus = static_cast(astro::distanceModulus(minDistance)); + if (minDistance > 0.0 && (factor + distanceModulus) > m_limitingFactor) + return false; - for (std::uint32_t i = 0; i < m_nObjects; ++i) - { - const auto& obj = m_firstObject[i]; - float absMag = obj->getAbsoluteMagnitude(); - if (absMag < dimmest) - { - double distance = (obsPosition - obj->getPosition()).norm() - obj->getBoundingSphereRadius(); - auto appMag = static_cast((distance >= 32.6167) ? astro::absToAppMag(static_cast(absMag), distance) : absMag); - - if (appMag < limitingFactor) - processor.process(obj, distance, absMag); - } - } + // Dimmest absolute magnitude to process + m_dimmest = minDistance > 0.0 ? (m_limitingFactor - distanceModulus) : 1000.0; - // See if any of the objects in child nodes are potentially included - // that we need to recurse deeper. - if (m_children != nullptr && - (minDistance <= 0.0 || astro::absToAppMag(static_cast(m_exclusionFactor), minDistance) <= limitingFactor)) - { - // Recurse into the child nodes - for (int i = 0; i < 8; ++i) - { - (*m_children)[i]->processVisibleObjects(processor, - obsPosition, - frustumPlanes, - limitingFactor, - scale * 0.5f); - } - } + return true; } -template<> void -DSOOctree::processCloseObjects(DSOHandler& processor, - const PointType& obsPosition, - double boundingRadius, - double scale) const +DSOOctreeVisibleObjectsProcessor::process(const std::unique_ptr& obj) const //NOSONAR { - // Compute the distance to node; this is equal to the distance to - // the cellCenterPos of the node minus the boundingRadius of the node, scale * SQRT3. - double nodeDistance = (obsPosition - m_cellCenterPos).norm() - scale * numbers::sqrt3; - - if (nodeDistance > boundingRadius) + float absMag = obj->getAbsoluteMagnitude(); + if (absMag > m_dimmest) return; - // At this point, we've determined that the cellCenterPos of the node is - // close enough that we must check individual objects for proximity. + double distance = (m_obsPosition - obj->getPosition()).norm() - obj->getBoundingSphereRadius(); + auto appMag = static_cast((distance >= 32.6167) ? astro::absToAppMag(static_cast(absMag), distance) : absMag); - // Compute distance squared to avoid having to sqrt for distance - // comparison. - double radiusSquared = boundingRadius * boundingRadius; + if (appMag <= m_limitingFactor) + m_dsoHandler->process(obj, distance, absMag); +} - // Check all the objects in the node. - for (std::uint32_t i = 0; i < m_nObjects; ++i) - { - const auto& obj = m_firstObject[i]; - PointType offset = obsPosition - obj->getPosition(); - if (offset.squaredNorm() < radiusSquared) - { - float absMag = obj->getAbsoluteMagnitude(); - double distance = offset.norm() - obj->getBoundingSphereRadius(); - processor.process(obj, distance, absMag); - } - } +DSOOctreeCloseObjectsProcessor::DSOOctreeCloseObjectsProcessor(DSOHandler* dsoHandler, + const DSOOctree::PointType& obsPosition, + double boundingRadius) : + m_dsoHandler(dsoHandler), + m_obsPosition(obsPosition), + m_boundingRadius(boundingRadius), + m_radiusSquared(math::square(boundingRadius)) +{ +} - // Recurse into the child nodes - if (m_children != nullptr) +bool +DSOOctreeCloseObjectsProcessor::checkNode(const DSOOctree::PointType& center, + double size, + float /* factor */) const +{ + // Compute the distance to node; this is equal to the distance to + // the cellCenterPos of the node minus the boundingRadius of the node, scale * SQRT3. + double nodeDistance = (m_obsPosition - center).norm() - size * numbers::sqrt3; + return nodeDistance <= m_boundingRadius; +} + +void +DSOOctreeCloseObjectsProcessor::process(const std::unique_ptr& obj) const //NOSONAR +{ + Eigen::Vector3d offset = m_obsPosition - obj->getPosition(); + if (offset.squaredNorm() < m_radiusSquared) { - for (int i = 0; i < 8; ++i) - { - (*m_children)[i]->processCloseObjects(processor, - obsPosition, - boundingRadius, - scale * 0.5f); - } + float absMag = obj->getAbsoluteMagnitude(); + double distance = offset.norm() - obj->getBoundingSphereRadius(); + m_dsoHandler->process(obj, distance, absMag); } } diff --git a/src/celengine/dsooctree.h b/src/celengine/dsooctree.h index fb56cc8113..fea925e278 100644 --- a/src/celengine/dsooctree.h +++ b/src/celengine/dsooctree.h @@ -15,6 +15,9 @@ #include #include +#include + +#include #include "deepskyobj.h" #include "octree.h" @@ -24,17 +27,50 @@ namespace celestia::engine using DSOOctree = StaticOctree, double>; using DSOHandler = OctreeProcessor, double>; -template<> -void DSOOctree::processVisibleObjects(DSOHandler&, - const PointType&, - const PlaneType*, - float, - double) const; - -template<> -void DSOOctree::processCloseObjects(DSOHandler&, - const PointType&, - double, - double) const; +// This class searches the octree for objects that are likely to be visible +// to a viewer with the specified obsPosition and limitingFactor. The +// octreeProcessor is invoked for each potentially visible object --no object with +// a property greater than limitingFactor will be processed, but +// objects that are outside the view frustum may be. Frustum tests are performed +// only at the node level to optimize the octree traversal, so an exact test +// (if one is required) is the responsibility of the callback method. +class DSOOctreeVisibleObjectsProcessor +{ +public: + using PlaneType = Eigen::Hyperplane; + + DSOOctreeVisibleObjectsProcessor(DSOHandler*, + const DSOOctree::PointType&, + util::array_view, + float); + + bool checkNode(const DSOOctree::PointType&, double, float); + void process(const std::unique_ptr&) const; //NOSONAR + +private: + DSOHandler* m_dsoHandler; + DSOOctree::PointType m_obsPosition; + util::array_view m_frustumPlanes; + float m_limitingFactor; + + float m_dimmest{ 1000.0f }; +}; + +class DSOOctreeCloseObjectsProcessor +{ +public: + DSOOctreeCloseObjectsProcessor(DSOHandler*, + const DSOOctree::PointType&, + double); + + bool checkNode(const DSOOctree::PointType&, double, float) const; + void process(const std::unique_ptr&) const; //NOSONAR + +private: + DSOHandler* m_dsoHandler; + DSOOctree::PointType m_obsPosition; + double m_boundingRadius; + double m_radiusSquared; +}; } // end namespace celestia::engine diff --git a/src/celengine/octree.h b/src/celengine/octree.h index d24f14bed0..8a840f6ba0 100644 --- a/src/celengine/octree.h +++ b/src/celengine/octree.h @@ -15,19 +15,46 @@ #include #include #include +#include +#include #include -#include namespace celestia::engine { -// The StaticOctree template arguments are: -// OBJ: object hanging from the node, -// PREC: floating point precision of the culling operations at node level. -// The hierarchy of octree nodes is built using a single precision value (excludingFactor), which relates to an -// OBJ's limiting property defined by the octree particular specialization: ie. we use [absolute magnitude] for star octrees, etc. -// For details, see notes below. +using OctreeNodeIndex = std::uint32_t; +using OctreeObjectIndex = std::uint32_t; + +constexpr inline OctreeNodeIndex InvalidOctreeNode = UINT32_MAX; + +namespace detail +{ + +template +struct StaticOctreeNode +{ + using PointType = Eigen::Matrix; + + StaticOctreeNode(const PointType&, PREC); + + PointType center; + PREC scale; + OctreeNodeIndex right{ InvalidOctreeNode }; + OctreeObjectIndex first{ 0 }; + OctreeObjectIndex last{ 0 }; + float brightFactor{ 1000.0 }; +}; + +template +StaticOctreeNode::StaticOctreeNode(const PointType& _center, + PREC _scale) : + center(_center), + scale(_scale) +{ +} + +} // end namespace celestia::engine::detail template class OctreeProcessor @@ -43,93 +70,96 @@ class OctreeProcessor template class DynamicOctree; +// The StaticOctree template arguments are: +// OBJ: object hanging from the node, +// PREC: floating point precision of the culling operations at node level. +// The hierarchy of octree nodes is built using a single precision value (excludingFactor), which relates to an +// OBJ's limiting property defined by the octree particular specialization: ie. we use [absolute magnitude] for star octrees, etc. +// For details, see notes below. + template class StaticOctree { public: using PointType = Eigen::Matrix; - using PlaneType = Eigen::Hyperplane; - - StaticOctree(const PointType& cellCenterPos, - const float exclusionFactor, - OBJ* _firstObject, - std::uint32_t nObjects); - - // These methods are only declared at the template level; we'll implement them as - // full specializations, allowing for different traversal strategies depending on the - // object type and nature. - - // This method searches the octree for objects that are likely to be visible - // to a viewer with the specified obsPosition and limitingFactor. The - // octreeProcessor is invoked for each potentially visible object --no object with - // a property greater than limitingFactor will be processed, but - // objects that are outside the view frustum may be. Frustum tests are performed - // only at the node level to optimize the octree traversal, so an exact test - // (if one is required) is the responsibility of the callback method. - void processVisibleObjects(OctreeProcessor& processor, - const PointType& obsPosition, - const PlaneType* frustumPlanes, - float limitingFactor, - PREC scale) const; - - void processCloseObjects(OctreeProcessor& processor, - const PointType& obsPosition, - PREC boundingRadius, - PREC scale) const; - - std::uint32_t countChildren() const; - std::uint32_t countObjects() const; + + StaticOctree() = default; + ~StaticOctree() = default; + + StaticOctree(const StaticOctree&) = delete; + StaticOctree& operator=(const StaticOctree&) = delete; + StaticOctree(StaticOctree&&) noexcept = default; + StaticOctree& operator=(StaticOctree&&) noexcept = default; + + template + void processDepthFirst(PROCESSOR&) const; + + OctreeObjectIndex size() const; + OctreeNodeIndex nodeCount() const; + + OBJ& operator[](OctreeObjectIndex); + const OBJ& operator[](OctreeObjectIndex) const; private: - using ChildrenType = std::array, 8>; + using NodeType = detail::StaticOctreeNode; - std::unique_ptr m_children; - PointType m_cellCenterPos; - float m_exclusionFactor; - std::uint32_t m_nObjects; - OBJ* m_firstObject; + std::vector m_nodes; + std::vector m_objects; friend class DynamicOctree; }; template -StaticOctree::StaticOctree(const PointType& cellCenterPos, - float exclusionFactor, - OBJ* firstObject, - std::uint32_t nObjects): - m_cellCenterPos(cellCenterPos), - m_exclusionFactor(exclusionFactor), - m_firstObject(firstObject), - m_nObjects(nObjects) +template +void +StaticOctree::processDepthFirst(PROCESSOR& processor) const { + OctreeNodeIndex nodeIdx = 0; + OctreeNodeIndex endIdx = nodeCount(); + while (nodeIdx < endIdx) + { + const NodeType& node = m_nodes[nodeIdx]; + if (!processor.checkNode(node.center, node.scale, node.brightFactor)) + { + nodeIdx = node.right; + continue; + } + + for (OctreeObjectIndex idx = node.first; idx < node.last; ++idx) + { + processor.process(m_objects[idx]); + } + + ++nodeIdx; + } } template -std::uint32_t -StaticOctree::countChildren() const +OctreeObjectIndex +StaticOctree::size() const { - if (m_children == nullptr) - return 0; - - std::uint32_t count = 0; - - for (int i = 0; i < 8; ++i) - count += UINT32_C(1) + (*m_children)[i]->countChildren(); - - return count; + return static_cast(m_objects.size()); } template -std::uint32_t -StaticOctree::countObjects() const +OctreeNodeIndex +StaticOctree::nodeCount() const { - std::uint32_t count = m_nObjects; + return static_cast(m_nodes.size()); +} - if (m_children != nullptr) - for (int i = 0; i < 8; ++i) - count += (*m_children)[i]->countObjects(); +template +OBJ& +StaticOctree::operator[](OctreeObjectIndex idx) +{ + return m_objects[idx]; +} - return count; +template +const OBJ& +StaticOctree::operator[](OctreeObjectIndex idx) const +{ + return m_objects[idx]; } } // end namespace celestia::engine diff --git a/src/celengine/octreebuilder.h b/src/celengine/octreebuilder.h index a75f08bae3..cca2a10adc 100644 --- a/src/celengine/octreebuilder.h +++ b/src/celengine/octreebuilder.h @@ -14,9 +14,11 @@ #pragma once +#include #include #include #include +#include #include #include @@ -46,8 +48,8 @@ class DynamicOctree DynamicOctree(const PointType& cellCenterPos, float exclusionFactor); - void insertObject(OBJ&, const PREC); - std::unique_ptr> rebuildAndSort(OBJ*&); + void insertObject(OBJ&, PREC); + std::unique_ptr> rebuildAndSort(PREC, OctreeObjectIndex) const; private: using ObjectList = std::vector; @@ -55,14 +57,16 @@ class DynamicOctree static const unsigned int SPLIT_THRESHOLD; - static bool exceedsBrightnessThreshold(const OBJ&, float); - static bool isStraddling(const PointType&, const OBJ&); - static float applyDecay(float); + static float getMagnitude(const OBJ&); + static bool isStraddling(const PointType&, const OBJ&); + static float applyDecay(float); + static unsigned int getChildIndex(const OBJ&, const PointType&); void add(OBJ&); - void split(const PREC); - void sortIntoChildNodes(); - DynamicOctree* getChild(const OBJ&, const PointType&) const; + void split(PREC); + DynamicOctree* getChild(const OBJ&, PREC); + + OctreeNodeIndex countNodes() const; std::unique_ptr m_children; PointType m_cellCenterPos; @@ -84,36 +88,39 @@ DynamicOctree::DynamicOctree(const PointType& cellCenterPos, template void -DynamicOctree::insertObject(OBJ& obj, const PREC scale) +DynamicOctree::insertObject(OBJ& obj, PREC scale) { - // If the object can't be placed into this node's children, then put it here: - if (exceedsBrightnessThreshold(obj, m_exclusionFactor) || isStraddling(m_cellCenterPos, obj)) - { - add(obj); - return; - } - - // If we haven't allocated child nodes yet, try to fit - // the object in this node, even though it could be put - // in a child. Only if there are more than SPLIT_THRESHOLD - // objects in the node will we attempt to place the - // object into a child node. This is done in order - // to avoid having the octree degenerate into one object - // per node. - if (m_children == nullptr) + for (DynamicOctree* node = this;;) { - if (m_objects == nullptr || m_objects->size() < SPLIT_THRESHOLD) + if (getMagnitude(obj) <= node->m_exclusionFactor || isStraddling(node->m_cellCenterPos, obj)) { - add(obj); + node->add(obj); return; } - split(scale * 0.5f); - } + // If we haven't allocated child nodes yet, try to fit + // the object in this node, even though it could be put + // in a child. Only if there are more than SPLIT_THRESHOLD + // objects in the node will we attempt to place the + // object into a child node. This is done in order + // to avoid having the octree degenerate into one object + // per node. + + scale *= PREC(0.5); - // We've already allocated child nodes; place the object - // into the appropriate one. - getChild(obj, m_cellCenterPos)->insertObject(obj, scale * PREC(0.5)); + if (node->m_children == nullptr) + { + if (node->m_objects == nullptr || node->m_objects->size() < SPLIT_THRESHOLD) + { + node->add(obj); + return; + } + + node->split(scale); + } + + node = node->getChild(obj, scale); + } } template @@ -128,43 +135,25 @@ DynamicOctree::add(OBJ& obj) template void -DynamicOctree::split(const PREC scale) +DynamicOctree::split(PREC scale) { + assert(m_children == nullptr); m_children = std::make_unique(); - for (unsigned int i = 0U; i < 8U; ++i) - { - PointType centerPos = m_cellCenterPos - + PointType(((i & OctreeXPos) != 0U) ? scale : -scale, - ((i & OctreeYPos) != 0U) ? scale : -scale, - ((i & OctreeZPos) != 0U) ? scale : -scale); - (*m_children)[i] = std::make_unique(centerPos, applyDecay(m_exclusionFactor)); - } - - sortIntoChildNodes(); -} - -// Sort this node's objects into objects that can remain here, -// and objects that should be placed into one of the eight -// child nodes. -template -void -DynamicOctree::sortIntoChildNodes() -{ auto writeIt = m_objects->begin(); auto endIt = m_objects->end(); for (auto readIt = writeIt; readIt != endIt; ++readIt) { OBJ& obj = **readIt; - if (exceedsBrightnessThreshold(obj, m_exclusionFactor) || isStraddling(m_cellCenterPos, obj)) + if (getMagnitude(obj) <= m_exclusionFactor || isStraddling(m_cellCenterPos, obj)) { *writeIt = *readIt; ++writeIt; } else { - getChild(obj, m_cellCenterPos)->add(obj); + getChild(obj, scale)->add(obj); } } @@ -172,32 +161,113 @@ DynamicOctree::sortIntoChildNodes() } template -std::unique_ptr> -DynamicOctree::rebuildAndSort(OBJ*& sortedObjects) +DynamicOctree* +DynamicOctree::getChild(const OBJ& obj, PREC scale) { - OBJ* firstObject = sortedObjects; + assert(m_children != nullptr); - if (m_objects != nullptr) + unsigned int childIndex = getChildIndex(obj, m_cellCenterPos); + auto& result = (*m_children)[childIndex]; + if (result == nullptr) { - for (OBJ* obj : *m_objects) + PointType centerPos = m_cellCenterPos + + PointType(((childIndex & OctreeXPos) != 0U) ? scale : -scale, + ((childIndex & OctreeYPos) != 0U) ? scale : -scale, + ((childIndex & OctreeZPos) != 0U) ? scale : -scale); + result = std::make_unique(centerPos, applyDecay(m_exclusionFactor)); + } + + return result.get(); +} + +template +OctreeNodeIndex +DynamicOctree::countNodes() const +{ + OctreeNodeIndex result = 0; + + std::vector nodeStack; + nodeStack.push_back(this); + while (!nodeStack.empty()) + { + const DynamicOctree* node = nodeStack.back(); + nodeStack.pop_back(); + + ++result; + + if (node->m_children == nullptr) + continue; + + for (const auto& child : *node->m_children) { - *sortedObjects = std::move(*obj); - ++sortedObjects; + if (child != nullptr) + nodeStack.push_back(child.get()); } } - auto nObjects = static_cast(sortedObjects - firstObject); - auto staticNode = std::make_unique>(m_cellCenterPos, m_exclusionFactor, firstObject, nObjects); + return result; +} + +template +std::unique_ptr> +DynamicOctree::rebuildAndSort(PREC scale, OctreeObjectIndex objectCount) const +{ + auto staticOctree = std::make_unique>(); + staticOctree->m_nodes.reserve(countNodes()); + staticOctree->m_objects.reserve(objectCount); + + std::vector> nodeStack; + std::vector nodeIndexStack; + nodeStack.emplace_back(this, scale, 0); - if (m_children != nullptr) + while (!nodeStack.empty()) { - staticNode->m_children = std::make_unique::ChildrenType>(); + auto [node, nodeScale, depth] = nodeStack.back(); + nodeStack.pop_back(); + + // any nodes in the node stack with a depth greater or equal to this one + // will have this node as the node to jump to when skipping the subtree + auto nodeIndex = static_cast(staticOctree->m_nodes.size()); + while (nodeIndexStack.size() > depth) + { + staticOctree->m_nodes[nodeIndexStack.back()].right = nodeIndex; + nodeIndexStack.pop_back(); + } + + nodeIndexStack.push_back(nodeIndex); - for (unsigned int i = 0U; i < 8U; ++i) - (*staticNode->m_children)[i] = (*m_children)[i]->rebuildAndSort(sortedObjects); + auto& staticNode = staticOctree->m_nodes.emplace_back(node->m_cellCenterPos, nodeScale); + if (node->m_objects != nullptr && !node->m_objects->empty()) + { + staticNode.first = static_cast(staticOctree->m_objects.size()); + staticNode.last = staticNode.first + static_cast(node->m_objects->size()); + + for (OBJ* obj : *node->m_objects) + { + staticNode.brightFactor = std::min(staticNode.brightFactor, getMagnitude(*obj)); + staticOctree->m_objects.emplace_back(std::move(*obj)); + } + + // update parent node brightness factors, necessary in case they have no + // stars + for (OctreeNodeIndex parentDepth = 0; parentDepth < depth; ++parentDepth) + { + auto& parentNode = staticOctree->m_nodes[nodeIndexStack[parentDepth]]; + parentNode.brightFactor = std::min(parentNode.brightFactor, staticNode.brightFactor); + } + } + + if (node->m_children == nullptr) + continue; + + for (const auto& child : *node->m_children) + { + if (child != nullptr) + nodeStack.emplace_back(child.get(), nodeScale * PREC(0.5), depth + 1); + } } - return staticNode; + return staticOctree; } } // end namespace celestia::engine diff --git a/src/celengine/stardb.cpp b/src/celengine/stardb.cpp index fedacf08a7..41aa2c0c3d 100644 --- a/src/celengine/stardb.cpp +++ b/src/celengine/stardb.cpp @@ -55,14 +55,13 @@ StarDatabase::find(AstroCatalog::IndexNumber catalogNumber) const catalogNumber, [this](std::uint32_t idx, AstroCatalog::IndexNumber catNum) { - return stars.get()[idx].getIndex() < catNum; + return (*octreeRoot)[idx].getIndex() < catNum; }); if (it == catalogNumberIndex.end()) return nullptr; - // False positive in cppcheck: stars.get() does NOT return a void pointer - Star* star = stars.get() + *it; // cppcheck-suppress arithOperationsOnVoidPointer + Star* star = &(*octreeRoot)[*it]; return star->getIndex() == catalogNumber ? star : nullptr; @@ -225,11 +224,12 @@ StarDatabase::findVisibleStars(engine::StarHandler& starHandler, frustumPlanes[i] = Eigen::Hyperplane(planeNormals[i], position); } - octreeRoot->processVisibleObjects(starHandler, - position, - frustumPlanes.data(), - limitingMag, - STAR_OCTREE_ROOT_SIZE); + engine::StarOctreeVisibleObjectsProcessor processor(&starHandler, + position, + frustumPlanes, + limitingMag); + + octreeRoot->processDepthFirst(processor); } void @@ -237,10 +237,11 @@ StarDatabase::findCloseStars(engine::StarHandler& starHandler, const Eigen::Vector3f& position, float radius) const { - octreeRoot->processCloseObjects(starHandler, - position, - radius, - STAR_OCTREE_ROOT_SIZE); + engine::StarOctreeCloseObjectsProcessor processor(&starHandler, + position, + radius); + + octreeRoot->processDepthFirst(processor); } const StarNameDatabase* diff --git a/src/celengine/stardb.h b/src/celengine/stardb.h index f753b12747..cf0842dbdc 100644 --- a/src/celengine/stardb.h +++ b/src/celengine/stardb.h @@ -73,8 +73,6 @@ class StarDatabase private: Star* searchCrossIndex(StarCatalog, AstroCatalog::IndexNumber number) const; - std::uint32_t nStars{ 0 }; - std::unique_ptr stars; //NOSONAR std::unique_ptr namesDB; std::vector catalogNumberIndex; std::unique_ptr octreeRoot; @@ -85,11 +83,11 @@ class StarDatabase inline Star* StarDatabase::getStar(const std::uint32_t n) const { - return stars.get() + n; + return &(*octreeRoot)[n]; } inline std::uint32_t StarDatabase::size() const { - return nStars; + return octreeRoot->size(); } diff --git a/src/celengine/stardbbuilder.cpp b/src/celengine/stardbbuilder.cpp index 99ad09dce9..1c290449ac 100644 --- a/src/celengine/stardbbuilder.cpp +++ b/src/celengine/stardbbuilder.cpp @@ -68,10 +68,10 @@ const inline std::uint32_t DynamicStarOctree::SPLIT_THRESHOLD = 75; // of the node is allowed contain a star brighter than this value, making it // possible to determine quickly whether or not to cull subtrees. template<> -bool -DynamicStarOctree::exceedsBrightnessThreshold(const Star& star, float absMag) +float +DynamicStarOctree::getMagnitude(const Star& star) { - return star.getAbsoluteMagnitude() <= absMag; + return star.getAbsoluteMagnitude(); } template<> @@ -95,8 +95,8 @@ DynamicStarOctree::applyDecay(float excludingFactor) } template<> -DynamicStarOctree* -DynamicStarOctree::getChild(const Star& obj, const Eigen::Vector3f& cellCenterPos) const +unsigned int +DynamicStarOctree::getChildIndex(const Star& obj, const Eigen::Vector3f& cellCenterPos) { Eigen::Vector3f objPos = obj.getPosition(); @@ -105,7 +105,7 @@ DynamicStarOctree::getChild(const Star& obj, const Eigen::Vector3f& cellCenterPo child |= objPos.y() < cellCenterPos.y() ? 0U : OctreeYPos; child |= objPos.z() < cellCenterPos.z() ? 0U : OctreeZPos; - return (*m_children)[child].get(); + return child; } struct StarDatabaseBuilder::StcHeader @@ -1073,20 +1073,19 @@ StarDatabaseBuilder::buildOctree() StarDatabase::STAR_OCTREE_ROOT_SIZE * celestia::numbers::sqrt3_v); auto root = std::make_unique(Eigen::Vector3f(1000.0f, 1000.0f, 1000.0f), absMag); + + auto starCount = static_cast(unsortedStars.size()); for (Star& star : unsortedStars) root->insertObject(star, StarDatabase::STAR_OCTREE_ROOT_SIZE); GetLogger()->debug("Spatially sorting stars for improved locality of reference . . .\n"); - auto sortedStars = std::make_unique(unsortedStars.size()); //NOSONAR - Star* firstStar = sortedStars.get(); - starDB->octreeRoot = root->rebuildAndSort(firstStar); + starDB->octreeRoot = root->rebuildAndSort(StarDatabase::STAR_OCTREE_ROOT_SIZE, starCount); GetLogger()->debug("{} stars total\nOctree has {} nodes and {} stars.\n", - firstStar - sortedStars.get(), - 1 + starDB->octreeRoot->countChildren(), starDB->octreeRoot->countObjects()); + starCount, + starDB->octreeRoot->nodeCount(), + starDB->octreeRoot->size()); - starDB->nStars = static_cast(unsortedStars.size()); - starDB->stars = std::move(sortedStars); unsortedStars.clear(); } @@ -1098,15 +1097,17 @@ StarDatabaseBuilder::buildIndexes() GetLogger()->info("Building catalog number indexes . . .\n"); + auto nStars = starDB->octreeRoot->size(); + starDB->catalogNumberIndex.clear(); - starDB->catalogNumberIndex.reserve(starDB->nStars); - for (std::uint32_t i = 0; i < starDB->nStars; ++i) + starDB->catalogNumberIndex.reserve(nStars); + for (std::uint32_t i = 0; i < nStars; ++i) starDB->catalogNumberIndex.push_back(i); - const Star* stars = starDB->stars.get(); + const auto& octreeRoot = *starDB->octreeRoot; std::sort(starDB->catalogNumberIndex.begin(), starDB->catalogNumberIndex.end(), - [stars](std::uint32_t idx0, std::uint32_t idx1) + [&octreeRoot](std::uint32_t idx0, std::uint32_t idx1) { - return stars[idx0].getIndex() < stars[idx1].getIndex(); + return octreeRoot[idx0].getIndex() < octreeRoot[idx1].getIndex(); }); } diff --git a/src/celengine/staroctree.cpp b/src/celengine/staroctree.cpp index 8b50fe3d67..7ad4692dbd 100644 --- a/src/celengine/staroctree.cpp +++ b/src/celengine/staroctree.cpp @@ -14,6 +14,7 @@ #include #include +#include namespace celestia::engine { @@ -33,14 +34,23 @@ constexpr float MAX_STAR_ORBIT_RADIUS = 1.0f; } // end unnamed namespace -// total specialization of the StaticOctree template process*() methods for stars: -template<> -void -StarOctree::processVisibleObjects(StarHandler& processor, - const PointType& obsPosition, - const PlaneType* frustumPlanes, - float limitingFactor, - float scale) const +// The version of cppcheck used by Codacy doesn't seem to detect the field initializer + +StarOctreeVisibleObjectsProcessor::StarOctreeVisibleObjectsProcessor(StarHandler* starHandler, // cppcheck-suppress uninitMemberVar + const StarOctree::PointType& obsPosition, + util::array_view frustumPlanes, + float limitingFactor) : + m_starHandler(starHandler), + m_obsPosition(obsPosition), + m_frustumPlanes(frustumPlanes), + m_limitingFactor(limitingFactor) +{ +} + +bool +StarOctreeVisibleObjectsProcessor::checkNode(const StarOctree::PointType& center, + float size, + float factor) { // See if this node lies within the view frustum @@ -48,94 +58,70 @@ StarOctree::processVisibleObjects(StarHandler& processor, // planes that define the infinite view frustum. for (unsigned int i = 0; i < 5; ++i) { - const PlaneType& plane = frustumPlanes[i]; - float r = scale * plane.normal().cwiseAbs().sum(); - if (plane.signedDistance(m_cellCenterPos) < -r) - return; + const PlaneType& plane = m_frustumPlanes[i]; + float r = size * plane.normal().cwiseAbs().sum(); + if (plane.signedDistance(center) < -r) + return false; } // Compute the distance to node; this is equal to the distance to // the cellCenterPos of the node minus the boundingRadius of the node, scale * SQRT3. - float minDistance = (obsPosition - m_cellCenterPos).norm() - scale * numbers::sqrt3_v; + float minDistance = (m_obsPosition - center).norm() - size * numbers::sqrt3_v; - // Process the objects in this node - float dimmest = minDistance > 0 ? astro::appToAbsMag(limitingFactor, minDistance) : 1000; + float distanceModulus = astro::distanceModulus(minDistance); + if (minDistance > 0.0 && (factor + distanceModulus) > m_limitingFactor) + return false; - for (std::uint32_t i = 0; i < m_nObjects; ++i) - { - const Star& obj = m_firstObject[i]; - if (obj.getAbsoluteMagnitude() < dimmest) - { - float distance = (obsPosition - obj.getPosition()).norm(); - float appMag = obj.getApparentMagnitude(distance); - - if (appMag < limitingFactor || (distance < MAX_STAR_ORBIT_RADIUS && obj.getOrbit())) - processor.process(obj, distance, appMag); - } - } + // Dimmest absolute magnitude to process + m_dimmest = minDistance > 0 ? (m_limitingFactor - distanceModulus) : 1000; - // See if any of the objects in child nodes are potentially included - // that we need to recurse deeper. - if (m_children != nullptr && - (minDistance <= 0 || astro::absToAppMag(m_exclusionFactor, minDistance) <= limitingFactor)) - { - // Recurse into the child nodes - for (int i = 0; i < 8; ++i) - { - (*m_children)[i]->processVisibleObjects(processor, - obsPosition, - frustumPlanes, - limitingFactor, - scale * 0.5f); - } - } + return true; } -template<> void -StarOctree::processCloseObjects(StarHandler& processor, - const PointType& obsPosition, - float boundingRadius, - float scale) const +StarOctreeVisibleObjectsProcessor::process(const Star& obj) const { - // Compute the distance to node; this is equal to the distance to - // the cellCenterPos of the node minus the boundingRadius of the node, scale * SQRT3. - float nodeDistance = (obsPosition - m_cellCenterPos).norm() - scale * numbers::sqrt3_v; - - if (nodeDistance > boundingRadius) + if (obj.getAbsoluteMagnitude() > m_dimmest) return; - // At this point, we've determined that the cellCenterPos of the node is - // close enough that we must check individual objects for proximity. + float distance = (m_obsPosition - obj.getPosition()).norm(); + float appMag = obj.getApparentMagnitude(distance); - // Compute distance squared to avoid having to sqrt for distance - // comparison. - float radiusSquared = boundingRadius * boundingRadius; + if (appMag <= m_limitingFactor || (distance < MAX_STAR_ORBIT_RADIUS && obj.getOrbit())) + m_starHandler->process(obj, distance, appMag); +} - // Check all the objects in the node. - for (std::uint32_t i = 0; i < m_nObjects; ++i) - { - const Star& obj = m_firstObject[i]; - PointType offset = obsPosition - obj.getPosition(); - if (offset.squaredNorm() < radiusSquared) - { - float distance = offset.norm(); - float appMag = obj.getApparentMagnitude(distance); - - processor.process(obj, distance, appMag); - } - } +StarOctreeCloseObjectsProcessor::StarOctreeCloseObjectsProcessor(StarHandler* starHandler, + const StarOctree::PointType& obsPosition, + float boundingRadius) : + m_starHandler(starHandler), + m_obsPosition(obsPosition), + m_boundingRadius(boundingRadius), + m_radiusSquared(math::square(boundingRadius)) +{ +} + +bool +StarOctreeCloseObjectsProcessor::checkNode(const StarOctree::PointType& center, + float size, + float /* factor */) const +{ + // Compute the distance to node; this is equal to the distance to + // the cellCenterPos of the node minus the boundingRadius of the node, scale * SQRT3. + float nodeDistance = (m_obsPosition - center).norm() - size * numbers::sqrt3_v; + return nodeDistance <= m_boundingRadius; +} - // Recurse into the child nodes - if (m_children != nullptr) +void +StarOctreeCloseObjectsProcessor::process(const Star& obj) const +{ + StarOctree::PointType offset = m_obsPosition - obj.getPosition(); + if (offset.squaredNorm() < m_radiusSquared) { - for (int i = 0; i < 8; ++i) - { - (*m_children)[i]->processCloseObjects(processor, - obsPosition, - boundingRadius, - scale * 0.5f); - } + float distance = offset.norm(); + float appMag = obj.getApparentMagnitude(distance); + + m_starHandler->process(obj, distance, appMag); } } diff --git a/src/celengine/staroctree.h b/src/celengine/staroctree.h index 8a88c38a31..46a17473c2 100644 --- a/src/celengine/staroctree.h +++ b/src/celengine/staroctree.h @@ -14,6 +14,8 @@ #include +#include + #include #include @@ -23,17 +25,50 @@ namespace celestia::engine using StarOctree = StaticOctree; using StarHandler = OctreeProcessor; -template<> -void StarOctree::processVisibleObjects(StarHandler&, - const PointType&, - const PlaneType*, - float, - float) const; - -template<> -void StarOctree::processCloseObjects(StarHandler&, - const PointType&, - float, - float) const; +// This class searches the octree for objects that are likely to be visible +// to a viewer with the specified obsPosition and limitingFactor. The +// octreeProcessor is invoked for each potentially visible object --no object with +// a property greater than limitingFactor will be processed, but +// objects that are outside the view frustum may be. Frustum tests are performed +// only at the node level to optimize the octree traversal, so an exact test +// (if one is required) is the responsibility of the callback method. +class StarOctreeVisibleObjectsProcessor +{ +public: + using PlaneType = Eigen::Hyperplane; + + StarOctreeVisibleObjectsProcessor(StarHandler*, + const StarOctree::PointType&, + util::array_view, + float); + + bool checkNode(const StarOctree::PointType&, float, float); + void process(const Star&) const; + +private: + StarHandler* m_starHandler; + StarOctree::PointType m_obsPosition; + util::array_view m_frustumPlanes; + float m_limitingFactor; + + float m_dimmest{ 1000.0f }; +}; + +class StarOctreeCloseObjectsProcessor +{ +public: + StarOctreeCloseObjectsProcessor(StarHandler*, + const StarOctree::PointType&, + float); + + bool checkNode(const StarOctree::PointType&, float, float) const; + void process(const Star&) const; + +private: + StarHandler* m_starHandler; + StarOctree::PointType m_obsPosition; + float m_boundingRadius; + float m_radiusSquared; +}; } // end namespace celestia::engine From 31b30c375f6377aaecfc69ed3bd3ca276f20821a Mon Sep 17 00:00:00 2001 From: Andrew Tribick Date: Fri, 26 Jul 2024 23:33:06 +0200 Subject: [PATCH 12/15] Linearize DynamicOctree --- src/celengine/dsodbbuilder.cpp | 76 ++++--- src/celengine/octree.h | 6 +- src/celengine/octreebuilder.h | 372 +++++++++++++++++++------------- src/celengine/stardbbuilder.cpp | 116 +++++----- src/celutil/blockarray.h | 2 + 5 files changed, 322 insertions(+), 250 deletions(-) diff --git a/src/celengine/dsodbbuilder.cpp b/src/celengine/dsodbbuilder.cpp index 4ac541f7ac..ec16865e44 100644 --- a/src/celengine/dsodbbuilder.cpp +++ b/src/celengine/dsodbbuilder.cpp @@ -33,6 +33,7 @@ #include "globular.h" #include "hash.h" #include "nebula.h" +#include "octree.h" #include "octreebuilder.h" #include "opencluster.h" #include "parser.h" @@ -41,12 +42,12 @@ namespace astro = celestia::astro; namespace engine = celestia::engine; -using DynamicDSOOctree = engine::DynamicOctree, double>; - using celestia::util::GetLogger; -template<> -const inline std::uint32_t DynamicDSOOctree::SPLIT_THRESHOLD = 10; +namespace +{ + +constexpr engine::OctreeObjectIndex DSOOctreeSplitThreshold = 10; // The octree node into which a dso is placed is dependent on two properties: // its obsPosition and its luminosity--the fainter the dso, the deeper the node @@ -54,47 +55,40 @@ const inline std::uint32_t DynamicDSOOctree::SPLIT_THRESHOLD = 10; // of the node is allowed contain a dso brighter than this value, making it // possible to determine quickly whether or not to cull subtrees. -template<> -float -DynamicDSOOctree::getMagnitude(const std::unique_ptr& dso) //NOSONAR +struct DSOOctreeTraits { - return dso->getAbsoluteMagnitude(); -} + using ObjectType = std::unique_ptr; + using PrecisionType = double; -template<> -bool -DynamicDSOOctree::isStraddling(const Eigen::Vector3d& cellCenterPos, - const std::unique_ptr& dso) //NOSONAR + static Eigen::Vector3d getPosition(const ObjectType&); //NOSONAR + static double getRadius(const ObjectType&); //NOSONAR + static float getMagnitude(const ObjectType&); //NOSONAR + static float applyDecay(float); +}; + +inline Eigen::Vector3d +DSOOctreeTraits::getPosition(const ObjectType& obj) //NOSONAR { - //checks if this dso's radius straddles child nodes - float dsoRadius = dso->getBoundingSphereRadius(); - return (dso->getPosition() - cellCenterPos).cwiseAbs().minCoeff() < dsoRadius; + return obj->getPosition(); } -template<> -float -DynamicDSOOctree::applyDecay(float excludingFactor) +inline double +DSOOctreeTraits::getRadius(const ObjectType& obj) //NOSONAR { - return excludingFactor + 0.5f; + return obj->getBoundingSphereRadius(); } -template<> -unsigned int -DynamicDSOOctree::getChildIndex(const std::unique_ptr& obj, //NOSONAR - const PointType& cellCenterPos) +inline float +DSOOctreeTraits::getMagnitude(const ObjectType& obj) //NOSONAR { - PointType objPos = obj->getPosition(); - - unsigned int child = 0U; - child |= objPos.x() < cellCenterPos.x() ? 0U : OctreeXPos; - child |= objPos.y() < cellCenterPos.y() ? 0U : OctreeYPos; - child |= objPos.z() < cellCenterPos.z() ? 0U : OctreeZPos; - - return child; + return obj->getAbsoluteMagnitude(); } -namespace +inline float +DSOOctreeTraits::applyDecay(float factor) { + return factor + 0.5f; +} constexpr float DSO_OCTREE_MAGNITUDE = 8.0f; @@ -157,22 +151,24 @@ addName(NameDatabase* namesDB, AstroCatalog::IndexNumber objCatalogNumber, std:: } std::unique_ptr -buildOctree(std::vector>& DSOs) +buildOctree(std::vector>&& DSOs) { GetLogger()->debug("Sorting DSOs into octree . . .\n"); float absMag = astro::appToAbsMag(DSO_OCTREE_MAGNITUDE, DSO_OCTREE_ROOT_SIZE * celestia::numbers::sqrt3_v); auto dsoCount = static_cast(DSOs.size()); - auto root = std::make_unique(Eigen::Vector3d::Zero(), absMag); - for (auto& dso : DSOs) - root->insertObject(dso, DSO_OCTREE_ROOT_SIZE); + auto root = engine::makeDynamicOctree(std::move(DSOs), + Eigen::Vector3d::Zero(), + DSO_OCTREE_ROOT_SIZE, + absMag, + DSOOctreeSplitThreshold); GetLogger()->debug("Spatially sorting DSOs for improved locality of reference . . .\n"); // The spatial sorting part is useless for DSOs since we // are storing pointers to objects and not the objects themselves: - auto octreeRoot = root->rebuildAndSort(DSO_OCTREE_ROOT_SIZE, dsoCount); + auto octreeRoot = root->build(); GetLogger()->debug("{} DSOs total.\nOctree has {} nodes and {} DSOs.\n", dsoCount, @@ -280,11 +276,11 @@ DSODatabaseBuilder::load(std::istream& in, const fs::path& resourcePath) std::unique_ptr DSODatabaseBuilder::finish() { - auto octreeRoot = buildOctree(DSOs); + auto octreeRoot = buildOctree(std::move(DSOs)); auto catalogNumberIndex = buildCatalogNumberIndex(*octreeRoot); float avgAbsMag = calcAvgAbsMag(*octreeRoot); - GetLogger()->info(_("Loaded {} deep space objects\n"), DSOs.size()); + GetLogger()->info(_("Loaded {} deep space objects\n"), octreeRoot->size()); return std::make_unique(std::move(octreeRoot), std::move(namesDB), diff --git a/src/celengine/octree.h b/src/celengine/octree.h index 8a840f6ba0..a41e89260a 100644 --- a/src/celengine/octree.h +++ b/src/celengine/octree.h @@ -25,6 +25,7 @@ namespace celestia::engine using OctreeNodeIndex = std::uint32_t; using OctreeObjectIndex = std::uint32_t; +using OctreeDepthType = std::uint32_t; constexpr inline OctreeNodeIndex InvalidOctreeNode = UINT32_MAX; @@ -67,7 +68,7 @@ class OctreeProcessor OctreeProcessor() = default; }; -template +template class DynamicOctree; // The StaticOctree template arguments are: @@ -106,7 +107,8 @@ class StaticOctree std::vector m_nodes; std::vector m_objects; - friend class DynamicOctree; + template + friend class DynamicOctree; }; template diff --git a/src/celengine/octreebuilder.h b/src/celengine/octreebuilder.h index cca2a10adc..c8be4cd219 100644 --- a/src/celengine/octreebuilder.h +++ b/src/celengine/octreebuilder.h @@ -16,22 +16,52 @@ #include #include -#include +#include #include -#include +#include #include #include #include +#include #include "octree.h" namespace celestia::engine { -constexpr inline unsigned int OctreeXPos = 1; -constexpr inline unsigned int OctreeYPos = 2; -constexpr inline unsigned int OctreeZPos = 4; +namespace detail +{ + +template +struct DynamicOctreeNode +{ + using PointType = Eigen::Matrix; + using ChildrenType = std::array; + + explicit DynamicOctreeNode(const PointType&); + + bool isStraddling(const PointType&, PREC); + + PointType center; + std::vector objIndices; + std::unique_ptr children; +}; + +template +DynamicOctreeNode::DynamicOctreeNode(const PointType& _center) : + center(_center) +{ +} + +template +bool +DynamicOctreeNode::isStraddling(const PointType& pos, PREC radius) +{ + return radius > PREC(0) && (pos - center).cwiseAbs().minCoeff() < radius; +} + +} // end namespace celestia::engine::detail // The DynamicOctree is built first by inserting objects from a database or // catalog and is then 'compiled' into a StaticOctree. In the process of @@ -39,62 +69,97 @@ constexpr inline unsigned int OctreeZPos = 4; // with objects in the same octree node all placed adjacent to each other. // This spatial sorting of the objects dramatically improves the performance // of octree operations through much more coherent memory access. -template + +template class DynamicOctree { public: - using PointType = Eigen::Matrix; - - DynamicOctree(const PointType& cellCenterPos, - float exclusionFactor); + using StorageType = STORAGE; + using ObjectType = typename TRAITS::ObjectType; + using PrecisionType = typename TRAITS::PrecisionType; + using PointType = Eigen::Matrix; + using StaticOctreeType = StaticOctree; - void insertObject(OBJ&, PREC); - std::unique_ptr> rebuildAndSort(PREC, OctreeObjectIndex) const; + static_assert(std::is_same_v()[std::declval()]), ObjectType&>, "Incorrect element type in STORAGE"); + static_assert(std::is_same_v())), PointType>, "Incorrect return type for TRAITS::getPosition"); + static_assert(std::is_same_v())), PrecisionType>, "Incorrect return type for TRAITS::getRadius"); -private: - using ObjectList = std::vector; - using ChildrenType = std::array, 8>; - - static const unsigned int SPLIT_THRESHOLD; - - static float getMagnitude(const OBJ&); - static bool isStraddling(const PointType&, const OBJ&); - static float applyDecay(float); - static unsigned int getChildIndex(const OBJ&, const PointType&); + DynamicOctree(STORAGE&& objects, + const PointType& rootCenter, + PrecisionType rootSize, + float rootExclusionFactor, + OctreeObjectIndex splitThreshold); - void add(OBJ&); - void split(PREC); - DynamicOctree* getChild(const OBJ&, PREC); + DynamicOctree(const DynamicOctree&) = delete; + DynamicOctree& operator=(const DynamicOctree&) = delete; + DynamicOctree(DynamicOctree&&) noexcept = default; + DynamicOctree& operator=(DynamicOctree&&) noexcept = default; - OctreeNodeIndex countNodes() const; + std::unique_ptr build(); - std::unique_ptr m_children; - PointType m_cellCenterPos; - float m_exclusionFactor; - std::unique_ptr m_objects; +private: + using NodeType = detail::DynamicOctreeNode; + + void insertObject(OctreeObjectIndex); + void splitNode(NodeType&, OctreeDepthType); + OctreeNodeIndex getChild(NodeType&, OctreeDepthType, const PointType&); + void buildNode(StaticOctreeType&, + const NodeType&, + OctreeDepthType, + std::vector&); + + BlockArray m_nodes; + STORAGE m_objects; + std::vector m_sizes; + std::vector m_factors; + std::vector m_excluded; + OctreeObjectIndex m_splitThreshold; }; -// The SPLIT_THRESHOLD is the number of objects a node must contain before its -// children are generated. Increasing this number will decrease the number of -// octree nodes in the tree, which will use less memory but make culling less -// efficient. -template -DynamicOctree::DynamicOctree(const PointType& cellCenterPos, - float exclusionFactor): - m_cellCenterPos(cellCenterPos), - m_exclusionFactor(exclusionFactor) +template +DynamicOctree::DynamicOctree(STORAGE&& objects, + const PointType& rootCenter, + PrecisionType rootSize, + float rootExclusionFactor, + OctreeObjectIndex splitThreshold) : + m_objects(std::move(objects)), + m_splitThreshold(splitThreshold) { + m_nodes.emplace_back(rootCenter); + m_sizes.emplace_back(rootSize); + m_factors.emplace_back(rootExclusionFactor); + + for (OctreeObjectIndex i = 0, end = static_cast(m_objects.size()); i < end; ++i) + { + insertObject(i); + } } -template +template void -DynamicOctree::insertObject(OBJ& obj, PREC scale) +DynamicOctree::insertObject(OctreeObjectIndex idx) { - for (DynamicOctree* node = this;;) + const ObjectType& obj = m_objects[idx]; + PointType pos = TRAITS::getPosition(obj); + if ((pos - m_nodes[0].center).cwiseAbs().maxCoeff() > m_sizes[0]) + { + m_excluded.push_back(idx); + return; + } + + OctreeNodeIndex nodeIdx = 0; + OctreeDepthType depth = 0; + for (;;) { - if (getMagnitude(obj) <= node->m_exclusionFactor || isStraddling(node->m_cellCenterPos, obj)) + NodeType& node = m_nodes[nodeIdx]; + + if (m_factors.size() <= depth) + m_factors.push_back(TRAITS::applyDecay(m_factors.back())); + + // If the object can't be placed into this node's children, then put it here: + if (TRAITS::getMagnitude(obj) <= m_factors[depth] || node.isStraddling(pos, TRAITS::getRadius(obj))) { - node->add(obj); + node.objIndices.push_back(idx); return; } @@ -105,169 +170,180 @@ DynamicOctree::insertObject(OBJ& obj, PREC scale) // object into a child node. This is done in order // to avoid having the octree degenerate into one object // per node. - - scale *= PREC(0.5); - - if (node->m_children == nullptr) + if (node.children == nullptr) { - if (node->m_objects == nullptr || node->m_objects->size() < SPLIT_THRESHOLD) + if (node.objIndices.size() < m_splitThreshold) { - node->add(obj); + node.objIndices.push_back(idx); return; } - node->split(scale); + splitNode(node, depth); } - node = node->getChild(obj, scale); + ++depth; + nodeIdx = getChild(node, depth, pos); } } -template +template void -DynamicOctree::add(OBJ& obj) +DynamicOctree::splitNode(NodeType& node, OctreeDepthType depth) { - if (m_objects == nullptr) - m_objects = std::make_unique(); + assert(node.children == nullptr); + node.children = std::make_unique(); + node.children->fill(InvalidOctreeNode); - m_objects->push_back(&obj); -} - -template -void -DynamicOctree::split(PREC scale) -{ - assert(m_children == nullptr); - m_children = std::make_unique(); - - auto writeIt = m_objects->begin(); - auto endIt = m_objects->end(); + auto writeIt = node.objIndices.begin(); + auto endIt = node.objIndices.end(); for (auto readIt = writeIt; readIt != endIt; ++readIt) { - OBJ& obj = **readIt; - if (getMagnitude(obj) <= m_exclusionFactor || isStraddling(m_cellCenterPos, obj)) + const ObjectType& obj = m_objects[*readIt]; + PointType pos = TRAITS::getPosition(obj); + if (TRAITS::getMagnitude(obj) <= m_factors[depth] || node.isStraddling(pos, TRAITS::getRadius(obj))) { *writeIt = *readIt; ++writeIt; } else { - getChild(obj, scale)->add(obj); + NodeType& childNode = m_nodes[getChild(node, depth + 1, pos)]; + childNode.objIndices.push_back(*readIt); } } - m_objects->erase(writeIt, endIt); + if (writeIt == node.objIndices.begin()) + { + // No objects retained: release heap allocation + node.objIndices = std::vector(); + } + else + { + node.objIndices.erase(writeIt, endIt); + } } -template -DynamicOctree* -DynamicOctree::getChild(const OBJ& obj, PREC scale) +template +OctreeNodeIndex +DynamicOctree::getChild(NodeType& node, OctreeDepthType depth, const PointType& pos) { - assert(m_children != nullptr); + assert(node.children != nullptr); - unsigned int childIndex = getChildIndex(obj, m_cellCenterPos); - auto& result = (*m_children)[childIndex]; - if (result == nullptr) + auto childIndex = static_cast(pos.x() >= node.center.x()) | + (static_cast(pos.y() >= node.center.y()) << 1U) | + (static_cast(pos.z() >= node.center.z()) << 2U); + OctreeNodeIndex& result = (*node.children)[childIndex]; + if (result == InvalidOctreeNode) { - PointType centerPos = m_cellCenterPos - + PointType(((childIndex & OctreeXPos) != 0U) ? scale : -scale, - ((childIndex & OctreeYPos) != 0U) ? scale : -scale, - ((childIndex & OctreeZPos) != 0U) ? scale : -scale); - result = std::make_unique(centerPos, applyDecay(m_exclusionFactor)); + result = static_cast(m_nodes.size()); + if (m_sizes.size() <= depth) + m_sizes.push_back(m_sizes.back() * PrecisionType(0.5)); + PrecisionType scale = m_sizes[depth]; + PointType centerPos = node.center + + scale * PointType(static_cast(static_cast((childIndex & 1U) << 1U) - 1), + static_cast(static_cast(childIndex & 2U) - 1), + static_cast(static_cast((childIndex & 4U) >> 1U) - 1)); + m_nodes.emplace_back(centerPos); } - return result.get(); + return result; } -template -OctreeNodeIndex -DynamicOctree::countNodes() const +template +std::unique_ptr::StaticOctreeType> +DynamicOctree::build() { - OctreeNodeIndex result = 0; + auto staticOctree = std::make_unique(); + staticOctree->m_nodes.reserve(m_nodes.size()); + staticOctree->m_objects.reserve(m_objects.size()); - std::vector nodeStack; - nodeStack.push_back(this); + std::vector> nodeStack; + std::vector prevByDepth; + nodeStack.emplace_back(0, 0); while (!nodeStack.empty()) { - const DynamicOctree* node = nodeStack.back(); + auto [nodeIdx, depth] = nodeStack.back(); + const NodeType& node = m_nodes[nodeIdx]; + buildNode(*staticOctree, node, depth, prevByDepth); nodeStack.pop_back(); - ++result; - - if (node->m_children == nullptr) + if (node.children == nullptr) continue; - for (const auto& child : *node->m_children) + for (OctreeNodeIndex child : *node.children) { - if (child != nullptr) - nodeStack.push_back(child.get()); + if (child != InvalidOctreeNode) + nodeStack.emplace_back(child, depth + 1); } } - return result; + for (OctreeObjectIndex objIndex : m_excluded) + staticOctree->m_objects.emplace_back(std::move(m_objects[objIndex])); + + return staticOctree; } -template -std::unique_ptr> -DynamicOctree::rebuildAndSort(PREC scale, OctreeObjectIndex objectCount) const +template +void +DynamicOctree::buildNode(StaticOctreeType& staticOctree, + const NodeType& node, + OctreeDepthType depth, + std::vector& prevByDepth) { - auto staticOctree = std::make_unique>(); - staticOctree->m_nodes.reserve(countNodes()); - staticOctree->m_objects.reserve(objectCount); - - std::vector> nodeStack; - std::vector nodeIndexStack; - nodeStack.emplace_back(this, scale, 0); - - while (!nodeStack.empty()) + // any nodes in the node stack with a depth greater or equal to this one + // will have this node as the node to jump to when skipping the subtree + auto staticNodeIdx = static_cast(staticOctree.m_nodes.size()); + while (prevByDepth.size() > depth) { - auto [node, nodeScale, depth] = nodeStack.back(); - nodeStack.pop_back(); + staticOctree.m_nodes[prevByDepth.back()].right = staticNodeIdx; + prevByDepth.pop_back(); + } - // any nodes in the node stack with a depth greater or equal to this one - // will have this node as the node to jump to when skipping the subtree - auto nodeIndex = static_cast(staticOctree->m_nodes.size()); - while (nodeIndexStack.size() > depth) - { - staticOctree->m_nodes[nodeIndexStack.back()].right = nodeIndex; - nodeIndexStack.pop_back(); - } + prevByDepth.push_back(staticNodeIdx); - nodeIndexStack.push_back(nodeIndex); + auto& staticNode = staticOctree.m_nodes.emplace_back(node.center, m_sizes[depth]); - auto& staticNode = staticOctree->m_nodes.emplace_back(node->m_cellCenterPos, nodeScale); - if (node->m_objects != nullptr && !node->m_objects->empty()) - { - staticNode.first = static_cast(staticOctree->m_objects.size()); - staticNode.last = staticNode.first + static_cast(node->m_objects->size()); + if (node.objIndices.empty()) + return; - for (OBJ* obj : *node->m_objects) - { - staticNode.brightFactor = std::min(staticNode.brightFactor, getMagnitude(*obj)); - staticOctree->m_objects.emplace_back(std::move(*obj)); - } + staticNode.first = static_cast(staticOctree.m_objects.size()); + staticNode.last = staticNode.first + static_cast(node.objIndices.size()); - // update parent node brightness factors, necessary in case they have no - // stars - for (OctreeNodeIndex parentDepth = 0; parentDepth < depth; ++parentDepth) - { - auto& parentNode = staticOctree->m_nodes[nodeIndexStack[parentDepth]]; - parentNode.brightFactor = std::min(parentNode.brightFactor, staticNode.brightFactor); - } - } + // move the objects into the sorted octree, keep track of brightest + for (OctreeObjectIndex objIdx : node.objIndices) + { + ObjectType& obj = m_objects[objIdx]; + staticNode.brightFactor = std::min(staticNode.brightFactor, TRAITS::getMagnitude(obj)); + staticOctree.m_objects.emplace_back(std::move(obj)); + } - if (node->m_children == nullptr) - continue; + // update parent node brightness factors, in case node was empty or + // (less likely) filled with objects straddling the center + for (OctreeNodeIndex parentDepth = depth; parentDepth-- > 0;) + { + auto& parentNode = staticOctree.m_nodes[prevByDepth[parentDepth]]; + if (parentNode.brightFactor <= staticNode.brightFactor) + break; - for (const auto& child : *node->m_children) - { - if (child != nullptr) - nodeStack.emplace_back(child.get(), nodeScale * PREC(0.5), depth + 1); - } + parentNode.brightFactor = staticNode.brightFactor; } +} - return staticOctree; +template +inline std::unique_ptr> +makeDynamicOctree(STORAGE&& objects, + const Eigen::Matrix& rootCenter, + typename TRAITS::PrecisionType rootSize, + float rootExclusionFactor, + OctreeObjectIndex splitThreshold) +{ + static_assert(!std::is_reference_v, "makeDynamicOctree must be called with rvalue for objects parameter"); + return std::make_unique>(std::forward(objects), + rootCenter, + rootSize, + rootExclusionFactor, + splitThreshold); } } // end namespace celestia::engine diff --git a/src/celengine/stardbbuilder.cpp b/src/celengine/stardbbuilder.cpp index 1c290449ac..22ea2d1d7c 100644 --- a/src/celengine/stardbbuilder.cpp +++ b/src/celengine/stardbbuilder.cpp @@ -52,62 +52,8 @@ namespace ephem = celestia::ephem; namespace math = celestia::math; namespace util = celestia::util; -using DynamicStarOctree = engine::DynamicOctree; - using util::GetLogger; -// In testing, changing SPLIT_THRESHOLD from 100 to 50 nearly -// doubled the number of nodes in the tree, but provided only between a -// 0 to 5 percent frame rate improvement. -template<> -const inline std::uint32_t DynamicStarOctree::SPLIT_THRESHOLD = 75; - -// The octree node into which a star is placed is dependent on two properties: -// its obsPosition and its luminosity--the fainter the star, the deeper the node -// in which it will reside. Each node stores an absolute magnitude; no child -// of the node is allowed contain a star brighter than this value, making it -// possible to determine quickly whether or not to cull subtrees. -template<> -float -DynamicStarOctree::getMagnitude(const Star& star) -{ - return star.getAbsoluteMagnitude(); -} - -template<> -bool -DynamicStarOctree::isStraddling(const Eigen::Vector3f& cellCenterPos, const Star& star) -{ - //checks if this star's orbit straddles child nodes - float orbitalRadius = star.getOrbitalRadius(); - if (orbitalRadius == 0.0f) - return false; - - Eigen::Vector3f starPos = star.getPosition(); - return (starPos - cellCenterPos).cwiseAbs().minCoeff() < orbitalRadius; -} - -template<> -float -DynamicStarOctree::applyDecay(float excludingFactor) -{ - return astro::lumToAbsMag(astro::absMagToLum(excludingFactor) / 4.0f); -} - -template<> -unsigned int -DynamicStarOctree::getChildIndex(const Star& obj, const Eigen::Vector3f& cellCenterPos) -{ - Eigen::Vector3f objPos = obj.getPosition(); - - unsigned int child = 0U; - child |= objPos.x() < cellCenterPos.x() ? 0U : OctreeXPos; - child |= objPos.y() < cellCenterPos.y() ? 0U : OctreeYPos; - child |= objPos.z() < cellCenterPos.z() ? 0U : OctreeZPos; - - return child; -} - struct StarDatabaseBuilder::StcHeader { explicit StcHeader(const fs::path&); @@ -144,6 +90,54 @@ struct fmt::formatter : formatterdebug("Sorting stars into octree . . .\n"); + auto starCount = static_cast(unsortedStars.size()); + float absMag = astro::appToAbsMag(STAR_OCTREE_MAGNITUDE, StarDatabase::STAR_OCTREE_ROOT_SIZE * celestia::numbers::sqrt3_v); - auto root = std::make_unique(Eigen::Vector3f(1000.0f, 1000.0f, 1000.0f), - absMag); - auto starCount = static_cast(unsortedStars.size()); - for (Star& star : unsortedStars) - root->insertObject(star, StarDatabase::STAR_OCTREE_ROOT_SIZE); + auto root = engine::makeDynamicOctree(std::move(unsortedStars), + Eigen::Vector3f(1000.0f, 1000.0f, 1000.0f), + StarDatabase::STAR_OCTREE_ROOT_SIZE, + absMag, + StarOctreeSplitThreshold); GetLogger()->debug("Spatially sorting stars for improved locality of reference . . .\n"); - starDB->octreeRoot = root->rebuildAndSort(StarDatabase::STAR_OCTREE_ROOT_SIZE, starCount); + starDB->octreeRoot = root->build(); GetLogger()->debug("{} stars total\nOctree has {} nodes and {} stars.\n", starCount, diff --git a/src/celutil/blockarray.h b/src/celutil/blockarray.h index 5471c4122f..a09057e9fb 100644 --- a/src/celutil/blockarray.h +++ b/src/celutil/blockarray.h @@ -39,6 +39,8 @@ class BlockArray using difference_type = std::ptrdiff_t; using size_type = std::size_t; + static_assert(BLOCKSIZE > 0, "BLOCKSIZE must be greater than zero"); + class const_iterator; class iterator From 927c4f9c9c0454f46f7898a8f92e4744d605ca70 Mon Sep 17 00:00:00 2001 From: Andrew Tribick Date: Tue, 6 Aug 2024 18:42:45 +0200 Subject: [PATCH 13/15] Support fmt 11 - Bump minimum supported fmt version to 8.0.0 (supply 8.1.1 in thirdparty/fmt) - Use const format functions - Rewrite path formatter to avoid copying on non-Windows systems - Update vcpkg version to latest --- .github/workflows/ci.yml | 2 +- CMakeLists.txt | 2 +- src/cel3ds/3dsread.cpp | 7 ++++--- src/celengine/stardbbuilder.cpp | 2 +- src/celutil/formatnum.cpp | 4 ++-- src/celutil/formatnum.h | 5 ++--- src/celutil/logger.h | 22 ++++++++++++++++++---- thirdparty/fmt | 2 +- 8 files changed, 30 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4e027d2ad1..c93a1300a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: - name: Update vcpkg shell: pwsh run: | - $vcpkgCommit = 'cacf5994341f27e9a14a7b8724b0634b138ecb30' + $vcpkgCommit = 'a2367ceec5f092d8777606ca110426cadd7ed7db' pushd "$env:VCPKG_INSTALLATION_ROOT" git cat-file -e "${vcpkgCommit}^{commit}" 2> $null if (!$?) { diff --git a/CMakeLists.txt b/CMakeLists.txt index 55e7bbb2ca..a2107ca137 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -272,7 +272,7 @@ find_package(Eigen3 3.3 REQUIRED NO_MODULE) # -DEigen3_DIR=... message(STATUS "Found Eigen3 ${EIGEN3_VERSION_STRING}") link_libraries(Eigen3::Eigen) -find_package(fmt 6.1.0 CONFIG QUIET) +find_package(fmt 8.0.0 CONFIG QUIET) if(NOT fmt_FOUND) message(STATUS "Using fmt submodule") add_subdirectory("${CMAKE_SOURCE_DIR}/thirdparty/fmt") diff --git a/src/cel3ds/3dsread.cpp b/src/cel3ds/3dsread.cpp index 78547b7674..6bddbc05e1 100644 --- a/src/cel3ds/3dsread.cpp +++ b/src/cel3ds/3dsread.cpp @@ -87,13 +87,14 @@ constexpr auto chunkHeaderSize = static_cast(sizeof(M3DChunkType) template<> struct fmt::formatter { - constexpr auto parse(const format_parse_context& ctx) const -> decltype(ctx.begin()) { + constexpr auto parse(const format_parse_context& ctx) const + { // we should validate the format here but exceptions are disabled return ctx.begin(); } - template - auto format(const M3DChunkType& chunkType, FormatContext& ctx) -> decltype(ctx.out()) { + auto format(const M3DChunkType& chunkType, format_context& ctx) const + { return format_to(ctx.out(), "{:04x}", static_cast(chunkType)); } }; diff --git a/src/celengine/stardbbuilder.cpp b/src/celengine/stardbbuilder.cpp index 22ea2d1d7c..2759b32704 100644 --- a/src/celengine/stardbbuilder.cpp +++ b/src/celengine/stardbbuilder.cpp @@ -75,7 +75,7 @@ StarDatabaseBuilder::StcHeader::StcHeader(const fs::path& _path) : template<> struct fmt::formatter : formatter { - format_context::iterator format(const StarDatabaseBuilder::StcHeader& header, format_context& ctx) + auto format(const StarDatabaseBuilder::StcHeader& header, format_context& ctx) const { fmt::basic_memory_buffer data; fmt::format_to(std::back_inserter(data), "line {}", header.lineNumber); diff --git a/src/celutil/formatnum.cpp b/src/celutil/formatnum.cpp index 13367db3ea..bb86d08413 100644 --- a/src/celutil/formatnum.cpp +++ b/src/celutil/formatnum.cpp @@ -52,7 +52,7 @@ ExtendedSubstring::ExtendedSubstring(std::string_view _source, template<> struct fmt::formatter { - constexpr format_parse_context::iterator parse(const format_parse_context& ctx) const + constexpr auto parse(const format_parse_context& ctx) const { auto it = ctx.begin(); if (it != ctx.end() && *it != '}') @@ -68,7 +68,7 @@ struct fmt::formatter return it; } - format_context::iterator format(const ExtendedSubstring& value, format_context& ctx) const + auto format(const ExtendedSubstring& value, format_context& ctx) const { if (value.start >= value.source.size()) return format_to(ctx.out(), "{:0<{}}", ""sv, value.length); diff --git a/src/celutil/formatnum.h b/src/celutil/formatnum.h index c00c94e665..5b7fba9ba4 100644 --- a/src/celutil/formatnum.h +++ b/src/celutil/formatnum.h @@ -116,7 +116,7 @@ FormattedFloat::format(fmt::format_context& ctx) const template struct fmt::formatter> { - constexpr format_parse_context::iterator parse(const format_parse_context& ctx) const + constexpr auto parse(const format_parse_context& ctx) const { auto it = ctx.begin(); if (it != ctx.end() && *it != '}') @@ -132,8 +132,7 @@ struct fmt::formatter> return it; } - format_context::iterator format(const celestia::util::FormattedFloat& f, - format_context& ctx) const + auto format(const celestia::util::FormattedFloat& f, format_context& ctx) const { return f.format(ctx); } diff --git a/src/celutil/logger.h b/src/celutil/logger.h index ea5ba85ccf..16967f3d24 100644 --- a/src/celutil/logger.h +++ b/src/celutil/logger.h @@ -12,19 +12,33 @@ #pragma once #include +#include + +#ifdef _WIN32 #include +#endif #include #include template <> -struct fmt::formatter : formatter +struct fmt::formatter : formatter { - template - auto format(const fs::path &path, FormatContext &ctx) + auto format(const fs::path &path, format_context &ctx) const { - return formatter::format(path.string(), ctx); +#ifdef _WIN32 + auto u8path = path.u8string(); +#if __cpp_char8_t >= 201811L + // Future-proof against C++20 defining the result of the above as std::u8string + std::string_view sv(reinterpret_cast(u8path.data()), u8path.size()); + return formatter::format(sv, ctx); +#else + return formatter::format(u8path, ctx); +#endif +#else + return formatter::format(path.native(), ctx); +#endif } }; diff --git a/thirdparty/fmt b/thirdparty/fmt index 4ca6821e8f..b6f4ceaed0 160000 --- a/thirdparty/fmt +++ b/thirdparty/fmt @@ -1 +1 @@ -Subproject commit 4ca6821e8f26c96f815064f77dff0a74f562bffb +Subproject commit b6f4ceaed0a0a24ccf575fab6c56dd50ccf6f1a9 From 95aa3a9f42daa086f6243040e1d85edef847a4c3 Mon Sep 17 00:00:00 2001 From: Andrew Tribick Date: Fri, 9 Aug 2024 18:10:00 +0200 Subject: [PATCH 14/15] Fix Modify when Dec is not specified --- src/celengine/stardbbuilder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/celengine/stardbbuilder.cpp b/src/celengine/stardbbuilder.cpp index 2759b32704..35bf003e27 100644 --- a/src/celengine/stardbbuilder.cpp +++ b/src/celengine/stardbbuilder.cpp @@ -375,7 +375,7 @@ checkPolarCoordinates(const StarDatabaseBuilder::StcHeader& header, // Disable Sonar on the below: suggests using value-or which would eagerly-evaluate the replacement value double distance = distanceValue.has_value() ? *distanceValue : v.norm(); //NOSONAR double ra = raValue.has_value() ? *raValue : (math::radToDeg(std::atan2(v.y(), v.x())) / astro::DEG_PER_HRA); //NOSONAR - double dec = decValue.has_value() ? *decValue : math::radToDeg(std::asin(std::clamp(v.z(), -1.0, 1.0))); //NOSONAR + double dec = decValue.has_value() ? *decValue : math::radToDeg(std::asin(std::clamp(v.z() / v.norm(), -1.0, 1.0))); //NOSONAR position = astro::equatorialToCelestialCart(ra, dec, distance).cast(); return true; From 593afbcab6c55300235a69ed5912bf713cd0e907 Mon Sep 17 00:00:00 2001 From: ajtribick Date: Sun, 11 Aug 2024 13:37:14 +0200 Subject: [PATCH 15/15] Fix polar coordinate modification --- src/celengine/stardbbuilder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/celengine/stardbbuilder.cpp b/src/celengine/stardbbuilder.cpp index 35bf003e27..c4b46493f1 100644 --- a/src/celengine/stardbbuilder.cpp +++ b/src/celengine/stardbbuilder.cpp @@ -371,7 +371,7 @@ checkPolarCoordinates(const StarDatabaseBuilder::StcHeader& header, // Convert from Celestia's coordinate system const Eigen::Vector3f& p = star->getPosition(); - Eigen::Vector3d v = math::XRotation(math::degToRad(astro::J2000Obliquity)) * Eigen::Vector3f(p.x(), -p.z(), p.y()).cast(); + Eigen::Vector3d v = math::XRotation(astro::J2000Obliquity) * Eigen::Vector3f(p.x(), -p.z(), p.y()).cast(); // Disable Sonar on the below: suggests using value-or which would eagerly-evaluate the replacement value double distance = distanceValue.has_value() ? *distanceValue : v.norm(); //NOSONAR double ra = raValue.has_value() ? *raValue : (math::radToDeg(std::atan2(v.y(), v.x())) / astro::DEG_PER_HRA); //NOSONAR