#include #include #include #include #include #include #if defined(_WIN32) typedef void (*_GLUfuncptr)(void); #endif 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), uMapDistanceLocation_(GL_INVALID_INDEX), vao_ {GL_INVALID_INDEX}, vbo_ {GL_INVALID_INDEX}, numVertices_ {0} { tessellator_ = gluNewTess(); gluTessCallback(tessellator_, // GLU_TESS_COMBINE_DATA, (_GLUfuncptr) &TessellateCombineCallback); gluTessCallback(tessellator_, // GLU_TESS_VERTEX_DATA, (_GLUfuncptr) &TessellateVertexCallback); // Force GLU_TRIANGLES gluTessCallback(tessellator_, // GLU_TESS_EDGE_FLAG, []() {}); gluTessCallback(tessellator_, // GLU_TESS_ERROR, (_GLUfuncptr) &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}; bool thresholded_ {false}; std::vector> polygonList_ {}; boost::container::stable_vector tessCombineBuffer_ {}; std::mutex bufferMutex_ {}; std::vector currentBuffer_ {}; std::vector currentThresholdBuffer_ {}; std::vector newBuffer_ {}; std::vector newThresholdBuffer_ {}; GLUtesselator* tessellator_; std::shared_ptr shaderProgram_; GLint uMVPMatrixLocation_; GLint uMapMatrixLocation_; GLint uMapScreenCoordLocation_; GLint uMapDistanceLocation_; GLuint vao_; std::array vbo_; GLsizei numVertices_; GLint currentThreshold_; }; 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::set_thresholded(bool thresholded) { p->thresholded_ = thresholded; } void PlacefilePolygons::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 PlacefilePolygons::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 PlacefilePolygons::Deinitialize() { gl::OpenGLFunctions& gl = p->context_->gl(); gl.glDeleteVertexArrays(1, &p->vao_); gl.glDeleteBuffers(2, p->vbo_.data()); } void PlacefilePolygons::StartPolygons() { // Clear the new buffer p->newBuffer_.clear(); p->newThresholdBuffer_.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_); p->currentThresholdBuffer_.swap(p->newThresholdBuffer_); // Mark the draw item dirty p->dirty_ = true; } void PlacefilePolygons::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; } } 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_; // Current threshold units::length::nautical_miles threshold = di->threshold_; currentThreshold_ = static_cast(std::round(threshold.value())); 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(); newThresholdBuffer_.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 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_])}); self->newThresholdBuffer_.push_back(self->currentThreshold_); } void PlacefilePolygons::Impl::TessellateErrorCallback(GLenum errorCode) { logger_->error("GL Error: {}", errorCode); } } // namespace draw } // namespace gl } // namespace qt } // namespace scwx