mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-01-04 18:51:17 -06:00
Merge branch 'master' into feature/jwst-update
This commit is contained in:
15
CREDITS.md
15
CREDITS.md
@@ -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
6
Jenkinsfile
vendored
@@ -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 = [];
|
||||
|
||||
@@ -34,7 +34,7 @@ OpenSpace requires at least support for [OpenGL](https://www.opengl.org/) versio
|
||||

|
||||
|
||||
# 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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
75
apps/OpenSpace/ext/launcher/include/profile/assetedit.h
Normal file
75
apps/OpenSpace/ext/launcher/include/profile/assetedit.h
Normal 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__
|
||||
@@ -60,6 +60,7 @@ private:
|
||||
* \return the #std::string summary
|
||||
*/
|
||||
QString createTextSummary();
|
||||
void openAssetEditor();
|
||||
|
||||
openspace::Profile* _profile = nullptr;
|
||||
AssetTreeModel _assetTreeModel;
|
||||
|
||||
136
apps/OpenSpace/ext/launcher/include/profile/horizonsdialog.h
Normal file
136
apps/OpenSpace/ext/launcher/include/profile/horizonsdialog.h
Normal 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__
|
||||
@@ -40,6 +40,7 @@
|
||||
#include <QPushButton>
|
||||
#include <QTextBrowser>
|
||||
#include <QVector>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
class DisplayWindowUnion : public QWidget {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
208
apps/OpenSpace/ext/launcher/src/profile/assetedit.cpp
Normal file
208
apps/OpenSpace/ext/launcher/src/profile/assetedit.cpp
Normal 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();
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
1198
apps/OpenSpace/ext/launcher/src/profile/horizonsdialog.cpp
Normal file
1198
apps/OpenSpace/ext/launcher/src/profile/horizonsdialog.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
Submodule apps/OpenSpace/ext/sgct updated: 4964fd4109...2b3445eeb8
@@ -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
|
||||
|
||||
|
||||
@@ -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 ()
|
||||
@@ -1 +0,0 @@
|
||||
set(DEFAULT_APPLICATION ON)
|
||||
@@ -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 |
@@ -1 +0,0 @@
|
||||
IDI_ICON1 ICON DISCARDABLE "openspace.ico"
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
188
data/assets/util/asset_helper.asset
Normal file
188
data/assets/util/asset_helper.asset
Normal 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)
|
||||
37
data/assets/util/dpiscaling.asset
Normal file
37
data/assets/util/dpiscaling.asset
Normal 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"
|
||||
}
|
||||
Submodule ext/ghoul updated: 8d85f42fc3...5e0f6278c4
@@ -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; };
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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__
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
872
modules/space/horizonsfile.cpp
Normal file
872
modules/space/horizonsfile.cpp
Normal 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
|
||||
139
modules/space/horizonsfile.h
Normal file
139
modules/space/horizonsfile.h
Normal 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__
|
||||
@@ -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*>(×tamp), 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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -86,8 +86,6 @@ private:
|
||||
std::string frame;
|
||||
std::string node;
|
||||
} _target;
|
||||
std::string _name = "ImagePlane";
|
||||
bool _moving = false;
|
||||
bool _hasImage = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -1072,7 +1072,8 @@ scripting::LuaLibrary RenderEngine::luaLibrary() {
|
||||
{
|
||||
codegen::lua::AddScreenSpaceRenderable,
|
||||
codegen::lua::RemoveScreenSpaceRenderable,
|
||||
codegen::lua::TakeScreenshot
|
||||
codegen::lua::TakeScreenshot,
|
||||
codegen::lua::DpiScaling
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user