diff --git a/.cppQuickFix b/.cppQuickFix
new file mode 100644
index 00000000..873e6d80
--- /dev/null
+++ b/.cppQuickFix
@@ -0,0 +1,8 @@
+[CppEditor.QuickFix]
+GettersOutsideClassFrom=-1
+ResetNameTemplateV2=\"reset_\" + name
+SetterNameTemplateV2=\"set_\" + name
+SetterParameterNameV2=\"new_\" + name
+SettersOutsideClassFrom=-1
+SignalNameTemplateV2=name + \"_changed\"
+SignalWithNewValue=true
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 451e62bf..6e7b3f9e 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -39,7 +39,7 @@ jobs:
fetch-depth: 0
fetch-tags: true
- - name: Install Qt native version (the one provided by aqt doesn't seem to work)
+ - name: Install Qt native version (the one provided by aqt does not seem to work)
uses: jurplel/install-qt-action@v4
with:
aqtversion: '==3.1.*'
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index 10a298f8..975b723d 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -65,16 +65,6 @@ jobs:
modules: 'qtcharts qtpositioning'
cache: true
- - name: Debug output
- shell: bash
- run: |
- echo "${{github.workspace}}/qt/Qt/6.8.1":
- ls ${{github.workspace}}/qt/Qt/6.8.1
- echo "==="
- echo "${QT_ROOT_DIR}/lib/cmake/Qt6Linguist:"
- ls ${QT_ROOT_DIR}/lib/cmake/Qt6Linguist
- echo "==="
-
- name: Configure
env:
CC: ${{ matrix.CC }}
diff --git a/.gitignore b/.gitignore
index 6cd6bbc5..45eeb0a4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,12 @@
/doc/*
/doc
/build/*
+app/icons/eaws/eaws_menu_closed.png
+app/icons/eaws/eaws_report_active.png
+app/icons/eaws/eaws_report_inactive.png
+app/icons/eaws/risk_level_active.png
+app/icons/eaws/risk_level_inactive.png
+app/icons/eaws/slope_angle_active.png
+app/icons/eaws/slope_angle_inactive.png
+app/icons/eaws/stop_or_go_active.png
+app/icons/eaws/stop_or_go_inactive.png
diff --git a/CMakeLists.txt b/CMakeLists.txt
index fdf56fc0..37993fc7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -27,7 +27,7 @@ option(ALP_ENABLE_TRACK_OBJECT_LIFECYCLE "enables debug cmd printout of construc
option(ALP_ENABLE_APP_SHUTDOWN_AFTER_60S "Shuts down the app after 60S, used for CI testing with asan." OFF)
option(ALP_ENABLE_LTO "Enable link time optimisation." OFF)
option(ALP_ENABLE_GL_ENGINE "Enable OpenGL/WebGL engine" ON)
-option(ALP_ENABLE_AVLANCHE_WARNING_LAYER "Enables avalanche warning layer (requires Qt Gui in nucleus)" OFF)
+option(ALP_ENABLE_AVLANCHE_WARNING_LAYER "Enables avalanche warning layer (requires Qt Gui in nucleus)" ON)
option(ALP_ENABLE_LABELS "Enables label rendering" ON)
set(ALP_EXTERN_DIR "extern" CACHE STRING "name of the directory to store external libraries, fonts etc..")
diff --git a/app/About.qml b/app/About.qml
index 4a1a860c..359cc3e0 100644
--- a/app/About.qml
+++ b/app/About.qml
@@ -84,7 +84,7 @@ it is licensed under the Open Data Commons Open Database License (ODbL) by the O
Authors:
-Adam Celarek, Lucas Dworschak, Gerald Kimmersdorfer, Jakob Lindner, Patrick Komon, Jakob Maier, Markus Rampp
+Adam Celarek, Lucas Dworschak, Gerald Kimmersdorfer, Jakob Lindner, Joerg-Christian Reiher, Patrick Komon, Jakob Maier, Markus Rampp
Impressum:
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
index 5403ac8e..fe7741c6 100644
--- a/app/CMakeLists.txt
+++ b/app/CMakeLists.txt
@@ -43,6 +43,11 @@ qt_add_qml_module(alpineapp
icons/menu.png
icons/search.png
icons/icon.png
+ icons/eaws/eaws_menu.png
+ icons/eaws/eaws_report.png
+ icons/eaws/risk_level.png
+ icons/eaws/slope_angle.png
+ icons/eaws/stop_or_go.png
icons/material/monitoring.png
icons/material/3d_rotation.png
icons/material/map.png
@@ -65,6 +70,10 @@ qt_add_qml_module(alpineapp
icons/logo_type_horizontal.png
icons/logo_type_vertical.png
icons/logo_type_horizontal_short.png
+ eaws/banner_eaws_report.png
+ eaws/banner_risk_level.png
+ eaws/banner_slope_angle.png
+ eaws/banner_stop_or_go.png
QML_FILES
Main.qml
About.qml
@@ -102,7 +111,8 @@ qt_add_qml_module(alpineapp
picker/Default.qml
picker/PoiAlpineHut.qml
picker/PoiSettlement.qml
- SOURCES TileStatistics.h TileStatistics.cpp
+ SOURCES
+ TileStatistics.h TileStatistics.cpp
)
qt_add_resources(alpineapp "fonts"
diff --git a/app/FloatingActionButtonGroup.qml b/app/FloatingActionButtonGroup.qml
index 4c555638..f01bc04b 100644
--- a/app/FloatingActionButtonGroup.qml
+++ b/app/FloatingActionButtonGroup.qml
@@ -20,8 +20,10 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
-import app
import "components"
+import app
+
+
ColumnLayout {
id: fab_group
@@ -76,7 +78,13 @@ ColumnLayout {
FloatingActionButton {
image: _r + "icons/presets/basic.png"
- onClicked: map.set_gl_preset("AAABIHjaY2BgYLL_wAAGGPRhY2EHEP303YEDIPrZPr0FQHr_EU-HBAYEwKn_5syZIPX2DxgEGLDQcP0_ILQDBwMKcHBgwAoc7KC0CJTuhyh0yGRAoeHueIBK4wAKQMwIxXAAAFQuIIw")
+ onClicked: {
+ risk_level_toggle.checked = false;
+ eaws_report_toggle.checked = false;
+ slope_angle_toggle.checked = false;
+ stop_or_go_toggle.checked = false;
+ map.set_gl_preset("AAABIHjaY2BgYLL_wAAGGPRhY2EHEP303YEDIPrZPr0FQHr_EU-HBAYEwKn_5syZIPX2DxgEGLDQcP0_ILQDBwMKcHBgwAoc7KC0CJTuhyh0yGRAoeHueIBK4wAKQMwIxXAAAFQuIIw")
+ }
size: parent.height
image_size: 42
image_opacity: 1.0
@@ -87,7 +95,13 @@ ColumnLayout {
FloatingActionButton {
image: _r + "icons/presets/shaded.png"
- onClicked: map.set_gl_preset("AAABIHjaY2BgYLL_wAAGGPRhY2EHEP1s0rwEMG32D0TvPxS4yIEBAXDqvzlz5gIQ_YBBgAELDdf_A0I7cDCgAAcHBqzAwQ5Ki0DpfohCh0wGFBrujgeoNBAwQjEyXwFNHEwDAMaIIAM")
+ onClicked: {
+ risk_level_toggle.checked = false;
+ eaws_report_toggle.checked = false;
+ slope_angle_toggle.checked = false;
+ stop_or_go_toggle.checked = false;
+ map.set_gl_preset("AAABIHjaY2BgYLL_wAAGGPRhY2EHEP1s0rwEMG32D0TvPxS4yIEBAXDqvzlz5gIQ_YBBgAELDdf_A0I7cDCgAAcHBqzAwQ5Ki0DpfohCh0wGFBrujgeoNBAwQjEyXwFNHEwDAMaIIAM")
+ }
size: parent.height
image_size: 42
image_opacity: 1.0
@@ -98,7 +112,13 @@ ColumnLayout {
FloatingActionButton {
image: _r + "icons/presets/snow.png"
- onClicked: map.set_gl_preset("AAABIHjaY2BgYLL_wAAGGPRhY2EHEP1s0rwEMG32D0TvPxS4yIEBAXDqvzlz5gIQ_YBBgAELDdf_A0I7cDCgAAcHVPPg4nZQWgRK90MUOmQyoNBwdzxApYGAEYqR-Qpo4mAaAFhrITI")
+ onClicked: {
+ risk_level_toggle.checked = false;
+ eaws_report_toggle.checked = false;
+ slope_angle_toggle.checked = false;
+ stop_or_go_toggle.checked = false;
+ map.set_gl_preset("AAABIHjaY2BgYLL_wAAGGPRhY2EHEP1s0rwEMG32D0TvPxS4yIEBAXDqvzlz5gIQ_YBBgAELDdf_A0I7cDCgAAcHVPPg4nZQWgRK90MUOmQyoNBwdzxApYGAEYqR-Qpo4mAaAFhrITI")
+ }
size: parent.height
image_size: 42
image_opacity: 1.0
@@ -119,7 +139,14 @@ ColumnLayout {
}
FloatingActionButton {
image: _r + "icons/material/steepness.png"
- onClicked: {map.shared_config.overlay_mode = 101; toggleSteepnessLegend();}
+ onClicked: {
+ risk_level_toggle.checked = false;
+ eaws_report_toggle.checked = false;
+ slope_angle_toggle.checked = false;
+ stop_or_go_toggle.checked = false;
+
+ map.shared_config.overlay_mode = 101;
+ toggleSteepnessLegend();}
size: parent.height
image_size: 24
image_opacity: 1.0
@@ -131,6 +158,279 @@ ColumnLayout {
}
}
+ // Button for avalanche menu
+ FloatingActionButton {
+ id: avalanche_menu
+ image: _r + "icons/" + (checked ? "material/chevron_left.png": "eaws/eaws_menu.png")
+ size: parent.width
+ checkable: true
+ property bool firstClickDone: false // Tracks if the button was clicked before
+ onClicked:{
+ if (!firstClickDone) {firstClickDone = false}
+ map.updateEawsReportDate(date_picker.selectedDate.getDate(), date_picker.selectedDate.getMonth()+1, date_picker.selectedDate.getFullYear())
+ }
+
+ // Textbox for warning , only shown on first click of avalanche menu button
+ Rectangle {
+ id: warning
+ visible: avalanche_menu.checked && !avalanche_menu.firstClickDone //parent.checked
+ height: 300
+ width: 400
+ radius: avalanche_menu.radius
+ anchors.bottom: parent.bottom
+ ColumnLayout {
+ anchors.fill: parent
+
+ Label {
+ id: warning_text
+ textFormat: Text.StyledText
+ text: "EXPERIMENTAL FEATURE!
+
These visualisation tools are experimental and should not be used as a sole basis for decision-making during tour planning.
+
We cannot guarantee the correctness of the information displayed.
+
Any liability for accidents and damages in connection with the use of this service is excluded. The planning and execution of your winter sports activities is at your own risk and under your sole responsibility."
+ wrapMode: Text.WordWrap
+ horizontalAlignment: Text.AlignJustify
+ Layout.alignment: Qt.AlignHCenter
+ Layout.maximumWidth: parent.width - 30 // prevent overflow
+ Layout.margins: 15
+ }
+
+ Rectangle {
+ Layout.alignment: Qt.AlignCenter
+ Layout.fillHeight: true
+ color: "blue"
+ Layout.preferredWidth: 0
+ }
+ RowLayout {
+ Layout.alignment: Qt.AlignHCenter
+ Layout.margins: 15
+ Button {
+ text: "Read more"
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("Open Thesis by Johannes Eschner at TU Wien")
+ onClicked: {
+ Qt.openUrlExternally("https://repositum.tuwien.at/handle/20.500.12708/177341?mode=simple")
+ }
+ }
+
+ Button {
+ text: "Accept and Continue"
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("Accept terms of use and open avalanche risk visualisation menu")
+ onClicked: {
+ avalanche_menu.firstClickDone = true
+ }
+ }
+ }
+ }
+ }
+
+ // Box with all avalanche menu buttons , shown after opening avalanche menu
+ Rectangle {
+ visible: avalanche_menu.checked && avalanche_menu.firstClickDone //parent.checked
+ height: 64
+ width: avalanche_subgroup.implicitWidth
+ radius: avalanche_menu.radius
+ anchors.left: parent.right
+ anchors.bottom: parent.bottom
+
+ color: Qt.alpha(Material.backgroundColor, 0.9)
+ border { width: 2; color: Qt.alpha( "black", 0.5); }
+
+ RowLayout {
+ anchors.fill: parent
+ id: avalanche_subgroup
+ spacing: 0
+ height: parent.height
+
+ // stop-or-go toggle button
+ FloatingActionButton {
+ id: stop_or_go_toggle
+ image: _r + "icons/eaws/stop_or_go.png"
+ onClicked:{
+ eaws_report_toggle.checked = false;
+ risk_level_toggle.checked = false;
+ slope_angle_toggle.checked = false;
+ banner_image.source = "eaws/banner_stop_or_go.png"
+ map.set_stop_or_go_layer(checked);
+ }
+ size: parent.height
+ image_size: 42
+ image_opacity: (checked? 1.0 : 0.4)
+ checkable: true
+
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("Stop or Go")
+ }
+
+ // Risk Level Toggle Button
+ FloatingActionButton {
+ id: risk_level_toggle
+ image: _r + "icons/eaws/risk_level.png"
+ onClicked:{
+ eaws_report_toggle.checked = false;
+ slope_angle_toggle.checked = false;
+ stop_or_go_toggle.checked = false;
+ banner_image.source = "eaws/banner_risk_level.png"
+ map.set_risk_level_layer(checked);
+ }
+ size: parent.height
+ image_size: 42
+ image_opacity: (checked? 1.0 : 0.4)
+ checkable: true
+
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("Risk Level")
+ }
+
+ // Slope Angle Toggle Button
+ FloatingActionButton {
+ id: slope_angle_toggle
+ image: _r + "icons/eaws/slope_angle.png"
+
+ onClicked:{
+ eaws_report_toggle.checked = false;
+ risk_level_toggle.checked = false;
+ stop_or_go_toggle.checked = false;
+ banner_image.source = "eaws/banner_slope_angle.png"
+ map.set_slope_angle_layer(checked);
+ }
+ size: parent.height
+ image_size: 42
+ image_opacity: (checked? 1.0 : 0.4)
+ checkable: true
+
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("Slope Angle")
+ }
+
+ //EAWS Report Toggle Button
+ FloatingActionButton {
+ id: eaws_report_toggle
+ image: _r + "icons/eaws/eaws_report.png"
+ image_opacity: (checked? 1.0 : 0.4)
+ onClicked:{
+ risk_level_toggle.checked = false;
+ slope_angle_toggle.checked = false;
+ stop_or_go_toggle.checked = false;
+ banner_image.source = "eaws/banner_eaws_report.png"
+ map.set_eaws_warning_layer(checked);
+ }
+ size: parent.height
+ image_size: 42
+ checkable: true
+
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("Show EAWS Report")
+
+ }
+
+ // Banner with color chart (only visible when an avalanche overlay is active
+ Image{
+ id: banner_image
+ Layout.preferredWidth: implicitWidth * Layout.preferredHeight / implicitHeight + 20
+ Layout.preferredHeight: 60
+ fillMode: Image.PreserveAspectFit // Keep aspect ratio
+ visible: (eaws_report_toggle.checked || risk_level_toggle.checked || slope_angle_toggle.checked || stop_or_go_toggle.checked)
+ }
+
+ // subrectangle with date slection functionality
+ RowLayout {
+ id: dateControls
+ spacing: 0
+ Layout.alignment: Qt.AlignVCenter
+ Layout.fillHeight: true
+
+ // Previous day button
+ FloatingActionButton {
+ text: "<"
+ Layout.fillHeight: true
+ onClicked: {
+ let d = new Date(date_picker.selectedDate)
+ d.setDate(d.getDate() - 1)
+ date_picker.selectedDate = d
+ }
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("previous day")
+ }
+
+ // Date picker. Item ensures it is vertically centered in the rectangle
+ Item {
+ id: datePickerWrapper
+ Layout.alignment: Qt.AlignVCenter
+ width:80
+ height:25
+
+ DatePicker {
+ id: date_picker
+ Layout.alignment: Qt.AlignVCenter
+ Layout.preferredWidth: 80
+ Layout.preferredHeight: 60
+ selectedDate: new Date()
+ onSelectedDateChanged: {
+ map.updateEawsReportDate(
+ selectedDate.getDate(),
+ selectedDate.getMonth() + 1,
+ selectedDate.getFullYear()
+ )
+ }
+ }
+ }
+
+ // Next day button
+ FloatingActionButton {
+ text: ">"
+ Layout.fillHeight: true
+ onClicked: {
+ let d = new Date(date_picker.selectedDate)
+ d.setDate(d.getDate() + 1)
+ date_picker.selectedDate = d
+ }
+
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("next day")
+ }
+
+ // Today button only appears when selected date differs from today
+ FloatingActionButton {
+ text: "Today"
+ width: 60
+ height: 20
+ onClicked: { date_picker.selectedDate = new Date() }
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("Set date to today")
+ visible: {
+ var today = new Date()
+ var sel = date_picker.selectedDate
+ return !(sel.getDate() === today.getDate() &&
+ sel.getMonth() === today.getMonth() &&
+ sel.getFullYear() === today.getFullYear())
+ }
+ }
+
+ // Button that opens report of selected date on www.avalanche.report
+ ToolButton {
+ text: "avalanche.report"
+ onClicked: {
+ if (date_picker.selectedDate) {
+ let date = date_picker.selectedDate;
+ let year = date.getFullYear();
+ let month = (date.getMonth() + 1).toString().padStart(2, "0");
+ let day = date.getDate().toString().padStart(2, "0");
+ let url = "https://avalanche.report/bulletin/" + year + "-" + month + "-" + day;
+ Qt.openUrlExternally(url);
+ }
+ }
+
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("Open the selected date on Avalanche.report")
+ }
+ }
+ }
+ }
+ }
+
+
Connections {
enabled: fab_location.checked || fab_presets.checked
target: map
@@ -156,3 +456,5 @@ ColumnLayout {
}
+
+
diff --git a/app/RenderingContext.cpp b/app/RenderingContext.cpp
index 7ff57f47..0c60b1c4 100644
--- a/app/RenderingContext.cpp
+++ b/app/RenderingContext.cpp
@@ -23,11 +23,17 @@
#include
#include
#include
+#include
#include
#include
#include
#include
#include
+#include
+#include
+#include
+#include
+#include
#include
#include
#include
@@ -39,7 +45,6 @@
#include
#include
#include
-
using namespace nucleus::tile;
using namespace nucleus::map_label;
using namespace nucleus::picker;
@@ -54,6 +59,8 @@ struct RenderingContext::Data {
// the ones below are on the scheduler thread.
nucleus::tile::setup::GeometrySchedulerHolder geometry;
nucleus::tile::setup::TextureSchedulerHolder ortho_texture;
+ nucleus::tile::setup::TextureSchedulerHolder surfaceshaded_texture;
+ nucleus::avalanche::setup::EawsTextureSchedulerHolder eaws_texture;
nucleus::map_label::setup::SchedulerHolder map_label;
std::shared_ptr data_querier;
std::unique_ptr camera_controller;
@@ -61,6 +68,7 @@ struct RenderingContext::Data {
std::shared_ptr picker_manager;
std::shared_ptr aabb_decorator;
std::unique_ptr scheduler_director;
+ std::shared_ptr eaws_report_load_service;
};
RenderingContext::RenderingContext(QObject* parent)
@@ -90,29 +98,44 @@ RenderingContext::RenderingContext(QObject* parent)
m->geometry = nucleus::tile::setup::geometry_scheduler(std::move(geometry_service), m->aabb_decorator, m->scheduler_thread.get());
m->scheduler_director->check_in("geometry", m->geometry.scheduler);
m->data_querier = std::make_shared(&m->geometry.scheduler->ram_cache());
+
// auto ortho_service = std::make_unique("https://gataki.cg.tuwien.ac.at/raw/basemap/tiles/", TilePattern::ZYX_yPointingSouth, ".jpeg");
auto ortho_service = std::make_unique("https://mapsneu.wien.gv.at/basemap/bmaporthofoto30cm/normal/google3857/", TilePattern::ZYX_yPointingSouth, ".jpeg");
m->ortho_texture = nucleus::tile::setup::texture_scheduler(std::move(ortho_service), m->aabb_decorator, m->scheduler_thread.get());
m->scheduler_director->check_in("ortho", m->ortho_texture.scheduler);
+
+ auto surfaceshaded_service = std::make_unique("https://mapsneu.wien.gv.at/basemap/bmapoberflaeche/grau/google3857/", TilePattern::ZYX_yPointingSouth, ".jpeg");
+ m->surfaceshaded_texture = nucleus::tile::setup::texture_scheduler(std::move(surfaceshaded_service), m->aabb_decorator, m->scheduler_thread.get());
+ m->scheduler_director->check_in("surfaceshading", m->surfaceshaded_texture.scheduler);
+
auto map_label_service = std::make_unique("https://osm.cg.tuwien.ac.at/vector_tiles/poi_v1/", TilePattern::ZXY_yPointingSouth, "");
m->map_label = nucleus::map_label::setup::scheduler(std::move(map_label_service), m->aabb_decorator, m->data_querier, m->scheduler_thread.get());
m->scheduler_director->check_in("map_label", m->map_label.scheduler);
+
+ auto eaws_regions_service = std::make_unique("https://osm.cg.tuwien.ac.at/vector_tiles/eaws-regions/", TilePattern::ZXY_yPointingSouth, "");
+ m->eaws_texture = nucleus::avalanche::setup::eaws_texture_scheduler(std::move(eaws_regions_service), m->aabb_decorator, m->scheduler_thread.get());
+ m->scheduler_director->check_in("eaws_regions", m->eaws_texture.scheduler);
// clang-format on
m->scheduler_director->visit([](nucleus::tile::Scheduler* sch) { nucleus::utils::thread::async_call(sch, [sch]() { sch->read_disk_cache(); }); });
}
+
m->map_label.scheduler->set_geometry_ram_cache(&m->geometry.scheduler->ram_cache());
m->geometry.scheduler->set_dataquerier(m->data_querier);
-
+ m->eaws_report_load_service = std::make_shared(m->eaws_texture.scheduler->get_uint_id_manager());
m->picker_manager = std::make_shared();
m->label_filter = std::make_shared();
if (m->scheduler_thread) {
m->picker_manager->moveToThread(m->scheduler_thread.get());
m->label_filter->moveToThread(m->scheduler_thread.get());
+ m->eaws_report_load_service->moveToThread(m->scheduler_thread.get());
}
+
// clang-format off
connect(m->geometry.scheduler.get(), &nucleus::tile::GeometryScheduler::gpu_tiles_updated, RenderThreadNotifier::instance(), &RenderThreadNotifier::notify);
connect(m->ortho_texture.scheduler.get(), &nucleus::tile::TextureScheduler::gpu_tiles_updated, RenderThreadNotifier::instance(), &RenderThreadNotifier::notify);
+ connect(m->surfaceshaded_texture.scheduler.get(), &nucleus::tile::TextureScheduler::gpu_tiles_updated, RenderThreadNotifier::instance(), &RenderThreadNotifier::notify);
+ connect(m->eaws_texture.scheduler.get(), &nucleus::avalanche::Scheduler::gpu_tiles_updated, RenderThreadNotifier::instance(), &RenderThreadNotifier::notify);
connect(m->map_label.scheduler.get(), &nucleus::map_label::Scheduler::gpu_tiles_updated, RenderThreadNotifier::instance(), &RenderThreadNotifier::notify);
connect(m->map_label.scheduler.get(), &nucleus::map_label::Scheduler::gpu_tiles_updated, m->picker_manager.get(), &PickerManager::update_quads);
connect(m->map_label.scheduler.get(), &nucleus::map_label::Scheduler::gpu_tiles_updated, m->label_filter.get(), &Filter::update_quads);
@@ -120,14 +143,10 @@ RenderingContext::RenderingContext(QObject* parent)
if (QNetworkInformation::loadDefaultBackend() && QNetworkInformation::instance()) {
QNetworkInformation* n = QNetworkInformation::instance();
- m->geometry.scheduler->set_network_reachability(n->reachability());
- m->ortho_texture.scheduler->set_network_reachability(n->reachability());
- m->map_label.scheduler->set_network_reachability(n->reachability());
- // clang-format off
- connect(n, &QNetworkInformation::reachabilityChanged, m->geometry.scheduler.get(), &nucleus::tile::Scheduler::set_network_reachability);
- connect(n, &QNetworkInformation::reachabilityChanged, m->ortho_texture.scheduler.get(), &nucleus::tile::Scheduler::set_network_reachability);
- connect(n, &QNetworkInformation::reachabilityChanged, m->map_label.scheduler.get(), &nucleus::tile::Scheduler::set_network_reachability);
- // clang-format on
+ m->scheduler_director->visit([n](nucleus::tile::Scheduler* sch) {
+ sch->set_network_reachability(n->reachability());
+ connect(n, &QNetworkInformation::reachabilityChanged, sch, &nucleus::tile::Scheduler::set_network_reachability);
+ });
}
#ifdef ALP_ENABLE_THREADING
qDebug() << "Scheduler thread: " << m->scheduler_thread.get();
@@ -142,7 +161,6 @@ RenderingContext::~RenderingContext()
RenderingContext* RenderingContext::instance()
{
-
static RenderingContext s_instance;
return &s_instance;
}
@@ -157,10 +175,14 @@ void RenderingContext::initialise()
// standard tiles
m->engine_context->set_tile_geometry(std::make_shared(65));
m->engine_context->set_ortho_layer(std::make_shared(512));
+ m->engine_context->set_surfaceshaded_layer(std::make_shared(512));
m->engine_context->tile_geometry()->set_tile_limit(2048);
+ m->engine_context->set_eaws_layer(std::make_shared());
m->engine_context->tile_geometry()->set_aabb_decorator(m->aabb_decorator);
m->engine_context->set_aabb_decorator(m->aabb_decorator);
m->engine_context->ortho_layer()->set_tile_limit(1024);
+ m->engine_context->surfaceshaded_layer()->set_tile_limit(1024);
+ m->engine_context->eaws_layer()->set_tile_limit(1024);
nucleus::utils::thread::async_call(m->geometry.scheduler.get(), [this]() { m->geometry.scheduler->set_enabled(true); });
const auto texture_compression = gl_engine::Texture::compression_algorithm();
@@ -168,6 +190,11 @@ void RenderingContext::initialise()
m->ortho_texture.scheduler->set_texture_compression_algorithm(texture_compression);
m->ortho_texture.scheduler->set_enabled(true);
});
+ nucleus::utils::thread::async_call(m->surfaceshaded_texture.scheduler.get(), [this, texture_compression]() {
+ m->surfaceshaded_texture.scheduler->set_texture_compression_algorithm(texture_compression);
+ m->surfaceshaded_texture.scheduler->set_enabled(true);
+ });
+ nucleus::utils::thread::async_call(m->eaws_texture.scheduler.get(), [this]() { m->eaws_texture.scheduler->set_enabled(true); });
// labels
m->engine_context->set_map_label_manager(std::make_unique(m->aabb_decorator));
@@ -175,8 +202,10 @@ void RenderingContext::initialise()
nucleus::utils::thread::async_call(m->map_label.scheduler.get(), [this]() { m->map_label.scheduler->set_enabled(true); });
// clang-format off
- connect(m->geometry.scheduler.get(), &nucleus::tile::GeometryScheduler::gpu_tiles_updated, m->engine_context->tile_geometry(), &gl_engine::TileGeometry::update_gpu_tiles);
- connect(m->ortho_texture.scheduler.get(), &nucleus::tile::TextureScheduler::gpu_tiles_updated, m->engine_context->ortho_layer(), &gl_engine::TextureLayer::update_gpu_tiles);
+ connect(m->geometry.scheduler.get(), &nucleus::tile::GeometryScheduler::gpu_tiles_updated, m->engine_context->tile_geometry(), &gl_engine::TileGeometry::update_gpu_tiles);
+ connect(m->ortho_texture.scheduler.get(), &nucleus::tile::TextureScheduler::gpu_tiles_updated, m->engine_context->ortho_layer(), &gl_engine::TextureLayer::update_gpu_tiles);
+ connect(m->surfaceshaded_texture.scheduler.get(), &nucleus::tile::TextureScheduler::gpu_tiles_updated, m->engine_context->surfaceshaded_layer(), &gl_engine::TextureLayer::update_gpu_tiles);
+ connect(m->eaws_texture.scheduler.get(), &nucleus::avalanche::Scheduler::gpu_tiles_updated, m->engine_context->eaws_layer(), &gl_engine::AvalancheWarningLayer::update_gpu_tiles);
connect(QOpenGLContext::currentContext(), &QOpenGLContext::aboutToBeDestroyed, m->engine_context.get(), &nucleus::EngineContext::destroy);
connect(QOpenGLContext::currentContext(), &QOpenGLContext::aboutToBeDestroyed, this, &RenderingContext::destroy);
@@ -200,12 +229,14 @@ void RenderingContext::destroy()
m->picker_manager.reset();
m->map_label.scheduler.reset();
m->ortho_texture.scheduler.reset();
+ m->eaws_texture.scheduler.reset();
m->scheduler_director.reset();
});
nucleus::utils::thread::sync_call(m->geometry.tile_service.get(), [this]() {
m->geometry.tile_service.reset();
m->map_label.tile_service.reset();
m->ortho_texture.tile_service.reset();
+ m->eaws_texture.tile_service.reset();
});
m->scheduler_thread->quit();
m->scheduler_thread->wait(500); // msec
@@ -261,8 +292,26 @@ nucleus::tile::TextureScheduler* RenderingContext::ortho_scheduler() const
return m->ortho_texture.scheduler.get();
}
+nucleus::tile::TextureScheduler* RenderingContext::surfaceshaded_scheduler() const
+{
+ QMutexLocker locker(&m->shared_ptr_mutex);
+ return m->surfaceshaded_texture.scheduler.get();
+}
+
SchedulerDirector* RenderingContext::scheduler_director() const
{
QMutexLocker locker(&m->shared_ptr_mutex);
return m->scheduler_director.get();
}
+
+nucleus::avalanche::Scheduler* RenderingContext::eaws_scheduler() const
+{
+ QMutexLocker locker(&m->shared_ptr_mutex);
+ return m->eaws_texture.scheduler.get();
+}
+
+std::shared_ptr RenderingContext::eaws_report_load_service() const
+{
+ QMutexLocker locker(&m->shared_ptr_mutex);
+ return m->eaws_report_load_service;
+}
diff --git a/app/RenderingContext.h b/app/RenderingContext.h
index e49b6661..37d722c9 100644
--- a/app/RenderingContext.h
+++ b/app/RenderingContext.h
@@ -19,7 +19,6 @@
#pragma once
#include
-
// move to pimpl to avoid including all the stuff in the header.
namespace gl_engine {
@@ -46,6 +45,12 @@ namespace nucleus::tile::utils {
class AabbDecorator;
}
+namespace nucleus::avalanche {
+class Scheduler;
+class UIntIdManager;
+class ReportLoadService;
+} // namespace nucleus::avalanche
+
class RenderingContext : public QObject {
Q_OBJECT
QML_ELEMENT
@@ -77,7 +82,10 @@ class RenderingContext : public QObject {
[[nodiscard]] std::shared_ptr label_filter() const;
[[nodiscard]] nucleus::map_label::Scheduler* map_label_scheduler() const;
[[nodiscard]] nucleus::tile::TextureScheduler* ortho_scheduler() const;
+ [[nodiscard]] nucleus::tile::TextureScheduler* surfaceshaded_scheduler() const;
[[nodiscard]] nucleus::tile::SchedulerDirector* scheduler_director() const;
+ [[nodiscard]] nucleus::avalanche::Scheduler* eaws_scheduler() const;
+ [[nodiscard]] std::shared_ptr eaws_report_load_service() const;
signals:
void initialised();
diff --git a/app/StatsWindow.qml b/app/StatsWindow.qml
index 3438d73f..568b665f 100644
--- a/app/StatsWindow.qml
+++ b/app/StatsWindow.qml
@@ -544,7 +544,7 @@ Rectangle {
// LABEL
//--------------------------
Label {
- text: qsTr("Map Label requested: ")
+ text: qsTr("Label requested: ")
}
ProgressBar {
id: map_label_n_quads_requested
@@ -570,6 +570,37 @@ Rectangle {
Label {
text: "(" + map_label_n_quads_ram.value + ")"
}
+
+ //--------------------------
+ // Eaws regions
+ //--------------------------
+ Label {
+ text: qsTr("EAWS requested: ")
+ }
+ ProgressBar {
+ id: eaws_n_quads_requested
+ Layout.fillWidth: true
+ from: 0
+ to: 500
+ value: map.tile_statistics.scheduler.eaws_n_quads_requested * 4
+ }
+ Label {
+ text: "(" + eaws_n_quads_requested.value + ")"
+ }
+
+ Label {
+ text: qsTr("EAWS ram: ")
+ }
+ ProgressBar {
+ id: eaws_n_quads_ram
+ Layout.fillWidth: true
+ from: 0
+ to: map.tile_statistics.scheduler.eaws_n_quads_ram_max * 4
+ value: map.tile_statistics.scheduler.eaws_n_quads_ram * 4
+ }
+ Label {
+ text: "(" + eaws_n_quads_ram.value + ")"
+ }
}
CheckGroup {
diff --git a/app/TerrainRenderer.cpp b/app/TerrainRenderer.cpp
index 6201b843..52de76f1 100644
--- a/app/TerrainRenderer.cpp
+++ b/app/TerrainRenderer.cpp
@@ -30,6 +30,9 @@
#include
#include
#include
+#include
+#include
+#include
#include
#include
#include
@@ -38,7 +41,6 @@
#include
#include
#include
-
TerrainRenderer::TerrainRenderer()
{
using nucleus::map_label::Filter;
@@ -59,14 +61,17 @@ TerrainRenderer::TerrainRenderer()
// In Qt/QML the rendering thread goes to sleep (at least until Qt 6.5, See RenderThreadNotifier).
// At the time of writing, an additional connection from tile_ready and tile_expired to the notifier is made.
// this only works if ALP_ENABLE_THREADING is on, i.e., the tile scheduler is on an extra thread. -> potential issue on webassembly
- connect(m_camera_controller.get(), &CameraController::definition_changed, ctx->geometry_scheduler(), &Scheduler::update_camera);
- connect(m_camera_controller.get(), &CameraController::definition_changed, ctx->map_label_scheduler(), &Scheduler::update_camera);
- connect(m_camera_controller.get(), &CameraController::definition_changed, ctx->ortho_scheduler(), &Scheduler::update_camera);
- connect(m_camera_controller.get(), &CameraController::definition_changed, m_glWindow.get(), &gl_engine::Window::update_camera);
-
- connect(ctx->geometry_scheduler(), &nucleus::tile::GeometryScheduler::gpu_tiles_updated, gl_window_ptr, &gl_engine::Window::update_requested);
- connect(ctx->ortho_scheduler(), &nucleus::tile::TextureScheduler::gpu_tiles_updated, gl_window_ptr, &gl_engine::Window::update_requested);
- connect(ctx->label_filter().get(), &Filter::filter_finished, gl_window_ptr, &gl_engine::Window::update_requested);
+ connect(m_camera_controller.get(), &CameraController::definition_changed, ctx->geometry_scheduler(), &Scheduler::update_camera);
+ connect(m_camera_controller.get(), &CameraController::definition_changed, ctx->map_label_scheduler(), &Scheduler::update_camera);
+ connect(m_camera_controller.get(), &CameraController::definition_changed, ctx->ortho_scheduler(), &Scheduler::update_camera);
+ connect(m_camera_controller.get(), &CameraController::definition_changed, ctx->surfaceshaded_scheduler(), &Scheduler::update_camera);
+ connect(m_camera_controller.get(), &CameraController::definition_changed, ctx->eaws_scheduler(), &Scheduler::update_camera);
+ connect(m_camera_controller.get(), &CameraController::definition_changed, m_glWindow.get(), &gl_engine::Window::update_camera);
+
+ connect(ctx->geometry_scheduler(), &nucleus::tile::GeometryScheduler::gpu_tiles_updated, gl_window_ptr, &gl_engine::Window::update_requested);
+ connect(ctx->ortho_scheduler(), &nucleus::tile::TextureScheduler::gpu_tiles_updated, gl_window_ptr, &gl_engine::Window::update_requested);
+ connect(ctx->eaws_scheduler(), &nucleus::avalanche::Scheduler::gpu_tiles_updated, gl_window_ptr, &gl_engine::Window::update_requested);
+ connect(ctx->label_filter().get(), &Filter::filter_finished, gl_window_ptr, &gl_engine::Window::update_requested);
connect(ctx->picker_manager().get(), &PickerManager::pick_requested, gl_window_ptr, &gl_engine::Window::pick_value);
connect(gl_window_ptr, &gl_engine::Window::value_picked, ctx->picker_manager().get(), &PickerManager::eval_pick);
@@ -74,6 +79,12 @@ TerrainRenderer::TerrainRenderer()
m_glWindow->initialise_gpu();
// ctx->scheduler()->set_enabled(true); // after tile manager moves to ctx.
+
+ m_eaws_report_load_service = ctx->eaws_report_load_service();
+ connect(ctx->eaws_report_load_service().get(),
+ &nucleus::avalanche::ReportLoadService::load_from_TU_Wien_finished,
+ gl_window_ptr,
+ &gl_engine::Window::update_eaws_reports);
}
TerrainRenderer::~TerrainRenderer() = default;
@@ -147,3 +158,9 @@ gl_engine::Window *TerrainRenderer::glWindow() const
}
nucleus::camera::Controller* TerrainRenderer::controller() const { return m_camera_controller.get(); }
+
+std::shared_ptr TerrainRenderer::eaws_report_load_service()
+{
+ assert(m_eaws_report_load_service);
+ return m_eaws_report_load_service;
+}
diff --git a/app/TerrainRenderer.h b/app/TerrainRenderer.h
index ca6e9ca1..8f9d013f 100644
--- a/app/TerrainRenderer.h
+++ b/app/TerrainRenderer.h
@@ -32,6 +32,9 @@ class Controller;
namespace nucleus::camera {
class Controller;
}
+namespace nucleus::avalanche {
+class ReportLoadService;
+}
class TerrainRenderer : public QObject, public QQuickFramebufferObject::Renderer {
Q_OBJECT
@@ -49,8 +52,11 @@ class TerrainRenderer : public QObject, public QQuickFramebufferObject::Renderer
[[nodiscard]] nucleus::camera::Controller* controller() const;
+ [[nodiscard]] std::shared_ptr eaws_report_load_service();
+
private:
QQuickWindow* m_window = nullptr;
std::unique_ptr m_glWindow;
std::unique_ptr m_camera_controller;
+ std::shared_ptr m_eaws_report_load_service;
};
diff --git a/app/TerrainRendererItem.cpp b/app/TerrainRendererItem.cpp
index fa369b99..555b653e 100644
--- a/app/TerrainRendererItem.cpp
+++ b/app/TerrainRendererItem.cpp
@@ -38,6 +38,8 @@
#include
#include
#include
+#include
+#include
#include
#include
#include
@@ -118,6 +120,7 @@ QQuickFramebufferObject::Renderer* TerrainRendererItem::createRenderer() const
connect(ctx->geometry_scheduler(), &nucleus::tile::Scheduler::stats_ready, this->m_tile_statistics, &TileStatistics::update_scheduler_stats);
connect(ctx->map_label_scheduler(), &nucleus::tile::Scheduler::stats_ready, this->m_tile_statistics, &TileStatistics::update_scheduler_stats);
connect(ctx->ortho_scheduler(), &nucleus::tile::Scheduler::stats_ready, this->m_tile_statistics, &TileStatistics::update_scheduler_stats);
+ connect(ctx->eaws_scheduler(), &nucleus::tile::Scheduler::stats_ready, this->m_tile_statistics, &TileStatistics::update_scheduler_stats);
connect(m_update_timer, &QTimer::timeout, this, &QQuickFramebufferObject::update);
connect(this, &TerrainRendererItem::touch_made, r->controller(), &nucleus::camera::Controller::touch);
@@ -153,6 +156,9 @@ QQuickFramebufferObject::Renderer* TerrainRendererItem::createRenderer() const
connect(this, &TerrainRendererItem::label_filter_changed, ctx->label_filter().get(), &nucleus::map_label::Filter::update_filter);
connect(ctx->picker_manager().get(), &nucleus::picker::PickerManager::pick_evaluated, this, &TerrainRendererItem::set_picked_feature);
+ connect(
+ this, &TerrainRendererItem::eaws_report_date_changed, ctx->eaws_report_load_service().get(), &nucleus::avalanche::ReportLoadService::load_from_tu_wien);
+
#ifdef ALP_ENABLE_DEV_TOOLS
connect(r->glWindow(), &gl_engine::Window::timer_measurements_ready, TimerFrontendManager::instance(), &TimerFrontendManager::receive_measurements);
#endif
@@ -160,7 +166,6 @@ QQuickFramebufferObject::Renderer* TerrainRendererItem::createRenderer() const
// We now have to initialize everything based on the url, but we need to do this on the thread this instance
// belongs to. (gui thread?) Therefore we use the following signal to signal the init process
emit init_after_creation();
-
return r;
}
@@ -515,3 +520,36 @@ void TerrainRendererItem::gl_sundir_date_link_changed(bool)
{
recalculate_sun_angles();
}
+
+void TerrainRendererItem::set_eaws_warning_layer(bool value)
+{
+ gl_engine::uboSharedConfig tmp;
+ tmp.m_eaws_danger_rating_enabled = value;
+ set_shared_config(tmp);
+}
+
+void TerrainRendererItem::set_risk_level_layer(bool value)
+{
+ gl_engine::uboSharedConfig tmp;
+ tmp.m_eaws_risk_level_enabled = value;
+ set_shared_config(tmp);
+}
+
+void TerrainRendererItem::set_slope_angle_layer(bool value)
+{
+ gl_engine::uboSharedConfig tmp;
+ tmp.m_eaws_slope_angle_enabled = value;
+ set_shared_config(tmp);
+}
+
+void TerrainRendererItem::set_stop_or_go_layer(bool value)
+{
+ gl_engine::uboSharedConfig tmp;
+ tmp.m_eaws_stop_or_go_enabled = value;
+ set_shared_config(tmp);
+}
+
+void TerrainRendererItem::updateEawsReportDate(int day, int month, int year)
+{
+ emit eaws_report_date_changed(QDate(year, month, day));
+}
diff --git a/app/TerrainRendererItem.h b/app/TerrainRendererItem.h
index b3d3120b..9c294710 100644
--- a/app/TerrainRendererItem.h
+++ b/app/TerrainRendererItem.h
@@ -85,7 +85,6 @@ class TerrainRendererItem : public QQuickFramebufferObject {
void shared_config_changed(gl_engine::uboSharedConfig new_shared_config) const;
void label_filter_changed(const nucleus::map_label::FilterDefinitions label_filter) const;
void hud_visible_changed(bool new_hud_visible);
-
void rotation_north_requested();
void camera_changed();
void camera_width_changed();
@@ -113,6 +112,8 @@ class TerrainRendererItem : public QQuickFramebufferObject {
void world_space_cursor_position_changed(const QVector3D& world_space_cursor_position);
+ void eaws_report_date_changed(QDate newDate) const; // This is emitted after user picked a date for eaws avalanche report in the gui
+
protected:
void touchEvent(QTouchEvent*) override;
void mousePressEvent(QMouseEvent*) override;
@@ -127,7 +128,11 @@ public slots:
void rotate_north();
void set_gl_preset(const QString& preset_b64_string);
void camera_definition_changed(const nucleus::camera::Definition& new_definition); // gets called whenever camera changes
-
+ void set_eaws_warning_layer(bool value);
+ void set_risk_level_layer(bool value);
+ void set_slope_angle_layer(bool value);
+ void set_stop_or_go_layer(bool value);
+ void updateEawsReportDate(int day, int month, int year);
private slots:
void schedule_update();
void init_after_creation_slot();
diff --git a/app/TrackModel.cpp b/app/TrackModel.cpp
index 7717c1f9..b9341acf 100644
--- a/app/TrackModel.cpp
+++ b/app/TrackModel.cpp
@@ -134,8 +134,10 @@ void TrackModel::upload_track()
#else
const auto path = QFileDialog::getOpenFileName(nullptr, tr("Open GPX track"), "", "GPX (*.gpx *.xml)");
auto file = QFile(path);
- file.open(QFile::ReadOnly);
- fileContentReady(file.fileName(), file.readAll());
+ if (file.open(QFile::ReadOnly))
+ fileContentReady(file.fileName(), file.readAll());
+ else
+ qDebug() << "Failed to open " << file.fileName();
#endif
}
diff --git a/app/eaws/banner_eaws_report.png b/app/eaws/banner_eaws_report.png
new file mode 100644
index 00000000..e3197550
Binary files /dev/null and b/app/eaws/banner_eaws_report.png differ
diff --git a/app/eaws/banner_risk_level.png b/app/eaws/banner_risk_level.png
new file mode 100644
index 00000000..04d6ae5e
Binary files /dev/null and b/app/eaws/banner_risk_level.png differ
diff --git a/app/eaws/banner_slope_angle.png b/app/eaws/banner_slope_angle.png
new file mode 100644
index 00000000..455e2991
Binary files /dev/null and b/app/eaws/banner_slope_angle.png differ
diff --git a/app/eaws/banner_stop_or_go.png b/app/eaws/banner_stop_or_go.png
new file mode 100644
index 00000000..38935cf5
Binary files /dev/null and b/app/eaws/banner_stop_or_go.png differ
diff --git a/app/icons/eaws/eaws_menu.png b/app/icons/eaws/eaws_menu.png
new file mode 100644
index 00000000..242c16f3
Binary files /dev/null and b/app/icons/eaws/eaws_menu.png differ
diff --git a/app/icons/eaws/eaws_report.png b/app/icons/eaws/eaws_report.png
new file mode 100644
index 00000000..2d572104
Binary files /dev/null and b/app/icons/eaws/eaws_report.png differ
diff --git a/app/icons/eaws/risk_level.png b/app/icons/eaws/risk_level.png
new file mode 100644
index 00000000..39e05e76
Binary files /dev/null and b/app/icons/eaws/risk_level.png differ
diff --git a/app/icons/eaws/slope_angle.png b/app/icons/eaws/slope_angle.png
new file mode 100644
index 00000000..9be0c413
Binary files /dev/null and b/app/icons/eaws/slope_angle.png differ
diff --git a/app/icons/eaws/stop_or_go.png b/app/icons/eaws/stop_or_go.png
new file mode 100644
index 00000000..8d5bf3b7
Binary files /dev/null and b/app/icons/eaws/stop_or_go.png differ
diff --git a/gl_engine/AvalancheWarningLayer.cpp b/gl_engine/AvalancheWarningLayer.cpp
new file mode 100644
index 00000000..1b0fb20d
--- /dev/null
+++ b/gl_engine/AvalancheWarningLayer.cpp
@@ -0,0 +1,119 @@
+/*****************************************************************************
+ * AlpineMaps.org
+ * Copyright (C) 2024 Adam Celarek
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *****************************************************************************/
+
+#include "AvalancheWarningLayer.h"
+
+#include "ShaderProgram.h"
+#include "ShaderRegistry.h"
+#include "TileGeometry.h"
+#include
+#include
+
+namespace gl_engine {
+
+AvalancheWarningLayer::AvalancheWarningLayer(QObject* parent)
+ : QObject { parent }
+{
+}
+
+void gl_engine::AvalancheWarningLayer::init(ShaderRegistry* shader_registry, std::shared_ptr surfaceshaded_layer)
+{
+ m_shader = std::make_shared("tile.vert", "eaws.frag");
+ shader_registry->add_shader(m_shader);
+
+ m_texture_array = std::make_unique(Texture::Target::_2dArray, Texture::Format::R16UI);
+ m_texture_array->setParams(Texture::Filter::Nearest, Texture::Filter::Nearest, false);
+ m_texture_array->allocate_array(m_resolution, m_resolution, unsigned(m_gpu_array_helper.size()));
+
+ m_instanced_zoom = std::make_unique(Texture::Target::_2d, Texture::Format::R8UI);
+ m_instanced_zoom->setParams(Texture::Filter::Nearest, Texture::Filter::Nearest);
+
+ m_instanced_array_index = std::make_unique(Texture::Target::_2d, Texture::Format::R16UI);
+ m_instanced_array_index->setParams(Texture::Filter::Nearest, Texture::Filter::Nearest);
+ m_surfshaded_layer = surfaceshaded_layer;
+}
+
+void AvalancheWarningLayer::draw(
+ const TileGeometry& tile_geometry, const nucleus::camera::Definition& camera, const std::vector& draw_list) const
+{
+ m_shader->bind();
+ m_texture_array->bind(2);
+ m_shader->set_uniform("texture_sampler", 2);
+
+ nucleus::Raster zoom_level_raster = { glm::uvec2 { 1024, 1 } };
+ nucleus::Raster array_index_raster = { glm::uvec2 { 1024, 1 } };
+ for (unsigned i = 0; i < std::min(unsigned(draw_list.size()), 1024u); ++i) {
+ const auto layer = m_gpu_array_helper.layer(draw_list[i].id);
+ zoom_level_raster.pixel({ i, 0 }) = layer.id.zoom_level;
+ array_index_raster.pixel({ i, 0 }) = layer.index;
+ }
+
+ m_instanced_array_index->bind(7);
+ m_shader->set_uniform("instanced_texture_array_index_sampler", 7);
+ m_instanced_array_index->upload(array_index_raster);
+
+ m_instanced_zoom->bind(8);
+ m_shader->set_uniform("instanced_texture_zoom_sampler", 8);
+ m_instanced_zoom->upload(zoom_level_raster);
+
+ m_surfshaded_layer->m_texture_array->bind(9);
+ m_shader->set_uniform("texture_sampler2", 9);
+ for (unsigned i = 0; i < std::min(unsigned(draw_list.size()), 1024u); ++i) {
+ const auto layer = m_surfshaded_layer->m_gpu_array_helper.layer(draw_list[i].id);
+ zoom_level_raster.pixel({ i, 0 }) = layer.id.zoom_level;
+ array_index_raster.pixel({ i, 0 }) = layer.index;
+ }
+
+ m_surfshaded_layer->m_instanced_array_index->bind(10);
+ m_shader->set_uniform("instanced_texture_array_index_sampler2", 10);
+ m_surfshaded_layer->m_instanced_array_index->upload(array_index_raster);
+
+ m_surfshaded_layer->m_instanced_zoom->bind(11);
+ m_shader->set_uniform("instanced_texture_zoom_sampler2", 11);
+ m_surfshaded_layer->m_instanced_zoom->upload(zoom_level_raster);
+
+ tile_geometry.draw(m_shader.get(), camera, draw_list);
+}
+
+void AvalancheWarningLayer::update_gpu_tiles(const std::vector& deleted_tiles, const std::vector& new_tiles)
+{
+ if (!QOpenGLContext::currentContext()) // can happen during shutdown.
+ return;
+
+ for (const auto& tile_id : deleted_tiles) {
+ m_gpu_array_helper.remove_tile(tile_id);
+ }
+ for (const auto& tile : new_tiles) {
+ // test for validity
+ assert(tile.id.zoom_level < 100);
+ assert(tile.texture);
+
+ // find empty spot and upload texture
+ const auto layer_index = m_gpu_array_helper.add_tile(tile.id);
+ m_texture_array->upload(*tile.texture, layer_index);
+ }
+}
+
+void AvalancheWarningLayer::set_tile_limit(unsigned int new_limit)
+{
+ assert(new_limit < 2048); // array textures with size > 2048 are not supported on all devices
+ assert(!m_texture_array);
+ m_gpu_array_helper.set_tile_limit(new_limit);
+}
+
+} // namespace gl_engine
diff --git a/gl_engine/AvalancheWarningLayer.h b/gl_engine/AvalancheWarningLayer.h
new file mode 100644
index 00000000..e8711750
--- /dev/null
+++ b/gl_engine/AvalancheWarningLayer.h
@@ -0,0 +1,65 @@
+/*****************************************************************************
+ * AlpineMaps.org
+ * Copyright (C) 2024 Adam Celarek
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *****************************************************************************/
+
+#pragma once
+
+#include "Texture.h"
+#include
+#include
+#include
+#include
+#include
+
+namespace camera {
+class Definition;
+}
+
+class QOpenGLShaderProgram;
+class QOpenGLBuffer;
+class QOpenGLVertexArrayObject;
+
+namespace gl_engine {
+class ShaderRegistry;
+class ShaderProgram;
+class TileGeometry;
+class TextureLayer;
+
+class AvalancheWarningLayer : public QObject {
+ Q_OBJECT
+public:
+ explicit AvalancheWarningLayer(QObject* parent = nullptr);
+ void init(ShaderRegistry* shader_registry, std::shared_ptr surfaceshaded_layer); // needs OpenGL context
+ void draw(const TileGeometry& tile_geometry, const nucleus::camera::Definition& camera, const std::vector& draw_list) const;
+
+ unsigned int tile_count() const;
+
+public slots:
+ void update_gpu_tiles(const std::vector& deleted_tiles, const std::vector& new_tiles);
+ void set_tile_limit(unsigned new_limit);
+
+private:
+ const unsigned m_resolution = 512u;
+
+ std::shared_ptr m_shader;
+ std::unique_ptr m_texture_array;
+ std::unique_ptr m_instanced_zoom;
+ std::unique_ptr m_instanced_array_index;
+ nucleus::tile::GpuArrayHelper m_gpu_array_helper;
+ std::shared_ptr m_surfshaded_layer = nullptr;
+};
+} // namespace gl_engine
diff --git a/gl_engine/CMakeLists.txt b/gl_engine/CMakeLists.txt
index 81934f37..885a3a6f 100644
--- a/gl_engine/CMakeLists.txt
+++ b/gl_engine/CMakeLists.txt
@@ -44,6 +44,13 @@ qt_add_library(gl_engine STATIC
types.h
)
+
+if (ALP_ENABLE_AVLANCHE_WARNING_LAYER)
+ target_sources(gl_engine PRIVATE
+ AvalancheWarningLayer.h AvalancheWarningLayer.cpp
+ )
+endif()
+
if(ALP_ENABLE_LABELS)
target_sources(gl_engine PRIVATE
MapLabels.h MapLabels.cpp
@@ -84,6 +91,8 @@ qt_add_resources(gl_engine "shaders"
shaders/track.vert
shaders/turbo_colormap.glsl
shaders/intersection.glsl
+ shaders/eaws.glsl
+ shaders/eaws.frag
)
target_compile_definitions(gl_engine PUBLIC ALP_RESOURCES_PREFIX="${CMAKE_CURRENT_SOURCE_DIR}/shaders/")
diff --git a/gl_engine/Context.cpp b/gl_engine/Context.cpp
index efdd3ddc..04eb5ae6 100644
--- a/gl_engine/Context.cpp
+++ b/gl_engine/Context.cpp
@@ -17,6 +17,7 @@
*****************************************************************************/
#include "Context.h"
+#include "AvalancheWarningLayer.h"
#include "MapLabels.h"
#include "ShaderRegistry.h"
#include "TextureLayer.h"
@@ -59,12 +60,20 @@ void Context::internal_initialise()
if (m_ortho_layer)
m_ortho_layer->init(m_shader_registry.get());
+
+ if (m_surfaceshaded_layer)
+ m_surfaceshaded_layer->init(m_shader_registry.get());
+
+ if (m_eaws_layer && m_surfaceshaded_layer)
+ m_eaws_layer->init(m_shader_registry.get(), m_surfaceshaded_layer);
}
void Context::internal_destroy()
{
// this is necessary for a clean shutdown (and we want a clean shutdown for the ci integration test).
m_ortho_layer.reset();
+ m_surfaceshaded_layer.reset();
+ m_eaws_layer.reset();
m_tile_geometry.reset();
m_track_manager.reset();
m_shader_registry.reset();
@@ -73,10 +82,26 @@ void Context::internal_destroy()
TextureLayer* Context::ortho_layer() const { return m_ortho_layer.get(); }
-void Context::set_ortho_layer(std::shared_ptr new_ortho_layer)
+AvalancheWarningLayer* Context::eaws_layer() const { return m_eaws_layer.get(); }
+
+void Context::set_ortho_layer(std::shared_ptr new_layer)
+{
+ assert(!is_alive()); // only set before init is called.
+ m_ortho_layer = std::move(new_layer);
+}
+
+TextureLayer* Context::surfaceshaded_layer() const { return m_surfaceshaded_layer.get(); }
+
+void Context::set_surfaceshaded_layer(std::shared_ptr new_layer)
+{
+ assert(!is_alive()); // only set before init is called.
+ m_surfaceshaded_layer = std::move(new_layer);
+}
+
+void Context::set_eaws_layer(std::shared_ptr new_layer)
{
assert(!is_alive()); // only set before init is called.
- m_ortho_layer = std::move(new_ortho_layer);
+ m_eaws_layer = std::move(new_layer);
}
TileGeometry* Context::tile_geometry() const { return m_tile_geometry.get(); }
diff --git a/gl_engine/Context.h b/gl_engine/Context.h
index e9dde0e3..c478f850 100644
--- a/gl_engine/Context.h
+++ b/gl_engine/Context.h
@@ -18,15 +18,19 @@
#pragma once
+#include "TrackManager.h"
#include
-#include "TrackManager.h"
+namespace nucleus::avalanche {
+class UIntIdManager;
+}
namespace gl_engine {
class MapLabels;
class ShaderRegistry;
class TileGeometry;
class TextureLayer;
+class AvalancheWarningLayer;
class Context : public nucleus::EngineContext {
private:
@@ -48,13 +52,21 @@ class Context : public nucleus::EngineContext {
[[nodiscard]] TextureLayer* ortho_layer() const;
void set_ortho_layer(std::shared_ptr new_ortho_layer);
+ [[nodiscard]] TextureLayer* surfaceshaded_layer() const;
+ void set_surfaceshaded_layer(std::shared_ptr new_layer);
+
+ [[nodiscard]] AvalancheWarningLayer* eaws_layer() const;
+ void set_eaws_layer(std::shared_ptr new_ortho_layer);
+
protected:
void internal_initialise() override;
void internal_destroy() override;
private:
std::shared_ptr m_tile_geometry;
+ std::shared_ptr m_surfaceshaded_layer;
std::shared_ptr m_ortho_layer;
+ std::shared_ptr m_eaws_layer;
std::shared_ptr m_map_label_manager;
std::shared_ptr m_track_manager;
std::shared_ptr m_shader_registry;
diff --git a/gl_engine/Texture.cpp b/gl_engine/Texture.cpp
index 50819172..8230bb71 100644
--- a/gl_engine/Texture.cpp
+++ b/gl_engine/Texture.cpp
@@ -56,6 +56,8 @@ GlParams gl_tex_params(gl_engine::Texture::Format format)
return { GL_RG8, GL_RG, GL_UNSIGNED_BYTE, 2, 1, true };
case F::RG32UI:
return { GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT, 2, 4 };
+ case F::RGB32UI:
+ return { GL_RGB32UI, GL_RGB_INTEGER, GL_UNSIGNED_INT, 3, 4 };
case F::R8UI:
return { GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, 1, 1 };
case F::R16UI:
@@ -227,8 +229,9 @@ template void gl_engine::Texture::upload(const nucleus::Raster&
template void gl_engine::Texture::upload(const nucleus::Raster&, unsigned);
template void gl_engine::Texture::upload(const nucleus::Raster&, unsigned);
template void gl_engine::Texture::upload(const nucleus::Raster&, unsigned);
-template void gl_engine::Texture::upload>(const nucleus::Raster>&, unsigned);
template void gl_engine::Texture::upload>(const nucleus::Raster>&, unsigned);
+template void gl_engine::Texture::upload>(const nucleus::Raster>&, unsigned);
+template void gl_engine::Texture::upload>(const nucleus::Raster>&, unsigned);
template void gl_engine::Texture::upload>(const nucleus::Raster>&, unsigned);
template void gl_engine::Texture::upload>(const nucleus::Raster>&, unsigned);
@@ -257,6 +260,7 @@ template void gl_engine::Texture::upload(const nucleus::Raster
template void gl_engine::Texture::upload(const nucleus::Raster&);
template void gl_engine::Texture::upload(const nucleus::Raster&);
template void gl_engine::Texture::upload>(const nucleus::Raster>&);
+template void gl_engine::Texture::upload>(const nucleus::Raster>&);
template void gl_engine::Texture::upload>(const nucleus::Raster>&);
template void gl_engine::Texture::upload>(const nucleus::Raster>&);
template void gl_engine::Texture::upload>(const nucleus::Raster>&);
diff --git a/gl_engine/Texture.h b/gl_engine/Texture.h
index 697d1820..e8cb32e3 100644
--- a/gl_engine/Texture.h
+++ b/gl_engine/Texture.h
@@ -38,6 +38,7 @@ class Texture {
RGBA32F,
RG8, // normalised on gpu
RG32UI,
+ RGB32UI,
R8UI,
R16UI,
R32UI,
@@ -83,7 +84,12 @@ class Texture {
extern template void gl_engine::Texture::upload(const nucleus::Raster&);
extern template void gl_engine::Texture::upload(const nucleus::Raster&);
extern template void gl_engine::Texture::upload>(const nucleus::Raster>&);
+extern template void gl_engine::Texture::upload>(const nucleus::Raster>&);
extern template void gl_engine::Texture::upload>(const nucleus::Raster>&);
extern template void gl_engine::Texture::upload>(const nucleus::Raster>&);
+extern template void gl_engine::Texture::upload(const nucleus::Raster&, unsigned int);
+extern template void gl_engine::Texture::upload>(const nucleus::Raster>&, unsigned int);
+extern template void gl_engine::Texture::upload>(const nucleus::Raster>&, unsigned int);
+
} // namespace gl_engine
diff --git a/gl_engine/TextureLayer.h b/gl_engine/TextureLayer.h
index 516600d7..1190ac1b 100644
--- a/gl_engine/TextureLayer.h
+++ b/gl_engine/TextureLayer.h
@@ -38,9 +38,12 @@ class ShaderRegistry;
class ShaderProgram;
class Texture;
class TileGeometry;
+class AvalancheWarningLayer;
class TextureLayer : public QObject {
Q_OBJECT
+ friend class AvalancheWarningLayer;
+
public:
explicit TextureLayer(unsigned resolution = 256, QObject* parent = nullptr);
void init(ShaderRegistry* shader_registry); // needs OpenGL context
diff --git a/gl_engine/UniformBuffer.cpp b/gl_engine/UniformBuffer.cpp
index 6e759eb1..660d9a64 100644
--- a/gl_engine/UniformBuffer.cpp
+++ b/gl_engine/UniformBuffer.cpp
@@ -16,13 +16,14 @@
* along with this program. If not, see .
*****************************************************************************/
#include "UniformBuffer.h"
-#include
#include "ShaderProgram.h"
#include "UniformBufferObjects.h"
-#include
#include
#include
+#include
#include
+#include
+#include
#if defined(__ANDROID__)
#include // for GL_UNIFORM_BUFFER! DONT EXACTLY KNOW WHY I NEED THIS HERE! (on other platforms it works without)
#endif
@@ -89,6 +90,7 @@ template class gl_engine::UniformBuffer;
template class gl_engine::UniformBuffer;
template class gl_engine::UniformBuffer;
template class gl_engine::UniformBuffer;
+template class gl_engine::UniformBuffer;
template class gl_engine::UniformBuffer>;
template class gl_engine::UniformBuffer>;
template class gl_engine::UniformBuffer>;
diff --git a/gl_engine/UniformBufferObjects.h b/gl_engine/UniformBufferObjects.h
index 578b1088..7e8261a1 100644
--- a/gl_engine/UniformBufferObjects.h
+++ b/gl_engine/UniformBufferObjects.h
@@ -83,6 +83,11 @@ struct uboSharedConfig {
GLuint m_overlay_shadowmaps_enabled = false;
GLuint m_padi1 = 0;
+ GLuint m_eaws_danger_rating_enabled = false;
+ GLuint m_eaws_risk_level_enabled = false;
+ GLuint m_eaws_slope_angle_enabled = false;
+ GLuint m_eaws_stop_or_go_enabled = false;
+
// WARNING: Don't move the following Q_PROPERTIES to the top, otherwise the MOC
// will do weird things with the data alignment!!
Q_PROPERTY(QVector4D sun_light MEMBER m_sun_light)
@@ -143,7 +148,6 @@ struct uboShadowConfig {
glm::vec2 buff;
};
-
// This struct is only used for unit tests
struct uboTestConfig {
Q_GADGET
diff --git a/gl_engine/Window.cpp b/gl_engine/Window.cpp
index ec37477d..9a91a6f6 100644
--- a/gl_engine/Window.cpp
+++ b/gl_engine/Window.cpp
@@ -21,6 +21,7 @@
* along with this program. If not, see .
*****************************************************************************/
#include "Window.h"
+#include "AvalancheWarningLayer.h"
#include "Context.h"
#include "Framebuffer.h"
#include "SSAO.h"
@@ -44,6 +45,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -211,6 +213,10 @@ void Window::initialise_gpu()
m_shadow_config_ubo->init();
m_shadow_config_ubo->bind_to_shader(shader_registry->all());
+ m_eaws_reports_ubo = std::make_shared>(5, "eaws_reports");
+ m_eaws_reports_ubo->init();
+ m_eaws_reports_ubo->bind_to_shader(shader_registry->all());
+
{ // INITIALIZE CPU AND GPU TIMER
using namespace std;
using nucleus::timing::CpuTimer;
@@ -218,17 +224,17 @@ void Window::initialise_gpu()
// GPU Timing Queries not supported on Web GL
#if (defined(__linux)) || defined(_WIN32) || defined(_WIN64)
- m_timer->add_timer(make_shared("ssao", "GPU", 240, 1.0f/60.0f));
+ m_timer->add_timer(make_shared("ssao", "GPU", 240, 1.0f / 60.0f));
m_timer->add_timer(make_shared("atmosphere", "GPU", 240, 1.0f / 60.0f));
- m_timer->add_timer(make_shared("tiles", "GPU", 240, 1.0f/60.0f));
- m_timer->add_timer(make_shared("tracks", "GPU", 240, 1.0f/60.0f));
- m_timer->add_timer(make_shared("shadowmap", "GPU", 240, 1.0f/60.0f));
- m_timer->add_timer(make_shared("compose", "GPU", 240, 1.0f/60.0f));
+ m_timer->add_timer(make_shared("tiles", "GPU", 240, 1.0f / 60.0f));
+ m_timer->add_timer(make_shared("tracks", "GPU", 240, 1.0f / 60.0f));
+ m_timer->add_timer(make_shared("shadowmap", "GPU", 240, 1.0f / 60.0f));
+ m_timer->add_timer(make_shared("compose", "GPU", 240, 1.0f / 60.0f));
m_timer->add_timer(make_shared("labels", "GPU", 240, 1.0f / 60.0f));
m_timer->add_timer(make_shared("picker", "GPU", 240, 1.0f / 60.0f));
- m_timer->add_timer(make_shared("gpu_total", "TOTAL", 240, 1.0f/60.0f));
+ m_timer->add_timer(make_shared("gpu_total", "TOTAL", 240, 1.0f / 60.0f));
#endif
- m_timer->add_timer(make_shared("cpu_total", "TOTAL", 240, 1.0f/60.0f));
+ m_timer->add_timer(make_shared("cpu_total", "TOTAL", 240, 1.0f / 60.0f));
m_timer->add_timer(make_shared("cpu_b2b", "TOTAL", 240, 1.0f / 60.0f));
m_timer->add_timer(make_shared("draw_list", "TOTAL", 240, 1.0f / 60.0f));
}
@@ -240,7 +246,8 @@ void Window::resize_framebuffer(int width, int height)
return;
QOpenGLFunctions* f = QOpenGLContext::currentContext()->functions();
- if (!f) return;
+ if (!f)
+ return;
m_gbuffer->resize({ width, height });
{
m_decoration_buffer->resize({ width, height });
@@ -260,7 +267,7 @@ void Window::paint(QOpenGLFramebufferObject* framebuffer)
m_timer->start_timer("cpu_total");
m_timer->start_timer("gpu_total");
- QOpenGLExtraFunctions *f = QOpenGLContext::currentContext()->extraFunctions();
+ QOpenGLExtraFunctions* f = QOpenGLContext::currentContext()->extraFunctions();
f->glEnable(GL_CULL_FACE);
f->glCullFace(GL_BACK);
@@ -340,18 +347,25 @@ void Window::paint(QOpenGLFramebufferObject* framebuffer)
}
f->glEnable(GL_DEPTH_TEST);
- f->glDepthFunc(GL_GREATER); // reverse z
+ f->glDepthFunc(GL_GEQUAL); // reverse z, reuse z buffer for sucessive passes
m_timer->start_timer("tiles");
- m_context->ortho_layer()->draw(*m_context->tile_geometry(), m_camera, culled_draw_list);
+
+ if (m_shared_config_ubo->data.m_eaws_danger_rating_enabled || m_shared_config_ubo->data.m_eaws_risk_level_enabled
+ || m_shared_config_ubo->data.m_eaws_slope_angle_enabled || m_shared_config_ubo->data.m_eaws_stop_or_go_enabled) {
+ m_context->surfaceshaded_layer()->draw(*m_context->tile_geometry(), m_camera, culled_draw_list);
+ m_context->eaws_layer()->draw(*m_context->tile_geometry(), m_camera, culled_draw_list);
+ } else {
+ m_context->ortho_layer()->draw(*m_context->tile_geometry(), m_camera, culled_draw_list);
+ }
m_timer->stop_timer("tiles");
m_gbuffer->unbind();
-
if (m_shared_config_ubo->data.m_ssao_enabled) {
m_timer->start_timer("ssao");
- m_ssao->draw(m_gbuffer.get(), &m_screen_quad_geometry, m_camera, m_shared_config_ubo->data.m_ssao_kernel, m_shared_config_ubo->data.m_ssao_blur_kernel_size);
+ m_ssao->draw(
+ m_gbuffer.get(), &m_screen_quad_geometry, m_camera, m_shared_config_ubo->data.m_ssao_kernel, m_shared_config_ubo->data.m_ssao_blur_kernel_size);
m_timer->stop_timer("ssao");
}
@@ -382,15 +396,14 @@ void Window::paint(QOpenGLFramebufferObject* framebuffer)
m_gbuffer->bind_colour_texture(1, 1);
m_compose_shader->set_uniform("texin_normal", 2);
m_gbuffer->bind_colour_texture(2, 2);
+ m_compose_shader->set_uniform("texin_atmosphere", 4);
+ m_atmospherebuffer->bind_colour_texture(0, 4);
- m_compose_shader->set_uniform("texin_atmosphere", 3);
- m_atmospherebuffer->bind_colour_texture(0, 3);
-
- m_compose_shader->set_uniform("texin_ssao", 4);
- m_ssao->bind_ssao_texture(4);
+ m_compose_shader->set_uniform("texin_ssao", 5);
+ m_ssao->bind_ssao_texture(5);
/* texture units 5 - 8 */
- m_shadowmapping->bind_shadow_maps(m_compose_shader.get(), 5);
+ m_shadowmapping->bind_shadow_maps(m_compose_shader.get(), 6);
m_timer->start_timer("compose");
m_screen_quad_geometry.draw();
@@ -459,13 +472,15 @@ void Window::paint(QOpenGLFramebufferObject* framebuffer)
emit tile_stats_ready(tile_stats);
}
-void Window::shared_config_changed(gl_engine::uboSharedConfig ubo) {
+void Window::shared_config_changed(gl_engine::uboSharedConfig ubo)
+{
m_shared_config_ubo->data = ubo;
m_shared_config_ubo->update_gpu_data();
emit update_requested();
}
-void Window::reload_shader() {
+void Window::reload_shader()
+{
auto do_reload = [this]() {
auto* shader_manager = m_context->shader_registry();
shader_manager->reload_shaders();
@@ -473,6 +488,7 @@ void Window::reload_shader() {
m_shared_config_ubo->bind_to_shader(shader_manager->all());
m_camera_config_ubo->bind_to_shader(shader_manager->all());
m_shadow_config_ubo->bind_to_shader(shader_manager->all());
+ m_eaws_reports_ubo->bind_to_shader(shader_manager->all());
qDebug("all shaders reloaded");
emit update_requested();
};
@@ -480,7 +496,7 @@ void Window::reload_shader() {
// Reload shaders from the web and afterwards do the reload
ShaderProgram::web_download_shader_files_and_put_in_cache(do_reload);
#else
- // Reset shader cache. The shaders will then be reload from file
+ // Reset shader cache. The shaders will then be reload from file
ShaderProgram::reset_shader_cache();
do_reload();
#endif
@@ -513,6 +529,14 @@ void Window::pick_value(const glm::dvec2& screen_space_coordinates)
emit value_picked(value);
}
+void Window::update_eaws_reports(const nucleus::avalanche::UboEawsReports& newUboEawsReports)
+{
+ assert(m_eaws_reports_ubo);
+ m_eaws_reports_ubo->data = newUboEawsReports;
+ m_eaws_reports_ubo->update_gpu_data();
+ emit update_requested();
+}
+
glm::dvec3 Window::position(const glm::dvec2& normalised_device_coordinates)
{
return m_camera.position() + m_camera.ray_direction(normalised_device_coordinates) * (double)depth(normalised_device_coordinates);
diff --git a/gl_engine/Window.h b/gl_engine/Window.h
index f9c0bb3c..16a38aaf 100644
--- a/gl_engine/Window.h
+++ b/gl_engine/Window.h
@@ -44,6 +44,11 @@ class QOpenGLTexture;
class QOpenGLShaderProgram;
class QOpenGLVertexArrayObject;
+namespace nucleus::avalanche {
+class UIntIdManager;
+struct UboEawsReports;
+} // namespace nucleus::avalanche
+
namespace gl_engine {
class MapLabels;
@@ -52,7 +57,6 @@ class Framebuffer;
class SSAO;
class ShadowMapping;
class Context;
-
class Window : public nucleus::AbstractRenderWindow, public nucleus::camera::AbstractDepthTester {
Q_OBJECT
public:
@@ -75,6 +79,7 @@ public slots:
void shared_config_changed(gl_engine::uboSharedConfig ubo);
void reload_shader();
void pick_value(const glm::dvec2& screen_space_coordinates) override;
+ void update_eaws_reports(const nucleus::avalanche::UboEawsReports& uboEawsReports);
signals:
void timer_measurements_ready(QList values);
@@ -98,7 +103,7 @@ public slots:
std::shared_ptr> m_shared_config_ubo; // needs opengl context
std::shared_ptr> m_camera_config_ubo;
std::shared_ptr> m_shadow_config_ubo;
-
+ std::shared_ptr> m_eaws_reports_ubo;
helpers::ScreenQuadGeometry m_screen_quad_geometry;
nucleus::camera::Definition m_camera;
@@ -110,7 +115,6 @@ public slots:
QString m_debug_scheduler_stats;
std::unique_ptr m_timer;
-
};
-} // namespace
+} // namespace gl_engine
diff --git a/gl_engine/shaders/compose.frag b/gl_engine/shaders/compose.frag
index fb886f34..2d6259ca 100644
--- a/gl_engine/shaders/compose.frag
+++ b/gl_engine/shaders/compose.frag
@@ -151,7 +151,6 @@ highp float csm_shadow_term(highp vec4 pos_cws, highp vec3 normal_ws, out lowp i
void main() {
lowp vec3 albedo = texture(texin_albedo, texcoords).rgb;
-
highp vec4 pos_dist = texture(texin_position, texcoords);
highp vec3 pos_cws = pos_dist.xyz;
highp float dist = pos_dist.w; // negative if sky
diff --git a/gl_engine/shaders/eaws.frag b/gl_engine/shaders/eaws.frag
new file mode 100644
index 00000000..2342b1e9
--- /dev/null
+++ b/gl_engine/shaders/eaws.frag
@@ -0,0 +1,196 @@
+/*****************************************************************************
+* AlpineMaps.org
+* Copyright (C) 2022 Adam Celarek
+* Copyright (C) 2023 Gerald Kimmersdorfer
+* Copyright (C) 2024 Jörg Christian Reiher
+* Copyright (C) 2024 Johannes Eschner
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see .
+*****************************************************************************/
+#ifdef GL_ES
+precision highp float;
+#endif
+
+#include "shared_config.glsl"
+#include "camera_config.glsl"
+#include "encoder.glsl"
+#include "tile_id.glsl"
+#include "eaws.glsl"
+
+uniform highp usampler2DArray texture_sampler;
+uniform highp usampler2D instanced_texture_array_index_sampler;
+uniform highp usampler2D instanced_texture_zoom_sampler;
+uniform highp sampler2DArray texture_sampler2;
+uniform highp usampler2D instanced_texture_array_index_sampler2;
+uniform highp usampler2D instanced_texture_zoom_sampler2;
+
+layout (location = 0) out lowp vec3 texout_albedo;
+layout (location = 1) out highp vec4 texout_position;
+layout (location = 2) out highp uvec2 texout_normal;
+layout (location = 3) out lowp vec4 texout_depth;
+
+flat in highp uvec3 var_tile_id;
+in highp vec2 var_uv;
+in highp vec3 var_pos_cws;
+in highp vec3 var_normal;
+flat in highp uint instance_id;
+
+#if CURTAIN_DEBUG_MODE > 0
+in lowp float is_curtain;
+#endif
+flat in lowp vec3 vertex_color;
+in highp float var_altitude;
+
+highp vec3 normal_by_fragment_position_interpolation() {
+ highp vec3 dFdxPos = dFdx(var_pos_cws);
+ highp vec3 dFdyPos = dFdy(var_pos_cws);
+ return normalize(cross(dFdxPos, dFdyPos));
+}
+
+void main() {
+#if CURTAIN_DEBUG_MODE == 2
+ if (is_curtain == 0.0) {
+ discard;
+ }
+#endif
+ highp uvec3 tile_id = var_tile_id;
+ highp vec2 uv = var_uv;
+
+ // photo texture drawing
+ decrease_zoom_level_until(tile_id, uv, texelFetch(instanced_texture_zoom_sampler2, ivec2(instance_id, 0), 0).x);
+ highp float texture_layer2_f = float(texelFetch(instanced_texture_array_index_sampler2, ivec2(instance_id, 0), 0).x);
+
+ lowp vec3 terrain_color = texture(texture_sampler2, vec3(uv, texture_layer2_f)).rgb;
+ terrain_color = mix(terrain_color, conf.material_color.rgb, conf.material_color.a);
+
+ // Write Position (and distance) in gbuffer
+ highp float dist = length(var_pos_cws);
+ texout_position = vec4(var_pos_cws, dist);
+
+ // Write and encode normal in gbuffer
+ highp vec3 normal = vec3(0.0);
+ if (conf.normal_mode == 0u) normal = normal_by_fragment_position_interpolation();
+ else normal = var_normal;
+ texout_normal = octNormalEncode2u16(normal);
+
+ // Write and encode distance for readback
+ texout_depth = vec4(depthWSEncode2n8(dist), 0.0, 0.0);
+
+ // HANDLE OVERLAYS (and mix it with the albedo color) THAT CAN JUST BE DONE IN THIS STAGE
+ // (because of DATA thats not forwarded)
+ // NOTE: Performancewise its generally better to handle overlays in the compose step! (screenspace effect)
+ if (conf.overlay_mode > 0u && conf.overlay_mode < 100u) {
+ lowp vec3 overlay_color = vec3(0.0);
+ switch(conf.overlay_mode) {
+ case 1u: overlay_color = normal * 0.5 + 0.5; break;
+ default: overlay_color = vertex_color;
+ }
+ terrain_color = mix(terrain_color, overlay_color, conf.overlay_strength);
+ }
+
+#if CURTAIN_DEBUG_MODE == 1
+ if (is_curtain > 0.0) {
+ texout_albedo = vec3(1.0, 0.0, 0.0);
+ return;
+ }
+#endif
+
+
+ //From here on: EAWS Layer Drawing
+ decrease_zoom_level_until(tile_id, uv, texelFetch(instanced_texture_zoom_sampler, ivec2(instance_id, 0), 0).x);
+ highp float texture_layer_f = float(texelFetch(instanced_texture_array_index_sampler, ivec2(instance_id, 0), 0).x);
+
+ highp uint eawsRegionId = texelFetch(texture_sampler, ivec3(int(uv.x * float(512)), int(uv.y * float(512)) , texture_layer_f), 0).r;
+ ivec4 report = eaws.reports[eawsRegionId];
+ vec3 eaws_color = color_no_report_available;
+
+ // // debug output regions
+ //eaws_color = vec3(float((eawsRegionId >> 8u) & 255u) / 256.0, float(eawsRegionId & 255u) / 256.0, float((eawsRegionId >> 16u) & 255u) / 256.0);
+ //return;
+
+ // Get altitude and slope normal
+ highp float frag_height = var_altitude;
+ vec3 fragNormal = var_normal; // just to clarify naming
+
+ // calculate frag color according to selected overlay type
+ if(bool(conf.eaws_slope_angle_enabled)) // Slope Angle overlay is activated (does not require avalanche report)
+ {
+ // assign a color to slope angle obtained from (not normalized) normal
+ eaws_color = slopeAngleColorFromNormal(fragNormal);
+ }
+ else if(report.a > 0 || report.z > 0 ) // avalanche report is available. report.x = < 0 would mean no report available since .x stores the exposition as described in the Masters THesis of Joey which must be >0
+ {
+ // get avalanche ratings for eaws region of current fragment
+ int bound = report.y; // bound dividing moutain in Hi region and low region
+ int ratingHi = report.a; // rating should be value in {0,1,2,3,4}
+ int ratingLo = report.z; // rating should be value in {0,1,2,3,4}
+ int rating = ratingLo;
+
+ // if eaws report overlay activated: calculate color for danger level(blend at borders)
+ if(bool(conf.eaws_danger_rating_enabled))
+ {
+ // color fragment according to danger level
+ float margin = 200.0; // margin within which colorblending between hi and lo happens
+ if(frag_height > float(bound))
+ eaws_color = color_from_eaws_danger_rating(ratingHi);
+ else if (frag_height < float(bound) - margin)
+ eaws_color = color_from_eaws_danger_rating(ratingLo);
+ else
+ {
+ // around border: blend colors between upper and lower danger rating
+ float a = (frag_height - (float(bound) - margin)) / margin; // This is a value between 0 and 1
+ eaws_color = mix(color_from_eaws_danger_rating(ratingLo), color_from_eaws_danger_rating(ratingHi), a);
+ }
+ }
+
+ // If risk Level Overlay is activated, read unfavorable expositions information and check if fragment has unfavorable exposition
+ else if(bool(conf.eaws_risk_level_enabled))
+ {
+ // report.x encodes dangerous directions bitwise as 1000000000 = North is unfavorable, 01000000 = NE is unfavorable etc.
+ // direction() returns the direction of the fragment
+ // the bitwise & comparison checks if direction bit is marked in report.x as unfavorable direction
+ bool unfavorable = (0 != (report.x & direction(fragNormal)));
+
+ // color the fragment according to danger level
+ float margin = 200.f; // margin within which colorblending between hi and lo happens
+ if(frag_height > float(bound))
+ eaws_color = color_from_snowCard_risk_parameters(ratingHi, fragNormal, unfavorable);
+ else if (frag_height < float(bound) - margin)
+ eaws_color = color_from_snowCard_risk_parameters(ratingLo, fragNormal, unfavorable);
+ else
+ {
+ // around border: blend colors between upper and lower danger rating
+ float a = (frag_height - (float(bound) - margin)) / margin; // This is a value between 0 and 1
+ vec3 colorLo = color_from_snowCard_risk_parameters(ratingLo, fragNormal, unfavorable);
+ vec3 colorHi = color_from_snowCard_risk_parameters(ratingHi, fragNormal, unfavorable);
+ eaws_color = mix(colorLo, colorHi, a); // color_from_snowCard_risk_parameters(int eaws_danger_rating, int slope_angle_in_deg, bool unfavorable)
+ }
+ }
+
+ //if Stop or GO Layer activated
+ else if(bool(conf.eaws_stop_or_go_enabled))
+ {
+ // Get eaws danger rating from fragment altitude
+ int eaws_danger_rating = frag_height >= float(bound)? ratingHi: ratingLo;
+ eaws_color = color_from_stop_or_go(fragNormal, eaws_danger_rating);
+ }
+ }
+
+ // merge photo texture with eaws color
+ if(eaws_color.r > 0.0 || eaws_color.g > 0.0 || eaws_color.b > 0.0)
+ texout_albedo = mix(terrain_color, eaws_color, 0.5);
+ else if(eaws_color.r < 0.0 ) // no report available or danger rating = 0: grey
+ texout_albedo = mix(terrain_color, vec3(0.5,0.5,0.5), 0.9);
+ else
+ texout_albedo = terrain_color;
+}
diff --git a/gl_engine/shaders/eaws.glsl b/gl_engine/shaders/eaws.glsl
new file mode 100644
index 00000000..e9f322b5
--- /dev/null
+++ b/gl_engine/shaders/eaws.glsl
@@ -0,0 +1,184 @@
+/*****************************************************************************
+ * AlpineMaps.org
+ * Copyright (C) 2024 Joerg Christian Reiher
+ * Copyright (C) 2024 Johannes Eschner
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *****************************************************************************/
+
+#ifdef GL_ES
+precision highp float;
+#endif
+
+layout (std140) uniform eaws_reports {
+ // length of array must be the same as in nucleus::avalanche::uboEawsReports on host side
+ ivec4 reports[1000];
+} eaws;
+
+// Color for areas where no report is available
+vec3 color_no_report_available = vec3(-1.0,-1.0,-1.0);
+
+vec3 color_from_eaws_danger_rating(int rating)
+{
+ if(1 == rating) return vec3(0.0,1.0,0.0); // green for 1 = low
+ if(2 == rating) return vec3(1.0,1.0,0.0); // yellow for 2 = moderate
+ if(3 == rating) return vec3(1.0,0.53f,0.0); // orange for 3 = considerable
+ if(4 == rating) return vec3(1.0,0.0,0.0); // red for 4 = high
+ if(5 == rating) return vec3(0.5333,0.0,0.0); // dark red for 5 = extreme
+ return(color_no_report_available); // grey for undefined cases
+}
+
+vec3 snowCardLevel[6] = vec3[6](
+ vec3(1.0 , 1.0 , 1.0 ), // level 0 = white
+ vec3(0.9961 , 0.8000, 0.3608), // level 1 = yellow
+ vec3(0.9922 , 0.5530, 0.2353), // level 2 = orange
+ vec3(0.9412 , 0.2314, 0.1255), // level 3 = red
+ vec3(0.4588 , 0.0510, 0.1333), // level 4 = dark red
+ vec3(0.0 ,0.0 ,0.0 ) // level 5 = black
+);
+
+
+vec3 slopeAngleColorFromNormal(vec3 notNormalizedNormal)
+{
+ // Calculte slope angle
+ vec3 normal = normalize(notNormalizedNormal);
+ float slope_in_rad = acos(normal.z);
+ float slope_in_deg = degrees(slope_in_rad);
+
+ // Get color for slope angle
+ vec3 slopeColor;
+ if(slope_in_deg < 30.0) // white
+ slopeColor= vec3(1.0,1.0,1.0);
+ else if (30.0 <= slope_in_deg && slope_in_deg < 35.0) //yellow
+ slopeColor = vec3(0.9490196078431372, 0.8980392156862745, 0.0392156862745098);
+ else if (35.0 <= slope_in_deg && slope_in_deg < 40.0) //orange
+ slopeColor = vec3(0.95686274, 0.43529411764705883,0.1411764705882353);
+ else if (40.0 <= slope_in_deg && slope_in_deg < 45.0) //red
+ slopeColor = vec3(0.8705882352941177, 0.0196078431372549, 0.3568627450980392);
+ else // purple if > 45
+ slopeColor = vec3(0.7843137254901961, 0.5372549019607843, 0.7333333333333333);
+
+ // Return color
+ return slopeColor;
+}
+
+// SnowCard Risk overlay:
+// adapts eaws rating according to slope angle and favorable/unfavorable position
+// E.g. favorable1[0] contains snowcard rating for ewas rating level 1 at favorable position for 27deg slope angle,
+// favorable1[1]for eaws rating 1 at 28deg etc. up to 45deg
+// --------------------------------------------
+// 27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45
+int favorable1[19] = int[19]( 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5); // 1 = danger rating, favorable
+int favorable2[19] = int[19]( 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 5); // 2 = danger rating, favorable
+int favorable3[19] = int[19]( 0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 4, 5); // 3 = danger rating, favorable
+int favorable4[19] = int[19]( 1, 2, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5); // 4 = danger rating, favorable
+int favorable5[19] = int[19]( 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5); // 5 = danger rating, favorable
+int unfavorable1[19] = int[19]( 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 5); // 1 = danger rating, unfavorable
+int unfavorable2[19] = int[19]( 0, 0, 0, 1, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5); // 2 = danger rating, unfavorable
+int unfavorable3[19] = int[19]( 0, 0, 0, 1, 2, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5); // 3 = danger rating, unfavorable
+int unfavorable4[19] = int[19]( 1, 2, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5); // 4 = danger rating, unfavorable
+int unfavorable5[19] = int[19]( 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5); // 5 = danger rating, unfavorable
+
+// returns warning level according to SnowCard for a given eaws reprot on a slope angle at favorable/unfavorable exposition
+// returns 0 if exposition contains something else than 1(favorable) or -1(unfavorable)
+vec3 color_from_snowCard_risk_parameters(int eaws_danger_rating, vec3 notNormalizedNormal, bool unfavorable)
+{
+ // Calculate slope angle and return black if steeper than 45 deg
+ vec3 normal = normalize(notNormalizedNormal);
+ float slope_in_rad = acos(normal.z);
+ int slopeAngleAsInt = int(degrees(slope_in_rad));
+
+ // Truncate slope angle and calculate corresponding index for accessing array with danger ratings
+ int angle = min(max(slopeAngleAsInt,27), 45); // angle below 27deg is treated like 27deg (same for above 45deg)
+ int idx = angle-27; // array[0] contains rating for 27 degree, see above
+
+ // Calculate mixing factor for smooth transition over borders
+ float a = degrees(slope_in_rad) - float(slopeAngleAsInt);
+
+ //Avoid out index overflow
+ int nextIdx = min(idx+1,18);
+ if(true == unfavorable)
+ {
+ // pick unfavorable array according to eaws danger rating
+ switch(eaws_danger_rating)
+ {
+ case 0: return color_no_report_available;
+ case 1: return (1.0-a) * snowCardLevel[unfavorable1[idx]] + a * snowCardLevel[unfavorable1[nextIdx]];
+ case 2: return (1.0-a) * snowCardLevel[unfavorable2[idx]] + a * snowCardLevel[unfavorable2[nextIdx]];
+ case 3: return (1.0-a) * snowCardLevel[unfavorable3[idx]] + a * snowCardLevel[unfavorable3[nextIdx]];
+ case 4: return (1.0-a) * snowCardLevel[unfavorable4[idx]] + a * snowCardLevel[unfavorable4[nextIdx]];
+ case 5: return (1.0-a) * snowCardLevel[unfavorable5[idx]] + a * snowCardLevel[unfavorable5[nextIdx]];
+ }
+ }
+ else // favorable direction
+ {
+ // pick favorable array according to eaws danger rating
+ switch(eaws_danger_rating)
+ {
+ case 0: return color_no_report_available;
+ case 1: return (1.0-a) * snowCardLevel[favorable1[idx]] + a * snowCardLevel[favorable1[nextIdx]];
+ case 2: return (1.0-a) * snowCardLevel[favorable2[idx]] + a * snowCardLevel[favorable2[nextIdx]];
+ case 3: return (1.0-a) * snowCardLevel[favorable3[idx]] + a * snowCardLevel[favorable3[nextIdx]];
+ case 4: return (1.0-a) * snowCardLevel[favorable4[idx]] + a * snowCardLevel[favorable4[nextIdx]];
+ case 5: return (1.0-a) * snowCardLevel[favorable5[idx]] + a * snowCardLevel[favorable5[nextIdx]];
+ }
+ }
+}
+
+
+// Colors for stop or go map
+bool go0to30[5] = bool[5](true, true, true, true, false);
+bool go30to35[5] = bool[5](true, true, true, false, false);
+bool go35to40[5] = bool[5](true, true, false, false, false);
+bool goOver40[5] = bool[5](true, false, false, false, false);
+vec3 color_from_stop_or_go(vec3 notNormalizedNormal, int eaws_danger_rating)
+{
+ // danger rating must be in [1,5]
+ if(eaws_danger_rating < 1 || 5 < eaws_danger_rating) return color_no_report_available;
+ int idx = eaws_danger_rating -1;
+
+ // Calculte slope angle
+ vec3 normal = normalize(notNormalizedNormal);
+ float slope_in_rad = acos(normal.z);
+ float slope_in_deg = degrees(slope_in_rad);
+ bool go = false;
+ if(slope_in_deg <= 30.0) go = go0to30[idx];
+ else if (slope_in_deg <= 35.0) go = go30to35[idx];
+ else if (slope_in_deg <= 40.0) go = go35to40[idx];
+ else if (slope_in_deg <= 45.0) go = goOver40[idx];
+
+ if(go) return vec3(0.0,0.0,0.0); // GO : return 0 0 0 so overlay is transparent
+ return vec3(1.0,0.0,0.0); // STOP: return red;
+}
+
+// converts a 3d normal vector into a bit encoded direction N, NE, E etc
+// n must be a unitvector !!!
+int direction(vec3 n)
+{
+ //Ensure n has length = 1
+ n = normalize(n);
+
+ // calculate direction of fragment (North, South etc
+ float angle = sign(n.y)*degrees(acos(n.x));
+ if(112.5 <= angle && angle < 157.5) return 1; // Encodes NW = 00000001
+ else if(157.5 <= abs(angle) && abs(angle) <=180.0) return (1<<1); // Encodes W = 00000010
+ else if(-157.5 <= angle && angle < -112.5) return (1<<2); // Encodes SW = 00000100
+ else if(-112.5 <= angle && angle < -67.5) return (1<<3); // Encodes S = 00001000
+ else if(-112.5 <= angle && angle < -67.5) return (1<<4); // Encodes SE = 00010000
+ else if(0.0 <= abs(angle) && abs(angle) < 22.5) return (1<<5); // Encodes E = 00100000
+ else if(22.5 <= angle && angle < 67.5) return (1<<6); // Encode NE = 01000000
+ else return (1<<7); // Enncodes N = 10000000
+}
+
+
+
diff --git a/gl_engine/shaders/shared_config.glsl b/gl_engine/shaders/shared_config.glsl
index 2f71e202..e1186eca 100644
--- a/gl_engine/shaders/shared_config.glsl
+++ b/gl_engine/shaders/shared_config.glsl
@@ -52,4 +52,9 @@ layout (std140) uniform shared_config {
highp uint csm_enabled;
highp uint overlay_shadowmaps_enabled;
highp uint padi1;
+
+ highp uint eaws_danger_rating_enabled;
+ highp uint eaws_risk_level_enabled;
+ highp uint eaws_slope_angle_enabled;
+ highp uint eaws_stop_or_go_enabled;
} conf;
diff --git a/gl_engine/shaders/tile.frag b/gl_engine/shaders/tile.frag
index abb8493b..5f61d5fe 100644
--- a/gl_engine/shaders/tile.frag
+++ b/gl_engine/shaders/tile.frag
@@ -33,6 +33,7 @@ layout (location = 0) out lowp vec3 texout_albedo;
layout (location = 1) out highp vec4 texout_position;
layout (location = 2) out highp uvec2 texout_normal;
layout (location = 3) out lowp vec4 texout_depth;
+layout (location = 4) out lowp vec4 texout_eaws;
flat in highp uvec3 var_tile_id;
in highp vec2 var_uv;
@@ -44,10 +45,6 @@ in lowp float is_curtain;
flat in lowp vec3 vertex_color;
flat in highp uint instance_id;
-highp float calculate_falloff(highp float dist, highp float from, highp float to) {
- return clamp(1.0 - (dist - from) / (to - from), 0.0, 1.0);
-}
-
highp vec3 normal_by_fragment_position_interpolation() {
highp vec3 dFdxPos = dFdx(var_pos_cws);
highp vec3 dFdyPos = dFdy(var_pos_cws);
diff --git a/gl_engine/shaders/tile.glsl b/gl_engine/shaders/tile.glsl
index 02753b7f..78db0527 100644
--- a/gl_engine/shaders/tile.glsl
+++ b/gl_engine/shaders/tile.glsl
@@ -37,8 +37,8 @@ highp float y_to_lat(highp float y) {
return latRad;
}
-
-void compute_vertex(out vec3 position, out vec2 uv, out uvec3 tile_id, bool compute_normal, out vec3 normal) {
+// Note: position contains a corrected z value for normal calculation, altitude is the height above sealevel
+void compute_vertex(out vec3 position, out vec2 uv, out uvec3 tile_id, bool compute_normal, out vec3 normal, out float altitude) {
tile_id = unpack_tile_id(instance_tile_id_packed);
highp uvec3 dtm_tile_id = tile_id;
@@ -90,7 +90,7 @@ void compute_vertex(out vec3 position, out vec2 uv, out uvec3 tile_id, bool comp
// highp float dtm_texture_layer_f = float(texelFetch(instance_2_array_index_sampler, ivec2(uint(gl_InstanceID), 0), 0).x);
highp float dtm_texture_layer_f = float(dtm_array_index);
float altitude_tex = float(texture(height_tex_sampler, vec3(dtm_uv, dtm_texture_layer_f)).r);
-
+ altitude = 0.125 * altitude_tex;
// Note: for higher zoom levels it would be enough to calculate the altitude_correction_factor on cpu
// for lower zoom levels we could bake it into the texture.
// there was no measurable difference despite a cos and a atan, so leaving as is for now.
@@ -142,5 +142,6 @@ void compute_vertex(out vec3 position) {
highp vec2 uv;
highp uvec3 tile_id;
vec3 normal;
- compute_vertex(position, uv, tile_id, false, normal);
+ highp float altitude;
+ compute_vertex(position, uv, tile_id, false, normal, altitude);
}
diff --git a/gl_engine/shaders/tile.vert b/gl_engine/shaders/tile.vert
index f43c95f0..c29d648e 100644
--- a/gl_engine/shaders/tile.vert
+++ b/gl_engine/shaders/tile.vert
@@ -31,10 +31,10 @@ out lowp float is_curtain;
#endif
flat out lowp vec3 vertex_color;
flat out highp uint instance_id;
-
+out highp float var_altitude;
void main() {
- compute_vertex(var_pos_cws, var_uv, var_tile_id, conf.normal_mode == 1u, var_normal);
+ compute_vertex(var_pos_cws, var_uv, var_tile_id, conf.normal_mode == 1u, var_normal, var_altitude);
gl_Position = camera.view_proj_matrix * vec4(var_pos_cws, 1);
instance_id = uint(gl_InstanceID);
diff --git a/misc/build_custom_qt_for_webassembly b/misc/build_custom_qt_for_webassembly
new file mode 100755
index 00000000..a24a6b13
--- /dev/null
+++ b/misc/build_custom_qt_for_webassembly
@@ -0,0 +1,22 @@
+#!/bin/bash
+qt_version="6.8.0"
+
+# dest_dir="wasm_lite_lto"
+# build_dir="wasm_lite_lto_build"
+# rm -rf ./${dest_dir}/*
+# rm -rf ./${build_dir}/*
+# mkdir -p ${build_dir}
+# cd ${build_dir}
+# /home/madam/bin/Qt/${qt_version}/Src/configure -qt-host-path /home/madam/bin/Qt/${qt_version}/gcc_64/ -release -ltcg $(cat /home/madam/bin/Qt/${qt_version}/qt_lite_alpine_maps.txt) -prefix /home/madam/bin/Qt/${qt_version}/${dest_dir}/
+# sed -i 's/-flto=thin/-flto/g' ./build.ninja
+# cmake --build . --parallel && cmake --install .
+# cd ..
+
+dest_dir="wasm_lite"
+build_dir="wasm_lite_build"
+rm -rf ./${dest_dir}/*
+rm -rf ./${build_dir}/*
+mkdir -p ${build_dir}
+cd ${build_dir}
+/home/madam/bin/Qt/${qt_version}/Src/configure -qt-host-path /home/madam/bin/Qt/${qt_version}/gcc_64/ -release $(cat /home/madam/bin/Qt/${qt_version}/qt_lite.txt) -prefix /home/madam/bin/Qt/${qt_version}/${dest_dir}/ && cmake --build . --parallel && cmake --install .
+cd ..
diff --git a/nucleus/CMakeLists.txt b/nucleus/CMakeLists.txt
index a50455b7..0080cc21 100644
--- a/nucleus/CMakeLists.txt
+++ b/nucleus/CMakeLists.txt
@@ -29,6 +29,7 @@ if(ALP_ENABLE_LABELS)
alp_add_git_repository(vector_tiles URL https://github.com/AlpineMapsOrg/vector-tile.git COMMITISH faba88257716c4bc01ebd44d8b8b98f711ecb78c)
endif()
alp_add_git_repository(goofy_tc URL https://github.com/AlpineMapsOrgDependencies/Goofy_slim.git COMMITISH 13b228784960a6227bb6ca704ff34161bbac1b91 DO_NOT_ADD_SUBPROJECT)
+alp_add_git_repository(cdt URL https://github.com/artem-ogre/CDT.git COMMITISH 46f1ce1f495a97617d90e8c833d0d29406335fdf DO_NOT_ADD_SUBPROJECT)
add_library(zppbits INTERFACE)
target_include_directories(zppbits SYSTEM INTERFACE ${zppbits_SOURCE_DIR})
@@ -40,6 +41,9 @@ set_target_properties(goofy_tc PROPERTIES SYSTEM true)
add_library(tl_expected INTERFACE)
target_include_directories(tl_expected INTERFACE ${tl_expected_SOURCE_DIR}/include)
+add_library(cdt INTERFACE)
+target_include_directories(cdt INTERFACE ${cdt_SOURCE_DIR}/CDT/include)
+
set(alp_version_out ${CMAKE_BINARY_DIR}/alp_version/nucleus/version.cpp)
# cmake tests for existance of ${alp_version_out}.do_always_run. since it's always missing, cmake tries to generate it using this command.
@@ -113,6 +117,7 @@ qt_add_library(nucleus STATIC
tile/GeometryScheduler.h tile/GeometryScheduler.cpp
utils/error.h
utils/lang.h
+ utils/rasterizer.h utils/rasterizer.cpp
tile/SchedulerDirector.h tile/SchedulerDirector.cpp
tile/drawing.h tile/drawing.cpp
camera/gesture.h
@@ -120,7 +125,13 @@ qt_add_library(nucleus STATIC
if (ALP_ENABLE_AVLANCHE_WARNING_LAYER)
target_sources(nucleus
- PUBLIC avalanche/eaws.h avalanche/eaws.cpp
+ PRIVATE
+ avalanche/eaws.h avalanche/eaws.cpp
+ avalanche/Scheduler.h avalanche/Scheduler.cpp
+ avalanche/ReportLoadService.h avalanche/ReportLoadService.cpp
+ avalanche/UIntIdManager.h avalanche/UIntIdManager.cpp
+ avalanche/eaws.h avalanche/eaws.cpp
+ avalanche/setup.h
)
target_link_libraries(nucleus PUBLIC Qt::Gui)
endif()
@@ -147,7 +158,7 @@ endif()
target_include_directories(nucleus PUBLIC ${CMAKE_SOURCE_DIR})
# Please keep Qt::Gui outside the nucleus. If you need it optional via a cmake based switch
-target_link_libraries(nucleus PUBLIC radix Qt::Core Qt::Network zppbits tl_expected nucleus_version stb_slim goofy_tc)
+target_link_libraries(nucleus PUBLIC radix Qt::Core Qt::Network zppbits tl_expected nucleus_version stb_slim goofy_tc cdt)
qt_add_resources(nucleus "icons"
PREFIX "/map_icons"
diff --git a/nucleus/avalanche/ReportLoadService.cpp b/nucleus/avalanche/ReportLoadService.cpp
new file mode 100644
index 00000000..0d1ff958
--- /dev/null
+++ b/nucleus/avalanche/ReportLoadService.cpp
@@ -0,0 +1,191 @@
+#include "nucleus/avalanche/ReportLoadService.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace nucleus::avalanche {
+
+// helper function that encodes reports in an uint vector
+nucleus::avalanche::UboEawsReports convertReportsToUbo(
+ UboEawsReports& ubo, const std::vector& reports, std::shared_ptr m_uint_id_manager)
+{
+ // Fill array with initial vectors
+ std::fill(ubo.reports, ubo.reports + 1000, glm::ivec4(-1, 0, 0, 0));
+
+ // if reports arrived as expected write them to ubo object
+ for (const nucleus::avalanche::ReportTUWien& report : reports) {
+
+ // Correct region id if it has invalid format: That means create new sub regions with same forecast
+ std::vector new_region_ids;
+ if (report.region_id.endsWith("-00")) {
+ // remove last three digits
+ QString parent_region_id = report.region_id;
+ parent_region_id = parent_region_id.left(parent_region_id.length() - 3);
+
+ // check if our map contains subregions of current report region and save these as vector
+ uint i = 1;
+ QString new_region_id = parent_region_id + QString("-01");
+ while (m_uint_id_manager->contains(new_region_id)) {
+ new_region_ids.push_back(new_region_id);
+ i++;
+ new_region_id = i < 10 ? parent_region_id + QString("-0") + QString::number(i) : parent_region_id + QString("-") + QString::number(i);
+ }
+
+ // If no subregions of report region were found, use report region
+ if (new_region_ids.empty()) {
+ new_region_ids.push_back(parent_region_id);
+ }
+ } else {
+ new_region_ids.push_back(report.region_id);
+ }
+
+ // Write (corrected) region(s) to ubo
+ for (QString new_region_id : new_region_ids) {
+ uint idx = m_uint_id_manager->convert_region_id_to_internal_id(new_region_id);
+ ubo.reports[idx] = glm::ivec4(report.unfavorable, report.border, report.rating_lo, report.rating_hi);
+ }
+ }
+ return ubo;
+}
+
+// Constructor: only creates network manager that lives the whole runtime. Ideally the whole app would only use one Manager !
+ReportLoadService::ReportLoadService(std::shared_ptr uint_id_manager)
+ : m_network_manager(new QNetworkAccessManager(this))
+ , m_uint_id_manager(uint_id_manager)
+{
+}
+
+void ReportLoadService::load_from_tu_wien(const QDate& date) const
+{
+
+ // Prepare ubo to be returned
+ UboEawsReports ubo;
+ std::fill(ubo.reports, ubo.reports + 1000, glm::ivec4(-1, 0, 0, 0));
+
+ QString date_string = date.toString("yyyy-MM-dd");
+ QUrl qurl(QString("https://alpinemaps.cg.tuwien.ac.at/avalanche-reports-v2/get-current-report?date=" + date_string));
+ QNetworkRequest request(qurl);
+ request.setTransferTimeout(int(8000));
+ request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
+#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
+ request.setAttribute(QNetworkRequest::UseCredentialsAttribute, false);
+#endif
+
+ // Make a GET request to the provided url
+ QNetworkReply* reply = m_network_manager->get(request);
+
+ // Process the reply
+ QObject::connect(reply, &QNetworkReply::finished, [this, ubo, reply]() {
+ // Error message is emitted in case something goes wrong
+ QString error_message("\nERROR: ");
+
+ // Check if Network Error occured
+ if (reply->error() != QNetworkReply::NoError) {
+ std::cout << "\nERROR: Eaws Report Load Service has network error.";
+ emit this->load_from_TU_Wien_finished(ubo);
+ reply->deleteLater();
+ return;
+ }
+
+ // Read the response data
+ QByteArray data = reply->readAll();
+
+ // Convert data to Json
+ QJsonParseError parse_error;
+ QJsonDocument json_document = QJsonDocument::fromJson(data, &parse_error);
+
+ // Check for parsing error
+ if (parse_error.error != QJsonParseError::NoError) {
+ error_message.append("Parse error = ").append(parse_error.errorString());
+ std::cout << error_message.toStdString();
+ emit this->load_from_TU_Wien_finished(ubo);
+ reply->deleteLater();
+ return;
+ }
+
+ // Check for empty json
+ if (json_document.isEmpty() || json_document.isNull()) {
+ error_message.append("Empty or Null json.");
+ std::cout << error_message.toStdString();
+ emit this->load_from_TU_Wien_finished(ubo);
+ reply->deleteLater();
+ return;
+ }
+
+ // Check if json doc is array
+ if (!json_document.isArray()) {
+ error_message.append("jsonDocument does not contain array.");
+ std::cout << error_message.toStdString();
+ emit this->load_from_TU_Wien_finished(ubo);
+ reply->deleteLater();
+ return;
+ }
+
+ // Create json Array and check if empty
+ QJsonArray jsonArray = json_document.array();
+ if (jsonArray.isEmpty()) {
+ error_message.append("Array empty!");
+ std::cout << error_message.toStdString();
+ emit this->load_from_TU_Wien_finished(ubo);
+ reply->deleteLater();
+ return;
+ }
+
+ // parse array containing report for each region
+ std::vector region_ratings;
+ for (const QJsonValue& jsonValue_region_rating : jsonArray) {
+ // prepare an item that goes into the bulletin
+ ReportTUWien region_rating;
+
+ // Check if Json array contains json objects
+ if (!jsonValue_region_rating.isObject()) {
+ error_message.append("json object is array of other type than json object");
+ std::cout << error_message.toStdString();
+ emit this->load_from_TU_Wien_finished(ubo);
+ reply->deleteLater();
+ return;
+ }
+
+ // Write regional report to struct
+ QJsonObject jsonObject_region_rating = jsonValue_region_rating.toObject();
+ if (jsonObject_region_rating.contains("regionCode"))
+ region_rating.region_id = jsonObject_region_rating["regionCode"].toString();
+ if (jsonObject_region_rating.contains("dangerBorder")) {
+ // dangerBorder = null is interpreted as dangerBorder = treeLine = 1600, see Joey's thesis p.44
+ QJsonValue val = jsonObject_region_rating["dangerBorder"];
+ region_rating.border = ((val.isNull() || val.isUndefined()) ? 1600 : val.toInt());
+ }
+ if (jsonObject_region_rating.contains("dangerRatingHi"))
+ region_rating.rating_hi = jsonObject_region_rating["dangerRatingHi"].toInt();
+ if (jsonObject_region_rating.contains("dangerRatingLo"))
+ region_rating.rating_lo = jsonObject_region_rating["dangerRatingLo"].toInt();
+ if (jsonObject_region_rating.contains("startTime"))
+ region_rating.start_time = jsonObject_region_rating["startTime"].toString();
+ if (jsonObject_region_rating.contains("endTime"))
+ region_rating.end_time = jsonObject_region_rating["endTime"].toString();
+ if (jsonObject_region_rating.contains("unfavorable"))
+ region_rating.unfavorable = jsonObject_region_rating["unfavorable"].toInt();
+
+ // Write struct to vector to be returned
+ region_ratings.push_back(region_rating);
+ }
+
+ // Convert reports to ubo
+ nucleus::avalanche::UboEawsReports ubo = convertReportsToUbo(ubo, region_ratings, m_uint_id_manager);
+
+ // Emit ratings
+ emit this->load_from_TU_Wien_finished(ubo);
+ reply->deleteLater();
+ });
+}
+
+} // namespace nucleus::avalanche
diff --git a/nucleus/avalanche/ReportLoadService.h b/nucleus/avalanche/ReportLoadService.h
new file mode 100644
index 00000000..2c9f926d
--- /dev/null
+++ b/nucleus/avalanche/ReportLoadService.h
@@ -0,0 +1,67 @@
+#pragma once
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+class QNetworkAccessManager;
+
+namespace nucleus::avalanche {
+class UIntIdManager;
+
+// Relevant data from a CAAML json provided by avalanche services
+struct DangerRatingCAAML {
+public:
+ QString main_value = "";
+ int lower_bound = INT_MAX;
+ int upper_bound = INT_MIN;
+ QString valid_time_period = "";
+ bool operator==(const DangerRatingCAAML& rhs) const = default;
+};
+
+// Contains a list of regions and the altitude dependent ratings valid for these regions
+struct BulletinItemCAAML {
+public:
+ std::unordered_set regions_ids;
+ std::vector danger_ratings;
+ bool operator==(const BulletinItemCAAML& rhs) const = default;
+};
+
+// Contains rating for one region as obtained from TU Wien server
+struct ReportTUWien {
+ QString region_id = "";
+ QString start_time = "";
+ QString end_time = "";
+ int border = INT_MAX;
+ int rating_hi = -1;
+ int rating_lo = -1;
+ int unfavorable = -1;
+ bool operator==(const ReportTUWien& rhs) const = default;
+};
+
+// Loads a Bulletinn from the server and converts it to custom struct
+class ReportLoadService : public QObject {
+ Q_OBJECT
+private:
+ std::shared_ptr m_network_manager;
+ std::shared_ptr m_uint_id_manager;
+
+public:
+ ReportLoadService(std::shared_ptr m_uint_id_manager); // Constructor creates a new NetworkManager with id manager it obtains from context
+public slots:
+ void load_from_tu_wien(const QDate& date) const;
+
+signals:
+ void load_from_TU_Wien_finished(const nucleus::avalanche::UboEawsReports& ubo) const;
+
+public:
+ // QNetworkAccessManager m_network_manager;
+ const QString m_url_latest_report = "https://static.avalanche.report/bulletins/latest/EUREGIO_de_CAAMLv6.json";
+ const QString m_url_custom_report = "https://static.avalanche.report/eaws_bulletins/${date}/${date}-${region}.json";
+
+ bool operator==(const ReportLoadService& rhs) { return this->m_url_latest_report == rhs.m_url_latest_report; }
+};
+} // namespace nucleus::avalanche
diff --git a/nucleus/avalanche/Scheduler.cpp b/nucleus/avalanche/Scheduler.cpp
new file mode 100644
index 00000000..957a0edc
--- /dev/null
+++ b/nucleus/avalanche/Scheduler.cpp
@@ -0,0 +1,106 @@
+/*****************************************************************************
+ * AlpineMaps.org
+ * Copyright (C) 2024 Adam Celarek
+ * Copyright (C) 2025 Joerg-Christian Reiher
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *****************************************************************************/
+
+#include "Scheduler.h"
+#include "eaws.h"
+#include
+#include
+
+namespace nucleus::avalanche {
+
+Scheduler::Scheduler(const Settings& settings)
+ : nucleus::tile::Scheduler(settings)
+ , m_default_raster(glm::uvec2(settings.tile_resolution), 0)
+{
+ m_uint_id_manager = std::make_shared(QDate(2025, 7, 1));
+}
+
+Scheduler::~Scheduler() = default;
+
+void Scheduler::transform_and_emit(const std::vector& new_quads, const std::vector& deleted_quads)
+{
+ std::vector new_gpu_tiles;
+ new_gpu_tiles.reserve(new_quads.size());
+
+ Q_UNUSED(deleted_quads)
+ for (const auto& quad : new_quads) {
+ nucleus::tile::GpuEawsTile gpu_tile_from_quad;
+ gpu_tile_from_quad.id = quad.id;
+ nucleus::Raster quad_as_raster = to_raster(quad, m_default_raster, m_uint_id_manager);
+ gpu_tile_from_quad.texture = std::make_shared>(quad_as_raster);
+ new_gpu_tiles.push_back(gpu_tile_from_quad);
+ }
+
+ emit gpu_tiles_updated(deleted_quads, new_gpu_tiles);
+}
+
+nucleus::Raster Scheduler::to_raster(
+ const nucleus::tile::DataQuad& quad, const nucleus::Raster& default_raster, std::shared_ptr uint_id_manager)
+{
+ std::array