From 8e8ac4e90e3ef5f28f2d2e559566751f46aebc13 Mon Sep 17 00:00:00 2001 From: Ylva Selling Date: Wed, 2 Jun 2021 15:35:07 +0200 Subject: [PATCH] Initialize browsers with lua api and load image collections upon startup. Add 3D browser functionality --- data/assets/renderableBrowser.asset | 32 +- .../skybrowser/include/renderableskybrowser.h | 15 + .../include/screenspaceskybrowser.h | 1 - modules/skybrowser/include/utility.h | 10 +- modules/skybrowser/include/wwtdatahandler.h | 2 - modules/skybrowser/skybrowsermodule.cpp | 174 ++++----- modules/skybrowser/skybrowsermodule.h | 10 +- modules/skybrowser/skybrowsermodule_lua.inl | 332 ++++++++++++------ .../skybrowser/src/renderableskybrowser.cpp | 84 ++++- .../skybrowser/src/screenspaceskybrowser.cpp | 15 +- modules/skybrowser/src/utility.cpp | 46 ++- 11 files changed, 476 insertions(+), 245 deletions(-) diff --git a/data/assets/renderableBrowser.asset b/data/assets/renderableBrowser.asset index 7f04a9101f..2748b32081 100644 --- a/data/assets/renderableBrowser.asset +++ b/data/assets/renderableBrowser.asset @@ -2,9 +2,10 @@ local assetHelper = asset.require("util/asset_helper") local transforms = asset.require("scene/solarsystem/sun/transforms") local PARSEC_CONSTANT = 3.0856776E16; +local browserId = "SkyBrowser3D"; -local spec = { - Identifier = "RenderableSkyBrowser1", +local browser = { + Identifier = browserId, Parent = transforms.SolarSystemBarycenter.Identifier, Transform = { Translation = { @@ -13,21 +14,36 @@ local spec = { -3.915 * PARSEC_CONSTANT, -150.153 * PARSEC_CONSTANT, -120.706 * PARSEC_CONSTANT - } + }, }, + Rotation = { + Type = "StaticRotation", + Rotation = {0.0, 0.0, 0.0} + } }, Renderable = { + Identifier = "SkyBrowser3DRenderable", Type = "RenderableSkyBrowser", Size = 10.0E11, Origin = "Center", - Billboard = true, - Url = "http://localhost:8000" + Billboard = false, + Url = "http://localhost:8000", + Opacity = 0.99 }, GUI = { - Name = "Renderable Sky Browser", + Name = "Sky Browser 3D", Path = "/SkyBrowser", } } -local objects = { spec } -assetHelper.registerSceneGraphNodesAndExport(asset, objects) +asset.onInitialize(function () + openspace.addSceneGraphNode(browser) + openspace.skybrowser.addToSkyBrowserModule(browserId) +end) + +asset.onDeinitialize(function () + openspace.removeScreenSpaceRenderable(browserId) + openspace.removeScreenSpaceRenderable(targetId) +end) + +asset.export("browser", {browser}) diff --git a/modules/skybrowser/include/renderableskybrowser.h b/modules/skybrowser/include/renderableskybrowser.h index d9c5eabc46..10c545b34b 100644 --- a/modules/skybrowser/include/renderableskybrowser.h +++ b/modules/skybrowser/include/renderableskybrowser.h @@ -6,6 +6,7 @@ #include #include #include +#include #ifdef _MSC_VER #pragma warning (push) @@ -36,6 +37,7 @@ namespace openspace { class BrowserInstance; class RenderHandler; class WebKeyboardHandler; + class ImageData; class RenderableSkyBrowser : public RenderablePlane { @@ -52,12 +54,20 @@ namespace openspace { void executeJavascript(std::string script) const; bool sendMessageToWWT(const ghoul::Dictionary& msg); + void connectToWwt(); + void stopConnectingToWwt(); + void displayImage(ImageData& image, int i); + void removeSelectedImage(ImageData& image, int i); + void setIdInBrowser(std::string id); + float fieldOfView() const; + std::deque& selectedImages(); protected: properties::Vec2Property _dimensions; std::unique_ptr _browserInstance; std::unique_ptr _texture; + private: class ScreenSpaceRenderHandler : public WebRenderHandler { @@ -80,7 +90,12 @@ namespace openspace { bool _isUrlDirty = false; bool _isDimensionsDirty = false; + + float _fov; + bool _connectToWwt; + std::thread _threadWwtMessages; + std::deque _selectedImages; }; } diff --git a/modules/skybrowser/include/screenspaceskybrowser.h b/modules/skybrowser/include/screenspaceskybrowser.h index 09c2158ec6..92a311bff8 100644 --- a/modules/skybrowser/include/screenspaceskybrowser.h +++ b/modules/skybrowser/include/screenspaceskybrowser.h @@ -67,7 +67,6 @@ namespace openspace { // For capping the calls to change the zoom from scrolling constexpr static const std::chrono::milliseconds TimeUpdateInterval{ 10 }; std::chrono::system_clock::time_point _lastUpdateTime; - int _imageId{ 0 }; bool _hasLoadedCollections{ false }; std::deque _selectedImages; }; diff --git a/modules/skybrowser/include/utility.h b/modules/skybrowser/include/utility.h index e76564f1ad..da5355124d 100644 --- a/modules/skybrowser/include/utility.h +++ b/modules/skybrowser/include/utility.h @@ -37,17 +37,17 @@ namespace openspace { glm::dvec3 J2000SphericalToScreenSpace(glm::dvec2 coords); glm::dvec3 J2000CartesianToScreenSpace(glm::dvec3 coords); glm::dvec3 galacticToScreenSpace(glm::dvec3 galacticCoord); + double calculateRoll(glm::dvec3 upWorld, glm::dvec3 forwardWorld); } namespace wwtmessage { // WWT messages ghoul::Dictionary moveCamera(const glm::dvec2 celestCoords, - const double fov, const bool moveInstantly = true); + const double fov, const double roll, const bool moveInstantly = true); ghoul::Dictionary loadCollection(const std::string& url); ghoul::Dictionary setForeground(const std::string& name); - ghoul::Dictionary createImageLayer(ImageData& image, int id = 0); - ghoul::Dictionary removeImageLayer(ImageData& image); - ghoul::Dictionary setLayerOpacity(const ImageData& image, - double opacity); + ghoul::Dictionary createImageLayer(const std::string& imageUrl, const std::string& id); + ghoul::Dictionary removeImageLayer(const std::string& imageId); + ghoul::Dictionary setLayerOpacity(const std::string& imageId, double opacity); ghoul::Dictionary setForegroundOpacity(double val); } } diff --git a/modules/skybrowser/include/wwtdatahandler.h b/modules/skybrowser/include/wwtdatahandler.h index 90cc02e2cc..8d078cd804 100644 --- a/modules/skybrowser/include/wwtdatahandler.h +++ b/modules/skybrowser/include/wwtdatahandler.h @@ -54,7 +54,6 @@ namespace openspace::speck { namespace openspace { struct ImageData { - static constexpr int NO_ID = -1; std::string name; std::string thumbnailUrl; std::string imageUrl; @@ -66,7 +65,6 @@ namespace openspace { bool hasCelestCoords{ false }; bool has3dCoords{ false }; glm::dvec3 position3d; - int id{ NO_ID }; }; struct ImageCollection { diff --git a/modules/skybrowser/skybrowsermodule.cpp b/modules/skybrowser/skybrowsermodule.cpp index 082bf6455b..832c492188 100644 --- a/modules/skybrowser/skybrowsermodule.cpp +++ b/modules/skybrowser/skybrowsermodule.cpp @@ -37,7 +37,7 @@ #include #include #include -#include + #include #include "skybrowsermodule_lua.inl" #include @@ -178,14 +178,6 @@ namespace openspace { "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" }, - { - "remove3dSkyBrowser", - &skybrowser::luascriptfunctions::remove3dSkyBrowser, - {}, - "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" - }, { "setOpacityOfImageLayer", &skybrowser::luascriptfunctions::setOpacityOfImageLayer, @@ -203,8 +195,8 @@ namespace openspace { "input. An input string should be the name of the system host star" }, { - "initializeBrowserAndTarget", - &skybrowser::luascriptfunctions::initializeBrowserAndTarget, + "initializeBrowser", + &skybrowser::luascriptfunctions::initializeBrowser, {}, "string or list of strings", "Add one or multiple exoplanet systems to the scene, as specified by the " @@ -225,6 +217,14 @@ 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" + }, + { + "set3dSelectedImagesAs2dSelection", + &skybrowser::luascriptfunctions::set3dSelectedImagesAs2dSelection, + {}, + "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" }, }; @@ -239,6 +239,7 @@ SkyBrowserModule::SkyBrowserModule() , currentlyDraggingObject(false) , resizeVector(0.f, 0.f) , changeViewWithinBrowser(false) + , _browser3d(nullptr) { global::callback::mousePosition->emplace_back( [&](double x, double y) { @@ -415,7 +416,7 @@ SkyBrowserModule::SkyBrowserModule() double solarSystemRadius = 30.0 * distanceconstants::AstronomicalUnit; double cameraSSBDistance = glm::length( global::navigationHandler->camera()->positionVec3()); - bool _cameraInSolarSystem = cameraSSBDistance < solarSystemRadius; + _cameraInSolarSystem = cameraSSBDistance < solarSystemRadius; double fadingTime = 2.0; double deltaTime = global::windowDelegate->deltaTime(); @@ -428,11 +429,19 @@ SkyBrowserModule::SkyBrowserModule() if (fadingIsFinished) { browser->property("Enabled")->set(false); + // Select the 3D browser when moving out of the solar system + if (_browser3d != nullptr) { + selectedBrowser = _browser3d->renderable()->identifier(); + } } } // If within solar system and browser is not visible else if (_cameraInSolarSystem && !browser->isEnabled()) { browser->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 within solar system and browser is visible if (_cameraInSolarSystem && browser->isEnabled()) { @@ -540,22 +549,22 @@ void SkyBrowserModule::createTargetBrowserPair() { ); openspace::global::scriptEngine->queueScript( - "openspace.skybrowser.addToSkyBrowserModule(" + idTarget + ");", + "openspace.skybrowser.addToSkyBrowserModule('" + idTarget + "');", scripting::ScriptEngine::RemoteScripting::Yes ); openspace::global::scriptEngine->queueScript( - "openspace.skybrowser.addToSkyBrowserModule(" + idBrowser + ");", + "openspace.skybrowser.addToSkyBrowserModule('" + idBrowser + "');", scripting::ScriptEngine::RemoteScripting::Yes ); openspace::global::scriptEngine->queueScript( - "openspace.skybrowser.connectBrowserTarget(" + idBrowser + ");", + "openspace.skybrowser.connectBrowserTarget('" + idBrowser + "');", scripting::ScriptEngine::RemoteScripting::Yes ); openspace::global::scriptEngine->queueScript( - "openspace.skybrowser.connectBrowserTarget(" + idTarget + ");", + "openspace.skybrowser.connectBrowserTarget('" + idTarget + "');", scripting::ScriptEngine::RemoteScripting::Yes ); } @@ -600,82 +609,69 @@ void SkyBrowserModule::removeTargetBrowserPair(std::string& browserId) { } -void SkyBrowserModule::create3dBrowser(ImageData& image) { - std::string id = "SkyBrowser3d" + std::to_string(browsers3d.size()+1); - glm::dvec3 position = image.position3d * distanceconstants::Parsec; - std::string translation = ghoul::to_string(position); - std::string guiPath = "/SkyBrowser"; +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::galacticCartesianToJ2000Cartesian(position); + double adjacent = glm::length(j2000); + double opposite = 2 * adjacent * glm::tan(glm::radians(image.fov * 0.5)); - // 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::galacticCartesianToJ2000Cartesian(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; - // 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); + 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 + ); - glm::dmat3 originOrientedRotation = glm::dmat3(1.0); - originOrientedRotation[0] = newRight; - originOrientedRotation[1] = newUp; - originOrientedRotation[2] = normal; - - - const std::string browser = "{" - "Identifier = '" + id + "'," - "Parent = 'SolarSystemBarycenter'," - "Renderable = {" - "Type = 'RenderableSkyBrowser'," - "Size = " + std::to_string(opposite) + "," - "Origin = 'Center'," - "Billboard = false," - "Url = 'http://localhost:8000'" - "}," - "Transform = {" - "Translation = {" - "Type = 'StaticTranslation'," - "Position = " + translation + "" - "}," - "Rotation = {" - "Type = 'StaticRotation'," - "Rotation = " + ghoul::to_string(originOrientedRotation) + "" - "}" - "}," - "GUI = {" - "Name = '" + image.name + "'," - "Path = '" + guiPath + "'" - "}" - "}"; - LINFO(browser); - openspace::global::scriptEngine->queueScript( - "openspace.addSceneGraphNode(" + browser + ");", - scripting::ScriptEngine::RemoteScripting::Yes - ); + // Target camera on the 3D sky browser + openspace::global::scriptEngine->queueScript( + "openspace.setPropertyValueSingle(\"NavigationHandler.OrbitalNavigator.RetargetAnchor\", nil)", + scripting::ScriptEngine::RemoteScripting::Yes + ); + openspace::global::scriptEngine->queueScript( + "openspace.setPropertyValueSingle(\"NavigationHandler.OrbitalNavigator.Anchor\", '" + id + "')", + scripting::ScriptEngine::RemoteScripting::Yes + ); + openspace::global::scriptEngine->queueScript( + "openspace.setPropertyValueSingle(\"NavigationHandler.OrbitalNavigator.Aim\", '')", + scripting::ScriptEngine::RemoteScripting::Yes + ); + } } -void SkyBrowserModule::add3dBrowser(RenderableSkyBrowser* node) { - browsers3d.push_back(node); -} - -void SkyBrowserModule::remove3dBrowser(std::string& id) { - // Remove pointer to the renderable from module vector - browsers3d.erase(std::remove_if(std::begin(browsers3d), std::end(browsers3d), - [&](RenderableSkyBrowser* browser) { - return browser->identifier() == id; - })); - - openspace::global::scriptEngine->queueScript( - "openspace.removeSceneGraphNode(" + id + ");", - scripting::ScriptEngine::RemoteScripting::Yes - ); +void SkyBrowserModule::add3dBrowser(SceneGraphNode* node) { + _browser3d = node; } ScreenSpaceSkyBrowser* SkyBrowserModule::to_browser(ScreenSpaceRenderable* ptr) { @@ -697,6 +693,10 @@ std::vector& SkyBrowserModule::getBrowsersAndTargets() { return renderables; } +SceneGraphNode* SkyBrowserModule::get3dBrowser() { + return _browser3d; +} + void SkyBrowserModule::startRotation(glm::dvec2 coordsEnd) { // Save coordinates to rotate to in galactic world coordinates diff --git a/modules/skybrowser/skybrowsermodule.h b/modules/skybrowser/skybrowsermodule.h index 8d6f8261c6..aea1eb69d5 100644 --- a/modules/skybrowser/skybrowsermodule.h +++ b/modules/skybrowser/skybrowsermodule.h @@ -61,6 +61,7 @@ public: WWTDataHandler* getWWTDataHandler(); std::map& getSkyBrowsers(); std::vector& getBrowsersAndTargets(); + SceneGraphNode* get3dBrowser(); void startRotation(glm::dvec2 coordsEnd); void rotateCamera(double deltaTime); bool fadeBrowserAndTarget(bool makeTransparent, double fadeTime, double deltaTime); @@ -69,12 +70,11 @@ public: bool browserIdExists(std::string id); std::string selectedBrowserId(); int loadImages(const std::string& root, const std::string& directory); - void add3dBrowser(RenderableSkyBrowser* node); - void remove3dBrowser(std::string& id); + void add3dBrowser(SceneGraphNode* node); bool cameraInSolarSystem(); void createTargetBrowserPair(); void removeTargetBrowserPair(std::string& browserId); - void create3dBrowser(ImageData& image); + void place3dBrowser(ImageData& image); scripting::LuaLibrary luaLibrary() const override; //std::vector documentations() const override; @@ -91,8 +91,8 @@ protected: std::vector renderables; // Only the browsers std::map browsers; - // 3D browsers - std::vector browsers3d; + // 3D browser + SceneGraphNode* _browser3d; // Pointer to what mouse is currently on ScreenSpaceRenderable* _mouseOnObject; // Dragging diff --git a/modules/skybrowser/skybrowsermodule_lua.inl b/modules/skybrowser/skybrowsermodule_lua.inl index 64874570e6..7fa46e68a4 100644 --- a/modules/skybrowser/skybrowsermodule_lua.inl +++ b/modules/skybrowser/skybrowsermodule_lua.inl @@ -39,15 +39,17 @@ namespace openspace::skybrowser::luascriptfunctions { ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::selectImage"); const int i = ghoul::lua::value(L, 1); SkyBrowserModule* module = global::moduleEngine->module(); - ScreenSpaceSkyBrowser* selectedBrowser = module->getSkyBrowsers()[module->selectedBrowserId()]; - if (selectedBrowser) { - ImageData& resultImage = module->getWWTDataHandler()->getLoadedImages()[i]; + // TO DO: Make this prettier with 3D browsers... + ScreenSpaceSkyBrowser* selectedBrowser = nullptr; + if (module->browserIdExists(module->selectedBrowserId())) { + selectedBrowser = module->getSkyBrowsers()[module->selectedBrowserId()]; + } + ImageData& resultImage = module->getWWTDataHandler()->getLoadedImages()[i]; - // Load image, if the image has not been loaded yet - if (resultImage.id == ImageData::NO_ID) { - LINFO("Loading image " + resultImage.name); - selectedBrowser->addSelectedImage(resultImage, i); - } + if (selectedBrowser) { + // Load image into browser + LINFO("Loading image " + resultImage.name); + selectedBrowser->addSelectedImage(resultImage, i); ScreenSpaceSkyTarget* selectedTarget = selectedBrowser->getSkyTarget(); // If the image has coordinates, move the target @@ -59,7 +61,8 @@ namespace openspace::skybrowser::luascriptfunctions { glm::dvec3 imgCoordsOnScreen = J2000SphericalToScreenSpace(resultImage.celestCoords); glm::vec2 windowRatio = global::windowDelegate->currentWindowSize(); float r = windowRatio.x / windowRatio.y; - bool coordIsWithinView = (abs(imgCoordsOnScreen.x) < r && abs(imgCoordsOnScreen.y) < 1.f && imgCoordsOnScreen.z < 0); + bool coordIsWithinView = (abs(imgCoordsOnScreen.x) < r && + abs(imgCoordsOnScreen.y) < 1.f && imgCoordsOnScreen.z < 0); bool coordIsBehindCamera = imgCoordsOnScreen.z > 0; // If the coordinate is not in view, rotate camera if (!coordIsWithinView || coordIsBehindCamera) { @@ -68,7 +71,17 @@ namespace openspace::skybrowser::luascriptfunctions { } } else { - LINFO("No browser selected!"); + SceneGraphNode* node = module->get3dBrowser(); + if (node) { + RenderableSkyBrowser* browser3d = dynamic_cast( + node->renderable()); + if (browser3d) { + browser3d->displayImage(resultImage, i); + } + else { + LINFO("No browser selected!"); + } + } } return 0; @@ -134,17 +147,30 @@ namespace openspace::skybrowser::luascriptfunctions { const std::string id = ghoul::lua::value(L, 1); LINFO("Connection established to WorldWide Telescope application in " + id); LINFO("Loading image collections to " + id); - SkyBrowserModule* module = global::moduleEngine->module(); // Load the collections here because here we know that the browser can execute javascript - std::string root = "https://raw.githubusercontent.com/WorldWideTelescope/wwt-web-client/master/assets/webclient-explore-root.wtml"; - if (module->browserIdExists(id)) { - ScreenSpaceSkyBrowser* browser = module->getSkyBrowsers()[id]; - if (!browser->hasLoadedCollections()) { - browser->sendMessageToWWT(wwtmessage::loadCollection(root)); - browser->setHasLoadedCollections(true); - } - } + std::string root = "https://raw.githubusercontent.com/WorldWideTelescope/wwt-web-client/master/assets/webclient-explore-root.wtml"; + + ScreenSpaceSkyBrowser* browser = dynamic_cast( + global::renderEngine->screenSpaceRenderable(id)); + if (browser && !browser->hasLoadedCollections()) { + browser->sendMessageToWWT(wwtmessage::loadCollection(root)); + browser->setHasLoadedCollections(true); + } + else { + SceneGraphNode* node = global::renderEngine->scene()->sceneGraphNode(id); + if (node) { + RenderableSkyBrowser* browser3d = dynamic_cast( + node->renderable()); + if (browser3d) { + // Load Image collections + browser3d->stopConnectingToWwt(); + LINFO("Load images to " + browser3d->identifier()); + browser3d->sendMessageToWWT(wwtmessage::loadCollection(root)); + LINFO("Image collection loaded in " + browser3d->identifier()); + } + } + } return 0; } @@ -159,47 +185,61 @@ namespace openspace::skybrowser::luascriptfunctions { for (std::pair pair : browsers) { pair.second->setIdInBrowser(); } - + SceneGraphNode* node = module->get3dBrowser(); + if(node) { + std::string id = node->identifier(); + RenderableSkyBrowser* browsers3d = dynamic_cast( + node->renderable()); + browsers3d->setIdInBrowser(id); + } return 0; } int connectBrowserTarget(lua_State* L) { - // In order to connect, the target and browsers must have been loaded into the - // module. This is to ensure that the renderables have been found - ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::connectBrowserTarget"); const std::string id = ghoul::lua::value(L, 1); - // Find the screenspace renderable that has the id - SkyBrowserModule* module = global::moduleEngine->module(); - std::vector renderables = module->getBrowsersAndTargets(); - auto found = std::find_if(std::begin(renderables), std::end(renderables), - [&](ScreenSpaceRenderable* renderable) { - return renderable && id == renderable->identifier(); - }); - if (dynamic_cast(*found)) { - ScreenSpaceSkyBrowser* browser = dynamic_cast(*found); + // Find the ScreenSpaceRenderable that has the id + ScreenSpaceRenderable* found = global::renderEngine->screenSpaceRenderable(id); + + // Connect it to its corresponding target / browser + if (dynamic_cast(found)) { + ScreenSpaceSkyBrowser* browser = dynamic_cast(found); browser->setConnectedTarget(); } - else if (dynamic_cast(*found)) { - ScreenSpaceSkyTarget* target = dynamic_cast(*found); + else if (dynamic_cast(found)) { + ScreenSpaceSkyTarget* target = dynamic_cast(found); target->setConnectedBrowser(); } return 0; } - int initializeBrowserAndTarget(lua_State* L) { + int initializeBrowser(lua_State* L) { // Initialize browser with ID and its corresponding target - ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::initializeBrowserAndTarget"); + ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::initializeBrowser"); const std::string id = ghoul::lua::value(L, 1); - SkyBrowserModule* module = global::moduleEngine->module(); - if (module->browserIdExists(id)) { - module->getSkyBrowsers()[id]->initializeBrowser(); - ScreenSpaceSkyTarget* target = module->getSkyBrowsers()[id]->getSkyTarget(); + ScreenSpaceSkyBrowser* browser = dynamic_cast( + global::renderEngine->screenSpaceRenderable(id)); + LINFO("Initializing sky browsers"); + if (browser) { + browser->initializeBrowser(); + ScreenSpaceSkyTarget* target = browser->getSkyTarget(); if (target) { target->initializeWithBrowser(); } } + else { + SceneGraphNode* node = global::renderEngine->scene()->sceneGraphNode(id); + if (node) { + RenderableSkyBrowser* browser3d = dynamic_cast( + node->renderable()); + if (browser3d && id == node->identifier()) { + // Initialize + LINFO("Initializing 3D sky browsers"); + browser3d->connectToWwt(); + } + } + } return 0; } @@ -207,10 +247,22 @@ namespace openspace::skybrowser::luascriptfunctions { int addToSkyBrowserModule(lua_State* L) { ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::addToSkyBrowserModule"); 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); - SkyBrowserModule* module = global::moduleEngine->module(); - module->addRenderable(object); + if (object) { + // Add to module + module->addRenderable(object); + } + else { + SceneGraphNode* node = global::renderEngine->scene()->sceneGraphNode(id); + if (node) { + // Add to module + module->add3dBrowser(node); + } + } + return 0; } @@ -295,7 +347,6 @@ namespace openspace::skybrowser::luascriptfunctions { std::vector viewDirCelestVec = { cartesianJ2000.x, cartesianJ2000.y, cartesianJ2000.z }; - // Calculate the smallest FOV of vertical and horizontal float HFOV = global::windowDelegate->getHorizFieldOfView(); glm::vec2 windowRatio = global::windowDelegate->currentWindowSize(); @@ -318,50 +369,93 @@ namespace openspace::skybrowser::luascriptfunctions { lua_settable(L, -3); // Pass data for all the browsers and the corresponding targets - std::map browsers = module->getSkyBrowsers(); + if (module->cameraInSolarSystem()) { + std::map browsers = module->getSkyBrowsers(); - for (std::pair pair : browsers) { - ScreenSpaceSkyBrowser* browser = pair.second; - std::string id = pair.first; + for (std::pair pair : browsers) { + ScreenSpaceSkyBrowser* browser = pair.second; + std::string id = pair.first; + // Convert deque to vector so ghoul can read it + std::vector selectedImagesVector; + std::deque selectedImages = browser->selectedImages(); + std::for_each(selectedImages.begin(), selectedImages.end(), [&](int index) { + selectedImagesVector.push_back(index); + }); + // Only add browsers that have an initialized target + ScreenSpaceSkyTarget* target = browser->getSkyTarget(); + if (target) { + glm::dvec2 celestialSpherical = target->getTargetDirectionCelestial(); + glm::dvec3 celestialCart = skybrowser::sphericalToCartesian(celestialSpherical); + std::vector celestialCartVec = { celestialCart.x, celestialCart.y, celestialCart.z }; + // Convert color to vector so ghoul can read it + glm::ivec3 color = browser->_borderColor.value(); + 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->fieldOfView()); + 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); + + // Set table for the current target + lua_settable(L, -3); + } + } + } + else { + SceneGraphNode* node = module->get3dBrowser(); + RenderableSkyBrowser* browser3d = dynamic_cast( + node->renderable()); // Convert deque to vector so ghoul can read it std::vector selectedImagesVector; - std::deque selectedImages = browser->selectedImages(); + std::deque selectedImages = browser3d->selectedImages(); std::for_each(selectedImages.begin(), selectedImages.end(), [&](int index) { selectedImagesVector.push_back(index); }); - // Only add browsers that have an initialized target - ScreenSpaceSkyTarget* target = browser->getSkyTarget(); - if (target) { - glm::dvec2 celestialSpherical = target->getTargetDirectionCelestial(); - glm::dvec3 celestialCart = skybrowser::sphericalToCartesian(celestialSpherical); - std::vector celestialCartVec = { celestialCart.x, celestialCart.y, celestialCart.z }; - // Convert color to vector so ghoul can read it - glm::ivec3 color = browser->_borderColor.value(); - std::vector colorVec = { color.r, color.g, color.b }; + glm::dvec3 worldPosition = node->position(); + glm::dvec3 celestialCart = skybrowser::galacticCartesianToJ2000Cartesian(worldPosition); + 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.value(); + std::vector colorVec = { 200, 200, 200 }; - 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->fieldOfView()); - 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); - - // Set table for the current target - lua_settable(L, -3); - } + ghoul::lua::push(L, browser3d->identifier()); + lua_newtable(L); + // Push ("Key", value) + ghoul::lua::push(L, "id", browser3d->identifier()); + lua_settable(L, -3); + ghoul::lua::push(L, "name", node->guiName()); + lua_settable(L, -3); + ghoul::lua::push(L, "FOV", browser3d->fieldOfView()); + 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); + + // Set table for the current target + lua_settable(L, -3); } @@ -371,27 +465,68 @@ 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(); - if (module->browserIdExists(id)) { + 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) { module->startRotation(target->getTargetDirectionCelestial()); } } + else if (!module->cameraInSolarSystem() && id3dBrowser == id) { + std::string cameraAim = "NavigationHandler.OrbitalNavigator.Aim"; + openspace::global::scriptEngine->queueScript( + "openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.RetargetAnchor', Nil)" + "openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Anchor', '" + idNode3dBrowser + "')" + "openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Aim', '')", + scripting::ScriptEngine::RemoteScripting::Yes + ); + } return 0; } + + int set3dSelectedImagesAs2dSelection(lua_State* L) { + ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::set3dSelectedImagesAs2dSelection"); + const std::string id = ghoul::lua::value(L, 1); + SkyBrowserModule* module = global::moduleEngine->module(); + + if (module->browserIdExists(id) && module->get3dBrowser() != nullptr) { + ScreenSpaceSkyBrowser* browser = module->getSkyBrowsers()[id]; + RenderableSkyBrowser* browser3d = dynamic_cast( + module->get3dBrowser()->renderable()); + // Empty 3D browser selection + browser3d->selectedImages().clear(); + // Copy 2D selection of images to 3D browser + std::deque images = browser->selectedImages(); + std::for_each(std::begin(images), std::end(images), [&](int index) { + ImageData& image = module->getWWTDataHandler()->getLoadedImages()[index]; + browser3d->displayImage(image, index); + }); + } + + return 0; + } + int setOpacityOfImageLayer(lua_State* L) { ghoul::lua::checkArgumentsAndThrow(L, 3, "lua::setOpacityOfImageLayer"); const std::string browserId = ghoul::lua::value(L, 1); const int i = ghoul::lua::value(L, 2); double opacity = ghoul::lua::value(L, 3); SkyBrowserModule* module = global::moduleEngine->module(); + ImageData& image = module->getWWTDataHandler()->getLoadedImages()[i]; + ghoul::Dictionary message = wwtmessage::setLayerOpacity(std::to_string(i), opacity); - if (module->browserIdExists(browserId)) { - ImageData& image = module->getWWTDataHandler()->getLoadedImages()[i]; - ghoul::Dictionary message = wwtmessage::setLayerOpacity(image, opacity); + if (module->browserIdExists(browserId)) { module->getSkyBrowsers()[browserId]->sendMessageToWWT(message); } + else if (module->get3dBrowser() != nullptr) { + RenderableSkyBrowser* browser3d = dynamic_cast( + module->get3dBrowser()->renderable()); + browser3d->sendMessageToWWT(message); + } + return 0; } @@ -431,7 +566,10 @@ namespace openspace::skybrowser::luascriptfunctions { // If the image has a 3D position, add it to the scene graph if (image.has3dCoords) { - module->create3dBrowser(image); + RenderableSkyBrowser* browser = dynamic_cast( + module->get3dBrowser()->renderable()); + browser->displayImage(image, i); + module->place3dBrowser(image); } else { LINFO("Image has no 3D coordinate!"); @@ -440,16 +578,6 @@ namespace openspace::skybrowser::luascriptfunctions { return 0; } - int remove3dSkyBrowser(lua_State* L) { - ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::remove3dSkyBrowser"); - // Image index to place in 3D - std::string id = ghoul::lua::value(L, 1); - SkyBrowserModule* module = global::moduleEngine->module(); - module->remove3dBrowser(id); - - return 0; - } - int removeSelectedImageInBrowser(lua_State* L) { ghoul::lua::checkArgumentsAndThrow(L, 2, "lua::removeSelectedImageInBrowser"); // Image index @@ -457,11 +585,19 @@ namespace openspace::skybrowser::luascriptfunctions { const std::string browserId = ghoul::lua::value(L, 2); // Get browser SkyBrowserModule* module = global::moduleEngine->module(); - ScreenSpaceSkyBrowser* browser = module->getSkyBrowsers()[browserId]; ImageData& resultImage = module->getWWTDataHandler()->getLoadedImages()[i]; - // Remove image - browser->removeSelectedImage(resultImage, i); + + if (module->browserIdExists(browserId)) { + ScreenSpaceSkyBrowser* browser = module->getSkyBrowsers()[browserId]; + // Remove image + browser->removeSelectedImage(resultImage, i); + } + else if (module->get3dBrowser() != nullptr) { + RenderableSkyBrowser* browser3d = dynamic_cast( + module->get3dBrowser()->renderable()); + browser3d->removeSelectedImage(resultImage, i); + } return 0; } diff --git a/modules/skybrowser/src/renderableskybrowser.cpp b/modules/skybrowser/src/renderableskybrowser.cpp index 39a51878e3..13d34f170a 100644 --- a/modules/skybrowser/src/renderableskybrowser.cpp +++ b/modules/skybrowser/src/renderableskybrowser.cpp @@ -1,8 +1,10 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -14,7 +16,7 @@ #include #include #include - +#include namespace { @@ -37,10 +39,14 @@ namespace { "Reload the web browser" }; - struct [[codegen::Dictionary(ScreenSpaceSkyBrowser)]] Parameters { + + struct [[codegen::Dictionary(RenderableSkyBrowser)]] Parameters { // [[codegen::verbatim(DimensionsInfo.description)]] std::optional browserDimensions; + + // [[codegen::verbatim(UrlInfo.description)]] + std::optional url; }; #include "renderableskybrowser_codegen.cpp" @@ -62,7 +68,12 @@ namespace openspace { , _url(UrlInfo) , _dimensions(DimensionsInfo, glm::vec2(0.f), glm::vec2(0.f), glm::vec2(3000.f)) , _reload(ReloadInfo) + , _fov(70.f) + , _connectToWwt(true) { + // Handle target dimension property + const Parameters p = codegen::bake(dictionary); + _url = p.url.value_or(_url); std::string identifier; if (dictionary.hasValue(KeyIdentifier)) { @@ -73,10 +84,6 @@ namespace openspace { } setIdentifier(identifier); - if (dictionary.hasValue(UrlInfo.identifier)) { - _url = dictionary.value(UrlInfo.identifier); - } - // Ensure the texture is a square for now // Maybe change later glm::vec2 windowDimensions = global::windowDelegate->currentSubwindowSize(); @@ -118,9 +125,6 @@ namespace openspace { _browserInstance->initialize(); _browserInstance->loadUrl(_url); _browserInstance->reshape(_dimensions.value()); - // Add pointer to module - SkyBrowserModule* module = global::moduleEngine->module(); - module->add3dBrowser(this); } void RenderableSkyBrowser::deinitializeGL() { @@ -183,4 +187,66 @@ namespace openspace { executeJavascript(script); return true; } + + void RenderableSkyBrowser::displayImage(ImageData& image, int i) { + sendMessageToWWT(wwtmessage::moveCamera(image.celestCoords, image.fov, 0.0)); + _fov = image.fov; + // Add to selected images if there are no duplicates + auto it = std::find(std::begin(_selectedImages), std::end(_selectedImages), i); + if (it == std::end(_selectedImages)) { + // Push newly selected image to front + _selectedImages.push_front(i); + // Create image layer and center WWT app on the image + sendMessageToWWT(wwtmessage::createImageLayer(image.imageUrl, std::to_string(i))); + LINFO("Image has been loaded to " + identifier()); + } + } + + void RenderableSkyBrowser::removeSelectedImage(ImageData& image, int i) { + // Remove from selected list + auto it = std::find(std::begin(_selectedImages), std::end(_selectedImages), i); + if (it != std::end(_selectedImages)) { + _selectedImages.erase(it); + sendMessageToWWT(wwtmessage::removeImageLayer(std::to_string(i))); + } + } + + void RenderableSkyBrowser::setIdInBrowser(std::string id) { + // Send ID to it's browser + executeJavascript("setId('" + id + "')"); + } + + float RenderableSkyBrowser::fieldOfView() const { + return _fov; + } + + void RenderableSkyBrowser::connectToWwt() { + + // Start a thread to enable user interaction while sending the calls to WWT + _threadWwtMessages = std::thread([&] { + while (_connectToWwt) { + + glm::dvec2 aim { 0.0 }; + // Send a message just to establish contact + ghoul::Dictionary message = wwtmessage::moveCamera(aim, _fov, 0.0); + sendMessageToWWT(message); + + // Sleep so we don't bombard WWT with too many messages + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + }); + } + + void RenderableSkyBrowser::stopConnectingToWwt() { + _connectToWwt = false; + + if (_threadWwtMessages.joinable()) { + _threadWwtMessages.join(); + } + } + + std::deque& RenderableSkyBrowser::selectedImages() { + return _selectedImages; + } + } // namespace diff --git a/modules/skybrowser/src/screenspaceskybrowser.cpp b/modules/skybrowser/src/screenspaceskybrowser.cpp index 4a20c26d6a..3c0b8c03cd 100644 --- a/modules/skybrowser/src/screenspaceskybrowser.cpp +++ b/modules/skybrowser/src/screenspaceskybrowser.cpp @@ -89,6 +89,7 @@ namespace openspace { while (glm::length(_borderColor.value()) < 222) { _borderColor = glm::ivec3(rand() % 256, rand() % 256, rand() % 256); } + // Handle target dimension property const Parameters p = codegen::bake(dictionary); _browserDimensions = p.browserDimensions.value_or(_browserDimensions); @@ -242,7 +243,11 @@ namespace openspace { while (_camIsSyncedWWT) { if (_skyTarget) { glm::dvec2 aim = _skyTarget->getTargetDirectionCelestial(); - ghoul::Dictionary message = wwtmessage::moveCamera(aim, _vfieldOfView); + // Calculate roll between up vector of camera and J2000 equatorial north + glm::dvec3 upVector = global::navigationHandler->camera()->lookUpVectorWorldSpace(); + glm::dvec3 viewVector = global::navigationHandler->camera()->viewDirectionWorldSpace(); + double roll = skybrowser::calculateRoll(upVector, viewVector); + ghoul::Dictionary message = wwtmessage::moveCamera(aim, _vfieldOfView, roll); sendMessageToWWT(message); } @@ -330,23 +335,23 @@ namespace openspace { } void ScreenSpaceSkyBrowser::addSelectedImage(ImageData& image, int i) { - sendMessageToWWT(wwtmessage::createImageLayer(image, _imageId)); - sendMessageToWWT(wwtmessage::setLayerOpacity(image, 1.0)); - _imageId++; // Ensure there are no duplicates auto it = std::find(std::begin(_selectedImages), std::end(_selectedImages), i); if (it == std::end(_selectedImages)) { // Push newly selected image to front _selectedImages.push_front(i); + // Index of image is used as layer ID as it is unique in the image data set + sendMessageToWWT(wwtmessage::createImageLayer(image.imageUrl, std::to_string(i))); + sendMessageToWWT(wwtmessage::setLayerOpacity(std::to_string(i), 1.0)); } } void ScreenSpaceSkyBrowser::removeSelectedImage(ImageData& image, int i) { - sendMessageToWWT(wwtmessage::removeImageLayer(image)); // Remove from selected list auto it = std::find(std::begin(_selectedImages), std::end(_selectedImages), i); if (it != std::end(_selectedImages)) { _selectedImages.erase(it); + sendMessageToWWT(wwtmessage::removeImageLayer(std::to_string(i))); } } } diff --git a/modules/skybrowser/src/utility.cpp b/modules/skybrowser/src/utility.cpp index fa3fd27a34..41cee753d4 100644 --- a/modules/skybrowser/src/utility.cpp +++ b/modules/skybrowser/src/utility.cpp @@ -90,25 +90,26 @@ namespace openspace::skybrowser { // Transform galactic coord to screen space return galacticToScreenSpace(imageCoordsGalacticCartesian); } -} -// WWT messages -namespace openspace::wwtmessage { - // WWT messages - ghoul::Dictionary moveCamera(const glm::dvec2 celestCoords, const double fov, const bool moveInstantly) { - using namespace std::string_literals; - ghoul::Dictionary msg; - - // Calculate roll between up vector of camera and J2000 equatorial north - glm::dvec3 upVector = global::navigationHandler->camera()->lookUpVectorWorldSpace(); - glm::dvec3 viewVector = global::navigationHandler->camera()->viewDirectionWorldSpace(); - glm::dvec3 camUpJ2000 = skybrowser::galacticCartesianToJ2000Cartesian(upVector); - glm::dvec3 camForwardJ2000 = skybrowser::galacticCartesianToJ2000Cartesian(viewVector); + double calculateRoll(glm::dvec3 upWorld, glm::dvec3 forwardWorld) { + glm::dvec3 camUpJ2000 = skybrowser::galacticCartesianToJ2000Cartesian(upWorld); + glm::dvec3 camForwardJ2000 = skybrowser::galacticCartesianToJ2000Cartesian(forwardWorld); glm::dvec3 crossUpNorth = glm::cross(camUpJ2000, skybrowser::NORTH_POLE); double dotNorthUp = glm::dot(skybrowser::NORTH_POLE, camUpJ2000); double dotCrossUpNorthForward = glm::dot(crossUpNorth, camForwardJ2000); double roll = glm::degrees(atan2(dotCrossUpNorthForward, dotNorthUp)); + return roll; + } +} + +// WWT messages +namespace openspace::wwtmessage { + // WWT messages + ghoul::Dictionary moveCamera(const glm::dvec2 celestCoords, const double fov, + const double roll, const bool moveInstantly) { + using namespace std::string_literals; + ghoul::Dictionary msg; // Create message msg.setValue("event", "center_on_coordinates"s); @@ -140,34 +141,29 @@ namespace openspace::wwtmessage { return msg; } - ghoul::Dictionary createImageLayer(ImageData& image, int id) { - std::string idString = std::to_string(id); - image.id = id; - + ghoul::Dictionary createImageLayer(const std::string& imageUrl, const std::string& id) { using namespace std::string_literals; ghoul::Dictionary msg; msg.setValue("event", "image_layer_create"s); - msg.setValue("id", idString); - msg.setValue("url", image.imageUrl); + msg.setValue("id", id); + msg.setValue("url", imageUrl); return msg; } - ghoul::Dictionary removeImageLayer(ImageData& image) { + ghoul::Dictionary removeImageLayer(const std::string& imageId) { using namespace std::string_literals; ghoul::Dictionary msg; msg.setValue("event", "image_layer_remove"s); - msg.setValue("id", std::to_string(image.id)); - image.id = ImageData::NO_ID; + msg.setValue("id", imageId); return msg; } - ghoul::Dictionary setLayerOpacity(const ImageData& image, double opacity) { - std::string idString = std::to_string(image.id); + ghoul::Dictionary setLayerOpacity(const std::string& imageId, double opacity) { using namespace std::string_literals; ghoul::Dictionary msg; msg.setValue("event", "image_layer_set"s); - msg.setValue("id", idString); + msg.setValue("id", imageId); msg.setValue("setting", "opacity"s); msg.setValue("value", opacity);