diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e158fd0275..c93a1300a8 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 = 'a2367ceec5f092d8777606ca110426cadd7ed7db' pushd "$env:VCPKG_INSTALLATION_ROOT" git cat-file -e "${vcpkgCommit}^{commit}" 2> $null if (!$?) { diff --git a/CMakeLists.txt b/CMakeLists.txt index b88eb09e8a..a2107ca137 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) @@ -269,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") @@ -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) 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/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/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 791f0f622a..95662b965e 100644 --- a/src/celengine/dsodb.cpp +++ b/src/celengine/dsodb.cpp @@ -1,65 +1,58 @@ +// 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" - -using celestia::util::GetLogger; +#include "name.h" -namespace astro = celestia::astro; - -namespace -{ +namespace engine = celestia::engine; -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"; +using celestia::util::GetLogger; -} // end unnamed namespace +DSODatabase::~DSODatabase() = default; -DSODatabase::~DSODatabase() +DSODatabase::DSODatabase(std::unique_ptr&& octreeRoot, + std::unique_ptr&& namesDB, + std::vector&& catalogNumberIndex, + float avgAbsMag) : + m_octreeRoot(std::move(octreeRoot)), + m_namesDB(std::move(namesDB)), + m_catalogNumberIndex(std::move(catalogNumberIndex)), + m_avgAbsMag(avgAbsMag) { - 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; }); + auto it = std::lower_bound(m_catalogNumberIndex.begin(), + m_catalogNumberIndex.end(), + catalogNumber, + [this](std::uint32_t idx, AstroCatalog::IndexNumber catNum) + { + return (*m_octreeRoot)[idx]->getIndex() < catNum; + }); - if (dso != catalogNumberIndex + nDSOs && (*dso)->getIndex() == catalogNumber) - return *dso; - else + if (it == m_catalogNumberIndex.end()) return nullptr; + + DeepSkyObject* dso = (*m_octreeRoot)[*it].get(); + return dso->getIndex() == catalogNumber ? dso : nullptr; } DeepSkyObject* @@ -68,34 +61,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 @@ -116,10 +102,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(" / "); @@ -133,7 +119,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, @@ -141,267 +127,44 @@ DSODatabase::findVisibleDSOs(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); } - octreeRoot->processVisibleObjects(dsoHandler, - obsPos, - frustumPlanes, - limitingMag, - DSO_OCTREE_ROOT_SIZE); + engine::DSOOctreeVisibleObjectsProcessor processor(&dsoHandler, + obsPos, + frustumPlanes, + limitingMag); + + m_octreeRoot->processDepthFirst(processor); } void -DSODatabase::findCloseDSOs(DSOHandler& dsoHandler, +DSODatabase::findCloseDSOs(engine::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; - } - - DeepSkyObject* obj = nullptr; - if (compareIgnoringCase(objType, "Galaxy") == 0) - obj = new Galaxy(); - else if (compareIgnoringCase(objType, "Globular") == 0) - obj = new Globular(); - else if (compareIgnoringCase(objType, "Nebula") == 0) - obj = new Nebula(); - else if (compareIgnoringCase(objType, "OpenCluster") == 0) - obj = new OpenCluster(); - - 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; - - obj->setIndex(objCatalogNumber); - - 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(); - /* - // 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); -} - -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. - DynamicDSOOctree* root = new DynamicDSOOctree(Eigen::Vector3d::Zero(), absMag); - for (int i = 0; i < nDSOs; ++i) - { - root->insertObject(DSOs[i], DSO_OCTREE_ROOT_SIZE); - } - - GetLogger()->debug("Spatially sorting DSOs for improved locality of reference . . .\n"); - DeepSkyObject** sortedDSOs = new DeepSkyObject*[nDSOs]; - DeepSkyObject** firstDSO = sortedDSOs; - - // The spatial sorting part is useless for DSOs since we - // are storing pointers to objects and not the objects themselves: - root->rebuildAndSort(octreeRoot, firstDSO); - - GetLogger()->debug("{} DSOs total.\nOctree has {} nodes and {} DSOs.\n", - static_cast(firstDSO - sortedDSOs), - 1 + octreeRoot->countChildren(), - octreeRoot->countObjects()); - - // Clean up . . . - delete[] DSOs; - delete root; - - DSOs = sortedDSOs; -} - -void -DSODatabase::calcAvgAbsMag() -{ - uint32_t nDSOeff = size(); - for (int i = 0; i < nDSOs; ++i) - { - float DSOmag = DSOs[i]->getAbsoluteMagnitude(); + engine::DSOOctreeCloseObjectsProcessor processor(&dsoHandler, + obsPos, + radius); - // 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 = new DeepSkyObject*[nDSOs]; - for (int i = 0; i < nDSOs; ++i) - catalogNumberIndex[i] = DSOs[i]; - - std::sort(catalogNumberIndex, - catalogNumberIndex + nDSOs, - [](const DeepSkyObject* dso0, const DeepSkyObject* dso1) { return dso0->getIndex() < dso1->getIndex(); }); -} - -float -DSODatabase::getAverageAbsoluteMagnitude() const -{ - return avgAbsMag; + m_octreeRoot->processDepthFirst(processor); } diff --git a/src/celengine/dsodb.h b/src/celengine/dsodb.h index 5a8949429a..c9a05ed8c3 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,8 +35,12 @@ 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; +public: + DSODatabase(std::unique_ptr&&, + std::unique_ptr&&, + std::vector&&, + float); + ~DSODatabase(); DeepSkyObject* getDSO(const std::uint32_t) const; @@ -46,52 +51,46 @@ 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; 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(); - - int nDSOs{ 0 }; - int capacity{ 0 }; - DeepSkyObject** DSOs{ nullptr }; - std::unique_ptr namesDB; - DeepSkyObject** catalogNumberIndex{ nullptr }; - DSOOctree* octreeRoot{ nullptr }; - AstroCatalog::IndexNumber nextAutoCatalogNumber{ 0xfffffffe }; - - float avgAbsMag{ 0.0f }; + std::unique_ptr m_octreeRoot; + std::unique_ptr m_namesDB; + std::vector m_catalogNumberIndex; + + float m_avgAbsMag{ 0.0f }; + + friend class DSODatabaseBuilder; }; inline DeepSkyObject* DSODatabase::getDSO(const std::uint32_t n) const { - return *(DSOs + n); + return (*m_octreeRoot)[n].get(); } inline std::uint32_t DSODatabase::size() const { - return nDSOs; + return m_octreeRoot->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..ec16865e44 --- /dev/null +++ b/src/celengine/dsodbbuilder.cpp @@ -0,0 +1,289 @@ +// 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 "octree.h" +#include "octreebuilder.h" +#include "opencluster.h" +#include "parser.h" +#include "value.h" + +namespace astro = celestia::astro; +namespace engine = celestia::engine; + +using celestia::util::GetLogger; + +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 +// 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. + +struct DSOOctreeTraits +{ + using ObjectType = std::unique_ptr; + using PrecisionType = double; + + 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 +{ + return obj->getPosition(); +} + +inline double +DSOOctreeTraits::getRadius(const ObjectType& obj) //NOSONAR +{ + return obj->getBoundingSphereRadius(); +} + +inline float +DSOOctreeTraits::getMagnitude(const ObjectType& obj) //NOSONAR +{ + return obj->getAbsoluteMagnitude(); +} + +inline float +DSOOctreeTraits::applyDecay(float factor) +{ + return factor + 0.5f; +} + +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 engine::DSOOctree& DSOs) +{ + auto nDSOeff = DSOs.size(); + float avgAbsMag = 0.0f; + for (engine::OctreeObjectIndex i = 0, end = nDSOeff; i < end; ++i) + { + float DSOmag = DSOs[i]->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 dsoCount = static_cast(DSOs.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->build(); + + GetLogger()->debug("{} DSOs total.\nOctree has {} nodes and {} DSOs.\n", + dsoCount, + octreeRoot->nodeCount(), + octreeRoot->size()); + + return octreeRoot; +} + +std::vector +buildCatalogNumberIndex(const engine::DSOOctree& 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(std::move(DSOs)); + auto catalogNumberIndex = buildCatalogNumberIndex(*octreeRoot); + float avgAbsMag = calcAvgAbsMag(*octreeRoot); + + GetLogger()->info(_("Loaded {} deep space objects\n"), octreeRoot->size()); + + return std::make_unique(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 4c8dec7e1c..84fd21615b 100644 --- a/src/celengine/dsooctree.cpp +++ b/src/celengine/dsooctree.cpp @@ -10,167 +10,104 @@ // 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 +#include -namespace astro = celestia::astro; - -// 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. - -bool dsoAbsoluteMagnitudePredicate(DeepSkyObject* const & _dso, const float absMag) -{ - return _dso->getAbsoluteMagnitude() <= absMag; -} - - -bool dsoStraddlesNodesPredicate(const Vector3d& cellCenterPos, DeepSkyObject* const & _dso, const float /*unused*/) +namespace celestia::engine { - //checks if this dso's radius straddles child nodes - float dsoRadius = _dso->getBoundingSphereRadius(); - - return (_dso->getPosition() - cellCenterPos).cwiseAbs().minCoeff() < dsoRadius; -} +// The version of cppcheck used by Codacy doesn't seem to detect the field initializer -double dsoAbsoluteMagnitudeDecayFunction(const double excludingFactor) +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) { - return excludingFactor + 0.5f; } - -template <> -DynamicDSOOctree* DynamicDSOOctree::getChild(DeepSkyObject* const & _obj, const PointType& cellCenterPos) +bool +DSOOctreeVisibleObjectsProcessor::checkNode(const DSOOctree::PointType& center, + double size, + float factor) { - 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 _children[child]; -} - - -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 -{ - // See if this node lies within the view frustum - // 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 Hyperplane& plane = frustumPlanes[i]; + const PlaneType& plane = m_frustumPlanes[i]; - double r = scale * plane.normal().cwiseAbs().sum(); - if (plane.signedDistance(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 - cellCenterPos).norm() - scale * DSOOctree::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 (unsigned int i=0; igetAbsoluteMagnitude(); - if (absMag < dimmest) - { - double distance = (obsPosition - _obj->getPosition()).norm() - _obj->getBoundingSphereRadius(); - float appMag = (float) ((distance >= 32.6167) ? astro::absToAppMag((double) 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 (minDistance <= 0.0 || astro::absToAppMag((double) exclusionFactor, minDistance) <= limitingFactor) - { - // Recurse into the child nodes - if (_children != nullptr) - { - for (int i = 0; i < 8; ++i) - { - _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 +void +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 - cellCenterPos).norm() - scale * DSOOctree::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. - - // Compute distance squared to avoid having to sqrt for distance - // comparison. - double radiusSquared = boundingRadius * boundingRadius; // + double distance = (m_obsPosition - obj->getPosition()).norm() - obj->getBoundingSphereRadius(); + auto appMag = static_cast((distance >= 32.6167) ? astro::absToAppMag(static_cast(absMag), distance) : absMag); - // Check all the objects in the node. - for (unsigned int i=0; iprocess(obj, distance, absMag); +} - if ((obsPosition - _obj->getPosition()).squaredNorm() < radiusSquared) // - { - float absMag = _obj->getAbsoluteMagnitude(); - double distance = (obsPosition - _obj->getPosition()).norm() - _obj->getBoundingSphereRadius(); +DSOOctreeCloseObjectsProcessor::DSOOctreeCloseObjectsProcessor(DSOHandler* dsoHandler, + const DSOOctree::PointType& obsPosition, + double boundingRadius) : + m_dsoHandler(dsoHandler), + m_obsPosition(obsPosition), + m_boundingRadius(boundingRadius), + m_radiusSquared(math::square(boundingRadius)) +{ +} - processor.process(_obj, distance, absMag); - } - } +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; +} - // Recurse into the child nodes - if (_children != nullptr) +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) - { - _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); } } + +} // end namespace celestia::engine diff --git a/src/celengine/dsooctree.h b/src/celengine/dsooctree.h index 183cd82d5a..fea925e278 100644 --- a/src/celengine/dsooctree.h +++ b/src/celengine/dsooctree.h @@ -12,10 +12,65 @@ #pragma once -#include -#include +#include +#include +#include -using DynamicDSOOctree = DynamicOctree; -using DSOOctree = StaticOctree; -using DSOHandler = OctreeProcessor; +#include +#include "deepskyobj.h" +#include "octree.h" + +namespace celestia::engine +{ + +using DSOOctree = StaticOctree, double>; +using DSOHandler = OctreeProcessor, double>; + +// 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/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..15c4d2606c 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 celestia::engine::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..a41e89260a 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 @@ -12,395 +12,156 @@ #pragma once -#include -#include -#include +#include +#include +#include +#include #include -// The DynamicOctree and 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. +#include -template class OctreeProcessor +namespace celestia::engine { - public: - OctreeProcessor() {}; - virtual ~OctreeProcessor() {}; - - virtual void process(const OBJ& obj, PREC distance, float appMag) = 0; -}; +using OctreeNodeIndex = std::uint32_t; +using OctreeObjectIndex = std::uint32_t; +using OctreeDepthType = std::uint32_t; +constexpr inline OctreeNodeIndex InvalidOctreeNode = UINT32_MAX; -struct OctreeLevelStatistics +namespace detail { - unsigned int nodeCount; - unsigned int objectCount; - double size; -}; - -template class StaticOctree; -template class DynamicOctree +template +struct StaticOctreeNode { -public: - typedef Eigen::Matrix PointType; - -private: - typedef std::vector ObjectList; + using PointType = Eigen::Matrix; + StaticOctreeNode(const PointType&, PREC); - 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; + PointType center; + PREC scale; + OctreeNodeIndex right{ InvalidOctreeNode }; + OctreeObjectIndex first{ 0 }; + OctreeObjectIndex last{ 0 }; + float brightFactor{ 1000.0 }; }; -// 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(); - - // 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 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; -}; - - - - - - - - -// 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, -}; - -// 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 +StaticOctreeNode::StaticOctreeNode(const PointType& _center, + PREC _scale) : + center(_center), + scale(_scale) { } +} // end namespace celestia::engine::detail template -inline DynamicOctree::~DynamicOctree() +class OctreeProcessor { - 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) -{ - // If the object can't be placed into this node's children, then put it here: - if (limitingFactorPredicate(obj, exclusionFactor) || straddlingPredicate(cellCenterPos, obj, exclusionFactor)) - { - 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 (_children == nullptr) - { - if (_objects == nullptr || _objects->size() < DynamicOctree::SPLIT_THRESHOLD) - { - add(obj); - return; - } +public: + virtual ~OctreeProcessor() = default; + virtual void process(const OBJ& obj, PREC distance, float appMag) = 0; - split(scale * 0.5f); - } +protected: + OctreeProcessor() = default; +}; - // We've already allocated child nodes; place the object - // into the appropriate one. - this->getChild(obj, cellCenterPos)->insertObject(obj, scale * (PREC) 0.5); -} +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 -inline void DynamicOctree::add(const OBJ& obj) +template +class StaticOctree { - if (_objects == nullptr) - _objects = new ObjectList; +public: + using PointType = Eigen::Matrix; - _objects->push_back(&obj); -} + StaticOctree() = default; + ~StaticOctree() = default; + StaticOctree(const StaticOctree&) = delete; + StaticOctree& operator=(const StaticOctree&) = delete; + StaticOctree(StaticOctree&&) noexcept = default; + StaticOctree& operator=(StaticOctree&&) noexcept = default; -template -inline void DynamicOctree::split(const PREC scale) -{ - _children = new DynamicOctree*[8]; + template + void processDepthFirst(PROCESSOR&) const; - for (int i = 0; i < 8; ++i) - { - Eigen::Matrix centerPos = cellCenterPos; + OctreeObjectIndex size() const; + OctreeNodeIndex nodeCount() const; - centerPos += Eigen::Matrix(((i & XPos) != 0) ? scale : -scale, - ((i & YPos) != 0) ? scale : -scale, - ((i & ZPos) != 0) ? scale : -scale); + OBJ& operator[](OctreeObjectIndex); + const OBJ& operator[](OctreeObjectIndex) const; -#if 0 - centerPos.x += ((i & XPos) != 0) ? scale : -scale; - centerPos.y += ((i & YPos) != 0) ? scale : -scale; - centerPos.z += ((i & ZPos) != 0) ? scale : -scale; -#endif +private: + using NodeType = detail::StaticOctreeNode; - _children[i] = new DynamicOctree(centerPos, - decayFunction(exclusionFactor)); - } - sortIntoChildNodes(); -} + std::vector m_nodes; + std::vector m_objects; + template + friend class DynamicOctree; +}; -// 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 +template +void +StaticOctree::processDepthFirst(PROCESSOR& processor) const { - unsigned int nKeptInParent = 0; - - for (unsigned int i=0; i<_objects->size(); ++i) + OctreeNodeIndex nodeIdx = 0; + OctreeNodeIndex endIdx = nodeCount(); + while (nodeIdx < endIdx) { - const OBJ& obj = *(*_objects)[i]; - - if (limitingFactorPredicate(obj, exclusionFactor) || - straddlingPredicate(cellCenterPos, obj, exclusionFactor) ) - { - (*_objects)[nKeptInParent++] = (*_objects)[i]; - } - else + const NodeType& node = m_nodes[nodeIdx]; + if (!processor.checkNode(node.center, node.scale, node.brightFactor)) { - this->getChild(obj, cellCenterPos)->add(obj); + nodeIdx = node.right; + continue; } - } - - _objects->resize(nKeptInParent); -} - -template -inline void DynamicOctree::rebuildAndSort(StaticOctree*& _staticNode, OBJ*& _sortedObjects) -{ - OBJ* _firstObject = _sortedObjects; - - if (_objects != nullptr) - for (typename ObjectList::const_iterator iter = _objects->begin(); iter != _objects->end(); ++iter) + for (OctreeObjectIndex idx = node.first; idx < node.last; ++idx) { - *_sortedObjects++ = **iter; + processor.process(m_objects[idx]); } - 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); + ++nodeIdx; } } - -//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) +template +OctreeObjectIndex +StaticOctree::size() const { + return static_cast(m_objects.size()); } - -template -inline StaticOctree::~StaticOctree() +template +OctreeNodeIndex +StaticOctree::nodeCount() const { - if (_children != nullptr) - { - for (int i = 0; i < 8; ++i) - delete _children[i]; - - delete[] _children; - } + return static_cast(m_nodes.size()); } - -template -inline int StaticOctree::countChildren() const +template +OBJ& +StaticOctree::operator[](OctreeObjectIndex idx) { - int count = 0; - - for (int i = 0; i < 8; ++i) - count += _children != nullptr ? 1 + _children[i]->countChildren() : 0; - - return count; + return m_objects[idx]; } - -template -inline int StaticOctree::countObjects() const +template +const OBJ& +StaticOctree::operator[](OctreeObjectIndex idx) const { - int count = nObjects; - - if (_children != nullptr) - for (int i = 0; i < 8; ++i) - count += _children[i]->countObjects(); - - return count; + return m_objects[idx]; } - -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); - } - } - - 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); - } -} +} // end namespace celestia::engine diff --git a/src/celengine/octreebuilder.h b/src/celengine/octreebuilder.h new file mode 100644 index 0000000000..c8be4cd219 --- /dev/null +++ b/src/celengine/octreebuilder.h @@ -0,0 +1,349 @@ +// 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 + +#include + +#include +#include "octree.h" + +namespace celestia::engine +{ + +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 +// 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 StorageType = STORAGE; + using ObjectType = typename TRAITS::ObjectType; + using PrecisionType = typename TRAITS::PrecisionType; + using PointType = Eigen::Matrix; + using StaticOctreeType = StaticOctree; + + 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"); + + DynamicOctree(STORAGE&& objects, + const PointType& rootCenter, + PrecisionType rootSize, + float rootExclusionFactor, + OctreeObjectIndex splitThreshold); + + DynamicOctree(const DynamicOctree&) = delete; + DynamicOctree& operator=(const DynamicOctree&) = delete; + DynamicOctree(DynamicOctree&&) noexcept = default; + DynamicOctree& operator=(DynamicOctree&&) noexcept = default; + + std::unique_ptr build(); + +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; +}; + +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 +void +DynamicOctree::insertObject(OctreeObjectIndex idx) +{ + 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 (;;) + { + 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.objIndices.push_back(idx); + 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 (node.children == nullptr) + { + if (node.objIndices.size() < m_splitThreshold) + { + node.objIndices.push_back(idx); + return; + } + + splitNode(node, depth); + } + + ++depth; + nodeIdx = getChild(node, depth, pos); + } +} + +template +void +DynamicOctree::splitNode(NodeType& node, OctreeDepthType depth) +{ + assert(node.children == nullptr); + node.children = std::make_unique(); + node.children->fill(InvalidOctreeNode); + + auto writeIt = node.objIndices.begin(); + auto endIt = node.objIndices.end(); + + for (auto readIt = writeIt; readIt != endIt; ++readIt) + { + 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 + { + NodeType& childNode = m_nodes[getChild(node, depth + 1, pos)]; + childNode.objIndices.push_back(*readIt); + } + } + + if (writeIt == node.objIndices.begin()) + { + // No objects retained: release heap allocation + node.objIndices = std::vector(); + } + else + { + node.objIndices.erase(writeIt, endIt); + } +} + +template +OctreeNodeIndex +DynamicOctree::getChild(NodeType& node, OctreeDepthType depth, const PointType& pos) +{ + assert(node.children != 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) + { + 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; +} + +template +std::unique_ptr::StaticOctreeType> +DynamicOctree::build() +{ + auto staticOctree = std::make_unique(); + staticOctree->m_nodes.reserve(m_nodes.size()); + staticOctree->m_objects.reserve(m_objects.size()); + + std::vector> nodeStack; + std::vector prevByDepth; + nodeStack.emplace_back(0, 0); + while (!nodeStack.empty()) + { + auto [nodeIdx, depth] = nodeStack.back(); + const NodeType& node = m_nodes[nodeIdx]; + buildNode(*staticOctree, node, depth, prevByDepth); + nodeStack.pop_back(); + + if (node.children == nullptr) + continue; + + for (OctreeNodeIndex child : *node.children) + { + if (child != InvalidOctreeNode) + nodeStack.emplace_back(child, depth + 1); + } + } + + for (OctreeObjectIndex objIndex : m_excluded) + staticOctree->m_objects.emplace_back(std::move(m_objects[objIndex])); + + return staticOctree; +} + +template +void +DynamicOctree::buildNode(StaticOctreeType& staticOctree, + const NodeType& node, + OctreeDepthType depth, + std::vector& prevByDepth) +{ + // 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) + { + staticOctree.m_nodes[prevByDepth.back()].right = staticNodeIdx; + prevByDepth.pop_back(); + } + + prevByDepth.push_back(staticNodeIdx); + + auto& staticNode = staticOctree.m_nodes.emplace_back(node.center, m_sizes[depth]); + + if (node.objIndices.empty()) + return; + + staticNode.first = static_cast(staticOctree.m_objects.size()); + staticNode.last = staticNode.first + static_cast(node.objIndices.size()); + + // 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)); + } + + // 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; + + parentNode.brightFactor = staticNode.brightFactor; + } +} + +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/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/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; diff --git a/src/celengine/stardb.cpp b/src/celengine/stardb.cpp index 71962ccbd1..41aa2c0c3d 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 { @@ -54,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; @@ -197,7 +197,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, @@ -224,22 +224,24 @@ StarDatabase::findVisibleStars(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 -StarDatabase::findCloseStars(StarHandler& starHandler, +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 7d20b9d5bf..cf0842dbdc 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; @@ -73,11 +73,9 @@ 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; - StarOctree* octreeRoot; + std::unique_ptr namesDB; + std::vector catalogNumberIndex; + std::unique_ptr octreeRoot; friend class StarDatabaseBuilder; }; @@ -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 45c7271b51..c4b46493f1 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" @@ -74,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); @@ -89,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); - for (const 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"); - auto sortedStars = std::make_unique(unsortedStars.size()); - Star* firstStar = sortedStars.get(); - root->rebuildAndSort(starDB->octreeRoot, firstStar); + starDB->octreeRoot = root->build(); 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(); } @@ -1043,15 +1093,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 49df095bc4..7ad4692dbd 100644 --- a/src/celengine/staroctree.cpp +++ b/src/celengine/staroctree.cpp @@ -10,11 +10,17 @@ // 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 +#include -namespace astro = celestia::astro; +namespace celestia::engine +{ + +namespace +{ // Maximum permitted orbital radius for stars, in light years. Orbital // radii larger than this value are not guaranteed to give correct @@ -24,74 +30,27 @@ 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; - - -// 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) -{ - return star.getAbsoluteMagnitude() <= absMag; -} - - -bool starOrbitStraddlesNodesPredicate(const Vector3f& cellCenterPos, const Star& star, const float /*unused*/) -{ - //checks if this star's orbit straddles child nodes - float orbitalRadius = star.getOrbitalRadius(); - if (orbitalRadius == 0.0f) - return false; - - Vector3f starPos = star.getPosition(); +constexpr float MAX_STAR_ORBIT_RADIUS = 1.0f; - return (starPos - cellCenterPos).cwiseAbs().minCoeff() < orbitalRadius; -} +} // end unnamed namespace +// The version of cppcheck used by Codacy doesn't seem to detect the field initializer -float starAbsoluteMagnitudeDecayFunction(const float excludingFactor) +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) { - return astro::lumToAbsMag(astro::absMagToLum(excludingFactor) / 4.0f); } - -template<> -DynamicStarOctree* DynamicStarOctree::getChild(const Star& obj, - const Vector3f& cellCenterPos) -{ - 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 _children[child]; -} - - -// 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 +bool +StarOctreeVisibleObjectsProcessor::checkNode(const StarOctree::PointType& center, + float size, + float factor) { // See if this node lies within the view frustum @@ -99,96 +58,71 @@ 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]; - float r = scale * plane.normal().cwiseAbs().sum(); - if (plane.signedDistance(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 - cellCenterPos).norm() - scale * StarOctree::SQRT3; + 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 (unsigned int i=0; i 0 ? (m_limitingFactor - distanceModulus) : 1000; - if (obj.getAbsoluteMagnitude() < dimmest) - { - float distance = (obsPosition - obj.getPosition()).norm(); - float appMag = obj.getApparentMagnitude(distance); + return true; +} - if (appMag < limitingFactor || (distance < MAX_STAR_ORBIT_RADIUS && obj.getOrbit())) - processor.process(obj, distance, appMag); - } - } +void +StarOctreeVisibleObjectsProcessor::process(const Star& obj) const +{ + if (obj.getAbsoluteMagnitude() > m_dimmest) + return; - // See if any of the objects in child nodes are potentially included - // that we need to recurse deeper. - if (minDistance <= 0 || astro::absToAppMag(exclusionFactor, minDistance) <= limitingFactor) - { - // Recurse into the child nodes - if (_children != nullptr) - { - for (int i=0; i<8; ++i) - { - _children[i]->processVisibleObjects(processor, - obsPosition, - frustumPlanes, - limitingFactor, - scale * 0.5f); - } - } - } + float distance = (m_obsPosition - obj.getPosition()).norm(); + float appMag = obj.getApparentMagnitude(distance); + + if (appMag <= m_limitingFactor || (distance < MAX_STAR_ORBIT_RADIUS && obj.getOrbit())) + m_starHandler->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)) +{ +} -template<> -void StarOctree::processCloseObjects(StarHandler& processor, - const Vector3f& obsPosition, - float boundingRadius, - float scale) const +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 = (obsPosition - cellCenterPos).norm() - scale * StarOctree::SQRT3; - - if (nodeDistance > boundingRadius) - return; - - // At this point, we've determined that the cellCenterPos of the node is - // close enough that we must check individual objects for proximity. - - // Compute distance squared to avoid having to sqrt for distance - // comparison. - float radiusSquared = boundingRadius * boundingRadius; + float nodeDistance = (m_obsPosition - center).norm() - size * numbers::sqrt3_v; + return nodeDistance <= m_boundingRadius; +} - // Check all the objects in the node. - for (unsigned int i = 0; i < nObjects; ++i) +void +StarOctreeCloseObjectsProcessor::process(const Star& obj) const +{ + StarOctree::PointType offset = m_obsPosition - obj.getPosition(); + if (offset.squaredNorm() < m_radiusSquared) { - Star& obj = _firstObject[i]; + float distance = offset.norm(); + float appMag = obj.getApparentMagnitude(distance); - if ((obsPosition - obj.getPosition()).squaredNorm() < radiusSquared) - { - float distance = (obsPosition - obj.getPosition()).norm(); - float appMag = obj.getApparentMagnitude(distance); - - processor.process(obj, distance, appMag); - } - } - - // Recurse into the child nodes - if (_children != nullptr) - { - for (int i = 0; i < 8; ++i) - { - _children[i]->processCloseObjects(processor, - obsPosition, - boundingRadius, - scale * 0.5f); - } + m_starHandler->process(obj, distance, appMag); } } + +} // end namespace celestia::engine diff --git a/src/celengine/staroctree.h b/src/celengine/staroctree.h index 3d8c67268c..46a17473c2 100644 --- a/src/celengine/staroctree.h +++ b/src/celengine/staroctree.h @@ -12,10 +12,63 @@ #pragma once +#include + +#include + #include #include +namespace celestia::engine +{ + +using StarOctree = StaticOctree; +using StarHandler = OctreeProcessor; + +// 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; +}; -typedef DynamicOctree DynamicStarOctree; -typedef StaticOctree StarOctree; -typedef OctreeProcessor StarHandler; +} // end namespace celestia::engine diff --git a/src/celengine/universe.cpp b/src/celengine/universe.cpp index 8d09cdb4bc..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, @@ -424,7 +414,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; @@ -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,9 +437,8 @@ 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,12 +461,11 @@ DSOPicker::process(DeepSkyObject* const & dso, double /*unused*/, float /*unused if (sinAngle2 <= sinAngle2Closest) { sinAngle2Closest = std::max(sinAngle2, ANGULAR_RES); - pickedDSO = dso; + pickedDSO = dso.get(); } } - -class CloseDSOPicker : public DSOHandler +class CloseDSOPicker : public engine::DSOHandler { public: CloseDSOPicker(const Eigen::Vector3d& pos, @@ -488,7 +475,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; @@ -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,9 +501,8 @@ 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 +517,7 @@ CloseDSOPicker::process(DeepSkyObject* const & dso, if ((pickOrigin - dso->getPosition()).norm() > dso->getRadius() && cosAngleToBoundCenter > largestCosAngle) { - closestDSO = dso; + closestDSO = dso.get(); largestCosAngle = cosAngleToBoundCenter; } } @@ -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, diff --git a/src/celephem/customrotation.cpp b/src/celephem/customrotation.cpp index 4c7455f2bd..9a8db6c9f8 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: @@ -269,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 { @@ -281,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); @@ -303,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]) @@ -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)); } }; @@ -471,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 @@ -495,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 @@ -507,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); } }; @@ -525,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 @@ -552,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 @@ -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); } }; @@ -910,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); } }; @@ -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 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 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 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