From ad324b1292c4ce1910227f612d8386f881fdf832 Mon Sep 17 00:00:00 2001 From: Emma Broman Date: Tue, 22 Apr 2025 15:02:13 +0200 Subject: [PATCH 01/10] Remove "Up" keybind and retire `property_helper` and `renderable_helper` (#3604) * Remove keybindings to the UP key Removed the actions as well as they are probably not needed. We have the "now" button and it is rare that one want to interpolate to now * Replace property_helper usage with function from core_scripts (only `invert` was used) And retire the property_helper and renderable_helper assets. They are no longer needed! --- data/assets/default_keybindings.asset | 46 +++-------------- .../bastille_day/density_volume.asset | 1 - .../heliosphere/bastille_day/fieldlines.asset | 1 - .../heliosphere/bastille_day/fluxnodes.asset | 1 - .../bastille_day/fluxnodescutplane.asset | 1 - .../scene/solarsystem/sun/EUV_layer.asset | 1 - data/assets/util/joysticks/any-joystick.asset | 1 - .../joysticks/microsoft-xbox-360-pad.asset | 7 ++- data/assets/util/joysticks/ps4.asset | 7 ++- data/assets/util/joysticks/ps5.asset | 7 ++- .../space-mouse-compact-wireless.asset | 1 - .../util/joysticks/space-mouse-compact.asset | 1 - .../space-mouse-enterprise-wireless.asset | 1 - .../joysticks/space-mouse-enterprise.asset | 1 - .../assets/util/joysticks/xbox-wireless.asset | 7 ++- data/assets/util/joysticks/xbox.asset | 7 ++- data/assets/util/property_helper.asset | 50 ------------------- data/assets/util/renderable_helper.asset | 16 ------ 18 files changed, 21 insertions(+), 136 deletions(-) delete mode 100644 data/assets/util/property_helper.asset delete mode 100644 data/assets/util/renderable_helper.asset diff --git a/data/assets/default_keybindings.asset b/data/assets/default_keybindings.asset index 7137dfe9ff..8981cdfef5 100644 --- a/data/assets/default_keybindings.asset +++ b/data/assets/default_keybindings.asset @@ -1,11 +1,7 @@ -local propertyHelper = asset.require("util/property_helper") - - - local ToggleNativeUi = { Identifier = "os.ToggleNativeUi", Name = "Show native GUI", - Command = propertyHelper.invert("Modules.ImGUI.Enabled"), + Command = [[openspace.invertBooleanProperty("Modules.ImGUI.Enabled")]], Documentation = "Shows or hides the native UI", GuiPath = "/System/GUI", IsLocal = true @@ -53,7 +49,7 @@ local TogglePauseImmediate = { local ToggleRotationFriction = { Identifier = "os.ToggleRotationFriction", Name = "Toggle rotation friction", - Command = propertyHelper.invert("NavigationHandler.OrbitalNavigator.Friction.RotationalFriction"), + Command = [[openspace.invertBooleanProperty("NavigationHandler.OrbitalNavigator.Friction.RotationalFriction")]], Documentation = [[Toggles the rotational friction of the camera. If it is disabled, the camera rotates around the focus object indefinitely]], GuiPath = "/Navigation", @@ -63,7 +59,7 @@ local ToggleRotationFriction = { local ToggleZoomFriction = { Identifier = "os.ToggleZoomFriction", Name = "Toggle zoom friction", - Command = propertyHelper.invert("NavigationHandler.OrbitalNavigator.Friction.ZoomFriction"), + Command = [[openspace.invertBooleanProperty("NavigationHandler.OrbitalNavigator.Friction.ZoomFriction")]], Documentation = [[Toggles the zoom friction of the camera. If it is disabled, the camera rises up from or closes in towards the focus object indefinitely]], GuiPath = "/Navigation", @@ -73,7 +69,7 @@ local ToggleZoomFriction = { local ToggleRollFriction = { Identifier = "os.ToggleRollFriction", Name = "Toggle roll friction", - Command = propertyHelper.invert("NavigationHandler.OrbitalNavigator.Friction.RollFriction"), + Command = [[openspace.invertBooleanProperty("NavigationHandler.OrbitalNavigator.Friction.RollFriction")]], Documentation = [[Toggles the roll friction of the camera. If it is disabled, the camera rolls around its own axis indefinitely]], GuiPath = "/Navigation", @@ -99,7 +95,7 @@ local FadeToBlack = { local ToggleMainGui = { Identifier = "os.ToggleMainGui", Name = "Toggle main GUI", - Command = propertyHelper.invert("Modules.CefWebGui.Visible"), + Command = [[openspace.invertBooleanProperty("Modules.CefWebGui.Visible")]], Documentation = "Toggles the main GUI", GuiPath = "/System/GUI", IsLocal = true @@ -123,7 +119,7 @@ local ToggleOverlays = { local ToggleMasterRendering = { Identifier = "os.ToggleMasterRendering", Name = "Toggle rendering on master", - Command = propertyHelper.invert("RenderEngine.DisableMasterRendering"), + Command = [[openspace.invertBooleanProperty("RenderEngine.DisableMasterRendering")]], Documentation = "Toggles the rendering on master", GuiPath = "/System/Rendering", IsLocal = true @@ -187,24 +183,6 @@ local RealTimeDeltaStepImmediate = { IsLocal = true } -local DateToNowInterpolate = { - Identifier = "os.DateToNowInterpolate", - Name = "Set the in-game time to now (interpolate)", - Command = "openspace.time.interpolateTime(openspace.time.currentWallTime())", - Documentation = "Immediately set the current in-game time to the 'now' time", - GuiPath = "/Time/Simulation Speed", - IsLocal = true -} - -local DateToNowImmediate = { - Identifier = "os.DateToNowImmediate", - Name = "Set the in-game time to now (immediate)", - Command = "openspace.time.setTime(openspace.time.currentWallTime())", - Documentation = "Smoothly interpolate the current in-game time to the 'now' time", - GuiPath = "/Time/Simulation Speed", - IsLocal = true -} - local ReloadGui = { Identifier = "os.ReloadGui", Name = "Reload GUI", @@ -271,12 +249,6 @@ asset.onInitialize(function() openspace.action.registerAction(RealTimeDeltaStepImmediate) openspace.bindKey("Shift+Down", RealTimeDeltaStepImmediate.Identifier) - openspace.action.registerAction(DateToNowInterpolate) - openspace.bindKey("Up", DateToNowInterpolate.Identifier) - - openspace.action.registerAction(DateToNowImmediate) - openspace.bindKey("Shift+Up", DateToNowImmediate.Identifier) - openspace.action.registerAction(ReloadGui) openspace.bindKey("F5", ReloadGui.Identifier) end) @@ -285,12 +257,6 @@ asset.onDeinitialize(function() openspace.clearKey("F5") openspace.action.removeAction(ReloadGui) - openspace.clearKey("Shift+Up") - openspace.action.removeAction(DateToNowImmediate) - - openspace.clearKey("Up") - openspace.action.removeAction(DateToNowInterpolate) - openspace.clearKey("Shift+Down") openspace.action.removeAction(RealTimeDeltaStepImmediate) diff --git a/data/assets/scene/solarsystem/heliosphere/bastille_day/density_volume.asset b/data/assets/scene/solarsystem/heliosphere/bastille_day/density_volume.asset index 1f7dbef0e1..59a3de2b7d 100644 --- a/data/assets/scene/solarsystem/heliosphere/bastille_day/density_volume.asset +++ b/data/assets/scene/solarsystem/heliosphere/bastille_day/density_volume.asset @@ -1,4 +1,3 @@ -local propertyHelper = asset.require("util/property_helper") local sunTransforms = asset.require("scene/solarsystem/sun/transforms") local sunAsset = asset.require("scene/solarsystem/sun/sun") diff --git a/data/assets/scene/solarsystem/heliosphere/bastille_day/fieldlines.asset b/data/assets/scene/solarsystem/heliosphere/bastille_day/fieldlines.asset index 9b3d361ce5..402f770e1b 100644 --- a/data/assets/scene/solarsystem/heliosphere/bastille_day/fieldlines.asset +++ b/data/assets/scene/solarsystem/heliosphere/bastille_day/fieldlines.asset @@ -1,5 +1,4 @@ local heliosphereTransforms = asset.require("scene/solarsystem/sun/transforms_heliosphere") -local propertyHelper = asset.require("util/property_helper") local rot = asset.require("./carrington_to_heeq_rotation") diff --git a/data/assets/scene/solarsystem/heliosphere/bastille_day/fluxnodes.asset b/data/assets/scene/solarsystem/heliosphere/bastille_day/fluxnodes.asset index cd87a530a6..bfa8853ccd 100644 --- a/data/assets/scene/solarsystem/heliosphere/bastille_day/fluxnodes.asset +++ b/data/assets/scene/solarsystem/heliosphere/bastille_day/fluxnodes.asset @@ -1,5 +1,4 @@ local heliosphereTransforms = asset.require("scene/solarsystem/sun/transforms_heliosphere") -local propertyHelper = asset.require("util/property_helper") local rot = asset.require("./carrington_to_heeq_rotation") diff --git a/data/assets/scene/solarsystem/heliosphere/bastille_day/fluxnodescutplane.asset b/data/assets/scene/solarsystem/heliosphere/bastille_day/fluxnodescutplane.asset index b9724a9190..4d0bf11e2d 100644 --- a/data/assets/scene/solarsystem/heliosphere/bastille_day/fluxnodescutplane.asset +++ b/data/assets/scene/solarsystem/heliosphere/bastille_day/fluxnodescutplane.asset @@ -1,4 +1,3 @@ -local propertyHelper = asset.require("util/property_helper") local transforms = asset.require("scene/solarsystem/sun/transforms_heliosphere") local rot = asset.require("./carrington_to_heeq_rotation") diff --git a/data/assets/scene/solarsystem/sun/EUV_layer.asset b/data/assets/scene/solarsystem/sun/EUV_layer.asset index 6fd0757944..755a756111 100644 --- a/data/assets/scene/solarsystem/sun/EUV_layer.asset +++ b/data/assets/scene/solarsystem/sun/EUV_layer.asset @@ -1,4 +1,3 @@ -local propertyHelper = asset.require("util/property_helper") local transforms = asset.require("./transforms") diff --git a/data/assets/util/joysticks/any-joystick.asset b/data/assets/util/joysticks/any-joystick.asset index 9e9ef66a9e..94033b38e0 100644 --- a/data/assets/util/joysticks/any-joystick.asset +++ b/data/assets/util/joysticks/any-joystick.asset @@ -1,4 +1,3 @@ -local propertyHelper = asset.require("../property_helper") local joystickHelper = asset.require("./joystick_helper") diff --git a/data/assets/util/joysticks/microsoft-xbox-360-pad.asset b/data/assets/util/joysticks/microsoft-xbox-360-pad.asset index 528c6b18f9..443216b31e 100644 --- a/data/assets/util/joysticks/microsoft-xbox-360-pad.asset +++ b/data/assets/util/joysticks/microsoft-xbox-360-pad.asset @@ -1,4 +1,3 @@ -local propertyHelper = asset.require("../property_helper") local joystickHelper = asset.require("./joystick_helper") @@ -119,19 +118,19 @@ asset.onInitialize(function() openspace.navigation.bindJoystickButton( name, controller.A, - propertyHelper.invert("NavigationHandler.OrbitalNavigator.Friction.RotationalFriction"), + [[openspace.invertBooleanProperty("NavigationHandler.OrbitalNavigator.Friction.RotationalFriction")]], "Toggle rotational friction" ) openspace.navigation.bindJoystickButton( name, controller.B, - propertyHelper.invert("NavigationHandler.OrbitalNavigator.Friction.ZoomFriction"), + [[openspace.invertBooleanProperty("NavigationHandler.OrbitalNavigator.Friction.ZoomFriction")]], "Toggle zoom friction" ) openspace.navigation.bindJoystickButton( name, controller.Y, - propertyHelper.invert("NavigationHandler.OrbitalNavigator.Friction.RollFriction"), + [[openspace.invertBooleanProperty("NavigationHandler.OrbitalNavigator.Friction.RollFriction")]], "Toggle roll friction" ) diff --git a/data/assets/util/joysticks/ps4.asset b/data/assets/util/joysticks/ps4.asset index f8696079e8..e59f492657 100644 --- a/data/assets/util/joysticks/ps4.asset +++ b/data/assets/util/joysticks/ps4.asset @@ -1,4 +1,3 @@ -local propertyHelper = asset.require("../property_helper") local joystickHelper = asset.require("./joystick_helper") @@ -121,19 +120,19 @@ asset.onInitialize(function() openspace.navigation.bindJoystickButton( name, controller.Cross, - propertyHelper.invert("NavigationHandler.OrbitalNavigator.Friction.RotationalFriction"), + [[openspace.invertBooleanProperty("NavigationHandler.OrbitalNavigator.Friction.RotationalFriction")]], "Toggle rotational friction" ) openspace.navigation.bindJoystickButton( name, controller.Circle, - propertyHelper.invert("NavigationHandler.OrbitalNavigator.Friction.ZoomFriction"), + [[openspace.invertBooleanProperty("NavigationHandler.OrbitalNavigator.Friction.ZoomFriction")]], "Toggle zoom friction" ) openspace.navigation.bindJoystickButton( name, controller.Triangle, - propertyHelper.invert("NavigationHandler.OrbitalNavigator.Friction.RollFriction"), + [[openspace.invertBooleanProperty("NavigationHandler.OrbitalNavigator.Friction.RollFriction")]], "Toggle roll friction" ) diff --git a/data/assets/util/joysticks/ps5.asset b/data/assets/util/joysticks/ps5.asset index f2c55bc0e0..a61fa4f78c 100644 --- a/data/assets/util/joysticks/ps5.asset +++ b/data/assets/util/joysticks/ps5.asset @@ -1,4 +1,3 @@ -local propertyHelper = asset.require("../property_helper") local joystickHelper = asset.require("./joystick_helper") @@ -124,19 +123,19 @@ asset.onInitialize(function() openspace.navigation.bindJoystickButton( name, controller.Cross, - propertyHelper.invert("NavigationHandler.OrbitalNavigator.Friction.RotationalFriction"), + [[openspace.invertBooleanProperty("NavigationHandler.OrbitalNavigator.Friction.RotationalFriction")]], "Toggle rotational friction" ) openspace.navigation.bindJoystickButton( name, controller.Circle, - propertyHelper.invert("NavigationHandler.OrbitalNavigator.Friction.ZoomFriction"), + [[openspace.invertBooleanProperty("NavigationHandler.OrbitalNavigator.Friction.ZoomFriction")]], "Toggle zoom friction" ) openspace.navigation.bindJoystickButton( name, controller.Triangle, - propertyHelper.invert("NavigationHandler.OrbitalNavigator.Friction.RollFriction"), + [[openspace.invertBooleanProperty("NavigationHandler.OrbitalNavigator.Friction.RollFriction")]], "Toggle roll friction" ) diff --git a/data/assets/util/joysticks/space-mouse-compact-wireless.asset b/data/assets/util/joysticks/space-mouse-compact-wireless.asset index 3ef46d306c..46d4b6578a 100644 --- a/data/assets/util/joysticks/space-mouse-compact-wireless.asset +++ b/data/assets/util/joysticks/space-mouse-compact-wireless.asset @@ -1,4 +1,3 @@ -local propertyHelper = asset.require("../property_helper") local joystickHelper = asset.require("./joystick_helper") diff --git a/data/assets/util/joysticks/space-mouse-compact.asset b/data/assets/util/joysticks/space-mouse-compact.asset index 77b911effe..d63d3e41c2 100644 --- a/data/assets/util/joysticks/space-mouse-compact.asset +++ b/data/assets/util/joysticks/space-mouse-compact.asset @@ -1,4 +1,3 @@ -local propertyHelper = asset.require("../property_helper") local joystickHelper = asset.require("./joystick_helper") diff --git a/data/assets/util/joysticks/space-mouse-enterprise-wireless.asset b/data/assets/util/joysticks/space-mouse-enterprise-wireless.asset index 279a8c3cd2..de7982cfe9 100644 --- a/data/assets/util/joysticks/space-mouse-enterprise-wireless.asset +++ b/data/assets/util/joysticks/space-mouse-enterprise-wireless.asset @@ -1,4 +1,3 @@ -local propertyHelper = asset.require("../property_helper") local joystickHelper = asset.require("./joystick_helper") diff --git a/data/assets/util/joysticks/space-mouse-enterprise.asset b/data/assets/util/joysticks/space-mouse-enterprise.asset index b3ee78bf1c..c9f5dbf2d1 100644 --- a/data/assets/util/joysticks/space-mouse-enterprise.asset +++ b/data/assets/util/joysticks/space-mouse-enterprise.asset @@ -1,4 +1,3 @@ -local propertyHelper = asset.require("../property_helper") local joystickHelper = asset.require("./joystick_helper") diff --git a/data/assets/util/joysticks/xbox-wireless.asset b/data/assets/util/joysticks/xbox-wireless.asset index 699dc1eb6a..0c7579515d 100644 --- a/data/assets/util/joysticks/xbox-wireless.asset +++ b/data/assets/util/joysticks/xbox-wireless.asset @@ -1,4 +1,3 @@ -local propertyHelper = asset.require("../property_helper") local joystickHelper = asset.require("./joystick_helper") @@ -119,19 +118,19 @@ asset.onInitialize(function() openspace.navigation.bindJoystickButton( name, controller.A, - propertyHelper.invert("NavigationHandler.OrbitalNavigator.Friction.RotationalFriction"), + [[openspace.invertBooleanProperty("NavigationHandler.OrbitalNavigator.Friction.RotationalFriction")]], "Toggle rotational friction" ) openspace.navigation.bindJoystickButton( name, controller.B, - propertyHelper.invert("NavigationHandler.OrbitalNavigator.Friction.ZoomFriction"), + [[openspace.invertBooleanProperty("NavigationHandler.OrbitalNavigator.Friction.ZoomFriction")]], "Toggle zoom friction" ) openspace.navigation.bindJoystickButton( name, controller.Y, - propertyHelper.invert("NavigationHandler.OrbitalNavigator.Friction.RollFriction"), + [[openspace.invertBooleanProperty("NavigationHandler.OrbitalNavigator.Friction.RollFriction")]], "Toggle roll friction" ) diff --git a/data/assets/util/joysticks/xbox.asset b/data/assets/util/joysticks/xbox.asset index a31b8cb5db..46a4bb6b16 100644 --- a/data/assets/util/joysticks/xbox.asset +++ b/data/assets/util/joysticks/xbox.asset @@ -1,4 +1,3 @@ -local propertyHelper = asset.require("../property_helper") local joystickHelper = asset.require("./joystick_helper") @@ -119,19 +118,19 @@ asset.onInitialize(function() openspace.navigation.bindJoystickButton( name, controller.A, - propertyHelper.invert("NavigationHandler.OrbitalNavigator.Friction.RotationalFriction"), + [[openspace.invertBooleanProperty("NavigationHandler.OrbitalNavigator.Friction.RotationalFriction")]], "Toggle rotational friction" ) openspace.navigation.bindJoystickButton( name, controller.B, - propertyHelper.invert("NavigationHandler.OrbitalNavigator.Friction.ZoomFriction"), + [[openspace.invertBooleanProperty("NavigationHandler.OrbitalNavigator.Friction.ZoomFriction")]], "Toggle zoom friction" ) openspace.navigation.bindJoystickButton( name, controller.Y, - propertyHelper.invert("NavigationHandler.OrbitalNavigator.Friction.RollFriction"), + [[openspace.invertBooleanProperty("NavigationHandler.OrbitalNavigator.Friction.RollFriction")]], "Toggle roll friction" ) diff --git a/data/assets/util/property_helper.asset b/data/assets/util/property_helper.asset deleted file mode 100644 index 99f059b6a6..0000000000 --- a/data/assets/util/property_helper.asset +++ /dev/null @@ -1,50 +0,0 @@ --- Function that returns the string that inverts the fully qualified boolean property 'property' -function invert(prop) - local escaped_property = [["]] .. prop .. [["]] - return "openspace.setPropertyValueSingle(" .. escaped_property .. ", not openspace.propertyValue(" .. escaped_property .. "))" -end - --- Function that returns the string that increments the 'property' by the 'value' -function increment(prop, value) - local v = value or 1 - local escaped_property = [["]] .. prop .. [["]] - return "openspace.setPropertyValueSingle(" .. escaped_property .. ", openspace.propertyValue(" .. escaped_property .. ") + " .. v .. ")" -end - --- Function that returns the string that decrements the 'property' by the 'value' -function decrement(prop, value) - return increment(prop, -value) -end - -function fade(prop, value, duration) - assert(type(prop) == "string", "prop must be a number") - assert(type(duration) == "number", "duration must be a number") - - local escaped_property = [["]] .. prop .. [["]] - return "openspace.setPropertyValueSingle(" .. escaped_property ..", " .. tostring(value) .. ", " .. tostring(duration) .. ")" -end - -function fadeOut(prop, duration) - return fade(prop, 0.0, duration) -end - -function fadeIn(prop, duration) - return fade(prop, 1.0, duration) -end - -function fadeInOut(prop, duration) - assert(type(prop) == "string", "prop must be a number") - assert(type(duration) == "number", "duration must be a number") - - local escaped_property = [["]] .. prop .. [["]] - -- If the value is > 0.5 fade out, otherwise fade in - return "local v = openspace.propertyValue(" .. escaped_property .. ") if v <= 0.5 then " .. fadeIn(prop, duration) .. " else " .. fadeOut(prop, duration) .. " end" -end - -asset.export("invert", invert) -asset.export("increment", increment) -asset.export("decrement", decrement) -asset.export("fade", fade) -asset.export("fadeIn", fadeIn) -asset.export("fadeOut", fadeOut) -asset.export("fadeInOut", fadeInOut) diff --git a/data/assets/util/renderable_helper.asset b/data/assets/util/renderable_helper.asset deleted file mode 100644 index 72e32f1cf4..0000000000 --- a/data/assets/util/renderable_helper.asset +++ /dev/null @@ -1,16 +0,0 @@ -local propertyHelper = asset.require("./property_helper") - - - --- Function that returns the string that enables/disables the renderable 'renderable' -function toggle(renderable) - return propertyHelper.invert(renderable .. ".Renderable.Enabled") -end - --- Function that returns the string that sets the enabled property of to -function setEnabled(renderable, enabled) - return [[openspace.setPropertyValue("]] .. renderable .. [[.Renderable.Enabled", ]] .. (enabled and "true" or "false") .. ")" -end - -asset.export("toggle", toggle) -asset.export("setEnabled", setEnabled) From ad29099140c45d34d917092547436c30f5f54d40 Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Tue, 22 Apr 2025 20:38:55 +0200 Subject: [PATCH 02/10] Fix the tab order of the launcher widget (closes #3616) --- .../ext/launcher/src/launcherwindow.cpp | 98 +++++++++---------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp b/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp index a3d0930422..2cd8b53aac 100644 --- a/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp +++ b/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp @@ -189,30 +189,6 @@ LauncherWindow::LauncherWindow(bool profileEnabled, const Configuration& globalC labelChoose->setObjectName("label_choose"); } - _editProfileButton = new QPushButton("Edit", centralWidget); - _editProfileButton->setObjectName("small"); - _editProfileButton->setGeometry(geometry::EditProfileButton); - _editProfileButton->setCursor(Qt::PointingHandCursor); - _editProfileButton->setAutoDefault(true); - _editProfileButton->setAccessibleName("Edit profile"); - connect( - _editProfileButton, &QPushButton::released, - this, &LauncherWindow::editProfile - ); - - { - QPushButton* newProfileButton = new QPushButton("New", centralWidget); - newProfileButton->setObjectName("small"); - newProfileButton->setGeometry(geometry::NewProfileButton); - newProfileButton->setCursor(Qt::PointingHandCursor); - newProfileButton->setAutoDefault(true); - newProfileButton->setAccessibleName("New profile"); - connect( - newProfileButton, &QPushButton::released, - this, &LauncherWindow::newProfile - ); - } - // Creating the profile box _after_ the Edit and New buttons as the comboboxes // `selectionChanged` signal will trigger that will try to make changes to the edit // button @@ -249,12 +225,36 @@ LauncherWindow::LauncherWindow(bool profileEnabled, const Configuration& globalC this, &LauncherWindow::updateStartButton ); + + _editProfileButton = new QPushButton("Edit", centralWidget); + _editProfileButton->setObjectName("small"); + _editProfileButton->setGeometry(geometry::EditProfileButton); + _editProfileButton->setCursor(Qt::PointingHandCursor); + _editProfileButton->setAutoDefault(true); + _editProfileButton->setAccessibleName("Edit profile"); + connect( + _editProfileButton, &QPushButton::released, + this, &LauncherWindow::editProfile + ); { // Set up the default value for the edit button std::string selection = std::get<1>(_profileBox->currentSelection()); _editProfileButton->setEnabled(std::filesystem::exists(selection)); } + { + QPushButton* newProfileButton = new QPushButton("New", centralWidget); + newProfileButton->setObjectName("small"); + newProfileButton->setGeometry(geometry::NewProfileButton); + newProfileButton->setCursor(Qt::PointingHandCursor); + newProfileButton->setAutoDefault(true); + newProfileButton->setAccessibleName("New profile"); + connect( + newProfileButton, &QPushButton::released, + this, &LauncherWindow::newProfile + ); + } + // @@ -266,31 +266,6 @@ LauncherWindow::LauncherWindow(bool profileEnabled, const Configuration& globalC optionsLabel->setObjectName("label_options"); } - _editWindowButton = new QPushButton("Edit", centralWidget); - _editWindowButton->setVisible(true); - _editWindowButton->setObjectName("small"); - _editWindowButton->setGeometry(geometry::EditWindowButton); - _editWindowButton->setCursor(Qt::PointingHandCursor); - _editWindowButton->setAutoDefault(true); - _editWindowButton->setAccessibleName("Edit window configuration"); - connect( - _editWindowButton, &QPushButton::released, - this, &LauncherWindow::editConfiguration - ); - - { - QPushButton* newWindowButton = new QPushButton("New", centralWidget); - newWindowButton->setObjectName("small"); - newWindowButton->setGeometry(geometry::NewWindowButton); - newWindowButton->setCursor(Qt::PointingHandCursor); - newWindowButton->setAutoDefault(true); - newWindowButton->setAccessibleName("New window configuration"); - connect( - newWindowButton, &QPushButton::released, - this, &LauncherWindow::newConfiguration - ); - } - _windowConfigBox = new SplitComboBox( centralWidget, _userConfigPath, @@ -329,11 +304,36 @@ LauncherWindow::LauncherWindow(bool profileEnabled, const Configuration& globalC this, &LauncherWindow::updateStartButton ); + + + _editWindowButton = new QPushButton("Edit", centralWidget); + _editWindowButton->setVisible(true); + _editWindowButton->setObjectName("small"); + _editWindowButton->setGeometry(geometry::EditWindowButton); + _editWindowButton->setCursor(Qt::PointingHandCursor); + _editWindowButton->setAutoDefault(true); + _editWindowButton->setAccessibleName("Edit window configuration"); + connect( + _editWindowButton, &QPushButton::released, + this, &LauncherWindow::editConfiguration + ); { // Set up the default value for the edit button std::string selection = std::get<1>(_windowConfigBox->currentSelection()); _editWindowButton->setEnabled(std::filesystem::exists(selection)); } + { + QPushButton* newWindowButton = new QPushButton("New", centralWidget); + newWindowButton->setObjectName("small"); + newWindowButton->setGeometry(geometry::NewWindowButton); + newWindowButton->setCursor(Qt::PointingHandCursor); + newWindowButton->setAutoDefault(true); + newWindowButton->setAccessibleName("New window configuration"); + connect( + newWindowButton, &QPushButton::released, + this, &LauncherWindow::newConfiguration + ); + } // From b02680a36014caea27a74b398264ca837b78e483 Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Tue, 22 Apr 2025 20:47:17 +0200 Subject: [PATCH 03/10] Add new TimeFrame that parses Spice kernels (#3600) --- .../{ => timeframeinterval}/interval.asset | 0 .../timeframekernel/kernel_ck-multiple.asset | 44 ++ .../timeframe/timeframekernel/kernel_ck.asset | 42 ++ .../timeframekernel/kernel_spk-ck.asset | 48 ++ .../timeframekernel/kernel_spk-multiple.asset | 42 ++ .../timeframekernel/kernel_spk.asset | 40 ++ .../{ => timeframeunion}/union.asset | 0 include/openspace/util/spicemanager.h | 7 + modules/base/timeframe/timeframeunion.cpp | 4 +- modules/space/CMakeLists.txt | 2 + modules/space/spacemodule.cpp | 9 + modules/space/timeframe/timeframekernel.cpp | 485 ++++++++++++++++++ modules/space/timeframe/timeframekernel.h | 52 ++ src/util/spicemanager.cpp | 9 +- support/coding/codegen | 2 +- 15 files changed, 781 insertions(+), 5 deletions(-) rename data/assets/examples/timeframe/{ => timeframeinterval}/interval.asset (100%) create mode 100644 data/assets/examples/timeframe/timeframekernel/kernel_ck-multiple.asset create mode 100644 data/assets/examples/timeframe/timeframekernel/kernel_ck.asset create mode 100644 data/assets/examples/timeframe/timeframekernel/kernel_spk-ck.asset create mode 100644 data/assets/examples/timeframe/timeframekernel/kernel_spk-multiple.asset create mode 100644 data/assets/examples/timeframe/timeframekernel/kernel_spk.asset rename data/assets/examples/timeframe/{ => timeframeunion}/union.asset (100%) create mode 100644 modules/space/timeframe/timeframekernel.cpp create mode 100644 modules/space/timeframe/timeframekernel.h diff --git a/data/assets/examples/timeframe/interval.asset b/data/assets/examples/timeframe/timeframeinterval/interval.asset similarity index 100% rename from data/assets/examples/timeframe/interval.asset rename to data/assets/examples/timeframe/timeframeinterval/interval.asset diff --git a/data/assets/examples/timeframe/timeframekernel/kernel_ck-multiple.asset b/data/assets/examples/timeframe/timeframekernel/kernel_ck-multiple.asset new file mode 100644 index 0000000000..c8e27a403b --- /dev/null +++ b/data/assets/examples/timeframe/timeframekernel/kernel_ck-multiple.asset @@ -0,0 +1,44 @@ +-- CK Multiple +-- This example creates a time frame based on the information provided by multiple SPICE +-- kernel files that contain orientation information about the same object. The created +-- scene graph node will only be valid whenever any window in any of the provided kernels +-- contains information about refernence frame "-98000", which is the intertial +-- orientation frame for the New Horizons spacecraft. + +-- We need a SPICE kernel to work with in this example +local data = asset.resource({ + Name = "New Horizons Kernels", + Type = "HttpSynchronization", + Identifier = "newhorizons_kernels", + Version = 1 +}) + +local Node = { + Identifier = "TimeFrameKernel_Example_CK_Multiple", + TimeFrame = { + Type = "TimeFrameKernel", + CK = { + Kernels = { + data .. "nh_apf_20150404_20150420_001.bc", + data .. "nh_apf_20150420_20150504_001.bc", + data .. "new-horizons_1121.tsc" + }, + Reference = "-98000" + } + }, + Renderable = { + Type = "RenderableCartesianAxes" + }, + GUI = { + Name = "TimeFrameKernel - Basic (CK, Multiple)", + Path = "/Examples" + } +} + +asset.onInitialize(function() + openspace.addSceneGraphNode(Node) +end) + +asset.onDeinitialize(function() + openspace.removeSceneGraphNode(Node) +end) diff --git a/data/assets/examples/timeframe/timeframekernel/kernel_ck.asset b/data/assets/examples/timeframe/timeframekernel/kernel_ck.asset new file mode 100644 index 0000000000..c9354830e9 --- /dev/null +++ b/data/assets/examples/timeframe/timeframekernel/kernel_ck.asset @@ -0,0 +1,42 @@ +-- CK Basic +-- This example creates a time frame based on the information provided by a single SPICE +-- kernel file. The created scene graph node will only be valid whenever the provided +-- kernel contains information about about the reference frame "-98000", which is the +-- interial orientation frame for the New Horizons spacecraft. + +-- We need a SPICE kernel to work with in this example +local data = asset.resource({ + Name = "New Horizons Kernels", + Type = "HttpSynchronization", + Identifier = "newhorizons_kernels", + Version = 1 +}) + +local Node = { + Identifier = "TimeFrameKernel_Example_CK", + TimeFrame = { + Type = "TimeFrameKernel", + CK = { + Kernels = { + data .. "nh_apf_20150404_20150420_001.bc", + data .. "new-horizons_1121.tsc", + }, + Reference = "-98000" + } + }, + Renderable = { + Type = "RenderableCartesianAxes" + }, + GUI = { + Name = "TimeFrameKernel - Basic (CK)", + Path = "/Examples" + } +} + +asset.onInitialize(function() + openspace.addSceneGraphNode(Node) +end) + +asset.onDeinitialize(function() + openspace.removeSceneGraphNode(Node) +end) diff --git a/data/assets/examples/timeframe/timeframekernel/kernel_spk-ck.asset b/data/assets/examples/timeframe/timeframekernel/kernel_spk-ck.asset new file mode 100644 index 0000000000..87547dc032 --- /dev/null +++ b/data/assets/examples/timeframe/timeframekernel/kernel_spk-ck.asset @@ -0,0 +1,48 @@ +-- Combined example +-- This example creates a time frame based on the information provided by multiple SPICE +-- kernel files. The created scene graph node will only be valid whenever the provided +-- kernels contain information positional information about the object "JUICE" as well as +-- orientation information for the reference frame "-28002" which is the measured attitude +-- for the JUICE spacecraft. The time frame will only be valid if both pieces of data are +-- available. + +-- We need a SPICE kernel to work with in this example +local data = asset.resource({ + Name = "JUICE Kernels", + Type = "HttpSynchronization", + Identifier = "juice_kernels", + Version = 2 +}) + +local Node = { + Identifier = "TimeFrameKernel_Example_Combined_SPK-CK", + TimeFrame = { + Type = "TimeFrameKernel", + SPK = { + Kernels = data .. "juice_orbc_000031_230414_310721_v03.bsp", + Object = "JUICE" + }, + CK = { + Kernels = { + data .. "juice_sc_meas_230413_230415_s230414_v01.bc", + data .. "juice_step_230414_v01.tsc" + }, + Reference = "-28002" + } + }, + Renderable = { + Type = "RenderableCartesianAxes" + }, + GUI = { + Name = "TimeFrameKernel - Combined (SPK+CK)", + Path = "/Examples" + } +} + +asset.onInitialize(function() + openspace.addSceneGraphNode(Node) +end) + +asset.onDeinitialize(function() + openspace.removeSceneGraphNode(Node) +end) diff --git a/data/assets/examples/timeframe/timeframekernel/kernel_spk-multiple.asset b/data/assets/examples/timeframe/timeframekernel/kernel_spk-multiple.asset new file mode 100644 index 0000000000..9425d231ad --- /dev/null +++ b/data/assets/examples/timeframe/timeframekernel/kernel_spk-multiple.asset @@ -0,0 +1,42 @@ +-- SPK Multiple +-- This example creates a time frame based on the information provided by multiple SPICE +-- kernel files that contain position information about the same object. The created scene +-- graph node will only be valid whenever any window in any of the provided kernels +-- contains information about object "VOYAGER 1". + +-- We need a SPICE kernel to work with in this example +local data = asset.resource({ + Name = "Voyager 1 Kernels", + Type = "HttpSynchronization", + Identifier = "voyager1_spice", + Version = 2 +}) + +local Node = { + Identifier = "TimeFrameKernel_Example_SPK_Multiple", + TimeFrame = { + Type = "TimeFrameKernel", + SPK = { + Kernels = { + data .. "vgr1_jup230.bsp", + data .. "vgr1_sat337.bsp" + }, + Object = "VOYAGER 1" + } + }, + Renderable = { + Type = "RenderableCartesianAxes" + }, + GUI = { + Name = "TimeFrameKernel - Basic (SPK, Multiple)", + Path = "/Examples" + } +} + +asset.onInitialize(function() + openspace.addSceneGraphNode(Node) +end) + +asset.onDeinitialize(function() + openspace.removeSceneGraphNode(Node) +end) diff --git a/data/assets/examples/timeframe/timeframekernel/kernel_spk.asset b/data/assets/examples/timeframe/timeframekernel/kernel_spk.asset new file mode 100644 index 0000000000..f77fccf4e7 --- /dev/null +++ b/data/assets/examples/timeframe/timeframekernel/kernel_spk.asset @@ -0,0 +1,40 @@ +-- SPK Basic +-- This example creates a time frame based on the information provided by a single SPICE +-- kernel file. The created scene graph node will only be valid whenever the provided +-- kernel contains information about object "-915", which is Apollo 15. In this specific +-- case, the Apollo15 kernel contains two windows of valid data, both of which are used by +-- this time frame. + +-- We need a SPICE kernel to work with in this example +local data = asset.resource({ + Name = "Apollo Kernels", + Type = "HttpSynchronization", + Identifier = "apollo_spice", + Version = 1 +}) + +local Node = { + Identifier = "TimeFrameKernel_Example_SPK", + TimeFrame = { + Type = "TimeFrameKernel", + SPK = { + Kernels = data .. "apollo15-1.bsp", + Object = "-915" + } + }, + Renderable = { + Type = "RenderableCartesianAxes" + }, + GUI = { + Name = "TimeFrameKernel - Basic (SPK)", + Path = "/Examples" + } +} + +asset.onInitialize(function() + openspace.addSceneGraphNode(Node) +end) + +asset.onDeinitialize(function() + openspace.removeSceneGraphNode(Node) +end) diff --git a/data/assets/examples/timeframe/union.asset b/data/assets/examples/timeframe/timeframeunion/union.asset similarity index 100% rename from data/assets/examples/timeframe/union.asset rename to data/assets/examples/timeframe/timeframeunion/union.asset diff --git a/include/openspace/util/spicemanager.h b/include/openspace/util/spicemanager.h index 20e55d0189..69c1197fbc 100644 --- a/include/openspace/util/spicemanager.h +++ b/include/openspace/util/spicemanager.h @@ -1029,6 +1029,13 @@ public: */ UseException exceptionHandling() const; + /** + * Returns the path to the most current leap second kernel. + * + * \return The path to the most current leap second kernel. + */ + static std::filesystem::path leapSecondKernel(); + static scripting::LuaLibrary luaLibrary(); private: diff --git a/modules/base/timeframe/timeframeunion.cpp b/modules/base/timeframe/timeframeunion.cpp index 285096d850..5be70a6bae 100644 --- a/modules/base/timeframe/timeframeunion.cpp +++ b/modules/base/timeframe/timeframeunion.cpp @@ -40,8 +40,8 @@ namespace { openspace::properties::Property::Visibility::AdvancedUser }; - // This TimeFrame class will accept the union of all passed-in TimeFrames. This means - // that this TimeFrame will be active if at least one of the child TimeFrames is + // This `TimeFrame` class will accept the union of all passed-in TimeFrames. This + // means that this TimeFrame will be active if at least one of the child TimeFrames is // active and it will be inactive if none of the child TimeFrames are active. // // This can be used to create more complex TimeFrames that are made up of several, diff --git a/modules/space/CMakeLists.txt b/modules/space/CMakeLists.txt index 15cedfd993..45528b42b3 100644 --- a/modules/space/CMakeLists.txt +++ b/modules/space/CMakeLists.txt @@ -37,6 +37,7 @@ set(HEADER_FILES rendering/renderableorbitalkepler.h rendering/renderablestars.h rendering/renderabletravelspeed.h + timeframe/timeframekernel.h translation/gptranslation.h translation/keplertranslation.h translation/spicetranslation.h @@ -59,6 +60,7 @@ set(SOURCE_FILES rendering/renderableorbitalkepler.cpp rendering/renderablestars.cpp rendering/renderabletravelspeed.cpp + timeframe/timeframekernel.cpp translation/gptranslation.cpp translation/keplertranslation.cpp translation/spicetranslation.cpp diff --git a/modules/space/spacemodule.cpp b/modules/space/spacemodule.cpp index 8a6939d61f..fc08350ef9 100644 --- a/modules/space/spacemodule.cpp +++ b/modules/space/spacemodule.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -105,6 +106,7 @@ void SpaceModule::internalInitialize(const ghoul::Dictionary& dictionary) { fRenderable->registerClass("RenderableStars"); fRenderable->registerClass("RenderableTravelSpeed"); + ghoul::TemplateFactory* fTranslation = FactoryManager::ref().factory(); ghoul_assert(fTranslation, "Ephemeris factory was not created"); @@ -114,12 +116,19 @@ void SpaceModule::internalInitialize(const ghoul::Dictionary& dictionary) { fTranslation->registerClass("GPTranslation"); fTranslation->registerClass("HorizonsTranslation"); + ghoul::TemplateFactory* fRotation = FactoryManager::ref().factory(); ghoul_assert(fRotation, "Rotation factory was not created"); fRotation->registerClass("SpiceRotation"); + + ghoul::TemplateFactory* fTimeFrame = + FactoryManager::ref().factory(); + ghoul_assert(fTimeFrame, "Scale factory was not created"); + fTimeFrame->registerClass("TimeFrameKernel"); + const Parameters p = codegen::bake(dictionary); _showSpiceExceptions = p.showExceptions.value_or(_showSpiceExceptions); } diff --git a/modules/space/timeframe/timeframekernel.cpp b/modules/space/timeframe/timeframekernel.cpp new file mode 100644 index 0000000000..cf12dc09cf --- /dev/null +++ b/modules/space/timeframe/timeframekernel.cpp @@ -0,0 +1,485 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2025 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include +#include +#include "SpiceUsr.h" + +namespace { + constexpr std::string_view _loggerCat = "TimeFrameKernel"; + constexpr unsigned SpiceErrorBufferSize = 1841; + + std::vector extractTimeFramesSPK( + const std::vector& kernels, + const std::variant& object) + { + using namespace openspace; + std::vector res; + + // Load the kernel to be able to resolve the provided object name + const std::filesystem::path currentDirectory = std::filesystem::current_path(); + for (const std::filesystem::path& kernel : kernels) { + const std::filesystem::path p = kernel.parent_path(); + std::filesystem::current_path(p); + const std::string k = kernel.string(); + furnsh_c(k.c_str()); + } + + // Convert the provided object name into a SpiceInt + SpiceBoolean success = SPICEFALSE; + SpiceInt id = 0; + if (std::holds_alternative(object)) { + std::string s = std::get(object); + bods2c_c(s.c_str(), &id, &success); + if (!success) { + throw ghoul::RuntimeError(std::format("Error finding object '{}'", s)); + } + } + else { + ghoul_assert(std::holds_alternative(object), "Additional variant type"); + id = std::get(object); + } + + + // Set up variables + constexpr unsigned int MaxObj = 1024; + SPICEINT_CELL(ids, MaxObj); + constexpr unsigned int WinSiz = 16384; + SPICEDOUBLE_CELL(cover, WinSiz); + scard_c(0, &cover); + + // Get all objects in the provided kernels + for (const std::filesystem::path& kernel : kernels) { + const std::string k = kernel.string(); + + constexpr int ArchitectureSize = 128; + std::array architecture; + std::memset(architecture.data(), ArchitectureSize, 0); + + constexpr int TypeSize = 16; + std::array type; + std::memset(type.data(), TypeSize, 0); + + getfat_c( + k.c_str(), + ArchitectureSize, + TypeSize, + architecture.data(), + type.data() + ); + + if (std::string_view(type.data()) != "SPK") { + // Only SPK kernels are allowed, but we want the user to be able to pass + // the full list of kernels to this class, which includes other types + continue; + } + + spkobj_c(k.c_str(), &ids); + if (failed_c()) { + std::string buffer; + buffer.resize(SpiceErrorBufferSize); + getmsg_c("LONG", SpiceErrorBufferSize, buffer.data()); + reset_c(); + throw ghoul::RuntimeError(std::format( + "Error loading kernel {}. {}", kernel, buffer + )); + } + for (SpiceInt i = 0; i < card_c(&ids); i++) { + const SpiceInt obj = SPICE_CELL_ELEM_I(&ids, i); + + if (obj != id) { + // We only want to find the coverage for the specific identifier + continue; + } + + // Get coverage for the object + spkcov_c(k.c_str(), obj, &cover); + if (failed_c()) { + std::string buffer; + buffer.resize(SpiceErrorBufferSize); + getmsg_c("LONG", SpiceErrorBufferSize, buffer.data()); + reset_c(); + throw ghoul::RuntimeError(std::format( + "Error finding SPK coverage '{}'. {}", kernel, buffer + )); + } + + // Access all of the windows + const SpiceInt numberOfIntervals = wncard_c(&cover); + for (SpiceInt j = 0; j < numberOfIntervals; j++) { + // Get the endpoints of the jth interval + SpiceDouble b = 0.0; + SpiceDouble e = 0.0; + wnfetd_c(&cover, j, &b, &e); + if (failed_c()) { + std::string buffer; + buffer.resize(SpiceErrorBufferSize); + getmsg_c("LONG", SpiceErrorBufferSize, buffer.data()); + reset_c(); + throw ghoul::RuntimeError(std::format( + "Error finding window {} in SPK '{}'. {}", j, kernel, buffer + )); + } + + res.emplace_back(b, e); + } + } + } + + + // We no longer need to have need for the kernel being loaded + for (const std::filesystem::path& kernel : kernels) { + const std::filesystem::path p = kernel.parent_path(); + std::filesystem::current_path(p); + const std::string k = kernel.string(); + unload_c(k.c_str()); + } + std::filesystem::current_path(currentDirectory); + + + return res; + } + + std::vector extractTimeFramesCK( + const std::vector& kernels, + const std::variant& object) + { + using namespace openspace; + std::vector res; + + std::filesystem::path lsk = SpiceManager::leapSecondKernel(); + const std::string l = lsk.string(); + furnsh_c(l.c_str()); + + // Load the kernel to be able to resolve the provided object name + const std::filesystem::path currentDirectory = std::filesystem::current_path(); + + for (const std::filesystem::path& kernel : kernels) { + const std::filesystem::path p = kernel.parent_path(); + std::filesystem::current_path(p); + const std::string k = kernel.string(); + furnsh_c(k.c_str()); + } + + // Convert the provided reference name into a SpiceInt + SpiceBoolean success = SPICEFALSE; + SpiceInt id = 0; + if (std::holds_alternative(object)) { + std::string s = std::get(object); + bods2c_c(s.c_str(), &id, &success); + if (!success) { + throw ghoul::RuntimeError(std::format("Error finding object '{}'", s)); + } + } + else { + ghoul_assert(std::holds_alternative(object), "Additional variant type"); + id = std::get(object); + } + + + // Set up variables + constexpr unsigned int MaxObj = 1024; + SPICEINT_CELL(ids, MaxObj); + constexpr unsigned int WinSiz = 16384; + SPICEDOUBLE_CELL(cover, WinSiz); + scard_c(0, &cover); + + // Get all objects in the provided kernel + for (const std::filesystem::path& kernel : kernels) { + const std::string k = kernel.string(); + + constexpr int ArchitectureSize = 128; + std::array architecture; + std::memset(architecture.data(), ArchitectureSize, 0); + + constexpr int TypeSize = 16; + std::array type; + std::memset(type.data(), TypeSize, 0); + + getfat_c( + k.c_str(), + ArchitectureSize, + TypeSize, + architecture.data(), + type.data() + ); + + if (std::string_view(type.data()) != "CK") { + // Since SCLK kernels are allowed as well we can't throw an exception + // here. We can't even warn about it since the tested spacecraft clock + // kernels report a type and architecture of '?' which is not helpful. + continue; + } + + ckobj_c(k.c_str(), &ids); + if (failed_c()) { + std::string buffer; + buffer.resize(SpiceErrorBufferSize); + getmsg_c("LONG", SpiceErrorBufferSize, buffer.data()); + reset_c(); + throw ghoul::RuntimeError(std::format( + "Error loading kernel {}. {}", kernel, buffer + )); + } + for (SpiceInt i = 0; i < card_c(&ids); i++) { + const SpiceInt frame = SPICE_CELL_ELEM_I(&ids, i); + + if (frame != id) { + // We only want to find the coverage for the specific identifier + continue; + } + + // Get coverage for the object + ckcov_c(k.c_str(), frame, SPICEFALSE, "SEGMENT", 0.0, "TDB", &cover); + if (failed_c()) { + std::string buffer; + buffer.resize(SpiceErrorBufferSize); + getmsg_c("LONG", SpiceErrorBufferSize, buffer.data()); + reset_c(); + throw ghoul::RuntimeError(std::format( + "Error finding CK coverage '{}'. {}", kernel, buffer + )); + } + + // Access all of the windows + const SpiceInt numberOfIntervals = wncard_c(&cover); + for (SpiceInt j = 0; j < numberOfIntervals; j++) { + // Get the endpoints of the jth interval + SpiceDouble b = 0.0; + SpiceDouble e = 0.0; + wnfetd_c(&cover, j, &b, &e); + if (failed_c()) { + std::string buffer; + buffer.resize(SpiceErrorBufferSize); + getmsg_c("LONG", SpiceErrorBufferSize, buffer.data()); + reset_c(); + throw ghoul::RuntimeError(std::format( + "Error finding window {} in SPK '{}'. {}", j, kernel, buffer + )); + } + + res.emplace_back(b, e); + } + } + } + + // We no longer need to have need for the kernel being loaded + for (const std::filesystem::path& kernel : kernels) { + const std::filesystem::path p = kernel.parent_path(); + std::filesystem::current_path(p); + const std::string k = kernel.string(); + unload_c(k.c_str()); + } + unload_c(l.c_str()); + std::filesystem::current_path(currentDirectory); + + + return res; + } + + void normalizeTimeRanges(std::vector& ranges) { + using namespace openspace; + + if (ranges.size() <= 1) { + // Nothing to do here if there is 0 or 1 elements in the vector + return; + } + + // 1. Sort time frames based on their beginning time. If the beginning times are + // the same, sort by the end date instead + std::sort( + ranges.begin(), + ranges.end(), + [](const TimeRange& lhs, const TimeRange& rhs) { + return lhs.start == rhs.start ? lhs.end < rhs.end : lhs.start < lhs.start; + } + ); + + // 2. If `i`'s end time is after `i+1`'s begin time, we can merge these two + ghoul_assert(ranges.size() > 1, "Too few items. Possible underflow"); + for (size_t i = 0; i < ranges.size() - 1; i++) { + TimeRange& curr = ranges[i]; + TimeRange& next = ranges[i + 1]; + + if (curr.end >= next.start) { + // Include the next with the current + curr.include(next); + + // Remove the next as we have subsumed it + ranges.erase(ranges.begin() + i + 1); + } + } + } + + // This `TimeFrame` class determines its time ranges based on the set of provided + // SPICE kernels. Any number of SPK (for position information) or CK (for orientation + // information) kernels can be specified together with a SPICE object name (for + // position information) or the name of a valid reference frame (for orientation + // information). For more information about Spice kernels, windows, or IDs, see the + // required reading documentation from NAIF: + // - https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/req/kernel.html + // - https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/req/spk.html + // - https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/req/ck.html + // - https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/req/time.html + // - https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/req/windows.html + // + // Note that for the CK kernels, a valid CK kernel as well as the corresponding SCLK + // kernel must be provided as the latter is required to be able to interpret the time + // codes of the former. For this reason it is not possible to provide only a single + // kernel to the CK struct in this class. + // + // The resulting validity of the time frame is based on the following conditions: + // + // 1. If either SPK or CK (but not both) are specified, the time frame depends on + // the union of all windows within all kernels that were provided. This means + // that if the simulation time is within any time where the kernel has data for + // the provided object, the TimeFrame will be valid. + // 2. If SPK and CK kernels are both specified, the time range validity for SPK and + // CK kernels are calculated separately, but both results must be valid to result + // in a valid time frame. This means that if only position data is available but + // not orientation data, the time frame is invalid. Only if positional and + // orientation data is available, then the TimeFrame will be valid. + // 3. If neither SPK nor CK kernels are specified, the creation of the `TimeFrame` + // will fail. + struct [[codegen::Dictionary(TimeFrameKernel)]] Parameters { + // Specifies information about the kernels and object name used to extract the + // times when positional information for the provided object is available. + struct SPK { + // The path to the kernel or list of kernels that should be loaded to extract + // the positional information. At least one kernel must be a SPK-type kernel. + // Any other kernel type is ignored. + std::variant< + std::filesystem::path, std::vector + > kernels; + + // The NAIF name of the object for which the positional information should be + // extracted + std::variant object; + }; + std::optional spk [[codegen::key("SPK")]]; + + // Specifies information about the kernels and refrence frame name used to extract + // the times when positional information for the provided object is available. + struct CK { + // The path to the list of kernels that should be loaded to extract + // orientation information. At least one kernel must be a CK-type kernel and + // if needed, a SCLK (spacecraft clock) kernel musat be provided. Any other + // kernel type is ignored. + std::vector kernels; + + // The NAIF name of the reference frame for which the times are extacted at + // which this reference frame has data in the provided kernels + std::variant reference; + }; + std::optional ck [[codegen::key("CK")]]; + }; +#include "timeframekernel_codegen.cpp" +} // namespace + +namespace openspace { + +documentation::Documentation TimeFrameKernel::Documentation() { + return codegen::doc("space_time_frame_kernel"); +} + +TimeFrameKernel::TimeFrameKernel(const ghoul::Dictionary& dictionary) + : _initialization(dictionary) +{ + // Baking the dictionary here to detect any error + codegen::bake(dictionary); +} + +bool TimeFrameKernel::initialize() { + const Parameters p = codegen::bake(_initialization); + + // Either the SPK or the CK variable must be specified + if (!p.spk.has_value() && !p.ck.has_value()) { + throw ghoul::RuntimeError( + "Either the SPK or the CK (or both) values must be specified for the " + "TimeFrameKernel. Neither was specified." + ); + } + + + // Extract the SPK file/files if they were specified + if (p.spk.has_value()) { + std::vector kernels; + if (std::holds_alternative(p.spk->kernels)) { + kernels = { std::get(p.spk->kernels) }; + } + else { + kernels = std::get>(p.spk->kernels); + } + + _timeRangesSPK = extractTimeFramesSPK(kernels, p.spk->object); + LDEBUG(std::format("Extracted {} SPK time ranges", _timeRangesSPK.size())); + } + + // Extract the CK file/files if they were specified + if (p.ck.has_value()) { + _timeRangesCK = extractTimeFramesCK(p.ck->kernels, p.ck->reference); + LDEBUG(std::format("Extracted {} CK time ranges", _timeRangesCK.size())); + } + + // + // Normalize the timeframes to simplify them as much as possible to reduce the length + // of the vector and improve performance in the `update` lookup + normalizeTimeRanges(_timeRangesSPK); + normalizeTimeRanges(_timeRangesCK); + + return true; +} + +void TimeFrameKernel::update(const Time& time) { + // We don't set _isInTimeFrame directly here as that would trigger an invalidation of + // the property and cause a data transmission every frame. This way, the data is only + // sent if the value actually changes, which should be rare + const double t = time.j2000Seconds(); + bool isInTimeFrameSPK = false; + if (_timeRangesSPK.empty()) { + isInTimeFrameSPK = true; + } + for (const TimeRange& range : _timeRangesSPK) { + if (range.includes(t)) { + isInTimeFrameSPK = true; + break; + } + } + bool isInTimeFrameCK = false; + if (_timeRangesCK.empty()) { + isInTimeFrameCK = true; + } + for (const TimeRange& range : _timeRangesCK) { + if (range.includes(t)) { + isInTimeFrameCK = true; + break; + } + } + _isInTimeFrame = isInTimeFrameSPK && isInTimeFrameCK; +} + +} // namespace diff --git a/modules/space/timeframe/timeframekernel.h b/modules/space/timeframe/timeframekernel.h new file mode 100644 index 0000000000..568325f338 --- /dev/null +++ b/modules/space/timeframe/timeframekernel.h @@ -0,0 +1,52 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2025 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_MODULE_BASE___TIMEFRAMEKERNEL___H__ +#define __OPENSPACE_MODULE_BASE___TIMEFRAMEKERNEL___H__ + +#include + +#include + +namespace openspace { + +class TimeFrameKernel : public TimeFrame { +public: + explicit TimeFrameKernel(const ghoul::Dictionary& dictionary); + + bool initialize() override; + void update(const Time& time) override; + + static documentation::Documentation Documentation(); + +private: + ghoul::Dictionary _initialization; + + std::vector _timeRangesSPK; + std::vector _timeRangesCK; +}; + +} // namespace openspace + +#endif // __OPENSPACE_MODULE_BASE___TIMEFRAMEKERNEL___H__ diff --git a/src/util/spicemanager.cpp b/src/util/spicemanager.cpp index 74652d9585..b53fa40b9c 100644 --- a/src/util/spicemanager.cpp +++ b/src/util/spicemanager.cpp @@ -1388,7 +1388,7 @@ glm::dmat3 SpiceManager::getEstimatedTransformMatrix(const std::string& fromFram return result; } -void SpiceManager::loadLeapSecondsSpiceKernel() { +std::filesystem::path SpiceManager::leapSecondKernel() { constexpr std::string_view Naif00012tlsSource = R"(KPL/LSK @@ -1542,12 +1542,17 @@ DELTET/DELTA_AT = ( 10, @1972-JAN-1 )"; -const std::filesystem::path path = std::filesystem::temp_directory_path(); + const std::filesystem::path path = std::filesystem::temp_directory_path(); const std::filesystem::path file = path / "naif0012.tls"; { std::ofstream f(file); f << Naif00012tlsSource; } + return file; +} + +void SpiceManager::loadLeapSecondsSpiceKernel() { + std::filesystem::path file = leapSecondKernel(); loadKernel(file); } diff --git a/support/coding/codegen b/support/coding/codegen index 3d3cb4e0c0..664bb14e12 160000 --- a/support/coding/codegen +++ b/support/coding/codegen @@ -1 +1 @@ -Subproject commit 3d3cb4e0c00d82f886531ffe9698961d0104a88d +Subproject commit 664bb14e12335b0d5ea166f633fd571859fdd5c2 From a4ddce76ef54f3abc7420176dc8f2fe79233a5ff Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Tue, 22 Apr 2025 21:12:04 +0200 Subject: [PATCH 04/10] Add submenu to the "New" buttons (#3601) - Add submenu to the "New" buttons - Don't prompt to save an empty profile - Set pointing hand cursor - Style the new menu bar - Reduce the number of different grayscales in the launcher QSS --- .../ext/launcher/resources/qss/launcher.qss | 29 ++++++--- .../ext/launcher/src/launcherwindow.cpp | 62 +++++++++++++++---- .../ext/launcher/src/splitcombobox.cpp | 2 + 3 files changed, 71 insertions(+), 22 deletions(-) diff --git a/apps/OpenSpace/ext/launcher/resources/qss/launcher.qss b/apps/OpenSpace/ext/launcher/resources/qss/launcher.qss index 96f0b0b004..0847382e6f 100644 --- a/apps/OpenSpace/ext/launcher/resources/qss/launcher.qss +++ b/apps/OpenSpace/ext/launcher/resources/qss/launcher.qss @@ -27,7 +27,7 @@ LauncherWindow QLabel { } LauncherWindow QLabel#label_choose, QLabel#label_options { - color: rgb(255, 255, 255); + color: white; font-size: 10pt; } @@ -37,11 +37,11 @@ LauncherWindow QLabel#clear { LauncherWindow QLabel#version-info { font-size: 10pt; - color: #dfdfdf; + color: rgb(225, 225, 225); } LauncherWindow QComboBox#config { - background: rgb(86, 86, 86); + background: rgb(96, 96, 96); border: 1px solid rgb(225, 225, 225); border-radius: 2px; padding: 1px 18px 1px 3px; @@ -49,7 +49,7 @@ LauncherWindow QComboBox#config { font-size: 10pt; font-family: Segoe UI; font-weight: bold; - color: rgb(255, 255, 255); + color: white; } LauncherWindow QComboBox#config:hover { @@ -61,6 +61,17 @@ LauncherWindow QComboBox#config:disabled { color: rgb(225, 225, 225); } +LauncherWindow QMenu#newprofile { + background: rgb(60, 60, 60); + min-width: 8em; + max-width: 8em; + color: white; +} + +LauncherWindow QMenu#newprofile::item:selected { + background: rgb(110, 110, 110); +} + LauncherWindow QPushButton#start { background: rgb(96, 96, 96); border: 2px solid rgb(225, 225, 225); @@ -69,10 +80,10 @@ LauncherWindow QPushButton#start { font-size: 16pt; font-weight: bold; letter-spacing: 1px; - color: rgb(255, 255, 255); + color: white; } LauncherWindow QPushButton#start:hover { - background: rgb(120, 120, 120); + background: rgb(110, 110, 110); } LauncherWindow QPushButton#start:disabled { background: rgb(60, 60, 60); @@ -81,14 +92,14 @@ LauncherWindow QPushButton#start:disabled { } LauncherWindow QPushButton#small { - background: rgb(86, 86, 86); + background: rgb(90, 90, 90); border: 1px solid rgb(225, 225, 225); border-radius: 2px; border-style: outset; min-height: 1em; font-size: 10pt; font-weight: bold; - color: rgb(255, 255, 255); + color: white; } LauncherWindow QPushButton#small:hover { background: rgb(110, 110, 110); @@ -221,7 +232,7 @@ HorizonsDialog QLabel#thin { } HorizonsDialog QLabel#normal { - color: rgb(0, 0, 0); + color: black; } /* diff --git a/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp b/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp index 2cd8b53aac..092ff5b379 100644 --- a/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp +++ b/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -214,8 +215,8 @@ LauncherWindow::LauncherWindow(bool profileEnabled, const Configuration& globalC _profileBox->setObjectName("config"); _profileBox->setGeometry(geometry::ProfileBox); _profileBox->setAccessibleName("Choose profile"); - _profileBox->populateList(globalConfig.profile); _profileBox->setEnabled(profileEnabled); + _profileBox->populateList(globalConfig.profile); connect( _profileBox, &SplitComboBox::selectionChanged, this, &LauncherWindow::selectProfile @@ -253,6 +254,28 @@ LauncherWindow::LauncherWindow(bool profileEnabled, const Configuration& globalC newProfileButton, &QPushButton::released, this, &LauncherWindow::newProfile ); + + QMenu* menu = new QMenu(this); + menu->setObjectName("newprofile"); + menu->setToolTipsVisible(true); + QAction* newEmpty = new QAction("Empty profile", this); + newEmpty->setToolTip("Creates a new empty profile without any existing content"); + connect( + newEmpty, &QAction::triggered, + this, &LauncherWindow::newProfile + ); + QAction* newFromCurrent = new QAction("Duplicate profile", this); + newFromCurrent->setToolTip( + "Creates a duplicate of the currently selected profile. This duplicate can " + "be edited and saved under a new name, or if it was a user profile be " + "overwritten" + ); + connect( + newFromCurrent, &QAction::triggered, + this, &LauncherWindow::editProfile + ); + menu->addActions({ newEmpty, newFromCurrent }); + newProfileButton->setMenu(menu); } @@ -407,7 +430,19 @@ void LauncherWindow::selectProfile(std::optional selection) { ghoul_assert(selection.has_value(), "No special item in the profiles"); if (selection.has_value()) { // Having the `if` statement here to satisfy the MSVC code analysis - _editProfileButton->setEnabled(std::filesystem::exists(*selection)); + + // Enable the Edit button only for the user profiles + const bool isUser = selection->starts_with(_userProfilePath.string()); + _editProfileButton->setEnabled(isUser); + + if (isUser) { + _editProfileButton->setToolTip(""); + } + else { + _editProfileButton->setToolTip( + "Cannot edit the selected profile as it is one of the built-in profiles" + ); + } } } @@ -423,8 +458,7 @@ void LauncherWindow::selectConfiguration(std::optional selection) { // If the configuration is a default configuration, we don't allow editing _editWindowButton->setEnabled(false); _editWindowButton->setToolTip( - "Cannot edit since the selected configuration is one of the files " - "provided by OpenSpace" + "Cannot edit the selected configuration as it is one of the built-in profiles" ); } else { @@ -614,19 +648,21 @@ void LauncherWindow::openProfileEditor(const std::string& profile, bool isUserPr &ProfileEdit::raiseExitWindow, [&editor, &savePath, &p, &profile]() { const std::string origPath = std::format("{}{}.profile", savePath, profile); - // If this is a new profile we want to prompt the user - if (!std::filesystem::exists(origPath)) { + // If this is a new profile we want to prompt the user, but only if the user + // actually changed something. If it is still an empty profile, there is no + // need to save it + if (!std::filesystem::exists(origPath) && *p != Profile()) { + editor.promptUserOfUnsavedChanges(); + return; + } + // Check if the profile is the same as current existing file + if (std::filesystem::exists(origPath) && *p != Profile(origPath)) { editor.promptUserOfUnsavedChanges(); return; } - // Check if the profile is the same as current existing file - if (*p != Profile(origPath)) { - editor.promptUserOfUnsavedChanges(); - } - else { - editor.closeWithoutSaving(); - } + // If we got this far, we can safely close the dialog without saving anything + editor.closeWithoutSaving(); } ); diff --git a/apps/OpenSpace/ext/launcher/src/splitcombobox.cpp b/apps/OpenSpace/ext/launcher/src/splitcombobox.cpp index ee8ffd0be7..c89e50aaa7 100644 --- a/apps/OpenSpace/ext/launcher/src/splitcombobox.cpp +++ b/apps/OpenSpace/ext/launcher/src/splitcombobox.cpp @@ -42,6 +42,8 @@ SplitComboBox::SplitComboBox(QWidget* parent, std::filesystem::path userPath, , _fileFilter(std::move(fileFilter)) , _createTooltip(std::move(createTooltip)) { + setCursor(Qt::PointingHandCursor); + connect( this, QOverload::of(&QComboBox::currentIndexChanged), From 6dc738e54ebcc895134fad5abbd38568681937c1 Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Wed, 23 Apr 2025 16:15:17 +0200 Subject: [PATCH 05/10] Move functions about globe locations from the GlobeBrowsingModule into the core (#3613) * Make GlobeRotation and GlobeTranslation work for non-globes and move them to the base module * Move ellipsoid and geodetic classes into the core * Allow more things to take scene graph nodes instead of RenderableGlobes * Move goToGeo and related Lua functions into the core * Move more functions from the pathnavigation namespace into the navigation namespace --------- Co-authored-by: Emma Broman --- data/assets/util/webgui.asset | 9 +- include/openspace/rendering/renderable.h | 3 + include/openspace/scene/scenegraphnode.h | 3 + .../openspace/util}/ellipsoid.h | 10 +- include/openspace/util/geodetic.h | 83 +++ modules/base/CMakeLists.txt | 4 + modules/base/basemodule.cpp | 6 + .../src => base/rotation}/globerotation.cpp | 91 ++- .../src => base/rotation}/globerotation.h | 24 +- .../translation}/globetranslation.cpp | 84 +-- .../translation}/globetranslation.h | 16 +- modules/globebrowsing/CMakeLists.txt | 6 - modules/globebrowsing/globebrowsingmodule.cpp | 188 +---- modules/globebrowsing/globebrowsingmodule.h | 30 +- .../globebrowsing/globebrowsingmodule_lua.inl | 301 +------- modules/globebrowsing/src/basictypes.h | 14 - .../src/dashboarditemglobelocation.cpp | 3 +- modules/globebrowsing/src/geodeticpatch.h | 1 + .../src/geojson/geojsoncomponent.cpp | 2 +- .../src/geojson/globegeometryhelper.cpp | 2 +- .../src/geojson/globegeometryhelper.h | 10 +- .../src/globelabelscomponent.cpp | 6 +- modules/globebrowsing/src/renderableglobe.cpp | 2 +- modules/globebrowsing/src/renderableglobe.h | 4 +- .../src/tileprovider/tileprovider.h | 2 +- modules/server/src/topics/cameratopic.cpp | 4 +- src/CMakeLists.txt | 4 + src/engine/openspaceengine.cpp | 4 +- src/navigation/navigationhandler.cpp | 17 +- src/navigation/navigationhandler_lua.inl | 653 ++++++++++++++++++ src/navigation/pathnavigator.cpp | 9 - src/navigation/pathnavigator_lua.inl | 362 ---------- src/rendering/renderable.cpp | 5 + src/scene/scenegraphnode.cpp | 9 + .../src => src/util}/ellipsoid.cpp | 8 +- src/util/geodetic.cpp | 130 ++++ tests/test_latlonpatch.cpp | 3 + tests/test_sessionrecording.cpp | 2 +- 38 files changed, 1089 insertions(+), 1025 deletions(-) rename {modules/globebrowsing/src => include/openspace/util}/ellipsoid.h (95%) create mode 100644 include/openspace/util/geodetic.h rename modules/{globebrowsing/src => base/rotation}/globerotation.cpp (80%) rename modules/{globebrowsing/src => base/rotation}/globerotation.h (83%) rename modules/{globebrowsing/src => base/translation}/globetranslation.cpp (80%) rename modules/{globebrowsing/src => base/translation}/globetranslation.h (88%) rename {modules/globebrowsing/src => src/util}/ellipsoid.cpp (97%) create mode 100644 src/util/geodetic.cpp diff --git a/data/assets/util/webgui.asset b/data/assets/util/webgui.asset index a382a4a524..d0930275c1 100644 --- a/data/assets/util/webgui.asset +++ b/data/assets/util/webgui.asset @@ -4,13 +4,16 @@ local guiCustomization = asset.require("customization/gui") -- Select which commit hashes to use for the frontend and backend -local frontendHash = "1e64af3b19764af15ad844dec543b1fffec99fe9" +local frontendHash = "ca129595ad31b89eafeba3a2a95d13777115eb86" + +-- The name of the file to download from the server +local file = "frontendOld.zip" local frontend = asset.resource({ Identifier = "WebGuiFrontend", Name = "Web Gui Frontend", Type = "UrlSynchronization", - Url = "http://data.openspaceproject.com/files/webgui/frontend/" .. frontendHash .. "/frontend.zip" + Url = "http://data.openspaceproject.com/files/webgui/frontend/" .. frontendHash .. "/" .. file }) @@ -18,7 +21,7 @@ asset.onInitialize(function() -- Unzip the frontend bundle local dest = frontend .. "frontend" if not openspace.directoryExists(dest) then - openspace.unzipFile(frontend .. "frontend.zip", dest, true) + openspace.unzipFile(frontend .. file, dest, true) end -- Disable the server, add production gui endpoint, and restart server. diff --git a/include/openspace/rendering/renderable.h b/include/openspace/rendering/renderable.h index 68149545f0..f434928015 100644 --- a/include/openspace/rendering/renderable.h +++ b/include/openspace/rendering/renderable.h @@ -45,6 +45,7 @@ namespace ghoul::opengl { namespace openspace { class Camera; +class Ellipsoid; struct RenderData; struct RendererTasks; struct SurfacePositionHandle; @@ -103,6 +104,8 @@ public: virtual SurfacePositionHandle calculateSurfacePositionHandle( const glm::dvec3& targetModelSpace) const; + virtual Ellipsoid ellipsoid() const; + virtual bool renderedWithDesiredData() const; RenderBin renderBin() const; diff --git a/include/openspace/scene/scenegraphnode.h b/include/openspace/scene/scenegraphnode.h index 225643103f..7f08699654 100644 --- a/include/openspace/scene/scenegraphnode.h +++ b/include/openspace/scene/scenegraphnode.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -130,6 +131,8 @@ public: const std::vector& onRecedeAction() const; const std::vector& onExitAction() const; + Ellipsoid ellipsoid() const; + double boundingSphere() const; double interactionSphere() const; diff --git a/modules/globebrowsing/src/ellipsoid.h b/include/openspace/util/ellipsoid.h similarity index 95% rename from modules/globebrowsing/src/ellipsoid.h rename to include/openspace/util/ellipsoid.h index 9fa33b4a91..5a4dd2e186 100644 --- a/modules/globebrowsing/src/ellipsoid.h +++ b/include/openspace/util/ellipsoid.h @@ -22,14 +22,14 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#ifndef __OPENSPACE_MODULE_GLOBEBROWSING___ELLIPSOID___H__ -#define __OPENSPACE_MODULE_GLOBEBROWSING___ELLIPSOID___H__ +#ifndef __OPENSPACE_CORE___ELLIPSOID___H__ +#define __OPENSPACE_CORE___ELLIPSOID___H__ #include #include -namespace openspace::globebrowsing { +namespace openspace { struct Geodetic2; struct Geodetic3; @@ -114,6 +114,6 @@ private: std::vector _shadowConfArray; }; -} // namespace openspace::globebrowsing +} // namespace openspace -#endif // __OPENSPACE_MODULE_GLOBEBROWSING___ELLIPSOID___H__ +#endif // __OPENSPACE_CORE___ELLIPSOID___H__ diff --git a/include/openspace/util/geodetic.h b/include/openspace/util/geodetic.h new file mode 100644 index 0000000000..ddc614b7a4 --- /dev/null +++ b/include/openspace/util/geodetic.h @@ -0,0 +1,83 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2025 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_CORE___GEODETIC___H__ +#define __OPENSPACE_CORE___GEODETIC___H__ + +#include +#include + +namespace openspace { + +class SceneGraphNode; + +struct Geodetic2 { + double lat = 0.0; // in radians + double lon = 0.0; // in radians +}; + +struct Geodetic3 { + Geodetic2 geodetic2; + double height = 0.0; // in meters +}; + +/** + * Sets the camera position for the next frame to be at the location identified by the + * provided geodetic position \p geo relative to the SceneGraphNode \p sgn. The current + * altitude of the camera over the selected scene graph node is maintained. + */ +void goToGeodetic2(const SceneGraphNode& sgn, Geodetic2 geo); + +/** + * Sets the camera position for the next frame to be at the location identified by the + * provided geodetic position \p geo relative to the SceneGraphNode \p sgn. + */ +void goToGeodetic3(const SceneGraphNode& sgn, Geodetic3 geo); + +/** + * Returns the Cartesian coordinate for the provided geodetic position of \p latitude, + * \p longitude, and \p altitude relative to the SceneGraphNode \p sgn. If no altitude is + * provided, the current altitude of the camera relative to the scene graph node is used. + */ +glm::vec3 cartesianCoordinatesFromGeo(const SceneGraphNode& sgn, double latitude, + double longitude, std::optional altitude = std::nullopt); + +/** + * Returns the position of the camera relative to the current anchor node as geodetic + * coordinates. The returned value contains the latitude in the x coordinate, the + * longitude in the y coordinate, and the altitude in the z coordinate. The longitude and + * latitude are provided in degrees, the altitude is provided in meters. + */ +glm::dvec3 geoPositionFromCamera(); + +/** + * Returns the height of the camera relative to the provided SceneGraphNode \p sgn in + * meters. If \p useHeightMap is provided as `true` and \p sgn is a globe with an existing + * height map, that height map is used to calculate the altitude. + */ +double altitudeFromCamera(const SceneGraphNode& sgn, bool useHeightMap = false); + +} // namespace openspace + +#endif // __OPENSPACE_CORE___GEODETIC___H__ diff --git a/modules/base/CMakeLists.txt b/modules/base/CMakeLists.txt index d03de639d8..b4467daaff 100644 --- a/modules/base/CMakeLists.txt +++ b/modules/base/CMakeLists.txt @@ -76,6 +76,7 @@ set(HEADER_FILES rotation/timelinerotation.h rotation/constantrotation.h rotation/fixedrotation.h + rotation/globerotation.h rotation/luarotation.h rotation/multirotation.h rotation/staticrotation.h @@ -87,6 +88,7 @@ set(HEADER_FILES scale/timelinescale.h timeframe/timeframeinterval.h timeframe/timeframeunion.h + translation/globetranslation.h translation/luatranslation.h translation/multitranslation.h translation/statictranslation.h @@ -147,6 +149,7 @@ set(SOURCE_FILES rotation/timelinerotation.cpp rotation/constantrotation.cpp rotation/fixedrotation.cpp + rotation/globerotation.cpp rotation/luarotation.cpp rotation/multirotation.cpp rotation/staticrotation.cpp @@ -158,6 +161,7 @@ set(SOURCE_FILES scale/timelinescale.cpp timeframe/timeframeinterval.cpp timeframe/timeframeunion.cpp + translation/globetranslation.cpp translation/luatranslation.cpp translation/multitranslation.cpp translation/statictranslation.cpp diff --git a/modules/base/basemodule.cpp b/modules/base/basemodule.cpp index d41cf1b052..3fa210df41 100644 --- a/modules/base/basemodule.cpp +++ b/modules/base/basemodule.cpp @@ -71,6 +71,7 @@ #include #include #include +#include #include #include #include @@ -82,6 +83,7 @@ #include #include #include +#include #include #include #include @@ -195,6 +197,7 @@ void BaseModule::internalInitialize(const ghoul::Dictionary&) { fRotation->registerClass("ConstantRotation"); fRotation->registerClass("FixedRotation"); + fRotation->registerClass("GlobeRotation"); fRotation->registerClass("LuaRotation"); fRotation->registerClass("MultiRotation"); fRotation->registerClass("StaticRotation"); @@ -224,6 +227,7 @@ void BaseModule::internalInitialize(const ghoul::Dictionary&) { FactoryManager::ref().factory(); ghoul_assert(fTranslation, "Ephemeris factory was not created"); + fTranslation->registerClass("GlobeTranslation"); fTranslation->registerClass("LuaTranslation"); fTranslation->registerClass("MultiTranslation"); fTranslation->registerClass("StaticTranslation"); @@ -289,6 +293,7 @@ std::vector BaseModule::documentations() const { ConstantRotation::Documentation(), FixedRotation::Documentation(), + GlobeRotation::Documentation(), LuaRotation::Documentation(), MultiRotation::Documentation(), StaticRotation::Documentation(), @@ -304,6 +309,7 @@ std::vector BaseModule::documentations() const { TimeFrameInterval::Documentation(), TimeFrameUnion::Documentation(), + GlobeTranslation::Documentation(), LuaTranslation::Documentation(), MultiTranslation::Documentation(), StaticTranslation::Documentation(), diff --git a/modules/globebrowsing/src/globerotation.cpp b/modules/base/rotation/globerotation.cpp similarity index 80% rename from modules/globebrowsing/src/globerotation.cpp rename to modules/base/rotation/globerotation.cpp index c8e19f7953..0b37d213ee 100644 --- a/modules/globebrowsing/src/globerotation.cpp +++ b/modules/base/rotation/globerotation.cpp @@ -22,16 +22,16 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#include +#include -#include -#include #include #include #include #include #include #include +#include +#include #include #include #include @@ -40,7 +40,10 @@ namespace { constexpr openspace::properties::Property::PropertyInfo GlobeInfo = { "Globe", "Attached Globe", - "The globe on which the longitude/latitude is specified.", + "The node on which the longitude/latitude is specified. If the node is a globe, " + "the correct height information for the globe is used. Otherwise, the position " + "is specified based on the longitude and latitude on the node's interaction " + "sphere", openspace::properties::Property::Visibility::User }; @@ -90,12 +93,11 @@ namespace { // This `Rotation` orients the scene graph node in such a way that the y-axis points // away from the provided globe, the x-axis points towards the globe's southern pole // and the z-axis points in a western direction. Using this rotation generally means - // using the [GlobeTranslation](#globebrowsing_translation_globetranslation) to place - // the scene graph node at the same position for which the rotation is calculated. + // using the [GlobeTranslation](#base_translation_globetranslation) to place the scene + // graph node at the same position for which the rotation is calculated. struct [[codegen::Dictionary(GlobeRotation)]] Parameters { // [[codegen::verbatim(GlobeInfo.description)]] - std::string globe - [[codegen::annotation("A valid scene graph node with a RenderableGlobe")]]; + std::string globe [[codegen::identifier()]]; // [[codegen::verbatim(LatitudeInfo.description)]] double latitude [[codegen::inrange(-90.0, 90.0)]]; @@ -115,15 +117,15 @@ namespace { #include "globerotation_codegen.cpp" } // namespace -namespace openspace::globebrowsing { +namespace openspace { documentation::Documentation GlobeRotation::Documentation() { - return codegen::doc("globebrowsing_rotation_globerotation"); + return codegen::doc("base_rotation_globerotation"); } GlobeRotation::GlobeRotation(const ghoul::Dictionary& dictionary) : Rotation(dictionary) - , _globe(GlobeInfo) + , _sceneGraphNode(GlobeInfo) , _latitude(LatitudeInfo, 0.0, -90.0, 90.0) , _longitude(LongitudeInfo, 0.0, -180.0, 180.0) , _angle(AngleInfo, 0.0, 0.0, 360.0) @@ -132,12 +134,12 @@ GlobeRotation::GlobeRotation(const ghoul::Dictionary& dictionary) { const Parameters p = codegen::bake(dictionary); - _globe = p.globe; - _globe.onChange([this]() { - findGlobe(); + _sceneGraphNode = p.globe; + _sceneGraphNode.onChange([this]() { + findNode(); setUpdateVariables(); }); - addProperty(_globe); + addProperty(_sceneGraphNode); _latitude = p.latitude; _latitude.onChange([this]() { setUpdateVariables(); }); @@ -160,21 +162,16 @@ GlobeRotation::GlobeRotation(const ghoul::Dictionary& dictionary) addProperty(_useCamera); } -void GlobeRotation::findGlobe() { - SceneGraphNode* n = sceneGraphNode(_globe); - if (n && n->renderable() && dynamic_cast(n->renderable())) { - _globeNode = dynamic_cast(n->renderable()); - } - else { +void GlobeRotation::findNode() { + SceneGraphNode* n = sceneGraphNode(_sceneGraphNode); + if (!n || !n->renderable()) { LERRORC( "GlobeRotation", - "Could not set attached node as it does not have a RenderableGlobe" + "Could not set attached node as it does not have a Renderable" ); - if (_globeNode) { - // Reset the globe name to it's previous name - _globe = _globeNode->identifier(); - } + return; } + _attachedNode = n; } void GlobeRotation::setUpdateVariables() { @@ -183,21 +180,26 @@ void GlobeRotation::setUpdateVariables() { } glm::vec3 GlobeRotation::computeSurfacePosition(double latitude, double longitude) const { - ghoul_assert(_globeNode, "Globe cannot be nullptr"); + ghoul_assert(_attachedNode, "Renderable cannot be nullptr"); - GlobeBrowsingModule* mod = global::moduleEngine->module(); - const glm::vec3 groundPos = mod->cartesianCoordinatesFromGeo( - *_globeNode, + const Geodetic3 pos = { + { .lat = glm::radians(latitude), .lon = glm::radians(longitude) }, + altitudeFromCamera(*_attachedNode) + }; + + const glm::vec3 groundPos = cartesianCoordinatesFromGeo( + *_attachedNode, latitude, longitude, 0.0 ); - const SurfacePositionHandle h = _globeNode->calculateSurfacePositionHandle(groundPos); + const SurfacePositionHandle h = + _attachedNode->calculateSurfacePositionHandle(groundPos); // Compute position including heightmap - return mod->cartesianCoordinatesFromGeo( - *_globeNode, + return cartesianCoordinatesFromGeo( + *_attachedNode, latitude, longitude, h.heightToSurface @@ -205,6 +207,11 @@ glm::vec3 GlobeRotation::computeSurfacePosition(double latitude, double longitud } void GlobeRotation::update(const UpdateData& data) { + if (!_attachedNode) [[unlikely]] { + findNode(); + _matrixIsDirty = true; + } + if (_useHeightmap || _useCamera) { // If we use the heightmap, we have to compute the height every frame setUpdateVariables(); @@ -214,23 +221,14 @@ void GlobeRotation::update(const UpdateData& data) { } glm::dmat3 GlobeRotation::matrix(const UpdateData&) const { - if (!_globeNode) { - // @TODO(abock): The const cast should be removed on a redesign of the rotation - // to make the matrix function not constant. Const casting this - // away is fine as the factories that create the rotations don't - // create them as constant objects - const_cast(this)->findGlobe(); - _matrixIsDirty = true; - } - if (!_matrixIsDirty) [[likely]] { return _matrix; } - if (!_globeNode) { + if (!_attachedNode) { LERRORC( "GlobeRotation", - std::format("Could not find globe '{}'", _globe.value()) + std::format("Could not find globe '{}'", _sceneGraphNode.value()) ); return _matrix; } @@ -239,8 +237,7 @@ glm::dmat3 GlobeRotation::matrix(const UpdateData&) const { double lon = _longitude; if (_useCamera) { - GlobeBrowsingModule* mod = global::moduleEngine->module(); - const glm::dvec3 position = mod->geoPosition(); + const glm::dvec3 position = geoPositionFromCamera(); lat = position.x; lon = position.y; } @@ -260,7 +257,7 @@ glm::dmat3 GlobeRotation::matrix(const UpdateData&) const { else { const float latitudeRad = glm::radians(static_cast(lat)); const float longitudeRad = glm::radians(static_cast(lon)); - yAxis = _globeNode->ellipsoid().geodeticSurfaceNormal( + yAxis = _attachedNode->ellipsoid().geodeticSurfaceNormal( { latitudeRad, longitudeRad } ); } diff --git a/modules/globebrowsing/src/globerotation.h b/modules/base/rotation/globerotation.h similarity index 83% rename from modules/globebrowsing/src/globerotation.h rename to modules/base/rotation/globerotation.h index 78c4cd3142..753741c884 100644 --- a/modules/globebrowsing/src/globerotation.h +++ b/modules/base/rotation/globerotation.h @@ -22,8 +22,8 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#ifndef __OPENSPACE_MODULE_GLOBEBROWSING___GLOBEROTATION___H__ -#define __OPENSPACE_MODULE_GLOBEBROWSING___GLOBEROTATION___H__ +#ifndef __OPENSPACE_MODULE_BASE___GLOBEROTATION___H__ +#define __OPENSPACE_MODULE_BASE___GLOBEROTATION___H__ #include @@ -31,9 +31,9 @@ #include #include -namespace openspace::globebrowsing { +namespace openspace { -class RenderableGlobe; +class SceneGraphNode; class GlobeRotation : public Rotation { public: @@ -45,23 +45,29 @@ public: static documentation::Documentation Documentation(); private: - void findGlobe(); + void findNode(); void setUpdateVariables(); + + /** + * Calculates the position on the surface of the node based on the provided latitude + * and longitude (in degrees). Returns the position on the surface of the node + * corresponding to the provided latitude and longitude. + */ glm::vec3 computeSurfacePosition(double latitude, double longitude) const; - properties::StringProperty _globe; + properties::StringProperty _sceneGraphNode; properties::DoubleProperty _latitude; properties::DoubleProperty _longitude; properties::DoubleProperty _angle; properties::BoolProperty _useHeightmap; properties::BoolProperty _useCamera; - RenderableGlobe* _globeNode = nullptr; + SceneGraphNode* _attachedNode = nullptr; mutable bool _matrixIsDirty = true; mutable glm::dmat3 _matrix = glm::dmat3(0.0); }; -} // namespace openspace::globebrowsing +} // namespace openspace -#endif // __OPENSPACE_MODULE_GLOBEBROWSING___GLOBEROTATION___H__ +#endif // __OPENSPACE_MODULE_BASE___GLOBEROTATION___H__ diff --git a/modules/globebrowsing/src/globetranslation.cpp b/modules/base/translation/globetranslation.cpp similarity index 80% rename from modules/globebrowsing/src/globetranslation.cpp rename to modules/base/translation/globetranslation.cpp index 311233eeda..1576c2c6bb 100644 --- a/modules/globebrowsing/src/globetranslation.cpp +++ b/modules/base/translation/globetranslation.cpp @@ -22,16 +22,17 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#include +#include #include -#include #include #include #include #include +#include #include #include +#include #include #include @@ -39,7 +40,10 @@ namespace { constexpr openspace::properties::Property::PropertyInfo GlobeInfo = { "Globe", "Attached Globe", - "The globe on which the longitude/latitude is specified.", + "The node on which the longitude/latitude is specified. If the node is a globe, " + "the correct height information for the globe is used. Otherwise, the position " + "is specified based on the longitude and latitude on the node's interaction " + "sphere", openspace::properties::Property::Visibility::User }; @@ -93,10 +97,21 @@ namespace { openspace::properties::Property::Visibility::AdvancedUser }; + // This `Translation` places the scene graph node at a specific location relative to + // another scene graph node. The position is identified via the longitude, latitude, + // and altitude parameters. If the provided scene graph node is a globe, the positions + // correspond to the native coordinate frame of that object. If it is a node without a + // renderable or a non-globe renderable, the latitude/longitude grid used is the + // node's interaction sphere. + // This class is useful in conjunction with the + // [GlobeRotation](#base_rotation_globerotation) rotation to orient a scene graph node + // away from the center of the body. + // + // If the `UseCamera` value is set, the object's position automatically updates based + // on the current camera location. struct [[codegen::Dictionary(GlobeTranslation)]] Parameters { // [[codegen::verbatim(GlobeInfo.description)]] - std::string globe - [[codegen::annotation("A valid scene graph node with a RenderableGlobe")]]; + std::string globe [[codegen::identifier()]]; // [[codegen::verbatim(LatitudeInfo.description)]] std::optional latitude; @@ -119,15 +134,15 @@ namespace { #include "globetranslation_codegen.cpp" } // namespace -namespace openspace::globebrowsing { +namespace openspace { documentation::Documentation GlobeTranslation::Documentation() { - return codegen::doc("globebrowsing_translation_globetranslation"); + return codegen::doc("base_translation_globetranslation"); } GlobeTranslation::GlobeTranslation(const ghoul::Dictionary& dictionary) : Translation(dictionary) - , _globe(GlobeInfo) + , _sceneGraphNode(GlobeInfo) , _latitude(LatitudeInfo, 0.0, -90.0, 90.0) , _longitude(LongitudeInfo, 0.0, -180.0, 180.0) , _altitude(AltitudeInfo, 0.0, -1e12, 1e12) @@ -137,12 +152,12 @@ GlobeTranslation::GlobeTranslation(const ghoul::Dictionary& dictionary) { const Parameters p = codegen::bake(dictionary); - _globe = p.globe; - _globe.onChange([this]() { + _sceneGraphNode = p.globe; + _sceneGraphNode.onChange([this]() { fillAttachedNode(); setUpdateVariables(); }); - addProperty(_globe); + addProperty(_sceneGraphNode); _latitude = p.latitude.value_or(_latitude); _latitude.onChange([this]() { setUpdateVariables(); }); @@ -172,20 +187,16 @@ GlobeTranslation::GlobeTranslation(const ghoul::Dictionary& dictionary) } void GlobeTranslation::fillAttachedNode() { - SceneGraphNode* n = sceneGraphNode(_globe); - if (n && n->renderable() && dynamic_cast(n->renderable())) { - _attachedNode = dynamic_cast(n->renderable()); - } - else { + SceneGraphNode* n = sceneGraphNode(_sceneGraphNode); + if (!n || !n->renderable()) { LERRORC( "GlobeTranslation", - "Could not set attached node as it does not have a RenderableGlobe" + "Could not set attached node as it does not have a renderable" ); - if (_attachedNode) { - // Reset the globe name to its previous name - _globe = _attachedNode->identifier(); - } + return; } + + _attachedNode = n; } void GlobeTranslation::setUpdateVariables() { @@ -194,6 +205,11 @@ void GlobeTranslation::setUpdateVariables() { } void GlobeTranslation::update(const UpdateData& data) { + if (!_attachedNode) [[unlikely]] { + fillAttachedNode(); + _positionIsDirty = true; + } + if (_useHeightmap || _useCamera) { // If we use the heightmap, we have to compute the height every frame setUpdateVariables(); @@ -203,15 +219,6 @@ void GlobeTranslation::update(const UpdateData& data) { } glm::dvec3 GlobeTranslation::position(const UpdateData&) const { - if (!_attachedNode) { - // @TODO(abock): The const cast should be removed on a redesign of the translation - // to make the position function not constant. Const casting this - // away is fine as the factories that create the translations don't - // create them as constant objects - const_cast(this)->fillAttachedNode(); - _positionIsDirty = true; - } - if (!_positionIsDirty) [[likely]] { return _position; } @@ -219,19 +226,17 @@ glm::dvec3 GlobeTranslation::position(const UpdateData&) const { if (!_attachedNode) { LERRORC( "GlobeRotation", - std::format("Could not find attached node '{}'", _globe.value()) + std::format("Could not find attached node '{}'", _sceneGraphNode.value()) ); return _position; } - GlobeBrowsingModule* mod = global::moduleEngine->module(); - double lat = _latitude; double lon = _longitude; double alt = _altitude; if (_useCamera) { - const glm::dvec3 position = mod->geoPosition(); + const glm::dvec3 position = geoPositionFromCamera(); lat = position.x; lon = position.y; if (_useCameraAltitude) { @@ -240,7 +245,7 @@ glm::dvec3 GlobeTranslation::position(const UpdateData&) const { } if (_useHeightmap) { - const glm::vec3 groundPos = mod->cartesianCoordinatesFromGeo( + const glm::vec3 groundPos = cartesianCoordinatesFromGeo( *_attachedNode, lat, lon, @@ -251,24 +256,23 @@ glm::dvec3 GlobeTranslation::position(const UpdateData&) const { groundPos ); - _position = mod->cartesianCoordinatesFromGeo( + _position = cartesianCoordinatesFromGeo( *_attachedNode, lat, lon, h.heightToSurface + alt ); - return _position; } else { - _position = mod->cartesianCoordinatesFromGeo( + _position = cartesianCoordinatesFromGeo( *_attachedNode, lat, lon, alt ); _positionIsDirty = false; - return _position; } + return _position; } -} // namespace openspace::globebrowsing +} // namespace openspace diff --git a/modules/globebrowsing/src/globetranslation.h b/modules/base/translation/globetranslation.h similarity index 88% rename from modules/globebrowsing/src/globetranslation.h rename to modules/base/translation/globetranslation.h index e904985209..675692b44b 100644 --- a/modules/globebrowsing/src/globetranslation.h +++ b/modules/base/translation/globetranslation.h @@ -22,8 +22,8 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#ifndef __OPENSPACE_MODULE_GLOBEBROWSING___GLOBETRANSLATION___H__ -#define __OPENSPACE_MODULE_GLOBEBROWSING___GLOBETRANSLATION___H__ +#ifndef __OPENSPACE_MODULE_BASE___GLOBETRANSLATION___H__ +#define __OPENSPACE_MODULE_BASE___GLOBETRANSLATION___H__ #include @@ -31,9 +31,9 @@ #include #include -namespace openspace::globebrowsing { +namespace openspace { -class RenderableGlobe; +class SceneGraphNode; class GlobeTranslation : public Translation { public: @@ -48,7 +48,7 @@ private: void fillAttachedNode(); void setUpdateVariables(); - properties::StringProperty _globe; + properties::StringProperty _sceneGraphNode; properties::DoubleProperty _latitude; properties::DoubleProperty _longitude; properties::DoubleProperty _altitude; @@ -56,12 +56,12 @@ private: properties::BoolProperty _useCamera; properties::BoolProperty _useCameraAltitude; - RenderableGlobe* _attachedNode = nullptr; + SceneGraphNode* _attachedNode = nullptr; mutable bool _positionIsDirty = true; mutable glm::dvec3 _position = glm::dvec3(0.0); }; -} // namespace openspace::globebrowsing +} // namespace openspace -#endif // __OPENSPACE_MODULE_GLOBEBROWSING___GLOBETRANSLATION___H__ +#endif // __OPENSPACE_MODULE_BASE___GLOBETRANSLATION___H__ diff --git a/modules/globebrowsing/CMakeLists.txt b/modules/globebrowsing/CMakeLists.txt index 0b88dc1e86..b3bc627443 100644 --- a/modules/globebrowsing/CMakeLists.txt +++ b/modules/globebrowsing/CMakeLists.txt @@ -29,12 +29,9 @@ set(HEADER_FILES src/asynctiledataprovider.h src/basictypes.h src/dashboarditemglobelocation.h - src/ellipsoid.h src/gdalwrapper.h src/geodeticpatch.h src/globelabelscomponent.h - src/globetranslation.h - src/globerotation.h src/gpulayergroup.h src/layer.h src/layeradjustment.h @@ -84,12 +81,9 @@ set(SOURCE_FILES globebrowsingmodule_lua.inl src/asynctiledataprovider.cpp src/dashboarditemglobelocation.cpp - src/ellipsoid.cpp src/gdalwrapper.cpp src/geodeticpatch.cpp src/globelabelscomponent.cpp - src/globetranslation.cpp - src/globerotation.cpp src/gpulayergroup.cpp src/layer.cpp src/layeradjustment.cpp diff --git a/modules/globebrowsing/globebrowsingmodule.cpp b/modules/globebrowsing/globebrowsingmodule.cpp index 1e8209de29..b5a9563830 100644 --- a/modules/globebrowsing/globebrowsingmodule.cpp +++ b/modules/globebrowsing/globebrowsingmodule.cpp @@ -32,8 +32,6 @@ #include #include #include -#include -#include #include #include #include @@ -66,6 +64,7 @@ #include #include #include +#include #include #include #include @@ -291,16 +290,6 @@ void GlobeBrowsingModule::internalInitialize(const ghoul::Dictionary& dict) { ghoul_assert(fRenderable, "Renderable factory was not created"); fRenderable->registerClass("RenderableGlobe"); - ghoul::TemplateFactory* fTranslation = - FactoryManager::ref().factory(); - ghoul_assert(fTranslation, "Translation factory was not created"); - fTranslation->registerClass("GlobeTranslation"); - - ghoul::TemplateFactory* fRotation = - FactoryManager::ref().factory(); - ghoul_assert(fRotation, "Rotation factory was not created"); - fRotation->registerClass("GlobeRotation"); - FactoryManager::ref().addFactory("TileProvider"); ghoul::TemplateFactory* fTileProvider = @@ -334,8 +323,6 @@ std::vector GlobeBrowsingModule::documentations() return { globebrowsing::Layer::Documentation(), globebrowsing::LayerAdjustment::Documentation(), - globebrowsing::GlobeTranslation::Documentation(), - globebrowsing::GlobeRotation::Documentation(), globebrowsing::RenderableGlobe::Documentation(), globebrowsing::DefaultTileProvider::Documentation(), globebrowsing::ImageSequenceTileProvider::Documentation(), @@ -354,177 +341,28 @@ std::vector GlobeBrowsingModule::documentations() }; } -void GlobeBrowsingModule::goToChunk(const globebrowsing::RenderableGlobe& globe, +void GlobeBrowsingModule::goToChunk(const SceneGraphNode& node, int x, int y, int level) { ghoul_assert(level < std::numeric_limits::max(), "Level way too big"); - goToChunk( - globe, - globebrowsing::TileIndex(x, y, static_cast(level)), - glm::vec2(0.5f, 0.5f) - ); -} - -void GlobeBrowsingModule::goToGeo(const globebrowsing::RenderableGlobe& globe, - double latitude, double longitude) -{ - using namespace globebrowsing; - goToGeodetic2( - globe, - Geodetic2{ glm::radians(latitude), glm::radians(longitude) } - ); -} - -void GlobeBrowsingModule::goToGeo(const globebrowsing::RenderableGlobe& globe, - double latitude, double longitude, double altitude) -{ - using namespace globebrowsing; - goToGeodetic3( - globe, - { - Geodetic2{ glm::radians(latitude), glm::radians(longitude) }, - altitude - } - ); -} - -glm::vec3 GlobeBrowsingModule::cartesianCoordinatesFromGeo( - const globebrowsing::RenderableGlobe& globe, - double latitude, - double longitude, - std::optional altitude) -{ + using namespace openspace; using namespace globebrowsing; - const Geodetic3 pos = { - { .lat = glm::radians(latitude), .lon = glm::radians(longitude) }, - altitude.value_or(altitudeFromCamera(globe)) - }; - - return glm::vec3(globe.ellipsoid().cartesianPosition(pos)); -} - -glm::dvec3 GlobeBrowsingModule::geoPosition() const { - using namespace globebrowsing; - - const SceneGraphNode* n = global::navigationHandler->orbitalNavigator().anchorNode(); - if (!n) { - return glm::dvec3(0.0); - } - const RenderableGlobe* globe = dynamic_cast(n->renderable()); - if (!globe) { - return glm::dvec3(0.0); - } - - const glm::dvec3 cameraPosition = global::navigationHandler->camera()->positionVec3(); - const glm::dmat4 inverseModelTransform = glm::inverse(n->modelTransform()); - const glm::dvec3 cameraPositionModelSpace = - glm::dvec3(inverseModelTransform * glm::dvec4(cameraPosition, 1.0)); - const SurfacePositionHandle posHandle = globe->calculateSurfacePositionHandle( - cameraPositionModelSpace + const GeodeticPatch patch = GeodeticPatch( + globebrowsing::TileIndex(x, y, static_cast(level)) ); - - const Geodetic2 geo2 = globe->ellipsoid().cartesianToGeodetic2( - posHandle.centerToReferenceSurface - ); - - const double lat = glm::degrees(geo2.lat); - const double lon = glm::degrees(geo2.lon); - - double altitude = glm::length( - cameraPositionModelSpace - posHandle.centerToReferenceSurface - ); - - if (glm::length(cameraPositionModelSpace) < - glm::length(posHandle.centerToReferenceSurface)) - { - altitude = -altitude; - } - - return glm::dvec3(lat, lon, altitude); -} - -double GlobeBrowsingModule::altitudeFromCamera( - const globebrowsing::RenderableGlobe& globe, - bool useHeightMap) const -{ - using namespace globebrowsing; - - const glm::dvec3 cameraPosition = global::navigationHandler->camera()->positionVec3(); - SceneGraphNode* sgn = dynamic_cast(globe.owner()); - if (!sgn) { - LERROR("Could not find scene graph node for globe"); - return 0.0; - } - - const glm::dmat4 inverseModelTransform = glm::inverse(sgn->modelTransform()); - - const glm::dvec3 cameraPositionModelSpace = - glm::dvec3(inverseModelTransform * glm::dvec4(cameraPosition, 1.0)); - - SurfacePositionHandle posHandle = globe.calculateSurfacePositionHandle( - cameraPositionModelSpace - ); - - if (useHeightMap) { - const glm::dvec3 centerToActualSurface = posHandle.centerToReferenceSurface + - posHandle.referenceSurfaceOutDirection * posHandle.heightToSurface; - - return glm::length(cameraPositionModelSpace - centerToActualSurface); - } - else { - // Do not use height map => compute distance to reference surface - return glm::length(cameraPositionModelSpace - posHandle.centerToReferenceSurface); - } -} - -void GlobeBrowsingModule::goToChunk(const globebrowsing::RenderableGlobe& globe, - const globebrowsing::TileIndex& ti, - const glm::vec2& uv) -{ - using namespace globebrowsing; - - const GeodeticPatch patch(ti); const Geodetic2 corner = patch.corner(SOUTH_WEST); Geodetic2 positionOnPatch = patch.size(); - positionOnPatch.lat *= uv.y; - positionOnPatch.lon *= uv.x; + positionOnPatch.lat *= 0.5f; + positionOnPatch.lon *= 0.5f; const Geodetic2 pointPosition = { .lat = corner.lat + positionOnPatch.lat, .lon = corner.lon + positionOnPatch.lon }; - const double altitude = altitudeFromCamera(globe); + const double altitude = altitudeFromCamera(node); - goToGeodetic3(globe, { pointPosition, altitude }); -} - -void GlobeBrowsingModule::goToGeodetic2(const globebrowsing::RenderableGlobe& globe, - globebrowsing::Geodetic2 geo2) -{ - using namespace globebrowsing; - - const double altitude = altitudeFromCamera(globe); - - goToGeodetic3(globe, { geo2, altitude }); -} - -void GlobeBrowsingModule::goToGeodetic3(const globebrowsing::RenderableGlobe& globe, - globebrowsing::Geodetic3 geo3) -{ - using namespace globebrowsing; - const glm::dvec3 positionModelSpace = globe.ellipsoid().cartesianPosition(geo3); - - interaction::NavigationState state; - state.anchor = globe.owner()->identifier(); - state.referenceFrame = globe.owner()->identifier(); - state.position = positionModelSpace; - // For globes, we know that the up-direction will always be positive Z. - // @TODO (2023-12-06 emmbr) Eventually, we want each scene graph node to be aware of - // its own preferred up-direction. At that time, this should no longer be hardcoded - state.up = glm::dvec3(0.0, 0.0, 1.0); - - global::navigationHandler->setNavigationStateNextFrame(state); + goToGeodetic3(node, { pointPosition, altitude }); } const globebrowsing::RenderableGlobe* @@ -671,12 +509,6 @@ scripting::LuaLibrary GlobeBrowsingModule::luaLibrary() const { codegen::lua::LayersDeprecated, codegen::lua::MoveLayer, codegen::lua::GoToChunk, - codegen::lua::JumpToGeo, - codegen::lua::GoToGeoDeprecated, - codegen::lua::FlyToGeo2, - codegen::lua::FlyToGeo, - codegen::lua::LocalPositionFromGeo, - codegen::lua::LocalPositionFromGeoDeprecated, codegen::lua::GeoPositionForCamera, codegen::lua::GeoPositionForCameraDeprecated, codegen::lua::LoadWMSCapabilities, @@ -684,7 +516,7 @@ scripting::LuaLibrary GlobeBrowsingModule::luaLibrary() const { codegen::lua::CapabilitiesWMS, codegen::lua::AddGeoJson, codegen::lua::DeleteGeoJson, - codegen::lua::AddGeoJsonFromFile, + codegen::lua::AddGeoJsonFromFile }, .scripts = { absPath("${MODULE_GLOBEBROWSING}/scripts/layer_support.lua"), diff --git a/modules/globebrowsing/globebrowsingmodule.h b/modules/globebrowsing/globebrowsingmodule.h index d785462707..4617cd7b98 100644 --- a/modules/globebrowsing/globebrowsingmodule.h +++ b/modules/globebrowsing/globebrowsingmodule.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -38,8 +39,6 @@ namespace openspace::globebrowsing { class RenderableGlobe; struct TileIndex; - struct Geodetic2; - struct Geodetic3; namespace cache { class MemoryAwareTileCache; } } // namespace openspace::globebrowsing @@ -47,6 +46,9 @@ namespace openspace::globebrowsing { namespace openspace { class Camera; +struct Geodetic2; +struct Geodetic3; +class SceneGraphNode; class GlobeBrowsingModule : public OpenSpaceModule { public: @@ -54,20 +56,7 @@ public: GlobeBrowsingModule(); - void goToChunk(const globebrowsing::RenderableGlobe& globe, int x, int y, int level); - void goToGeo(const globebrowsing::RenderableGlobe& globe, - double latitude, double longitude); - - void goToGeo(const globebrowsing::RenderableGlobe& globe, - double latitude, double longitude, double altitude); - - glm::vec3 cartesianCoordinatesFromGeo(const globebrowsing::RenderableGlobe& globe, - double latitude, double longitude, std::optional altitude = std::nullopt); - - glm::dvec3 geoPosition() const; - - double altitudeFromCamera(const globebrowsing::RenderableGlobe& globe, - bool useHeightMap = false) const; + void goToChunk(const SceneGraphNode& globe, int x, int y, int level); globebrowsing::cache::MemoryAwareTileCache* tileCache(); scripting::LuaLibrary luaLibrary() const override; @@ -107,15 +96,6 @@ protected: void internalInitialize(const ghoul::Dictionary&) override; private: - void goToChunk(const globebrowsing::RenderableGlobe& globe, - const globebrowsing::TileIndex& ti, const glm::vec2& uv); - - void goToGeodetic2(const globebrowsing::RenderableGlobe& globe, - globebrowsing::Geodetic2 geo2); - - void goToGeodetic3(const globebrowsing::RenderableGlobe& globe, - globebrowsing::Geodetic3 geo3); - properties::UIntProperty _tileCacheSizeMB; properties::StringProperty _defaultGeoPointTexturePath; diff --git a/modules/globebrowsing/globebrowsingmodule_lua.inl b/modules/globebrowsing/globebrowsingmodule_lua.inl index 34e372eaaa..507de3540d 100644 --- a/modules/globebrowsing/globebrowsingmodule_lua.inl +++ b/modules/globebrowsing/globebrowsingmodule_lua.inl @@ -300,288 +300,7 @@ namespace { throw ghoul::lua::LuaError("Identifier must be a RenderableGlobe"); } - global::moduleEngine->module()->goToChunk(*globe, x, y, level); -} - -/** - * Immediately move the camera to a geographic coordinate on a globe by first fading the - * rendering to black, jump to the specified coordinate, and then fade in. - * - * This is done by triggering another script that handles the logic. - * - * \param globe The identifier of a scene graph node that has a RenderableGlobe attached. - * If an empty string is provided, the current anchor node is used - * \param latitude The latitude of the target coordinate, in degrees - * \param longitude The longitude of the target coordinate, in degrees - * \param altitude An optional altitude, given in meters over the reference surface of - * the globe. If no altitude is provided, the altitude will be kept as - * the current distance to the reference surface of the specified globe. - * \param fadeDuration An optional duration for the fading. If not included, the - * property in Navigation Handler will be used - */ -[[codegen::luawrap]] void jumpToGeo(std::string globe, double latitude, double longitude, - std::optional altitude, - std::optional fadeDuration) -{ - using namespace openspace; - using namespace globebrowsing; - - std::string script; - - if (altitude.has_value()) { - script = std::format( - "openspace.globebrowsing.flyToGeo('{}', {}, {}, {}, 0)", - globe, latitude, longitude, *altitude - ); - } - else { - script = std::format( - "openspace.globebrowsing.flyToGeo2('{}', {}, {}, true, 0)", - globe, latitude, longitude - ); - } - - if (fadeDuration.has_value()) { - global::navigationHandler->triggerFadeToTransition( - std::move(script), - static_cast(*fadeDuration) - ); - } - else { - global::navigationHandler->triggerFadeToTransition(script); - } -} - -/** - * Immediately move the camera to a geographic coordinate on a globe. - * - * \param globe The identifier of a scene graph node that has a RenderableGlobe attached. - * If an empty string is provided, the current anchor node is used - * \param latitude The latitude of the target coordinate, in degrees - * \param longitude The longitude of the target coordinate, in degrees - * \param altitude An optional altitude, given in meters over the reference surface of - * the globe. If no altitude is provided, the altitude will be kept as - * the current distance to the reference surface of the specified globe. - */ -[[codegen::luawrap("goToGeo")]] void goToGeoDeprecated(std::string globe, double latitude, - double longitude, - std::optional altitude) -{ - LWARNINGC( - "Deprecation", - "'goToGeo' function is deprecated and should be replaced with 'jumpToGeo'" - ); - - return jumpToGeo( - std::move(globe), - latitude, - longitude, - altitude, - 0 - ); -} - -void flyToGeoInternal(std::string globe, double latitude, - double longitude, std::optional altitude, - std::optional duration, - std::optional shouldUseUpVector) -{ - using namespace openspace; - using namespace globebrowsing; - - const SceneGraphNode* n; - if (!globe.empty()) { - n = sceneGraphNode(globe); - if (!n) { - throw ghoul::lua::LuaError("Unknown globe name: " + globe); - } - } - else { - n = global::navigationHandler->orbitalNavigator().anchorNode(); - if (!n) { - throw ghoul::lua::LuaError("No anchor node is set"); - } - } - - const RenderableGlobe* gl = dynamic_cast(n->renderable()); - if (!gl) { - throw ghoul::lua::LuaError("The targetted node is not a RenderableGlobe"); - } - - auto module = global::moduleEngine->module(); - const glm::dvec3 positionModelCoords = module->cartesianCoordinatesFromGeo( - *gl, - latitude, - longitude, - altitude - ); - - const glm::dvec3 currentPosW = global::navigationHandler->camera()->positionVec3(); - const glm::dvec3 currentPosModelCoords = - glm::inverse(gl->modelTransform()) * glm::dvec4(currentPosW, 1.0); - - constexpr double LengthEpsilon = 10.0; // meters - if (glm::distance(currentPosModelCoords, positionModelCoords) < LengthEpsilon) { - LINFOC("GlobeBrowsing", "flyToGeo: Already at the requested position"); - return; - } - - ghoul::Dictionary instruction; - instruction.setValue("TargetType", std::string("Node")); - instruction.setValue("Target", n->identifier()); - instruction.setValue("Position", positionModelCoords); - instruction.setValue("PathType", std::string("ZoomOutOverview")); - - if (duration.has_value()) { - if (*duration < 0) { - throw ghoul::lua::LuaError("Duration must be a positive value"); - } - instruction.setValue("Duration", *duration); - } - - if (shouldUseUpVector.has_value()) { - instruction.setValue("UseTargetUpDirection", *shouldUseUpVector); - - } - - global::navigationHandler->pathNavigator().createPath(instruction); - global::navigationHandler->pathNavigator().startPath(); -} - -/** - * Fly the camera to a geographic coordinate (latitude and longitude) on a globe, using - * the path navigation system. - * - * The distance to fly to can either be set to be the current distance of the camera to - * the target object, or the default distance from the path navigation system. - * - * \param globe The identifier of a scene graph node that has a RenderableGlobe attached. - * If an empty string is provided, the current anchor node is used - * \param latitude The latitude of the target coordinate, in degrees - * \param longitude The longitude of the target coordinate, in degrees - * \param useCurrentDistance If true, use the current distance of the camera to the - * target globe when going to the specified position. If false, - * or not specified, set the distance based on the bounding - * sphere and the distance factor setting in Path Navigator - * \param duration An optional duration for the motion to take, in seconds. For example, - * a value of 5 means "fly to this position over a duration of 5 seconds" - * \param shouldUseUpVector If true, try to use the up-direction when computing the - * target position for the camera. For globes, this means that - * North should be up, in relation to the camera's view - * direction. Note that for this to take effect, rolling motions - * must be enabled in the Path Navigator settings. - */ -[[codegen::luawrap]] void flyToGeo2(std::string globe, double latitude, double longitude, - std::optional useCurrentDistance, - std::optional duration, - std::optional shouldUseUpVector) -{ - using namespace openspace; - - std::optional altitude; - if (useCurrentDistance.has_value() && *useCurrentDistance) { - altitude = std::nullopt; - } - else { - altitude = global::navigationHandler->pathNavigator().defaultArrivalHeight(globe); - } - - flyToGeoInternal( - globe, - latitude, - longitude, - std::nullopt, - duration, - shouldUseUpVector - ); -} - - /** - * Fly the camera to a geographic coordinate (latitude, longitude and altitude) on a - * globe, using the path navigation system. - * - * \param globe The identifier of a scene graph node that has a RenderableGlobe attached. - * If an empty string is provided, the current anchor node is used - * \param latitude The latitude of the target coordinate, in degrees - * \param longitude The longitude of the target coordinate, in degrees - * \param altitude The altitude of the target coordinate, in meters - * \param duration An optional duration for the motion to take, in seconds. For example, - * a value of 5 means "fly to this position over a duration of 5 seconds" - * \param shouldUseUpVector If true, try to use the up-direction when computing the - * target position for the camera. For globes, this means that - * North should be up, in relation to the camera's view - * direction. Note that for this to take effect, rolling motions - * must be enabled in the Path Navigator settings. - */ -[[codegen::luawrap]] void flyToGeo(std::string globe, double latitude, - double longitude, double altitude, - std::optional duration, - std::optional shouldUseUpVector) -{ - flyToGeoInternal(globe, latitude, longitude, altitude, duration, shouldUseUpVector); -} - -/** - * Returns the position in the local Cartesian coordinate system of the specified globe - * that corresponds to the given geographic coordinates. In the local coordinate system, - * the position (0,0,0) corresponds to the globe's center. - * - * \param globeIdentifier The identifier of the scene graph node for the globe - * \param latitude The latitude of the geograpic position, in degrees - * \param longitude The longitude of the geographic position, in degrees - * \param altitude The altitude, in meters - */ -[[codegen::luawrap]] -std::tuple -localPositionFromGeo(std::string globeIdentifier, double latitude, double longitude, - double altitude) -{ - using namespace openspace; - using namespace globebrowsing; - - SceneGraphNode* n = sceneGraphNode(globeIdentifier); - if (!n) { - throw ghoul::lua::LuaError("Unknown globe identifier: " + globeIdentifier); - } - const RenderableGlobe* globe = dynamic_cast(n->renderable()); - if (!globe) { - throw ghoul::lua::LuaError("Identifier must be a RenderableGlobe"); - } - - GlobeBrowsingModule& mod = *(global::moduleEngine->module()); - glm::vec3 p = mod.cartesianCoordinatesFromGeo(*globe, latitude, longitude, altitude); - return { p.x, p.y, p.z }; -} - -/** -* Returns the position in the local Cartesian coordinate system of the specified globe -* that corresponds to the given geographic coordinates. In the local coordinate system, -* the position (0,0,0) corresponds to the globe's center. -* -* Deprecated in favor of `localPositionFromGeo`. -* -* \param globeIdentifier The identifier of the scene graph node for the globe -* \param latitude The latitude of the geograpic position, in degrees -* \param longitude The longitude of the geographic position, in degrees -* \param altitude The altitude, in meters -*/ -[[codegen::luawrap("getLocalPositionFromGeo")]] -std::tuple -localPositionFromGeoDeprecated(std::string globeIdentifier, double latitude, - double longitude, double altitude) -{ - LWARNINGC( - "Deprecation", - "'getLocalPositionFromGeo' function is deprecated and should be replaced with " - "'localPositionFromGeo'" - ); - - return localPositionFromGeo( - std::move(globeIdentifier), - latitude, - longitude, - altitude - ); + global::moduleEngine->module()->goToChunk(*n, x, y, level); } /** @@ -645,15 +364,15 @@ localPositionFromGeoDeprecated(std::string globeIdentifier, double latitude, return { glm::degrees(geo2.lat), glm::degrees(geo2.lon), altitude }; } - /** - * Get geographic coordinates of the camera position in latitude, longitude, and altitude - * (degrees and meters). - * - * Deprecated in favor of `geoPositionForCamera`. - * - * \param useEyePosition If true, use the view direction of the camera instead of the - * camera position - */ +/** + * Get geographic coordinates of the camera position in latitude, longitude, and altitude + * (degrees and meters). + * + * Deprecated in favor of `geoPositionForCamera`. + * + * \param useEyePosition If true, use the view direction of the camera instead of the + * camera position + */ [[codegen::luawrap("getGeoPositionForCamera")]] std::tuple geoPositionForCameraDeprecated(bool useEyePosition = false) diff --git a/modules/globebrowsing/src/basictypes.h b/modules/globebrowsing/src/basictypes.h index c4a58380d4..f72f01df3d 100644 --- a/modules/globebrowsing/src/basictypes.h +++ b/modules/globebrowsing/src/basictypes.h @@ -42,20 +42,6 @@ struct AABB3 { -struct Geodetic2 { - double lat = 0.0; // in radians - double lon = 0.0; // in radians -}; - - - -struct Geodetic3 { - Geodetic2 geodetic2; - double height = 0.0; -}; - - - struct PixelRegion { glm::ivec2 start = glm::ivec2(0); glm::ivec2 numPixels = glm::ivec2(0); diff --git a/modules/globebrowsing/src/dashboarditemglobelocation.cpp b/modules/globebrowsing/src/dashboarditemglobelocation.cpp index 4db7e86a9f..e0e1e208ee 100644 --- a/modules/globebrowsing/src/dashboarditemglobelocation.cpp +++ b/modules/globebrowsing/src/dashboarditemglobelocation.cpp @@ -149,8 +149,7 @@ DashboardItemGlobeLocation::DashboardItemGlobeLocation( void DashboardItemGlobeLocation::update() { ZoneScoped; - GlobeBrowsingModule* module = global::moduleEngine->module(); - const glm::dvec3 position = module->geoPosition(); + const glm::dvec3 position = geoPositionFromCamera(); double lat = position.x; double lon = position.y; const double altitude = position.z; diff --git a/modules/globebrowsing/src/geodeticpatch.h b/modules/globebrowsing/src/geodeticpatch.h index d859a06ec5..17e5ff7f9d 100644 --- a/modules/globebrowsing/src/geodeticpatch.h +++ b/modules/globebrowsing/src/geodeticpatch.h @@ -26,6 +26,7 @@ #define __OPENSPACE_MODULE_GLOBEBROWSING___GEODETICPATCH___H__ #include +#include namespace openspace::globebrowsing { diff --git a/modules/globebrowsing/src/geojson/geojsoncomponent.cpp b/modules/globebrowsing/src/geojson/geojsoncomponent.cpp index 09b2c02d36..5469b17978 100644 --- a/modules/globebrowsing/src/geojson/geojsoncomponent.cpp +++ b/modules/globebrowsing/src/geojson/geojsoncomponent.cpp @@ -841,7 +841,7 @@ void GeoJsonComponent::flyToFeature(std::optional index) const { float lon = centroidLon + _latLongOffset.value().y; const std::string script = std::format( - "openspace.globebrowsing.flyToGeo([[{}]], {}, {}, {})", + "openspace.navigation.flyToGeo([[{}]], {}, {}, {})", _globeNode.owner()->identifier(), lat, lon, d ); global::scriptEngine->queueScript(script); diff --git a/modules/globebrowsing/src/geojson/globegeometryhelper.cpp b/modules/globebrowsing/src/geojson/globegeometryhelper.cpp index a5c9185136..14e93fc81c 100644 --- a/modules/globebrowsing/src/geojson/globegeometryhelper.cpp +++ b/modules/globebrowsing/src/geojson/globegeometryhelper.cpp @@ -242,7 +242,7 @@ subdivideTriangle(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, std::vector pointCoords; pointCoords.reserve(3 * maxSteps + 1); - const globebrowsing::Ellipsoid& ellipsoid = globe.ellipsoid(); + const Ellipsoid& ellipsoid = globe.ellipsoid(); const float lengthEdge01 = glm::length(v1 - v0); const float lengthEdge02 = glm::length(v2 - v0); diff --git a/modules/globebrowsing/src/geojson/globegeometryhelper.h b/modules/globebrowsing/src/geojson/globegeometryhelper.h index 7afd9f8fb0..7d4782de5b 100644 --- a/modules/globebrowsing/src/geojson/globegeometryhelper.h +++ b/modules/globebrowsing/src/geojson/globegeometryhelper.h @@ -28,15 +28,13 @@ #include #include -namespace openspace::globebrowsing { +namespace openspace { struct Geodetic2; struct Geodetic3; - class RenderableGlobe; -} // namespace openspace::globebrowsing -namespace openspace::rendering::helper { - struct VertexXYZNormal; -} // namespace openspace::rendering::helper + namespace globebrowsing { class RenderableGlobe; } + namespace rendering::helper { struct VertexXYZNormal; } +} // namespace openspace namespace geos::geom { class Coordinate; diff --git a/modules/globebrowsing/src/globelabelscomponent.cpp b/modules/globebrowsing/src/globelabelscomponent.cpp index 5e105646a4..8184f83c77 100644 --- a/modules/globebrowsing/src/globelabelscomponent.cpp +++ b/modules/globebrowsing/src/globelabelscomponent.cpp @@ -469,10 +469,8 @@ bool GlobeLabelsComponent::readLabelsFile(const std::filesystem::path& file) { } std::strncpy(lEntry.feature, token.c_str(), 255); - GlobeBrowsingModule* _globeBrowsingModule = - global::moduleEngine->module(); - lEntry.geoPosition = _globeBrowsingModule->cartesianCoordinatesFromGeo( - *_globe, + lEntry.geoPosition = cartesianCoordinatesFromGeo( + *static_cast(_globe->owner()), lEntry.latitude, lEntry.longitude, lEntry.diameter diff --git a/modules/globebrowsing/src/renderableglobe.cpp b/modules/globebrowsing/src/renderableglobe.cpp index bfa0efc870..fb5229535c 100644 --- a/modules/globebrowsing/src/renderableglobe.cpp +++ b/modules/globebrowsing/src/renderableglobe.cpp @@ -1049,7 +1049,7 @@ GeoJsonManager& RenderableGlobe::geoJsonManager() { return _geoJsonManager; } -const Ellipsoid& RenderableGlobe::ellipsoid() const { +Ellipsoid RenderableGlobe::ellipsoid() const { return _ellipsoid; } diff --git a/modules/globebrowsing/src/renderableglobe.h b/modules/globebrowsing/src/renderableglobe.h index ce2a6d95f9..ce0192e4d3 100644 --- a/modules/globebrowsing/src/renderableglobe.h +++ b/modules/globebrowsing/src/renderableglobe.h @@ -27,7 +27,6 @@ #include -#include #include #include #include @@ -41,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -114,7 +114,7 @@ public: bool renderedWithDesiredData() const override; - const Ellipsoid& ellipsoid() const; + Ellipsoid ellipsoid() const override; const LayerManager& layerManager() const; LayerManager& layerManager(); const GeoJsonManager& geoJsonManager() const; diff --git a/modules/globebrowsing/src/tileprovider/tileprovider.h b/modules/globebrowsing/src/tileprovider/tileprovider.h index 808cda1466..08522ebc21 100644 --- a/modules/globebrowsing/src/tileprovider/tileprovider.h +++ b/modules/globebrowsing/src/tileprovider/tileprovider.h @@ -28,7 +28,6 @@ #include #include -#include #include #include #include @@ -36,6 +35,7 @@ #include #include #include +#include #include #include diff --git a/modules/server/src/topics/cameratopic.cpp b/modules/server/src/topics/cameratopic.cpp index b188be35d2..e26e728ff4 100644 --- a/modules/server/src/topics/cameratopic.cpp +++ b/modules/server/src/topics/cameratopic.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include namespace { @@ -82,8 +83,7 @@ void CameraTopic::handleJson(const nlohmann::json& json) { void CameraTopic::sendCameraData() { #ifdef OPENSPACE_MODULE_SPACE_ENABLED - GlobeBrowsingModule* module = global::moduleEngine->module(); - glm::dvec3 position = module->geoPosition(); + glm::dvec3 position = geoPositionFromCamera(); std::pair altSimplified = simplifyDistance(position.z); const nlohmann::json jsonData = { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0dc5282bee..3c341a8b82 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -172,7 +172,9 @@ set(OPENSPACE_SOURCE util/collisionhelper.cpp util/coordinateconversion.cpp util/distanceconversion.cpp + util/ellipsoid.cpp util/factorymanager.cpp + util/geodetic.cpp util/httprequest.cpp util/json_helper.cpp util/keys.cpp @@ -365,8 +367,10 @@ set(OPENSPACE_HEADER ${PROJECT_SOURCE_DIR}/include/openspace/util/coordinateconversion.h ${PROJECT_SOURCE_DIR}/include/openspace/util/distanceconstants.h ${PROJECT_SOURCE_DIR}/include/openspace/util/distanceconversion.h + ${PROJECT_SOURCE_DIR}/include/openspace/util/ellipsoid.h ${PROJECT_SOURCE_DIR}/include/openspace/util/factorymanager.h ${PROJECT_SOURCE_DIR}/include/openspace/util/factorymanager.inl + ${PROJECT_SOURCE_DIR}/include/openspace/util/geodetic.h ${PROJECT_SOURCE_DIR}/include/openspace/util/httprequest.h ${PROJECT_SOURCE_DIR}/include/openspace/util/job.h ${PROJECT_SOURCE_DIR}/include/openspace/util/json_helper.h diff --git a/src/engine/openspaceengine.cpp b/src/engine/openspaceengine.cpp index 846ce21ed5..64c4119828 100644 --- a/src/engine/openspaceengine.cpp +++ b/src/engine/openspaceengine.cpp @@ -1800,13 +1800,13 @@ void setCameraFromProfile(const Profile& p) { std::string geoScript; if (geo.altitude.has_value()) { geoScript = std::format( - "openspace.globebrowsing.flyToGeo([[{}]], {}, {}, {}, 0)", + "openspace.navigation.flyToGeo([[{}]], {}, {}, {}, 0)", geo.anchor, geo.latitude, geo.longitude, *geo.altitude ); } else { geoScript = std::format( - "openspace.globebrowsing.flyToGeo2([[{}]], {}, {}, false, 0)", + "openspace.navigation.flyToGeo2([[{}]], {}, {}, false, 0)", geo.anchor, geo.latitude, geo.longitude ); } diff --git a/src/navigation/navigationhandler.cpp b/src/navigation/navigationhandler.cpp index cd8eeb4ac8..cc39cbe53d 100644 --- a/src/navigation/navigationhandler.cpp +++ b/src/navigation/navigationhandler.cpp @@ -810,7 +810,22 @@ scripting::LuaLibrary NavigationHandler::luaLibrary() { codegen::lua::SetFocus, codegen::lua::DistanceToFocus, codegen::lua::DistanceToFocusBoundingSphere, - codegen::lua::DistanceToFocusInteractionSphere + codegen::lua::DistanceToFocusInteractionSphere, + codegen::lua::JumpToGeo, + codegen::lua::GoToGeoDeprecated, + codegen::lua::FlyToGeo2, + codegen::lua::FlyToGeo, + codegen::lua::LocalPositionFromGeo, + codegen::lua::LocalPositionFromGeoDeprecated, + codegen::lua::IsFlying, + codegen::lua::FlyTo, + codegen::lua::FlyToHeight, + codegen::lua::FlyToNavigationState, + codegen::lua::ZoomToFocus, + codegen::lua::ZoomToDistance, + codegen::lua::ZoomToDistanceRelative, + codegen::lua::JumpTo, + codegen::lua::JumpToNavigationState } }; } diff --git a/src/navigation/navigationhandler_lua.inl b/src/navigation/navigationhandler_lua.inl index 6da4d6e1bb..5d7314c971 100644 --- a/src/navigation/navigationhandler_lua.inl +++ b/src/navigation/navigationhandler_lua.inl @@ -24,6 +24,8 @@ #include +#include + namespace { /** @@ -650,6 +652,657 @@ struct [[codegen::Dictionary(JoystickAxis)]] JoystickAxis { return distance - focus->interactionSphere(); } +/** + * Immediately move the camera to a geographic coordinate on a node by first fading the + * rendering to black, jump to the specified coordinate, and then fade in. If the node is + * a globe, the longitude and latitude values are expressed in the body's native + * coordinate system. If it is not, the position on the surface of the interaction sphere + * is used instead. + * + * This is done by triggering another script that handles the logic. + * + * \param node The identifier of a scene graph node. If an empty string is provided, the + * current anchor node is used + * \param latitude The latitude of the target coordinate, in degrees + * \param longitude The longitude of the target coordinate, in degrees + * \param altitude An optional altitude, given in meters over the reference surface of + * the globe. If no altitude is provided, the altitude will be kept as + * the current distance to the reference surface of the specified node + * \param fadeDuration An optional duration for the fading. If not included, the + * property in Navigation Handler will be used + */ +[[codegen::luawrap]] void jumpToGeo(std::string node, double latitude, double longitude, + std::optional altitude, + std::optional fadeDuration) +{ + using namespace openspace; + + std::string script; + + if (altitude.has_value()) { + script = std::format( + "openspace.navigation.flyToGeo('{}', {}, {}, {}, 0)", + node, latitude, longitude, *altitude + ); + } + else { + script = std::format( + "openspace.navigation.flyToGeo2('{}', {}, {}, true, 0)", + node, latitude, longitude + ); + } + + if (fadeDuration.has_value()) { + global::navigationHandler->triggerFadeToTransition( + std::move(script), + static_cast(*fadeDuration) + ); + } + else { + global::navigationHandler->triggerFadeToTransition(script); + } +} + +/** + * Immediately move the camera to a geographic coordinate on a globe. If the node is a + * globe, the longitude and latitude is expressed in the body's native coordinate system. + * If it is not, the position on the surface of the interaction sphere is used instead. + * + * \param node The identifier of a scene graph node. If an empty string is provided, the + * current anchor node is used + * \param latitude The latitude of the target coordinate, in degrees + * \param longitude The longitude of the target coordinate, in degrees + * \param altitude An optional altitude, given in meters over the reference surface of + * the globe. If no altitude is provided, the altitude will be kept as + * the current distance to the reference surface of the specified globe. + */ +[[codegen::luawrap("goToGeo")]] void goToGeoDeprecated(std::string node, double latitude, + double longitude, + std::optional altitude) +{ + LWARNINGC( + "Deprecation", + "'goToGeo' function is deprecated and should be replaced with 'jumpToGeo'" + ); + + return jumpToGeo(std::move(node), latitude, longitude, altitude, 0); +} + +void flyToGeoInternal(std::string node, double latitude, double longitude, + std::optional altitude, std::optional duration, + std::optional shouldUseUpVector) +{ + using namespace openspace; + + const SceneGraphNode* n; + if (!node.empty()) { + n = sceneGraphNode(node); + if (!n) { + throw ghoul::lua::LuaError("Unknown scene graph node: " + node); + } + } + else { + n = global::navigationHandler->orbitalNavigator().anchorNode(); + if (!n) { + throw ghoul::lua::LuaError("No anchor node is set"); + } + } + + const glm::dvec3 positionModelCoords = cartesianCoordinatesFromGeo( + *n, + latitude, + longitude, + altitude + ); + + const glm::dvec3 currentPosW = global::navigationHandler->camera()->positionVec3(); + const glm::dvec3 currentPosModelCoords = + glm::inverse(n->modelTransform()) * glm::dvec4(currentPosW, 1.0); + + constexpr double LengthEpsilon = 10.0; // meters + if (glm::distance(currentPosModelCoords, positionModelCoords) < LengthEpsilon) { + LINFOC("GlobeBrowsing", "flyToGeo: Already at the requested position"); + return; + } + + ghoul::Dictionary instruction; + instruction.setValue("TargetType", std::string("Node")); + instruction.setValue("Target", n->identifier()); + instruction.setValue("Position", positionModelCoords); + instruction.setValue("PathType", std::string("ZoomOutOverview")); + + if (duration.has_value()) { + if (*duration < 0) { + throw ghoul::lua::LuaError("Duration must be a positive value"); + } + instruction.setValue("Duration", *duration); + } + + if (shouldUseUpVector.has_value()) { + instruction.setValue("UseTargetUpDirection", *shouldUseUpVector); + + } + + global::navigationHandler->pathNavigator().createPath(instruction); + global::navigationHandler->pathNavigator().startPath(); +} + +/** + * Fly the camera to a geographic coordinate (latitude and longitude) on a globe, using + * the path navigation system. If the node is a globe, the longitude and latitude is + * expressed in the body's native coordinate system. If it is not, the position on the + * surface of the interaction sphere is used instead. + * + * The distance to fly to can either be set to be the current distance of the camera to + * the target object, or the default distance from the path navigation system. + * + * \param node The identifier of a scene graph node. If an empty string is provided, the + * current anchor node is used + * \param latitude The latitude of the target coordinate, in degrees + * \param longitude The longitude of the target coordinate, in degrees + * \param useCurrentDistance If true, use the current distance of the camera to the + * target globe when going to the specified position. If false, + * or not specified, set the distance based on the bounding + * sphere and the distance factor setting in Path Navigator + * \param duration An optional duration for the motion to take, in seconds. For example, + * a value of 5 means "fly to this position over a duration of 5 seconds" + * \param shouldUseUpVector If true, try to use the up-direction when computing the + * target position for the camera. For globes, this means that + * North should be up, in relation to the camera's view + * direction. Note that for this to take effect, rolling motions + * must be enabled in the Path Navigator settings. + */ +[[codegen::luawrap]] void flyToGeo2(std::string node, double latitude, double longitude, + std::optional useCurrentDistance, + std::optional duration, + std::optional shouldUseUpVector) +{ + using namespace openspace; + + std::optional altitude; + if (useCurrentDistance.has_value() && *useCurrentDistance) { + altitude = std::nullopt; + } + else { + altitude = global::navigationHandler->pathNavigator().defaultArrivalHeight(node); + } + + flyToGeoInternal( + node, + latitude, + longitude, + std::nullopt, + duration, + shouldUseUpVector + ); +} + + /** + * Fly the camera to a geographic coordinate (latitude, longitude and altitude) on a + * globe, using the path navigation system. If the node is a globe, the longitude and + * latitude is expressed in the body's native coordinate system. If it is not, the + * position on the surface of the interaction sphere is used instead. + * + * \param node The identifier of a scene graph node. If an empty string is provided, the + * current anchor node is used + * \param latitude The latitude of the target coordinate, in degrees + * \param longitude The longitude of the target coordinate, in degrees + * \param altitude The altitude of the target coordinate, in meters + * \param duration An optional duration for the motion to take, in seconds. For example, + * a value of 5 means "fly to this position over a duration of 5 seconds" + * \param shouldUseUpVector If true, try to use the up-direction when computing the + * target position for the camera. For globes, this means that + * North should be up, in relation to the camera's view + * direction. Note that for this to take effect, rolling motions + * must be enabled in the Path Navigator settings. + */ +[[codegen::luawrap]] void flyToGeo(std::string node, double latitude, + double longitude, double altitude, + std::optional duration, + std::optional shouldUseUpVector) +{ + flyToGeoInternal(node, latitude, longitude, altitude, duration, shouldUseUpVector); +} + +/** + * Returns the position in the local Cartesian coordinate system of the specified node + * that corresponds to the given geographic coordinates. In the local coordinate system, + * the position (0,0,0) corresponds to the globe's center. If the node is a globe, the + * longitude and latitude is expressed in the body's native coordinate system. If it is + * not, the position on the surface of the interaction sphere is used instead. + * + * \param nodeIdentifier The identifier of the scene graph node + * \param latitude The latitude of the geograpic position, in degrees + * \param longitude The longitude of the geographic position, in degrees + * \param altitude The altitude, in meters + */ +[[codegen::luawrap]] +std::tuple +localPositionFromGeo(std::string nodeIdentifier, double latitude, double longitude, + double altitude) +{ + using namespace openspace; + + SceneGraphNode* n = sceneGraphNode(nodeIdentifier); + if (!n) { + throw ghoul::lua::LuaError("Unknown globe identifier: " + nodeIdentifier); + } + + glm::vec3 p = cartesianCoordinatesFromGeo(*n, latitude, longitude, altitude); + return { p.x, p.y, p.z }; +} + +/** +* Returns the position in the local Cartesian coordinate system of the specified globe +* that corresponds to the given geographic coordinates. In the local coordinate system, +* the position (0,0,0) corresponds to the globe's center. If the node is a globe, the +* longitude and latitude is expressed in the body's native coordinate system. If it is +* not, the position on the surface of the interaction sphere is used instead. +* +* Deprecated in favor of `localPositionFromGeo`. +* +* \param globeIdentifier The identifier of the scene graph node +* \param latitude The latitude of the geograpic position, in degrees +* \param longitude The longitude of the geographic position, in degrees +* \param altitude The altitude, in meters +*/ +[[codegen::luawrap("getLocalPositionFromGeo")]] +std::tuple +localPositionFromGeoDeprecated(std::string nodeIdentifier, double latitude, + double longitude, double altitude) +{ + LWARNINGC( + "Deprecation", + "'getLocalPositionFromGeo' function is deprecated and should be replaced with " + "'localPositionFromGeo'" + ); + + return localPositionFromGeo(std::move(nodeIdentifier), latitude, longitude, altitude); +} + +/** + * Returns true if a camera path is currently running, and false otherwise. + * + * \return Whether a camera path is currently active, or not + */ +[[codegen::luawrap]] bool isFlying() { + using namespace openspace; + return global::openSpaceEngine->currentMode() == OpenSpaceEngine::Mode::CameraPath; +} + +/** + * Move the camera to the node with the specified identifier. The optional double + * specifies the duration of the motion, in seconds. If the optional bool is set to true + * the target up vector for camera is set based on the target node. Either of the optional + * parameters can be left out. + * + * \param nodeIdentifier The identifier of the node to which we want to fly + * \param useUpFromTargetOrDuration If this value is a boolean value (`true` or `false`), + * this value determines whether we want to end up with the camera facing along the + * selected node's up direction. If this value is a numerical value, refer to the + * documnentation of the `duration` parameter + * \param duration The duration (in seconds) how long the flying to the selected node + * should take. If this value is left out, a sensible default value is uses, which + * can be configured in the engine + */ +[[codegen::luawrap]] void flyTo(std::string nodeIdentifier, + std::optional> useUpFromTargetOrDuration, + std::optional duration) +{ + using namespace openspace; + if (useUpFromTargetOrDuration.has_value() && + std::holds_alternative(*useUpFromTargetOrDuration) && + duration.has_value()) + { + throw ghoul::lua::LuaError("Duration cannot be specified twice"); + } + + if (!sceneGraphNode(nodeIdentifier)) { + throw ghoul::lua::LuaError("Unknown node name: " + nodeIdentifier); + } + + ghoul::Dictionary insDict; + insDict.setValue("TargetType", std::string("Node")); + insDict.setValue("Target", nodeIdentifier); + if (useUpFromTargetOrDuration.has_value()) { + if (std::holds_alternative(*useUpFromTargetOrDuration)) { + insDict.setValue( + "UseTargetUpDirection", + std::get(*useUpFromTargetOrDuration) + ); + } + else { + double d = std::get(*useUpFromTargetOrDuration); + if (d < 0.0) { + throw ghoul::lua::LuaError("Duration must be a positive value"); + } + insDict.setValue("Duration", d); + } + } + if (duration.has_value()) { + double d = *duration; + if (d < 0.0) { + throw ghoul::lua::LuaError("Duration must be a positive value"); + } + insDict.setValue("Duration", d); + } + + global::navigationHandler->pathNavigator().createPath(insDict); + + if (global::navigationHandler->pathNavigator().hasCurrentPath()) { + global::navigationHandler->pathNavigator().startPath(); + } +} + +/** + * Move the camera to the node with the specified identifier. The second argument is the + * desired target height above the target node's bounding sphere, in meters. The optional + * double specifies the duration of the motion, in seconds. If the optional bool is set to + * true, the target up vector for camera is set based on the target node. Either of the + * optional parameters can be left out. + * + * \param nodeIdentifier The identifier of the node to which we want to fly + * \param height The height (in meters) to which we want to fly. The way the height is + * defined specifically determines on the type of node to which the fly-to command + * is pointed. + * \param useUpFromTargetOrDuration If this value is a boolean value (`true` or `false`), + * this value determines whether we want to end up with the camera facing along the + * selected node's up direction. If this value is a numerical value, refer to the + * documnentation of the `duration` parameter + * \param duration The duration (in seconds) how long the flying to the selected node + * should take. If this value is left out, a sensible default value is uses, which + * can be configured in the engine + */ +[[codegen::luawrap]] void flyToHeight(std::string nodeIdentifier, double height, + std::optional> useUpFromTargetOrDuration, + std::optional duration) +{ + using namespace openspace; + if (!sceneGraphNode(nodeIdentifier)) { + throw ghoul::lua::LuaError("Unknown node name: " + nodeIdentifier); + } + + ghoul::Dictionary insDict; + insDict.setValue("TargetType", std::string("Node")); + insDict.setValue("Target", nodeIdentifier); + insDict.setValue("Height", height); + if (useUpFromTargetOrDuration.has_value()) { + if (std::holds_alternative(*useUpFromTargetOrDuration)) { + insDict.setValue( + "UseTargetUpDirection", + std::get(*useUpFromTargetOrDuration) + ); + } + else { + double d = std::get(*useUpFromTargetOrDuration); + if (d < 0.0) { + throw ghoul::lua::LuaError("Duration must be a positive value"); + } + insDict.setValue("Duration", d); + } + } + if (duration.has_value()) { + double d = *duration; + if (d < 0.0) { + throw ghoul::lua::LuaError("Duration must be a positive value"); + } + insDict.setValue("Duration", d); + } + + global::navigationHandler->pathNavigator().createPath(insDict); + + if (global::navigationHandler->pathNavigator().hasCurrentPath()) { + global::navigationHandler->pathNavigator().startPath(); + } +} + +/** + * Create a path to the navigation state described by the input table. Note that roll must + * be included for the target up direction in the navigation state to be taken into + * account. + * + * \param navigationState A [NavigationState](#core_navigation_state) to fly to + * \param duration An optional duration for the motion to take, in seconds. For example, + * a value of 5 means "fly to this position over a duration of 5 seconds" + */ +[[codegen::luawrap]] void flyToNavigationState(ghoul::Dictionary navigationState, + std::optional duration) +{ + using namespace openspace; + try { + documentation::testSpecificationAndThrow( + interaction::NavigationState::Documentation(), + navigationState, + "NavigationState" + ); + } + catch (const documentation::SpecificationError& e) { + logError(e, "flyToNavigationState"); + throw ghoul::lua::LuaError(std::format("Unable to create a path: {}", e.what())); + } + + ghoul::Dictionary instruction; + instruction.setValue("TargetType", std::string("NavigationState")); + instruction.setValue("NavigationState", navigationState); + + if (duration.has_value()) { + double d = *duration; + if (d < 0.0) { + throw ghoul::lua::LuaError("Duration must be a positive value"); + } + instruction.setValue("Duration", d); + } + + global::navigationHandler->pathNavigator().createPath(instruction); + + if (global::navigationHandler->pathNavigator().hasCurrentPath()) { + global::navigationHandler->pathNavigator().startPath(); + } +} + +/** + * Zoom linearly to the current focus node, using the default distance. + * + * \param duration An optional duration for the motion to take, in seconds. For example, + * a value of 5 means "zoom in over 5 seconds" + */ +[[codegen::luawrap]] void zoomToFocus(std::optional duration) { + using namespace openspace; + const SceneGraphNode* node = global::navigationHandler->anchorNode(); + if (!node) { + throw ghoul::lua::LuaError("Could not determine current focus node"); + } + + ghoul::Dictionary insDict; + insDict.setValue("TargetType", std::string("Node")); + insDict.setValue("Target", node->identifier()); + insDict.setValue("PathType", std::string("Linear")); + + if (duration.has_value()) { + double d = *duration; + if (d < 0.0) { + throw ghoul::lua::LuaError("Duration must be a positive value"); + } + insDict.setValue("Duration", d); + } + + global::navigationHandler->pathNavigator().createPath(insDict); + + if (global::navigationHandler->pathNavigator().hasCurrentPath()) { + global::navigationHandler->pathNavigator().startPath(); + } +} + +/** + * Fly linearly to a specific distance in relation to the focus node. + * + * \param distance The distance to fly to, in meters above the bounding sphere. + * \param duration An optional duration for the motion to take, in seconds. + */ +[[codegen::luawrap]] void zoomToDistance(double distance, std::optional duration) +{ + using namespace openspace; + if (distance <= 0.0) { + throw ghoul::lua::LuaError("The distance must be larger than zero"); + } + + const SceneGraphNode* node = global::navigationHandler->anchorNode(); + if (!node) { + throw ghoul::lua::LuaError("Could not determine current focus node"); + } + + ghoul::Dictionary insDict; + insDict.setValue("TargetType", std::string("Node")); + insDict.setValue("Target", node->identifier()); + insDict.setValue("Height", distance); + insDict.setValue("PathType", std::string("Linear")); + + if (duration.has_value()) { + double d = *duration; + if (d < 0.0) { + throw ghoul::lua::LuaError("Duration must be a positive value"); + } + insDict.setValue("Duration", d); + } + + global::navigationHandler->pathNavigator().createPath(insDict); + + if (global::navigationHandler->pathNavigator().hasCurrentPath()) { + global::navigationHandler->pathNavigator().startPath(); + } +} + +/** + * Fly linearly to a specific distance in relation to the focus node, given as a relative + * value based on the size of the object rather than in meters. + * + * \param distance The distance to fly to, given as a multiple of the bounding sphere of + * the current focus node bounding sphere. A value of 1 will result in a + * position at a distance of one times the size of the bounding + * sphere away from the object. + * \param duration An optional duration for the motion, in seconds. + */ +[[codegen::luawrap]] void zoomToDistanceRelative(double distance, + std::optional duration) +{ + using namespace openspace; + if (distance <= 0.0) { + throw ghoul::lua::LuaError("The distance must be larger than zero"); + } + + const SceneGraphNode* node = global::navigationHandler->anchorNode(); + if (!node) { + throw ghoul::lua::LuaError("Could not determine current focus node"); + } + + distance *= node->boundingSphere(); + + ghoul::Dictionary insDict; + insDict.setValue("TargetType", std::string("Node")); + insDict.setValue("Target", node->identifier()); + insDict.setValue("Height", distance); + insDict.setValue("PathType", std::string("Linear")); + + if (duration.has_value()) { + double d = *duration; + if (d < 0.0) { + throw ghoul::lua::LuaError("Duration must be a positive value"); + } + insDict.setValue("Duration", d); + } + + global::navigationHandler->pathNavigator().createPath(insDict); + + if (global::navigationHandler->pathNavigator().hasCurrentPath()) { + global::navigationHandler->pathNavigator().startPath(); + } +} + +/** + * Fade rendering to black, jump to the specified node, and then fade in. This is done by + * triggering another script that handles the logic. + * + * \param navigationState A [NavigationState](#core_navigation_state) to jump to. + * \param useTimeStamp if true, and the provided NavigationState includes a timestamp, + * the time will be set as well. + * \param fadeDuration An optional duration for the fading. If not included, the + * property in Navigation Handler will be used. + */ +[[codegen::luawrap]] void jumpToNavigationState(ghoul::Dictionary navigationState, + std::optional useTimeStamp, + std::optional fadeDuration) +{ + using namespace openspace; + try { + documentation::testSpecificationAndThrow( + interaction::NavigationState::Documentation(), + navigationState, + "NavigationState" + ); + } + catch (const documentation::SpecificationError& e) { + logError(e, "jumpToNavigationState"); + throw ghoul::lua::LuaError(std::format( + "Unable to jump to navigation state: {}", e.what() + )); + } + + // When copy pasting a printed navigation state from the console, the formatting of + // the navigation state dictionary won't be completely correct if using the + // dictionary directly, due to the number keys for arrays. We solve this by first + // creating an object of the correct datatype + // (@TODO emmbr 2024-04-03, This formatting problem should probably be fixed) + interaction::NavigationState ns = interaction::NavigationState(navigationState); + + bool setTime = (ns.timestamp.has_value() && useTimeStamp.value_or(false)); + + const std::string script = std::format( + "openspace.navigation.setNavigationState({}, {})", + ghoul::formatLua(ns.dictionary()), setTime + ); + + if (fadeDuration.has_value()) { + global::navigationHandler->triggerFadeToTransition( + std::move(script), + static_cast(*fadeDuration) + ); + } + else { + global::navigationHandler->triggerFadeToTransition(script); + } +} + +/** + * Fade rendering to black, jump to the specified navigation state, and then fade in. + * This is done by triggering another script that handles the logic. + * + * \param nodeIdentifier The identifier of the scene graph node to jump to + * \param fadeDuration An optional duration for the fading. If not included, the + * property in Navigation Handler will be used + */ +[[codegen::luawrap]] void jumpTo(std::string nodeIdentifier, + std::optional fadeDuration) +{ + using namespace openspace; + if (SceneGraphNode* n = sceneGraphNode(nodeIdentifier); !n) { + throw ghoul::lua::LuaError("Unknown node name: " + nodeIdentifier); + } + + const std::string script = std::format( + "openspace.navigation.flyTo('{}', 0)", nodeIdentifier + ); + + if (fadeDuration.has_value()) { + global::navigationHandler->triggerFadeToTransition( + std::move(script), + static_cast(*fadeDuration) + ); + } + else { + global::navigationHandler->triggerFadeToTransition(script); + } +} + #include "navigationhandler_lua_codegen.cpp" } // namespace diff --git a/src/navigation/pathnavigator.cpp b/src/navigation/pathnavigator.cpp index 7928c452bd..5fc9c75306 100644 --- a/src/navigation/pathnavigator.cpp +++ b/src/navigation/pathnavigator.cpp @@ -587,19 +587,10 @@ scripting::LuaLibrary PathNavigator::luaLibrary() { return { "pathnavigation", { - codegen::lua::IsFlying, codegen::lua::ContinuePath, codegen::lua::PausePath, codegen::lua::StopPath, codegen::lua::SkipToEnd, - codegen::lua::FlyTo, - codegen::lua::FlyToHeight, - codegen::lua::FlyToNavigationState, - codegen::lua::ZoomToFocus, - codegen::lua::ZoomToDistance, - codegen::lua::ZoomToDistanceRelative, - codegen::lua::JumpTo, - codegen::lua::JumpToNavigationState, codegen::lua::CreatePath } }; diff --git a/src/navigation/pathnavigator_lua.inl b/src/navigation/pathnavigator_lua.inl index 43307dc5ef..b931699967 100644 --- a/src/navigation/pathnavigator_lua.inl +++ b/src/navigation/pathnavigator_lua.inl @@ -26,16 +26,6 @@ namespace { -/** - * Returns true if a camera path is currently running, and false otherwise. - * - * \return Whether a camera path is currently active, or not - */ -[[codegen::luawrap]] bool isFlying() { - using namespace openspace; - return global::openSpaceEngine->currentMode() == OpenSpaceEngine::Mode::CameraPath; -} - /** * Continue playing a paused camera path. */ @@ -64,358 +54,6 @@ namespace { openspace::global::navigationHandler->pathNavigator().skipToEnd(); } -/** - * Move the camera to the node with the specified identifier. The optional double - * specifies the duration of the motion, in seconds. If the optional bool is set to true - * the target up vector for camera is set based on the target node. Either of the optional - * parameters can be left out. - */ -[[codegen::luawrap]] void flyTo(std::string nodeIdentifier, - std::optional> useUpFromTargetOrDuration, - std::optional duration) -{ - using namespace openspace; - if (useUpFromTargetOrDuration.has_value() && - std::holds_alternative(*useUpFromTargetOrDuration) && - duration.has_value()) - { - throw ghoul::lua::LuaError("Duration cannot be specified twice"); - } - - if (!sceneGraphNode(nodeIdentifier)) { - throw ghoul::lua::LuaError("Unknown node name: " + nodeIdentifier); - } - - ghoul::Dictionary insDict; - insDict.setValue("TargetType", std::string("Node")); - insDict.setValue("Target", nodeIdentifier); - if (useUpFromTargetOrDuration.has_value()) { - if (std::holds_alternative(*useUpFromTargetOrDuration)) { - insDict.setValue( - "UseTargetUpDirection", - std::get(*useUpFromTargetOrDuration) - ); - } - else { - double d = std::get(*useUpFromTargetOrDuration); - if (d < 0.0) { - throw ghoul::lua::LuaError("Duration must be a positive value"); - } - insDict.setValue("Duration", d); - } - } - if (duration.has_value()) { - double d = *duration; - if (d < 0.0) { - throw ghoul::lua::LuaError("Duration must be a positive value"); - } - insDict.setValue("Duration", d); - } - - global::navigationHandler->pathNavigator().createPath(insDict); - - if (global::navigationHandler->pathNavigator().hasCurrentPath()) { - global::navigationHandler->pathNavigator().startPath(); - } -} - -/** - * Move the camera to the node with the specified identifier. The second argument is the - * desired target height above the target node's bounding sphere, in meters. The optional - * double specifies the duration of the motion, in seconds. If the optional bool is set to - * true, the target up vector for camera is set based on the target node. Either of the - * optional parameters can be left out. - */ -[[codegen::luawrap]] void flyToHeight(std::string nodeIdentifier, double height, - std::optional> useUpFromTargetOrDuration, - std::optional duration) -{ - using namespace openspace; - if (!sceneGraphNode(nodeIdentifier)) { - throw ghoul::lua::LuaError("Unknown node name: " + nodeIdentifier); - } - - ghoul::Dictionary insDict; - insDict.setValue("TargetType", std::string("Node")); - insDict.setValue("Target", nodeIdentifier); - insDict.setValue("Height", height); - if (useUpFromTargetOrDuration.has_value()) { - if (std::holds_alternative(*useUpFromTargetOrDuration)) { - insDict.setValue( - "UseTargetUpDirection", - std::get(*useUpFromTargetOrDuration) - ); - } - else { - double d = std::get(*useUpFromTargetOrDuration); - if (d < 0.0) { - throw ghoul::lua::LuaError("Duration must be a positive value"); - } - insDict.setValue("Duration", d); - } - } - if (duration.has_value()) { - double d = *duration; - if (d < 0.0) { - throw ghoul::lua::LuaError("Duration must be a positive value"); - } - insDict.setValue("Duration", d); - } - - global::navigationHandler->pathNavigator().createPath(insDict); - - if (global::navigationHandler->pathNavigator().hasCurrentPath()) { - global::navigationHandler->pathNavigator().startPath(); - } -} - -/** - * Create a path to the navigation state described by the input table. Note that roll must - * be included for the target up direction in the navigation state to be taken into - * account. - * - * \param navigationState A [NavigationState](#core_navigation_state) to fly to - * \param duration An optional duration for the motion to take, in seconds. For example, - * a value of 5 means "fly to this position over a duration of 5 seconds" - */ -[[codegen::luawrap]] void flyToNavigationState(ghoul::Dictionary navigationState, - std::optional duration) -{ - using namespace openspace; - try { - documentation::testSpecificationAndThrow( - interaction::NavigationState::Documentation(), - navigationState, - "NavigationState" - ); - } - catch (const documentation::SpecificationError& e) { - logError(e, "flyToNavigationState"); - throw ghoul::lua::LuaError(std::format("Unable to create a path: {}", e.what())); - } - - ghoul::Dictionary instruction; - instruction.setValue("TargetType", std::string("NavigationState")); - instruction.setValue("NavigationState", navigationState); - - if (duration.has_value()) { - double d = *duration; - if (d < 0.0) { - throw ghoul::lua::LuaError("Duration must be a positive value"); - } - instruction.setValue("Duration", d); - } - - global::navigationHandler->pathNavigator().createPath(instruction); - - if (global::navigationHandler->pathNavigator().hasCurrentPath()) { - global::navigationHandler->pathNavigator().startPath(); - } -} - -/** - * Zoom linearly to the current focus node, using the default distance. - * - * \param duration An optional duration for the motion to take, in seconds. For example, - * a value of 5 means "zoom in over 5 seconds" - */ -[[codegen::luawrap]] void zoomToFocus(std::optional duration) { - using namespace openspace; - const SceneGraphNode* node = global::navigationHandler->anchorNode(); - if (!node) { - throw ghoul::lua::LuaError("Could not determine current focus node"); - } - - ghoul::Dictionary insDict; - insDict.setValue("TargetType", std::string("Node")); - insDict.setValue("Target", node->identifier()); - insDict.setValue("PathType", std::string("Linear")); - - if (duration.has_value()) { - double d = *duration; - if (d < 0.0) { - throw ghoul::lua::LuaError("Duration must be a positive value"); - } - insDict.setValue("Duration", d); - } - - global::navigationHandler->pathNavigator().createPath(insDict); - - if (global::navigationHandler->pathNavigator().hasCurrentPath()) { - global::navigationHandler->pathNavigator().startPath(); - } -} - -/** - * Fly linearly to a specific distance in relation to the focus node. - * - * \param distance The distance to fly to, in meters above the bounding sphere. - * \param duration An optional duration for the motion to take, in seconds. - */ -[[codegen::luawrap]] void zoomToDistance(double distance, std::optional duration) -{ - using namespace openspace; - if (distance <= 0.0) { - throw ghoul::lua::LuaError("The distance must be larger than zero"); - } - - const SceneGraphNode* node = global::navigationHandler->anchorNode(); - if (!node) { - throw ghoul::lua::LuaError("Could not determine current focus node"); - } - - ghoul::Dictionary insDict; - insDict.setValue("TargetType", std::string("Node")); - insDict.setValue("Target", node->identifier()); - insDict.setValue("Height", distance); - insDict.setValue("PathType", std::string("Linear")); - - if (duration.has_value()) { - double d = *duration; - if (d < 0.0) { - throw ghoul::lua::LuaError("Duration must be a positive value"); - } - insDict.setValue("Duration", d); - } - - global::navigationHandler->pathNavigator().createPath(insDict); - - if (global::navigationHandler->pathNavigator().hasCurrentPath()) { - global::navigationHandler->pathNavigator().startPath(); - } -} - -/** - * Fly linearly to a specific distance in relation to the focus node, given as a relative - * value based on the size of the object rather than in meters. - * - * \param distance The distance to fly to, given as a multiple of the bounding sphere of - * the current focus node bounding sphere. A value of 1 will result in a - * position at a distance of one times the size of the bounding - * sphere away from the object. - * \param duration An optional duration for the motion, in seconds. - */ -[[codegen::luawrap]] void zoomToDistanceRelative(double distance, - std::optional duration) -{ - using namespace openspace; - if (distance <= 0.0) { - throw ghoul::lua::LuaError("The distance must be larger than zero"); - } - - const SceneGraphNode* node = global::navigationHandler->anchorNode(); - if (!node) { - throw ghoul::lua::LuaError("Could not determine current focus node"); - } - - distance *= node->boundingSphere(); - - ghoul::Dictionary insDict; - insDict.setValue("TargetType", std::string("Node")); - insDict.setValue("Target", node->identifier()); - insDict.setValue("Height", distance); - insDict.setValue("PathType", std::string("Linear")); - - if (duration.has_value()) { - double d = *duration; - if (d < 0.0) { - throw ghoul::lua::LuaError("Duration must be a positive value"); - } - insDict.setValue("Duration", d); - } - - global::navigationHandler->pathNavigator().createPath(insDict); - - if (global::navigationHandler->pathNavigator().hasCurrentPath()) { - global::navigationHandler->pathNavigator().startPath(); - } -} - -/** - * Fade rendering to black, jump to the specified node, and then fade in. This is done by - * triggering another script that handles the logic. - * - * \param navigationState A [NavigationState](#core_navigation_state) to jump to. - * \param useTimeStamp if true, and the provided NavigationState includes a timestamp, - * the time will be set as well. - * \param fadeDuration An optional duration for the fading. If not included, the - * property in Navigation Handler will be used. - */ -[[codegen::luawrap]] void jumpToNavigationState(ghoul::Dictionary navigationState, - std::optional useTimeStamp, - std::optional fadeDuration) -{ - using namespace openspace; - try { - documentation::testSpecificationAndThrow( - interaction::NavigationState::Documentation(), - navigationState, - "NavigationState" - ); - } - catch (const documentation::SpecificationError& e) { - logError(e, "jumpToNavigationState"); - throw ghoul::lua::LuaError(std::format( - "Unable to jump to navigation state: {}", e.what() - )); - } - - // When copy pasting a printed navigation state from the console, the formatting of - // the navigation state dictionary won't be completely correct if using the - // dictionary directly, due to the number keys for arrays. We solve this by first - // creating an object of the correct datatype - // (@TODO emmbr 2024-04-03, This formatting problem should probably be fixed) - interaction::NavigationState ns = interaction::NavigationState(navigationState); - - bool setTime = (ns.timestamp.has_value() && useTimeStamp.value_or(false)); - - const std::string script = std::format( - "openspace.navigation.setNavigationState({}, {})", - ghoul::formatLua(ns.dictionary()), setTime - ); - - if (fadeDuration.has_value()) { - global::navigationHandler->triggerFadeToTransition( - std::move(script), - static_cast(*fadeDuration) - ); - } - else { - global::navigationHandler->triggerFadeToTransition(script); - } -} - -/** - * Fade rendering to black, jump to the specified navigation state, and then fade in. - * This is done by triggering another script that handles the logic. - * - * \param nodeIdentifier The identifier of the scene graph node to jump to - * \param fadeDuration An optional duration for the fading. If not included, the - * property in Navigation Handler will be used - */ -[[codegen::luawrap]] void jumpTo(std::string nodeIdentifier, - std::optional fadeDuration) -{ - using namespace openspace; - if (SceneGraphNode* n = sceneGraphNode(nodeIdentifier); !n) { - throw ghoul::lua::LuaError("Unknown node name: " + nodeIdentifier); - } - - const std::string script = std::format( - "openspace.pathnavigation.flyTo('{}', 0)", nodeIdentifier - ); - - if (fadeDuration.has_value()) { - global::navigationHandler->triggerFadeToTransition( - std::move(script), - static_cast(*fadeDuration) - ); - } - else { - global::navigationHandler->triggerFadeToTransition(script); - } -} - /** * Create a camera path as described by the instruction in the input argument. * diff --git a/src/rendering/renderable.cpp b/src/rendering/renderable.cpp index a567719d0b..4bd7d3ff83 100644 --- a/src/rendering/renderable.cpp +++ b/src/rendering/renderable.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -259,6 +260,10 @@ SurfacePositionHandle Renderable::calculateSurfacePositionHandle( }; } +Ellipsoid Renderable::ellipsoid() const { + return Ellipsoid(glm::dvec3(_interactionSphere)); +} + bool Renderable::renderedWithDesiredData() const { return true; } diff --git a/src/scene/scenegraphnode.cpp b/src/scene/scenegraphnode.cpp index 68fd47c29b..8801fbb9e0 100644 --- a/src/scene/scenegraphnode.cpp +++ b/src/scene/scenegraphnode.cpp @@ -1316,6 +1316,15 @@ const std::vector& SceneGraphNode::onExitAction() const { return _onExitAction; } +Ellipsoid SceneGraphNode::ellipsoid() const { + if (_renderable) { + return _renderable->ellipsoid(); + } + else { + return Ellipsoid(glm::dvec3(interactionSphere())); + } +} + double SceneGraphNode::boundingSphere() const { if (_overrideBoundingSphere.has_value()) { return glm::compMax(scale() * *_overrideBoundingSphere); diff --git a/modules/globebrowsing/src/ellipsoid.cpp b/src/util/ellipsoid.cpp similarity index 97% rename from modules/globebrowsing/src/ellipsoid.cpp rename to src/util/ellipsoid.cpp index 286a546961..32585a853f 100644 --- a/modules/globebrowsing/src/ellipsoid.cpp +++ b/src/util/ellipsoid.cpp @@ -22,9 +22,9 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#include +#include -#include +#include #include #include #include @@ -33,7 +33,7 @@ namespace { constexpr size_t MaxIterations = 8; } // namespace -namespace openspace::globebrowsing { +namespace openspace { Ellipsoid::Ellipsoid(glm::dvec3 radii) : _radii(std::move(radii)) { updateInternalCache(); @@ -174,4 +174,4 @@ Ellipsoid::shadowConfigurationArray() const return _shadowConfArray; } -} // namespace openspace::globebrowsing +} // namespace openspace diff --git a/src/util/geodetic.cpp b/src/util/geodetic.cpp new file mode 100644 index 0000000000..3d3038c16f --- /dev/null +++ b/src/util/geodetic.cpp @@ -0,0 +1,130 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2025 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace openspace { + +void goToGeodetic2(const SceneGraphNode& globe, Geodetic2 geo) { + const double altitude = altitudeFromCamera(globe); + + goToGeodetic3(globe, { std::move(geo), altitude }); +} + +void goToGeodetic3(const SceneGraphNode& sgn, Geodetic3 geo) { + const glm::dvec3 positionModelSpace = sgn.ellipsoid().cartesianPosition(geo); + + interaction::NavigationState state; + state.anchor = sgn.identifier(); + state.referenceFrame = sgn.identifier(); + state.position = positionModelSpace; + // For globes, we know that the up-direction will always be positive Z. + // @TODO (2023-12-06 emmbr) Eventually, we want each scene graph node to be aware of + // its own preferred up-direction. At that time, this should no longer be hardcoded + state.up = glm::dvec3(0.0, 0.0, 1.0); + + global::navigationHandler->setNavigationStateNextFrame(state); +} + +glm::vec3 cartesianCoordinatesFromGeo(const SceneGraphNode& sgn, double latitude, + double longitude, std::optional altitude) +{ + const Geodetic3 pos = { + {.lat = glm::radians(latitude), .lon = glm::radians(longitude) }, + altitude.value_or(altitudeFromCamera(sgn)) + }; + return glm::vec3(sgn.ellipsoid().cartesianPosition(pos)); +} + +glm::dvec3 geoPositionFromCamera() { + const SceneGraphNode* n = global::navigationHandler->orbitalNavigator().anchorNode(); + if (!n) { + return glm::dvec3(0.0); + } + const Renderable* renderable = n->renderable(); + if (!renderable) { + return glm::dvec3(0.0); + } + + const glm::dvec3 cameraPosition = global::navigationHandler->camera()->positionVec3(); + const glm::dmat4 inverseModelTransform = glm::inverse(n->modelTransform()); + const glm::dvec3 cameraPositionModelSpace = + glm::dvec3(inverseModelTransform * glm::dvec4(cameraPosition, 1.0)); + const SurfacePositionHandle posHandle = renderable->calculateSurfacePositionHandle( + cameraPositionModelSpace + ); + + const Geodetic2 geo2 = renderable->ellipsoid().cartesianToGeodetic2( + posHandle.centerToReferenceSurface + ); + + const double lat = glm::degrees(geo2.lat); + const double lon = glm::degrees(geo2.lon); + + double altitude = glm::length( + cameraPositionModelSpace - posHandle.centerToReferenceSurface + ); + + if (glm::length(cameraPositionModelSpace) < + glm::length(posHandle.centerToReferenceSurface)) + { + altitude = -altitude; + } + + return glm::dvec3(lat, lon, altitude); +} + +double altitudeFromCamera(const SceneGraphNode& sgn, bool useHeightMap) { + const glm::dvec3 cameraPosition = global::navigationHandler->camera()->positionVec3(); + + const glm::dmat4 inverseModelTransform = glm::inverse(sgn.modelTransform()); + + const glm::dvec3 cameraPositionModelSpace = + glm::dvec3(inverseModelTransform * glm::dvec4(cameraPosition, 1.0)); + + SurfacePositionHandle posHandle = sgn.calculateSurfacePositionHandle( + cameraPositionModelSpace + ); + + if (useHeightMap) { + const glm::dvec3 centerToActualSurface = posHandle.centerToReferenceSurface + + posHandle.referenceSurfaceOutDirection * posHandle.heightToSurface; + + return glm::length(cameraPositionModelSpace - centerToActualSurface); + } + else { + // Do not use height map => compute distance to reference surface + return glm::length(cameraPositionModelSpace - posHandle.centerToReferenceSurface); + } +} + +} // namespace openspace diff --git a/tests/test_latlonpatch.cpp b/tests/test_latlonpatch.cpp index 63786ea846..b9cef1de73 100644 --- a/tests/test_latlonpatch.cpp +++ b/tests/test_latlonpatch.cpp @@ -26,6 +26,7 @@ #include #include +#include #include TEST_CASE("LatLonPatch: findCenterControlPoint", "[latlonpatch]") { @@ -35,6 +36,7 @@ TEST_CASE("LatLonPatch: findCenterControlPoint", "[latlonpatch]") { } TEST_CASE("LatLonPatch: Find Closest Corner", "[latlonpatch]") { + using namespace openspace; using namespace openspace::globebrowsing; constexpr float piOver4 = glm::pi() / 4.f; @@ -53,6 +55,7 @@ TEST_CASE("LatLonPatch: Find Closest Corner", "[latlonpatch]") { } TEST_CASE("LatLonPatch: Find Closest Corner 2", "[latlonpatch]") { + using namespace openspace; using namespace openspace::globebrowsing; constexpr float piOver6 = glm::pi() / 4.f; diff --git a/tests/test_sessionrecording.cpp b/tests/test_sessionrecording.cpp index 8b8d0bbcbd..6b156ebeac 100644 --- a/tests/test_sessionrecording.cpp +++ b/tests/test_sessionrecording.cpp @@ -449,7 +449,7 @@ TEST_CASE("SessionRecording: 01.00 Ascii Linux", "[sessionrecording]") { CHECK(e.simulationTime == 762933562.877); REQUIRE(std::holds_alternative(e.value)); const auto& script = std::get(e.value); - CHECK(script == "openspace.pathnavigation.flyTo(\"Mars\")"); + CHECK(script == "openspace.navigation.flyTo(\"Mars\")"); } { const SessionRecording::Entry& e = rec.entries[4]; From ad786f7d5c8104d14adf4166d11296d0133bc5fb Mon Sep 17 00:00:00 2001 From: Anders Lundkvist <57524362+lundkvistarn@users.noreply.github.com> Date: Wed, 23 Apr 2025 10:44:24 -0400 Subject: [PATCH 06/10] Add RenderableSwitch class (#3597) --- .../renderableswitch/switch-far.asset | 30 +++ .../renderableswitch/switch-near.asset | 29 +++ .../renderable/renderableswitch/switch.asset | 36 ++++ modules/base/CMakeLists.txt | 2 + modules/base/basemodule.cpp | 3 + modules/base/rendering/renderableswitch.cpp | 195 ++++++++++++++++++ modules/base/rendering/renderableswitch.h | 62 ++++++ modules/kameleon/ext/kameleon | 2 +- 8 files changed, 358 insertions(+), 1 deletion(-) create mode 100644 data/assets/examples/renderable/renderableswitch/switch-far.asset create mode 100644 data/assets/examples/renderable/renderableswitch/switch-near.asset create mode 100644 data/assets/examples/renderable/renderableswitch/switch.asset create mode 100644 modules/base/rendering/renderableswitch.cpp create mode 100644 modules/base/rendering/renderableswitch.h diff --git a/data/assets/examples/renderable/renderableswitch/switch-far.asset b/data/assets/examples/renderable/renderableswitch/switch-far.asset new file mode 100644 index 0000000000..2524b76e9e --- /dev/null +++ b/data/assets/examples/renderable/renderableswitch/switch-far.asset @@ -0,0 +1,30 @@ +-- Only Far Renderable +-- This example uses only shows a textured plane when the camera is further than the +-- specified distance from the object and shows nothing if the camera is closer than that +-- distance + +local Node = { + Identifier = "RenderableSwitch_Example-Far", + Renderable = { + Type = "RenderableSwitch", + RenderableFar = { + Type = "RenderablePlaneImageLocal", + Size = 300000000000, + Texture = openspace.absPath("${DATA}/test.jpg") + }, + DistanceThreshold = 3000000000000 + }, + GUI = { + Name = "RenderableSwitch - Far", + Path = "/Examples" + } +} + +asset.onInitialize(function() + openspace.addSceneGraphNode(Node) +end) + +asset.onDeinitialize(function() + openspace.removeSceneGraphNode(Node) +end) + diff --git a/data/assets/examples/renderable/renderableswitch/switch-near.asset b/data/assets/examples/renderable/renderableswitch/switch-near.asset new file mode 100644 index 0000000000..6100238ec8 --- /dev/null +++ b/data/assets/examples/renderable/renderableswitch/switch-near.asset @@ -0,0 +1,29 @@ +-- Only Near Renderable +-- This example uses only shows a textured plane when the camera is within the specified +-- distance from the object and shows nothing if the camera is further away. + +local Node = { + Identifier = "RenderableSwitch_Example-Near", + Renderable = { + Type = "RenderableSwitch", + RenderableNear = { + Type = "RenderablePlaneImageLocal", + Size = 300000000000, + Texture = openspace.absPath("${DATA}/test.jpg") + }, + DistanceThreshold = 2000000000000 + }, + GUI = { + Name = "RenderableSwitch - Near", + Path = "/Examples" + } +} + +asset.onInitialize(function() + openspace.addSceneGraphNode(Node) +end) + +asset.onDeinitialize(function() + openspace.removeSceneGraphNode(Node) +end) + diff --git a/data/assets/examples/renderable/renderableswitch/switch.asset b/data/assets/examples/renderable/renderableswitch/switch.asset new file mode 100644 index 0000000000..b3fa31ed0c --- /dev/null +++ b/data/assets/examples/renderable/renderableswitch/switch.asset @@ -0,0 +1,36 @@ +-- Basic +-- This example shows how to create a renderable that switches between two textured planes +-- in 3D space, where one texture is loaded from a local file on disk and the other is +-- loaded from the internet though a web URL. +-- The switch is done based on the distance from the camera to the renderable. + +local Node = { + Identifier = "RenderableSwitch_Example", + Renderable = { + Type = "RenderableSwitch", + RenderableNear = { + Type = "RenderablePlaneImageLocal", + Size = 300000000000, + Texture = openspace.absPath("${DATA}/test.jpg") + }, + RenderableFar = { + Type = "RenderablePlaneImageOnline", + Size = 300000000000, + URL = "http://data.openspaceproject.com/examples/renderableplaneimageonline.jpg" + }, + DistanceThreshold = 2000000000000 + }, + GUI = { + Name = "RenderableSwitch - Basic", + Path = "/Examples" + } +} + +asset.onInitialize(function() + openspace.addSceneGraphNode(Node) +end) + +asset.onDeinitialize(function() + openspace.removeSceneGraphNode(Node) +end) + diff --git a/modules/base/CMakeLists.txt b/modules/base/CMakeLists.txt index b4467daaff..4b0df2c09d 100644 --- a/modules/base/CMakeLists.txt +++ b/modules/base/CMakeLists.txt @@ -64,6 +64,7 @@ set(HEADER_FILES rendering/renderablesphere.h rendering/renderablesphereimagelocal.h rendering/renderablesphereimageonline.h + rendering/renderableswitch.h rendering/renderabletimevaryingsphere.h rendering/renderabletrail.h rendering/renderabletrailorbit.h @@ -136,6 +137,7 @@ set(SOURCE_FILES rendering/renderablesphere.cpp rendering/renderablesphereimagelocal.cpp rendering/renderablesphereimageonline.cpp + rendering/renderableswitch.cpp rendering/renderabletimevaryingsphere.cpp rendering/renderabletrail.cpp rendering/renderabletrailorbit.cpp diff --git a/modules/base/basemodule.cpp b/modules/base/basemodule.cpp index 3fa210df41..5d0b0a8fbe 100644 --- a/modules/base/basemodule.cpp +++ b/modules/base/basemodule.cpp @@ -57,6 +57,7 @@ #include #include #include +#include #include #include #include @@ -186,6 +187,7 @@ void BaseModule::internalInitialize(const ghoul::Dictionary&) { fRenderable->registerClass( "RenderableSphereImageOnline" ); + fRenderable->registerClass("RenderableSwitch"); fRenderable->registerClass("RenderableSphericalGrid"); fRenderable->registerClass("RenderableTrailOrbit"); fRenderable->registerClass("RenderableTrailTrajectory"); @@ -279,6 +281,7 @@ std::vector BaseModule::documentations() const { RenderableSphereImageLocal::Documentation(), RenderableSphereImageOnline::Documentation(), RenderableSphericalGrid::Documentation(), + RenderableSwitch::Documentation(), RenderableTimeVaryingSphere::Documentation(), RenderableTrailOrbit::Documentation(), RenderableTrailTrajectory::Documentation(), diff --git a/modules/base/rendering/renderableswitch.cpp b/modules/base/rendering/renderableswitch.cpp new file mode 100644 index 0000000000..1022f1aa17 --- /dev/null +++ b/modules/base/rendering/renderableswitch.cpp @@ -0,0 +1,195 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2025 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + constexpr openspace::properties::Property::PropertyInfo DistanceThresholdInfo = { + "DistanceThreshold", + "Distance Threshold", + "Threshold in meters for when the switch happens between the two renderables.", + openspace::properties::Property::Visibility::AdvancedUser + }; + + // A RenderableSwitch can be used to render one of two renderables depending on the + // distance between the camera and the object's position. + // + // The two renderables are specified separately: `RenderableNear` and `RenderableFar`. + // These can be any renderable types. + // + // The `DistanceThreshold` property determines which renderable will be shown. + // If the camera is closer to the object than the threshold, `RenderableNear` is used, + // otherwise, `RenderableFar` is rendered. + struct [[codegen::Dictionary(RenderableSwitch)]] Parameters { + // The renderable to show when the camera is closer to the object than the + // threshold. + std::optional + renderableNear [[codegen::reference("renderable")]]; + + // The renderable to show when the camera is further away than the threshold. + std::optional + renderableFar [[codegen::reference("renderable")]]; + + // [[codegen::verbatim(DistanceThresholdInfo.description)]] + std::optional distanceThreshold [[codegen::greater(0.f)]]; + }; +#include "renderableswitch_codegen.cpp" +} // namespace + +namespace openspace { + +documentation::Documentation RenderableSwitch::Documentation() { + return codegen::doc( + "base_renderable_switch" + ); +} + +RenderableSwitch::RenderableSwitch(const ghoul::Dictionary& dictionary) + : Renderable(dictionary) + , _distanceThreshold(DistanceThresholdInfo, 1.f, 0.f, 1e25f) +{ + const Parameters p = codegen::bake(dictionary); + + if (!p.renderableNear.has_value() && !p.renderableFar.has_value()) { + throw ghoul::RuntimeError( + "Either a RenderableNear or a RenderableFar (or both) has to be provided, " + "but omitting both is invalid." + ); + } + + if (p.renderableNear.has_value()) { + _renderableNear = createFromDictionary(*p.renderableNear); + _renderableNear->setIdentifier("RenderableNear"); + _renderableNear->setGuiName("Renderable Near"); + addPropertySubOwner(_renderableNear.get()); + } + + if (p.renderableFar.has_value()) { + _renderableFar = createFromDictionary(*p.renderableFar); + _renderableFar->setIdentifier("RenderableFar"); + _renderableFar->setGuiName("Renderable Far"); + addPropertySubOwner(_renderableFar.get()); + } + + _distanceThreshold = p.distanceThreshold.value_or(_distanceThreshold); + addProperty(_distanceThreshold); +} + +void RenderableSwitch::initialize() { + ghoul_assert(_renderableNear || _renderableFar, "No renderable"); + + if (_renderableNear) { + _renderableNear->initialize(); + } + + if (_renderableFar) { + _renderableFar->initialize(); + } +} + +void RenderableSwitch::deinitialize() { + ghoul_assert(_renderableNear || _renderableFar, "No renderable"); + + if (_renderableNear) { + _renderableNear->deinitialize(); + } + + if (_renderableFar) { + _renderableFar->deinitialize(); + } +} + +void RenderableSwitch::initializeGL() { + ghoul_assert(_renderableNear || _renderableFar, "No renderable"); + + if (_renderableNear) { + _renderableNear->initializeGL(); + } + + if (_renderableFar) { + _renderableFar->initializeGL(); + } +} + +void RenderableSwitch::deinitializeGL() { + ghoul_assert(_renderableNear || _renderableFar, "No renderable"); + + if (_renderableNear) { + _renderableNear->deinitializeGL(); + } + + if (_renderableFar) { + _renderableFar->deinitializeGL(); + } +} + +bool RenderableSwitch::isReady() const { + const bool near = _renderableNear ? _renderableNear->isReady() : true; + const bool far = _renderableFar ? _renderableFar->isReady() : true; + return near && far; +} + +void RenderableSwitch::update(const UpdateData& data) { + ghoul_assert(_renderableNear || _renderableFar, "No renderable"); + + if (_renderableNear) { + _renderableNear->update(data); + } + + if (_renderableFar) { + _renderableFar->update(data); + } +} + +void RenderableSwitch::render(const RenderData& data, RendererTasks& tasks) { + glm::dvec3 cameraPosition = data.camera.positionVec3(); + glm::dvec3 modelPosition = data.modelTransform.translation; + + if (glm::distance(cameraPosition, modelPosition) < _distanceThreshold) { + if (_renderableNear && _renderableNear->isEnabled()) { + _renderableNear->render(data, tasks); + } + } + else { + if (_renderableFar && _renderableFar->isEnabled()) { + _renderableFar->render(data, tasks); + } + } +} + +} // namespace openspace diff --git a/modules/base/rendering/renderableswitch.h b/modules/base/rendering/renderableswitch.h new file mode 100644 index 0000000000..597236c888 --- /dev/null +++ b/modules/base/rendering/renderableswitch.h @@ -0,0 +1,62 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2025 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_MODULE_BASE___RENDERABLESWITCH___H__ +#define __OPENSPACE_MODULE_BASE___RENDERABLESWITCH___H__ + +#include + +namespace openspace { + +struct RenderData; +struct UpdateData; + +namespace documentation { struct Documentation; } + +class RenderableSwitch : public Renderable { +public: + explicit RenderableSwitch(const ghoul::Dictionary& dictionary); + + void initialize() override; + void deinitialize() override; + void initializeGL() override; + void deinitializeGL() override; + + bool isReady() const override; + + void update(const UpdateData& data) override; + void render(const RenderData& data, RendererTasks& tasks) override; + + static documentation::Documentation Documentation(); + +protected: + properties::DoubleProperty _distanceThreshold; + + ghoul::mm_unique_ptr _renderableNear; + ghoul::mm_unique_ptr _renderableFar; + +}; +} // namespace openspace + +#endif // __OPENSPACE_MODULE_BASE___RENDERABLESWITCH___H__ diff --git a/modules/kameleon/ext/kameleon b/modules/kameleon/ext/kameleon index 91485053e1..eee25cf7f4 160000 --- a/modules/kameleon/ext/kameleon +++ b/modules/kameleon/ext/kameleon @@ -1 +1 @@ -Subproject commit 91485053e1c260b337d1a9fd770c441abc0aa4a1 +Subproject commit eee25cf7f4d06dc72fd057326cf6b337a4df8e24 From b94e0966fe58f900c4f5d7cba8ece32f4d25618f Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Wed, 23 Apr 2025 16:52:48 +0200 Subject: [PATCH 07/10] Fix up the OpenSpace unit test --- tests/sessionrecording/0100_ascii_linux.osrectxt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sessionrecording/0100_ascii_linux.osrectxt b/tests/sessionrecording/0100_ascii_linux.osrectxt index d51e31c259..056847aa41 100644 --- a/tests/sessionrecording/0100_ascii_linux.osrectxt +++ b/tests/sessionrecording/0100_ascii_linux.osrectxt @@ -2,6 +2,6 @@ OpenSpace_record/playback01.00A script 212.227 0 762933560.401 1 openspace.setPropertyValueSingle("Modules.CefWebGui.Visible", true) camera 212.237 0.0107105 762933560.412 -13521714.7579869 -18987604.3886074 -1780842.2573154 0.6953145 -0.2337213 -0.1973068 0.6503710 0.00126470590475947 F Earth camera 212.248 0.0210999 762933560.423 -13521714.7579868 -18987604.3885993 -1780842.2573175 0.6953145 -0.2337212 -0.1973068 0.6503710 0.00126470590475947 F Earth -script 214.708 2.48093 762933562.877 1 openspace.pathnavigation.flyTo("Mars") +script 214.708 2.48093 762933562.877 1 openspace.navigation.flyTo("Mars") camera 214.709 2.48222 762933562.886 -13521714.7579865 -18987604.3886450 -1780842.2572641 0.6953144 -0.2337212 -0.1973068 0.6503708 0.00126470590475947 F Earth camera 214.717 2.49067 762933562.886 -13523362.1802436 -18989917.8348593 -1781059.2290947 0.6953144 -0.2337212 -0.1973068 0.6503708 0.00126449402887374 F Earth From 66bf57eed61f8dfa1236cd5c8bf6c55665294ac8 Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Wed, 23 Apr 2025 17:13:38 +0200 Subject: [PATCH 08/10] Rename the codegen tool because it is a reserved CMake name (closes #3502) --- CMakeLists.txt | 6 +++--- support/coding/codegen | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 32390f3be2..8ea6a5662a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -157,15 +157,15 @@ add_custom_target( ALL DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/__codegen.h" ) -add_dependencies(run_codegen codegen) +add_dependencies(run_codegen codegen-tool) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/__codegen.h" - COMMAND codegen ARGS "modules" "src" + COMMAND codegen-tool ARGS "modules" "src" WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" VERBATIM ) set_target_properties(codegen-lib PROPERTIES FOLDER "support") -set_target_properties(codegen PROPERTIES FOLDER "support") +set_target_properties(codegen-tool PROPERTIES FOLDER "support") set_target_properties(run_codegen PROPERTIES FOLDER "support") diff --git a/support/coding/codegen b/support/coding/codegen index 664bb14e12..3b2c085fcd 160000 --- a/support/coding/codegen +++ b/support/coding/codegen @@ -1 +1 @@ -Subproject commit 664bb14e12335b0d5ea166f633fd571859fdd5c2 +Subproject commit 3b2c085fcd1be8edc3719aea40d7c4387044322c From 6257f96dca408ef49aee8b6826b9348a3af1e320 Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Wed, 23 Apr 2025 17:21:19 +0200 Subject: [PATCH 09/10] Adapt to the change in the codegen repository --- include/openspace/documentation/verifier.h | 18 +++++++++++---- src/documentation/verifier.cpp | 26 ++++++++++++++++++---- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/include/openspace/documentation/verifier.h b/include/openspace/documentation/verifier.h index 16956c7f10..7a02a2cf06 100644 --- a/include/openspace/documentation/verifier.h +++ b/include/openspace/documentation/verifier.h @@ -191,30 +191,40 @@ public: /** * A Verifier that checks whether a given key inside a ghoul::Dictionary is a string and - * refers to an existing file on disk. + * optionally refers to an existing file on disk. */ class FileVerifier : public StringVerifier { public: - FileVerifier(); + explicit FileVerifier(bool fileMustExist = true); TestResult operator()(const ghoul::Dictionary& dict, const std::string& key) const override; std::string type() const override; + + bool mustExist() const; + +private: + bool _fileMustExist = true; }; /** * A Verifier that checks whether a given key inside a ghoul::Dictionary is a string and - * refers to an existing directory on disk. + * optionally refers to an existing directory on disk. */ class DirectoryVerifier : public StringVerifier { public: - DirectoryVerifier(); + explicit DirectoryVerifier(bool directoryMustExist = true); TestResult operator()(const ghoul::Dictionary& dict, const std::string& key) const override; std::string type() const override; + + bool mustExist() const; + +private: + bool _directoryMustExist = true; }; /** diff --git a/src/documentation/verifier.cpp b/src/documentation/verifier.cpp index f7bbcea885..e7d100facf 100644 --- a/src/documentation/verifier.cpp +++ b/src/documentation/verifier.cpp @@ -247,7 +247,10 @@ std::string IdentifierVerifier::type() const { return "Identifier"; } -FileVerifier::FileVerifier() : StringVerifier(true) {} +FileVerifier::FileVerifier(bool fileMustExist) + : StringVerifier(true) + , _fileMustExist(fileMustExist) +{} TestResult FileVerifier::operator()(const ghoul::Dictionary& dict, const std::string& key) const @@ -258,7 +261,9 @@ TestResult FileVerifier::operator()(const ghoul::Dictionary& dict, } const std::string file = dict.value(key); - if (!std::filesystem::exists(file) || !std::filesystem::is_regular_file(file)) { + if (_fileMustExist && + (!std::filesystem::exists(file) || !std::filesystem::is_regular_file(file))) + { res.success = false; TestResult::Offense o = { .offender = key, @@ -274,7 +279,14 @@ std::string FileVerifier::type() const { return "File"; } -DirectoryVerifier::DirectoryVerifier() : StringVerifier(true) {} +bool FileVerifier::mustExist() const { + return _fileMustExist; +} + +DirectoryVerifier::DirectoryVerifier(bool directoryMusExist) + : StringVerifier(true) + , _directoryMustExist(directoryMusExist) +{} TestResult DirectoryVerifier::operator()(const ghoul::Dictionary& dict, const std::string& key) const @@ -285,7 +297,9 @@ TestResult DirectoryVerifier::operator()(const ghoul::Dictionary& dict, } const std::string dir = dict.value(key); - if (!std::filesystem::exists(dir) || !std::filesystem::is_directory(dir)) { + if (_directoryMustExist && + (!std::filesystem::exists(dir) || !std::filesystem::is_directory(dir))) + { res.success = false; TestResult::Offense o = { .offender = key, @@ -301,6 +315,10 @@ std::string DirectoryVerifier::type() const { return "Directory"; } +bool DirectoryVerifier::mustExist() const { + return _directoryMustExist; +} + DateTimeVerifier::DateTimeVerifier() : StringVerifier(true) {} TestResult DateTimeVerifier::operator()(const ghoul::Dictionary& dict, From c82a8a763ffa3832e70f89212d2bc17825362cb2 Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Wed, 23 Apr 2025 17:38:32 +0200 Subject: [PATCH 10/10] Reparent the milkyway galaxy to the root to make it not disappear when SPICE information runs out (closes #3232) --- data/assets/scene/milkyway/milkyway/volume.asset | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/assets/scene/milkyway/milkyway/volume.asset b/data/assets/scene/milkyway/milkyway/volume.asset index 642e21d4f4..a99c785910 100644 --- a/data/assets/scene/milkyway/milkyway/volume.asset +++ b/data/assets/scene/milkyway/milkyway/volume.asset @@ -14,7 +14,7 @@ local KiloParsec = 3.086E19 local MilkyWayVolume = { Identifier = "MilkyWayVolume", - Parent = transforms.SolarSystemBarycenter.Identifier, + -- No parent; this node is attached to the scene graph root Transform = { Translation = { Type = "StaticTranslation",