Merge branch 'master' into feature/jwst-update

This commit is contained in:
Malin E
2022-04-08 17:24:16 +02:00
68 changed files with 3625 additions and 81699 deletions

View File

@@ -1,15 +1,15 @@
# Core Team
Alexander Bock
Emma Broman
Emil Axelsson
Gene Payne
Kalle Bladin
Jonathas Costa
Gene Payne
Emma Broman
Jonas Strandstedt
Micah Acinapura
Michal Marcinkowski
Malin Ejdbo
Elon Olsson
Micah Acinapura
Jonas Strandstedt
Michal Marcinkowski
Joakim Kilby
Lovisa Hassler
Mikael Petterson
@@ -23,6 +23,8 @@ Erik Broberg
Jonathan Bosson
Michael Nilsson
Jonathan Franzen
ChristianAdamsson
Emilie Ho
Karin Reidarman
Hans-Christian Helltegen
Anton Arbring
@@ -39,15 +41,16 @@ Michael Sjöström
Michael Novén
Christoffer Särevall
# Community Support
Anteige
arfon
DavidLaidlaw
ethanejohnsons
johnriedel
mik3caprio
mingenuity
nbartzokas
nealmcb
noahdasanaike
PTrottier
sa5bke

6
Jenkinsfile vendored
View File

