From c85e4cef58b614eb4e0abd75ecdc81e2e0ba22dd Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Sun, 20 Aug 2023 22:37:46 -0500 Subject: [PATCH] Basic placefile lines rendering Desired to make the line styling look more like warning boxes --- scwx-qt/scwx-qt.cmake | 2 + .../scwx/qt/gl/draw/placefile_lines.cpp | 386 ++++++++++++++++++ .../scwx/qt/gl/draw/placefile_lines.hpp | 61 +++ .../source/scwx/qt/map/placefile_layer.cpp | 13 + .../source/scwx/qt/util/geographic_lib.cpp | 13 +- .../source/scwx/qt/util/geographic_lib.hpp | 14 + 6 files changed, 487 insertions(+), 2 deletions(-) create mode 100644 scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp create mode 100644 scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 129d3f14..fd8bd87a 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -58,12 +58,14 @@ 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_lines.hpp source/scwx/qt/gl/draw/placefile_polygons.hpp source/scwx/qt/gl/draw/placefile_text.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_lines.cpp source/scwx/qt/gl/draw/placefile_polygons.cpp source/scwx/qt/gl/draw/placefile_text.cpp source/scwx/qt/gl/draw/rectangle.cpp) diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp new file mode 100644 index 00000000..50714a4e --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.cpp @@ -0,0 +1,386 @@ +#include +#include +#include +#include +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +static const std::string logPrefix_ = "scwx::qt::gl::draw::placefile_lines"; +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; + +static const std::string kTextureName_ = "lines/default-1x7"; + +class PlacefileLines::Impl +{ +public: + explicit Impl(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() {} + + std::shared_ptr context_; + + bool dirty_ {false}; + bool thresholded_ {false}; + + std::mutex lineMutex_; + + std::vector> + currentLineList_ {}; + std::vector> + newLineList_ {}; + + std::size_t currentNumLines_ {}; + std::size_t newNumLines_ {}; + + std::vector lineBuffer_ {}; + std::vector thresholdBuffer_ {}; + + std::shared_ptr shaderProgram_; + GLint uMVPMatrixLocation_; + GLint uMapMatrixLocation_; + GLint uMapScreenCoordLocation_; + GLint uMapDistanceLocation_; + + GLuint vao_; + std::array vbo_; + + GLsizei numVertices_; + + void UpdateBuffers(); + void Update(bool textureAtlasChanged); +}; + +PlacefileLines::PlacefileLines(std::shared_ptr context) : + DrawItem(context->gl()), p(std::make_unique(context)) +{ +} +PlacefileLines::~PlacefileLines() = default; + +PlacefileLines::PlacefileLines(PlacefileLines&&) noexcept = default; +PlacefileLines& PlacefileLines::operator=(PlacefileLines&&) noexcept = default; + +void PlacefileLines::set_thresholded(bool thresholded) +{ + p->thresholded_ = thresholded; +} + +void PlacefileLines::Initialize() +{ + gl::OpenGLFunctions& gl = p->context_->gl(); + + p->shaderProgram_ = p->context_->GetShaderProgram( + {{GL_VERTEX_SHADER, ":/gl/geo_texture2d.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); + + // 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); + + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_[1]); + gl.glBufferData(GL_ARRAY_BUFFER, 0u, nullptr, GL_DYNAMIC_DRAW); + + // aThreshold + gl.glVertexAttribIPointer(5, // + 1, + GL_INT, + 0, + static_cast(0)); + gl.glVertexAttribDivisor(5, 2); // One value per rectangle + gl.glEnableVertexAttribArray(5); + + p->dirty_ = true; +} + +void PlacefileLines::Render( + const QMapLibreGL::CustomLayerRenderParameters& params, + bool textureAtlasChanged) +{ + std::unique_lock lock {p->lineMutex_}; + + if (!p->currentLineList_.empty()) + { + gl::OpenGLFunctions& gl = p->context_->gl(); + + gl.glBindVertexArray(p->vao_); + + p->Update(textureAtlasChanged); + 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); + } + + // Interpolate texture coordinates + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Draw icons + gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); + } +} + +void PlacefileLines::Deinitialize() +{ + gl::OpenGLFunctions& gl = p->context_->gl(); + + gl.glDeleteVertexArrays(1, &p->vao_); + gl.glDeleteBuffers(2, p->vbo_.data()); + + std::unique_lock lock {p->lineMutex_}; + + p->currentLineList_.clear(); + p->lineBuffer_.clear(); + p->thresholdBuffer_.clear(); +} + +void PlacefileLines::StartLines() +{ + // Clear the new buffer + p->newLineList_.clear(); + + p->newNumLines_ = 0u; +} + +void PlacefileLines::AddLine( + const std::shared_ptr& di) +{ + if (di != nullptr) + { + p->newLineList_.emplace_back(di); + if (!di->elements_.empty()) + { + p->newNumLines_ += di->elements_.size() - 1; + } + } +} + +void PlacefileLines::FinishLines() +{ + std::unique_lock lock {p->lineMutex_}; + + // Swap buffers + p->currentLineList_.swap(p->newLineList_); + + // Clear the new buffers + p->newLineList_.clear(); + + // Update the number of lines + p->currentNumLines_ = p->newNumLines_; + + // Mark the draw item dirty + p->dirty_ = true; +} + +void PlacefileLines::Impl::UpdateBuffers() +{ + auto texture = + util::TextureAtlas::Instance().GetTextureAttributes(kTextureName_); + + // Texture coordinates (rotated) + const float ls = texture.tTop_; + const float rs = texture.tBottom_; + const float tt = texture.sLeft_; + const float bt = texture.sRight_; + + lineBuffer_.clear(); + lineBuffer_.reserve(currentNumLines_ * kBufferLength); + thresholdBuffer_.clear(); + thresholdBuffer_.reserve(currentNumLines_); + numVertices_ = 0; + + for (auto& di : currentLineList_) + { + // Threshold value + units::length::nautical_miles threshold = di->threshold_; + GLint thresholdValue = static_cast(std::round(threshold.value())); + + // TODO: Angle in degrees + // const float a = 0.0f; + + // Line width / half width + const float lw = static_cast(di->width_); + const float hw = lw * 0.5f; + + // For each element pair inside a Line statement + for (std::size_t i = 0; i < di->elements_.size() - 1; ++i) + { + // Latitude and longitude coordinates in degrees + const float lat1 = static_cast(di->elements_[i].latitude_); + const float lon1 = static_cast(di->elements_[i].longitude_); + const float lat2 = static_cast(di->elements_[i + 1].latitude_); + const float lon2 = static_cast(di->elements_[i + 1].longitude_); + + // TODO: Base X/Y offsets in pixels + // const float x1 = static_cast(di->elements_[i].x_); + // const float y1 = static_cast(di->elements_[i].y_); + // const float x2 = static_cast(di->elements_[i + 1].x_); + // const float y2 = static_cast(di->elements_[i + 1].y_); + + // TODO: Refactor this to placefile update time instead of buffer time + const units::angle::degrees angle = + util::GeographicLib::GetAngle(lat1, lon1, lat2, lon2); + const float a = static_cast(angle.value()); + + // Final X/Y offsets in pixels + const float lx = -hw; + const float rx = +hw; + const float ty = +hw; + const float by = -hw; + + // Modulate color + const float mc0 = di->color_[0] / 255.0f; + const float mc1 = di->color_[1] / 255.0f; + const float mc2 = di->color_[2] / 255.0f; + const float mc3 = di->color_[3] / 255.0f; + + lineBuffer_.insert( + lineBuffer_.end(), + { + // Icon + lat1, lon1, lx, by, ls, bt, mc0, mc1, mc2, mc3, a, // BL + lat2, lon2, lx, ty, ls, tt, mc0, mc1, mc2, mc3, a, // TL + lat1, lon1, rx, by, rs, bt, mc0, mc1, mc2, mc3, a, // BR + lat1, lon1, rx, by, rs, bt, mc0, mc1, mc2, mc3, a, // BR + lat2, lon2, rx, ty, rs, tt, mc0, mc1, mc2, mc3, a, // TR + lat2, lon2, lx, ty, ls, tt, mc0, mc1, mc2, mc3, a // TL + }); + thresholdBuffer_.push_back(thresholdValue); + } + } + + dirty_ = true; +} + +void PlacefileLines::Impl::Update(bool textureAtlasChanged) +{ + // If the texture atlas has changed + if (dirty_ || textureAtlasChanged) + { + // Update OpenGL buffer data + UpdateBuffers(); + + gl::OpenGLFunctions& gl = context_->gl(); + + // Buffer vertex data + gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[0]); + gl.glBufferData(GL_ARRAY_BUFFER, + sizeof(float) * lineBuffer_.size(), + lineBuffer_.data(), + GL_DYNAMIC_DRAW); + + // Buffer threshold data + gl.glBindBuffer(GL_ARRAY_BUFFER, vbo_[1]); + gl.glBufferData(GL_ARRAY_BUFFER, + sizeof(GLint) * thresholdBuffer_.size(), + thresholdBuffer_.data(), + GL_DYNAMIC_DRAW); + + numVertices_ = + static_cast(lineBuffer_.size() / kVerticesPerRectangle); + } + + dirty_ = false; +} + +} // namespace draw +} // namespace gl +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp new file mode 100644 index 00000000..2252e441 --- /dev/null +++ b/scwx-qt/source/scwx/qt/gl/draw/placefile_lines.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace gl +{ +namespace draw +{ + +class PlacefileLines : public DrawItem +{ +public: + explicit PlacefileLines(std::shared_ptr context); + ~PlacefileLines(); + + PlacefileLines(const PlacefileLines&) = delete; + PlacefileLines& operator=(const PlacefileLines&) = delete; + + PlacefileLines(PlacefileLines&&) noexcept; + PlacefileLines& operator=(PlacefileLines&&) noexcept; + + void set_thresholded(bool thresholded); + + void Initialize() override; + void Render(const QMapLibreGL::CustomLayerRenderParameters& params, + bool textureAtlasChanged) override; + void Deinitialize() override; + + /** + * Resets and prepares the draw item for adding a new set of lines. + */ + void StartLines(); + + /** + * Adds a placefile line to the internal draw list. + * + * @param [in] di Placefile line + */ + void AddLine(const std::shared_ptr& di); + + /** + * Finalizes the draw item after adding new lines. + */ + void FinishLines(); + +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 d1bb23b2..b6ec78d4 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 @@ -27,6 +28,7 @@ public: self_ {self}, placefileName_ {placefileName}, placefileIcons_ {std::make_shared(context)}, + placefileLines_ {std::make_shared(context)}, placefilePolygons_ { std::make_shared(context)}, placefileText_ { @@ -46,6 +48,7 @@ public: std::mutex dataMutex_ {}; std::shared_ptr placefileIcons_; + std::shared_ptr placefileLines_; std::shared_ptr placefilePolygons_; std::shared_ptr placefileText_; }; @@ -56,6 +59,7 @@ PlacefileLayer::PlacefileLayer(std::shared_ptr context, p(std::make_unique(this, context, placefileName)) { AddDrawItem(p->placefileIcons_); + AddDrawItem(p->placefileLines_); AddDrawItem(p->placefilePolygons_); AddDrawItem(p->placefileText_); @@ -119,6 +123,7 @@ void PlacefileLayer::Render( bool thresholded = placefileManager->placefile_thresholded(placefile->name()); p->placefileIcons_->set_thresholded(thresholded); + p->placefileLines_->set_thresholded(thresholded); p->placefilePolygons_->set_thresholded(thresholded); p->placefileText_->set_thresholded(thresholded); } @@ -156,6 +161,7 @@ void PlacefileLayer::ReloadData() // Start draw items p->placefileIcons_->StartIcons(); + p->placefileLines_->StartLines(); p->placefilePolygons_->StartPolygons(); p->placefileText_->StartText(); @@ -178,6 +184,12 @@ void PlacefileLayer::ReloadData() drawItem)); break; + case gr::Placefile::ItemType::Line: + p->placefileLines_->AddLine( + std::static_pointer_cast( + drawItem)); + break; + case gr::Placefile::ItemType::Polygon: p->placefilePolygons_->AddPolygon( std::static_pointer_cast( @@ -191,6 +203,7 @@ void PlacefileLayer::ReloadData() // Finish draw items p->placefileIcons_->FinishIcons(); + p->placefileLines_->FinishLines(); p->placefilePolygons_->FinishPolygons(); p->placefileText_->FinishText(); diff --git a/scwx-qt/source/scwx/qt/util/geographic_lib.cpp b/scwx-qt/source/scwx/qt/util/geographic_lib.cpp index 0496d88a..6718715a 100644 --- a/scwx-qt/source/scwx/qt/util/geographic_lib.cpp +++ b/scwx-qt/source/scwx/qt/util/geographic_lib.cpp @@ -18,12 +18,21 @@ const ::GeographicLib::Geodesic& DefaultGeodesic() return geodesic_; } +units::angle::degrees +GetAngle(double lat1, double lon1, double lat2, double lon2) +{ + double azi1; + double azi2; + DefaultGeodesic().Inverse(lat1, lon1, lat2, lon2, azi1, azi2); + + return units::angle::degrees {azi1}; +} + units::length::meters GetDistance(double lat1, double lon1, double lat2, double lon2) { double distance; - util::GeographicLib::DefaultGeodesic().Inverse( - lat1, lon1, lat2, lon2, distance); + DefaultGeodesic().Inverse(lat1, lon1, lat2, lon2, distance); return units::length::meters {distance}; } diff --git a/scwx-qt/source/scwx/qt/util/geographic_lib.hpp b/scwx-qt/source/scwx/qt/util/geographic_lib.hpp index d93dce2b..d03aac04 100644 --- a/scwx-qt/source/scwx/qt/util/geographic_lib.hpp +++ b/scwx-qt/source/scwx/qt/util/geographic_lib.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace scwx @@ -19,6 +20,19 @@ namespace GeographicLib */ const ::GeographicLib::Geodesic& DefaultGeodesic(); +/** + * Get the angle between two points. + * + * @param [in] lat1 latitude of point 1 (degrees) + * @param [in] lon1 longitude of point 1 (degrees) + * @param [in] lat2 latitude of point 2 (degrees) + * @param [in] lon2 longitude of point 2 (degrees) + * + * @return angle between point 1 and point 2 + */ +units::angle::degrees +GetAngle(double lat1, double lon1, double lat2, double lon2); + /** * Get the distance between two points. *