From d5422f429da85d0b198bdbbb1edc6462523a5bcb Mon Sep 17 00:00:00 2001 From: sylvass Date: Wed, 3 Nov 2021 10:37:20 -0400 Subject: [PATCH] Restructure and clean up --- data/assets/skyBrowserTargetPair.asset | 7 +- .../skybrowser/include/renderableskybrowser.h | 9 +- .../include/screenspaceskybrowser.h | 15 +- .../skybrowser/include/screenspaceskytarget.h | 7 + modules/skybrowser/include/utility.h | 19 +- modules/skybrowser/include/wwtdatahandler.h | 97 ++- modules/skybrowser/skybrowsermodule.cpp | 634 +++++++++--------- modules/skybrowser/skybrowsermodule.h | 94 ++- modules/skybrowser/skybrowsermodule_lua.inl | 363 +++++----- .../skybrowser/src/renderableskybrowser.cpp | 62 +- .../skybrowser/src/screenspaceskybrowser.cpp | 51 +- .../skybrowser/src/screenspaceskytarget.cpp | 91 ++- modules/skybrowser/src/utility.cpp | 80 ++- modules/skybrowser/src/wwtdatahandler.cpp | 554 ++++++++------- 14 files changed, 1154 insertions(+), 929 deletions(-) diff --git a/data/assets/skyBrowserTargetPair.asset b/data/assets/skyBrowserTargetPair.asset index 5fde6352b3..4a741655da 100644 --- a/data/assets/skyBrowserTargetPair.asset +++ b/data/assets/skyBrowserTargetPair.asset @@ -20,17 +20,16 @@ local target = { Identifier = targetId, Name = "Sky Target", FaceCamera = false, - BrowserID = browserId, + BrowserId = browserId, }; asset.onInitialize(function () openspace.addScreenSpaceRenderable(browser) openspace.addScreenSpaceRenderable(target) - openspace.skybrowser.addToSkyBrowserModule(browserId) - openspace.skybrowser.addToSkyBrowserModule(targetId) openspace.skybrowser.connectBrowserTarget(browserId) openspace.skybrowser.connectBrowserTarget(targetId) - openspace.skybrowser.setSelectedBrowser(browserId) + openspace.skybrowser.addPairToSkyBrowserModule(targetId,browserId) + openspace.skybrowser.setSelectedBrowser(browserId) end) asset.onDeinitialize(function () diff --git a/modules/skybrowser/include/renderableskybrowser.h b/modules/skybrowser/include/renderableskybrowser.h index 85daa90142..56d27923e6 100644 --- a/modules/skybrowser/include/renderableskybrowser.h +++ b/modules/skybrowser/include/renderableskybrowser.h @@ -57,18 +57,21 @@ namespace openspace { void setIdInBrowser(std::string id); // WorldWide Telescope communication - void displayImage(ImageData& image, int i); - void removeSelectedImage(ImageData& image, int i); + void displayImage(const ImageData& image, int i); + void removeSelectedImage(const ImageData& image, int i); bool sendMessageToWwt(const ghoul::Dictionary& msg); void syncWwtView(); void stopSyncingWwtView(); + // Place + void placeAt3dPosition(const ImageData& image); + // Getters float verticalFov() const; std::deque& getSelectedImages(); // Setters - void setImageLayerOrder(int i, int order, int version); + void setImageLayerOrder(int i, int order); private: // Properties diff --git a/modules/skybrowser/include/screenspaceskybrowser.h b/modules/skybrowser/include/screenspaceskybrowser.h index 52d716787b..9b81cfd5d2 100644 --- a/modules/skybrowser/include/screenspaceskybrowser.h +++ b/modules/skybrowser/include/screenspaceskybrowser.h @@ -5,6 +5,7 @@ #include #include #include +#include #include namespace openspace { @@ -25,12 +26,14 @@ namespace openspace { // Target - browser connection bool connectToSkyTarget(); void initializeBrowser(); + glm::dvec2 fineTuneTarget(glm::dvec2 drag); // Getters returning values bool hasLoadedImages() const; glm::vec2 browserPixelDimensions() const; glm::ivec3 borderColor() const; float verticalFov() const; + glm::dvec2 fieldsOfView(); // Getters returning references ScreenSpaceSkyTarget* getSkyTarget(); @@ -49,10 +52,14 @@ namespace openspace { void executeJavascript(std::string script); void sendIdToBrowser(); + // Display + void highlight(glm::ivec3 addition); + void removeHighlight(glm::ivec3 removal); + // Communication with WorldWide Telescope - void addSelectedImage(ImageData& image, int i); - void removeSelectedImage(ImageData& image, int i); - void setImageOrder(int i, int order, int version); + void addSelectedImage(const ImageData& image, int i); + void removeSelectedImage(const ImageData& image, int i); + void setImageOrder(int i, int order); void sendMessageToWwt(const ghoul::Dictionary& msg); void syncWwtView(); @@ -75,7 +82,7 @@ namespace openspace { properties::FloatProperty _verticalFov; properties::StringProperty _skyTargetId; properties::Vec2Property _browserDimensions; - properties::Vec3Property _borderColor; + properties::IVec3Property _borderColor; // Flags bool _hasLoadedImages{ false }; diff --git a/modules/skybrowser/include/screenspaceskytarget.h b/modules/skybrowser/include/screenspaceskytarget.h index 49fd66e66f..f42484986b 100644 --- a/modules/skybrowser/include/screenspaceskytarget.h +++ b/modules/skybrowser/include/screenspaceskytarget.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -60,11 +61,17 @@ namespace openspace { void animateToCoordinate(double deltaTime); bool animateToFov(float endFOV, float deltaTime); + // Display + void highlight(glm::ivec3 addition); + void removeHighlight(glm::ivec3 removal); + private: // Properties properties::StringProperty _skyBrowserId; properties::FloatProperty _showCrosshairThreshold; properties::FloatProperty _showRectangleThreshold; + properties::DoubleProperty _stopAnimationThreshold; + properties::DoubleProperty _animationSpeed; // Flags bool _isAnimated{ false }; diff --git a/modules/skybrowser/include/utility.h b/modules/skybrowser/include/utility.h index 32069a701f..bf7627aa19 100644 --- a/modules/skybrowser/include/utility.h +++ b/modules/skybrowser/include/utility.h @@ -23,29 +23,40 @@ namespace openspace { -0.483834991775, 0.746982248696, 0.455983794523 // col 2 }); + // Galactic coordinates are projected onto the celestial sphere + // Equatorial coordinates are unit length // Conversion spherical <-> Cartesian glm::dvec2 cartesianToSpherical(glm::dvec3 coords); glm::dvec3 sphericalToCartesian(glm::dvec2 coords); // Conversion J2000 equatorial <-> galactic glm::dvec3 galacticToEquatorial(glm::dvec3 coords); - glm::dvec3 galacticToCameraLocal(glm::dvec3 coords); + glm::dvec3 galacticToLocalCamera(glm::dvec3 coords); glm::dvec3 equatorialToGalactic(glm::dvec3 coords); // Conversion J2000 equatorial / galactic <-> screen space glm::dvec3 equatorialToScreenSpace(glm::dvec3 coords); glm::dvec3 galacticToScreenSpace(glm::dvec3 coords); - glm::dvec3 screenSpaceToGalactic(glm::dvec3 coords); - glm::dvec3 screenSpaceToEquatorial(glm::dvec3 coords); + glm::dvec3 localCameraToGalactic(glm::dvec3 coords); + glm::dvec3 localCameraToEquatorial(glm::dvec3 coords); // Camera roll and direction // Camera roll is with respect to the equatorial North Pole + bool coordinateIsOutsideView(glm::dvec3 equatorial); + float windowRatio(); double cameraRoll(); + glm::vec2 pixelToScreenSpace(glm::vec2& mouseCoordinate); + glm::dvec2 fovWindow(); glm::dvec3 cameraDirectionGalactic(); glm::dvec3 cameraDirectionEquatorial(); + double angleVector(glm::dvec3 start, glm::dvec3 end); + void incrementalAnimationMatrix(glm::dmat4& rotation, glm::dvec3 start, + glm::dvec3 end, double deltaTime, + double speedFactor = 1.0); } // WorldWide Telescope messages namespace wwtmessage { + inline int messageCounter{ 0 }; ghoul::Dictionary moveCamera(const glm::dvec2 celestCoords, const double fov, const double roll, const bool moveInstantly = true); ghoul::Dictionary loadCollection(const std::string& url); @@ -54,7 +65,7 @@ namespace openspace { ghoul::Dictionary removeImage(const std::string& id); ghoul::Dictionary setImageOpacity(const std::string& id, double opacity); ghoul::Dictionary setForegroundOpacity(double val); - ghoul::Dictionary setLayerOrder(const std::string& id, int order, int version); + ghoul::Dictionary setLayerOrder(const std::string& id, int version); } } diff --git a/modules/skybrowser/include/wwtdatahandler.h b/modules/skybrowser/include/wwtdatahandler.h index 45ab0a7a6e..97726c3512 100644 --- a/modules/skybrowser/include/wwtdatahandler.h +++ b/modules/skybrowser/include/wwtdatahandler.h @@ -4,13 +4,13 @@ #include #include #include +#include +#include // For speck loading #include #include #include #include -#include -#include namespace openspace::documentation { struct Documentation; } @@ -49,28 +49,41 @@ namespace openspace::speck { Dataset loadSpeckFile(std::filesystem::path path, SkipAllZeroLines skipAllZeroLines = SkipAllZeroLines::Yes); -} // namespace openspace::speck +} // namespace openspace::speck\ + +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 openspace { - struct ImageData { - std::string name; - std::string thumbnailUrl; - std::string imageUrl; - std::string creditsUrl; - std::string credits; - glm::dvec2 celestialCoords; - std::string collection; - float fov; + 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 }; bool hasCelestialCoords{ false }; bool has3dCoords{ false }; - glm::dvec3 position3d; - }; - - struct ImageCollection { - std::string name; - std::string url; - bool loaded = false; + float fov{ 0.f }; + glm::dvec2 equatorialSpherical{ 0.0 }; + glm::dvec3 equatorialCartesian{ 0.0 }; + glm::dvec3 position3d{ 0.0 }; }; class WwtDataHandler { @@ -80,54 +93,24 @@ namespace openspace { WwtDataHandler() = default; ~WwtDataHandler(); - // Image downloading and xml parsing - bool loadWtmlCollectionsFromUrl(std::string directory, std::string url, - std::string fileName); - bool loadWtmlCollectionsFromDirectory(std::string directory); - int loadImagesFromLoadedXmls(); - - // Loading speck files - void loadSpeckData(speck::Dataset& dataset); - - // Getters - const std::vector& getAllImageCollectionUrls() const; - std::vector& getLoadedImages(); + void loadImages(const std::string& root, const std::string& directory, + std::vector& speckFiles); + int nLoadedImages() const; + const ImageData& getImage(const int i) const; private: - // Parsing and downloading of wtml files - bool downloadFile(std::string& url, std::string& fileDestination); - void loadImagesFromXml(tinyxml2::XMLElement* node, - std::string collectionName); - int loadImageFromXmlNode(tinyxml2::XMLElement* imageSet, - std::string collectionName); - void setImageDataValues(tinyxml2::XMLElement* node, - std::string credits, - std::string creditsUrl, - std::string thumbnail, - std::string collectionName, - std::string imageUrl, - ImageData& img); - bool directoryExists(std::string& path); + void saveImageFromNode(tinyxml2::XMLElement* node, std::string collection); + void saveImagesFromXml(tinyxml2::XMLElement* root, std::string collection); - std::string getChildNodeContentFromImageSet(tinyxml2::XMLElement* imageSet, - std::string elementName); - std::string getUrlFromPlace(tinyxml2::XMLElement* place); - tinyxml2::XMLElement* getDirectChildNode(tinyxml2::XMLElement* node, - std::string name); - tinyxml2::XMLElement* getChildNode(tinyxml2::XMLElement* node, std::string name); - - // Used for matching names - std::string createSearchableString(std::string name); - // Images std::vector _images; - std::vector _imageUrls; std::vector _xmls; + int _nMatched3dPositions = 0; // 3D position data loaded from speck files std::unordered_map _3dPositions; - int _nImagesWith3dPositions = 0; + }; } diff --git a/modules/skybrowser/skybrowsermodule.cpp b/modules/skybrowser/skybrowsermodule.cpp index 4c3bf15d95..d70c9b5527 100644 --- a/modules/skybrowser/skybrowsermodule.cpp +++ b/modules/skybrowser/skybrowsermodule.cpp @@ -22,28 +22,28 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ -#include -#include - //#include - //#include + #include +#include #include #include #include #include #include -#include -#include +#include #include -#include #include - +#include +#include #include +#include +#include #include "skybrowsermodule_lua.inl" +#include +#include // For printing glm data #include #include #include // For atan2 -#include // For printing glm data #include #include @@ -211,8 +211,8 @@ namespace openspace { "input. An input string should be the name of the system host star" }, { - "addToSkyBrowserModule", - &skybrowser::luascriptfunctions::addToSkyBrowserModule, + "add3dBrowserToSkyBrowserModule", + &skybrowser::luascriptfunctions::add3dBrowserToSkyBrowserModule, {}, "string or list of strings", "Add one or multiple exoplanet systems to the scene, as specified by the " @@ -241,177 +241,65 @@ namespace openspace { "string or list of strings", "Add one or multiple exoplanet systems to the scene, as specified by the " "input. An input string should be the name of the system host star" + }, + { + "addPairToSkyBrowserModule", + &skybrowser::luascriptfunctions::addPairToSkyBrowserModule, + {}, + "string or list of strings", + "Add one or multiple exoplanet systems to the scene, as specified by the " + "input. An input string should be the name of the system host star" }, }; return res; } -// Transforms a pixel coordinate to a screen space coordinate -glm::vec2 pixelToScreenSpace(glm::vec2& mouseCoordinate) { - glm::vec2 size = global::windowDelegate->currentWindowSize(); - // Change origin to middle of the window - glm::vec2 screenSpacePos = glm::vec2((mouseCoordinate - (size / 2.0f))); - // Ensure the upper right corner is positive on the y axis - screenSpacePos *= glm::vec2(1.0f, -1.0f); - // Transform pixel coordinates to screen space coordinates [-1,1][-ratio, ratio] - screenSpacePos /= (0.5f * size.y); - return screenSpacePos; -} SkyBrowserModule::SkyBrowserModule() : OpenSpaceModule(SkyBrowserModule::Name) { - global::callback::mousePosition->emplace_back( - [&](double x, double y) { - - glm::vec2 pixel = glm::vec2(static_cast(x), static_cast(y)); - _mousePosition = pixelToScreenSpace(pixel); - - if (_isDragging) { - - glm::dvec2 move = _mousePosition - _startMousePosition; - - // Change view within the browser and move target accordingly to mouse drag movement - if (_fineTuneMode) { - // WWT FOV - double WWTVerticalFOV = toBrowser(_mouseOnObject)->verticalFov(); - glm::dvec2 browserDim = toBrowser(_mouseOnObject)->screenSpaceDimensions(); - double browserRatio = browserDim.x / browserDim.y; - glm::dvec2 WWTFOV = glm::dvec2(WWTVerticalFOV * browserRatio, WWTVerticalFOV); - - // OpenSpace FOV - glm::dvec2 windowDim = global::windowDelegate->currentWindowSize(); - double windowRatio = windowDim.y / windowDim.x; - double OpenSpaceHorizontalFOV = global::windowDelegate->getHorizFieldOfView(); - glm::dvec2 OpenSpaceFOV = glm::dvec2(OpenSpaceHorizontalFOV, OpenSpaceHorizontalFOV * windowRatio); - - glm::dvec2 angleResult = WWTFOV * (move / browserDim); - glm::dvec2 OSresult = angleResult / OpenSpaceFOV; - - // Calculate translation in ScreenSpaceCoordinates - glm::dvec2 screenSpaceCoord{ (2 / windowRatio), 2.f }; - glm::dvec2 result = screenSpaceCoord * OSresult; - - toBrowser(_mouseOnObject)->getSkyTarget()->translate(-result, _startDragPosition); - - } - // Move browser or target - else _mouseOnObject->translate(move, _startDragPosition); - - } - else if (_isResizing) { - // Calculate scaling factor - glm::vec2 mouseDragVector = (_mousePosition - _startMousePosition); - glm::vec2 scalingVector = mouseDragVector * _resizeDirection; - glm::vec2 newSizeRelToOld = (_startBrowserSize + (scalingVector)) / _startBrowserSize; - // Scale the browser - toBrowser(_mouseOnObject)->setScale(newSizeRelToOld); - - // For dragging functionality, translate so it looks like the browser isn't moving - // Make sure the browser doesn't move in directions it's not supposed to - _mouseOnObject->translate(mouseDragVector * abs(_resizeDirection) / 2.f, _startDragPosition); - } - // If there is no dragging or resizing, look for new objects - else { - // Save old selection for removing highlight - ScreenSpaceRenderable* lastObj = _mouseOnObject; - - // Find and save what mouse is currently hovering on - auto currentlyOnObject = std::find_if(_renderables.begin(), _renderables.end(), [&](ScreenSpaceRenderable* obj) { - return obj && (obj->coordIsInsideCornersScreenSpace(_mousePosition) && obj->isEnabled()); - }); - _mouseOnObject = currentlyOnObject != _renderables.end() ? *currentlyOnObject : nullptr; - - // Selection has changed - if (lastObj != _mouseOnObject) { - // Remove highlight - if (toBrowser(lastObj)) { - toBrowser(lastObj)->setWebpageBorderColor(toBrowser(lastObj)->borderColor() - _highlightAddition); - } - else if (toTarget(lastObj)) { - toTarget(lastObj)->setColor(toTarget(lastObj)->borderColor() - _highlightAddition); - } - - // Add highlight - if (toBrowser(_mouseOnObject)) { - toBrowser(_mouseOnObject)->setWebpageBorderColor(toBrowser(_mouseOnObject)->borderColor() + _highlightAddition); - } - else if (toTarget(_mouseOnObject)) { - toTarget(_mouseOnObject)->setColor(toTarget(_mouseOnObject)->borderColor() + _highlightAddition); - } - } - - } - } - ); - - global::callback::mouseScrollWheel->emplace_back( - [&](double, double scroll) -> bool { - - // If mouse is on browser or target, apply zoom - if (toBrowser(_mouseOnObject)) { - toBrowser(_mouseOnObject)->setVerticalFovWithScroll(static_cast(scroll)); - return true; - } - else if (toTarget(_mouseOnObject) && toTarget(_mouseOnObject)->getSkyBrowser()) { - toTarget(_mouseOnObject)->getSkyBrowser()->setVerticalFovWithScroll(static_cast(scroll)); - } - - return false; - } - ); - global::callback::mouseButton->emplace_back( [&](MouseButton button, MouseAction action, KeyModifier modifier) -> bool { - if (_mouseOnObject && action == MouseAction::Press) { + if (_mouseOnPair && action == MouseAction::Press) { // Get the currently selected browser - if (toBrowser(_mouseOnObject)) { - setSelectedBrowser(toBrowser(_mouseOnObject)); - } - else if (toTarget(_mouseOnObject) && - toTarget(_mouseOnObject)->getSkyBrowser()) { - - setSelectedBrowser(toTarget(_mouseOnObject)->getSkyBrowser()); - } + setSelectedBrowser(_mouseOnPair->getBrowser()); if (button == MouseButton::Left) { - _isRotating = false; + _cameraIsRotating = false; _startMousePosition = _mousePosition; - _startDragPosition = _mouseOnObject->screenSpacePosition(); + _startDragPosition = _isBrowser ? _mouseOnPair->getBrowser()->screenSpacePosition() + : _mouseOnPair->getTarget()->screenSpacePosition(); // If current object is browser, check for resizing - if (toBrowser(_mouseOnObject)) { + if (_isBrowser) { // Resize browser if mouse is over resize button - _resizeDirection = toBrowser(_mouseOnObject)->isOnResizeArea(_mousePosition); + _resizeDirection = _mouseOnPair->getBrowser()->isOnResizeArea(_mousePosition); if (_resizeDirection != glm::vec2{ 0 }) { - toBrowser(_mouseOnObject)->saveResizeStartSize(); - _startBrowserSize = toBrowser(_mouseOnObject)->screenSpaceDimensions(); + _mouseOnPair->getBrowser()->saveResizeStartSize(); + _startBrowserSize = _mouseOnPair->getBrowser()->screenSpaceDimensions(); _isResizing = true; return true; } } // If you start dragging around the target, it should unlock - if (toTarget(_mouseOnObject)) { - toTarget(_mouseOnObject)->unlock(); + else { + _mouseOnPair->getTarget()->unlock(); } _isDragging = true; return true; - } - else if (toBrowser(_mouseOnObject) && button == MouseButton::Right) { + } + else if (_isBrowser && button == MouseButton::Right) { // If you start dragging around on the browser, the target should unlock - if (toBrowser(_mouseOnObject) && toBrowser(_mouseOnObject)->getSkyTarget()) { - toBrowser(_mouseOnObject)->getSkyTarget()->unlock(); - } + _mouseOnPair->getTarget()->unlock(); // Change view (by moving target) within browser if right mouse click on browser _startMousePosition = _mousePosition; - _startDragPosition = toBrowser(_mouseOnObject)->getSkyTarget()->screenSpacePosition(); + _startDragPosition = _mouseOnPair->getTarget()->screenSpacePosition(); _fineTuneMode = true; - _isDragging = true; return true; } @@ -419,12 +307,16 @@ SkyBrowserModule::SkyBrowserModule() else if (action == MouseAction::Release) { if (_isDragging) { _isDragging = false; + + return true; + } + if (_fineTuneMode) { _fineTuneMode = false; return true; } if (_isResizing) { _isResizing = false; - toBrowser(_mouseOnObject)->updateBrowserSize(); + _mouseOnPair->getBrowser()->updateBrowserSize(); return true; } } @@ -433,24 +325,80 @@ SkyBrowserModule::SkyBrowserModule() } ); + global::callback::mousePosition->emplace_back( + [&](double x, double y) { + + if (_cameraInSolarSystem) { + glm::vec2 pixel{ static_cast(x), static_cast(y) }; + _mousePosition = skybrowser::pixelToScreenSpace(pixel); + glm::vec2 translation = _mousePosition - _startMousePosition; + + if (_isDragging || _isResizing) { + if (_isResizing) { + // Calculate scaling factor + glm::vec2 mouseDragVector = (_mousePosition - _startMousePosition); + glm::vec2 scalingVector = mouseDragVector * _resizeDirection; + glm::vec2 newSizeRelToOld = (_startBrowserSize + (scalingVector)) / _startBrowserSize; + // Scale the browser + _mouseOnPair->getBrowser()->setScale(newSizeRelToOld); + + // For dragging functionality, translate so it looks like the browser + // isn't moving. Make sure the browser doesn't move in directions it's + // not supposed to + translation = mouseDragVector * abs(_resizeDirection) / 2.f; + + } + // Translate + if (_isBrowser) { + _mouseOnPair->getBrowser()->translate(translation, _startDragPosition); + } + else { + _mouseOnPair->getTarget()->translate(translation, _startDragPosition); + } + } + else if (_fineTuneMode) { + glm::vec2 fineTune = _mouseOnPair->getBrowser()->fineTuneTarget(translation); + _mouseOnPair->getTarget()->translate(fineTune, _startDragPosition); + } + // If there is no dragging or resizing, look for new objects + else { + setSelectedObject(); + } + } + + } + ); + + global::callback::mouseScrollWheel->emplace_back( + [&](double, double scroll) -> bool { + + // If mouse is on browser or target, apply zoom + if (_mouseOnPair) { + _mouseOnPair->getBrowser()->setVerticalFovWithScroll(static_cast(scroll)); + return true; + } + + return false; + } + ); + + global::callback::preSync->emplace_back([this]() { // Disable browser and targets when camera is outside of solar system - double solarSystemRadius = 30.0 * distanceconstants::AstronomicalUnit; double cameraSSBDistance = glm::length( global::navigationHandler->camera()->positionVec3()); - _cameraInSolarSystem = cameraSSBDistance < solarSystemRadius; - double fadingTime = 2.0; + _cameraInSolarSystem = cameraSSBDistance < _solarSystemRadius; double deltaTime = global::windowDelegate->deltaTime(); // Fade out or in browser & target - for (std::pair pair : _browsers) { - ScreenSpaceSkyBrowser* browser = pair.second; + for (SkyBrowserModule::Pair pair : _targetsBrowsers) { + ScreenSpaceSkyBrowser* browser = pair.getBrowser(); // If outside solar system and browser is visible if (!_cameraInSolarSystem && browser->isEnabled()) { - bool fadingIsFinished = fadeBrowserAndTarget(true, fadingTime, deltaTime); + bool fadingIsFinished = fadeBrowserAndTarget(true, _fadingTime, deltaTime); if (fadingIsFinished) { - browser->property("Enabled")->set(false); + pair.getBrowser()->property("Enabled")->set(false); // Select the 3D browser when moving out of the solar system if (_browser3d != nullptr) { _selectedBrowser = _browser3d->renderable()->identifier(); @@ -459,25 +407,26 @@ SkyBrowserModule::SkyBrowserModule() } // If within solar system and browser is not visible else if (_cameraInSolarSystem && !browser->isEnabled()) { - browser->property("Enabled")->set(true); + pair.getBrowser()->property("Enabled")->set(true); // Select the first 2D browser when moving into the solar system - if (_browsers.size() != 0) { - _selectedBrowser = std::begin(_browsers)->second->identifier(); + if (_targetsBrowsers.size() != 0) { + _selectedBrowser = std::begin(_targetsBrowsers)->getBrowser()->identifier(); } } // If within solar system and browser is visible if (_cameraInSolarSystem && browser->isEnabled()) { - fadeBrowserAndTarget(false, fadingTime, deltaTime); + fadeBrowserAndTarget(false, _fadingTime, deltaTime); - if (browser->getSkyTarget()) { - browser->getSkyTarget()->animateToCoordinate(deltaTime); - } + pair.getTarget()->animateToCoordinate(deltaTime); } } - if (_isRotating) { + if (_cameraIsRotating) { rotateCamera(deltaTime); } }); + + _hoverCircle = dynamic_cast( + global::renderEngine->screenSpaceRenderable("HoverCircle")); } SkyBrowserModule::~SkyBrowserModule() { @@ -508,30 +457,58 @@ void SkyBrowserModule::internalInitialize(const ghoul::Dictionary& dict) { _dataHandler = new WwtDataHandler(); } -int SkyBrowserModule::getAndIncrementMessageOrder() { - return _messageOrder++; -} +void SkyBrowserModule::setSelectedObject() +{ + // Save old selection for removing highlight + Pair* lastObj = _mouseOnPair; -void SkyBrowserModule::addRenderable(ScreenSpaceRenderable* object) { - _renderables.push_back(object); - // Sort on z coordinate, objects closer to camera are in beginning of list - std::sort(_renderables.begin(), _renderables.end()); - ScreenSpaceSkyBrowser* browser = toBrowser(object); - if (browser) { - _browsers[browser->identifier()] = browser; + // Find and save what mouse is currently hovering on + auto it = std::find_if(std::begin(_targetsBrowsers), std::end(_targetsBrowsers),[&](Pair &pair) { + bool onBrowser = pair.getBrowser()->coordIsInsideCornersScreenSpace(_mousePosition); + bool onTarget = pair.getTarget()->coordIsInsideCornersScreenSpace(_mousePosition); + if (onBrowser || onTarget) { + _mouseOnPair = &pair; + _isBrowser = onBrowser; + return true; + } + return false; + }); + + if (it == std::end(_targetsBrowsers)) { + _mouseOnPair = nullptr; + } + + + // Selection has changed + if (lastObj != _mouseOnPair) { + + // Remove highlight + if (lastObj) { + lastObj->getBrowser()->removeHighlight(_highlightAddition); + lastObj->getTarget()->removeHighlight(_highlightAddition); + } + // Add highlight to new selection + if (_mouseOnPair) { + _mouseOnPair->getBrowser()->highlight(_highlightAddition); + _mouseOnPair->getTarget()->highlight(_highlightAddition); + std::string id = _mouseOnPair->getBrowser()->identifier(); + std::string info = "Currently selected browser is " + id; + LINFO(info + id); + } + } } -bool SkyBrowserModule::browserIdExists(std::string id) { - // If the id doesn't exist, return false - if (_browsers.find(id) == _browsers.end()) { - return false; +void SkyBrowserModule::addTargetBrowserPair(ScreenSpaceSkyTarget* newTarget, ScreenSpaceSkyBrowser* newBrowser) { + // Assert pair to have both target and browser + if (newBrowser && newTarget) { + Pair newPair(newBrowser, newTarget); + _targetsBrowsers.push_back(newPair); } - return true; } void SkyBrowserModule::createTargetBrowserPair() { - int noOfPairs = static_cast(getSkyBrowsers().size()) + 1; + int noOfPairs = static_cast(_targetsBrowsers.size()) + 1; std::string nameBrowser = "Sky Browser " + std::to_string(noOfPairs); std::string nameTarget = "Sky Target " + std::to_string(noOfPairs); std::string idBrowser = "SkyBrowser" + std::to_string(noOfPairs); @@ -547,7 +524,7 @@ void SkyBrowserModule::createTargetBrowserPair() { "Name = '" + nameBrowser + "'," "Url = '"+ url +"'," "FaceCamera = false," - "TargetID = '" + idTarget + "'," + "TargetId = '" + idTarget + "'," "CartesianPosition = " + ghoul::to_string(positionBrowser) + "," "}"; const std::string target = "{" @@ -555,7 +532,7 @@ void SkyBrowserModule::createTargetBrowserPair() { "Type = 'ScreenSpaceSkyTarget'," "Name = '" + nameTarget + "'," "FaceCamera = false," - "BrowserID = '" + idBrowser + "'," + "BrowserId = '" + idBrowser + "'," "}"; openspace::global::scriptEngine->queueScript( @@ -568,16 +545,6 @@ void SkyBrowserModule::createTargetBrowserPair() { scripting::ScriptEngine::RemoteScripting::Yes ); - openspace::global::scriptEngine->queueScript( - "openspace.skybrowser.addToSkyBrowserModule('" + idTarget + "');", - scripting::ScriptEngine::RemoteScripting::Yes - ); - - openspace::global::scriptEngine->queueScript( - "openspace.skybrowser.addToSkyBrowserModule('" + idBrowser + "');", - scripting::ScriptEngine::RemoteScripting::Yes - ); - openspace::global::scriptEngine->queueScript( "openspace.skybrowser.connectBrowserTarget('" + idBrowser + "');", scripting::ScriptEngine::RemoteScripting::Yes @@ -588,125 +555,133 @@ void SkyBrowserModule::createTargetBrowserPair() { scripting::ScriptEngine::RemoteScripting::Yes ); + openspace::global::scriptEngine->queueScript( + "openspace.skybrowser.addPairToSkyBrowserModule('" + idTarget + "','" + idBrowser + "');", + scripting::ScriptEngine::RemoteScripting::Yes + ); + openspace::global::scriptEngine->queueScript( "openspace.skybrowser.setSelectedBrowser('" + idBrowser + "');", scripting::ScriptEngine::RemoteScripting::Yes ); } -void SkyBrowserModule::removeTargetBrowserPair(std::string& browserId) { - if (!browserIdExists(browserId)) return; +void SkyBrowserModule::removeTargetBrowserPair(std::string& id) { - ScreenSpaceSkyBrowser* browser = _browsers[browserId]; - - // Find corresponding target - std::string targetId{ "" }; - bool hasTarget = browser->getSkyTarget(); - if (hasTarget) { - targetId = browser->getSkyTarget()->identifier(); + Pair* found = getPair(id); + if (!found) { + return; } - // Remove pointer to the renderable from browsers vector - _browsers.erase(browserId); + auto it = std::remove(std::begin(_targetsBrowsers), std::end(_targetsBrowsers), + *found); - // Remove pointer to the renderable from screenspace renderable vector - _renderables.erase(std::remove_if(std::begin(_renderables), std::end(_renderables), - [&](ScreenSpaceRenderable* renderable) { - if (renderable->identifier() == browserId) { - return true; - } - else if (renderable->identifier() == targetId) { - return true; - } - else { - return false; - } - }), std::end(_renderables)); + std::string browserId = found->getBrowser()->identifier(); + std::string targetId = found->getTarget()->identifier(); // Remove from engine openspace::global::scriptEngine->queueScript( "openspace.removeScreenSpaceRenderable('" + browserId + "');", scripting::ScriptEngine::RemoteScripting::Yes ); - if (hasTarget) { - openspace::global::scriptEngine->queueScript( - "openspace.removeScreenSpaceRenderable('" + targetId + "');", - scripting::ScriptEngine::RemoteScripting::Yes - ); - } + openspace::global::scriptEngine->queueScript( + "openspace.removeScreenSpaceRenderable('" + targetId + "');", + scripting::ScriptEngine::RemoteScripting::Yes + ); - _mouseOnObject = nullptr; + _targetsBrowsers.erase(it, std::end(_targetsBrowsers)); + _mouseOnPair = nullptr; } -void SkyBrowserModule::place3dBrowser(ImageData& image) { - if (_browser3d) { - std::string id = _browser3d->identifier(); - std::string renderableId = _browser3d->renderable()->identifier(); - // Uris for properties - std::string sizeUri = "Scene." + id + "." + renderableId + ".Size"; - std::string positionUri = "Scene." + id + ".Translation.Position"; - std::string rotationUri = "Scene." + id + ".Rotation.Rotation"; - std::string cameraAim = "NavigationHandler.OrbitalNavigator.Aim"; - glm::dvec3 position = image.position3d * distanceconstants::Parsec; - // Calculate the size of the plane with trigonometry - // Calculate in equatorial coordinate system since the FOV is from Earth - // /| - // /_| Adjacent is the horizontal line, opposite the vertical - // \ | Calculate for half the triangle first, then multiply with 2 - // \| - glm::dvec3 j2000 = skybrowser::galacticToEquatorial(position); - double adjacent = glm::length(j2000); - double opposite = 2 * adjacent * glm::tan(glm::radians(image.fov * 0.5)); - - // Calculate rotation to make the plane face the solar system barycenter - glm::dvec3 normal = glm::normalize(-position); - glm::dvec3 newRight = glm::normalize( - glm::cross(glm::dvec3(0.0, 0.0, 1.0), normal) - ); - glm::dvec3 newUp = glm::cross(normal, newRight); - // Face the Solar System Barycenter - glm::dmat3 rotation = glm::dmat3(1.0); - rotation[0] = newRight; - rotation[1] = newUp; - rotation[2] = normal; - - openspace::global::scriptEngine->queueScript( - "openspace.setPropertyValueSingle('" + sizeUri + "', " + std::to_string(opposite) + ");", - scripting::ScriptEngine::RemoteScripting::Yes - ); - openspace::global::scriptEngine->queueScript( - "openspace.setPropertyValueSingle('" + positionUri + "', " + ghoul::to_string(position) + ");", - scripting::ScriptEngine::RemoteScripting::Yes - ); - openspace::global::scriptEngine->queueScript( - "openspace.setPropertyValueSingle('" + rotationUri + "', " + ghoul::to_string(rotation) + ");", - scripting::ScriptEngine::RemoteScripting::Yes - ); - lookAt3dBrowser(); - - } -} void SkyBrowserModule::set3dBrowser(SceneGraphNode* node) { _browser3d = node; } -ScreenSpaceSkyBrowser* SkyBrowserModule::toBrowser(ScreenSpaceRenderable* ptr) { - return dynamic_cast(ptr); -} -ScreenSpaceSkyTarget* SkyBrowserModule::toTarget(ScreenSpaceRenderable* ptr) { - return dynamic_cast(ptr); +void SkyBrowserModule::selectImage2dBrowser(int i) +{ + Pair* selected = getPair(_selectedBrowser); + if (selected) { + + const ImageData& image = _dataHandler->getImage(i); + // Load image into browser + LINFO("Loading image " + image.name); + selected->getBrowser()->addSelectedImage(image, i); + + // If the image has coordinates, move the target + if (image.hasCelestialCoords) { + + // Animate the target to the image coordinate position + selected->getTarget()->unlock(); + selected->getTarget()->startAnimation(image.equatorialCartesian, image.fov); + + // If the coordinate is not in view, rotate camera + if (skybrowser::coordinateIsOutsideView(image.equatorialCartesian)) { + glm::dvec3 galactic = skybrowser::equatorialToGalactic(image.equatorialCartesian); + startRotation(galactic); + } + } + } } -WwtDataHandler* SkyBrowserModule::getWWTDataHandler() { + +void SkyBrowserModule::selectImage3dBrowser(int i) +{ + const ImageData& image = _dataHandler->getImage(i); + if (_browser3d) { + RenderableSkyBrowser* browser3d = dynamic_cast( + _browser3d->renderable()); + if (browser3d) { + browser3d->displayImage(image, i); + } + } +} + +void SkyBrowserModule::moveHoverCircle(int i) +{ + const ImageData& image = _dataHandler->getImage(i); + + // Only move and show circle if the image has coordinates + if (_hoverCircle && image.hasCelestialCoords && _cameraInSolarSystem) { + // Make circle visible + _hoverCircle->property("Enabled")->set(true); + + // Calculate coords for the circle and translate + glm::vec3 coordsScreen = skybrowser::equatorialToScreenSpace( + image.equatorialCartesian + ); + _hoverCircle->property("CartesianPosition")->set(coordsScreen); + } +} + +void SkyBrowserModule::loadImages(const std::string& root, const std::string& directory, + std::vector& speckFiles) +{ + _dataHandler->loadImages(root, directory, speckFiles); +} + +int SkyBrowserModule::nLoadedImages() +{ + return _dataHandler->nLoadedImages(); +} + +const WwtDataHandler* SkyBrowserModule::getWWTDataHandler() { return _dataHandler; } -std::map& SkyBrowserModule::getSkyBrowsers() { - return _browsers; +std::vector& SkyBrowserModule::getPairs() +{ + return _targetsBrowsers; } -std::vector& SkyBrowserModule::getBrowsersAndTargets() { - return _renderables; +SkyBrowserModule::Pair* SkyBrowserModule::getPair(std::string id) +{ + auto it = std::find_if(std::begin(_targetsBrowsers), std::end(_targetsBrowsers), + [&](Pair& pair) { + bool foundBrowser = pair.getBrowser()->identifier() == id; + bool foundTarget = pair.getTarget()->identifier() == id; + return foundBrowser || foundTarget; + }); + return &(*it); } SceneGraphNode* SkyBrowserModule::get3dBrowser() { @@ -717,15 +692,18 @@ void SkyBrowserModule::lookAt3dBrowser() { std::string id = _browser3d->identifier(); // Target camera on the 3D sky browser openspace::global::scriptEngine->queueScript( - "openspace.setPropertyValueSingle(\"NavigationHandler.OrbitalNavigator.RetargetAnchor\", nil)", + "openspace.setPropertyValueSingle(\"NavigationHandler.OrbitalNavigator." + "RetargetAnchor\", nil)", scripting::ScriptEngine::RemoteScripting::Yes ); openspace::global::scriptEngine->queueScript( - "openspace.setPropertyValueSingle(\"NavigationHandler.OrbitalNavigator.Anchor\", '" + id + "')", + "openspace.setPropertyValueSingle(\"NavigationHandler.OrbitalNavigator." + "Anchor\", '" + id + "')", scripting::ScriptEngine::RemoteScripting::Yes ); openspace::global::scriptEngine->queueScript( - "openspace.setPropertyValueSingle(\"NavigationHandler.OrbitalNavigator.Aim\", '')", + "openspace.setPropertyValueSingle(\"NavigationHandler.OrbitalNavigator." + "Aim\", '')", scripting::ScriptEngine::RemoteScripting::Yes ); } @@ -734,50 +712,66 @@ void SkyBrowserModule::startRotation(glm::dvec3 endAnimation) { // Save coordinates to rotate to in galactic world coordinates _endAnimation = endAnimation; _startAnimation = skybrowser::cameraDirectionGalactic(); - _isRotating = true; + _cameraIsRotating = true; } void SkyBrowserModule::rotateCamera(double deltaTime) { - + // Find smallest angle between the two vectors - double smallestAngle = std::acos(glm::dot(_startAnimation, _endAnimation) / (glm::length(_startAnimation) * glm::length(_endAnimation))); - // Only keep animating when target is not at final position - if (abs(smallestAngle) > 0.0001) { - // Calculate rotation this frame - double rotationAngle = smallestAngle * deltaTime; - // Create the rotation matrix for local camera space - glm::dvec3 rotationAxis = glm::normalize(glm::cross(_startAnimation, _endAnimation)); - glm::dmat4 rotmat = glm::rotate(rotationAngle, rotationAxis); + double smallestAngle = skybrowser::angleVector(_startAnimation, _endAnimation); + + if(smallestAngle > _threshold) { + + glm::dmat4 rotMat; + skybrowser::incrementalAnimationMatrix( + rotMat, + _startAnimation, + _endAnimation, + deltaTime, + _speed + ); + // Rotate - global::navigationHandler->camera()->rotate(glm::quat_cast(rotmat)); + global::navigationHandler->camera()->rotate(glm::quat_cast(rotMat)); // Update camera direction _startAnimation = skybrowser::cameraDirectionGalactic(); } else { - _isRotating = false; + _cameraIsRotating = false; } } -bool SkyBrowserModule::fadeBrowserAndTarget(bool makeTransparent, double fadeTime, double deltaTime) { +bool SkyBrowserModule::fadeBrowserAndTarget(bool makeTransparent, double fadeTime, + double deltaTime) { float opacityDelta = static_cast(deltaTime / fadeTime); float highTreshold = 0.99f; float lowThreshold = 0.01f; float transparent = 0.0; float opaque = 1.0; + if (makeTransparent) { opacityDelta *= -1.f; } bool finished = true; - for (std::pair idAndBrowser : _browsers) { - ScreenSpaceSkyBrowser* browser = idAndBrowser.second; - // If there is a target, fade it as well. Otherwise, skip - ScreenSpaceSkyTarget* target = browser->getSkyTarget(); + + for (Pair pair : _targetsBrowsers) { + ScreenSpaceSkyBrowser* browser = pair.getBrowser(); + ScreenSpaceSkyTarget* target = pair.getTarget(); + bool targetFinished = true; + bool browserFinished = true; + if (target) { target->setOpacity(target->opacity() + opacityDelta); float opacityTarget = abs(target->opacity()); - targetFinished = makeTransparent ? opacityTarget < lowThreshold : opacityTarget > highTreshold; + + if (makeTransparent) { + targetFinished = opacityTarget < lowThreshold; + } + else { + targetFinished = opacityTarget > highTreshold; + } if (targetFinished) { float newOpacity = makeTransparent ? transparent : opaque; target->setOpacity(newOpacity); @@ -786,13 +780,21 @@ bool SkyBrowserModule::fadeBrowserAndTarget(bool makeTransparent, double fadeTim // Keep fading the browsers until all are finished browser->getOpacity() = browser->getOpacity().value() + opacityDelta; float opacityBrowser = abs(browser->getOpacity().value()); - bool browserFinished = makeTransparent ? opacityBrowser < lowThreshold : opacityBrowser > highTreshold; + + if (makeTransparent) { + browserFinished = opacityBrowser < lowThreshold; + } + else { + browserFinished = opacityBrowser > highTreshold; + } + if (browserFinished && targetFinished) { browser->getOpacity() = makeTransparent ? transparent : opaque; } else { finished = false; } + } return finished; } @@ -811,37 +813,7 @@ std::string SkyBrowserModule::selectedBrowserId() { return _selectedBrowser; } -int SkyBrowserModule::loadImages(const std::string& root, const std::string& directory) { - - // Load speck files for 3D positions - std::filesystem::path globularClusters = absPath("${BASE}/sync/http/digitaluniverse_globularclusters_speck/2/gc.speck"); - std::filesystem::path openClusters = absPath("${BASE}/sync/http/digitaluniverse_openclusters_speck/2/oc.speck"); - speck::Dataset speckGlobularClusters = speck::loadSpeckFile(globularClusters); - speck::Dataset speckOpenClusters = speck::loadSpeckFile(openClusters); - - _dataHandler->loadSpeckData(speckGlobularClusters); - _dataHandler->loadSpeckData(speckOpenClusters); - - int nLoadedImages; - - // Read from disc - bool loadedImages = _dataHandler->loadWtmlCollectionsFromDirectory(directory); - - // Reading from url if there is no directory - if (loadedImages) { - LINFO("Loading images from directory"); - } - else { - LINFO("Loading images from url"); - _dataHandler->loadWtmlCollectionsFromUrl(directory, root, "root"); - } - - nLoadedImages = _dataHandler->loadImagesFromLoadedXmls(); - LINFO("Loaded " + std::to_string(nLoadedImages) + " WorldWide Telescope images."); - - return nLoadedImages; -} bool SkyBrowserModule::cameraInSolarSystem() { return _cameraInSolarSystem; diff --git a/modules/skybrowser/skybrowsermodule.h b/modules/skybrowser/skybrowsermodule.h index 7c41afda01..f8fec8fb67 100644 --- a/modules/skybrowser/skybrowsermodule.h +++ b/modules/skybrowser/skybrowsermodule.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -43,6 +44,7 @@ class ScreenSpaceSkyBrowser; class ScreenSpaceSkyTarget; class RenderableSkyBrowser; class ScreenSpaceRenderable; +class ScreenSpaceImageLocal; class WwtDataHandler; class SceneGraphNode; class ImageData; @@ -50,73 +52,117 @@ class ImageData; class SkyBrowserModule : public OpenSpaceModule { public: + constexpr static const char* Name = "SkyBrowser"; - + + class Pair { + public: + + Pair(ScreenSpaceSkyBrowser* browser, ScreenSpaceSkyTarget* target) + : _target(target), _browser(browser) {} + + Pair(Pair const&) = default; + + Pair& operator=(Pair other) + { + std::swap(_target, other._target); + std::swap(_browser, other._browser); + return *this; + } + + ScreenSpaceSkyTarget* getTarget() { + return _target; + } + + ScreenSpaceSkyBrowser* getBrowser() { + return _browser; + } + + friend bool operator==(const Pair& lhs, const Pair& rhs) { + return lhs._target == rhs._target && lhs._browser == rhs._browser; + } + friend bool operator!=(const Pair& lhs, const Pair& rhs) { + return !(lhs == rhs); + } + + private: + ScreenSpaceSkyTarget* _target{ nullptr }; + ScreenSpaceSkyBrowser* _browser{ nullptr }; + }; + // Constructor & destructor SkyBrowserModule(); virtual ~SkyBrowserModule(); // Getters - std::map& getSkyBrowsers(); - std::vector& getBrowsersAndTargets(); + std::vector& getPairs(); + Pair* getPair(std::string id); SceneGraphNode* get3dBrowser(); - WwtDataHandler* getWWTDataHandler(); + const WwtDataHandler* getWWTDataHandler(); std::string selectedBrowserId(); // Setters void setSelectedBrowser(ScreenSpaceSkyBrowser* ptr); void setSelectedBrowser(std::string id); void set3dBrowser(SceneGraphNode* node); - + void selectImage2dBrowser(int i); + void selectImage3dBrowser(int i); + // Rotation and animation - void startRotation(glm::dvec3 endAnimation); // Pass in galactic coord + void startRotation(glm::dvec3 endAnimation); // Pass in galactic coordinate void rotateCamera(double deltaTime); bool fadeBrowserAndTarget(bool makeTransparent, double fadeTime, double deltaTime); void lookAt3dBrowser(); // Boolean functions - bool browserIdExists(std::string id); bool cameraInSolarSystem(); // Managing the browsers void createTargetBrowserPair(); void removeTargetBrowserPair(std::string& browserId); - void addRenderable(ScreenSpaceRenderable* object); - void place3dBrowser(ImageData& image); + void addTargetBrowserPair(ScreenSpaceSkyTarget* target, ScreenSpaceSkyBrowser* browser); + void moveHoverCircle(int i); // Image collection handling - int loadImages(const std::string& root, const std::string& directory); - int getAndIncrementMessageOrder(); // For version handling calls to WWT + void loadImages(const std::string& root, const std::string& directory, + std::vector& speckFiles); + int nLoadedImages(); + + // Manage mouse interactions + void setSelectedObject(); scripting::LuaLibrary luaLibrary() const override; //std::vector documentations() const override; + protected: void internalInitialize(const ghoul::Dictionary& dict) override; void internalDeinitialize() override; private: - // Cast screen space renderable to either target or browser - ScreenSpaceSkyBrowser* toBrowser(ScreenSpaceRenderable* ptr); - ScreenSpaceSkyTarget* toTarget(ScreenSpaceRenderable* ptr); - // The browsers and targets - std::vector _renderables; // 2D browsers and targets - std::map _browsers; // Only the 2D browsers - ScreenSpaceRenderable* _mouseOnObject{ nullptr }; // Pointer to what mouse is currently on + std::vector _targetsBrowsers; + Pair* _mouseOnPair{ nullptr }; + Pair* _selectedPair{ nullptr }; + bool _isBrowser{ false }; + ScreenSpaceImageLocal* _hoverCircle{ nullptr }; SceneGraphNode* _browser3d{ nullptr }; - std::string _selectedBrowser; // Currently selected browser (2D or 3D) + std::string _selectedBrowser{ "" }; // Currently selected browser (2D or 3D) + + // 2D vs 3D visualization mode + double _solarSystemRadius = 30.0 * distanceconstants::AstronomicalUnit; + double _fadingTime = 2.0; // Flags bool _fineTuneMode{ false }; bool _isResizing{ false }; bool _isDragging{ false }; bool _cameraInSolarSystem{ true }; - bool _isRotating = false; + bool _cameraIsRotating = false; // Mouse interaction - dragging and resizing - glm::vec2 _mousePosition; // Current mouse position in screen space coordinates glm::ivec3 _highlightAddition{ 35 }; // Highlight object when mouse hovers + glm::vec2 _mousePosition; // Current mouse position in screen space coordinates glm::vec2 _startMousePosition; glm::vec2 _startDragPosition; glm::vec2 _startBrowserSize; @@ -125,11 +171,11 @@ private: // Animation of rotation of camera to look at coordinate galactic coordinates glm::dvec3 _startAnimation; glm::dvec3 _endAnimation; + double _threshold{ 0.0005 }; + double _speed{ 1.0 }; // Data handler for the image collections - WwtDataHandler* _dataHandler; - int _messageOrder{ 0 }; // Version handler for WorldWide Telescope messages - + WwtDataHandler* _dataHandler; }; } // namespace openspace diff --git a/modules/skybrowser/skybrowsermodule_lua.inl b/modules/skybrowser/skybrowsermodule_lua.inl index 02421ca2b6..2b6a7239ce 100644 --- a/modules/skybrowser/skybrowsermodule_lua.inl +++ b/modules/skybrowser/skybrowsermodule_lua.inl @@ -1,27 +1,26 @@ -#include +#include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include #include #include -#include -#include #include #include -#include +#include +#include #include +#include #include #include #include @@ -39,53 +38,12 @@ namespace openspace::skybrowser::luascriptfunctions { ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::selectImage"); const int i = ghoul::lua::value(L, 1); SkyBrowserModule* module = global::moduleEngine->module(); - // TO DO: Make this prettier with 3D browsers... - ScreenSpaceSkyBrowser* selectedBrowser = nullptr; - if (module->browserIdExists(module->selectedBrowserId())) { - selectedBrowser = module->getSkyBrowsers()[module->selectedBrowserId()]; - } - ImageData& image = module->getWWTDataHandler()->getLoadedImages()[i]; - if (selectedBrowser) { - // Load image into browser - LINFO("Loading image " + image.name); - selectedBrowser->addSelectedImage(image, i); - - ScreenSpaceSkyTarget* selectedTarget = selectedBrowser->getSkyTarget(); - // If the image has coordinates, move the target - if (image.hasCelestialCoords && selectedTarget) { - - // Animate the target to the image coord position - selectedTarget->unlock(); - glm::dvec3 equatorial = skybrowser::sphericalToCartesian(image.celestialCoords); - selectedTarget->startAnimation(equatorial, image.fov); - - // Check if image coordinate is within current FOV - glm::dvec3 coordsScreen = equatorialToScreenSpace(equatorial); - glm::vec2 windowRatio = global::windowDelegate->currentWindowSize(); - float r = windowRatio.x / windowRatio.y; - bool coordIsWithinView = (abs(coordsScreen.x) < r && - abs(coordsScreen.y) < 1.f && coordsScreen.z < 0); - bool coordIsBehindCamera = coordsScreen.z > 0; - // If the coordinate is not in view, rotate camera - if (!coordIsWithinView || coordIsBehindCamera) { - glm::dvec3 galactic = skybrowser::equatorialToGalactic(equatorial); - module->startRotation(galactic); - } - } + if (module->cameraInSolarSystem()) { + module->selectImage2dBrowser(i); } else { - SceneGraphNode* node = module->get3dBrowser(); - if (node) { - RenderableSkyBrowser* browser3d = dynamic_cast( - node->renderable()); - if (browser3d) { - browser3d->displayImage(image, i); - } - else { - LINFO("No browser selected!"); - } - } + module->selectImage3dBrowser(i); } return 0; @@ -96,19 +54,7 @@ namespace openspace::skybrowser::luascriptfunctions { ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::moveCircleToHoverImage"); const int i = ghoul::lua::value(L, 1); SkyBrowserModule* module = global::moduleEngine->module(); - const ImageData& image = module->getWWTDataHandler()->getLoadedImages()[i]; - - // Only move and show circle if the image has coordinates - if (image.hasCelestialCoords && module->cameraInSolarSystem()) { - // Make circle visible - ScreenSpaceImageLocal* hoverCircle = dynamic_cast( - global::renderEngine->screenSpaceRenderable("HoverCircle")); - hoverCircle->property("Enabled")->set(true); - // Calculate coords for the circle and translate - glm::dvec3 equatorialCartesian = skybrowser::sphericalToCartesian(image.celestialCoords); - glm::vec3 coordsScreen = skybrowser::equatorialToScreenSpace(equatorialCartesian); - hoverCircle->property("CartesianPosition")->set(coordsScreen); - } + module->moveHoverCircle(i); return 0; } @@ -128,11 +74,9 @@ namespace openspace::skybrowser::luascriptfunctions { ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::lockTarget"); const std::string id = ghoul::lua::value(L, 1); SkyBrowserModule* module = global::moduleEngine->module(); - if (module->browserIdExists(id)) { - ScreenSpaceSkyTarget* target = module->getSkyBrowsers()[id]->getSkyTarget(); - if (target) { - target->lock(); - } + if (module->getPair(id)) { + SkyBrowserModule::Pair* pair = module->getPair(id); + pair->getTarget()->lock(); } return 0; } @@ -141,11 +85,9 @@ namespace openspace::skybrowser::luascriptfunctions { ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::unlockTarget"); const std::string id = ghoul::lua::value(L, 1); SkyBrowserModule* module = global::moduleEngine->module(); - if (module->browserIdExists(id)) { - ScreenSpaceSkyTarget* target = module->getSkyBrowsers()[id]->getSkyTarget(); - if (target) { - target->unlock(); - } + if (module->getPair(id)) { + SkyBrowserModule::Pair* pair = module->getPair(id); + pair->getTarget()->unlock(); } return 0; } @@ -153,21 +95,19 @@ namespace openspace::skybrowser::luascriptfunctions { int setImageLayerOrder(lua_State* L) { ghoul::lua::checkArgumentsAndThrow(L, 3, "lua::setImageLayerOrder"); - const std::string browserId = ghoul::lua::value(L, 1); + const std::string id = ghoul::lua::value(L, 1); const int i = ghoul::lua::value(L, 2); int order = ghoul::lua::value(L, 3); SkyBrowserModule* module = global::moduleEngine->module(); - int version = module->getAndIncrementMessageOrder(); - if (module->browserIdExists(browserId)) { - ScreenSpaceSkyBrowser* browser = module->getSkyBrowsers()[browserId]; - - browser->setImageOrder(i, order, version); + if (module->getPair(id)) { + SkyBrowserModule::Pair* pair = module->getPair(id); + pair->getBrowser()->setImageOrder(i, order); } else if (module->get3dBrowser() != nullptr) { RenderableSkyBrowser* browser3d = dynamic_cast( module->get3dBrowser()->renderable()); - browser3d->setImageLayerOrder(i, order, version); + browser3d->setImageLayerOrder(i, order); } return 0; @@ -213,9 +153,9 @@ namespace openspace::skybrowser::luascriptfunctions { // Send out ID's to the browsers SkyBrowserModule* module = global::moduleEngine->module(); - std::map browsers = module->getSkyBrowsers(); - for (std::pair pair : browsers) { - pair.second->sendIdToBrowser(); + std::vector pairs = module->getPairs(); + for (SkyBrowserModule::Pair pair : pairs) { + pair.getBrowser()->sendIdToBrowser(); } SceneGraphNode* node = module->get3dBrowser(); if(node) { @@ -278,25 +218,39 @@ namespace openspace::skybrowser::luascriptfunctions { return 0; } - int addToSkyBrowserModule(lua_State* L) { - ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::addToSkyBrowserModule"); + int add3dBrowserToSkyBrowserModule(lua_State* L) { + ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::add3dBrowserToSkyBrowserModule"); const std::string id = ghoul::lua::value(L, 1); SkyBrowserModule* module = global::moduleEngine->module(); LINFO("Add to sky browser module id " + id); - ScreenSpaceRenderable* object = global::renderEngine->screenSpaceRenderable(id); - if (object) { + SceneGraphNode* node = global::renderEngine->scene()->sceneGraphNode(id); + if (node) { // Add to module - module->addRenderable(object); - } - else { - SceneGraphNode* node = global::renderEngine->scene()->sceneGraphNode(id); - if (node) { - // Add to module - module->set3dBrowser(node); - } + module->set3dBrowser(node); } + + return 0; + } + + int addPairToSkyBrowserModule(lua_State* L) { + ghoul::lua::checkArgumentsAndThrow(L, 2, "lua::addPairToSkyBrowserModule"); + const std::string targetId = ghoul::lua::value(L, 1); + const std::string browserId = ghoul::lua::value(L, 2); + SkyBrowserModule* module = global::moduleEngine->module(); + + LINFO("Add browser " + browserId + " to sky browser module."); + LINFO("Add target " + targetId + " to sky browser module."); + ScreenSpaceSkyTarget* target = dynamic_cast( + global::renderEngine->screenSpaceRenderable(targetId) + ); + ScreenSpaceSkyBrowser* browser = dynamic_cast( + global::renderEngine->screenSpaceRenderable(browserId) + ); + + + module->addTargetBrowserPair(target, browser); return 0; } @@ -306,50 +260,60 @@ namespace openspace::skybrowser::luascriptfunctions { ghoul::lua::checkArgumentsAndThrow(L, 0, "lua::getListOfImages"); SkyBrowserModule* module = global::moduleEngine->module(); - std::string root = "https://raw.githubusercontent.com/WorldWideTelescope/wwt-web-client/master/assets/webclient-explore-root.wtml"; - std::string hubble = "http://www.worldwidetelescope.org/wwtweb/catalog.aspx?W=hubble"; - std::string directory = absPath("${MODULE_SKYBROWSER}/WWTimagedata/"); - // If no data has been loaded yet, download the data from the web! - if (module->getWWTDataHandler()->getLoadedImages().size() == 0) { - module->loadImages(root, directory); + + if (module->nLoadedImages() == 0) { + std::string root = "https://raw.githubusercontent.com/WorldWideTelescope/wwt-web-client/master/assets/webclient-explore-root.wtml"; + //std::string hubble = "http://www.worldwidetelescope.org/wwtweb/catalog.aspx?W=hubble"; + std::string directory = absPath("${MODULE_SKYBROWSER}/WWTimagedata/"); + + // 3D images + std::string http = "${BASE}/sync/http/"; + std::string globular = "digitaluniverse_globularclusters_speck/2/gc.speck"; + std::string open = "digitaluniverse_openclusters_speck/2/oc.speck"; + // Load speck files for 3D positions + std::filesystem::path globularClusters = absPath(http + globular); + std::filesystem::path openClusters = absPath(http + open); + std::vector specks = { openClusters, globularClusters }; + + module->loadImages(root, directory, specks); } // Create Lua table to send to the GUI - const std::vector& images = module->getWWTDataHandler()->getLoadedImages(); lua_newtable(L); - for (int i = 0; i < images.size(); i++) { - std::string name = images[i].name != "" ? images[i].name : "undefined"; - std::string thumbnail = images[i].thumbnailUrl != "" ? images[i].thumbnailUrl : "undefined"; - glm::dvec3 cartCoords = skybrowser::sphericalToCartesian(images[i].celestialCoords); + for (int i = 0; i < module->nLoadedImages(); i++) { + const ImageData& img = module->getWWTDataHandler()->getImage(i); + glm::dvec3 cartCoords = img.equatorialCartesian; + glm::dvec3 position = img.position3d; + + // Conversions for ghoul std::vector cartCoordsVec = { cartCoords.x, cartCoords.y, cartCoords.z }; - glm::dvec3 position = images[i].position3d; std::vector position3d = { position.x, position.y, position.z }; // Index for current ImageData ghoul::lua::push(L, i + 1); lua_newtable(L); // Push ("Key", value) - ghoul::lua::push(L, "name", name); + ghoul::lua::push(L, "name", img.name); lua_settable(L, -3); - ghoul::lua::push(L, "thumbnail", thumbnail); + ghoul::lua::push(L, "thumbnail", img.thumbnailUrl); lua_settable(L, -3); - ghoul::lua::push(L, "ra", images[i].celestialCoords.x); + ghoul::lua::push(L, "ra", img.equatorialSpherical.x); lua_settable(L, -3); - ghoul::lua::push(L, "dec", images[i].celestialCoords.y); + ghoul::lua::push(L, "dec", img.equatorialSpherical.y); lua_settable(L, -3); ghoul::lua::push(L, "cartesianDirection", cartCoordsVec); lua_settable(L, -3); - ghoul::lua::push(L, "hasCelestialCoords", images[i].hasCelestialCoords); + ghoul::lua::push(L, "hasCelestialCoords", img.hasCelestialCoords); lua_settable(L, -3); - ghoul::lua::push(L, "credits", images[i].credits); + ghoul::lua::push(L, "credits", img.credits); lua_settable(L, -3); - ghoul::lua::push(L, "creditsUrl", images[i].creditsUrl); + ghoul::lua::push(L, "creditsUrl", img.creditsUrl); lua_settable(L, -3); ghoul::lua::push(L, "identifier", std::to_string(i)); lua_settable(L, -3); - ghoul::lua::push(L, "has3dCoords", images[i].has3dCoords); + ghoul::lua::push(L, "has3dCoords", img.has3dCoords); lua_settable(L, -3); ghoul::lua::push(L, "position3d", position3d); lua_settable(L, -3); @@ -378,10 +342,8 @@ namespace openspace::skybrowser::luascriptfunctions { // Calculate the smallest FOV of vertical and horizontal - double HFOV = global::windowDelegate->getHorizFieldOfView(); - glm::dvec2 windowRatio = global::windowDelegate->currentWindowSize(); - double VFOV = HFOV * (windowRatio.y / windowRatio.x); - double FOV = std::min(HFOV, VFOV); + glm::dvec2 fovs = skybrowser::fovWindow(); + double FOV = std::min(fovs.x, fovs.y); // Push window data ghoul::lua::push(L, "windowHFOV", FOV); lua_settable(L, -3); @@ -400,53 +362,52 @@ namespace openspace::skybrowser::luascriptfunctions { // Pass data for all the browsers and the corresponding targets if (module->cameraInSolarSystem()) { - std::map browsers = module->getSkyBrowsers(); + std::vector pairs = module->getPairs(); - for (std::pair pair : browsers) { - ScreenSpaceSkyBrowser* browser = pair.second; - std::string id = pair.first; + for (SkyBrowserModule::Pair pair : pairs) { + ScreenSpaceSkyBrowser* browser = pair.getBrowser(); + ScreenSpaceSkyTarget* target = pair.getTarget(); + std::string id = browser->identifier(); // Convert deque to vector so ghoul can read it std::vector selectedImagesVector; std::deque selectedImages = browser->getSelectedImages(); std::for_each(selectedImages.begin(), selectedImages.end(), [&](int i) { selectedImagesVector.push_back(i); }); - // Only add browsers that have an initialized target - ScreenSpaceSkyTarget* target = browser->getSkyTarget(); - if (target) { - glm::dvec3 celestialCart = target->targetDirectionEquatorial(); - glm::dvec2 celestialSpherical = skybrowser::cartesianToSpherical(celestialCart); + + glm::dvec3 celestialCart = target->targetDirectionEquatorial(); + glm::dvec2 celestialSpherical = skybrowser::cartesianToSpherical(celestialCart); - std::vector celestialCartVec = { celestialCart.x, celestialCart.y, celestialCart.z }; - // Convert color to vector so ghoul can read it - glm::ivec3 color = browser->borderColor(); - std::vector colorVec = { color.r, color.g, color.b }; + std::vector celestialCartVec = { celestialCart.x, celestialCart.y, celestialCart.z }; + // Convert color to vector so ghoul can read it + glm::ivec3 color = browser->borderColor(); + std::vector colorVec = { color.r, color.g, color.b }; - ghoul::lua::push(L, id); - lua_newtable(L); - // Push ("Key", value) - ghoul::lua::push(L, "id", id); - lua_settable(L, -3); - ghoul::lua::push(L, "name", browser->guiName()); - lua_settable(L, -3); - ghoul::lua::push(L, "FOV", browser->verticalFov()); - lua_settable(L, -3); - ghoul::lua::push(L, "selectedImages", selectedImagesVector); - lua_settable(L, -3); - ghoul::lua::push(L, "cartesianDirection", celestialCartVec); - lua_settable(L, -3); - ghoul::lua::push(L, "ra", celestialSpherical.x); - lua_settable(L, -3); - ghoul::lua::push(L, "dec", celestialSpherical.y); - lua_settable(L, -3); - ghoul::lua::push(L, "color", colorVec); - lua_settable(L, -3); - ghoul::lua::push(L, "isLocked", target->isLocked()); - lua_settable(L, -3); + ghoul::lua::push(L, id); + lua_newtable(L); + // Push ("Key", value) + ghoul::lua::push(L, "id", id); + lua_settable(L, -3); + ghoul::lua::push(L, "name", browser->guiName()); + lua_settable(L, -3); + ghoul::lua::push(L, "FOV", browser->verticalFov()); + lua_settable(L, -3); + ghoul::lua::push(L, "selectedImages", selectedImagesVector); + lua_settable(L, -3); + ghoul::lua::push(L, "cartesianDirection", celestialCartVec); + lua_settable(L, -3); + ghoul::lua::push(L, "ra", celestialSpherical.x); + lua_settable(L, -3); + ghoul::lua::push(L, "dec", celestialSpherical.y); + lua_settable(L, -3); + ghoul::lua::push(L, "color", colorVec); + lua_settable(L, -3); + ghoul::lua::push(L, "isLocked", target->isLocked()); + lua_settable(L, -3); - // Set table for the current target - lua_settable(L, -3); - } + // Set table for the current target + lua_settable(L, -3); + } } else { @@ -498,17 +459,12 @@ namespace openspace::skybrowser::luascriptfunctions { ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::adjustCamera"); const std::string id = ghoul::lua::value(L, 1); SkyBrowserModule* module = global::moduleEngine->module(); - std::string idNode3dBrowser = module->get3dBrowser()->identifier(); - std::string id3dBrowser = module->get3dBrowser()->renderable()->identifier(); - if(module->cameraInSolarSystem() && module->browserIdExists(id)) { - ScreenSpaceSkyTarget* target = module->getSkyBrowsers()[id]->getSkyTarget(); - if (target) { - glm::dvec3 cartesian = target->targetDirectionEquatorial(); - module->startRotation(skybrowser::equatorialToGalactic(cartesian)); - } + if(module->cameraInSolarSystem() && module->getPair(id)) { + SkyBrowserModule::Pair* pair = module->getPair(id); + module->startRotation(pair->getTarget()->targetDirectionGalactic()); } - else if (!module->cameraInSolarSystem() && id3dBrowser == id) { + else if (!module->cameraInSolarSystem() && module->get3dBrowser()) { module->lookAt3dBrowser(); } @@ -519,18 +475,18 @@ namespace openspace::skybrowser::luascriptfunctions { ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::set3dSelectedImagesAs2dSelection"); const std::string id = ghoul::lua::value(L, 1); SkyBrowserModule* module = global::moduleEngine->module(); + SkyBrowserModule::Pair* pair = module->getPair(id); - if (module->browserIdExists(id) && module->get3dBrowser() != nullptr) { - ScreenSpaceSkyBrowser* browser = module->getSkyBrowsers()[id]; + if (pair && module->get3dBrowser()) { RenderableSkyBrowser* browser3d = dynamic_cast( module->get3dBrowser()->renderable()); // Empty 3D browser selection browser3d->getSelectedImages().clear(); // Copy 2D selection of images to 3D browser - std::deque images = browser->getSelectedImages(); - std::for_each(std::begin(images), std::end(images), [&](int index) { - ImageData& image = module->getWWTDataHandler()->getLoadedImages()[index]; - browser3d->displayImage(image, index); + std::deque images = pair->getBrowser()->getSelectedImages(); + std::for_each(std::begin(images), std::end(images), [&](int i) { + const ImageData& image = module->getWWTDataHandler()->getImage(i); + browser3d->displayImage(image, i); }); } @@ -539,14 +495,15 @@ namespace openspace::skybrowser::luascriptfunctions { int setOpacityOfImageLayer(lua_State* L) { ghoul::lua::checkArgumentsAndThrow(L, 3, "lua::setOpacityOfImageLayer"); - const std::string browserId = ghoul::lua::value(L, 1); + const std::string id = ghoul::lua::value(L, 1); const std::string i = std::to_string(ghoul::lua::value(L, 2)); double opacity = ghoul::lua::value(L, 3); SkyBrowserModule* module = global::moduleEngine->module(); ghoul::Dictionary message = wwtmessage::setImageOpacity(i, opacity); - if (module->browserIdExists(browserId)) { - module->getSkyBrowsers()[browserId]->sendMessageToWwt(message); + if (module->getPair(id)) { + SkyBrowserModule::Pair* pair = module->getPair(id); + pair->getBrowser()->sendMessageToWwt(message); } else if (module->get3dBrowser() != nullptr) { RenderableSkyBrowser* browser3d = dynamic_cast( @@ -561,19 +518,17 @@ namespace openspace::skybrowser::luascriptfunctions { ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::centerTargetOnScreen"); const std::string id = ghoul::lua::value(L, 1); SkyBrowserModule* module = global::moduleEngine->module(); - if (module->browserIdExists(id)) { - ScreenSpaceSkyBrowser* browser = module->getSkyBrowsers()[id]; - if (browser && browser->getSkyTarget()) { - // Animate the target to the center of the screen - browser->getSkyTarget()->unlock(); - // Get camera direction in celestial spherical coordinates - glm::dvec3 viewDirection = skybrowser::cameraDirectionEquatorial(); - // Keep the current fov - float fov = browser->verticalFov(); - browser->getSkyTarget()->startAnimation(viewDirection, fov, false); - browser->getSkyTarget()->unlock(); - } + SkyBrowserModule::Pair* pair = module->getPair(id); + if (pair) { + // Animate the target to the center of the screen + pair->getTarget()->unlock(); + // Get camera direction in celestial spherical coordinates + glm::dvec3 viewDirection = skybrowser::cameraDirectionEquatorial(); + // Keep the current fov + float currentFov = pair->getBrowser()->verticalFov(); + pair->getTarget()->startAnimation(viewDirection, currentFov, false); } + return 0; } @@ -581,7 +536,7 @@ namespace openspace::skybrowser::luascriptfunctions { ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::setSelectedBrowser"); const std::string id = ghoul::lua::value(L, 1); SkyBrowserModule* module = global::moduleEngine->module(); - if (module->browserIdExists(id)) { + if (module->getPair(id)) { module->setSelectedBrowser(id); } return 0; @@ -609,14 +564,14 @@ namespace openspace::skybrowser::luascriptfunctions { // Image index to place in 3D const int i = ghoul::lua::value(L, 1); SkyBrowserModule* module = global::moduleEngine->module(); - ImageData image = module->getWWTDataHandler()->getLoadedImages()[i]; + const ImageData image = module->getWWTDataHandler()->getImage(i); // If the image has a 3D position, add it to the scene graph - if (image.has3dCoords) { + if (image.has3dCoords && module->get3dBrowser()) { RenderableSkyBrowser* browser = dynamic_cast( module->get3dBrowser()->renderable()); browser->displayImage(image, i); - module->place3dBrowser(image); + browser->placeAt3dPosition(image); } else { LINFO("Image has no 3D coordinate!"); @@ -629,16 +584,16 @@ namespace openspace::skybrowser::luascriptfunctions { ghoul::lua::checkArgumentsAndThrow(L, 2, "lua::removeSelectedImageInBrowser"); // Image index const int i = ghoul::lua::value(L, 1); - const std::string browserId = ghoul::lua::value(L, 2); + const std::string id = ghoul::lua::value(L, 2); // Get browser SkyBrowserModule* module = global::moduleEngine->module(); - ImageData& image = module->getWWTDataHandler()->getLoadedImages()[i]; + const ImageData& image = module->getWWTDataHandler()->getImage(i); - if (module->browserIdExists(browserId)) { - ScreenSpaceSkyBrowser* browser = module->getSkyBrowsers()[browserId]; + if (module->getPair(id)) { + SkyBrowserModule::Pair* pair = module->getPair(id); // Remove image - browser->removeSelectedImage(image, i); + pair->getBrowser()->removeSelectedImage(image, i); } else if (module->get3dBrowser() != nullptr) { RenderableSkyBrowser* browser3d = dynamic_cast( diff --git a/modules/skybrowser/src/renderableskybrowser.cpp b/modules/skybrowser/src/renderableskybrowser.cpp index 4b01031b52..58dd3c1325 100644 --- a/modules/skybrowser/src/renderableskybrowser.cpp +++ b/modules/skybrowser/src/renderableskybrowser.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -8,6 +9,7 @@ #include #include #include +#include #include #include // formatJson #include @@ -188,8 +190,8 @@ namespace openspace { return true; } - void RenderableSkyBrowser::displayImage(ImageData& image, int i) { - sendMessageToWwt(wwtmessage::moveCamera(image.celestialCoords, image.fov, 0.0)); + void RenderableSkyBrowser::displayImage(const ImageData& image, int i) { + sendMessageToWwt(wwtmessage::moveCamera(image.equatorialSpherical, image.fov, 0.0)); _verticalFov = image.fov; // Add to selected images if there are no duplicates auto it = std::find(std::begin(_selectedImages), std::end(_selectedImages), i); @@ -202,7 +204,7 @@ namespace openspace { } } - void RenderableSkyBrowser::removeSelectedImage(ImageData& image, int i) { + void RenderableSkyBrowser::removeSelectedImage(const ImageData& image, int i) { // Remove from selected list auto it = std::find(std::begin(_selectedImages), std::end(_selectedImages), i); if (it != std::end(_selectedImages)) { @@ -249,11 +251,59 @@ namespace openspace { } } - std::deque& RenderableSkyBrowser::getSelectedImages() { + void RenderableSkyBrowser::placeAt3dPosition(const ImageData& image) + { + std::string renderableId = dynamic_cast( + this)->renderable()->identifier(); + // Uris for properties + std::string sizeUri = "Scene." + _identifier + "." + renderableId + ".Size"; + std::string positionUri = "Scene." + _identifier + ".Translation.Position"; + std::string rotationUri = "Scene." + _identifier + ".Rotation.Rotation"; + std::string cameraAim = "NavigationHandler.OrbitalNavigator.Aim"; + glm::dvec3 position = image.position3d * distanceconstants::Parsec; + // Calculate the size of the plane with trigonometry + // Calculate in equatorial coordinate system since the FOV is from Earth + // /| + // /_| Adjacent is the horizontal line, opposite the vertical + // \ | Calculate for half the triangle first, then multiply with 2 + // \| + glm::dvec3 j2000 = skybrowser::galacticToEquatorial(position); + double adjacent = glm::length(j2000); + double opposite = 2 * adjacent * glm::tan(glm::radians(image.fov * 0.5)); + + // Calculate rotation to make the plane face the solar system barycenter + glm::dvec3 normal = glm::normalize(-position); + glm::dvec3 newRight = glm::normalize( + glm::cross(glm::dvec3(0.0, 0.0, 1.0), normal) + ); + glm::dvec3 newUp = glm::cross(normal, newRight); + // Face the Solar System Barycenter + glm::dmat3 rotation = glm::dmat3(1.0); + rotation[0] = newRight; + rotation[1] = newUp; + rotation[2] = normal; + + std::string setValue = "openspace.setPropertyValueSingle('"; + + openspace::global::scriptEngine->queueScript( + setValue + sizeUri + "', " + std::to_string(opposite) + ");", + scripting::ScriptEngine::RemoteScripting::Yes + ); + openspace::global::scriptEngine->queueScript( + setValue + positionUri + "', " + ghoul::to_string(position) + ");", + scripting::ScriptEngine::RemoteScripting::Yes + ); + openspace::global::scriptEngine->queueScript( + setValue + rotationUri + "', " + ghoul::to_string(rotation) + ");", + scripting::ScriptEngine::RemoteScripting::Yes + ); + } + + std::deque& RenderableSkyBrowser::getSelectedImages() { return _selectedImages; } - void RenderableSkyBrowser::setImageLayerOrder(int i, int order, int version) { + void RenderableSkyBrowser::setImageLayerOrder(int i, int order) { // Remove from selected list auto current = std::find(std::begin(_selectedImages), std::end(_selectedImages), i); auto target = std::begin(_selectedImages) + order; @@ -266,7 +316,7 @@ namespace openspace { int reverseOrder = _selectedImages.size() - order - 1; ghoul::Dictionary message = wwtmessage::setLayerOrder(std::to_string(i), - reverseOrder, version); + reverseOrder); sendMessageToWwt(message); } diff --git a/modules/skybrowser/src/screenspaceskybrowser.cpp b/modules/skybrowser/src/screenspaceskybrowser.cpp index 2ebcce44e7..1eb1ddb010 100644 --- a/modules/skybrowser/src/screenspaceskybrowser.cpp +++ b/modules/skybrowser/src/screenspaceskybrowser.cpp @@ -66,7 +66,7 @@ namespace { std::optional targetId; // [[codegen::verbatim(BorderColorInfo.description)]] - std::optional borderColor; + std::optional borderColor; }; #include "screenspaceskybrowser_codegen.cpp" @@ -78,7 +78,7 @@ namespace openspace { : ScreenSpaceBrowser(dictionary) , _browserDimensions(BrowserDimensionInfo, _dimensions, glm::ivec2(0), glm::ivec2(300)) , _verticalFov(VerticalFovInfo, 10.f, 0.1f, 70.f) - , _borderColor(BorderColorInfo, glm::vec3(rand() % 256, rand() % 256, rand() % 256)) + , _borderColor(BorderColorInfo, glm::ivec3(200), glm::ivec3(0), glm::ivec3(255)) , _skyTargetId(TargetIdInfo) { // Make the color property display a color picker in the GUI @@ -139,7 +139,7 @@ namespace openspace { // Make sure the RGB color at least is 50% brightness // By making each channel 50% bright in general // 222 = sqrt(3*(0.5*256)^2) - while (glm::length(_borderColor.value()) < 222) { + while (glm::length(glm::vec3(_borderColor.value())) < 222.f) { _borderColor = glm::vec3(rand() % 256, rand() % 256, rand() % 256); } } @@ -162,6 +162,18 @@ namespace openspace { executeJavascript("setId('" + identifier() + "')"); } + void ScreenSpaceSkyBrowser::highlight(glm::ivec3 addition) + { + glm::ivec3 color = glm::ivec3(_borderColor.value()); + setWebpageBorderColor( color + addition ); + } + + void ScreenSpaceSkyBrowser::removeHighlight(glm::ivec3 removal) + { + glm::ivec3 color = glm::ivec3(_borderColor.value()); + setWebpageBorderColor( color - removal ); + } + void ScreenSpaceSkyBrowser::initializeBrowser() { // If the camera is already synced, the browser is already initialized if (!_syncViewWithWwt) { @@ -177,6 +189,21 @@ namespace openspace { } } + glm::dvec2 ScreenSpaceSkyBrowser::fineTuneTarget(glm::dvec2 drag) { + // Fine tuning of target + glm::dvec2 wwtFov = fieldsOfView(); + glm::dvec2 openSpaceFOV = skybrowser::fovWindow(); + + glm::dvec2 browserDim = screenSpaceDimensions(); + glm::dvec2 angleResult = wwtFov * (drag / browserDim); + glm::dvec2 resultRelativeOs = angleResult / openSpaceFOV; + + // Convert to screen space coordinate system + glm::dvec2 convertToScreenSpace{ (2 * skybrowser::windowRatio()), 2.f }; + glm::dvec2 result = - convertToScreenSpace * resultRelativeOs; + return result; + } + bool ScreenSpaceSkyBrowser::deinitializeGL() { // Set flag to false so the thread can exit _syncViewWithWwt = false; @@ -244,6 +271,14 @@ namespace openspace { return _verticalFov.value(); } + glm::dvec2 ScreenSpaceSkyBrowser::fieldsOfView() { + glm::dvec2 browserDim = screenSpaceDimensions(); + double browserRatio = browserDim.x / browserDim.y; + glm::dvec2 browserFov = glm::dvec2(verticalFov() * browserRatio, verticalFov()); + + return browserFov; + } + void ScreenSpaceSkyBrowser::setWebpageBorderColor(glm::ivec3 color) { std::string stringColor = std::to_string(color.x) + "," + std::to_string(color.y) + "," + std::to_string(color.z); @@ -292,6 +327,7 @@ namespace openspace { // Make sure coordinate is on browser if (!coordIsInsideCornersScreenSpace(screenSpaceCoord)) return resizePosition; + // TO DO: turn this into a vector and use prettier vector arithmetic float resizeAreaY = screenSpaceDimensions().y * _resizeAreaPercentage; float resizeAreaX = screenSpaceDimensions().x * _resizeAreaPercentage; @@ -359,7 +395,7 @@ namespace openspace { return _selectedImages; } - void ScreenSpaceSkyBrowser::addSelectedImage(ImageData& image, int i) { + void ScreenSpaceSkyBrowser::addSelectedImage(const ImageData& image, int i) { // Ensure there are no duplicates auto it = std::find(std::begin(_selectedImages), std::end(_selectedImages), i); bool found = it != std::end(_selectedImages); @@ -372,7 +408,7 @@ namespace openspace { } } - void ScreenSpaceSkyBrowser::removeSelectedImage(ImageData& image, int i) { + void ScreenSpaceSkyBrowser::removeSelectedImage(const ImageData& image, int i) { // Remove from selected list auto it = std::find(std::begin(_selectedImages), std::end(_selectedImages), i); bool found = it != std::end(_selectedImages); @@ -382,7 +418,7 @@ namespace openspace { } } - void ScreenSpaceSkyBrowser::setImageOrder(int i, int order, int version) { + void ScreenSpaceSkyBrowser::setImageOrder(int i, int order) { // Find the selected image auto selected = std::find( @@ -408,8 +444,7 @@ namespace openspace { int reverseOrder = _selectedImages.size() - order - 1; ghoul::Dictionary message = wwtmessage::setLayerOrder( std::to_string(i), - reverseOrder, - version + reverseOrder ); sendMessageToWwt(message); } diff --git a/modules/skybrowser/src/screenspaceskytarget.cpp b/modules/skybrowser/src/screenspaceskytarget.cpp index d422b6cbcd..f22c86b4d4 100644 --- a/modules/skybrowser/src/screenspaceskytarget.cpp +++ b/modules/skybrowser/src/screenspaceskytarget.cpp @@ -34,7 +34,7 @@ namespace { constexpr const openspace::properties::Property::PropertyInfo BrowserIDInfo = { - "BrowserID", + "BrowserId", "Browser Identifier", "The identifier of the corresponding sky browser." }; @@ -55,16 +55,39 @@ namespace { "be rendered in the target." }; + constexpr const openspace::properties::Property::PropertyInfo AnimationSpeedInfo = + { + "AnimationSpeed", + "Animation Speed", + "The factor which is multiplied with the animation speed of the target." + }; + + constexpr const openspace::properties::Property::PropertyInfo AnimationThresholdInfo = + { + "AnimationThreshold", + "Animation Threshold", + "The threshold for when the target is determined to have appeared at its " + "destination. Angle in radians between the destination and the target position in" + "equatorial Cartesian coordinate system." + }; + struct [[codegen::Dictionary(ScreenSpaceSkyTarget)]] Parameters { // [[codegen::verbatim(BrowserIDInfo.description)]] - std::optional browserID; + std::optional browserId; // [[codegen::verbatim(CrosshairThresholdInfo.description)]] std::optional crosshairThreshold; // [[codegen::verbatim(RectangleThresholdInfo.description)]] std::optional rectangleThreshold; + + // [[codegen::verbatim(RectangleThresholdInfo.description)]] + std::optional animationSpeed; + + // [[codegen::verbatim(RectangleThresholdInfo.description)]] + std::optional animationThreshold; + }; #include "screenspaceskytarget_codegen.cpp" @@ -78,17 +101,23 @@ namespace openspace { , _skyBrowser(nullptr) , _showCrosshairThreshold(CrosshairThresholdInfo, 2.0f, 0.1f, 70.f) , _showRectangleThreshold(RectangleThresholdInfo, 0.6f, 0.1f, 70.f) + , _stopAnimationThreshold(AnimationThresholdInfo, 0.0005, 0.0, 1.0) + , _animationSpeed(AnimationSpeedInfo, 5.0, 0.1, 10.0) , _color(220, 220, 220) { // Handle target dimension property const Parameters p = codegen::bake(dictionary); - _skyBrowserId = p.browserID.value_or(_skyBrowserId); + _skyBrowserId = p.browserId.value_or(_skyBrowserId); _showCrosshairThreshold = p.crosshairThreshold.value_or(_showCrosshairThreshold); _showRectangleThreshold = p.rectangleThreshold.value_or(_showRectangleThreshold); + _stopAnimationThreshold = p.crosshairThreshold.value_or(_stopAnimationThreshold); + _animationSpeed = p.animationSpeed.value_or(_animationSpeed); addProperty(_skyBrowserId); addProperty(_showCrosshairThreshold); addProperty(_showRectangleThreshold); + addProperty(_stopAnimationThreshold); + addProperty(_animationSpeed); // If the ID changes for the corresponding browser, update _skyBrowserId.onChange([&]() { @@ -201,8 +230,13 @@ namespace openspace { void ScreenSpaceSkyTarget::render() { - bool showCrosshair = _skyBrowser->verticalFov() < _showCrosshairThreshold; - bool showRectangle = _skyBrowser->verticalFov() > _showRectangleThreshold; + bool showCrosshair = false; + bool showRectangle = true; + if (_skyBrowser) { + showCrosshair = _skyBrowser->verticalFov() < _showCrosshairThreshold; + showRectangle = _skyBrowser->verticalFov() > _showRectangleThreshold; + } + glm::vec4 color = { glm::vec3(_color) / 255.f, _opacity.value() }; glm::mat4 modelTransform = globalRotationMatrix() * translationMatrix() * localRotationMatrix() * scaleMatrix(); @@ -245,15 +279,12 @@ namespace openspace { // Update the scale of the target (the height of the target in relation to the // OpenSpace window) void ScreenSpaceSkyTarget::setScale(float verticalFov) { - - // Calculate the vertical field of view of the OpenSpace window - float hFovOs = global::windowDelegate->getHorizFieldOfView(); - glm::vec2 windowRatio = glm::vec2(global::windowDelegate->currentWindowSize()); - float vFovOs = hFovOs * windowRatio.y / windowRatio.x; + + glm::dvec2 fovs = skybrowser::fovWindow(); // Cap the scale at small scales so it is still visible - float heightRatio = verticalFov / vFovOs; - float smallestHeightRatio = _showRectangleThreshold.value() / vFovOs; + float heightRatio = verticalFov / fovs.y; + float smallestHeightRatio = _showRectangleThreshold.value() / fovs.y; _scale = std::max(heightRatio, smallestHeightRatio); } @@ -275,8 +306,7 @@ namespace openspace { // Start a thread to enable user interactions while locking target _lockTarget = std::thread([&] { while (_isLocked) { - glm::vec3 coordsScreen = skybrowser::equatorialToScreenSpace(_lockedCoordinates); - _cartesianPosition = coordsScreen; + _cartesianPosition = skybrowser::equatorialToScreenSpace(_lockedCoordinates); } }); } @@ -288,22 +318,27 @@ namespace openspace { glm::dvec3 ScreenSpaceSkyTarget::targetDirectionEquatorial() const { // Calculate the galactic coordinate of the target direction // projected onto the celestial sphere - return skybrowser::screenSpaceToEquatorial(_cartesianPosition.value()); + return skybrowser::localCameraToEquatorial(_cartesianPosition.value()); } void ScreenSpaceSkyTarget::animateToCoordinate(double deltaTime) { + if (_isAnimated) { + // Find smallest angle between the two vectors - double smallestAngle = std::acos(glm::dot(_animationStart, _animationEnd) / (glm::length(_animationStart) * glm::length(_animationEnd))); + double smallestAngle = skybrowser::angleVector(_animationStart, _animationEnd); // Only keep animating when target is not at final position - if (abs(smallestAngle) > 0.0005) { - // Calculate rotation this frame - double rotationAngle = smallestAngle * deltaTime * 5.0; - // Create the rotation matrix - glm::dvec3 rotationAxis = glm::normalize(glm::cross(_animationStart, _animationEnd)); - glm::dmat4 rotmat = glm::rotate(rotationAngle, rotationAxis); + if (smallestAngle > _stopAnimationThreshold) { + glm::dmat4 rotMat; + skybrowser::incrementalAnimationMatrix( + rotMat, + _animationStart, + _animationEnd, + deltaTime, + _animationSpeed + ); // Rotate target direction - glm::dvec3 newDir = rotmat * glm::dvec4(_animationStart, 1.0); + glm::dvec3 newDir = rotMat * glm::dvec4(_animationStart, 1.0); // Convert to screen space _cartesianPosition = skybrowser::equatorialToScreenSpace(newDir); // Update position @@ -346,6 +381,16 @@ namespace openspace { return true; } + void ScreenSpaceSkyTarget::highlight(glm::ivec3 addition) + { + _color += addition; + } + + void ScreenSpaceSkyTarget::removeHighlight(glm::ivec3 removal) + { + _color -= removal; + } + void ScreenSpaceSkyTarget::startAnimation(glm::dvec3 coordsEnd, float FOVEnd, bool lockAfterwards) { // Save the Cartesian celestial coordinates for animation diff --git a/modules/skybrowser/src/utility.cpp b/modules/skybrowser/src/utility.cpp index b83ba06a44..406f9f6639 100644 --- a/modules/skybrowser/src/utility.cpp +++ b/modules/skybrowser/src/utility.cpp @@ -5,11 +5,11 @@ #include #include // For atan2 #include // For printing glm data +#include +#include #define _USE_MATH_DEFINES #include - - namespace openspace::skybrowser { glm::dvec3 sphericalToCartesian(glm::dvec2 coords) { @@ -49,7 +49,7 @@ namespace openspace::skybrowser { glm::dvec3 galacticToScreenSpace(glm::dvec3 coords) { - glm::dvec3 localCameraSpace = galacticToCameraLocal(coords); + glm::dvec3 localCameraSpace = galacticToLocalCamera(coords); // Ensure that if the coord is behind the camera, // the converted coordinate will be there too double zCoord = localCameraSpace.z > 0 ? -ScreenSpaceZ : ScreenSpaceZ; @@ -63,7 +63,7 @@ namespace openspace::skybrowser { return screenSpace; } - glm::dvec3 screenSpaceToGalactic(glm::dvec3 coords) { + glm::dvec3 localCameraToGalactic(glm::dvec3 coords) { glm::dmat4 rotation = glm::inverse( global::navigationHandler->camera()->viewRotationMatrix()); glm::dvec4 position = glm::dvec4(coords, 1.0); @@ -71,16 +71,16 @@ namespace openspace::skybrowser { return glm::normalize(rotation * position) * skybrowser::CelestialSphereRadius; } - glm::dvec3 screenSpaceToEquatorial(glm::dvec3 coords) { + glm::dvec3 localCameraToEquatorial(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::screenSpaceToGalactic(coords); + glm::dvec3 galactic = camPos + skybrowser::localCameraToGalactic(coords); return skybrowser::galacticToEquatorial(galactic); } - glm::dvec3 galacticToCameraLocal(glm::dvec3 coords) { + glm::dvec3 galacticToLocalCamera(glm::dvec3 coords) { // Transform vector to camera's local coordinate system glm::dvec3 camPos = global::navigationHandler->camera()->positionVec3(); glm::dmat4 camMat = global::navigationHandler->camera()->viewRotationMatrix(); @@ -125,6 +125,66 @@ namespace openspace::skybrowser { return galCoord; } + + float windowRatio() { + glm::vec2 windowRatio = global::windowDelegate->currentWindowSize(); + return windowRatio.x / windowRatio.y; + } + + bool coordinateIsOutsideView(glm::dvec3 equatorial) { + // Check if image coordinate is within current FOV + glm::dvec3 coordsScreen = equatorialToScreenSpace(equatorial); + double r = static_cast(windowRatio()); + + bool coordIsWithinView = (abs(coordsScreen.x) < r && + abs(coordsScreen.y) < 1.f && coordsScreen.z < 0); + bool coordIsBehindCamera = coordsScreen.z > 0; + // If the coordinate is not in view, rotate camera + return !coordIsWithinView || coordIsBehindCamera; + } + + // Transforms a pixel coordinate to a screen space coordinate + glm::vec2 pixelToScreenSpace(glm::vec2& mouseCoordinate) { + glm::vec2 size = glm::vec2(global::windowDelegate->currentWindowSize()); + // Change origin to middle of the window + glm::vec2 screenSpacePos = mouseCoordinate - (size / 2.0f); + // Ensure the upper right corner is positive on the y axis + screenSpacePos *= glm::vec2(1.0f, -1.0f); + // Transform pixel coordinates to screen space coordinates [-1,1][-ratio, ratio] + screenSpacePos /= (0.5f * size.y); + return screenSpacePos; + } + + // The horizontal and vertical fov of the OpenSpace window + glm::dvec2 fovWindow() { + // OpenSpace FOV + glm::dvec2 windowDim = glm::dvec2(global::windowDelegate->currentWindowSize()); + double windowRatio = windowDim.y / windowDim.x; + double hFov = global::windowDelegate->getHorizFieldOfView(); + glm::dvec2 OpenSpaceFOV = glm::dvec2(hFov, hFov * windowRatio); + return OpenSpaceFOV; + } + + double angleVector(glm::dvec3 start, glm::dvec3 end) { + + // Find smallest angle between the two vectors + double cos = glm::dot(start, end) / (glm::length(start) * glm::length(end)); + return std::acos(cos); + } + + void incrementalAnimationMatrix(glm::dmat4& rotation, glm::dvec3 start, + glm::dvec3 end, double deltaTime, + double speedFactor) { + + double smallestAngle = angleVector(start, end); + // Calculate rotation this frame + double rotationAngle = smallestAngle * deltaTime * speedFactor; + + // Create the rotation matrix for local camera space + glm::dvec3 rotationAxis = glm::normalize(glm::cross(start, end)); + rotation = glm::rotate(rotationAngle, rotationAxis); + } + } // WWT messages @@ -206,7 +266,7 @@ namespace openspace::wwtmessage { return msg; } - ghoul::Dictionary setLayerOrder(const std::string& id, int order, int version) { + ghoul::Dictionary setLayerOrder(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; @@ -214,7 +274,9 @@ namespace openspace::wwtmessage { msg.setValue("event", "image_layer_order"s); msg.setValue("id", id); msg.setValue("order", order); - msg.setValue("version", version); + msg.setValue("version", messageCounter); + + messageCounter++; return msg; } diff --git a/modules/skybrowser/src/wwtdatahandler.cpp b/modules/skybrowser/src/wwtdatahandler.cpp index f08a4b10bd..ff10da4abd 100644 --- a/modules/skybrowser/src/wwtdatahandler.cpp +++ b/modules/skybrowser/src/wwtdatahandler.cpp @@ -1,8 +1,9 @@ #include +#include #include // For downloading files from url -#include // To iterate through files in directory #include #include +#include // To iterate through files in directory #include #include #include @@ -20,25 +21,21 @@ namespace { namespace openspace { - bool hasAttribute(tinyxml2::XMLElement* element, std::string name) { + bool hasAttribute(const tinyxml2::XMLElement* element, const std::string& name) { return element->FindAttribute(name.c_str()); } - std::string getAttribute(tinyxml2::XMLElement* element, std::string name) { + std::string getAttribute(const tinyxml2::XMLElement* element, const std::string& name) { if (hasAttribute(element, name)) { return element->FindAttribute(name.c_str())->Value(); } else { - return ""; + return wwt::Undefined; } } - WwtDataHandler::~WwtDataHandler() { - // Call destructor of all allocated xmls - _xmls.clear(); - } - - bool WwtDataHandler::downloadFile(std::string& url, std::string& fileDestination) { + // Parsing and downloading of wtml files + bool downloadFile(const std::string& url, const std::string& fileDestination) { // Get the web page and save to file HttpRequest::RequestOptions opt{ 5 }; SyncHttpFileDownload wtml_root( @@ -48,62 +45,7 @@ namespace openspace { return wtml_root.hasSucceeded(); } - bool WwtDataHandler::loadWtmlCollectionsFromUrl(std::string directory, - std::string url, std::string fileName) - { - // Look for WWT image data folder - if (!directoryExists(directory)) { - std::string newDir = directory; - newDir.pop_back(); - LINFO("Creating directory WWTimagedata"); - std::filesystem::create_directory(newDir); - } - - // Get file - std::string file = directory + fileName + ".aspx"; - if (!downloadFile(url, file)) { - LINFO("Couldn't download file " + url); - return false; - } - // Parse to XML - using namespace tinyxml2; - tinyxml2::XMLDocument* doc = new tinyxml2::XMLDocument(); - doc->LoadFile(file.c_str()); - - XMLElement* root = doc->RootElement(); - XMLElement* element = root->FirstChildElement(std::string("Folder").c_str()); - - // If there are no folders, or there are folder but without urls, stop recursion - bool folderExists = element; - bool folderContainNoUrls = folderExists && !hasAttribute(element, "Url"); - - if (!folderExists || folderContainNoUrls) { - // Save the url - if (hasAttribute(root, "Name")) { - std::string collectionName = getAttribute(root, "Name"); - ImageCollection newCollection{ collectionName, url }; - _imageUrls.push_back(newCollection); - } - _xmls.push_back(doc); - LINFO("Saving " + url); - - return true; - } - // Iterate through all the folders - while (element && std::string(element->Value()) == "Folder") { - - // Get all attributes for the - if (hasAttribute(element, "Url") && hasAttribute(element, "Name")) { - std::string subUrl = getAttribute(element, "Url"); - std::string subName = getAttribute(element, "Name"); - loadWtmlCollectionsFromUrl(directory, subUrl, subName); - } - element = element->NextSiblingElement(); - } - } - - - bool WwtDataHandler::directoryExists(std::string& path) + bool directoryExists(const std::string& path) { struct stat info; @@ -111,13 +53,13 @@ namespace openspace { if (statRC != 0) { // something along the path does not exist - if (errno == ENOENT) { - return false; - } + if (errno == ENOENT) { + return false; + } // something in path prefix is not a dir - if (errno == ENOTDIR) { - return false; - } + if (errno == ENOTDIR) { + return false; + } return false; } @@ -126,48 +68,201 @@ namespace openspace { return directoryExists; } - bool WwtDataHandler::loadWtmlCollectionsFromDirectory(std::string directory) { - - if (!directoryExists(directory)) return false; + std::string createSearchableString(std::string str) { + // Remove white spaces and all special characters + str.erase(std::remove_if(std::begin(str), std::end(str), [](char c) { + bool isNumberOrLetter = std::isdigit(c) || std::isalpha(c); + return !isNumberOrLetter; + }), + std::end(str)); + // Make the word lower case + std::transform(std::begin(str), std::end(str), std::begin(str), [](char c) { + return std::tolower(c); + }); + return str; + } + std::unordered_map + loadSpeckData(const speck::Dataset& dataset) { + // Create map + std::unordered_map positions3d; + + for (speck::Dataset::Entry entry : dataset.entries) { + if (entry.comment.has_value()) { + std::string name = createSearchableString(entry.comment.value()); + positions3d[name] = std::move(entry.position); + } + } + return positions3d; + } + + 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(); + tinyxml2::XMLElement* imageSet = nullptr; + + // Traverse the children and look at all their first child to find ImageSet + while (child) { + imageSet = getDirectChildNode(child, name); + // Found + if (imageSet) { + break; + } + child = child->NextSiblingElement(); + } + return imageSet; + } + + 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 getAttribute(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::string& directory) { for (const auto& entry : std::filesystem::directory_iterator(directory)) { - + tinyxml2::XMLDocument* document = new tinyxml2::XMLDocument(); std::string path = entry.path().u8string(); tinyxml2::XMLError successCode = document->LoadFile(path.c_str()); - bool fileIsLoaded = successCode == tinyxml2::XMLError::XML_SUCCESS; - if (fileIsLoaded) { - tinyxml2::XMLElement* root = document->RootElement(); - - if (hasAttribute(root, "Name")) { - std::string collectionName = getAttribute(root, "Name"); - ImageCollection newCollection{ collectionName, path }; - _imageUrls.push_back(newCollection); - } + if (successCode == tinyxml2::XMLError::XML_SUCCESS) { _xmls.push_back(document); } } - return true; } - std::ostream& operator<<(std::ostream& os, const ImageData& img) { - os << "Name: " << img.name << " Coords: ra: " << img.celestialCoords.x - << " dec: " << img.celestialCoords.y << std::endl; - os << "Thumbnail: " << img.thumbnailUrl << std::endl; - os << "Collection: " << img.collection << std::endl << std::endl; - return os; + bool downloadAndParseWtmlFilesFromUrl(std::vector& _xmls, + const std::string& 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; + // Remove the '/' at the end + newDir.pop_back(); + LINFO("Creating directory WWTimagedata"); + std::filesystem::create_directory(newDir); + } + + // Download file from url + std::string file = directory.c_str() + fileName + ".aspx"; + if (!downloadFile(url, file)) { + LINFO("Couldn't download file " + url); + return false; + } + + // Parse file to XML + using namespace tinyxml2; + tinyxml2::XMLDocument* doc = new tinyxml2::XMLDocument(); + doc->LoadFile(file.c_str()); + + // Search XML file for folders with urls + XMLElement* root = doc->RootElement(); + XMLElement* element = root->FirstChildElement(wwt::Folder.c_str()); + bool folderExists = element; + 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 url = getAttribute(element, wwt::Url); + std::string fileName = getAttribute(element, wwt::Name); + downloadAndParseWtmlFilesFromUrl(_xmls, directory, url, fileName); + } + element = element->NextSiblingElement(); + } + } + WwtDataHandler::~WwtDataHandler() { + // Call destructor of all allocated xmls + _xmls.clear(); + } - int WwtDataHandler::loadImagesFromLoadedXmls() { + void WwtDataHandler::loadImages(const std::string& root, const std::string& directory, + std::vector& speckFiles) { + + // Load 3D data from speck files + for (std::filesystem::path& path : speckFiles) { + speck::Dataset speck = speck::loadSpeckFile(path); + _3dPositions = loadSpeckData(speck); + LINFO("Loaded speck file with " + std::to_string(_3dPositions.size()) + + " entries!"); + } + + // Collect the wtml files, either by reading from disc or from a url + if (directoryExists(directory)) { + parseWtmlsFromDisc(_xmls, directory); + LINFO("Loading images from directory"); + } + else { + downloadAndParseWtmlFilesFromUrl(_xmls, directory, root, "root"); + LINFO("Loading images from url"); + } + + // Traverse through the collected wtml documents and collect the images for (tinyxml2::XMLDocument* doc : _xmls) { tinyxml2::XMLElement* root = doc->FirstChildElement(); - std::string collectionName = root->FindAttribute("Name") ? root->FindAttribute("Name")->Value() : ""; - loadImagesFromXml(root, collectionName); + std::string collectionName = getAttribute(root, wwt::Name); + saveImagesFromXml(root, collectionName); } + // Sort images in alphabetical order - std::sort(_images.begin(), _images.end(), [](ImageData a, ImageData b) { + 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 @@ -178,184 +273,139 @@ namespace openspace { } return a.name < b.name; }); - LINFO(std::to_string(_nImagesWith3dPositions) + " 3D positions were matched in the speck files!"); + LINFO("Loaded " + std::to_string(_images.size()) + " WorldWide Telescope " + "images."); + + LINFO(std::to_string(_nMatched3dPositions) + " 3D positions were matched in " + "the speck files!"); + } + + int WwtDataHandler::nLoadedImages() const + { return _images.size(); } - const std::vector& WwtDataHandler::getAllImageCollectionUrls() const { - return _imageUrls; + const ImageData& WwtDataHandler::getImage(const int i) const + { + assert(i < _images.size(), "Index outside of image vector boundaries!"); + return _images[i]; } - void WwtDataHandler::loadImagesFromXml(tinyxml2::XMLElement* node, std::string collectionName) { - // Get direct child of node called "Place" - using namespace tinyxml2; - XMLElement* ptr = node->FirstChildElement(); + void WwtDataHandler::saveImageFromNode(tinyxml2::XMLElement* node, + std::string collection) { - // Go through all siblings of ptr and open folders recursively - // Iterate through all siblings at same level and load - while (ptr) { - // If node is an image or place, load it - if (std::string(ptr->Name()) == "ImageSet" || std::string(ptr->Name()) == "Place") { - loadImageFromXmlNode(ptr, collectionName); - } - // If node is another folder, open recursively - else if (std::string(ptr->Name()) == "Folder") { - std::string newCollectionName = collectionName + "/"; - if (ptr->FindAttribute("Name")) { - newCollectionName += std::string(ptr->FindAttribute("Name")->Value()); - } - loadImagesFromXml(ptr, newCollectionName); - } + // 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()); - ptr = ptr->NextSiblingElement(); - } - - } - - int WwtDataHandler::loadImageFromXmlNode(tinyxml2::XMLElement* node, std::string collectionName) { - // Only load "Sky" type images - if (std::string(node->FindAttribute("DataSetType")->Value()) != "Sky") - return -1; - - std::string thumbnailUrl; - std::string credits; - std::string creditsUrl; - std::string imageUrl; - tinyxml2::XMLElement* imageSet = nullptr; - // Get url. The thumbnail can be located either in the Place or the ImageSet - if (std::string(node->Name()) == "ImageSet") { - thumbnailUrl = getChildNodeContentFromImageSet(node, "ThumbnailUrl"); + if (type == wwt::ImageSet) { + thumbnailUrl = getChildNodeContentFromImageSet(node, wwt::ThumbnailUrl); imageSet = node; - } - else if (std::string(node->Name()) == "Place") { + } // Place + else if (type == wwt::Place) { thumbnailUrl = getUrlFromPlace(node); - imageSet = getChildNode(node, "ImageSet"); + imageSet = getChildNode(node, wwt::ImageSet); } - else { - return -1; - } - - // Only load images that have a thumbnail image url - if (thumbnailUrl == "" || !imageSet) { - return -1; - } - // Only load images that contain a image url - if (!imageSet->FindAttribute("Url")) { - return -1; - } - // The credits and image url are always children nodes of ImageSet - credits = getChildNodeContentFromImageSet(imageSet, "Credits"); - creditsUrl = getChildNodeContentFromImageSet(imageSet, "CreditsUrl"); - imageUrl = imageSet->FindAttribute("Url")->Value(); - ImageData image{}; - setImageDataValues(node, credits, creditsUrl, thumbnailUrl, collectionName, imageUrl, image); + // Only collect the images that have a thumbnail image, that are sky images and + // that have an image + bool hasThumbnailUrl = thumbnailUrl != wwt::Undefined; + bool isSkyImage = getAttribute(node, wwt::DataSetType) == wwt::Sky; + bool hasImageUrl = imageSet ? hasAttribute(imageSet, wwt::Url) : false; + + if (!(hasThumbnailUrl && isSkyImage && hasImageUrl)) { + return; + } + + // Collect name, image url and credits + std::string name = getAttribute(node, wwt::Name); + std::string imageUrl = getAttribute(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{ 0.0 }; + glm::dvec3 equatorialCartesian{ 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(getAttribute(node, wwt::RA)); + double dec = std::stod(getAttribute(node, wwt::Dec)); + equatorialSpherical = { 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(getAttribute(node, wwt::ZoomLevel)) / 6.0; + } + + // Find 3D position by matching with speck file + bool has3dCoords{ false }; + glm::dvec3 position3d{ 0.0 }; + auto it = _3dPositions.find(createSearchableString(name)); + if (it != _3dPositions.end()) { + position3d = it->second; + has3dCoords = true; + _nMatched3dPositions++; + } + + ImageData image = { + name, + thumbnailUrl, + imageUrl, + credits, + creditsUrl, + collection, + hasCelestialCoords, + has3dCoords, + fov, + equatorialSpherical, + equatorialCartesian, + position3d + }; _images.push_back(image); - // Return index of image in vector - return _images.size(); } - std::string WwtDataHandler::getChildNodeContentFromImageSet(tinyxml2::XMLElement* imageSet, 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()); - return imageSetChild ? imageSetChild->GetText() ? imageSetChild->GetText() : "" : ""; - } + void WwtDataHandler::saveImagesFromXml(tinyxml2::XMLElement* root, + std::string collection) { - std::string WwtDataHandler::getUrlFromPlace(tinyxml2::XMLElement* place) { - // Get thumbnail attribute, if there is one - std::string url = place->FindAttribute("Thumbnail") ? place->FindAttribute("Thumbnail")->Value() : ""; - // Url found! Return it - if (url != "") return url; + // Get direct child of node called Place + using namespace tinyxml2; + XMLElement* node = root->FirstChildElement(); - // If the place doesn't have a thumbnail url data attribute, - // Load the image set it stores instead - tinyxml2::XMLElement* imageSet = getChildNode(place, "ImageSet"); - // If it doesn't contain an ImageSet, it doesn't have an url -> return empty string - // If there is an imageSet, collect thumbnail url - return imageSet ? getChildNodeContentFromImageSet(imageSet, "ThumbnailUrl") : ""; - } - - tinyxml2::XMLElement* WwtDataHandler::getDirectChildNode(tinyxml2::XMLElement* node, std::string name) { - while (node && std::string(node->Name()) != name) { - node = node->FirstChildElement(); - } - return node; - } - - tinyxml2::XMLElement* WwtDataHandler::getChildNode(tinyxml2::XMLElement* node, std::string name) { - // Traverse the children and look at all their first child to find ImageSet - tinyxml2::XMLElement* child = node->FirstChildElement(); - tinyxml2::XMLElement* imageSet = nullptr; - while (child) { - imageSet = getDirectChildNode(child, name); - if (imageSet) break; - child = child->NextSiblingElement(); - } - return imageSet; - } - - void WwtDataHandler::setImageDataValues(tinyxml2::XMLElement* node, - std::string credits, - std::string creditsUrl, - std::string thumbnail, - std::string collectionName, - std::string imageUrl, - ImageData& img) { - // Get attributes for the image - img.name = node->FindAttribute("Name") ? node->FindAttribute("Name")->Value() : ""; - img.hasCelestialCoords = node->FindAttribute("RA") && node->FindAttribute("Dec"); - if (img.hasCelestialCoords) { - // The RA from WWT is in the unit hours: to convert to degrees, multiply with 360 (deg) /24 (h) = 15 - img.celestialCoords.x = 15.0f * std::stof(node->FindAttribute("RA")->Value()); - img.celestialCoords.y = std::stof(node->FindAttribute("Dec")->Value()); - } - img.collection = collectionName; - img.thumbnailUrl = thumbnail; - // In WWT, the definition of ZoomLevel is: VFOV = ZoomLevel / 6 - img.fov = node->FindAttribute("ZoomLevel") ? std::stof(node->FindAttribute("ZoomLevel")->Value()) / 6: 0.f; - img.credits = credits; - img.creditsUrl = creditsUrl; - img.imageUrl = imageUrl; - // Look for 3D position in the data loaded from speck files - std::string str = createSearchableString(img.name); - // Look for 3D coordinate - auto it = _3dPositions.find(str); - if (it != _3dPositions.end()) { - img.position3d = it->second; - img.has3dCoords = true; - _nImagesWith3dPositions++; - } - } - - std::vector& WwtDataHandler::getLoadedImages() { - return _images; - } - - void WwtDataHandler::loadSpeckData(speck::Dataset& dataset) { - for (speck::Dataset::Entry entry : dataset.entries) { - if (entry.comment.has_value()) { - std::string name = createSearchableString(entry.comment.value()); - _3dPositions[name] = std::move(entry.position); + // 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 node is another folder, open recursively + else if (name == wwt::Folder) { + std::string newCollectionName = collection + "/"; + newCollectionName += getAttribute(node, wwt::Name); + + saveImagesFromXml(node, newCollectionName); + } + node = node->NextSiblingElement(); } - LINFO("Loaded speck file with " + std::to_string(_3dPositions.size()) + " entries!"); } - std::string WwtDataHandler::createSearchableString(std::string str) { - // Remove white spaces and all special characters - str.erase(std::remove_if(str.begin(), str.end(), [](char c) { - 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(), - [](unsigned char c) { return std::tolower(c); }); - return str; - } } // Loading of speck files