diff --git a/data/assets/util/webgui.asset b/data/assets/util/webgui.asset index 1e5501383a..5edcc06087 100644 --- a/data/assets/util/webgui.asset +++ b/data/assets/util/webgui.asset @@ -3,7 +3,7 @@ asset.require("./static_server") local guiCustomization = asset.require("customization/gui") -- Select which commit hashes to use for the frontend and backend -local frontendHash = "5011a84942cee1567565a4be250501f4453f26a2" +local frontendHash = "644f308be66309c2089bf6f94929205b24d56a8f" local dataProvider = "data.openspaceproject.com/files/webgui" local frontend = asset.syncedResource({ diff --git a/modules/skybrowser/include/targetbrowserpair.h b/modules/skybrowser/include/targetbrowserpair.h index cc0fdc2a9f..de3e2f75d2 100644 --- a/modules/skybrowser/include/targetbrowserpair.h +++ b/modules/skybrowser/include/targetbrowserpair.h @@ -87,17 +87,17 @@ public: std::string targetNodeId() const; ScreenSpaceSkyBrowser* browser() const; - std::vector selectedImages() const; + std::vector selectedImages() const; ghoul::Dictionary dataAsDictionary() const; // WorldWide Telescope image handling - void setImageOrder(int i, int order); - void selectImage(const ImageData& image, int i); - void addImageLayerToWwt(const std::string& url, int i); - void removeSelectedImage(int i); + void setImageOrder(const std::string& imageUrl, int order); + void selectImage(const ImageData& image); + void addImageLayerToWwt(const std::string& imageUrl); + void removeSelectedImage(const std::string& imageUrl); void loadImageCollection(const std::string& collection); - void setImageOpacity(int i, float opacity); + void setImageOpacity(const std::string& imageUrl, float opacity); void hideChromeInterface(); private: diff --git a/modules/skybrowser/include/wwtcommunicator.h b/modules/skybrowser/include/wwtcommunicator.h index b7e26c040f..61f3e2ac65 100644 --- a/modules/skybrowser/include/wwtcommunicator.h +++ b/modules/skybrowser/include/wwtcommunicator.h @@ -31,6 +31,7 @@ #include namespace openspace { +using SelectedImageDeque = std::deque>; class WwtCommunicator : public Browser { public: @@ -40,12 +41,12 @@ public: void update(); // WorldWide Telescope communication - void selectImage(const std::string& url, int i); - void addImageLayerToWwt(const std::string& url, int i); - void removeSelectedImage(int i); - void setImageOrder(int image, int order); + void selectImage(const std::string& imageUrl); + void addImageLayerToWwt(const std::string& imageUrl); + void removeSelectedImage(const std::string& imageUrl); + void setImageOrder(const std::string& imageUrl, int order); void loadImageCollection(const std::string& collection); - void setImageOpacity(int i, float opacity); + void setImageOpacity(const std::string& imageUrl, float opacity); void hideChromeInterface() const; bool isImageCollectionLoaded() const; @@ -54,7 +55,7 @@ public: glm::ivec3 borderColor() const; glm::dvec2 equatorialAim() const; glm::dvec2 fieldsOfView() const; - std::vector selectedImages() const; + std::vector selectedImages() const; std::vector opacities() const; double borderRadius() const; @@ -70,7 +71,7 @@ public: protected: void setIdInBrowser(const std::string& id) const; - std::deque>::iterator findSelectedImage(int i); + SelectedImageDeque::iterator findSelectedImage(const std::string& id); properties::DoubleProperty _verticalFov; @@ -79,7 +80,7 @@ protected: glm::dvec2 _equatorialAim = glm::dvec2(0.0); double _targetRoll = 0.0; bool _isImageCollectionLoaded = false; - std::deque> _selectedImages; + SelectedImageDeque _selectedImages; private: void sendMessageToWwt(const ghoul::Dictionary& msg) const; diff --git a/modules/skybrowser/include/wwtdatahandler.h b/modules/skybrowser/include/wwtdatahandler.h index 206f0baf9f..2e1ce2f5ec 100644 --- a/modules/skybrowser/include/wwtdatahandler.h +++ b/modules/skybrowser/include/wwtdatahandler.h @@ -45,19 +45,21 @@ struct ImageData { float fov = 0.f; glm::dvec2 equatorialSpherical = glm::dvec2(0.0); glm::dvec3 equatorialCartesian = glm::dvec3(0.0); + std::string identifier; }; class WwtDataHandler { public: void loadImages(const std::string& root, const std::filesystem::path& directory); int nLoadedImages() const; - const ImageData& image(int i) const; + std::optional image(const std::string& imageUrl) const; + const std::map& images() const; private: void saveImagesFromXml(const tinyxml2::XMLElement* root, std::string collection); // Images - std::vector _images; + std::map _images; }; } // namespace openspace diff --git a/modules/skybrowser/skybrowsermodule.cpp b/modules/skybrowser/skybrowsermodule.cpp index 55f6c41cc0..c3bae40e1f 100644 --- a/modules/skybrowser/skybrowsermodule.cpp +++ b/modules/skybrowser/skybrowsermodule.cpp @@ -318,9 +318,12 @@ void SkyBrowserModule::setHoverCircle(SceneGraphNode* circle) { disableHoverCircle(); } -void SkyBrowserModule::moveHoverCircle(int i, bool useScript) { - const ImageData& image = _dataHandler.image(i); - +void SkyBrowserModule::moveHoverCircle(const std::string& imageUrl, bool useScript) { + std::optional found = _dataHandler.image(imageUrl); + if (!found.has_value()) { + return; + } + const ImageData image = *found; // Only move and show circle if the image has coordinates if (!(_hoverCircle && image.hasCelestialCoords && _isCameraInSolarSystem)) { return; diff --git a/modules/skybrowser/skybrowsermodule.h b/modules/skybrowser/skybrowsermodule.h index d29f073136..822b613b9c 100644 --- a/modules/skybrowser/skybrowsermodule.h +++ b/modules/skybrowser/skybrowsermodule.h @@ -76,7 +76,7 @@ public: void addTargetBrowserPair(const std::string& targetId, const std::string& browserId); // Hover circle - void moveHoverCircle(int i, bool useScript = true); + void moveHoverCircle(const std::string& imageUrl, bool useScript = true); void disableHoverCircle(bool useScript = true); // Image collection handling diff --git a/modules/skybrowser/skybrowsermodule_lua.inl b/modules/skybrowser/skybrowsermodule_lua.inl index c72f16b93d..c8a9921bd8 100644 --- a/modules/skybrowser/skybrowsermodule_lua.inl +++ b/modules/skybrowser/skybrowsermodule_lua.inl @@ -74,7 +74,7 @@ namespace { * Takes an index to an image and selects that image in the currently * selected sky browser. */ -[[codegen::luawrap]] void selectImage(int imageIndex) { +[[codegen::luawrap]] void selectImage(std::string imageUrl) { using namespace openspace; // Load image @@ -83,8 +83,17 @@ namespace { if (module->isCameraInSolarSystem()) { TargetBrowserPair* selected = module->pair(module->selectedBrowserId()); if (selected) { - const ImageData& image = module->wwtDataHandler().image(imageIndex); + std::optional found = module->wwtDataHandler().image( + imageUrl + ); + if (!found.has_value()) { + LINFO(fmt::format( + "No image with identifier {} was found in the collection.", imageUrl + )); + return; + } // Load image into browser + const ImageData& image = found.value(); std::string str = image.name; // Check if character is ASCII - if it isn't, remove str.erase( @@ -97,7 +106,7 @@ namespace { str.end() ); LINFO("Loading image " + str); - selected->selectImage(image, imageIndex); + selected->selectImage(image); bool isInView = skybrowser::isCoordinateInView(image.equatorialCartesian); // If the coordinate is not in view, rotate camera @@ -130,10 +139,10 @@ namespace { /** * Moves the hover circle to the coordinate specified by the image index. */ -[[codegen::luawrap]] void moveCircleToHoverImage(int imageIndex) { +[[codegen::luawrap]] void moveCircleToHoverImage(std::string imageUrl) { using namespace openspace; - global::moduleEngine->module()->moveHoverCircle(imageIndex, false); + global::moduleEngine->module()->moveHoverCircle(imageUrl, false); } /** @@ -150,7 +159,7 @@ namespace { * which it should have in the selected image list. The image is then changed to have this * order. */ -[[codegen::luawrap]] void setImageLayerOrder(std::string identifier, int imageIndex, +[[codegen::luawrap]] void setImageLayerOrder(std::string identifier, std::string imageUrl, int imageOrder) { using namespace openspace; @@ -158,7 +167,7 @@ namespace { SkyBrowserModule* module = global::moduleEngine->module(); TargetBrowserPair* pair = module->pair(identifier); if (pair) { - pair->setImageOrder(imageIndex, imageOrder); + pair->setImageOrder(imageUrl, imageOrder); } } @@ -296,10 +305,7 @@ namespace { // Create Lua table to send to the GUI ghoul::Dictionary list; - - for (int i = 0; i < module->nLoadedImages(); i++) { - const ImageData& img = module->wwtDataHandler().image(i); - + for (auto const& [id, img] : module->wwtDataHandler().images()) { // Push ("Key", value) ghoul::Dictionary image; image.setValue("name", img.name); @@ -312,11 +318,9 @@ namespace { image.setValue("hasCelestialCoords", img.hasCelestialCoords); image.setValue("credits", img.credits); image.setValue("creditsUrl", img.creditsUrl); - image.setValue("identifier", std::to_string(i)); + image.setValue("identifier", img.identifier); - // Index for current ImageData - // Set table for the current ImageData - list.setValue(std::to_string(i + 1), image); + list.setValue(img.identifier, image); } return list; @@ -409,15 +413,15 @@ namespace { * Takes an identifier to a sky browser or sky target, an index to an image and a value * for the opacity. */ -[[codegen::luawrap]] void setOpacityOfImageLayer(std::string identifier, int imageIndex, - float opacity) +[[codegen::luawrap]] void setOpacityOfImageLayer(std::string identifier, + std::string imageUrl, float opacity) { using namespace openspace; SkyBrowserModule* module = global::moduleEngine->module(); TargetBrowserPair* pair = module->pair(identifier); if (pair) { - pair->setImageOpacity(imageIndex, opacity); + pair->setImageOpacity(imageUrl, opacity); } } @@ -593,14 +597,14 @@ namespace { * image from that sky browser. */ [[codegen::luawrap]] void removeSelectedImageInBrowser(std::string identifier, - int imageIndex) + std::string imageUrl) { using namespace openspace; SkyBrowserModule* module = global::moduleEngine->module(); TargetBrowserPair* pair = module->pair(identifier); if (pair) { - pair->browser()->removeSelectedImage(imageIndex); + pair->browser()->removeSelectedImage(imageUrl); } } @@ -771,13 +775,13 @@ namespace { LINFO("Image collection is loaded in Screen Space Sky Browser " + identifier); pair->setImageCollectionIsLoaded(true); // Add all selected images to WorldWide Telescope - const std::vector& images = pair->selectedImages(); + const std::vector& images = pair->selectedImages(); std::for_each( images.rbegin(), images.rend(), - [&](int index) { - const ImageData& image = module->wwtDataHandler().image(index); + [&](std::string imageUrl) { + const ImageData& image = module->wwtDataHandler().image(imageUrl).value(); // Index of image is used as layer ID as it's unique in the image data set - pair->browser()->addImageLayerToWwt(image.imageUrl, index); + pair->browser()->addImageLayerToWwt(image.imageUrl); } ); } diff --git a/modules/skybrowser/src/targetbrowserpair.cpp b/modules/skybrowser/src/targetbrowserpair.cpp index 01542b8f40..2bab94451e 100644 --- a/modules/skybrowser/src/targetbrowserpair.cpp +++ b/modules/skybrowser/src/targetbrowserpair.cpp @@ -69,8 +69,8 @@ TargetBrowserPair::TargetBrowserPair(SceneGraphNode* targetNode, _targetRenderable = dynamic_cast(_targetNode->renderable()); } -void TargetBrowserPair::setImageOrder(int i, int order) { - _browser->setImageOrder(i, order); +void TargetBrowserPair::setImageOrder(const std::string& imageUrl, int order) { + _browser->setImageOrder(imageUrl, order); } void TargetBrowserPair::startFinetuningTarget() { @@ -151,13 +151,23 @@ double TargetBrowserPair::verticalFov() const { return _browser->verticalFov(); } -std::vector TargetBrowserPair::selectedImages() const { +std::vector TargetBrowserPair::selectedImages() const { return _browser->selectedImages(); } ghoul::Dictionary TargetBrowserPair::dataAsDictionary() const { glm::dvec2 spherical = targetDirectionEquatorial(); glm::dvec3 cartesian = skybrowser::sphericalToCartesian(spherical); + SkyBrowserModule* module = global::moduleEngine->module(); + std::vector selectedImagesIndices; + + for (const std::string& imageUrl : selectedImages()) { + bool imageExists = module->wwtDataHandler().image(imageUrl).has_value(); + ghoul_assert(imageExists, "Image doesn't exist in the wwt catalog!"); + selectedImagesIndices.push_back( + module->wwtDataHandler().image(imageUrl)->identifier + ); + } ghoul::Dictionary res; res.setValue("id", browserId()); @@ -172,7 +182,7 @@ ghoul::Dictionary TargetBrowserPair::dataAsDictionary() const { res.setValue("ratio", static_cast(_browser->browserRatio())); res.setValue("isFacingCamera", isFacingCamera()); res.setValue("isUsingRae", isUsingRadiusAzimuthElevation()); - res.setValue("selectedImages", selectedImages()); + res.setValue("selectedImages", selectedImagesIndices); res.setValue("scale", static_cast(_browser->scale())); res.setValue("opacities", _browser->opacities()); res.setValue("borderRadius", _browser->borderRadius()); @@ -193,9 +203,9 @@ ghoul::Dictionary TargetBrowserPair::dataAsDictionary() const { return res; } -void TargetBrowserPair::selectImage(const ImageData& image, int i) { +void TargetBrowserPair::selectImage(const ImageData& image) { // Load image into browser - _browser->selectImage(image.imageUrl, i); + _browser->selectImage(image.imageUrl); // If the image has coordinates, move the target if (image.hasCelestialCoords) { @@ -205,20 +215,20 @@ void TargetBrowserPair::selectImage(const ImageData& image, int i) { } } -void TargetBrowserPair::addImageLayerToWwt(const std::string& url, int i) { - _browser->addImageLayerToWwt(url, i); +void TargetBrowserPair::addImageLayerToWwt(const std::string& imageUrl) { + _browser->addImageLayerToWwt(imageUrl); } -void TargetBrowserPair::removeSelectedImage(int i) { - _browser->removeSelectedImage(i); +void TargetBrowserPair::removeSelectedImage(const std::string& imageUrl) { + _browser->removeSelectedImage(imageUrl); } void TargetBrowserPair::loadImageCollection(const std::string& collection) { _browser->loadImageCollection(collection); } -void TargetBrowserPair::setImageOpacity(int i, float opacity) { - _browser->setImageOpacity(i, opacity); +void TargetBrowserPair::setImageOpacity(const std::string& imageUrl, float opacity) { + _browser->setImageOpacity(imageUrl, opacity); } void TargetBrowserPair::hideChromeInterface() { diff --git a/modules/skybrowser/src/wwtcommunicator.cpp b/modules/skybrowser/src/wwtcommunicator.cpp index 6fd099b2d5..a02daeb31a 100644 --- a/modules/skybrowser/src/wwtcommunicator.cpp +++ b/modules/skybrowser/src/wwtcommunicator.cpp @@ -69,12 +69,12 @@ namespace { return msg; } - ghoul::Dictionary addImageMessage(const std::string& id, const std::string& url) { + ghoul::Dictionary addImageMessage(const std::string& url) { using namespace std::string_literals; ghoul::Dictionary msg; msg.setValue("event", "image_layer_create"s); - msg.setValue("id", id); + msg.setValue("id", url); msg.setValue("url", url); msg.setValue("mode", "preloaded"s); msg.setValue("goto", false); @@ -101,7 +101,7 @@ namespace { return msg; } - ghoul::Dictionary setLayerOrderMessage(const std::string& id, int order) { + ghoul::Dictionary setLayerOrderMessage(const std::string& imageUrl, int order) { static int MessageCounter = 0; // The lower the layer order, the more towards the back the image is placed @@ -110,7 +110,7 @@ namespace { ghoul::Dictionary msg; msg.setValue("event", "image_layer_order"s); - msg.setValue("id", id); + msg.setValue("id", imageUrl); msg.setValue("order", order); msg.setValue("version", MessageCounter); @@ -165,33 +165,33 @@ void WwtCommunicator::update() { Browser::update(); } -void WwtCommunicator::selectImage(const std::string& url, int i) { +void WwtCommunicator::selectImage(const std::string& url) { // Ensure there are no duplicates - auto it = findSelectedImage(i); + auto it = findSelectedImage(url); if (it == _selectedImages.end()) { // Push newly selected image to front - _selectedImages.push_front(std::pair(i, 1.0)); + _selectedImages.push_front(std::pair(url, 1.0)); // If wwt has not loaded the collection yet, wait with passing the message if (_isImageCollectionLoaded) { - addImageLayerToWwt(url, i); + addImageLayerToWwt(url); } } } -void WwtCommunicator::addImageLayerToWwt(const std::string& url, int i) { +void WwtCommunicator::addImageLayerToWwt(const std::string& imageUrl) { // Index of image is used as layer ID as it is unique in the image data set - sendMessageToWwt(addImageMessage(std::to_string(i), url)); - sendMessageToWwt(setImageOpacityMessage(std::to_string(i), 1.0)); + sendMessageToWwt(addImageMessage(imageUrl)); + sendMessageToWwt(setImageOpacityMessage(imageUrl, 1.0)); } -void WwtCommunicator::removeSelectedImage(int i) { +void WwtCommunicator::removeSelectedImage(const std::string& imageUrl) { // Remove from selected list - auto it = findSelectedImage(i); + auto it = findSelectedImage(imageUrl); if (it != _selectedImages.end()) { _selectedImages.erase(it); - sendMessageToWwt(removeImageMessage(std::to_string(i))); + sendMessageToWwt(removeImageMessage(imageUrl)); } } @@ -200,14 +200,14 @@ void WwtCommunicator::sendMessageToWwt(const ghoul::Dictionary& msg) const { executeJavascript(fmt::format("sendMessageToWWT({});", m)); } -std::vector WwtCommunicator::selectedImages() const { - std::vector selectedImagesVector; +std::vector WwtCommunicator::selectedImages() const { + std::vector selectedImagesVector; selectedImagesVector.resize(_selectedImages.size()); std::transform( _selectedImages.cbegin(), _selectedImages.cend(), selectedImagesVector.begin(), - [](const std::pair& image) { return image.first; } + [](const std::pair& image) { return image.first; } ); return selectedImagesVector; } @@ -219,7 +219,7 @@ std::vector WwtCommunicator::opacities() const { _selectedImages.cbegin(), _selectedImages.cend(), opacities.begin(), - [](const std::pair& image) { return image.second; } + [](const std::pair& image) { return image.second; } ); return opacities; } @@ -277,11 +277,15 @@ bool WwtCommunicator::isImageCollectionLoaded() const { return _isImageCollectionLoaded; } -std::deque>::iterator WwtCommunicator::findSelectedImage(int i) { +SelectedImageDeque::iterator WwtCommunicator::findSelectedImage( + const std::string& imageUrl) +{ auto it = std::find_if( _selectedImages.begin(), _selectedImages.end(), - [i](const std::pair& pair) { return pair.first == i; } + [imageUrl](const std::pair& pair) { + return pair.first == imageUrl; + } ); return it; } @@ -290,12 +294,12 @@ glm::dvec2 WwtCommunicator::equatorialAim() const { return _equatorialAim; } -void WwtCommunicator::setImageOrder(int image, int order) { +void WwtCommunicator::setImageOrder(const std::string& imageUrl, int order) { // Find in selected images list - auto current = findSelectedImage(image); + auto current = findSelectedImage(imageUrl); int currentIndex = static_cast(std::distance(_selectedImages.begin(), current)); - std::deque> newDeque; + std::deque> newDeque; for (int i = 0; i < static_cast(_selectedImages.size()); i++) { if (i == currentIndex) { @@ -318,7 +322,7 @@ void WwtCommunicator::setImageOrder(int image, int order) { _selectedImages = newDeque; int reverseOrder = static_cast(_selectedImages.size()) - order - 1; - ghoul::Dictionary message = setLayerOrderMessage(std::to_string(image), reverseOrder); + ghoul::Dictionary message = setLayerOrderMessage(imageUrl, reverseOrder); sendMessageToWwt(message); } @@ -328,11 +332,11 @@ void WwtCommunicator::loadImageCollection(const std::string& collection) { } } -void WwtCommunicator::setImageOpacity(int i, float opacity) { - auto it = findSelectedImage(i); +void WwtCommunicator::setImageOpacity(const std::string& imageUrl, float opacity) { + auto it = findSelectedImage(imageUrl); it->second = opacity; - ghoul::Dictionary msg = setImageOpacityMessage(std::to_string(i), opacity); + ghoul::Dictionary msg = setImageOpacityMessage(imageUrl, opacity); sendMessageToWwt(msg); } diff --git a/modules/skybrowser/src/wwtdatahandler.cpp b/modules/skybrowser/src/wwtdatahandler.cpp index 916ca88ab5..a465eb4ab7 100644 --- a/modules/skybrowser/src/wwtdatahandler.cpp +++ b/modules/skybrowser/src/wwtdatahandler.cpp @@ -171,9 +171,8 @@ namespace { return true; } - std::optional loadImageFromNode( - const tinyxml2::XMLElement* node, - std::string collection) + std::optional + loadImageFromNode(const tinyxml2::XMLElement* node, const std::string& collection) { using namespace openspace; @@ -243,7 +242,8 @@ namespace { hasCelestialCoords, fov, equatorialSpherical, - equatorialCartesian + equatorialCartesian, + "" }; } } //namespace @@ -318,13 +318,21 @@ void WwtDataHandler::loadImages(const std::string& root, saveImagesFromXml(rootNode, collectionName); } } - - // Sort images in alphabetical order - std::sort( - _images.begin(), - _images.end(), - [](ImageData& a, ImageData& b) { return a.name < b.name; } + // Sort images. Copy images to vector + std::vector _imageVector; + for (const auto& [id, img] : _images) { + _imageVector.push_back(img); + } + // Sort + std::sort(_imageVector.begin(), _imageVector.end(), + [](const ImageData& lhs, const ImageData& rhs) { + return lhs.name < rhs.name; + } ); + // Set the identifiers to the correct order + for (int i = 0; i < _imageVector.size(); i++) { + _images[_imageVector[i].imageUrl].identifier = std::to_string(i); + } LINFO(fmt::format("Loaded {} WorldWide Telescope images", _images.size())); } @@ -333,9 +341,16 @@ int WwtDataHandler::nLoadedImages() const { return static_cast(_images.size()); } -const ImageData& WwtDataHandler::image(int i) const { - ghoul_assert(i < static_cast(_images.size()), "Index outside of vector size"); - return _images[i]; +std::optional WwtDataHandler::image(const std::string& imageUrl) const { + auto it = _images.find(imageUrl); + if (it == _images.end()) { + return std::nullopt; + } + return it->second; +} + +const std::map& WwtDataHandler::images() const { + return _images; } void WwtDataHandler::saveImagesFromXml(const tinyxml2::XMLElement* root, @@ -350,9 +365,11 @@ void WwtDataHandler::saveImagesFromXml(const tinyxml2::XMLElement* root, const std::string name = node->Name(); // If node is an image or place, load it if (name == ImageSet || name == Place) { - std::optional image = loadImageFromNode(node, collection); + std::optional image = loadImageFromNode( + node, collection + ); if (image.has_value()) { - _images.push_back(std::move(*image)); + _images.insert({ image.value().imageUrl, std::move(*image) }); } }