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();
}