diff --git a/scwx-qt/CMakeLists.txt b/scwx-qt/CMakeLists.txt index 80c6ae22..8a3114c5 100644 --- a/scwx-qt/CMakeLists.txt +++ b/scwx-qt/CMakeLists.txt @@ -1,2 +1,8 @@ cmake_minimum_required(VERSION 3.11) + +set_property(DIRECTORY + APPEND + PROPERTY CMAKE_CONFIGURE_DEPENDS + scwx-qt.cmake) + include(scwx-qt.cmake) diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 41242d13..1dc3bce5 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -11,6 +11,8 @@ set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +find_package(Boost) + # QtCreator supports the following variables for Android, which are identical to qmake Android variables. # Check https://doc.qt.io/qt/deployment-android.html for more information. # They need to be set before the find_package( ...) calls below. @@ -47,8 +49,10 @@ set(HDR_MAIN source/scwx/qt/main/main_window.hpp) set(SRC_MAIN source/scwx/qt/main/main.cpp source/scwx/qt/main/main_window.cpp) set(UI_MAIN source/scwx/qt/main/main_window.ui) -set(HDR_MAP source/scwx/qt/map/map_widget.hpp) -set(SRC_MAP source/scwx/qt/map/map_widget.cpp) +set(HDR_MAP source/scwx/qt/map/map_widget.hpp + source/scwx/qt/map/triangle_layer.hpp) +set(SRC_MAP source/scwx/qt/map/map_widget.cpp + source/scwx/qt/map/triangle_layer.cpp) set(TS_FILES ts/scwx_en_US.ts) @@ -76,4 +80,6 @@ target_include_directories(scwx-qt PRIVATE ${scwx-qt_SOURCE_DIR}/source) target_link_libraries(scwx-qt PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::OpenGLWidgets - qmapboxgl) + Boost::log + qmapboxgl + opengl32) diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index a5478a70..40b9e88f 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -1,5 +1,7 @@ #include "map_widget.hpp" +#include + #include #include #include @@ -44,8 +46,26 @@ void MapWidget::changeStyle() { currentStyleIndex = 0; } +} +void MapWidget::AddLayers() +{ + // QMapboxGL::addCustomLayer will take ownership of the QScopedPointer + QScopedPointer pHost(new TriangleLayer()); - sourceAdded_ = false; + QString before = "ferry"; + + for (const QString& layer : map_->layerIds()) + { + // Draw below tunnels, ferries and roads + if (layer.startsWith("tunnel") || layer.startsWith("ferry") || + layer.startsWith("road")) + { + before = layer; + break; + } + } + + map_->addCustomLayer("triangle", pHost, before); } void MapWidget::keyPressEvent(QKeyEvent* ev) @@ -55,362 +75,9 @@ void MapWidget::keyPressEvent(QKeyEvent* ev) case Qt::Key_S: changeStyle(); break; case Qt::Key_L: { - if (sourceAdded_) + for (const QString& layer : map_->layerIds()) { - return; - } - - sourceAdded_ = true; - - // Not in all styles, but will work on streets - QString before = "waterway-label"; - - QFile geojson(":source1.geojson"); - geojson.open(QIODevice::ReadOnly); - - // The data source for the route line and markers - QVariantMap routeSource; - routeSource["type"] = "geojson"; - routeSource["data"] = geojson.readAll(); - map_->addSource("routeSource", routeSource); - - // The route case, painted before the route - QVariantMap routeCase; - routeCase["id"] = "routeCase"; - routeCase["type"] = "line"; - routeCase["source"] = "routeSource"; - map_->addLayer(routeCase, before); - - map_->setPaintProperty("routeCase", "line-color", QColor("white")); - map_->setPaintProperty("routeCase", "line-width", 20.0); - map_->setLayoutProperty("routeCase", "line-join", "round"); - map_->setLayoutProperty("routeCase", "line-cap", "round"); - - // The route, painted on top of the route case - QVariantMap route; - route["id"] = "route"; - route["type"] = "line"; - route["source"] = "routeSource"; - map_->addLayer(route, before); - - map_->setPaintProperty("route", "line-color", QColor("blue")); - map_->setPaintProperty("route", "line-width", 8.0); - map_->setLayoutProperty("route", "line-join", "round"); - map_->setLayoutProperty("route", "line-cap", "round"); - - QVariantList lineDashArray; - lineDashArray.append(1); - lineDashArray.append(2); - - map_->setPaintProperty("route", "line-dasharray", lineDashArray); - - // Markers at the beginning and end of the route - map_->addImage("label-arrow", QImage(":label-arrow.svg")); - map_->addImage("label-background", QImage(":label-background.svg")); - - QVariantMap markerArrow; - markerArrow["id"] = "markerArrow"; - markerArrow["type"] = "symbol"; - markerArrow["source"] = "routeSource"; - map_->addLayer(markerArrow); - - map_->setLayoutProperty("markerArrow", "icon-image", "label-arrow"); - map_->setLayoutProperty("markerArrow", "icon-size", 0.5); - map_->setLayoutProperty("markerArrow", "icon-ignore-placement", true); - - QVariantList arrowOffset; - arrowOffset.append(0.0); - arrowOffset.append(-15.0); - map_->setLayoutProperty("markerArrow", "icon-offset", arrowOffset); - - QVariantMap markerBackground; - markerBackground["id"] = "markerBackground"; - markerBackground["type"] = "symbol"; - markerBackground["source"] = "routeSource"; - map_->addLayer(markerBackground); - - map_->setLayoutProperty( - "markerBackground", "icon-image", "label-background"); - map_->setLayoutProperty("markerBackground", "text-field", "{name}"); - map_->setLayoutProperty("markerBackground", "icon-text-fit", "both"); - map_->setLayoutProperty( - "markerBackground", "icon-ignore-placement", true); - map_->setLayoutProperty( - "markerBackground", "text-ignore-placement", true); - map_->setLayoutProperty("markerBackground", "text-anchor", "left"); - map_->setLayoutProperty("markerBackground", "text-size", 16.0); - map_->setLayoutProperty("markerBackground", "text-padding", 0.0); - map_->setLayoutProperty("markerBackground", "text-line-height", 1.0); - map_->setLayoutProperty("markerBackground", "text-max-width", 8.0); - - QVariantList iconTextFitPadding; - iconTextFitPadding.append(15.0); - iconTextFitPadding.append(10.0); - iconTextFitPadding.append(15.0); - iconTextFitPadding.append(10.0); - map_->setLayoutProperty( - "markerBackground", "icon-text-fit-padding", iconTextFitPadding); - - QVariantList backgroundOffset; - backgroundOffset.append(-0.5); - backgroundOffset.append(-1.5); - map_->setLayoutProperty( - "markerBackground", "text-offset", backgroundOffset); - - map_->setPaintProperty("markerBackground", "text-color", QColor("white")); - - QVariantList filterExpression; - filterExpression.append("=="); - filterExpression.append("$type"); - filterExpression.append("Point"); - - QVariantList filter; - filter.append(filterExpression); - - map_->setFilter("markerArrow", filter); - map_->setFilter("markerBackground", filter); - - // Tilt the labels when tilting the map and make them larger - map_->setLayoutProperty("road-label-large", "text-size", 30.0); - map_->setLayoutProperty( - "road-label-large", "text-pitch-alignment", "viewport"); - - map_->setLayoutProperty("road-label-medium", "text-size", 30.0); - map_->setLayoutProperty( - "road-label-medium", "text-pitch-alignment", "viewport"); - - map_->setLayoutProperty( - "road-label-small", "text-pitch-alignment", "viewport"); - map_->setLayoutProperty("road-label-small", "text-size", 30.0); - - // Buildings extrusion - QVariantMap buildings; - buildings["id"] = "3d-buildings"; - buildings["source"] = "composite"; - buildings["source-layer"] = "building"; - buildings["type"] = "fill-extrusion"; - buildings["minzoom"] = 15.0; - map_->addLayer(buildings); - - QVariantList buildingsFilterExpression; - buildingsFilterExpression.append("=="); - buildingsFilterExpression.append("extrude"); - buildingsFilterExpression.append("true"); - - QVariantList buildingsFilter; - buildingsFilter.append(buildingsFilterExpression); - - map_->setFilter("3d-buildings", buildingsFilterExpression); - - QString fillExtrusionColorJSON = R"JSON( - [ - "interpolate", - ["linear"], - ["get", "height"], - 0.0, "blue", - 20.0, "royalblue", - 40.0, "cyan", - 60.0, "lime", - 80.0, "yellow", - 100.0, "red" - ] - )JSON"; - - map_->setPaintProperty( - "3d-buildings", "fill-extrusion-color", fillExtrusionColorJSON); - map_->setPaintProperty("3d-buildings", "fill-extrusion-opacity", .6); - - QVariantMap extrusionHeight; - extrusionHeight["type"] = "identity"; - extrusionHeight["property"] = "height"; - - map_->setPaintProperty( - "3d-buildings", "fill-extrusion-height", extrusionHeight); - - QVariantMap extrusionBase; - extrusionBase["type"] = "identity"; - extrusionBase["property"] = "min_height"; - - map_->setPaintProperty( - "3d-buildings", "fill-extrusion-base", extrusionBase); - } - break; - case Qt::Key_1: - { - if (symbolAnnotationId_.isNull()) - { - QMapbox::Coordinate coordinate = map_->coordinate(); - QMapbox::SymbolAnnotation symbol {coordinate, "default_marker"}; - map_->addAnnotationIcon("default_marker", - QImage(":default_marker.svg")); - symbolAnnotationId_ = map_->addAnnotation( - QVariant::fromValue(symbol)); - } - else - { - map_->removeAnnotation(symbolAnnotationId_.toUInt()); - symbolAnnotationId_.clear(); - } - } - break; - case Qt::Key_2: - { - if (lineAnnotationId_.isNull()) - { - QMapbox::Coordinates coordinates; - coordinates.push_back(map_->coordinateForPixel({0, 0})); - coordinates.push_back(map_->coordinateForPixel( - {qreal(size().width()), qreal(size().height())})); - - QMapbox::CoordinatesCollection collection; - collection.push_back(coordinates); - - QMapbox::CoordinatesCollections lineGeometry; - lineGeometry.push_back(collection); - - QMapbox::ShapeAnnotationGeometry annotationGeometry( - QMapbox::ShapeAnnotationGeometry::LineStringType, lineGeometry); - - QMapbox::LineAnnotation line; - line.geometry = annotationGeometry; - line.opacity = 0.5f; - line.width = 1.0f; - line.color = Qt::red; - lineAnnotationId_ = map_->addAnnotation( - QVariant::fromValue(line)); - } - else - { - map_->removeAnnotation(lineAnnotationId_.toUInt()); - lineAnnotationId_.clear(); - } - } - break; - case Qt::Key_3: - { - if (fillAnnotationId_.isNull()) - { - QMapbox::Coordinates coordinates; - coordinates.push_back( - map_->coordinateForPixel({qreal(size().width()), 0})); - coordinates.push_back(map_->coordinateForPixel( - {qreal(size().width()), qreal(size().height())})); - coordinates.push_back( - map_->coordinateForPixel({0, qreal(size().height())})); - coordinates.push_back(map_->coordinateForPixel({0, 0})); - - QMapbox::CoordinatesCollection collection; - collection.push_back(coordinates); - - QMapbox::CoordinatesCollections fillGeometry; - fillGeometry.push_back(collection); - - QMapbox::ShapeAnnotationGeometry annotationGeometry( - QMapbox::ShapeAnnotationGeometry::PolygonType, fillGeometry); - - QMapbox::FillAnnotation fill; - fill.geometry = annotationGeometry; - fill.opacity = 0.5f; - fill.color = Qt::green; - fill.outlineColor = QVariant::fromValue(QColor(Qt::black)); - fillAnnotationId_ = map_->addAnnotation( - QVariant::fromValue(fill)); - } - else - { - map_->removeAnnotation(fillAnnotationId_.toUInt()); - fillAnnotationId_.clear(); - } - } - break; - case Qt::Key_5: - { - if (map_->layerExists("circleLayer")) - { - map_->removeLayer("circleLayer"); - map_->removeSource("circleSource"); - } - else - { - QMapbox::Coordinates coordinates; - coordinates.push_back(map_->coordinate()); - - QMapbox::CoordinatesCollection collection; - collection.push_back(coordinates); - - QMapbox::CoordinatesCollections point; - point.push_back(collection); - - QMapbox::Feature feature(QMapbox::Feature::PointType, point, {}, {}); - - QVariantMap circleSource; - circleSource["type"] = "geojson"; - circleSource["data"] = QVariant::fromValue(feature); - map_->addSource("circleSource", circleSource); - - QVariantMap circle; - circle["id"] = "circleLayer"; - circle["type"] = "circle"; - circle["source"] = "circleSource"; - map_->addLayer(circle); - - map_->setPaintProperty("circleLayer", "circle-radius", 10.0); - map_->setPaintProperty("circleLayer", "circle-color", QColor("black")); - } - } - break; - case Qt::Key_6: - { - if (map_->layerExists("innerCirclesLayer") || - map_->layerExists("outerCirclesLayer")) - { - map_->removeLayer("innerCirclesLayer"); - map_->removeLayer("outerCirclesLayer"); - map_->removeSource("innerCirclesSource"); - map_->removeSource("outerCirclesSource"); - } - else - { - auto makePoint = [&](double dx, double dy, const QString& color) { - auto coordinate = map_->coordinate(); - coordinate.first += dx; - coordinate.second += dy; - return QMapbox::Feature {QMapbox::Feature::PointType, - {{{coordinate}}}, - {{"color", color}}, - {}}; - }; - - // multiple features by QVector - QVector inner {makePoint(0.001, 0, "red"), - makePoint(0, 0.001, "green"), - makePoint(0, -0.001, "blue")}; - - map_->addSource( - "innerCirclesSource", - {{"type", "geojson"}, {"data", QVariant::fromValue(inner)}}); - map_->addLayer({{"id", "innerCirclesLayer"}, - {"type", "circle"}, - {"source", "innerCirclesSource"}}); - - // multiple features by QList - QList outer {makePoint(0.002, 0.002, "cyan"), - makePoint(-0.002, 0.002, "magenta"), - makePoint(0.002, -0.002, "yellow"), - makePoint(-0.002, -0.002, "black")}; - - map_->addSource( - "outerCirclesSource", - {{"type", "geojson"}, {"data", QVariant::fromValue(outer)}}); - map_->addLayer({{"id", "outerCirclesLayer"}, - {"type", "circle"}, - {"source", "outerCirclesSource"}}); - - QVariantList getColor {"get", "color"}; - map_->setPaintProperty("innerCirclesLayer", "circle-radius", 10.0); - map_->setPaintProperty("innerCirclesLayer", "circle-color", getColor); - map_->setPaintProperty("outerCirclesLayer", "circle-radius", 15.0); - map_->setPaintProperty("outerCirclesLayer", "circle-color", getColor); + qDebug() << "Layer: " << layer; } } break; @@ -512,6 +179,8 @@ void MapWidget::initializeGL() map_->setStyleUrl(styleUrl); setWindowTitle(QString("Mapbox GL: ") + styleUrl); } + + connect(map_.get(), &QMapboxGL::mapChanged, this, &MapWidget::mapChanged); } void MapWidget::paintGL() @@ -523,5 +192,13 @@ void MapWidget::paintGL() map_->render(); } +void MapWidget::mapChanged(QMapboxGL::MapChange mapChange) +{ + if (mapChange == QMapboxGL::MapChangeDidFinishLoadingStyle) + { + AddLayers(); + } +} + } // namespace qt } // namespace scwx diff --git a/scwx-qt/source/scwx/qt/map/map_widget.hpp b/scwx-qt/source/scwx/qt/map/map_widget.hpp index 95deb747..768c028d 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.hpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.hpp @@ -38,6 +38,8 @@ private: void initializeGL() override final; void paintGL() override final; + void AddLayers(); + QPointF lastPos_; QMapboxGLSettings settings_; @@ -45,11 +47,8 @@ private: uint64_t frameDraws_ = 0; - QVariant symbolAnnotationId_; - QVariant lineAnnotationId_; - QVariant fillAnnotationId_; - - bool sourceAdded_ = false; +private slots: + void mapChanged(QMapboxGL::MapChange); }; } // namespace qt diff --git a/scwx-qt/source/scwx/qt/map/triangle_layer.cpp b/scwx-qt/source/scwx/qt/map/triangle_layer.cpp new file mode 100644 index 00000000..79c0d191 --- /dev/null +++ b/scwx-qt/source/scwx/qt/map/triangle_layer.cpp @@ -0,0 +1,167 @@ +#include + +#include + +#include + +namespace scwx +{ +namespace qt +{ + +static const std::string logPrefix_ = "[scwx::map::triangle_layer] "; + +class TriangleLayerImpl +{ +public: + explicit TriangleLayerImpl() : + gl_(), + shaderProgram_ {GL_INVALID_INDEX}, + vbo_ {GL_INVALID_INDEX}, + vao_ {GL_INVALID_INDEX} + { + gl_.initializeOpenGLFunctions(); + } + ~TriangleLayerImpl() = default; + + QOpenGLFunctions_3_3_Core gl_; + + GLuint shaderProgram_; + GLuint vbo_; + GLuint vao_; +}; + +TriangleLayer::TriangleLayer() : p(std::make_unique()) {} +TriangleLayer::~TriangleLayer() = default; + +TriangleLayer::TriangleLayer(TriangleLayer&&) noexcept = default; +TriangleLayer& TriangleLayer::operator=(TriangleLayer&&) noexcept = default; + +void TriangleLayer::initialize() +{ + QOpenGLFunctions_3_3_Core& gl = p->gl_; + + static const char* vertexShaderSource = + "#version 330 core\n" + "layout (location = 0) in vec3 aPos;" + "void main()" + "{" + " gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);" + "}"; + + static const char* fragmentShaderSource = + "#version 330 core\n" + "out vec4 FragColor;" + "void main()" + "{" + " FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);" + "}"; + + BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "initialize()"; + + GLint success; + + // Create a vertex shader + GLuint vertexShader = gl.glCreateShader(GL_VERTEX_SHADER); + + // Attach the shader source code and compile the shader + gl.glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); + gl.glCompileShader(vertexShader); + + // Check for errors + gl.glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); + if (!success) + { + char infoLog[512]; + gl.glGetShaderInfoLog(vertexShader, 512, NULL, infoLog); + BOOST_LOG_TRIVIAL(error) + << logPrefix_ << "Vertex shader compilation failed"; + } + + // Create a fragment shader + GLuint fragmentShader = gl.glCreateShader(GL_FRAGMENT_SHADER); + + // Attach the shader source and compile the shader + gl.glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); + gl.glCompileShader(fragmentShader); + + // Check for errors + gl.glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); + if (!success) + { + char infoLog[512]; + gl.glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog); + BOOST_LOG_TRIVIAL(error) + << logPrefix_ << "Fragment shader compilation failed: " << infoLog; + } + + // Create shader program + p->shaderProgram_ = gl.glCreateProgram(); + + gl.glAttachShader(p->shaderProgram_, vertexShader); + gl.glAttachShader(p->shaderProgram_, fragmentShader); + gl.glLinkProgram(p->shaderProgram_); + + // Check for errors + gl.glGetProgramiv(p->shaderProgram_, GL_LINK_STATUS, &success); + if (!success) + { + char infoLog[512]; + gl.glGetProgramInfoLog(p->shaderProgram_, 512, NULL, infoLog); + BOOST_LOG_TRIVIAL(error) + << logPrefix_ << "Shader program link failed: " << infoLog; + } + + // Delete shaders + gl.glDeleteShader(vertexShader); + gl.glDeleteShader(fragmentShader); + + // Define 3 (x,y,z) vertices + GLfloat vertices[] = { + -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f, 0.0f, 0.5f, 0.0f}; + + // Generate a vertex buffer object + gl.glGenBuffers(1, &p->vbo_); + + // Generate a vertex array object + gl.glGenVertexArrays(1, &p->vao_); + + // Bind vertex array object + gl.glBindVertexArray(p->vao_); + + // Copy vertices array in a buffer for OpenGL to use + gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_); + gl.glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + // Set the vertex attributes pointers + gl.glVertexAttribPointer( + 0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), static_cast(0)); + gl.glEnableVertexAttribArray(0); +} + +void TriangleLayer::render(const QMapbox::CustomLayerRenderParameters&) +{ + QOpenGLFunctions_3_3_Core& gl = p->gl_; + + gl.glUseProgram(p->shaderProgram_); + gl.glBindVertexArray(p->vao_); + gl.glDrawArrays(GL_TRIANGLES, 0, 3); +} + +void TriangleLayer::deinitialize() +{ + QOpenGLFunctions_3_3_Core& gl = p->gl_; + + BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "deinitialize()"; + + gl.glDeleteProgram(p->shaderProgram_); + gl.glDeleteVertexArrays(1, &p->vao_); + gl.glDeleteBuffers(1, &p->vbo_); + + p->shaderProgram_ = GL_INVALID_INDEX; + p->vao_ = GL_INVALID_INDEX; + p->vbo_ = GL_INVALID_INDEX; +} + +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/map/triangle_layer.hpp b/scwx-qt/source/scwx/qt/map/triangle_layer.hpp new file mode 100644 index 00000000..7020f4b7 --- /dev/null +++ b/scwx-qt/source/scwx/qt/map/triangle_layer.hpp @@ -0,0 +1,31 @@ +#include + +namespace scwx +{ +namespace qt +{ + +class TriangleLayerImpl; + +class TriangleLayer : public QMapbox::CustomLayerHostInterface +{ +public: + explicit TriangleLayer(); + ~TriangleLayer(); + + TriangleLayer(const TriangleLayer&) = delete; + TriangleLayer& operator=(const TriangleLayer&) = delete; + + TriangleLayer(TriangleLayer&&) noexcept; + TriangleLayer& operator=(TriangleLayer&&) noexcept; + + void initialize() override final; + void render(const QMapbox::CustomLayerRenderParameters&) override final; + void deinitialize() override final; + +private: + std::unique_ptr p; +}; + +} // namespace qt +} // namespace scwx