diff --git a/scwx-qt/gl/geo_texture2d.vert b/scwx-qt/gl/geo_texture2d.vert new file mode 100644 index 00000000..35bc85fb --- /dev/null +++ b/scwx-qt/gl/geo_texture2d.vert @@ -0,0 +1,42 @@ +#version 330 core + +#define DEGREES_MAX 360.0f +#define LATITUDE_MAX 85.051128779806604f +#define LONGITUDE_MAX 180.0f +#define PI 3.1415926535897932384626433f +#define RAD2DEG 57.295779513082320876798156332941f + +layout (location = 0) in vec2 aLatLong; +layout (location = 1) in vec2 aXYOffset; +layout (location = 2) in vec2 aTexCoord; +layout (location = 3) in vec4 aModulate; +layout (location = 4) in float aAngle; + +uniform mat4 uMVPMatrix; +uniform mat4 uMapMatrix; +uniform vec2 uMapScreenCoord; + +smooth out vec2 texCoord; +flat out vec4 modulate; + +vec2 latLngToScreenCoordinate(in vec2 latLng) +{ + vec2 p; + latLng.x = clamp(latLng.x, -LATITUDE_MAX, LATITUDE_MAX); + p.xy = vec2(LONGITUDE_MAX + latLng.y, + -(LONGITUDE_MAX - RAD2DEG * log(tan(PI / 4 + latLng.x * PI / DEGREES_MAX)))); + return p; +} + +void main() +{ + // Pass the texture coordinate and color modulate to the fragment shader + texCoord = aTexCoord; + modulate = aModulate; + + vec2 p = latLngToScreenCoordinate(aLatLong) - uMapScreenCoord; + + // Transform the position to screen coordinates + gl_Position = uMapMatrix * vec4(p, 0.0f, 1.0f) - + uMVPMatrix * vec4(aXYOffset, 0.0f, 0.0f); +} diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 9a60ef01..a024e12e 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -57,9 +57,11 @@ set(SRC_GL source/scwx/qt/gl/gl_context.cpp source/scwx/qt/gl/text_shader.cpp) set(HDR_GL_DRAW source/scwx/qt/gl/draw/draw_item.hpp source/scwx/qt/gl/draw/geo_line.hpp + source/scwx/qt/gl/draw/placefile_icons.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_line.cpp + source/scwx/qt/gl/draw/placefile_icons.cpp source/scwx/qt/gl/draw/rectangle.cpp) set(HDR_MANAGER source/scwx/qt/manager/placefile_manager.hpp source/scwx/qt/manager/radar_product_manager.hpp @@ -240,6 +242,7 @@ set(RESOURCE_FILES scwx-qt.qrc) set(SHADER_FILES gl/color.frag gl/color.vert gl/geo_line.vert + gl/geo_texture2d.vert gl/radar.frag gl/radar.vert gl/text.frag diff --git a/scwx-qt/scwx-qt.qrc b/scwx-qt/scwx-qt.qrc index 6d786fff..fa10ef6f 100644 --- a/scwx-qt/scwx-qt.qrc +++ b/scwx-qt/scwx-qt.qrc @@ -3,6 +3,7 @@ gl/color.frag gl/color.vert gl/geo_line.vert + gl/geo_texture2d.vert gl/radar.frag gl/radar.vert gl/text.frag diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp new file mode 100644 index 00000000..69e6887e --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.cpp @@ -0,0 +1,370 @@ +#include +#include +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +static const std::string logPrefix_ = "scwx::qt::gl::draw::placefile_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 = 11; +static constexpr std::size_t kBufferLength = + kNumTriangles * kVerticesPerTriangle * kPointsPerVertex; + +struct PlacefileIconInfo +{ + PlacefileIconInfo( + const std::shared_ptr& iconFile, + const std::string& baseUrlString) : + iconFile_ {iconFile} + { + // Resolve using base URL + auto baseUrl = QUrl::fromUserInput(QString::fromStdString(baseUrlString)); + auto relativeUrl = QUrl(QString::fromStdString(iconFile->filename_)); + + texture_ = util::TextureAtlas::Instance().GetTextureAttributes( + baseUrl.resolved(relativeUrl).toString().toStdString()); + + if (iconFile->iconWidth_ > 0 && iconFile->iconHeight_ > 0) + { + columns_ = texture_.size_.x / iconFile->iconWidth_; + rows_ = texture_.size_.y / iconFile->iconHeight_; + } + else + { + columns_ = 0u; + rows_ = 0u; + } + + numIcons_ = columns_ * rows_; + + 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_ = iconFile_->iconWidth_ * xFactor; + scaledHeight_ = iconFile_->iconHeight_ * yFactor; + } + + std::shared_ptr iconFile_; + util::TextureAttributes texture_; + std::size_t rows_ {}; + std::size_t columns_ {}; + std::size_t numIcons_ {}; + float scaledWidth_ {}; + float scaledHeight_ {}; +}; + +class PlacefileIcons::Impl +{ +public: + explicit Impl(std::shared_ptr context) : + context_ {context}, + shaderProgram_ {nullptr}, + uMVPMatrixLocation_(GL_INVALID_INDEX), + uMapMatrixLocation_(GL_INVALID_INDEX), + uMapScreenCoordLocation_(GL_INVALID_INDEX), + vao_ {GL_INVALID_INDEX}, + vbo_ {GL_INVALID_INDEX}, + numVertices_ {0} + { + } + + ~Impl() {} + + std::shared_ptr context_; + + bool dirty_ {false}; + + boost::unordered_flat_map + iconFiles_ {}; + + std::vector> iconList_ {}; + + std::shared_ptr shaderProgram_; + GLint uMVPMatrixLocation_; + GLint uMapMatrixLocation_; + GLint uMapScreenCoordLocation_; + + GLuint vao_; + GLuint vbo_; + + GLsizei numVertices_; + + void Update(); +}; + +PlacefileIcons::PlacefileIcons(std::shared_ptr context) : + DrawItem(context->gl()), p(std::make_unique(context)) +{ +} +PlacefileIcons::~PlacefileIcons() = default; + +PlacefileIcons::PlacefileIcons(PlacefileIcons&&) noexcept = default; +PlacefileIcons& PlacefileIcons::operator=(PlacefileIcons&&) noexcept = default; + +void PlacefileIcons::Initialize() +{ + gl::OpenGLFunctions& gl = p->context_->gl(); + + p->shaderProgram_ = p->context_->GetShaderProgram(":/gl/geo_texture2d.vert", + ":/gl/texture2d.frag"); + + p->uMVPMatrixLocation_ = + gl.glGetUniformLocation(p->shaderProgram_->id(), "uMVPMatrix"); + if (p->uMVPMatrixLocation_ == -1) + { + logger_->warn("Could not find uMVPMatrix"); + } + + p->uMapMatrixLocation_ = + gl.glGetUniformLocation(p->shaderProgram_->id(), "uMapMatrix"); + if (p->uMapMatrixLocation_ == -1) + { + logger_->warn("Could not find uMapMatrix"); + } + + p->uMapScreenCoordLocation_ = + gl.glGetUniformLocation(p->shaderProgram_->id(), "uMapScreenCoord"); + if (p->uMapScreenCoordLocation_ == -1) + { + logger_->warn("Could not find uMapScreenCoord"); + } + + gl.glGenVertexArrays(1, &p->vao_); + gl.glGenBuffers(1, &p->vbo_); + + gl.glBindVertexArray(p->vao_); + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_); + 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); + + // aTexCoord + gl.glVertexAttribPointer(2, + 2, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(4 * sizeof(float))); + gl.glEnableVertexAttribArray(2); + + // aModulate + gl.glVertexAttribPointer(3, + 4, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(6 * sizeof(float))); + gl.glEnableVertexAttribArray(3); + + // aAngle + gl.glVertexAttribPointer(4, + 1, + GL_FLOAT, + GL_FALSE, + kPointsPerVertex * sizeof(float), + reinterpret_cast(10 * sizeof(float))); + gl.glEnableVertexAttribArray(4); + + p->dirty_ = true; +} + +void PlacefileIcons::Render( + const QMapLibreGL::CustomLayerRenderParameters& params) +{ + if (!p->iconList_.empty()) + { + gl::OpenGLFunctions& gl = p->context_->gl(); + + gl.glBindVertexArray(p->vao_); + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_); + + p->Update(); + p->shaderProgram_->Use(); + UseDefaultProjection(params, p->uMVPMatrixLocation_); + UseMapProjection( + params, p->uMapMatrixLocation_, p->uMapScreenCoordLocation_); + + // Draw icons + gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); + } +} + +void PlacefileIcons::Deinitialize() +{ + gl::OpenGLFunctions& gl = p->context_->gl(); + + gl.glDeleteVertexArrays(1, &p->vao_); + gl.glDeleteBuffers(1, &p->vbo_); +} + +void PlacefileIcons::SetIconFiles( + const std::vector>& iconFiles, + const std::string& baseUrl) +{ + p->dirty_ = true; + + // Populate icon file map + p->iconFiles_.clear(); + + for (auto& file : iconFiles) + { + p->iconFiles_.emplace( + std::piecewise_construct, + std::tuple {file->fileNumber_}, + std::forward_as_tuple(PlacefileIconInfo {file, baseUrl})); + } +} + +void PlacefileIcons::AddIcon( + const std::shared_ptr& di) +{ + if (di != nullptr) + { + p->iconList_.emplace_back(di); + p->dirty_ = true; + } +} + +void PlacefileIcons::Reset() +{ + // Clear the icon list, and mark the draw item dirty + p->iconList_.clear(); + p->dirty_ = true; +} + +void PlacefileIcons::Impl::Update() +{ + if (dirty_) + { + static std::vector buffer {}; + buffer.clear(); + buffer.reserve(iconList_.size() * kBufferLength); + numVertices_ = 0; + + for (auto& di : iconList_) + { + auto it = iconFiles_.find(di->fileNumber_); + if (it == iconFiles_.cend()) + { + // No file found + logger_->trace("Could not find file number: {}", di->fileNumber_); + continue; + } + + auto& icon = it->second; + + // Validate icon + if (di->iconNumber_ == 0 || di->iconNumber_ > icon.numIcons_) + { + // No icon found + logger_->trace("Invalid icon number: {}", di->iconNumber_); + continue; + } + + // 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_); + + // Half icon size + const float hw = static_cast(icon.iconFile_->iconWidth_) * 0.5f; + const float hh = + static_cast(icon.iconFile_->iconHeight_) * 0.5f; + + // Final X/Y offsets in pixels + const float lx = x - hw; + const float rx = x + hw; + const float by = y - hh; + const float ty = y + hh; + + // Angle in degrees + // TODO: Properly convert + const float a = static_cast(di->angle_.value()); + + // Texture coordinates + const std::size_t iconRow = (di->iconNumber_ - 1) / icon.columns_; + const std::size_t iconColumn = (di->iconNumber_ - 1) % 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_; + + // Fixed modulate color + const float mc0 = 1.0f; + const float mc1 = 1.0f; + const float mc2 = 1.0f; + const float mc3 = 1.0f; + + buffer.insert(buffer.end(), + { + // Icon + lat, lon, lx, by, ls, bt, mc0, mc1, mc2, mc3, a, // BL + lat, lon, lx, by, ls, tt, mc0, mc1, mc2, mc3, a, // TL + lat, lon, rx, ty, rs, bt, mc0, mc1, mc2, mc3, a, // BR + lat, lon, rx, ty, rs, bt, mc0, mc1, mc2, mc3, a, // BR + lat, lon, rx, ty, rs, tt, mc0, mc1, mc2, mc3, a, // TR + lat, lon, lx, by, ls, tt, mc0, mc1, mc2, mc3, a // TL + }); + + numVertices_ += 6; + } + + gl::OpenGLFunctions& gl = context_->gl(); + + gl.glBufferData(GL_ARRAY_BUFFER, + sizeof(float) * buffer.size(), + buffer.data(), + GL_DYNAMIC_DRAW); + + dirty_ = false; + } +} + +} // namespace draw +} // namespace gl +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp new file mode 100644 index 00000000..9f771d8e --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_icons.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +class PlacefileIcons : public DrawItem +{ +public: + explicit PlacefileIcons(std::shared_ptr context); + ~PlacefileIcons(); + + PlacefileIcons(const PlacefileIcons&) = delete; + PlacefileIcons& operator=(const PlacefileIcons&) = delete; + + PlacefileIcons(PlacefileIcons&&) noexcept; + PlacefileIcons& operator=(PlacefileIcons&&) noexcept; + + void Initialize() override; + void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override; + void Deinitialize() override; + + /** + * Configures the textures for drawing the placefile icons. + * + * @param [in] iconFiles A list of icon files + * @param [in] baseUrl The base URL of the placefile + */ + void SetIconFiles( + const std::vector>& + iconFiles, + const std::string& baseUrl); + + /** + * Adds a placefile icon to the internal draw list. + * + * @param [in] di Placefile icon + */ + void AddIcon(const std::shared_ptr& di); + + /** + * Resets the list of icons in preparation for rendering a new frame. + */ + void Reset(); + +private: + class Impl; + + std::unique_ptr p; +}; + +} // namespace draw +} // namespace gl +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp index 1b82af67..6030e264 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -23,12 +24,18 @@ static const auto logger_ = scwx::util::Logger::Create(logPrefix_); class PlacefileLayer::Impl { public: - explicit Impl() {}; + explicit Impl(std::shared_ptr context) : + placefileIcons_ {std::make_shared(context)} + { + } ~Impl() = default; + void + RenderIconDrawItem(const QMapLibreGL::CustomLayerRenderParameters& params, + const std::shared_ptr& di); void RenderTextDrawItem(const QMapLibreGL::CustomLayerRenderParameters& params, - std::shared_ptr di); + const std::shared_ptr& di); void RenderText(const QMapLibreGL::CustomLayerRenderParameters& params, const std::string& text, @@ -44,11 +51,14 @@ public: float halfHeight_ {}; bool thresholded_ {true}; ImFont* monospaceFont_ {}; + + std::shared_ptr placefileIcons_; }; PlacefileLayer::PlacefileLayer(std::shared_ptr context) : - DrawLayer(context), p(std::make_unique()) + DrawLayer(context), p(std::make_unique(context)) { + AddDrawItem(p->placefileIcons_); } PlacefileLayer::~PlacefileLayer() = default; @@ -60,9 +70,25 @@ void PlacefileLayer::Initialize() DrawLayer::Initialize(); } +void PlacefileLayer::Impl::RenderIconDrawItem( + const QMapLibreGL::CustomLayerRenderParameters& params, + const std::shared_ptr& di) +{ + auto distance = + (thresholded_) ? + util::GeographicLib::GetDistance( + params.latitude, params.longitude, di->latitude_, di->longitude_) : + 0; + + if (distance < di->threshold_) + { + placefileIcons_->AddIcon(di); + } +} + void PlacefileLayer::Impl::RenderTextDrawItem( - const QMapLibreGL::CustomLayerRenderParameters& params, - std::shared_ptr di) + const QMapLibreGL::CustomLayerRenderParameters& params, + const std::shared_ptr& di) { auto distance = (thresholded_) ? @@ -133,11 +159,12 @@ void PlacefileLayer::Render( { gl::OpenGLFunctions& gl = context()->gl(); - DrawLayer::Render(params); - // Reset text ID per frame p->textId_ = 0; + // Reset graphics + p->placefileIcons_->Reset(); + // Update map screen coordinate and scale information p->mapScreenCoordLocation_ = util::maplibre::LatLongToScreenCoordinate( {params.latitude, params.longitude}); @@ -171,6 +198,9 @@ void PlacefileLayer::Render( p->thresholded_ = placefileManager->placefile_thresholded(placefile->name()); + p->placefileIcons_->SetIconFiles(placefile->icon_files(), + placefile->name()); + for (auto& drawItem : placefile->GetDrawItems()) { switch (drawItem->itemType_) @@ -181,12 +211,20 @@ void PlacefileLayer::Render( std::static_pointer_cast(drawItem)); break; + case gr::Placefile::ItemType::Icon: + p->RenderIconDrawItem( + params, + std::static_pointer_cast(drawItem)); + break; + default: break; } } } + DrawLayer::Render(params); + SCWX_GL_CHECK_ERROR(); }