Merge branch 'master' into thesis/2025/black-hole

This commit is contained in:
Emil Wallberg
2025-04-23 20:30:19 +02:00
85 changed files with 2410 additions and 1251 deletions

View File

@@ -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")

View File

@@ -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;
}
/*

View File

@@ -36,6 +36,7 @@
#include <QFile>
#include <QKeyEvent>
#include <QLabel>
#include <QMenu>
#include <QMessageBox>
#include <QPushButton>
#include <QStandardItemModel>
@@ -189,30 +190,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
@@ -238,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
@@ -249,12 +226,58 @@ 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
);
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);
}
//
@@ -266,31 +289,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 +327,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
);
}
//
@@ -407,7 +430,19 @@ void LauncherWindow::selectProfile(std::optional<std::string> 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<std::string> 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();
}
);

View File

@@ -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<int>::of(&QComboBox::currentIndexChanged),

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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",

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -1,4 +1,3 @@
local propertyHelper = asset.require("util/property_helper")
local transforms = asset.require("./transforms")

View File

@@ -1,4 +1,3 @@
local propertyHelper = asset.require("../property_helper")
local joystickHelper = asset.require("./joystick_helper")

View File

@@ -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"
)

View File

@@ -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"
)

View File

@@ -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"
)

View File

@@ -1,4 +1,3 @@
local propertyHelper = asset.require("../property_helper")
local joystickHelper = asset.require("./joystick_helper")

View File

@@ -1,4 +1,3 @@
local propertyHelper = asset.require("../property_helper")
local joystickHelper = asset.require("./joystick_helper")

View File

@@ -1,4 +1,3 @@
local propertyHelper = asset.require("../property_helper")
local joystickHelper = asset.require("./joystick_helper")

View File

@@ -1,4 +1,3 @@
local propertyHelper = asset.require("../property_helper")
local joystickHelper = asset.require("./joystick_helper")

View File

@@ -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"
)

View File

@@ -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"
)

View File

@@ -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)

View File

@@ -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 <renderable> to <enabled>
function setEnabled(renderable, enabled)
return [[openspace.setPropertyValue("]] .. renderable .. [[.Renderable.Enabled", ]] .. (enabled and "true" or "false") .. ")"
end
asset.export("toggle", toggle)
asset.export("setEnabled", setEnabled)

View File

@@ -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.

View File

@@ -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;
};
/**

View File

@@ -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;

View File

@@ -32,6 +32,7 @@
#include <openspace/properties/scalar/doubleproperty.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/properties/vector/ivec2property.h>
#include <openspace/util/ellipsoid.h>
#include <ghoul/glm.h>
#include <ghoul/misc/boolean.h>
#include <ghoul/misc/managedmemoryuniqueptr.h>
@@ -130,6 +131,8 @@ public:
const std::vector<std::string>& onRecedeAction() const;
const std::vector<std::string>& onExitAction() const;
Ellipsoid ellipsoid() const;
double boundingSphere() const;
double interactionSphere() const;

View File

@@ -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 <ghoul/glm.h>
#include <vector>
namespace openspace::globebrowsing {
namespace openspace {
struct Geodetic2;
struct Geodetic3;
@@ -114,6 +114,6 @@ private:
std::vector<Ellipsoid::ShadowConfiguration> _shadowConfArray;
};
} // namespace openspace::globebrowsing
} // namespace openspace
#endif // __OPENSPACE_MODULE_GLOBEBROWSING___ELLIPSOID___H__
#endif // __OPENSPACE_CORE___ELLIPSOID___H__

View File

@@ -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 <ghoul/glm.h>
#include <optional>
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<double> 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__

View File

@@ -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:

View File

@@ -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
@@ -76,6 +77,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 +89,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
@@ -134,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
@@ -147,6 +151,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 +163,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

View File

@@ -57,6 +57,7 @@
#include <modules/base/rendering/renderablenodeline.h>
#include <modules/base/rendering/renderablesphereimagelocal.h>
#include <modules/base/rendering/renderablesphereimageonline.h>
#include <modules/base/rendering/renderableswitch.h>
#include <modules/base/rendering/renderabletrailorbit.h>
#include <modules/base/rendering/renderabletrailtrajectory.h>
#include <modules/base/rendering/renderableplaneimagelocal.h>
@@ -71,6 +72,7 @@
#include <modules/base/rendering/screenspacerenderablerenderable.h>
#include <modules/base/rotation/constantrotation.h>
#include <modules/base/rotation/fixedrotation.h>
#include <modules/base/rotation/globerotation.h>
#include <modules/base/rotation/luarotation.h>
#include <modules/base/rotation/multirotation.h>
#include <modules/base/rotation/staticrotation.h>
@@ -82,6 +84,7 @@
#include <modules/base/scale/timedependentscale.h>
#include <modules/base/scale/timelinescale.h>
#include <modules/base/translation/timelinetranslation.h>
#include <modules/base/translation/globetranslation.h>
#include <modules/base/translation/luatranslation.h>
#include <modules/base/translation/multitranslation.h>
#include <modules/base/translation/statictranslation.h>
@@ -184,6 +187,7 @@ void BaseModule::internalInitialize(const ghoul::Dictionary&) {
fRenderable->registerClass<RenderableSphereImageOnline>(
"RenderableSphereImageOnline"
);
fRenderable->registerClass<RenderableSwitch>("RenderableSwitch");
fRenderable->registerClass<RenderableSphericalGrid>("RenderableSphericalGrid");
fRenderable->registerClass<RenderableTrailOrbit>("RenderableTrailOrbit");
fRenderable->registerClass<RenderableTrailTrajectory>("RenderableTrailTrajectory");
@@ -195,6 +199,7 @@ void BaseModule::internalInitialize(const ghoul::Dictionary&) {
fRotation->registerClass<ConstantRotation>("ConstantRotation");
fRotation->registerClass<FixedRotation>("FixedRotation");
fRotation->registerClass<GlobeRotation>("GlobeRotation");
fRotation->registerClass<LuaRotation>("LuaRotation");
fRotation->registerClass<MultiRotation>("MultiRotation");
fRotation->registerClass<StaticRotation>("StaticRotation");
@@ -224,6 +229,7 @@ void BaseModule::internalInitialize(const ghoul::Dictionary&) {
FactoryManager::ref().factory<Translation>();
ghoul_assert(fTranslation, "Ephemeris factory was not created");
fTranslation->registerClass<GlobeTranslation>("GlobeTranslation");
fTranslation->registerClass<LuaTranslation>("LuaTranslation");
fTranslation->registerClass<MultiTranslation>("MultiTranslation");
fTranslation->registerClass<StaticTranslation>("StaticTranslation");
@@ -275,6 +281,7 @@ std::vector<documentation::Documentation> BaseModule::documentations() const {
RenderableSphereImageLocal::Documentation(),
RenderableSphereImageOnline::Documentation(),
RenderableSphericalGrid::Documentation(),
RenderableSwitch::Documentation(),
RenderableTimeVaryingSphere::Documentation(),
RenderableTrailOrbit::Documentation(),
RenderableTrailTrajectory::Documentation(),
@@ -289,6 +296,7 @@ std::vector<documentation::Documentation> BaseModule::documentations() const {
ConstantRotation::Documentation(),
FixedRotation::Documentation(),
GlobeRotation::Documentation(),
LuaRotation::Documentation(),
MultiRotation::Documentation(),
StaticRotation::Documentation(),
@@ -304,6 +312,7 @@ std::vector<documentation::Documentation> BaseModule::documentations() const {
TimeFrameInterval::Documentation(),
TimeFrameUnion::Documentation(),
GlobeTranslation::Documentation(),
LuaTranslation::Documentation(),
MultiTranslation::Documentation(),
StaticTranslation::Documentation(),

View File

@@ -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 <modules/base/rendering/renderableswitch.h>
#include <modules/base/basemodule.h>
#include <openspace/documentation/documentation.h>
#include <openspace/documentation/verifier.h>
#include <openspace/util/updatestructures.h>
#include <ghoul/filesystem/file.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/io/texture/texturereader.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/misc/crc32.h>
#include <ghoul/misc/profiling.h>
#include <ghoul/opengl/texture.h>
#include <fstream>
#include <optional>
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<ghoul::Dictionary>
renderableNear [[codegen::reference("renderable")]];
// The renderable to show when the camera is further away than the threshold.
std::optional<ghoul::Dictionary>
renderableFar [[codegen::reference("renderable")]];
// [[codegen::verbatim(DistanceThresholdInfo.description)]]
std::optional<double> distanceThreshold [[codegen::greater(0.f)]];
};
#include "renderableswitch_codegen.cpp"
} // namespace
namespace openspace {
documentation::Documentation RenderableSwitch::Documentation() {
return codegen::doc<Parameters>(
"base_renderable_switch"
);
}
RenderableSwitch::RenderableSwitch(const ghoul::Dictionary& dictionary)
: Renderable(dictionary)
, _distanceThreshold(DistanceThresholdInfo, 1.f, 0.f, 1e25f)
{
const Parameters p = codegen::bake<Parameters>(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

View File

@@ -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 <openspace/rendering/renderable.h>
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<Renderable> _renderableNear;
ghoul::mm_unique_ptr<Renderable> _renderableFar;
};
} // namespace openspace
#endif // __OPENSPACE_MODULE_BASE___RENDERABLESWITCH___H__

View File

@@ -22,16 +22,16 @@
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/globebrowsing/src/globerotation.h>
#include <modules/base/rotation/globerotation.h>
#include <modules/globebrowsing/globebrowsingmodule.h>
#include <modules/globebrowsing/src/renderableglobe.h>
#include <openspace/documentation/documentation.h>
#include <openspace/documentation/verifier.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/moduleengine.h>
#include <openspace/scene/scenegraphnode.h>
#include <openspace/query/query.h>
#include <openspace/util/ellipsoid.h>
#include <openspace/util/geodetic.h>
#include <openspace/util/updatestructures.h>
#include <ghoul/logging/logmanager.h>
#include <glm/gtx/quaternion.hpp>
@@ -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<Parameters>("globebrowsing_rotation_globerotation");
return codegen::doc<Parameters>("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<Parameters>(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<RenderableGlobe*>(n->renderable())) {
_globeNode = dynamic_cast<RenderableGlobe*>(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<GlobeBrowsingModule>();
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<GlobeRotation*>(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<GlobeBrowsingModule>();
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<float>(lat));
const float longitudeRad = glm::radians(static_cast<float>(lon));
yAxis = _globeNode->ellipsoid().geodeticSurfaceNormal(
yAxis = _attachedNode->ellipsoid().geodeticSurfaceNormal(
{ latitudeRad, longitudeRad }
);
}

View File

@@ -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 <openspace/scene/rotation.h>
@@ -31,9 +31,9 @@
#include <openspace/properties/scalar/boolproperty.h>
#include <openspace/properties/scalar/doubleproperty.h>
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__

View File

@@ -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,

View File

@@ -22,16 +22,17 @@
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/globebrowsing/src/globetranslation.h>
#include <modules/base/translation/globetranslation.h>
#include <modules/globebrowsing/globebrowsingmodule.h>
#include <modules/globebrowsing/src/renderableglobe.h>
#include <openspace/documentation/documentation.h>
#include <openspace/documentation/verifier.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/moduleengine.h>
#include <openspace/rendering/renderable.h>
#include <openspace/scene/scenegraphnode.h>
#include <openspace/query/query.h>
#include <openspace/util/geodetic.h>
#include <openspace/util/updatestructures.h>
#include <ghoul/logging/logmanager.h>
@@ -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<double> latitude;
@@ -119,15 +134,15 @@ namespace {
#include "globetranslation_codegen.cpp"
} // namespace
namespace openspace::globebrowsing {
namespace openspace {
documentation::Documentation GlobeTranslation::Documentation() {
return codegen::doc<Parameters>("globebrowsing_translation_globetranslation");
return codegen::doc<Parameters>("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<Parameters>(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<RenderableGlobe*>(n->renderable())) {
_attachedNode = dynamic_cast<RenderableGlobe*>(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<GlobeTranslation*>(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<GlobeBrowsingModule>();
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

View File

@@ -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 <openspace/scene/translation.h>
@@ -31,9 +31,9 @@
#include <openspace/properties/scalar/boolproperty.h>
#include <openspace/properties/scalar/doubleproperty.h>
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__

View File

@@ -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

View File

@@ -32,8 +32,6 @@
#include <modules/globebrowsing/src/geojson/geojsonmanager.h>
#include <modules/globebrowsing/src/geojson/geojsonproperties.h>
#include <modules/globebrowsing/src/globelabelscomponent.h>
#include <modules/globebrowsing/src/globetranslation.h>
#include <modules/globebrowsing/src/globerotation.h>
#include <modules/globebrowsing/src/layer.h>
#include <modules/globebrowsing/src/layeradjustment.h>
#include <modules/globebrowsing/src/layergroup.h>
@@ -66,6 +64,7 @@
#include <openspace/scene/scenegraphnode.h>
#include <openspace/scripting/lualibrary.h>
#include <openspace/util/factorymanager.h>
#include <openspace/util/geodetic.h>
#include <openspace/util/updatestructures.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/format.h>
@@ -291,16 +290,6 @@ void GlobeBrowsingModule::internalInitialize(const ghoul::Dictionary& dict) {
ghoul_assert(fRenderable, "Renderable factory was not created");
fRenderable->registerClass<globebrowsing::RenderableGlobe>("RenderableGlobe");
ghoul::TemplateFactory<Translation>* fTranslation =
FactoryManager::ref().factory<Translation>();
ghoul_assert(fTranslation, "Translation factory was not created");
fTranslation->registerClass<globebrowsing::GlobeTranslation>("GlobeTranslation");
ghoul::TemplateFactory<Rotation>* fRotation =
FactoryManager::ref().factory<Rotation>();
ghoul_assert(fRotation, "Rotation factory was not created");
fRotation->registerClass<globebrowsing::GlobeRotation>("GlobeRotation");
FactoryManager::ref().addFactory<TileProvider>("TileProvider");
ghoul::TemplateFactory<TileProvider>* fTileProvider =
@@ -334,8 +323,6 @@ std::vector<documentation::Documentation> 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<documentation::Documentation> 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<uint8_t>::max(), "Level way too big");
goToChunk(
globe,
globebrowsing::TileIndex(x, y, static_cast<uint8_t>(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<double> 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<const RenderableGlobe*>(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<uint8_t>(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<SceneGraphNode*>(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"),

View File

@@ -30,6 +30,7 @@
#include <openspace/properties/misc/stringproperty.h>
#include <openspace/properties/scalar/boolproperty.h>
#include <openspace/properties/scalar/uintproperty.h>
#include <openspace/util/ellipsoid.h>
#include <ghoul/glm.h>
#include <future>
#include <memory>
@@ -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<double> 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;

View File

@@ -300,288 +300,7 @@ namespace {
throw ghoul::lua::LuaError("Identifier must be a RenderableGlobe");
}
global::moduleEngine->module<GlobeBrowsingModule>()->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<double> altitude,
std::optional<double> 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<float>(*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<double> 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<double> altitude,
std::optional<double> duration,
std::optional<bool> 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<const RenderableGlobe*>(n->renderable());
if (!gl) {
throw ghoul::lua::LuaError("The targetted node is not a RenderableGlobe");
}
auto module = global::moduleEngine->module<GlobeBrowsingModule>();
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<bool> useCurrentDistance,
std::optional<double> duration,
std::optional<bool> shouldUseUpVector)
{
using namespace openspace;
std::optional<double> 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<double> duration,
std::optional<bool> 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<double, double, double>
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<const RenderableGlobe*>(n->renderable());
if (!globe) {
throw ghoul::lua::LuaError("Identifier must be a RenderableGlobe");
}
GlobeBrowsingModule& mod = *(global::moduleEngine->module<GlobeBrowsingModule>());
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<double, double, double>
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<GlobeBrowsingModule>()->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<double, double, double>
geoPositionForCameraDeprecated(bool useEyePosition = false)

View File

@@ -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);

View File

@@ -149,8 +149,7 @@ DashboardItemGlobeLocation::DashboardItemGlobeLocation(
void DashboardItemGlobeLocation::update() {
ZoneScoped;
GlobeBrowsingModule* module = global::moduleEngine->module<GlobeBrowsingModule>();
const glm::dvec3 position = module->geoPosition();
const glm::dvec3 position = geoPositionFromCamera();
double lat = position.x;
double lon = position.y;
const double altitude = position.z;

View File

@@ -26,6 +26,7 @@
#define __OPENSPACE_MODULE_GLOBEBROWSING___GEODETICPATCH___H__
#include <modules/globebrowsing/src/basictypes.h>
#include <openspace/util/geodetic.h>
namespace openspace::globebrowsing {

View File

@@ -841,7 +841,7 @@ void GeoJsonComponent::flyToFeature(std::optional<int> 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);

View File

@@ -242,7 +242,7 @@ subdivideTriangle(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2,
std::vector<Coordinate> 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);

View File

@@ -28,15 +28,13 @@
#include <ghoul/glm.h>
#include <vector>
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;

View File

@@ -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<openspace::GlobeBrowsingModule>();
lEntry.geoPosition = _globeBrowsingModule->cartesianCoordinatesFromGeo(
*_globe,
lEntry.geoPosition = cartesianCoordinatesFromGeo(
*static_cast<SceneGraphNode*>(_globe->owner()),
lEntry.latitude,
lEntry.longitude,
lEntry.diameter

View File

@@ -1049,7 +1049,7 @@ GeoJsonManager& RenderableGlobe::geoJsonManager() {
return _geoJsonManager;
}
const Ellipsoid& RenderableGlobe::ellipsoid() const {
Ellipsoid RenderableGlobe::ellipsoid() const {
return _ellipsoid;
}

View File

@@ -27,7 +27,6 @@
#include <openspace/rendering/renderable.h>
#include <modules/globebrowsing/src/ellipsoid.h>
#include <modules/globebrowsing/src/geodeticpatch.h>
#include <modules/globebrowsing/src/geojson/geojsonmanager.h>
#include <modules/globebrowsing/src/globelabelscomponent.h>
@@ -41,6 +40,7 @@
#include <openspace/properties/scalar/boolproperty.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/properties/scalar/intproperty.h>
#include <openspace/util/ellipsoid.h>
#include <ghoul/misc/memorypool.h>
#include <ghoul/opengl/uniformcache.h>
#include <cstddef>
@@ -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;

View File

@@ -28,7 +28,6 @@
#include <openspace/properties/propertyowner.h>
#include <modules/globebrowsing/src/basictypes.h>
#include <modules/globebrowsing/src/ellipsoid.h>
#include <modules/globebrowsing/src/layergroupid.h>
#include <modules/globebrowsing/src/tileindex.h>
#include <modules/globebrowsing/src/tiletextureinitdata.h>
@@ -36,6 +35,7 @@
#include <openspace/properties/misc/stringproperty.h>
#include <openspace/properties/scalar/boolproperty.h>
#include <openspace/properties/scalar/intproperty.h>
#include <openspace/util/ellipsoid.h>
#include <unordered_map>
#include <ghoul/opengl/programobject.h>

View File

@@ -33,6 +33,7 @@
#include <openspace/engine/globals.h>
#include <openspace/properties/property.h>
#include <openspace/util/distanceconversion.h>
#include <openspace/util/geodetic.h>
#include <ghoul/logging/logmanager.h>
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<GlobeBrowsingModule>();
glm::dvec3 position = module->geoPosition();
glm::dvec3 position = geoPositionFromCamera();
std::pair<double, std::string_view> altSimplified = simplifyDistance(position.z);
const nlohmann::json jsonData = {

View File

@@ -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

View File

@@ -33,6 +33,7 @@
#include <modules/space/rendering/renderablerings.h>
#include <modules/space/rendering/renderablestars.h>
#include <modules/space/rendering/renderabletravelspeed.h>
#include <modules/space/timeframe/timeframekernel.h>
#include <modules/space/translation/keplertranslation.h>
#include <modules/space/translation/spicetranslation.h>
#include <modules/space/translation/gptranslation.h>
@@ -105,6 +106,7 @@ void SpaceModule::internalInitialize(const ghoul::Dictionary& dictionary) {
fRenderable->registerClass<RenderableStars>("RenderableStars");
fRenderable->registerClass<RenderableTravelSpeed>("RenderableTravelSpeed");
ghoul::TemplateFactory<Translation>* fTranslation =
FactoryManager::ref().factory<Translation>();
ghoul_assert(fTranslation, "Ephemeris factory was not created");
@@ -114,12 +116,19 @@ void SpaceModule::internalInitialize(const ghoul::Dictionary& dictionary) {
fTranslation->registerClass<GPTranslation>("GPTranslation");
fTranslation->registerClass<HorizonsTranslation>("HorizonsTranslation");
ghoul::TemplateFactory<Rotation>* fRotation =
FactoryManager::ref().factory<Rotation>();
ghoul_assert(fRotation, "Rotation factory was not created");
fRotation->registerClass<SpiceRotation>("SpiceRotation");
ghoul::TemplateFactory<TimeFrame>* fTimeFrame =
FactoryManager::ref().factory<TimeFrame>();
ghoul_assert(fTimeFrame, "Scale factory was not created");
fTimeFrame->registerClass<TimeFrameKernel>("TimeFrameKernel");
const Parameters p = codegen::bake<Parameters>(dictionary);
_showSpiceExceptions = p.showExceptions.value_or(_showSpiceExceptions);
}

View File

@@ -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 <modules/space/timeframe/timeframekernel.h>
#include <openspace/util/spicemanager.h>
#include <openspace/util/time.h>
#include <ghoul/logging/logmanager.h>
#include "SpiceUsr.h"
namespace {
constexpr std::string_view _loggerCat = "TimeFrameKernel";
constexpr unsigned SpiceErrorBufferSize = 1841;
std::vector<openspace::TimeRange> extractTimeFramesSPK(
const std::vector<std::filesystem::path>& kernels,
const std::variant<std::string, int>& object)
{
using namespace openspace;
std::vector<TimeRange> 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<std::string>(object)) {
std::string s = std::get<std::string>(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<int>(object), "Additional variant type");
id = std::get<int>(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<char, ArchitectureSize> architecture;
std::memset(architecture.data(), ArchitectureSize, 0);
constexpr int TypeSize = 16;
std::array<char, TypeSize> 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<openspace::TimeRange> extractTimeFramesCK(
const std::vector<std::filesystem::path>& kernels,
const std::variant<std::string, int>& object)
{
using namespace openspace;
std::vector<TimeRange> 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<std::string>(object)) {
std::string s = std::get<std::string>(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<int>(object), "Additional variant type");
id = std::get<int>(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<char, ArchitectureSize> architecture;
std::memset(architecture.data(), ArchitectureSize, 0);
constexpr int TypeSize = 16;
std::array<char, TypeSize> 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<openspace::TimeRange>& 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<std::filesystem::path>
> kernels;
// The NAIF name of the object for which the positional information should be
// extracted
std::variant<std::string, int> object;
};
std::optional<SPK> 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<std::filesystem::path> 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<std::string, int> reference;
};
std::optional<CK> ck [[codegen::key("CK")]];
};
#include "timeframekernel_codegen.cpp"
} // namespace
namespace openspace {
documentation::Documentation TimeFrameKernel::Documentation() {
return codegen::doc<Parameters>("space_time_frame_kernel");
}
TimeFrameKernel::TimeFrameKernel(const ghoul::Dictionary& dictionary)
: _initialization(dictionary)
{
// Baking the dictionary here to detect any error
codegen::bake<Parameters>(dictionary);
}
bool TimeFrameKernel::initialize() {
const Parameters p = codegen::bake<Parameters>(_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<std::filesystem::path> kernels;
if (std::holds_alternative<std::filesystem::path>(p.spk->kernels)) {
kernels = { std::get<std::filesystem::path>(p.spk->kernels) };
}
else {
kernels = std::get<std::vector<std::filesystem::path>>(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

View File

@@ -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 <openspace/scene/timeframe.h>
#include <openspace/util/timerange.h>
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<TimeRange> _timeRangesSPK;
std::vector<TimeRange> _timeRangesCK;
};
} // namespace openspace
#endif // __OPENSPACE_MODULE_BASE___TIMEFRAMEKERNEL___H__

View File

@@ -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

View File

@@ -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<std::string>(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<std::string>(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,

View File

@@ -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
);
}

View File

@@ -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
}
};
}

View File

@@ -24,6 +24,8 @@
#include <ghoul/lua/lua_helper.h>
#include <openspace/util/geodetic.h>
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<double> altitude,
std::optional<double> 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<float>(*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<double> 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<double> altitude, std::optional<double> duration,
std::optional<bool> 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<bool> useCurrentDistance,
std::optional<double> duration,
std::optional<bool> shouldUseUpVector)
{
using namespace openspace;
std::optional<double> 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<double> duration,
std::optional<bool> 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<double, double, double>
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<double, double, double>
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<std::variant<bool, double>> useUpFromTargetOrDuration,
std::optional<double> duration)
{
using namespace openspace;
if (useUpFromTargetOrDuration.has_value() &&
std::holds_alternative<double>(*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<bool>(*useUpFromTargetOrDuration)) {
insDict.setValue(
"UseTargetUpDirection",
std::get<bool>(*useUpFromTargetOrDuration)
);
}
else {
double d = std::get<double>(*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<std::variant<bool, double>> useUpFromTargetOrDuration,
std::optional<double> 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<bool>(*useUpFromTargetOrDuration)) {
insDict.setValue(
"UseTargetUpDirection",
std::get<bool>(*useUpFromTargetOrDuration)
);
}
else {
double d = std::get<double>(*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<double> 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<double> 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<double> 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<double> 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<bool> useTimeStamp,
std::optional<double> 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<float>(*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<double> 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<float>(*fadeDuration)
);
}
else {
global::navigationHandler->triggerFadeToTransition(script);
}
}
#include "navigationhandler_lua_codegen.cpp"
} // namespace

View File

@@ -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
}
};

View File

@@ -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<std::variant<bool, double>> useUpFromTargetOrDuration,
std::optional<double> duration)
{
using namespace openspace;
if (useUpFromTargetOrDuration.has_value() &&
std::holds_alternative<double>(*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<bool>(*useUpFromTargetOrDuration)) {
insDict.setValue(
"UseTargetUpDirection",
std::get<bool>(*useUpFromTargetOrDuration)
);
}
else {
double d = std::get<double>(*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<std::variant<bool, double>> useUpFromTargetOrDuration,
std::optional<double> 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<bool>(*useUpFromTargetOrDuration)) {
insDict.setValue(
"UseTargetUpDirection",
std::get<bool>(*useUpFromTargetOrDuration)
);
}
else {
double d = std::get<double>(*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<double> 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<double> 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<double> 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<double> 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<bool> useTimeStamp,
std::optional<double> 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<float>(*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<double> 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<float>(*fadeDuration)
);
}
else {
global::navigationHandler->triggerFadeToTransition(script);
}
}
/**
* Create a camera path as described by the instruction in the input argument.
*

View File

@@ -32,6 +32,7 @@
#include <openspace/events/eventengine.h>
#include <openspace/navigation/navigationhandler.h>
#include <openspace/scene/scenegraphnode.h>
#include <openspace/util/ellipsoid.h>
#include <openspace/util/factorymanager.h>
#include <openspace/util/memorymanager.h>
#include <openspace/util/updatestructures.h>
@@ -259,6 +260,10 @@ SurfacePositionHandle Renderable::calculateSurfacePositionHandle(
};
}
Ellipsoid Renderable::ellipsoid() const {
return Ellipsoid(glm::dvec3(_interactionSphere));
}
bool Renderable::renderedWithDesiredData() const {
return true;
}

View File

@@ -1316,6 +1316,15 @@ const std::vector<std::string>& 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);

View File

@@ -22,9 +22,9 @@
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/globebrowsing/src/ellipsoid.h>
#include <openspace/util/ellipsoid.h>
#include <modules/globebrowsing/src/basictypes.h>
#include <openspace/util/geodetic.h>
#include <algorithm>
#include <array>
#include <vector>
@@ -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

130
src/util/geodetic.cpp Normal file
View File

@@ -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 <openspace/util/geodetic.h>
#include <openspace/camera/camera.h>
#include <openspace/engine/globals.h>
#include <openspace/navigation/navigationhandler.h>
#include <openspace/rendering/renderable.h>
#include <openspace/util/ellipsoid.h>
#include <openspace/util/updatestructures.h>
#include <ghoul/logging/logmanager.h>
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<double> 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

View File

@@ -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);
}

View File

@@ -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

View File

@@ -26,6 +26,7 @@
#include <modules/globebrowsing/src/basictypes.h>
#include <modules/globebrowsing/src/geodeticpatch.h>
#include <openspace/util/geodetic.h>
#include <ghoul/glm.h>
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<float>() / 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<float>() / 4.f;

View File

@@ -449,7 +449,7 @@ TEST_CASE("SessionRecording: 01.00 Ascii Linux", "[sessionrecording]") {
CHECK(e.simulationTime == 762933562.877);
REQUIRE(std::holds_alternative<SessionRecording::Entry::Script>(e.value));
const auto& script = std::get<SessionRecording::Entry::Script>(e.value);
CHECK(script == "openspace.pathnavigation.flyTo(\"Mars\")");
CHECK(script == "openspace.navigation.flyTo(\"Mars\")");
}
{
const SessionRecording::Entry& e = rec.entries[4];