From 8dfb9f1105d1224b47528e97324c6d64eed21827 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Mon, 28 Aug 2023 00:15:57 -0500 Subject: [PATCH] Placefile line hover in-work --- scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp | 15 +- scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp | 5 +- .../scwx/qt/gl/draw/placefile_lines.cpp | 181 ++++++++++++++++-- .../scwx/qt/gl/draw/placefile_lines.hpp | 3 + .../source/scwx/qt/gl/draw/placefile_text.cpp | 3 +- .../source/scwx/qt/gl/draw/placefile_text.hpp | 4 +- scwx-qt/source/scwx/qt/map/draw_layer.cpp | 5 +- scwx-qt/source/scwx/qt/map/draw_layer.hpp | 5 +- scwx-qt/source/scwx/qt/map/generic_layer.cpp | 3 +- scwx-qt/source/scwx/qt/map/generic_layer.hpp | 5 +- scwx-qt/source/scwx/qt/map/map_widget.cpp | 23 ++- scwx-qt/source/scwx/qt/map/map_widget.hpp | 2 + scwx-qt/source/scwx/qt/util/maplibre.cpp | 23 +++ scwx-qt/source/scwx/qt/util/maplibre.hpp | 2 + 14 files changed, 243 insertions(+), 36 deletions(-) diff --git a/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp b/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp index 56d9b574..e1e45dee 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/draw_item.cpp @@ -54,7 +54,8 @@ void DrawItem::Render(const QMapLibreGL::CustomLayerRenderParameters& params, } bool DrawItem::RunMousePicking( - const QMapLibreGL::CustomLayerRenderParameters& /* params */) + const QMapLibreGL::CustomLayerRenderParameters& /* params */, + const glm::vec2& /* mousePos */) { // By default, the draw item is not picked return false; @@ -97,17 +98,7 @@ void DrawItem::UseMapProjection( { OpenGLFunctions& gl = p->gl_; - // TODO: Refactor to utility class - const float scale = std::pow(2.0, params.zoom) * 2.0f * - mbgl::util::tileSize_D / mbgl::util::DEGREES_MAX; - const float xScale = scale / params.width; - const float yScale = scale / params.height; - - glm::mat4 uMVPMatrix(1.0f); - uMVPMatrix = glm::scale(uMVPMatrix, glm::vec3(xScale, yScale, 1.0f)); - uMVPMatrix = glm::rotate(uMVPMatrix, - glm::radians(params.bearing), - glm::vec3(0.0f, 0.0f, 1.0f)); + const glm::mat4 uMVPMatrix = util::maplibre::GetMapMatrix(params); gl.glUniform2fv(uMapScreenCoordLocation, 1, diff --git a/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp b/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp index 5e559d12..50704506 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/draw_item.hpp @@ -5,6 +5,7 @@ #include #include +#include namespace scwx { @@ -37,11 +38,13 @@ public: * @brief Run mouse picking on the draw item. * * @param [in] params Custom layer render parameters + * @param [in] mousePos Mouse cursor location in map screen coordinates * * @return true if the draw item was picked, otherwise false */ virtual bool - RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params); + RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, + const glm::vec2& mousePos); protected: void diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp index 6112d078..d94a0209 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp @@ -1,8 +1,12 @@ #include +#include +#include #include #include #include +#include + namespace scwx { namespace qt @@ -28,6 +32,15 @@ static const boost::gil::rgba8_pixel_t kBlack_ {0, 0, 0, 255}; class PlacefileLines::Impl { public: + struct LineHoverEntry + { + std::string hoverText_; + glm::vec2 p1_; + glm::vec2 p2_; + glm::mat2 rotate_; + float width_; + }; + explicit Impl(const std::shared_ptr& context) : context_ {context}, shaderProgram_ {nullptr}, @@ -46,9 +59,10 @@ public: void BufferLine(const gr::Placefile::LineDrawItem::Element& e1, const gr::Placefile::LineDrawItem::Element& e2, const float width, - const float angle, + const units::angle::degrees angle, const boost::gil::rgba8_pixel_t color, - const GLint threshold); + const GLint threshold, + const std::string& hoverText = {}); void UpdateBuffers(const std::shared_ptr& di); void Update(); @@ -68,6 +82,9 @@ public: std::vector newLinesBuffer_ {}; std::vector newThresholdBuffer_ {}; + std::vector currentHoverLines_ {}; + std::vector newHoverLines_ {}; + std::shared_ptr shaderProgram_; GLint uMVPMatrixLocation_; GLint uMapMatrixLocation_; @@ -213,6 +230,125 @@ void PlacefileLines::Deinitialize() p->currentLinesBuffer_.clear(); p->currentThresholdBuffer_.clear(); + p->currentHoverLines_.clear(); +} + +void DrawTooltip(const std::string& hoverText) +{ + // Get monospace font pointer + std::size_t fontSize = 16; + auto fontSizes = + manager::SettingsManager::general_settings().font_sizes().GetValue(); + if (fontSizes.size() > 1) + { + fontSize = fontSizes[1]; + } + else if (fontSizes.size() > 0) + { + fontSize = fontSizes[0]; + } + auto monospace = + manager::ResourceManager::Font(types::Font::Inconsolata_Regular); + auto monospaceFont = monospace->ImGuiFont(fontSize); + + ImGui::BeginTooltip(); + ImGui::PushFont(monospaceFont); + ImGui::TextUnformatted(hoverText.c_str()); + ImGui::PopFont(); + ImGui::EndTooltip(); +} + +bool IsPointInPolygon(const std::vector vertices, + const glm::vec2& point) +{ + bool inPolygon = true; + + // For each vertex, assume counterclockwise order + for (std::size_t i = 0; i < vertices.size(); ++i) + { + const auto& p1 = vertices[i]; + const auto& p2 = + (i == vertices.size() - 1) ? vertices[0] : vertices[i + 1]; + + // Test which side of edge point lies on + const float a = -(p2.y - p1.y); + const float b = p2.x - p1.x; + const float c = -(a * p1.x + b * p1.y); + const float d = a * point.x + b * point.y + c; + + // If d < 0, the point is on the right-hand side, and outside of the + // polygon + if (d < 0) + { + inPolygon = false; + break; + } + } + + return inPolygon; +} + +bool PlacefileLines::RunMousePicking( + const QMapLibreGL::CustomLayerRenderParameters& params, + const glm::vec2& mousePos) +{ + std::unique_lock lock {p->lineMutex_}; + + bool itemPicked = false; + + // Calculate map scale, remove width and height from original calculation + glm::vec2 scale = util::maplibre::GetMapScale(params); + scale = 1.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)); + + // For each pickable line + for (auto& line : p->currentHoverLines_) + { + // Initialize vertices + glm::vec2 bl = line.p1_; + glm::vec2 br = bl; + glm::vec2 tl = line.p2_; + glm::vec2 tr = tl; + + // Calculate offsets + // - Offset is half the line width (pixels) in each direction + // - Rotate the offset at each vertex + // - Multiply the offset by the map matrix + const float hw = line.width_ * 0.5f; + const glm::vec2 otl = + mapMatrix * + glm::vec4 {line.rotate_ * glm::vec2 {-hw, -hw}, 0.0f, 1.0f}; + const glm::vec2 obl = + mapMatrix * glm::vec4 {line.rotate_ * glm::vec2 {-hw, hw}, 0.0f, 1.0f}; + const glm::vec2 obr = + mapMatrix * glm::vec4 {line.rotate_ * glm::vec2 {hw, hw}, 0.0f, 1.0f}; + const glm::vec2 otr = + mapMatrix * glm::vec4 {line.rotate_ * glm::vec2 {hw, -hw}, 0.0f, 1.0f}; + + // Offset vertices + tl += otl; + bl += obl; + br += obr; + tr += otr; + + // TODO: X/Y offsets + + // Test point against polygon bounds + if (IsPointInPolygon({tl, bl, br, tr}, mousePos)) + { + itemPicked = true; + DrawTooltip(line.hoverText_); + break; + } + } + + return itemPicked; } void PlacefileLines::StartLines() @@ -220,6 +356,7 @@ void PlacefileLines::StartLines() // Clear the new buffers p->newLinesBuffer_.clear(); p->newThresholdBuffer_.clear(); + p->newHoverLines_.clear(); p->newNumLines_ = 0u; } @@ -241,10 +378,12 @@ void PlacefileLines::FinishLines() // Swap buffers p->currentLinesBuffer_.swap(p->newLinesBuffer_); p->currentThresholdBuffer_.swap(p->newThresholdBuffer_); + p->currentHoverLines_.swap(p->newHoverLines_); // Clear the new buffers p->newLinesBuffer_.clear(); p->newThresholdBuffer_.clear(); + p->newHoverLines_.clear(); // Update the number of lines p->currentNumLines_ = p->newNumLines_; @@ -262,7 +401,7 @@ void PlacefileLines::Impl::UpdateBuffers( units::length::nautical_miles threshold = di->threshold_; GLint thresholdValue = static_cast(std::round(threshold.value())); - std::vector angles {}; + std::vector> angles {}; angles.reserve(di->elements_.size() - 1); // For each element pair inside a Line statement, render a black line @@ -277,26 +416,27 @@ void PlacefileLines::Impl::UpdateBuffers( // Calculate angle const units::angle::degrees angle = util::GeographicLib::GetAngle(lat1, lon1, lat2, lon2); - float angleValue = angle.value(); - angles.push_back(angleValue); + angles.push_back(angle); + // Buffer line with hover text BufferLine(di->elements_[i], di->elements_[i + 1], di->width_ + 2, - static_cast(angleValue), + angle, kBlack_, - thresholdValue); + thresholdValue, + di->hoverText_); } // For each element pair inside a Line statement, render a colored line for (std::size_t i = 0; i < di->elements_.size() - 1; ++i) { - float angleValue = angles[i]; + auto angle = angles[i]; BufferLine(di->elements_[i], di->elements_[i + 1], di->width_, - static_cast(angleValue), + angle, di->color_, thresholdValue); } @@ -306,9 +446,10 @@ void PlacefileLines::Impl::BufferLine( const gr::Placefile::LineDrawItem::Element& e1, const gr::Placefile::LineDrawItem::Element& e2, const float width, - const float angle, + const units::angle::degrees angle, const boost::gil::rgba8_pixel_t color, - const GLint threshold) + const GLint threshold, + const std::string& hoverText) { // Latitude and longitude coordinates in degrees const float lat1 = static_cast(e1.latitude_); @@ -323,7 +464,7 @@ void PlacefileLines::Impl::BufferLine( // const float y2 = static_cast(e2.y_); // Angle - const float a = angle; + const float a = static_cast(angle.value()); // Final X/Y offsets in pixels const float hw = width * 0.5f; @@ -352,6 +493,22 @@ void PlacefileLines::Impl::BufferLine( newThresholdBuffer_.insert( newThresholdBuffer_.end(), {threshold, threshold, threshold, threshold, threshold, threshold}); + + if (!hoverText.empty()) + { + const units::angle::radians radians = angle; + + const auto sc1 = util::maplibre::LatLongToScreenCoordinate({lat1, lon1}); + const auto sc2 = util::maplibre::LatLongToScreenCoordinate({lat2, lon2}); + + const float cosAngle = cosf(static_cast(radians.value())); + const float sinAngle = sinf(static_cast(radians.value())); + + const glm::mat2 rotate {cosAngle, -sinAngle, sinAngle, cosAngle}; + + newHoverLines_.emplace_back( + LineHoverEntry {hoverText, sc1, sc2, rotate, width}); + } } void PlacefileLines::Impl::Update() diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp index 4dbf518d..551b955c 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp @@ -31,6 +31,9 @@ public: void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override; void Deinitialize() override; + bool RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, + const glm::vec2& mousePos) override; + /** * Resets and prepares the draw item for adding a new set of lines. */ diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp index 99de2e87..ccd77f0c 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.cpp @@ -210,7 +210,8 @@ void PlacefileText::Deinitialize() } bool PlacefileText::RunMousePicking( - const QMapLibreGL::CustomLayerRenderParameters& /* params */) + const QMapLibreGL::CustomLayerRenderParameters& /* params */, + const glm::vec2& /* mousePos */) { bool itemPicked = false; diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp index 65c05905..a4e51fec 100644 --- a/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_text.hpp @@ -33,8 +33,8 @@ public: void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override; void Deinitialize() override; - bool RunMousePicking( - const QMapLibreGL::CustomLayerRenderParameters& params) override; + bool RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, + const glm::vec2& mousePos) override; /** * Resets and prepares the draw item for adding a new set of text. diff --git a/scwx-qt/source/scwx/qt/map/draw_layer.cpp b/scwx-qt/source/scwx/qt/map/draw_layer.cpp index 2116a1bd..6a6895e7 100644 --- a/scwx-qt/source/scwx/qt/map/draw_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/draw_layer.cpp @@ -77,7 +77,8 @@ void DrawLayer::Deinitialize() } bool DrawLayer::RunMousePicking( - const QMapLibreGL::CustomLayerRenderParameters& params) + const QMapLibreGL::CustomLayerRenderParameters& params, + const glm::vec2& mousePos) { bool itemPicked = false; @@ -85,7 +86,7 @@ bool DrawLayer::RunMousePicking( for (auto it = p->drawList_.rbegin(); it != p->drawList_.rend(); ++it) { // Run mouse picking on each draw item - if ((*it)->RunMousePicking(params)) + if ((*it)->RunMousePicking(params, mousePos)) { // If a draw item was picked, don't process additional items itemPicked = true; diff --git a/scwx-qt/source/scwx/qt/map/draw_layer.hpp b/scwx-qt/source/scwx/qt/map/draw_layer.hpp index e2b3694c..2924a408 100644 --- a/scwx-qt/source/scwx/qt/map/draw_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/draw_layer.hpp @@ -23,8 +23,9 @@ public: Render(const QMapLibreGL::CustomLayerRenderParameters&) override; virtual void Deinitialize() override; - virtual bool RunMousePicking( - const QMapLibreGL::CustomLayerRenderParameters& params) override; + virtual bool + RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, + const glm::vec2& mousePos) override; protected: void AddDrawItem(const std::shared_ptr& drawItem); diff --git a/scwx-qt/source/scwx/qt/map/generic_layer.cpp b/scwx-qt/source/scwx/qt/map/generic_layer.cpp index 3ec27513..ce8673a4 100644 --- a/scwx-qt/source/scwx/qt/map/generic_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/generic_layer.cpp @@ -27,7 +27,8 @@ GenericLayer::GenericLayer(std::shared_ptr context) : GenericLayer::~GenericLayer() = default; bool GenericLayer::RunMousePicking( - const QMapLibreGL::CustomLayerRenderParameters& /* params */) + const QMapLibreGL::CustomLayerRenderParameters& /* params */, + const glm::vec2& /* mousePos */) { // By default, the layer has nothing to pick return false; diff --git a/scwx-qt/source/scwx/qt/map/generic_layer.hpp b/scwx-qt/source/scwx/qt/map/generic_layer.hpp index b44e38f3..48eb9ca0 100644 --- a/scwx-qt/source/scwx/qt/map/generic_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/generic_layer.hpp @@ -6,6 +6,7 @@ #include #include +#include namespace scwx { @@ -32,11 +33,13 @@ public: * @brief Run mouse picking on the layer. * * @param [in] params Custom layer render parameters + * @param [in] mousePos Mouse cursor location in map screen coordinates * * @return true if a draw item was picked, otherwise false */ virtual bool - RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params); + RunMousePicking(const QMapLibreGL::CustomLayerRenderParameters& params, + const glm::vec2& mousePos); 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 cb164277..9d49bae8 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -172,6 +173,7 @@ public: common::Level2Product selectedLevel2Product_; + bool hasMouse_ {false}; QPointF lastPos_; std::size_t currentStyleIndex_; const MapStyle* currentStyle_; @@ -862,6 +864,16 @@ void MapWidgetImpl::AddLayer(const std::string& id, layerList_.push_back(id); } +void MapWidget::enterEvent(QEnterEvent* /* ev */) +{ + p->hasMouse_ = true; +} + +void MapWidget::leaveEvent(QEvent* /* ev */) +{ + p->hasMouse_ = false; +} + void MapWidget::keyPressEvent(QKeyEvent* ev) { switch (ev->key()) @@ -1024,7 +1036,10 @@ void MapWidget::paintGL() p->map_->render(); // Perform mouse picking - p->RunMousePicking(); + if (p->hasMouse_) + { + p->RunMousePicking(); + } // Render ImGui Frame ImGui::Render(); @@ -1039,13 +1054,17 @@ void MapWidgetImpl::RunMousePicking() const QMapLibreGL::CustomLayerRenderParameters params = context_->render_parameters(); + auto coordinate = map_->coordinateForPixel(lastPos_); + auto mouseScreenCoordinate = + util::maplibre::LatLongToScreenCoordinate(coordinate); + // For each layer in reverse // TODO: All Generic Layers, not just Placefile Layers for (auto it = placefileLayers_.rbegin(); it != placefileLayers_.rend(); ++it) { // Run mouse picking for each layer - if ((*it)->RunMousePicking(params)) + if ((*it)->RunMousePicking(params, mouseScreenCoordinate)) { // If a draw item was picked, don't process additional layers break; diff --git a/scwx-qt/source/scwx/qt/map/map_widget.hpp b/scwx-qt/source/scwx/qt/map/map_widget.hpp index e1c75a79..d18bec6e 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.hpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.hpp @@ -119,7 +119,9 @@ private: qreal pixelRatio(); // QWidget implementation. + void enterEvent(QEnterEvent* ev) override final; void keyPressEvent(QKeyEvent* ev) override final; + void leaveEvent(QEvent* ev) override final; void mousePressEvent(QMouseEvent* ev) override final; void mouseMoveEvent(QMouseEvent* ev) override final; void wheelEvent(QWheelEvent* ev) override final; diff --git a/scwx-qt/source/scwx/qt/util/maplibre.cpp b/scwx-qt/source/scwx/qt/util/maplibre.cpp index 8cde77ca..1093ce02 100644 --- a/scwx-qt/source/scwx/qt/util/maplibre.cpp +++ b/scwx-qt/source/scwx/qt/util/maplibre.cpp @@ -20,6 +20,29 @@ GetMapDistance(const QMapLibreGL::CustomLayerRenderParameters& params) (params.width + params.height) / 2.0); } +glm::mat4 GetMapMatrix(const QMapLibreGL::CustomLayerRenderParameters& params) +{ + glm::vec2 scale = GetMapScale(params); + + 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)); + + return mapMatrix; +} + +glm::vec2 GetMapScale(const QMapLibreGL::CustomLayerRenderParameters& params) +{ + const float scale = std::pow(2.0, params.zoom) * 2.0f * + mbgl::util::tileSize_D / mbgl::util::DEGREES_MAX; + const float xScale = scale / params.width; + const float yScale = scale / params.height; + + return glm::vec2 {xScale, yScale}; +} + glm::vec2 LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate) { static constexpr double RAD2DEG_D = 180.0 / M_PI; diff --git a/scwx-qt/source/scwx/qt/util/maplibre.hpp b/scwx-qt/source/scwx/qt/util/maplibre.hpp index 37d205eb..d62fb878 100644 --- a/scwx-qt/source/scwx/qt/util/maplibre.hpp +++ b/scwx-qt/source/scwx/qt/util/maplibre.hpp @@ -15,6 +15,8 @@ namespace maplibre units::length::meters GetMapDistance(const QMapLibreGL::CustomLayerRenderParameters& params); +glm::mat4 GetMapMatrix(const QMapLibreGL::CustomLayerRenderParameters& params); +glm::vec2 GetMapScale(const QMapLibreGL::CustomLayerRenderParameters& params); glm::vec2 LatLongToScreenCoordinate(const QMapLibreGL::Coordinate& coordinate); } // namespace maplibre