From 9c8d851cf39de5a05fbf4b578fc204989c6d85ad Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sat, 13 Jan 2024 00:37:26 -0600 Subject: [PATCH] Add icons for (x, y) placement on the map --- scwx-qt/gl/texture2d_array.vert | 26 + scwx-qt/scwx-qt.cmake | 5 + scwx-qt/scwx-qt.qrc | 1 + scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp | 78 +-- scwx-qt/source/scwx/qt/gl/draw/icons.cpp | 620 +++++++++++++++++++ scwx-qt/source/scwx/qt/gl/draw/icons.hpp | 154 +++++ scwx-qt/source/scwx/qt/types/icon_types.cpp | 52 ++ scwx-qt/source/scwx/qt/types/icon_types.hpp | 46 ++ 8 files changed, 909 insertions(+), 73 deletions(-) create mode 100644 scwx-qt/gl/texture2d_array.vert create mode 100644 scwx-qt/source/scwx/qt/gl/draw/icons.cpp create mode 100644 scwx-qt/source/scwx/qt/gl/draw/icons.hpp create mode 100644 scwx-qt/source/scwx/qt/types/icon_types.cpp create mode 100644 scwx-qt/source/scwx/qt/types/icon_types.hpp diff --git a/scwx-qt/gl/texture2d_array.vert b/scwx-qt/gl/texture2d_array.vert new file mode 100644 index 00000000..cd250c55 --- /dev/null +++ b/scwx-qt/gl/texture2d_array.vert @@ -0,0 +1,26 @@ +#version 330 core + +#define DEG2RAD 0.0174532925199432957692369055556f + +layout (location = 0) in vec2 aVertex; +layout (location = 1) in vec2 aXYOffset; +layout (location = 2) in vec3 aTexCoord; +layout (location = 3) in vec4 aModulate; +layout (location = 4) in float aAngleDeg; + +uniform mat4 uMVPMatrix; + +smooth out vec3 texCoord; +smooth out vec4 color; + +void main() +{ + // Rotate clockwise + float angle = aAngleDeg * DEG2RAD; + mat2 rotate = mat2(cos(angle), -sin(angle), + sin(angle), cos(angle)); + + gl_Position = uMVPMatrix * vec4(aVertex + rotate * aXYOffset, 0.0f, 1.0f); + texCoord = aTexCoord; + color = aModulate; +} diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 1af0b0ae..6ee67f88 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -62,6 +62,7 @@ set(SRC_GL source/scwx/qt/gl/gl_context.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/icons.hpp source/scwx/qt/gl/draw/placefile_icons.hpp source/scwx/qt/gl/draw/placefile_images.hpp source/scwx/qt/gl/draw/placefile_lines.hpp @@ -72,6 +73,7 @@ set(HDR_GL_DRAW source/scwx/qt/gl/draw/draw_item.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/icons.cpp source/scwx/qt/gl/draw/placefile_icons.cpp source/scwx/qt/gl/draw/placefile_images.cpp source/scwx/qt/gl/draw/placefile_lines.cpp @@ -176,6 +178,7 @@ set(SRC_SETTINGS source/scwx/qt/settings/audio_settings.cpp set(HDR_TYPES source/scwx/qt/types/alert_types.hpp source/scwx/qt/types/font_types.hpp source/scwx/qt/types/github_types.hpp + source/scwx/qt/types/icon_types.hpp source/scwx/qt/types/imgui_font.hpp source/scwx/qt/types/layer_types.hpp source/scwx/qt/types/location_types.hpp @@ -188,6 +191,7 @@ set(HDR_TYPES source/scwx/qt/types/alert_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/icon_types.cpp source/scwx/qt/types/imgui_font.cpp source/scwx/qt/types/layer_types.cpp source/scwx/qt/types/location_types.cpp @@ -315,6 +319,7 @@ set(SHADER_FILES gl/color.frag gl/texture1d.vert gl/texture2d.frag gl/texture2d_array.frag + gl/texture2d_array.vert gl/threshold.geom) set(CMAKE_FILES scwx-qt.cmake) diff --git a/scwx-qt/scwx-qt.qrc b/scwx-qt/scwx-qt.qrc index c949ddc3..eeed2a70 100644 --- a/scwx-qt/scwx-qt.qrc +++ b/scwx-qt/scwx-qt.qrc @@ -11,6 +11,7 @@ gl/texture1d.vert gl/texture2d.frag gl/texture2d_array.frag + gl/texture2d_array.vert gl/threshold.geom res/audio/wikimedia/Emergency_Alert_System_Attention_Signal_20s.ogg res/config/radar_sites.json 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 e3c7b0a3..8eae57e9 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/geo_icons.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -34,36 +35,6 @@ static constexpr std::size_t kTextureBufferLength = // 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_ {}; @@ -126,8 +97,9 @@ public: std::mutex iconMutex_; - boost::unordered_flat_map currentIconSheets_ {}; - boost::unordered_flat_map newIconSheets_ {}; + boost::unordered_flat_map + currentIconSheets_ {}; + boost::unordered_flat_map newIconSheets_ {}; std::vector> currentIconList_ {}; std::vector> newIconList_ {}; @@ -348,46 +320,6 @@ void GeoIcons::Deinitialize() 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_; - } - else - { - columns_ = 1u; - rows_ = 1u; - - 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_; - - // 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::SetVisible(bool visible) { p->visible_ = visible; @@ -408,7 +340,7 @@ void GeoIcons::AddIconSheet(const std::string& name, // Populate icon sheet map p->newIconSheets_.emplace(std::piecewise_construct, std::tuple {name}, - std::forward_as_tuple(IconInfo { + std::forward_as_tuple(types::IconInfo { name, iconWidth, iconHeight, hotX, hotY})); } diff --git a/scwx-qt/source/scwx/qt/gl/draw/icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/icons.cpp new file mode 100644 index 00000000..8ccee650 --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/icons.cpp @@ -0,0 +1,620 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +static const std::string logPrefix_ = "scwx::qt::gl::draw::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; + +struct IconDrawItem +{ + boost::gil::rgba8_pixel_t modulate_ {255, 255, 255, 255}; + double x_ {}; + double y_ {}; + units::degrees angle_ {}; + std::string iconSheet_ {}; + std::size_t iconIndex_ {}; + std::string hoverText_ {}; +}; + +class Icons::Impl +{ +public: + struct IconHoverEntry + { + std::shared_ptr di_; + + 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), + vao_ {GL_INVALID_INDEX}, + vbo_ {GL_INVALID_INDEX}, + numVertices_ {0} + { + } + + ~Impl() {} + + void UpdateBuffers(); + void UpdateTextureBuffer(); + void Update(bool textureAtlasChanged); + + std::shared_ptr context_; + + bool visible_ {true}; + bool dirty_ {false}; + bool lastTextureAtlasChanged_ {false}; + + 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 newIconBuffer_ {}; + + std::vector textureBuffer_ {}; + + std::vector currentHoverIcons_ {}; + std::vector newHoverIcons_ {}; + + std::shared_ptr shaderProgram_; + GLint uMVPMatrixLocation_; + + GLuint vao_; + std::array vbo_; + + GLsizei numVertices_; +}; + +Icons::Icons(const std::shared_ptr& context) : + DrawItem(context->gl()), p(std::make_unique(context)) +{ +} +Icons::~Icons() = default; + +Icons::Icons(Icons&&) noexcept = default; +Icons& Icons::operator=(Icons&&) noexcept = default; + +void Icons::Initialize() +{ + gl::OpenGLFunctions& gl = p->context_->gl(); + + p->shaderProgram_ = p->context_->GetShaderProgram( + {{GL_VERTEX_SHADER, ":/gl/texture2d_array.vert"}, + {GL_FRAGMENT_SHADER, ":/gl/texture2d_array.frag"}}); + + p->uMVPMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMVPMatrix"); + + 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); + + // aVertex + gl.glVertexAttribPointer(0, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_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); + + p->dirty_ = true; +} + +void Icons::Render(const QMapLibreGL::CustomLayerRenderParameters& params, + bool textureAtlasChanged) +{ + if (!p->visible_) + { + if (textureAtlasChanged) + { + p->lastTextureAtlasChanged_ = true; + } + + return; + } + + 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(); + UseDefaultProjection(params, p->uMVPMatrixLocation_); + + // 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 Icons::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->textureBuffer_.clear(); +} + +void Icons::SetVisible(bool visible) +{ + p->visible_ = visible; +} + +void Icons::StartIconSheets() +{ + // Clear the new buffer + p->newIconSheets_.clear(); +} + +void Icons::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(types::IconInfo { + name, iconWidth, iconHeight, hotX, hotY})); +} + +void Icons::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 Icons::StartIcons() +{ + // Clear the new buffer + p->newIconList_.clear(); + p->newValidIconList_.clear(); + p->newIconBuffer_.clear(); + p->newHoverIcons_.clear(); +} + +std::shared_ptr Icons::AddIcon() +{ + return p->newIconList_.emplace_back(std::make_shared()); +} + +void Icons::SetIconTexture(const std::shared_ptr& di, + const std::string& iconSheet, + std::size_t iconIndex) +{ + di->iconSheet_ = iconSheet; + di->iconIndex_ = iconIndex; +} + +void Icons::SetIconLocation(const std::shared_ptr& di, + double x, + double y) +{ + di->x_ = x; + di->y_ = y; +} + +void Icons::SetIconAngle(const std::shared_ptr& di, + units::angle::degrees angle) +{ + di->angle_ = angle; +} + +void Icons::SetIconModulate(const std::shared_ptr& di, + boost::gil::rgba8_pixel_t modulate) +{ + di->modulate_ = modulate; +} + +void Icons::SetIconHoverText(const std::shared_ptr& di, + const std::string& text) +{ + di->hoverText_ = text; +} + +void Icons::FinishIcons() +{ + // Update buffers + p->UpdateBuffers(); + + std::unique_lock lock {p->iconMutex_}; + + // Swap buffers + p->currentIconList_.swap(p->newValidIconList_); + p->currentIconBuffer_.swap(p->newIconBuffer_); + p->currentHoverIcons_.swap(p->newHoverIcons_); + + // Clear the new buffers, except the full icon list (used to update buffers + // without re-adding icons) + p->newValidIconList_.clear(); + p->newIconBuffer_.clear(); + p->newHoverIcons_.clear(); + + // Mark the draw item dirty + p->dirty_ = true; +} + +void Icons::Impl::UpdateBuffers() +{ + newIconBuffer_.clear(); + newIconBuffer_.reserve(newIconList_.size() * kIconBufferLength); + newValidIconList_.clear(); + newHoverIcons_.clear(); + + for (auto& di : newIconList_) + { + 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); + + // 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(-hx); + const float rx = std::roundf(lx + iw); + const float ty = std::roundf(+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 + x, y, lx, by, mc0, mc1, mc2, mc3, a, // BL + x, y, lx, ty, mc0, mc1, mc2, mc3, a, // TL + x, y, rx, by, mc0, mc1, mc2, mc3, a, // BR + x, y, rx, by, mc0, mc1, mc2, mc3, a, // BR + x, y, rx, ty, mc0, mc1, mc2, mc3, a, // TR + x, y, lx, ty, mc0, mc1, mc2, mc3, a // TL + }); + + if (!di->hoverText_.empty()) + { + const units::angle::radians radians = angle; + + 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, otl, otr, obl, obr}); + } + } +} + +void Icons::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 Icons::Impl::Update(bool textureAtlasChanged) +{ + gl::OpenGLFunctions& gl = context_->gl(); + + // If the texture atlas has changed + if (dirty_ || textureAtlasChanged || lastTextureAtlasChanged_) + { + // 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); + + lastTextureAtlasChanged_ = false; + } + + // 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); + + numVertices_ = + static_cast(currentIconBuffer_.size() / kPointsPerVertex); + } + + dirty_ = false; +} + +bool Icons::RunMousePicking( + const QMapLibreGL::CustomLayerRenderParameters& params, + const QPointF& mouseLocalPos, + const QPointF& mouseGlobalPos, + const glm::vec2& /* mouseCoords */, + const common::Coordinate& /* mouseGeoCoords */) +{ + std::unique_lock lock {p->iconMutex_}; + + bool itemPicked = false; + + // Convert local coordinates to icon coordinates + glm::vec2 mouseLocalCoords {mouseLocalPos.x(), + params.height - mouseLocalPos.y()}; + + // For each pickable icon + auto it = std::find_if( // + std::execution::par_unseq, + p->currentHoverIcons_.crbegin(), + p->currentHoverIcons_.crend(), + [&mouseLocalCoords](const auto& icon) + { + // Initialize vertices + glm::vec2 bl = {static_cast(icon.di_->x_), + static_cast(icon.di_->y_)}; + glm::vec2 br = bl; + glm::vec2 tl = br; + glm::vec2 tr = tl; + + // Offset vertices + tl += icon.otl_; + bl += icon.obl_; + br += icon.obr_; + tr += icon.otr_; + + // Test point against polygon bounds + return util::maplibre::IsPointInPolygon({tl, bl, br, tr}, + mouseLocalCoords); + }); + + 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/icons.hpp b/scwx-qt/source/scwx/qt/gl/draw/icons.hpp new file mode 100644 index 00000000..7e3ac356 --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/icons.hpp @@ -0,0 +1,154 @@ +#pragma once + +#include +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +struct IconDrawItem; + +class Icons : public DrawItem +{ +public: + explicit Icons(const std::shared_ptr& context); + ~Icons(); + + Icons(const Icons&) = delete; + Icons& operator=(const Icons&) = delete; + + Icons(Icons&&) noexcept; + Icons& operator=(Icons&&) noexcept; + + 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, + const common::Coordinate& mouseGeoCoords) override; + + /** + * Sets the visibility of the icons. + * + * @param [in] visible Icon visibility + */ + void SetVisible(bool visible); + + /** + * Resets and prepares the draw item for adding a new set of icon sheets. + */ + void StartIconSheets(); + + /** + * Adds an icon sheet for drawing the 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 an icon to the internal draw list. + * + * @return Icon draw item + */ + std::shared_ptr AddIcon(); + + /** + * Sets the texture of an icon. + * + * @param [in] di 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 + */ + static void SetIconTexture(const std::shared_ptr& di, + const std::string& iconSheet, + std::size_t iconIndex); + + /** + * Sets the location of an icon. + * + * @param [in] di Icon draw item + * @param [in] x The x location of the icon in pixels. + * @param [in] y The y location of the icon in pixels. + */ + static void + SetIconLocation(const std::shared_ptr& di, double x, double y); + + /** + * Sets the angle of an icon. + * + * @param [in] di Icon draw item + * @param [in] angle Angle in degrees + */ + static void SetIconAngle(const std::shared_ptr& di, + units::angle::degrees angle); + + /** + * Sets the modulate color of an icon. + * + * @param [in] di Icon draw item + * @param [in] modulate Modulate color + */ + static void SetIconModulate(const std::shared_ptr& di, + boost::gil::rgba8_pixel_t modulate); + + /** + * Sets the hover text of an icon. + * + * @param [in] di Icon draw item + * @param [in] text Hover text + */ + static void SetIconHoverText(const std::shared_ptr& di, + const std::string& text); + + /** + * 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 diff --git a/scwx-qt/source/scwx/qt/types/icon_types.cpp b/scwx-qt/source/scwx/qt/types/icon_types.cpp new file mode 100644 index 00000000..8323ef83 --- /dev/null +++ b/scwx-qt/source/scwx/qt/types/icon_types.cpp @@ -0,0 +1,52 @@ +#include + +namespace scwx +{ +namespace qt +{ +namespace types +{ + +void IconInfo::UpdateTextureInfo() +{ + texture_ = util::TextureAtlas::Instance().GetTextureAttributes(iconSheet_); + + if (iconWidth_ > 0 && iconHeight_ > 0) + { + columns_ = texture_.size_.x / iconWidth_; + rows_ = texture_.size_.y / iconHeight_; + } + else + { + columns_ = 1u; + rows_ = 1u; + + 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_; + + // 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; +} + +} // namespace types +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/types/icon_types.hpp b/scwx-qt/source/scwx/qt/types/icon_types.hpp new file mode 100644 index 00000000..c6ae7abe --- /dev/null +++ b/scwx-qt/source/scwx/qt/types/icon_types.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace types +{ + +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_ {}; +}; + +} // namespace types +} // namespace qt +} // namespace scwx