From 9ea284f6c6ebce8ae7fc93f60aade66822651b29 Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Mon, 22 Aug 2022 14:17:04 +0200 Subject: [PATCH 1/4] Compile fix for MSVC 17.3 --- src/engine/globalscallbacks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/globalscallbacks.cpp b/src/engine/globalscallbacks.cpp index f04aacff05..e7e871e42b 100644 --- a/src/engine/globalscallbacks.cpp +++ b/src/engine/globalscallbacks.cpp @@ -138,7 +138,7 @@ void create() { #endif // WIN32 #ifdef WIN32 - keyboard = new (currentPos) std::vector + keyboard = new (currentPos) std::vector(); ghoul_assert(keyboard, "No keyboard"); currentPos += sizeof(std::vector); #else // ^^^ WIN32 / !WIN32 vvv From 979a5e33786c12e0f889fd5fc5edeba9ac6bb038 Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Mon, 22 Aug 2022 15:16:07 +0200 Subject: [PATCH 2/4] SkyBrowser Hash Handling (#2201) * Add the loading of a hash for wwt image files and automatically force a redownload of the files if the hash has changed * Move the wwtdataimages location into the sync folder * Some general code cleanup --- include/openspace/rendering/renderable.h | 2 + include/openspace/rendering/renderengine.h | 4 +- modules/server/src/topics/skybrowsertopic.cpp | 2 +- modules/skybrowser/include/browser.h | 6 +- .../skybrowser/include/renderableskytarget.h | 1 - .../include/screenspaceskybrowser.h | 4 +- .../skybrowser/include/targetbrowserpair.h | 13 - modules/skybrowser/include/utility.h | 26 +- modules/skybrowser/include/wwtcommunicator.h | 20 +- modules/skybrowser/include/wwtdatahandler.h | 60 +- modules/skybrowser/skybrowsermodule.cpp | 97 +-- modules/skybrowser/skybrowsermodule.h | 35 +- modules/skybrowser/skybrowsermodule_lua.inl | 22 +- modules/skybrowser/src/browser.cpp | 55 +- .../skybrowser/src/screenspaceskybrowser.cpp | 4 +- modules/skybrowser/src/targetbrowserpair.cpp | 78 +-- modules/skybrowser/src/utility.cpp | 67 +- modules/skybrowser/src/wwtcommunicator.cpp | 271 ++++---- modules/skybrowser/src/wwtdatahandler.cpp | 596 +++++++++--------- src/rendering/renderable.cpp | 4 + src/rendering/renderengine.cpp | 5 +- 21 files changed, 630 insertions(+), 742 deletions(-) diff --git a/include/openspace/rendering/renderable.h b/include/openspace/rendering/renderable.h index c9dc788a9e..c29305ed08 100644 --- a/include/openspace/rendering/renderable.h +++ b/include/openspace/rendering/renderable.h @@ -96,6 +96,8 @@ public: void setRenderBin(RenderBin bin); bool matchesRenderBinMask(int binMask); + void setFade(float fade); + bool isVisible() const; void onEnabledChange(std::function callback); diff --git a/include/openspace/rendering/renderengine.h b/include/openspace/rendering/renderengine.h index fba0a8458a..dfc751a3f3 100644 --- a/include/openspace/rendering/renderengine.h +++ b/include/openspace/rendering/renderengine.h @@ -98,8 +98,8 @@ public: void addScreenSpaceRenderable(std::unique_ptr s); void removeScreenSpaceRenderable(ScreenSpaceRenderable* s); - void removeScreenSpaceRenderable(const std::string& identifier); - ScreenSpaceRenderable* screenSpaceRenderable(const std::string& identifier); + void removeScreenSpaceRenderable(std::string_view identifier); + ScreenSpaceRenderable* screenSpaceRenderable(std::string_view identifier); std::vector screenSpaceRenderables() const; std::unique_ptr buildRenderProgram( diff --git a/modules/server/src/topics/skybrowsertopic.cpp b/modules/server/src/topics/skybrowsertopic.cpp index e76172020f..1c45527b03 100644 --- a/modules/server/src/topics/skybrowsertopic.cpp +++ b/modules/server/src/topics/skybrowsertopic.cpp @@ -101,7 +101,7 @@ void SkyBrowserTopic::sendBrowserData() { // Pass data for all the browsers and the corresponding targets if (module->isCameraInSolarSystem()) { - const std::vector>& pairs = module->getPairs(); + const std::vector>& pairs = module->pairs(); ghoul::Dictionary targets; for (const std::unique_ptr& pair : pairs) { std::string id = pair->browserId(); diff --git a/modules/skybrowser/include/browser.h b/modules/skybrowser/include/browser.h index 1fb230dbe5..04e7a70f7e 100644 --- a/modules/skybrowser/include/browser.h +++ b/modules/skybrowser/include/browser.h @@ -28,8 +28,8 @@ #include #include #include -#include #include +#include #ifdef _MSC_VER #pragma warning (push) @@ -64,7 +64,7 @@ public: explicit Browser(const ghoul::Dictionary& dictionary); virtual ~Browser(); - bool initializeGL(); + void initializeGL(); void deinitializeGL(); bool isReady() const; @@ -78,7 +78,7 @@ public: void setCallbackDimensions(const std::function& function); protected: - properties::Vec2Property _browserPixeldimensions; + properties::Vec2Property _browserDimensions; properties::StringProperty _url; properties::TriggerProperty _reload; diff --git a/modules/skybrowser/include/renderableskytarget.h b/modules/skybrowser/include/renderableskytarget.h index 1cc6972253..03859dcddb 100644 --- a/modules/skybrowser/include/renderableskytarget.h +++ b/modules/skybrowser/include/renderableskytarget.h @@ -29,7 +29,6 @@ #include #include -#include namespace openspace::documentation { struct Documentation; } diff --git a/modules/skybrowser/include/screenspaceskybrowser.h b/modules/skybrowser/include/screenspaceskybrowser.h index 6c470bed53..a2faed2372 100644 --- a/modules/skybrowser/include/screenspaceskybrowser.h +++ b/modules/skybrowser/include/screenspaceskybrowser.h @@ -29,9 +29,9 @@ #include #include -#include +#include #include -#include +#include namespace openspace { diff --git a/modules/skybrowser/include/targetbrowserpair.h b/modules/skybrowser/include/targetbrowserpair.h index 1dffd0b3ac..411467344e 100644 --- a/modules/skybrowser/include/targetbrowserpair.h +++ b/modules/skybrowser/include/targetbrowserpair.h @@ -26,8 +26,6 @@ #define __OPENSPACE_MODULE_SKYBROWSER___TARGETBROWSERPAIR___H__ #include -#include -#include namespace ghoul { class Dictionary; } @@ -42,7 +40,6 @@ class ScreenSpaceRenderable; class TargetBrowserPair { public: TargetBrowserPair(SceneGraphNode* target, ScreenSpaceSkyBrowser* browser); - TargetBrowserPair& operator=(TargetBrowserPair other); // Target & Browser void initialize(); @@ -60,9 +57,7 @@ public: // Browser void sendIdToBrowser() const; - void updateBrowserSize(); std::vector> displayCopies() const; - bool isImageCollectionLoaded(); // Target void centerTargetOnScreen(); @@ -73,7 +68,6 @@ public: bool isEnabled() const; void setEnabled(bool enable); - void setOpacity(float opacity); void setVerticalFov(double vfov); void setEquatorialAim(const glm::dvec2& aim); void setBorderColor(const glm::ivec3& color); @@ -89,9 +83,7 @@ public: std::string browserId() const; std::string targetRenderableId() const; std::string targetNodeId() const; - float browserRatio() const; - SceneGraphNode* targetNode() const; ScreenSpaceSkyBrowser* browser() const; std::vector selectedImages() const; @@ -106,12 +98,7 @@ public: void setImageOpacity(int i, float opacity); void hideChromeInterface(); - friend bool operator==(const TargetBrowserPair& lhs, const TargetBrowserPair& rhs); - friend bool operator!=(const TargetBrowserPair& lhs, const TargetBrowserPair& rhs); - private: - void aimTargetGalactic(glm::dvec3 direction); - // Target and browser RenderableSkyTarget* _targetRenderable = nullptr; ScreenSpaceSkyBrowser* _browser = nullptr; diff --git a/modules/skybrowser/include/utility.h b/modules/skybrowser/include/utility.h index 12cf747f91..077ef13dba 100644 --- a/modules/skybrowser/include/utility.h +++ b/modules/skybrowser/include/utility.h @@ -33,20 +33,7 @@ namespace openspace::skybrowser { // Constants constexpr double ScreenSpaceZ = -2.1; -constexpr glm::dvec3 NorthPole = { 0.0, 0.0, 1.0 }; -constexpr double CelestialSphereRadius = 4 * distanceconstants::Parsec; - -// Conversion matrix - J2000 equatorial <-> galactic -// https://arxiv.org/abs/1010.3773v1 -const glm::dmat3 conversionMatrix = glm::dmat3( - -0.054875539390, 0.494109453633, -0.867666135681, // col 0 - -0.873437104725, -0.444829594298, -0.198076389622, // col 1 - -0.483834991775, 0.746982248696, 0.455983794523 // col 2 -); - -// Galactic coordinates are projected onto the celestial sphere -// Equatorial coordinates are unit length -// Conversion spherical <-> Cartesian +constexpr double CelestialSphereRadius = 4.0 * distanceconstants::Parsec; /** * Converts from Cartesian coordinates to spherical coordinates with unit length. @@ -205,9 +192,8 @@ public: Animation(T start, T goal, double time) : _goal(std::move(goal)) , _start(std::move(start)) - { - _animationTime = std::chrono::milliseconds(static_cast(time * 1000)); - } + , _animationTime(std::chrono::milliseconds(static_cast(time * 1000))) + {} void start() { _isStarted = true; @@ -219,12 +205,12 @@ public: } bool isAnimating() const { - bool timeLeft = timeSpent().count() < _animationTime.count() ? true : false; + bool timeLeft = timeSpent().count() < _animationTime.count(); return timeLeft && _isStarted; } - T getNewValue(); - glm::dmat4 getRotationMatrix(); + T newValue() const; + glm::dmat4 rotationMatrix(); private: std::chrono::duration timeSpent() const { diff --git a/modules/skybrowser/include/wwtcommunicator.h b/modules/skybrowser/include/wwtcommunicator.h index 7e40e116df..e8344e2142 100644 --- a/modules/skybrowser/include/wwtcommunicator.h +++ b/modules/skybrowser/include/wwtcommunicator.h @@ -27,10 +27,6 @@ #include -#include -#include -#include -#include #include namespace openspace { @@ -38,8 +34,7 @@ namespace openspace { class WwtCommunicator : public Browser { public: explicit WwtCommunicator(const ghoul::Dictionary& dictionary); - WwtCommunicator(const WwtCommunicator&) = default; - ~WwtCommunicator() override; + ~WwtCommunicator() override = default; void update(); @@ -82,23 +77,10 @@ protected: std::deque> _selectedImages; private: - void setWebpageBorderColor(glm::ivec3 color) const; void sendMessageToWwt(const ghoul::Dictionary& msg) const; - // WorldWide Telescope messages - ghoul::Dictionary moveCameraMessage(const glm::dvec2& celestCoords, double fov, - double roll, bool shouldMoveInstantly = true) const; - ghoul::Dictionary loadCollectionMessage(const std::string& url) const; - ghoul::Dictionary setForegroundMessage(const std::string& name) const; - ghoul::Dictionary addImageMessage(const std::string& id, - const std::string& url) const; - ghoul::Dictionary removeImageMessage(const std::string& id) const; - ghoul::Dictionary setImageOpacityMessage(const std::string& id, double opacity) const; - ghoul::Dictionary setLayerOrderMessage(const std::string& id, int version); - bool _borderColorIsDirty = false; bool _equatorialAimIsDirty = false; - int messageCounter = 0; // Time variables // For capping the message passing to WWT diff --git a/modules/skybrowser/include/wwtdatahandler.h b/modules/skybrowser/include/wwtdatahandler.h index b3a45dd46c..fe7d289879 100644 --- a/modules/skybrowser/include/wwtdatahandler.h +++ b/modules/skybrowser/include/wwtdatahandler.h @@ -25,51 +25,22 @@ #ifndef __OPENSPACE_MODULE_SKYBROWSER___WWTDATAHANDLER___H__ #define __OPENSPACE_MODULE_SKYBROWSER___WWTDATAHANDLER___H__ -#include -#include -#include +#include +#include -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wsuggest-override" -#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" -#endif - -#include - -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic pop -#endif - -namespace openspace::documentation { struct Documentation; } - -namespace openspace::wwt { - const std::string Thumbnail = "Thumbnail"; - const std::string Name = "Name"; - const std::string ImageSet = "ImageSet"; - const std::string Dec = "Dec"; - const std::string RA = "RA"; - const std::string Undefined = ""; - const std::string Folder = "Folder"; - const std::string Place = "Place"; - const std::string ThumbnailUrl = "ThumbnailUrl"; - const std::string Url = "Url"; - const std::string Credits = "Credits"; - const std::string CreditsUrl = "CreditsUrl"; - const std::string ZoomLevel = "ZoomLevel"; - const std::string DataSetType = "DataSetType"; - const std::string Sky = "Sky"; -} // namespace openspace::wwt +namespace tinyxml2 { class XMLElement; } namespace openspace { +namespace documentation { struct Documentation; } + struct ImageData { - std::string name = wwt::Undefined; - std::string thumbnailUrl = wwt::Undefined; - std::string imageUrl = wwt::Undefined; - std::string credits = wwt::Undefined; - std::string creditsUrl = wwt::Undefined; - std::string collection = wwt::Undefined; + std::string name; + std::string thumbnailUrl; + std::string imageUrl; + std::string credits; + std::string creditsUrl; + std::string collection; bool hasCelestialCoords = false; float fov = 0.f; glm::dvec2 equatorialSpherical = glm::dvec2(0.0); @@ -78,20 +49,15 @@ struct ImageData { class WwtDataHandler { public: - WwtDataHandler() = default; - ~WwtDataHandler(); - void loadImages(const std::string& root, const std::filesystem::path& directory); int nLoadedImages() const; - const ImageData& getImage(int i) const; + const ImageData& image(int i) const; private: - void saveImageFromNode(tinyxml2::XMLElement* node, std::string collection); - void saveImagesFromXml(tinyxml2::XMLElement* root, std::string collection); + void saveImagesFromXml(const tinyxml2::XMLElement* root, std::string collection); // Images std::vector _images; - std::vector _xmls; }; } // namespace openspace diff --git a/modules/skybrowser/skybrowsermodule.cpp b/modules/skybrowser/skybrowsermodule.cpp index 9bac48e218..5d8c692343 100644 --- a/modules/skybrowser/skybrowsermodule.cpp +++ b/modules/skybrowser/skybrowsermodule.cpp @@ -150,8 +150,10 @@ SkyBrowserModule::SkyBrowserModule() , _hideTargetsBrowsersWithGui(HideWithGuiInfo, false) , _inverseZoomDirection(InverseZoomInfo, false) , _spaceCraftAnimationTime(SpaceCraftTimeInfo, 2.0, 0.0, 10.0) - , _wwtImageCollectionUrl(ImageCollectionInfo, - "https://data.openspaceproject.com/wwt/1/imagecollection.wtml") + , _wwtImageCollectionUrl( + ImageCollectionInfo, + "https://data.openspaceproject.com/wwt/1/imagecollection.wtml" + ) { addProperty(_enabled); addProperty(_showTitleInGuiBrowser); @@ -189,7 +191,8 @@ SkyBrowserModule::SkyBrowserModule() if (vizModeChanged) { constexpr float FadeDuration = 2.f; - if (camWasInSolarSystem) { // Camera moved out of the solar system => fade out + if (camWasInSolarSystem) { + // Camera moved out of the solar system => fade out for (const std::unique_ptr& pair : _targetsBrowsers) { pair->startFading(0.f, FadeDuration); } @@ -197,7 +200,8 @@ SkyBrowserModule::SkyBrowserModule() // Also hide the hover circle disableHoverCircle(); } - else { // Camera moved into the solar system => fade in + else { + // Camera moved into the solar system => fade in for (const std::unique_ptr& pair : _targetsBrowsers) { pair->startFading(1.f, FadeDuration); } @@ -246,10 +250,6 @@ void SkyBrowserModule::internalInitialize(const ghoul::Dictionary& dict) { // Register ScreenSpaceSkyTarget fRenderable->registerClass("RenderableSkyTarget"); - - // Create data handler dynamically to avoid the linking error that - // came up when including the include file in the module header file - _dataHandler = std::make_unique(); } void SkyBrowserModule::addTargetBrowserPair(const std::string& targetId, @@ -281,8 +281,6 @@ void SkyBrowserModule::removeTargetBrowserPair(const std::string& id) { _targetsBrowsers.begin(), _targetsBrowsers.end(), [&](const std::unique_ptr& pair) { - // should this be? - // found == pair.get() return found == pair.get(); } ); @@ -298,6 +296,7 @@ void SkyBrowserModule::lookAtTarget(const std::string& id) { } void SkyBrowserModule::setHoverCircle(SceneGraphNode* circle) { + ghoul_assert(circle, "No circle specified"); _hoverCircle = circle; // Always disable it per default. It should only be visible on interaction @@ -305,46 +304,48 @@ void SkyBrowserModule::setHoverCircle(SceneGraphNode* circle) { } void SkyBrowserModule::moveHoverCircle(int i, bool useScript) { - const ImageData& image = _dataHandler->getImage(i); + const ImageData& image = _dataHandler.image(i); // Only move and show circle if the image has coordinates - if (_hoverCircle && image.hasCelestialCoords && _isCameraInSolarSystem) { - const std::string id = _hoverCircle->identifier(); + if (!(_hoverCircle && image.hasCelestialCoords && _isCameraInSolarSystem)) { + return; + } - // Show the circle - if (useScript) { - const std::string script = fmt::format( - "openspace.setPropertyValueSingle('Scene.{}.Renderable.Fade', 1.0);", - id - ); - global::scriptEngine->queueScript( - script, - scripting::ScriptEngine::RemoteScripting::Yes - ); - } - else { - Renderable* renderable = _hoverCircle->renderable(); - if (renderable) { - renderable->property("Fade")->set(1.f); - } - } + const std::string id = _hoverCircle->identifier(); - // Set the exact target position - // Move it slightly outside of the celestial sphere so it doesn't overlap with - // the target - glm::dvec3 pos = skybrowser::equatorialToGalactic(image.equatorialCartesian); - pos *= skybrowser::CelestialSphereRadius * 1.1; - - // Note that the position can only be set through the script engine + // Show the circle + if (useScript) { const std::string script = fmt::format( - "openspace.setPropertyValueSingle('Scene.{}.Translation.Position', {});", - id, ghoul::to_string(pos) + "openspace.setPropertyValueSingle('Scene.{}.Renderable.Fade', 1.0);", + id ); global::scriptEngine->queueScript( script, scripting::ScriptEngine::RemoteScripting::Yes ); } + else { + Renderable* renderable = _hoverCircle->renderable(); + if (renderable) { + renderable->setFade(1.f); + } + } + + // Set the exact target position + // Move it slightly outside of the celestial sphere so it doesn't overlap with + // the target + glm::dvec3 pos = skybrowser::equatorialToGalactic(image.equatorialCartesian); + pos *= skybrowser::CelestialSphereRadius * 1.1; + + // Note that the position can only be set through the script engine + const std::string script = fmt::format( + "openspace.setPropertyValueSingle('Scene.{}.Translation.Position', {});", + id, ghoul::to_string(pos) + ); + global::scriptEngine->queueScript( + script, + scripting::ScriptEngine::RemoteScripting::Yes + ); } void SkyBrowserModule::disableHoverCircle(bool useScript) { @@ -368,18 +369,18 @@ void SkyBrowserModule::disableHoverCircle(bool useScript) { void SkyBrowserModule::loadImages(const std::string& root, const std::filesystem::path& directory) { - _dataHandler->loadImages(root, directory); + _dataHandler.loadImages(root, directory); } int SkyBrowserModule::nLoadedImages() const { - return _dataHandler->nLoadedImages(); + return _dataHandler.nLoadedImages(); } -const std::unique_ptr& SkyBrowserModule::getWwtDataHandler() const { +const WwtDataHandler& SkyBrowserModule::wwtDataHandler() const { return _dataHandler; } -std::vector>& SkyBrowserModule::getPairs() { +std::vector>& SkyBrowserModule::pairs() { return _targetsBrowsers; } @@ -387,7 +388,7 @@ int SkyBrowserModule::nPairs() const { return static_cast(_targetsBrowsers.size()); } -TargetBrowserPair* SkyBrowserModule::pair(const std::string& id) const { +TargetBrowserPair* SkyBrowserModule::pair(std::string_view id) const { auto it = std::find_if( _targetsBrowsers.begin(), _targetsBrowsers.end(), @@ -416,7 +417,7 @@ void SkyBrowserModule::startRotatingCamera(glm::dvec3 endAnimation) { void SkyBrowserModule::incrementallyRotateCamera() { if (_cameraRotation.isAnimating()) { - glm::dmat4 rotMat = _cameraRotation.getRotationMatrix(); + glm::dmat4 rotMat = _cameraRotation.rotationMatrix(); global::navigationHandler->camera()->rotate(glm::quat_cast(rotMat)); } } @@ -445,9 +446,9 @@ std::string SkyBrowserModule::wwtImageCollectionUrl() const { return _wwtImageCollectionUrl; } -void SkyBrowserModule::setSelectedBrowser(const std::string& id) { - TargetBrowserPair* found = pair(id); - if (found) { +void SkyBrowserModule::setSelectedBrowser(std::string_view id) { + TargetBrowserPair* p = pair(id); + if (p) { _selectedBrowser = id; } } diff --git a/modules/skybrowser/skybrowsermodule.h b/modules/skybrowser/skybrowsermodule.h index 939db00654..52c85970f9 100644 --- a/modules/skybrowser/skybrowsermodule.h +++ b/modules/skybrowser/skybrowsermodule.h @@ -25,24 +25,21 @@ #ifndef __OPENSPACE_MODULE_SKYBROWSER___SKYBROWSERMODULE___H__ #define __OPENSPACE_MODULE_SKYBROWSER___SKYBROWSERMODULE___H__ -#include - #include -#include -#include -#include + +#include +#include #include #include #include -#include +#include namespace openspace { -class ScreenSpaceImageLocal; -class WwtDataHandler; -class TargetBrowserPair; -class SceneGraphNode; struct ImageData; +class SceneGraphNode; +class ScreenSpaceImageLocal; +class TargetBrowserPair; class SkyBrowserModule : public OpenSpaceModule { public: @@ -50,25 +47,24 @@ public: SkyBrowserModule(); - std::vector>& getPairs(); + std::vector>& pairs(); int nPairs() const; - TargetBrowserPair* pair(const std::string& id) const; - const std::unique_ptr& getWwtDataHandler() const; + TargetBrowserPair* pair(std::string_view id) const; + const WwtDataHandler& wwtDataHandler() const; std::string selectedBrowserId() const; std::string selectedTargetId() const; int uniqueIdentifierCounter() const; - void setSelectedBrowser(const std::string& id); + void setSelectedBrowser(std::string_view id); void setHoverCircle(SceneGraphNode* circle); // Rotation, animation, placement void lookAtTarget(const std::string& id); void startRotatingCamera(glm::dvec3 endAnimation); // Pass in galactic coordinate - void incrementallyRotateCamera(); - void incrementallyAnimateTargets(); double targetAnimationSpeed() const; double browserAnimationSpeed() const; double spaceCraftAnimationTime() const; + std::string wwtImageCollectionUrl() const; bool isCameraInSolarSystem() const; @@ -94,6 +90,9 @@ protected: void internalInitialize(const ghoul::Dictionary& dict) override; private: + void incrementallyRotateCamera(); + void incrementallyAnimateTargets(); + properties::BoolProperty _enabled; properties::BoolProperty _showTitleInGuiBrowser; properties::BoolProperty _allowCameraRotation; @@ -108,7 +107,7 @@ private: // The browsers and targets std::vector> _targetsBrowsers; SceneGraphNode* _hoverCircle = nullptr; - std::string _selectedBrowser = ""; // Currently selected browser + std::string _selectedBrowser; // Currently selected browser int _uniqueIdentifierCounter = 0; // Flags @@ -119,7 +118,7 @@ private: skybrowser::Animation(glm::dvec3(0.0), glm::dvec3(0.0), 0.0); // Data handler for the image collections - std::unique_ptr _dataHandler; + WwtDataHandler _dataHandler; }; } // namespace openspace diff --git a/modules/skybrowser/skybrowsermodule_lua.inl b/modules/skybrowser/skybrowsermodule_lua.inl index 088dff0a33..25b1c8a769 100644 --- a/modules/skybrowser/skybrowsermodule_lua.inl +++ b/modules/skybrowser/skybrowsermodule_lua.inl @@ -52,7 +52,7 @@ namespace { if (module->isCameraInSolarSystem()) { TargetBrowserPair* selected = module->pair(module->selectedBrowserId()); if (selected) { - const ImageData& image = module->getWwtDataHandler()->getImage(imageIndex); + const ImageData& image = module->wwtDataHandler().image(imageIndex); // Load image into browser std::string str = image.name; // Check if character is ASCII - if it isn't, remove @@ -147,7 +147,7 @@ namespace { TargetBrowserPair* pair = module->pair(identifier); if (pair) { pair->hideChromeInterface(); - pair->loadImageCollection(module->wwtImageCollectionUrl()); + pair->browser()->loadImageCollection(module->wwtImageCollectionUrl()); } } @@ -162,7 +162,7 @@ namespace { // Set all border colors to the border color in the master node if (global::windowDelegate->isMaster()) { SkyBrowserModule* module = global::moduleEngine->module(); - const std::vector>& pairs = module->getPairs(); + const std::vector>& pairs = module->pairs(); for (const std::unique_ptr& pair : pairs) { std::string id = pair->browserId(); glm::ivec3 color = pair->borderColor(); @@ -194,7 +194,7 @@ namespace { // This is called when the sky_browser website is connected to OpenSpace // Send out identifiers to the browsers SkyBrowserModule* module = global::moduleEngine->module(); - const std::vector>& pairs = module->getPairs(); + const std::vector>& pairs = module->pairs(); for (const std::unique_ptr& pair : pairs) { pair->sendIdToBrowser(); } @@ -257,9 +257,9 @@ namespace { // Send image list to GUI SkyBrowserModule* module = global::moduleEngine->module(); std::string url = module->wwtImageCollectionUrl(); - // If no data has been loaded yet, download the data from the web! + // If no data has been loaded yet, download the data from the web if (module->nLoadedImages() == 0) { - std::filesystem::path directory = absPath("${MODULE_SKYBROWSER}/wwtimagedata/"); + std::filesystem::path directory = absPath("${SYNC}/wwtimagedata/"); module->loadImages(url, directory); } @@ -267,7 +267,7 @@ namespace { ghoul::Dictionary list; for (int i = 0; i < module->nLoadedImages(); i++) { - const ImageData& img = module->getWwtDataHandler()->getImage(i); + const ImageData& img = module->wwtDataHandler().image(i); // Push ("Key", value) ghoul::Dictionary image; @@ -327,7 +327,7 @@ namespace { // Pass data for all the browsers and the corresponding targets if (module->isCameraInSolarSystem()) { - const std::vector>& pairs = module->getPairs(); + const std::vector>& pairs = module->pairs(); for (const std::unique_ptr& pair : pairs) { std::string id = pair->browserId(); @@ -561,7 +561,7 @@ namespace { SkyBrowserModule* module = global::moduleEngine->module(); TargetBrowserPair* pair = module->pair(identifier); if (pair) { - pair->removeSelectedImage(imageIndex); + pair->browser()->removeSelectedImage(imageIndex); } } @@ -725,7 +725,7 @@ namespace { std::for_each( images.rbegin(), images.rend(), [&](int index) { - const ImageData& image = module->getWwtDataHandler()->getImage(index); + const ImageData& image = module->wwtDataHandler().image(index); // Index of image is used as layer ID as it is unique in the image data set pair->browser()->addImageLayerToWwt(image.imageUrl, index); } @@ -740,7 +740,7 @@ namespace { [[codegen::luawrap]] void showAllTargetsAndBrowsers(bool show) { using namespace openspace; SkyBrowserModule* module = global::moduleEngine->module(); - const std::vector>& pairs = module->getPairs(); + const std::vector>& pairs = module->pairs(); for (const std::unique_ptr& pair : pairs) { pair->setEnabled(show); } diff --git a/modules/skybrowser/src/browser.cpp b/modules/skybrowser/src/browser.cpp index 90e942a6f5..fd8553e594 100644 --- a/modules/skybrowser/src/browser.cpp +++ b/modules/skybrowser/src/browser.cpp @@ -55,9 +55,6 @@ namespace { }; struct [[codegen::Dictionary(Browser)]] Parameters { - // [[codegen::verbatim(DimensionsInfo.description)]] - std::optional dimensions; - // [[codegen::verbatim(UrlInfo.description)]] std::optional url; @@ -66,7 +63,6 @@ namespace { }; #include "browser_codegen.cpp" - } // namespace namespace openspace { @@ -80,29 +76,21 @@ void Browser::RenderHandler::setTexture(GLuint t) { } Browser::Browser(const ghoul::Dictionary& dictionary) - : _browserPixeldimensions( + : _browserDimensions( DimensionsInfo, - glm::vec2(500.f), + global::windowDelegate->currentSubwindowSize(), glm::vec2(10.f), glm::vec2(3000.f) ) , _url(UrlInfo) , _reload(ReloadInfo) { - if (dictionary.hasValue(UrlInfo.identifier)) { - _url = dictionary.value(UrlInfo.identifier); - } - - // Handle target dimension property const Parameters p = codegen::bake(dictionary); + _url = p.url.value_or(_url); - _browserPixeldimensions = p.dimensions.value_or(_browserPixeldimensions); - - glm::vec2 windowDimensions = global::windowDelegate->currentSubwindowSize(); - _browserPixeldimensions = windowDimensions; - _url.onChange([this]() { _isUrlDirty = true; }); - _browserPixeldimensions.onChange([this]() { _isDimensionsDirty = true; }); + + _browserDimensions.onChange([this]() { _isDimensionsDirty = true; }); _reload.onChange([this]() { _shouldReload = true; }); // Create browser and render handler @@ -121,9 +109,9 @@ Browser::Browser(const ghoul::Dictionary& dictionary) Browser::~Browser() {} -bool Browser::initializeGL() { +void Browser::initializeGL() { _texture = std::make_unique( - glm::uvec3(glm::ivec2(_browserPixeldimensions.value()), 1), + glm::uvec3(glm::ivec2(_browserDimensions.value()), 1), GL_TEXTURE_2D ); @@ -131,7 +119,6 @@ bool Browser::initializeGL() { _browserInstance->initialize(); _browserInstance->loadUrl(_url); - return isReady(); } void Browser::deinitializeGL() { @@ -164,11 +151,11 @@ void Browser::update() { _browserInstance->loadUrl(_url); _isUrlDirty = false; } + if (_isDimensionsDirty) { - if (_browserPixeldimensions.value().x > 0 && - _browserPixeldimensions.value().y > 0) - { - _browserInstance->reshape(_browserPixeldimensions.value()); + glm::vec2 dim = _browserDimensions; + if (dim.x > 0 && dim.y > 0) { + _browserInstance->reshape(dim); _isDimensionsDirty = false; } } @@ -184,12 +171,12 @@ bool Browser::isReady() const { } glm::vec2 Browser::browserPixelDimensions() const { - return _browserPixeldimensions; + return _browserDimensions; } // Updates the browser size to match the size of the texture void Browser::updateBrowserSize() { - _browserPixeldimensions = _texture->dimensions(); + _browserDimensions = _texture->dimensions(); } float Browser::browserRatio() const { @@ -198,23 +185,19 @@ float Browser::browserRatio() const { } void Browser::setCallbackDimensions(const std::function& func) { - _browserPixeldimensions.onChange([&]() { - func(_browserPixeldimensions.value()); + _browserDimensions.onChange([&]() { + func(_browserDimensions.value()); }); } void Browser::executeJavascript(const std::string& script) const { // Make sure that the browser has a main frame - const bool browserExists = _browserInstance && _browserInstance->getBrowser(); - const bool frameIsLoaded = browserExists && - _browserInstance->getBrowser()->GetMainFrame(); + bool browserExists = _browserInstance && _browserInstance->getBrowser(); + bool frameIsLoaded = browserExists && _browserInstance->getBrowser()->GetMainFrame(); if (frameIsLoaded) { - _browserInstance->getBrowser()->GetMainFrame()->ExecuteJavaScript( - script, - _browserInstance->getBrowser()->GetMainFrame()->GetURL(), - 0 - ); + CefRefPtr frame = _browserInstance->getBrowser()->GetMainFrame(); + frame->ExecuteJavaScript(script, frame->GetURL(), 0); } } diff --git a/modules/skybrowser/src/screenspaceskybrowser.cpp b/modules/skybrowser/src/screenspaceskybrowser.cpp index d73a164b5f..63cee8a767 100644 --- a/modules/skybrowser/src/screenspaceskybrowser.cpp +++ b/modules/skybrowser/src/screenspaceskybrowser.cpp @@ -116,7 +116,7 @@ ScreenSpaceSkyBrowser::ScreenSpaceSkyBrowser(const ghoul::Dictionary& dictionary addProperty(_isHidden); addProperty(_url); - addProperty(_browserPixeldimensions); + addProperty(_browserDimensions); addProperty(_reload); addProperty(_textureQuality); @@ -198,7 +198,7 @@ void ScreenSpaceSkyBrowser::updateTextureResolution() { float newResX = newResY * _ratio; glm::vec2 newSize = glm::vec2(newResX , newResY) * _textureQuality.value(); - _browserPixeldimensions = glm::ivec2(newSize); + _browserDimensions = glm::ivec2(newSize); _texture->setDimensions(glm::ivec3(newSize, 1)); _objectSize = glm::ivec3(_texture->dimensions()); } diff --git a/modules/skybrowser/src/targetbrowserpair.cpp b/modules/skybrowser/src/targetbrowserpair.cpp index aa02d8766a..cdc74f04f1 100644 --- a/modules/skybrowser/src/targetbrowserpair.cpp +++ b/modules/skybrowser/src/targetbrowserpair.cpp @@ -40,6 +40,22 @@ #include #include +namespace { + void aimTargetGalactic(std::string id, glm::dvec3 direction) { + glm::dvec3 positionCelestial = glm::normalize(direction) * + openspace::skybrowser::CelestialSphereRadius; + + std::string script = fmt::format( + "openspace.setPropertyValueSingle('Scene.{}.Translation.Position', {});", + id, ghoul::to_string(positionCelestial) + ); + openspace::global::scriptEngine->queueScript( + script, + openspace::scripting::ScriptEngine::RemoteScripting::Yes + ); + } +} // namespace + namespace openspace { TargetBrowserPair::TargetBrowserPair(SceneGraphNode* targetNode, @@ -53,31 +69,10 @@ TargetBrowserPair::TargetBrowserPair(SceneGraphNode* targetNode, _targetRenderable = dynamic_cast(_targetNode->renderable()); } -TargetBrowserPair& TargetBrowserPair::operator=(TargetBrowserPair other) { - std::swap(_targetNode, other._targetNode); - std::swap(_browser, other._browser); - return *this; -} - void TargetBrowserPair::setImageOrder(int i, int order) { _browser->setImageOrder(i, order); } -void TargetBrowserPair::aimTargetGalactic(glm::dvec3 direction) { - std::string id = _targetNode->identifier(); - glm::dvec3 positionCelestial = glm::normalize(direction) * - skybrowser::CelestialSphereRadius; - - std::string script = fmt::format( - "openspace.setPropertyValueSingle('Scene.{}.Translation.Position', {});", - id, ghoul::to_string(positionCelestial) - ); - openspace::global::scriptEngine->queueScript( - script, - scripting::ScriptEngine::RemoteScripting::Yes - ); -} - void TargetBrowserPair::startFinetuningTarget() { _startTargetPosition = _targetNode->worldPosition(); } @@ -100,7 +95,7 @@ void TargetBrowserPair::fineTuneTarget(const glm::vec2& startMouse, ); glm::dvec3 translationWorld = endWorld - startWorld; - aimTargetGalactic(_startTargetPosition + translationWorld); + aimTargetGalactic(_targetNode->identifier(), _startTargetPosition + translationWorld); } void TargetBrowserPair::synchronizeAim() { @@ -116,11 +111,6 @@ void TargetBrowserPair::setEnabled(bool enable) { _targetRenderable->property("Enabled")->set(enable); } -void TargetBrowserPair::setOpacity(float opacity) { - _browser->property("Opacity")->set(opacity); - _targetRenderable->property("Opacity")->set(opacity); -} - bool TargetBrowserPair::isEnabled() const { return _targetRenderable->isEnabled() || _browser->isEnabled(); } @@ -165,10 +155,6 @@ std::string TargetBrowserPair::targetNodeId() const { return _targetNode->identifier(); } -float TargetBrowserPair::browserRatio() const { - return _browser->browserRatio(); -} - double TargetBrowserPair::verticalFov() const { return _browser->verticalFov(); } @@ -190,7 +176,7 @@ ghoul::Dictionary TargetBrowserPair::dataAsDictionary() const { res.setValue("roll", targetRoll()); res.setValue("color", borderColor()); res.setValue("cartesianDirection", cartesian); - res.setValue("ratio", static_cast(browserRatio())); + res.setValue("ratio", static_cast(_browser->browserRatio())); res.setValue("isFacingCamera", isFacingCamera()); res.setValue("isUsingRae", isUsingRadiusAzimuthElevation()); res.setValue("selectedImages", selectedImages()); @@ -248,19 +234,10 @@ void TargetBrowserPair::hideChromeInterface() { void TargetBrowserPair::sendIdToBrowser() const { _browser->setIdInBrowser(); } - -void TargetBrowserPair::updateBrowserSize() { - _browser->updateBrowserSize(); -} - std::vector> TargetBrowserPair::displayCopies() const { return _browser->displayCopies(); } -bool TargetBrowserPair::isImageCollectionLoaded() { - return _browser->isImageCollectionLoaded(); -} - void TargetBrowserPair::setVerticalFov(double vfov) { _browser->setVerticalFov(vfov); _targetRenderable->setVerticalFov(vfov); @@ -268,6 +245,7 @@ void TargetBrowserPair::setVerticalFov(double vfov) { void TargetBrowserPair::setEquatorialAim(const glm::dvec2& aim) { aimTargetGalactic( + _targetNode->identifier(), skybrowser::equatorialToGalactic(skybrowser::sphericalToCartesian(aim)) ); _browser->setEquatorialAim(aim); @@ -294,16 +272,16 @@ void TargetBrowserPair::setImageCollectionIsLoaded(bool isLoaded) { void TargetBrowserPair::incrementallyAnimateToCoordinate() { // Animate the target before the field of view starts to animate if (_targetAnimation.isAnimating()) { - aimTargetGalactic(_targetAnimation.getNewValue()); + aimTargetGalactic(_targetNode->identifier(), _targetAnimation.newValue()); } else if (!_targetAnimation.isAnimating() && _targetIsAnimating) { // Set the finished position - aimTargetGalactic(_targetAnimation.getNewValue()); + aimTargetGalactic(_targetNode->identifier(), _targetAnimation.newValue()); _fovAnimation.start(); _targetIsAnimating = false; } if (_fovAnimation.isAnimating()) { - _browser->setVerticalFov(_fovAnimation.getNewValue()); + _browser->setVerticalFov(_fovAnimation.newValue()); _targetRenderable->setVerticalFov(_browser->verticalFov()); } } @@ -377,20 +355,8 @@ bool TargetBrowserPair::isUsingRadiusAzimuthElevation() const { return _browser->isUsingRaeCoords(); } -SceneGraphNode* TargetBrowserPair::targetNode() const { - return _targetNode; -} - ScreenSpaceSkyBrowser* TargetBrowserPair::browser() const { return _browser; } -bool operator==(const TargetBrowserPair& lhs, const TargetBrowserPair& rhs) { - return lhs._targetNode == rhs._targetNode && lhs._browser == rhs._browser; -} - -bool operator!=(const TargetBrowserPair& lhs, const TargetBrowserPair& rhs) { - return !(lhs == rhs); -} - } // namespace openspace diff --git a/modules/skybrowser/src/utility.cpp b/modules/skybrowser/src/utility.cpp index 40549d58e8..831eebb59e 100644 --- a/modules/skybrowser/src/utility.cpp +++ b/modules/skybrowser/src/utility.cpp @@ -32,6 +32,20 @@ #include #include +namespace { + // Galactic coordinates are projected onto the celestial sphere + // Equatorial coordinates are unit length + // Conversion spherical <-> Cartesian + + // Conversion matrix - J2000 equatorial <-> galactic + // https://arxiv.org/abs/1010.3773v1 + constexpr glm::dmat3 ConversionMatrix = glm::dmat3( + -0.054875539390, 0.494109453633, -0.867666135681, // col 0 + -0.873437104725, -0.444829594298, -0.198076389622, // col 1 + -0.483834991775, 0.746982248696, 0.455983794523 // col 2 + ); +} // namespace + namespace openspace::skybrowser { // Converts from spherical coordinates in the unit of degrees to cartesian coordianates @@ -53,26 +67,26 @@ glm::dvec2 cartesianToSpherical(const glm::dvec3& coord) { double ra = atan2(coord.y, coord.x); double dec = atan2(coord.z, glm::sqrt((coord.x * coord.x) + (coord.y * coord.y))); - ra = ra > 0 ? ra : ra + glm::two_pi(); + ra = ra > 0.0 ? ra : ra + glm::two_pi(); glm::dvec2 celestialCoords = glm::dvec2(ra, dec); return glm::degrees(celestialCoords); } glm::dvec3 galacticToEquatorial(const glm::dvec3& coords) { - return glm::transpose(conversionMatrix) * glm::normalize(coords); + return glm::transpose(ConversionMatrix) * glm::normalize(coords); } glm::dvec3 equatorialToGalactic(const glm::dvec3& coords) { // On the unit sphere - glm::dvec3 rGalactic = conversionMatrix * glm::normalize(coords); + glm::dvec3 rGalactic = ConversionMatrix * glm::normalize(coords); return rGalactic; } glm::dvec3 localCameraToScreenSpace3d(const glm::dvec3& coords) { // Ensure that if the coord is behind the camera, // the converted coordinate will be there too - double zCoord = coords.z > 0 ? -ScreenSpaceZ : ScreenSpaceZ; + double zCoord = coords.z > 0.0 ? -ScreenSpaceZ : ScreenSpaceZ; // Calculate screen space coords x and y double tanX = coords.x / coords.z; @@ -91,15 +105,15 @@ glm::dvec3 localCameraToGalactic(const glm::dvec3& coords) { // Subtract camera position to get the view direction glm::dvec3 galactic = glm::dvec3(camMat * coordsVec4) - camPos; - return glm::normalize(galactic) * skybrowser::CelestialSphereRadius; + return glm::normalize(galactic) * CelestialSphereRadius; } glm::dvec3 localCameraToEquatorial(const glm::dvec3& coords) { // Calculate the galactic coordinate of the target direction // projected onto the celestial sphere glm::dvec3 camPos = global::navigationHandler->camera()->positionVec3(); - glm::dvec3 galactic = camPos + skybrowser::localCameraToGalactic(coords); - return skybrowser::galacticToEquatorial(galactic); + glm::dvec3 galactic = camPos + localCameraToGalactic(coords); + return galacticToEquatorial(galactic); } glm::dvec3 equatorialToLocalCamera(const glm::dvec3& coords) { @@ -117,8 +131,10 @@ glm::dvec3 galacticToLocalCamera(const glm::dvec3& coords) { } double targetRoll(const glm::dvec3& up, const glm::dvec3& forward) { - glm::dvec3 upJ2000 = skybrowser::galacticToEquatorial(up); - glm::dvec3 forwardJ2000 = skybrowser::galacticToEquatorial(forward); + constexpr glm::dvec3 NorthPole = glm::dvec3(0.0, 0.0, 1.0); + + glm::dvec3 upJ2000 = galacticToEquatorial(up); + glm::dvec3 forwardJ2000 = galacticToEquatorial(forward); glm::dvec3 crossUpNorth = glm::cross(upJ2000, NorthPole); double dotNorthUp = glm::dot(NorthPole, upJ2000); @@ -129,14 +145,15 @@ double targetRoll(const glm::dvec3& up, const glm::dvec3& forward) { glm::dvec3 cameraDirectionEquatorial() { // Get the view direction of the screen in cartesian J2000 coordinates - return galacticToEquatorial(cameraDirectionGalactic()); + glm::dvec3 camDirGalactic = cameraDirectionGalactic(); + return galacticToEquatorial(camDirGalactic); } glm::dvec3 cameraDirectionGalactic() { // Get the view direction of the screen in galactic coordinates glm::dvec3 camPos = global::navigationHandler->camera()->positionVec3(); glm::dvec3 view = global::navigationHandler->camera()->viewDirectionWorldSpace(); - glm::dvec3 galCoord = camPos + (skybrowser::CelestialSphereRadius * view); + glm::dvec3 galCoord = camPos + CelestialSphereRadius * view; return galCoord; } @@ -150,11 +167,10 @@ bool isCoordinateInView(const glm::dvec3& equatorial) { // Check if image coordinate is within current FOV glm::dvec3 localCamera = equatorialToLocalCamera(equatorial); glm::dvec3 coordsScreen = localCameraToScreenSpace3d(localCamera); - double r = static_cast(windowRatio()); - - bool isCoordInView = abs(coordsScreen.x) < r && abs(coordsScreen.y) < 1.f && - coordsScreen.z < 0; + double r = windowRatio(); + bool isCoordInView = + abs(coordsScreen.x) < r && abs(coordsScreen.y) < 1.f && coordsScreen.z < 0.f; return isCoordInView; } @@ -200,31 +216,18 @@ glm::dmat4 incrementalAnimationMatrix(const glm::dvec3& start, const glm::dvec3& } double sizeFromFov(double fov, glm::dvec3 worldPosition) { - // Calculate the size with trigonometry // /| // /_| Adjacent is the horizontal line, opposite the vertical // \ | Calculate for half the triangle first, then multiply with 2 // \| double adjacent = glm::length(worldPosition); - double opposite = 2 * adjacent * glm::tan(glm::radians(fov * 0.5)); + double opposite = 2.0 * adjacent * glm::tan(glm::radians(fov * 0.5)); return opposite; } template <> -float Animation::getNewValue() { - if (!isAnimating()) { - return _goal; - } - else { - float percentage = static_cast(percentageSpent()); - float diff = static_cast((_goal - _start) * ghoul::exponentialEaseOut(percentage)); - return _start + diff; - } -} - -template <> -double Animation::getNewValue() { +double Animation::newValue() const { if (!isAnimating()) { return _goal; } @@ -236,7 +239,7 @@ double Animation::getNewValue() { } template <> -glm::dmat4 Animation::getRotationMatrix() { +glm::dmat4 Animation::rotationMatrix() { if (!isAnimating()) { return glm::dmat4(1.0); } @@ -254,7 +257,7 @@ glm::dmat4 Animation::getRotationMatrix() { } template <> -glm::dvec3 Animation::getNewValue() { +glm::dvec3 Animation::newValue() const { if (!isAnimating()) { return _goal; } diff --git a/modules/skybrowser/src/wwtcommunicator.cpp b/modules/skybrowser/src/wwtcommunicator.cpp index 05c025497e..8f91b7bf91 100644 --- a/modules/skybrowser/src/wwtcommunicator.cpp +++ b/modules/skybrowser/src/wwtcommunicator.cpp @@ -32,6 +32,90 @@ namespace { constexpr std::string_view _loggerCat = "WwtCommunicator"; + + // WWT messages + ghoul::Dictionary moveCameraMessage(const glm::dvec2& celestCoords, double fov, + double roll) + { + using namespace std::string_literals; + + ghoul::Dictionary msg; + msg.setValue("event", "center_on_coordinates"s); + msg.setValue("ra", celestCoords.x); + msg.setValue("dec", celestCoords.y); + msg.setValue("fov", fov); + msg.setValue("roll", roll); + msg.setValue("instant", true); + return msg; + } + + ghoul::Dictionary loadCollectionMessage(const std::string& url) { + using namespace std::string_literals; + + ghoul::Dictionary msg; + msg.setValue("event", "load_image_collection"s); + msg.setValue("url", url); + msg.setValue("loadChildFolders", true); + return msg; + } + + ghoul::Dictionary setForegroundMessage(const std::string& name) { + using namespace std::string_literals; + + ghoul::Dictionary msg; + msg.setValue("event", "set_foreground_by_name"s); + msg.setValue("name", name); + return msg; + } + + ghoul::Dictionary addImageMessage(const std::string& id, const std::string& url) { + using namespace std::string_literals; + + ghoul::Dictionary msg; + msg.setValue("event", "image_layer_create"s); + msg.setValue("id", id); + msg.setValue("url", url); + msg.setValue("mode", "preloaded"s); + msg.setValue("goto", false); + return msg; + } + + ghoul::Dictionary removeImageMessage(const std::string& imageId) { + using namespace std::string_literals; + + ghoul::Dictionary msg; + msg.setValue("event", "image_layer_remove"s); + msg.setValue("id", imageId); + return msg; + } + + ghoul::Dictionary setImageOpacityMessage(const std::string& imageId, double opacity) { + using namespace std::string_literals; + + ghoul::Dictionary msg; + msg.setValue("event", "image_layer_set"s); + msg.setValue("id", imageId); + msg.setValue("setting", "opacity"s); + msg.setValue("value", opacity); + return msg; + } + + ghoul::Dictionary setLayerOrderMessage(const std::string& id, int order) { + static int MessageCounter = 0; + + // The lower the layer order, the more towards the back the image is placed + // 0 is the background + using namespace std::string_literals; + + ghoul::Dictionary msg; + msg.setValue("event", "image_layer_order"s); + msg.setValue("id", id); + msg.setValue("order", order); + msg.setValue("version", MessageCounter); + + MessageCounter++; + return msg; + } } // namespace namespace openspace { @@ -40,7 +124,27 @@ WwtCommunicator::WwtCommunicator(const ghoul::Dictionary& dictionary) : Browser(dictionary) {} -WwtCommunicator::~WwtCommunicator() {} +void WwtCommunicator::update() { + // Cap how messages are passed + std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); + std::chrono::system_clock::duration timeSinceLastUpdate = now - _lastUpdateTime; + + if (timeSinceLastUpdate > TimeUpdateInterval) { + if (_equatorialAimIsDirty) { + updateAim(); + _equatorialAimIsDirty = false; + } + if (_borderColorIsDirty) { + updateBorderColor(); + _borderColorIsDirty = false; + } + _lastUpdateTime = std::chrono::system_clock::now(); + } + if (_shouldReload) { + _isImageCollectionLoaded = false; + } + Browser::update(); +} void WwtCommunicator::selectImage(const std::string& url, int i) { // Ensure there are no duplicates @@ -66,7 +170,6 @@ void WwtCommunicator::addImageLayerToWwt(const std::string& url, int i) { void WwtCommunicator::removeSelectedImage(int i) { // Remove from selected list auto it = findSelectedImage(i); - if (it != _selectedImages.end()) { _selectedImages.erase(it); sendMessageToWwt(removeImageMessage(std::to_string(i))); @@ -74,23 +177,31 @@ void WwtCommunicator::removeSelectedImage(int i) { } void WwtCommunicator::sendMessageToWwt(const ghoul::Dictionary& msg) const { - std::string script = "sendMessageToWWT(" + ghoul::formatJson(msg) + ");"; - executeJavascript(script); + std::string m = ghoul::formatJson(msg); + executeJavascript(fmt::format("sendMessageToWWT({});", m)); } std::vector WwtCommunicator::selectedImages() const { std::vector selectedImagesVector; - for (const std::pair& image : _selectedImages) { - selectedImagesVector.push_back(image.first); - } + selectedImagesVector.resize(_selectedImages.size()); + std::transform( + _selectedImages.cbegin(), + _selectedImages.cend(), + selectedImagesVector.begin(), + [](const std::pair& image) { return image.first; } + ); return selectedImagesVector; } std::vector WwtCommunicator::opacities() const { std::vector opacities; - for (const std::pair& image : _selectedImages) { - opacities.push_back(image.second); - } + opacities.resize(_selectedImages.size()); + std::transform( + _selectedImages.cbegin(), + _selectedImages.cend(), + opacities.begin(), + [](const std::pair& image) { return image.second; } + ); return opacities; } @@ -103,12 +214,6 @@ void WwtCommunicator::setVerticalFov(double vfov) { _equatorialAimIsDirty = true; } -void WwtCommunicator::setWebpageBorderColor(glm::ivec3 color) const { - std::string stringColor = fmt::format("{},{},{}", color.x, color.y, color.z); - std::string scr = "document.body.style.backgroundColor = 'rgb(" + stringColor + ")';"; - executeJavascript(scr); -} - void WwtCommunicator::setEquatorialAim(glm::dvec2 equatorial) { _equatorialAim = std::move(equatorial); _equatorialAimIsDirty = true; @@ -120,7 +225,11 @@ void WwtCommunicator::setBorderColor(glm::ivec3 color) { } void WwtCommunicator::updateBorderColor() const { - setWebpageBorderColor(_borderColor); + std::string script = fmt::format( + "document.body.style.backgroundColor = 'rgb({},{},{})';", + _borderColor.x, _borderColor.y, _borderColor.z + ); + executeJavascript(script); } void WwtCommunicator::updateAim() const { @@ -130,8 +239,9 @@ void WwtCommunicator::updateAim() const { } glm::dvec2 WwtCommunicator::fieldsOfView() const { - glm::dvec2 browserFov = glm::dvec2(verticalFov() * browserRatio(), verticalFov()); - return browserFov; + const double vFov = verticalFov(); + const double hFov = vFov * browserRatio(); + return glm::dvec2(hFov, vFov); } bool WwtCommunicator::isImageCollectionLoaded() const { @@ -139,10 +249,11 @@ bool WwtCommunicator::isImageCollectionLoaded() const { } std::deque>::iterator WwtCommunicator::findSelectedImage(int i) { - auto it = std::find_if(_selectedImages.begin(), _selectedImages.end(), - [i](std::pair& pair) { - return (pair.first == i); - }); + auto it = std::find_if( + _selectedImages.begin(), + _selectedImages.end(), + [i](const std::pair& pair) { return pair.first == i; } + ); return it; } @@ -186,35 +297,13 @@ void WwtCommunicator::hideChromeInterface() const { executeJavascript(script); } -void WwtCommunicator::update() { - // Cap how messages are passed - std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); - std::chrono::system_clock::duration timeSinceLastUpdate = now - _lastUpdateTime; - - if (timeSinceLastUpdate > TimeUpdateInterval) { - if (_equatorialAimIsDirty) { - updateAim(); - _equatorialAimIsDirty = false; - } - if (_borderColorIsDirty) { - updateBorderColor(); - _borderColorIsDirty = false; - } - _lastUpdateTime = std::chrono::system_clock::now(); - } - if (_shouldReload) { - _isImageCollectionLoaded = false; - } - Browser::update(); -} - void WwtCommunicator::setImageCollectionIsLoaded(bool isLoaded) { _isImageCollectionLoaded = isLoaded; } void WwtCommunicator::setIdInBrowser(const std::string& id) const { - // Send ID to it's browser - executeJavascript("setId('" + id + "')"); + // Send ID to its browser + executeJavascript(fmt::format("setId('{}')", id)); } glm::ivec3 WwtCommunicator::borderColor() const { @@ -225,92 +314,4 @@ double WwtCommunicator::verticalFov() const { return _verticalFov; } -// WWT messages -ghoul::Dictionary WwtCommunicator::moveCameraMessage(const glm::dvec2& celestCoords, - double fov, double roll, - bool shouldMoveInstantly) const -{ - using namespace std::string_literals; - - ghoul::Dictionary msg; - msg.setValue("event", "center_on_coordinates"s); - msg.setValue("ra", celestCoords.x); - msg.setValue("dec", celestCoords.y); - msg.setValue("fov", fov); - msg.setValue("roll", roll); - msg.setValue("instant", shouldMoveInstantly); - return msg; -} - -ghoul::Dictionary WwtCommunicator::loadCollectionMessage(const std::string& url) const { - using namespace std::string_literals; - - ghoul::Dictionary msg; - msg.setValue("event", "load_image_collection"s); - msg.setValue("url", url); - msg.setValue("loadChildFolders", true); - return msg; -} - -ghoul::Dictionary WwtCommunicator::setForegroundMessage(const std::string& name) const { - using namespace std::string_literals; - - ghoul::Dictionary msg; - msg.setValue("event", "set_foreground_by_name"s); - msg.setValue("name", name); - return msg; -} - -ghoul::Dictionary WwtCommunicator::addImageMessage(const std::string& id, - const std::string& url) const -{ - using namespace std::string_literals; - - ghoul::Dictionary msg; - msg.setValue("event", "image_layer_create"s); - msg.setValue("id", id); - msg.setValue("url", url); - msg.setValue("mode", "preloaded"s); - msg.setValue("goto", false); - return msg; -} - -ghoul::Dictionary WwtCommunicator::removeImageMessage(const std::string& imageId) const { - using namespace std::string_literals; - - ghoul::Dictionary msg; - msg.setValue("event", "image_layer_remove"s); - msg.setValue("id", imageId); - return msg; -} - -ghoul::Dictionary WwtCommunicator::setImageOpacityMessage(const std::string& imageId, - double opacity) const -{ - using namespace std::string_literals; - - ghoul::Dictionary msg; - msg.setValue("event", "image_layer_set"s); - msg.setValue("id", imageId); - msg.setValue("setting", "opacity"s); - msg.setValue("value", opacity); - return msg; -} - -ghoul::Dictionary WwtCommunicator::setLayerOrderMessage(const std::string& id, int order) -{ - // The lower the layer order, the more towards the back the image is placed - // 0 is the background - using namespace std::string_literals; - - ghoul::Dictionary msg; - msg.setValue("event", "image_layer_order"s); - msg.setValue("id", id); - msg.setValue("order", order); - msg.setValue("version", messageCounter); - - messageCounter++; - return msg; -} - } // namespace openspace diff --git a/modules/skybrowser/src/wwtdatahandler.cpp b/modules/skybrowser/src/wwtdatahandler.cpp index 0f4399acd4..b5945d7a99 100644 --- a/modules/skybrowser/src/wwtdatahandler.cpp +++ b/modules/skybrowser/src/wwtdatahandler.cpp @@ -25,239 +25,316 @@ #include #include -#include #include -#include #include -#include -#include -#include -#include +#include + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsuggest-override" +#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" +#endif + +#include + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif namespace { constexpr std::string_view _loggerCat = "WwtDataHandler"; + + constexpr std::string_view Thumbnail = "Thumbnail"; + constexpr std::string_view Name = "Name"; + constexpr std::string_view ImageSet = "ImageSet"; + constexpr std::string_view Dec = "Dec"; + constexpr std::string_view RA = "RA"; + constexpr std::string_view Undefined = ""; + constexpr std::string_view Folder = "Folder"; + constexpr std::string_view Place = "Place"; + constexpr std::string_view ThumbnailUrl = "ThumbnailUrl"; + constexpr std::string_view Url = "Url"; + constexpr std::string_view Credits = "Credits"; + constexpr std::string_view CreditsUrl = "CreditsUrl"; + constexpr std::string_view ZoomLevel = "ZoomLevel"; + constexpr std::string_view DataSetType = "DataSetType"; + constexpr std::string_view Sky = "Sky"; + + bool hasAttribute(const tinyxml2::XMLElement* element, std::string_view name) { + std::string n = std::string(name); + return element->FindAttribute(n.c_str()); + } + + std::string attribute(const tinyxml2::XMLElement* element, std::string_view name) { + if (hasAttribute(element, name)) { + std::string n = std::string(name); + return element->FindAttribute(n.c_str())->Value(); + } + return std::string(Undefined); + } + + // Parsing and downloading of wtml files + bool downloadFile(const std::string& url, const std::filesystem::path& destination) { + using namespace openspace; + + HttpFileDownload wtmlRoot(url, destination, HttpFileDownload::Overwrite::Yes); + wtmlRoot.start(std::chrono::milliseconds(10000)); + return wtmlRoot.wait(); + } + + bool directoryExists(const std::filesystem::path& path) { + return std::filesystem::exists(path) && std::filesystem::is_directory(path); + } + + const tinyxml2::XMLElement* directChildNode(const tinyxml2::XMLElement* node, + std::string_view name) + { + while (node && node->Name() != name) { + node = node->FirstChildElement(); + } + return node; + } + + const tinyxml2::XMLElement* childNode(const tinyxml2::XMLElement* node, + std::string_view name) + { + const tinyxml2::XMLElement* child = node->FirstChildElement(); + + // Traverse the children and look at all their first child to find ImageSet + while (child) { + const tinyxml2::XMLElement* imageSet = directChildNode(child, name); + if (imageSet) { + return imageSet; + } + child = child->NextSiblingElement(); + } + return nullptr; + } + + std::string childNodeContentFromImageSet(const tinyxml2::XMLElement* imageSet, + std::string_view elementName) + { + // Find the thumbnail image url + // The thumbnail is the last node so traverse backwards for speed + std::string n = std::string(elementName); + const tinyxml2::XMLElement* child = imageSet->FirstChildElement(n.c_str()); + return child && child->GetText() ? child->GetText() : std::string(Undefined); + } + + std::string urlFromPlace(const tinyxml2::XMLElement* place) { + // If the place has a thumbnail url, return it + if (hasAttribute(place, Thumbnail)) { + return attribute(place, Thumbnail); + } + + // If the place doesn't have a thumbnail url data attribute, + // Load the image set it stores instead + const tinyxml2::XMLElement* imageSet = childNode(place, ImageSet); + + // If there is an imageSet, collect thumbnail url, if it doesn't contain an + // ImageSet, it doesn't have an url + return imageSet ? + childNodeContentFromImageSet(imageSet, ThumbnailUrl) : + std::string(Undefined); + } + + bool downloadWtmlFiles(const std::filesystem::path& directory, const std::string& url, + const std::string& fileName) + { + using namespace openspace; + // Download file from url + std::filesystem::path file = directory.string() + fileName + ".aspx"; + const bool success = downloadFile(url, file); + if (!success) { + LINFO(fmt::format( + "Could not download file '{}' to directory {}", url, directory + )); + return false; + } + + // Parse file to XML + auto document = std::make_unique(); + document->LoadFile(file.string().c_str()); + + // Search XML file for folders with urls + const tinyxml2::XMLElement* root = document->RootElement(); + const tinyxml2::XMLElement* element = root->FirstChildElement(Folder.data()); + const bool folderExists = element != nullptr; + const bool folderContainNoUrls = folderExists && !hasAttribute(element, Url); + + // If the file contains no folders, or there are folders but without urls, + // stop recursion + if (!folderExists || folderContainNoUrls) { + LINFO(fmt::format("Saving {}", url)); + return true; + } + + // Iterate through all the folders in the XML file + while (element && std::string(element->Value()) == Folder) { + // If folder contains urls, download and parse those urls + if (hasAttribute(element, Url) && hasAttribute(element, Name)) { + std::string urlAttr = attribute(element, Url); + std::string fileNameAttr = attribute(element, Name); + downloadWtmlFiles(directory, urlAttr, fileNameAttr); + } + element = element->NextSiblingElement(); + } + return true; + } + + std::optional loadImageFromNode( + const tinyxml2::XMLElement* node, + std::string collection) + { + using namespace openspace; + + // Collect the image set of the node. The structure is different depending on if + // it is a Place or an ImageSet + std::string thumbnailUrl = std::string(Undefined); + const tinyxml2::XMLElement* imageSet = nullptr; + std::string type = node->Name(); + + if (type == ImageSet) { + thumbnailUrl = childNodeContentFromImageSet(node, ThumbnailUrl); + imageSet = node; + } + else if (type == Place) { + thumbnailUrl = urlFromPlace(node); + imageSet = childNode(node, ImageSet); + } + + // Only collect the images that have a thumbnail image, that are sky images and + // that have an image + const bool hasThumbnailUrl = thumbnailUrl != Undefined; + const bool isSkyImage = attribute(node, DataSetType) == Sky; + const bool hasImageUrl = imageSet ? hasAttribute(imageSet, Url) : false; + + if (!(hasThumbnailUrl && isSkyImage && hasImageUrl)) { + return std::nullopt; + } + + // Collect name, image url and credits + std::string name = attribute(node, Name); + if (std::islower(name[0])) { + // convert string to upper case + name[0] = static_cast(std::toupper(name[0])); + } + + std::string imageUrl = attribute(imageSet, Url); + std::string credits = childNodeContentFromImageSet(imageSet, Credits); + std::string creditsUrl = childNodeContentFromImageSet(imageSet, CreditsUrl); + + // Collect equatorial coordinates. All-sky surveys do not have these coordinates + bool hasCelestialCoords = hasAttribute(node, RA) && hasAttribute(node, Dec); + glm::dvec2 equatorialSpherical = glm::dvec2(0.0); + glm::dvec3 equatorialCartesian = glm::dvec3(0.0); + + if (hasCelestialCoords) { + // The RA from WWT is in the unit hours: + // to convert to degrees, multiply with 360 (deg) /24 (h) = 15 + double ra = 15.0 * std::stod(attribute(node, RA)); + double dec = std::stod(attribute(node, Dec)); + equatorialSpherical = glm::dvec2(ra, dec); + equatorialCartesian = skybrowser::sphericalToCartesian(equatorialSpherical); + } + + // Collect field of view. The WWT definition of ZoomLevel is: VFOV = ZoomLevel / 6 + float fov = 0.f; + if (hasAttribute(node, ZoomLevel)) { + fov = std::stof(attribute(node, ZoomLevel)) / 6.f; + } + + return ImageData{ + name, + thumbnailUrl, + imageUrl, + credits, + creditsUrl, + collection, + hasCelestialCoords, + fov, + equatorialSpherical, + equatorialCartesian + }; + } } //namespace namespace openspace { -bool hasAttribute(const tinyxml2::XMLElement* element, const std::string_view& name) { - return element->FindAttribute(std::string(name).c_str()); -} - -std::string attribute(const tinyxml2::XMLElement* element, const std::string& name) { - if (hasAttribute(element, name)) { - return element->FindAttribute(name.c_str())->Value(); - } - return wwt::Undefined; -} - -// Parsing and downloading of wtml files -bool downloadFile(const std::string& url, const std::filesystem::path& fileDestination) { - // Get the web page and save to file - HttpFileDownload wtmlRoot( - url, - fileDestination, - HttpFileDownload::Overwrite::Yes - ); - wtmlRoot.start(std::chrono::milliseconds(10000)); - return wtmlRoot.wait(); -} - -bool directoryExists(const std::filesystem::path& path) { - return std::filesystem::exists(path) && std::filesystem::is_directory(path); -} - -std::string createSearchableString(std::string str) { - // Remove white spaces and all special characters - str.erase( - std::remove_if( - str.begin(), str.end(), - [](char c) { - const bool isNumberOrLetter = std::isdigit(c) || std::isalpha(c); - return !isNumberOrLetter; - } - ), - str.end() - ); - // Make the word lower case - std::transform( - str.begin(), str.end(), - str.begin(), - [](char c) { return static_cast(std::tolower(c)); } - ); - return str; -} - -tinyxml2::XMLElement* getDirectChildNode(tinyxml2::XMLElement* node, - const std::string& name) -{ - while (node && node->Name() != name) { - node = node->FirstChildElement(); - } - return node; -} - -tinyxml2::XMLElement* getChildNode(tinyxml2::XMLElement* node, - const std::string& name) -{ - tinyxml2::XMLElement* child = node->FirstChildElement(); - - // Traverse the children and look at all their first child to find ImageSet - while (child) { - tinyxml2::XMLElement* imageSet = getDirectChildNode(child, name); - // Found - if (imageSet) { - return imageSet; - } - child = child->NextSiblingElement(); - } - return nullptr; -} - -std::string getChildNodeContentFromImageSet(tinyxml2::XMLElement* imageSet, - const std::string& elementName) -{ - // Find the thumbnail image url - // The thumbnail is the last node so traverse backwards for speed - tinyxml2::XMLElement* imageSetChild = - imageSet->FirstChildElement(elementName.c_str()); - - if (imageSetChild && imageSetChild->GetText()) { - return imageSetChild->GetText(); - } - else { - return wwt::Undefined; - } -} - -std::string getUrlFromPlace(tinyxml2::XMLElement* place) { - // If the place has a thumbnail url, return it - if (hasAttribute(place, wwt::Thumbnail)) { - return attribute(place, wwt::Thumbnail); - } - - // If the place doesn't have a thumbnail url data attribute, - // Load the image set it stores instead - tinyxml2::XMLElement* imageSet = getChildNode(place, wwt::ImageSet); - - // If there is an imageSet, collect thumbnail url - if (imageSet) { - return getChildNodeContentFromImageSet(imageSet, wwt::ThumbnailUrl); - } - else { - // If it doesn't contain an ImageSet, it doesn't have an url - return wwt::Undefined; - } -} - -void parseWtmlsFromDisc(std::vector& xmls, - const std::filesystem::path& directory) -{ - for (const auto& entry : std::filesystem::directory_iterator(directory)) { - tinyxml2::XMLDocument* document = new tinyxml2::XMLDocument(); - std::string path = entry.path().string(); - tinyxml2::XMLError successCode = document->LoadFile(path.c_str()); - - if (successCode == tinyxml2::XMLError::XML_SUCCESS) { - xmls.push_back(document); - } - } -} - -bool downloadAndParseWtmlFilesFromUrl(std::vector& xmls, - const std::filesystem::path& directory, - const std::string& url, const std::string& fileName) -{ - // Look for WWT image data folder, create folder if it doesn't exist - if (!directoryExists(directory)) { - std::string newDir = directory.string(); - // Remove the '/' at the end - newDir.pop_back(); - LINFO("Creating directory" + newDir); - std::filesystem::create_directory(newDir); - } - - // Download file from url - std::filesystem::path file = directory.string() + fileName + ".aspx"; - if (!downloadFile(url, file)) { - LINFO( - fmt::format("Couldn't download file '{}' to directory '{}'", url, directory) - ); - return false; - } - - // Parse file to XML - using namespace tinyxml2; - tinyxml2::XMLDocument* doc = new tinyxml2::XMLDocument(); - doc->LoadFile(file.string().c_str()); - - // Search XML file for folders with urls - XMLElement* root = doc->RootElement(); - XMLElement* element = root->FirstChildElement(wwt::Folder.c_str()); - const bool folderExists = element; - const bool folderContainNoUrls = folderExists && !hasAttribute(element, wwt::Url); - - // If the file contains no folders, or there are folders but without urls, - // stop recursion - if (!folderExists || folderContainNoUrls) { - xmls.push_back(doc); - LINFO("Saving " + url); - return true; - } - - // Iterate through all the folders in the XML file - while (element && std::string(element->Value()) == wwt::Folder) { - // If folder contains urls, download and parse those urls - if (hasAttribute(element, wwt::Url) && hasAttribute(element, wwt::Name)) { - std::string urlAttr = attribute(element, wwt::Url); - std::string fileNameAttr = attribute(element, wwt::Name); - downloadAndParseWtmlFilesFromUrl(xmls, directory, urlAttr, fileNameAttr); - } - element = element->NextSiblingElement(); - } - return true; -} - -WwtDataHandler::~WwtDataHandler() { - // Call destructor of all allocated xmls - _xmls.clear(); -} - void WwtDataHandler::loadImages(const std::string& root, const std::filesystem::path& directory) { - // Collect the wtml files, either by reading from disc or from a url - if (directoryExists(directory) && !std::filesystem::is_empty(directory)) { - parseWtmlsFromDisc(_xmls, directory); - LINFO("Loading images from directory"); - } - else { - downloadAndParseWtmlFilesFromUrl(_xmls, directory, root, "root"); - LINFO("Loading images from url"); + // Steps to download new images + // 1. Create the target directory if it doesn't already exist + // 2. If the 'root' has an associated hash file, download and compare it with the + // local file. If the hash has changed, nuke the folder + // 3. If the folder is empty, download files + + // 1. + if (!directoryExists(directory)) { + LINFO(fmt::format("Creating directory {}", directory)); + std::filesystem::create_directory(directory); } - // Traverse through the collected wtml documents and collect the images - for (tinyxml2::XMLDocument* doc : _xmls) { - tinyxml2::XMLElement* rootNode = doc->FirstChildElement(); - std::string collectionName = attribute(rootNode, wwt::Name); - saveImagesFromXml(rootNode, collectionName); + // Get the hash from the remote. If no such hash exists, the remoteHash will be empty + std::string remoteHash; + { + std::string remoteHashFile = root.substr(0, root.find_last_of('/')) + "/hash.md5"; + bool success = downloadFile(remoteHashFile, directory / "hash.tmp"); + // The hash download might fail if the provided 'root' does not have a hash + // in which case we assume that the underlying data has not changed + if (success) { + std::ifstream(directory / "hash.tmp") >> remoteHash; + std::filesystem::remove(directory / "hash.tmp"); + } + } + + // Load the local hash. If no such hash exists, the localHash will be empty + std::string localHash; + std::filesystem::path localHashFile = directory / "hash.md5"; + if (std::filesystem::exists(localHashFile)) { + std::ifstream(localHashFile) >> localHash; + } + + // Check if the hash has changed. This will be ignored if either the local of remote + // hash does not exist + if (!localHash.empty() && !remoteHash.empty() && localHash != remoteHash) { + LINFO(fmt::format( + "Local hash '{}' differs from remote hash '{}'. Cleaning directory", + localHash, remoteHash + )); + + std::filesystem::remove_all(directory); + std::filesystem::create_directory(directory); + } + + // If there is no directory (either because it is the first start, or the previous + // contents were deleted because of a change in hash) we have to download the files + if (std::filesystem::is_empty(directory)) { + LINFO("Loading images from url"); + downloadWtmlFiles(directory, root, "root"); + std::ofstream(localHashFile) << remoteHash; + } + + // Finally, we can load the files that are now on disk + LINFO("Loading images from directory"); + for (const auto& entry : std::filesystem::directory_iterator(directory)) { + tinyxml2::XMLDocument document; + std::string path = entry.path().string(); + tinyxml2::XMLError successCode = document.LoadFile(path.c_str()); + + if (successCode == tinyxml2::XMLError::XML_SUCCESS) { + tinyxml2::XMLElement* rootNode = document.FirstChildElement(); + std::string collectionName = attribute(rootNode, Name); + saveImagesFromXml(rootNode, collectionName); + } } // Sort images in alphabetical order std::sort( _images.begin(), _images.end(), - [](ImageData& a, ImageData& b) { - // If the first character in the names are lowercase, make it upper case - if (std::islower(a.name[0])) { - // convert string to upper case - a.name[0] = static_cast(::toupper(a.name[0])); - } - if (std::islower(b.name[0])) { - b.name[0] = static_cast(::toupper(b.name[0])); - } - return a.name < b.name; - } + [](ImageData& a, ImageData& b) { return a.name < b.name; } ); LINFO(fmt::format("Loaded {} WorldWide Telescope images", _images.size())); @@ -267,100 +344,33 @@ int WwtDataHandler::nLoadedImages() const { return static_cast(_images.size()); } -const ImageData& WwtDataHandler::getImage(int i) const { +const ImageData& WwtDataHandler::image(int i) const { ghoul_assert(i < static_cast(_images.size()), "Index outside of vector size"); return _images[i]; } -void WwtDataHandler::saveImageFromNode(tinyxml2::XMLElement* node, std::string collection) -{ - // Collect the image set of the node. The structure is different depending on if - // it is a Place or an ImageSet - std::string thumbnailUrl = wwt::Undefined; - tinyxml2::XMLElement* imageSet = nullptr; - std::string type = std::string(node->Name()); - - if (type == wwt::ImageSet) { - thumbnailUrl = getChildNodeContentFromImageSet(node, wwt::ThumbnailUrl); - imageSet = node; - } - else if (type == wwt::Place) { - thumbnailUrl = getUrlFromPlace(node); - imageSet = getChildNode(node, wwt::ImageSet); - } - - // Only collect the images that have a thumbnail image, that are sky images and - // that have an image - const bool hasThumbnailUrl = thumbnailUrl != wwt::Undefined; - const bool isSkyImage = attribute(node, wwt::DataSetType) == wwt::Sky; - const bool hasImageUrl = imageSet ? hasAttribute(imageSet, wwt::Url) : false; - - if (!(hasThumbnailUrl && isSkyImage && hasImageUrl)) { - return; - } - - // Collect name, image url and credits - std::string name = attribute(node, wwt::Name); - std::string imageUrl = attribute(imageSet, wwt::Url); - std::string credits = getChildNodeContentFromImageSet(imageSet, wwt::Credits); - std::string creditsUrl = getChildNodeContentFromImageSet(imageSet, wwt::CreditsUrl); - - // Collect equatorial coordinates. All-sky surveys do not have this kind of - // coordinate - bool hasCelestialCoords = hasAttribute(node, wwt::RA) && hasAttribute(node, wwt::Dec); - glm::dvec2 equatorialSpherical = glm::dvec2(0.0); - glm::dvec3 equatorialCartesian = glm::vec3(0.0); - - if (hasCelestialCoords) { - // The RA from WWT is in the unit hours: - // to convert to degrees, multiply with 360 (deg) /24 (h) = 15 - double ra = 15.0 * std::stod(attribute(node, wwt::RA)); - double dec = std::stod(attribute(node, wwt::Dec)); - equatorialSpherical = glm::dvec2(ra, dec); - equatorialCartesian = skybrowser::sphericalToCartesian(equatorialSpherical); - } - - // Collect field of view. The WWT definition of ZoomLevel is: VFOV = ZoomLevel / 6 - float fov = 0.f; - if (hasAttribute(node, wwt::ZoomLevel)) { - fov = std::stof(attribute(node, wwt::ZoomLevel)) / 6.0f; - } - - ImageData image = { - name, - thumbnailUrl, - imageUrl, - credits, - creditsUrl, - collection, - hasCelestialCoords, - fov, - equatorialSpherical, - equatorialCartesian - }; - - _images.push_back(image); -} - -void WwtDataHandler::saveImagesFromXml(tinyxml2::XMLElement* root, std::string collection) +void WwtDataHandler::saveImagesFromXml(const tinyxml2::XMLElement* root, + std::string collection) { // Get direct child of node called Place - using namespace tinyxml2; - XMLElement* node = root->FirstChildElement(); + const tinyxml2::XMLElement* node = root->FirstChildElement(); // Iterate through all siblings of node. If sibling is folder, open recursively. // If sibling is image, save it. while (node) { const std::string name = node->Name(); // If node is an image or place, load it - if (name == wwt::ImageSet || name == wwt::Place) { - saveImageFromNode(node, collection); + if (name == ImageSet || name == Place) { + std::optional image = loadImageFromNode(node, collection); + if (image.has_value()) { + _images.push_back(std::move(*image)); + } + } // If node is another folder, open recursively - else if (name == wwt::Folder) { - std::string newCollectionName = collection + "/"; - newCollectionName += attribute(node, wwt::Name); - + else if (name == Folder) { + std::string nodeName = attribute(node, Name); + std::string newCollectionName = fmt::format("{}/{}", collection, nodeName); saveImagesFromXml(node, newCollectionName); } node = node->NextSiblingElement(); diff --git a/src/rendering/renderable.cpp b/src/rendering/renderable.cpp index 4fc6dd66b4..90db17c018 100644 --- a/src/rendering/renderable.cpp +++ b/src/rendering/renderable.cpp @@ -267,6 +267,10 @@ bool Renderable::matchesRenderBinMask(int binMask) { return binMask & static_cast(renderBin()); } +void Renderable::setFade(float fade) { + _fade = fade; +} + bool Renderable::isVisible() const { return _enabled && _opacity > 0.f && _fade > 0.f; } diff --git a/src/rendering/renderengine.cpp b/src/rendering/renderengine.cpp index 761c490485..4803954ae2 100644 --- a/src/rendering/renderengine.cpp +++ b/src/rendering/renderengine.cpp @@ -1099,15 +1099,14 @@ void RenderEngine::removeScreenSpaceRenderable(ScreenSpaceRenderable* s) { } } -void RenderEngine::removeScreenSpaceRenderable(const std::string& identifier) { +void RenderEngine::removeScreenSpaceRenderable(std::string_view identifier) { ScreenSpaceRenderable* s = screenSpaceRenderable(identifier); if (s) { removeScreenSpaceRenderable(s); } } -ScreenSpaceRenderable* RenderEngine::screenSpaceRenderable(const std::string& identifier) -{ +ScreenSpaceRenderable* RenderEngine::screenSpaceRenderable(std::string_view identifier) { const auto it = std::find_if( global::screenSpaceRenderables->begin(), global::screenSpaceRenderables->end(), From 06c1c13f78c69b66c1b93bc25e1fc0ffb0a60e24 Mon Sep 17 00:00:00 2001 From: Emma Broman Date: Tue, 23 Aug 2022 13:54:57 +0200 Subject: [PATCH 3/4] Update compiler info to C++20 in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9be01673e1..b3ac1f86ab 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ This repository contains the source code and example profiles for OpenSpace, but Requirements for compiling are: - CMake version 3.10 or above - - C++ compiler supporting C++17 (MSVC 16.10, GCC9, Clang10) + - C++ compiler supporting C++20 (MSVC 19.31, GCC11, Clang14, AppleClang 13.1.6) - [Boost](http://www.boost.org/) - [Qt](http://www.qt.io/download) From c7bf728be8d5093b7231acbefdd804e275d3b2ce Mon Sep 17 00:00:00 2001 From: Emma Broman Date: Thu, 25 Aug 2022 08:44:31 +0200 Subject: [PATCH 4/4] Fix interpolation parameter being out of range (closes #2211) --- src/navigation/path.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/navigation/path.cpp b/src/navigation/path.cpp index 987ec92e55..10b9004471 100644 --- a/src/navigation/path.cpp +++ b/src/navigation/path.cpp @@ -316,6 +316,7 @@ glm::dquat Path::linearPathRotation(double t) const { } glm::dquat Path::lookAtTargetsRotation(double t) const { + t = glm::clamp(t, 0.0, 1.0); const double t1 = 0.2; const double t2 = 0.8; @@ -331,13 +332,15 @@ glm::dquat Path::lookAtTargetsRotation(double t) const { const glm::dvec3 viewDir = ghoul::viewDirection(_start.rotation()); const glm::dvec3 inFrontOfStart = startPos + inFrontDistance * viewDir; - const double tScaled = ghoul::cubicEaseInOut(t / t1); + const double tScaled = glm::clamp(t / t1, 0.0, 1.0); + const double tEased = ghoul::cubicEaseInOut(tScaled); lookAtPos = - ghoul::interpolateLinear(tScaled, inFrontOfStart, startNodePos); + ghoul::interpolateLinear(tEased, inFrontOfStart, startNodePos); } else if (t <= t2) { - const double tScaled = ghoul::cubicEaseInOut((t - t1) / (t2 - t1)); - lookAtPos = ghoul::interpolateLinear(tScaled, startNodePos, endNodePos); + const double tScaled = glm::clamp((t - t1) / (t2 - t1), 0.0, 1.0); + const double tEased = ghoul::cubicEaseInOut(tScaled); + lookAtPos = ghoul::interpolateLinear(tEased, startNodePos, endNodePos); } else { // (t > t2) @@ -346,8 +349,9 @@ glm::dquat Path::lookAtTargetsRotation(double t) const { const glm::dvec3 viewDir = ghoul::viewDirection(_end.rotation()); const glm::dvec3 inFrontOfEnd = endPos + inFrontDistance * viewDir; - const double tScaled = ghoul::cubicEaseInOut((t - t2) / (1.0 - t2)); - lookAtPos = ghoul::interpolateLinear(tScaled, endNodePos, inFrontOfEnd); + const double tScaled = glm::clamp((t - t2) / (1.0 - t2), 0.0, 1.0); + const double tEased = ghoul::cubicEaseInOut(tScaled); + lookAtPos = ghoul::interpolateLinear(tEased, endNodePos, inFrontOfEnd); } // Handle up vector separately @@ -416,6 +420,7 @@ double Path::speedAlongPath(double traveledDistance) const { const double remainingDistance = pathLength() - traveledDistance; dampeningFactor = remainingDistance / closeUpDistance; } + dampeningFactor = glm::clamp(dampeningFactor, 0.0, 1.0); dampeningFactor = ghoul::sineEaseOut(dampeningFactor); // Prevent multiplying with 0 (and hence a speed of 0.0 => no movement) @@ -706,7 +711,7 @@ Path createPathFromDictionary(const ghoul::Dictionary& dictionary, try { return Path(startPoint, waypointToAdd, type, duration); } - catch (const PathCurve::TooShortPathError& e) { + catch (const PathCurve::TooShortPathError&) { LINFO("Already at the requested target"); // Rethrow e, so the pathnavigator can handle it as well throw;