diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 8c3282f8..ab091fe2 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -61,6 +61,7 @@ set(HDR_GL_DRAW source/scwx/qt/gl/draw/draw_item.hpp source/scwx/qt/gl/draw/placefile_lines.hpp source/scwx/qt/gl/draw/placefile_polygons.hpp source/scwx/qt/gl/draw/placefile_text.hpp + source/scwx/qt/gl/draw/placefile_triangles.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 @@ -68,6 +69,7 @@ set(SRC_GL_DRAW source/scwx/qt/gl/draw/draw_item.cpp source/scwx/qt/gl/draw/placefile_lines.cpp source/scwx/qt/gl/draw/placefile_polygons.cpp source/scwx/qt/gl/draw/placefile_text.cpp + source/scwx/qt/gl/draw/placefile_triangles.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 diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.cpp new file mode 100644 index 00000000..693e9cca --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.cpp @@ -0,0 +1,306 @@ +#include +#include +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +static const std::string logPrefix_ = "scwx::qt::gl::draw::placefile_triangles"; +static const auto logger_ = scwx::util::Logger::Create(logPrefix_); + +static constexpr std::size_t kVerticesPerTriangle = 3; +static constexpr std::size_t kPointsPerVertex = 8; + +class PlacefileTriangles::Impl +{ +public: + explicit Impl(const std::shared_ptr& context) : + context_ {context}, + shaderProgram_ {nullptr}, + uMVPMatrixLocation_(GL_INVALID_INDEX), + uMapMatrixLocation_(GL_INVALID_INDEX), + uMapScreenCoordLocation_(GL_INVALID_INDEX), + uMapDistanceLocation_(GL_INVALID_INDEX), + vao_ {GL_INVALID_INDEX}, + vbo_ {GL_INVALID_INDEX}, + numVertices_ {0} + { + } + + ~Impl() {} + + void UpdateBuffers( + const std::shared_ptr& di); + void Update(); + + std::shared_ptr context_; + + bool dirty_ {false}; + bool thresholded_ {false}; + + std::mutex bufferMutex_ {}; + + std::vector currentBuffer_ {}; + std::vector currentThresholdBuffer_ {}; + std::vector newBuffer_ {}; + std::vector newThresholdBuffer_ {}; + + std::shared_ptr shaderProgram_; + GLint uMVPMatrixLocation_; + GLint uMapMatrixLocation_; + GLint uMapScreenCoordLocation_; + GLint uMapDistanceLocation_; + + GLuint vao_; + std::array vbo_; + + GLsizei numVertices_; +}; + +PlacefileTriangles::PlacefileTriangles( + const std::shared_ptr& context) : + DrawItem(context->gl()), p(std::make_unique(context)) +{ +} +PlacefileTriangles::~PlacefileTriangles() = default; + +PlacefileTriangles::PlacefileTriangles(PlacefileTriangles&&) noexcept = default; +PlacefileTriangles& +PlacefileTriangles::operator=(PlacefileTriangles&&) noexcept = default; + +void PlacefileTriangles::set_thresholded(bool thresholded) +{ + p->thresholded_ = thresholded; +} + +void PlacefileTriangles::Initialize() +{ + gl::OpenGLFunctions& gl = p->context_->gl(); + + p->shaderProgram_ = p->context_->GetShaderProgram( + {{GL_VERTEX_SHADER, ":/gl/map_color.vert"}, + {GL_GEOMETRY_SHADER, ":/gl/threshold.geom"}, + {GL_FRAGMENT_SHADER, ":/gl/color.frag"}}); + + p->uMVPMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMVPMatrix"); + p->uMapMatrixLocation_ = p->shaderProgram_->GetUniformLocation("uMapMatrix"); + p->uMapScreenCoordLocation_ = + p->shaderProgram_->GetUniformLocation("uMapScreenCoord"); + p->uMapDistanceLocation_ = + p->shaderProgram_->GetUniformLocation("uMapDistance"); + + gl.glGenVertexArrays(1, &p->vao_); + gl.glGenBuffers(2, p->vbo_.data()); + + gl.glBindVertexArray(p->vao_); + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[0]); + 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); + + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); + gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // aThreshold + gl.glVertexAttribIPointer(3, // + 1, + GL_INT, + 0, + static_cast(0)); + gl.glEnableVertexAttribArray(3); + + p->dirty_ = true; +} + +void PlacefileTriangles::Render( + const QMapLibreGL::CustomLayerRenderParameters& params) +{ + if (!p->currentBuffer_.empty()) + { + gl::OpenGLFunctions& gl = p->context_->gl(); + + gl.glBindVertexArray(p->vao_); + + p->Update(); + p->shaderProgram_->Use(); + UseRotationProjection(params, p->uMVPMatrixLocation_); + UseMapProjection( + params, p->uMapMatrixLocation_, p->uMapScreenCoordLocation_); + + if (p->thresholded_) + { + // If thresholding is enabled, set the map distance + units::length::nautical_miles mapDistance = + util::maplibre::GetMapDistance(params); + gl.glUniform1f(p->uMapDistanceLocation_, mapDistance.value()); + } + else + { + // If thresholding is disabled, set the map distance to 0 + gl.glUniform1f(p->uMapDistanceLocation_, 0.0f); + } + + // Draw icons + gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); + } +} + +void PlacefileTriangles::Deinitialize() +{ + gl::OpenGLFunctions& gl = p->context_->gl(); + + gl.glDeleteVertexArrays(1, &p->vao_); + gl.glDeleteBuffers(2, p->vbo_.data()); + + std::unique_lock lock {p->bufferMutex_}; + + // Clear the current buffers + p->currentBuffer_.clear(); + p->currentThresholdBuffer_.clear(); +} + +void PlacefileTriangles::StartTriangles() +{ + // Clear the new buffers + p->newBuffer_.clear(); + p->newThresholdBuffer_.clear(); +} + +void PlacefileTriangles::AddTriangles( + const std::shared_ptr& di) +{ + if (di != nullptr) + { + p->UpdateBuffers(di); + } +} + +void PlacefileTriangles::FinishTriangles() +{ + std::unique_lock lock {p->bufferMutex_}; + + // Swap buffers + p->currentBuffer_.swap(p->newBuffer_); + p->currentThresholdBuffer_.swap(p->newThresholdBuffer_); + + // Clear the new buffers + p->newBuffer_.clear(); + p->newThresholdBuffer_.clear(); + + // Mark the draw item dirty + p->dirty_ = true; +} + +void PlacefileTriangles::Impl::UpdateBuffers( + const std::shared_ptr& di) +{ + // Threshold value + units::length::nautical_miles threshold = di->threshold_; + GLint thresholdValue = static_cast(std::round(threshold.value())); + + // Default color to "Color" statement + boost::gil::rgba8_pixel_t lastColor = di->color_; + + // For each element inside a Triangles statement, add a vertex + for (auto& element : di->elements_) + { + // Calculate screen coordinate + auto screenCoordinate = util::maplibre::LatLongToScreenCoordinate( + {element.latitude_, element.longitude_}); + + // X/Y offset in pixels + const float x = static_cast(element.x_); + const float y = static_cast(element.y_); + + // Update the most recent color if specified + if (element.color_.has_value()) + { + lastColor = element.color_.value(); + } + + // Color value + const float r = lastColor[0] / 255.0f; + const float g = lastColor[1] / 255.0f; + const float b = lastColor[2] / 255.0f; + const float a = lastColor[3] / 255.0f; + + newBuffer_.insert( + newBuffer_.end(), + {screenCoordinate.x, screenCoordinate.y, x, y, r, g, b, a}); + newThresholdBuffer_.push_back(thresholdValue); + } + + // Remove extra vertices that don't correspond to a full triangle + while (newBuffer_.size() % kVerticesPerTriangle != 0) + { + newBuffer_.pop_back(); + newThresholdBuffer_.pop_back(); + } +} + +void PlacefileTriangles::Impl::Update() +{ + if (dirty_) + { + gl::OpenGLFunctions& gl = context_->gl(); + + std::unique_lock lock {bufferMutex_}; + + // Buffer vertex data + gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); + gl.glBufferData(GL_ARRAY_BUFFER, + sizeof(GLfloat) * currentBuffer_.size(), + currentBuffer_.data(), + GL_DYNAMIC_DRAW); + + // Buffer threshold data + gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); + gl.glBufferData(GL_ARRAY_BUFFER, + sizeof(GLint) * currentThresholdBuffer_.size(), + currentThresholdBuffer_.data(), + GL_DYNAMIC_DRAW); + + numVertices_ = + static_cast(currentBuffer_.size() / kPointsPerVertex); + + dirty_ = false; + } +} + +} // namespace draw +} // namespace gl +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.hpp new file mode 100644 index 00000000..bb98316f --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_triangles.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +class PlacefileTriangles : public DrawItem +{ +public: + explicit PlacefileTriangles(const std::shared_ptr& context); + ~PlacefileTriangles(); + + PlacefileTriangles(const PlacefileTriangles&) = delete; + PlacefileTriangles& operator=(const PlacefileTriangles&) = delete; + + PlacefileTriangles(PlacefileTriangles&&) noexcept; + PlacefileTriangles& operator=(PlacefileTriangles&&) noexcept; + + void set_thresholded(bool thresholded); + + 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 triangles. + */ + void StartTriangles(); + + /** + * Adds placefile triangles to the internal draw list. + * + * @param [in] di Placefile triangles + */ + void + AddTriangles(const std::shared_ptr& di); + + /** + * Finalizes the draw item after adding new triangles. + */ + void FinishTriangles(); + +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 2c1b66ab..b6f8e190 100644 --- a/scwx-qt/source/scwx/qt/map/placefile_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/placefile_layer.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,8 @@ public: placefileLines_ {std::make_shared(context)}, placefilePolygons_ { std::make_shared(context)}, + placefileTriangles_ { + std::make_shared(context)}, placefileText_ { std::make_shared(context, placefileName)} { @@ -47,10 +50,11 @@ public: std::string placefileName_; std::mutex dataMutex_ {}; - std::shared_ptr placefileIcons_; - std::shared_ptr placefileLines_; - std::shared_ptr placefilePolygons_; - std::shared_ptr placefileText_; + std::shared_ptr placefileIcons_; + std::shared_ptr placefileLines_; + std::shared_ptr placefilePolygons_; + std::shared_ptr placefileTriangles_; + std::shared_ptr placefileText_; }; PlacefileLayer::PlacefileLayer(const std::shared_ptr& context, @@ -59,8 +63,9 @@ PlacefileLayer::PlacefileLayer(const std::shared_ptr& context, p(std::make_unique(this, context, placefileName)) { AddDrawItem(p->placefileIcons_); - AddDrawItem(p->placefileLines_); AddDrawItem(p->placefilePolygons_); + AddDrawItem(p->placefileTriangles_); + AddDrawItem(p->placefileLines_); AddDrawItem(p->placefileText_); ReloadData(); @@ -125,6 +130,7 @@ void PlacefileLayer::Render( p->placefileIcons_->set_thresholded(thresholded); p->placefileLines_->set_thresholded(thresholded); p->placefilePolygons_->set_thresholded(thresholded); + p->placefileTriangles_->set_thresholded(thresholded); p->placefileText_->set_thresholded(thresholded); } @@ -163,6 +169,7 @@ void PlacefileLayer::ReloadData() p->placefileIcons_->StartIcons(); p->placefileLines_->StartLines(); p->placefilePolygons_->StartPolygons(); + p->placefileTriangles_->StartTriangles(); p->placefileText_->StartText(); p->placefileIcons_->SetIconFiles(placefile->icon_files(), @@ -196,6 +203,12 @@ void PlacefileLayer::ReloadData() drawItem)); break; + case gr::Placefile::ItemType::Triangles: + p->placefileTriangles_->AddTriangles( + std::static_pointer_cast( + drawItem)); + break; + default: break; } @@ -205,6 +218,7 @@ void PlacefileLayer::ReloadData() p->placefileIcons_->FinishIcons(); p->placefileLines_->FinishLines(); p->placefilePolygons_->FinishPolygons(); + p->placefileTriangles_->FinishTriangles(); p->placefileText_->FinishText(); Q_EMIT DataReloaded();