From f3583224c23c6d1b8b90e6a3644caaac1962352d Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Mon, 24 Mar 2025 14:51:58 +0100 Subject: [PATCH 01/17] Correctly store the last profile run even if it is in a subfolder (closes #3570) --- .../ext/launcher/include/launcherwindow.h | 10 ++--- apps/OpenSpace/main.cpp | 39 +++++++++++++++++-- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/apps/OpenSpace/ext/launcher/include/launcherwindow.h b/apps/OpenSpace/ext/launcher/include/launcherwindow.h index cbe15c696f..c8b63783de 100644 --- a/apps/OpenSpace/ext/launcher/include/launcherwindow.h +++ b/apps/OpenSpace/ext/launcher/include/launcherwindow.h @@ -62,18 +62,16 @@ public: bool wasLaunchSelected() const; /** - * Returns the selected profile name when launcher window closed. + * Returns the selected profile name when the launcher window closed. * - * \return The name of selected profile (this is only the name without file extension - * and without path) + * \return The path to the selected profile */ std::string selectedProfile() const; /** - * Returns the selected sgct window configuration when launcher window closed. + * Returns the selected SGCT window configuration when the launcher window closed. * - * \return The name of selected profile (this is only the name without file extension - * and without path) + * \return The path to the selected profile */ std::string selectedWindowConfig() const; diff --git a/apps/OpenSpace/main.cpp b/apps/OpenSpace/main.cpp index d403c4fe6a..f6f7e7963f 100644 --- a/apps/OpenSpace/main.cpp +++ b/apps/OpenSpace/main.cpp @@ -1415,9 +1415,42 @@ int main(int argc, char* argv[]) { openspace::Settings settings = loadSettings(); settings.hasStartedBefore = true; - const std::filesystem::path p = global::configuration->profile; - const std::filesystem::path reducedName = p.filename().replace_extension(); - settings.profile = reducedName.string(); + const std::filesystem::path profile = global::configuration->profile; + auto isSubDirectory = [](std::filesystem::path p, std::filesystem::path root) { + while (p != p.parent_path()) { + if (p == root) { + return true; + } + p = p.parent_path(); + } + return false; + }; + + const bool isDefaultProfile = isSubDirectory(profile, absPath("${PROFILES}")); + const bool isUserProfile = isSubDirectory(profile, absPath("${USER_PROFILES}")); + + if (isDefaultProfile) { + std::filesystem::path p = std::filesystem::relative( + profile, + absPath("${PROFILES}") + ); + p.replace_extension(); + settings.profile = p.string(); + } + else if (isUserProfile) { + std::filesystem::path p = std::filesystem::relative( + profile, + absPath("${USER_PROFILES}") + ); + p.replace_extension(); + settings.profile = p.string(); + } + else { + LWARNING( + "Cannot save remembered profile when starting a profile that is not in " + "the data/profiles or user/data/profiles folder." + ); + } settings.configuration = isGeneratedWindowConfig ? "" : global::configuration->windowConfiguration; From b1a8c82a1eb78fd1cb368f5328c2aea043bcbae1 Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Mon, 24 Mar 2025 15:10:34 +0100 Subject: [PATCH 02/17] Use central subdirectory function. Fix compile issue with Tracy --- apps/OpenSpace/main.cpp | 19 ++++++++----------- ext/ghoul | 2 +- src/scene/scene.cpp | 1 + 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/apps/OpenSpace/main.cpp b/apps/OpenSpace/main.cpp index f6f7e7963f..ba64091dbb 100644 --- a/apps/OpenSpace/main.cpp +++ b/apps/OpenSpace/main.cpp @@ -1416,18 +1416,15 @@ int main(int argc, char* argv[]) { settings.hasStartedBefore = true; const std::filesystem::path profile = global::configuration->profile; - auto isSubDirectory = [](std::filesystem::path p, std::filesystem::path root) { - while (p != p.parent_path()) { - if (p == root) { - return true; - } - p = p.parent_path(); - } - return false; - }; - const bool isDefaultProfile = isSubDirectory(profile, absPath("${PROFILES}")); - const bool isUserProfile = isSubDirectory(profile, absPath("${USER_PROFILES}")); + const bool isDefaultProfile = ghoul::filesystem::isSubdirectory( + profile, + absPath("${PROFILES}") + ); + const bool isUserProfile = ghoul::filesystem::isSubdirectory( + profile, + absPath("${USER_PROFILES}") + ); if (isDefaultProfile) { std::filesystem::path p = std::filesystem::relative( diff --git a/ext/ghoul b/ext/ghoul index 1ee8a47a1c..2972e7b963 160000 --- a/ext/ghoul +++ b/ext/ghoul @@ -1 +1 @@ -Subproject commit 1ee8a47a1cb861efd4d446e34142cd45a53819e6 +Subproject commit 2972e7b963abe1a0ddd57efd8aaf28840a8da37e diff --git a/src/scene/scene.cpp b/src/scene/scene.cpp index 36f60772ce..b2108d30a7 100644 --- a/src/scene/scene.cpp +++ b/src/scene/scene.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include From e93ad7910f2bc93d884aab6b8a3d3535538b75e9 Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Mon, 24 Mar 2025 17:12:47 +0100 Subject: [PATCH 03/17] Rename propertyowner and gui to geographic overlays (#2883) Breaking change --- modules/globebrowsing/src/geojson/geojsonmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/globebrowsing/src/geojson/geojsonmanager.cpp b/modules/globebrowsing/src/geojson/geojsonmanager.cpp index b3fcda6e70..a615c3f71e 100644 --- a/modules/globebrowsing/src/geojson/geojsonmanager.cpp +++ b/modules/globebrowsing/src/geojson/geojsonmanager.cpp @@ -37,7 +37,7 @@ namespace { namespace openspace::globebrowsing { GeoJsonManager::GeoJsonManager() - : properties::PropertyOwner({ "GeoJson", "Geometry Overlays" }) + : properties::PropertyOwner({ "GeographicOverlays", "Geographic Overlays" }) {} void GeoJsonManager::initialize(RenderableGlobe* globe) { From ce56e2d1189e0652c78e16abb84a1b34482044b6 Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Mon, 31 Mar 2025 11:02:18 +0200 Subject: [PATCH 04/17] Add WMS layer for Titan HiSAR map (#3565) --- ..._sar_hisar_global_mosaic_351m_sweden.asset | 36 +++++++++++++++++++ ...ni_sar_hisar_global_mosaic_351m_sweden.wms | 20 +++++++++++ 2 files changed, 56 insertions(+) create mode 100644 data/assets/scene/solarsystem/planets/saturn/titan/layers/colorlayers/cassini_sar_hisar_global_mosaic_351m_sweden.asset create mode 100644 data/assets/scene/solarsystem/planets/saturn/titan/layers/colorlayers/cassini_sar_hisar_global_mosaic_351m_sweden.wms diff --git a/data/assets/scene/solarsystem/planets/saturn/titan/layers/colorlayers/cassini_sar_hisar_global_mosaic_351m_sweden.asset b/data/assets/scene/solarsystem/planets/saturn/titan/layers/colorlayers/cassini_sar_hisar_global_mosaic_351m_sweden.asset new file mode 100644 index 0000000000..84df419cd5 --- /dev/null +++ b/data/assets/scene/solarsystem/planets/saturn/titan/layers/colorlayers/cassini_sar_hisar_global_mosaic_351m_sweden.asset @@ -0,0 +1,36 @@ +local globe = asset.require("../../titan") + + + +local Layer = { + Identifier = "Cassini_SAR_HiSAR_Global_Mosaic_351m", + Name = "Cassini SAR - HiSAR Global Mosaic (351m)", + Enabled = asset.enabled, + ZIndex = 20, + FilePath = asset.resource("cassini_sar_hisar_global_mosaic_351m_sweden.wms"), + Description = [[This global map of Titan is a preliminary product showing coverage from + Synthetic Aperture Radar (SAR) and High Altitude Synthetic Aperture Radar (HiSAR) + images at a pixel resolution of 351 meters per pixel (m). This mosaic merges Cassini + swaths through flyby T104 into a single mosaic. (Description from URL)]] +} + + +asset.onInitialize(function() + openspace.globebrowsing.addLayer(globe.Titan.Identifier, "ColorLayers", Layer) +end) + +asset.onDeinitialize(function() + openspace.globebrowsing.deleteLayer(globe.Titan.Identifier, "ColorLayers", Layer) +end) + +asset.export("layer", Layer) + + + +asset.meta = { + Name = "Titan Cassini SAR - HiSAR Global Mosaic 351m", + Description = "Cassini global synthetic aperture radar image layer for Titan", + Author = "USGS", + URL = "https://astrogeology.usgs.gov/search/map/titan_cassini_sar_hisar_global_mosaic_351m", + License = "NASA/PDS" +} diff --git a/data/assets/scene/solarsystem/planets/saturn/titan/layers/colorlayers/cassini_sar_hisar_global_mosaic_351m_sweden.wms b/data/assets/scene/solarsystem/planets/saturn/titan/layers/colorlayers/cassini_sar_hisar_global_mosaic_351m_sweden.wms new file mode 100644 index 0000000000..2eddf8c8a3 --- /dev/null +++ b/data/assets/scene/solarsystem/planets/saturn/titan/layers/colorlayers/cassini_sar_hisar_global_mosaic_351m_sweden.wms @@ -0,0 +1,20 @@ + + + http://wms.itn.liu.se/Titan/HiSAR_Global_Mosaic_351m/tile/${z}/${y}/${x} + + + -180.0 + 90.0 + 180.0 + -90.0 + 46080 + 23040 + 7 + top + + EPSG:4326 + 512 + 512 + 1 + 10 + From 9f7d6e9044b98fd5582d21cbfc581cb80ac114a1 Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Mon, 31 Mar 2025 11:02:44 +0200 Subject: [PATCH 05/17] Add tilt and window blitting options to the Launcher (#3568) --- .../launcher/include/sgctedit/windowcontrol.h | 40 ++-- .../src/sgctedit/displaywindowunion.cpp | 10 +- .../launcher/src/sgctedit/windowcontrol.cpp | 201 +++++++++++++----- 3 files changed, 184 insertions(+), 67 deletions(-) diff --git a/apps/OpenSpace/ext/launcher/include/sgctedit/windowcontrol.h b/apps/OpenSpace/ext/launcher/include/sgctedit/windowcontrol.h index 4fe3a35baa..a60b0fe86f 100644 --- a/apps/OpenSpace/ext/launcher/include/sgctedit/windowcontrol.h +++ b/apps/OpenSpace/ext/launcher/include/sgctedit/windowcontrol.h @@ -127,9 +127,10 @@ public: * * \param quality The value for number of vertical lines of resolution. This will be * compared against the QualityValues array in order to set the correct - * combobox index + * combobox index + * \tilt The tilt of the fisheye in degrees */ - void setProjectionFisheye(int quality); + void setProjectionFisheye(int quality, float tilt); /** * Sets the window's projection type to spherical mirror, with the accompanying @@ -162,6 +163,21 @@ public: */ void setProjectionEquirectangular(int quality); + /** + * Sets the window's projection type to blitting the contents of another window. + * + * \param windowBlitId The id of the window from which to blit + */ + void setProjectionBlit(int windowBlitId); + + /** + * This function must be called by users of this class whenever the total number of + * windows has changed. + * + * \param newWindowCount the number of windows after the change + */ + void updateWindowCount(int newWindowCount); + signals: void windowChanged(int monitorIndex, int windowIndex, const QRectF& newDimensions); @@ -171,6 +187,7 @@ private: QWidget* createSphericalMirrorWidget(); QWidget* createCylindricalWidget(); QWidget* createEquirectangularWidget(); + QWidget* createBlitWidget(); void onSizeXChanged(int newValue); void onSizeYChanged(int newValue); @@ -209,44 +226,39 @@ private: struct { QWidget* widget = nullptr; - QLabel* labelInfo = nullptr; QDoubleSpinBox* fovH = nullptr; QDoubleSpinBox* fovV = nullptr; - QLabel* labelFovH = nullptr; - QLabel* labelFovV = nullptr; QPushButton* buttonLockFov = nullptr; } _planar; struct { QWidget* widget = nullptr; - QLabel* labelInfo = nullptr; QComboBox* quality = nullptr; - QLabel* labelQuality = nullptr; + QDoubleSpinBox* tilt = nullptr; } _fisheye; struct { QWidget* widget = nullptr; - QLabel* labelInfo = nullptr; QComboBox* quality = nullptr; - QLabel* labelQuality = nullptr; } _sphericalMirror; struct { QWidget* widget = nullptr; - QLabel* labelInfo = nullptr; QComboBox* quality = nullptr; - QLabel* labelQuality = nullptr; QDoubleSpinBox* heightOffset = nullptr; - QLabel* labelHeightOffset = nullptr; } _cylindrical; struct { QWidget* widget = nullptr; - QLabel* labelInfo = nullptr; QComboBox* quality = nullptr; - QLabel* labelQuality = nullptr; } _equirectangular; + struct { + QWidget* widget = nullptr; + QComboBox* windowId = nullptr; + QLabel* unavailable = nullptr; + } _blit; + const QIcon _lockIcon; const QIcon _unlockIcon; }; diff --git a/apps/OpenSpace/ext/launcher/src/sgctedit/displaywindowunion.cpp b/apps/OpenSpace/ext/launcher/src/sgctedit/displaywindowunion.cpp index 62ea7a1c09..5d69a00042 100644 --- a/apps/OpenSpace/ext/launcher/src/sgctedit/displaywindowunion.cpp +++ b/apps/OpenSpace/ext/launcher/src/sgctedit/displaywindowunion.cpp @@ -194,7 +194,7 @@ void DisplayWindowUnion::initialize(const std::vector& monitorSizeList, }, [&](const sgct::config::FisheyeProjection& p) { if (p.quality.has_value()) { - wCtrl->setProjectionFisheye(*p.quality); + wCtrl->setProjectionFisheye(*p.quality, p.tilt.value_or(0.f)); } }, [&](const sgct::config::PlanarProjection& p) { @@ -208,7 +208,12 @@ void DisplayWindowUnion::initialize(const std::vector& monitorSizeList, wCtrl->setProjectionSphericalMirror(*p.quality); } }, - [&](const sgct::config::NoProjection&) {}, + [&](const sgct::config::NoProjection&) { + // We can only generate blitting when there is no projection selected. + if (w.blitWindowId.has_value()) { + wCtrl->setProjectionBlit(*w.blitWindowId); + } + }, [&](const sgct::config::ProjectionPlane&) {}, [&](const sgct::config::CubemapProjection&) {}, }, @@ -251,6 +256,7 @@ void DisplayWindowUnion::updateWindows() { _addWindowButton->setEnabled(_nWindowsDisplayed != _windowControls.size()); for (WindowControl* w : _windowControls) { w->showWindowLabel(_nWindowsDisplayed > 1); + w->updateWindowCount(_nWindowsDisplayed); } emit nWindowsChanged(_nWindowsDisplayed); diff --git a/apps/OpenSpace/ext/launcher/src/sgctedit/windowcontrol.cpp b/apps/OpenSpace/ext/launcher/src/sgctedit/windowcontrol.cpp index 96fa09ded0..0a15189d00 100644 --- a/apps/OpenSpace/ext/launcher/src/sgctedit/windowcontrol.cpp +++ b/apps/OpenSpace/ext/launcher/src/sgctedit/windowcontrol.cpp @@ -64,7 +64,8 @@ namespace { Fisheye, SphericalMirror, Cylindrical, - Equirectangular + Equirectangular, + Blit }; constexpr int LineEditWidthFixedWindowSize = 95; @@ -378,7 +379,7 @@ WindowControl::WindowControl(int monitorIndex, int windowIndex, _projectionType = new QComboBox; _projectionType->addItems({ "Planar Projection", "Fisheye", "Spherical Mirror Projection", - "Cylindrical Projection", "Equirectangular Projection" + "Cylindrical Projection", "Equirectangular Projection", "Copy Window Contents" }); _projectionType->setToolTip("Select from the supported window projection types"); _projectionType->setCurrentIndex(0); @@ -403,6 +404,9 @@ WindowControl::WindowControl(int monitorIndex, int windowIndex, _equirectangular.widget = createEquirectangularWidget(); projectionLayout->addWidget(_equirectangular.widget); + _blit.widget = createBlitWidget(); + projectionLayout->addWidget(_blit.widget); + // We need to trigger this once to ensure that all of the defaults are correct onProjectionChanged(0); @@ -424,21 +428,21 @@ QWidget* WindowControl::createPlanarWidget() { QGridLayout* layout = new QGridLayout(widget); layout->setColumnStretch(1, 1); - _planar.labelInfo = new QLabel( + QLabel* labelInfo = new QLabel( "This projection type is the 'regular' projection with a horizontal and a " "vertical field of view, given in degrees. The wider the field of view, the " "more content is shown at the same time, but everything becomes smaller. Very " "large values will introduce distortions on the corners." ); - _planar.labelInfo->setObjectName("info"); - _planar.labelInfo->setWordWrap(true); - layout->addWidget(_planar.labelInfo, 0, 0, 1, 3); + labelInfo->setObjectName("info"); + labelInfo->setWordWrap(true); + layout->addWidget(labelInfo, 0, 0, 1, 3); - _planar.labelFovH = new QLabel("Horizontal FOV"); + QLabel* labelFovH = new QLabel("Horizontal FOV"); const QString hfovTip = "The total horizontal field of view of the viewport (degrees)"; - _planar.labelFovH->setToolTip(hfovTip); - layout->addWidget(_planar.labelFovH, 1, 0); + labelFovH->setToolTip(hfovTip); + layout->addWidget(labelFovH, 1, 0); _planar.fovH = new QDoubleSpinBox; _planar.fovH->setMinimum(FovEpsilon); @@ -452,11 +456,11 @@ QWidget* WindowControl::createPlanarWidget() { ); layout->addWidget(_planar.fovH, 1, 1); - _planar.labelFovV = new QLabel("Vertical FOV"); + QLabel* labelFovV = new QLabel("Vertical FOV"); const QString vfovTip = "The total vertical field of view of the viewport (degrees). " "Internally,\nthe values for 'up' & 'down' will each be half this value"; - _planar.labelFovV->setToolTip(vfovTip); - layout->addWidget(_planar.labelFovV, 2, 0); + labelFovV->setToolTip(vfovTip); + layout->addWidget(labelFovV, 2, 0); _planar.fovV = new QDoubleSpinBox; _planar.fovV->setMinimum(FovEpsilon); @@ -493,29 +497,29 @@ QWidget* WindowControl::createFisheyeWidget() { // *------------*-----------* // | { Informational text } | Row 0 // | Quality * [DDDDD>] | Row 1 - // | [] Spout Output | Row 2 + // | Tilt * [oooooo] | Row 2 // *------------*-----------* QWidget* widget = new QWidget; QGridLayout* layout = new QGridLayout(widget); layout->setColumnStretch(1, 1); - _fisheye.labelInfo = new QLabel( + QLabel* labelInfo = new QLabel( "This projection provides a rendering in a format that is suitable for " "planetariums and other immersive environments. A field-of-view of 180 degrees " "is presented as a circular image in the center of the screen. For this " "projection a square window is suggested, but not necessary." ); - _fisheye.labelInfo->setObjectName("info"); - _fisheye.labelInfo->setWordWrap(true); - layout->addWidget(_fisheye.labelInfo, 0, 0, 1, 2); + labelInfo->setObjectName("info"); + labelInfo->setWordWrap(true); + layout->addWidget(labelInfo, 0, 0, 1, 2); - _fisheye.labelQuality = new QLabel("Quality"); + QLabel* labelQuality = new QLabel("Quality"); const 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"; - _fisheye.labelQuality->setToolTip(qualityTip); - layout->addWidget(_fisheye.labelQuality, 1, 0); + "expense of increased rendering times."; + labelQuality->setToolTip(qualityTip); + layout->addWidget(labelQuality, 1, 0); _fisheye.quality = new QComboBox; _fisheye.quality->addItems(qualityList()); @@ -523,6 +527,19 @@ QWidget* WindowControl::createFisheyeWidget() { _fisheye.quality->setCurrentIndex(2); layout->addWidget(_fisheye.quality, 1, 1); + QLabel* labelTilt = new QLabel("Tilt"); + const QString tiltTip = "Determines the tilt (in degrees) of the fisheye rendering. " + "Changing this value will cause the entire rendering to be tilted by the set " + "number of degrees."; + labelTilt->setToolTip(tiltTip); + layout->addWidget(labelTilt, 2, 0); + + _fisheye.tilt = new QDoubleSpinBox; + _fisheye.tilt->setToolTip(tiltTip); + _fisheye.tilt->setMinimum(-180.0); + _fisheye.tilt->setMaximum(180.0); + layout->addWidget(_fisheye.tilt, 2, 1); + return widget; } @@ -536,22 +553,22 @@ QWidget* WindowControl::createSphericalMirrorWidget() { QGridLayout* layout = new QGridLayout(widget); layout->setColumnStretch(1, 1); - _sphericalMirror.labelInfo = new QLabel( + QLabel* labelInfo = new QLabel( "This projection is rendering a image suite for use with a spherical mirror " "projection as described by Paul Bourke (http://paulbourke.net/dome/mirrordome/) " "and which is a low-cost yet effective way to provide content for a sphericalal " "display surface using a regular projector." ); - _sphericalMirror.labelInfo->setObjectName("info"); - _sphericalMirror.labelInfo->setWordWrap(true); - layout->addWidget(_sphericalMirror.labelInfo, 0, 0, 1, 2); + labelInfo->setObjectName("info"); + labelInfo->setWordWrap(true); + layout->addWidget(labelInfo, 0, 0, 1, 2); - _sphericalMirror.labelQuality = new QLabel("Quality"); + QLabel* labelQuality = new QLabel("Quality"); const 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"; - _sphericalMirror.labelQuality->setToolTip(qualityTip); - layout->addWidget(_sphericalMirror.labelQuality, 1, 0); + labelQuality->setToolTip(qualityTip); + layout->addWidget(labelQuality, 1, 0); _sphericalMirror.quality = new QComboBox; _sphericalMirror.quality->addItems(qualityList()); @@ -573,22 +590,22 @@ QWidget* WindowControl::createCylindricalWidget() { QGridLayout* layout = new QGridLayout(widget); layout->setColumnStretch(1, 1); - _cylindrical.labelInfo = new QLabel( + QLabel* labelInfo = new QLabel( "This projection type provides a cylindrical rendering that covers 360 degrees " "around the camera, which can be useful in immersive environments that are not " "spherical, but where, for example, all walls of a room are covered with " "projectors." ); - _cylindrical.labelInfo->setObjectName("info"); - _cylindrical.labelInfo->setWordWrap(true); - layout->addWidget(_cylindrical.labelInfo, 0, 0, 1, 2); + labelInfo->setObjectName("info"); + labelInfo->setWordWrap(true); + layout->addWidget(labelInfo, 0, 0, 1, 2); - _cylindrical.labelQuality = new QLabel("Quality"); + QLabel* labelQuality = new QLabel("Quality"); const 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"; - _cylindrical.labelQuality->setToolTip(qualityTip); - layout->addWidget(_cylindrical.labelQuality, 1, 0); + labelQuality->setToolTip(qualityTip); + layout->addWidget(labelQuality, 1, 0); _cylindrical.quality = new QComboBox; _cylindrical.quality->addItems(qualityList()); @@ -596,13 +613,13 @@ QWidget* WindowControl::createCylindricalWidget() { _cylindrical.quality->setCurrentIndex(2); layout->addWidget(_cylindrical.quality, 1, 1); - _cylindrical.labelHeightOffset = new QLabel("Height Offset"); + QLabel* labelHeightOffset = new QLabel("Height Offset"); const 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\n" "a 'standard' cylindrical projection"; - _cylindrical.labelHeightOffset->setToolTip(heightTip); - layout->addWidget(_cylindrical.labelHeightOffset, 2, 0); + labelHeightOffset->setToolTip(heightTip); + layout->addWidget(labelHeightOffset, 2, 0); _cylindrical.heightOffset = new QDoubleSpinBox; _cylindrical.heightOffset->setMinimum(-1000000.0); @@ -611,7 +628,6 @@ QWidget* WindowControl::createCylindricalWidget() { _cylindrical.heightOffset->setToolTip(heightTip); layout->addWidget(_cylindrical.heightOffset, 2, 1); - return widget; } @@ -620,28 +636,27 @@ QWidget* WindowControl::createEquirectangularWidget() { // *------------*-----------* // | { Informational text } | Row 0 // | Quality * [DDDDD>] | Row 1 - // | [] Spout Output | Row 2 // *------------*-----------* QWidget* widget = new QWidget; QGridLayout* layout = new QGridLayout(widget); layout->setColumnStretch(1, 1); - _equirectangular.labelInfo = new QLabel( + QLabel* labelInfo = new QLabel( "This projection provides the rendering as an image in equirectangular " "projection, which is a common display type for 360 surround video. When " "uploading a video in equirectangular projection to YouTube, for example, it " "will use it as a 360 video." ); - _equirectangular.labelInfo->setObjectName("info"); - _equirectangular.labelInfo->setWordWrap(true); - layout->addWidget(_equirectangular.labelInfo, 0, 0, 1, 2); + labelInfo->setObjectName("info"); + labelInfo->setWordWrap(true); + layout->addWidget(labelInfo, 0, 0, 1, 2); - _equirectangular.labelQuality = new QLabel("Quality"); + QLabel* labelQuality = new QLabel("Quality"); const 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"; - _equirectangular.labelQuality->setToolTip(qualityTip); - layout->addWidget(_equirectangular.labelQuality, 1, 0); + labelQuality->setToolTip(qualityTip); + layout->addWidget(labelQuality, 1, 0); _equirectangular.quality = new QComboBox; _equirectangular.quality->addItems(qualityList()); @@ -652,6 +667,49 @@ QWidget* WindowControl::createEquirectangularWidget() { return widget; } +QWidget* WindowControl::createBlitWidget() { + // Column 0 Column 1 + // *------------*-----------* + // | { Informational text } | Row 0 + // | Window ID * [DDDDD>] | Row 1 + // | { Unavailability } | Row 2 + // *------------*-----------* + + QWidget* widget = new QWidget; + QGridLayout* layout = new QGridLayout(widget); + layout->setColumnStretch(1, 1); + + QLabel* labelInfo = new QLabel( + "This projection type will reuse the contents of another window. This can be " + "useful for GUI windows that should show the 3D scene, but not incur the cost of " + "rendering the scene twice. Note that the contents of the rendering will be " + "copied in their entirety, which means that if the rendering windows have " + "different aspect ratios, the image in the receiving window will be stretched." + ); + labelInfo->setObjectName("info"); + labelInfo->setWordWrap(true); + layout->addWidget(labelInfo, 0, 0, 1, 2); + + QLabel* labelBlitId = new QLabel("Window ID"); + const QString blitTip = "Determines the window from which to copy the contents."; + labelBlitId->setToolTip(blitTip); + layout->addWidget(labelBlitId, 1, 0); + + _blit.windowId = new QComboBox; + _blit.windowId->setToolTip(blitTip); + layout->addWidget(_blit.windowId, 1, 1); + + _blit.unavailable = new QLabel( + "It is only possible to copy the contents of another window if at least two " + "windows have been created. Add a second window before selecting this projection " + "type." + ); + _blit.unavailable->setWordWrap(true); + layout->addWidget(_blit.unavailable, 2, 0, 1, 2); + + return widget; +} + void WindowControl::resetToDefaults() { // // Determine ideal window sizes @@ -684,9 +742,11 @@ void WindowControl::resetToDefaults() { _planar.fovV->setValue(DefaultFovShortEdge); _cylindrical.heightOffset->setValue(DefaultHeightOffset); _fisheye.quality->setCurrentIndex(2); + _fisheye.tilt->setValue(0.0); _sphericalMirror.quality->setCurrentIndex(2); _cylindrical.quality->setCurrentIndex(2); _equirectangular.quality->setCurrentIndex(2); + _blit.windowId->setCurrentIndex(0); emit windowChanged(_monitorIndexDefault, _windowIndex, _windowDimensions); } @@ -736,6 +796,7 @@ void WindowControl::generateWindowInformation(sgct::config::Window& window) cons ); window.draw2D = _render2D->isChecked(); window.draw3D = _render3D->isChecked(); + window.isDecorated = _windowDecoration->isChecked(); if (_spoutOutput->isChecked()) { window.spout = sgct::config::Window::Spout{ @@ -746,6 +807,8 @@ void WindowControl::generateWindowInformation(sgct::config::Window& window) cons window.name = _windowName->text().toStdString(); } + window.viewports.clear(); + // The rest of this function is just specifying the rendering, which we can skip if we // don't want to render 3D anyway if (!window.draw3D) { @@ -762,7 +825,7 @@ void WindowControl::generateWindowInformation(sgct::config::Window& window) cons vp.projection = sgct::config::FisheyeProjection { .fov = 180.f, .quality = Quality[_fisheye.quality->currentIndex()].first, - .tilt = 0.f + .tilt = static_cast(_fisheye.tilt->value()) }; break; case ProjectionIndices::SphericalMirror: @@ -781,6 +844,16 @@ void WindowControl::generateWindowInformation(sgct::config::Window& window) cons .quality = Quality[_equirectangular.quality->currentIndex()].first }; break; + case ProjectionIndices::Blit: + // We have to subtract here as SGCT uses 0-indexing, but we present it to the + // user as 1-indexing + window.blitWindowId = _blit.windowId->currentText().toInt() - 1; + window.draw3D = false; + + // We are falling through the planar value on purpose as for a variety of + // reasons requires a projection to be defined even when we are blitting the + // contents of another window. + [[fallthrough]]; case ProjectionIndices::Planar: { double fovH = _planar.fovH->value(); @@ -799,8 +872,6 @@ void WindowControl::generateWindowInformation(sgct::config::Window& window) cons break; } } - - window.viewports.clear(); window.viewports.push_back(vp); } @@ -810,8 +881,9 @@ void WindowControl::setProjectionPlanar(float hfov, float vfov) { _projectionType->setCurrentIndex(static_cast(ProjectionIndices::Planar)); } -void WindowControl::setProjectionFisheye(int quality) { +void WindowControl::setProjectionFisheye(int quality, float tilt) { _fisheye.quality->setCurrentIndex(indexForQuality(quality)); + _fisheye.tilt->setValue(tilt); _projectionType->setCurrentIndex(static_cast(ProjectionIndices::Fisheye)); } @@ -835,6 +907,32 @@ void WindowControl::setProjectionEquirectangular(int quality) { ); } +void WindowControl::setProjectionBlit(int windowBlitId) { + // We add 1 here as SGCT uses a 0-indexing for the window idx, but we present it to + // the user as a 1-indexing + int idx = _blit.windowId->findText(QString::number(windowBlitId + 1)); + ghoul_assert(idx != -1, "Could not find window blit id"); + _blit.windowId->setCurrentIndex(idx); + _projectionType->setCurrentIndex( + static_cast(ProjectionIndices::Blit) + ); +} + +void WindowControl::updateWindowCount(int newWindowCount) { + QString currentIdx = _blit.windowId->currentText(); + _blit.windowId->clear(); + for (int idx = 0; idx < newWindowCount; idx++) { + if (idx == _windowIndex) { + continue; + } + _blit.windowId->addItem(QString::number(idx + 1)); + } + _blit.windowId->setCurrentText(currentIdx); + + // Set the correct visibility + _blit.unavailable->setVisible(newWindowCount == 1); +} + void WindowControl::onSizeXChanged(int newValue) { _windowDimensions.setWidth(newValue); if (_aspectRatioLocked) { @@ -896,6 +994,7 @@ void WindowControl::onProjectionChanged(int newSelection) const { _sphericalMirror.widget->setVisible(selected == ProjectionIndices::SphericalMirror); _cylindrical.widget->setVisible(selected == ProjectionIndices::Cylindrical); _equirectangular.widget->setVisible(selected == ProjectionIndices::Equirectangular); + _blit.widget->setVisible(selected == ProjectionIndices::Blit); } void WindowControl::onAspectRatioLockClicked() { From 627b09296a1c037fa6206a3482f12d8119a4dc5f Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Mon, 31 Mar 2025 11:32:40 +0200 Subject: [PATCH 06/17] Add unit tests for conversions between profile versions (closes #2701) --- tests/profile/conversion/version_10.profile | 116 +++++++++++++++++ tests/profile/conversion/version_11.profile | 130 +++++++++++++++++++ tests/profile/conversion/version_12.profile | 131 ++++++++++++++++++++ tests/profile/conversion/version_13.profile | 131 ++++++++++++++++++++ tests/test_profile.cpp | 56 +++++++++ 5 files changed, 564 insertions(+) create mode 100644 tests/profile/conversion/version_10.profile create mode 100644 tests/profile/conversion/version_11.profile create mode 100644 tests/profile/conversion/version_12.profile create mode 100644 tests/profile/conversion/version_13.profile diff --git a/tests/profile/conversion/version_10.profile b/tests/profile/conversion/version_10.profile new file mode 100644 index 0000000000..9d2393e51f --- /dev/null +++ b/tests/profile/conversion/version_10.profile @@ -0,0 +1,116 @@ +{ + "version": { "major": 1, "minor": 0 }, + "meta": { + "name": "name", + "version": "version", + "description": "description", + "author": "author", + "url": "url", + "license": "license" + }, + "modules": [ + { "name": "abs-module" }, + { + "name": "def-module", + "loadedInstruction": "instr" + }, + { + "name": "ghi-module", + "notLoadedInstruction": "not_instr" + }, + { + "name": "jkl-module", + "loadedInstruction": "instr", + "notLoadedInstruction": "not_instr" + } + ], + "assets": [ + "scene/solarsystem/planets/earth/earth", + "scene/solarsystem/planets/earth/satellites/satellites", + "folder1/folder2/asset", + "folder3/folder4/asset2", + "folder5/folder6/asset3" + ], + "properties": [ + { + "type": "setPropertyValue", + "name": "{earth_satellites}.Renderable.Enabled", + "value": "false" + }, + { + "type": "setPropertyValue", + "name": "property_name_1", + "value": "property_value_1" + }, + { + "type": "setPropertyValue", + "name": "property_name_2", + "value": "property_value_2" + }, + { + "type": "setPropertyValue", + "name": "property_name_3", + "value": "property_value_3" + }, + { + "type": "setPropertyValueSingle", + "name": "property_name_4", + "value": "property_value_4" + }, + { + "type": "setPropertyValueSingle", + "name": "property_name_5", + "value": "property_value_5" + }, + { + "type": "setPropertyValueSingle", + "name": "property_name_6", + "value": "property_value_6" + } + ], + "keybindings": [ + { + "key": "T", + "documentation": "T documentation", + "name": "T name", + "gui_path": "T Gui-Path", + "is_local": true, + "script": "T script" + }, + { + "key": "U", + "documentation": "U documentation", + "name": "U name", + "gui_path": "U Gui-Path", + "is_local": false, + "script": "U script" + }, + { + "key": "CTRL+V", + "documentation": "CTRL+V documentation", + "name": "CTRL+V name", + "gui_path": "CTRL+V Gui-Path", + "is_local": false, + "script": "CTRL+V script" + } + ], + "time": { + "type": "relative", + "value": "-1d" + }, + "camera": { + "type": "goToGeo", + "anchor": "Earth", + "latitude": 58.5877, + "longitude": 16.1924, + "altitude": 2.0e+07 + }, + "mark_nodes": [ + "Earth", "Mars", "Moon", "Sun" + ], + "additional_scripts": [ + "script-1", + "script-2", + "script-3" + ] +} diff --git a/tests/profile/conversion/version_11.profile b/tests/profile/conversion/version_11.profile new file mode 100644 index 0000000000..594593dea5 --- /dev/null +++ b/tests/profile/conversion/version_11.profile @@ -0,0 +1,130 @@ +{ + "version": { "major": 1, "minor": 1 }, + "meta": { + "name": "name", + "version": "version", + "description": "description", + "author": "author", + "url": "url", + "license": "license" + }, + "modules": [ + { "name": "abs-module" }, + { + "name": "def-module", + "loadedInstruction": "instr" + }, + { + "name": "ghi-module", + "notLoadedInstruction": "not_instr" + }, + { + "name": "jkl-module", + "loadedInstruction": "instr", + "notLoadedInstruction": "not_instr" + } + ], + "assets": [ + "scene/solarsystem/planets/earth/earth", + "scene/solarsystem/planets/earth/satellites/satellites", + "folder1/folder2/asset", + "folder3/folder4/asset2", + "folder5/folder6/asset3" + ], + "properties": [ + { + "type": "setPropertyValue", + "name": "{earth_satellites}.Renderable.Enabled", + "value": "false" + }, + { + "type": "setPropertyValue", + "name": "property_name_1", + "value": "property_value_1" + }, + { + "type": "setPropertyValue", + "name": "property_name_2", + "value": "property_value_2" + }, + { + "type": "setPropertyValue", + "name": "property_name_3", + "value": "property_value_3" + }, + { + "type": "setPropertyValueSingle", + "name": "property_name_4", + "value": "property_value_4" + }, + { + "type": "setPropertyValueSingle", + "name": "property_name_5", + "value": "property_value_5" + }, + { + "type": "setPropertyValueSingle", + "name": "property_name_6", + "value": "property_value_6" + } + ], + "actions": [ + { + "identifier": "profile.keybind.0", + "documentation": "T documentation", + "name": "T name", + "gui_path": "T Gui-Path", + "is_local": true, + "script": "T script" + }, + { + "identifier": "profile.keybind.1", + "documentation": "U documentation", + "name": "U name", + "gui_path": "U Gui-Path", + "is_local": false, + "script": "U script" + }, + { + "identifier": "profile.keybind.2", + "documentation": "CTRL+V documentation", + "name": "CTRL+V name", + "gui_path": "CTRL+V Gui-Path", + "is_local": false, + "script": "CTRL+V script" + } + ], + "keybindings": [ + { + "action": "profile.keybind.0", + "key": "T" + }, + { + "action": "profile.keybind.1", + "key": "U" + }, + { + "action": "profile.keybind.2", + "key": "CTRL+V" + } + ], + "time": { + "type": "relative", + "value": "-1d" + }, + "camera": { + "type": "goToGeo", + "anchor": "Earth", + "latitude": 58.5877, + "longitude": 16.1924, + "altitude": 2.0e+07 + }, + "mark_nodes": [ + "Earth", "Mars", "Moon", "Sun" + ], + "additional_scripts": [ + "script-1", + "script-2", + "script-3" + ] +} diff --git a/tests/profile/conversion/version_12.profile b/tests/profile/conversion/version_12.profile new file mode 100644 index 0000000000..60bc75ff5f --- /dev/null +++ b/tests/profile/conversion/version_12.profile @@ -0,0 +1,131 @@ +{ + "version": { "major": 1, "minor": 2 }, + "meta": { + "name": "name", + "version": "version", + "description": "description", + "author": "author", + "url": "url", + "license": "license" + }, + "modules": [ + { "name": "abs-module" }, + { + "name": "def-module", + "loadedInstruction": "instr" + }, + { + "name": "ghi-module", + "notLoadedInstruction": "not_instr" + }, + { + "name": "jkl-module", + "loadedInstruction": "instr", + "notLoadedInstruction": "not_instr" + } + ], + "assets": [ + "scene/solarsystem/planets/earth/earth", + "scene/solarsystem/planets/earth/satellites/satellites", + "folder1/folder2/asset", + "folder3/folder4/asset2", + "folder5/folder6/asset3" + ], + "properties": [ + { + "type": "setPropertyValue", + "name": "{earth_satellites}.Renderable.Enabled", + "value": "false" + }, + { + "type": "setPropertyValue", + "name": "property_name_1", + "value": "property_value_1" + }, + { + "type": "setPropertyValue", + "name": "property_name_2", + "value": "property_value_2" + }, + { + "type": "setPropertyValue", + "name": "property_name_3", + "value": "property_value_3" + }, + { + "type": "setPropertyValueSingle", + "name": "property_name_4", + "value": "property_value_4" + }, + { + "type": "setPropertyValueSingle", + "name": "property_name_5", + "value": "property_value_5" + }, + { + "type": "setPropertyValueSingle", + "name": "property_name_6", + "value": "property_value_6" + } + ], + "actions": [ + { + "identifier": "profile.keybind.0", + "documentation": "T documentation", + "name": "T name", + "gui_path": "T Gui-Path", + "is_local": true, + "script": "T script" + }, + { + "identifier": "profile.keybind.1", + "documentation": "U documentation", + "name": "U name", + "gui_path": "U Gui-Path", + "is_local": false, + "script": "U script" + }, + { + "identifier": "profile.keybind.2", + "documentation": "CTRL+V documentation", + "name": "CTRL+V name", + "gui_path": "CTRL+V Gui-Path", + "is_local": false, + "script": "CTRL+V script" + } + ], + "keybindings": [ + { + "action": "profile.keybind.0", + "key": "T" + }, + { + "action": "profile.keybind.1", + "key": "U" + }, + { + "action": "profile.keybind.2", + "key": "CTRL+V" + } + ], + "time": { + "type": "relative", + "value": "-1d", + "is_paused": false + }, + "camera": { + "type": "goToGeo", + "anchor": "Earth", + "latitude": 58.5877, + "longitude": 16.1924, + "altitude": 2.0e+07 + }, + "mark_nodes": [ + "Earth", "Mars", "Moon", "Sun" + ], + "additional_scripts": [ + "script-1", + "script-2", + "script-3" + ] +} diff --git a/tests/profile/conversion/version_13.profile b/tests/profile/conversion/version_13.profile new file mode 100644 index 0000000000..0d97a97633 --- /dev/null +++ b/tests/profile/conversion/version_13.profile @@ -0,0 +1,131 @@ +{ + "version": { "major": 1, "minor": 3 }, + "meta": { + "name": "name", + "version": "version", + "description": "description", + "author": "author", + "url": "url", + "license": "license" + }, + "modules": [ + { "name": "abs-module" }, + { + "name": "def-module", + "loadedInstruction": "instr" + }, + { + "name": "ghi-module", + "notLoadedInstruction": "not_instr" + }, + { + "name": "jkl-module", + "loadedInstruction": "instr", + "notLoadedInstruction": "not_instr" + } + ], + "assets": [ + "scene/solarsystem/planets/earth/earth", + "scene/solarsystem/planets/earth/satellites/satellites", + "folder1/folder2/asset", + "folder3/folder4/asset2", + "folder5/folder6/asset3" + ], + "properties": [ + { + "type": "setPropertyValue", + "name": "{earth_satellites}.Renderable.Enabled", + "value": "false" + }, + { + "type": "setPropertyValue", + "name": "property_name_1", + "value": "property_value_1" + }, + { + "type": "setPropertyValue", + "name": "property_name_2", + "value": "property_value_2" + }, + { + "type": "setPropertyValue", + "name": "property_name_3", + "value": "property_value_3" + }, + { + "type": "setPropertyValueSingle", + "name": "property_name_4", + "value": "property_value_4" + }, + { + "type": "setPropertyValueSingle", + "name": "property_name_5", + "value": "property_value_5" + }, + { + "type": "setPropertyValueSingle", + "name": "property_name_6", + "value": "property_value_6" + } + ], + "actions": [ + { + "identifier": "profile.keybind.0", + "documentation": "T documentation", + "name": "T name", + "gui_path": "T Gui-Path", + "is_local": true, + "script": "T script" + }, + { + "identifier": "profile.keybind.1", + "documentation": "U documentation", + "name": "U name", + "gui_path": "U Gui-Path", + "is_local": false, + "script": "U script" + }, + { + "identifier": "profile.keybind.2", + "documentation": "CTRL+V documentation", + "name": "CTRL+V name", + "gui_path": "CTRL+V Gui-Path", + "is_local": false, + "script": "CTRL+V script" + } + ], + "keybindings": [ + { + "action": "profile.keybind.0", + "key": "T" + }, + { + "action": "profile.keybind.1", + "key": "U" + }, + { + "action": "profile.keybind.2", + "key": "CTRL+V" + } + ], + "time": { + "type": "relative", + "value": "-1d", + "is_paused": false + }, + "camera": { + "type": "goToGeo", + "anchor": "Earth", + "latitude": 58.5877, + "longitude": 16.1924, + "altitude": 2.0e+07 + }, + "mark_nodes": [ + "Earth", "Mars", "Moon", "Sun" + ], + "additional_scripts": [ + "script-1", + "script-2", + "script-3" + ] +} diff --git a/tests/test_profile.cpp b/tests/test_profile.cpp index 3da07cb636..a37dc2fff1 100644 --- a/tests/test_profile.cpp +++ b/tests/test_profile.cpp @@ -861,6 +861,62 @@ TEST_CASE("Save settings to profile", "[profile]") { CHECK(profile.time->value == "current-time"); } +TEST_CASE("Version 1.0 -> 1.1", "[profile]") { + constexpr std::string_view Src = "${TESTDIR}/profile/conversion/version_10.profile"; + constexpr std::string_view Dest = "${TESTDIR}/profile/conversion/version_11.profile"; + + Profile src = Profile(absPath(Src)); + Profile dst = Profile(absPath(Dest)); + CHECK(src == dst); +} + +TEST_CASE("Version 1.0 -> 1.2", "[profile]") { + constexpr std::string_view Src = "${TESTDIR}/profile/conversion/version_10.profile"; + constexpr std::string_view Dest = "${TESTDIR}/profile/conversion/version_12.profile"; + + Profile src = Profile(absPath(Src)); + Profile dst = Profile(absPath(Dest)); + CHECK(src == dst); +} + +TEST_CASE("Version 1.0 -> 1.3", "[profile]") { + constexpr std::string_view Src = "${TESTDIR}/profile/conversion/version_10.profile"; + constexpr std::string_view Dest = "${TESTDIR}/profile/conversion/version_13.profile"; + + Profile src = Profile(absPath(Src)); + Profile dst = Profile(absPath(Dest)); + CHECK(src == dst); +} + +TEST_CASE("Version 1.1 -> 1.2", "[profile]") { + constexpr std::string_view Src = "${TESTDIR}/profile/conversion/version_11.profile"; + constexpr std::string_view Dest = "${TESTDIR}/profile/conversion/version_12.profile"; + + Profile src = Profile(absPath(Src)); + Profile dst = Profile(absPath(Dest)); + CHECK(src == dst); +} + +TEST_CASE("Version 1.1 -> 1.3", "[profile]") { + constexpr std::string_view Src = "${TESTDIR}/profile/conversion/version_11.profile"; + constexpr std::string_view Dest = "${TESTDIR}/profile/conversion/version_13.profile"; + + Profile src = Profile(absPath(Src)); + Profile dst = Profile(absPath(Dest)); + CHECK(src == dst); +} + +TEST_CASE("Version 1.2 -> 1.3", "[profile]") { + constexpr std::string_view Src = "${TESTDIR}/profile/conversion/version_12.profile"; + constexpr std::string_view Dest = "${TESTDIR}/profile/conversion/version_13.profile"; + + Profile src = Profile(absPath(Src)); + Profile dst = Profile(absPath(Dest)); + CHECK(src == dst); +} + + + // // Error states // From bf2fee7d71f1c537149ef9bedaf715bdb24cf918 Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Mon, 31 Mar 2025 22:30:31 +0200 Subject: [PATCH 07/17] Submodule updates (#3586) Update submodules and also provide compatibility with CMake 4.0. - assimp: `v5.4.1` -> `v5.4.3` - catch: `v3.5.3` -> `3.8.0` - date: `master` -> `v3.0.3` - freetype2: `v2.10.4` -> `v2.13.3`. Also change to freetypes original Git server - glbindings: `v3.3.0` -> `v3.4.0` - glm: `master` -> `v1.0.2` - scnlib: `v4.0.1` -> `v2.0.2` - soloud: `master` -> `master` - tinyxml: `master` -> `v11.0.0` - tracy: `v0.10.1` -> `v0.11.0` --- CMakeLists.txt | 6 ++++++ apps/OpenSpace/ext/sgct | 2 +- ext/date | 2 +- ext/ghoul | 2 +- modules/audio/ext/soloud | 2 +- modules/fitsfilereader/ext/CCfits | 2 +- modules/fitsfilereader/ext/cfitsio | 2 +- modules/kameleon/ext/kameleon | 2 +- modules/webbrowser/CMakeLists.txt | 2 +- 9 files changed, 14 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d4304ad6d..32390f3be2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,12 @@ cmake_policy(VERSION 3.25) project(OpenSpace) +# CMake 4.0 will no longer allow minimum required version below 3.5 and some of our +# dependencies have not been updated to reflect this. To make things work, we blanked +# require to 3.10 here to remove those issues. Once CMake configures and generates +# successfully without this next line, it can be removed +set(CMAKE_POLICY_VERSION_MINIMUM 3.10) + set(OPENSPACE_RELEASE_BUILD OFF) set(OPENSPACE_VERSION_MAJOR) set(OPENSPACE_VERSION_MINOR) diff --git a/apps/OpenSpace/ext/sgct b/apps/OpenSpace/ext/sgct index 1418d9beb2..7ac1bd4891 160000 --- a/apps/OpenSpace/ext/sgct +++ b/apps/OpenSpace/ext/sgct @@ -1 +1 @@ -Subproject commit 1418d9beb25ad325f9e9e831acee22317e6b2578 +Subproject commit 7ac1bd4891efb2e34f6574a26e591d4a984e8e6b diff --git a/ext/date b/ext/date index 569b2d6785..5bdb7e6f31 160000 --- a/ext/date +++ b/ext/date @@ -1 +1 @@ -Subproject commit 569b2d678547985d0ead3b73ee93a28919000887 +Subproject commit 5bdb7e6f31fac909c090a46dbd9fea27b6e609a4 diff --git a/ext/ghoul b/ext/ghoul index 2972e7b963..8d73afb420 160000 --- a/ext/ghoul +++ b/ext/ghoul @@ -1 +1 @@ -Subproject commit 2972e7b963abe1a0ddd57efd8aaf28840a8da37e +Subproject commit 8d73afb420eee59881df53ee3dba998fbeaec595 diff --git a/modules/audio/ext/soloud b/modules/audio/ext/soloud index 1157475881..e82fd32c1f 160000 --- a/modules/audio/ext/soloud +++ b/modules/audio/ext/soloud @@ -1 +1 @@ -Subproject commit 1157475881da0d7f76102578255b937c7d4e8f57 +Subproject commit e82fd32c1f62183922f08c14c814a02b58db1873 diff --git a/modules/fitsfilereader/ext/CCfits b/modules/fitsfilereader/ext/CCfits index 15e78f599d..d9f9811b3e 160000 --- a/modules/fitsfilereader/ext/CCfits +++ b/modules/fitsfilereader/ext/CCfits @@ -1 +1 @@ -Subproject commit 15e78f599dcf619a573cc011e32b2635be9bf3dc +Subproject commit d9f9811b3ef639d4d185385dc9b6cefe7f722c59 diff --git a/modules/fitsfilereader/ext/cfitsio b/modules/fitsfilereader/ext/cfitsio index 8d98f1495b..49258f81de 160000 --- a/modules/fitsfilereader/ext/cfitsio +++ b/modules/fitsfilereader/ext/cfitsio @@ -1 +1 @@ -Subproject commit 8d98f1495b8c762f1c56e79da33cbd3c7d623d80 +Subproject commit 49258f81ded8e78bc2bcc74fab44770c822b32b6 diff --git a/modules/kameleon/ext/kameleon b/modules/kameleon/ext/kameleon index eee25cf7f4..91485053e1 160000 --- a/modules/kameleon/ext/kameleon +++ b/modules/kameleon/ext/kameleon @@ -1 +1 @@ -Subproject commit eee25cf7f4d06dc72fd057326cf6b337a4df8e24 +Subproject commit 91485053e1c260b337d1a9fd770c441abc0aa4a1 diff --git a/modules/webbrowser/CMakeLists.txt b/modules/webbrowser/CMakeLists.txt index fac4e9a432..51e6610443 100644 --- a/modules/webbrowser/CMakeLists.txt +++ b/modules/webbrowser/CMakeLists.txt @@ -38,7 +38,7 @@ set(PROJECT_ARCH "x86_64") # reserved. Use of this source code is governed by a BSD-style license that # can be found in the LICENSE file. -cmake_minimum_required(VERSION 2.8.12.1) +cmake_minimum_required(VERSION 3.10) # Use folders in the resulting project files. set_property(GLOBAL PROPERTY OS_FOLDERS ON) From 070d1180e53665999c72d8baf4c7250d976fe4a4 Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Tue, 1 Apr 2025 10:13:47 +0200 Subject: [PATCH 08/17] Fix issue with scnlib update --- modules/space/kepler.cpp | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/modules/space/kepler.cpp b/modules/space/kepler.cpp index 8cf8cbadfc..730594b7d9 100644 --- a/modules/space/kepler.cpp +++ b/modules/space/kepler.cpp @@ -208,12 +208,25 @@ namespace { if (e.find('.') == std::string::npos) { e += ".0"; } - // @TODO(abock, 2024-05-20) 'd' suffix is needed until scnlib updates to v3 - auto res = scn::scan(e, "{:2d}{}"); - if (!res) { + if (e.size() <= 2) { + throw ghoul::RuntimeError( + std::format("Error parsing epoch '{}'. Invalid date string", epoch) + ); + } + + std::string_view yearStr = std::string_view(e).substr(0, 2); + std::string_view daysInYearStr = std::string_view(e).substr(2); + auto resYear = scn::scan(yearStr, "{}"); + if (!resYear) { throw ghoul::RuntimeError(std::format("Error parsing epoch '{}'", epoch)); } - auto [year, daysInYear] = res->values(); + int year = resYear->value(); + + auto resDaysInYear = scn::scan(daysInYearStr, "{}"); + if (!resDaysInYear) { + throw ghoul::RuntimeError(std::format("Error parsing epoch '{}'", epoch)); + } + double daysInYear = resDaysInYear->value(); year += year > 57 ? 1900 : 2000; const int daysSince2000 = countDays(year); @@ -336,9 +349,8 @@ namespace { // We have the first form int month = 0; int days = 0; - // @TODO(abock, 2024-05-20) 'd' suffix is needed until scnlib updates to v3 auto res = scn::scan( - epoch, "{:4d}-{:2d}-{:2d}T{:2d}:{:2d}:{}" + epoch, "{:4}-{:2}-{:2}T{:2}:{:2}:{}" ); if (!res) { throw ghoul::RuntimeError(std::format("Error parsing epoch '{}'", epoch)); @@ -350,9 +362,8 @@ namespace { else if (pos == 8) { // We have the second form - // @TODO(abock, 2024-05-20) 'd' suffix is needed until scnlib updates to v3 auto res = scn::scan( - epoch, "{:4d}-{:3d}T{:2d}:{:2d}:{}" + epoch, "{:4}-{:3}T{:2}:{:2}:{}" //date.year, date.nDays, date.hours, date.minutes, date.seconds ); if (!res) { From 80955e3ec1fb821af95f48a0242fd5e7d0fda58c Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Tue, 1 Apr 2025 12:45:35 +0200 Subject: [PATCH 09/17] Add ScreenSpaceRenderableRenderable class (#3575) --- .../renderablerenderable.asset | 22 ++ .../renderablerenderable_axes.asset | 24 ++ .../renderablerenderable_model-distance.asset | 41 +++ .../renderablerenderable_model.asset | 47 +++ modules/base/CMakeLists.txt | 2 + modules/base/basemodule.cpp | 5 + .../base/rendering/screenspacedashboard.cpp | 2 +- modules/base/rendering/screenspacedashboard.h | 2 +- .../base/rendering/screenspaceframebuffer.cpp | 31 +- .../base/rendering/screenspaceframebuffer.h | 6 +- .../screenspacerenderablerenderable.cpp | 307 ++++++++++++++++++ .../screenspacerenderablerenderable.h | 76 +++++ src/properties/propertyowner.cpp | 3 +- src/scene/scenegraphnode.cpp | 2 +- 14 files changed, 540 insertions(+), 30 deletions(-) create mode 100644 data/assets/examples/screenspacerenderable/screenspacerenderablerenderable/renderablerenderable.asset create mode 100644 data/assets/examples/screenspacerenderable/screenspacerenderablerenderable/renderablerenderable_axes.asset create mode 100644 data/assets/examples/screenspacerenderable/screenspacerenderablerenderable/renderablerenderable_model-distance.asset create mode 100644 data/assets/examples/screenspacerenderable/screenspacerenderablerenderable/renderablerenderable_model.asset create mode 100644 modules/base/rendering/screenspacerenderablerenderable.cpp create mode 100644 modules/base/rendering/screenspacerenderablerenderable.h diff --git a/data/assets/examples/screenspacerenderable/screenspacerenderablerenderable/renderablerenderable.asset b/data/assets/examples/screenspacerenderable/screenspacerenderablerenderable/renderablerenderable.asset new file mode 100644 index 0000000000..636a0d96b8 --- /dev/null +++ b/data/assets/examples/screenspacerenderable/screenspacerenderablerenderable/renderablerenderable.asset @@ -0,0 +1,22 @@ +-- Basic +-- Creates a screenspace image that shows a spherical grid as an example for any +-- [Renderable](#renderable) that can be displayed. + +local Object = { + Type = "ScreenSpaceRenderableRenderable", + Identifier = "ScreenSpaceRenderableRenderable_Example", + Renderable = { + Type = "RenderableSphericalGrid", + Opacity = 1.0, + Color = { 0.3, 0.84, 1.0 }, + LineWidth = 2.0 + } +} + +asset.onInitialize(function() + openspace.addScreenSpaceRenderable(Object) +end) + +asset.onDeinitialize(function() + openspace.removeScreenSpaceRenderable(Object) +end) diff --git a/data/assets/examples/screenspacerenderable/screenspacerenderablerenderable/renderablerenderable_axes.asset b/data/assets/examples/screenspacerenderable/screenspacerenderablerenderable/renderablerenderable_axes.asset new file mode 100644 index 0000000000..48e264e518 --- /dev/null +++ b/data/assets/examples/screenspacerenderable/screenspacerenderablerenderable/renderablerenderable_axes.asset @@ -0,0 +1,24 @@ +-- Axes +-- Creates a screenspace image that renders three Cartesian axes into the screen space +-- window. This example also modifies the original camera position to give an oblique view +-- onto the axes and increases the field of view of the camera to a wider degree. We also +-- set the background color to be fully opaque to make it easier to see the axes. + +local Object = { + Type = "ScreenSpaceRenderableRenderable", + Identifier = "ScreenSpaceRenderableRenderable_Example_Axes", + Renderable = { + Type = "RenderableCartesianAxes" + }, + BackgroundColor = { 0.0, 0.0, 0.0, 1.0 }, + CameraPosition = { 1.0, 1.0, 1.0 }, + CameraFov = 80.0 +} + +asset.onInitialize(function() + openspace.addScreenSpaceRenderable(Object) +end) + +asset.onDeinitialize(function() + openspace.removeScreenSpaceRenderable(Object) +end) diff --git a/data/assets/examples/screenspacerenderable/screenspacerenderablerenderable/renderablerenderable_model-distance.asset b/data/assets/examples/screenspacerenderable/screenspacerenderablerenderable/renderablerenderable_model-distance.asset new file mode 100644 index 0000000000..ec19a4bd1b --- /dev/null +++ b/data/assets/examples/screenspacerenderable/screenspacerenderablerenderable/renderablerenderable_model-distance.asset @@ -0,0 +1,41 @@ +-- Model Distance +-- Creates a screen space window into which 3D model of the Eiffel tower is rendered. As +-- the objects are rendered in meter scale, and the Eiffel tower is about 300m tall, we +-- place the camera at a great distance to be able to see the entire Eiffel tower at the +-- same time. + +-- Download the model file for the Eiffel tower +local modelFolder = asset.resource({ + Name = "Scale Eiffel Tower", + Type = "HttpSynchronization", + Identifier = "scale_model_eiffel_tower", + Version = 1 +}) + +local Object = { + Type = "ScreenSpaceRenderableRenderable", + Identifier = "ScreenSpaceRenderableRenderable_Example_ModelDistance", + Renderable = { + Type = "RenderableModel", + GeometryFile = modelFolder .. "eiffeltower.osmodel", + RotationVector = { 0.0, 45.0, 0.0 }, + LightSources = { + { + Identifier = "Camera", + Type = "CameraLightSource", + Intensity = 5.0 + } + } + }, + Scale = 1.25, + CameraPosition = { 0.0, 3500.0, 9000.0 }, + CameraCenter = { 0.0, 2750.0, 0.0 } +} + +asset.onInitialize(function() + openspace.addScreenSpaceRenderable(Object) +end) + +asset.onDeinitialize(function() + openspace.removeScreenSpaceRenderable(Object) +end) diff --git a/data/assets/examples/screenspacerenderable/screenspacerenderablerenderable/renderablerenderable_model.asset b/data/assets/examples/screenspacerenderable/screenspacerenderablerenderable/renderablerenderable_model.asset new file mode 100644 index 0000000000..19abddfac3 --- /dev/null +++ b/data/assets/examples/screenspacerenderable/screenspacerenderablerenderable/renderablerenderable_model.asset @@ -0,0 +1,47 @@ +-- Model +-- Creates a screen space window into which 3D model of the Eiffel tower is rendered. As +-- the objects are rendered in meter scale, and the Eiffel tower is about 300m tall, we +-- both shrink the rendering to make the entire model fit into the view and also modify +-- the position of the camera. + +-- Download the model file for the Eiffel tower +local modelFolder = asset.resource({ + Name = "Scale Eiffel Tower", + Type = "HttpSynchronization", + Identifier = "scale_model_eiffel_tower", + Version = 1 +}) + +local Object = { + Type = "ScreenSpaceRenderableRenderable", + Identifier = "ScreenSpaceRenderableRenderable_Example_Model", + Transform = { + Scale = { + Type = "StaticScale", + Scale = 0.0002 + } + }, + Renderable = { + Type = "RenderableModel", + GeometryFile = modelFolder .. "eiffeltower.osmodel", + RotationVector = { 0.0, 45.0, 0.0 }, + LightSources = { + { + Identifier = "Camera", + Type = "CameraLightSource", + Intensity = 5.0 + } + } + }, + Scale = 1.25, + CameraPosition = { 0.0, 1.0, 2.0 }, + CameraCenter = { 0.0, 0.5, 0.0 } +} + +asset.onInitialize(function() + openspace.addScreenSpaceRenderable(Object) +end) + +asset.onDeinitialize(function() + openspace.removeScreenSpaceRenderable(Object) +end) diff --git a/modules/base/CMakeLists.txt b/modules/base/CMakeLists.txt index 296add425e..d03de639d8 100644 --- a/modules/base/CMakeLists.txt +++ b/modules/base/CMakeLists.txt @@ -72,6 +72,7 @@ set(HEADER_FILES rendering/screenspaceframebuffer.h rendering/screenspaceimagelocal.h rendering/screenspaceimageonline.h + rendering/screenspacerenderablerenderable.h rotation/timelinerotation.h rotation/constantrotation.h rotation/fixedrotation.h @@ -142,6 +143,7 @@ set(SOURCE_FILES rendering/screenspaceframebuffer.cpp rendering/screenspaceimagelocal.cpp rendering/screenspaceimageonline.cpp + rendering/screenspacerenderablerenderable.cpp rotation/timelinerotation.cpp rotation/constantrotation.cpp rotation/fixedrotation.cpp diff --git a/modules/base/basemodule.cpp b/modules/base/basemodule.cpp index bc4641b24f..d41cf1b052 100644 --- a/modules/base/basemodule.cpp +++ b/modules/base/basemodule.cpp @@ -68,6 +68,7 @@ #include #include #include +#include #include #include #include @@ -110,6 +111,9 @@ void BaseModule::internalInitialize(const ghoul::Dictionary&) { fSsRenderable->registerClass("ScreenSpaceImageLocal"); fSsRenderable->registerClass("ScreenSpaceImageOnline"); fSsRenderable->registerClass("ScreenSpaceFramebuffer"); + fSsRenderable->registerClass( + "ScreenSpaceRenderableRenderable" + ); ghoul::TemplateFactory* fDashboard = @@ -281,6 +285,7 @@ std::vector BaseModule::documentations() const { ScreenSpaceFramebuffer::Documentation(), ScreenSpaceImageLocal::Documentation(), ScreenSpaceImageOnline::Documentation(), + ScreenSpaceRenderableRenderable::Documentation(), ConstantRotation::Documentation(), FixedRotation::Documentation(), diff --git a/modules/base/rendering/screenspacedashboard.cpp b/modules/base/rendering/screenspacedashboard.cpp index e43a7bb1e2..1a4ca00033 100644 --- a/modules/base/rendering/screenspacedashboard.cpp +++ b/modules/base/rendering/screenspacedashboard.cpp @@ -101,7 +101,7 @@ bool ScreenSpaceDashboard::initializeGL() { ScreenSpaceFramebuffer::initializeGL(); addRenderFunction([this]() { - glm::vec2 penPosition = glm::vec2(0.f, _size.value().w); + glm::vec2 penPosition = glm::vec2(0.f, _size.value().x); if (_useMainDashboard) { global::dashboard->render(penPosition); diff --git a/modules/base/rendering/screenspacedashboard.h b/modules/base/rendering/screenspacedashboard.h index 23a5e4c81e..ba453ff8e5 100644 --- a/modules/base/rendering/screenspacedashboard.h +++ b/modules/base/rendering/screenspacedashboard.h @@ -40,7 +40,7 @@ namespace openspace { namespace documentation { struct Documentation; } namespace scripting { struct LuaLibrary; } -class ScreenSpaceDashboard: public ScreenSpaceFramebuffer { +class ScreenSpaceDashboard : public ScreenSpaceFramebuffer { public: explicit ScreenSpaceDashboard(const ghoul::Dictionary& dictionary); virtual ~ScreenSpaceDashboard() override = default; diff --git a/modules/base/rendering/screenspaceframebuffer.cpp b/modules/base/rendering/screenspaceframebuffer.cpp index 36aa7f2f2f..e6233961bf 100644 --- a/modules/base/rendering/screenspaceframebuffer.cpp +++ b/modules/base/rendering/screenspaceframebuffer.cpp @@ -55,7 +55,7 @@ documentation::Documentation ScreenSpaceFramebuffer::Documentation() { ScreenSpaceFramebuffer::ScreenSpaceFramebuffer(const ghoul::Dictionary& dictionary) : ScreenSpaceRenderable(dictionary) - , _size(SizeInfo, glm::vec4(0), glm::vec4(0), glm::vec4(16384)) + , _size(SizeInfo, glm::vec2(16), glm::vec2(16), glm::vec2(16384)) { documentation::testSpecificationAndThrow( Documentation(), @@ -75,14 +75,8 @@ ScreenSpaceFramebuffer::ScreenSpaceFramebuffer(const ghoul::Dictionary& dictiona } } - if (_guiName.empty()) { - // Adding an extra space to the user-facing name as it looks nicer - setGuiName("ScreenSpaceFramebuffer " + std::to_string(iIdentifier)); - } - - const glm::vec2 resolution = global::windowDelegate->currentDrawBufferResolution(); + _size = global::windowDelegate->currentDrawBufferResolution(); addProperty(_size); - _size = glm::vec4(0.f, 0.f, resolution.x, resolution.y); } ScreenSpaceFramebuffer::~ScreenSpaceFramebuffer() {} @@ -107,22 +101,13 @@ bool ScreenSpaceFramebuffer::deinitializeGL() { void ScreenSpaceFramebuffer::render(const RenderData& renderData) { const glm::vec2& resolution = global::windowDelegate->currentDrawBufferResolution(); - const glm::vec4& size = _size.value(); - - const float xratio = resolution.x / (size.z - size.x); - const float yratio = resolution.y / (size.w - size.y); + const glm::vec2& size = _size.value(); + const glm::vec2 ratio = resolution / size; if (!_renderFunctions.empty()) { std::array viewport; - //glGetIntegerv(GL_VIEWPORT, viewport); - global::renderEngine->openglStateCache().viewport(viewport.data()); - glViewport( - static_cast(-size.x * xratio), - static_cast(-size.y * yratio), - static_cast(resolution.x * xratio), - static_cast(resolution.y * yratio) - ); - global::renderEngine->openglStateCache().setViewportState(viewport.data()); + glGetIntegerv(GL_VIEWPORT, viewport.data()); + glViewport(0, 0, static_cast(size.x), static_cast(size.y)); const GLint defaultFBO = ghoul::opengl::FramebufferObject::getActiveObject(); _framebuffer->activate(); @@ -142,7 +127,7 @@ void ScreenSpaceFramebuffer::render(const RenderData& renderData) { const glm::mat4 localRotation = localRotationMatrix(); const glm::mat4 scale = glm::scale( scaleMatrix(), - glm::vec3((1.f / xratio), (1.f / yratio), 1.f) + glm::vec3((1.f / ratio.x), (1.f / ratio.y), 1.f) ); const glm::mat4 modelTransform = globalRotation*translation*localRotation*scale; draw(modelTransform, renderData); @@ -153,7 +138,7 @@ bool ScreenSpaceFramebuffer::isReady() const { return _shader && _texture; } -void ScreenSpaceFramebuffer::setSize(glm::vec4 size) { +void ScreenSpaceFramebuffer::setSize(glm::vec2 size) { _size = std::move(size); } diff --git a/modules/base/rendering/screenspaceframebuffer.h b/modules/base/rendering/screenspaceframebuffer.h index 7b134614bc..5d9ef522ab 100644 --- a/modules/base/rendering/screenspaceframebuffer.h +++ b/modules/base/rendering/screenspaceframebuffer.h @@ -27,7 +27,7 @@ #include -#include +#include namespace ghoul::opengl { class FramebufferObject; @@ -56,7 +56,7 @@ public: void render(const RenderData& renderData) override; bool isReady() const override; - void setSize(glm::vec4 size); + void setSize(glm::vec2 size); void addRenderFunction(RenderFunction renderFunction); void removeAllRenderFunctions(); @@ -64,7 +64,7 @@ public: protected: void createFramebuffer(); - properties::Vec4Property _size; + properties::Vec2Property _size; private: void bindTexture() override; diff --git a/modules/base/rendering/screenspacerenderablerenderable.cpp b/modules/base/rendering/screenspacerenderablerenderable.cpp new file mode 100644 index 0000000000..45367c6b7e --- /dev/null +++ b/modules/base/rendering/screenspacerenderablerenderable.cpp @@ -0,0 +1,307 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2025 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +namespace { + constexpr openspace::properties::Property::PropertyInfo TimeInfo = { + "Time", + "Time", + "The time (in J2000 seconds) that is used to calculate transformations and the " + "renderable's data." + }; + + constexpr openspace::properties::Property::PropertyInfo CameraPositionInfo = { + "CameraPosition", + "Camera Position", + "Specifies the location of the virtual camera that is showing the renderable " + "class. This position is provided in meters." + }; + + constexpr openspace::properties::Property::PropertyInfo CameraCenterInfo = { + "CameraCenter", + "Camera Center", + "The location of the camera's focal point. The camera's view direction will " + "always be pointing at the provided center location. This position is provided " + "in meters." + }; + + constexpr openspace::properties::Property::PropertyInfo CameraUpInfo = { + "CameraUp", + "Camera Up", + "The direction that is 'up' for the provided camera. This value does not have " + "any units." + }; + + constexpr openspace::properties::Property::PropertyInfo CameraFovInfo = { + "CameraFov", + "Camera Field of view", + "The camera's field of view in degrees." + }; + + const openspace::properties::PropertyOwner::PropertyOwnerInfo TransformInfo = { + "Transform", + "Transform", + "The Translation, Rotation, and Scale that are applied to the rendered " + "Renderable." + }; + + // This [ScreenSpaceRenderable](#core_screenspacerenderable) object can render any + // [Renderable](#renderable) type into an image that is shown in screen space. This + // can be used to display a rendered object as an overlay in front of the regular 3D + // rendering of the scene. + // + // Note that to use this `ScreenSpaceRenderable`, it might be necessary to specify the + // `size` parameter, which determines the resolution of the inset window into which + // the Renderable is rendered. For many use cases, the default should suffice, + // however. + // + // A possible use-case for the ScreenSpaceRenderable would be to show a 3D model of a + // spacecraft without the need to place it at a position in the 3D scene with the need + // to fly to that object to talk about it. + struct [[codegen::Dictionary(ScreenSpaceRenderableRenderable)]] Parameters { + std::optional identifier [[codegen::private()]]; + + // The [Renderable](#renderable) object that is shown in this ScreenSpace object. + // See the list of creatable renderable objects for options that can be used for + // this type. + ghoul::Dictionary renderable [[codegen::reference("renderable")]]; + + struct Transform { + // The [Translation](#core_transform_translation) object that is used for the + // provided [Renderable](#renderable). If no value is specified, a + // [StaticTranslation](#base_transform_translation_static) is created instead. + std::optional translation + [[codegen::reference("core_transform_translation")]]; + + // The [Rotation](#core_transform_rotation) object that is used for the + // provided [Renderable](#renderable). If no value is specified, a + // [StaticRotation](#base_transform_rotation_static) is created instead. + std::optional rotation + [[codegen::reference("core_transform_rotation")]]; + + // The [Scale](#core_transform_scale) object that is used for the provided + // [Renderable](#renderable). If no value is specified, a + // [StaticScale](#base_transform_scale_static) is created instead. + std::optional scale + [[codegen::reference("core_transform_scale")]]; + }; + // The collection of transformations that are applied to the + // [Renderable](#renderable) before it is shown on screen. + std::optional transform; + + // Specifies the start date that is used to control the renderable and the + // transforms. If no value is specified the date of 2000 JAN 01 12:00:00 is used + // instead. + std::optional time [[codegen::datetime()]]; + + // [[codegen::verbatim(CameraPositionInfo.description)]] + std::optional cameraPosition; + + // [[codegen::verbatim(CameraCenterInfo.description)]] + std::optional cameraCenter; + + // [[codegen::verbatim(CameraUpInfo.description)]] + std::optional cameraUp; + + // [[codegen::verbatim(CameraFovInfo.description)]] + std::optional cameraFov; + }; +#include "screenspacerenderablerenderable_codegen.cpp" +} // namespace + +namespace openspace { + +documentation::Documentation ScreenSpaceRenderableRenderable::Documentation() { + return codegen::doc( + "base_screenspace_renderable", + ScreenSpaceFramebuffer::Documentation() + ); +} + +ScreenSpaceRenderableRenderable::ScreenSpaceRenderableRenderable( + const ghoul::Dictionary& dictionary) + : ScreenSpaceFramebuffer(dictionary) + , _time( + TimeInfo, + 0.0, + -std::numeric_limits::max(), + std::numeric_limits::max() + ) + , _cameraPosition( + CameraPositionInfo, + glm::vec3(0.f, 2.f, 2.f), + glm::vec3(-10.f, -10.f, -10.f), + glm::vec3(10.f, 10.f, 10.f) + ) + , _cameraCenter(CameraCenterInfo, glm::vec3(0.f), glm::vec3(-1.f), glm::vec3(1.f)) + , _cameraUp(CameraUpInfo, glm::vec3(0.f, 1.f, 0.f), glm::vec3(-1.f), glm::vec3(1.f)) + , _cameraFov(CameraFovInfo, 45.f, 5.f, 90.f) +{ + const Parameters p = codegen::bake(dictionary); + + std::string identifier = p.identifier.value_or("ScreenSpaceRenderableRenderable"); + setIdentifier(makeUniqueIdentifier(std::move(identifier))); + + if (p.time.has_value()) { + _time = Time(*p.time).j2000Seconds(); + } + _time.onChange([this]() { _previousTime = _time; }); + addProperty(_time); + + _cameraPosition = p.cameraPosition.value_or(_cameraPosition); + addProperty(_cameraPosition); + _cameraCenter = p.cameraCenter.value_or(_cameraCenter); + addProperty(_cameraCenter); + _cameraUp = p.cameraUp.value_or(_cameraUp); + addProperty(_cameraUp); + _cameraFov = p.cameraFov.value_or(_cameraFov); + addProperty(_cameraFov); + + _renderable = Renderable::createFromDictionary(p.renderable); + _renderable->initialize(); + addPropertySubOwner(_renderable.get()); + + _transform = ghoul::mm_unique_ptr( + new properties::PropertyOwner(TransformInfo) + ); + addPropertySubOwner(_transform.get()); + + if (p.transform.has_value() && p.transform->translation.has_value()) { + _translation = Translation::createFromDictionary(*p.transform->translation); + } + else { + ghoul::Dictionary translation; + translation.setValue("Type", std::string("StaticTranslation")); + translation.setValue("Position", glm::dvec3(0.0)); + _translation = Translation::createFromDictionary(translation); + } + _translation->initialize(); + _transform->addPropertySubOwner(_translation.get()); + + if (p.transform.has_value() && p.transform->rotation.has_value()) { + _rotation = Rotation::createFromDictionary(*p.transform->rotation); + } + else { + ghoul::Dictionary rotation; + rotation.setValue("Type", std::string("StaticRotation")); + rotation.setValue("Rotation", glm::dvec3(0.0)); + _rotation = Rotation::createFromDictionary(rotation); + } + _rotation->initialize(); + _transform->addPropertySubOwner(_rotation.get()); + + if (p.transform.has_value() && p.transform->scale.has_value()) { + _scale = Scale::createFromDictionary(*p.transform->scale); + } + else { + ghoul::Dictionary scale; + scale.setValue("Type", std::string("StaticScale")); + scale.setValue("Scale", 1.0); + _scale = Scale::createFromDictionary(scale); + } + _scale->initialize(); + _transform->addPropertySubOwner(_scale.get()); +} + +ScreenSpaceRenderableRenderable::~ScreenSpaceRenderableRenderable() {} + +bool ScreenSpaceRenderableRenderable::initializeGL() { + ScreenSpaceFramebuffer::initializeGL(); + + _renderable->initializeGL(); + + addRenderFunction([this]() { + glm::vec4 bg = _backgroundColor; + glClearColor(bg.r, bg.g, bg.b, bg.a); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + Camera camera; + // @TODO (2025-03-24, abock): These two lines can be removed once #3573 is fixed + camera.setPositionVec3(glm::dvec3(0.0, 0.0, 0.0)); + camera.setRotation(glm::dvec3(0.0, 0.0, 0.0)); + + glm::mat4 view = glm::lookAt( + _cameraPosition.value(), + _cameraCenter.value(), + glm::normalize(_cameraUp.value()) + ); + camera.sgctInternal.setViewMatrix(view); + + glm::mat4 proj = glm::perspectiveFov( + glm::radians(_cameraFov.value()), + _size.value().x, + _size.value().y, + 0.1f, + 20.f + ); + camera.sgctInternal.setProjectionMatrix(proj); + + openspace::RenderData renderData = { + .camera = camera, + .time = Time(_time), + .modelTransform = { + .translation = _translation->position(), + .rotation = _rotation->matrix(), + .scale = _scale->scaleValue() + } + }; + RendererTasks tasks; + _renderable->render(renderData, tasks); + }); + return true; +} + +bool ScreenSpaceRenderableRenderable::deinitializeGL() { + _renderable->deinitializeGL(); + _renderable->deinitialize(); + return ScreenSpaceFramebuffer::deinitializeGL(); +} + +void ScreenSpaceRenderableRenderable::update() { + UpdateData updateData = { + .time = Time(_time), + .previousFrameTime = Time(_previousTime) + }; + + _translation->update(updateData); + updateData.modelTransform.translation = _translation->position(); + + _rotation->update(updateData); + updateData.modelTransform.rotation = _rotation->matrix(); + + _scale->update(updateData); + updateData.modelTransform.scale = _scale->scaleValue(); + + _renderable->update(updateData); +} + +} // namespace openspace diff --git a/modules/base/rendering/screenspacerenderablerenderable.h b/modules/base/rendering/screenspacerenderablerenderable.h new file mode 100644 index 0000000000..199f030c0f --- /dev/null +++ b/modules/base/rendering/screenspacerenderablerenderable.h @@ -0,0 +1,76 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2025 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_MODULE_BASE___SCREENSPACERENDERABLERENDERABLE___H__ +#define __OPENSPACE_MODULE_BASE___SCREENSPACERENDERABLERENDERABLE___H__ + +#include + +#include +#include +#include +#include + +namespace openspace { + +namespace properties { class PropertyOwner; } + +class Renderable; +class Rotation; +class Scale; +class Translation; + +namespace documentation { struct Documentation; } + +class ScreenSpaceRenderableRenderable : public ScreenSpaceFramebuffer { +public: + using RenderFunction = std::function; + + explicit ScreenSpaceRenderableRenderable(const ghoul::Dictionary& dictionary); + virtual ~ScreenSpaceRenderableRenderable() override; + + bool initializeGL() override; + bool deinitializeGL() override; + void update() override; + + static documentation::Documentation Documentation(); + +private: + ghoul::mm_unique_ptr _translation = nullptr; + ghoul::mm_unique_ptr _transform = nullptr; + ghoul::mm_unique_ptr _rotation = nullptr; + ghoul::mm_unique_ptr _scale = nullptr; + ghoul::mm_unique_ptr _renderable = nullptr; + + double _previousTime = 0.0; + properties::DoubleProperty _time; + properties::Vec3Property _cameraPosition; + properties::Vec3Property _cameraCenter; + properties::Vec3Property _cameraUp; + properties::FloatProperty _cameraFov; +}; + +} //namespace openspace + +#endif // __OPENSPACE_MODULE_BASE___SCREENSPACERENDERABLERENDERABLE___H__ diff --git a/src/properties/propertyowner.cpp b/src/properties/propertyowner.cpp index e1589f859f..3d238f8715 100644 --- a/src/properties/propertyowner.cpp +++ b/src/properties/propertyowner.cpp @@ -302,7 +302,8 @@ void PropertyOwner::addPropertySubOwner(openspace::properties::PropertyOwner* ow const bool hasProp = hasProperty(owner->identifier()); if (hasProp) { LERROR(std::format( - "PropertyOwner '{}'s name already names a Property", owner->identifier() + "PropertyOwner '{}'s identifier is already in use for a Property", + owner->identifier() )); return; } diff --git a/src/scene/scenegraphnode.cpp b/src/scene/scenegraphnode.cpp index b7e8a38ab3..c32b171cf0 100644 --- a/src/scene/scenegraphnode.cpp +++ b/src/scene/scenegraphnode.cpp @@ -924,7 +924,7 @@ void SceneGraphNode::renderDebugSphere(const Camera& camera, double size, void SceneGraphNode::setParent(SceneGraphNode& parent) { ghoul_assert(_parent != nullptr, "Node must be attached to a parent"); - parent.attachChild(_parent->detachChild(*this)); + _parent = &parent; } void SceneGraphNode::attachChild(ghoul::mm_unique_ptr child) { From eb709b830cc91dd84f685e5464a928a50c82e14a Mon Sep 17 00:00:00 2001 From: Emma Broman Date: Wed, 2 Apr 2025 09:28:11 +0200 Subject: [PATCH 10/17] Small option property cleanup (#3577) * Notify change listeners I can't come up with a reason for why we would not want to do this * Remove the Display Type for OptionProperty (only used in ImGui) * Replace extra boolean with internal anonymous addOption function * Tiny refactor * removed dropdown on a couple of more places * Update optionproperty.cpp --------- Co-authored-by: ElonOlsson --- .../properties/misc/optionproperty.h | 29 +------- modules/base/dashboard/dashboarditemangle.cpp | 15 +--- .../base/dashboard/dashboarditemdistance.cpp | 12 +--- .../base/dashboard/dashboarditemframerate.cpp | 2 +- .../dashboarditemsimulationincrement.cpp | 2 +- .../base/dashboard/dashboarditemvelocity.cpp | 2 +- .../pointcloud/renderablepointcloud.cpp | 5 +- .../pointcloud/sizemappingcomponent.cpp | 5 +- .../rendering/renderabledistancelabel.cpp | 2 +- modules/base/rendering/renderablelabel.cpp | 12 +--- modules/base/rendering/renderablemodel.cpp | 5 +- modules/base/rendering/renderableplane.cpp | 2 +- modules/base/rendering/renderablesphere.cpp | 7 +- modules/base/rendering/renderabletrail.cpp | 5 +- modules/base/rotation/fixedrotation.cpp | 15 +--- .../rendering/renderabledumeshes.cpp | 2 +- .../renderablefieldlinessequence.cpp | 9 +-- .../gaia/rendering/renderablegaiastars.cpp | 9 +-- modules/galaxy/rendering/renderablegalaxy.cpp | 7 +- .../src/geojson/geojsoncomponent.cpp | 5 +- .../src/geojson/geojsonproperties.cpp | 10 +-- .../src/globelabelscomponent.cpp | 5 +- modules/globebrowsing/src/layer.cpp | 4 +- modules/globebrowsing/src/layeradjustment.cpp | 2 +- modules/imgui/src/renderproperties.cpp | 65 +++++++---------- .../rendering/renderablekameleonvolume.cpp | 2 +- .../space/rendering/renderablefluxnodes.cpp | 8 +-- .../rendering/renderableorbitalkepler.cpp | 5 +- modules/space/rendering/renderablestars.cpp | 14 ++-- .../rendering/renderableshadowcylinder.cpp | 5 +- .../rendering/renderabletimevaryingvolume.cpp | 2 +- src/navigation/pathnavigator.cpp | 5 +- src/properties/misc/optionproperty.cpp | 70 +++++++++---------- src/rendering/colormappingcomponent.cpp | 2 +- 34 files changed, 115 insertions(+), 236 deletions(-) diff --git a/include/openspace/properties/misc/optionproperty.h b/include/openspace/properties/misc/optionproperty.h index 6507d43a45..a833bf4830 100644 --- a/include/openspace/properties/misc/optionproperty.h +++ b/include/openspace/properties/misc/optionproperty.h @@ -48,11 +48,6 @@ public: std::string description; }; - enum class DisplayType { - Radio, - Dropdown - }; - /** * The constructor delegating the `identifier` and the `guiName` to its super class. * @@ -64,18 +59,6 @@ public: */ OptionProperty(Property::PropertyInfo info); - /** - * The constructor delegating the `identifier` and the `guiName` to its super class. - * - * \param info The PropertyInfo structure that contains all the required static - * information for initializing this Property - * \param displayType Optional DisplayType for GUI (default RADIO) - * - * \pre \p info.identifier must not be empty - * \pre \p info.guiName must not be empty - */ - OptionProperty(PropertyInfo info, DisplayType displayType); - /** * Returns the name of the class for reflection purposes. * @@ -86,21 +69,14 @@ public: using TemplateProperty::operator=; - /** - * Returns the type for GUI display. - * - * \return OptionType for display purposes - */ - DisplayType displayType() const; - /** * Adds the passed option to the list of available options. The `value` of the * `option` must not have been registered previously, or a warning will be logged. * * \param value The option that will be added to the list of available options - * \param desc The description of the value that will be added + * \param description The description of the value that will be added */ - void addOption(int value, std::string desc); + void addOption(int value, std::string description); /** * Adds multiple options to the OptionProperty. Each value in the vector consists of @@ -171,7 +147,6 @@ private: /// The list of options which have been registered with this OptionProperty std::vector