diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 57d1d123..765c1273 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -51,6 +51,8 @@ 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_MANAGER source/scwx/qt/manager/radar_manager.hpp) +set(SRC_MANAGER source/scwx/qt/manager/radar_manager.cpp) set(HDR_MAP source/scwx/qt/map/map_widget.hpp source/scwx/qt/map/radar_layer.hpp source/scwx/qt/map/radar_range_layer.hpp @@ -62,6 +64,8 @@ set(SRC_MAP source/scwx/qt/map/map_widget.cpp set(HDR_UTIL source/scwx/qt/util/gl.hpp source/scwx/qt/util/shader_program.hpp) set(SRC_UTIL source/scwx/qt/util/shader_program.cpp) +set(HDR_VIEW source/scwx/qt/view/radar_view.hpp) +set(SRC_VIEW source/scwx/qt/view/radar_view.cpp) set(RESOURCE_FILES scwx-qt.qrc) @@ -72,25 +76,33 @@ set(TS_FILES ts/scwx_en_US.ts) set(PROJECT_SOURCES ${HDR_MAIN} ${SRC_MAIN} + ${HDR_MANAGER} + ${SRC_MANAGER} ${UI_MAIN} ${HDR_MAP} ${SRC_MAP} ${HDR_UTIL} ${SRC_UTIL} + ${HDR_VIEW} + ${SRC_VIEW} ${SHADER_FILES} ${RESOURCE_FILES} ${TS_FILES}) -source_group("Header Files\\main" FILES ${HDR_MAIN}) -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}) +source_group("Header Files\\main" FILES ${HDR_MAIN}) +source_group("Source Files\\main" FILES ${SRC_MAIN}) +source_group("Header Files\\manager" FILES ${HDR_MANAGER}) +source_group("Source Files\\manager" FILES ${SRC_MANAGER}) +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("Header Files\\view" FILES ${HDR_VIEW}) +source_group("Source Files\\view" FILES ${SRC_VIEW}) +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 ${PROJECT_SOURCES} @@ -103,9 +115,9 @@ 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 - Boost::log Boost::timer qmapboxgl opengl32 GeographicLib::GeographicLib - glm::glm) + glm::glm + wxdata) diff --git a/scwx-qt/source/scwx/qt/manager/radar_manager.cpp b/scwx-qt/source/scwx/qt/manager/radar_manager.cpp new file mode 100644 index 00000000..cc9ba02c --- /dev/null +++ b/scwx-qt/source/scwx/qt/manager/radar_manager.cpp @@ -0,0 +1,107 @@ +#include +#include + +#include + +#include +#include +#include +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace manager +{ + +static const std::string logPrefix_ = "[scwx::qt::manager::radar_manager] "; + +static constexpr uint32_t NUM_RADIAL_GATES_0_5_DEGREE = + common::MAX_0_5_DEGREE_RADIALS * common::MAX_DATA_MOMENT_GATES; +static constexpr uint32_t NUM_RADIAL_GATES_1_DEGREE = + common::MAX_1_DEGREE_RADIALS * common::MAX_DATA_MOMENT_GATES; +static constexpr uint32_t NUM_COORIDNATES_0_5_DEGREE = + NUM_RADIAL_GATES_0_5_DEGREE * 2; +static constexpr uint32_t NUM_COORIDNATES_1_DEGREE = + NUM_RADIAL_GATES_1_DEGREE * 2; + +class RadarManagerImpl +{ +public: + explicit RadarManagerImpl() {} + ~RadarManagerImpl() = default; + + std::vector coordinates0_5Degree_; + std::vector coordinates1Degree_; +}; + +RadarManager::RadarManager() : p(std::make_unique()) {} +RadarManager::~RadarManager() = default; + +RadarManager::RadarManager(RadarManager&&) noexcept = default; +RadarManager& RadarManager::operator=(RadarManager&&) noexcept = default; + +const std::vector& +RadarManager::coordinates(common::RadialSize radialSize) const +{ + switch (radialSize) + { + case common::RadialSize::_0_5Degree: return p->coordinates0_5Degree_; + case common::RadialSize::_1Degree: return p->coordinates1Degree_; + } + + throw std::exception("Invalid radial size"); +} + +void RadarManager::Initialize() +{ + BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "Initialize()"; + + boost::timer::cpu_timer timer; + + // Calculate coordinates + timer.start(); + std::vector& coordinates0_5Degree = p->coordinates0_5Degree_; + + coordinates0_5Degree.resize(NUM_COORIDNATES_0_5_DEGREE); + + GeographicLib::Geodesic geodesic(GeographicLib::Constants::WGS84_a(), + GeographicLib::Constants::WGS84_f()); + + const QMapbox::Coordinate radar(38.6986, -90.6828); + auto radialGates = boost::irange(0, NUM_RADIAL_GATES_0_5_DEGREE); + + std::for_each( + std::execution::par_unseq, + radialGates.begin(), + radialGates.end(), + [&](uint32_t radialGate) { + const uint16_t gate = + static_cast(radialGate % common::MAX_DATA_MOMENT_GATES); + const uint16_t radial = + static_cast(radialGate / common::MAX_DATA_MOMENT_GATES); + + const float angle = radial * 0.5f - 0.25f; // 0.5 degree radial + const float range = (gate + 1) * 250.0f; // 0.25km gate size + const size_t offset = radialGate * 2; + + double latitude; + double longitude; + + geodesic.Direct( + radar.first, radar.second, angle, range, latitude, longitude); + + coordinates0_5Degree[offset] = latitude; + coordinates0_5Degree[offset + 1] = longitude; + }); + timer.stop(); + BOOST_LOG_TRIVIAL(debug) + << logPrefix_ << "Coordinates (0.5 degree) calculated in " + << timer.format(6, "%ws"); +} + +} // namespace manager +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/manager/radar_manager.hpp b/scwx-qt/source/scwx/qt/manager/radar_manager.hpp new file mode 100644 index 00000000..144f303e --- /dev/null +++ b/scwx-qt/source/scwx/qt/manager/radar_manager.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace manager +{ + +class RadarManagerImpl; + +class RadarManager +{ +public: + explicit RadarManager(); + ~RadarManager(); + + RadarManager(const RadarManager&) = delete; + RadarManager& operator=(const RadarManager&) = delete; + + RadarManager(RadarManager&&) noexcept; + RadarManager& operator=(RadarManager&&) noexcept; + + const std::vector& coordinates(common::RadialSize radialSize) const; + + void Initialize(); + +private: + std::unique_ptr p; +}; + +} // namespace manager +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/map/map_widget.cpp b/scwx-qt/source/scwx/qt/map/map_widget.cpp index 4f75673f..14aaaa6b 100644 --- a/scwx-qt/source/scwx/qt/map/map_widget.cpp +++ b/scwx-qt/source/scwx/qt/map/map_widget.cpp @@ -87,9 +87,17 @@ void MapWidget::changeStyle() void MapWidget::AddLayers() { + std::shared_ptr radarManager = + std::make_shared(); + std::shared_ptr radarView = + std::make_shared(radarManager, p->map_); + + radarManager->Initialize(); + radarView->Initialize(); + // QMapboxGL::addCustomLayer will take ownership of the QScopedPointer QScopedPointer pHost( - new RadarLayer(p->map_, p->gl_)); + new RadarLayer(radarView, p->gl_)); QString before = "ferry"; diff --git a/scwx-qt/source/scwx/qt/map/radar_layer.cpp b/scwx-qt/source/scwx/qt/map/radar_layer.cpp index 6be1840a..43c8f717 100644 --- a/scwx-qt/source/scwx/qt/map/radar_layer.cpp +++ b/scwx-qt/source/scwx/qt/map/radar_layer.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include @@ -28,25 +27,22 @@ LatLongToScreenCoordinate(const QMapbox::Coordinate& coordinate); class RadarLayerImpl { public: - explicit RadarLayerImpl(std::shared_ptr map, - OpenGLFunctions& gl) : - map_(map), + explicit RadarLayerImpl(std::shared_ptr radarView, + OpenGLFunctions& gl) : + radarView_(radarView), gl_(gl), shaderProgram_(gl), uMVPMatrixLocation_(GL_INVALID_INDEX), uMapScreenCoordLocation_(GL_INVALID_INDEX), vbo_ {GL_INVALID_INDEX}, vao_ {GL_INVALID_INDEX}, - scale_ {0.0}, - bearing_ {0.0}, numVertices_ {0} { } ~RadarLayerImpl() = default; - std::shared_ptr map_; - - OpenGLFunctions& gl_; + std::shared_ptr radarView_; + OpenGLFunctions& gl_; ShaderProgram shaderProgram_; GLint uMVPMatrixLocation_; @@ -54,14 +50,12 @@ public: GLuint vbo_; GLuint vao_; - double scale_; - double bearing_; - GLsizeiptr numVertices_; }; -RadarLayer::RadarLayer(std::shared_ptr map, OpenGLFunctions& gl) : - p(std::make_unique(map, gl)) +RadarLayer::RadarLayer(std::shared_ptr radarView, + OpenGLFunctions& gl) : + p(std::make_unique(radarView, gl)) { } RadarLayer::~RadarLayer() = default; @@ -95,95 +89,7 @@ void RadarLayer::initialize() << logPrefix_ << "Could not find uMapScreenCoord"; } - // Calculate coordinates - static std::array - coordinates; - - GeographicLib::Geodesic geodesic(GeographicLib::Constants::WGS84_a(), - GeographicLib::Constants::WGS84_f()); - - const QMapbox::Coordinate radar(38.6986, -90.6828); - auto radialGates = - boost::irange(0, MAX_RADIALS * MAX_DATA_MOMENT_GATES); - - timer.start(); - std::for_each( - std::execution::par_unseq, - radialGates.begin(), - radialGates.end(), - [&](uint32_t radialGate) { - const uint16_t gate = - static_cast(radialGate % MAX_DATA_MOMENT_GATES); - const uint16_t radial = - static_cast(radialGate / MAX_DATA_MOMENT_GATES); - - const float angle = radial * 0.5f - 0.25f; - const float range = (gate + 1) * 250.0f; - const size_t offset = radialGate * 2; - - double latitude; - double longitude; - - geodesic.Direct( - radar.first, radar.second, angle, range, latitude, longitude); - - coordinates[offset] = latitude; - coordinates[offset + 1] = longitude; - }); - timer.stop(); - BOOST_LOG_TRIVIAL(debug) - << logPrefix_ << "Coordinates calculated in " << timer.format(6, "%ws"); - - // Calculate vertices - static std::array - vertices; - GLsizeiptr index = 0; - - timer.start(); - for (uint16_t radial = 0; radial < 720; ++radial) - { - const float dataMomentRange = 2.125f * 1000.0f; - const float dataMomentInterval = 0.25f * 1000.0f; - const float dataMomentIntervalH = dataMomentInterval * 0.5f; - const float snrThreshold = 2.0f; - - const uint16_t startGate = 7; - const uint16_t numberOfDataMomentGates = 1832; - const uint16_t endGate = std::min( - numberOfDataMomentGates + startGate, MAX_DATA_MOMENT_GATES - 1); - - for (uint16_t gate = startGate; gate < endGate; ++gate) - { - size_t offset1 = (radial * MAX_DATA_MOMENT_GATES + gate) * 2; - size_t offset2 = offset1 + 2; - size_t offset3 = - (((radial + 1) % MAX_RADIALS) * MAX_DATA_MOMENT_GATES + gate) * 2; - size_t offset4 = offset3 + 2; - - vertices[index++] = coordinates[offset1]; - vertices[index++] = coordinates[offset1 + 1]; - - vertices[index++] = coordinates[offset2]; - vertices[index++] = coordinates[offset2 + 1]; - - vertices[index++] = coordinates[offset3]; - vertices[index++] = coordinates[offset3 + 1]; - - vertices[index++] = coordinates[offset3]; - vertices[index++] = coordinates[offset3 + 1]; - - vertices[index++] = coordinates[offset4]; - vertices[index++] = coordinates[offset4 + 1]; - - vertices[index++] = coordinates[offset2]; - vertices[index++] = coordinates[offset2 + 1]; - } - - break; - } - timer.stop(); - BOOST_LOG_TRIVIAL(debug) - << logPrefix_ << "Vertices calculated in " << timer.format(6, "%ws"); + const std::vector& vertices = p->radarView_->vertices(); // Generate a vertex buffer object gl.glGenBuffers(1, &p->vbo_); @@ -198,7 +104,7 @@ void RadarLayer::initialize() gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_); timer.start(); gl.glBufferData(GL_ARRAY_BUFFER, - index * sizeof(GLfloat), + vertices.size() * sizeof(GLfloat), vertices.data(), GL_STATIC_DRAW); timer.stop(); @@ -210,8 +116,7 @@ void RadarLayer::initialize() 0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), static_cast(0)); gl.glEnableVertexAttribArray(0); - p->numVertices_ = index; - p->bearing_ = p->map_->bearing(); + p->numVertices_ = vertices.size(); } void RadarLayer::render(const QMapbox::CustomLayerRenderParameters& params) @@ -220,15 +125,15 @@ void RadarLayer::render(const QMapbox::CustomLayerRenderParameters& params) p->shaderProgram_.Use(); - const float scale = - p->map_->scale() * 2.0f * mbgl::util::tileSize / mbgl::util::DEGREES_MAX; + const float scale = p->radarView_->scale() * 2.0f * mbgl::util::tileSize / + mbgl::util::DEGREES_MAX; const float xScale = scale / params.width; const float yScale = scale / params.height; glm::mat4 uMVPMatrix(1.0f); uMVPMatrix = glm::scale(uMVPMatrix, glm::vec3(xScale, yScale, 1.0f)); uMVPMatrix = glm::rotate(uMVPMatrix, - glm::radians(params.bearing - p->bearing_), + glm::radians(params.bearing), glm::vec3(0.0f, 0.0f, 1.0f)); gl.glUniform2fv(p->uMapScreenCoordLocation_, diff --git a/scwx-qt/source/scwx/qt/map/radar_layer.hpp b/scwx-qt/source/scwx/qt/map/radar_layer.hpp index dce792dc..c3cb696b 100644 --- a/scwx-qt/source/scwx/qt/map/radar_layer.hpp +++ b/scwx-qt/source/scwx/qt/map/radar_layer.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include @@ -14,7 +15,7 @@ class RadarLayerImpl; class RadarLayer : public QMapbox::CustomLayerHostInterface { public: - explicit RadarLayer(std::shared_ptr map, OpenGLFunctions& gl); + explicit RadarLayer(std::shared_ptr radarView, OpenGLFunctions& gl); ~RadarLayer(); RadarLayer(const RadarLayer&) = delete; diff --git a/scwx-qt/source/scwx/qt/view/radar_view.cpp b/scwx-qt/source/scwx/qt/view/radar_view.cpp new file mode 100644 index 00000000..745f7f11 --- /dev/null +++ b/scwx-qt/source/scwx/qt/view/radar_view.cpp @@ -0,0 +1,127 @@ +#include +#include + +#include +#include + +namespace scwx +{ +namespace qt +{ +namespace view +{ + +static const std::string logPrefix_ = "[scwx::qt::view::radar_view] "; + +static constexpr uint32_t VERTICES_PER_BIN = 6; +static constexpr uint32_t VALUES_PER_VERTEX = 2; + +class RadarViewImpl +{ +public: + explicit RadarViewImpl(std::shared_ptr radarManager, + std::shared_ptr map) : + radarManager_(radarManager), map_(map) + { + } + ~RadarViewImpl() = default; + + std::shared_ptr radarManager_; + std::shared_ptr map_; + + std::vector vertices_; +}; + +RadarView::RadarView(std::shared_ptr radarManager, + std::shared_ptr map) : + p(std::make_unique(radarManager, map)) +{ +} +RadarView::~RadarView() = default; + +RadarView::RadarView(RadarView&&) noexcept = default; +RadarView& RadarView::operator=(RadarView&&) noexcept = default; + +double RadarView::bearing() const +{ + return p->map_->bearing(); +} + +double RadarView::scale() const +{ + return p->map_->scale(); +} + +const std::vector& RadarView::vertices() const +{ + return p->vertices_; +} + +void RadarView::Initialize() +{ + BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "Initialize()"; + + boost::timer::cpu_timer timer; + + const std::vector& coordinates = + p->radarManager_->coordinates(common::RadialSize::_0_5Degree); + + // Calculate vertices + timer.start(); + std::vector& vertices = p->vertices_; + const uint32_t radials = common::MAX_RADIALS; + const uint32_t gates = common::MAX_DATA_MOMENT_GATES; + vertices.clear(); + vertices.resize(radials * gates * VERTICES_PER_BIN * VALUES_PER_VERTEX); + size_t index = 0; + + for (uint16_t radial = 0; radial < 720; ++radial) + { + const float dataMomentRange = 2.125f * 1000.0f; + const float dataMomentInterval = 0.25f * 1000.0f; + const float dataMomentIntervalH = dataMomentInterval * 0.5f; + const float snrThreshold = 2.0f; + + const uint16_t startGate = 7; + const uint16_t numberOfDataMomentGates = 1832; + const uint16_t endGate = + std::min(numberOfDataMomentGates + startGate, + common::MAX_DATA_MOMENT_GATES - 1); + + for (uint16_t gate = startGate; gate < endGate; ++gate) + { + size_t offset1 = (radial * common::MAX_DATA_MOMENT_GATES + gate) * 2; + size_t offset2 = offset1 + 2; + size_t offset3 = (((radial + 1) % common::MAX_RADIALS) * + common::MAX_DATA_MOMENT_GATES + + gate) * + 2; + size_t offset4 = offset3 + 2; + + vertices[index++] = coordinates[offset1]; + vertices[index++] = coordinates[offset1 + 1]; + + vertices[index++] = coordinates[offset2]; + vertices[index++] = coordinates[offset2 + 1]; + + vertices[index++] = coordinates[offset3]; + vertices[index++] = coordinates[offset3 + 1]; + + vertices[index++] = coordinates[offset3]; + vertices[index++] = coordinates[offset3 + 1]; + + vertices[index++] = coordinates[offset4]; + vertices[index++] = coordinates[offset4 + 1]; + + vertices[index++] = coordinates[offset2]; + vertices[index++] = coordinates[offset2 + 1]; + } + } + timer.stop(); + BOOST_LOG_TRIVIAL(debug) + << logPrefix_ << "Vertices calculated in " << timer.format(6, "%ws"); +} + +} // namespace view +} // namespace qt +} // namespace scwx diff --git a/scwx-qt/source/scwx/qt/view/radar_view.hpp b/scwx-qt/source/scwx/qt/view/radar_view.hpp new file mode 100644 index 00000000..97374c00 --- /dev/null +++ b/scwx-qt/source/scwx/qt/view/radar_view.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include + +#include +#include + +#include + +namespace scwx +{ +namespace qt +{ +namespace view +{ + +class RadarViewImpl; + +class RadarView +{ +public: + explicit RadarView(std::shared_ptr radarManager, + std::shared_ptr map); + ~RadarView(); + + RadarView(const RadarView&) = delete; + RadarView& operator=(const RadarView&) = delete; + + RadarView(RadarView&&) noexcept; + RadarView& operator=(RadarView&&) noexcept; + + double bearing() const; + double scale() const; + const std::vector& vertices() const; + + void Initialize(); + +private: + std::unique_ptr p; +}; + +} // namespace view +} // namespace qt +} // namespace scwx diff --git a/test/test.cmake b/test/test.cmake index 319e4192..319e089f 100644 --- a/test/test.cmake +++ b/test/test.cmake @@ -37,13 +37,5 @@ target_compile_definitions(wxtest PRIVATE SCWX_TEST_DATA_DIR="${SCWX_DIR}/test/d gtest_discover_tests(wxtest) -target_link_libraries(wxtest Boost::iostreams - Boost::log - BZip2::BZip2 - GTest::gtest - hsluv-c +target_link_libraries(wxtest GTest::gtest wxdata) - -if (WIN32) - target_link_libraries(wxtest Ws2_32) -endif() diff --git a/wxdata/include/scwx/common/constants.hpp b/wxdata/include/scwx/common/constants.hpp new file mode 100644 index 00000000..c361bcf1 --- /dev/null +++ b/wxdata/include/scwx/common/constants.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace scwx +{ +namespace common +{ + +constexpr uint32_t MAX_1_DEGREE_RADIALS = 360; +constexpr uint32_t MAX_0_5_DEGREE_RADIALS = 720; +constexpr uint32_t MAX_RADIALS = MAX_0_5_DEGREE_RADIALS; +constexpr uint32_t MAX_DATA_MOMENT_GATES = 1840; + +} // namespace common +} // namespace scwx diff --git a/wxdata/include/scwx/common/types.hpp b/wxdata/include/scwx/common/types.hpp new file mode 100644 index 00000000..957ddc66 --- /dev/null +++ b/wxdata/include/scwx/common/types.hpp @@ -0,0 +1,15 @@ +#pragma once + +namespace scwx +{ +namespace common +{ + +enum class RadialSize +{ + _0_5Degree, + _1Degree +}; + +} // namespace common +} // namespace scwx diff --git a/wxdata/wxdata.cmake b/wxdata/wxdata.cmake index ac2ebbf3..70f8e762 100644 --- a/wxdata/wxdata.cmake +++ b/wxdata/wxdata.cmake @@ -2,7 +2,9 @@ project(scwx-data) find_package(Boost) -set(HDR_COMMON include/scwx/common/color_table.hpp) +set(HDR_COMMON include/scwx/common/color_table.hpp + include/scwx/common/constants.hpp + include/scwx/common/types.hpp) set(SRC_COMMON source/scwx/common/color_table.cpp) set(HDR_UTIL include/scwx/util/rangebuf.hpp include/scwx/util/vectorbuf.hpp) @@ -58,6 +60,15 @@ if(MSVC) target_compile_options(wxdata PRIVATE /W3) endif() +target_link_libraries(wxdata INTERFACE Boost::iostreams + Boost::log + BZip2::BZip2 + hsluv-c) + +if (WIN32) + target_link_libraries(wxdata INTERFACE Ws2_32) +endif() + set_target_properties(wxdata PROPERTIES CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF)