From 6a1b6b7458e40d09a72651933c5bc82ad25a37d8 Mon Sep 17 00:00:00 2001 From: Andrew Tribick Date: Sun, 9 Jun 2024 21:47:29 +0200 Subject: [PATCH] Refactor the stc parser - Fix handling of modification of individual RA/Dec coordinates - Detect cyclic OrbitBarycenter references - Detect missing magnitude, spectral type when converting Barycenter back to Star - Fix line number handling in Tokenizer - Allow Modify to remove OrbitBarycenter if RA/Dec/Position are specified --- src/celengine/body.cpp | 6 +- src/celengine/observer.cpp | 20 +- src/celengine/star.cpp | 311 +++--- src/celengine/star.h | 170 ++-- src/celengine/starbrowser.cpp | 2 +- src/celengine/stardbbuilder.cpp | 1121 +++++++++++----------- src/celengine/stardbbuilder.h | 58 +- src/celengine/stellarclass.h | 57 +- src/celestia/hud.cpp | 4 +- src/celestia/qt/qtsolarsystembrowser.cpp | 55 +- src/celutil/tokenizer.cpp | 1 + test/unit/category_test.cpp | 2 +- 12 files changed, 886 insertions(+), 921 deletions(-) diff --git a/src/celengine/body.cpp b/src/celengine/body.cpp index dd7656f1037..e425be1b617 100644 --- a/src/celengine/body.cpp +++ b/src/celengine/body.cpp @@ -397,13 +397,13 @@ float Body::getTemperature(double time) const } else // the sun is a barycenter { - const auto* orbitingStars = sun->getOrbitingStars(); - if (orbitingStars == nullptr || orbitingStars->empty()) + auto orbitingStars = sun->getOrbitingStars(); + if (orbitingStars.empty()) return 0.0f; const UniversalCoord bodyPos = getPosition(time); float flux = 0.0f; - for (const auto *s : *orbitingStars) + for (const auto *s : orbitingStars) { float distFromSun = (float)s->getPosition(time).distanceFromKm(bodyPos); float lum = math::square(s->getRadius()) * pow(s->getTemperature(), 4.0f); diff --git a/src/celengine/observer.cpp b/src/celengine/observer.cpp index cb3bff72764..e9ed553cd03 100644 --- a/src/celengine/observer.cpp +++ b/src/celengine/observer.cpp @@ -1117,17 +1117,15 @@ static double getPreferredDistance(const Selection& selection) { // Handle star system barycenters specially, using the same approach as // for reference points in solar systems. - const std::vector* orbitingStars = selection.star()->getOrbitingStars(); - double maxOrbitRadius = orbitingStars == nullptr - ? 0.0 - : std::accumulate(orbitingStars->begin(), orbitingStars->end(), 0.0, - [](double r, const Star* s) - { - const celestia::ephem::Orbit* orbit = s->getOrbit(); - return orbit == nullptr - ? r - : std::max(r, orbit->getBoundingRadius()); - }); + auto orbitingStars = selection.star()->getOrbitingStars(); + double maxOrbitRadius = std::accumulate(orbitingStars.begin(), orbitingStars.end(), 0.0, + [](double r, const Star* s) + { + const celestia::ephem::Orbit* orbit = s->getOrbit(); + return orbit == nullptr + ? r + : std::max(r, orbit->getBoundingRadius()); + }); return maxOrbitRadius == 0.0 ? astro::AUtoKilometers(1.0) : maxOrbitRadius * 5.0; } diff --git a/src/celengine/star.cpp b/src/celengine/star.cpp index 137f2026444..9f81ddfb6e1 100644 --- a/src/celengine/star.cpp +++ b/src/celengine/star.cpp @@ -29,6 +29,7 @@ using namespace std::string_view_literals; namespace astro = celestia::astro; namespace ephem = celestia::ephem; namespace math = celestia::math; +namespace util = celestia::util; namespace { @@ -563,26 +564,15 @@ getPeriodAndBmagCorrection(StellarClass::SpectralClass specClass, } } -boost::intrusive_ptr -CreateStandardStarType(std::string_view specTypeName, - float _temperature, - float _rotationPeriod) - +inline void +unshare(boost::intrusive_ptr& details) { - auto details = StarDetails::create(); - - details->setTemperature(_temperature); - details->setSpectralType(specTypeName); - - details->setRotationModel(std::make_shared(_rotationPeriod, - 0.0f, - astro::J2000, - 0.0f, - 0.0f)); - - return details; + if (details->shared()) + details = details->clone(); } +} // end unnamed namespace + class StarDetailsManager { public: @@ -626,6 +616,9 @@ class StarDetailsManager (scIndex * static_cast(StellarClass::SubclassCount)); } + static boost::intrusive_ptr createStandardStarType(std::string_view specTypeName, + float _temperature, + float _rotationPeriod); boost::intrusive_ptr createNormalStarDetails(StellarClass::SpectralClass, unsigned int, StellarClass::LuminosityClass); @@ -650,14 +643,12 @@ class StarDetailsManager boost::intrusive_ptr barycenterDetails{ }; }; - StarDetailsManager::StarDetailsManager() { std::fill(normalStarDetails.begin(), normalStarDetails.end(), nullptr); std::fill(whiteDwarfDetails.begin(), whiteDwarfDetails.end(), nullptr); } - StarDetailsManager& StarDetailsManager::getManager() { @@ -730,6 +721,26 @@ StarDetailsManager::getBarycenterDetails() return barycenterDetails; } +boost::intrusive_ptr +StarDetailsManager::createStandardStarType(std::string_view specTypeName, + float _temperature, + float _rotationPeriod) +{ + boost::intrusive_ptr details(new StarDetails, false); + details->temperature = _temperature; + + auto length = std::min(details->spectralType.size() - 1, specTypeName.size()); + std::memcpy(details->spectralType.data(), specTypeName.data(), length); + details->spectralType[length] = '\0'; + + details->rotationModel = std::make_shared(_rotationPeriod, + 0.0f, + astro::J2000, + 0.0f, + 0.0f); + return details; +} + boost::intrusive_ptr StarDetailsManager::createNormalStarDetails(StellarClass::SpectralClass specClass, unsigned int subclass, @@ -765,11 +776,11 @@ StarDetailsManager::createNormalStarDetails(StellarClass::SpectralClass specClas float period; getPeriodAndBmagCorrection(specClass, subclass, lumIndex, period, bmagCorrection); - auto details = CreateStandardStarType(name, temp, period); - details->setBolometricCorrection(bmagCorrection); + auto details = createStandardStarType(name, temp, period); + details->bolometricCorrection = bmagCorrection; const MultiResTexture& starTex = starTextures.starTex[specClass]; - details->setTexture(starTex.isValid() ? starTex : starTextures.defaultTex); + details->texture = starTex.isValid() ? starTex : starTextures.defaultTex; return details; } @@ -800,10 +811,10 @@ StarDetailsManager::createWhiteDwarfDetails(std::size_t scIndex, // rough, as white rotation rates vary a lot. float period = 1.0f / 48.0f; - auto details = CreateStandardStarType(name, temp, period); + auto details = createStandardStarType(name, temp, period); const MultiResTexture& starTex = starTextures.starTex[StellarClass::Spectral_D]; - details->setTexture(starTex.isValid() ? starTex : starTextures.defaultTex); - details->setBolometricCorrection(bmagCorrection); + details->bolometricCorrection = bmagCorrection; + details->texture = starTex.isValid() ? starTex : starTextures.defaultTex; return details; } @@ -812,12 +823,12 @@ StarDetailsManager::createNeutronStarDetails() { // The default neutron star has a rotation period of one second, // surface temperature of five million K. - auto details = CreateStandardStarType("Q"sv, 5000000.0f, + auto details = createStandardStarType("Q"sv, 5000000.0f, 1.0f / 86400.0f); - details->setRadius(10.0f); - details->addKnowledge(StarDetails::KnowRadius); + details->radius = 10.0f; + details->knowledge = StarDetails::Knowledge::KnowRadius; const MultiResTexture& starTex = starTextures.neutronStarTex; - details->setTexture(starTex.isValid() ? starTex : starTextures.defaultTex); + details->texture = starTex.isValid() ? starTex : starTextures.defaultTex; return details; } @@ -828,25 +839,22 @@ StarDetailsManager::createBlackHoleDetails() // black hole. // The temperature is computed from the equation: // T=h_bar c^3/(8 pi G k m) - auto details = CreateStandardStarType("X"sv, 6.15e-8f, 1.0f / 86400.0f); - details->setRadius(2.9f); - details->addKnowledge(StarDetails::KnowRadius); + auto details = createStandardStarType("X"sv, 6.15e-8f, 1.0f / 86400.0f); + details->radius = 2.9f; + details->knowledge = StarDetails::Knowledge::KnowRadius; return details; } - inline boost::intrusive_ptr StarDetailsManager::createBarycenterDetails() { - auto details = CreateStandardStarType("Bary"sv, 1.0f, 1.0f); - details->setRadius(0.001f); - details->addKnowledge(StarDetails::KnowRadius); - details->setVisibility(false); + auto details = createStandardStarType("Bary"sv, 1.0f, 1.0f); + details->radius = 0.001f; + details->knowledge = StarDetails::Knowledge::KnowRadius; + details->visible = false; return details; } -} // end unnamed namespace - boost::intrusive_ptr StarDetails::GetStarDetails(const StellarClass& sc) { @@ -889,17 +897,11 @@ StarDetails::StarDetails() spectralType[0] = '\0'; } -boost::intrusive_ptr -StarDetails::create() -{ - return boost::intrusive_ptr(new StarDetails, false); -} - boost::intrusive_ptr StarDetails::clone() const { assert(isShared); - auto newDetails = create(); + boost::intrusive_ptr newDetails(new StarDetails, false); newDetails->radius = radius; newDetails->temperature = temperature; newDetails->bolometricCorrection = bolometricCorrection; @@ -918,6 +920,33 @@ StarDetails::clone() const return newDetails; } +void +StarDetails::mergeFromStandard(const StarDetails* other) +{ + assert(!isShared); + spectralType = other->spectralType; + temperature = other->temperature; + bolometricCorrection = other->bolometricCorrection; + if (other->isBarycenter()) + { + // Use default values when replacement object is a barycenter + texture = other->texture; + rotationModel = other->rotationModel; + geometry = other->geometry; + radius = other->radius; + semiAxes = other->semiAxes; + knowledge = other->knowledge; + } + else + { + if (!util::is_set(knowledge, Knowledge::KnowTexture)) + texture = other->texture; + if (!util::is_set(knowledge, Knowledge::KnowRotation)) + rotationModel = other->rotationModel; + } + visible = other->visible; +} + /*! Return the InfoURL. If the InfoURL has not been set, this method * returns an empty string. */ @@ -928,74 +957,56 @@ StarDetails::getInfoURL() const } void -StarDetails::setRadius(float _radius) -{ - radius = _radius; -} - -void -StarDetails::setTemperature(float _temperature) +StarDetails::setRadius(boost::intrusive_ptr& details, float _radius) { - temperature = _temperature; + unshare(details); + details->radius = _radius; + details->knowledge |= Knowledge::KnowRadius; } void -StarDetails::setSpectralType(std::string_view s) +StarDetails::setTemperature(boost::intrusive_ptr& details, float _temperature) { - auto length = std::min(spectralType.size() - 1, s.size()); - std::memcpy(spectralType.data(), s.data(), length); - spectralType[length] = '\0'; + unshare(details); + details->temperature = _temperature; } void -StarDetails::setKnowledge(std::uint32_t _knowledge) +StarDetails::setBolometricCorrection(boost::intrusive_ptr& details, float correction) { - knowledge = _knowledge; + unshare(details); + details->bolometricCorrection = correction; } void -StarDetails::addKnowledge(std::uint32_t _knowledge) +StarDetails::setTexture(boost::intrusive_ptr& details, const MultiResTexture& tex) { - knowledge |= _knowledge; + unshare(details); + details->texture = tex; + details->knowledge |= Knowledge::KnowTexture; } void -StarDetails::setBolometricCorrection(float correction) +StarDetails::setGeometry(boost::intrusive_ptr& details, ResourceHandle rh) { - bolometricCorrection = correction; + unshare(details); + details->geometry = rh; } void -StarDetails::setTexture(const MultiResTexture& tex) +StarDetails::setOrbit(boost::intrusive_ptr& details, const std::shared_ptr& o) { - texture = tex; -} - -void -StarDetails::setGeometry(ResourceHandle rh) -{ - geometry = rh; -} - -void -StarDetails::setOrbit(const std::shared_ptr& o) -{ - orbit = o; - computeOrbitalRadius(); -} - -void -StarDetails::setOrbitBarycenter(Star* bc) -{ - barycenter = bc; - computeOrbitalRadius(); + unshare(details); + details->orbit = o; + details->computeOrbitalRadius(); } void -StarDetails::setOrbitalRadius(float r) +StarDetails::setOrbitBarycenter(boost::intrusive_ptr& details, Star* bc) { - if (orbit != nullptr) - orbitalRadius = r; + unshare(details); + details->barycenter = bc; + details->computeOrbitalRadius(); } void @@ -1014,50 +1025,44 @@ StarDetails::computeOrbitalRadius() } void -StarDetails::setVisibility(bool b) +StarDetails::setVisibility(boost::intrusive_ptr& details, bool b) { - visible = b; + unshare(details); + details->visible = b; } void -StarDetails::setRotationModel(const std::shared_ptr& rm) +StarDetails::setRotationModel(boost::intrusive_ptr& details, + const std::shared_ptr& rm) { - rotationModel = rm; + unshare(details); + details->rotationModel = rm; + details->knowledge |= Knowledge::KnowRotation; } /*! Set the InfoURL for this star. */ void -StarDetails::setInfoURL(std::string_view _infoURL) +StarDetails::setInfoURL(boost::intrusive_ptr& details, std::string_view _infoURL) { - infoURL = _infoURL; + unshare(details); + details->infoURL = _infoURL; } -// Return the radius of the star in kilometers -float -Star::getRadius() const +void +StarDetails::setEllipsoidSemiAxes(boost::intrusive_ptr& details, const Eigen::Vector3f& v) { - if (details->getKnowledge(StarDetails::KnowRadius)) - return details->getRadius(); - -#ifdef NO_BOLOMETRIC_MAGNITUDE_CORRECTION - auto lum = getLuminosity(); -#else - // Calculate the luminosity of the star from the bolometric, not the - // visual magnitude of the star. - auto lum = getBolometricLuminosity(); -#endif - - // Use the Stefan-Boltzmann law to estimate the radius of a - // star from surface temperature and luminosity - return astro::SOLAR_RADIUS * std::sqrt(lum) * - math::square(SOLAR_TEMPERATURE / getTemperature()); + unshare(details); + details->semiAxes = v; } void -StarDetails::setEllipsoidSemiAxes(const Eigen::Vector3f& v) +StarDetails::addOrbitingStar(boost::intrusive_ptr& details, Star* star) { - semiAxes = v; + unshare(details); + if (details->orbitingStars == nullptr) + details->orbitingStars = std::make_unique>(); + details->orbitingStars->push_back(star); } bool @@ -1066,13 +1071,11 @@ StarDetails::shared() const return isShared; } -void -StarDetails::addOrbitingStar(Star* star) +Star::Star(AstroCatalog::IndexNumber _indexNumber, const boost::intrusive_ptr& _details) : + indexNumber(_indexNumber), + details(_details) { - assert(!shared()); - if (orbitingStars == nullptr) - orbitingStars = std::make_unique>(); - orbitingStars->push_back(star); + assert(details != nullptr); } /*! Get the position of the star in the universal coordinate system. @@ -1146,6 +1149,27 @@ Star::getVelocity(double t) const } } +// Return the radius of the star in kilometers +float +Star::getRadius() const +{ + if (util::is_set(details->knowledge, StarDetails::Knowledge::KnowRadius)) + return details->getRadius(); + +#ifdef NO_BOLOMETRIC_MAGNITUDE_CORRECTION + auto lum = getLuminosity(); +#else + // Calculate the luminosity of the star from the bolometric, not the + // visual magnitude of the star. + auto lum = getBolometricLuminosity(); +#endif + + // Use the Stefan-Boltzmann law to estimate the radius of a + // star from surface temperature and luminosity + return astro::SOLAR_RADIUS * std::sqrt(lum) * + math::square(SOLAR_TEMPERATURE / getTemperature()); +} + MultiResTexture Star::getTexture() const { @@ -1168,9 +1192,9 @@ Star::getInfoURL() const } void -Star::setPosition(float x, float y, float z) +Star::setIndex(AstroCatalog::IndexNumber idx) { - position = Eigen::Vector3f(x, y, z); + indexNumber = idx; } void @@ -1197,12 +1221,6 @@ Star::getLuminosity() const return astro::absMagToLum(absMag); } -void -Star::setLuminosity(float lum) -{ - absMag = astro::lumToAbsMag(lum); -} - float Star::getBolometricLuminosity() const { @@ -1217,41 +1235,6 @@ Star::getBolometricLuminosity() const #endif } -StarDetails* -Star::getDetails() const -{ - return details.get(); -} - -void -Star::setDetails(boost::intrusive_ptr&& sd) -{ - // TODO: delete existing details if they aren't shared - details = std::move(sd); -} - -void -Star::setOrbitBarycenter(Star* s) -{ - if (details->shared()) - details = details->clone(); - details->setOrbitBarycenter(s); -} - -void -Star::computeOrbitalRadius() -{ - details->computeOrbitalRadius(); -} - -void -Star::addOrbitingStar(Star* star) -{ - if (details->shared()) - details = details->clone(); - details->addOrbitingStar(star); -} - void Star::setExtinction(float _extinction) { diff --git a/src/celengine/star.h b/src/celengine/star.h index e16a675cfa6..df88c006b61 100644 --- a/src/celengine/star.h +++ b/src/celengine/star.h @@ -26,10 +26,13 @@ #include #include #include +#include +#include #include class Selection; class Star; +class StarDatabaseBuilder; class UniversalCoord; namespace celestia::ephem @@ -38,9 +41,11 @@ class Orbit; class RotationModel; } +class StarDetailsManager; + class StarDetails { - public: +public: struct StarTextureSet { MultiResTexture defaultTex{ }; @@ -54,8 +59,8 @@ class StarDetails StarDetails(StarDetails&&) = delete; StarDetails& operator=(StarDetails&&) = delete; - static boost::intrusive_ptr create(); boost::intrusive_ptr clone() const; + void mergeFromStandard(const StarDetails* standardDetails); float getRadius() const; float getTemperature() const; @@ -70,55 +75,51 @@ class StarDetails const std::shared_ptr& getRotationModel() const; Eigen::Vector3f getEllipsoidSemiAxes() const; const std::string& getInfoURL() const; - - void setRadius(float); - void setTemperature(float); - void setSpectralType(std::string_view); - void setBolometricCorrection(float); - void setTexture(const MultiResTexture&); - void setGeometry(ResourceHandle); - void setOrbit(const std::shared_ptr&); - void setOrbitBarycenter(Star*); - void setOrbitalRadius(float); - void computeOrbitalRadius(); - void setVisibility(bool); - void setRotationModel(const std::shared_ptr&); - void setEllipsoidSemiAxes(const Eigen::Vector3f&); - void setInfoURL(std::string_view _infoURL); + bool isBarycenter() const; + + static void setRadius(boost::intrusive_ptr&, float); + static void setTemperature(boost::intrusive_ptr&, float); + static void setBolometricCorrection(boost::intrusive_ptr&, float); + static void setTexture(boost::intrusive_ptr&, const MultiResTexture&); + static void setGeometry(boost::intrusive_ptr&, ResourceHandle); + static void setOrbit(boost::intrusive_ptr&, const std::shared_ptr&); + static void setOrbitBarycenter(boost::intrusive_ptr&, Star*); + static void setVisibility(boost::intrusive_ptr&, bool); + static void setRotationModel(boost::intrusive_ptr&, const std::shared_ptr&); + static void setEllipsoidSemiAxes(boost::intrusive_ptr&, const Eigen::Vector3f&); + static void setInfoURL(boost::intrusive_ptr&, std::string_view _infoURL); + static void addOrbitingStar(boost::intrusive_ptr&, Star*); bool shared() const; inline bool hasCorona() const; - enum + enum class Knowledge : unsigned int { + None = 0, KnowRadius = 0x1, KnowRotation = 0x2, KnowTexture = 0x4, }; - std::uint32_t getKnowledge() const; - bool getKnowledge(std::uint32_t) const; - void setKnowledge(std::uint32_t); - void addKnowledge(std::uint32_t); static boost::intrusive_ptr GetStarDetails(const StellarClass&); static boost::intrusive_ptr GetBarycenterDetails(); static void SetStarTextures(const StarTextureSet&); - private: +private: StarDetails(); friend class Star; - void addOrbitingStar(Star*); + void computeOrbitalRadius(); - friend void + inline friend void intrusive_ptr_add_ref(StarDetails* p) { p->refCount.fetch_add(1, std::memory_order_relaxed); } - friend void + inline friend void intrusive_ptr_release(StarDetails* p) { if (p->refCount.fetch_sub(1, std::memory_order_acq_rel) == 1) @@ -131,7 +132,7 @@ class StarDetails float temperature{ 0.0f }; float bolometricCorrection{ 0.0f }; - std::uint32_t knowledge{ 0 }; + Knowledge knowledge{ Knowledge::None }; bool visible{ true }; std::array spectralType{ }; @@ -150,8 +151,11 @@ class StarDetails std::unique_ptr> orbitingStars{ nullptr }; bool isShared{ true }; + + friend class StarDetailsManager; }; +ENUM_CLASS_BITWISE_OPS(StarDetails::Knowledge); inline float StarDetails::getRadius() const @@ -189,18 +193,6 @@ StarDetails::getOrbitalRadius() const return orbitalRadius; } -inline std::uint32_t -StarDetails::getKnowledge() const -{ - return knowledge; -} - -inline bool -StarDetails::getKnowledge(std::uint32_t knowledgeFlags) const -{ - return ((knowledge & knowledgeFlags) == knowledgeFlags); -} - inline const char* StarDetails::getSpectralType() const { @@ -237,66 +229,61 @@ StarDetails::getEllipsoidSemiAxes() const return semiAxes; } -bool +inline bool StarDetails::hasCorona() const { // Y dwarfs and T dwarf subclasses 5-9 don't have a corona return spectralType[0] != 'Y' && (spectralType[0] != 'T' || spectralType[1] < '5'); } - +inline bool +StarDetails::isBarycenter() const +{ + using namespace std::string_view_literals; + return spectralType.data() == "Bary"sv; +} class Star { public: + static constexpr AstroCatalog::IndexNumber MaxTychoCatalogNumber = 0xf0000000; + + // Required for array initialization in star octree builder + Star() = default; + Star(AstroCatalog::IndexNumber, const boost::intrusive_ptr&); + + Star(const Star&) = default; + Star& operator=(const Star&) = default; + Star(Star&&) noexcept = default; + Star& operator=(Star&&) noexcept = default; + + AstroCatalog::IndexNumber getIndex() const; + void setIndex(AstroCatalog::IndexNumber idx); /** This getPosition() method returns the approximate star position; that is, * star position without any orbital motion taken into account. For a * star in an orbit, the position should be set to the 'root' barycenter * of the system. */ - Eigen::Vector3f getPosition() const - { - return position; - } + const Eigen::Vector3f& getPosition() const; + void setPosition(const Eigen::Vector3f& positionLy); - float getAbsoluteMagnitude() const - { - return absMag; - } + float getAbsoluteMagnitude() const; + void setAbsoluteMagnitude(float); + + float getExtinction() const; + void setExtinction(float); float getApparentMagnitude(float) const; float getLuminosity() const; float getBolometricLuminosity() const; - void setExtinction(float); - float getExtinction() const - { - return extinction; - } - // Return the exact position of the star, accounting for its orbit UniversalCoord getPosition(double t) const; UniversalCoord getOrbitBarycenterPosition(double t) const; Eigen::Vector3d getVelocity(double t) const; - void setPosition(float, float, float); - void setPosition(const Eigen::Vector3f& positionLy); - void setAbsoluteMagnitude(float); - void setLuminosity(float); - - StarDetails* getDetails() const; - void setDetails(boost::intrusive_ptr&&); - void setOrbitBarycenter(Star*); - void computeOrbitalRadius(); - - void addOrbitingStar(Star*); - const std::vector* getOrbitingStars() const; - - AstroCatalog::IndexNumber getIndex() const { return indexNumber; } - void setIndex(AstroCatalog::IndexNumber idx) { indexNumber = idx; } - // Accessor methods that delegate to StarDetails float getRadius() const; float getTemperature() const; @@ -312,8 +299,9 @@ class Star Eigen::Vector3f getEllipsoidSemiAxes() const; const std::string& getInfoURL() const; bool hasCorona() const; + bool isBarycenter() const; - static constexpr AstroCatalog::IndexNumber MaxTychoCatalogNumber = 0xf0000000; + celestia::util::array_view getOrbitingStars() const; private: AstroCatalog::IndexNumber indexNumber{ AstroCatalog::InvalidIndex }; @@ -321,8 +309,33 @@ class Star float absMag{ 4.83f }; float extinction{ 0.0f }; boost::intrusive_ptr details{ nullptr }; + + friend class StarDatabaseBuilder; }; +inline AstroCatalog::IndexNumber +Star::getIndex() const +{ + return indexNumber; +} + +inline const Eigen::Vector3f& +Star::getPosition() const +{ + return position; +} + +inline float +Star::getAbsoluteMagnitude() const +{ + return absMag; +} + +inline float +Star::getExtinction() const +{ + return extinction; +} inline float Star::getTemperature() const @@ -378,10 +391,13 @@ Star::getEllipsoidSemiAxes() const return details->getEllipsoidSemiAxes(); } -inline const std::vector* +inline celestia::util::array_view Star::getOrbitingStars() const { - return details->orbitingStars.get(); + if (details->orbitingStars != nullptr) + return *details->orbitingStars; + + return {}; } inline bool @@ -389,3 +405,9 @@ Star::hasCorona() const { return details->hasCorona(); } + +inline bool +Star::isBarycenter() const +{ + return details->isBarycenter(); +} diff --git a/src/celengine/starbrowser.cpp b/src/celengine/starbrowser.cpp index 6fbfe8bebd7..827efd856eb 100644 --- a/src/celengine/starbrowser.cpp +++ b/src/celengine/starbrowser.cpp @@ -223,7 +223,7 @@ StarFilter::operator()(const Star* star) const // Check the number of stars orbiting the barycenter to handle cases // like the Sun orbiting the Solar System Barycenter - if (barycenter == nullptr || barycenter->getOrbitingStars()->size() < 2) + if (barycenter == nullptr || barycenter->getOrbitingStars().size() < 2) return false; } diff --git a/src/celengine/stardbbuilder.cpp b/src/celengine/stardbbuilder.cpp index 89eeff955ad..4b6e7e9682b 100644 --- a/src/celengine/stardbbuilder.cpp +++ b/src/celengine/stardbbuilder.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -24,7 +25,10 @@ #include +#include + #include +#include #include #include #include @@ -43,28 +47,43 @@ using namespace std::string_view_literals; namespace astro = celestia::astro; namespace engine = celestia::engine; +namespace ephem = celestia::ephem; namespace math = celestia::math; namespace util = celestia::util; using util::GetLogger; -// Enable the below to switch back to parsing coordinates as float to match -// legacy behaviour. This shouldn't be necessary since stars.dat stores -// Cartesian coordinates. -// #define PARSE_COORDS_FLOAT +struct StarDatabaseBuilder::StcHeader +{ + explicit StcHeader(const fs::path&); + explicit StcHeader(fs::path&&) = delete; + + const fs::path* path; + int lineNumber{ 0 }; + DataDisposition disposition{ DataDisposition::Add }; + bool isStar{ true }; + AstroCatalog::IndexNumber catalogNumber{ AstroCatalog::InvalidIndex }; + std::vector names; +}; + +StarDatabaseBuilder::StcHeader::StcHeader(const fs::path& _path) : + path(&_path) +{ +} -struct StarDatabaseBuilder::CustomStarDetails +template<> +struct fmt::formatter : formatter { - bool hasCustomDetails{false}; - fs::path modelName; - fs::path textureName; - std::shared_ptr orbit; - std::shared_ptr rm; - std::optional semiAxes{std::nullopt}; - std::optional radius{std::nullopt}; - double temperature{0.0}; - std::optional bolometricCorrection{std::nullopt}; - const std::string* infoURL{nullptr}; + format_context::iterator format(const StarDatabaseBuilder::StcHeader& header, format_context& ctx) + { + fmt::basic_memory_buffer data; + fmt::format_to(std::back_inserter(data), "line {}", header.lineNumber); + if (header.catalogNumber <= Star::MaxTychoCatalogNumber) + fmt::format_to(std::back_inserter(data), " - HIP {}", header.catalogNumber); + if (!header.names.empty()) + fmt::format_to(std::back_inserter(data), " - {}", header.names.front()); + return formatter::format(std::string_view(data.data(), data.size()), ctx); + } }; namespace @@ -72,6 +91,11 @@ namespace constexpr float STAR_OCTREE_MAGNITUDE = 6.0f; +// We can't compute the intrinsic brightness of the star from +// the apparent magnitude if the star is within a few AU of the +// origin. +constexpr float VALID_APPMAG_DISTANCE_THRESHOLD = 1e-5f; + constexpr std::string_view STARSDAT_MAGIC = "CELSTARS"sv; constexpr std::uint16_t StarDBVersion = 0x0100; @@ -129,86 +153,380 @@ parseStarsDatHeader(std::istream& in, std::uint32_t& nStarsInFile) return true; } -void -stcError(const Tokenizer& tok, std::string_view msg) +inline void +stcError(const StarDatabaseBuilder::StcHeader& header, std::string_view msg) +{ + GetLogger()->error(_("Error in .stc file ({}): {}\n"), header, msg); +} + +inline void +stcWarn(const StarDatabaseBuilder::StcHeader& header, std::string_view msg) +{ + GetLogger()->warn(_("Warning in .stc file ({}): {}\n"), header, msg); +} + +bool +parseStcHeader(Tokenizer& tokenizer, StarDatabaseBuilder::StcHeader& header) +{ + header.lineNumber = tokenizer.getLineNumber(); + + header.isStar = true; + + // Parse the disposition--either Add, Replace, or Modify. The disposition + // may be omitted. The default value is Add. + header.disposition = DataDisposition::Add; + if (auto tokenValue = tokenizer.getNameValue(); tokenValue.has_value()) + { + if (*tokenValue == "Modify") + { + header.disposition = DataDisposition::Modify; + tokenizer.nextToken(); + } + else if (*tokenValue == "Replace") + { + header.disposition = DataDisposition::Replace; + tokenizer.nextToken(); + } + else if (*tokenValue == "Add") + { + header.disposition = DataDisposition::Add; + tokenizer.nextToken(); + } + } + + // Parse the object type--either Star or Barycenter. The object type + // may be omitted. The default is Star. + if (auto tokenValue = tokenizer.getNameValue(); tokenValue.has_value()) + { + if (*tokenValue == "Star") + { + header.isStar = true; + } + else if (*tokenValue == "Barycenter") + { + header.isStar = false; + } + else + { + stcError(header, _("unrecognized object type")); + return false; + } + tokenizer.nextToken(); + } + + // Parse the catalog number; it may be omitted if a name is supplied. + header.catalogNumber = AstroCatalog::InvalidIndex; + if (auto tokenValue = tokenizer.getNumberValue(); tokenValue.has_value()) + { + header.catalogNumber = static_cast(*tokenValue); + tokenizer.nextToken(); + } + + header.names.clear(); + if (auto tokenValue = tokenizer.getStringValue(); tokenValue.has_value()) + { + for (std::string_view remaining = *tokenValue; !remaining.empty();) + { + auto pos = remaining.find(':'); + if (std::string_view name = remaining.substr(0, pos); + !name.empty() && std::find(header.names.cbegin(), header.names.cend(), name) == header.names.cend()) + { + header.names.emplace_back(name); + } + + if (pos == std::string_view::npos || header.names.size() == StarDatabase::MAX_STAR_NAMES) + break; + + remaining = remaining.substr(pos + 1); + } + + tokenizer.nextToken(); + } + else if (header.catalogNumber == AstroCatalog::InvalidIndex) + { + stcError(header, _("entry missing name and catalog number")); + return false; + } + + return true; +} + +bool +checkSpectralType(const StarDatabaseBuilder::StcHeader& header, + const AssociativeArray* starData, + const Star* star, + boost::intrusive_ptr& newDetails) +{ + const std::string* spectralType = starData->getString("SpectralType"); + if (!header.isStar) + { + if (spectralType != nullptr) + stcWarn(header, _("ignoring SpectralType on Barycenter")); + newDetails = StarDetails::GetBarycenterDetails(); + } + else if (spectralType != nullptr) + { + newDetails = StarDetails::GetStarDetails(StellarClass::parse(*spectralType)); + if (newDetails == nullptr) + { + stcError(header, _("invalid SpectralType")); + return false; + } + } + else if (header.disposition != DataDisposition::Modify || star->isBarycenter()) + { + stcError(header, _("missing SpectralType on Star")); + return false; + } + + return true; +} + +bool +checkPolarCoordinates(const StarDatabaseBuilder::StcHeader& header, + const AssociativeArray* starData, + const Star* star, + std::optional& position) +{ + constexpr unsigned int has_ra = 1; + constexpr unsigned int has_dec = 2; + constexpr unsigned int has_distance = 4; + constexpr unsigned int has_all = has_ra | has_dec | has_distance; + + unsigned int status = 0; + + auto raValue = starData->getAngle("RA", astro::DEG_PER_HRA, 1.0); + auto decValue = starData->getAngle("Dec"); + auto distanceValue = starData->getLength("Distance", astro::KM_PER_LY); + status = (static_cast(raValue.has_value()) * has_ra) + | (static_cast(decValue.has_value()) * has_dec) + | (static_cast(distanceValue.has_value()) * has_distance); + + if (status == 0) + return true; + + if (status == has_all) + { + position = astro::equatorialToCelestialCart(*raValue, *decValue, *distanceValue).cast(); + return true; + } + + if (header.disposition != DataDisposition::Modify) + { + stcError(header, _("incomplete set of coordinates RA/Dec/Distance specified")); + return false; + } + + // Partial modification of polar coordinates + assert(star != nullptr); + + // 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(); + // 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 + + position = astro::equatorialToCelestialCart(ra, dec, distance).cast(); + return true; +} + +bool +checkMagnitudes(const StarDatabaseBuilder::StcHeader& header, + const AssociativeArray* starData, + const Star* star, + float distance, + std::optional& absMagnitude, + std::optional& extinction) { - GetLogger()->error(_("Error in .stc file (line {}): {}\n"), tok.getLineNumber(), msg); + assert(header.disposition != DataDisposition::Modify || star != nullptr); + absMagnitude = starData->getNumber("AbsMag"); + auto appMagnitude = starData->getNumber("AppMag"); + + if (!header.isStar) + { + if (absMagnitude.has_value()) + stcWarn(header, _("AbsMag ignored on Barycenter")); + if (appMagnitude.has_value()) + stcWarn(header, _("AppMag ignored on Barycenter")); + absMagnitude = 30.0f; + return true; + } + + extinction = starData->getNumber("Extinction"); + if (extinction.has_value() && distance < VALID_APPMAG_DISTANCE_THRESHOLD) + { + stcWarn(header, _("Extinction ignored for stars close to the origin")); + extinction = std::nullopt; + } + + if (absMagnitude.has_value()) + { + if (appMagnitude.has_value()) + stcWarn(header, _("AppMag ignored when AbsMag is supplied")); + } + else if (appMagnitude.has_value()) + { + if (distance < VALID_APPMAG_DISTANCE_THRESHOLD) + { + stcError(header, _("AppMag cannot be used close to the origin")); + return false; + } + + float extinctionValue = 0.0; + if (extinction.has_value()) + extinctionValue = *extinction; + else if (header.disposition == DataDisposition::Modify) + extinctionValue = star->getExtinction() * distance; + + absMagnitude = astro::appToAbsMag(*appMagnitude, distance) - extinctionValue; + } + else if (header.disposition != DataDisposition::Modify || star->isBarycenter()) + { + stcError(header, _("no magnitude defined for star")); + return false; + } + + return true; } void -modifyStarDetails(Star* star, - boost::intrusive_ptr&& referenceDetails, - bool hasCustomDetails) +mergeStarDetails(boost::intrusive_ptr& existingDetails, + const boost::intrusive_ptr& referenceDetails) { - StarDetails* existingDetails = star->getDetails(); - assert(existingDetails != nullptr); + if (referenceDetails == nullptr) + return; if (existingDetails->shared()) { - // If the star definition has extended information, clone the - // star details so we can customize it without affecting other - // stars of the same spectral type. - if (hasCustomDetails) - star->setDetails(referenceDetails == nullptr ? existingDetails->clone() : referenceDetails->clone()); - else if (referenceDetails != nullptr) - star->setDetails(std::move(referenceDetails)); + // If there are no extended information values set, just + // use the new reference details object + existingDetails = referenceDetails; + } + else + { + // There are custom details: copy the new data into the + // existing record + existingDetails->mergeFromStandard(referenceDetails.get()); } - else if (referenceDetails != nullptr) +} + +void +applyTemperatureBoloCorrection(const StarDatabaseBuilder::StcHeader& header, + const AssociativeArray* starData, + boost::intrusive_ptr& details) +{ + auto bolometricCorrection = starData->getNumber("BoloCorrection"); + if (bolometricCorrection.has_value()) { - // If the spectral type was modified, copy the new data - // to the custom details record. - existingDetails->setSpectralType(referenceDetails->getSpectralType()); - existingDetails->setTemperature(referenceDetails->getTemperature()); - existingDetails->setBolometricCorrection(referenceDetails->getBolometricCorrection()); - if ((existingDetails->getKnowledge() & StarDetails::KnowTexture) == 0) - existingDetails->setTexture(referenceDetails->getTexture()); - if ((existingDetails->getKnowledge() & StarDetails::KnowRotation) == 0) - existingDetails->setRotationModel(referenceDetails->getRotationModel()); - existingDetails->setVisibility(referenceDetails->getVisibility()); + if (!header.isStar) + stcWarn(header, _("BoloCorrection is ignored on Barycenters")); + else + StarDetails::setBolometricCorrection(details, *bolometricCorrection); + } + + if (auto temperature = starData->getNumber("Temperature"); temperature.has_value()) + { + if (!header.isStar) + { + stcWarn(header, _("Temperature is ignored on Barycenters")); + } + else if (*temperature > 0.0) + { + StarDetails::setTemperature(details, *temperature); + if (!bolometricCorrection.has_value()) + { + // if we change the temperature, recalculate the bolometric + // correction using formula from formula for main sequence + // stars given in B. Cameron Reed (1998), "The Composite + // Observational-Theoretical HR Diagram", Journal of the Royal + // Astronomical Society of Canada, Vol 92. p36. + + double logT = std::log10(static_cast(*temperature)) - 4.0; + double bc = -8.499 * std::pow(logT, 4) + 13.421 * std::pow(logT, 3) + - 8.131 * logT * logT - 3.901 * logT - 0.438; + + StarDetails::setBolometricCorrection(details, static_cast(bc)); + } + } + else + { + stcWarn(header, _("Temperature value must be greater than zero")); + } } } -StarDatabaseBuilder::CustomStarDetails -parseCustomStarDetails(const Hash* starData, - const fs::path& path) +void +applyCustomDetails(const StarDatabaseBuilder::StcHeader& header, + const AssociativeArray* starData, + boost::intrusive_ptr& details) { - StarDatabaseBuilder::CustomStarDetails customDetails; + if (const auto* mesh = starData->getString("Mesh"); mesh != nullptr) + { + if (!header.isStar) + { + stcWarn(header, _("Mesh is ignored on Barycenters")); + } + else if (auto meshPath = util::U8FileName(*mesh); meshPath.has_value()) + { + using engine::GeometryInfo; + using engine::GetGeometryManager; + ResourceHandle geometryHandle = GetGeometryManager()->getHandle(GeometryInfo(*meshPath, + *header.path, + Eigen::Vector3f::Zero(), + 1.0f, + true)); + StarDetails::setGeometry(details, geometryHandle); + } + else + { + stcError(header, _("invalid filename in Mesh")); + } + } - if (const std::string* mesh = starData->getString("Mesh"); mesh != nullptr) + if (const auto* texture = starData->getString("Texture"); texture != nullptr) { - if (auto meshPath = util::U8FileName(*mesh); meshPath.has_value()) - customDetails.modelName = std::move(*meshPath); + if (!header.isStar) + stcWarn(header, _("Texture is ignored on Barycenters")); + else if (auto texturePath = util::U8FileName(*texture); texturePath.has_value()) + StarDetails::setTexture(details, MultiResTexture(*texturePath, *header.path)); else - GetLogger()->error("Invalid filename in Mesh\n"); + stcError(header, _("invalid filename in Texture")); } - if (const std::string* texture = starData->getString("Texture"); texture != nullptr) + if (auto rotationModel = CreateRotationModel(starData, *header.path, 1.0); rotationModel != nullptr) { - if (auto texturePath = util::U8FileName(*texture); texturePath.has_value()) - customDetails.textureName = std::move(*texturePath); + if (!header.isStar) + stcWarn(header, _("Rotation is ignored on Barycenters")); else - GetLogger()->error("Invalid filename in Texture\n"); + StarDetails::setRotationModel(details, rotationModel); } - customDetails.orbit = CreateOrbit(Selection(), starData, path, true); - customDetails.rm = CreateRotationModel(starData, path, 1.0); - customDetails.semiAxes = starData->getLengthVector("SemiAxes"); - customDetails.radius = starData->getLength("Radius"); - customDetails.temperature = starData->getNumber("Temperature").value_or(0.0); - customDetails.bolometricCorrection = starData->getNumber("BoloCorrection"); - customDetails.infoURL = starData->getString("InfoURL"); - - customDetails.hasCustomDetails = !customDetails.modelName.empty() || - !customDetails.textureName.empty() || - customDetails.orbit != nullptr || - customDetails.rm != nullptr || - customDetails.semiAxes.has_value() || - customDetails.radius.has_value() || - customDetails.temperature > 0.0 || - customDetails.bolometricCorrection.has_value() || - customDetails.infoURL != nullptr; - - return customDetails; + if (auto semiAxes = starData->getLengthVector("SemiAxes"); semiAxes.has_value()) + { + if (!header.isStar) + stcWarn(header, _("SemiAxes is ignored on Barycenters")); + else if (semiAxes->minCoeff() >= 0.0) + StarDetails::setEllipsoidSemiAxes(details, *semiAxes); + else + stcWarn(header, _("SemiAxes must be greater than zero")); + } + + if (auto radius = starData->getLength("Radius"); radius.has_value()) + { + if (!header.isStar) + stcWarn(header, _("Radius is ignored on Barycenters")); + else if (*radius >= 0.0) + StarDetails::setRadius(details, *radius); + else + stcWarn(header, _("Radius must be greater than zero")); + } + + applyTemperatureBoloCorrection(header, starData, details); + + if (const auto* infoUrl = starData->getString("InfoURL"); infoUrl != nullptr) + StarDetails::setInfoURL(details, *infoUrl); } } // end unnamed namespace @@ -236,9 +554,9 @@ StarDatabaseBuilder::loadBinary(std::istream& in) for (std::uint32_t i = 0; i < recordsToRead; ++i) { auto catNo = util::fromMemoryLE(ptr + offsetof(StarsDatRecord, catNo)); - auto x = util::fromMemoryLE(ptr + offsetof(StarsDatRecord, x)); - auto y = util::fromMemoryLE(ptr + offsetof(StarsDatRecord, y)); - auto z = util::fromMemoryLE(ptr + offsetof(StarsDatRecord, z)); + Eigen::Vector3f position(util::fromMemoryLE(ptr + offsetof(StarsDatRecord, x)), + util::fromMemoryLE(ptr + offsetof(StarsDatRecord, y)), + util::fromMemoryLE(ptr + offsetof(StarsDatRecord, z))); auto absMag = util::fromMemoryLE(ptr + offsetof(StarsDatRecord, absMag)); auto spectralType = util::fromMemoryLE(ptr + offsetof(StarsDatRecord, spectralType)); @@ -252,14 +570,11 @@ StarDatabaseBuilder::loadBinary(std::istream& in) continue; } - Star& star = unsortedStars.emplace_back(); - star.setPosition(x, y, z); + Star& star = unsortedStars.emplace_back(catNo, details); + star.setPosition(position); star.setAbsoluteMagnitude(static_cast(absMag) / 256.0f); - star.setDetails(std::move(details)); - star.setIndex(catNo); ptr += sizeof(StarsDatRecord); - ++starDB->nStars; } nStarsRemaining -= recordsToRead; @@ -271,21 +586,18 @@ StarDatabaseBuilder::loadBinary(std::istream& in) auto loadTime = timer.getTime(); GetLogger()->debug("StarDatabase::read: nStars = {}, time = {} ms\n", nStarsInFile, loadTime); - GetLogger()->info(_("{} stars in binary database\n"), starDB->nStars); + GetLogger()->info(_("{} stars in binary database\n"), unsortedStars.size()); // Create the temporary list of stars sorted by catalog number; this // will be used to lookup stars during file loading. After loading is // complete, the stars are sorted into an octree and this list gets // replaced. - if (auto binFileStarCount = unsortedStars.size(); binFileStarCount > 0) - { - binFileCatalogNumberIndex.reserve(binFileStarCount); - for (Star& star : unsortedStars) - binFileCatalogNumberIndex.push_back(&star); + binFileCatalogNumberIndex.reserve(unsortedStars.size()); + for (Star& star : unsortedStars) + binFileCatalogNumberIndex.push_back(&star); - std::sort(binFileCatalogNumberIndex.begin(), binFileCatalogNumberIndex.end(), - [](const Star* star0, const Star* star1) { return star0->getIndex() < star1->getIndex(); }); - } + std::sort(binFileCatalogNumberIndex.begin(), binFileCatalogNumberIndex.end(), + [](const Star* star0, const Star* star1) { return star0->getIndex() < star1->getIndex(); }); return true; } @@ -337,192 +649,51 @@ StarDatabaseBuilder::load(std::istream& in, const fs::path& resourcePath) std::string domain; #endif + StcHeader header(resourcePath); while (tokenizer.nextToken() != Tokenizer::TokenEnd) { - bool isStar = true; - - // Parse the disposition--either Add, Replace, or Modify. The disposition - // may be omitted. The default value is Add. - DataDisposition disposition = DataDisposition::Add; - if (auto tokenValue = tokenizer.getNameValue(); tokenValue.has_value()) - { - if (*tokenValue == "Modify") - { - disposition = DataDisposition::Modify; - tokenizer.nextToken(); - } - else if (*tokenValue == "Replace") - { - disposition = DataDisposition::Replace; - tokenizer.nextToken(); - } - else if (*tokenValue == "Add") - { - disposition = DataDisposition::Add; - tokenizer.nextToken(); - } - } - - // Parse the object type--either Star or Barycenter. The object type - // may be omitted. The default is Star. - if (auto tokenValue = tokenizer.getNameValue(); tokenValue.has_value()) - { - if (*tokenValue == "Star") - { - isStar = true; - } - else if (*tokenValue == "Barycenter") - { - isStar = false; - } - else - { - stcError(tokenizer, "unrecognized object type"); - return false; - } - tokenizer.nextToken(); - } - - // Parse the catalog number; it may be omitted if a name is supplied. - AstroCatalog::IndexNumber catalogNumber = AstroCatalog::InvalidIndex; - if (auto tokenValue = tokenizer.getNumberValue(); tokenValue.has_value()) - { - catalogNumber = static_cast(*tokenValue); - tokenizer.nextToken(); - } - - std::string objName; - std::string firstName; - if (auto tokenValue = tokenizer.getStringValue(); tokenValue.has_value()) - { - // A star name (or names) is present - objName = *tokenValue; - tokenizer.nextToken(); - if (!objName.empty()) - { - std::string::size_type next = objName.find(':', 0); - firstName = objName.substr(0, next); - } - } - - // now goes the star definition - if (tokenizer.getTokenType() != Tokenizer::TokenBeginGroup) - { - GetLogger()->error("Unexpected token at line {}!\n", tokenizer.getLineNumber()); + if (!parseStcHeader(tokenizer, header)) return false; - } - - Star* star = nullptr; - - switch (disposition) - { - case DataDisposition::Add: - // Automatically generate a catalog number for the star if one isn't - // supplied. - if (catalogNumber == AstroCatalog::InvalidIndex) - { - if (!isStar && firstName.empty()) - { - GetLogger()->error("Bad barycenter: neither catalog number nor name set at line {}.\n", tokenizer.getLineNumber()); - return false; - } - catalogNumber = nextAutoCatalogNumber--; - } - else - { - star = findWhileLoading(catalogNumber); - } - break; - - case DataDisposition::Replace: - if (catalogNumber == AstroCatalog::InvalidIndex && !firstName.empty()) - catalogNumber = starDB->namesDB->findCatalogNumberByName(firstName, false); - - if (catalogNumber == AstroCatalog::InvalidIndex) - catalogNumber = nextAutoCatalogNumber--; - else - star = findWhileLoading(catalogNumber); - break; - - case DataDisposition::Modify: - // If no catalog number was specified, try looking up the star by name - if (catalogNumber == AstroCatalog::InvalidIndex && !firstName.empty()) - catalogNumber = starDB->namesDB->findCatalogNumberByName(firstName, false); - - if (catalogNumber != AstroCatalog::InvalidIndex) - star = findWhileLoading(catalogNumber); - - break; - } - - bool isNewStar = star == nullptr; + // now goes the star definition tokenizer.pushBack(); - const Value starDataValue = parser.readValue(); const Hash* starData = starDataValue.getHash(); if (starData == nullptr) { - GetLogger()->error("Bad star definition at line {}.\n", tokenizer.getLineNumber()); + GetLogger()->error(_("Bad star definition at line {}.\n"), tokenizer.getLineNumber()); return false; } - if (isNewStar) - star = new Star(); - - bool ok = false; - if (isNewStar && disposition == DataDisposition::Modify) - { - GetLogger()->warn("Modify requested for nonexistent star.\n"); - } - else - { - ok = createStar(star, disposition, catalogNumber, starData, resourcePath, !isStar); - loadCategories(catalogNumber, starData, disposition, domain); - } + if (header.disposition != DataDisposition::Add && header.catalogNumber == AstroCatalog::InvalidIndex) + header.catalogNumber = starDB->namesDB->findCatalogNumberByName(header.names.front(), false); - if (ok) + Star* star = findWhileLoading(header.catalogNumber); + if (star == nullptr) { - if (isNewStar) + if (header.disposition == DataDisposition::Modify) { - unsortedStars.push_back(*star); - ++starDB->nStars; - delete star; - - // Add the new star to the temporary (load time) index. - stcFileCatalogNumberIndex[catalogNumber] = &unsortedStars[unsortedStars.size() - 1]; + GetLogger()->error(_("Modify requested for nonexistent star.\n")); + continue; } - if (starDB->namesDB != nullptr && !objName.empty()) + if (header.catalogNumber == AstroCatalog::InvalidIndex) { - // List of namesDB will replace any that already exist for - // this star. - starDB->namesDB->erase(catalogNumber); - - // Iterate through the string for names delimited - // by ':', and insert them into the star database. - // Note that db->add() will skip empty namesDB. - 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 starName = objName.substr(startPos, length); - starDB->namesDB->add(catalogNumber, starName); - startPos = next; - } + header.catalogNumber = nextAutoCatalogNumber; + --nextAutoCatalogNumber; } } - else + + if (createOrUpdateStar(header, starData, star)) { - if (isNewStar) - delete star; - GetLogger()->info("Bad star definition--will continue parsing file.\n"); + loadCategories(header, starData, domain); + + if (!header.names.empty()) + { + starDB->namesDB->erase(header.catalogNumber); + for (const auto& name : header.names) + starDB->namesDB->add(header.catalogNumber, name); + } } } @@ -535,11 +706,10 @@ StarDatabaseBuilder::setNameDatabase(std::unique_ptr&& nameDB) starDB->namesDB = std::move(nameDB); } - std::unique_ptr StarDatabaseBuilder::finish() { - GetLogger()->info(_("Total star count: {}\n"), starDB->nStars); + GetLogger()->info(_("Total star count: {}\n"), unsortedStars.size()); buildOctree(); buildIndexes(); @@ -549,16 +719,16 @@ StarDatabaseBuilder::finish() // the barycenters have been resolved, and these are required when building // the octree. This will only rarely cause a problem, but it still needs // to be addressed. - for (const auto& b : barycenters) + for (const auto [starIdx, barycenterIdx] : barycenters) { - Star* star = starDB->find(b.catNo); - Star* barycenter = starDB->find(b.barycenterCatNo); + Star* star = starDB->find(starIdx); + Star* barycenter = starDB->find(barycenterIdx); assert(star != nullptr); assert(barycenter != nullptr); if (star != nullptr && barycenter != nullptr) { - star->setOrbitBarycenter(barycenter); - barycenter->addOrbitingStar(star); + StarDetails::setOrbitBarycenter(star->details, barycenter); + StarDetails::addOrbitingStar(barycenter->details, star); } } @@ -574,348 +744,173 @@ StarDatabaseBuilder::finish() /*! Load star data from a property list into a star instance. */ bool -StarDatabaseBuilder::createStar(Star* star, - DataDisposition disposition, - AstroCatalog::IndexNumber catalogNumber, - const Hash* starData, - const fs::path& path, - bool isBarycenter) +StarDatabaseBuilder::createOrUpdateStar(const StcHeader& header, + const AssociativeArray* starData, + Star* star) { - std::optional barycenterPosition = std::nullopt; - if (!createOrUpdateStarDetails(star, - disposition, - catalogNumber, - starData, - path, - isBarycenter, - barycenterPosition)) + boost::intrusive_ptr newDetails = nullptr; + if (!checkSpectralType(header, starData, star, newDetails)) return false; - if (disposition != DataDisposition::Modify) - star->setIndex(catalogNumber); + std::optional position = std::nullopt; + std::optional barycenterNumber = std::nullopt; + std::shared_ptr orbit = nullptr; + if (!checkStcPosition(header, starData, star, position, barycenterNumber, orbit)) + return false; - // Compute the position in rectangular coordinates. If a star has an - // orbit and barycenter, its position is the position of the barycenter. - if (barycenterPosition.has_value()) - star->setPosition(*barycenterPosition); - else if (auto rectangularPos = starData->getLengthVector("Position", astro::KM_PER_LY); rectangularPos.has_value()) + std::optional absMagnitude = std::nullopt; + std::optional extinction = std::nullopt; + float distance; + if (position.has_value()) { - // "Position" allows the position of the star to be specified in - // coordinates matching those used in stars.dat, allowing an exact - // translation of stars.dat entries to .stc. - star->setPosition(*rectangularPos); + distance = position->norm(); } else { - double ra = 0.0; - double dec = 0.0; - double distance = 0.0; - - if (disposition == DataDisposition::Modify) - { - Eigen::Vector3f pos = star->getPosition(); - - // Convert from Celestia's coordinate system - Eigen::Vector3f v(pos.x(), -pos.z(), pos.y()); - v = Eigen::Quaternionf(Eigen::AngleAxis((float) astro::J2000Obliquity, Eigen::Vector3f::UnitX())) * v; - - distance = v.norm(); - if (distance > 0.0) - { - v.normalize(); - ra = math::radToDeg(std::atan2(v.y(), v.x())) / astro::DEG_PER_HRA; - dec = math::radToDeg(std::asin(v.z())); - } - } - - bool modifyPosition = false; - if (auto raValue = starData->getAngle("RA", astro::DEG_PER_HRA, 1.0); raValue.has_value()) - { - ra = *raValue; - modifyPosition = true; - } - else if (disposition != DataDisposition::Modify) - { - GetLogger()->error(_("Invalid star: missing right ascension\n")); - return false; - } - - if (auto decValue = starData->getAngle("Dec"); decValue.has_value()) - { - dec = *decValue; - modifyPosition = true; - } - else if (disposition != DataDisposition::Modify) - { - GetLogger()->error(_("Invalid star: missing declination.\n")); - return false; - } + assert(star != nullptr); + distance = star->getPosition().norm(); + } - if (auto dist = starData->getLength("Distance", astro::KM_PER_LY); dist.has_value()) - { - distance = *dist; - modifyPosition = true; - } - else if (disposition != DataDisposition::Modify) - { - GetLogger()->error(_("Invalid star: missing distance.\n")); - return false; - } + if (!checkMagnitudes(header, starData, star, distance, absMagnitude, extinction)) + return false; - if (modifyPosition) - { -#ifdef PARSE_COORDS_FLOAT - // Truncate to floats to match behavior of reading from binary file. - // (No longer applies since binary file stores Cartesians) - // The conversion to rectangular coordinates is still performed at - // double precision, however. - Eigen::Vector3d pos = astro::equatorialToCelestialCart(static_cast(static_cast(ra)), - static_cast(static_cast(dec)), - static_cast(static_cast(distance))); -#else - Eigen::Vector3d pos = astro::equatorialToCelestialCart(ra, dec, distance); -#endif - star->setPosition(pos.cast()); - } + if (star == nullptr) + { + assert(newDetails != nullptr); + star = &unsortedStars.emplace_back(header.catalogNumber, newDetails); + stcFileCatalogNumberIndex[header.catalogNumber] = star; } - - if (isBarycenter) + else if (header.disposition == DataDisposition::Modify) { - star->setAbsoluteMagnitude(30.0f); + mergeStarDetails(star->details, newDetails); } else { - bool absoluteDefined = true; - std::optional magnitude = starData->getNumber("AbsMag"); - if (!magnitude.has_value()) - { - absoluteDefined = false; - if (auto appMag = starData->getNumber("AppMag"); appMag.has_value()) - { - float distance = star->getPosition().norm(); - - // We can't compute the intrinsic brightness of the star from - // the apparent magnitude if the star is within a few AU of the - // origin. - if (distance < 1e-5f) - { - GetLogger()->error(_("Invalid star: absolute (not apparent) magnitude must be specified for star near origin\n")); - return false; - } - magnitude = astro::appToAbsMag(*appMag, distance); - } - else if (disposition != DataDisposition::Modify) - { - GetLogger()->error(_("Invalid star: missing magnitude.\n")); - return false; - } - } - - if (magnitude.has_value()) - star->setAbsoluteMagnitude(*magnitude); - - if (auto extinction = starData->getNumber("Extinction"); extinction.has_value()) - { - if (float distance = star->getPosition().norm(); distance != 0.0f) - star->setExtinction(*extinction / distance); - else - extinction = 0.0f; - - if (!absoluteDefined) - star->setAbsoluteMagnitude(star->getAbsoluteMagnitude() - *extinction); - } + assert(newDetails != nullptr); + star->details = newDetails; } - return true; -} + if (position.has_value()) + star->setPosition(*position); -bool -StarDatabaseBuilder::createOrUpdateStarDetails(Star* star, - DataDisposition disposition, - AstroCatalog::IndexNumber catalogNumber, - const Hash* starData, - const fs::path& path, - const bool isBarycenter, - std::optional& barycenterPosition) -{ - barycenterPosition = std::nullopt; - boost::intrusive_ptr referenceDetails; + if (absMagnitude.has_value()) + star->setAbsoluteMagnitude(*absMagnitude); - // Get the magnitude and spectral type; if the star is actually - // a barycenter placeholder, these fields are ignored. - if (isBarycenter) - { - referenceDetails = StarDetails::GetBarycenterDetails(); - } - else - { - const std::string* spectralType = starData->getString("SpectralType"); - if (spectralType != nullptr) - { - StellarClass sc = StellarClass::parse(*spectralType); - referenceDetails = StarDetails::GetStarDetails(sc); - if (referenceDetails == nullptr) - { - GetLogger()->error(_("Invalid star: bad spectral type.\n")); - return false; - } - } - else if (disposition != DataDisposition::Modify) - { - // Spectral type is required for new stars - GetLogger()->error(_("Invalid star: missing spectral type.\n")); - return false; - } - } + if (extinction.has_value()) + star->setExtinction(*extinction / distance); - CustomStarDetails customDetails = parseCustomStarDetails(starData, path); - barycenterPosition = std::nullopt; + if (barycenterNumber == AstroCatalog::InvalidIndex) + barycenters.erase(header.catalogNumber); + else if (barycenterNumber.has_value()) + barycenters[header.catalogNumber] = *barycenterNumber; - if (disposition == DataDisposition::Modify) - modifyStarDetails(star, std::move(referenceDetails), customDetails.hasCustomDetails); - else - star->setDetails(customDetails.hasCustomDetails ? referenceDetails->clone() : referenceDetails); - - return applyCustomStarDetails(star, - catalogNumber, - starData, - path, - customDetails, - barycenterPosition); + if (orbit != nullptr) + StarDetails::setOrbit(star->details, orbit); + + applyCustomDetails(header, starData, star->details); + return true; } bool -StarDatabaseBuilder::applyCustomStarDetails(const Star* star, - AstroCatalog::IndexNumber catalogNumber, - const Hash* starData, - const fs::path& path, - const CustomStarDetails& customDetails, - std::optional& barycenterPosition) +StarDatabaseBuilder::checkStcPosition(const StarDatabaseBuilder::StcHeader& header, + const AssociativeArray* starData, + const Star* star, + std::optional& position, + std::optional& barycenterNumber, + std::shared_ptr& orbit) const { - if (!customDetails.hasCustomDetails) - return true; - - StarDetails* details = star->getDetails(); - assert(!details->shared()); + position = std::nullopt; + barycenterNumber = std::nullopt; - if (!customDetails.textureName.empty()) - { - details->setTexture(MultiResTexture(customDetails.textureName, path)); - details->addKnowledge(StarDetails::KnowTexture); - } + if (!checkPolarCoordinates(header, starData, star, position)) + return false; - if (!customDetails.modelName.empty()) + if (auto positionValue = starData->getLengthVector("Position", astro::KM_PER_LY); + positionValue.has_value()) { - using engine::GeometryInfo; - using engine::GetGeometryManager; - ResourceHandle geometryHandle = GetGeometryManager()->getHandle(GeometryInfo(customDetails.modelName, - path, - Eigen::Vector3f::Zero(), - 1.0f, - true)); - details->setGeometry(geometryHandle); + if (position.has_value()) + stcWarn(header, _("ignoring RA/Dec/Distance in favor of Position")); + position = *positionValue; } - if (customDetails.semiAxes.has_value()) - details->setEllipsoidSemiAxes(customDetails.semiAxes->cast()); + if (!checkBarycenter(header, starData, position, barycenterNumber)) + return false; - if (customDetails.radius.has_value()) - { - details->setRadius(*customDetails.radius); - details->addKnowledge(StarDetails::KnowRadius); - } + // we consider a star to have a barycenter if it has an OrbitBarycenter defined + // or the star is modified without overriding its position, and it has no other + // position overrides. + bool hasBarycenter = (barycenterNumber.has_value() && *barycenterNumber != AstroCatalog::InvalidIndex) + || (header.disposition == DataDisposition::Modify + && !position.has_value() + && barycenters.find(header.catalogNumber) != barycenters.end()); - if (customDetails.temperature > 0.0) + if (auto newOrbit = CreateOrbit(Selection(), starData, *header.path, true); newOrbit != nullptr) { - details->setTemperature(static_cast(customDetails.temperature)); - - if (!customDetails.bolometricCorrection.has_value()) - { - // if we change the temperature, recalculate the bolometric - // correction using formula from formula for main sequence - // stars given in B. Cameron Reed (1998), "The Composite - // Observational-Theoretical HR Diagram", Journal of the Royal - // Astronomical Society of Canada, Vol 92. p36. - - double logT = std::log10(customDetails.temperature) - 4; - double bc = -8.499 * std::pow(logT, 4) + 13.421 * std::pow(logT, 3) - - 8.131 * logT * logT - 3.901 * logT - 0.438; - - details->setBolometricCorrection(static_cast(bc)); - } + if (hasBarycenter) + orbit = std::move(newOrbit); + else + stcWarn(header, _("ignoring orbit for object without OrbitBarycenter")); } - - if (customDetails.bolometricCorrection.has_value()) + else if (hasBarycenter && star != nullptr && star->getOrbit() == nullptr) { - details->setBolometricCorrection(*customDetails.bolometricCorrection); - } - - if (customDetails.infoURL != nullptr) - details->setInfoURL(*customDetails.infoURL); - - if (!applyOrbit(catalogNumber, starData, details, customDetails, barycenterPosition)) + stcError(header, _("no orbit specified for star with OrbitBarycenter")); return false; - - if (customDetails.rm != nullptr) - details->setRotationModel(customDetails.rm); + } return true; } bool -StarDatabaseBuilder::applyOrbit(AstroCatalog::IndexNumber catalogNumber, - const Hash* starData, - StarDetails* details, - const CustomStarDetails& customDetails, - std::optional& barycenterPosition) +StarDatabaseBuilder::checkBarycenter(const StarDatabaseBuilder::StcHeader& header, + const AssociativeArray* starData, + std::optional& position, + std::optional& barycenterNumber) const { - if (customDetails.orbit == nullptr) + // If we override RA/Dec/Position, remove the barycenter + if (position.has_value()) + barycenterNumber = AstroCatalog::InvalidIndex; + + const Value* orbitBarycenterValue = starData->getValue("OrbitBarycenter"); + if (orbitBarycenterValue == nullptr) return true; - details->setOrbit(customDetails.orbit); + if (auto bcNumber = orbitBarycenterValue->getNumber(); bcNumber.has_value()) + { + barycenterNumber = static_cast(*bcNumber); + } + else if (auto bcName = orbitBarycenterValue->getString(); bcName != nullptr) + { + barycenterNumber = starDB->namesDB->findCatalogNumberByName(*bcName, false); + } + else + { + stcError(header, _("OrbitBarycenter should be either a string or an integer")); + return false; + } - // See if a barycenter was specified as well - AstroCatalog::IndexNumber barycenterCatNo = AstroCatalog::InvalidIndex; - bool barycenterDefined = false; + if (*barycenterNumber == header.catalogNumber) + { + stcError(header, _("OrbitBarycenter cycle detected")); + return false; + } - const std::string* barycenterName = starData->getString("OrbitBarycenter"); - if (barycenterName != nullptr) + if (const Star* barycenter = findWhileLoading(*barycenterNumber); barycenter != nullptr) { - barycenterCatNo = starDB->namesDB->findCatalogNumberByName(*barycenterName, false); - barycenterDefined = true; + if (position.has_value()) + stcWarn(header, "ignoring stellar coordinates in favor of OrbitBarycenter"); + position = barycenter->getPosition(); } - else if (auto barycenterNumber = starData->getNumber("OrbitBarycenter"); - barycenterNumber.has_value()) + else { - barycenterCatNo = *barycenterNumber; - barycenterDefined = true; + stcError(header, _("OrbitBarycenter refers to nonexistent star")); + return false; } - if (barycenterDefined) + for (auto it = barycenters.find(*barycenterNumber); it != barycenters.end(); it = barycenters.find(it->second)) { - if (barycenterCatNo != AstroCatalog::InvalidIndex) - { - // We can't actually resolve the barycenter catalog number - // to a Star pointer until after all stars have been loaded - // and spatially sorted. Just store it in a list to be - // resolved after sorting. - BarycenterUsage bc; - bc.catNo = catalogNumber; - bc.barycenterCatNo = barycenterCatNo; - barycenters.push_back(bc); - - // Even though we can't actually get the Star pointer for - // the barycenter, we can get the star information. - if (const Star* barycenter = findWhileLoading(barycenterCatNo); barycenter != nullptr) - barycenterPosition = barycenter->getPosition(); - } - - if (!barycenterPosition.has_value()) + if (it->second == header.catalogNumber) { - if (barycenterName == nullptr) - GetLogger()->error(_("Barycenter {} does not exist.\n"), barycenterCatNo); - else - GetLogger()->error(_("Barycenter {} does not exist.\n"), *barycenterName); + stcError(header, _("OrbitBarycenter cycle detected")); return false; } } @@ -924,15 +919,14 @@ StarDatabaseBuilder::applyOrbit(AstroCatalog::IndexNumber catalogNumber, } void -StarDatabaseBuilder::loadCategories(AstroCatalog::IndexNumber catalogNumber, - const Hash *hash, - DataDisposition disposition, - const std::string &domain) +StarDatabaseBuilder::loadCategories(const StcHeader& header, + const AssociativeArray* starData, + const std::string& domain) { - if (disposition == DataDisposition::Replace) - categories.erase(catalogNumber); + if (header.disposition == DataDisposition::Replace) + categories.erase(header.catalogNumber); - const Value* categoryValue = hash->getValue("Category"); + const Value* categoryValue = starData->getValue("Category"); if (categoryValue == nullptr) return; @@ -941,7 +935,7 @@ StarDatabaseBuilder::loadCategories(AstroCatalog::IndexNumber catalogNumber, if (categoryName->empty()) return; - addCategory(catalogNumber, *categoryName, domain); + addCategory(header.catalogNumber, *categoryName, domain); return; } @@ -955,7 +949,7 @@ StarDatabaseBuilder::loadCategories(AstroCatalog::IndexNumber catalogNumber, if (categoryName == nullptr || categoryName->empty()) continue; - addCategory(catalogNumber, *categoryName, domain); + addCategory(header.catalogNumber, *categoryName, domain); } } @@ -995,6 +989,9 @@ StarDatabaseBuilder::addCategory(AstroCatalog::IndexNumber catalogNumber, Star* StarDatabaseBuilder::findWhileLoading(AstroCatalog::IndexNumber catalogNumber) const { + if (catalogNumber == AstroCatalog::InvalidIndex) + return nullptr; + // First check for stars loaded from the binary database if (auto it = std::lower_bound(binFileCatalogNumberIndex.cbegin(), binFileCatalogNumberIndex.cend(), catalogNumber, @@ -1025,17 +1022,17 @@ StarDatabaseBuilder::buildOctree() root->insertObject(star, StarDatabase::STAR_OCTREE_ROOT_SIZE); GetLogger()->debug("Spatially sorting stars for improved locality of reference . . .\n"); - auto sortedStars = std::make_unique(starDB->nStars); + auto sortedStars = std::make_unique(unsortedStars.size()); Star* firstStar = sortedStars.get(); root->rebuildAndSort(starDB->octreeRoot, firstStar); - // ASSERT((int) (firstStar - sortedStars) == nStars); GetLogger()->debug("{} stars total\nOctree has {} nodes and {} stars.\n", firstStar - sortedStars.get(), 1 + starDB->octreeRoot->countChildren(), starDB->octreeRoot->countObjects()); - unsortedStars.clear(); + starDB->nStars = static_cast(unsortedStars.size()); starDB->stars = std::move(sortedStars); + unsortedStars.clear(); } void diff --git a/src/celengine/stardbbuilder.h b/src/celengine/stardbbuilder.h index 41d936ca960..0544e8886d9 100644 --- a/src/celengine/stardbbuilder.h +++ b/src/celengine/stardbbuilder.h @@ -31,6 +31,11 @@ class AssociativeArray; class StarDatabase; +namespace celestia::ephem +{ +class Orbit; +} + class StarDatabaseBuilder { public: @@ -49,43 +54,24 @@ class StarDatabaseBuilder std::unique_ptr finish(); - struct CustomStarDetails; + struct StcHeader; private: - struct BarycenterUsage - { - AstroCatalog::IndexNumber catNo; - AstroCatalog::IndexNumber barycenterCatNo; - }; - - bool createStar(Star* star, - DataDisposition disposition, - AstroCatalog::IndexNumber catalogNumber, - const AssociativeArray* starData, - const fs::path& path, - const bool isBarycenter); - bool createOrUpdateStarDetails(Star* star, - DataDisposition disposition, - AstroCatalog::IndexNumber catalogNumber, - const AssociativeArray* starData, - const fs::path& path, - const bool isBarycenter, - std::optional& barycenterPosition); - bool applyCustomStarDetails(const Star*, - AstroCatalog::IndexNumber, - const AssociativeArray*, - const fs::path&, - const CustomStarDetails&, - std::optional&); - bool applyOrbit(AstroCatalog::IndexNumber catalogNumber, - const AssociativeArray* starData, - StarDetails* details, - const CustomStarDetails& customDetails, - std::optional& barycenterPosition); - void loadCategories(AstroCatalog::IndexNumber catalogNumber, - const AssociativeArray *starData, - DataDisposition disposition, - const std::string &domain); + bool createOrUpdateStar(const StcHeader&, const AssociativeArray*, Star*); + bool checkStcPosition(const StcHeader&, + const AssociativeArray*, + const Star*, + std::optional&, + std::optional&, + std::shared_ptr&) const; + bool checkBarycenter(const StarDatabaseBuilder::StcHeader&, + const AssociativeArray*, + std::optional&, + std::optional&) const; + + void loadCategories(const StarDatabaseBuilder::StcHeader&, + const AssociativeArray* starData, + const std::string&); void addCategory(AstroCatalog::IndexNumber catalogNumber, const std::string& name, const std::string& domain); @@ -103,6 +89,6 @@ class StarDatabaseBuilder std::vector binFileCatalogNumberIndex; // Catalog number -> star mapping for stars loaded from stc files std::map stcFileCatalogNumberIndex; - std::vector barycenters; + std::map barycenters; std::multimap categories; }; diff --git a/src/celengine/stellarclass.h b/src/celengine/stellarclass.h index 06c0c328c6e..67935f96696 100644 --- a/src/celengine/stellarclass.h +++ b/src/celengine/stellarclass.h @@ -12,7 +12,6 @@ #include #include - class StellarClass { public: @@ -79,16 +78,16 @@ class StellarClass static constexpr unsigned int Subclass_Unknown = 10; - inline StellarClass(); - inline StellarClass(StarType, - SpectralClass, - unsigned int, - LuminosityClass); + constexpr StellarClass() = default; + StellarClass(StarType, + SpectralClass, + unsigned int, + LuminosityClass); - inline StarType getStarType() const; - inline SpectralClass getSpectralClass() const; - inline unsigned int getSubclass() const; - inline LuminosityClass getLuminosityClass() const; + StarType getStarType() const; + SpectralClass getSpectralClass() const; + unsigned int getSubclass() const; + LuminosityClass getLuminosityClass() const; static StellarClass parse(std::string_view); @@ -103,21 +102,20 @@ class StellarClass bool unpackV2(std::uint16_t); private: - StarType starType; - SpectralClass specClass; - LuminosityClass lumClass; - unsigned int subclass; + StarType starType{ NormalStar }; + SpectralClass specClass{ Spectral_Unknown }; + LuminosityClass lumClass{ Lum_Unknown }; + unsigned int subclass{ Subclass_Unknown }; }; - // A rough ordering of stellar classes, from 'early' to 'late' . . . // Useful for organizing a list of stars by spectral class. bool operator<(const StellarClass& sc0, const StellarClass& sc1); -StellarClass::StellarClass(StarType t, - SpectralClass sc, - unsigned int ssub, - LuminosityClass lum) : +inline StellarClass::StellarClass(StarType t, + SpectralClass sc, + unsigned int ssub, + LuminosityClass lum) : starType(t), specClass(sc), lumClass(lum), @@ -125,31 +123,26 @@ StellarClass::StellarClass(StarType t, { } -StellarClass::StellarClass() : - starType(NormalStar), - specClass(Spectral_Unknown), - lumClass(Lum_Unknown), - subclass(Subclass_Unknown) -{ - -} - -StellarClass::StarType StellarClass::getStarType() const +inline StellarClass::StarType +StellarClass::getStarType() const { return starType; } -StellarClass::SpectralClass StellarClass::getSpectralClass() const +inline StellarClass::SpectralClass +StellarClass::getSpectralClass() const { return specClass; } -unsigned int StellarClass::getSubclass() const +inline unsigned int +StellarClass::getSubclass() const { return subclass; } -StellarClass::LuminosityClass StellarClass::getLuminosityClass() const +inline StellarClass::LuminosityClass +StellarClass::getLuminosityClass() const { return lumClass; } diff --git a/src/celestia/hud.cpp b/src/celestia/hud.cpp index 49fc948d739..3208bcdd572 100644 --- a/src/celestia/hud.cpp +++ b/src/celestia/hud.cpp @@ -567,14 +567,14 @@ displayPlanetInfo(const util::NumberFormatter& formatter, { showPhaseAngle = true; } - else if (const auto* orbitingStars = sun->getOrbitingStars(); orbitingStars != nullptr && orbitingStars->size() == 1) + else if (auto orbitingStars = sun->getOrbitingStars(); orbitingStars.size() == 1) { // The planet's orbit is defined with respect to a barycenter. If there's // a single star orbiting the barycenter, we'll compute the phase angle // for the planet with respect to that star. If there are no stars, the // planet is an orphan, drifting through space with no star. We also skip // displaying the phase angle when there are multiple stars (for now.) - sun = orbitingStars->front(); + sun = orbitingStars.front(); showPhaseAngle = sun->getVisibility(); } diff --git a/src/celestia/qt/qtsolarsystembrowser.cpp b/src/celestia/qt/qtsolarsystembrowser.cpp index 2de5f1be584..b45da91136e 100644 --- a/src/celestia/qt/qtsolarsystembrowser.cpp +++ b/src/celestia/qt/qtsolarsystembrowser.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include "qtcolorswatchwidget.h" @@ -172,13 +173,13 @@ class SolarSystemBrowser::SolarSystemTreeModel : public QAbstractItemModel, publ TreeItem* createTreeItem(Selection sel, TreeItem* parent, int childIndex); void addTreeItemChildren(TreeItem* item, const PlanetarySystem* sys, - const std::vector* orbitingStars); + util::array_view orbitingStars); void addTreeItemChildrenFiltered(TreeItem* item, const PlanetarySystem* sys, - const std::vector* orbitingStars); + util::array_view orbitingStars); void addTreeItemChildrenGrouped(TreeItem* item, const PlanetarySystem* sys, - const std::vector* orbitingStars, + util::array_view orbitingStars, Selection parent); TreeItem* createGroupTreeItem(BodyClassification classification, const std::vector& objects, @@ -253,7 +254,7 @@ SolarSystemBrowser::SolarSystemTreeModel::createTreeItem(Selection sel, item->obj = sel; item->childIndex = childIndex; - const std::vector* orbitingStars = nullptr; + util::array_view orbitingStars; const PlanetarySystem* sys = nullptr; if (sel.body() != nullptr) @@ -286,13 +287,11 @@ SolarSystemBrowser::SolarSystemTreeModel::createTreeItem(Selection sel, void SolarSystemBrowser::SolarSystemTreeModel::addTreeItemChildren(TreeItem* item, const PlanetarySystem* sys, - const std::vector* orbitingStars) + util::array_view orbitingStars) { // Calculate the number of children: the number of orbiting stars plus // the number of orbiting solar system bodies. - item->nChildren = 0; - if (orbitingStars != nullptr) - item->nChildren += orbitingStars->size(); + item->nChildren = static_cast(orbitingStars.size()); if (sys != nullptr) item->nChildren += sys->getSystemSize(); @@ -302,14 +301,10 @@ SolarSystemBrowser::SolarSystemTreeModel::addTreeItemChildren(TreeItem* item, item->children = new TreeItem*[item->nChildren]; // Add the stars - if (orbitingStars != nullptr) + for (Star* star : orbitingStars) { - for (unsigned int i = 0; i < orbitingStars->size(); i++) - { - Selection child(orbitingStars->at(i)); - item->children[childIndex] = createTreeItem(child, item, childIndex); - childIndex++; - } + item->children[childIndex] = createTreeItem(star, item, childIndex); + ++childIndex; } // Add the solar system bodies @@ -328,7 +323,7 @@ SolarSystemBrowser::SolarSystemTreeModel::addTreeItemChildren(TreeItem* item, void SolarSystemBrowser::SolarSystemTreeModel::addTreeItemChildrenFiltered(TreeItem* item, const PlanetarySystem* sys, - const std::vector* orbitingStars) + util::array_view orbitingStars) { std::vector bodies; @@ -341,9 +336,7 @@ SolarSystemBrowser::SolarSystemTreeModel::addTreeItemChildrenFiltered(TreeItem* // Calculate the total number of children // WARN: max(size_t) > max(int) so in theory it's possible to have a negative value - item->nChildren = static_cast(bodies.size()); - if (orbitingStars != nullptr) - item->nChildren += static_cast(orbitingStars->size()); + item->nChildren = static_cast(bodies.size()) + static_cast(orbitingStars.size()); if (item->nChildren == 0) return; @@ -352,13 +345,10 @@ SolarSystemBrowser::SolarSystemTreeModel::addTreeItemChildrenFiltered(TreeItem* // Add the orbiting stars int childIndex = 0; - if (orbitingStars != nullptr) + for (Star* star : orbitingStars) { - for (Star* star : *orbitingStars) - { - item->children[childIndex] = createTreeItem(star, item, childIndex); - ++childIndex; - } + item->children[childIndex] = createTreeItem(star, item, childIndex); + ++childIndex; } // Add the direct children @@ -377,7 +367,7 @@ SolarSystemBrowser::SolarSystemTreeModel::addTreeItemChildrenFiltered(TreeItem* void SolarSystemBrowser::SolarSystemTreeModel::addTreeItemChildrenGrouped(TreeItem* item, const PlanetarySystem* sys, - const std::vector* orbitingStars, + util::array_view orbitingStars, Selection parent) { std::vector asteroids; @@ -449,9 +439,7 @@ SolarSystemBrowser::SolarSystemTreeModel::addTreeItemChildrenGrouped(TreeItem* i } // Calculate the total number of children - item->nChildren = 0; - if (orbitingStars != nullptr) - item->nChildren += orbitingStars->size(); + item->nChildren = static_cast(orbitingStars.size()); item->nChildren += normal.size(); if (!asteroids.empty()) @@ -473,13 +461,10 @@ SolarSystemBrowser::SolarSystemTreeModel::addTreeItemChildrenGrouped(TreeItem* i item->children = new TreeItem*[item->nChildren]; { // Add the stars - if (orbitingStars != nullptr) + for (Star* star : orbitingStars) { - for (Star* star : *orbitingStars) - { - item->children[childIndex] = createTreeItem(star, item, childIndex); - ++childIndex; - } + item->children[childIndex] = createTreeItem(star, item, childIndex); + ++childIndex; } // Add the direct children diff --git a/src/celutil/tokenizer.cpp b/src/celutil/tokenizer.cpp index ec0f19a4a8d..9e85c26afc2 100644 --- a/src/celutil/tokenizer.cpp +++ b/src/celutil/tokenizer.cpp @@ -268,6 +268,7 @@ TokenizerImpl::skipWhitespace() position = it - buffer.cbegin(); if (it != bufferEnd) { + ++lineNumber; ++position; break; } diff --git a/test/unit/category_test.cpp b/test/unit/category_test.cpp index f8929c3e406..20cd90eed72 100644 --- a/test/unit/category_test.cpp +++ b/test/unit/category_test.cpp @@ -167,7 +167,7 @@ TEST_CASE("Objects in categories") auto categoryId = manager.create("foo", UserCategoryId::Invalid, {}); REQUIRE(categoryId != UserCategoryId::Invalid); - Star star; + Star star(12345, StarDetails::GetBarycenterDetails()); Selection sel{&star}; REQUIRE(manager.addObject(sel, categoryId));