/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2021 * * * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * * software and associated documentation files (the "Software"), to deal in the Software * * without restriction, including without limitation the rights to use, copy, modify, * * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to the following * * conditions: * * * * The above copyright notice and this permission notice shall be included in all copies * * or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr const char* _loggerCat = "PDatMessHand"; } // namespace namespace openspace { using namespace softwareintegration; void PointDataMessageHandler::handlePointDataMessage(const std::vector& message, std::shared_ptr connection) { size_t messageOffset = 0; std::string identifier; checkRenderable(message, messageOffset, connection, identifier); size_t nPoints; size_t dimensionality; std::vector points; try { // The following order of creating variables is the exact order they are received // in the message. If the order is not the same, the global variable // 'message offset' will be wrong nPoints = static_cast(simp::readIntValue(message, messageOffset)); dimensionality = static_cast(simp::readIntValue(message, messageOffset)); simp::readPointData(message, messageOffset, nPoints, dimensionality, points); } catch (const simp::SimpError& err) { LERROR(fmt::format("Error when reading point data message: {}", err.message)); return; } // Use the renderable identifier as the data key const auto key = storage::getStorageKey(identifier, storage::Key::DataPoints); auto module = global::moduleEngine->module(); module->storeData(key, std::move(points)); } void PointDataMessageHandler::handleFixedColorMessage(const std::vector& message, std::shared_ptr connection) { size_t messageOffset = 0; std::string identifier; checkRenderable(message, messageOffset, connection, identifier); glm::vec4 color; try { color = simp::readColor(message, messageOffset); } catch (const simp::SimpError& err) { LERROR(fmt::format("Error when reading fixed color message: {}", err.message)); return; } auto callback = [this, identifier, color] { // Get renderable auto r = getRenderable(identifier); // Get color of renderable properties::Property* colorProperty = r->property("Color"); glm::vec4 propertyColor = std::any_cast(colorProperty->get()); // Update color of renderable if (propertyColor != color) { openspace::global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.Color', {});", identifier, ghoul::to_string(color) ), scripting::ScriptEngine::RemoteScripting::Yes ); } openspace::global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.ColormapEnabled', {});", identifier, "false" ), scripting::ScriptEngine::RemoteScripting::Yes ); }; addCallback(identifier, { callback }); } void PointDataMessageHandler::handleColormapMessage(const std::vector& message, std::shared_ptr connection) { size_t messageOffset = 0; std::string identifier; checkRenderable(message, messageOffset, connection, identifier); float min; float max; size_t nColors; std::vector colorMap; try { min = simp::readFloatValue(message, messageOffset); max = simp::readFloatValue(message, messageOffset); nColors = static_cast(simp::readIntValue(message, messageOffset)); simp::readColormap(message, messageOffset, nColors, colorMap); } catch (const simp::SimpError& err) { LERROR(fmt::format("Error when reading color map message: {}", err.message)); return; } // Use the renderable identifier as the data key const auto key = storage::getStorageKey(identifier, storage::Key::Colormap); auto module = global::moduleEngine->module(); module->storeData(key, std::move(colorMap)); auto colormapLimitsCallback = [this, identifier, min, max] { // Get renderable auto r = getRenderable(identifier); properties::Property* colormapMinProperty = r->property("ColormapMin"); float colormapMin = std::any_cast(colormapMinProperty->get()); if (min != colormapMin) { openspace::global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.ColormapMin', {});", identifier, ghoul::to_string(min) ), scripting::ScriptEngine::RemoteScripting::Yes ); } properties::Property* colormapMaxProperty = r->property("ColormapMax"); float colormapMax = std::any_cast(colormapMaxProperty->get()); if (max != colormapMax) { openspace::global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.ColormapMax', {});", identifier, ghoul::to_string(max) ), scripting::ScriptEngine::RemoteScripting::Yes ); } }; addCallback(identifier, { colormapLimitsCallback }); auto enableColormapCallback = [this, identifier] { openspace::global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.ColormapEnabled', {});", identifier, "true" ), scripting::ScriptEngine::RemoteScripting::Yes ); }; // Callback std::vector dataToWaitFor{ storage::Key::Colormap, storage::Key::ColormapAttrData }; addCallback(identifier, { enableColormapCallback, dataToWaitFor }); } void PointDataMessageHandler::handleAttributeDataMessage(const std::vector& message, std::shared_ptr connection) { size_t messageOffset = 0; std::string identifier; checkRenderable(message, messageOffset, connection, identifier); std::string usedFor; size_t nValues; std::vector attributeData; try { usedFor = simp::readString(message, messageOffset); nValues = static_cast(simp::readIntValue(message, messageOffset)); attributeData.reserve(nValues); for (size_t i = 0; i < nValues; ++i) attributeData.push_back(simp::readFloatValue(message, messageOffset)); } catch (const simp::SimpError& err) { LERROR(fmt::format("Error when reading message with scalars for color map: {}", err.message)); return; } auto module = global::moduleEngine->module(); // Use the renderable identifier as the data key std::string key; storage::Key keyEnum = storage::Key::Unknown; if (storage::hasStorageKey(usedFor)) { key = storage::getStorageKey(identifier, usedFor); keyEnum = storage::getStorageKeyEnum(usedFor); } else { LERROR(fmt::format( "The received attribute data had the \"usedFor\" value {}, which is unrecognized.", usedFor )); return; } std::vector allDataToWaitFor{ keyEnum }; switch (keyEnum) { case storage::Key::ColormapAttrData : { allDataToWaitFor.push_back(storage::Key::Colormap); break; } default: break; } module->storeData(key, std::move(attributeData)); auto callback = [this, identifier, keyEnum] { switch (keyEnum) { case storage::Key::ColormapAttrData : { openspace::global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.ColormapEnabled', {});", identifier, "true" ), scripting::ScriptEngine::RemoteScripting::Yes ); break; } case storage::Key::LinearSizeAttrData : { openspace::global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.LinearSizeEnabled', {});", identifier, "true" ), scripting::ScriptEngine::RemoteScripting::Yes ); break; } default: break; } }; addCallback(identifier, { callback, allDataToWaitFor }); } void PointDataMessageHandler::handleOpacityMessage(const std::vector& message, std::shared_ptr connection) { size_t messageOffset = 0; std::string identifier; checkRenderable(message, messageOffset, connection, identifier); float opacity; try { opacity = simp::readFloatValue(message, messageOffset); } catch (const simp::SimpError& err) { LERROR(fmt::format("Error when reading opacity message: {}", err.message)); return; } auto callback = [this, identifier, opacity] { // Get renderable auto r = getRenderable(identifier); // Get opacity from renderable properties::Property* opacityProperty = r->property("Opacity"); auto propertyAny = opacityProperty->get(); float propertyOpacity = std::any_cast(propertyAny); // Update opacity of renderable if (propertyOpacity != opacity) { std::string script = fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.Opacity', {});", identifier, ghoul::to_string(opacity) ); openspace::global::scriptEngine->queueScript( script, scripting::ScriptEngine::RemoteScripting::Yes ); } }; addCallback(identifier, { callback }); } void PointDataMessageHandler::handleFixedPointSizeMessage(const std::vector& message, std::shared_ptr connection) { size_t messageOffset = 0; std::string identifier; checkRenderable(message, messageOffset, connection, identifier); float size; try { size = simp::readFloatValue(message, messageOffset); } catch (const simp::SimpError& err) { LERROR(fmt::format("Error when reading fixed point size message: {}", err.message)); return; } auto callback = [this, identifier, size] { // Get renderable auto r = getRenderable(identifier); // Get size from renderable properties::Property* sizeProperty = r->property("Size"); auto propertyAny = sizeProperty->get(); float propertySize = std::any_cast(propertyAny); // Update size of renderable if (propertySize != size) { // TODO: Add interpolation to script, but do not send back // updates to external software until the interpolation is done // Use this: "openspace.setPropertyValueSingle('Scene.{}.Renderable.Size', {}, 1);", openspace::global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.Size', {});", identifier, ghoul::to_string(size) ), scripting::ScriptEngine::RemoteScripting::Yes ); openspace::global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.LinearSizeEnabled', {});", identifier, "false" ), scripting::ScriptEngine::RemoteScripting::Yes ); } }; addCallback(identifier, { callback }); } void PointDataMessageHandler::handleLinearPointSizeMessage(const std::vector& message, std::shared_ptr connection) { size_t messageOffset = 0; std::string identifier; checkRenderable(message, messageOffset, connection, identifier); float size; float min; float max; try { size = simp::readFloatValue(message, messageOffset); min = simp::readFloatValue(message, messageOffset); max = simp::readFloatValue(message, messageOffset); } catch (const simp::SimpError& err) { LERROR(fmt::format("Error when reading linear point size message: {}", err.message)); return; } auto linearSizeCallback = [this, identifier, size, min, max] { // Get renderable auto r = getRenderable(identifier); // Get size from renderable properties::Property* sizeProperty = r->property("Size"); auto propertyAny = sizeProperty->get(); float propertySize = std::any_cast(propertyAny); // Update size of renderable if (propertySize != size) { openspace::global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.Size', {});", identifier, ghoul::to_string(size) ), scripting::ScriptEngine::RemoteScripting::Yes ); } properties::Property* linearSizeMinProperty = r->property("LinearSizeMin"); float linearSizeMin = std::any_cast(linearSizeMinProperty->get()); if (min != linearSizeMin) { openspace::global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.LinearSizeMin', {});", identifier, ghoul::to_string(min) ), scripting::ScriptEngine::RemoteScripting::Yes ); } properties::Property* linearSizeMaxProperty = r->property("LinearSizeMax"); float linearSizeMax = std::any_cast(linearSizeMaxProperty->get()); if (max != linearSizeMax) { openspace::global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.LinearSizeMax', {});", identifier, ghoul::to_string(max) ), scripting::ScriptEngine::RemoteScripting::Yes ); } }; addCallback(identifier, { linearSizeCallback }); auto enableLinearSizeCallback = [this, identifier] { openspace::global::scriptEngine->queueScript( fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.LinearSizeEnabled', {});", identifier, "true" ), scripting::ScriptEngine::RemoteScripting::Yes ); }; addCallback( identifier, { enableLinearSizeCallback, { storage::Key::LinearSizeAttrData } } ); } void PointDataMessageHandler::handleVisiblityMessage(const std::vector& message, std::shared_ptr connection) { size_t messageOffset = 0; std::string identifier; checkRenderable(message, messageOffset, connection, identifier); std::string visibilityMessage; try { visibilityMessage = simp::readString(message, messageOffset); } catch (const simp::SimpError& err) { LERROR(fmt::format("Error when reading visibility message: {}", err.message)); return; } auto callback = [this, identifier, visibilityMessage] { // Toggle visibility from renderable const std::string visability = visibilityMessage == "T" ? "true" : "false"; std::string script = fmt::format( "openspace.setPropertyValueSingle('Scene.{}.Renderable.Enabled', {});", identifier, visability ); openspace::global::scriptEngine->queueScript( script, scripting::ScriptEngine::RemoteScripting::Yes ); }; addCallback(identifier, { callback }); } void PointDataMessageHandler::handleRemoveSGNMessage(const std::vector& message,std::shared_ptr connection) { size_t messageOffset = 0; std::string identifier; try { identifier = simp::readString(message, messageOffset); } catch (const simp::SimpError& err) { LERROR(fmt::format("Error when reading message: {}", err.message)); return; } const std::string currentAnchor = global::navigationHandler->orbitalNavigator().anchorNode()->identifier(); if (currentAnchor == identifier) { // If the deleted node is the current anchor, first change focus to the Sun openspace::global::scriptEngine->queueScript( "openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Anchor', 'Sun')" "openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Aim', '')", scripting::ScriptEngine::RemoteScripting::Yes ); } openspace::global::scriptEngine->queueScript( "openspace.removeSceneGraphNode('" + identifier + "');", scripting::ScriptEngine::RemoteScripting::Yes ); connection->removeSceneGraphNode(identifier); LDEBUG(fmt::format("Scene graph node '{}' removed.", identifier)); } void PointDataMessageHandler::postSync() { std::lock_guard guard(_onceNodeExistsCallbacksMutex); // Check if the scene graph node has been created. // If so, call the corresponding callback functions to set up any subscriptions auto callbackMapIt = _onceNodeExistsCallbacks.begin(); while (callbackMapIt != _onceNodeExistsCallbacks.end()) { auto& [identifier, callbackList] = *callbackMapIt; try { const SceneGraphNode* sgn = global::renderEngine->scene()->sceneGraphNode(identifier); if (!sgn) throw std::exception{}; auto r = getRenderable(identifier); if (!r) throw std::exception{}; auto softwareIntegrationModule = global::moduleEngine->module(); auto callbacksIt = callbackList.begin(); while (callbacksIt != callbackList.end()) { auto& [callback, waitForData] = *callbacksIt; try { for (auto& waitFor : waitForData) { if ( softwareIntegrationModule->isSyncDataDirty( storage::getStorageKey(identifier, waitFor) ) ) { throw std::exception{}; } } callback(); callbacksIt = callbackList.erase(callbacksIt); } catch (std::exception&) { ++callbacksIt; } } callbackMapIt = _onceNodeExistsCallbacks.erase(callbackMapIt); _onceNodeExistsCallbacksRetries = 0; } catch(std::exception &err) { ++_onceNodeExistsCallbacksRetries; ghoul_assert(_onceNodeExistsCallbacksRetries < 10, "Too many callback retries"); LDEBUG(fmt::format("Error when trying to run callback: {}", err.what())); break; } } } const Renderable* PointDataMessageHandler::getRenderable(const std::string& identifier) { return renderable(identifier); } void PointDataMessageHandler::checkRenderable( const std::vector& message, size_t& messageOffset, std::shared_ptr connection, std::string& identifier ) { std::string guiName; try { // The following order of creating variables is the exact order they are received // in the message. If the order is not the same, the global variable // 'message offset' will be wrong identifier = simp::readString(message, messageOffset); guiName = simp::readString(message, messageOffset); } catch (const simp::SimpError& err) { LERROR(fmt::format("Error when reading identifier and guiName from message: {}", err.message)); return; } connection->addSceneGraphNode(identifier); const Renderable* r = renderable(identifier); bool hasCallbacks = false; { std::lock_guard guard(_onceNodeExistsCallbacksMutex); hasCallbacks = _onceNodeExistsCallbacks.count(identifier) > 0; } if (!r && !hasCallbacks) { LDEBUG(fmt::format("No renderable with identifier '{}' was found. Creating it.", identifier)); // Create a renderable, since it didn't exist using namespace std::string_literals; ghoul::Dictionary renderablePointsCloud; renderablePointsCloud.setValue("Type", "RenderablePointsCloud"s); renderablePointsCloud.setValue("Identifier", identifier); ghoul::Dictionary gui; gui.setValue("Name", guiName); gui.setValue("Path", "/Software Integration"s); ghoul::Dictionary node; node.setValue("Identifier", identifier); node.setValue("Renderable", renderablePointsCloud); node.setValue("GUI", gui); openspace::global::scriptEngine->queueScript( "openspace.addSceneGraphNode(" + ghoul::formatLua(node) + ")" "openspace.setPropertyValueSingle('Modules.CefWebGui.Reload', nil)", // Reload WebGUI so that SoftwareIntegration GUI appears scripting::ScriptEngine::RemoteScripting::Yes ); auto subscriptionCallback = [this, identifier, connection] { subscribeToRenderableUpdates(identifier, connection); }; addCallback(identifier, { subscriptionCallback }); } else { subscribeToRenderableUpdates(identifier, connection); } auto reanchorCallback = [identifier] { openspace::global::scriptEngine->queueScript( "openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.RetargetAnchor', nil)" "openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Anchor', '" + identifier + "')" "openspace.setPropertyValueSingle('NavigationHandler.OrbitalNavigator.Aim', '')", scripting::ScriptEngine::RemoteScripting::Yes ); }; addCallback(identifier, { reanchorCallback }); } void PointDataMessageHandler::subscribeToRenderableUpdates( const std::string& identifier, std::shared_ptr connection ) { // Get renderable auto aRenderable = getRenderable(identifier); if (!aRenderable) return; if (!connection->isConnected()) { LERROR(fmt::format( "Could not subscribe to updates for renderable '{}' due to lost connection", identifier )); return; } properties::Property* colorProperty = aRenderable->property("Color"); properties::Property* opacityProperty = aRenderable->property("Opacity"); properties::Property* sizeProperty = aRenderable->property("Size"); properties::Property* visibilityProperty = aRenderable->property("Enabled"); // Update color of renderable auto updateColor = [colorProperty, identifier, connection]() { if (!connection->isConnected()) { SoftwareConnection::PointDataMessageHandlerFriends::removePropertySubscription( connection, "Color", identifier ); return; } glm::vec4 color = std::any_cast(colorProperty->get()); const std::string message = simp::formatColorMessage(identifier, color); connection->sendMessage(message); }; if (colorProperty) { connection->addPropertySubscription("Color", identifier, updateColor); } // Update opacity of renderable auto updateOpacity = [opacityProperty, identifier, connection]() { if (!connection->isConnected()) { SoftwareConnection::PointDataMessageHandlerFriends::removePropertySubscription( connection, "Opacity", identifier ); return; } float value = std::any_cast(opacityProperty->get()); std::string hex_value = simp::floatToHex(value); const std::string message = simp::formatUpdateMessage(simp::MessageType::Opacity, identifier, hex_value); connection->sendMessage(message); }; if (opacityProperty) { connection->addPropertySubscription("Opacity", identifier, updateOpacity); } // Update size of renderable auto updateSize = [sizeProperty, identifier, connection]() { if (!connection->isConnected()) { SoftwareConnection::PointDataMessageHandlerFriends::removePropertySubscription( connection, "Size", identifier ); return; } float value = std::any_cast(sizeProperty->get()); std::string hex_value = simp::floatToHex(value); const std::string message = simp::formatUpdateMessage(simp::MessageType::FixedSize, identifier, hex_value); connection->sendMessage(message); }; if (sizeProperty) { connection->addPropertySubscription("Size", identifier, updateSize); } // Toggle visibility of renderable auto toggleVisibility = [visibilityProperty, identifier, connection]() { if (!connection->isConnected()) { SoftwareConnection::PointDataMessageHandlerFriends::removePropertySubscription( connection, "Enabled", identifier ); return; } bool isVisible = std::any_cast(visibilityProperty->get()); std::string_view visibilityFlag = isVisible ? "T" : "F"; const std::string message = simp::formatUpdateMessage(simp::MessageType::Visibility, identifier, visibilityFlag); connection->sendMessage(message); }; if (visibilityProperty) { connection->addPropertySubscription("Enabled", identifier, toggleVisibility); } } void PointDataMessageHandler::addCallback( const std::string& identifier, const Callback& newCallback ) { std::lock_guard guard(_onceNodeExistsCallbacksMutex); auto it = _onceNodeExistsCallbacks.find(identifier); if (it == _onceNodeExistsCallbacks.end()) { CallbackList newCallbackList{ newCallback }; _onceNodeExistsCallbacks.emplace(identifier, newCallbackList); } else { it->second.push_back(newCallback); } } } // namespace openspace