@@ -6,6 +6,12 @@ library('sharedSpace'); // jenkins-pipeline-lib
def url = 'https://github.com/OpenSpace/OpenSpace';
def branch = env.BRANCH_NAME;
// The CHANGE_BRANCH only exists if we are building a PR branch in which case it returns
// the original branch
if (env.CHANGE_BRANCH) {
branch = env.CHANGE_BRANCH;
}
@NonCPS
def readDir() {
def dirsl = [];

View File

@@ -34,7 +34,7 @@ OpenSpace requires at least support for [OpenGL](https://www.opengl.org/) versio
![Image](https://github.com/OpenSpace/openspace.github.io/raw/master/assets/images/display-systems.jpg)
# Getting Started
This repository contains the source code and example profiles for OpenSpace, but does not contain any data. To build and install the application, please check out the [OpenSpace Wiki](http://wiki.openspaceproject.com/). Here, you will find two pages, a [build instruction](http://wiki.openspaceproject.com/docs/developers/compiling/general) for all operating systems and then additional instructions for [Windows](http://wiki.openspaceproject.com/docs/developers/compiling/windows), [Linux (Ubuntu)](http://wiki.openspaceproject.com/docs/developers/compiling/ubuntu), and [MacOS](http://wiki.openspaceproject.com/docs/developers/compiling/macos).
This repository contains the source code and example profiles for OpenSpace, but does not contain any data. To build and install the application, please check out the [GitHub Wiki](https://github.com/OpenSpace/OpenSpace/wiki). Here, you will find two pages, a [build instruction](https://github.com/OpenSpace/OpenSpace/wiki/Compiling) for all operating systems and then additional instructions for [Windows](https://github.com/OpenSpace/OpenSpace/wiki/Compiling-Windows), [Linux (Ubuntu)](https://github.com/OpenSpace/OpenSpace/wiki/Compiling-Ubuntu), and [MacOS](https://github.com/OpenSpace/OpenSpace/wiki/Compiling-MacOS). Please note that the Apple Silicon series of chips do not support OpenGL natively and Metal 2 does not support `double` precision accuracy (see [here](https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf) Section 2.1), therefore only the Intel processors for MacOS are supported and maintained.
Requirements for compiling are:
- CMake version 3.10 or above

View File

@@ -30,10 +30,12 @@ set(HEADER_FILES
include/profile/actiondialog.h
include/profile/additionalscriptsdialog.h
include/profile/assetsdialog.h
include/profile/assetedit.h
include/profile/assettreeitem.h
include/profile/assettreemodel.h
include/profile/cameradialog.h
include/profile/deltatimesdialog.h
include/profile/horizonsdialog.h
include/profile/scriptlogdialog.h
include/profile/line.h
include/profile/marknodesdialog.h
@@ -57,10 +59,12 @@ set(SOURCE_FILES
src/profile/actiondialog.cpp
src/profile/additionalscriptsdialog.cpp
src/profile/assetsdialog.cpp
src/profile/assetedit.cpp
src/profile/assettreeitem.cpp
src/profile/assettreemodel.cpp
src/profile/cameradialog.cpp
src/profile/deltatimesdialog.cpp
src/profile/horizonsdialog.cpp
src/profile/scriptlogdialog.cpp
src/profile/line.cpp
src/profile/marknodesdialog.cpp
@@ -83,9 +87,11 @@ set(HEADER_SOURCE
include/profile/actiondialog.h
include/profile/additionalscriptsdialog.h
include/profile/assetsdialog.h
include/profile/assetedit.h
include/profile/assettreemodel.h
include/profile/cameradialog.h
include/profile/deltatimesdialog.h
include/profile/horizonsdialog.h
include/profile/scriptlogdialog.h
include/profile/marknodesdialog.h
include/profile/metadialog.h
@@ -102,13 +108,13 @@ set(HEADER_SOURCE
include/sgctedit/windowcontrol.h
)
find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Widgets REQUIRED)
find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Widgets Network REQUIRED)
set(MOC_FILES "")
set(RESOURCE_FILES "")
set(LIBRARIES "")
if (${QT_VERSION_MAJOR} EQUAL 5)
find_package(Qt5 COMPONENTS Widgets)
find_package(Qt5 COMPONENTS Core Widgets Network)
qt5_wrap_cpp(
MOC_FILES
${HEADER_SOURCE}
@@ -116,7 +122,7 @@ if (${QT_VERSION_MAJOR} EQUAL 5)
qt5_add_resources(RESOURCE_FILES resources/resources.qrc)
set(LIBRARIES )
elseif (${QT_VERSION_MAJOR} EQUAL 6)
find_package(Qt6 COMPONENTS Widgets REQUIRED)
find_package(Qt6 COMPONENTS Core Widgets Network REQUIRED)
qt6_wrap_cpp(
MOC_FILES
@@ -142,7 +148,11 @@ target_link_libraries(
openspace-ui-launcher
PUBLIC
openspace-core
Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Widgets
openspace-module-collection
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Gui
Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::Network
)
if (MSVC)

View File

@@ -58,6 +58,8 @@ private:
void actionSaved();
void clearActionFields();
void actionRejected();
void chooseScripts();
void appendScriptsToTextfield(std::string scripts);
openspace::Profile::Keybinding* selectedKeybinding();
void keybindingAdd();
@@ -80,6 +82,7 @@ private:
QLineEdit* guiPath = nullptr;
QLineEdit* documentation = nullptr;
QCheckBox* isLocal = nullptr;
QPushButton* chooseScripts = nullptr;
QTextEdit* script = nullptr;
QPushButton* addButton = nullptr;
QPushButton* removeButton = nullptr;

View File

@@ -0,0 +1,75 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* *
* 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_UI_LAUNCHER___ASSETEDIT___H__
#define __OPENSPACE_UI_LAUNCHER___ASSETEDIT___H__
#include <QDialog>
#include <filesystem>
#include <string>
namespace openspace { class Asset; }
class QBoxLayout;
class QComboBox;
class QLabel;
class QLineEdit;
class QWidget;
class AssetEdit : public QDialog {
Q_OBJECT
public:
/**
* Constructor for AssetEdit class
*/
AssetEdit(QWidget* parent);
private slots:
//void openComponent();
//void openHorizonsFile();
void openHorizons();
void approved();
private:
void createWidgets();
QBoxLayout* _layout = nullptr;
QLineEdit* _nameEdit = nullptr;
//QComboBox* _components = nullptr;
//std::filesystem::path _horizonsFile;
//QLineEdit* _horizonsFileEdit = nullptr;
QLabel* _errorMsg = nullptr;
// List of all the supported components
/*QStringList _supportedComponents = {
"Choose Component",
"Horizons Translation"
};*/
};
#endif // __OPENSPACE_UI_LAUNCHER___ASSETEDIT___H__

View File

@@ -60,6 +60,7 @@ private:
* \return the #std::string summary
*/
QString createTextSummary();
void openAssetEditor();
openspace::Profile* _profile = nullptr;
AssetTreeModel _assetTreeModel;

View File

@@ -0,0 +1,136 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* *
* 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_UI_LAUNCHER___HORIZONS___H__
#define __OPENSPACE_UI_LAUNCHER___HORIZONS___H__
#include <openspace/json.h>
#include <QDialog>
#include <filesystem>
#include <string>
#ifdef OPENSPACE_MODULE_SPACE_ENABLED
#include <modules/space/horizonsfile.h>
#endif // OPENSPACE_MODULE_SPACE_ENABLED
class QComboBox;
class QDateTimeEdit;
class QLabel;
class QLineEdit;
class QNetworkAccessManager;
class QNetworkReply;
class QPlainTextEdit;
class QProgressBar;
class HorizonsDialog : public QDialog {
Q_OBJECT
public:
/**
* Constructor for HorizonsDialog class
*/
HorizonsDialog(QWidget* parent);
#ifdef OPENSPACE_MODULE_SPACE_ENABLED
std::filesystem::path file() const;
#endif // OPENSPACE_MODULE_SPACE_ENABLED
private slots:
void openSaveAs();
void typeOnChange(int index);
void downloadProgress(qint64 ist, qint64 max);
void importTimeRange();
#ifdef OPENSPACE_MODULE_SPACE_ENABLED
void approved();
#endif // OPENSPACE_MODULE_SPACE_ENABLED
private:
enum class LogLevel {
Error,
Warning,
Info,
None
};
enum class HTTPCodes {
Ok = 200,
BadRequest = 400,
MethodNotAllowed = 405,
InternalServerError = 500,
ServiceUnavailable = 503
};
void createWidgets();
void cleanAllWidgets();
void styleLabel(QLabel* label, bool isDirty);
bool isValidInput();
nlohmann::json sendRequest(const std::string& url);
nlohmann::json handleReply(QNetworkReply* reply);
bool checkHttpStatus(const QVariant& statusCode);
void appendLog(const std::string& message, LogLevel level);
#ifdef OPENSPACE_MODULE_SPACE_ENABLED
std::pair<std::string, std::string> readTimeRange();
bool handleRequest();
std::string constructUrl();
openspace::HorizonsFile handleAnswer(nlohmann::json& answer);
bool handleResult(openspace::HorizonsResultCode& result);
openspace::HorizonsFile _horizonsFile;
#endif // OPENSPACE_MODULE_SPACE_ENABLED
QNetworkAccessManager* _manager;
QLabel* _typeLabel = nullptr;
QComboBox* _typeCombo = nullptr;
QLabel* _fileLabel = nullptr;
QLineEdit* _fileEdit = nullptr;
QLabel* _targetLabel = nullptr;
QLineEdit* _targetEdit = nullptr;
QComboBox* _chooseTargetCombo = nullptr;
std::string _targetName;
QLabel* _centerLabel = nullptr;
QLineEdit* _centerEdit = nullptr;
QComboBox* _chooseObserverCombo = nullptr;
std::string _observerName;
QLabel* _startLabel = nullptr;
QDateTimeEdit* _startEdit = nullptr;
std::string _startTime;
QLabel* _endLabel = nullptr;
QDateTimeEdit* _endEdit = nullptr;
std::string _endTime;
std::pair<std::string, std::string> _validTimeRange;
QPushButton* _importTimeButton = nullptr;
QLabel* _stepLabel = nullptr;
QLineEdit* _stepEdit = nullptr;
QComboBox* _timeTypeCombo = nullptr;
QProgressBar* _downloadProgress = nullptr;
QLabel* _downloadLabel = nullptr;
QPlainTextEdit* _log = nullptr;
QLabel* _errorMsg = nullptr;
std::string _latestHorizonsError;
};
#endif // __OPENSPACE_UI_LAUNCHER___HORIZONS___H__

View File

@@ -40,6 +40,7 @@
#include <QPushButton>
#include <QTextBrowser>
#include <QVector>
#include <memory>
#include <vector>
class DisplayWindowUnion : public QWidget {

View File

@@ -145,6 +145,29 @@ DeltaTimesDialog QListWidget {
/*
* Camera
*/
CameraDialog QLabel#error-message {
CameraDialog QLabel#error-message {
min-width: 10em;
}
/*
* Horizons dialog
*/
QPlainTextEdit#log {
font-family: Courier;
}
QComboBox#mono {
font-family: Courier;
}
QLabel#thin {
font-weight: normal;
}
QLabel#error {
color: rgb(221, 17, 17);
}
QLabel#normal {
color: rgb(0, 0, 0);
}

View File

@@ -25,6 +25,7 @@
#include "profile/actiondialog.h"
#include "profile/line.h"
#include "profile/scriptlogdialog.h"
#include <openspace/util/keys.h>
#include <ghoul/fmt.h>
#include <ghoul/misc/assert.h>
@@ -81,7 +82,7 @@ void ActionDialog::createWidgets() {
// | | Name | [oooooooooooo] | Row 2
// | | GUI Path | [oooooooooooo] | Row 3
// | | Documentation | [oooooooooooo] | Row 4
// | | Is Local | [] | Row 5
// | | Is Local | [] [choosescr] | Row 5
// | | Script | [oooooooooooo] | Row 6
// *----------------------*---------------*----------------*
// | [+] [-] | | [Save] [Cancel]| Row 7
@@ -205,6 +206,17 @@ void ActionDialog::createActionWidgets(QGridLayout* layout) {
_actionWidgets.isLocal->setEnabled(false);
layout->addWidget(_actionWidgets.isLocal, 5, 2);
_actionWidgets.chooseScripts = new QPushButton("Choose Scripts");
_actionWidgets.chooseScripts->setToolTip(
"Press this button to choose scripts for your action from the logs/scriptlog.txt"
);
connect(
_actionWidgets.chooseScripts, &QPushButton::clicked,
this, &ActionDialog::chooseScripts
);
_actionWidgets.chooseScripts->setEnabled(false);
layout->addWidget(_actionWidgets.chooseScripts, 5, 2, Qt::AlignRight);
layout->addWidget(new QLabel("Script"), 6, 1);
_actionWidgets.script = new QTextEdit;
_actionWidgets.script->setToolTip(
@@ -499,6 +511,7 @@ void ActionDialog::actionSelected() {
_actionWidgets.documentation->setEnabled(true);
_actionWidgets.isLocal->setChecked(action->isLocal);
_actionWidgets.isLocal->setEnabled(true);
_actionWidgets.chooseScripts->setEnabled(true);
_actionWidgets.script->setText(QString::fromStdString(action->script));
_actionWidgets.script->setEnabled(true);
_actionWidgets.addButton->setEnabled(false);
@@ -589,6 +602,7 @@ void ActionDialog::clearActionFields() {
_actionWidgets.documentation->setEnabled(false);
_actionWidgets.isLocal->setChecked(false);
_actionWidgets.isLocal->setEnabled(false);
_actionWidgets.chooseScripts->setEnabled(false);
_actionWidgets.script->clear();
_actionWidgets.script->setEnabled(false);
_actionWidgets.saveButtons->setEnabled(false);
@@ -604,6 +618,16 @@ void ActionDialog::actionRejected() {
clearActionFields();
}
void ActionDialog::chooseScripts() {
ScriptlogDialog d(this);
connect(&d, &ScriptlogDialog::scriptsSelected, this, &ActionDialog::appendScriptsToTextfield);
d.exec();
}
void ActionDialog::appendScriptsToTextfield(std::string scripts) {
_actionWidgets.script->append(QString::fromStdString(std::move(scripts)));
}
Profile::Keybinding* ActionDialog::selectedKeybinding() {
QListWidgetItem* item = _keybindingWidgets.list->currentItem();
const int idx = _keybindingWidgets.list->row(item);

View File

@@ -0,0 +1,208 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* *
* 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 "profile/assetedit.h"
#include "profile/horizonsdialog.h"
#include "profile/line.h"
#include <openspace/scene/asset.h>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QFileDialog>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
AssetEdit::AssetEdit(QWidget* parent)
: QDialog(parent)
{
setWindowTitle("Asset Editor");
createWidgets();
}
void AssetEdit::createWidgets() {
_layout = new QVBoxLayout(this);
{
QLabel* heading =
new QLabel("The Asset Editor is coming soon in a future release", this);
heading->setObjectName("heading");
_layout->addWidget(heading);
}
{
QPushButton* generateButton = new QPushButton("Generate Horizons File", this);
connect(
generateButton,
&QPushButton::released,
this,
&AssetEdit::openHorizons
);
generateButton->setDefault(false);
// In order to generate a Horizons File the Space module is required
#ifndef OPENSPACE_MODULE_SPACE_ENABLED
generateButton->setEnabled(false);
generateButton->setToolTip(
"Cannot generate Horizons file without the space module enabled"
);
#else
generateButton->setCursor(Qt::PointingHandCursor);
#endif // OPENSPACE_MODULE_SPACE_ENABLED
_layout->addWidget(generateButton);
}
/*{
QBoxLayout* container = new QHBoxLayout(this);
QLabel* assetLabel = new QLabel("Asset Name:");
assetLabel->setObjectName("profile");
container->addWidget(assetLabel);
_nameEdit = new QLineEdit();
container->addWidget(_nameEdit);
_layout->addLayout(container);
}
_layout->addWidget(new Line);
{
QBoxLayout* container = new QHBoxLayout(this);
_components = new QComboBox(this);
_components->addItems(_supportedComponents);
_components->setCurrentIndex(0);
container->addWidget(_components);
QPushButton* addButton = new QPushButton("Add", this);
connect(addButton, &QPushButton::clicked, this, &AssetEdit::openComponent);
addButton->setCursor(Qt::PointingHandCursor);
container->addWidget(addButton);
_layout->addLayout(container);
}*/
_layout->addWidget(new Line);
{
QBoxLayout* footer = new QHBoxLayout;
_errorMsg = new QLabel;
_errorMsg->setObjectName("error-message");
_errorMsg->setWordWrap(true);
footer->addWidget(_errorMsg);
QDialogButtonBox* buttons = new QDialogButtonBox;
buttons->setStandardButtons(QDialogButtonBox::Save | QDialogButtonBox::Cancel);
connect(buttons, &QDialogButtonBox::accepted, this, &AssetEdit::approved);
connect(buttons, &QDialogButtonBox::rejected, this, &AssetEdit::reject);
footer->addWidget(buttons);
_layout->addLayout(footer);
}
}
/*void AssetEdit::openComponent() {
switch (_components->currentIndex()) {
case 0:
_errorMsg->setText("Choose a component to add to the asset");
break;
case 1: {
QBoxLayout* horizonsLayout = new QVBoxLayout(this);
{
QLabel* label = new QLabel("Horizons Translation:", this);
label->setObjectName("heading");
horizonsLayout->addWidget(label);
}
{
QBoxLayout* container = new QHBoxLayout(this);
QLabel* fileLabel = new QLabel("File path:", this);
container->addWidget(fileLabel);
_horizonsFileEdit = new QLineEdit(this);
container->addWidget(_horizonsFileEdit);
QPushButton* fileButton = new QPushButton("Browse", this);
connect(
fileButton,
&QPushButton::released,
this,
&AssetEdit::openHorizonsFile
);
fileButton->setCursor(Qt::PointingHandCursor);
container->addWidget(fileButton);
QPushButton* generateButton = new QPushButton("Generate", this);
connect(
generateButton,
&QPushButton::released,
this,
&AssetEdit::openHorizons
);
// In order to generate a Horizons File the Space module is required
#ifndef OPENSPACE_MODULE_SPACE_ENABLED
generateButton->setEnabled(false);
generateButton->setToolTip(
"Connot generate Horizons file without the space module enabled"
);
#else
generateButton->setCursor(Qt::PointingHandCursor);
#endif // OPENSPACE_MODULE_SPACE_ENABLED
container->addWidget(generateButton);
horizonsLayout->addLayout(container);
}
horizonsLayout->addWidget(new Line);
_layout->insertLayout(_layout->count() - 3, horizonsLayout);
break;
}
default:
_errorMsg->setText("Unkown component");
break;
}
}
void AssetEdit::openHorizonsFile() {
std::string filePath = QFileDialog::getOpenFileName(
this,
tr("Open Horizons file"),
"",
tr("Horiozons file (*.dat)")
).toStdString();
_horizonsFile = std::filesystem::absolute(filePath);
_horizonsFileEdit->setText(QString(_horizonsFile.string().c_str()));
}*/
void AssetEdit::openHorizons() {
_errorMsg->clear();
#ifdef OPENSPACE_MODULE_SPACE_ENABLED
HorizonsDialog* horizonsDialog = new HorizonsDialog(this);
horizonsDialog->exec();
//_horizonsFile = horizonsDialog->file();
//_horizonsFileEdit->setText(QString(_horizonsFile.string().c_str()));
#endif // OPENSPACE_MODULE_SPACE_ENABLED
}
void AssetEdit::approved() {
/*std::string assetName = _nameEdit->text().toStdString();
if (assetName.empty()) {
_errorMsg->setText("Asset name must be specified");
return;
}*/
accept();
}

View File

@@ -24,12 +24,15 @@
#include "profile/assetsdialog.h"
#include "profile/assetedit.h"
#include "profile/line.h"
#include <openspace/scene/profile.h>
#include <ghoul/fmt.h>
#include <QDialogButtonBox>
#include <QHeaderView>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <QTextEdit>
#include <QTreeView>
@@ -134,9 +137,22 @@ AssetsDialog::AssetsDialog(QWidget* parent, openspace::Profile* profile,
QBoxLayout* layout = new QVBoxLayout(this);
{
QGridLayout* container = new QGridLayout;
container->setColumnStretch(1, 1);
QLabel* heading = new QLabel("Select assets from /data/assets");
heading->setObjectName("heading");
layout->addWidget(heading);
container->addWidget(heading, 0, 0);
QPushButton* newAssetButton = new QPushButton("New Asset", this);
connect(
newAssetButton, &QPushButton::released,
this, &AssetsDialog::openAssetEditor
);
newAssetButton->setCursor(Qt::PointingHandCursor);
newAssetButton->setDefault(false);
container->addWidget(newAssetButton, 0, 2);
layout->addLayout(container);
}
{
_assetTree = new QTreeView;
@@ -224,6 +240,11 @@ QString AssetsDialog::createTextSummary() {
return summary;
}
void AssetsDialog::openAssetEditor() {
AssetEdit editor(this);
editor.exec();
}
void AssetsDialog::parseSelections() {
_profile->assets.clear();
std::vector<std::string> summaryPaths;

File diff suppressed because it is too large Load Diff

View File

@@ -38,7 +38,7 @@ ScriptlogDialog::ScriptlogDialog(QWidget* parent)
{
setWindowTitle("Scriptlog");
createWidgets();
QFile file(QString::fromStdString(absPath("${LOGS}/scriptLog.txt").string()));
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream in(&file);
@@ -65,7 +65,7 @@ void ScriptlogDialog::createWidgets() {
_scriptlogList = new QListWidget;
_scriptlogList->setSelectionMode(QAbstractItemView::SelectionMode::MultiSelection);
layout->addWidget(_scriptlogList);
layout->addWidget(new Line);
{
QDialogButtonBox* buttons = new QDialogButtonBox;

View File

@@ -28,6 +28,7 @@
#include <openspace/engine/openspaceengine.h>
#include <openspace/engine/windowdelegate.h>
#include <openspace/interaction/joystickinputstate.h>
#include <openspace/scripting/scriptengine.h>
#include <openspace/util/keys.h>
#include <ghoul/ghoul.h>
#include <ghoul/filesystem/filesystem.h>
@@ -810,6 +811,36 @@ void setSgctDelegateFunctions() {
vec2 scale = currentWindow->scale();
return glm::vec2(scale.x, scale.y);
};
sgctDelegate.osDpiScaling = []() {
ZoneScoped
// Detect which DPI scaling to use
// 1. If there is a GUI window, use the GUI window's content scale value
const Window* dpiWindow = nullptr;
for (const std::unique_ptr<Window>& window : Engine::instance().windows()) {
if (window->hasTag("GUI")) {
dpiWindow = window.get();
break;
}
}
// 2. If there isn't a GUI window, use the first window's value
if (!dpiWindow) {
dpiWindow = Engine::instance().windows().front().get();
}
glm::vec2 scale = glm::vec2(1.f, 1.f);
glfwGetWindowContentScale(dpiWindow->windowHandle(), &scale.x, &scale.y);
if (scale.x != scale.y) {
LWARNING(fmt::format(
"Non-square window scaling detected ({0}x{1}), using {0}x{0} instead",
scale.x, scale.y
));
}
return scale.x;
};
sgctDelegate.hasGuiWindow = []() {
ZoneScoped

View File

@@ -1,64 +0,0 @@
##########################################################################################
# #
# OpenSpace #
# #
# Copyright (c) 2014-2022 #
# #
# 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_CMAKE_EXT_DIR}/application_definition.cmake)
set_source_files_properties(
${CMAKE_CURRENT_SOURCE_DIR}/openspace.icns
PROPERTIES MACOSX_PACKAGE_LOCATION "Resources"
)
set(MACOSX_BUNDLE_ICON_FILE openspace.icns)
create_new_application(
Wormhole MACOSX_BUNDLE
${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/openspace.rc
${CMAKE_CURRENT_SOURCE_DIR}/openspace.icns
)
target_link_libraries(Wormhole PRIVATE openspace-core openspace-module-collection)
# Web Browser and Web gui
# Why not put these in the module's path? Because they do not have access to the
# target as of July 2017, which is needed.
if (OPENSPACE_MODULE_WEBBROWSER AND CEF_ROOT)
# wanted by CEF
set(CMAKE_BUILD_TYPE Debug CACHE STRING "CMAKE_BUILD_TYPE")
set(PROJECT_ARCH "x86_64")
if (WIN32)
set(RESOURCE_FILE ${OPENSPACE_APPS_DIR}/OpenSpace/openspace.rc)
endif ()
# Add the CEF binary distribution's cmake/ directory to the module path and
# find CEF to initialize it properly.
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${WEBBROWSER_MODULE_PATH}/cmake")
include(webbrowser_helpers)
set_cef_targets("${CEF_ROOT}" Wormhole)
run_cef_platform_config("${CEF_ROOT}" "${CEF_TARGET}" "${WEBBROWSER_MODULE_PATH}")
elseif (OPENSPACE_MODULE_WEBBROWSER)
message(WARNING "Web configured to be included, but no CEF_ROOT was found, please try configuring CMake again.")
endif ()

View File

@@ -1 +0,0 @@
set(DEFAULT_APPLICATION ON)

View File

@@ -1,133 +0,0 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* *
* 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/network/parallelserver.h>
#include <ghoul/fmt.h>
#include <ghoul/cmdparser/commandlineparser.h>
#include <ghoul/cmdparser/singlecommand.h>
#include <ghoul/logging/logmanager.h>
#include <iomanip>
namespace {
constexpr const char*_loggerCat = "Wormhole";
} // namespace
int main(int argc, char** argv) {
using namespace openspace;
using namespace ghoul::cmdparser;
std::vector<std::string> arguments(argv, argv + argc);
CommandlineParser commandlineParser(
"Wormhole",
CommandlineParser::AllowUnknownCommands::Yes
);
struct {
std::string port;
std::string password;
std::string changeHostPassword;
} settings;
commandlineParser.addCommand(
std::make_unique<ghoul::cmdparser::SingleCommand<std::string>>(
settings.port,
"--port",
"-p",
"Sets the port to listen on"
)
);
commandlineParser.addCommand(
std::make_unique<ghoul::cmdparser::SingleCommand<std::string>>(
settings.password,
"--password",
"-l",
"Sets the password to use"
)
);
commandlineParser.addCommand(
std::make_unique<ghoul::cmdparser::SingleCommand<std::string>>(
settings.changeHostPassword,
"--hostpassword",
"-h",
"Sets the host password to use"
)
);
ghoul::logging::LogManager::initialize(
ghoul::logging::LogLevel::Debug,
ghoul::logging::LogManager::ImmediateFlush::Yes
);
commandlineParser.setCommandLine(arguments);
commandlineParser.execute();
if (settings.password.empty()) {
std::stringstream defaultPassword;
defaultPassword << std::hex << std::setfill('0') << std::setw(6) <<
(std::hash<size_t>{}(
std::chrono::system_clock::now().time_since_epoch().count()
) % 0xffffff);
settings.password = defaultPassword.str();
}
if (settings.changeHostPassword.empty()) {
std::stringstream defaultChangeHostPassword;
defaultChangeHostPassword << std::hex << std::setfill('0') << std::setw(6) <<
(std::hash<size_t>{}(
std::chrono::system_clock::now().time_since_epoch().count() + 1
) % 0xffffff);
settings.changeHostPassword = defaultChangeHostPassword.str();
}
LINFO(fmt::format("Connection password: {}", settings.password));
LINFO(fmt::format("Host password: {}", settings.changeHostPassword));
int port = 25001;
if (!settings.port.empty()) {
try {
port = std::stoi(settings.port);
}
catch (const std::invalid_argument&) {
LERROR(fmt::format("Invalid port: {}", settings.port));
}
}
ParallelServer server;
server.start(port, settings.password, settings.changeHostPassword);
server.setDefaultHostAddress("127.0.0.1");
LINFO(fmt::format("Server listening to port {}", port));
while (std::cin.get() != 'q') {
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
server.stop();
LINFO("Server stopped");
return 0;
}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -1 +0,0 @@
IDI_ICON1 ICON DISCARDABLE "openspace.ico"

View File

@@ -5,13 +5,16 @@ local propertyHelper = asset.require("util/property_helper")
-- Specifying which other assets should be loaded in this scene
asset.require("spice/base")
-- Load default key bindings applicable to most scenes
asset.require("dashboard/default_dashboard")
-- Load default key bindings applicable to most scenes
asset.require("util/default_keybindings")
-- Load web gui
local webGui = asset.require("util/webgui")
-- Scale the different UI components based on the operating system's DPI scaling value
asset.require("util/dpiscaling")
local toggle_trails = {
Identifier = "os_default.toggle_trails",
Name = "Toggle Trails",

View File

@@ -4,7 +4,7 @@ local trajectory = asset.syncedResource({
Name = "C-2019 Q4 Borisov Trajectory",
Type = "HttpSynchronization",
Identifier = "borisov_horizons",
Version = 1
Version = 2
})
local C2019Q4BorisovTrail = {
@@ -14,18 +14,23 @@ local C2019Q4BorisovTrail = {
Type = "RenderableTrailTrajectory",
Translation = {
Type = "HorizonsTranslation",
HorizonsTextFile = trajectory .. "horizons_c2019q4borisov.dat"
HorizonsTextFile = trajectory .. "horizons_c2019q4borisov.hrz"
-- Target: Borisov (C/2019 Q4)
-- Observer: SSB
-- Start time: 2015-Jan-01 00:00:00 (first data point)
-- End time: 2050-Jan-01 00:00:00 (last data point)
-- Step size: 1 day
},
Color = { 0.9, 0.9, 0.0 },
StartTime = "2015 JAN 01 00:00:00",
EndTime = "2024 JAN 01 00:00:00",
SampleInterval = 60 * 60 * 24 * 7
EndTime = "2050 JAN 01 00:00:00",
SampleInterval = 60*60*24 -- = 86 400 seconds in 1 day
},
GUI = {
Name = "C/2019 Q4 Borisov Trail",
Path = "/Solar System/Interstellar",
Description = [[Trail of C-2019 Q4 Borisov from 2015 JAN 01 00:00:00
to 2024 JAN 01 00:00:00. Data from JPL Horizons]]
to 2050 JAN 01 00:00:00. Data from JPL Horizons]]
}
}
@@ -35,14 +40,14 @@ local C2019Q4BorisovPosition = {
Transform = {
Translation = {
Type = "HorizonsTranslation",
HorizonsTextFile = trajectory .. "horizons_c2019q4borisov.dat"
HorizonsTextFile = trajectory .. "horizons_c2019q4borisov.hrz"
}
},
GUI = {
Name = "C/2019 Q4 Borisov",
Path = "/Solar System/Interstellar",
Description = [[Position of C-2019 Q4 Borisov from 2015 JAN 01 00:00:00
to 2024 JAN 01 00:00:00. Data from JPL Horizons]]
to 2050 JAN 01 00:00:00. Data from JPL Horizons]]
}
}
@@ -63,10 +68,10 @@ asset.export(C2019Q4BorisovTrail)
asset.meta = {
Name = "C/2019 Q4 Borisov",
Version = "1.1",
Version = "1.2",
Description = [[ This asset contains the trail
and position of C-2019 Q4 Borisov from 2015 JAN 01 00:00:00
to 2024 JAN 01 00:00:00. Data from JPL Horizons']],
to 2050 JAN 01 00:00:00. Data from JPL Horizons]],
Author = "OpenSpace Team",
URL = "https://ssd.jpl.nasa.gov/horizons.cgi",
License = [[ JPL-authored documents are sponsored by NASA under Contract

View File

@@ -4,7 +4,7 @@ local trajectory = asset.syncedResource({
Name = "'Oumuamua Trajectory",
Type = "HttpSynchronization",
Identifier = "oumuamua_horizons",
Version = 1
Version = 2
})
local OumuamuaTrail = {
@@ -14,18 +14,23 @@ local OumuamuaTrail = {
Type = "RenderableTrailTrajectory",
Translation = {
Type = "HorizonsTranslation",
HorizonsTextFile = trajectory .. "horizons_oumuamua.dat"
HorizonsTextFile = trajectory .. "horizons_oumuamua.hrz"
-- Target: 1I/'Oumuamua (A/2017 U1)
-- Observer: SSB
-- Start time: 2014-Jan-01 00:00:00 (first data point)
-- End time: 2050-Jan-01 00:00:00 (last data point)
-- Step size: 1 day
},
Color = { 0.9, 0.9, 0.0 },
StartTime = "2014 JAN 01 00:00:00",
EndTime = "2023 JAN 01 00:00:00",
SampleInterval = 7000,
EndTime = "2050 JAN 01 00:00:00",
SampleInterval = 86400,
TimeStampSubsampleFactor = 1
},
GUI = {
Name = "'Oumuamua Trail",
Path = "/Solar System/Interstellar",
Description = [[Trail of 'Oumuamua from 2014 JAN 01 00:00:00 to 2023 JAN 01 00:00:00.
Description = [[Trail of 'Oumuamua from 2014 JAN 01 00:00:00 to 2050 JAN 01 00:00:00.
Data from JPL Horizons']],
}
}
@@ -36,13 +41,13 @@ local OumuamuaPosition = {
Transform = {
Translation = {
Type = "HorizonsTranslation",
HorizonsTextFile = trajectory .. "horizons_oumuamua.dat"
HorizonsTextFile = trajectory .. "horizons_oumuamua.hrz"
},
},
GUI = {
Name = "'Oumuamua",
Path = "/Solar System/Interstellar",
Description = [[ Position of 'Oumuamua from 2014 JAN 01 00:00:00 to 2023 JAN 01
Description = [[ Position of 'Oumuamua from 2014 JAN 01 00:00:00 to 2050 JAN 01
00:00:00. Data from JPL Horizons']],
}
}
@@ -64,9 +69,9 @@ asset.export(OumuamuaTrail)
asset.meta = {
Name = "'Oumuamua",
Version = "1.1",
Version = "1.2",
Description = [[ This asset contains the trail and position of 'Oumuamua from 2014
JAN 01 00:00:00 to 2023 JAN 01 00:00:00. Data from JPL Horizons']],
JAN 01 00:00:00 to 2050 JAN 01 00:00:00. Data from JPL Horizons]],
Author = "OpenSpace Team",
URL = "https://ssd.jpl.nasa.gov/horizons.cgi",
License = "NASA"

View File

@@ -5,27 +5,30 @@ local trail = asset.syncedResource({
Name = "Gaia Trail",
Type = "HttpSynchronization",
Identifier = "gaia_trail",
Version = 2
Version = 3
})
local GaiaTrail = {
Identifier = "GaiaTrail",
Parent = earthTransforms.EarthCenter.Identifier,
Renderable = {
Type = "RenderableTrailTrajectory",
Type = "RenderableTrailOrbit",
Translation = {
Type = "HorizonsTranslation",
HorizonsTextFile = trail .. "gaia_orbit_horizons.dat"
HorizonsTextFile = {
trail .. "gaia_orbit_horizons_1.hrz", --2013-12-19 09:55:27 to 2014-01-19 00:00:00
trail .. "gaia_orbit_horizons_2.hrz" --2014-01-19 00:00:00 to 2026-09-14 00:00:00
-- Target: Gaia (spacecraft) (-139479)
-- Observer: Earth (399 GEOCENTRIC)
-- Start time: 2013-Dec-19 09:55:27 (first data point)
-- End time: 2026-Sep-14 00:00:00 (last data point)
-- Step size: First file has 40 minutes and seocnd has 1 day step size
}
},
Color = { 0.0, 0.8, 0.7 },
ShowFullTrail = false,
StartTime = "2013 DEC 19 09:55:10",
EndTime = "2019 JUN 20 05:55:10",
PointSize = 5,
SampleInterval = 12000,
TimeStampSubsampleFactor = 1,
EnableFade = true,
Rendering = "Lines"
Period = 365, -- 1 year orbit time
Resolution = 24244 -- Sameple rate of 40 minutes
},
GUI = {
Name = "Gaia Trail",
@@ -37,21 +40,21 @@ local GaiaTrailEclip = {
Identifier = "GaiaTrail_Eclip",
Parent = sunTransforms.SunCenter.Identifier,
Renderable = {
Type = "RenderableTrailTrajectory",
Type = "RenderableTrailOrbit",
Enabled = false,
Translation = {
Type = "HorizonsTranslation",
HorizonsTextFile = trail .. "gaia_orbit_horizons_sun.dat"
HorizonsTextFile = trail .. "gaia_orbit_horizons_sun.hrz"
-- Target: Gaia (spacecraft) (-139479)
-- Observer: Sun (10 BODYCENTRIC)
-- Start time: 2013-Dec-20 00:00:00 (first data point)
-- End time: 2026-Sep-14 00:00:00 (last data point)
-- Step size: 1 day
},
Color = { 1.0, 0.0, 0.0 },
ShowFullTrail = false,
StartTime = "2013 DEC 19 09:55:10",
EndTime = "2019 JUN 20 05:55:10",
PointSize = 5,
SampleInterval = 6000,
TimeStampSubsampleFactor = 1,
EnableFade = true,
Rendering = "Lines"
Period = 365, -- 1 year orbit time
Resolution = 365 -- Sameple rate of 1 day
},
GUI = {
Name = "Gaia Ecliptic Trail",
@@ -71,3 +74,14 @@ end)
asset.export(GaiaTrail)
asset.export(GaiaTrailEclip)
asset.meta = {
Name = "Gaia Trail",
Version = "1.0",
Description = [[ This asset contains the trail of Gaia around both Earth and the Sun.
Data from JPL Horizons from 2013 DEC 19 09:55:27 to 2026 SEP 14 21:15:27]],
Author = "OpenSpace Team",
URL = "https://ssd.jpl.nasa.gov/horizons.cgi",
License = "NASA"
}

View File

@@ -5,7 +5,7 @@ local trail = asset.syncedResource({
Name = "Gaia Trail",
Type = "HttpSynchronization",
Identifier = "gaia_trail",
Version = 1
Version = 3
})
local GaiaPosition = {
@@ -14,7 +14,15 @@ local GaiaPosition = {
Transform = {
Translation = {
Type = "HorizonsTranslation",
HorizonsTextFile = trail .. "gaia_orbit_horizons.dat"
HorizonsTextFile = {
trail .. "gaia_orbit_horizons_1.hrz", --2013-12-19 09:55:27 to 2014-01-19 00:00:00
trail .. "gaia_orbit_horizons_2.hrz" --2014-01-19 00:00:00 to 2026-09-14 00:00:00
-- Target: Gaia (spacecraft) (-139479)
-- Observer: Earth (399 GEOCENTRIC)
-- Start time: 2013-Dec-19 09:55:27 (first data point)
-- End time: 2026-Sep-14 00:00:00 (last data point)
-- Step size: First file has 40 minutes and seocnd has 1 day step size
}
},
},
GUI = {
@@ -32,3 +40,14 @@ asset.onDeinitialize(function()
end)
asset.export(GaiaPosition)
asset.meta = {
Name = "Gaia Transform",
Version = "1.0",
Description = [[ This asset contains the position of Gaia from 2013
DEC 19 09:55:27 to 2026 SEP 14 21:15:27. Data from JPL Horizons]],
Author = "OpenSpace Team",
URL = "https://ssd.jpl.nasa.gov/horizons.cgi",
License = "NASA"
}

View File

@@ -1,5 +1,19 @@
local sampleInterval = 24*60*60
local voyager_horizons = asset.syncedResource({
Name = "Voyager horizons",
Type = "HttpSynchronization",
Identifier = "voyager_horizons",
Version = 1
})
local pioneer_horizons = asset.syncedResource({
Name = "Pioneer horizons",
Type = "HttpSynchronization",
Identifier = "pioneer_horizons",
Version = 1
})
local voyager1 = {
Identifier = "Voyager1",
Parent = "SolarSystemBarycenter",
@@ -8,19 +22,24 @@ local voyager1 = {
Enabled = false,
Translation = {
Type = "HorizonsTranslation",
HorizonsTextFile = asset.localResource("voyager_1.txt")
HorizonsTextFile = voyager_horizons .. "voyager_1.hrz"
-- Target: Voyager 1 (spacecraft) (-31)
-- Observer: SSB
-- Start time: 1977-Sep-06 00:00:00 (first data point)
-- End time: 2030-Jan-01 00:00:00 (last data point)
-- Step size: 1 day
},
Color = { 0.9, 0.9, 0.0 },
Fade = 5.0,
StartTime = "1977 SEP 06 00:00:00",
EndTime = "2030 DEC 31 00:00:00",
EndTime = "2030 JAN 01 00:00:00",
SampleInterval = sampleInterval,
TimeStampSubsampleFactor = 1
},
GUI = {
Name = "Voyager 1 Trail",
Path = "/Solar System/Missions/Voyager",
Description = [[Voyager 1 Trail, spanning September 6th, 1977 to December 31st,
Description = [[Voyager 1 Trail, spanning September 6th, 1977 to January 1st,
2030. Data from JPL Horizons.]]
}
}
@@ -33,19 +52,24 @@ local voyager2 = {
Enabled = false,
Translation = {
Type = "HorizonsTranslation",
HorizonsTextFile = asset.localResource("voyager_2.txt")
HorizonsTextFile = voyager_horizons .. "voyager_2.hrz"
-- Target: Voyager 2 (spacecraft) (-32)
-- Observer: SSB
-- Start time: 1977-Aug-21 00:00:00 (first data point)
-- End time: 2030-Jan-01 00:00:00 (last data point)
-- Step size: 1 day
},
Color = { 0.9, 0.9, 0.0 },
Fade = 5.0,
StartTime = "1977 AUG 21 00:00:00",
EndTime = "2030 DEC 31 00:00:00",
EndTime = "2030 JAN 01 00:00:00",
SampleInterval = sampleInterval,
TimeStampSubsampleFactor = 1
},
GUI = {
Name = "Voyager 2 Trail",
Path = "/Solar System/Missions/Voyager",
Description = [[Voyager 2 Trail, spanning August 21st, 1977 to December 31st, 2030.
Description = [[Voyager 2 Trail, spanning August 21st, 1977 to January 1st, 2030.
Data from JPL Horizons.]]
}
}
@@ -58,19 +82,24 @@ local pioneer10 = {
Enabled = false,
Translation = {
Type = "HorizonsTranslation",
HorizonsTextFile = asset.localResource("pioneer_10.txt")
HorizonsTextFile = pioneer_horizons .. "pioneer_10.hrz"
-- Target: Pioneer 10 (spacecraft) (-23)
-- Observer: SSB
-- Start time: 1972-Mar-04 00:00:00 (first data point)
-- End time: 2030-Jan-01 00:00:00 (last data point)
-- Step size: 1 day
},
Color = { 0.9, 0.3, 0.0 },
Fade = 5.0,
StartTime = "1972 MAR 04 00:00:00",
EndTime = "2030 DEC 31 00:00:00",
EndTime = "2030 JAN 01 00:00:00",
SampleInterval = sampleInterval,
TimeStampSubsampleFactor = 1
},
GUI = {
Name = "Pioneer 10 Trail",
Path = "/Solar System/Missions/Pioneer",
Description = [[Pioneer 10 Trail, spanning March 4th, 1972 to December 31st, 2030.
Description = [[Pioneer 10 Trail, spanning March 4th, 1972 to January 1st, 2030.
Data from JPL Horizons.]]
}
}
@@ -83,19 +112,24 @@ local pioneer11 ={
Enabled = false,
Translation = {
Type = "HorizonsTranslation",
HorizonsTextFile = asset.localResource("pioneer_11.txt")
HorizonsTextFile = pioneer_horizons .. "pioneer_11.hrz"
-- Target: Pioneer 11 (spacecraft) (-24)
-- Observer: SSB
-- Start time: 1973-Apr-07 00:00:00 (first data point)
-- End time: 2030-Jan-01 00:00:00 (last data point)
-- Step size: 1 day
},
Color = { 0.9, 0.3, 0.0 },
Fade = 5.0,
StartTime = "1973 APR 07 00:00:00",
EndTime = "2030 DEC 31 00:00:00",
EndTime = "2030 JAN 01 00:00:00",
SampleInterval = sampleInterval,
TimeStampSubsampleFactor = 1
},
GUI = {
Name = "Pioneer 11 Trail",
Path = "/Solar System/Missions/Pioneer",
Description = [[Pioneer 11 Trail, spanning April 7th, 1973 to December 31st, 2030.
Description = [[Pioneer 11 Trail, spanning April 7th, 1973 to January 1st, 2030.
Data from JPL Horizons.]]
}
}
@@ -123,10 +157,10 @@ asset.export(pioneer11)
asset.meta = {
Name = "Pioneer and Voyager Trails",
Version = "1.0",
Version = "1.1",
Description = [[ Pioneer 10, Pioneer 11, Voyager 1 and Voyager 2 trails. Driven by JPL
Horizons data for better performance then spice but lower resolution. Data is from
shortly after mission launches until December 31st, 2030.]],
shortly after mission launches until January 1st, 2030.]],
Author = "OpenSpace Team",
URL = "http://openspaceproject.com",
License = "MIT license",

View File

@@ -1,26 +1,29 @@
local sunTransforms = asset.require("scene/solarsystem/sun/transforms")
local trajectory = asset.syncedResource({
local horizons = asset.syncedResource({
Name = "5 Astraea Trajectory",
Type = "HttpSynchronization",
Identifier = "astraea_horizons",
Version = 1
})
})
local AstraeaTrail = {
Identifier = "AstraeaTrail",
Parent = sunTransforms.SolarSystemBarycenter.Identifier,
Renderable = {
Type = "RenderableTrailTrajectory",
Type = "RenderableTrailOrbit",
Translation = {
Type = "HorizonsTranslation",
HorizonsTextFile = trajectory .. "horizons_astraea.dat"
HorizonsTextFile = horizons .. "horizons_astraea.hrz"
-- Target: 5 Astraea (A845 XA)
-- Observer: SSB
-- Start time: 2014-Jan-01 00:00 (first data point)
-- End time: 2050-Jan-01 00:00 (last data point)
-- Step size: 1 day
},
Color = { 0.9, 0.9, 0.0 },
StartTime = "2014 JAN 01 00:00:00",
EndTime = "2023 JAN 01 00:00:00",
SampleInterval = 7000,
TimeStampSubsampleFactor = 1
Period = 1507, -- Orbit time 1507 days
Resolution = 1507 -- Step size of 1 day
},
GUI = {
Name = "5 Astraea Trail",
@@ -34,7 +37,7 @@ local AstraeaPosition = {
Transform = {
Translation = {
Type = "HorizonsTranslation",
HorizonsTextFile = trajectory .. "horizons_astraea.dat"
HorizonsTextFile = horizons .. "horizons_astraea.hrz"
}
},
GUI = {
@@ -60,10 +63,10 @@ asset.export(AstraeaTrail)
asset.meta = {
Name = "5 Astraea",
Version = "1.1",
Version = "1.2",
Description = [[ This asset contains the trail
and position of 5 Astraea from 2014 JAN 01 00:00:00
to 2023 JAN 01 00:00:00. Data from JPL Horizons']],
to 2050 JAN 01 00:00:00. Data from JPL Horizons']],
Author = "Zach Shaffer",
URL = "https://ssd.jpl.nasa.gov/horizons.cgi",
License = [[ JPL-authored documents are sponsored by NASA under Contract

View File

@@ -4,8 +4,8 @@ local orbit = asset.syncedResource({
Name = "Comet C/2019 Y4 ATLAS",
Type = "HttpSynchronization",
Identifier = "horizons_c2019y4atlas",
Version = 1
}) .. "c2019y4atlas.txt"
Version = 2
}) .. "c2019y4atlas.hrz"
local C2019Y4AtlasTrail = {
Identifier = "C2019Y4AtlasTrail",
@@ -15,12 +15,17 @@ local C2019Y4AtlasTrail = {
Translation = {
Type = "HorizonsTranslation",
HorizonsTextFile = orbit
-- Target: ATLAS (C/2019 Y4)
-- Observer: SSB
-- Start time: 1950-Jan-01 00:00:00.000 (first data point)
-- End time: 2099-Dec-31 23:59:59.9985 (last data point)
-- Step size: 90 000 steps
},
Color = { 0.533333, 0.850980, 0.996078 },
EnableFade = false,
StartTime = "1950 JAN 1 0:00:00",
EndTime = "2100 JAN 1 00:00:00",
SampleInterval = 35000,
SampleInterval = 52595, -- About 14h 36m 35.5200s in seconds
TimeStampSubsampleFactor = 1
},
GUI = {
@@ -62,7 +67,7 @@ asset.export(C2019Y4AtlasTrail)
asset.meta = {
Name = "C/2019 Y4 Atlas",
Version = "1.1",
Version = "1.2",
Description = [[ This asset contains the trail and position of C/2019 Y4 Atlas from
1950 JAN 01 00:00:00 to 2100 JAN 01 00:00:00. Data from JPL Horizons]],
Author = "OpenSpace Team",

View File

@@ -4,22 +4,26 @@ local sync = asset.syncedResource({
Name = "Swift Tuttle Orbit",
Type = "HttpSynchronization",
Identifier = "swift_tuttle_horizons",
Version = 1
Version = 2
})
local SwiftTuttleTrail = {
Identifier = "SwiftTuttleTrail",
Parent = sunTransforms.SolarSystemBarycenter.Identifier,
Renderable = {
Type = "RenderableTrailTrajectory",
Type = "RenderableTrailOrbit",
Translation = {
Type = "HorizonsTranslation",
HorizonsTextFile = sync .. "horizons_swifttuttle.dat"
HorizonsTextFile = sync .. "horizons_swifttuttle.hrz"
-- Target: 109P/Swift-Tuttle (1995 epoch)
-- Observer: SSB
-- Start time: 1879-Jun-27 00:00:00 (first data point)
-- End time: 2050-Jan-01 00:00:00 (last data point)
-- Step size: 1 day
},
Color = { 0.9, 0.9, 0.0 },
StartTime = "1879 JUN 27 00:00:00",
EndTime = "1879 JUN 27 00:00:00",
SampleInterval = 60
Period = 48577.2124, -- Orbit time 133 years
Resolution = 48577 -- Step size: 1 day
},
GUI = {
Name = "Swift Tuttle Trail",
@@ -33,7 +37,7 @@ local SwiftTuttlePosition = {
Transform = {
Translation = {
Type = "HorizonsTranslation",
HorizonsTextFile = sync .. "horizons_swifttuttle.dat"
HorizonsTextFile = sync .. "horizons_swifttuttle.hrz"
}
},
GUI = {
@@ -59,9 +63,9 @@ asset.export(SwiftTuttleTrail)
asset.meta = {
Name = "Swift Tuttle",
Version = "1.1",
Version = "1.2",
Description = [[ Position and Trail of Swift Tuttle from 1879 JUN 27
to 1879 JUN 27. Data from JPL Horizons]],
to 2050 JAN 01. Data from JPL Horizons]],
Author = "OpenSpace Team",
URL = "https://ssd.jpl.nasa.gov/horizons.cgi",
License = [[ JPL-authored documents are sponsored by NASA under Contract

View File

@@ -4,23 +4,26 @@ local orbit = asset.syncedResource({
Name = "Tesla Roadster Orbit",
Type = "HttpSynchronization",
Identifier = "tesla_horizons",
Version = 1
Version = 2
})
local TeslaRoadsterTrail = {
Identifier = "TeslaRoadsterTrail",
Parent = sunTransforms.SolarSystemBarycenter.Identifier,
Renderable = {
Type = "RenderableTrailTrajectory",
Type = "RenderableTrailOrbit",
Translation = {
Type = "HorizonsTranslation",
HorizonsTextFile = orbit .. "horizons_tesla.dat"
HorizonsTextFile = orbit .. "horizons_tesla.hrz"
-- Target: SpaceX Roadster (spacecraft) (-143205)
-- Observer: SSB
-- Start time: 2018-Feb-07 03:00:00 (first data point)
-- End time: 2089-Dec-31 03:00:00 (last data point)
-- Step size: 1 day
},
Color = { 0.9, 0.9, 0.0 },
StartTime = "2018 FEB 8 00:00:00",
EndTime = "2022 FEB 7 00:00:00",
SampleInterval = 3000,
TimeStampSubsampleFactor = 1
Period = 557, -- Orbit time 557 days
Resolution = 557 -- Step size 1 day
},
GUI = {
Name = "Tesla Roadster Trail",
@@ -34,14 +37,14 @@ local TeslaPosition = {
Transform = {
Translation = {
Type = "HorizonsTranslation",
HorizonsTextFile = orbit .. "horizons_tesla.dat"
HorizonsTextFile = orbit .. "horizons_tesla.hrz"
}
},
GUI = {
Name = "Tesla Roadster",
Path = "/Solar System/SSSB",
Description = [[Position and Trail of Tesla Roadster from 2018 FEB 8
to 2022 FEB 7 00:00:00. Data from JPL Horizons.]]
Description = [[Position and Trail of Tesla Roadster from 2018 FEB 7 03:00:00
to 2089 DEC 31 03:00:00. Data from JPL Horizons.]]
}
}
@@ -62,9 +65,9 @@ asset.export(TeslaRoadsterTrail)
asset.meta = {
Name = "Tesla Roadster",
Version = "1.1",
Description = [[ Position and Trail of Tesla Roadster from 2018 FEB 8
to 2022 FEB 7 00:00:00. Data from JPL Horizons.]],
Version = "1.2",
Description = [[ Position and Trail of Tesla Roadster from 2018 FEB 7 03:00:00
to 2089 DEC 31 03:00:00. Data from JPL Horizons.]],
Author = "OpenSpace Team",
URL = "https://ssd.jpl.nasa.gov/horizons.cgi",
License = [[ JPL-authored documents are sponsored by NASA under Contract

View File

@@ -0,0 +1,188 @@
openspace.printWarning("Warning asset_helper has been deprecated and will be removed in future verions");
local tableLength = function(table)
local count = 0
for _ in pairs(table) do count = count + 1 end
return count
end
local registerSpiceKernels = function (spiceAsset, kernels)
spiceAsset.onInitialize(function ()
for i, kernel in ipairs(kernels) do
openspace.spice.loadKernel(kernel)
end
end)
spiceAsset.onDeinitialize(function ()
for i = #kernels, 1, -1 do
local kernel = kernels[i]
openspace.spice.unloadKernel(kernel)
end
end)
end
local registerSceneGraphNodes = function (sceneAsset, nodes, override)
override = override or false
if not override then
if tableLength(nodes) == 0 then
openspace.printWarning(sceneAsset.filePath .. ": Register function was called with an empty node list. Pass 'true' as third argument to silence this warning.")
return
end
end
sceneAsset.onInitialize(function ()
for i, node in ipairs(nodes) do
openspace.addSceneGraphNode(node)
end
end)
sceneAsset.onDeinitialize(function ()
for i = #nodes, 1, -1 do
node = nodes[i]
openspace.removeSceneGraphNode(node.Identifier)
end
end)
end
local registerScreenSpaceRenderables = function (sceneAsset, renderables, override)
override = override or false
if not override then
if tableLength(renderables) == 0 then
openspace.printWarning(sceneAsset.filePath .. ": Register function was called with an empty node list. Pass 'true' as third argument to silence this warning.")
return
end
end
sceneAsset.onInitialize(function ()
for i, node in ipairs(renderables) do
openspace.addScreenSpaceRenderable(node)
end
end)
sceneAsset.onDeinitialize(function ()
for i = #renderables, 1, -1 do
renderable = renderables[i]
openspace.removeScreenSpaceRenderable(renderable.Identifier)
end
end)
end
local registerDashboardItems = function (dashboardAsset, items)
dashboardAsset.onInitialize(
function ()
for i, item in ipairs(items) do
openspace.dashboard.addDashboardItem(item)
end
end
)
dashboardAsset.onDeinitialize(function ()
for i = #items, 1, -1 do
local item = items[i]
openspace.dashboard.removeDashboardItem(item.Identifier)
end
end)
end
local registerSceneGraphNodesAndExport = function (sceneAsset, nodes, override)
override = override or false
if not override then
if tableLength(nodes) == 0 then
openspace.printWarning(sceneAsset.filePath .. ": Register function was called with an empty node list. Pass 'true' as third argument to silence this warning.")
return
end
end
for i, node in ipairs(nodes) do
if not node.Identifier then
openspace.printError("Could not load asset as Identifier was missing")
end
end
sceneAsset.onInitialize(function ()
for i, node in ipairs(nodes) do
openspace.addSceneGraphNode(node)
end
end)
sceneAsset.onDeinitialize(function ()
for i = #nodes, 1, -1 do
local node = nodes[i]
openspace.removeSceneGraphNode(node.Identifier)
end
end)
for i, node in ipairs(nodes) do
sceneAsset.export(node.Identifier, node)
end
end
local requireAll = function (sceneAsset, directory)
function string.ends(String,End)
return End=='' or string.sub(String,-string.len(End))==End
end
local result = {}
local files = openspace.walkDirectoryFiles(sceneAsset.localResource('') .. directory, true)
for _, file in pairs(files) do
if file:ends('.asset') then
openspace.printDebug("Requiring: " .. file:sub(file:find(directory), -7))
local exports = sceneAsset.require(file:sub(1, -7))
table.insert(result, exports)
end
end
return result
end
local getDefaultLightSources = function (solarSystemBarycenterIdentifier)
local sourceList = {
{
Type = "SceneGraphLightSource",
Identifier = "Sun",
Node = solarSystemBarycenterIdentifier,
Intensity = 1.0
},
{
Identifier = "Camera",
Type = "CameraLightSource",
Intensity = 0.5
}
}
return sourceList
end
local createModelPart = function (parent, sunLightSourceNode, models, geometry, texture, performShading)
local lightSources = {}
if performShading then
lightSources[1] = {
Type = "SceneGraphLightSource",
Identifier = "Sun",
Node = sunLightSourceNode,
Intensity = 1.0
}
end
return {
Identifier = parent .. "-" .. geometry,
Parent = parent,
Renderable = {
Type = "RenderableModel",
GeometryFile = models .. "/" .. geometry .. ".obj",
LightSources = lightSources,
PerformShading = performShading,
DisableFaceCulling = true
},
GUI = {
Hidden = true
}
}
end
asset.export("registerSceneGraphNodes", registerSceneGraphNodes)
asset.export("registerSceneGraphNodesAndExport", registerSceneGraphNodesAndExport)
asset.export("registerScreenSpaceRenderables", registerScreenSpaceRenderables)
asset.export("registerSpiceKernels", registerSpiceKernels)
asset.export("registerDashboardItems", registerDashboardItems)
asset.export("requireAll", requireAll)
asset.export("getDefaultLightSources", getDefaultLightSources)
asset.export("createModelPart", createModelPart)

View File

@@ -0,0 +1,37 @@
asset.onInitialize(function ()
local scale = openspace.dpiScaling()
openspace.printInfo("Setting the DPI scaling factor to " .. tostring(scale))
if openspace.modules.isLoaded("CefWebGui") then
openspace.setPropertyValueSingle(
"Modules.CefWebGui.GuiScale",
openspace.getPropertyValue("Modules.CefWebGui.GuiScale") * scale
)
end
local dashboards = openspace.getProperty("Dashboard.*.FontSize");
for _, v in ipairs(dashboards) do
openspace.setPropertyValueSingle(
v,
openspace.getPropertyValue(v) * scale
)
end
local offset = openspace.getPropertyValue("Dashboard.StartPositionOffset")
openspace.setPropertyValueSingle(
"Dashboard.StartPositionOffset",
{ offset[1] * scale, offset[2] * scale }
)
end)
asset.meta = {
Name = "DPI Scaling",
Version = "1.0",
Description = [[ Retrieves the DPI scaling from the operating system and applies it to
a few selected places to make them scale better on "large resolution but small size"
monitors ]],
Author = "OpenSpace Team",
URL = "http://openspaceproject.com",
License = "MIT license"
}

View File

@@ -64,6 +64,8 @@ struct WindowDelegate {
glm::vec2 (*dpiScaling)() = []() { return glm::vec2(1.f); };
float (*osDpiScaling)() = []() { return 1.f; };
bool (*hasGuiWindow)() = []() { return false; };
bool (*isGuiWindow)() = []() { return false; };

View File

@@ -87,6 +87,13 @@ public:
*/
bool hasReachedEnd() const;
/**
* Compute the interpolated camera pose at a certain distance along a *linear*
* path. Note that the linear path is a special case, to avoid risks of precision
* problems for long paths
*/
CameraPose linearInterpolatedPose(double distance, double displacement);
/**
* Compute the interpolated camera pose at a certain distance along the path
*/

View File

@@ -59,6 +59,7 @@ public:
const Path* currentPath() const;
double speedScale() const;
double arrivalDistanceFactor() const;
float linearRotationSpeedFactor() const;
bool hasCurrentPath() const;
bool hasFinished() const;
@@ -103,6 +104,7 @@ private:
properties::FloatProperty _speedScale;
properties::BoolProperty _applyIdleBehaviorOnFinish;
properties::DoubleProperty _arrivalDistanceFactor;
properties::FloatProperty _linearRotationSpeedFactor;
properties::DoubleProperty _minValidBoundingSphere;
properties::StringListProperty _relevantNodeTags;

View File

@@ -89,13 +89,13 @@ struct CameraKeyframe {
sizeof(_followNodeRotation)
);
int nodeNameLength = static_cast<int>(_focusNode.size());
uint32_t nodeNameLength = static_cast<uint32_t>(_focusNode.size());
// Add focus node
buffer.insert(
buffer.end(),
reinterpret_cast<const char*>(&nodeNameLength),
reinterpret_cast<const char*>(&nodeNameLength) + sizeof(nodeNameLength)
reinterpret_cast<const char*>(&nodeNameLength) + sizeof(uint32_t)
);
buffer.insert(
buffer.end(),

View File

@@ -42,14 +42,13 @@ public:
Host
};
enum class MessageType : uint32_t {
enum class MessageType : uint8_t {
Authentication = 0,
Data,
ConnectionStatus,
HostshipRequest,
HostshipResignation,
NConnections,
Disconnection
NConnections
};
struct Message {
@@ -71,7 +70,9 @@ public:
class ConnectionLostError : public ghoul::RuntimeError {
public:
explicit ConnectionLostError();
explicit ConnectionLostError(bool shouldLogError = true);
bool shouldLogError;
};
ParallelConnection(std::unique_ptr<ghoul::io::TcpSocket> socket);
@@ -84,9 +85,11 @@ public:
ParallelConnection::Message receiveMessage();
static const unsigned int ProtocolVersion;
static const uint8_t ProtocolVersion;
private:
std::unique_ptr<ghoul::io::TcpSocket> _socket;
bool _shouldDisconnect = false;
};
} // namespace openspace

View File

@@ -1,117 +0,0 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* *
* 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___PARALLELSERVER___H__
#define __OPENSPACE_CORE___PARALLELSERVER___H__
#include <openspace/network/parallelconnection.h>
#include <openspace/util/concurrentqueue.h>
#include <ghoul/io/socket/tcpsocketserver.h>
#include <atomic>
#include <string>
#include <unordered_map>
namespace openspace {
class ParallelServer {
public:
void start(int port, const std::string& password,
const std::string& changeHostPassword);
void setDefaultHostAddress(std::string defaultHostAddress);
std::string defaultHostAddress() const;
void stop();
size_t nConnections() const;
private:
struct Peer {
size_t id;
std::string name;
ParallelConnection parallelConnection;
ParallelConnection::Status status;
std::thread thread;
};
struct PeerMessage {
size_t peerId;
ParallelConnection::Message message;
};
bool isConnected(const Peer& peer) const;
void sendMessage(Peer& peer, ParallelConnection::MessageType messageType,
const std::vector<char>& message);
void sendMessageToAll(ParallelConnection::MessageType messageType,
const std::vector<char>& message);
void sendMessageToClients(ParallelConnection::MessageType messageType,
const std::vector<char>& message);
void disconnect(Peer& peer);
void setName(Peer& peer, std::string name);
void assignHost(std::shared_ptr<Peer> newHost);
void setToClient(Peer& peer);
void setNConnections(size_t nConnections);
void sendConnectionStatus(Peer& peer);
void handleAuthentication(std::shared_ptr<Peer> peer, std::vector<char> message);
void handleData(const Peer& peer, std::vector<char> data);
void handleHostshipRequest(std::shared_ptr<Peer> peer, std::vector<char> message);
void handleHostshipResignation(Peer& peer);
void handleNewPeers();
void eventLoop();
std::shared_ptr<Peer> peer(size_t id);
void handlePeer(size_t id);
void handlePeerMessage(PeerMessage peerMessage);
std::unordered_map<size_t, std::shared_ptr<Peer>> _peers;
mutable std::mutex _peerListMutex;
std::thread _serverThread;
std::thread _eventLoopThread;
ghoul::io::TcpSocketServer _socketServer;
size_t _passwordHash;
size_t _changeHostPasswordHash;
size_t _nextConnectionId = 1;
std::atomic_bool _shouldStop = false;
std::atomic_size_t _nConnections = 0;
std::atomic_size_t _hostPeerId = 0;
mutable std::mutex _hostInfoMutex;
std::string _hostName;
std::string _defaultHostAddress;
ConcurrentQueue<PeerMessage> _incomingMessages;
};
} // namespace openspace
#endif // __OPENSPACE_CORE___PARALLELSERVER___H__

View File

@@ -144,14 +144,14 @@ public:
/**
* Sets a relative time from profile.
*/
void setTimeRelativeFromProfile(const std::string& setTime);
static void setTimeRelativeFromProfile(const std::string& setTime);
/**
* Sets an absolute time from profile.
* \param setTime a string containing time to set, which must be a valid
* ISO 8601-like date string of the format YYYY-MM-DDTHH:MN:SS
*/
void setTimeAbsoluteFromProfile(const std::string& setTime);
static void setTimeAbsoluteFromProfile(const std::string& setTime);
/**
* Returns the Lua library that contains all Lua functions available to change the

View File

@@ -25,6 +25,7 @@
include(${OPENSPACE_CMAKE_EXT_DIR}/module_definition.cmake)
set(HEADER_FILES
horizonsfile.h
speckloader.h
rendering/planetgeometry.h
rendering/renderableconstellationbounds.h
@@ -46,6 +47,7 @@ set(HEADER_FILES
source_group("Header Files" FILES ${HEADER_FILES})
set(SOURCE_FILES
horizonsfile.cpp
spacemodule_lua.inl
speckloader.cpp
rendering/planetgeometry.cpp

View File

@@ -0,0 +1,872 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* *
* 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/horizonsfile.h>
#include <openspace/util/spicemanager.h>
#include <openspace/util/time.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/fmt.h>
#include <ghoul/logging/logmanager.h>
#include <filesystem>
#include <fstream>
using json = nlohmann::json;
namespace {
constexpr std::string_view _loggerCat = "HorizonsFile";
constexpr std::string_view ApiSource = "NASA/JPL Horizons API";
constexpr std::string_view CurrentVersion = "1.1";
// Values needed to construct the url for the http request to JPL Horizons API
constexpr std::string_view VectorUrl = "https://ssd.jpl.nasa.gov/api/horizons.api?"
"format=json&MAKE_EPHEM='YES'&EPHEM_TYPE='VECTORS'&VEC_TABLE='1'&VEC_LABELS='NO'&"
"CSV_FORMAT='NO'";
constexpr std::string_view ObserverUrl = "https://ssd.jpl.nasa.gov/api/horizons.api?"
"format=json&MAKE_EPHEM='YES'&EPHEM_TYPE='OBSERVER'&QUANTITIES='20,33'&"
"RANGE_UNITS='KM'&SUPPRESS_RANGE_RATE='YES'&CSV_FORMAT='NO'";
constexpr std::string_view Command = "&COMMAND=";
constexpr std::string_view Center = "&CENTER=";
constexpr std::string_view StartTime = "&START_TIME=";
constexpr std::string_view StopTime = "&STOP_TIME=";
constexpr std::string_view StepSize = "&STEP_SIZE=";
// URL encoding
constexpr std::string_view WhiteSpace = "%20";
constexpr std::string_view HashTag = "%23";
constexpr std::string_view DollarSign = "%24";
constexpr std::string_view Ampersand = "%26";
constexpr std::string_view PlusSign = "%2B";
constexpr std::string_view Comma = "%2C";
constexpr std::string_view Slash = "%2F";
constexpr std::string_view Colon = "%3A";
constexpr std::string_view Semicolon = "%3B";
constexpr std::string_view EqualsSign = "%3D";
constexpr std::string_view QuestionMark = "%3F";
constexpr std::string_view AtSymbol = "%40";
constexpr std::string_view LeftSquareBracket = "%5B";
constexpr std::string_view RightSquareBracket = "%5D";
std::string replaceAll(std::string string, const std::string& from,
const std::string& to)
{
if (from.empty()) {
return "";
}
size_t pos = string.find(from);
while (pos != std::string::npos) {
string.replace(pos, from.length(), to);
// In case 'to' contains 'from', ex replacing 'x' with 'yx'
size_t offset = pos + to.length();
pos = string.find(from, offset);
}
return string;
}
std::string urlEncode(const std::string& string) {
std::string result;
result = replaceAll(string, " ", static_cast<std::string>(WhiteSpace));
result = replaceAll(result, "#", static_cast<std::string>(HashTag));
result = replaceAll(result, "$", static_cast<std::string>(DollarSign));
result = replaceAll(result, "&", static_cast<std::string>(Ampersand));
result = replaceAll(result, "+", static_cast<std::string>(PlusSign));
result = replaceAll(result, ",", static_cast<std::string>(Comma));
result = replaceAll(result, "/", static_cast<std::string>(Slash));
result = replaceAll(result, ":", static_cast<std::string>(Colon));
result = replaceAll(result, ";", static_cast<std::string>(Semicolon));
result = replaceAll(result, "=", static_cast<std::string>(EqualsSign));
result = replaceAll(result, "?", static_cast<std::string>(QuestionMark));
result = replaceAll(result, "@", static_cast<std::string>(AtSymbol));
result = replaceAll(result, "[", static_cast<std::string>(LeftSquareBracket));
result = replaceAll(result, "]", static_cast<std::string>(RightSquareBracket));
return result;
}
} // namespace
namespace openspace {
HorizonsFile::HorizonsFile(std::filesystem::path file)
: _file(std::move(file))
{}
HorizonsFile::HorizonsFile(std::filesystem::path filePath, const std::string& result) {
// Write the response into a new file and save it
std::ofstream file(filePath);
file << replaceAll(result, "\\n", "\n") << std::endl;
file.close();
_file = std::move(filePath);
}
void HorizonsFile::setFile(std::filesystem::path file) {
_file = std::move(file);
}
const std::filesystem::path& HorizonsFile::file() const {
return _file;
}
std::filesystem::path& HorizonsFile::file() {
return _file;
}
std::string constructHorizonsUrl(HorizonsType type, const std::string& target,
const std::string& observer,
const std::string& startTime,
const std::string& stopTime, const std::string& stepSize,
const std::string& unit)
{
// Construct url for request
std::string url = "";
switch (type) {
case HorizonsType::Vector:
url = VectorUrl;
break;
case HorizonsType::Observer:
url = ObserverUrl;
break;
default:
throw ghoul::MissingCaseException();
}
url += fmt::format("{}'{}'", Command, urlEncode(target));
url += fmt::format("{}'{}'", Center, urlEncode(observer));
url += fmt::format("{}'{}'", StartTime, urlEncode(startTime));
url += fmt::format("{}'{}'", StopTime, urlEncode(stopTime));
if (unit.empty()) {
url += fmt::format("{}'{}'", StepSize, urlEncode(stepSize));
}
else {
url += fmt::format("{}'{}{}{}'", StepSize, urlEncode(stepSize), WhiteSpace, unit);
}
return url;
}
HorizonsResultCode isValidHorizonsAnswer(const json& answer) {
// Signature, source and version
if (auto signature = answer.find("signature"); signature != answer.end()) {
if (auto source = signature->find("source"); source != signature->end()) {
if (*source != static_cast<std::string>(ApiSource)) {
LWARNING(fmt::format("Horizons answer from unkown source '{}'", *source));
}
}
else {
LWARNING("Could not find source information, source might not be acceptable");
}
if (auto version = signature->find("version"); version != signature->end()) {
if (*version != static_cast<std::string>(CurrentVersion)) {
LWARNING(fmt::format(
"Unknown Horizons version '{}' found. The currently supported "
"version is {}", *version, CurrentVersion
));
}
}
else {
LWARNING(
"Could not find version information, version might not be supported"
);
}
}
else {
LWARNING("Could not find signature information");
}
// Errors
if (auto it = answer.find("error"); it != answer.end()) {
// There was an error
std::string errorMsg = *it;
// @CPP23 (malej, 2022-04-08) In all cases below, the string function contains
// should be used instead of find
// Projected output length (~X) exceeds 90024 line max -- change step-size
if (errorMsg.find("Projected output length") != std::string::npos) {
return HorizonsResultCode::ErrorSize;
}
// STEP_SIZE too big, exceeds available span.
else if (errorMsg.find("STEP_SIZE too big") != std::string::npos) {
return HorizonsResultCode::ErrorSpan;
}
// No ephemeris for target "X" after A.D. Y UT
else if (errorMsg.find("No ephemeris for target") != std::string::npos) {
return HorizonsResultCode::ErrorTimeRange;
}
// No site matches. Use "*@body" to list, "c@body" to enter coords, ?! for help.
else if (errorMsg.find("No site matches") != std::string::npos ||
errorMsg.find("Cannot find central body") != std::string::npos)
{
return HorizonsResultCode::ErrorNoObserver;
}
// Observer table for X / Y->Y disallowed.
else if (errorMsg.find("disallowed") != std::string::npos) {
return HorizonsResultCode::ErrorObserverTargetSame;
}
// Insufficient ephemeris data has been loaded to compute the state of X
// relative to Y at the ephemeris epoch Z;
else if (errorMsg.find("Insufficient ephemeris data") != std::string::npos) {
return HorizonsResultCode::ErrorNoData;
}
// # E. Lon DXY DZ Observatory Name;
// -- - -------- ------ - ------ - ----------------;
// * Observer station *
// Multiple matching stations found.
else if (errorMsg.find("Multiple matching stations found") != std::string::npos) {
return HorizonsResultCode::MultipleObserverStations;
}
// Unknown error
else {
LERROR(errorMsg);
return HorizonsResultCode::UnknownError;
}
}
return HorizonsResultCode::Valid;
}
// Check whether the given Horizons file is valid or not
// Return an error code with what is the problem if there was one
HorizonsResultCode isValidHorizonsFile(std::filesystem::path file) {
std::ifstream fileStream(file);
if (!fileStream.good()) {
return HorizonsResultCode::Empty;
}
// The header of a Horizons file has a lot of information about the
// query that can tell us if the file is valid or not.
// The line $$SOE indicates start of data.
std::string line;
bool foundTarget = false;
std::getline(fileStream, line);
std::getline(fileStream, line); // First line is just stars (*) no information, skip
// @CPP23 (malej, 2022-04-08) In all cases below, the string function contains
// should be used instead of find
// Valid Target?
if (fileStream.good() && (line.find("Revised") != std::string::npos ||
line.find("JPL") != std::string::npos))
{
// If the target is valid, the first line is the date it was last revised
// In case of comets it says the Source in the top
foundTarget = true;
}
HorizonsResultCode result = HorizonsResultCode::UnknownError;
while (fileStream.good() && line.find("$$SOE") == std::string::npos) {
// Selected time range too big and step size too small?
if (line.find("change step-size") != std::string::npos) {
fileStream.close();
return HorizonsResultCode::ErrorSize;
}
// Selected time range too big for avalable time span?
if (line.find("STEP_SIZE too big") != std::string::npos) {
fileStream.close();
return HorizonsResultCode::ErrorSpan;
}
// Outside valid time range?
if (line.find("No ephemeris for target") != std::string::npos) {
// Available time range is located several lines before this in the file
// The avalable time range is persed later
fileStream.close();
return HorizonsResultCode::ErrorTimeRange;
}
// Valid Observer?
if (line.find("No site matches") != std::string::npos ||
line.find("Cannot find central body") != std::string::npos)
{
fileStream.close();
return HorizonsResultCode::ErrorNoObserver;
}
// Are observer and target the same?
if (line.find("disallowed") != std::string::npos) {
fileStream.close();
return HorizonsResultCode::ErrorObserverTargetSame;
}
// Enough data?
if (line.find("Insufficient ephemeris data") != std::string::npos) {
fileStream.close();
return HorizonsResultCode::ErrorNoData;
}
// Multiple Observer stations?
if (line.find("Multiple matching stations found") != std::string::npos) {
result = HorizonsResultCode::MultipleObserverStations;
}
// Multiple matching major bodies?
if (line.find("Multiple major-bodies match string") != std::string::npos) {
// Target
if (!foundTarget) {
// If target was not found then it is the target that has multiple matches
result = HorizonsResultCode::MultipleTarget;
}
// Observer
else {
result = HorizonsResultCode::MultipleObserver;
}
}
// Multiple matching small bodies?
if (line.find("Small-body Index Search Results") != std::string::npos) {
// Small bodies can only be targets not observers
result = HorizonsResultCode::MultipleTarget;
}
// No Target?
if (line.find("No matches found") != std::string::npos) {
fileStream.close();
return HorizonsResultCode::ErrorNoTarget;
}
std::getline(fileStream, line);
}
if (result != HorizonsResultCode::UnknownError) {
fileStream.close();
return result;
}
// If we reached end of file before we found the start of data then it is
// not a valid file
if (fileStream.good()) {
fileStream.close();
return HorizonsResultCode::Valid;
}
else {
fileStream.close();
return HorizonsResultCode::UnknownError;
}
}
bool HorizonsFile::hasFile() const {
return std::filesystem::is_regular_file(_file);
}
void HorizonsFile::displayErrorMessage(const HorizonsResultCode code) const {
switch (code) {
case HorizonsResultCode::Valid:
return;
case HorizonsResultCode::Empty:
LERROR("The horizons file is empty");
break;
case HorizonsResultCode::ErrorSize:
LERROR(
"The selected time range with the selected step size is too big, "
"try to increase the step size and/or decrease the time range"
);
break;
case HorizonsResultCode::ErrorSpan:
LERROR("Step size is too big, exceeds available time span for target");
break;
case HorizonsResultCode::ErrorTimeRange: {
LERROR("Time range is outside the valid range for target");
std::pair<std::string, std::string> validTimeRange = parseValidTimeRange(
"Trajectory files",
"************",
"Trajectory name"
);
if (validTimeRange.first.empty()) {
LERROR("Could not parse the valid time range from file");
break;
}
LINFO(fmt::format(
"Valid time range is '{}' to '{}'", validTimeRange.first,
validTimeRange.second
));
break;
}
case HorizonsResultCode::ErrorNoObserver:
LERROR("No match was found for the observer");
break;
case HorizonsResultCode::ErrorObserverTargetSame:
LERROR("The observer and target are the same");
break;
case HorizonsResultCode::ErrorNoData:
LERROR(
"There is not enough data to compute the state of the target in "
"relation to the observer for the selected time range."
);
break;
case HorizonsResultCode::MultipleObserverStations: {
LWARNING(
"Multiple matching observer stations were found for the "
"selected observer"
);
std::vector<std::string> matchingstations =
parseMatches("Observatory Name", "Multiple matching stations found");
if (matchingstations.empty()) {
LERROR("Could not parse the matching stations");
break;
}
std::string matches;
for (std::string station : matchingstations) {
matches += '\n' + station;
}
LINFO(fmt::format("Matching Observer Stations: {}", matches));
break;
}
case HorizonsResultCode::MultipleObserver: {
LWARNING("Multiple matches were found for the selected observer");
std::vector<std::string> matchingObservers =
parseMatches("Name", "matches", ">MATCH NAME<");
if (matchingObservers.empty()) {
LERROR("Could not parse the matching observers");
break;
}
std::string matches;
for (std::string observer : matchingObservers) {
matches += '\n' + observer;
}
LINFO(fmt::format("Matching Observers: {}", matches));
break;
}
case HorizonsResultCode::ErrorNoTarget:
LERROR("No match was found for the target");
break;
case HorizonsResultCode::MultipleTarget: {
// Case Small Bodies:
// Line before data: Matching small-bodies
// Format: Record #, Epoch-yr, >MATCH DESIG<, Primary Desig, Name
// Line after data:
// (X matches. To SELECT, enter record # (integer), followed by semi-colon.)
// Case Major Bodies:
// Line before data: Multiple major-bodies match string "X*"
// Format: ID#, Name, Designation, IAU/aliases/other
// Line after data: Number of matches = X. Use ID# to make unique selection.
LWARNING("Multiple matches were found for the target");
std::vector<std::string> matchingTargets =
parseMatches("Name", "matches", ">MATCH NAME<");
if (matchingTargets.empty()) {
LERROR("Could not parse the matching targets");
break;
}
std::string matches;
for (std::string target : matchingTargets) {
matches += '\n' + target;
}
LINFO(fmt::format("Matching targets: {}", matches));
break;
}
case HorizonsResultCode::UnknownError:
LERROR("An unknown error occured");
break;
default:
LERROR("Unknown result type");
break;
}
}
HorizonsResult readHorizonsFile(std::filesystem::path file) {
// Check if valid
HorizonsResultCode code = isValidHorizonsFile(file);
if (code != HorizonsResultCode::Valid) {
HorizonsResult result;
result.errorCode = code;
return result;
}
std::ifstream fileStream(file);
if (!fileStream.good()) {
LERROR(fmt::format("Failed to open Horizons file '{}'", file));
return HorizonsResult();
}
// Identify which type the file is
// Vector Table type has:"
// JDTDB
// X Y Z
// " Before data starts, Observer table doesn't
std::string line;
std::getline(fileStream, line);
while (line[0] != '$') {
if (line == "JDTDB") {
fileStream.close();
return readHorizonsVectorFile(file);
}
std::getline(fileStream, line);
}
fileStream.close();
return readHorizonsObserverFile(file);
}
HorizonsResult readHorizonsVectorFile(std::filesystem::path file) {
HorizonsResult result;
result.type = HorizonsType::Vector;
result.errorCode = HorizonsResultCode::Valid;
std::vector<HorizonsKeyframe> data;
std::ifstream fileStream(file);
if (!fileStream.good()) {
LERROR(fmt::format("Failed to open Horizons text file '{}'", file));
return HorizonsResult();
}
// The beginning of a Horizons file has a header with a lot of information about the
// query that we do not care about. Ignore everything until data starts, including
// the row marked by $$SOE (i.e. Start Of Ephemerides).
std::string line;
do {
std::getline(fileStream, line);
} while (line[0] != '$');
// Read data line by line until $$EOE (i.e. End Of Ephemerides).
// Skip the rest of the file.
std::getline(fileStream, line); // Skip the line with the $$EOE
while (line[0] != '$') {
HorizonsKeyframe dataPoint;
std::stringstream str1(line);
// File is structured as (data over two lines):
// JulianDayNumber = A.D. YYYY-MM-DD HH:MM:SS TDB
// X Y Z
std::string temp;
std::string date;
std::string time;
str1 >> temp >> temp >> temp >> date >> time >> temp;
// Get next line of same data point
std::getline(fileStream, line);
if (!fileStream.good()) {
LERROR(fmt::format("Malformed Horizons file '{}'", file));
return HorizonsResult();
}
std::stringstream str2(line);
// X Y Z
double xPos;
double yPos;
double zPos;
str2 >> xPos >> yPos >> zPos;
// Convert date and time to seconds after 2000
std::string timeString = fmt::format("{} {}", date, time);
double timeInJ2000 = Time::convertTime(timeString);
glm::dvec3 pos = glm::dvec3(1000 * xPos, 1000 * yPos, 1000 * zPos);
glm::dmat3 transform =
SpiceManager::ref().positionTransformMatrix("ECLIPJ2000", "GALACTIC", 0.0);
pos = transform * pos;
// Add position to stored data
dataPoint.time = timeInJ2000;
dataPoint.position = pos;
data.push_back(dataPoint);
std::getline(fileStream, line);
}
fileStream.close();
result.data = data;
return result;
}
HorizonsResult readHorizonsObserverFile(std::filesystem::path file) {
HorizonsResult result;
result.type = HorizonsType::Observer;
result.errorCode = HorizonsResultCode::Valid;
std::vector<HorizonsKeyframe> data;
std::ifstream fileStream(file);
if (!fileStream.good()) {
LERROR(fmt::format("Failed to open Horizons text file '{}'", file));
return HorizonsResult();
}
// The beginning of a Horizons file has a header with a lot of information about the
// query that we do not care about. Ignore everything until data starts, including
// the row marked by $$SOE (i.e. Start Of Ephemerides).
std::string line;
do {
std::getline(fileStream, line);
} while (line[0] != '$');
// Read data line by line until $$EOE (i.e. End Of Ephemerides).
// Skip the rest of the file.
std::getline(fileStream, line); // Skip the line with the $$EOE
while (line[0] != '$') {
HorizonsKeyframe dataPoint;
std::stringstream str(line);
// File is structured by (all in one line):
// YYYY-MM-DD
// HH:MM:SS
// Range-to-observer (km)
// Range-delta (km/s) -- suppressed!
// Galactic Longitude (degrees)
// Galactic Latitude (degrees)
std::string date;
std::string time;
double range = 0;
double gLon = 0;
double gLat = 0;
str >> date >> time >> range >> gLon >> gLat;
// Convert date and time to seconds after 2000
// and pos to Galactic positions in meter from Observer.
std::string timeString = fmt::format("{} {}", date, time);
double timeInJ2000 = Time::convertTime(timeString);
glm::dvec3 gPos = glm::dvec3(
1000 * range * cos(glm::radians(gLat)) * cos(glm::radians(gLon)),
1000 * range * cos(glm::radians(gLat)) * sin(glm::radians(gLon)),
1000 * range * sin(glm::radians(gLat))
);
// Add position to stored data
dataPoint.time = timeInJ2000;
dataPoint.position = gPos;
data.push_back(dataPoint);
std::getline(fileStream, line);
}
fileStream.close();
LWARNING(
"Observer table data from Horizons might not align with SPICE data well. "
"We recommend using Vector table data from Horizons instead"
);
result.data = data;
return result;
}
std::vector<std::string> HorizonsFile::parseMatches(const std::string& startPhrase,
const std::string& endPhrase,
const std::string& altStartPhrase) const
{
std::ifstream fileStream(_file);
std::vector<std::string> matches;
if (!fileStream.good()) {
fileStream.close();
return matches;
}
// @CPP23 (malej, 2022-04-08) In all cases below, the string function contains
// should be used instead of find
// Ignore everything until start of matches
std::string line;
while (fileStream.good()) {
// Add the line with the start phrase first, to give context
if (line.find(startPhrase) != std::string::npos) {
matches.push_back(line);
break;
}
else if (!altStartPhrase.empty() && line.find(altStartPhrase) != std::string::npos) {
matches.push_back(line);
break;
}
std::getline(fileStream, line);
}
if (!fileStream.good()) {
fileStream.close();
return std::vector<std::string>();
}
// There will be one empty line before the list of matches, skip
std::getline(fileStream, line);
std::getline(fileStream, line);
while (fileStream.good()) {
// End of matches or file
if (line == " " || line.empty() || line.find(endPhrase) != std::string::npos) {
fileStream.close();
return matches;
}
matches.push_back(line);
std::getline(fileStream, line);
}
fileStream.close();
return std::vector<std::string>();
}
// Parse the valid time range from the horizons file
// Example of how it can look (MRO):
// Trajectory files (from MRO Nav., JPL) Start (TDB) End (TDB)
// -------------------------------------- ----------------- -----------------
// mro_cruise 2005-Aug-12 12:42 2006-Mar-10 22:06
// mro_ab 2006-Mar-10 22:06 2006-Sep-12 06:40
// misc reconstruction(mro_psp1 - 61) 2006-Sep-12 06:40 2022-Jan-01 01:01
// mro_psp_rec 2022-Jan-01 01:01 2022-Jan-30 22:40
// mro_psp 2022-Jan-30 22:40 2022-Apr-04 02:27
// *******************************************************************************
//
// Another example (Gaia):
// Trajectory name Start Stop
// -------------------------------------------- ----------- -----------
// ORB1_20220201_000001 2013-Dec-19 2026-Sep-14
// *******************************************************************************
//
// Another example (Tesla):
// Trajectory name Start (TDB) Stop (TDB)
// -------------------------------- ----------------- -----------------
// tesla_s10 2018-Feb-07 03:00 2090-Jan-01 00:00
//
// So the number of trajectory files can differ and they can have time info or not.
// The first row is parsed for both the start and end time.
// All other lines are only parsed for the end time and updates the previously parsed end
// time. Assumes that there are no gaps in the data coverage and that the files are sorted
// in respect to time.
std::pair<std::string, std::string> HorizonsFile::parseValidTimeRange(
const std::string& startPhrase,
const std::string& endPhrase,
const std::string& altStartPhrase,
bool hasTime) const
{
std::ifstream fileStream(_file);
if (!fileStream.good()) {
fileStream.close();
return { "", "" };
}
// @CPP23 (malej, 2022-04-08) In all cases below, the string function contains
// should be used instead of find
// Ignore everything until head of time range list
std::string line;
std::getline(fileStream, line);
while (fileStream.good()) {
// Add the line with the start phrase first, to give context
if (line.find(startPhrase) != std::string::npos) {
break;
}
else if (!altStartPhrase.empty() && line.find(altStartPhrase) != std::string::npos) {
break;
}
std::getline(fileStream, line);
}
if (!fileStream.good()) {
fileStream.close();
return { "", "" };
}
// There will be one empty line before the list of time ranges, skip
std::getline(fileStream, line);
// In the first file parse both start and end time
// From the first line get the start time
std::string startTime, endTime;
std::getline(fileStream, line);
if (fileStream.good()) {
std::stringstream str(line);
// Read and save each word
std::vector<std::string> words;
std::string word;
while (str >> word) {
words.push_back(word);
}
// Parse time stamps backwards
// Format: Trajectory file Name, Start, End (yyyy-mon-dd hh:mm)
if (hasTime && words.size() > 4) {
startTime = fmt::format(
"{} T {}", words[words.size() - 4], words[words.size() - 3]
);
endTime = fmt::format(
"{} T {}", words[words.size() - 2], words[words.size() - 1]
);
}
else if (words.size() > 2){
// Sometimes the format can be yyyy-mon-dd without time
startTime = words[words.size() - 2];
endTime = words[words.size() - 1];
}
else {
return { "", "" };
}
}
if (startTime.empty() || endTime.empty()) {
fileStream.close();
return { "", "" };
}
// In the other lines only parse the end time and update it
// Get the end time from the last trajectery
while (fileStream.good()) {
if (line.find(endPhrase) != std::string::npos || line.empty() || line == " ") {
fileStream.close();
return { startTime, endTime };
}
// Read and save each word.
std::stringstream str(line);
std::vector<std::string> words;
std::string word;
while (str >> word) {
words.push_back(word);
}
// Parse time stamps backwards
// Format: Trajectory file Name, Start, End (yyyy-mon-dd hh:mm)
if (hasTime && words.size() > 4) {
endTime = fmt::format(
"{} T {}", words[words.size() - 2], words[words.size() - 1]
);
}
else if (words.size() > 2) {
// Sometimes the format can be yyyy-mon-dd without time
endTime = words[words.size() - 1];
}
else {
return { "", "" };
}
std::getline(fileStream, line);
}
fileStream.close();
return { "", "" };
}
} // namespace openspace

View File

@@ -0,0 +1,139 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* *
* 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_SPACE___HORIZONSFILE___H__
#define __OPENSPACE_MODULE_SPACE___HORIZONSFILE___H__
#include <openspace/json.h>
#include <ghoul/glm.h>
#include <filesystem>
#include <string>
#include <vector>
namespace openspace {
/**
* A Horizons file is a text file generated from NASA JPL HORIZONS Website
* (https://ssd.jpl.nasa.gov/horizons.cgi). The implementation supports both Vector
* and Observer as Horizons data table
*
* In case of Vector table data the implementation expects a file with format:
* TIME(JulianDayNumber = A.D. YYYY-MM-DD HH:MM:SS TDB)
* X(km) Y(km) Z(km)
* TIME - Only the "YYYY-MM-DD HH:MM:SS" part is of interest, the rest is ignored
* X - X position in kilometers in Ecliptic J2000 reference frame
* Y - Y position in kilometers in Ecliptic J2000 reference frame
* Z - Z position in kilometers in Ecliptic J2000 reference frame
* Changes required in the "Table Settings" for compatible data:
* 1. Under "Select Output Quantities" choose option "Position components {x, y, z} only"
* 2. Uncheck the "Vector labels" options
*
* In case of Observer table data the implementation expects a file with format:
* TIME(YYYY-MM-DD HH:MM:SS) Range(km) GalLon(degrees) GalLat(degrees)
* Range - The distance from target to observer in kilometers
* GalLon - Galactic Longitude in degrees
* GalLat - Galactic Latitude in degrees
* Changes required in the "Table Settings" for compatible data:
* 1. Under "Observer Table Settings" uncheck all options except
* "Observer range & range-rate" and "Galactic longitude & latitude"
* 2. Change "Range units" to "kilometers (km)" instead of "astronomical units (au)"
* 3. Check the "Suppress range-rate" option
*/
enum class HorizonsResultCode {
Valid,
Empty,
// Erros caught by the error field in the json output
ErrorSize,
ErrorSpan,
ErrorTimeRange,
ErrorNoObserver,
ErrorObserverTargetSame,
ErrorNoData,
MultipleObserverStations,
// Erros/problems NOT caught by the error field in the json output
MultipleObserver,
ErrorNoTarget,
MultipleTarget,
UnknownError
};
enum class HorizonsType {
Observer, // Default
Vector, // Default for sending for new data
Invalid // If errors or empty etc
};
struct HorizonsKeyframe {
double time; // J2000 seconds
glm::dvec3 position; // GALACTIC cartesian coordinates in meters
};
struct HorizonsResult {
HorizonsType type = HorizonsType::Invalid;
HorizonsResultCode errorCode = HorizonsResultCode::UnknownError;
std::vector<HorizonsKeyframe> data = std::vector<HorizonsKeyframe>();
};
class HorizonsFile {
public:
HorizonsFile() = default;
HorizonsFile(std::filesystem::path file);
HorizonsFile(std::filesystem::path filePath, const std::string& result);
void setFile(std::filesystem::path file);
const std::filesystem::path& file() const;
std::filesystem::path& file();
bool hasFile() const;
void displayErrorMessage(const HorizonsResultCode code) const;
std::vector<std::string> parseMatches(const std::string& startPhrase,
const std::string& endPhrase, const std::string& altStartPhrase = "") const;
std::pair<std::string, std::string> parseValidTimeRange(
const std::string& startPhrase, const std::string& endPhrase,
const std::string& altStartPhrase = "", bool hasTime = true) const;
private:
std::filesystem::path _file;
};
// Free functions
std::string constructHorizonsUrl(HorizonsType type, const std::string& target,
const std::string& observer, const std::string& startTime,
const std::string& stopTime, const std::string& stepSize,
const std::string& unit);
HorizonsResultCode isValidHorizonsAnswer(const nlohmann::json& answer);
HorizonsResultCode isValidHorizonsFile(std::filesystem::path file);
HorizonsResult readHorizonsFile(std::filesystem::path file);
HorizonsResult readHorizonsVectorFile(std::filesystem::path file);
HorizonsResult readHorizonsObserverFile(std::filesystem::path file);
} // namespace openspace
#endif // __OPENSPACE_MODULE_SPACE___HORIZONSFILE___H__

View File

@@ -35,22 +35,24 @@
#include <ghoul/lua/lua_helper.h>
#include <filesystem>
#include <fstream>
#include <type_traits>
namespace {
constexpr const char* _loggerCat = "HorizonsTranslation";
constexpr int8_t CurrentCacheVersion = 2;
} // namespace
namespace {
constexpr openspace::properties::Property::PropertyInfo HorizonsTextFileInfo = {
"HorizonsTextFile",
"Horizons Text File",
"This value is the path to the text file generated by Horizons with observer "
"range and Galactiv longitude and latitude for different timestamps."
"This value is the path to the file or files generated by Horizons with "
"either a Vector table or an Observer table with the correct settings (see wiki)."
};
struct [[codegen::Dictionary(HorizonsTranslation)]] Parameters {
// [[codegen::verbatim(HorizonsTextFileInfo.description)]]
std::string horizonsTextFile;
std::variant<std::string, std::vector<std::string>> horizonsTextFile;
};
#include "horizonstranslation_codegen.cpp"
} // namespace
@@ -62,19 +64,13 @@ documentation::Documentation HorizonsTranslation::Documentation() {
}
HorizonsTranslation::HorizonsTranslation()
: _horizonsTextFile(HorizonsTextFileInfo)
: _horizonsTextFiles(HorizonsTextFileInfo)
{
addProperty(_horizonsTextFile);
addProperty(_horizonsTextFiles);
_horizonsTextFile.onChange([&](){
_horizonsTextFiles.onChange([&](){
requireUpdate();
_fileHandle = std::make_unique<ghoul::filesystem::File>(
_horizonsTextFile.value()
);
_fileHandle->setCallback([this]() {
requireUpdate();
notifyObservers();
});
notifyObservers();
loadData();
});
}
@@ -83,7 +79,35 @@ HorizonsTranslation::HorizonsTranslation(const ghoul::Dictionary& dictionary)
: HorizonsTranslation()
{
const Parameters p = codegen::bake<Parameters>(dictionary);
_horizonsTextFile = absPath(p.horizonsTextFile).string();
if (std::holds_alternative<std::string>(p.horizonsTextFile)) {
std::string file = std::get<std::string>(p.horizonsTextFile);
if (!std::filesystem::is_regular_file(absPath(file))) {
LWARNING(fmt::format("The Horizons text file '{}' could not be found", file));
return;
}
std::vector<std::string> files;
files.push_back(file);
_horizonsTextFiles.set(files);
}
else if (std::holds_alternative<std::vector<std::string>>(p.horizonsTextFile)) {
std::vector<std::string> files =
std::get<std::vector<std::string>>(p.horizonsTextFile);
for (const std::string& file : files) {
if (!std::filesystem::is_regular_file(absPath(file))) {
LWARNING(fmt::format(
"The Horizons text file '{}' could not be found", file
));
return;
}
}
_horizonsTextFiles = files;
}
else {
throw ghoul::MissingCaseException();
}
}
glm::dvec3 HorizonsTranslation::position(const UpdateData& data) const {
@@ -116,90 +140,68 @@ glm::dvec3 HorizonsTranslation::position(const UpdateData& data) const {
}
void HorizonsTranslation::loadData() {
std::filesystem::path file = absPath(_horizonsTextFile.value());
if (!std::filesystem::is_regular_file(file)) {
return;
}
std::filesystem::path cachedFile = FileSys.cacheManager()->cachedFilename(file);
bool hasCachedFile = std::filesystem::is_regular_file(cachedFile);
if (hasCachedFile) {
LINFO(fmt::format("Cached file {} used for Horizon file {}", cachedFile, file));
bool success = loadCachedFile(cachedFile);
if (success) {
for (const std::string& filePath : _horizonsTextFiles.value()) {
std::filesystem::path file = absPath(filePath);
if (!std::filesystem::is_regular_file(file)) {
LWARNING(fmt::format("The Horizons text file '{}' could not be found", file));
return;
}
else {
FileSys.cacheManager()->removeCacheFile(file);
// Intentional fall-through to the 'else' computation to generate the cache
// file for the next run
std::filesystem::path cachedFile = FileSys.cacheManager()->cachedFilename(file);
bool hasCachedFile = std::filesystem::is_regular_file(cachedFile);
if (hasCachedFile) {
LINFO(fmt::format(
"Cached file '{}' used for Horizon file '{}'", cachedFile, file
));
if (loadCachedFile(cachedFile)) {
continue;
}
else {
FileSys.cacheManager()->removeCacheFile(file);
// Intentional fall-through to the 'else' computation to generate the
// cache file for the next run
}
}
}
else {
LINFO(fmt::format("Cache for Horizon file {} not found", file));
}
LINFO(fmt::format("Loading Horizon file {}", file));
else {
LINFO(fmt::format("Cache for Horizon file '{}' not found", file));
}
LINFO(fmt::format("Loading Horizon file '{}'", file));
readHorizonsTextFile();
HorizonsFile horizonsFile(file);
if (!readHorizonsTextFile(horizonsFile)) {
LERROR(fmt::format("Could not read data from Horizons file '{}'", file));
return;
}
LINFO("Saving cache");
saveCachedFile(cachedFile);
LINFO("Saving cache");
saveCachedFile(cachedFile);
}
}
void HorizonsTranslation::readHorizonsTextFile() {
std::filesystem::path f = absPath(_horizonsTextFile);
std::ifstream fileStream(f);
if (!fileStream.good()) {
LERROR(fmt::format("Failed to open Horizons text file {}", f));
return;
bool HorizonsTranslation::readHorizonsTextFile(HorizonsFile& horizonsFile) {
HorizonsResult result = readHorizonsFile(horizonsFile.file());
if (result.errorCode != HorizonsResultCode::Valid) {
horizonsFile.displayErrorMessage(result.errorCode);
return false;
}
// The beginning of a Horizons file has a header with a lot of information about the
// query that we do not care about. Ignore everything until data starts, including
// the row marked by $$SOE (i.e. Start Of Ephemerides).
std::string line;
while (line[0] != '$') {
std::getline(fileStream, line);
}
// Read data line by line until $$EOE (i.e. End Of Ephemerides).
// Skip the rest of the file.
std::getline(fileStream, line);
while (line[0] != '$') {
std::stringstream str(line);
std::string date;
std::string time;
double range = 0;
double gLon = 0;
double gLat = 0;
// File is structured by:
// YYYY-MM-DD
// HH:MM:SS
// Range-to-observer (km)
// Range-delta (km/s) -- suppressed!
// Galactic Longitude (degrees)
// Galactic Latitude (degrees)
str >> date >> time >> range >> gLon >> gLat;
// Convert date and time to seconds after 2000
// and pos to Galactic positions in meter from Observer.
std::string timeString = date + " " + time;
double timeInJ2000 = Time::convertTime(timeString);
glm::dvec3 gPos = glm::dvec3(
1000 * range * cos(glm::radians(gLat)) * cos(glm::radians(gLon)),
1000 * range * cos(glm::radians(gLat)) * sin(glm::radians(gLon)),
1000 * range * sin(glm::radians(gLat))
for (HorizonsKeyframe& keyframe : result.data) {
// Search if the keyframe already exist in the timeline
auto it = std::find_if(
_timeline.keyframes().begin(),
_timeline.keyframes().end(),
[keyframe](const Keyframe<glm::dvec3>& kf) {
return kf.timestamp == keyframe.time;
}
);
// Add position to stored timeline.
_timeline.addKeyframe(timeInJ2000, std::move(gPos));
std::getline(fileStream, line);
// If it doesn't exist in the timeline then add it, prevent duplicates
if (it == _timeline.keyframes().end()) {
_timeline.addKeyframe(keyframe.time, std::move(keyframe.position));
}
}
fileStream.close();
return true;
}
bool HorizonsTranslation::loadCachedFile(const std::filesystem::path& file) {
@@ -210,6 +212,16 @@ bool HorizonsTranslation::loadCachedFile(const std::filesystem::path& file) {
return false;
}
// Check the caching version
int8_t version = 0;
fileStream.read(reinterpret_cast<char*>(&version), sizeof(int8_t));
if (version != CurrentCacheVersion) {
LINFO("The format of the cached file has changed: deleting old cache");
fileStream.close();
FileSys.cacheManager()->removeCacheFile(file);
return false;
}
// Read how many keyframes to read
int32_t nKeyframes = 0;
@@ -218,24 +230,15 @@ bool HorizonsTranslation::loadCachedFile(const std::filesystem::path& file) {
throw ghoul::RuntimeError("Error reading cache: No values were loaded");
}
// Read the values in same order as they were written
// Read all data in one go
std::vector<CacheKeyframe> cacheKeyframes;
cacheKeyframes.reserve(nKeyframes);
fileStream.read(reinterpret_cast<char*>(cacheKeyframes.data()), sizeof(CacheKeyframe) * nKeyframes);
// Extract the data from the cache Keyframe vector
for (int i = 0; i < nKeyframes; ++i) {
double timestamp, x, y, z;
glm::dvec3 gPos;
// Timestamp
fileStream.read(reinterpret_cast<char*>(&timestamp), sizeof(double));
// Position vector components
fileStream.read(reinterpret_cast<char*>(&x), sizeof(double));
fileStream.read(reinterpret_cast<char*>(&y), sizeof(double));
fileStream.read(reinterpret_cast<char*>(&z), sizeof(double));
// Recreate the position vector
gPos = glm::dvec3(x, y, z);
// Add keyframe in timeline
_timeline.addKeyframe(timestamp, std::move(gPos));
_timeline.addKeyframe(std::move(cacheKeyframes[i].timestamp), std::move(cacheKeyframes[i].position));
}
return fileStream.good();
@@ -248,6 +251,12 @@ void HorizonsTranslation::saveCachedFile(const std::filesystem::path& file) cons
return;
}
// Write which version of caching that is used
fileStream.write(
reinterpret_cast<const char*>(&CurrentCacheVersion),
sizeof(int8_t)
);
// Write how many keyframes are to be written
int32_t nKeyframes = static_cast<int32_t>(_timeline.nKeyframes());
if (nKeyframes == 0) {
@@ -255,28 +264,28 @@ void HorizonsTranslation::saveCachedFile(const std::filesystem::path& file) cons
}
fileStream.write(reinterpret_cast<const char*>(&nKeyframes), sizeof(int32_t));
// Write data
// Transfer all data to a cache key frame vector, write it all in one go
std::deque<Keyframe<glm::dvec3>> keyframes = _timeline.keyframes();
std::vector<CacheKeyframe> cachKeyframes;
cachKeyframes.reserve(nKeyframes);
for (int i = 0; i < nKeyframes; i++) {
// First write timestamp
fileStream.write(reinterpret_cast<const char*>(
&keyframes[i].timestamp),
sizeof(double)
);
CacheKeyframe cacheKeyframe;
cacheKeyframe.timestamp = keyframes[i].timestamp;
cacheKeyframe.position = keyframes[i].data;
// and then the components of the position vector one by one
fileStream.write(reinterpret_cast<const char*>(
&keyframes[i].data.x),
sizeof(double)
);
fileStream.write(reinterpret_cast<const char*>(
&keyframes[i].data.y),
sizeof(double)
);
fileStream.write(reinterpret_cast<const char*>(
&keyframes[i].data.z),
sizeof(double)
);
cachKeyframes.push_back(cacheKeyframe);
}
// Write of entire vector will only work if the data is plain old data type,
// is_pod is depricated in C++20 and replaced with both is_trivial and
// is_standard_layout
assert(std::is_trivial<CacheKeyframe>::value);
assert(std::is_standard_layout<CacheKeyframe>::value);
// Write data
fileStream.write(
reinterpret_cast<const char*>(cachKeyframes.data()),
sizeof(CacheKeyframe) * nKeyframes
);
}
} // namespace openspace

View File

@@ -27,10 +27,11 @@
#include <openspace/scene/translation.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/list/stringlistproperty.h>
#include <openspace/util/timeline.h>
#include <ghoul/filesystem/file.h>
#include <ghoul/lua/luastate.h>
#include <modules/space/horizonsfile.h>
#include <memory>
namespace openspace {
@@ -40,14 +41,30 @@ namespace documentation { struct Documentation; }
/**
* The HorizonsTranslation is based on text files generated from NASA JPL HORIZONS Website
* (https://ssd.jpl.nasa.gov/horizons.cgi). The implementation expects a file with format:
* (https://ssd.jpl.nasa.gov/horizons.cgi). The implementation supports both Vector
* and Observer as Horizons data table
*
* In case of Vector table data the implementation expects a file with format:
* TIME(JulianDayNumber = A.D. YYYY-MM-DD HH:MM:SS TDB)
* X(km) Y(km) Z(km)
* TIME - Only the "YYYY-MM-DD HH:MM:SS" part is of interest, the rest is ignored
* X - X position in kilometers in Ecliptic J2000 reference frame
* Y - Y position in kilometers in Ecliptic J2000 reference frame
* Z - Z position in kilometers in Ecliptic J2000 reference frame
* Changes required in the "Table Settings" for compatible data:
* 1. Under "Select Output Quantities" choose option "Position components {x, y, z} only"
* 2. Uncheck the "Vector labels" options
*
* In case of Observer table data the implementation expects a file with format:
* TIME(YYYY-MM-DD HH:MM:SS) Range(km) GalLon(degrees) GalLat(degrees)
* Range - The distance from target to observer. Chosen as "Observer range & range-rate"
* in "Table Setting". This also generates a delta that can be suppressed under "Optional
* observer-table settings" to limit file size. User must set output settings to
* kilometers.
* GalLon - Galactic Longitude. User must set output to Degrees in "Table Settings".
* GalLat - Galactic Latitude. User must set output to Degrees in "Table Settings".
* Range - The distance from target to observer in kilometers
* GalLon - Galactic Longitude in degrees
* GalLat - Galactic Latitude in degrees
* Changes required in the "Table Settings" for compatible data:
* 1. Under "Observer Table Settings" uncheck all options except
* "Observer range & range-rate" and "Galactic longitude & latitude"
* 2. Change "Range units" to "kilometers (km)" instead of "astronomical units (au)"
* 3. Check the "Suppress range-rate" option
*/
class HorizonsTranslation : public Translation {
public:
@@ -59,13 +76,17 @@ public:
static documentation::Documentation Documentation();
private:
struct CacheKeyframe {
double timestamp;
glm::dvec3 position;
};
void loadData();
void readHorizonsTextFile();
bool readHorizonsTextFile(HorizonsFile& horizonsFile);
bool loadCachedFile(const std::filesystem::path& file);
void saveCachedFile(const std::filesystem::path& file) const;
properties::StringProperty _horizonsTextFile;
std::unique_ptr<ghoul::filesystem::File> _fileHandle;
properties::StringListProperty _horizonsTextFiles;
ghoul::lua::LuaState _state;
Timeline<glm::dvec3> _timeline;
};

View File

@@ -45,13 +45,19 @@ namespace {
constexpr const char* _loggerCat = "RenderablePlaneProjection";
constexpr const char* GalacticFrame = "GALACTIC";
// @TODO (emmbr 2022-01-20) Add documentation
struct [[codegen::Dictionary(RenderablePlaneProjection)]] Parameters {
std::optional<std::string> spacecraft;
std::optional<std::string> instrument;
std::optional<bool> moving;
std::optional<std::string> name;
// The SPICE name of the spacecraft from which the projection is performed
std::string spacecraft;
// The SPICE name of the instrument that is used to project the image onto this
// RenderablePlaneProjection
std::string instrument;
// The SPICE name of the default target that is imaged by this planet
std::optional<std::string> defaultTarget;
// The image that is used on this plane before any image is loaded from the
// ImageSequencerr
std::optional<std::string> texture;
};
#include "renderableplaneprojection_codegen.cpp"
@@ -60,17 +66,15 @@ namespace {
namespace openspace {
documentation::Documentation RenderablePlaneProjection::Documentation() {
return codegen::doc<Parameters>("spacecraftinstruments_renderableorbitdisc");
return codegen::doc<Parameters>("spacecraftinstruments_renderableplaneprojection");
}
RenderablePlaneProjection::RenderablePlaneProjection(const ghoul::Dictionary& dict)
: Renderable(dict)
{
const Parameters p = codegen::bake<Parameters>(dict);
_spacecraft = p.spacecraft.value_or(_spacecraft);
_instrument = p.instrument.value_or(_instrument);
_moving = p.moving.value_or(_moving);
_name = p.name.value_or(_name);
_spacecraft = p.spacecraft;
_instrument = p.instrument;
_defaultTarget = p.defaultTarget.value_or(_defaultTarget);
if (p.texture.has_value()) {
@@ -118,7 +122,7 @@ void RenderablePlaneProjection::render(const RenderData& data, RendererTasks&) {
_instrument
);
if (!_hasImage || (_moving && !active)) {
if (!_hasImage) {
return;
}
@@ -161,7 +165,7 @@ void RenderablePlaneProjection::update(const UpdateData& data) {
const double timePast = std::abs(img.timeRange.start - _previousTime);
if (_moving || _planeIsDirty) {
if (_planeIsDirty) {
updatePlane(img, time);
}
else if (timePast > std::numeric_limits<double>::epsilon()) {
@@ -245,9 +249,7 @@ void RenderablePlaneProjection::updatePlane(const Image& img, double currentTime
) * bounds[j];
glm::dvec3 cornerPosition = glm::proj(vecToTarget, bounds[j]);
if (!_moving) {
cornerPosition -= vecToTarget;
}
cornerPosition -= vecToTarget;
cornerPosition = SpiceManager::ref().frameTransformationMatrix(
GalacticFrame,
_target.frame,
@@ -258,15 +260,6 @@ void RenderablePlaneProjection::updatePlane(const Image& img, double currentTime
projection[j] = glm::vec3(cornerPosition * 1000.0);
}
if (!_moving) {
Scene* scene = global::renderEngine->scene();
SceneGraphNode* thisNode = scene->sceneGraphNode(_name);
SceneGraphNode* newParent = scene->sceneGraphNode(_target.node);
if (thisNode && newParent) {
thisNode->setParent(*newParent);
}
}
const GLfloat vertex_data[] = {
// square of two triangles drawn within fov in target coordinates
// x y z w s t
@@ -299,7 +292,7 @@ void RenderablePlaneProjection::updatePlane(const Image& img, double currentTime
reinterpret_cast<void*>(sizeof(GLfloat) * 4)
);
if (!_moving && !img.path.empty()) {
if (!img.path.empty()) {
_texturePath = img.path;
loadTexture();
}

View File

@@ -86,8 +86,6 @@ private:
std::string frame;
std::string node;
} _target;
std::string _name = "ImagePlane";
bool _moving = false;
bool _hasImage = false;
};

View File

@@ -82,7 +82,6 @@ set(OPENSPACE_SOURCE
${OPENSPACE_BASE_DIR}/src/network/parallelconnection.cpp
${OPENSPACE_BASE_DIR}/src/network/parallelpeer.cpp
${OPENSPACE_BASE_DIR}/src/network/parallelpeer_lua.inl
${OPENSPACE_BASE_DIR}/src/network/parallelserver.cpp
${OPENSPACE_BASE_DIR}/src/properties/optionproperty.cpp
${OPENSPACE_BASE_DIR}/src/properties/property.cpp
${OPENSPACE_BASE_DIR}/src/properties/propertyowner.cpp
@@ -262,7 +261,6 @@ set(OPENSPACE_HEADER
${OPENSPACE_BASE_DIR}/include/openspace/navigation/waypoint.h
${OPENSPACE_BASE_DIR}/include/openspace/network/parallelconnection.h
${OPENSPACE_BASE_DIR}/include/openspace/network/parallelpeer.h
${OPENSPACE_BASE_DIR}/include/openspace/network/parallelserver.h
${OPENSPACE_BASE_DIR}/include/openspace/network/messagestructures.h
${OPENSPACE_BASE_DIR}/include/openspace/network/messagestructureshelper.h
${OPENSPACE_BASE_DIR}/include/openspace/properties/listproperty.h

View File

@@ -1659,8 +1659,14 @@ void OpenSpaceEngine::removeModeChangeCallback(CallbackHandle handle) {
void setCameraFromProfile(const Profile& p) {
if (!p.camera.has_value()) {
throw ghoul::RuntimeError("No 'camera' entry exists in the startup profile");
// If the camera is not specified, we want to set it to a sensible default value
interaction::NavigationState nav;
nav.anchor = "Root";
nav.referenceFrame = "Root";
global::navigationHandler->setNavigationStateNextFrame(nav);
return;
}
std::visit(
overloaded{
[](const Profile::CameraNavState& navStateProfile) {

View File

@@ -40,6 +40,7 @@
#include <openspace/util/universalhelpers.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/misc/interpolator.h>
#include <glm/ext/quaternion_relational.hpp>
namespace {
constexpr const char _loggerCat[] = "Path";
@@ -131,7 +132,6 @@ Path::Path(Waypoint start, Waypoint end, Type type,
// We now know how long it took to traverse the path. Use that
_speedFactorFromDuration = _progressedTime / *duration;
resetPlaybackVariables();
}
}
@@ -161,23 +161,7 @@ CameraPose Path::traversePath(double dt, float speedScale) {
if (_type == Type::Linear) {
// Special handling of linear paths, so that it can be used when we are
// traversing very large distances without introducing precision problems
const glm::dvec3 prevPosToEnd = _prevPose.position - _end.position();
const double remainingDistance = glm::length(prevPosToEnd);
// Actual displacement may not be bigger than remaining distance
if (displacement > remainingDistance) {
displacement = remainingDistance;
_traveledDistance = pathLength();
_shouldQuit = true;
return _end.pose();
}
// Just move along the line from the current position to the target
newPose.position = _prevPose.position -
displacement * glm::normalize(prevPosToEnd);
const double relativeDistance = _traveledDistance / pathLength();
newPose.rotation = interpolateRotation(relativeDistance);
newPose = linearInterpolatedPose(_traveledDistance, displacement);
}
else {
if (std::abs(prevDistance - _traveledDistance) < LengthEpsilon) {
@@ -204,7 +188,14 @@ bool Path::hasReachedEnd() const {
return true;
}
return (_traveledDistance / pathLength()) >= 1.0;
bool isPositionFinished = (_traveledDistance / pathLength()) >= 1.0;
bool isRotationFinished = glm::all(glm::equal(
_prevPose.rotation,
_end.rotation(),
glm::epsilon<double>()
));
return isPositionFinished && isRotationFinished;
}
void Path::resetPlaybackVariables() {
@@ -214,6 +205,28 @@ void Path::resetPlaybackVariables() {
_shouldQuit = false;
}
CameraPose Path::linearInterpolatedPose(double distance, double displacement) {
ghoul_assert(_type == Type::Linear, "Path type must be linear");
const double relativeDistance = distance / pathLength();
const glm::dvec3 prevPosToEnd = _prevPose.position - _end.position();
const double remainingDistance = glm::length(prevPosToEnd);
CameraPose pose;
// Actual displacement may not be bigger than remaining distance
if (displacement > remainingDistance) {
_traveledDistance = pathLength();
pose.position = _end.position();
}
else {
// Just move along line from the current position to the target
const glm::dvec3 lineDir = glm::normalize(prevPosToEnd);
pose.position = _prevPose.position - displacement * lineDir;
}
pose.rotation = linearPathRotation(relativeDistance);
return pose;
}
CameraPose Path::interpolatedPose(double distance) const {
const double relativeDistance = distance / pathLength();
CameraPose cs;
@@ -227,6 +240,8 @@ glm::dquat Path::interpolateRotation(double t) const {
case Type::AvoidCollision:
return easedSlerpRotation(t);
case Type::Linear:
// @TODO (2022-03-29, emmbr) Fix so that rendering the rotation of linear
// paths works again. I.e. openspace.debugging.renderCameraPath
return linearPathRotation(t);
case Type::ZoomOutOverview:
case Type::AvoidCollisionWithLookAt:
@@ -243,45 +258,59 @@ glm::dquat Path::easedSlerpRotation(double t) const {
}
glm::dquat Path::linearPathRotation(double t) const {
const double tHalf = 0.5;
const glm::dvec3 a = ghoul::viewDirection(_start.rotation());
const glm::dvec3 b = ghoul::viewDirection(_end.rotation());
const double angle = std::acos(glm::dot(a, b)); // assumes length 1.0 for a & b
const glm::dvec3 endNodePos = _end.node()->worldPosition();
const glm::dvec3 endUp = _end.rotation() * glm::dvec3(0.0, 1.0, 0.0);
// Seconds per pi angles. Per default, it takes 5 seconds to turn 90 degrees
double factor = 5.0 / glm::half_pi<double>();
factor *= global::navigationHandler->pathNavigator().linearRotationSpeedFactor();
if (t < tHalf) {
// Interpolate to look at target
const glm::dvec3 halfWayPosition = _curve->positionAt(tHalf);
const glm::dquat q = ghoul::lookAtQuaternion(halfWayPosition, endNodePos, endUp);
double turnDuration = std::max(angle * factor, 1.0); // Always at least 1 second
const double time = glm::clamp(_progressedTime / turnDuration, 0.0, 1.0);
return easedSlerpRotation(time);
const double tScaled = ghoul::sineEaseInOut(t / tHalf);
return glm::slerp(_start.rotation(), q, tScaled);
}
// @TODO (2022-03-18, emmbr) Leaving this for now, as something similar might have to
// be implemented for navigation states. But should be removed/reimplemented
// This distance is guaranteed to be strictly decreasing for linear paths
const double distanceToEnd = glm::distance(_prevPose.position, _end.position());
//const glm::dvec3 endNodePos = _end.node()->worldPosition();
//const glm::dvec3 endUp = _end.rotation() * glm::dvec3(0.0, 1.0, 0.0);
// Determine the distance at which to start interpolating to the target rotation.
// The magic numbers here are just randomly picked constants, set to make the
// resulting rotation look ok-ish
double closingUpDistance = 10.0 * _end.validBoundingSphere();
if (pathLength() < 2.0 * closingUpDistance) {
closingUpDistance = 0.2 * pathLength();
}
//const double tHalf = 0.5;
//if (t < tHalf) {
// // Interpolate to look at target
// const glm::dvec3 halfWayPosition = _curve->positionAt(tHalf);
// const glm::dquat q = ghoul::lookAtQuaternion(halfWayPosition, endNodePos, endUp);
if (distanceToEnd < closingUpDistance) {
// Interpolate to target rotation
const double tScaled = ghoul::sineEaseInOut(1.0 - distanceToEnd / closingUpDistance);
// const double tScaled = ghoul::sineEaseInOut(t / tHalf);
// return glm::slerp(_start.rotation(), q, tScaled);
//}
// Compute a position in front of the camera at the end orientation
const double inFrontDistance = glm::distance(_end.position(), endNodePos);
const glm::dvec3 viewDir = ghoul::viewDirection(_end.rotation());
const glm::dvec3 inFrontOfEnd = _end.position() + inFrontDistance * viewDir;
const glm::dvec3 lookAtPos = ghoul::interpolateLinear(tScaled, endNodePos, inFrontOfEnd);
return ghoul::lookAtQuaternion(_prevPose.position, lookAtPos, endUp);
}
//// This distance is guaranteed to be strictly decreasing for linear paths
//const double distanceToEnd = glm::distance(_prevPose.position, _end.position());
// Keep looking at the end node
return ghoul::lookAtQuaternion(_prevPose.position, endNodePos, endUp);
//// Determine the distance at which to start interpolating to the target rotation.
//// The magic numbers here are just randomly picked constants, set to make the
//// resulting rotation look ok-ish
//double closingUpDistance = 10.0 * _end.validBoundingSphere();
//if (pathLength() < 2.0 * closingUpDistance) {
// closingUpDistance = 0.2 * pathLength();
//}
//if (distanceToEnd < closingUpDistance) {
// // Interpolate to target rotation
// const double tScaled = ghoul::sineEaseInOut(1.0 - distanceToEnd / closingUpDistance);
// // Compute a position in front of the camera at the end orientation
// const double inFrontDistance = glm::distance(_end.position(), endNodePos);
// const glm::dvec3 viewDir = ghoul::viewDirection(_end.rotation());
// const glm::dvec3 inFrontOfEnd = _end.position() + inFrontDistance * viewDir;
// const glm::dvec3 lookAtPos = ghoul::interpolateLinear(tScaled, endNodePos, inFrontOfEnd);
// return ghoul::lookAtQuaternion(_prevPose.position, lookAtPos, endUp);
//}
//// Keep looking at the end node
//return ghoul::lookAtQuaternion(_prevPose.position, endNodePos, endUp);
}
glm::dquat Path::lookAtTargetsRotation(double t) const {
@@ -350,8 +379,17 @@ double Path::speedAlongPath(double traveledDistance) const {
double startUpDistance = DampenDistanceFactor * _start.validBoundingSphere();
double closeUpDistance = DampenDistanceFactor * _end.validBoundingSphere();
// Kind of an ugly workaround to make damping behave over very long paths, and/or
// where the target nodes might have large bounding spheres. The current max is set
// based on the order of magnitude of the solar system, which ofc is very specific to
// our space content...
// @TODO (2022-03-22, emmbr) Come up with a better more general solution
constexpr const double MaxDistance = 1E12;
startUpDistance = glm::min(MaxDistance, startUpDistance);
closeUpDistance = glm::min(MaxDistance, closeUpDistance);
if (pathLength() < startUpDistance + closeUpDistance) {
startUpDistance = 0.49 * pathLength(); // a little less than half
startUpDistance = 0.4 * pathLength(); // a little less than half
closeUpDistance = startUpDistance;
}
@@ -619,6 +657,23 @@ Path createPathFromDictionary(const ghoul::Dictionary& dictionary,
p.useTargetUpDirection.value_or(false)
};
double startToTargetCenterDistance = glm::distance(
startPoint.position(),
targetNode->worldPosition()
);
// Use a linear path if camera start is within the bounding sphere
const PathNavigator& navigator = global::navigationHandler->pathNavigator();
const double bs = navigator.findValidBoundingSphere(targetNode);
bool withinTargetBoundingSphere = startToTargetCenterDistance < bs;
if (withinTargetBoundingSphere) {
LINFO(
"Camera is within the bounding sphere of the target node. "
"Using linear path"
);
type = Path::Type::Linear;
}
waypoints = { computeWaypointFromNodeInfo(info, startPoint, type) };
break;
}

View File

@@ -69,7 +69,7 @@ namespace {
constexpr openspace::properties::Property::PropertyInfo SpeedScaleInfo = {
"SpeedScale",
"Speed Scale",
"Scale factor that the speed will be mulitplied with during path traversal. "
"Scale factor that the speed will be multiplied with during path traversal. "
"Can be used to speed up or slow down the camera motion, depending on if the "
"value is larger than or smaller than one."
};
@@ -90,6 +90,14 @@ namespace {
"object."
};
constexpr openspace::properties::Property::PropertyInfo RotationSpeedFactorInfo = {
"RotationSpeedFactor",
"Rotation Speed Factor (Linear Path)",
"Affects how fast the camera rotates to the target rotation during a linear "
"path. A value of 1 means that the camera will rotate 90 degrees in about 5 "
"seconds. A value of 2 means twice that fast, and so on."
};
constexpr const openspace::properties::Property::PropertyInfo MinBoundingSphereInfo =
{
"MinimalValidBoundingSphere",
@@ -119,6 +127,7 @@ PathNavigator::PathNavigator()
, _speedScale(SpeedScaleInfo, 1.f, 0.01f, 2.f)
, _applyIdleBehaviorOnFinish(IdleBehaviorOnFinishInfo, false)
, _arrivalDistanceFactor(ArrivalDistanceFactorInfo, 2.0, 0.1, 20.0)
, _linearRotationSpeedFactor(RotationSpeedFactorInfo, 1.f, 0.1f, 2.f)
, _minValidBoundingSphere(MinBoundingSphereInfo, 10.0, 1.0, 3e10)
, _relevantNodeTags(RelevantNodeTagsInfo)
{
@@ -134,6 +143,7 @@ PathNavigator::PathNavigator()
addProperty(_speedScale);
addProperty(_applyIdleBehaviorOnFinish);
addProperty(_arrivalDistanceFactor);
addProperty(_linearRotationSpeedFactor);
addProperty(_minValidBoundingSphere);
_relevantNodeTags = std::vector<std::string>{
@@ -166,6 +176,10 @@ double PathNavigator::arrivalDistanceFactor() const {
return _arrivalDistanceFactor;
}
float PathNavigator::linearRotationSpeedFactor() const {
return _linearRotationSpeedFactor;
}
bool PathNavigator::hasCurrentPath() const {
return _currentPath != nullptr;
}

View File

@@ -37,7 +37,8 @@ namespace {
namespace openspace {
const unsigned int ParallelConnection::ProtocolVersion = 5;
// Gonna do some UTF-like magic once we reach 255 to introduce a second byte or something
const uint8_t ParallelConnection::ProtocolVersion = 6;
ParallelConnection::Message::Message(MessageType t, std::vector<char> c)
: type(t)
@@ -52,8 +53,9 @@ ParallelConnection::DataMessage::DataMessage(datamessagestructures::Type t,
, content(std::move(c))
{}
ParallelConnection::ConnectionLostError::ConnectionLostError()
ParallelConnection::ConnectionLostError::ConnectionLostError(bool shouldLogError_)
: ghoul::RuntimeError("Parallel connection lost", "ParallelConnection")
, shouldLogError(shouldLogError_)
{}
ParallelConnection::ParallelConnection(std::unique_ptr<ghoul::io::TcpSocket> socket)
@@ -65,14 +67,14 @@ bool ParallelConnection::isConnectedOrConnecting() const {
}
void ParallelConnection::sendDataMessage(const DataMessage& dataMessage) {
const uint32_t dataMessageTypeOut = static_cast<uint32_t>(dataMessage.type);
const uint8_t dataMessageTypeOut = static_cast<uint8_t>(dataMessage.type);
const double dataMessageTimestamp = dataMessage.timestamp;
std::vector<char> messageContent;
messageContent.insert(
messageContent.end(),
reinterpret_cast<const char*>(&dataMessageTypeOut),
reinterpret_cast<const char*>(&dataMessageTypeOut) + sizeof(uint32_t)
reinterpret_cast<const char*>(&dataMessageTypeOut) + sizeof(uint8_t)
);
messageContent.insert(
@@ -90,7 +92,7 @@ void ParallelConnection::sendDataMessage(const DataMessage& dataMessage) {
}
bool ParallelConnection::sendMessage(const Message& message) {
const uint32_t messageTypeOut = static_cast<uint32_t>(message.type);
const uint8_t messageTypeOut = static_cast<uint8_t>(message.type);
const uint32_t messageSizeOut = static_cast<uint32_t>(message.content.size());
std::vector<char> header;
@@ -100,12 +102,12 @@ bool ParallelConnection::sendMessage(const Message& message) {
header.insert(header.end(),
reinterpret_cast<const char*>(&ProtocolVersion),
reinterpret_cast<const char*>(&ProtocolVersion) + sizeof(uint32_t)
reinterpret_cast<const char*>(&ProtocolVersion) + sizeof(uint8_t)
);
header.insert(header.end(),
reinterpret_cast<const char*>(&messageTypeOut),
reinterpret_cast<const char*>(&messageTypeOut) + sizeof(uint32_t)
reinterpret_cast<const char*>(&messageTypeOut) + sizeof(uint8_t)
);
header.insert(header.end(),
@@ -123,6 +125,7 @@ bool ParallelConnection::sendMessage(const Message& message) {
}
void ParallelConnection::disconnect() {
_shouldDisconnect = true;
if (_socket) {
_socket->disconnect();
}
@@ -136,7 +139,9 @@ ParallelConnection::Message ParallelConnection::receiveMessage() {
// Header consists of...
constexpr size_t HeaderSize =
2 * sizeof(char) + // OS
3 * sizeof(uint32_t); // Protocol version, message type and message size
sizeof(uint8_t) + // Protocol version
sizeof(uint8_t) + // Message type
sizeof(uint32_t); // message size
// Create basic buffer for receiving first part of messages
std::vector<char> headerBuffer(HeaderSize);
@@ -144,20 +149,27 @@ ParallelConnection::Message ParallelConnection::receiveMessage() {
// Receive the header data
if (!_socket->get(headerBuffer.data(), HeaderSize)) {
LERROR("Failed to read header from socket. Disconencting.");
throw ConnectionLostError();
// The `get` call is blocking until something happens, so we might end up here if
// the socket properly closed or if the loading legitimately failed
if (_shouldDisconnect) {
throw ConnectionLostError(false);
}
else {
LERROR("Failed to read header from socket. Disconnecting");
throw ConnectionLostError();
}
}
// Make sure that header matches this version of OpenSpace
if (!(headerBuffer[0] == 'O' && headerBuffer[1] == 'S')) {
LERROR("Expected to read message header 'OS' from socket.");
LERROR("Expected to read message header 'OS' from socket");
throw ConnectionLostError();
}
size_t offset = 2;
const uint32_t protocolVersionIn =
*reinterpret_cast<uint32_t*>(headerBuffer.data() + offset);
offset += sizeof(uint32_t);
const uint8_t protocolVersionIn =
*reinterpret_cast<uint8_t*>(headerBuffer.data() + offset);
offset += sizeof(uint8_t);
if (protocolVersionIn != ProtocolVersion) {
LERROR(fmt::format(
@@ -168,9 +180,9 @@ ParallelConnection::Message ParallelConnection::receiveMessage() {
throw ConnectionLostError();
}
const uint32_t messageTypeIn =
*reinterpret_cast<uint32_t*>(headerBuffer.data() + offset);
offset += sizeof(uint32_t);
const uint8_t messageTypeIn =
*reinterpret_cast<uint8_t*>(headerBuffer.data() + offset);
offset += sizeof(uint8_t);
const uint32_t messageSizeIn =
*reinterpret_cast<uint32_t*>(headerBuffer.data() + offset);
@@ -181,7 +193,7 @@ ParallelConnection::Message ParallelConnection::receiveMessage() {
// Receive the payload
messageBuffer.resize(messageSize);
if (!_socket->get(messageBuffer.data(), messageSize)) {
LERROR("Failed to read message from socket. Disconencting.");
LERROR("Failed to read message from socket. Disconnecting");
throw ConnectionLostError();
}

View File

@@ -163,31 +163,59 @@ void ParallelPeer::disconnect() {
}
void ParallelPeer::sendAuthentication() {
std::string name = _name;
// Length of this nodes name
const uint32_t nameLength = static_cast<uint32_t>(name.length());
std::string password = _password;
if (password.size() > std::numeric_limits<uint16_t>::max()) {
password.resize(std::numeric_limits<uint16_t>::max());
}
const uint16_t passwordSize = static_cast<uint16_t>(password.size());
// Total size of the buffer: (passcode + namelength + name)
const size_t size = sizeof(uint64_t) + sizeof(uint32_t) + nameLength;
std::string hostPassword = _hostPassword;
if (hostPassword.size() > std::numeric_limits<uint16_t>::max()) {
hostPassword.resize(std::numeric_limits<uint16_t>::max());
}
const uint16_t hostPasswordSize = static_cast<uint16_t>(hostPassword.size());
std::string name = _name;
if (name.size() > std::numeric_limits<uint8_t>::max()) {
name.resize(std::numeric_limits<uint8_t>::max());
}
const uint8_t nameLength = static_cast<uint8_t>(name.length());
// Total size of the buffer
const size_t size =
sizeof(uint16_t) + // password length
passwordSize + // password
sizeof(uint16_t) + // host password length
hostPasswordSize + // host password
sizeof(uint8_t) + // name length
nameLength; // name
// Create and reserve buffer
std::vector<char> buffer;
buffer.reserve(size);
const uint64_t passCode = std::hash<std::string>{}(_password.value());
// Write the hashed password to buffer
// Write the password to buffer
buffer.insert(
buffer.end(),
reinterpret_cast<const char*>(&passCode),
reinterpret_cast<const char*>(&passCode) + sizeof(uint64_t)
reinterpret_cast<const char*>(&passwordSize),
reinterpret_cast<const char*>(&passwordSize) + sizeof(uint16_t)
);
buffer.insert(buffer.end(), password.begin(), password.end());
// Write the host password to buffer
buffer.insert(
buffer.end(),
reinterpret_cast<const char*>(&hostPasswordSize),
reinterpret_cast<const char*>(&hostPasswordSize) + sizeof(uint16_t)
);
buffer.insert(buffer.end(), hostPassword.begin(), hostPassword.end());
// Write the length of the nodes name to buffer
buffer.insert(
buffer.end(),
reinterpret_cast<const char*>(&nameLength),
reinterpret_cast<const char*>(&nameLength) + sizeof(uint32_t)
reinterpret_cast<const char*>(&nameLength) + sizeof(uint8_t)
);
// Write this node's name to buffer
@@ -265,8 +293,8 @@ void ParallelPeer::dataMessageReceived(const std::vector<char>& message) {
size_t offset = 0;
// The type of data message received
const uint32_t type = *(reinterpret_cast<const uint32_t*>(message.data() + offset));
offset += sizeof(uint32_t);
const uint8_t type = *(reinterpret_cast<const uint8_t*>(message.data() + offset));
offset += sizeof(uint8_t);
const double timestamp = *(reinterpret_cast<const double*>(message.data() + offset));
offset += sizeof(double);
@@ -363,19 +391,19 @@ void ParallelPeer::dataMessageReceived(const std::vector<char>& message) {
}
void ParallelPeer::connectionStatusMessageReceived(const std::vector<char>& message) {
if (message.size() < 2 * sizeof(uint32_t)) {
if (message.size() < 2 * sizeof(uint8_t)) {
LERROR("Malformed connection status message");
return;
}
size_t pointer = 0;
uint32_t statusIn = *(reinterpret_cast<const uint32_t*>(&message[pointer]));
const uint8_t statusIn = *(reinterpret_cast<const uint8_t*>(&message[pointer]));
const ParallelConnection::Status status = static_cast<ParallelConnection::Status>(
statusIn
);
pointer += sizeof(uint32_t);
pointer += sizeof(uint8_t);
const size_t hostNameSize = *(reinterpret_cast<const uint32_t*>(&message[pointer]));
pointer += sizeof(uint32_t);
const uint8_t hostNameSize = *(reinterpret_cast<const uint8_t*>(&message[pointer]));
pointer += sizeof(uint8_t);
if (hostNameSize > message.size() - pointer) {
LERROR("Malformed connection status message");
@@ -409,8 +437,7 @@ void ParallelPeer::connectionStatusMessageReceived(const std::vector<char>& mess
global::timeManager->clearKeyframes();
}
void ParallelPeer::nConnectionsMessageReceived(const std::vector<char>& message)
{
void ParallelPeer::nConnectionsMessageReceived(const std::vector<char>& message) {
if (message.size() < sizeof(uint32_t)) {
LERROR("Malformed host info message");
return;
@@ -424,8 +451,10 @@ void ParallelPeer::handleCommunication() {
try {
ParallelConnection::Message m = _connection.receiveMessage();
queueInMessage(m);
} catch (const ParallelConnection::ConnectionLostError&) {
LERROR("Parallel connection lost");
} catch (const ParallelConnection::ConnectionLostError& e) {
if (e.shouldLogError) {
LERROR("Parallel connection lost");
}
}
}
setStatus(ParallelConnection::Status::Disconnected);
@@ -445,12 +474,14 @@ void ParallelPeer::setName(std::string name) {
void ParallelPeer::requestHostship() {
std::vector<char> buffer;
uint64_t passwordHash = std::hash<std::string>{}(_hostPassword);
std::string hostPw = _hostPassword;
uint16_t hostPwSize = static_cast<uint16_t>(hostPw.size());
buffer.insert(
buffer.end(),
reinterpret_cast<char*>(&passwordHash),
reinterpret_cast<char*>(&passwordHash) + sizeof(uint64_t)
reinterpret_cast<const char*>(&hostPwSize),
reinterpret_cast<const char*>(&hostPwSize) + sizeof(uint16_t)
);
buffer.insert(buffer.end(), hostPw.begin(), hostPw.end());
_connection.sendMessage(ParallelConnection::Message(
ParallelConnection::MessageType::HostshipRequest,
@@ -504,7 +535,7 @@ void ParallelPeer::resetTimeOffset() {
void ParallelPeer::preSynchronization() {
ZoneScoped
std::unique_lock<std::mutex> unqlock(_receiveBufferMutex);
std::unique_lock<std::mutex> unlock(_receiveBufferMutex);
while (!_receiveBuffer.empty()) {
ParallelConnection::Message& message = _receiveBuffer.front();
handleMessage(message);

View File

@@ -1,426 +0,0 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2022 *
* *
* 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/network/parallelserver.h>
#include <ghoul/fmt.h>
#include <ghoul/io/socket/tcpsocket.h>
#include <ghoul/logging/logmanager.h>
#include <functional>
// @TODO(abock): In the entire class remove std::shared_ptr<Peer> by const Peer& where
// possible to simplify the interface
namespace {
constexpr const char* _loggerCat = "ParallelServer";
} // namespace
namespace openspace {
void ParallelServer::start(int port, const std::string& password,
const std::string& changeHostPassword)
{
_socketServer.listen(port);
_passwordHash = std::hash<std::string>{}(password);
_changeHostPasswordHash = std::hash<std::string>{}(changeHostPassword);
_serverThread = std::thread([this](){ handleNewPeers(); });
_eventLoopThread = std::thread([this]() { eventLoop(); });
}
void ParallelServer::setDefaultHostAddress(std::string defaultHostAddress) {
std::lock_guard lock(_hostInfoMutex);
_defaultHostAddress = std::move(defaultHostAddress);
}
std::string ParallelServer::defaultHostAddress() const {
std::lock_guard lock(_hostInfoMutex);
return _defaultHostAddress;
}
void ParallelServer::stop() {
_shouldStop = true;
_socketServer.close();
}
void ParallelServer::handleNewPeers() {
while (!_shouldStop) {
std::unique_ptr<ghoul::io::TcpSocket> s = _socketServer.awaitPendingTcpSocket();
s->startStreams();
const size_t id = _nextConnectionId++;
auto p = std::make_shared<Peer>(Peer{
id,
"",
ParallelConnection(std::move(s)),
ParallelConnection::Status::Connecting,
std::thread()
});
auto it = _peers.emplace(p->id, p);
it.first->second->thread = std::thread([this, id]() { handlePeer(id); });
}
}
std::shared_ptr<ParallelServer::Peer> ParallelServer::peer(size_t id) {
std::lock_guard lock(_peerListMutex);
const auto it = _peers.find(id);
if (it == _peers.end()) {
return nullptr;
}
return it->second;
}
void ParallelServer::handlePeer(size_t id) {
while (!_shouldStop) {
std::shared_ptr<Peer> p = peer(id);
if (!p) {
return;
}
if (!p->parallelConnection.isConnectedOrConnecting()) {
return;
}
try {
ParallelConnection::Message m = p->parallelConnection.receiveMessage();
PeerMessage msg;
msg.peerId = id;
msg.message = m;
_incomingMessages.push(msg);
}
catch (const ParallelConnection::ConnectionLostError&) {
LERROR(fmt::format("Connection lost to {}", p->id));
PeerMessage msg;
msg.peerId = id;
msg.message = ParallelConnection::Message(
ParallelConnection::MessageType::Disconnection, std::vector<char>()
);
_incomingMessages.push(msg);
return;
}
}
}
void ParallelServer::eventLoop() {
while (!_shouldStop) {
PeerMessage pm = _incomingMessages.pop();
handlePeerMessage(std::move(pm));
}
}
void ParallelServer::handlePeerMessage(PeerMessage peerMessage) {
const size_t peerId = peerMessage.peerId;
auto it = _peers.find(peerId);
if (it == _peers.end()) {
return;
}
std::shared_ptr<Peer>& peer = it->second;
const ParallelConnection::MessageType type = peerMessage.message.type;
std::vector<char>& data = peerMessage.message.content;
switch (type) {
case ParallelConnection::MessageType::Authentication:
handleAuthentication(peer, std::move(data));
break;
case ParallelConnection::MessageType::Data:
handleData(*peer, std::move(data));
break;
case ParallelConnection::MessageType::HostshipRequest:
handleHostshipRequest(peer, std::move(data));
break;
case ParallelConnection::MessageType::HostshipResignation:
handleHostshipResignation(*peer);
break;
case ParallelConnection::MessageType::Disconnection:
disconnect(*peer);
break;
default:
LERROR(fmt::format("Unsupported message type: {}", static_cast<int>(type)));
break;
}
}
void ParallelServer::handleAuthentication(std::shared_ptr<Peer> peer,
std::vector<char> message)
{
std::stringstream input(std::string(message.begin(), message.end()));
// 8 bytes passcode
uint64_t passwordHash = 0;
input.read(reinterpret_cast<char*>(&passwordHash), sizeof(uint64_t));
if (passwordHash != _passwordHash) {
LERROR(fmt::format("Connection {} provided incorrect passcode.", peer->id));
disconnect(*peer);
return;
}
// 4 bytes name size
uint32_t nameSize = 0;
input.read(reinterpret_cast<char*>(&nameSize), sizeof(uint32_t));
// <nameSize> bytes name
std::string name(nameSize, static_cast<char>(0));
input.read(&name[0], nameSize);
if (nameSize == 0) {
name = "Anonymous";
}
setName(*peer, name);
LINFO(fmt::format("Connection established with {} ('{}')", peer->id, name));
std::string defaultHostAddress;
{
std::lock_guard _hostMutex(_hostInfoMutex);
defaultHostAddress = _defaultHostAddress;
}
if (_hostPeerId == 0 &&
peer->parallelConnection.socket()->address() == defaultHostAddress)
{
// Directly promote the conenction to host (initialize) if there is no host, and
// ip matches default host ip.
LINFO(fmt::format("Connection {} directly promoted to host", peer->id));
assignHost(peer);
for (std::pair<const size_t, std::shared_ptr<Peer>>& it : _peers) {
// sendConnectionStatus(it->second) ?
sendConnectionStatus(*peer);
}
}
else {
setToClient(*peer);
}
setNConnections(nConnections() + 1);
}
void ParallelServer::handleData(const Peer& peer, std::vector<char> data) {
if (peer.id != _hostPeerId) {
LINFO(fmt::format(
"Ignoring connection {} trying to send data without being host", peer.id
));
}
sendMessageToClients(ParallelConnection::MessageType::Data, data);
}
void ParallelServer::handleHostshipRequest(std::shared_ptr<Peer> peer,
std::vector<char> message)
{
std::stringstream input(std::string(message.begin(), message.end()));
LINFO(fmt::format("Connection {} requested hostship", peer->id));
uint64_t passwordHash = 0;
input.read(reinterpret_cast<char*>(&passwordHash), sizeof(uint64_t));
if (passwordHash != _changeHostPasswordHash) {
LERROR(fmt::format("Connection {} provided incorrect host password", peer->id));
return;
}
size_t oldHostPeerId = 0;
{
std::lock_guard lock(_hostInfoMutex);
oldHostPeerId = _hostPeerId;
}
if (oldHostPeerId == peer->id) {
LINFO(fmt::format("Connection {} is already the host", peer->id));
return;
}
assignHost(peer);
LINFO(fmt::format("Switched host from {} to {}", oldHostPeerId, peer->id));
}
void ParallelServer::handleHostshipResignation(Peer& peer) {
LINFO(fmt::format("Connection {} wants to resign its hostship", peer.id));
setToClient(peer);
LINFO(fmt::format("Connection {} resigned as host", peer.id));
}
bool ParallelServer::isConnected(const Peer& peer) const {
return peer.status != ParallelConnection::Status::Connecting &&
peer.status != ParallelConnection::Status::Disconnected;
}
void ParallelServer::sendMessage(Peer& peer, ParallelConnection::MessageType messageType,
const std::vector<char>& message)
{
peer.parallelConnection.sendMessage({ messageType, message });
}
void ParallelServer::sendMessageToAll(ParallelConnection::MessageType messageType,
const std::vector<char>& message)
{
for (std::pair<const size_t, std::shared_ptr<Peer>>& it : _peers) {
if (isConnected(*it.second)) {
it.second->parallelConnection.sendMessage({ messageType, message });
}
}
}
void ParallelServer::sendMessageToClients(ParallelConnection::MessageType messageType,
const std::vector<char>& message)
{
for (std::pair<const size_t, std::shared_ptr<Peer>>& it : _peers) {
if (it.second->status == ParallelConnection::Status::ClientWithHost) {
it.second->parallelConnection.sendMessage({ messageType, message });
}
}
}
void ParallelServer::disconnect(Peer& peer) {
if (isConnected(peer)) {
setNConnections(nConnections() - 1);
}
size_t hostPeerId = 0;
{
std::lock_guard lock(_hostInfoMutex);
hostPeerId = _hostPeerId;
}
// Make sure any disconnecting host is first degraded to client, in order to notify
// other clients about host disconnection.
if (peer.id == hostPeerId) {
setToClient(peer);
}
peer.parallelConnection.disconnect();
peer.thread.join();
_peers.erase(peer.id);
}
void ParallelServer::setName(Peer& peer, std::string name) {
peer.name = std::move(name);
size_t hostPeerId = 0;
{
std::lock_guard lock(_hostInfoMutex);
hostPeerId = _hostPeerId;
}
// Make sure everyone gets the new host name.
if (peer.id == hostPeerId) {
{
std::lock_guard lock(_hostInfoMutex);
_hostName = peer.name;
}
for (std::pair<const size_t, std::shared_ptr<Peer>>& it : _peers) {
// sendConnectionStatus(it->second) ?
sendConnectionStatus(peer);
}
}
}
void ParallelServer::assignHost(std::shared_ptr<Peer> newHost) {
{
std::lock_guard lock(_hostInfoMutex);
std::shared_ptr<ParallelServer::Peer> oldHost = peer(_hostPeerId);
if (oldHost) {
oldHost->status = ParallelConnection::Status::ClientWithHost;
}
_hostPeerId = newHost->id;
_hostName = newHost->name;
}
newHost->status = ParallelConnection::Status::Host;
for (std::pair<const size_t, std::shared_ptr<Peer>>& it : _peers) {
if (it.second != newHost) {
it.second->status = ParallelConnection::Status::ClientWithHost;
}
sendConnectionStatus(*it.second);
}
}
void ParallelServer::setToClient(Peer& peer) {
if (peer.status == ParallelConnection::Status::Host) {
{
std::lock_guard lock(_hostInfoMutex);
_hostPeerId = 0;
_hostName.clear();
}
// If host becomes client, make all clients hostless.
for (std::pair<const size_t, std::shared_ptr<Peer>>& it : _peers) {
it.second->status = ParallelConnection::Status::ClientWithoutHost;
sendConnectionStatus(*it.second);
}
}
else {
peer.status = (_hostPeerId > 0) ?
ParallelConnection::Status::ClientWithHost :
ParallelConnection::Status::ClientWithoutHost;
sendConnectionStatus(peer);
}
}
void ParallelServer::setNConnections(size_t nConnections) {
_nConnections = nConnections;
std::vector<char> data;
const uint32_t n = static_cast<uint32_t>(_nConnections);
data.insert(
data.end(),
reinterpret_cast<const char*>(&n),
reinterpret_cast<const char*>(&n) + sizeof(uint32_t)
);
sendMessageToAll(ParallelConnection::MessageType::NConnections, data);
}
void ParallelServer::sendConnectionStatus(Peer& peer) {
std::vector<char> data;
const uint32_t outStatus = static_cast<uint32_t>(peer.status);
data.insert(
data.end(),
reinterpret_cast<const char*>(&outStatus),
reinterpret_cast<const char*>(&outStatus) + sizeof(uint32_t)
);
const uint32_t outHostNameSize = static_cast<uint32_t>(_hostName.size());
data.insert(
data.end(),
reinterpret_cast<const char*>(&outHostNameSize),
reinterpret_cast<const char*>(&outHostNameSize) + sizeof(uint32_t)
);
data.insert(
data.end(),
_hostName.data(),
_hostName.data() + outHostNameSize
);
sendMessage(peer, ParallelConnection::MessageType::ConnectionStatus, data);
}
size_t ParallelServer::nConnections() const {
return _nConnections;
}
} // namespace openspace

View File

@@ -1072,7 +1072,8 @@ scripting::LuaLibrary RenderEngine::luaLibrary() {
{
codegen::lua::AddScreenSpaceRenderable,
codegen::lua::RemoveScreenSpaceRenderable,
codegen::lua::TakeScreenshot
codegen::lua::TakeScreenshot,
codegen::lua::DpiScaling
}
};
}

View File

@@ -67,6 +67,12 @@ namespace {
return static_cast<int>(screenshotNumber);
}
// Extracts the DPI scaling for either the GUI window or if there is no dedicated GUI
// window, the first window.
[[codegen::luawrap]] float dpiScaling() {
return openspace::global::windowDelegate->osDpiScaling();
}
#include "renderengine_lua_codegen.cpp"
} // namespace

View File

@@ -343,6 +343,10 @@ bool AssetManager::loadAsset(Asset* asset, Asset* parent) {
LERROR(fmt::format("Could not load asset {}: {}", asset->path(), e.message));
return false;
}
catch (const ghoul::RuntimeError& e) {
LERRORC(e.component, e.message);
return false;
}
// Extract meta information from the asset file if it was provided
lua_getglobal(*_luaState, AssetGlobalVariableName);

View File

@@ -61,7 +61,7 @@ namespace {
* Returns the paths to all loaded assets, loaded directly or indirectly, as a table
* containing the paths to all loaded assets.
*/
[[codegen::luawrap]] std::vector<std::string> allAssets(std::string assetName) {
[[codegen::luawrap]] std::vector<std::string> allAssets() {
using namespace openspace;
std::vector<const Asset*> as = global::openSpaceEngine->assetManager().allAssets();
std::vector<std::string> res;

View File

@@ -34,7 +34,7 @@ namespace {
OS os = CpuCap.operatingSystem();
switch (os) {
case OS::Windows10:
case OS::Windows10or11:
case OS::WindowsServer2016:
case OS::WindowsVista:
case OS::WindowsServer2008:

View File

@@ -896,24 +896,24 @@ bool TimeManager::isPlayingBackSessionRecording() const {
}
void TimeManager::setTimeFromProfile(const Profile& p) {
Time t;
if (p.time.has_value()) {
switch (p.time.value().type) {
case Profile::Time::Type::Relative:
t.setTimeRelativeFromProfile(p.time.value().value);
break;
case Profile::Time::Type::Relative:
Time::setTimeRelativeFromProfile(p.time.value().value);
break;
case Profile::Time::Type::Absolute:
t.setTimeAbsoluteFromProfile(p.time.value().value);
break;
case Profile::Time::Type::Absolute:
Time::setTimeAbsoluteFromProfile(p.time.value().value);
break;
default:
throw ghoul::MissingCaseException();
default:
throw ghoul::MissingCaseException();
}
}
else {
throw ghoul::RuntimeError("No 'time' entry exists in the startup profile");
// No value was specified so we are using 'now' instead
std::string now = std::string(Time::now().UTC());
Time::setTimeAbsoluteFromProfile(now);
}
}