diff --git a/scwx-qt/gl/map_color.vert b/scwx-qt/gl/map_color.vert
new file mode 100644
index 00000000..e2f96d5a
--- /dev/null
+++ b/scwx-qt/gl/map_color.vert
@@ -0,0 +1,23 @@
+#version 330 core
+
+layout (location = 0) in vec2 aScreenCoord;
+layout (location = 1) in vec2 aXYOffset;
+layout (location = 2) in vec4 aColor;
+
+uniform mat4 uMVPMatrix;
+uniform mat4 uMapMatrix;
+uniform vec2 uMapScreenCoord;
+
+out vec4 color;
+
+void main()
+{
+ // Pass the color to the fragment shader
+ color = aColor;
+
+ vec2 p = aScreenCoord - 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 a024e12e..7af57f18 100644
--- a/scwx-qt/scwx-qt.cmake
+++ b/scwx-qt/scwx-qt.cmake
@@ -58,10 +58,12 @@ 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_line.hpp
source/scwx/qt/gl/draw/placefile_icons.hpp
+ source/scwx/qt/gl/draw/placefile_polygons.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/placefile_polygons.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
@@ -243,6 +245,7 @@ set(SHADER_FILES gl/color.frag
gl/color.vert
gl/geo_line.vert
gl/geo_texture2d.vert
+ gl/map_color.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 fa10ef6f..1f9e1123 100644
--- a/scwx-qt/scwx-qt.qrc
+++ b/scwx-qt/scwx-qt.qrc
@@ -4,6 +4,7 @@
gl/color.vert
gl/geo_line.vert
gl/geo_texture2d.vert
+ gl/map_color.vert
gl/radar.frag
gl/radar.vert
gl/text.frag
diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp
new file mode 100644
index 00000000..a2337d33
--- /dev/null
+++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp
@@ -0,0 +1,393 @@
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+
+namespace scwx
+{
+namespace qt
+{
+namespace gl
+{
+namespace draw
+{
+
+static const std::string logPrefix_ = "scwx::qt::gl::draw::placefile_polygons";
+static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
+
+static constexpr std::size_t kVerticesPerTriangle = 3;
+static constexpr std::size_t kPointsPerVertex = 8;
+
+static constexpr std::size_t kTessVertexScreenX_ = 0;
+static constexpr std::size_t kTessVertexScreenY_ = 1;
+static constexpr std::size_t kTessVertexScreenZ_ = 2;
+static constexpr std::size_t kTessVertexXOffset_ = 3;
+static constexpr std::size_t kTessVertexYOffset_ = 4;
+static constexpr std::size_t kTessVertexR_ = 5;
+static constexpr std::size_t kTessVertexG_ = 6;
+static constexpr std::size_t kTessVertexB_ = 7;
+static constexpr std::size_t kTessVertexA_ = 8;
+static constexpr std::size_t kTessVertexSize_ = kTessVertexA_ + 1;
+
+typedef std::array TessVertexArray;
+
+class PlacefilePolygons::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}
+ {
+ tessellator_ = gluNewTess();
+
+ gluTessCallback(tessellator_, //
+ GLU_TESS_COMBINE_DATA,
+ (GLvoid(*)()) & TessellateCombineCallback);
+ gluTessCallback(tessellator_, //
+ GLU_TESS_VERTEX_DATA,
+ (GLvoid(*)()) & TessellateVertexCallback);
+
+ // Force GLU_TRIANGLES
+ gluTessCallback(tessellator_, //
+ GLU_TESS_EDGE_FLAG,
+ []() {});
+
+ gluTessCallback(tessellator_, //
+ GLU_TESS_ERROR,
+ (GLvoid(*)()) & TessellateErrorCallback);
+ }
+
+ ~Impl() { gluDeleteTess(tessellator_); }
+
+ void Update();
+
+ void Tessellate(const std::shared_ptr& di);
+
+ static void TessellateCombineCallback(GLdouble coords[3],
+ void* vertexData[4],
+ GLfloat weight[4],
+ void** outData,
+ void* polygonData);
+ static void TessellateVertexCallback(void* vertexData, void* polygonData);
+ static void TessellateErrorCallback(GLenum errorCode);
+
+ std::shared_ptr context_;
+
+ bool dirty_ {false};
+
+ std::vector>
+ polygonList_ {};
+
+ boost::container::stable_vector tessCombineBuffer_ {};
+
+ std::mutex bufferMutex_ {};
+ std::vector currentBuffer_ {};
+ std::vector newBuffer_ {};
+
+ GLUtesselator* tessellator_;
+
+ std::shared_ptr shaderProgram_;
+ GLint uMVPMatrixLocation_;
+ GLint uMapMatrixLocation_;
+ GLint uMapScreenCoordLocation_;
+
+ GLuint vao_;
+ GLuint vbo_;
+
+ GLsizei numVertices_;
+
+ boost::gil::rgba8_pixel_t currentColor_ {255, 255, 255, 255};
+};
+
+PlacefilePolygons::PlacefilePolygons(std::shared_ptr context) :
+ DrawItem(context->gl()), p(std::make_unique(context))
+{
+}
+PlacefilePolygons::~PlacefilePolygons() = default;
+
+PlacefilePolygons::PlacefilePolygons(PlacefilePolygons&&) noexcept = default;
+PlacefilePolygons&
+PlacefilePolygons::operator=(PlacefilePolygons&&) noexcept = default;
+
+void PlacefilePolygons::Initialize()
+{
+ gl::OpenGLFunctions& gl = p->context_->gl();
+
+ p->shaderProgram_ =
+ p->context_->GetShaderProgram(":/gl/map_color.vert", ":/gl/color.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);
+
+ // aScreenCoord
+ 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);
+
+ // aColor
+ gl.glVertexAttribPointer(2,
+ 4,
+ GL_FLOAT,
+ GL_FALSE,
+ kPointsPerVertex * sizeof(float),
+ reinterpret_cast(4 * sizeof(float)));
+ gl.glEnableVertexAttribArray(2);
+
+ p->dirty_ = true;
+}
+
+void PlacefilePolygons::Render(
+ const QMapLibreGL::CustomLayerRenderParameters& params)
+{
+ if (!p->polygonList_.empty())
+ {
+ gl::OpenGLFunctions& gl = p->context_->gl();
+
+ gl.glBindVertexArray(p->vao_);
+ gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_);
+
+ p->Update();
+ p->shaderProgram_->Use();
+ UseRotationProjection(params, p->uMVPMatrixLocation_);
+ UseMapProjection(
+ params, p->uMapMatrixLocation_, p->uMapScreenCoordLocation_);
+
+ // Draw icons
+ gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_);
+ }
+}
+
+void PlacefilePolygons::Deinitialize()
+{
+ gl::OpenGLFunctions& gl = p->context_->gl();
+
+ gl.glDeleteVertexArrays(1, &p->vao_);
+ gl.glDeleteBuffers(1, &p->vbo_);
+}
+
+void PlacefilePolygons::StartPolygons()
+{
+ // Clear the new buffer
+ p->newBuffer_.clear();
+
+ // Clear the polygon list
+ p->polygonList_.clear();
+}
+
+void PlacefilePolygons::AddPolygon(
+ const std::shared_ptr& di)
+{
+ if (di != nullptr)
+ {
+ p->polygonList_.emplace_back(di);
+ p->Tessellate(di);
+ }
+}
+
+void PlacefilePolygons::FinishPolygons()
+{
+ std::unique_lock lock {p->bufferMutex_};
+
+ // Swap buffers
+ p->currentBuffer_.swap(p->newBuffer_);
+
+ // Mark the draw item dirty
+ p->dirty_ = true;
+}
+
+void PlacefilePolygons::Impl::Update()
+{
+ if (dirty_)
+ {
+ gl::OpenGLFunctions& gl = context_->gl();
+
+ std::unique_lock lock {bufferMutex_};
+
+ gl.glBufferData(GL_ARRAY_BUFFER,
+ sizeof(GLfloat) * currentBuffer_.size(),
+ currentBuffer_.data(),
+ GL_DYNAMIC_DRAW);
+
+ numVertices_ =
+ static_cast(currentBuffer_.size() / kPointsPerVertex);
+
+ dirty_ = false;
+ }
+}
+
+void PlacefilePolygons::Impl::Tessellate(
+ const std::shared_ptr& di)
+{
+ // Vertex storage
+ boost::container::stable_vector vertices {};
+
+ // Default color to "Color" statement
+ boost::gil::rgba8_pixel_t lastColor = di->color_;
+
+ gluTessBeginPolygon(tessellator_, this);
+
+ for (auto& contour : di->contours_)
+ {
+ gluTessBeginContour(tessellator_);
+
+ for (auto& element : contour)
+ {
+ // Calculate screen coordinate
+ auto screenCoordinate = util::maplibre::LatLongToScreenCoordinate(
+ {element.latitude_, element.longitude_});
+
+ // Update the most recent color if specified
+ if (element.color_.has_value())
+ {
+ lastColor = element.color_.value();
+ }
+
+ // Add vertex to temporary storage
+ auto& vertex =
+ vertices.emplace_back(TessVertexArray {screenCoordinate.x,
+ screenCoordinate.y,
+ 0.0, // z
+ element.x_,
+ element.y_,
+ lastColor[0] / 255.0,
+ lastColor[1] / 255.0,
+ lastColor[2] / 255.0,
+ lastColor[3] / 255.0});
+
+ // Tessellate vertex
+ gluTessVertex(tessellator_, vertex.data(), vertex.data());
+ }
+
+ gluTessEndContour(tessellator_);
+ }
+
+ gluTessEndPolygon(tessellator_);
+
+ // Clear temporary storage
+ tessCombineBuffer_.clear();
+
+ // Remove extra vertices that don't correspond to a full triangle
+ while (newBuffer_.size() % kVerticesPerTriangle != 0)
+ {
+ newBuffer_.pop_back();
+ }
+}
+
+void PlacefilePolygons::Impl::TessellateCombineCallback(GLdouble coords[3],
+ void* vertexData[4],
+ GLfloat w[4],
+ void** outData,
+ void* polygonData)
+{
+ static constexpr std::size_t r = kTessVertexR_;
+ static constexpr std::size_t g = kTessVertexG_;
+ static constexpr std::size_t b = kTessVertexB_;
+ static constexpr std::size_t a = kTessVertexA_;
+
+ Impl* self = static_cast(polygonData);
+
+ // Create new vertex data with given coordinates and interpolated color
+ auto& newVertexData = self->tessCombineBuffer_.emplace_back( //
+ TessVertexArray {
+ coords[0],
+ coords[1],
+ coords[2],
+ 0.0, // offsetX
+ 0.0, // offsetY
+ 0.0, // r
+ 0.0, // g
+ 0.0, // b
+ 0.0 // a
+ });
+
+ for (std::size_t i = 0; i < 4; ++i)
+ {
+ GLdouble* d = static_cast(vertexData[i]);
+ if (d != nullptr)
+ {
+ for (std::size_t color = r; color <= a; ++color)
+ {
+ newVertexData[color] += w[i] * d[color];
+ }
+ }
+ }
+
+ // Return new vertex data
+ *outData = &newVertexData;
+}
+
+void PlacefilePolygons::Impl::TessellateVertexCallback(void* vertexData,
+ void* polygonData)
+{
+ Impl* self = static_cast(polygonData);
+ GLdouble* data = static_cast(vertexData);
+
+ // Buffer vertex
+ self->newBuffer_.insert(self->newBuffer_.end(),
+ {static_cast(data[kTessVertexScreenX_]),
+ static_cast(data[kTessVertexScreenY_]),
+ static_cast(data[kTessVertexXOffset_]),
+ static_cast(data[kTessVertexYOffset_]),
+ static_cast(data[kTessVertexR_]),
+ static_cast(data[kTessVertexG_]),
+ static_cast(data[kTessVertexB_]),
+ static_cast(data[kTessVertexA_])});
+}
+
+void PlacefilePolygons::Impl::TessellateErrorCallback(GLenum errorCode)
+{
+ logger_->error("GL Error: {}", errorCode);
+}
+
+} // namespace draw
+} // namespace gl
+} // namespace qt
+} // namespace scwx
diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.hpp
new file mode 100644
index 00000000..f4a44ecd
--- /dev/null
+++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.hpp
@@ -0,0 +1,60 @@
+#pragma once
+
+#include
+#include
+#include
+
+#include
+
+namespace scwx
+{
+namespace qt
+{
+namespace gl
+{
+namespace draw
+{
+
+class PlacefilePolygons : public DrawItem
+{
+public:
+ explicit PlacefilePolygons(std::shared_ptr context);
+ ~PlacefilePolygons();
+
+ PlacefilePolygons(const PlacefilePolygons&) = delete;
+ PlacefilePolygons& operator=(const PlacefilePolygons&) = delete;
+
+ PlacefilePolygons(PlacefilePolygons&&) noexcept;
+ PlacefilePolygons& operator=(PlacefilePolygons&&) noexcept;
+
+ void Initialize() override;
+ void Render(const QMapLibreGL::CustomLayerRenderParameters& params) override;
+ void Deinitialize() override;
+
+ /**
+ * Resets and prepares the draw item for adding a new set of polygons.
+ */
+ void StartPolygons();
+
+ /**
+ * Adds a placefile polygon to the internal draw list.
+ *
+ * @param [in] di Placefile polygon
+ */
+ void AddPolygon(const std::shared_ptr& di);
+
+ /**
+ * Finalizes the draw item after adding new polygons.
+ */
+ void FinishPolygons();
+
+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 2117932b..c98338f7 100644
--- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp
+++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp
@@ -1,5 +1,6 @@
#include
#include
+#include
#include
#include
#include
@@ -30,7 +31,9 @@ public:
const std::string& placefileName) :
self_ {self},
placefileName_ {placefileName},
- placefileIcons_ {std::make_shared(context)}
+ placefileIcons_ {std::make_shared(context)},
+ placefilePolygons_ {
+ std::make_shared(context)}
{
ConnectSignals();
}
@@ -41,6 +44,9 @@ public:
void
RenderIconDrawItem(const QMapLibreGL::CustomLayerRenderParameters& params,
const std::shared_ptr& di);
+ void RenderPolygonDrawItem(
+ const QMapLibreGL::CustomLayerRenderParameters& params,
+ const std::shared_ptr& di);
void
RenderTextDrawItem(const QMapLibreGL::CustomLayerRenderParameters& params,
const std::shared_ptr& di);
@@ -67,7 +73,8 @@ public:
bool thresholded_ {true};
ImFont* monospaceFont_ {};
- std::shared_ptr placefileIcons_;
+ std::shared_ptr placefileIcons_;
+ std::shared_ptr placefilePolygons_;
};
PlacefileLayer::PlacefileLayer(std::shared_ptr context,
@@ -76,6 +83,7 @@ PlacefileLayer::PlacefileLayer(std::shared_ptr context,
p(std::make_unique(this, context, placefileName))
{
AddDrawItem(p->placefileIcons_);
+ AddDrawItem(p->placefilePolygons_);
}
PlacefileLayer::~PlacefileLayer() = default;
@@ -135,6 +143,28 @@ void PlacefileLayer::Impl::RenderIconDrawItem(
}
}
+void PlacefileLayer::Impl::RenderPolygonDrawItem(
+ const QMapLibreGL::CustomLayerRenderParameters& params,
+ const std::shared_ptr& di)
+{
+ if (!dirty_)
+ {
+ return;
+ }
+
+ auto distance = (thresholded_) ?
+ util::GeographicLib::GetDistance(params.latitude,
+ params.longitude,
+ di->center_.latitude_,
+ di->center_.longitude_) :
+ 0;
+
+ if (distance < di->threshold_)
+ {
+ placefilePolygons_->AddPolygon(di);
+ }
+}
+
void PlacefileLayer::Impl::RenderTextDrawItem(
const QMapLibreGL::CustomLayerRenderParameters& params,
const std::shared_ptr& di)
@@ -269,6 +299,9 @@ void PlacefileLayer::Render(
p->placefileIcons_->Reset();
p->placefileIcons_->SetIconFiles(placefile->icon_files(),
placefile->name());
+
+ // Reset Placefile Polygons
+ p->placefilePolygons_->StartPolygons();
}
for (auto& drawItem : placefile->GetDrawItems())
@@ -287,10 +320,23 @@ void PlacefileLayer::Render(
std::static_pointer_cast(drawItem));
break;
+ case gr::Placefile::ItemType::Polygon:
+ p->RenderPolygonDrawItem(
+ params,
+ std::static_pointer_cast(
+ drawItem));
+ break;
+
default:
break;
}
}
+
+ if (p->dirty_)
+ {
+ // Finish Placefile Polygons
+ p->placefilePolygons_->FinishPolygons();
+ }
}
DrawLayer::Render(params);
diff --git a/wxdata/include/scwx/gr/placefile.hpp b/wxdata/include/scwx/gr/placefile.hpp
index 0da773a5..8330aa4f 100644
--- a/wxdata/include/scwx/gr/placefile.hpp
+++ b/wxdata/include/scwx/gr/placefile.hpp
@@ -1,5 +1,7 @@
#pragma once
+#include
+
#include
#include
#include
@@ -157,6 +159,7 @@ public:
};
std::vector> contours_ {};
+ scwx::common::Coordinate center_ {};
};
bool IsValid() const;
diff --git a/wxdata/source/scwx/gr/placefile.cpp b/wxdata/source/scwx/gr/placefile.cpp
index fa98a6a5..1725dd64 100644
--- a/wxdata/source/scwx/gr/placefile.cpp
+++ b/wxdata/source/scwx/gr/placefile.cpp
@@ -729,17 +729,30 @@ void Placefile::Impl::ProcessElementEnd()
{
if (currentStatement_ == DrawingStatement::Polygon)
{
+ auto di = std::static_pointer_cast(currentDrawItem_);
+
// Complete the current contour when ending the Polygon statement
if (!currentPolygonContour_.empty())
{
- auto& contours =
- std::static_pointer_cast(currentDrawItem_)
- ->contours_;
+ auto& contours = di->contours_;
auto& newContour =
contours.emplace_back(std::vector {});
newContour.swap(currentPolygonContour_);
}
+
+ if (!di->contours_.empty())
+ {
+ std::vector coordinates {};
+ std::transform(di->contours_[0].cbegin(),
+ di->contours_[0].cend(),
+ std::back_inserter(coordinates),
+ [](auto& element) {
+ return common::Coordinate {element.latitude_,
+ element.longitude_};
+ });
+ di->center_ = GetCentroid(coordinates);
+ }
}
}