mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-01-07 20:21:24 -06:00
Merge branch 'master' into thesis/2025/black-hole
This commit is contained in:
@@ -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")
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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",
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
local propertyHelper = asset.require("util/property_helper")
|
||||
local transforms = asset.require("./transforms")
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
local propertyHelper = asset.require("../property_helper")
|
||||
local joystickHelper = asset.require("./joystick_helper")
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
local propertyHelper = asset.require("../property_helper")
|
||||
local joystickHelper = asset.require("./joystick_helper")
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
local propertyHelper = asset.require("../property_helper")
|
||||
local joystickHelper = asset.require("./joystick_helper")
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
local propertyHelper = asset.require("../property_helper")
|
||||
local joystickHelper = asset.require("./joystick_helper")
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
local propertyHelper = asset.require("../property_helper")
|
||||
local joystickHelper = asset.require("./joystick_helper")
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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__
|
||||
83
include/openspace/util/geodetic.h
Normal file
83
include/openspace/util/geodetic.h
Normal 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__
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
195
modules/base/rendering/renderableswitch.cpp
Normal file
195
modules/base/rendering/renderableswitch.cpp
Normal 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
|
||||
62
modules/base/rendering/renderableswitch.h
Normal file
62
modules/base/rendering/renderableswitch.h
Normal 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__
|
||||
@@ -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 }
|
||||
);
|
||||
}
|
||||
@@ -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__
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -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__
|
||||
@@ -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
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#define __OPENSPACE_MODULE_GLOBEBROWSING___GEODETICPATCH___H__
|
||||
|
||||
#include <modules/globebrowsing/src/basictypes.h>
|
||||
#include <openspace/util/geodetic.h>
|
||||
|
||||
namespace openspace::globebrowsing {
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1049,7 +1049,7 @@ GeoJsonManager& RenderableGlobe::geoJsonManager() {
|
||||
return _geoJsonManager;
|
||||
}
|
||||
|
||||
const Ellipsoid& RenderableGlobe::ellipsoid() const {
|
||||
Ellipsoid RenderableGlobe::ellipsoid() const {
|
||||
return _ellipsoid;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Submodule modules/kameleon/ext/kameleon updated: 91485053e1...eee25cf7f4
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
485
modules/space/timeframe/timeframekernel.cpp
Normal file
485
modules/space/timeframe/timeframekernel.cpp
Normal 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
|
||||
52
modules/space/timeframe/timeframekernel.h
Normal file
52
modules/space/timeframe/timeframekernel.h
Normal 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__
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
130
src/util/geodetic.cpp
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Submodule support/coding/codegen updated: 3d3cb4e0c0...3b2c085fcd
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user