/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2022 * * * * 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 #include #include #include #include #include #include #include namespace { enum class Command { Connect = 0, Disconnect, InputState, UpdateView, Autopilot, Friction, Lua }; using AxisType = openspace::interaction::WebsocketCameraStates::AxisType; constexpr const char* _loggerCat = "FlightControllerTopic"; constexpr const char* TypeKey = "type"; constexpr const char* ValuesKey = "values"; // Disconnect JSON keys constexpr const char* SuccessKey = "success"; // Connect JSON keys constexpr const char* FocusNodesKey = "focusNodes"; constexpr const char* AllNodesKey = "allNodes"; constexpr const char* InterestingTimesKey = "interestingTimes"; // Change focus JSON keys constexpr const char* FocusKey = "focus"; constexpr const char* AnchorKey = "anchor"; constexpr const char* AimKey = "aim"; constexpr const char* ResetVelocitiesKey = "resetVelocities"; constexpr const char* RetargetAnchorKey = "retargetAnchor"; constexpr const char* RetargetAimKey = "retargetAim"; constexpr const char* SceneNodeName = "identifier"; constexpr const char* SceneNodeEnabled = "enabled"; constexpr const char* RenderableKey = "renderable"; constexpr const char* RenderableEnabled = "Enabled"; // Autopilot JSON keys constexpr const char* AutopilotEngagedKey = "engaged"; constexpr const char* AutopilotInputKey = "autopilotInput"; // Friction JSON Keys constexpr const char* FrictionRotationKey = "rotation"; constexpr const char* FrictionZoomKey = "zoom"; constexpr const char* FrictionRollKey = "roll"; constexpr const char* RotationalFriction = "Friction.RotationalFriction"; constexpr const char* ZoomFriction = "Friction.ZoomFriction"; constexpr const char* RollFriction = "Friction.RollFriction"; // Friction Lua Keys constexpr const char* LuaScript = "script"; constexpr const char* OrbitX = "orbitX"; constexpr const char* OrbitY = "orbitY"; constexpr const char* ZoomIn = "zoomIn"; constexpr const char* ZoomOut = "zoomOut"; constexpr const char* LocalRollX = "localRollX"; constexpr const char* LocalRollY = "localRollY"; constexpr const char* GlobalRollX = "globalRollX"; constexpr const char* GlobalRollY = "globalRollY"; constexpr const char* PanX = "panX"; constexpr const char* PanY = "panY"; constexpr const char* Connect = "connect"; constexpr const char* Disconnect = "disconnect"; constexpr const char* InputState = "inputState"; constexpr const char* UpdateView = "updateView"; constexpr const char* Autopilot = "autopilot"; constexpr const char* Friction = "friction"; constexpr const char* Lua = "lua"; const static std::unordered_map AxisIndexMap ({ { OrbitX, AxisType::OrbitX }, { OrbitY, AxisType::OrbitY }, { ZoomIn, AxisType::ZoomIn }, { ZoomOut, AxisType::ZoomOut }, { LocalRollX, AxisType::LocalRollX }, { LocalRollY, AxisType::LocalRollY }, { GlobalRollX, AxisType::GlobalRollX }, { GlobalRollY, AxisType::GlobalRollY }, { PanX, AxisType::PanX }, { PanY, AxisType::PanY } }); const static std::unordered_map CommandMap ({ { Connect, Command::Connect }, { Disconnect, Command::Disconnect }, { InputState, Command::InputState }, { UpdateView, Command::UpdateView }, { Autopilot, Command::Autopilot }, { Friction, Command::Friction }, { Lua, Command::Lua } }); } // namespace using nlohmann::json; namespace openspace { FlightControllerTopic::FlightControllerTopic() { for (auto it = AxisIndexMap.begin(); it != AxisIndexMap.end(); ++it) { global::navigationHandler->setWebsocketAxisMapping( int(std::distance(AxisIndexMap.begin(), it)), it->second ); } // Add WebsocketInputState to global states (*global::websocketInputStates)[_topicId] = &_inputState; } FlightControllerTopic::~FlightControllerTopic() { // Reset global websocketInputStates global::websocketInputStates->erase(_topicId); *global::websocketInputStates = interaction::WebsocketInputStates(); } bool FlightControllerTopic::isDone() const { if (_isDone) { disengageAutopilot(); } return _isDone; } void FlightControllerTopic::handleJson(const nlohmann::json& json) { auto it = CommandMap.find(json[TypeKey]); if (it == CommandMap.end()) { LWARNING( fmt::format("Poorly formatted JSON command: no '{}' in payload", TypeKey) ); return; } switch (it->second) { case Command::Connect: connect(); break; case Command::Disconnect: disconnect(); break; case Command::InputState: processInputState(json); break; case Command::UpdateView: updateView(json); break; case Command::Autopilot: handleAutopilot(json[Autopilot]); break; case Command::Friction: setFriction(json[Friction]); break; case Command::Lua: processLua(json[Lua]); break; default: LWARNING(fmt::format("Unrecognized action: {}", it->first)); break; } } void FlightControllerTopic::connect() { _isDone = false; std::fill(_inputState.axes.begin(), _inputState.axes.end(), 0.f); _payload[TypeKey] = Connect; setFocusNodes(); setInterestingTimes(); _payload[Connect][FocusNodesKey] = _focusNodes; _payload[Connect][AllNodesKey] = _allNodes; _payload[Connect][InterestingTimesKey] = _interestingTimes; _connection->sendJson(wrappedPayload(_payload)); } void FlightControllerTopic::setFocusNodes() { // Get all scene nodes std::vector nodes = global::renderEngine->scene()->allSceneGraphNodes(); // Remove all nodes with no renderable nodes.erase( std::remove_if( nodes.begin(), nodes.end(), [](SceneGraphNode* node) { return node->renderable() == nullptr; } ), nodes.end() ); // Sort them alphabetically std::sort( nodes.begin(), nodes.end(), [](SceneGraphNode* lhs, SceneGraphNode* rhs) { return lhs->guiName() < rhs->guiName(); } ); // Add to interesting nodes list and all nodes list for (SceneGraphNode* n : nodes) { // Set whether it's enabled const std::vector& tags = n->tags(); const auto it = std::find(tags.begin(), tags.end(), "GUI.Interesting"); if (it != tags.end()) { _focusNodes[n->guiName()][SceneNodeName] = n->identifier(); _focusNodes[n->guiName()][SceneNodeEnabled] = n->renderable()->isEnabled(); } _allNodes[n->guiName()][SceneNodeName] = n->identifier(); _allNodes[n->guiName()][SceneNodeEnabled] = n->renderable()->isEnabled(); } } void FlightControllerTopic::setInterestingTimes() { std::vector times = global::renderEngine->scene()->interestingTimes(); std::sort( times.begin(), times.end(), [](Scene::InterestingTime lhs, Scene::InterestingTime rhs) { return lhs.name < rhs.name; } ); for (const Scene::InterestingTime& t : times) { _interestingTimes[t.name] = t.time; } } void FlightControllerTopic::updateView(const nlohmann::json& json) const { if (json.find(RenderableKey) != json.end()) { setRenderableEnabled(json); } else { changeFocus(json); } } void FlightControllerTopic::changeFocus(const nlohmann::json& json) const { if (json.find(FocusKey) == json.end()) { const std::string j = json; LWARNING( fmt::format("Could not find {} key in JSON. JSON was:\n{}", FocusKey, j) ); if (json.find(AimKey) == json.end()) { LWARNING( fmt::format("Could not find {} key in JSON. JSON was:\n{}", AimKey, j) ); if (json.find(AnchorKey) == json.end()) { LWARNING(fmt::format( "Could not find {} key in JSON. JSON was:\n{}", AnchorKey, j )); return; } } } const std::string focus = json.find(FocusKey) != json.end() ? json[FocusKey] : ""; const std::string aim = json.find(AimKey) != json.end() ? json[AimKey] : ""; const std::string anchor = json.find(AnchorKey) != json.end() ? json[AnchorKey] : ""; const bool resetVelocities = json[ResetVelocitiesKey]; const bool retargetAnchor = json[RetargetAnchorKey]; const bool retargetAim = json[RetargetAimKey]; Scene* scene = global::renderEngine->scene(); const SceneGraphNode* focusNode = scene->sceneGraphNode(focus); const SceneGraphNode* aimNode = scene->sceneGraphNode(aim); const SceneGraphNode* anchorNode = scene->sceneGraphNode(anchor); if (focusNode) { global::navigationHandler->orbitalNavigator().setFocusNode( focusNode, resetVelocities ); } else { if (aimNode) { global::navigationHandler->orbitalNavigator().setAimNode(aim); } if (anchorNode) { global::navigationHandler->orbitalNavigator().setAnchorNode(anchor); } } if (retargetAnchor) { global::navigationHandler->orbitalNavigator().startRetargetAnchor(); } if (retargetAim) { global::navigationHandler->orbitalNavigator().startRetargetAim(); } } void FlightControllerTopic::setRenderableEnabled(const nlohmann::json& json) const { if (json[RenderableKey].find(SceneNodeName) == json[RenderableKey].end()) { const std::string j = json; LWARNING( fmt::format("Could not find {} key in JSON. JSON was:\n{}", FocusKey, j) ); return; } const std::string name = json[RenderableKey][SceneNodeName]; const bool enabled = json[RenderableKey][SceneNodeEnabled]; const SceneGraphNode* node = global::renderEngine->scene()->sceneGraphNode(name); if (node && node->renderable() != nullptr) { node->renderable()->property(RenderableEnabled)->set(enabled); } } void FlightControllerTopic::disconnect() { // Reset global websocketInputStates global::websocketInputStates->erase(_topicId); *global::websocketInputStates = interaction::WebsocketInputStates(); // Update FlightController nlohmann::json j; j[TypeKey] = Disconnect; j[Disconnect][SuccessKey] = true; _connection->sendJson(wrappedPayload(j)); _isDone = true; } void FlightControllerTopic::setFriction(bool all) const { setFriction(all, all, all); } void FlightControllerTopic::setFriction(bool roll, bool rotation, bool zoom) const { const interaction::OrbitalNavigator& navigator = global::navigationHandler->orbitalNavigator(); navigator.property(RollFriction)->set(roll); navigator.property(RotationalFriction)->set(rotation); navigator.property(ZoomFriction)->set(zoom); // Update FlightController nlohmann::json j; j[TypeKey] = Friction; j[Friction][FrictionRollKey] = roll; j[Friction][FrictionRotationKey] = rotation; j[Friction][FrictionZoomKey] = zoom; _connection->sendJson(wrappedPayload(j)); } void FlightControllerTopic::setFriction(const nlohmann::json& json) const { setFriction(json[FrictionRollKey], json[FrictionRotationKey], json[FrictionZoomKey]); } void FlightControllerTopic::disengageAutopilot() const { setFriction(true); } void FlightControllerTopic::engageAutopilot(const nlohmann::json &json) { // Disable/enable friction setFriction(false); auto input = json[AutopilotInputKey][ValuesKey]; std::fill(_inputState.axes.begin(), _inputState.axes.end(), 0.f); _inputState.isConnected = true; for (auto it = input.begin(); it != input.end(); ++it) { const auto mapIt = AxisIndexMap.find(it.key()); if (mapIt == AxisIndexMap.end()) { if (it.key() != TypeKey || CommandMap.find(it.value()) == CommandMap.end()) { LWARNING(fmt::format( "No axis, button, or command named {} (value: {})", it.key(), static_cast(it.value()) )); } continue; } _inputState.axes[std::distance(AxisIndexMap.begin(), mapIt)] = float(it.value()); } } void FlightControllerTopic::handleAutopilot(const nlohmann::json &json) { const bool engaged = json[AutopilotEngagedKey]; if (engaged) { engageAutopilot(json); } else { disengageAutopilot(); } _autopilotEngaged = engaged; nlohmann::json j; j[TypeKey] = Autopilot; j[Autopilot][AutopilotEngagedKey] = _autopilotEngaged; _connection->sendJson(wrappedPayload(j)); } void FlightControllerTopic::processInputState(const nlohmann::json& json) { std::fill(_inputState.axes.begin(), _inputState.axes.end(), 0.f); _inputState.isConnected = true; // Get "inputState" object from "payload" auto input = json[InputState][ValuesKey]; for (auto it = input.begin(); it != input.end(); ++it) { const auto mapIt = AxisIndexMap.find(it.key()); if (mapIt == AxisIndexMap.end()) { if (it.key() != TypeKey || CommandMap.find(it.value()) == CommandMap.end()) { LWARNING(fmt::format( "No axis, button, or command named {} (value: {})", it.key() , static_cast(it.value()) )); } continue; } _inputState.axes[std::distance(AxisIndexMap.begin(), mapIt)] = float(it.value()); } } void FlightControllerTopic::processLua(const nlohmann::json &json) { const std::string script = json[LuaScript]; global::scriptEngine->queueScript( script, openspace::scripting::ScriptEngine::RemoteScripting::Yes ); } } // namespace openspace