diff --git a/apps/OpenSpace/ext/launcher/CMakeLists.txt b/apps/OpenSpace/ext/launcher/CMakeLists.txt index 39035ff4b8..305cdffd05 100644 --- a/apps/OpenSpace/ext/launcher/CMakeLists.txt +++ b/apps/OpenSpace/ext/launcher/CMakeLists.txt @@ -42,6 +42,13 @@ set(HEADER_FILES include/profile/timedialog.h include/profile/profileedit.h include/profile/propertiesdialog.h + include/sgctedit/displaywindowunion.h + include/sgctedit/filesupport.h + include/sgctedit/monitorbox.h + include/sgctedit/orientation.h + include/sgctedit/orientationdialog.h + include/sgctedit/sgctedit.h + include/sgctedit/windowcontrol.h ) set(SOURCE_FILES @@ -62,6 +69,13 @@ set(SOURCE_FILES src/profile/timedialog.cpp src/profile/profileedit.cpp src/profile/propertiesdialog.cpp + src/sgctedit/sgctedit.cpp + src/sgctedit/displaywindowunion.cpp + src/sgctedit/filesupport.cpp + src/sgctedit/monitorbox.cpp + src/sgctedit/orientation.cpp + src/sgctedit/orientationdialog.cpp + src/sgctedit/windowcontrol.cpp ) set(HEADER_SOURCE @@ -79,6 +93,13 @@ set(HEADER_SOURCE include/profile/timedialog.h include/profile/profileedit.h include/profile/propertiesdialog.h + include/sgctedit/displaywindowunion.h + include/sgctedit/filesupport.h + include/sgctedit/monitorbox.h + include/sgctedit/orientation.h + include/sgctedit/orientationdialog.h + include/sgctedit/sgctedit.h + include/sgctedit/windowcontrol.h ) find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Widgets REQUIRED) @@ -110,7 +131,13 @@ endif() add_library(openspace-ui-launcher STATIC ${HEADER_FILES} ${SOURCE_FILES} ${MOC_FILES} ${RESOURCE_FILES}) set_openspace_compile_settings(openspace-ui-launcher) -target_include_directories(openspace-ui-launcher PUBLIC include) +target_include_directories( + openspace-ui-launcher + PUBLIC + include + ${OPENSPACE_APPS_DIR}/OpenSpace/ext/sgct/include + ${OPENSPACE_APPS_DIR}/OpenSpace/ext/sgct/sgct/ext/glm +) target_link_libraries( openspace-ui-launcher PUBLIC diff --git a/apps/OpenSpace/ext/launcher/include/launcherwindow.h b/apps/OpenSpace/ext/launcher/include/launcherwindow.h index c75c474038..4cc74a6f3e 100644 --- a/apps/OpenSpace/ext/launcher/include/launcherwindow.h +++ b/apps/OpenSpace/ext/launcher/include/launcherwindow.h @@ -27,7 +27,9 @@ #include +#include "sgctedit/sgctedit.h" #include +#include #include namespace openspace::configuration { struct Configuration; } @@ -82,6 +84,7 @@ private: void setBackgroundImage(const std::string& syncPath); void openProfileEditor(const std::string& profile, bool isUserProfile); + void openWindowEditor(); void populateProfilesList(std::string preset); void populateWindowConfigsList(std::string preset); diff --git a/apps/OpenSpace/ext/launcher/include/sgctedit/displaywindowunion.h b/apps/OpenSpace/ext/launcher/include/sgctedit/displaywindowunion.h new file mode 100644 index 0000000000..c2aff2c374 --- /dev/null +++ b/apps/OpenSpace/ext/launcher/include/sgctedit/displaywindowunion.h @@ -0,0 +1,101 @@ +/***************************************************************************************** + * * + * 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___DISPLAYWINDOWUNION___H__ +#define __OPENSPACE_UI_LAUNCHER___DISPLAYWINDOWUNION___H__ + +#include + +#include "windowcontrol.h" +#include "monitorbox.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class DisplayWindowUnion : public QWidget { +Q_OBJECT +public: + /** + * Constructor for DisplayWindowUnion class, which manages the overall control layout + * including monitorBox, multiple WindowControl columns, and additional controls + * + * \param monitorRenderBox pointer to the MonitorBox object + * \param monitorSizeList A vector containing QRect objects containing pixel dims + * of each monitor + * \param nMaxWindows The maximum number of windows allowed (depends on the number + * of monitors in the system) + * \param winColors An array of QColor objects for window colors. The indexing of + * this array matches the window indexing used elsewhere in the + * class. This allows for a unique color for each window. + */ + DisplayWindowUnion(std::shared_ptr monitorRenderBox, + std::vector& monitorSizeList, unsigned int nMaxWindows, + const std::array& winColors); + /** + * Returns a vector of pointers to all WindowControl objects for all windows + * + * \return vector of pointers of WindowControl objects + */ + std::vector> windowControls() const; + /** + * Returns the current number of windows + * + * \return the currently-selected number of windows in unsigned int + */ + unsigned int nWindows() const; + +private slots: + void addWindow(); + void removeWindow(); + +private: + void initializeWindowControl(); + void initializeLayout(); + void showWindows(); + std::shared_ptr _monBox; + std::vector& _monitorResolutions; + unsigned int _nWindowsAllocated = 0; + unsigned int _nWindowsDisplayed = 0; + unsigned int _nMaxWindows = 3; + const std::array& _winColors; + std::vector> _windowControl; + QPushButton* _addWindowButton = nullptr; + QPushButton* _removeWindowButton = nullptr; + unsigned int _monitorIdx = 0; + std::vector _winCtrlLayouts; + std::vector _layoutWindowWrappers; + std::vector _frameBorderLines; + QFrame* _borderFrame = nullptr; +}; + +#endif // __OPENSPACE_UI_LAUNCHER___DISPLAYWINDOWUNION___H__ diff --git a/apps/OpenSpace/ext/launcher/include/sgctedit/filesupport.h b/apps/OpenSpace/ext/launcher/include/sgctedit/filesupport.h new file mode 100644 index 0000000000..1913ff1846 --- /dev/null +++ b/apps/OpenSpace/ext/launcher/include/sgctedit/filesupport.h @@ -0,0 +1,119 @@ +/***************************************************************************************** + * * + * 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___FILESUPPORT___H__ +#define __OPENSPACE_UI_LAUNCHER___FILESUPPORT___H__ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using ProjectionOptions = std::variant< + sgct::config::NoProjection, + sgct::config::CylindricalProjection, + sgct::config::EquirectangularProjection, + sgct::config::FisheyeProjection, + sgct::config::PlanarProjection, + sgct::config::ProjectionPlane, + sgct::config::SphericalMirrorProjection, + sgct::config::SpoutOutputProjection, + sgct::config::SpoutFlatProjection +>; + +struct SgctConfigElements { + std::vector& windowList; + sgct::config::Cluster& cluster; +}; + +struct UserConfigurationElements { + std::vector& monitorList; + std::shared_ptr display; + Orientation* orientation; + const std::string configSavePath; +}; + +class FileSupport : public QWidget { +Q_OBJECT +public: + /** + * Constructor for FileSupport class, which saves the window configuration settings + * into the SGCT json structure according to the sgct code + * + * \param parentLayout Qt vertical (QVBoxLayout) layout where controls are added + * \param cfgElements struct of elements needed to read user settings from GUI + * \param sgctElements struct of the window and cluster objects needed for saving + * \param finishedCallback function to be called when user has selected to either + * save changes to file, apply and run without saving, or cancel + */ + FileSupport(QVBoxLayout* parentLayout, UserConfigurationElements& cfgElements, + SgctConfigElements& sgctElements, std::function finishedCallback); + std::string saveFilename(); + +private slots: + void cancel(); + void save(); + void apply(); + +private: + bool isWindowFullscreen(unsigned int monitorIdx, sgct::ivec2 wDims); + std::optional findGuiWindow(); + void saveConfigToSgctFormat(); + void saveCluster(); + void saveWindows(); + void saveUser(); + ProjectionOptions saveProjectionInformation( + std::shared_ptr winControl); + ProjectionOptions saveProjectionSpout(std::shared_ptr winControl); + ProjectionOptions saveProjectionNoSpout(std::shared_ptr winControl); + sgct::config::Viewport generateViewport(); + sgct::config::Window saveWindowsDimensions(std::shared_ptr wCtrl); + void saveWindowsWebGui(unsigned int wIdx, sgct::config::Window& win); + + QHBoxLayout* _layoutButtonBox = nullptr; + QPushButton* _saveButton = nullptr; + QPushButton* _cancelButton = nullptr; + QPushButton* _applyButton = nullptr; + std::shared_ptr _displayWidget; + Orientation* _orientationWidget; + std::vector& _monitors; + sgct::config::Cluster& _cluster; + std::vector& _windowList; + std::function _finishedCallback; + const std::string _userConfigPath; + std::string _saveTarget; +}; + +#endif // __OPENSPACE_UI_LAUNCHER___FILESUPPORT___H__ diff --git a/apps/OpenSpace/ext/launcher/include/sgctedit/monitorbox.h b/apps/OpenSpace/ext/launcher/include/sgctedit/monitorbox.h new file mode 100644 index 0000000000..9d5eebf8e0 --- /dev/null +++ b/apps/OpenSpace/ext/launcher/include/sgctedit/monitorbox.h @@ -0,0 +1,126 @@ +/***************************************************************************************** + * * + * 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___MONITORBOX___H__ +#define __OPENSPACE_UI_LAUNCHER___MONITORBOX___H__ + +#include + +#include "windowcontrol.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class MonitorBox : public QWidget { +Q_OBJECT +public: + /** + * Constructor for MonitorBox class, which displays the system's monitor(s), + * their relative position and size, and window(s) that they contain + * + * \param widgetDims The size of the display widget in pixels, stored in QRect + * \param monitorResolution A vector containing the monitor's maximum display + * size in pixels in a QRect object + * \param nWindows The current number of windows that has been selected by the user + * \param winColors An array of QColor objects for window colors. The indexing of + * this array matches the window indexing used elsewhere in the + * class. This allows for a unique color for each window. + */ + MonitorBox(QRect widgetDims, std::vector monitorResolution, + unsigned int nWindows, const std::array& winColors); + /** + * Maps window resolution into the scaled resolution of the display widget + * + * \param mIdx The zero-based monitor index (primary monitor is 0) + * \param wIdx The zero-based window index + * \param winDimensions Dimensions (pixels) of window to be mapped in QRect + */ + void mapWindowResolutionToWidgetCoordinates(unsigned int mIdx, unsigned int wIdx, + const QRectF& winDimensions); + /** + * Sets the number of windows to be displayed + * + * \param nWindows Number of windows to be displayed + */ + void setNumWindowsDisplayed(unsigned int nWindows); + /** + * Called when window dimensions or monitor location have changed, requiring redraw + * + * \param mIdx The zero-based monitor index (primary monitor is 0) + * \param wIdx The zero-based window index + * \param newDimensions Dimensions (pixels) of window to be mapped in QRect + */ + void windowDimensionsChanged(unsigned int mIdx, unsigned int wIdx, + const QRectF& newDimensions); + +protected: + void paintEvent(QPaintEvent* event) override; + +private: + void determineMonitorArrangement(); + void mapMonitorResolutionToWidgetCoordinates(); + void paintWidgetBorder(QPainter& painter, int width, int height); + void paintMonitorBackgrounds(QPainter& painter); + void paintWindow(QPainter& painter, size_t winIdx); + void paintWindowBeyondBounds(QPainter& painter, unsigned int winIdx); + void paintWindowNumber(QPainter& painter, unsigned int winIdx); + void setPenSpecificToWindow(QPainter& painter, unsigned int windowIdx, + bool visibleBorder); + void computeScaledResolutionLandscape(float aspectRatio, float maxWidth); + void computeScaledResolutionPortrait(float aspectRatio, float maxHeight); + + unsigned int _maxNumMonitors = 2; + QRectF _monitorWidgetSize; + QRectF _monitorBoundaryRect; + unsigned int _nMonitors = 1; + float _monitorArrangementAspectRatio = 1.f; + QSizeF _monitorArrangementDimensions = { 0.0, 0.0 }; + std::vector _monitorResolution; + std::vector _monitorDimensionsScaled; + QRectF _negativeCorrectionOffsets = {0.f, 0.f, 0.f, 0.f}; + std::vector _windowResolutions; + std::vector _windowRendering = { + {0.f, 0.f, 0.f, 0.f}, + {0.f, 0.f, 0.f, 0.f}, + {0.f, 0.f, 0.f, 0.f}, + {0.f, 0.f, 0.f, 0.f} + }; + unsigned int _nWindows = 1; + const std::array _colorsForWindows; + int _alphaWindowOpacity = 170; + float _monitorScaleFactor = 1.0; + bool _showLabel = false; + float _marginWidget = 5.0; + std::vector _monitorOffsets; +}; + +#endif // __OPENSPACE_UI_LAUNCHER___MONITORBOX___H__ diff --git a/apps/OpenSpace/ext/launcher/include/sgctedit/orientation.h b/apps/OpenSpace/ext/launcher/include/sgctedit/orientation.h new file mode 100644 index 0000000000..79f9806cb4 --- /dev/null +++ b/apps/OpenSpace/ext/launcher/include/sgctedit/orientation.h @@ -0,0 +1,83 @@ +/***************************************************************************************** + * * + * 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___ORIENTATION___H__ +#define __OPENSPACE_UI_LAUNCHER___ORIENTATION___H__ + +#include + +#include +#include +#include +#include +#include +#include + +class Orientation : public QWidget { +Q_OBJECT +public: + /** + * Constructor for Orientation class, which manages the overall control layout + * including monitorBox, multiple WindowControl columns, and additional controls + * + * \param monitorRenderBox pointer to the MonitorBox object + * \param monitorSizeList A vector containing QRect objects containing pixel dims + * of each monitor + * \param nMaxWindows The maximum number of windows allowed (depends on the number + * of monitors in the system) + * \param winColors An array of QColor objects for window colors. The indexing of + * this array matches the window indexing used elsewhere in the + * class. This allows for a unique color for each window. + */ + Orientation(); + /** + * Add Orientation controls to the parent layout + * + * \param parentLayout the layout to which the Orientation's controls will be added + */ + void addControlsToParentLayout(QVBoxLayout* parentLayout); + /** + * Gets the user-provided x,y,z orientation values (degrees) + * + * \return the orientation angles provided in sgct::quat object + */ + sgct::quat orientationValue() const; + /** + * Gets the value for if VSync is enabled + * + * \return true if the VSync option is checked/enabled + */ + bool vsyncValue() const; + +private slots: + void orientationDialog(); + +private: + sgct::quat _orientationValue = {0.f, 0.f, 0.f, 0.f}; + OrientationDialog _orientationDialog; + QHBoxLayout* _layoutOrientationFull = nullptr; + QCheckBox* _checkBoxVsync = nullptr; +}; + +#endif // __OPENSPACE_UI_LAUNCHER___ORIENTATION___H__ diff --git a/apps/OpenSpace/ext/launcher/include/sgctedit/orientationdialog.h b/apps/OpenSpace/ext/launcher/include/sgctedit/orientationdialog.h new file mode 100644 index 0000000000..9d9615dcbf --- /dev/null +++ b/apps/OpenSpace/ext/launcher/include/sgctedit/orientationdialog.h @@ -0,0 +1,59 @@ +/***************************************************************************************** + * * + * 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___ORIENTATIONDIALOG___H__ +#define __OPENSPACE_UI_LAUNCHER___ORIENTATIONDIALOG___H__ + +#include + +#include +#include +#include + +class QWidget; + +class OrientationDialog : public QDialog { +Q_OBJECT +public: + /** + * Constructor for OrientationDialog object which contains the input text boxes for + * orientation x,y,z values + * + * \param orientation x,y,z angles in degrees contained in sgct::quat object + * \param parent pointer to Qt QWidget parent object + */ + OrientationDialog(sgct::quat& orientation, QWidget* parent); + +private slots: + void cancel(); + void ok(); + +private: + QLineEdit* _linePitch = nullptr; + QLineEdit* _lineRoll = nullptr; + QLineEdit* _lineYaw = nullptr; + sgct::quat& _orientationValue; +}; + +#endif // __OPENSPACE_UI_LAUNCHER___ORIENTATIONDIALOG___H__ diff --git a/apps/OpenSpace/ext/launcher/include/sgctedit/sgctedit.h b/apps/OpenSpace/ext/launcher/include/sgctedit/sgctedit.h new file mode 100644 index 0000000000..5ce30f6024 --- /dev/null +++ b/apps/OpenSpace/ext/launcher/include/sgctedit/sgctedit.h @@ -0,0 +1,105 @@ +/***************************************************************************************** + * * + * 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___SGCTEDIT___H__ +#define __OPENSPACE_UI_LAUNCHER___SGCTEDIT___H__ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class QWidget; + +class SgctEdit final : public QDialog { +Q_OBJECT +public: + /** + * Constructor for SgctEdit class, the underlying class for the full window + * configuration editor + * + * \param parent The Qt QWidget parent object + * \param windowList vector of sgct::config::Window objects which will be modified + * by the user settings, and then used for writing to file in + * the launcher code + * \param cluster reference to sgct::config::Cluster object that contains sgct + * objects that will be modified by the window configuration settings + * \param screenList A QList containing a QScreen object for each monitor in the + * system + * \param userConfigPath A string containing the file path of the user config + * directory where all window configs are stored + */ + SgctEdit(QWidget* parent, std::vector& windowList, + sgct::config::Cluster& cluster, const QList& screenList, + const std::string userConfigPath); + ~SgctEdit(); + /** + * Used to determine if the window configuration was saved to file, or canceled + * + * \return true if configuration was saved to file + */ + bool wasSaved() const; + /** + * Returns the saved filename + * + * \return saved filename in std::string + */ + std::string saveFilename(); + +private: + void addDisplayLayout(QHBoxLayout* layout); + void createWidgets(); + void systemMonitorConfiguration(const QList& screenList); + + std::shared_ptr _monBox = nullptr; + std::vector _monitorSizeList; + QVBoxLayout* _displayLayout = nullptr; + QFrame* _displayFrame = nullptr; + std::shared_ptr _displayWidget = nullptr; + QRect _monitorWidgetSize = {0, 0, 500, 500}; + FileSupport* _fileSupportWidget = nullptr; + Orientation* _orientationWidget = nullptr; + sgct::config::Cluster& _cluster; + std::vector& _windowList; + const std::string _userConfigPath; + bool _saveSelected = false; + unsigned int _nMaxWindows = 3; + const std::array _colorsForWindows = { + QColor(0x2B, 0x9E, 0xC3), + QColor(0xFC, 0xAB, 0x10), + QColor(0x44, 0xAF, 0x69), + QColor(0xF8, 0x33, 0x3C) + }; +}; + +#endif // __OPENSPACE_UI_LAUNCHER___SGCTEDIT___H__ diff --git a/apps/OpenSpace/ext/launcher/include/sgctedit/windowcontrol.h b/apps/OpenSpace/ext/launcher/include/sgctedit/windowcontrol.h new file mode 100644 index 0000000000..efd0a13a8c --- /dev/null +++ b/apps/OpenSpace/ext/launcher/include/sgctedit/windowcontrol.h @@ -0,0 +1,251 @@ +/***************************************************************************************** + * * + * 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___WINDOWCONTROL___H__ +#define __OPENSPACE_UI_LAUNCHER___WINDOWCONTROL___H__ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +class WindowControl : public QWidget { +Q_OBJECT +public: + /** + * Constructor for WindowControl class, which contains settings and configuration + * for individual windows + * + * \param monitorIndex The zero-based index for monitor number that this window + * resides in + * \param windowIndex The zero-based window index + * \param monitorDims Vector of monitor dimensions in QRect form + * \param winColor A QColor object for this window's unique color + */ + WindowControl(unsigned int monitorIndex, unsigned int windowIndex, + std::vector& monitorDims, const QColor& winColor, + QWidget *parent); + ~WindowControl(); + /** + * Sets callback function to be invoked when a window's setting changes + * + * \param cb Callback function that accepts the listed arg types, in order of + * monitorIndex, windowIndex, and windowDims (that were just changed) + */ + void setWindowChangeCallback(std::function cb); + /** + * Sets callback function to be invoked when a window gets its GUI checkbox selected + * + * \param cb Callback function that accepts the index of the window that has its + * WebGUI option selected + */ + void setWebGuiChangeCallback(std::function cb); + /** + * Makes the window label at top of a window control column visible + * + * \param bool Shows the window label if true + */ + void showWindowLabel(bool show); + /** + * Initializes the layout of a window controls column, returning the Qt layout object + * + * \return the QVBoxLayout object that contains the entire windows control column + */ + QVBoxLayout* initializeLayout(); + /** + * Returns the dimensions of the window + * + * \return the QRectF object that contains the windows dimensions + */ + QRectF& dimensions(); + /** + * Returns the title name of the window + * + * \return the std::string of the window name + */ + std::string windowName() const; + /** + * Returns the user-entered window size width, height from the text line objects + * + * \return the user-entered window size in sgct::ivec2 object + */ + sgct::ivec2 windowSize() const; + /** + * Returns the user-entered window position in x,y pixles from the text line objects + * + * \return the user-entered window position in sgct::ivec2 object + */ + sgct::ivec2 windowPos() const; + /** + * Returns bool for if the window control checkbox is set to be decorated + * + * \return bool for if window decoration is enabled + */ + bool isDecorated() const; + /** + * Returns bool for if the window control checkbox spout selection is enabled + * + * \return bool for if window has spout enabled + */ + bool isSpoutSelected() const; + /** + * Returns bool for if the window control checkbox for WebGUI is enabled + * + * \return bool for if window has WebGUI enabled + */ + bool isGuiWindow() const; + /** + * Function called in order to disable/uncheck the WebGUI checkbox option + */ + void uncheckWebGuiOption(); + /** + * Returns index number of the selected window quality value. This is an index into + * the QualityValues array + * + * \return index int into the QualityValues array + */ + int qualitySelectedValue() const; + /** + * Returns index number of the monitor that this window is assigned to + * + * \return int index of monitor + */ + unsigned int monitorNum() const; + /** + * Returns the user-entered horizontal field-of-view (planar projection only) + * + * \return float value of horizontal FOV + */ + float fovH() const; + /** + * Returns the user-entered vertical field-of-view (planar projection only) + * + * \return float value of vertical FOV + */ + float fovV() const; + /** + * Returns the user-entered height offset (cylindrical projection only) + * + * \return float value of height offset + */ + float heightOffset() const; + enum class ProjectionIndeces { + Planar = 0, + Fisheye, + SphericalMirror, + Cylindrical, + Equirectangular + }; + /** + * Returns the user-selected window projection type + * + * \return ProjectionIndeces enum of the projection type + */ + ProjectionIndeces projectionSelectedIndex() const; + /** + * Resets all controls for this window to default settings + */ + void resetToDefaults(); + +private slots: + void onSizeXChanged(const QString& newText); + void onSizeYChanged(const QString& newText); + void onOffsetXChanged(const QString& newText); + void onOffsetYChanged(const QString& newText); + void onMonitorChanged(int newSelection); + void onProjectionChanged(int newSelection); + void onFullscreenClicked(); + void onSpoutSelection(int selectionState); + void onWebGuiSelection(int selectionState); + void onAspectRatioLockClicked(); + void onFovLockClicked(); + +private: + void createWidgets(QWidget* parent); + void determineIdealWindowSize(); + QString resolutionLabelText(QRect resolution); + void updatePlanarLockedFov(); + void updateScaledWindowDimensions(); + std::function _windowChangeCallback; + std::function _windowGuiCheckCallback; + QRectF defaultWindowSizes[4] = { + {50.f, 50.f, 1280.f, 720.f}, + {150.f, 150.f, 1280.f, 720.f}, + {50.f, 50.f, 1280.f, 720.f}, + {150.f, 150.f, 1280.f, 720.f} + }; + QList _monitorNames = { "Primary", "Secondary" }; + int QualityValues[10] = { 256, 512, 1024, 1536, 2048, 4096, 8192, 16384, + 32768, 65536 }; + int _lineEditWidthFixedWinSize = 50; + int _lineEditWidthFixedFov = 80; + const float _idealAspectRatio = 16.f / 9.f; + float _aspectRatioSize = _idealAspectRatio; + float _marginFractionOfWidgetSize = 0.025f; + float _defaultFovH = 80.f; + float _defaultFovV = 50.534f; + float _defaultHeightOffset = 0.f; + unsigned int _nMonitors = 1; + unsigned int _monIndex = 0; + unsigned int _monIndexDefault = 0; + unsigned int _index = 0; + bool _aspectRatioLocked = false; + bool _FovLocked = true; + std::vector& _monitorResolutions; + int _maxWindowSizePixels = 10000; + const QColor& _colorForWindow; + QVBoxLayout* _layoutFullWindow = nullptr; + QLabel* _labelWinNum = nullptr; + QLineEdit* _sizeX = nullptr; + QLineEdit* _sizeY = nullptr; + QLineEdit* _offsetX = nullptr; + QLineEdit* _offsetY = nullptr; + QPushButton* _buttonLockAspectRatio = nullptr; + QPushButton* _buttonLockFov = nullptr; + QRectF _windowDims; + QPushButton* _fullscreenButton = nullptr; + QCheckBox* _checkBoxWindowDecor = nullptr; + QCheckBox* _checkBoxWebGui = nullptr; + QCheckBox* _checkBoxSpoutOutput = nullptr; + QComboBox* _comboMonitorSelect = nullptr; + QComboBox* _comboProjection = nullptr; + QComboBox* _comboQuality = nullptr; + QLabel* _labelQuality = nullptr; + QLabel* _labelFovH = nullptr; + QLineEdit* _lineFovH = nullptr; + QLabel* _labelFovV = nullptr; + QLineEdit* _lineFovV = nullptr; + QLabel* _labelHeightOffset = nullptr; + QLineEdit* _lineHeightOffset = nullptr; + QLineEdit* _windowName = nullptr; + QIcon _lockIcon; + QIcon _unlockIcon; +}; + +#endif // __OPENSPACE_UI_LAUNCHER___WINDOWCONTROL___H__ diff --git a/apps/OpenSpace/ext/launcher/resources/images/outline_locked.png b/apps/OpenSpace/ext/launcher/resources/images/outline_locked.png new file mode 100644 index 0000000000..a920237d1a Binary files /dev/null and b/apps/OpenSpace/ext/launcher/resources/images/outline_locked.png differ diff --git a/apps/OpenSpace/ext/launcher/resources/images/outline_unlocked.png b/apps/OpenSpace/ext/launcher/resources/images/outline_unlocked.png new file mode 100644 index 0000000000..9cda380c49 Binary files /dev/null and b/apps/OpenSpace/ext/launcher/resources/images/outline_unlocked.png differ diff --git a/apps/OpenSpace/ext/launcher/resources/resources.qrc b/apps/OpenSpace/ext/launcher/resources/resources.qrc index 5cc3931e9c..6942acaf81 100644 --- a/apps/OpenSpace/ext/launcher/resources/resources.qrc +++ b/apps/OpenSpace/ext/launcher/resources/resources.qrc @@ -3,5 +3,7 @@ qss/launcher.qss images/openspace-horiz-logo-small.png images/launcher-background.png + images/outline_locked.png + images/outline_unlocked.png diff --git a/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp b/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp index 8702e0e8da..758a08ce09 100644 --- a/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp +++ b/apps/OpenSpace/ext/launcher/src/launcherwindow.cpp @@ -40,6 +40,7 @@ #include #include #include +#include using namespace openspace; @@ -61,18 +62,24 @@ namespace { constexpr const QRect ProfileBox( LeftRuler, TopRuler + 110, ItemWidth, ItemHeight ); - constexpr const QRect OptionsLabel(LeftRuler, TopRuler + 180, 151, 24); + constexpr const QRect NewProfileButton( + LeftRuler + 140, TopRuler + 180, SmallItemWidth, SmallItemHeight + ); + constexpr const QRect EditProfileButton( + LeftRuler, TopRuler + 180, SmallItemWidth, SmallItemHeight + ); + constexpr const QRect OptionsLabel(LeftRuler, TopRuler + 230, 151, 24); constexpr const QRect WindowConfigBox( - LeftRuler, TopRuler + 210, ItemWidth, ItemHeight + LeftRuler, TopRuler + 260, ItemWidth, ItemHeight + ); + constexpr const QRect NewWindowButton( + LeftRuler + 140, TopRuler + 330, SmallItemWidth, SmallItemHeight + ); + constexpr const QRect EditWindowButton( + LeftRuler, TopRuler + 330, SmallItemWidth, SmallItemHeight ); constexpr const QRect StartButton( - LeftRuler, TopRuler + 290, ItemWidth, ItemHeight - ); - constexpr const QRect NewButton( - LeftRuler + 140, TopRuler + 380, SmallItemWidth, SmallItemHeight - ); - constexpr const QRect EditButton( - LeftRuler, TopRuler + 380, SmallItemWidth, SmallItemHeight + LeftRuler, TopRuler + 400, ItemWidth, ItemHeight ); } // geometry @@ -134,6 +141,25 @@ namespace { ); } } + + void saveWindowConfig(QWidget* parent, const std::string& path, + sgct::config::Cluster& cluster) + { + std::ofstream outFile; + try { + outFile.open(path, std::ofstream::out); + outFile << sgct::serializeConfig(cluster); + } + catch (const std::ofstream::failure& e) { + QMessageBox::critical( + parent, + "Exception", + QString::fromStdString(fmt::format( + "Error writing data to file: {} ({})", path, e.what() + )) + ); + } + } } // namespace using namespace openspace; @@ -248,20 +274,20 @@ QWidget* LauncherWindow::createCentralWidget() { startButton->setGeometry(geometry::StartButton); startButton->setCursor(Qt::PointingHandCursor); - QPushButton* newButton = new QPushButton("New", centralWidget); + QPushButton* newProfileButton = new QPushButton("New", centralWidget); connect( - newButton, &QPushButton::released, + newProfileButton, &QPushButton::released, [this]() { openProfileEditor("", true); } ); - newButton->setObjectName("small"); - newButton->setGeometry(geometry::NewButton); - newButton->setCursor(Qt::PointingHandCursor); + newProfileButton->setObjectName("small"); + newProfileButton->setGeometry(geometry::NewProfileButton); + newProfileButton->setCursor(Qt::PointingHandCursor); - QPushButton* editButton = new QPushButton("Edit", centralWidget); + QPushButton* editProfileButton = new QPushButton("Edit", centralWidget); connect( - editButton, &QPushButton::released, + editProfileButton, &QPushButton::released, [this]() { const std::string selection = _profileBox->currentText().toStdString(); int selectedIndex = _profileBox->currentIndex(); @@ -269,9 +295,20 @@ QWidget* LauncherWindow::createCentralWidget() { openProfileEditor(selection, isUserProfile); } ); - editButton->setObjectName("small"); - editButton->setGeometry(geometry::EditButton); - editButton->setCursor(Qt::PointingHandCursor); + editProfileButton->setObjectName("small"); + editProfileButton->setGeometry(geometry::EditProfileButton); + editProfileButton->setCursor(Qt::PointingHandCursor); + + QPushButton* newWindowButton = new QPushButton("New", centralWidget); + connect( + newWindowButton, &QPushButton::released, + [this]() { + openWindowEditor(); + } + ); + newWindowButton->setObjectName("small"); + newWindowButton->setGeometry(geometry::NewWindowButton); + newWindowButton->setCursor(Qt::PointingHandCursor); return centralWidget; } @@ -354,13 +391,18 @@ void LauncherWindow::populateProfilesList(std::string preset) { ++_userAssetCount; // Add all the files with the .profile extension to the dropdown + std::vector profiles; for (const fs::directory_entry& p : fs::directory_iterator(_userProfilePath)) { if (p.path().extension() != ".profile") { continue; } - _profileBox->addItem(QString::fromStdString(p.path().stem().string())); + profiles.push_back(p); ++_userAssetCount; } + std::sort(profiles.begin(), profiles.end()); + for (const fs::directory_entry& p : profiles) { + _profileBox->addItem(QString::fromStdString(p.path().stem().string())); + } _profileBox->addItem(QString::fromStdString("--- OpenSpace Profiles ---")); model = qobject_cast(_profileBox->model()); @@ -368,11 +410,17 @@ void LauncherWindow::populateProfilesList(std::string preset) { ++_userAssetCount; // Add all the files with the .profile extension to the dropdown - for (const fs::directory_entry& p : fs::directory_iterator(_profilePath)) { - if (p.path().extension() != ".profile") { + profiles.clear(); + for (const fs::directory_entry& path : fs::directory_iterator(_profilePath)) { + if (path.path().extension() != ".profile") { continue; } - _profileBox->addItem(QString::fromStdString(p.path().stem().string())); + profiles.push_back(path); + } + std::sort(profiles.begin(), profiles.end()); + //add sorted items to list + for (const fs::directory_entry& profile : profiles) { + _profileBox->addItem(QString::fromStdString(profile.path().stem().string())); } // Try to find the requested profile and set it as the current one @@ -414,8 +462,15 @@ void LauncherWindow::populateWindowConfigsList(std::string preset) { bool hasXmlConfig = false; - // Add all the files with the .xml or .json extension to the dropdown + //sort files + std::vector files; for (const fs::directory_entry& p : fs::directory_iterator(_userConfigPath)) { + files.push_back(p); + } + std::sort(files.begin(), files.end()); + + // Add all the files with the .xml or .json extension to the dropdown + for (const fs::directory_entry& p : files) { bool isConfigFile = handleConfigurationFile(*_windowConfigBox, p); if (isConfigFile) { ++_userConfigCount; @@ -428,8 +483,14 @@ void LauncherWindow::populateWindowConfigsList(std::string preset) { model->item(_userConfigCount)->setEnabled(false); if (std::filesystem::exists(_configPath)) { - // Add all the files with the .xml or .json extension to the dropdown + //sort files + files.clear(); for (const fs::directory_entry& p : fs::directory_iterator(_configPath)) { + files.push_back(p); + } + std::sort(files.begin(), files.end()); + // Add all the files with the .xml or .json extension to the dropdown + for (const fs::directory_entry& p : files) { handleConfigurationFile(*_windowConfigBox, p); hasXmlConfig |= p.path().extension() == ".xml"; } @@ -452,6 +513,8 @@ void LauncherWindow::populateWindowConfigsList(std::string preset) { ); } + //Always add the .cfg sgct default as first item + _windowConfigBox->insertItem(0, QString::fromStdString(_sgctConfigName)); // Try to find the requested configuration file and set it as the current one. As we // have support for function-generated configuration files that will not be in the // list we need to add a preset that doesn't exist a file for @@ -461,8 +524,11 @@ void LauncherWindow::populateWindowConfigsList(std::string preset) { } else { // Add the requested preset at the top - _windowConfigBox->insertItem(0, QString::fromStdString(preset)); - _windowConfigBox->setCurrentIndex(0); + _windowConfigBox->insertItem(1, QString::fromStdString(preset)); + //Increment the user config count because there is an additional option added + //before the user config options + _userConfigCount++; + _windowConfigBox->setCurrentIndex(1); } } @@ -500,6 +566,44 @@ void LauncherWindow::openProfileEditor(const std::string& profile, bool isUserPr } } +void LauncherWindow::openWindowEditor() { + QList screenList = qApp->screens(); + if (screenList.length() == 0) { + LERRORC( + "LauncherWindow", + "Error: Qt reports no screens/monitors available" + ); + return; + } + sgct::config::Cluster cluster; + std::vector windowList; + SgctEdit editor(this, windowList, cluster, screenList, _userConfigPath); + editor.exec(); + if (editor.wasSaved()) { + std::string ext = ".json"; + std::string savePath = editor.saveFilename(); + if (savePath.size() >= ext.size() + && !(savePath.substr(savePath.size() - ext.size()).compare(ext) == 0)) + { + savePath += ext; + } + if (cluster.nodes.size() == 0) { + cluster.nodes.push_back(sgct::config::Node()); + } + for (auto w : windowList) { + cluster.nodes[0].windows.push_back(w); + } + saveWindowConfig(this, savePath, cluster); + //Truncate path to convert this back to path relative to _userConfigPath + savePath = savePath.substr(_userConfigPath.size()); + populateWindowConfigsList(savePath); + } + else { + const std::string current = _windowConfigBox->currentText().toStdString(); + populateWindowConfigsList(current); + } +} + bool LauncherWindow::wasLaunchSelected() const { return _shouldLaunch; } diff --git a/apps/OpenSpace/ext/launcher/src/sgctedit/displaywindowunion.cpp b/apps/OpenSpace/ext/launcher/src/sgctedit/displaywindowunion.cpp new file mode 100644 index 0000000000..5fba039b22 --- /dev/null +++ b/apps/OpenSpace/ext/launcher/src/sgctedit/displaywindowunion.cpp @@ -0,0 +1,179 @@ +/***************************************************************************************** + * * + * 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 "sgctedit/displaywindowunion.h" + +#include +#include "sgctedit/monitorbox.h" +#include "sgctedit/windowcontrol.h" +#include +#include +#include +#include + +DisplayWindowUnion::DisplayWindowUnion(std::shared_ptr monitorRenderBox, + std::vector& monitorSizeList, unsigned int nMaxWindows, + const std::array& winColors) + : _monBox(monitorRenderBox) + , _monitorResolutions(monitorSizeList) + , _nMaxWindows(nMaxWindows) + , _winColors(winColors) +{ + _addWindowButton = new QPushButton("Add Window"); + _removeWindowButton = new QPushButton("Remove Window"); + //Add all window controls (some will be hidden from GUI initially) + for (unsigned int i = 0; i < _nMaxWindows; ++i) { + initializeWindowControl(); + } + connect( + _addWindowButton, + &QPushButton::clicked, + this, + &DisplayWindowUnion::addWindow + ); + connect( + _removeWindowButton, + &QPushButton::clicked, + this, + &DisplayWindowUnion::removeWindow + ); + initializeLayout(); +} + +void DisplayWindowUnion::initializeLayout() { + QVBoxLayout* layout = new QVBoxLayout(this); + { + QHBoxLayout* layoutMonButton = new QHBoxLayout; + _removeWindowButton->setToolTip( + "Remove window from the configuration (at least one window is required)" + ); + std::string addTip = fmt::format( + "Add a window to the configuration (up to {} windows allowed)", _nMaxWindows + ); + _addWindowButton->setToolTip(QString::fromStdString(addTip)); + _addWindowButton->setFocusPolicy(Qt::NoFocus); + _removeWindowButton->setFocusPolicy(Qt::NoFocus); + layoutMonButton->addWidget(_removeWindowButton); + layoutMonButton->addStretch(1); + layoutMonButton->addWidget(_addWindowButton); + layout->addLayout(layoutMonButton); + } + QHBoxLayout* layoutWindows = new QHBoxLayout; + layout->addStretch(); + + for (unsigned int i = 0; i < _nMaxWindows; ++i) { + QVBoxLayout* layoutForNextWindow = _windowControl[i]->initializeLayout(); + _winCtrlLayouts.push_back(layoutForNextWindow); + QWidget* layoutWrapper = new QWidget(); + layoutWrapper->setLayout(layoutForNextWindow); + _layoutWindowWrappers.push_back(layoutWrapper); + layoutWindows->addWidget(layoutWrapper); + if (i < (_nMaxWindows - 1)) { + QFrame* frameForNextWindow = new QFrame(); + frameForNextWindow->setFrameShape(QFrame::VLine); + _frameBorderLines.push_back(frameForNextWindow); + layoutWindows->addWidget(frameForNextWindow); + } + } + _nWindowsDisplayed = 1; + showWindows(); + layout->addLayout(layoutWindows); +} + +std::vector> DisplayWindowUnion::windowControls() const { + return _windowControl; +} + +unsigned int DisplayWindowUnion::nWindows() const { + return _nWindowsDisplayed; +} + +void DisplayWindowUnion::addWindow() { + if (_nWindowsDisplayed < _nMaxWindows) { + _windowControl[_nWindowsDisplayed]->resetToDefaults(); + _nWindowsDisplayed++; + showWindows(); + } +} + +void DisplayWindowUnion::removeWindow() { + if (_nWindowsDisplayed > 1) { + _nWindowsDisplayed--; + showWindows(); + } +} + +void DisplayWindowUnion::showWindows() { + for (size_t i = 0; i < _layoutWindowWrappers.size(); ++i) { + _layoutWindowWrappers[i]->setVisible(i < _nWindowsDisplayed); + } + for (size_t i = 0; i < _frameBorderLines.size(); ++i) { + _frameBorderLines[i]->setVisible(i < (_nWindowsDisplayed - 1)); + } + _removeWindowButton->setEnabled(_nWindowsDisplayed > 1); + _addWindowButton->setEnabled(_nWindowsDisplayed != _nMaxWindows); + for (std::shared_ptr w : _windowControl) { + w->showWindowLabel(_nWindowsDisplayed > 1); + } + _monBox->setNumWindowsDisplayed(_nWindowsDisplayed); +} + +void DisplayWindowUnion::initializeWindowControl() { + if (_nWindowsAllocated < _nMaxWindows) { + unsigned int monitorNumForThisWindow = 0; + if (_nMaxWindows > 3 && _nWindowsAllocated >= 2) { + monitorNumForThisWindow = 1; + } + _windowControl.push_back( + std::make_shared( + monitorNumForThisWindow, + _nWindowsAllocated, + _monitorResolutions, + _winColors[_nWindowsAllocated], + this + ) + ); + _windowControl.back()->setWindowChangeCallback( + [this](int monIndex, int winIndex, const QRectF& newDims) { + _monBox->windowDimensionsChanged(monIndex, winIndex, newDims); + } + ); + _windowControl.back()->setWebGuiChangeCallback( + [this](unsigned int winIndex) { + for (unsigned int w = 0; w < _nMaxWindows; ++w) { + if (w != winIndex) { + _windowControl[w]->uncheckWebGuiOption(); + } + } + } + ); + _monBox->mapWindowResolutionToWidgetCoordinates( + monitorNumForThisWindow, + _nWindowsAllocated, + _windowControl.back()->dimensions() + ); + _nWindowsAllocated++; + } +} + diff --git a/apps/OpenSpace/ext/launcher/src/sgctedit/filesupport.cpp b/apps/OpenSpace/ext/launcher/src/sgctedit/filesupport.cpp new file mode 100644 index 0000000000..9746e16745 --- /dev/null +++ b/apps/OpenSpace/ext/launcher/src/sgctedit/filesupport.cpp @@ -0,0 +1,291 @@ +/***************************************************************************************** + * * + * 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 "sgctedit/filesupport.h" + +FileSupport::FileSupport(QVBoxLayout* parentLayout, + UserConfigurationElements& cfgElements, + SgctConfigElements& sgctElements, + std::function finishedCallback) + : _displayWidget(cfgElements.display) + , _orientationWidget(cfgElements.orientation) + , _monitors(cfgElements.monitorList) + , _cluster(sgctElements.cluster) + , _windowList(sgctElements.windowList) + , _finishedCallback(finishedCallback) + , _userConfigPath(cfgElements.configSavePath) +{ + QVBoxLayout* layoutFullVertical = new QVBoxLayout; + _saveButton = new QPushButton("Save As"); + _saveButton->setToolTip("Save configuration changes (opens file chooser dialog)"); + _saveButton->setFocusPolicy(Qt::NoFocus); + connect(_saveButton, &QPushButton::released, this, &FileSupport::save); + _cancelButton = new QPushButton("Cancel"); + _cancelButton->setToolTip("Cancel changes"); + _cancelButton->setFocusPolicy(Qt::NoFocus); + connect(_cancelButton, &QPushButton::released, this, &FileSupport::cancel); + _applyButton = new QPushButton("Apply Without Saving"); + _applyButton->setToolTip("Apply configuration changes without saving to file"); + _applyButton->setFocusPolicy(Qt::NoFocus); + connect(_applyButton, &QPushButton::released, this, &FileSupport::apply); + { + QHBoxLayout* layoutButtonBox = new QHBoxLayout; + layoutButtonBox->addStretch(1); + layoutButtonBox->addWidget(_cancelButton); + layoutButtonBox->addWidget(_saveButton); + layoutButtonBox->addWidget(_applyButton); + layoutFullVertical->addLayout(layoutButtonBox); + } + parentLayout->addLayout(layoutFullVertical); +} + +void FileSupport::saveCluster() { + if (_orientationWidget) { + sgct::config::Scene initScene; + initScene.orientation = _orientationWidget->orientationValue(); + _cluster.nodes.clear(); + sgct::config::Node tmpNode; + tmpNode.address = "localhost"; + tmpNode.port = 20401; + _cluster.nodes.push_back(tmpNode); + _cluster.masterAddress = "localhost"; + _cluster.scene = std::move(initScene); + _cluster.firmSync = _orientationWidget->vsyncValue(); + } +} + +void FileSupport::saveUser() { + if (_orientationWidget) { + sgct::config::User user; + user.eyeSeparation = 0.065f; + user.position = {0.0f, 0.0f, 4.0f}; + _cluster.users.push_back(user); + } +} + +bool FileSupport::isWindowFullscreen(unsigned int monitorIdx, sgct::ivec2 wDims) { + return (_monitors[monitorIdx].width() == wDims.x && + _monitors[monitorIdx].height() == wDims.y); +} + +std::optional FileSupport::findGuiWindow() { + unsigned int windowIndex = 0; + for (unsigned int w = 0; w < _displayWidget->nWindows(); ++w) { + if (_displayWidget->windowControls()[w]->isGuiWindow()) { + return std::optional(windowIndex); + } + windowIndex++; + } + return std::nullopt; +} + +void FileSupport::saveWindows() { + unsigned int windowIndex = 0; + for (unsigned int w = 0; w < _displayWidget->nWindows(); ++w) { + std::shared_ptr wCtrl = _displayWidget->windowControls()[w]; + sgct::config::Window tmpWindow = saveWindowsDimensions(wCtrl); + tmpWindow.viewports.push_back(generateViewport()); + tmpWindow.viewports.back().projection = saveProjectionInformation(wCtrl); + tmpWindow.isDecorated = wCtrl->isDecorated(); + tmpWindow.isFullScreen = isWindowFullscreen( + wCtrl->monitorNum(), + wCtrl->windowSize() + ); + if (tmpWindow.isFullScreen) { + tmpWindow.monitor = wCtrl->monitorNum(); + } + saveWindowsWebGui(windowIndex, tmpWindow); + if (!wCtrl->windowName().empty()) { + tmpWindow.name = wCtrl->windowName(); + } + tmpWindow.id = windowIndex++; + _windowList.push_back(tmpWindow); + } +} + +sgct::config::Viewport FileSupport::generateViewport() { + sgct::config::Viewport vp; + vp.isTracked = true; + vp.position = {0.f, 0.f}; + vp.size = {1.f, 1.f}; + return vp; +} + +sgct::config::Window FileSupport::saveWindowsDimensions( + std::shared_ptr wCtrl) +{ + sgct::config::Window tmpWindow; + tmpWindow.size = wCtrl->windowSize(); + tmpWindow.pos = { + _monitors[wCtrl->monitorNum()].x() + wCtrl->windowPos().x, + _monitors[wCtrl->monitorNum()].y() + wCtrl->windowPos().y, + }; + return tmpWindow; +} + +void FileSupport::saveWindowsWebGui(unsigned int wIdx, sgct::config::Window& win) { + win.viewports.back().isTracked = true; + std::optional webGuiWindowIndex = findGuiWindow(); + bool isOneOfWindowsSetAsWebGui = webGuiWindowIndex.has_value(); + if (isOneOfWindowsSetAsWebGui) { + if (wIdx == webGuiWindowIndex.value()) { + win.viewports.back().isTracked = false; + win.tags.push_back("GUI"); + } + win.draw2D = (wIdx == webGuiWindowIndex.value()); + win.draw3D = !(win.draw2D.value()); + } +} + +ProjectionOptions FileSupport::saveProjectionInformation( + std::shared_ptr winControl) +{ + if (winControl->isSpoutSelected()) { + return saveProjectionSpout(winControl); + } + else { + return saveProjectionNoSpout(winControl); + } +} + +ProjectionOptions FileSupport::saveProjectionSpout( + std::shared_ptr winControl) +{ + sgct::config::SpoutOutputProjection projection; + switch(winControl->projectionSelectedIndex()) { + case WindowControl::ProjectionIndeces::Fisheye: + projection.mapping + = sgct::config::SpoutOutputProjection::Mapping::Fisheye; + break; + + case WindowControl::ProjectionIndeces::Equirectangular: + default: + projection.mapping + = sgct::config::SpoutOutputProjection::Mapping::Equirectangular; + break; + } + projection.quality = winControl->qualitySelectedValue(); + projection.mappingSpoutName = "OpenSpace"; + return projection; +} + +ProjectionOptions FileSupport::saveProjectionNoSpout( + std::shared_ptr winControl) +{ + switch(winControl->projectionSelectedIndex()) { + case WindowControl::ProjectionIndeces::Fisheye: + { + sgct::config::FisheyeProjection projection; + projection.quality = winControl->qualitySelectedValue(); + projection.fov = 180.f; + projection.tilt = 0.f; + return projection; + } + break; + + case WindowControl::ProjectionIndeces::SphericalMirror: + { + sgct::config::SphericalMirrorProjection projection; + projection.quality = winControl->qualitySelectedValue(); + return projection; + } + break; + + case WindowControl::ProjectionIndeces::Cylindrical: + { + sgct::config::CylindricalProjection projection; + projection.quality = winControl->qualitySelectedValue(); + projection.heightOffset = winControl->heightOffset(); + return projection; + } + break; + + case WindowControl::ProjectionIndeces::Equirectangular: + { + sgct::config::EquirectangularProjection projection; + projection.quality = winControl->qualitySelectedValue(); + return projection; + } + break; + + case WindowControl::ProjectionIndeces::Planar: + default: + { + // The negative values for left & down are according to sgct's convention + sgct::config::PlanarProjection projection; + projection.fov.right = winControl->fovH() / 2.0; + projection.fov.left = -projection.fov.right; + projection.fov.up = winControl->fovV() / 2.0; + projection.fov.down = -projection.fov.up; + return projection; + } + break; + } +} + +std::string FileSupport::saveFilename() { + return _saveTarget; +} + +void FileSupport::save() { + QString fileName = QFileDialog::getSaveFileName( + this, + "Save Window Configuration File", + QString::fromStdString(_userConfigPath), + "Window Configuration (*.json);;(*.json)", + nullptr +#ifdef __linux__ + , QFileDialog::DontUseNativeDialog +#endif + ); + if (fileName.length() != 0) { + _saveTarget = fileName.toStdString(); + saveConfigToSgctFormat(); + _finishedCallback(true); + } +} + +void FileSupport::cancel() { + _finishedCallback(false); +} + +void FileSupport::apply() { + std::string userCfgTempDir = _userConfigPath; + if (userCfgTempDir.back() != '/') { + userCfgTempDir += "/"; + } + userCfgTempDir += "temp"; + if (!std::filesystem::is_directory(userCfgTempDir)) { + std::filesystem::create_directories(absPath(userCfgTempDir)); + } + _saveTarget = userCfgTempDir + "/" + "apply-without-saving.json"; + saveConfigToSgctFormat(); + _finishedCallback(true); +} + +void FileSupport::saveConfigToSgctFormat() { + saveCluster(); + saveWindows(); + saveUser(); +} diff --git a/apps/OpenSpace/ext/launcher/src/sgctedit/monitorbox.cpp b/apps/OpenSpace/ext/launcher/src/sgctedit/monitorbox.cpp new file mode 100644 index 0000000000..23214a79be --- /dev/null +++ b/apps/OpenSpace/ext/launcher/src/sgctedit/monitorbox.cpp @@ -0,0 +1,255 @@ +/***************************************************************************************** + * * + * 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 "sgctedit/monitorbox.h" + +constexpr float MarginFractionOfWidgetSize = 0.05f; + +MonitorBox::MonitorBox(QRect widgetDims, std::vector monitorResolution, + unsigned int nWindows, const std::array& winColors) + : _monitorWidgetSize(widgetDims) + , _monitorResolution(monitorResolution) + , _nWindows(nWindows) + , _colorsForWindows(winColors) +{ + _nMonitors = static_cast(monitorResolution.size()); + _showLabel = (_nMonitors > 1); + determineMonitorArrangement(); + this->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + float borderMargin = MarginFractionOfWidgetSize * 2.f; + if (_monitorArrangementAspectRatio > 1.0) { + borderMargin *= _monitorWidgetSize.width(); + _monitorWidgetSize.setHeight(_monitorWidgetSize.width() + / _monitorArrangementAspectRatio + borderMargin); + } + else { + borderMargin *= _monitorWidgetSize.height(); + _monitorWidgetSize.setWidth(_monitorWidgetSize.height() + * _monitorArrangementAspectRatio + borderMargin); + } + this->setFixedSize(_monitorWidgetSize.width(), _monitorWidgetSize.height()); + mapMonitorResolutionToWidgetCoordinates(); +} + +void MonitorBox::paintEvent(QPaintEvent* event) { + Q_UNUSED(event) + QPainter painter(this); + QPen pen = painter.pen(); + painter.setPen(pen); + paintWidgetBorder(painter, width(), height()); + //Draw window out-of-bounds region(s) first + for (unsigned int i = 0; i < _nWindows; ++i) { + paintWindowBeyondBounds(painter, i); + } + //Draw & fill monitors over the out-of-bounds regions + paintMonitorBackgrounds(painter); + //Draw window number(s) first for darker contrast, then window(s) over both + //out-of-bounds and monitors + for (unsigned int i = 0; i < _nWindows; ++i) { + paintWindowNumber(painter, i); + } + for (unsigned int i = 0; i < _nWindows; ++i) { + paintWindow(painter, i); + } +} + +void MonitorBox::paintWidgetBorder(QPainter& painter, int width, int height) { + constexpr int Radius = 10; + painter.setPen(QPen(Qt::gray, 4)); + painter.drawRoundedRect(0, 0, width - 1, height - 1, Radius, Radius); +} + +void MonitorBox::paintMonitorBackgrounds(QPainter& painter) { + painter.setPen(QPen(Qt::black, 2)); + QFont f("Arial"); + f.setPixelSize(24); + painter.setFont(f); + for (unsigned int i = 0; i < _nMonitors; ++i) { + if (i <= _monitorDimensionsScaled.size()) { + painter.drawRect(_monitorDimensionsScaled[i]); + QColor fillColor("#DDDDDD"); + QBrush brush(fillColor); + brush.setStyle(Qt::SolidPattern); + painter.fillRect(_monitorDimensionsScaled[i], brush); + if (_showLabel) { + QPointF textPos = QPointF( + _monitorDimensionsScaled[i].left() + 4, + _monitorDimensionsScaled[i].top() + 24 + ); + if (i == 0) { + painter.drawText(textPos, "Primary"); + } + } + } + } +} + +void MonitorBox::paintWindowBeyondBounds(QPainter& painter, unsigned int winIdx) { + painter.setBrush(Qt::BDiagPattern); + setPenSpecificToWindow(painter, winIdx, false); + if (winIdx <= _windowRendering.size()) { + painter.drawRect(_windowRendering[winIdx]); + } + setPenSpecificToWindow(painter, winIdx, true); + painter.setBrush(Qt::NoBrush); +} + +void MonitorBox::paintWindow(QPainter& painter, size_t winIdx) { + setPenSpecificToWindow(painter, static_cast(winIdx), true); + if (winIdx <= _windowRendering.size()) { + painter.drawRect(_windowRendering[winIdx]); + QColor fillColor = _colorsForWindows[winIdx]; + fillColor.setAlpha(_alphaWindowOpacity); + QBrush brush(fillColor); + brush.setStyle(Qt::SolidPattern); + painter.fillRect(_windowRendering[winIdx], brush); + } +} + +void MonitorBox::paintWindowNumber(QPainter& painter, unsigned int winIdx) { + QPointF textPos = QPointF(_windowRendering[winIdx].left() + 5, + _windowRendering[winIdx].bottom() - 5); + textPos.setX(std::clamp(textPos.x(), 0.0, _monitorWidgetSize.width() - 10)); + textPos.setY(std::clamp(textPos.y(), 20.0, _monitorWidgetSize.height())); + painter.drawText(textPos, QString::fromStdString(std::to_string(winIdx + 1))); +} + +void MonitorBox::setPenSpecificToWindow(QPainter& painter, unsigned int windowIdx, + bool visibleBorder) +{ + int penWidth = (visibleBorder) ? 1 : -1; + painter.setPen(QPen(_colorsForWindows[windowIdx], penWidth)); +} + +void MonitorBox::windowDimensionsChanged(unsigned int mIdx, unsigned int wIdx, + const QRectF& newDimensions) +{ + mapWindowResolutionToWidgetCoordinates(mIdx, wIdx, newDimensions); +} + +void MonitorBox::determineMonitorArrangement() { + for (const QRect& m : _monitorResolution) { + if (m.x() < _negativeCorrectionOffsets.x()) { + _negativeCorrectionOffsets.setX(m.x()); + } + if (m.y() < _negativeCorrectionOffsets.y()) { + _negativeCorrectionOffsets.setY(m.y()); + } + } + for (const QRect& m : _monitorResolution) { + if ((m.x() + m.width() - _negativeCorrectionOffsets.x()) + > _monitorArrangementDimensions.width()) + { + _monitorArrangementDimensions.setWidth( + m.x() + m.width() - _negativeCorrectionOffsets.x()); + } + if ((m.y() + m.height() - _negativeCorrectionOffsets.y()) + > _monitorArrangementDimensions.height()) + { + _monitorArrangementDimensions.setHeight( + m.y() + m.height() - _negativeCorrectionOffsets.y()); + } + } + _monitorArrangementAspectRatio = _monitorArrangementDimensions.width() + / _monitorArrangementDimensions.height(); +} + +void MonitorBox::mapMonitorResolutionToWidgetCoordinates() { + if (_monitorArrangementAspectRatio >= 1.0) { + computeScaledResolutionLandscape( + _monitorArrangementAspectRatio, + _monitorArrangementDimensions.width() + ); + } + else { + computeScaledResolutionPortrait( + _monitorArrangementAspectRatio, + _monitorArrangementDimensions.height() + ); + } + for (size_t m = 0; m < _monitorResolution.size(); ++m) { + _monitorDimensionsScaled.push_back({ + _monitorOffsets[m].width(), + _monitorOffsets[m].height(), + _monitorResolution[m].width() * _monitorScaleFactor, + _monitorResolution[m].height() * _monitorScaleFactor, + }); + } + update(); +} + +void MonitorBox::computeScaledResolutionLandscape(float aspectRatio, float maxWidth) { + _marginWidget = _monitorWidgetSize.width() * MarginFractionOfWidgetSize; + float virtualWidth = _monitorWidgetSize.width() + * (1.0 - MarginFractionOfWidgetSize * 2.0); + _monitorScaleFactor = virtualWidth / maxWidth; + float newHeight = virtualWidth / aspectRatio; + for (size_t m = 0; m < _monitorResolution.size(); ++m) { + _monitorOffsets.push_back({ + _marginWidget + (_monitorResolution[m].x() - _negativeCorrectionOffsets.x()) + * _monitorScaleFactor, + _marginWidget + (_monitorWidgetSize.height() - newHeight - _marginWidget) / 4.0 + + (_monitorResolution[m].y() - _negativeCorrectionOffsets.y()) + * _monitorScaleFactor + }); + } +} + +void MonitorBox::computeScaledResolutionPortrait(float aspectRatio, float maxHeight) { + _marginWidget = _monitorWidgetSize.height() * MarginFractionOfWidgetSize; + float virtualHeight = _monitorWidgetSize.height() + * (1.0 - MarginFractionOfWidgetSize * 2.0); + _monitorScaleFactor = virtualHeight / maxHeight; + float newWidth = virtualHeight * aspectRatio; + for (size_t m = 0; m < _monitorResolution.size(); ++m) { + _monitorOffsets.push_back({ + _marginWidget + (_monitorWidgetSize.width() - newWidth - _marginWidget) / 4.0 + + (_monitorResolution[m].x() - _negativeCorrectionOffsets.x()) + * _monitorScaleFactor, + _marginWidget + (_monitorResolution[m].y() - _negativeCorrectionOffsets.y()) + * _monitorScaleFactor + }); + } +} + +void MonitorBox::setNumWindowsDisplayed(unsigned int nWindows) { + if (_nWindows != nWindows) { + _nWindows = nWindows; + update(); + } +} + +void MonitorBox::mapWindowResolutionToWidgetCoordinates(unsigned int mIdx, + unsigned int wIdx, + const QRectF& winDimensions) +{ + QRectF wF = winDimensions; + _windowRendering[wIdx] = { + _monitorDimensionsScaled[mIdx].x() + wF.left() * _monitorScaleFactor, + _monitorDimensionsScaled[mIdx].y() + wF.top() * _monitorScaleFactor, + wF.width() * _monitorScaleFactor, + wF.height() * _monitorScaleFactor + }; + update(); +} diff --git a/apps/OpenSpace/ext/launcher/src/sgctedit/orientation.cpp b/apps/OpenSpace/ext/launcher/src/sgctedit/orientation.cpp new file mode 100644 index 0000000000..4f47b82ec8 --- /dev/null +++ b/apps/OpenSpace/ext/launcher/src/sgctedit/orientation.cpp @@ -0,0 +1,73 @@ +/***************************************************************************************** + * * + * 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 "sgctedit/orientation.h" + +#include "sgctedit/orientationdialog.h" + +Orientation::Orientation() + : _orientationDialog(_orientationValue, this) +{ + _layoutOrientationFull = new QHBoxLayout; + { + QVBoxLayout* layoutOrientationControls = new QVBoxLayout; + QPushButton* orientationButton = new QPushButton("Global Orientation"); + _checkBoxVsync = new QCheckBox("VSync All Windows", this); + _checkBoxVsync->setToolTip( + "If enabled, the server will frame lock and wait for all client nodes" + ); + layoutOrientationControls->addWidget(_checkBoxVsync); + orientationButton->setToolTip( + "Opens a separate dialog for setting the pitch, " + "yaw, and roll of the camera\n(the orientation applies to all viewports)" + ); + orientationButton->setFocusPolicy(Qt::NoFocus); + layoutOrientationControls->addWidget(orientationButton); + _layoutOrientationFull->addStretch(1); + _layoutOrientationFull->addLayout(layoutOrientationControls); + _layoutOrientationFull->addStretch(1); + connect( + orientationButton, + &QPushButton::released, + this, + &Orientation::orientationDialog + ); + } +} + +void Orientation::addControlsToParentLayout(QVBoxLayout* parentLayout) { + parentLayout->addLayout(_layoutOrientationFull); +} + +void Orientation::orientationDialog() { + _orientationDialog.exec(); +} + +sgct::quat Orientation::orientationValue() const { + return _orientationValue; +} + +bool Orientation::vsyncValue() const { + return (_checkBoxVsync->checkState() == Qt::Checked); +} diff --git a/apps/OpenSpace/ext/launcher/src/sgctedit/orientationdialog.cpp b/apps/OpenSpace/ext/launcher/src/sgctedit/orientationdialog.cpp new file mode 100644 index 0000000000..91ef797269 --- /dev/null +++ b/apps/OpenSpace/ext/launcher/src/sgctedit/orientationdialog.cpp @@ -0,0 +1,119 @@ +/***************************************************************************************** + * * + * 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 "sgctedit/orientationdialog.h" + +#include "sgctedit/displaywindowunion.h" + +OrientationDialog::OrientationDialog(sgct::quat& orientation, QWidget* parent) + : QDialog(parent) + , _orientationValue(orientation) +{ + setWindowTitle("Global Orientation"); + QVBoxLayout* layoutWindow = new QVBoxLayout(this); + + _linePitch = new QLineEdit; + _lineRoll = new QLineEdit; + _lineYaw = new QLineEdit; + _linePitch->setText(QString::number(_orientationValue.x)); + _lineRoll->setText(QString::number(_orientationValue.z)); + _lineYaw->setText(QString::number(_orientationValue.y)); + { + QDoubleValidator* validatorPitch = new QDoubleValidator(-90.0, 90.0, 15); + QDoubleValidator* validatorRoll = new QDoubleValidator(-360.0, 360.0, 15); + QDoubleValidator* validatorYaw = new QDoubleValidator(-180.0, 180.0, 15); + validatorPitch->setNotation(QDoubleValidator::StandardNotation); + validatorRoll->setNotation(QDoubleValidator::StandardNotation); + validatorYaw->setNotation(QDoubleValidator::StandardNotation); + _linePitch->setValidator(validatorPitch); + _lineRoll->setValidator(validatorRoll); + _lineYaw->setValidator(validatorYaw); + } + { + QLabel* labelPitch = new QLabel; + labelPitch->setText("Pitch: "); + QHBoxLayout* layoutPitch = new QHBoxLayout; + layoutPitch->addStretch(1); + QString pitchTip = "Pitch or elevation: negative numbers tilt the camera " + "downwards; positive numbers tilt upwards.\nThe allowed range is [-90, 90]. " + "Internally, this corresponds to the x value in the quaternion."; + labelPitch->setToolTip(pitchTip); + _linePitch->setToolTip(pitchTip); + layoutPitch->addWidget(labelPitch); + layoutPitch->addWidget(_linePitch); + layoutWindow->addLayout(layoutPitch); + QLabel* labelRoll = new QLabel; + labelRoll ->setText("Roll: "); + QHBoxLayout* layoutRoll = new QHBoxLayout; + layoutRoll->addStretch(1); + QString rollTip = "Roll or bank: negative numbers rotate the camera counter-" + "clockwise; positive numbers clockwise.\nThe allowed range is [-180, 180]. " + "Internally, this corresponds to the z value in the quaternion."; + labelRoll->setToolTip(rollTip); + _lineRoll->setToolTip(rollTip); + layoutRoll->addWidget(labelRoll); + layoutRoll->addWidget(_lineRoll); + layoutWindow->addLayout(layoutRoll); + QLabel* labelYaw = new QLabel; + labelYaw ->setText("Yaw: "); + QHBoxLayout* layoutYaw = new QHBoxLayout; + layoutYaw->addStretch(1); + QString yawTip = "Yaw, heading, or azimuth: negative numbers pan the camera " + "to the left; positive numbers pan to the\nright. The allowed range is " + "[-360, 360]. Internally, this corresponds to the y value in the quaternion."; + labelYaw->setToolTip(yawTip); + _lineYaw->setToolTip(yawTip); + layoutYaw->addWidget(labelYaw); + layoutYaw->addWidget(_lineYaw); + layoutWindow->addLayout(layoutYaw); + } + { + QHBoxLayout* layoutButtonBox = new QHBoxLayout; + QPushButton* buttonSave = new QPushButton("OK"); + buttonSave->setToolTip("Save global orientation changes"); + buttonSave->setFocusPolicy(Qt::NoFocus); + layoutButtonBox->addStretch(1); + layoutButtonBox->addWidget(buttonSave); + QPushButton* buttonCancel = new QPushButton("Cancel"); + buttonCancel->setToolTip("Cancel global orientation changes"); + buttonCancel->setFocusPolicy(Qt::NoFocus); + layoutButtonBox->addWidget(buttonCancel); + layoutButtonBox->addStretch(1); + connect(buttonSave, &QPushButton::released, this, &OrientationDialog::ok); + connect(buttonCancel, &QPushButton::released, this, &OrientationDialog::cancel); + layoutWindow->addLayout(layoutButtonBox); + } +} + +void OrientationDialog::ok() { + _orientationValue.x = _linePitch->text().toFloat() / 180.0 * glm::pi(); + _orientationValue.y = _lineYaw->text().toFloat() / 180.0 * glm::pi(); + _orientationValue.z = _lineRoll->text().toFloat() / 180.0 * glm::pi(); + _orientationValue.w = 1.0; + accept(); +} + +void OrientationDialog::cancel() { + reject(); +} diff --git a/apps/OpenSpace/ext/launcher/src/sgctedit/sgctedit.cpp b/apps/OpenSpace/ext/launcher/src/sgctedit/sgctedit.cpp new file mode 100644 index 0000000000..ae2fd75917 --- /dev/null +++ b/apps/OpenSpace/ext/launcher/src/sgctedit/sgctedit.cpp @@ -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. * + ****************************************************************************************/ + +#include "sgctedit/sgctedit.h" +#include + +SgctEdit::SgctEdit(QWidget* parent, std::vector& windowList, + sgct::config::Cluster& cluster, const QList& screenList, + const std::string userConfigPath) + : QDialog(parent) + , _cluster(cluster) + , _windowList(windowList) + , _userConfigPath(userConfigPath) +{ + systemMonitorConfiguration(screenList); + setWindowTitle("Window Configuration Editor"); + createWidgets(); +} + +void SgctEdit::systemMonitorConfiguration(const QList& screenList) { + size_t nScreensManaged = std::min(static_cast(screenList.length()), 2); + for (unsigned int s = 0; s < static_cast(nScreensManaged); ++s) { + int actualWidth = std::max( + screenList[s]->size().width(), + screenList[s]->availableGeometry().width() + ); + int actualHeight = std::max( + screenList[s]->size().height(), + screenList[s]->availableGeometry().height() + ); + _monitorSizeList.push_back({ + screenList[s]->availableGeometry().x(), + screenList[s]->availableGeometry().y(), + actualWidth, + actualHeight + }); + } + _nMaxWindows = (_monitorSizeList.size() == 1) ? 3 : 4; +} + +void SgctEdit::createWidgets() { + QVBoxLayout* layoutMainV = new QVBoxLayout(this); + QHBoxLayout* layoutMainH = new QHBoxLayout; + _orientationWidget = new Orientation(); + { + _monBox = std::make_shared( + _monitorWidgetSize, + _monitorSizeList, + _nMaxWindows, + _colorsForWindows + ); + QHBoxLayout* layoutMonBox = new QHBoxLayout; + layoutMonBox->addStretch(1); + layoutMonBox->addWidget(_monBox.get()); + layoutMonBox->addStretch(1); + layoutMainV->addLayout(layoutMonBox); + addDisplayLayout(layoutMainH); + } + { + layoutMainV->addLayout(layoutMainH); + _orientationWidget->addControlsToParentLayout(layoutMainV); + + QFrame* bottomBorder = new QFrame(); + bottomBorder->setFrameShape(QFrame::HLine); + layoutMainV->addWidget(bottomBorder); + + SgctConfigElements sgctCfg = {_windowList, _cluster}; + UserConfigurationElements userCfg = { + _monitorSizeList, + _displayWidget, + _orientationWidget, + _userConfigPath + }; + _fileSupportWidget = new FileSupport( + layoutMainV, + userCfg, + sgctCfg, + [this](bool accepted) { + if (accepted) { + _saveSelected = true; + accept(); + } + else { + reject(); + } + } + ); + } +} + +void SgctEdit::addDisplayLayout(QHBoxLayout* layout) { + _displayLayout = new QVBoxLayout; + _displayWidget = std::make_shared( + _monBox, + _monitorSizeList, + _nMaxWindows, + _colorsForWindows + ); + _displayFrame = new QFrame; + _displayLayout->addWidget(_displayWidget.get()); + _displayFrame->setLayout(_displayLayout); + _displayFrame->setFrameStyle(QFrame::StyledPanel | QFrame::Plain); + layout->addWidget(_displayFrame); +} + +bool SgctEdit::wasSaved() const { + return _saveSelected; +} + +std::string SgctEdit::saveFilename() { + return _fileSupportWidget->saveFilename(); +} + +SgctEdit::~SgctEdit() { + delete _orientationWidget; + delete _fileSupportWidget; + delete _displayLayout; +} diff --git a/apps/OpenSpace/ext/launcher/src/sgctedit/windowcontrol.cpp b/apps/OpenSpace/ext/launcher/src/sgctedit/windowcontrol.cpp new file mode 100644 index 0000000000..ee4b28c114 --- /dev/null +++ b/apps/OpenSpace/ext/launcher/src/sgctedit/windowcontrol.cpp @@ -0,0 +1,677 @@ +/***************************************************************************************** + * * + * 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 "sgctedit/windowcontrol.h" + +#include +#include "sgctedit/displaywindowunion.h" +#include "sgctedit/monitorbox.h" + +const std::string ProjectionTypeNames[5] = {"Planar", "Fisheye", "Spherical Mirror", + "Cylindrical", "Equirectangular"}; +QList ProjectionTypes = { + QString::fromStdString(ProjectionTypeNames[static_cast( + WindowControl::ProjectionIndeces::Planar)]), + QString::fromStdString(ProjectionTypeNames[static_cast( + WindowControl::ProjectionIndeces::Fisheye)]), + QString::fromStdString(ProjectionTypeNames[static_cast( + WindowControl::ProjectionIndeces::SphericalMirror)]), + QString::fromStdString(ProjectionTypeNames[static_cast( + WindowControl::ProjectionIndeces::Cylindrical)]), + QString::fromStdString(ProjectionTypeNames[static_cast( + WindowControl::ProjectionIndeces::Equirectangular)]) +}; +const QList QualityTypes = { + "Low (256)", + "Medium (512)", + "High (1K)", + "1.5K (1536)", + "2K (2048)", + "4K (4096)", + "8K (8192)", + "16K (16384)", + "32K (32768)", + "64K (65536)" +}; + +WindowControl::WindowControl(unsigned int monitorIndex, unsigned int windowIndex, + std::vector& monitorDims, + const QColor& winColor, QWidget *parent) + : QWidget(parent) + , _monIndex(monitorIndex) + , _monIndexDefault(monitorIndex) + , _index(windowIndex) + , _monitorResolutions(monitorDims) + , _colorForWindow(winColor) +{ + Q_INIT_RESOURCE(resources); + _nMonitors = static_cast(_monitorResolutions.size()); + createWidgets(parent); + resetToDefaults(); +} + +WindowControl::~WindowControl() { + delete _layoutFullWindow; +} + +void WindowControl::resetToDefaults() { + determineIdealWindowSize(); + _windowName->setText(""); + _monIndex = _monIndexDefault; + if (_nMonitors > 1) { + _comboMonitorSelect->setCurrentIndex(_monIndexDefault); + } + _checkBoxWindowDecor->setCheckState(Qt::CheckState::Checked); + _checkBoxWebGui->setCheckState(Qt::CheckState::Unchecked); + onWebGuiSelection(_checkBoxWebGui->checkState()); + _checkBoxSpoutOutput->setCheckState(Qt::CheckState::Unchecked); + onSpoutSelection(_checkBoxSpoutOutput->checkState()); + _comboProjection->setCurrentIndex(static_cast(ProjectionIndeces::Planar)); + onProjectionChanged(_comboProjection->currentIndex()); + _lineFovH->setText(QString::number(_defaultFovH)); + _lineFovV->setText(QString::number(_defaultFovV)); + _lineHeightOffset->setText(QString::number(_defaultHeightOffset)); + _comboQuality->setCurrentIndex(2); + if (_windowChangeCallback) { + _windowChangeCallback(_monIndex, _index, _windowDims); + } +} + +void WindowControl::determineIdealWindowSize() { + constexpr float idealAspectRatio = 16.f / 9.f; + constexpr float idealScaleVerticalLines = 2.f / 3.f; + const unsigned int primaryMonitorIdx = 0; + _windowDims = defaultWindowSizes[_index]; + _offsetX->setText(QString::number(_windowDims.x())); + _offsetY->setText(QString::number(_windowDims.y())); + float newHeight = static_cast(_monitorResolutions[primaryMonitorIdx].height()) + * idealScaleVerticalLines; + float newWidth = newHeight * idealAspectRatio; + _windowDims.setHeight(newHeight); + _windowDims.setWidth(newWidth); + _sizeX->setText(QString::number(static_cast(newWidth))); + _sizeY->setText(QString::number(static_cast(newHeight))); +} + +QString WindowControl::resolutionLabelText(QRect resolution) { + return QString::number(resolution.width()) + "x" + + QString::number(resolution.height()); +} + +void WindowControl::createWidgets(QWidget* parent) { + _windowName = new QLineEdit(parent); + _sizeX = new QLineEdit(parent); + _sizeY = new QLineEdit(parent); + _offsetX = new QLineEdit(parent); + _offsetY = new QLineEdit(parent); + _labelQuality = new QLabel; + _labelFovH = new QLabel; + _labelFovV = new QLabel; + _labelHeightOffset = new QLabel; + _buttonLockAspectRatio = new QPushButton(parent); + _buttonLockFov = new QPushButton(parent); + _lockIcon.addPixmap(QPixmap(":/images/outline_locked.png")); + _unlockIcon.addPixmap(QPixmap(":/images/outline_unlocked.png")); + _buttonLockAspectRatio->setIcon(_unlockIcon); + _buttonLockFov->setIcon(_lockIcon); + { + QIntValidator* validatorSizeX = new QIntValidator(10, _maxWindowSizePixels); + QIntValidator* validatorSizeY = new QIntValidator(10, _maxWindowSizePixels); + QIntValidator* validatorOffsetX = new QIntValidator( + -_maxWindowSizePixels, + _maxWindowSizePixels + ); + QIntValidator* validatorOffsetY = new QIntValidator( + -_maxWindowSizePixels, + _maxWindowSizePixels + ); + _sizeX->setValidator(validatorSizeX); + _sizeY->setValidator(validatorSizeY); + _offsetX->setValidator(validatorOffsetX); + _offsetY->setValidator(validatorOffsetY); + } + if (_nMonitors > 1) { + _comboMonitorSelect = new QComboBox(this); + for (unsigned int i = 0; i < _nMonitors; ++i) { + _monitorNames[i] += " (" + resolutionLabelText(_monitorResolutions[i]) + ")"; + } + _comboMonitorSelect->addItems(_monitorNames); + _comboMonitorSelect->setCurrentIndex(_monIndexDefault); + } + _fullscreenButton = new QPushButton(this); + _fullscreenButton->setText("Set to Fullscreen"); + _checkBoxWindowDecor = new QCheckBox("Window Decoration", this); + _checkBoxWindowDecor->setCheckState(Qt::CheckState::Checked); + _checkBoxWebGui = new QCheckBox("WebGUI only this window", this); + _checkBoxSpoutOutput = new QCheckBox("Spout Output", this); + _comboProjection = new QComboBox(this); + _comboProjection->addItems(ProjectionTypes); + + _comboQuality = new QComboBox(this); + _comboQuality->addItems(QualityTypes); + + { + _lineFovH = new QLineEdit(QString::number(_defaultFovH), parent); + _lineFovV = new QLineEdit(QString::number(_defaultFovV), parent); + QDoubleValidator* validatorFovH = new QDoubleValidator(-180.0, 180.0, 10); + _lineFovH->setValidator(validatorFovH); + QDoubleValidator* validatorFovV = new QDoubleValidator(-90.0, 90.0, 10); + _lineFovV->setValidator(validatorFovV); + _lineHeightOffset = new QLineEdit(QString::number(_defaultHeightOffset), parent); + QDoubleValidator* validatorHtOff= new QDoubleValidator(-1000000.0, 1000000.0, 12); + _lineHeightOffset->setValidator(validatorHtOff); + } + + connect(_sizeX, &QLineEdit::textChanged, this, &WindowControl::onSizeXChanged); + connect(_sizeY, &QLineEdit::textChanged, this, &WindowControl::onSizeYChanged); + connect(_offsetX, &QLineEdit::textChanged, this, &WindowControl::onOffsetXChanged); + connect(_offsetY, &QLineEdit::textChanged, this, &WindowControl::onOffsetYChanged); + if (_nMonitors > 1) { + connect( + _comboMonitorSelect, + qOverload(&QComboBox::currentIndexChanged), + this, + &WindowControl::onMonitorChanged + ); + } + connect( + _comboProjection, + qOverload(&QComboBox::currentIndexChanged), + this, + &WindowControl::onProjectionChanged + ); + connect( + _checkBoxSpoutOutput, + &QCheckBox::stateChanged, + this, + &WindowControl::onSpoutSelection + ); + connect( + _checkBoxWebGui, + &QCheckBox::stateChanged, + this, + &WindowControl::onWebGuiSelection + ); + connect( + _fullscreenButton, + &QPushButton::released, + this, + &WindowControl::onFullscreenClicked + ); + connect( + _buttonLockAspectRatio, + &QPushButton::released, + this, + &WindowControl::onAspectRatioLockClicked + ); + connect( + _buttonLockFov, + &QPushButton::released, + this, + &WindowControl::onFovLockClicked + ); +} + +QVBoxLayout* WindowControl::initializeLayout() { + _layoutFullWindow = new QVBoxLayout; + //Window size + QVBoxLayout* layoutWindowCtrl = new QVBoxLayout; + + _labelWinNum = new QLabel; + _labelWinNum->setText("Window " + QString::number(_index + 1)); + QString colorStr = QString::fromStdString( + fmt::format("QLabel {{ color : #{:02x}{:02x}{:02x}; }}", + _colorForWindow.red(), _colorForWindow.green(), _colorForWindow.blue())); + _labelWinNum->setStyleSheet(colorStr); + + QHBoxLayout* layoutWinNum = new QHBoxLayout; + layoutWinNum->addStretch(1); + layoutWinNum->addWidget(_labelWinNum); + layoutWinNum->addStretch(1); + layoutWindowCtrl->addLayout(layoutWinNum); + + { + QHBoxLayout* layoutName = new QHBoxLayout; + QString tip("Enter a name for the window (displayed in title bar)"); + QLabel* labelName = new QLabel(this); + labelName->setText("Name: "); + labelName->setToolTip(tip); + _windowName->setFixedWidth(160); + _windowName->setToolTip(tip); + layoutName->addWidget(labelName); + layoutName->addWidget(_windowName); + layoutName->addStretch(1); + layoutWindowCtrl->addLayout(layoutName); + } + + if (_nMonitors > 1) { + QHBoxLayout* layoutMonitorNum = new QHBoxLayout; + QLabel* labelLocation = new QLabel(this); + labelLocation->setText("Monitor: "); + QString tip("Select monitor where this window is located"); + labelLocation->setToolTip(tip); + _comboMonitorSelect->setToolTip(tip); + layoutMonitorNum->addWidget(labelLocation); + layoutMonitorNum->addWidget(_comboMonitorSelect); + layoutMonitorNum->addStretch(1); + layoutWindowCtrl->addLayout(layoutMonitorNum); + } + _sizeX->setFixedWidth(_lineEditWidthFixedWinSize); + _sizeY->setFixedWidth(_lineEditWidthFixedWinSize); + { + QLabel* labelSize = new QLabel(this); + QLabel* labelDelim = new QLabel(this); + QLabel* labelUnit = new QLabel(this); + QHBoxLayout* layoutSize = new QHBoxLayout; + labelSize->setToolTip("Enter window width & height in pixels"); + _sizeX->setToolTip("Enter window width (pixels)"); + _sizeY->setToolTip("Enter window height (pixels)"); + _buttonLockAspectRatio->setFocusPolicy(Qt::NoFocus); + _buttonLockAspectRatio->setToolTip("Locks/Unlocks size aspect ratio"); + layoutSize->addWidget(labelSize); + labelSize->setText("Size:"); + labelSize->setFixedWidth(55); + layoutSize->addWidget(_sizeX); + layoutSize->addWidget(labelDelim); + layoutSize->addWidget(_sizeY); + layoutSize->addWidget(labelUnit); + layoutSize->addWidget(_buttonLockAspectRatio); + layoutSize->addStretch(1); + labelDelim->setText("x"); + labelDelim->setFixedWidth(9); + labelUnit->setText(" px"); + layoutWindowCtrl->addLayout(layoutSize); + } + + _offsetX->setFixedWidth(_lineEditWidthFixedWinSize); + _offsetY->setFixedWidth(_lineEditWidthFixedWinSize); + { + QLabel* labelOffset = new QLabel(this); + QLabel* labelComma = new QLabel(this); + QLabel* labelUnit = new QLabel(this); + QHBoxLayout* layoutOffset = new QHBoxLayout; + std::string baseTip = "Enter {} location of window's upper left " + "corner from monitor's {} (pixels)"; + labelOffset->setToolTip(QString::fromStdString(fmt::format( + baseTip, "x,y", "upper-left corner origin"))); + _offsetX->setToolTip(QString::fromStdString(fmt::format( + baseTip, "x", "left side"))); + _offsetY->setToolTip(QString::fromStdString(fmt::format( + baseTip, "y", "top edge"))); + layoutOffset->addWidget(labelOffset); + labelOffset->setText("Offset:"); + labelOffset->setFixedWidth(55); + layoutOffset->addWidget(_offsetX); + layoutOffset->addWidget(labelComma); + layoutOffset->addWidget(_offsetY); + layoutOffset->addWidget(labelUnit); + layoutOffset->addStretch(1); + labelComma->setText(","); + labelComma->setFixedWidth(9); + labelUnit->setText(" px"); + layoutWindowCtrl->addLayout(layoutOffset); + } + { + QHBoxLayout* layoutCheckboxesFull1 = new QHBoxLayout; + QVBoxLayout* layoutCheckboxesFull2 = new QVBoxLayout; + QHBoxLayout* layoutFullscreenButton = new QHBoxLayout; + _fullscreenButton->setToolTip("If enabled, the window will be created in an " + "exclusive fullscreen mode. The size of this\nwindow will be set to the " + "screen resolution, and the window decoration automatically disabled."); + _fullscreenButton->setFocusPolicy(Qt::NoFocus); + layoutFullscreenButton->addWidget(_fullscreenButton); + layoutFullscreenButton->addStretch(1); + layoutCheckboxesFull2->addLayout(layoutFullscreenButton); + QHBoxLayout* layoutCBoxWindowDecor = new QHBoxLayout; + _checkBoxWindowDecor->setToolTip("If enabled, the window will not have a border " + "frame or title bar, and no\n controls for minimizing/maximizing, " + "resizing, or closing the window."); + layoutCBoxWindowDecor->addWidget(_checkBoxWindowDecor); + layoutCBoxWindowDecor->addStretch(1); + layoutCheckboxesFull2->addLayout(layoutCBoxWindowDecor); + QHBoxLayout* _layoutCBoxWebGui= new QHBoxLayout; + _checkBoxWebGui->setToolTip("If enabled, the window will be dedicated solely to " + "displaying the GUI controls, and will not\nrender any 3D content. All other " + "window(s) will render in 3D but will not have GUI controls."); + _layoutCBoxWebGui->addWidget(_checkBoxWebGui); + _layoutCBoxWebGui->addStretch(1); + layoutCheckboxesFull2->addLayout(_layoutCBoxWebGui); + QVBoxLayout* layoutProjectionGroup = new QVBoxLayout; + QHBoxLayout* layoutComboProjection = new QHBoxLayout; + _comboProjection->setToolTip("Select from the supported window projection types"); + layoutComboProjection->addWidget(_comboProjection); + layoutComboProjection->addWidget(_buttonLockFov); + _buttonLockFov->setToolTip("Locks and scales the Horizontal & Vertical F.O.V. " + "to the ideal settings based on aspect ratio."); + _buttonLockFov->setFocusPolicy(Qt::NoFocus); + layoutComboProjection->addStretch(1); + layoutProjectionGroup->addLayout(layoutComboProjection); + QFrame* borderProjectionGroup = new QFrame; + borderProjectionGroup->setFrameStyle(QFrame::StyledPanel | QFrame::Plain); + borderProjectionGroup->setLayout(layoutProjectionGroup); + borderProjectionGroup->setVisible(true); + QHBoxLayout* layoutCBoxSpoutOutput= new QHBoxLayout; + QString spoutTip = "This projection method provides the ability to share the " + "reprojected image using the Spout library.\nThis library only supports the " + "Windows operating system. Spout makes it possible to make the rendered\n" + "images available to other real-time applications on the same machine for " + "further processing.\nThe SpoutOutputProjection option can work with either " + "Fisheye or Equirectangular projection."; + _checkBoxSpoutOutput->setToolTip(spoutTip); + layoutCBoxSpoutOutput->addWidget(_checkBoxSpoutOutput); + layoutCBoxSpoutOutput->addStretch(1); + layoutProjectionGroup->addLayout(layoutCBoxSpoutOutput); + QHBoxLayout* layoutComboQuality = new QHBoxLayout; + _labelQuality->setText("Quality:"); + QString qualityTip = "Determines the pixel resolution of the projection " + "rendering. The higher resolution,\nthe better the rendering quality, but at " + "the expense of increased rendering times."; + _labelQuality->setToolTip(qualityTip); + _comboQuality->setToolTip(qualityTip); + layoutComboQuality->addWidget(_labelQuality); + layoutComboQuality->addWidget(_comboQuality); + layoutComboQuality->addStretch(1); + layoutProjectionGroup->addLayout(layoutComboQuality); + QHBoxLayout* layoutFovH = new QHBoxLayout; + _labelFovH->setText("Horizontal FOV:"); + QString hfovTip = "The total horizontal field of view of the viewport (degrees). " + "Internally,\nthe values for 'left' & 'right' will each be half this value."; + _labelFovH->setToolTip(hfovTip); + _lineFovH->setToolTip(hfovTip); + layoutFovH->addWidget(_labelFovH); + layoutFovH->addStretch(1); + layoutFovH->addWidget(_lineFovH); + QHBoxLayout* layoutFovV = new QHBoxLayout; + _labelFovV->setText("Vertical FOV:"); + QString vfovTip = "The total vertical field of view of the viewport (degrees). " + "Internally,\nthe values for 'up' & 'down' will each be half this value."; + _labelFovV->setToolTip(vfovTip); + _lineFovV->setToolTip(vfovTip); + layoutFovV->addWidget(_labelFovV); + layoutFovV->addStretch(1); + layoutFovV->addWidget(_lineFovV); + _lineFovH->setFixedWidth(_lineEditWidthFixedFov); + _lineFovV->setFixedWidth(_lineEditWidthFixedFov); + _lineFovH->setEnabled(false); + _lineFovV->setEnabled(false); + layoutProjectionGroup->addLayout(layoutFovH); + layoutProjectionGroup->addLayout(layoutFovV); + QHBoxLayout* layoutHeightOffset = new QHBoxLayout; + _labelHeightOffset->setText("Height Offset:"); + QString heightTip = "Offsets the height from which the cylindrical projection " + "is generated.\nThis is, in general, only necessary if the user position is " + "offset and\ncountering that offset is desired in order to continue producing" + "\na 'standard' cylindrical projection."; + _labelHeightOffset->setToolTip(heightTip); + _lineHeightOffset->setToolTip(heightTip); + layoutHeightOffset->addWidget(_labelHeightOffset); + layoutHeightOffset->addWidget(_lineHeightOffset); + layoutHeightOffset->addStretch(1); + layoutProjectionGroup->addLayout(layoutHeightOffset); + layoutCheckboxesFull2->addWidget(borderProjectionGroup); + layoutCheckboxesFull1->addLayout(layoutCheckboxesFull2); + layoutCheckboxesFull1->addStretch(1); + layoutWindowCtrl->addLayout(layoutCheckboxesFull1); + } + layoutWindowCtrl->addStretch(1); + _layoutFullWindow->addLayout(layoutWindowCtrl); + + _comboProjection->setCurrentIndex(0); + onProjectionChanged(static_cast(ProjectionIndeces::Planar)); + _comboQuality->setCurrentIndex(2); + + return _layoutFullWindow; +} + +void WindowControl::showWindowLabel(bool show) { + _labelWinNum->setVisible(show); +} + +void WindowControl::onSizeXChanged(const QString& newText) { + _windowDims.setWidth(newText.toInt()); + if (_aspectRatioLocked) { + int updatedHeight = _windowDims.width() / _aspectRatioSize; + _sizeY->blockSignals(true); + _sizeY->setText(QString::number(updatedHeight)); + _sizeY->blockSignals(false); + _windowDims.setHeight(updatedHeight); + } + if (_windowChangeCallback) { + _windowChangeCallback(_monIndex, _index, _windowDims); + } + if (_FovLocked) { + updatePlanarLockedFov(); + } +} + +void WindowControl::onSizeYChanged(const QString& newText) { + _windowDims.setHeight(newText.toInt()); + if (_aspectRatioLocked) { + int updatedWidth = _windowDims.height() * _aspectRatioSize; + _sizeX->blockSignals(true); + _sizeX->setText(QString::number(updatedWidth)); + _sizeX->blockSignals(false); + _windowDims.setWidth(updatedWidth); + } + if (_windowChangeCallback) { + _windowChangeCallback(_monIndex, _index, _windowDims); + } + if (_FovLocked) { + updatePlanarLockedFov(); + } +} + +void WindowControl::onOffsetXChanged(const QString& newText) { + float prevWidth = _windowDims.width(); + try { + _windowDims.setX(newText.toInt()); + _windowDims.setWidth(prevWidth); + if (_windowChangeCallback) { + _windowChangeCallback(_monIndex, _index, _windowDims); + } + } + catch (std::exception const&) { + //The QIntValidator ensures that the range is a +/- integer + //However, it's possible to enter only a - character which + //causes an exception throw, which is ignored here (when user + //enters an integer after the - then the value will be updated). + } +} + +void WindowControl::onOffsetYChanged(const QString& newText) { + float prevHeight = _windowDims.height(); + try { + _windowDims.setY(newText.toInt()); + _windowDims.setHeight(prevHeight); + if (_windowChangeCallback) { + _windowChangeCallback(_monIndex, _index, _windowDims); + } + } + catch (std::exception const&) { + //See comment in onOffsetXChanged + } +} + +void WindowControl::onFullscreenClicked() { + _offsetX->setText("0"); + _offsetY->setText("0"); + _sizeX->setText(QString::number(_monitorResolutions[_monIndex].width())); + _sizeY->setText(QString::number(_monitorResolutions[_monIndex].height())); + _checkBoxWindowDecor->setCheckState(Qt::Unchecked); +} + +void WindowControl::onWebGuiSelection(int selectionState) { + if (_windowGuiCheckCallback && (selectionState == Qt::Checked)) { + _windowGuiCheckCallback(_index); + } +} + +void WindowControl::onSpoutSelection(int selectionState) { + if (selectionState == Qt::Checked) { + WindowControl::ProjectionIndeces currentProjectionSelection; + currentProjectionSelection = static_cast( + _comboProjection->currentIndex() + ); + if ((currentProjectionSelection != ProjectionIndeces::Equirectangular) && + (currentProjectionSelection != ProjectionIndeces::Fisheye)) + { + _comboProjection->setCurrentIndex( + static_cast(ProjectionIndeces::Equirectangular) + ); + } + } +} + +void WindowControl::onMonitorChanged(int newSelection) { + _monIndex = newSelection; + if (_windowChangeCallback) { + _windowChangeCallback(_monIndex, _index, _windowDims); + } +} + +void WindowControl::onProjectionChanged(int newSelection) { + WindowControl::ProjectionIndeces selected + = static_cast(newSelection); + _comboQuality->setVisible(selected != ProjectionIndeces::Planar); + _labelQuality->setVisible(selected != ProjectionIndeces::Planar); + _labelFovH->setVisible(selected == ProjectionIndeces::Planar); + _lineFovH->setVisible(selected == ProjectionIndeces::Planar); + _labelFovV->setVisible(selected == ProjectionIndeces::Planar); + _lineFovV->setVisible(selected == ProjectionIndeces::Planar); + _buttonLockFov->setVisible(selected == ProjectionIndeces::Planar); + _labelHeightOffset->setVisible(selected == ProjectionIndeces::Cylindrical); + _lineHeightOffset->setVisible(selected == ProjectionIndeces::Cylindrical); + _checkBoxSpoutOutput->setVisible(selected == ProjectionIndeces::Fisheye + || selected == ProjectionIndeces::Equirectangular); +} + +void WindowControl::onAspectRatioLockClicked() { + _aspectRatioLocked = !_aspectRatioLocked; + _buttonLockAspectRatio->setIcon(_aspectRatioLocked ? _lockIcon : _unlockIcon); + if (_aspectRatioLocked) { + _aspectRatioSize = _windowDims.width() / _windowDims.height(); + } +} + +void WindowControl::onFovLockClicked() { + _FovLocked = !_FovLocked; + _buttonLockFov->setIcon(_FovLocked ? _lockIcon : _unlockIcon); + if (_FovLocked) { + _lineFovH->setEnabled(false); + _lineFovV->setEnabled(false); + updatePlanarLockedFov(); + } + else { + _lineFovH->setEnabled(true); + _lineFovV->setEnabled(true); + } +} + +void WindowControl::updatePlanarLockedFov() { + float currentAspectRatio = _windowDims.width() / _windowDims.height(); + float relativeRatio = currentAspectRatio / _idealAspectRatio; + if (relativeRatio >= 1.0) { + _lineFovH->setText(QString::number(std::min(_defaultFovH *relativeRatio, 180.f))); + _lineFovV->setText(QString::number(_defaultFovV)); + } + else { + _lineFovH->setText(QString::number(_defaultFovH)); + _lineFovV->setText(QString::number(std::min(_defaultFovV /relativeRatio, 180.f))); + } +} + +void WindowControl::setWindowChangeCallback( + std::function cb) +{ + _windowChangeCallback = std::move(cb); +} + +void WindowControl::setWebGuiChangeCallback(std::function cb) +{ + _windowGuiCheckCallback = std::move(cb); +} + +void WindowControl::uncheckWebGuiOption() { + _checkBoxWebGui->setCheckState(Qt::Unchecked); +} + +QRectF& WindowControl::dimensions() { + return _windowDims; +} + +std::string WindowControl::windowName() const { + return _windowName->text().toStdString(); +} + +sgct::ivec2 WindowControl::windowSize() const { + return { + _sizeX->text().toInt(), + _sizeY->text().toInt() + }; +} + +sgct::ivec2 WindowControl::windowPos() const { + return { + _offsetX->text().toInt(), + _offsetY->text().toInt() + }; +} + +bool WindowControl::isDecorated() const { + return (_checkBoxWindowDecor->checkState() == Qt::Checked); +} + +bool WindowControl::isGuiWindow() const { + return (_checkBoxWebGui->checkState() == Qt::Checked); +} + +bool WindowControl::isSpoutSelected() const { + return (_checkBoxSpoutOutput->checkState() == Qt::Checked); +} + +WindowControl::ProjectionIndeces WindowControl::projectionSelectedIndex() const { + return + static_cast(_comboProjection->currentIndex()); +} + +int WindowControl::qualitySelectedValue() const { + return QualityValues[_comboQuality->currentIndex()]; +} + +float WindowControl::fovH() const { + return _lineFovH->text().toFloat(); +} + +float WindowControl::fovV() const { + return _lineFovV->text().toFloat(); +} + +float WindowControl::heightOffset() const { + return _lineHeightOffset->text().toFloat(); +} + +unsigned int WindowControl::monitorNum() const { + return _monIndex; +} + diff --git a/apps/OpenSpace/main.cpp b/apps/OpenSpace/main.cpp index 5857440162..9addd0c833 100644 --- a/apps/OpenSpace/main.cpp +++ b/apps/OpenSpace/main.cpp @@ -1044,9 +1044,10 @@ int main(int argc, char* argv[]) { // to make it possible to find other files in the same directory. FileSys.registerPathToken( "${BIN}", - std::filesystem::path(argv[0]).parent_path(), + std::filesystem::current_path() / std::filesystem::path(argv[0]).parent_path(), ghoul::filesystem::FileSystem::Override::Yes ); + LDEBUG(fmt::format("Registering ${{BIN}} to {}", absPath("${BIN}"))); // // Parse commandline arguments diff --git a/data/assets/events/toggle_sun.asset b/data/assets/events/toggle_sun.asset new file mode 100644 index 0000000000..29d1a48d34 --- /dev/null +++ b/data/assets/events/toggle_sun.asset @@ -0,0 +1,61 @@ +local toggle_sun = { + Identifier = "os.toggle_sun", + Name = "Toggle Sun", + Command = [[ + if not is_declared("args") then + openspace.printError("Cannot execute 'os.toggle_sun' manually") + return + end + + if args.Transition == "Approaching" then + openspace.setPropertyValueSingle("Scene.SunGlare.Renderable.Enabled", false) + openspace.setPropertyValueSingle("Scene.Sun.Renderable.Enabled", true) + else -- "Exiting" + openspace.setPropertyValueSingle("Scene.SunGlare.Renderable.Enabled", true) + openspace.setPropertyValueSingle("Scene.Sun.Renderable.Enabled", false) + end + ]], + Documentation = [[Toggles the visibility of the Sun glare and the Sun globe when the + camera is approaching either so that from far away the Sun Glare is rendered and when + close up, the globe is rendered instead.]], + GuiPath = "/Sun", + IsLocal = false +} + +asset.onInitialize(function() + openspace.action.registerAction(toggle_sun) + openspace.event.registerEventAction( + "CameraFocusTransition", + toggle_sun.Identifier, + { Node = "Sun", Transition = "Approaching" } + ) + openspace.event.registerEventAction( + "CameraFocusTransition", + toggle_sun.Identifier, + { Node = "Sun", Transition = "Exiting" } + ) +end) + +asset.onDeinitialize(function() + openspace.event.unregisterEventAction( + "CameraFocusTransition", + toggle_sun.Identifier, + { Node = "Sun", Transition = "Exiting" } + ) + openspace.event.unregisterEventAction( + "CameraFocusTransition", + toggle_sun.Identifier, + { Node = "Sun", Transition = "Approaching" } + ) + openspace.action.removeAction(toggle_sun) +end) + + +asset.meta = { + Name = "Actions - Toggle current Trails", + Version = "1.0", + Description = [[ Asset providing actions to toggle trails]], + Author = "OpenSpace Team", + URL = "http://openspaceproject.com", + License = "MIT license" +} diff --git a/data/assets/events/toggle_trail.asset b/data/assets/events/toggle_trail.asset index 803885cf73..27b054134a 100644 --- a/data/assets/events/toggle_trail.asset +++ b/data/assets/events/toggle_trail.asset @@ -5,12 +5,12 @@ asset.onInitialize(function() "CameraFocusTransition", action.show_trail, { Transition = "Exiting" } - ); + ) openspace.event.registerEventAction( "CameraFocusTransition", action.hide_trail, { Transition = "Approaching" } - ); + ) end) asset.onDeinitialize(function() @@ -18,10 +18,10 @@ asset.onDeinitialize(function() "CameraFocusTransition", action.show_trail, { Transition = "Exiting" } - ); + ) openspace.event.unregisterEventAction( "CameraFocusTransition", action.hide_trail, { Transition = "Approaching" } - ); + ) end) diff --git a/data/assets/scene/milkyway/gaia/gaiastars.asset b/data/assets/scene/milkyway/gaia/gaiastars.asset index a3f761a349..39032409e8 100644 --- a/data/assets/scene/milkyway/gaia/gaiastars.asset +++ b/data/assets/scene/milkyway/gaia/gaiastars.asset @@ -1,3 +1,9 @@ +local fullOS = openspace.systemCapabilities.fullOperatingSystem() +if string.find(fullOS, "Darwin") then + openspace.printWarning("Gaia module (RenderableGaiaStars) not supported on mac") + return +end + -- Download a preprocessed binary octree of Radial Velocity subset values per star (preprocessed into 8 binary files). local starsFolder = asset.syncedResource({ Name = "Gaia Stars RV", diff --git a/data/assets/scene/solarsystem/missions/newhorizons/pluto.asset b/data/assets/scene/solarsystem/missions/newhorizons/pluto.asset index 234234b79c..b6456a2a5a 100644 --- a/data/assets/scene/solarsystem/missions/newhorizons/pluto.asset +++ b/data/assets/scene/solarsystem/missions/newhorizons/pluto.asset @@ -27,7 +27,7 @@ local images = asset.syncedResource({ Name = "Pluto Images", Type = "HttpSynchronization", Identifier = "newhorizons_plutoencounter_pluto_images", - Version = 1 + Version = 2 }) local plutoRadius = 1.173E6 diff --git a/data/assets/scene/solarsystem/planets/default_layers.asset b/data/assets/scene/solarsystem/planets/default_layers.asset index 2aae0ad044..1e4565ec20 100644 --- a/data/assets/scene/solarsystem/planets/default_layers.asset +++ b/data/assets/scene/solarsystem/planets/default_layers.asset @@ -7,9 +7,7 @@ asset.require("./jupiter/europa/default_layers") asset.require("./jupiter/ganymede/default_layers") asset.require("./jupiter/io/default_layers") -asset.require("./mars/default_layers") -asset.require("./mars/moons/layers/colorlayers/deimos_viking") -asset.require("./mars/moons/layers/colorlayers/phobos_viking") +asset.require('./mars/default_layers') asset.require("./mercury/default_layers") diff --git a/data/assets/scene/solarsystem/planets/earth/satellites/communications/starlink.asset b/data/assets/scene/solarsystem/planets/earth/satellites/communications/starlink.asset new file mode 100644 index 0000000000..2a9d8500c5 --- /dev/null +++ b/data/assets/scene/solarsystem/planets/earth/satellites/communications/starlink.asset @@ -0,0 +1,23 @@ +local shared = asset.require("util/tle_helper") + +local group = { + Title = "Starlink", + Url = "http://www.celestrak.com/NORAD/elements/starlink.txt", + TrailColor = { 0.65, 0.55, 0.55 }, + Description = [[LEO satellite constellation launched by SpaceX to provide + broadband internet access.]] +} + +local tle = shared.downloadTLEFile(asset, group.Url, group.Title) +shared.registerSatelliteGroupObjects(asset, group, tle, true) + + +asset.meta = { + Name = "Satellites Communications - Starlink", + Version = "1.0", + Description = [[ Satellites asset for Communications - Starlink. Data from + Celestrak]], + Author = "OpenSpace Team", + URL = "https://celestrak.com/NORAD/elements/", + License = "Celestrak" +} diff --git a/data/assets/scene/solarsystem/planets/earth/satellites/misc/active.asset b/data/assets/scene/solarsystem/planets/earth/satellites/misc/active.asset new file mode 100644 index 0000000000..0144732ffc --- /dev/null +++ b/data/assets/scene/solarsystem/planets/earth/satellites/misc/active.asset @@ -0,0 +1,21 @@ +local shared = asset.require("util/tle_helper") + +local group = { + Title = "Active", + Url = "http://www.celestrak.com/NORAD/elements/active.txt", + TrailColor = { 0.45, 0.25, 0.45 }, + Description = [[Satellites that employ active communication.]] +} + +local tle = shared.downloadTLEFile(asset, group.Url, group.Title) +shared.registerSatelliteGroupObjects(asset, group, tle, true) + + +asset.meta = { + Name = "Satellites Active", + Version = "1.0", + Description = [[ Satellites that employ active communication. Data from Celestrak]], + Author = "OpenSpace Team", + URL = "https://celestrak.com/NORAD/elements/", + License = "Celestrak" +} diff --git a/data/assets/scene/solarsystem/planets/earth/satellites/satellites_communications.asset b/data/assets/scene/solarsystem/planets/earth/satellites/satellites_communications.asset index b5c6871dc8..b5caa1c6f5 100644 --- a/data/assets/scene/solarsystem/planets/earth/satellites/satellites_communications.asset +++ b/data/assets/scene/solarsystem/planets/earth/satellites/satellites_communications.asset @@ -11,6 +11,7 @@ asset.require("./communications/other_comm") asset.require("./communications/gorizont") asset.require("./communications/raduga") asset.require("./communications/molniya") +asset.require("./communications/starlink") asset.meta = { diff --git a/data/assets/scene/solarsystem/planets/earth/satellites/satellites_misc.asset b/data/assets/scene/solarsystem/planets/earth/satellites/satellites_misc.asset index 76fb2afaed..b05af852dc 100644 --- a/data/assets/scene/solarsystem/planets/earth/satellites/satellites_misc.asset +++ b/data/assets/scene/solarsystem/planets/earth/satellites/satellites_misc.asset @@ -2,6 +2,7 @@ asset.require("./misc/military") asset.require("./misc/radar") asset.require("./misc/cubesats") asset.require("./misc/other") +asset.require("./misc/active") asset.meta = { diff --git a/data/assets/scene/solarsystem/planets/mars/layers/colorlayers/themis_ir_night_sweden.asset b/data/assets/scene/solarsystem/planets/mars/layers/colorlayers/themis_ir_night_sweden.asset index 4baf2cec7f..76781643a2 100644 --- a/data/assets/scene/solarsystem/planets/mars/layers/colorlayers/themis_ir_night_sweden.asset +++ b/data/assets/scene/solarsystem/planets/mars/layers/colorlayers/themis_ir_night_sweden.asset @@ -3,7 +3,7 @@ local globeIdentifier = asset.require("../../mars").Mars.Identifier local layer = { Identifier = "Themis_IR_Night_Sweden", Name = "Themis IR Night [Sweden]", - FilePath = asset.localResource("themis_ir_night_sweden.wms"), + FilePath = asset.localResource("themis_ir_night_sweden.vrt"), BlendMode = "Color", Description = [[This mosaic represents the Thermal Emission Imaging System (THEMIS) -nighttime infrared (IR) 100 meter/pixel mosaic (version 12) released in the diff --git a/data/assets/scene/solarsystem/planets/mars/layers/colorlayers/themis_ir_night_sweden.vrt b/data/assets/scene/solarsystem/planets/mars/layers/colorlayers/themis_ir_night_sweden.vrt new file mode 100644 index 0000000000..9e774461f2 --- /dev/null +++ b/data/assets/scene/solarsystem/planets/mars/layers/colorlayers/themis_ir_night_sweden.vrt @@ -0,0 +1,26 @@ + + GEOGCS["GCS_Mars_2000_Sphere",DATUM["D_Mars_2000_Sphere",SPHEROID["Mars_2000_Sphere_IAU_IAG",3396190.0,0.0]],PRIMEM["Reference_Meridian",0.0],UNIT["Degree",0.0174532925199433]] + -1.8000000000000000e+02, 1.6870676889047182e-03, 0.0000000000000000e+00, 9.0000000000000000e+01, 0.0000000000000000e+00, -1.6870518768452129e-03 + + Gray + + themis_ir_night_sweden.wms + 1 + + + + + + + Alpha + + themis_ir_night_sweden.wms + 1 + + + + 255 + 0 + + + diff --git a/data/assets/scene/solarsystem/planets/mars/layers/colorlayers/themis_ir_night_sweden.wms b/data/assets/scene/solarsystem/planets/mars/layers/colorlayers/themis_ir_night_sweden.wms index 849d34bc5b..4ec51d1669 100644 --- a/data/assets/scene/solarsystem/planets/mars/layers/colorlayers/themis_ir_night_sweden.wms +++ b/data/assets/scene/solarsystem/planets/mars/layers/colorlayers/themis_ir_night_sweden.wms @@ -4,9 +4,9 @@ -180.0 - 90.0 + 60.0 180.0 - -90.0 + -60.0 213388 71130 9 diff --git a/data/assets/scene/solarsystem/planets/mars/layers/colorlayers/themis_ir_night_utah.asset b/data/assets/scene/solarsystem/planets/mars/layers/colorlayers/themis_ir_night_utah.asset index 3ec6fd56b7..696049397f 100644 --- a/data/assets/scene/solarsystem/planets/mars/layers/colorlayers/themis_ir_night_utah.asset +++ b/data/assets/scene/solarsystem/planets/mars/layers/colorlayers/themis_ir_night_utah.asset @@ -3,7 +3,7 @@ local globeIdentifier = asset.require("../../mars").Mars.Identifier local layer = { Identifier = "Themis_IR_Night_Utah", Name = "Themis IR Night [Utah]", - FilePath = asset.localResource("themis_ir_night_utah.wms"), + FilePath = asset.localResource("themis_ir_night_utah.vrt"), BlendMode = "Color", Description = [[This mosaic represents the Thermal Emission Imaging System (THEMIS) -nighttime infrared (IR) 100 meter/pixel mosaic (version 12) released in the diff --git a/data/assets/scene/solarsystem/planets/mars/layers/colorlayers/themis_ir_night_utah.vrt b/data/assets/scene/solarsystem/planets/mars/layers/colorlayers/themis_ir_night_utah.vrt new file mode 100644 index 0000000000..3521c013b7 --- /dev/null +++ b/data/assets/scene/solarsystem/planets/mars/layers/colorlayers/themis_ir_night_utah.vrt @@ -0,0 +1,26 @@ + + GEOGCS["GCS_Mars_2000_Sphere",DATUM["D_Mars_2000_Sphere",SPHEROID["Mars_2000_Sphere_IAU_IAG",3396190.0,0.0]],PRIMEM["Reference_Meridian",0.0],UNIT["Degree",0.0174532925199433]] + -1.8000000000000000e+02, 1.6870676889047182e-03, 0.0000000000000000e+00, 9.0000000000000000e+01, 0.0000000000000000e+00, -1.6870518768452129e-03 + + Gray + + themis_ir_night_utah.wms + 1 + + + + + + + Alpha + + themis_ir_night_utah.wms + 1 + + + + 255 + 0 + + + diff --git a/data/assets/scene/solarsystem/planets/mars/layers/colorlayers/themis_ir_night_utah.wms b/data/assets/scene/solarsystem/planets/mars/layers/colorlayers/themis_ir_night_utah.wms index 6ae6c9848d..9ad8055b56 100644 --- a/data/assets/scene/solarsystem/planets/mars/layers/colorlayers/themis_ir_night_utah.wms +++ b/data/assets/scene/solarsystem/planets/mars/layers/colorlayers/themis_ir_night_utah.wms @@ -4,9 +4,9 @@ -180.0 - 90.0 + 60.0 180.0 - -90.0 + -60.0 213388 71130 9 diff --git a/data/assets/scene/solarsystem/planets/mars/moons/deimos.asset b/data/assets/scene/solarsystem/planets/mars/moons/deimos.asset index bb9f981936..db687edcbf 100644 --- a/data/assets/scene/solarsystem/planets/mars/moons/deimos.asset +++ b/data/assets/scene/solarsystem/planets/mars/moons/deimos.asset @@ -1,4 +1,13 @@ -local transforms = asset.require("../transforms") +local transforms = asset.require('scene/solarsystem/planets/mars/transforms') +local sunTransforms = asset.require('scene/solarsystem/sun/transforms') +local sun = asset.require("scene/solarsystem/sun/sun") + +local model = asset.syncedResource({ + Name = "Deimos Model", + Type = "HttpSynchronization", + Identifier = "deimos_model", + Version = 1 +}) local kernels = asset.syncedResource({ Name = "Mars Spice Kernels", @@ -25,10 +34,17 @@ local Deimos = { } }, Renderable = { - Type = "RenderableGlobe", - Radii = { 15000, 12200, 11000 }, - SegmentsPerPatch = 90, - Layers = {} + Type = "RenderableModel", + GeometryFile = model .. "/Deimos_1_1000.glb", + ModelScale = 'Kilometer', + RotationVector = { 180, 0, 180 }, + AmbientIntensity = 0.02, + SpecularIntensity = 0.0, + LightSources = { + sun.LightSource + }, + PerformShading = true, + DisableFaceCulling = true }, Tag = { "moon_solarSystem", "moon_terrestrial", "moon_mars" }, GUI = { @@ -74,11 +90,10 @@ asset.export(Deimos) asset.export(DeimosTrail) - asset.meta = { Name = "Deimos", - Version = "1.1", - Description = [[ RenderableGlobe and Trail for Deimos.]], + Version = "1.0", + Description = [[ RenderableModel and Trail for Deimos.]], Author = "OpenSpace Team", URL = "http://openspaceproject.com", License = "MIT license" diff --git a/data/assets/scene/solarsystem/planets/mars/moons/deimos_globe.asset b/data/assets/scene/solarsystem/planets/mars/moons/deimos_globe.asset new file mode 100644 index 0000000000..4fb9ad6aa0 --- /dev/null +++ b/data/assets/scene/solarsystem/planets/mars/moons/deimos_globe.asset @@ -0,0 +1,63 @@ +local transforms = asset.require('../transforms') + +local kernels = asset.syncedResource({ + Name = "Mars Spice Kernels", + Type = "HttpSynchronization", + Identifier = "mars_kernels", + Version = 1 +}) + + +local DeimosGlobe = { + Identifier = "Deimos_Globe", + Parent = transforms.MarsBarycenter.Identifier, + Transform = { + Rotation = { + Type = "SpiceRotation", + SourceFrame = "IAU_DEIMOS", + DestinationFrame = "GALACTIC", + Kernels = kernels .. "mar097.bsp" + }, + Translation = { + Type = "SpiceTranslation", + Target = "DEIMOS", + Observer = "MARS BARYCENTER", + Kernels = kernels .. "mar097.bsp" + } + }, + Renderable = { + Type = "RenderableGlobe", + Radii = { 7500, 6100, 5200 }, +--Radius source +--https://sci.esa.int/documents/35171/36506/1567259108230-5-ESLAB46-Day2-Rosenblatt.pdf + SegmentsPerPatch = 90, + Layers = {} + }, + Tag = { "moon_solarSystem", "moon_terrestrial", "moon_mars" }, + GUI = { + Name = "Deimos_Globe", + Path = "/Solar System/Planets/Mars", + Description = [[One of two moons of Mars.]] + } +} + +asset.onInitialize(function() + openspace.addSceneGraphNode(DeimosGlobe) +end) + +asset.onDeinitialize(function() + openspace.removeSceneGraphNode(DeimosGlobe) +end) + +asset.export(DeimosGlobe) + + +asset.meta = { + Name = "Deimos Globe", + Version = "1.0", + Description = [[ RenderableGlobe for Deimos.]], + Author = "OpenSpace Team", + URL = "http://openspaceproject.com", + License = "MIT license", + Identifiers = {"Deimos"} +} diff --git a/data/assets/scene/solarsystem/planets/mars/moons/layers/colorlayers/deimos_viking.asset b/data/assets/scene/solarsystem/planets/mars/moons/layers/colorlayers/deimos_viking.asset index a07f1b275f..bb4eac747d 100644 --- a/data/assets/scene/solarsystem/planets/mars/moons/layers/colorlayers/deimos_viking.asset +++ b/data/assets/scene/solarsystem/planets/mars/moons/layers/colorlayers/deimos_viking.asset @@ -1,4 +1,4 @@ -local globeIdentifier = asset.require("../../deimos").Deimos.Identifier +local globeIdentifier = asset.require("./../../deimos_globe").Deimos_Globe.Identifier local layer = { Identifier = "Deimos_Global_Mosaic_USGS", diff --git a/data/assets/scene/solarsystem/planets/mars/moons/layers/colorlayers/phobos_viking.asset b/data/assets/scene/solarsystem/planets/mars/moons/layers/colorlayers/phobos_viking.asset index d4f2b61ef5..6b259f3ab0 100644 --- a/data/assets/scene/solarsystem/planets/mars/moons/layers/colorlayers/phobos_viking.asset +++ b/data/assets/scene/solarsystem/planets/mars/moons/layers/colorlayers/phobos_viking.asset @@ -1,4 +1,4 @@ -local globeIdentifier = asset.require("../../phobos").Phobos.Identifier +local globeIdentifier = asset.require("./../../phobos_globe").Phobos_Globe.Identifier local layer = { Identifier = "Phobos_Global_Shaded_Relief_USGS", diff --git a/data/assets/scene/solarsystem/planets/mars/moons/phobos.asset b/data/assets/scene/solarsystem/planets/mars/moons/phobos.asset index 81223634ca..ad27d32b46 100644 --- a/data/assets/scene/solarsystem/planets/mars/moons/phobos.asset +++ b/data/assets/scene/solarsystem/planets/mars/moons/phobos.asset @@ -1,4 +1,13 @@ -local transforms = asset.require("../transforms") +local transforms = asset.require('scene/solarsystem/planets/mars/transforms') +local sunTransforms = asset.require('scene/solarsystem/sun/transforms') +local sun = asset.require("scene/solarsystem/sun/sun") + +local model = asset.syncedResource({ + Name = "Phobos Model", + Type = "HttpSynchronization", + Identifier = "phobos_model", + Version = 1 +}) local kernels = asset.syncedResource({ Name = "Mars Spice Kernels", @@ -7,6 +16,7 @@ local kernels = asset.syncedResource({ Version = 1 }) + local Phobos = { Identifier = "Phobos", Parent = transforms.MarsBarycenter.Identifier, @@ -25,10 +35,17 @@ local Phobos = { } }, Renderable = { - Type = "RenderableGlobe", - Radii = { 27000, 22000, 18000 }, - SegmentsPerPatch = 90, - Layers = {} + Type = "RenderableModel", + GeometryFile = model .. "/Phobos_1_1000.glb", + ModelScale = 'Kilometer', + RotationVector = { 90, 0, 90 }, + AmbientIntensity = 0.02, + SpecularIntensity = 0.0, + LightSources = { + sun.LightSource + }, + PerformShading = true, + DisableFaceCulling = true }, Tag = { "moon_solarSystem", "moon_terrestrial", "moon_mars" }, GUI = { @@ -46,7 +63,8 @@ local PhobosTrail = { Translation = { Type = "SpiceTranslation", Target = "PHOBOS", - Observer = "MARS BARYCENTER" + Observer = "MARS BARYCENTER", + Kernels = kernels .. "mar097.bsp" }, Color = { 1.0, 0.605, 0.420 }, Period = 0.31891023, @@ -77,8 +95,8 @@ asset.export(PhobosTrail) asset.meta = { Name = "Phobos", - Version = "1.1", - Description = [[ RenderableGlobe and Trail for Phobos.]], + Version = "1.0", + Description = [[ RenderableModel and Trail for Phobos.]], Author = "OpenSpace Team", URL = "http://openspaceproject.com", License = "MIT license" diff --git a/data/assets/scene/solarsystem/planets/mars/moons/phobos_globe.asset b/data/assets/scene/solarsystem/planets/mars/moons/phobos_globe.asset new file mode 100644 index 0000000000..efdba64a7a --- /dev/null +++ b/data/assets/scene/solarsystem/planets/mars/moons/phobos_globe.asset @@ -0,0 +1,63 @@ +local transforms = asset.require('../transforms') + +local kernels = asset.syncedResource({ + Name = "Mars Spice Kernels", + Type = "HttpSynchronization", + Identifier = "mars_kernels", + Version = 1 +}) + + +local PhobosGlobe = { + Identifier = "Phobos_Globe", + Parent = transforms.MarsBarycenter.Identifier, + Transform = { + Rotation = { + Type = "SpiceRotation", + SourceFrame = "IAU_PHOBOS", + DestinationFrame = "GALACTIC", + Kernels = kernels .. "mar097.bsp" + }, + Translation = { + Type = "SpiceTranslation", + Target = "PHOBOS", + Observer = "MARS BARYCENTER", + Kernels = kernels .. "mar097.bsp" + } + }, + Renderable = { + Type = "RenderableGlobe", + Radii = { 13030, 11400, 9140 }, +--Radius source +--https://naif.jpl.nasa.gov/pub/naif/generic_kernels/dsk/satellites/willner_etal_phobos.pdf + SegmentsPerPatch = 90, + Layers = {} + }, + Tag = { "moon_solarSystem", "moon_terrestrial", "moon_mars" }, + GUI = { + Name = "Phobos_Globe", + Path = "/Solar System/Planets/Mars", + Description = [[One of two moons of Mars.]] + } +} + +asset.onInitialize(function() + openspace.addSceneGraphNode(PhobosGlobe) +end) + +asset.onDeinitialize(function() + openspace.removeSceneGraphNode(PhobosGlobe) +end) + +asset.export(PhobosGlobe) + + +asset.meta = { + Name = "Phobos Globe", + Version = "1.0", + Description = [[ RenderableGlobe for Phobos.]], + Author = "OpenSpace Team", + URL = "http://openspaceproject.com", + License = "MIT license", + Identifiers = {"Phobos"} +} diff --git a/data/assets/scene/solarsystem/planets/mercury/default_layers.asset b/data/assets/scene/solarsystem/planets/mercury/default_layers.asset index b5b9eba9b5..3379168eec 100644 --- a/data/assets/scene/solarsystem/planets/mercury/default_layers.asset +++ b/data/assets/scene/solarsystem/planets/mercury/default_layers.asset @@ -33,13 +33,15 @@ asset.require("./layers/colorlayers/fesimap_02122015") asset.require("./layers/colorlayers/mgsimap_02122015") asset.require("./layers/colorlayers/ssimap_02122015") -asset.require("./layers/heightlayers/messenger_dem_utah") +local heightLayer = asset.require("./layers/heightlayers/messenger_dem_utah") -- Set enabled layers (temporary solution) -- @TODO: do this using a boolean that's passed to the 'asset.require' instead asset.onInitialize(function () openspace.setPropertyValueSingle("Scene.Mercury.Renderable.Layers.ColorLayers." .. colorLayer.layer.Identifier .. ".Enabled", true) + openspace.setPropertyValueSingle("Scene.Mercury.Renderable.Layers.HeightLayers." .. + heightLayer.layer.Identifier .. ".Enabled", true) end) diff --git a/data/assets/scene/solarsystem/planets/mercury/layers/heightlayers/messenger_dem_utah.asset b/data/assets/scene/solarsystem/planets/mercury/layers/heightlayers/messenger_dem_utah.asset index b2bc20cf7e..a2d24d2ee9 100644 --- a/data/assets/scene/solarsystem/planets/mercury/layers/heightlayers/messenger_dem_utah.asset +++ b/data/assets/scene/solarsystem/planets/mercury/layers/heightlayers/messenger_dem_utah.asset @@ -4,6 +4,10 @@ local layer = { Identifier = "Messenger_DEM_Utah", Name = "Messenger DEM [Utah]", FilePath = asset.localResource("messenger_dem_utah.wms"), + Settings = { + Gamma = 1.59, + Multiplier = 1.38 + }, TilePixelSize = 64 } diff --git a/data/assets/scene/solarsystem/planets/saturn/mimas/mimas.asset b/data/assets/scene/solarsystem/planets/saturn/mimas/mimas.asset index 1a950717a6..1309dc9832 100644 --- a/data/assets/scene/solarsystem/planets/saturn/mimas/mimas.asset +++ b/data/assets/scene/solarsystem/planets/saturn/mimas/mimas.asset @@ -27,7 +27,7 @@ local Mimas = { }, Renderable = { Type = "RenderableGlobe", - Radii = 198000, + Radii = { 207000, 197000, 191000 }, SegmentsPerPatch = 64, Layers = { }, Labels = { diff --git a/data/assets/scene/solarsystem/sun/sun.asset b/data/assets/scene/solarsystem/sun/sun.asset index 0c6964a694..23c35428e4 100644 --- a/data/assets/scene/solarsystem/sun/sun.asset +++ b/data/assets/scene/solarsystem/sun/sun.asset @@ -12,6 +12,7 @@ local Sun = { Layers = {}, PerformShading = false }, + ApproachFactor = 15.0, GUI = { Name = "Sun", Path = "/Solar System/Sun", diff --git a/data/profiles/default.profile b/data/profiles/default.profile index 6d3077cc1b..1eabb6ca02 100644 --- a/data/profiles/default.profile +++ b/data/profiles/default.profile @@ -27,6 +27,7 @@ ], "assets": [ "base", + "events/toggle_sun", "scene/solarsystem/planets/earth/earth", "scene/solarsystem/planets/earth/satellites/satellites" ], @@ -104,4 +105,4 @@ "major": 1, "minor": 1 } -} \ No newline at end of file +} diff --git a/data/profiles/default_full.profile b/data/profiles/default_full.profile index d2bbfc3a8c..e693cd6e9b 100644 --- a/data/profiles/default_full.profile +++ b/data/profiles/default_full.profile @@ -35,6 +35,7 @@ ], "assets": [ "base", + "events/toggle_sun", "scene/solarsystem/planets/earth/earth", "scene/solarsystem/planets/earth/satellites/satellites", "scene/solarsystem/planets/jupiter/major_moons", @@ -130,4 +131,4 @@ "major": 1, "minor": 1 } -} \ No newline at end of file +} diff --git a/include/openspace/navigation/path.h b/include/openspace/navigation/path.h index 17aa20c690..e3de95defd 100644 --- a/include/openspace/navigation/path.h +++ b/include/openspace/navigation/path.h @@ -39,10 +39,10 @@ namespace openspace::interaction { class Path { public: - enum Type { - AvoidCollision, - Linear, + enum class Type { + AvoidCollision = 0, ZoomOutOverview, + Linear, AvoidCollisionWithLookAt // @TODO (2021-08-13, emmbr) This type right now leads // to rapid rotations, but is useful in specific // scenarios, e.g. close to surfaces. Later we want to diff --git a/modules/base/rendering/grids/renderableboxgrid.cpp b/modules/base/rendering/grids/renderableboxgrid.cpp index 22ee194217..4cf68b3b9d 100644 --- a/modules/base/rendering/grids/renderableboxgrid.cpp +++ b/modules/base/rendering/grids/renderableboxgrid.cpp @@ -235,6 +235,8 @@ void RenderableBoxGrid::update(const UpdateData&) { _varray.push_back({ v7.x, v7.y, v7.z }); _varray.push_back({ v3.x, v3.y, v3.z }); + setBoundingSphere(glm::length(glm::dvec3(urb))); + glBindVertexArray(_vaoID); glBindBuffer(GL_ARRAY_BUFFER, _vBufferID); glBufferData( diff --git a/modules/base/rendering/grids/renderablegrid.cpp b/modules/base/rendering/grids/renderablegrid.cpp index 7af979a23e..0140717e70 100644 --- a/modules/base/rendering/grids/renderablegrid.cpp +++ b/modules/base/rendering/grids/renderablegrid.cpp @@ -243,6 +243,8 @@ void RenderableGrid::update(const UpdateData&) { _varray[nr++] = { halfSize.x, y1, 0.f }; } + setBoundingSphere(glm::length(glm::dvec2(halfSize))); + glBindVertexArray(_vaoID); glBindBuffer(GL_ARRAY_BUFFER, _vBufferID); glBufferData( diff --git a/modules/base/rendering/grids/renderableradialgrid.cpp b/modules/base/rendering/grids/renderableradialgrid.cpp index 67454d91b7..e260af9626 100644 --- a/modules/base/rendering/grids/renderableradialgrid.cpp +++ b/modules/base/rendering/grids/renderableradialgrid.cpp @@ -274,6 +274,8 @@ void RenderableRadialGrid::update(const UpdateData&) { } _lines.update(); + setBoundingSphere(static_cast(outerRadius)); + _gridIsDirty = false; } diff --git a/modules/base/rendering/grids/renderablesphericalgrid.cpp b/modules/base/rendering/grids/renderablesphericalgrid.cpp index 8be264d66f..059cb6dac7 100644 --- a/modules/base/rendering/grids/renderablesphericalgrid.cpp +++ b/modules/base/rendering/grids/renderablesphericalgrid.cpp @@ -103,6 +103,9 @@ RenderableSphericalGrid::RenderableSphericalGrid(const ghoul::Dictionary& dictio _lineWidth = p.lineWidth.value_or(_lineWidth); addProperty(_lineWidth); + + // Radius is always 1 + setBoundingSphere(1.0); } bool RenderableSphericalGrid::isReady() const { diff --git a/modules/digitaluniverse/rendering/renderabledumeshes.cpp b/modules/digitaluniverse/rendering/renderabledumeshes.cpp index bbd77b824e..56f1c1d350 100644 --- a/modules/digitaluniverse/rendering/renderabledumeshes.cpp +++ b/modules/digitaluniverse/rendering/renderabledumeshes.cpp @@ -460,6 +460,9 @@ bool RenderableDUMeshes::readSpeckFile() { return false; } + const float scale = static_cast(toMeter(_unit)); + double maxRadius = 0.0; + int meshIndex = 0; // The beginning of the speck file has a header that either contains comments @@ -540,23 +543,55 @@ bool RenderableDUMeshes::readSpeckFile() { // We can now read the vertices data: for (int l = 0; l < mesh.numU * mesh.numV; ++l) { std::getline(file, line); - if (line.substr(0, 1) != "}") { - std::stringstream lineData(line); - for (int i = 0; i < 7; ++i) { - GLfloat value; - lineData >> value; - bool errorReading = lineData.rdstate() & std::ifstream::failbit; - if (!errorReading) { - mesh.vertices.push_back(value); - } - else { - break; - } - } - } - else { + if (line.substr(0, 1) == "}") { break; } + + std::stringstream lineData(line); + + // Try to read three values for the position + glm::vec3 pos; + bool success = true; + for (int i = 0; i < 3; ++i) { + GLfloat value; + lineData >> value; + bool errorReading = lineData.rdstate() & std::ifstream::failbit; + if (errorReading) { + success = false; + break; + } + + GLfloat scaledValue = value * scale; + pos[i] = scaledValue; + mesh.vertices.push_back(scaledValue); + } + + if (!success) { + LERROR(fmt::format( + "Failed reading position on line {} of mesh {} in file: '{}'. " + "Stopped reading mesh data", l, meshIndex, _speckFile + )); + break; + } + + // Check if new max radius + const double r = glm::length(glm::dvec3(pos)); + maxRadius = std::max(maxRadius, r); + + // OLD CODE: + // (2022-03-23, emmbr) None of our files included texture coordinates, + // and if they would they would still not be used by the shader + //for (int i = 0; i < 7; ++i) { + // GLfloat value; + // lineData >> value; + // bool errorReading = lineData.rdstate() & std::ifstream::failbit; + // if (!errorReading) { + // mesh.vertices.push_back(value); + // } + // else { + // break; + // } + //} } std::getline(file, line); @@ -569,6 +604,8 @@ bool RenderableDUMeshes::readSpeckFile() { } } + setBoundingSphere(maxRadius); + return true; } @@ -579,12 +616,6 @@ void RenderableDUMeshes::createMeshes() { LDEBUG("Creating planes"); for (std::pair& p : _renderingMeshesMap) { - float scale = static_cast(toMeter(_unit)); - - for (GLfloat& v : p.second.vertices) { - v *= scale; - } - for (int i = 0; i < p.second.numU; ++i) { GLuint vao; glGenVertexArrays(1, &vao); @@ -605,29 +636,32 @@ void RenderableDUMeshes::createMeshes() { ); // in_position glEnableVertexAttribArray(0); - // U and V may not be given by the user - if (p.second.vertices.size() / (p.second.numU * p.second.numV) > 3) { - glVertexAttribPointer( - 0, - 3, - GL_FLOAT, - GL_FALSE, - sizeof(GLfloat) * 5, - reinterpret_cast(sizeof(GLfloat) * i * p.second.numV) - ); + // (2022-03-23, emmbr) This code was actually never used. We only read three + // values per line and di not handle any texture cooridnates, even if there + // would have been some in the file + //// U and V may not be given by the user + //if (p.second.vertices.size() / (p.second.numU * p.second.numV) > 3) { + // glVertexAttribPointer( + // 0, + // 3, + // GL_FLOAT, + // GL_FALSE, + // sizeof(GLfloat) * 5, + // reinterpret_cast(sizeof(GLfloat) * i * p.second.numV) + // ); - // texture coords - glEnableVertexAttribArray(1); - glVertexAttribPointer( - 1, - 2, - GL_FLOAT, - GL_FALSE, - sizeof(GLfloat) * 7, - reinterpret_cast(sizeof(GLfloat) * 3 * i * p.second.numV) - ); - } - else { // no U and V: + // // texture coords + // glEnableVertexAttribArray(1); + // glVertexAttribPointer( + // 1, + // 2, + // GL_FLOAT, + // GL_FALSE, + // sizeof(GLfloat) * 7, + // reinterpret_cast(sizeof(GLfloat) * 3 * i * p.second.numV) + // ); + //} + //else { // no U and V: glVertexAttribPointer( 0, 3, @@ -636,7 +670,7 @@ void RenderableDUMeshes::createMeshes() { 0, reinterpret_cast(sizeof(GLfloat) * 3 * i * p.second.numV) ); - } + //} } // Grid: we need columns diff --git a/modules/digitaluniverse/rendering/renderablepoints.cpp b/modules/digitaluniverse/rendering/renderablepoints.cpp index d94ad66e88..b1f36dd249 100644 --- a/modules/digitaluniverse/rendering/renderablepoints.cpp +++ b/modules/digitaluniverse/rendering/renderablepoints.cpp @@ -397,12 +397,17 @@ std::vector RenderablePoints::createDataSlice() { slice.reserve(4 * _dataset.entries.size()); } + double maxRadius = 0.0; + int colorIndex = 0; for (const speck::Dataset::Entry& e : _dataset.entries) { glm::dvec3 p = e.position; double scale = toMeter(_unit); p *= scale; + const double r = glm::length(p); + maxRadius = std::max(maxRadius, r); + glm::dvec4 position(p, 1.0); if (_hasColorMapFile) { @@ -423,6 +428,7 @@ std::vector RenderablePoints::createDataSlice() { 0 : colorIndex + 1; } + setBoundingSphere(maxRadius); return slice; } diff --git a/modules/galaxy/rendering/renderablegalaxy.cpp b/modules/galaxy/rendering/renderablegalaxy.cpp index 891270155b..bfe86b1871 100644 --- a/modules/galaxy/rendering/renderablegalaxy.cpp +++ b/modules/galaxy/rendering/renderablegalaxy.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -318,6 +319,10 @@ RenderableGalaxy::RenderableGalaxy(const ghoul::Dictionary& dictionary) _downScaleVolumeRendering.setVisibility(properties::Property::Visibility::Developer); addProperty(_downScaleVolumeRendering); addProperty(_numberOfRayCastingSteps); + + // Use max component instead of length, to avoid problems with taking square + // of huge value + setBoundingSphere(glm::compMax(0.5f * _volumeSize)); } void RenderableGalaxy::initialize() { diff --git a/modules/globebrowsing/globebrowsingmodule.cpp b/modules/globebrowsing/globebrowsingmodule.cpp index 774bb2fb10..b9074cda6b 100644 --- a/modules/globebrowsing/globebrowsingmodule.cpp +++ b/modules/globebrowsing/globebrowsingmodule.cpp @@ -716,7 +716,7 @@ scripting::LuaLibrary GlobeBrowsingModule::luaLibrary() const { codegen::lua::GetGeoPositionForCamera, codegen::lua::LoadWMSCapabilities, codegen::lua::RemoveWMSServer, - codegen::lua::Capabilities + codegen::lua::CapabilitiesWMS }; res.scripts = { absPath("${MODULE_GLOBEBROWSING}/scripts/layer_support.lua") diff --git a/modules/globebrowsing/globebrowsingmodule_lua.inl b/modules/globebrowsing/globebrowsingmodule_lua.inl index 045e3b8ec9..8c6a6cee94 100644 --- a/modules/globebrowsing/globebrowsingmodule_lua.inl +++ b/modules/globebrowsing/globebrowsingmodule_lua.inl @@ -437,7 +437,7 @@ getLocalPositionFromGeo(std::string globeIdentifier, double latitude, double lon * can be used in the 'FilePath' argument for a call to the 'addLayer' function to add the * value to a globe. */ -[[codegen::luawrap]] std::vector capabilities(std::string name) { +[[codegen::luawrap]] std::vector capabilitiesWMS(std::string name) { using namespace openspace; using namespace globebrowsing; diff --git a/modules/spacecraftinstruments/dashboard/dashboarditeminstruments.cpp b/modules/spacecraftinstruments/dashboard/dashboarditeminstruments.cpp index 87f549dd7b..f9f27d4931 100644 --- a/modules/spacecraftinstruments/dashboard/dashboarditeminstruments.cpp +++ b/modules/spacecraftinstruments/dashboard/dashboarditeminstruments.cpp @@ -131,8 +131,9 @@ void DashboardItemInstruments::render(glm::vec2& penPosition) { double previous = sequencer.prevCaptureTime(currentTime); double next = sequencer.nextCaptureTime(currentTime); - double remaining = sequencer.nextCaptureTime(currentTime) - currentTime; - const float t = static_cast(1.0 - remaining / (next - previous)); + double remaining = next - currentTime; + float t = static_cast(1.0 - remaining / (next - previous)); + t = std::clamp(t, 0.f, 1.f); if (remaining > 0.0) { RenderFont( diff --git a/modules/webbrowser/CMakeLists.txt b/modules/webbrowser/CMakeLists.txt index a7aabfaf0f..ac92c74dc0 100644 --- a/modules/webbrowser/CMakeLists.txt +++ b/modules/webbrowser/CMakeLists.txt @@ -168,8 +168,6 @@ if (OS_MACOSX) ADD_LOGICAL_TARGET("cef_sandbox_lib" "${CEF_SANDBOX_LIB_DEBUG}" "${CEF_SANDBOX_LIB_RELEASE}") foreach(target IN LISTS Targets) # Helper executable target. - message(STATUS "set tar : ${target}") - add_executable(${target} MACOSX_BUNDLE ${WEBBROWSER_HELPER_SOURCES}) SET_EXECUTABLE_TARGET_PROPERTIES(${target}) # add_cef_logical_target("libcef_lib" "${CEF_LIB_DEBUG}" "${CEF_LIB_RELEASE}") diff --git a/src/interaction/sessionrecording.cpp b/src/interaction/sessionrecording.cpp index 8ccd49385c..3d6ce56dc8 100644 --- a/src/interaction/sessionrecording.cpp +++ b/src/interaction/sessionrecording.cpp @@ -2155,6 +2155,7 @@ std::vector SessionRecording::playbackList() const { } } } + std::sort(fileList.begin(), fileList.end()); return fileList; } diff --git a/src/interaction/sessionrecording_lua.inl b/src/interaction/sessionrecording_lua.inl index 7b8cfbf626..66766e7e22 100644 --- a/src/interaction/sessionrecording_lua.inl +++ b/src/interaction/sessionrecording_lua.inl @@ -70,7 +70,9 @@ namespace { * value of true is given, then playback will continually loop until it is manually * stopped. */ -[[codegen::luawrap]] void startPlaybackDefault(std::string file, bool loop = false) { +[[codegen::luawrap("startPlayback")]] void startPlaybackDefault(std::string file, + bool loop = false) +{ using namespace openspace; if (file.empty()) { diff --git a/src/navigation/navigationhandler_lua.inl b/src/navigation/navigationhandler_lua.inl index 2a6107a966..05b8dfcd6d 100644 --- a/src/navigation/navigationhandler_lua.inl +++ b/src/navigation/navigationhandler_lua.inl @@ -218,8 +218,10 @@ joystickAxis(std::string joystickName, int axis) * Finds the input joystick with the given 'name' and sets the deadzone for a particular * joystick axis, which means that any input less than this value is completely ignored. */ -[[codegen::luawrap]] void setJoystickAxisDeadZone(std::string joystickName, int axis, - float deadzone) +[[codegen::luawrap("setAxisDeadZone")]] void setJoystickAxisDeadZone( + std::string joystickName, + int axis, + float deadzone) { using namespace openspace; global::navigationHandler->setJoystickAxisDeadzone(joystickName, axis, deadzone); @@ -228,7 +230,9 @@ joystickAxis(std::string joystickName, int axis) /** * Returns the deadzone for the desired axis of the provided joystick. */ -[[codegen::luawrap]] float joystickAxisDeadzone(std::string joystickName, int axis) { +[[codegen::luawrap("axisDeadzone")]] float joystickAxisDeadzone(std::string joystickName, + int axis) +{ float deadzone = openspace::global::navigationHandler->joystickAxisDeadzone( joystickName, axis diff --git a/src/navigation/path.cpp b/src/navigation/path.cpp index 1bd4eb3b30..1c991fb2c8 100644 --- a/src/navigation/path.cpp +++ b/src/navigation/path.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -533,6 +534,34 @@ Waypoint computeWaypointFromNodeInfo(const NodeInfo& info, const Waypoint& start return Waypoint(targetPos, targetRot, info.identifier); } +void checkVisibilityAndShowMessage(const SceneGraphNode* node) { + auto isEnabled = [](const Renderable* r) { + std::any propertyValueAny = r->property("Enabled")->get(); + return std::any_cast(propertyValueAny); + }; + + // Show some info related to the visiblity of the object + const Renderable* renderable = node->renderable(); + if (!renderable) { + // Check if any of the children are visible, if it has children + bool foundVisible = false; + for (const SceneGraphNode* child : node->children()) { + const Renderable* cr = child->renderable(); + if (cr && isEnabled(cr)) { + foundVisible = true; + break; + } + } + + if (!foundVisible) { + LINFO("Creating path to a node without a renderable or visible child nodes"); + } + } + else if (!isEnabled(renderable)) { + LINFO("Creating path to disabled object"); + } +} + Path createPathFromDictionary(const ghoul::Dictionary& dictionary, std::optional forceType) { @@ -606,6 +635,8 @@ Path createPathFromDictionary(const ghoul::Dictionary& dictionary, throw PathCurve::TooShortPathError("Path too short!"); } + checkVisibilityAndShowMessage(waypointToAdd.node()); + try { return Path(startPoint, waypointToAdd, type, duration); } diff --git a/src/navigation/pathnavigator.cpp b/src/navigation/pathnavigator.cpp index 98e74dca57..81148164b1 100644 --- a/src/navigation/pathnavigator.cpp +++ b/src/navigation/pathnavigator.cpp @@ -123,10 +123,10 @@ PathNavigator::PathNavigator() , _relevantNodeTags(RelevantNodeTagsInfo) { _defaultPathType.addOptions({ - { Path::Type::AvoidCollision, "AvoidCollision" }, - { Path::Type::ZoomOutOverview, "ZoomOutOverview"}, - { Path::Type::Linear, "Linear" }, - { Path::Type::AvoidCollisionWithLookAt, "AvoidCollisionWithLookAt"} + { static_cast(Path::Type::AvoidCollision), "AvoidCollision" }, + { static_cast(Path::Type::ZoomOutOverview), "ZoomOutOverview" }, + { static_cast(Path::Type::Linear), "Linear" }, + { static_cast(Path::Type::AvoidCollisionWithLookAt), "AvoidCollisionWithLookAt"} }); addProperty(_defaultPathType); @@ -343,8 +343,7 @@ void PathNavigator::continuePath() { } Path::Type PathNavigator::defaultPathType() const { - const int pathType = _defaultPathType; - return Path::Type(pathType); + return static_cast(_defaultPathType.value()); } double PathNavigator::minValidBoundingSphere() const { diff --git a/src/scripting/systemcapabilitiesbinding_lua.inl b/src/scripting/systemcapabilitiesbinding_lua.inl index 44d676cfa5..aa34390ceb 100644 --- a/src/scripting/systemcapabilitiesbinding_lua.inl +++ b/src/scripting/systemcapabilitiesbinding_lua.inl @@ -95,7 +95,7 @@ namespace { } // Returns the L2 associativity. -[[codegen::luawrap]] int l2Associativity() { +[[codegen::luawrap("L2Associativity")]] int l2Associativity() { int assoc = static_cast(CpuCap.L2Associativity()); return assoc; } diff --git a/src/util/time.cpp b/src/util/time.cpp index 8bb5d1ed3e..a0a7fe8d2e 100644 --- a/src/util/time.cpp +++ b/src/util/time.cpp @@ -164,6 +164,7 @@ scripting::LuaLibrary Time::luaLibrary() { codegen::lua::InterpolateTimeRelative, codegen::lua::CurrentTime, codegen::lua::CurrentTimeUTC, + codegen::lua::CurrentTimeSpice, codegen::lua::CurrentWallTime, codegen::lua::CurrentApplicationTime, codegen::lua::AdvancedTime diff --git a/src/util/time_lua.inl b/src/util/time_lua.inl index b19dc1b7f2..ae68fc9c6f 100644 --- a/src/util/time_lua.inl +++ b/src/util/time_lua.inl @@ -273,7 +273,16 @@ namespace { /** * Returns the current time as an ISO 8601 date string (YYYY-MM-DDTHH:MN:SS). */ -[[codegen::luawrap]] std::string currentTimeUTC() { +[[codegen::luawrap("UTC")]] std::string currentTimeUTC() { + return std::string(openspace::global::timeManager->time().ISO8601()); +} + + +/** + * Returns the current time as an date string of the form + * (YYYY MON DDTHR:MN:SC.### ::RND) as returned by SPICE. + */ +[[codegen::luawrap("SPICE")]] std::string currentTimeSpice() { return std::string(openspace::global::timeManager->time().UTC()); } diff --git a/support/coding/codegen b/support/coding/codegen index c6178634f7..b2c8623b81 160000 --- a/support/coding/codegen +++ b/support/coding/codegen @@ -1 +1 @@ -Subproject commit c6178634f7e6bcdc62a67af08f315ee384d0227d +Subproject commit b2c8623b81005283666ee319b3a972dd2b03c7a0