diff --git a/apps/OpenSpace/main.cpp b/apps/OpenSpace/main.cpp index 1be2584abe..51fa7aa05e 100644 --- a/apps/OpenSpace/main.cpp +++ b/apps/OpenSpace/main.cpp @@ -383,7 +383,10 @@ void mainInitFunc() { for (size_t i = 0; i < nWindows; ++i) { sgct::SGCTWindow* w = SgctEngine->getWindowPtr(i); - constexpr const char* screenshotNames = "OpenSpace"; + const std::string screenshotNames = nWindows > 1 ? + fmt::format("OpenSpace_{}", i) : + "OpenSpace"; + sgct_core::ScreenCapture* cpt0 = w->getScreenCapturePointer(0); sgct_core::ScreenCapture* cpt1 = w->getScreenCapturePointer(1); @@ -1014,6 +1017,7 @@ void setSgctDelegateFunctions() { sgctDelegate.takeScreenshot = [](bool applyWarping) { sgct::SGCTSettings::instance()->setCaptureFromBackBuffer(applyWarping); sgct::Engine::instance()->takeScreenshot(); + return sgct::Engine::instance()->getScreenShotNumber(); }; sgctDelegate.swapBuffer = []() { GLFWwindow* w = glfwGetCurrentContext(); diff --git a/data/assets/util/default_keybindings.asset b/data/assets/util/default_keybindings.asset index 1022144d37..cd8559d306 100644 --- a/data/assets/util/default_keybindings.asset +++ b/data/assets/util/default_keybindings.asset @@ -20,16 +20,16 @@ local Keybindings = { { Key = "PRINT_SCREEN", Name = "Take Screenshot", - Command = "openspace.setPropertyValueSingle('RenderEngine.TakeScreenshot', nil)", - Documentation = "Saves the contents of the screen to a file in the working directory.", + Command = "openspace.takeScreenshot()", + Documentation = "Saves the contents of the screen to a file in the ${SCREENSHOTS} directory.", GuiPath = "/Rendering", Local = true }, { Key = "F12", Name = "Take Screenshot", - Command = "openspace.setPropertyValueSingle('RenderEngine.TakeScreenshot', nil)", - Documentation = "Saves the contents of the screen to a file in the working directory.", + Command = "openspace.takeScreenshot()", + Documentation = "Saves the contents of the screen to a file in the ${SCREENSHOTS} directory.", GuiPath = "/Rendering", Local = true }, diff --git a/data/assets/util/screenshots_endpoint.asset b/data/assets/util/screenshots_endpoint.asset new file mode 100644 index 0000000000..712b0a5a8b --- /dev/null +++ b/data/assets/util/screenshots_endpoint.asset @@ -0,0 +1,21 @@ +asset.onInitialize(function () + -- Disable the server, add production gui endpoint, and restart server. + -- The temporary disabling avoids restarting the server on each property change. + -- TODO: Add a trigger property to the module to restart the server "manually" + -- and remove automatic restart on each property change, + -- since frequent restarting seems to be unstable on mac. + + local enabled = openspace.getPropertyValue("Modules.WebGui.ServerProcessEnabled") + openspace.setPropertyValueSingle("Modules.WebGui.ServerProcessEnabled", false) + + local directories = openspace.getPropertyValue("Modules.WebGui.Directories") + directories[#directories + 1] = "screenshots" + directories[#directories + 1] = "${SCREENSHOTS}" + openspace.setPropertyValueSingle("Modules.WebGui.Directories", directories) + openspace.setPropertyValueSingle("Modules.WebGui.ServerProcessEnabled", enabled) +end) + +asset.onDeinitialize(function () + -- TODO: Remove endpoints. As of 2019-10-29, OpenSpace sometimes + -- crashes when endpoints are removed while the application is closing. +end) \ No newline at end of file diff --git a/data/assets/util/testing_keybindings.asset b/data/assets/util/testing_keybindings.asset index 8565b3c1cf..e902f34c37 100644 --- a/data/assets/util/testing_keybindings.asset +++ b/data/assets/util/testing_keybindings.asset @@ -7,8 +7,8 @@ local Keybindings = { { Key = "F7", Name = "Take Screenshot", - Command = "openspace.setPropertyValueSingle('RenderEngine.TakeScreenshot', nil)", - Documentation = "Saves the contents of the screen to a file in the working directory.", + Command = "openspace.takeScreenshot()", + Documentation = "Saves the contents of the screen to a file in the ${SCREENSHOTS} directory.", GuiPath = "/Rendering", Local = true } diff --git a/ext/ghoul b/ext/ghoul index 38272a9b7d..e6ef5101e7 160000 --- a/ext/ghoul +++ b/ext/ghoul @@ -1 +1 @@ -Subproject commit 38272a9b7d30bc07656336d35cbaae3a24ffd4bb +Subproject commit e6ef5101e7dd5e5ba19329ed9f5e5806e257b5ed diff --git a/include/openspace/engine/globals.h b/include/openspace/engine/globals.h index 994ba901fa..2e1ed8dd77 100644 --- a/include/openspace/engine/globals.h +++ b/include/openspace/engine/globals.h @@ -53,6 +53,7 @@ namespace configuration { struct Configuration; } namespace interaction { struct JoystickInputStates; struct WebsocketInputStates; + class InteractionMonitor; class KeybindingManager; class NavigationHandler; class SessionRecording; @@ -87,6 +88,7 @@ VersionChecker& gVersionChecker(); VirtualPropertyManager& gVirtualPropertyManager(); WindowDelegate& gWindowDelegate(); configuration::Configuration& gConfiguration(); +interaction::InteractionMonitor& gInteractionMonitor(); interaction::JoystickInputStates& gJoystickInputStates(); interaction::WebsocketInputStates& gWebsocketInputStates(); interaction::KeybindingManager& gKeybindingManager(); @@ -120,6 +122,7 @@ static VersionChecker& versionChecker = detail::gVersionChecker(); static VirtualPropertyManager& virtualPropertyManager = detail::gVirtualPropertyManager(); static WindowDelegate& windowDelegate = detail::gWindowDelegate(); static configuration::Configuration& configuration = detail::gConfiguration(); +static interaction::InteractionMonitor& interactionMonitor = detail::gInteractionMonitor(); static interaction::JoystickInputStates& joystickInputStates = detail::gJoystickInputStates(); static interaction::WebsocketInputStates& websocketInputStates = diff --git a/include/openspace/engine/windowdelegate.h b/include/openspace/engine/windowdelegate.h index b3c44e181d..324c838b86 100644 --- a/include/openspace/engine/windowdelegate.h +++ b/include/openspace/engine/windowdelegate.h @@ -107,7 +107,7 @@ struct WindowDelegate { bool (*isFisheyeRendering)() = []() { return false; }; - void (*takeScreenshot)(bool applyWarping) = [](bool) { }; + unsigned int(*takeScreenshot)(bool applyWarping) = [](bool) { return 0u; }; void (*swapBuffer)() = []() {}; diff --git a/include/openspace/interaction/interactionmonitor.h b/include/openspace/interaction/interactionmonitor.h new file mode 100644 index 0000000000..59748a00c8 --- /dev/null +++ b/include/openspace/interaction/interactionmonitor.h @@ -0,0 +1,70 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2019 * + * * + * 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. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_CORE___INTERACTIONMONITOR___H__ +#define __OPENSPACE_CORE___INTERACTIONMONITOR___H__ + +#include + +#include +#include + +namespace openspace::interaction { + +/** + * The class InteractionMonitor keeps track of user interactions during an OpenSpace + * session. It keeps track of when the latest interaction was made and of when the state + * changes to idle. + */ +class InteractionMonitor : public properties::PropertyOwner { +public: + InteractionMonitor(); + + void setActivityState(bool isActive); + void setIdleTime(float time); + + /* + * Called every frame from OpenSpaceEngine and calculates the activity state depending + * on the last registered interaction. + */ + void updateActivityState(); + + /* + * Called from all places we want to mark activity from. Updates the last registered + * interaction time + */ + void markInteraction(); + +private: + double _lastInteractionTime = 0; + properties::BoolProperty _isInActiveState; + properties::FloatProperty _idleTime; // in seconds + + // @TODO (lovisa) make a list of interactions to listen for + // and only allow registering updates from those interactions +}; + +} // namespace openspace::interaction + +#endif // __OPENSPACE_CORE___INTERACTIONMONITOR___H__ diff --git a/include/openspace/interaction/navigationhandler.h b/include/openspace/interaction/navigationhandler.h index 89fc965785..dcdd4df76b 100644 --- a/include/openspace/interaction/navigationhandler.h +++ b/include/openspace/interaction/navigationhandler.h @@ -136,6 +136,7 @@ public: WebsocketCameraStates::AxisNormalize shouldNormalize = WebsocketCameraStates::AxisNormalize::No); + NavigationState navigationState() const; NavigationState navigationState(const SceneGraphNode& referenceFrame) const; void saveNavigationState(const std::string& filepath, diff --git a/include/openspace/rendering/renderengine.h b/include/openspace/rendering/renderengine.h index 8093c16c4d..943c8a0ae0 100644 --- a/include/openspace/rendering/renderengine.h +++ b/include/openspace/rendering/renderengine.h @@ -142,9 +142,14 @@ public: void setResolveData(ghoul::Dictionary resolveData); /** - * Mark that one screenshot should be taken + * Take a screenshot and store in the ${SCREENSHOTS} directory */ - void takeScreenShot(); + void takeScreenshot(); + + /** + * Get the filename of the latest screenshot + */ + unsigned int latestScreenshotNumber() const; /** * Returns the Lua library that contains all Lua functions available to affect the @@ -187,8 +192,6 @@ private: properties::BoolProperty _showVersionInfo; properties::BoolProperty _showCameraInfo; - properties::TriggerProperty _takeScreenshot; - bool _shouldTakeScreenshot = false; properties::BoolProperty _applyWarping; properties::BoolProperty _showFrameInformation; #ifdef OPENSPACE_WITH_INSTRUMENTATION @@ -226,6 +229,7 @@ private: properties::Vec3Property _masterRotation; uint64_t _frameNumber = 0; + unsigned int _latestScreenshotNumber = 0; std::vector _programs; diff --git a/modules/globebrowsing/scripts/layer_support.lua b/modules/globebrowsing/scripts/layer_support.lua index d76864112f..4ac0cf6971 100644 --- a/modules/globebrowsing/scripts/layer_support.lua +++ b/modules/globebrowsing/scripts/layer_support.lua @@ -48,6 +48,16 @@ openspace.globebrowsing.documentation = { "}" .. ")" }, + { + Name = "addGibsLayer", + Arguments = "string, string, string, string, string", + Documentation = "Adds a new layer from NASA GIBS to the Earth globe. Arguments " .. + "are: imagery layer name, imagery resolution, start date, end date, format. " .. + "For all specifications, see " .. + "https://wiki.earthdata.nasa.gov/display/GIBS/GIBS+Available+Imagery+Products" .. + "Usage:" .. + "openspace.globebrowsing.addGibsLayer('AIRS_Temperature_850hPa_Night', '2km', '2013-07-15', 'Present', 'png')" + }, { Name = "parseInfoFile", Arguments = "string", @@ -97,6 +107,14 @@ openspace.globebrowsing.documentation = { } } +openspace.globebrowsing.addGibsLayer = function(layer, resolution, format, startDate, endDate) + if endDate == 'Present' then + endDate = '' + end + local xml = openspace.globebrowsing.createTemporalGibsGdalXml(layer, startDate, endDate, '1d', resolution, format) + openspace.globebrowsing.addLayer('Earth', 'ColorLayers', { Identifier = layer, Type = "TemporalTileLayer", FilePath = xml }) +end + openspace.globebrowsing.createTemporalGibsGdalXml = function (layerName, startDate, endDate, timeResolution, resolution, format) temporalTemplate = "" .. @@ -332,5 +350,4 @@ openspace.globebrowsing.loadWMSServersFromFile = function (file_path) ) end end - end diff --git a/modules/touch/touchmodule.cpp b/modules/touch/touchmodule.cpp index 4c59fffdfb..f0e38dd2c6 100644 --- a/modules/touch/touchmodule.cpp +++ b/modules/touch/touchmodule.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -46,54 +47,59 @@ using namespace TUIO; namespace openspace { -bool TouchModule::hasNewInput() { +bool TouchModule::processNewInput() { // Get new input from listener - listOfContactPoints = ear.getInput(); - ear.clearInput(); + + _listOfContactPoints = _ear.getInput(); + _ear.clearInput(); // Set touch property to active (to void mouse input, mainly for mtdev bridges) - touch.touchActive(!listOfContactPoints.empty()); + _touch.touchActive(!_listOfContactPoints.empty()); + + if (!_listOfContactPoints.empty()) { + global::interactionMonitor.markInteraction(); + } // Erase old input id's that no longer exists - lastProcessed.erase( + _lastProcessed.erase( std::remove_if( - lastProcessed.begin(), - lastProcessed.end(), + _lastProcessed.begin(), + _lastProcessed.end(), [this](const Point& point) { return std::find_if( - listOfContactPoints.begin(), - listOfContactPoints.end(), + _listOfContactPoints.begin(), + _listOfContactPoints.end(), [&point](const TuioCursor& c) { return point.first == c.getSessionID(); } - ) == listOfContactPoints.end(); }), - lastProcessed.end()); + ) == _listOfContactPoints.end(); }), + _lastProcessed.end()); // if tap occured, we have new input - if (listOfContactPoints.empty() && lastProcessed.empty() && ear.tap()) { - TuioCursor c = ear.getTap(); - listOfContactPoints.push_back(c); - lastProcessed.emplace_back(c.getSessionID(), c.getPath().back()); - touch.tap(); + if (_listOfContactPoints.empty() && _lastProcessed.empty() && _ear.tap()) { + TuioCursor c = _ear.getTap(); + _listOfContactPoints.push_back(c); + _lastProcessed.emplace_back(c.getSessionID(), c.getPath().back()); + _touch.tap(); return true; } // Check if we need to parse touchevent to the webgui - hasNewWebInput(listOfContactPoints); + processNewWebInput(_listOfContactPoints); // Return true if we got new input - if (listOfContactPoints.size() == lastProcessed.size() && - !listOfContactPoints.empty()) + if (_listOfContactPoints.size() == _lastProcessed.size() && + !_listOfContactPoints.empty()) { bool newInput = true; // go through list and check if the last registrered time is newer than the one in // lastProcessed (last frame) std::for_each( - lastProcessed.begin(), - lastProcessed.end(), + _lastProcessed.begin(), + _lastProcessed.end(), [this, &newInput](Point& p) { std::vector::iterator cursor = std::find_if( - listOfContactPoints.begin(), - listOfContactPoints.end(), + _listOfContactPoints.begin(), + _listOfContactPoints.end(), [&p](const TuioCursor& c) { return c.getSessionID() == p.first; } ); double now = cursor->getPath().back().getTuioTime().getTotalMilliseconds(); @@ -113,10 +119,9 @@ bool TouchModule::hasNewInput() { } } -void TouchModule::hasNewWebInput(const std::vector& listOfContactPoints) { - // If one point input and no data in webPosition callback send mouse click to webgui +void TouchModule::processNewWebInput(const std::vector& listOfContactPoints) { bool isWebPositionCallbackZero = - (webPositionCallback.x == 0 && webPositionCallback.y == 0); + (_webPositionCallback.x == 0 && _webPositionCallback.y == 0); bool isSingleContactPoint = (listOfContactPoints.size() == 1); if (isSingleContactPoint && isWebPositionCallbackZero) { glm::ivec2 res = global::windowDelegate.currentWindowSize(); @@ -128,16 +133,16 @@ void TouchModule::hasNewWebInput(const std::vector& listOfContactPoi #ifdef OPENSPACE_MODULE_WEBBROWSER_ENABLED WebBrowserModule& module = *(global::moduleEngine.module()); if (module.eventHandler().hasContentCallback(pos.x, pos.y)) { - webPositionCallback = glm::vec2(pos.x, pos.y); + _webPositionCallback = glm::vec2(pos.x, pos.y); module.eventHandler().touchPressCallback(pos.x, pos.y); } } // Send mouse release if not same point input else if (!isSingleContactPoint && !isWebPositionCallbackZero) { WebBrowserModule& module = *(global::moduleEngine.module()); - module.eventHandler().touchReleaseCallback(webPositionCallback.x, - webPositionCallback.y); - webPositionCallback = glm::vec2(0, 0); + module.eventHandler().touchReleaseCallback(_webPositionCallback.x, + _webPositionCallback.y); + _webPositionCallback = glm::vec2(0, 0); #endif } } @@ -145,43 +150,43 @@ void TouchModule::hasNewWebInput(const std::vector& listOfContactPoi TouchModule::TouchModule() : OpenSpaceModule("Touch") { - addPropertySubOwner(touch); - addPropertySubOwner(markers); + addPropertySubOwner(_touch); + addPropertySubOwner(_markers); global::callback::initializeGL.push_back([&]() { LDEBUGC("TouchModule", "Initializing TouchMarker OpenGL"); - markers.initialize(); + _markers.initialize(); }); global::callback::deinitializeGL.push_back([&]() { LDEBUGC("TouchMarker", "Deinitialize TouchMarker OpenGL"); - markers.deinitialize(); + _markers.deinitialize(); }); global::callback::preSync.push_back([&]() { - touch.setCamera(global::navigationHandler.camera()); - touch.setFocusNode(global::navigationHandler.orbitalNavigator().anchorNode()); + _touch.setCamera(global::navigationHandler.camera()); + _touch.setFocusNode(global::navigationHandler.orbitalNavigator().anchorNode()); - if (hasNewInput() && global::windowDelegate.isMaster()) { - touch.updateStateFromInput(listOfContactPoints, lastProcessed); + if (processNewInput() && global::windowDelegate.isMaster()) { + _touch.updateStateFromInput(_listOfContactPoints, _lastProcessed); } - else if (listOfContactPoints.empty()) { - touch.resetAfterInput(); + else if (_listOfContactPoints.empty()) { + _touch.resetAfterInput(); } // update lastProcessed - lastProcessed.clear(); - for (const TuioCursor& c : listOfContactPoints) { - lastProcessed.emplace_back(c.getSessionID(), c.getPath().back()); + _lastProcessed.clear(); + for (const TuioCursor& c : _listOfContactPoints) { + _lastProcessed.emplace_back(c.getSessionID(), c.getPath().back()); } // used to save data from solver, only calculated for one frame when user chooses // in GUI - touch.unitTest(); + _touch.unitTest(); // calculate the new camera state for this frame - touch.step(global::windowDelegate.deltaTime()); + _touch.step(global::windowDelegate.deltaTime()); }); - global::callback::render.push_back([&]() { markers.render(listOfContactPoints); }); + global::callback::render.push_back([&]() { _markers.render(_listOfContactPoints); }); } diff --git a/modules/touch/touchmodule.h b/modules/touch/touchmodule.h index b916c00318..a596944ab2 100644 --- a/modules/touch/touchmodule.h +++ b/modules/touch/touchmodule.h @@ -41,19 +41,19 @@ namespace openspace { /** * Returns true if new touch input occured since the last frame */ - bool hasNewInput(); + bool processNewInput(); /** * Checks if touchevent should be parsed to the webgui */ - void hasNewWebInput(const std::vector& listOfContactPoints); + void processNewWebInput(const std::vector& listOfContactPoints); - TuioEar ear; - TouchInteraction touch; - TouchMarker markers; - std::vector listOfContactPoints; + TuioEar _ear; + TouchInteraction _touch; + TouchMarker _markers; + std::vector _listOfContactPoints; // contains an id and the TuioPoint that was processed last frame - std::vector lastProcessed; - glm::ivec2 webPositionCallback = glm::ivec2(0,0); + std::vector _lastProcessed; + glm::ivec2 _webPositionCallback = glm::ivec2(0,0); }; } // namespace openspace diff --git a/modules/webbrowser/src/eventhandler.cpp b/modules/webbrowser/src/eventhandler.cpp index 1231178231..c805e22722 100644 --- a/modules/webbrowser/src/eventhandler.cpp +++ b/modules/webbrowser/src/eventhandler.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -225,6 +226,7 @@ bool EventHandler::mouseButtonCallback(MouseButton button, return false; } + global::interactionMonitor.markInteraction(); MouseButtonState& state = (button == MouseButton::Left) ? _leftButton : _rightButton; int clickCount = BrowserInstance::SingleClick; @@ -274,6 +276,8 @@ bool EventHandler::mousePositionCallback(double x, double y) { _mousePosition.x = floor(static_cast(x) * dpiScaling.x); _mousePosition.y = floor(static_cast(y) * dpiScaling.y); _browserInstance->sendMouseMoveEvent(mouseEvent()); + global::interactionMonitor.markInteraction(); + // Let the mouse event trickle on return false; } diff --git a/modules/webgui/webguimodule.cpp b/modules/webgui/webguimodule.cpp index 41a7f4c49f..86d86666d3 100644 --- a/modules/webgui/webguimodule.cpp +++ b/modules/webgui/webguimodule.cpp @@ -246,16 +246,15 @@ void WebGuiModule::startProcess() { std::string formattedDirectories = "["; std::vector directories = _directories.value(); - bool first = true; - - for (const std::string& str : directories) { - if (!first) { + for (size_t i = 0; i < directories.size(); ++i) { + std::string arg = directories[i]; + if (i & 1) { + arg = absPath(arg); + } + formattedDirectories += "\\\"" + escapedJson(escapedJson(arg)) + "\\\""; + if (i != directories.size() - 1) { formattedDirectories += ","; } - first = false; - - // Escape twice: First json, and then bash (which needs same escape sequences) - formattedDirectories += "\\\"" + escapedJson(escapedJson(str)) + "\\\""; } formattedDirectories += "]"; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ddb716d4a6..d6bae69e26 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -44,6 +44,7 @@ set(OPENSPACE_SOURCE ${OPENSPACE_BASE_DIR}/src/engine/syncengine.cpp ${OPENSPACE_BASE_DIR}/src/engine/virtualpropertymanager.cpp ${OPENSPACE_BASE_DIR}/src/interaction/camerainteractionstates.cpp + ${OPENSPACE_BASE_DIR}/src/interaction/interactionmonitor.cpp ${OPENSPACE_BASE_DIR}/src/interaction/inputstate.cpp ${OPENSPACE_BASE_DIR}/src/interaction/joystickinputstate.cpp ${OPENSPACE_BASE_DIR}/src/interaction/joystickcamerastates.cpp @@ -228,6 +229,7 @@ set(OPENSPACE_HEADER ${OPENSPACE_BASE_DIR}/include/openspace/interaction/delayedvariable.inl ${OPENSPACE_BASE_DIR}/include/openspace/interaction/camerainteractionstates.h ${OPENSPACE_BASE_DIR}/include/openspace/interaction/inputstate.h + ${OPENSPACE_BASE_DIR}/include/openspace/interaction/interactionmonitor.h ${OPENSPACE_BASE_DIR}/include/openspace/interaction/interpolator.h ${OPENSPACE_BASE_DIR}/include/openspace/interaction/interpolator.inl ${OPENSPACE_BASE_DIR}/include/openspace/interaction/joystickinputstate.h diff --git a/src/engine/globals.cpp b/src/engine/globals.cpp index eda76862d9..6e0bb08628 100644 --- a/src/engine/globals.cpp +++ b/src/engine/globals.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -150,6 +151,11 @@ configuration::Configuration& gConfiguration() { return g; } +interaction::InteractionMonitor& gInteractionMonitor() { + static interaction::InteractionMonitor g; + return g; +} + interaction::JoystickInputStates& gJoystickInputStates() { static interaction::JoystickInputStates g; return g; @@ -214,6 +220,7 @@ void initialize() { global::navigationHandler.setPropertyOwner(&global::rootPropertyOwner); // New property subowners also have to be added to the ImGuiModule callback! global::rootPropertyOwner.addPropertySubOwner(global::navigationHandler); + global::rootPropertyOwner.addPropertySubOwner(global::interactionMonitor); global::rootPropertyOwner.addPropertySubOwner(global::sessionRecording); global::rootPropertyOwner.addPropertySubOwner(global::timeManager); diff --git a/src/engine/openspaceengine.cpp b/src/engine/openspaceengine.cpp index d62d07937d..5ca2fdfec6 100644 --- a/src/engine/openspaceengine.cpp +++ b/src/engine/openspaceengine.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -1030,6 +1031,7 @@ void OpenSpaceEngine::preSynchronization() { } global::sessionRecording.preSynchronization(); global::parallelPeer.preSynchronization(); + global::interactionMonitor.updateActivityState(); } for (const std::function& func : global::callback::preSync) { @@ -1218,6 +1220,7 @@ void OpenSpaceEngine::keyboardCallback(Key key, KeyModifier mod, KeyAction actio global::navigationHandler.keyboardCallback(key, mod, action); global::keybindingManager.keyboardCallback(key, mod, action); + global::interactionMonitor.markInteraction(); } void OpenSpaceEngine::charCallback(unsigned int codepoint, KeyModifier modifier) { @@ -1230,6 +1233,7 @@ void OpenSpaceEngine::charCallback(unsigned int codepoint, KeyModifier modifier) } global::luaConsole.charCallback(codepoint, modifier); + global::interactionMonitor.markInteraction(); } void OpenSpaceEngine::mouseButtonCallback(MouseButton button, @@ -1265,6 +1269,7 @@ void OpenSpaceEngine::mouseButtonCallback(MouseButton button, } global::navigationHandler.mouseButtonCallback(button, action); + global::interactionMonitor.markInteraction(); } void OpenSpaceEngine::mousePositionCallback(double x, double y) { @@ -1274,6 +1279,7 @@ void OpenSpaceEngine::mousePositionCallback(double x, double y) { } global::navigationHandler.mousePositionCallback(x, y); + global::interactionMonitor.markInteraction(); } void OpenSpaceEngine::mouseScrollWheelCallback(double posX, double posY) { @@ -1286,6 +1292,7 @@ void OpenSpaceEngine::mouseScrollWheelCallback(double posX, double posY) { } global::navigationHandler.mouseScrollWheelCallback(posY); + global::interactionMonitor.markInteraction(); } std::vector OpenSpaceEngine::encode() { diff --git a/src/interaction/interactionmonitor.cpp b/src/interaction/interactionmonitor.cpp new file mode 100644 index 0000000000..8c6afe7617 --- /dev/null +++ b/src/interaction/interactionmonitor.cpp @@ -0,0 +1,81 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2019 * + * * + * 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 + +namespace { + constexpr const char* _loggerCat = "InteractionMonitor"; + + constexpr openspace::properties::Property::PropertyInfo IdleTimeInfo = { + "IdleTime", + "Idle Time", + "Time in seconds that has passed from latest registered interaction until the " + "application goes idle." + }; + constexpr openspace::properties::Property::PropertyInfo IsInActiveStateInfo = { + "IsInActiveState", + "Is State Active", + "Keeps track whether the interaction session is in active state or not. False if " + "application is in idle state, true if it is in active state." + }; +} // namespace + +namespace openspace::interaction { + +InteractionMonitor::InteractionMonitor() + : properties::PropertyOwner({ "InteractionMonitor" }) + , _isInActiveState(IsInActiveStateInfo, false) + , _idleTime(IdleTimeInfo, 120.f) +{ + addProperty(_isInActiveState); + addProperty(_idleTime); +} + +void InteractionMonitor::setActivityState(bool isActive) { + _isInActiveState = isActive; +} + +void InteractionMonitor::setIdleTime(float time) { + _idleTime = time; +} + +void InteractionMonitor::updateActivityState() { + const double currentApplicationTime = global::windowDelegate.applicationTime(); + const double timeDiff = currentApplicationTime - _lastInteractionTime; + + if (timeDiff >= _idleTime && _isInActiveState) { + _isInActiveState = false; + } +} + +void InteractionMonitor::markInteraction() { + _lastInteractionTime = global::windowDelegate.applicationTime(); + _isInActiveState = true; +} + +} // namespace openspace::interaction diff --git a/src/interaction/navigationhandler.cpp b/src/interaction/navigationhandler.cpp index 3cb6361ddf..9c85dbbd3a 100644 --- a/src/interaction/navigationhandler.cpp +++ b/src/interaction/navigationhandler.cpp @@ -337,6 +337,18 @@ void NavigationHandler::keyboardCallback(Key key, KeyModifier modifier, KeyActio _inputState.keyboardCallback(key, modifier, action); } +NavigationHandler::NavigationState NavigationHandler::navigationState() const { + const SceneGraphNode* referenceFrame = _orbitalNavigator.followingAnchorRotation() ? + _orbitalNavigator.anchorNode() : + sceneGraph()->root(); + ghoul_assert( + referenceFrame, + "The root will always exist and the anchor node ought to be reset when removed" + ); + + return navigationState(*referenceFrame); +} + NavigationHandler::NavigationState NavigationHandler::navigationState( const SceneGraphNode& referenceFrame) const { @@ -383,12 +395,9 @@ NavigationHandler::NavigationState NavigationHandler::navigationState( void NavigationHandler::saveNavigationState(const std::string& filepath, const std::string& referenceFrameIdentifier) { - const SceneGraphNode* referenceFrame = _orbitalNavigator.followingAnchorRotation() ? - _orbitalNavigator.anchorNode() : - sceneGraph()->root(); - + NavigationHandler::NavigationState state; if (!referenceFrameIdentifier.empty()) { - referenceFrame = sceneGraphNode(referenceFrameIdentifier); + const SceneGraphNode* referenceFrame = sceneGraphNode(referenceFrameIdentifier); if (!referenceFrame) { LERROR(fmt::format( "Could not find node '{}' to use as reference frame", @@ -396,17 +405,18 @@ void NavigationHandler::saveNavigationState(const std::string& filepath, )); return; } + state = navigationState(*referenceFrame).dictionary(); + } else { + state = navigationState().dictionary(); } if (!filepath.empty()) { std::string absolutePath = absPath(filepath); LINFO(fmt::format("Saving camera position: {}", absolutePath)); - ghoul::Dictionary cameraDict = navigationState(*referenceFrame).dictionary(); ghoul::DictionaryLuaFormatter formatter; - std::ofstream ofs(absolutePath.c_str()); - ofs << "return " << formatter.format(cameraDict); + ofs << "return " << formatter.format(state.dictionary()); ofs.close(); } } @@ -558,13 +568,24 @@ scripting::LuaLibrary NavigationHandler::luaLibrary() { return { "navigation", { + { + "getNavigationState", + &luascriptfunctions::getNavigationState, + {}, + "[string]", + "Return the current navigation state as a lua table. The optional " + "argument is the scene graph node to use as reference frame. By default, " + "the reference frame will picked based on whether the orbital navigator " + "is currently following the anchor node rotation. If it is, the anchor " + "will be chosen as reference frame. If not, the reference frame will be " + "set to the scene graph root." + }, { "setNavigationState", &luascriptfunctions::setNavigationState, {}, "table", - "Set the navigation state. " - "The argument must be a valid Navigation State." + "Set the navigation state. The argument must be a valid Navigation State." }, { "saveNavigationState", @@ -642,7 +663,7 @@ scripting::LuaLibrary NavigationHandler::luaLibrary() { "locally or remotely. The latter being the default." }, { - "clearJoystickButotn", + "clearJoystickButton", &luascriptfunctions::clearJoystickButton, {}, "int", diff --git a/src/interaction/navigationhandler_lua.inl b/src/interaction/navigationhandler_lua.inl index 6ca1baec8a..c7fe94143d 100644 --- a/src/interaction/navigationhandler_lua.inl +++ b/src/interaction/navigationhandler_lua.inl @@ -46,6 +46,77 @@ int loadNavigationState(lua_State* L) { return 0; } +int getNavigationState(lua_State* L) { + const int n = ghoul::lua::checkArgumentsAndThrow( + L, + { 0, 1 }, + "lua::getNavigationState" + ); + + interaction::NavigationHandler::NavigationState state; + if (n == 1) { + const std::string referenceFrameIdentifier = ghoul::lua::value(L, 1); + const SceneGraphNode* referenceFrame = sceneGraphNode(referenceFrameIdentifier); + if (!referenceFrame) { + LERROR(fmt::format( + "Could not find node '{}' to use as reference frame", + referenceFrameIdentifier + )); + lua_settop(L, 0); + return 0; + } + state = global::navigationHandler.navigationState(*referenceFrame); + } + else { + state = global::navigationHandler.navigationState(); + } + + lua_settop(L, 0); + + const auto pushVector = [](lua_State* L, const glm::dvec3& v) { + lua_newtable(L); + ghoul::lua::push(L, 1, v.x); + lua_rawset(L, -3); + ghoul::lua::push(L, 2, v.y); + lua_rawset(L, -3); + ghoul::lua::push(L, 3, v.z); + lua_rawset(L, -3); + }; + + lua_newtable(L); + ghoul::lua::push(L, "Anchor", state.anchor); + lua_rawset(L, -3); + + if (!state.aim.empty()) { + ghoul::lua::push(L, "Aim", state.aim); + lua_rawset(L, -3); + } + if (!state.referenceFrame.empty()) { + ghoul::lua::push(L, "ReferenceFrame", state.referenceFrame); + lua_rawset(L, -3); + } + ghoul::lua::push(L, "Position"); + pushVector(L, state.position); + lua_rawset(L, -3); + + if (state.up.has_value()) { + ghoul::lua::push(L, "Up"); + pushVector(L, state.up.value()); + lua_rawset(L, -3); + } + if (state.yaw != 0) { + ghoul::lua::push(L, "Yaw", state.yaw); + lua_rawset(L, -3); + } + if (state.pitch != 0) { + ghoul::lua::push(L, "Pitch", state.pitch); + lua_rawset(L, -3); + } + + ghoul_assert(lua_gettop(L) == 1, "Incorrect number of items left on stack"); + return 1; +} + int setNavigationState(lua_State* L) { ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::setNavigationState"); diff --git a/src/interaction/sessionrecording.cpp b/src/interaction/sessionrecording.cpp index b46206e6d7..c0c7a22674 100644 --- a/src/interaction/sessionrecording.cpp +++ b/src/interaction/sessionrecording.cpp @@ -1033,7 +1033,7 @@ void SessionRecording::moveAheadInTime() { const Renderable* focusRenderable = focusNode->renderable(); if (!focusRenderable || focusRenderable->renderedWithDesiredData()) { _saveRenderingCurrentRecordedTime += _saveRenderingDeltaTime; - global::renderEngine.takeScreenShot(); + global::renderEngine.takeScreenshot(); } } } diff --git a/src/rendering/framebufferrenderer.cpp b/src/rendering/framebufferrenderer.cpp index b6ce5df96e..39a85fe1fd 100644 --- a/src/rendering/framebufferrenderer.cpp +++ b/src/rendering/framebufferrenderer.cpp @@ -439,10 +439,16 @@ void FramebufferRenderer::applyTMO(float blackoutFactor) { _hdrFilteringProgram->setUniform(_hdrUniformCache.Saturation, _saturation); _hdrFilteringProgram->setUniform(_hdrUniformCache.Value, _value); + glDepthMask(false); + glDisable(GL_DEPTH_TEST); + glBindVertexArray(_screenQuad); glDrawArrays(GL_TRIANGLES, 0, 6); glBindVertexArray(0); + glDepthMask(true); + glEnable(GL_DEPTH_TEST); + _hdrFilteringProgram->deactivate(); } @@ -473,10 +479,16 @@ void FramebufferRenderer::applyFXAA() { glm::vec2 inverseScreenSize(1.f/_resolution.x, 1.f/_resolution.y); _fxaaProgram->setUniform(_fxaaUniformCache.inverseScreenSize, inverseScreenSize); + glDepthMask(false); + glDisable(GL_DEPTH_TEST); + glBindVertexArray(_screenQuad); glDrawArrays(GL_TRIANGLES, 0, 6); glBindVertexArray(0); + glDepthMask(true); + glEnable(GL_DEPTH_TEST); + _fxaaProgram->deactivate(); } @@ -760,7 +772,7 @@ void FramebufferRenderer::updateResolution() { _resolution.y, 0, GL_RGBA, - GL_BYTE, + GL_UNSIGNED_BYTE, nullptr ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -1049,7 +1061,7 @@ void FramebufferRenderer::render(Scene* scene, Camera* camera, float blackoutFac if (doPerformanceMeasurements) { perfInternal = std::make_unique( "FramebufferRenderer::render::deferredTasks" - ); + ); } performDeferredTasks(tasks.deferredcasterTasks); } @@ -1067,6 +1079,8 @@ void FramebufferRenderer::render(Scene* scene, Camera* camera, float blackoutFac if (_enableFXAA) { glBindFramebuffer(GL_FRAMEBUFFER, _fxaaBuffers.fxaaFramebuffer); + glDrawBuffers(1, ColorAttachment0Array); + glDisable(GL_BLEND); } else { // When applying the TMO, the result is saved to the default FBO to be displayed diff --git a/src/rendering/renderengine.cpp b/src/rendering/renderengine.cpp index 212e30ea12..918f5638f2 100644 --- a/src/rendering/renderengine.cpp +++ b/src/rendering/renderengine.cpp @@ -124,16 +124,6 @@ namespace { "shown on the screen" }; - constexpr openspace::properties::Property::PropertyInfo TakeScreenshotInfo = { - "TakeScreenshot", - "Take Screenshot", - "If this property is triggered, a screenshot is taken and stored in the current " - "working directory (which is the same directory where the OpenSpace.exe) is " - "located in most cases. The images are prefixed with 'SGCT' and postfixed with " - "the number of frames. This function will silently overwrite images that are " - "already present in the folder." - }; - constexpr openspace::properties::Property::PropertyInfo ApplyWarpingInfo = { "ApplyWarpingScreenshot", "Apply Warping to Screenshots", @@ -264,7 +254,6 @@ RenderEngine::RenderEngine() , _showLog(ShowLogInfo, true) , _showVersionInfo(ShowVersionInfo, true) , _showCameraInfo(ShowCameraInfo, true) - , _takeScreenshot(TakeScreenshotInfo) , _applyWarping(ApplyWarpingInfo, false) , _showFrameInformation(ShowFrameNumberInfo, false) #ifdef OPENSPACE_WITH_INSTRUMENTATION @@ -379,9 +368,6 @@ RenderEngine::RenderEngine() }); addProperty(_horizFieldOfView); - _takeScreenshot.onChange([this](){ _shouldTakeScreenshot = true; }); - addProperty(_takeScreenshot); - addProperty(_showFrameInformation); #ifdef OPENSPACE_WITH_INSTRUMENTATION _saveFrameInformation.onChange([&]() { @@ -864,21 +850,6 @@ void RenderEngine::renderDashboard() { void RenderEngine::postDraw() { ++_frameNumber; - if (_shouldTakeScreenshot) { - // We only create the directory here, as we don't want to spam the users - // screenshot folder everytime we start OpenSpace even when we are not taking any - // screenshots. So the first time we actually take one, we create the folder: - if (!FileSys.directoryExists(absPath("${SCREENSHOTS}"))) { - FileSys.createDirectory( - absPath("${SCREENSHOTS}"), - ghoul::filesystem::FileSystem::Recursive::Yes - ); - } - - global::windowDelegate.takeScreenshot(_applyWarping); - _shouldTakeScreenshot = false; - } - if (global::performanceManager.isEnabled()) { global::performanceManager.storeScenePerformanceMeasurements( scene()->allSceneGraphNodes() @@ -1061,10 +1032,28 @@ void RenderEngine::setResolveData(ghoul::Dictionary resolveData) { } /** - * Mark that one screenshot should be taken -*/ -void RenderEngine::takeScreenShot() { - _shouldTakeScreenshot = true; + * Take a screenshot and store it in the ${SCREENSHOTS} directory + */ +void RenderEngine::takeScreenshot() { + // We only create the directory here, as we don't want to spam the users + // screenshot folder everytime we start OpenSpace even when we are not taking any + // screenshots. So the first time we actually take one, we create the folder: + + if (!FileSys.directoryExists(absPath("${SCREENSHOTS}"))) { + FileSys.createDirectory( + absPath("${SCREENSHOTS}"), + ghoul::filesystem::FileSystem::Recursive::Yes + ); + } + + _latestScreenshotNumber = global::windowDelegate.takeScreenshot(_applyWarping); +} + +/** + * Get the latest screenshot filename + */ +unsigned int RenderEngine::latestScreenshotNumber() const { + return _latestScreenshotNumber; } /** @@ -1123,6 +1112,14 @@ scripting::LuaLibrary RenderEngine::luaLibrary() { "Given a ScreenSpaceRenderable name this script will remove it from the " "renderengine" }, + { + "takeScreenshot", + &luascriptfunctions::takeScreenshot, + {}, + "", + "Take a screenshot and return the screenshot number. The screenshot will " + "be stored in the ${SCREENSHOTS} folder. " + } }, }; } diff --git a/src/rendering/renderengine_lua.inl b/src/rendering/renderengine_lua.inl index 3222c6b6e3..932e861a50 100644 --- a/src/rendering/renderengine_lua.inl +++ b/src/rendering/renderengine_lua.inl @@ -89,4 +89,14 @@ int removeScreenSpaceRenderable(lua_State* L) { return 0; } +int takeScreenshot(lua_State* L) { + ghoul::lua::checkArgumentsAndThrow(L, 0, "lua::takeScreenshot"); + + global::renderEngine.takeScreenshot(); + const unsigned int screenshotNumber = global::renderEngine.latestScreenshotNumber(); + + lua_pushinteger(L, screenshotNumber); + return 1; +} + }// namespace openspace::luascriptfunctions