From cdef5a9938cb9abc8a2aa6ee456bb70df56f00d9 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 11 Aug 2023 00:45:42 -0500 Subject: [PATCH] Placefile polygon rendering --- scwx-qt/gl/map_color.vert | 23 + scwx-qt/scwx-qt.cmake | 3 + scwx-qt/scwx-qt.qrc | 1 + .../scwx/qt/gl/draw/placefile_polygons.cpp | 393 ++++++++++++++++++ .../scwx/qt/gl/draw/placefile_polygons.hpp | 60 +++ .../source/scwx/qt/map/placefile_layer.cpp | 50 ++- wxdata/include/scwx/gr/placefile.hpp | 3 + wxdata/source/scwx/gr/placefile.cpp | 19 +- 8 files changed, 547 insertions(+), 5 deletions(-) create mode 100644 scwx-qt/gl/map_color.vert create mode 100644 scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.cpp create mode 100644 scwx-qt/source/scwx/qt/gl/draw/placefile_polygons.hpp 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); + } } }