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