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