From f31f98f5459c58c9e66845350a46bb186acb1a3b Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 21 Nov 2023 05:26:14 -0600 Subject: [PATCH 01/15] Add Qt Positioning dependency --- .github/workflows/ci.yml | 4 ++-- scwx-qt/scwx-qt.cmake | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef95a8a3..245da98b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: msvc_version: 2022 qt_version: 6.6.0 qt_arch: win64_msvc2019_64 - qt_modules: qtimageformats + qt_modules: qtimageformats qtpositioning qt_tools: '' conan_arch: x86_64 conan_compiler: Visual Studio @@ -46,7 +46,7 @@ jobs: compiler: gcc qt_version: 6.6.0 qt_arch: gcc_64 - qt_modules: qtimageformats + qt_modules: qtimageformats qtpositioning qt_tools: '' conan_arch: x86_64 conan_compiler: gcc diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 650d94f7..0e53f5d7 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -25,6 +25,7 @@ find_package(QT NAMES Qt6 Network OpenGL OpenGLWidgets + Positioning Widgets REQUIRED) find_package(Qt${QT_VERSION_MAJOR} @@ -33,6 +34,7 @@ find_package(Qt${QT_VERSION_MAJOR} Network OpenGL OpenGLWidgets + Positioning Widgets REQUIRED) @@ -503,6 +505,7 @@ endif() target_link_libraries(scwx-qt PUBLIC Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::OpenGLWidgets + Qt${QT_VERSION_MAJOR}::Positioning Boost::json Boost::timer qmaplibregl From f515ac7b4525dd2840dbbdfc20f5432ab7e5a516 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Tue, 21 Nov 2023 22:15:21 -0600 Subject: [PATCH 02/15] Add track location to settings --- scwx-qt/source/scwx/qt/settings/general_settings.cpp | 10 ++++++++++ scwx-qt/source/scwx/qt/settings/general_settings.hpp | 1 + test/data | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/settings/general_settings.cpp b/scwx-qt/source/scwx/qt/settings/general_settings.cpp index 997d4e06..0eab43e9 100644 --- a/scwx-qt/source/scwx/qt/settings/general_settings.cpp +++ b/scwx-qt/source/scwx/qt/settings/general_settings.cpp @@ -42,6 +42,7 @@ public: mapProvider_.SetDefault(defaultMapProviderValue); mapboxApiKey_.SetDefault("?"); maptilerApiKey_.SetDefault("?"); + trackLocation_.SetDefault(false); updateNotificationsEnabled_.SetDefault(true); fontSizes_.SetElementMinimum(1); @@ -118,6 +119,7 @@ public: SettingsVariable mapProvider_ {"map_provider"}; SettingsVariable mapboxApiKey_ {"mapbox_api_key"}; SettingsVariable maptilerApiKey_ {"maptiler_api_key"}; + SettingsVariable trackLocation_ {"track_location"}; SettingsVariable updateNotificationsEnabled_ {"update_notifications"}; }; @@ -137,6 +139,7 @@ GeneralSettings::GeneralSettings() : &p->mapProvider_, &p->mapboxApiKey_, &p->maptilerApiKey_, + &p->trackLocation_, &p->updateNotificationsEnabled_}); SetDefaults(); } @@ -212,6 +215,11 @@ SettingsVariable& GeneralSettings::maptiler_api_key() const return p->maptilerApiKey_; } +SettingsVariable& GeneralSettings::track_location() const +{ + return p->trackLocation_; +} + SettingsVariable& GeneralSettings::update_notifications_enabled() const { return p->updateNotificationsEnabled_; @@ -225,6 +233,7 @@ bool GeneralSettings::Shutdown() dataChanged |= p->loopDelay_.Commit(); dataChanged |= p->loopSpeed_.Commit(); dataChanged |= p->loopTime_.Commit(); + dataChanged |= p->trackLocation_.Commit(); return dataChanged; } @@ -250,6 +259,7 @@ bool operator==(const GeneralSettings& lhs, const GeneralSettings& rhs) lhs.p->mapProvider_ == rhs.p->mapProvider_ && lhs.p->mapboxApiKey_ == rhs.p->mapboxApiKey_ && lhs.p->maptilerApiKey_ == rhs.p->maptilerApiKey_ && + lhs.p->trackLocation_ == rhs.p->trackLocation_ && lhs.p->updateNotificationsEnabled_ == rhs.p->updateNotificationsEnabled_); } diff --git a/scwx-qt/source/scwx/qt/settings/general_settings.hpp b/scwx-qt/source/scwx/qt/settings/general_settings.hpp index 57045ac6..9c6256b7 100644 --- a/scwx-qt/source/scwx/qt/settings/general_settings.hpp +++ b/scwx-qt/source/scwx/qt/settings/general_settings.hpp @@ -38,6 +38,7 @@ public: SettingsVariable& map_provider() const; SettingsVariable& mapbox_api_key() const; SettingsVariable& maptiler_api_key() const; + SettingsVariable& track_location() const; SettingsVariable& update_notifications_enabled() const; static GeneralSettings& Instance(); diff --git a/test/data b/test/data index fc428fa1..49a5bf59 160000 --- a/test/data +++ b/test/data @@ -1 +1 @@ -Subproject commit fc428fa1460b3d5ce04646727e379e2f4f90f5ec +Subproject commit 49a5bf59c30822b585b5726d7262b8e4ed4f10a7 From fca6a6484e402323e8c593bc9dcbb37baa84e5fc Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 22 Nov 2023 07:27:37 -0600 Subject: [PATCH 03/15] Add position manager to handle location updates --- scwx-qt/scwx-qt.cmake | 2 + .../scwx/qt/manager/position_manager.cpp | 121 ++++++++++++++++++ .../scwx/qt/manager/position_manager.hpp | 42 ++++++ 3 files changed, 165 insertions(+) create mode 100644 scwx-qt/source/scwx/qt/manager/position_manager.cpp create mode 100644 scwx-qt/source/scwx/qt/manager/position_manager.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 0e53f5d7..0b59b326 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -76,6 +76,7 @@ set(SRC_GL_DRAW source/scwx/qt/gl/draw/draw_item.cpp source/scwx/qt/gl/draw/rectangle.cpp) set(HDR_MANAGER source/scwx/qt/manager/font_manager.hpp source/scwx/qt/manager/placefile_manager.hpp + source/scwx/qt/manager/position_manager.hpp source/scwx/qt/manager/radar_product_manager.hpp source/scwx/qt/manager/radar_product_manager_notifier.hpp source/scwx/qt/manager/resource_manager.hpp @@ -85,6 +86,7 @@ set(HDR_MANAGER source/scwx/qt/manager/font_manager.hpp source/scwx/qt/manager/update_manager.hpp) set(SRC_MANAGER source/scwx/qt/manager/font_manager.cpp source/scwx/qt/manager/placefile_manager.cpp + source/scwx/qt/manager/position_manager.cpp source/scwx/qt/manager/radar_product_manager.cpp source/scwx/qt/manager/radar_product_manager_notifier.cpp source/scwx/qt/manager/resource_manager.cpp diff --git a/scwx-qt/source/scwx/qt/manager/position_manager.cpp b/scwx-qt/source/scwx/qt/manager/position_manager.cpp new file mode 100644 index 00000000..15f3bd09 --- /dev/null +++ b/scwx-qt/source/scwx/qt/manager/position_manager.cpp @@ -0,0 +1,121 @@ +#include +#include +#include + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace manager +{ + +static const std::string logPrefix_ = "scwx::qt::manager::position_manager"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +class PositionManager::Impl +{ +public: + explicit Impl(PositionManager* self) : self_ {self} + { + // TODO: macOS requires permission + geoPositionInfoSource_ = + QGeoPositionInfoSource::createDefaultSource(self); + + if (geoPositionInfoSource_ != nullptr) + { + logger_->debug("Using position source: {}", + geoPositionInfoSource_->sourceName().toStdString()); + + QObject::connect(geoPositionInfoSource_, + &QGeoPositionInfoSource::positionUpdated, + self_, + [this](const QGeoPositionInfo& info) + { + auto coordinate = info.coordinate(); + + if (coordinate != position_.coordinate()) + { + logger_->debug("Position updated: {}, {}", + coordinate.latitude(), + coordinate.longitude()); + } + + position_ = info; + + Q_EMIT self_->PositionUpdated(info); + }); + } + } + + ~Impl() {} + + PositionManager* self_; + + std::set uuids_ {}; + + QGeoPositionInfoSource* geoPositionInfoSource_ {}; + QGeoPositionInfo position_ {}; +}; + +PositionManager::PositionManager() : p(std::make_unique(this)) {} +PositionManager::~PositionManager() = default; + +QGeoPositionInfo PositionManager::position() const +{ + return p->position_; +} + +void PositionManager::TrackLocation(boost::uuids::uuid uuid, + bool trackingEnabled) +{ + if (p->geoPositionInfoSource_ == nullptr) + { + return; + } + + if (trackingEnabled) + { + if (p->uuids_.empty()) + { + p->geoPositionInfoSource_->startUpdates(); + } + + p->uuids_.insert(uuid); + } + else + { + p->uuids_.erase(uuid); + + if (p->uuids_.empty()) + { + p->geoPositionInfoSource_->stopUpdates(); + } + } +} + +std::shared_ptr PositionManager::Instance() +{ + static std::weak_ptr positionManagerReference_ {}; + static std::mutex instanceMutex_ {}; + + std::unique_lock lock(instanceMutex_); + + std::shared_ptr positionManager = + positionManagerReference_.lock(); + + if (positionManager == nullptr) + { + positionManager = std::make_shared(); + positionManagerReference_ = positionManager; + } + + return positionManager; +} + +} // namespace manager +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/manager/position_manager.hpp b/scwx-qt/source/scwx/qt/manager/position_manager.hpp new file mode 100644 index 00000000..815b76c9 --- /dev/null +++ b/scwx-qt/source/scwx/qt/manager/position_manager.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include +#include + +class QGeoPositionInfo; + +namespace scwx +{ +namespace qt +{ +namespace manager +{ + +class PositionManager : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(PositionManager) + +public: + explicit PositionManager(); + ~PositionManager(); + + QGeoPositionInfo position() const; + + void TrackLocation(boost::uuids::uuid uuid, bool trackingEnabled); + + static std::shared_ptr Instance(); + +signals: + void PositionUpdated(const QGeoPositionInfo& info); + +private: + class Impl; + std::unique_ptr p; +}; + +} // namespace manager +} // namespace qt +} // namespace scwx From da7cedddfff3ddb32e34b85d6cd9be14e9938d94 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Wed, 22 Nov 2023 07:33:28 -0600 Subject: [PATCH 04/15] Add track location checkbox to map settings group --- scwx-qt/source/scwx/qt/main/main_window.cpp | 34 +++++++++++++++++++++ scwx-qt/source/scwx/qt/main/main_window.ui | 11 +++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index d07ca41c..e480aeea 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,7 @@ #include #include +#include #include #include #include @@ -61,6 +63,7 @@ class MainWindowImpl : public QObject public: explicit MainWindowImpl(MainWindow* mainWindow) : + uuid_ {boost::uuids::random_generator()()}, mainWindow_ {mainWindow}, settings_ {}, activeMap_ {nullptr}, @@ -82,6 +85,7 @@ public: settingsDialog_ {nullptr}, updateDialog_ {nullptr}, placefileManager_ {manager::PlacefileManager::Instance()}, + positionManager_ {manager::PositionManager::Instance()}, textEventManager_ {manager::TextEventManager::Instance()}, timelineManager_ {manager::TimelineManager::Instance()}, updateManager_ {manager::UpdateManager::Instance()}, @@ -117,6 +121,11 @@ public: settings_.setApiKey(QString {mapProviderApiKey.c_str()}); settings_.setCacheDatabasePath(QString {cacheDbPath.c_str()}); settings_.setCacheDatabaseMaximumSize(20 * 1024 * 1024); + + if (settings::GeneralSettings::Instance().track_location().GetValue()) + { + positionManager_->TrackLocation(uuid_, true); + } } ~MainWindowImpl() { threadPool_.join(); } @@ -146,6 +155,8 @@ public: boost::asio::thread_pool threadPool_ {1u}; + boost::uuids::uuid uuid_; + MainWindow* mainWindow_; QMapLibreGL::Settings settings_; map::MapProvider mapProvider_; @@ -172,6 +183,7 @@ public: ui::UpdateDialog* updateDialog_; std::shared_ptr placefileManager_; + std::shared_ptr positionManager_; std::shared_ptr textEventManager_; std::shared_ptr timelineManager_; std::shared_ptr updateManager_; @@ -244,9 +256,13 @@ MainWindow::MainWindow(QWidget* parent) : p->mapSettingsGroup_ = new ui::CollapsibleGroup(tr("Map Settings"), this); p->mapSettingsGroup_->GetContentsLayout()->addWidget(ui->mapStyleLabel); p->mapSettingsGroup_->GetContentsLayout()->addWidget(ui->mapStyleComboBox); + p->mapSettingsGroup_->GetContentsLayout()->addWidget( + ui->trackLocationCheckBox); ui->radarToolboxScrollAreaContents->layout()->replaceWidget( ui->mapSettingsGroupBox, p->mapSettingsGroup_); ui->mapSettingsGroupBox->setVisible(false); + ui->trackLocationCheckBox->setChecked( + settings::GeneralSettings::Instance().track_location().GetValue()); // Add Level 2 Products p->level2ProductsGroup_ = @@ -846,6 +862,24 @@ void MainWindowImpl::ConnectOtherSignals() } } }); + connect(mainWindow_->ui->trackLocationCheckBox, + &QCheckBox::stateChanged, + mainWindow_, + [this](int state) + { + bool trackingEnabled = (state == Qt::CheckState::Checked); + + settings::GeneralSettings::Instance().track_location().StageValue( + trackingEnabled); + + for (std::size_t i = 0; i < maps_.size(); ++i) + { + // maps_[i]->SetTrackLocation(trackingEnabled); + } + + // Turn on location tracking (location manager) + positionManager_->TrackLocation(uuid_, trackingEnabled); + }); connect(level2ProductsWidget_, &ui::Level2ProductsWidget::RadarProductSelected, mainWindow_, diff --git a/scwx-qt/source/scwx/qt/main/main_window.ui b/scwx-qt/source/scwx/qt/main/main_window.ui index 9cd627eb..56c659f3 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.ui +++ b/scwx-qt/source/scwx/qt/main/main_window.ui @@ -39,7 +39,7 @@ 0 0 1024 - 21 + 22 @@ -142,7 +142,7 @@ 0 0 157 - 702 + 697 @@ -244,6 +244,13 @@ + + + + Track Location + + + From 5170130de823f51f201b2a847b3cc3468c3e03a2 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 23 Nov 2023 05:35:33 -0600 Subject: [PATCH 05/15] Add geo icons draw item --- scwx-qt/scwx-qt.cmake | 2 + scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp | 877 +++++++++++++++++++ scwx-qt/source/scwx/qt/gl/draw/geo_icons.hpp | 163 ++++ 3 files changed, 1042 insertions(+) create mode 100644 scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp create mode 100644 scwx-qt/source/scwx/qt/gl/draw/geo_icons.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 0b59b326..f9b56876 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -57,6 +57,7 @@ set(HDR_GL source/scwx/qt/gl/gl.hpp set(SRC_GL source/scwx/qt/gl/gl_context.cpp source/scwx/qt/gl/shader_program.cpp) set(HDR_GL_DRAW source/scwx/qt/gl/draw/draw_item.hpp + source/scwx/qt/gl/draw/geo_icons.hpp source/scwx/qt/gl/draw/geo_line.hpp source/scwx/qt/gl/draw/placefile_icons.hpp source/scwx/qt/gl/draw/placefile_images.hpp @@ -66,6 +67,7 @@ set(HDR_GL_DRAW source/scwx/qt/gl/draw/draw_item.hpp source/scwx/qt/gl/draw/placefile_triangles.hpp source/scwx/qt/gl/draw/rectangle.hpp) set(SRC_GL_DRAW source/scwx/qt/gl/draw/draw_item.cpp + source/scwx/qt/gl/draw/geo_icons.cpp source/scwx/qt/gl/draw/geo_line.cpp source/scwx/qt/gl/draw/placefile_icons.cpp source/scwx/qt/gl/draw/placefile_images.cpp diff --git a/scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp new file mode 100644 index 00000000..1c8f59f9 --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp @@ -0,0 +1,877 @@ +#include +#include +#include +#include +#include + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +static const std::string logPrefix_ = "scwx::qt::gl::draw::geo_icons"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +static constexpr std::size_t kNumRectangles = 1; +static constexpr std::size_t kNumTriangles = kNumRectangles * 2; +static constexpr std::size_t kVerticesPerTriangle = 3; +static constexpr std::size_t kVerticesPerRectangle = kVerticesPerTriangle * 2; +static constexpr std::size_t kPointsPerVertex = 9; +static constexpr std::size_t kPointsPerTexCoord = 3; +static constexpr std::size_t kIconBufferLength = + kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; +static constexpr std::size_t kTextureBufferLength = + kNumTriangles * kVerticesPerTriangle * kPointsPerTexCoord; + +// Threshold, start time, end time +static constexpr std::size_t kIntegersPerVertex_ = 3; + +struct IconInfo +{ + IconInfo(const std::string& iconSheet, + std::size_t iconWidth, + std::size_t iconHeight, + std::int32_t hotX, + std::int32_t hotY) : + iconSheet_ {iconSheet}, + iconWidth_ {iconWidth}, + iconHeight_ {iconHeight}, + hotX_ {hotX}, + hotY_ {hotY} + { + } + + void UpdateTextureInfo(); + + std::string iconSheet_; + std::size_t iconWidth_; + std::size_t iconHeight_; + std::int32_t hotX_; + std::int32_t hotY_; + util::TextureAttributes texture_ {}; + std::size_t rows_ {}; + std::size_t columns_ {}; + std::size_t numIcons_ {}; + float scaledWidth_ {}; + float scaledHeight_ {}; +}; + +struct GeoIconDrawItem +{ + units::length::nautical_miles threshold_ {}; + std::chrono::sys_time startTime_ {}; + std::chrono::sys_time endTime_ {}; + + bool visible_ {}; + boost::gil::rgba8_pixel_t modulate_ {}; + double latitude_ {}; + double longitude_ {}; + double x_ {}; + double y_ {}; + units::degrees angle_ {}; + std::string iconSheet_ {}; + std::size_t iconIndex_ {}; + std::string hoverText_ {}; +}; + +class GeoIcons::Impl +{ +public: + struct IconHoverEntry + { + std::shared_ptr di_; + + glm::vec2 p_; + glm::vec2 otl_; + glm::vec2 otr_; + glm::vec2 obl_; + glm::vec2 obr_; + }; + + explicit Impl(const std::shared_ptr& context) : + context_ {context}, + shaderProgram_ {nullptr}, + uMVPMatrixLocation_(GL_INVALID_INDEX), + uMapMatrixLocation_(GL_INVALID_INDEX), + uMapScreenCoordLocation_(GL_INVALID_INDEX), + uMapDistanceLocation_(GL_INVALID_INDEX), + uSelectedTimeLocation_(GL_INVALID_INDEX), + vao_ {GL_INVALID_INDEX}, + vbo_ {GL_INVALID_INDEX}, + numVertices_ {0} + { + } + + ~Impl() {} + + void UpdateBuffers(); + void UpdateTextureBuffer(); + void Update(bool textureAtlasChanged); + + std::shared_ptr context_; + + bool dirty_ {false}; + bool thresholded_ {false}; + + std::chrono::system_clock::time_point selectedTime_ {}; + + std::mutex iconMutex_; + + boost::unordered_flat_map currentIconSheets_ {}; + boost::unordered_flat_map newIconSheets_ {}; + + std::vector> currentIconList_ {}; + std::vector> newIconList_ {}; + std::vector> newValidIconList_ {}; + + std::vector currentIconBuffer_ {}; + std::vector currentIntegerBuffer_ {}; + std::vector newIconBuffer_ {}; + std::vector newIntegerBuffer_ {}; + + std::vector textureBuffer_ {}; + + std::vector currentHoverIcons_ {}; + std::vector newHoverIcons_ {}; + + std::shared_ptr shaderProgram_; + GLint uMVPMatrixLocation_; + GLint uMapMatrixLocation_; + GLint uMapScreenCoordLocation_; + GLint uMapDistanceLocation_; + GLint uSelectedTimeLocation_; + + GLuint vao_; + std::array vbo_; + + GLsizei numVertices_; +}; + +GeoIcons::GeoIcons(const std::shared_ptr& context) : + DrawItem(context->gl()), p(std::make_unique(context)) +{ +} +GeoIcons::~GeoIcons() = default; + +GeoIcons::GeoIcons(GeoIcons&&) noexcept = default; +GeoIcons& GeoIcons::operator=(GeoIcons&&) noexcept = default; + +void GeoIcons::set_selected_time( + std::chrono::system_clock::time_point selectedTime) +{ + p->selectedTime_ = selectedTime; +} + +void GeoIcons::set_thresholded(bool thresholded) +{ + p->thresholded_ = thresholded; +} + +void GeoIcons::Initialize() +{ + gl::OpenGLFunctions& gl = p->context_->gl(); + + p->shaderProgram_ = p->context_->GetShaderProgram( + {{GL_VERTEX_SHADER, ":/gl/geo_texture2d.vert"}, + {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, + {GL_FRAGMENT_SHADER, ":/gl/texture2d_array.frag"}}); + + p->uMVPMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMVPMatrix"); + p->uMapMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMapMatrix"); + p->uMapScreenCoordLocation_ = + p->shaderProgram_->GetUniformLocation("uMapScreenCoord"); + p->uMapDistanceLocation_ = + p->shaderProgram_->GetUniformLocation("uMapDistance"); + p->uSelectedTimeLocation_ = + p->shaderProgram_->GetUniformLocation("uSelectedTime"); + + gl.glGenVertexArrays(1, &p->vao_); + gl.glGenBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); + + gl.glBindVertexArray(p->vao_); + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); + gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // aLatLong + gl.glVertexAttribPointer(0, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + static_cast(0)); + gl.glEnableVertexAttribArray(0); + + // aXYOffset + gl.glVertexAttribPointer(1, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(2 * sizeof(float))); + gl.glEnableVertexAttribArray(1); + + // aModulate + gl.glVertexAttribPointer(3, + 4, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(4 * sizeof(float))); + gl.glEnableVertexAttribArray(3); + + // aAngle + gl.glVertexAttribPointer(4, + 1, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(8 * sizeof(float))); + gl.glEnableVertexAttribArray(4); + + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); + gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // aTexCoord + gl.glVertexAttribPointer(2, + 3, + GL_FLOAT, + GL_FALSE, + kPointsPerTexCoord * sizeof(float), + static_cast(0)); + gl.glEnableVertexAttribArray(2); + + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[2]); + gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // aThreshold + gl.glVertexAttribIPointer(5, // + 1, + GL_INT, + 0, + static_cast(0)); + gl.glEnableVertexAttribArray(5); + + // aTimeRange + gl.glVertexAttribIPointer(6, // + 2, + GL_INT, + kIntegersPerVertex_ * sizeof(GLint), + reinterpret_cast(1 * sizeof(GLint))); + gl.glEnableVertexAttribArray(6); + + p->dirty_ = true; +} + +void GeoIcons::Render(const QMapLibreGL::CustomLayerRenderParameters& params, + bool textureAtlasChanged) +{ + std::unique_lock lock {p->iconMutex_}; + + if (!p->currentIconList_.empty()) + { + gl::OpenGLFunctions& gl = p->context_->gl(); + + gl.glBindVertexArray(p->vao_); + + p->Update(textureAtlasChanged); + p->shaderProgram_->Use(); + UseRotationProjection(params, p->uMVPMatrixLocation_); + UseMapProjection( + params, p->uMapMatrixLocation_, p->uMapScreenCoordLocation_); + + if (p->thresholded_) + { + // If thresholding is enabled, set the map distance + units::length::nautical_miles mapDistance = + util::maplibre::GetMapDistance(params); + gl.glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); + } + else + { + // If thresholding is disabled, set the map distance to 0 + gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); + } + + // Selected time + std::chrono::system_clock::time_point selectedTime = + (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? + std::chrono::system_clock::now() : + p->selectedTime_; + gl.glUniform1i( + p->uSelectedTimeLocation_, + static_cast(std::chrono::duration_cast( + selectedTime.time_since_epoch()) + .count())); + + // Interpolate texture coordinates + gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Draw icons + gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); + } +} + +void GeoIcons::Deinitialize() +{ + gl::OpenGLFunctions& gl = p->context_->gl(); + + gl.glDeleteVertexArrays(1, &p->vao_); + gl.glDeleteBuffers(static_cast(p->vbo_.size()), p->vbo_.data()); + + std::unique_lock lock {p->iconMutex_}; + + p->currentIconList_.clear(); + p->currentIconSheets_.clear(); + p->currentHoverIcons_.clear(); + p->currentIconBuffer_.clear(); + p->currentIntegerBuffer_.clear(); + p->textureBuffer_.clear(); +} + +void IconInfo::UpdateTextureInfo() +{ + texture_ = util::TextureAtlas::Instance().GetTextureAttributes(iconSheet_); + + if (iconWidth_ > 0 && iconHeight_ > 0) + { + columns_ = texture_.size_.x / iconWidth_; + rows_ = texture_.size_.y / iconHeight_; + + if (hotX_ == -1 || hotY_ == -1) + { + hotX_ = static_cast(iconWidth_ / 2); + hotY_ = static_cast(iconHeight_ / 2); + } + } + else + { + columns_ = 1u; + rows_ = 1u; + + if (hotX_ == -1 || hotY_ == -1) + { + hotX_ = static_cast(texture_.size_.x / 2); + hotY_ = static_cast(texture_.size_.y / 2); + } + } + + numIcons_ = columns_ * rows_; + + // Pixel size + float xFactor = 0.0f; + float yFactor = 0.0f; + + if (texture_.size_.x > 0 && texture_.size_.y > 0) + { + xFactor = (texture_.sRight_ - texture_.sLeft_) / texture_.size_.x; + yFactor = (texture_.tBottom_ - texture_.tTop_) / texture_.size_.y; + } + + scaledWidth_ = iconWidth_ * xFactor; + scaledHeight_ = iconHeight_ * yFactor; +} + +void GeoIcons::StartIconSheets() +{ + // Clear the new buffer + p->newIconSheets_.clear(); +} + +void GeoIcons::AddIconSheet(const std::string& name, + std::size_t iconWidth, + std::size_t iconHeight, + std::int32_t hotX, + std::int32_t hotY) +{ + // Populate icon sheet map + p->newIconSheets_.emplace(std::piecewise_construct, + std::tuple {name}, + std::forward_as_tuple(IconInfo { + name, iconWidth, iconHeight, hotX, hotY})); +} + +void GeoIcons::FinishIconSheets() +{ + // Update icon sheets + for (auto& iconSheet : p->newIconSheets_) + { + iconSheet.second.UpdateTextureInfo(); + } + + std::unique_lock lock {p->iconMutex_}; + + // Swap buffers + p->currentIconSheets_.swap(p->newIconSheets_); + + // Clear the new buffers + p->newIconSheets_.clear(); + + // Mark the draw item dirty + p->dirty_ = true; +} + +void GeoIcons::StartIcons() +{ + // Clear the new buffer + p->newIconList_.clear(); + p->newValidIconList_.clear(); + p->newIconBuffer_.clear(); + p->newIntegerBuffer_.clear(); + p->newHoverIcons_.clear(); +} + +std::shared_ptr GeoIcons::AddIcon() +{ + return p->newIconList_.emplace_back(std::make_shared()); +} + +void GeoIcons::SetIconTexture(const std::shared_ptr& di, + const std::string& iconSheet, + std::size_t iconIndex) +{ + di->iconSheet_ = iconSheet; + di->iconIndex_ = iconIndex; +} + +void GeoIcons::SetIconLocation(const std::shared_ptr& di, + units::angle::degrees latitude, + units::angle::degrees longitude, + double xOffset, + double yOffset) +{ + di->latitude_ = latitude.value(); + di->longitude_ = longitude.value(); + di->x_ = xOffset; + di->y_ = yOffset; +} + +void GeoIcons::SetIconAngle(const std::shared_ptr& di, + units::angle::degrees angle) +{ + di->angle_ = angle; +} + +void GeoIcons::SetIconModulate(const std::shared_ptr& di, + boost::gil::rgba8_pixel_t modulate) +{ + di->modulate_ = modulate; +} + +void GeoIcons::SetIconHoverText(const std::shared_ptr& di, + const std::string& text) +{ + di->hoverText_ = text; +} + +void GeoIcons::SetIconVisible(const std::shared_ptr& di, + bool visible) +{ + di->visible_ = visible; +} + +void GeoIcons::FinishIcons() +{ + // Update buffers + p->UpdateBuffers(); + + std::unique_lock lock {p->iconMutex_}; + + // Swap buffers + p->currentIconList_.swap(p->newValidIconList_); + p->currentIconBuffer_.swap(p->newIconBuffer_); + p->currentIntegerBuffer_.swap(p->newIntegerBuffer_); + p->currentHoverIcons_.swap(p->newHoverIcons_); + + // Clear the new buffers + p->newIconList_.clear(); + p->newValidIconList_.clear(); + p->newIconBuffer_.clear(); + p->newIntegerBuffer_.clear(); + p->newHoverIcons_.clear(); + + // Mark the draw item dirty + p->dirty_ = true; +} + +void GeoIcons::Impl::UpdateBuffers() +{ + newIconBuffer_.clear(); + newIconBuffer_.reserve(newIconList_.size() * kIconBufferLength); + newIntegerBuffer_.clear(); + newIntegerBuffer_.reserve(newIconList_.size() * kVerticesPerRectangle * + kIntegersPerVertex_); + + for (auto& di : newIconList_) + { + // Skip hidden icons + if (!di->visible_) + { + continue; + } + + auto it = currentIconSheets_.find(di->iconSheet_); + if (it == currentIconSheets_.cend()) + { + // No icon sheet found + logger_->warn("Could not find icon sheet: {}", di->iconSheet_); + continue; + } + + auto& icon = it->second; + + // Validate icon + if (di->iconIndex_ >= icon.numIcons_) + { + // No icon found + logger_->warn("Invalid icon index: {}", di->iconIndex_); + continue; + } + + // Icon is valid, add to valid icon list + newValidIconList_.push_back(di); + + // Threshold value + units::length::nautical_miles threshold = di->threshold_; + GLint thresholdValue = static_cast(std::round(threshold.value())); + + // Start and end time + GLint startTime = + static_cast(std::chrono::duration_cast( + di->startTime_.time_since_epoch()) + .count()); + GLint endTime = + static_cast(std::chrono::duration_cast( + di->endTime_.time_since_epoch()) + .count()); + + // Latitude and longitude coordinates in degrees + const float lat = static_cast(di->latitude_); + const float lon = static_cast(di->longitude_); + + // Base X/Y offsets in pixels + const float x = static_cast(di->x_); + const float y = static_cast(di->y_); + + // Icon size + const float iw = static_cast(icon.iconWidth_); + const float ih = static_cast(icon.iconHeight_); + + // Hot X/Y (zero-based icon center) + const float hx = static_cast(icon.hotX_); + const float hy = static_cast(icon.hotY_); + + // Final X/Y offsets in pixels + const float lx = std::roundf(x - hx); + const float rx = std::roundf(lx + iw); + const float ty = std::roundf(y + hy); + const float by = std::roundf(ty - ih); + + // Angle in degrees + units::angle::degrees angle = di->angle_; + const float a = angle.value(); + + // Modulate color + const float mc0 = di->modulate_[0] / 255.0f; + const float mc1 = di->modulate_[1] / 255.0f; + const float mc2 = di->modulate_[2] / 255.0f; + const float mc3 = di->modulate_[3] / 255.0f; + + newIconBuffer_.insert(newIconBuffer_.end(), + { + // Icon + lat, lon, lx, by, mc0, mc1, mc2, mc3, a, // BL + lat, lon, lx, ty, mc0, mc1, mc2, mc3, a, // TL + lat, lon, rx, by, mc0, mc1, mc2, mc3, a, // BR + lat, lon, rx, by, mc0, mc1, mc2, mc3, a, // BR + lat, lon, rx, ty, mc0, mc1, mc2, mc3, a, // TR + lat, lon, lx, ty, mc0, mc1, mc2, mc3, a // TL + }); + newIntegerBuffer_.insert(newIntegerBuffer_.end(), + {thresholdValue, + startTime, + endTime, + thresholdValue, + startTime, + endTime, + thresholdValue, + startTime, + endTime, + thresholdValue, + startTime, + endTime, + thresholdValue, + startTime, + endTime, + thresholdValue, + startTime, + endTime}); + + if (!di->hoverText_.empty()) + { + const units::angle::radians radians = angle; + + const auto sc = util::maplibre::LatLongToScreenCoordinate({lat, lon}); + + const float cosAngle = cosf(static_cast(radians.value())); + const float sinAngle = sinf(static_cast(radians.value())); + + const glm::mat2 rotate {cosAngle, -sinAngle, sinAngle, cosAngle}; + + const glm::vec2 otl = rotate * glm::vec2 {lx, ty}; + const glm::vec2 otr = rotate * glm::vec2 {rx, ty}; + const glm::vec2 obl = rotate * glm::vec2 {lx, by}; + const glm::vec2 obr = rotate * glm::vec2 {rx, by}; + + newHoverIcons_.emplace_back( + IconHoverEntry {di, sc, otl, otr, obl, obr}); + } + } +} + +void GeoIcons::Impl::UpdateTextureBuffer() +{ + textureBuffer_.clear(); + textureBuffer_.reserve(currentIconList_.size() * kTextureBufferLength); + + for (auto& di : currentIconList_) + { + auto it = currentIconSheets_.find(di->iconSheet_); + if (it == currentIconSheets_.cend()) + { + // No file found. Should not get here, but insert empty data to match + // up with data already buffered + logger_->error("Could not find icon sheet: {}", di->iconSheet_); + + // clang-format off + textureBuffer_.insert( + textureBuffer_.end(), + { + // Icon + 0.0f, 0.0f, 0.0f, // BL + 0.0f, 0.0f, 0.0f, // TL + 0.0f, 0.0f, 0.0f, // BR + 0.0f, 0.0f, 0.0f, // BR + 0.0f, 0.0f, 0.0f, // TR + 0.0f, 0.0f, 0.0f // TL + }); + // clang-format on + + continue; + } + + auto& icon = it->second; + + // Validate icon + if (di->iconIndex_ >= icon.numIcons_) + { + // No icon found + logger_->error("Invalid icon index: {}", di->iconIndex_); + + // Will get here if a texture changes, and the texture shrunk such that + // the icon is no longer found + + // clang-format off + textureBuffer_.insert( + textureBuffer_.end(), + { + // Icon + 0.0f, 0.0f, 0.0f, // BL + 0.0f, 0.0f, 0.0f, // TL + 0.0f, 0.0f, 0.0f, // BR + 0.0f, 0.0f, 0.0f, // BR + 0.0f, 0.0f, 0.0f, // TR + 0.0f, 0.0f, 0.0f // TL + }); + // clang-format on + + continue; + } + + // Texture coordinates + const std::size_t iconRow = (di->iconIndex_) / icon.columns_; + const std::size_t iconColumn = (di->iconIndex_) % icon.columns_; + + const float iconX = iconColumn * icon.scaledWidth_; + const float iconY = iconRow * icon.scaledHeight_; + + const float ls = icon.texture_.sLeft_ + iconX; + const float rs = ls + icon.scaledWidth_; + const float tt = icon.texture_.tTop_ + iconY; + const float bt = tt + icon.scaledHeight_; + const float r = static_cast(icon.texture_.layerId_); + + // clang-format off + textureBuffer_.insert( + textureBuffer_.end(), + { + // Icon + ls, bt, r, // BL + ls, tt, r, // TL + rs, bt, r, // BR + rs, bt, r, // BR + rs, tt, r, // TR + ls, tt, r // TL + }); + // clang-format on + } +} + +void GeoIcons::Impl::Update(bool textureAtlasChanged) +{ + gl::OpenGLFunctions& gl = context_->gl(); + + // If the texture atlas has changed + if (dirty_ || textureAtlasChanged) + { + // Update texture coordinates + for (auto& iconSheet : currentIconSheets_) + { + iconSheet.second.UpdateTextureInfo(); + } + + // Update OpenGL texture buffer data + UpdateTextureBuffer(); + + // Buffer texture data + gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); + gl.glBufferData(GL_ARRAY_BUFFER, + sizeof(float) * textureBuffer_.size(), + textureBuffer_.data(), + GL_DYNAMIC_DRAW); + } + + // If buffers need updating + if (dirty_) + { + // Buffer vertex data + gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); + gl.glBufferData(GL_ARRAY_BUFFER, + sizeof(float) * currentIconBuffer_.size(), + currentIconBuffer_.data(), + GL_DYNAMIC_DRAW); + + // Buffer threshold data + gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[2]); + gl.glBufferData(GL_ARRAY_BUFFER, + sizeof(GLint) * currentIntegerBuffer_.size(), + currentIntegerBuffer_.data(), + GL_DYNAMIC_DRAW); + + numVertices_ = + static_cast(currentIconBuffer_.size() / kPointsPerVertex); + } + + dirty_ = false; +} + +bool GeoIcons::RunMousePicking( + const QMapLibreGL::CustomLayerRenderParameters& params, + const QPointF& /* mouseLocalPos */, + const QPointF& mouseGlobalPos, + const glm::vec2& mouseCoords) +{ + std::unique_lock lock {p->iconMutex_}; + + bool itemPicked = false; + + // Calculate map scale, remove width and height from original calculation + glm::vec2 scale = util::maplibre::GetMapScale(params); + scale = 2.0f / glm::vec2 {scale.x * params.width, scale.y * params.height}; + + // Scale and rotate the identity matrix to create the map matrix + glm::mat4 mapMatrix {1.0f}; + mapMatrix = glm::scale(mapMatrix, glm::vec3 {scale, 1.0f}); + mapMatrix = glm::rotate(mapMatrix, + glm::radians(params.bearing), + glm::vec3(0.0f, 0.0f, 1.0f)); + + units::length::meters mapDistance = + (p->thresholded_) ? util::maplibre::GetMapDistance(params) : + units::length::meters {0.0}; + + // If no time has been selected, use the current time + std::chrono::system_clock::time_point selectedTime = + (p->selectedTime_ == std::chrono::system_clock::time_point {}) ? + std::chrono::system_clock::now() : + p->selectedTime_; + + // For each pickable icon + auto it = std::find_if( + std::execution::par_unseq, + p->currentHoverIcons_.crbegin(), + p->currentHoverIcons_.crend(), + [&mapDistance, &selectedTime, &mapMatrix, &mouseCoords](const auto& icon) + { + if (( + // Geo icon is thresholded + mapDistance > units::length::meters {0.0} && + + // Geo icon threshold is < 999 nmi + static_cast(std::round( + units::length::nautical_miles {icon.di_->threshold_} + .value())) < 999 && + + // Map distance is beyond the threshold + icon.di_->threshold_ < mapDistance) || + + ( + // Geo icon has a start time + icon.di_->startTime_ != + std::chrono::system_clock::time_point {} && + + // The time range has not yet started + (selectedTime < icon.di_->startTime_ || + + // The time range has ended + icon.di_->endTime_ <= selectedTime))) + { + // Icon is not pickable + return false; + } + + // Initialize vertices + glm::vec2 bl = icon.p_; + glm::vec2 br = bl; + glm::vec2 tl = br; + glm::vec2 tr = tl; + + // Calculate offsets + // - Rotated offset is based on final X/Y offsets (pixels) + // - Multiply the offset by the scaled and rotated map matrix + const glm::vec2 otl = mapMatrix * glm::vec4 {icon.otl_, 0.0f, 1.0f}; + const glm::vec2 obl = mapMatrix * glm::vec4 {icon.obl_, 0.0f, 1.0f}; + const glm::vec2 obr = mapMatrix * glm::vec4 {icon.obr_, 0.0f, 1.0f}; + const glm::vec2 otr = mapMatrix * glm::vec4 {icon.otr_, 0.0f, 1.0f}; + + // Offset vertices + tl += otl; + bl += obl; + br += obr; + tr += otr; + + // Test point against polygon bounds + return util::maplibre::IsPointInPolygon({tl, bl, br, tr}, mouseCoords); + }); + + if (it != p->currentHoverIcons_.crend()) + { + itemPicked = true; + util::tooltip::Show(it->di_->hoverText_, mouseGlobalPos); + } + + return itemPicked; +} + +} // namespace draw +} // namespace gl +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/gl/draw/geo_icons.hpp b/scwx-qt/source/scwx/qt/gl/draw/geo_icons.hpp new file mode 100644 index 00000000..40482cc7 --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/geo_icons.hpp @@ -0,0 +1,163 @@ +#pragma once + +#include +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +struct GeoIconDrawItem; + +class GeoIcons : public DrawItem +{ +public: + explicit GeoIcons(const std::shared_ptr& context); + ~GeoIcons(); + + GeoIcons(const GeoIcons&) = delete; + GeoIcons& operator=(const GeoIcons&) = delete; + + GeoIcons(GeoIcons&&) noexcept; + GeoIcons& operator=(GeoIcons&&) noexcept; + + void set_selected_time(std::chrono::system_clock::time_point selectedTime); + void set_thresholded(bool thresholded); + + void Initialize() override; + void Render(const QMapLibreGL::CustomLayerRenderParameters& params, + bool textureAtlasChanged) override; + void Deinitialize() override; + + bool RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, + const QPointF& mouseLocalPos, + const QPointF& mouseGlobalPos, + const glm::vec2& mouseCoords) override; + + /** + * Resets and prepares the draw item for adding a new set of icon sheets. + */ + void StartIconSheets(); + + /** + * Adds an icon sheet for drawing the geo icons. The icon sheet must already + * exist in the texture atlas. + * + * @param [in] name The name of the icon sheet in the texture atlas + * @param [in] iconWidth The width of each icon in the icon sheet. Default is + * 0 for a single icon. + * @param [in] iconHeight The height of each icon in the icon sheet. Default + * is 0 for a single icon. + * @param [in] hotX The zero-based center of the each icon in the icon sheet. + * Default is -1 to center the icon. + * @param [in] hotY The zero-based center of the each icon in the icon sheet. + * Default is -1 to center the icon. + */ + void AddIconSheet(const std::string& name, + std::size_t iconWidth = 0, + std::size_t iconHeight = 0, + std::int32_t hotX = -1, + std::int32_t hotY = -1); + + /** + * Resets and prepares the draw item for adding a new set of icon sheets. + */ + void FinishIconSheets(); + + /** + * Resets and prepares the draw item for adding a new set of icons. + */ + void StartIcons(); + + /** + * Adds a geo icon to the internal draw list. + * + * @return Geo icon draw item + */ + std::shared_ptr AddIcon(); + + /** + * Sets the texture of a geo icon. + * + * @param [in] di Geo icon draw item + * @param [in] iconSheet The name of the icon sheet in the texture atlas + * @param [in] iconIndex The zero-based index of the icon in the icon sheet + */ + void SetIconTexture(const std::shared_ptr& di, + const std::string& iconSheet, + std::size_t iconIndex); + + /** + * Sets the location of a geo icon. + * + * @param [in] di Geo icon draw item + * @param [in] latitude The latitude of the geo icon. + * @param [in] longitude The longitude of the geo icon. + * @param [in] xOffset The x-offset of the geo icon. Default is 0. + * @param [in] yOffset The y-offset of the geo icon. Default is 0. + */ + void SetIconLocation(const std::shared_ptr& di, + units::angle::degrees latitude, + units::angle::degrees longitude, + double xOffset = 0.0, + double yOffset = 0.0); + + /** + * Sets the angle of a geo icon. + * + * @param [in] di Geo icon draw item + * @param [in] angle Angle in degrees + */ + void SetIconAngle(const std::shared_ptr& di, + units::angle::degrees angle); + + /** + * Sets the modulate color of a geo icon. + * + * @param [in] di Geo icon draw item + * @param [in] modulate Modulate color + */ + void SetIconModulate(const std::shared_ptr& di, + boost::gil::rgba8_pixel_t modulate); + + /** + * Sets the hover text of a geo icon. + * + * @param [in] di Geo icon draw item + * @param [in] text Hover text + */ + void SetIconHoverText(const std::shared_ptr& di, + const std::string& text); + + /** + * Sets the visibility of a geo icon. + * + * @param [in] di Geo icon draw item + * @param [in] visible Icon visibility + */ + void SetIconVisible(const std::shared_ptr& di, + bool visible); + + /** + * Finalizes the draw item after adding new icons. + */ + void FinishIcons(); + +private: + class Impl; + + std::unique_ptr p; +}; + +} // namespace draw +} // namespace gl +} // namespace qt +} // namespace scwx From 64c5d60483ccdaf9b84628065ca6e03fcdaab1b1 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 23 Nov 2023 06:24:13 -0600 Subject: [PATCH 06/15] Add crosshairs texture for location --- scwx-qt/res/textures/images/crosshairs-32.png | Bin 0 -> 6822 bytes scwx-qt/scwx-qt.qrc | 1 + 2 files changed, 1 insertion(+) create mode 100644 scwx-qt/res/textures/images/crosshairs-32.png diff --git a/scwx-qt/res/textures/images/crosshairs-32.png b/scwx-qt/res/textures/images/crosshairs-32.png new file mode 100644 index 0000000000000000000000000000000000000000..6163b3aebeeba27976f1275f71ce9029f6c26843 GIT binary patch literal 6822 zcmeHLc{o(<`yYFDEhLmNC}LJKX31{sSrUrUYgR_aFf-OtBuOH&rR*iju2c$e@jIj5-nxF@>-t^qb^X5oH8W?Bf*1${ zlC-iUIs#`f?^q)Y{BuG>XF(tlcBs={t|KW3?8j!&89p>HH^h$yrUf(TAW-n1`FmX} z)v#-pdNdCUii$#8x)k0he)(dTa5XfuBKvFTZJRsc$B!%MtSoTS4W}%++}7jX?S-6w zeqx=NGrIECQRkzXk_$ygxtAXw-GrSJBt3XL8iZ}RwyY2|$G5nvYp@`aZMgnpK;CiA zks~`s_79ms63H2n5AhGWv;2m`Z$GQ77@93Mxv+?b*?wA9m>=a7O>{6Pzb1IpHptHs zyD9Q_`@7}p+j*(+EbW^!ny@~#hm^ijj|^`-gwjP?k{*NRowU1Sn7p^52dE}jk+C7A`6px8a}L- z9jkAj2P@yym>-Pptgq7g(9jKy?LHX$(VW$GD+H&l9^II6>z=*g`vS%1XC3@H-uJpB z#9E$~L<-{Pn^54v=g93v5oMi=*#lz{;Mf#(qL@VQUu)xDMT*w&f2>HD?n90|!HLup zZ=F|>fT-ef-s{~N@qt)(G$d>SIiIvMa1h{+PZvIZX&_8cH+{(4$I32k-TD)f&Bk=2 zV`0SOG_Vt&%NmIlE0xCR?T4fuDdGL!-@`1(Lt5l_Z*VMbE$~=ZaL)1Jl}qQtq|!N$ zG>7-Ip-y_1NjBP7h8(R+Ze1H3I`v9c+?)CIuy~wpEVQc6x@hx6x9RYfLu1ux2Sg+d z>y=gG(VsLf+F{>V>+H<(znt!w9?=aaSMJU?I2pl632H(LxNHB>) z)hkgKtlkJ=7s?)NikOIg5H|RiWR(h|P>Z}ntf}z1>O%Qt$7aK!tDOaSNvl0ixD|a# zOqn{V&4!82<-(z8svS=1+S{gp@|GrX7oFh4sRdBS9LYu-!y?#V^gpK}u}{PtyH7`Xo2|xniC*)67$3PHAju ztQU>sHYI#&*StqF3uvxe({nFK%DDqJEh?W=Fkf0c|1@yYe1x_FZvJMYFaFvbmzaFwr`%G@l0SoUej>B`<}3dcD^ zX{dWr%~Gt1MKd&Up;BPm!;%BD>eAx(-h6QCY|iz|8Z7s0XCcF{5sd`9B-f3%SDI{A z_Gdkjhm~*0UHY_h>hn`AB|=G}k;qAB{-o>t1t)I4jWJt0^AsP)xTH?=H_6gFl%b1I z>y1^?4*Fy{VCiGFK~p(ljb31Kveyyi?v>?`&D^DH&pUb*RjEBVYLiOA1&`we;2tBd zVHk%z>BGMu@VvA7y=RVHhJnW9#OB7kAr

8J~4hvAawE3Y>KjayrPUAEAK6;W_Av z;%MiFy$qbkkhlc=zCyU*17~?TXMZz$BY`eNjB-rN7Y>Q%ffj7ikanbsDS(G?F~YQEuh z<^jr|O0>F|aV=bB<4t8TK4bD;Yok|B>;A8@WAn}UArV&QyoDchBV8@ zU)s03Id>PxOSh}kl_>r}d1sSKNPm)H!gm_>YDdMgIeOOW)5fKh%e8KC4yKQ}_+xbY zE1)~Y^Ry8IDZ@IgsHm=oCP7?ixEyyMrclMtUryz#-DQ_`A2q64r1NCScgC~a$6i{{ zW^?m=&Qw|H=dahOb_l%Lm76+U_>NS*wOT1fW=s+ex4S8~v3A+On~CYX-T#ajm4K7c zy6RMx7wt5&C7rcj07qOO+Hyzga9@3y`kuwLZ~oLyR!z6VL}i#sW{I$cmuk2Aj(t)~ zz_-43jap;G40+;BbF3LV@=|Hcd1$H4L6=p!SpINS`4pbD-uAUR)lNGznHCgBO6b2Q zm18TKb4A_Bd4|I`OAKB4B33a)S6gZNjFnO#%1u{pzYYTNMKMfG9jr`E|GxVI_uP!I zM19LfhYeBoyYus8MPg6ksD*ZNL^GxQQ)cB3e~Oh5&ehCHpGT`{!s3q|Y-q@Cx*8dK ztyHS?82F38;Mm}$5r}$!+6K4#!EdGwI_?Zl^@oK!z;&K|I0tfSN1d%GJl9%!Dq_#9 z2gvvfLIb(`Oo3eYRB_p%v5U83r?e;Q42$-(rb|r4+~@4XY7a^drrX*K*VrqO&8$=m zwr#Qt?2mWS1qLw5Q|o!CHYZj!t0m= zgSYcr57obuLd;&WIWc5;#LdJ&x+c2|ON&_Emvg8+bKi_ofwa+idevb8B_)1}(8B|B z!9jZ#`<6d4IwnO7j<|- z#wCG+nLfT8La;t$6_)^9^Sa>>@Tv&+pgv@;tpnJU#ioJLFfEJm0Ea@>CDGAX9F#_*VW4O-5&@&jrx0EHpzA~3pi929}qK|s+M1b{+C>p&3*I);vD5dZWz_>j1CKmk|-3|}fMi1W{o6N5?H%_Z^pL}C#L zT?`J5Lts%j;QWt}3ysYIa*>CMM8I^uQNo)T0uT;BEs2+@0KlpS5DmeUO(StxY$q1W zM<2p-3e3~|F>DJgC@P6dB9gc?02G1JAs~?i6v_#KB>+DR0*cTjAbyf(Q5p1*|4W*; ze876&Cf$<30qlpYc70n@yJ`O4THl&JjMc>i2Cps)0*Uf11P&>XMqTw2!1^{s@gn(p z(t!H$Jz@XuXZ(j`z+&+T4318L;*mg7p)oXFD2a|FL3K%FJcf?N;iy2l`Nc4YMdt>Q z*fe8LfJcBUAVF8T0&9GqD$QTzgS=?Gc>pMbB9YMVl;L<}!M_I!=T(fqqt%1|FFy2E z1%4_qfZn%0pmYJ15dNbSe&>r zyo0ZWRY(Pa1gflv#!h=1+tWI2R6XTe49g@V;<~mRYuPV13)XP9v8zot73AEEGMVUw zs--OpOymyS63;JSvQpLFOHZNtL(kjX>F&tiL3u#5RTMpa){0*^YfGRx`RdjyMR%kn z9z9yr>s+kqESp=@XjaRAS^%l!D;CJ+6R-CvKg?GnbVP)k|1{!c#+m#eiL8);9Rpee zR$G%)GVPLfZjEVq3%cIRj6ZXxDZ27<`t*yjz!Nd z^^C{7#gXMt0Md+`y}$)7UiFplp8Y*3*7=YtPS)Wz9u}Ii7q}}0;rYpD$rT!Pld&|{ zJjVI^WF((pg84<2IPpiUNIK+!`H$FG*%89RP^5EoCq=E`?IVj^)H>pdGkt!K}5PxC@6_|{;kL*;yt z4{wHhqZMS`dzxnSTHmpA*V<2>s!nOA1I?GOUrWnQzLII&B`ovc1D_)-t!bv_@p_l? zo}*qCn(FayPnM47K1sV7>@6$Pjc!7>Cj@<#>rV?GxedF)jK){!Jh~3rpK7h{?~dgz z_A~uY5h2+i*1aKtM+2{!*EhzQHwCzB%Egibv?WkW8P@st=fYZtXZW2gQsN!G<${Iv z#QP@)oudT^Db{A%ZDRJ0Tzw6y;yv8Z2Wc}!|mL( zw3)MQ_a&Di_Z=hSadDEPfpi~<9UUK?iGMJFY#e=>@$w-!8SzcuN)TZzL&w6XfvBP-JRPd7r?$T#k& z-pRS6La8Tu+)CtHsp~up_#ZXD^_SbYk%3m|k?-v2*n8l>fpv*x5z5<;-~?BcZBli0 z^}&NWIy&eJw&5=|4fqN0v^F3cB+#i1ElSSAL9;Pf>?m5o^x?Zo(D3g1-q2b*Na7k= zwm`N>r<>Y3*L3u<@fsK;(ehk)ZXJtnU5w!8&`gk-zW1g&d01nSxl_?IgPp1i@@~`7 zwWo#4-jXYDtpPs$GlZ-Bv6^VTix6POG;`@3zn xujj7xj4N%~VyBmJRa6hv5xlJEhieU9hJ)5BTjX1y2YK&Ptjz3)r6wN7{s$25?eG8q literal 0 HcmV?d00001 diff --git a/scwx-qt/scwx-qt.qrc b/scwx-qt/scwx-qt.qrc index 298ef828..0caeac1a 100644 --- a/scwx-qt/scwx-qt.qrc +++ b/scwx-qt/scwx-qt.qrc @@ -59,5 +59,6 @@ res/palettes/wct/ZDR.pal res/textures/lines/default-1x7.png res/textures/lines/test-pattern.png + res/textures/images/crosshairs-32.png From 400db66f096e108a51e66354a1bf0ec05e044bee Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 23 Nov 2023 06:24:32 -0600 Subject: [PATCH 07/15] Add texture types for resource loading and reference --- scwx-qt/scwx-qt.cmake | 6 ++- .../scwx/qt/manager/resource_manager.cpp | 18 +++++-- .../source/scwx/qt/types/texture_types.cpp | 50 +++++++++++++++++++ .../source/scwx/qt/types/texture_types.hpp | 39 +++++++++++++++ 4 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 scwx-qt/source/scwx/qt/types/texture_types.cpp create mode 100644 scwx-qt/source/scwx/qt/types/texture_types.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index f9b56876..6e38717e 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -172,7 +172,8 @@ set(HDR_TYPES source/scwx/qt/types/alert_types.hpp source/scwx/qt/types/qt_types.hpp source/scwx/qt/types/radar_product_record.hpp source/scwx/qt/types/text_event_key.hpp - source/scwx/qt/types/text_types.hpp) + source/scwx/qt/types/text_types.hpp + source/scwx/qt/types/texture_types.hpp) set(SRC_TYPES source/scwx/qt/types/alert_types.cpp source/scwx/qt/types/github_types.cpp source/scwx/qt/types/imgui_font.cpp @@ -180,7 +181,8 @@ set(SRC_TYPES source/scwx/qt/types/alert_types.cpp source/scwx/qt/types/map_types.cpp source/scwx/qt/types/radar_product_record.cpp source/scwx/qt/types/text_event_key.cpp - source/scwx/qt/types/text_types.cpp) + source/scwx/qt/types/text_types.cpp + source/scwx/qt/types/texture_types.cpp) set(HDR_UI source/scwx/qt/ui/about_dialog.hpp source/scwx/qt/ui/alert_dialog.hpp source/scwx/qt/ui/alert_dock_widget.hpp diff --git a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp index 3048fc6c..df4b4f77 100644 --- a/scwx-qt/source/scwx/qt/manager/resource_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/resource_manager.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -91,10 +92,19 @@ static void LoadFonts() static void LoadTextures() { util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance(); - textureAtlas.RegisterTexture("lines/default-1x7", - ":/res/textures/lines/default-1x7.png"); - textureAtlas.RegisterTexture("lines/test-pattern", - ":/res/textures/lines/test-pattern.png"); + + for (auto imageTexture : types::ImageTextureIterator()) + { + textureAtlas.RegisterTexture(GetTextureName(imageTexture), + GetTexturePath(imageTexture)); + } + + for (auto lineTexture : types::LineTextureIterator()) + { + textureAtlas.RegisterTexture(GetTextureName(lineTexture), + GetTexturePath(lineTexture)); + } + textureAtlas.BuildAtlas(2048, 2048); } diff --git a/scwx-qt/source/scwx/qt/types/texture_types.cpp b/scwx-qt/source/scwx/qt/types/texture_types.cpp new file mode 100644 index 00000000..348ae781 --- /dev/null +++ b/scwx-qt/source/scwx/qt/types/texture_types.cpp @@ -0,0 +1,50 @@ +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace types +{ + +struct TextureInfo +{ + std::string name_ {}; + std::string path_ {}; +}; + +static const std::unordered_map imageTextureInfo_ { + {ImageTexture::Crosshairs32, + {"images/crosshairs-32", ":/res/textures/images/crosshairs-32.png"}}}; + +static const std::unordered_map lineTextureInfo_ { + {LineTexture::Default1x7, + {"lines/default-1x7", ":/res/textures/lines/default-1x7.png"}}, + {LineTexture::TestPattern, + {"lines/test-pattern", ":/res/textures/lines/test-pattern.png"}}}; + +const std::string& GetTextureName(ImageTexture imageTexture) +{ + return imageTextureInfo_.at(imageTexture).name_; +} + +const std::string& GetTextureName(LineTexture lineTexture) +{ + return lineTextureInfo_.at(lineTexture).name_; +} + +const std::string& GetTexturePath(ImageTexture imageTexture) +{ + return imageTextureInfo_.at(imageTexture).path_; +} + +const std::string& GetTexturePath(LineTexture lineTexture) +{ + return lineTextureInfo_.at(lineTexture).path_; +} + +} // namespace types +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/types/texture_types.hpp b/scwx-qt/source/scwx/qt/types/texture_types.hpp new file mode 100644 index 00000000..7e1999b4 --- /dev/null +++ b/scwx-qt/source/scwx/qt/types/texture_types.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace types +{ + +enum class ImageTexture +{ + Crosshairs32 +}; +typedef scwx::util::Iterator + ImageTextureIterator; + +enum class LineTexture +{ + Default1x7, + TestPattern +}; +typedef scwx::util:: + Iterator + LineTextureIterator; + +const std::string& GetTextureName(ImageTexture imageTexture); +const std::string& GetTextureName(LineTexture lineTexture); +const std::string& GetTexturePath(ImageTexture imageTexture); +const std::string& GetTexturePath(LineTexture lineTexture); + +} // namespace types +} // namespace qt +} // namespace scwx From 18f500fe5f10ff75d66b77aff4144d61e27de6fc Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 23 Nov 2023 08:06:34 -0600 Subject: [PATCH 08/15] Position manager updates for tracking --- scwx-qt/source/scwx/qt/main/main_window.cpp | 15 +++-------- .../scwx/qt/manager/position_manager.cpp | 25 ++++++++++++++++--- .../scwx/qt/manager/position_manager.hpp | 6 ++++- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/scwx-qt/source/scwx/qt/main/main_window.cpp b/scwx-qt/source/scwx/qt/main/main_window.cpp index e480aeea..3f4765b4 100644 --- a/scwx-qt/source/scwx/qt/main/main_window.cpp +++ b/scwx-qt/source/scwx/qt/main/main_window.cpp @@ -35,7 +35,6 @@ #include #include -#include #include #include #include @@ -63,7 +62,6 @@ class MainWindowImpl : public QObject public: explicit MainWindowImpl(MainWindow* mainWindow) : - uuid_ {boost::uuids::random_generator()()}, mainWindow_ {mainWindow}, settings_ {}, activeMap_ {nullptr}, @@ -124,7 +122,7 @@ public: if (settings::GeneralSettings::Instance().track_location().GetValue()) { - positionManager_->TrackLocation(uuid_, true); + positionManager_->TrackLocation(true); } } ~MainWindowImpl() { threadPool_.join(); } @@ -155,8 +153,6 @@ public: boost::asio::thread_pool threadPool_ {1u}; - boost::uuids::uuid uuid_; - MainWindow* mainWindow_; QMapLibreGL::Settings settings_; map::MapProvider mapProvider_; @@ -872,13 +868,8 @@ void MainWindowImpl::ConnectOtherSignals() settings::GeneralSettings::Instance().track_location().StageValue( trackingEnabled); - for (std::size_t i = 0; i < maps_.size(); ++i) - { - // maps_[i]->SetTrackLocation(trackingEnabled); - } - - // Turn on location tracking (location manager) - positionManager_->TrackLocation(uuid_, trackingEnabled); + // Turn on location tracking + positionManager_->TrackLocation(trackingEnabled); }); connect(level2ProductsWidget_, &ui::Level2ProductsWidget::RadarProductSelected, diff --git a/scwx-qt/source/scwx/qt/manager/position_manager.cpp b/scwx-qt/source/scwx/qt/manager/position_manager.cpp index 15f3bd09..23357444 100644 --- a/scwx-qt/source/scwx/qt/manager/position_manager.cpp +++ b/scwx-qt/source/scwx/qt/manager/position_manager.cpp @@ -4,6 +4,7 @@ #include +#include #include namespace scwx @@ -19,7 +20,8 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class PositionManager::Impl { public: - explicit Impl(PositionManager* self) : self_ {self} + explicit Impl(PositionManager* self) : + self_ {self}, trackingUuid_ {boost::uuids::random_generator()()} { // TODO: macOS requires permission geoPositionInfoSource_ = @@ -55,6 +57,9 @@ public: PositionManager* self_; + boost::uuids::uuid trackingUuid_; + bool trackingEnabled_ {false}; + std::set uuids_ {}; QGeoPositionInfoSource* geoPositionInfoSource_ {}; @@ -69,15 +74,20 @@ QGeoPositionInfo PositionManager::position() const return p->position_; } -void PositionManager::TrackLocation(boost::uuids::uuid uuid, - bool trackingEnabled) +bool PositionManager::IsLocationTracked() +{ + return p->trackingEnabled_; +} + +void PositionManager::EnablePositionUpdates(boost::uuids::uuid uuid, + bool enabled) { if (p->geoPositionInfoSource_ == nullptr) { return; } - if (trackingEnabled) + if (enabled) { if (p->uuids_.empty()) { @@ -97,6 +107,13 @@ void PositionManager::TrackLocation(boost::uuids::uuid uuid, } } +void PositionManager::TrackLocation(bool trackingEnabled) +{ + p->trackingEnabled_ = trackingEnabled; + EnablePositionUpdates(p->trackingUuid_, trackingEnabled); + Q_EMIT LocationTrackingChanged(trackingEnabled); +} + std::shared_ptr PositionManager::Instance() { static std::weak_ptr positionManagerReference_ {}; diff --git a/scwx-qt/source/scwx/qt/manager/position_manager.hpp b/scwx-qt/source/scwx/qt/manager/position_manager.hpp index 815b76c9..367114c5 100644 --- a/scwx-qt/source/scwx/qt/manager/position_manager.hpp +++ b/scwx-qt/source/scwx/qt/manager/position_manager.hpp @@ -25,11 +25,15 @@ public: QGeoPositionInfo position() const; - void TrackLocation(boost::uuids::uuid uuid, bool trackingEnabled); + bool IsLocationTracked(); + + void EnablePositionUpdates(boost::uuids::uuid uuid, bool enabled); + void TrackLocation(bool trackingEnabled); static std::shared_ptr Instance(); signals: + void LocationTrackingChanged(bool trackingEnabled); void PositionUpdated(const QGeoPositionInfo& info); private: From ad4c402404f70236be07cde268d2f9b615d349c5 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 23 Nov 2023 08:19:43 -0600 Subject: [PATCH 09/15] Geo icons updates for rendering --- scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp | 38 +++++++---- scwx-qt/source/scwx/qt/gl/draw/geo_icons.hpp | 67 ++++++++++++-------- 2 files changed, 64 insertions(+), 41 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp index 1c8f59f9..5615d3f3 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp @@ -70,8 +70,7 @@ struct GeoIconDrawItem std::chrono::sys_time startTime_ {}; std::chrono::sys_time endTime_ {}; - bool visible_ {}; - boost::gil::rgba8_pixel_t modulate_ {}; + boost::gil::rgba8_pixel_t modulate_ {255, 255, 255, 255}; double latitude_ {}; double longitude_ {}; double x_ {}; @@ -118,6 +117,7 @@ public: std::shared_ptr context_; + bool visible_ {true}; bool dirty_ {false}; bool thresholded_ {false}; @@ -273,6 +273,11 @@ void GeoIcons::Initialize() void GeoIcons::Render(const QMapLibreGL::CustomLayerRenderParameters& params, bool textureAtlasChanged) { + if (!p->visible_) + { + return; + } + std::unique_lock lock {p->iconMutex_}; if (!p->currentIconList_.empty()) @@ -380,6 +385,11 @@ void IconInfo::UpdateTextureInfo() scaledHeight_ = iconHeight_ * yFactor; } +void GeoIcons::SetVisible(bool visible) +{ + p->visible_ = visible; +} + void GeoIcons::StartIconSheets() { // Clear the new buffer @@ -454,6 +464,18 @@ void GeoIcons::SetIconLocation(const std::shared_ptr& di, di->y_ = yOffset; } +void GeoIcons::SetIconLocation(const std::shared_ptr& di, + double latitude, + double longitude, + double xOffset, + double yOffset) +{ + di->latitude_ = latitude; + di->longitude_ = longitude; + di->x_ = xOffset; + di->y_ = yOffset; +} + void GeoIcons::SetIconAngle(const std::shared_ptr& di, units::angle::degrees angle) { @@ -472,12 +494,6 @@ void GeoIcons::SetIconHoverText(const std::shared_ptr& di, di->hoverText_ = text; } -void GeoIcons::SetIconVisible(const std::shared_ptr& di, - bool visible) -{ - di->visible_ = visible; -} - void GeoIcons::FinishIcons() { // Update buffers @@ -512,12 +528,6 @@ void GeoIcons::Impl::UpdateBuffers() for (auto& di : newIconList_) { - // Skip hidden icons - if (!di->visible_) - { - continue; - } - auto it = currentIconSheets_.find(di->iconSheet_); if (it == currentIconSheets_.cend()) { diff --git a/scwx-qt/source/scwx/qt/gl/draw/geo_icons.hpp b/scwx-qt/source/scwx/qt/gl/draw/geo_icons.hpp index 40482cc7..9d4bc9a7 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/geo_icons.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/geo_icons.hpp @@ -42,6 +42,13 @@ public: const QPointF& mouseGlobalPos, const glm::vec2& mouseCoords) override; + /** + * Sets the visibility of the geo icons. + * + * @param [in] visible Icon visibility + */ + void SetVisible(bool visible); + /** * Resets and prepares the draw item for adding a new set of icon sheets. */ @@ -91,24 +98,39 @@ public: * @param [in] iconSheet The name of the icon sheet in the texture atlas * @param [in] iconIndex The zero-based index of the icon in the icon sheet */ - void SetIconTexture(const std::shared_ptr& di, - const std::string& iconSheet, - std::size_t iconIndex); + static void SetIconTexture(const std::shared_ptr& di, + const std::string& iconSheet, + std::size_t iconIndex); /** * Sets the location of a geo icon. * * @param [in] di Geo icon draw item - * @param [in] latitude The latitude of the geo icon. - * @param [in] longitude The longitude of the geo icon. - * @param [in] xOffset The x-offset of the geo icon. Default is 0. - * @param [in] yOffset The y-offset of the geo icon. Default is 0. + * @param [in] latitude The latitude of the geo icon in degrees. + * @param [in] longitude The longitude of the geo icon in degrees. + * @param [in] xOffset The x-offset of the geo icon in pixels. Default is 0. + * @param [in] yOffset The y-offset of the geo icon in pixels. Default is 0. */ - void SetIconLocation(const std::shared_ptr& di, - units::angle::degrees latitude, - units::angle::degrees longitude, - double xOffset = 0.0, - double yOffset = 0.0); + static void SetIconLocation(const std::shared_ptr& di, + units::angle::degrees latitude, + units::angle::degrees longitude, + double xOffset = 0.0, + double yOffset = 0.0); + + /** + * Sets the location of a geo icon. + * + * @param [in] di Geo icon draw item + * @param [in] latitude The latitude of the geo icon in degrees. + * @param [in] longitude The longitude of the geo icon in degrees. + * @param [in] xOffset The x-offset of the geo icon in pixels. Default is 0. + * @param [in] yOffset The y-offset of the geo icon in pixels. Default is 0. + */ + static void SetIconLocation(const std::shared_ptr& di, + double latitude, + double longitude, + double xOffset = 0.0, + double yOffset = 0.0); /** * Sets the angle of a geo icon. @@ -116,8 +138,8 @@ public: * @param [in] di Geo icon draw item * @param [in] angle Angle in degrees */ - void SetIconAngle(const std::shared_ptr& di, - units::angle::degrees angle); + static void SetIconAngle(const std::shared_ptr& di, + units::angle::degrees angle); /** * Sets the modulate color of a geo icon. @@ -125,8 +147,8 @@ public: * @param [in] di Geo icon draw item * @param [in] modulate Modulate color */ - void SetIconModulate(const std::shared_ptr& di, - boost::gil::rgba8_pixel_t modulate); + static void SetIconModulate(const std::shared_ptr& di, + boost::gil::rgba8_pixel_t modulate); /** * Sets the hover text of a geo icon. @@ -134,17 +156,8 @@ public: * @param [in] di Geo icon draw item * @param [in] text Hover text */ - void SetIconHoverText(const std::shared_ptr& di, - const std::string& text); - - /** - * Sets the visibility of a geo icon. - * - * @param [in] di Geo icon draw item - * @param [in] visible Icon visibility - */ - void SetIconVisible(const std::shared_ptr& di, - bool visible); + static void SetIconHoverText(const std::shared_ptr& di, + const std::string& text); /** * Finalizes the draw item after adding new icons. From 320857328c0573fb9d2e89b8985e64d08328ca05 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 23 Nov 2023 08:20:02 -0600 Subject: [PATCH 10/15] Update the map widget when requested by a layer --- scwx-qt/source/scwx/qt/map/generic_layer.hpp | 3 +++ scwx-qt/source/scwx/qt/map/map_widget.cpp | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/scwx-qt/source/scwx/qt/map/generic_layer.hpp b/scwx-qt/source/scwx/qt/map/generic_layer.hpp index 239d271b..355680b8 100644 --- a/scwx-qt/source/scwx/qt/map/generic_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/generic_layer.hpp @@ -45,6 +45,9 @@ public: const QPointF& mouseGlobalPos, const glm::vec2& mouseCoords); +signals: + void NeedsRendering(); + protected: std::shared_ptr context() const; diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 7c543bdd..a897748b 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -969,6 +969,11 @@ void MapWidgetImpl::AddLayer(const std::string& id, layerList_.push_back(id); genericLayers_.push_back(layer); + + connect(layer.get(), + &GenericLayer::NeedsRendering, + widget_, + [this]() { widget_->update(); }); } catch (const std::exception&) { From c91b1c9a65cd4be1555d3bc03a64c02084ad7d92 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 23 Nov 2023 08:21:46 -0600 Subject: [PATCH 11/15] Remove unused includes from overlay layer --- scwx-qt/source/scwx/qt/map/overlay_layer.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/scwx-qt/source/scwx/qt/map/overlay_layer.cpp b/scwx-qt/source/scwx/qt/map/overlay_layer.cpp index c0577063..4656f2bf 100644 --- a/scwx-qt/source/scwx/qt/map/overlay_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/overlay_layer.cpp @@ -1,25 +1,15 @@ #include #include -#include #include #include #include -#include #if defined(_MSC_VER) # pragma warning(push, 0) #endif -#include -#include -#include -#include -#include -#include -#include #include -#include #if !defined(_MSC_VER) # include From 9590e2cc5288c062db5c8a1f6d912a151a5899e7 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 23 Nov 2023 08:22:19 -0600 Subject: [PATCH 12/15] Add location icon to overlay layer --- scwx-qt/source/scwx/qt/map/overlay_layer.cpp | 72 +++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/scwx-qt/source/scwx/qt/map/overlay_layer.cpp b/scwx-qt/source/scwx/qt/map/overlay_layer.cpp index 4656f2bf..3f7377b4 100644 --- a/scwx-qt/source/scwx/qt/map/overlay_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/overlay_layer.cpp @@ -1,5 +1,8 @@ #include +#include #include +#include +#include #include #include @@ -10,6 +13,7 @@ #endif #include +#include #if !defined(_MSC_VER) # include @@ -34,13 +38,24 @@ class OverlayLayerImpl public: explicit OverlayLayerImpl(std::shared_ptr context) : activeBoxOuter_ {std::make_shared(context)}, - activeBoxInner_ {std::make_shared(context)} + activeBoxInner_ {std::make_shared(context)}, + icons_ {std::make_shared(context)}, + locationIconName_ { + types::GetTextureName(types::ImageTexture::Crosshairs32)} { } ~OverlayLayerImpl() = default; + std::shared_ptr positionManager_ { + manager::PositionManager::Instance()}; + QGeoPositionInfo currentPosition_ {}; + std::shared_ptr activeBoxOuter_; std::shared_ptr activeBoxInner_; + std::shared_ptr icons_; + + const std::string& locationIconName_; + std::shared_ptr locationIcon_ {}; std::string sweepTimeString_ {}; bool sweepTimeNeedsUpdate_ {true}; @@ -52,6 +67,7 @@ OverlayLayer::OverlayLayer(std::shared_ptr context) : { AddDrawItem(p->activeBoxOuter_); AddDrawItem(p->activeBoxInner_); + AddDrawItem(p->icons_); p->activeBoxOuter_->SetPosition(0.0f, 0.0f); } @@ -73,6 +89,42 @@ void OverlayLayer::Initialize() this, &OverlayLayer::UpdateSweepTimeNextFrame); } + + p->currentPosition_ = p->positionManager_->position(); + auto coordinate = p->currentPosition_.coordinate(); + + p->icons_->StartIconSheets(); + p->icons_->AddIconSheet(p->locationIconName_); + p->icons_->FinishIconSheets(); + + p->icons_->StartIcons(); + p->locationIcon_ = p->icons_->AddIcon(); + gl::draw::GeoIcons::SetIconTexture( + p->locationIcon_, p->locationIconName_, 0); + gl::draw::GeoIcons::SetIconLocation( + p->locationIcon_, coordinate.latitude(), coordinate.longitude()); + p->icons_->FinishIcons(); + + connect(p->positionManager_.get(), + &manager::PositionManager::LocationTrackingChanged, + this, + [this]() { Q_EMIT NeedsRendering(); }); + connect(p->positionManager_.get(), + &manager::PositionManager::PositionUpdated, + this, + [this](const QGeoPositionInfo& position) + { + auto coordinate = position.coordinate(); + if (position.isValid() && + p->currentPosition_.coordinate() != coordinate) + { + gl::draw::GeoIcons::SetIconLocation(p->locationIcon_, + coordinate.latitude(), + coordinate.longitude()); + Q_EMIT NeedsRendering(); + } + p->currentPosition_ = position; + }); } void OverlayLayer::Render( @@ -85,6 +137,9 @@ void OverlayLayer::Render( context()->set_render_parameters(params); + // Set OpenGL blend mode for transparency + gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + p->sweepTimePicked_ = false; if (p->sweepTimeNeedsUpdate_ && radarProductView != nullptr) @@ -117,6 +172,10 @@ void OverlayLayer::Render( p->activeBoxInner_->SetBorder(1.0f * pixelRatio, {255, 255, 255, 255}); } + // Location Icon + p->icons_->SetVisible(p->currentPosition_.isValid() && + p->positionManager_->IsLocationTracked()); + DrawLayer::Render(params); if (radarProductView != nullptr) @@ -198,6 +257,17 @@ void OverlayLayer::Deinitialize() this, &OverlayLayer::UpdateSweepTimeNextFrame); } + + disconnect(p->positionManager_.get(), + &manager::PositionManager::LocationTrackingChanged, + this, + nullptr); + disconnect(p->positionManager_.get(), + &manager::PositionManager::PositionUpdated, + this, + nullptr); + + p->locationIcon_ = nullptr; } bool OverlayLayer::RunMousePicking( From cf0b2688227942f8f41fd8f4bb356e0b26991a8c Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 23 Nov 2023 10:53:27 -0600 Subject: [PATCH 13/15] Fixing geo icon size --- scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp index 5615d3f3..712bdc44 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp @@ -350,23 +350,20 @@ void IconInfo::UpdateTextureInfo() { columns_ = texture_.size_.x / iconWidth_; rows_ = texture_.size_.y / iconHeight_; - - if (hotX_ == -1 || hotY_ == -1) - { - hotX_ = static_cast(iconWidth_ / 2); - hotY_ = static_cast(iconHeight_ / 2); - } } else { columns_ = 1u; rows_ = 1u; - if (hotX_ == -1 || hotY_ == -1) - { - hotX_ = static_cast(texture_.size_.x / 2); - hotY_ = static_cast(texture_.size_.y / 2); - } + iconWidth_ = static_cast(texture_.size_.x); + iconHeight_ = static_cast(texture_.size_.y); + } + + if (hotX_ == -1 || hotY_ == -1) + { + hotX_ = static_cast(iconWidth_ / 2); + hotY_ = static_cast(iconHeight_ / 2); } numIcons_ = columns_ * rows_; From 7057f14b22ebd8b0c6fee81343da4d09ea762ba5 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 23 Nov 2023 22:16:00 -0600 Subject: [PATCH 14/15] Shrinking location crosshairs to 24x24, rotating 45 degrees --- scwx-qt/res/textures/images/crosshairs-24.png | Bin 0 -> 1054 bytes scwx-qt/scwx-qt.qrc | 2 +- scwx-qt/source/scwx/qt/map/overlay_layer.cpp | 4 +++- scwx-qt/source/scwx/qt/types/texture_types.cpp | 4 ++-- scwx-qt/source/scwx/qt/types/texture_types.hpp | 5 +++-- 5 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 scwx-qt/res/textures/images/crosshairs-24.png diff --git a/scwx-qt/res/textures/images/crosshairs-24.png b/scwx-qt/res/textures/images/crosshairs-24.png new file mode 100644 index 0000000000000000000000000000000000000000..f2a7ae2d4b2f2a3b8a02d4be1ac8acbe32888fba GIT binary patch literal 1054 zcmV+(1mXLMP)EX>4Tx04R}tkv&MmKpe$iQ>CI62RoGFkfC+5AS&W0RV;#q(pG5I!Q|2}XktiG zTpR`0f`cE6RRU7TlmpZjwRDn*k4K9P8q>4rtTPCUJ7 z>74h8L#!ex#OK6g23?T&k?XR{Z=A~x3p_Jqn?%w`A)9&vFjy`g-ef3&{00006VoOIv0RI600RN!9r;`8x010qNS#tmY z4c7nw4c7reD4Tcy00J&aL_t(Y$HkVtPUAoj$A3q6D?yve;=1&~DO@2%>39J|5Klnu ziU)9w-yo~dBAOuWT9!acs-)y1gOnytklUPcSI!k@BVR6o6CrnhX$sHm&aCGfUdKLg z0T|#Jker?Yo6Gij2fhFurIdtWD7{`!CX>l;)bIBt3`0>$3DC*!{DGYnycY1FwLW=z zdZJpba&vQo<2YnlMwVqbj>Gl!HPvdB$Hzys)+fLNaQfEE&ppqRBuT_tE3WGb&;^cx z+reNUgTX+6+nm>RU01BNk|c?Eo+rThI^`|T^JF|8OArJCjDUJcepNDXRpQryuR##V zcswqQoUZcuB1w`$`8}YP&a~q=mNl~$*OfF)i|pD5xCp~gj4?$(H$~SN zBViZ{aIpg%9vmDHMG-4}`yRBTD56%Y0UQE6PbQP1LT~d7X|M6~IvnM9u5ulc>zsYc zKghD|<@9_$r_pHq04l(@H~GhW YpGJ_uNN)f^djJ3c07*qoM6N<$g8uX3(f|Me literal 0 HcmV?d00001 diff --git a/scwx-qt/scwx-qt.qrc b/scwx-qt/scwx-qt.qrc index 0caeac1a..3e7e55dd 100644 --- a/scwx-qt/scwx-qt.qrc +++ b/scwx-qt/scwx-qt.qrc @@ -59,6 +59,6 @@ res/palettes/wct/ZDR.pal res/textures/lines/default-1x7.png res/textures/lines/test-pattern.png - res/textures/images/crosshairs-32.png + res/textures/images/crosshairs-24.png diff --git a/scwx-qt/source/scwx/qt/map/overlay_layer.cpp b/scwx-qt/source/scwx/qt/map/overlay_layer.cpp index 3f7377b4..89c6248b 100644 --- a/scwx-qt/source/scwx/qt/map/overlay_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/overlay_layer.cpp @@ -41,7 +41,7 @@ public: activeBoxInner_ {std::make_shared(context)}, icons_ {std::make_shared(context)}, locationIconName_ { - types::GetTextureName(types::ImageTexture::Crosshairs32)} + types::GetTextureName(types::ImageTexture::Crosshairs24)} { } ~OverlayLayerImpl() = default; @@ -101,6 +101,8 @@ void OverlayLayer::Initialize() p->locationIcon_ = p->icons_->AddIcon(); gl::draw::GeoIcons::SetIconTexture( p->locationIcon_, p->locationIconName_, 0); + gl::draw::GeoIcons::SetIconAngle(p->locationIcon_, + units::angle::degrees {45.0}); gl::draw::GeoIcons::SetIconLocation( p->locationIcon_, coordinate.latitude(), coordinate.longitude()); p->icons_->FinishIcons(); diff --git a/scwx-qt/source/scwx/qt/types/texture_types.cpp b/scwx-qt/source/scwx/qt/types/texture_types.cpp index 348ae781..5f2123ad 100644 --- a/scwx-qt/source/scwx/qt/types/texture_types.cpp +++ b/scwx-qt/source/scwx/qt/types/texture_types.cpp @@ -16,8 +16,8 @@ struct TextureInfo }; static const std::unordered_map imageTextureInfo_ { - {ImageTexture::Crosshairs32, - {"images/crosshairs-32", ":/res/textures/images/crosshairs-32.png"}}}; + {ImageTexture::Crosshairs24, + {"images/crosshairs-24", ":/res/textures/images/crosshairs-24.png"}}}; static const std::unordered_map lineTextureInfo_ { {LineTexture::Default1x7, diff --git a/scwx-qt/source/scwx/qt/types/texture_types.hpp b/scwx-qt/source/scwx/qt/types/texture_types.hpp index 7e1999b4..c40f7141 100644 --- a/scwx-qt/source/scwx/qt/types/texture_types.hpp +++ b/scwx-qt/source/scwx/qt/types/texture_types.hpp @@ -13,11 +13,12 @@ namespace types enum class ImageTexture { + Crosshairs24, Crosshairs32 }; typedef scwx::util::Iterator + ImageTexture::Crosshairs24, + ImageTexture::Crosshairs24> ImageTextureIterator; enum class LineTexture From ad4ae80da88bc601e226b308514e391140ee3da1 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 23 Nov 2023 22:17:43 -0600 Subject: [PATCH 15/15] Fix first time display of location icon --- scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp | 16 +++++++++++++--- scwx-qt/source/scwx/qt/map/overlay_layer.cpp | 1 + 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp index 712bdc44..fb5dad75 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp @@ -120,6 +120,7 @@ public: bool visible_ {true}; bool dirty_ {false}; bool thresholded_ {false}; + bool lastTextureAtlasChanged_ {false}; std::chrono::system_clock::time_point selectedTime_ {}; @@ -275,6 +276,11 @@ void GeoIcons::Render(const QMapLibreGL::CustomLayerRenderParameters& params, { if (!p->visible_) { + if (textureAtlasChanged) + { + p->lastTextureAtlasChanged_ = true; + } + return; } @@ -504,8 +510,8 @@ void GeoIcons::FinishIcons() p->currentIntegerBuffer_.swap(p->newIntegerBuffer_); p->currentHoverIcons_.swap(p->newHoverIcons_); - // Clear the new buffers - p->newIconList_.clear(); + // Clear the new buffers, except the full icon list (used to update buffers + // without re-adding icons) p->newValidIconList_.clear(); p->newIconBuffer_.clear(); p->newIntegerBuffer_.clear(); @@ -522,6 +528,8 @@ void GeoIcons::Impl::UpdateBuffers() newIntegerBuffer_.clear(); newIntegerBuffer_.reserve(newIconList_.size() * kVerticesPerRectangle * kIntegersPerVertex_); + newValidIconList_.clear(); + newHoverIcons_.clear(); for (auto& di : newIconList_) { @@ -737,7 +745,7 @@ void GeoIcons::Impl::Update(bool textureAtlasChanged) gl::OpenGLFunctions& gl = context_->gl(); // If the texture atlas has changed - if (dirty_ || textureAtlasChanged) + if (dirty_ || textureAtlasChanged || lastTextureAtlasChanged_) { // Update texture coordinates for (auto& iconSheet : currentIconSheets_) @@ -754,6 +762,8 @@ void GeoIcons::Impl::Update(bool textureAtlasChanged) sizeof(float) * textureBuffer_.size(), textureBuffer_.data(), GL_DYNAMIC_DRAW); + + lastTextureAtlasChanged_ = false; } // If buffers need updating diff --git a/scwx-qt/source/scwx/qt/map/overlay_layer.cpp b/scwx-qt/source/scwx/qt/map/overlay_layer.cpp index 89c6248b..fdf17463 100644 --- a/scwx-qt/source/scwx/qt/map/overlay_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/overlay_layer.cpp @@ -123,6 +123,7 @@ void OverlayLayer::Initialize() gl::draw::GeoIcons::SetIconLocation(p->locationIcon_, coordinate.latitude(), coordinate.longitude()); + p->icons_->FinishIcons(); Q_EMIT NeedsRendering(); } p->currentPosition_ = position;