From 516983ab095361cadcb47400a542185d8b18ea56 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Thu, 15 Jul 2021 22:32:53 -0500 Subject: [PATCH] Draw a radial. This needs some cleanup, and some MVP matrix tweaking. --- CMakeLists.txt | 1 + scwx-qt/gl/radar.frag | 13 ++ scwx-qt/gl/radar.vert | 17 ++ scwx-qt/scwx-qt.cmake | 21 +- scwx-qt/scwx-qt.qrc | 6 + scwx-qt/source/scwx/qt/map/map_widget.cpp | 6 +- scwx-qt/source/scwx/qt/map/radar_layer.cpp | 215 ++++++++++++++++++ scwx-qt/source/scwx/qt/map/radar_layer.hpp | 31 +++ .../source/scwx/qt/util/shader_program.cpp | 158 +++++++++++++ .../source/scwx/qt/util/shader_program.hpp | 42 ++++ 10 files changed, 506 insertions(+), 4 deletions(-) create mode 100644 scwx-qt/gl/radar.frag create mode 100644 scwx-qt/gl/radar.vert create mode 100644 scwx-qt/scwx-qt.qrc create mode 100644 scwx-qt/source/scwx/qt/map/radar_layer.cpp create mode 100644 scwx-qt/source/scwx/qt/map/radar_layer.hpp create mode 100644 scwx-qt/source/scwx/qt/util/shader_program.cpp create mode 100644 scwx-qt/source/scwx/qt/util/shader_program.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 724f857a..dcda2407 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,7 @@ set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) include(${PROJECT_SOURCE_DIR}/external/cmake-conan/conan.cmake) conan_cmake_configure(REQUIRES boost/1.76.0 + glm/0.9.9.8 gtest/cci.20210126 openssl/1.1.1k vulkan-loader/1.2.172 diff --git a/scwx-qt/gl/radar.frag b/scwx-qt/gl/radar.frag new file mode 100644 index 00000000..38d16861 --- /dev/null +++ b/scwx-qt/gl/radar.frag @@ -0,0 +1,13 @@ +#version 330 core + +// Lower the default precision to medium +precision mediump float; + +uniform sampler2D uTexture; + +in vec2 texCoord; + +void main() +{ + gl_FragColor = vec4(1.0f, 0.0f, 0.0f, 1.0f); +} diff --git a/scwx-qt/gl/radar.vert b/scwx-qt/gl/radar.vert new file mode 100644 index 00000000..8fb83720 --- /dev/null +++ b/scwx-qt/gl/radar.vert @@ -0,0 +1,17 @@ +#version 330 core + +layout (location = 0) in vec2 aPosition; +layout (location = 1) in vec2 aTexCoord; + +uniform mat4 uMVPMatrix; + +out vec2 texCoord; + +void main() +{ + // Pass the texture coordinate to the fragment shader + texCoord = aTexCoord; + + // Transform the position to screen coordinates + gl_Position = uMVPMatrix * vec4(aPosition, 0.0f, 1.0f); +} diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 1dc3bce5..e8555458 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -12,6 +12,7 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Boost) +find_package(glm) # 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. @@ -50,9 +51,18 @@ 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 + source/scwx/qt/map/radar_layer.hpp source/scwx/qt/map/triangle_layer.hpp) set(SRC_MAP source/scwx/qt/map/map_widget.cpp + source/scwx/qt/map/radar_layer.cpp source/scwx/qt/map/triangle_layer.cpp) +set(HDR_UTIL source/scwx/qt/util/shader_program.hpp) +set(SRC_UTIL source/scwx/qt/util/shader_program.cpp) + +set(RESOURCE_FILES scwx-qt.qrc) + +set(SHADER_FILES gl/radar.frag + gl/radar.vert) set(TS_FILES ts/scwx_en_US.ts) @@ -61,6 +71,10 @@ set(PROJECT_SOURCES ${HDR_MAIN} ${UI_MAIN} ${HDR_MAP} ${SRC_MAP} + ${HDR_UTIL} + ${SRC_UTIL} + ${SHADER_FILES} + ${RESOURCE_FILES} ${TS_FILES}) source_group("Header Files\\main" FILES ${HDR_MAIN}) @@ -68,6 +82,10 @@ source_group("Source Files\\main" FILES ${SRC_MAIN}) source_group("UI Files\\main" FILES ${UI_MAIN}) source_group("Header Files\\map" FILES ${HDR_MAP}) source_group("Source Files\\map" FILES ${SRC_MAP}) +source_group("Header Files\\util" FILES ${HDR_UTIL}) +source_group("Source Files\\util" FILES ${SRC_UTIL}) +source_group("OpenGL Shaders" FILES ${SHADER_FILES}) +source_group("Resources" FILES ${RESOURCE_FILES}) source_group("I18N Files" FILES ${TS_FILES}) qt_add_executable(scwx-qt @@ -82,4 +100,5 @@ target_link_libraries(scwx-qt PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::OpenGLWidgets Boost::log qmapboxgl - opengl32) + opengl32 + glm::glm) diff --git a/scwx-qt/scwx-qt.qrc b/scwx-qt/scwx-qt.qrc new file mode 100644 index 00000000..39f00ae6 --- /dev/null +++ b/scwx-qt/scwx-qt.qrc @@ -0,0 +1,6 @@ + + + gl/radar.frag + gl/radar.vert + + diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index bc052287..4ff9c13d 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -1,6 +1,6 @@ #include "map_widget.hpp" -#include +#include #include #include @@ -65,7 +65,7 @@ void MapWidget::changeStyle() void MapWidget::AddLayers() { // QMapboxGL::addCustomLayer will take ownership of the QScopedPointer - QScopedPointer pHost(new TriangleLayer()); + QScopedPointer pHost(new RadarLayer()); QString before = "ferry"; @@ -80,7 +80,7 @@ void MapWidget::AddLayers() } } - map_->addCustomLayer("triangle", pHost, before); + map_->addCustomLayer("radar", pHost, before); } void MapWidget::keyPressEvent(QKeyEvent* ev) diff --git a/scwx-qt/source/scwx/qt/map/radar_layer.cpp b/scwx-qt/source/scwx/qt/map/radar_layer.cpp new file mode 100644 index 00000000..9c33eb67 --- /dev/null +++ b/scwx-qt/source/scwx/qt/map/radar_layer.cpp @@ -0,0 +1,215 @@ +#define _USE_MATH_DEFINES + +#include +#include + +#include + +#include + +#include +#include +#include + +namespace scwx +{ +namespace qt +{ + +static const std::string logPrefix_ = "[scwx::qt::map::radar_layer] "; + +class RadarLayerImpl +{ +public: + explicit RadarLayerImpl() : + gl_(), + shaderProgram_(), + uMVPMatrixLocation_(GL_INVALID_INDEX), + vbo_ {GL_INVALID_INDEX}, + vao_ {GL_INVALID_INDEX}, + numVertices_ {0} + { + gl_.initializeOpenGLFunctions(); + } + ~RadarLayerImpl() = default; + + QOpenGLFunctions_3_3_Core gl_; + + ShaderProgram shaderProgram_; + GLint uMVPMatrixLocation_; + GLuint vbo_; + GLuint vao_; + + GLsizeiptr numVertices_; +}; + +RadarLayer::RadarLayer() : p(std::make_unique()) {} +RadarLayer::~RadarLayer() = default; + +RadarLayer::RadarLayer(RadarLayer&&) noexcept = default; +RadarLayer& RadarLayer::operator=(RadarLayer&&) noexcept = default; + +void RadarLayer::initialize() +{ + BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "initialize()"; + + QOpenGLFunctions_3_3_Core& gl = p->gl_; + + p->shaderProgram_.Load(":/gl/radar.vert", ":/gl/radar.frag"); + + p->uMVPMatrixLocation_ = + gl.glGetUniformLocation(p->shaderProgram_.id(), "uMVPMatrix"); + if (p->uMVPMatrixLocation_ == -1) + { + BOOST_LOG_TRIVIAL(warning) << logPrefix_ << "Could not find uMVPMatrix"; + } + constexpr uint16_t MAX_RADIALS = 720; + constexpr uint16_t MAX_DATA_MOMENT_GATES = 1840; + + static std::array + vertices; + + constexpr float angleDelta = 0.5f * static_cast(M_PI) / 180.0f; + + float angle1 = 0; + float angle2 = angleDelta; + + GLsizeiptr index = 0; + + for (uint16_t azimuth = 0; azimuth < 720; ++azimuth) + { + const float dataMomentRange = 2.125f; + const float dataMomentInterval = 0.25f; + const float dataMomentIntervalH = dataMomentInterval * 0.5f; + const float snrThreshold = 2.0f; + + const uint16_t numberOfDataMomentGates = 1832; + + float range1 = dataMomentRange - dataMomentIntervalH; + float range2 = range1 + dataMomentInterval; + + float sinTheta1 = std::sinf(angle1); + float sinTheta2 = std::sinf(angle2); + float cosTheta1 = std::cosf(angle1); + float cosTheta2 = std::cosf(angle2); + + for (uint16_t gate = 0; gate < numberOfDataMomentGates; ++gate) + { + float r1SinTheta1 = range1 * sinTheta1; + float r1SinTheta2 = range1 * sinTheta2; + float r2SinTheta1 = range2 * sinTheta1; + float r2SinTheta2 = range2 * sinTheta2; + + float r1CosTheta1 = range1 * cosTheta1; + float r1CosTheta2 = range1 * cosTheta2; + float r2CosTheta1 = range2 * cosTheta1; + float r2CosTheta2 = range2 * cosTheta2; + + vertices[index++] = r1SinTheta1; + vertices[index++] = r1CosTheta1; + + vertices[index++] = r2SinTheta1; + vertices[index++] = r2CosTheta1; + + vertices[index++] = r1SinTheta2; + vertices[index++] = r1CosTheta2; + + vertices[index++] = r1SinTheta2; + vertices[index++] = r1CosTheta2; + + vertices[index++] = r2SinTheta2; + vertices[index++] = r2CosTheta2; + + vertices[index++] = r2SinTheta1; + vertices[index++] = r2CosTheta1; + + range1 += dataMomentInterval; + range2 += dataMomentInterval; + } + + angle1 += angleDelta; + angle2 += angleDelta; + break; + } + + // 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, + index * sizeof(GLfloat), + vertices.data(), + GL_STATIC_DRAW); + + // Set the vertex attributes pointers + gl.glVertexAttribPointer( + 0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), static_cast(0)); + gl.glEnableVertexAttribArray(0); + + p->numVertices_ = index; +} + +void RadarLayer::render(const QMapbox::CustomLayerRenderParameters& params) +{ + QOpenGLFunctions_3_3_Core& gl = p->gl_; + + p->shaderProgram_.Use(); + + float metersPerPixel = + QMapbox::metersPerPixelAtLatitude(params.latitude, params.zoom); + + const float scale = 1000.0f / metersPerPixel * 2.0f; + const float xScale = scale / params.width; + const float yScale = scale / params.height; + + const QMapbox::Coordinate radar(38.6986, -90.6828); + + const QMapbox::ProjectedMeters radarMeters = + QMapbox::projectedMetersForCoordinate(radar); + const QMapbox::ProjectedMeters mapMeters = + QMapbox::projectedMetersForCoordinate( + {params.latitude, params.longitude}); + + const float yTranslate = (radarMeters.first - mapMeters.first) * 0.001f; + const float xTranslate = (radarMeters.second - mapMeters.second) * 0.001f; + + glm::mat4 uMVPMatrix(1.0f); + uMVPMatrix = glm::scale(uMVPMatrix, glm::vec3(xScale, yScale, 1.0f)); + uMVPMatrix = + glm::translate(uMVPMatrix, glm::vec3(xTranslate, yTranslate, 0.0f)); + uMVPMatrix = glm::rotate(uMVPMatrix, + glm::radians(params.bearing), + glm::vec3(0.0f, 0.0f, 1.0f)); + + gl.glUniformMatrix4fv( + p->uMVPMatrixLocation_, 1, GL_FALSE, glm::value_ptr(uMVPMatrix)); + + gl.glBindVertexArray(p->vao_); + gl.glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + gl.glDrawArrays(GL_TRIANGLES, 0, p->numVertices_); + gl.glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +} + +void RadarLayer::deinitialize() +{ + QOpenGLFunctions_3_3_Core& gl = p->gl_; + + BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "deinitialize()"; + + gl.glDeleteVertexArrays(1, &p->vao_); + gl.glDeleteBuffers(1, &p->vbo_); + + p->uMVPMatrixLocation_ = 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/radar_layer.hpp b/scwx-qt/source/scwx/qt/map/radar_layer.hpp new file mode 100644 index 00000000..afb1bc20 --- /dev/null +++ b/scwx-qt/source/scwx/qt/map/radar_layer.hpp @@ -0,0 +1,31 @@ +#include + +namespace scwx +{ +namespace qt +{ + +class RadarLayerImpl; + +class RadarLayer : public QMapbox::CustomLayerHostInterface +{ +public: + explicit RadarLayer(); + ~RadarLayer(); + + RadarLayer(const RadarLayer&) = delete; + RadarLayer& operator=(const RadarLayer&) = delete; + + RadarLayer(RadarLayer&&) noexcept; + RadarLayer& operator=(RadarLayer&&) 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 diff --git a/scwx-qt/source/scwx/qt/util/shader_program.cpp b/scwx-qt/source/scwx/qt/util/shader_program.cpp new file mode 100644 index 00000000..7c00d824 --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/shader_program.cpp @@ -0,0 +1,158 @@ +#include + +#include +#include + +#include + +namespace scwx +{ +namespace qt +{ + +static const std::string logPrefix_ = "[scwx::qt::util::shader_program] "; + +class ShaderProgramImpl +{ +public: + explicit ShaderProgramImpl() : gl_(), id_ {GL_INVALID_INDEX} + { + gl_.initializeOpenGLFunctions(); + + // Create shader program + id_ = gl_.glCreateProgram(); + } + + ~ShaderProgramImpl() + { + // Delete shader program + gl_.glDeleteProgram(id_); + } + + QOpenGLFunctions_3_3_Core gl_; + + GLuint id_; +}; + +ShaderProgram::ShaderProgram() : p(std::make_unique()) {} +ShaderProgram::~ShaderProgram() = default; + +ShaderProgram::ShaderProgram(ShaderProgram&&) noexcept = default; +ShaderProgram& ShaderProgram::operator=(ShaderProgram&&) noexcept = default; + +GLuint ShaderProgram::id() const +{ + return p->id_; +} + +bool ShaderProgram::Load(const std::string& vertexPath, + const std::string& fragmentPath) +{ + BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "Load()"; + + QOpenGLFunctions_3_3_Core& gl = p->gl_; + + GLint glSuccess; + bool success = true; + + QFile vertexFile(vertexPath.c_str()); + QFile fragmentFile(fragmentPath.c_str()); + + vertexFile.open(QIODevice::ReadOnly | QIODevice::Text); + fragmentFile.open(QIODevice::ReadOnly | QIODevice::Text); + + if (!vertexFile.isOpen()) + { + BOOST_LOG_TRIVIAL(error) + << logPrefix_ << "Could not load vertex shader: " << vertexPath; + return false; + } + + if (!fragmentFile.isOpen()) + { + BOOST_LOG_TRIVIAL(error) + << logPrefix_ << "Could not load fragment shader: " << fragmentPath; + return false; + } + + QTextStream vertexShaderStream(&vertexFile); + QTextStream fragmentShaderStream(&fragmentFile); + + vertexShaderStream.setEncoding(QStringConverter::Utf8); + fragmentShaderStream.setEncoding(QStringConverter::Utf8); + + std::string vertexShaderSource = vertexShaderStream.readAll().toStdString(); + std::string fragmentShaderSource = + fragmentShaderStream.readAll().toStdString(); + + const char* vertexShaderSourceC = vertexShaderSource.c_str(); + const char* fragmentShaderSourceC = fragmentShaderSource.c_str(); + + // Create a vertex shader + GLuint vertexShader = gl.glCreateShader(GL_VERTEX_SHADER); + + // Attach the shader source code and compile the shader + gl.glShaderSource(vertexShader, 1, &vertexShaderSourceC, NULL); + gl.glCompileShader(vertexShader); + + // Check for errors + gl.glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &glSuccess); + if (!glSuccess) + { + char infoLog[512]; + gl.glGetShaderInfoLog(vertexShader, 512, NULL, infoLog); + BOOST_LOG_TRIVIAL(error) + << logPrefix_ << "Vertex shader compilation failed"; + success = false; + } + + // Create a fragment shader + GLuint fragmentShader = gl.glCreateShader(GL_FRAGMENT_SHADER); + + // Attach the shader source and compile the shader + gl.glShaderSource(fragmentShader, 1, &fragmentShaderSourceC, NULL); + gl.glCompileShader(fragmentShader); + + // Check for errors + gl.glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &glSuccess); + if (!glSuccess) + { + char infoLog[512]; + gl.glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog); + BOOST_LOG_TRIVIAL(error) + << logPrefix_ << "Fragment shader compilation failed: " << infoLog; + success = false; + } + + if (success) + { + gl.glAttachShader(p->id_, vertexShader); + gl.glAttachShader(p->id_, fragmentShader); + gl.glLinkProgram(p->id_); + + // Check for errors + gl.glGetProgramiv(p->id_, GL_LINK_STATUS, &glSuccess); + if (!glSuccess) + { + char infoLog[512]; + gl.glGetProgramInfoLog(p->id_, 512, NULL, infoLog); + BOOST_LOG_TRIVIAL(error) + << logPrefix_ << "Shader program link failed: " << infoLog; + success = false; + } + } + + // Delete shaders + gl.glDeleteShader(vertexShader); + gl.glDeleteShader(fragmentShader); + + return false; +} + +void ShaderProgram::Use() const +{ + p->gl_.glUseProgram(p->id_); +} + +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/util/shader_program.hpp b/scwx-qt/source/scwx/qt/util/shader_program.hpp new file mode 100644 index 00000000..0703ed68 --- /dev/null +++ b/scwx-qt/source/scwx/qt/util/shader_program.hpp @@ -0,0 +1,42 @@ +#pragma once + +#ifdef _WIN32 +#include +#endif + +#include +#include + +#include + +namespace scwx +{ +namespace qt +{ + +class ShaderProgramImpl; + +class ShaderProgram +{ +public: + explicit ShaderProgram(); + ~ShaderProgram(); + + ShaderProgram(const ShaderProgram&) = delete; + ShaderProgram& operator=(const ShaderProgram&) = delete; + + ShaderProgram(ShaderProgram&&) noexcept; + ShaderProgram& operator=(ShaderProgram&&) noexcept; + + GLuint id() const; + + bool Load(const std::string& vertexPath, const std::string& fragmentPath); + + void Use() const; + +private: + std::unique_ptr p; +}; + +} // namespace qt +} // namespace scwx